+ 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