mirror of
https://github.com/Kunzisoft/KeePassDX.git
synced 2025-12-04 15:49:33 +01:00
First merge implementation
This commit is contained in:
@@ -415,6 +415,9 @@ class EntryActivity : DatabaseLockActivity() {
|
|||||||
R.id.menu_save_database -> {
|
R.id.menu_save_database -> {
|
||||||
saveDatabase()
|
saveDatabase()
|
||||||
}
|
}
|
||||||
|
R.id.menu_merge_database -> {
|
||||||
|
mergeDatabase()
|
||||||
|
}
|
||||||
R.id.menu_reload_database -> {
|
R.id.menu_reload_database -> {
|
||||||
reloadDatabase()
|
reloadDatabase()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1070,6 +1070,10 @@ class GroupActivity : DatabaseLockActivity(),
|
|||||||
saveDatabase()
|
saveDatabase()
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
R.id.menu_merge_database -> {
|
||||||
|
mergeDatabase()
|
||||||
|
return true
|
||||||
|
}
|
||||||
R.id.menu_reload_database -> {
|
R.id.menu_reload_database -> {
|
||||||
reloadDatabase()
|
reloadDatabase()
|
||||||
return true
|
return true
|
||||||
|
|||||||
@@ -87,6 +87,10 @@ abstract class DatabaseLockActivity : DatabaseModeActivity(),
|
|||||||
mDatabaseTaskProvider?.startDatabaseSave(save)
|
mDatabaseTaskProvider?.startDatabaseSave(save)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
mDatabaseViewModel.mergeDatabase.observe(this) { fixDuplicateUuid ->
|
||||||
|
mDatabaseTaskProvider?.startDatabaseMerge(fixDuplicateUuid)
|
||||||
|
}
|
||||||
|
|
||||||
mDatabaseViewModel.reloadDatabase.observe(this) { fixDuplicateUuid ->
|
mDatabaseViewModel.reloadDatabase.observe(this) { fixDuplicateUuid ->
|
||||||
mDatabaseTaskProvider?.startDatabaseReload(fixDuplicateUuid)
|
mDatabaseTaskProvider?.startDatabaseReload(fixDuplicateUuid)
|
||||||
}
|
}
|
||||||
@@ -212,6 +216,7 @@ abstract class DatabaseLockActivity : DatabaseModeActivity(),
|
|||||||
) {
|
) {
|
||||||
super.onDatabaseActionFinished(database, actionTask, result)
|
super.onDatabaseActionFinished(database, actionTask, result)
|
||||||
when (actionTask) {
|
when (actionTask) {
|
||||||
|
DatabaseTaskNotificationService.ACTION_DATABASE_MERGE_TASK,
|
||||||
DatabaseTaskNotificationService.ACTION_DATABASE_RELOAD_TASK -> {
|
DatabaseTaskNotificationService.ACTION_DATABASE_RELOAD_TASK -> {
|
||||||
// Reload the current activity
|
// Reload the current activity
|
||||||
if (result.isSuccess) {
|
if (result.isSuccess) {
|
||||||
@@ -254,6 +259,10 @@ abstract class DatabaseLockActivity : DatabaseModeActivity(),
|
|||||||
mDatabaseTaskProvider?.startDatabaseSave(true)
|
mDatabaseTaskProvider?.startDatabaseSave(true)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun mergeDatabase() {
|
||||||
|
mDatabaseTaskProvider?.startDatabaseMerge(false)
|
||||||
|
}
|
||||||
|
|
||||||
fun reloadDatabase() {
|
fun reloadDatabase() {
|
||||||
mDatabaseTaskProvider?.startDatabaseReload(false)
|
mDatabaseTaskProvider?.startDatabaseReload(false)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -53,6 +53,7 @@ import com.kunzisoft.keepass.services.DatabaseTaskNotificationService.Companion.
|
|||||||
import com.kunzisoft.keepass.services.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_DELETE_ENTRY_HISTORY
|
import com.kunzisoft.keepass.services.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_DELETE_ENTRY_HISTORY
|
||||||
import com.kunzisoft.keepass.services.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_DELETE_NODES_TASK
|
import com.kunzisoft.keepass.services.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_DELETE_NODES_TASK
|
||||||
import com.kunzisoft.keepass.services.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_LOAD_TASK
|
import com.kunzisoft.keepass.services.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_LOAD_TASK
|
||||||
|
import com.kunzisoft.keepass.services.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_MERGE_TASK
|
||||||
import com.kunzisoft.keepass.services.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_MOVE_NODES_TASK
|
import com.kunzisoft.keepass.services.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_MOVE_NODES_TASK
|
||||||
import com.kunzisoft.keepass.services.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_RELOAD_TASK
|
import com.kunzisoft.keepass.services.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_RELOAD_TASK
|
||||||
import com.kunzisoft.keepass.services.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_REMOVE_UNLINKED_DATA_TASK
|
import com.kunzisoft.keepass.services.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_REMOVE_UNLINKED_DATA_TASK
|
||||||
@@ -354,6 +355,13 @@ class DatabaseTaskProvider {
|
|||||||
, ACTION_DATABASE_LOAD_TASK)
|
, ACTION_DATABASE_LOAD_TASK)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun startDatabaseMerge(fixDuplicateUuid: Boolean) {
|
||||||
|
start(Bundle().apply {
|
||||||
|
putBoolean(DatabaseTaskNotificationService.FIX_DUPLICATE_UUID_KEY, fixDuplicateUuid)
|
||||||
|
}
|
||||||
|
, ACTION_DATABASE_MERGE_TASK)
|
||||||
|
}
|
||||||
|
|
||||||
fun startDatabaseReload(fixDuplicateUuid: Boolean) {
|
fun startDatabaseReload(fixDuplicateUuid: Boolean) {
|
||||||
start(Bundle().apply {
|
start(Bundle().apply {
|
||||||
putBoolean(DatabaseTaskNotificationService.FIX_DUPLICATE_UUID_KEY, fixDuplicateUuid)
|
putBoolean(DatabaseTaskNotificationService.FIX_DUPLICATE_UUID_KEY, fixDuplicateUuid)
|
||||||
|
|||||||
@@ -0,0 +1,70 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2021 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 <http://www.gnu.org/licenses/>.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
package com.kunzisoft.keepass.database.action
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import com.kunzisoft.keepass.database.element.Database
|
||||||
|
import com.kunzisoft.keepass.database.element.binary.BinaryData
|
||||||
|
import com.kunzisoft.keepass.database.element.binary.LoadedKey
|
||||||
|
import com.kunzisoft.keepass.database.exception.LoadDatabaseException
|
||||||
|
import com.kunzisoft.keepass.settings.PreferencesUtil
|
||||||
|
import com.kunzisoft.keepass.tasks.ActionRunnable
|
||||||
|
import com.kunzisoft.keepass.tasks.ProgressTaskUpdater
|
||||||
|
import com.kunzisoft.keepass.utils.UriUtil
|
||||||
|
|
||||||
|
class MergeDatabaseRunnable(private val context: Context,
|
||||||
|
private val mDatabase: Database,
|
||||||
|
private val progressTaskUpdater: ProgressTaskUpdater?,
|
||||||
|
private val mLoadDatabaseResult: ((Result) -> Unit)?)
|
||||||
|
: ActionRunnable() {
|
||||||
|
|
||||||
|
private var tempCipherKey: LoadedKey? = null
|
||||||
|
|
||||||
|
override fun onStartRun() {
|
||||||
|
tempCipherKey = mDatabase.binaryCache.loadedCipherKey
|
||||||
|
mDatabase.wasReloaded = true
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onActionRun() {
|
||||||
|
try {
|
||||||
|
mDatabase.mergeData(context.contentResolver,
|
||||||
|
UriUtil.getBinaryDir(context),
|
||||||
|
{ memoryWanted ->
|
||||||
|
BinaryData.canMemoryBeAllocatedInRAM(context, memoryWanted)
|
||||||
|
},
|
||||||
|
tempCipherKey ?: LoadedKey.generateNewCipherKey(),
|
||||||
|
progressTaskUpdater)
|
||||||
|
} catch (e: LoadDatabaseException) {
|
||||||
|
setError(e)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (result.isSuccess) {
|
||||||
|
// Register the current time to init the lock timer
|
||||||
|
PreferencesUtil.saveCurrentTime(context)
|
||||||
|
} else {
|
||||||
|
tempCipherKey = null
|
||||||
|
mDatabase.clearAndClose(context)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onFinishRun() {
|
||||||
|
mLoadDatabaseResult?.invoke(result)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -52,6 +52,7 @@ import com.kunzisoft.keepass.database.file.input.DatabaseInputKDB
|
|||||||
import com.kunzisoft.keepass.database.file.input.DatabaseInputKDBX
|
import com.kunzisoft.keepass.database.file.input.DatabaseInputKDBX
|
||||||
import com.kunzisoft.keepass.database.file.output.DatabaseOutputKDB
|
import com.kunzisoft.keepass.database.file.output.DatabaseOutputKDB
|
||||||
import com.kunzisoft.keepass.database.file.output.DatabaseOutputKDBX
|
import com.kunzisoft.keepass.database.file.output.DatabaseOutputKDBX
|
||||||
|
import com.kunzisoft.keepass.database.merge.DatabaseKDBXMerger
|
||||||
import com.kunzisoft.keepass.database.search.SearchHelper
|
import com.kunzisoft.keepass.database.search.SearchHelper
|
||||||
import com.kunzisoft.keepass.database.search.SearchParameters
|
import com.kunzisoft.keepass.database.search.SearchParameters
|
||||||
import com.kunzisoft.keepass.icons.IconDrawableFactory
|
import com.kunzisoft.keepass.icons.IconDrawableFactory
|
||||||
@@ -625,6 +626,52 @@ class Database {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Throws(LoadDatabaseException::class)
|
||||||
|
fun mergeData(contentResolver: ContentResolver,
|
||||||
|
cacheDirectory: File,
|
||||||
|
isRAMSufficient: (memoryWanted: Long) -> Boolean,
|
||||||
|
tempCipherKey: LoadedKey,
|
||||||
|
progressTaskUpdater: ProgressTaskUpdater?) {
|
||||||
|
|
||||||
|
// New database instance to get new changes
|
||||||
|
val databaseToMerge = Database()
|
||||||
|
databaseToMerge.fileUri = this.fileUri
|
||||||
|
try {
|
||||||
|
databaseToMerge.fileUri?.let { databaseUri ->
|
||||||
|
databaseToMerge.readDatabaseStream(contentResolver, databaseUri,
|
||||||
|
{ databaseInputStream ->
|
||||||
|
DatabaseInputKDB(cacheDirectory, isRAMSufficient)
|
||||||
|
.openDatabase(databaseInputStream,
|
||||||
|
masterKey,
|
||||||
|
tempCipherKey,
|
||||||
|
progressTaskUpdater)
|
||||||
|
},
|
||||||
|
{ databaseInputStream ->
|
||||||
|
DatabaseInputKDBX(cacheDirectory, isRAMSufficient)
|
||||||
|
.openDatabase(databaseInputStream,
|
||||||
|
masterKey,
|
||||||
|
tempCipherKey,
|
||||||
|
progressTaskUpdater)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
} ?: run {
|
||||||
|
Log.e(TAG, "Database URI is null, database cannot be reloaded")
|
||||||
|
throw IODatabaseException()
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO Merge KDB
|
||||||
|
mDatabaseKDBX?.let { databaseKDBX ->
|
||||||
|
databaseToMerge.mDatabaseKDBX?.let { databaseKDBXToMerge ->
|
||||||
|
DatabaseKDBXMerger(databaseKDBX).merge(databaseKDBXToMerge)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (e: Exception) {
|
||||||
|
throw LoadDatabaseException(e)
|
||||||
|
} finally {
|
||||||
|
databaseToMerge.clearAndClose()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Throws(LoadDatabaseException::class)
|
@Throws(LoadDatabaseException::class)
|
||||||
fun reloadData(contentResolver: ContentResolver,
|
fun reloadData(contentResolver: ContentResolver,
|
||||||
cacheDirectory: File,
|
cacheDirectory: File,
|
||||||
@@ -636,20 +683,20 @@ class Database {
|
|||||||
try {
|
try {
|
||||||
fileUri?.let { oldDatabaseUri ->
|
fileUri?.let { oldDatabaseUri ->
|
||||||
readDatabaseStream(contentResolver, oldDatabaseUri,
|
readDatabaseStream(contentResolver, oldDatabaseUri,
|
||||||
{ databaseInputStream ->
|
{ databaseInputStream ->
|
||||||
DatabaseInputKDB(cacheDirectory, isRAMSufficient)
|
DatabaseInputKDB(cacheDirectory, isRAMSufficient)
|
||||||
.openDatabase(databaseInputStream,
|
.openDatabase(databaseInputStream,
|
||||||
masterKey,
|
masterKey,
|
||||||
tempCipherKey,
|
tempCipherKey,
|
||||||
progressTaskUpdater)
|
progressTaskUpdater)
|
||||||
},
|
},
|
||||||
{ databaseInputStream ->
|
{ databaseInputStream ->
|
||||||
DatabaseInputKDBX(cacheDirectory, isRAMSufficient)
|
DatabaseInputKDBX(cacheDirectory, isRAMSufficient)
|
||||||
.openDatabase(databaseInputStream,
|
.openDatabase(databaseInputStream,
|
||||||
masterKey,
|
masterKey,
|
||||||
tempCipherKey,
|
tempCipherKey,
|
||||||
progressTaskUpdater)
|
progressTaskUpdater)
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
} ?: run {
|
} ?: run {
|
||||||
Log.e(TAG, "Database URI is null, database cannot be reloaded")
|
Log.e(TAG, "Database URI is null, database cannot be reloaded")
|
||||||
|
|||||||
@@ -0,0 +1,101 @@
|
|||||||
|
package com.kunzisoft.keepass.database.merge
|
||||||
|
|
||||||
|
import com.kunzisoft.keepass.database.action.node.NodeHandler
|
||||||
|
import com.kunzisoft.keepass.database.element.database.DatabaseKDBX
|
||||||
|
import com.kunzisoft.keepass.database.element.entry.EntryKDBX
|
||||||
|
import com.kunzisoft.keepass.database.element.group.GroupKDBX
|
||||||
|
import java.io.IOException
|
||||||
|
|
||||||
|
class DatabaseKDBXMerger(private var database: DatabaseKDBX) {
|
||||||
|
|
||||||
|
fun merge(databaseToMerge: DatabaseKDBX) {
|
||||||
|
|
||||||
|
val databaseRootGroupId = database.rootGroup?.nodeId
|
||||||
|
val databaseRootGroupIdToMerge = databaseToMerge.rootGroup?.nodeId
|
||||||
|
|
||||||
|
if (databaseRootGroupId == null || databaseRootGroupIdToMerge == null) {
|
||||||
|
throw IOException("Database is not open")
|
||||||
|
}
|
||||||
|
|
||||||
|
// UUID of the root group to merge is unknown
|
||||||
|
if (database.getGroupById(databaseRootGroupIdToMerge) == null) {
|
||||||
|
// Change it to copy children database root
|
||||||
|
// TODO Test
|
||||||
|
databaseToMerge.rootGroup?.let { databaseRootGroupToMerge ->
|
||||||
|
databaseToMerge.removeGroupIndex(databaseRootGroupToMerge)
|
||||||
|
databaseRootGroupToMerge.nodeId = databaseRootGroupId
|
||||||
|
databaseToMerge.updateGroup(databaseRootGroupToMerge)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
databaseToMerge.rootGroup?.doForEachChild(
|
||||||
|
object : NodeHandler<EntryKDBX>() {
|
||||||
|
override fun operate(node: EntryKDBX): Boolean {
|
||||||
|
val entryId = node.nodeId
|
||||||
|
databaseToMerge.getEntryById(entryId)?.let {
|
||||||
|
mergeEntry(database.getEntryById(entryId), it)
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
object : NodeHandler<GroupKDBX>() {
|
||||||
|
override fun operate(node: GroupKDBX): Boolean {
|
||||||
|
val groupId = node.nodeId
|
||||||
|
databaseToMerge.getGroupById(groupId)?.let {
|
||||||
|
mergeGroup(database.getGroupById(groupId), it)
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun mergeEntry(databaseEntry: EntryKDBX?, databaseEntryToMerge: EntryKDBX) {
|
||||||
|
// Retrieve parent in current database
|
||||||
|
var parentEntry: GroupKDBX? = null
|
||||||
|
databaseEntryToMerge.parent?.nodeId?.let {
|
||||||
|
parentEntry = database.getGroupById(it)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (databaseEntry == null) {
|
||||||
|
// If entry parent to add exists and in current database
|
||||||
|
if (parentEntry != null) {
|
||||||
|
database.addEntryTo(databaseEntryToMerge, parentEntry)
|
||||||
|
}
|
||||||
|
} else if (databaseEntry.lastModificationTime.date
|
||||||
|
.before(databaseEntryToMerge.lastModificationTime.date)
|
||||||
|
) {
|
||||||
|
// Update entry with databaseEntryToMerge and merge history
|
||||||
|
database.removeEntryFrom(databaseEntry, databaseEntry.parent)
|
||||||
|
val newDatabaseEntry = EntryKDBX().apply {
|
||||||
|
updateWith(databaseEntryToMerge)
|
||||||
|
// TODO history =
|
||||||
|
}
|
||||||
|
if (parentEntry != null) {
|
||||||
|
database.addEntryTo(newDatabaseEntry, parentEntry)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun mergeGroup(databaseGroup: GroupKDBX?, databaseGroupToMerge: GroupKDBX) {
|
||||||
|
// Retrieve parent in current database
|
||||||
|
var parentGroup: GroupKDBX? = null
|
||||||
|
databaseGroupToMerge.parent?.nodeId?.let {
|
||||||
|
parentGroup = database.getGroupById(it)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (databaseGroup == null) {
|
||||||
|
// If group parent to add exists and in current database
|
||||||
|
if (parentGroup != null) {
|
||||||
|
database.addGroupTo(databaseGroupToMerge, parentGroup)
|
||||||
|
}
|
||||||
|
} else if (databaseGroup.lastModificationTime.date
|
||||||
|
.before(databaseGroupToMerge.lastModificationTime.date)
|
||||||
|
) {
|
||||||
|
database.removeGroupFrom(databaseGroup, databaseGroup.parent)
|
||||||
|
if (parentGroup != null) {
|
||||||
|
database.addGroupTo(databaseGroupToMerge, parentGroup)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -226,6 +226,7 @@ open class DatabaseTaskNotificationService : LockNotificationService(), Progress
|
|||||||
val actionRunnable: ActionRunnable? = when (intentAction) {
|
val actionRunnable: ActionRunnable? = when (intentAction) {
|
||||||
ACTION_DATABASE_CREATE_TASK -> buildDatabaseCreateActionTask(intent, database)
|
ACTION_DATABASE_CREATE_TASK -> buildDatabaseCreateActionTask(intent, database)
|
||||||
ACTION_DATABASE_LOAD_TASK -> buildDatabaseLoadActionTask(intent, database)
|
ACTION_DATABASE_LOAD_TASK -> buildDatabaseLoadActionTask(intent, database)
|
||||||
|
ACTION_DATABASE_MERGE_TASK -> buildDatabaseMergeActionTask(database)
|
||||||
ACTION_DATABASE_RELOAD_TASK -> buildDatabaseReloadActionTask(database)
|
ACTION_DATABASE_RELOAD_TASK -> buildDatabaseReloadActionTask(database)
|
||||||
ACTION_DATABASE_ASSIGN_PASSWORD_TASK -> buildDatabaseAssignPasswordActionTask(intent, database)
|
ACTION_DATABASE_ASSIGN_PASSWORD_TASK -> buildDatabaseAssignPasswordActionTask(intent, database)
|
||||||
ACTION_DATABASE_CREATE_GROUP_TASK -> buildDatabaseCreateGroupActionTask(intent, database)
|
ACTION_DATABASE_CREATE_GROUP_TASK -> buildDatabaseCreateGroupActionTask(intent, database)
|
||||||
@@ -331,6 +332,7 @@ open class DatabaseTaskNotificationService : LockNotificationService(), Progress
|
|||||||
|
|
||||||
return when (intentAction) {
|
return when (intentAction) {
|
||||||
ACTION_DATABASE_LOAD_TASK,
|
ACTION_DATABASE_LOAD_TASK,
|
||||||
|
ACTION_DATABASE_MERGE_TASK,
|
||||||
ACTION_DATABASE_RELOAD_TASK,
|
ACTION_DATABASE_RELOAD_TASK,
|
||||||
null -> {
|
null -> {
|
||||||
START_STICKY
|
START_STICKY
|
||||||
@@ -367,6 +369,7 @@ open class DatabaseTaskNotificationService : LockNotificationService(), Progress
|
|||||||
when (intentAction) {
|
when (intentAction) {
|
||||||
ACTION_DATABASE_CREATE_TASK -> R.string.creating_database
|
ACTION_DATABASE_CREATE_TASK -> R.string.creating_database
|
||||||
ACTION_DATABASE_LOAD_TASK,
|
ACTION_DATABASE_LOAD_TASK,
|
||||||
|
ACTION_DATABASE_MERGE_TASK,
|
||||||
ACTION_DATABASE_RELOAD_TASK -> R.string.loading_database
|
ACTION_DATABASE_RELOAD_TASK -> R.string.loading_database
|
||||||
ACTION_DATABASE_SAVE -> R.string.saving_database
|
ACTION_DATABASE_SAVE -> R.string.saving_database
|
||||||
else -> {
|
else -> {
|
||||||
@@ -378,6 +381,7 @@ open class DatabaseTaskNotificationService : LockNotificationService(), Progress
|
|||||||
|
|
||||||
mMessageId = when (intentAction) {
|
mMessageId = when (intentAction) {
|
||||||
ACTION_DATABASE_LOAD_TASK,
|
ACTION_DATABASE_LOAD_TASK,
|
||||||
|
ACTION_DATABASE_MERGE_TASK,
|
||||||
ACTION_DATABASE_RELOAD_TASK -> null
|
ACTION_DATABASE_RELOAD_TASK -> null
|
||||||
else -> null
|
else -> null
|
||||||
}
|
}
|
||||||
@@ -385,6 +389,7 @@ open class DatabaseTaskNotificationService : LockNotificationService(), Progress
|
|||||||
mWarningId =
|
mWarningId =
|
||||||
if (!saveAction
|
if (!saveAction
|
||||||
|| intentAction == ACTION_DATABASE_LOAD_TASK
|
|| intentAction == ACTION_DATABASE_LOAD_TASK
|
||||||
|
|| intentAction == ACTION_DATABASE_MERGE_TASK
|
||||||
|| intentAction == ACTION_DATABASE_RELOAD_TASK)
|
|| intentAction == ACTION_DATABASE_RELOAD_TASK)
|
||||||
null
|
null
|
||||||
else
|
else
|
||||||
@@ -597,6 +602,17 @@ open class DatabaseTaskNotificationService : LockNotificationService(), Progress
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun buildDatabaseMergeActionTask(database: Database): ActionRunnable {
|
||||||
|
return MergeDatabaseRunnable(
|
||||||
|
this,
|
||||||
|
database,
|
||||||
|
this
|
||||||
|
) { result ->
|
||||||
|
// No need to add each info to reload database
|
||||||
|
result.data = Bundle()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private fun buildDatabaseReloadActionTask(database: Database): ActionRunnable {
|
private fun buildDatabaseReloadActionTask(database: Database): ActionRunnable {
|
||||||
return ReloadDatabaseRunnable(
|
return ReloadDatabaseRunnable(
|
||||||
this,
|
this,
|
||||||
@@ -907,6 +923,7 @@ open class DatabaseTaskNotificationService : LockNotificationService(), Progress
|
|||||||
|
|
||||||
const val ACTION_DATABASE_CREATE_TASK = "ACTION_DATABASE_CREATE_TASK"
|
const val ACTION_DATABASE_CREATE_TASK = "ACTION_DATABASE_CREATE_TASK"
|
||||||
const val ACTION_DATABASE_LOAD_TASK = "ACTION_DATABASE_LOAD_TASK"
|
const val ACTION_DATABASE_LOAD_TASK = "ACTION_DATABASE_LOAD_TASK"
|
||||||
|
const val ACTION_DATABASE_MERGE_TASK = "ACTION_DATABASE_MERGE_TASK"
|
||||||
const val ACTION_DATABASE_RELOAD_TASK = "ACTION_DATABASE_RELOAD_TASK"
|
const val ACTION_DATABASE_RELOAD_TASK = "ACTION_DATABASE_RELOAD_TASK"
|
||||||
const val ACTION_DATABASE_ASSIGN_PASSWORD_TASK = "ACTION_DATABASE_ASSIGN_PASSWORD_TASK"
|
const val ACTION_DATABASE_ASSIGN_PASSWORD_TASK = "ACTION_DATABASE_ASSIGN_PASSWORD_TASK"
|
||||||
const val ACTION_DATABASE_CREATE_GROUP_TASK = "ACTION_DATABASE_CREATE_GROUP_TASK"
|
const val ACTION_DATABASE_CREATE_GROUP_TASK = "ACTION_DATABASE_CREATE_GROUP_TASK"
|
||||||
|
|||||||
@@ -115,6 +115,10 @@ class NestedDatabaseSettingsFragment : NestedSettingsFragment(), DatabaseRetriev
|
|||||||
mDatabaseViewModel.saveDatabase(save)
|
mDatabaseViewModel.saveDatabase(save)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun mergeDatabase() {
|
||||||
|
mDatabaseViewModel.mergeDatabase(false)
|
||||||
|
}
|
||||||
|
|
||||||
private fun reloadDatabase() {
|
private fun reloadDatabase() {
|
||||||
mDatabaseViewModel.reloadDatabase(false)
|
mDatabaseViewModel.reloadDatabase(false)
|
||||||
}
|
}
|
||||||
@@ -665,6 +669,10 @@ class NestedDatabaseSettingsFragment : NestedSettingsFragment(), DatabaseRetriev
|
|||||||
saveDatabase(!mDatabaseReadOnly)
|
saveDatabase(!mDatabaseReadOnly)
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
R.id.menu_merge_database -> {
|
||||||
|
mergeDatabase()
|
||||||
|
return true
|
||||||
|
}
|
||||||
R.id.menu_reload_database -> {
|
R.id.menu_reload_database -> {
|
||||||
reloadDatabase()
|
reloadDatabase()
|
||||||
return true
|
return true
|
||||||
|
|||||||
@@ -21,6 +21,9 @@ class DatabaseViewModel: ViewModel() {
|
|||||||
val saveDatabase : LiveData<Boolean> get() = _saveDatabase
|
val saveDatabase : LiveData<Boolean> get() = _saveDatabase
|
||||||
private val _saveDatabase = SingleLiveEvent<Boolean>()
|
private val _saveDatabase = SingleLiveEvent<Boolean>()
|
||||||
|
|
||||||
|
val mergeDatabase : LiveData<Boolean> get() = _mergeDatabase
|
||||||
|
private val _mergeDatabase = SingleLiveEvent<Boolean>()
|
||||||
|
|
||||||
val reloadDatabase : LiveData<Boolean> get() = _reloadDatabase
|
val reloadDatabase : LiveData<Boolean> get() = _reloadDatabase
|
||||||
private val _reloadDatabase = SingleLiveEvent<Boolean>()
|
private val _reloadDatabase = SingleLiveEvent<Boolean>()
|
||||||
|
|
||||||
@@ -84,6 +87,10 @@ class DatabaseViewModel: ViewModel() {
|
|||||||
_saveDatabase.value = save
|
_saveDatabase.value = save
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun mergeDatabase(fixDuplicateUuid: Boolean) {
|
||||||
|
_mergeDatabase.value = fixDuplicateUuid
|
||||||
|
}
|
||||||
|
|
||||||
fun reloadDatabase(fixDuplicateUuid: Boolean) {
|
fun reloadDatabase(fixDuplicateUuid: Boolean) {
|
||||||
_reloadDatabase.value = fixDuplicateUuid
|
_reloadDatabase.value = fixDuplicateUuid
|
||||||
}
|
}
|
||||||
|
|||||||
10
app/src/main/res/drawable/ic_merge_white_24dp.xml
Normal file
10
app/src/main/res/drawable/ic_merge_white_24dp.xml
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:width="24dp"
|
||||||
|
android:height="24dp"
|
||||||
|
android:viewportWidth="24"
|
||||||
|
android:viewportHeight="24">
|
||||||
|
<path
|
||||||
|
android:fillColor="#ffffff"
|
||||||
|
android:pathData="M 11 2.0566406 C 6.762335 2.4220229 3.0067094 5.7987155 2.203125 9.9785156 C 1.3601754 13.960549 3.1781148 18.394742 6.7089844 20.480469 C 9.6237318 22.368157 13.514425 22.492178 16.582031 20.892578 C 17.959775 20.180473 19.316015 19.099467 20.087891 17.808594 L 18.402344 16.791016 C 16.277892 19.724364 12.039121 20.844607 8.7519531 19.306641 C 5.4810648 17.911181 3.4461927 14.150571 4.109375 10.648438 C 4.6649663 7.2806968 7.5784749 4.4226117 11 4.0839844 L 11 2.0566406 z M 13 2.0644531 L 13 4.09375 C 16.367309 4.4801388 19.308004 7.2166096 19.861328 10.574219 C 20.123351 12.069186 19.935388 13.632673 19.367188 15.037109 C 19.94644 15.387646 20.527063 15.73602 21.105469 16.087891 C 22.671735 12.714066 22.12099 8.4920873 19.708984 5.6542969 C 18.063396 3.6246553 15.604973 2.2995703 13 2.0644531 z M 9 6 L 7.5859375 7.4140625 L 11 10.828125 L 11 13 L 7 13 L 12 18 L 17 13 L 13 13 L 13 10 L 12.910156 10 L 12.955078 9.9550781 L 9 6 z M 15.541016 6 L 12.769531 8.7695312 L 14.183594 10.183594 L 16.955078 7.4140625 L 15.541016 6 z" />
|
||||||
|
</vector>
|
||||||
@@ -25,10 +25,16 @@
|
|||||||
android:orderInCategory="95"
|
android:orderInCategory="95"
|
||||||
app:iconTint="?attr/colorControlNormal"
|
app:iconTint="?attr/colorControlNormal"
|
||||||
app:showAsAction="ifRoom" />
|
app:showAsAction="ifRoom" />
|
||||||
<item android:id="@+id/menu_reload_database"
|
<item android:id="@+id/menu_merge_database"
|
||||||
android:icon="@drawable/ic_downloading_white_24dp"
|
android:icon="@drawable/ic_merge_white_24dp"
|
||||||
android:title="@string/menu_reload_database"
|
android:title="@string/menu_merge_database"
|
||||||
android:orderInCategory="96"
|
android:orderInCategory="96"
|
||||||
app:iconTint="?attr/colorControlNormal"
|
app:iconTint="?attr/colorControlNormal"
|
||||||
app:showAsAction="ifRoom" />
|
app:showAsAction="ifRoom" />
|
||||||
|
<item android:id="@+id/menu_reload_database"
|
||||||
|
android:icon="@drawable/ic_downloading_white_24dp"
|
||||||
|
android:title="@string/menu_reload_database"
|
||||||
|
android:orderInCategory="97"
|
||||||
|
app:iconTint="?attr/colorControlNormal"
|
||||||
|
app:showAsAction="ifRoom" />
|
||||||
</menu>
|
</menu>
|
||||||
@@ -224,6 +224,7 @@
|
|||||||
<string name="menu_hide_password">Hide password</string>
|
<string name="menu_hide_password">Hide password</string>
|
||||||
<string name="menu_lock">Lock database</string>
|
<string name="menu_lock">Lock database</string>
|
||||||
<string name="menu_save_database">Save database</string>
|
<string name="menu_save_database">Save database</string>
|
||||||
|
<string name="menu_merge_database">Merge database</string>
|
||||||
<string name="menu_reload_database">Reload database</string>
|
<string name="menu_reload_database">Reload database</string>
|
||||||
<string name="menu_open">Open</string>
|
<string name="menu_open">Open</string>
|
||||||
<string name="menu_search">Search</string>
|
<string name="menu_search">Search</string>
|
||||||
@@ -323,7 +324,7 @@
|
|||||||
<string name="warning_empty_keyfile">It is not recommended to add an empty keyfile.</string>
|
<string name="warning_empty_keyfile">It is not recommended to add an empty keyfile.</string>
|
||||||
<string name="warning_empty_keyfile_explanation">The content of the keyfile should never be changed, and in the best case, should contain randomly generated data.</string>
|
<string name="warning_empty_keyfile_explanation">The content of the keyfile should never be changed, and in the best case, should contain randomly generated data.</string>
|
||||||
<string name="warning_database_info_changed">The information contained in your database file has been modified outside the app.</string>
|
<string name="warning_database_info_changed">The information contained in your database file has been modified outside the app.</string>
|
||||||
<string name="warning_database_info_changed_options">Overwrite the external modifications by saving the database or reload it with the latest changes.</string>
|
<string name="warning_database_info_changed_options">Merge the data, overwrite the external modifications by saving the database or reload the database with the latest changes.</string>
|
||||||
<string name="warning_database_revoked">Access to the file revoked by the file manager, close the database and reopen it from its location.</string>
|
<string name="warning_database_revoked">Access to the file revoked by the file manager, close the database and reopen it from its location.</string>
|
||||||
<string name="warning_exact_alarm">You have not allowed the app to use an exact alarm. As a result, the features requiring a timer will not be done with an exact time.</string>
|
<string name="warning_exact_alarm">You have not allowed the app to use an exact alarm. As a result, the features requiring a timer will not be done with an exact time.</string>
|
||||||
<string name="permission">Permission</string>
|
<string name="permission">Permission</string>
|
||||||
|
|||||||
59
art/merge_database.svg
Normal file
59
art/merge_database.svg
Normal file
@@ -0,0 +1,59 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||||
|
<svg
|
||||||
|
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||||
|
xmlns:cc="http://creativecommons.org/ns#"
|
||||||
|
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||||
|
xmlns:svg="http://www.w3.org/2000/svg"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||||
|
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||||
|
width="24"
|
||||||
|
height="24"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
version="1.1"
|
||||||
|
id="svg4"
|
||||||
|
sodipodi:docname="merge_database.svg"
|
||||||
|
inkscape:version="0.92.4 (5da689c313, 2019-01-14)">
|
||||||
|
<metadata
|
||||||
|
id="metadata10">
|
||||||
|
<rdf:RDF>
|
||||||
|
<cc:Work
|
||||||
|
rdf:about="">
|
||||||
|
<dc:format>image/svg+xml</dc:format>
|
||||||
|
<dc:type
|
||||||
|
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||||
|
<dc:title></dc:title>
|
||||||
|
</cc:Work>
|
||||||
|
</rdf:RDF>
|
||||||
|
</metadata>
|
||||||
|
<defs
|
||||||
|
id="defs8" />
|
||||||
|
<sodipodi:namedview
|
||||||
|
pagecolor="#c8c8c8"
|
||||||
|
bordercolor="#666666"
|
||||||
|
borderopacity="1"
|
||||||
|
objecttolerance="10"
|
||||||
|
gridtolerance="10"
|
||||||
|
guidetolerance="10"
|
||||||
|
inkscape:pageopacity="0.28235294"
|
||||||
|
inkscape:pageshadow="2"
|
||||||
|
inkscape:window-width="1920"
|
||||||
|
inkscape:window-height="1016"
|
||||||
|
id="namedview6"
|
||||||
|
showgrid="true"
|
||||||
|
inkscape:zoom="22.627418"
|
||||||
|
inkscape:cx="4.5821118"
|
||||||
|
inkscape:cy="13.022387"
|
||||||
|
inkscape:window-x="1920"
|
||||||
|
inkscape:window-y="27"
|
||||||
|
inkscape:window-maximized="1"
|
||||||
|
inkscape:current-layer="svg4">
|
||||||
|
<inkscape:grid
|
||||||
|
type="xygrid"
|
||||||
|
id="grid818" />
|
||||||
|
</sodipodi:namedview>
|
||||||
|
<path
|
||||||
|
style="fill:#ffffff"
|
||||||
|
d="M 11 2.0566406 C 6.762335 2.4220229 3.0067094 5.7987155 2.203125 9.9785156 C 1.3601754 13.960549 3.1781148 18.394742 6.7089844 20.480469 C 9.6237318 22.368157 13.514425 22.492178 16.582031 20.892578 C 17.959775 20.180473 19.316015 19.099467 20.087891 17.808594 L 18.402344 16.791016 C 16.277892 19.724364 12.039121 20.844607 8.7519531 19.306641 C 5.4810648 17.911181 3.4461927 14.150571 4.109375 10.648438 C 4.6649663 7.2806968 7.5784749 4.4226117 11 4.0839844 L 11 2.0566406 z M 13 2.0644531 L 13 4.09375 C 16.367309 4.4801388 19.308004 7.2166096 19.861328 10.574219 C 20.123351 12.069186 19.935388 13.632673 19.367188 15.037109 C 19.94644 15.387646 20.527063 15.73602 21.105469 16.087891 C 22.671735 12.714066 22.12099 8.4920873 19.708984 5.6542969 C 18.063396 3.6246553 15.604973 2.2995703 13 2.0644531 z M 9 6 L 7.5859375 7.4140625 L 11 10.828125 L 11 13 L 7 13 L 12 18 L 17 13 L 13 13 L 13 10 L 12.910156 10 L 12.955078 9.9550781 L 9 6 z M 15.541016 6 L 12.769531 8.7695312 L 14.183594 10.183594 L 16.955078 7.4140625 L 15.541016 6 z "
|
||||||
|
id="path868" />
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 2.6 KiB |
Reference in New Issue
Block a user