fix issues with background sync and multiple databases (especially autoopen)

This commit is contained in:
Philipp Crocoll
2025-06-03 15:37:21 +02:00
parent a4a3112dc6
commit 0cd9df7415
8 changed files with 76 additions and 48 deletions

View File

@@ -9,6 +9,7 @@ using KeePassLib.Serialization;
using keepass2android.Io;
using KeePass.Util;
using Group.Pals.Android.Lib.UI.Filechooser.Utils;
using KeePassLib;
namespace keepass2android
{
@@ -16,12 +17,14 @@ namespace keepass2android
{
private readonly IKp2aApp _app;
private IDatabaseModificationWatcher _modificationWatcher;
private readonly Database _database;
public SynchronizeCachedDatabase(IKp2aApp app, OnOperationFinishedHandler operationFinishedHandler, IDatabaseModificationWatcher modificationWatcher)
public SynchronizeCachedDatabase(IKp2aApp app, Database database, OnOperationFinishedHandler operationFinishedHandler, IDatabaseModificationWatcher modificationWatcher)
: base(app, operationFinishedHandler)
{
_app = app;
_database = database;
_modificationWatcher = modificationWatcher;
}
@@ -29,7 +32,7 @@ namespace keepass2android
{
try
{
IOConnectionInfo ioc = _app.CurrentDb.Ioc;
IOConnectionInfo ioc = _database.Ioc;
IFileStorage fileStorage = _app.GetFileStorage(ioc);
if (!(fileStorage is CachingFileStorage))
{
@@ -86,13 +89,13 @@ namespace keepass2android
{
Finish(true, _app.GetResourceString(UiStringKey.SynchronizedDatabaseSuccessfully));
}
}), _app.CurrentDb, false, remoteData, _modificationWatcher);
}), _database, false, remoteData, _modificationWatcher);
_saveDb.SetStatusLogger(StatusLogger);
_saveDb.DoNotSetStatusLoggerMessage = true; //Keep "sync db" as main message
_saveDb.SyncInBackground = false;
_saveDb.Run();
_app.CurrentDb.UpdateGlobals();
_database.UpdateGlobals();
_app.MarkAllGroupsAsDirty();
}
@@ -109,7 +112,7 @@ namespace keepass2android
{
new Handler(Looper.MainLooper).Post(() =>
{
_app.CurrentDb.UpdateGlobals();
_database.UpdateGlobals();
_app.MarkAllGroupsAsDirty();
Finish(true, _app.GetResourceString(UiStringKey.SynchronizedDatabaseSuccessfully));
@@ -118,10 +121,9 @@ namespace keepass2android
}
});
var _loadDb = new LoadDb(_app, ioc, Task.FromResult(remoteData),
_app.CurrentDb.KpDatabase.MasterKey, null, onFinished, true, false, _modificationWatcher);
_database.KpDatabase.MasterKey, null, onFinished, true, false, _modificationWatcher);
_loadDb.SetStatusLogger(StatusLogger);
_loadDb.DoNotSetStatusLoggerMessage = true; //Keep "sync db" as main message
_loadDb.SyncInBackground = false;
_loadDb.Run();
}

View File

@@ -41,7 +41,7 @@ namespace keepass2android
IDatabaseFormat _format;
public bool DoNotSetStatusLoggerMessage = false;
public bool SyncInBackground { get; set; }
public LoadDb(IKp2aApp app, IOConnectionInfo ioc, Task<MemoryStream> databaseData, CompositeKey compositeKey,
string keyfileOrProvider, OnOperationFinishedHandler operationFinishedHandler,
@@ -56,7 +56,6 @@ namespace keepass2android
_updateLastUsageTimestamp = updateLastUsageTimestamp;
_makeCurrent = makeCurrent;
_rememberKeyfile = app.GetBooleanPreference(PreferenceKey.remember_keyfile);
SyncInBackground = _app.SyncInBackgroundPreference;
}
protected bool success = false;
@@ -76,7 +75,7 @@ namespace keepass2android
var fileStorage = _app.GetFileStorage(_ioc);
bool requiresSubsequentSync = false;
RequiresSubsequentSync = false;
if (!DoNotSetStatusLoggerMessage)
@@ -100,7 +99,7 @@ namespace keepass2android
cachingFileStorage.IsOffline = true;
//no warning. We'll trigger a sync later.
cachingFileStorage.TriggerWarningWhenFallingBackToCache = false;
requiresSubsequentSync = true;
RequiresSubsequentSync = true;
}
using (Stream s = fileStorage.OpenFileForRead(_ioc))
@@ -118,7 +117,7 @@ namespace keepass2android
//ok, try to load the database. Let's start with Kdbx format and retry later if that is the wrong guess:
_format = new KdbxDatabaseFormat(KdbxDatabaseFormat.GetFormatToUse(fileStorage.GetFileExtension(_ioc)));
TryLoad(databaseStream, requiresSubsequentSync);
TryLoad(databaseStream);
@@ -171,12 +170,14 @@ namespace keepass2android
}
/// <summary>
public bool RequiresSubsequentSync { get; set; } = false;
/// <summary>
/// Holds the exception which was thrown during execution (if any)
/// </summary>
public Exception Exception { get; set; }
Database TryLoad(MemoryStream databaseStream, bool requiresSubsequentSync)
Database TryLoad(MemoryStream databaseStream)
{
//create a copy of the stream so we can try again if we get an exception which indicates we should change parameters
//This is not optimal in terms of (short-time) memory usage but is hard to avoid because the Keepass library closes streams also in case of errors.
@@ -197,27 +198,14 @@ namespace keepass2android
Database newDb =
_app.LoadDatabase(_ioc, workingCopy, _compositeKey, StatusLogger, _format, _makeCurrent, _modificationWatcher);
Kp2aLog.Log("LoadDB OK");
if (requiresSubsequentSync)
{
var syncTask = new SynchronizeCachedDatabase(_app, new ActionOnOperationFinished(_app,
(success, message, context) =>
{
if (!String.IsNullOrEmpty(message))
_app.ShowMessage(context, message,
success ? MessageSeverity.Info : MessageSeverity.Error);
}), new BackgroundDatabaseModificationLocker(_app)
);
OperationRunner.Instance.Run(_app, syncTask);
}
Finish(true, _format.SuccessMessage);
return newDb;
}
catch (OldFormatException)
{
_format = new KdbDatabaseFormat(_app);
return TryLoad(databaseStream, requiresSubsequentSync);
return TryLoad(databaseStream);
}
catch (InvalidCompositeKeyException)
{
@@ -229,7 +217,7 @@ namespace keepass2android
//retry without password:
_compositeKey.RemoveUserKey(passwordKey);
//retry:
return TryLoad(databaseStream, requiresSubsequentSync);
return TryLoad(databaseStream);
}
else throw;
}

View File

@@ -235,7 +235,7 @@ namespace keepass2android
{
if (requiresSubsequentSync)
{
var syncTask = new SynchronizeCachedDatabase(_app, new ActionOnOperationFinished(_app,
var syncTask = new SynchronizeCachedDatabase(_app, _db, new ActionOnOperationFinished(_app,
(success, message, context) =>
{
if (!System.String.IsNullOrEmpty(message))

View File

@@ -1222,7 +1222,7 @@ namespace keepass2android
return true;
case Resource.Id.menu_sync:
new SyncUtil(this).StartSynchronizeDatabase();
new SyncUtil(this).StartSynchronizeDatabase(App.Kp2a.CurrentDb.Ioc);
return true;
case Resource.Id.menu_work_offline:
@@ -1233,7 +1233,7 @@ namespace keepass2android
case Resource.Id.menu_work_online:
App.Kp2a.OfflineMode = App.Kp2a.OfflineModePreference = false;
UpdateOfflineModeMenu();
new SyncUtil(this).StartSynchronizeDatabase();
new SyncUtil(this).StartSynchronizeDatabase(App.Kp2a.CurrentDb.Ioc);
return true;
case Resource.Id.menu_open_other_db:
AppTask.SetActivityResult(this, KeePass.ExitLoadAnotherDb);

View File

@@ -222,6 +222,7 @@ namespace keepass2android
//StackBaseActivity will launch the next activity
Intent data = new Intent();
data.PutExtra("ioc", IOConnectionInfo.SerializeToString(_ioConnection));
data.PutExtra("requiresSubsequentSync", _lastLoadOperation?.RequiresSubsequentSync == true);
SetResult(Result.Ok, data);
@@ -1441,14 +1442,21 @@ namespace keepass2android
Handler handler = new Handler();
OnOperationFinishedHandler onOperationFinishedHandler = new AfterLoad(handler, this, _ioConnection);
LoadDb task = (KeyProviderTypes.Contains(KeyProviders.Otp))
LoadDb loadOperation = (KeyProviderTypes.Contains(KeyProviders.Otp))
? new SaveOtpAuxFileAndLoadDb(App.Kp2a, _ioConnection, _loadDbFileTask, compositeKey, GetKeyProviderString(),
onOperationFinishedHandler, this, true, _makeCurrent)
: new LoadDb(App.Kp2a, _ioConnection, _loadDbFileTask, compositeKey, GetKeyProviderString(), onOperationFinishedHandler,true, _makeCurrent);
_loadDbFileTask = null; // prevent accidental re-use
new BlockingOperationStarter(App.Kp2a, task).Run();
}
_lastLoadOperation = loadOperation;
//Don't use BlockingOperationStarter as that would cancel running operations.
//This is bad when used with AutoOpen: we might get here when one database has loaded and is now synced in the background.
//We don't want to cancel this.
OperationRunner.Instance.Run(App.Kp2a, loadOperation, true);
}
catch (Exception e)
{
Kp2aLog.LogUnexpectedError(new Exception("cannot load database: "+e + ", c: " + (compositeKey != null) + (_ioConnection != null) + (_keyFile != null), e));
@@ -1572,7 +1580,9 @@ namespace keepass2android
}
private bool hasRequestedKeyboardActivation = false;
protected override void OnStart()
private LoadDb _lastLoadOperation;
protected override void OnStart()
{
base.OnStart();
_starting = true;
@@ -1886,11 +1896,12 @@ namespace keepass2android
Handler handler = new Handler();
OnOperationFinishedHandler onOperationFinishedHandler = new AfterLoad(handler, this, _ioConnection);
_performingLoad = true;
LoadDb task = new LoadDb(App.Kp2a, _ioConnection, _loadDbFileTask, compositeKeyForImmediateLoad, GetKeyProviderString(),
LoadDb loadOperation = new LoadDb(App.Kp2a, _ioConnection, _loadDbFileTask, compositeKeyForImmediateLoad, GetKeyProviderString(),
onOperationFinishedHandler, false, _makeCurrent);
_loadDbFileTask = null; // prevent accidental re-use
new BlockingOperationStarter(App.Kp2a, task).Run();
compositeKeyForImmediateLoad = null; //don't reuse or keep in memory
_lastLoadOperation = loadOperation;
OperationRunner.Instance.Run(App.Kp2a, loadOperation, true);
compositeKeyForImmediateLoad = null; //don't reuse or keep in memory
}
else
@@ -2242,7 +2253,7 @@ namespace keepass2android
if (!Success)
_act.InitFingerprintUnlock();
_act._lastLoadOperation = null;
_act._performingLoad = false;
}

View File

@@ -339,7 +339,7 @@ namespace keepass2android
if (PreferenceManager.GetDefaultSharedPreferences(this)
.GetBoolean(GetString(Resource.String.SyncAfterQuickUnlock_key), false))
{
new SyncUtil(this).StartSynchronizeDatabase();
new SyncUtil(this).StartSynchronizeDatabase(App.Kp2a.CurrentDb.Ioc);
}
Finish();

View File

@@ -558,6 +558,7 @@ namespace keepass2android
private OpenDatabaseAdapter _adapter;
private MyBroadcastReceiver _intentReceiver;
private bool _isForeground;
private readonly List<IOConnectionInfo> _pendingBackgroundSyncs = new List<IOConnectionInfo>();
public override void OnBackPressed()
{
@@ -598,11 +599,36 @@ namespace keepass2android
string iocString = data?.GetStringExtra("ioc");
IOConnectionInfo ioc = IOConnectionInfo.UnserializeFromString(iocString);
//we first store the required sync operation and delay its execution until we loaded all AutoOpen entries (from local file)
//if required
bool requiresSubsequentSync = data?.GetBooleanExtra("requiresSubsequentSync", false) ?? false;
if (requiresSubsequentSync)
{
_pendingBackgroundSyncs.Add(ioc);
}
if (App.Kp2a.TrySelectCurrentDb(ioc))
{
if (OpenAutoExecEntries(App.Kp2a.CurrentDb)) return;
LaunchingOther = true;
AppTask.CanActivateSearchViewOnStart = true;
foreach (var pendingSyncIoc in _pendingBackgroundSyncs)
{
try
{
new SyncUtil(this).StartSynchronizeDatabase(pendingSyncIoc);
}
catch (Exception e)
{
App.Kp2a.ShowMessage(this, "Failed to synchronize database", MessageSeverity.Error);
Kp2aLog.LogUnexpectedError(e);
}
}
_pendingBackgroundSyncs.Clear();
AppTask.LaunchFirstGroupActivity(this);
}

View File

@@ -50,9 +50,11 @@ namespace keepass2android
}
public void StartSynchronizeDatabase()
public void StartSynchronizeDatabase(IOConnectionInfo ioc)
{
var filestorage = App.Kp2a.GetFileStorage(App.Kp2a.CurrentDb.Ioc);
var filestorage = App.Kp2a.GetFileStorage(ioc);
var databaseForIoc = App.Kp2a.GetDatabase(ioc);
OperationWithFinishHandler task;
OnOperationFinishedHandler onOperationFinishedHandler = new ActionInContextInstanceOnOperationFinished(_activity.ContextInstanceId, App.Kp2a, (success, message, context) =>
{
@@ -67,9 +69,9 @@ namespace keepass2android
adapter?.NotifyDataSetChanged();
});
if (App.Kp2a.CurrentDb?.OtpAuxFileIoc != null)
if (databaseForIoc?.OtpAuxFileIoc != null)
{
var task2 = new SyncOtpAuxFile(_activity, App.Kp2a.CurrentDb.OtpAuxFileIoc);
var task2 = new SyncOtpAuxFile(_activity, databaseForIoc.OtpAuxFileIoc);
OperationRunner.Instance.Run(App.Kp2a, task2);
}
@@ -79,11 +81,10 @@ namespace keepass2android
if (filestorage is CachingFileStorage)
{
task = new SynchronizeCachedDatabase(App.Kp2a, onOperationFinishedHandler, new BackgroundDatabaseModificationLocker(App.Kp2a));
task = new SynchronizeCachedDatabase(App.Kp2a, databaseForIoc, onOperationFinishedHandler, new BackgroundDatabaseModificationLocker(App.Kp2a));
}
else
{
//TODO do we want this to run in the background?
task = new CheckDatabaseForChanges( App.Kp2a, onOperationFinishedHandler);
}