mirror of
https://github.com/moparisthebest/keepass2android
synced 2024-11-29 04:22:22 -05:00
Fixed SaveDb for CachingFileStorage and target file not existing
-> + Tests First, very simple implementation of ChangingFileStorage in KP2A App (preliminary)
This commit is contained in:
parent
289e10e1c4
commit
a671c4f241
@ -20,7 +20,7 @@ namespace keepass2android.Io
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="ioc">The file which we tried to write</param>
|
/// <param name="ioc">The file which we tried to write</param>
|
||||||
/// <param name="e">The exception why the remote file couldn't be updated</param>
|
/// <param name="e">The exception why the remote file couldn't be updated</param>
|
||||||
void CouldntSaveToRemote(IOConnectionInfo ioc, Exception e);
|
void CouldntSaveToRemote(IOConnectionInfo ioc, Exception ex);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Called when only the local file could be opened during an open operation.
|
/// Called when only the local file could be opened during an open operation.
|
||||||
|
@ -99,12 +99,10 @@ namespace keepass2android
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
if (
|
if (
|
||||||
(_streamForOrigFile != null)
|
(_streamForOrigFile != null)
|
||||||
|| fileStorage.CheckForFileChangeFast(ioc, _app.GetDb().LastFileVersion) //first try to use the fast change detection
|
|| 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:
|
|| (FileHashChanged(ioc, _app.GetDb().KpDatabase.HashOfFileOnDisk) == FileHashChange.Changed) //if that fails, hash the file and compare:
|
||||||
)
|
)
|
||||||
{
|
{
|
||||||
|
|
||||||
@ -243,14 +241,26 @@ namespace keepass2android
|
|||||||
_app.GetDb().LastFileVersion = fileStorage.GetCurrentFileVersionFast(ioc);
|
_app.GetDb().LastFileVersion = fileStorage.GetCurrentFileVersionFast(ioc);
|
||||||
}
|
}
|
||||||
|
|
||||||
public byte[] HashFile(IOConnectionInfo iocFile)
|
public byte[] HashOriginalFile(IOConnectionInfo iocFile)
|
||||||
{
|
{
|
||||||
if (iocFile == null) { Debug.Assert(false); return null; } // Assert only
|
if (iocFile == null) { Debug.Assert(false); return null; } // Assert only
|
||||||
|
|
||||||
Stream sIn;
|
Stream sIn;
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
sIn = _app.GetFileStorage(iocFile).OpenFileForRead(iocFile);
|
IFileStorage fileStorage = _app.GetFileStorage(iocFile);
|
||||||
|
CachingFileStorage cachingFileStorage = fileStorage as CachingFileStorage;
|
||||||
|
if (cachingFileStorage != null)
|
||||||
|
{
|
||||||
|
string hash;
|
||||||
|
cachingFileStorage.GetRemoteDataAndHash(iocFile, out hash);
|
||||||
|
return MemUtil.HexStringToByteArray(hash);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
sIn = fileStorage.OpenFileForRead(iocFile);
|
||||||
|
}
|
||||||
|
|
||||||
if (sIn == null) throw new FileNotFoundException();
|
if (sIn == null) throw new FileNotFoundException();
|
||||||
}
|
}
|
||||||
catch (Exception) { return null; }
|
catch (Exception) { return null; }
|
||||||
@ -267,10 +277,20 @@ namespace keepass2android
|
|||||||
return pbHash;
|
return pbHash;
|
||||||
}
|
}
|
||||||
|
|
||||||
private bool FileHashChanged(IOConnectionInfo ioc, byte[] hashOfFileOnDisk)
|
enum FileHashChange
|
||||||
|
{
|
||||||
|
Equal,
|
||||||
|
Changed,
|
||||||
|
FileNotAvailable
|
||||||
|
}
|
||||||
|
|
||||||
|
private FileHashChange FileHashChanged(IOConnectionInfo ioc, byte[] hashOfFileOnDisk)
|
||||||
{
|
{
|
||||||
StatusLogger.UpdateSubMessage(_app.GetResourceString(UiStringKey.CheckingTargetFileForChanges));
|
StatusLogger.UpdateSubMessage(_app.GetResourceString(UiStringKey.CheckingTargetFileForChanges));
|
||||||
return !MemUtil.ArraysEqual(HashFile(ioc), hashOfFileOnDisk);
|
byte[] fileHash = HashOriginalFile(ioc);
|
||||||
|
if (fileHash == null)
|
||||||
|
return FileHashChange.FileNotAvailable;
|
||||||
|
return MemUtil.ArraysEqual(fileHash, hashOfFileOnDisk) ? FileHashChange.Equal : FileHashChange.Changed;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -71,6 +71,7 @@
|
|||||||
<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="TestSaveDbCached.cs" />
|
||||||
<Compile Include="TestSynchronizeCachedDatabase.cs" />
|
<Compile Include="TestSynchronizeCachedDatabase.cs" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
@ -19,8 +19,10 @@ 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(TestSynchronizeCachedDatabase) });
|
//runner.AddTests(new List<Type> { typeof(TestSaveDbCached) });
|
||||||
//runner.AddTests(new List<Type> { typeof(TestLoadDb) });}}
|
//runner.AddTests(typeof(TestSaveDbCached).GetMethod("TestLoadEditSaveWhenModified"));
|
||||||
|
|
||||||
|
//runner.AddTests(new List<Type> { typeof(TestSaveDb) });
|
||||||
//runner.AddTests(new List<Type> { typeof(TestCachingFileStorage) });
|
//runner.AddTests(new List<Type> { typeof(TestCachingFileStorage) });
|
||||||
//runner.AddTests(typeof(TestCachingFileStorage).GetMethod("TestSaveToRemote"));
|
//runner.AddTests(typeof(TestCachingFileStorage).GetMethod("TestSaveToRemote"));
|
||||||
//runner.AddTests(typeof(TestLoadDb).GetMethod("TestLoadKdbpWithPasswordOnly"));
|
//runner.AddTests(typeof(TestLoadDb).GetMethod("TestLoadKdbpWithPasswordOnly"));
|
||||||
|
@ -191,8 +191,6 @@ namespace Kp2aUnitTests
|
|||||||
|
|
||||||
Assert.IsTrue(_testCacheSupervisor.CouldntOpenFromRemoteCalled);
|
Assert.IsTrue(_testCacheSupervisor.CouldntOpenFromRemoteCalled);
|
||||||
Assert.IsFalse(_testCacheSupervisor.CouldntSaveToRemoteCalled);
|
Assert.IsFalse(_testCacheSupervisor.CouldntSaveToRemoteCalled);
|
||||||
Assert.IsFalse(_testCacheSupervisor.RestoredRemoteCalled);
|
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -126,6 +126,35 @@ namespace Kp2aUnitTests
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public void TestLoadEditSaveWithWriteBecauseTargetNotExists()
|
||||||
|
{
|
||||||
|
//create the default database:
|
||||||
|
IKp2aApp app = SetupAppWithDefaultDatabase();
|
||||||
|
//save it and reload it so we have a base version
|
||||||
|
SaveDatabase(app);
|
||||||
|
app = LoadDatabase(DefaultFilename, DefaultPassword, DefaultKeyfile);
|
||||||
|
|
||||||
|
//modify the database by adding a group:
|
||||||
|
app.GetDb().KpDatabase.RootGroup.AddGroup(new PwGroup(true, true, "TestGroup", PwIcon.Apple), true);
|
||||||
|
|
||||||
|
//delete the file:
|
||||||
|
File.Delete(DefaultFilename);
|
||||||
|
|
||||||
|
//save the database:
|
||||||
|
SaveDatabase(app);
|
||||||
|
|
||||||
|
//make sure no question was asked
|
||||||
|
Assert.AreEqual(null, ((TestKp2aApp)app).LastYesNoCancelQuestionTitle);
|
||||||
|
|
||||||
|
//load database to a new app instance:
|
||||||
|
IKp2aApp resultApp = LoadDatabase(DefaultFilename, DefaultPassword, DefaultKeyfile);
|
||||||
|
|
||||||
|
//ensure the file was saved:
|
||||||
|
AssertDatabasesAreEqual(app.GetDb().KpDatabase, resultApp.GetDb().KpDatabase);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
[TestMethod]
|
[TestMethod]
|
||||||
public void TestLoadEditSaveWithSyncOverwriteBecauseOfNoCheck()
|
public void TestLoadEditSaveWithSyncOverwriteBecauseOfNoCheck()
|
||||||
{
|
{
|
||||||
|
148
src/Kp2aUnitTests/TestSaveDbCached.cs
Normal file
148
src/Kp2aUnitTests/TestSaveDbCached.cs
Normal file
@ -0,0 +1,148 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.IO;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
|
||||||
|
using Android.App;
|
||||||
|
using Android.Content;
|
||||||
|
using Android.OS;
|
||||||
|
using Android.Runtime;
|
||||||
|
using Android.Views;
|
||||||
|
using Android.Widget;
|
||||||
|
using KeePassLib;
|
||||||
|
using KeePassLib.Serialization;
|
||||||
|
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||||
|
using keepass2android;
|
||||||
|
using keepass2android.Io;
|
||||||
|
|
||||||
|
namespace Kp2aUnitTests
|
||||||
|
{
|
||||||
|
[TestClass]
|
||||||
|
class TestSaveDbCached: TestBase
|
||||||
|
{
|
||||||
|
private TestCacheSupervisor _testCacheSupervisor = new TestCacheSupervisor();
|
||||||
|
private TestFileStorage _testFileStorage = new TestFileStorage();
|
||||||
|
|
||||||
|
protected override TestKp2aApp CreateTestKp2aApp()
|
||||||
|
{
|
||||||
|
TestKp2aApp app = base.CreateTestKp2aApp();
|
||||||
|
app.FileStorage = new CachingFileStorage(_testFileStorage, "/mnt/sdcard/kp2atest/cache/", _testCacheSupervisor);
|
||||||
|
return app;
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public void TestLoadEditSave()
|
||||||
|
{
|
||||||
|
//create the default database:
|
||||||
|
IKp2aApp app = SetupAppWithDefaultDatabase();
|
||||||
|
IOConnection.DeleteFile(new IOConnectionInfo { Path = DefaultFilename });
|
||||||
|
//save it and reload it so we have a base version
|
||||||
|
SaveDatabase(app);
|
||||||
|
app = LoadDatabase(DefaultFilename, DefaultPassword, DefaultKeyfile);
|
||||||
|
//modify the database by adding a group:
|
||||||
|
app.GetDb().KpDatabase.RootGroup.AddGroup(new PwGroup(true, true, "TestGroup", PwIcon.Apple), true);
|
||||||
|
//save the database again:
|
||||||
|
SaveDatabase(app);
|
||||||
|
Assert.IsNull(((TestKp2aApp)app).LastYesNoCancelQuestionTitle);
|
||||||
|
Assert.IsFalse(_testCacheSupervisor.CouldntOpenFromRemoteCalled);
|
||||||
|
Assert.IsFalse(_testCacheSupervisor.CouldntSaveToRemoteCalled);
|
||||||
|
|
||||||
|
//load database to a new app instance:
|
||||||
|
IKp2aApp resultApp = LoadDatabase(DefaultFilename, DefaultPassword, DefaultKeyfile);
|
||||||
|
|
||||||
|
//ensure the change was saved:
|
||||||
|
AssertDatabasesAreEqual(app.GetDb().KpDatabase, resultApp.GetDb().KpDatabase);
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public void TestLoadEditSaveWhenDeleted()
|
||||||
|
{
|
||||||
|
//create the default database:
|
||||||
|
IKp2aApp app = SetupAppWithDefaultDatabase();
|
||||||
|
IOConnection.DeleteFile(new IOConnectionInfo { Path = DefaultFilename });
|
||||||
|
//save it and reload it so we have a base version
|
||||||
|
SaveDatabase(app);
|
||||||
|
app = LoadDatabase(DefaultFilename, DefaultPassword, DefaultKeyfile);
|
||||||
|
|
||||||
|
//delete the file:
|
||||||
|
File.Delete(DefaultFilename);
|
||||||
|
|
||||||
|
//modify the database by adding a group:
|
||||||
|
app.GetDb().KpDatabase.RootGroup.AddGroup(new PwGroup(true, true, "TestGroup", PwIcon.Apple), true);
|
||||||
|
//save the database again:
|
||||||
|
SaveDatabase(app);
|
||||||
|
Assert.IsNull(((TestKp2aApp) app).LastYesNoCancelQuestionTitle);
|
||||||
|
Assert.IsFalse(_testCacheSupervisor.CouldntOpenFromRemoteCalled);
|
||||||
|
Assert.IsFalse(_testCacheSupervisor.CouldntSaveToRemoteCalled);
|
||||||
|
|
||||||
|
//load database to a new app instance:
|
||||||
|
IKp2aApp resultApp = LoadDatabase(DefaultFilename, DefaultPassword, DefaultKeyfile);
|
||||||
|
|
||||||
|
//ensure the change was saved:
|
||||||
|
AssertDatabasesAreEqual(app.GetDb().KpDatabase, resultApp.GetDb().KpDatabase);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public void TestLoadEditSaveWhenModified()
|
||||||
|
{
|
||||||
|
//create the default database:
|
||||||
|
IKp2aApp app = SetupAppWithDefaultDatabase();
|
||||||
|
IOConnection.DeleteFile(new IOConnectionInfo { Path = DefaultFilename });
|
||||||
|
//save it and reload it so we have a base version
|
||||||
|
SaveDatabase(app);
|
||||||
|
app = LoadDatabase(DefaultFilename, DefaultPassword, DefaultKeyfile);
|
||||||
|
|
||||||
|
foreach (var group in app.GetDb().KpDatabase.RootGroup.Groups)
|
||||||
|
Kp2aLog.Log("app c: " + group.Name);
|
||||||
|
|
||||||
|
//load once more:
|
||||||
|
var app2 = LoadDatabase(DefaultFilename, DefaultPassword, DefaultKeyfile);
|
||||||
|
|
||||||
|
//modifiy once:
|
||||||
|
PwGroup group2 = new PwGroup(true, true, "TestGroup2", PwIcon.Apple);
|
||||||
|
app2.GetDb().KpDatabase.RootGroup.AddGroup(group2, true);
|
||||||
|
|
||||||
|
foreach (var group in app.GetDb().KpDatabase.RootGroup.Groups)
|
||||||
|
Kp2aLog.Log("app b: " + group.Name);
|
||||||
|
|
||||||
|
SaveDatabase(app2);
|
||||||
|
|
||||||
|
foreach (var group in app.GetDb().KpDatabase.RootGroup.Groups)
|
||||||
|
Kp2aLog.Log("app d: " + group.Name);
|
||||||
|
Assert.IsNull(((TestKp2aApp)app).LastYesNoCancelQuestionTitle);
|
||||||
|
Assert.IsFalse(_testCacheSupervisor.CouldntOpenFromRemoteCalled);
|
||||||
|
Assert.IsFalse(_testCacheSupervisor.CouldntSaveToRemoteCalled);
|
||||||
|
|
||||||
|
//modify the database by adding a group:
|
||||||
|
PwGroup group1 = new PwGroup(true, true, "TestGroup", PwIcon.Apple);
|
||||||
|
app.GetDb().KpDatabase.RootGroup.AddGroup(group1, true);
|
||||||
|
|
||||||
|
foreach (var group in app.GetDb().KpDatabase.RootGroup.Groups)
|
||||||
|
Kp2aLog.Log("app a: " + group.Name);
|
||||||
|
|
||||||
|
|
||||||
|
//save the database again:
|
||||||
|
SaveDatabase(app);
|
||||||
|
Assert.AreEqual(((TestKp2aApp)app).LastYesNoCancelQuestionTitle, UiStringKey.TitleSyncQuestion);
|
||||||
|
Assert.IsFalse(_testCacheSupervisor.CouldntOpenFromRemoteCalled);
|
||||||
|
Assert.IsFalse(_testCacheSupervisor.CouldntSaveToRemoteCalled);
|
||||||
|
|
||||||
|
|
||||||
|
//load database to a new app instance:
|
||||||
|
IKp2aApp resultApp = LoadDatabase(DefaultFilename, DefaultPassword, DefaultKeyfile);
|
||||||
|
|
||||||
|
app2.GetDb().KpDatabase.RootGroup.AddGroup(group1, true);
|
||||||
|
foreach (var group in app.GetDb().KpDatabase.RootGroup.Groups)
|
||||||
|
Kp2aLog.Log("app: "+group.Name);
|
||||||
|
|
||||||
|
foreach (var group in resultApp.GetDb().KpDatabase.RootGroup.Groups)
|
||||||
|
Kp2aLog.Log("resultApp: " + group.Name);
|
||||||
|
|
||||||
|
//ensure the change was saved:
|
||||||
|
AssertDatabasesAreEqual(app2.GetDb().KpDatabase, resultApp.GetDb().KpDatabase);
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -20,6 +20,7 @@ using Android.App;
|
|||||||
using Android.Content;
|
using Android.Content;
|
||||||
using Android.OS;
|
using Android.OS;
|
||||||
using Android.Runtime;
|
using Android.Runtime;
|
||||||
|
using Android.Widget;
|
||||||
using KeePassLib.Serialization;
|
using KeePassLib.Serialization;
|
||||||
using Android.Preferences;
|
using Android.Preferences;
|
||||||
using keepass2android.Io;
|
using keepass2android.Io;
|
||||||
@ -57,7 +58,7 @@ namespace keepass2android
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Main implementation of the IKp2aApp interface for usage in the real app.
|
/// Main implementation of the IKp2aApp interface for usage in the real app.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class Kp2aApp: IKp2aApp
|
public class Kp2aApp: IKp2aApp, ICacheSupervisor
|
||||||
{
|
{
|
||||||
public bool IsShutdown()
|
public bool IsShutdown()
|
||||||
{
|
{
|
||||||
@ -253,7 +254,13 @@ namespace keepass2android
|
|||||||
|
|
||||||
public IFileStorage GetFileStorage(IOConnectionInfo iocInfo)
|
public IFileStorage GetFileStorage(IOConnectionInfo iocInfo)
|
||||||
{
|
{
|
||||||
|
if (iocInfo.IsLocalFile())
|
||||||
return new BuiltInFileStorage();
|
return new BuiltInFileStorage();
|
||||||
|
else
|
||||||
|
{
|
||||||
|
//todo: check if desired
|
||||||
|
return new CachingFileStorage(new BuiltInFileStorage(), Application.Context.CacheDir.Path, this);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void TriggerReload(Context ctx)
|
public void TriggerReload(Context ctx)
|
||||||
@ -294,6 +301,31 @@ namespace keepass2android
|
|||||||
_db = new Database(new DrawableFactory(), this);
|
_db = new Database(new DrawableFactory(), this);
|
||||||
return _db;
|
return _db;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void ShowToast(string message)
|
||||||
|
{
|
||||||
|
var handler = new Handler(Looper.MainLooper);
|
||||||
|
handler.Post(() => { Toast.MakeText(Application.Context, message, ToastLength.Long).Show(); });
|
||||||
|
}
|
||||||
|
|
||||||
|
public void CouldntSaveToRemote(IOConnectionInfo ioc, Exception e)
|
||||||
|
{
|
||||||
|
//TODO use resource strings
|
||||||
|
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)
|
||||||
|
{
|
||||||
|
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.");
|
||||||
|
}
|
||||||
|
|
||||||
|
public void NotifyOpenFromLocalDueToConflict(IOConnectionInfo ioc)
|
||||||
|
{
|
||||||
|
ShowToast("Opened local file due to conflict with changes in remote file. Use Synchronize menu to merge.");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user