diff --git a/CHANGELOG.md b/CHANGELOG.md index e99507f4ab..50ae21b729 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -43,6 +43,7 @@ Attention: don't forget to add the flag for F-Droid before release - [FIX] Share infrared remote after rename - [FIX] Fix app bar colors on remote controls - [FIX] Fix remote controls texts and favorite dropdown +- [FIX] Fix crash when saving files on remote controls - [FIX] Remove share from remote controls - [CI] Fix merge-queue files diff - [CI] Add https://github.com/LionZXY/detekt-decompose-rule diff --git a/components/remote-controls/setup/api/src/main/kotlin/com/flipperdevices/remotecontrols/api/SaveTempSignalApi.kt b/components/remote-controls/setup/api/src/main/kotlin/com/flipperdevices/remotecontrols/api/SaveTempSignalApi.kt index 85331f82a5..91a4aa234e 100644 --- a/components/remote-controls/setup/api/src/main/kotlin/com/flipperdevices/remotecontrols/api/SaveTempSignalApi.kt +++ b/components/remote-controls/setup/api/src/main/kotlin/com/flipperdevices/remotecontrols/api/SaveTempSignalApi.kt @@ -17,9 +17,7 @@ interface SaveTempSignalApi : InstanceKeeper.Instance { sealed interface State { data object Pending : State - data class Uploading(val progressInternal: Long, val total: Long) : State { - val progressPercent: Float = if (total == 0L) 0f else progressInternal / total.toFloat() - } + data class Uploading(val progressPercent: Float) : State data object Uploaded : State } diff --git a/components/remote-controls/setup/impl/build.gradle.kts b/components/remote-controls/setup/impl/build.gradle.kts index d6f6a1d0bb..f4aedeaddb 100644 --- a/components/remote-controls/setup/impl/build.gradle.kts +++ b/components/remote-controls/setup/impl/build.gradle.kts @@ -16,11 +16,14 @@ dependencies { implementation(projects.components.core.ui.ktx) implementation(projects.components.core.ui.res) implementation(projects.components.core.ui.dialog) + implementation(projects.components.core.preference) + implementation(projects.components.core.progress) implementation(projects.components.bridge.dao.api) implementation(projects.components.bridge.service.api) implementation(projects.components.bridge.pbutils) implementation(projects.components.bridge.api) + implementation(projects.components.bridge.rpc.api) implementation(projects.components.keyemulate.api) implementation(projects.components.infrared.utils) implementation(projects.components.infrared.api) diff --git a/components/remote-controls/setup/impl/src/main/kotlin/com/flipperdevices/remotecontrols/impl/setup/api/save/file/SaveFileApi.kt b/components/remote-controls/setup/impl/src/main/kotlin/com/flipperdevices/remotecontrols/impl/setup/api/save/file/SaveFileApi.kt deleted file mode 100644 index 787fe55403..0000000000 --- a/components/remote-controls/setup/impl/src/main/kotlin/com/flipperdevices/remotecontrols/impl/setup/api/save/file/SaveFileApi.kt +++ /dev/null @@ -1,22 +0,0 @@ -package com.flipperdevices.remotecontrols.impl.setup.api.save.file - -import com.flipperdevices.bridge.api.manager.FlipperRequestApi -import kotlinx.coroutines.flow.Flow - -interface SaveFileApi { - fun save( - requestApi: FlipperRequestApi, - textContent: String, - absolutePath: String - ): Flow - - sealed interface Status { - data class Saving( - val uploaded: Long, - val size: Long, - val lastWriteSize: Long - ) : Status - - data object Finished : Status - } -} diff --git a/components/remote-controls/setup/impl/src/main/kotlin/com/flipperdevices/remotecontrols/impl/setup/api/save/file/SaveFileApiImpl.kt b/components/remote-controls/setup/impl/src/main/kotlin/com/flipperdevices/remotecontrols/impl/setup/api/save/file/SaveFileApiImpl.kt deleted file mode 100644 index d7d266bcf5..0000000000 --- a/components/remote-controls/setup/impl/src/main/kotlin/com/flipperdevices/remotecontrols/impl/setup/api/save/file/SaveFileApiImpl.kt +++ /dev/null @@ -1,81 +0,0 @@ -package com.flipperdevices.remotecontrols.impl.setup.api.save.file - -import com.flipperdevices.bridge.api.manager.FlipperRequestApi -import com.flipperdevices.bridge.api.model.FlipperRequest -import com.flipperdevices.bridge.api.model.FlipperRequestPriority -import com.flipperdevices.bridge.api.model.wrapToRequest -import com.flipperdevices.bridge.protobuf.streamToCommandFlow -import com.flipperdevices.core.di.AppGraph -import com.flipperdevices.core.log.LogTagProvider -import com.flipperdevices.core.log.info -import com.flipperdevices.protobuf.main -import com.flipperdevices.protobuf.storage.file -import com.flipperdevices.protobuf.storage.writeRequest -import com.squareup.anvil.annotations.ContributesBinding -import kotlinx.coroutines.flow.Flow -import kotlinx.coroutines.flow.channelFlow -import kotlinx.coroutines.flow.collect -import kotlinx.coroutines.flow.map -import java.io.ByteArrayInputStream -import javax.inject.Inject - -@ContributesBinding(AppGraph::class, SaveFileApi::class) -class SaveFileApiImpl @Inject constructor() : LogTagProvider, SaveFileApi { - override val TAG: String = "SaveFileApi" - override fun save( - requestApi: FlipperRequestApi, - textContent: String, - absolutePath: String - ): Flow = channelFlow { - val byteArray = textContent.toByteArray() - val totalSize = byteArray.size.toLong() - var progressInternal = 0L - info { "#save Saving content: size: $totalSize $textContent" } - ByteArrayInputStream(byteArray).use { stream -> - val commandFlow = streamToCommandFlow( - stream, - totalSize, - requestWrapper = { chunkData -> - storageWriteRequest = writeRequest { - path = absolutePath - file = file { data = chunkData } - } - } - ) - val requestFlow = commandFlow.map { message -> - FlipperRequest( - data = message, - priority = FlipperRequestPriority.FOREGROUND, - onSendCallback = { - val size = message.storageWriteRequest.file.data.size().toLong() - progressInternal += size - val status = SaveFileApi.Status.Saving( - uploaded = progressInternal, - size = totalSize, - lastWriteSize = size - ) - info { "#onSendCallback $status" } - send(status) - } - ) - } - val response = requestApi.request( - commandFlow = requestFlow, - onCancel = { id -> - requestApi.request( - main { - commandId = id - hasNext = false - storageWriteRequest = writeRequest { - path = absolutePath - } - }.wrapToRequest(FlipperRequestPriority.RIGHT_NOW) - ).collect() - send(SaveFileApi.Status.Finished) - info { "#onCancel" } - } - ) - info { "File send with response $response" } - } - } -} diff --git a/components/remote-controls/setup/impl/src/main/kotlin/com/flipperdevices/remotecontrols/impl/setup/api/save/folder/SaveFolderApi.kt b/components/remote-controls/setup/impl/src/main/kotlin/com/flipperdevices/remotecontrols/impl/setup/api/save/folder/SaveFolderApi.kt deleted file mode 100644 index 9fc0d274fc..0000000000 --- a/components/remote-controls/setup/impl/src/main/kotlin/com/flipperdevices/remotecontrols/impl/setup/api/save/folder/SaveFolderApi.kt +++ /dev/null @@ -1,10 +0,0 @@ -package com.flipperdevices.remotecontrols.impl.setup.api.save.folder - -import com.flipperdevices.bridge.api.manager.FlipperRequestApi - -interface SaveFolderApi { - suspend fun save( - requestApi: FlipperRequestApi, - absolutePath: String - ) -} diff --git a/components/remote-controls/setup/impl/src/main/kotlin/com/flipperdevices/remotecontrols/impl/setup/api/save/folder/SaveFolderApiImpl.kt b/components/remote-controls/setup/impl/src/main/kotlin/com/flipperdevices/remotecontrols/impl/setup/api/save/folder/SaveFolderApiImpl.kt deleted file mode 100644 index 220adcaf81..0000000000 --- a/components/remote-controls/setup/impl/src/main/kotlin/com/flipperdevices/remotecontrols/impl/setup/api/save/folder/SaveFolderApiImpl.kt +++ /dev/null @@ -1,28 +0,0 @@ -package com.flipperdevices.remotecontrols.impl.setup.api.save.folder - -import com.flipperdevices.bridge.api.manager.FlipperRequestApi -import com.flipperdevices.bridge.api.model.FlipperRequestPriority -import com.flipperdevices.bridge.api.model.wrapToRequest -import com.flipperdevices.core.di.AppGraph -import com.flipperdevices.protobuf.main -import com.flipperdevices.protobuf.storage.mkdirRequest -import com.squareup.anvil.annotations.ContributesBinding -import kotlinx.coroutines.flow.collect -import javax.inject.Inject - -@ContributesBinding(AppGraph::class, SaveFolderApi::class) -class SaveFolderApiImpl @Inject constructor() : SaveFolderApi { - override suspend fun save( - requestApi: FlipperRequestApi, - absolutePath: String - ) { - requestApi.request( - command = main { - hasNext = false - storageMkdirRequest = mkdirRequest { - path = absolutePath - } - }.wrapToRequest(FlipperRequestPriority.FOREGROUND), - ).collect() - } -} diff --git a/components/remote-controls/setup/impl/src/main/kotlin/com/flipperdevices/remotecontrols/impl/setup/presentation/viewmodel/SaveTempSignalViewModel.kt b/components/remote-controls/setup/impl/src/main/kotlin/com/flipperdevices/remotecontrols/impl/setup/presentation/viewmodel/SaveTempSignalViewModel.kt index cfd88da999..a6043803d2 100644 --- a/components/remote-controls/setup/impl/src/main/kotlin/com/flipperdevices/remotecontrols/impl/setup/presentation/viewmodel/SaveTempSignalViewModel.kt +++ b/components/remote-controls/setup/impl/src/main/kotlin/com/flipperdevices/remotecontrols/impl/setup/presentation/viewmodel/SaveTempSignalViewModel.kt @@ -1,21 +1,17 @@ package com.flipperdevices.remotecontrols.impl.setup.presentation.viewmodel +import android.content.Context import com.flipperdevices.bridge.dao.api.model.FlipperFilePath -import com.flipperdevices.bridge.service.api.provider.FlipperServiceProvider +import com.flipperdevices.bridge.rpc.api.FlipperStorageApi import com.flipperdevices.core.di.AppGraph -import com.flipperdevices.core.ktx.jre.FlipperDispatchers import com.flipperdevices.core.ktx.jre.launchWithLock import com.flipperdevices.core.log.LogTagProvider +import com.flipperdevices.core.preference.FlipperStorageProvider import com.flipperdevices.core.ui.lifecycle.DecomposeViewModel import com.flipperdevices.remotecontrols.api.SaveTempSignalApi -import com.flipperdevices.remotecontrols.impl.setup.api.save.file.SaveFileApi -import com.flipperdevices.remotecontrols.impl.setup.api.save.folder.SaveFolderApi import com.squareup.anvil.annotations.ContributesBinding import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.asStateFlow -import kotlinx.coroutines.flow.collect -import kotlinx.coroutines.flow.flowOn -import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.launch import kotlinx.coroutines.sync.Mutex import javax.inject.Inject @@ -24,9 +20,8 @@ private const val EXT_PATH = "/ext" @ContributesBinding(AppGraph::class, SaveTempSignalApi::class) class SaveTempSignalViewModel @Inject constructor( - private val serviceProvider: FlipperServiceProvider, - private val saveFileApi: SaveFileApi, - private val saveFolderApi: SaveFolderApi, + private val flipperStorageApi: FlipperStorageApi, + private val context: Context ) : DecomposeViewModel(), LogTagProvider, SaveTempSignalApi { @@ -40,38 +35,26 @@ class SaveTempSignalViewModel @Inject constructor( onFinished: () -> Unit, ) { viewModelScope.launch { - var progressInternal = 0L - val totalSize = filesDesc.sumOf { it.textContent.toByteArray().size.toLong() } - _state.emit(SaveTempSignalApi.State.Uploading(0, totalSize)) - val serviceApi = serviceProvider.getServiceApi() + _state.emit(SaveTempSignalApi.State.Uploading(0f)) launchWithLock(mutex, viewModelScope, "load") { - filesDesc.forEach { fileDesc -> - val absolutePath = FlipperFilePath( - folder = fileDesc.extFolderPath, - nameWithExtension = fileDesc.nameWithExtension - ).getPathOnFlipper() - - saveFolderApi.save(serviceApi.requestApi, "$EXT_PATH/${fileDesc.extFolderPath}") - val saveFileFlow = saveFileApi.save( - requestApi = serviceApi.requestApi, - textContent = fileDesc.textContent, - absolutePath = absolutePath - ) - saveFileFlow - .flowOn(FlipperDispatchers.workStealingDispatcher) - .onEach { - _state.value = when (it) { - SaveFileApi.Status.Finished -> SaveTempSignalApi.State.Uploaded - is SaveFileApi.Status.Saving -> { - progressInternal += it.lastWriteSize - SaveTempSignalApi.State.Uploading( - progressInternal, - totalSize - ) - } + filesDesc.forEachIndexed { index, fileDesc -> + FlipperStorageProvider.useTemporaryFile(context) { deviceFile -> + deviceFile.writeText(fileDesc.textContent) + val fAbsolutePath = FlipperFilePath( + folder = fileDesc.extFolderPath, + nameWithExtension = fileDesc.nameWithExtension + ).getPathOnFlipper() + flipperStorageApi.mkdirs("$EXT_PATH/${fileDesc.extFolderPath}") + flipperStorageApi.upload( + pathOnFlipper = fAbsolutePath, + fileOnAndroid = deviceFile, + progressListener = { currentProgress -> + _state.value = SaveTempSignalApi.State.Uploading( + progressPercent = index + currentProgress + ) } - } - .collect() + ) + } } _state.value = SaveTempSignalApi.State.Uploaded onFinished.invoke()