Multiple action node for Move Copy and Delete

This commit is contained in:
J-Jamet
2019-10-10 17:53:35 +02:00
parent 8d84358d48
commit fb2ea4c0ed
19 changed files with 411 additions and 509 deletions

View File

@@ -33,10 +33,7 @@ import com.kunzisoft.keepass.activities.dialogs.GeneratePasswordDialogFragment
import com.kunzisoft.keepass.activities.dialogs.IconPickerDialogFragment import com.kunzisoft.keepass.activities.dialogs.IconPickerDialogFragment
import com.kunzisoft.keepass.activities.lock.LockingHideActivity import com.kunzisoft.keepass.activities.lock.LockingHideActivity
import com.kunzisoft.keepass.database.action.ProgressDialogSaveDatabaseThread import com.kunzisoft.keepass.database.action.ProgressDialogSaveDatabaseThread
import com.kunzisoft.keepass.database.action.node.ActionNodeValues import com.kunzisoft.keepass.database.action.node.*
import com.kunzisoft.keepass.database.action.node.AddEntryRunnable
import com.kunzisoft.keepass.database.action.node.AfterActionNodeFinishRunnable
import com.kunzisoft.keepass.database.action.node.UpdateEntryRunnable
import com.kunzisoft.keepass.database.element.* import com.kunzisoft.keepass.database.element.*
import com.kunzisoft.keepass.education.EntryEditActivityEducation import com.kunzisoft.keepass.education.EntryEditActivityEducation
import com.kunzisoft.keepass.settings.PreferencesUtil import com.kunzisoft.keepass.settings.PreferencesUtil

View File

@@ -52,7 +52,6 @@ import com.kunzisoft.keepass.autofill.AutofillHelper
import com.kunzisoft.keepass.database.SortNodeEnum import com.kunzisoft.keepass.database.SortNodeEnum
import com.kunzisoft.keepass.database.action.ProgressDialogSaveDatabaseThread import com.kunzisoft.keepass.database.action.ProgressDialogSaveDatabaseThread
import com.kunzisoft.keepass.database.action.node.* import com.kunzisoft.keepass.database.action.node.*
import com.kunzisoft.keepass.database.action.node.ActionNodeDatabaseRunnable.Companion.NODE_POSITION_FOR_ACTION_NATURAL_ORDER_KEY
import com.kunzisoft.keepass.database.element.* import com.kunzisoft.keepass.database.element.*
import com.kunzisoft.keepass.education.GroupActivityEducation import com.kunzisoft.keepass.education.GroupActivityEducation
import com.kunzisoft.keepass.icons.assignDatabaseIcon import com.kunzisoft.keepass.icons.assignDatabaseIcon
@@ -502,93 +501,47 @@ class GroupActivity : LockingActivity(),
override fun onPasteMenuClick(pasteMode: ListNodesFragment.PasteMode?, override fun onPasteMenuClick(pasteMode: ListNodesFragment.PasteMode?,
nodes: List<NodeVersioned>): Boolean { nodes: List<NodeVersioned>): Boolean {
// TODO multiple paste when (pasteMode) {
nodes.forEach { currentNode -> ListNodesFragment.PasteMode.PASTE_FROM_COPY -> {
when (pasteMode) { // Copy
ListNodesFragment.PasteMode.PASTE_FROM_COPY -> { mCurrentGroup?.let { newParent ->
// COPY ProgressDialogSaveDatabaseThread(this) {
when (currentNode.type) { CopyNodesRunnable(this,
Type.GROUP -> Log.e(TAG, "Copy not allowed for group") Database.getInstance(),
Type.ENTRY -> { nodes,
mCurrentGroup?.let { newParent -> newParent,
ProgressDialogSaveDatabaseThread(this) { AfterAddNodeRunnable(),
CopyEntryRunnable(this, !mReadOnly)
Database.getInstance(), }.start()
currentNode as EntryVersioned,
newParent,
AfterAddNodeRunnable(),
!mReadOnly)
}.start()
}
}
}
} }
}
ListNodesFragment.PasteMode.PASTE_FROM_MOVE -> { ListNodesFragment.PasteMode.PASTE_FROM_MOVE -> {
// Move // Move
when (currentNode.type) { mCurrentGroup?.let { newParent ->
Type.GROUP -> { ProgressDialogSaveDatabaseThread(this) {
mCurrentGroup?.let { newParent -> MoveNodesRunnable(
ProgressDialogSaveDatabaseThread(this) { this,
MoveGroupRunnable( Database.getInstance(),
this, nodes,
Database.getInstance(), newParent,
currentNode as GroupVersioned, AfterAddNodeRunnable(),
newParent, !mReadOnly)
AfterAddNodeRunnable(), }.start()
!mReadOnly)
}.start()
}
}
Type.ENTRY -> {
mCurrentGroup?.let { newParent ->
ProgressDialogSaveDatabaseThread(this) {
MoveEntryRunnable(
this,
Database.getInstance(),
currentNode as EntryVersioned,
newParent,
AfterAddNodeRunnable(),
!mReadOnly)
}.start()
}
}
}
} }
} }
} }
finishNodeAction()
return true return true
} }
override fun onDeleteMenuClick(nodes: List<NodeVersioned>): Boolean { override fun onDeleteMenuClick(nodes: List<NodeVersioned>): Boolean {
// TODO multiple delete ProgressDialogSaveDatabaseThread(this) {
nodes.forEach { currentNode -> DeleteNodesRunnable(
when (currentNode.type) { this,
Type.GROUP -> { Database.getInstance(),
//TODO Verify trash recycle bin nodes,
ProgressDialogSaveDatabaseThread(this) { AfterDeleteNodeRunnable(),
DeleteGroupRunnable( !mReadOnly)
this, }.start()
Database.getInstance(),
currentNode as GroupVersioned,
AfterDeleteNodeRunnable(),
!mReadOnly)
}.start()
}
Type.ENTRY -> {
ProgressDialogSaveDatabaseThread(this) {
DeleteEntryRunnable(
this,
Database.getInstance(),
currentNode as EntryVersioned,
AfterDeleteNodeRunnable(),
!mReadOnly)
}.start()
}
}
}
finishNodeAction()
return true return true
} }
@@ -776,8 +729,7 @@ class GroupActivity : LockingActivity(),
}.start() }.start()
} }
} }
else -> { else -> {}
}
} }
} }
} }
@@ -786,9 +738,9 @@ class GroupActivity : LockingActivity(),
override fun onActionNodeFinish(actionNodeValues: ActionNodeValues) { override fun onActionNodeFinish(actionNodeValues: ActionNodeValues) {
runOnUiThread { runOnUiThread {
if (actionNodeValues.result.isSuccess) { if (actionNodeValues.result.isSuccess) {
if (actionNodeValues.newNode != null) mListNodesFragment?.addNodes(actionNodeValues.newNodes)
mListNodesFragment?.addNode(actionNodeValues.newNode)
} }
finishNodeAction()
} }
} }
} }
@@ -797,9 +749,9 @@ class GroupActivity : LockingActivity(),
override fun onActionNodeFinish(actionNodeValues: ActionNodeValues) { override fun onActionNodeFinish(actionNodeValues: ActionNodeValues) {
runOnUiThread { runOnUiThread {
if (actionNodeValues.result.isSuccess) { if (actionNodeValues.result.isSuccess) {
if (actionNodeValues.oldNode!= null && actionNodeValues.newNode != null) mListNodesFragment?.updateNodes(actionNodeValues.oldNodes, actionNodeValues.newNodes)
mListNodesFragment?.updateNode(actionNodeValues.oldNode, actionNodeValues.newNode)
} }
finishNodeAction()
} }
} }
} }
@@ -809,16 +761,12 @@ class GroupActivity : LockingActivity(),
runOnUiThread { runOnUiThread {
if (actionNodeValues.result.isSuccess) { if (actionNodeValues.result.isSuccess) {
// If the action register the position, use it to remove the entry view // Rebuold all the list the avoid bug when delete node from db sort
val positionNode = actionNodeValues.result.data?.getInt(NODE_POSITION_FOR_ACTION_NATURAL_ORDER_KEY) if (PreferencesUtil.getListSort(this@GroupActivity) == SortNodeEnum.DB) {
if (PreferencesUtil.getListSort(this@GroupActivity) == SortNodeEnum.DB mListNodesFragment?.rebuildList()
&& positionNode != null) {
mListNodesFragment?.removeNodeAt(positionNode)
} else { } else {
// Use the old Node that was the entry unchanged with the old parent // Use the old Nodes / entries unchanged with the old parent
actionNodeValues.oldNode?.let { oldNode -> mListNodesFragment?.removeNodes(actionNodeValues.oldNodes)
mListNodesFragment?.removeNode(oldNode)
}
} }
// Add trash in views list if it doesn't exists // Add trash in views list if it doesn't exists
@@ -835,6 +783,7 @@ class GroupActivity : LockingActivity(),
} }
} }
} }
finishNodeAction()
} }
} }
} }

View File

@@ -366,18 +366,34 @@ class ListNodesFragment : StylishFragment(), SortDialogFragment.SortSelectionLis
mAdapter?.addNode(newNode) mAdapter?.addNode(newNode)
} }
fun addNodes(newNodes: List<NodeVersioned>) {
mAdapter?.addNodes(newNodes)
}
fun updateNode(oldNode: NodeVersioned, newNode: NodeVersioned? = null) { fun updateNode(oldNode: NodeVersioned, newNode: NodeVersioned? = null) {
mAdapter?.updateNode(oldNode, newNode ?: oldNode) mAdapter?.updateNode(oldNode, newNode ?: oldNode)
} }
fun updateNodes(oldNodes: List<NodeVersioned>, newNodes: List<NodeVersioned>) {
mAdapter?.updateNodes(oldNodes, newNodes)
}
fun removeNode(pwNode: NodeVersioned) { fun removeNode(pwNode: NodeVersioned) {
mAdapter?.removeNode(pwNode) mAdapter?.removeNode(pwNode)
} }
fun removeNodes(nodes: List<NodeVersioned>) {
mAdapter?.removeNodes(nodes)
}
fun removeNodeAt(position: Int) { fun removeNodeAt(position: Int) {
mAdapter?.removeNodeAt(position) mAdapter?.removeNodeAt(position)
} }
fun removeNodesAt(positions: IntArray) {
mAdapter?.removeNodesAt(positions)
}
/** /**
* Callback listener to redefine to do an action when a node is click * Callback listener to redefine to do an action when a node is click
*/ */

View File

@@ -156,6 +156,14 @@ class NodeAdapter
nodeSortedList.add(node) nodeSortedList.add(node)
} }
/**
* Add nodes to the list
* @param nodes Nodes to add
*/
fun addNodes(nodes: List<NodeVersioned>) {
nodeSortedList.addAll(nodes)
}
/** /**
* Remove a node in the list * Remove a node in the list
* @param node Node to delete * @param node Node to delete
@@ -164,6 +172,16 @@ class NodeAdapter
nodeSortedList.remove(node) nodeSortedList.remove(node)
} }
/**
* Remove nodes in the list
* @param nodes Nodes to delete
*/
fun removeNodes(nodes: List<NodeVersioned>) {
nodes.forEach { node ->
nodeSortedList.remove(node)
}
}
/** /**
* Remove a node at [position] in the list * Remove a node at [position] in the list
*/ */
@@ -173,6 +191,18 @@ class NodeAdapter
notifyItemRangeChanged(position, nodeSortedList.size() - position) notifyItemRangeChanged(position, nodeSortedList.size() - position)
} }
/**
* Remove nodes in the list by [positions]
* Note : algorithm remove the higher position at each iteration
*/
fun removeNodesAt(positions: IntArray) {
val positionsSortDescending = positions.toMutableList()
positionsSortDescending.sortDescending()
positionsSortDescending.forEach {
removeNodeAt(it)
}
}
/** /**
* Update a node in the list * Update a node in the list
* @param oldNode Node before the update * @param oldNode Node before the update
@@ -185,6 +215,20 @@ class NodeAdapter
nodeSortedList.endBatchedUpdates() nodeSortedList.endBatchedUpdates()
} }
/**
* Update nodes in the list
* @param oldNodes Nodes before the update
* @param newNodes Node after the update
*/
fun updateNodes(oldNodes: List<NodeVersioned>, newNodes: List<NodeVersioned>) {
nodeSortedList.beginBatchedUpdates()
oldNodes.forEach { oldNode ->
nodeSortedList.remove(oldNode)
}
nodeSortedList.addAll(newNodes)
nodeSortedList.endBatchedUpdates()
}
fun notifyNodeChanged(node: NodeVersioned) { fun notifyNodeChanged(node: NodeVersioned) {
notifyItemChanged(nodeSortedList.indexOf(node)) notifyItemChanged(nodeSortedList.indexOf(node))
} }

View File

@@ -45,8 +45,4 @@ abstract class ActionNodeDatabaseRunnable(
super.onFinishRun(result) super.onFinishRun(result)
} }
companion object {
const val NODE_POSITION_FOR_ACTION_NATURAL_ORDER_KEY = "NODE_POSITION_FOR_ACTION_NATURAL_ORDER_KEY"
}
} }

View File

@@ -23,6 +23,7 @@ import androidx.fragment.app.FragmentActivity
import com.kunzisoft.keepass.database.element.Database import com.kunzisoft.keepass.database.element.Database
import com.kunzisoft.keepass.database.element.EntryVersioned import com.kunzisoft.keepass.database.element.EntryVersioned
import com.kunzisoft.keepass.database.element.GroupVersioned import com.kunzisoft.keepass.database.element.GroupVersioned
import com.kunzisoft.keepass.database.element.NodeVersioned
class AddEntryRunnable constructor( class AddEntryRunnable constructor(
context: FragmentActivity, context: FragmentActivity,
@@ -45,6 +46,10 @@ class AddEntryRunnable constructor(
database.removeEntryFrom(mNewEntry, it) database.removeEntryFrom(mNewEntry, it)
} }
} }
return ActionNodeValues(result, null, mNewEntry)
val oldNodesReturn = ArrayList<NodeVersioned>()
val newNodesReturn = ArrayList<NodeVersioned>()
newNodesReturn.add(mNewEntry)
return ActionNodeValues(result, oldNodesReturn, newNodesReturn)
} }
} }

View File

@@ -22,6 +22,7 @@ package com.kunzisoft.keepass.database.action.node
import androidx.fragment.app.FragmentActivity import androidx.fragment.app.FragmentActivity
import com.kunzisoft.keepass.database.element.Database import com.kunzisoft.keepass.database.element.Database
import com.kunzisoft.keepass.database.element.GroupVersioned import com.kunzisoft.keepass.database.element.GroupVersioned
import com.kunzisoft.keepass.database.element.NodeVersioned
class AddGroupRunnable constructor( class AddGroupRunnable constructor(
context: FragmentActivity, context: FragmentActivity,
@@ -42,6 +43,10 @@ class AddGroupRunnable constructor(
if (!result.isSuccess) { if (!result.isSuccess) {
database.removeGroupFrom(mNewGroup, mParent) database.removeGroupFrom(mNewGroup, mParent)
} }
return ActionNodeValues(result, null, mNewGroup)
val oldNodesReturn = ArrayList<NodeVersioned>()
val newNodesReturn = ArrayList<NodeVersioned>()
newNodesReturn.add(mNewGroup)
return ActionNodeValues(result, oldNodesReturn, newNodesReturn)
} }
} }

View File

@@ -24,13 +24,13 @@ import com.kunzisoft.keepass.tasks.ActionRunnable
/** /**
* Callback method who return the node(s) modified after an action * Callback method who return the node(s) modified after an action
* - Add : @param oldNode NULL, @param newNode CreatedNode * - Add : @param oldNodes empty, @param newNodes CreatedNodes
* - Copy : @param oldNode NodeToCopy, @param newNode NodeCopied * - Copy : @param oldNodes NodesToCopy, @param newNodes NodesCopied
* - Delete : @param oldNode NodeToDelete, @param NULL * - Delete : @param oldNodes NodesToDelete, @param newNodes empty
* - Move : @param oldNode NULL, @param NodeToMove * - Move : @param oldNodes empty, @param newNodes NodesToMove
* - Update : @param oldNode NodeToUpdate, @param NodeUpdated * - Update : @param oldNodes NodesToUpdate, @param newNodes NodesUpdated
*/ */
data class ActionNodeValues(val result: ActionRunnable.Result, val oldNode: NodeVersioned?, val newNode: NodeVersioned?) class ActionNodeValues(val result: ActionRunnable.Result, val oldNodes: List<NodeVersioned>, val newNodes: List<NodeVersioned>)
abstract class AfterActionNodeFinishRunnable { abstract class AfterActionNodeFinishRunnable {
abstract fun onActionNodeFinish(actionNodeValues: ActionNodeValues) abstract fun onActionNodeFinish(actionNodeValues: ActionNodeValues)

View File

@@ -1,76 +0,0 @@
/*
* Copyright 2019 Jeremy Jamet / Kunzisoft.
*
* This file is part of KeePass DX.
*
* KeePass DX 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.
*
* KeePass DX 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 KeePass DX. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.kunzisoft.keepass.database.action.node
import androidx.fragment.app.FragmentActivity
import android.util.Log
import com.kunzisoft.keepass.R
import com.kunzisoft.keepass.database.element.Database
import com.kunzisoft.keepass.database.element.EntryVersioned
import com.kunzisoft.keepass.database.element.GroupVersioned
class CopyEntryRunnable constructor(
context: FragmentActivity,
database: Database,
private val mEntryToCopy: EntryVersioned,
private val mNewParent: GroupVersioned,
afterAddNodeRunnable: AfterActionNodeFinishRunnable?,
save: Boolean)
: ActionNodeDatabaseRunnable(context, database, afterAddNodeRunnable, save) {
private var mEntryCopied: EntryVersioned? = null
override fun nodeAction() {
// Condition
var conditionAccepted = true
if(mNewParent == database.rootGroup && !database.rootCanContainsEntry())
conditionAccepted = false
if (conditionAccepted) {
// Update entry with new values
mNewParent.touch(modified = false, touchParents = true)
mEntryCopied = database.copyEntryTo(mEntryToCopy, mNewParent)
} else {
// Only finish thread
throw Exception(context.getString(R.string.error_copy_entry_here))
}
mEntryCopied?.apply {
touch(modified = true, touchParents = true)
} ?: Log.e(TAG, "Unable to create a copy of the entry")
}
override fun nodeFinish(result: Result): ActionNodeValues {
if (!result.isSuccess) {
// If we fail to save, try to delete the copy
try {
mEntryCopied?.let {
database.deleteEntry(it)
}
} catch (e: Exception) {
Log.i(TAG, "Unable to delete the copied entry")
}
}
return ActionNodeValues(result, mEntryToCopy, mEntryCopied)
}
companion object {
private val TAG = CopyEntryRunnable::class.java.name
}
}

View File

@@ -0,0 +1,79 @@
/*
* Copyright 2019 Jeremy Jamet / Kunzisoft.
*
* This file is part of KeePass DX.
*
* KeePass DX 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.
*
* KeePass DX 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 KeePass DX. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.kunzisoft.keepass.database.action.node
import android.util.Log
import androidx.fragment.app.FragmentActivity
import com.kunzisoft.keepass.R
import com.kunzisoft.keepass.database.element.*
class CopyNodesRunnable constructor(
context: FragmentActivity,
database: Database,
private val mNodesToCopy: List<NodeVersioned>,
private val mNewParent: GroupVersioned,
afterAddNodeRunnable: AfterActionNodeFinishRunnable?,
save: Boolean)
: ActionNodeDatabaseRunnable(context, database, afterAddNodeRunnable, save) {
private var mEntriesCopied = ArrayList<EntryVersioned>()
override fun nodeAction() {
mNodesToCopy.forEach { currentNode ->
when (currentNode.type) {
Type.GROUP -> Log.e(TAG, "Copy not allowed for group")
Type.ENTRY -> {
// Root can contains entry
if (mNewParent != database.rootGroup || database.rootCanContainsEntry()) {
// Update entry with new values
mNewParent.touch(modified = false, touchParents = true)
database.copyEntryTo(currentNode as EntryVersioned, mNewParent)?.let { entryCopied ->
entryCopied.touch(modified = true, touchParents = true)
mEntriesCopied.add(entryCopied)
} ?: Log.e(TAG, "Unable to create a copy of the entry")
} else {
// Only finish thread
throw Exception(context.getString(R.string.error_copy_entry_here))
}
}
}
}
}
override fun nodeFinish(result: Result): ActionNodeValues {
if (!result.isSuccess) {
// If we fail to save, try to delete the copy
mEntriesCopied.forEach {
try {
database.deleteEntry(it)
} catch (e: Exception) {
Log.i(TAG, "Unable to delete the copied entry")
}
}
}
return ActionNodeValues(result, mNodesToCopy, mEntriesCopied)
}
companion object {
private val TAG = CopyNodesRunnable::class.java.name
}
}

View File

@@ -1,85 +0,0 @@
/*
* Copyright 2019 Jeremy Jamet / Kunzisoft.
*
* This file is part of KeePass DX.
*
* KeePass DX 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.
*
* KeePass DX 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 KeePass DX. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.kunzisoft.keepass.database.action.node
import android.os.Bundle
import androidx.fragment.app.FragmentActivity
import com.kunzisoft.keepass.database.element.Database
import com.kunzisoft.keepass.database.element.EntryVersioned
import com.kunzisoft.keepass.database.element.GroupVersioned
class DeleteEntryRunnable constructor(
context: FragmentActivity,
database: Database,
private val mEntryToDelete: EntryVersioned,
finishRunnable: AfterActionNodeFinishRunnable?,
save: Boolean)
: ActionNodeDatabaseRunnable(context, database, finishRunnable, save) {
private var mParent: GroupVersioned? = null
private var mCanRecycle: Boolean = false
private var mEntryToDeleteBackup: EntryVersioned? = null
private var mNodePosition: Int? = null
override fun nodeAction() {
mParent = mEntryToDelete.parent
mParent?.touch(modified = false, touchParents = true)
// Get the node position
mNodePosition = mEntryToDelete.nodePositionInParent
// Create a copy to keep the old ref and remove it visually
mEntryToDeleteBackup = EntryVersioned(mEntryToDelete)
// Remove Entry from parent
mCanRecycle = database.canRecycle(mEntryToDelete)
if (mCanRecycle) {
database.recycle(mEntryToDelete, context.resources)
} else {
database.deleteEntry(mEntryToDelete)
}
}
override fun nodeFinish(result: Result): ActionNodeValues {
if (!result.isSuccess) {
mParent?.let {
if (mCanRecycle) {
database.undoRecycle(mEntryToDelete, it)
} else {
database.undoDeleteEntry(mEntryToDelete, it)
}
}
}
// Add position in bundle to delete the node in view
mNodePosition?.let { position ->
result.data = Bundle().apply {
putInt(NODE_POSITION_FOR_ACTION_NATURAL_ORDER_KEY, position )
}
} ?: run {
result.data?.remove(NODE_POSITION_FOR_ACTION_NATURAL_ORDER_KEY)
}
// Return a copy of unchanged entry as old param
// and entry deleted or moved in recycle bin as new param
return ActionNodeValues(result, mEntryToDeleteBackup, mEntryToDelete)
}
}

View File

@@ -1,84 +0,0 @@
/*
* Copyright 2019 Jeremy Jamet / Kunzisoft.
*
* This file is part of KeePass DX.
*
* KeePass DX 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.
*
* KeePass DX 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 KeePass DX. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.kunzisoft.keepass.database.action.node
import android.os.Bundle
import androidx.fragment.app.FragmentActivity
import com.kunzisoft.keepass.database.element.Database
import com.kunzisoft.keepass.database.element.GroupVersioned
class DeleteGroupRunnable(context: FragmentActivity,
database: Database,
private val mGroupToDelete: GroupVersioned,
finish: AfterActionNodeFinishRunnable,
save: Boolean) : ActionNodeDatabaseRunnable(context, database, finish, save) {
private var mParent: GroupVersioned? = null
private var mRecycle: Boolean = false
private var mGroupToDeleteBackup: GroupVersioned? = null
private var mNodePosition: Int? = null
override fun nodeAction() {
mParent = mGroupToDelete.parent
mParent?.touch(modified = false, touchParents = true)
// Get the node position
mNodePosition = mGroupToDelete.nodePositionInParent
// Create a copy to keep the old ref and remove it visually
mGroupToDeleteBackup = GroupVersioned(mGroupToDelete)
// Remove Group from parent
mRecycle = database.canRecycle(mGroupToDelete)
if (mRecycle) {
database.recycle(mGroupToDelete, context.resources)
} else {
database.deleteGroup(mGroupToDelete)
}
}
override fun nodeFinish(result: Result): ActionNodeValues {
if (!result.isSuccess) {
if (mRecycle) {
mParent?.let {
database.undoRecycle(mGroupToDelete, it)
}
}
// else {
// Let's not bother recovering from a failure to save a deleted tree. It is too much work.
// TODO database.undoDeleteGroupFrom(mGroup, mParent);
// }
}
// Add position in bundle to delete the node in view
mNodePosition?.let { position ->
result.data = Bundle().apply {
putInt(NODE_POSITION_FOR_ACTION_NATURAL_ORDER_KEY, position )
}
} ?: run {
result.data?.remove(NODE_POSITION_FOR_ACTION_NATURAL_ORDER_KEY)
}
// Return a copy of unchanged group as old param
// and group deleted or moved in recycle bin as new param
return ActionNodeValues(result, mGroupToDeleteBackup, mGroupToDelete)
}
}

View File

@@ -0,0 +1,94 @@
/*
* Copyright 2019 Jeremy Jamet / Kunzisoft.
*
* This file is part of KeePass DX.
*
* KeePass DX 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.
*
* KeePass DX 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 KeePass DX. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.kunzisoft.keepass.database.action.node
import androidx.fragment.app.FragmentActivity
import com.kunzisoft.keepass.database.element.*
class DeleteNodesRunnable(context: FragmentActivity,
database: Database,
private val mNodesToDelete: List<NodeVersioned>,
finish: AfterActionNodeFinishRunnable,
save: Boolean) : ActionNodeDatabaseRunnable(context, database, finish, save) {
private var mParent: GroupVersioned? = null
private var mCanRecycle: Boolean = false
private var mNodesToDeleteBackup = ArrayList<NodeVersioned>()
override fun nodeAction() {
mNodesToDelete.forEach { currentNode ->
mParent = currentNode.parent
mParent?.touch(modified = false, touchParents = true)
when (currentNode.type) {
Type.GROUP -> {
// Create a copy to keep the old ref and remove it visually
mNodesToDeleteBackup.add(GroupVersioned(currentNode as GroupVersioned))
// Remove Node from parent
mCanRecycle = database.canRecycle(currentNode)
if (mCanRecycle) {
database.recycle(currentNode, context.resources)
} else {
database.deleteGroup(currentNode)
}
}
Type.ENTRY -> {
// Create a copy to keep the old ref and remove it visually
mNodesToDeleteBackup.add(EntryVersioned(currentNode as EntryVersioned))
// Remove Node from parent
mCanRecycle = database.canRecycle(currentNode)
if (mCanRecycle) {
database.recycle(currentNode, context.resources)
} else {
database.deleteEntry(currentNode)
}
}
}
}
}
override fun nodeFinish(result: Result): ActionNodeValues {
if (!result.isSuccess) {
if (mCanRecycle) {
mParent?.let {
mNodesToDeleteBackup.forEach { backupNode ->
when (backupNode.type) {
Type.GROUP -> {
database.undoRecycle(backupNode as GroupVersioned, it)
}
Type.ENTRY -> {
database.undoRecycle(backupNode as EntryVersioned, it)
}
}
}
}
}
// else {
// Let's not bother recovering from a failure to save a deleted tree. It is too much work.
// TODO database.undoDeleteGroupFrom(mGroup, mParent);
// }
}
// Return a copy of unchanged nodes as old param
// and nodes deleted or moved in recycle bin as new param
return ActionNodeValues(result, mNodesToDeleteBackup, mNodesToDelete)
}
}

View File

@@ -1,77 +0,0 @@
/*
* Copyright 2019 Jeremy Jamet / Kunzisoft.
*
* This file is part of KeePass DX.
*
* KeePass DX 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.
*
* KeePass DX 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 KeePass DX. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.kunzisoft.keepass.database.action.node
import androidx.fragment.app.FragmentActivity
import android.util.Log
import com.kunzisoft.keepass.R
import com.kunzisoft.keepass.database.element.Database
import com.kunzisoft.keepass.database.element.EntryVersioned
import com.kunzisoft.keepass.database.element.GroupVersioned
class MoveEntryRunnable constructor(
context: FragmentActivity,
database: Database,
private val mEntryToMove: EntryVersioned?,
private val mNewParent: GroupVersioned,
afterAddNodeRunnable: AfterActionNodeFinishRunnable?,
save: Boolean)
: ActionNodeDatabaseRunnable(context, database, afterAddNodeRunnable, save) {
private var mOldParent: GroupVersioned? = null
override fun nodeAction() {
// Move entry in new parent
mEntryToMove?.let {
mOldParent = it.parent
// Condition
var conditionAccepted = true
if(mNewParent == database.rootGroup && !database.rootCanContainsEntry())
conditionAccepted = false
// Move only if the parent change
if (mOldParent != mNewParent && conditionAccepted) {
database.moveEntryTo(it, mNewParent)
} else {
// Only finish thread
throw Exception(context.getString(R.string.error_move_entry_here))
}
it.touch(modified = true, touchParents = true)
} ?: Log.e(TAG, "Unable to create a copy of the entry")
}
override fun nodeFinish(result: Result): ActionNodeValues {
if (!result.isSuccess) {
// If we fail to save, try to remove in the first place
try {
if (mEntryToMove != null && mOldParent != null)
database.moveEntryTo(mEntryToMove, mOldParent!!)
} catch (e: Exception) {
Log.i(TAG, "Unable to replace the entry")
}
}
return ActionNodeValues(result, null, mEntryToMove)
}
companion object {
private val TAG = MoveEntryRunnable::class.java.name
}
}

View File

@@ -1,71 +0,0 @@
/*
* Copyright 2019 Jeremy Jamet / Kunzisoft.
*
* This file is part of KeePass DX.
*
* KeePass DX 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.
*
* KeePass DX 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 KeePass DX. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.kunzisoft.keepass.database.action.node
import androidx.fragment.app.FragmentActivity
import android.util.Log
import com.kunzisoft.keepass.R
import com.kunzisoft.keepass.database.element.Database
import com.kunzisoft.keepass.database.element.GroupVersioned
class MoveGroupRunnable constructor(
context: FragmentActivity,
database: Database,
private val mGroupToMove: GroupVersioned?,
private val mNewParent: GroupVersioned,
afterAddNodeRunnable: AfterActionNodeFinishRunnable?,
save: Boolean)
: ActionNodeDatabaseRunnable(context, database, afterAddNodeRunnable, save) {
private var mOldParent: GroupVersioned? = null
override fun nodeAction() {
mGroupToMove?.let {
mOldParent = it.parent
// Move group in new parent if not in the current group
if (mGroupToMove != mNewParent && !mNewParent.isContainedIn(mGroupToMove)) {
database.moveGroupTo(mGroupToMove, mNewParent)
mGroupToMove.touch(modified = true, touchParents = true)
finishRun(true)
} else {
// Only finish thread
throw Exception(context.getString(R.string.error_move_folder_in_itself))
}
} ?: Log.e(TAG, "Unable to create a copy of the group")
}
override fun nodeFinish(result: Result): ActionNodeValues {
if (!result.isSuccess) {
// If we fail to save, try to move in the first place
try {
if (mGroupToMove != null && mOldParent != null)
database.moveGroupTo(mGroupToMove, mOldParent!!)
} catch (e: Exception) {
Log.i(TAG, "Unable to replace the group")
}
}
return ActionNodeValues(result, null, mGroupToMove)
}
companion object {
private val TAG = MoveGroupRunnable::class.java.name
}
}

View File

@@ -0,0 +1,98 @@
/*
* Copyright 2019 Jeremy Jamet / Kunzisoft.
*
* This file is part of KeePass DX.
*
* KeePass DX 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.
*
* KeePass DX 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 KeePass DX. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.kunzisoft.keepass.database.action.node
import androidx.fragment.app.FragmentActivity
import android.util.Log
import com.kunzisoft.keepass.R
import com.kunzisoft.keepass.database.element.*
class MoveNodesRunnable constructor(
context: FragmentActivity,
database: Database,
private val mNodesToMove: List<NodeVersioned>,
private val mNewParent: GroupVersioned,
afterAddNodeRunnable: AfterActionNodeFinishRunnable?,
save: Boolean)
: ActionNodeDatabaseRunnable(context, database, afterAddNodeRunnable, save) {
private var mOldParent: GroupVersioned? = null
override fun nodeAction() {
mNodesToMove.forEach { nodeToMove ->
// Move node in new parent
mOldParent = nodeToMove.parent
when (nodeToMove.type) {
Type.GROUP -> {
val groupToMove = nodeToMove as GroupVersioned
// Move group in new parent if not in the current group
if (groupToMove != mNewParent
&& !mNewParent.isContainedIn(groupToMove)) {
database.moveGroupTo(groupToMove, mNewParent)
finishRun(true)
} else {
// Only finish thread
throw Exception(context.getString(R.string.error_move_folder_in_itself))
}
}
Type.ENTRY -> {
val entryToMove = nodeToMove as EntryVersioned
// Move only if the parent change
if (mOldParent != mNewParent
// and root can contains entry
&& (mNewParent != database.rootGroup || database.rootCanContainsEntry())) {
database.moveEntryTo(entryToMove, mNewParent)
finishRun(true)
} else {
// Only finish thread
throw Exception(context.getString(R.string.error_move_entry_here))
}
}
}
nodeToMove.touch(modified = true, touchParents = true)
}
}
override fun nodeFinish(result: Result): ActionNodeValues {
if (!result.isSuccess) {
try {
mNodesToMove.forEach { nodeToMove ->
// If we fail to save, try to move in the first place
if (mOldParent != null) {
when (nodeToMove.type) {
Type.GROUP -> database.moveGroupTo(nodeToMove as GroupVersioned, mOldParent!!)
Type.ENTRY -> database.moveEntryTo(nodeToMove as EntryVersioned, mOldParent!!)
}
}
}
} catch (e: Exception) {
Log.i(TAG, "Unable to replace the node")
}
}
return ActionNodeValues(result, ArrayList(), mNodesToMove)
}
companion object {
private val TAG = MoveNodesRunnable::class.java.name
}
}

View File

@@ -22,6 +22,7 @@ package com.kunzisoft.keepass.database.action.node
import androidx.fragment.app.FragmentActivity import androidx.fragment.app.FragmentActivity
import com.kunzisoft.keepass.database.element.Database import com.kunzisoft.keepass.database.element.Database
import com.kunzisoft.keepass.database.element.EntryVersioned import com.kunzisoft.keepass.database.element.EntryVersioned
import com.kunzisoft.keepass.database.element.NodeVersioned
class UpdateEntryRunnable constructor( class UpdateEntryRunnable constructor(
context: FragmentActivity, context: FragmentActivity,
@@ -55,6 +56,11 @@ class UpdateEntryRunnable constructor(
mOldEntry.updateWith(it) mOldEntry.updateWith(it)
} }
} }
return ActionNodeValues(result, mOldEntry, mNewEntry)
val oldNodesReturn = ArrayList<NodeVersioned>()
oldNodesReturn.add(mOldEntry)
val newNodesReturn = ArrayList<NodeVersioned>()
newNodesReturn.add(mNewEntry)
return ActionNodeValues(result, oldNodesReturn, newNodesReturn)
} }
} }

View File

@@ -22,6 +22,7 @@ package com.kunzisoft.keepass.database.action.node
import androidx.fragment.app.FragmentActivity import androidx.fragment.app.FragmentActivity
import com.kunzisoft.keepass.database.element.Database import com.kunzisoft.keepass.database.element.Database
import com.kunzisoft.keepass.database.element.GroupVersioned import com.kunzisoft.keepass.database.element.GroupVersioned
import com.kunzisoft.keepass.database.element.NodeVersioned
class UpdateGroupRunnable constructor( class UpdateGroupRunnable constructor(
context: FragmentActivity, context: FragmentActivity,
@@ -46,6 +47,11 @@ class UpdateGroupRunnable constructor(
// If we fail to save, back out changes to global structure // If we fail to save, back out changes to global structure
mOldGroup.updateWith(mBackupGroup) mOldGroup.updateWith(mBackupGroup)
} }
return ActionNodeValues(result, mOldGroup, mNewGroup)
val oldNodesReturn = ArrayList<NodeVersioned>()
oldNodesReturn.add(mOldGroup)
val newNodesReturn = ArrayList<NodeVersioned>()
newNodesReturn.add(mNewGroup)
return ActionNodeValues(result, oldNodesReturn, newNodesReturn)
} }
} }

View File

@@ -5,9 +5,9 @@ interface NodeVersioned: PwNodeInterface<GroupVersioned> {
val nodePositionInParent: Int val nodePositionInParent: Int
get() { get() {
parent?.getChildren(true)?.let { children -> parent?.getChildren(true)?.let { children ->
for ((i, child) in children.withIndex()) { children.forEachIndexed { index, nodeVersioned ->
if (child == this) if (nodeVersioned == this)
return i return index
} }
} }
return -1 return -1