* 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:
@@ -2,6 +2,7 @@ using System;
|
||||
using Android.Content;
|
||||
using Android.OS;
|
||||
using KeePassLib.Serialization;
|
||||
using keepass2android.Io;
|
||||
|
||||
namespace keepass2android
|
||||
{
|
||||
@@ -57,5 +58,7 @@ namespace keepass2android
|
||||
Handler UiThreadHandler { get; }
|
||||
|
||||
IProgressDialog CreateProgressDialog(Context ctx);
|
||||
IFileStorage GetFileStorage(IOConnectionInfo iocInfo);
|
||||
|
||||
}
|
||||
}
|
||||
@@ -3,7 +3,7 @@ namespace keepass2android
|
||||
public interface IProgressDialog
|
||||
{
|
||||
void SetTitle(string title);
|
||||
void SetMessage(string getResourceString);
|
||||
void SetMessage(string resourceString);
|
||||
void Dismiss();
|
||||
void Show();
|
||||
}
|
||||
|
||||
104
src/Kp2aBusinessLogic/Io/BuiltInFileStorage.cs
Normal file
104
src/Kp2aBusinessLogic/Io/BuiltInFileStorage.cs
Normal file
@@ -0,0 +1,104 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
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.Serialization;
|
||||
using KeePassLib.Utility;
|
||||
|
||||
namespace keepass2android.Io
|
||||
{
|
||||
public class BuiltInFileStorage: IFileStorage
|
||||
{
|
||||
public void DeleteFile(IOConnectionInfo ioc)
|
||||
{
|
||||
IOConnection.DeleteFile(ioc);
|
||||
}
|
||||
|
||||
public bool CheckForFileChangeFast(IOConnectionInfo ioc, string previousFileVersion)
|
||||
{
|
||||
if (!ioc.IsLocalFile())
|
||||
return false;
|
||||
DateTime previousDate;
|
||||
if (!DateTime.TryParse(previousFileVersion, out previousDate))
|
||||
return false;
|
||||
return File.GetLastWriteTimeUtc(ioc.Path) > previousDate;
|
||||
}
|
||||
|
||||
|
||||
public string GetCurrentFileVersionFast(IOConnectionInfo ioc)
|
||||
{
|
||||
|
||||
if (ioc.IsLocalFile())
|
||||
{
|
||||
return File.GetLastWriteTimeUtc(ioc.Path).ToString(CultureInfo.InvariantCulture);
|
||||
}
|
||||
else
|
||||
{
|
||||
return DateTime.MinValue.ToString(CultureInfo.InvariantCulture);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
public Stream OpenFileForRead(IOConnectionInfo ioc)
|
||||
{
|
||||
return IOConnection.OpenRead(ioc);
|
||||
}
|
||||
|
||||
public IWriteTransaction OpenWriteTransaction(IOConnectionInfo ioc, bool useFileTransaction)
|
||||
{
|
||||
return new BuiltInFileTransaction(ioc, useFileTransaction);
|
||||
}
|
||||
|
||||
public class BuiltInFileTransaction : IWriteTransaction
|
||||
{
|
||||
private readonly FileTransactionEx _transaction;
|
||||
|
||||
public BuiltInFileTransaction(IOConnectionInfo ioc, bool useFileTransaction)
|
||||
{
|
||||
_transaction = new FileTransactionEx(ioc, useFileTransaction);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public Stream OpenFile()
|
||||
{
|
||||
return _transaction.OpenWrite();
|
||||
}
|
||||
|
||||
public void CommitWrite()
|
||||
{
|
||||
_transaction.CommitWrite();
|
||||
}
|
||||
}
|
||||
|
||||
public bool CompleteIoId()
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public bool? FileExists()
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public string GetFilenameWithoutPathAndExt(IOConnectionInfo ioc)
|
||||
{
|
||||
return UrlUtil.StripExtension(
|
||||
UrlUtil.GetFileName(ioc.Path));
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
82
src/Kp2aBusinessLogic/Io/IFileStorage.cs
Normal file
82
src/Kp2aBusinessLogic/Io/IFileStorage.cs
Normal file
@@ -0,0 +1,82 @@
|
||||
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.Keys;
|
||||
using KeePassLib.Serialization;
|
||||
|
||||
namespace keepass2android.Io
|
||||
{
|
||||
/// <summary>
|
||||
/// Called as a callback from CheckForFileChangeAsync.
|
||||
/// </summary>
|
||||
/// <param name="ioc"></param>
|
||||
/// <param name="fileChanged"></param>
|
||||
public delegate void OnCheckForFileChangeCompleted(IOConnectionInfo ioc, bool fileChanged);
|
||||
|
||||
/// <summary>
|
||||
/// Interface to encapsulate all access to disk or cloud.
|
||||
/// </summary>
|
||||
/// This interface might be implemented for different cloud storage providers in the future to extend the possibilities of the
|
||||
/// "built-in" IOConnection class in the Keepass-Lib.
|
||||
/// Note that it was decided to use the IOConnectionInfo also for cloud storage (unless it turns out that this isn't possible, but
|
||||
/// with prefixes like dropbox:// it should be). The advantage is that the database for saving recent files etc. will then work without
|
||||
/// much work to do. Furthermore, the IOConnectionInfo seems generic info to capture all required data, even though it might be nicer to
|
||||
/// have an IIoStorageId interface in few cases.*/
|
||||
public interface IFileStorage
|
||||
{
|
||||
/// <summary>
|
||||
/// Deletes the given file.
|
||||
/// </summary>
|
||||
void DeleteFile(IOConnectionInfo ioc);
|
||||
|
||||
/// <summary>
|
||||
/// Tests whether the file was changed.
|
||||
/// </summary>
|
||||
/// Note: This function may return false even if the file might have changed. The function
|
||||
/// should focus on being fast and cheap instead of doing things like hashing or downloading a full file.
|
||||
/// <returns>Returns true if a change was detected, false otherwise.</returns>
|
||||
bool CheckForFileChangeFast(IOConnectionInfo ioc , string previousFileVersion);
|
||||
|
||||
/// <summary>
|
||||
/// Returns a string describing the "version" of the file specified by ioc.
|
||||
/// </summary>
|
||||
/// This string may have a deliberate value (except null) and should not be used by callers except for passing it to
|
||||
/// CheckForFileChangeFast().
|
||||
/// <returns>A string which should not be null.</returns>
|
||||
string GetCurrentFileVersionFast(IOConnectionInfo ioc);
|
||||
|
||||
Stream OpenFileForRead(IOConnectionInfo ioc);
|
||||
//Stream OpenFileForWrite( IOConnectionInfo ioc, bool useTransaction);
|
||||
IWriteTransaction OpenWriteTransaction(IOConnectionInfo ioc, bool useFileTransaction);
|
||||
|
||||
/// <summary>
|
||||
/// brings up a dialog to query credentials or something like this.
|
||||
/// </summary>
|
||||
/// <returns>true if success, false if error or cancelled by user</returns>
|
||||
bool CompleteIoId( /*in/out ioId*/);
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Checks whether the given file exists.
|
||||
/// </summary>
|
||||
/// <returns>true if it exists, false if not. Null if the check couldn't be performed (e.g. because no credentials available or no connection established.)</returns>
|
||||
bool? FileExists( /*ioId*/);
|
||||
|
||||
string GetFilenameWithoutPathAndExt(IOConnectionInfo ioc);
|
||||
}
|
||||
|
||||
public interface IWriteTransaction: IDisposable
|
||||
{
|
||||
Stream OpenFile();
|
||||
void CommitWrite();
|
||||
}
|
||||
}
|
||||
@@ -41,6 +41,8 @@
|
||||
<Reference Include="System.Xml" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Compile Include="Io\BuiltInFileStorage.cs" />
|
||||
<Compile Include="Io\IFileStorage.cs" />
|
||||
<Compile Include="IProgressDialog.cs" />
|
||||
<Compile Include="PreferenceKey.cs" />
|
||||
<Compile Include="UiStringKey.cs" />
|
||||
@@ -68,7 +70,7 @@
|
||||
<Compile Include="Resources\Resource.Designer.cs" />
|
||||
<Compile Include="Properties\AssemblyInfo.cs" />
|
||||
<Compile Include="SearchDbHelper.cs" />
|
||||
<Compile Include="UpdateStatus.cs" />
|
||||
<Compile Include="ProgressDialogStatusLogger.cs" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\KeePassLib2Android\KeePassLib2Android.csproj">
|
||||
|
||||
@@ -6,6 +6,7 @@ namespace keepass2android
|
||||
public enum PreferenceKey
|
||||
{
|
||||
remember_keyfile,
|
||||
UseFileTransactions
|
||||
UseFileTransactions,
|
||||
CheckForFileChangesOnSave
|
||||
}
|
||||
}
|
||||
@@ -25,34 +25,54 @@ namespace keepass2android
|
||||
/// <summary>
|
||||
/// StatusLogger implementation which shows the progress in a progress dialog
|
||||
/// </summary>
|
||||
public class UpdateStatus: IStatusLogger {
|
||||
public class ProgressDialogStatusLogger: IStatusLogger {
|
||||
private readonly IProgressDialog _progressDialog;
|
||||
readonly IKp2aApp _app;
|
||||
private readonly Handler _handler;
|
||||
|
||||
public UpdateStatus() {
|
||||
private string _message = "";
|
||||
|
||||
public ProgressDialogStatusLogger() {
|
||||
|
||||
}
|
||||
|
||||
public UpdateStatus(IKp2aApp app, Handler handler, IProgressDialog pd) {
|
||||
public ProgressDialogStatusLogger(IKp2aApp app, Handler handler, IProgressDialog pd) {
|
||||
_app = app;
|
||||
_progressDialog = pd;
|
||||
_handler = handler;
|
||||
}
|
||||
|
||||
public void UpdateMessage(UiStringKey stringKey) {
|
||||
if ( _app != null && _progressDialog != null && _handler != null ) {
|
||||
_handler.Post( () => {_progressDialog.SetMessage(_app.GetResourceString(stringKey));});
|
||||
}
|
||||
if (_app != null)
|
||||
UpdateMessage(_app.GetResourceString(stringKey));
|
||||
}
|
||||
|
||||
public void UpdateMessage (String message)
|
||||
{
|
||||
_message = message;
|
||||
if ( _app!= null && _progressDialog != null && _handler != null ) {
|
||||
_handler.Post(() => {_progressDialog.SetMessage(message); } );
|
||||
}
|
||||
}
|
||||
|
||||
public void UpdateSubMessage(String submessage)
|
||||
{
|
||||
if (_app != null && _progressDialog != null && _handler != null)
|
||||
{
|
||||
_handler.Post(() =>
|
||||
{
|
||||
if (String.IsNullOrEmpty(submessage))
|
||||
{
|
||||
_progressDialog.SetMessage(_message + " (" + submessage + ")");
|
||||
}
|
||||
else
|
||||
{
|
||||
_progressDialog.SetMessage(_message);
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#region IStatusLogger implementation
|
||||
|
||||
public void StartLogging (string strOperation, bool bWriteOperationToLog)
|
||||
@@ -72,10 +92,32 @@ namespace keepass2android
|
||||
|
||||
public bool SetText (string strNewText, LogStatusType lsType)
|
||||
{
|
||||
UpdateMessage(strNewText);
|
||||
if (strNewText.StartsWith("KP2AKEY_"))
|
||||
{
|
||||
UiStringKey key;
|
||||
if (Enum.TryParse(strNewText.Substring("KP2AKEY_".Length), true, out key))
|
||||
{
|
||||
UpdateMessage(_app.GetResourceString(key), lsType);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
UpdateMessage(strNewText, lsType);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private void UpdateMessage(string message, LogStatusType lsType)
|
||||
{
|
||||
if (lsType == LogStatusType.AdditionalInfo)
|
||||
{
|
||||
UpdateSubMessage(message);
|
||||
}
|
||||
else
|
||||
{
|
||||
UpdateMessage(message);
|
||||
}
|
||||
}
|
||||
|
||||
public bool ContinueWork ()
|
||||
{
|
||||
return true;
|
||||
@@ -32,7 +32,7 @@ namespace keepass2android
|
||||
private readonly IKp2aApp _app;
|
||||
private Thread _thread;
|
||||
|
||||
public ProgressTask(IKp2aApp app, Context ctx, RunnableOnFinish task, UiStringKey messageKey) {
|
||||
public ProgressTask(IKp2aApp app, Context ctx, RunnableOnFinish task) {
|
||||
_task = task;
|
||||
_handler = app.UiThreadHandler;
|
||||
_app = app;
|
||||
@@ -40,11 +40,12 @@ namespace keepass2android
|
||||
// Show process dialog
|
||||
_progressDialog = app.CreateProgressDialog(ctx);
|
||||
_progressDialog.SetTitle(_app.GetResourceString(UiStringKey.progress_title));
|
||||
_progressDialog.SetMessage(_app.GetResourceString(messageKey));
|
||||
_progressDialog.SetMessage("Initializing...");
|
||||
|
||||
// Set code to run when this is finished
|
||||
_task.SetStatus(new UpdateStatus(_app, _handler, _progressDialog));
|
||||
_task.OnFinishToRun = new AfterTask(task.OnFinishToRun, _handler, _progressDialog);
|
||||
_task.SetStatusLogger(new ProgressDialogStatusLogger(_app, _handler, _progressDialog));
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -17,6 +17,19 @@ namespace keepass2android
|
||||
keyfile_does_not_exist,
|
||||
RecycleBin,
|
||||
progress_create,
|
||||
loading_database
|
||||
loading_database,
|
||||
AddingEntry,
|
||||
AddingGroup,
|
||||
DeletingEntry,
|
||||
DeletingGroup,
|
||||
SettingPassword,
|
||||
UndoingChanges,
|
||||
TransformingKey,
|
||||
DecodingDatabase,
|
||||
ParsingDatabase,
|
||||
CheckingTargetFileForChanges,
|
||||
TitleSyncQuestion,
|
||||
MessageSyncQuestions,
|
||||
SynchronizingDatabase
|
||||
}
|
||||
}
|
||||
@@ -17,10 +17,12 @@ This file is part of Keepass2Android, Copyright 2013 Philipp Crocoll. This file
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using Android.Content;
|
||||
using KeePassLib;
|
||||
using KeePassLib.Keys;
|
||||
using KeePassLib.Serialization;
|
||||
using keepass2android.Io;
|
||||
|
||||
namespace keepass2android
|
||||
{
|
||||
@@ -34,7 +36,7 @@ namespace keepass2android
|
||||
public PwGroup Root;
|
||||
public PwDatabase KpDatabase;
|
||||
public IOConnectionInfo Ioc { get { return KpDatabase.IOConnectionInfo; } }
|
||||
public DateTime LastChangeDate;
|
||||
public string LastFileVersion;
|
||||
public SearchDbHelper SearchHelper;
|
||||
|
||||
public IDrawableFactory DrawableFactory;
|
||||
@@ -83,15 +85,16 @@ namespace keepass2android
|
||||
|
||||
public bool DidOpenFileChange()
|
||||
{
|
||||
if ((Loaded == false) || (Ioc.IsLocalFile() == false))
|
||||
if (Loaded == false)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
return System.IO.File.GetLastWriteTimeUtc(Ioc.Path) > LastChangeDate;
|
||||
return _app.GetFileStorage(Ioc).CheckForFileChangeFast(Ioc, LastFileVersion);
|
||||
|
||||
}
|
||||
|
||||
|
||||
public void LoadData(IKp2aApp app, IOConnectionInfo iocInfo, String password, String keyfile, UpdateStatus status)
|
||||
public void LoadData(IKp2aApp app, IOConnectionInfo iocInfo, String password, String keyfile, ProgressDialogStatusLogger status)
|
||||
{
|
||||
PwDatabase pwDatabase = new PwDatabase();
|
||||
|
||||
@@ -103,15 +106,17 @@ namespace keepass2android
|
||||
try
|
||||
{
|
||||
compositeKey.AddUserKey(new KcpKeyFile(keyfile));
|
||||
} catch (Exception)
|
||||
} catch (Exception e)
|
||||
{
|
||||
Kp2aLog.Log(e.ToString());
|
||||
throw new KeyFileException();
|
||||
}
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
pwDatabase.Open(iocInfo, compositeKey, status);
|
||||
IFileStorage fileStorage = _app.GetFileStorage(iocInfo);
|
||||
pwDatabase.Open(fileStorage.OpenFileForRead(iocInfo), fileStorage.GetFilenameWithoutPathAndExt(iocInfo), iocInfo, compositeKey, status);
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
@@ -125,14 +130,9 @@ namespace keepass2android
|
||||
else throw;
|
||||
}
|
||||
|
||||
status.UpdateSubMessage("");
|
||||
|
||||
if (iocInfo.IsLocalFile())
|
||||
{
|
||||
LastChangeDate = System.IO.File.GetLastWriteTimeUtc(iocInfo.Path);
|
||||
} else
|
||||
{
|
||||
LastChangeDate = DateTime.MinValue;
|
||||
}
|
||||
LastFileVersion = _app.GetFileStorage(iocInfo).GetCurrentFileVersionFast(iocInfo);
|
||||
|
||||
Root = pwDatabase.RootGroup;
|
||||
PopulateGlobals(Root);
|
||||
@@ -184,9 +184,14 @@ namespace keepass2android
|
||||
public void SaveData(Context ctx) {
|
||||
|
||||
KpDatabase.UseFileTransactions = _app.GetBooleanPreference(PreferenceKey.UseFileTransactions);
|
||||
KpDatabase.Save(null);
|
||||
|
||||
using (IWriteTransaction trans = _app.GetFileStorage(Ioc).OpenWriteTransaction(Ioc, KpDatabase.UseFileTransactions))
|
||||
{
|
||||
KpDatabase.Save(trans.OpenFile(), null);
|
||||
trans.CommitWrite();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
private void PopulateGlobals (PwGroup currentGroup)
|
||||
{
|
||||
|
||||
@@ -21,31 +21,38 @@ using KeePassLib;
|
||||
namespace keepass2android
|
||||
{
|
||||
public class AddEntry : RunnableOnFinish {
|
||||
protected Database Db;
|
||||
protected Database Db
|
||||
{
|
||||
get { return _app.GetDb(); }
|
||||
}
|
||||
|
||||
private readonly IKp2aApp _app;
|
||||
private readonly PwEntry _entry;
|
||||
private readonly PwGroup _parentGroup;
|
||||
private readonly Context _ctx;
|
||||
|
||||
public static AddEntry GetInstance(Context ctx, Database db, PwEntry entry, PwGroup parentGroup, OnFinish finish) {
|
||||
public static AddEntry GetInstance(Context ctx, IKp2aApp app, PwEntry entry, PwGroup parentGroup, OnFinish finish) {
|
||||
|
||||
return new AddEntry(ctx, db, entry, parentGroup, finish);
|
||||
return new AddEntry(ctx, app, entry, parentGroup, finish);
|
||||
}
|
||||
|
||||
protected AddEntry(Context ctx, Database db, PwEntry entry, PwGroup parentGroup, OnFinish finish):base(finish) {
|
||||
protected AddEntry(Context ctx, IKp2aApp app, PwEntry entry, PwGroup parentGroup, OnFinish finish):base(finish) {
|
||||
_ctx = ctx;
|
||||
_parentGroup = parentGroup;
|
||||
Db = db;
|
||||
_app = app;
|
||||
_entry = entry;
|
||||
|
||||
OnFinishToRun = new AfterAdd(db, entry, OnFinishToRun);
|
||||
_onFinishToRun = new AfterAdd(app.GetDb(), entry, OnFinishToRun);
|
||||
}
|
||||
|
||||
|
||||
public override void Run() {
|
||||
public override void Run() {
|
||||
StatusLogger.UpdateMessage(UiStringKey.AddingEntry);
|
||||
_parentGroup.AddEntry(_entry, true);
|
||||
|
||||
// Commit to disk
|
||||
SaveDb save = new SaveDb(_ctx, Db, OnFinishToRun);
|
||||
SaveDb save = new SaveDb(_ctx, _app, OnFinishToRun);
|
||||
save.SetStatusLogger(StatusLogger);
|
||||
save.Run();
|
||||
}
|
||||
|
||||
@@ -72,7 +79,9 @@ namespace keepass2android
|
||||
// Add entry to global
|
||||
_db.Entries[_entry.Uuid] = _entry;
|
||||
|
||||
} else {
|
||||
} else
|
||||
{
|
||||
StatusLogger.UpdateMessage(UiStringKey.UndoingChanges);
|
||||
//TODO test fail
|
||||
_entry.ParentGroup.Entries.Remove(_entry);
|
||||
}
|
||||
|
||||
@@ -23,7 +23,11 @@ namespace keepass2android
|
||||
{
|
||||
|
||||
public class AddGroup : RunnableOnFinish {
|
||||
internal Database Db;
|
||||
internal Database Db
|
||||
{
|
||||
get { return _app.GetDb(); }
|
||||
}
|
||||
private IKp2aApp _app;
|
||||
private readonly String _name;
|
||||
private readonly int _iconId;
|
||||
internal PwGroup Group;
|
||||
@@ -32,31 +36,32 @@ namespace keepass2android
|
||||
readonly Context _ctx;
|
||||
|
||||
|
||||
public static AddGroup GetInstance(Context ctx, Database db, String name, int iconid, PwGroup parent, OnFinish finish, bool dontSave) {
|
||||
return new AddGroup(ctx, db, name, iconid, parent, finish, dontSave);
|
||||
public static AddGroup GetInstance(Context ctx, IKp2aApp app, String name, int iconid, PwGroup parent, OnFinish finish, bool dontSave) {
|
||||
return new AddGroup(ctx, app, name, iconid, parent, finish, dontSave);
|
||||
}
|
||||
|
||||
|
||||
private AddGroup(Context ctx, Database db, String name, int iconid, PwGroup parent, OnFinish finish, bool dontSave): base(finish) {
|
||||
private AddGroup(Context ctx, IKp2aApp app, String name, int iconid, PwGroup parent, OnFinish finish, bool dontSave): base(finish) {
|
||||
_ctx = ctx;
|
||||
Db = db;
|
||||
_name = name;
|
||||
_iconId = iconid;
|
||||
Parent = parent;
|
||||
DontSave = dontSave;
|
||||
|
||||
OnFinishToRun = new AfterAdd(this, OnFinishToRun);
|
||||
_app = app;
|
||||
|
||||
_onFinishToRun = new AfterAdd(this, OnFinishToRun);
|
||||
}
|
||||
|
||||
|
||||
public override void Run() {
|
||||
|
||||
StatusLogger.UpdateMessage(UiStringKey.AddingGroup);
|
||||
// Generate new group
|
||||
Group = new PwGroup(true, true, _name, (PwIcon)_iconId);
|
||||
Parent.AddGroup(Group, true);
|
||||
|
||||
// Commit to disk
|
||||
SaveDb save = new SaveDb(_ctx, Db, OnFinishToRun, DontSave);
|
||||
SaveDb save = new SaveDb(_ctx, _app, OnFinishToRun, DontSave);
|
||||
save.SetStatusLogger(StatusLogger);
|
||||
save.Run();
|
||||
}
|
||||
|
||||
@@ -77,6 +82,7 @@ namespace keepass2android
|
||||
// Add group to global list
|
||||
_addGroup.Db.Groups[_addGroup.Group.Uuid] = _addGroup.Group;
|
||||
} else {
|
||||
StatusLogger.UpdateMessage(UiStringKey.UndoingChanges);
|
||||
_addGroup.Parent.Groups.Remove(_addGroup.Group);
|
||||
}
|
||||
|
||||
|
||||
@@ -40,6 +40,7 @@ namespace keepass2android
|
||||
|
||||
|
||||
public override void Run() {
|
||||
StatusLogger.UpdateMessage(UiStringKey.progress_create);
|
||||
Database db = _app.CreateNewDatabase();
|
||||
|
||||
db.KpDatabase = new KeePassLib.PwDatabase();
|
||||
@@ -58,14 +59,15 @@ namespace keepass2android
|
||||
db.SearchHelper = new SearchDbHelper(_app);
|
||||
|
||||
// Add a couple default groups
|
||||
AddGroup internet = AddGroup.GetInstance(_ctx, db, "Internet", 1, db.KpDatabase.RootGroup, null, true);
|
||||
AddGroup internet = AddGroup.GetInstance(_ctx, _app, "Internet", 1, db.KpDatabase.RootGroup, null, true);
|
||||
internet.Run();
|
||||
AddGroup email = AddGroup.GetInstance(_ctx, db, "eMail", 19, db.KpDatabase.RootGroup, null, true);
|
||||
AddGroup email = AddGroup.GetInstance(_ctx, _app, "eMail", 19, db.KpDatabase.RootGroup, null, true);
|
||||
email.Run();
|
||||
|
||||
// Commit changes
|
||||
SaveDb save = new SaveDb(_ctx, db, OnFinishToRun, _dontSave);
|
||||
OnFinishToRun = null;
|
||||
SaveDb save = new SaveDb(_ctx, _app, OnFinishToRun, _dontSave);
|
||||
save.SetStatusLogger(StatusLogger);
|
||||
_onFinishToRun = null;
|
||||
save.Run();
|
||||
|
||||
|
||||
|
||||
@@ -48,8 +48,9 @@ namespace keepass2android
|
||||
}
|
||||
}
|
||||
|
||||
public override void Run() {
|
||||
|
||||
public override void Run()
|
||||
{
|
||||
StatusLogger.UpdateMessage(UiStringKey.DeletingEntry);
|
||||
PwDatabase pd = Db.KpDatabase;
|
||||
|
||||
PwGroup pgRecycleBin = pd.RootGroup.FindGroup(pd.RecycleBinUuid, true);
|
||||
@@ -68,7 +69,7 @@ namespace keepass2android
|
||||
PwDeletedObject pdo = new PwDeletedObject(pe.Uuid, dtNow);
|
||||
pd.DeletedObjects.Add(pdo);
|
||||
|
||||
OnFinishToRun = new ActionOnFinish((success, message) =>
|
||||
_onFinishToRun = new ActionOnFinish((success, message) =>
|
||||
{
|
||||
if (success)
|
||||
{
|
||||
@@ -89,7 +90,7 @@ namespace keepass2android
|
||||
pgRecycleBin.AddEntry(pe, true, true);
|
||||
pe.Touch(false);
|
||||
|
||||
OnFinishToRun = new ActionOnFinish( (success, message) =>
|
||||
_onFinishToRun = new ActionOnFinish( (success, message) =>
|
||||
{
|
||||
if ( success ) {
|
||||
// Mark previous parent dirty
|
||||
@@ -106,7 +107,8 @@ namespace keepass2android
|
||||
}
|
||||
|
||||
// Commit database
|
||||
SaveDb save = new SaveDb(Ctx, Db, OnFinishToRun, false);
|
||||
SaveDb save = new SaveDb(Ctx, App, OnFinishToRun, false);
|
||||
save.SetStatusLogger(StatusLogger);
|
||||
save.Run();
|
||||
|
||||
|
||||
|
||||
@@ -70,6 +70,7 @@ namespace keepass2android
|
||||
|
||||
|
||||
public override void Run() {
|
||||
StatusLogger.UpdateMessage(UiStringKey.DeletingGroup);
|
||||
//from KP Desktop
|
||||
PwGroup pg = _group;
|
||||
PwGroup pgParent = pg.ParentGroup;
|
||||
@@ -86,7 +87,7 @@ namespace keepass2android
|
||||
|
||||
PwDeletedObject pdo = new PwDeletedObject(pg.Uuid, DateTime.Now);
|
||||
pd.DeletedObjects.Add(pdo);
|
||||
OnFinishToRun = new AfterDeletePermanently(OnFinishToRun, App, _group);
|
||||
_onFinishToRun = new AfterDeletePermanently(OnFinishToRun, App, _group);
|
||||
}
|
||||
else // Recycle
|
||||
{
|
||||
@@ -95,7 +96,7 @@ namespace keepass2android
|
||||
|
||||
pgRecycleBin.AddGroup(pg, true, true);
|
||||
pg.Touch(false);
|
||||
OnFinishToRun = new ActionOnFinish((success, message) =>
|
||||
_onFinishToRun = new ActionOnFinish((success, message) =>
|
||||
{
|
||||
if ( success ) {
|
||||
// Mark new parent (Recycle bin) dirty
|
||||
@@ -113,7 +114,8 @@ namespace keepass2android
|
||||
}
|
||||
|
||||
// Save
|
||||
SaveDb save = new SaveDb(Ctx, Db, OnFinishToRun, DontSave);
|
||||
SaveDb save = new SaveDb(Ctx, App, OnFinishToRun, DontSave);
|
||||
save.SetStatusLogger(StatusLogger);
|
||||
save.Run();
|
||||
|
||||
}
|
||||
|
||||
@@ -109,12 +109,12 @@ namespace keepass2android
|
||||
(dlgSender, dlgEvt) =>
|
||||
{
|
||||
DeletePermanently = true;
|
||||
ProgressTask pt = new ProgressTask(App, Ctx, this, UiStringKey.saving_database);
|
||||
ProgressTask pt = new ProgressTask(App, Ctx, this);
|
||||
pt.Run();
|
||||
},
|
||||
(dlgSender, dlgEvt) => {
|
||||
DeletePermanently = false;
|
||||
ProgressTask pt = new ProgressTask(App, Ctx, this, UiStringKey.saving_database);
|
||||
ProgressTask pt = new ProgressTask(App, Ctx, this);
|
||||
pt.Run();
|
||||
},
|
||||
(dlgSender, dlgEvt) => {},
|
||||
@@ -124,7 +124,7 @@ namespace keepass2android
|
||||
|
||||
} else
|
||||
{
|
||||
ProgressTask pt = new ProgressTask(App, Ctx, this, UiStringKey.saving_database);
|
||||
ProgressTask pt = new ProgressTask(App, Ctx, this);
|
||||
pt.Run();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -43,7 +43,8 @@ namespace keepass2android
|
||||
{
|
||||
try
|
||||
{
|
||||
_app.GetDb().LoadData (_app, _ioc, _pass, _key, Status);
|
||||
StatusLogger.UpdateMessage(UiStringKey.loading_database);
|
||||
_app.GetDb().LoadData (_app, _ioc, _pass, _key, StatusLogger);
|
||||
SaveFileData (_ioc, _key);
|
||||
|
||||
} catch (KeyFileException) {
|
||||
|
||||
@@ -29,6 +29,13 @@ namespace keepass2android
|
||||
|
||||
protected OnFinish BaseOnFinish;
|
||||
protected Handler Handler;
|
||||
private ProgressDialogStatusLogger _statusLogger = new ProgressDialogStatusLogger(); //default: no logging but not null -> can be used whenever desired
|
||||
|
||||
public ProgressDialogStatusLogger StatusLogger
|
||||
{
|
||||
get { return _statusLogger; }
|
||||
set { _statusLogger = value; }
|
||||
}
|
||||
|
||||
protected OnFinish() {
|
||||
}
|
||||
@@ -47,7 +54,7 @@ namespace keepass2android
|
||||
BaseOnFinish = finish;
|
||||
Handler = null;
|
||||
}
|
||||
|
||||
|
||||
public void SetResult(bool success, String message) {
|
||||
Success = success;
|
||||
Message = message;
|
||||
|
||||
@@ -21,13 +21,19 @@ namespace keepass2android
|
||||
|
||||
public abstract class RunnableOnFinish {
|
||||
|
||||
public OnFinish OnFinishToRun;
|
||||
public UpdateStatus Status;
|
||||
protected OnFinish _onFinishToRun;
|
||||
public ProgressDialogStatusLogger StatusLogger = new ProgressDialogStatusLogger(); //default: empty but not null
|
||||
|
||||
protected RunnableOnFinish(OnFinish finish) {
|
||||
OnFinishToRun = finish;
|
||||
_onFinishToRun = finish;
|
||||
}
|
||||
|
||||
|
||||
public OnFinish OnFinishToRun
|
||||
{
|
||||
get { return _onFinishToRun; }
|
||||
set { _onFinishToRun = value; }
|
||||
}
|
||||
|
||||
protected void Finish(bool result, String message) {
|
||||
if ( OnFinishToRun != null ) {
|
||||
OnFinishToRun.SetResult(result, message);
|
||||
@@ -42,8 +48,12 @@ namespace keepass2android
|
||||
}
|
||||
}
|
||||
|
||||
public void SetStatus(UpdateStatus status) {
|
||||
Status = status;
|
||||
public void SetStatusLogger(ProgressDialogStatusLogger status) {
|
||||
if (OnFinishToRun != null)
|
||||
{
|
||||
OnFinishToRun.StatusLogger = status;
|
||||
}
|
||||
StatusLogger = status;
|
||||
}
|
||||
|
||||
abstract public void Run();
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -25,21 +25,23 @@ namespace keepass2android
|
||||
|
||||
private readonly String _password;
|
||||
private readonly String _keyfile;
|
||||
private readonly Database _db;
|
||||
private readonly IKp2aApp _app;
|
||||
private readonly bool _dontSave;
|
||||
private readonly Context _ctx;
|
||||
|
||||
public SetPassword(Context ctx, Database db, String password, String keyfile, OnFinish finish): base(finish) {
|
||||
public SetPassword(Context ctx, IKp2aApp app, String password, String keyfile, OnFinish finish): base(finish) {
|
||||
_ctx = ctx;
|
||||
_db = db;
|
||||
_app = app;
|
||||
_password = password;
|
||||
_keyfile = keyfile;
|
||||
_dontSave = false;
|
||||
}
|
||||
|
||||
public SetPassword(Context ctx, Database db, String password, String keyfile, OnFinish finish, bool dontSave): base(finish) {
|
||||
|
||||
public SetPassword(Context ctx, IKp2aApp app, String password, String keyfile, OnFinish finish, bool dontSave)
|
||||
: base(finish)
|
||||
{
|
||||
_ctx = ctx;
|
||||
_db = db;
|
||||
_app = app;
|
||||
_password = password;
|
||||
_keyfile = keyfile;
|
||||
_dontSave = dontSave;
|
||||
@@ -48,7 +50,8 @@ namespace keepass2android
|
||||
|
||||
public override void Run ()
|
||||
{
|
||||
PwDatabase pm = _db.KpDatabase;
|
||||
StatusLogger.UpdateMessage(UiStringKey.SettingPassword);
|
||||
PwDatabase pm = _app.GetDb().KpDatabase;
|
||||
CompositeKey newKey = new CompositeKey ();
|
||||
if (String.IsNullOrEmpty (_password) == false) {
|
||||
newKey.AddUserKey (new KcpPassword (_password));
|
||||
@@ -69,8 +72,9 @@ namespace keepass2android
|
||||
pm.MasterKey = newKey;
|
||||
|
||||
// Save Database
|
||||
OnFinishToRun = new AfterSave(previousKey, previousMasterKeyChanged, pm, OnFinishToRun);
|
||||
SaveDb save = new SaveDb(_ctx, _db, OnFinishToRun, _dontSave);
|
||||
_onFinishToRun = new AfterSave(previousKey, previousMasterKeyChanged, pm, OnFinishToRun);
|
||||
SaveDb save = new SaveDb(_ctx, _app, OnFinishToRun, _dontSave);
|
||||
save.SetStatusLogger(StatusLogger);
|
||||
save.Run();
|
||||
}
|
||||
|
||||
|
||||
@@ -22,32 +22,33 @@ namespace keepass2android
|
||||
{
|
||||
|
||||
public class UpdateEntry : RunnableOnFinish {
|
||||
private readonly Database _db;
|
||||
private readonly IKp2aApp _app;
|
||||
private readonly Context _ctx;
|
||||
|
||||
public UpdateEntry(Context ctx, Database db, PwEntry oldE, PwEntry newE, OnFinish finish):base(finish) {
|
||||
public UpdateEntry(Context ctx, IKp2aApp app, PwEntry oldE, PwEntry newE, OnFinish finish):base(finish) {
|
||||
_ctx = ctx;
|
||||
_db = db;
|
||||
_app = app;
|
||||
|
||||
OnFinishToRun = new AfterUpdate(oldE, newE, db, finish);
|
||||
_onFinishToRun = new AfterUpdate(oldE, newE, app, finish);
|
||||
}
|
||||
|
||||
|
||||
public override void Run() {
|
||||
// Commit to disk
|
||||
SaveDb save = new SaveDb(_ctx, _db, OnFinishToRun);
|
||||
SaveDb save = new SaveDb(_ctx, _app, OnFinishToRun);
|
||||
save.SetStatusLogger(StatusLogger);
|
||||
save.Run();
|
||||
}
|
||||
|
||||
private class AfterUpdate : OnFinish {
|
||||
private readonly PwEntry _backup;
|
||||
private readonly PwEntry _updatedEntry;
|
||||
private readonly Database _db;
|
||||
private readonly IKp2aApp _app;
|
||||
|
||||
public AfterUpdate(PwEntry backup, PwEntry updatedEntry, Database db, OnFinish finish):base(finish) {
|
||||
public AfterUpdate(PwEntry backup, PwEntry updatedEntry, IKp2aApp app, OnFinish finish):base(finish) {
|
||||
_backup = backup;
|
||||
_updatedEntry = updatedEntry;
|
||||
_db = db;
|
||||
_app = app;
|
||||
}
|
||||
|
||||
public override void Run() {
|
||||
@@ -65,11 +66,12 @@ namespace keepass2android
|
||||
if ( parent != null ) {
|
||||
|
||||
// Mark parent group dirty
|
||||
_db.Dirty.Add(parent);
|
||||
_app.GetDb().Dirty.Add(parent);
|
||||
|
||||
}
|
||||
}
|
||||
} else {
|
||||
StatusLogger.UpdateMessage(UiStringKey.UndoingChanges);
|
||||
// If we fail to save, back out changes to global structure
|
||||
//TODO test fail
|
||||
_updatedEntry.AssignProperties(_backup, false, true, false);
|
||||
|
||||
Reference in New Issue
Block a user