Remove data-directory markdown flow and complete SQLCipher backend cleanup

This commit is contained in:
Jacob Schmidt 2026-02-28 17:31:53 -06:00
parent 72f8221605
commit 9e92619fc2
12 changed files with 115 additions and 142 deletions

View File

@ -1,20 +1,20 @@
namespace Journal.Core.Dtos;
internal sealed record VaultInitializePayload(string Password, string VaultDirectory);
internal sealed record VaultPayload(string Password, string VaultDirectory, string DataDirectory, string? NowUtc = null);
internal sealed record ClearDataPayload(string DataDirectory);
internal sealed record EntryListPayload(string? DataDirectory = null);
internal sealed record VaultPayload(string Password, string VaultDirectory, string? NowUtc = null);
internal sealed record ClearDataPayload();
internal sealed record EntryListPayload();
internal sealed record EntryLoadPayload(string FilePath);
public sealed record EntrySavePayload(string Content, string? FilePath = null, string? Mode = null, string? FileName = null);
public sealed record EntryListItem(string FileName, string FilePath);
public sealed record EntryLoadResult(string FileName, string FilePath, JournalEntryDto Entry);
public sealed record EntrySaveResult(string FilePath);
internal sealed record EntryDeletePayload(string FilePath);
internal sealed record EntryTemplateListPayload(string? DataDirectory = null);
internal sealed record EntryTemplateListPayload();
internal sealed record EntryTemplateLoadPayload(string FilePath);
internal sealed record EntryTemplateDeletePayload(string FilePath);
public sealed record EntryTemplateLoadResult(string FileName, string FilePath, string Content);
public sealed record EntryTemplateSavePayload(string Name, string Content, string? FilePath = null, string? DataDirectory = null);
public sealed record EntryTemplateSavePayload(string Name, string Content, string? FilePath = null);
internal sealed record DatabasePayload(string Password, string? DataDirectory = null);
internal sealed record AiSummarizeEntryPayload(string Content, string? FileStem = null);
internal sealed record AiSummarizeAllPayload(List<string>? Entries);
@ -30,7 +30,6 @@ internal sealed record SpeechTranscribePayload(
int? SimulateDelayMs = null,
int? Simulate_Delay_Ms = null);
internal sealed record SearchEntriesPayload(
string DataDirectory,
string? Query = null,
string? Section = null,
string? StartDate = null,

View File

@ -1,7 +1,6 @@
namespace Journal.Core.Dtos;
public sealed record EntrySearchRequestDto(
string DataDirectory,
string? Query = null,
string? Section = null,
string? StartDate = null,

View File

@ -214,10 +214,9 @@ public class Entry(
break;
case "search.entries":
var searchPayload = DeserializePayload<SearchEntriesPayload>(cmd.Payload);
if (searchPayload is null || string.IsNullOrWhiteSpace(searchPayload.DataDirectory))
if (searchPayload is null)
return Error("Missing or invalid payload");
var searchRequest = new EntrySearchRequestDto(
DataDirectory: searchPayload.DataDirectory,
Query: searchPayload.Query,
Section: searchPayload.Section,
StartDate: searchPayload.StartDate,
@ -229,18 +228,12 @@ public class Entry(
result = await _entrySearch.SearchEntriesAsync(searchRequest);
break;
case "entries.list":
var listPayload = DeserializePayload<EntryListPayload>(cmd.Payload);
var listDataDirectory = !string.IsNullOrWhiteSpace(listPayload?.DataDirectory)
? listPayload.DataDirectory
: _config.Current.DataDirectory;
result = _entryFiles.ListEntries(listDataDirectory);
_ = DeserializePayload<EntryListPayload>(cmd.Payload);
result = _entryFiles.ListEntries();
break;
case "templates.list":
var templateListPayload = DeserializePayload<EntryTemplateListPayload>(cmd.Payload);
var templateListDirectory = !string.IsNullOrWhiteSpace(templateListPayload?.DataDirectory)
? templateListPayload.DataDirectory
: _config.Current.DataDirectory;
result = _entryFiles.ListTemplates(templateListDirectory);
_ = DeserializePayload<EntryTemplateListPayload>(cmd.Payload);
result = _entryFiles.ListTemplates();
break;
case "entries.load":
var loadEntryPayload = DeserializePayload<EntryLoadPayload>(cmd.Payload);
@ -258,13 +251,13 @@ public class Entry(
var saveEntryPayload = DeserializePayload<EntrySavePayload>(cmd.Payload);
if (saveEntryPayload is null || string.IsNullOrWhiteSpace(saveEntryPayload.Content))
return Error("Missing or invalid payload");
result = _entryFiles.SaveEntry(saveEntryPayload, _config.Current.DataDirectory);
result = _entryFiles.SaveEntry(saveEntryPayload);
break;
case "templates.save":
var saveTemplatePayload = DeserializePayload<EntryTemplateSavePayload>(cmd.Payload);
if (saveTemplatePayload is null || string.IsNullOrWhiteSpace(saveTemplatePayload.Name))
return Error("Missing or invalid payload");
result = _entryFiles.SaveTemplate(saveTemplatePayload, _config.Current.DataDirectory);
result = _entryFiles.SaveTemplate(saveTemplatePayload);
break;
case "entries.delete":
var deleteEntryPayload = DeserializePayload<EntryDeletePayload>(cmd.Payload);
@ -343,9 +336,10 @@ public class Entry(
var loadPayload = DeserializePayload<VaultPayload>(cmd.Payload);
if (loadPayload is null)
return Error("Missing or invalid payload");
var loaded = _vaultStorage.LoadAllVaults(loadPayload.Password, loadPayload.VaultDirectory, loadPayload.DataDirectory);
var vaultStorageDirectory = ResolveVaultStorageDirectory();
var loaded = _vaultStorage.LoadAllVaults(loadPayload.Password, loadPayload.VaultDirectory, vaultStorageDirectory);
if (loaded)
_databaseSession.SetPassword(loadPayload.Password, loadPayload.DataDirectory);
_databaseSession.SetPassword(loadPayload.Password);
result = loaded;
break;
case "vault.save_current_month":
@ -355,7 +349,7 @@ public class Entry(
result = _vaultStorage.SaveCurrentMonthVault(
saveCurrentPayload.Password,
saveCurrentPayload.VaultDirectory,
saveCurrentPayload.DataDirectory,
ResolveVaultStorageDirectory(),
ParseNowOrDefault(saveCurrentPayload.NowUtc));
break;
case "vault.rebuild_all":
@ -363,16 +357,16 @@ public class Entry(
if (rebuildPayload is null)
return Error("Missing or invalid payload");
_databaseSession.CloseConnection();
_vaultStorage.RebuildAllVaults(rebuildPayload.Password, rebuildPayload.VaultDirectory, rebuildPayload.DataDirectory);
_vaultStorage.RebuildAllVaults(rebuildPayload.Password, rebuildPayload.VaultDirectory, ResolveVaultStorageDirectory());
result = true;
break;
case "vault.clear_data_directory":
var clearPayload = DeserializePayload<ClearDataPayload>(cmd.Payload);
if (clearPayload is null || string.IsNullOrWhiteSpace(clearPayload.DataDirectory))
if (clearPayload is null)
return Error("Missing or invalid payload");
if (_databaseSession is IDisposable disposableSession)
disposableSession.Dispose();
_vaultStorage.ClearDataDirectory(clearPayload.DataDirectory);
_vaultStorage.ClearDataDirectory(ResolveVaultStorageDirectory());
result = true;
break;
case "db.status":
@ -393,7 +387,7 @@ public class Entry(
if (dbHydratePayload is null || string.IsNullOrWhiteSpace(dbHydratePayload.Password))
return Error("Missing or invalid payload");
result = _database.HydrateWorkspace(dbHydratePayload.Password, dbHydratePayload.DataDirectory);
_databaseSession.SetPassword(dbHydratePayload.Password, dbHydratePayload.DataDirectory);
_databaseSession.SetPassword(dbHydratePayload.Password);
break;
default:
CommandLogger.LogFailure(action, correlationId, "unknown_action");
@ -473,22 +467,27 @@ public class Entry(
if (!VaultSyncActions.Contains(action))
return;
if (!_databaseSession.TryGetSession(out var password, out var sessionDataDirectory))
if (!_databaseSession.TryGetSession(out var password, out _))
return;
try
{
var config = _config.Current;
var dataDirectory = string.IsNullOrWhiteSpace(sessionDataDirectory)
? config.DataDirectory
: sessionDataDirectory;
_databaseSession.CloseConnection();
_vaultStorage.RebuildAllVaults(password, config.VaultDirectory, dataDirectory);
_vaultStorage.RebuildAllVaults(password, config.VaultDirectory, ResolveVaultStorageDirectory());
}
catch (Exception ex)
{
CommandLogger.LogFailure(action, correlationId, "vault_auto_sync_failed", ex.Message);
}
}
private string ResolveVaultStorageDirectory()
{
var dbPath = _database.GetDatabasePath();
var directory = Path.GetDirectoryName(dbPath);
return string.IsNullOrWhiteSpace(directory)
? Path.GetFullPath(".")
: Path.GetFullPath(directory);
}
}

View File

@ -1,35 +0,0 @@
namespace Journal.Core.Repositories;
public sealed class DiskEntryFileRepository : IEntryFileRepository
{
public IReadOnlyList<string> ListMarkdownFiles(string dataDirectory)
{
if (!Directory.Exists(dataDirectory))
return [];
return [.. Directory.GetFiles(dataDirectory, "*.md").OrderBy(Path.GetFileName, StringComparer.Ordinal)];
}
public string ReadFile(string filePath) => File.ReadAllText(filePath);
public void WriteFile(string filePath, string content) => File.WriteAllText(filePath, content);
public void AppendFile(string filePath, string content) => File.AppendAllText(filePath, content);
public bool FileExists(string filePath) => File.Exists(filePath);
public string GetFullPath(string filePath) => Path.GetFullPath(filePath);
public string GetFileName(string filePath) => Path.GetFileName(filePath);
public string GetFileNameWithoutExtension(string filePath) => Path.GetFileNameWithoutExtension(filePath);
public void EnsureDirectory(string path)
{
var dir = Path.GetDirectoryName(path);
if (!string.IsNullOrWhiteSpace(dir))
Directory.CreateDirectory(dir);
}
public void DeleteFile(string filePath) => File.Delete(filePath);
}

View File

@ -2,7 +2,7 @@ namespace Journal.Core.Repositories;
public interface IEntryFileRepository
{
IReadOnlyList<string> ListMarkdownFiles(string dataDirectory);
IReadOnlyList<string> ListMarkdownFiles();
string ReadFile(string filePath);
void WriteFile(string filePath, string content);
void AppendFile(string filePath, string content);

View File

@ -9,7 +9,7 @@ public sealed class SqliteEntryFileRepository(IDatabaseSessionService session) :
private const string TemplatePrefix = "db://template/";
private readonly IDatabaseSessionService _session = session;
public IReadOnlyList<string> ListMarkdownFiles(string dataDirectory)
public IReadOnlyList<string> ListMarkdownFiles()
{
var conn = _session.GetConnection();
using var cmd = conn.CreateCommand();

View File

@ -20,9 +20,7 @@ public sealed class JournalDatabaseService(IJournalConfigService config) : IJour
public string GetDatabasePath(string? dataDirectory = null)
{
var directory = string.IsNullOrWhiteSpace(dataDirectory)
? _config.Current.DataDirectory
: dataDirectory;
var directory = ResolveDatabaseDirectory();
Directory.CreateDirectory(directory);
return Path.GetFullPath(Path.Combine(directory, _config.Current.DatabaseFilename));
@ -136,9 +134,7 @@ public sealed class JournalDatabaseService(IJournalConfigService config) : IJour
public string WriteSchemaBootstrap(string? dataDirectory = null)
{
var directory = string.IsNullOrWhiteSpace(dataDirectory)
? _config.Current.DataDirectory
: dataDirectory;
var directory = ResolveDatabaseDirectory();
Directory.CreateDirectory(directory);
var bootstrapPath = Path.GetFullPath(Path.Combine(directory, "journal_schema.sql"));
@ -168,9 +164,7 @@ public sealed class JournalDatabaseService(IJournalConfigService config) : IJour
public JournalDatabaseHydrationResult HydrateWorkspace(string password, string? dataDirectory = null)
{
var directory = string.IsNullOrWhiteSpace(dataDirectory)
? _config.Current.DataDirectory
: dataDirectory;
var directory = ResolveDatabaseDirectory();
Directory.CreateDirectory(directory);
using var connection = OpenEncryptedConnection(password, directory);
@ -267,4 +261,13 @@ public sealed class JournalDatabaseService(IJournalConfigService config) : IJour
return (false, $"SQLCipher runtime check failed: {ex.Message}");
}
}
private string ResolveDatabaseDirectory()
{
var overrideDir = Environment.GetEnvironmentVariable("JOURNAL_DATABASE_DIR");
if (!string.IsNullOrWhiteSpace(overrideDir))
return Path.GetFullPath(overrideDir);
return Path.GetFullPath(Path.Combine(_config.Current.VaultDirectory, "db"));
}
}

View File

@ -7,18 +7,18 @@ public sealed class EntryFileService(IEntryFileRepository repo) : IEntryFileServ
{
private readonly IEntryFileRepository _repo = repo ?? throw new ArgumentNullException(nameof(repo));
public IReadOnlyList<EntryListItem> ListEntries(string dataDirectory)
public IReadOnlyList<EntryListItem> ListEntries()
{
return [.. _repo.ListMarkdownFiles(dataDirectory)
return [.. _repo.ListMarkdownFiles()
.Where(path => !EntryFileNaming.IsTemplateFileName(_repo.GetFileName(path)))
.Select(path => new EntryListItem(
FileName: _repo.GetFileName(path),
FilePath: _repo.GetFullPath(path)))];
}
public IReadOnlyList<EntryListItem> ListTemplates(string dataDirectory)
public IReadOnlyList<EntryListItem> ListTemplates()
{
return [.. _repo.ListMarkdownFiles(dataDirectory)
return [.. _repo.ListMarkdownFiles()
.Where(path => EntryFileNaming.IsTemplateFileName(_repo.GetFileName(path)))
.Select(path => new EntryListItem(
FileName: _repo.GetFileName(path),
@ -55,9 +55,9 @@ public sealed class EntryFileService(IEntryFileRepository repo) : IEntryFileServ
return new EntryTemplateLoadResult(fileName, normalizedPath, rawContent);
}
public EntrySaveResult SaveEntry(EntrySavePayload payload, string defaultDataDirectory)
public EntrySaveResult SaveEntry(EntrySavePayload payload)
{
var targetPath = ResolveTargetPath(payload.FilePath, payload.FileName, defaultDataDirectory);
var targetPath = ResolveTargetPath(payload.FilePath, payload.FileName);
var mode = string.IsNullOrWhiteSpace(payload.Mode) ? "Daily" : payload.Mode.Trim();
var sanitizedContent = HtmlSanitizer.StripRichHtml(payload.Content ?? "");
_repo.EnsureDirectory(targetPath);
@ -93,16 +93,13 @@ public sealed class EntryFileService(IEntryFileRepository repo) : IEntryFileServ
return new EntrySaveResult(targetPath);
}
public EntrySaveResult SaveTemplate(EntryTemplateSavePayload payload, string defaultDataDirectory)
public EntrySaveResult SaveTemplate(EntryTemplateSavePayload payload)
{
ArgumentNullException.ThrowIfNull(payload);
if (string.IsNullOrWhiteSpace(payload.Name))
throw new ArgumentException("Template name is required.");
var directory = string.IsNullOrWhiteSpace(payload.DataDirectory)
? defaultDataDirectory
: payload.DataDirectory;
var targetPath = ResolveTemplatePath(payload.FilePath, payload.Name, directory);
var targetPath = ResolveTemplatePath(payload.FilePath, payload.Name);
var fileName = _repo.GetFileName(targetPath);
if (!EntryFileNaming.IsTemplateFileName(fileName))
throw new ArgumentException("Template file name must end with .template.md.");
@ -136,7 +133,7 @@ public sealed class EntryFileService(IEntryFileRepository repo) : IEntryFileServ
return true;
}
private string ResolveTargetPath(string? filePath, string? fileName, string defaultDataDirectory)
private string ResolveTargetPath(string? filePath, string? fileName)
{
if (!string.IsNullOrWhiteSpace(filePath))
return _repo.GetFullPath(filePath);
@ -145,16 +142,16 @@ public sealed class EntryFileService(IEntryFileRepository repo) : IEntryFileServ
? SanitizeFileName(fileName)
: $"{DateTime.Now:yyyy-MM-dd}";
return _repo.GetFullPath(Path.Combine(defaultDataDirectory, $"{name}.md"));
return _repo.GetFullPath($"{name}.md");
}
private string ResolveTemplatePath(string? filePath, string templateName, string defaultDataDirectory)
private string ResolveTemplatePath(string? filePath, string templateName)
{
if (!string.IsNullOrWhiteSpace(filePath))
return _repo.GetFullPath(filePath);
var name = SanitizeFileName(templateName);
return _repo.GetFullPath(Path.Combine(defaultDataDirectory, $"{name}{EntryFileNaming.TemplateSuffix}"));
return _repo.GetFullPath($"{name}{EntryFileNaming.TemplateSuffix}");
}
private static string SanitizeFileName(string name)

View File

@ -15,8 +15,6 @@ public class EntrySearchService(IEntryFileRepository repo) : IEntrySearchService
public Task<IReadOnlyList<EntrySearchResultDto>> SearchEntriesAsync(EntrySearchRequestDto request)
{
ArgumentNullException.ThrowIfNull(request);
if (string.IsNullOrWhiteSpace(request.DataDirectory))
throw new ArgumentException("Data directory is required.", nameof(request.DataDirectory));
var hasQuery = !string.IsNullOrWhiteSpace(request.Query);
var query = request.Query?.Trim() ?? "";
@ -35,7 +33,7 @@ public class EntrySearchService(IEntryFileRepository repo) : IEntrySearchService
if (startDate.HasValue && endDate.HasValue && startDate.Value > endDate.Value)
throw new ArgumentException("startDate cannot be after endDate.");
var currentFiles = _repo.ListMarkdownFiles(request.DataDirectory)
var currentFiles = _repo.ListMarkdownFiles()
.OrderBy(Path.GetFileName, StringComparer.Ordinal)
.ToArray();

View File

@ -4,12 +4,12 @@ namespace Journal.Core.Services.Entries;
public interface IEntryFileService
{
IReadOnlyList<EntryListItem> ListEntries(string dataDirectory);
IReadOnlyList<EntryListItem> ListTemplates(string dataDirectory);
IReadOnlyList<EntryListItem> ListEntries();
IReadOnlyList<EntryListItem> ListTemplates();
EntryLoadResult LoadEntry(string filePath);
EntryTemplateLoadResult LoadTemplate(string filePath);
EntrySaveResult SaveEntry(EntrySavePayload payload, string defaultDataDirectory);
EntrySaveResult SaveTemplate(EntryTemplateSavePayload payload, string defaultDataDirectory);
EntrySaveResult SaveEntry(EntrySavePayload payload);
EntrySaveResult SaveTemplate(EntryTemplateSavePayload payload);
bool DeleteEntry(string filePath);
bool DeleteTemplate(string filePath);
}

View File

@ -6,11 +6,12 @@ using Journal.Core.Services.Vault;
namespace Journal.Core.Services.Sidecar;
public sealed class SidecarCli(IVaultStorageService vaultStorage, IEntrySearchService entrySearch, IJournalConfigService config)
public sealed class SidecarCli(IVaultStorageService vaultStorage, IEntrySearchService entrySearch, IJournalConfigService config, IEntryFileService entryFiles)
{
private readonly IVaultStorageService _vaultStorage = vaultStorage;
private readonly IEntrySearchService _entrySearch = entrySearch;
private readonly IJournalConfigService _config = config;
private readonly IEntryFileService _entryFiles = entryFiles;
public async Task<int> RunAsync(string[] args, Entry entry)
{
@ -73,25 +74,25 @@ public sealed class SidecarCli(IVaultStorageService vaultStorage, IEntrySearchSe
return 2;
}
var (vaultDirectory, dataDirectory) = ResolveDirectories(options.VaultDirectory, options.DataDirectory);
var vaultDirectory = ResolveVaultDirectory(options.VaultDirectory);
try
{
if (action == "load")
{
var ok = _vaultStorage.LoadAllVaults(password, vaultDirectory, dataDirectory);
var ok = _vaultStorage.LoadAllVaults(password, vaultDirectory, ResolveVaultStorageDirectory());
if (!ok)
{
Console.Error.WriteLine("Incorrect password.");
return 1;
}
Console.WriteLine($"Vault loaded. Decrypted files are in {dataDirectory}");
Console.WriteLine("Vault loaded.");
return 0;
}
_vaultStorage.RebuildAllVaults(password, vaultDirectory, dataDirectory);
Console.WriteLine($"Vault saved from decrypted files in {dataDirectory}");
_vaultStorage.RebuildAllVaults(password, vaultDirectory, ResolveVaultStorageDirectory());
Console.WriteLine("Vault saved.");
return 0;
}
catch (Exception ex)
@ -117,21 +118,16 @@ public sealed class SidecarCli(IVaultStorageService vaultStorage, IEntrySearchSe
return 2;
}
var (_, dataDirectory) = ResolveDirectories(vaultOverride: null, options.DataDirectory);
var entryCount = Directory.Exists(dataDirectory)
? Directory.GetFiles(dataDirectory, "*.md")
.Count(path => !EntryFileNaming.IsTemplateFileName(Path.GetFileName(path)))
: 0;
var entryCount = _entryFiles.ListEntries().Count;
if (entryCount == 0)
{
Console.WriteLine("No decrypted journal entries found. Please load the vault first: journal vault load");
Console.WriteLine("No journal entries found. Please load the vault first: journal vault load");
return 0;
}
try
{
var request = new EntrySearchRequestDto(
DataDirectory: dataDirectory,
Query: options.Query,
Section: options.Section,
StartDate: options.StartDate,
@ -194,9 +190,6 @@ public sealed class SidecarCli(IVaultStorageService vaultStorage, IEntrySearchSe
case "--vault-dir":
parsed.VaultDirectory = value;
break;
case "--data-dir":
parsed.DataDirectory = value;
break;
default:
options = parsed;
error = $"Unknown option '{token}'.";
@ -247,9 +240,6 @@ public sealed class SidecarCli(IVaultStorageService vaultStorage, IEntrySearchSe
var value = args[i + 1];
switch (token)
{
case "--data-dir":
parsed.DataDirectory = value;
break;
case "--tag":
case "-t":
parsed.Tags.Add(value);
@ -292,15 +282,22 @@ public sealed class SidecarCli(IVaultStorageService vaultStorage, IEntrySearchSe
return true;
}
private (string VaultDirectory, string DataDirectory) ResolveDirectories(string? vaultOverride, string? dataOverride)
private string ResolveVaultDirectory(string? vaultOverride)
{
var envVault = Environment.GetEnvironmentVariable("JOURNAL_VAULT_DIR");
var envData = Environment.GetEnvironmentVariable("JOURNAL_DATA_DIR");
var defaults = _config.Current;
var vault = FirstNonEmpty(vaultOverride, envVault) ?? defaults.VaultDirectory;
var data = FirstNonEmpty(dataOverride, envData) ?? defaults.DataDirectory;
return (Path.GetFullPath(vault), Path.GetFullPath(data));
return Path.GetFullPath(vault);
}
private string ResolveVaultStorageDirectory()
{
var dbDirOverride = Environment.GetEnvironmentVariable("JOURNAL_DATABASE_DIR");
if (!string.IsNullOrWhiteSpace(dbDirOverride))
return Path.GetFullPath(dbDirOverride);
return Path.GetFullPath(Path.Combine(_config.Current.VaultDirectory, "db"));
}
private static string? FirstNonEmpty(params string?[] values) =>
@ -345,35 +342,33 @@ public sealed class SidecarCli(IVaultStorageService vaultStorage, IEntrySearchSe
{
Console.WriteLine("Usage:");
Console.WriteLine(" Journal.Sidecar # sidecar stdin/stdout mode");
Console.WriteLine(" Journal.Sidecar vault load [--password <value>] [--vault-dir <path>] [--data-dir <path>]");
Console.WriteLine(" Journal.Sidecar vault save [--password <value>] [--vault-dir <path>] [--data-dir <path>]");
Console.WriteLine(" Journal.Sidecar search [query] [--tag <value>] [--type <value>] [--start-date <yyyy-MM-dd>] [--end-date <yyyy-MM-dd>] [--section <title>] [--checked <text>] [--unchecked <text>] [--data-dir <path>]");
Console.WriteLine(" Journal.Sidecar vault load [--password <value>] [--vault-dir <path>]");
Console.WriteLine(" Journal.Sidecar vault save [--password <value>] [--vault-dir <path>]");
Console.WriteLine(" Journal.Sidecar search [query] [--tag <value>] [--type <value>] [--start-date <yyyy-MM-dd>] [--end-date <yyyy-MM-dd>] [--section <title>] [--checked <text>] [--unchecked <text>]");
}
private static void PrintVaultUsage()
{
Console.WriteLine("Vault usage:");
Console.WriteLine(" Journal.Sidecar vault load [--password <value>] [--vault-dir <path>] [--data-dir <path>]");
Console.WriteLine(" Journal.Sidecar vault save [--password <value>] [--vault-dir <path>] [--data-dir <path>]");
Console.WriteLine(" Journal.Sidecar vault load [--password <value>] [--vault-dir <path>]");
Console.WriteLine(" Journal.Sidecar vault save [--password <value>] [--vault-dir <path>]");
}
private static void PrintSearchUsage()
{
Console.WriteLine("Search usage:");
Console.WriteLine(" Journal.Sidecar search [query] [--tag <value>] [--type <value>] [--start-date <yyyy-MM-dd>] [--end-date <yyyy-MM-dd>] [--section <title>] [--checked <text>] [--unchecked <text>] [--data-dir <path>]");
Console.WriteLine(" Journal.Sidecar search [query] [--tag <value>] [--type <value>] [--start-date <yyyy-MM-dd>] [--end-date <yyyy-MM-dd>] [--section <title>] [--checked <text>] [--unchecked <text>]");
}
private sealed class VaultOptions
{
public string? Password { get; set; }
public string? VaultDirectory { get; set; }
public string? DataDirectory { get; set; }
}
private sealed class SearchOptions
{
public string? Query { get; set; }
public string? DataDirectory { get; set; }
public string? StartDate { get; set; }
public string? EndDate { get; set; }
public string? Section { get; set; }

View File

@ -1,11 +1,13 @@
using System.Diagnostics;
using System.Security.Cryptography;
using Journal.Core.Services.Database;
namespace Journal.Core.Services.Vault;
public class VaultStorageService(IVaultCryptoService crypto) : IVaultStorageService
public class VaultStorageService(IVaultCryptoService crypto, IJournalDatabaseService database) : IVaultStorageService
{
private readonly IVaultCryptoService _crypto = crypto;
private readonly IJournalDatabaseService _database = database;
private readonly object _vaultIoLock = new();
private const string DatabaseVaultPrefix = "_db_";
@ -19,11 +21,12 @@ public class VaultStorageService(IVaultCryptoService crypto) : IVaultStorageServ
lock (_vaultIoLock)
{
Directory.CreateDirectory(dataDirectory);
var dbDirectory = GetDatabaseDirectory();
Directory.CreateDirectory(dbDirectory);
if (!Directory.Exists(vaultDirectory))
return true;
return RestoreDatabaseVaults(password, vaultDirectory, dataDirectory);
return RestoreDatabaseVaults(password, vaultDirectory, dbDirectory);
}
}
@ -34,10 +37,11 @@ public class VaultStorageService(IVaultCryptoService crypto) : IVaultStorageServ
lock (_vaultIoLock)
{
Directory.CreateDirectory(vaultDirectory);
if (!Directory.Exists(dataDirectory))
var dbDirectory = GetDatabaseDirectory();
if (!Directory.Exists(dbDirectory))
return false;
SaveDatabaseVaults(password, vaultDirectory, dataDirectory);
SaveDatabaseVaults(password, vaultDirectory, dbDirectory);
return true;
}
}
@ -49,10 +53,11 @@ public class VaultStorageService(IVaultCryptoService crypto) : IVaultStorageServ
lock (_vaultIoLock)
{
Directory.CreateDirectory(vaultDirectory);
if (!Directory.Exists(dataDirectory))
var dbDirectory = GetDatabaseDirectory();
if (!Directory.Exists(dbDirectory))
return;
SaveDatabaseVaults(password, vaultDirectory, dataDirectory);
SaveDatabaseVaults(password, vaultDirectory, dbDirectory);
}
}
@ -63,8 +68,12 @@ public class VaultStorageService(IVaultCryptoService crypto) : IVaultStorageServ
lock (_vaultIoLock)
{
DeleteDirectoryWithRetries(dataDirectory);
Directory.CreateDirectory(dataDirectory);
var normalizedDataDir = Path.GetFullPath(dataDirectory);
var dbDirectory = GetDatabaseDirectory();
if (string.Equals(normalizedDataDir, dbDirectory, StringComparison.OrdinalIgnoreCase))
return;
DeleteDirectoryWithRetries(normalizedDataDir);
}
}
@ -135,6 +144,15 @@ public class VaultStorageService(IVaultCryptoService crypto) : IVaultStorageServ
throw new ArgumentException("Data directory is required.", nameof(dataDirectory));
}
private string GetDatabaseDirectory()
{
var dbPath = _database.GetDatabasePath();
var directory = Path.GetDirectoryName(dbPath);
return string.IsNullOrWhiteSpace(directory)
? Path.GetFullPath(".")
: Path.GetFullPath(directory);
}
private static void DeleteDirectoryWithRetries(string dataDirectory, int retries = 5, int delayMs = 200)
{
if (!Directory.Exists(dataDirectory))