Beispiel #1
0
 public bool DidOpenFileChange()
 {
     if (Loaded == false)
     {
         return(false);
     }
     return(_app.GetFileStorage(Ioc).CheckForFileChangeFast(Ioc, LastFileVersion));
 }
            public override void Run()
            {
                StatusLogger.UpdateMessage(UiStringKey.exporting_database);

                try
                {
                    var fileStorage = _app.GetFileStorage(_targetIoc);
                    if (fileStorage is IOfflineSwitchable)
                    {
                        ((IOfflineSwitchable)fileStorage).IsOffline = false;
                    }

                    CompositeKey masterKey = App.Kp2a.CurrentDb.KpDatabase.MasterKey;
                    var          sourceIoc = ((KcpKeyFile)masterKey.GetUserKey(typeof(KcpKeyFile))).Ioc;

                    IoUtil.Copy(_targetIoc, sourceIoc, App.Kp2a);

                    if (fileStorage is IOfflineSwitchable)
                    {
                        ((IOfflineSwitchable)fileStorage).IsOffline = App.Kp2a.OfflineMode;
                    }

                    Finish(true);
                }
                catch (Exception ex)
                {
                    Finish(false, ex.Message);
                }
            }
Beispiel #3
0
        public void TestCreateSaveAndLoad_TestIdenticalFiles_kdb()
        {
            string   filename = DefaultDirectory + "createsaveandload.kdb";
            IKp2aApp app      = SetupAppWithDatabase(filename);
            string   kdbxXml  = DatabaseToXml(app);

            //save it and reload it
            Android.Util.Log.Debug("KP2A", "-- Save DB -- ");
            SaveDatabase(app);
            Android.Util.Log.Debug("KP2A", "-- Load DB -- ");


            PwDatabase pwImp      = new PwDatabase();
            PwDatabase pwDatabase = app.GetDb().KpDatabase;

            pwImp.New(new IOConnectionInfo(), pwDatabase.MasterKey);
            pwImp.MemoryProtection = pwDatabase.MemoryProtection.CloneDeep();
            pwImp.MasterKey        = pwDatabase.MasterKey;

            IOConnectionInfo ioc = new IOConnectionInfo()
            {
                Path = filename
            };

            using (Stream s = app.GetFileStorage(ioc).OpenFileForRead(ioc))
            {
                app.GetDb().DatabaseFormat.PopulateDatabaseFromStream(pwImp, s, null);
            }
            string kdbxReloadedXml = DatabaseToXml(app);

            RemoveKdbLines(ref kdbxReloadedXml);
            RemoveKdbLines(ref kdbxXml);

            Assert.AreEqual(kdbxXml, kdbxReloadedXml);
        }
Beispiel #4
0
            public override void Run()
            {
                StatusLogger.UpdateMessage(UiStringKey.exporting_database);
                var          pd     = _app.GetDb().KpDatabase;
                PwExportInfo pwInfo = new PwExportInfo(pd.RootGroup, pd, true);

                try
                {
                    using (var writeTransaction = _app.GetFileStorage(_targetIoc).OpenWriteTransaction(_targetIoc, _app.GetDb().KpDatabase.UseFileTransactions))
                    {
                        Stream sOut = writeTransaction.OpenFile();
                        _fileFormat.Export(pwInfo, sOut, new NullStatusLogger());

                        if (sOut != null)
                        {
                            sOut.Close();
                        }

                        writeTransaction.CommitWrite();
                    }
                    Finish(true);
                }
                catch (Exception ex)
                {
                    Finish(false, ex.Message);
                }
            }
            public override void Run()
            {
                try
                {
                    var fileStorage = _app.GetFileStorage(_targetIoc);
                    if (fileStorage is IOfflineSwitchable)
                    {
                        ((IOfflineSwitchable)fileStorage).IsOffline = false;
                    }
                    using (var writeTransaction = fileStorage.OpenWriteTransaction(_targetIoc, _app.GetDb().KpDatabase.UseFileTransactions))
                    {
                        Stream sOut = writeTransaction.OpenFile();

                        byte[] byteArray = _data.ReadData();
                        sOut.Write(byteArray, 0, byteArray.Length);

                        sOut.Close();

                        writeTransaction.CommitWrite();
                    }
                    if (fileStorage is IOfflineSwitchable)
                    {
                        ((IOfflineSwitchable)fileStorage).IsOffline = App.Kp2a.OfflineMode;
                    }

                    Finish(true);
                }
                catch (Exception ex)
                {
                    Finish(false, ex.Message);
                }
            }
Beispiel #6
0
        //creates a local ioc where the sourceIoc can be stored to
        public static IOConnectionInfo GetInternalIoc(IOConnectionInfo sourceIoc, Context ctx, IKp2aApp app)
        {
            Java.IO.File internalDirectory = IoUtil.GetInternalDirectory(ctx);
            var          filestorage       = app.GetFileStorage(sourceIoc);

            string targetPath = filestorage.GetFilenameWithoutPathAndExt(sourceIoc);

            targetPath = targetPath.Trim("|\\?*<\":>+[]/'".ToCharArray());
            if (targetPath == "")
            {
                targetPath = "internal";
            }
            if (new File(internalDirectory, targetPath).Exists())
            {
                int c   = 1;
                var ext = UrlUtil.GetExtension(targetPath);
                var filenameWithoutExt = UrlUtil.StripExtension(targetPath);
                do
                {
                    c++;
                    targetPath = filenameWithoutExt + c;
                    if (!String.IsNullOrEmpty(ext))
                    {
                        targetPath += "." + ext;
                    }
                } while (new File(internalDirectory, targetPath).Exists());
            }
            return(IOConnectionInfo.FromPath(new File(internalDirectory, targetPath).CanonicalPath));
        }
Beispiel #7
0
        public static void Copy(IOConnectionInfo targetIoc, IOConnectionInfo sourceIoc, IKp2aApp app)
        {
            IFileStorage sourceStorage = app.GetFileStorage(sourceIoc, false);             //don't cache source. file won't be used ever again
            IFileStorage targetStorage = app.GetFileStorage(targetIoc);

            using (
                var writeTransaction = targetStorage.OpenWriteTransaction(targetIoc,
                                                                          app.GetBooleanPreference(
                                                                              PreferenceKey.UseFileTransactions)))
            {
                using (var writeStream = writeTransaction.OpenFile())
                {
                    sourceStorage.OpenFileForRead(sourceIoc).CopyTo(writeStream);
                }
                writeTransaction.CommitWrite();
            }
        }
Beispiel #8
0
        public static void Copy(IOConnectionInfo targetIoc, IOConnectionInfo sourceIoc, IKp2aApp app)
        {
            IFileStorage sourceStorage = app.GetFileStorage(sourceIoc, false); //don't cache source. file won't be used ever again
            IFileStorage targetStorage = app.GetFileStorage(targetIoc);

            using (
                var writeTransaction = targetStorage.OpenWriteTransaction(targetIoc,
                                                                          app.GetBooleanPreference(
                                                                              PreferenceKey.UseFileTransactions)))
            {
                using (var writeStream = writeTransaction.OpenFile())
                {
                    sourceStorage.OpenFileForRead(sourceIoc).CopyTo(writeStream);
                }
                writeTransaction.CommitWrite();
            }
        }
Beispiel #9
0
        public override void Run()
        {
            StatusLogger.UpdateMessage(UiStringKey.progress_create);
            Database db = _app.CreateNewDatabase(_makeCurrent);

            db.KpDatabase = new KeePassLib.PwDatabase();

            if (_key == null)
            {
                _key = new CompositeKey();                 //use a temporary key which should be changed after creation
            }

            db.KpDatabase.New(_ioc, _key, _app.GetFileStorage(_ioc).GetFilenameWithoutPathAndExt(_ioc));

            db.KpDatabase.KdfParameters = (new AesKdf()).GetDefaultParameters();
            db.KpDatabase.Name          = "Keepass2Android Password Database";
            //re-set the name of the root group because the PwDatabase uses UrlUtil which is not appropriate for all file storages:
            db.KpDatabase.RootGroup.Name = _app.GetFileStorage(_ioc).GetFilenameWithoutPathAndExt(_ioc);

            // Set Database state
            db.Root         = db.KpDatabase.RootGroup;
            db.SearchHelper = new SearchDbHelper(_app);

            // Add a couple default groups
            AddGroup internet = AddGroup.GetInstance(_ctx, _app, "Internet", 1, null, db.KpDatabase.RootGroup, null, true);

            internet.Run();
            AddGroup email = AddGroup.GetInstance(_ctx, _app, "eMail", 19, null, db.KpDatabase.RootGroup, null, true);

            email.Run();

            List <PwEntry>     addedEntries;
            AddTemplateEntries addTemplates = new AddTemplateEntries(_ctx, _app, null);

            addTemplates.AddTemplates(out addedEntries);

            // Commit changes
            SaveDb save = new SaveDb(_ctx, _app, db, OnFinishToRun, _dontSave);

            save.SetStatusLogger(StatusLogger);
            _onFinishToRun = null;
            save.Run();

            db.UpdateGlobals();
        }
        private void PrimaryIocSelected(IOConnectionInfo ioc)
        {
            var filestorage = _app.GetFileStorage(ioc, false);

            if (!filestorage.IsPermanentLocation(ioc))
            {
                string message = _app.GetResourceString(UiStringKey.FileIsTemporarilyAvailable) + " " + _app.GetResourceString(UiStringKey.CopyFileRequired) + " " + _app.GetResourceString(UiStringKey.ClickOkToSelectLocation);
                EventHandler <DialogClickEventArgs> onOk     = (sender, args) => { MoveToWritableLocation(ioc); };
                EventHandler <DialogClickEventArgs> onCancel = (sender, args) => { ReturnCancel(); };
                ShowAlertDialog(message, onOk, onCancel);
                return;
            }


            if ((RequestedWritableRequirements != WritableRequirements.ReadOnly) && (filestorage.IsReadOnly(ioc)))
            {
                string             readOnlyExplanation = _app.GetResourceString(UiStringKey.FileIsReadOnly);
                BuiltInFileStorage builtInFileStorage  = filestorage as BuiltInFileStorage;
                if (builtInFileStorage != null)
                {
                    if (builtInFileStorage.IsReadOnlyBecauseKitkatRestrictions(ioc))
                    {
                        readOnlyExplanation = _app.GetResourceString(UiStringKey.FileIsReadOnlyOnKitkat);
                    }
                }
                EventHandler <DialogClickEventArgs> onOk     = (sender, args) => { MoveToWritableLocation(ioc); };
                EventHandler <DialogClickEventArgs> onCancel = (sender, args) =>
                {
                    if (RequestedWritableRequirements == WritableRequirements.WriteDemanded)
                    {
                        ReturnCancel();
                    }
                    else
                    {
                        ReturnOk(ioc);
                    }
                };
                ShowAlertDialog(readOnlyExplanation + " "
                                + (RequestedWritableRequirements == WritableRequirements.WriteDemanded ?
                                   _app.GetResourceString(UiStringKey.CopyFileRequired)
                                                                           : _app.GetResourceString(UiStringKey.CopyFileRequiredForEditing))
                                + " "
                                + _app.GetResourceString(UiStringKey.ClickOkToSelectLocation), onOk, onCancel);
                return;
            }
            ReturnOk(ioc);
        }
            public override void BindView(View view, Context context, ICursor cursor)
            {
                String path = cursor.GetString(1);

                TextView         textView = view.FindViewById <TextView>(Resource.Id.file_filename);
                IOConnectionInfo ioc      = new IOConnectionInfo {
                    Path = path
                };
                var fileStorage = _app.GetFileStorage(ioc);

                String displayName = cursor.GetString(6);

                if (string.IsNullOrEmpty(displayName))
                {
                    displayName = fileStorage.GetDisplayName(ioc);
                }

                textView.Text = displayName;
                textView.Tag  = ioc.Path;
            }
Beispiel #12
0
        public override void Run()
        {
            try
            {
                IOConnectionInfo ioc         = _app.GetDb().Ioc;
                IFileStorage     fileStorage = _app.GetFileStorage(ioc);
                if (fileStorage is CachingFileStorage)
                {
                    throw new Exception("Cannot sync a cached database!");
                }
                StatusLogger.UpdateMessage(UiStringKey.CheckingDatabaseForChanges);

                //download file from remote location and calculate hash:
                StatusLogger.UpdateSubMessage(_app.GetResourceString(UiStringKey.DownloadingRemoteFile));


                MemoryStream remoteData = new MemoryStream();
                using (
                    HashingStreamEx hashingRemoteStream = new HashingStreamEx(fileStorage.OpenFileForRead(ioc), false,
                                                                              new SHA256Managed()))
                {
                    hashingRemoteStream.CopyTo(remoteData);
                    hashingRemoteStream.Close();

                    if (!MemUtil.ArraysEqual(_app.GetDb().KpDatabase.HashOfFileOnDisk, hashingRemoteStream.Hash))
                    {
                        _app.TriggerReload(_context);
                        Finish(true);
                    }
                    else
                    {
                        Finish(true, _app.GetResourceString(UiStringKey.RemoteDatabaseUnchanged));
                    }
                }
            }
            catch (Exception e)
            {
                Finish(false, e.Message);
            }
        }
Beispiel #13
0
        public void Start()
        {
            string messageSuffix = ShowDatabaseIocInStatus ? "(" + App.GetFileStorage(Db.Ioc).GetDisplayName(Db.Ioc) + ")" : "";

            if (CanRecycle)
            {
                App.AskYesNoCancel(UiStringKey.AskDeletePermanently_title,
                                   QuestionRecycleResourceId,
                                   (dlgSender, dlgEvt) =>
                {
                    DeletePermanently = true;
                    ProgressTask pt   = new ProgressTask(App, Ctx, this);
                    pt.Run();
                },
                                   (dlgSender, dlgEvt) =>
                {
                    DeletePermanently = false;
                    ProgressTask pt   = new ProgressTask(App, Ctx, this);
                    pt.Run();
                },
                                   (dlgSender, dlgEvt) => { },
                                   Ctx, messageSuffix);
            }
            else
            {
                App.AskYesNoCancel(UiStringKey.AskDeletePermanently_title,
                                   QuestionNoRecycleResourceId,
                                   (dlgSender, dlgEvt) =>
                {
                    ProgressTask pt = new ProgressTask(App, Ctx, this);
                    pt.Run();
                },
                                   null,
                                   (dlgSender, dlgEvt) => { },
                                   Ctx, messageSuffix);
            }
        }
Beispiel #14
0
            public override void Run()
            {
                StatusLogger.UpdateMessage(UiStringKey.exporting_database);
                var          pd     = _app.CurrentDb.KpDatabase;
                PwExportInfo pwInfo = new PwExportInfo(pd.RootGroup, pd, true);

                try
                {
                    var fileStorage = _app.GetFileStorage(_targetIoc);
                    if (fileStorage is IOfflineSwitchable)
                    {
                        ((IOfflineSwitchable)fileStorage).IsOffline = false;
                    }
                    using (var writeTransaction = fileStorage.OpenWriteTransaction(_targetIoc, _app.GetBooleanPreference(PreferenceKey.UseFileTransactions)))
                    {
                        Stream sOut = writeTransaction.OpenFile();
                        _fileFormat.Export(pwInfo, sOut, new NullStatusLogger());

                        if (sOut != null)
                        {
                            sOut.Close();
                        }

                        writeTransaction.CommitWrite();
                    }
                    if (fileStorage is IOfflineSwitchable)
                    {
                        ((IOfflineSwitchable)fileStorage).IsOffline = App.Kp2a.OfflineMode;
                    }

                    Finish(true);
                }
                catch (Exception ex)
                {
                    Finish(false, ex.Message);
                }
            }
Beispiel #15
0
        public override void Run()
        {
            if (!_dontSave)
            {
                try
                {
                    if (_db.CanWrite == false)
                    {
                        //this should only happen if there is a problem in the UI so that the user sees an edit interface.
                        Finish(false, "Cannot save changes. File is read-only!");
                        return;
                    }

                    string message = _app.GetResourceString(UiStringKey.saving_database);

                    if (ShowDatabaseIocInStatus)
                    {
                        message += " (" + _app.GetFileStorage(_db.Ioc).GetDisplayName(_db.Ioc) + ")";
                    }

                    StatusLogger.UpdateMessage(message);

                    IOConnectionInfo ioc         = _db.Ioc;
                    IFileStorage     fileStorage = _app.GetFileStorage(ioc);

                    if (_streamForOrigFile == null)
                    {
                        if ((!_app.GetBooleanPreference(PreferenceKey.CheckForFileChangesOnSave)) ||
                            (_db.KpDatabase.HashOfFileOnDisk == null))                                //first time saving
                        {
                            PerformSaveWithoutCheck(fileStorage, ioc);
                            Finish(true);
                            return;
                        }
                    }


                    if (
                        (_streamForOrigFile != null) ||
                        fileStorage.CheckForFileChangeFast(ioc, _db.LastFileVersion) ||                          //first try to use the fast change detection
                        (FileHashChanged(ioc, _db.KpDatabase.HashOfFileOnDisk) == FileHashChange.Changed)                            //if that fails, hash the file and compare:
                        )
                    {
                        //ask user...
                        _app.AskYesNoCancel(UiStringKey.TitleSyncQuestion, UiStringKey.MessageSyncQuestion,
                                            UiStringKey.YesSynchronize,
                                            UiStringKey.NoOverwrite,
                                            //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.
                                MergeIn(fileStorage, ioc);
                                PerformSaveWithoutCheck(fileStorage, ioc);
                                _db.UpdateGlobals();
                                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)
                     * {
                     * MessageService.ShowSaveWarning(pd.IOConnectionInfo, exSave, true);
                     * bSuccess = false;
                     * }
                     */
                    Kp2aLog.LogUnexpectedError(e);
                    Finish(false, e.Message);
                    return;
                }
            }
            else
            {
                Finish(true);
            }
        }
        public override void Run()
        {
            try
            {
                IOConnectionInfo ioc         = _app.GetDb().Ioc;
                IFileStorage     fileStorage = _app.GetFileStorage(ioc);
                if (!(fileStorage is CachingFileStorage))
                {
                    throw new Exception("Cannot sync a non-cached database!");
                }
                StatusLogger.UpdateMessage(UiStringKey.SynchronizingCachedDatabase);
                CachingFileStorage cachingFileStorage = (CachingFileStorage)fileStorage;

                //download file from remote location and calculate hash:
                StatusLogger.UpdateSubMessage(_app.GetResourceString(UiStringKey.DownloadingRemoteFile));
                string hash;

                MemoryStream remoteData;
                try
                {
                    remoteData = cachingFileStorage.GetRemoteDataAndHash(ioc, out hash);
                }
                catch (FileNotFoundException)
                {
                    StatusLogger.UpdateSubMessage(_app.GetResourceString(UiStringKey.RestoringRemoteFile));
                    cachingFileStorage.UpdateRemoteFile(ioc, _app.GetBooleanPreference(PreferenceKey.UseFileTransactions));
                    Finish(true, _app.GetResourceString(UiStringKey.SynchronizedDatabaseSuccessfully));
                    return;
                }

                //check if remote file was modified:
                if (cachingFileStorage.GetBaseVersionHash(ioc) != hash)
                {
                    //remote file is modified
                    if (cachingFileStorage.HasLocalChanges(ioc))
                    {
                        //conflict! need to merge
                        _saveDb = new SaveDb(_context, _app, new ActionOnFinish((success, result) =>
                        {
                            if (!success)
                            {
                                Finish(false, result);
                            }
                            else
                            {
                                Finish(true, _app.GetResourceString(UiStringKey.SynchronizedDatabaseSuccessfully));
                            }
                            _saveDb = null;
                        }), false, remoteData);
                        _saveDb.Run();

                        _app.GetDb().MarkAllGroupsAsDirty();
                    }
                    else
                    {
                        //only the remote file was modified -> reload database.
                        //note: it's best to lock the database and do a complete reload here (also better for UI consistency in case something goes wrong etc.)
                        _app.TriggerReload(_context);
                        Finish(true);
                    }
                }
                else
                {
                    //remote file is unmodified
                    if (cachingFileStorage.HasLocalChanges(ioc))
                    {
                        //but we have local changes -> upload:
                        StatusLogger.UpdateSubMessage(_app.GetResourceString(UiStringKey.UploadingFile));
                        cachingFileStorage.UpdateRemoteFile(ioc, _app.GetBooleanPreference(PreferenceKey.UseFileTransactions));
                        StatusLogger.UpdateSubMessage("");
                        Finish(true, _app.GetResourceString(UiStringKey.SynchronizedDatabaseSuccessfully));
                    }
                    else
                    {
                        //files are in sync: just set the result
                        Finish(true, _app.GetResourceString(UiStringKey.FilesInSync));
                    }
                }
            }
            catch (Exception e)
            {
                Finish(false, e.Message);
            }
        }
Beispiel #17
0
 public bool DidOpenFileChange()
 {
     return(_app.GetFileStorage(Ioc).CheckForFileChangeFast(Ioc, LastFileVersion));
 }
Beispiel #18
0
        public override void Run()
        {
            try
            {
                try
                {
                    //make sure the file data is stored in the recent files list even if loading fails
                    SaveFileData(_ioc, _keyfileOrProvider);


                    StatusLogger.UpdateMessage(UiStringKey.loading_database);
                    //get the stream data into a single stream variable (databaseStream) regardless whether its preloaded or not:
                    MemoryStream preloadedMemoryStream = _databaseData == null ? null : _databaseData.Result;
                    MemoryStream databaseStream;
                    if (preloadedMemoryStream != null)
                    {
                        databaseStream = preloadedMemoryStream;
                    }
                    else
                    {
                        using (Stream s = _app.GetFileStorage(_ioc).OpenFileForRead(_ioc))
                        {
                            databaseStream = new MemoryStream();
                            s.CopyTo(databaseStream);
                            databaseStream.Seek(0, SeekOrigin.Begin);
                        }
                    }

                    //ok, try to load the database. Let's start with Kdbx format and retry later if that is the wrong guess:
                    _format = new KdbxDatabaseFormat(KdbpFile.GetFormatToUse(_app.GetFileStorage(_ioc).GetFileExtension(_ioc)));
                    TryLoad(databaseStream);



                    success = true;
                }
                catch (Exception e)
                {
                    this.Exception = e;
                    throw;
                }
            }
            catch (KeyFileException)
            {
                Kp2aLog.Log("KeyFileException");
                Finish(false,                 /*TODO Localize: use Keepass error text KPRes.KeyFileError (including "or invalid format")*/
                       _app.GetResourceString(UiStringKey.keyfile_does_not_exist), false, Exception);
            }
            catch (AggregateException e)
            {
                string message = e.Message;
                foreach (var innerException in e.InnerExceptions)
                {
                    message = innerException.Message;
                    // Override the message shown with the last (hopefully most recent) inner exception
                    Kp2aLog.LogUnexpectedError(innerException);
                }
                Finish(false, _app.GetResourceString(UiStringKey.ErrorOcurred) + " " + message, false, Exception);
                return;
            }
            catch (DuplicateUuidsException e)
            {
                Kp2aLog.Log(e.ToString());
                Finish(false, _app.GetResourceString(UiStringKey.DuplicateUuidsError) + " " + e.Message + _app.GetResourceString(UiStringKey.DuplicateUuidsErrorAdditional), false, Exception);
                return;
            }
            catch (Exception e)
            {
                if (!(e is InvalidCompositeKeyException))
                {
                    Kp2aLog.LogUnexpectedError(e);
                }
                Finish(false, _app.GetResourceString(UiStringKey.ErrorOcurred) + " " + (e.Message ?? (e is FileNotFoundException ? _app.GetResourceString(UiStringKey.FileNotFound) :  "")), false, Exception);
                return;
            }
        }
Beispiel #19
0
        public override void Run()
        {
            if (!_dontSave)
            {
                try
                {
                    if (_db.CanWrite == false)
                    {
                        //this should only happen if there is a problem in the UI so that the user sees an edit interface.
                        Finish(false, "Cannot save changes. File is read-only!");
                        return;
                    }

                    string message = _app.GetResourceString(UiStringKey.saving_database);

                    if (ShowDatabaseIocInStatus)
                    {
                        message += " (" + _app.GetFileStorage(_db.Ioc).GetDisplayName(_db.Ioc) + ")";
                    }

                    StatusLogger.UpdateMessage(message);

                    IOConnectionInfo ioc         = _db.Ioc;
                    IFileStorage     fileStorage = _app.GetFileStorage(ioc);

                    if (_streamForOrigFile == null)
                    {
                        if ((!_app.GetBooleanPreference(PreferenceKey.CheckForFileChangesOnSave)) ||
                            (_db.KpDatabase.HashOfFileOnDisk == null))                                //first time saving
                        {
                            PerformSaveWithoutCheck(fileStorage, ioc);
                            Finish(true);
                            return;
                        }
                    }


                    bool hasStreamForOrigFile = (_streamForOrigFile != null);
                    bool hasChangeFast        = hasStreamForOrigFile ||
                                                fileStorage.CheckForFileChangeFast(ioc, _db.LastFileVersion); //first try to use the fast change detection;
                    bool hasHashChanged = hasChangeFast ||
                                          (FileHashChanged(ioc, _db.KpDatabase.HashOfFileOnDisk) ==
                                           FileHashChange.Changed); //if that fails, hash the file and compare:

                    if (hasHashChanged)
                    {
                        Kp2aLog.Log("Conflict. " + hasStreamForOrigFile + " " + hasChangeFast + " " + hasHashChanged);

                        bool alwaysMerge = (PreferenceManager.GetDefaultSharedPreferences(Application.Context)
                                            .GetBoolean("AlwaysMergeOnConflict", false));

                        if (alwaysMerge)
                        {
                            MergeAndFinish(fileStorage, ioc);
                        }
                        else
                        {
                            //ask user...
                            _app.AskYesNoCancel(UiStringKey.TitleSyncQuestion, UiStringKey.MessageSyncQuestion,
                                                UiStringKey.YesSynchronize,
                                                UiStringKey.NoOverwrite,
                                                //yes = sync
                                                (sender, args) =>
                            {
                                Action runHandler = () => { MergeAndFinish(fileStorage, ioc); };
                                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)
                     * {
                     * MessageService.ShowSaveWarning(pd.IOConnectionInfo, exSave, true);
                     * bSuccess = false;
                     * }
                     */
                    Kp2aLog.LogUnexpectedError(e);
                    Finish(false, e.Message);
                    return;
                }
            }
            else
            {
                Finish(true);
            }
        }