* Introduced IFileStorage interface: Better abstraction than current IOConnection (suitable for cloud support). Currently only implemented by the built-in IOConnection (local/http/ftp)
* Implemented Merge functionality for SaveDB. UI is not yet implemented! * Added tests for merge functionality
This commit is contained in:
		| @@ -164,7 +164,6 @@ namespace KeePassLib.Native | ||||
|  | ||||
| 			try | ||||
| 			{ | ||||
| 				//Kp2aLog.Log("4+1"+new Kp2atest.TestClass().Add1(4)); | ||||
| 				Com.Keepassdroid.Crypto.Finalkey.NativeFinalKey key = new Com.Keepassdroid.Crypto.Finalkey.NativeFinalKey(); | ||||
|  | ||||
| 				byte[] newKey = key.TransformMasterKey(pKey256, pBuf256, (int)uRounds); | ||||
|   | ||||
| @@ -562,22 +562,40 @@ namespace KeePassLib | ||||
| 		/// Open a database. The URL may point to any supported data source. | ||||
| 		/// </summary> | ||||
| 		/// <param name="ioSource">IO connection to load the database from.</param> | ||||
| 		/// <param name="pwKey">Key used to open the specified database.</param> | ||||
| 		/// s<param name="pwKey">Key used to open the specified database.</param> | ||||
| 		/// <param name="slLogger">Logger, which gets all status messages.</param> | ||||
| 		public void Open(IOConnectionInfo ioSource, CompositeKey pwKey, | ||||
| 			IStatusLogger slLogger) | ||||
| 		{ | ||||
| 			Debug.Assert(ioSource != null); | ||||
| 			if(ioSource == null) throw new ArgumentNullException("ioSource"); | ||||
| 			Open(IOConnection.OpenRead(ioSource),  UrlUtil.StripExtension( | ||||
| 					UrlUtil.GetFileName(ioSource.Path)), ioSource, pwKey, slLogger ); | ||||
| 			 | ||||
| 		} | ||||
|  | ||||
| 		/// <summary> | ||||
| 		/// Open a database. The URL may point to any supported data source. | ||||
| 		/// </summary> | ||||
| 		/// <param name="ioSource">IO connection to load the database from.</param> | ||||
| 		/// <param name="pwKey">Key used to open the specified database.</param> | ||||
| 		/// <param name="slLogger">Logger, which gets all status messages.</param> | ||||
| 		public void Open(Stream s, string fileNameWithoutPathAndExt, IOConnectionInfo ioSource, CompositeKey pwKey, | ||||
| 			IStatusLogger slLogger) | ||||
| 		{ | ||||
| 			Debug.Assert(s != null); | ||||
| 			if (s == null) throw new ArgumentNullException("s"); | ||||
| 			Debug.Assert(fileNameWithoutPathAndExt != null); | ||||
| 			if (fileNameWithoutPathAndExt == null) throw new ArgumentException("fileNameWithoutPathAndExt"); | ||||
| 			Debug.Assert(pwKey != null); | ||||
| 			if(pwKey == null) throw new ArgumentNullException("pwKey"); | ||||
| 			Debug.Assert(ioSource != null); | ||||
| 			if (ioSource == null) throw new ArgumentNullException("ioSource"); | ||||
| 			 | ||||
| 			if (pwKey == null) throw new ArgumentNullException("pwKey"); | ||||
|  | ||||
| 			Close(); | ||||
|  | ||||
| 			try | ||||
| 			{ | ||||
| 				m_pgRootGroup = new PwGroup(true, true, UrlUtil.StripExtension( | ||||
| 					UrlUtil.GetFileName(ioSource.Path)), PwIcon.FolderOpen); | ||||
| 				m_pgRootGroup = new PwGroup(true, true, fileNameWithoutPathAndExt, PwIcon.FolderOpen); | ||||
| 				m_pgRootGroup.IsExpanded = true; | ||||
|  | ||||
| 				m_pwUserKey = pwKey; | ||||
| @@ -587,8 +605,8 @@ namespace KeePassLib | ||||
| 				KdbxFile kdbx = new KdbxFile(this); | ||||
| 				kdbx.DetachBinaries = m_strDetachBins; | ||||
|  | ||||
| 				Stream s = IOConnection.OpenRead(ioSource); | ||||
| 				kdbx.Load(s, KdbxFormat.Default, slLogger); | ||||
|  | ||||
| 				s.Close(); | ||||
|  | ||||
| 				m_pbHashOfLastIO = kdbx.HashOfFileOnDisk; | ||||
| @@ -598,13 +616,14 @@ namespace KeePassLib | ||||
| 				m_bDatabaseOpened = true; | ||||
| 				m_ioSource = ioSource; | ||||
| 			} | ||||
| 			catch(Exception) | ||||
| 			catch (Exception) | ||||
| 			{ | ||||
| 				Clear(); | ||||
| 				throw; | ||||
| 			} | ||||
| 		} | ||||
|  | ||||
|  | ||||
| 		/// <summary> | ||||
| 		/// Save the currently opened database. The file is written to the location | ||||
| 		/// it has been opened from. | ||||
| @@ -626,7 +645,7 @@ namespace KeePassLib | ||||
| 				kdb.Save(s, null, KdbxFormat.Default, slLogger); | ||||
|  | ||||
| 				ft.CommitWrite(); | ||||
|  | ||||
| 				 | ||||
| 				m_pbHashOfLastIO = kdb.HashOfFileOnDisk; | ||||
| 				m_pbHashOfFileOnDisk = kdb.HashOfFileOnDisk; | ||||
| 				Debug.Assert(m_pbHashOfFileOnDisk != null); | ||||
| @@ -636,6 +655,23 @@ namespace KeePassLib | ||||
| 			m_bModified = false; | ||||
| 		} | ||||
|  | ||||
| 		/// <summary> | ||||
| 		/// Save the currently opened database. The file is written to the given stream which is expected to be the original location. | ||||
| 		/// </summary> | ||||
| 		/// This allows to save to cloud locations etc.  | ||||
| 		public void Save(Stream streamOfOriginalLocation, IStatusLogger slLogger) | ||||
| 		{ | ||||
| 			Debug.Assert(ValidateUuidUniqueness()); | ||||
| 			Stream s = streamOfOriginalLocation; | ||||
| 			KdbxFile kdb = new KdbxFile(this); | ||||
| 			kdb.Save(s, null, KdbxFormat.Default, slLogger); | ||||
|  | ||||
| 			m_pbHashOfLastIO = kdb.HashOfFileOnDisk; | ||||
| 			m_pbHashOfFileOnDisk = kdb.HashOfFileOnDisk; | ||||
| 			Debug.Assert(m_pbHashOfFileOnDisk != null); | ||||
| 			m_bModified = false; | ||||
| 		} | ||||
|  | ||||
| 		/// <summary> | ||||
| 		/// Save the currently opened database to a different location. If | ||||
| 		/// <paramref name="bIsPrimaryNow" /> is <c>true</c>, the specified | ||||
|   | ||||
| @@ -91,12 +91,18 @@ namespace KeePassLib.Serialization | ||||
| 					if((sDecrypted == null) || (sDecrypted == hashedStream)) | ||||
| 						throw new SecurityException(KLRes.CryptoStreamFailed); | ||||
|  | ||||
| 					if (m_slLogger != null) | ||||
| 						m_slLogger.SetText("KP2AKEY_TransformingKey", LogStatusType.AdditionalInfo); | ||||
|  | ||||
| 					brDecrypted = new BinaryReaderEx(sDecrypted, encNoBom, KLRes.FileCorrupted); | ||||
| 					byte[] pbStoredStartBytes = brDecrypted.ReadBytes(32); | ||||
|  | ||||
| 					if((m_pbStreamStartBytes == null) || (m_pbStreamStartBytes.Length != 32)) | ||||
| 						throw new InvalidDataException(); | ||||
|  | ||||
| 					if (m_slLogger != null) | ||||
| 						m_slLogger.SetText("KP2AKEY_DecodingDatabase", LogStatusType.AdditionalInfo); | ||||
|  | ||||
| 					for(int iStart = 0; iStart < 32; ++iStart) | ||||
| 					{ | ||||
| 						if(pbStoredStartBytes[iStart] != m_pbStreamStartBytes[iStart]) | ||||
| @@ -126,7 +132,8 @@ namespace KeePassLib.Serialization | ||||
| 						m_pbProtectedStreamKey); | ||||
| 				} | ||||
| 				else m_randomStream = null; // No random stream for plain-text files | ||||
|  | ||||
| 				if (m_slLogger != null) | ||||
| 					m_slLogger.SetText("KP2AKEY_ParsingDatabase", LogStatusType.AdditionalInfo); | ||||
| 				ReadXmlStreamed(readerStream, hashedStream); | ||||
| 				// ReadXmlDom(readerStream); | ||||
|  | ||||
| @@ -312,7 +319,8 @@ namespace KeePassLib.Serialization | ||||
| 			if(m_pbMasterSeed.Length != 32) | ||||
| 				throw new FormatException(KLRes.MasterSeedLengthInvalid); | ||||
| 			ms.Write(m_pbMasterSeed, 0, 32); | ||||
|  | ||||
| 			if (m_slLogger != null) | ||||
| 				m_slLogger.SetText("KP2AKEY_TransformingKey", LogStatusType.AdditionalInfo); | ||||
| 			byte[] pKey32 = m_pwDatabase.MasterKey.GenerateKey32(m_pbTransformSeed, | ||||
| 				m_pwDatabase.KeyEncryptionRounds).ReadData(); | ||||
| 			if((pKey32 == null) || (pKey32.Length != 32)) | ||||
|   | ||||
| @@ -2,6 +2,7 @@ using System; | ||||
| using Android.Content; | ||||
| using Android.OS; | ||||
| using KeePassLib.Serialization; | ||||
| using keepass2android.Io; | ||||
|  | ||||
| namespace keepass2android | ||||
| { | ||||
| @@ -57,5 +58,7 @@ namespace keepass2android | ||||
| 		Handler UiThreadHandler { get; } | ||||
|  | ||||
| 		IProgressDialog CreateProgressDialog(Context ctx); | ||||
| 		IFileStorage GetFileStorage(IOConnectionInfo iocInfo); | ||||
| 		 | ||||
|     } | ||||
| } | ||||
| @@ -3,7 +3,7 @@ namespace keepass2android | ||||
| 	public interface IProgressDialog | ||||
| 	{ | ||||
| 		void SetTitle(string title); | ||||
| 		void SetMessage(string getResourceString); | ||||
| 		void SetMessage(string resourceString); | ||||
| 		void Dismiss(); | ||||
| 		void Show(); | ||||
| 	} | ||||
|   | ||||
							
								
								
									
										104
									
								
								src/Kp2aBusinessLogic/Io/BuiltInFileStorage.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										104
									
								
								src/Kp2aBusinessLogic/Io/BuiltInFileStorage.cs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,104 @@ | ||||
| using System; | ||||
| using System.Collections.Generic; | ||||
| using System.Globalization; | ||||
| using System.IO; | ||||
| using System.Linq; | ||||
| using System.Text; | ||||
|  | ||||
| using Android.App; | ||||
| using Android.Content; | ||||
| using Android.OS; | ||||
| using Android.Runtime; | ||||
| using Android.Views; | ||||
| using Android.Widget; | ||||
| using KeePassLib.Serialization; | ||||
| using KeePassLib.Utility; | ||||
|  | ||||
| namespace keepass2android.Io | ||||
| { | ||||
| 	public class BuiltInFileStorage: IFileStorage | ||||
| 	{ | ||||
| 		public void DeleteFile(IOConnectionInfo ioc) | ||||
| 		{ | ||||
| 			IOConnection.DeleteFile(ioc); | ||||
| 		} | ||||
|  | ||||
| 		public bool CheckForFileChangeFast(IOConnectionInfo ioc, string previousFileVersion) | ||||
| 		{ | ||||
| 			if (!ioc.IsLocalFile()) | ||||
| 				return false; | ||||
| 			DateTime previousDate; | ||||
| 			if (!DateTime.TryParse(previousFileVersion, out previousDate)) | ||||
| 				return false; | ||||
| 			return File.GetLastWriteTimeUtc(ioc.Path) > previousDate; | ||||
| 		} | ||||
|  | ||||
| 		 | ||||
| 		public string GetCurrentFileVersionFast(IOConnectionInfo ioc) | ||||
| 		{ | ||||
|  | ||||
| 			if (ioc.IsLocalFile()) | ||||
| 			{ | ||||
| 				return File.GetLastWriteTimeUtc(ioc.Path).ToString(CultureInfo.InvariantCulture); | ||||
| 			} | ||||
| 			else | ||||
| 			{ | ||||
| 				return DateTime.MinValue.ToString(CultureInfo.InvariantCulture); | ||||
| 			} | ||||
|  | ||||
|  | ||||
| 		} | ||||
|  | ||||
| 		public Stream OpenFileForRead(IOConnectionInfo ioc) | ||||
| 		{ | ||||
| 			return IOConnection.OpenRead(ioc); | ||||
| 		} | ||||
|  | ||||
| 		public IWriteTransaction OpenWriteTransaction(IOConnectionInfo ioc, bool useFileTransaction) | ||||
| 		{ | ||||
| 			return new BuiltInFileTransaction(ioc, useFileTransaction); | ||||
| 		} | ||||
|  | ||||
| 		public class BuiltInFileTransaction : IWriteTransaction | ||||
| 		{ | ||||
| 			private readonly FileTransactionEx _transaction; | ||||
|  | ||||
| 			public BuiltInFileTransaction(IOConnectionInfo ioc, bool useFileTransaction) | ||||
| 			{ | ||||
| 				_transaction = new FileTransactionEx(ioc, useFileTransaction); | ||||
| 			} | ||||
|  | ||||
| 			public void Dispose() | ||||
| 			{ | ||||
| 				 | ||||
| 			} | ||||
|  | ||||
| 			public Stream OpenFile() | ||||
| 			{ | ||||
| 				return _transaction.OpenWrite(); | ||||
| 			} | ||||
|  | ||||
| 			public void CommitWrite() | ||||
| 			{ | ||||
| 				_transaction.CommitWrite(); | ||||
| 			} | ||||
| 		} | ||||
|  | ||||
| 		public bool CompleteIoId() | ||||
| 		{ | ||||
| 			throw new NotImplementedException(); | ||||
| 		} | ||||
|  | ||||
| 		public bool? FileExists() | ||||
| 		{ | ||||
| 			throw new NotImplementedException(); | ||||
| 		} | ||||
|  | ||||
| 		public string GetFilenameWithoutPathAndExt(IOConnectionInfo ioc) | ||||
| 		{ | ||||
| 			return UrlUtil.StripExtension( | ||||
| 				UrlUtil.GetFileName(ioc.Path)); | ||||
|  | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
							
								
								
									
										82
									
								
								src/Kp2aBusinessLogic/Io/IFileStorage.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										82
									
								
								src/Kp2aBusinessLogic/Io/IFileStorage.cs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,82 @@ | ||||
| using System; | ||||
| using System.Collections.Generic; | ||||
| using System.IO; | ||||
| using System.Linq; | ||||
| using System.Text; | ||||
|  | ||||
| using Android.App; | ||||
| using Android.Content; | ||||
| using Android.OS; | ||||
| using Android.Runtime; | ||||
| using Android.Views; | ||||
| using Android.Widget; | ||||
| using KeePassLib.Keys; | ||||
| using KeePassLib.Serialization; | ||||
|  | ||||
| namespace keepass2android.Io | ||||
| { | ||||
| 	/// <summary> | ||||
| 	/// Called as a callback from CheckForFileChangeAsync. | ||||
| 	/// </summary> | ||||
| 	/// <param name="ioc"></param> | ||||
| 	/// <param name="fileChanged"></param> | ||||
| 	public delegate void OnCheckForFileChangeCompleted(IOConnectionInfo ioc, bool fileChanged); | ||||
|  | ||||
| 	/// <summary> | ||||
| 	/// Interface to encapsulate all access to disk or cloud. | ||||
| 	/// </summary> | ||||
| 	/// This interface might be implemented for different cloud storage providers in the future to extend the possibilities of the | ||||
| 	/// "built-in" IOConnection class in the Keepass-Lib.  | ||||
| 	/// Note that it was decided to use the IOConnectionInfo also for cloud storage (unless it turns out that this isn't possible, but  | ||||
| 	/// with prefixes like dropbox:// it should be). The advantage is that the database for saving recent files etc. will then work without  | ||||
| 	/// much work to do. Furthermore, the IOConnectionInfo seems generic info to capture all required data, even though it might be nicer to  | ||||
| 	/// have an IIoStorageId interface in few cases.*/ | ||||
| 	public interface IFileStorage | ||||
| 	{ | ||||
| 		/// <summary> | ||||
| 		/// Deletes the given file. | ||||
| 		/// </summary> | ||||
| 		void DeleteFile(IOConnectionInfo ioc); | ||||
|  | ||||
| 		/// <summary> | ||||
| 		/// Tests whether the file was changed.  | ||||
| 		/// </summary> | ||||
| 		/// Note: This function may return false even if the file might have changed. The function | ||||
| 		/// should focus on being fast and cheap instead of doing things like hashing or downloading a full file. | ||||
| 		/// <returns>Returns true if a change was detected, false otherwise.</returns> | ||||
| 		bool CheckForFileChangeFast(IOConnectionInfo ioc , string previousFileVersion); | ||||
|  | ||||
| 		/// <summary> | ||||
| 		/// Returns a string describing the "version" of the file specified by ioc. | ||||
| 		/// </summary> | ||||
| 		/// This string may have a deliberate value (except null) and should not be used by callers except for passing it to | ||||
| 		/// CheckForFileChangeFast(). | ||||
| 		/// <returns>A string which should not be null.</returns> | ||||
| 		string GetCurrentFileVersionFast(IOConnectionInfo ioc); | ||||
|  | ||||
| 		Stream OpenFileForRead(IOConnectionInfo ioc); | ||||
| 		//Stream OpenFileForWrite( IOConnectionInfo ioc, bool useTransaction); | ||||
| 		IWriteTransaction OpenWriteTransaction(IOConnectionInfo ioc, bool useFileTransaction); | ||||
|  | ||||
| 		/// <summary> | ||||
| 		/// brings up a dialog to query credentials or something like this. | ||||
| 		/// </summary> | ||||
| 		/// <returns>true if success, false if error or cancelled by user</returns> | ||||
| 		bool CompleteIoId( /*in/out ioId*/); | ||||
|  | ||||
|  | ||||
| 		/// <summary> | ||||
| 		/// Checks whether the given file exists. | ||||
| 		/// </summary> | ||||
| 		/// <returns>true if it exists, false if not. Null if the check couldn't be performed (e.g. because no credentials available or no connection established.)</returns> | ||||
| 		bool? FileExists( /*ioId*/); | ||||
|  | ||||
| 		string GetFilenameWithoutPathAndExt(IOConnectionInfo ioc); | ||||
| 	} | ||||
|  | ||||
| 	public interface IWriteTransaction: IDisposable | ||||
| 	{ | ||||
| 		Stream OpenFile(); | ||||
| 		void CommitWrite(); | ||||
| 	} | ||||
| } | ||||
| @@ -41,6 +41,8 @@ | ||||
|     <Reference Include="System.Xml" /> | ||||
|   </ItemGroup> | ||||
|   <ItemGroup> | ||||
|     <Compile Include="Io\BuiltInFileStorage.cs" /> | ||||
|     <Compile Include="Io\IFileStorage.cs" /> | ||||
|     <Compile Include="IProgressDialog.cs" /> | ||||
|     <Compile Include="PreferenceKey.cs" /> | ||||
|     <Compile Include="UiStringKey.cs" /> | ||||
| @@ -68,7 +70,7 @@ | ||||
|     <Compile Include="Resources\Resource.Designer.cs" /> | ||||
|     <Compile Include="Properties\AssemblyInfo.cs" /> | ||||
|     <Compile Include="SearchDbHelper.cs" /> | ||||
|     <Compile Include="UpdateStatus.cs" /> | ||||
|     <Compile Include="ProgressDialogStatusLogger.cs" /> | ||||
|   </ItemGroup> | ||||
|   <ItemGroup> | ||||
|     <ProjectReference Include="..\KeePassLib2Android\KeePassLib2Android.csproj"> | ||||
|   | ||||
| @@ -6,6 +6,7 @@ namespace keepass2android | ||||
|     public enum PreferenceKey | ||||
|     { | ||||
|         remember_keyfile, | ||||
|         UseFileTransactions | ||||
|         UseFileTransactions, | ||||
| 		CheckForFileChangesOnSave | ||||
|     } | ||||
| } | ||||
| @@ -25,34 +25,54 @@ namespace keepass2android | ||||
| 	/// <summary> | ||||
| 	/// StatusLogger implementation which shows the progress in a progress dialog | ||||
| 	/// </summary> | ||||
| 	public class UpdateStatus: IStatusLogger { | ||||
| 	public class ProgressDialogStatusLogger: IStatusLogger { | ||||
| 		private readonly IProgressDialog _progressDialog; | ||||
| 		readonly IKp2aApp _app; | ||||
| 		private readonly Handler _handler; | ||||
| 		 | ||||
| 		public UpdateStatus() { | ||||
| 		private string _message = ""; | ||||
| 
 | ||||
| 		public ProgressDialogStatusLogger() { | ||||
| 			 | ||||
| 		} | ||||
| 		 | ||||
| 		public UpdateStatus(IKp2aApp app, Handler handler, IProgressDialog pd) { | ||||
| 		public ProgressDialogStatusLogger(IKp2aApp app, Handler handler, IProgressDialog pd) { | ||||
| 			_app = app; | ||||
| 			_progressDialog = pd; | ||||
| 			_handler = handler; | ||||
| 		} | ||||
| 		 | ||||
| 		public void UpdateMessage(UiStringKey stringKey) { | ||||
| 			if ( _app != null && _progressDialog != null && _handler != null ) { | ||||
| 				_handler.Post( () => {_progressDialog.SetMessage(_app.GetResourceString(stringKey));}); | ||||
| 			} | ||||
| 			if (_app != null) | ||||
| 				UpdateMessage(_app.GetResourceString(stringKey)); | ||||
| 		} | ||||
| 
 | ||||
| 		public void UpdateMessage (String message) | ||||
| 		{ | ||||
| 			_message = message; | ||||
| 			if ( _app!= null && _progressDialog != null && _handler != null ) { | ||||
| 				_handler.Post(() => {_progressDialog.SetMessage(message); } ); | ||||
| 			} | ||||
| 		} | ||||
| 
 | ||||
| 		public void UpdateSubMessage(String submessage) | ||||
| 		{ | ||||
| 			if (_app != null && _progressDialog != null && _handler != null) | ||||
| 			{ | ||||
| 				_handler.Post(() =>  | ||||
| 				{  | ||||
| 					if (String.IsNullOrEmpty(submessage)) | ||||
| 					{ | ||||
| 						_progressDialog.SetMessage(_message + " (" + submessage + ")"); | ||||
| 					} | ||||
| 					else | ||||
| 					{ | ||||
| 						_progressDialog.SetMessage(_message); | ||||
| 					} | ||||
| 				} | ||||
| 			); | ||||
| 			} | ||||
| 		} | ||||
| 
 | ||||
| 		#region IStatusLogger implementation | ||||
| 
 | ||||
| 		public void StartLogging (string strOperation, bool bWriteOperationToLog) | ||||
| @@ -72,10 +92,32 @@ namespace keepass2android | ||||
| 
 | ||||
| 		public bool SetText (string strNewText, LogStatusType lsType) | ||||
| 		{ | ||||
| 			UpdateMessage(strNewText); | ||||
| 			if (strNewText.StartsWith("KP2AKEY_")) | ||||
| 			{ | ||||
| 				UiStringKey key; | ||||
| 				if (Enum.TryParse(strNewText.Substring("KP2AKEY_".Length), true, out key)) | ||||
| 				{ | ||||
| 					UpdateMessage(_app.GetResourceString(key), lsType); | ||||
| 					return true; | ||||
| 				} | ||||
| 			} | ||||
| 			UpdateMessage(strNewText, lsType);	 | ||||
| 			 | ||||
| 			return true; | ||||
| 		} | ||||
| 
 | ||||
| 		private void UpdateMessage(string message, LogStatusType lsType) | ||||
| 		{ | ||||
| 			if (lsType == LogStatusType.AdditionalInfo) | ||||
| 			{ | ||||
| 				UpdateSubMessage(message); | ||||
| 			} | ||||
| 			else | ||||
| 			{ | ||||
| 				UpdateMessage(message); | ||||
| 			} | ||||
| 		} | ||||
| 
 | ||||
| 		public bool ContinueWork () | ||||
| 		{ | ||||
| 			return true; | ||||
| @@ -32,7 +32,7 @@ namespace keepass2android | ||||
|         private readonly IKp2aApp _app; | ||||
| 		private Thread _thread; | ||||
|  | ||||
| 		public ProgressTask(IKp2aApp app, Context ctx, RunnableOnFinish task, UiStringKey messageKey) { | ||||
| 		public ProgressTask(IKp2aApp app, Context ctx, RunnableOnFinish task) { | ||||
| 			_task = task; | ||||
| 			_handler = app.UiThreadHandler; | ||||
|             _app = app; | ||||
| @@ -40,11 +40,12 @@ namespace keepass2android | ||||
| 			// Show process dialog | ||||
| 			_progressDialog = app.CreateProgressDialog(ctx); | ||||
| 			_progressDialog.SetTitle(_app.GetResourceString(UiStringKey.progress_title)); | ||||
|             _progressDialog.SetMessage(_app.GetResourceString(messageKey)); | ||||
| 			_progressDialog.SetMessage("Initializing..."); | ||||
| 			 | ||||
| 			// Set code to run when this is finished | ||||
| 			_task.SetStatus(new UpdateStatus(_app, _handler, _progressDialog)); | ||||
| 			_task.OnFinishToRun = new AfterTask(task.OnFinishToRun, _handler, _progressDialog); | ||||
| 			_task.SetStatusLogger(new ProgressDialogStatusLogger(_app, _handler, _progressDialog)); | ||||
| 			 | ||||
| 			 | ||||
| 		} | ||||
| 		 | ||||
|   | ||||
| @@ -17,6 +17,19 @@ namespace keepass2android | ||||
|         keyfile_does_not_exist, | ||||
|         RecycleBin, | ||||
|         progress_create, | ||||
|         loading_database | ||||
|         loading_database, | ||||
| 		AddingEntry, | ||||
| 		AddingGroup, | ||||
| 		DeletingEntry, | ||||
| 		DeletingGroup, | ||||
| 		SettingPassword, | ||||
| 		UndoingChanges, | ||||
| 		TransformingKey, | ||||
| 		DecodingDatabase, | ||||
| 		ParsingDatabase, | ||||
| 		CheckingTargetFileForChanges, | ||||
| 		TitleSyncQuestion, | ||||
| 		MessageSyncQuestions, | ||||
| 		SynchronizingDatabase | ||||
|     } | ||||
| } | ||||
| @@ -17,10 +17,12 @@ This file is part of Keepass2Android, Copyright 2013 Philipp Crocoll. This file | ||||
|  | ||||
| using System; | ||||
| using System.Collections.Generic; | ||||
| using System.IO; | ||||
| using Android.Content; | ||||
| using KeePassLib; | ||||
| using KeePassLib.Keys; | ||||
| using KeePassLib.Serialization; | ||||
| using keepass2android.Io; | ||||
|  | ||||
| namespace keepass2android | ||||
| { | ||||
| @@ -34,7 +36,7 @@ namespace keepass2android | ||||
| 		public PwGroup Root; | ||||
| 		public PwDatabase KpDatabase; | ||||
| 		public IOConnectionInfo Ioc { get { return KpDatabase.IOConnectionInfo; } } | ||||
| 		public DateTime LastChangeDate; | ||||
| 		public string LastFileVersion; | ||||
| 		public SearchDbHelper SearchHelper; | ||||
| 		 | ||||
| 		public IDrawableFactory DrawableFactory; | ||||
| @@ -83,15 +85,16 @@ namespace keepass2android | ||||
| 		 | ||||
| 		public bool DidOpenFileChange() | ||||
| 		{ | ||||
| 			if ((Loaded == false) || (Ioc.IsLocalFile() == false)) | ||||
| 			if (Loaded == false) | ||||
| 			{ | ||||
| 				return false; | ||||
| 			} | ||||
| 			return System.IO.File.GetLastWriteTimeUtc(Ioc.Path) > LastChangeDate; | ||||
| 			return _app.GetFileStorage(Ioc).CheckForFileChangeFast(Ioc, LastFileVersion); | ||||
| 			 | ||||
| 		} | ||||
|  | ||||
| 		 | ||||
| 		public void LoadData(IKp2aApp app, IOConnectionInfo iocInfo, String password, String keyfile, UpdateStatus status) | ||||
| 		public void LoadData(IKp2aApp app, IOConnectionInfo iocInfo, String password, String keyfile, ProgressDialogStatusLogger status) | ||||
| 		{ | ||||
| 			PwDatabase pwDatabase = new PwDatabase(); | ||||
|  | ||||
| @@ -103,15 +106,17 @@ namespace keepass2android | ||||
| 				try | ||||
| 				{ | ||||
| 					compositeKey.AddUserKey(new KcpKeyFile(keyfile)); | ||||
| 				} catch (Exception) | ||||
| 				} catch (Exception e) | ||||
| 				{ | ||||
| 					Kp2aLog.Log(e.ToString()); | ||||
| 					throw new KeyFileException(); | ||||
| 				} | ||||
| 			} | ||||
| 			 | ||||
| 			try | ||||
| 			{ | ||||
| 				pwDatabase.Open(iocInfo, compositeKey, status); | ||||
| 				IFileStorage fileStorage = _app.GetFileStorage(iocInfo); | ||||
| 				pwDatabase.Open(fileStorage.OpenFileForRead(iocInfo), fileStorage.GetFilenameWithoutPathAndExt(iocInfo), iocInfo, compositeKey, status); | ||||
| 			} | ||||
| 			catch (Exception) | ||||
| 			{ | ||||
| @@ -125,14 +130,9 @@ namespace keepass2android | ||||
| 				else throw; | ||||
| 			} | ||||
| 			 | ||||
| 			status.UpdateSubMessage(""); | ||||
|  | ||||
| 			if (iocInfo.IsLocalFile()) | ||||
| 			{ | ||||
| 				LastChangeDate = System.IO.File.GetLastWriteTimeUtc(iocInfo.Path); | ||||
| 			} else | ||||
| 			{ | ||||
| 				LastChangeDate  = DateTime.MinValue; | ||||
| 			} | ||||
| 			LastFileVersion = _app.GetFileStorage(iocInfo).GetCurrentFileVersionFast(iocInfo); | ||||
|  | ||||
| 			Root = pwDatabase.RootGroup; | ||||
| 			PopulateGlobals(Root); | ||||
| @@ -184,9 +184,14 @@ namespace keepass2android | ||||
| 		public void SaveData(Context ctx)  { | ||||
|              | ||||
| 			KpDatabase.UseFileTransactions = _app.GetBooleanPreference(PreferenceKey.UseFileTransactions); | ||||
| 			KpDatabase.Save(null); | ||||
|  | ||||
| 			using (IWriteTransaction trans = _app.GetFileStorage(Ioc).OpenWriteTransaction(Ioc, KpDatabase.UseFileTransactions)) | ||||
| 			{ | ||||
| 				KpDatabase.Save(trans.OpenFile(), null); | ||||
| 				trans.CommitWrite(); | ||||
| 			} | ||||
| 			 | ||||
| 		} | ||||
|  | ||||
| 		 | ||||
| 		private void PopulateGlobals (PwGroup currentGroup) | ||||
| 		{ | ||||
|   | ||||
| @@ -21,31 +21,38 @@ using KeePassLib; | ||||
| namespace keepass2android | ||||
| { | ||||
| 	public class AddEntry : RunnableOnFinish { | ||||
| 		protected Database Db; | ||||
| 		protected Database Db | ||||
| 		{ | ||||
| 			get { return _app.GetDb(); } | ||||
| 		} | ||||
|  | ||||
| 		private readonly IKp2aApp _app; | ||||
| 		private readonly PwEntry _entry; | ||||
| 		private readonly PwGroup _parentGroup; | ||||
| 		private readonly Context _ctx; | ||||
| 		 | ||||
| 		public static AddEntry GetInstance(Context ctx, Database db, PwEntry entry, PwGroup parentGroup, OnFinish finish) { | ||||
| 		public static AddEntry GetInstance(Context ctx, IKp2aApp app, PwEntry entry, PwGroup parentGroup, OnFinish finish) { | ||||
|  | ||||
| 			return new AddEntry(ctx, db, entry, parentGroup, finish); | ||||
| 			return new AddEntry(ctx, app, entry, parentGroup, finish); | ||||
| 		} | ||||
| 		 | ||||
| 		protected AddEntry(Context ctx, Database db, PwEntry entry, PwGroup parentGroup, OnFinish finish):base(finish) { | ||||
| 		protected AddEntry(Context ctx, IKp2aApp app, PwEntry entry, PwGroup parentGroup, OnFinish finish):base(finish) { | ||||
| 			_ctx = ctx; | ||||
| 			_parentGroup = parentGroup; | ||||
| 			Db = db; | ||||
| 			_app = app; | ||||
| 			_entry = entry; | ||||
| 			 | ||||
| 			OnFinishToRun = new AfterAdd(db, entry, OnFinishToRun); | ||||
| 			_onFinishToRun = new AfterAdd(app.GetDb(), entry, OnFinishToRun); | ||||
| 		} | ||||
| 		 | ||||
| 		 | ||||
| 		public override void Run() { | ||||
| 		public override void Run() {	 | ||||
| 			StatusLogger.UpdateMessage(UiStringKey.AddingEntry); | ||||
| 			_parentGroup.AddEntry(_entry, true); | ||||
| 			 | ||||
| 			// Commit to disk | ||||
| 			SaveDb save = new SaveDb(_ctx, Db, OnFinishToRun); | ||||
| 			SaveDb save = new SaveDb(_ctx, _app, OnFinishToRun); | ||||
| 			save.SetStatusLogger(StatusLogger); | ||||
| 			save.Run(); | ||||
| 		} | ||||
| 		 | ||||
| @@ -72,7 +79,9 @@ namespace keepass2android | ||||
| 					// Add entry to global | ||||
| 					_db.Entries[_entry.Uuid] = _entry; | ||||
| 					 | ||||
| 				} else { | ||||
| 				} else | ||||
| 				{ | ||||
| 					StatusLogger.UpdateMessage(UiStringKey.UndoingChanges); | ||||
| 					//TODO test fail | ||||
| 					_entry.ParentGroup.Entries.Remove(_entry); | ||||
| 				} | ||||
|   | ||||
| @@ -23,7 +23,11 @@ namespace keepass2android | ||||
| { | ||||
|  | ||||
| 	public class AddGroup : RunnableOnFinish { | ||||
| 		internal Database Db; | ||||
| 		internal Database Db | ||||
| 		{ | ||||
| 			get { return _app.GetDb(); } | ||||
| 		} | ||||
| 		private IKp2aApp _app; | ||||
| 		private readonly String _name; | ||||
| 		private readonly int _iconId; | ||||
| 		internal PwGroup Group; | ||||
| @@ -32,31 +36,32 @@ namespace keepass2android | ||||
| 		readonly Context _ctx; | ||||
| 		 | ||||
| 		 | ||||
| 		public static AddGroup GetInstance(Context ctx, Database db, String name, int iconid, PwGroup parent, OnFinish finish, bool dontSave) { | ||||
| 			return new AddGroup(ctx, db, name, iconid, parent, finish, dontSave); | ||||
| 		public static AddGroup GetInstance(Context ctx, IKp2aApp app, String name, int iconid, PwGroup parent, OnFinish finish, bool dontSave) { | ||||
| 			return new AddGroup(ctx, app, name, iconid, parent, finish, dontSave); | ||||
| 		} | ||||
| 		 | ||||
| 		 | ||||
| 		private AddGroup(Context ctx, Database db, String name, int iconid, PwGroup parent, OnFinish finish, bool dontSave): base(finish) { | ||||
| 		private AddGroup(Context ctx, IKp2aApp app, String name, int iconid, PwGroup parent, OnFinish finish, bool dontSave): base(finish) { | ||||
| 			_ctx = ctx; | ||||
| 			Db = db; | ||||
| 			_name = name; | ||||
| 			_iconId = iconid; | ||||
| 			Parent = parent; | ||||
| 			DontSave = dontSave; | ||||
| 			 | ||||
| 			OnFinishToRun = new AfterAdd(this, OnFinishToRun); | ||||
| 			_app = app; | ||||
|  | ||||
| 			_onFinishToRun = new AfterAdd(this, OnFinishToRun); | ||||
| 		} | ||||
| 		 | ||||
| 		 | ||||
| 		public override void Run() { | ||||
| 			 | ||||
| 			StatusLogger.UpdateMessage(UiStringKey.AddingGroup); | ||||
| 			// Generate new group | ||||
| 			Group = new PwGroup(true, true, _name, (PwIcon)_iconId); | ||||
| 			Parent.AddGroup(Group, true); | ||||
|  | ||||
| 			// Commit to disk | ||||
| 			SaveDb save = new SaveDb(_ctx, Db, OnFinishToRun, DontSave); | ||||
| 			SaveDb save = new SaveDb(_ctx, _app, OnFinishToRun, DontSave); | ||||
| 			save.SetStatusLogger(StatusLogger); | ||||
| 			save.Run(); | ||||
| 		} | ||||
| 		 | ||||
| @@ -77,6 +82,7 @@ namespace keepass2android | ||||
| 					// Add group to global list | ||||
| 					_addGroup.Db.Groups[_addGroup.Group.Uuid] = _addGroup.Group; | ||||
| 				} else { | ||||
| 					StatusLogger.UpdateMessage(UiStringKey.UndoingChanges); | ||||
| 					_addGroup.Parent.Groups.Remove(_addGroup.Group); | ||||
| 				} | ||||
| 				 | ||||
|   | ||||
| @@ -40,6 +40,7 @@ namespace keepass2android | ||||
| 		 | ||||
|  | ||||
| 		public override void Run() { | ||||
| 			StatusLogger.UpdateMessage(UiStringKey.progress_create); | ||||
| 			Database db = _app.CreateNewDatabase(); | ||||
|  | ||||
| 			db.KpDatabase = new KeePassLib.PwDatabase(); | ||||
| @@ -58,14 +59,15 @@ namespace keepass2android | ||||
| 			db.SearchHelper = new SearchDbHelper(_app); | ||||
|  | ||||
| 			// Add a couple default groups | ||||
| 			AddGroup internet = AddGroup.GetInstance(_ctx, db, "Internet", 1, db.KpDatabase.RootGroup, null, true); | ||||
| 			AddGroup internet = AddGroup.GetInstance(_ctx, _app, "Internet", 1, db.KpDatabase.RootGroup, null, true); | ||||
| 			internet.Run(); | ||||
| 			AddGroup email = AddGroup.GetInstance(_ctx, db, "eMail", 19, db.KpDatabase.RootGroup, null, true); | ||||
| 			AddGroup email = AddGroup.GetInstance(_ctx, _app, "eMail", 19, db.KpDatabase.RootGroup, null, true); | ||||
| 			email.Run(); | ||||
| 			 | ||||
| 			// Commit changes | ||||
| 			SaveDb save = new SaveDb(_ctx, db, OnFinishToRun, _dontSave); | ||||
| 			OnFinishToRun = null; | ||||
| 			SaveDb save = new SaveDb(_ctx, _app, OnFinishToRun, _dontSave); | ||||
| 			save.SetStatusLogger(StatusLogger); | ||||
| 			_onFinishToRun = null; | ||||
| 			save.Run(); | ||||
| 			 | ||||
| 			 | ||||
|   | ||||
| @@ -48,8 +48,9 @@ namespace keepass2android | ||||
| 			} | ||||
| 		} | ||||
|  | ||||
| 		public override void Run() { | ||||
|  | ||||
| 		public override void Run() | ||||
| 		{ | ||||
| 			StatusLogger.UpdateMessage(UiStringKey.DeletingEntry); | ||||
| 			PwDatabase pd = Db.KpDatabase; | ||||
|  | ||||
| 			PwGroup pgRecycleBin = pd.RootGroup.FindGroup(pd.RecycleBinUuid, true); | ||||
| @@ -68,7 +69,7 @@ namespace keepass2android | ||||
| 					PwDeletedObject pdo = new PwDeletedObject(pe.Uuid, dtNow); | ||||
| 					pd.DeletedObjects.Add(pdo); | ||||
|  | ||||
| 					OnFinishToRun = new ActionOnFinish((success, message) => | ||||
| 					_onFinishToRun = new ActionOnFinish((success, message) => | ||||
| 						{ | ||||
| 							if (success) | ||||
| 							{ | ||||
| @@ -89,7 +90,7 @@ namespace keepass2android | ||||
| 					pgRecycleBin.AddEntry(pe, true, true); | ||||
| 					pe.Touch(false); | ||||
|  | ||||
| 					OnFinishToRun = new ActionOnFinish( (success, message) =>  | ||||
| 					_onFinishToRun = new ActionOnFinish( (success, message) =>  | ||||
| 					                             { | ||||
| 						if ( success ) { | ||||
| 							// Mark previous parent dirty | ||||
| @@ -106,7 +107,8 @@ namespace keepass2android | ||||
| 			} | ||||
|  | ||||
| 			// Commit database | ||||
| 			SaveDb save = new SaveDb(Ctx, Db, OnFinishToRun, false); | ||||
| 			SaveDb save = new SaveDb(Ctx, App, OnFinishToRun, false); | ||||
| 			save.SetStatusLogger(StatusLogger); | ||||
| 			save.Run(); | ||||
| 			 | ||||
| 			 | ||||
|   | ||||
| @@ -70,6 +70,7 @@ namespace keepass2android | ||||
| 		 | ||||
| 		 | ||||
| 		public override void Run() { | ||||
| 			StatusLogger.UpdateMessage(UiStringKey.DeletingGroup); | ||||
| 			//from KP Desktop | ||||
| 			PwGroup pg = _group; | ||||
| 			PwGroup pgParent = pg.ParentGroup; | ||||
| @@ -86,7 +87,7 @@ namespace keepass2android | ||||
| 				 | ||||
| 				PwDeletedObject pdo = new PwDeletedObject(pg.Uuid, DateTime.Now); | ||||
| 				pd.DeletedObjects.Add(pdo); | ||||
| 				OnFinishToRun = new AfterDeletePermanently(OnFinishToRun, App, _group); | ||||
| 				_onFinishToRun = new AfterDeletePermanently(OnFinishToRun, App, _group); | ||||
| 			} | ||||
| 			else // Recycle | ||||
| 			{ | ||||
| @@ -95,7 +96,7 @@ namespace keepass2android | ||||
| 				 | ||||
| 				pgRecycleBin.AddGroup(pg, true, true); | ||||
| 				pg.Touch(false); | ||||
| 				OnFinishToRun = new ActionOnFinish((success, message) =>  | ||||
| 				_onFinishToRun = new ActionOnFinish((success, message) =>  | ||||
| 				                             { | ||||
| 					if ( success ) { | ||||
| 						// Mark new parent (Recycle bin) dirty | ||||
| @@ -113,7 +114,8 @@ namespace keepass2android | ||||
| 			} | ||||
|  | ||||
| 			// Save | ||||
| 			SaveDb save = new SaveDb(Ctx, Db, OnFinishToRun, DontSave); | ||||
| 			SaveDb save = new SaveDb(Ctx, App, OnFinishToRun, DontSave); | ||||
| 			save.SetStatusLogger(StatusLogger); | ||||
| 			save.Run(); | ||||
| 			 | ||||
| 		} | ||||
|   | ||||
| @@ -109,12 +109,12 @@ namespace keepass2android | ||||
|                     (dlgSender, dlgEvt) =>  | ||||
| 	                    { | ||||
| 		                    DeletePermanently = true; | ||||
| 		                    ProgressTask pt = new ProgressTask(App, Ctx, this, UiStringKey.saving_database); | ||||
| 		                    ProgressTask pt = new ProgressTask(App, Ctx, this); | ||||
| 		                    pt.Run(); | ||||
| 	                    }, | ||||
|                 (dlgSender, dlgEvt) => {	 | ||||
| 	                                       DeletePermanently = false; | ||||
| 	                                       ProgressTask pt = new ProgressTask(App, Ctx, this, UiStringKey.saving_database); | ||||
| 	                                       ProgressTask pt = new ProgressTask(App, Ctx, this); | ||||
| 	                                       pt.Run(); | ||||
|                 }, | ||||
|                 (dlgSender, dlgEvt) => {}, | ||||
| @@ -124,7 +124,7 @@ namespace keepass2android | ||||
| 				 | ||||
| 			} else | ||||
| 			{ | ||||
| 				ProgressTask pt = new ProgressTask(App, Ctx, this, UiStringKey.saving_database); | ||||
| 				ProgressTask pt = new ProgressTask(App, Ctx, this); | ||||
| 				pt.Run(); | ||||
| 			} | ||||
| 		} | ||||
|   | ||||
| @@ -43,7 +43,8 @@ namespace keepass2android | ||||
| 		{ | ||||
| 			try | ||||
| 			{ | ||||
| 				_app.GetDb().LoadData (_app, _ioc, _pass, _key, Status); | ||||
| 				StatusLogger.UpdateMessage(UiStringKey.loading_database); | ||||
| 				_app.GetDb().LoadData (_app, _ioc, _pass, _key, StatusLogger); | ||||
| 				SaveFileData (_ioc, _key); | ||||
| 				 | ||||
| 			} catch (KeyFileException) { | ||||
|   | ||||
| @@ -29,6 +29,13 @@ namespace keepass2android | ||||
| 		 | ||||
| 		protected OnFinish BaseOnFinish; | ||||
| 		protected Handler Handler; | ||||
| 		private ProgressDialogStatusLogger _statusLogger = new ProgressDialogStatusLogger(); //default: no logging but not null -> can be used whenever desired | ||||
|  | ||||
| 		public ProgressDialogStatusLogger StatusLogger | ||||
| 		{ | ||||
| 			get { return _statusLogger; } | ||||
| 			set { _statusLogger = value; } | ||||
| 		} | ||||
|  | ||||
| 		protected OnFinish() { | ||||
| 		} | ||||
| @@ -47,7 +54,7 @@ namespace keepass2android | ||||
| 			BaseOnFinish = finish; | ||||
| 			Handler = null; | ||||
| 		} | ||||
| 		 | ||||
|  | ||||
| 		public void SetResult(bool success, String message) { | ||||
| 			Success = success; | ||||
| 			Message = message; | ||||
|   | ||||
| @@ -21,13 +21,19 @@ namespace keepass2android | ||||
|  | ||||
| 	public abstract class RunnableOnFinish  { | ||||
| 		 | ||||
| 		public OnFinish OnFinishToRun; | ||||
| 		public UpdateStatus Status; | ||||
| 		protected OnFinish _onFinishToRun; | ||||
| 		public ProgressDialogStatusLogger StatusLogger = new ProgressDialogStatusLogger(); //default: empty but not null | ||||
|  | ||||
| 		protected RunnableOnFinish(OnFinish finish) { | ||||
| 			OnFinishToRun = finish; | ||||
| 			_onFinishToRun = finish; | ||||
| 		} | ||||
| 		 | ||||
|  | ||||
| 		public OnFinish OnFinishToRun | ||||
| 		{ | ||||
| 			get { return _onFinishToRun; } | ||||
| 			set { _onFinishToRun = value; } | ||||
| 		} | ||||
|  | ||||
| 		protected void Finish(bool result, String message) { | ||||
| 			if ( OnFinishToRun != null ) { | ||||
| 				OnFinishToRun.SetResult(result, message); | ||||
| @@ -42,8 +48,12 @@ namespace keepass2android | ||||
| 			} | ||||
| 		} | ||||
| 		 | ||||
| 		public void SetStatus(UpdateStatus status) { | ||||
| 			Status = status; | ||||
| 		public void SetStatusLogger(ProgressDialogStatusLogger status) { | ||||
| 			if (OnFinishToRun != null) | ||||
| 			{ | ||||
| 				OnFinishToRun.StatusLogger = status; | ||||
| 			} | ||||
| 			StatusLogger = status; | ||||
| 		} | ||||
| 		 | ||||
| 		abstract public void Run(); | ||||
|   | ||||
| @@ -14,26 +14,40 @@ This file is part of Keepass2Android, Copyright 2013 Philipp Crocoll. This file | ||||
|   You should have received a copy of the GNU General Public License | ||||
|   along with Keepass2Android.  If not, see <http://www.gnu.org/licenses/>. | ||||
|   */ | ||||
|  | ||||
| using System; | ||||
| using System.IO; | ||||
| using System.Security.Cryptography; | ||||
| using Android.Content; | ||||
| using Android.OS; | ||||
| using Java.Lang; | ||||
| using KeePassLib; | ||||
| using KeePassLib.Serialization; | ||||
| using KeePassLib.Utility; | ||||
| using keepass2android.Io; | ||||
| using Debug = System.Diagnostics.Debug; | ||||
| using Exception = System.Exception; | ||||
|  | ||||
| namespace keepass2android | ||||
| { | ||||
|  | ||||
| 	public class SaveDb : RunnableOnFinish { | ||||
| 		private readonly Database _db; | ||||
| 		private readonly IKp2aApp _app; | ||||
| 		private readonly bool _dontSave; | ||||
| 		private readonly Context _ctx; | ||||
| 		 | ||||
| 		public SaveDb(Context ctx, Database db, OnFinish finish, bool dontSave): base(finish) { | ||||
| 		private Thread _workerThread; | ||||
|  | ||||
| 		public SaveDb(Context ctx, IKp2aApp app, OnFinish finish, bool dontSave): base(finish) { | ||||
| 			_ctx = ctx; | ||||
| 			_db = db; | ||||
| 			_app = app; | ||||
| 			_dontSave = dontSave; | ||||
| 		} | ||||
|  | ||||
| 		public SaveDb(Context ctx, Database db, OnFinish finish):base(finish) { | ||||
| 		public SaveDb(Context ctx, IKp2aApp app, OnFinish finish) | ||||
| 			: base(finish) | ||||
| 		{ | ||||
| 			_ctx = ctx; | ||||
| 			_db = db; | ||||
| 			_app = app; | ||||
| 			_dontSave = false; | ||||
| 		} | ||||
| 		 | ||||
| @@ -42,10 +56,67 @@ namespace keepass2android | ||||
| 		{ | ||||
| 			 | ||||
| 			if (! _dontSave) { | ||||
| 				try { | ||||
| 					_db.SaveData (_ctx); | ||||
| 					if (_db.Ioc.IsLocalFile()) | ||||
| 						_db.LastChangeDate = System.IO.File.GetLastWriteTimeUtc(_db.Ioc.Path); | ||||
| 				try | ||||
| 				{ | ||||
| 					StatusLogger.UpdateMessage(UiStringKey.saving_database); | ||||
| 					IOConnectionInfo ioc = _app.GetDb().Ioc; | ||||
| 					IFileStorage fileStorage = _app.GetFileStorage(ioc); | ||||
| 					 | ||||
| 					if ((!_app.GetBooleanPreference(PreferenceKey.CheckForFileChangesOnSave)) | ||||
| 						|| (_app.GetDb().KpDatabase.HashOfFileOnDisk == null)) //first time saving | ||||
| 					{ | ||||
| 						PerformSaveWithoutCheck(fileStorage, ioc); | ||||
| 						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: | ||||
| 					{ | ||||
| 						//ask user... | ||||
| 						_app.AskYesNoCancel(UiStringKey.TitleSyncQuestion, UiStringKey.MessageSyncQuestions, | ||||
| 							//yes = sync | ||||
| 							(sender, args) => | ||||
| 								{ | ||||
| 									Action runHandler = () => | ||||
| 										{ | ||||
| 											//note: when synced, the file might be downloaded once again from the server. Caching the data | ||||
| 											//in the hashing function would solve this but increases complexity. I currently assume the files are  | ||||
| 											//small. | ||||
| 											StatusLogger.UpdateSubMessage(_app.GetResourceString(UiStringKey.SynchronizingDatabase)); | ||||
| 											MergeIn(fileStorage, ioc); | ||||
| 											PerformSaveWithoutCheck(fileStorage, ioc); | ||||
| 											Finish(true); | ||||
| 										}; | ||||
| 									RunInWorkerThread(runHandler); | ||||
| 								}, | ||||
| 							//no = overwrite | ||||
| 							(sender, args) => | ||||
| 								{ | ||||
| 									RunInWorkerThread( () => | ||||
| 										{ | ||||
| 											PerformSaveWithoutCheck(fileStorage, ioc); | ||||
| 											Finish(true); | ||||
| 										}); | ||||
| 								}, | ||||
| 							//cancel  | ||||
| 							(sender, args) => | ||||
| 								{ | ||||
| 									RunInWorkerThread(() => Finish(false)); | ||||
| 								}, | ||||
| 							_ctx | ||||
| 							); | ||||
|  | ||||
|  | ||||
| 					} | ||||
| 					else | ||||
| 					{ | ||||
| 						PerformSaveWithoutCheck(fileStorage, ioc); | ||||
| 						Finish(true); | ||||
| 					} | ||||
| 					 | ||||
| 				} catch (Exception e) { | ||||
| 					/* TODO KPDesktop: | ||||
| 					 * catch(Exception exSave) | ||||
| @@ -54,13 +125,85 @@ namespace keepass2android | ||||
| 				bSuccess = false; | ||||
| 			} | ||||
| */ | ||||
| 					Finish (false, e.Message); | ||||
| 					Finish (false, e.ToString()); | ||||
| 					return; | ||||
| 				}  | ||||
| 			} | ||||
| 			 | ||||
| 			Finish(true); | ||||
| 			 | ||||
| 		} | ||||
|  | ||||
| 		private void RunInWorkerThread(Action runHandler) | ||||
| 		{ | ||||
| 			try | ||||
| 			{ | ||||
| 				_workerThread = new Thread(runHandler); | ||||
| 				_workerThread.Run(); | ||||
| 			} | ||||
| 			catch (Exception e) | ||||
| 			{ | ||||
| 				Kp2aLog.Log("Error in worker thread of SaveDb: "+e); | ||||
| 				Finish(false, e.Message); | ||||
| 			} | ||||
| 			 | ||||
| 		} | ||||
|  | ||||
| 		public void JoinWorkerThread() | ||||
| 		{ | ||||
| 			if (_workerThread != null) | ||||
| 				_workerThread.Join(); | ||||
| 		} | ||||
|  | ||||
| 		private void MergeIn(IFileStorage fileStorage, IOConnectionInfo ioc) | ||||
| 		{ | ||||
| 			PwDatabase pwImp = new PwDatabase(); | ||||
| 			PwDatabase pwDatabase = _app.GetDb().KpDatabase; | ||||
| 			pwImp.New(new IOConnectionInfo(), pwDatabase.MasterKey); | ||||
| 			pwImp.MemoryProtection = pwDatabase.MemoryProtection.CloneDeep(); | ||||
| 			pwImp.MasterKey = pwDatabase.MasterKey; | ||||
| 			KdbxFile kdbx = new KdbxFile(pwImp); | ||||
| 			kdbx.Load(fileStorage.OpenFileForRead(ioc), KdbxFormat.Default, null); | ||||
|  | ||||
| 			pwDatabase.MergeIn(pwImp, PwMergeMethod.Synchronize, null);  | ||||
|  | ||||
| 		} | ||||
|  | ||||
| 		private void PerformSaveWithoutCheck(IFileStorage fileStorage, IOConnectionInfo ioc) | ||||
| 		{ | ||||
| 			_app.GetDb().SaveData(_ctx); | ||||
| 			_app.GetDb().LastFileVersion = fileStorage.GetCurrentFileVersionFast(ioc); | ||||
| 		} | ||||
|  | ||||
| 		public byte[] HashFile(IOConnectionInfo iocFile) | ||||
| 		{ | ||||
| 			if (iocFile == null) { Debug.Assert(false); return null; } // Assert only | ||||
|  | ||||
| 			Stream sIn; | ||||
| 			try | ||||
| 			{ | ||||
| 				sIn = _app.GetFileStorage(iocFile).OpenFileForRead(iocFile); | ||||
| 				if (sIn == null) throw new FileNotFoundException(); | ||||
| 			} | ||||
| 			catch (Exception) { return null; } | ||||
|  | ||||
| 			byte[] pbHash; | ||||
| 			try | ||||
| 			{ | ||||
| 				SHA256Managed sha256 = new SHA256Managed(); | ||||
| 				pbHash = sha256.ComputeHash(sIn); | ||||
| 			} | ||||
| 			catch (Exception) { Debug.Assert(false); sIn.Close(); return null; } | ||||
|  | ||||
| 			sIn.Close(); | ||||
| 			return pbHash; | ||||
| 		} | ||||
|  | ||||
| 		private bool FileHashChanged(IOConnectionInfo ioc, byte[] hashOfFileOnDisk) | ||||
| 		{ | ||||
| 			StatusLogger.UpdateSubMessage(_app.GetResourceString(UiStringKey.CheckingTargetFileForChanges)); | ||||
| 			return !MemUtil.ArraysEqual(HashFile(ioc), hashOfFileOnDisk); | ||||
| 		} | ||||
|  | ||||
| 		 | ||||
| 	} | ||||
|  | ||||
|   | ||||
| @@ -25,21 +25,23 @@ namespace keepass2android | ||||
| 		 | ||||
| 		private readonly String _password; | ||||
| 		private readonly String _keyfile; | ||||
| 		private readonly Database _db; | ||||
| 		private readonly IKp2aApp _app; | ||||
| 		private readonly bool _dontSave; | ||||
| 		private readonly Context _ctx; | ||||
| 		 | ||||
| 		public SetPassword(Context ctx, Database db, String password, String keyfile, OnFinish finish): base(finish) { | ||||
| 		public SetPassword(Context ctx, IKp2aApp app, String password, String keyfile, OnFinish finish): base(finish) { | ||||
| 			_ctx = ctx; | ||||
| 			_db = db; | ||||
| 			_app = app; | ||||
| 			_password = password; | ||||
| 			_keyfile = keyfile; | ||||
| 			_dontSave = false; | ||||
| 		} | ||||
| 		 | ||||
| 		public SetPassword(Context ctx, Database db, String password, String keyfile, OnFinish finish, bool dontSave): base(finish) { | ||||
|  | ||||
| 		public SetPassword(Context ctx, IKp2aApp app, String password, String keyfile, OnFinish finish, bool dontSave) | ||||
| 			: base(finish) | ||||
| 		{ | ||||
| 			_ctx = ctx; | ||||
| 			_db = db; | ||||
| 			_app = app; | ||||
| 			_password = password; | ||||
| 			_keyfile = keyfile; | ||||
| 			_dontSave = dontSave; | ||||
| @@ -48,7 +50,8 @@ namespace keepass2android | ||||
| 		 | ||||
| 		public override void Run () | ||||
| 		{ | ||||
| 			PwDatabase pm = _db.KpDatabase; | ||||
| 			StatusLogger.UpdateMessage(UiStringKey.SettingPassword); | ||||
| 			PwDatabase pm = _app.GetDb().KpDatabase; | ||||
| 			CompositeKey newKey = new CompositeKey (); | ||||
| 			if (String.IsNullOrEmpty (_password) == false) { | ||||
| 				newKey.AddUserKey (new KcpPassword (_password));  | ||||
| @@ -69,8 +72,9 @@ namespace keepass2android | ||||
| 			pm.MasterKey = newKey; | ||||
|  | ||||
| 			// Save Database | ||||
| 			OnFinishToRun = new AfterSave(previousKey, previousMasterKeyChanged, pm, OnFinishToRun); | ||||
| 			SaveDb save = new SaveDb(_ctx, _db, OnFinishToRun, _dontSave); | ||||
| 			_onFinishToRun = new AfterSave(previousKey, previousMasterKeyChanged, pm, OnFinishToRun); | ||||
| 			SaveDb save = new SaveDb(_ctx, _app, OnFinishToRun, _dontSave); | ||||
| 			save.SetStatusLogger(StatusLogger); | ||||
| 			save.Run(); | ||||
| 		} | ||||
| 		 | ||||
|   | ||||
| @@ -22,32 +22,33 @@ namespace keepass2android | ||||
| { | ||||
|  | ||||
| 	public class UpdateEntry : RunnableOnFinish { | ||||
| 		private readonly Database _db; | ||||
| 		private readonly IKp2aApp _app; | ||||
| 		private readonly Context _ctx; | ||||
| 		 | ||||
| 		public UpdateEntry(Context ctx, Database db, PwEntry oldE, PwEntry newE, OnFinish finish):base(finish) { | ||||
| 		public UpdateEntry(Context ctx, IKp2aApp app, PwEntry oldE, PwEntry newE, OnFinish finish):base(finish) { | ||||
| 			_ctx = ctx; | ||||
| 			_db = db; | ||||
| 			_app = app; | ||||
|  | ||||
| 			OnFinishToRun = new AfterUpdate(oldE, newE, db, finish); | ||||
| 			_onFinishToRun = new AfterUpdate(oldE, newE, app, finish); | ||||
| 		} | ||||
| 		 | ||||
| 		 | ||||
| 		public override void Run() { | ||||
| 			// Commit to disk | ||||
| 			SaveDb save = new SaveDb(_ctx, _db, OnFinishToRun); | ||||
| 			SaveDb save = new SaveDb(_ctx, _app, OnFinishToRun); | ||||
| 			save.SetStatusLogger(StatusLogger); | ||||
| 			save.Run(); | ||||
| 		} | ||||
| 		 | ||||
| 		private class AfterUpdate : OnFinish { | ||||
| 			private readonly PwEntry _backup; | ||||
| 			private readonly PwEntry _updatedEntry; | ||||
| 			private readonly Database _db; | ||||
| 			private readonly IKp2aApp _app; | ||||
| 			 | ||||
| 			public AfterUpdate(PwEntry backup, PwEntry updatedEntry, Database db, OnFinish finish):base(finish) { | ||||
| 			public AfterUpdate(PwEntry backup, PwEntry updatedEntry, IKp2aApp app, OnFinish finish):base(finish) { | ||||
| 				_backup = backup; | ||||
| 				_updatedEntry = updatedEntry; | ||||
| 				_db = db; | ||||
| 				_app = app; | ||||
| 			} | ||||
| 			 | ||||
| 			public override void Run() { | ||||
| @@ -65,11 +66,12 @@ namespace keepass2android | ||||
| 						if ( parent != null ) { | ||||
|  | ||||
| 							// Mark parent group dirty | ||||
| 							_db.Dirty.Add(parent); | ||||
| 							_app.GetDb().Dirty.Add(parent); | ||||
| 							 | ||||
| 						} | ||||
| 					} | ||||
| 				} else { | ||||
| 					StatusLogger.UpdateMessage(UiStringKey.UndoingChanges); | ||||
| 					// If we fail to save, back out changes to global structure | ||||
| 					//TODO test fail | ||||
| 					_updatedEntry.AssignProperties(_backup, false, true, false); | ||||
|   | ||||
| @@ -20,7 +20,8 @@ namespace Kp2aUnitTests | ||||
|             // Run all tests from this assembly | ||||
|             runner.AddTests(Assembly.GetExecutingAssembly()); | ||||
| 			//runner.AddTests(new List<Type> { typeof(TestSaveDb)}); | ||||
| 			//runner.AddTests(typeof(TestLoadDb).GetMethod("TestLoadWithPasswordOnly"));}} | ||||
| 			//runner.AddTests(typeof(TestSaveDb).GetMethod("TestLoadEditSaveWithSyncConflict")); | ||||
| 			//runner.AddTests(typeof(TestSaveDb).GetMethod("TestLoadEditSave")); | ||||
|             return runner; | ||||
|         } | ||||
|     } | ||||
|   | ||||
| @@ -12,9 +12,9 @@ namespace Kp2aUnitTests | ||||
| 			 | ||||
| 		} | ||||
|  | ||||
| 		public void SetMessage(string getResourceString) | ||||
| 		public void SetMessage(string resourceString) | ||||
| 		{ | ||||
| 			 | ||||
| 			Kp2aLog.Log("Progress message: " + resourceString); | ||||
| 		} | ||||
|  | ||||
| 		public void Dismiss() | ||||
|   | ||||
| @@ -74,7 +74,7 @@ namespace Kp2aUnitTests | ||||
| 						 | ||||
| 				}) | ||||
| 				); | ||||
| 			ProgressTask pt = new ProgressTask(app, Application.Context, task, UiStringKey.loading_database); | ||||
| 			ProgressTask pt = new ProgressTask(app, Application.Context, task); | ||||
| 			pt.Run(); | ||||
| 			pt.JoinWorkerThread(); | ||||
| 			Assert.IsTrue(loadSuccesful); | ||||
| @@ -83,16 +83,27 @@ namespace Kp2aUnitTests | ||||
|  | ||||
| 		protected void SaveDatabase(IKp2aApp app) | ||||
| 		{ | ||||
| 			bool saveSuccesful = false; | ||||
| 			SaveDb save = new SaveDb(Application.Context, app.GetDb(), new ActionOnFinish((success, message) => | ||||
| 				{ | ||||
| 					saveSuccesful = success;  | ||||
| 				}), false); | ||||
| 			save.Run(); | ||||
| 			bool saveSuccesful = TrySaveDatabase(app); | ||||
|  | ||||
| 			Assert.IsTrue(saveSuccesful); | ||||
| 		} | ||||
|  | ||||
| 		public static bool TrySaveDatabase(IKp2aApp app) | ||||
| 		{ | ||||
| 			bool saveSuccesful = false; | ||||
| 			SaveDb save = new SaveDb(Application.Context, app, new ActionOnFinish((success, message) => | ||||
| 				{ | ||||
| 					saveSuccesful = success; | ||||
| 					if (!success) | ||||
| 					{ | ||||
| 						Kp2aLog.Log("Error during TestBase.SaveDatabase: " + message); | ||||
| 					} | ||||
| 				}), false); | ||||
| 			save.Run(); | ||||
| 			save.JoinWorkerThread(); | ||||
| 			return saveSuccesful; | ||||
| 		} | ||||
|  | ||||
| 		protected IKp2aApp SetupAppWithDefaultDatabase() | ||||
| 		{ | ||||
| 			IKp2aApp app = new TestKp2aApp(); | ||||
|   | ||||
| @@ -1,8 +1,10 @@ | ||||
| using System; | ||||
| using System.Collections.Generic; | ||||
| using Android.Content; | ||||
| using Android.OS; | ||||
| using KeePassLib.Serialization; | ||||
| using keepass2android; | ||||
| using keepass2android.Io; | ||||
|  | ||||
| namespace Kp2aUnitTests | ||||
| { | ||||
| @@ -11,7 +13,14 @@ namespace Kp2aUnitTests | ||||
| 	/// </summary> | ||||
| 	internal class TestKp2aApp : IKp2aApp | ||||
| 	{ | ||||
| 		internal enum YesNoCancelResult | ||||
| 		{ | ||||
| 			Yes, No, Cancel | ||||
| 		} | ||||
|  | ||||
| 		private Database _db; | ||||
| 		private YesNoCancelResult _yesNoCancelResult = YesNoCancelResult.Yes; | ||||
| 		private Dictionary<PreferenceKey, bool> _preferences = new Dictionary<PreferenceKey, bool>(); | ||||
|  | ||||
| 		public void SetShutdown() | ||||
| 		{ | ||||
| @@ -43,13 +52,32 @@ namespace Kp2aUnitTests | ||||
|  | ||||
| 		public bool GetBooleanPreference(PreferenceKey key) | ||||
| 		{ | ||||
| 			if (_preferences.ContainsKey(key)) | ||||
| 				return _preferences[key]; | ||||
| 			return true; | ||||
| 		} | ||||
|  | ||||
| 		public UiStringKey? LastYesNoCancelQuestionTitle { get; set; } | ||||
|  | ||||
| 		public void AskYesNoCancel(UiStringKey titleKey, UiStringKey messageKey, EventHandler<DialogClickEventArgs> yesHandler, EventHandler<DialogClickEventArgs> noHandler, | ||||
| 		                           EventHandler<DialogClickEventArgs> cancelHandler, Context ctx) | ||||
| 		{ | ||||
| 			yesHandler(null, null); | ||||
| 			LastYesNoCancelQuestionTitle = titleKey; | ||||
| 			switch (_yesNoCancelResult) | ||||
| 			{ | ||||
| 				case YesNoCancelResult.Yes: | ||||
| 					yesHandler(null, null); | ||||
| 					break; | ||||
| 				case YesNoCancelResult.No: | ||||
| 					noHandler(null, null); | ||||
| 					break; | ||||
| 				case YesNoCancelResult.Cancel: | ||||
| 					cancelHandler(null, null); | ||||
| 					break; | ||||
| 				default: | ||||
| 					throw new Exception("unexpected case!"); | ||||
| 			} | ||||
| 			 | ||||
| 		} | ||||
|  | ||||
| 		public Handler UiThreadHandler { | ||||
| @@ -59,5 +87,20 @@ namespace Kp2aUnitTests | ||||
| 		{ | ||||
| 			return new ProgressDialogStub(); | ||||
| 		} | ||||
|  | ||||
| 		public IFileStorage GetFileStorage(IOConnectionInfo iocInfo) | ||||
| 		{ | ||||
| 			return new BuiltInFileStorage(); | ||||
| 		} | ||||
|  | ||||
| 		public void SetYesNoCancelResult(YesNoCancelResult yesNoCancelResult) | ||||
| 		{ | ||||
| 			_yesNoCancelResult = yesNoCancelResult; | ||||
| 		} | ||||
|  | ||||
| 		public void SetPreference(PreferenceKey key, bool value) | ||||
| 		{ | ||||
| 			_preferences[key] = value; | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
| @@ -26,7 +26,7 @@ namespace Kp2aUnitTests | ||||
| 						loadSuccesful = success; 		 | ||||
| 					}) | ||||
| 				); | ||||
| 			ProgressTask pt = new ProgressTask(app, Application.Context, task, UiStringKey.loading_database); | ||||
| 			ProgressTask pt = new ProgressTask(app, Application.Context, task); | ||||
| 			Android.Util.Log.Debug("KP2ATest", "Running ProgressTask"); | ||||
| 			pt.Run(); | ||||
| 			pt.JoinWorkerThread(); | ||||
|   | ||||
| @@ -1,11 +1,13 @@ | ||||
| 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; | ||||
| @@ -23,6 +25,7 @@ namespace Kp2aUnitTests | ||||
| 		{ | ||||
| 			//create the default database: | ||||
| 			IKp2aApp app = SetupAppWithDefaultDatabase(); | ||||
| 			IOConnection.DeleteFile(new IOConnectionInfo { Path = DefaultFilename }); | ||||
| 			//save it and reload it so we have a base version | ||||
| 			SaveDatabase(app); | ||||
| 			app = LoadDatabase(DefaultFilename, DefaultPassword, DefaultKeyfile); | ||||
| @@ -56,9 +59,14 @@ namespace Kp2aUnitTests | ||||
| 			//save the database from app 1: | ||||
| 			SaveDatabase(app); | ||||
|  | ||||
| 			((TestKp2aApp)app2).SetYesNoCancelResult(TestKp2aApp.YesNoCancelResult.Yes); | ||||
|  | ||||
| 			//save the database from app 2: This save operation must detect the changes made from app 1 and ask if it should sync: | ||||
| 			SaveDatabase(app2); | ||||
|  | ||||
| 			//make sure the right question was asked | ||||
| 			Assert.AreEqual(UiStringKey.TitleSyncQuestion, ((TestKp2aApp)app2).LastYesNoCancelQuestionTitle); | ||||
|  | ||||
| 			//add group 2 to app 1: | ||||
| 			app.GetDb().KpDatabase.RootGroup.AddGroup(group2, true); | ||||
|  | ||||
| @@ -68,7 +76,162 @@ namespace Kp2aUnitTests | ||||
| 			//ensure the sync was successful: | ||||
| 			AssertDatabasesAreEqual(app.GetDb().KpDatabase, resultApp.GetDb().KpDatabase); | ||||
|  | ||||
| 			Assert.IsTrue(false, "todo: test for sync question, test overwrite or cancel!"); | ||||
| 		} | ||||
|  | ||||
|  | ||||
| 		[TestMethod] | ||||
| 		public void TestLoadEditSaveWithSyncOverwrite() | ||||
| 		{ | ||||
| 			//create the default database: | ||||
| 			IKp2aApp app = SetupAppWithDefaultDatabase(); | ||||
| 			//save it and reload it so we have a base version | ||||
| 			SaveDatabase(app); | ||||
| 			app = LoadDatabase(DefaultFilename, DefaultPassword, DefaultKeyfile); | ||||
| 			//load it once again: | ||||
| 			IKp2aApp app2 = LoadDatabase(DefaultFilename, DefaultPassword, DefaultKeyfile); | ||||
|  | ||||
| 			//modify the database by adding a group in both databases: | ||||
| 			app.GetDb().KpDatabase.RootGroup.AddGroup(new PwGroup(true, true, "TestGroup", PwIcon.Apple), true); | ||||
| 			var group2 = new PwGroup(true, true, "TestGroup2", PwIcon.Energy); | ||||
| 			app2.GetDb().KpDatabase.RootGroup.AddGroup(group2, true); | ||||
| 			//save the database from app 1: | ||||
| 			SaveDatabase(app); | ||||
|  | ||||
| 			//the user clicks the "no" button when asked if the sync should be performed -> overwrite expected! | ||||
| 			((TestKp2aApp)app2).SetYesNoCancelResult(TestKp2aApp.YesNoCancelResult.No); | ||||
|  | ||||
| 			//save the database from app 2: This save operation must detect the changes made from app 1 and ask if it should sync: | ||||
| 			SaveDatabase(app2); | ||||
|  | ||||
| 			//make sure the right question was asked | ||||
| 			Assert.AreEqual(UiStringKey.TitleSyncQuestion, ((TestKp2aApp)app2).LastYesNoCancelQuestionTitle); | ||||
|  | ||||
| 			//load database to a new app instance: | ||||
| 			IKp2aApp resultApp = LoadDatabase(DefaultFilename, DefaultPassword, DefaultKeyfile); | ||||
|  | ||||
| 			//ensure the sync was NOT performed (overwrite expected!): | ||||
| 			AssertDatabasesAreEqual(app2.GetDb().KpDatabase, resultApp.GetDb().KpDatabase); | ||||
|  | ||||
| 		} | ||||
|  | ||||
|  | ||||
| 		[TestMethod] | ||||
| 		public void TestLoadEditSaveWithSyncOverwriteBecauseOfNoCheck() | ||||
| 		{ | ||||
| 			//create the default database: | ||||
| 			IKp2aApp app = SetupAppWithDefaultDatabase(); | ||||
| 			//save it and reload it so we have a base version | ||||
| 			SaveDatabase(app); | ||||
| 			app = LoadDatabase(DefaultFilename, DefaultPassword, DefaultKeyfile); | ||||
| 			//load it once again: | ||||
| 			IKp2aApp app2 = LoadDatabase(DefaultFilename, DefaultPassword, DefaultKeyfile); | ||||
|  | ||||
| 			//modify the database by adding a group in both databases: | ||||
| 			app.GetDb().KpDatabase.RootGroup.AddGroup(new PwGroup(true, true, "TestGroup", PwIcon.Apple), true); | ||||
| 			var group2 = new PwGroup(true, true, "TestGroup2", PwIcon.Energy); | ||||
| 			app2.GetDb().KpDatabase.RootGroup.AddGroup(group2, true); | ||||
| 			//save the database from app 1: | ||||
| 			SaveDatabase(app); | ||||
|  | ||||
| 			//the user doesn't want to perform check for file change: | ||||
| 			((TestKp2aApp) app2).SetPreference(PreferenceKey.CheckForFileChangesOnSave, false); | ||||
|  | ||||
| 			//save the database from app 2: This save operation must detect the changes made from app 1 and ask if it should sync: | ||||
| 			SaveDatabase(app2); | ||||
|  | ||||
| 			//make sure no question was asked | ||||
| 			Assert.AreEqual(null, ((TestKp2aApp)app2).LastYesNoCancelQuestionTitle); | ||||
|  | ||||
| 			//load database to a new app instance: | ||||
| 			IKp2aApp resultApp = LoadDatabase(DefaultFilename, DefaultPassword, DefaultKeyfile); | ||||
|  | ||||
| 			//ensure the sync was NOT performed (overwrite expected!): | ||||
| 			AssertDatabasesAreEqual(app2.GetDb().KpDatabase, resultApp.GetDb().KpDatabase); | ||||
|  | ||||
| 		} | ||||
|  | ||||
| 		[TestMethod] | ||||
| 		public void TestLoadEditSaveWithSyncCancel() | ||||
| 		{ | ||||
| 			//create the default database: | ||||
| 			IKp2aApp app = SetupAppWithDefaultDatabase(); | ||||
| 			//save it and reload it so we have a base version | ||||
| 			SaveDatabase(app); | ||||
| 			app = LoadDatabase(DefaultFilename, DefaultPassword, DefaultKeyfile); | ||||
| 			//load it once again: | ||||
| 			IKp2aApp app2 = LoadDatabase(DefaultFilename, DefaultPassword, DefaultKeyfile); | ||||
|  | ||||
| 			//modify the database by adding a group in both databases: | ||||
| 			app.GetDb().KpDatabase.RootGroup.AddGroup(new PwGroup(true, true, "TestGroup", PwIcon.Apple), true); | ||||
| 			var group2 = new PwGroup(true, true, "TestGroup2", PwIcon.Energy); | ||||
| 			app2.GetDb().KpDatabase.RootGroup.AddGroup(group2, true); | ||||
| 			//save the database from app 1: | ||||
| 			SaveDatabase(app); | ||||
|  | ||||
| 			//the user clicks the "cancel" button when asked if the sync should be performed | ||||
| 			((TestKp2aApp)app2).SetYesNoCancelResult(TestKp2aApp.YesNoCancelResult.Cancel); | ||||
|  | ||||
| 			//save the database from app 2: This save operation must detect the changes made from app 1 and ask if it should sync: | ||||
| 			Assert.AreEqual(false, TrySaveDatabase(app2)); | ||||
|  | ||||
| 			//make sure the right question was asked | ||||
| 			Assert.AreEqual(UiStringKey.TitleSyncQuestion, ((TestKp2aApp)app2).LastYesNoCancelQuestionTitle); | ||||
|  | ||||
| 			//load database to a new app instance: | ||||
| 			IKp2aApp resultApp = LoadDatabase(DefaultFilename, DefaultPassword, DefaultKeyfile); | ||||
|  | ||||
| 			//ensure the sync was NOT performed (cancel expected!): | ||||
| 			AssertDatabasesAreEqual(app.GetDb().KpDatabase, resultApp.GetDb().KpDatabase); | ||||
|  | ||||
| 		} | ||||
|  | ||||
|  | ||||
| 		[TestMethod] | ||||
| 		public void TestLoadEditSaveWithSyncConflict() | ||||
| 		{ | ||||
| 		 | ||||
| 			//create the default database: | ||||
| 			IKp2aApp app = SetupAppWithDefaultDatabase(); | ||||
| 			//save it and reload it so we have a base version | ||||
| 			SaveDatabase(app); | ||||
| 			app = LoadDatabase(DefaultFilename, DefaultPassword, DefaultKeyfile); | ||||
| 			//load it once again: | ||||
| 			IKp2aApp app2 = LoadDatabase(DefaultFilename, DefaultPassword, DefaultKeyfile); | ||||
|  | ||||
| 			//modify the database by renaming the same group in both databases: | ||||
| 			app.GetDb().KpDatabase.RootGroup.Groups.Single(g => g.Name == "Internet").Name += "abc"; | ||||
| 			app2.GetDb().KpDatabase.RootGroup.Groups.Single(g => g.Name == "Internet").Name += "abcde"; | ||||
| 			//app1 also changes the master password: | ||||
| 			var compositeKey = app.GetDb().KpDatabase.MasterKey; | ||||
| 			compositeKey.RemoveUserKey(compositeKey.GetUserKey(typeof (KcpPassword))); | ||||
| 			compositeKey.AddUserKey(new KcpPassword("abc")); | ||||
| 			 | ||||
| 			//save the database from app 1: | ||||
| 			SaveDatabase(app); | ||||
|  | ||||
|  | ||||
| 			((TestKp2aApp)app2).SetYesNoCancelResult(TestKp2aApp.YesNoCancelResult.Yes); | ||||
|  | ||||
| 			//save the database from app 2: This save operation must fail because the target file cannot be loaded: | ||||
| 			Assert.IsFalse(TrySaveDatabase(app2)); | ||||
|  | ||||
| 			//make sure the right question was asked | ||||
| 			Assert.AreEqual(UiStringKey.TitleSyncQuestion, ((TestKp2aApp)app2).LastYesNoCancelQuestionTitle); | ||||
|  | ||||
| 		} | ||||
|  | ||||
|  | ||||
|  | ||||
| 		[TestMethod] | ||||
| 		public void TestSaveAsWhenReadOnly() | ||||
| 		{ | ||||
| 			Assert.Fail("TODO: Test "); | ||||
| 		} | ||||
|  | ||||
| 		[TestMethod] | ||||
| 		public void TestSaveAsWhenSyncError() | ||||
| 		{ | ||||
| 			Assert.Fail("TODO: Test "); | ||||
| 		} | ||||
|  | ||||
| 		[TestMethod] | ||||
| @@ -119,4 +282,6 @@ namespace Kp2aUnitTests | ||||
| 			return sOutput.Text; | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	 | ||||
| } | ||||
|   | ||||
| @@ -123,8 +123,8 @@ namespace keepass2android | ||||
| 				Entry.Expires = true; | ||||
| 				Entry.Touch(true); | ||||
| 				RequiresRefresh(); | ||||
| 				UpdateEntry update = new UpdateEntry(this, App.Kp2a.GetDb(), backupEntry, Entry, null); | ||||
|                 ProgressTask pt = new ProgressTask(App.Kp2a, this, update, UiStringKey.saving_database); | ||||
| 				UpdateEntry update = new UpdateEntry(this, App.Kp2a, backupEntry, Entry, null); | ||||
|                 ProgressTask pt = new ProgressTask(App.Kp2a, this, update); | ||||
| 				pt.Run(); | ||||
| 			} | ||||
| 			FillData(false); | ||||
|   | ||||
| @@ -383,11 +383,11 @@ namespace keepass2android | ||||
| 			},closeOrShowError); | ||||
|  | ||||
| 			if ( State.IsNew ) { | ||||
| 				runnable = AddEntry.GetInstance(this, App.Kp2a.GetDb(), newEntry, State.ParentGroup, afterAddEntry); | ||||
| 				runnable = AddEntry.GetInstance(this, App.Kp2a, newEntry, State.ParentGroup, afterAddEntry); | ||||
| 			} else { | ||||
| 				runnable = new UpdateEntry(this, App.Kp2a.GetDb(), initialEntry, newEntry, closeOrShowError); | ||||
| 				runnable = new UpdateEntry(this, App.Kp2a, initialEntry, newEntry, closeOrShowError); | ||||
| 			} | ||||
|             ProgressTask pt = new ProgressTask(App.Kp2a, act, runnable, UiStringKey.saving_database); | ||||
|             ProgressTask pt = new ProgressTask(App.Kp2a, act, runnable); | ||||
| 			pt.Run(); | ||||
| 			 | ||||
|  | ||||
|   | ||||
| @@ -177,8 +177,8 @@ namespace keepass2android | ||||
| 				int groupIconId = data.Extras.GetInt(GroupEditActivity.KeyIconId); | ||||
| 				GroupBaseActivity act = this; | ||||
| 				Handler handler = new Handler(); | ||||
| 				AddGroup task = AddGroup.GetInstance(this, App.Kp2a.GetDb(), groupName, groupIconId, Group, new RefreshTask(handler, this), false); | ||||
|                 ProgressTask pt = new ProgressTask(App.Kp2a, act, task, UiStringKey.saving_database); | ||||
| 				AddGroup task = AddGroup.GetInstance(this, App.Kp2a, groupName, groupIconId, Group, new RefreshTask(handler, this), false); | ||||
|                 ProgressTask pt = new ProgressTask(App.Kp2a, act, task); | ||||
| 				pt.Run(); | ||||
| 				break; | ||||
| 				 | ||||
|   | ||||
| @@ -365,7 +365,7 @@ namespace keepass2android | ||||
| 				 | ||||
| 				Handler handler = new Handler(); | ||||
| 				LoadDb task = new LoadDb(App.Kp2a, _ioConnection, pass, key, new AfterLoad(handler, this)); | ||||
| 				ProgressTask pt = new ProgressTask(App.Kp2a, this, task, UiStringKey.loading_database); | ||||
| 				ProgressTask pt = new ProgressTask(App.Kp2a, this, task); | ||||
| 				pt.Run(); | ||||
| 			}; | ||||
| 			 | ||||
|   | ||||
							
								
								
									
										62
									
								
								src/keepass2android/Resources/Resource.designer.cs
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										62
									
								
								src/keepass2android/Resources/Resource.designer.cs
									
									
									
										generated
									
									
									
								
							| @@ -1230,6 +1230,12 @@ namespace keepass2android | ||||
| 			// aapt resource value: 0x7f080033 | ||||
| 			public const int AboutText = 2131230771; | ||||
| 			 | ||||
| 			// aapt resource value: 0x7f08011e | ||||
| 			public const int AddingEntry = 2131231006; | ||||
| 			 | ||||
| 			// aapt resource value: 0x7f08011f | ||||
| 			public const int AddingGroup = 2131231007; | ||||
| 			 | ||||
| 			// aapt resource value: 0x7f080114 | ||||
| 			public const int AskDeletePermanentlyEntry = 2131230996; | ||||
| 			 | ||||
| @@ -1278,29 +1284,32 @@ namespace keepass2android | ||||
| 			// aapt resource value: 0x7f0800f5 | ||||
| 			public const int BinaryDirectory_title = 2131230965; | ||||
| 			 | ||||
| 			// aapt resource value: 0x7f080125 | ||||
| 			public const int ChangeLog = 2131231013; | ||||
| 			// aapt resource value: 0x7f08012f | ||||
| 			public const int ChangeLog = 2131231023; | ||||
| 			 | ||||
| 			// aapt resource value: 0x7f080124 | ||||
| 			public const int ChangeLog_0_7 = 2131231012; | ||||
| 			// aapt resource value: 0x7f08012e | ||||
| 			public const int ChangeLog_0_7 = 2131231022; | ||||
| 			 | ||||
| 			// aapt resource value: 0x7f080122 | ||||
| 			public const int ChangeLog_0_8 = 2131231010; | ||||
| 			// aapt resource value: 0x7f08012c | ||||
| 			public const int ChangeLog_0_8 = 2131231020; | ||||
| 			 | ||||
| 			// aapt resource value: 0x7f080121 | ||||
| 			public const int ChangeLog_0_8_1 = 2131231009; | ||||
| 			// aapt resource value: 0x7f08012b | ||||
| 			public const int ChangeLog_0_8_1 = 2131231019; | ||||
| 			 | ||||
| 			// aapt resource value: 0x7f080120 | ||||
| 			public const int ChangeLog_0_8_2 = 2131231008; | ||||
| 			// aapt resource value: 0x7f08012a | ||||
| 			public const int ChangeLog_0_8_2 = 2131231018; | ||||
| 			 | ||||
| 			// aapt resource value: 0x7f08011f | ||||
| 			public const int ChangeLog_0_8_3 = 2131231007; | ||||
| 			// aapt resource value: 0x7f080129 | ||||
| 			public const int ChangeLog_0_8_3 = 2131231017; | ||||
| 			 | ||||
| 			// aapt resource value: 0x7f080123 | ||||
| 			public const int ChangeLog_keptDonate = 2131231011; | ||||
| 			// aapt resource value: 0x7f08012d | ||||
| 			public const int ChangeLog_keptDonate = 2131231021; | ||||
| 			 | ||||
| 			// aapt resource value: 0x7f08011e | ||||
| 			public const int ChangeLog_title = 2131231006; | ||||
| 			// aapt resource value: 0x7f080128 | ||||
| 			public const int ChangeLog_title = 2131231016; | ||||
| 			 | ||||
| 			// aapt resource value: 0x7f080127 | ||||
| 			public const int CheckingTargetFileForChanges = 2131231015; | ||||
| 			 | ||||
| 			// aapt resource value: 0x7f080048 | ||||
| 			public const int ClearClipboard = 2131230792; | ||||
| @@ -1311,6 +1320,15 @@ namespace keepass2android | ||||
| 			// aapt resource value: 0x7f080034 | ||||
| 			public const int CreditsText = 2131230772; | ||||
| 			 | ||||
| 			// aapt resource value: 0x7f080125 | ||||
| 			public const int DecodingDatabase = 2131231013; | ||||
| 			 | ||||
| 			// aapt resource value: 0x7f080120 | ||||
| 			public const int DeletingEntry = 2131231008; | ||||
| 			 | ||||
| 			// aapt resource value: 0x7f080121 | ||||
| 			public const int DeletingGroup = 2131231009; | ||||
| 			 | ||||
| 			// aapt resource value: 0x7f080081 | ||||
| 			public const int FileNotFound = 2131230849; | ||||
| 			 | ||||
| @@ -1335,6 +1353,9 @@ namespace keepass2android | ||||
| 			// aapt resource value: 0x7f08010c | ||||
| 			public const int OpenKp2aKeyboardAutomatically_title = 2131230988; | ||||
| 			 | ||||
| 			// aapt resource value: 0x7f080126 | ||||
| 			public const int ParsingDatabase = 2131231014; | ||||
| 			 | ||||
| 			// aapt resource value: 0x7f080023 | ||||
| 			public const int QuickUnlockDefaultEnabled_key = 2131230755; | ||||
| 			 | ||||
| @@ -1389,6 +1410,9 @@ namespace keepass2android | ||||
| 			// aapt resource value: 0x7f0800fb | ||||
| 			public const int SaveAttachment_doneMessage = 2131230971; | ||||
| 			 | ||||
| 			// aapt resource value: 0x7f080122 | ||||
| 			public const int SettingPassword = 2131231010; | ||||
| 			 | ||||
| 			// aapt resource value: 0x7f080109 | ||||
| 			public const int ShowCopyToClipboardNotification_summary = 2131230985; | ||||
| 			 | ||||
| @@ -1422,9 +1446,15 @@ namespace keepass2android | ||||
| 			// aapt resource value: 0x7f0800de | ||||
| 			public const int TanExpiresOnUse_title = 2131230942; | ||||
| 			 | ||||
| 			// aapt resource value: 0x7f080124 | ||||
| 			public const int TransformingKey = 2131231012; | ||||
| 			 | ||||
| 			// aapt resource value: 0x7f08002b | ||||
| 			public const int TranslationURL = 2131230763; | ||||
| 			 | ||||
| 			// aapt resource value: 0x7f080123 | ||||
| 			public const int UndoingChanges = 2131231011; | ||||
| 			 | ||||
| 			// aapt resource value: 0x7f080026 | ||||
| 			public const int UsageCount_key = 2131230758; | ||||
| 			 | ||||
|   | ||||
| @@ -240,6 +240,17 @@ | ||||
|   <string name="suggest_improvements">Suggest or vote for improvements</string> | ||||
|   <string name="rate_app">Rate this app</string> | ||||
|   <string name="translate_app">Translate KP2A</string> | ||||
| 	<string name="AddingEntry">Adding entry…</string> | ||||
| 	<string name="AddingGroup">Adding group…</string> | ||||
| 	<string name="DeletingEntry">Deleting entry…</string> | ||||
| 	<string name="DeletingGroup">Deleting group…</string> | ||||
| 	<string name="SettingPassword">Setting password…</string> | ||||
| 	<string name="UndoingChanges">Undoing changes…</string> | ||||
| 	<string name="TransformingKey">Transforming master key…</string> | ||||
| 	<string name="DecodingDatabase">Decoding database…</string> | ||||
| 	<string name="ParsingDatabase">Parsing database…</string> | ||||
| 	<string name="CheckingTargetFileForChanges">Checking target file for changes…</string> | ||||
| 	 | ||||
|   <string name="ChangeLog_title">Change log</string> | ||||
|   <string name="ChangeLog_0_8_3"><b>Version 0.8.3</b>\n | ||||
| * Username/TAN index displayed in entry list (see settings)\n | ||||
|   | ||||
| @@ -71,8 +71,8 @@ namespace keepass2android | ||||
| 					 | ||||
| 				} | ||||
| 				 | ||||
| 				SetPassword sp = new SetPassword(Context, App.Kp2a.GetDb(), pass, keyfile, new AfterSave(this, _finish, new Handler())); | ||||
| 				ProgressTask pt = new ProgressTask(App.Kp2a, Context, sp, UiStringKey.saving_database); | ||||
| 				SetPassword sp = new SetPassword(Context, App.Kp2a, pass, keyfile, new AfterSave(this, _finish, new Handler())); | ||||
| 				ProgressTask pt = new ProgressTask(App.Kp2a, Context, sp); | ||||
| 				pt.Run(); | ||||
| 			}; | ||||
| 				 | ||||
|   | ||||
| @@ -22,6 +22,7 @@ using Android.OS; | ||||
| using Android.Runtime; | ||||
| using KeePassLib.Serialization; | ||||
| using Android.Preferences; | ||||
| using keepass2android.Io; | ||||
|  | ||||
| namespace keepass2android | ||||
| { | ||||
| @@ -223,6 +224,11 @@ namespace keepass2android | ||||
| 			return new RealProgressDialog(ctx); | ||||
| 		} | ||||
|  | ||||
| 		public IFileStorage GetFileStorage(IOConnectionInfo iocInfo) | ||||
| 		{ | ||||
| 			return new BuiltInFileStorage(); | ||||
| 		} | ||||
|  | ||||
|  | ||||
| 		internal void OnTerminate() | ||||
|         { | ||||
|   | ||||
| @@ -170,8 +170,7 @@ namespace keepass2android | ||||
| 				CreateDb create = new CreateDb(App.Kp2a, this, IOConnectionInfo.FromPath(filename), collectPassword, true); | ||||
| 				ProgressTask createTask = new ProgressTask( | ||||
|                     App.Kp2a, | ||||
| 					this, create, | ||||
| 					UiStringKey.progress_create); | ||||
| 					this, create); | ||||
| 				createTask.Run(); | ||||
| 				 | ||||
| 				 | ||||
| @@ -313,7 +312,8 @@ namespace keepass2android | ||||
| 					GroupActivity.Launch(_activity, _activity.AppTask); | ||||
| 					 | ||||
| 				} else { | ||||
| 					IOConnection.DeleteFile(_ioc); | ||||
| 					App.Kp2a.GetFileStorage(_ioc).DeleteFile(_ioc); | ||||
| 					 | ||||
| 				} | ||||
| 			} | ||||
| 		} | ||||
|   | ||||
| @@ -73,7 +73,7 @@ namespace keepass2android | ||||
| 					String previousUsername = db.KpDatabase.DefaultUserName; | ||||
| 					db.KpDatabase.DefaultUserName = e.NewValue.ToString(); | ||||
| 				 | ||||
| 					SaveDb save = new SaveDb(this, App.Kp2a.GetDb(), new ActionOnFinish( (success, message) =>  | ||||
| 					SaveDb save = new SaveDb(this, App.Kp2a, new ActionOnFinish( (success, message) =>  | ||||
| 					                                                         { | ||||
| 						if (!success) | ||||
| 						{ | ||||
| @@ -82,7 +82,7 @@ namespace keepass2android | ||||
| 							Toast.MakeText(this, message, ToastLength.Long).Show(); | ||||
| 						} | ||||
| 					})); | ||||
| 					ProgressTask pt = new ProgressTask(App.Kp2a, this, save, UiStringKey.saving_database); | ||||
| 					ProgressTask pt = new ProgressTask(App.Kp2a, this, save); | ||||
| 					pt.Run(); | ||||
| 				}; | ||||
|  | ||||
| @@ -95,7 +95,7 @@ namespace keepass2android | ||||
| 					String previousName = db.KpDatabase.Name; | ||||
| 					db.KpDatabase.Name = e.NewValue.ToString(); | ||||
| 					 | ||||
| 					SaveDb save = new SaveDb(this, App.Kp2a.GetDb(), new ActionOnFinish( (success, message) =>  | ||||
| 					SaveDb save = new SaveDb(this, App.Kp2a, new ActionOnFinish( (success, message) =>  | ||||
| 					                                                               { | ||||
| 						if (!success) | ||||
| 						{ | ||||
| @@ -104,7 +104,7 @@ namespace keepass2android | ||||
| 							Toast.MakeText(this, message, ToastLength.Long).Show(); | ||||
| 						} | ||||
| 					})); | ||||
|                     ProgressTask pt = new ProgressTask(App.Kp2a, this, save, UiStringKey.saving_database); | ||||
|                     ProgressTask pt = new ProgressTask(App.Kp2a, this, save); | ||||
| 					pt.Run(); | ||||
| 				}; | ||||
| 				 | ||||
|   | ||||
| @@ -80,8 +80,8 @@ namespace keepass2android.settings | ||||
| 				PwDatabase.KeyEncryptionRounds = rounds; | ||||
|  | ||||
| 				Handler handler = new Handler(); | ||||
| 				SaveDb save = new SaveDb(Context, App.Kp2a.GetDb(), new AfterSave(Context, handler, oldRounds, this)); | ||||
| 				ProgressTask pt = new ProgressTask(App.Kp2a, Context, save, UiStringKey.saving_database); | ||||
| 				SaveDb save = new SaveDb(Context, App.Kp2a, new AfterSave(Context, handler, oldRounds, this)); | ||||
| 				ProgressTask pt = new ProgressTask(App.Kp2a, Context, save); | ||||
| 				pt.Run(); | ||||
| 				 | ||||
| 			} | ||||
|   | ||||
		Reference in New Issue
	
	Block a user
	 Philipp Crocoll
					Philipp Crocoll