Restmore may be broken
This commit is contained in:
Syer10
2024-09-21 13:42:22 -04:00
parent 478c58a9ac
commit a4a828dc62
19 changed files with 278 additions and 80 deletions

View File

@@ -135,7 +135,6 @@ android {
"META-INF/README.md", "META-INF/README.md",
"META-INF/NOTICE", "META-INF/NOTICE",
"META-INF/*.kotlin_module", "META-INF/*.kotlin_module",
"META-INF/*.version",
)) ))
} }
} }

View File

@@ -65,11 +65,11 @@ subprojects {
} }
tasks.withType<org.jmailen.gradle.kotlinter.tasks.LintTask> { tasks.withType<org.jmailen.gradle.kotlinter.tasks.LintTask> {
source(files("src")) source(files("src"))
exclude("ca/gosyer/jui/*/build") exclude("ca/gosyer/jui/*/build", "graphql")
} }
tasks.withType<org.jmailen.gradle.kotlinter.tasks.FormatTask> { tasks.withType<org.jmailen.gradle.kotlinter.tasks.FormatTask> {
source(files("src")) source(files("src"))
exclude("ca/gosyer/jui/*/build") exclude("ca/gosyer/jui/*/build", "ca/gosyer/jui/*/build")
} }
plugins.withType<com.android.build.gradle.BasePlugin> { plugins.withType<com.android.build.gradle.BasePlugin> {
configure<com.android.build.gradle.BaseExtension> { configure<com.android.build.gradle.BaseExtension> {

View File

@@ -7,6 +7,7 @@
package ca.gosyer.jui.core.io package ca.gosyer.jui.core.io
import ca.gosyer.jui.core.lang.withIOContext import ca.gosyer.jui.core.lang.withIOContext
import io.ktor.utils.io.ByteReadChannel
import okio.Buffer import okio.Buffer
import okio.BufferedSink import okio.BufferedSink
import okio.BufferedSource import okio.BufferedSource
@@ -15,6 +16,8 @@ import okio.Path
import okio.Source import okio.Source
import okio.buffer import okio.buffer
import okio.use import okio.use
import kotlin.coroutines.CoroutineContext
import kotlin.coroutines.EmptyCoroutineContext
suspend fun Source.saveTo(path: Path) { suspend fun Source.saveTo(path: Path) {
withIOContext { withIOContext {
@@ -36,3 +39,5 @@ suspend fun Source.copyTo(sink: BufferedSink) {
} }
fun ByteArray.source(): BufferedSource = Buffer().write(this) fun ByteArray.source(): BufferedSource = Buffer().write(this)
expect suspend fun ByteReadChannel.toSource(context: CoroutineContext = EmptyCoroutineContext): Source

View File

@@ -4,7 +4,7 @@
* file, You can obtain one at https://mozilla.org/MPL/2.0/. * file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/ */
package ca.gosyer.jui.ui.util.lang package ca.gosyer.jui.core.io
import io.ktor.utils.io.ByteReadChannel import io.ktor.utils.io.ByteReadChannel
import io.ktor.utils.io.cancel import io.ktor.utils.io.cancel
@@ -17,7 +17,7 @@ import kotlin.coroutines.CoroutineContext
actual suspend fun ByteReadChannel.toSource(context: CoroutineContext): Source { actual suspend fun ByteReadChannel.toSource(context: CoroutineContext): Source {
val channel = this val channel = this
return object : okio.Source { return object : Source {
override fun close() { override fun close() {
channel.cancel() channel.cancel()
} }

View File

@@ -0,0 +1,10 @@
package ca.gosyer.jui.core.io
import io.ktor.utils.io.ByteReadChannel
import io.ktor.utils.io.jvm.javaio.toInputStream
import kotlinx.coroutines.Job
import okio.Source
import okio.source
import kotlin.coroutines.CoroutineContext
actual suspend fun ByteReadChannel.toSource(context: CoroutineContext): Source = toInputStream(context[Job]).source()

View File

@@ -0,0 +1,35 @@
fragment RestoreStatusFragment on BackupRestoreStatus {
state
mangaProgress
totalManga
}
query ValidateBackup($backup: Upload!) {
validateBackup(input: {backup: $backup}) {
missingSources {
id
name
}
}
}
mutation RestoreBackup($backup: Upload!) {
restoreBackup(input: {backup: $backup}) {
id
status {
...RestoreStatusFragment
}
}
}
query RestoreStatus($id: String!) {
restoreStatus(id: $id) {
...RestoreStatusFragment
}
}
mutation CreateBackup($includeCategories: Boolean!, $includeChapters: Boolean!) {
createBackup(input: {includeCategories: $includeCategories, includeChapters: $includeChapters}) {
url
}
}

View File

@@ -7,8 +7,10 @@
package ca.gosyer.jui.data package ca.gosyer.jui.data
import ca.gosyer.jui.core.lang.addSuffix import ca.gosyer.jui.core.lang.addSuffix
import ca.gosyer.jui.data.backup.BackupRepositoryImpl
import ca.gosyer.jui.data.chapter.ChapterRepositoryImpl import ca.gosyer.jui.data.chapter.ChapterRepositoryImpl
import ca.gosyer.jui.data.settings.SettingsRepositoryImpl import ca.gosyer.jui.data.settings.SettingsRepositoryImpl
import ca.gosyer.jui.domain.backup.service.BackupRepository
import ca.gosyer.jui.domain.chapter.service.ChapterRepository import ca.gosyer.jui.domain.chapter.service.ChapterRepository
import ca.gosyer.jui.domain.server.Http import ca.gosyer.jui.domain.server.Http
import ca.gosyer.jui.domain.server.service.ServerPreferences import ca.gosyer.jui.domain.server.service.ServerPreferences
@@ -59,4 +61,11 @@ interface DataComponent : SharedDataComponent {
http: Http, http: Http,
serverPreferences: ServerPreferences, serverPreferences: ServerPreferences,
): ChapterRepository = ChapterRepositoryImpl(apolloClient, http, serverPreferences.serverUrl().get()) ): ChapterRepository = ChapterRepositoryImpl(apolloClient, http, serverPreferences.serverUrl().get())
@Provides
fun backupRepository(
apolloClient: ApolloClient,
http: Http,
serverPreferences: ServerPreferences,
): BackupRepository = BackupRepositoryImpl(apolloClient, http, serverPreferences.serverUrl().get())
} }

View File

@@ -0,0 +1,120 @@
package ca.gosyer.jui.data.backup
import ca.gosyer.jui.core.io.toSource
import ca.gosyer.jui.data.graphql.CreateBackupMutation
import ca.gosyer.jui.data.graphql.RestoreBackupMutation
import ca.gosyer.jui.data.graphql.RestoreStatusQuery
import ca.gosyer.jui.data.graphql.ValidateBackupQuery
import ca.gosyer.jui.data.graphql.fragment.RestoreStatusFragment
import ca.gosyer.jui.data.graphql.type.BackupRestoreState
import ca.gosyer.jui.domain.backup.model.BackupValidationResult
import ca.gosyer.jui.domain.backup.model.RestoreState
import ca.gosyer.jui.domain.backup.model.RestoreStatus
import ca.gosyer.jui.domain.backup.service.BackupRepository
import ca.gosyer.jui.domain.server.Http
import com.apollographql.apollo.ApolloClient
import com.apollographql.apollo.api.DefaultUpload
import io.ktor.client.request.HttpRequestBuilder
import io.ktor.client.request.get
import io.ktor.client.statement.bodyAsChannel
import io.ktor.http.Url
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.map
import okio.Source
import okio.buffer
class BackupRepositoryImpl(
private val apolloClient: ApolloClient,
private val http: Http,
private val serverUrl: Url,
) : BackupRepository {
override fun validateBackup(source: Source): Flow<BackupValidationResult> {
return apolloClient.query(
ValidateBackupQuery(
DefaultUpload.Builder()
.content {
it.writeAll(source.buffer())
}
.fileName("backup.tachibk")
.contentType("application/octet-stream")
.build()
)
).toFlow()
.map {
BackupValidationResult(
missingSources = it.dataAssertNoErrors.validateBackup.missingSources.map { source ->
"${source.name} (${source.id})"
},
missingTrackers = emptyList()
)
}
}
override fun restoreBackup(source: Source): Flow<Pair<String, RestoreStatus>> {
return apolloClient.mutation(
RestoreBackupMutation(
DefaultUpload.Builder()
.content {
it.writeAll(source.buffer())
}
.fileName("backup.tachibk")
.contentType("application/octet-stream")
.build()
)
).toFlow()
.map {
val data = it.dataAssertNoErrors
data.restoreBackup.id to data.restoreBackup.status!!
.restoreStatusFragment
.toRestoreStatus()
}
}
override fun restoreStatus(id: String): Flow<RestoreStatus> {
return apolloClient.query(
RestoreStatusQuery(id)
).toFlow()
.map {
val data = it.dataAssertNoErrors
data.restoreStatus!!.restoreStatusFragment.toRestoreStatus()
}
}
override fun createBackup(
includeCategories: Boolean,
includeChapters: Boolean,
block: HttpRequestBuilder.() -> Unit,
): Flow<Pair<String, Source>> {
return apolloClient
.mutation(
CreateBackupMutation(includeCategories, includeChapters)
)
.toFlow()
.map {
val url = it.dataAssertNoErrors.createBackup.url
val response = http.get(
Url("$serverUrl${url}")
)
val fileName = response.headers["content-disposition"]!!
.substringAfter("filename=")
.trim('"')
fileName to response.bodyAsChannel().toSource()
}
}
companion object {
private fun RestoreStatusFragment.toRestoreStatus() = RestoreStatus(
when (state) {
BackupRestoreState.IDLE -> RestoreState.IDLE
BackupRestoreState.SUCCESS -> RestoreState.SUCCESS
BackupRestoreState.FAILURE -> RestoreState.FAILURE
BackupRestoreState.RESTORING_CATEGORIES -> RestoreState.RESTORING_CATEGORIES
BackupRestoreState.RESTORING_MANGA -> RestoreState.RESTORING_MANGA
BackupRestoreState.UNKNOWN__ -> RestoreState.UNKNOWN
},
mangaProgress,
totalManga,
)
}
}

View File

@@ -6,7 +6,7 @@
package ca.gosyer.jui.domain.backup.interactor package ca.gosyer.jui.domain.backup.interactor
import ca.gosyer.jui.domain.backup.service.BackupRepositoryOld import ca.gosyer.jui.domain.backup.service.BackupRepository
import io.ktor.client.request.HttpRequestBuilder import io.ktor.client.request.HttpRequestBuilder
import kotlinx.coroutines.flow.catch import kotlinx.coroutines.flow.catch
import kotlinx.coroutines.flow.singleOrNull import kotlinx.coroutines.flow.singleOrNull
@@ -16,19 +16,25 @@ import org.lighthousegames.logging.logging
class ExportBackupFile class ExportBackupFile
@Inject @Inject
constructor( constructor(
private val backupRepositoryOld: BackupRepositoryOld, private val backupRepository: BackupRepository,
) { ) {
suspend fun await( suspend fun await(
includeCategories: Boolean,
includeChapters: Boolean,
block: HttpRequestBuilder.() -> Unit = {}, block: HttpRequestBuilder.() -> Unit = {},
onError: suspend (Throwable) -> Unit = {}, onError: suspend (Throwable) -> Unit = {},
) = asFlow(block) ) = asFlow(includeCategories, includeChapters, block)
.catch { .catch {
onError(it) onError(it)
log.warn(it) { "Failed to export backup" } log.warn(it) { "Failed to export backup" }
} }
.singleOrNull() .singleOrNull()
fun asFlow(block: HttpRequestBuilder.() -> Unit = {}) = backupRepositoryOld.exportBackupFile(block) fun asFlow(
includeCategories: Boolean,
includeChapters: Boolean,
block: HttpRequestBuilder.() -> Unit = {},
) = backupRepository.createBackup(includeCategories, includeChapters, block)
companion object { companion object {
private val log = logging() private val log = logging()

View File

@@ -6,24 +6,24 @@
package ca.gosyer.jui.domain.backup.interactor package ca.gosyer.jui.domain.backup.interactor
import ca.gosyer.jui.domain.backup.service.BackupRepositoryOld import ca.gosyer.jui.domain.backup.service.BackupRepository
import io.ktor.client.request.HttpRequestBuilder
import kotlinx.coroutines.flow.catch import kotlinx.coroutines.flow.catch
import kotlinx.coroutines.flow.singleOrNull import kotlinx.coroutines.flow.singleOrNull
import me.tatarka.inject.annotations.Inject import me.tatarka.inject.annotations.Inject
import okio.FileSystem
import okio.Path import okio.Path
import okio.SYSTEM
import org.lighthousegames.logging.logging import org.lighthousegames.logging.logging
class ImportBackupFile class ImportBackupFile
@Inject @Inject
constructor( constructor(
private val backupRepositoryOld: BackupRepositoryOld, private val backupRepository: BackupRepository,
) { ) {
suspend fun await( suspend fun await(
file: Path, file: Path,
block: HttpRequestBuilder.() -> Unit = {},
onError: suspend (Throwable) -> Unit = {}, onError: suspend (Throwable) -> Unit = {},
) = asFlow(file, block) ) = asFlow(file)
.catch { .catch {
onError(it) onError(it)
log.warn(it) { "Failed to import backup ${file.name}" } log.warn(it) { "Failed to import backup ${file.name}" }
@@ -32,8 +32,7 @@ class ImportBackupFile
fun asFlow( fun asFlow(
file: Path, file: Path,
block: HttpRequestBuilder.() -> Unit = {}, ) = backupRepository.restoreBackup(FileSystem.SYSTEM.source(file))
) = backupRepositoryOld.importBackupFile(BackupRepositoryOld.buildBackupFormData(file), block)
companion object { companion object {
private val log = logging() private val log = logging()

View File

@@ -6,24 +6,24 @@
package ca.gosyer.jui.domain.backup.interactor package ca.gosyer.jui.domain.backup.interactor
import ca.gosyer.jui.domain.backup.service.BackupRepositoryOld import ca.gosyer.jui.domain.backup.service.BackupRepository
import io.ktor.client.request.HttpRequestBuilder
import kotlinx.coroutines.flow.catch import kotlinx.coroutines.flow.catch
import kotlinx.coroutines.flow.singleOrNull import kotlinx.coroutines.flow.singleOrNull
import me.tatarka.inject.annotations.Inject import me.tatarka.inject.annotations.Inject
import okio.FileSystem
import okio.Path import okio.Path
import okio.SYSTEM
import org.lighthousegames.logging.logging import org.lighthousegames.logging.logging
class ValidateBackupFile class ValidateBackupFile
@Inject @Inject
constructor( constructor(
private val backupRepositoryOld: BackupRepositoryOld, private val backupRepository: BackupRepository,
) { ) {
suspend fun await( suspend fun await(
file: Path, file: Path,
block: HttpRequestBuilder.() -> Unit = {},
onError: suspend (Throwable) -> Unit = {}, onError: suspend (Throwable) -> Unit = {},
) = asFlow(file, block) ) = asFlow(file)
.catch { .catch {
onError(it) onError(it)
log.warn(it) { "Failed to validate backup ${file.name}" } log.warn(it) { "Failed to validate backup ${file.name}" }
@@ -32,8 +32,7 @@ class ValidateBackupFile
fun asFlow( fun asFlow(
file: Path, file: Path,
block: HttpRequestBuilder.() -> Unit = {}, ) = backupRepository.validateBackup(FileSystem.SYSTEM.source(file))
) = backupRepositoryOld.validateBackupFile(BackupRepositoryOld.buildBackupFormData(file), block)
companion object { companion object {
private val log = logging() private val log = logging()

View File

@@ -0,0 +1,25 @@
/*
* 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.backup.model
import kotlinx.serialization.Serializable
enum class RestoreState {
IDLE,
SUCCESS,
FAILURE,
RESTORING_CATEGORIES,
RESTORING_MANGA,
UNKNOWN,
}
@Serializable
data class RestoreStatus(
val state: RestoreState,
val completed: Int,
val total: Int,
)

View File

@@ -0,0 +1,18 @@
package ca.gosyer.jui.domain.backup.service
import ca.gosyer.jui.domain.backup.model.BackupValidationResult
import ca.gosyer.jui.domain.backup.model.RestoreStatus
import io.ktor.client.request.HttpRequestBuilder
import kotlinx.coroutines.flow.Flow
import okio.Source
interface BackupRepository {
fun validateBackup(source: Source): Flow<BackupValidationResult>
fun restoreBackup(source: Source): Flow<Pair<String, RestoreStatus>>
fun restoreStatus(id: String): Flow<RestoreStatus>
fun createBackup(
includeCategories: Boolean,
includeChapters: Boolean,
block: HttpRequestBuilder.() -> Unit,
): Flow<Pair<String, Source>>
}

View File

@@ -18,19 +18,21 @@ import okio.Source
import okio.source import okio.source
actual class FileChooser( actual class FileChooser(
private val resultLauncher: ManagedActivityResultLauncher<String, Uri?>, private val resultLauncher: ManagedActivityResultLauncher<Array<String>, Uri?>,
) { ) {
actual fun launch(extension: String) { actual fun launch(vararg extensions: String) {
val mime = MimeTypeMap.getSingleton() val mimes = extensions.mapNotNull { extension ->
.getMimeTypeFromExtension(extension) ?: return MimeTypeMap.getSingleton()
resultLauncher.launch(mime) .getMimeTypeFromExtension(extension)
}.toTypedArray()
resultLauncher.launch(mimes)
} }
} }
@Composable @Composable
actual fun rememberFileChooser(onFileFound: (Source) -> Unit): FileChooser { actual fun rememberFileChooser(onFileFound: (Source) -> Unit): FileChooser {
val context = LocalContext.current val context = LocalContext.current
val result = rememberLauncherForActivityResult(ActivityResultContracts.GetContent()) { val result = rememberLauncherForActivityResult(ActivityResultContracts.OpenDocument()) {
if (it != null) { if (it != null) {
context.contentResolver.openInputStream(it)?.source()?.let(onFileFound) context.contentResolver.openInputStream(it)?.source()?.let(onFileFound)
} }

View File

@@ -10,7 +10,7 @@ import androidx.compose.runtime.Composable
import okio.Source import okio.Source
expect class FileChooser { expect class FileChooser {
fun launch(extension: String) fun launch(vararg extensions: String)
} }
@Composable @Composable

View File

@@ -45,6 +45,8 @@ import ca.gosyer.jui.core.lang.throwIfCancellation
import ca.gosyer.jui.domain.backup.interactor.ExportBackupFile import ca.gosyer.jui.domain.backup.interactor.ExportBackupFile
import ca.gosyer.jui.domain.backup.interactor.ImportBackupFile import ca.gosyer.jui.domain.backup.interactor.ImportBackupFile
import ca.gosyer.jui.domain.backup.interactor.ValidateBackupFile import ca.gosyer.jui.domain.backup.interactor.ValidateBackupFile
import ca.gosyer.jui.domain.backup.model.RestoreState
import ca.gosyer.jui.domain.backup.model.RestoreStatus
import ca.gosyer.jui.i18n.MR import ca.gosyer.jui.i18n.MR
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.file.rememberFileChooser
@@ -53,7 +55,6 @@ import ca.gosyer.jui.ui.base.model.StableHolder
import ca.gosyer.jui.ui.base.navigation.Toolbar import ca.gosyer.jui.ui.base.navigation.Toolbar
import ca.gosyer.jui.ui.base.prefs.PreferenceRow import ca.gosyer.jui.ui.base.prefs.PreferenceRow
import ca.gosyer.jui.ui.main.components.bottomNav import ca.gosyer.jui.ui.main.components.bottomNav
import ca.gosyer.jui.ui.util.lang.toSource
import ca.gosyer.jui.ui.viewModel import ca.gosyer.jui.ui.viewModel
import ca.gosyer.jui.uicore.components.VerticalScrollbar import ca.gosyer.jui.uicore.components.VerticalScrollbar
import ca.gosyer.jui.uicore.components.rememberScrollbarAdapter import ca.gosyer.jui.uicore.components.rememberScrollbarAdapter
@@ -70,8 +71,6 @@ import com.vanpra.composematerialdialogs.listItems
import com.vanpra.composematerialdialogs.rememberMaterialDialogState import com.vanpra.composematerialdialogs.rememberMaterialDialogState
import com.vanpra.composematerialdialogs.title import com.vanpra.composematerialdialogs.title
import io.ktor.client.plugins.onDownload import io.ktor.client.plugins.onDownload
import io.ktor.client.plugins.onUpload
import io.ktor.client.statement.bodyAsChannel
import kotlinx.collections.immutable.ImmutableList import kotlinx.collections.immutable.ImmutableList
import kotlinx.collections.immutable.persistentListOf import kotlinx.collections.immutable.persistentListOf
import kotlinx.collections.immutable.toImmutableList import kotlinx.collections.immutable.toImmutableList
@@ -172,19 +171,12 @@ class SettingsBackupViewModel
fun restoreBackup(file: Path) { fun restoreBackup(file: Path) {
importBackupFile importBackupFile
.asFlow(file) { .asFlow(file)
onUpload { bytesSentTotal, contentLength ->
_restoreStatus.value = Status.InProgress(
(bytesSentTotal.toFloat() / contentLength)
.coerceAtMost(1.0F),
)
}
}
.onStart { .onStart {
_restoreStatus.value = Status.InProgress(null) _restoreStatus.value = Status.InProgress(null)
} }
.onEach { .onEach {
_restoreStatus.value = Status.Success _restoreStatus.value = it.second.toStatus()
} }
.catch { .catch {
toast(it.message.orEmpty()) toast(it.message.orEmpty())
@@ -194,6 +186,15 @@ class SettingsBackupViewModel
.launchIn(scope) .launchIn(scope)
} }
private fun RestoreStatus.toStatus() = when (state) {
RestoreState.IDLE -> Status.Success
RestoreState.SUCCESS -> Status.Success
RestoreState.FAILURE -> Status.Error
RestoreState.RESTORING_CATEGORIES -> Status.InProgress(0.01f)
RestoreState.RESTORING_MANGA -> Status.InProgress((completed.toFloat() / total).coerceIn(0f, 1f))
RestoreState.UNKNOWN -> Status.Error
}
fun stopRestore() { fun stopRestore() {
_restoreStatus.value = Status.Error _restoreStatus.value = Status.Error
} }
@@ -203,7 +204,9 @@ class SettingsBackupViewModel
fun exportBackup() { fun exportBackup() {
exportBackupFile exportBackupFile
.asFlow { .asFlow(
true, true // todo
) {
onDownload { bytesSentTotal, contentLength -> onDownload { bytesSentTotal, contentLength ->
_creatingStatus.value = Status.InProgress( _creatingStatus.value = Status.InProgress(
(bytesSentTotal.toFloat() / contentLength) (bytesSentTotal.toFloat() / contentLength)
@@ -214,15 +217,12 @@ class SettingsBackupViewModel
.onStart { .onStart {
_creatingStatus.value = Status.InProgress(null) _creatingStatus.value = Status.InProgress(null)
} }
.onEach { backup -> .onEach { (filename, source) ->
val filename =
backup.headers["content-disposition"]?.substringAfter("filename=")
?.trim('"') ?: "backup"
tempFile.value = FileSystem.SYSTEM_TEMPORARY_DIRECTORY.resolve(filename).also { tempFile.value = FileSystem.SYSTEM_TEMPORARY_DIRECTORY.resolve(filename).also {
mutex.tryLock() mutex.tryLock()
scope.launch { scope.launch {
try { try {
backup.bodyAsChannel().toSource().saveTo(it) source.saveTo(it)
} catch (e: Exception) { } catch (e: Exception) {
e.throwIfCancellation() e.throwIfCancellation()
log.warn(e) { "Error creating backup" } log.warn(e) { "Error creating backup" }
@@ -338,7 +338,7 @@ private fun SettingsBackupScreenContent(
stringResource(MR.strings.backup_restore_sub), stringResource(MR.strings.backup_restore_sub),
restoreStatus, restoreStatus,
) { ) {
fileChooser.launch("gz") fileChooser.launch("gz", "tachibk")
} }
PreferenceFile( PreferenceFile(
stringResource(MR.strings.backup_create), stringResource(MR.strings.backup_create),

View File

@@ -1,14 +0,0 @@
/*
* 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.ui.util.lang
import io.ktor.utils.io.ByteReadChannel
import okio.Source
import kotlin.coroutines.CoroutineContext
import kotlin.coroutines.EmptyCoroutineContext
expect suspend fun ByteReadChannel.toSource(context: CoroutineContext = EmptyCoroutineContext): Source

View File

@@ -26,9 +26,9 @@ actual class FileChooser(
details?.actionPerformed(null) details?.actionPerformed(null)
} }
actual fun launch(extension: String) { actual fun launch(vararg extensions: String) {
scope.launchDefault { scope.launchDefault {
fileChooser.fileFilter = FileNameExtensionFilter("$extension file", extension) fileChooser.fileFilter = FileNameExtensionFilter("${extensions.joinToString()} files", *extensions)
when (fileChooser.showOpenDialog(null)) { when (fileChooser.showOpenDialog(null)) {
JFileChooser.APPROVE_OPTION -> onFileFound(fileChooser.selectedFile.source()) JFileChooser.APPROVE_OPTION -> onFileFound(fileChooser.selectedFile.source())
} }

View File

@@ -1,15 +0,0 @@
/*
* 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.ui.util.lang
import io.ktor.utils.io.ByteReadChannel
import io.ktor.utils.io.jvm.javaio.toInputStream
import okio.Source
import okio.source
import kotlin.coroutines.CoroutineContext
actual suspend fun ByteReadChannel.toSource(context: CoroutineContext): Source = toInputStream().source()