From 66f461186693e21c25b6f5d5cf5164d761f5f033 Mon Sep 17 00:00:00 2001 From: J-Jamet Date: Fri, 7 Feb 2020 18:22:37 +0100 Subject: [PATCH] Restore and delete entry history #335 --- .../keepass/activities/EntryActivity.kt | 46 +++++++++++-- .../database/action/ProgressDialogThread.kt | 30 +++++++++ .../DeleteEntryHistoryDatabaseRunnable.kt | 44 ++++++++++++ .../RestoreEntryHistoryDatabaseRunnable.kt | 67 +++++++++++++++++++ .../keepass/database/element/Entry.kt | 4 ++ .../database/element/entry/EntryKDBX.kt | 4 ++ .../DatabaseTaskNotificationService.kt | 43 ++++++++++++ .../res/drawable/ic_autorenew_white_24dp.xml | 9 +++ app/src/main/res/menu/entry_history.xml | 32 +++++++++ app/src/main/res/values/strings.xml | 2 + 10 files changed, 277 insertions(+), 4 deletions(-) create mode 100644 app/src/main/java/com/kunzisoft/keepass/database/action/history/DeleteEntryHistoryDatabaseRunnable.kt create mode 100644 app/src/main/java/com/kunzisoft/keepass/database/action/history/RestoreEntryHistoryDatabaseRunnable.kt create mode 100644 app/src/main/res/drawable/ic_autorenew_white_24dp.xml create mode 100644 app/src/main/res/menu/entry_history.xml diff --git a/app/src/main/java/com/kunzisoft/keepass/activities/EntryActivity.kt b/app/src/main/java/com/kunzisoft/keepass/activities/EntryActivity.kt index 01593d975..1ef682c3c 100644 --- a/app/src/main/java/com/kunzisoft/keepass/activities/EntryActivity.kt +++ b/app/src/main/java/com/kunzisoft/keepass/activities/EntryActivity.kt @@ -48,6 +48,8 @@ import com.kunzisoft.keepass.model.AttachmentState import com.kunzisoft.keepass.model.EntryAttachment import com.kunzisoft.keepass.notifications.AttachmentFileNotificationService import com.kunzisoft.keepass.notifications.ClipboardEntryNotificationService +import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_DELETE_ENTRY_HISTORY +import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_RESTORE_ENTRY_HISTORY import com.kunzisoft.keepass.settings.PreferencesUtil import com.kunzisoft.keepass.settings.SettingsAutofillActivity import com.kunzisoft.keepass.tasks.AttachmentFileBinderManager @@ -73,7 +75,10 @@ class EntryActivity : LockingActivity() { private var mDatabase: Database? = null private var mEntry: Entry? = null + private var mIsHistory: Boolean = false + private var mEntryLastVersion: Entry? = null + private var mEntryHistoryPosition: Int = -1 private var mShowPassword: Boolean = false @@ -122,6 +127,18 @@ class EntryActivity : LockingActivity() { // Init attachment service binder manager mAttachmentFileBinderManager = AttachmentFileBinderManager(this) + + mProgressDialogThread?.onActionFinish = { actionTask, result -> + when (actionTask) { + ACTION_DATABASE_RESTORE_ENTRY_HISTORY, + ACTION_DATABASE_DELETE_ENTRY_HISTORY -> { + // Close the current activity after an history action + if (result.isSuccess) + finish() + } + } + // TODO Visual error for entry history + } } override fun onResume() { @@ -131,11 +148,13 @@ class EntryActivity : LockingActivity() { try { val keyEntry: NodeId = intent.getParcelableExtra(KEY_ENTRY) mEntry = mDatabase?.getEntryById(keyEntry) + mEntryLastVersion = mEntry } catch (e: ClassCastException) { Log.e(TAG, "Unable to retrieve the entry key") } - val historyPosition = intent.getIntExtra(KEY_ENTRY_HISTORY_POSITION, -1) + val historyPosition = intent.getIntExtra(KEY_ENTRY_HISTORY_POSITION, mEntryHistoryPosition) + mEntryHistoryPosition = historyPosition if (historyPosition >= 0) { mIsHistory = true mEntry = mEntry?.getHistory()?.get(historyPosition) @@ -220,7 +239,7 @@ class EntryActivity : LockingActivity() { "\n\n" + getString(R.string.clipboard_warning)) .create().apply { - setButton(AlertDialog.BUTTON_POSITIVE, getText(R.string.enable)) {dialog, _ -> + setButton(AlertDialog.BUTTON_POSITIVE, getText(R.string.enable)) { dialog, _ -> PreferencesUtil.setAllowCopyPasswordAndProtectedFields(this@EntryActivity, true) dialog.dismiss() fillEntryDataInContentsView(entry) @@ -340,7 +359,7 @@ class EntryActivity : LockingActivity() { if (showHistoryView) { entryContentsView?.assignHistory(entryHistory) entryContentsView?.onHistoryClick { historyItem, position -> - launch(this, historyItem, true, position) + launch(this, historyItem, mReadOnly, position) } } entryContentsView?.refreshHistory() @@ -389,7 +408,10 @@ class EntryActivity : LockingActivity() { MenuUtil.contributionMenuInflater(inflater, menu) inflater.inflate(R.menu.entry, menu) inflater.inflate(R.menu.database, menu) - if (mReadOnly) { + if (mIsHistory && !mReadOnly) { + inflater.inflate(R.menu.entry_history, menu) + } + if (mIsHistory || mReadOnly) { menu.findItem(R.id.menu_save_database)?.isVisible = false menu.findItem(R.id.menu_edit)?.isVisible = false } @@ -482,6 +504,22 @@ class EntryActivity : LockingActivity() { UriUtil.gotoUrl(this, url) return true } + R.id.menu_restore_entry_history -> { + mEntryLastVersion?.let { mainEntry -> + mProgressDialogThread?.startDatabaseRestoreEntryHistory( + mainEntry, + mEntryHistoryPosition, + !mReadOnly && mAutoSaveEnable) + } + } + R.id.menu_delete_entry_history -> { + mEntryLastVersion?.let { mainEntry -> + mProgressDialogThread?.startDatabaseDeleteEntryHistory( + mainEntry, + mEntryHistoryPosition, + !mReadOnly && mAutoSaveEnable) + } + } R.id.menu_lock -> { lockAndExit() return true diff --git a/app/src/main/java/com/kunzisoft/keepass/database/action/ProgressDialogThread.kt b/app/src/main/java/com/kunzisoft/keepass/database/action/ProgressDialogThread.kt index 05c45cc18..65aa1c495 100644 --- a/app/src/main/java/com/kunzisoft/keepass/database/action/ProgressDialogThread.kt +++ b/app/src/main/java/com/kunzisoft/keepass/database/action/ProgressDialogThread.kt @@ -41,9 +41,11 @@ import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Compa import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_CREATE_ENTRY_TASK import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_CREATE_GROUP_TASK import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_CREATE_TASK +import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_DELETE_ENTRY_HISTORY import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_DELETE_NODES_TASK import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_LOAD_TASK import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_MOVE_NODES_TASK +import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_RESTORE_ENTRY_HISTORY import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_SAVE import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_UPDATE_COLOR_TASK import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_UPDATE_COMPRESSION_TASK @@ -367,6 +369,34 @@ class ProgressDialogThread(private val activity: FragmentActivity) { startDatabaseActionListNodes(ACTION_DATABASE_DELETE_NODES_TASK, nodesToDelete, null, save) } + /* + ----------------- + Entry History Settings + ----------------- + */ + + fun startDatabaseRestoreEntryHistory(mainEntry: Entry, + entryHistoryPosition: Int, + save: Boolean) { + start(Bundle().apply { + putParcelable(DatabaseTaskNotificationService.ENTRY_ID_KEY, mainEntry.nodeId) + putInt(DatabaseTaskNotificationService.ENTRY_HISTORY_POSITION_KEY, entryHistoryPosition) + putBoolean(DatabaseTaskNotificationService.SAVE_DATABASE_KEY, save) + } + , ACTION_DATABASE_RESTORE_ENTRY_HISTORY) + } + + fun startDatabaseDeleteEntryHistory(mainEntry: Entry, + entryHistoryPosition: Int, + save: Boolean) { + start(Bundle().apply { + putParcelable(DatabaseTaskNotificationService.ENTRY_ID_KEY, mainEntry.nodeId) + putInt(DatabaseTaskNotificationService.ENTRY_HISTORY_POSITION_KEY, entryHistoryPosition) + putBoolean(DatabaseTaskNotificationService.SAVE_DATABASE_KEY, save) + } + , ACTION_DATABASE_DELETE_ENTRY_HISTORY) + } + /* ----------------- Main Settings diff --git a/app/src/main/java/com/kunzisoft/keepass/database/action/history/DeleteEntryHistoryDatabaseRunnable.kt b/app/src/main/java/com/kunzisoft/keepass/database/action/history/DeleteEntryHistoryDatabaseRunnable.kt new file mode 100644 index 000000000..11a91d672 --- /dev/null +++ b/app/src/main/java/com/kunzisoft/keepass/database/action/history/DeleteEntryHistoryDatabaseRunnable.kt @@ -0,0 +1,44 @@ +/* + * Copyright 2020 Jeremy Jamet / Kunzisoft. + * + * This file is part of KeePassDX. + * + * KeePassDX is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * KeePassDX is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with KeePassDX. If not, see . + * + */ +package com.kunzisoft.keepass.database.action.history + +import android.content.Context +import com.kunzisoft.keepass.database.action.SaveDatabaseRunnable +import com.kunzisoft.keepass.database.element.Database +import com.kunzisoft.keepass.database.element.Entry + +class DeleteEntryHistoryDatabaseRunnable ( + context: Context, + database: Database, + private val mainEntry: Entry, + private val entryHistoryPosition: Int, + saveDatabase: Boolean) + : SaveDatabaseRunnable(context, database, saveDatabase) { + + override fun onStartRun() { + try { + mainEntry.removeEntryFromHistory(entryHistoryPosition) + } catch (e: Exception) { + setError(e) + } + + super.onStartRun() + } +} diff --git a/app/src/main/java/com/kunzisoft/keepass/database/action/history/RestoreEntryHistoryDatabaseRunnable.kt b/app/src/main/java/com/kunzisoft/keepass/database/action/history/RestoreEntryHistoryDatabaseRunnable.kt new file mode 100644 index 000000000..7163cdf38 --- /dev/null +++ b/app/src/main/java/com/kunzisoft/keepass/database/action/history/RestoreEntryHistoryDatabaseRunnable.kt @@ -0,0 +1,67 @@ +/* + * Copyright 2020 Jeremy Jamet / Kunzisoft. + * + * This file is part of KeePassDX. + * + * KeePassDX is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * KeePassDX is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with KeePassDX. If not, see . + * + */ +package com.kunzisoft.keepass.database.action.history + +import android.content.Context +import com.kunzisoft.keepass.database.action.node.UpdateEntryRunnable +import com.kunzisoft.keepass.database.element.Database +import com.kunzisoft.keepass.database.element.Entry +import com.kunzisoft.keepass.tasks.ActionRunnable + +class RestoreEntryHistoryDatabaseRunnable ( + private val context: Context, + private val database: Database, + private val mainEntry: Entry, + private val entryHistoryPosition: Int, + private val saveDatabase: Boolean) + : ActionRunnable() { + + private var updateEntryRunnable: UpdateEntryRunnable? = null + + override fun onStartRun() { + try { + val historyToRestore = Entry(mainEntry.getHistory()[entryHistoryPosition]) + // Copy history of main entry in the restore entry + mainEntry.getHistory().forEach { + historyToRestore.addEntryToHistory(it) + } + // Update the entry with the fresh formatted entry to restore + updateEntryRunnable = UpdateEntryRunnable(context, + database, + mainEntry, + historyToRestore, + saveDatabase, + null) + + updateEntryRunnable?.onStartRun() + + } catch (e: Exception) { + setError(e) + } + } + + override fun onActionRun() { + updateEntryRunnable?.onActionRun() + } + + override fun onFinishRun() { + updateEntryRunnable?.onFinishRun() + } +} diff --git a/app/src/main/java/com/kunzisoft/keepass/database/element/Entry.kt b/app/src/main/java/com/kunzisoft/keepass/database/element/Entry.kt index 1f5ee1cb2..f6a15b84a 100644 --- a/app/src/main/java/com/kunzisoft/keepass/database/element/Entry.kt +++ b/app/src/main/java/com/kunzisoft/keepass/database/element/Entry.kt @@ -354,6 +354,10 @@ class Entry : Node, EntryVersionedInterface { } } + fun removeEntryFromHistory(position: Int) { + entryKDBX?.removeEntryFromHistory(position) + } + fun removeAllHistory() { entryKDBX?.removeAllHistory() } diff --git a/app/src/main/java/com/kunzisoft/keepass/database/element/entry/EntryKDBX.kt b/app/src/main/java/com/kunzisoft/keepass/database/element/entry/EntryKDBX.kt index 8158d7e46..2037ede0e 100644 --- a/app/src/main/java/com/kunzisoft/keepass/database/element/entry/EntryKDBX.kt +++ b/app/src/main/java/com/kunzisoft/keepass/database/element/entry/EntryKDBX.kt @@ -304,6 +304,10 @@ class EntryKDBX : EntryVersioned, NodeKDBXInte history.add(entry) } + fun removeEntryFromHistory(position: Int) { + history.removeAt(position) + } + fun removeAllHistory() { history.clear() } diff --git a/app/src/main/java/com/kunzisoft/keepass/notifications/DatabaseTaskNotificationService.kt b/app/src/main/java/com/kunzisoft/keepass/notifications/DatabaseTaskNotificationService.kt index f455a7189..28e29acb0 100644 --- a/app/src/main/java/com/kunzisoft/keepass/notifications/DatabaseTaskNotificationService.kt +++ b/app/src/main/java/com/kunzisoft/keepass/notifications/DatabaseTaskNotificationService.kt @@ -28,6 +28,8 @@ import android.os.IBinder import com.kunzisoft.keepass.R import com.kunzisoft.keepass.app.database.CipherDatabaseEntity import com.kunzisoft.keepass.database.action.* +import com.kunzisoft.keepass.database.action.history.DeleteEntryHistoryDatabaseRunnable +import com.kunzisoft.keepass.database.action.history.RestoreEntryHistoryDatabaseRunnable import com.kunzisoft.keepass.database.action.node.* import com.kunzisoft.keepass.database.element.* import com.kunzisoft.keepass.database.element.node.Node @@ -126,6 +128,8 @@ class DatabaseTaskNotificationService : NotificationService(), ProgressTaskUpdat ACTION_DATABASE_COPY_NODES_TASK -> buildDatabaseCopyNodesActionTask(intent) ACTION_DATABASE_MOVE_NODES_TASK -> buildDatabaseMoveNodesActionTask(intent) ACTION_DATABASE_DELETE_NODES_TASK -> buildDatabaseDeleteNodesActionTask(intent) + ACTION_DATABASE_RESTORE_ENTRY_HISTORY -> buildDatabaseRestoreEntryHistoryActionTask(intent) + ACTION_DATABASE_DELETE_ENTRY_HISTORY -> buildDatabaseDeleteEntryHistoryActionTask(intent) ACTION_DATABASE_UPDATE_COMPRESSION_TASK -> buildDatabaseUpdateCompressionActionTask(intent) ACTION_DATABASE_UPDATE_NAME_TASK, ACTION_DATABASE_UPDATE_DESCRIPTION_TASK, @@ -428,6 +432,42 @@ class DatabaseTaskNotificationService : NotificationService(), ProgressTaskUpdat } } + private fun buildDatabaseRestoreEntryHistoryActionTask(intent: Intent): ActionRunnable? { + return if (intent.hasExtra(ENTRY_ID_KEY) + && intent.hasExtra(ENTRY_HISTORY_POSITION_KEY) + && intent.hasExtra(SAVE_DATABASE_KEY) + ) { + val database = Database.getInstance() + database.getEntryById(intent.getParcelableExtra(ENTRY_ID_KEY))?.let { mainEntry -> + RestoreEntryHistoryDatabaseRunnable(this, + database, + mainEntry, + intent.getIntExtra(ENTRY_HISTORY_POSITION_KEY, -1), + intent.getBooleanExtra(SAVE_DATABASE_KEY, false)) + } + } else { + null + } + } + + private fun buildDatabaseDeleteEntryHistoryActionTask(intent: Intent): ActionRunnable? { + return if (intent.hasExtra(ENTRY_ID_KEY) + && intent.hasExtra(ENTRY_HISTORY_POSITION_KEY) + && intent.hasExtra(SAVE_DATABASE_KEY) + ) { + val database = Database.getInstance() + database.getEntryById(intent.getParcelableExtra(ENTRY_ID_KEY))?.let { mainEntry -> + DeleteEntryHistoryDatabaseRunnable(this, + database, + mainEntry, + intent.getIntExtra(ENTRY_HISTORY_POSITION_KEY, -1), + intent.getBooleanExtra(SAVE_DATABASE_KEY, false)) + } + } else { + null + } + } + private fun buildDatabaseUpdateCompressionActionTask(intent: Intent): ActionRunnable? { return if (intent.hasExtra(OLD_ELEMENT_KEY) && intent.hasExtra(NEW_ELEMENT_KEY) @@ -522,6 +562,8 @@ class DatabaseTaskNotificationService : NotificationService(), ProgressTaskUpdat const val ACTION_DATABASE_COPY_NODES_TASK = "ACTION_DATABASE_COPY_NODES_TASK" const val ACTION_DATABASE_MOVE_NODES_TASK = "ACTION_DATABASE_MOVE_NODES_TASK" const val ACTION_DATABASE_DELETE_NODES_TASK = "ACTION_DATABASE_DELETE_NODES_TASK" + const val ACTION_DATABASE_RESTORE_ENTRY_HISTORY = "ACTION_DATABASE_RESTORE_ENTRY_HISTORY" + const val ACTION_DATABASE_DELETE_ENTRY_HISTORY = "ACTION_DATABASE_DELETE_ENTRY_HISTORY" const val ACTION_DATABASE_UPDATE_NAME_TASK = "ACTION_DATABASE_UPDATE_NAME_TASK" const val ACTION_DATABASE_UPDATE_DESCRIPTION_TASK = "ACTION_DATABASE_UPDATE_DESCRIPTION_TASK" const val ACTION_DATABASE_UPDATE_DEFAULT_USERNAME_TASK = "ACTION_DATABASE_UPDATE_DEFAULT_USERNAME_TASK" @@ -551,6 +593,7 @@ class DatabaseTaskNotificationService : NotificationService(), ProgressTaskUpdat const val GROUPS_ID_KEY = "GROUPS_ID_KEY" const val ENTRIES_ID_KEY = "ENTRIES_ID_KEY" const val PARENT_ID_KEY = "PARENT_ID_KEY" + const val ENTRY_HISTORY_POSITION_KEY = "ENTRY_HISTORY_POSITION_KEY" const val SAVE_DATABASE_KEY = "SAVE_DATABASE_KEY" const val OLD_NODES_KEY = "OLD_NODES_KEY" const val NEW_NODES_KEY = "NEW_NODES_KEY" diff --git a/app/src/main/res/drawable/ic_autorenew_white_24dp.xml b/app/src/main/res/drawable/ic_autorenew_white_24dp.xml new file mode 100644 index 000000000..12fd45eab --- /dev/null +++ b/app/src/main/res/drawable/ic_autorenew_white_24dp.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/menu/entry_history.xml b/app/src/main/res/menu/entry_history.xml new file mode 100644 index 000000000..c4fc8b419 --- /dev/null +++ b/app/src/main/res/menu/entry_history.xml @@ -0,0 +1,32 @@ + + + + + + diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 2931c3d2c..4f2183000 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -185,6 +185,8 @@ Write-protected Modifiable Empty the recycle bin + Restore history + Delete history Minus Never No search results