Skip to content

Commit

Permalink
Add core storage (#955)
Browse files Browse the repository at this point in the history
      **Background**

We are now using a platform-specific version of flipper storage
throughout the project.
This is not correct. As part of the overall migration of the project to
KMP, we are gradually moving to okio + core:storage

**Changes**

- Add `core:storage`
- Add `core:atomicfile`

**Test plan**

Try launch tests and also migrating from old version of application
  • Loading branch information
LionZXY authored Sep 17, 2024
1 parent 4d2b9e5 commit d12b5e9
Show file tree
Hide file tree
Showing 68 changed files with 913 additions and 435 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@ Attention: don't forget to add the flag for F-Droid before release
- [Refactor] Migrate :bridge:connection:* to KMP
- [Refactor] Migrate :bridge:synchronization, :core:ktx and :core:theme to KMP
- [Refactor] Remove ktorfit
- [Refactor] Add `core:storage`
- [Refactor] Add `core:atomicfile`
- [FIX] Distinct fap items by id in paging sources
- [FIX] Battery level charge
- [FIX] Button arrow tint
Expand Down
1 change: 1 addition & 0 deletions components/bridge/connection/sample/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ dependencies {
implementation(projects.components.core.di)
implementation(projects.components.core.log)
implementation(projects.components.core.preference)
implementation(projects.components.core.storage)

implementation(projects.components.bridge.connection.transport.ble.api)
implementation(projects.components.bridge.connection.transport.ble.impl)
Expand Down
3 changes: 3 additions & 0 deletions components/bridge/dao/impl/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -17,12 +17,15 @@ dependencies {
implementation(projects.components.core.log)
implementation(projects.components.core.ktx)
implementation(projects.components.core.preference)
implementation(projects.components.core.storage)

implementation(libs.room.runtime)
implementation(libs.room.ktx)
ksp(libs.room.ksp)

implementation(libs.kotlin.immutable.collections)
implementation(libs.okio)
implementation(libs.okio.fake)

// Testing
testImplementation(projects.components.core.test)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,13 +1,12 @@
package com.flipperdevices.bridge.dao.impl.api.delegates

import android.content.Context
import com.flipperdevices.bridge.dao.api.model.FlipperKeyContent
import com.flipperdevices.bridge.dao.impl.repository.key.DeleteKeyDao
import com.flipperdevices.core.FlipperStorageProvider
import com.flipperdevices.core.di.AppGraph
import com.flipperdevices.core.di.provideDelegate
import com.flipperdevices.core.log.LogTagProvider
import com.flipperdevices.core.log.info
import com.flipperdevices.core.preference.FlipperStorageProvider
import com.squareup.anvil.annotations.ContributesBinding
import java.io.File
import javax.inject.Inject
Expand All @@ -16,10 +15,10 @@ import javax.inject.Provider
@ContributesBinding(AppGraph::class, KeyContentCleaner::class)
class KeyContentCleanerImpl @Inject constructor(
private val deleteKeyDaoProvider: Provider<DeleteKeyDao>,
context: Context
flipperStorageProvider: FlipperStorageProvider
) : KeyContentCleaner, LogTagProvider {
override val TAG = "KeyContentCleaner"
private val keyFolder = FlipperStorageProvider.getKeyFolder(context)
private val keyFolder = flipperStorageProvider.getKeyFolder().toFile()

private val deleteKeyDao by deleteKeyDaoProvider

Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
@@ -1,11 +1,64 @@
package com.flipperdevices.bridge.dao.impl.comparator

import java.io.InputStream
import com.flipperdevices.core.FlipperStorageProvider
import com.flipperdevices.core.ktx.jre.FlipperDispatchers
import kotlinx.coroutines.withContext
import okio.BufferedSource
import okio.FileSystem
import okio.Path
import okio.buffer
import javax.inject.Inject

interface FileComparator {
class FileComparator @Inject constructor(
private val flipperStorageProvider: FlipperStorageProvider
) {

/**
* Check if two streams have identical content
*/
suspend fun isSameContent(istream1: InputStream, istream2: InputStream): Boolean
suspend fun isSameContent(
source1: BufferedSource,
source2: BufferedSource
): Boolean = withContext(FlipperDispatchers.workStealingDispatcher) {
val isSource1Empty = source1.request(byteCount = 1).not()
val isSource2Empty = source2.request(byteCount = 1).not()
if (isSource1Empty && isSource2Empty) {
return@withContext true
}
if (isSource1Empty || isSource2Empty) {
return@withContext false
}
do {
val ch1 = runCatching { source1.readByte() }.getOrNull()
val ch2 = runCatching { source2.readByte() }.getOrNull()
if (ch1 != ch2) return@withContext false
} while (ch1 != null)
return@withContext true
}

/**
* Checking two files for same content
*
* If files doesn't exists returns true
*/
suspend fun isSameContent(
file1: Path,
file2: Path,
fileSystem: FileSystem = flipperStorageProvider.fileSystem
): Boolean = withContext(FlipperDispatchers.workStealingDispatcher) {
if (!fileSystem.exists(file1) && !fileSystem.exists(file2)) return@withContext true
if (!fileSystem.exists(file1) || !fileSystem.exists(file2)) return@withContext false

if (fileSystem.metadataOrNull(file1)?.size != fileSystem.metadataOrNull(file2)?.size) {
return@withContext false
}
return@withContext fileSystem.source(file1).buffer().use { source1 ->
fileSystem.source(file2).buffer().use { source2 ->
isSameContent(
source1,
source2
)
}
}
}
}

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -7,17 +7,20 @@ import com.flipperdevices.bridge.dao.api.model.FlipperKeyContent
import com.flipperdevices.bridge.dao.impl.md5.MD5Converter
import com.flipperdevices.bridge.dao.impl.md5.MD5FileProvider
import com.flipperdevices.bridge.dao.impl.model.DatabaseKeyContent
import com.flipperdevices.core.ktx.jre.createNewFileWithMkDirs
import com.flipperdevices.core.FlipperStorageProvider
import com.flipperdevices.core.ktx.jre.runBlockingWithLog
import com.flipperdevices.core.log.BuildConfig
import com.flipperdevices.core.log.LogTagProvider
import com.flipperdevices.core.log.verbose
import okio.buffer
import okio.source
import javax.inject.Inject

@ProvidedTypeConverter
class DatabaseKeyContentConverter @Inject constructor(
private val md5Converter: MD5Converter,
private val mD5FileProvider: MD5FileProvider,
private val flipperStorageProvider: FlipperStorageProvider
) : LogTagProvider {
override val TAG = "DatabaseKeyContentConverter"

Expand All @@ -44,16 +47,19 @@ class DatabaseKeyContentConverter @Inject constructor(
val contentMd5 = md5Converter.convert(keyContent.openStream())

val pathToFile = mD5FileProvider.getPathToFile(contentMd5, keyContent)
if (pathToFile.exists()) return pathToFile.absolutePath
if (flipperStorageProvider.fileSystem.exists(pathToFile)) {
return pathToFile.normalized().toString()
}

pathToFile.createNewFileWithMkDirs()
flipperStorageProvider.mkdirsParent(pathToFile)
verbose { "Create new file with hash $contentMd5" }
pathToFile.outputStream().use { fileStream ->
keyContent.openStream().use { contentStream ->
contentStream.copyTo(fileStream)

flipperStorageProvider.fileSystem.sink(pathToFile).buffer().use { fileSink ->
keyContent.openStream().source().buffer().use { contentSource ->
fileSink.writeAll(contentSource)
}
}

return pathToFile.absolutePath
return pathToFile.normalized().toString()
}
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
package com.flipperdevices.bridge.dao.impl.md5

import com.flipperdevices.bridge.dao.api.model.FlipperKeyContent
import java.io.File
import okio.Path

interface MD5FileProvider {
/**
Expand All @@ -11,5 +11,5 @@ interface MD5FileProvider {
suspend fun getPathToFile(
contentMd5: String,
keyContent: FlipperKeyContent
): File
): Path
}
Original file line number Diff line number Diff line change
@@ -1,28 +1,30 @@
package com.flipperdevices.bridge.dao.impl.md5

import android.content.Context
import com.flipperdevices.bridge.dao.api.model.FlipperKeyContent
import com.flipperdevices.bridge.dao.impl.comparator.FileComparator
import com.flipperdevices.core.FlipperStorageProvider
import com.flipperdevices.core.di.AppGraph
import com.flipperdevices.core.log.verbose
import com.flipperdevices.core.preference.FlipperStorageProvider
import com.squareup.anvil.annotations.ContributesBinding
import java.io.File
import okio.Path
import okio.buffer
import okio.source
import okio.use
import javax.inject.Inject

@ContributesBinding(AppGraph::class, MD5FileProvider::class)
class MD5FileProviderImpl @Inject constructor(
context: Context,
private val fileComparator: FileComparator,
private val storageProvider: FlipperStorageProvider
) : MD5FileProvider {
private val keyFolder: File = FlipperStorageProvider.getKeyFolder(context)
private val keyFolder = storageProvider.getKeyFolder()

/**
* @return list of files with same MD5 signature
*/
private fun getSameMd5Files(md5: String): List<File> {
return keyFolder.listFiles()
?.filterNotNull()
private fun getSameMd5Files(md5: String): List<Path> {
return runCatching { storageProvider.fileSystem.list(keyFolder) }
.getOrNull()
?.filter { file -> file.name.startsWith(md5) }
.orEmpty()
}
Expand All @@ -32,7 +34,7 @@ class MD5FileProviderImpl @Inject constructor(
* 4bdb3a2214c898d7463ef3b0d1aeca37_2
* @return the index of MD5 named file
*/
private fun getMd5FileIndex(file: File): Int {
private fun getMd5FileIndex(file: Path): Int {
if (!file.name.contains("_")) return 0
return file.name.split("_").last().toIntOrNull() ?: 0
}
Expand All @@ -47,16 +49,20 @@ class MD5FileProviderImpl @Inject constructor(
private suspend fun getSameContentFile(
contentMd5: String,
keyContent: FlipperKeyContent
): File? = getSameMd5Files(contentMd5).firstOrNull { sameMD5File ->
fileComparator.isSameContent(
keyContent.openStream(),
sameMD5File.inputStream()
)
): Path? = getSameMd5Files(contentMd5).firstOrNull { sameMD5File ->
keyContent.openStream().source().buffer().use { keyContentSource ->
storageProvider.fileSystem.source(sameMD5File).buffer().use { fileSource ->
fileComparator.isSameContent(
keyContentSource,
fileSource
)
}
}
}

override suspend fun getPathToFile(contentMd5: String, keyContent: FlipperKeyContent): File {
val pathToFile = File(keyFolder, contentMd5)
if (!pathToFile.exists()) return pathToFile
override suspend fun getPathToFile(contentMd5: String, keyContent: FlipperKeyContent): Path {
val pathToFile = keyFolder.resolve(contentMd5)
if (!storageProvider.fileSystem.exists(pathToFile)) return pathToFile

val sameContentFile = getSameContentFile(contentMd5, keyContent)
if (sameContentFile != null) {
Expand All @@ -66,6 +72,6 @@ class MD5FileProviderImpl @Inject constructor(

verbose { "Already find file with hash $contentMd5" }
val index = getMaxMD5FileIndex(contentMd5) + 1
return File(keyFolder, contentMd5 + "_$index")
return keyFolder.resolve("${contentMd5}_$index")
}
}
Loading

0 comments on commit d12b5e9

Please sign in to comment.