* 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:
Philipp Crocoll 2013-07-09 09:59:17 +02:00
parent 64e62cae70
commit 84aeb31fd0
42 changed files with 912 additions and 161 deletions

View File

@ -164,7 +164,6 @@ namespace KeePassLib.Native
try try
{ {
//Kp2aLog.Log("4+1"+new Kp2atest.TestClass().Add1(4));
Com.Keepassdroid.Crypto.Finalkey.NativeFinalKey key = new Com.Keepassdroid.Crypto.Finalkey.NativeFinalKey(); Com.Keepassdroid.Crypto.Finalkey.NativeFinalKey key = new Com.Keepassdroid.Crypto.Finalkey.NativeFinalKey();
byte[] newKey = key.TransformMasterKey(pKey256, pBuf256, (int)uRounds); byte[] newKey = key.TransformMasterKey(pKey256, pBuf256, (int)uRounds);

View File

@ -562,22 +562,40 @@ namespace KeePassLib
/// Open a database. The URL may point to any supported data source. /// Open a database. The URL may point to any supported data source.
/// </summary> /// </summary>
/// <param name="ioSource">IO connection to load the database from.</param> /// <param name="ioSource">IO connection to load the database from.</param>
/// <param name="pwKey">Key used to open the specified database.</param> /// s<param name="pwKey">Key used to open the specified database.</param>
/// <param name="slLogger">Logger, which gets all status messages.</param> /// <param name="slLogger">Logger, which gets all status messages.</param>
public void Open(IOConnectionInfo ioSource, CompositeKey pwKey, public void Open(IOConnectionInfo ioSource, CompositeKey pwKey,
IStatusLogger slLogger) IStatusLogger slLogger)
{ {
Debug.Assert(ioSource != null); Open(IOConnection.OpenRead(ioSource), UrlUtil.StripExtension(
if(ioSource == null) throw new ArgumentNullException("ioSource"); UrlUtil.GetFileName(ioSource.Path)), ioSource, pwKey, slLogger );
}
/// <summary>
/// Open a database. The URL may point to any supported data source.
/// </summary>
/// <param name="ioSource">IO connection to load the database from.</param>
/// <param name="pwKey">Key used to open the specified database.</param>
/// <param name="slLogger">Logger, which gets all status messages.</param>
public void Open(Stream s, string fileNameWithoutPathAndExt, IOConnectionInfo ioSource, CompositeKey pwKey,
IStatusLogger slLogger)
{
Debug.Assert(s != null);
if (s == null) throw new ArgumentNullException("s");
Debug.Assert(fileNameWithoutPathAndExt != null);
if (fileNameWithoutPathAndExt == null) throw new ArgumentException("fileNameWithoutPathAndExt");
Debug.Assert(pwKey != null); Debug.Assert(pwKey != null);
if(pwKey == null) throw new ArgumentNullException("pwKey"); Debug.Assert(ioSource != null);
if (ioSource == null) throw new ArgumentNullException("ioSource");
if (pwKey == null) throw new ArgumentNullException("pwKey");
Close(); Close();
try try
{ {
m_pgRootGroup = new PwGroup(true, true, UrlUtil.StripExtension( m_pgRootGroup = new PwGroup(true, true, fileNameWithoutPathAndExt, PwIcon.FolderOpen);
UrlUtil.GetFileName(ioSource.Path)), PwIcon.FolderOpen);
m_pgRootGroup.IsExpanded = true; m_pgRootGroup.IsExpanded = true;
m_pwUserKey = pwKey; m_pwUserKey = pwKey;
@ -587,8 +605,8 @@ namespace KeePassLib
KdbxFile kdbx = new KdbxFile(this); KdbxFile kdbx = new KdbxFile(this);
kdbx.DetachBinaries = m_strDetachBins; kdbx.DetachBinaries = m_strDetachBins;
Stream s = IOConnection.OpenRead(ioSource);
kdbx.Load(s, KdbxFormat.Default, slLogger); kdbx.Load(s, KdbxFormat.Default, slLogger);
s.Close(); s.Close();
m_pbHashOfLastIO = kdbx.HashOfFileOnDisk; m_pbHashOfLastIO = kdbx.HashOfFileOnDisk;
@ -598,13 +616,14 @@ namespace KeePassLib
m_bDatabaseOpened = true; m_bDatabaseOpened = true;
m_ioSource = ioSource; m_ioSource = ioSource;
} }
catch(Exception) catch (Exception)
{ {
Clear(); Clear();
throw; throw;
} }
} }
/// <summary> /// <summary>
/// Save the currently opened database. The file is written to the location /// Save the currently opened database. The file is written to the location
/// it has been opened from. /// it has been opened from.
@ -626,7 +645,7 @@ namespace KeePassLib
kdb.Save(s, null, KdbxFormat.Default, slLogger); kdb.Save(s, null, KdbxFormat.Default, slLogger);
ft.CommitWrite(); ft.CommitWrite();
m_pbHashOfLastIO = kdb.HashOfFileOnDisk; m_pbHashOfLastIO = kdb.HashOfFileOnDisk;
m_pbHashOfFileOnDisk = kdb.HashOfFileOnDisk; m_pbHashOfFileOnDisk = kdb.HashOfFileOnDisk;
Debug.Assert(m_pbHashOfFileOnDisk != null); Debug.Assert(m_pbHashOfFileOnDisk != null);
@ -636,6 +655,23 @@ namespace KeePassLib
m_bModified = false; m_bModified = false;
} }
/// <summary>
/// Save the currently opened database. The file is written to the given stream which is expected to be the original location.
/// </summary>
/// This allows to save to cloud locations etc.
public void Save(Stream streamOfOriginalLocation, IStatusLogger slLogger)
{
Debug.Assert(ValidateUuidUniqueness());
Stream s = streamOfOriginalLocation;
KdbxFile kdb = new KdbxFile(this);
kdb.Save(s, null, KdbxFormat.Default, slLogger);
m_pbHashOfLastIO = kdb.HashOfFileOnDisk;
m_pbHashOfFileOnDisk = kdb.HashOfFileOnDisk;
Debug.Assert(m_pbHashOfFileOnDisk != null);
m_bModified = false;
}
/// <summary> /// <summary>
/// Save the currently opened database to a different location. If /// Save the currently opened database to a different location. If
/// <paramref name="bIsPrimaryNow" /> is <c>true</c>, the specified /// <paramref name="bIsPrimaryNow" /> is <c>true</c>, the specified

View File

@ -91,12 +91,18 @@ namespace KeePassLib.Serialization
if((sDecrypted == null) || (sDecrypted == hashedStream)) if((sDecrypted == null) || (sDecrypted == hashedStream))
throw new SecurityException(KLRes.CryptoStreamFailed); throw new SecurityException(KLRes.CryptoStreamFailed);
if (m_slLogger != null)
m_slLogger.SetText("KP2AKEY_TransformingKey", LogStatusType.AdditionalInfo);
brDecrypted = new BinaryReaderEx(sDecrypted, encNoBom, KLRes.FileCorrupted); brDecrypted = new BinaryReaderEx(sDecrypted, encNoBom, KLRes.FileCorrupted);
byte[] pbStoredStartBytes = brDecrypted.ReadBytes(32); byte[] pbStoredStartBytes = brDecrypted.ReadBytes(32);
if((m_pbStreamStartBytes == null) || (m_pbStreamStartBytes.Length != 32)) if((m_pbStreamStartBytes == null) || (m_pbStreamStartBytes.Length != 32))
throw new InvalidDataException(); throw new InvalidDataException();
if (m_slLogger != null)
m_slLogger.SetText("KP2AKEY_DecodingDatabase", LogStatusType.AdditionalInfo);
for(int iStart = 0; iStart < 32; ++iStart) for(int iStart = 0; iStart < 32; ++iStart)
{ {
if(pbStoredStartBytes[iStart] != m_pbStreamStartBytes[iStart]) if(pbStoredStartBytes[iStart] != m_pbStreamStartBytes[iStart])
@ -126,7 +132,8 @@ namespace KeePassLib.Serialization
m_pbProtectedStreamKey); m_pbProtectedStreamKey);
} }
else m_randomStream = null; // No random stream for plain-text files else m_randomStream = null; // No random stream for plain-text files
if (m_slLogger != null)
m_slLogger.SetText("KP2AKEY_ParsingDatabase", LogStatusType.AdditionalInfo);
ReadXmlStreamed(readerStream, hashedStream); ReadXmlStreamed(readerStream, hashedStream);
// ReadXmlDom(readerStream); // ReadXmlDom(readerStream);
@ -312,7 +319,8 @@ namespace KeePassLib.Serialization
if(m_pbMasterSeed.Length != 32) if(m_pbMasterSeed.Length != 32)
throw new FormatException(KLRes.MasterSeedLengthInvalid); throw new FormatException(KLRes.MasterSeedLengthInvalid);
ms.Write(m_pbMasterSeed, 0, 32); ms.Write(m_pbMasterSeed, 0, 32);
if (m_slLogger != null)
m_slLogger.SetText("KP2AKEY_TransformingKey", LogStatusType.AdditionalInfo);
byte[] pKey32 = m_pwDatabase.MasterKey.GenerateKey32(m_pbTransformSeed, byte[] pKey32 = m_pwDatabase.MasterKey.GenerateKey32(m_pbTransformSeed,
m_pwDatabase.KeyEncryptionRounds).ReadData(); m_pwDatabase.KeyEncryptionRounds).ReadData();
if((pKey32 == null) || (pKey32.Length != 32)) if((pKey32 == null) || (pKey32.Length != 32))

View File

@ -2,6 +2,7 @@ using System;
using Android.Content; using Android.Content;
using Android.OS; using Android.OS;
using KeePassLib.Serialization; using KeePassLib.Serialization;
using keepass2android.Io;
namespace keepass2android namespace keepass2android
{ {
@ -57,5 +58,7 @@ namespace keepass2android
Handler UiThreadHandler { get; } Handler UiThreadHandler { get; }
IProgressDialog CreateProgressDialog(Context ctx); IProgressDialog CreateProgressDialog(Context ctx);
IFileStorage GetFileStorage(IOConnectionInfo iocInfo);
} }
} }

View File

@ -3,7 +3,7 @@ namespace keepass2android
public interface IProgressDialog public interface IProgressDialog
{ {
void SetTitle(string title); void SetTitle(string title);
void SetMessage(string getResourceString); void SetMessage(string resourceString);
void Dismiss(); void Dismiss();
void Show(); void Show();
} }

View 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));
}
}
}

View 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();
}
}

View File

@ -41,6 +41,8 @@
<Reference Include="System.Xml" /> <Reference Include="System.Xml" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<Compile Include="Io\BuiltInFileStorage.cs" />
<Compile Include="Io\IFileStorage.cs" />
<Compile Include="IProgressDialog.cs" /> <Compile Include="IProgressDialog.cs" />
<Compile Include="PreferenceKey.cs" /> <Compile Include="PreferenceKey.cs" />
<Compile Include="UiStringKey.cs" /> <Compile Include="UiStringKey.cs" />
@ -68,7 +70,7 @@
<Compile Include="Resources\Resource.Designer.cs" /> <Compile Include="Resources\Resource.Designer.cs" />
<Compile Include="Properties\AssemblyInfo.cs" /> <Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="SearchDbHelper.cs" /> <Compile Include="SearchDbHelper.cs" />
<Compile Include="UpdateStatus.cs" /> <Compile Include="ProgressDialogStatusLogger.cs" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\KeePassLib2Android\KeePassLib2Android.csproj"> <ProjectReference Include="..\KeePassLib2Android\KeePassLib2Android.csproj">

View File

@ -6,6 +6,7 @@ namespace keepass2android
public enum PreferenceKey public enum PreferenceKey
{ {
remember_keyfile, remember_keyfile,
UseFileTransactions UseFileTransactions,
CheckForFileChangesOnSave
} }
} }

View File

@ -25,34 +25,54 @@ namespace keepass2android
/// <summary> /// <summary>
/// StatusLogger implementation which shows the progress in a progress dialog /// StatusLogger implementation which shows the progress in a progress dialog
/// </summary> /// </summary>
public class UpdateStatus: IStatusLogger { public class ProgressDialogStatusLogger: IStatusLogger {
private readonly IProgressDialog _progressDialog; private readonly IProgressDialog _progressDialog;
readonly IKp2aApp _app; readonly IKp2aApp _app;
private readonly Handler _handler; private readonly Handler _handler;
private string _message = "";
public UpdateStatus() {
public ProgressDialogStatusLogger() {
} }
public UpdateStatus(IKp2aApp app, Handler handler, IProgressDialog pd) { public ProgressDialogStatusLogger(IKp2aApp app, Handler handler, IProgressDialog pd) {
_app = app; _app = app;
_progressDialog = pd; _progressDialog = pd;
_handler = handler; _handler = handler;
} }
public void UpdateMessage(UiStringKey stringKey) { public void UpdateMessage(UiStringKey stringKey) {
if ( _app != null && _progressDialog != null && _handler != null ) { if (_app != null)
_handler.Post( () => {_progressDialog.SetMessage(_app.GetResourceString(stringKey));}); UpdateMessage(_app.GetResourceString(stringKey));
}
} }
public void UpdateMessage (String message) public void UpdateMessage (String message)
{ {
_message = message;
if ( _app!= null && _progressDialog != null && _handler != null ) { if ( _app!= null && _progressDialog != null && _handler != null ) {
_handler.Post(() => {_progressDialog.SetMessage(message); } ); _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 #region IStatusLogger implementation
public void StartLogging (string strOperation, bool bWriteOperationToLog) public void StartLogging (string strOperation, bool bWriteOperationToLog)
@ -72,10 +92,32 @@ namespace keepass2android
public bool SetText (string strNewText, LogStatusType lsType) 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; return true;
} }
private void UpdateMessage(string message, LogStatusType lsType)
{
if (lsType == LogStatusType.AdditionalInfo)
{
UpdateSubMessage(message);
}
else
{
UpdateMessage(message);
}
}
public bool ContinueWork () public bool ContinueWork ()
{ {
return true; return true;

View File

@ -32,7 +32,7 @@ namespace keepass2android
private readonly IKp2aApp _app; private readonly IKp2aApp _app;
private Thread _thread; private Thread _thread;
public ProgressTask(IKp2aApp app, Context ctx, RunnableOnFinish task, UiStringKey messageKey) { public ProgressTask(IKp2aApp app, Context ctx, RunnableOnFinish task) {
_task = task; _task = task;
_handler = app.UiThreadHandler; _handler = app.UiThreadHandler;
_app = app; _app = app;
@ -40,11 +40,12 @@ namespace keepass2android
// Show process dialog // Show process dialog
_progressDialog = app.CreateProgressDialog(ctx); _progressDialog = app.CreateProgressDialog(ctx);
_progressDialog.SetTitle(_app.GetResourceString(UiStringKey.progress_title)); _progressDialog.SetTitle(_app.GetResourceString(UiStringKey.progress_title));
_progressDialog.SetMessage(_app.GetResourceString(messageKey)); _progressDialog.SetMessage("Initializing...");
// Set code to run when this is finished // Set code to run when this is finished
_task.SetStatus(new UpdateStatus(_app, _handler, _progressDialog));
_task.OnFinishToRun = new AfterTask(task.OnFinishToRun, _handler, _progressDialog); _task.OnFinishToRun = new AfterTask(task.OnFinishToRun, _handler, _progressDialog);
_task.SetStatusLogger(new ProgressDialogStatusLogger(_app, _handler, _progressDialog));
} }

View File

@ -17,6 +17,19 @@ namespace keepass2android
keyfile_does_not_exist, keyfile_does_not_exist,
RecycleBin, RecycleBin,
progress_create, progress_create,
loading_database loading_database,
AddingEntry,
AddingGroup,
DeletingEntry,
DeletingGroup,
SettingPassword,
UndoingChanges,
TransformingKey,
DecodingDatabase,
ParsingDatabase,
CheckingTargetFileForChanges,
TitleSyncQuestion,
MessageSyncQuestions,
SynchronizingDatabase
} }
} }

View File

@ -17,10 +17,12 @@ This file is part of Keepass2Android, Copyright 2013 Philipp Crocoll. This file
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO;
using Android.Content; using Android.Content;
using KeePassLib; using KeePassLib;
using KeePassLib.Keys; using KeePassLib.Keys;
using KeePassLib.Serialization; using KeePassLib.Serialization;
using keepass2android.Io;
namespace keepass2android namespace keepass2android
{ {
@ -34,7 +36,7 @@ namespace keepass2android
public PwGroup Root; public PwGroup Root;
public PwDatabase KpDatabase; public PwDatabase KpDatabase;
public IOConnectionInfo Ioc { get { return KpDatabase.IOConnectionInfo; } } public IOConnectionInfo Ioc { get { return KpDatabase.IOConnectionInfo; } }
public DateTime LastChangeDate; public string LastFileVersion;
public SearchDbHelper SearchHelper; public SearchDbHelper SearchHelper;
public IDrawableFactory DrawableFactory; public IDrawableFactory DrawableFactory;
@ -83,15 +85,16 @@ namespace keepass2android
public bool DidOpenFileChange() public bool DidOpenFileChange()
{ {
if ((Loaded == false) || (Ioc.IsLocalFile() == false)) if (Loaded == false)
{ {
return 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(); PwDatabase pwDatabase = new PwDatabase();
@ -103,15 +106,17 @@ namespace keepass2android
try try
{ {
compositeKey.AddUserKey(new KcpKeyFile(keyfile)); compositeKey.AddUserKey(new KcpKeyFile(keyfile));
} catch (Exception) } catch (Exception e)
{ {
Kp2aLog.Log(e.ToString());
throw new KeyFileException(); throw new KeyFileException();
} }
} }
try try
{ {
pwDatabase.Open(iocInfo, compositeKey, status); IFileStorage fileStorage = _app.GetFileStorage(iocInfo);
pwDatabase.Open(fileStorage.OpenFileForRead(iocInfo), fileStorage.GetFilenameWithoutPathAndExt(iocInfo), iocInfo, compositeKey, status);
} }
catch (Exception) catch (Exception)
{ {
@ -125,14 +130,9 @@ namespace keepass2android
else throw; else throw;
} }
status.UpdateSubMessage("");
if (iocInfo.IsLocalFile()) LastFileVersion = _app.GetFileStorage(iocInfo).GetCurrentFileVersionFast(iocInfo);
{
LastChangeDate = System.IO.File.GetLastWriteTimeUtc(iocInfo.Path);
} else
{
LastChangeDate = DateTime.MinValue;
}
Root = pwDatabase.RootGroup; Root = pwDatabase.RootGroup;
PopulateGlobals(Root); PopulateGlobals(Root);
@ -184,9 +184,14 @@ namespace keepass2android
public void SaveData(Context ctx) { public void SaveData(Context ctx) {
KpDatabase.UseFileTransactions = _app.GetBooleanPreference(PreferenceKey.UseFileTransactions); 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) private void PopulateGlobals (PwGroup currentGroup)
{ {

View File

@ -21,31 +21,38 @@ using KeePassLib;
namespace keepass2android namespace keepass2android
{ {
public class AddEntry : RunnableOnFinish { public class AddEntry : RunnableOnFinish {
protected Database Db; protected Database Db
{
get { return _app.GetDb(); }
}
private readonly IKp2aApp _app;
private readonly PwEntry _entry; private readonly PwEntry _entry;
private readonly PwGroup _parentGroup; private readonly PwGroup _parentGroup;
private readonly Context _ctx; 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; _ctx = ctx;
_parentGroup = parentGroup; _parentGroup = parentGroup;
Db = db; _app = app;
_entry = entry; _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); _parentGroup.AddEntry(_entry, true);
// Commit to disk // Commit to disk
SaveDb save = new SaveDb(_ctx, Db, OnFinishToRun); SaveDb save = new SaveDb(_ctx, _app, OnFinishToRun);
save.SetStatusLogger(StatusLogger);
save.Run(); save.Run();
} }
@ -72,7 +79,9 @@ namespace keepass2android
// Add entry to global // Add entry to global
_db.Entries[_entry.Uuid] = _entry; _db.Entries[_entry.Uuid] = _entry;
} else { } else
{
StatusLogger.UpdateMessage(UiStringKey.UndoingChanges);
//TODO test fail //TODO test fail
_entry.ParentGroup.Entries.Remove(_entry); _entry.ParentGroup.Entries.Remove(_entry);
} }

View File

@ -23,7 +23,11 @@ namespace keepass2android
{ {
public class AddGroup : RunnableOnFinish { public class AddGroup : RunnableOnFinish {
internal Database Db; internal Database Db
{
get { return _app.GetDb(); }
}
private IKp2aApp _app;
private readonly String _name; private readonly String _name;
private readonly int _iconId; private readonly int _iconId;
internal PwGroup Group; internal PwGroup Group;
@ -32,31 +36,32 @@ namespace keepass2android
readonly Context _ctx; readonly Context _ctx;
public static AddGroup GetInstance(Context ctx, Database db, String name, int iconid, PwGroup parent, OnFinish finish, bool dontSave) { public static AddGroup GetInstance(Context ctx, IKp2aApp app, String name, int iconid, PwGroup parent, OnFinish finish, bool dontSave) {
return new AddGroup(ctx, db, name, iconid, parent, finish, 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; _ctx = ctx;
Db = db;
_name = name; _name = name;
_iconId = iconid; _iconId = iconid;
Parent = parent; Parent = parent;
DontSave = dontSave; DontSave = dontSave;
_app = app;
OnFinishToRun = new AfterAdd(this, OnFinishToRun);
_onFinishToRun = new AfterAdd(this, OnFinishToRun);
} }
public override void Run() { public override void Run() {
StatusLogger.UpdateMessage(UiStringKey.AddingGroup);
// Generate new group // Generate new group
Group = new PwGroup(true, true, _name, (PwIcon)_iconId); Group = new PwGroup(true, true, _name, (PwIcon)_iconId);
Parent.AddGroup(Group, true); Parent.AddGroup(Group, true);
// Commit to disk // Commit to disk
SaveDb save = new SaveDb(_ctx, Db, OnFinishToRun, DontSave); SaveDb save = new SaveDb(_ctx, _app, OnFinishToRun, DontSave);
save.SetStatusLogger(StatusLogger);
save.Run(); save.Run();
} }
@ -77,6 +82,7 @@ namespace keepass2android
// Add group to global list // Add group to global list
_addGroup.Db.Groups[_addGroup.Group.Uuid] = _addGroup.Group; _addGroup.Db.Groups[_addGroup.Group.Uuid] = _addGroup.Group;
} else { } else {
StatusLogger.UpdateMessage(UiStringKey.UndoingChanges);
_addGroup.Parent.Groups.Remove(_addGroup.Group); _addGroup.Parent.Groups.Remove(_addGroup.Group);
} }

View File

@ -40,6 +40,7 @@ namespace keepass2android
public override void Run() { public override void Run() {
StatusLogger.UpdateMessage(UiStringKey.progress_create);
Database db = _app.CreateNewDatabase(); Database db = _app.CreateNewDatabase();
db.KpDatabase = new KeePassLib.PwDatabase(); db.KpDatabase = new KeePassLib.PwDatabase();
@ -58,14 +59,15 @@ namespace keepass2android
db.SearchHelper = new SearchDbHelper(_app); db.SearchHelper = new SearchDbHelper(_app);
// Add a couple default groups // 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(); 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(); email.Run();
// Commit changes // Commit changes
SaveDb save = new SaveDb(_ctx, db, OnFinishToRun, _dontSave); SaveDb save = new SaveDb(_ctx, _app, OnFinishToRun, _dontSave);
OnFinishToRun = null; save.SetStatusLogger(StatusLogger);
_onFinishToRun = null;
save.Run(); save.Run();

View File

@ -48,8 +48,9 @@ namespace keepass2android
} }
} }
public override void Run() { public override void Run()
{
StatusLogger.UpdateMessage(UiStringKey.DeletingEntry);
PwDatabase pd = Db.KpDatabase; PwDatabase pd = Db.KpDatabase;
PwGroup pgRecycleBin = pd.RootGroup.FindGroup(pd.RecycleBinUuid, true); PwGroup pgRecycleBin = pd.RootGroup.FindGroup(pd.RecycleBinUuid, true);
@ -68,7 +69,7 @@ namespace keepass2android
PwDeletedObject pdo = new PwDeletedObject(pe.Uuid, dtNow); PwDeletedObject pdo = new PwDeletedObject(pe.Uuid, dtNow);
pd.DeletedObjects.Add(pdo); pd.DeletedObjects.Add(pdo);
OnFinishToRun = new ActionOnFinish((success, message) => _onFinishToRun = new ActionOnFinish((success, message) =>
{ {
if (success) if (success)
{ {
@ -89,7 +90,7 @@ namespace keepass2android
pgRecycleBin.AddEntry(pe, true, true); pgRecycleBin.AddEntry(pe, true, true);
pe.Touch(false); pe.Touch(false);
OnFinishToRun = new ActionOnFinish( (success, message) => _onFinishToRun = new ActionOnFinish( (success, message) =>
{ {
if ( success ) { if ( success ) {
// Mark previous parent dirty // Mark previous parent dirty
@ -106,7 +107,8 @@ namespace keepass2android
} }
// Commit database // Commit database
SaveDb save = new SaveDb(Ctx, Db, OnFinishToRun, false); SaveDb save = new SaveDb(Ctx, App, OnFinishToRun, false);
save.SetStatusLogger(StatusLogger);
save.Run(); save.Run();

View File

@ -70,6 +70,7 @@ namespace keepass2android
public override void Run() { public override void Run() {
StatusLogger.UpdateMessage(UiStringKey.DeletingGroup);
//from KP Desktop //from KP Desktop
PwGroup pg = _group; PwGroup pg = _group;
PwGroup pgParent = pg.ParentGroup; PwGroup pgParent = pg.ParentGroup;
@ -86,7 +87,7 @@ namespace keepass2android
PwDeletedObject pdo = new PwDeletedObject(pg.Uuid, DateTime.Now); PwDeletedObject pdo = new PwDeletedObject(pg.Uuid, DateTime.Now);
pd.DeletedObjects.Add(pdo); pd.DeletedObjects.Add(pdo);
OnFinishToRun = new AfterDeletePermanently(OnFinishToRun, App, _group); _onFinishToRun = new AfterDeletePermanently(OnFinishToRun, App, _group);
} }
else // Recycle else // Recycle
{ {
@ -95,7 +96,7 @@ namespace keepass2android
pgRecycleBin.AddGroup(pg, true, true); pgRecycleBin.AddGroup(pg, true, true);
pg.Touch(false); pg.Touch(false);
OnFinishToRun = new ActionOnFinish((success, message) => _onFinishToRun = new ActionOnFinish((success, message) =>
{ {
if ( success ) { if ( success ) {
// Mark new parent (Recycle bin) dirty // Mark new parent (Recycle bin) dirty
@ -113,7 +114,8 @@ namespace keepass2android
} }
// Save // Save
SaveDb save = new SaveDb(Ctx, Db, OnFinishToRun, DontSave); SaveDb save = new SaveDb(Ctx, App, OnFinishToRun, DontSave);
save.SetStatusLogger(StatusLogger);
save.Run(); save.Run();
} }

View File

@ -109,12 +109,12 @@ namespace keepass2android
(dlgSender, dlgEvt) => (dlgSender, dlgEvt) =>
{ {
DeletePermanently = true; DeletePermanently = true;
ProgressTask pt = new ProgressTask(App, Ctx, this, UiStringKey.saving_database); ProgressTask pt = new ProgressTask(App, Ctx, this);
pt.Run(); pt.Run();
}, },
(dlgSender, dlgEvt) => { (dlgSender, dlgEvt) => {
DeletePermanently = false; DeletePermanently = false;
ProgressTask pt = new ProgressTask(App, Ctx, this, UiStringKey.saving_database); ProgressTask pt = new ProgressTask(App, Ctx, this);
pt.Run(); pt.Run();
}, },
(dlgSender, dlgEvt) => {}, (dlgSender, dlgEvt) => {},
@ -124,7 +124,7 @@ namespace keepass2android
} else } else
{ {
ProgressTask pt = new ProgressTask(App, Ctx, this, UiStringKey.saving_database); ProgressTask pt = new ProgressTask(App, Ctx, this);
pt.Run(); pt.Run();
} }
} }

View File

@ -43,7 +43,8 @@ namespace keepass2android
{ {
try 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); SaveFileData (_ioc, _key);
} catch (KeyFileException) { } catch (KeyFileException) {

View File

@ -29,6 +29,13 @@ namespace keepass2android
protected OnFinish BaseOnFinish; protected OnFinish BaseOnFinish;
protected Handler Handler; 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() { protected OnFinish() {
} }
@ -47,7 +54,7 @@ namespace keepass2android
BaseOnFinish = finish; BaseOnFinish = finish;
Handler = null; Handler = null;
} }
public void SetResult(bool success, String message) { public void SetResult(bool success, String message) {
Success = success; Success = success;
Message = message; Message = message;

View File

@ -21,13 +21,19 @@ namespace keepass2android
public abstract class RunnableOnFinish { public abstract class RunnableOnFinish {
public OnFinish OnFinishToRun; protected OnFinish _onFinishToRun;
public UpdateStatus Status; public ProgressDialogStatusLogger StatusLogger = new ProgressDialogStatusLogger(); //default: empty but not null
protected RunnableOnFinish(OnFinish finish) { protected RunnableOnFinish(OnFinish finish) {
OnFinishToRun = finish; _onFinishToRun = finish;
} }
public OnFinish OnFinishToRun
{
get { return _onFinishToRun; }
set { _onFinishToRun = value; }
}
protected void Finish(bool result, String message) { protected void Finish(bool result, String message) {
if ( OnFinishToRun != null ) { if ( OnFinishToRun != null ) {
OnFinishToRun.SetResult(result, message); OnFinishToRun.SetResult(result, message);
@ -42,8 +48,12 @@ namespace keepass2android
} }
} }
public void SetStatus(UpdateStatus status) { public void SetStatusLogger(ProgressDialogStatusLogger status) {
Status = status; if (OnFinishToRun != null)
{
OnFinishToRun.StatusLogger = status;
}
StatusLogger = status;
} }
abstract public void Run(); abstract public void Run();

View File

@ -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 You should have received a copy of the GNU General Public License
along with Keepass2Android. If not, see <http://www.gnu.org/licenses/>. along with Keepass2Android. If not, see <http://www.gnu.org/licenses/>.
*/ */
using System; using System;
using System.IO;
using System.Security.Cryptography;
using Android.Content; 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 namespace keepass2android
{ {
public class SaveDb : RunnableOnFinish { public class SaveDb : RunnableOnFinish {
private readonly Database _db; private readonly IKp2aApp _app;
private readonly bool _dontSave; private readonly bool _dontSave;
private readonly Context _ctx; private readonly Context _ctx;
private Thread _workerThread;
public SaveDb(Context ctx, Database db, OnFinish finish, bool dontSave): base(finish) {
public SaveDb(Context ctx, IKp2aApp app, OnFinish finish, bool dontSave): base(finish) {
_ctx = ctx; _ctx = ctx;
_db = db; _app = app;
_dontSave = dontSave; _dontSave = dontSave;
} }
public SaveDb(Context ctx, Database db, OnFinish finish):base(finish) { public SaveDb(Context ctx, IKp2aApp app, OnFinish finish)
: base(finish)
{
_ctx = ctx; _ctx = ctx;
_db = db; _app = app;
_dontSave = false; _dontSave = false;
} }
@ -42,10 +56,67 @@ namespace keepass2android
{ {
if (! _dontSave) { if (! _dontSave) {
try { try
_db.SaveData (_ctx); {
if (_db.Ioc.IsLocalFile()) StatusLogger.UpdateMessage(UiStringKey.saving_database);
_db.LastChangeDate = System.IO.File.GetLastWriteTimeUtc(_db.Ioc.Path); 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) { } catch (Exception e) {
/* TODO KPDesktop: /* TODO KPDesktop:
* catch(Exception exSave) * catch(Exception exSave)
@ -54,13 +125,85 @@ namespace keepass2android
bSuccess = false; bSuccess = false;
} }
*/ */
Finish (false, e.Message); Finish (false, e.ToString());
return; 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);
}
} }

View File

@ -25,21 +25,23 @@ namespace keepass2android
private readonly String _password; private readonly String _password;
private readonly String _keyfile; private readonly String _keyfile;
private readonly Database _db; private readonly IKp2aApp _app;
private readonly bool _dontSave; private readonly bool _dontSave;
private readonly Context _ctx; 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; _ctx = ctx;
_db = db; _app = app;
_password = password; _password = password;
_keyfile = keyfile; _keyfile = keyfile;
_dontSave = false; _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; _ctx = ctx;
_db = db; _app = app;
_password = password; _password = password;
_keyfile = keyfile; _keyfile = keyfile;
_dontSave = dontSave; _dontSave = dontSave;
@ -48,7 +50,8 @@ namespace keepass2android
public override void Run () public override void Run ()
{ {
PwDatabase pm = _db.KpDatabase; StatusLogger.UpdateMessage(UiStringKey.SettingPassword);
PwDatabase pm = _app.GetDb().KpDatabase;
CompositeKey newKey = new CompositeKey (); CompositeKey newKey = new CompositeKey ();
if (String.IsNullOrEmpty (_password) == false) { if (String.IsNullOrEmpty (_password) == false) {
newKey.AddUserKey (new KcpPassword (_password)); newKey.AddUserKey (new KcpPassword (_password));
@ -69,8 +72,9 @@ namespace keepass2android
pm.MasterKey = newKey; pm.MasterKey = newKey;
// Save Database // Save Database
OnFinishToRun = new AfterSave(previousKey, previousMasterKeyChanged, pm, OnFinishToRun); _onFinishToRun = new AfterSave(previousKey, previousMasterKeyChanged, pm, OnFinishToRun);
SaveDb save = new SaveDb(_ctx, _db, OnFinishToRun, _dontSave); SaveDb save = new SaveDb(_ctx, _app, OnFinishToRun, _dontSave);
save.SetStatusLogger(StatusLogger);
save.Run(); save.Run();
} }

View File

@ -22,32 +22,33 @@ namespace keepass2android
{ {
public class UpdateEntry : RunnableOnFinish { public class UpdateEntry : RunnableOnFinish {
private readonly Database _db; private readonly IKp2aApp _app;
private readonly Context _ctx; 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; _ctx = ctx;
_db = db; _app = app;
OnFinishToRun = new AfterUpdate(oldE, newE, db, finish); _onFinishToRun = new AfterUpdate(oldE, newE, app, finish);
} }
public override void Run() { public override void Run() {
// Commit to disk // Commit to disk
SaveDb save = new SaveDb(_ctx, _db, OnFinishToRun); SaveDb save = new SaveDb(_ctx, _app, OnFinishToRun);
save.SetStatusLogger(StatusLogger);
save.Run(); save.Run();
} }
private class AfterUpdate : OnFinish { private class AfterUpdate : OnFinish {
private readonly PwEntry _backup; private readonly PwEntry _backup;
private readonly PwEntry _updatedEntry; 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; _backup = backup;
_updatedEntry = updatedEntry; _updatedEntry = updatedEntry;
_db = db; _app = app;
} }
public override void Run() { public override void Run() {
@ -65,11 +66,12 @@ namespace keepass2android
if ( parent != null ) { if ( parent != null ) {
// Mark parent group dirty // Mark parent group dirty
_db.Dirty.Add(parent); _app.GetDb().Dirty.Add(parent);
} }
} }
} else { } else {
StatusLogger.UpdateMessage(UiStringKey.UndoingChanges);
// If we fail to save, back out changes to global structure // If we fail to save, back out changes to global structure
//TODO test fail //TODO test fail
_updatedEntry.AssignProperties(_backup, false, true, false); _updatedEntry.AssignProperties(_backup, false, true, false);

View File

@ -20,7 +20,8 @@ namespace Kp2aUnitTests
// 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(TestSaveDb)}); //runner.AddTests(new List<Type> { typeof(TestSaveDb)});
//runner.AddTests(typeof(TestLoadDb).GetMethod("TestLoadWithPasswordOnly"));}} //runner.AddTests(typeof(TestSaveDb).GetMethod("TestLoadEditSaveWithSyncConflict"));
//runner.AddTests(typeof(TestSaveDb).GetMethod("TestLoadEditSave"));
return runner; return runner;
} }
} }

View File

@ -12,9 +12,9 @@ namespace Kp2aUnitTests
} }
public void SetMessage(string getResourceString) public void SetMessage(string resourceString)
{ {
Kp2aLog.Log("Progress message: " + resourceString);
} }
public void Dismiss() public void Dismiss()

View File

@ -74,7 +74,7 @@ namespace Kp2aUnitTests
}) })
); );
ProgressTask pt = new ProgressTask(app, Application.Context, task, UiStringKey.loading_database); ProgressTask pt = new ProgressTask(app, Application.Context, task);
pt.Run(); pt.Run();
pt.JoinWorkerThread(); pt.JoinWorkerThread();
Assert.IsTrue(loadSuccesful); Assert.IsTrue(loadSuccesful);
@ -83,16 +83,27 @@ namespace Kp2aUnitTests
protected void SaveDatabase(IKp2aApp app) protected void SaveDatabase(IKp2aApp app)
{ {
bool saveSuccesful = false; bool saveSuccesful = TrySaveDatabase(app);
SaveDb save = new SaveDb(Application.Context, app.GetDb(), new ActionOnFinish((success, message) =>
{
saveSuccesful = success;
}), false);
save.Run();
Assert.IsTrue(saveSuccesful); Assert.IsTrue(saveSuccesful);
} }
public static bool TrySaveDatabase(IKp2aApp app)
{
bool saveSuccesful = false;
SaveDb save = new SaveDb(Application.Context, app, new ActionOnFinish((success, message) =>
{
saveSuccesful = success;
if (!success)
{
Kp2aLog.Log("Error during TestBase.SaveDatabase: " + message);
}
}), false);
save.Run();
save.JoinWorkerThread();
return saveSuccesful;
}
protected IKp2aApp SetupAppWithDefaultDatabase() protected IKp2aApp SetupAppWithDefaultDatabase()
{ {
IKp2aApp app = new TestKp2aApp(); IKp2aApp app = new TestKp2aApp();

View File

@ -1,8 +1,10 @@
using System; using System;
using System.Collections.Generic;
using Android.Content; using Android.Content;
using Android.OS; using Android.OS;
using KeePassLib.Serialization; using KeePassLib.Serialization;
using keepass2android; using keepass2android;
using keepass2android.Io;
namespace Kp2aUnitTests namespace Kp2aUnitTests
{ {
@ -11,7 +13,14 @@ namespace Kp2aUnitTests
/// </summary> /// </summary>
internal class TestKp2aApp : IKp2aApp internal class TestKp2aApp : IKp2aApp
{ {
internal enum YesNoCancelResult
{
Yes, No, Cancel
}
private Database _db; private Database _db;
private YesNoCancelResult _yesNoCancelResult = YesNoCancelResult.Yes;
private Dictionary<PreferenceKey, bool> _preferences = new Dictionary<PreferenceKey, bool>();
public void SetShutdown() public void SetShutdown()
{ {
@ -43,13 +52,32 @@ namespace Kp2aUnitTests
public bool GetBooleanPreference(PreferenceKey key) public bool GetBooleanPreference(PreferenceKey key)
{ {
if (_preferences.ContainsKey(key))
return _preferences[key];
return true; return true;
} }
public UiStringKey? LastYesNoCancelQuestionTitle { get; set; }
public void AskYesNoCancel(UiStringKey titleKey, UiStringKey messageKey, EventHandler<DialogClickEventArgs> yesHandler, EventHandler<DialogClickEventArgs> noHandler, public void AskYesNoCancel(UiStringKey titleKey, UiStringKey messageKey, EventHandler<DialogClickEventArgs> yesHandler, EventHandler<DialogClickEventArgs> noHandler,
EventHandler<DialogClickEventArgs> cancelHandler, Context ctx) EventHandler<DialogClickEventArgs> cancelHandler, Context ctx)
{ {
yesHandler(null, null); LastYesNoCancelQuestionTitle = titleKey;
switch (_yesNoCancelResult)
{
case YesNoCancelResult.Yes:
yesHandler(null, null);
break;
case YesNoCancelResult.No:
noHandler(null, null);
break;
case YesNoCancelResult.Cancel:
cancelHandler(null, null);
break;
default:
throw new Exception("unexpected case!");
}
} }
public Handler UiThreadHandler { public Handler UiThreadHandler {
@ -59,5 +87,20 @@ namespace Kp2aUnitTests
{ {
return new ProgressDialogStub(); return new ProgressDialogStub();
} }
public IFileStorage GetFileStorage(IOConnectionInfo iocInfo)
{
return new BuiltInFileStorage();
}
public void SetYesNoCancelResult(YesNoCancelResult yesNoCancelResult)
{
_yesNoCancelResult = yesNoCancelResult;
}
public void SetPreference(PreferenceKey key, bool value)
{
_preferences[key] = value;
}
} }
} }

View File

@ -26,7 +26,7 @@ namespace Kp2aUnitTests
loadSuccesful = success; loadSuccesful = success;
}) })
); );
ProgressTask pt = new ProgressTask(app, Application.Context, task, UiStringKey.loading_database); ProgressTask pt = new ProgressTask(app, Application.Context, task);
Android.Util.Log.Debug("KP2ATest", "Running ProgressTask"); Android.Util.Log.Debug("KP2ATest", "Running ProgressTask");
pt.Run(); pt.Run();
pt.JoinWorkerThread(); pt.JoinWorkerThread();

View File

@ -1,11 +1,13 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO; using System.IO;
using System.Linq;
using System.Security.Cryptography; using System.Security.Cryptography;
using System.Text; using System.Text;
using Android.App; using Android.App;
using Android.OS; using Android.OS;
using KeePassLib; using KeePassLib;
using KeePassLib.Keys;
using KeePassLib.Serialization; using KeePassLib.Serialization;
using KeePassLib.Utility; using KeePassLib.Utility;
using Microsoft.VisualStudio.TestTools.UnitTesting; using Microsoft.VisualStudio.TestTools.UnitTesting;
@ -23,6 +25,7 @@ namespace Kp2aUnitTests
{ {
//create the default database: //create the default database:
IKp2aApp app = SetupAppWithDefaultDatabase(); IKp2aApp app = SetupAppWithDefaultDatabase();
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);
app = LoadDatabase(DefaultFilename, DefaultPassword, DefaultKeyfile); app = LoadDatabase(DefaultFilename, DefaultPassword, DefaultKeyfile);
@ -56,9 +59,14 @@ namespace Kp2aUnitTests
//save the database from app 1: //save the database from app 1:
SaveDatabase(app); SaveDatabase(app);
((TestKp2aApp)app2).SetYesNoCancelResult(TestKp2aApp.YesNoCancelResult.Yes);
//save the database from app 2: This save operation must detect the changes made from app 1 and ask if it should sync: //save the database from app 2: This save operation must detect the changes made from app 1 and ask if it should sync:
SaveDatabase(app2); SaveDatabase(app2);
//make sure the right question was asked
Assert.AreEqual(UiStringKey.TitleSyncQuestion, ((TestKp2aApp)app2).LastYesNoCancelQuestionTitle);
//add group 2 to app 1: //add group 2 to app 1:
app.GetDb().KpDatabase.RootGroup.AddGroup(group2, true); app.GetDb().KpDatabase.RootGroup.AddGroup(group2, true);
@ -68,7 +76,162 @@ namespace Kp2aUnitTests
//ensure the sync was successful: //ensure the sync was successful:
AssertDatabasesAreEqual(app.GetDb().KpDatabase, resultApp.GetDb().KpDatabase); AssertDatabasesAreEqual(app.GetDb().KpDatabase, resultApp.GetDb().KpDatabase);
Assert.IsTrue(false, "todo: test for sync question, test overwrite or cancel!"); }
[TestMethod]
public void TestLoadEditSaveWithSyncOverwrite()
{
//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);
//load it once again:
IKp2aApp app2 = LoadDatabase(DefaultFilename, DefaultPassword, DefaultKeyfile);
//modify the database by adding a group in both databases:
app.GetDb().KpDatabase.RootGroup.AddGroup(new PwGroup(true, true, "TestGroup", PwIcon.Apple), true);
var group2 = new PwGroup(true, true, "TestGroup2", PwIcon.Energy);
app2.GetDb().KpDatabase.RootGroup.AddGroup(group2, true);
//save the database from app 1:
SaveDatabase(app);
//the user clicks the "no" button when asked if the sync should be performed -> overwrite expected!
((TestKp2aApp)app2).SetYesNoCancelResult(TestKp2aApp.YesNoCancelResult.No);
//save the database from app 2: This save operation must detect the changes made from app 1 and ask if it should sync:
SaveDatabase(app2);
//make sure the right question was asked
Assert.AreEqual(UiStringKey.TitleSyncQuestion, ((TestKp2aApp)app2).LastYesNoCancelQuestionTitle);
//load database to a new app instance:
IKp2aApp resultApp = LoadDatabase(DefaultFilename, DefaultPassword, DefaultKeyfile);
//ensure the sync was NOT performed (overwrite expected!):
AssertDatabasesAreEqual(app2.GetDb().KpDatabase, resultApp.GetDb().KpDatabase);
}
[TestMethod]
public void TestLoadEditSaveWithSyncOverwriteBecauseOfNoCheck()
{
//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);
//load it once again:
IKp2aApp app2 = LoadDatabase(DefaultFilename, DefaultPassword, DefaultKeyfile);
//modify the database by adding a group in both databases:
app.GetDb().KpDatabase.RootGroup.AddGroup(new PwGroup(true, true, "TestGroup", PwIcon.Apple), true);
var group2 = new PwGroup(true, true, "TestGroup2", PwIcon.Energy);
app2.GetDb().KpDatabase.RootGroup.AddGroup(group2, true);
//save the database from app 1:
SaveDatabase(app);
//the user doesn't want to perform check for file change:
((TestKp2aApp) app2).SetPreference(PreferenceKey.CheckForFileChangesOnSave, false);
//save the database from app 2: This save operation must detect the changes made from app 1 and ask if it should sync:
SaveDatabase(app2);
//make sure no question was asked
Assert.AreEqual(null, ((TestKp2aApp)app2).LastYesNoCancelQuestionTitle);
//load database to a new app instance:
IKp2aApp resultApp = LoadDatabase(DefaultFilename, DefaultPassword, DefaultKeyfile);
//ensure the sync was NOT performed (overwrite expected!):
AssertDatabasesAreEqual(app2.GetDb().KpDatabase, resultApp.GetDb().KpDatabase);
}
[TestMethod]
public void TestLoadEditSaveWithSyncCancel()
{
//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);
//load it once again:
IKp2aApp app2 = LoadDatabase(DefaultFilename, DefaultPassword, DefaultKeyfile);
//modify the database by adding a group in both databases:
app.GetDb().KpDatabase.RootGroup.AddGroup(new PwGroup(true, true, "TestGroup", PwIcon.Apple), true);
var group2 = new PwGroup(true, true, "TestGroup2", PwIcon.Energy);
app2.GetDb().KpDatabase.RootGroup.AddGroup(group2, true);
//save the database from app 1:
SaveDatabase(app);
//the user clicks the "cancel" button when asked if the sync should be performed
((TestKp2aApp)app2).SetYesNoCancelResult(TestKp2aApp.YesNoCancelResult.Cancel);
//save the database from app 2: This save operation must detect the changes made from app 1 and ask if it should sync:
Assert.AreEqual(false, TrySaveDatabase(app2));
//make sure the right question was asked
Assert.AreEqual(UiStringKey.TitleSyncQuestion, ((TestKp2aApp)app2).LastYesNoCancelQuestionTitle);
//load database to a new app instance:
IKp2aApp resultApp = LoadDatabase(DefaultFilename, DefaultPassword, DefaultKeyfile);
//ensure the sync was NOT performed (cancel expected!):
AssertDatabasesAreEqual(app.GetDb().KpDatabase, resultApp.GetDb().KpDatabase);
}
[TestMethod]
public void TestLoadEditSaveWithSyncConflict()
{
//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);
//load it once again:
IKp2aApp app2 = LoadDatabase(DefaultFilename, DefaultPassword, DefaultKeyfile);
//modify the database by renaming the same group in both databases:
app.GetDb().KpDatabase.RootGroup.Groups.Single(g => g.Name == "Internet").Name += "abc";
app2.GetDb().KpDatabase.RootGroup.Groups.Single(g => g.Name == "Internet").Name += "abcde";
//app1 also changes the master password:
var compositeKey = app.GetDb().KpDatabase.MasterKey;
compositeKey.RemoveUserKey(compositeKey.GetUserKey(typeof (KcpPassword)));
compositeKey.AddUserKey(new KcpPassword("abc"));
//save the database from app 1:
SaveDatabase(app);
((TestKp2aApp)app2).SetYesNoCancelResult(TestKp2aApp.YesNoCancelResult.Yes);
//save the database from app 2: This save operation must fail because the target file cannot be loaded:
Assert.IsFalse(TrySaveDatabase(app2));
//make sure the right question was asked
Assert.AreEqual(UiStringKey.TitleSyncQuestion, ((TestKp2aApp)app2).LastYesNoCancelQuestionTitle);
}
[TestMethod]
public void TestSaveAsWhenReadOnly()
{
Assert.Fail("TODO: Test ");
}
[TestMethod]
public void TestSaveAsWhenSyncError()
{
Assert.Fail("TODO: Test ");
} }
[TestMethod] [TestMethod]
@ -119,4 +282,6 @@ namespace Kp2aUnitTests
return sOutput.Text; return sOutput.Text;
} }
} }
} }

View File

@ -123,8 +123,8 @@ namespace keepass2android
Entry.Expires = true; Entry.Expires = true;
Entry.Touch(true); Entry.Touch(true);
RequiresRefresh(); RequiresRefresh();
UpdateEntry update = new UpdateEntry(this, App.Kp2a.GetDb(), backupEntry, Entry, null); UpdateEntry update = new UpdateEntry(this, App.Kp2a, backupEntry, Entry, null);
ProgressTask pt = new ProgressTask(App.Kp2a, this, update, UiStringKey.saving_database); ProgressTask pt = new ProgressTask(App.Kp2a, this, update);
pt.Run(); pt.Run();
} }
FillData(false); FillData(false);

View File

@ -383,11 +383,11 @@ namespace keepass2android
},closeOrShowError); },closeOrShowError);
if ( State.IsNew ) { if ( State.IsNew ) {
runnable = AddEntry.GetInstance(this, App.Kp2a.GetDb(), newEntry, State.ParentGroup, afterAddEntry); runnable = AddEntry.GetInstance(this, App.Kp2a, newEntry, State.ParentGroup, afterAddEntry);
} else { } else {
runnable = new UpdateEntry(this, App.Kp2a.GetDb(), initialEntry, newEntry, closeOrShowError); runnable = new UpdateEntry(this, App.Kp2a, initialEntry, newEntry, closeOrShowError);
} }
ProgressTask pt = new ProgressTask(App.Kp2a, act, runnable, UiStringKey.saving_database); ProgressTask pt = new ProgressTask(App.Kp2a, act, runnable);
pt.Run(); pt.Run();

View File

@ -177,8 +177,8 @@ namespace keepass2android
int groupIconId = data.Extras.GetInt(GroupEditActivity.KeyIconId); int groupIconId = data.Extras.GetInt(GroupEditActivity.KeyIconId);
GroupBaseActivity act = this; GroupBaseActivity act = this;
Handler handler = new Handler(); Handler handler = new Handler();
AddGroup task = AddGroup.GetInstance(this, App.Kp2a.GetDb(), groupName, groupIconId, Group, new RefreshTask(handler, this), false); AddGroup task = AddGroup.GetInstance(this, App.Kp2a, groupName, groupIconId, Group, new RefreshTask(handler, this), false);
ProgressTask pt = new ProgressTask(App.Kp2a, act, task, UiStringKey.saving_database); ProgressTask pt = new ProgressTask(App.Kp2a, act, task);
pt.Run(); pt.Run();
break; break;

View File

@ -365,7 +365,7 @@ namespace keepass2android
Handler handler = new Handler(); Handler handler = new Handler();
LoadDb task = new LoadDb(App.Kp2a, _ioConnection, pass, key, new AfterLoad(handler, this)); LoadDb task = new LoadDb(App.Kp2a, _ioConnection, pass, key, new AfterLoad(handler, this));
ProgressTask pt = new ProgressTask(App.Kp2a, this, task, UiStringKey.loading_database); ProgressTask pt = new ProgressTask(App.Kp2a, this, task);
pt.Run(); pt.Run();
}; };

View File

@ -1230,6 +1230,12 @@ namespace keepass2android
// aapt resource value: 0x7f080033 // aapt resource value: 0x7f080033
public const int AboutText = 2131230771; public const int AboutText = 2131230771;
// aapt resource value: 0x7f08011e
public const int AddingEntry = 2131231006;
// aapt resource value: 0x7f08011f
public const int AddingGroup = 2131231007;
// aapt resource value: 0x7f080114 // aapt resource value: 0x7f080114
public const int AskDeletePermanentlyEntry = 2131230996; public const int AskDeletePermanentlyEntry = 2131230996;
@ -1278,29 +1284,32 @@ namespace keepass2android
// aapt resource value: 0x7f0800f5 // aapt resource value: 0x7f0800f5
public const int BinaryDirectory_title = 2131230965; public const int BinaryDirectory_title = 2131230965;
// aapt resource value: 0x7f080125 // aapt resource value: 0x7f08012f
public const int ChangeLog = 2131231013; public const int ChangeLog = 2131231023;
// aapt resource value: 0x7f080124 // aapt resource value: 0x7f08012e
public const int ChangeLog_0_7 = 2131231012; public const int ChangeLog_0_7 = 2131231022;
// aapt resource value: 0x7f080122 // aapt resource value: 0x7f08012c
public const int ChangeLog_0_8 = 2131231010; public const int ChangeLog_0_8 = 2131231020;
// aapt resource value: 0x7f080121 // aapt resource value: 0x7f08012b
public const int ChangeLog_0_8_1 = 2131231009; public const int ChangeLog_0_8_1 = 2131231019;
// aapt resource value: 0x7f080120 // aapt resource value: 0x7f08012a
public const int ChangeLog_0_8_2 = 2131231008; public const int ChangeLog_0_8_2 = 2131231018;
// aapt resource value: 0x7f08011f // aapt resource value: 0x7f080129
public const int ChangeLog_0_8_3 = 2131231007; public const int ChangeLog_0_8_3 = 2131231017;
// aapt resource value: 0x7f080123 // aapt resource value: 0x7f08012d
public const int ChangeLog_keptDonate = 2131231011; public const int ChangeLog_keptDonate = 2131231021;
// aapt resource value: 0x7f08011e // aapt resource value: 0x7f080128
public const int ChangeLog_title = 2131231006; public const int ChangeLog_title = 2131231016;
// aapt resource value: 0x7f080127
public const int CheckingTargetFileForChanges = 2131231015;
// aapt resource value: 0x7f080048 // aapt resource value: 0x7f080048
public const int ClearClipboard = 2131230792; public const int ClearClipboard = 2131230792;
@ -1311,6 +1320,15 @@ namespace keepass2android
// aapt resource value: 0x7f080034 // aapt resource value: 0x7f080034
public const int CreditsText = 2131230772; public const int CreditsText = 2131230772;
// aapt resource value: 0x7f080125
public const int DecodingDatabase = 2131231013;
// aapt resource value: 0x7f080120
public const int DeletingEntry = 2131231008;
// aapt resource value: 0x7f080121
public const int DeletingGroup = 2131231009;
// aapt resource value: 0x7f080081 // aapt resource value: 0x7f080081
public const int FileNotFound = 2131230849; public const int FileNotFound = 2131230849;
@ -1335,6 +1353,9 @@ namespace keepass2android
// aapt resource value: 0x7f08010c // aapt resource value: 0x7f08010c
public const int OpenKp2aKeyboardAutomatically_title = 2131230988; public const int OpenKp2aKeyboardAutomatically_title = 2131230988;
// aapt resource value: 0x7f080126
public const int ParsingDatabase = 2131231014;
// aapt resource value: 0x7f080023 // aapt resource value: 0x7f080023
public const int QuickUnlockDefaultEnabled_key = 2131230755; public const int QuickUnlockDefaultEnabled_key = 2131230755;
@ -1389,6 +1410,9 @@ namespace keepass2android
// aapt resource value: 0x7f0800fb // aapt resource value: 0x7f0800fb
public const int SaveAttachment_doneMessage = 2131230971; public const int SaveAttachment_doneMessage = 2131230971;
// aapt resource value: 0x7f080122
public const int SettingPassword = 2131231010;
// aapt resource value: 0x7f080109 // aapt resource value: 0x7f080109
public const int ShowCopyToClipboardNotification_summary = 2131230985; public const int ShowCopyToClipboardNotification_summary = 2131230985;
@ -1422,9 +1446,15 @@ namespace keepass2android
// aapt resource value: 0x7f0800de // aapt resource value: 0x7f0800de
public const int TanExpiresOnUse_title = 2131230942; public const int TanExpiresOnUse_title = 2131230942;
// aapt resource value: 0x7f080124
public const int TransformingKey = 2131231012;
// aapt resource value: 0x7f08002b // aapt resource value: 0x7f08002b
public const int TranslationURL = 2131230763; public const int TranslationURL = 2131230763;
// aapt resource value: 0x7f080123
public const int UndoingChanges = 2131231011;
// aapt resource value: 0x7f080026 // aapt resource value: 0x7f080026
public const int UsageCount_key = 2131230758; public const int UsageCount_key = 2131230758;

View File

@ -240,6 +240,17 @@
<string name="suggest_improvements">Suggest or vote for improvements</string> <string name="suggest_improvements">Suggest or vote for improvements</string>
<string name="rate_app">Rate this app</string> <string name="rate_app">Rate this app</string>
<string name="translate_app">Translate KP2A</string> <string name="translate_app">Translate KP2A</string>
<string name="AddingEntry">Adding entry…</string>
<string name="AddingGroup">Adding group…</string>
<string name="DeletingEntry">Deleting entry…</string>
<string name="DeletingGroup">Deleting group…</string>
<string name="SettingPassword">Setting password…</string>
<string name="UndoingChanges">Undoing changes…</string>
<string name="TransformingKey">Transforming master key…</string>
<string name="DecodingDatabase">Decoding database…</string>
<string name="ParsingDatabase">Parsing database…</string>
<string name="CheckingTargetFileForChanges">Checking target file for changes…</string>
<string name="ChangeLog_title">Change log</string> <string name="ChangeLog_title">Change log</string>
<string name="ChangeLog_0_8_3"><b>Version 0.8.3</b>\n <string name="ChangeLog_0_8_3"><b>Version 0.8.3</b>\n
* Username/TAN index displayed in entry list (see settings)\n * Username/TAN index displayed in entry list (see settings)\n

View File

@ -71,8 +71,8 @@ namespace keepass2android
} }
SetPassword sp = new SetPassword(Context, App.Kp2a.GetDb(), pass, keyfile, new AfterSave(this, _finish, new Handler())); SetPassword sp = new SetPassword(Context, App.Kp2a, pass, keyfile, new AfterSave(this, _finish, new Handler()));
ProgressTask pt = new ProgressTask(App.Kp2a, Context, sp, UiStringKey.saving_database); ProgressTask pt = new ProgressTask(App.Kp2a, Context, sp);
pt.Run(); pt.Run();
}; };

View File

@ -22,6 +22,7 @@ using Android.OS;
using Android.Runtime; using Android.Runtime;
using KeePassLib.Serialization; using KeePassLib.Serialization;
using Android.Preferences; using Android.Preferences;
using keepass2android.Io;
namespace keepass2android namespace keepass2android
{ {
@ -223,6 +224,11 @@ namespace keepass2android
return new RealProgressDialog(ctx); return new RealProgressDialog(ctx);
} }
public IFileStorage GetFileStorage(IOConnectionInfo iocInfo)
{
return new BuiltInFileStorage();
}
internal void OnTerminate() internal void OnTerminate()
{ {

View File

@ -170,8 +170,7 @@ namespace keepass2android
CreateDb create = new CreateDb(App.Kp2a, this, IOConnectionInfo.FromPath(filename), collectPassword, true); CreateDb create = new CreateDb(App.Kp2a, this, IOConnectionInfo.FromPath(filename), collectPassword, true);
ProgressTask createTask = new ProgressTask( ProgressTask createTask = new ProgressTask(
App.Kp2a, App.Kp2a,
this, create, this, create);
UiStringKey.progress_create);
createTask.Run(); createTask.Run();
@ -313,7 +312,8 @@ namespace keepass2android
GroupActivity.Launch(_activity, _activity.AppTask); GroupActivity.Launch(_activity, _activity.AppTask);
} else { } else {
IOConnection.DeleteFile(_ioc); App.Kp2a.GetFileStorage(_ioc).DeleteFile(_ioc);
} }
} }
} }

View File

@ -73,7 +73,7 @@ namespace keepass2android
String previousUsername = db.KpDatabase.DefaultUserName; String previousUsername = db.KpDatabase.DefaultUserName;
db.KpDatabase.DefaultUserName = e.NewValue.ToString(); db.KpDatabase.DefaultUserName = e.NewValue.ToString();
SaveDb save = new SaveDb(this, App.Kp2a.GetDb(), new ActionOnFinish( (success, message) => SaveDb save = new SaveDb(this, App.Kp2a, new ActionOnFinish( (success, message) =>
{ {
if (!success) if (!success)
{ {
@ -82,7 +82,7 @@ namespace keepass2android
Toast.MakeText(this, message, ToastLength.Long).Show(); Toast.MakeText(this, message, ToastLength.Long).Show();
} }
})); }));
ProgressTask pt = new ProgressTask(App.Kp2a, this, save, UiStringKey.saving_database); ProgressTask pt = new ProgressTask(App.Kp2a, this, save);
pt.Run(); pt.Run();
}; };
@ -95,7 +95,7 @@ namespace keepass2android
String previousName = db.KpDatabase.Name; String previousName = db.KpDatabase.Name;
db.KpDatabase.Name = e.NewValue.ToString(); db.KpDatabase.Name = e.NewValue.ToString();
SaveDb save = new SaveDb(this, App.Kp2a.GetDb(), new ActionOnFinish( (success, message) => SaveDb save = new SaveDb(this, App.Kp2a, new ActionOnFinish( (success, message) =>
{ {
if (!success) if (!success)
{ {
@ -104,7 +104,7 @@ namespace keepass2android
Toast.MakeText(this, message, ToastLength.Long).Show(); Toast.MakeText(this, message, ToastLength.Long).Show();
} }
})); }));
ProgressTask pt = new ProgressTask(App.Kp2a, this, save, UiStringKey.saving_database); ProgressTask pt = new ProgressTask(App.Kp2a, this, save);
pt.Run(); pt.Run();
}; };

View File

@ -80,8 +80,8 @@ namespace keepass2android.settings
PwDatabase.KeyEncryptionRounds = rounds; PwDatabase.KeyEncryptionRounds = rounds;
Handler handler = new Handler(); Handler handler = new Handler();
SaveDb save = new SaveDb(Context, App.Kp2a.GetDb(), new AfterSave(Context, handler, oldRounds, this)); SaveDb save = new SaveDb(Context, App.Kp2a, new AfterSave(Context, handler, oldRounds, this));
ProgressTask pt = new ProgressTask(App.Kp2a, Context, save, UiStringKey.saving_database); ProgressTask pt = new ProgressTask(App.Kp2a, Context, save);
pt.Run(); pt.Run();
} }