+ SynchronizeCachedDatabase.cs: Synchronizes the local cache with the remote file. Applies merging if necessary.
+ Tests (not yet complete)
This commit is contained in:
		| @@ -105,7 +105,8 @@ namespace KeePassLib.Serialization | ||||
| 			// Not implemented and ignored in Mono < 2.10 | ||||
| 			try | ||||
| 			{ | ||||
| 				request.CachePolicy = new RequestCachePolicy(RequestCacheLevel.NoCacheNoStore); | ||||
| 				//deactivated. No longer supported in Mono 4.8?  | ||||
| 				//request.CachePolicy = new RequestCachePolicy(RequestCacheLevel.NoCacheNoStore); | ||||
| 			} | ||||
| 			catch(NotImplementedException) { } | ||||
| 			catch(Exception) { Debug.Assert(false); } | ||||
|   | ||||
| @@ -1,4 +1,5 @@ | ||||
| using System; | ||||
| using Android.App; | ||||
| using Android.Content; | ||||
| using Android.OS; | ||||
| using KeePassLib.Serialization; | ||||
| @@ -70,5 +71,6 @@ namespace keepass2android | ||||
| 		IProgressDialog CreateProgressDialog(Context ctx); | ||||
| 		IFileStorage GetFileStorage(IOConnectionInfo iocInfo); | ||||
|  | ||||
| 		void TriggerReload(Context context); | ||||
|     } | ||||
| } | ||||
| @@ -118,7 +118,7 @@ namespace keepass2android.Io | ||||
| 			try | ||||
| 			{ | ||||
| 				if (!IsCached(ioc) | ||||
| 				    || File.ReadAllText(VersionFilePath(ioc)) == File.ReadAllText(BaseVersionFilePath(ioc))) | ||||
| 				    || GetLocalVersionHash(ioc) == GetBaseVersionHash(ioc)) | ||||
| 				{ | ||||
| 					return OpenFileForReadWhenNoLocalChanges(ioc, cachedFilePath); | ||||
| 				} | ||||
| @@ -141,7 +141,7 @@ namespace keepass2android.Io | ||||
| 		{ | ||||
| 			//file is cached but has local modifications | ||||
| 			//try to upload the changes if remote file doesn't have changes as well: | ||||
| 			var hash = Calculate(ioc); | ||||
| 			var hash = CalculateHash(ioc); | ||||
|  | ||||
| 			if (File.ReadAllText(BaseVersionFilePath(ioc)) == hash) | ||||
| 			{ | ||||
| @@ -160,10 +160,9 @@ namespace keepass2android.Io | ||||
| 			return File.OpenRead(cachedFilePath); | ||||
| 		} | ||||
|  | ||||
| 		private string Calculate(IOConnectionInfo ioc) | ||||
| 		public MemoryStream GetRemoteDataAndHash(IOConnectionInfo ioc, out string hash) | ||||
| 		{ | ||||
| 			MemoryStream remoteData = new MemoryStream(); | ||||
| 			string hash; | ||||
| 			using ( | ||||
| 				HashingStreamEx hashingRemoteStream = new HashingStreamEx(_cachedStorage.OpenFileForRead(ioc), false, | ||||
| 																		  new SHA256Managed())) | ||||
| @@ -172,6 +171,14 @@ namespace keepass2android.Io | ||||
| 				hashingRemoteStream.Close(); | ||||
| 				hash = MemUtil.ByteArrayToHexString(hashingRemoteStream.Hash); | ||||
| 			} | ||||
| 			remoteData.Position = 0; | ||||
| 			return remoteData; | ||||
| 		} | ||||
|  | ||||
| 		private string CalculateHash(IOConnectionInfo ioc) | ||||
| 		{ | ||||
| 			string hash; | ||||
| 			GetRemoteDataAndHash(ioc, out hash); | ||||
| 			return hash; | ||||
| 		} | ||||
|  | ||||
| @@ -203,17 +210,7 @@ namespace keepass2android.Io | ||||
| 		{ | ||||
| 			try | ||||
| 			{ | ||||
| 				//try to write to remote: | ||||
| 				using ( | ||||
| 					IWriteTransaction remoteTrans = _cachedStorage.OpenWriteTransaction(ioc, useFileTransaction)) | ||||
| 				{ | ||||
| 					Stream remoteStream = remoteTrans.OpenFile(); | ||||
| 					cachedData.CopyTo(remoteStream); | ||||
| 					remoteStream.Close(); | ||||
| 					remoteTrans.CommitWrite(); | ||||
| 				} | ||||
| 				//success. Update base-version of cache: | ||||
| 				File.WriteAllText(VersionFilePath(ioc), hash); | ||||
| 				UpdateRemoteFile(cachedData, ioc, useFileTransaction, hash); | ||||
| 			} | ||||
| 			catch (Exception e) | ||||
| 			{ | ||||
| @@ -224,6 +221,33 @@ namespace keepass2android.Io | ||||
| 			} | ||||
| 		} | ||||
|  | ||||
| 		private void UpdateRemoteFile(Stream cachedData, IOConnectionInfo ioc, bool useFileTransaction, string hash) | ||||
| 		{ | ||||
| 			//try to write to remote: | ||||
| 			using ( | ||||
| 				IWriteTransaction remoteTrans = _cachedStorage.OpenWriteTransaction(ioc, useFileTransaction)) | ||||
| 			{ | ||||
| 				Stream remoteStream = remoteTrans.OpenFile(); | ||||
| 				cachedData.CopyTo(remoteStream); | ||||
| 				remoteStream.Close(); | ||||
| 				remoteTrans.CommitWrite(); | ||||
| 			} | ||||
| 			//success. Update base-version of cache: | ||||
| 			File.WriteAllText(BaseVersionFilePath(ioc), hash); | ||||
| 			File.WriteAllText(VersionFilePath(ioc), hash); | ||||
| 		} | ||||
|  | ||||
| 		public void UpdateRemoteFile(IOConnectionInfo ioc, bool useFileTransaction) | ||||
| 		{ | ||||
| 			using (Stream cachedData = File.OpenRead(CachedFilePath(ioc))) | ||||
| 			{ | ||||
| 				UpdateRemoteFile(cachedData, ioc, useFileTransaction, GetLocalVersionHash(ioc));	 | ||||
| 			} | ||||
| 			 | ||||
| 		} | ||||
|  | ||||
| 		 | ||||
|  | ||||
| 		private class CachedWriteTransaction: IWriteTransaction | ||||
| 		{ | ||||
| 			private class CachedWriteMemoryStream : MemoryStream | ||||
| @@ -231,6 +255,7 @@ namespace keepass2android.Io | ||||
| 				private readonly IOConnectionInfo ioc; | ||||
| 				private readonly CachingFileStorage _cachingFileStorage; | ||||
| 				private readonly bool _useFileTransaction; | ||||
| 				private bool _closed; | ||||
|  | ||||
| 				public CachedWriteMemoryStream(IOConnectionInfo ioc, CachingFileStorage cachingFileStorage, bool useFileTransaction) | ||||
| 				{ | ||||
| @@ -242,6 +267,8 @@ namespace keepass2android.Io | ||||
|  | ||||
| 				public override void Close() | ||||
| 				{ | ||||
| 					if (_closed) return; | ||||
|  | ||||
| 					//write file to cache: | ||||
| 					//(note: this might overwrite local changes. It's assumed that a sync operation or check was performed before | ||||
| 					string hash; | ||||
| @@ -257,9 +284,20 @@ namespace keepass2android.Io | ||||
| 					File.WriteAllText(_cachingFileStorage.VersionFilePath(ioc), hash); | ||||
| 					//update file on remote. This might overwrite changes there as well, see above. | ||||
| 					Position = 0; | ||||
| 					if (_cachingFileStorage.IsCached(ioc)) | ||||
| 					{ | ||||
| 						//if the file already is in the cache, it's ok if writing to remote fails. | ||||
| 						_cachingFileStorage.TryUpdateRemoteFile(this, ioc, _useFileTransaction, hash); | ||||
| 					} | ||||
| 					else | ||||
| 					{ | ||||
| 						//if not, we don't accept a failure (e.g. invalid credentials would always remain a problem) | ||||
| 						_cachingFileStorage.UpdateRemoteFile(this, ioc, _useFileTransaction, hash); | ||||
| 					} | ||||
|  | ||||
| 					base.Close(); | ||||
|  | ||||
| 					_closed = true; | ||||
| 				} | ||||
|  | ||||
| 			} | ||||
| @@ -280,8 +318,19 @@ namespace keepass2android.Io | ||||
| 			public void Dispose() | ||||
| 			{ | ||||
| 				if (!_committed) | ||||
| 				{ | ||||
| 					try | ||||
| 					{ | ||||
| 						_memoryStream.Dispose(); | ||||
| 					} | ||||
| 					catch (ObjectDisposedException e) | ||||
| 					{ | ||||
| 						Kp2aLog.Log("Ignoring exception in Dispose: "+e); | ||||
| 					} | ||||
| 					 | ||||
| 				} | ||||
| 					 | ||||
| 			} | ||||
|  | ||||
| 			public Stream OpenFile() | ||||
| 			{ | ||||
| @@ -321,5 +370,32 @@ namespace keepass2android.Io | ||||
| 			return UrlUtil.StripExtension( | ||||
| 				UrlUtil.GetFileName(ioc.Path)); | ||||
| 		} | ||||
|  | ||||
|  | ||||
| 		public string GetBaseVersionHash(IOConnectionInfo ioc) | ||||
| 		{ | ||||
| 			return File.ReadAllText(BaseVersionFilePath(ioc)); | ||||
| 		} | ||||
| 		public string GetLocalVersionHash(IOConnectionInfo ioc) | ||||
| 		{ | ||||
| 			return File.ReadAllText(VersionFilePath(ioc)); | ||||
| 		} | ||||
| 		public bool HasLocalChanges(IOConnectionInfo ioc) | ||||
| 		{ | ||||
| 			return IsCached(ioc) | ||||
| 			       && GetLocalVersionHash(ioc) != GetBaseVersionHash(ioc); | ||||
| 		} | ||||
|  | ||||
| 		public Stream OpenRemoteForReadIfAvailable(IOConnectionInfo ioc) | ||||
| 		{ | ||||
| 			try | ||||
| 			{ | ||||
| 				return _cachedStorage.OpenFileForRead(ioc); | ||||
| 			} | ||||
| 			catch (Exception) | ||||
| 			{ | ||||
| 				return File.OpenRead(CachedFilePath(ioc)); | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
|   | ||||
| @@ -51,6 +51,7 @@ | ||||
|     <Reference Include="System.Xml" /> | ||||
|   </ItemGroup> | ||||
|   <ItemGroup> | ||||
|     <Compile Include="database\SynchronizeCachedDatabase.cs" /> | ||||
|     <Compile Include="Io\BuiltInFileStorage.cs" /> | ||||
|     <Compile Include="Io\CachingFileStorage.cs" /> | ||||
|     <Compile Include="Io\IFileStorage.cs" /> | ||||
|   | ||||
| @@ -34,6 +34,11 @@ namespace keepass2android | ||||
| 		yes, | ||||
| 		no, | ||||
| 		YesSynchronize,  | ||||
| 		NoOverwrite | ||||
| 		NoOverwrite, | ||||
| 		SynchronizingCachedDatabase, | ||||
| 		DownloadingRemoteFile, | ||||
| 		UploadingFile, | ||||
| 		FilesInSync, | ||||
| 		SynchronizedDatabaseSuccessfully | ||||
|     } | ||||
| } | ||||
							
								
								
									
										98
									
								
								src/Kp2aBusinessLogic/database/SynchronizeCachedDatabase.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										98
									
								
								src/Kp2aBusinessLogic/database/SynchronizeCachedDatabase.cs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,98 @@ | ||||
| using System; | ||||
| using System.Collections.Generic; | ||||
| using System.IO; | ||||
| using System.Text; | ||||
| using Android.App; | ||||
| using Android.Content; | ||||
| using KeePassLib.Serialization; | ||||
| using keepass2android.Io; | ||||
|  | ||||
| namespace keepass2android | ||||
| { | ||||
| 	public class SynchronizeCachedDatabase: RunnableOnFinish  | ||||
| 	{ | ||||
| 		private readonly Context _context; | ||||
| 		private readonly IKp2aApp _app; | ||||
|  | ||||
| 		public SynchronizeCachedDatabase(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 non-cached database!"); | ||||
| 				} | ||||
| 				StatusLogger.UpdateMessage(UiStringKey.SynchronizingCachedDatabase); | ||||
| 				CachingFileStorage cachingFileStorage = (CachingFileStorage) fileStorage; | ||||
|  | ||||
| 				//download file from remote location and calculate hash: | ||||
| 				StatusLogger.UpdateSubMessage(_app.GetResourceString(UiStringKey.DownloadingRemoteFile)); | ||||
| 				string hash; | ||||
| 				//todo: catch filenotfound and upload then | ||||
| 				MemoryStream remoteData = cachingFileStorage.GetRemoteDataAndHash(ioc, out hash); | ||||
|  | ||||
| 				//todo: what happens if something fails here? | ||||
|  | ||||
| 				//check if remote file was modified: | ||||
| 				if (cachingFileStorage.GetBaseVersionHash(ioc) != hash) | ||||
| 				{ | ||||
| 					//remote file is unmodified | ||||
| 					if (cachingFileStorage.HasLocalChanges(ioc)) | ||||
| 					{ | ||||
| 						//conflict! need to merge | ||||
| 						SaveDb saveDb = new SaveDb(_context, _app, new ActionOnFinish((success, result) => | ||||
| 							{ | ||||
| 								if (!success) | ||||
| 								{ | ||||
| 									Finish(false, result); | ||||
| 								} | ||||
| 								else | ||||
| 								{ | ||||
| 									Finish(true, _app.GetResourceString(UiStringKey.SynchronizedDatabaseSuccessfully)); | ||||
| 								} | ||||
| 							}), false, remoteData); | ||||
| 						saveDb.Run(); | ||||
| 					} | ||||
| 					else | ||||
| 					{ | ||||
| 						//only the remote file was modified -> reload database. | ||||
| 						//note: it's best to lock the database and do a complete reload here (also better for UI consistency in case something goes wrong etc.) | ||||
| 						_app.TriggerReload(_context); | ||||
| 						Finish(true); | ||||
| 					} | ||||
| 				} | ||||
| 				else | ||||
| 				{ | ||||
| 					//remote file is unmodified | ||||
| 					if (cachingFileStorage.HasLocalChanges(ioc)) | ||||
| 					{ | ||||
| 						//but we have local changes -> upload: | ||||
| 						StatusLogger.UpdateSubMessage(_app.GetResourceString(UiStringKey.UploadingFile)); | ||||
| 						cachingFileStorage.UpdateRemoteFile(ioc, _app.GetBooleanPreference(PreferenceKey.UseFileTransactions)); | ||||
| 						StatusLogger.UpdateSubMessage(""); | ||||
| 						Finish(true, _app.GetResourceString(UiStringKey.SynchronizedDatabaseSuccessfully)); | ||||
| 					} | ||||
| 					else | ||||
| 					{ | ||||
| 						//files are in sync: just set the result | ||||
| 						Finish(true, _app.GetResourceString(UiStringKey.FilesInSync)); | ||||
| 					} | ||||
| 				} | ||||
| 			} | ||||
| 			catch (Exception e) | ||||
| 			{ | ||||
| 				Finish(false, e.Message); | ||||
| 			} | ||||
| 			 | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
| @@ -34,15 +34,39 @@ namespace keepass2android | ||||
| 	public class SaveDb : RunnableOnFinish { | ||||
| 		private readonly IKp2aApp _app; | ||||
| 		private readonly bool _dontSave; | ||||
|  | ||||
| 		/// <summary> | ||||
| 		/// stream for reading the data from the original file. If this is set to a non-null value, we know we need to sync | ||||
| 		/// </summary> | ||||
| 		private readonly Stream _streamForOrigFile; | ||||
| 		private readonly Context _ctx; | ||||
| 		private Thread _workerThread; | ||||
|  | ||||
| 		public SaveDb(Context ctx, IKp2aApp app, OnFinish finish, bool dontSave): base(finish) { | ||||
| 		public SaveDb(Context ctx, IKp2aApp app, OnFinish finish, bool dontSave) | ||||
| 			: base(finish) | ||||
| 		{ | ||||
| 			_ctx = ctx; | ||||
| 			_app = app; | ||||
| 			_dontSave = dontSave; | ||||
| 		} | ||||
| 		 | ||||
| 		/// <summary> | ||||
| 		/// Constructor for sync | ||||
| 		/// </summary> | ||||
| 		/// <param name="ctx"></param> | ||||
| 		/// <param name="app"></param> | ||||
| 		/// <param name="finish"></param> | ||||
| 		/// <param name="dontSave"></param> | ||||
| 		/// <param name="streamForOrigFile">Stream for reading the data from the (changed) original location</param> | ||||
| 		public SaveDb(Context ctx, IKp2aApp app, OnFinish finish, bool dontSave, Stream streamForOrigFile) | ||||
| 			: base(finish) | ||||
| 		{ | ||||
| 			_ctx = ctx; | ||||
| 			_app = app; | ||||
| 			_dontSave = dontSave; | ||||
| 			_streamForOrigFile = streamForOrigFile; | ||||
| 		} | ||||
|  | ||||
| 		public SaveDb(Context ctx, IKp2aApp app, OnFinish finish) | ||||
| 			: base(finish) | ||||
| 		{ | ||||
| @@ -63,6 +87,8 @@ namespace keepass2android | ||||
| 					IOConnectionInfo ioc = _app.GetDb().Ioc; | ||||
| 					IFileStorage fileStorage = _app.GetFileStorage(ioc); | ||||
|  | ||||
| 					if (_streamForOrigFile == null) | ||||
| 					{ | ||||
| 						if ((!_app.GetBooleanPreference(PreferenceKey.CheckForFileChangesOnSave)) | ||||
| 							|| (_app.GetDb().KpDatabase.HashOfFileOnDisk == null)) //first time saving | ||||
| 						{ | ||||
| @@ -70,11 +96,16 @@ namespace keepass2android | ||||
| 							Finish(true); | ||||
| 							return; | ||||
| 						}	 | ||||
| 					} | ||||
| 					 | ||||
|  | ||||
|  | ||||
| 					if (fileStorage.CheckForFileChangeFast(ioc, _app.GetDb().LastFileVersion)  //first try to use the fast change detection | ||||
| 						|| (FileHashChanged(ioc, _app.GetDb().KpDatabase.HashOfFileOnDisk))) //if that fails, hash the file and compare: | ||||
|  | ||||
| 					if ( | ||||
| 						(_streamForOrigFile != null) | ||||
| 						|| fileStorage.CheckForFileChangeFast(ioc, _app.GetDb().LastFileVersion)  //first try to use the fast change detection | ||||
| 						|| (FileHashChanged(ioc, _app.GetDb().KpDatabase.HashOfFileOnDisk)) //if that fails, hash the file and compare: | ||||
| 						) | ||||
| 					{ | ||||
|  | ||||
| 						//ask user... | ||||
| @@ -183,12 +214,28 @@ namespace keepass2android | ||||
| 			pwImp.MemoryProtection = pwDatabase.MemoryProtection.CloneDeep(); | ||||
| 			pwImp.MasterKey = pwDatabase.MasterKey; | ||||
| 			KdbxFile kdbx = new KdbxFile(pwImp); | ||||
| 			kdbx.Load(fileStorage.OpenFileForRead(ioc), KdbpFile.GetFormatToUse(ioc), null); | ||||
| 			kdbx.Load(GetStreamForBaseFile(fileStorage, ioc), KdbpFile.GetFormatToUse(ioc), null); | ||||
|  | ||||
| 			pwDatabase.MergeIn(pwImp, PwMergeMethod.Synchronize, null);  | ||||
|  | ||||
| 		} | ||||
|  | ||||
| 		private Stream GetStreamForBaseFile(IFileStorage fileStorage, IOConnectionInfo ioc) | ||||
| 		{ | ||||
| 			//if we have the original file already available: use it | ||||
| 			if (_streamForOrigFile != null) | ||||
| 				return _streamForOrigFile; | ||||
|  | ||||
| 			//if the file storage caches, it might return the local data in case of a conflict. This would result in data loss | ||||
| 			// so we need to ensure we get the data from remote (only if the remote file is available. if not, we won't overwrite anything) | ||||
| 			CachingFileStorage cachingFileStorage = fileStorage as CachingFileStorage; | ||||
| 			if (cachingFileStorage != null) | ||||
| 			{ | ||||
| 				return cachingFileStorage.OpenRemoteForReadIfAvailable(ioc); | ||||
| 			} | ||||
| 			return fileStorage.OpenFileForRead(ioc); | ||||
| 		} | ||||
|  | ||||
| 		private void PerformSaveWithoutCheck(IFileStorage fileStorage, IOConnectionInfo ioc) | ||||
| 		{ | ||||
| 			StatusLogger.UpdateSubMessage(""); | ||||
|   | ||||
| @@ -53,6 +53,7 @@ | ||||
|   <ItemGroup> | ||||
|     <None Include="Additions\AboutAdditions.txt" /> | ||||
|     <None Include="Jars\AboutJars.txt" /> | ||||
|     <LibraryProjectZip Include="project.zip" /> | ||||
|   </ItemGroup> | ||||
|   <ItemGroup> | ||||
|     <TransformFile Include="Transforms\EnumFields.xml" /> | ||||
| @@ -60,9 +61,4 @@ | ||||
|     <TransformFile Include="Transforms\Metadata.xml" /> | ||||
|   </ItemGroup> | ||||
|   <Import Project="$(MSBuildExtensionsPath)\Novell\Xamarin.Android.Bindings.targets" /> | ||||
|   <ItemGroup> | ||||
|     <LibraryProjectZip Include="..\java\KP2ASoftKeyboard\project.zip"> | ||||
|       <Link>project.zip</Link> | ||||
|     </LibraryProjectZip> | ||||
|   </ItemGroup> | ||||
| </Project> | ||||
| @@ -1,4 +1,5 @@ | ||||
| <metadata> | ||||
| 	<remove-node path="/api/package[@name='keepass2android.softkeyboard']/class[@name='KP2AKeyboard']" /> | ||||
| 	<!-- | ||||
|   This sample removes the class: android.support.v4.content.AsyncTaskLoader.LoadTask: | ||||
|   <remove-node path="/api/package[@name='android.support.v4.content']/class[@name='AsyncTaskLoader.LoadTask']" /> | ||||
|   | ||||
| @@ -15,6 +15,7 @@ | ||||
|     <AndroidApplication>true</AndroidApplication> | ||||
|     <AndroidResgenFile>Resources\Resource.Designer.cs</AndroidResgenFile> | ||||
|     <GenerateSerializationAssemblies>Off</GenerateSerializationAssemblies> | ||||
|     <AndroidManifest>Properties\AndroidManifest.xml</AndroidManifest> | ||||
|   </PropertyGroup> | ||||
|   <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' "> | ||||
|     <DebugSymbols>true</DebugSymbols> | ||||
| @@ -58,16 +59,19 @@ | ||||
|   <ItemGroup> | ||||
|     <Compile Include="ProgressDialogStub.cs" /> | ||||
|     <Compile Include="TestBase.cs" /> | ||||
|     <Compile Include="TestCacheSupervisor.cs" /> | ||||
|     <Compile Include="TestDrawableFactory.cs" /> | ||||
|     <Compile Include="TestCreateDb.cs" /> | ||||
|     <Compile Include="MainActivity.cs" /> | ||||
|     <Compile Include="Resources\Resource.Designer.cs" /> | ||||
|     <Compile Include="Properties\AssemblyInfo.cs" /> | ||||
|     <Compile Include="TestFileStorage.cs" /> | ||||
|     <Compile Include="TestKp2aApp.cs" /> | ||||
|     <Compile Include="TestLoadDb.cs" /> | ||||
|     <Compile Include="TestLoadDbCredentials.cs" /> | ||||
|     <Compile Include="TestCachingFileStorage.cs" /> | ||||
|     <Compile Include="TestSaveDb.cs" /> | ||||
|     <Compile Include="TestSynchronizeCachedDatabase.cs" /> | ||||
|   </ItemGroup> | ||||
|   <ItemGroup> | ||||
|     <None Include="Resources\AboutResources.txt" /> | ||||
| @@ -96,6 +100,9 @@ | ||||
|       <Name>MonoDroidUnitTesting</Name> | ||||
|     </ProjectReference> | ||||
|   </ItemGroup> | ||||
|   <ItemGroup> | ||||
|     <TransformFile Include="Properties\AndroidManifest.xml" /> | ||||
|   </ItemGroup> | ||||
|   <Import Project="$(MSBuildExtensionsPath)\Xamarin\Android\Xamarin.Android.CSharp.targets" /> | ||||
|   <!-- To modify your build process, add your task inside one of the targets below and uncomment it.  | ||||
|      Other similar extension points exist, see Microsoft.Common.targets. | ||||
|   | ||||
| @@ -18,8 +18,8 @@ namespace Kp2aUnitTests | ||||
|         { | ||||
|             TestRunner runner = new TestRunner(); | ||||
|             // Run all tests from this assembly | ||||
|             //runner.AddTests(Assembly.GetExecutingAssembly()); | ||||
| 			runner.AddTests(new List<Type> { typeof(TestCachingFileStorage) }); | ||||
|             runner.AddTests(Assembly.GetExecutingAssembly()); | ||||
| 			//runner.AddTests(new List<Type> { typeof(TestSynchronizeCachedDatabase) }); | ||||
| 			//runner.AddTests(typeof(TestCachingFileStorage).GetMethod("TestSaveToRemote")); | ||||
| 			//runner.AddTests(typeof(TestLoadDb).GetMethod("TestLoadKdbpWithPasswordOnly")); | ||||
| 			//runner.AddTests(typeof(TestSaveDb).GetMethod("TestLoadKdbxAndSaveKdbp_TestIdenticalFiles")); | ||||
|   | ||||
							
								
								
									
										5
									
								
								src/Kp2aUnitTests/Properties/AndroidManifest.xml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										5
									
								
								src/Kp2aUnitTests/Properties/AndroidManifest.xml
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,5 @@ | ||||
| <?xml version="1.0" encoding="utf-8"?> | ||||
| <manifest xmlns:android="http://schemas.android.com/apk/res/android"> | ||||
| 	<uses-sdk android:minSdkVersion="8" android:targetSdkVersion="8" /> | ||||
| 	<application></application> | ||||
| </manifest> | ||||
| @@ -61,9 +61,9 @@ namespace Kp2aUnitTests | ||||
| 			get { return DefaultDirectory + "savedWithDesktop/"; } | ||||
| 		} | ||||
|  | ||||
| 		protected IKp2aApp LoadDatabase(string filename, string password, string keyfile) | ||||
| 		protected TestKp2aApp LoadDatabase(string filename, string password, string keyfile) | ||||
| 		{ | ||||
| 			IKp2aApp app = new TestKp2aApp(); | ||||
| 			var app = CreateTestKp2aApp(); | ||||
| 			app.CreateNewDatabase(); | ||||
| 			bool loadSuccesful = false; | ||||
| 			LoadDb task = new LoadDb(app, new IOConnectionInfo() { Path = filename }, password, keyfile, new ActionOnFinish((success, message) => | ||||
| @@ -81,6 +81,12 @@ namespace Kp2aUnitTests | ||||
| 			return app; | ||||
| 		} | ||||
|  | ||||
| 		protected virtual TestKp2aApp CreateTestKp2aApp() | ||||
| 		{ | ||||
| 			TestKp2aApp app = new TestKp2aApp(); | ||||
| 			return app; | ||||
| 		} | ||||
|  | ||||
| 		protected void SaveDatabase(IKp2aApp app) | ||||
| 		{ | ||||
| 			bool saveSuccesful = TrySaveDatabase(app); | ||||
| @@ -104,16 +110,16 @@ namespace Kp2aUnitTests | ||||
| 			return saveSuccesful; | ||||
| 		} | ||||
|  | ||||
| 		protected IKp2aApp SetupAppWithDefaultDatabase() | ||||
| 		protected TestKp2aApp SetupAppWithDefaultDatabase() | ||||
| 		{ | ||||
| 			string filename = DefaultFilename; | ||||
|  | ||||
| 			return SetupAppWithDatabase(filename); | ||||
| 		} | ||||
|  | ||||
| 		protected IKp2aApp SetupAppWithDatabase(string filename) | ||||
| 		protected TestKp2aApp SetupAppWithDatabase(string filename) | ||||
| 		{ | ||||
| 			IKp2aApp app = new TestKp2aApp(); | ||||
| 			TestKp2aApp app = CreateTestKp2aApp(); | ||||
|  | ||||
| 			IOConnectionInfo ioc = new IOConnectionInfo {Path = filename}; | ||||
| 			Database db = app.CreateNewDatabase(); | ||||
|   | ||||
							
								
								
									
										29
									
								
								src/Kp2aUnitTests/TestCacheSupervisor.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										29
									
								
								src/Kp2aUnitTests/TestCacheSupervisor.cs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,29 @@ | ||||
| using System; | ||||
| using KeePassLib.Serialization; | ||||
| using keepass2android.Io; | ||||
|  | ||||
| namespace Kp2aUnitTests | ||||
| { | ||||
| 	class TestCacheSupervisor: ICacheSupervisor | ||||
| 	{ | ||||
| 		public bool CouldntOpenFromRemoteCalled { get; set; } | ||||
| 		public bool CouldntSaveToRemoteCalled { get; set; } | ||||
| 		public bool NotifyOpenFromLocalDueToConflictCalled { get; set; } | ||||
|  | ||||
|  | ||||
| 		public void CouldntSaveToRemote(IOConnectionInfo ioc, Exception e) | ||||
| 		{ | ||||
| 			CouldntSaveToRemoteCalled = true; | ||||
| 		} | ||||
|  | ||||
| 		public void CouldntOpenFromRemote(IOConnectionInfo ioc, Exception ex) | ||||
| 		{ | ||||
| 			CouldntOpenFromRemoteCalled = true; | ||||
| 		} | ||||
|  | ||||
| 		public void NotifyOpenFromLocalDueToConflict(IOConnectionInfo ioc) | ||||
| 		{ | ||||
| 			NotifyOpenFromLocalDueToConflictCalled = true; | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
| @@ -1,5 +1,4 @@ | ||||
| using System; | ||||
| using System.Collections.Generic; | ||||
| using System.Collections.Generic; | ||||
| using System.IO; | ||||
| using System.Linq; | ||||
| using System.Text; | ||||
| @@ -20,118 +19,6 @@ namespace Kp2aUnitTests | ||||
| 		private string _defaultCacheFileContents = "default contents"; | ||||
| 		private TestCacheSupervisor _testCacheSupervisor; | ||||
|  | ||||
| 		class TestCacheSupervisor: ICacheSupervisor | ||||
| 		{ | ||||
| 			public bool CouldntOpenFromRemoteCalled { get; set; } | ||||
| 			public bool CouldntSaveToRemoteCalled { get; set; } | ||||
| 			public bool NotifyOpenFromLocalDueToConflictCalled { get; set; } | ||||
|  | ||||
|  | ||||
| 			public void CouldntSaveToRemote(IOConnectionInfo ioc, Exception e) | ||||
| 			{ | ||||
| 				CouldntSaveToRemoteCalled = true; | ||||
| 			} | ||||
|  | ||||
| 			public void CouldntOpenFromRemote(IOConnectionInfo ioc, Exception ex) | ||||
| 			{ | ||||
| 				CouldntOpenFromRemoteCalled = true; | ||||
| 			} | ||||
|  | ||||
| 			public void NotifyOpenFromLocalDueToConflict(IOConnectionInfo ioc) | ||||
| 			{ | ||||
| 				NotifyOpenFromLocalDueToConflictCalled = true; | ||||
| 			} | ||||
| 		} | ||||
|  | ||||
| 		public class TestFileStorage: IFileStorage | ||||
| 		{ | ||||
| 			private BuiltInFileStorage _builtIn = new BuiltInFileStorage(); | ||||
|  | ||||
| 			public bool Offline { get; set; } | ||||
|  | ||||
|  | ||||
| 			public void DeleteFile(IOConnectionInfo ioc) | ||||
| 			{ | ||||
| 				if (Offline) | ||||
| 					throw new IOException("offline"); | ||||
| 				_builtIn.DeleteFile(ioc); | ||||
| 			} | ||||
|  | ||||
| 			public bool CheckForFileChangeFast(IOConnectionInfo ioc, string previousFileVersion) | ||||
| 			{ | ||||
| 				if (Offline) | ||||
| 					return false; | ||||
| 				return _builtIn.CheckForFileChangeFast(ioc, previousFileVersion); | ||||
| 			} | ||||
|  | ||||
| 			public string GetCurrentFileVersionFast(IOConnectionInfo ioc) | ||||
| 			{ | ||||
| 				if (Offline) | ||||
| 					throw new IOException("offline"); | ||||
| 				return _builtIn.GetCurrentFileVersionFast(ioc); | ||||
| 			} | ||||
|  | ||||
| 			public Stream OpenFileForRead(IOConnectionInfo ioc) | ||||
| 			{ | ||||
| 				if (Offline) | ||||
| 					throw new IOException("offline"); | ||||
| 				return _builtIn.OpenFileForRead(ioc); | ||||
| 			} | ||||
|  | ||||
| 			public IWriteTransaction OpenWriteTransaction(IOConnectionInfo ioc, bool useFileTransaction) | ||||
| 			{ | ||||
| 				if (Offline) | ||||
| 					throw new IOException("offline"); | ||||
| 				return new TestFileTransaction(ioc, useFileTransaction, Offline); | ||||
| 			} | ||||
|  | ||||
| 			public class TestFileTransaction : IWriteTransaction | ||||
| 			{ | ||||
| 				private readonly bool _offline; | ||||
| 				private readonly FileTransactionEx _transaction; | ||||
|  | ||||
| 				public TestFileTransaction(IOConnectionInfo ioc, bool useFileTransaction, bool offline) | ||||
| 				{ | ||||
| 					_offline = offline; | ||||
| 					_transaction = new FileTransactionEx(ioc, useFileTransaction); | ||||
| 				} | ||||
|  | ||||
| 				public void Dispose() | ||||
| 				{ | ||||
|  | ||||
| 				} | ||||
|  | ||||
| 				public Stream OpenFile() | ||||
| 				{ | ||||
| 					if (_offline) | ||||
| 						throw new IOException("offline"); | ||||
| 					return _transaction.OpenWrite(); | ||||
| 				} | ||||
|  | ||||
| 				public void CommitWrite() | ||||
| 				{ | ||||
| 					if (_offline) | ||||
| 						throw new IOException("offline"); | ||||
| 					_transaction.CommitWrite(); | ||||
| 				} | ||||
| 			} | ||||
|  | ||||
| 			public bool CompleteIoId() | ||||
| 			{ | ||||
| 				throw new NotImplementedException(); | ||||
| 			} | ||||
|  | ||||
| 			public bool? FileExists() | ||||
| 			{ | ||||
| 				throw new NotImplementedException(); | ||||
| 			} | ||||
|  | ||||
| 			public string GetFilenameWithoutPathAndExt(IOConnectionInfo ioc) | ||||
| 			{ | ||||
| 				return _builtIn.GetFilenameWithoutPathAndExt(ioc); | ||||
| 			} | ||||
| 		} | ||||
|  | ||||
| 		/// <summary> | ||||
| 		/// Tests correct behavior in case that either remote or cache are not available | ||||
| 		/// </summary> | ||||
|   | ||||
							
								
								
									
										96
									
								
								src/Kp2aUnitTests/TestFileStorage.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										96
									
								
								src/Kp2aUnitTests/TestFileStorage.cs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,96 @@ | ||||
| using System; | ||||
| using System.IO; | ||||
| using KeePassLib.Serialization; | ||||
| using keepass2android.Io; | ||||
|  | ||||
| namespace Kp2aUnitTests | ||||
| { | ||||
| 	internal class TestFileStorage: IFileStorage | ||||
| 	{ | ||||
| 		private BuiltInFileStorage _builtIn = new BuiltInFileStorage(); | ||||
|  | ||||
| 		public bool Offline { get; set; } | ||||
|  | ||||
|  | ||||
| 		public void DeleteFile(IOConnectionInfo ioc) | ||||
| 		{ | ||||
| 			if (Offline) | ||||
| 				throw new IOException("offline"); | ||||
| 			_builtIn.DeleteFile(ioc); | ||||
| 		} | ||||
|  | ||||
| 		public bool CheckForFileChangeFast(IOConnectionInfo ioc, string previousFileVersion) | ||||
| 		{ | ||||
| 			if (Offline) | ||||
| 				return false; | ||||
| 			return _builtIn.CheckForFileChangeFast(ioc, previousFileVersion); | ||||
| 		} | ||||
|  | ||||
| 		public string GetCurrentFileVersionFast(IOConnectionInfo ioc) | ||||
| 		{ | ||||
| 			if (Offline) | ||||
| 				throw new IOException("offline"); | ||||
| 			return _builtIn.GetCurrentFileVersionFast(ioc); | ||||
| 		} | ||||
|  | ||||
| 		public Stream OpenFileForRead(IOConnectionInfo ioc) | ||||
| 		{ | ||||
| 			if (Offline) | ||||
| 				throw new IOException("offline"); | ||||
| 			return _builtIn.OpenFileForRead(ioc); | ||||
| 		} | ||||
|  | ||||
| 		public IWriteTransaction OpenWriteTransaction(IOConnectionInfo ioc, bool useFileTransaction) | ||||
| 		{ | ||||
| 			if (Offline) | ||||
| 				throw new IOException("offline"); | ||||
| 			return new TestFileTransaction(ioc, useFileTransaction, Offline); | ||||
| 		} | ||||
|  | ||||
| 		public class TestFileTransaction : IWriteTransaction | ||||
| 		{ | ||||
| 			private readonly bool _offline; | ||||
| 			private readonly FileTransactionEx _transaction; | ||||
|  | ||||
| 			public TestFileTransaction(IOConnectionInfo ioc, bool useFileTransaction, bool offline) | ||||
| 			{ | ||||
| 				_offline = offline; | ||||
| 				_transaction = new FileTransactionEx(ioc, useFileTransaction); | ||||
| 			} | ||||
|  | ||||
| 			public void Dispose() | ||||
| 			{ | ||||
|  | ||||
| 			} | ||||
|  | ||||
| 			public Stream OpenFile() | ||||
| 			{ | ||||
| 				if (_offline) | ||||
| 					throw new IOException("offline"); | ||||
| 				return _transaction.OpenWrite(); | ||||
| 			} | ||||
|  | ||||
| 			public void CommitWrite() | ||||
| 			{ | ||||
| 				if (_offline) | ||||
| 					throw new IOException("offline"); | ||||
| 				_transaction.CommitWrite(); | ||||
| 			} | ||||
| 		} | ||||
|  | ||||
| 		public bool CompleteIoId() | ||||
| 		{ | ||||
| 			throw new NotImplementedException(); | ||||
| 		} | ||||
|  | ||||
| 		public bool? FileExists() | ||||
| 		{ | ||||
| 			throw new NotImplementedException(); | ||||
| 		} | ||||
|  | ||||
| 		public string GetFilenameWithoutPathAndExt(IOConnectionInfo ioc) | ||||
| 		{ | ||||
| 			return _builtIn.GetFilenameWithoutPathAndExt(ioc); | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
| @@ -1,5 +1,6 @@ | ||||
| using System; | ||||
| using System.Collections.Generic; | ||||
| using Android.App; | ||||
| using Android.Content; | ||||
| using Android.OS; | ||||
| using KeePassLib.Serialization; | ||||
| @@ -22,6 +23,7 @@ namespace Kp2aUnitTests | ||||
| 		private YesNoCancelResult _yesNoCancelResult = YesNoCancelResult.Yes; | ||||
| 		private Dictionary<PreferenceKey, bool> _preferences = new Dictionary<PreferenceKey, bool>(); | ||||
|  | ||||
|  | ||||
| 		public void SetShutdown() | ||||
| 		{ | ||||
| 			 | ||||
| @@ -40,7 +42,7 @@ namespace Kp2aUnitTests | ||||
| 		public Database CreateNewDatabase() | ||||
| 		{ | ||||
| 			TestDrawableFactory testDrawableFactory = new TestDrawableFactory(); | ||||
| 			_db = new Database(testDrawableFactory, new TestKp2aApp()); | ||||
| 			_db = new Database(testDrawableFactory, this); | ||||
| 			return _db; | ||||
|  | ||||
| 		} | ||||
| @@ -98,6 +100,9 @@ namespace Kp2aUnitTests | ||||
| 		public Handler UiThreadHandler { | ||||
| 			get { return null; } //ensure everything runs in the same thread. Otherwise the OnFinish-callback would run after the test has already finished (with failure) | ||||
| 		} | ||||
|  | ||||
| 		public IFileStorage FileStorage { get; set; } | ||||
|  | ||||
| 		public IProgressDialog CreateProgressDialog(Context ctx) | ||||
| 		{ | ||||
| 			return new ProgressDialogStub(); | ||||
| @@ -105,7 +110,20 @@ namespace Kp2aUnitTests | ||||
|  | ||||
| 		public IFileStorage GetFileStorage(IOConnectionInfo iocInfo) | ||||
| 		{ | ||||
| 			return new BuiltInFileStorage(); | ||||
| 			return FileStorage; | ||||
| 		} | ||||
|  | ||||
| 		 | ||||
| 		public bool TriggerReloadCalled; | ||||
|  | ||||
| 		public TestKp2aApp() | ||||
| 		{ | ||||
| 			FileStorage = new BuiltInFileStorage(); | ||||
| 		} | ||||
|  | ||||
| 		public void TriggerReload(Context ctx) | ||||
| 		{ | ||||
| 			TriggerReloadCalled = true; | ||||
| 		} | ||||
|  | ||||
| 		public void SetYesNoCancelResult(YesNoCancelResult yesNoCancelResult) | ||||
|   | ||||
							
								
								
									
										117
									
								
								src/Kp2aUnitTests/TestSynchronizeCachedDatabase.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										117
									
								
								src/Kp2aUnitTests/TestSynchronizeCachedDatabase.cs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,117 @@ | ||||
| using System; | ||||
| using System.Collections.Generic; | ||||
| using System.IO; | ||||
| using System.Linq; | ||||
| using System.Security.Cryptography; | ||||
| using System.Text; | ||||
| using Android.App; | ||||
| using Android.OS; | ||||
| using KeePassLib; | ||||
| using KeePassLib.Keys; | ||||
| using KeePassLib.Serialization; | ||||
| using KeePassLib.Utility; | ||||
| using Microsoft.VisualStudio.TestTools.UnitTesting; | ||||
| using keepass2android; | ||||
| using keepass2android.Io; | ||||
|  | ||||
| namespace Kp2aUnitTests | ||||
| { | ||||
| 	[TestClass] | ||||
| 	internal class TestSynchronizeCachedDatabase : TestBase | ||||
| 	{ | ||||
| 		private TestCacheSupervisor _testCacheSupervisor = new TestCacheSupervisor(); | ||||
| 		private TestFileStorage _testFileStorage = new TestFileStorage(); | ||||
|  | ||||
| 		[TestMethod] | ||||
| 		public void TestTodos() | ||||
| 		{ | ||||
| 			Assert.IsFalse(true, "Wird immer ManagedTransform benutzt??"); | ||||
| 			Assert.IsFalse(true, "TODOs in SyncDb"); | ||||
| 			Assert.IsFalse(true, "FileNotFound"); | ||||
| 			Assert.IsFalse(true, "Test merge files"); | ||||
| 		} | ||||
|  | ||||
| 		protected override TestKp2aApp CreateTestKp2aApp() | ||||
| 		{ | ||||
| 			TestKp2aApp app = base.CreateTestKp2aApp(); | ||||
| 			app.FileStorage = new CachingFileStorage(_testFileStorage, "/mnt/sdcard/kp2atest/cache/", _testCacheSupervisor); | ||||
| 			return app; | ||||
| 		} | ||||
|  | ||||
| 		/// <summary> | ||||
| 		/// Tests that synchronizing works if  | ||||
| 		///  - no changes in remote and local db | ||||
| 		///  - remote is offline -> error | ||||
| 		///  - only local file was changed | ||||
| 		/// </summary> | ||||
| 		[TestMethod] | ||||
| 		public void TestSimpleSyncCases() | ||||
| 		{ | ||||
| 			 | ||||
| 			//create the default database: | ||||
| 			TestKp2aApp app = SetupAppWithDefaultDatabase(); | ||||
| 			 | ||||
| 			 | ||||
| 			IOConnection.DeleteFile(new IOConnectionInfo {Path = DefaultFilename}); | ||||
| 			//save it and reload it so we have a base version ("remote" and in the cache) | ||||
| 			SaveDatabase(app); | ||||
| 			app = LoadDatabase(DefaultFilename, DefaultPassword, DefaultKeyfile); | ||||
| 		 | ||||
| 			string resultMessage; | ||||
| 			bool wasSuccessful; | ||||
|  | ||||
| 			//sync without changes on any side: | ||||
| 			Synchronize(app, out wasSuccessful, out resultMessage); | ||||
| 			Assert.IsTrue(wasSuccessful); | ||||
| 			Assert.AreEqual(resultMessage, app.GetResourceString(UiStringKey.FilesInSync)); | ||||
|  | ||||
| 			//go offline: | ||||
| 			_testFileStorage.Offline = true; | ||||
|  | ||||
| 			//sync when offline (->error) | ||||
| 			Synchronize(app, out wasSuccessful, out resultMessage); | ||||
| 			Assert.IsFalse(wasSuccessful); | ||||
| 			Assert.AreEqual(resultMessage, "offline"); | ||||
|  | ||||
| 			//modify the database by adding a group: | ||||
| 			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; | ||||
|  | ||||
| 			//go online again: | ||||
| 			_testFileStorage.Offline = false; | ||||
|  | ||||
| 			//sync with local changes only (-> upload): | ||||
| 			Synchronize(app, out wasSuccessful, out resultMessage); | ||||
| 			Assert.IsTrue(wasSuccessful); | ||||
| 			Assert.AreEqual(resultMessage, app.GetResourceString(UiStringKey.SynchronizedDatabaseSuccessfully)); | ||||
|  | ||||
| 			//ensure both files are identical and up to date now: | ||||
| 			_testFileStorage.Offline = true; | ||||
| 			var appOfflineLoaded = LoadDatabase(DefaultFilename, DefaultPassword, DefaultKeyfile); | ||||
| 			_testCacheSupervisor.CouldntOpenFromRemoteCalled = false; | ||||
| 			_testFileStorage.Offline = false; | ||||
| 			var appRemoteLoaded = LoadDatabase(DefaultFilename, DefaultPassword, DefaultKeyfile); | ||||
| 			Assert.IsFalse(_testCacheSupervisor.CouldntOpenFromRemoteCalled); | ||||
|  | ||||
| 			AssertDatabasesAreEqual(app.GetDb().KpDatabase, appOfflineLoaded.GetDb().KpDatabase); | ||||
| 			AssertDatabasesAreEqual(app.GetDb().KpDatabase, appRemoteLoaded.GetDb().KpDatabase); | ||||
| 		} | ||||
|  | ||||
| 		private void Synchronize(TestKp2aApp app, out bool wasSuccessful, out string resultMessage) | ||||
| 		{ | ||||
| 			bool success = false; | ||||
| 			string result = null; | ||||
| 			var sync = new SynchronizeCachedDatabase(Application.Context, app, new ActionOnFinish((_success, _result) => | ||||
| 				{  | ||||
| 					success = _success; | ||||
| 					result = _result; | ||||
| 				})); | ||||
| 			sync.Run(); | ||||
| 			wasSuccessful = success; | ||||
| 			resultMessage = result; | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
| @@ -270,7 +270,7 @@ | ||||
| 		* External database changes are detected and merged when saving\n | ||||
| 		* Improved loading performance\n | ||||
| 		* Improved search toolbar with suggestions\n | ||||
| 		* New App logo! | ||||
| 		* New App logo!\n | ||||
| 		* Added support for .kdbp format for faster loading/saving\n | ||||
| 		* Improved editing of extra strings and hidden display when protected\n | ||||
| 		Thanks to Alex Vallat for his code contributions!\n | ||||
|   | ||||
| @@ -125,7 +125,14 @@ namespace keepass2android | ||||
|                 { | ||||
|                     activity.SetResult(KeePass.ExitReloadDb); | ||||
|                     activity.Finish(); | ||||
| 					//todo: return? | ||||
|                 } | ||||
| 	            AskForReload(activity); | ||||
|             } | ||||
|         } | ||||
|  | ||||
| 		private void AskForReload(Activity activity) | ||||
| 		{ | ||||
| 			AlertDialog.Builder builder = new AlertDialog.Builder(activity); | ||||
| 			builder.SetTitle(activity.GetString(Resource.String.AskReloadFile_title)); | ||||
|  | ||||
| @@ -149,7 +156,6 @@ namespace keepass2android | ||||
| 			Dialog dialog = builder.Create(); | ||||
| 			dialog.Show(); | ||||
| 		} | ||||
|         } | ||||
|  | ||||
| 		public void StoreOpenedFileAsRecent(IOConnectionInfo ioc, string keyfile) | ||||
|         { | ||||
| @@ -250,6 +256,11 @@ namespace keepass2android | ||||
| 			return new BuiltInFileStorage(); | ||||
| 		} | ||||
|  | ||||
| 		public void TriggerReload(Context ctx) | ||||
| 		{ | ||||
| 			AskForReload((Activity)ctx); | ||||
| 		} | ||||
|  | ||||
|  | ||||
| 		internal void OnTerminate() | ||||
|         { | ||||
|   | ||||
| @@ -644,6 +644,7 @@ | ||||
|   <Import Project="$(MSBuildExtensionsPath)\Novell\Novell.MonoDroid.CSharp.targets" /> | ||||
|   <ItemGroup> | ||||
|     <Folder Include="Assets\" /> | ||||
|     <Folder Include="SupportLib\" /> | ||||
|   </ItemGroup> | ||||
|   <ItemGroup> | ||||
|     <ProjectReference Include="..\KeePassLib2Android\KeePassLib2Android.csproj"> | ||||
| @@ -672,9 +673,6 @@ | ||||
|       </Properties> | ||||
|     </MonoDevelop> | ||||
|   </ProjectExtensions> | ||||
|   <ItemGroup> | ||||
|     <AndroidJavaLibrary Include="SupportLib\android-support-v4.jar" /> | ||||
|   </ItemGroup> | ||||
|   <ItemGroup> | ||||
|     <AndroidNativeLibrary Include="..\java\kp2akeytransform\libs\armeabi\libfinal-key.so"> | ||||
|       <Link>libs\armeabi\libfinal-key.so</Link> | ||||
|   | ||||
		Reference in New Issue
	
	Block a user
	 Philipp Crocoll
					Philipp Crocoll