* Introduced IFileStorage interface: Better abstraction than current IOConnection (suitable for cloud support). Currently only implemented by the built-in IOConnection (local/http/ftp)
* Implemented Merge functionality for SaveDB. UI is not yet implemented! * Added tests for merge functionality
This commit is contained in:
@@ -14,26 +14,40 @@ This file is part of Keepass2Android, Copyright 2013 Philipp Crocoll. This file
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with Keepass2Android. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Security.Cryptography;
|
||||
using Android.Content;
|
||||
using Android.OS;
|
||||
using Java.Lang;
|
||||
using KeePassLib;
|
||||
using KeePassLib.Serialization;
|
||||
using KeePassLib.Utility;
|
||||
using keepass2android.Io;
|
||||
using Debug = System.Diagnostics.Debug;
|
||||
using Exception = System.Exception;
|
||||
|
||||
namespace keepass2android
|
||||
{
|
||||
|
||||
public class SaveDb : RunnableOnFinish {
|
||||
private readonly Database _db;
|
||||
private readonly IKp2aApp _app;
|
||||
private readonly bool _dontSave;
|
||||
private readonly Context _ctx;
|
||||
|
||||
public SaveDb(Context ctx, Database db, OnFinish finish, bool dontSave): base(finish) {
|
||||
private Thread _workerThread;
|
||||
|
||||
public SaveDb(Context ctx, IKp2aApp app, OnFinish finish, bool dontSave): base(finish) {
|
||||
_ctx = ctx;
|
||||
_db = db;
|
||||
_app = app;
|
||||
_dontSave = dontSave;
|
||||
}
|
||||
|
||||
public SaveDb(Context ctx, Database db, OnFinish finish):base(finish) {
|
||||
public SaveDb(Context ctx, IKp2aApp app, OnFinish finish)
|
||||
: base(finish)
|
||||
{
|
||||
_ctx = ctx;
|
||||
_db = db;
|
||||
_app = app;
|
||||
_dontSave = false;
|
||||
}
|
||||
|
||||
@@ -42,10 +56,67 @@ namespace keepass2android
|
||||
{
|
||||
|
||||
if (! _dontSave) {
|
||||
try {
|
||||
_db.SaveData (_ctx);
|
||||
if (_db.Ioc.IsLocalFile())
|
||||
_db.LastChangeDate = System.IO.File.GetLastWriteTimeUtc(_db.Ioc.Path);
|
||||
try
|
||||
{
|
||||
StatusLogger.UpdateMessage(UiStringKey.saving_database);
|
||||
IOConnectionInfo ioc = _app.GetDb().Ioc;
|
||||
IFileStorage fileStorage = _app.GetFileStorage(ioc);
|
||||
|
||||
if ((!_app.GetBooleanPreference(PreferenceKey.CheckForFileChangesOnSave))
|
||||
|| (_app.GetDb().KpDatabase.HashOfFileOnDisk == null)) //first time saving
|
||||
{
|
||||
PerformSaveWithoutCheck(fileStorage, ioc);
|
||||
Finish(true);
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
|
||||
if (fileStorage.CheckForFileChangeFast(ioc, _app.GetDb().LastFileVersion) //first try to use the fast change detection
|
||||
|| (FileHashChanged(ioc, _app.GetDb().KpDatabase.HashOfFileOnDisk))) //if that fails, hash the file and compare:
|
||||
{
|
||||
//ask user...
|
||||
_app.AskYesNoCancel(UiStringKey.TitleSyncQuestion, UiStringKey.MessageSyncQuestions,
|
||||
//yes = sync
|
||||
(sender, args) =>
|
||||
{
|
||||
Action runHandler = () =>
|
||||
{
|
||||
//note: when synced, the file might be downloaded once again from the server. Caching the data
|
||||
//in the hashing function would solve this but increases complexity. I currently assume the files are
|
||||
//small.
|
||||
StatusLogger.UpdateSubMessage(_app.GetResourceString(UiStringKey.SynchronizingDatabase));
|
||||
MergeIn(fileStorage, ioc);
|
||||
PerformSaveWithoutCheck(fileStorage, ioc);
|
||||
Finish(true);
|
||||
};
|
||||
RunInWorkerThread(runHandler);
|
||||
},
|
||||
//no = overwrite
|
||||
(sender, args) =>
|
||||
{
|
||||
RunInWorkerThread( () =>
|
||||
{
|
||||
PerformSaveWithoutCheck(fileStorage, ioc);
|
||||
Finish(true);
|
||||
});
|
||||
},
|
||||
//cancel
|
||||
(sender, args) =>
|
||||
{
|
||||
RunInWorkerThread(() => Finish(false));
|
||||
},
|
||||
_ctx
|
||||
);
|
||||
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
PerformSaveWithoutCheck(fileStorage, ioc);
|
||||
Finish(true);
|
||||
}
|
||||
|
||||
} catch (Exception e) {
|
||||
/* TODO KPDesktop:
|
||||
* catch(Exception exSave)
|
||||
@@ -54,13 +125,85 @@ namespace keepass2android
|
||||
bSuccess = false;
|
||||
}
|
||||
*/
|
||||
Finish (false, e.Message);
|
||||
Finish (false, e.ToString());
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
Finish(true);
|
||||
|
||||
}
|
||||
|
||||
private void RunInWorkerThread(Action runHandler)
|
||||
{
|
||||
try
|
||||
{
|
||||
_workerThread = new Thread(runHandler);
|
||||
_workerThread.Run();
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Kp2aLog.Log("Error in worker thread of SaveDb: "+e);
|
||||
Finish(false, e.Message);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public void JoinWorkerThread()
|
||||
{
|
||||
if (_workerThread != null)
|
||||
_workerThread.Join();
|
||||
}
|
||||
|
||||
private void MergeIn(IFileStorage fileStorage, IOConnectionInfo ioc)
|
||||
{
|
||||
PwDatabase pwImp = new PwDatabase();
|
||||
PwDatabase pwDatabase = _app.GetDb().KpDatabase;
|
||||
pwImp.New(new IOConnectionInfo(), pwDatabase.MasterKey);
|
||||
pwImp.MemoryProtection = pwDatabase.MemoryProtection.CloneDeep();
|
||||
pwImp.MasterKey = pwDatabase.MasterKey;
|
||||
KdbxFile kdbx = new KdbxFile(pwImp);
|
||||
kdbx.Load(fileStorage.OpenFileForRead(ioc), KdbxFormat.Default, null);
|
||||
|
||||
pwDatabase.MergeIn(pwImp, PwMergeMethod.Synchronize, null);
|
||||
|
||||
}
|
||||
|
||||
private void PerformSaveWithoutCheck(IFileStorage fileStorage, IOConnectionInfo ioc)
|
||||
{
|
||||
_app.GetDb().SaveData(_ctx);
|
||||
_app.GetDb().LastFileVersion = fileStorage.GetCurrentFileVersionFast(ioc);
|
||||
}
|
||||
|
||||
public byte[] HashFile(IOConnectionInfo iocFile)
|
||||
{
|
||||
if (iocFile == null) { Debug.Assert(false); return null; } // Assert only
|
||||
|
||||
Stream sIn;
|
||||
try
|
||||
{
|
||||
sIn = _app.GetFileStorage(iocFile).OpenFileForRead(iocFile);
|
||||
if (sIn == null) throw new FileNotFoundException();
|
||||
}
|
||||
catch (Exception) { return null; }
|
||||
|
||||
byte[] pbHash;
|
||||
try
|
||||
{
|
||||
SHA256Managed sha256 = new SHA256Managed();
|
||||
pbHash = sha256.ComputeHash(sIn);
|
||||
}
|
||||
catch (Exception) { Debug.Assert(false); sIn.Close(); return null; }
|
||||
|
||||
sIn.Close();
|
||||
return pbHash;
|
||||
}
|
||||
|
||||
private bool FileHashChanged(IOConnectionInfo ioc, byte[] hashOfFileOnDisk)
|
||||
{
|
||||
StatusLogger.UpdateSubMessage(_app.GetResourceString(UiStringKey.CheckingTargetFileForChanges));
|
||||
return !MemUtil.ArraysEqual(HashFile(ioc), hashOfFileOnDisk);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user