mirror of
https://github.com/Suwayomi/TachideskJUI.git
synced 2025-12-10 06:42:05 +01:00
Add install extension file from filesystem
This commit is contained in:
@@ -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()
|
||||
}
|
||||
}
|
||||
@@ -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")
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user