mirror of
https://github.com/Kunzisoft/KeePassDX.git
synced 2025-12-04 15:49:33 +01:00
Merge branch 'develop' into feature/Custom_Icons
This commit is contained in:
@@ -1,5 +1,7 @@
|
||||
KeePassDX(2.9.14)
|
||||
* Dark Themes (WARNING: You must reselect your theme if upgrading from an old installation)
|
||||
* Dark Themes (WARNING: You must reselect your theme if upgrading from an old installation) #532 #714
|
||||
* Fix binary deduplication #715
|
||||
* Fix IconId #901
|
||||
|
||||
KeePassDX(2.9.13)
|
||||
* Binary image viewer #473 #749
|
||||
|
||||
@@ -11,8 +11,6 @@ import org.junit.Test
|
||||
import java.io.DataInputStream
|
||||
import java.io.File
|
||||
import java.io.InputStream
|
||||
import java.lang.Exception
|
||||
import java.security.MessageDigest
|
||||
|
||||
class BinaryAttachmentTest {
|
||||
|
||||
@@ -132,20 +130,6 @@ class BinaryAttachmentTest {
|
||||
}
|
||||
}
|
||||
|
||||
private fun BinaryAttachment.md5(): String {
|
||||
val md = MessageDigest.getInstance("MD5")
|
||||
return this.getInputDataStream(loadedKey).use { fis ->
|
||||
val buffer = ByteArray(DEFAULT_BUFFER_SIZE)
|
||||
generateSequence {
|
||||
when (val bytesRead = fis.read(buffer)) {
|
||||
-1 -> null
|
||||
else -> bytesRead
|
||||
}
|
||||
}.forEach { bytesRead -> md.update(buffer, 0, bytesRead) }
|
||||
md.digest().joinToString("") { "%02x".format(it) }
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val TEST_FILE_CACHE_A = "testA"
|
||||
private const val TEST_FILE_CACHE_B = "testB"
|
||||
|
||||
@@ -172,10 +172,14 @@ class EntryEditActivity : LockingActivity(),
|
||||
val parentIcon = mParent?.icon
|
||||
tempEntryInfo = mDatabase?.createEntry()?.getEntryInfo(mDatabase, true)
|
||||
// Set default icon
|
||||
if (parentIcon != null
|
||||
&& parentIcon.iconId != IconImage.UNKNOWN_ID
|
||||
&& parentIcon.iconId != IconImageStandard.FOLDER) {
|
||||
tempEntryInfo?.icon = parentIcon
|
||||
if (parentIcon != null) {
|
||||
if (parentIcon.custom.isUnknown
|
||||
&& parentIcon.standard.id != IconImageStandard.FOLDER_ID) {
|
||||
tempEntryInfo?.icon = IconImage(parentIcon.standard)
|
||||
}
|
||||
if (!parentIcon.custom.isUnknown) {
|
||||
tempEntryInfo?.icon = IconImage(parentIcon.custom)
|
||||
}
|
||||
}
|
||||
// Set default username
|
||||
tempEntryInfo?.username = mDatabase?.defaultUsername ?: ""
|
||||
@@ -711,7 +715,7 @@ class EntryEditActivity : LockingActivity(),
|
||||
}
|
||||
}
|
||||
|
||||
override fun iconPicked(icon: IconImageStandard) {
|
||||
override fun iconPicked(icon: IconImage) {
|
||||
entryEditFragment?.icon = icon
|
||||
}
|
||||
|
||||
|
||||
@@ -54,7 +54,7 @@ import com.kunzisoft.keepass.adapters.SearchEntryCursorAdapter
|
||||
import com.kunzisoft.keepass.autofill.AutofillComponent
|
||||
import com.kunzisoft.keepass.autofill.AutofillHelper
|
||||
import com.kunzisoft.keepass.database.element.*
|
||||
import com.kunzisoft.keepass.database.element.icon.IconImageStandard
|
||||
import com.kunzisoft.keepass.database.element.icon.IconImage
|
||||
import com.kunzisoft.keepass.database.element.node.Node
|
||||
import com.kunzisoft.keepass.database.element.node.NodeId
|
||||
import com.kunzisoft.keepass.database.element.node.Type
|
||||
@@ -1122,7 +1122,7 @@ class GroupActivity : LockingActivity(),
|
||||
}
|
||||
|
||||
// For icon in create tree dialog
|
||||
override fun iconPicked(icon: IconImageStandard) {
|
||||
override fun iconPicked(icon: IconImage) {
|
||||
(supportFragmentManager
|
||||
.findFragmentByTag(GroupEditDialogFragment.TAG_CREATE_GROUP) as GroupEditDialogFragment)
|
||||
.iconPicked(icon)
|
||||
|
||||
@@ -35,7 +35,7 @@ import com.kunzisoft.keepass.activities.dialogs.GroupEditDialogFragment.EditGrou
|
||||
import com.kunzisoft.keepass.activities.dialogs.GroupEditDialogFragment.EditGroupDialogAction.UPDATE
|
||||
import com.kunzisoft.keepass.database.element.Database
|
||||
import com.kunzisoft.keepass.database.element.DateInstant
|
||||
import com.kunzisoft.keepass.database.element.icon.IconImageStandard
|
||||
import com.kunzisoft.keepass.database.element.icon.IconImage
|
||||
import com.kunzisoft.keepass.icons.assignDatabaseIcon
|
||||
import com.kunzisoft.keepass.model.GroupInfo
|
||||
import com.kunzisoft.keepass.view.ExpirationView
|
||||
@@ -210,7 +210,7 @@ class GroupEditDialogFragment : DialogFragment(), IconPickerDialogFragment.IconP
|
||||
}
|
||||
}
|
||||
|
||||
override fun iconPicked(icon: IconImageStandard) {
|
||||
override fun iconPicked(icon: IconImage) {
|
||||
mGroupInfo.icon = icon
|
||||
assignIconView()
|
||||
}
|
||||
|
||||
@@ -28,10 +28,11 @@ import androidx.fragment.app.*
|
||||
import androidx.viewpager.widget.ViewPager
|
||||
import com.google.android.material.tabs.TabLayout
|
||||
import com.kunzisoft.keepass.R
|
||||
import com.kunzisoft.keepass.database.element.icon.IconImage
|
||||
import com.kunzisoft.keepass.database.element.icon.IconImageStandard
|
||||
|
||||
|
||||
class IconPickerDialogFragment : DialogFragment(), IconStandardFragment.IconStandardPickerListener {
|
||||
class IconPickerDialogFragment : DialogFragment() {
|
||||
|
||||
private lateinit var iconPickerPagerAdapter: IconPickerPagerAdapter
|
||||
private lateinit var viewPager: ViewPager
|
||||
@@ -78,40 +79,46 @@ class IconPickerDialogFragment : DialogFragment(), IconStandardFragment.IconStan
|
||||
val root = layoutInflater.inflate(R.layout.fragment_icon_picker, container)
|
||||
viewPager = root.findViewById(R.id.icon_picker_pager)
|
||||
tabLayout = root.findViewById(R.id.icon_picker_tabs)
|
||||
iconPickerPagerAdapter = IconPickerPagerAdapter(requireContext(), childFragmentManager)
|
||||
iconPickerPagerAdapter = IconPickerPagerAdapter(childFragmentManager) { icon ->
|
||||
iconPickerListener?.iconPicked(IconImage(icon))
|
||||
dismiss()
|
||||
}
|
||||
viewPager.adapter = iconPickerPagerAdapter
|
||||
tabLayout.setupWithViewPager(viewPager)
|
||||
|
||||
return root
|
||||
}
|
||||
|
||||
class IconPickerPagerAdapter(private val context: Context, fragmentManager: FragmentManager)
|
||||
class IconPickerPagerAdapter(fragmentManager: FragmentManager,
|
||||
iconStandardSelected: (icon: IconImageStandard) -> Unit)
|
||||
: FragmentStatePagerAdapter(fragmentManager, BEHAVIOR_SET_USER_VISIBLE_HINT) {
|
||||
|
||||
override fun getCount(): Int = 3
|
||||
private val iconStandardFragment = IconStandardFragment()
|
||||
private val iconCustomFragment = IconStandardFragment()
|
||||
|
||||
init {
|
||||
iconStandardFragment.iconStandardPickerListener = iconStandardSelected
|
||||
}
|
||||
|
||||
override fun getCount(): Int = 2
|
||||
|
||||
override fun getItem(i: Int): Fragment {
|
||||
val fragment = IconStandardFragment()
|
||||
return fragment
|
||||
return when (i) {
|
||||
1 -> iconCustomFragment
|
||||
else -> iconStandardFragment
|
||||
}
|
||||
}
|
||||
|
||||
override fun getPageTitle(position: Int): CharSequence {
|
||||
return when (position) {
|
||||
0 -> "Standard" //context.getString(R.string.iconStandard)
|
||||
1 -> "Service" //context.getString(R.string.iconStandard)
|
||||
else -> "Custom" //context.getString(R.string.iconStandard)
|
||||
1 -> "Custom" //context.getString(R.string.iconStandard)
|
||||
else -> "Standard" //context.getString(R.string.iconStandard)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
override fun iconStandardPicked(icon: IconImageStandard) {
|
||||
iconPickerListener?.iconPicked(icon)
|
||||
dismiss()
|
||||
}
|
||||
|
||||
interface IconPickerListener {
|
||||
fun iconPicked(icon: IconImageStandard)
|
||||
fun iconPicked(icon: IconImage)
|
||||
}
|
||||
|
||||
companion object {
|
||||
|
||||
@@ -25,18 +25,7 @@ class IconStandardFragment : Fragment() {
|
||||
|
||||
private lateinit var currIconGridView: GridView
|
||||
private var iconPack: IconPack? = null
|
||||
private var iconStandardPickerListener: IconPickerDialogFragment.IconPickerListener? = null
|
||||
|
||||
override fun onAttach(context: Context) {
|
||||
super.onAttach(context)
|
||||
try {
|
||||
iconStandardPickerListener = context as IconPickerDialogFragment.IconPickerListener
|
||||
} catch (e: ClassCastException) {
|
||||
// The activity doesn't implement the interface, throw exception
|
||||
throw ClassCastException(context.toString()
|
||||
+ " must implement " + IconStandardFragment::class.java.name)
|
||||
}
|
||||
}
|
||||
var iconStandardPickerListener: ((icon: IconImageStandard) -> Unit)? = null
|
||||
|
||||
override fun onDetach() {
|
||||
iconStandardPickerListener = null
|
||||
@@ -57,7 +46,7 @@ class IconStandardFragment : Fragment() {
|
||||
iconPack = IconPackChooser.getSelectedIconPack(requireContext())
|
||||
currIconGridView.adapter = IconStandardAdapter(requireActivity())
|
||||
currIconGridView.setOnItemClickListener { _, _, position, _ ->
|
||||
iconStandardPickerListener?.iconPicked(IconImageStandard(position))
|
||||
iconStandardPickerListener?.invoke(IconImageStandard(position))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -110,10 +110,10 @@ class SearchEntryCursorAdapter(private val context: Context,
|
||||
return database.createEntry()?.apply {
|
||||
database.startManageEntry(this)
|
||||
entryKDB?.let { entryKDB ->
|
||||
(cursor as EntryCursorKDB).populateEntry(entryKDB, database.iconFactory)
|
||||
(cursor as EntryCursorKDB).populateEntry(entryKDB, database.iconPool)
|
||||
}
|
||||
entryKDBX?.let { entryKDBX ->
|
||||
(cursor as EntryCursorKDBX).populateEntry(entryKDBX, database.iconFactory)
|
||||
(cursor as EntryCursorKDBX).populateEntry(entryKDBX, database.iconPool)
|
||||
}
|
||||
database.stopManageEntry(this)
|
||||
}
|
||||
|
||||
@@ -23,7 +23,8 @@ import android.database.MatrixCursor
|
||||
import android.provider.BaseColumns
|
||||
import com.kunzisoft.keepass.database.element.DateInstant
|
||||
import com.kunzisoft.keepass.database.element.entry.EntryVersioned
|
||||
import com.kunzisoft.keepass.database.element.icon.IconImageFactory
|
||||
import com.kunzisoft.keepass.database.element.icon.IconImage
|
||||
import com.kunzisoft.keepass.database.element.icon.IconPool
|
||||
import com.kunzisoft.keepass.database.element.node.NodeId
|
||||
import java.util.*
|
||||
|
||||
@@ -49,12 +50,14 @@ abstract class EntryCursor<EntryId, PwEntryV : EntryVersioned<*, EntryId, *, *>>
|
||||
|
||||
abstract fun getPwNodeId(): NodeId<EntryId>
|
||||
|
||||
open fun populateEntry(pwEntry: PwEntryV, iconFactory: IconImageFactory) {
|
||||
open fun populateEntry(pwEntry: PwEntryV, iconPool: IconPool) {
|
||||
pwEntry.nodeId = getPwNodeId()
|
||||
pwEntry.title = getString(getColumnIndex(COLUMN_INDEX_TITLE))
|
||||
|
||||
val iconStandard = iconFactory.getIcon(getInt(getColumnIndex(COLUMN_INDEX_ICON_STANDARD)))
|
||||
pwEntry.icon = iconStandard
|
||||
val iconStandard = iconPool.getIcon(getInt(getColumnIndex(COLUMN_INDEX_ICON_STANDARD)))
|
||||
val iconCustom = iconPool.getIcon(UUID(getLong(getColumnIndex(COLUMN_INDEX_ICON_CUSTOM_UUID_MOST_SIGNIFICANT_BITS)),
|
||||
getLong(getColumnIndex(COLUMN_INDEX_ICON_CUSTOM_UUID_LEAST_SIGNIFICANT_BITS))))
|
||||
pwEntry.icon = IconImage(iconStandard, iconCustom)
|
||||
|
||||
pwEntry.username = getString(getColumnIndex(COLUMN_INDEX_USERNAME))
|
||||
pwEntry.password = getString(getColumnIndex(COLUMN_INDEX_PASSWORD))
|
||||
|
||||
@@ -19,7 +19,6 @@
|
||||
*/
|
||||
package com.kunzisoft.keepass.database.cursor
|
||||
|
||||
import com.kunzisoft.keepass.database.element.database.DatabaseVersioned
|
||||
import com.kunzisoft.keepass.database.element.entry.EntryKDB
|
||||
|
||||
class EntryCursorKDB : EntryCursorUUID<EntryKDB>() {
|
||||
@@ -30,9 +29,9 @@ class EntryCursorKDB : EntryCursorUUID<EntryKDB>() {
|
||||
entry.id.mostSignificantBits,
|
||||
entry.id.leastSignificantBits,
|
||||
entry.title,
|
||||
entry.icon.iconId,
|
||||
DatabaseVersioned.UUID_ZERO.mostSignificantBits,
|
||||
DatabaseVersioned.UUID_ZERO.leastSignificantBits,
|
||||
entry.icon.standard.id,
|
||||
entry.icon.custom.uuid.mostSignificantBits,
|
||||
entry.icon.custom.uuid.leastSignificantBits,
|
||||
entry.username,
|
||||
entry.password,
|
||||
entry.url,
|
||||
|
||||
@@ -20,9 +20,7 @@
|
||||
package com.kunzisoft.keepass.database.cursor
|
||||
|
||||
import com.kunzisoft.keepass.database.element.entry.EntryKDBX
|
||||
import com.kunzisoft.keepass.database.element.icon.IconImageFactory
|
||||
|
||||
import java.util.UUID
|
||||
import com.kunzisoft.keepass.database.element.icon.IconPool
|
||||
|
||||
class EntryCursorKDBX : EntryCursorUUID<EntryKDBX>() {
|
||||
|
||||
@@ -34,9 +32,9 @@ class EntryCursorKDBX : EntryCursorUUID<EntryKDBX>() {
|
||||
entry.id.mostSignificantBits,
|
||||
entry.id.leastSignificantBits,
|
||||
entry.title,
|
||||
entry.icon.iconId,
|
||||
entry.iconCustom.uuid.mostSignificantBits,
|
||||
entry.iconCustom.uuid.leastSignificantBits,
|
||||
entry.icon.standard.id,
|
||||
entry.icon.custom.uuid.mostSignificantBits,
|
||||
entry.icon.custom.uuid.leastSignificantBits,
|
||||
entry.username,
|
||||
entry.password,
|
||||
entry.url,
|
||||
@@ -52,14 +50,8 @@ class EntryCursorKDBX : EntryCursorUUID<EntryKDBX>() {
|
||||
entryId++
|
||||
}
|
||||
|
||||
override fun populateEntry(pwEntry: EntryKDBX, iconFactory: IconImageFactory) {
|
||||
super.populateEntry(pwEntry, iconFactory)
|
||||
|
||||
// Retrieve custom icon
|
||||
val iconCustom = iconFactory.getIcon(
|
||||
UUID(getLong(getColumnIndex(COLUMN_INDEX_ICON_CUSTOM_UUID_MOST_SIGNIFICANT_BITS)),
|
||||
getLong(getColumnIndex(COLUMN_INDEX_ICON_CUSTOM_UUID_LEAST_SIGNIFICANT_BITS))))
|
||||
pwEntry.iconCustom = iconCustom
|
||||
override fun populateEntry(pwEntry: EntryKDBX, iconPool: IconPool) {
|
||||
super.populateEntry(pwEntry, iconPool)
|
||||
|
||||
// Retrieve extra fields
|
||||
if (extraFieldCursor.moveToFirst()) {
|
||||
|
||||
@@ -26,7 +26,7 @@ import android.util.Log
|
||||
import com.kunzisoft.keepass.crypto.keyDerivation.KdfEngine
|
||||
import com.kunzisoft.keepass.database.action.node.NodeHandler
|
||||
import com.kunzisoft.keepass.database.element.database.*
|
||||
import com.kunzisoft.keepass.database.element.icon.IconImageFactory
|
||||
import com.kunzisoft.keepass.database.element.icon.IconPool
|
||||
import com.kunzisoft.keepass.database.element.node.NodeId
|
||||
import com.kunzisoft.keepass.database.element.node.NodeIdInt
|
||||
import com.kunzisoft.keepass.database.element.node.NodeIdUUID
|
||||
@@ -51,7 +51,6 @@ import java.security.Key
|
||||
import java.security.SecureRandom
|
||||
import java.util.*
|
||||
import javax.crypto.KeyGenerator
|
||||
import javax.crypto.spec.IvParameterSpec
|
||||
import kotlin.collections.ArrayList
|
||||
|
||||
|
||||
@@ -92,9 +91,9 @@ class Database {
|
||||
return mDatabaseKDB?.loadedCipherKey ?: mDatabaseKDBX?.loadedCipherKey
|
||||
}
|
||||
|
||||
val iconFactory: IconImageFactory
|
||||
val iconPool: IconPool
|
||||
get() {
|
||||
return mDatabaseKDB?.iconFactory ?: mDatabaseKDBX?.iconFactory ?: IconImageFactory()
|
||||
return mDatabaseKDB?.iconPool ?: mDatabaseKDBX?.iconPool ?: IconPool()
|
||||
}
|
||||
|
||||
val allowName: Boolean
|
||||
@@ -625,6 +624,7 @@ class Database {
|
||||
}
|
||||
|
||||
fun clear(filesDirectory: File? = null) {
|
||||
iconPool.clearCache()
|
||||
drawFactory.clearCache()
|
||||
// Delete the cache of the database if present
|
||||
mDatabaseKDB?.clearCache()
|
||||
|
||||
@@ -109,7 +109,7 @@ class Entry : Node, EntryVersionedInterface<Group> {
|
||||
|
||||
override var icon: IconImage
|
||||
get() {
|
||||
return entryKDB?.icon ?: entryKDBX?.icon ?: IconImageStandard()
|
||||
return entryKDB?.icon ?: entryKDBX?.icon ?: IconImage()
|
||||
}
|
||||
set(value) {
|
||||
entryKDB?.icon = value
|
||||
@@ -257,31 +257,12 @@ class Entry : Node, EntryVersionedInterface<Group> {
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
------------
|
||||
KDB Methods
|
||||
------------
|
||||
*/
|
||||
|
||||
/**
|
||||
* If it's a node with only meta information like Meta-info SYSTEM Database Color
|
||||
* @return false by default, true if it's a meta stream
|
||||
*/
|
||||
val isMetaStream: Boolean
|
||||
get() = entryKDB?.isMetaStream ?: false
|
||||
|
||||
/*
|
||||
------------
|
||||
KDBX Methods
|
||||
------------
|
||||
*/
|
||||
|
||||
var iconCustom: IconImageCustom
|
||||
get() = entryKDBX?.iconCustom ?: IconImageCustom.UNKNOWN_ICON
|
||||
set(value) {
|
||||
entryKDBX?.iconCustom = value
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve extra fields to show, key is the label, value is the value of field (protected or not)
|
||||
* @return Map of label/value
|
||||
|
||||
@@ -26,7 +26,6 @@ import com.kunzisoft.keepass.database.element.group.GroupKDB
|
||||
import com.kunzisoft.keepass.database.element.group.GroupKDBX
|
||||
import com.kunzisoft.keepass.database.element.group.GroupVersionedInterface
|
||||
import com.kunzisoft.keepass.database.element.icon.IconImage
|
||||
import com.kunzisoft.keepass.database.element.icon.IconImageStandard
|
||||
import com.kunzisoft.keepass.database.element.node.*
|
||||
import com.kunzisoft.keepass.model.EntryInfo
|
||||
import com.kunzisoft.keepass.model.GroupInfo
|
||||
@@ -124,7 +123,7 @@ class Group : Node, GroupVersionedInterface<Group, Entry> {
|
||||
}
|
||||
|
||||
override var icon: IconImage
|
||||
get() = groupKDB?.icon ?: groupKDBX?.icon ?: IconImageStandard()
|
||||
get() = groupKDB?.icon ?: groupKDBX?.icon ?: IconImage()
|
||||
set(value) {
|
||||
groupKDB?.icon = value
|
||||
groupKDBX?.icon = value
|
||||
|
||||
@@ -28,6 +28,7 @@ import com.kunzisoft.keepass.database.element.Database
|
||||
import com.kunzisoft.keepass.stream.readAllBytes
|
||||
import org.apache.commons.io.output.CountingOutputStream
|
||||
import java.io.*
|
||||
import java.security.MessageDigest
|
||||
import java.util.zip.GZIPInputStream
|
||||
import java.util.zip.GZIPOutputStream
|
||||
import javax.crypto.Cipher
|
||||
@@ -176,6 +177,25 @@ class BinaryAttachment : Parcelable {
|
||||
throw IOException("Unable to delete temp file " + dataFile!!.absolutePath)
|
||||
}
|
||||
|
||||
/**
|
||||
* MD5 of the raw encrypted file in temp folder, only to compare binary data
|
||||
*/
|
||||
fun md5(): String {
|
||||
val md = MessageDigest.getInstance("MD5")
|
||||
if (dataFile == null)
|
||||
return ""
|
||||
return FileInputStream(dataFile).use { fis ->
|
||||
val buffer = ByteArray(DEFAULT_BUFFER_SIZE)
|
||||
generateSequence {
|
||||
when (val bytesRead = fis.read(buffer)) {
|
||||
-1 -> null
|
||||
else -> bytesRead
|
||||
}
|
||||
}.forEach { bytesRead -> md.update(buffer, 0, bytesRead) }
|
||||
md.digest().joinToString("") { "%02x".format(it) }
|
||||
}
|
||||
}
|
||||
|
||||
override fun equals(other: Any?): Boolean {
|
||||
if (this === other)
|
||||
return true
|
||||
|
||||
@@ -99,7 +99,13 @@ class BinaryPool {
|
||||
private fun orderedBinaries(): List<KeyBinary> {
|
||||
val keyBinaryList = ArrayList<KeyBinary>()
|
||||
for ((key, binary) in pool) {
|
||||
keyBinaryList.add(KeyBinary(key, binary))
|
||||
// Don't deduplicate
|
||||
val existentBinary = keyBinaryList.find { it.binary.md5() == binary.md5() }
|
||||
if (existentBinary == null) {
|
||||
keyBinaryList.add(KeyBinary(binary, key))
|
||||
} else {
|
||||
existentBinary.addKey(key)
|
||||
}
|
||||
}
|
||||
return keyBinaryList
|
||||
}
|
||||
@@ -108,7 +114,7 @@ class BinaryPool {
|
||||
* To register a binary with a ref corresponding to an ordered index
|
||||
*/
|
||||
fun getBinaryIndexFromKey(key: Int): Int? {
|
||||
val index = orderedBinaries().indexOfFirst { it.key == key }
|
||||
val index = orderedBinaries().indexOfFirst { it.keys.contains(key) }
|
||||
return if (index < 0)
|
||||
null
|
||||
else
|
||||
@@ -118,8 +124,10 @@ class BinaryPool {
|
||||
/**
|
||||
* Different from doForEach, provide an ordered index to each binary
|
||||
*/
|
||||
fun doForEachOrderedBinary(action: (index: Int, keyBinary: KeyBinary) -> Unit) {
|
||||
orderedBinaries().forEachIndexed(action)
|
||||
fun doForEachOrderedBinary(action: (index: Int, binary: BinaryAttachment) -> Unit) {
|
||||
orderedBinaries().forEachIndexed { index, keyBinary ->
|
||||
action.invoke(index, keyBinary.binary)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -149,7 +157,16 @@ class BinaryPool {
|
||||
}
|
||||
|
||||
/**
|
||||
* Utility data class to order binaries
|
||||
* Utility class to order binaries
|
||||
*/
|
||||
data class KeyBinary(val key: Int, val binary: BinaryAttachment)
|
||||
private class KeyBinary(val binary: BinaryAttachment, key: Int) {
|
||||
val keys = HashSet<Int>()
|
||||
init {
|
||||
addKey(key)
|
||||
}
|
||||
|
||||
fun addKey(key: Int) {
|
||||
keys.add(key)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,6 +24,7 @@ import com.kunzisoft.keepass.crypto.keyDerivation.KdfEngine
|
||||
import com.kunzisoft.keepass.crypto.keyDerivation.KdfFactory
|
||||
import com.kunzisoft.keepass.database.element.entry.EntryKDB
|
||||
import com.kunzisoft.keepass.database.element.group.GroupKDB
|
||||
import com.kunzisoft.keepass.database.element.icon.IconImageStandard
|
||||
import com.kunzisoft.keepass.database.element.node.NodeIdInt
|
||||
import com.kunzisoft.keepass.database.element.node.NodeIdUUID
|
||||
import com.kunzisoft.keepass.database.element.node.NodeVersioned
|
||||
@@ -175,6 +176,10 @@ class DatabaseKDB : DatabaseVersioned<Int, UUID, GroupKDB, EntryKDB>() {
|
||||
return false
|
||||
}
|
||||
|
||||
override fun getStandardIcon(iconId: Int): IconImageStandard {
|
||||
return this.iconPool.getIcon(iconId)
|
||||
}
|
||||
|
||||
override fun containsCustomData(): Boolean {
|
||||
return false
|
||||
}
|
||||
@@ -223,7 +228,7 @@ class DatabaseKDB : DatabaseVersioned<Int, UUID, GroupKDB, EntryKDB>() {
|
||||
// Create recycle bin
|
||||
val recycleBinGroup = createGroup().apply {
|
||||
title = BACKUP_FOLDER_TITLE
|
||||
icon = iconFactory.trashIcon
|
||||
icon.standard = getStandardIcon(IconImageStandard.TRASH_ID)
|
||||
}
|
||||
addGroupTo(recycleBinGroup, rootGroup)
|
||||
backupGroupId = recycleBinGroup.id
|
||||
|
||||
@@ -36,6 +36,7 @@ import com.kunzisoft.keepass.database.element.database.DatabaseKDB.Companion.BAC
|
||||
import com.kunzisoft.keepass.database.element.entry.EntryKDBX
|
||||
import com.kunzisoft.keepass.database.element.group.GroupKDBX
|
||||
import com.kunzisoft.keepass.database.element.icon.IconImageCustom
|
||||
import com.kunzisoft.keepass.database.element.icon.IconImageStandard
|
||||
import com.kunzisoft.keepass.database.element.node.NodeIdUUID
|
||||
import com.kunzisoft.keepass.database.element.node.NodeVersioned
|
||||
import com.kunzisoft.keepass.database.element.security.EncryptionAlgorithm
|
||||
@@ -105,7 +106,6 @@ class DatabaseKDBX : DatabaseVersioned<UUID, UUID, GroupKDBX, EntryKDBX> {
|
||||
var lastTopVisibleGroupUUID = UUID_ZERO
|
||||
var memoryProtection = MemoryProtectionConfig()
|
||||
val deletedObjects = ArrayList<DeletedObject>()
|
||||
val customIcons = ArrayList<IconImageCustom>()
|
||||
val customData = HashMap<String, String>()
|
||||
|
||||
var binaryPool = BinaryPool()
|
||||
@@ -129,7 +129,7 @@ class DatabaseKDBX : DatabaseVersioned<UUID, UUID, GroupKDBX, EntryKDBX> {
|
||||
kdbxVersion = FILE_VERSION_32_3
|
||||
val group = createGroup().apply {
|
||||
title = rootName
|
||||
icon = iconFactory.folderIcon
|
||||
icon.standard = getStandardIcon(IconImageStandard.FOLDER_ID)
|
||||
}
|
||||
rootGroup = group
|
||||
addGroupIndex(group)
|
||||
@@ -307,16 +307,20 @@ class DatabaseKDBX : DatabaseVersioned<UUID, UUID, GroupKDBX, EntryKDBX> {
|
||||
this.dataEngine = dataEngine
|
||||
}
|
||||
|
||||
fun getCustomIcons(): List<IconImageCustom> {
|
||||
return customIcons
|
||||
override fun getStandardIcon(iconId: Int): IconImageStandard {
|
||||
return this.iconPool.getIcon(iconId)
|
||||
}
|
||||
|
||||
fun addCustomIcon(customIcon: IconImageCustom) {
|
||||
this.customIcons.add(customIcon)
|
||||
fun getCustomIcon(iconUuid: UUID): IconImageCustom {
|
||||
return this.iconPool.getIcon(iconUuid)
|
||||
}
|
||||
|
||||
fun getCustomData(): Map<String, String> {
|
||||
return customData
|
||||
fun putCustomIcon(customIcon: IconImageCustom) {
|
||||
this.iconPool.putIcon(customIcon)
|
||||
}
|
||||
|
||||
fun containsCustomIcons(): Boolean {
|
||||
return this.iconPool.containsCustomIcons()
|
||||
}
|
||||
|
||||
fun putCustomData(label: String, value: String) {
|
||||
@@ -324,7 +328,7 @@ class DatabaseKDBX : DatabaseVersioned<UUID, UUID, GroupKDBX, EntryKDBX> {
|
||||
}
|
||||
|
||||
override fun containsCustomData(): Boolean {
|
||||
return getCustomData().isNotEmpty()
|
||||
return customData.isNotEmpty()
|
||||
}
|
||||
|
||||
@Throws(IOException::class)
|
||||
@@ -550,7 +554,7 @@ class DatabaseKDBX : DatabaseVersioned<UUID, UUID, GroupKDBX, EntryKDBX> {
|
||||
// Create recycle bin
|
||||
val recycleBinGroup = createGroup().apply {
|
||||
title = resources.getString(R.string.recycle_bin)
|
||||
icon = iconFactory.trashIcon
|
||||
icon.standard = getStandardIcon(IconImageStandard.TRASH_ID)
|
||||
enableAutoType = false
|
||||
enableSearching = false
|
||||
isExpanded = false
|
||||
|
||||
@@ -23,7 +23,8 @@ import com.kunzisoft.keepass.crypto.keyDerivation.KdfEngine
|
||||
import com.kunzisoft.keepass.database.element.Database
|
||||
import com.kunzisoft.keepass.database.element.entry.EntryVersioned
|
||||
import com.kunzisoft.keepass.database.element.group.GroupVersioned
|
||||
import com.kunzisoft.keepass.database.element.icon.IconImageFactory
|
||||
import com.kunzisoft.keepass.database.element.icon.IconImageStandard
|
||||
import com.kunzisoft.keepass.database.element.icon.IconPool
|
||||
import com.kunzisoft.keepass.database.element.node.NodeId
|
||||
import com.kunzisoft.keepass.database.element.node.Type
|
||||
import com.kunzisoft.keepass.database.element.security.EncryptionAlgorithm
|
||||
@@ -55,8 +56,7 @@ abstract class DatabaseVersioned<
|
||||
var finalKey: ByteArray? = null
|
||||
protected set
|
||||
|
||||
var iconFactory = IconImageFactory()
|
||||
protected set
|
||||
val iconPool = IconPool()
|
||||
|
||||
var changeDuplicateId = false
|
||||
|
||||
@@ -329,6 +329,8 @@ abstract class DatabaseVersioned<
|
||||
|
||||
abstract fun rootCanContainsEntry(): Boolean
|
||||
|
||||
abstract fun getStandardIcon(iconId: Int): IconImageStandard
|
||||
|
||||
abstract fun containsCustomData(): Boolean
|
||||
|
||||
fun addGroupTo(newGroup: Group, parent: Group?) {
|
||||
|
||||
@@ -21,15 +21,15 @@ package com.kunzisoft.keepass.database.element.entry
|
||||
|
||||
import android.os.Parcel
|
||||
import android.os.Parcelable
|
||||
import com.kunzisoft.keepass.database.element.Attachment
|
||||
import com.kunzisoft.keepass.database.element.database.BinaryAttachment
|
||||
import com.kunzisoft.keepass.database.element.group.GroupKDB
|
||||
import com.kunzisoft.keepass.database.element.icon.IconImageStandard.Companion.KEY_ID
|
||||
import com.kunzisoft.keepass.database.element.node.NodeId
|
||||
import com.kunzisoft.keepass.database.element.node.NodeIdUUID
|
||||
import com.kunzisoft.keepass.database.element.node.NodeKDBInterface
|
||||
import com.kunzisoft.keepass.database.element.node.Type
|
||||
import com.kunzisoft.keepass.database.element.database.BinaryAttachment
|
||||
import com.kunzisoft.keepass.database.element.Attachment
|
||||
import java.util.*
|
||||
import kotlin.collections.ArrayList
|
||||
|
||||
/**
|
||||
* Structure containing information about one entry.
|
||||
@@ -68,7 +68,8 @@ class EntryKDB : EntryVersioned<Int, UUID, GroupKDB, EntryKDB>, NodeKDBInterface
|
||||
if (username.isEmpty()) return false
|
||||
if (username != PMS_ID_USER) return false
|
||||
if (url.isEmpty()) return false
|
||||
return if (url != PMS_ID_URL) false else icon.isMetaStreamIcon
|
||||
if (url != PMS_ID_URL) return false
|
||||
return icon.standard.id == KEY_ID
|
||||
}
|
||||
|
||||
override fun initNodeId(): NodeId<UUID> {
|
||||
|
||||
@@ -26,9 +26,6 @@ import com.kunzisoft.keepass.database.element.DateInstant
|
||||
import com.kunzisoft.keepass.database.element.database.BinaryPool
|
||||
import com.kunzisoft.keepass.database.element.database.DatabaseKDBX
|
||||
import com.kunzisoft.keepass.database.element.group.GroupKDBX
|
||||
import com.kunzisoft.keepass.database.element.icon.IconImage
|
||||
import com.kunzisoft.keepass.database.element.icon.IconImageCustom
|
||||
import com.kunzisoft.keepass.database.element.icon.IconImageStandard
|
||||
import com.kunzisoft.keepass.database.element.node.NodeId
|
||||
import com.kunzisoft.keepass.database.element.node.NodeIdUUID
|
||||
import com.kunzisoft.keepass.database.element.node.NodeKDBXInterface
|
||||
@@ -48,19 +45,6 @@ class EntryKDBX : EntryVersioned<UUID, UUID, GroupKDBX, EntryKDBX>, NodeKDBXInte
|
||||
@Transient
|
||||
private var mDecodeRef = false
|
||||
|
||||
override var icon: IconImage
|
||||
get() {
|
||||
return when {
|
||||
iconCustom.isUnknown -> super.icon
|
||||
else -> iconCustom
|
||||
}
|
||||
}
|
||||
set(value) {
|
||||
if (value is IconImageStandard)
|
||||
iconCustom = IconImageCustom.UNKNOWN_ICON
|
||||
super.icon = value
|
||||
}
|
||||
var iconCustom = IconImageCustom.UNKNOWN_ICON
|
||||
var customData = LinkedHashMap<String, String>()
|
||||
var fields = LinkedHashMap<String, ProtectedString>()
|
||||
var binaries = LinkedHashMap<String, Int>() // Map<Label, PoolId>
|
||||
@@ -103,7 +87,6 @@ class EntryKDBX : EntryVersioned<UUID, UUID, GroupKDBX, EntryKDBX>, NodeKDBXInte
|
||||
constructor() : super()
|
||||
|
||||
constructor(parcel: Parcel) : super(parcel) {
|
||||
iconCustom = parcel.readParcelable(IconImageCustom::class.java.classLoader) ?: iconCustom
|
||||
usageCount = UnsignedLong(parcel.readLong())
|
||||
locationChanged = parcel.readParcelable(DateInstant::class.java.classLoader) ?: locationChanged
|
||||
customData = ParcelableUtil.readStringParcelableMap(parcel)
|
||||
@@ -121,7 +104,6 @@ class EntryKDBX : EntryVersioned<UUID, UUID, GroupKDBX, EntryKDBX>, NodeKDBXInte
|
||||
|
||||
override fun writeToParcel(dest: Parcel, flags: Int) {
|
||||
super.writeToParcel(dest, flags)
|
||||
dest.writeParcelable(iconCustom, flags)
|
||||
dest.writeLong(usageCount.toKotlinLong())
|
||||
dest.writeParcelable(locationChanged, flags)
|
||||
ParcelableUtil.writeStringParcelableMap(dest, customData)
|
||||
@@ -143,7 +125,6 @@ class EntryKDBX : EntryVersioned<UUID, UUID, GroupKDBX, EntryKDBX>, NodeKDBXInte
|
||||
*/
|
||||
fun updateWith(source: EntryKDBX, copyHistory: Boolean = true) {
|
||||
super.updateWith(source)
|
||||
iconCustom = IconImageCustom(source.iconCustom)
|
||||
usageCount = source.usageCount
|
||||
locationChanged = DateInstant(source.locationChanged)
|
||||
// Add all custom elements in map
|
||||
|
||||
@@ -21,37 +21,18 @@ package com.kunzisoft.keepass.database.element.group
|
||||
|
||||
import android.os.Parcel
|
||||
import android.os.Parcelable
|
||||
import com.kunzisoft.keepass.database.element.database.DatabaseVersioned
|
||||
import com.kunzisoft.keepass.database.element.DateInstant
|
||||
import com.kunzisoft.keepass.database.element.database.DatabaseVersioned
|
||||
import com.kunzisoft.keepass.database.element.entry.EntryKDBX
|
||||
import com.kunzisoft.keepass.database.element.icon.IconImage
|
||||
import com.kunzisoft.keepass.database.element.icon.IconImageCustom
|
||||
import com.kunzisoft.keepass.database.element.icon.IconImageStandard
|
||||
import com.kunzisoft.keepass.database.element.node.NodeId
|
||||
import com.kunzisoft.keepass.database.element.node.NodeIdUUID
|
||||
import com.kunzisoft.keepass.database.element.node.NodeKDBXInterface
|
||||
import com.kunzisoft.keepass.database.element.node.Type
|
||||
import com.kunzisoft.keepass.utils.UnsignedLong
|
||||
|
||||
import java.util.HashMap
|
||||
import java.util.UUID
|
||||
import java.util.*
|
||||
|
||||
class GroupKDBX : GroupVersioned<UUID, UUID, GroupKDBX, EntryKDBX>, NodeKDBXInterface {
|
||||
|
||||
// TODO Encapsulate
|
||||
override var icon: IconImage
|
||||
get() {
|
||||
return if (iconCustom.isUnknown)
|
||||
super.icon
|
||||
else
|
||||
iconCustom
|
||||
}
|
||||
set(value) {
|
||||
if (value is IconImageStandard)
|
||||
iconCustom = IconImageCustom.UNKNOWN_ICON
|
||||
super.icon = value
|
||||
}
|
||||
var iconCustom = IconImageCustom.UNKNOWN_ICON
|
||||
private val customData = HashMap<String, String>()
|
||||
var notes = ""
|
||||
|
||||
@@ -77,7 +58,6 @@ class GroupKDBX : GroupVersioned<UUID, UUID, GroupKDBX, EntryKDBX>, NodeKDBXInte
|
||||
constructor() : super()
|
||||
|
||||
constructor(parcel: Parcel) : super(parcel) {
|
||||
iconCustom = parcel.readParcelable(IconImageCustom::class.java.classLoader) ?: iconCustom
|
||||
usageCount = UnsignedLong(parcel.readLong())
|
||||
locationChanged = parcel.readParcelable(DateInstant::class.java.classLoader) ?: locationChanged
|
||||
// TODO customData = ParcelableUtil.readStringParcelableMap(parcel);
|
||||
@@ -101,7 +81,6 @@ class GroupKDBX : GroupVersioned<UUID, UUID, GroupKDBX, EntryKDBX>, NodeKDBXInte
|
||||
|
||||
override fun writeToParcel(dest: Parcel, flags: Int) {
|
||||
super.writeToParcel(dest, flags)
|
||||
dest.writeParcelable(iconCustom, flags)
|
||||
dest.writeLong(usageCount.toKotlinLong())
|
||||
dest.writeParcelable(locationChanged, flags)
|
||||
// TODO ParcelableUtil.writeStringParcelableMap(dest, customData);
|
||||
@@ -115,7 +94,6 @@ class GroupKDBX : GroupVersioned<UUID, UUID, GroupKDBX, EntryKDBX>, NodeKDBXInte
|
||||
|
||||
fun updateWith(source: GroupKDBX) {
|
||||
super.updateWith(source)
|
||||
iconCustom = IconImageCustom(source.iconCustom)
|
||||
usageCount = source.usageCount
|
||||
locationChanged = DateInstant(source.locationChanged)
|
||||
// Add all custom elements in map
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2019 Jeremy Jamet / Kunzisoft.
|
||||
* Copyright 2021 Jeremy Jamet / Kunzisoft.
|
||||
*
|
||||
* This file is part of KeePassDX.
|
||||
*
|
||||
@@ -19,19 +19,49 @@
|
||||
*/
|
||||
package com.kunzisoft.keepass.database.element.icon
|
||||
|
||||
import android.os.Parcel
|
||||
import android.os.Parcelable
|
||||
|
||||
abstract class IconImage protected constructor() : Parcelable {
|
||||
class IconImage() : Parcelable {
|
||||
|
||||
abstract val iconId: Int
|
||||
abstract val isUnknown: Boolean
|
||||
abstract val isMetaStreamIcon: Boolean
|
||||
var standard: IconImageStandard = IconImageStandard()
|
||||
var custom: IconImageCustom = IconImageCustom()
|
||||
|
||||
constructor(iconImageStandard: IconImageStandard) : this() {
|
||||
this.standard = iconImageStandard
|
||||
}
|
||||
|
||||
constructor(iconImageCustom: IconImageCustom) : this() {
|
||||
this.custom = iconImageCustom
|
||||
}
|
||||
|
||||
constructor(iconImageStandard: IconImageStandard,
|
||||
iconImageCustom: IconImageCustom) : this() {
|
||||
this.standard = iconImageStandard
|
||||
this.custom = iconImageCustom
|
||||
}
|
||||
|
||||
constructor(parcel: Parcel) : this() {
|
||||
standard = parcel.readParcelable(IconImageStandard::class.java.classLoader) ?: standard
|
||||
custom = parcel.readParcelable(IconImageCustom::class.java.classLoader) ?: custom
|
||||
}
|
||||
|
||||
override fun writeToParcel(parcel: Parcel, flags: Int) {
|
||||
parcel.writeParcelable(standard, flags)
|
||||
parcel.writeParcelable(custom, flags)
|
||||
}
|
||||
|
||||
override fun describeContents(): Int {
|
||||
return 0
|
||||
}
|
||||
|
||||
companion object {
|
||||
const val UNKNOWN_ID = -1
|
||||
companion object CREATOR : Parcelable.Creator<IconImage> {
|
||||
override fun createFromParcel(parcel: Parcel): IconImage {
|
||||
return IconImage(parcel)
|
||||
}
|
||||
|
||||
override fun newArray(size: Int): Array<IconImage?> {
|
||||
return arrayOfNulls(size)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2019 Brian Pellin, Jeremy Jamet / Kunzisoft.
|
||||
* Copyright 2021 Jeremy Jamet / Kunzisoft.
|
||||
*
|
||||
* This file is part of KeePassDX.
|
||||
*
|
||||
@@ -22,36 +22,34 @@ package com.kunzisoft.keepass.database.element.icon
|
||||
import android.os.Parcel
|
||||
import android.os.Parcelable
|
||||
import com.kunzisoft.keepass.database.element.database.DatabaseVersioned
|
||||
import java.util.*
|
||||
|
||||
import java.util.UUID
|
||||
class IconImageCustom() : Parcelable {
|
||||
|
||||
class IconImageCustom : IconImage {
|
||||
|
||||
val uuid: UUID
|
||||
var uuid: UUID = DatabaseVersioned.UUID_ZERO
|
||||
@Transient
|
||||
var imageData: ByteArray = ByteArray(0)
|
||||
|
||||
constructor(uuid: UUID, data: ByteArray) : super() {
|
||||
constructor(uuid: UUID, data: ByteArray) : this() {
|
||||
this.uuid = uuid
|
||||
this.imageData = data
|
||||
}
|
||||
|
||||
constructor(uuid: UUID) : super() {
|
||||
constructor(uuid: UUID) : this() {
|
||||
this.uuid = uuid
|
||||
this.imageData = ByteArray(0)
|
||||
}
|
||||
|
||||
constructor(icon: IconImageCustom) : super() {
|
||||
uuid = icon.uuid
|
||||
imageData = icon.imageData
|
||||
}
|
||||
|
||||
constructor(parcel: Parcel) {
|
||||
constructor(parcel: Parcel) : this() {
|
||||
uuid = parcel.readSerializable() as UUID
|
||||
// TODO Take too much memories
|
||||
// parcel.readByteArray(imageData);
|
||||
}
|
||||
|
||||
override fun describeContents(): Int {
|
||||
return 0
|
||||
}
|
||||
|
||||
override fun writeToParcel(dest: Parcel, flags: Int) {
|
||||
dest.writeSerializable(uuid)
|
||||
// Too big for a parcelable dest.writeByteArray(imageData);
|
||||
@@ -74,17 +72,10 @@ class IconImageCustom : IconImage {
|
||||
return uuid == other.uuid
|
||||
}
|
||||
|
||||
override val iconId: Int
|
||||
get() = UNKNOWN_ID
|
||||
|
||||
override val isUnknown: Boolean
|
||||
get() = this == UNKNOWN_ICON
|
||||
|
||||
override val isMetaStreamIcon: Boolean
|
||||
get() = false
|
||||
val isUnknown: Boolean
|
||||
get() = uuid == DatabaseVersioned.UUID_ZERO
|
||||
|
||||
companion object {
|
||||
val UNKNOWN_ICON = IconImageCustom(DatabaseVersioned.UUID_ZERO, ByteArray(0))
|
||||
|
||||
@JvmField
|
||||
val CREATOR: Parcelable.Creator<IconImageCustom> = object : Parcelable.Creator<IconImageCustom> {
|
||||
|
||||
@@ -22,32 +22,37 @@ package com.kunzisoft.keepass.database.element.icon
|
||||
import android.os.Parcel
|
||||
import android.os.Parcelable
|
||||
|
||||
class IconImageStandard : IconImage {
|
||||
class IconImageStandard : Parcelable {
|
||||
|
||||
val id: Int
|
||||
|
||||
constructor() {
|
||||
this.iconId = KEY
|
||||
this.id = KEY_ID
|
||||
}
|
||||
|
||||
constructor(iconId: Int) {
|
||||
this.iconId = iconId
|
||||
}
|
||||
|
||||
constructor(icon: IconImageStandard) {
|
||||
this.iconId = icon.iconId
|
||||
if (iconId < MIN_ID || iconId > MAX_ID)
|
||||
this.id = KEY_ID
|
||||
else
|
||||
this.id = iconId
|
||||
}
|
||||
|
||||
constructor(parcel: Parcel) {
|
||||
iconId = parcel.readInt()
|
||||
id = parcel.readInt()
|
||||
}
|
||||
|
||||
override fun describeContents(): Int {
|
||||
return 0
|
||||
}
|
||||
|
||||
override fun writeToParcel(dest: Parcel, flags: Int) {
|
||||
dest.writeInt(iconId)
|
||||
dest.writeInt(id)
|
||||
}
|
||||
|
||||
override fun hashCode(): Int {
|
||||
val prime = 31
|
||||
var result = 1
|
||||
result = prime * result + iconId
|
||||
result = prime * result + id
|
||||
return result
|
||||
}
|
||||
|
||||
@@ -59,22 +64,16 @@ class IconImageStandard : IconImage {
|
||||
if (other !is IconImageStandard) {
|
||||
return false
|
||||
}
|
||||
return iconId == other.iconId
|
||||
return id == other.id
|
||||
}
|
||||
|
||||
override val iconId: Int
|
||||
|
||||
override val isUnknown: Boolean
|
||||
get() = iconId == UNKNOWN_ID
|
||||
|
||||
override val isMetaStreamIcon: Boolean
|
||||
get() = iconId == 0
|
||||
|
||||
companion object {
|
||||
|
||||
const val KEY = 0
|
||||
const val TRASH = 43
|
||||
const val FOLDER = 48
|
||||
const val KEY_ID = 0
|
||||
const val TRASH_ID = 43
|
||||
const val FOLDER_ID = 48
|
||||
const val MIN_ID = 0
|
||||
const val MAX_ID = 48
|
||||
|
||||
@JvmField
|
||||
val CREATOR: Parcelable.Creator<IconImageStandard> = object : Parcelable.Creator<IconImageStandard> {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2019 Brian Pellin, Jeremy Jamet / Kunzisoft.
|
||||
* Copyright 2021 Jeremy Jamet / Kunzisoft.
|
||||
*
|
||||
* This file is part of KeePassDX.
|
||||
*
|
||||
@@ -19,49 +19,26 @@
|
||||
*/
|
||||
package com.kunzisoft.keepass.database.element.icon
|
||||
|
||||
import org.apache.commons.collections.map.AbstractReferenceMap
|
||||
import org.apache.commons.collections.map.ReferenceMap
|
||||
|
||||
import java.util.UUID
|
||||
|
||||
class IconImageFactory {
|
||||
/** customIconMap
|
||||
* Cache for icon drawable.
|
||||
* Keys: Integer, Values: IconImageStandard
|
||||
*/
|
||||
private val cache = ReferenceMap(AbstractReferenceMap.HARD, AbstractReferenceMap.WEAK)
|
||||
class IconPool {
|
||||
|
||||
/** standardIconMap
|
||||
* Cache for icon drawable.
|
||||
* Keys: UUID, Values: IconImageCustom
|
||||
*/
|
||||
private val customCache = ReferenceMap(AbstractReferenceMap.HARD, AbstractReferenceMap.WEAK)
|
||||
|
||||
val unknownIcon: IconImageStandard
|
||||
get() = getIcon(IconImage.UNKNOWN_ID)
|
||||
|
||||
val keyIcon: IconImageStandard
|
||||
get() = getIcon(IconImageStandard.KEY)
|
||||
|
||||
val trashIcon: IconImageStandard
|
||||
get() = getIcon(IconImageStandard.TRASH)
|
||||
|
||||
val folderIcon: IconImageStandard
|
||||
get() = getIcon(IconImageStandard.FOLDER)
|
||||
private val standardCache = HashMap<Int, IconImageStandard?>()
|
||||
private val customCache = HashMap<UUID, IconImageCustom?>()
|
||||
|
||||
fun getIcon(iconId: Int): IconImageStandard {
|
||||
var icon: IconImageStandard? = cache[iconId] as IconImageStandard?
|
||||
var icon: IconImageStandard? = standardCache[iconId]
|
||||
|
||||
if (icon == null) {
|
||||
icon = IconImageStandard(iconId)
|
||||
cache[iconId] = icon
|
||||
standardCache[iconId] = icon
|
||||
}
|
||||
|
||||
return icon
|
||||
}
|
||||
|
||||
fun getIcon(iconUuid: UUID): IconImageCustom {
|
||||
var icon: IconImageCustom? = customCache[iconUuid] as IconImageCustom?
|
||||
var icon: IconImageCustom? = customCache[iconUuid]
|
||||
|
||||
if (icon == null) {
|
||||
icon = IconImageCustom(iconUuid)
|
||||
@@ -71,7 +48,25 @@ class IconImageFactory {
|
||||
return icon
|
||||
}
|
||||
|
||||
fun put(icon: IconImageCustom) {
|
||||
fun putIcon(icon: IconImageCustom) {
|
||||
customCache[icon.uuid] = icon
|
||||
}
|
||||
|
||||
fun containsCustomIcons(): Boolean {
|
||||
return customCache.isNotEmpty()
|
||||
}
|
||||
|
||||
fun doForEachCustomIcon(action: (customIcon: IconImageCustom) -> Unit) {
|
||||
for ((_, customIcon) in customCache) {
|
||||
action.invoke(customIcon!!)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear the cache of icons
|
||||
*/
|
||||
fun clearCache() {
|
||||
standardCache.clear()
|
||||
customCache.clear()
|
||||
}
|
||||
}
|
||||
@@ -22,11 +22,10 @@ package com.kunzisoft.keepass.database.element.node
|
||||
|
||||
import android.os.Parcel
|
||||
import android.os.Parcelable
|
||||
import com.kunzisoft.keepass.database.element.*
|
||||
import com.kunzisoft.keepass.database.element.DateInstant
|
||||
import com.kunzisoft.keepass.database.element.entry.EntryVersionedInterface
|
||||
import com.kunzisoft.keepass.database.element.group.GroupVersionedInterface
|
||||
import com.kunzisoft.keepass.database.element.icon.IconImage
|
||||
import com.kunzisoft.keepass.database.element.icon.IconImageStandard
|
||||
import org.joda.time.LocalDateTime
|
||||
|
||||
/**
|
||||
@@ -88,7 +87,7 @@ abstract class NodeVersioned<IdType, Parent : GroupVersionedInterface<Parent, En
|
||||
|
||||
final override var parent: Parent? = null
|
||||
|
||||
override var icon: IconImage = IconImageStandard()
|
||||
final override var icon: IconImage = IconImage()
|
||||
|
||||
final override var creationTime: DateInstant = DateInstant()
|
||||
|
||||
|
||||
@@ -231,7 +231,7 @@ class DatabaseInputKDB(cacheDirectory: File)
|
||||
if (iconId == -1) {
|
||||
iconId = 0
|
||||
}
|
||||
entry.icon = mDatabase.iconFactory.getIcon(iconId)
|
||||
entry.icon.standard = mDatabase.getStandardIcon(iconId)
|
||||
}
|
||||
}
|
||||
0x0004 -> {
|
||||
@@ -260,7 +260,7 @@ class DatabaseInputKDB(cacheDirectory: File)
|
||||
}
|
||||
0x0007 -> {
|
||||
newGroup?.let { group ->
|
||||
group.icon = mDatabase.iconFactory.getIcon(cipherInputStream.readBytes4ToUInt().toKotlinInt())
|
||||
group.icon.standard = mDatabase.getStandardIcon(cipherInputStream.readBytes4ToUInt().toKotlinInt())
|
||||
} ?:
|
||||
newEntry?.let { entry ->
|
||||
entry.password = cipherInputStream.readBytesToString(fieldSize,false)
|
||||
|
||||
@@ -507,9 +507,9 @@ class DatabaseInputKDBX(cacheDirectory: File)
|
||||
} else if (name.equals(DatabaseKDBXXML.ElemNotes, ignoreCase = true)) {
|
||||
ctxGroup?.notes = readString(xpp)
|
||||
} else if (name.equals(DatabaseKDBXXML.ElemIcon, ignoreCase = true)) {
|
||||
ctxGroup?.icon = mDatabase.iconFactory.getIcon(readUInt(xpp, UnsignedInt(0)).toKotlinInt())
|
||||
ctxGroup?.icon?.standard = mDatabase.getStandardIcon(readUInt(xpp, UnsignedInt(0)).toKotlinInt())
|
||||
} else if (name.equals(DatabaseKDBXXML.ElemCustomIconID, ignoreCase = true)) {
|
||||
ctxGroup?.iconCustom = mDatabase.iconFactory.getIcon(readUuid(xpp))
|
||||
ctxGroup?.icon?.custom = mDatabase.getCustomIcon(readUuid(xpp))
|
||||
} else if (name.equals(DatabaseKDBXXML.ElemTimes, ignoreCase = true)) {
|
||||
return switchContext(ctx, KdbContext.GroupTimes, xpp)
|
||||
} else if (name.equals(DatabaseKDBXXML.ElemIsExpanded, ignoreCase = true)) {
|
||||
@@ -561,9 +561,9 @@ class DatabaseInputKDBX(cacheDirectory: File)
|
||||
KdbContext.Entry -> if (name.equals(DatabaseKDBXXML.ElemUuid, ignoreCase = true)) {
|
||||
ctxEntry?.nodeId = NodeIdUUID(readUuid(xpp))
|
||||
} else if (name.equals(DatabaseKDBXXML.ElemIcon, ignoreCase = true)) {
|
||||
ctxEntry?.icon = mDatabase.iconFactory.getIcon(readUInt(xpp, UnsignedInt(0)).toKotlinInt())
|
||||
ctxEntry?.icon?.standard = mDatabase.getStandardIcon(readUInt(xpp, UnsignedInt(0)).toKotlinInt())
|
||||
} else if (name.equals(DatabaseKDBXXML.ElemCustomIconID, ignoreCase = true)) {
|
||||
ctxEntry?.iconCustom = mDatabase.iconFactory.getIcon(readUuid(xpp))
|
||||
ctxEntry?.icon?.custom = mDatabase.getCustomIcon(readUuid(xpp))
|
||||
} else if (name.equals(DatabaseKDBXXML.ElemFgColor, ignoreCase = true)) {
|
||||
ctxEntry?.foregroundColor = readString(xpp)
|
||||
} else if (name.equals(DatabaseKDBXXML.ElemBgColor, ignoreCase = true)) {
|
||||
@@ -706,8 +706,7 @@ class DatabaseInputKDBX(cacheDirectory: File)
|
||||
} else if (ctx == KdbContext.CustomIcon && name.equals(DatabaseKDBXXML.ElemCustomIconItem, ignoreCase = true)) {
|
||||
if (customIconID != DatabaseVersioned.UUID_ZERO && customIconData != null) {
|
||||
val icon = IconImageCustom(customIconID, customIconData!!)
|
||||
mDatabase.addCustomIcon(icon)
|
||||
mDatabase.iconFactory.put(icon)
|
||||
mDatabase.putCustomIcon(icon)
|
||||
}
|
||||
|
||||
customIconID = DatabaseVersioned.UUID_ZERO
|
||||
|
||||
@@ -35,7 +35,6 @@ import com.kunzisoft.keepass.database.element.database.DatabaseKDBX.Companion.BA
|
||||
import com.kunzisoft.keepass.database.element.entry.AutoType
|
||||
import com.kunzisoft.keepass.database.element.entry.EntryKDBX
|
||||
import com.kunzisoft.keepass.database.element.group.GroupKDBX
|
||||
import com.kunzisoft.keepass.database.element.icon.IconImageCustom
|
||||
import com.kunzisoft.keepass.database.element.node.NodeKDBXInterface
|
||||
import com.kunzisoft.keepass.database.element.security.MemoryProtectionConfig
|
||||
import com.kunzisoft.keepass.database.element.security.ProtectedString
|
||||
@@ -137,24 +136,23 @@ class DatabaseOutputKDBX(private val mDatabaseKDBX: DatabaseKDBX,
|
||||
dataOutputStream.writeInt(streamKeySize)
|
||||
dataOutputStream.write(header.innerRandomStreamKey)
|
||||
|
||||
database.binaryPool.doForEachOrderedBinary { _, keyBinary ->
|
||||
val protectedBinary = keyBinary.binary
|
||||
val binaryCipherKey = database.loadedCipherKey
|
||||
?: throw IOException("Unable to retrieve cipher key to write binaries")
|
||||
database.binaryPool.doForEachOrderedBinary { _, binary ->
|
||||
// Force decompression to add binary in header
|
||||
protectedBinary.decompress(binaryCipherKey)
|
||||
binary.decompress(binaryCipherKey)
|
||||
// Write type binary
|
||||
dataOutputStream.writeByte(DatabaseHeaderKDBX.PwDbInnerHeaderV4Fields.Binary)
|
||||
// Write size
|
||||
dataOutputStream.writeUInt(UnsignedInt.fromKotlinLong(protectedBinary.length + 1))
|
||||
dataOutputStream.writeUInt(UnsignedInt.fromKotlinLong(binary.length + 1))
|
||||
// Write protected flag
|
||||
var flag = DatabaseHeaderKDBX.KdbxBinaryFlags.None
|
||||
if (protectedBinary.isProtected) {
|
||||
if (binary.isProtected) {
|
||||
flag = flag or DatabaseHeaderKDBX.KdbxBinaryFlags.Protected
|
||||
}
|
||||
dataOutputStream.writeByte(flag)
|
||||
|
||||
protectedBinary.getInputDataStream(binaryCipherKey).use { inputStream ->
|
||||
binary.getInputDataStream(binaryCipherKey).use { inputStream ->
|
||||
inputStream.readAllBytes { buffer ->
|
||||
dataOutputStream.write(buffer)
|
||||
}
|
||||
@@ -363,10 +361,10 @@ class DatabaseOutputKDBX(private val mDatabaseKDBX: DatabaseKDBX,
|
||||
writeUuid(DatabaseKDBXXML.ElemUuid, group.id)
|
||||
writeObject(DatabaseKDBXXML.ElemName, group.title)
|
||||
writeObject(DatabaseKDBXXML.ElemNotes, group.notes)
|
||||
writeObject(DatabaseKDBXXML.ElemIcon, group.icon.iconId.toLong())
|
||||
writeObject(DatabaseKDBXXML.ElemIcon, group.icon.standard.id.toLong())
|
||||
|
||||
if (group.iconCustom != IconImageCustom.UNKNOWN_ICON) {
|
||||
writeUuid(DatabaseKDBXXML.ElemCustomIconID, group.iconCustom.uuid)
|
||||
if (!group.icon.custom.isUnknown) {
|
||||
writeUuid(DatabaseKDBXXML.ElemCustomIconID, group.icon.custom.uuid)
|
||||
}
|
||||
|
||||
writeTimes(group)
|
||||
@@ -388,10 +386,10 @@ class DatabaseOutputKDBX(private val mDatabaseKDBX: DatabaseKDBX,
|
||||
xml.startTag(null, DatabaseKDBXXML.ElemEntry)
|
||||
|
||||
writeUuid(DatabaseKDBXXML.ElemUuid, entry.id)
|
||||
writeObject(DatabaseKDBXXML.ElemIcon, entry.icon.iconId.toLong())
|
||||
writeObject(DatabaseKDBXXML.ElemIcon, entry.icon.standard.id.toLong())
|
||||
|
||||
if (entry.iconCustom != IconImageCustom.UNKNOWN_ICON) {
|
||||
writeUuid(DatabaseKDBXXML.ElemCustomIconID, entry.iconCustom.uuid)
|
||||
if (!entry.icon.custom.isUnknown) {
|
||||
writeUuid(DatabaseKDBXXML.ElemCustomIconID, entry.icon.custom.uuid)
|
||||
}
|
||||
|
||||
writeObject(DatabaseKDBXXML.ElemFgColor, entry.foregroundColor)
|
||||
@@ -499,10 +497,9 @@ class DatabaseOutputKDBX(private val mDatabaseKDBX: DatabaseKDBX,
|
||||
xml.startTag(null, DatabaseKDBXXML.ElemBinaries)
|
||||
|
||||
// Use indexes because necessarily (binary header ref is the order)
|
||||
mDatabaseKDBX.binaryPool.doForEachOrderedBinary { index, keyBinary ->
|
||||
mDatabaseKDBX.binaryPool.doForEachOrderedBinary { index, binary ->
|
||||
xml.startTag(null, DatabaseKDBXXML.ElemBinary)
|
||||
xml.attribute(null, DatabaseKDBXXML.AttrId, index.toString())
|
||||
val binary = keyBinary.binary
|
||||
if (binary.length > 0) {
|
||||
if (binary.isCompressed) {
|
||||
xml.attribute(null, DatabaseKDBXXML.AttrCompressed, DatabaseKDBXXML.ValTrue)
|
||||
@@ -700,16 +697,15 @@ class DatabaseOutputKDBX(private val mDatabaseKDBX: DatabaseKDBX,
|
||||
|
||||
@Throws(IllegalArgumentException::class, IllegalStateException::class, IOException::class)
|
||||
private fun writeCustomIconList() {
|
||||
val customIcons = mDatabaseKDBX.customIcons
|
||||
if (customIcons.size == 0) return
|
||||
if (!mDatabaseKDBX.containsCustomIcons()) return
|
||||
|
||||
xml.startTag(null, DatabaseKDBXXML.ElemCustomIcons)
|
||||
|
||||
for (icon in customIcons) {
|
||||
mDatabaseKDBX.iconPool.doForEachCustomIcon { customIcon ->
|
||||
xml.startTag(null, DatabaseKDBXXML.ElemCustomIconItem)
|
||||
|
||||
writeUuid(DatabaseKDBXXML.ElemCustomIconItemID, icon.uuid)
|
||||
writeObject(DatabaseKDBXXML.ElemCustomIconItemData, String(Base64.encode(icon.imageData, BASE_64_FLAG)))
|
||||
writeUuid(DatabaseKDBXXML.ElemCustomIconItemID, customIcon.uuid)
|
||||
writeObject(DatabaseKDBXXML.ElemCustomIconItemData, String(Base64.encode(customIcon.imageData, BASE_64_FLAG)))
|
||||
|
||||
xml.endTag(null, DatabaseKDBXXML.ElemCustomIconItem)
|
||||
}
|
||||
|
||||
@@ -53,7 +53,7 @@ class EntryOutputKDB(private val mEntry: EntryKDB,
|
||||
// Image ID
|
||||
mOutputStream.write(IMAGEID_FIELD_TYPE)
|
||||
mOutputStream.write(IMAGEID_FIELD_SIZE)
|
||||
mOutputStream.write(uIntTo4Bytes(UnsignedInt(mEntry.icon.iconId)))
|
||||
mOutputStream.write(uIntTo4Bytes(UnsignedInt(mEntry.icon.standard.id)))
|
||||
|
||||
// Title
|
||||
//byte[] title = mEntry.title.getBytes("UTF-8");
|
||||
|
||||
@@ -72,7 +72,7 @@ class GroupOutputKDB(private val mGroup: GroupKDB,
|
||||
// Image ID
|
||||
mOutputStream.write(IMAGEID_FIELD_TYPE)
|
||||
mOutputStream.write(IMAGEID_FIELD_SIZE)
|
||||
mOutputStream.write(uIntTo4Bytes(UnsignedInt(mGroup.icon.iconId)))
|
||||
mOutputStream.write(uIntTo4Bytes(UnsignedInt(mGroup.icon.standard.id)))
|
||||
|
||||
// Level
|
||||
mOutputStream.write(LEVEL_FIELD_TYPE)
|
||||
|
||||
@@ -38,7 +38,6 @@ import androidx.core.widget.ImageViewCompat
|
||||
import com.kunzisoft.keepass.R
|
||||
import com.kunzisoft.keepass.database.element.icon.IconImage
|
||||
import com.kunzisoft.keepass.database.element.icon.IconImageCustom
|
||||
import com.kunzisoft.keepass.database.element.icon.IconImageStandard
|
||||
import org.apache.commons.collections.map.AbstractReferenceMap
|
||||
import org.apache.commons.collections.map.ReferenceMap
|
||||
|
||||
@@ -110,19 +109,14 @@ class IconDrawableFactory {
|
||||
* Get the [SuperDrawable] [icon] (from cache, or build it and add it to the cache if not exists yet), then [tint] it with [tintColor] if needed
|
||||
*/
|
||||
fun getIconSuperDrawable(context: Context, icon: IconImage, width: Int, tint: Boolean = false, tintColor: Int = Color.WHITE): SuperDrawable {
|
||||
return when (icon) {
|
||||
is IconImageStandard -> {
|
||||
val resId = IconPackChooser.getSelectedIconPack(context)?.iconToResId(icon.iconId) ?: R.drawable.ic_blank_32dp
|
||||
return if (!icon.custom.isUnknown) {
|
||||
SuperDrawable(getIconDrawable(context.resources, icon.custom))
|
||||
} else IconPackChooser.getSelectedIconPack(context)?.iconToResId(icon.standard.id)?.let { resId ->
|
||||
getIconSuperDrawable(context, resId, width, tint, tintColor)
|
||||
}
|
||||
is IconImageCustom -> {
|
||||
SuperDrawable(getIconDrawable(context.resources, icon))
|
||||
}
|
||||
else -> {
|
||||
} ?: run {
|
||||
SuperDrawable(PatternIcon(context.resources).blankDrawable)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the [SuperDrawable] IconImageStandard from [iconId] (cache, or build it and add it to the cache if not exists yet)
|
||||
|
||||
@@ -3,14 +3,14 @@ package com.kunzisoft.keepass.model
|
||||
import android.os.Parcel
|
||||
import android.os.Parcelable
|
||||
import com.kunzisoft.keepass.database.element.icon.IconImageStandard
|
||||
import com.kunzisoft.keepass.database.element.icon.IconImageStandard.Companion.FOLDER
|
||||
import com.kunzisoft.keepass.database.element.icon.IconImageStandard.Companion.FOLDER_ID
|
||||
|
||||
class GroupInfo : NodeInfo {
|
||||
|
||||
var notes: String? = null
|
||||
|
||||
init {
|
||||
icon = IconImageStandard(FOLDER)
|
||||
icon.standard = IconImageStandard(FOLDER_ID)
|
||||
}
|
||||
|
||||
constructor(): super()
|
||||
|
||||
@@ -4,12 +4,11 @@ import android.os.Parcel
|
||||
import android.os.Parcelable
|
||||
import com.kunzisoft.keepass.database.element.DateInstant
|
||||
import com.kunzisoft.keepass.database.element.icon.IconImage
|
||||
import com.kunzisoft.keepass.database.element.icon.IconImageStandard
|
||||
|
||||
open class NodeInfo() : Parcelable {
|
||||
|
||||
var title: String = ""
|
||||
var icon: IconImage = IconImageStandard()
|
||||
var icon: IconImage = IconImage()
|
||||
var creationTime: DateInstant = DateInstant()
|
||||
var lastModificationTime: DateInstant = DateInstant()
|
||||
var expires: Boolean = false
|
||||
|
||||
@@ -1 +1,3 @@
|
||||
* Dark Themes (WARNING: You must reselect your theme if upgrading from an old installation)
|
||||
* Dark Themes (WARNING: You must reselect your theme if upgrading from an old installation) #532 #714
|
||||
* Fix binary deduplication #715
|
||||
* Fix IconId #901
|
||||
@@ -1 +1,3 @@
|
||||
* Thèmes sombres (ATTENTION: Vous devez reselectionner votre theme si mise à niveau d'une ancienne installation)
|
||||
* Thèmes sombres (ATTENTION: Vous devez reselectionner votre theme si mise à niveau d'une ancienne installation) #532 #714
|
||||
* Correction de la duplication des binaires #715
|
||||
* Correction IconId #901
|
||||
Reference in New Issue
Block a user