fix issues with background sync and multiple databases (especially autoopen)
This commit is contained in:
		| @@ -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(); | ||||
|  | ||||
|                     } | ||||
|   | ||||
| @@ -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; | ||||
| 			} | ||||
|   | ||||
| @@ -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)) | ||||
|   | ||||
| @@ -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); | ||||
|   | ||||
| @@ -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; | ||||
|  | ||||
| 			} | ||||
|   | ||||
| @@ -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(); | ||||
|   | ||||
| @@ -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); | ||||
|                         } | ||||
|  | ||||
|   | ||||
| @@ -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); | ||||
|             } | ||||
|  | ||||
|   | ||||
		Reference in New Issue
	
	Block a user
	 Philipp Crocoll
					Philipp Crocoll