Implementation fo "Merge from" and "Save a copy to"

This commit is contained in:
J-Jamet
2022-02-11 19:40:56 +01:00
parent 59f134e0cd
commit 6686ce15c1
8 changed files with 131 additions and 72 deletions

View File

@@ -51,6 +51,7 @@ import com.kunzisoft.keepass.R
import com.kunzisoft.keepass.activities.dialogs.*
import com.kunzisoft.keepass.activities.fragments.GroupFragment
import com.kunzisoft.keepass.activities.helpers.EntrySelectionHelper
import com.kunzisoft.keepass.activities.helpers.ExternalFileHelper
import com.kunzisoft.keepass.activities.helpers.SpecialMode
import com.kunzisoft.keepass.activities.legacy.DatabaseLockActivity
import com.kunzisoft.keepass.adapters.BreadcrumbAdapter
@@ -123,6 +124,9 @@ class GroupActivity : DatabaseLockActivity(),
private var actionNodeMode: ActionMode? = null
// Manage merge
private var mExternalFileHelper: ExternalFileHelper? = null
// Manage group
private var mSearchState: SearchState? = null
private var mMainGroupState: GroupState? = null // Group state, not a search
@@ -253,6 +257,19 @@ class GroupActivity : DatabaseLockActivity(),
drawerLayout?.addDrawerListener(toggle)
toggle.syncState()
// Manage 'merge from" and "save to"
mExternalFileHelper = ExternalFileHelper(this)
mExternalFileHelper?.buildOpenDocument { uri ->
uri?.let {
mergeDatabaseFrom(it)
}
}
mExternalFileHelper?.buildCreateDocument("application/x-keepass") { uri ->
uri?.let {
saveDatabaseTo(it)
}
}
// Menu in drawer
databaseNavView?.apply {
inflateMenu(R.menu.settings)
@@ -264,6 +281,15 @@ class GroupActivity : DatabaseLockActivity(),
// To avoid flickering when launch settings in a LockingActivity
SettingsActivity.launch(this@GroupActivity, true)
}
R.id.menu_merge_from -> {
mExternalFileHelper?.openDocument()
}
R.id.menu_save_copy_to -> {
mExternalFileHelper?.createDocument(
getString(R.string.database_file_name_default) +
getString(R.string.database_file_name_copy) +
getString(R.string.database_file_extension_default))
}
R.id.menu_contribute -> {
UriUtil.gotoUrl(this@GroupActivity, R.string.contribution_url)
}

View File

@@ -88,8 +88,8 @@ abstract class DatabaseLockActivity : DatabaseModeActivity(),
mDatabaseTaskProvider?.startDatabaseSave(save)
}
mDatabaseViewModel.mergeDatabase.observe(this) { fixDuplicateUuid ->
mDatabaseTaskProvider?.startDatabaseMerge(fixDuplicateUuid)
mDatabaseViewModel.mergeDatabase.observe(this) {
mDatabaseTaskProvider?.startDatabaseMerge()
}
mDatabaseViewModel.reloadDatabase.observe(this) { fixDuplicateUuid ->
@@ -263,8 +263,16 @@ abstract class DatabaseLockActivity : DatabaseModeActivity(),
mDatabaseTaskProvider?.startDatabaseSave(true)
}
fun saveDatabaseTo(uri: Uri) {
mDatabaseTaskProvider?.startDatabaseSave(true, uri)
}
fun mergeDatabase() {
mDatabaseTaskProvider?.startDatabaseMerge(false)
mDatabaseTaskProvider?.startDatabaseMerge()
}
fun mergeDatabaseFrom(uri: Uri) {
mDatabaseTaskProvider?.startDatabaseMerge(uri)
}
fun reloadDatabase() {

View File

@@ -84,7 +84,6 @@ import com.kunzisoft.keepass.utils.DATABASE_START_TASK_ACTION
import com.kunzisoft.keepass.utils.DATABASE_STOP_TASK_ACTION
import kotlinx.coroutines.launch
import java.util.*
import kotlin.collections.ArrayList
/**
* Utility class to connect an activity or a service to the DatabaseTaskNotificationService,
@@ -356,9 +355,11 @@ class DatabaseTaskProvider {
, ACTION_DATABASE_LOAD_TASK)
}
fun startDatabaseMerge(fixDuplicateUuid: Boolean) {
fun startDatabaseMerge(fromDatabaseUri: Uri? = null) {
start(Bundle().apply {
putBoolean(DatabaseTaskNotificationService.FIX_DUPLICATE_UUID_KEY, fixDuplicateUuid)
if (fromDatabaseUri != null) {
putParcelable(DatabaseTaskNotificationService.DATABASE_URI_KEY, fromDatabaseUri)
}
}
, ACTION_DATABASE_MERGE_TASK)
}
@@ -693,9 +694,12 @@ class DatabaseTaskProvider {
/**
* Save Database without parameter
*/
fun startDatabaseSave(save: Boolean) {
fun startDatabaseSave(save: Boolean, saveToUri: Uri? = null) {
start(Bundle().apply {
putBoolean(DatabaseTaskNotificationService.SAVE_DATABASE_KEY, save)
if (saveToUri != null) {
putParcelable(DatabaseTaskNotificationService.DATABASE_URI_KEY, saveToUri)
}
}
, ACTION_DATABASE_SAVE)
}

View File

@@ -20,17 +20,17 @@
package com.kunzisoft.keepass.database.action
import android.content.Context
import android.net.Uri
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 mDatabaseToMergeUri: Uri?,
private val progressTaskUpdater: ProgressTaskUpdater?,
private val mLoadDatabaseResult: ((Result) -> Unit)?)
: ActionRunnable() {
@@ -41,7 +41,7 @@ class MergeDatabaseRunnable(private val context: Context,
override fun onActionRun() {
try {
mDatabase.mergeData(context.contentResolver,
mDatabase.mergeData(mDatabaseToMergeUri, context.contentResolver,
{ memoryWanted ->
BinaryData.canMemoryBeAllocatedInRAM(context, memoryWanted)
},

View File

@@ -20,13 +20,15 @@
package com.kunzisoft.keepass.database.action
import android.content.Context
import android.net.Uri
import com.kunzisoft.keepass.database.element.Database
import com.kunzisoft.keepass.database.exception.DatabaseException
import com.kunzisoft.keepass.tasks.ActionRunnable
open class SaveDatabaseRunnable(protected var context: Context,
protected var database: Database,
private var saveDatabase: Boolean)
private var saveDatabase: Boolean,
private var databaseCopyUri: Uri? = null)
: ActionRunnable() {
var mAfterSaveDatabase: ((Result) -> Unit)? = null
@@ -37,7 +39,7 @@ open class SaveDatabaseRunnable(protected var context: Context,
database.checkVersion()
if (saveDatabase && result.isSuccess) {
try {
database.saveData(context.contentResolver)
database.saveData(databaseCopyUri, context.contentResolver)
} catch (e: DatabaseException) {
setError(e)
}

View File

@@ -671,7 +671,8 @@ class Database {
}
@Throws(LoadDatabaseException::class)
fun mergeData(contentResolver: ContentResolver,
fun mergeData(databaseToMergeUri: Uri?,
contentResolver: ContentResolver,
isRAMSufficient: (memoryWanted: Long) -> Boolean,
progressTaskUpdater: ProgressTaskUpdater?) {
@@ -681,7 +682,7 @@ class Database {
// New database instance to get new changes
val databaseToMerge = Database()
databaseToMerge.fileUri = this.fileUri
databaseToMerge.fileUri = databaseToMergeUri ?: this.fileUri
try {
databaseToMerge.fileUri?.let { databaseUri ->
@@ -867,10 +868,58 @@ class Database {
}
@Throws(DatabaseOutputException::class)
fun saveData(contentResolver: ContentResolver) {
fun saveData(uri: Uri?, contentResolver: ContentResolver) {
try {
this.fileUri?.let {
saveData(contentResolver, it)
(uri ?: this.fileUri)?.let { saveUri ->
if (saveUri.scheme == "file") {
saveUri.path?.let { filename ->
val tempFile = File("$filename.tmp")
var fileOutputStream: FileOutputStream? = null
try {
fileOutputStream = FileOutputStream(tempFile)
val pmo = mDatabaseKDB?.let { DatabaseOutputKDB(it, fileOutputStream) }
?: mDatabaseKDBX?.let { DatabaseOutputKDBX(it, fileOutputStream) }
pmo?.output()
} catch (e: Exception) {
throw IOException(e)
} finally {
fileOutputStream?.close()
}
// Force data to disk before continuing
try {
fileOutputStream?.fd?.sync()
} catch (e: SyncFailedException) {
// Ignore if fsync fails. We tried.
}
if (!tempFile.renameTo(File(filename))) {
throw IOException()
}
}
} else {
var outputStream: OutputStream? = null
try {
outputStream = contentResolver.openOutputStream(saveUri, "rwt")
outputStream?.let { definedOutputStream ->
val databaseOutput =
mDatabaseKDB?.let { DatabaseOutputKDB(it, definedOutputStream) }
?: mDatabaseKDBX?.let {
DatabaseOutputKDBX(
it,
definedOutputStream
)
}
databaseOutput?.output()
}
} catch (e: Exception) {
throw IOException(e)
} finally {
outputStream?.close()
}
}
this.dataModifiedSinceLastLoading = false
}
} catch (e: Exception) {
Log.e(TAG, "Unable to save database", e)
@@ -878,55 +927,6 @@ class Database {
}
}
@Throws(IOException::class, DatabaseOutputException::class)
private fun saveData(contentResolver: ContentResolver, uri: Uri) {
if (uri.scheme == "file") {
uri.path?.let { filename ->
val tempFile = File("$filename.tmp")
var fileOutputStream: FileOutputStream? = null
try {
fileOutputStream = FileOutputStream(tempFile)
val pmo = mDatabaseKDB?.let { DatabaseOutputKDB(it, fileOutputStream) }
?: mDatabaseKDBX?.let { DatabaseOutputKDBX(it, fileOutputStream) }
pmo?.output()
} catch (e: Exception) {
throw IOException(e)
} finally {
fileOutputStream?.close()
}
// Force data to disk before continuing
try {
fileOutputStream?.fd?.sync()
} catch (e: SyncFailedException) {
// Ignore if fsync fails. We tried.
}
if (!tempFile.renameTo(File(filename))) {
throw IOException()
}
}
} else {
var outputStream: OutputStream? = null
try {
outputStream = contentResolver.openOutputStream(uri, "rwt")
outputStream?.let { definedOutputStream ->
val databaseOutput = mDatabaseKDB?.let { DatabaseOutputKDB(it, definedOutputStream) }
?: mDatabaseKDBX?.let { DatabaseOutputKDBX(it, definedOutputStream) }
databaseOutput?.output()
}
} catch (e: Exception) {
throw IOException(e)
} finally {
outputStream?.close()
}
}
this.fileUri = uri
this.dataModifiedSinceLastLoading = false
}
fun clearIndexesAndBinaries(filesDirectory: File? = null) {
this.mDatabaseKDB?.clearIndexes()
this.mDatabaseKDBX?.clearIndexes()

View File

@@ -22,7 +22,10 @@ package com.kunzisoft.keepass.services
import android.app.PendingIntent
import android.content.Intent
import android.net.Uri
import android.os.*
import android.os.Binder
import android.os.Build
import android.os.Bundle
import android.os.IBinder
import android.util.Log
import androidx.media.app.NotificationCompat
import com.kunzisoft.keepass.R
@@ -44,11 +47,13 @@ import com.kunzisoft.keepass.model.SnapFileDatabaseInfo
import com.kunzisoft.keepass.tasks.ActionRunnable
import com.kunzisoft.keepass.tasks.ProgressTaskUpdater
import com.kunzisoft.keepass.timeout.TimeoutHelper
import com.kunzisoft.keepass.utils.*
import com.kunzisoft.keepass.utils.DATABASE_START_TASK_ACTION
import com.kunzisoft.keepass.utils.DATABASE_STOP_TASK_ACTION
import com.kunzisoft.keepass.utils.LOCK_ACTION
import com.kunzisoft.keepass.utils.closeDatabase
import com.kunzisoft.keepass.viewmodels.FileDatabaseInfo
import kotlinx.coroutines.*
import java.util.*
import kotlin.collections.ArrayList
open class DatabaseTaskNotificationService : LockNotificationService(), ProgressTaskUpdater {
@@ -226,7 +231,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_MERGE_TASK -> buildDatabaseMergeActionTask(intent, database)
ACTION_DATABASE_RELOAD_TASK -> buildDatabaseReloadActionTask(database)
ACTION_DATABASE_ASSIGN_PASSWORD_TASK -> buildDatabaseAssignPasswordActionTask(intent, database)
ACTION_DATABASE_CREATE_GROUP_TASK -> buildDatabaseCreateGroupActionTask(intent, database)
@@ -606,10 +611,16 @@ open class DatabaseTaskNotificationService : LockNotificationService(), Progress
}
}
private fun buildDatabaseMergeActionTask(database: Database): ActionRunnable {
private fun buildDatabaseMergeActionTask(intent: Intent, database: Database): ActionRunnable {
var databaseToMergeUri: Uri? = null
if (intent.hasExtra(DATABASE_URI_KEY)) {
databaseToMergeUri = intent.getParcelableExtra(DATABASE_URI_KEY)
}
return MergeDatabaseRunnable(
this,
database,
databaseToMergeUri,
this
) { result ->
// No need to add each info to reload database
@@ -911,9 +922,16 @@ open class DatabaseTaskNotificationService : LockNotificationService(), Progress
*/
private fun buildDatabaseSave(intent: Intent, database: Database): ActionRunnable? {
return if (intent.hasExtra(SAVE_DATABASE_KEY)) {
var databaseCopyUri: Uri? = null
if (intent.hasExtra(DATABASE_URI_KEY)) {
databaseCopyUri = intent.getParcelableExtra(DATABASE_URI_KEY)
}
SaveDatabaseRunnable(this,
database,
!database.isReadOnly && intent.getBooleanExtra(SAVE_DATABASE_KEY, false))
!database.isReadOnly && intent.getBooleanExtra(SAVE_DATABASE_KEY, false),
databaseCopyUri)
} else {
null
}

View File

@@ -46,6 +46,7 @@
<!-- File Path -->
<string name="database_file_name_default" translatable="false">keepass</string>
<string name="database_file_name_copy" translatable="false">_copy</string>
<string name="database_file_extension_default" translatable="false">.kdbx</string>
<string name="database_default_name" translatable="false">KeePassDX Database</string>
<string name="app_properties_file_name" translatable="false">keepassdx_%1$s.properties</string>