mirror of
https://github.com/moparisthebest/keepass2android
synced 2024-12-01 13:32:27 -05:00
+ SynchronizeCachedDatabase.cs: Synchronizes the local cache with the remote file. Applies merging if necessary.
+ Tests (not yet complete)
This commit is contained in:
parent
3cfb2c17e6
commit
c0520c055f
@ -105,7 +105,8 @@ namespace KeePassLib.Serialization
|
|||||||
// Not implemented and ignored in Mono < 2.10
|
// Not implemented and ignored in Mono < 2.10
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
request.CachePolicy = new RequestCachePolicy(RequestCacheLevel.NoCacheNoStore);
|
//deactivated. No longer supported in Mono 4.8?
|
||||||
|
//request.CachePolicy = new RequestCachePolicy(RequestCacheLevel.NoCacheNoStore);
|
||||||
}
|
}
|
||||||
catch(NotImplementedException) { }
|
catch(NotImplementedException) { }
|
||||||
catch(Exception) { Debug.Assert(false); }
|
catch(Exception) { Debug.Assert(false); }
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
using System;
|
using System;
|
||||||
|
using Android.App;
|
||||||
using Android.Content;
|
using Android.Content;
|
||||||
using Android.OS;
|
using Android.OS;
|
||||||
using KeePassLib.Serialization;
|
using KeePassLib.Serialization;
|
||||||
@ -70,5 +71,6 @@ namespace keepass2android
|
|||||||
IProgressDialog CreateProgressDialog(Context ctx);
|
IProgressDialog CreateProgressDialog(Context ctx);
|
||||||
IFileStorage GetFileStorage(IOConnectionInfo iocInfo);
|
IFileStorage GetFileStorage(IOConnectionInfo iocInfo);
|
||||||
|
|
||||||
|
void TriggerReload(Context context);
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -118,7 +118,7 @@ namespace keepass2android.Io
|
|||||||
try
|
try
|
||||||
{
|
{
|
||||||
if (!IsCached(ioc)
|
if (!IsCached(ioc)
|
||||||
|| File.ReadAllText(VersionFilePath(ioc)) == File.ReadAllText(BaseVersionFilePath(ioc)))
|
|| GetLocalVersionHash(ioc) == GetBaseVersionHash(ioc))
|
||||||
{
|
{
|
||||||
return OpenFileForReadWhenNoLocalChanges(ioc, cachedFilePath);
|
return OpenFileForReadWhenNoLocalChanges(ioc, cachedFilePath);
|
||||||
}
|
}
|
||||||
@ -141,7 +141,7 @@ namespace keepass2android.Io
|
|||||||
{
|
{
|
||||||
//file is cached but has local modifications
|
//file is cached but has local modifications
|
||||||
//try to upload the changes if remote file doesn't have changes as well:
|
//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)
|
if (File.ReadAllText(BaseVersionFilePath(ioc)) == hash)
|
||||||
{
|
{
|
||||||
@ -160,18 +160,25 @@ namespace keepass2android.Io
|
|||||||
return File.OpenRead(cachedFilePath);
|
return File.OpenRead(cachedFilePath);
|
||||||
}
|
}
|
||||||
|
|
||||||
private string Calculate(IOConnectionInfo ioc)
|
public MemoryStream GetRemoteDataAndHash(IOConnectionInfo ioc, out string hash)
|
||||||
{
|
{
|
||||||
MemoryStream remoteData = new MemoryStream();
|
MemoryStream remoteData = new MemoryStream();
|
||||||
string hash;
|
|
||||||
using (
|
using (
|
||||||
HashingStreamEx hashingRemoteStream = new HashingStreamEx(_cachedStorage.OpenFileForRead(ioc), false,
|
HashingStreamEx hashingRemoteStream = new HashingStreamEx(_cachedStorage.OpenFileForRead(ioc), false,
|
||||||
new SHA256Managed()))
|
new SHA256Managed()))
|
||||||
{
|
{
|
||||||
hashingRemoteStream.CopyTo(remoteData);
|
hashingRemoteStream.CopyTo(remoteData);
|
||||||
hashingRemoteStream.Close();
|
hashingRemoteStream.Close();
|
||||||
hash = MemUtil.ByteArrayToHexString(hashingRemoteStream.Hash);
|
hash = MemUtil.ByteArrayToHexString(hashingRemoteStream.Hash);
|
||||||
}
|
}
|
||||||
|
remoteData.Position = 0;
|
||||||
|
return remoteData;
|
||||||
|
}
|
||||||
|
|
||||||
|
private string CalculateHash(IOConnectionInfo ioc)
|
||||||
|
{
|
||||||
|
string hash;
|
||||||
|
GetRemoteDataAndHash(ioc, out hash);
|
||||||
return hash;
|
return hash;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -203,17 +210,7 @@ namespace keepass2android.Io
|
|||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
//try to write to remote:
|
UpdateRemoteFile(cachedData, ioc, useFileTransaction, hash);
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
catch (Exception e)
|
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 CachedWriteTransaction: IWriteTransaction
|
||||||
{
|
{
|
||||||
private class CachedWriteMemoryStream : MemoryStream
|
private class CachedWriteMemoryStream : MemoryStream
|
||||||
@ -231,6 +255,7 @@ namespace keepass2android.Io
|
|||||||
private readonly IOConnectionInfo ioc;
|
private readonly IOConnectionInfo ioc;
|
||||||
private readonly CachingFileStorage _cachingFileStorage;
|
private readonly CachingFileStorage _cachingFileStorage;
|
||||||
private readonly bool _useFileTransaction;
|
private readonly bool _useFileTransaction;
|
||||||
|
private bool _closed;
|
||||||
|
|
||||||
public CachedWriteMemoryStream(IOConnectionInfo ioc, CachingFileStorage cachingFileStorage, bool useFileTransaction)
|
public CachedWriteMemoryStream(IOConnectionInfo ioc, CachingFileStorage cachingFileStorage, bool useFileTransaction)
|
||||||
{
|
{
|
||||||
@ -242,6 +267,8 @@ namespace keepass2android.Io
|
|||||||
|
|
||||||
public override void Close()
|
public override void Close()
|
||||||
{
|
{
|
||||||
|
if (_closed) return;
|
||||||
|
|
||||||
//write file to cache:
|
//write file to cache:
|
||||||
//(note: this might overwrite local changes. It's assumed that a sync operation or check was performed before
|
//(note: this might overwrite local changes. It's assumed that a sync operation or check was performed before
|
||||||
string hash;
|
string hash;
|
||||||
@ -257,9 +284,20 @@ namespace keepass2android.Io
|
|||||||
File.WriteAllText(_cachingFileStorage.VersionFilePath(ioc), hash);
|
File.WriteAllText(_cachingFileStorage.VersionFilePath(ioc), hash);
|
||||||
//update file on remote. This might overwrite changes there as well, see above.
|
//update file on remote. This might overwrite changes there as well, see above.
|
||||||
Position = 0;
|
Position = 0;
|
||||||
_cachingFileStorage.TryUpdateRemoteFile(this, ioc, _useFileTransaction, hash);
|
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();
|
base.Close();
|
||||||
|
|
||||||
|
_closed = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@ -280,7 +318,18 @@ namespace keepass2android.Io
|
|||||||
public void Dispose()
|
public void Dispose()
|
||||||
{
|
{
|
||||||
if (!_committed)
|
if (!_committed)
|
||||||
_memoryStream.Dispose();
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
_memoryStream.Dispose();
|
||||||
|
}
|
||||||
|
catch (ObjectDisposedException e)
|
||||||
|
{
|
||||||
|
Kp2aLog.Log("Ignoring exception in Dispose: "+e);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public Stream OpenFile()
|
public Stream OpenFile()
|
||||||
@ -321,5 +370,32 @@ namespace keepass2android.Io
|
|||||||
return UrlUtil.StripExtension(
|
return UrlUtil.StripExtension(
|
||||||
UrlUtil.GetFileName(ioc.Path));
|
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" />
|
<Reference Include="System.Xml" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
<Compile Include="database\SynchronizeCachedDatabase.cs" />
|
||||||
<Compile Include="Io\BuiltInFileStorage.cs" />
|
<Compile Include="Io\BuiltInFileStorage.cs" />
|
||||||
<Compile Include="Io\CachingFileStorage.cs" />
|
<Compile Include="Io\CachingFileStorage.cs" />
|
||||||
<Compile Include="Io\IFileStorage.cs" />
|
<Compile Include="Io\IFileStorage.cs" />
|
||||||
|
@ -34,6 +34,11 @@ namespace keepass2android
|
|||||||
yes,
|
yes,
|
||||||
no,
|
no,
|
||||||
YesSynchronize,
|
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 {
|
public class SaveDb : RunnableOnFinish {
|
||||||
private readonly IKp2aApp _app;
|
private readonly IKp2aApp _app;
|
||||||
private readonly bool _dontSave;
|
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 readonly Context _ctx;
|
||||||
private Thread _workerThread;
|
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;
|
_ctx = ctx;
|
||||||
_app = app;
|
_app = app;
|
||||||
_dontSave = dontSave;
|
_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)
|
public SaveDb(Context ctx, IKp2aApp app, OnFinish finish)
|
||||||
: base(finish)
|
: base(finish)
|
||||||
{
|
{
|
||||||
@ -63,18 +87,25 @@ namespace keepass2android
|
|||||||
IOConnectionInfo ioc = _app.GetDb().Ioc;
|
IOConnectionInfo ioc = _app.GetDb().Ioc;
|
||||||
IFileStorage fileStorage = _app.GetFileStorage(ioc);
|
IFileStorage fileStorage = _app.GetFileStorage(ioc);
|
||||||
|
|
||||||
if ((!_app.GetBooleanPreference(PreferenceKey.CheckForFileChangesOnSave))
|
if (_streamForOrigFile == null)
|
||||||
|| (_app.GetDb().KpDatabase.HashOfFileOnDisk == null)) //first time saving
|
|
||||||
{
|
{
|
||||||
PerformSaveWithoutCheck(fileStorage, ioc);
|
if ((!_app.GetBooleanPreference(PreferenceKey.CheckForFileChangesOnSave))
|
||||||
Finish(true);
|
|| (_app.GetDb().KpDatabase.HashOfFileOnDisk == null)) //first time saving
|
||||||
return;
|
{
|
||||||
|
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:
|
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...
|
//ask user...
|
||||||
@ -183,12 +214,28 @@ namespace keepass2android
|
|||||||
pwImp.MemoryProtection = pwDatabase.MemoryProtection.CloneDeep();
|
pwImp.MemoryProtection = pwDatabase.MemoryProtection.CloneDeep();
|
||||||
pwImp.MasterKey = pwDatabase.MasterKey;
|
pwImp.MasterKey = pwDatabase.MasterKey;
|
||||||
KdbxFile kdbx = new KdbxFile(pwImp);
|
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);
|
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)
|
private void PerformSaveWithoutCheck(IFileStorage fileStorage, IOConnectionInfo ioc)
|
||||||
{
|
{
|
||||||
StatusLogger.UpdateSubMessage("");
|
StatusLogger.UpdateSubMessage("");
|
||||||
|
@ -53,6 +53,7 @@
|
|||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<None Include="Additions\AboutAdditions.txt" />
|
<None Include="Additions\AboutAdditions.txt" />
|
||||||
<None Include="Jars\AboutJars.txt" />
|
<None Include="Jars\AboutJars.txt" />
|
||||||
|
<LibraryProjectZip Include="project.zip" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<TransformFile Include="Transforms\EnumFields.xml" />
|
<TransformFile Include="Transforms\EnumFields.xml" />
|
||||||
@ -60,9 +61,4 @@
|
|||||||
<TransformFile Include="Transforms\Metadata.xml" />
|
<TransformFile Include="Transforms\Metadata.xml" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<Import Project="$(MSBuildExtensionsPath)\Novell\Xamarin.Android.Bindings.targets" />
|
<Import Project="$(MSBuildExtensionsPath)\Novell\Xamarin.Android.Bindings.targets" />
|
||||||
<ItemGroup>
|
|
||||||
<LibraryProjectZip Include="..\java\KP2ASoftKeyboard\project.zip">
|
|
||||||
<Link>project.zip</Link>
|
|
||||||
</LibraryProjectZip>
|
|
||||||
</ItemGroup>
|
|
||||||
</Project>
|
</Project>
|
@ -1,4 +1,5 @@
|
|||||||
<metadata>
|
<metadata>
|
||||||
|
<remove-node path="/api/package[@name='keepass2android.softkeyboard']/class[@name='KP2AKeyboard']" />
|
||||||
<!--
|
<!--
|
||||||
This sample removes the class: android.support.v4.content.AsyncTaskLoader.LoadTask:
|
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']" />
|
<remove-node path="/api/package[@name='android.support.v4.content']/class[@name='AsyncTaskLoader.LoadTask']" />
|
||||||
|
@ -15,6 +15,7 @@
|
|||||||
<AndroidApplication>true</AndroidApplication>
|
<AndroidApplication>true</AndroidApplication>
|
||||||
<AndroidResgenFile>Resources\Resource.Designer.cs</AndroidResgenFile>
|
<AndroidResgenFile>Resources\Resource.Designer.cs</AndroidResgenFile>
|
||||||
<GenerateSerializationAssemblies>Off</GenerateSerializationAssemblies>
|
<GenerateSerializationAssemblies>Off</GenerateSerializationAssemblies>
|
||||||
|
<AndroidManifest>Properties\AndroidManifest.xml</AndroidManifest>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
|
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
|
||||||
<DebugSymbols>true</DebugSymbols>
|
<DebugSymbols>true</DebugSymbols>
|
||||||
@ -58,16 +59,19 @@
|
|||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<Compile Include="ProgressDialogStub.cs" />
|
<Compile Include="ProgressDialogStub.cs" />
|
||||||
<Compile Include="TestBase.cs" />
|
<Compile Include="TestBase.cs" />
|
||||||
|
<Compile Include="TestCacheSupervisor.cs" />
|
||||||
<Compile Include="TestDrawableFactory.cs" />
|
<Compile Include="TestDrawableFactory.cs" />
|
||||||
<Compile Include="TestCreateDb.cs" />
|
<Compile Include="TestCreateDb.cs" />
|
||||||
<Compile Include="MainActivity.cs" />
|
<Compile Include="MainActivity.cs" />
|
||||||
<Compile Include="Resources\Resource.Designer.cs" />
|
<Compile Include="Resources\Resource.Designer.cs" />
|
||||||
<Compile Include="Properties\AssemblyInfo.cs" />
|
<Compile Include="Properties\AssemblyInfo.cs" />
|
||||||
|
<Compile Include="TestFileStorage.cs" />
|
||||||
<Compile Include="TestKp2aApp.cs" />
|
<Compile Include="TestKp2aApp.cs" />
|
||||||
<Compile Include="TestLoadDb.cs" />
|
<Compile Include="TestLoadDb.cs" />
|
||||||
<Compile Include="TestLoadDbCredentials.cs" />
|
<Compile Include="TestLoadDbCredentials.cs" />
|
||||||
<Compile Include="TestCachingFileStorage.cs" />
|
<Compile Include="TestCachingFileStorage.cs" />
|
||||||
<Compile Include="TestSaveDb.cs" />
|
<Compile Include="TestSaveDb.cs" />
|
||||||
|
<Compile Include="TestSynchronizeCachedDatabase.cs" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<None Include="Resources\AboutResources.txt" />
|
<None Include="Resources\AboutResources.txt" />
|
||||||
@ -96,6 +100,9 @@
|
|||||||
<Name>MonoDroidUnitTesting</Name>
|
<Name>MonoDroidUnitTesting</Name>
|
||||||
</ProjectReference>
|
</ProjectReference>
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
<ItemGroup>
|
||||||
|
<TransformFile Include="Properties\AndroidManifest.xml" />
|
||||||
|
</ItemGroup>
|
||||||
<Import Project="$(MSBuildExtensionsPath)\Xamarin\Android\Xamarin.Android.CSharp.targets" />
|
<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.
|
<!-- 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.
|
Other similar extension points exist, see Microsoft.Common.targets.
|
||||||
|
@ -18,8 +18,8 @@ namespace Kp2aUnitTests
|
|||||||
{
|
{
|
||||||
TestRunner runner = new TestRunner();
|
TestRunner runner = new TestRunner();
|
||||||
// Run all tests from this assembly
|
// Run all tests from this assembly
|
||||||
//runner.AddTests(Assembly.GetExecutingAssembly());
|
runner.AddTests(Assembly.GetExecutingAssembly());
|
||||||
runner.AddTests(new List<Type> { typeof(TestCachingFileStorage) });
|
//runner.AddTests(new List<Type> { typeof(TestSynchronizeCachedDatabase) });
|
||||||
//runner.AddTests(typeof(TestCachingFileStorage).GetMethod("TestSaveToRemote"));
|
//runner.AddTests(typeof(TestCachingFileStorage).GetMethod("TestSaveToRemote"));
|
||||||
//runner.AddTests(typeof(TestLoadDb).GetMethod("TestLoadKdbpWithPasswordOnly"));
|
//runner.AddTests(typeof(TestLoadDb).GetMethod("TestLoadKdbpWithPasswordOnly"));
|
||||||
//runner.AddTests(typeof(TestSaveDb).GetMethod("TestLoadKdbxAndSaveKdbp_TestIdenticalFiles"));
|
//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/"; }
|
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();
|
app.CreateNewDatabase();
|
||||||
bool loadSuccesful = false;
|
bool loadSuccesful = false;
|
||||||
LoadDb task = new LoadDb(app, new IOConnectionInfo() { Path = filename }, password, keyfile, new ActionOnFinish((success, message) =>
|
LoadDb task = new LoadDb(app, new IOConnectionInfo() { Path = filename }, password, keyfile, new ActionOnFinish((success, message) =>
|
||||||
@ -81,6 +81,12 @@ namespace Kp2aUnitTests
|
|||||||
return app;
|
return app;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected virtual TestKp2aApp CreateTestKp2aApp()
|
||||||
|
{
|
||||||
|
TestKp2aApp app = new TestKp2aApp();
|
||||||
|
return app;
|
||||||
|
}
|
||||||
|
|
||||||
protected void SaveDatabase(IKp2aApp app)
|
protected void SaveDatabase(IKp2aApp app)
|
||||||
{
|
{
|
||||||
bool saveSuccesful = TrySaveDatabase(app);
|
bool saveSuccesful = TrySaveDatabase(app);
|
||||||
@ -104,16 +110,16 @@ namespace Kp2aUnitTests
|
|||||||
return saveSuccesful;
|
return saveSuccesful;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected IKp2aApp SetupAppWithDefaultDatabase()
|
protected TestKp2aApp SetupAppWithDefaultDatabase()
|
||||||
{
|
{
|
||||||
string filename = DefaultFilename;
|
string filename = DefaultFilename;
|
||||||
|
|
||||||
return SetupAppWithDatabase(filename);
|
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};
|
IOConnectionInfo ioc = new IOConnectionInfo {Path = filename};
|
||||||
Database db = app.CreateNewDatabase();
|
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.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
@ -20,118 +19,6 @@ namespace Kp2aUnitTests
|
|||||||
private string _defaultCacheFileContents = "default contents";
|
private string _defaultCacheFileContents = "default contents";
|
||||||
private TestCacheSupervisor _testCacheSupervisor;
|
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>
|
/// <summary>
|
||||||
/// Tests correct behavior in case that either remote or cache are not available
|
/// Tests correct behavior in case that either remote or cache are not available
|
||||||
/// </summary>
|
/// </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;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using Android.App;
|
||||||
using Android.Content;
|
using Android.Content;
|
||||||
using Android.OS;
|
using Android.OS;
|
||||||
using KeePassLib.Serialization;
|
using KeePassLib.Serialization;
|
||||||
@ -22,6 +23,7 @@ namespace Kp2aUnitTests
|
|||||||
private YesNoCancelResult _yesNoCancelResult = YesNoCancelResult.Yes;
|
private YesNoCancelResult _yesNoCancelResult = YesNoCancelResult.Yes;
|
||||||
private Dictionary<PreferenceKey, bool> _preferences = new Dictionary<PreferenceKey, bool>();
|
private Dictionary<PreferenceKey, bool> _preferences = new Dictionary<PreferenceKey, bool>();
|
||||||
|
|
||||||
|
|
||||||
public void SetShutdown()
|
public void SetShutdown()
|
||||||
{
|
{
|
||||||
|
|
||||||
@ -40,7 +42,7 @@ namespace Kp2aUnitTests
|
|||||||
public Database CreateNewDatabase()
|
public Database CreateNewDatabase()
|
||||||
{
|
{
|
||||||
TestDrawableFactory testDrawableFactory = new TestDrawableFactory();
|
TestDrawableFactory testDrawableFactory = new TestDrawableFactory();
|
||||||
_db = new Database(testDrawableFactory, new TestKp2aApp());
|
_db = new Database(testDrawableFactory, this);
|
||||||
return _db;
|
return _db;
|
||||||
|
|
||||||
}
|
}
|
||||||
@ -98,6 +100,9 @@ namespace Kp2aUnitTests
|
|||||||
public Handler UiThreadHandler {
|
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)
|
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)
|
public IProgressDialog CreateProgressDialog(Context ctx)
|
||||||
{
|
{
|
||||||
return new ProgressDialogStub();
|
return new ProgressDialogStub();
|
||||||
@ -105,7 +110,20 @@ namespace Kp2aUnitTests
|
|||||||
|
|
||||||
public IFileStorage GetFileStorage(IOConnectionInfo iocInfo)
|
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)
|
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
|
* External database changes are detected and merged when saving\n
|
||||||
* Improved loading performance\n
|
* Improved loading performance\n
|
||||||
* Improved search toolbar with suggestions\n
|
* Improved search toolbar with suggestions\n
|
||||||
* New App logo!
|
* New App logo!\n
|
||||||
* Added support for .kdbp format for faster loading/saving\n
|
* Added support for .kdbp format for faster loading/saving\n
|
||||||
* Improved editing of extra strings and hidden display when protected\n
|
* Improved editing of extra strings and hidden display when protected\n
|
||||||
Thanks to Alex Vallat for his code contributions!\n
|
Thanks to Alex Vallat for his code contributions!\n
|
||||||
|
@ -125,33 +125,39 @@ namespace keepass2android
|
|||||||
{
|
{
|
||||||
activity.SetResult(KeePass.ExitReloadDb);
|
activity.SetResult(KeePass.ExitReloadDb);
|
||||||
activity.Finish();
|
activity.Finish();
|
||||||
|
//todo: return?
|
||||||
}
|
}
|
||||||
AlertDialog.Builder builder = new AlertDialog.Builder(activity);
|
AskForReload(activity);
|
||||||
builder.SetTitle(activity.GetString(Resource.String.AskReloadFile_title));
|
|
||||||
|
|
||||||
builder.SetMessage(activity.GetString(Resource.String.AskReloadFile));
|
|
||||||
|
|
||||||
builder.SetPositiveButton(activity.GetString(Android.Resource.String.Yes),
|
|
||||||
(dlgSender, dlgEvt) =>
|
|
||||||
{
|
|
||||||
_db.ReloadRequested = true;
|
|
||||||
activity.SetResult(KeePass.ExitReloadDb);
|
|
||||||
activity.Finish();
|
|
||||||
|
|
||||||
});
|
|
||||||
|
|
||||||
builder.SetNegativeButton(activity.GetString(Android.Resource.String.No), (dlgSender, dlgEvt) =>
|
|
||||||
{
|
|
||||||
|
|
||||||
});
|
|
||||||
|
|
||||||
|
|
||||||
Dialog dialog = builder.Create();
|
|
||||||
dialog.Show();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void StoreOpenedFileAsRecent(IOConnectionInfo ioc, string keyfile)
|
private void AskForReload(Activity activity)
|
||||||
|
{
|
||||||
|
AlertDialog.Builder builder = new AlertDialog.Builder(activity);
|
||||||
|
builder.SetTitle(activity.GetString(Resource.String.AskReloadFile_title));
|
||||||
|
|
||||||
|
builder.SetMessage(activity.GetString(Resource.String.AskReloadFile));
|
||||||
|
|
||||||
|
builder.SetPositiveButton(activity.GetString(Android.Resource.String.Yes),
|
||||||
|
(dlgSender, dlgEvt) =>
|
||||||
|
{
|
||||||
|
_db.ReloadRequested = true;
|
||||||
|
activity.SetResult(KeePass.ExitReloadDb);
|
||||||
|
activity.Finish();
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
builder.SetNegativeButton(activity.GetString(Android.Resource.String.No), (dlgSender, dlgEvt) =>
|
||||||
|
{
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
Dialog dialog = builder.Create();
|
||||||
|
dialog.Show();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void StoreOpenedFileAsRecent(IOConnectionInfo ioc, string keyfile)
|
||||||
{
|
{
|
||||||
FileDbHelper.CreateFile(ioc, keyfile);
|
FileDbHelper.CreateFile(ioc, keyfile);
|
||||||
}
|
}
|
||||||
@ -250,6 +256,11 @@ namespace keepass2android
|
|||||||
return new BuiltInFileStorage();
|
return new BuiltInFileStorage();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void TriggerReload(Context ctx)
|
||||||
|
{
|
||||||
|
AskForReload((Activity)ctx);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
internal void OnTerminate()
|
internal void OnTerminate()
|
||||||
{
|
{
|
||||||
|
@ -644,6 +644,7 @@
|
|||||||
<Import Project="$(MSBuildExtensionsPath)\Novell\Novell.MonoDroid.CSharp.targets" />
|
<Import Project="$(MSBuildExtensionsPath)\Novell\Novell.MonoDroid.CSharp.targets" />
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<Folder Include="Assets\" />
|
<Folder Include="Assets\" />
|
||||||
|
<Folder Include="SupportLib\" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<ProjectReference Include="..\KeePassLib2Android\KeePassLib2Android.csproj">
|
<ProjectReference Include="..\KeePassLib2Android\KeePassLib2Android.csproj">
|
||||||
@ -672,9 +673,6 @@
|
|||||||
</Properties>
|
</Properties>
|
||||||
</MonoDevelop>
|
</MonoDevelop>
|
||||||
</ProjectExtensions>
|
</ProjectExtensions>
|
||||||
<ItemGroup>
|
|
||||||
<AndroidJavaLibrary Include="SupportLib\android-support-v4.jar" />
|
|
||||||
</ItemGroup>
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<AndroidNativeLibrary Include="..\java\kp2akeytransform\libs\armeabi\libfinal-key.so">
|
<AndroidNativeLibrary Include="..\java\kp2akeytransform\libs\armeabi\libfinal-key.so">
|
||||||
<Link>libs\armeabi\libfinal-key.so</Link>
|
<Link>libs\armeabi\libfinal-key.so</Link>
|
||||||
|
Loading…
Reference in New Issue
Block a user