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 d9ed8c2d8..513234633 100644
--- a/app/src/main/java/com/kunzisoft/keepass/activities/EntryActivity.kt
+++ b/app/src/main/java/com/kunzisoft/keepass/activities/EntryActivity.kt
@@ -415,6 +415,9 @@ class EntryActivity : DatabaseLockActivity() {
R.id.menu_save_database -> {
saveDatabase()
}
+ R.id.menu_merge_database -> {
+ mergeDatabase()
+ }
R.id.menu_reload_database -> {
reloadDatabase()
}
diff --git a/app/src/main/java/com/kunzisoft/keepass/activities/GroupActivity.kt b/app/src/main/java/com/kunzisoft/keepass/activities/GroupActivity.kt
index 363d4b1ab..76e8b9ee3 100644
--- a/app/src/main/java/com/kunzisoft/keepass/activities/GroupActivity.kt
+++ b/app/src/main/java/com/kunzisoft/keepass/activities/GroupActivity.kt
@@ -1070,6 +1070,10 @@ class GroupActivity : DatabaseLockActivity(),
saveDatabase()
return true
}
+ R.id.menu_merge_database -> {
+ mergeDatabase()
+ return true
+ }
R.id.menu_reload_database -> {
reloadDatabase()
return true
diff --git a/app/src/main/java/com/kunzisoft/keepass/activities/legacy/DatabaseLockActivity.kt b/app/src/main/java/com/kunzisoft/keepass/activities/legacy/DatabaseLockActivity.kt
index f5de6dd2e..c74bb6e47 100644
--- a/app/src/main/java/com/kunzisoft/keepass/activities/legacy/DatabaseLockActivity.kt
+++ b/app/src/main/java/com/kunzisoft/keepass/activities/legacy/DatabaseLockActivity.kt
@@ -87,6 +87,10 @@ abstract class DatabaseLockActivity : DatabaseModeActivity(),
mDatabaseTaskProvider?.startDatabaseSave(save)
}
+ mDatabaseViewModel.mergeDatabase.observe(this) { fixDuplicateUuid ->
+ mDatabaseTaskProvider?.startDatabaseMerge(fixDuplicateUuid)
+ }
+
mDatabaseViewModel.reloadDatabase.observe(this) { fixDuplicateUuid ->
mDatabaseTaskProvider?.startDatabaseReload(fixDuplicateUuid)
}
@@ -212,6 +216,7 @@ abstract class DatabaseLockActivity : DatabaseModeActivity(),
) {
super.onDatabaseActionFinished(database, actionTask, result)
when (actionTask) {
+ DatabaseTaskNotificationService.ACTION_DATABASE_MERGE_TASK,
DatabaseTaskNotificationService.ACTION_DATABASE_RELOAD_TASK -> {
// Reload the current activity
if (result.isSuccess) {
@@ -254,6 +259,10 @@ abstract class DatabaseLockActivity : DatabaseModeActivity(),
mDatabaseTaskProvider?.startDatabaseSave(true)
}
+ fun mergeDatabase() {
+ mDatabaseTaskProvider?.startDatabaseMerge(false)
+ }
+
fun reloadDatabase() {
mDatabaseTaskProvider?.startDatabaseReload(false)
}
diff --git a/app/src/main/java/com/kunzisoft/keepass/database/action/DatabaseTaskProvider.kt b/app/src/main/java/com/kunzisoft/keepass/database/action/DatabaseTaskProvider.kt
index 20848b4a5..a5adea39d 100644
--- a/app/src/main/java/com/kunzisoft/keepass/database/action/DatabaseTaskProvider.kt
+++ b/app/src/main/java/com/kunzisoft/keepass/database/action/DatabaseTaskProvider.kt
@@ -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_NODES_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_RELOAD_TASK
import com.kunzisoft.keepass.services.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_REMOVE_UNLINKED_DATA_TASK
@@ -354,6 +355,13 @@ class DatabaseTaskProvider {
, 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) {
start(Bundle().apply {
putBoolean(DatabaseTaskNotificationService.FIX_DUPLICATE_UUID_KEY, fixDuplicateUuid)
diff --git a/app/src/main/java/com/kunzisoft/keepass/database/action/MergeDatabaseRunnable.kt b/app/src/main/java/com/kunzisoft/keepass/database/action/MergeDatabaseRunnable.kt
new file mode 100644
index 000000000..849ef28b9
--- /dev/null
+++ b/app/src/main/java/com/kunzisoft/keepass/database/action/MergeDatabaseRunnable.kt
@@ -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 .
+ *
+ */
+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)
+ }
+}
diff --git a/app/src/main/java/com/kunzisoft/keepass/database/element/Database.kt b/app/src/main/java/com/kunzisoft/keepass/database/element/Database.kt
index ee597bb0e..7b3a16635 100644
--- a/app/src/main/java/com/kunzisoft/keepass/database/element/Database.kt
+++ b/app/src/main/java/com/kunzisoft/keepass/database/element/Database.kt
@@ -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.output.DatabaseOutputKDB
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.SearchParameters
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)
fun reloadData(contentResolver: ContentResolver,
cacheDirectory: File,
@@ -636,20 +683,20 @@ class Database {
try {
fileUri?.let { oldDatabaseUri ->
readDatabaseStream(contentResolver, oldDatabaseUri,
- { databaseInputStream ->
- DatabaseInputKDB(cacheDirectory, isRAMSufficient)
- .openDatabase(databaseInputStream,
- masterKey,
- tempCipherKey,
- progressTaskUpdater)
- },
- { databaseInputStream ->
- DatabaseInputKDBX(cacheDirectory, isRAMSufficient)
- .openDatabase(databaseInputStream,
- masterKey,
- tempCipherKey,
- progressTaskUpdater)
- }
+ { 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")
diff --git a/app/src/main/java/com/kunzisoft/keepass/database/merge/DatabaseKDBXMerger.kt b/app/src/main/java/com/kunzisoft/keepass/database/merge/DatabaseKDBXMerger.kt
new file mode 100644
index 000000000..853c5aee4
--- /dev/null
+++ b/app/src/main/java/com/kunzisoft/keepass/database/merge/DatabaseKDBXMerger.kt
@@ -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() {
+ override fun operate(node: EntryKDBX): Boolean {
+ val entryId = node.nodeId
+ databaseToMerge.getEntryById(entryId)?.let {
+ mergeEntry(database.getEntryById(entryId), it)
+ }
+ return true
+ }
+ },
+ object : NodeHandler() {
+ 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)
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/kunzisoft/keepass/services/DatabaseTaskNotificationService.kt b/app/src/main/java/com/kunzisoft/keepass/services/DatabaseTaskNotificationService.kt
index fbb0ee680..59c5abdbe 100644
--- a/app/src/main/java/com/kunzisoft/keepass/services/DatabaseTaskNotificationService.kt
+++ b/app/src/main/java/com/kunzisoft/keepass/services/DatabaseTaskNotificationService.kt
@@ -226,6 +226,7 @@ open class DatabaseTaskNotificationService : LockNotificationService(), Progress
val actionRunnable: ActionRunnable? = when (intentAction) {
ACTION_DATABASE_CREATE_TASK -> buildDatabaseCreateActionTask(intent, database)
ACTION_DATABASE_LOAD_TASK -> buildDatabaseLoadActionTask(intent, database)
+ ACTION_DATABASE_MERGE_TASK -> buildDatabaseMergeActionTask(database)
ACTION_DATABASE_RELOAD_TASK -> buildDatabaseReloadActionTask(database)
ACTION_DATABASE_ASSIGN_PASSWORD_TASK -> buildDatabaseAssignPasswordActionTask(intent, database)
ACTION_DATABASE_CREATE_GROUP_TASK -> buildDatabaseCreateGroupActionTask(intent, database)
@@ -331,6 +332,7 @@ open class DatabaseTaskNotificationService : LockNotificationService(), Progress
return when (intentAction) {
ACTION_DATABASE_LOAD_TASK,
+ ACTION_DATABASE_MERGE_TASK,
ACTION_DATABASE_RELOAD_TASK,
null -> {
START_STICKY
@@ -367,6 +369,7 @@ open class DatabaseTaskNotificationService : LockNotificationService(), Progress
when (intentAction) {
ACTION_DATABASE_CREATE_TASK -> R.string.creating_database
ACTION_DATABASE_LOAD_TASK,
+ ACTION_DATABASE_MERGE_TASK,
ACTION_DATABASE_RELOAD_TASK -> R.string.loading_database
ACTION_DATABASE_SAVE -> R.string.saving_database
else -> {
@@ -378,6 +381,7 @@ open class DatabaseTaskNotificationService : LockNotificationService(), Progress
mMessageId = when (intentAction) {
ACTION_DATABASE_LOAD_TASK,
+ ACTION_DATABASE_MERGE_TASK,
ACTION_DATABASE_RELOAD_TASK -> null
else -> null
}
@@ -385,6 +389,7 @@ open class DatabaseTaskNotificationService : LockNotificationService(), Progress
mWarningId =
if (!saveAction
|| intentAction == ACTION_DATABASE_LOAD_TASK
+ || intentAction == ACTION_DATABASE_MERGE_TASK
|| intentAction == ACTION_DATABASE_RELOAD_TASK)
null
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 {
return ReloadDatabaseRunnable(
this,
@@ -907,6 +923,7 @@ open class DatabaseTaskNotificationService : LockNotificationService(), Progress
const val ACTION_DATABASE_CREATE_TASK = "ACTION_DATABASE_CREATE_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_ASSIGN_PASSWORD_TASK = "ACTION_DATABASE_ASSIGN_PASSWORD_TASK"
const val ACTION_DATABASE_CREATE_GROUP_TASK = "ACTION_DATABASE_CREATE_GROUP_TASK"
diff --git a/app/src/main/java/com/kunzisoft/keepass/settings/NestedDatabaseSettingsFragment.kt b/app/src/main/java/com/kunzisoft/keepass/settings/NestedDatabaseSettingsFragment.kt
index bbe8b8b81..e050073e3 100644
--- a/app/src/main/java/com/kunzisoft/keepass/settings/NestedDatabaseSettingsFragment.kt
+++ b/app/src/main/java/com/kunzisoft/keepass/settings/NestedDatabaseSettingsFragment.kt
@@ -115,6 +115,10 @@ class NestedDatabaseSettingsFragment : NestedSettingsFragment(), DatabaseRetriev
mDatabaseViewModel.saveDatabase(save)
}
+ private fun mergeDatabase() {
+ mDatabaseViewModel.mergeDatabase(false)
+ }
+
private fun reloadDatabase() {
mDatabaseViewModel.reloadDatabase(false)
}
@@ -665,6 +669,10 @@ class NestedDatabaseSettingsFragment : NestedSettingsFragment(), DatabaseRetriev
saveDatabase(!mDatabaseReadOnly)
true
}
+ R.id.menu_merge_database -> {
+ mergeDatabase()
+ return true
+ }
R.id.menu_reload_database -> {
reloadDatabase()
return true
diff --git a/app/src/main/java/com/kunzisoft/keepass/viewmodels/DatabaseViewModel.kt b/app/src/main/java/com/kunzisoft/keepass/viewmodels/DatabaseViewModel.kt
index 9e3dafcaa..8608b144a 100644
--- a/app/src/main/java/com/kunzisoft/keepass/viewmodels/DatabaseViewModel.kt
+++ b/app/src/main/java/com/kunzisoft/keepass/viewmodels/DatabaseViewModel.kt
@@ -21,6 +21,9 @@ class DatabaseViewModel: ViewModel() {
val saveDatabase : LiveData get() = _saveDatabase
private val _saveDatabase = SingleLiveEvent()
+ val mergeDatabase : LiveData get() = _mergeDatabase
+ private val _mergeDatabase = SingleLiveEvent()
+
val reloadDatabase : LiveData get() = _reloadDatabase
private val _reloadDatabase = SingleLiveEvent()
@@ -84,6 +87,10 @@ class DatabaseViewModel: ViewModel() {
_saveDatabase.value = save
}
+ fun mergeDatabase(fixDuplicateUuid: Boolean) {
+ _mergeDatabase.value = fixDuplicateUuid
+ }
+
fun reloadDatabase(fixDuplicateUuid: Boolean) {
_reloadDatabase.value = fixDuplicateUuid
}
diff --git a/app/src/main/res/drawable/ic_merge_white_24dp.xml b/app/src/main/res/drawable/ic_merge_white_24dp.xml
new file mode 100644
index 000000000..e180205de
--- /dev/null
+++ b/app/src/main/res/drawable/ic_merge_white_24dp.xml
@@ -0,0 +1,10 @@
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/menu/database.xml b/app/src/main/res/menu/database.xml
index 43d5a5231..ce31d9e40 100644
--- a/app/src/main/res/menu/database.xml
+++ b/app/src/main/res/menu/database.xml
@@ -25,10 +25,16 @@
android:orderInCategory="95"
app:iconTint="?attr/colorControlNormal"
app:showAsAction="ifRoom" />
-
+
\ No newline at end of file
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index 7f45f2b47..02da1218e 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -224,6 +224,7 @@
Hide password
Lock database
Save database
+ Merge database
Reload database
Open
Search
@@ -323,7 +324,7 @@
It is not recommended to add an empty keyfile.
The content of the keyfile should never be changed, and in the best case, should contain randomly generated data.
The information contained in your database file has been modified outside the app.
- Overwrite the external modifications by saving the database or reload it with the latest changes.
+ Merge the data, overwrite the external modifications by saving the database or reload the database with the latest changes.
Access to the file revoked by the file manager, close the database and reopen it from its location.
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.
Permission
diff --git a/art/merge_database.svg b/art/merge_database.svg
new file mode 100644
index 000000000..7e772e394
--- /dev/null
+++ b/art/merge_database.svg
@@ -0,0 +1,59 @@
+
+