Remove data-directory markdown flow and complete SQLCipher backend cleanup
This commit is contained in:
parent
72f8221605
commit
9e92619fc2
@ -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,
|
||||
|
||||
@ -1,7 +1,6 @@
|
||||
namespace Journal.Core.Dtos;
|
||||
|
||||
public sealed record EntrySearchRequestDto(
|
||||
string DataDirectory,
|
||||
string? Query = null,
|
||||
string? Section = null,
|
||||
string? StartDate = null,
|
||||
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@ -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);
|
||||
}
|
||||
@ -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);
|
||||
|
||||
@ -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();
|
||||
|
||||
@ -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"));
|
||||
}
|
||||
}
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -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();
|
||||
|
||||
|
||||
@ -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);
|
||||
}
|
||||
|
||||
@ -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; }
|
||||
|
||||
@ -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))
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user