mirror of
https://github.com/Kunzisoft/KeePassDX.git
synced 2025-12-04 15:49:33 +01:00
Merge branch 'feature/File_Attachment' into develop #189
This commit is contained in:
@@ -1,4 +1,5 @@
|
|||||||
KeePassDX(2.8.3)
|
KeePassDX(2.8.3)
|
||||||
|
* Add attachments
|
||||||
|
|
||||||
KeePassDX(2.8.2)
|
KeePassDX(2.8.2)
|
||||||
* Fix themes / new UI
|
* Fix themes / new UI
|
||||||
|
|||||||
@@ -45,8 +45,9 @@ import com.kunzisoft.keepass.database.element.node.NodeId
|
|||||||
import com.kunzisoft.keepass.education.EntryActivityEducation
|
import com.kunzisoft.keepass.education.EntryActivityEducation
|
||||||
import com.kunzisoft.keepass.icons.assignDatabaseIcon
|
import com.kunzisoft.keepass.icons.assignDatabaseIcon
|
||||||
import com.kunzisoft.keepass.magikeyboard.MagikIME
|
import com.kunzisoft.keepass.magikeyboard.MagikIME
|
||||||
import com.kunzisoft.keepass.model.AttachmentState
|
import com.kunzisoft.keepass.database.element.Attachment
|
||||||
import com.kunzisoft.keepass.model.EntryAttachment
|
import com.kunzisoft.keepass.model.EntryAttachmentState
|
||||||
|
import com.kunzisoft.keepass.model.StreamDirection
|
||||||
import com.kunzisoft.keepass.notifications.AttachmentFileNotificationService
|
import com.kunzisoft.keepass.notifications.AttachmentFileNotificationService
|
||||||
import com.kunzisoft.keepass.notifications.ClipboardEntryNotificationService
|
import com.kunzisoft.keepass.notifications.ClipboardEntryNotificationService
|
||||||
import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_DELETE_ENTRY_HISTORY
|
import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_DELETE_ENTRY_HISTORY
|
||||||
@@ -86,7 +87,7 @@ class EntryActivity : LockingActivity() {
|
|||||||
private var mShowPassword: Boolean = false
|
private var mShowPassword: Boolean = false
|
||||||
|
|
||||||
private var mAttachmentFileBinderManager: AttachmentFileBinderManager? = null
|
private var mAttachmentFileBinderManager: AttachmentFileBinderManager? = null
|
||||||
private var mAttachmentsToDownload: HashMap<Int, EntryAttachment> = HashMap()
|
private var mAttachmentsToDownload: HashMap<Int, Attachment> = HashMap()
|
||||||
|
|
||||||
private var clipboardHelper: ClipboardHelper? = null
|
private var clipboardHelper: ClipboardHelper? = null
|
||||||
private var mFirstLaunchOfActivity: Boolean = false
|
private var mFirstLaunchOfActivity: Boolean = false
|
||||||
@@ -212,8 +213,8 @@ class EntryActivity : LockingActivity() {
|
|||||||
mAttachmentFileBinderManager?.apply {
|
mAttachmentFileBinderManager?.apply {
|
||||||
registerProgressTask()
|
registerProgressTask()
|
||||||
onActionTaskListener = object : AttachmentFileNotificationService.ActionTaskListener {
|
onActionTaskListener = object : AttachmentFileNotificationService.ActionTaskListener {
|
||||||
override fun onAttachmentProgress(fileUri: Uri, attachment: EntryAttachment) {
|
override fun onAttachmentAction(fileUri: Uri, entryAttachmentState: EntryAttachmentState) {
|
||||||
entryContentsView?.updateAttachmentDownloadProgress(attachment)
|
entryContentsView?.putAttachment(entryAttachmentState)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -332,15 +333,10 @@ class EntryActivity : LockingActivity() {
|
|||||||
entryContentsView?.setHiddenProtectedValue(!mShowPassword)
|
entryContentsView?.setHiddenProtectedValue(!mShowPassword)
|
||||||
|
|
||||||
// Manage attachments
|
// Manage attachments
|
||||||
entryContentsView?.assignAttachments(entry.getAttachments()) { attachmentItem ->
|
mDatabase?.binaryPool?.let { binaryPool ->
|
||||||
when (attachmentItem.downloadState) {
|
entryContentsView?.assignAttachments(entry.getAttachments(binaryPool), StreamDirection.DOWNLOAD) { attachmentItem ->
|
||||||
AttachmentState.NULL, AttachmentState.ERROR, AttachmentState.COMPLETE -> {
|
createDocument(this, attachmentItem.name)?.let { requestCode ->
|
||||||
createDocument(this, attachmentItem.name)?.let { requestCode ->
|
mAttachmentsToDownload[requestCode] = attachmentItem
|
||||||
mAttachmentsToDownload[requestCode] = attachmentItem
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else -> {
|
|
||||||
// TODO Stop download
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -22,6 +22,8 @@ import android.app.Activity
|
|||||||
import android.app.DatePickerDialog
|
import android.app.DatePickerDialog
|
||||||
import android.app.TimePickerDialog
|
import android.app.TimePickerDialog
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
|
import android.net.Uri
|
||||||
|
import android.os.Build
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.os.Handler
|
import android.os.Handler
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
@@ -36,18 +38,18 @@ import androidx.coordinatorlayout.widget.CoordinatorLayout
|
|||||||
import androidx.core.widget.NestedScrollView
|
import androidx.core.widget.NestedScrollView
|
||||||
import com.kunzisoft.keepass.R
|
import com.kunzisoft.keepass.R
|
||||||
import com.kunzisoft.keepass.activities.dialogs.*
|
import com.kunzisoft.keepass.activities.dialogs.*
|
||||||
|
import com.kunzisoft.keepass.activities.dialogs.FileTooBigDialogFragment.Companion.MAX_WARNING_BINARY_FILE
|
||||||
|
import com.kunzisoft.keepass.activities.helpers.SelectFileHelper
|
||||||
import com.kunzisoft.keepass.activities.lock.LockingActivity
|
import com.kunzisoft.keepass.activities.lock.LockingActivity
|
||||||
import com.kunzisoft.keepass.database.element.Database
|
import com.kunzisoft.keepass.database.element.*
|
||||||
import com.kunzisoft.keepass.database.element.DateInstant
|
import com.kunzisoft.keepass.database.element.database.CompressionAlgorithm
|
||||||
import com.kunzisoft.keepass.database.element.Entry
|
|
||||||
import com.kunzisoft.keepass.database.element.Group
|
|
||||||
import com.kunzisoft.keepass.database.element.icon.IconImage
|
import com.kunzisoft.keepass.database.element.icon.IconImage
|
||||||
import com.kunzisoft.keepass.database.element.icon.IconImageStandard
|
import com.kunzisoft.keepass.database.element.icon.IconImageStandard
|
||||||
import com.kunzisoft.keepass.database.element.node.NodeId
|
import com.kunzisoft.keepass.database.element.node.NodeId
|
||||||
import com.kunzisoft.keepass.database.element.security.ProtectedString
|
import com.kunzisoft.keepass.database.element.security.ProtectedString
|
||||||
import com.kunzisoft.keepass.education.EntryEditActivityEducation
|
import com.kunzisoft.keepass.education.EntryEditActivityEducation
|
||||||
import com.kunzisoft.keepass.model.Field
|
import com.kunzisoft.keepass.model.*
|
||||||
import com.kunzisoft.keepass.model.FocusedEditField
|
import com.kunzisoft.keepass.notifications.AttachmentFileNotificationService
|
||||||
import com.kunzisoft.keepass.notifications.ClipboardEntryNotificationService
|
import com.kunzisoft.keepass.notifications.ClipboardEntryNotificationService
|
||||||
import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_CREATE_ENTRY_TASK
|
import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_CREATE_ENTRY_TASK
|
||||||
import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_UPDATE_ENTRY_TASK
|
import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_UPDATE_ENTRY_TASK
|
||||||
@@ -55,8 +57,10 @@ import com.kunzisoft.keepass.notifications.KeyboardEntryNotificationService
|
|||||||
import com.kunzisoft.keepass.otp.OtpElement
|
import com.kunzisoft.keepass.otp.OtpElement
|
||||||
import com.kunzisoft.keepass.otp.OtpEntryFields
|
import com.kunzisoft.keepass.otp.OtpEntryFields
|
||||||
import com.kunzisoft.keepass.settings.PreferencesUtil
|
import com.kunzisoft.keepass.settings.PreferencesUtil
|
||||||
|
import com.kunzisoft.keepass.tasks.AttachmentFileBinderManager
|
||||||
import com.kunzisoft.keepass.timeout.TimeoutHelper
|
import com.kunzisoft.keepass.timeout.TimeoutHelper
|
||||||
import com.kunzisoft.keepass.utils.MenuUtil
|
import com.kunzisoft.keepass.utils.MenuUtil
|
||||||
|
import com.kunzisoft.keepass.utils.UriUtil
|
||||||
import com.kunzisoft.keepass.view.EntryEditContentsView
|
import com.kunzisoft.keepass.view.EntryEditContentsView
|
||||||
import com.kunzisoft.keepass.view.showActionError
|
import com.kunzisoft.keepass.view.showActionError
|
||||||
import com.kunzisoft.keepass.view.updateLockPaddingLeft
|
import com.kunzisoft.keepass.view.updateLockPaddingLeft
|
||||||
@@ -69,7 +73,9 @@ class EntryEditActivity : LockingActivity(),
|
|||||||
GeneratePasswordDialogFragment.GeneratePasswordListener,
|
GeneratePasswordDialogFragment.GeneratePasswordListener,
|
||||||
SetOTPDialogFragment.CreateOtpListener,
|
SetOTPDialogFragment.CreateOtpListener,
|
||||||
DatePickerDialog.OnDateSetListener,
|
DatePickerDialog.OnDateSetListener,
|
||||||
TimePickerDialog.OnTimeSetListener {
|
TimePickerDialog.OnTimeSetListener,
|
||||||
|
FileTooBigDialogFragment.ActionChooseListener,
|
||||||
|
ReplaceFileDialogFragment.ActionChooseListener {
|
||||||
|
|
||||||
private var mDatabase: Database? = null
|
private var mDatabase: Database? = null
|
||||||
|
|
||||||
@@ -90,6 +96,11 @@ class EntryEditActivity : LockingActivity(),
|
|||||||
|
|
||||||
private var mFocusedEditExtraField: FocusedEditField? = null
|
private var mFocusedEditExtraField: FocusedEditField? = null
|
||||||
|
|
||||||
|
// To manage attachments
|
||||||
|
private var mSelectFileHelper: SelectFileHelper? = null
|
||||||
|
private var mAttachmentFileBinderManager: AttachmentFileBinderManager? = null
|
||||||
|
private var mAllowMultipleAttachments: Boolean = false
|
||||||
|
|
||||||
// Education
|
// Education
|
||||||
private var entryEditActivityEducation: EntryEditActivityEducation? = null
|
private var entryEditActivityEducation: EntryEditActivityEducation? = null
|
||||||
|
|
||||||
@@ -221,10 +232,16 @@ class EntryEditActivity : LockingActivity(),
|
|||||||
isVisible = allowCustomField
|
isVisible = allowCustomField
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Attachment not compatible below KitKat
|
||||||
|
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT) {
|
||||||
|
menu.findItem(R.id.menu_add_attachment).isVisible = false
|
||||||
|
}
|
||||||
|
|
||||||
menu.findItem(R.id.menu_add_otp).apply {
|
menu.findItem(R.id.menu_add_otp).apply {
|
||||||
val allowOTP = mDatabase?.allowOTP == true
|
val allowOTP = mDatabase?.allowOTP == true
|
||||||
isEnabled = allowOTP
|
isEnabled = allowOTP
|
||||||
isVisible = allowOTP
|
// OTP not compatible below KitKat
|
||||||
|
isVisible = allowOTP && Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT
|
||||||
}
|
}
|
||||||
|
|
||||||
setOnMenuItemClickListener { item ->
|
setOnMenuItemClickListener { item ->
|
||||||
@@ -233,6 +250,10 @@ class EntryEditActivity : LockingActivity(),
|
|||||||
addNewCustomField()
|
addNewCustomField()
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
R.id.menu_add_attachment -> {
|
||||||
|
addNewAttachment(item)
|
||||||
|
true
|
||||||
|
}
|
||||||
R.id.menu_add_otp -> {
|
R.id.menu_add_otp -> {
|
||||||
setupOTP()
|
setupOTP()
|
||||||
true
|
true
|
||||||
@@ -242,6 +263,10 @@ class EntryEditActivity : LockingActivity(),
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// To retrieve attachment
|
||||||
|
mSelectFileHelper = SelectFileHelper(this)
|
||||||
|
mAttachmentFileBinderManager = AttachmentFileBinderManager(this)
|
||||||
|
|
||||||
// Save button
|
// Save button
|
||||||
validateButton = findViewById(R.id.entry_edit_validate)
|
validateButton = findViewById(R.id.entry_edit_validate)
|
||||||
validateButton?.setOnClickListener { saveEntry() }
|
validateButton?.setOnClickListener { saveEntry() }
|
||||||
@@ -273,6 +298,41 @@ class EntryEditActivity : LockingActivity(),
|
|||||||
|
|
||||||
// Padding if lock button visible
|
// Padding if lock button visible
|
||||||
entryEditAddToolBar?.updateLockPaddingLeft()
|
entryEditAddToolBar?.updateLockPaddingLeft()
|
||||||
|
|
||||||
|
mAllowMultipleAttachments = mDatabase?.allowMultipleAttachments == true
|
||||||
|
mAttachmentFileBinderManager?.apply {
|
||||||
|
registerProgressTask()
|
||||||
|
onActionTaskListener = object : AttachmentFileNotificationService.ActionTaskListener {
|
||||||
|
override fun onAttachmentAction(fileUri: Uri, entryAttachmentState: EntryAttachmentState) {
|
||||||
|
when (entryAttachmentState.downloadState) {
|
||||||
|
AttachmentState.START -> {
|
||||||
|
// When only one attachment is allowed
|
||||||
|
if (!mAllowMultipleAttachments) {
|
||||||
|
entryEditContentsView?.clearAttachments()
|
||||||
|
}
|
||||||
|
entryEditContentsView?.putAttachment(entryAttachmentState)
|
||||||
|
}
|
||||||
|
AttachmentState.IN_PROGRESS -> {
|
||||||
|
entryEditContentsView?.putAttachment(entryAttachmentState)
|
||||||
|
}
|
||||||
|
AttachmentState.COMPLETE -> {
|
||||||
|
entryEditContentsView?.putAttachment(entryAttachmentState)
|
||||||
|
}
|
||||||
|
AttachmentState.ERROR -> {
|
||||||
|
mDatabase?.removeAttachmentIfNotUsed(entryAttachmentState.attachment)
|
||||||
|
entryEditContentsView?.removeAttachment(entryAttachmentState)
|
||||||
|
}
|
||||||
|
else -> {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onPause() {
|
||||||
|
mAttachmentFileBinderManager?.unregisterProgressTask()
|
||||||
|
|
||||||
|
super.onPause()
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun populateViewsWithEntry(newEntry: Entry) {
|
private fun populateViewsWithEntry(newEntry: Entry) {
|
||||||
@@ -298,8 +358,11 @@ class EntryEditActivity : LockingActivity(),
|
|||||||
assignExtraFields(newEntry.customFields.mapTo(ArrayList()) {
|
assignExtraFields(newEntry.customFields.mapTo(ArrayList()) {
|
||||||
Field(it.key, it.value)
|
Field(it.key, it.value)
|
||||||
}, mFocusedEditExtraField)
|
}, mFocusedEditExtraField)
|
||||||
assignAttachments(newEntry.getAttachments()) { attachment ->
|
|
||||||
newEntry.removeAttachment(attachment)
|
mDatabase?.binaryPool?.let { binaryPool ->
|
||||||
|
assignAttachments(newEntry.getAttachments(binaryPool), StreamDirection.UPLOAD) { attachment ->
|
||||||
|
newEntry.removeAttachment(attachment)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -321,9 +384,14 @@ class EntryEditActivity : LockingActivity(),
|
|||||||
expiryTime = entryView.expiresDate
|
expiryTime = entryView.expiresDate
|
||||||
}
|
}
|
||||||
notes = entryView.notes
|
notes = entryView.notes
|
||||||
entryView.getExtraField().forEach { customField ->
|
entryView.getExtraFields().forEach { customField ->
|
||||||
putExtraField(customField.name, customField.protectedValue)
|
putExtraField(customField.name, customField.protectedValue)
|
||||||
}
|
}
|
||||||
|
mDatabase?.binaryPool?.let { binaryPool ->
|
||||||
|
entryView.getAttachments().forEach {
|
||||||
|
putAttachment(it, binaryPool)
|
||||||
|
}
|
||||||
|
}
|
||||||
mFocusedEditExtraField = entryView.getExtraFieldFocused()
|
mFocusedEditExtraField = entryView.getExtraFieldFocused()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -358,6 +426,63 @@ class EntryEditActivity : LockingActivity(),
|
|||||||
|
|
||||||
override fun onNewCustomFieldCanceled(label: String, protection: Boolean) {}
|
override fun onNewCustomFieldCanceled(label: String, protection: Boolean) {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add a new attachment
|
||||||
|
*/
|
||||||
|
private fun addNewAttachment(item: MenuItem) {
|
||||||
|
mSelectFileHelper?.selectFileOnClickViewListener?.onMenuItemClick(item)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onValidateUploadFileTooBig(attachmentToUploadUri: Uri?, fileName: String?) {
|
||||||
|
if (attachmentToUploadUri != null && fileName != null) {
|
||||||
|
buildNewAttachment(attachmentToUploadUri, fileName)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onValidateReplaceFile(attachmentToUploadUri: Uri?, attachment: Attachment?) {
|
||||||
|
if (attachmentToUploadUri != null && attachment != null) {
|
||||||
|
mAttachmentFileBinderManager?.startUploadAttachment(attachmentToUploadUri, attachment)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun buildNewAttachment(attachmentToUploadUri: Uri, fileName: String) {
|
||||||
|
val compression = mDatabase?.compressionForNewEntry() ?: false
|
||||||
|
mDatabase?.buildNewBinary(applicationContext.filesDir, false, compression)?.let { binaryAttachment ->
|
||||||
|
val entryAttachment = Attachment(fileName, binaryAttachment)
|
||||||
|
// Ask to replace the current attachment
|
||||||
|
if ((mDatabase?.allowMultipleAttachments != true && entryEditContentsView?.containsAttachment() == true) ||
|
||||||
|
entryEditContentsView?.containsAttachment(EntryAttachmentState(entryAttachment, StreamDirection.UPLOAD)) == true) {
|
||||||
|
ReplaceFileDialogFragment.build(attachmentToUploadUri, entryAttachment)
|
||||||
|
.show(supportFragmentManager, "replacementFileFragment")
|
||||||
|
} else {
|
||||||
|
mAttachmentFileBinderManager?.startUploadAttachment(attachmentToUploadUri, entryAttachment)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
|
||||||
|
super.onActivityResult(requestCode, resultCode, data)
|
||||||
|
|
||||||
|
mSelectFileHelper?.onActivityResultCallback(requestCode, resultCode, data) { uri ->
|
||||||
|
uri?.let { attachmentToUploadUri ->
|
||||||
|
// TODO Async to get the name
|
||||||
|
UriUtil.getFileData(this, attachmentToUploadUri)?.also { documentFile ->
|
||||||
|
documentFile.name?.let { fileName ->
|
||||||
|
if (documentFile.length() > MAX_WARNING_BINARY_FILE) {
|
||||||
|
FileTooBigDialogFragment.build(attachmentToUploadUri, fileName)
|
||||||
|
.show(supportFragmentManager, "fileTooBigFragment")
|
||||||
|
} else {
|
||||||
|
buildNewAttachment(attachmentToUploadUri, fileName)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set up OTP (HOTP or TOTP) and add it as extra field
|
||||||
|
*/
|
||||||
private fun setupOTP() {
|
private fun setupOTP() {
|
||||||
// Retrieve the current otpElement if exists
|
// Retrieve the current otpElement if exists
|
||||||
// and open the dialog to set up the OTP
|
// and open the dialog to set up the OTP
|
||||||
|
|||||||
@@ -45,7 +45,7 @@ import com.kunzisoft.keepass.R
|
|||||||
import com.kunzisoft.keepass.activities.dialogs.AssignMasterKeyDialogFragment
|
import com.kunzisoft.keepass.activities.dialogs.AssignMasterKeyDialogFragment
|
||||||
import com.kunzisoft.keepass.activities.helpers.EntrySelectionHelper
|
import com.kunzisoft.keepass.activities.helpers.EntrySelectionHelper
|
||||||
import com.kunzisoft.keepass.activities.helpers.EntrySelectionHelper.KEY_SEARCH_INFO
|
import com.kunzisoft.keepass.activities.helpers.EntrySelectionHelper.KEY_SEARCH_INFO
|
||||||
import com.kunzisoft.keepass.activities.helpers.OpenFileHelper
|
import com.kunzisoft.keepass.activities.helpers.SelectFileHelper
|
||||||
import com.kunzisoft.keepass.activities.selection.SpecialModeActivity
|
import com.kunzisoft.keepass.activities.selection.SpecialModeActivity
|
||||||
import com.kunzisoft.keepass.adapters.FileDatabaseHistoryAdapter
|
import com.kunzisoft.keepass.adapters.FileDatabaseHistoryAdapter
|
||||||
import com.kunzisoft.keepass.app.database.FileDatabaseHistoryAction
|
import com.kunzisoft.keepass.app.database.FileDatabaseHistoryAction
|
||||||
@@ -82,7 +82,7 @@ class FileDatabaseSelectActivity : SpecialModeActivity(),
|
|||||||
|
|
||||||
private var mDatabaseFileUri: Uri? = null
|
private var mDatabaseFileUri: Uri? = null
|
||||||
|
|
||||||
private var mOpenFileHelper: OpenFileHelper? = null
|
private var mSelectFileHelper: SelectFileHelper? = null
|
||||||
|
|
||||||
private var mProgressDatabaseTaskProvider: ProgressDatabaseTaskProvider? = null
|
private var mProgressDatabaseTaskProvider: ProgressDatabaseTaskProvider? = null
|
||||||
|
|
||||||
@@ -108,10 +108,10 @@ class FileDatabaseSelectActivity : SpecialModeActivity(),
|
|||||||
createDatabaseButtonView?.setOnClickListener { createNewFile() }
|
createDatabaseButtonView?.setOnClickListener { createNewFile() }
|
||||||
|
|
||||||
// Open database button
|
// Open database button
|
||||||
mOpenFileHelper = OpenFileHelper(this)
|
mSelectFileHelper = SelectFileHelper(this)
|
||||||
openDatabaseButtonView = findViewById(R.id.open_keyfile_button)
|
openDatabaseButtonView = findViewById(R.id.open_keyfile_button)
|
||||||
openDatabaseButtonView?.apply {
|
openDatabaseButtonView?.apply {
|
||||||
mOpenFileHelper?.openFileOnClickViewListener?.let {
|
mSelectFileHelper?.selectFileOnClickViewListener?.let {
|
||||||
setOnClickListener(it)
|
setOnClickListener(it)
|
||||||
setOnLongClickListener(it)
|
setOnLongClickListener(it)
|
||||||
}
|
}
|
||||||
@@ -389,7 +389,7 @@ class FileDatabaseSelectActivity : SpecialModeActivity(),
|
|||||||
AutofillHelper.onActivityResultSetResultAndFinish(this, requestCode, resultCode, data)
|
AutofillHelper.onActivityResultSetResultAndFinish(this, requestCode, resultCode, data)
|
||||||
}
|
}
|
||||||
|
|
||||||
mOpenFileHelper?.onActivityResultCallback(requestCode, resultCode, data) { uri ->
|
mSelectFileHelper?.onActivityResultCallback(requestCode, resultCode, data) { uri ->
|
||||||
if (uri != null) {
|
if (uri != null) {
|
||||||
launchPasswordActivityWithPath(uri)
|
launchPasswordActivityWithPath(uri)
|
||||||
}
|
}
|
||||||
@@ -445,7 +445,7 @@ class FileDatabaseSelectActivity : SpecialModeActivity(),
|
|||||||
openDatabaseButtonView!!,
|
openDatabaseButtonView!!,
|
||||||
{tapTargetView ->
|
{tapTargetView ->
|
||||||
tapTargetView?.let {
|
tapTargetView?.let {
|
||||||
mOpenFileHelper?.openFileOnClickViewListener?.onClick(it)
|
mSelectFileHelper?.selectFileOnClickViewListener?.onClick(it)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{}
|
{}
|
||||||
|
|||||||
@@ -44,8 +44,8 @@ import com.kunzisoft.keepass.R
|
|||||||
import com.kunzisoft.keepass.activities.dialogs.DuplicateUuidDialog
|
import com.kunzisoft.keepass.activities.dialogs.DuplicateUuidDialog
|
||||||
import com.kunzisoft.keepass.activities.helpers.EntrySelectionHelper
|
import com.kunzisoft.keepass.activities.helpers.EntrySelectionHelper
|
||||||
import com.kunzisoft.keepass.activities.helpers.EntrySelectionHelper.KEY_SEARCH_INFO
|
import com.kunzisoft.keepass.activities.helpers.EntrySelectionHelper.KEY_SEARCH_INFO
|
||||||
import com.kunzisoft.keepass.activities.helpers.OpenFileHelper
|
|
||||||
import com.kunzisoft.keepass.activities.helpers.ReadOnlyHelper
|
import com.kunzisoft.keepass.activities.helpers.ReadOnlyHelper
|
||||||
|
import com.kunzisoft.keepass.activities.helpers.SelectFileHelper
|
||||||
import com.kunzisoft.keepass.activities.lock.LockingActivity
|
import com.kunzisoft.keepass.activities.lock.LockingActivity
|
||||||
import com.kunzisoft.keepass.activities.selection.SpecialModeActivity
|
import com.kunzisoft.keepass.activities.selection.SpecialModeActivity
|
||||||
import com.kunzisoft.keepass.app.database.CipherDatabaseEntity
|
import com.kunzisoft.keepass.app.database.CipherDatabaseEntity
|
||||||
@@ -64,7 +64,9 @@ import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Compa
|
|||||||
import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.MASTER_PASSWORD_KEY
|
import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.MASTER_PASSWORD_KEY
|
||||||
import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.READ_ONLY_KEY
|
import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.READ_ONLY_KEY
|
||||||
import com.kunzisoft.keepass.settings.PreferencesUtil
|
import com.kunzisoft.keepass.settings.PreferencesUtil
|
||||||
import com.kunzisoft.keepass.utils.*
|
import com.kunzisoft.keepass.utils.BACK_PREVIOUS_KEYBOARD_ACTION
|
||||||
|
import com.kunzisoft.keepass.utils.MenuUtil
|
||||||
|
import com.kunzisoft.keepass.utils.UriUtil
|
||||||
import com.kunzisoft.keepass.view.AdvancedUnlockInfoView
|
import com.kunzisoft.keepass.view.AdvancedUnlockInfoView
|
||||||
import com.kunzisoft.keepass.view.KeyFileSelectionView
|
import com.kunzisoft.keepass.view.KeyFileSelectionView
|
||||||
import com.kunzisoft.keepass.view.asError
|
import com.kunzisoft.keepass.view.asError
|
||||||
@@ -92,7 +94,7 @@ open class PasswordActivity : SpecialModeActivity() {
|
|||||||
private var mDatabaseKeyFileUri: Uri? = null
|
private var mDatabaseKeyFileUri: Uri? = null
|
||||||
|
|
||||||
private var mRememberKeyFile: Boolean = false
|
private var mRememberKeyFile: Boolean = false
|
||||||
private var mOpenFileHelper: OpenFileHelper? = null
|
private var mSelectFileHelper: SelectFileHelper? = null
|
||||||
|
|
||||||
private var mPermissionAsked = false
|
private var mPermissionAsked = false
|
||||||
private var readOnly: Boolean = false
|
private var readOnly: Boolean = false
|
||||||
@@ -136,9 +138,9 @@ open class PasswordActivity : SpecialModeActivity() {
|
|||||||
readOnly = ReadOnlyHelper.retrieveReadOnlyFromInstanceStateOrPreference(this, savedInstanceState)
|
readOnly = ReadOnlyHelper.retrieveReadOnlyFromInstanceStateOrPreference(this, savedInstanceState)
|
||||||
mRememberKeyFile = PreferencesUtil.rememberKeyFileLocations(this)
|
mRememberKeyFile = PreferencesUtil.rememberKeyFileLocations(this)
|
||||||
|
|
||||||
mOpenFileHelper = OpenFileHelper(this@PasswordActivity)
|
mSelectFileHelper = SelectFileHelper(this@PasswordActivity)
|
||||||
keyFileSelectionView?.apply {
|
keyFileSelectionView?.apply {
|
||||||
mOpenFileHelper?.openFileOnClickViewListener?.let {
|
mSelectFileHelper?.selectFileOnClickViewListener?.let {
|
||||||
setOnClickListener(it)
|
setOnClickListener(it)
|
||||||
setOnLongClickListener(it)
|
setOnLongClickListener(it)
|
||||||
}
|
}
|
||||||
@@ -747,7 +749,7 @@ open class PasswordActivity : SpecialModeActivity() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var keyFileResult = false
|
var keyFileResult = false
|
||||||
mOpenFileHelper?.let {
|
mSelectFileHelper?.let {
|
||||||
keyFileResult = it.onActivityResultCallback(requestCode, resultCode, data
|
keyFileResult = it.onActivityResultCallback(requestCode, resultCode, data
|
||||||
) { uri ->
|
) { uri ->
|
||||||
if (uri != null) {
|
if (uri != null) {
|
||||||
|
|||||||
@@ -25,16 +25,16 @@ import android.content.DialogInterface
|
|||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import com.google.android.material.textfield.TextInputLayout
|
|
||||||
import androidx.fragment.app.DialogFragment
|
|
||||||
import androidx.appcompat.app.AlertDialog
|
|
||||||
import android.text.Editable
|
import android.text.Editable
|
||||||
import android.text.TextWatcher
|
import android.text.TextWatcher
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.widget.CompoundButton
|
import android.widget.CompoundButton
|
||||||
import android.widget.TextView
|
import android.widget.TextView
|
||||||
|
import androidx.appcompat.app.AlertDialog
|
||||||
|
import androidx.fragment.app.DialogFragment
|
||||||
|
import com.google.android.material.textfield.TextInputLayout
|
||||||
import com.kunzisoft.keepass.R
|
import com.kunzisoft.keepass.R
|
||||||
import com.kunzisoft.keepass.activities.helpers.OpenFileHelper
|
import com.kunzisoft.keepass.activities.helpers.SelectFileHelper
|
||||||
import com.kunzisoft.keepass.view.KeyFileSelectionView
|
import com.kunzisoft.keepass.view.KeyFileSelectionView
|
||||||
|
|
||||||
class AssignMasterKeyDialogFragment : DialogFragment() {
|
class AssignMasterKeyDialogFragment : DialogFragment() {
|
||||||
@@ -56,7 +56,7 @@ class AssignMasterKeyDialogFragment : DialogFragment() {
|
|||||||
|
|
||||||
private var mListener: AssignPasswordDialogListener? = null
|
private var mListener: AssignPasswordDialogListener? = null
|
||||||
|
|
||||||
private var mOpenFileHelper: OpenFileHelper? = null
|
private var mSelectFileHelper: SelectFileHelper? = null
|
||||||
|
|
||||||
private val passwordTextWatcher = object : TextWatcher {
|
private val passwordTextWatcher = object : TextWatcher {
|
||||||
override fun beforeTextChanged(charSequence: CharSequence, i: Int, i1: Int, i2: Int) {}
|
override fun beforeTextChanged(charSequence: CharSequence, i: Int, i1: Int, i2: Int) {}
|
||||||
@@ -113,10 +113,10 @@ class AssignMasterKeyDialogFragment : DialogFragment() {
|
|||||||
keyFileCheckBox = rootView?.findViewById(R.id.keyfile_checkox)
|
keyFileCheckBox = rootView?.findViewById(R.id.keyfile_checkox)
|
||||||
keyFileSelectionView = rootView?.findViewById(R.id.keyfile_selection)
|
keyFileSelectionView = rootView?.findViewById(R.id.keyfile_selection)
|
||||||
|
|
||||||
mOpenFileHelper = OpenFileHelper(this)
|
mSelectFileHelper = SelectFileHelper(this)
|
||||||
keyFileSelectionView?.apply {
|
keyFileSelectionView?.apply {
|
||||||
setOnClickListener(mOpenFileHelper?.openFileOnClickViewListener)
|
setOnClickListener(mSelectFileHelper?.selectFileOnClickViewListener)
|
||||||
setOnLongClickListener(mOpenFileHelper?.openFileOnClickViewListener)
|
setOnLongClickListener(mSelectFileHelper?.selectFileOnClickViewListener)
|
||||||
}
|
}
|
||||||
|
|
||||||
val dialog = builder.create()
|
val dialog = builder.create()
|
||||||
@@ -249,8 +249,7 @@ class AssignMasterKeyDialogFragment : DialogFragment() {
|
|||||||
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
|
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
|
||||||
super.onActivityResult(requestCode, resultCode, data)
|
super.onActivityResult(requestCode, resultCode, data)
|
||||||
|
|
||||||
mOpenFileHelper?.onActivityResultCallback(requestCode, resultCode, data
|
mSelectFileHelper?.onActivityResultCallback(requestCode, resultCode, data) { uri ->
|
||||||
) { uri ->
|
|
||||||
uri?.let { pathUri ->
|
uri?.let { pathUri ->
|
||||||
keyFileCheckBox?.isChecked = true
|
keyFileCheckBox?.isChecked = true
|
||||||
keyFileSelectionView?.uri = pathUri
|
keyFileSelectionView?.uri = pathUri
|
||||||
|
|||||||
@@ -0,0 +1,92 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2019 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.activities.dialogs
|
||||||
|
|
||||||
|
import android.app.Dialog
|
||||||
|
import android.content.Context
|
||||||
|
import android.net.Uri
|
||||||
|
import android.os.Bundle
|
||||||
|
import android.text.SpannableStringBuilder
|
||||||
|
import androidx.appcompat.app.AlertDialog
|
||||||
|
import androidx.fragment.app.DialogFragment
|
||||||
|
import com.kunzisoft.keepass.R
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Custom Dialog to confirm big file to upload
|
||||||
|
*/
|
||||||
|
class FileTooBigDialogFragment : DialogFragment() {
|
||||||
|
|
||||||
|
private var mActionChooseListener: ActionChooseListener? = null
|
||||||
|
|
||||||
|
override fun onAttach(context: Context) {
|
||||||
|
super.onAttach(context)
|
||||||
|
// Verify that the host activity implements the callback interface
|
||||||
|
try {
|
||||||
|
mActionChooseListener = context as ActionChooseListener
|
||||||
|
} catch (e: ClassCastException) {
|
||||||
|
throw ClassCastException(context.toString()
|
||||||
|
+ " must implement " + ActionChooseListener::class.java.name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
|
||||||
|
activity?.let { activity ->
|
||||||
|
// Use the Builder class for convenient dialog construction
|
||||||
|
val builder = AlertDialog.Builder(activity)
|
||||||
|
builder.setMessage(SpannableStringBuilder().apply {
|
||||||
|
append(getString(R.string.warning_file_too_big))
|
||||||
|
append("\n\n")
|
||||||
|
append(getString(R.string.warning_sure_add_file))
|
||||||
|
})
|
||||||
|
builder.setPositiveButton(android.R.string.yes) { _, _ ->
|
||||||
|
mActionChooseListener?.onValidateUploadFileTooBig(
|
||||||
|
arguments?.getParcelable(KEY_FILE_URI),
|
||||||
|
arguments?.getString(KEY_FILE_NAME))
|
||||||
|
}
|
||||||
|
builder.setNegativeButton(android.R.string.no) { _, _ ->
|
||||||
|
dismiss()
|
||||||
|
}
|
||||||
|
// Create the AlertDialog object and return it
|
||||||
|
return builder.create()
|
||||||
|
}
|
||||||
|
return super.onCreateDialog(savedInstanceState)
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ActionChooseListener {
|
||||||
|
fun onValidateUploadFileTooBig(attachmentToUploadUri: Uri?, fileName: String?)
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
const val MAX_WARNING_BINARY_FILE = 5242880
|
||||||
|
|
||||||
|
private const val KEY_FILE_URI = "KEY_FILE_URI"
|
||||||
|
private const val KEY_FILE_NAME = "KEY_FILE_NAME"
|
||||||
|
|
||||||
|
fun build(attachmentToUploadUri: Uri,
|
||||||
|
fileName: String): FileTooBigDialogFragment {
|
||||||
|
val fragment = FileTooBigDialogFragment()
|
||||||
|
fragment.arguments = Bundle().apply {
|
||||||
|
putParcelable(KEY_FILE_URI, attachmentToUploadUri)
|
||||||
|
putString(KEY_FILE_NAME, fileName)
|
||||||
|
}
|
||||||
|
return fragment
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,91 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2019 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.activities.dialogs
|
||||||
|
|
||||||
|
import android.app.Dialog
|
||||||
|
import android.content.Context
|
||||||
|
import android.net.Uri
|
||||||
|
import android.os.Bundle
|
||||||
|
import android.text.SpannableStringBuilder
|
||||||
|
import androidx.appcompat.app.AlertDialog
|
||||||
|
import androidx.fragment.app.DialogFragment
|
||||||
|
import com.kunzisoft.keepass.R
|
||||||
|
import com.kunzisoft.keepass.database.element.Attachment
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Custom Dialog to confirm big file to upload
|
||||||
|
*/
|
||||||
|
class ReplaceFileDialogFragment : DialogFragment() {
|
||||||
|
|
||||||
|
private var mActionChooseListener: ActionChooseListener? = null
|
||||||
|
|
||||||
|
override fun onAttach(context: Context) {
|
||||||
|
super.onAttach(context)
|
||||||
|
// Verify that the host activity implements the callback interface
|
||||||
|
try {
|
||||||
|
mActionChooseListener = context as ActionChooseListener
|
||||||
|
} catch (e: ClassCastException) {
|
||||||
|
throw ClassCastException(context.toString()
|
||||||
|
+ " must implement " + ActionChooseListener::class.java.name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
|
||||||
|
activity?.let { activity ->
|
||||||
|
// Use the Builder class for convenient dialog construction
|
||||||
|
val builder = AlertDialog.Builder(activity)
|
||||||
|
builder.setMessage(SpannableStringBuilder().apply {
|
||||||
|
append(getString(R.string.warning_replace_file))
|
||||||
|
append("\n\n")
|
||||||
|
append(getString(R.string.warning_sure_add_file))
|
||||||
|
})
|
||||||
|
builder.setPositiveButton(android.R.string.yes) { _, _ ->
|
||||||
|
mActionChooseListener?.onValidateReplaceFile(
|
||||||
|
arguments?.getParcelable(KEY_FILE_URI),
|
||||||
|
arguments?.getParcelable(KEY_ENTRY_ATTACHMENT))
|
||||||
|
}
|
||||||
|
builder.setNegativeButton(android.R.string.no) { _, _ ->
|
||||||
|
dismiss()
|
||||||
|
}
|
||||||
|
// Create the AlertDialog object and return it
|
||||||
|
return builder.create()
|
||||||
|
}
|
||||||
|
return super.onCreateDialog(savedInstanceState)
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ActionChooseListener {
|
||||||
|
fun onValidateReplaceFile(attachmentToUploadUri: Uri?, attachment: Attachment?)
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
private const val KEY_FILE_URI = "KEY_FILE_URI"
|
||||||
|
private const val KEY_ENTRY_ATTACHMENT = "KEY_ENTRY_ATTACHMENT"
|
||||||
|
|
||||||
|
fun build(attachmentToUploadUri: Uri,
|
||||||
|
attachment: Attachment): ReplaceFileDialogFragment {
|
||||||
|
val fragment = ReplaceFileDialogFragment()
|
||||||
|
fragment.arguments = Bundle().apply {
|
||||||
|
putParcelable(KEY_FILE_URI, attachmentToUploadUri)
|
||||||
|
putParcelable(KEY_ENTRY_ATTACHMENT, attachment)
|
||||||
|
}
|
||||||
|
return fragment
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -28,19 +28,20 @@ import android.content.pm.PackageManager
|
|||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
|
import android.view.MenuItem
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import androidx.fragment.app.Fragment
|
import androidx.fragment.app.Fragment
|
||||||
import androidx.fragment.app.FragmentActivity
|
import androidx.fragment.app.FragmentActivity
|
||||||
import com.kunzisoft.keepass.activities.dialogs.FileManagerDialogFragment
|
import com.kunzisoft.keepass.activities.dialogs.FileManagerDialogFragment
|
||||||
import com.kunzisoft.keepass.utils.UriUtil
|
import com.kunzisoft.keepass.utils.UriUtil
|
||||||
|
|
||||||
class OpenFileHelper {
|
class SelectFileHelper {
|
||||||
|
|
||||||
private var activity: Activity? = null
|
private var activity: Activity? = null
|
||||||
private var fragment: Fragment? = null
|
private var fragment: Fragment? = null
|
||||||
|
|
||||||
val openFileOnClickViewListener: OpenFileOnClickViewListener
|
val selectFileOnClickViewListener: SelectFileOnClickViewListener
|
||||||
get() = OpenFileOnClickViewListener()
|
get() = SelectFileOnClickViewListener()
|
||||||
|
|
||||||
constructor(context: Activity) {
|
constructor(context: Activity) {
|
||||||
this.activity = context
|
this.activity = context
|
||||||
@@ -52,7 +53,10 @@ class OpenFileHelper {
|
|||||||
this.fragment = context
|
this.fragment = context
|
||||||
}
|
}
|
||||||
|
|
||||||
inner class OpenFileOnClickViewListener : View.OnClickListener, View.OnLongClickListener {
|
inner class SelectFileOnClickViewListener :
|
||||||
|
View.OnClickListener,
|
||||||
|
View.OnLongClickListener,
|
||||||
|
MenuItem.OnMenuItemClickListener {
|
||||||
|
|
||||||
private fun onAbstractClick(longClick: Boolean = false) {
|
private fun onAbstractClick(longClick: Boolean = false) {
|
||||||
try {
|
try {
|
||||||
@@ -85,6 +89,11 @@ class OpenFileHelper {
|
|||||||
onAbstractClick(true)
|
onAbstractClick(true)
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun onMenuItemClick(item: MenuItem?): Boolean {
|
||||||
|
onAbstractClick()
|
||||||
|
return true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressLint("InlinedApi")
|
@SuppressLint("InlinedApi")
|
||||||
@@ -32,6 +32,14 @@ abstract class AnimatedItemsAdapter<Item, T: RecyclerView.ViewHolder>(val contex
|
|||||||
onListSizeChangedListener?.invoke(previousSize, itemsList.size)
|
onListSizeChangedListener?.invoke(previousSize, itemsList.size)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
open fun isEmpty(): Boolean {
|
||||||
|
return itemsList.isEmpty()
|
||||||
|
}
|
||||||
|
|
||||||
|
open fun contains(item: Item): Boolean {
|
||||||
|
return itemsList.contains(item)
|
||||||
|
}
|
||||||
|
|
||||||
open fun putItem(item: Item) {
|
open fun putItem(item: Item) {
|
||||||
val previousSize = itemsList.size
|
val previousSize = itemsList.size
|
||||||
if (itemsList.contains(item)) {
|
if (itemsList.contains(item)) {
|
||||||
@@ -46,6 +54,13 @@ abstract class AnimatedItemsAdapter<Item, T: RecyclerView.ViewHolder>(val contex
|
|||||||
onListSizeChangedListener?.invoke(previousSize, itemsList.size)
|
onListSizeChangedListener?.invoke(previousSize, itemsList.size)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
open fun removeItem(item: Item) {
|
||||||
|
if (itemsList.contains(item)) {
|
||||||
|
mItemToRemove = item
|
||||||
|
notifyItemChanged(itemsList.indexOf(item))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fun onBindDeleteButton(holder: T, deleteButton: View, item: Item, position: Int) {
|
fun onBindDeleteButton(holder: T, deleteButton: View, item: Item, position: Int) {
|
||||||
deleteButton.apply {
|
deleteButton.apply {
|
||||||
visibility = View.VISIBLE
|
visibility = View.VISIBLE
|
||||||
|
|||||||
@@ -23,36 +23,34 @@ import android.content.Context
|
|||||||
import android.text.format.Formatter
|
import android.text.format.Formatter
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
|
import android.widget.ImageView
|
||||||
import android.widget.ProgressBar
|
import android.widget.ProgressBar
|
||||||
import android.widget.TextView
|
import android.widget.TextView
|
||||||
import androidx.recyclerview.widget.RecyclerView
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
import com.kunzisoft.keepass.R
|
import com.kunzisoft.keepass.R
|
||||||
import com.kunzisoft.keepass.database.element.Database
|
|
||||||
import com.kunzisoft.keepass.database.element.database.CompressionAlgorithm
|
import com.kunzisoft.keepass.database.element.database.CompressionAlgorithm
|
||||||
import com.kunzisoft.keepass.model.AttachmentState
|
import com.kunzisoft.keepass.model.AttachmentState
|
||||||
import com.kunzisoft.keepass.model.EntryAttachment
|
import com.kunzisoft.keepass.model.EntryAttachmentState
|
||||||
|
import com.kunzisoft.keepass.model.StreamDirection
|
||||||
|
|
||||||
class EntryAttachmentsItemsAdapter(context: Context, private val editable: Boolean)
|
class EntryAttachmentsItemsAdapter(context: Context)
|
||||||
: AnimatedItemsAdapter<EntryAttachment, EntryAttachmentsItemsAdapter.EntryBinariesViewHolder>(context) {
|
: AnimatedItemsAdapter<EntryAttachmentState, EntryAttachmentsItemsAdapter.EntryBinariesViewHolder>(context) {
|
||||||
|
|
||||||
var onItemClickListener: ((item: EntryAttachment)->Unit)? = null
|
var onItemClickListener: ((item: EntryAttachmentState)->Unit)? = null
|
||||||
|
|
||||||
private val mDatabase = Database.getInstance()
|
|
||||||
|
|
||||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): EntryBinariesViewHolder {
|
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): EntryBinariesViewHolder {
|
||||||
return EntryBinariesViewHolder(inflater.inflate(R.layout.item_attachment, parent, false))
|
return EntryBinariesViewHolder(inflater.inflate(R.layout.item_attachment, parent, false))
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onBindViewHolder(holder: EntryBinariesViewHolder, position: Int) {
|
override fun onBindViewHolder(holder: EntryBinariesViewHolder, position: Int) {
|
||||||
val entryAttachment = itemsList[position]
|
val entryAttachmentState = itemsList[position]
|
||||||
|
|
||||||
holder.itemView.visibility = View.VISIBLE
|
holder.itemView.visibility = View.VISIBLE
|
||||||
holder.binaryFileTitle.text = entryAttachment.name
|
holder.binaryFileTitle.text = entryAttachmentState.attachment.name
|
||||||
holder.binaryFileSize.text = Formatter.formatFileSize(context,
|
holder.binaryFileSize.text = Formatter.formatFileSize(context,
|
||||||
entryAttachment.binaryAttachment.length())
|
entryAttachmentState.attachment.binaryAttachment.length())
|
||||||
holder.binaryFileCompression.apply {
|
holder.binaryFileCompression.apply {
|
||||||
if (mDatabase.compressionAlgorithm == CompressionAlgorithm.GZip
|
if (entryAttachmentState.attachment.binaryAttachment.isCompressed) {
|
||||||
|| entryAttachment.binaryAttachment.isCompressed == true) {
|
|
||||||
text = CompressionAlgorithm.GZip.getName(context.resources)
|
text = CompressionAlgorithm.GZip.getName(context.resources)
|
||||||
visibility = View.VISIBLE
|
visibility = View.VISIBLE
|
||||||
} else {
|
} else {
|
||||||
@@ -60,42 +58,60 @@ class EntryAttachmentsItemsAdapter(context: Context, private val editable: Boole
|
|||||||
visibility = View.GONE
|
visibility = View.GONE
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (editable) {
|
when (entryAttachmentState.streamDirection) {
|
||||||
holder.binaryFileProgressContainer.visibility = View.GONE
|
StreamDirection.UPLOAD -> {
|
||||||
holder.binaryFileDeleteButton.apply {
|
holder.binaryFileProgressIcon.isActivated = true
|
||||||
visibility = View.VISIBLE
|
when (entryAttachmentState.downloadState) {
|
||||||
onBindDeleteButton(holder, this, entryAttachment, position)
|
AttachmentState.START,
|
||||||
}
|
AttachmentState.IN_PROGRESS -> {
|
||||||
} else {
|
holder.binaryFileProgressContainer.visibility = View.VISIBLE
|
||||||
holder.binaryFileProgressContainer.visibility = View.VISIBLE
|
holder.binaryFileProgress.apply {
|
||||||
holder.binaryFileDeleteButton.visibility = View.GONE
|
visibility = View.VISIBLE
|
||||||
holder.binaryFileProgress.apply {
|
progress = entryAttachmentState.downloadProgression
|
||||||
visibility = when (entryAttachment.downloadState) {
|
}
|
||||||
AttachmentState.NULL, AttachmentState.COMPLETE, AttachmentState.ERROR -> View.GONE
|
holder.binaryFileDeleteButton.apply {
|
||||||
AttachmentState.START, AttachmentState.IN_PROGRESS -> View.VISIBLE
|
visibility = View.GONE
|
||||||
|
setOnClickListener(null)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
AttachmentState.NULL,
|
||||||
|
AttachmentState.ERROR,
|
||||||
|
AttachmentState.COMPLETE -> {
|
||||||
|
holder.binaryFileProgressContainer.visibility = View.GONE
|
||||||
|
holder.binaryFileProgress.visibility = View.GONE
|
||||||
|
holder.binaryFileDeleteButton.apply {
|
||||||
|
visibility = View.VISIBLE
|
||||||
|
onBindDeleteButton(holder, this, entryAttachmentState, position)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
progress = entryAttachment.downloadProgression
|
holder.itemView.setOnClickListener(null)
|
||||||
}
|
}
|
||||||
holder.itemView.setOnClickListener {
|
StreamDirection.DOWNLOAD -> {
|
||||||
onItemClickListener?.invoke(entryAttachment)
|
holder.binaryFileProgressIcon.isActivated = false
|
||||||
|
holder.binaryFileProgressContainer.visibility = View.VISIBLE
|
||||||
|
holder.binaryFileDeleteButton.visibility = View.GONE
|
||||||
|
holder.binaryFileProgress.apply {
|
||||||
|
visibility = when (entryAttachmentState.downloadState) {
|
||||||
|
AttachmentState.NULL, AttachmentState.COMPLETE, AttachmentState.ERROR -> View.GONE
|
||||||
|
AttachmentState.START, AttachmentState.IN_PROGRESS -> View.VISIBLE
|
||||||
|
}
|
||||||
|
progress = entryAttachmentState.downloadProgression
|
||||||
|
}
|
||||||
|
holder.itemView.setOnClickListener {
|
||||||
|
onItemClickListener?.invoke(entryAttachmentState)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun updateProgress(entryAttachment: EntryAttachment) {
|
class EntryBinariesViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
|
||||||
val indexEntryAttachment = itemsList.indexOfLast { current -> current.name == entryAttachment.name }
|
|
||||||
if (indexEntryAttachment != -1) {
|
|
||||||
itemsList[indexEntryAttachment] = entryAttachment
|
|
||||||
notifyItemChanged(indexEntryAttachment)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
inner class EntryBinariesViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
|
|
||||||
|
|
||||||
var binaryFileTitle: TextView = itemView.findViewById(R.id.item_attachment_title)
|
var binaryFileTitle: TextView = itemView.findViewById(R.id.item_attachment_title)
|
||||||
var binaryFileSize: TextView = itemView.findViewById(R.id.item_attachment_size)
|
var binaryFileSize: TextView = itemView.findViewById(R.id.item_attachment_size)
|
||||||
var binaryFileCompression: TextView = itemView.findViewById(R.id.item_attachment_compression)
|
var binaryFileCompression: TextView = itemView.findViewById(R.id.item_attachment_compression)
|
||||||
var binaryFileProgressContainer: View = itemView.findViewById(R.id.item_attachment_progress_container)
|
var binaryFileProgressContainer: View = itemView.findViewById(R.id.item_attachment_progress_container)
|
||||||
|
var binaryFileProgressIcon: ImageView = itemView.findViewById(R.id.item_attachment_icon)
|
||||||
var binaryFileProgress: ProgressBar = itemView.findViewById(R.id.item_attachment_progress)
|
var binaryFileProgress: ProgressBar = itemView.findViewById(R.id.item_attachment_progress)
|
||||||
var binaryFileDeleteButton: View = itemView.findViewById(R.id.item_attachment_delete_button)
|
var binaryFileDeleteButton: View = itemView.findViewById(R.id.item_attachment_delete_button)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -338,6 +338,9 @@ class NodeAdapter (private val context: Context)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
holder.attachmentIcon?.visibility =
|
||||||
|
if (entry.containsAttachment()) View.VISIBLE else View.GONE
|
||||||
|
|
||||||
mDatabase.stopManageEntry(entry)
|
mDatabase.stopManageEntry(entry)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -391,6 +394,7 @@ class NodeAdapter (private val context: Context)
|
|||||||
var text: TextView = itemView.findViewById(R.id.node_text)
|
var text: TextView = itemView.findViewById(R.id.node_text)
|
||||||
var subText: TextView = itemView.findViewById(R.id.node_subtext)
|
var subText: TextView = itemView.findViewById(R.id.node_subtext)
|
||||||
var numberChildren: TextView? = itemView.findViewById(R.id.node_child_numbers)
|
var numberChildren: TextView? = itemView.findViewById(R.id.node_child_numbers)
|
||||||
|
var attachmentIcon: ImageView? = itemView.findViewById(R.id.node_attachment_icon)
|
||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
|
|||||||
@@ -64,6 +64,10 @@ class DeleteNodesRunnable(context: Context,
|
|||||||
} else {
|
} else {
|
||||||
database.deleteEntry(currentNode)
|
database.deleteEntry(currentNode)
|
||||||
}
|
}
|
||||||
|
// Remove the oldest attachments
|
||||||
|
currentNode.getAttachments(database.binaryPool).forEach {
|
||||||
|
database.removeAttachmentIfNotUsed(it)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,6 +20,7 @@
|
|||||||
package com.kunzisoft.keepass.database.action.node
|
package com.kunzisoft.keepass.database.action.node
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
|
import com.kunzisoft.keepass.database.element.Attachment
|
||||||
import com.kunzisoft.keepass.database.element.Database
|
import com.kunzisoft.keepass.database.element.Database
|
||||||
import com.kunzisoft.keepass.database.element.Entry
|
import com.kunzisoft.keepass.database.element.Entry
|
||||||
import com.kunzisoft.keepass.database.element.node.Node
|
import com.kunzisoft.keepass.database.element.node.Node
|
||||||
@@ -40,16 +41,34 @@ class UpdateEntryRunnable constructor(
|
|||||||
// WARNING : Re attribute parent removed in entry edit activity to save memory
|
// WARNING : Re attribute parent removed in entry edit activity to save memory
|
||||||
mNewEntry.addParentFrom(mOldEntry)
|
mNewEntry.addParentFrom(mOldEntry)
|
||||||
|
|
||||||
|
// Build oldest attachments
|
||||||
|
val oldEntryAttachments = mOldEntry.getAttachments(database.binaryPool)
|
||||||
|
val newEntryAttachments = mNewEntry.getAttachments(database.binaryPool)
|
||||||
|
val attachmentsToRemove = ArrayList<Attachment>(oldEntryAttachments)
|
||||||
|
// Not use equals because only check name
|
||||||
|
newEntryAttachments.forEach { newAttachment ->
|
||||||
|
oldEntryAttachments.forEach { oldAttachment ->
|
||||||
|
if (oldAttachment.name == newAttachment.name
|
||||||
|
&& oldAttachment.binaryAttachment == newAttachment.binaryAttachment)
|
||||||
|
attachmentsToRemove.remove(oldAttachment)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Update entry with new values
|
// Update entry with new values
|
||||||
mOldEntry.updateWith(mNewEntry)
|
mOldEntry.updateWith(mNewEntry)
|
||||||
mNewEntry.touch(modified = true, touchParents = true)
|
mNewEntry.touch(modified = true, touchParents = true)
|
||||||
|
|
||||||
// Create an entry history (an entry history don't have history)
|
// Create an entry history (an entry history don't have history)
|
||||||
mOldEntry.addEntryToHistory(Entry(mBackupEntryHistory, copyHistory = false))
|
mOldEntry.addEntryToHistory(Entry(mBackupEntryHistory, copyHistory = false))
|
||||||
database.removeOldestEntryHistory(mOldEntry)
|
database.removeOldestEntryHistory(mOldEntry, database.binaryPool)
|
||||||
|
|
||||||
// Only change data in index
|
// Only change data in index
|
||||||
database.updateEntry(mOldEntry)
|
database.updateEntry(mOldEntry)
|
||||||
|
|
||||||
|
// Remove oldest attachments
|
||||||
|
attachmentsToRemove.forEach {
|
||||||
|
database.removeAttachmentIfNotUsed(it)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun nodeFinish(): ActionNodesValues {
|
override fun nodeFinish(): ActionNodesValues {
|
||||||
|
|||||||
@@ -0,0 +1,69 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2019 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.element
|
||||||
|
|
||||||
|
import android.os.Parcel
|
||||||
|
import android.os.Parcelable
|
||||||
|
import com.kunzisoft.keepass.database.element.database.BinaryAttachment
|
||||||
|
|
||||||
|
data class Attachment(var name: String,
|
||||||
|
var binaryAttachment: BinaryAttachment) : Parcelable {
|
||||||
|
|
||||||
|
constructor(parcel: Parcel) : this(
|
||||||
|
parcel.readString() ?: "",
|
||||||
|
parcel.readParcelable(BinaryAttachment::class.java.classLoader) ?: BinaryAttachment()
|
||||||
|
)
|
||||||
|
|
||||||
|
override fun writeToParcel(parcel: Parcel, flags: Int) {
|
||||||
|
parcel.writeString(name)
|
||||||
|
parcel.writeParcelable(binaryAttachment, flags)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun describeContents(): Int {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun toString(): String {
|
||||||
|
return "$name at $binaryAttachment"
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun equals(other: Any?): Boolean {
|
||||||
|
if (this === other) return true
|
||||||
|
if (other !is Attachment) return false
|
||||||
|
|
||||||
|
if (name != other.name) return false
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun hashCode(): Int {
|
||||||
|
return name.hashCode()
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object CREATOR : Parcelable.Creator<Attachment> {
|
||||||
|
override fun createFromParcel(parcel: Parcel): Attachment {
|
||||||
|
return Attachment(parcel)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun newArray(size: Int): Array<Attachment?> {
|
||||||
|
return arrayOfNulls(size)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -25,13 +25,12 @@ import android.net.Uri
|
|||||||
import android.util.Log
|
import android.util.Log
|
||||||
import com.kunzisoft.keepass.crypto.keyDerivation.KdfEngine
|
import com.kunzisoft.keepass.crypto.keyDerivation.KdfEngine
|
||||||
import com.kunzisoft.keepass.database.action.node.NodeHandler
|
import com.kunzisoft.keepass.database.action.node.NodeHandler
|
||||||
import com.kunzisoft.keepass.database.element.database.CompressionAlgorithm
|
import com.kunzisoft.keepass.database.element.database.*
|
||||||
import com.kunzisoft.keepass.database.element.database.DatabaseKDB
|
|
||||||
import com.kunzisoft.keepass.database.element.database.DatabaseKDBX
|
|
||||||
import com.kunzisoft.keepass.database.element.icon.IconImageFactory
|
import com.kunzisoft.keepass.database.element.icon.IconImageFactory
|
||||||
import com.kunzisoft.keepass.database.element.node.NodeId
|
import com.kunzisoft.keepass.database.element.node.NodeId
|
||||||
import com.kunzisoft.keepass.database.element.node.NodeIdInt
|
import com.kunzisoft.keepass.database.element.node.NodeIdInt
|
||||||
import com.kunzisoft.keepass.database.element.node.NodeIdUUID
|
import com.kunzisoft.keepass.database.element.node.NodeIdUUID
|
||||||
|
import com.kunzisoft.keepass.database.element.database.BinaryAttachment
|
||||||
import com.kunzisoft.keepass.database.element.security.EncryptionAlgorithm
|
import com.kunzisoft.keepass.database.element.security.EncryptionAlgorithm
|
||||||
import com.kunzisoft.keepass.database.exception.DatabaseOutputException
|
import com.kunzisoft.keepass.database.exception.DatabaseOutputException
|
||||||
import com.kunzisoft.keepass.database.exception.FileNotFoundDatabaseException
|
import com.kunzisoft.keepass.database.exception.FileNotFoundDatabaseException
|
||||||
@@ -52,6 +51,7 @@ import com.kunzisoft.keepass.utils.SingletonHolder
|
|||||||
import com.kunzisoft.keepass.utils.UriUtil
|
import com.kunzisoft.keepass.utils.UriUtil
|
||||||
import java.io.*
|
import java.io.*
|
||||||
import java.util.*
|
import java.util.*
|
||||||
|
import kotlin.collections.ArrayList
|
||||||
|
|
||||||
|
|
||||||
class Database {
|
class Database {
|
||||||
@@ -157,6 +157,17 @@ class Database {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun compressionForNewEntry(): Boolean {
|
||||||
|
if (mDatabaseKDB != null)
|
||||||
|
return false
|
||||||
|
// Default compression not necessary if stored in header
|
||||||
|
mDatabaseKDBX?.let {
|
||||||
|
return it.compressionAlgorithm == CompressionAlgorithm.GZip
|
||||||
|
&& it.kdbxVersion.toKotlinLong() < DatabaseHeaderKDBX.FILE_VERSION_32_4.toKotlinLong()
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
fun updateDataBinaryCompression(oldCompression: CompressionAlgorithm,
|
fun updateDataBinaryCompression(oldCompression: CompressionAlgorithm,
|
||||||
newCompression: CompressionAlgorithm) {
|
newCompression: CompressionAlgorithm) {
|
||||||
mDatabaseKDBX?.changeBinaryCompression(oldCompression, newCompression)
|
mDatabaseKDBX?.changeBinaryCompression(oldCompression, newCompression)
|
||||||
@@ -428,6 +439,37 @@ class Database {
|
|||||||
}, omitBackup, max)
|
}, omitBackup, max)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val binaryPool: BinaryPool
|
||||||
|
get() {
|
||||||
|
return mDatabaseKDBX?.binaryPool ?: BinaryPool()
|
||||||
|
}
|
||||||
|
|
||||||
|
val allowMultipleAttachments: Boolean
|
||||||
|
get() {
|
||||||
|
if (mDatabaseKDB != null)
|
||||||
|
return false
|
||||||
|
if (mDatabaseKDBX != null)
|
||||||
|
return true
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
fun buildNewBinary(cacheDirectory: File,
|
||||||
|
enableProtection: Boolean = false,
|
||||||
|
compressed: Boolean = false): BinaryAttachment? {
|
||||||
|
return mDatabaseKDB?.buildNewBinary(cacheDirectory)
|
||||||
|
?: mDatabaseKDBX?.buildNewBinary(cacheDirectory, enableProtection, compressed)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun removeAttachmentIfNotUsed(attachment: Attachment) {
|
||||||
|
// No need in KDB database because unique attachment by entry
|
||||||
|
mDatabaseKDBX?.removeAttachmentIfNotUsed(attachment)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun removeUnlinkedAttachments() {
|
||||||
|
// No check in database KDB because unique attachment by entry
|
||||||
|
mDatabaseKDBX?.removeUnlinkedAttachments()
|
||||||
|
}
|
||||||
|
|
||||||
@Throws(DatabaseOutputException::class)
|
@Throws(DatabaseOutputException::class)
|
||||||
fun saveData(contentResolver: ContentResolver) {
|
fun saveData(contentResolver: ContentResolver) {
|
||||||
try {
|
try {
|
||||||
@@ -800,7 +842,7 @@ class Database {
|
|||||||
rootGroup?.doForEachChildAndForIt(
|
rootGroup?.doForEachChildAndForIt(
|
||||||
object : NodeHandler<Entry>() {
|
object : NodeHandler<Entry>() {
|
||||||
override fun operate(node: Entry): Boolean {
|
override fun operate(node: Entry): Boolean {
|
||||||
removeOldestEntryHistory(node)
|
removeOldestEntryHistory(node, binaryPool)
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -808,7 +850,8 @@ class Database {
|
|||||||
override fun operate(node: Group): Boolean {
|
override fun operate(node: Group): Boolean {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
})
|
}
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun removeEachEntryHistory() {
|
fun removeEachEntryHistory() {
|
||||||
@@ -829,9 +872,8 @@ class Database {
|
|||||||
/**
|
/**
|
||||||
* Remove oldest history if more than max items or max memory
|
* Remove oldest history if more than max items or max memory
|
||||||
*/
|
*/
|
||||||
fun removeOldestEntryHistory(entry: Entry) {
|
fun removeOldestEntryHistory(entry: Entry, binaryPool: BinaryPool) {
|
||||||
mDatabaseKDBX?.let {
|
mDatabaseKDBX?.let {
|
||||||
|
|
||||||
val maxItems = historyMaxItems
|
val maxItems = historyMaxItems
|
||||||
if (maxItems >= 0) {
|
if (maxItems >= 0) {
|
||||||
while (entry.getHistory().size > maxItems) {
|
while (entry.getHistory().size > maxItems) {
|
||||||
@@ -844,7 +886,7 @@ class Database {
|
|||||||
while (true) {
|
while (true) {
|
||||||
var historySize: Long = 0
|
var historySize: Long = 0
|
||||||
for (entryHistory in entry.getHistory()) {
|
for (entryHistory in entry.getHistory()) {
|
||||||
historySize += entryHistory.getSize()
|
historySize += entryHistory.getSize(binaryPool)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (historySize > maxSize) {
|
if (historySize > maxSize) {
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ package com.kunzisoft.keepass.database.element
|
|||||||
|
|
||||||
import android.os.Parcel
|
import android.os.Parcel
|
||||||
import android.os.Parcelable
|
import android.os.Parcelable
|
||||||
|
import com.kunzisoft.keepass.database.element.database.BinaryPool
|
||||||
import com.kunzisoft.keepass.database.element.database.DatabaseKDBX
|
import com.kunzisoft.keepass.database.element.database.DatabaseKDBX
|
||||||
import com.kunzisoft.keepass.database.element.entry.EntryKDB
|
import com.kunzisoft.keepass.database.element.entry.EntryKDB
|
||||||
import com.kunzisoft.keepass.database.element.entry.EntryKDBX
|
import com.kunzisoft.keepass.database.element.entry.EntryKDBX
|
||||||
@@ -33,7 +34,6 @@ import com.kunzisoft.keepass.database.element.node.NodeId
|
|||||||
import com.kunzisoft.keepass.database.element.node.NodeIdUUID
|
import com.kunzisoft.keepass.database.element.node.NodeIdUUID
|
||||||
import com.kunzisoft.keepass.database.element.node.Type
|
import com.kunzisoft.keepass.database.element.node.Type
|
||||||
import com.kunzisoft.keepass.database.element.security.ProtectedString
|
import com.kunzisoft.keepass.database.element.security.ProtectedString
|
||||||
import com.kunzisoft.keepass.model.EntryAttachment
|
|
||||||
import com.kunzisoft.keepass.model.EntryInfo
|
import com.kunzisoft.keepass.model.EntryInfo
|
||||||
import com.kunzisoft.keepass.model.Field
|
import com.kunzisoft.keepass.model.Field
|
||||||
import com.kunzisoft.keepass.otp.OtpElement
|
import com.kunzisoft.keepass.otp.OtpElement
|
||||||
@@ -317,40 +317,38 @@ class Entry : Node, EntryVersionedInterface<Group> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun startToManageFieldReferences(db: DatabaseKDBX) {
|
fun startToManageFieldReferences(database: DatabaseKDBX) {
|
||||||
entryKDBX?.startToManageFieldReferences(db)
|
entryKDBX?.startToManageFieldReferences(database)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun stopToManageFieldReferences() {
|
fun stopToManageFieldReferences() {
|
||||||
entryKDBX?.stopToManageFieldReferences()
|
entryKDBX?.stopToManageFieldReferences()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getAttachments(): ArrayList<EntryAttachment> {
|
fun getAttachments(binaryPool: BinaryPool): ArrayList<Attachment> {
|
||||||
val attachments = ArrayList<EntryAttachment>()
|
val attachments = ArrayList<Attachment>()
|
||||||
|
entryKDB?.getAttachments()?.let {
|
||||||
entryKDB?.binaryData?.let { binaryKDB ->
|
attachments.addAll(it)
|
||||||
attachments.add(EntryAttachment(entryKDB?.binaryDescription ?: "", binaryKDB))
|
|
||||||
}
|
}
|
||||||
|
entryKDBX?.getAttachments(binaryPool)?.let {
|
||||||
entryKDBX?.binaries?.let { binariesKDBX ->
|
attachments.addAll(it)
|
||||||
for ((key, value) in binariesKDBX) {
|
|
||||||
attachments.add(EntryAttachment(key, value))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return attachments
|
return attachments
|
||||||
}
|
}
|
||||||
|
|
||||||
fun removeAttachment(attachment: EntryAttachment) {
|
fun containsAttachment(): Boolean {
|
||||||
entryKDB?.apply {
|
return entryKDB?.containsAttachment() == true
|
||||||
if (binaryDescription == attachment.name
|
|| entryKDBX?.containsAttachment() == true
|
||||||
&& binaryData == attachment.binaryAttachment) {
|
}
|
||||||
binaryDescription = ""
|
|
||||||
binaryData = null
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
entryKDBX?.removeProtectedBinary(attachment.name)
|
fun putAttachment(attachment: Attachment, binaryPool: BinaryPool) {
|
||||||
|
entryKDB?.putAttachment(attachment)
|
||||||
|
entryKDBX?.putAttachment(attachment, binaryPool)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun removeAttachment(attachment: Attachment) {
|
||||||
|
entryKDB?.removeAttachment(attachment)
|
||||||
|
entryKDBX?.removeAttachment(attachment)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getHistory(): ArrayList<Entry> {
|
fun getHistory(): ArrayList<Entry> {
|
||||||
@@ -380,8 +378,8 @@ class Entry : Node, EntryVersionedInterface<Group> {
|
|||||||
entryKDBX?.removeOldestEntryFromHistory()
|
entryKDBX?.removeOldestEntryFromHistory()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getSize(): Long {
|
fun getSize(binaryPool: BinaryPool): Long {
|
||||||
return entryKDBX?.size ?: 0L
|
return entryKDBX?.getSize(binaryPool) ?: 0L
|
||||||
}
|
}
|
||||||
|
|
||||||
fun containsCustomData(): Boolean {
|
fun containsCustomData(): Boolean {
|
||||||
|
|||||||
@@ -17,10 +17,8 @@
|
|||||||
* along with KeePassDX. If not, see <http://www.gnu.org/licenses/>.
|
* along with KeePassDX. If not, see <http://www.gnu.org/licenses/>.
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
package com.kunzisoft.keepass.database.element.security
|
package com.kunzisoft.keepass.database.element.database
|
||||||
|
|
||||||
import android.content.ContentResolver
|
|
||||||
import android.net.Uri
|
|
||||||
import android.os.Parcel
|
import android.os.Parcel
|
||||||
import android.os.Parcelable
|
import android.os.Parcelable
|
||||||
import com.kunzisoft.keepass.stream.readBytes
|
import com.kunzisoft.keepass.stream.readBytes
|
||||||
@@ -30,7 +28,7 @@ import java.util.zip.GZIPOutputStream
|
|||||||
|
|
||||||
class BinaryAttachment : Parcelable {
|
class BinaryAttachment : Parcelable {
|
||||||
|
|
||||||
var isCompressed: Boolean? = null
|
var isCompressed: Boolean = false
|
||||||
private set
|
private set
|
||||||
var isProtected: Boolean = false
|
var isProtected: Boolean = false
|
||||||
private set
|
private set
|
||||||
@@ -46,12 +44,12 @@ class BinaryAttachment : Parcelable {
|
|||||||
* Empty protected binary
|
* Empty protected binary
|
||||||
*/
|
*/
|
||||||
constructor() {
|
constructor() {
|
||||||
this.isCompressed = null
|
this.isCompressed = false
|
||||||
this.isProtected = false
|
this.isProtected = false
|
||||||
this.dataFile = null
|
this.dataFile = null
|
||||||
}
|
}
|
||||||
|
|
||||||
constructor(dataFile: File, enableProtection: Boolean = false, compressed: Boolean? = null) {
|
constructor(dataFile: File, enableProtection: Boolean = false, compressed: Boolean = false) {
|
||||||
this.isCompressed = compressed
|
this.isCompressed = compressed
|
||||||
this.isProtected = enableProtection
|
this.isProtected = enableProtection
|
||||||
this.dataFile = dataFile
|
this.dataFile = dataFile
|
||||||
@@ -59,7 +57,7 @@ class BinaryAttachment : Parcelable {
|
|||||||
|
|
||||||
private constructor(parcel: Parcel) {
|
private constructor(parcel: Parcel) {
|
||||||
val compressedByte = parcel.readByte().toInt()
|
val compressedByte = parcel.readByte().toInt()
|
||||||
isCompressed = if (compressedByte == 2) null else compressedByte != 0
|
isCompressed = compressedByte != 0
|
||||||
isProtected = parcel.readByte().toInt() != 0
|
isProtected = parcel.readByte().toInt() != 0
|
||||||
parcel.readString()?.let {
|
parcel.readString()?.let {
|
||||||
dataFile = File(it)
|
dataFile = File(it)
|
||||||
@@ -74,32 +72,51 @@ class BinaryAttachment : Parcelable {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Throws(IOException::class)
|
||||||
|
fun getUnGzipInputDataStream(): InputStream {
|
||||||
|
return if (isCompressed)
|
||||||
|
GZIPInputStream(getInputDataStream())
|
||||||
|
else
|
||||||
|
getInputDataStream()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Throws(IOException::class)
|
||||||
|
fun getOutputDataStream(): OutputStream {
|
||||||
|
return when {
|
||||||
|
dataFile != null -> FileOutputStream(dataFile!!)
|
||||||
|
else -> throw IOException("Unable to write in an unknown file")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Throws(IOException::class)
|
||||||
|
fun getGzipOutputDataStream(): OutputStream {
|
||||||
|
return if (isCompressed) {
|
||||||
|
GZIPOutputStream(getOutputDataStream())
|
||||||
|
} else {
|
||||||
|
getOutputDataStream()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Throws(IOException::class)
|
@Throws(IOException::class)
|
||||||
fun compress(bufferSize: Int = DEFAULT_BUFFER_SIZE) {
|
fun compress(bufferSize: Int = DEFAULT_BUFFER_SIZE) {
|
||||||
dataFile?.let { concreteDataFile ->
|
dataFile?.let { concreteDataFile ->
|
||||||
// To compress, create a new binary with file
|
// To compress, create a new binary with file
|
||||||
if (isCompressed != true) {
|
if (!isCompressed) {
|
||||||
val fileBinaryCompress = File(concreteDataFile.parent, concreteDataFile.name + "_temp")
|
val fileBinaryCompress = File(concreteDataFile.parent, concreteDataFile.name + "_temp")
|
||||||
var outputStream: GZIPOutputStream? = null
|
GZIPOutputStream(FileOutputStream(fileBinaryCompress)).use { outputStream ->
|
||||||
var inputStream: InputStream? = null
|
getInputDataStream().use { inputStream ->
|
||||||
try {
|
inputStream.readBytes(bufferSize) { buffer ->
|
||||||
outputStream = GZIPOutputStream(FileOutputStream(fileBinaryCompress))
|
outputStream.write(buffer)
|
||||||
inputStream = getInputDataStream()
|
|
||||||
inputStream.readBytes(bufferSize) { buffer ->
|
|
||||||
outputStream.write(buffer)
|
|
||||||
}
|
|
||||||
} finally {
|
|
||||||
inputStream?.close()
|
|
||||||
outputStream?.close()
|
|
||||||
|
|
||||||
// Remove unGzip file
|
|
||||||
if (concreteDataFile.delete()) {
|
|
||||||
if (fileBinaryCompress.renameTo(concreteDataFile)) {
|
|
||||||
// Harmonize with database compression
|
|
||||||
isCompressed = true
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// Remove unGzip file
|
||||||
|
if (concreteDataFile.delete()) {
|
||||||
|
if (fileBinaryCompress.renameTo(concreteDataFile)) {
|
||||||
|
// Harmonize with database compression
|
||||||
|
isCompressed = true
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -107,52 +124,20 @@ class BinaryAttachment : Parcelable {
|
|||||||
@Throws(IOException::class)
|
@Throws(IOException::class)
|
||||||
fun decompress(bufferSize: Int = DEFAULT_BUFFER_SIZE) {
|
fun decompress(bufferSize: Int = DEFAULT_BUFFER_SIZE) {
|
||||||
dataFile?.let { concreteDataFile ->
|
dataFile?.let { concreteDataFile ->
|
||||||
if (isCompressed != false) {
|
if (isCompressed) {
|
||||||
val fileBinaryDecompress = File(concreteDataFile.parent, concreteDataFile.name + "_temp")
|
val fileBinaryDecompress = File(concreteDataFile.parent, concreteDataFile.name + "_temp")
|
||||||
var outputStream: FileOutputStream? = null
|
FileOutputStream(fileBinaryDecompress).use { outputStream ->
|
||||||
var inputStream: GZIPInputStream? = null
|
getUnGzipInputDataStream().use { inputStream ->
|
||||||
try {
|
inputStream.readBytes(bufferSize) { buffer ->
|
||||||
outputStream = FileOutputStream(fileBinaryDecompress)
|
outputStream.write(buffer)
|
||||||
inputStream = GZIPInputStream(getInputDataStream())
|
|
||||||
inputStream.readBytes(bufferSize) { buffer ->
|
|
||||||
outputStream.write(buffer)
|
|
||||||
}
|
|
||||||
} finally {
|
|
||||||
inputStream?.close()
|
|
||||||
outputStream?.close()
|
|
||||||
|
|
||||||
// Remove gzip file
|
|
||||||
if (concreteDataFile.delete()) {
|
|
||||||
if (fileBinaryDecompress.renameTo(concreteDataFile)) {
|
|
||||||
// Harmonize with database compression
|
|
||||||
isCompressed = false
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
// Remove gzip file
|
||||||
}
|
if (concreteDataFile.delete()) {
|
||||||
}
|
if (fileBinaryDecompress.renameTo(concreteDataFile)) {
|
||||||
|
// Harmonize with database compression
|
||||||
fun download(createdFileUri: Uri,
|
isCompressed = false
|
||||||
contentResolver: ContentResolver,
|
|
||||||
bufferSize: Int = DEFAULT_BUFFER_SIZE,
|
|
||||||
update: ((percent: Int)->Unit)? = null) {
|
|
||||||
|
|
||||||
var dataDownloaded = 0
|
|
||||||
contentResolver.openOutputStream(createdFileUri).use { outputStream ->
|
|
||||||
outputStream?.let { fileOutputStream ->
|
|
||||||
if (isCompressed == true) {
|
|
||||||
GZIPInputStream(getInputDataStream())
|
|
||||||
} else {
|
|
||||||
getInputDataStream()
|
|
||||||
}.use { inputStream ->
|
|
||||||
inputStream.readBytes(bufferSize) { buffer ->
|
|
||||||
fileOutputStream.write(buffer)
|
|
||||||
dataDownloaded += buffer.size
|
|
||||||
try {
|
|
||||||
val percentDownload = (100 * dataDownloaded / length()).toInt()
|
|
||||||
update?.invoke(percentDownload)
|
|
||||||
} catch (e: Exception) {}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -185,18 +170,22 @@ class BinaryAttachment : Parcelable {
|
|||||||
override fun hashCode(): Int {
|
override fun hashCode(): Int {
|
||||||
|
|
||||||
var result = 0
|
var result = 0
|
||||||
result = 31 * result + if (isCompressed == null) 2 else if (isCompressed!!) 1 else 0
|
result = 31 * result + if (isCompressed) 1 else 0
|
||||||
result = 31 * result + if (isProtected) 1 else 0
|
result = 31 * result + if (isProtected) 1 else 0
|
||||||
result = 31 * result + dataFile!!.hashCode()
|
result = 31 * result + dataFile!!.hashCode()
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun toString(): String {
|
||||||
|
return dataFile.toString()
|
||||||
|
}
|
||||||
|
|
||||||
override fun describeContents(): Int {
|
override fun describeContents(): Int {
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun writeToParcel(dest: Parcel, flags: Int) {
|
override fun writeToParcel(dest: Parcel, flags: Int) {
|
||||||
dest.writeByte((if (isCompressed == null) 2 else if (isCompressed!!) 1 else 0).toByte())
|
dest.writeByte((if (isCompressed) 1 else 0).toByte())
|
||||||
dest.writeByte((if (isProtected) 1 else 0).toByte())
|
dest.writeByte((if (isProtected) 1 else 0).toByte())
|
||||||
dest.writeString(dataFile?.absolutePath)
|
dest.writeString(dataFile?.absolutePath)
|
||||||
}
|
}
|
||||||
@@ -19,52 +19,126 @@
|
|||||||
*/
|
*/
|
||||||
package com.kunzisoft.keepass.database.element.database
|
package com.kunzisoft.keepass.database.element.database
|
||||||
|
|
||||||
import android.util.SparseArray
|
|
||||||
import com.kunzisoft.keepass.database.element.security.BinaryAttachment
|
|
||||||
import java.io.IOException
|
import java.io.IOException
|
||||||
|
|
||||||
class BinaryPool {
|
class BinaryPool {
|
||||||
private val pool = SparseArray<BinaryAttachment>()
|
private val pool = LinkedHashMap<Int, BinaryAttachment>()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* To get a binary by the pool key (ref attribute in entry)
|
||||||
|
*/
|
||||||
operator fun get(key: Int): BinaryAttachment? {
|
operator fun get(key: Int): BinaryAttachment? {
|
||||||
return pool[key]
|
return pool[key]
|
||||||
}
|
}
|
||||||
|
|
||||||
fun put(key: Int, value: BinaryAttachment) {
|
/**
|
||||||
pool.put(key, value)
|
* To linked a binary with a pool key, if the pool key doesn't exists, create an unused one
|
||||||
|
*/
|
||||||
|
fun put(key: Int?, value: BinaryAttachment) {
|
||||||
|
if (key == null)
|
||||||
|
put(value)
|
||||||
|
else
|
||||||
|
pool[key] = value
|
||||||
}
|
}
|
||||||
|
|
||||||
fun doForEachBinary(action: (key: Int, binary: BinaryAttachment) -> Unit) {
|
/**
|
||||||
for (i in 0 until pool.size()) {
|
* To put a [binaryAttachment] in the pool,
|
||||||
action.invoke(i, pool.get(pool.keyAt(i)))
|
* if already exists, replace the current one,
|
||||||
|
* else add it with a new key
|
||||||
|
*/
|
||||||
|
fun put(binaryAttachment: BinaryAttachment): Int {
|
||||||
|
var key = findKey(binaryAttachment)
|
||||||
|
if (key == null) {
|
||||||
|
key = findUnusedKey()
|
||||||
}
|
}
|
||||||
|
pool[key] = binaryAttachment
|
||||||
|
return key
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Remove a binary from the pool, the file is not deleted
|
||||||
|
*/
|
||||||
@Throws(IOException::class)
|
@Throws(IOException::class)
|
||||||
fun clear() {
|
fun remove(binaryAttachment: BinaryAttachment) {
|
||||||
doForEachBinary { _, binary ->
|
findKey(binaryAttachment)?.let {
|
||||||
binary.clear()
|
pool.remove(it)
|
||||||
}
|
}
|
||||||
pool.clear()
|
// Don't clear attachment here because a file can be used in many BinaryAttachment
|
||||||
}
|
}
|
||||||
|
|
||||||
fun add(fileBinary: BinaryAttachment) {
|
/**
|
||||||
if (findKey(fileBinary) == null) {
|
* Utility method to find an unused key in the pool
|
||||||
pool.put(findUnusedKey(), fileBinary)
|
*/
|
||||||
}
|
private fun findUnusedKey(): Int {
|
||||||
}
|
var unusedKey = 0
|
||||||
|
while (pool[unusedKey] != null)
|
||||||
fun findUnusedKey(): Int {
|
|
||||||
var unusedKey = pool.size()
|
|
||||||
while (get(unusedKey) != null)
|
|
||||||
unusedKey++
|
unusedKey++
|
||||||
return unusedKey
|
return unusedKey
|
||||||
}
|
}
|
||||||
|
|
||||||
fun findKey(pb: BinaryAttachment): Int? {
|
/**
|
||||||
for (i in 0 until pool.size()) {
|
* Return key of [binaryAttachmentToRetrieve] or null if not found
|
||||||
if (pool.get(pool.keyAt(i)) == pb) return i
|
*/
|
||||||
|
private fun findKey(binaryAttachmentToRetrieve: BinaryAttachment): Int? {
|
||||||
|
val contains = pool.containsValue(binaryAttachmentToRetrieve)
|
||||||
|
return if (!contains)
|
||||||
|
null
|
||||||
|
else {
|
||||||
|
for ((key, binary) in pool) {
|
||||||
|
if (binary == binaryAttachmentToRetrieve) {
|
||||||
|
return key
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null
|
||||||
}
|
}
|
||||||
return null
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Utility method to order binaries and solve index problem in database v4
|
||||||
|
*/
|
||||||
|
private fun orderedBinaries(): List<KeyBinary> {
|
||||||
|
val keyBinaryList = ArrayList<KeyBinary>()
|
||||||
|
for ((key, binary) in pool) {
|
||||||
|
keyBinaryList.add(KeyBinary(key, binary))
|
||||||
|
}
|
||||||
|
return keyBinaryList
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* To register a binary with a ref corresponding to an ordered index
|
||||||
|
*/
|
||||||
|
fun getBinaryIndexFromKey(key: Int): Int? {
|
||||||
|
val index = orderedBinaries().indexOfFirst { it.key == key }
|
||||||
|
return if (index < 0)
|
||||||
|
null
|
||||||
|
else
|
||||||
|
index
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Different from doForEach, provide an ordered index to each binary
|
||||||
|
*/
|
||||||
|
fun doForEachOrderedBinary(action: (index: Int, keyBinary: KeyBinary) -> Unit) {
|
||||||
|
orderedBinaries().forEachIndexed(action)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* To do an action on each binary in the pool
|
||||||
|
*/
|
||||||
|
fun doForEachBinary(action: (binary: BinaryAttachment) -> Unit) {
|
||||||
|
pool.values.forEach { action.invoke(it) }
|
||||||
|
}
|
||||||
|
|
||||||
|
@Throws(IOException::class)
|
||||||
|
fun clear() {
|
||||||
|
doForEachBinary {
|
||||||
|
it.clear()
|
||||||
|
}
|
||||||
|
pool.clear()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Utility data class to order binaries
|
||||||
|
*/
|
||||||
|
data class KeyBinary(val key: Int, val binary: BinaryAttachment)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -28,6 +28,7 @@ import com.kunzisoft.keepass.database.element.node.NodeIdInt
|
|||||||
import com.kunzisoft.keepass.database.element.node.NodeIdUUID
|
import com.kunzisoft.keepass.database.element.node.NodeIdUUID
|
||||||
import com.kunzisoft.keepass.database.element.security.EncryptionAlgorithm
|
import com.kunzisoft.keepass.database.element.security.EncryptionAlgorithm
|
||||||
import com.kunzisoft.keepass.stream.NullOutputStream
|
import com.kunzisoft.keepass.stream.NullOutputStream
|
||||||
|
import java.io.File
|
||||||
import java.io.IOException
|
import java.io.IOException
|
||||||
import java.io.InputStream
|
import java.io.InputStream
|
||||||
import java.security.DigestOutputStream
|
import java.security.DigestOutputStream
|
||||||
@@ -249,6 +250,12 @@ class DatabaseKDB : DatabaseVersioned<Int, UUID, GroupKDB, EntryKDB>() {
|
|||||||
addEntryTo(entry, origParent)
|
addEntryTo(entry, origParent)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun buildNewBinary(cacheDirectory: File): BinaryAttachment {
|
||||||
|
// Generate an unique new file with timestamp
|
||||||
|
val fileInCache = File(cacheDirectory, System.currentTimeMillis().toString())
|
||||||
|
return BinaryAttachment(fileInCache)
|
||||||
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
val TYPE = DatabaseKDB::class.java
|
val TYPE = DatabaseKDB::class.java
|
||||||
|
|
||||||
|
|||||||
@@ -29,8 +29,10 @@ import com.kunzisoft.keepass.crypto.engine.CipherEngine
|
|||||||
import com.kunzisoft.keepass.crypto.keyDerivation.KdfEngine
|
import com.kunzisoft.keepass.crypto.keyDerivation.KdfEngine
|
||||||
import com.kunzisoft.keepass.crypto.keyDerivation.KdfFactory
|
import com.kunzisoft.keepass.crypto.keyDerivation.KdfFactory
|
||||||
import com.kunzisoft.keepass.crypto.keyDerivation.KdfParameters
|
import com.kunzisoft.keepass.crypto.keyDerivation.KdfParameters
|
||||||
|
import com.kunzisoft.keepass.database.action.node.NodeHandler
|
||||||
import com.kunzisoft.keepass.database.element.DateInstant
|
import com.kunzisoft.keepass.database.element.DateInstant
|
||||||
import com.kunzisoft.keepass.database.element.DeletedObject
|
import com.kunzisoft.keepass.database.element.DeletedObject
|
||||||
|
import com.kunzisoft.keepass.database.element.Attachment
|
||||||
import com.kunzisoft.keepass.database.element.database.DatabaseKDB.Companion.BACKUP_FOLDER_TITLE
|
import com.kunzisoft.keepass.database.element.database.DatabaseKDB.Companion.BACKUP_FOLDER_TITLE
|
||||||
import com.kunzisoft.keepass.database.element.entry.EntryKDBX
|
import com.kunzisoft.keepass.database.element.entry.EntryKDBX
|
||||||
import com.kunzisoft.keepass.database.element.group.GroupKDBX
|
import com.kunzisoft.keepass.database.element.group.GroupKDBX
|
||||||
@@ -40,12 +42,14 @@ import com.kunzisoft.keepass.database.element.node.NodeVersioned
|
|||||||
import com.kunzisoft.keepass.database.element.security.EncryptionAlgorithm
|
import com.kunzisoft.keepass.database.element.security.EncryptionAlgorithm
|
||||||
import com.kunzisoft.keepass.database.element.security.MemoryProtectionConfig
|
import com.kunzisoft.keepass.database.element.security.MemoryProtectionConfig
|
||||||
import com.kunzisoft.keepass.database.exception.UnknownKDF
|
import com.kunzisoft.keepass.database.exception.UnknownKDF
|
||||||
|
import com.kunzisoft.keepass.database.file.DatabaseHeaderKDBX
|
||||||
import com.kunzisoft.keepass.database.file.DatabaseHeaderKDBX.Companion.FILE_VERSION_32_3
|
import com.kunzisoft.keepass.database.file.DatabaseHeaderKDBX.Companion.FILE_VERSION_32_3
|
||||||
import com.kunzisoft.keepass.database.file.DatabaseHeaderKDBX.Companion.FILE_VERSION_32_4
|
import com.kunzisoft.keepass.database.file.DatabaseHeaderKDBX.Companion.FILE_VERSION_32_4
|
||||||
import com.kunzisoft.keepass.utils.UnsignedInt
|
import com.kunzisoft.keepass.utils.UnsignedInt
|
||||||
import com.kunzisoft.keepass.utils.VariantDictionary
|
import com.kunzisoft.keepass.utils.VariantDictionary
|
||||||
import org.w3c.dom.Node
|
import org.w3c.dom.Node
|
||||||
import org.w3c.dom.Text
|
import org.w3c.dom.Text
|
||||||
|
import java.io.File
|
||||||
import java.io.IOException
|
import java.io.IOException
|
||||||
import java.io.InputStream
|
import java.io.InputStream
|
||||||
import java.security.MessageDigest
|
import java.security.MessageDigest
|
||||||
@@ -173,33 +177,51 @@ class DatabaseKDBX : DatabaseVersioned<UUID, UUID, GroupKDBX, EntryKDBX> {
|
|||||||
|
|
||||||
fun changeBinaryCompression(oldCompression: CompressionAlgorithm,
|
fun changeBinaryCompression(oldCompression: CompressionAlgorithm,
|
||||||
newCompression: CompressionAlgorithm) {
|
newCompression: CompressionAlgorithm) {
|
||||||
binaryPool.doForEachBinary { key, binary ->
|
when (oldCompression) {
|
||||||
|
CompressionAlgorithm.None -> {
|
||||||
try {
|
when (newCompression) {
|
||||||
when (oldCompression) {
|
CompressionAlgorithm.None -> {}
|
||||||
CompressionAlgorithm.None -> {
|
|
||||||
when (newCompression) {
|
|
||||||
CompressionAlgorithm.None -> {
|
|
||||||
}
|
|
||||||
CompressionAlgorithm.GZip -> {
|
|
||||||
// To compress, create a new binary with file
|
|
||||||
binary.compress(BUFFER_SIZE_BYTES)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
CompressionAlgorithm.GZip -> {
|
CompressionAlgorithm.GZip -> {
|
||||||
when (newCompression) {
|
// Only in databaseV3.1, in databaseV4 the header is zipped during the save
|
||||||
CompressionAlgorithm.None -> {
|
if (kdbxVersion.toKotlinLong() < FILE_VERSION_32_4.toKotlinLong()) {
|
||||||
// To decompress, create a new binary with file
|
compressAllBinaries()
|
||||||
binary.decompress(BUFFER_SIZE_BYTES)
|
|
||||||
}
|
|
||||||
CompressionAlgorithm.GZip -> {
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
CompressionAlgorithm.GZip -> {
|
||||||
|
// In databaseV4 the header is zipped during the save, so not necessary here
|
||||||
|
if (kdbxVersion.toKotlinLong() >= FILE_VERSION_32_4.toKotlinLong()) {
|
||||||
|
decompressAllBinaries()
|
||||||
|
} else {
|
||||||
|
when (newCompression) {
|
||||||
|
CompressionAlgorithm.None -> {
|
||||||
|
decompressAllBinaries()
|
||||||
|
}
|
||||||
|
CompressionAlgorithm.GZip -> {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun compressAllBinaries() {
|
||||||
|
binaryPool.doForEachBinary { binary ->
|
||||||
|
try {
|
||||||
|
// To compress, create a new binary with file
|
||||||
|
binary.compress(BUFFER_SIZE_BYTES)
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
Log.e(TAG, "Unable to change compression for $key")
|
Log.e(TAG, "Unable to compress $binary", e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun decompressAllBinaries() {
|
||||||
|
binaryPool.doForEachBinary { binary ->
|
||||||
|
try {
|
||||||
|
binary.decompress(BUFFER_SIZE_BYTES)
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Log.e(TAG, "Unable to decompress $binary", e)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -536,6 +558,52 @@ class DatabaseKDBX : DatabaseVersioned<UUID, UUID, GroupKDBX, EntryKDBX> {
|
|||||||
return publicCustomData.size() > 0
|
return publicCustomData.size() > 0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun buildNewBinary(cacheDirectory: File,
|
||||||
|
protection: Boolean,
|
||||||
|
compression: Boolean,
|
||||||
|
binaryPoolId: Int? = null): BinaryAttachment {
|
||||||
|
// New file with current time
|
||||||
|
val fileInCache = File(cacheDirectory, System.currentTimeMillis().toString())
|
||||||
|
val binaryAttachment = BinaryAttachment(fileInCache, protection, compression)
|
||||||
|
// add attachment to pool
|
||||||
|
binaryPool.put(binaryPoolId, binaryAttachment)
|
||||||
|
return binaryAttachment
|
||||||
|
}
|
||||||
|
|
||||||
|
fun removeAttachmentIfNotUsed(attachment: Attachment) {
|
||||||
|
// Remove attachment from pool
|
||||||
|
removeUnlinkedAttachments(attachment.binaryAttachment)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun removeUnlinkedAttachments(vararg binaries: BinaryAttachment) {
|
||||||
|
// Build binaries to remove with all binaries known
|
||||||
|
val binariesToRemove = ArrayList<BinaryAttachment>()
|
||||||
|
if (binaries.isEmpty()) {
|
||||||
|
binaryPool.doForEachBinary { binary ->
|
||||||
|
binariesToRemove.add(binary)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
binariesToRemove.addAll(binaries)
|
||||||
|
}
|
||||||
|
// Remove binaries from the list
|
||||||
|
rootGroup?.doForEachChild(object : NodeHandler<EntryKDBX>() {
|
||||||
|
override fun operate(node: EntryKDBX): Boolean {
|
||||||
|
node.getAttachments(binaryPool).forEach {
|
||||||
|
binariesToRemove.remove(it.binaryAttachment)
|
||||||
|
}
|
||||||
|
return binariesToRemove.isNotEmpty()
|
||||||
|
}
|
||||||
|
}, null)
|
||||||
|
// Effective removing
|
||||||
|
binariesToRemove.forEach {
|
||||||
|
try {
|
||||||
|
binaryPool.remove(it)
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Log.w(TAG, "Unable to clean binaries", e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
override fun validatePasswordEncoding(password: String?, containsKeyFile: Boolean): Boolean {
|
override fun validatePasswordEncoding(password: String?, containsKeyFile: Boolean): Boolean {
|
||||||
if (password == null)
|
if (password == null)
|
||||||
return true
|
return true
|
||||||
|
|||||||
@@ -26,8 +26,10 @@ import com.kunzisoft.keepass.database.element.node.NodeId
|
|||||||
import com.kunzisoft.keepass.database.element.node.NodeIdUUID
|
import com.kunzisoft.keepass.database.element.node.NodeIdUUID
|
||||||
import com.kunzisoft.keepass.database.element.node.NodeKDBInterface
|
import com.kunzisoft.keepass.database.element.node.NodeKDBInterface
|
||||||
import com.kunzisoft.keepass.database.element.node.Type
|
import com.kunzisoft.keepass.database.element.node.Type
|
||||||
import com.kunzisoft.keepass.database.element.security.BinaryAttachment
|
import com.kunzisoft.keepass.database.element.database.BinaryAttachment
|
||||||
|
import com.kunzisoft.keepass.database.element.Attachment
|
||||||
import java.util.*
|
import java.util.*
|
||||||
|
import kotlin.collections.ArrayList
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Structure containing information about one entry.
|
* Structure containing information about one entry.
|
||||||
@@ -135,6 +137,30 @@ class EntryKDB : EntryVersioned<Int, UUID, GroupKDB, EntryKDB>, NodeKDBInterface
|
|||||||
override val type: Type
|
override val type: Type
|
||||||
get() = Type.ENTRY
|
get() = Type.ENTRY
|
||||||
|
|
||||||
|
fun getAttachments(): ArrayList<Attachment> {
|
||||||
|
return ArrayList<Attachment>().apply {
|
||||||
|
val binary = binaryData
|
||||||
|
if (binary != null)
|
||||||
|
add(Attachment(binaryDescription, binary))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun containsAttachment(): Boolean {
|
||||||
|
return binaryData != null
|
||||||
|
}
|
||||||
|
|
||||||
|
fun putAttachment(attachment: Attachment) {
|
||||||
|
this.binaryDescription = attachment.name
|
||||||
|
this.binaryData = attachment.binaryAttachment
|
||||||
|
}
|
||||||
|
|
||||||
|
fun removeAttachment(attachment: Attachment) {
|
||||||
|
if (this.binaryDescription == attachment.name) {
|
||||||
|
this.binaryDescription = ""
|
||||||
|
this.binaryData = null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
|
|
||||||
/** Size of byte buffer needed to hold this struct. */
|
/** Size of byte buffer needed to hold this struct. */
|
||||||
|
|||||||
@@ -31,11 +31,13 @@ import com.kunzisoft.keepass.database.element.node.NodeId
|
|||||||
import com.kunzisoft.keepass.database.element.node.NodeIdUUID
|
import com.kunzisoft.keepass.database.element.node.NodeIdUUID
|
||||||
import com.kunzisoft.keepass.database.element.node.NodeKDBXInterface
|
import com.kunzisoft.keepass.database.element.node.NodeKDBXInterface
|
||||||
import com.kunzisoft.keepass.database.element.node.Type
|
import com.kunzisoft.keepass.database.element.node.Type
|
||||||
import com.kunzisoft.keepass.database.element.security.BinaryAttachment
|
|
||||||
import com.kunzisoft.keepass.database.element.security.ProtectedString
|
import com.kunzisoft.keepass.database.element.security.ProtectedString
|
||||||
|
import com.kunzisoft.keepass.database.element.Attachment
|
||||||
|
import com.kunzisoft.keepass.database.element.database.BinaryPool
|
||||||
import com.kunzisoft.keepass.utils.ParcelableUtil
|
import com.kunzisoft.keepass.utils.ParcelableUtil
|
||||||
import com.kunzisoft.keepass.utils.UnsignedLong
|
import com.kunzisoft.keepass.utils.UnsignedLong
|
||||||
import java.util.*
|
import java.util.*
|
||||||
|
import kotlin.collections.ArrayList
|
||||||
import kotlin.collections.LinkedHashMap
|
import kotlin.collections.LinkedHashMap
|
||||||
|
|
||||||
class EntryKDBX : EntryVersioned<UUID, UUID, GroupKDBX, EntryKDBX>, NodeKDBXInterface {
|
class EntryKDBX : EntryVersioned<UUID, UUID, GroupKDBX, EntryKDBX>, NodeKDBXInterface {
|
||||||
@@ -60,8 +62,9 @@ class EntryKDBX : EntryVersioned<UUID, UUID, GroupKDBX, EntryKDBX>, NodeKDBXInte
|
|||||||
}
|
}
|
||||||
var iconCustom = IconImageCustom.UNKNOWN_ICON
|
var iconCustom = IconImageCustom.UNKNOWN_ICON
|
||||||
private var customData = LinkedHashMap<String, String>()
|
private var customData = LinkedHashMap<String, String>()
|
||||||
|
// TODO Private
|
||||||
var fields = LinkedHashMap<String, ProtectedString>()
|
var fields = LinkedHashMap<String, ProtectedString>()
|
||||||
var binaries = LinkedHashMap<String, BinaryAttachment>()
|
var binaries = LinkedHashMap<String, Int>() // Map<Label, PoolId>
|
||||||
var foregroundColor = ""
|
var foregroundColor = ""
|
||||||
var backgroundColor = ""
|
var backgroundColor = ""
|
||||||
var overrideURL = ""
|
var overrideURL = ""
|
||||||
@@ -70,36 +73,32 @@ class EntryKDBX : EntryVersioned<UUID, UUID, GroupKDBX, EntryKDBX>, NodeKDBXInte
|
|||||||
var additional = ""
|
var additional = ""
|
||||||
var tags = ""
|
var tags = ""
|
||||||
|
|
||||||
val size: Long
|
fun getSize(binaryPool: BinaryPool): Long {
|
||||||
get() {
|
var size = FIXED_LENGTH_SIZE
|
||||||
var size = FIXED_LENGTH_SIZE
|
|
||||||
|
|
||||||
for (entry in fields.entries) {
|
for (entry in fields.entries) {
|
||||||
size += entry.key.length.toLong()
|
size += entry.key.length.toLong()
|
||||||
size += entry.value.length().toLong()
|
size += entry.value.length().toLong()
|
||||||
}
|
|
||||||
|
|
||||||
for ((key, value) in binaries) {
|
|
||||||
size += key.length.toLong()
|
|
||||||
size += value.length()
|
|
||||||
}
|
|
||||||
|
|
||||||
size += autoType.defaultSequence.length.toLong()
|
|
||||||
for ((key, value) in autoType.entrySet()) {
|
|
||||||
size += key.length.toLong()
|
|
||||||
size += value.length.toLong()
|
|
||||||
}
|
|
||||||
|
|
||||||
for (entry in history) {
|
|
||||||
size += entry.size
|
|
||||||
}
|
|
||||||
|
|
||||||
size += overrideURL.length.toLong()
|
|
||||||
size += tags.length.toLong()
|
|
||||||
|
|
||||||
return size
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
size += getAttachmentsSize(binaryPool)
|
||||||
|
|
||||||
|
size += autoType.defaultSequence.length.toLong()
|
||||||
|
for ((key, value) in autoType.entrySet()) {
|
||||||
|
size += key.length.toLong()
|
||||||
|
size += value.length.toLong()
|
||||||
|
}
|
||||||
|
|
||||||
|
for (entry in history) {
|
||||||
|
size += entry.getSize(binaryPool)
|
||||||
|
}
|
||||||
|
|
||||||
|
size += overrideURL.length.toLong()
|
||||||
|
size += tags.length.toLong()
|
||||||
|
|
||||||
|
return size
|
||||||
|
}
|
||||||
|
|
||||||
override var expires: Boolean = false
|
override var expires: Boolean = false
|
||||||
|
|
||||||
constructor() : super()
|
constructor() : super()
|
||||||
@@ -110,7 +109,7 @@ class EntryKDBX : EntryVersioned<UUID, UUID, GroupKDBX, EntryKDBX>, NodeKDBXInte
|
|||||||
locationChanged = parcel.readParcelable(DateInstant::class.java.classLoader) ?: locationChanged
|
locationChanged = parcel.readParcelable(DateInstant::class.java.classLoader) ?: locationChanged
|
||||||
customData = ParcelableUtil.readStringParcelableMap(parcel)
|
customData = ParcelableUtil.readStringParcelableMap(parcel)
|
||||||
fields = ParcelableUtil.readStringParcelableMap(parcel, ProtectedString::class.java)
|
fields = ParcelableUtil.readStringParcelableMap(parcel, ProtectedString::class.java)
|
||||||
binaries = ParcelableUtil.readStringParcelableMap(parcel, BinaryAttachment::class.java)
|
binaries = ParcelableUtil.readStringIntMap(parcel)
|
||||||
foregroundColor = parcel.readString() ?: foregroundColor
|
foregroundColor = parcel.readString() ?: foregroundColor
|
||||||
backgroundColor = parcel.readString() ?: backgroundColor
|
backgroundColor = parcel.readString() ?: backgroundColor
|
||||||
overrideURL = parcel.readString() ?: overrideURL
|
overrideURL = parcel.readString() ?: overrideURL
|
||||||
@@ -128,7 +127,7 @@ class EntryKDBX : EntryVersioned<UUID, UUID, GroupKDBX, EntryKDBX>, NodeKDBXInte
|
|||||||
dest.writeParcelable(locationChanged, flags)
|
dest.writeParcelable(locationChanged, flags)
|
||||||
ParcelableUtil.writeStringParcelableMap(dest, customData)
|
ParcelableUtil.writeStringParcelableMap(dest, customData)
|
||||||
ParcelableUtil.writeStringParcelableMap(dest, flags, fields)
|
ParcelableUtil.writeStringParcelableMap(dest, flags, fields)
|
||||||
ParcelableUtil.writeStringParcelableMap(dest, flags, binaries)
|
ParcelableUtil.writeStringIntMap(dest, binaries)
|
||||||
dest.writeString(foregroundColor)
|
dest.writeString(foregroundColor)
|
||||||
dest.writeString(backgroundColor)
|
dest.writeString(backgroundColor)
|
||||||
dest.writeString(overrideURL)
|
dest.writeString(overrideURL)
|
||||||
@@ -167,8 +166,8 @@ class EntryKDBX : EntryVersioned<UUID, UUID, GroupKDBX, EntryKDBX>, NodeKDBXInte
|
|||||||
tags = source.tags
|
tags = source.tags
|
||||||
}
|
}
|
||||||
|
|
||||||
fun startToManageFieldReferences(db: DatabaseKDBX) {
|
fun startToManageFieldReferences(database: DatabaseKDBX) {
|
||||||
this.mDatabase = db
|
this.mDatabase = database
|
||||||
this.mDecodeRef = true
|
this.mDecodeRef = true
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -284,14 +283,38 @@ class EntryKDBX : EntryVersioned<UUID, UUID, GroupKDBX, EntryKDBX>, NodeKDBXInte
|
|||||||
fields[label] = value
|
fields[label] = value
|
||||||
}
|
}
|
||||||
|
|
||||||
fun putProtectedBinary(key: String, value: BinaryAttachment) {
|
fun getAttachments(binaryPool: BinaryPool): ArrayList<Attachment> {
|
||||||
binaries[key] = value
|
val entryAttachmentList = ArrayList<Attachment>()
|
||||||
|
for ((label, poolId) in binaries) {
|
||||||
|
binaryPool[poolId]?.let { binary ->
|
||||||
|
entryAttachmentList.add(Attachment(label, binary))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return entryAttachmentList
|
||||||
}
|
}
|
||||||
|
|
||||||
fun removeProtectedBinary(name: String) {
|
fun containsAttachment(): Boolean {
|
||||||
binaries.remove(name)
|
return binaries.isNotEmpty()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun putAttachment(attachment: Attachment, binaryPool: BinaryPool) {
|
||||||
|
binaries[attachment.name] = binaryPool.put(attachment.binaryAttachment)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun removeAttachment(attachment: Attachment) {
|
||||||
|
binaries.remove(attachment.name)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun getAttachmentsSize(binaryPool: BinaryPool): Long {
|
||||||
|
var size = 0L
|
||||||
|
for ((label, poolId) in binaries) {
|
||||||
|
size += label.length.toLong()
|
||||||
|
size += binaryPool[poolId]?.length() ?: 0
|
||||||
|
}
|
||||||
|
return size
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO Remove ?
|
||||||
fun sizeOfHistory(): Int {
|
fun sizeOfHistory(): Int {
|
||||||
return history.size
|
return history.size
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -27,14 +27,12 @@ import com.kunzisoft.keepass.database.element.entry.EntryKDB
|
|||||||
import com.kunzisoft.keepass.database.element.group.GroupKDB
|
import com.kunzisoft.keepass.database.element.group.GroupKDB
|
||||||
import com.kunzisoft.keepass.database.element.node.NodeIdInt
|
import com.kunzisoft.keepass.database.element.node.NodeIdInt
|
||||||
import com.kunzisoft.keepass.database.element.node.NodeIdUUID
|
import com.kunzisoft.keepass.database.element.node.NodeIdUUID
|
||||||
import com.kunzisoft.keepass.database.element.security.BinaryAttachment
|
|
||||||
import com.kunzisoft.keepass.database.element.security.EncryptionAlgorithm
|
import com.kunzisoft.keepass.database.element.security.EncryptionAlgorithm
|
||||||
import com.kunzisoft.keepass.database.exception.*
|
import com.kunzisoft.keepass.database.exception.*
|
||||||
import com.kunzisoft.keepass.database.file.DatabaseHeader
|
import com.kunzisoft.keepass.database.file.DatabaseHeader
|
||||||
import com.kunzisoft.keepass.database.file.DatabaseHeaderKDB
|
import com.kunzisoft.keepass.database.file.DatabaseHeaderKDB
|
||||||
import com.kunzisoft.keepass.stream.*
|
import com.kunzisoft.keepass.stream.*
|
||||||
import com.kunzisoft.keepass.tasks.ProgressTaskUpdater
|
import com.kunzisoft.keepass.tasks.ProgressTaskUpdater
|
||||||
import org.joda.time.Instant
|
|
||||||
import java.io.*
|
import java.io.*
|
||||||
import java.security.*
|
import java.security.*
|
||||||
import java.util.*
|
import java.util.*
|
||||||
@@ -282,11 +280,9 @@ class DatabaseInputKDB(cacheDirectory: File,
|
|||||||
0x000E -> {
|
0x000E -> {
|
||||||
newEntry?.let { entry ->
|
newEntry?.let { entry ->
|
||||||
if (fieldSize > 0) {
|
if (fieldSize > 0) {
|
||||||
// Generate an unique new file with timestamp
|
val binaryAttachment = mDatabaseToOpen.buildNewBinary(cacheDirectory)
|
||||||
val binaryFile = File(cacheDirectory,
|
entry.binaryData = binaryAttachment
|
||||||
Instant.now().millis.toString())
|
BufferedOutputStream(binaryAttachment.getOutputDataStream()).use { outputStream ->
|
||||||
entry.binaryData = BinaryAttachment(binaryFile)
|
|
||||||
BufferedOutputStream(FileOutputStream(binaryFile)).use { outputStream ->
|
|
||||||
cipherInputStream.readBytes(fieldSize,
|
cipherInputStream.readBytes(fieldSize,
|
||||||
DatabaseKDB.BUFFER_SIZE_BYTES) { buffer ->
|
DatabaseKDB.BUFFER_SIZE_BYTES) { buffer ->
|
||||||
outputStream.write(buffer)
|
outputStream.write(buffer)
|
||||||
|
|||||||
@@ -26,6 +26,7 @@ import com.kunzisoft.keepass.crypto.StreamCipherFactory
|
|||||||
import com.kunzisoft.keepass.crypto.engine.CipherEngine
|
import com.kunzisoft.keepass.crypto.engine.CipherEngine
|
||||||
import com.kunzisoft.keepass.database.element.DateInstant
|
import com.kunzisoft.keepass.database.element.DateInstant
|
||||||
import com.kunzisoft.keepass.database.element.DeletedObject
|
import com.kunzisoft.keepass.database.element.DeletedObject
|
||||||
|
import com.kunzisoft.keepass.database.element.Attachment
|
||||||
import com.kunzisoft.keepass.database.element.database.CompressionAlgorithm
|
import com.kunzisoft.keepass.database.element.database.CompressionAlgorithm
|
||||||
import com.kunzisoft.keepass.database.element.database.DatabaseKDBX
|
import com.kunzisoft.keepass.database.element.database.DatabaseKDBX
|
||||||
import com.kunzisoft.keepass.database.element.database.DatabaseKDBX.Companion.BASE_64_FLAG
|
import com.kunzisoft.keepass.database.element.database.DatabaseKDBX.Companion.BASE_64_FLAG
|
||||||
@@ -35,7 +36,7 @@ import com.kunzisoft.keepass.database.element.group.GroupKDBX
|
|||||||
import com.kunzisoft.keepass.database.element.icon.IconImageCustom
|
import com.kunzisoft.keepass.database.element.icon.IconImageCustom
|
||||||
import com.kunzisoft.keepass.database.element.node.NodeIdUUID
|
import com.kunzisoft.keepass.database.element.node.NodeIdUUID
|
||||||
import com.kunzisoft.keepass.database.element.node.NodeKDBXInterface
|
import com.kunzisoft.keepass.database.element.node.NodeKDBXInterface
|
||||||
import com.kunzisoft.keepass.database.element.security.BinaryAttachment
|
import com.kunzisoft.keepass.database.element.database.BinaryAttachment
|
||||||
import com.kunzisoft.keepass.database.element.security.ProtectedString
|
import com.kunzisoft.keepass.database.element.security.ProtectedString
|
||||||
import com.kunzisoft.keepass.database.exception.*
|
import com.kunzisoft.keepass.database.exception.*
|
||||||
import com.kunzisoft.keepass.database.file.DatabaseHeaderKDBX
|
import com.kunzisoft.keepass.database.file.DatabaseHeaderKDBX
|
||||||
@@ -49,12 +50,14 @@ import org.bouncycastle.crypto.StreamCipher
|
|||||||
import org.xmlpull.v1.XmlPullParser
|
import org.xmlpull.v1.XmlPullParser
|
||||||
import org.xmlpull.v1.XmlPullParserException
|
import org.xmlpull.v1.XmlPullParserException
|
||||||
import org.xmlpull.v1.XmlPullParserFactory
|
import org.xmlpull.v1.XmlPullParserFactory
|
||||||
import java.io.*
|
import java.io.File
|
||||||
|
import java.io.IOException
|
||||||
|
import java.io.InputStream
|
||||||
|
import java.io.UnsupportedEncodingException
|
||||||
import java.nio.charset.Charset
|
import java.nio.charset.Charset
|
||||||
import java.text.ParseException
|
import java.text.ParseException
|
||||||
import java.util.*
|
import java.util.*
|
||||||
import java.util.zip.GZIPInputStream
|
import java.util.zip.GZIPInputStream
|
||||||
import java.util.zip.GZIPOutputStream
|
|
||||||
import javax.crypto.Cipher
|
import javax.crypto.Cipher
|
||||||
import javax.crypto.CipherInputStream
|
import javax.crypto.CipherInputStream
|
||||||
import kotlin.math.min
|
import kotlin.math.min
|
||||||
@@ -68,9 +71,6 @@ class DatabaseInputKDBX(cacheDirectory: File,
|
|||||||
|
|
||||||
private var hashOfHeader: ByteArray? = null
|
private var hashOfHeader: ByteArray? = null
|
||||||
|
|
||||||
private val unusedCacheFileName: String
|
|
||||||
get() = mDatabase.binaryPool.findUnusedKey().toString()
|
|
||||||
|
|
||||||
private var readNextNode = true
|
private var readNextNode = true
|
||||||
private val ctxGroups = Stack<GroupKDBX>()
|
private val ctxGroups = Stack<GroupKDBX>()
|
||||||
private var ctxGroup: GroupKDBX? = null
|
private var ctxGroup: GroupKDBX? = null
|
||||||
@@ -233,8 +233,10 @@ class DatabaseInputKDBX(cacheDirectory: File,
|
|||||||
|
|
||||||
var data = ByteArray(0)
|
var data = ByteArray(0)
|
||||||
if (size > 0) {
|
if (size > 0) {
|
||||||
if (fieldId != DatabaseHeaderKDBX.PwDbInnerHeaderV4Fields.Binary)
|
if (fieldId != DatabaseHeaderKDBX.PwDbInnerHeaderV4Fields.Binary) {
|
||||||
|
// TODO OOM here
|
||||||
data = dataInputStream.readBytes(size)
|
data = dataInputStream.readBytes(size)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var result = true
|
var result = true
|
||||||
@@ -249,18 +251,16 @@ class DatabaseInputKDBX(cacheDirectory: File,
|
|||||||
header.innerRandomStreamKey = data
|
header.innerRandomStreamKey = data
|
||||||
}
|
}
|
||||||
DatabaseHeaderKDBX.PwDbInnerHeaderV4Fields.Binary -> {
|
DatabaseHeaderKDBX.PwDbInnerHeaderV4Fields.Binary -> {
|
||||||
val flag = dataInputStream.readBytes(1)[0].toInt() != 0
|
|
||||||
val protectedFlag = flag && DatabaseHeaderKDBX.KdbxBinaryFlags.Protected.toInt() != DatabaseHeaderKDBX.KdbxBinaryFlags.None.toInt()
|
|
||||||
val byteLength = size - 1
|
|
||||||
// Read in a file
|
// Read in a file
|
||||||
val file = File(cacheDirectory, unusedCacheFileName)
|
val protectedFlag = dataInputStream.readBytes(1)[0].toInt() != 0
|
||||||
FileOutputStream(file).use { outputStream ->
|
val byteLength = size - 1
|
||||||
|
// No compression at this level
|
||||||
|
val protectedBinary = mDatabase.buildNewBinary(cacheDirectory, protectedFlag, false)
|
||||||
|
protectedBinary.getOutputDataStream().use { outputStream ->
|
||||||
dataInputStream.readBytes(byteLength, DatabaseKDBX.BUFFER_SIZE_BYTES) { buffer ->
|
dataInputStream.readBytes(byteLength, DatabaseKDBX.BUFFER_SIZE_BYTES) { buffer ->
|
||||||
outputStream.write(buffer)
|
outputStream.write(buffer)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
val protectedBinary = BinaryAttachment(file, protectedFlag)
|
|
||||||
mDatabase.binaryPool.add(protectedBinary)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -443,14 +443,7 @@ class DatabaseInputKDBX(cacheDirectory: File,
|
|||||||
}
|
}
|
||||||
|
|
||||||
KdbContext.Binaries -> if (name.equals(DatabaseKDBXXML.ElemBinary, ignoreCase = true)) {
|
KdbContext.Binaries -> if (name.equals(DatabaseKDBXXML.ElemBinary, ignoreCase = true)) {
|
||||||
val key = xpp.getAttributeValue(null, DatabaseKDBXXML.AttrId)
|
readBinary(xpp)
|
||||||
if (key != null) {
|
|
||||||
val pbData = readBinary(xpp)
|
|
||||||
val id = Integer.parseInt(key)
|
|
||||||
mDatabase.binaryPool.put(id, pbData!!)
|
|
||||||
} else {
|
|
||||||
readUnknown(xpp)
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
readUnknown(xpp)
|
readUnknown(xpp)
|
||||||
}
|
}
|
||||||
@@ -766,8 +759,9 @@ class DatabaseInputKDBX(cacheDirectory: File,
|
|||||||
|
|
||||||
return KdbContext.Entry
|
return KdbContext.Entry
|
||||||
} else if (ctx == KdbContext.EntryBinary && name.equals(DatabaseKDBXXML.ElemBinary, ignoreCase = true)) {
|
} else if (ctx == KdbContext.EntryBinary && name.equals(DatabaseKDBXXML.ElemBinary, ignoreCase = true)) {
|
||||||
if (ctxBinaryName != null && ctxBinaryValue != null)
|
if (ctxBinaryName != null && ctxBinaryValue != null) {
|
||||||
ctxEntry?.putProtectedBinary(ctxBinaryName!!, ctxBinaryValue!!)
|
ctxEntry?.putAttachment(Attachment(ctxBinaryName!!, ctxBinaryValue!!), mDatabase.binaryPool)
|
||||||
|
}
|
||||||
ctxBinaryName = null
|
ctxBinaryName = null
|
||||||
ctxBinaryValue = null
|
ctxBinaryValue = null
|
||||||
|
|
||||||
@@ -947,50 +941,56 @@ class DatabaseInputKDBX(cacheDirectory: File,
|
|||||||
|
|
||||||
// Reference Id to a binary already present in binary pool
|
// Reference Id to a binary already present in binary pool
|
||||||
val ref = xpp.getAttributeValue(null, DatabaseKDBXXML.AttrRef)
|
val ref = xpp.getAttributeValue(null, DatabaseKDBXXML.AttrRef)
|
||||||
if (ref != null) {
|
// New id to a binary
|
||||||
xpp.next() // Consume end tag
|
val key = xpp.getAttributeValue(null, DatabaseKDBXXML.AttrId)
|
||||||
|
|
||||||
val id = Integer.parseInt(ref)
|
return when {
|
||||||
return mDatabase.binaryPool[id]
|
ref != null -> {
|
||||||
}
|
xpp.next() // Consume end tag
|
||||||
|
val id = Integer.parseInt(ref)
|
||||||
// New binary to retrieve
|
// A ref is not necessarily an index in Database V3.1
|
||||||
else {
|
mDatabase.binaryPool[id]
|
||||||
var compressed = false
|
|
||||||
var protected = false
|
|
||||||
|
|
||||||
if (xpp.attributeCount > 0) {
|
|
||||||
val compress = xpp.getAttributeValue(null, DatabaseKDBXXML.AttrCompressed)
|
|
||||||
if (compress != null) {
|
|
||||||
compressed = compress.equals(DatabaseKDBXXML.ValTrue, ignoreCase = true)
|
|
||||||
}
|
|
||||||
|
|
||||||
val protect = xpp.getAttributeValue(null, DatabaseKDBXXML.AttrProtected)
|
|
||||||
if (protect != null) {
|
|
||||||
protected = protect.equals(DatabaseKDBXXML.ValTrue, ignoreCase = true)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
key != null -> {
|
||||||
val base64 = readString(xpp)
|
createBinary(key.toIntOrNull(), xpp)
|
||||||
if (base64.isEmpty())
|
}
|
||||||
return BinaryAttachment()
|
else -> {
|
||||||
val data = Base64.decode(base64, BASE_64_FLAG)
|
// New binary to retrieve
|
||||||
|
createBinary(null, xpp)
|
||||||
val file = File(cacheDirectory, unusedCacheFileName)
|
|
||||||
return FileOutputStream(file).use { outputStream ->
|
|
||||||
// Force compression in this specific case
|
|
||||||
if (mDatabase.compressionAlgorithm == CompressionAlgorithm.GZip
|
|
||||||
&& !compressed) {
|
|
||||||
GZIPOutputStream(outputStream).write(data)
|
|
||||||
BinaryAttachment(file, protected, true)
|
|
||||||
} else {
|
|
||||||
outputStream.write(data)
|
|
||||||
BinaryAttachment(file, protected, compressed)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Throws(IOException::class, XmlPullParserException::class)
|
||||||
|
private fun createBinary(binaryId: Int?, xpp: XmlPullParser): BinaryAttachment? {
|
||||||
|
var compressed = false
|
||||||
|
var protected = false
|
||||||
|
|
||||||
|
if (xpp.attributeCount > 0) {
|
||||||
|
val compress = xpp.getAttributeValue(null, DatabaseKDBXXML.AttrCompressed)
|
||||||
|
if (compress != null) {
|
||||||
|
compressed = compress.equals(DatabaseKDBXXML.ValTrue, ignoreCase = true)
|
||||||
|
}
|
||||||
|
|
||||||
|
val protect = xpp.getAttributeValue(null, DatabaseKDBXXML.AttrProtected)
|
||||||
|
if (protect != null) {
|
||||||
|
protected = protect.equals(DatabaseKDBXXML.ValTrue, ignoreCase = true)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val base64 = readString(xpp)
|
||||||
|
if (base64.isEmpty())
|
||||||
|
return null
|
||||||
|
val data = Base64.decode(base64, BASE_64_FLAG)
|
||||||
|
|
||||||
|
// Build the new binary and compress
|
||||||
|
val binaryAttachment = mDatabase.buildNewBinary(cacheDirectory, protected, compressed, binaryId)
|
||||||
|
binaryAttachment.getOutputDataStream().use { outputStream ->
|
||||||
|
outputStream.write(data)
|
||||||
|
}
|
||||||
|
return binaryAttachment
|
||||||
|
}
|
||||||
|
|
||||||
@Throws(IOException::class, XmlPullParserException::class)
|
@Throws(IOException::class, XmlPullParserException::class)
|
||||||
private fun readString(xpp: XmlPullParser): String {
|
private fun readString(xpp: XmlPullParser): String {
|
||||||
val buf = readProtectedBase64String(xpp)
|
val buf = readProtectedBase64String(xpp)
|
||||||
|
|||||||
@@ -47,18 +47,25 @@ class DatabaseInnerHeaderOutputKDBX(private val database: DatabaseKDBX,
|
|||||||
dataOutputStream.writeInt(streamKeySize)
|
dataOutputStream.writeInt(streamKeySize)
|
||||||
dataOutputStream.write(header.innerRandomStreamKey)
|
dataOutputStream.write(header.innerRandomStreamKey)
|
||||||
|
|
||||||
database.binaryPool.doForEachBinary { _, protectedBinary ->
|
database.binaryPool.doForEachOrderedBinary { _, keyBinary ->
|
||||||
|
val protectedBinary = keyBinary.binary
|
||||||
var flag = DatabaseHeaderKDBX.KdbxBinaryFlags.None
|
var flag = DatabaseHeaderKDBX.KdbxBinaryFlags.None
|
||||||
if (protectedBinary.isProtected) {
|
if (protectedBinary.isProtected) {
|
||||||
flag = flag or DatabaseHeaderKDBX.KdbxBinaryFlags.Protected
|
flag = flag or DatabaseHeaderKDBX.KdbxBinaryFlags.Protected
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Force decompression to add binary in header
|
||||||
|
protectedBinary.decompress()
|
||||||
|
|
||||||
dataOutputStream.write(DatabaseHeaderKDBX.PwDbInnerHeaderV4Fields.Binary.toInt())
|
dataOutputStream.write(DatabaseHeaderKDBX.PwDbInnerHeaderV4Fields.Binary.toInt())
|
||||||
dataOutputStream.writeInt(protectedBinary.length().toInt() + 1) // TODO verify
|
dataOutputStream.writeInt(protectedBinary.length().toInt() + 1)
|
||||||
dataOutputStream.write(flag.toInt())
|
dataOutputStream.write(flag.toInt())
|
||||||
|
|
||||||
protectedBinary.getInputDataStream().readBytes(BUFFER_SIZE_BYTES) { buffer ->
|
// if was compressed in cache, uncompress it
|
||||||
dataOutputStream.write(buffer)
|
protectedBinary.getInputDataStream().use { inputStream ->
|
||||||
|
inputStream.readBytes(BUFFER_SIZE_BYTES) { buffer ->
|
||||||
|
dataOutputStream.write(buffer)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -38,7 +38,7 @@ import com.kunzisoft.keepass.database.element.entry.EntryKDBX
|
|||||||
import com.kunzisoft.keepass.database.element.group.GroupKDBX
|
import com.kunzisoft.keepass.database.element.group.GroupKDBX
|
||||||
import com.kunzisoft.keepass.database.element.icon.IconImageCustom
|
import com.kunzisoft.keepass.database.element.icon.IconImageCustom
|
||||||
import com.kunzisoft.keepass.database.element.node.NodeKDBXInterface
|
import com.kunzisoft.keepass.database.element.node.NodeKDBXInterface
|
||||||
import com.kunzisoft.keepass.database.element.security.BinaryAttachment
|
import com.kunzisoft.keepass.database.element.database.BinaryAttachment
|
||||||
import com.kunzisoft.keepass.database.element.security.MemoryProtectionConfig
|
import com.kunzisoft.keepass.database.element.security.MemoryProtectionConfig
|
||||||
import com.kunzisoft.keepass.database.element.security.ProtectedString
|
import com.kunzisoft.keepass.database.element.security.ProtectedString
|
||||||
import com.kunzisoft.keepass.database.exception.DatabaseOutputException
|
import com.kunzisoft.keepass.database.exception.DatabaseOutputException
|
||||||
@@ -55,7 +55,6 @@ import java.io.OutputStream
|
|||||||
import java.security.NoSuchAlgorithmException
|
import java.security.NoSuchAlgorithmException
|
||||||
import java.security.SecureRandom
|
import java.security.SecureRandom
|
||||||
import java.util.*
|
import java.util.*
|
||||||
import java.util.zip.GZIPInputStream
|
|
||||||
import java.util.zip.GZIPOutputStream
|
import java.util.zip.GZIPOutputStream
|
||||||
import javax.crypto.Cipher
|
import javax.crypto.Cipher
|
||||||
import javax.crypto.CipherOutputStream
|
import javax.crypto.CipherOutputStream
|
||||||
@@ -422,7 +421,6 @@ class DatabaseOutputKDBX(private val mDatabaseKDBX: DatabaseKDBX,
|
|||||||
private fun writeBinary(binary : BinaryAttachment) {
|
private fun writeBinary(binary : BinaryAttachment) {
|
||||||
val binaryLength = binary.length()
|
val binaryLength = binary.length()
|
||||||
if (binaryLength > 0) {
|
if (binaryLength > 0) {
|
||||||
|
|
||||||
if (binary.isProtected) {
|
if (binary.isProtected) {
|
||||||
xml.attribute(null, DatabaseKDBXXML.AttrProtected, DatabaseKDBXXML.ValTrue)
|
xml.attribute(null, DatabaseKDBXXML.AttrProtected, DatabaseKDBXXML.ValTrue)
|
||||||
|
|
||||||
@@ -433,21 +431,11 @@ class DatabaseOutputKDBX(private val mDatabaseKDBX: DatabaseKDBX,
|
|||||||
xml.text(charArray, 0, charArray.size)
|
xml.text(charArray, 0, charArray.size)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// Force binary compression from database (compression was harmonized during import)
|
if (binary.isCompressed) {
|
||||||
if (mDatabaseKDBX.compressionAlgorithm === CompressionAlgorithm.GZip) {
|
|
||||||
xml.attribute(null, DatabaseKDBXXML.AttrCompressed, DatabaseKDBXXML.ValTrue)
|
xml.attribute(null, DatabaseKDBXXML.AttrCompressed, DatabaseKDBXXML.ValTrue)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Force decompression in this specific case
|
|
||||||
val binaryInputStream = if (mDatabaseKDBX.compressionAlgorithm == CompressionAlgorithm.None
|
|
||||||
&& binary.isCompressed == true) {
|
|
||||||
GZIPInputStream(binary.getInputDataStream())
|
|
||||||
} else {
|
|
||||||
binary.getInputDataStream()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Write the XML
|
// Write the XML
|
||||||
binaryInputStream.readBytes(BUFFER_SIZE_BYTES) { buffer ->
|
binary.getInputDataStream().readBytes(BUFFER_SIZE_BYTES) { buffer ->
|
||||||
val charArray = String(Base64.encode(buffer, BASE_64_FLAG)).toCharArray()
|
val charArray = String(Base64.encode(buffer, BASE_64_FLAG)).toCharArray()
|
||||||
xml.text(charArray, 0, charArray.size)
|
xml.text(charArray, 0, charArray.size)
|
||||||
}
|
}
|
||||||
@@ -459,10 +447,11 @@ class DatabaseOutputKDBX(private val mDatabaseKDBX: DatabaseKDBX,
|
|||||||
private fun writeMetaBinaries() {
|
private fun writeMetaBinaries() {
|
||||||
xml.startTag(null, DatabaseKDBXXML.ElemBinaries)
|
xml.startTag(null, DatabaseKDBXXML.ElemBinaries)
|
||||||
|
|
||||||
mDatabaseKDBX.binaryPool.doForEachBinary { key, binary ->
|
// Use indexes because necessarily in DatabaseV4 (binary header ref is the order)
|
||||||
|
mDatabaseKDBX.binaryPool.doForEachOrderedBinary { index, keyBinary ->
|
||||||
xml.startTag(null, DatabaseKDBXXML.ElemBinary)
|
xml.startTag(null, DatabaseKDBXXML.ElemBinary)
|
||||||
xml.attribute(null, DatabaseKDBXXML.AttrId, key.toString())
|
xml.attribute(null, DatabaseKDBXXML.AttrId, index.toString())
|
||||||
writeBinary(binary)
|
writeBinary(keyBinary.binary)
|
||||||
xml.endTag(null, DatabaseKDBXXML.ElemBinary)
|
xml.endTag(null, DatabaseKDBXXML.ElemBinary)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -559,23 +548,22 @@ class DatabaseOutputKDBX(private val mDatabaseKDBX: DatabaseKDBX,
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Throws(IllegalArgumentException::class, IllegalStateException::class, IOException::class)
|
@Throws(IllegalArgumentException::class, IllegalStateException::class, IOException::class)
|
||||||
private fun writeEntryBinaries(binaries: Map<String, BinaryAttachment>) {
|
private fun writeEntryBinaries(binaries: LinkedHashMap<String, Int>) {
|
||||||
for ((key, binary) in binaries) {
|
for ((label, poolId) in binaries) {
|
||||||
xml.startTag(null, DatabaseKDBXXML.ElemBinary)
|
// Retrieve the right index with the poolId, don't use ref because of header in DatabaseV4
|
||||||
xml.startTag(null, DatabaseKDBXXML.ElemKey)
|
mDatabaseKDBX.binaryPool.getBinaryIndexFromKey(poolId)?.toString()?.let { indexString ->
|
||||||
xml.text(safeXmlString(key))
|
xml.startTag(null, DatabaseKDBXXML.ElemBinary)
|
||||||
xml.endTag(null, DatabaseKDBXXML.ElemKey)
|
xml.startTag(null, DatabaseKDBXXML.ElemKey)
|
||||||
|
xml.text(safeXmlString(label))
|
||||||
|
xml.endTag(null, DatabaseKDBXXML.ElemKey)
|
||||||
|
|
||||||
xml.startTag(null, DatabaseKDBXXML.ElemValue)
|
xml.startTag(null, DatabaseKDBXXML.ElemValue)
|
||||||
val ref = mDatabaseKDBX.binaryPool.findKey(binary)
|
// Use only pool data in Meta to save binaries
|
||||||
if (ref != null) {
|
xml.attribute(null, DatabaseKDBXXML.AttrRef, indexString)
|
||||||
xml.attribute(null, DatabaseKDBXXML.AttrRef, ref.toString())
|
xml.endTag(null, DatabaseKDBXXML.ElemValue)
|
||||||
} else {
|
|
||||||
writeBinary(binary)
|
xml.endTag(null, DatabaseKDBXXML.ElemBinary)
|
||||||
}
|
}
|
||||||
xml.endTag(null, DatabaseKDBXXML.ElemValue)
|
|
||||||
|
|
||||||
xml.endTag(null, DatabaseKDBXXML.ElemBinary)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -21,24 +21,25 @@ package com.kunzisoft.keepass.model
|
|||||||
|
|
||||||
import android.os.Parcel
|
import android.os.Parcel
|
||||||
import android.os.Parcelable
|
import android.os.Parcelable
|
||||||
import com.kunzisoft.keepass.database.element.security.BinaryAttachment
|
import com.kunzisoft.keepass.database.element.Attachment
|
||||||
|
import com.kunzisoft.keepass.database.element.database.BinaryAttachment
|
||||||
import com.kunzisoft.keepass.utils.readEnum
|
import com.kunzisoft.keepass.utils.readEnum
|
||||||
import com.kunzisoft.keepass.utils.writeEnum
|
import com.kunzisoft.keepass.utils.writeEnum
|
||||||
|
|
||||||
data class EntryAttachment(var name: String,
|
data class EntryAttachmentState(var attachment: Attachment,
|
||||||
var binaryAttachment: BinaryAttachment,
|
var streamDirection: StreamDirection,
|
||||||
var downloadState: AttachmentState = AttachmentState.NULL,
|
var downloadState: AttachmentState = AttachmentState.NULL,
|
||||||
var downloadProgression: Int = 0) : Parcelable {
|
var downloadProgression: Int = 0) : Parcelable {
|
||||||
|
|
||||||
constructor(parcel: Parcel) : this(
|
constructor(parcel: Parcel) : this(
|
||||||
parcel.readString() ?: "",
|
parcel.readParcelable(Attachment::class.java.classLoader) ?: Attachment("", BinaryAttachment()),
|
||||||
parcel.readParcelable(BinaryAttachment::class.java.classLoader) ?: BinaryAttachment(),
|
parcel.readEnum<StreamDirection>() ?: StreamDirection.DOWNLOAD,
|
||||||
parcel.readEnum<AttachmentState>() ?: AttachmentState.NULL,
|
parcel.readEnum<AttachmentState>() ?: AttachmentState.NULL,
|
||||||
parcel.readInt())
|
parcel.readInt())
|
||||||
|
|
||||||
override fun writeToParcel(parcel: Parcel, flags: Int) {
|
override fun writeToParcel(parcel: Parcel, flags: Int) {
|
||||||
parcel.writeString(name)
|
parcel.writeParcelable(attachment, flags)
|
||||||
parcel.writeParcelable(binaryAttachment, flags)
|
parcel.writeEnum(streamDirection)
|
||||||
parcel.writeEnum(downloadState)
|
parcel.writeEnum(downloadState)
|
||||||
parcel.writeInt(downloadProgression)
|
parcel.writeInt(downloadProgression)
|
||||||
}
|
}
|
||||||
@@ -49,26 +50,23 @@ data class EntryAttachment(var name: String,
|
|||||||
|
|
||||||
override fun equals(other: Any?): Boolean {
|
override fun equals(other: Any?): Boolean {
|
||||||
if (this === other) return true
|
if (this === other) return true
|
||||||
if (other !is EntryAttachment) return false
|
if (other !is EntryAttachmentState) return false
|
||||||
|
|
||||||
if (name != other.name) return false
|
if (attachment != other.attachment) return false
|
||||||
if (binaryAttachment != other.binaryAttachment) return false
|
|
||||||
|
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun hashCode(): Int {
|
override fun hashCode(): Int {
|
||||||
var result = name.hashCode()
|
return attachment.hashCode()
|
||||||
result = 31 * result + binaryAttachment.hashCode()
|
|
||||||
return result
|
|
||||||
}
|
}
|
||||||
|
|
||||||
companion object CREATOR : Parcelable.Creator<EntryAttachment> {
|
companion object CREATOR : Parcelable.Creator<EntryAttachmentState> {
|
||||||
override fun createFromParcel(parcel: Parcel): EntryAttachment {
|
override fun createFromParcel(parcel: Parcel): EntryAttachmentState {
|
||||||
return EntryAttachment(parcel)
|
return EntryAttachmentState(parcel)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun newArray(size: Int): Array<EntryAttachment?> {
|
override fun newArray(size: Int): Array<EntryAttachmentState?> {
|
||||||
return arrayOfNulls(size)
|
return arrayOfNulls(size)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
package com.kunzisoft.keepass.model
|
||||||
|
|
||||||
|
enum class StreamDirection {
|
||||||
|
UPLOAD, DOWNLOAD
|
||||||
|
}
|
||||||
@@ -28,17 +28,24 @@ import android.os.IBinder
|
|||||||
import android.util.Log
|
import android.util.Log
|
||||||
import androidx.documentfile.provider.DocumentFile
|
import androidx.documentfile.provider.DocumentFile
|
||||||
import com.kunzisoft.keepass.R
|
import com.kunzisoft.keepass.R
|
||||||
|
import com.kunzisoft.keepass.database.element.Attachment
|
||||||
|
import com.kunzisoft.keepass.database.element.database.BinaryAttachment
|
||||||
import com.kunzisoft.keepass.model.AttachmentState
|
import com.kunzisoft.keepass.model.AttachmentState
|
||||||
import com.kunzisoft.keepass.model.EntryAttachment
|
import com.kunzisoft.keepass.model.EntryAttachmentState
|
||||||
|
import com.kunzisoft.keepass.model.StreamDirection
|
||||||
|
import com.kunzisoft.keepass.stream.readBytes
|
||||||
import com.kunzisoft.keepass.timeout.TimeoutHelper
|
import com.kunzisoft.keepass.timeout.TimeoutHelper
|
||||||
|
import com.kunzisoft.keepass.utils.UriUtil
|
||||||
import kotlinx.coroutines.*
|
import kotlinx.coroutines.*
|
||||||
|
import java.io.BufferedInputStream
|
||||||
import java.util.*
|
import java.util.*
|
||||||
import kotlin.collections.HashMap
|
import java.util.concurrent.CopyOnWriteArrayList
|
||||||
|
|
||||||
|
|
||||||
class AttachmentFileNotificationService: LockNotificationService() {
|
class AttachmentFileNotificationService: LockNotificationService() {
|
||||||
|
|
||||||
override val notificationId: Int = 10000
|
override val notificationId: Int = 10000
|
||||||
|
private val attachmentNotificationList = CopyOnWriteArrayList<AttachmentNotification>()
|
||||||
|
|
||||||
private var mActionTaskBinder = ActionTaskBinder()
|
private var mActionTaskBinder = ActionTaskBinder()
|
||||||
private var mActionTaskListeners = LinkedList<ActionTaskListener>()
|
private var mActionTaskListeners = LinkedList<ActionTaskListener>()
|
||||||
@@ -51,33 +58,31 @@ class AttachmentFileNotificationService: LockNotificationService() {
|
|||||||
|
|
||||||
fun addActionTaskListener(actionTaskListener: ActionTaskListener) {
|
fun addActionTaskListener(actionTaskListener: ActionTaskListener) {
|
||||||
mActionTaskListeners.add(actionTaskListener)
|
mActionTaskListeners.add(actionTaskListener)
|
||||||
|
attachmentNotificationList.forEach {
|
||||||
downloadFileUris.forEach(object : (Map.Entry<Uri, AttachmentNotification>) -> Unit {
|
it.attachmentFileAction?.listener = attachmentFileActionListener
|
||||||
override fun invoke(entry: Map.Entry<Uri, AttachmentNotification>) {
|
}
|
||||||
entry.value.attachmentTask?.onUpdate = { uri, attachment, notificationIdAttach ->
|
|
||||||
newNotification(uri, attachment, notificationIdAttach)
|
|
||||||
mActionTaskListeners.forEach { actionListener ->
|
|
||||||
actionListener.onAttachmentProgress(entry.key, attachment)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun removeActionTaskListener(actionTaskListener: ActionTaskListener) {
|
fun removeActionTaskListener(actionTaskListener: ActionTaskListener) {
|
||||||
downloadFileUris.forEach(object : (Map.Entry<Uri, AttachmentNotification>) -> Unit {
|
attachmentNotificationList.forEach {
|
||||||
override fun invoke(entry: Map.Entry<Uri, AttachmentNotification>) {
|
it.attachmentFileAction?.listener = null
|
||||||
entry.value.attachmentTask?.onUpdate = null
|
}
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
mActionTaskListeners.remove(actionTaskListener)
|
mActionTaskListeners.remove(actionTaskListener)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private val attachmentFileActionListener = object: AttachmentFileAction.AttachmentFileActionListener {
|
||||||
|
override fun onUpdate(attachmentNotification: AttachmentNotification) {
|
||||||
|
newNotification(attachmentNotification)
|
||||||
|
mActionTaskListeners.forEach { actionListener ->
|
||||||
|
actionListener.onAttachmentAction(attachmentNotification.uri,
|
||||||
|
attachmentNotification.entryAttachmentState)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
interface ActionTaskListener {
|
interface ActionTaskListener {
|
||||||
fun onAttachmentProgress(fileUri: Uri, attachment: EntryAttachment)
|
fun onAttachmentAction(fileUri: Uri, entryAttachmentState: EntryAttachmentState)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onBind(intent: Intent): IBinder? {
|
override fun onBind(intent: Intent): IBinder? {
|
||||||
@@ -87,49 +92,29 @@ class AttachmentFileNotificationService: LockNotificationService() {
|
|||||||
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
|
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
|
||||||
super.onStartCommand(intent, flags, startId)
|
super.onStartCommand(intent, flags, startId)
|
||||||
|
|
||||||
val downloadFileUri: Uri? = if (intent?.hasExtra(DOWNLOAD_FILE_URI_KEY) == true) {
|
val downloadFileUri: Uri? = if (intent?.hasExtra(FILE_URI_KEY) == true) {
|
||||||
intent.getParcelableExtra(DOWNLOAD_FILE_URI_KEY)
|
intent.getParcelableExtra(FILE_URI_KEY)
|
||||||
} else null
|
} else null
|
||||||
|
|
||||||
when(intent?.action) {
|
when(intent?.action) {
|
||||||
|
ACTION_ATTACHMENT_FILE_START_UPLOAD -> {
|
||||||
|
actionUploadOrDownload(downloadFileUri,
|
||||||
|
intent,
|
||||||
|
StreamDirection.UPLOAD)
|
||||||
|
}
|
||||||
ACTION_ATTACHMENT_FILE_START_DOWNLOAD -> {
|
ACTION_ATTACHMENT_FILE_START_DOWNLOAD -> {
|
||||||
if (downloadFileUri != null
|
actionUploadOrDownload(downloadFileUri,
|
||||||
&& intent.hasExtra(ATTACHMENT_KEY)) {
|
intent,
|
||||||
|
StreamDirection.DOWNLOAD)
|
||||||
val nextNotificationId = (downloadFileUris.values.maxByOrNull { it.notificationId }
|
|
||||||
?.notificationId ?: notificationId) + 1
|
|
||||||
|
|
||||||
try {
|
|
||||||
intent.getParcelableExtra<EntryAttachment>(ATTACHMENT_KEY)?.let { entryAttachment ->
|
|
||||||
val attachmentNotification = AttachmentNotification(nextNotificationId, entryAttachment)
|
|
||||||
downloadFileUris[downloadFileUri] = attachmentNotification
|
|
||||||
|
|
||||||
mainScope.launch {
|
|
||||||
AttachmentFileActionClass(downloadFileUri,
|
|
||||||
attachmentNotification,
|
|
||||||
contentResolver).apply {
|
|
||||||
onUpdate = { uri, attachment, notificationIdAttach ->
|
|
||||||
newNotification(uri, attachment, notificationIdAttach)
|
|
||||||
mActionTaskListeners.forEach { actionListener ->
|
|
||||||
actionListener.onAttachmentProgress(downloadFileUri, attachment)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}.executeAction()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (e: Exception) {
|
|
||||||
Log.e(TAG, "Unable to download $downloadFileUri", e)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
else -> {
|
else -> {
|
||||||
if (downloadFileUri != null) {
|
if (downloadFileUri != null) {
|
||||||
downloadFileUris[downloadFileUri]?.notificationId?.let {
|
attachmentNotificationList.firstOrNull { it.uri == downloadFileUri }?.let { elementToRemove ->
|
||||||
notificationManager?.cancel(it)
|
notificationManager?.cancel(elementToRemove.notificationId)
|
||||||
downloadFileUris.remove(downloadFileUri)
|
attachmentNotificationList.remove(elementToRemove)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (downloadFileUris.isEmpty()) {
|
if (attachmentNotificationList.isEmpty()) {
|
||||||
stopSelf()
|
stopSelf()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -138,25 +123,35 @@ class AttachmentFileNotificationService: LockNotificationService() {
|
|||||||
return START_REDELIVER_INTENT
|
return START_REDELIVER_INTENT
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Synchronized
|
||||||
fun checkCurrentAttachmentProgress() {
|
fun checkCurrentAttachmentProgress() {
|
||||||
downloadFileUris.forEach(object : (Map.Entry<Uri, AttachmentNotification>) -> Unit {
|
attachmentNotificationList.forEach { attachmentNotification ->
|
||||||
override fun invoke(entry: Map.Entry<Uri, AttachmentNotification>) {
|
mActionTaskListeners.forEach { actionListener ->
|
||||||
mActionTaskListeners.forEach { actionListener ->
|
actionListener.onAttachmentAction(
|
||||||
actionListener.onAttachmentProgress(entry.key, entry.value.entryAttachment)
|
attachmentNotification.uri,
|
||||||
}
|
attachmentNotification.entryAttachmentState
|
||||||
|
)
|
||||||
}
|
}
|
||||||
})
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun newNotification(downloadFileUri: Uri,
|
@Synchronized
|
||||||
entryAttachment: EntryAttachment,
|
fun removeAttachmentAction(entryAttachment: EntryAttachmentState) {
|
||||||
notificationIdAttachment: Int) {
|
attachmentNotificationList.firstOrNull {
|
||||||
|
it.entryAttachmentState == entryAttachment
|
||||||
|
}?.let {
|
||||||
|
attachmentNotificationList.remove(it)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun newNotification(attachmentNotification: AttachmentNotification) {
|
||||||
|
|
||||||
val pendingContentIntent = PendingIntent.getActivity(this,
|
val pendingContentIntent = PendingIntent.getActivity(this,
|
||||||
0,
|
0,
|
||||||
Intent().apply {
|
Intent().apply {
|
||||||
action = Intent.ACTION_VIEW
|
action = Intent.ACTION_VIEW
|
||||||
setDataAndType(downloadFileUri, contentResolver.getType(downloadFileUri))
|
setDataAndType(attachmentNotification.uri,
|
||||||
|
contentResolver.getType(attachmentNotification.uri))
|
||||||
addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
|
addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
|
||||||
}, PendingIntent.FLAG_CANCEL_CURRENT)
|
}, PendingIntent.FLAG_CANCEL_CURRENT)
|
||||||
|
|
||||||
@@ -164,54 +159,84 @@ class AttachmentFileNotificationService: LockNotificationService() {
|
|||||||
0,
|
0,
|
||||||
Intent(this, AttachmentFileNotificationService::class.java).apply {
|
Intent(this, AttachmentFileNotificationService::class.java).apply {
|
||||||
// No action to delete the service
|
// No action to delete the service
|
||||||
putExtra(DOWNLOAD_FILE_URI_KEY, downloadFileUri)
|
putExtra(FILE_URI_KEY, attachmentNotification.uri)
|
||||||
}, PendingIntent.FLAG_CANCEL_CURRENT)
|
}, PendingIntent.FLAG_CANCEL_CURRENT)
|
||||||
|
|
||||||
val fileName = DocumentFile.fromSingleUri(this, downloadFileUri)?.name ?: ""
|
val fileName = DocumentFile.fromSingleUri(this, attachmentNotification.uri)?.name ?: ""
|
||||||
|
|
||||||
val builder = buildNewNotification().apply {
|
val builder = buildNewNotification().apply {
|
||||||
setSmallIcon(R.drawable.ic_file_download_white_24dp)
|
when (attachmentNotification.entryAttachmentState.streamDirection) {
|
||||||
setContentTitle(getString(R.string.download_attachment, fileName))
|
StreamDirection.UPLOAD -> {
|
||||||
|
setSmallIcon(R.drawable.ic_file_upload_white_24dp)
|
||||||
|
setContentTitle(getString(R.string.upload_attachment, fileName))
|
||||||
|
}
|
||||||
|
StreamDirection.DOWNLOAD -> {
|
||||||
|
setSmallIcon(R.drawable.ic_file_download_white_24dp)
|
||||||
|
setContentTitle(getString(R.string.download_attachment, fileName))
|
||||||
|
}
|
||||||
|
}
|
||||||
setAutoCancel(false)
|
setAutoCancel(false)
|
||||||
when (entryAttachment.downloadState) {
|
when (attachmentNotification.entryAttachmentState.downloadState) {
|
||||||
AttachmentState.NULL, AttachmentState.START -> {
|
AttachmentState.NULL, AttachmentState.START -> {
|
||||||
setContentText(getString(R.string.download_initialization))
|
setContentText(getString(R.string.download_initialization))
|
||||||
setOngoing(true)
|
setOngoing(true)
|
||||||
}
|
}
|
||||||
AttachmentState.IN_PROGRESS -> {
|
AttachmentState.IN_PROGRESS -> {
|
||||||
if (entryAttachment.downloadProgression > 100) {
|
if (attachmentNotification.entryAttachmentState.downloadProgression > 100) {
|
||||||
setContentText(getString(R.string.download_finalization))
|
setContentText(getString(R.string.download_finalization))
|
||||||
} else {
|
} else {
|
||||||
setProgress(100, entryAttachment.downloadProgression, false)
|
setProgress(100,
|
||||||
setContentText(getString(R.string.download_progression, entryAttachment.downloadProgression))
|
attachmentNotification.entryAttachmentState.downloadProgression,
|
||||||
|
false)
|
||||||
|
setContentText(getString(R.string.download_progression,
|
||||||
|
attachmentNotification.entryAttachmentState.downloadProgression))
|
||||||
}
|
}
|
||||||
setOngoing(true)
|
setOngoing(true)
|
||||||
}
|
}
|
||||||
AttachmentState.COMPLETE, AttachmentState.ERROR -> {
|
AttachmentState.COMPLETE -> {
|
||||||
setContentText(getString(R.string.download_complete))
|
setContentText(getString(R.string.download_complete))
|
||||||
setContentIntent(pendingContentIntent)
|
when (attachmentNotification.entryAttachmentState.streamDirection) {
|
||||||
|
StreamDirection.UPLOAD -> {
|
||||||
|
|
||||||
|
}
|
||||||
|
StreamDirection.DOWNLOAD -> {
|
||||||
|
setContentIntent(pendingContentIntent)
|
||||||
|
}
|
||||||
|
}
|
||||||
setDeleteIntent(pendingDeleteIntent)
|
setDeleteIntent(pendingDeleteIntent)
|
||||||
setOngoing(false)
|
setOngoing(false)
|
||||||
}
|
}
|
||||||
|
AttachmentState.ERROR -> {
|
||||||
|
setContentText(getString(R.string.error_file_not_create))
|
||||||
|
setOngoing(false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
when (attachmentNotification.entryAttachmentState.downloadState) {
|
||||||
|
AttachmentState.ERROR,
|
||||||
|
AttachmentState.COMPLETE -> {
|
||||||
|
stopForeground(false)
|
||||||
|
notificationManager?.notify(attachmentNotification.notificationId, builder.build())
|
||||||
|
} else -> {
|
||||||
|
startForeground(attachmentNotification.notificationId, builder.build())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
notificationManager?.notify(notificationIdAttachment, builder.build())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onDestroy() {
|
override fun onDestroy() {
|
||||||
downloadFileUris.forEach(object : (Map.Entry<Uri, AttachmentNotification>) -> Unit {
|
attachmentNotificationList.forEach { attachmentNotification ->
|
||||||
override fun invoke(entry: Map.Entry<Uri, AttachmentNotification>) {
|
attachmentNotification.attachmentFileAction?.listener = null
|
||||||
entry.value.attachmentTask?.onUpdate = null
|
notificationManager?.cancel(attachmentNotification.notificationId)
|
||||||
notificationManager?.cancel(entry.value.notificationId)
|
}
|
||||||
}
|
attachmentNotificationList.clear()
|
||||||
})
|
|
||||||
|
|
||||||
super.onDestroy()
|
super.onDestroy()
|
||||||
}
|
}
|
||||||
|
|
||||||
private data class AttachmentNotification(var notificationId: Int,
|
private data class AttachmentNotification(var uri: Uri,
|
||||||
var entryAttachment: EntryAttachment,
|
var notificationId: Int,
|
||||||
var attachmentTask: AttachmentFileActionClass? = null) {
|
var entryAttachmentState: EntryAttachmentState,
|
||||||
|
var attachmentFileAction: AttachmentFileAction? = null) {
|
||||||
override fun equals(other: Any?): Boolean {
|
override fun equals(other: Any?): Boolean {
|
||||||
if (this === other) return true
|
if (this === other) return true
|
||||||
if (javaClass != other?.javaClass) return false
|
if (javaClass != other?.javaClass) return false
|
||||||
@@ -228,52 +253,85 @@ class AttachmentFileNotificationService: LockNotificationService() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private class AttachmentFileActionClass(
|
private fun actionUploadOrDownload(downloadFileUri: Uri?,
|
||||||
private val fileUri: Uri,
|
intent: Intent,
|
||||||
|
streamDirection: StreamDirection) {
|
||||||
|
if (downloadFileUri != null
|
||||||
|
&& intent.hasExtra(ATTACHMENT_KEY)) {
|
||||||
|
try {
|
||||||
|
intent.getParcelableExtra<Attachment>(ATTACHMENT_KEY)?.let { entryAttachment ->
|
||||||
|
|
||||||
|
val nextNotificationId = (attachmentNotificationList.maxByOrNull { it.notificationId }
|
||||||
|
?.notificationId ?: notificationId) + 1
|
||||||
|
val entryAttachmentState = EntryAttachmentState(entryAttachment, streamDirection)
|
||||||
|
val attachmentNotification = AttachmentNotification(downloadFileUri, nextNotificationId, entryAttachmentState)
|
||||||
|
attachmentNotificationList.add(attachmentNotification)
|
||||||
|
|
||||||
|
mainScope.launch {
|
||||||
|
AttachmentFileAction(attachmentNotification,
|
||||||
|
contentResolver).apply {
|
||||||
|
listener = attachmentFileActionListener
|
||||||
|
}.executeAction()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Log.e(TAG, "Unable to upload/download $downloadFileUri", e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private class AttachmentFileAction(
|
||||||
private val attachmentNotification: AttachmentNotification,
|
private val attachmentNotification: AttachmentNotification,
|
||||||
private val contentResolver: ContentResolver) {
|
private val contentResolver: ContentResolver) {
|
||||||
|
|
||||||
private val updateMinFrequency = 1000
|
private val updateMinFrequency = 1000
|
||||||
private var previousSaveTime = System.currentTimeMillis()
|
private var previousSaveTime = System.currentTimeMillis()
|
||||||
var onUpdate: ((Uri, EntryAttachment, Int)->Unit)? = null
|
var listener: AttachmentFileActionListener? = null
|
||||||
|
|
||||||
|
interface AttachmentFileActionListener {
|
||||||
|
fun onUpdate(attachmentNotification: AttachmentNotification)
|
||||||
|
}
|
||||||
|
|
||||||
suspend fun executeAction() {
|
suspend fun executeAction() {
|
||||||
TimeoutHelper.temporarilyDisableTimeout()
|
TimeoutHelper.temporarilyDisableTimeout()
|
||||||
|
|
||||||
// on pre execute
|
// on pre execute
|
||||||
attachmentNotification.attachmentTask = this
|
attachmentNotification.attachmentFileAction = this
|
||||||
attachmentNotification.entryAttachment.apply {
|
attachmentNotification.entryAttachmentState.apply {
|
||||||
downloadState = AttachmentState.START
|
downloadState = AttachmentState.START
|
||||||
downloadProgression = 0
|
downloadProgression = 0
|
||||||
}
|
}
|
||||||
onUpdate?.invoke(fileUri,
|
listener?.onUpdate(attachmentNotification)
|
||||||
attachmentNotification.entryAttachment,
|
|
||||||
attachmentNotification.notificationId)
|
|
||||||
|
|
||||||
withContext(Dispatchers.IO) {
|
withContext(Dispatchers.IO) {
|
||||||
// on Progress with thread
|
// on Progress with thread
|
||||||
val asyncResult: Deferred<Boolean> = async {
|
val asyncResult: Deferred<Boolean> = async {
|
||||||
var progressResult = true
|
var progressResult = true
|
||||||
try {
|
try {
|
||||||
attachmentNotification.entryAttachment.apply {
|
attachmentNotification.entryAttachmentState.apply {
|
||||||
downloadState = AttachmentState.IN_PROGRESS
|
downloadState = AttachmentState.IN_PROGRESS
|
||||||
binaryAttachment.download(fileUri, contentResolver, 1024) { percent ->
|
|
||||||
// Publish progress
|
when (streamDirection) {
|
||||||
val currentTime = System.currentTimeMillis()
|
StreamDirection.UPLOAD -> {
|
||||||
if (previousSaveTime + updateMinFrequency < currentTime) {
|
uploadToDatabase(
|
||||||
attachmentNotification.entryAttachment.apply {
|
attachmentNotification.uri,
|
||||||
downloadState = AttachmentState.IN_PROGRESS
|
attachment.binaryAttachment,
|
||||||
downloadProgression = percent
|
contentResolver, 1024) { percent ->
|
||||||
|
publishProgress(percent)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
StreamDirection.DOWNLOAD -> {
|
||||||
|
downloadFromDatabase(
|
||||||
|
attachmentNotification.uri,
|
||||||
|
attachment.binaryAttachment,
|
||||||
|
contentResolver, 1024) { percent ->
|
||||||
|
publishProgress(percent)
|
||||||
}
|
}
|
||||||
onUpdate?.invoke(fileUri,
|
|
||||||
attachmentNotification.entryAttachment,
|
|
||||||
attachmentNotification.notificationId)
|
|
||||||
Log.d(TAG, "Download file $fileUri : $percent%")
|
|
||||||
previousSaveTime = currentTime
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
|
Log.e(TAG, "Unable to upload or download file", e)
|
||||||
progressResult = false
|
progressResult = false
|
||||||
}
|
}
|
||||||
progressResult
|
progressResult
|
||||||
@@ -282,33 +340,95 @@ class AttachmentFileNotificationService: LockNotificationService() {
|
|||||||
// on post execute
|
// on post execute
|
||||||
withContext(Dispatchers.Main) {
|
withContext(Dispatchers.Main) {
|
||||||
val result = asyncResult.await()
|
val result = asyncResult.await()
|
||||||
attachmentNotification.attachmentTask = null
|
attachmentNotification.attachmentFileAction = null
|
||||||
attachmentNotification.entryAttachment.apply {
|
attachmentNotification.entryAttachmentState.apply {
|
||||||
downloadState = if (result) AttachmentState.COMPLETE else AttachmentState.ERROR
|
downloadState = if (result) AttachmentState.COMPLETE else AttachmentState.ERROR
|
||||||
downloadProgression = 100
|
downloadProgression = 100
|
||||||
}
|
}
|
||||||
onUpdate?.invoke(fileUri,
|
listener?.onUpdate(attachmentNotification)
|
||||||
attachmentNotification.entryAttachment,
|
TimeoutHelper.releaseTemporarilyDisableTimeout()
|
||||||
attachmentNotification.notificationId)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun downloadFromDatabase(attachmentToUploadUri: Uri,
|
||||||
|
binaryAttachment: BinaryAttachment,
|
||||||
|
contentResolver: ContentResolver,
|
||||||
|
bufferSize: Int = DEFAULT_BUFFER_SIZE,
|
||||||
|
update: ((percent: Int)->Unit)? = null) {
|
||||||
|
var dataDownloaded = 0L
|
||||||
|
val fileSize = binaryAttachment.length()
|
||||||
|
UriUtil.getUriOutputStream(contentResolver, attachmentToUploadUri)?.use { outputStream ->
|
||||||
|
binaryAttachment.getUnGzipInputDataStream().use { inputStream ->
|
||||||
|
inputStream.readBytes(bufferSize) { buffer ->
|
||||||
|
outputStream.write(buffer)
|
||||||
|
dataDownloaded += buffer.size
|
||||||
|
try {
|
||||||
|
val percentDownload = (100 * dataDownloaded / fileSize).toInt()
|
||||||
|
update?.invoke(percentDownload)
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Log.e(TAG, "", e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun uploadToDatabase(attachmentFromDownloadUri: Uri,
|
||||||
|
binaryAttachment: BinaryAttachment,
|
||||||
|
contentResolver: ContentResolver,
|
||||||
|
bufferSize: Int = DEFAULT_BUFFER_SIZE,
|
||||||
|
update: ((percent: Int)->Unit)? = null) {
|
||||||
|
var dataUploaded = 0L
|
||||||
|
val fileSize = contentResolver.openFileDescriptor(attachmentFromDownloadUri, "r")?.statSize ?: 0
|
||||||
|
UriUtil.getUriInputStream(contentResolver, attachmentFromDownloadUri)?.let { inputStream ->
|
||||||
|
binaryAttachment.getGzipOutputDataStream().use { outputStream ->
|
||||||
|
BufferedInputStream(inputStream).use { attachmentBufferedInputStream ->
|
||||||
|
attachmentBufferedInputStream.readBytes(bufferSize) { buffer ->
|
||||||
|
outputStream.write(buffer)
|
||||||
|
dataUploaded += buffer.size
|
||||||
|
try {
|
||||||
|
val percentDownload = (100 * dataUploaded / fileSize).toInt()
|
||||||
|
update?.invoke(percentDownload)
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Log.e(TAG, "", e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun publishProgress(percent: Int) {
|
||||||
|
// Publish progress
|
||||||
|
val currentTime = System.currentTimeMillis()
|
||||||
|
if (previousSaveTime + updateMinFrequency < currentTime) {
|
||||||
|
attachmentNotification.entryAttachmentState.apply {
|
||||||
|
downloadState = AttachmentState.IN_PROGRESS
|
||||||
|
downloadProgression = percent
|
||||||
|
}
|
||||||
|
CoroutineScope(Dispatchers.Main).launch {
|
||||||
|
listener?.onUpdate(attachmentNotification)
|
||||||
|
Log.d(TAG, "Download file ${attachmentNotification.uri} : $percent%")
|
||||||
|
}
|
||||||
|
previousSaveTime = currentTime
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
private val TAG = AttachmentFileActionClass::class.java.name
|
private val TAG = AttachmentFileAction::class.java.name
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
private val TAG = AttachmentFileNotificationService::javaClass.name
|
private val TAG = AttachmentFileNotificationService::javaClass.name
|
||||||
|
|
||||||
|
const val ACTION_ATTACHMENT_FILE_START_UPLOAD = "ACTION_ATTACHMENT_FILE_START_UPLOAD"
|
||||||
const val ACTION_ATTACHMENT_FILE_START_DOWNLOAD = "ACTION_ATTACHMENT_FILE_START_DOWNLOAD"
|
const val ACTION_ATTACHMENT_FILE_START_DOWNLOAD = "ACTION_ATTACHMENT_FILE_START_DOWNLOAD"
|
||||||
|
|
||||||
const val DOWNLOAD_FILE_URI_KEY = "DOWNLOAD_FILE_URI_KEY"
|
const val FILE_URI_KEY = "FILE_URI_KEY"
|
||||||
const val ATTACHMENT_KEY = "ATTACHMENT_KEY"
|
const val ATTACHMENT_KEY = "ATTACHMENT_KEY"
|
||||||
|
|
||||||
private val downloadFileUris = HashMap<Uri, AttachmentNotification>()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -1,59 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2017 Brian Pellin, 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.stream
|
|
||||||
|
|
||||||
import java.io.IOException
|
|
||||||
import java.io.OutputStream
|
|
||||||
import java.io.RandomAccessFile
|
|
||||||
|
|
||||||
class RandomFileOutputStream internal constructor(private val mFile: RandomAccessFile) : OutputStream() {
|
|
||||||
|
|
||||||
@Throws(IOException::class)
|
|
||||||
override fun close() {
|
|
||||||
super.close()
|
|
||||||
|
|
||||||
mFile.close()
|
|
||||||
}
|
|
||||||
|
|
||||||
@Throws(IOException::class)
|
|
||||||
override fun write(buffer: ByteArray, offset: Int, count: Int) {
|
|
||||||
super.write(buffer, offset, count)
|
|
||||||
|
|
||||||
mFile.write(buffer, offset, count)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Throws(IOException::class)
|
|
||||||
override fun write(buffer: ByteArray) {
|
|
||||||
super.write(buffer)
|
|
||||||
|
|
||||||
mFile.write(buffer)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Throws(IOException::class)
|
|
||||||
override fun write(oneByte: Int) {
|
|
||||||
mFile.write(oneByte)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Throws(IOException::class)
|
|
||||||
fun seek(pos: Long) {
|
|
||||||
mFile.seek(pos)
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -28,9 +28,12 @@ import android.net.Uri
|
|||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.os.IBinder
|
import android.os.IBinder
|
||||||
import androidx.fragment.app.FragmentActivity
|
import androidx.fragment.app.FragmentActivity
|
||||||
import com.kunzisoft.keepass.model.EntryAttachment
|
import com.kunzisoft.keepass.database.element.Attachment
|
||||||
|
import com.kunzisoft.keepass.model.AttachmentState
|
||||||
|
import com.kunzisoft.keepass.model.EntryAttachmentState
|
||||||
import com.kunzisoft.keepass.notifications.AttachmentFileNotificationService
|
import com.kunzisoft.keepass.notifications.AttachmentFileNotificationService
|
||||||
import com.kunzisoft.keepass.notifications.AttachmentFileNotificationService.Companion.ACTION_ATTACHMENT_FILE_START_DOWNLOAD
|
import com.kunzisoft.keepass.notifications.AttachmentFileNotificationService.Companion.ACTION_ATTACHMENT_FILE_START_DOWNLOAD
|
||||||
|
import com.kunzisoft.keepass.notifications.AttachmentFileNotificationService.Companion.ACTION_ATTACHMENT_FILE_START_UPLOAD
|
||||||
|
|
||||||
class AttachmentFileBinderManager(private val activity: FragmentActivity) {
|
class AttachmentFileBinderManager(private val activity: FragmentActivity) {
|
||||||
|
|
||||||
@@ -43,8 +46,18 @@ class AttachmentFileBinderManager(private val activity: FragmentActivity) {
|
|||||||
private var mServiceConnection: ServiceConnection? = null
|
private var mServiceConnection: ServiceConnection? = null
|
||||||
|
|
||||||
private val mActionTaskListener = object: AttachmentFileNotificationService.ActionTaskListener {
|
private val mActionTaskListener = object: AttachmentFileNotificationService.ActionTaskListener {
|
||||||
override fun onAttachmentProgress(fileUri: Uri, attachment: EntryAttachment) {
|
override fun onAttachmentAction(fileUri: Uri, entryAttachmentState: EntryAttachmentState) {
|
||||||
onActionTaskListener?.onAttachmentProgress(fileUri, attachment)
|
onActionTaskListener?.let {
|
||||||
|
it.onAttachmentAction(fileUri, entryAttachmentState)
|
||||||
|
when (entryAttachmentState.downloadState) {
|
||||||
|
AttachmentState.COMPLETE,
|
||||||
|
AttachmentState.ERROR -> {
|
||||||
|
// Finish the action when capture by activity
|
||||||
|
consummeAttachmentAction(entryAttachmentState)
|
||||||
|
}
|
||||||
|
else -> {}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -85,22 +98,32 @@ class AttachmentFileBinderManager(private val activity: FragmentActivity) {
|
|||||||
mServiceConnection = null
|
mServiceConnection = null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Synchronized
|
||||||
|
fun consummeAttachmentAction(attachment: EntryAttachmentState) {
|
||||||
|
mBinder?.getService()?.removeAttachmentAction(attachment)
|
||||||
|
}
|
||||||
|
|
||||||
@Synchronized
|
@Synchronized
|
||||||
private fun start(bundle: Bundle? = null, actionTask: String) {
|
private fun start(bundle: Bundle? = null, actionTask: String) {
|
||||||
activity.stopService(mIntentTask)
|
|
||||||
if (bundle != null)
|
if (bundle != null)
|
||||||
mIntentTask.putExtras(bundle)
|
mIntentTask.putExtras(bundle)
|
||||||
activity.runOnUiThread {
|
mIntentTask.action = actionTask
|
||||||
mIntentTask.action = actionTask
|
activity.startService(mIntentTask)
|
||||||
activity.startService(mIntentTask)
|
}
|
||||||
}
|
|
||||||
|
fun startUploadAttachment(uploadFileUri: Uri,
|
||||||
|
attachment: Attachment) {
|
||||||
|
start(Bundle().apply {
|
||||||
|
putParcelable(AttachmentFileNotificationService.FILE_URI_KEY, uploadFileUri)
|
||||||
|
putParcelable(AttachmentFileNotificationService.ATTACHMENT_KEY, attachment)
|
||||||
|
}, ACTION_ATTACHMENT_FILE_START_UPLOAD)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun startDownloadAttachment(downloadFileUri: Uri,
|
fun startDownloadAttachment(downloadFileUri: Uri,
|
||||||
entryAttachment: EntryAttachment) {
|
attachment: Attachment) {
|
||||||
start(Bundle().apply {
|
start(Bundle().apply {
|
||||||
putParcelable(AttachmentFileNotificationService.DOWNLOAD_FILE_URI_KEY, downloadFileUri)
|
putParcelable(AttachmentFileNotificationService.FILE_URI_KEY, downloadFileUri)
|
||||||
putParcelable(AttachmentFileNotificationService.ATTACHMENT_KEY, entryAttachment)
|
putParcelable(AttachmentFileNotificationService.ATTACHMENT_KEY, attachment)
|
||||||
}, ACTION_ATTACHMENT_FILE_START_DOWNLOAD)
|
}, ACTION_ATTACHMENT_FILE_START_DOWNLOAD)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -60,6 +60,15 @@ object ParcelableUtil {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// For writing map with string key and Int value to a Parcel
|
||||||
|
fun writeStringIntMap(parcel: Parcel, map: LinkedHashMap<String, Int>) {
|
||||||
|
parcel.writeInt(map.size)
|
||||||
|
for ((key, value) in map) {
|
||||||
|
parcel.writeString(key)
|
||||||
|
parcel.writeInt(value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// For reading map with string key from a Parcel
|
// For reading map with string key from a Parcel
|
||||||
fun <V : Parcelable> readStringParcelableMap(
|
fun <V : Parcelable> readStringParcelableMap(
|
||||||
parcel: Parcel, vClass: Class<V>): LinkedHashMap<String, V> {
|
parcel: Parcel, vClass: Class<V>): LinkedHashMap<String, V> {
|
||||||
@@ -74,6 +83,19 @@ object ParcelableUtil {
|
|||||||
return map
|
return map
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// For reading map with string key and Int value from a Parcel
|
||||||
|
fun readStringIntMap(parcel: Parcel): LinkedHashMap<String, Int> {
|
||||||
|
val size = parcel.readInt()
|
||||||
|
val map = LinkedHashMap<String, Int>(size)
|
||||||
|
for (i in 0 until size) {
|
||||||
|
val key: String? = parcel.readString()
|
||||||
|
val value: Int? = parcel.readInt()
|
||||||
|
if (key != null && value != null)
|
||||||
|
map[key] = value
|
||||||
|
}
|
||||||
|
return map
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
// For writing map with string key and string value to a Parcel
|
// For writing map with string key and string value to a Parcel
|
||||||
fun writeStringParcelableMap(dest: Parcel, map: LinkedHashMap<String, String>) {
|
fun writeStringParcelableMap(dest: Parcel, map: LinkedHashMap<String, String>) {
|
||||||
|
|||||||
@@ -27,10 +27,7 @@ import android.os.Build
|
|||||||
import android.widget.Toast
|
import android.widget.Toast
|
||||||
import androidx.documentfile.provider.DocumentFile
|
import androidx.documentfile.provider.DocumentFile
|
||||||
import com.kunzisoft.keepass.R
|
import com.kunzisoft.keepass.R
|
||||||
import java.io.File
|
import java.io.*
|
||||||
import java.io.FileInputStream
|
|
||||||
import java.io.FileNotFoundException
|
|
||||||
import java.io.InputStream
|
|
||||||
import java.util.*
|
import java.util.*
|
||||||
|
|
||||||
|
|
||||||
@@ -52,6 +49,17 @@ object UriUtil {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Throws(FileNotFoundException::class)
|
||||||
|
fun getUriOutputStream(contentResolver: ContentResolver, fileUri: Uri?): OutputStream? {
|
||||||
|
if (fileUri == null)
|
||||||
|
return null
|
||||||
|
return when {
|
||||||
|
isFileScheme(fileUri) -> fileUri.path?.let { FileOutputStream(it) }
|
||||||
|
isContentScheme(fileUri) -> contentResolver.openOutputStream(fileUri)
|
||||||
|
else -> null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Throws(FileNotFoundException::class)
|
@Throws(FileNotFoundException::class)
|
||||||
fun getUriInputStream(contentResolver: ContentResolver, fileUri: Uri?): InputStream? {
|
fun getUriInputStream(contentResolver: ContentResolver, fileUri: Uri?): InputStream? {
|
||||||
if (fileUri == null)
|
if (fileUri == null)
|
||||||
|
|||||||
@@ -35,9 +35,11 @@ import com.kunzisoft.keepass.adapters.EntryAttachmentsItemsAdapter
|
|||||||
import com.kunzisoft.keepass.adapters.EntryHistoryAdapter
|
import com.kunzisoft.keepass.adapters.EntryHistoryAdapter
|
||||||
import com.kunzisoft.keepass.database.element.DateInstant
|
import com.kunzisoft.keepass.database.element.DateInstant
|
||||||
import com.kunzisoft.keepass.database.element.Entry
|
import com.kunzisoft.keepass.database.element.Entry
|
||||||
|
import com.kunzisoft.keepass.database.element.Attachment
|
||||||
import com.kunzisoft.keepass.database.element.security.ProtectedString
|
import com.kunzisoft.keepass.database.element.security.ProtectedString
|
||||||
import com.kunzisoft.keepass.database.search.UuidUtil
|
import com.kunzisoft.keepass.database.search.UuidUtil
|
||||||
import com.kunzisoft.keepass.model.EntryAttachment
|
import com.kunzisoft.keepass.model.EntryAttachmentState
|
||||||
|
import com.kunzisoft.keepass.model.StreamDirection
|
||||||
import com.kunzisoft.keepass.otp.OtpElement
|
import com.kunzisoft.keepass.otp.OtpElement
|
||||||
import com.kunzisoft.keepass.otp.OtpType
|
import com.kunzisoft.keepass.otp.OtpType
|
||||||
import java.util.*
|
import java.util.*
|
||||||
@@ -69,7 +71,7 @@ class EntryContentsView @JvmOverloads constructor(context: Context,
|
|||||||
|
|
||||||
private val attachmentsContainerView: View
|
private val attachmentsContainerView: View
|
||||||
private val attachmentsListView: RecyclerView
|
private val attachmentsListView: RecyclerView
|
||||||
private val attachmentsAdapter = EntryAttachmentsItemsAdapter(context, false)
|
private val attachmentsAdapter = EntryAttachmentsItemsAdapter(context)
|
||||||
|
|
||||||
private val historyContainerView: View
|
private val historyContainerView: View
|
||||||
private val historyListView: RecyclerView
|
private val historyListView: RecyclerView
|
||||||
@@ -105,7 +107,7 @@ class EntryContentsView @JvmOverloads constructor(context: Context,
|
|||||||
attachmentsContainerView = findViewById(R.id.entry_attachments_container)
|
attachmentsContainerView = findViewById(R.id.entry_attachments_container)
|
||||||
attachmentsListView = findViewById(R.id.entry_attachments_list)
|
attachmentsListView = findViewById(R.id.entry_attachments_list)
|
||||||
attachmentsListView?.apply {
|
attachmentsListView?.apply {
|
||||||
layoutManager = LinearLayoutManager(context, LinearLayoutManager.VERTICAL, true)
|
layoutManager = LinearLayoutManager(context, LinearLayoutManager.VERTICAL, false)
|
||||||
adapter = attachmentsAdapter
|
adapter = attachmentsAdapter
|
||||||
(itemAnimator as SimpleItemAnimator).supportsChangeAnimations = false
|
(itemAnimator as SimpleItemAnimator).supportsChangeAnimations = false
|
||||||
}
|
}
|
||||||
@@ -315,17 +317,18 @@ class EntryContentsView @JvmOverloads constructor(context: Context,
|
|||||||
attachmentsContainerView.visibility = if (show) View.VISIBLE else View.GONE
|
attachmentsContainerView.visibility = if (show) View.VISIBLE else View.GONE
|
||||||
}
|
}
|
||||||
|
|
||||||
fun assignAttachments(attachments: ArrayList<EntryAttachment>,
|
fun assignAttachments(attachments: ArrayList<Attachment>,
|
||||||
onAttachmentClicked: (attachment: EntryAttachment)->Unit) {
|
streamDirection: StreamDirection,
|
||||||
|
onAttachmentClicked: (attachment: Attachment)->Unit) {
|
||||||
showAttachments(attachments.isNotEmpty())
|
showAttachments(attachments.isNotEmpty())
|
||||||
attachmentsAdapter.assignItems(attachments)
|
attachmentsAdapter.assignItems(attachments.map { EntryAttachmentState(it, streamDirection) })
|
||||||
attachmentsAdapter.onItemClickListener = { item ->
|
attachmentsAdapter.onItemClickListener = { item ->
|
||||||
onAttachmentClicked.invoke(item)
|
onAttachmentClicked.invoke(item.attachment)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun updateAttachmentDownloadProgress(attachmentToDownload: EntryAttachment) {
|
fun putAttachment(attachmentToDownload: EntryAttachmentState) {
|
||||||
attachmentsAdapter.updateProgress(attachmentToDownload)
|
attachmentsAdapter.putItem(attachmentToDownload)
|
||||||
}
|
}
|
||||||
|
|
||||||
/* -------------
|
/* -------------
|
||||||
|
|||||||
@@ -38,9 +38,11 @@ import com.kunzisoft.keepass.database.element.icon.IconImage
|
|||||||
import com.kunzisoft.keepass.icons.IconDrawableFactory
|
import com.kunzisoft.keepass.icons.IconDrawableFactory
|
||||||
import com.kunzisoft.keepass.icons.assignDatabaseIcon
|
import com.kunzisoft.keepass.icons.assignDatabaseIcon
|
||||||
import com.kunzisoft.keepass.icons.assignDefaultDatabaseIcon
|
import com.kunzisoft.keepass.icons.assignDefaultDatabaseIcon
|
||||||
import com.kunzisoft.keepass.model.EntryAttachment
|
import com.kunzisoft.keepass.database.element.Attachment
|
||||||
|
import com.kunzisoft.keepass.model.EntryAttachmentState
|
||||||
import com.kunzisoft.keepass.model.Field
|
import com.kunzisoft.keepass.model.Field
|
||||||
import com.kunzisoft.keepass.model.FocusedEditField
|
import com.kunzisoft.keepass.model.FocusedEditField
|
||||||
|
import com.kunzisoft.keepass.model.StreamDirection
|
||||||
import org.joda.time.Duration
|
import org.joda.time.Duration
|
||||||
import org.joda.time.Instant
|
import org.joda.time.Instant
|
||||||
|
|
||||||
@@ -68,7 +70,7 @@ class EntryEditContentsView @JvmOverloads constructor(context: Context,
|
|||||||
private val attachmentsListView: RecyclerView
|
private val attachmentsListView: RecyclerView
|
||||||
|
|
||||||
private val extraFieldsAdapter = EntryExtraFieldsItemsAdapter(context)
|
private val extraFieldsAdapter = EntryExtraFieldsItemsAdapter(context)
|
||||||
private val attachmentsAdapter = EntryAttachmentsItemsAdapter(context, true)
|
private val attachmentsAdapter = EntryAttachmentsItemsAdapter(context)
|
||||||
|
|
||||||
private var iconColor: Int = 0
|
private var iconColor: Int = 0
|
||||||
private var expiresInstant: DateInstant = DateInstant(Instant.now().plus(Duration.standardDays(30)).toDate())
|
private var expiresInstant: DateInstant = DateInstant(Instant.now().plus(Duration.standardDays(30)).toDate())
|
||||||
@@ -124,7 +126,7 @@ class EntryEditContentsView @JvmOverloads constructor(context: Context,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
attachmentsListView?.apply {
|
attachmentsListView?.apply {
|
||||||
layoutManager = LinearLayoutManager(context, LinearLayoutManager.VERTICAL, true)
|
layoutManager = LinearLayoutManager(context, LinearLayoutManager.VERTICAL, false)
|
||||||
adapter = attachmentsAdapter
|
adapter = attachmentsAdapter
|
||||||
(itemAnimator as SimpleItemAnimator).supportsChangeAnimations = false
|
(itemAnimator as SimpleItemAnimator).supportsChangeAnimations = false
|
||||||
}
|
}
|
||||||
@@ -242,7 +244,7 @@ class EntryEditContentsView @JvmOverloads constructor(context: Context,
|
|||||||
* -------------
|
* -------------
|
||||||
*/
|
*/
|
||||||
|
|
||||||
fun getExtraField(): MutableList<Field> {
|
fun getExtraFields(): List<Field> {
|
||||||
return extraFieldsAdapter.itemsList
|
return extraFieldsAdapter.itemsList
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -278,15 +280,41 @@ class EntryEditContentsView @JvmOverloads constructor(context: Context,
|
|||||||
* -------------
|
* -------------
|
||||||
*/
|
*/
|
||||||
|
|
||||||
fun assignAttachments(attachments: ArrayList<EntryAttachment>,
|
fun getAttachments(): List<Attachment> {
|
||||||
onDeleteItem: (attachment: EntryAttachment)->Unit) {
|
return attachmentsAdapter.itemsList.map { it.attachment }
|
||||||
|
}
|
||||||
|
|
||||||
|
fun assignAttachments(attachments: ArrayList<Attachment>,
|
||||||
|
streamDirection: StreamDirection,
|
||||||
|
onDeleteItem: (attachment: Attachment)->Unit) {
|
||||||
attachmentsContainerView.visibility = if (attachments.isEmpty()) View.GONE else View.VISIBLE
|
attachmentsContainerView.visibility = if (attachments.isEmpty()) View.GONE else View.VISIBLE
|
||||||
attachmentsAdapter.assignItems(attachments)
|
attachmentsAdapter.assignItems(attachments.map { EntryAttachmentState(it, streamDirection) })
|
||||||
attachmentsAdapter.onDeleteButtonClickListener = { item ->
|
attachmentsAdapter.onDeleteButtonClickListener = { item ->
|
||||||
onDeleteItem.invoke(item)
|
onDeleteItem.invoke(item.attachment)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun containsAttachment(): Boolean {
|
||||||
|
return !attachmentsAdapter.isEmpty()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun containsAttachment(attachment: EntryAttachmentState): Boolean {
|
||||||
|
return attachmentsAdapter.contains(attachment)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun putAttachment(attachment: EntryAttachmentState) {
|
||||||
|
attachmentsContainerView.visibility = View.VISIBLE
|
||||||
|
attachmentsAdapter.putItem(attachment)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun removeAttachment(attachment: EntryAttachmentState) {
|
||||||
|
attachmentsAdapter.removeItem(attachment)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun clearAttachments() {
|
||||||
|
attachmentsAdapter.clear()
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Validate or not the entry form
|
* Validate or not the entry form
|
||||||
*
|
*
|
||||||
|
|||||||
7
app/src/main/res/drawable/ic_file_stream_white_24dp.xml
Normal file
7
app/src/main/res/drawable/ic_file_stream_white_24dp.xml
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<selector xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<item android:state_activated="true"
|
||||||
|
android:drawable="@drawable/ic_file_upload_white_24dp" />
|
||||||
|
<item android:state_activated="false"
|
||||||
|
android:drawable="@drawable/ic_file_download_white_24dp" />
|
||||||
|
</selector>
|
||||||
5
app/src/main/res/drawable/ic_file_upload_white_24dp.xml
Normal file
5
app/src/main/res/drawable/ic_file_upload_white_24dp.xml
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
<rotate xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:drawable="@drawable/ic_file_download_white_24dp"
|
||||||
|
android:fromDegrees="180"
|
||||||
|
android:toDegrees="180"
|
||||||
|
android:visible="true" />
|
||||||
@@ -31,7 +31,6 @@
|
|||||||
app:layout_constraintTop_toTopOf="parent"
|
app:layout_constraintTop_toTopOf="parent"
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
app:layout_constraintEnd_toStartOf="@+id/item_attachment_size_container"
|
app:layout_constraintEnd_toStartOf="@+id/item_attachment_size_container"
|
||||||
app:layout_constraintBottom_toBottomOf="parent"
|
|
||||||
android:layout_width="0dp"
|
android:layout_width="0dp"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
style="@style/KeepassDXStyle.TextAppearance.TextEntryItem"
|
style="@style/KeepassDXStyle.TextAppearance.TextEntryItem"
|
||||||
@@ -95,7 +94,7 @@
|
|||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_gravity="center"
|
android:layout_gravity="center"
|
||||||
android:src="@drawable/ic_file_download_white_24dp"
|
android:src="@drawable/ic_file_stream_white_24dp"
|
||||||
android:contentDescription="@string/download"
|
android:contentDescription="@string/download"
|
||||||
style="@style/KeepassDXStyle.ImageButton.Simple" />
|
style="@style/KeepassDXStyle.ImageButton.Simple" />
|
||||||
<ProgressBar
|
<ProgressBar
|
||||||
|
|||||||
@@ -25,7 +25,7 @@
|
|||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
style="@style/KeepassDXStyle.Selectable.Item">
|
style="@style/KeepassDXStyle.Selectable.Item">
|
||||||
<RelativeLayout
|
<androidx.constraintlayout.widget.ConstraintLayout
|
||||||
android:layout_width="0dp"
|
android:layout_width="0dp"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_gravity="center_vertical"
|
android:layout_gravity="center_vertical"
|
||||||
@@ -46,20 +46,26 @@
|
|||||||
android:layout_marginEnd="@dimen/image_list_margin"
|
android:layout_marginEnd="@dimen/image_list_margin"
|
||||||
android:scaleType="fitXY"
|
android:scaleType="fitXY"
|
||||||
android:src="@drawable/ic_blank_32dp"
|
android:src="@drawable/ic_blank_32dp"
|
||||||
android:layout_centerVertical="true"
|
app:layout_constraintTop_toTopOf="parent"
|
||||||
android:layout_alignParentLeft="true"
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
android:layout_alignParentStart="true" />
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintLeft_toLeftOf="parent" />
|
||||||
<LinearLayout
|
<LinearLayout
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="0dp"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:orientation="vertical"
|
android:orientation="vertical"
|
||||||
android:paddingTop="2dp"
|
android:paddingTop="2dp"
|
||||||
android:paddingBottom="4dp"
|
android:paddingBottom="4dp"
|
||||||
android:layout_centerVertical="true"
|
android:layout_marginLeft="@dimen/image_list_margin"
|
||||||
android:layout_alignParentRight="true"
|
android:layout_marginStart="@dimen/image_list_margin"
|
||||||
android:layout_alignParentEnd="true"
|
android:layout_marginRight="@dimen/image_list_margin"
|
||||||
android:layout_toRightOf="@+id/node_icon"
|
android:layout_marginEnd="@dimen/image_list_margin"
|
||||||
android:layout_toEndOf="@+id/node_icon">
|
app:layout_constraintTop_toTopOf="parent"
|
||||||
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
|
app:layout_constraintStart_toEndOf="@+id/node_icon"
|
||||||
|
app:layout_constraintLeft_toRightOf="@+id/node_icon"
|
||||||
|
app:layout_constraintEnd_toStartOf="@+id/node_attachment_icon"
|
||||||
|
app:layout_constraintRight_toLeftOf="@+id/node_attachment_icon">
|
||||||
<TextView
|
<TextView
|
||||||
android:id="@+id/node_text"
|
android:id="@+id/node_text"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
@@ -78,5 +84,18 @@
|
|||||||
android:singleLine="true"
|
android:singleLine="true"
|
||||||
style="@style/KeepassDXStyle.TextAppearance.Entry.SubTitle" />
|
style="@style/KeepassDXStyle.TextAppearance.Entry.SubTitle" />
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
</RelativeLayout>
|
<androidx.appcompat.widget.AppCompatImageView
|
||||||
|
android:id="@+id/node_attachment_icon"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginLeft="@dimen/image_list_margin"
|
||||||
|
android:layout_marginStart="@dimen/image_list_margin"
|
||||||
|
android:layout_marginRight="@dimen/image_list_margin"
|
||||||
|
android:layout_marginEnd="@dimen/image_list_margin"
|
||||||
|
android:src="@drawable/ic_attach_file_white_24dp"
|
||||||
|
app:layout_constraintTop_toTopOf="parent"
|
||||||
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintRight_toRightOf="parent" />
|
||||||
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||||
@@ -26,13 +26,11 @@
|
|||||||
android:title="@string/entry_add_field"
|
android:title="@string/entry_add_field"
|
||||||
android:orderInCategory="92"
|
android:orderInCategory="92"
|
||||||
app:showAsAction="always" />
|
app:showAsAction="always" />
|
||||||
<!--
|
|
||||||
<item android:id="@+id/menu_add_attachment"
|
<item android:id="@+id/menu_add_attachment"
|
||||||
android:icon="@drawable/ic_attach_file_white_24dp"
|
android:icon="@drawable/ic_attach_file_white_24dp"
|
||||||
android:title="@string/entry_add_attachment"
|
android:title="@string/entry_add_attachment"
|
||||||
android:orderInCategory="93"
|
android:orderInCategory="93"
|
||||||
app:showAsAction="always" />
|
app:showAsAction="always" />
|
||||||
-->
|
|
||||||
<item android:id="@+id/menu_add_otp"
|
<item android:id="@+id/menu_add_otp"
|
||||||
android:icon="@drawable/ic_otp_white_24dp"
|
android:icon="@drawable/ic_otp_white_24dp"
|
||||||
android:title="@string/entry_setup_otp"
|
android:title="@string/entry_setup_otp"
|
||||||
|
|||||||
@@ -43,7 +43,7 @@
|
|||||||
<string name="entry_title">العنوان</string>
|
<string name="entry_title">العنوان</string>
|
||||||
<string name="entry_url">رابط</string>
|
<string name="entry_url">رابط</string>
|
||||||
<string name="entry_user_name">اسم المستخدم</string>
|
<string name="entry_user_name">اسم المستخدم</string>
|
||||||
<string name="error_file_not_create">تعذر إنشاء الملف :</string>
|
<string name="error_file_not_create">تعذر إنشاء الملف</string>
|
||||||
<string name="error_invalid_path">تأكد أن المسار صحيح.</string>
|
<string name="error_invalid_path">تأكد أن المسار صحيح.</string>
|
||||||
<string name="error_no_name">ادخل اسمًا.</string>
|
<string name="error_no_name">ادخل اسمًا.</string>
|
||||||
<string name="error_pass_match">كلمتا السر غير متطابقتين.</string>
|
<string name="error_pass_match">كلمتا السر غير متطابقتين.</string>
|
||||||
|
|||||||
@@ -57,7 +57,7 @@
|
|||||||
<string name="entry_user_name">Usuari</string>
|
<string name="entry_user_name">Usuari</string>
|
||||||
<string name="error_arc4">L\'encriptació Arcfour no està suportada.</string>
|
<string name="error_arc4">L\'encriptació Arcfour no està suportada.</string>
|
||||||
<string name="error_can_not_handle_uri">KeePassDX no pot manejar aquesta URI.</string>
|
<string name="error_can_not_handle_uri">KeePassDX no pot manejar aquesta URI.</string>
|
||||||
<string name="error_file_not_create">No s\'ha pogut crear l\'arxiu:</string>
|
<string name="error_file_not_create">No s\'ha pogut crear l\'arxiu</string>
|
||||||
<string name="error_invalid_db">No s\'ha pogut llegir la base de dades.</string>
|
<string name="error_invalid_db">No s\'ha pogut llegir la base de dades.</string>
|
||||||
<string name="error_invalid_path">Assegureu-vos que el camí eś correcte.</string>
|
<string name="error_invalid_path">Assegureu-vos que el camí eś correcte.</string>
|
||||||
<string name="error_no_name">Introduïu-hi un nom.</string>
|
<string name="error_no_name">Introduïu-hi un nom.</string>
|
||||||
|
|||||||
@@ -59,7 +59,7 @@
|
|||||||
<string name="entry_user_name">Uživatelské jméno</string>
|
<string name="entry_user_name">Uživatelské jméno</string>
|
||||||
<string name="error_arc4">Arcfour proudová šifra není podporována.</string>
|
<string name="error_arc4">Arcfour proudová šifra není podporována.</string>
|
||||||
<string name="error_can_not_handle_uri">KeePassDX nemůže zpracovat toto URI.</string>
|
<string name="error_can_not_handle_uri">KeePassDX nemůže zpracovat toto URI.</string>
|
||||||
<string name="error_file_not_create">Soubor se nedaří vytvořit:</string>
|
<string name="error_file_not_create">Soubor se nedaří vytvořit</string>
|
||||||
<string name="error_invalid_db">Nelze přečíst databázi.</string>
|
<string name="error_invalid_db">Nelze přečíst databázi.</string>
|
||||||
<string name="error_invalid_path">Neplatná cesta.</string>
|
<string name="error_invalid_path">Neplatná cesta.</string>
|
||||||
<string name="error_no_name">Vložte jméno.</string>
|
<string name="error_no_name">Vložte jméno.</string>
|
||||||
@@ -419,7 +419,7 @@
|
|||||||
<string name="database_custom_color_title">Vlastní barva databáze</string>
|
<string name="database_custom_color_title">Vlastní barva databáze</string>
|
||||||
<string name="compression">Komprese</string>
|
<string name="compression">Komprese</string>
|
||||||
<string name="compression_none">Žádná</string>
|
<string name="compression_none">Žádná</string>
|
||||||
<string name="compression_gzip">gzip</string>
|
<string name="compression_gzip">Gzip</string>
|
||||||
<string name="device_keyboard_setting_title">Nastavení klávesnice zařízení</string>
|
<string name="device_keyboard_setting_title">Nastavení klávesnice zařízení</string>
|
||||||
<string name="error_save_database">Nebylo možno uložit databázi.</string>
|
<string name="error_save_database">Nebylo možno uložit databázi.</string>
|
||||||
<string name="menu_save_database">Uložit databázi</string>
|
<string name="menu_save_database">Uložit databázi</string>
|
||||||
@@ -440,7 +440,7 @@
|
|||||||
<string name="download_initialization">Zahajuji…</string>
|
<string name="download_initialization">Zahajuji…</string>
|
||||||
<string name="download_progression">Probíhá: %1$d%%</string>
|
<string name="download_progression">Probíhá: %1$d%%</string>
|
||||||
<string name="download_finalization">Dokončuji…</string>
|
<string name="download_finalization">Dokončuji…</string>
|
||||||
<string name="download_complete">Ukončeno! Klepnout pro otevření souboru.</string>
|
<string name="download_complete">Klepnout pro otevření souboru</string>
|
||||||
<string name="hide_expired_entries_title">Skrýt propadlé záznamy</string>
|
<string name="hide_expired_entries_title">Skrýt propadlé záznamy</string>
|
||||||
<string name="hide_expired_entries_summary">Propadlé záznamy jsou skryty</string>
|
<string name="hide_expired_entries_summary">Propadlé záznamy jsou skryty</string>
|
||||||
<string name="contact">Kontakt</string>
|
<string name="contact">Kontakt</string>
|
||||||
|
|||||||
@@ -58,7 +58,7 @@
|
|||||||
<string name="entry_user_name">Brugernavn</string>
|
<string name="entry_user_name">Brugernavn</string>
|
||||||
<string name="error_arc4">Arcfour stream cipher er ikke understøttet.</string>
|
<string name="error_arc4">Arcfour stream cipher er ikke understøttet.</string>
|
||||||
<string name="error_can_not_handle_uri">Kunne ikke håndtere URI i KeePassDX.</string>
|
<string name="error_can_not_handle_uri">Kunne ikke håndtere URI i KeePassDX.</string>
|
||||||
<string name="error_file_not_create">Kunne ikke oprette fil:</string>
|
<string name="error_file_not_create">Kunne ikke oprette fil</string>
|
||||||
<string name="error_invalid_db">Kunne ikke læse databasen.</string>
|
<string name="error_invalid_db">Kunne ikke læse databasen.</string>
|
||||||
<string name="error_invalid_path">Sørg for, at stien er korrekt.</string>
|
<string name="error_invalid_path">Sørg for, at stien er korrekt.</string>
|
||||||
<string name="error_no_name">Indtast et navn.</string>
|
<string name="error_no_name">Indtast et navn.</string>
|
||||||
@@ -419,7 +419,7 @@
|
|||||||
<string name="database_custom_color_title">Brugerdefineret databasefarve</string>
|
<string name="database_custom_color_title">Brugerdefineret databasefarve</string>
|
||||||
<string name="compression">Komprimering</string>
|
<string name="compression">Komprimering</string>
|
||||||
<string name="compression_none">Ingen</string>
|
<string name="compression_none">Ingen</string>
|
||||||
<string name="compression_gzip">gzip</string>
|
<string name="compression_gzip">Gzip</string>
|
||||||
<string name="device_keyboard_setting_title">Indstillinger for enhedens tastatur</string>
|
<string name="device_keyboard_setting_title">Indstillinger for enhedens tastatur</string>
|
||||||
<string name="error_save_database">Databasen kunne ikke gemmes.</string>
|
<string name="error_save_database">Databasen kunne ikke gemmes.</string>
|
||||||
<string name="menu_save_database">Gem database</string>
|
<string name="menu_save_database">Gem database</string>
|
||||||
@@ -440,7 +440,7 @@
|
|||||||
<string name="download_initialization">Initialiserer…</string>
|
<string name="download_initialization">Initialiserer…</string>
|
||||||
<string name="download_progression">I gang: %1$d%%</string>
|
<string name="download_progression">I gang: %1$d%%</string>
|
||||||
<string name="download_finalization">Færdiggørelse…</string>
|
<string name="download_finalization">Færdiggørelse…</string>
|
||||||
<string name="download_complete">Komplet! Tryk for at åbne filen.</string>
|
<string name="download_complete">Komplet!</string>
|
||||||
<string name="hide_expired_entries_title">Skjul udløbne poster</string>
|
<string name="hide_expired_entries_title">Skjul udløbne poster</string>
|
||||||
<string name="hide_expired_entries_summary">Udløbne poster er skjult</string>
|
<string name="hide_expired_entries_summary">Udløbne poster er skjult</string>
|
||||||
<string name="contact">Kontakt</string>
|
<string name="contact">Kontakt</string>
|
||||||
|
|||||||
@@ -67,7 +67,7 @@
|
|||||||
<string name="entry_user_name">Benutzername</string>
|
<string name="entry_user_name">Benutzername</string>
|
||||||
<string name="error_arc4">Die RC4/Arcfour-Stromverschlüsselung wird nicht unterstützt.</string>
|
<string name="error_arc4">Die RC4/Arcfour-Stromverschlüsselung wird nicht unterstützt.</string>
|
||||||
<string name="error_can_not_handle_uri">KeePassDX kann diese URI-Adresse nicht verarbeiten.</string>
|
<string name="error_can_not_handle_uri">KeePassDX kann diese URI-Adresse nicht verarbeiten.</string>
|
||||||
<string name="error_file_not_create">Konnte Datei nicht erstellen:</string>
|
<string name="error_file_not_create">Konnte Datei nicht erstellen</string>
|
||||||
<string name="error_invalid_db">Datenbank nicht lesbar.</string>
|
<string name="error_invalid_db">Datenbank nicht lesbar.</string>
|
||||||
<string name="error_invalid_path">Sicherstellen, dass der Pfad korrekt ist.</string>
|
<string name="error_invalid_path">Sicherstellen, dass der Pfad korrekt ist.</string>
|
||||||
<string name="error_no_name">Namen eingeben.</string>
|
<string name="error_no_name">Namen eingeben.</string>
|
||||||
@@ -435,7 +435,7 @@
|
|||||||
<string name="database_custom_color_title">Benutzerdefinierte Datenbankfarbe</string>
|
<string name="database_custom_color_title">Benutzerdefinierte Datenbankfarbe</string>
|
||||||
<string name="compression">Kompression</string>
|
<string name="compression">Kompression</string>
|
||||||
<string name="compression_none">Keine</string>
|
<string name="compression_none">Keine</string>
|
||||||
<string name="compression_gzip">gzip</string>
|
<string name="compression_gzip">Gzip</string>
|
||||||
<string name="device_keyboard_setting_title">Gerätetastatur-Einstellungen</string>
|
<string name="device_keyboard_setting_title">Gerätetastatur-Einstellungen</string>
|
||||||
<string name="error_save_database">Die Datenbank konnte nicht gespeichert werden.</string>
|
<string name="error_save_database">Die Datenbank konnte nicht gespeichert werden.</string>
|
||||||
<string name="menu_save_database">Datenbank speichern</string>
|
<string name="menu_save_database">Datenbank speichern</string>
|
||||||
@@ -456,7 +456,7 @@
|
|||||||
<string name="download_initialization">Initialisieren…</string>
|
<string name="download_initialization">Initialisieren…</string>
|
||||||
<string name="download_progression">Fortschritt: %1$d%%</string>
|
<string name="download_progression">Fortschritt: %1$d%%</string>
|
||||||
<string name="download_finalization">Fertigstellen…</string>
|
<string name="download_finalization">Fertigstellen…</string>
|
||||||
<string name="download_complete">Vollständig! Tippen Sie, um die Datei zu öffnen.</string>
|
<string name="download_complete">Vollständig!</string>
|
||||||
<string name="hide_expired_entries_title">Abgelaufene Einträge ausblenden</string>
|
<string name="hide_expired_entries_title">Abgelaufene Einträge ausblenden</string>
|
||||||
<string name="hide_expired_entries_summary">Abgelaufene Einträge werden ausgeblendet</string>
|
<string name="hide_expired_entries_summary">Abgelaufene Einträge werden ausgeblendet</string>
|
||||||
<string name="style_choose_title">App-Design</string>
|
<string name="style_choose_title">App-Design</string>
|
||||||
|
|||||||
@@ -61,7 +61,7 @@
|
|||||||
<string name="entry_user_name">Όνομα Χρήστη</string>
|
<string name="entry_user_name">Όνομα Χρήστη</string>
|
||||||
<string name="error_arc4">Η ροή κρυπτογράφησης Arcfour δεν υποστηρίζεται.</string>
|
<string name="error_arc4">Η ροή κρυπτογράφησης Arcfour δεν υποστηρίζεται.</string>
|
||||||
<string name="error_can_not_handle_uri">Το KeePassDX δε μπορεί να χειριστεί αυτή τη διεύθυνση URI.</string>
|
<string name="error_can_not_handle_uri">Το KeePassDX δε μπορεί να χειριστεί αυτή τη διεύθυνση URI.</string>
|
||||||
<string name="error_file_not_create">Δεν ήταν δυνατή η δημιουργία αρχείου:</string>
|
<string name="error_file_not_create">Δεν ήταν δυνατή η δημιουργία αρχείου</string>
|
||||||
<string name="error_invalid_db">Δεν ήταν δυνατή η ανάγνωση της βάσης δεδομένων.</string>
|
<string name="error_invalid_db">Δεν ήταν δυνατή η ανάγνωση της βάσης δεδομένων.</string>
|
||||||
<string name="error_invalid_path">Βεβαιωθείτε ότι η διαδρομή είναι σωστή.</string>
|
<string name="error_invalid_path">Βεβαιωθείτε ότι η διαδρομή είναι σωστή.</string>
|
||||||
<string name="error_no_name">Εισαγάγετε ένα όνομα.</string>
|
<string name="error_no_name">Εισαγάγετε ένα όνομα.</string>
|
||||||
@@ -418,7 +418,7 @@
|
|||||||
<string name="database_custom_color_title">Προσαρμοσμένο χρώμα βάσης δεδομένων</string>
|
<string name="database_custom_color_title">Προσαρμοσμένο χρώμα βάσης δεδομένων</string>
|
||||||
<string name="compression">Συμπίεση</string>
|
<string name="compression">Συμπίεση</string>
|
||||||
<string name="compression_none">Καμιά</string>
|
<string name="compression_none">Καμιά</string>
|
||||||
<string name="compression_gzip">gzip</string>
|
<string name="compression_gzip">Gzip</string>
|
||||||
<string name="magic_keyboard_explanation_summary">Ενεργοποιώντας ένα προσαρμοσμένο πληκτρολόγιο συγκεντρώνει τους κωδικούς πρόσβασής σας και όλα τα πεδία ταυτότητας</string>
|
<string name="magic_keyboard_explanation_summary">Ενεργοποιώντας ένα προσαρμοσμένο πληκτρολόγιο συγκεντρώνει τους κωδικούς πρόσβασής σας και όλα τα πεδία ταυτότητας</string>
|
||||||
<string name="device_keyboard_setting_title">Ρυθμίσεις πληκτρολογίου συσκευής</string>
|
<string name="device_keyboard_setting_title">Ρυθμίσεις πληκτρολογίου συσκευής</string>
|
||||||
<string name="education_biometric_title">Ξεκλείδωμα Βάσης Δεδομένων με βιομετρικά στοιχεία</string>
|
<string name="education_biometric_title">Ξεκλείδωμα Βάσης Δεδομένων με βιομετρικά στοιχεία</string>
|
||||||
@@ -442,7 +442,7 @@
|
|||||||
<string name="download_initialization">Αρχικοποίηση…</string>
|
<string name="download_initialization">Αρχικοποίηση…</string>
|
||||||
<string name="download_progression">Σε εξέλιξη: %1$d%%</string>
|
<string name="download_progression">Σε εξέλιξη: %1$d%%</string>
|
||||||
<string name="download_finalization">Ολοκλήρωση…</string>
|
<string name="download_finalization">Ολοκλήρωση…</string>
|
||||||
<string name="download_complete">Ολοκληρώθηκε! Πατήστε για να ανοίξετε το αρχείο.</string>
|
<string name="download_complete">Ολοκληρώθηκε!</string>
|
||||||
<string name="hide_expired_entries_title">Απόκρυψη καταχωρίσεων που έχουν λήξει</string>
|
<string name="hide_expired_entries_title">Απόκρυψη καταχωρίσεων που έχουν λήξει</string>
|
||||||
<string name="hide_expired_entries_summary">Οι καταχωρίσεις που έχουν λήξει είναι κρυμμένες</string>
|
<string name="hide_expired_entries_summary">Οι καταχωρίσεις που έχουν λήξει είναι κρυμμένες</string>
|
||||||
<string name="show_recent_files_title">Εμφάνιση πρόσφατων αρχείων</string>
|
<string name="show_recent_files_title">Εμφάνιση πρόσφατων αρχείων</string>
|
||||||
|
|||||||
@@ -58,7 +58,7 @@
|
|||||||
<string name="entry_user_name">Nombre de usuario</string>
|
<string name="entry_user_name">Nombre de usuario</string>
|
||||||
<string name="error_arc4">No se admite el cifrador de flujo Arcfour.</string>
|
<string name="error_arc4">No se admite el cifrador de flujo Arcfour.</string>
|
||||||
<string name="error_can_not_handle_uri">KeePassDX no puede manejar este URI.</string>
|
<string name="error_can_not_handle_uri">KeePassDX no puede manejar este URI.</string>
|
||||||
<string name="error_file_not_create">No se pudo crear el archivo:</string>
|
<string name="error_file_not_create">No se pudo crear el archivo</string>
|
||||||
<string name="error_invalid_db">No se pudo leer la base de datos.</string>
|
<string name="error_invalid_db">No se pudo leer la base de datos.</string>
|
||||||
<string name="error_invalid_path">Asegúrese de que la ruta sea correcta.</string>
|
<string name="error_invalid_path">Asegúrese de que la ruta sea correcta.</string>
|
||||||
<string name="error_no_name">Proporcione un nombre.</string>
|
<string name="error_no_name">Proporcione un nombre.</string>
|
||||||
@@ -397,7 +397,7 @@
|
|||||||
<string name="error_create_database">No fue posible crear el archivo de base de datos.</string>
|
<string name="error_create_database">No fue posible crear el archivo de base de datos.</string>
|
||||||
<string name="html_about_contribution">Parar lograr <strong>mantener nuestra libertad</strong>, <strong>corregir errores</strong>, <strong>agregar características</strong> y <strong>siempre estar activos</strong>, contamos con tu <strong>contribución</strong>.</string>
|
<string name="html_about_contribution">Parar lograr <strong>mantener nuestra libertad</strong>, <strong>corregir errores</strong>, <strong>agregar características</strong> y <strong>siempre estar activos</strong>, contamos con tu <strong>contribución</strong>.</string>
|
||||||
<string name="content_description_add_item">Añadir elemento</string>
|
<string name="content_description_add_item">Añadir elemento</string>
|
||||||
<string name="download_complete">Descarga completa! Toca para abrir el archivo.</string>
|
<string name="download_complete">Descarga completa!</string>
|
||||||
<string name="download_finalization">Finalizando…</string>
|
<string name="download_finalization">Finalizando…</string>
|
||||||
<string name="download_progression">En progreso: %1$d%%</string>
|
<string name="download_progression">En progreso: %1$d%%</string>
|
||||||
<string name="download_initialization">Inicializando…</string>
|
<string name="download_initialization">Inicializando…</string>
|
||||||
@@ -407,7 +407,7 @@
|
|||||||
<string name="autofill_block">Bloquear autocompletado</string>
|
<string name="autofill_block">Bloquear autocompletado</string>
|
||||||
<string name="keyboard_change">Cambiar teclado</string>
|
<string name="keyboard_change">Cambiar teclado</string>
|
||||||
<string name="keyboard_auto_go_action_summary">Acción de la tecla \"Ir\" al presionar una tecla \"Campo\"</string>
|
<string name="keyboard_auto_go_action_summary">Acción de la tecla \"Ir\" al presionar una tecla \"Campo\"</string>
|
||||||
<string name="compression_gzip">gzip</string>
|
<string name="compression_gzip">Gzip</string>
|
||||||
<string name="compression_none">Ninguna</string>
|
<string name="compression_none">Ninguna</string>
|
||||||
<string name="compression">Compresión</string>
|
<string name="compression">Compresión</string>
|
||||||
<string name="database_default_username_title">Nombre de usuario predeterminado</string>
|
<string name="database_default_username_title">Nombre de usuario predeterminado</string>
|
||||||
|
|||||||
@@ -61,7 +61,7 @@
|
|||||||
<string name="entry_user_name">Erabiltzaile izena</string>
|
<string name="entry_user_name">Erabiltzaile izena</string>
|
||||||
<string name="error_arc4">Arcfour stream zifratze sisterako ez dago euskarririk..</string>
|
<string name="error_arc4">Arcfour stream zifratze sisterako ez dago euskarririk..</string>
|
||||||
<string name="error_can_not_handle_uri">KeePassDX-ek ezin dut uri hau kudeatu.</string>
|
<string name="error_can_not_handle_uri">KeePassDX-ek ezin dut uri hau kudeatu.</string>
|
||||||
<string name="error_file_not_create">Ezin izan da fitxategia sortu:</string>
|
<string name="error_file_not_create">Ezin izan da fitxategia sortu</string>
|
||||||
<string name="error_invalid_db">Datubase baliogabea.</string>
|
<string name="error_invalid_db">Datubase baliogabea.</string>
|
||||||
<string name="error_invalid_path">Fitxategirako bide baliogabea.</string>
|
<string name="error_invalid_path">Fitxategirako bide baliogabea.</string>
|
||||||
<string name="error_no_name">Izen bat behar da.</string>
|
<string name="error_no_name">Izen bat behar da.</string>
|
||||||
|
|||||||
@@ -61,7 +61,7 @@
|
|||||||
<string name="entry_user_name">Käyttäjänimi</string>
|
<string name="entry_user_name">Käyttäjänimi</string>
|
||||||
<string name="error_arc4">Arcfour stream cipher ei ole tuettu.</string>
|
<string name="error_arc4">Arcfour stream cipher ei ole tuettu.</string>
|
||||||
<string name="error_can_not_handle_uri">KeePassDX ei osaa käsitellä tätä osoitetta.</string>
|
<string name="error_can_not_handle_uri">KeePassDX ei osaa käsitellä tätä osoitetta.</string>
|
||||||
<string name="error_file_not_create">Tiedoston luonti epäonnistui:</string>
|
<string name="error_file_not_create">Tiedoston luonti epäonnistui</string>
|
||||||
<string name="error_invalid_db">Tietokantaa ei pystytty lukemaan.</string>
|
<string name="error_invalid_db">Tietokantaa ei pystytty lukemaan.</string>
|
||||||
<string name="error_invalid_path">Varmista että polku on oikein.</string>
|
<string name="error_invalid_path">Varmista että polku on oikein.</string>
|
||||||
<string name="error_no_name">Anna nimi.</string>
|
<string name="error_no_name">Anna nimi.</string>
|
||||||
|
|||||||
@@ -65,7 +65,7 @@
|
|||||||
<string name="entry_user_name">Nom d’utilisateur</string>
|
<string name="entry_user_name">Nom d’utilisateur</string>
|
||||||
<string name="error_arc4">Le chiffrement de flux Arcfour n’est pas pris en charge.</string>
|
<string name="error_arc4">Le chiffrement de flux Arcfour n’est pas pris en charge.</string>
|
||||||
<string name="error_can_not_handle_uri">Impossible de gérer cette URI dans KeePassDX.</string>
|
<string name="error_can_not_handle_uri">Impossible de gérer cette URI dans KeePassDX.</string>
|
||||||
<string name="error_file_not_create">Impossible de créer le fichier :</string>
|
<string name="error_file_not_create">Impossible de créer le fichier</string>
|
||||||
<string name="error_invalid_db">Impossible de lire la base de données.</string>
|
<string name="error_invalid_db">Impossible de lire la base de données.</string>
|
||||||
<string name="error_invalid_path">Vérifier la validité du chemin d’accès.</string>
|
<string name="error_invalid_path">Vérifier la validité du chemin d’accès.</string>
|
||||||
<string name="error_no_name">Saisir un nom.</string>
|
<string name="error_no_name">Saisir un nom.</string>
|
||||||
@@ -435,7 +435,7 @@
|
|||||||
<string name="database_custom_color_title">Couleur de la base de données</string>
|
<string name="database_custom_color_title">Couleur de la base de données</string>
|
||||||
<string name="compression">Compression</string>
|
<string name="compression">Compression</string>
|
||||||
<string name="compression_none">Aucune</string>
|
<string name="compression_none">Aucune</string>
|
||||||
<string name="compression_gzip">gzip</string>
|
<string name="compression_gzip">Gzip</string>
|
||||||
<string name="device_keyboard_setting_title">Paramètres du clavier de l’appareil</string>
|
<string name="device_keyboard_setting_title">Paramètres du clavier de l’appareil</string>
|
||||||
<string name="error_save_database">Impossible d’enregistrer la base de données.</string>
|
<string name="error_save_database">Impossible d’enregistrer la base de données.</string>
|
||||||
<string name="menu_save_database">Enregistrer la base de données</string>
|
<string name="menu_save_database">Enregistrer la base de données</string>
|
||||||
@@ -456,7 +456,7 @@
|
|||||||
<string name="download_initialization">Initialisation…</string>
|
<string name="download_initialization">Initialisation…</string>
|
||||||
<string name="download_progression">En cours : %1$d%%</string>
|
<string name="download_progression">En cours : %1$d%%</string>
|
||||||
<string name="download_finalization">Finalisation…</string>
|
<string name="download_finalization">Finalisation…</string>
|
||||||
<string name="download_complete">Terminé ! Appuyer pour ouvrir le fichier.</string>
|
<string name="download_complete">Terminé !</string>
|
||||||
<string name="hide_expired_entries_title">Masquer les entrées expirées</string>
|
<string name="hide_expired_entries_title">Masquer les entrées expirées</string>
|
||||||
<string name="hide_expired_entries_summary">Les entrées expirées sont cachées</string>
|
<string name="hide_expired_entries_summary">Les entrées expirées sont cachées</string>
|
||||||
<string name="contact">Contact</string>
|
<string name="contact">Contact</string>
|
||||||
|
|||||||
@@ -62,7 +62,7 @@
|
|||||||
<string name="entry_url">यू.आर.एल</string>
|
<string name="entry_url">यू.आर.एल</string>
|
||||||
<string name="entry_user_name">उपयोगकर्ता का नाम</string>
|
<string name="entry_user_name">उपयोगकर्ता का नाम</string>
|
||||||
<string name="error_can_not_handle_uri">KeePassDX में इस URI को संभाल नहीं सका।</string>
|
<string name="error_can_not_handle_uri">KeePassDX में इस URI को संभाल नहीं सका।</string>
|
||||||
<string name="error_file_not_create">फाइल नहीं बना सका:</string>
|
<string name="error_file_not_create">फाइल नहीं बना सका</string>
|
||||||
<string name="error_invalid_db">डाटाबेस नहीं पढ़ सका।</string>
|
<string name="error_invalid_db">डाटाबेस नहीं पढ़ सका।</string>
|
||||||
<string name="error_invalid_path">सुनिश्चित करें कि रास्ता सही है।</string>
|
<string name="error_invalid_path">सुनिश्चित करें कि रास्ता सही है।</string>
|
||||||
<string name="error_no_name">एक नाम दर्ज करें।</string>
|
<string name="error_no_name">एक नाम दर्ज करें।</string>
|
||||||
|
|||||||
@@ -233,12 +233,12 @@
|
|||||||
<string name="other">Ostalo</string>
|
<string name="other">Ostalo</string>
|
||||||
<string name="compression">Komprimiranje</string>
|
<string name="compression">Komprimiranje</string>
|
||||||
<string name="compression_none">Bez</string>
|
<string name="compression_none">Bez</string>
|
||||||
<string name="compression_gzip">gzip</string>
|
<string name="compression_gzip">Gzip</string>
|
||||||
<string name="recycle_bin">Koš za smeće</string>
|
<string name="recycle_bin">Koš za smeće</string>
|
||||||
<string name="content_description_node_children">Pod-čvor</string>
|
<string name="content_description_node_children">Pod-čvor</string>
|
||||||
<string name="entry_accessed">Pristupljeno</string>
|
<string name="entry_accessed">Pristupljeno</string>
|
||||||
<string name="error_arc4">Arcfour šifriranje nije podržano.</string>
|
<string name="error_arc4">Arcfour šifriranje nije podržano.</string>
|
||||||
<string name="error_file_not_create">Nije moguće stvoriti datoteku:</string>
|
<string name="error_file_not_create">Nije moguće stvoriti datoteku</string>
|
||||||
<string name="error_invalid_db">Nije moguće čitati bazu podataka.</string>
|
<string name="error_invalid_db">Nije moguće čitati bazu podataka.</string>
|
||||||
<string name="error_invalid_path">Provjeri putanju do datoteke.</string>
|
<string name="error_invalid_path">Provjeri putanju do datoteke.</string>
|
||||||
<string name="error_invalid_OTP">Neispravan OTP tajni ključ.</string>
|
<string name="error_invalid_OTP">Neispravan OTP tajni ključ.</string>
|
||||||
@@ -469,7 +469,7 @@
|
|||||||
\n
|
\n
|
||||||
\nGrupe (~mape) organiziraju unose u bazi podataka.</string>
|
\nGrupe (~mape) organiziraju unose u bazi podataka.</string>
|
||||||
<string name="download_progression">U tijeku: %1$d%%</string>
|
<string name="download_progression">U tijeku: %1$d%%</string>
|
||||||
<string name="download_complete">Gotovo! Dodirni, za otvaranje datoteke.</string>
|
<string name="download_complete">Gotovo!</string>
|
||||||
<string name="keyboard_previous_fill_in_summary">Automatski se vrati na prethodnu tipkovnicu nakon izvršavanja automatske radnje tipke</string>
|
<string name="keyboard_previous_fill_in_summary">Automatski se vrati na prethodnu tipkovnicu nakon izvršavanja automatske radnje tipke</string>
|
||||||
<string name="keyboard_previous_fill_in_title">Automatska radnja tipke</string>
|
<string name="keyboard_previous_fill_in_title">Automatska radnja tipke</string>
|
||||||
<string name="keyboard_previous_database_credentials_summary">Automatski se prebaci na prethodnu tipkovnicu pri ekranu za unos podataka za prijavu u bazu podataka</string>
|
<string name="keyboard_previous_database_credentials_summary">Automatski se prebaci na prethodnu tipkovnicu pri ekranu za unos podataka za prijavu u bazu podataka</string>
|
||||||
|
|||||||
@@ -60,7 +60,7 @@
|
|||||||
<string name="entry_user_name">Felhasználónév</string>
|
<string name="entry_user_name">Felhasználónév</string>
|
||||||
<string name="error_arc4">Az Arcfour adatfolyam-titkosítás nem támogatott.</string>
|
<string name="error_arc4">Az Arcfour adatfolyam-titkosítás nem támogatott.</string>
|
||||||
<string name="error_can_not_handle_uri">Ez az URI nem kezelhető a KeePassDX-ben.</string>
|
<string name="error_can_not_handle_uri">Ez az URI nem kezelhető a KeePassDX-ben.</string>
|
||||||
<string name="error_file_not_create">Nem sikerült létrehozni a fájlt:</string>
|
<string name="error_file_not_create">Nem sikerült létrehozni a fájlt</string>
|
||||||
<string name="error_invalid_db">Az adatbázist nem lehet olvasni.</string>
|
<string name="error_invalid_db">Az adatbázist nem lehet olvasni.</string>
|
||||||
<string name="error_invalid_path">Győződjön meg róla, hogy az útvonal helyes.</string>
|
<string name="error_invalid_path">Győződjön meg róla, hogy az útvonal helyes.</string>
|
||||||
<string name="error_no_name">Adjon meg egy nevet.</string>
|
<string name="error_no_name">Adjon meg egy nevet.</string>
|
||||||
@@ -384,7 +384,7 @@
|
|||||||
<string name="contact">Kapcsolat</string>
|
<string name="contact">Kapcsolat</string>
|
||||||
<string name="hide_expired_entries_summary">A lejárt bejegyzések rejtettek</string>
|
<string name="hide_expired_entries_summary">A lejárt bejegyzések rejtettek</string>
|
||||||
<string name="hide_expired_entries_title">Lejárt bejegyzések elrejtése</string>
|
<string name="hide_expired_entries_title">Lejárt bejegyzések elrejtése</string>
|
||||||
<string name="download_complete">Kész! Koppintson a fájl megnyitásához.</string>
|
<string name="download_complete">Kész!</string>
|
||||||
<string name="download_finalization">Befejezés…</string>
|
<string name="download_finalization">Befejezés…</string>
|
||||||
<string name="download_progression">Folyamatban: %1$d%%</string>
|
<string name="download_progression">Folyamatban: %1$d%%</string>
|
||||||
<string name="download_initialization">Előkészítés…</string>
|
<string name="download_initialization">Előkészítés…</string>
|
||||||
@@ -405,7 +405,7 @@
|
|||||||
<string name="menu_save_database">Adatbázis mentése</string>
|
<string name="menu_save_database">Adatbázis mentése</string>
|
||||||
<string name="error_save_database">Az adatbázis mentése sikertelen.</string>
|
<string name="error_save_database">Az adatbázis mentése sikertelen.</string>
|
||||||
<string name="device_keyboard_setting_title">Eszköz billentyűzetének beállításai</string>
|
<string name="device_keyboard_setting_title">Eszköz billentyűzetének beállításai</string>
|
||||||
<string name="compression_gzip">gzip</string>
|
<string name="compression_gzip">Gzip</string>
|
||||||
<string name="compression_none">Nincs</string>
|
<string name="compression_none">Nincs</string>
|
||||||
<string name="compression">Tömörítés</string>
|
<string name="compression">Tömörítés</string>
|
||||||
<string name="database_custom_color_title">Egyéni adatbázisszín</string>
|
<string name="database_custom_color_title">Egyéni adatbázisszín</string>
|
||||||
|
|||||||
@@ -63,7 +63,7 @@
|
|||||||
<string name="entry_user_name">Nome utente</string>
|
<string name="entry_user_name">Nome utente</string>
|
||||||
<string name="error_arc4">La codifica a flusso Arcfour non è supportata.</string>
|
<string name="error_arc4">La codifica a flusso Arcfour non è supportata.</string>
|
||||||
<string name="error_can_not_handle_uri">KeePassDX non può gestire questo URI.</string>
|
<string name="error_can_not_handle_uri">KeePassDX non può gestire questo URI.</string>
|
||||||
<string name="error_file_not_create">Impossibile creare il file:</string>
|
<string name="error_file_not_create">Impossibile creare il file</string>
|
||||||
<string name="error_invalid_db">Lettura del database fallita.</string>
|
<string name="error_invalid_db">Lettura del database fallita.</string>
|
||||||
<string name="error_invalid_path">Assicurati che il percorso sia corretto.</string>
|
<string name="error_invalid_path">Assicurati che il percorso sia corretto.</string>
|
||||||
<string name="error_no_name">Inserisci un nome.</string>
|
<string name="error_no_name">Inserisci un nome.</string>
|
||||||
@@ -448,7 +448,7 @@
|
|||||||
<string name="hide_expired_entries_summary">I record scaduti sono nascosti</string>
|
<string name="hide_expired_entries_summary">I record scaduti sono nascosti</string>
|
||||||
<string name="hide_expired_entries_title">Nascondi i record scaduti</string>
|
<string name="hide_expired_entries_title">Nascondi i record scaduti</string>
|
||||||
<string name="education_setup_OTP_summary">Imposta la gestione delle OTP (HOTP / TOTP) per generare un token richiesto per la 2FA.</string>
|
<string name="education_setup_OTP_summary">Imposta la gestione delle OTP (HOTP / TOTP) per generare un token richiesto per la 2FA.</string>
|
||||||
<string name="download_complete">Completo! Tocca per aprire il file.</string>
|
<string name="download_complete">Completo!</string>
|
||||||
<string name="download_finalization">Finalizzazione…</string>
|
<string name="download_finalization">Finalizzazione…</string>
|
||||||
<string name="download_progression">Avanzamento %1$d%%</string>
|
<string name="download_progression">Avanzamento %1$d%%</string>
|
||||||
<string name="download_initialization">Inizializzazione…</string>
|
<string name="download_initialization">Inizializzazione…</string>
|
||||||
@@ -461,7 +461,7 @@
|
|||||||
<string name="keyboard_auto_go_action_summary">Dopo la pressione del tasto \"Campo\" invia il tasto \"Vai\"</string>
|
<string name="keyboard_auto_go_action_summary">Dopo la pressione del tasto \"Campo\" invia il tasto \"Vai\"</string>
|
||||||
<string name="keyboard_auto_go_action_title">Azione auto key</string>
|
<string name="keyboard_auto_go_action_title">Azione auto key</string>
|
||||||
<string name="device_keyboard_setting_title">Impostazioni tastiera dispositivo</string>
|
<string name="device_keyboard_setting_title">Impostazioni tastiera dispositivo</string>
|
||||||
<string name="compression_gzip">gzip</string>
|
<string name="compression_gzip">Gzip</string>
|
||||||
<string name="compression_none">Nessuna</string>
|
<string name="compression_none">Nessuna</string>
|
||||||
<string name="compression">Compressione</string>
|
<string name="compression">Compressione</string>
|
||||||
<string name="database_custom_color_title">Colore del database customizzato</string>
|
<string name="database_custom_color_title">Colore del database customizzato</string>
|
||||||
|
|||||||
@@ -60,7 +60,7 @@
|
|||||||
<string name="entry_user_name">שם משתמש</string>
|
<string name="entry_user_name">שם משתמש</string>
|
||||||
<string name="error_arc4">צופן זרם Arcfour אינו נתמך.</string>
|
<string name="error_arc4">צופן זרם Arcfour אינו נתמך.</string>
|
||||||
<string name="error_can_not_handle_uri">KeePassDX לא יכול לטפל ב-URI הזה.</string>
|
<string name="error_can_not_handle_uri">KeePassDX לא יכול לטפל ב-URI הזה.</string>
|
||||||
<string name="error_file_not_create">לא הצליח ליצור קובץ:</string>
|
<string name="error_file_not_create">לא הצליח ליצור קובץ</string>
|
||||||
<string name="error_invalid_db">מסד נתונים לא חוקי.</string>
|
<string name="error_invalid_db">מסד נתונים לא חוקי.</string>
|
||||||
<string name="error_invalid_path">נתיב לא חוקי.</string>
|
<string name="error_invalid_path">נתיב לא חוקי.</string>
|
||||||
<string name="error_no_name">שם נדרש.</string>
|
<string name="error_no_name">שם נדרש.</string>
|
||||||
|
|||||||
@@ -57,7 +57,7 @@
|
|||||||
<string name="entry_user_name">ユーザー名</string>
|
<string name="entry_user_name">ユーザー名</string>
|
||||||
<string name="error_arc4">Arcfour ストリーム暗号には対応していません。</string>
|
<string name="error_arc4">Arcfour ストリーム暗号には対応していません。</string>
|
||||||
<string name="error_can_not_handle_uri">KeePassDX ではこの URI を処理できませんでした。</string>
|
<string name="error_can_not_handle_uri">KeePassDX ではこの URI を処理できませんでした。</string>
|
||||||
<string name="error_file_not_create">ファイルを作成できませんでした:</string>
|
<string name="error_file_not_create">ファイルを作成できませんでした</string>
|
||||||
<string name="error_invalid_db">データベースを読み取れませんでした。</string>
|
<string name="error_invalid_db">データベースを読み取れませんでした。</string>
|
||||||
<string name="error_invalid_path">パスが正しいことを確認してください。</string>
|
<string name="error_invalid_path">パスが正しいことを確認してください。</string>
|
||||||
<string name="error_no_name">名前を入力してください。</string>
|
<string name="error_no_name">名前を入力してください。</string>
|
||||||
@@ -407,7 +407,7 @@
|
|||||||
<string name="clipboard_explanation_summary">デバイスのクリップボードを使用して、エントリーのフィールドをコピーします</string>
|
<string name="clipboard_explanation_summary">デバイスのクリップボードを使用して、エントリーのフィールドをコピーします</string>
|
||||||
<string name="html_text_dev_feature_work_hard">この機能をすばやくリリースするために開発に勤しんでいます。</string>
|
<string name="html_text_dev_feature_work_hard">この機能をすばやくリリースするために開発に勤しんでいます。</string>
|
||||||
<string name="magic_keyboard_explanation_summary">パスワードとすべての ID フィールドを格納するカスタム キーボードを有効にします</string>
|
<string name="magic_keyboard_explanation_summary">パスワードとすべての ID フィールドを格納するカスタム キーボードを有効にします</string>
|
||||||
<string name="download_complete">完了しました!タップするとファイルが開きます。</string>
|
<string name="download_complete">完了しました!</string>
|
||||||
<string name="download_progression">進行中:%1$d%%</string>
|
<string name="download_progression">進行中:%1$d%%</string>
|
||||||
<string name="download_attachment">%1$s をダウンロード</string>
|
<string name="download_attachment">%1$s をダウンロード</string>
|
||||||
<string name="contribute">貢献</string>
|
<string name="contribute">貢献</string>
|
||||||
@@ -422,7 +422,7 @@
|
|||||||
<string name="allow_no_password_title">空のマスターキーを許可</string>
|
<string name="allow_no_password_title">空のマスターキーを許可</string>
|
||||||
<string name="autofill_auto_search_title">自動検索</string>
|
<string name="autofill_auto_search_title">自動検索</string>
|
||||||
<string name="keyboard_label">Magikeyboard (KeePassDX)</string>
|
<string name="keyboard_label">Magikeyboard (KeePassDX)</string>
|
||||||
<string name="compression_gzip">gzip</string>
|
<string name="compression_gzip">Gzip</string>
|
||||||
<string name="compression_none">なし</string>
|
<string name="compression_none">なし</string>
|
||||||
<string name="compression">圧縮</string>
|
<string name="compression">圧縮</string>
|
||||||
<string name="other">その他</string>
|
<string name="other">その他</string>
|
||||||
|
|||||||
@@ -64,7 +64,7 @@
|
|||||||
<string name="entry_user_name">아이디</string>
|
<string name="entry_user_name">아이디</string>
|
||||||
<string name="error_arc4">Arcfour 스트림 암호는 지원되지 않습니다.</string>
|
<string name="error_arc4">Arcfour 스트림 암호는 지원되지 않습니다.</string>
|
||||||
<string name="error_can_not_handle_uri">KeePassDX에서는 이 URI를 처리할 수 없습니다.</string>
|
<string name="error_can_not_handle_uri">KeePassDX에서는 이 URI를 처리할 수 없습니다.</string>
|
||||||
<string name="error_file_not_create">파일을 생성할 수 없음:</string>
|
<string name="error_file_not_create">파일을 생성할 수 없음</string>
|
||||||
<string name="error_invalid_db">데이터베이스를 읽을 수 없음.</string>
|
<string name="error_invalid_db">데이터베이스를 읽을 수 없음.</string>
|
||||||
<string name="error_invalid_path">경로가 확실한지 확인하십시오.</string>
|
<string name="error_invalid_path">경로가 확실한지 확인하십시오.</string>
|
||||||
<string name="error_no_name">이름을 입력하십시오.</string>
|
<string name="error_no_name">이름을 입력하십시오.</string>
|
||||||
|
|||||||
@@ -58,7 +58,7 @@
|
|||||||
<string name="entry_user_name">Lietotāja vārds</string>
|
<string name="entry_user_name">Lietotāja vārds</string>
|
||||||
<string name="error_arc4">Arcfour plūsmas šifrs netiek atbalstīts.</string>
|
<string name="error_arc4">Arcfour plūsmas šifrs netiek atbalstīts.</string>
|
||||||
<string name="error_can_not_handle_uri">Neizdevās pātiet uz norādīto adresi.</string>
|
<string name="error_can_not_handle_uri">Neizdevās pātiet uz norādīto adresi.</string>
|
||||||
<string name="error_file_not_create">Neizdevās izveidot failu:</string>
|
<string name="error_file_not_create">Neizdevās izveidot failu</string>
|
||||||
<string name="error_invalid_db">Nederīga datu bāze.</string>
|
<string name="error_invalid_db">Nederīga datu bāze.</string>
|
||||||
<string name="error_invalid_path">Nederīgs ceļš.</string>
|
<string name="error_invalid_path">Nederīgs ceļš.</string>
|
||||||
<string name="error_no_name">Vajag ievadīt faila nosaukumu</string>
|
<string name="error_no_name">Vajag ievadīt faila nosaukumu</string>
|
||||||
|
|||||||
@@ -76,7 +76,7 @@
|
|||||||
<string name="error_save_database">ഡാറ്റാബേസ് സംരക്ഷിക്കാൻ കഴിഞ്ഞില്ല.</string>
|
<string name="error_save_database">ഡാറ്റാബേസ് സംരക്ഷിക്കാൻ കഴിഞ്ഞില്ല.</string>
|
||||||
<string name="error_pass_match">പാസ്വേഡുകൾ പൊരുത്തപ്പെടുന്നില്ല.</string>
|
<string name="error_pass_match">പാസ്വേഡുകൾ പൊരുത്തപ്പെടുന്നില്ല.</string>
|
||||||
<string name="error_no_name">ഒരു പേര് നൽകുക.</string>
|
<string name="error_no_name">ഒരു പേര് നൽകുക.</string>
|
||||||
<string name="error_file_not_create">ഫയൽ സൃഷ്ടിക്കാൻ കഴിഞ്ഞില്ല:</string>
|
<string name="error_file_not_create">ഫയൽ സൃഷ്ടിക്കാൻ കഴിഞ്ഞില്ല</string>
|
||||||
<string name="error_invalid_db">ഡാറ്റാബേസ് വായിക്കാൻ സാധിച്ചില്ല.</string>
|
<string name="error_invalid_db">ഡാറ്റാബേസ് വായിക്കാൻ സാധിച്ചില്ല.</string>
|
||||||
<string name="entry_user_name">ഉപയോക്തൃനാമം</string>
|
<string name="entry_user_name">ഉപയോക്തൃനാമം</string>
|
||||||
<string name="entry_url">URL</string>
|
<string name="entry_url">URL</string>
|
||||||
@@ -154,12 +154,12 @@
|
|||||||
<string name="encryption_chacha20">ChaCha20</string>
|
<string name="encryption_chacha20">ChaCha20</string>
|
||||||
<string name="encryption_twofish">Twofish</string>
|
<string name="encryption_twofish">Twofish</string>
|
||||||
<string name="encryption_rijndael">Rijndael (AES)</string>
|
<string name="encryption_rijndael">Rijndael (AES)</string>
|
||||||
<string name="download_complete">പൂർത്തിയാക്കി! ഫയൽ തുറക്കാൻ സ്പർശിക്കുക</string>
|
<string name="download_complete">പൂർത്തിയാക്കി!</string>
|
||||||
<string name="education_create_database_title">നിങ്ങളുടെ ഡാറ്റാബേസ് ഫയൽ സൃഷ്ടിക്കുക</string>
|
<string name="education_create_database_title">നിങ്ങളുടെ ഡാറ്റാബേസ് ഫയൽ സൃഷ്ടിക്കുക</string>
|
||||||
<string name="autofill_auto_search_title">സ്വയം തിരയൽ</string>
|
<string name="autofill_auto_search_title">സ്വയം തിരയൽ</string>
|
||||||
<string name="keyboard_change">കീബോർഡ് മാറ്റുക</string>
|
<string name="keyboard_change">കീബോർഡ് മാറ്റുക</string>
|
||||||
<string name="keyboard_label">Magikeyboard (KeePassDX)</string>
|
<string name="keyboard_label">Magikeyboard (KeePassDX)</string>
|
||||||
<string name="compression_gzip">gzip</string>
|
<string name="compression_gzip">Gzip</string>
|
||||||
<string name="database_version_title">"ഡാറ്റാബ്സിൻ്റെ പതിപ്പ്"</string>
|
<string name="database_version_title">"ഡാറ്റാബ്സിൻ്റെ പതിപ്പ്"</string>
|
||||||
<string name="max_history_items_title">പരമാവധി നമ്പർ</string>
|
<string name="max_history_items_title">പരമാവധി നമ്പർ</string>
|
||||||
<string name="recycle_bin_title">റീസൈക്കിൾ ബിനിൻ്റെ ഉപയോഗം</string>
|
<string name="recycle_bin_title">റീസൈക്കിൾ ബിനിൻ്റെ ഉപയോഗം</string>
|
||||||
|
|||||||
@@ -64,7 +64,7 @@
|
|||||||
<string name="entry_user_name">Brukernavn</string>
|
<string name="entry_user_name">Brukernavn</string>
|
||||||
<string name="error_arc4">Arcfour-strømchifferet støttes ikke.</string>
|
<string name="error_arc4">Arcfour-strømchifferet støttes ikke.</string>
|
||||||
<string name="error_can_not_handle_uri">KeePassDX kan ikke håntere denne URI-en.</string>
|
<string name="error_can_not_handle_uri">KeePassDX kan ikke håntere denne URI-en.</string>
|
||||||
<string name="error_file_not_create">Kunne ikke opprette fil:</string>
|
<string name="error_file_not_create">Kunne ikke opprette fil</string>
|
||||||
<string name="error_invalid_db">Ugyldig database eller fremmed hovednøkkel.</string>
|
<string name="error_invalid_db">Ugyldig database eller fremmed hovednøkkel.</string>
|
||||||
<string name="error_invalid_path">Ugyldig sti.</string>
|
<string name="error_invalid_path">Ugyldig sti.</string>
|
||||||
<string name="error_no_name">Et navn er påkrevd.</string>
|
<string name="error_no_name">Et navn er påkrevd.</string>
|
||||||
@@ -387,7 +387,7 @@
|
|||||||
<string name="database_data_compression_summary">Datakomprimering reduserer databasens størrelse.</string>
|
<string name="database_data_compression_summary">Datakomprimering reduserer databasens størrelse.</string>
|
||||||
<string name="compression">Komprimering</string>
|
<string name="compression">Komprimering</string>
|
||||||
<string name="compression_none">Ingen</string>
|
<string name="compression_none">Ingen</string>
|
||||||
<string name="compression_gzip">GZip</string>
|
<string name="compression_gzip">Gzip</string>
|
||||||
<string name="error_save_database">Kunne ikke lagre database.</string>
|
<string name="error_save_database">Kunne ikke lagre database.</string>
|
||||||
<string name="menu_empty_recycle_bin">Tøm papirkurven</string>
|
<string name="menu_empty_recycle_bin">Tøm papirkurven</string>
|
||||||
<string name="command_execution">Kjører kommandoen…</string>
|
<string name="command_execution">Kjører kommandoen…</string>
|
||||||
@@ -401,7 +401,7 @@
|
|||||||
<string name="download_attachment">Last ned %1$s</string>
|
<string name="download_attachment">Last ned %1$s</string>
|
||||||
<string name="download_progression">Underveis: %1$d%%</string>
|
<string name="download_progression">Underveis: %1$d%%</string>
|
||||||
<string name="download_finalization">Fullfører…</string>
|
<string name="download_finalization">Fullfører…</string>
|
||||||
<string name="download_complete">Fullført. Trykk for å åpne filen.</string>
|
<string name="download_complete">Fullført!</string>
|
||||||
<string name="hide_expired_entries_title">Skjul utløpte oppføringer</string>
|
<string name="hide_expired_entries_title">Skjul utløpte oppføringer</string>
|
||||||
<string name="auto_focus_search_title">Hurtigsøk</string>
|
<string name="auto_focus_search_title">Hurtigsøk</string>
|
||||||
<string name="entry_add_attachment">Legg til vedlegg</string>
|
<string name="entry_add_attachment">Legg til vedlegg</string>
|
||||||
|
|||||||
@@ -59,7 +59,7 @@
|
|||||||
<string name="entry_user_name">Gebruikersnaam</string>
|
<string name="entry_user_name">Gebruikersnaam</string>
|
||||||
<string name="error_arc4">De Arcfour stream-versleuteling wordt niet ondersteund.</string>
|
<string name="error_arc4">De Arcfour stream-versleuteling wordt niet ondersteund.</string>
|
||||||
<string name="error_can_not_handle_uri">KeePassDX kan deze URI niet verwerken.</string>
|
<string name="error_can_not_handle_uri">KeePassDX kan deze URI niet verwerken.</string>
|
||||||
<string name="error_file_not_create">Bestand is niet aangemaakt:</string>
|
<string name="error_file_not_create">Bestand is niet aangemaakt</string>
|
||||||
<string name="error_invalid_db">Kan database niet uitlezen.</string>
|
<string name="error_invalid_db">Kan database niet uitlezen.</string>
|
||||||
<string name="error_invalid_path">Zorg ervoor dat het pad juist is.</string>
|
<string name="error_invalid_path">Zorg ervoor dat het pad juist is.</string>
|
||||||
<string name="error_no_name">Voer een naam in.</string>
|
<string name="error_no_name">Voer een naam in.</string>
|
||||||
@@ -425,7 +425,7 @@
|
|||||||
<string name="database_custom_color_title">Aangepaste databasekleur</string>
|
<string name="database_custom_color_title">Aangepaste databasekleur</string>
|
||||||
<string name="compression">Compressie</string>
|
<string name="compression">Compressie</string>
|
||||||
<string name="compression_none">Geen</string>
|
<string name="compression_none">Geen</string>
|
||||||
<string name="compression_gzip">gzip</string>
|
<string name="compression_gzip">Gzip</string>
|
||||||
<string name="device_keyboard_setting_title">Toetsenbordinstellingen</string>
|
<string name="device_keyboard_setting_title">Toetsenbordinstellingen</string>
|
||||||
<string name="enable_auto_save_database_summary">Sla de database op na elke belangrijke actie (in \"Schrijf\" modus)</string>
|
<string name="enable_auto_save_database_summary">Sla de database op na elke belangrijke actie (in \"Schrijf\" modus)</string>
|
||||||
<string name="education_setup_OTP_title">Instellingen OTP</string>
|
<string name="education_setup_OTP_title">Instellingen OTP</string>
|
||||||
@@ -433,7 +433,7 @@
|
|||||||
<string name="remember_database_locations_title">Databaselocatie opslaan</string>
|
<string name="remember_database_locations_title">Databaselocatie opslaan</string>
|
||||||
<string name="hide_expired_entries_summary">Verlopen items worden verborgen</string>
|
<string name="hide_expired_entries_summary">Verlopen items worden verborgen</string>
|
||||||
<string name="hide_expired_entries_title">Verberg verlopen items</string>
|
<string name="hide_expired_entries_title">Verberg verlopen items</string>
|
||||||
<string name="download_complete">Klaar! Tik om het bestand te openen.</string>
|
<string name="download_complete">Tik om het bestand te openen</string>
|
||||||
<string name="download_finalization">Voltooien…</string>
|
<string name="download_finalization">Voltooien…</string>
|
||||||
<string name="download_progression">Voortgang: %1$d%%</string>
|
<string name="download_progression">Voortgang: %1$d%%</string>
|
||||||
<string name="download_initialization">Initialiseren…</string>
|
<string name="download_initialization">Initialiseren…</string>
|
||||||
|
|||||||
@@ -56,7 +56,7 @@
|
|||||||
<string name="entry_user_name">Brukaramn</string>
|
<string name="entry_user_name">Brukaramn</string>
|
||||||
<string name="error_arc4">Kan ikkje bruka Arcfour dataflytkryptering.</string>
|
<string name="error_arc4">Kan ikkje bruka Arcfour dataflytkryptering.</string>
|
||||||
<string name="error_can_not_handle_uri">KeePassDX kan ikkje bruka denne ressursen.</string>
|
<string name="error_can_not_handle_uri">KeePassDX kan ikkje bruka denne ressursen.</string>
|
||||||
<string name="error_file_not_create">Klarte ikkje å laga fila:</string>
|
<string name="error_file_not_create">Klarte ikkje å laga fila</string>
|
||||||
<string name="error_invalid_db">Ugyldig database.</string>
|
<string name="error_invalid_db">Ugyldig database.</string>
|
||||||
<string name="error_invalid_path">Ugyldig stig.</string>
|
<string name="error_invalid_path">Ugyldig stig.</string>
|
||||||
<string name="error_no_name">Treng eit namn.</string>
|
<string name="error_no_name">Treng eit namn.</string>
|
||||||
|
|||||||
@@ -6,7 +6,7 @@
|
|||||||
<string name="icon_pack_choose_title">ਆਈਕਾਨ ਪੈਕ</string>
|
<string name="icon_pack_choose_title">ਆਈਕਾਨ ਪੈਕ</string>
|
||||||
<string name="style_choose_summary">ਐਪ ਵਿੱਚ ਵਰਤਿਆ ਥੀਮ</string>
|
<string name="style_choose_summary">ਐਪ ਵਿੱਚ ਵਰਤਿਆ ਥੀਮ</string>
|
||||||
<string name="style_choose_title">ਐਪ ਦਾ ਥੀਮ</string>
|
<string name="style_choose_title">ਐਪ ਦਾ ਥੀਮ</string>
|
||||||
<string name="download_complete">ਪੂਰਾ ਹੋਇਆ! ਫ਼ਾਇਲ ਖੋਲ੍ਹਣ ਲਈ ਛੂਹੋ।</string>
|
<string name="download_complete">ਪੂਰਾ ਹੋਇਆ!</string>
|
||||||
<string name="download_finalization">…ਪੂਰਾ ਕੀਤਾ ਜਾ ਰਿਹਾ ਹੈ</string>
|
<string name="download_finalization">…ਪੂਰਾ ਕੀਤਾ ਜਾ ਰਿਹਾ ਹੈ</string>
|
||||||
<string name="download_progression">ਜਾਰੀ ਹੈ: %1$d%%</string>
|
<string name="download_progression">ਜਾਰੀ ਹੈ: %1$d%%</string>
|
||||||
<string name="download_initialization">…ਸ਼ੁਰੂ ਕੀਤਾ ਜਾ ਰਿਹਾ ਹੈ</string>
|
<string name="download_initialization">…ਸ਼ੁਰੂ ਕੀਤਾ ਜਾ ਰਿਹਾ ਹੈ</string>
|
||||||
@@ -156,7 +156,7 @@
|
|||||||
<string name="error_invalid_OTP">ਗ਼ਲਤ OTP ਭੇਤ ਹੈ।</string>
|
<string name="error_invalid_OTP">ਗ਼ਲਤ OTP ਭੇਤ ਹੈ।</string>
|
||||||
<string name="error_invalid_path">ਪਾਥ ਦੇ ਠੀਕ ਹੋਣ ਨੂੰ ਯਕੀਨੀ ਬਣਾਓ।</string>
|
<string name="error_invalid_path">ਪਾਥ ਦੇ ਠੀਕ ਹੋਣ ਨੂੰ ਯਕੀਨੀ ਬਣਾਓ।</string>
|
||||||
<string name="error_invalid_db">ਡਾਟਾਬੇਸ ਪੜ੍ਹਿਆ ਨਹੀਂ ਜਾ ਸਕਿਆ।</string>
|
<string name="error_invalid_db">ਡਾਟਾਬੇਸ ਪੜ੍ਹਿਆ ਨਹੀਂ ਜਾ ਸਕਿਆ।</string>
|
||||||
<string name="error_file_not_create">ਫ਼ਾਇਲ ਬਣਾਈ ਨਹੀਂ ਜਾ ਸਕੀ:</string>
|
<string name="error_file_not_create">ਫ਼ਾਇਲ ਬਣਾਈ ਨਹੀਂ ਜਾ ਸਕੀ</string>
|
||||||
<string name="error_can_not_handle_uri">ਇਹ URI KeePassDX ਵਿੱਚ ਹੈਂਡਲ ਨਹੀਂ ਕੀਤਾ ਜਾ ਸਕਿਆ।</string>
|
<string name="error_can_not_handle_uri">ਇਹ URI KeePassDX ਵਿੱਚ ਹੈਂਡਲ ਨਹੀਂ ਕੀਤਾ ਜਾ ਸਕਿਆ।</string>
|
||||||
<string name="error_arc4">Arcfour ਸਟਰੀਮ ਸੀਫ਼ਰ ਸਹਾਇਕ ਨਹੀਂ ਹੈ।</string>
|
<string name="error_arc4">Arcfour ਸਟਰੀਮ ਸੀਫ਼ਰ ਸਹਾਇਕ ਨਹੀਂ ਹੈ।</string>
|
||||||
<string name="entry_user_name">ਵਰਤੋਂਕਾਰ-ਨਾਂ</string>
|
<string name="entry_user_name">ਵਰਤੋਂਕਾਰ-ਨਾਂ</string>
|
||||||
|
|||||||
@@ -55,7 +55,7 @@
|
|||||||
<string name="entry_user_name">Nazwa użytkownika</string>
|
<string name="entry_user_name">Nazwa użytkownika</string>
|
||||||
<string name="error_arc4">Strumieniowe szyfrowanie Arcfour nie jest wspierane.</string>
|
<string name="error_arc4">Strumieniowe szyfrowanie Arcfour nie jest wspierane.</string>
|
||||||
<string name="error_can_not_handle_uri">Nie można obsłużyć tego identyfikatora URI w KeePassDX.</string>
|
<string name="error_can_not_handle_uri">Nie można obsłużyć tego identyfikatora URI w KeePassDX.</string>
|
||||||
<string name="error_file_not_create">Nie można utworzyć pliku:</string>
|
<string name="error_file_not_create">Nie można utworzyć pliku</string>
|
||||||
<string name="error_invalid_db">Nie można odczytać bazy danych.</string>
|
<string name="error_invalid_db">Nie można odczytać bazy danych.</string>
|
||||||
<string name="error_invalid_path">Upewnij się, że ścieżka jest prawidłowa.</string>
|
<string name="error_invalid_path">Upewnij się, że ścieżka jest prawidłowa.</string>
|
||||||
<string name="error_no_name">Wpisz nazwę.</string>
|
<string name="error_no_name">Wpisz nazwę.</string>
|
||||||
@@ -405,7 +405,7 @@
|
|||||||
<string name="database_custom_color_title">Niestandardowy kolor bazy danych</string>
|
<string name="database_custom_color_title">Niestandardowy kolor bazy danych</string>
|
||||||
<string name="compression">Kompresja</string>
|
<string name="compression">Kompresja</string>
|
||||||
<string name="compression_none">Żaden</string>
|
<string name="compression_none">Żaden</string>
|
||||||
<string name="compression_gzip">gzip</string>
|
<string name="compression_gzip">Gzip</string>
|
||||||
<string name="device_keyboard_setting_title">Ustawienia klawiatury urządzenia</string>
|
<string name="device_keyboard_setting_title">Ustawienia klawiatury urządzenia</string>
|
||||||
<string name="error_invalid_OTP">Nieprawidłowy klucz tajny OTP.</string>
|
<string name="error_invalid_OTP">Nieprawidłowy klucz tajny OTP.</string>
|
||||||
<string name="error_disallow_no_credentials">Należy ustawić co najmniej jedno poświadczenie.</string>
|
<string name="error_disallow_no_credentials">Należy ustawić co najmniej jedno poświadczenie.</string>
|
||||||
@@ -442,7 +442,7 @@
|
|||||||
<string name="download_initialization">Inicjowanie…</string>
|
<string name="download_initialization">Inicjowanie…</string>
|
||||||
<string name="download_progression">W trakcie realizacji: %1$d%%</string>
|
<string name="download_progression">W trakcie realizacji: %1$d%%</string>
|
||||||
<string name="download_finalization">Kończę…</string>
|
<string name="download_finalization">Kończę…</string>
|
||||||
<string name="download_complete">Kompletny! Stuknij, aby otworzyć plik.</string>
|
<string name="download_complete">Kompletny!</string>
|
||||||
<string name="hide_expired_entries_title">Ukryj wygasłe wpisy</string>
|
<string name="hide_expired_entries_title">Ukryj wygasłe wpisy</string>
|
||||||
<string name="hide_expired_entries_summary">Wygasłe wpisy są ukryte</string>
|
<string name="hide_expired_entries_summary">Wygasłe wpisy są ukryte</string>
|
||||||
<string name="contact">Kontakt</string>
|
<string name="contact">Kontakt</string>
|
||||||
|
|||||||
@@ -56,7 +56,7 @@
|
|||||||
<string name="entry_user_name">Nome de usuário</string>
|
<string name="entry_user_name">Nome de usuário</string>
|
||||||
<string name="error_arc4">A cifra de fluxo Arcfour não é suportada.</string>
|
<string name="error_arc4">A cifra de fluxo Arcfour não é suportada.</string>
|
||||||
<string name="error_can_not_handle_uri">Não pôde tratar esta URI no KeePassDX.</string>
|
<string name="error_can_not_handle_uri">Não pôde tratar esta URI no KeePassDX.</string>
|
||||||
<string name="error_file_not_create">Não foi possível criar o arquivo:</string>
|
<string name="error_file_not_create">Não foi possível criar o arquivo</string>
|
||||||
<string name="error_invalid_db">Falha ao ler o banco.</string>
|
<string name="error_invalid_db">Falha ao ler o banco.</string>
|
||||||
<string name="error_invalid_path">Certifique-se de que o caminho está correto.</string>
|
<string name="error_invalid_path">Certifique-se de que o caminho está correto.</string>
|
||||||
<string name="error_no_name">Digite um nome.</string>
|
<string name="error_no_name">Digite um nome.</string>
|
||||||
@@ -423,7 +423,7 @@
|
|||||||
<string name="database_custom_color_title">Cor personalizada do banco de dados</string>
|
<string name="database_custom_color_title">Cor personalizada do banco de dados</string>
|
||||||
<string name="compression">Compressão</string>
|
<string name="compression">Compressão</string>
|
||||||
<string name="compression_none">Nada</string>
|
<string name="compression_none">Nada</string>
|
||||||
<string name="compression_gzip">GZip</string>
|
<string name="compression_gzip">Gzip</string>
|
||||||
<string name="device_keyboard_setting_title">Configurações do teclado do aparelho</string>
|
<string name="device_keyboard_setting_title">Configurações do teclado do aparelho</string>
|
||||||
<string name="error_save_database">Não foi possível salvar no banco de dados.</string>
|
<string name="error_save_database">Não foi possível salvar no banco de dados.</string>
|
||||||
<string name="menu_save_database">Salvar banco de dados</string>
|
<string name="menu_save_database">Salvar banco de dados</string>
|
||||||
@@ -443,7 +443,7 @@
|
|||||||
<string name="autofill_auto_search_summary">Sugerir resultados de pesquisa de domínios da internet ou de aplicações automaticamente</string>
|
<string name="autofill_auto_search_summary">Sugerir resultados de pesquisa de domínios da internet ou de aplicações automaticamente</string>
|
||||||
<string name="hide_expired_entries_summary">Entradas expeiradas foram escondidas</string>
|
<string name="hide_expired_entries_summary">Entradas expeiradas foram escondidas</string>
|
||||||
<string name="hide_expired_entries_title">Esconder entradas expiradas</string>
|
<string name="hide_expired_entries_title">Esconder entradas expiradas</string>
|
||||||
<string name="download_complete">Completo! Toque para abrir o aquivo.</string>
|
<string name="download_complete">Completo!</string>
|
||||||
<string name="download_finalization">Finalizando…</string>
|
<string name="download_finalization">Finalizando…</string>
|
||||||
<string name="download_progression">Em progresso: %1$d%%</string>
|
<string name="download_progression">Em progresso: %1$d%%</string>
|
||||||
<string name="download_initialization">Inicializando…</string>
|
<string name="download_initialization">Inicializando…</string>
|
||||||
|
|||||||
@@ -61,7 +61,7 @@
|
|||||||
<string name="entry_user_name">Nome de utilizador</string>
|
<string name="entry_user_name">Nome de utilizador</string>
|
||||||
<string name="error_arc4">A cifra de fluxo Arcfour não é suportada.</string>
|
<string name="error_arc4">A cifra de fluxo Arcfour não é suportada.</string>
|
||||||
<string name="error_can_not_handle_uri">Não pôde tratar esta URI no KeePassDX.</string>
|
<string name="error_can_not_handle_uri">Não pôde tratar esta URI no KeePassDX.</string>
|
||||||
<string name="error_file_not_create">Não foi possível criar o ficheiro:</string>
|
<string name="error_file_not_create">Não foi possível criar o ficheiro</string>
|
||||||
<string name="error_invalid_db">Não foi possível ler a base de dados.</string>
|
<string name="error_invalid_db">Não foi possível ler a base de dados.</string>
|
||||||
<string name="error_invalid_path">Certifique-se que o caminho é válido.</string>
|
<string name="error_invalid_path">Certifique-se que o caminho é válido.</string>
|
||||||
<string name="error_no_name">Introduza um nome.</string>
|
<string name="error_no_name">Introduza um nome.</string>
|
||||||
@@ -420,7 +420,7 @@
|
|||||||
<string name="html_about_contribution">Para <strong>manter a liberdade</strong>, <strong>solucionar bugs</strong>, <strong>adicionar funções</strong> e <strong>para sermos sempre ativoa</strong>, contamos com sua <strong>contribuição</strong>.</string>
|
<string name="html_about_contribution">Para <strong>manter a liberdade</strong>, <strong>solucionar bugs</strong>, <strong>adicionar funções</strong> e <strong>para sermos sempre ativoa</strong>, contamos com sua <strong>contribuição</strong>.</string>
|
||||||
<string name="biometric_unlock_enable_title">Desbloqueio por biométrico</string>
|
<string name="biometric_unlock_enable_title">Desbloqueio por biométrico</string>
|
||||||
<string name="entry_attachments">Anexos</string>
|
<string name="entry_attachments">Anexos</string>
|
||||||
<string name="compression_gzip">gzip</string>
|
<string name="compression_gzip">Gzip</string>
|
||||||
<string name="discard">Descartar</string>
|
<string name="discard">Descartar</string>
|
||||||
<string name="remember_database_locations_summary">Lembrar o local das bases de dados</string>
|
<string name="remember_database_locations_summary">Lembrar o local das bases de dados</string>
|
||||||
<string name="auto_focus_search_summary">Solicitar uma pesquisa quando abrir a base de dados</string>
|
<string name="auto_focus_search_summary">Solicitar uma pesquisa quando abrir a base de dados</string>
|
||||||
@@ -428,7 +428,7 @@
|
|||||||
<string name="download_finalization">A finalizar…</string>
|
<string name="download_finalization">A finalizar…</string>
|
||||||
<string name="autofill_preference_title">Configurações de preenchimento automático</string>
|
<string name="autofill_preference_title">Configurações de preenchimento automático</string>
|
||||||
<string name="max_history_items_summary">Limitar a quantidade de itens do histórico por entrada</string>
|
<string name="max_history_items_summary">Limitar a quantidade de itens do histórico por entrada</string>
|
||||||
<string name="download_complete">Completo! Toque para abrir o ficheiro.</string>
|
<string name="download_complete">Completo!</string>
|
||||||
<string name="content_description_update_from_list">Atualizar</string>
|
<string name="content_description_update_from_list">Atualizar</string>
|
||||||
<string name="contribution">Contribuição</string>
|
<string name="contribution">Contribuição</string>
|
||||||
<string name="hide_broken_locations_summary">Esconder ligações quebradas na lista de bases de dados recentes</string>
|
<string name="hide_broken_locations_summary">Esconder ligações quebradas na lista de bases de dados recentes</string>
|
||||||
|
|||||||
@@ -95,7 +95,7 @@
|
|||||||
<string name="entry_user_name">Nume utilizator</string>
|
<string name="entry_user_name">Nume utilizator</string>
|
||||||
<string name="error_arc4">Cifrarea fluxului Arcfour nu este acceptată.</string>
|
<string name="error_arc4">Cifrarea fluxului Arcfour nu este acceptată.</string>
|
||||||
<string name="error_can_not_handle_uri">Nu s-a putut gestiona acest URI în KeePassDX.</string>
|
<string name="error_can_not_handle_uri">Nu s-a putut gestiona acest URI în KeePassDX.</string>
|
||||||
<string name="error_file_not_create">Nu s-a putut creea fisierul:</string>
|
<string name="error_file_not_create">Nu s-a putut creea fisierul</string>
|
||||||
<string name="error_invalid_db">Nu s-a putut citi baza de date.</string>
|
<string name="error_invalid_db">Nu s-a putut citi baza de date.</string>
|
||||||
<string name="error_invalid_path">Asigurați-vă că calea este corectă.</string>
|
<string name="error_invalid_path">Asigurați-vă că calea este corectă.</string>
|
||||||
<string name="error_invalid_OTP">Secret OTP nevalid.</string>
|
<string name="error_invalid_OTP">Secret OTP nevalid.</string>
|
||||||
@@ -323,7 +323,7 @@
|
|||||||
<string name="other">Alta</string>
|
<string name="other">Alta</string>
|
||||||
<string name="compression">Compresie</string>
|
<string name="compression">Compresie</string>
|
||||||
<string name="compression_none">Nimic</string>
|
<string name="compression_none">Nimic</string>
|
||||||
<string name="compression_gzip">gzip</string>
|
<string name="compression_gzip">Gzip</string>
|
||||||
<string name="recycle_bin">Cos de reciclare</string>
|
<string name="recycle_bin">Cos de reciclare</string>
|
||||||
<string name="keyboard">Tastatura</string>
|
<string name="keyboard">Tastatura</string>
|
||||||
<string name="magic_keyboard_title">TastaturaMagica</string>
|
<string name="magic_keyboard_title">TastaturaMagica</string>
|
||||||
@@ -418,7 +418,7 @@
|
|||||||
<string name="download_initialization">Inițializare …</string>
|
<string name="download_initialization">Inițializare …</string>
|
||||||
<string name="download_progression">In progress: %1$d%%</string>
|
<string name="download_progression">In progress: %1$d%%</string>
|
||||||
<string name="download_finalization">Finalizare …</string>
|
<string name="download_finalization">Finalizare …</string>
|
||||||
<string name="download_complete">Complet! Atingeți pentru a deschide fișierul.</string>
|
<string name="download_complete">Complet!</string>
|
||||||
<string name="encryption_rijndael">Rijndael (AES)</string>
|
<string name="encryption_rijndael">Rijndael (AES)</string>
|
||||||
<string name="encryption_twofish">Twofish</string>
|
<string name="encryption_twofish">Twofish</string>
|
||||||
<string name="encryption_chacha20">ChaCha20</string>
|
<string name="encryption_chacha20">ChaCha20</string>
|
||||||
|
|||||||
@@ -61,7 +61,7 @@
|
|||||||
<string name="entry_user_name">Имя пользователя</string>
|
<string name="entry_user_name">Имя пользователя</string>
|
||||||
<string name="error_arc4">Потоковый шифр Arcfour не поддерживается.</string>
|
<string name="error_arc4">Потоковый шифр Arcfour не поддерживается.</string>
|
||||||
<string name="error_can_not_handle_uri">Невозможно обработать указанный URI в KeePassDX.</string>
|
<string name="error_can_not_handle_uri">Невозможно обработать указанный URI в KeePassDX.</string>
|
||||||
<string name="error_file_not_create">Невозможно создать файл:</string>
|
<string name="error_file_not_create">Невозможно создать файл</string>
|
||||||
<string name="error_invalid_db">Невозможно прочитать базу.</string>
|
<string name="error_invalid_db">Невозможно прочитать базу.</string>
|
||||||
<string name="error_invalid_path">Убедитесь, что путь указан правильно.</string>
|
<string name="error_invalid_path">Убедитесь, что путь указан правильно.</string>
|
||||||
<string name="error_no_name">Введите название.</string>
|
<string name="error_no_name">Введите название.</string>
|
||||||
@@ -421,7 +421,7 @@
|
|||||||
<string name="database_custom_color_title">Произвольный цвет базы</string>
|
<string name="database_custom_color_title">Произвольный цвет базы</string>
|
||||||
<string name="compression">Сжатие</string>
|
<string name="compression">Сжатие</string>
|
||||||
<string name="compression_none">Нет</string>
|
<string name="compression_none">Нет</string>
|
||||||
<string name="compression_gzip">GZip</string>
|
<string name="compression_gzip">Gzip</string>
|
||||||
<string name="device_keyboard_setting_title">Настройки клавиатур устройства</string>
|
<string name="device_keyboard_setting_title">Настройки клавиатур устройства</string>
|
||||||
<string name="error_save_database">Невозможно сохранить базу.</string>
|
<string name="error_save_database">Невозможно сохранить базу.</string>
|
||||||
<string name="menu_save_database">Сохранить базу</string>
|
<string name="menu_save_database">Сохранить базу</string>
|
||||||
@@ -442,7 +442,7 @@
|
|||||||
<string name="download_initialization">Инициализация…</string>
|
<string name="download_initialization">Инициализация…</string>
|
||||||
<string name="download_progression">Выполнение: %1$d%%</string>
|
<string name="download_progression">Выполнение: %1$d%%</string>
|
||||||
<string name="download_finalization">Завершение…</string>
|
<string name="download_finalization">Завершение…</string>
|
||||||
<string name="download_complete">Готово! Нажмите, чтобы открыть файл.</string>
|
<string name="download_complete">Готово!</string>
|
||||||
<string name="hide_expired_entries_title">Скрывать устаревшие записи</string>
|
<string name="hide_expired_entries_title">Скрывать устаревшие записи</string>
|
||||||
<string name="hide_expired_entries_summary">Записи с истёкшим сроком окончания будут скрыты</string>
|
<string name="hide_expired_entries_summary">Записи с истёкшим сроком окончания будут скрыты</string>
|
||||||
<string name="contact">Контактная информация</string>
|
<string name="contact">Контактная информация</string>
|
||||||
|
|||||||
@@ -57,7 +57,7 @@
|
|||||||
<string name="entry_user_name">Meno používateľa</string>
|
<string name="entry_user_name">Meno používateľa</string>
|
||||||
<string name="error_arc4">Arcfour stream šifra nieje podporovaná.</string>
|
<string name="error_arc4">Arcfour stream šifra nieje podporovaná.</string>
|
||||||
<string name="error_can_not_handle_uri">KeePassDX nevie použiť túto uri.</string>
|
<string name="error_can_not_handle_uri">KeePassDX nevie použiť túto uri.</string>
|
||||||
<string name="error_file_not_create">Neviem vytvoriť súbor:</string>
|
<string name="error_file_not_create">Neviem vytvoriť súbor</string>
|
||||||
<string name="error_invalid_db">Chybná databáza.</string>
|
<string name="error_invalid_db">Chybná databáza.</string>
|
||||||
<string name="error_invalid_path">Chybná cesta.</string>
|
<string name="error_invalid_path">Chybná cesta.</string>
|
||||||
<string name="error_no_name">Vyžaduje sa meno.</string>
|
<string name="error_no_name">Vyžaduje sa meno.</string>
|
||||||
|
|||||||
@@ -60,7 +60,7 @@
|
|||||||
<string name="entry_user_name">Användarnamn</string>
|
<string name="entry_user_name">Användarnamn</string>
|
||||||
<string name="error_arc4">Strömchiffret Arcfour stöds inte.</string>
|
<string name="error_arc4">Strömchiffret Arcfour stöds inte.</string>
|
||||||
<string name="error_can_not_handle_uri">KeePassDX kunde inte hantera denna URI.</string>
|
<string name="error_can_not_handle_uri">KeePassDX kunde inte hantera denna URI.</string>
|
||||||
<string name="error_file_not_create">Kunde inte skapa filen:</string>
|
<string name="error_file_not_create">Kunde inte skapa filen</string>
|
||||||
<string name="error_invalid_db">Kunde inte läsa databas.</string>
|
<string name="error_invalid_db">Kunde inte läsa databas.</string>
|
||||||
<string name="error_invalid_path">Se till att sökvägen är korrekt.</string>
|
<string name="error_invalid_path">Se till att sökvägen är korrekt.</string>
|
||||||
<string name="error_no_name">Ange ett namn.</string>
|
<string name="error_no_name">Ange ett namn.</string>
|
||||||
@@ -418,7 +418,7 @@
|
|||||||
<string name="database_custom_color_title">Anpassad databasfärg</string>
|
<string name="database_custom_color_title">Anpassad databasfärg</string>
|
||||||
<string name="compression">Komprimering</string>
|
<string name="compression">Komprimering</string>
|
||||||
<string name="compression_none">Ingen</string>
|
<string name="compression_none">Ingen</string>
|
||||||
<string name="compression_gzip">GZip</string>
|
<string name="compression_gzip">Gzip</string>
|
||||||
<string name="magic_keyboard_explanation_summary">Aktivera ett anpassat tangentbord som innehåller dina lösenord och alla identitetsfält</string>
|
<string name="magic_keyboard_explanation_summary">Aktivera ett anpassat tangentbord som innehåller dina lösenord och alla identitetsfält</string>
|
||||||
<string name="device_keyboard_setting_title">Enhetens tangentbordsinställningar</string>
|
<string name="device_keyboard_setting_title">Enhetens tangentbordsinställningar</string>
|
||||||
<string name="education_biometric_title">Lås upp databasen med biometrik</string>
|
<string name="education_biometric_title">Lås upp databasen med biometrik</string>
|
||||||
@@ -446,7 +446,7 @@
|
|||||||
<string name="contact">Kontakt</string>
|
<string name="contact">Kontakt</string>
|
||||||
<string name="hide_expired_entries_summary">Utgångna poster är dolda</string>
|
<string name="hide_expired_entries_summary">Utgångna poster är dolda</string>
|
||||||
<string name="hide_expired_entries_title">Dölj utgångna poster</string>
|
<string name="hide_expired_entries_title">Dölj utgångna poster</string>
|
||||||
<string name="download_complete">Färdigt! Klicka för att öppna filen.</string>
|
<string name="download_complete">Färdigt!</string>
|
||||||
<string name="download_finalization">Färdigställande…</string>
|
<string name="download_finalization">Färdigställande…</string>
|
||||||
<string name="download_progression">Händelse %1$d%%</string>
|
<string name="download_progression">Händelse %1$d%%</string>
|
||||||
<string name="download_initialization">Initiering…</string>
|
<string name="download_initialization">Initiering…</string>
|
||||||
|
|||||||
@@ -53,7 +53,7 @@
|
|||||||
<string name="entry_title">Başlık</string>
|
<string name="entry_title">Başlık</string>
|
||||||
<string name="entry_url">URL</string>
|
<string name="entry_url">URL</string>
|
||||||
<string name="entry_user_name">Kullanıcı adı</string>
|
<string name="entry_user_name">Kullanıcı adı</string>
|
||||||
<string name="error_file_not_create">Dosya oluşturulamadı:</string>
|
<string name="error_file_not_create">Dosya oluşturulamadı</string>
|
||||||
<string name="error_invalid_db">Veritabanı okunamadı.</string>
|
<string name="error_invalid_db">Veritabanı okunamadı.</string>
|
||||||
<string name="error_invalid_path">Yolun doğru olduğundan emin olun.</string>
|
<string name="error_invalid_path">Yolun doğru olduğundan emin olun.</string>
|
||||||
<string name="error_no_name">Bir isim girin.</string>
|
<string name="error_no_name">Bir isim girin.</string>
|
||||||
@@ -405,7 +405,7 @@
|
|||||||
<string name="database_custom_color_title">Özel veritabanı rengi</string>
|
<string name="database_custom_color_title">Özel veritabanı rengi</string>
|
||||||
<string name="compression">Sıkıştırma</string>
|
<string name="compression">Sıkıştırma</string>
|
||||||
<string name="compression_none">Yok</string>
|
<string name="compression_none">Yok</string>
|
||||||
<string name="compression_gzip">gzip</string>
|
<string name="compression_gzip">Gzip</string>
|
||||||
<string name="device_keyboard_setting_title">Cihaz klavye ayarları</string>
|
<string name="device_keyboard_setting_title">Cihaz klavye ayarları</string>
|
||||||
<string name="error_save_database">Veritabanı kaydedilemedi.</string>
|
<string name="error_save_database">Veritabanı kaydedilemedi.</string>
|
||||||
<string name="menu_save_database">Veritabanını kaydet</string>
|
<string name="menu_save_database">Veritabanını kaydet</string>
|
||||||
@@ -426,7 +426,7 @@
|
|||||||
<string name="download_initialization">Başlatılıyor…</string>
|
<string name="download_initialization">Başlatılıyor…</string>
|
||||||
<string name="download_progression">Devam ediyor: %1$d%%</string>
|
<string name="download_progression">Devam ediyor: %1$d%%</string>
|
||||||
<string name="download_finalization">Sonlandırılıyor…</string>
|
<string name="download_finalization">Sonlandırılıyor…</string>
|
||||||
<string name="download_complete">Tamamlandı! Dosyayı açmak için dokunun.</string>
|
<string name="download_complete">Tamamlandı!</string>
|
||||||
<string name="hide_expired_entries_title">Süresi dolmuş girdileri gizle</string>
|
<string name="hide_expired_entries_title">Süresi dolmuş girdileri gizle</string>
|
||||||
<string name="hide_expired_entries_summary">Süresi dolmuş girdiler gizlenecek</string>
|
<string name="hide_expired_entries_summary">Süresi dolmuş girdiler gizlenecek</string>
|
||||||
<string name="warning_database_read_only">Veri tabanı değişikliklerini kaydetmek için dosya yazma erişimi ver</string>
|
<string name="warning_database_read_only">Veri tabanı değişikliklerini kaydetmek için dosya yazma erişimi ver</string>
|
||||||
|
|||||||
@@ -57,7 +57,7 @@
|
|||||||
<string name="entry_user_name">Ім’я користувача</string>
|
<string name="entry_user_name">Ім’я користувача</string>
|
||||||
<string name="error_arc4">Потокове шифрування Arcfour не підтримується.</string>
|
<string name="error_arc4">Потокове шифрування Arcfour не підтримується.</string>
|
||||||
<string name="error_can_not_handle_uri">Не вдалось обробити цей URI в KeePassDX.</string>
|
<string name="error_can_not_handle_uri">Не вдалось обробити цей URI в KeePassDX.</string>
|
||||||
<string name="error_file_not_create">Не вдалося створити нотатку:</string>
|
<string name="error_file_not_create">Не вдалося створити нотатку</string>
|
||||||
<string name="error_invalid_db">Неможливо прочитати базу даних.</string>
|
<string name="error_invalid_db">Неможливо прочитати базу даних.</string>
|
||||||
<string name="error_invalid_path">Переконайтеся у правильності шляху.</string>
|
<string name="error_invalid_path">Переконайтеся у правильності шляху.</string>
|
||||||
<string name="error_no_name">Введіть назву.</string>
|
<string name="error_no_name">Введіть назву.</string>
|
||||||
@@ -152,7 +152,7 @@
|
|||||||
<string name="content_description_add_group">Додати групу</string>
|
<string name="content_description_add_group">Додати групу</string>
|
||||||
<string name="content_description_update_from_list">Оновити</string>
|
<string name="content_description_update_from_list">Оновити</string>
|
||||||
<string name="keyboard">Клавіатура</string>
|
<string name="keyboard">Клавіатура</string>
|
||||||
<string name="compression_gzip">gzip</string>
|
<string name="compression_gzip">Gzip</string>
|
||||||
<string name="compression">Стиснення</string>
|
<string name="compression">Стиснення</string>
|
||||||
<string name="other">Інше</string>
|
<string name="other">Інше</string>
|
||||||
<string name="application_appearance">Застосунок</string>
|
<string name="application_appearance">Застосунок</string>
|
||||||
@@ -279,7 +279,7 @@
|
|||||||
<string name="kdf_Argon2">Argon2</string>
|
<string name="kdf_Argon2">Argon2</string>
|
||||||
<string name="kdf_AES">AES</string>
|
<string name="kdf_AES">AES</string>
|
||||||
<string name="encryption_chacha20">ChaCha20</string>
|
<string name="encryption_chacha20">ChaCha20</string>
|
||||||
<string name="download_complete">Готово! Торкніться, щоб відкрити файл.</string>
|
<string name="download_complete">Готово!</string>
|
||||||
<string name="download_finalization">Завершення…</string>
|
<string name="download_finalization">Завершення…</string>
|
||||||
<string name="download_progression">Виконується: %1$d%%</string>
|
<string name="download_progression">Виконується: %1$d%%</string>
|
||||||
<string name="download_initialization">Ініціалізація…</string>
|
<string name="download_initialization">Ініціалізація…</string>
|
||||||
|
|||||||
@@ -57,7 +57,7 @@
|
|||||||
<string name="entry_user_name">用户名</string>
|
<string name="entry_user_name">用户名</string>
|
||||||
<string name="error_arc4">不支持Arcfour流式加密。</string>
|
<string name="error_arc4">不支持Arcfour流式加密。</string>
|
||||||
<string name="error_can_not_handle_uri">无法在KeePassDX中处理此URI。</string>
|
<string name="error_can_not_handle_uri">无法在KeePassDX中处理此URI。</string>
|
||||||
<string name="error_file_not_create">无法新建文件:</string>
|
<string name="error_file_not_create">无法新建文件</string>
|
||||||
<string name="error_invalid_db">无法读取数据库。</string>
|
<string name="error_invalid_db">无法读取数据库。</string>
|
||||||
<string name="error_invalid_path">请确保路径正确。</string>
|
<string name="error_invalid_path">请确保路径正确。</string>
|
||||||
<string name="error_no_name">输入名称。</string>
|
<string name="error_no_name">输入名称。</string>
|
||||||
@@ -423,7 +423,7 @@
|
|||||||
<string name="database_custom_color_title">自定义数据库颜色</string>
|
<string name="database_custom_color_title">自定义数据库颜色</string>
|
||||||
<string name="compression">压缩</string>
|
<string name="compression">压缩</string>
|
||||||
<string name="compression_none">无</string>
|
<string name="compression_none">无</string>
|
||||||
<string name="compression_gzip">GZip压缩</string>
|
<string name="compression_gzip">Gzip压缩</string>
|
||||||
<string name="device_keyboard_setting_title">设备键盘设置</string>
|
<string name="device_keyboard_setting_title">设备键盘设置</string>
|
||||||
<string name="error_save_database">无法保存数据库。</string>
|
<string name="error_save_database">无法保存数据库。</string>
|
||||||
<string name="menu_save_database">保存数据库</string>
|
<string name="menu_save_database">保存数据库</string>
|
||||||
@@ -444,7 +444,7 @@
|
|||||||
<string name="download_initialization">正在初始化…</string>
|
<string name="download_initialization">正在初始化…</string>
|
||||||
<string name="download_progression">进行中:%1$d%%</string>
|
<string name="download_progression">进行中:%1$d%%</string>
|
||||||
<string name="download_finalization">正在完成…</string>
|
<string name="download_finalization">正在完成…</string>
|
||||||
<string name="download_complete">完成!点击打开文件。</string>
|
<string name="download_complete">完成!</string>
|
||||||
<string name="hide_expired_entries_title">隐藏过期条目</string>
|
<string name="hide_expired_entries_title">隐藏过期条目</string>
|
||||||
<string name="hide_expired_entries_summary">过期条目将被隐藏</string>
|
<string name="hide_expired_entries_summary">过期条目将被隐藏</string>
|
||||||
<string name="contact">联系我们</string>
|
<string name="contact">联系我们</string>
|
||||||
|
|||||||
@@ -56,7 +56,7 @@
|
|||||||
<string name="entry_user_name">用戶名</string>
|
<string name="entry_user_name">用戶名</string>
|
||||||
<string name="error_arc4">Arcfour流密碼不被支援。</string>
|
<string name="error_arc4">Arcfour流密碼不被支援。</string>
|
||||||
<string name="error_can_not_handle_uri">KeePassDX無法處理此URI。</string>
|
<string name="error_can_not_handle_uri">KeePassDX無法處理此URI。</string>
|
||||||
<string name="error_file_not_create">不能創建檔案:</string>
|
<string name="error_file_not_create">不能創建檔案</string>
|
||||||
<string name="error_invalid_db">無法閱讀資料庫。</string>
|
<string name="error_invalid_db">無法閱讀資料庫。</string>
|
||||||
<string name="error_invalid_path">請確保路徑正確。</string>
|
<string name="error_invalid_path">請確保路徑正確。</string>
|
||||||
<string name="error_no_name">請輸入用戶名。</string>
|
<string name="error_no_name">請輸入用戶名。</string>
|
||||||
|
|||||||
@@ -103,7 +103,7 @@
|
|||||||
<string name="entry_user_name">Username</string>
|
<string name="entry_user_name">Username</string>
|
||||||
<string name="error_arc4">The Arcfour stream cipher is not supported.</string>
|
<string name="error_arc4">The Arcfour stream cipher is not supported.</string>
|
||||||
<string name="error_can_not_handle_uri">Could not handle this URI in KeePassDX.</string>
|
<string name="error_can_not_handle_uri">Could not handle this URI in KeePassDX.</string>
|
||||||
<string name="error_file_not_create">Could not create file:</string>
|
<string name="error_file_not_create">Could not create file</string>
|
||||||
<string name="error_invalid_db">Could not read the database.</string>
|
<string name="error_invalid_db">Could not read the database.</string>
|
||||||
<string name="error_invalid_path">Make sure the path is correct.</string>
|
<string name="error_invalid_path">Make sure the path is correct.</string>
|
||||||
<string name="error_invalid_OTP">Invalid OTP secret.</string>
|
<string name="error_invalid_OTP">Invalid OTP secret.</string>
|
||||||
@@ -257,6 +257,9 @@
|
|||||||
<string name="warning_empty_password">Continue without password unlocking protection?</string>
|
<string name="warning_empty_password">Continue without password unlocking protection?</string>
|
||||||
<string name="warning_no_encryption_key">Continue without encryption key?</string>
|
<string name="warning_no_encryption_key">Continue without encryption key?</string>
|
||||||
<string name="warning_permanently_delete_nodes">Permanently delete selected nodes?</string>
|
<string name="warning_permanently_delete_nodes">Permanently delete selected nodes?</string>
|
||||||
|
<string name="warning_file_too_big">A KeePass database is supposed to contain only small utility files (such as PGP key files).\n\nYour database may get very large and reduce performance with this upload.</string>
|
||||||
|
<string name="warning_replace_file">Uploading this file will replace the existing one.</string>
|
||||||
|
<string name="warning_sure_add_file">Add the file anyway?</string>
|
||||||
<string name="version_label">Version %1$s</string>
|
<string name="version_label">Version %1$s</string>
|
||||||
<string name="build_label">Build %1$s</string>
|
<string name="build_label">Build %1$s</string>
|
||||||
<string name="configure_biometric">Biometric prompt is supported, but not set up.</string>
|
<string name="configure_biometric">Biometric prompt is supported, but not set up.</string>
|
||||||
@@ -351,7 +354,7 @@
|
|||||||
<string name="other">Other</string>
|
<string name="other">Other</string>
|
||||||
<string name="compression">Compression</string>
|
<string name="compression">Compression</string>
|
||||||
<string name="compression_none">None</string>
|
<string name="compression_none">None</string>
|
||||||
<string name="compression_gzip">gzip</string>
|
<string name="compression_gzip">Gzip</string>
|
||||||
<string name="recycle_bin">Recycle bin</string>
|
<string name="recycle_bin">Recycle bin</string>
|
||||||
<string name="keyboard">Keyboard</string>
|
<string name="keyboard">Keyboard</string>
|
||||||
<string name="magic_keyboard_title">Magikeyboard</string>
|
<string name="magic_keyboard_title">Magikeyboard</string>
|
||||||
@@ -451,10 +454,11 @@
|
|||||||
<string name="download">Download</string>
|
<string name="download">Download</string>
|
||||||
<string name="contribute">Contribute</string>
|
<string name="contribute">Contribute</string>
|
||||||
<string name="download_attachment">Download %1$s</string>
|
<string name="download_attachment">Download %1$s</string>
|
||||||
|
<string name="upload_attachment">Upload %1$s</string>
|
||||||
<string name="download_initialization">Initializing…</string>
|
<string name="download_initialization">Initializing…</string>
|
||||||
<string name="download_progression">In progress: %1$d%%</string>
|
<string name="download_progression">In progress: %1$d%%</string>
|
||||||
<string name="download_finalization">Finalizing…</string>
|
<string name="download_finalization">Finalizing…</string>
|
||||||
<string name="download_complete">Tap to open the file.</string>
|
<string name="download_complete">Complete!</string>
|
||||||
<string name="encryption_rijndael">Rijndael (AES)</string>
|
<string name="encryption_rijndael">Rijndael (AES)</string>
|
||||||
<string name="encryption_twofish">Twofish</string>
|
<string name="encryption_twofish">Twofish</string>
|
||||||
<string name="encryption_chacha20">ChaCha20</string>
|
<string name="encryption_chacha20">ChaCha20</string>
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
*
|
* Add attachments
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
*
|
* Ajout des fichiers joints
|
||||||
Reference in New Issue
Block a user