Massive rewrite

- Replace toothpick with kotlin-inject
- Create a data module
- Fix bugs from last rewrite
- Start replacing java.nio.Path with okio.Path
This commit is contained in:
Syer10
2022-01-29 15:36:48 -05:00
parent 9ad1baa36a
commit 8edd05aafe
172 changed files with 902 additions and 738 deletions

View File

@@ -1,11 +1,17 @@
import Config.migrationCode
import Config.serverCode
import Config.tachideskVersion
import com.codingfeline.buildkonfig.compiler.FieldSpec.Type
plugins { plugins {
kotlin("multiplatform") version "1.6.10" apply false kotlin("multiplatform") version "1.6.10" apply false
kotlin("kapt") version "1.6.10" apply false
kotlin("plugin.serialization") version "1.6.10" apply false kotlin("plugin.serialization") version "1.6.10" apply false
id("com.android.library") version "7.0.4" apply false id("com.android.library") version "7.0.4" apply false
id("com.android.application") version "7.0.4" apply false id("com.android.application") version "7.0.4" apply false
id("org.jetbrains.compose") version "1.0.1" apply false id("org.jetbrains.compose") version "1.0.1" apply false
id("com.google.devtools.ksp") version "1.6.10-1.0.2"
id("com.github.gmazzo.buildconfig") version "3.0.3" apply false id("com.github.gmazzo.buildconfig") version "3.0.3" apply false
id("com.codingfeline.buildkonfig") version "0.11.0" apply false
id("dev.icerock.mobile.multiplatform-resources") version "0.18.0" apply false id("dev.icerock.mobile.multiplatform-resources") version "0.18.0" apply false
id("org.jmailen.kotlinter") version "3.8.0" apply false id("org.jmailen.kotlinter") version "3.8.0" apply false
id("com.github.ben-manes.versions") version "0.41.0" id("com.github.ben-manes.versions") version "0.41.0"
@@ -34,6 +40,21 @@ allprojects {
} }
subprojects { subprojects {
tasks.withType<org.jetbrains.kotlin.gradle.dsl.KotlinJvmCompile> {
kotlinOptions {
freeCompilerArgs = freeCompilerArgs + listOf(
"-Xjvm-default=compatibility",
)
}
}
tasks.withType<org.jmailen.gradle.kotlinter.tasks.LintTask> {
source(files("src"))
exclude("ca/gosyer/*/build")
}
tasks.withType<org.jmailen.gradle.kotlinter.tasks.FormatTask> {
source(files("src"))
exclude("ca/gosyer/*/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> {
compileSdkVersion(31) compileSdkVersion(31)
@@ -47,7 +68,7 @@ subprojects {
}*/ }*/
} }
compileOptions { compileOptions {
//isCoreLibraryDesugaringEnabled = true isCoreLibraryDesugaringEnabled = true
sourceCompatibility(JavaVersion.VERSION_11) sourceCompatibility(JavaVersion.VERSION_11)
targetCompatibility(JavaVersion.VERSION_11) targetCompatibility(JavaVersion.VERSION_11)
} }
@@ -60,7 +81,38 @@ subprojects {
} }
} }
dependencies { dependencies {
//add("coreLibraryDesugaring", Deps.desugarJdkLibs) add("coreLibraryDesugaring", libs.desugarJdkLibs)
}
}
}
plugins.withType<com.codingfeline.buildkonfig.gradle.BuildKonfigPlugin> {
configure<com.codingfeline.buildkonfig.gradle.BuildKonfigExtension> {
defaultConfigs {
buildConfigField(Type.STRING, "NAME", rootProject.name)
buildConfigField(Type.STRING, "VERSION", project.version.toString())
buildConfigField(Type.INT, "MIGRATION_CODE", migrationCode.toString())
buildConfigField(Type.BOOLEAN, "DEBUG", project.hasProperty("debugApp").toString())
buildConfigField(Type.BOOLEAN, "IS_PREVIEW", project.hasProperty("preview").toString())
buildConfigField(Type.INT, "PREVIEW_BUILD", project.properties["preview"]?.toString()?.trim('"') ?: 0.toString())
// Tachidesk
buildConfigField(Type.STRING, "TACHIDESK_SP_VERSION", tachideskVersion)
buildConfigField(Type.INT, "SERVER_CODE", serverCode.toString())
}
}
}
plugins.withType<org.jmailen.gradle.kotlinter.KotlinterPlugin> {
configure<org.jmailen.gradle.kotlinter.KotlinterExtension> {
experimentalRules = true
disabledRules = arrayOf("experimental:argument-list-wrapping", "experimental:trailing-comma")
}
}
plugins.withType<com.google.devtools.ksp.gradle.KspGradleSubplugin> {
configure<com.google.devtools.ksp.gradle.KspExtension> {
arg("me.tatarka.inject.generateCompanionExtensions", "true")
if (project.hasProperty("debugApp")) {
arg("me.tatarka.inject.dumpGraph", "true")
} }
} }
} }

View File

@@ -10,5 +10,5 @@ object Config {
const val preview = true const val preview = true
const val previewCommit = "b714abddae9f13e91bc53c5daac54aeae564cd2a" const val previewCommit = "b714abddae9f13e91bc53c5daac54aeae564cd2a"
val jvmTarget = JavaVersion.VERSION_16 val desktopJvmTarget = JavaVersion.VERSION_16
} }

View File

@@ -1,9 +1,9 @@
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile as KotlinJvmCompile
plugins { plugins {
kotlin("multiplatform") kotlin("multiplatform")
kotlin("kapt")
id("com.android.library") id("com.android.library")
id("com.google.devtools.ksp")
id("com.codingfeline.buildkonfig")
id("org.jmailen.kotlinter")
} }
group = "ca.gosyer" group = "ca.gosyer"
@@ -15,7 +15,13 @@ repositories {
kotlin { kotlin {
android() android()
jvm("desktop") jvm("desktop") {
compilations {
all {
kotlinOptions.jvmTarget = Config.desktopJvmTarget.toString()
}
}
}
sourceSets { sourceSets {
all { all {
@@ -27,20 +33,23 @@ kotlin {
} }
} }
val commonMain by getting { val commonMain by getting {
kotlin.srcDir("build/generated/ksp/commonMain/kotlin")
dependencies { dependencies {
api(kotlin("stdlib-common")) api(kotlin("stdlib-common"))
api(libs.coroutinesCore) api(libs.coroutinesCore)
api(libs.json) api(libs.json)
api(libs.toothpickKsp) api(libs.kotlinInjectRuntime)
api(libs.ktorCore) api(libs.ktorCore)
api(libs.ktorSerialization) api(libs.ktorSerialization)
api(libs.okio) api(libs.okio)
api(libs.ktlogging)
api(libs.multiplatformSettingsCore) api(libs.multiplatformSettingsCore)
api(libs.multiplatformSettingsCoroutines) api(libs.multiplatformSettingsCoroutines)
api(libs.multiplatformSettingsSerialization) api(libs.multiplatformSettingsSerialization)
} }
} }
val commonTest by getting { val commonTest by getting {
kotlin.srcDir("build/generated/ksp/commonTest/kotlin")
dependencies { dependencies {
implementation(kotlin("test-common")) implementation(kotlin("test-common"))
implementation(kotlin("test-annotations-common")) implementation(kotlin("test-annotations-common"))
@@ -49,43 +58,36 @@ kotlin {
val desktopMain by getting { val desktopMain by getting {
kotlin.srcDir("src/jvmMain/kotlin") kotlin.srcDir("src/jvmMain/kotlin")
kotlin.srcDir("build/generated/ksp/desktopMain/kotlin")
dependencies { dependencies {
api(kotlin("stdlib-jdk8")) api(kotlin("stdlib-jdk8"))
api(libs.appDirs)
} }
} }
val desktopTest by getting { val desktopTest by getting {
kotlin.srcDir("src/jvmTest/kotlin") kotlin.srcDir("src/jvmTest/kotlin")
kotlin.srcDir("build/generated/ksp/desktopTest/kotlin")
} }
val androidMain by getting { val androidMain by getting {
kotlin.srcDir("src/jvmMain/kotlin") kotlin.srcDir("src/jvmMain/kotlin")
kotlin.srcDir("build/generated/ksp/androidMain/kotlin")
dependencies { dependencies {
api(kotlin("stdlib-jdk8")) api(kotlin("stdlib-jdk8"))
} }
} }
val androidTest by getting { val androidTest by getting {
kotlin.srcDir("src/jvmTest/kotlin") kotlin.srcDir("src/jvmTest/kotlin")
kotlin.srcDir("build/generated/ksp/androidTest/kotlin")
} }
} }
} }
dependencies { dependencies {
add("kapt", libs.toothpickCompiler) add("kspDesktop", libs.kotlinInjectCompiler)
add("kspAndroid", libs.kotlinInjectCompiler)
} }
tasks { buildkonfig {
withType<KotlinJvmCompile> { packageName = "ca.gosyer.core.build"
kotlinOptions {
freeCompilerArgs = listOf("-Xjvm-default=compatibility")
}
}
}
android {
compileSdk = 31
sourceSets["main"].manifest.srcFile("src/androidMain/AndroidManifest.xml")
defaultConfig {
minSdk = 21
targetSdk = 31
}
} }

View File

@@ -9,6 +9,9 @@ package ca.gosyer.core.prefs
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.merge
import kotlinx.coroutines.flow.onEach
/** /**
* A wrapper around application preferences without knowing implementation details. Instances of * A wrapper around application preferences without knowing implementation details. Instances of
@@ -57,3 +60,10 @@ interface Preference<T> {
*/ */
fun stateIn(scope: CoroutineScope): StateFlow<T> fun stateIn(scope: CoroutineScope): StateFlow<T>
} }
fun <T> Preference<T>.getAsFlow(action: (suspend (T) -> Unit)? = null): Flow<T> {
val flow = merge(flowOf(get()), changes())
return if (action != null) {
flow.onEach(action = action)
} else flow
}

View File

@@ -0,0 +1,24 @@
/*
* 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.core.io
import ca.gosyer.core.build.BuildKonfig
import mu.KotlinLogging
import net.harawata.appdirs.AppDirsFactory
import okio.FileSystem
import okio.Path
import okio.Path.Companion.toPath
private val logger = KotlinLogging.logger {}
val userDataDir: Path by lazy {
AppDirsFactory.getInstance().getUserDataDir(BuildKonfig.NAME, null, null).toPath().also {
if (!FileSystem.SYSTEM.exists(it)) {
logger.info("Attempted to create app data dir, result: {}", FileSystem.SYSTEM.createDirectories(it))
}
}
}

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.util.system package ca.gosyer.core.logging
import mu.KLogger import mu.KLogger
import mu.KotlinLogging import mu.KotlinLogging

View File

@@ -6,25 +6,8 @@
package ca.gosyer.core.di package ca.gosyer.core.di
import toothpick.Scope import me.tatarka.inject.annotations.Scope
import toothpick.ktp.KTP
/** @Scope
* The global scope for dependency injection that will provide all the application level components. @Target(AnnotationTarget.CLASS, AnnotationTarget.FUNCTION, AnnotationTarget.PROPERTY_GETTER)
*/ annotation class AppScope
object AppScope : Scope by KTP.openRootScope() {
/**
* Returns a new subscope inheriting the root scope.
*/
fun subscope(any: Any): Scope {
return openSubScope(any)
}
/**
* Returns an instance of [T] from the root scope.
*/
inline fun <reified T> getInstance(): T {
return getInstance(T::class.java)
}
}

View File

@@ -1,17 +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.core.di
import toothpick.Scope
import javax.inject.Provider
class GenericsProvider<T>(private val cls: Class<T>, val scope: Scope = AppScope) : Provider<T> {
override fun get(): T {
return scope.getInstance(cls)
}
}

View File

@@ -1,16 +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.core.di
import toothpick.config.Module
/**
* Binds the given [instance] to its class.
*/
inline fun <reified B> Module.bindInstance(instance: B) {
bind(B::class.java).toInstance(instance)
}

90
data/build.gradle.kts Normal file
View File

@@ -0,0 +1,90 @@
plugins {
kotlin("multiplatform")
id("com.google.devtools.ksp")
kotlin("plugin.serialization")
id("com.android.library")
id("com.codingfeline.buildkonfig")
id("org.jmailen.kotlinter")
}
kotlin {
android()
jvm("desktop") {
compilations {
all {
kotlinOptions.jvmTarget = Config.desktopJvmTarget.toString()
}
}
}
sourceSets {
all {
languageSettings {
optIn("kotlin.RequiresOptIn")
optIn("kotlinx.coroutines.ExperimentalCoroutinesApi")
}
}
val commonMain by getting {
kotlin.srcDir("build/generated/ksp/commonMain/kotlin")
dependencies {
api(kotlin("stdlib-common"))
api(libs.coroutinesCore)
api(libs.json)
api(libs.kotlinInjectRuntime)
api(libs.ktorCore)
api(libs.ktorSerialization)
api(libs.ktorAuth)
api(libs.ktorLogging)
api(libs.ktorWebsockets)
api(libs.ktorOkHttp)
api(libs.okio)
api(project(":core"))
api(project(":i18n"))
}
}
val commonTest by getting {
kotlin.srcDir("build/generated/ksp/commonTest/kotlin")
dependencies {
implementation(kotlin("test-common"))
implementation(kotlin("test-annotations-common"))
}
}
val desktopMain by getting {
kotlin.srcDir("src/jvmMain/kotlin")
kotlin.srcDir("build/generated/ksp/desktopMain/kotlin")
dependencies {
api(kotlin("stdlib-jdk8"))
}
}
val desktopTest by getting {
kotlin.srcDir("src/jvmTest/kotlin")
kotlin.srcDir("build/generated/ksp/desktopTest/kotlin")
}
val androidMain by getting {
kotlin.srcDir("src/jvmMain/kotlin")
kotlin.srcDir("build/generated/ksp/androidMain/kotlin")
dependencies {
api(kotlin("stdlib-jdk8"))
}
}
val androidTest by getting {
kotlin.srcDir("src/jvmTest/kotlin")
kotlin.srcDir("build/generated/ksp/androidTest/kotlin")
}
}
}
dependencies {
add("kspDesktop", libs.kotlinInjectCompiler)
add("kspAndroid", libs.kotlinInjectCompiler)
}
tasks {
}
buildkonfig {
packageName = "ca.gosyer.data.build"
}

View File

@@ -0,0 +1,2 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest package="ca.gosyer.data"/>

View File

@@ -0,0 +1,99 @@
/*
* 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.data
import ca.gosyer.core.di.AppScope
import ca.gosyer.core.prefs.PreferenceStoreFactory
import ca.gosyer.data.catalog.CatalogPreferences
import ca.gosyer.data.download.DownloadService
import ca.gosyer.data.extension.ExtensionPreferences
import ca.gosyer.data.library.LibraryPreferences
import ca.gosyer.data.library.LibraryUpdateService
import ca.gosyer.data.migration.MigrationPreferences
import ca.gosyer.data.migration.Migrations
import ca.gosyer.data.reader.ReaderPreferences
import ca.gosyer.data.server.Http
import ca.gosyer.data.server.HttpProvider
import ca.gosyer.data.server.ServerHostPreferences
import ca.gosyer.data.server.ServerPreferences
import ca.gosyer.data.server.ServerService
import ca.gosyer.data.ui.UiPreferences
import ca.gosyer.data.update.UpdateChecker
import ca.gosyer.data.update.UpdatePreferences
import me.tatarka.inject.annotations.Component
import me.tatarka.inject.annotations.Provides
@AppScope
@Component
abstract class DataComponent {
private val preferenceFactory = PreferenceStoreFactory()
protected abstract val httpProvider: HttpProvider
abstract val serverService: ServerService
abstract val downloadService: DownloadService
abstract val libraryUpdateService: LibraryUpdateService
abstract val migrations: Migrations
abstract val updateChecker: UpdateChecker
@get:AppScope
@get:Provides
val serverPreferences: ServerPreferences
get() = ServerPreferences(preferenceFactory.create("server"))
@get:AppScope
@get:Provides
val serverHostPreferences: ServerHostPreferences
get() = ServerHostPreferences(preferenceFactory.create("host"))
@get:AppScope
@get:Provides
val extensionPreferences: ExtensionPreferences
get() = ExtensionPreferences(preferenceFactory.create("extension"))
@get:AppScope
@get:Provides
val catalogPreferences: CatalogPreferences
get() = CatalogPreferences(preferenceFactory.create("catalog"))
@get:AppScope
@get:Provides
val libraryPreferences: LibraryPreferences
get() = LibraryPreferences(preferenceFactory.create("library"))
@get:AppScope
@get:Provides
val readerPreferences: ReaderPreferences
get() = ReaderPreferences(preferenceFactory.create("reader")) { name ->
preferenceFactory.create("reader", name)
}
@get:AppScope
@get:Provides
val uiPreferences: UiPreferences
get() = UiPreferences(preferenceFactory.create("ui"))
@get:AppScope
@get:Provides
val migrationPreferences: MigrationPreferences
get() = MigrationPreferences(preferenceFactory.create("migration"))
@get:AppScope
@get:Provides
val updatePreferences: UpdatePreferences
get() = UpdatePreferences(preferenceFactory.create("update"))
@get:AppScope
@get:Provides
val http: Http
get() = httpProvider.get(serverPreferences)
companion object
}

View File

@@ -4,13 +4,13 @@
* 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.core.service package ca.gosyer.data.base
import ca.gosyer.build.BuildConfig
import ca.gosyer.core.lang.throwIfCancellation import ca.gosyer.core.lang.throwIfCancellation
import ca.gosyer.core.logging.CKLogger
import ca.gosyer.data.build.BuildKonfig
import ca.gosyer.data.server.Http import ca.gosyer.data.server.Http
import ca.gosyer.data.server.ServerPreferences import ca.gosyer.data.server.ServerPreferences
import ca.gosyer.util.system.CKLogger
import io.ktor.client.features.websocket.ws import io.ktor.client.features.websocket.ws
import io.ktor.http.cio.websocket.Frame import io.ktor.http.cio.websocket.Frame
import kotlinx.coroutines.CancellationException import kotlinx.coroutines.CancellationException
@@ -33,7 +33,7 @@ abstract class WebsocketService(
protected val client: Http protected val client: Http
) { ) {
protected val json = Json { protected val json = Json {
ignoreUnknownKeys = !BuildConfig.DEBUG ignoreUnknownKeys = !BuildKonfig.DEBUG
} }
private val _status = MutableStateFlow(Status.STARTING) private val _status = MutableStateFlow(Status.STARTING)
val status = _status.asStateFlow() val status = _status.asStateFlow()

View File

@@ -6,14 +6,14 @@
package ca.gosyer.data.download package ca.gosyer.data.download
import ca.gosyer.core.service.WebsocketService import ca.gosyer.core.logging.CKLogger
import ca.gosyer.data.base.WebsocketService
import ca.gosyer.data.download.model.DownloadChapter import ca.gosyer.data.download.model.DownloadChapter
import ca.gosyer.data.download.model.DownloadStatus import ca.gosyer.data.download.model.DownloadStatus
import ca.gosyer.data.download.model.DownloaderStatus import ca.gosyer.data.download.model.DownloaderStatus
import ca.gosyer.data.server.Http import ca.gosyer.data.server.Http
import ca.gosyer.data.server.ServerPreferences import ca.gosyer.data.server.ServerPreferences
import ca.gosyer.data.server.requests.downloadsQuery import ca.gosyer.data.server.requests.downloadsQuery
import ca.gosyer.util.system.CKLogger
import io.ktor.http.cio.websocket.Frame import io.ktor.http.cio.websocket.Frame
import io.ktor.http.cio.websocket.readText import io.ktor.http.cio.websocket.readText
import kotlinx.coroutines.DelicateCoroutinesApi import kotlinx.coroutines.DelicateCoroutinesApi
@@ -22,7 +22,7 @@ import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.asSharedFlow import kotlinx.coroutines.flow.asSharedFlow
import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.flow.asStateFlow
import kotlinx.serialization.decodeFromString import kotlinx.serialization.decodeFromString
import javax.inject.Inject import me.tatarka.inject.annotations.Inject
@OptIn(DelicateCoroutinesApi::class) @OptIn(DelicateCoroutinesApi::class)
class DownloadService @Inject constructor( class DownloadService @Inject constructor(

View File

@@ -6,17 +6,17 @@
package ca.gosyer.data.library package ca.gosyer.data.library
import ca.gosyer.core.service.WebsocketService import ca.gosyer.core.logging.CKLogger
import ca.gosyer.data.base.WebsocketService
import ca.gosyer.data.library.model.UpdateStatus import ca.gosyer.data.library.model.UpdateStatus
import ca.gosyer.data.server.Http import ca.gosyer.data.server.Http
import ca.gosyer.data.server.ServerPreferences import ca.gosyer.data.server.ServerPreferences
import ca.gosyer.data.server.requests.updatesQuery import ca.gosyer.data.server.requests.updatesQuery
import ca.gosyer.util.system.CKLogger
import io.ktor.http.cio.websocket.Frame import io.ktor.http.cio.websocket.Frame
import io.ktor.http.cio.websocket.readText import io.ktor.http.cio.websocket.readText
import kotlinx.coroutines.DelicateCoroutinesApi import kotlinx.coroutines.DelicateCoroutinesApi
import kotlinx.serialization.decodeFromString import kotlinx.serialization.decodeFromString
import javax.inject.Inject import me.tatarka.inject.annotations.Inject
@OptIn(DelicateCoroutinesApi::class) @OptIn(DelicateCoroutinesApi::class)
class LibraryUpdateService @Inject constructor( class LibraryUpdateService @Inject constructor(

View File

@@ -6,9 +6,9 @@
package ca.gosyer.data.migration package ca.gosyer.data.migration
import ca.gosyer.build.BuildConfig import ca.gosyer.data.build.BuildKonfig
import ca.gosyer.data.reader.ReaderPreferences import ca.gosyer.data.reader.ReaderPreferences
import javax.inject.Inject import me.tatarka.inject.annotations.Inject
class Migrations @Inject constructor( class Migrations @Inject constructor(
private val migrationPreferences: MigrationPreferences, private val migrationPreferences: MigrationPreferences,
@@ -21,7 +21,7 @@ class Migrations @Inject constructor(
readerPreferences.modes().get().forEach { readerPreferences.modes().get().forEach {
readerPreferences.getMode(it).direction().delete() readerPreferences.getMode(it).direction().delete()
} }
migrationPreferences.version().set(BuildConfig.MIGRATION_CODE) migrationPreferences.version().set(BuildKonfig.MIGRATION_CODE)
return return
} }
} }

View File

@@ -6,7 +6,7 @@
package ca.gosyer.data.reader package ca.gosyer.data.reader
import ca.gosyer.util.system.getAsFlow import ca.gosyer.core.prefs.getAsFlow
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Job import kotlinx.coroutines.Job
import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.MutableStateFlow

View File

@@ -6,7 +6,7 @@
package ca.gosyer.data.server package ca.gosyer.data.server
import ca.gosyer.build.BuildConfig import ca.gosyer.data.build.BuildKonfig
import ca.gosyer.data.server.model.Auth import ca.gosyer.data.server.model.Auth
import ca.gosyer.data.server.model.Proxy import ca.gosyer.data.server.model.Proxy
import io.ktor.client.HttpClient import io.ktor.client.HttpClient
@@ -24,16 +24,13 @@ import io.ktor.client.features.logging.Logging
import io.ktor.client.features.websocket.WebSockets import io.ktor.client.features.websocket.WebSockets
import io.ktor.http.URLBuilder import io.ktor.http.URLBuilder
import kotlinx.serialization.json.Json import kotlinx.serialization.json.Json
import javax.inject.Inject import me.tatarka.inject.annotations.Inject
import javax.inject.Provider
import io.ktor.client.features.auth.Auth as AuthFeature import io.ktor.client.features.auth.Auth as AuthFeature
typealias Http = HttpClient typealias Http = HttpClient
internal class HttpProvider @Inject constructor( class HttpProvider @Inject constructor() {
private val serverPreferences: ServerPreferences fun get(serverPreferences: ServerPreferences): Http {
) : Provider<Http> {
override fun get(): Http {
return HttpClient(OkHttp) { return HttpClient(OkHttp) {
engine { engine {
proxy = when (serverPreferences.proxy().get()) { proxy = when (serverPreferences.proxy().get()) {
@@ -85,7 +82,7 @@ internal class HttpProvider @Inject constructor(
} }
install(WebSockets) install(WebSockets)
install(Logging) { install(Logging) {
level = if (BuildConfig.DEBUG) { level = if (BuildKonfig.DEBUG) {
LogLevel.HEADERS LogLevel.HEADERS
} else { } else {
LogLevel.INFO LogLevel.INFO

View File

@@ -6,10 +6,11 @@
package ca.gosyer.data.server package ca.gosyer.data.server
import ca.gosyer.build.BuildConfig import ca.gosyer.core.io.copyTo
import ca.gosyer.core.io.userDataDir
import ca.gosyer.core.lang.withIOContext import ca.gosyer.core.lang.withIOContext
import ca.gosyer.util.system.CKLogger import ca.gosyer.core.logging.CKLogger
import ca.gosyer.util.system.userDataDir import ca.gosyer.data.build.BuildKonfig
import kotlinx.coroutines.CoroutineExceptionHandler import kotlinx.coroutines.CoroutineExceptionHandler
import kotlinx.coroutines.DelicateCoroutinesApi import kotlinx.coroutines.DelicateCoroutinesApi
import kotlinx.coroutines.GlobalScope import kotlinx.coroutines.GlobalScope
@@ -20,23 +21,22 @@ import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.mapLatest import kotlinx.coroutines.flow.mapLatest
import kotlinx.coroutines.flow.merge import kotlinx.coroutines.flow.merge
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import me.tatarka.inject.annotations.Inject
import mu.KotlinLogging import mu.KotlinLogging
import okio.FileSystem
import okio.Path
import okio.Path.Companion.toPath
import okio.buffer
import okio.source
import java.io.File.pathSeparatorChar import java.io.File.pathSeparatorChar
import java.io.IOException import java.io.IOException
import java.io.Reader import java.io.Reader
import java.nio.file.Path
import java.util.jar.Attributes import java.util.jar.Attributes
import java.util.jar.JarInputStream import java.util.jar.JarInputStream
import javax.inject.Inject
import kotlin.concurrent.thread import kotlin.concurrent.thread
import kotlin.io.path.Path
import kotlin.io.path.absolutePathString import kotlin.io.path.absolutePathString
import kotlin.io.path.createDirectories
import kotlin.io.path.exists import kotlin.io.path.exists
import kotlin.io.path.inputStream
import kotlin.io.path.isExecutable import kotlin.io.path.isExecutable
import kotlin.io.path.name
import kotlin.io.path.outputStream
@OptIn(DelicateCoroutinesApi::class) @OptIn(DelicateCoroutinesApi::class)
class ServerService @Inject constructor( class ServerService @Inject constructor(
@@ -64,17 +64,14 @@ class ServerService @Inject constructor(
} }
@Throws(IOException::class) @Throws(IOException::class)
private fun copyJar(jarFile: Path) { private suspend fun copyJar(jarFile: Path) {
javaClass.getResourceAsStream("/Tachidesk.jar")?.buffered()?.use { input -> javaClass.getResourceAsStream("/Tachidesk.jar")?.source()
jarFile.outputStream().use { output -> ?.copyTo(FileSystem.SYSTEM.sink(jarFile).buffer())
input.copyTo(output)
}
}
} }
private fun getJavaFromPath(javaPath: Path): String? { private fun getJavaFromPath(javaPath: Path): String? {
val javaExeFile = javaPath.resolve("java.exe") val javaExeFile = javaPath.resolve("java.exe").toNioPath()
val javaUnixFile = javaPath.resolve("java") val javaUnixFile = javaPath.resolve("java").toNioPath()
return when { return when {
javaExeFile.exists() && javaExeFile.isExecutable() -> javaExeFile.absolutePathString() javaExeFile.exists() && javaExeFile.isExecutable() -> javaExeFile.absolutePathString()
javaUnixFile.exists() && javaUnixFile.isExecutable() -> javaUnixFile.absolutePathString() javaUnixFile.exists() && javaUnixFile.isExecutable() -> javaUnixFile.absolutePathString()
@@ -83,7 +80,7 @@ class ServerService @Inject constructor(
} }
private fun getRuntimeJava(): String? { private fun getRuntimeJava(): String? {
return System.getProperty("java.home")?.let { getJavaFromPath(Path(it).resolve("bin")) } return System.getProperty("java.home")?.let { getJavaFromPath(it.toPath().resolve("bin")) }
} }
private fun getPossibleJava(): String? { private fun getPossibleJava(): String? {
@@ -91,8 +88,8 @@ class ServerService @Inject constructor(
.orEmpty() .orEmpty()
.asSequence() .asSequence()
.mapNotNull { .mapNotNull {
val file = Path(it) val file = it.toPath()
if (file.absolutePathString().contains("java") || file.absolutePathString().contains("jdk")) { if (file.toString().contains("java") || file.toString().contains("jdk")) {
if (file.name.equals("bin", true)) { if (file.name.equals("bin", true)) {
file file
} else file.resolve("bin") } else file.resolve("bin")
@@ -126,25 +123,25 @@ class ServerService @Inject constructor(
} }
} }
GlobalScope.launch(handler) { GlobalScope.launch(handler) {
val jarFile = userDataDir.also { it.createDirectories() }.resolve("Tachidesk.jar") val jarFile = userDataDir / "Tachidesk.jar"
if (!jarFile.exists()) { if (!FileSystem.SYSTEM.exists(jarFile)) {
info { "Copying server to resources" } info { "Copying server to resources" }
withIOContext { copyJar(jarFile) } withIOContext { copyJar(jarFile) }
} else { } else {
try { try {
val jarVersion = withIOContext { val jarVersion = withIOContext {
JarInputStream(jarFile.inputStream()).use { jar -> JarInputStream(FileSystem.SYSTEM.source(jarFile).buffer().inputStream()).use { jar ->
jar.manifest?.mainAttributes?.getValue(Attributes.Name.IMPLEMENTATION_VERSION)?.toIntOrNull() jar.manifest?.mainAttributes?.getValue(Attributes.Name.IMPLEMENTATION_VERSION)?.toIntOrNull()
} }
} }
if (jarVersion != BuildConfig.SERVER_CODE) { if (jarVersion != BuildKonfig.SERVER_CODE) {
info { "Updating server file from resources" } info { "Updating server file from resources" }
withIOContext { copyJar(jarFile) } withIOContext { copyJar(jarFile) }
} }
} catch (e: IOException) { } catch (e: IOException) {
error(e) { error(e) {
"Error accessing server jar, cannot update server, ${BuildConfig.NAME} may not work properly" "Error accessing server jar, cannot update server, ${BuildKonfig.NAME} may not work properly"
} }
} }
} }
@@ -156,7 +153,7 @@ class ServerService @Inject constructor(
withIOContext { withIOContext {
val reader: Reader val reader: Reader
process = ProcessBuilder(javaPath, *properties, "-jar", jarFile.absolutePathString()) process = ProcessBuilder(javaPath, *properties, "-jar", jarFile.toString())
.redirectErrorStream(true) .redirectErrorStream(true)
.start() .start()
.also { .also {

View File

@@ -21,9 +21,10 @@ import io.ktor.client.statement.HttpResponse
import io.ktor.http.ContentType import io.ktor.http.ContentType
import io.ktor.http.Headers import io.ktor.http.Headers
import io.ktor.http.HttpHeaders import io.ktor.http.HttpHeaders
import java.nio.file.Path import me.tatarka.inject.annotations.Inject
import javax.inject.Inject import okio.FileSystem
import kotlin.io.path.readBytes import okio.Path
import okio.buffer
class BackupInteractionHandler @Inject constructor( class BackupInteractionHandler @Inject constructor(
client: Http, client: Http,
@@ -35,7 +36,7 @@ class BackupInteractionHandler @Inject constructor(
serverUrl + backupFileImportRequest(), serverUrl + backupFileImportRequest(),
formData = formData { formData = formData {
append( append(
"backup.proto.gz", file.readBytes(), "backup.proto.gz", FileSystem.SYSTEM.source(file).buffer().readByteArray(),
Headers.build { Headers.build {
append(HttpHeaders.ContentType, ContentType.MultiPart.FormData.toString()) append(HttpHeaders.ContentType, ContentType.MultiPart.FormData.toString())
append(HttpHeaders.ContentDisposition, "filename=backup.proto.gz") append(HttpHeaders.ContentDisposition, "filename=backup.proto.gz")
@@ -51,7 +52,7 @@ class BackupInteractionHandler @Inject constructor(
serverUrl + validateBackupFileRequest(), serverUrl + validateBackupFileRequest(),
formData = formData { formData = formData {
append( append(
"backup.proto.gz", file.readBytes(), "backup.proto.gz", FileSystem.SYSTEM.source(file).buffer().readByteArray(),
Headers.build { Headers.build {
append(HttpHeaders.ContentType, ContentType.MultiPart.FormData.toString()) append(HttpHeaders.ContentType, ContentType.MultiPart.FormData.toString())
append(HttpHeaders.ContentDisposition, "filename=backup.proto.gz") append(HttpHeaders.ContentDisposition, "filename=backup.proto.gz")

View File

@@ -26,7 +26,7 @@ import io.ktor.client.request.get
import io.ktor.client.statement.HttpResponse import io.ktor.client.statement.HttpResponse
import io.ktor.http.HttpMethod import io.ktor.http.HttpMethod
import io.ktor.http.Parameters import io.ktor.http.Parameters
import javax.inject.Inject import me.tatarka.inject.annotations.Inject
class CategoryInteractionHandler @Inject constructor( class CategoryInteractionHandler @Inject constructor(
client: Http, client: Http,

View File

@@ -19,7 +19,6 @@ import ca.gosyer.data.server.requests.queueDownloadChapterRequest
import ca.gosyer.data.server.requests.stopDownloadingChapterRequest import ca.gosyer.data.server.requests.stopDownloadingChapterRequest
import ca.gosyer.data.server.requests.updateChapterMetaRequest import ca.gosyer.data.server.requests.updateChapterMetaRequest
import ca.gosyer.data.server.requests.updateChapterRequest import ca.gosyer.data.server.requests.updateChapterRequest
import ca.gosyer.util.compose.imageFromUrl
import io.ktor.client.request.HttpRequestBuilder import io.ktor.client.request.HttpRequestBuilder
import io.ktor.client.request.delete import io.ktor.client.request.delete
import io.ktor.client.request.forms.submitForm import io.ktor.client.request.forms.submitForm
@@ -28,7 +27,8 @@ import io.ktor.client.request.parameter
import io.ktor.client.statement.HttpResponse import io.ktor.client.statement.HttpResponse
import io.ktor.http.HttpMethod import io.ktor.http.HttpMethod
import io.ktor.http.Parameters import io.ktor.http.Parameters
import javax.inject.Inject import io.ktor.utils.io.ByteReadChannel
import me.tatarka.inject.annotations.Inject
class ChapterInteractionHandler @Inject constructor( class ChapterInteractionHandler @Inject constructor(
client: Http, client: Http,
@@ -123,8 +123,7 @@ class ChapterInteractionHandler @Inject constructor(
) )
suspend fun getPage(mangaId: Long, chapterIndex: Int, pageNum: Int, block: HttpRequestBuilder.() -> Unit) = withIOContext { suspend fun getPage(mangaId: Long, chapterIndex: Int, pageNum: Int, block: HttpRequestBuilder.() -> Unit) = withIOContext {
imageFromUrl( client.get<ByteReadChannel>(
client,
serverUrl + getPageQuery(mangaId, chapterIndex, pageNum), serverUrl + getPageQuery(mangaId, chapterIndex, pageNum),
block block
) )

View File

@@ -14,7 +14,7 @@ import ca.gosyer.data.server.requests.downloadsStartRequest
import ca.gosyer.data.server.requests.downloadsStopRequest import ca.gosyer.data.server.requests.downloadsStopRequest
import io.ktor.client.request.get import io.ktor.client.request.get
import io.ktor.client.statement.HttpResponse import io.ktor.client.statement.HttpResponse
import javax.inject.Inject import me.tatarka.inject.annotations.Inject
class DownloadInteractionHandler @Inject constructor( class DownloadInteractionHandler @Inject constructor(
client: Http, client: Http,

View File

@@ -15,11 +15,11 @@ import ca.gosyer.data.server.requests.apkInstallQuery
import ca.gosyer.data.server.requests.apkUninstallQuery import ca.gosyer.data.server.requests.apkUninstallQuery
import ca.gosyer.data.server.requests.apkUpdateQuery import ca.gosyer.data.server.requests.apkUpdateQuery
import ca.gosyer.data.server.requests.extensionListQuery import ca.gosyer.data.server.requests.extensionListQuery
import ca.gosyer.util.compose.imageFromUrl
import io.ktor.client.request.HttpRequestBuilder import io.ktor.client.request.HttpRequestBuilder
import io.ktor.client.request.get import io.ktor.client.request.get
import io.ktor.client.statement.HttpResponse import io.ktor.client.statement.HttpResponse
import javax.inject.Inject import io.ktor.utils.io.ByteReadChannel
import me.tatarka.inject.annotations.Inject
class ExtensionInteractionHandler @Inject constructor( class ExtensionInteractionHandler @Inject constructor(
client: Http, client: Http,
@@ -51,8 +51,7 @@ class ExtensionInteractionHandler @Inject constructor(
} }
suspend fun getApkIcon(extension: Extension, block: HttpRequestBuilder.() -> Unit) = withIOContext { suspend fun getApkIcon(extension: Extension, block: HttpRequestBuilder.() -> Unit) = withIOContext {
imageFromUrl( client.get<ByteReadChannel>(
client,
serverUrl + apkIconQuery(extension.apkName), serverUrl + apkIconQuery(extension.apkName),
block block
) )

View File

@@ -15,7 +15,7 @@ import ca.gosyer.data.server.requests.removeMangaFromLibraryRequest
import io.ktor.client.request.delete import io.ktor.client.request.delete
import io.ktor.client.request.get import io.ktor.client.request.get
import io.ktor.client.statement.HttpResponse import io.ktor.client.statement.HttpResponse
import javax.inject.Inject import me.tatarka.inject.annotations.Inject
class LibraryInteractionHandler @Inject constructor( class LibraryInteractionHandler @Inject constructor(
client: Http, client: Http,

View File

@@ -13,7 +13,6 @@ import ca.gosyer.data.server.ServerPreferences
import ca.gosyer.data.server.requests.mangaQuery import ca.gosyer.data.server.requests.mangaQuery
import ca.gosyer.data.server.requests.mangaThumbnailQuery import ca.gosyer.data.server.requests.mangaThumbnailQuery
import ca.gosyer.data.server.requests.updateMangaMetaRequest import ca.gosyer.data.server.requests.updateMangaMetaRequest
import ca.gosyer.util.compose.imageFromUrl
import io.ktor.client.request.HttpRequestBuilder import io.ktor.client.request.HttpRequestBuilder
import io.ktor.client.request.forms.submitForm import io.ktor.client.request.forms.submitForm
import io.ktor.client.request.get import io.ktor.client.request.get
@@ -21,7 +20,8 @@ import io.ktor.client.request.parameter
import io.ktor.client.statement.HttpResponse import io.ktor.client.statement.HttpResponse
import io.ktor.http.HttpMethod import io.ktor.http.HttpMethod
import io.ktor.http.Parameters import io.ktor.http.Parameters
import javax.inject.Inject import io.ktor.utils.io.ByteReadChannel
import me.tatarka.inject.annotations.Inject
class MangaInteractionHandler @Inject constructor( class MangaInteractionHandler @Inject constructor(
client: Http, client: Http,
@@ -43,8 +43,7 @@ class MangaInteractionHandler @Inject constructor(
suspend fun getManga(manga: Manga, refresh: Boolean = false) = getManga(manga.id, refresh) suspend fun getManga(manga: Manga, refresh: Boolean = false) = getManga(manga.id, refresh)
suspend fun getMangaThumbnail(mangaId: Long, block: HttpRequestBuilder.() -> Unit) = withIOContext { suspend fun getMangaThumbnail(mangaId: Long, block: HttpRequestBuilder.() -> Unit) = withIOContext {
imageFromUrl( client.get<ByteReadChannel>(
client,
serverUrl + mangaThumbnailQuery(mangaId), serverUrl + mangaThumbnailQuery(mangaId),
block block
) )

View File

@@ -33,7 +33,7 @@ import io.ktor.http.ContentType
import io.ktor.http.contentType import io.ktor.http.contentType
import kotlinx.serialization.encodeToString import kotlinx.serialization.encodeToString
import kotlinx.serialization.json.Json import kotlinx.serialization.json.Json
import javax.inject.Inject import me.tatarka.inject.annotations.Inject
class SourceInteractionHandler @Inject constructor( class SourceInteractionHandler @Inject constructor(
client: Http, client: Http,

View File

@@ -18,7 +18,7 @@ import io.ktor.client.request.get
import io.ktor.client.request.post import io.ktor.client.request.post
import io.ktor.client.statement.HttpResponse import io.ktor.client.statement.HttpResponse
import io.ktor.http.Parameters import io.ktor.http.Parameters
import javax.inject.Inject import me.tatarka.inject.annotations.Inject
class UpdatesInteractionHandler @Inject constructor( class UpdatesInteractionHandler @Inject constructor(
client: Http, client: Http,

View File

@@ -0,0 +1,19 @@
/*
* 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.data.ui.model
import kotlinx.serialization.Serializable
@Serializable
data class WindowSettings(
val x: Int? = null,
val y: Int? = null,
val width: Int? = null,
val height: Int? = null,
val maximized: Boolean? = null,
val fullscreen: Boolean? = null
)

View File

@@ -6,16 +6,16 @@
package ca.gosyer.data.update package ca.gosyer.data.update
import ca.gosyer.build.BuildConfig
import ca.gosyer.core.lang.launch import ca.gosyer.core.lang.launch
import ca.gosyer.core.lang.withIOContext import ca.gosyer.core.lang.withIOContext
import ca.gosyer.data.build.BuildKonfig
import ca.gosyer.data.server.Http import ca.gosyer.data.server.Http
import ca.gosyer.data.update.model.GithubRelease import ca.gosyer.data.update.model.GithubRelease
import io.ktor.client.request.get import io.ktor.client.request.get
import kotlinx.coroutines.DelicateCoroutinesApi import kotlinx.coroutines.DelicateCoroutinesApi
import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.asSharedFlow import kotlinx.coroutines.flow.asSharedFlow
import javax.inject.Inject import me.tatarka.inject.annotations.Inject
class UpdateChecker @Inject constructor( class UpdateChecker @Inject constructor(
private val updatePreferences: UpdatePreferences, private val updatePreferences: UpdatePreferences,
@@ -42,19 +42,19 @@ class UpdateChecker @Inject constructor(
// Removes prefixes like "r" or "v" // Removes prefixes like "r" or "v"
val newVersion = versionTag.replace("[^\\d.]".toRegex(), "") val newVersion = versionTag.replace("[^\\d.]".toRegex(), "")
return if (BuildConfig.IS_PREVIEW) { return if (BuildKonfig.IS_PREVIEW) {
// Preview builds: based on releases in "Suwayomi/Tachidesk-JUI-preview" repo // Preview builds: based on releases in "Suwayomi/Tachidesk-JUI-preview" repo
// tagged as something like "r123" // tagged as something like "r123"
newVersion.toInt() > BuildConfig.PREVIEW_BUILD newVersion.toInt() > BuildKonfig.PREVIEW_BUILD
} else { } else {
// Release builds: based on releases in "Suwayomi/Tachidesk-JUI" repo // Release builds: based on releases in "Suwayomi/Tachidesk-JUI" repo
// tagged as something like "v1.1.2" // tagged as something like "v1.1.2"
newVersion != BuildConfig.VERSION newVersion != BuildKonfig.VERSION
} }
} }
companion object { companion object {
private val GITHUB_REPO = if (BuildConfig.IS_PREVIEW) { private val GITHUB_REPO = if (BuildKonfig.IS_PREVIEW) {
"Suwayomi/Tachidesk-JUI-preview" "Suwayomi/Tachidesk-JUI-preview"
} else { } else {
"Suwayomi/Tachidesk-JUI" "Suwayomi/Tachidesk-JUI"

Some files were not shown because too many files have changed in this diff Show More