2013-02-23 11:43:42 -05:00
|
|
|
/*
|
|
|
|
This file is part of Keepass2Android, Copyright 2013 Philipp Crocoll. This file is based on Keepassdroid, Copyright Brian Pellin.
|
|
|
|
|
|
|
|
Keepass2Android is free software: you can redistribute it and/or modify
|
|
|
|
it under the terms of the GNU General Public License as published by
|
|
|
|
the Free Software Foundation, either version 2 of the License, or
|
|
|
|
(at your option) any later version.
|
|
|
|
|
|
|
|
Keepass2Android is distributed in the hope that it will be useful,
|
|
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
|
|
GNU General Public License for more details.
|
|
|
|
|
|
|
|
You should have received a copy of the GNU General Public License
|
|
|
|
along with Keepass2Android. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
*/
|
2013-07-09 03:59:17 -04:00
|
|
|
|
2013-02-23 11:43:42 -05:00
|
|
|
using System;
|
2013-07-09 03:59:17 -04:00
|
|
|
using System.IO;
|
|
|
|
using System.Security.Cryptography;
|
2013-02-23 11:43:42 -05:00
|
|
|
using Android.Content;
|
2013-07-09 03:59:17 -04:00
|
|
|
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;
|
2013-02-23 11:43:42 -05:00
|
|
|
|
|
|
|
namespace keepass2android
|
|
|
|
{
|
|
|
|
|
2013-06-15 06:40:01 -04:00
|
|
|
public class SaveDb : RunnableOnFinish {
|
2013-07-09 03:59:17 -04:00
|
|
|
private readonly IKp2aApp _app;
|
2013-06-15 06:40:01 -04:00
|
|
|
private readonly bool _dontSave;
|
|
|
|
private readonly Context _ctx;
|
2013-07-09 03:59:17 -04:00
|
|
|
private Thread _workerThread;
|
|
|
|
|
|
|
|
public SaveDb(Context ctx, IKp2aApp app, OnFinish finish, bool dontSave): base(finish) {
|
2013-06-15 06:40:01 -04:00
|
|
|
_ctx = ctx;
|
2013-07-09 03:59:17 -04:00
|
|
|
_app = app;
|
2013-06-15 06:40:01 -04:00
|
|
|
_dontSave = dontSave;
|
2013-02-23 11:43:42 -05:00
|
|
|
}
|
|
|
|
|
2013-07-09 03:59:17 -04:00
|
|
|
public SaveDb(Context ctx, IKp2aApp app, OnFinish finish)
|
|
|
|
: base(finish)
|
|
|
|
{
|
2013-06-15 06:40:01 -04:00
|
|
|
_ctx = ctx;
|
2013-07-09 03:59:17 -04:00
|
|
|
_app = app;
|
2013-06-15 06:40:01 -04:00
|
|
|
_dontSave = false;
|
2013-02-23 11:43:42 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2013-06-15 06:40:01 -04:00
|
|
|
public override void Run ()
|
2013-02-23 11:43:42 -05:00
|
|
|
{
|
|
|
|
|
2013-06-15 06:40:01 -04:00
|
|
|
if (! _dontSave) {
|
2013-07-09 03:59:17 -04:00
|
|
|
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);
|
|
|
|
}
|
|
|
|
|
2013-02-23 11:43:42 -05:00
|
|
|
} catch (Exception e) {
|
|
|
|
/* TODO KPDesktop:
|
|
|
|
* catch(Exception exSave)
|
|
|
|
{
|
|
|
|
MessageService.ShowSaveWarning(pd.IOConnectionInfo, exSave, true);
|
|
|
|
bSuccess = false;
|
|
|
|
}
|
|
|
|
*/
|
2013-07-09 03:59:17 -04:00
|
|
|
Finish (false, e.ToString());
|
2013-02-23 11:43:42 -05:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2013-07-09 03:59:17 -04:00
|
|
|
|
2013-02-23 11:43:42 -05:00
|
|
|
}
|
2013-07-09 03:59:17 -04:00
|
|
|
|
|
|
|
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);
|
|
|
|
}
|
|
|
|
|
2013-02-23 11:43:42 -05:00
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|