Upload custom attachment and fix warning

This commit is contained in:
J-Jamet
2021-03-01 15:50:11 +01:00
parent 1a7b32e6d1
commit 5aa3f79616
34 changed files with 405 additions and 267 deletions

View File

@@ -3,7 +3,7 @@ package com.kunzisoft.keepass.tests.stream
import android.content.Context
import androidx.test.platform.app.InstrumentationRegistry
import com.kunzisoft.keepass.database.element.Database
import com.kunzisoft.keepass.database.element.database.BinaryAttachment
import com.kunzisoft.keepass.database.element.database.BinaryFile
import com.kunzisoft.keepass.stream.readAllBytes
import com.kunzisoft.keepass.utils.UriUtil
import junit.framework.TestCase.assertEquals
@@ -12,7 +12,7 @@ import java.io.DataInputStream
import java.io.File
import java.io.InputStream
class BinaryAttachmentTest {
class BinaryFileTest {
private val context: Context by lazy {
InstrumentationRegistry.getInstrumentation().context
@@ -25,9 +25,9 @@ class BinaryAttachmentTest {
private val loadedKey = Database.LoadedKey.generateNewCipherKey()
private fun saveBinary(asset: String, binaryAttachment: BinaryAttachment) {
private fun saveBinary(asset: String, binaryFile: BinaryFile) {
context.assets.open(asset).use { assetInputStream ->
binaryAttachment.getOutputDataStream(loadedKey).use { binaryOutputStream ->
binaryFile.getOutputDataStream(loadedKey).use { binaryOutputStream ->
assetInputStream.readAllBytes(DEFAULT_BUFFER_SIZE) { buffer ->
binaryOutputStream.write(buffer)
}
@@ -37,8 +37,8 @@ class BinaryAttachmentTest {
@Test
fun testSaveTextInCache() {
val binaryA = BinaryAttachment(fileA)
val binaryB = BinaryAttachment(fileB)
val binaryA = BinaryFile(fileA)
val binaryB = BinaryFile(fileB)
saveBinary(TEST_TEXT_ASSET, binaryA)
saveBinary(TEST_TEXT_ASSET, binaryB)
assertEquals("Save text binary length failed.", binaryA.length, binaryB.length)
@@ -47,8 +47,8 @@ class BinaryAttachmentTest {
@Test
fun testSaveImageInCache() {
val binaryA = BinaryAttachment(fileA)
val binaryB = BinaryAttachment(fileB)
val binaryA = BinaryFile(fileA)
val binaryB = BinaryFile(fileB)
saveBinary(TEST_IMAGE_ASSET, binaryA)
saveBinary(TEST_IMAGE_ASSET, binaryB)
assertEquals("Save image binary length failed.", binaryA.length, binaryB.length)
@@ -57,9 +57,9 @@ class BinaryAttachmentTest {
@Test
fun testCompressText() {
val binaryA = BinaryAttachment(fileA)
val binaryB = BinaryAttachment(fileB)
val binaryC = BinaryAttachment(fileC)
val binaryA = BinaryFile(fileA)
val binaryB = BinaryFile(fileB)
val binaryC = BinaryFile(fileC)
saveBinary(TEST_TEXT_ASSET, binaryA)
saveBinary(TEST_TEXT_ASSET, binaryB)
saveBinary(TEST_TEXT_ASSET, binaryC)
@@ -74,9 +74,9 @@ class BinaryAttachmentTest {
@Test
fun testCompressImage() {
val binaryA = BinaryAttachment(fileA)
var binaryB = BinaryAttachment(fileB)
val binaryC = BinaryAttachment(fileC)
val binaryA = BinaryFile(fileA)
var binaryB = BinaryFile(fileB)
val binaryC = BinaryFile(fileC)
saveBinary(TEST_IMAGE_ASSET, binaryA)
saveBinary(TEST_IMAGE_ASSET, binaryB)
saveBinary(TEST_IMAGE_ASSET, binaryC)
@@ -84,7 +84,7 @@ class BinaryAttachmentTest {
binaryB.compress(loadedKey)
assertEquals("Compress image length failed.", binaryA.length, binaryA.length)
assertEquals("Compress image failed.", binaryA.md5(), binaryA.md5())
binaryB = BinaryAttachment(fileB, true)
binaryB = BinaryFile(fileB, true)
binaryB.decompress(loadedKey)
assertEquals("Decompress image length failed.", binaryB.length, binaryC.length)
assertEquals("Decompress image failed.", binaryB.md5(), binaryC.md5())
@@ -92,7 +92,7 @@ class BinaryAttachmentTest {
@Test
fun testReadText() {
val binaryA = BinaryAttachment(fileA)
val binaryA = BinaryFile(fileA)
saveBinary(TEST_TEXT_ASSET, binaryA)
assert(streamAreEquals(context.assets.open(TEST_TEXT_ASSET),
binaryA.getInputDataStream(loadedKey)))
@@ -100,7 +100,7 @@ class BinaryAttachmentTest {
@Test
fun testReadImage() {
val binaryA = BinaryAttachment(fileA)
val binaryA = BinaryFile(fileA)
saveBinary(TEST_IMAGE_ASSET, binaryA)
assert(streamAreEquals(context.assets.open(TEST_IMAGE_ASSET),
binaryA.getInputDataStream(loadedKey)))

View File

@@ -484,7 +484,7 @@ class EntryEditActivity : LockingActivity(),
private fun buildNewAttachment(attachmentToUploadUri: Uri, fileName: String) {
val compression = mDatabase?.compressionForNewEntry() ?: false
mDatabase?.buildNewAttachment(UriUtil.getBinaryDir(this), compression)?.let { binaryAttachment ->
mDatabase?.buildNewBinaryAttachment(UriUtil.getBinaryDir(this), compression)?.let { binaryAttachment ->
val entryAttachment = Attachment(fileName, binaryAttachment)
// Ask to replace the current attachment
if ((mDatabase?.allowMultipleAttachments != true && entryEditFragment?.containsAttachment() == true) ||
@@ -506,7 +506,6 @@ class EntryEditActivity : LockingActivity(),
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) {
@@ -572,7 +571,7 @@ class EntryEditActivity : LockingActivity(),
// Delete temp attachment if not used
mTempAttachments.forEach { tempAttachmentState ->
val tempAttachment = tempAttachmentState.attachment
mDatabase?.binaryPool?.let { binaryPool ->
mDatabase?.attachmentPool?.let { binaryPool ->
if (!newEntry.getAttachments(binaryPool).contains(tempAttachment)) {
mDatabase?.removeAttachmentIfNotUsed(tempAttachment)
}

View File

@@ -20,8 +20,11 @@
package com.kunzisoft.keepass.activities
import android.app.Activity
import android.content.ContentResolver
import android.content.Intent
import android.net.Uri
import android.os.Bundle
import android.util.Log
import android.view.MenuItem
import android.view.View
import android.view.ViewGroup
@@ -34,8 +37,10 @@ import com.kunzisoft.keepass.activities.helpers.SelectFileHelper
import com.kunzisoft.keepass.activities.lock.LockingActivity
import com.kunzisoft.keepass.activities.lock.resetAppTimeoutWhenViewFocusedOrChanged
import com.kunzisoft.keepass.database.element.Database
import com.kunzisoft.keepass.database.element.database.BinaryFile
import com.kunzisoft.keepass.database.element.icon.IconImage
import com.kunzisoft.keepass.settings.PreferencesUtil
import com.kunzisoft.keepass.stream.readAllBytes
import com.kunzisoft.keepass.utils.UriUtil
import com.kunzisoft.keepass.view.updateLockPaddingLeft
import com.kunzisoft.keepass.viewmodels.IconPickerViewModel
@@ -132,16 +137,54 @@ class IconPickerActivity : LockingActivity() {
return super.onOptionsItemSelected(item)
}
private fun buildNewIcon(iconToUploadUri: Uri) {
mDatabase?.buildNewCustomIcon(UriUtil.getBinaryDir(this))?.let { customIcon ->
uploadIconToDatabase(iconToUploadUri, customIcon.binaryFile, contentResolver,
{
}
)
iconPickerViewModel.addCustomIcon(customIcon)
}
}
// TODO Encapsulate
fun uploadIconToDatabase(iconToUploadUri: Uri,
binaryFile: BinaryFile,
contentResolver: ContentResolver,
update: ((percent: Int)->Unit)? = null,
canceled: ()-> Boolean = { false },
bufferSize: Int = DEFAULT_BUFFER_SIZE,) {
var dataUploaded = 0L
val fileSize = contentResolver.openFileDescriptor(iconToUploadUri, "r")?.statSize ?: 0
UriUtil.getUriInputStream(contentResolver, iconToUploadUri)?.use { inputStream ->
Database.getInstance().loadedCipherKey?.let { binaryCipherKey ->
binaryFile.getGzipOutputDataStream(binaryCipherKey).use { outputStream ->
inputStream.readAllBytes(bufferSize, canceled) { buffer ->
outputStream.write(buffer)
dataUploaded += buffer.size
try {
val percentDownload = (100 * dataUploaded / fileSize).toInt()
update?.invoke(percentDownload)
} catch (e: Exception) {
Log.e("", "", e)
}
}
}
}
}
}
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
mSelectFileHelper?.onActivityResultCallback(requestCode, resultCode, data) { uri ->
uri?.let { iconToUploadUri ->
UriUtil.getFileData(this, iconToUploadUri)?.also { documentFile ->
if (documentFile.length() <= MAX_ICON_SIZE) {
mDatabase?.buildNewCustomIcon(UriUtil.getBinaryDir(this))
} else {
if (documentFile.length() > MAX_ICON_SIZE) {
// TODO Error Icon size too big
} else {
buildNewIcon(iconToUploadUri)
}
}
}

View File

@@ -55,7 +55,7 @@ class ImageViewerActivity : LockingActivity() {
intent.getParcelableExtra<Attachment>(IMAGE_ATTACHMENT_TAG)?.let { attachment ->
supportActionBar?.title = attachment.name
supportActionBar?.subtitle = Formatter.formatFileSize(this, attachment.binaryAttachment.length)
supportActionBar?.subtitle = Formatter.formatFileSize(this, attachment.binaryFile.length)
Attachment.loadBitmap(attachment, Database.getInstance().loadedCipherKey) { bitmapLoaded ->
if (bitmapLoaded == null) {

View File

@@ -18,12 +18,17 @@ class IconCustomFragment : IconFragment() {
}
override fun defineIconList(database: Database): List<IconImage> {
return database.iconPool.getCustomIconList()
return database.iconsManager.getCustomIconList()
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
iconPickerViewModel.iconCustomAdded.observe(viewLifecycleOwner) { _ ->
iconAdapter.setList(defineIconList(database))
iconAdapter.notifyDataSetChanged()
}
iconAdapter.iconPickerListener = object : IconAdapter.IconPickerListener {
override fun iconPicked(icon: IconImage) {
iconPickerViewModel.selectIconCustom(icon.custom)

View File

@@ -19,6 +19,8 @@ abstract class IconFragment : Fragment() {
private lateinit var iconsGridView: RecyclerView
protected lateinit var iconAdapter: IconAdapter
protected val database = Database.getInstance()
protected val iconPickerViewModel: IconPickerViewModel by activityViewModels()
abstract fun retrieveMainLayoutId(): Int
@@ -28,8 +30,6 @@ abstract class IconFragment : Fragment() {
override fun onAttach(context: Context) {
super.onAttach(context)
val database = Database.getInstance()
iconAdapter = IconAdapter(requireActivity()).apply {
iconDrawableFactory = database.iconDrawableFactory
setList(defineIconList(database))

View File

@@ -18,7 +18,7 @@ class IconStandardFragment : IconFragment() {
}
override fun defineIconList(database: Database): List<IconImage> {
return database.iconPool.getStandardIconList()
return database.iconsManager.getStandardIconList()
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {

View File

@@ -101,22 +101,22 @@ class EntryAttachmentsItemsAdapter(context: Context)
}
holder.binaryFileBroken.apply {
setColorFilter(Color.RED)
visibility = if (entryAttachmentState.attachment.binaryAttachment.isCorrupted) {
visibility = if (entryAttachmentState.attachment.binaryFile.isCorrupted) {
View.VISIBLE
} else {
View.GONE
}
}
holder.binaryFileTitle.text = entryAttachmentState.attachment.name
if (entryAttachmentState.attachment.binaryAttachment.isCorrupted) {
if (entryAttachmentState.attachment.binaryFile.isCorrupted) {
holder.binaryFileTitle.setTextColor(Color.RED)
} else {
holder.binaryFileTitle.setTextColor(mTitleColor)
}
holder.binaryFileSize.text = Formatter.formatFileSize(context,
entryAttachmentState.attachment.binaryAttachment.length)
entryAttachmentState.attachment.binaryFile.length)
holder.binaryFileCompression.apply {
if (entryAttachmentState.attachment.binaryAttachment.isCompressed) {
if (entryAttachmentState.attachment.binaryFile.isCompressed) {
text = CompressionAlgorithm.GZip.getName(context.resources)
visibility = View.VISIBLE
} else {

View File

@@ -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.iconPool)
(cursor as EntryCursorKDB).populateEntry(entryKDB, database.iconsManager)
}
entryKDBX?.let { entryKDBX ->
(cursor as EntryCursorKDBX).populateEntry(entryKDBX, database.iconPool)
(cursor as EntryCursorKDBX).populateEntry(entryKDBX, database.iconsManager)
}
database.stopManageEntry(this)
}

View File

@@ -65,7 +65,7 @@ class DeleteNodesRunnable(context: Context,
database.deleteEntry(currentNode)
}
// Remove the oldest attachments
currentNode.getAttachments(database.binaryPool).forEach {
currentNode.getAttachments(database.attachmentPool).forEach {
database.removeAttachmentIfNotUsed(it)
}
}

View File

@@ -42,14 +42,14 @@ class UpdateEntryRunnable constructor(
mNewEntry.addParentFrom(mOldEntry)
// Build oldest attachments
val oldEntryAttachments = mOldEntry.getAttachments(database.binaryPool, true)
val newEntryAttachments = mNewEntry.getAttachments(database.binaryPool, true)
val oldEntryAttachments = mOldEntry.getAttachments(database.attachmentPool, true)
val newEntryAttachments = mNewEntry.getAttachments(database.attachmentPool, true)
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)
&& oldAttachment.binaryFile == newAttachment.binaryFile)
attachmentsToRemove.remove(oldAttachment)
}
}
@@ -60,7 +60,7 @@ class UpdateEntryRunnable constructor(
// Create an entry history (an entry history don't have history)
mOldEntry.addEntryToHistory(Entry(mBackupEntryHistory, copyHistory = false))
database.removeOldestEntryHistory(mOldEntry, database.binaryPool)
database.removeOldestEntryHistory(mOldEntry, database.attachmentPool)
// Only change data in index
database.updateEntry(mOldEntry)

View File

@@ -24,7 +24,7 @@ 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.IconImage
import com.kunzisoft.keepass.database.element.icon.IconPool
import com.kunzisoft.keepass.database.element.icon.IconsManager
import com.kunzisoft.keepass.database.element.node.NodeId
import java.util.*
@@ -50,12 +50,12 @@ abstract class EntryCursor<EntryId, PwEntryV : EntryVersioned<*, EntryId, *, *>>
abstract fun getPwNodeId(): NodeId<EntryId>
open fun populateEntry(pwEntry: PwEntryV, iconPool: IconPool) {
open fun populateEntry(pwEntry: PwEntryV, iconsManager: IconsManager) {
pwEntry.nodeId = getPwNodeId()
pwEntry.title = getString(getColumnIndex(COLUMN_INDEX_TITLE))
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)),
val iconStandard = iconsManager.getIcon(getInt(getColumnIndex(COLUMN_INDEX_ICON_STANDARD)))
val iconCustom = iconsManager.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)

View File

@@ -20,7 +20,7 @@
package com.kunzisoft.keepass.database.cursor
import com.kunzisoft.keepass.database.element.entry.EntryKDBX
import com.kunzisoft.keepass.database.element.icon.IconPool
import com.kunzisoft.keepass.database.element.icon.IconsManager
class EntryCursorKDBX : EntryCursorUUID<EntryKDBX>() {
@@ -50,8 +50,8 @@ class EntryCursorKDBX : EntryCursorUUID<EntryKDBX>() {
entryId++
}
override fun populateEntry(pwEntry: EntryKDBX, iconPool: IconPool) {
super.populateEntry(pwEntry, iconPool)
override fun populateEntry(pwEntry: EntryKDBX, iconsManager: IconsManager) {
super.populateEntry(pwEntry, iconsManager)
// Retrieve extra fields
if (extraFieldCursor.moveToFirst()) {

View File

@@ -23,20 +23,20 @@ import android.graphics.Bitmap
import android.graphics.BitmapFactory
import android.os.Parcel
import android.os.Parcelable
import com.kunzisoft.keepass.database.element.database.BinaryAttachment
import com.kunzisoft.keepass.database.element.database.BinaryFile
import kotlinx.coroutines.*
data class Attachment(var name: String,
var binaryAttachment: BinaryAttachment) : Parcelable {
var binaryFile: BinaryFile) : Parcelable {
constructor(parcel: Parcel) : this(
parcel.readString() ?: "",
parcel.readParcelable(BinaryAttachment::class.java.classLoader) ?: BinaryAttachment()
parcel.readParcelable(BinaryFile::class.java.classLoader) ?: BinaryFile()
)
override fun writeToParcel(parcel: Parcel, flags: Int) {
parcel.writeString(name)
parcel.writeParcelable(binaryAttachment, flags)
parcel.writeParcelable(binaryFile, flags)
}
override fun describeContents(): Int {
@@ -44,7 +44,7 @@ data class Attachment(var name: String,
}
override fun toString(): String {
return "$name at $binaryAttachment"
return "$name at $binaryFile"
}
override fun equals(other: Any?): Boolean {
@@ -78,7 +78,7 @@ data class Attachment(var name: String,
runCatching {
binaryCipherKey?.let { binaryKey ->
var bitmap: Bitmap?
attachment.binaryAttachment.getUnGzipInputDataStream(binaryKey).use { bitmapInputStream ->
attachment.binaryFile.getUnGzipInputDataStream(binaryKey).use { bitmapInputStream ->
bitmap = BitmapFactory.decodeStream(bitmapInputStream)
}
bitmap

View File

@@ -27,7 +27,7 @@ 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.IconImageCustom
import com.kunzisoft.keepass.database.element.icon.IconPool
import com.kunzisoft.keepass.database.element.icon.IconsManager
import com.kunzisoft.keepass.database.element.node.NodeId
import com.kunzisoft.keepass.database.element.node.NodeIdInt
import com.kunzisoft.keepass.database.element.node.NodeIdUUID
@@ -68,7 +68,7 @@ class Database {
var isReadOnly = false
val iconDrawableFactory = IconDrawableFactory()
val iconDrawableFactory = IconDrawableFactory { loadedCipherKey }
var loaded = false
set(value) {
@@ -92,9 +92,9 @@ class Database {
return mDatabaseKDB?.loadedCipherKey ?: mDatabaseKDBX?.loadedCipherKey
}
val iconPool: IconPool
val iconsManager: IconsManager
get() {
return mDatabaseKDB?.iconPool ?: mDatabaseKDBX?.iconPool ?: IconPool()
return mDatabaseKDB?.iconsManager ?: mDatabaseKDBX?.iconsManager ?: IconsManager()
}
fun buildNewCustomIcon(cacheDirectory: File): IconImageCustom? {
@@ -537,9 +537,9 @@ class Database {
}, omitBackup, max)
}
val binaryPool: BinaryPool
val attachmentPool: AttachmentPool
get() {
return mDatabaseKDBX?.binaryPool ?: BinaryPool()
return mDatabaseKDBX?.binaryPool ?: AttachmentPool()
}
val allowMultipleAttachments: Boolean
@@ -551,9 +551,9 @@ class Database {
return false
}
fun buildNewAttachment(cacheDirectory: File,
compressed: Boolean = false,
protected: Boolean = false): BinaryAttachment? {
fun buildNewBinaryAttachment(cacheDirectory: File,
compressed: Boolean = false,
protected: Boolean = false): BinaryFile? {
return mDatabaseKDB?.buildNewAttachment(cacheDirectory)
?: mDatabaseKDBX?.buildNewAttachment(cacheDirectory, compressed, protected)
}
@@ -561,7 +561,7 @@ class Database {
fun removeAttachmentIfNotUsed(attachment: Attachment) {
// No need in KDB database because unique attachment by entry
// Don't clear to fix upload multiple times
mDatabaseKDBX?.removeUnlinkedAttachment(attachment.binaryAttachment, false)
mDatabaseKDBX?.removeUnlinkedAttachment(attachment.binaryFile, false)
}
fun removeUnlinkedAttachments() {
@@ -630,7 +630,7 @@ class Database {
}
fun clear(filesDirectory: File? = null) {
iconPool.clearCache()
iconsManager.clearCache()
iconDrawableFactory.clearCache()
// Delete the cache of the database if present
mDatabaseKDB?.clearCache()
@@ -797,7 +797,7 @@ class Database {
* @param entryToCopy
* @param newParent
*/
fun copyEntryTo(entryToCopy: Entry, newParent: Group): Entry? {
fun copyEntryTo(entryToCopy: Entry, newParent: Group): Entry {
val entryCopied = Entry(entryToCopy, false)
entryCopied.nodeId = mDatabaseKDB?.newEntryId() ?: mDatabaseKDBX?.newEntryId() ?: NodeIdUUID()
entryCopied.parent = newParent
@@ -954,7 +954,7 @@ class Database {
rootGroup?.doForEachChildAndForIt(
object : NodeHandler<Entry>() {
override fun operate(node: Entry): Boolean {
removeOldestEntryHistory(node, binaryPool)
removeOldestEntryHistory(node, attachmentPool)
return true
}
},
@@ -969,7 +969,7 @@ class Database {
/**
* Remove oldest history if more than max items or max memory
*/
fun removeOldestEntryHistory(entry: Entry, binaryPool: BinaryPool) {
fun removeOldestEntryHistory(entry: Entry, attachmentPool: AttachmentPool) {
mDatabaseKDBX?.let {
val maxItems = historyMaxItems
if (maxItems >= 0) {
@@ -983,7 +983,7 @@ class Database {
while (true) {
var historySize: Long = 0
for (entryHistory in entry.getHistory()) {
historySize += entryHistory.getSize(binaryPool)
historySize += entryHistory.getSize(attachmentPool)
}
if (historySize > maxSize) {
removeOldestEntryHistory(entry)
@@ -997,7 +997,7 @@ class Database {
private fun removeOldestEntryHistory(entry: Entry) {
entry.removeOldestEntryFromHistory()?.let {
it.getAttachments(binaryPool, false).forEach { attachmentToRemove ->
it.getAttachments(attachmentPool, false).forEach { attachmentToRemove ->
removeAttachmentIfNotUsed(attachmentToRemove)
}
}
@@ -1005,7 +1005,7 @@ class Database {
fun removeEntryHistory(entry: Entry, entryHistoryPosition: Int) {
entry.removeEntryFromHistory(entryHistoryPosition)?.let {
it.getAttachments(binaryPool, false).forEach { attachmentToRemove ->
it.getAttachments(attachmentPool, false).forEach { attachmentToRemove ->
removeAttachmentIfNotUsed(attachmentToRemove)
}
}

View File

@@ -21,14 +21,12 @@ package com.kunzisoft.keepass.database.element
import android.os.Parcel
import android.os.Parcelable
import com.kunzisoft.keepass.database.element.database.BinaryPool
import com.kunzisoft.keepass.database.element.database.AttachmentPool
import com.kunzisoft.keepass.database.element.database.DatabaseKDBX
import com.kunzisoft.keepass.database.element.entry.EntryKDB
import com.kunzisoft.keepass.database.element.entry.EntryKDBX
import com.kunzisoft.keepass.database.element.entry.EntryVersionedInterface
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.Node
import com.kunzisoft.keepass.database.element.node.NodeId
import com.kunzisoft.keepass.database.element.node.NodeIdUUID
@@ -311,12 +309,12 @@ class Entry : Node, EntryVersionedInterface<Group> {
entryKDBX?.stopToManageFieldReferences()
}
fun getAttachments(binaryPool: BinaryPool, inHistory: Boolean = false): List<Attachment> {
fun getAttachments(attachmentPool: AttachmentPool, inHistory: Boolean = false): List<Attachment> {
val attachments = ArrayList<Attachment>()
entryKDB?.getAttachment()?.let {
attachments.add(it)
}
entryKDBX?.getAttachments(binaryPool, inHistory)?.let {
entryKDBX?.getAttachments(attachmentPool, inHistory)?.let {
attachments.addAll(it)
}
return attachments
@@ -337,9 +335,9 @@ class Entry : Node, EntryVersionedInterface<Group> {
entryKDBX?.removeAttachments()
}
private fun putAttachment(attachment: Attachment, binaryPool: BinaryPool) {
private fun putAttachment(attachment: Attachment, attachmentPool: AttachmentPool) {
entryKDB?.putAttachment(attachment)
entryKDBX?.putAttachment(attachment, binaryPool)
entryKDBX?.putAttachment(attachment, attachmentPool)
}
fun getHistory(): ArrayList<Entry> {
@@ -371,8 +369,8 @@ class Entry : Node, EntryVersionedInterface<Group> {
return null
}
fun getSize(binaryPool: BinaryPool): Long {
return entryKDBX?.getSize(binaryPool) ?: 0L
fun getSize(attachmentPool: AttachmentPool): Long {
return entryKDBX?.getSize(attachmentPool) ?: 0L
}
fun containsCustomData(): Boolean {
@@ -414,7 +412,7 @@ class Entry : Node, EntryVersionedInterface<Group> {
// Replace parameter fields by generated OTP fields
entryInfo.customFields = OtpEntryFields.generateAutoFields(entryInfo.customFields)
}
database?.binaryPool?.let { binaryPool ->
database?.attachmentPool?.let { binaryPool ->
entryInfo.attachments = getAttachments(binaryPool)
}
@@ -441,7 +439,7 @@ class Entry : Node, EntryVersionedInterface<Group> {
url = newEntryInfo.url
notes = newEntryInfo.notes
addExtraFields(newEntryInfo.customFields)
database?.binaryPool?.let { binaryPool ->
database?.attachmentPool?.let { binaryPool ->
newEntryInfo.attachments.forEach { attachment ->
putAttachment(attachment, binaryPool)
}

View File

@@ -0,0 +1,70 @@
/*
* Copyright 2021 Jeremy Jamet / Kunzisoft.
*
* This file is part of KeePassDX.
*
* KeePassDX is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* KeePassDX is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with KeePassDX. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.kunzisoft.keepass.database.element.database
class AttachmentPool : BinaryPool<Int>() {
/**
* Utility method to find an unused key in the pool
*/
override fun findUnusedKey(): Int {
var unusedKey = 0
while (pool[unusedKey] != null)
unusedKey++
return unusedKey
}
/**
* Utility method to order binaries and solve index problem in database v4
*/
private fun orderedBinaries(): List<KeyBinary<Int>> {
val keyBinaryList = ArrayList<KeyBinary<Int>>()
for ((key, binary) in pool) {
// 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
}
/**
* To register a binary with a ref corresponding to an ordered index
*/
fun getBinaryIndexFromKey(key: Int): Int? {
val index = orderedBinaries().indexOfFirst { it.keys.contains(key) }
return if (index < 0)
null
else
index
}
/**
* Different from doForEach, provide an ordered index to each binary
*/
fun doForEachOrderedBinary(action: (index: Int, binary: BinaryFile) -> Unit) {
orderedBinaries().forEachIndexed { index, keyBinary ->
action.invoke(index, keyBinary.binary)
}
}
}

View File

@@ -36,7 +36,7 @@ import javax.crypto.CipherInputStream
import javax.crypto.CipherOutputStream
import javax.crypto.spec.IvParameterSpec
class BinaryAttachment : Parcelable {
class BinaryFile : Parcelable {
private var dataFile: File? = null
var length: Long = 0
@@ -74,12 +74,12 @@ class BinaryAttachment : Parcelable {
@Throws(IOException::class)
fun getInputDataStream(cipherKey: Database.LoadedKey): InputStream {
return buildInputStream(dataFile!!, cipherKey)
return buildInputStream(dataFile, cipherKey)
}
@Throws(IOException::class)
fun getOutputDataStream(cipherKey: Database.LoadedKey): OutputStream {
return buildOutputStream(dataFile!!, cipherKey)
return buildOutputStream(dataFile, cipherKey)
}
@Throws(IOException::class)
@@ -201,7 +201,7 @@ class BinaryAttachment : Parcelable {
return true
if (other == null || javaClass != other.javaClass)
return false
if (other !is BinaryAttachment)
if (other !is BinaryFile)
return false
var sameData = false
@@ -262,15 +262,15 @@ class BinaryAttachment : Parcelable {
companion object {
private val TAG = BinaryAttachment::class.java.name
private val TAG = BinaryFile::class.java.name
@JvmField
val CREATOR: Parcelable.Creator<BinaryAttachment> = object : Parcelable.Creator<BinaryAttachment> {
override fun createFromParcel(parcel: Parcel): BinaryAttachment {
return BinaryAttachment(parcel)
val CREATOR: Parcelable.Creator<BinaryFile> = object : Parcelable.Creator<BinaryFile> {
override fun createFromParcel(parcel: Parcel): BinaryFile {
return BinaryFile(parcel)
}
override fun newArray(size: Int): Array<BinaryAttachment?> {
override fun newArray(size: Int): Array<BinaryFile?> {
return arrayOfNulls(size)
}
}

View File

@@ -19,39 +19,58 @@
*/
package com.kunzisoft.keepass.database.element.database
import java.io.File
import java.io.IOException
class BinaryPool {
private val pool = LinkedHashMap<Int, BinaryAttachment>()
abstract class BinaryPool<T> {
protected val pool = LinkedHashMap<T, BinaryFile>()
private var binaryFileIncrement = 0L // Unique file id (don't use current time because CPU too fast)
/**
* To get a binary by the pool key (ref attribute in entry)
*/
operator fun get(key: Int): BinaryAttachment? {
operator fun get(key: T): BinaryFile? {
return pool[key]
}
/**
* Create and return a new binary file not yet linked to a binary
*/
fun put(cacheDirectory: File,
key: T? = null,
compression: Boolean = false,
protection: Boolean = false): KeyBinary<T> {
val fileInCache = File(cacheDirectory, binaryFileIncrement.toString())
binaryFileIncrement++
val newBinaryFile = BinaryFile(fileInCache, compression, protection)
val newKey = put(key, newBinaryFile)
return KeyBinary(newBinaryFile, newKey)
}
/**
* 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) {
fun put(key: T?, value: BinaryFile): T {
if (key == null)
put(value)
return put(value)
else
pool[key] = value
return key
}
/**
* To put a [binaryAttachment] in the pool,
* To put a [binaryFile] in the pool,
* if already exists, replace the current one,
* else add it with a new key
*/
fun put(binaryAttachment: BinaryAttachment): Int {
var key = findKey(binaryAttachment)
fun put(binaryFile: BinaryFile): T {
var key: T? = findKey(binaryFile)
if (key == null) {
key = findUnusedKey()
}
pool[key] = binaryAttachment
pool[key!!] = binaryFile
return key
}
@@ -59,8 +78,8 @@ class BinaryPool {
* Remove a binary from the pool, the file is not deleted
*/
@Throws(IOException::class)
fun remove(binaryAttachment: BinaryAttachment) {
findKey(binaryAttachment)?.let {
fun remove(binaryFile: BinaryFile) {
findKey(binaryFile)?.let {
pool.remove(it)
}
// Don't clear attachment here because a file can be used in many BinaryAttachment
@@ -69,23 +88,18 @@ class BinaryPool {
/**
* Utility method to find an unused key in the pool
*/
private fun findUnusedKey(): Int {
var unusedKey = 0
while (pool[unusedKey] != null)
unusedKey++
return unusedKey
}
abstract fun findUnusedKey(): T
/**
* Return key of [binaryAttachmentToRetrieve] or null if not found
* Return key of [binaryFileToRetrieve] or null if not found
*/
private fun findKey(binaryAttachmentToRetrieve: BinaryAttachment): Int? {
val contains = pool.containsValue(binaryAttachmentToRetrieve)
private fun findKey(binaryFileToRetrieve: BinaryFile): T? {
val contains = pool.containsValue(binaryFileToRetrieve)
return if (!contains)
null
else {
for ((key, binary) in pool) {
if (binary == binaryAttachmentToRetrieve) {
if (binary == binaryFileToRetrieve) {
return key
}
}
@@ -93,54 +107,23 @@ class BinaryPool {
}
}
/**
* 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) {
// 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
}
/**
* To register a binary with a ref corresponding to an ordered index
*/
fun getBinaryIndexFromKey(key: Int): Int? {
val index = orderedBinaries().indexOfFirst { it.keys.contains(key) }
return if (index < 0)
null
else
index
}
/**
* Different from doForEach, provide an ordered index to each binary
*/
fun doForEachOrderedBinary(action: (index: Int, binary: BinaryAttachment) -> Unit) {
orderedBinaries().forEachIndexed { index, keyBinary ->
action.invoke(index, keyBinary.binary)
}
}
/**
* To do an action on each binary in the pool
*/
fun doForEachBinary(action: (binary: BinaryAttachment) -> Unit) {
pool.values.forEach { action.invoke(it) }
fun doForEachBinary(action: (key: T, binary: BinaryFile) -> Unit) {
for ((key, value) in pool) {
action.invoke(key, value)
}
}
fun isEmpty(): Boolean {
return pool.isEmpty()
}
@Throws(IOException::class)
fun clear() {
doForEachBinary {
it.clear()
doForEachBinary { _, binary ->
binary.clear()
}
pool.clear()
}
@@ -159,13 +142,13 @@ class BinaryPool {
/**
* Utility class to order binaries
*/
private class KeyBinary(val binary: BinaryAttachment, key: Int) {
val keys = HashSet<Int>()
class KeyBinary<T>(val binary: BinaryFile, key: T) {
val keys = HashSet<T>()
init {
addKey(key)
}
fun addKey(key: Int) {
fun addKey(key: T) {
keys.add(key)
}
}

View File

@@ -69,7 +69,7 @@ class DatabaseKDB : DatabaseVersioned<Int, UUID, GroupKDB, EntryKDB>() {
getGroupById(backupGroupId)
}
override val kdfEngine: KdfEngine?
override val kdfEngine: KdfEngine
get() = kdfListV3[0]
override val kdfAvailableList: List<KdfEngine>
@@ -177,7 +177,7 @@ class DatabaseKDB : DatabaseVersioned<Int, UUID, GroupKDB, EntryKDB>() {
}
override fun getStandardIcon(iconId: Int): IconImageStandard {
return this.iconPool.getIcon(iconId)
return this.iconsManager.getIcon(iconId)
}
override fun containsCustomData(): Boolean {
@@ -274,11 +274,11 @@ class DatabaseKDB : DatabaseVersioned<Int, UUID, GroupKDB, EntryKDB>() {
addEntryTo(entry, origParent)
}
fun buildNewAttachment(cacheDirectory: File): BinaryAttachment {
fun buildNewAttachment(cacheDirectory: File): BinaryFile {
// Generate an unique new file
val fileInCache = File(cacheDirectory, binaryIncrement.toString())
binaryIncrement++
return BinaryAttachment(fileInCache)
return BinaryFile(fileInCache)
}
companion object {

View File

@@ -108,8 +108,7 @@ class DatabaseKDBX : DatabaseVersioned<UUID, UUID, GroupKDBX, EntryKDBX> {
val deletedObjects = ArrayList<DeletedObject>()
val customData = HashMap<String, String>()
var binaryPool = BinaryPool()
private var binaryIncrement = 0 // Unique id (don't use current time because CPU too fast)
var binaryPool = AttachmentPool()
var localizedAppName = "KeePassDX"
@@ -211,7 +210,7 @@ class DatabaseKDBX : DatabaseVersioned<UUID, UUID, GroupKDBX, EntryKDBX> {
}
private fun compressAllBinaries() {
binaryPool.doForEachBinary { binary ->
binaryPool.doForEachBinary { _, binary ->
try {
val cipherKey = loadedCipherKey
?: throw IOException("Unable to retrieve cipher key to compress binaries")
@@ -224,7 +223,7 @@ class DatabaseKDBX : DatabaseVersioned<UUID, UUID, GroupKDBX, EntryKDBX> {
}
private fun decompressAllBinaries() {
binaryPool.doForEachBinary { binary ->
binaryPool.doForEachBinary { _, binary ->
try {
val cipherKey = loadedCipherKey
?: throw IOException("Unable to retrieve cipher key to decompress binaries")
@@ -308,19 +307,19 @@ class DatabaseKDBX : DatabaseVersioned<UUID, UUID, GroupKDBX, EntryKDBX> {
}
override fun getStandardIcon(iconId: Int): IconImageStandard {
return this.iconPool.getIcon(iconId)
return this.iconsManager.getIcon(iconId)
}
fun getCustomIcon(iconUuid: UUID): IconImageCustom {
return this.iconPool.getIcon(iconUuid)
return this.iconsManager.getIcon(iconUuid)
}
fun putCustomIcon(customIcon: IconImageCustom) {
this.iconPool.putIcon(customIcon)
this.iconsManager.putIcon(customIcon)
}
fun containsCustomIcons(): Boolean {
return this.iconPool.containsCustomIcons()
return this.iconsManager.containsCustomIcons()
}
fun putCustomData(label: String, value: String) {
@@ -636,18 +635,12 @@ class DatabaseKDBX : DatabaseVersioned<UUID, UUID, GroupKDBX, EntryKDBX> {
fun buildNewAttachment(cacheDirectory: File,
compression: Boolean,
protection: Boolean,
binaryPoolId: Int? = null): BinaryAttachment {
// New file with current time
val fileInCache = File(cacheDirectory, binaryIncrement.toString())
binaryIncrement++
val binaryAttachment = BinaryAttachment(fileInCache, compression, protection)
// add attachment to pool
binaryPool.put(binaryPoolId, binaryAttachment)
return binaryAttachment
binaryPoolId: Int? = null): BinaryFile {
return binaryPool.put(cacheDirectory, binaryPoolId, compression, protection).binary
}
fun removeUnlinkedAttachment(binary: BinaryAttachment, clear: Boolean) {
val listBinaries = ArrayList<BinaryAttachment>()
fun removeUnlinkedAttachment(binary: BinaryFile, clear: Boolean) {
val listBinaries = ArrayList<BinaryFile>()
listBinaries.add(binary)
removeUnlinkedAttachments(listBinaries, clear)
}
@@ -656,11 +649,11 @@ class DatabaseKDBX : DatabaseVersioned<UUID, UUID, GroupKDBX, EntryKDBX> {
removeUnlinkedAttachments(emptyList(), clear)
}
private fun removeUnlinkedAttachments(binaries: List<BinaryAttachment>, clear: Boolean) {
private fun removeUnlinkedAttachments(binaries: List<BinaryFile>, clear: Boolean) {
// Build binaries to remove with all binaries known
val binariesToRemove = ArrayList<BinaryAttachment>()
val binariesToRemove = ArrayList<BinaryFile>()
if (binaries.isEmpty()) {
binaryPool.doForEachBinary { binary ->
binaryPool.doForEachBinary { _, binary ->
binariesToRemove.add(binary)
}
} else {
@@ -670,7 +663,7 @@ class DatabaseKDBX : DatabaseVersioned<UUID, UUID, GroupKDBX, EntryKDBX> {
rootGroup?.doForEachChild(object : NodeHandler<EntryKDBX>() {
override fun operate(node: EntryKDBX): Boolean {
node.getAttachments(binaryPool, true).forEach {
binariesToRemove.remove(it.binaryAttachment)
binariesToRemove.remove(it.binaryFile)
}
return binariesToRemove.isNotEmpty()
}

View File

@@ -25,7 +25,7 @@ import com.kunzisoft.keepass.database.element.entry.EntryVersioned
import com.kunzisoft.keepass.database.element.group.GroupVersioned
import com.kunzisoft.keepass.database.element.icon.IconImageCustom
import com.kunzisoft.keepass.database.element.icon.IconImageStandard
import com.kunzisoft.keepass.database.element.icon.IconPool
import com.kunzisoft.keepass.database.element.icon.IconsManager
import com.kunzisoft.keepass.database.element.node.NodeId
import com.kunzisoft.keepass.database.element.node.Type
import com.kunzisoft.keepass.database.element.security.EncryptionAlgorithm
@@ -54,7 +54,7 @@ abstract class DatabaseVersioned<
var finalKey: ByteArray? = null
protected set
val iconPool = IconPool()
val iconsManager = IconsManager()
var changeDuplicateId = false
@@ -329,13 +329,8 @@ abstract class DatabaseVersioned<
abstract fun getStandardIcon(iconId: Int): IconImageStandard
fun buildNewCustomIcon(cacheDirectory: File): IconImageCustom {
// New file with current time
val fileInCache = File(cacheDirectory, System.currentTimeMillis().toString())
val newCustomIcon = IconImageCustom(UUID.randomUUID())
// add icon to pool
iconPool.putIcon(newCustomIcon)
return newCustomIcon
fun buildNewCustomIcon(cacheDirectory: File, customIconId: UUID? = null): IconImageCustom {
return iconsManager.buildNewCustomIcon(cacheDirectory, customIconId)
}
abstract fun containsCustomData(): Boolean

View File

@@ -22,7 +22,7 @@ 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.database.BinaryFile
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
@@ -56,7 +56,7 @@ class EntryKDB : EntryVersioned<Int, UUID, GroupKDB, EntryKDB>, NodeKDBInterface
/** A string describing what is in binaryData */
var binaryDescription = ""
var binaryData: BinaryAttachment? = null
var binaryData: BinaryFile? = null
// Determine if this is a MetaStream entry
val isMetaStream: Boolean
@@ -89,7 +89,7 @@ class EntryKDB : EntryVersioned<Int, UUID, GroupKDB, EntryKDB>, NodeKDBInterface
url = parcel.readString() ?: url
notes = parcel.readString() ?: notes
binaryDescription = parcel.readString() ?: binaryDescription
binaryData = parcel.readParcelable(BinaryAttachment::class.java.classLoader)
binaryData = parcel.readParcelable(BinaryFile::class.java.classLoader)
}
override fun readParentParcelable(parcel: Parcel): GroupKDB? {
@@ -151,7 +151,7 @@ class EntryKDB : EntryVersioned<Int, UUID, GroupKDB, EntryKDB>, NodeKDBInterface
fun putAttachment(attachment: Attachment) {
this.binaryDescription = attachment.name
this.binaryData = attachment.binaryAttachment
this.binaryData = attachment.binaryFile
}
fun removeAttachment(attachment: Attachment? = null) {

View File

@@ -23,7 +23,7 @@ import android.os.Parcel
import android.os.Parcelable
import com.kunzisoft.keepass.database.element.Attachment
import com.kunzisoft.keepass.database.element.DateInstant
import com.kunzisoft.keepass.database.element.database.BinaryPool
import com.kunzisoft.keepass.database.element.database.AttachmentPool
import com.kunzisoft.keepass.database.element.database.DatabaseKDBX
import com.kunzisoft.keepass.database.element.group.GroupKDBX
import com.kunzisoft.keepass.database.element.node.NodeId
@@ -56,7 +56,7 @@ class EntryKDBX : EntryVersioned<UUID, UUID, GroupKDBX, EntryKDBX>, NodeKDBXInte
var additional = ""
var tags = ""
fun getSize(binaryPool: BinaryPool): Long {
fun getSize(attachmentPool: AttachmentPool): Long {
var size = FIXED_LENGTH_SIZE
for (entry in fields.entries) {
@@ -64,7 +64,7 @@ class EntryKDBX : EntryVersioned<UUID, UUID, GroupKDBX, EntryKDBX>, NodeKDBXInte
size += entry.value.length().toLong()
}
size += getAttachmentsSize(binaryPool)
size += getAttachmentsSize(attachmentPool)
size += autoType.defaultSequence.length.toLong()
for ((key, value) in autoType.entrySet()) {
@@ -73,7 +73,7 @@ class EntryKDBX : EntryVersioned<UUID, UUID, GroupKDBX, EntryKDBX>, NodeKDBXInte
}
for (entry in history) {
size += entry.getSize(binaryPool)
size += entry.getSize(attachmentPool)
}
size += overrideURL.length.toLong()
@@ -262,16 +262,16 @@ class EntryKDBX : EntryVersioned<UUID, UUID, GroupKDBX, EntryKDBX>, NodeKDBXInte
/**
* It's a list because history labels can be defined multiple times
*/
fun getAttachments(binaryPool: BinaryPool, inHistory: Boolean = false): List<Attachment> {
fun getAttachments(attachmentPool: AttachmentPool, inHistory: Boolean = false): List<Attachment> {
val entryAttachmentList = ArrayList<Attachment>()
for ((label, poolId) in binaries) {
binaryPool[poolId]?.let { binary ->
attachmentPool[poolId]?.let { binary ->
entryAttachmentList.add(Attachment(label, binary))
}
}
if (inHistory) {
history.forEach {
entryAttachmentList.addAll(it.getAttachments(binaryPool, false))
entryAttachmentList.addAll(it.getAttachments(attachmentPool, false))
}
}
return entryAttachmentList
@@ -281,8 +281,8 @@ class EntryKDBX : EntryVersioned<UUID, UUID, GroupKDBX, EntryKDBX>, NodeKDBXInte
return binaries.isNotEmpty()
}
fun putAttachment(attachment: Attachment, binaryPool: BinaryPool) {
binaries[attachment.name] = binaryPool.put(attachment.binaryAttachment)
fun putAttachment(attachment: Attachment, attachmentPool: AttachmentPool) {
binaries[attachment.name] = attachmentPool.put(attachment.binaryFile)
}
fun removeAttachment(attachment: Attachment) {
@@ -293,11 +293,11 @@ class EntryKDBX : EntryVersioned<UUID, UUID, GroupKDBX, EntryKDBX>, NodeKDBXInte
binaries.clear()
}
private fun getAttachmentsSize(binaryPool: BinaryPool): Long {
private fun getAttachmentsSize(attachmentPool: AttachmentPool): Long {
var size = 0L
for ((label, poolId) in binaries) {
size += label.length.toLong()
size += binaryPool[poolId]?.length ?: 0
size += attachmentPool[poolId]?.length ?: 0
}
return size
}
@@ -314,7 +314,7 @@ class EntryKDBX : EntryVersioned<UUID, UUID, GroupKDBX, EntryKDBX>, NodeKDBXInte
history.add(entry)
}
fun removeEntryFromHistory(position: Int): EntryKDBX? {
fun removeEntryFromHistory(position: Int): EntryKDBX {
return history.removeAt(position)
}

View File

@@ -21,29 +21,34 @@ package com.kunzisoft.keepass.database.element.icon
import android.os.Parcel
import android.os.Parcelable
import com.kunzisoft.keepass.database.element.database.BinaryFile
import com.kunzisoft.keepass.database.element.database.DatabaseVersioned
import java.util.*
class IconImageCustom() : Parcelable {
class IconImageCustom : Parcelable {
var uuid: UUID = DatabaseVersioned.UUID_ZERO
@Transient
var imageData: ByteArray = ByteArray(0)
var uuid: UUID
var binaryFile: BinaryFile
constructor(uuid: UUID, data: ByteArray) : this() {
this.uuid = uuid
this.imageData = data
constructor() {
uuid = DatabaseVersioned.UUID_ZERO
binaryFile = BinaryFile()
}
constructor(uuid: UUID) : this() {
constructor(uuid: UUID,
binaryFile: BinaryFile) {
this.uuid = uuid
this.imageData = ByteArray(0)
this.binaryFile = binaryFile
}
constructor(parcel: Parcel) : this() {
constructor(uuid: UUID) {
this.uuid = uuid
binaryFile = BinaryFile()
}
constructor(parcel: Parcel) {
uuid = parcel.readSerializable() as UUID
// TODO Take too much memories
// parcel.readByteArray(imageData);
binaryFile = parcel.readParcelable(BinaryFile::class.java.classLoader) ?: BinaryFile()
}
override fun describeContents(): Int {
@@ -52,7 +57,7 @@ class IconImageCustom() : Parcelable {
override fun writeToParcel(dest: Parcel, flags: Int) {
dest.writeSerializable(uuid)
// Too big for a parcelable dest.writeByteArray(imageData);
dest.writeParcelable(binaryFile, flags)
}
override fun hashCode(): Int {

View File

@@ -19,16 +19,17 @@
*/
package com.kunzisoft.keepass.database.element.icon
import com.kunzisoft.keepass.database.element.database.BinaryPool
import com.kunzisoft.keepass.icons.IconPack.Companion.NB_ICONS
import java.util.ArrayList
import java.util.UUID
import java.io.File
import java.util.*
class IconPool {
class IconsManager {
private val standardCache = List(NB_ICONS) {
IconImageStandard(it)
}
private val customCache = HashMap<UUID, IconImageCustom?>()
private val customCache = CustomIconPool()
fun getIcon(iconId: Int): IconImageStandard {
return standardCache[iconId]
@@ -42,33 +43,44 @@ class IconPool {
* Custom
*/
fun buildNewCustomIcon(cacheDirectory: File, key: UUID? = null): IconImageCustom {
val keyBinary = customCache.put(cacheDirectory, key)
return IconImageCustom(keyBinary.keys.first(), keyBinary.binary)
}
fun putIcon(icon: IconImageCustom) {
customCache[icon.uuid] = icon
customCache.put(icon.uuid, icon.binaryFile)
}
fun getIcon(iconUuid: UUID): IconImageCustom {
var icon: IconImageCustom? = customCache[iconUuid]
if (icon == null) {
icon = IconImageCustom(iconUuid)
customCache[iconUuid] = icon
customCache[iconUuid]?.let {
return IconImageCustom(iconUuid, it)
}
return icon
return IconImageCustom(iconUuid)
}
fun containsCustomIcons(): Boolean {
return customCache.isNotEmpty()
return !customCache.isEmpty()
}
fun getCustomIconList(): List<IconImage> {
val list = ArrayList<IconImage>(customCache.size)
for ((_, customIcon) in customCache) {
list.add(IconImage(customIcon!!))
val list = ArrayList<IconImage>()
customCache.doForEachBinary { key, binary ->
list.add(IconImage(IconImageCustom(key, binary)))
}
return list
}
class CustomIconPool : BinaryPool<UUID>() {
override fun findUnusedKey(): UUID {
var newUUID = UUID.randomUUID()
while (pool.containsKey(newUUID)) {
newUUID = UUID.randomUUID()
}
return newUUID
}
}
/**
* Clear the cache of icons
*/

View File

@@ -29,14 +29,13 @@ import com.kunzisoft.keepass.database.element.Attachment
import com.kunzisoft.keepass.database.element.Database
import com.kunzisoft.keepass.database.element.DateInstant
import com.kunzisoft.keepass.database.element.DeletedObject
import com.kunzisoft.keepass.database.element.database.BinaryAttachment
import com.kunzisoft.keepass.database.element.database.BinaryFile
import com.kunzisoft.keepass.database.element.database.CompressionAlgorithm
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.DatabaseVersioned
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.NodeIdUUID
import com.kunzisoft.keepass.database.element.node.NodeKDBXInterface
import com.kunzisoft.keepass.database.element.security.ProtectedString
@@ -79,7 +78,7 @@ class DatabaseInputKDBX(cacheDirectory: File)
private var ctxStringName: String? = null
private var ctxStringValue: ProtectedString? = null
private var ctxBinaryName: String? = null
private var ctxBinaryValue: BinaryAttachment? = null
private var ctxBinaryValue: BinaryFile? = null
private var ctxATName: String? = null
private var ctxATSeq: String? = null
private var entryInHistory = false
@@ -705,8 +704,12 @@ class DatabaseInputKDBX(cacheDirectory: File)
return KdbContext.Meta
} else if (ctx == KdbContext.CustomIcon && name.equals(DatabaseKDBXXML.ElemCustomIconItem, ignoreCase = true)) {
if (customIconID != DatabaseVersioned.UUID_ZERO && customIconData != null) {
val icon = IconImageCustom(customIconID, customIconData!!)
mDatabase.putCustomIcon(icon)
val customIcon = mDatabase.buildNewCustomIcon(cacheDirectory, customIconID)
mDatabase.loadedCipherKey?.let { cipherKey ->
customIcon.binaryFile.getOutputDataStream(cipherKey).use { outputStream ->
outputStream.write(customIconData)
}
}
}
customIconID = DatabaseVersioned.UUID_ZERO
@@ -962,7 +965,7 @@ class DatabaseInputKDBX(cacheDirectory: File)
}
@Throws(XmlPullParserException::class, IOException::class)
private fun readBinary(xpp: XmlPullParser): BinaryAttachment? {
private fun readBinary(xpp: XmlPullParser): BinaryFile? {
// Reference Id to a binary already present in binary pool
val ref = xpp.getAttributeValue(null, DatabaseKDBXXML.AttrRef)
@@ -993,7 +996,7 @@ class DatabaseInputKDBX(cacheDirectory: File)
}
@Throws(IOException::class, XmlPullParserException::class)
private fun createBinary(binaryId: Int?, xpp: XmlPullParser): BinaryAttachment? {
private fun createBinary(binaryId: Int?, xpp: XmlPullParser): BinaryFile? {
var compressed = false
var protected = true

View File

@@ -701,11 +701,17 @@ class DatabaseOutputKDBX(private val mDatabaseKDBX: DatabaseKDBX,
xml.startTag(null, DatabaseKDBXXML.ElemCustomIcons)
mDatabaseKDBX.iconPool.getCustomIconList().forEach { icon ->
mDatabaseKDBX.iconsManager.getCustomIconList().forEach { icon ->
xml.startTag(null, DatabaseKDBXXML.ElemCustomIconItem)
writeUuid(DatabaseKDBXXML.ElemCustomIconItemID, icon.custom.uuid)
writeObject(DatabaseKDBXXML.ElemCustomIconItemData, String(Base64.encode(icon.custom.imageData, BASE_64_FLAG)))
var customImageData = ByteArray(0)
mDatabaseKDBX.loadedCipherKey?.let { cipherKey ->
icon.custom.binaryFile.getInputDataStream(cipherKey).use { inputStream ->
customImageData = inputStream.readBytes()
}
}
writeObject(DatabaseKDBXXML.ElemCustomIconItemData, String(Base64.encode(customImageData, BASE_64_FLAG)))
xml.endTag(null, DatabaseKDBXXML.ElemCustomIconItem)
}

View File

@@ -36,6 +36,7 @@ import androidx.core.content.res.ResourcesCompat
import androidx.core.graphics.drawable.toBitmap
import androidx.core.widget.ImageViewCompat
import com.kunzisoft.keepass.R
import com.kunzisoft.keepass.database.element.Database
import com.kunzisoft.keepass.database.element.icon.IconImage
import com.kunzisoft.keepass.database.element.icon.IconImageCustom
import org.apache.commons.collections.map.AbstractReferenceMap
@@ -44,7 +45,7 @@ import org.apache.commons.collections.map.ReferenceMap
/**
* Factory class who build database icons dynamically, can assign an icon of IconPack, or a custom icon to an ImageView with a tint
*/
class IconDrawableFactory {
class IconDrawableFactory(private val retrieveCipherKey : () -> Database.LoadedKey?) {
/** customIconMap
* Cache for icon drawable.
@@ -155,19 +156,21 @@ class IconDrawableFactory {
*/
private fun getIconDrawable(resources: Resources, icon: IconImageCustom): Drawable {
val patternIcon = PatternIcon(resources)
var draw: Drawable? = customIconMap[icon.uuid] as Drawable?
if (draw == null) {
var bitmap: Bitmap? = BitmapFactory.decodeByteArray(icon.imageData, 0, icon.imageData.size)
// Could not understand custom icon
bitmap?.let { bitmapIcon ->
bitmap = resize(bitmapIcon, patternIcon)
draw = BitmapDrawable(resources, bitmap)
customIconMap[icon.uuid] = draw
val cipherKey = retrieveCipherKey.invoke()
if (cipherKey != null) {
var draw: Drawable? = customIconMap[icon.uuid] as Drawable?
if (draw == null) {
var bitmap: Bitmap? = BitmapFactory.decodeStream(icon.binaryFile.getInputDataStream(cipherKey))
// Could not understand custom icon
bitmap?.let { bitmapIcon ->
bitmap = resize(bitmapIcon, patternIcon)
draw = BitmapDrawable(resources, bitmap)
customIconMap[icon.uuid] = draw
return draw!!
}
} else {
return draw!!
}
} else {
return draw!!
}
return patternIcon.blankDrawable
}

View File

@@ -22,7 +22,7 @@ package com.kunzisoft.keepass.model
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.database.BinaryFile
import com.kunzisoft.keepass.utils.readEnum
import com.kunzisoft.keepass.utils.writeEnum
@@ -33,7 +33,7 @@ data class EntryAttachmentState(var attachment: Attachment,
var previewState: AttachmentState = AttachmentState.NULL) : Parcelable {
constructor(parcel: Parcel) : this(
parcel.readParcelable(Attachment::class.java.classLoader) ?: Attachment("", BinaryAttachment()),
parcel.readParcelable(Attachment::class.java.classLoader) ?: Attachment("", BinaryFile()),
parcel.readEnum<StreamDirection>() ?: StreamDirection.DOWNLOAD,
parcel.readEnum<AttachmentState>() ?: AttachmentState.NULL,
parcel.readInt(),

View File

@@ -30,7 +30,7 @@ import androidx.documentfile.provider.DocumentFile
import com.kunzisoft.keepass.R
import com.kunzisoft.keepass.database.element.Attachment
import com.kunzisoft.keepass.database.element.Database
import com.kunzisoft.keepass.database.element.database.BinaryAttachment
import com.kunzisoft.keepass.database.element.database.BinaryFile
import com.kunzisoft.keepass.model.AttachmentState
import com.kunzisoft.keepass.model.EntryAttachmentState
import com.kunzisoft.keepass.model.StreamDirection
@@ -86,7 +86,7 @@ class AttachmentFileNotificationService: LockNotificationService() {
fun onAttachmentAction(fileUri: Uri, entryAttachmentState: EntryAttachmentState)
}
override fun onBind(intent: Intent): IBinder? {
override fun onBind(intent: Intent): IBinder {
return mActionTaskBinder
}
@@ -349,7 +349,7 @@ class AttachmentFileNotificationService: LockNotificationService() {
StreamDirection.UPLOAD -> {
uploadToDatabase(
attachmentNotification.uri,
attachment.binaryAttachment,
attachment.binaryFile,
contentResolver, 1024,
{ // Cancellation
downloadState == AttachmentState.CANCELED
@@ -361,7 +361,7 @@ class AttachmentFileNotificationService: LockNotificationService() {
StreamDirection.DOWNLOAD -> {
downloadFromDatabase(
attachmentNotification.uri,
attachment.binaryAttachment,
attachment.binaryFile,
contentResolver, 1024) { percent ->
publishProgress(percent)
}
@@ -397,15 +397,15 @@ class AttachmentFileNotificationService: LockNotificationService() {
}
fun downloadFromDatabase(attachmentToUploadUri: Uri,
binaryAttachment: BinaryAttachment,
binaryFile: BinaryFile,
contentResolver: ContentResolver,
bufferSize: Int = DEFAULT_BUFFER_SIZE,
update: ((percent: Int)->Unit)? = null) {
var dataDownloaded = 0L
val fileSize = binaryAttachment.length
val fileSize = binaryFile.length
UriUtil.getUriOutputStream(contentResolver, attachmentToUploadUri)?.use { outputStream ->
Database.getInstance().loadedCipherKey?.let { binaryCipherKey ->
binaryAttachment.getUnGzipInputDataStream(binaryCipherKey).use { inputStream ->
binaryFile.getUnGzipInputDataStream(binaryCipherKey).use { inputStream ->
inputStream.readAllBytes(bufferSize) { buffer ->
outputStream.write(buffer)
dataDownloaded += buffer.size
@@ -422,7 +422,7 @@ class AttachmentFileNotificationService: LockNotificationService() {
}
fun uploadToDatabase(attachmentFromDownloadUri: Uri,
binaryAttachment: BinaryAttachment,
binaryFile: BinaryFile,
contentResolver: ContentResolver,
bufferSize: Int = DEFAULT_BUFFER_SIZE,
canceled: ()-> Boolean = { false },
@@ -431,7 +431,7 @@ class AttachmentFileNotificationService: LockNotificationService() {
val fileSize = contentResolver.openFileDescriptor(attachmentFromDownloadUri, "r")?.statSize ?: 0
UriUtil.getUriInputStream(contentResolver, attachmentFromDownloadUri)?.use { inputStream ->
Database.getInstance().loadedCipherKey?.let { binaryCipherKey ->
binaryAttachment.getGzipOutputDataStream(binaryCipherKey).use { outputStream ->
binaryFile.getGzipOutputDataStream(binaryCipherKey).use { outputStream ->
inputStream.readAllBytes(bufferSize, canceled) { buffer ->
outputStream.write(buffer)
dataUploaded += buffer.size

View File

@@ -0,0 +1,15 @@
package com.kunzisoft.keepass.tasks
import android.content.Context
import androidx.work.Worker
import androidx.work.WorkerParameters
class IconUploadWorker(appContext: Context, workerParams: WorkerParameters):
Worker(appContext, workerParams) {
override fun doWork(): Result {
//uploadImages()
return Result.success()
}
}

View File

@@ -15,6 +15,10 @@ class IconPickerViewModel: ViewModel() {
MutableLiveData<IconImageCustom>()
}
val iconCustomAdded: MutableLiveData<IconImageCustom> by lazy {
MutableLiveData<IconImageCustom>()
}
fun selectIconStandard(icon: IconImageStandard) {
iconStandardSelected.value = icon
}
@@ -22,4 +26,8 @@ class IconPickerViewModel: ViewModel() {
fun selectIconCustom(icon: IconImageCustom) {
iconCustomSelected.value = icon
}
fun addCustomIcon(icon: IconImageCustom) {
iconCustomAdded.value = icon
}
}

View File

@@ -434,7 +434,7 @@
<string name="unit_gibibyte">GiB</string>
<string name="unit_mebibyte">MiB</string>
<string name="unit_kibibyte">KiB</string>
<string name="upload_attachment">Last opp</string>
<string name="upload_attachment">Last opp %1$s</string>
<string name="education_add_attachment_title">Legg til vedlegg</string>
<string name="autofill_save_search_info_title">Lagre søkeinfo</string>
<string name="autofill_close_database_title">Lukk database</string>