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 package ca.gosyer.jui.domain.extension.service
import ca.gosyer.jui.core.io.SYSTEM
import ca.gosyer.jui.domain.extension.model.Extension import ca.gosyer.jui.domain.extension.model.Extension
import de.jensklingenberg.ktorfit.http.GET 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.Path
import de.jensklingenberg.ktorfit.http.ReqBuilder import de.jensklingenberg.ktorfit.http.ReqBuilder
import io.ktor.client.request.HttpRequestBuilder import io.ktor.client.request.HttpRequestBuilder
import io.ktor.client.request.forms.formData
import io.ktor.client.statement.HttpResponse 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 io.ktor.utils.io.ByteReadChannel
import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.Flow
import okio.FileSystem
import okio.buffer
interface ExtensionRepository { interface ExtensionRepository {
@GET("api/v1/extension/list") @GET("api/v1/extension/list")
fun getExtensionList(): Flow<List<Extension>> 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}") @GET("api/v1/extension/install/{pkgName}")
fun installExtension( fun installExtension(
@Path("pkgName") pkgName: String @Path("pkgName") pkgName: String
@@ -39,4 +56,17 @@ interface ExtensionRepository {
@Path("apkName") apkName: String, @Path("apkName") apkName: String,
@ReqBuilder block: HttpRequestBuilder.() -> Unit @ReqBuilder block: HttpRequestBuilder.() -> Unit
): Flow<ByteReadChannel> ): 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, enabledLangs = vm.enabledLangs.collectAsState().value,
availableLangs = vm.availableLangs.collectAsState().value, availableLangs = vm.availableLangs.collectAsState().value,
setEnabledLanguages = vm::setEnabledLanguages, setEnabledLanguages = vm::setEnabledLanguages,
installExtensionFile = vm::install,
installExtension = vm::install, installExtension = vm::install,
updateExtension = vm::update, updateExtension = vm::update,
uninstallExtension = vm::uninstall uninstallExtension = vm::uninstall

View File

@@ -8,9 +8,12 @@ package ca.gosyer.jui.ui.extensions
import androidx.compose.runtime.Immutable import androidx.compose.runtime.Immutable
import androidx.compose.ui.text.intl.Locale 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.displayName
import ca.gosyer.jui.core.lang.throwIfCancellation
import ca.gosyer.jui.domain.extension.interactor.GetExtensionList import ca.gosyer.jui.domain.extension.interactor.GetExtensionList
import ca.gosyer.jui.domain.extension.interactor.InstallExtension 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.UninstallExtension
import ca.gosyer.jui.domain.extension.interactor.UpdateExtension import ca.gosyer.jui.domain.extension.interactor.UpdateExtension
import ca.gosyer.jui.domain.extension.model.Extension 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.flow.stateIn
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import me.tatarka.inject.annotations.Inject import me.tatarka.inject.annotations.Inject
import okio.FileSystem
import okio.Source
import org.lighthousegames.logging.logging import org.lighthousegames.logging.logging
import kotlin.random.Random
class ExtensionsScreenViewModel @Inject constructor( class ExtensionsScreenViewModel @Inject constructor(
private val getExtensionList: GetExtensionList, private val getExtensionList: GetExtensionList,
private val installExtensionFile: InstallExtensionFile,
private val installExtension: InstallExtension, private val installExtension: InstallExtension,
private val updateExtension: UpdateExtension, private val updateExtension: UpdateExtension,
private val uninstallExtension: UninstallExtension, private val uninstallExtension: UninstallExtension,
@@ -77,6 +84,26 @@ class ExtensionsScreenViewModel @Inject constructor(
_isLoading.value = false _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) { fun install(extension: Extension) {
log.info { "Install clicked" } log.info { "Install clicked" }
scope.launch { scope.launch {

View File

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