Add install extension file from filesystem

This commit is contained in:
Syer10
2022-11-04 18:54:32 -04:00
parent b3ce8e0372
commit 67ba704aad
5 changed files with 109 additions and 6 deletions

View File

@@ -0,0 +1,27 @@
/*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package ca.gosyer.jui.domain.extension.interactor
import ca.gosyer.jui.domain.extension.service.ExtensionRepository
import kotlinx.coroutines.flow.catch
import kotlinx.coroutines.flow.collect
import me.tatarka.inject.annotations.Inject
import okio.Path
import org.lighthousegames.logging.logging
class InstallExtensionFile @Inject constructor(private val extensionRepository: ExtensionRepository) {
suspend fun await(path: Path) = asFlow(path)
.catch { log.warn(it) { "Failed to install extension from $path" } }
.collect()
fun asFlow(path: Path) = extensionRepository.installExtension(ExtensionRepository.buildExtensionFormData(path))
companion object {
private val log = logging()
}
}

View File

@@ -6,19 +6,36 @@
package ca.gosyer.jui.domain.extension.service
import ca.gosyer.jui.core.io.SYSTEM
import ca.gosyer.jui.domain.extension.model.Extension
import de.jensklingenberg.ktorfit.http.GET
import de.jensklingenberg.ktorfit.http.Multipart
import de.jensklingenberg.ktorfit.http.POST
import de.jensklingenberg.ktorfit.http.Part
import de.jensklingenberg.ktorfit.http.Path
import de.jensklingenberg.ktorfit.http.ReqBuilder
import io.ktor.client.request.HttpRequestBuilder
import io.ktor.client.request.forms.formData
import io.ktor.client.statement.HttpResponse
import io.ktor.http.ContentType
import io.ktor.http.Headers
import io.ktor.http.HttpHeaders
import io.ktor.http.content.PartData
import io.ktor.utils.io.ByteReadChannel
import kotlinx.coroutines.flow.Flow
import okio.FileSystem
import okio.buffer
interface ExtensionRepository {
@GET("api/v1/extension/list")
fun getExtensionList(): Flow<List<Extension>>
@Multipart
@POST("api/v1/extension/install")
fun installExtension(
@Part("") formData: List<PartData>,
): Flow<HttpResponse>
@GET("api/v1/extension/install/{pkgName}")
fun installExtension(
@Path("pkgName") pkgName: String
@@ -39,4 +56,17 @@ interface ExtensionRepository {
@Path("apkName") apkName: String,
@ReqBuilder block: HttpRequestBuilder.() -> Unit
): Flow<ByteReadChannel>
companion object {
fun buildExtensionFormData(file: okio.Path) = formData {
append(
"file",
FileSystem.SYSTEM.source(file).buffer().readByteArray(),
Headers.build {
append(HttpHeaders.ContentType, ContentType.MultiPart.FormData.toString())
append(HttpHeaders.ContentDisposition, "filename=file")
}
)
}
}
}

View File

@@ -30,6 +30,7 @@ class ExtensionsScreen : Screen {
enabledLangs = vm.enabledLangs.collectAsState().value,
availableLangs = vm.availableLangs.collectAsState().value,
setEnabledLanguages = vm::setEnabledLanguages,
installExtensionFile = vm::install,
installExtension = vm::install,
updateExtension = vm::update,
uninstallExtension = vm::uninstall

View File

@@ -8,9 +8,12 @@ package ca.gosyer.jui.ui.extensions
import androidx.compose.runtime.Immutable
import androidx.compose.ui.text.intl.Locale
import ca.gosyer.jui.core.io.saveTo
import ca.gosyer.jui.core.lang.displayName
import ca.gosyer.jui.core.lang.throwIfCancellation
import ca.gosyer.jui.domain.extension.interactor.GetExtensionList
import ca.gosyer.jui.domain.extension.interactor.InstallExtension
import ca.gosyer.jui.domain.extension.interactor.InstallExtensionFile
import ca.gosyer.jui.domain.extension.interactor.UninstallExtension
import ca.gosyer.jui.domain.extension.interactor.UpdateExtension
import ca.gosyer.jui.domain.extension.model.Extension
@@ -32,10 +35,14 @@ import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.launch
import me.tatarka.inject.annotations.Inject
import okio.FileSystem
import okio.Source
import org.lighthousegames.logging.logging
import kotlin.random.Random
class ExtensionsScreenViewModel @Inject constructor(
private val getExtensionList: GetExtensionList,
private val installExtensionFile: InstallExtensionFile,
private val installExtension: InstallExtension,
private val updateExtension: UpdateExtension,
private val uninstallExtension: UninstallExtension,
@@ -77,6 +84,26 @@ class ExtensionsScreenViewModel @Inject constructor(
_isLoading.value = false
}
fun install(source: Source) {
log.info { "Install file clicked" }
scope.launch {
try {
val file = FileSystem.SYSTEM_TEMPORARY_DIRECTORY
.resolve("tachidesk.${Random.nextLong()}.proto.gz")
.also { file ->
source.saveTo(file)
}
installExtensionFile.await(file)
} catch (e: Exception) {
log.warn(e) { "Error creating apk file" }
// todo toast if error
e.throwIfCancellation()
}
getExtensions()
}
}
fun install(extension: Extension) {
log.info { "Install clicked" }
scope.launch {

View File

@@ -33,6 +33,7 @@ import androidx.compose.material.MaterialTheme
import androidx.compose.material.Scaffold
import androidx.compose.material.Text
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.rounded.Add
import androidx.compose.material.icons.rounded.Translate
import androidx.compose.runtime.Composable
import androidx.compose.runtime.Stable
@@ -54,6 +55,7 @@ import ca.gosyer.jui.domain.extension.model.Extension
import ca.gosyer.jui.i18n.MR
import ca.gosyer.jui.presentation.build.BuildKonfig
import ca.gosyer.jui.ui.base.dialog.getMaterialDialogProperties
import ca.gosyer.jui.ui.base.file.rememberFileChooser
import ca.gosyer.jui.ui.base.navigation.ActionItem
import ca.gosyer.jui.ui.base.navigation.Toolbar
import ca.gosyer.jui.ui.extensions.ExtensionUI
@@ -74,6 +76,7 @@ import com.vanpra.composematerialdialogs.title
import kotlinx.collections.immutable.ImmutableList
import kotlinx.collections.immutable.ImmutableSet
import kotlinx.collections.immutable.toImmutableList
import okio.Source
@Composable
fun ExtensionsScreenContent(
@@ -84,11 +87,13 @@ fun ExtensionsScreenContent(
enabledLangs: ImmutableSet<String>,
availableLangs: ImmutableList<String>,
setEnabledLanguages: (Set<String>) -> Unit,
installExtensionFile: (Source) -> Unit,
installExtension: (Extension) -> Unit,
updateExtension: (Extension) -> Unit,
uninstallExtension: (Extension) -> Unit
) {
val languageDialogState = rememberMaterialDialogState()
val chooser = rememberFileChooser(installExtensionFile)
Scaffold(
modifier = Modifier.windowInsetsPadding(
WindowInsets.statusBars.add(
@@ -97,9 +102,12 @@ fun ExtensionsScreenContent(
),
topBar = {
ExtensionsToolbar(
query,
setQuery,
languageDialogState::show
searchText = query,
search = setQuery,
openLanguageDialog = languageDialogState::show,
openInstallExtensionFile = {
chooser.launch("apk")
}
)
}
) { padding ->
@@ -176,14 +184,18 @@ fun ExtensionsScreenContent(
fun ExtensionsToolbar(
searchText: String?,
search: (String) -> Unit,
openLanguageDialog: () -> Unit
openLanguageDialog: () -> Unit,
openInstallExtensionFile: () -> Unit,
) {
Toolbar(
stringResource(MR.strings.location_extensions),
searchText = searchText,
search = search,
actions = {
getActionItems(openLanguageDialog)
getActionItems(
openLanguageDialog,
openInstallExtensionFile
)
}
)
}
@@ -317,13 +329,19 @@ fun LanguageDialog(
@Stable
@Composable
private fun getActionItems(
openLanguageDialog: () -> Unit
openLanguageDialog: () -> Unit,
openInstallExtensionFile: () -> Unit,
): ImmutableList<ActionItem> {
return listOf(
ActionItem(
stringResource(MR.strings.enabled_languages),
Icons.Rounded.Translate,
doAction = openLanguageDialog
),
ActionItem(
stringResource(MR.strings.action_install),
Icons.Rounded.Add,
doAction = openInstallExtensionFile
)
).toImmutableList()
}