Refactoring Binary cache

This commit is contained in:
J-Jamet
2021-03-23 17:44:07 +01:00
parent 2b81dfb100
commit 145030e854
26 changed files with 348 additions and 345 deletions

View File

@@ -125,7 +125,7 @@ class EntryActivity : LockingActivity() {
historyView = findViewById(R.id.history_container)
entryContentsView = findViewById(R.id.entry_contents)
entryContentsView?.applyFontVisibilityToFields(PreferencesUtil.fieldFontIsInVisibility(this))
entryContentsView?.setAttachmentCipherKey(mDatabase?.loadedCipherKey)
entryContentsView?.setAttachmentCipherKey(mDatabase)
entryProgress = findViewById(R.id.entry_progress)
lockView = findViewById(R.id.lock_button)

View File

@@ -202,7 +202,7 @@ class EntryEditActivity : LockingActivity(),
// Build fragment to manage entry modification
entryEditFragment = supportFragmentManager.findFragmentByTag(ENTRY_EDIT_FRAGMENT_TAG) as? EntryEditFragment?
if (entryEditFragment == null) {
entryEditFragment = EntryEditFragment.getInstance(tempEntryInfo, mDatabase?.loadedCipherKey)
entryEditFragment = EntryEditFragment.getInstance(tempEntryInfo)
}
supportFragmentManager.beginTransaction()
.replace(R.id.entry_edit_contents, entryEditFragment!!, ENTRY_EDIT_FRAGMENT_TAG)

View File

@@ -232,8 +232,13 @@ class IconPickerActivity : LockingActivity() {
mDatabase?.buildNewCustomIcon(UriUtil.getBinaryDir(this@IconPickerActivity)) { customIcon, binary ->
if (customIcon != null) {
iconCustomState.iconCustom = customIcon
BinaryDatabaseManager.resizeBitmapAndStoreDataInBinaryFile(contentResolver,
iconToUploadUri, binary)
mDatabase?.let { database ->
BinaryDatabaseManager.resizeBitmapAndStoreDataInBinaryFile(
contentResolver,
database,
iconToUploadUri,
binary)
}
when {
binary == null -> {
}

View File

@@ -39,6 +39,8 @@ import kotlin.math.max
class ImageViewerActivity : LockingActivity() {
private var mDatabase: Database? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
@@ -59,6 +61,8 @@ class ImageViewerActivity : LockingActivity() {
resources.displayMetrics.heightPixels * 2
)
mDatabase = Database.getInstance()
try {
progressView.visibility = View.VISIBLE
intent.getParcelableExtra<Attachment>(IMAGE_ATTACHMENT_TAG)?.let { attachment ->
@@ -66,16 +70,18 @@ class ImageViewerActivity : LockingActivity() {
supportActionBar?.title = attachment.name
supportActionBar?.subtitle = Formatter.formatFileSize(this, attachment.binaryData.getSize())
BinaryDatabaseManager.loadBitmap(
attachment.binaryData,
Database.getInstance().loadedCipherKey,
mImagePreviewMaxWidth
) { bitmapLoaded ->
if (bitmapLoaded == null) {
finish()
} else {
progressView.visibility = View.GONE
imageView.setImageBitmap(bitmapLoaded)
mDatabase?.let { database ->
BinaryDatabaseManager.loadBitmap(
database,
attachment.binaryData,
mImagePreviewMaxWidth
) { bitmapLoaded ->
if (bitmapLoaded == null) {
finish()
} else {
progressView.visibility = View.GONE
imageView.setImageBitmap(bitmapLoaded)
}
}
}
} ?: finish()

View File

@@ -84,7 +84,6 @@ class EntryEditFragment: StylishFragment() {
// Elements to modify the current entry
private var mEntryInfo = EntryInfo()
private var mBinaryCipherKey: Database.LoadedKey? = null
private var mLastFocusedEditField: FocusedEditField? = null
private var mExtraViewToRequestFocus: EditText? = null
@@ -122,7 +121,9 @@ class EntryEditFragment: StylishFragment() {
attachmentsContainerView = rootView.findViewById(R.id.entry_attachments_container)
attachmentsListView = rootView.findViewById(R.id.entry_attachments_list)
attachmentsAdapter = EntryAttachmentsItemsAdapter(requireContext())
attachmentsAdapter.binaryCipherKey = arguments?.getSerializable(KEY_BINARY_CIPHER_KEY) as? Database.LoadedKey?
// TODO retrieve current database with its unique key
attachmentsAdapter.database = Database.getInstance()
//attachmentsAdapter.database = arguments?.getInt(KEY_DATABASE)
attachmentsAdapter.onListSizeChangedListener = { previousSize, newSize ->
if (previousSize > 0 && newSize == 0) {
attachmentsContainerView.collapse(true)
@@ -502,7 +503,6 @@ class EntryEditFragment: StylishFragment() {
override fun onSaveInstanceState(outState: Bundle) {
populateEntryWithViews()
outState.putParcelable(KEY_TEMP_ENTRY_INFO, mEntryInfo)
outState.putSerializable(KEY_BINARY_CIPHER_KEY, mBinaryCipherKey)
outState.putParcelable(KEY_LAST_FOCUSED_FIELD, mLastFocusedEditField)
super.onSaveInstanceState(outState)
@@ -510,15 +510,16 @@ class EntryEditFragment: StylishFragment() {
companion object {
const val KEY_TEMP_ENTRY_INFO = "KEY_TEMP_ENTRY_INFO"
const val KEY_BINARY_CIPHER_KEY = "KEY_BINARY_CIPHER_KEY"
const val KEY_DATABASE = "KEY_DATABASE"
const val KEY_LAST_FOCUSED_FIELD = "KEY_LAST_FOCUSED_FIELD"
fun getInstance(entryInfo: EntryInfo?,
loadedKey: Database.LoadedKey?): EntryEditFragment {
fun getInstance(entryInfo: EntryInfo?): EntryEditFragment {
//database: Database?): EntryEditFragment {
return EntryEditFragment().apply {
arguments = Bundle().apply {
putParcelable(KEY_TEMP_ENTRY_INFO, entryInfo)
putSerializable(KEY_BINARY_CIPHER_KEY, loadedKey)
// TODO Unique database key database.key
putInt(KEY_DATABASE, 0)
}
}
}

View File

@@ -45,7 +45,7 @@ import kotlin.math.max
class EntryAttachmentsItemsAdapter(context: Context)
: AnimatedItemsAdapter<EntryAttachmentState, EntryAttachmentsItemsAdapter.EntryBinariesViewHolder>(context) {
var binaryCipherKey: Database.LoadedKey? = null
var database: Database? = null
var onItemClickListener: ((item: EntryAttachmentState)->Unit)? = null
var onBinaryPreviewLoaded: ((item: EntryAttachmentState) -> Unit)? = null
@@ -82,21 +82,23 @@ class EntryAttachmentsItemsAdapter(context: Context)
if (entryAttachmentState.previewState == AttachmentState.NULL) {
entryAttachmentState.previewState = AttachmentState.IN_PROGRESS
// Load the bitmap image
BinaryDatabaseManager.loadBitmap(
entryAttachmentState.attachment.binaryData,
binaryCipherKey,
mImagePreviewMaxWidth
) { imageLoaded ->
if (imageLoaded == null) {
entryAttachmentState.previewState = AttachmentState.ERROR
visibility = View.GONE
onBinaryPreviewLoaded?.invoke(entryAttachmentState)
} else {
entryAttachmentState.previewState = AttachmentState.COMPLETE
setImageBitmap(imageLoaded)
if (visibility != View.VISIBLE) {
expand(true, resources.getDimensionPixelSize(R.dimen.item_file_info_height)) {
onBinaryPreviewLoaded?.invoke(entryAttachmentState)
database?.let { database ->
BinaryDatabaseManager.loadBitmap(
database,
entryAttachmentState.attachment.binaryData,
mImagePreviewMaxWidth
) { imageLoaded ->
if (imageLoaded == null) {
entryAttachmentState.previewState = AttachmentState.ERROR
visibility = View.GONE
onBinaryPreviewLoaded?.invoke(entryAttachmentState)
} else {
entryAttachmentState.previewState = AttachmentState.COMPLETE
setImageBitmap(imageLoaded)
if (visibility != View.VISIBLE) {
expand(true, resources.getDimensionPixelSize(R.dimen.item_file_info_height)) {
onBinaryPreviewLoaded?.invoke(entryAttachmentState)
}
}
}
}

View File

@@ -37,7 +37,7 @@ class ReloadDatabaseRunnable(private val context: Context,
private var tempCipherKey: Database.LoadedKey? = null
override fun onStartRun() {
tempCipherKey = mDatabase.loadedCipherKey
tempCipherKey = mDatabase.binaryCache.loadedCipherKey
// Clear before we load
mDatabase.clear(UriUtil.getBinaryDir(context))
mDatabase.wasReloaded = true

View File

@@ -70,7 +70,7 @@ class Database {
var isReadOnly = false
val iconDrawableFactory = IconDrawableFactory(
{ loadedCipherKey },
{ binaryCache },
{ iconId -> iconsManager.getBinaryForCustomIcon(iconId) }
)
@@ -92,13 +92,13 @@ class Database {
* Cipher key regenerated when the database is loaded and closed
* Can be used to temporarily store database elements
*/
var loadedCipherKey: LoadedKey?
var binaryCache: BinaryCache
private set(value) {
mDatabaseKDB?.loadedCipherKey = value
mDatabaseKDBX?.loadedCipherKey = value
mDatabaseKDB?.binaryCache = value
mDatabaseKDBX?.binaryCache = value
}
get() {
return mDatabaseKDB?.loadedCipherKey ?: mDatabaseKDBX?.loadedCipherKey
return mDatabaseKDB?.binaryCache ?: mDatabaseKDBX?.binaryCache ?: BinaryCache()
}
private val iconsManager: IconsManager
@@ -136,7 +136,7 @@ class Database {
fun removeCustomIcon(customIcon: IconImageCustom) {
iconDrawableFactory.clearFromCache(customIcon)
iconsManager.removeCustomIcon(customIcon.uuid)
iconsManager.removeCustomIcon(binaryCache, customIcon.uuid)
}
val allowName: Boolean
@@ -381,7 +381,7 @@ class Database {
fun createData(databaseUri: Uri, databaseName: String, rootName: String) {
val newDatabase = DatabaseKDBX(databaseName, rootName)
newDatabase.loadedCipherKey = LoadedKey.generateNewCipherKey()
newDatabase.binaryCache.loadedCipherKey = LoadedKey.generateNewCipherKey()
setDatabaseKDBX(newDatabase)
this.fileUri = databaseUri
// Set Database state
@@ -669,6 +669,7 @@ class Database {
}
fun clear(filesDirectory: File? = null) {
binaryCache.clear()
iconsManager.clearCache()
iconDrawableFactory.clearCache()
// Delete the cache of the database if present

View File

@@ -24,21 +24,16 @@ import android.os.Parcelable
import android.util.Base64
import android.util.Base64InputStream
import android.util.Base64OutputStream
import com.kunzisoft.keepass.database.element.Database
import com.kunzisoft.keepass.stream.readAllBytes
import java.io.*
import java.util.zip.GZIPOutputStream
import javax.crypto.Cipher
import javax.crypto.CipherInputStream
import javax.crypto.CipherOutputStream
import javax.crypto.spec.IvParameterSpec
class BinaryByte : BinaryData {
private var mDataByteId: Int? = null
private fun getByteArray(): ByteArray {
val keyData = KeyByteArray.getByteArray(mDataByteId)
private fun getByteArray(binaryCache: BinaryCache): ByteArray {
val keyData = binaryCache.getByteArray(mDataByteId)
mDataByteId = keyData.key
return keyData.data
}
@@ -46,14 +41,10 @@ class BinaryByte : BinaryData {
/**
* Empty protected binary
*/
constructor() : super() {
getByteArray()
}
constructor() : super()
constructor(compressed: Boolean = false,
protected: Boolean = false) : super(compressed, protected) {
getByteArray()
}
protected: Boolean = false) : super(compressed, protected)
constructor(mDataByteId: Int,
compressed: Boolean = false,
@@ -73,27 +64,25 @@ class BinaryByte : BinaryData {
}
@Throws(IOException::class)
override fun getInputDataStream(cipherKey: Database.LoadedKey): InputStream {
override fun getInputDataStream(binaryCache: BinaryCache): InputStream {
return when {
getSize() > 0 -> {
cipherDecryption.init(Cipher.DECRYPT_MODE, cipherKey.key, IvParameterSpec(cipherKey.iv))
Base64InputStream(CipherInputStream(ByteArrayInputStream(getByteArray()), cipherDecryption), Base64.NO_WRAP)
Base64InputStream(ByteArrayInputStream(getByteArray(binaryCache)), Base64.NO_WRAP)
}
else -> ByteArrayInputStream(ByteArray(0))
}
}
@Throws(IOException::class)
override fun getOutputDataStream(cipherKey: Database.LoadedKey): OutputStream {
cipherEncryption.init(Cipher.ENCRYPT_MODE, cipherKey.key, IvParameterSpec(cipherKey.iv))
return BinaryCountingOutputStream(Base64OutputStream(CipherOutputStream(ByteOutputStream(), cipherEncryption), Base64.NO_WRAP))
override fun getOutputDataStream(binaryCache: BinaryCache): OutputStream {
return BinaryCountingOutputStream(Base64OutputStream(ByteOutputStream(binaryCache), Base64.NO_WRAP))
}
@Throws(IOException::class)
override fun compress(cipherKey: Database.LoadedKey) {
override fun compress(binaryCache: BinaryCache) {
if (!isCompressed) {
GZIPOutputStream(getOutputDataStream(cipherKey)).use { outputStream ->
getInputDataStream(cipherKey).use { inputStream ->
GZIPOutputStream(getOutputDataStream(binaryCache)).use { outputStream ->
getInputDataStream(binaryCache).use { inputStream ->
inputStream.readAllBytes { buffer ->
outputStream.write(buffer)
}
@@ -104,10 +93,10 @@ class BinaryByte : BinaryData {
}
@Throws(IOException::class)
override fun decompress(cipherKey: Database.LoadedKey) {
override fun decompress(binaryCache: BinaryCache) {
if (isCompressed) {
getUnGzipInputDataStream(cipherKey).use { inputStream ->
getOutputDataStream(cipherKey).use { outputStream ->
getUnGzipInputDataStream(binaryCache).use { inputStream ->
getOutputDataStream(binaryCache).use { outputStream ->
inputStream.readAllBytes { buffer ->
outputStream.write(buffer)
}
@@ -118,12 +107,14 @@ class BinaryByte : BinaryData {
}
@Throws(IOException::class)
override fun clear() {
KeyByteArray.removeByteArray(mDataByteId)
override fun delete() {
mDataByteId = null
}
override fun toString(): String {
return getByteArray().toString()
@Throws(IOException::class)
override fun clear(binaryCache: BinaryCache) {
binaryCache.removeByteArray(mDataByteId)
mDataByteId = null
}
override fun equals(other: Any?): Boolean {
@@ -145,9 +136,9 @@ class BinaryByte : BinaryData {
/**
* Custom OutputStream to calculate the size and hash of binary file
*/
private inner class ByteOutputStream : ByteArrayOutputStream() {
private inner class ByteOutputStream(private val binaryCache: BinaryCache) : ByteArrayOutputStream() {
override fun close() {
KeyByteArray.setByteArray(mDataByteId, this.toByteArray())
binaryCache.setByteArray(mDataByteId, this.toByteArray())
super.close()
}
}

View File

@@ -0,0 +1,66 @@
package com.kunzisoft.keepass.database.element.database
import com.kunzisoft.keepass.database.element.Database
import java.util.*
class BinaryCache {
/**
* Cipher key generated when the database is loaded, and destroyed when the database is closed
* Can be used to temporarily store database elements
*/
var loadedCipherKey: Database.LoadedKey? = null
// Similar to file storage but much faster
private val byteArrayList = HashMap<Int, ByteArray>()
fun getByteArray(key: Int?): KeyByteArray {
if (key == null) {
val newItem = KeyByteArray(byteArrayList.size, ByteArray(0))
byteArrayList[newItem.key] = newItem.data
return newItem
}
if (!byteArrayList.containsKey(key)) {
val newItem = KeyByteArray(key, ByteArray(0))
byteArrayList[newItem.key] = newItem.data
return newItem
}
return KeyByteArray(key, byteArrayList[key]!!)
}
fun setByteArray(key: Int?, data: ByteArray): KeyByteArray {
return if (key == null) {
val keyByteArray = KeyByteArray(byteArrayList.size, data)
byteArrayList[keyByteArray.key] = keyByteArray.data
keyByteArray
} else {
byteArrayList[key] = data
KeyByteArray(key, data)
}
}
fun removeByteArray(key: Int?) {
key?.let {
byteArrayList.remove(it)
}
}
fun clear() {
byteArrayList.clear()
}
data class KeyByteArray(val key: Int, val data: ByteArray) {
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (other !is KeyByteArray) return false
if (key != other.key) return false
return true
}
override fun hashCode(): Int {
return key
}
}
}

View File

@@ -71,37 +71,40 @@ abstract class BinaryData : Parcelable {
}
@Throws(IOException::class)
abstract fun getInputDataStream(cipherKey: Database.LoadedKey): InputStream
abstract fun getInputDataStream(binaryCache: BinaryCache): InputStream
@Throws(IOException::class)
abstract fun getOutputDataStream(cipherKey: Database.LoadedKey): OutputStream
abstract fun getOutputDataStream(binaryCache: BinaryCache): OutputStream
@Throws(IOException::class)
fun getUnGzipInputDataStream(cipherKey: Database.LoadedKey): InputStream {
fun getUnGzipInputDataStream(binaryCache: BinaryCache): InputStream {
return if (isCompressed) {
GZIPInputStream(getInputDataStream(cipherKey))
GZIPInputStream(getInputDataStream(binaryCache))
} else {
getInputDataStream(cipherKey)
getInputDataStream(binaryCache)
}
}
@Throws(IOException::class)
fun getGzipOutputDataStream(cipherKey: Database.LoadedKey): OutputStream {
fun getGzipOutputDataStream(binaryCache: BinaryCache): OutputStream {
return if (isCompressed) {
GZIPOutputStream(getOutputDataStream(cipherKey))
GZIPOutputStream(getOutputDataStream(binaryCache))
} else {
getOutputDataStream(cipherKey)
getOutputDataStream(binaryCache)
}
}
@Throws(IOException::class)
abstract fun compress(cipherKey: Database.LoadedKey)
abstract fun compress(binaryCache: BinaryCache)
@Throws(IOException::class)
abstract fun decompress(cipherKey: Database.LoadedKey)
abstract fun decompress(binaryCache: BinaryCache)
@Throws(IOException::class)
abstract fun clear()
abstract fun delete()
@Throws(IOException::class)
abstract fun clear(binaryCache: BinaryCache)
open fun dataExists(): Boolean {
return mLength > 0

View File

@@ -24,12 +24,8 @@ import android.os.Parcelable
import android.util.Base64
import android.util.Base64InputStream
import android.util.Base64OutputStream
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.nio.ByteBuffer
import java.security.MessageDigest
import java.util.zip.GZIPOutputStream
import javax.crypto.Cipher
import javax.crypto.CipherInputStream
@@ -55,19 +51,20 @@ class BinaryFile : BinaryData {
}
@Throws(IOException::class)
override fun getInputDataStream(cipherKey: Database.LoadedKey): InputStream {
return buildInputStream(mDataFile, cipherKey)
override fun getInputDataStream(binaryCache: BinaryCache): InputStream {
return buildInputStream(mDataFile, binaryCache)
}
@Throws(IOException::class)
override fun getOutputDataStream(cipherKey: Database.LoadedKey): OutputStream {
return buildOutputStream(mDataFile, cipherKey)
override fun getOutputDataStream(binaryCache: BinaryCache): OutputStream {
return buildOutputStream(mDataFile, binaryCache)
}
@Throws(IOException::class)
private fun buildInputStream(file: File?, cipherKey: Database.LoadedKey): InputStream {
private fun buildInputStream(file: File?, binaryCache: BinaryCache): InputStream {
val cipherKey = binaryCache.loadedCipherKey
return when {
file != null && file.length() > 0 -> {
cipherKey != null && file != null && file.length() > 0 -> {
cipherDecryption.init(Cipher.DECRYPT_MODE, cipherKey.key, IvParameterSpec(cipherKey.iv))
Base64InputStream(CipherInputStream(FileInputStream(file), cipherDecryption), Base64.NO_WRAP)
}
@@ -76,9 +73,10 @@ class BinaryFile : BinaryData {
}
@Throws(IOException::class)
private fun buildOutputStream(file: File?, cipherKey: Database.LoadedKey): OutputStream {
private fun buildOutputStream(file: File?, binaryCache: BinaryCache): OutputStream {
val cipherKey = binaryCache.loadedCipherKey
return when {
file != null -> {
cipherKey != null && file != null -> {
cipherEncryption.init(Cipher.ENCRYPT_MODE, cipherKey.key, IvParameterSpec(cipherKey.iv))
BinaryCountingOutputStream(Base64OutputStream(CipherOutputStream(FileOutputStream(file), cipherEncryption), Base64.NO_WRAP))
}
@@ -87,14 +85,14 @@ class BinaryFile : BinaryData {
}
@Throws(IOException::class)
override fun compress(cipherKey: Database.LoadedKey) {
override fun compress(binaryCache: BinaryCache) {
mDataFile?.let { concreteDataFile ->
// To compress, create a new binary with file
if (!isCompressed) {
// Encrypt the new gzipped temp file
val fileBinaryCompress = File(concreteDataFile.parent, concreteDataFile.name + "_temp")
getInputDataStream(cipherKey).use { inputStream ->
GZIPOutputStream(buildOutputStream(fileBinaryCompress, cipherKey)).use { outputStream ->
getInputDataStream(binaryCache).use { inputStream ->
GZIPOutputStream(buildOutputStream(fileBinaryCompress, binaryCache)).use { outputStream ->
inputStream.readAllBytes { buffer ->
outputStream.write(buffer)
}
@@ -112,13 +110,13 @@ class BinaryFile : BinaryData {
}
@Throws(IOException::class)
override fun decompress(cipherKey: Database.LoadedKey) {
override fun decompress(binaryCache: BinaryCache) {
mDataFile?.let { concreteDataFile ->
if (isCompressed) {
// Encrypt the new ungzipped temp file
val fileBinaryDecompress = File(concreteDataFile.parent, concreteDataFile.name + "_temp")
getUnGzipInputDataStream(cipherKey).use { inputStream ->
buildOutputStream(fileBinaryDecompress, cipherKey).use { outputStream ->
getUnGzipInputDataStream(binaryCache).use { inputStream ->
buildOutputStream(fileBinaryDecompress, binaryCache).use { outputStream ->
inputStream.readAllBytes { buffer ->
outputStream.write(buffer)
}
@@ -135,12 +133,16 @@ class BinaryFile : BinaryData {
}
}
@Throws(IOException::class)
override fun clear() {
override fun delete() {
if (mDataFile != null && !mDataFile!!.delete())
throw IOException("Unable to delete temp file " + mDataFile!!.absolutePath)
}
@Throws(IOException::class)
override fun clear(binaryCache: BinaryCache) {
delete()
}
override fun dataExists(): Boolean {
return mDataFile != null && super.dataExists()
}

View File

@@ -20,7 +20,6 @@
package com.kunzisoft.keepass.database.element.database
import android.util.Log
import java.io.File
import java.io.IOException
import kotlin.math.abs
@@ -227,7 +226,7 @@ abstract class BinaryPool<T> {
@Throws(IOException::class)
fun clear() {
doForEachBinary { _, binary ->
binary.clear()
binary.delete()
}
pool.clear()
}

View File

@@ -212,10 +212,8 @@ class DatabaseKDBX : DatabaseVersioned<UUID, UUID, GroupKDBX, EntryKDBX> {
private fun compressAllBinaries() {
binaryPool.doForEachBinary { _, binary ->
try {
val cipherKey = loadedCipherKey
?: throw IOException("Unable to retrieve cipher key to compress binaries")
// To compress, create a new binary with file
binary.compress(cipherKey)
binary.compress(binaryCache)
} catch (e: Exception) {
Log.e(TAG, "Unable to compress $binary", e)
}
@@ -225,9 +223,7 @@ class DatabaseKDBX : DatabaseVersioned<UUID, UUID, GroupKDBX, EntryKDBX> {
private fun decompressAllBinaries() {
binaryPool.doForEachBinary { _, binary ->
try {
val cipherKey = loadedCipherKey
?: throw IOException("Unable to retrieve cipher key to decompress binaries")
binary.decompress(cipherKey)
binary.decompress(binaryCache)
} catch (e: Exception) {
Log.e(TAG, "Unable to decompress $binary", e)
}
@@ -690,7 +686,7 @@ class DatabaseKDBX : DatabaseVersioned<UUID, UUID, GroupKDBX, EntryKDBX> {
try {
binaryPool.remove(it)
if (clear)
it.clear()
it.clear(binaryCache)
} catch (e: Exception) {
Log.w(TAG, "Unable to clean binaries", e)
}

View File

@@ -57,10 +57,11 @@ abstract class DatabaseVersioned<
protected set
/**
* To manage binaries in faster way
* Cipher key generated when the database is loaded, and destroyed when the database is closed
* Can be used to temporarily store database elements
*/
var loadedCipherKey: Database.LoadedKey? = null
var binaryCache = BinaryCache()
val iconsManager = IconsManager()

View File

@@ -1,58 +0,0 @@
package com.kunzisoft.keepass.database.element.database
import java.util.HashMap
class KeyByteArray {
data class KeyByteArray(val key: Int, val data: ByteArray) {
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (other !is KeyByteArray) return false
if (key != other.key) return false
return true
}
override fun hashCode(): Int {
return key
}
}
companion object {
// Similar to file storage but much faster
private val byteArrayList = HashMap<Int, ByteArray>()
fun getByteArray(key: Int?): KeyByteArray {
if (key == null) {
val newItem = KeyByteArray(byteArrayList.size, ByteArray(0))
byteArrayList[newItem.key] = newItem.data
return newItem
}
if (!byteArrayList.containsKey(key)) {
val newItem = KeyByteArray(key, ByteArray(0))
byteArrayList[newItem.key] = newItem.data
return newItem
}
return KeyByteArray(key, byteArrayList[key]!!)
}
fun setByteArray(key: Int?, data: ByteArray): KeyByteArray {
return if (key == null) {
val keyByteArray = KeyByteArray(byteArrayList.size, data)
byteArrayList[keyByteArray.key] = keyByteArray.data
keyByteArray
} else {
byteArrayList[key] = data
KeyByteArray(key, data)
}
}
fun removeByteArray(key: Int?) {
key?.let {
byteArrayList.remove(it)
}
}
}
}

View File

@@ -20,10 +20,7 @@
package com.kunzisoft.keepass.database.element.icon
import android.util.Log
import com.kunzisoft.keepass.database.element.database.BinaryByte
import com.kunzisoft.keepass.database.element.database.BinaryData
import com.kunzisoft.keepass.database.element.database.BinaryFile
import com.kunzisoft.keepass.database.element.database.CustomIconPool
import com.kunzisoft.keepass.database.element.database.*
import com.kunzisoft.keepass.database.element.icon.IconImageStandard.Companion.KEY_ID
import com.kunzisoft.keepass.icons.IconPack.Companion.NB_ICONS
import java.io.File
@@ -82,11 +79,11 @@ class IconsManager {
return customCache.isBinaryDuplicate(binaryData)
}
fun removeCustomIcon(iconUuid: UUID) {
fun removeCustomIcon(binaryCache: BinaryCache, iconUuid: UUID) {
val binary = customCache[iconUuid]
customCache.remove(iconUuid)
try {
binary?.clear()
binary?.clear(binaryCache)
} catch (e: Exception) {
Log.w(TAG, "Unable to remove custom icon binary", e)
}

View File

@@ -60,7 +60,7 @@ class DatabaseInputKDB(cacheDirectory: File,
progressTaskUpdater: ProgressTaskUpdater?,
fixDuplicateUUID: Boolean): DatabaseKDB {
return openDatabase(databaseInputStream, progressTaskUpdater, fixDuplicateUUID) {
mDatabase.loadedCipherKey = loadedCipherKey
mDatabase.binaryCache.loadedCipherKey = loadedCipherKey
mDatabase.retrieveMasterKey(password, keyfileInputStream)
}
}
@@ -72,7 +72,7 @@ class DatabaseInputKDB(cacheDirectory: File,
progressTaskUpdater: ProgressTaskUpdater?,
fixDuplicateUUID: Boolean): DatabaseKDB {
return openDatabase(databaseInputStream, progressTaskUpdater, fixDuplicateUUID) {
mDatabase.loadedCipherKey = loadedCipherKey
mDatabase.binaryCache.loadedCipherKey = loadedCipherKey
mDatabase.masterKey = masterKey
}
}
@@ -309,9 +309,7 @@ class DatabaseInputKDB(cacheDirectory: File,
if (fieldSize > 0) {
val binaryData = mDatabase.buildNewAttachment(cacheDirectory)
entry.putBinary(binaryData, mDatabase.binaryPool)
val cipherKey = mDatabase.loadedCipherKey
?: throw IOException("Unable to retrieve cipher key to load binaries")
BufferedOutputStream(binaryData.getOutputDataStream(cipherKey)).use { outputStream ->
BufferedOutputStream(binaryData.getOutputDataStream(mDatabase.binaryCache)).use { outputStream ->
cipherInputStream.readBytes(fieldSize) { buffer ->
outputStream.write(buffer)
}

View File

@@ -102,7 +102,7 @@ class DatabaseInputKDBX(cacheDirectory: File,
progressTaskUpdater: ProgressTaskUpdater?,
fixDuplicateUUID: Boolean): DatabaseKDBX {
return openDatabase(databaseInputStream, progressTaskUpdater, fixDuplicateUUID) {
mDatabase.loadedCipherKey = loadedCipherKey
mDatabase.binaryCache.loadedCipherKey = loadedCipherKey
mDatabase.retrieveMasterKey(password, keyfileInputStream)
}
}
@@ -114,7 +114,7 @@ class DatabaseInputKDBX(cacheDirectory: File,
progressTaskUpdater: ProgressTaskUpdater?,
fixDuplicateUUID: Boolean): DatabaseKDBX {
return openDatabase(databaseInputStream, progressTaskUpdater, fixDuplicateUUID) {
mDatabase.loadedCipherKey = loadedCipherKey
mDatabase.binaryCache.loadedCipherKey = loadedCipherKey
mDatabase.masterKey = masterKey
}
}
@@ -279,9 +279,7 @@ class DatabaseInputKDBX(cacheDirectory: File,
// No compression at this level
val protectedBinary = mDatabase.buildNewAttachment(cacheDirectory,
isRAMSufficient.invoke(byteLength.toLong()), false, protectedFlag)
val cipherKey = mDatabase.loadedCipherKey
?: throw IOException("Unable to retrieve cipher key to load binaries")
protectedBinary.getOutputDataStream(cipherKey).use { outputStream ->
protectedBinary.getOutputDataStream(mDatabase.binaryCache).use { outputStream ->
dataInputStream.readBytes(byteLength) { buffer ->
outputStream.write(buffer)
}
@@ -708,10 +706,8 @@ class DatabaseInputKDBX(cacheDirectory: File,
val iconData = customIconData
if (customIconID != DatabaseVersioned.UUID_ZERO && iconData != null) {
mDatabase.addCustomIcon(cacheDirectory, customIconID, isRAMSufficient.invoke(iconData.size.toLong())) { _, binary ->
mDatabase.loadedCipherKey?.let { cipherKey ->
binary?.getOutputDataStream(cipherKey)?.use { outputStream ->
outputStream.write(iconData)
}
binary?.getOutputDataStream(mDatabase.binaryCache)?.use { outputStream ->
outputStream.write(iconData)
}
}
}
@@ -1023,16 +1019,14 @@ class DatabaseInputKDBX(cacheDirectory: File,
// Build the new binary and compress
val binaryAttachment = mDatabase.buildNewAttachment(cacheDirectory,
isRAMSufficient.invoke(base64.length.toLong()), compressed, protected, binaryId)
val binaryCipherKey = mDatabase.loadedCipherKey
?: throw IOException("Unable to retrieve cipher key to load binaries")
try {
binaryAttachment.getOutputDataStream(binaryCipherKey).use { outputStream ->
binaryAttachment.getOutputDataStream(mDatabase.binaryCache).use { outputStream ->
outputStream.write(Base64.decode(base64, BASE_64_FLAG))
}
} catch (e: Exception) {
Log.e(TAG, "Unable to read base 64 attachment", e)
binaryAttachment.isCorrupted = true
binaryAttachment.getOutputDataStream(binaryCipherKey).use { outputStream ->
binaryAttachment.getOutputDataStream(mDatabase.binaryCache).use { outputStream ->
outputStream.write(base64.toByteArray())
}
}

View File

@@ -217,7 +217,7 @@ class DatabaseOutputKDB(private val mDatabaseKDB: DatabaseKDB,
}
// Entries
mDatabaseKDB.doForEachEntryInIndex { entry ->
EntryOutputKDB(entry, outputStream, mDatabaseKDB.loadedCipherKey).output(mDatabaseKDB)
EntryOutputKDB(mDatabaseKDB, entry, outputStream).output()
}
}

View File

@@ -136,28 +136,26 @@ class DatabaseOutputKDBX(private val mDatabaseKDBX: DatabaseKDBX,
dataOutputStream.writeInt(streamKeySize)
dataOutputStream.write(header.innerRandomStreamKey)
database.loadedCipherKey?.let { binaryCipherKey ->
database.binaryPool.doForEachOrderedBinaryWithoutDuplication { _, binary ->
// Force decompression to add binary in header
binary.decompress(binaryCipherKey)
// Write type binary
dataOutputStream.writeByte(DatabaseHeaderKDBX.PwDbInnerHeaderV4Fields.Binary)
// Write size
dataOutputStream.writeUInt(UnsignedInt.fromKotlinLong(binary.getSize() + 1))
// Write protected flag
var flag = DatabaseHeaderKDBX.KdbxBinaryFlags.None
if (binary.isProtected) {
flag = flag or DatabaseHeaderKDBX.KdbxBinaryFlags.Protected
}
dataOutputStream.writeByte(flag)
database.binaryPool.doForEachOrderedBinaryWithoutDuplication { _, binary ->
// Force decompression to add binary in header
binary.decompress(database.binaryCache)
// Write type binary
dataOutputStream.writeByte(DatabaseHeaderKDBX.PwDbInnerHeaderV4Fields.Binary)
// Write size
dataOutputStream.writeUInt(UnsignedInt.fromKotlinLong(binary.getSize() + 1))
// Write protected flag
var flag = DatabaseHeaderKDBX.KdbxBinaryFlags.None
if (binary.isProtected) {
flag = flag or DatabaseHeaderKDBX.KdbxBinaryFlags.Protected
}
dataOutputStream.writeByte(flag)
binary.getInputDataStream(binaryCipherKey).use { inputStream ->
inputStream.readAllBytes { buffer ->
dataOutputStream.write(buffer)
}
binary.getInputDataStream(database.binaryCache).use { inputStream ->
inputStream.readAllBytes { buffer ->
dataOutputStream.write(buffer)
}
}
} ?: Log.e(TAG, "Unable to retrieve cipher key to write head binaries")
}
dataOutputStream.writeByte(DatabaseHeaderKDBX.PwDbInnerHeaderV4Fields.EndOfHeader)
dataOutputStream.writeInt(0)
@@ -494,31 +492,29 @@ class DatabaseOutputKDBX(private val mDatabaseKDBX: DatabaseKDBX,
// With kdbx4, don't use this method because binaries are in header file
@Throws(IllegalArgumentException::class, IllegalStateException::class, IOException::class)
private fun writeMetaBinaries() {
mDatabaseKDBX.loadedCipherKey?.let { binaryCipherKey ->
xml.startTag(null, DatabaseKDBXXML.ElemBinaries)
// Use indexes because necessarily (binary header ref is the order)
mDatabaseKDBX.binaryPool.doForEachOrderedBinaryWithoutDuplication { index, binary ->
xml.startTag(null, DatabaseKDBXXML.ElemBinary)
xml.attribute(null, DatabaseKDBXXML.AttrId, index.toString())
if (binary.getSize() > 0) {
if (binary.isCompressed) {
xml.attribute(null, DatabaseKDBXXML.AttrCompressed, DatabaseKDBXXML.ValTrue)
}
try {
// Write the XML
binary.getInputDataStream(binaryCipherKey).use { inputStream ->
inputStream.readAllBytes { buffer ->
xml.text(String(Base64.encode(buffer, BASE_64_FLAG)))
}
}
} catch (e: Exception) {
Log.e(TAG, "Unable to write binary", e)
}
xml.startTag(null, DatabaseKDBXXML.ElemBinaries)
// Use indexes because necessarily (binary header ref is the order)
mDatabaseKDBX.binaryPool.doForEachOrderedBinaryWithoutDuplication { index, binary ->
xml.startTag(null, DatabaseKDBXXML.ElemBinary)
xml.attribute(null, DatabaseKDBXXML.AttrId, index.toString())
if (binary.getSize() > 0) {
if (binary.isCompressed) {
xml.attribute(null, DatabaseKDBXXML.AttrCompressed, DatabaseKDBXXML.ValTrue)
}
try {
// Write the XML
binary.getInputDataStream(mDatabaseKDBX.binaryCache).use { inputStream ->
inputStream.readAllBytes { buffer ->
xml.text(String(Base64.encode(buffer, BASE_64_FLAG)))
}
}
} catch (e: Exception) {
Log.e(TAG, "Unable to write binary", e)
}
xml.endTag(null, DatabaseKDBXXML.ElemBinary)
}
xml.endTag(null, DatabaseKDBXXML.ElemBinaries)
} ?: Log.e(TAG, "Unable to retrieve cipher key to write binaries")
xml.endTag(null, DatabaseKDBXXML.ElemBinary)
}
xml.endTag(null, DatabaseKDBXXML.ElemBinaries)
}
@Throws(IllegalArgumentException::class, IllegalStateException::class, IOException::class)
@@ -699,39 +695,37 @@ class DatabaseOutputKDBX(private val mDatabaseKDBX: DatabaseKDBX,
@Throws(IllegalArgumentException::class, IllegalStateException::class, IOException::class)
private fun writeCustomIconList() {
mDatabaseKDBX.loadedCipherKey?.let { cipherKey ->
var firstElement = true
mDatabaseKDBX.iconsManager.doForEachCustomIcon { iconCustom, binary ->
if (binary.dataExists()) {
// Write the parent tag
if (firstElement) {
xml.startTag(null, DatabaseKDBXXML.ElemCustomIcons)
firstElement = false
}
xml.startTag(null, DatabaseKDBXXML.ElemCustomIconItem)
writeUuid(DatabaseKDBXXML.ElemCustomIconItemID, iconCustom.uuid)
var customImageData = ByteArray(0)
try {
binary.getInputDataStream(cipherKey).use { inputStream ->
customImageData = inputStream.readBytes()
}
} catch (e: Exception) {
Log.e(TAG, "Unable to write custom icon", e)
} finally {
writeObject(DatabaseKDBXXML.ElemCustomIconItemData,
String(Base64.encode(customImageData, BASE_64_FLAG)))
}
xml.endTag(null, DatabaseKDBXXML.ElemCustomIconItem)
var firstElement = true
mDatabaseKDBX.iconsManager.doForEachCustomIcon { iconCustom, binary ->
if (binary.dataExists()) {
// Write the parent tag
if (firstElement) {
xml.startTag(null, DatabaseKDBXXML.ElemCustomIcons)
firstElement = false
}
xml.startTag(null, DatabaseKDBXXML.ElemCustomIconItem)
writeUuid(DatabaseKDBXXML.ElemCustomIconItemID, iconCustom.uuid)
var customImageData = ByteArray(0)
try {
binary.getInputDataStream(mDatabaseKDBX.binaryCache).use { inputStream ->
customImageData = inputStream.readBytes()
}
} catch (e: Exception) {
Log.e(TAG, "Unable to write custom icon", e)
} finally {
writeObject(DatabaseKDBXXML.ElemCustomIconItemData,
String(Base64.encode(customImageData, BASE_64_FLAG)))
}
xml.endTag(null, DatabaseKDBXXML.ElemCustomIconItem)
}
// Close the parent tag
if (!firstElement) {
xml.endTag(null, DatabaseKDBXXML.ElemCustomIcons)
}
} ?: Log.e(TAG, "Unable to retrieve cipher key to write custom icons")
}
// Close the parent tag
if (!firstElement) {
xml.endTag(null, DatabaseKDBXXML.ElemCustomIcons)
}
}
private fun safeXmlString(text: String): String {

View File

@@ -19,8 +19,6 @@
*/
package com.kunzisoft.keepass.database.file.output
import android.util.Log
import com.kunzisoft.keepass.database.element.Database
import com.kunzisoft.keepass.database.element.database.DatabaseKDB
import com.kunzisoft.keepass.database.element.entry.EntryKDB
import com.kunzisoft.keepass.database.exception.DatabaseOutputException
@@ -34,13 +32,13 @@ import java.nio.charset.Charset
/**
* Output the GroupKDB to the stream
*/
class EntryOutputKDB(private val mEntry: EntryKDB,
private val mOutputStream: OutputStream,
private val mCipherKey: Database.LoadedKey?) {
class EntryOutputKDB(private val mDatabase: DatabaseKDB,
private val mEntry: EntryKDB,
private val mOutputStream: OutputStream) {
//NOTE: Need be to careful about using ints. The actual type written to file is a unsigned int
@Throws(DatabaseOutputException::class)
fun output(database: DatabaseKDB) {
fun output() {
try {
// UUID
mOutputStream.write(UUID_FIELD_TYPE)
@@ -95,22 +93,20 @@ class EntryOutputKDB(private val mEntry: EntryKDB,
StringDatabaseKDBUtils.writeStringToStream(mOutputStream, mEntry.binaryDescription)
// Binary
mCipherKey?.let { cipherKey ->
mOutputStream.write(BINARY_DATA_FIELD_TYPE)
val binaryData = mEntry.getBinary(database.binaryPool)
val binaryDataLength = binaryData?.getSize() ?: 0L
// Write data length
mOutputStream.write(uIntTo4Bytes(UnsignedInt.fromKotlinLong(binaryDataLength)))
// Write data
if (binaryDataLength > 0) {
binaryData?.getInputDataStream(cipherKey).use { inputStream ->
inputStream?.readAllBytes { buffer ->
mOutputStream.write(buffer)
}
inputStream?.close()
mOutputStream.write(BINARY_DATA_FIELD_TYPE)
val binaryData = mEntry.getBinary(mDatabase.binaryPool)
val binaryDataLength = binaryData?.getSize() ?: 0L
// Write data length
mOutputStream.write(uIntTo4Bytes(UnsignedInt.fromKotlinLong(binaryDataLength)))
// Write data
if (binaryDataLength > 0) {
binaryData?.getInputDataStream(mDatabase.binaryCache).use { inputStream ->
inputStream?.readAllBytes { buffer ->
mOutputStream.write(buffer)
}
inputStream?.close()
}
} ?: Log.e(TAG, "Unable to retrieve cipher key to write entry binary")
}
// End
mOutputStream.write(END_FIELD_TYPE)

View File

@@ -33,8 +33,8 @@ 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.database.BinaryData
import com.kunzisoft.keepass.database.element.database.BinaryCache
import com.kunzisoft.keepass.database.element.icon.IconImageCustom
import com.kunzisoft.keepass.database.element.icon.IconImageDraw
import kotlinx.coroutines.CoroutineScope
@@ -48,7 +48,7 @@ import kotlin.collections.HashMap
/**
* 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(private val retrieveCipherKey : () -> Database.LoadedKey?,
class IconDrawableFactory(private val retrieveBinaryCache : () -> BinaryCache?,
private val retrieveCustomIconBinary : (iconId: UUID) -> BinaryData?) {
/** customIconMap
@@ -87,13 +87,13 @@ class IconDrawableFactory(private val retrieveCipherKey : () -> Database.LoadedK
*/
private fun getIconDrawable(resources: Resources, icon: IconImageCustom, iconCustomBinary: BinaryData?): Drawable? {
val patternIcon = PatternIcon(resources)
val cipherKey = retrieveCipherKey()
if (cipherKey != null) {
val binaryManager = retrieveBinaryCache()
if (binaryManager != null) {
val draw: Drawable? = customIconMap[icon.uuid]?.get()
if (draw == null) {
iconCustomBinary?.let { binaryFile ->
try {
var bitmap: Bitmap? = BitmapFactory.decodeStream(binaryFile.getInputDataStream(cipherKey))
var bitmap: Bitmap? = BitmapFactory.decodeStream(binaryFile.getInputDataStream(binaryManager))
bitmap?.let { bitmapIcon ->
bitmap = resize(bitmapIcon, patternIcon)
val createdDraw = BitmapDrawable(resources, bitmap)

View File

@@ -29,6 +29,7 @@ import android.util.Log
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.model.AttachmentState
import com.kunzisoft.keepass.model.EntryAttachmentState
import com.kunzisoft.keepass.model.StreamDirection
@@ -48,6 +49,8 @@ class AttachmentFileNotificationService: LockNotificationService() {
private val mainScope = CoroutineScope(Dispatchers.Main)
private var mDatabase: Database? = Database.getInstance()
override fun retrieveChannelId(): String {
return CHANNEL_ATTACHMENT_ID
}
@@ -285,11 +288,14 @@ class AttachmentFileNotificationService: LockNotificationService() {
// Add action to the list on start
attachmentNotificationList.add(attachmentNotification)
mainScope.launch {
AttachmentFileAction(attachmentNotification,
contentResolver).apply {
listener = attachmentFileActionListener
}.executeAction()
mDatabase?.let { database ->
mainScope.launch {
AttachmentFileAction(attachmentNotification,
database,
contentResolver).apply {
listener = attachmentFileActionListener
}.executeAction()
}
}
}
} catch (e: Exception) {
@@ -313,6 +319,7 @@ class AttachmentFileNotificationService: LockNotificationService() {
private class AttachmentFileAction(
private val attachmentNotification: AttachmentNotification,
private val database: Database,
private val contentResolver: ContentResolver) {
private val updateMinFrequency = 1000
@@ -345,6 +352,7 @@ class AttachmentFileNotificationService: LockNotificationService() {
when (streamDirection) {
StreamDirection.UPLOAD -> {
BinaryDatabaseManager.uploadToDatabase(
database,
attachmentNotification.uri,
attachment.binaryData,
contentResolver,
@@ -358,6 +366,7 @@ class AttachmentFileNotificationService: LockNotificationService() {
}
StreamDirection.DOWNLOAD -> {
BinaryDatabaseManager.downloadFromDatabase(
database,
attachmentNotification.uri,
attachment.binaryData,
contentResolver,

View File

@@ -7,6 +7,7 @@ import android.net.Uri
import android.util.Log
import com.kunzisoft.keepass.database.element.Database
import com.kunzisoft.keepass.database.element.database.BinaryData
import com.kunzisoft.keepass.database.element.database.BinaryCache
import com.kunzisoft.keepass.stream.readAllBytes
import com.kunzisoft.keepass.utils.UriUtil
import kotlinx.coroutines.*
@@ -21,41 +22,42 @@ import kotlin.math.pow
object BinaryDatabaseManager {
fun downloadFromDatabase(attachmentToUploadUri: Uri,
fun downloadFromDatabase(database: Database,
attachmentToUploadUri: Uri,
binaryData: BinaryData,
contentResolver: ContentResolver,
update: ((percent: Int)->Unit)? = null,
canceled: ()-> Boolean = { false },
bufferSize: Int = DEFAULT_BUFFER_SIZE) {
UriUtil.getUriOutputStream(contentResolver, attachmentToUploadUri)?.use { outputStream ->
downloadFromDatabase(outputStream, binaryData, update, canceled, bufferSize)
downloadFromDatabase(database.binaryCache, outputStream, binaryData, update, canceled, bufferSize)
}
}
private fun downloadFromDatabase(outputStream: OutputStream,
private fun downloadFromDatabase(binaryCache: BinaryCache,
outputStream: OutputStream,
binaryData: BinaryData,
update: ((percent: Int)->Unit)? = null,
canceled: ()-> Boolean = { false },
bufferSize: Int = DEFAULT_BUFFER_SIZE) {
val fileSize = binaryData.getSize()
var dataDownloaded = 0L
Database.getInstance().loadedCipherKey?.let { binaryCipherKey ->
binaryData.getUnGzipInputDataStream(binaryCipherKey).use { inputStream ->
inputStream.readAllBytes(bufferSize, canceled) { buffer ->
outputStream.write(buffer)
dataDownloaded += buffer.size
try {
val percentDownload = (100 * dataDownloaded / fileSize).toInt()
update?.invoke(percentDownload)
} catch (e: Exception) {
Log.w(TAG, "Unable to call update callback during download", e)
}
binaryData.getUnGzipInputDataStream(binaryCache).use { inputStream ->
inputStream.readAllBytes(bufferSize, canceled) { buffer ->
outputStream.write(buffer)
dataDownloaded += buffer.size
try {
val percentDownload = (100 * dataDownloaded / fileSize).toInt()
update?.invoke(percentDownload)
} catch (e: Exception) {
Log.w(TAG, "Unable to call update callback during download", e)
}
}
}
}
fun uploadToDatabase(attachmentFromDownloadUri: Uri,
fun uploadToDatabase(database: Database,
attachmentFromDownloadUri: Uri,
binaryData: BinaryData,
contentResolver: ContentResolver,
update: ((percent: Int)->Unit)? = null,
@@ -63,34 +65,34 @@ object BinaryDatabaseManager {
bufferSize: Int = DEFAULT_BUFFER_SIZE) {
val fileSize = contentResolver.openFileDescriptor(attachmentFromDownloadUri, "r")?.statSize ?: 0
UriUtil.getUriInputStream(contentResolver, attachmentFromDownloadUri)?.use { inputStream ->
uploadToDatabase(inputStream, fileSize, binaryData, update, canceled, bufferSize)
uploadToDatabase(database.binaryCache, inputStream, fileSize, binaryData, update, canceled, bufferSize)
}
}
private fun uploadToDatabase(inputStream: InputStream,
private fun uploadToDatabase(binaryCache: BinaryCache,
inputStream: InputStream,
fileSize: Long,
binaryData: BinaryData,
update: ((percent: Int)->Unit)? = null,
canceled: ()-> Boolean = { false },
bufferSize: Int = DEFAULT_BUFFER_SIZE) {
var dataUploaded = 0L
Database.getInstance().loadedCipherKey?.let { binaryCipherKey ->
binaryData.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.w(TAG, "Unable to call update callback during upload", e)
}
binaryData.getGzipOutputDataStream(binaryCache).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.w(TAG, "Unable to call update callback during upload", e)
}
}
}
}
fun resizeBitmapAndStoreDataInBinaryFile(contentResolver: ContentResolver,
database: Database,
bitmapUri: Uri?,
binaryData: BinaryData?) {
try {
@@ -103,6 +105,7 @@ object BinaryDatabaseManager {
val bitmapData: ByteArray = byteArrayOutputStream.toByteArray()
val byteArrayInputStream = ByteArrayInputStream(bitmapData)
uploadToDatabase(
database.binaryCache,
byteArrayInputStream,
bitmapData.size.toLong(),
binaryData
@@ -118,7 +121,6 @@ object BinaryDatabaseManager {
/**
* reduces the size of the image
* @param image
* @param maxSize
* @return
*/
@@ -136,20 +138,18 @@ object BinaryDatabaseManager {
return Bitmap.createScaledBitmap(this, width, height, true)
}
fun loadBitmap(binaryData: BinaryData,
binaryCipherKey: Database.LoadedKey?,
fun loadBitmap(database: Database,
binaryData: BinaryData,
maxWidth: Int,
actionOnFinish: (Bitmap?) -> Unit) {
CoroutineScope(Dispatchers.Main).launch {
withContext(Dispatchers.IO) {
val asyncResult: Deferred<Bitmap?> = async {
runCatching {
binaryCipherKey?.let { binaryKey ->
val bitmap: Bitmap? = decodeSampledBitmap(binaryData,
binaryKey,
maxWidth)
bitmap
}
val bitmap: Bitmap? = decodeSampledBitmap(binaryData,
database.binaryCache,
maxWidth)
bitmap
}.getOrNull()
}
withContext(Dispatchers.Main) {
@@ -160,13 +160,13 @@ object BinaryDatabaseManager {
}
private fun decodeSampledBitmap(binaryData: BinaryData,
binaryCipherKey: Database.LoadedKey,
binaryCache: BinaryCache,
maxWidth: Int): Bitmap? {
// First decode with inJustDecodeBounds=true to check dimensions
return BitmapFactory.Options().run {
try {
inJustDecodeBounds = true
binaryData.getUnGzipInputDataStream(binaryCipherKey).use {
binaryData.getUnGzipInputDataStream(binaryCache).use {
BitmapFactory.decodeStream(it, null, this)
}
// Calculate inSampleSize
@@ -178,7 +178,7 @@ object BinaryDatabaseManager {
// Decode bitmap with inSampleSize set
inJustDecodeBounds = false
binaryData.getUnGzipInputDataStream(binaryCipherKey).use {
binaryData.getUnGzipInputDataStream(binaryCache).use {
BitmapFactory.decodeStream(it, null, this)
}
} catch (e: Exception) {

View File

@@ -322,8 +322,8 @@ class EntryContentsView @JvmOverloads constructor(context: Context,
* -------------
*/
fun setAttachmentCipherKey(cipherKey: Database.LoadedKey?) {
attachmentsAdapter.binaryCipherKey = cipherKey
fun setAttachmentCipherKey(database: Database?) {
attachmentsAdapter.database = database
}
private fun showAttachments(show: Boolean) {