diff --git a/Nickvision.Desktop.Tests/DatabaseServiceTests.cs b/Nickvision.Desktop.Tests/DatabaseServiceTests.cs index 7bd1935..9f73147 100644 --- a/Nickvision.Desktop.Tests/DatabaseServiceTests.cs +++ b/Nickvision.Desktop.Tests/DatabaseServiceTests.cs @@ -18,6 +18,7 @@ public void Case001_Init() { _databaseService = new DatabaseService(new MockLogger(), new AppInfo("org.nickvision.desktop.test.database", "Nickvision.Desktop.Test.Database", "Test Database"), new SecretService(new MockLogger())); Assert.IsNotNull(_databaseService); + Assert.IsFalse(_databaseService.IsEncrypted); } [TestMethod] @@ -25,6 +26,7 @@ public void Case002_EnsureTableAndTransaction() { Assert.IsNotNull(_databaseService); Assert.IsTrue(_databaseService.EnsureTableExists("test_table", "id TEXT PRIMARY KEY, name TEXT, age INTEGER")); + Assert.IsTrue(_databaseService.IsEncrypted); Assert.IsTrue(_databaseService.TableExists("test_table")); Assert.IsFalse(_databaseService.TableExists("missing_table")); using var transaction = _databaseService.CreateTransaction(); diff --git a/Nickvision.Desktop.WinUI/Nickvision.Desktop.WinUI.csproj b/Nickvision.Desktop.WinUI/Nickvision.Desktop.WinUI.csproj index 9e070d7..fa092ac 100644 --- a/Nickvision.Desktop.WinUI/Nickvision.Desktop.WinUI.csproj +++ b/Nickvision.Desktop.WinUI/Nickvision.Desktop.WinUI.csproj @@ -32,7 +32,7 @@ all - + diff --git a/Nickvision.Desktop/Application/DatabaseService.cs b/Nickvision.Desktop/Application/DatabaseService.cs index 24bbdea..1f6c349 100644 --- a/Nickvision.Desktop/Application/DatabaseService.cs +++ b/Nickvision.Desktop/Application/DatabaseService.cs @@ -17,12 +17,15 @@ public class DatabaseService : IAsyncDisposable, IDisposable, IDatabaseService private readonly AppInfo _appInfo; private SqliteConnection? _connection; + public bool IsEncrypted { get; private set; } + public DatabaseService(ILogger logger, AppInfo appInfo, ISecretService secretService) { _logger = logger; _secretService = secretService; _appInfo = appInfo; _connection = null; + IsEncrypted = false; } ~DatabaseService() @@ -471,18 +474,26 @@ private void EnsureDatabase() var secret = string.Empty; if (!_appInfo.IsPortable && (OperatingSystem.IsWindows() || OperatingSystem.IsMacOS() || OperatingSystem.IsLinux())) { - secret = ((Task.Run(() => _secretService.GetAsync(_appInfo.Id)).GetAwaiter().GetResult()) ?? (Task.Run(() => _secretService.CreateAsync(_appInfo.Id)).GetAwaiter().GetResult()))?.Value; + try + { + secret = ((Task.Run(() => _secretService.GetAsync(_appInfo.Id)).GetAwaiter().GetResult()) ?? (Task.Run(() => _secretService.CreateAsync(_appInfo.Id)).GetAwaiter().GetResult()))?.Value; + } + catch (Exception e) + { + _logger.LogWarning($"Secret service unavailable: {e.Message}. The database will not be encrypted."); + } } Directory.CreateDirectory(Path.GetDirectoryName(path)!); _connection = new SqliteConnection(new SqliteConnectionStringBuilder($"Data Source='{path}'") { Mode = SqliteOpenMode.ReadWriteCreate, - Password = secret, + Password = secret ?? string.Empty, Pooling = false }.ToString()); try { _connection.Open(); + IsEncrypted = !string.IsNullOrEmpty(secret); _logger.LogDebug($"Opened application database ({path})."); } catch (SqliteException e) @@ -490,7 +501,17 @@ private void EnsureDatabase() _logger.LogError($"Failed to open application database: {e}"); _connection.Dispose(); _connection = null; - throw; + if (string.IsNullOrEmpty(secret)) + { + _logger.LogWarning("The database may be encrypted but the secret service is unavailable. Falling back to an in-memory database."); + _connection = new SqliteConnection("Data Source=:memory:"); + _connection.Open(); + IsEncrypted = false; + } + else + { + throw; + } } } @@ -505,18 +526,26 @@ private async Task EnsureDatabaseAsync() var secret = string.Empty; if (!_appInfo.IsPortable && (OperatingSystem.IsWindows() || OperatingSystem.IsMacOS() || OperatingSystem.IsLinux())) { - secret = ((await _secretService.GetAsync(_appInfo.Id)) ?? (await _secretService.CreateAsync(_appInfo.Id)))?.Value; + try + { + secret = ((await _secretService.GetAsync(_appInfo.Id)) ?? (await _secretService.CreateAsync(_appInfo.Id)))?.Value; + } + catch (Exception e) + { + _logger.LogWarning($"Secret service unavailable: {e.Message}. The database will not be encrypted."); + } } Directory.CreateDirectory(Path.GetDirectoryName(path)!); _connection = new SqliteConnection(new SqliteConnectionStringBuilder($"Data Source='{path}'") { Mode = SqliteOpenMode.ReadWriteCreate, - Password = secret, + Password = secret ?? string.Empty, Pooling = false }.ToString()); try { await _connection.OpenAsync(); + IsEncrypted = !string.IsNullOrEmpty(secret); _logger.LogDebug($"Opened application database ({path})."); } catch (SqliteException e) @@ -524,7 +553,17 @@ private async Task EnsureDatabaseAsync() _logger.LogError($"Failed to open application database: {e}"); await _connection.DisposeAsync(); _connection = null; - throw; + if (string.IsNullOrEmpty(secret)) + { + _logger.LogWarning("The database may be encrypted but the secret service is unavailable. Falling back to an in-memory database."); + _connection = new SqliteConnection("Data Source=:memory:"); + await _connection.OpenAsync(); + IsEncrypted = false; + } + else + { + throw; + } } } diff --git a/Nickvision.Desktop/Application/IDatabaseService.cs b/Nickvision.Desktop/Application/IDatabaseService.cs index a2dfacf..0fcd11d 100644 --- a/Nickvision.Desktop/Application/IDatabaseService.cs +++ b/Nickvision.Desktop/Application/IDatabaseService.cs @@ -6,6 +6,7 @@ namespace Nickvision.Desktop.Application; public interface IDatabaseService { + bool IsEncrypted { get; } bool ClearTable(string tableName); Task ClearTableAsync(string tableName); int CountInTable(string tableName); diff --git a/Nickvision.Desktop/FreeDesktop/SecretServiceProxy.cs b/Nickvision.Desktop/FreeDesktop/SecretServiceProxy.cs index 6af43a5..7f26ba5 100644 --- a/Nickvision.Desktop/FreeDesktop/SecretServiceProxy.cs +++ b/Nickvision.Desktop/FreeDesktop/SecretServiceProxy.cs @@ -33,15 +33,24 @@ private SecretServiceProxy(DBusConnection connection, string sessionPath) { return null; } - var connection = new DBusConnection(sessionAddress); - await connection.ConnectAsync(); - var sessionPath = await OpenSessionAsync(connection); - if (string.IsNullOrEmpty(sessionPath) || sessionPath == "/") + DBusConnection? connection = null; + try + { + connection = new DBusConnection(sessionAddress); + await connection.ConnectAsync(); + var sessionPath = await OpenSessionAsync(connection); + if (string.IsNullOrEmpty(sessionPath) || sessionPath == "/") + { + connection.Dispose(); + return null; + } + return new SecretServiceProxy(connection, sessionPath); + } + catch (Exception) { - connection.Dispose(); + connection?.Dispose(); return null; } - return new SecretServiceProxy(connection, sessionPath); } internal async Task GetDefaultCollectionPathAsync() diff --git a/Nickvision.Desktop/Nickvision.Desktop.csproj b/Nickvision.Desktop/Nickvision.Desktop.csproj index fd17407..c449836 100644 --- a/Nickvision.Desktop/Nickvision.Desktop.csproj +++ b/Nickvision.Desktop/Nickvision.Desktop.csproj @@ -8,7 +8,7 @@ true true Nickvision.Desktop - 2026.4.7 + 2026.4.8 Nickvision Nickvision A cross-platform base for Nickvision desktop applications. @@ -26,16 +26,16 @@ - - - - + + + + all - +