* CachingFileStorage: Added more callbacks to provide user with more information what's going on
* Changed TestCacheSupervisor for easier use of the many callbacks * Adapted tests for new callbacks * GroupBaseActivity: Added sync menu command * Preferences: Added option to enable/disable offline caching * App: don't lock database when user wants to reload. This is done in PasswordActivity and should be done there after the password was filled into the pw field * CheckDatabaseForChanges.cs: used when syncing a non-cached database
This commit is contained in:
		| @@ -27,12 +27,29 @@ namespace keepass2android.Io | ||||
| 		/// </summary> | ||||
| 		void CouldntOpenFromRemote(IOConnectionInfo ioc, Exception ex); | ||||
|  | ||||
| 		/// <summary> | ||||
| 		/// Called when the local file either didn't exist or was unmodified, so the remote file | ||||
| 		/// was loaded and the cache was updated during the load operation. | ||||
| 		/// </summary> | ||||
| 		void UpdatedCachedFileOnLoad(IOConnectionInfo ioc); | ||||
|  | ||||
| 		/// <summary> | ||||
| 		/// Called when the remote file either didn't exist or was unmodified, so the local file | ||||
| 		/// was loaded and the remote file was updated during the load operation. | ||||
| 		/// </summary> | ||||
| 		void UpdatedRemoteFileOnLoad(IOConnectionInfo ioc); | ||||
|  | ||||
| 		/// <summary> | ||||
| 		/// Called to notify the supervisor that the file described by ioc is opened from the cache because there's a conflict | ||||
| 		/// with local and remote changes | ||||
| 		/// </summary> | ||||
| 		/// <param name="ioc"></param> | ||||
| 		void NotifyOpenFromLocalDueToConflict(IOConnectionInfo ioc); | ||||
|  | ||||
| 		/// <summary> | ||||
| 		/// Called when the load operation was performed and the remote file was identical with the local file | ||||
| 		/// </summary> | ||||
| 		void LoadedFromRemoteInSync(IOConnectionInfo ioc); | ||||
| 	} | ||||
|  | ||||
| 	/// <summary> | ||||
| @@ -148,7 +165,8 @@ namespace keepass2android.Io | ||||
| 				//no changes in remote file -> upload | ||||
| 				using (Stream localData = File.OpenRead(CachedFilePath(ioc))) | ||||
| 				{ | ||||
| 					TryUpdateRemoteFile(localData, ioc, true, hash); | ||||
| 					if (TryUpdateRemoteFile(localData, ioc, true, hash)) | ||||
| 						_cacheSupervisor.UpdatedRemoteFileOnLoad(ioc); | ||||
| 				} | ||||
| 			} | ||||
| 			else | ||||
| @@ -197,20 +215,34 @@ namespace keepass2android.Io | ||||
| 					cachedFile.Close(); | ||||
| 					fileHash = MemUtil.ByteArrayToHexString(cachedFile.Hash); | ||||
| 				} | ||||
|  | ||||
| 				//remember current hash | ||||
| 				string previousHash = null; | ||||
| 				string baseVersionFilePath = BaseVersionFilePath(ioc); | ||||
| 				if (File.Exists(baseVersionFilePath)) | ||||
| 					previousHash = File.ReadAllText(baseVersionFilePath); | ||||
|  | ||||
| 				//save hash in cache files: | ||||
| 				File.WriteAllText(VersionFilePath(ioc), fileHash); | ||||
| 				File.WriteAllText(BaseVersionFilePath(ioc), fileHash); | ||||
| 				File.WriteAllText(baseVersionFilePath, fileHash); | ||||
|  | ||||
| 				//notify supervisor what we did: | ||||
| 				if (previousHash != fileHash) | ||||
| 					_cacheSupervisor.UpdatedCachedFileOnLoad(ioc); | ||||
| 				else | ||||
| 					_cacheSupervisor.LoadedFromRemoteInSync(ioc); | ||||
|  | ||||
| 				return File.OpenRead(cachedFilePath);	 | ||||
| 			} | ||||
| 			 | ||||
| 		} | ||||
|  | ||||
| 		private void TryUpdateRemoteFile(Stream cachedData, IOConnectionInfo ioc, bool useFileTransaction, string hash) | ||||
| 		private bool TryUpdateRemoteFile(Stream cachedData, IOConnectionInfo ioc, bool useFileTransaction, string hash) | ||||
| 		{ | ||||
| 			try | ||||
| 			{ | ||||
| 				UpdateRemoteFile(cachedData, ioc, useFileTransaction, hash); | ||||
| 				return true; | ||||
| 			} | ||||
| 			catch (Exception e) | ||||
| 			{ | ||||
| @@ -218,6 +250,7 @@ namespace keepass2android.Io | ||||
| 				Kp2aLog.Log(e.ToString()); | ||||
| 				//notify the supervisor so it might display a warning or schedule a retry | ||||
| 				_cacheSupervisor.CouldntSaveToRemote(ioc, e); | ||||
| 				return false; | ||||
| 			} | ||||
| 		} | ||||
|  | ||||
|   | ||||
| @@ -51,6 +51,7 @@ | ||||
|     <Reference Include="System.Xml" /> | ||||
|   </ItemGroup> | ||||
|   <ItemGroup> | ||||
|     <Compile Include="database\CheckDatabaseForChanges.cs" /> | ||||
|     <Compile Include="database\SynchronizeCachedDatabase.cs" /> | ||||
|     <Compile Include="Io\BuiltInFileStorage.cs" /> | ||||
|     <Compile Include="Io\CachingFileStorage.cs" /> | ||||
|   | ||||
| @@ -40,6 +40,8 @@ namespace keepass2android | ||||
| 		UploadingFile, | ||||
| 		FilesInSync, | ||||
| 		SynchronizedDatabaseSuccessfully, | ||||
| 		RestoringRemoteFile | ||||
| 		RestoringRemoteFile, | ||||
| 		CheckingDatabaseForChanges, | ||||
| 		RemoteDatabaseUnchanged | ||||
|     } | ||||
| } | ||||
							
								
								
									
										74
									
								
								src/Kp2aBusinessLogic/database/CheckDatabaseForChanges.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										74
									
								
								src/Kp2aBusinessLogic/database/CheckDatabaseForChanges.cs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,74 @@ | ||||
| using System; | ||||
| using System.Collections.Generic; | ||||
| using System.IO; | ||||
| using System.Security.Cryptography; | ||||
| using System.Text; | ||||
| using Android.App; | ||||
| using Android.Content; | ||||
| using KeePassLib.Cryptography; | ||||
| using KeePassLib.Serialization; | ||||
| using KeePassLib.Utility; | ||||
| using keepass2android.Io; | ||||
|  | ||||
| namespace keepass2android | ||||
| { | ||||
| 	public class CheckDatabaseForChanges: RunnableOnFinish | ||||
| 	{ | ||||
| 		private readonly Context _context; | ||||
| 		private readonly IKp2aApp _app; | ||||
|  | ||||
|  | ||||
| 		public CheckDatabaseForChanges(Context context, IKp2aApp app, OnFinish finish) | ||||
| 			: base(finish) | ||||
| 		{ | ||||
| 			_context = context; | ||||
| 			_app = app; | ||||
| 		} | ||||
|  | ||||
| 		public override void Run() | ||||
| 		{ | ||||
| 			try | ||||
| 			{ | ||||
| 				IOConnectionInfo ioc = _app.GetDb().Ioc; | ||||
| 				IFileStorage fileStorage = _app.GetFileStorage(ioc); | ||||
| 				if (fileStorage is CachingFileStorage) | ||||
| 				{ | ||||
| 					throw new Exception("Cannot sync a cached database!"); | ||||
| 				} | ||||
| 				StatusLogger.UpdateMessage(UiStringKey.CheckingDatabaseForChanges); | ||||
| 				 | ||||
| 				//download file from remote location and calculate hash: | ||||
| 				StatusLogger.UpdateSubMessage(_app.GetResourceString(UiStringKey.DownloadingRemoteFile)); | ||||
| 				 | ||||
|  | ||||
| 				MemoryStream remoteData = new MemoryStream(); | ||||
| 				using ( | ||||
| 					HashingStreamEx hashingRemoteStream = new HashingStreamEx(fileStorage.OpenFileForRead(ioc), false, | ||||
| 																				new SHA256Managed())) | ||||
| 				{ | ||||
| 					hashingRemoteStream.CopyTo(remoteData); | ||||
| 					hashingRemoteStream.Close(); | ||||
| 					 | ||||
| 					if (!MemUtil.ArraysEqual(_app.GetDb().KpDatabase.HashOfFileOnDisk, hashingRemoteStream.Hash)) | ||||
| 					{ | ||||
| 						_app.TriggerReload(_context); | ||||
| 						Finish(true); | ||||
| 					} | ||||
| 					else | ||||
| 					{ | ||||
| 						Finish(true, _app.GetResourceString(UiStringKey.RemoteDatabaseUnchanged)); | ||||
| 					} | ||||
| 				} | ||||
| 			 | ||||
| 				 | ||||
| 				 | ||||
| 			} | ||||
| 			catch (Exception e) | ||||
| 			{ | ||||
| 				Finish(false, e.Message); | ||||
| 			} | ||||
|  | ||||
| 		} | ||||
|  | ||||
| 	} | ||||
| } | ||||
| @@ -48,7 +48,8 @@ namespace keepass2android | ||||
| 			try | ||||
| 			{ | ||||
| 				StatusLogger.UpdateMessage(UiStringKey.loading_database); | ||||
| 				_app.LoadDatabase(_ioc, _databaseData == null ? null : _databaseData.Result, _pass, _key, StatusLogger); | ||||
| 				MemoryStream memoryStream = _databaseData == null ? null : _databaseData.Result; | ||||
| 				_app.LoadDatabase(_ioc, memoryStream, _pass, _key, StatusLogger); | ||||
| 				SaveFileData (_ioc, _key); | ||||
| 				 | ||||
| 			} catch (KeyFileException) { | ||||
| @@ -56,7 +57,7 @@ namespace keepass2android | ||||
| 				Finish(false, /*TODO Localize: use Keepass error text KPRes.KeyFileError (including "or invalid format")*/ _app.GetResourceString(UiStringKey.keyfile_does_not_exist)); | ||||
| 			} | ||||
| 			catch (Exception e) { | ||||
| 				Kp2aLog.Log("Exception: " + e.Message); | ||||
| 				Kp2aLog.Log("Exception: " + e); | ||||
| 				Finish(false, "An error occured: " + e.Message); | ||||
| 				return; | ||||
| 			}  | ||||
|   | ||||
| @@ -51,9 +51,11 @@ | ||||
|     <Compile Include="Properties\AssemblyInfo.cs" /> | ||||
|   </ItemGroup> | ||||
|   <ItemGroup> | ||||
|     <LibraryProjectZip Include="..\java\KP2ASoftKeyboard\project.zip"> | ||||
|       <Link>project.zip</Link> | ||||
|     </LibraryProjectZip> | ||||
|     <None Include="Additions\AboutAdditions.txt" /> | ||||
|     <None Include="Jars\AboutJars.txt" /> | ||||
|     <LibraryProjectZip Include="project.zip" /> | ||||
|   </ItemGroup> | ||||
|   <ItemGroup> | ||||
|     <TransformFile Include="Transforms\EnumFields.xml" /> | ||||
|   | ||||
| @@ -19,7 +19,7 @@ namespace Kp2aUnitTests | ||||
|             TestRunner runner = new TestRunner(); | ||||
|             // Run all tests from this assembly | ||||
|             runner.AddTests(Assembly.GetExecutingAssembly()); | ||||
| 			//runner.AddTests(new List<Type> { typeof(TestSaveDbCached) }); | ||||
| 			//runner.AddTests(new List<Type> { typeof(TestSynchronizeCachedDatabase) }); | ||||
| 			//runner.AddTests(typeof(TestSaveDbCached).GetMethod("TestLoadEditSaveWhenModified")); | ||||
| 			 | ||||
| 			//runner.AddTests(new List<Type> { typeof(TestSaveDb) }); | ||||
|   | ||||
| @@ -66,7 +66,7 @@ namespace Kp2aUnitTests | ||||
| 			var app = CreateTestKp2aApp(); | ||||
| 			app.CreateNewDatabase(); | ||||
| 			bool loadSuccesful = false; | ||||
| 			LoadDb task = new LoadDb(app, new IOConnectionInfo() { Path = filename }, password, keyfile, new ActionOnFinish((success, message) => | ||||
| 			LoadDb task = new LoadDb(app, new IOConnectionInfo() { Path = filename }, null, password, keyfile, new ActionOnFinish((success, message) => | ||||
| 				{ | ||||
| 					if (!success) | ||||
| 						Kp2aLog.Log(message); | ||||
|   | ||||
| @@ -1,29 +1,78 @@ | ||||
| using System; | ||||
| using System.Collections.Generic; | ||||
| using System.Linq; | ||||
| using KeePassLib.Serialization; | ||||
| using Microsoft.VisualStudio.TestTools.UnitTesting; | ||||
| using keepass2android.Io; | ||||
|  | ||||
| namespace Kp2aUnitTests | ||||
| { | ||||
| 	class TestCacheSupervisor: ICacheSupervisor | ||||
| 	{ | ||||
| 		public bool CouldntOpenFromRemoteCalled { get; set; } | ||||
| 		public bool CouldntSaveToRemoteCalled { get; set; } | ||||
| 		public bool NotifyOpenFromLocalDueToConflictCalled { get; set; } | ||||
| 		public const string CouldntOpenFromRemoteId = "CouldntOpenFromRemote"; | ||||
| 		public const string CouldntSaveToRemoteId = "CouldntSaveToRemote"; | ||||
| 		public const string NotifyOpenFromLocalDueToConflictId = "CouldntSaveToRemote"; | ||||
| 		public const string UpdatedCachedFileOnLoadId = "UpdatedCachedFileOnLoad"; | ||||
| 		public const string LoadedFromRemoteInSyncId = "LoadedFromRemoteInSync"; | ||||
| 		public const string UpdatedRemoteFileOnLoadId = "UpdatedRemoteFileOnLoad"; | ||||
|  | ||||
| 		private HashSet<string> _callsMade = new HashSet<string>(); | ||||
| 		 | ||||
| 		public void Reset() | ||||
| 		{ | ||||
| 			_callsMade.Clear(); | ||||
| 		} | ||||
|  | ||||
| 		public void AssertNoCall() | ||||
| 		{ | ||||
| 			string allCalls = _callsMade.Aggregate("", (current, s) => current + s + ","); | ||||
| 			Assert.AreEqual("", allCalls); | ||||
| 		} | ||||
|  | ||||
| 		public void AssertSingleCall(string id) | ||||
| 		{ | ||||
| 			if ((_callsMade.Count == 1) | ||||
| 			    && _callsMade.Single() == id) | ||||
| 			{ | ||||
| 				Reset(); | ||||
| 				return; | ||||
| 			} | ||||
| 				 | ||||
|  | ||||
| 			Assert.Fail("expected only "+id+", but received: "+_callsMade.Aggregate("", (current, s) => current + s + ",")); | ||||
|  | ||||
| 		} | ||||
|  | ||||
| 		public void CouldntSaveToRemote(IOConnectionInfo ioc, Exception e) | ||||
| 		{ | ||||
| 			CouldntSaveToRemoteCalled = true; | ||||
| 			_callsMade.Add(CouldntSaveToRemoteId); | ||||
| 		} | ||||
|  | ||||
| 		public void CouldntOpenFromRemote(IOConnectionInfo ioc, Exception ex) | ||||
| 		{ | ||||
| 			CouldntOpenFromRemoteCalled = true; | ||||
| 			_callsMade.Add(CouldntOpenFromRemoteId); | ||||
| 		} | ||||
|  | ||||
| 		public void UpdatedCachedFileOnLoad(IOConnectionInfo ioc) | ||||
| 		{ | ||||
| 			_callsMade.Add(UpdatedCachedFileOnLoadId); | ||||
| 		} | ||||
|  | ||||
| 		public void UpdatedRemoteFileOnLoad(IOConnectionInfo ioc) | ||||
| 		{ | ||||
| 			_callsMade.Add(UpdatedRemoteFileOnLoadId); | ||||
| 		} | ||||
|  | ||||
| 		public void NotifyOpenFromLocalDueToConflict(IOConnectionInfo ioc) | ||||
| 		{ | ||||
| 			NotifyOpenFromLocalDueToConflictCalled = true; | ||||
| 			_callsMade.Add(NotifyOpenFromLocalDueToConflictId); | ||||
| 		} | ||||
|  | ||||
| 		public void LoadedFromRemoteInSync(IOConnectionInfo ioc) | ||||
| 		{ | ||||
| 			_callsMade.Add(LoadedFromRemoteInSyncId); | ||||
| 		} | ||||
|  | ||||
| 		 | ||||
| 	} | ||||
| } | ||||
| @@ -30,6 +30,8 @@ namespace Kp2aUnitTests | ||||
| 			//read the file once. Should now be in the cache. | ||||
| 			MemoryStream fileContents = ReadToMemoryStream(_fileStorage, CachingTestFile); | ||||
|  | ||||
| 			_testCacheSupervisor.AssertSingleCall(TestCacheSupervisor.UpdatedCachedFileOnLoadId); | ||||
|  | ||||
| 			//check it's the correct data: | ||||
| 			Assert.AreEqual(MemoryStreamToString(fileContents), _defaultCacheFileContents); | ||||
|  | ||||
| @@ -41,8 +43,7 @@ namespace Kp2aUnitTests | ||||
|  | ||||
| 			AssertEqual(fileContents, fileContents2); | ||||
|  | ||||
| 			Assert.IsTrue(_testCacheSupervisor.CouldntOpenFromRemoteCalled); | ||||
| 			Assert.IsFalse(_testCacheSupervisor.CouldntSaveToRemoteCalled); | ||||
| 			_testCacheSupervisor.AssertSingleCall(TestCacheSupervisor.CouldntOpenFromRemoteId); | ||||
| 			 | ||||
|  | ||||
| 		} | ||||
| @@ -67,8 +68,7 @@ namespace Kp2aUnitTests | ||||
| 			//read the file once. Should now be in the cache. | ||||
| 			ReadToMemoryStream(_fileStorage, CachingTestFile); | ||||
|  | ||||
| 			Assert.IsFalse(_testCacheSupervisor.CouldntOpenFromRemoteCalled); | ||||
| 			Assert.IsFalse(_testCacheSupervisor.CouldntSaveToRemoteCalled); | ||||
| 			_testCacheSupervisor.AssertSingleCall(TestCacheSupervisor.UpdatedCachedFileOnLoadId); | ||||
|  | ||||
| 			//let the base file storage go offline: | ||||
| 			_testFileStorage.Offline = true; | ||||
| @@ -77,16 +77,13 @@ namespace Kp2aUnitTests | ||||
| 			string newContent = "new content"; | ||||
| 			WriteContentToCacheFile(newContent); | ||||
|  | ||||
| 			Assert.IsFalse(_testCacheSupervisor.CouldntOpenFromRemoteCalled); | ||||
| 			Assert.IsTrue(_testCacheSupervisor.CouldntSaveToRemoteCalled); | ||||
| 			_testCacheSupervisor.CouldntSaveToRemoteCalled = false; | ||||
| 			_testCacheSupervisor.AssertSingleCall(TestCacheSupervisor.CouldntSaveToRemoteId); | ||||
|  | ||||
| 			//now try to read the file again: | ||||
| 			MemoryStream fileContents2 = ReadToMemoryStream(_fileStorage, CachingTestFile); | ||||
|  | ||||
| 			Assert.IsTrue(_testCacheSupervisor.CouldntOpenFromRemoteCalled); | ||||
| 			Assert.IsFalse(_testCacheSupervisor.CouldntSaveToRemoteCalled); | ||||
| 			_testCacheSupervisor.CouldntOpenFromRemoteCalled = false; | ||||
|  | ||||
| 			_testCacheSupervisor.AssertSingleCall(TestCacheSupervisor.CouldntOpenFromRemoteId); | ||||
|  | ||||
| 			//should return the written content: | ||||
| 			Assert.AreEqual(MemoryStreamToString(fileContents2), newContent); | ||||
| @@ -96,8 +93,7 @@ namespace Kp2aUnitTests | ||||
| 			MemoryStream fileContents3 = ReadToMemoryStream(_fileStorage, CachingTestFile); | ||||
| 			Assert.AreEqual(MemoryStreamToString(fileContents3), newContent); | ||||
|  | ||||
| 			Assert.IsFalse(_testCacheSupervisor.CouldntOpenFromRemoteCalled); | ||||
| 			Assert.IsFalse(_testCacheSupervisor.CouldntSaveToRemoteCalled); | ||||
| 			_testCacheSupervisor.AssertSingleCall(TestCacheSupervisor.UpdatedRemoteFileOnLoadId); | ||||
|  | ||||
| 			//ensure the data on the remote was synced: | ||||
| 			MemoryStream fileContents4 = ReadToMemoryStream(_testFileStorage, CachingTestFile); | ||||
| @@ -115,8 +111,7 @@ namespace Kp2aUnitTests | ||||
| 			//read the file once. Should now be in the cache. | ||||
| 			ReadToMemoryStream(_fileStorage, CachingTestFile); | ||||
|  | ||||
| 			Assert.IsFalse(_testCacheSupervisor.CouldntOpenFromRemoteCalled); | ||||
| 			Assert.IsFalse(_testCacheSupervisor.CouldntSaveToRemoteCalled); | ||||
| 			_testCacheSupervisor.AssertSingleCall(TestCacheSupervisor.UpdatedCachedFileOnLoadId); | ||||
|  | ||||
| 			//let the base file storage go offline: | ||||
| 			_testFileStorage.Offline = true; | ||||
| @@ -125,9 +120,7 @@ namespace Kp2aUnitTests | ||||
| 			string newLocalContent = "new local content"; | ||||
| 			WriteContentToCacheFile(newLocalContent); | ||||
|  | ||||
| 			Assert.IsFalse(_testCacheSupervisor.CouldntOpenFromRemoteCalled); | ||||
| 			Assert.IsTrue(_testCacheSupervisor.CouldntSaveToRemoteCalled); | ||||
| 			_testCacheSupervisor.CouldntSaveToRemoteCalled = false; | ||||
| 			_testCacheSupervisor.AssertSingleCall(TestCacheSupervisor.CouldntSaveToRemoteId); | ||||
|  | ||||
| 			//write something to the remote file: | ||||
| 			File.WriteAllText(CachingTestFile, "new remote content"); | ||||
| @@ -142,12 +135,7 @@ namespace Kp2aUnitTests | ||||
| 			Assert.AreEqual(MemoryStreamToString(fileContents2), newLocalContent); | ||||
|  | ||||
| 			//but a notification about the conflict should be made: | ||||
| 			Assert.IsFalse(_testCacheSupervisor.CouldntOpenFromRemoteCalled); | ||||
| 			Assert.IsFalse(_testCacheSupervisor.CouldntSaveToRemoteCalled); | ||||
| 			Assert.IsTrue(_testCacheSupervisor.NotifyOpenFromLocalDueToConflictCalled); | ||||
| 			_testCacheSupervisor.NotifyOpenFromLocalDueToConflictCalled = false; | ||||
|  | ||||
| 			 | ||||
| 			_testCacheSupervisor.AssertSingleCall(TestCacheSupervisor.NotifyOpenFromLocalDueToConflictId); | ||||
|  | ||||
| 		} | ||||
|  | ||||
| @@ -160,13 +148,13 @@ namespace Kp2aUnitTests | ||||
|  | ||||
| 			//read the file once. Should now be in the cache. | ||||
| 			ReadToMemoryStream(_fileStorage, CachingTestFile); | ||||
| 			_testCacheSupervisor.Reset(); | ||||
|  | ||||
| 			//write something to the cache: | ||||
| 			string newContent = "new content"; | ||||
| 			WriteContentToCacheFile(newContent); | ||||
|  | ||||
| 			Assert.IsFalse(_testCacheSupervisor.CouldntOpenFromRemoteCalled); | ||||
| 			Assert.IsFalse(_testCacheSupervisor.CouldntSaveToRemoteCalled); | ||||
| 			_testCacheSupervisor.AssertNoCall(); | ||||
|  | ||||
| 			Assert.AreEqual(newContent, File.ReadAllText(CachingTestFile));			 | ||||
| 		} | ||||
| @@ -180,17 +168,19 @@ namespace Kp2aUnitTests | ||||
| 			//read the file once. Should now be in the cache. | ||||
| 			ReadToMemoryStream(_fileStorage, CachingTestFile); | ||||
|  | ||||
| 			_testCacheSupervisor.Reset(); | ||||
|  | ||||
| 			//delete remote file: | ||||
| 			_testFileStorage.DeleteFile(IocForCacheFile); | ||||
|  | ||||
|  | ||||
| 			//read again. shouldn't throw and give the same result: | ||||
| 			var memStream = ReadToMemoryStream(_fileStorage, CachingTestFile); | ||||
|  | ||||
| 			//check if we received the correct content: | ||||
| 			Assert.AreEqual(_defaultCacheFileContents, MemoryStreamToString(memStream)); | ||||
|  | ||||
| 			Assert.IsTrue(_testCacheSupervisor.CouldntOpenFromRemoteCalled); | ||||
| 			Assert.IsFalse(_testCacheSupervisor.CouldntSaveToRemoteCalled); | ||||
| 			_testCacheSupervisor.AssertSingleCall(TestCacheSupervisor.CouldntOpenFromRemoteId); | ||||
| 			 | ||||
| 		} | ||||
|  | ||||
| @@ -216,7 +206,8 @@ namespace Kp2aUnitTests | ||||
| 		{ | ||||
| 			_testFileStorage = new TestFileStorage(); | ||||
| 			_testCacheSupervisor = new TestCacheSupervisor(); | ||||
| 			_fileStorage = new CachingFileStorage(_testFileStorage, Application.Context.CacheDir.Path, _testCacheSupervisor); | ||||
| 			//_fileStorage = new CachingFileStorage(_testFileStorage, Application.Context.CacheDir.Path, _testCacheSupervisor); | ||||
| 			_fileStorage = new CachingFileStorage(_testFileStorage, "/mnt/sdcard/kp2atest_cache", _testCacheSupervisor); | ||||
| 			_fileStorage.ClearCache(); | ||||
| 			File.WriteAllText(CachingTestFile, _defaultCacheFileContents); | ||||
| 		} | ||||
|   | ||||
| @@ -1,5 +1,6 @@ | ||||
| using System; | ||||
| using System.Collections.Generic; | ||||
| using System.IO; | ||||
| using Android.App; | ||||
| using Android.Content; | ||||
| using Android.OS; | ||||
| @@ -29,6 +30,18 @@ namespace Kp2aUnitTests | ||||
| 			 | ||||
| 		} | ||||
|  | ||||
| 		public void LockDatabase(bool allowQuickUnlock = true) | ||||
| 		{ | ||||
| 			throw new NotImplementedException(); | ||||
| 		} | ||||
|  | ||||
| 		public void LoadDatabase(IOConnectionInfo ioConnectionInfo, MemoryStream memoryStream, string password, string keyFile, | ||||
| 		                         ProgressDialogStatusLogger statusLogger) | ||||
| 		{ | ||||
| 			_db.LoadData(this, ioConnectionInfo, memoryStream, password, keyFile, statusLogger); | ||||
| 			 | ||||
| 		} | ||||
|  | ||||
| 		public Database GetDb() | ||||
| 		{ | ||||
| 			return _db; | ||||
|   | ||||
| @@ -21,7 +21,7 @@ namespace Kp2aUnitTests | ||||
| 			IKp2aApp app = new TestKp2aApp(); | ||||
| 			app.CreateNewDatabase(); | ||||
| 			bool loadSuccesful = false; | ||||
| 			LoadDb task = new LoadDb(app, new IOConnectionInfo() { Path = TestDbDirectory+filenameWithoutDir }, | ||||
| 			LoadDb task = new LoadDb(app, new IOConnectionInfo() { Path = TestDbDirectory+filenameWithoutDir }, null, | ||||
| 				password, keyfile, new ActionOnFinish((success, message) => | ||||
| 					{ | ||||
| 						if (!success) | ||||
| @@ -78,7 +78,7 @@ namespace Kp2aUnitTests | ||||
| 			app.CreateNewDatabase(); | ||||
| 			 | ||||
| 			bool loadSuccesful = false; | ||||
| 			LoadDb task = new LoadDb(app, ioc, "a", null, new ActionOnFinish((success, message) => | ||||
| 			LoadDb task = new LoadDb(app, ioc, null, "a", null, new ActionOnFinish((success, message) => | ||||
| 				{ | ||||
| 					if (!success) | ||||
| 						Android.Util.Log.Debug("KP2ATest", "error loading db: " + message); | ||||
| @@ -102,7 +102,7 @@ namespace Kp2aUnitTests | ||||
| 			app.CreateNewDatabase(); | ||||
| 			 | ||||
| 			bool loadSuccesful = false; | ||||
| 			LoadDb task = new LoadDb(app, ioc, "test", null, new ActionOnFinish((success, message) => | ||||
| 			LoadDb task = new LoadDb(app, ioc, null, "test", null, new ActionOnFinish((success, message) => | ||||
| 				{ | ||||
| 					if (!success) | ||||
| 						Android.Util.Log.Debug("KP2ATest", "error loading db: " + message); | ||||
| @@ -128,7 +128,7 @@ namespace Kp2aUnitTests | ||||
|  | ||||
| 			bool loadSuccesful = false; | ||||
| 			bool gotError = false; | ||||
| 			LoadDb task = new LoadDb(app, ioc, "test", null, new ActionOnFinish((success, message) => | ||||
| 			LoadDb task = new LoadDb(app, ioc, null, "test", null, new ActionOnFinish((success, message) => | ||||
| 			{ | ||||
| 				if (!success) | ||||
| 				{ | ||||
| @@ -156,7 +156,7 @@ namespace Kp2aUnitTests | ||||
|  | ||||
| 			bool loadSuccesful = false; | ||||
| 			bool gotError = false; | ||||
| 			LoadDb task = new LoadDb(app, ioc, "test", null, new ActionOnFinish((success, message) => | ||||
| 			LoadDb task = new LoadDb(app, ioc, null, "test", null, new ActionOnFinish((success, message) => | ||||
| 			{ | ||||
| 				if (!success) | ||||
| 				{ | ||||
|   | ||||
| @@ -39,14 +39,15 @@ namespace Kp2aUnitTests | ||||
| 			IOConnection.DeleteFile(new IOConnectionInfo { Path = DefaultFilename }); | ||||
| 			//save it and reload it so we have a base version | ||||
| 			SaveDatabase(app); | ||||
| 			_testCacheSupervisor.AssertNoCall(); | ||||
| 			app = LoadDatabase(DefaultFilename, DefaultPassword, DefaultKeyfile); | ||||
| 			_testCacheSupervisor.AssertSingleCall(TestCacheSupervisor.LoadedFromRemoteInSyncId); | ||||
| 			//modify the database by adding a group: | ||||
| 			app.GetDb().KpDatabase.RootGroup.AddGroup(new PwGroup(true, true, "TestGroup", PwIcon.Apple), true); | ||||
| 			//save the database again: | ||||
| 			SaveDatabase(app); | ||||
| 			Assert.IsNull(((TestKp2aApp)app).LastYesNoCancelQuestionTitle); | ||||
| 			Assert.IsFalse(_testCacheSupervisor.CouldntOpenFromRemoteCalled); | ||||
| 			Assert.IsFalse(_testCacheSupervisor.CouldntSaveToRemoteCalled); | ||||
| 			_testCacheSupervisor.AssertNoCall(); | ||||
|  | ||||
| 			//load database to a new app instance: | ||||
| 			IKp2aApp resultApp = LoadDatabase(DefaultFilename, DefaultPassword, DefaultKeyfile); | ||||
| @@ -71,10 +72,10 @@ namespace Kp2aUnitTests | ||||
| 			//modify the database by adding a group: | ||||
| 			app.GetDb().KpDatabase.RootGroup.AddGroup(new PwGroup(true, true, "TestGroup", PwIcon.Apple), true); | ||||
| 			//save the database again: | ||||
| 			_testCacheSupervisor.Reset(); | ||||
| 			SaveDatabase(app); | ||||
| 			Assert.IsNull(((TestKp2aApp) app).LastYesNoCancelQuestionTitle); | ||||
| 			Assert.IsFalse(_testCacheSupervisor.CouldntOpenFromRemoteCalled); | ||||
| 			Assert.IsFalse(_testCacheSupervisor.CouldntSaveToRemoteCalled); | ||||
| 			_testCacheSupervisor.AssertNoCall(); | ||||
|  | ||||
| 			//load database to a new app instance: | ||||
| 			IKp2aApp resultApp = LoadDatabase(DefaultFilename, DefaultPassword, DefaultKeyfile); | ||||
| @@ -109,11 +110,12 @@ namespace Kp2aUnitTests | ||||
|  | ||||
| 			SaveDatabase(app2); | ||||
|  | ||||
| 			_testCacheSupervisor.Reset(); | ||||
|  | ||||
| 			foreach (var group in app.GetDb().KpDatabase.RootGroup.Groups) | ||||
| 				Kp2aLog.Log("app d: " + group.Name); | ||||
| 			Assert.IsNull(((TestKp2aApp)app).LastYesNoCancelQuestionTitle); | ||||
| 			Assert.IsFalse(_testCacheSupervisor.CouldntOpenFromRemoteCalled); | ||||
| 			Assert.IsFalse(_testCacheSupervisor.CouldntSaveToRemoteCalled); | ||||
| 			_testCacheSupervisor.AssertNoCall(); | ||||
|  | ||||
| 			//modify the database by adding a group: | ||||
| 			PwGroup group1 = new PwGroup(true, true, "TestGroup", PwIcon.Apple); | ||||
| @@ -124,10 +126,10 @@ namespace Kp2aUnitTests | ||||
|  | ||||
|  | ||||
| 			//save the database again: | ||||
| 			_testCacheSupervisor.Reset(); | ||||
| 			SaveDatabase(app); | ||||
| 			Assert.AreEqual(((TestKp2aApp)app).LastYesNoCancelQuestionTitle, UiStringKey.TitleSyncQuestion); | ||||
| 			Assert.IsFalse(_testCacheSupervisor.CouldntOpenFromRemoteCalled); | ||||
| 			Assert.IsFalse(_testCacheSupervisor.CouldntSaveToRemoteCalled); | ||||
| 			_testCacheSupervisor.AssertNoCall(); | ||||
| 			 | ||||
|  | ||||
| 			//load database to a new app instance: | ||||
|   | ||||
| @@ -47,6 +47,7 @@ namespace Kp2aUnitTests | ||||
| 			//save it and reload it so we have a base version ("remote" and in the cache) | ||||
| 			SaveDatabase(app); | ||||
| 			app = LoadDatabase(DefaultFilename, DefaultPassword, DefaultKeyfile); | ||||
| 			_testCacheSupervisor.AssertSingleCall(TestCacheSupervisor.LoadedFromRemoteInSyncId); | ||||
| 		 | ||||
| 			string resultMessage; | ||||
| 			bool wasSuccessful; | ||||
| @@ -68,8 +69,8 @@ namespace Kp2aUnitTests | ||||
| 			app.GetDb().KpDatabase.RootGroup.AddGroup(new PwGroup(true, true, "TestGroup", PwIcon.Apple), true); | ||||
| 			//save the database again (will be saved locally only) | ||||
| 			SaveDatabase(app); | ||||
| 			Assert.IsTrue(_testCacheSupervisor.CouldntSaveToRemoteCalled); | ||||
| 			_testCacheSupervisor.CouldntSaveToRemoteCalled = false; | ||||
|  | ||||
| 			_testCacheSupervisor.AssertSingleCall(TestCacheSupervisor.CouldntSaveToRemoteId); | ||||
|  | ||||
| 			//go online again: | ||||
| 			_testFileStorage.Offline = false; | ||||
| @@ -82,10 +83,10 @@ namespace Kp2aUnitTests | ||||
| 			//ensure both files are identical and up to date now: | ||||
| 			_testFileStorage.Offline = true; | ||||
| 			var appOfflineLoaded = LoadDatabase(DefaultFilename, DefaultPassword, DefaultKeyfile); | ||||
| 			_testCacheSupervisor.CouldntOpenFromRemoteCalled = false; | ||||
| 			_testCacheSupervisor.AssertSingleCall(TestCacheSupervisor.CouldntOpenFromRemoteId); | ||||
| 			_testFileStorage.Offline = false; | ||||
| 			var appRemoteLoaded = LoadDatabase(DefaultFilename, DefaultPassword, DefaultKeyfile); | ||||
| 			Assert.IsFalse(_testCacheSupervisor.CouldntOpenFromRemoteCalled); | ||||
| 			_testCacheSupervisor.AssertSingleCall(TestCacheSupervisor.LoadedFromRemoteInSyncId); | ||||
|  | ||||
| 			AssertDatabasesAreEqual(app.GetDb().KpDatabase, appOfflineLoaded.GetDb().KpDatabase); | ||||
| 			AssertDatabasesAreEqual(app.GetDb().KpDatabase, appRemoteLoaded.GetDb().KpDatabase); | ||||
| @@ -101,6 +102,7 @@ namespace Kp2aUnitTests | ||||
| 			//save it and reload it so we have a base version ("remote" and in the cache) | ||||
| 			SaveDatabase(app); | ||||
| 			app = LoadDatabase(DefaultFilename, DefaultPassword, DefaultKeyfile); | ||||
| 			_testCacheSupervisor.AssertSingleCall(TestCacheSupervisor.LoadedFromRemoteInSyncId); | ||||
|  | ||||
| 			//delete remote: | ||||
| 			IOConnection.DeleteFile(new IOConnectionInfo { Path = DefaultFilename }); | ||||
| @@ -128,8 +130,11 @@ namespace Kp2aUnitTests | ||||
| 			//save it and reload it so we have a base version ("remote" and in the cache) | ||||
| 			SaveDatabase(app); | ||||
| 			app = LoadDatabase(DefaultFilename, DefaultPassword, DefaultKeyfile); | ||||
| 			_testCacheSupervisor.AssertSingleCall(TestCacheSupervisor.LoadedFromRemoteInSyncId); | ||||
|  | ||||
| 			var app2 = LoadDatabase(DefaultFilename, DefaultPassword, DefaultKeyfile); | ||||
| 			app2.FileStorage = _testFileStorage; //give app2 direct access to the remote file | ||||
| 			_testCacheSupervisor.AssertSingleCall(TestCacheSupervisor.LoadedFromRemoteInSyncId); | ||||
|  | ||||
| 			//go offline: | ||||
| 			_testFileStorage.Offline = true; | ||||
| @@ -145,8 +150,7 @@ namespace Kp2aUnitTests | ||||
| 			app2.GetDb().KpDatabase.RootGroup.AddGroup(newGroup2, true); | ||||
| 			//save the database again (will be saved locally only for "app") | ||||
| 			SaveDatabase(app); | ||||
| 			Assert.IsTrue(_testCacheSupervisor.CouldntSaveToRemoteCalled); | ||||
| 			_testCacheSupervisor.CouldntSaveToRemoteCalled = false; | ||||
| 			_testCacheSupervisor.AssertSingleCall(TestCacheSupervisor.CouldntSaveToRemoteId); | ||||
|  | ||||
| 			//go online again: | ||||
| 			_testFileStorage.Offline = false; | ||||
|   | ||||
| @@ -24,6 +24,7 @@ using Android.Views; | ||||
| using Android.Widget; | ||||
| using KeePassLib; | ||||
| using Android.Preferences; | ||||
| using keepass2android.Io; | ||||
| using keepass2android.view; | ||||
| using Android.Graphics.Drawables; | ||||
|  | ||||
| @@ -201,6 +202,14 @@ namespace keepass2android | ||||
|  | ||||
| 				searchView.SetSearchableInfo(searchManager.GetSearchableInfo(ComponentName)); | ||||
| 			} | ||||
| 			var item = menu.FindItem(Resource.Id.menu_sync); | ||||
| 			if (item != null) | ||||
| 			{ | ||||
| 				if (App.Kp2a.GetDb().Ioc.IsLocalFile()) | ||||
| 					item.SetVisible(false); | ||||
| 				else | ||||
| 					item.SetVisible(true); | ||||
| 			} | ||||
| 			return true; | ||||
| 		} | ||||
| 		 | ||||
| @@ -256,6 +265,10 @@ namespace keepass2android | ||||
| 				SetPassword(); | ||||
| 				return true; | ||||
|  | ||||
| 			case Resource.Id.menu_sync: | ||||
| 				Synchronize(); | ||||
| 				return true; | ||||
| 				 | ||||
| 			case Resource.Id.menu_sort: | ||||
| 				ToggleSort(); | ||||
| 				return true; | ||||
| @@ -293,6 +306,30 @@ namespace keepass2android | ||||
| 			return base.OnOptionsItemSelected(item); | ||||
| 		} | ||||
|  | ||||
| 		private void Synchronize() | ||||
| 		{ | ||||
| 			var filestorage = App.Kp2a.GetFileStorage(App.Kp2a.GetDb().Ioc); | ||||
| 			RunnableOnFinish task; | ||||
| 			ActionOnFinish onFinishShowMessage = new ActionOnFinish((success, message) => | ||||
| 			{ | ||||
| 				if (!String.IsNullOrEmpty(message)) | ||||
| 					Toast.MakeText(this, message, ToastLength.Long).Show(); | ||||
| 			}); | ||||
| 			if (filestorage is CachingFileStorage) | ||||
| 			{ | ||||
| 				 | ||||
| 				task = new SynchronizeCachedDatabase(this, App.Kp2a, onFinishShowMessage); | ||||
| 			} | ||||
| 			else | ||||
| 			{ | ||||
|  | ||||
| 				task = new CheckDatabaseForChanges(this, App.Kp2a, onFinishShowMessage); | ||||
| 			} | ||||
| 			var progressTask = new ProgressTask(App.Kp2a, this, task); | ||||
| 			progressTask.Run(); | ||||
|  | ||||
| 		} | ||||
|  | ||||
| 		private void ToggleSort() { | ||||
| 			// Toggle setting | ||||
| 			String sortKey = GetString(Resource.String.sort_key); | ||||
|   | ||||
							
								
								
									
										1484
									
								
								src/keepass2android/Resources/Resource.designer.cs
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										1484
									
								
								src/keepass2android/Resources/Resource.designer.cs
									
									
									
										generated
									
									
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							| @@ -45,6 +45,12 @@ | ||||
|         android:title="@string/menu_change_key" | ||||
|         android:showAsAction="never" | ||||
|     /> | ||||
| 	<item android:id="@+id/menu_sync" | ||||
| 		  android:icon="@android:drawable/ic_popup_sync" | ||||
| 			android:title="@string/synchronize_database_menu" | ||||
| 			android:showAsAction="never" | ||||
|     /> | ||||
|  | ||||
| 	<item android:id="@+id/menu_sort" | ||||
|         android:icon="@android:drawable/ic_menu_sort_by_size" | ||||
|         android:title="@string/sort_name" | ||||
| @@ -56,11 +62,6 @@ | ||||
|         android:title="@string/suggest_improvements" | ||||
|         android:showAsAction="never" | ||||
|     /> | ||||
|     <item android:id="@+id/menu_rate" | ||||
|         android:icon="@android:drawable/star_off" | ||||
|         android:title="@string/rate_app" | ||||
|         android:showAsAction="never" | ||||
|     /> | ||||
|     <item android:id="@+id/menu_translate" | ||||
|         android:title="@string/translate_app" | ||||
|         android:showAsAction="never" | ||||
|   | ||||
| @@ -38,6 +38,9 @@ | ||||
|     <item android:id="@+id/menu_change_master_key" | ||||
|         android:icon="@android:drawable/ic_menu_manage" | ||||
|         android:title="@string/menu_change_key" | ||||
|     /> | ||||
| 	<item android:id="@+id/menu_sync" | ||||
| 			android:title="@string/synchronize_database_menu" | ||||
|     /> | ||||
|     <item android:id="@+id/menu_sort" | ||||
|         android:icon="@android:drawable/ic_menu_sort_by_size" | ||||
| @@ -47,10 +50,6 @@ | ||||
|         android:icon="@android:drawable/ic_menu_directions" | ||||
|         android:title="@string/suggest_improvements" | ||||
|     /> | ||||
|     <item android:id="@+id/menu_rate" | ||||
|         android:icon="@android:drawable/star_off" | ||||
|         android:title="@string/rate_app" | ||||
|     /> | ||||
|     <item android:id="@+id/menu_translate" | ||||
|         android:title="@string/translate_app" | ||||
|        /> | ||||
|   | ||||
| @@ -63,6 +63,7 @@ | ||||
| 	<string name="LastInfoVersionCode_key">LastInfoVersion</string> | ||||
| 	 | ||||
| 	<string name="UseFileTransactions_key">UseFileTransactions</string> | ||||
| 	<string name="UseOfflineCache_key">UseOfflineCache</string> | ||||
| 	<string name="CheckForFileChangesOnSave_key">CheckForFileChangesOnSave</string> | ||||
|  | ||||
| 	<string name="MarketURL">market://details?id=</string> | ||||
|   | ||||
| @@ -224,6 +224,10 @@ | ||||
|   <string name="credentials_dialog_title">Enter server credentials</string> | ||||
|   <string name="UseFileTransactions_title">File transactions</string> | ||||
|   <string name="UseFileTransactions_summary">Use file transactions for writing databases</string> | ||||
| 	<string name="UseOfflineCache_title">Database caching</string> | ||||
| 	<string name="UseOfflineCache_summary">Keep a copy of remote database files in the application cache directory. This allows to use remote databases even when offline.</string> | ||||
| 	<string name="ClearOfflineCache_title">Clear cache?</string> | ||||
| 	<string name="ClearOfflineCache_question">This will delete all cached database files. Any changes you made while being offline which have not yet been synchronized will be lost! Continue?</string> | ||||
|   <string name="CheckForFileChangesOnSave_title">Check for modifications</string> | ||||
|   <string name="CheckForFileChangesOnSave_summary">Check whether the file was modified externally before saving changes.</string> | ||||
| 	 | ||||
| @@ -272,12 +276,23 @@ | ||||
| 	<string name="YesSynchronize">Yes, merge</string> | ||||
| 	<string name="NoOverwrite">No, overwrite</string> | ||||
|  | ||||
| 	<string name="SynchronizingCachedDatabase">Synchronizing cached database...</string> | ||||
| 	<string name="DownloadingRemoteFile">Downloading remote file...</string> | ||||
| 	<string name="UploadingFile">Uploading file...</string> | ||||
| 	<string name="RestoringRemoteFile">Restoring remote file...</string> | ||||
| 	<string name="SynchronizingCachedDatabase">Synchronizing cached database…</string> | ||||
| 	<string name="DownloadingRemoteFile">Downloading remote file…</string> | ||||
| 	<string name="UploadingFile">Uploading file…</string> | ||||
| 	<string name="RestoringRemoteFile">Restoring remote file…</string> | ||||
| 	<string name="FilesInSync">Files are in sync.</string> | ||||
| 	<string name="SynchronizedDatabaseSuccessfully">Database synchronized successfully!</string> | ||||
| 	<string name="CheckingDatabaseForChanges">Checking database for changes…</string> | ||||
|  | ||||
| 	<string name="CouldNotSaveToRemote">Could not save to remote: %1$s. Save again or use the Synchronize menu when remote connection is available again.</string> | ||||
| 	<string name="CouldNotLoadFromRemote">Could not open from remote: %1$s. Loaded file from local cache. You can still make changes in the database and synchronize them later.</string> | ||||
| 	<string name="UpdatedRemoteFileOnLoad">Updated remote file.</string> | ||||
| 	<string name="NotifyOpenFromLocalDueToConflict">Opened local file due to conflict with changes in remote file. Use Synchronize menu to merge.</string> | ||||
| 	<string name="LoadedFromRemoteInSync">Remote file and cache are synchronized.</string> | ||||
| 	<string name="UpdatedCachedFileOnLoad">Updated local cache copy of database.</string> | ||||
| 	<string name="RemoteDatabaseUnchanged">No changes detected.</string> | ||||
|  | ||||
| 	<string name="synchronize_database_menu">Synchronize database…</string> | ||||
|  | ||||
| 		<string name="ChangeLog_title">Change log</string> | ||||
| 	<string name="ChangeLog_0_8_4"> | ||||
|   | ||||
| @@ -156,6 +156,14 @@ | ||||
| 			  android:defaultValue="true" | ||||
| 			  android:title="@string/UseFileTransactions_title" | ||||
| 			  android:key="@string/UseFileTransactions_key" /> | ||||
| 		<CheckBoxPreference | ||||
| 			  android:enabled="true" | ||||
| 			  android:persistent="true" | ||||
| 			  android:summary="@string/UseOfflineCache_summary" | ||||
| 			  android:defaultValue="true" | ||||
| 			  android:title="@string/UseOfflineCache_title" | ||||
| 			  android:key="@string/UseOfflineCache_key" /> | ||||
|  | ||||
| 		<CheckBoxPreference | ||||
| 		  android:key="@string/RememberRecentFiles_key" | ||||
| 		  android:title="@string/RememberRecentFiles_title" | ||||
|   | ||||
| @@ -218,7 +218,6 @@ namespace keepass2android | ||||
| 				(dlgSender, dlgEvt) => | ||||
| 				{ | ||||
| 					_db.ReloadRequested = true; | ||||
| 					LockDatabase(false); | ||||
| 					activity.SetResult(KeePass.ExitReloadDb); | ||||
| 					activity.Finish(); | ||||
|  | ||||
| @@ -334,14 +333,25 @@ namespace keepass2android | ||||
| 				return new BuiltInFileStorage(); | ||||
| 			else | ||||
| 			{ | ||||
| 				//todo: check if desired | ||||
| 				var prefs = PreferenceManager.GetDefaultSharedPreferences(Application.Context); | ||||
| 				if (prefs.GetBoolean(Application.Context.Resources.GetString(Resource.String.UseOfflineCache_key), true)) | ||||
| 				{ | ||||
| 					return new CachingFileStorage(new BuiltInFileStorage(), Application.Context.CacheDir.Path, this);	 | ||||
| 				} | ||||
| 				else | ||||
| 				{ | ||||
| 					return new BuiltInFileStorage(); | ||||
| 				} | ||||
| 			} | ||||
| 		} | ||||
|  | ||||
| 		public void TriggerReload(Context ctx) | ||||
| 		{ | ||||
| 			AskForReload((Activity)ctx); | ||||
| 			Handler handler = new Handler(Looper.MainLooper); | ||||
| 			handler.Post(() => | ||||
| 				{ | ||||
| 					AskForReload((Activity) ctx); | ||||
| 				}); | ||||
| 		} | ||||
|  | ||||
|  | ||||
| @@ -386,21 +396,38 @@ namespace keepass2android | ||||
|  | ||||
| 		public void CouldntSaveToRemote(IOConnectionInfo ioc, Exception e) | ||||
| 		{ | ||||
| 			//TODO use resource strings | ||||
| 			ShowToast("Couldn't save to remote: "+e.Message+". Save again or use Sync menu when remote connection is available again."); | ||||
| 			ShowToast(Application.Context.GetString(Resource.String.CouldNotSaveToRemote, e.Message)); | ||||
| 		} | ||||
|  | ||||
| 		//todo: test changes in SaveDb with Cache: Save without conflict, save with conflict | ||||
| 		//add test? | ||||
| 		 | ||||
| 		public void CouldntOpenFromRemote(IOConnectionInfo ioc, Exception ex) | ||||
| 		{ | ||||
| 			ShowToast("Couldn't open from remote: " + ex.Message+". Loaded file from local cache. You can still make changes in the database and sync them later."); | ||||
| 			ShowToast(Application.Context.GetString(Resource.String.CouldNotLoadFromRemote, ex.Message)); | ||||
| 		} | ||||
|  | ||||
| 		public void UpdatedCachedFileOnLoad(IOConnectionInfo ioc) | ||||
| 		{ | ||||
| 			ShowToast(Application.Context.GetString(Resource.String.UpdatedCachedFileOnLoad)); | ||||
| 		} | ||||
|  | ||||
| 		public void UpdatedRemoteFileOnLoad(IOConnectionInfo ioc) | ||||
| 		{ | ||||
| 			ShowToast(Application.Context.GetString(Resource.String.UpdatedRemoteFileOnLoad)); | ||||
| 		} | ||||
|  | ||||
| 		public void NotifyOpenFromLocalDueToConflict(IOConnectionInfo ioc) | ||||
| 		{ | ||||
| 			ShowToast("Opened local file due to conflict with changes in remote file. Use Synchronize menu to merge."); | ||||
| 			ShowToast(Application.Context.GetString(Resource.String.NotifyOpenFromLocalDueToConflict)); | ||||
| 		} | ||||
|  | ||||
| 		public void LoadedFromRemoteInSync(IOConnectionInfo ioc) | ||||
| 		{ | ||||
| 			ShowToast(Application.Context.GetString(Resource.String.LoadedFromRemoteInSync)); | ||||
| 		} | ||||
|  | ||||
| 		public void ClearOfflineCache() | ||||
| 		{ | ||||
| 			new CachingFileStorage(new BuiltInFileStorage(), Application.Context.CacheDir.Path, this).ClearCache(); | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| @@ -408,6 +435,7 @@ namespace keepass2android | ||||
|     ///Application class for Keepass2Android: Contains static Database variable to be used by all components. | ||||
| #if NoNet | ||||
| 	[Application(Debuggable=false, Label=AppNames.AppName)] | ||||
| 	todo: remove caching preference | ||||
| #else | ||||
| #if RELEASE  | ||||
| 	[Application(Debuggable=false, Label=AppNames.AppName)]  | ||||
|   | ||||
| @@ -20,6 +20,8 @@ using Android.App; | ||||
| using Android.Content; | ||||
| using Android.OS; | ||||
| using Android.Preferences; | ||||
| using Android.Widget; | ||||
| using keepass2android.Io; | ||||
|  | ||||
| namespace keepass2android | ||||
| { | ||||
| @@ -42,10 +44,50 @@ namespace keepass2android | ||||
| 			 | ||||
| 			FindPreference(GetString(Resource.String.keyfile_key)).PreferenceChange += OnRememberKeyFileHistoryChanged; | ||||
| 			FindPreference(GetString(Resource.String.ShowUnlockedNotification_key)).PreferenceChange += OnShowUnlockedNotificationChanged;; | ||||
| 			FindPreference(GetString(Resource.String.UseOfflineCache_key)).PreferenceChange += OnUseOfflineCacheChanged; | ||||
|  | ||||
| 			FindPreference(GetString(Resource.String.db_key)).Enabled = false; | ||||
| 		} | ||||
|  | ||||
| 		private void OnUseOfflineCacheChanged(object sender, Preference.PreferenceChangeEventArgs e) | ||||
| 		{ | ||||
| 			//ensure the user gets a matching database | ||||
| 			if (App.Kp2a.GetDb().Loaded && !App.Kp2a.GetDb().Ioc.IsLocalFile()) | ||||
| 				App.Kp2a.LockDatabase(false); | ||||
|  | ||||
| 			if (!(bool)e.NewValue) | ||||
| 			{ | ||||
| 				AlertDialog.Builder builder = new AlertDialog.Builder(this); | ||||
| 				builder.SetTitle(GetString(Resource.String.ClearOfflineCache_title)); | ||||
|  | ||||
| 				builder.SetMessage(GetString(Resource.String.ClearOfflineCache_question)); | ||||
|  | ||||
| 				builder.SetPositiveButton(App.Kp2a.GetResourceString(UiStringKey.yes), (o, args) => | ||||
| 					 { | ||||
| 						 try | ||||
| 						 { | ||||
| 							 App.Kp2a.ClearOfflineCache(); | ||||
| 						 } | ||||
| 						 catch (Exception ex) | ||||
| 						 { | ||||
| 							 Kp2aLog.Log(ex.ToString()); | ||||
| 							 Toast.MakeText(Application.Context, ex.Message, ToastLength.Long).Show(); | ||||
| 						 } | ||||
| 					 } | ||||
| 					); | ||||
|  | ||||
| 				builder.SetNegativeButton(App.Kp2a.GetResourceString(UiStringKey.no), (o, args) => | ||||
| 				{ | ||||
| 				} | ||||
| 				); | ||||
|  | ||||
| 				Dialog dialog = builder.Create(); | ||||
| 				dialog.Show(); | ||||
|  | ||||
| 				 | ||||
| 			} | ||||
| 		} | ||||
|  | ||||
| 		internal static void OnRememberKeyFileHistoryChanged(object sender, Preference.PreferenceChangeEventArgs eventArgs) | ||||
| 		{ | ||||
| 			if (!(bool)eventArgs.NewValue) | ||||
|   | ||||
		Reference in New Issue
	
	Block a user
	 Philipp Crocoll
					Philipp Crocoll