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