Skip to content

Commit

Permalink
Fix mfkey32 download (#727)
Browse files Browse the repository at this point in the history
**Background**

Right now we don't shown progress on download file in mfkey32

**Changes**

Create `download` method and use it

**Test plan**

Try open mfkey32 util. And also put
[mfkey32.log](https://github.com/flipperdevices/Flipper-Android-App/files/13187632/mfkey32.log)
to `/ext/nfc/.mfkey32.log`
  • Loading branch information
LionZXY authored Oct 28, 2023
1 parent 710cb4f commit a09cdc2
Show file tree
Hide file tree
Showing 8 changed files with 155 additions and 59 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
- [FIX] Fix faphub manifest minor api version
- [FIX] Fix faphub manifest image base64
- [FIX] Fix crash on appication from unknown source
- [FIX] Fix mfkey32 download process

# 1.6.5

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,17 @@ interface FlipperStorageApi {

suspend fun delete(path: String, recursive: Boolean = false)

suspend fun upload(pathOnFlipper: String, fileOnAndroid: File, progressListener: ProgressListener)
suspend fun download(
pathOnFlipper: String,
fileOnAndroid: File,
progressListener: ProgressListener
)

suspend fun upload(
pathOnFlipper: String,
fileOnAndroid: File,
progressListener: ProgressListener
)

suspend fun listingDirectory(pathOnFlipper: String): List<String>

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import com.flipperdevices.bridge.api.model.wrapToRequest
import com.flipperdevices.bridge.api.utils.Constants
import com.flipperdevices.bridge.rpc.api.FlipperStorageApi
import com.flipperdevices.bridge.rpc.api.model.NameWithHash
import com.flipperdevices.bridge.rpc.impl.delegates.FlipperDownloadDelegate
import com.flipperdevices.bridge.rpc.impl.delegates.FlipperListingDelegate
import com.flipperdevices.bridge.rpc.impl.delegates.FlipperUploadDelegate
import com.flipperdevices.bridge.rpc.impl.delegates.MkDirDelegate
Expand All @@ -27,7 +28,8 @@ class FlipperStorageApiImpl @Inject constructor(
private val flipperServiceProvider: FlipperServiceProvider,
private val mkDirDelegate: MkDirDelegate,
private val flipperUploadDelegate: FlipperUploadDelegate,
private val listingDelegate: FlipperListingDelegate
private val listingDelegate: FlipperListingDelegate,
private val flipperDownloadDelegate: FlipperDownloadDelegate
) : FlipperStorageApi, LogTagProvider {
override val TAG = "FlipperStorageApi"

Expand All @@ -53,6 +55,19 @@ class FlipperStorageApiImpl @Inject constructor(
}
}

override suspend fun download(
pathOnFlipper: String,
fileOnAndroid: File,
progressListener: ProgressListener
) = withContext(Dispatchers.Default) {
flipperDownloadDelegate.download(
requestApi = flipperServiceProvider.getServiceApi().requestApi,
pathOnFlipper = pathOnFlipper,
fileOnAndroid = fileOnAndroid,
externalProgressListener = progressListener
)
}

override suspend fun upload(
pathOnFlipper: String,
fileOnAndroid: File,
Expand All @@ -75,7 +90,8 @@ class FlipperStorageApiImpl @Inject constructor(

override suspend fun listingDirectoryWithMd5(pathOnFlipper: String): List<NameWithHash> {
val serviceApi = flipperServiceProvider.getServiceApi()
val md5ListingSupported = serviceApi.flipperVersionApi.isSupported(Constants.API_SUPPORTED_MD5_LISTING)
val md5ListingSupported =
serviceApi.flipperVersionApi.isSupported(Constants.API_SUPPORTED_MD5_LISTING)
return if (md5ListingSupported) {
info { "Use new md5 request api" }
listingDelegate.listingWithMd5(
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
package com.flipperdevices.bridge.rpc.impl.delegates

import com.flipperdevices.bridge.api.manager.FlipperRequestApi
import com.flipperdevices.bridge.api.model.wrapToRequest
import com.flipperdevices.core.log.LogTagProvider
import com.flipperdevices.core.log.info
import com.flipperdevices.core.progress.ProgressListener
import com.flipperdevices.core.progress.ProgressWrapperTracker
import com.flipperdevices.protobuf.Flipper
import com.flipperdevices.protobuf.main
import com.flipperdevices.protobuf.storage.readRequest
import com.flipperdevices.protobuf.storage.statRequest
import kotlinx.coroutines.flow.toList
import java.io.File
import java.io.FileNotFoundException
import javax.inject.Inject

class FlipperDownloadDelegate @Inject constructor() : LogTagProvider {
override val TAG = "FlipperDownloadDelegate"
suspend fun download(
requestApi: FlipperRequestApi,
pathOnFlipper: String,
fileOnAndroid: File,
externalProgressListener: ProgressListener
) {
info { "Start download file $pathOnFlipper to ${fileOnAndroid.path}" }
val progressListener = ProgressWrapperTracker(externalProgressListener)

val totalSize = getTotalSize(requestApi, pathOnFlipper)
info { "Receive total size of file: $totalSize bytes" }

var totalDownloaded = 0L

requestApi.request(
main {
storageReadRequest = readRequest {
path = pathOnFlipper
}
}.wrapToRequest()
).collect { response ->
if (response.commandStatus == Flipper.CommandStatus.ERROR_STORAGE_NOT_EXIST) {
throw FileNotFoundException()
}
if (response.commandStatus != Flipper.CommandStatus.OK) {
error("Failed with $response")
}
val data = response.storageReadResponse.file.data
fileOnAndroid.appendBytes(data.toByteArray())
totalDownloaded += data.size()
progressListener.report(
totalDownloaded,
totalSize
)
}
}

private suspend fun getTotalSize(requestApi: FlipperRequestApi, pathOnFlipper: String): Long {
val listingList = requestApi.request(
main {
storageStatRequest = statRequest {
path = pathOnFlipper
}
}.wrapToRequest()
).toList()
if (listingList.isEmpty()) {
error("Not received any response for listing $pathOnFlipper")
}

if (listingList.size > 1) {
error(
"Listing request return more than one response. " +
"Are you sure that you want download a file, not a directory?"
)
}

val response = listingList.single()

if (response.commandStatus == Flipper.CommandStatus.ERROR_STORAGE_NOT_EXIST) {
throw FileNotFoundException()
}

if (!response.hasStorageStatResponse()) {
error("Failed with $response")
}

return response.storageStatResponse.file.size.toLong()
}
}
2 changes: 2 additions & 0 deletions components/nfc/mfkey32/screen/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -22,10 +22,12 @@ dependencies {
implementation(projects.components.core.ui.flippermockup)
implementation(projects.components.core.ui.lifecycle)
implementation(projects.components.core.markdown)
implementation(projects.components.core.progress)

implementation(projects.components.bridge.api)
implementation(projects.components.bridge.service.api)
implementation(projects.components.bridge.pbutils)
implementation(projects.components.bridge.rpc.api)

implementation(projects.components.analytics.metric.api)
implementation(projects.components.deeplink.api)
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,11 @@ import android.content.Context
import com.flipperdevices.bridge.api.manager.FlipperRequestApi
import com.flipperdevices.bridge.api.model.wrapToRequest
import com.flipperdevices.bridge.protobuf.streamToCommandFlow
import com.flipperdevices.bridge.rpc.api.FlipperStorageApi
import com.flipperdevices.core.ktx.jre.withLock
import com.flipperdevices.core.log.LogTagProvider
import com.flipperdevices.core.log.error
import com.flipperdevices.core.log.info
import com.flipperdevices.core.preference.FlipperStorageProvider
import com.flipperdevices.nfc.mfkey32.screen.model.DuplicatedSource
import com.flipperdevices.nfc.mfkey32.screen.model.FoundedInformation
Expand All @@ -26,7 +28,8 @@ private const val FLIPPER_DICT_USER_PATH = "/ext/nfc/assets/mf_classic_dict_user
private const val FLIPPER_DICT_PATH = "/ext/nfc/assets/mf_classic_dict.nfc"

class ExistedKeysStorage(
private val context: Context
private val context: Context,
private val flipperStorageApi: FlipperStorageApi
) : LogTagProvider {
override val TAG = "ExistedKeysStorage"
private val foundedInformationStateFlow = MutableStateFlow(FoundedInformation())
Expand All @@ -38,11 +41,11 @@ class ExistedKeysStorage(

fun getFoundedInformation(): StateFlow<FoundedInformation> = foundedInformationStateFlow

suspend fun load(requestApi: FlipperRequestApi) {
val foundedUserDict = loadDict(requestApi, FLIPPER_DICT_USER_PATH)
suspend fun load() {
val foundedUserDict = loadDict(FLIPPER_DICT_USER_PATH)
userDict.addAll(foundedUserDict)
userKeys.addAll(foundedUserDict)
val foundedDict = loadDict(requestApi, FLIPPER_DICT_PATH)
val foundedDict = loadDict(FLIPPER_DICT_PATH)
flipperKeys.addAll(foundedDict)
}

Expand Down Expand Up @@ -93,14 +96,20 @@ class ExistedKeysStorage(
}
}

private suspend fun loadDict(requestApi: FlipperRequestApi, path: String): List<String> {
private suspend fun loadDict(path: String): List<String> {
return try {
FlipperStorageProvider.useTemporaryFile(context) { tmpFile ->
DownloadFileHelper.downloadFile(requestApi, path, tmpFile)
flipperStorageApi.download(
pathOnFlipper = path,
fileOnAndroid = tmpFile,
progressListener = {
info { "Download dict with progress $it" }
}
)
tmpFile.readLines()
.filterNot { it.startsWith("/") || it.isEmpty() }
}
} catch (e: FileNotFoundException) {
} catch (e: Throwable) {
error(e) { "Failed load dict $path" }
emptyList()
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import androidx.lifecycle.viewModelScope
import com.flipperdevices.bridge.api.manager.FlipperRequestApi
import com.flipperdevices.bridge.api.manager.ktx.state.ConnectionState
import com.flipperdevices.bridge.api.model.wrapToRequest
import com.flipperdevices.bridge.rpc.api.FlipperStorageApi
import com.flipperdevices.bridge.service.api.FlipperServiceApi
import com.flipperdevices.bridge.service.api.provider.FlipperBleServiceConsumer
import com.flipperdevices.bridge.service.api.provider.FlipperServiceProvider
Expand Down Expand Up @@ -39,7 +40,6 @@ import kotlinx.coroutines.launch
import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock
import tangle.viewmodel.VMInject
import java.io.FileNotFoundException
import java.math.BigInteger
import java.util.concurrent.Executors

Expand All @@ -51,7 +51,8 @@ class MfKey32ViewModel @VMInject constructor(
private val nfcToolsApi: NfcToolsApi,
private val mfKey32Api: MfKey32Api,
private val metricApi: MetricApi,
flipperServiceProvider: FlipperServiceProvider
flipperServiceProvider: FlipperServiceProvider,
private val flipperStorageApi: FlipperStorageApi
) : LifecycleViewModel(), LogTagProvider, FlipperBleServiceConsumer {
override val TAG = "MfKey32ViewModel"
private val bruteforceDispatcher = Executors.newFixedThreadPool(
Expand All @@ -61,7 +62,7 @@ class MfKey32ViewModel @VMInject constructor(
MfKey32State.Error(ErrorType.FLIPPER_CONNECTION)
)

private val existedKeysStorage = ExistedKeysStorage(context)
private val existedKeysStorage = ExistedKeysStorage(context, flipperStorageApi)
private val fileWithNonce by lazy {
FlipperStorageProvider.getTemporaryFile(context)
}
Expand Down Expand Up @@ -98,6 +99,7 @@ class MfKey32ViewModel @VMInject constructor(
connectionState: ConnectionState
) {
info { "Start calculation on $connectionState" }

when (connectionState) {
ConnectionState.Connecting,
ConnectionState.Disconnecting,
Expand All @@ -115,7 +117,7 @@ class MfKey32ViewModel @VMInject constructor(
is ConnectionState.Ready -> {}
}

if (!prepare(serviceApi)) {
if (!prepare()) {
info { "Failed prepare" }
return
}
Expand All @@ -139,26 +141,27 @@ class MfKey32ViewModel @VMInject constructor(
mfKey32StateFlow.emit(MfKey32State.Saved(addedKeys.toImmutableList()))
}

private suspend fun prepare(serviceApi: FlipperServiceApi): Boolean {
private suspend fun prepare(): Boolean {
info { "Flipper connected" }
MfKey32State.DownloadingRawFile(null)
mfKey32StateFlow.emit(MfKey32State.DownloadingRawFile(null))

try {
DownloadFileHelper.downloadFile(
serviceApi.requestApi,
PATH_NONCE_LOG,
fileWithNonce
) {
info { "Download file progress $it" }
}
} catch (notFoundException: FileNotFoundException) {
error(notFoundException) { "Not found $PATH_NONCE_LOG" }
flipperStorageApi.download(
pathOnFlipper = PATH_NONCE_LOG,
fileOnAndroid = fileWithNonce,
progressListener = {
info { "Download file progress $it" }
mfKey32StateFlow.emit(MfKey32State.DownloadingRawFile(it))
}
)
} catch (error: Throwable) {
error(error) { "Not found $PATH_NONCE_LOG" }
mfKey32StateFlow.emit(MfKey32State.Error(ErrorType.NOT_FOUND_FILE))
return false
}
metricApi.reportSimpleEvent(SimpleEvent.MFKEY32)
try {
existedKeysStorage.load(serviceApi.requestApi)
existedKeysStorage.load()
} catch (exception: Throwable) {
error(exception) { "When load keys" }
mfKey32StateFlow.emit(MfKey32State.Error(ErrorType.READ_WRITE))
Expand Down

0 comments on commit a09cdc2

Please sign in to comment.