diff --git a/src/Kp2aBusinessLogic/IKp2aApp.cs b/src/Kp2aBusinessLogic/IKp2aApp.cs
index b5533928..af385c85 100644
--- a/src/Kp2aBusinessLogic/IKp2aApp.cs
+++ b/src/Kp2aBusinessLogic/IKp2aApp.cs
@@ -140,6 +140,9 @@ namespace keepass2android
#endif
-
- }
+
+ bool SyncInBackgroundPreference { get; set; }
+
+
+ }
}
\ No newline at end of file
diff --git a/src/Kp2aBusinessLogic/Io/CachingFileStorage.cs b/src/Kp2aBusinessLogic/Io/CachingFileStorage.cs
index 0d8e5e27..2e1f6230 100644
--- a/src/Kp2aBusinessLogic/Io/CachingFileStorage.cs
+++ b/src/Kp2aBusinessLogic/Io/CachingFileStorage.cs
@@ -186,8 +186,11 @@ namespace keepass2android.Io
Kp2aLog.Log("couldn't open from remote " + ioc.Path);
#endif
Kp2aLog.Log(ex.ToString());
-
- _cacheSupervisor.CouldntOpenFromRemote(ioc, ex);
+ if (TriggerWarningWhenFallingBackToCache)
+ {
+ _cacheSupervisor.CouldntOpenFromRemote(ioc, ex);
+ }
+
return File.OpenRead(cachedFilePath);
}
}
@@ -327,7 +330,10 @@ namespace keepass2android.Io
Kp2aLog.Log("couldn't save to remote " + ioc.Path);
Kp2aLog.Log(e.ToString());
//notify the supervisor so it might display a warning or schedule a retry
- _cacheSupervisor.CouldntSaveToRemote(ioc, e);
+ if (TriggerWarningWhenFallingBackToCache)
+ {
+ _cacheSupervisor.CouldntSaveToRemote(ioc, e); }
+
return false;
}
}
@@ -632,7 +638,9 @@ namespace keepass2android.Io
set { _cachedStorage.IsOffline = value; }
}
- public void OnRequestPermissionsResult(IFileStorageSetupActivity fileStorageSetupActivity, int requestCode,
+ public bool TriggerWarningWhenFallingBackToCache { get; set; }
+
+ public void OnRequestPermissionsResult(IFileStorageSetupActivity fileStorageSetupActivity, int requestCode,
string[] permissions, Permission[] grantResults)
{
_cachedStorage.OnRequestPermissionsResult(fileStorageSetupActivity, requestCode, permissions, grantResults);
diff --git a/src/Kp2aBusinessLogic/Io/OfflineSwitchableFileStorage.cs b/src/Kp2aBusinessLogic/Io/OfflineSwitchableFileStorage.cs
index 4d82ad26..3971b630 100644
--- a/src/Kp2aBusinessLogic/Io/OfflineSwitchableFileStorage.cs
+++ b/src/Kp2aBusinessLogic/Io/OfflineSwitchableFileStorage.cs
@@ -11,7 +11,8 @@ namespace keepass2android.Io
public interface IOfflineSwitchable
{
bool IsOffline { get; set; }
- }
+ bool TriggerWarningWhenFallingBackToCache { get; set; }
+ }
///
/// Encapsulates another IFileStorage. Allows to switch to offline mode by throwing
@@ -21,8 +22,9 @@ namespace keepass2android.Io
{
private readonly IFileStorage _baseStorage;
public bool IsOffline { get; set; }
+ public bool TriggerWarningWhenFallingBackToCache { get; set; }
- public OfflineSwitchableFileStorage(IFileStorage baseStorage)
+ public OfflineSwitchableFileStorage(IFileStorage baseStorage)
{
_baseStorage = baseStorage;
}
diff --git a/src/Kp2aBusinessLogic/database/Database.cs b/src/Kp2aBusinessLogic/database/Database.cs
index 501bff5b..4f73c2d5 100644
--- a/src/Kp2aBusinessLogic/database/Database.cs
+++ b/src/Kp2aBusinessLogic/database/Database.cs
@@ -194,9 +194,9 @@ namespace keepass2android
}
- public void SaveData() {
+ public void SaveData(IFileStorage fileStorage) {
- using (IWriteTransaction trans = _app.GetFileStorage(Ioc).OpenWriteTransaction(Ioc, _app.GetBooleanPreference(PreferenceKey.UseFileTransactions)))
+ using (IWriteTransaction trans = fileStorage.OpenWriteTransaction(Ioc, _app.GetBooleanPreference(PreferenceKey.UseFileTransactions)))
{
DatabaseFormat.Save(KpDatabase, trans.OpenFile());
diff --git a/src/Kp2aBusinessLogic/database/edit/LoadDB.cs b/src/Kp2aBusinessLogic/database/edit/LoadDB.cs
index db333f1b..d9829b2d 100644
--- a/src/Kp2aBusinessLogic/database/edit/LoadDB.cs
+++ b/src/Kp2aBusinessLogic/database/edit/LoadDB.cs
@@ -21,8 +21,10 @@ using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Android.App;
+using Android.OS;
using KeePass.Util;
using keepass2android.database.edit;
+using keepass2android.Io;
using KeePassLib;
using KeePassLib.Keys;
using KeePassLib.Serialization;
@@ -47,8 +49,6 @@ namespace keepass2android
_keyfileOrProvider = keyfileOrProvider;
_updateLastUsageTimestamp = updateLastUsageTimestamp;
_makeCurrent = makeCurrent;
-
-
_rememberKeyfile = app.GetBooleanPreference(PreferenceKey.remember_keyfile);
}
@@ -65,16 +65,31 @@ namespace keepass2android
//make sure the file data is stored in the recent files list even if loading fails
SaveFileData(_ioc, _keyfileOrProvider);
+ var fileStorage = _app.GetFileStorage(_ioc);
+
+ bool requiresSubsequentSync = false;
+
StatusLogger.UpdateMessage(UiStringKey.loading_database);
//get the stream data into a single stream variable (databaseStream) regardless whether its preloaded or not:
MemoryStream preloadedMemoryStream = _databaseData == null ? null : _databaseData.Result;
MemoryStream databaseStream;
- if (preloadedMemoryStream != null)
- databaseStream = preloadedMemoryStream;
- else
+ if (preloadedMemoryStream != null)
+ {
+ //note: if the stream has been loaded already, we don't need to trigger another sync later on
+ databaseStream = preloadedMemoryStream;
+ }
+ else
{
- using (Stream s = _app.GetFileStorage(_ioc).OpenFileForRead(_ioc))
+ if (_app.SyncInBackgroundPreference && fileStorage is IOfflineSwitchable offlineSwitchable)
+ {
+ offlineSwitchable.IsOffline = true;
+ //no warning. We'll trigger a sync later.
+ offlineSwitchable.TriggerWarningWhenFallingBackToCache = false;
+ requiresSubsequentSync = true;
+
+ }
+ using (Stream s = fileStorage.OpenFileForRead(_ioc))
{
databaseStream = new MemoryStream();
s.CopyTo(databaseStream);
@@ -83,8 +98,8 @@ 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(_app.GetFileStorage(_ioc).GetFileExtension(_ioc)));
- TryLoad(databaseStream);
+ _format = new KdbxDatabaseFormat(KdbxDatabaseFormat.GetFormatToUse(fileStorage.GetFileExtension(_ioc)));
+ TryLoad(databaseStream, requiresSubsequentSync);
@@ -136,7 +151,7 @@ namespace keepass2android
///
public Exception Exception { get; set; }
- Database TryLoad(MemoryStream databaseStream)
+ Database TryLoad(MemoryStream databaseStream, bool requiresSubsequentSync)
{
//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.
@@ -150,7 +165,20 @@ namespace keepass2android
try
{
Database newDb = _app.LoadDatabase(_ioc, workingCopy, _compositeKey, StatusLogger, _format, _makeCurrent);
- Kp2aLog.Log("LoadDB OK");
+ Kp2aLog.Log("LoadDB OK");
+
+ if (requiresSubsequentSync)
+ {
+ var syncTask = new SynchronizeCachedDatabase(ActiveActivity, _app, new ActionOnOperationFinished(ActiveActivity,
+ (success, message, activeActivity) =>
+ {
+ if (!String.IsNullOrEmpty(message))
+ _app.ShowMessage(activeActivity, message, success ? MessageSeverity.Info : MessageSeverity.Error);
+
+ })
+ );
+ BackgroundOperationRunner.Instance.Run(ActiveActivity, _app, syncTask);
+ }
Finish(true, _format.SuccessMessage);
return newDb;
@@ -158,7 +186,7 @@ namespace keepass2android
catch (OldFormatException)
{
_format = new KdbDatabaseFormat(_app);
- return TryLoad(databaseStream);
+ return TryLoad(databaseStream, requiresSubsequentSync);
}
catch (InvalidCompositeKeyException)
{
@@ -170,7 +198,7 @@ namespace keepass2android
//retry without password:
_compositeKey.RemoveUserKey(passwordKey);
//retry:
- return TryLoad(databaseStream);
+ return TryLoad(databaseStream, requiresSubsequentSync);
}
else throw;
}
diff --git a/src/Kp2aBusinessLogic/database/edit/SaveDB.cs b/src/Kp2aBusinessLogic/database/edit/SaveDB.cs
index de641535..f6781854 100644
--- a/src/Kp2aBusinessLogic/database/edit/SaveDB.cs
+++ b/src/Kp2aBusinessLogic/database/edit/SaveDB.cs
@@ -38,6 +38,7 @@ namespace keepass2android
private readonly IKp2aApp _app;
private readonly Database _db;
private readonly bool _dontSave;
+ private bool requiresSubsequentSync = false; //if true, we need to sync the file after saving.
///
/// stream for reading the data from the original file. If this is set to a non-null value, we know we need to sync
@@ -108,13 +109,23 @@ namespace keepass2android
IOConnectionInfo ioc = _db.Ioc;
IFileStorage fileStorage = _app.GetFileStorage(ioc);
- if (_streamForOrigFile == null)
+ if (_app.SyncInBackgroundPreference && fileStorage is IOfflineSwitchable offlineSwitchable)
+ {
+ offlineSwitchable.IsOffline = true;
+ //no warning. We'll trigger a sync later.
+ offlineSwitchable.TriggerWarningWhenFallingBackToCache = false;
+ requiresSubsequentSync = true;
+
+ }
+
+
+ if (_streamForOrigFile == null)
{
if ((!_app.GetBooleanPreference(PreferenceKey.CheckForFileChangesOnSave))
|| (_db.KpDatabase.HashOfFileOnDisk == null)) //first time saving
{
PerformSaveWithoutCheck(fileStorage, ioc);
- Finish(true);
+ FinishWithSuccess();
return;
}
}
@@ -123,9 +134,9 @@ namespace keepass2android
bool hasStreamForOrigFile = (_streamForOrigFile != null);
bool hasChangeFast = hasStreamForOrigFile ||
fileStorage.CheckForFileChangeFast(ioc, _db.LastFileVersion); //first try to use the fast change detection;
- bool hasHashChanged = hasChangeFast ||
+ bool hasHashChanged = !requiresSubsequentSync && (hasChangeFast ||
(FileHashChanged(ioc, _db.KpDatabase.HashOfFileOnDisk) ==
- FileHashChange.Changed); //if that fails, hash the file and compare:
+ FileHashChange.Changed)); //if that fails, hash the file and compare:
if (hasHashChanged)
{
@@ -158,7 +169,7 @@ namespace keepass2android
RunInWorkerThread(() =>
{
PerformSaveWithoutCheck(fileStorage, ioc);
- Finish(true);
+ FinishWithSuccess();
});
},
//cancel
@@ -174,7 +185,7 @@ namespace keepass2android
else
{
PerformSaveWithoutCheck(fileStorage, ioc);
- Finish(true);
+ FinishWithSuccess();
}
}
@@ -194,11 +205,28 @@ namespace keepass2android
}
else
{
- Finish(true);
+ FinishWithSuccess();
}
}
+ private void FinishWithSuccess()
+ {
+ if (requiresSubsequentSync)
+ {
+ var syncTask = new SynchronizeCachedDatabase(ActiveActivity, _app, new ActionOnOperationFinished(ActiveActivity,
+ (success, message, activeActivity) =>
+ {
+ if (!System.String.IsNullOrEmpty(message))
+ _app.ShowMessage(activeActivity, message, success ? MessageSeverity.Info : MessageSeverity.Error);
+
+ })
+ );
+ BackgroundOperationRunner.Instance.Run(ActiveActivity, _app, syncTask);
+ }
+ Finish(true);
+ }
+
private void MergeAndFinish(IFileStorage fileStorage, IOConnectionInfo ioc)
{
//note: when synced, the file might be downloaded once again from the server. Caching the data
@@ -207,7 +235,7 @@ namespace keepass2android
MergeIn(fileStorage, ioc);
PerformSaveWithoutCheck(fileStorage, ioc);
_db.UpdateGlobals();
- Finish(true);
+ FinishWithSuccess();
}
private void RunInWorkerThread(Action runHandler)
@@ -282,7 +310,7 @@ namespace keepass2android
private void PerformSaveWithoutCheck(IFileStorage fileStorage, IOConnectionInfo ioc)
{
StatusLogger.UpdateSubMessage("");
- _db.SaveData();
+ _db.SaveData(fileStorage);
_db.LastFileVersion = fileStorage.GetCurrentFileVersionFast(ioc);
}
diff --git a/src/keepass2android-app/PasswordActivity.cs b/src/keepass2android-app/PasswordActivity.cs
index bfe8b03c..533a9dae 100644
--- a/src/keepass2android-app/PasswordActivity.cs
+++ b/src/keepass2android-app/PasswordActivity.cs
@@ -1753,17 +1753,10 @@ namespace keepass2android
cbOfflineMode.Checked =
App.Kp2a
.OfflineModePreference; //this won't overwrite new user settings because every change is directly saved in settings
- LinearLayout offlineModeContainer = FindViewById(Resource.Id.work_offline_container);
- var cachingFileStorage = App.Kp2a.GetFileStorage(_ioConnection) as CachingFileStorage;
- if ((cachingFileStorage != null) && cachingFileStorage.IsCached(_ioConnection))
- {
- offlineModeContainer.Visibility = ViewStates.Visible;
- }
- else
- {
- offlineModeContainer.Visibility = ViewStates.Gone;
- App.Kp2a.OfflineMode = false;
- }
+
+ CheckBox cbSyncInBackground = (CheckBox)FindViewById(Resource.Id.sync_in_background)!;
+ cbSyncInBackground.Checked = App.Kp2a.SyncInBackgroundPreference;
+ UpdateInternalCacheCheckboxesVisibility();
@@ -2039,10 +2032,38 @@ namespace keepass2android
{
App.Kp2a.OfflineModePreference = App.Kp2a.OfflineMode = args.IsChecked;
};
-
- }
-
- private String LoadKeyProviderStringForIoc(String filename) {
+
+ CheckBox cbSyncInBackground = (CheckBox)FindViewById(Resource.Id.sync_in_background);
+ cbSyncInBackground.CheckedChange += (sender, args) =>
+ {
+ App.Kp2a.SyncInBackgroundPreference = args.IsChecked;
+ UpdateInternalCacheCheckboxesVisibility();
+
+ };
+
+ }
+
+ private void UpdateInternalCacheCheckboxesVisibility()
+ {
+
+ LinearLayout syncInBackgroundContainer = FindViewById(Resource.Id.sync_in_background_container)!;
+
+ LinearLayout offlineModeContainer = FindViewById(Resource.Id.work_offline_container)!;
+ var cachingFileStorage = App.Kp2a.GetFileStorage(_ioConnection) as CachingFileStorage;
+ if ((cachingFileStorage != null) && cachingFileStorage.IsCached(_ioConnection))
+ {
+ syncInBackgroundContainer.Visibility = ViewStates.Visible;
+ offlineModeContainer.Visibility =
+ App.Kp2a.SyncInBackgroundPreference ? ViewStates.Gone : ViewStates.Visible;
+ }
+ else
+ {
+ syncInBackgroundContainer.Visibility = offlineModeContainer.Visibility = ViewStates.Gone;
+ App.Kp2a.OfflineMode = false;
+ }
+ }
+
+ private String LoadKeyProviderStringForIoc(String filename) {
if ( _rememberKeyfile ) {
string keyfile = App.Kp2a.FileDbHelper.GetKeyFileForFile(filename);
if (String.IsNullOrEmpty(keyfile))
diff --git a/src/keepass2android-app/Resources/layout/password.xml b/src/keepass2android-app/Resources/layout/password.xml
index b21a343f..c216d9c5 100644
--- a/src/keepass2android-app/Resources/layout/password.xml
+++ b/src/keepass2android-app/Resources/layout/password.xml
@@ -318,6 +318,30 @@
android:text="@string/help_quickunlock"
/>
+
+
+
+
+
+
+
+