From 2280e8c7257f312f011dde40d5cf44bb8ff2584b Mon Sep 17 00:00:00 2001 From: Aria Moradi Date: Fri, 30 Jul 2021 15:05:21 +0430 Subject: [PATCH 1/7] initial PreferenceScreen support, works with 'NeoXXX Scans' (pt-br) --- .../preference/EditTextPreference.java | 46 ++++++++++++++ .../java/androidx/preference/Preference.java | 61 +++++++++++++++++++ .../androidx/preference/PreferenceScreen.java | 30 ++++++++- .../animesource/ConfigurableAnimeSource.kt | 2 +- .../tachiyomi/source/ConfigurableSource.kt | 1 - .../suwayomi/tachidesk/manga/TachideskAPI.kt | 7 +++ .../suwayomi/tachidesk/manga/impl/Source.kt | 26 +++++++- .../manga/model/dataclass/SourceDataClass.kt | 3 +- 8 files changed, 170 insertions(+), 6 deletions(-) create mode 100644 AndroidCompat/src/main/java/androidx/preference/EditTextPreference.java create mode 100644 AndroidCompat/src/main/java/androidx/preference/Preference.java diff --git a/AndroidCompat/src/main/java/androidx/preference/EditTextPreference.java b/AndroidCompat/src/main/java/androidx/preference/EditTextPreference.java new file mode 100644 index 00000000..088f8392 --- /dev/null +++ b/AndroidCompat/src/main/java/androidx/preference/EditTextPreference.java @@ -0,0 +1,46 @@ +package androidx.preference; + +import android.content.Context; + +public class EditTextPreference extends Preference { + private String title; + private CharSequence summary; + private CharSequence dialogTitle; + private CharSequence dialogMessage; + + public EditTextPreference(Context context) { + super(context); + } + + public String getTitle() { + return title; + } + + public void setTitle(CharSequence title) { + this.title = (String) title; + } + + public CharSequence getSummary() { + return summary; + } + + public void setSummary(CharSequence summary) { + this.summary = summary; + } + + public CharSequence getDialogTitle() { + return dialogTitle; + } + + public void setDialogTitle(CharSequence dialogTitle) { + this.dialogTitle = dialogTitle; + } + + public CharSequence getDialogMessage() { + return dialogMessage; + } + + public void setDialogMessage(CharSequence dialogMessage) { + this.dialogMessage = dialogMessage; + } +} diff --git a/AndroidCompat/src/main/java/androidx/preference/Preference.java b/AndroidCompat/src/main/java/androidx/preference/Preference.java new file mode 100644 index 00000000..7029b703 --- /dev/null +++ b/AndroidCompat/src/main/java/androidx/preference/Preference.java @@ -0,0 +1,61 @@ +package androidx.preference; + +/* + * Copyright (C) Contributors to the Suwayomi project + * + * 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/. */ + +import android.content.Context; + +/** A minimal implementation of androidx.preference.Preference */ +public class Preference { + // reference: https://android.googlesource.com/platform/frameworks/support/+/996971f962fcd554339a7cb2859cef9ca89dbcb7/preference/preference/src/main/java/androidx/preference/Preference.java + // Note: `Preference` doesn't actually hold or persist the value, `OnPreferenceChangeListener` is called and it's up to the extension to persist it. + + protected Context context; + + private String key; + private CharSequence title; + private Object defaultValue; + private OnPreferenceChangeListener onChangeListener; + + public Preference(Context context) { + this.context = context; + } + + public Context getContext() { + return context; + } + + + public String getKey() { + return key; + } + + public void setKey(String key) { + this.key = key; + } + + public void setDefaultValue(Object defaultValue) { + this.defaultValue = defaultValue; + } + + public CharSequence getTitle() { + return title; + } + + public void setOnPreferenceChangeListener(OnPreferenceChangeListener onPreferenceChangeListener) { + this.onChangeListener = onPreferenceChangeListener; + } + + public boolean callChangeListener(Object newValue) { + return onChangeListener == null || onChangeListener.onPreferenceChange(this, newValue); + } + + public interface OnPreferenceChangeListener { + boolean onPreferenceChange(Preference preference, Object newValue); + } + +} diff --git a/AndroidCompat/src/main/java/androidx/preference/PreferenceScreen.java b/AndroidCompat/src/main/java/androidx/preference/PreferenceScreen.java index cf1832e0..0b91501c 100644 --- a/AndroidCompat/src/main/java/androidx/preference/PreferenceScreen.java +++ b/AndroidCompat/src/main/java/androidx/preference/PreferenceScreen.java @@ -1,4 +1,32 @@ package androidx.preference; -public class PreferenceScreen { +/* + * Copyright (C) Contributors to the Suwayomi project + * + * 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/. */ + +import android.content.Context; + +import java.util.LinkedList; +import java.util.List; + +public class PreferenceScreen extends Preference { + private List preferences = new LinkedList<>(); + + + public PreferenceScreen(Context context) { + super(context); + } + + public boolean addPreference(Preference preference) { + preferences.add(preference); + + return true; + } + + public List getPreferences(){ + return preferences; + } } diff --git a/server/src/main/kotlin/eu/kanade/tachiyomi/animesource/ConfigurableAnimeSource.kt b/server/src/main/kotlin/eu/kanade/tachiyomi/animesource/ConfigurableAnimeSource.kt index 4358a7f5..5d4e872f 100644 --- a/server/src/main/kotlin/eu/kanade/tachiyomi/animesource/ConfigurableAnimeSource.kt +++ b/server/src/main/kotlin/eu/kanade/tachiyomi/animesource/ConfigurableAnimeSource.kt @@ -1,6 +1,6 @@ package eu.kanade.tachiyomi.animesource -import android.support.v7.preference.PreferenceScreen +import androidx.preference.PreferenceScreen interface ConfigurableAnimeSource : AnimeSource { diff --git a/server/src/main/kotlin/eu/kanade/tachiyomi/source/ConfigurableSource.kt b/server/src/main/kotlin/eu/kanade/tachiyomi/source/ConfigurableSource.kt index 0dd9bb4e..ac5b780a 100644 --- a/server/src/main/kotlin/eu/kanade/tachiyomi/source/ConfigurableSource.kt +++ b/server/src/main/kotlin/eu/kanade/tachiyomi/source/ConfigurableSource.kt @@ -7,7 +7,6 @@ package eu.kanade.tachiyomi.source * 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/. */ - import androidx.preference.PreferenceScreen interface ConfigurableSource : Source { diff --git a/server/src/main/kotlin/suwayomi/tachidesk/manga/TachideskAPI.kt b/server/src/main/kotlin/suwayomi/tachidesk/manga/TachideskAPI.kt index 88421c6b..e70ce519 100644 --- a/server/src/main/kotlin/suwayomi/tachidesk/manga/TachideskAPI.kt +++ b/server/src/main/kotlin/suwayomi/tachidesk/manga/TachideskAPI.kt @@ -30,6 +30,7 @@ import suwayomi.tachidesk.manga.impl.Search.sourceGlobalSearch import suwayomi.tachidesk.manga.impl.Search.sourceSearch import suwayomi.tachidesk.manga.impl.Source.getSource import suwayomi.tachidesk.manga.impl.Source.getSourceList +import suwayomi.tachidesk.manga.impl.Source.getSourcePreferences import suwayomi.tachidesk.manga.impl.backup.BackupFlags import suwayomi.tachidesk.manga.impl.backup.legacy.LegacyBackupExport.createLegacyBackup import suwayomi.tachidesk.manga.impl.backup.legacy.LegacyBackupImport.restoreLegacyBackup @@ -109,6 +110,12 @@ object TachideskAPI { ctx.json(getSource(sourceId)) } + // fetch preferences of source with id `sourceId` + app.get("/api/v1/source/:sourceId/preference-screen") { ctx -> + val sourceId = ctx.pathParam("sourceId").toLong() + ctx.json(getSourcePreferences(sourceId)) + } + // popular mangas from source with id `sourceId` app.get("/api/v1/source/:sourceId/popular/:pageNum") { ctx -> val sourceId = ctx.pathParam("sourceId").toLong() diff --git a/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/Source.kt b/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/Source.kt index 7f8373d5..1d0948b8 100644 --- a/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/Source.kt +++ b/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/Source.kt @@ -7,15 +7,21 @@ package suwayomi.tachidesk.manga.impl * 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/. */ +import androidx.preference.PreferenceScreen +import eu.kanade.tachiyomi.source.ConfigurableSource import mu.KotlinLogging import org.jetbrains.exposed.sql.select import org.jetbrains.exposed.sql.selectAll import org.jetbrains.exposed.sql.transactions.transaction +import org.kodein.di.DI +import org.kodein.di.conf.global +import org.kodein.di.instance import suwayomi.tachidesk.manga.impl.extension.Extension.getExtensionIconUrl import suwayomi.tachidesk.manga.impl.util.GetHttpSource.getHttpSource import suwayomi.tachidesk.manga.model.dataclass.SourceDataClass import suwayomi.tachidesk.manga.model.table.ExtensionTable import suwayomi.tachidesk.manga.model.table.SourceTable +import xyz.nulldev.androidcompat.androidimpl.CustomContext object Source { private val logger = KotlinLogging.logger {} @@ -28,7 +34,8 @@ object Source { it[SourceTable.name], it[SourceTable.lang], getExtensionIconUrl(ExtensionTable.select { ExtensionTable.id eq it[SourceTable.extension] }.first()[ExtensionTable.apkName]), - getHttpSource(it[SourceTable.id].value).supportsLatest + getHttpSource(it[SourceTable.id].value).supportsLatest, + getHttpSource(it[SourceTable.id].value) is ConfigurableSource ) } } @@ -43,8 +50,23 @@ object Source { source?.get(SourceTable.name), source?.get(SourceTable.lang), source?.let { ExtensionTable.select { ExtensionTable.id eq source[SourceTable.extension] }.first()[ExtensionTable.iconUrl] }, - source?.let { getHttpSource(sourceId).supportsLatest } + source?.let { getHttpSource(sourceId).supportsLatest }, + source?.let { getHttpSource(sourceId) is ConfigurableSource }, ) } } + + private val context by DI.global.instance() + + fun getSourcePreferences(sourceId: Long) { + val source = getHttpSource(sourceId) + + if (source is ConfigurableSource) { + val screen = PreferenceScreen(context) + + source.setupPreferenceScreen(screen) + + screen.preferences.forEach { println(it) } + } + } } diff --git a/server/src/main/kotlin/suwayomi/tachidesk/manga/model/dataclass/SourceDataClass.kt b/server/src/main/kotlin/suwayomi/tachidesk/manga/model/dataclass/SourceDataClass.kt index 939a356a..022356cc 100644 --- a/server/src/main/kotlin/suwayomi/tachidesk/manga/model/dataclass/SourceDataClass.kt +++ b/server/src/main/kotlin/suwayomi/tachidesk/manga/model/dataclass/SourceDataClass.kt @@ -12,5 +12,6 @@ data class SourceDataClass( val name: String?, val lang: String?, val iconUrl: String?, - val supportsLatest: Boolean? + val supportsLatest: Boolean?, + val isConfigurable: Boolean? ) From 7c03c73419b5bc02db6a0489ad89c9429ea63805 Mon Sep 17 00:00:00 2001 From: Aria Moradi Date: Fri, 30 Jul 2021 18:48:07 +0430 Subject: [PATCH 2/7] convert EditTextPreference to json successfully --- .../main/java/androidx/preference/Preference.java | 11 +++++++++-- build.gradle.kts | 3 +++ .../kotlin/suwayomi/tachidesk/manga/impl/Source.kt | 12 ++++++++++-- .../suwayomi/tachidesk/server/JavalinSetup.kt | 14 +++++++------- 4 files changed, 29 insertions(+), 11 deletions(-) diff --git a/AndroidCompat/src/main/java/androidx/preference/Preference.java b/AndroidCompat/src/main/java/androidx/preference/Preference.java index 7029b703..9a619dd1 100644 --- a/AndroidCompat/src/main/java/androidx/preference/Preference.java +++ b/AndroidCompat/src/main/java/androidx/preference/Preference.java @@ -8,18 +8,22 @@ package androidx.preference; * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ import android.content.Context; +import com.fasterxml.jackson.annotation.JsonIgnore; /** A minimal implementation of androidx.preference.Preference */ public class Preference { // reference: https://android.googlesource.com/platform/frameworks/support/+/996971f962fcd554339a7cb2859cef9ca89dbcb7/preference/preference/src/main/java/androidx/preference/Preference.java // Note: `Preference` doesn't actually hold or persist the value, `OnPreferenceChangeListener` is called and it's up to the extension to persist it. + @JsonIgnore protected Context context; private String key; private CharSequence title; private Object defaultValue; - private OnPreferenceChangeListener onChangeListener; + + @JsonIgnore + public OnPreferenceChangeListener onChangeListener; public Preference(Context context) { this.context = context; @@ -29,7 +33,6 @@ public class Preference { return context; } - public String getKey() { return key; } @@ -54,6 +57,10 @@ public class Preference { return onChangeListener == null || onChangeListener.onPreferenceChange(this, newValue); } + public Object getDefaultValue() { + return defaultValue; + } + public interface OnPreferenceChangeListener { boolean onPreferenceChange(Preference preference, Object newValue); } diff --git a/build.gradle.kts b/build.gradle.kts index be658d61..9697908b 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -84,5 +84,8 @@ configure(projects) { // APK parser implementation("net.dongliu:apk-parser:2.6.10") + + // Jackson + implementation("com.fasterxml.jackson.core:jackson-annotations:2.10.3") } } \ No newline at end of file diff --git a/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/Source.kt b/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/Source.kt index 1d0948b8..58f8db3c 100644 --- a/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/Source.kt +++ b/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/Source.kt @@ -58,7 +58,12 @@ object Source { private val context by DI.global.instance() - fun getSourcePreferences(sourceId: Long) { + data class PreferenceObject( + val type: String, + val props: Any + ) + + fun getSourcePreferences(sourceId: Long): List { val source = getHttpSource(sourceId) if (source is ConfigurableSource) { @@ -66,7 +71,10 @@ object Source { source.setupPreferenceScreen(screen) - screen.preferences.forEach { println(it) } + return screen.preferences.map { + PreferenceObject(it::class.java.name, it) + } } + return emptyList() } } diff --git a/server/src/main/kotlin/suwayomi/tachidesk/server/JavalinSetup.kt b/server/src/main/kotlin/suwayomi/tachidesk/server/JavalinSetup.kt index abe53b60..1a6ca2e8 100644 --- a/server/src/main/kotlin/suwayomi/tachidesk/server/JavalinSetup.kt +++ b/server/src/main/kotlin/suwayomi/tachidesk/server/JavalinSetup.kt @@ -1,5 +1,12 @@ package suwayomi.tachidesk.server +/* + * Copyright (C) Contributors to the Suwayomi project + * + * 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/. */ + import io.javalin.Javalin import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers @@ -13,13 +20,6 @@ import java.io.IOException import java.util.concurrent.CompletableFuture import kotlin.concurrent.thread -/* - * Copyright (C) Contributors to the Suwayomi project - * - * 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/. */ - object JavalinSetup { private val logger = KotlinLogging.logger {} From 1ed9bcf7c8bf34e03b69630c855f890c0bc67dac Mon Sep 17 00:00:00 2001 From: Aria Moradi Date: Sat, 31 Jul 2021 00:41:09 +0430 Subject: [PATCH 3/7] commit what I've got --- .../xyz/nulldev/androidcompat/androidimpl/CustomContext.java | 3 +-- .../src/main/kotlin/suwayomi/tachidesk/manga/impl/Source.kt | 5 +++++ 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/AndroidCompat/src/main/java/xyz/nulldev/androidcompat/androidimpl/CustomContext.java b/AndroidCompat/src/main/java/xyz/nulldev/androidcompat/androidimpl/CustomContext.java index 617a6f5a..3e51b134 100644 --- a/AndroidCompat/src/main/java/xyz/nulldev/androidcompat/androidimpl/CustomContext.java +++ b/AndroidCompat/src/main/java/xyz/nulldev/androidcompat/androidimpl/CustomContext.java @@ -50,10 +50,9 @@ import java.util.Map; /** * Custom context implementation. * - * TODO Deal with packagemanager for extension sources */ public class CustomContext extends Context implements DIAware { - private DI kodein; + private final DI kodein; public CustomContext() { this(KodeinGlobalHelper.kodein()); } diff --git a/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/Source.kt b/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/Source.kt index 58f8db3c..b4bc4f36 100644 --- a/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/Source.kt +++ b/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/Source.kt @@ -63,14 +63,19 @@ object Source { val props: Any ) + var lastPreferenceScreen: PreferenceScreen? = null + fun getSourcePreferences(sourceId: Long): List { val source = getHttpSource(sourceId) if (source is ConfigurableSource) { val screen = PreferenceScreen(context) + lastPreferenceScreen = screen source.setupPreferenceScreen(screen) + screen.preferences.first().callChangeListener("yo") + return screen.preferences.map { PreferenceObject(it::class.java.name, it) } From f18ca5811f7c072bbae07e58fb49d84554b3084b Mon Sep 17 00:00:00 2001 From: Aria Moradi Date: Sat, 31 Jul 2021 02:10:48 +0430 Subject: [PATCH 4/7] bring back the old SharedPreferences for CustomContext, implement Toast --- AndroidCompat/getAndroid.sh | 2 +- .../src/main/java/android/widget/Toast.java | 91 +++++++++++++++++++ .../androidimpl/CustomContext.java | 23 ++--- .../nulldev/androidcompat/io/AndroidFiles.kt | 2 + .../io/sharedprefs/JavaSharedPreferences.kt | 7 ++ .../io/sharedprefs/JsonSharedPreferences.java | 2 +- 6 files changed, 114 insertions(+), 13 deletions(-) create mode 100644 AndroidCompat/src/main/java/android/widget/Toast.java diff --git a/AndroidCompat/getAndroid.sh b/AndroidCompat/getAndroid.sh index f1136ef9..97de7772 100755 --- a/AndroidCompat/getAndroid.sh +++ b/AndroidCompat/getAndroid.sh @@ -13,7 +13,7 @@ do which $dep >/dev/null 2>&1 || { echo >&2 "Error: This script needs $dep installed."; abort=yes; } done -if [ $abort = yes ]; then +if [ "$abort" = yes ]; then echo "Some of the dependencies didn't exist. Aborting." exit 1 fi diff --git a/AndroidCompat/src/main/java/android/widget/Toast.java b/AndroidCompat/src/main/java/android/widget/Toast.java new file mode 100644 index 00000000..603efb9b --- /dev/null +++ b/AndroidCompat/src/main/java/android/widget/Toast.java @@ -0,0 +1,91 @@ +package android.widget; + +/* + * Copyright (C) Contributors to the Suwayomi project + * + * 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/. */ + +public class Toast { + public static final int LENGTH_LONG = 1; + public static final int LENGTH_SHORT = 0; + + private CharSequence text; + + private Toast(CharSequence text) { + this.text = text; + } + + public Toast(android.content.Context context) { + throw new RuntimeException("Stub!"); + } + + public void show() { + System.out.printf("made a Toast: \"%s\"\n", text.toString()); + } + + public void cancel() { + throw new RuntimeException("Stub!"); + } + + public void setView(android.view.View view) { + throw new RuntimeException("Stub!"); + } + + public android.view.View getView() { + throw new RuntimeException("Stub!"); + } + + public void setDuration(int duration) { + throw new RuntimeException("Stub!"); + } + + public int getDuration() { + throw new RuntimeException("Stub!"); + } + + public void setMargin(float horizontalMargin, float verticalMargin) { + throw new RuntimeException("Stub!"); + } + + public float getHorizontalMargin() { + throw new RuntimeException("Stub!"); + } + + public float getVerticalMargin() { + throw new RuntimeException("Stub!"); + } + + public void setGravity(int gravity, int xOffset, int yOffset) { + throw new RuntimeException("Stub!"); + } + + public int getGravity() { + throw new RuntimeException("Stub!"); + } + + public int getXOffset() { + throw new RuntimeException("Stub!"); + } + + public int getYOffset() { + throw new RuntimeException("Stub!"); + } + + public static Toast makeText(android.content.Context context, java.lang.CharSequence text, int duration) { + return new Toast(text); + } + + public static android.widget.Toast makeText(android.content.Context context, int resId, int duration) throws android.content.res.Resources.NotFoundException { + throw new RuntimeException("Stub!"); + } + + public void setText(int resId) { + throw new RuntimeException("Stub!"); + } + + public void setText(java.lang.CharSequence s) { + throw new RuntimeException("Stub!"); + } +} \ No newline at end of file diff --git a/AndroidCompat/src/main/java/xyz/nulldev/androidcompat/androidimpl/CustomContext.java b/AndroidCompat/src/main/java/xyz/nulldev/androidcompat/androidimpl/CustomContext.java index 3e51b134..4050320f 100644 --- a/AndroidCompat/src/main/java/xyz/nulldev/androidcompat/androidimpl/CustomContext.java +++ b/AndroidCompat/src/main/java/xyz/nulldev/androidcompat/androidimpl/CustomContext.java @@ -38,7 +38,7 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import xyz.nulldev.androidcompat.info.ApplicationInfoImpl; import xyz.nulldev.androidcompat.io.AndroidFiles; -import xyz.nulldev.androidcompat.io.sharedprefs.JavaSharedPreferences; +import xyz.nulldev.androidcompat.io.sharedprefs.JsonSharedPreferences; import xyz.nulldev.androidcompat.service.ServiceSupport; import xyz.nulldev.androidcompat.util.KodeinGlobalHelper; @@ -50,9 +50,10 @@ import java.util.Map; /** * Custom context implementation. * + * TODO Deal with packagemanager for extension sources */ public class CustomContext extends Context implements DIAware { - private final DI kodein; + private DI kodein; public CustomContext() { this(KodeinGlobalHelper.kodein()); } @@ -164,22 +165,23 @@ public class CustomContext extends Context implements DIAware { /** Fake shared prefs! **/ private Map prefs = new HashMap<>(); //Cache + private File sharedPrefsFileFromString(String s) { + return new File(androidFiles.getPrefsDir(), s + ".json"); + } + @Override public synchronized SharedPreferences getSharedPreferences(String s, int i) { SharedPreferences preferences = prefs.get(s); //Create new shared preferences if one does not exist if(preferences == null) { - preferences = new JavaSharedPreferences(s); + preferences = getSharedPreferences(sharedPrefsFileFromString(s), i); prefs.put(s, preferences); } return preferences; } - @Override - public SharedPreferences getSharedPreferences(@NotNull File file, int mode) { - String path = file.getAbsolutePath().replace('\\', '/'); - int firstSlash = path.indexOf("/"); - return new JavaSharedPreferences(path.substring(firstSlash)); + public SharedPreferences getSharedPreferences(File file, int mode) { + return new JsonSharedPreferences(file); } @Override @@ -189,8 +191,8 @@ public class CustomContext extends Context implements DIAware { @Override public boolean deleteSharedPreferences(String name) { - JavaSharedPreferences item = (JavaSharedPreferences) prefs.remove(name); - return item.deleteAll(); + prefs.remove(name); + return sharedPrefsFileFromString(name).delete(); } @Override @@ -733,4 +735,3 @@ public class CustomContext extends Context implements DIAware { } } - diff --git a/AndroidCompat/src/main/java/xyz/nulldev/androidcompat/io/AndroidFiles.kt b/AndroidCompat/src/main/java/xyz/nulldev/androidcompat/io/AndroidFiles.kt index dafa70ea..ea55b497 100644 --- a/AndroidCompat/src/main/java/xyz/nulldev/androidcompat/io/AndroidFiles.kt +++ b/AndroidCompat/src/main/java/xyz/nulldev/androidcompat/io/AndroidFiles.kt @@ -26,6 +26,8 @@ class AndroidFiles(val configManager: ConfigManager = GlobalConfigManager) { val downloadCacheDir: File get() = registerFile(filesConfig.downloadCacheDir) val databasesDir: File get() = registerFile(filesConfig.databasesDir) + val prefsDir: File get() = registerFile(filesConfig.prefsDir) + val packagesDir: File get() = registerFile(filesConfig.packageDir) fun registerFile(file: String): File { diff --git a/AndroidCompat/src/main/java/xyz/nulldev/androidcompat/io/sharedprefs/JavaSharedPreferences.kt b/AndroidCompat/src/main/java/xyz/nulldev/androidcompat/io/sharedprefs/JavaSharedPreferences.kt index 1c7f3ea8..a0c132f0 100644 --- a/AndroidCompat/src/main/java/xyz/nulldev/androidcompat/io/sharedprefs/JavaSharedPreferences.kt +++ b/AndroidCompat/src/main/java/xyz/nulldev/androidcompat/io/sharedprefs/JavaSharedPreferences.kt @@ -1,5 +1,12 @@ package xyz.nulldev.androidcompat.io.sharedprefs +/* + * Copyright (C) Contributors to the Suwayomi project + * + * 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/. */ + import android.content.SharedPreferences import com.russhwolf.settings.ExperimentalSettingsApi import com.russhwolf.settings.ExperimentalSettingsImplementation diff --git a/AndroidCompat/src/main/java/xyz/nulldev/androidcompat/io/sharedprefs/JsonSharedPreferences.java b/AndroidCompat/src/main/java/xyz/nulldev/androidcompat/io/sharedprefs/JsonSharedPreferences.java index 9b04c7b0..90703bb0 100644 --- a/AndroidCompat/src/main/java/xyz/nulldev/androidcompat/io/sharedprefs/JsonSharedPreferences.java +++ b/AndroidCompat/src/main/java/xyz/nulldev/androidcompat/io/sharedprefs/JsonSharedPreferences.java @@ -233,7 +233,7 @@ public class JsonSharedPreferences implements SharedPreferences { private JsonSharedPreferencesEditor() { } - private void recordChange(String key) { + private void recordChange(String key) { if (!affectedKeys.contains(key)) { affectedKeys.add(key); } From cd59aed8c791c52f3394651f73816c1ad9ec14f0 Mon Sep 17 00:00:00 2001 From: Syer10 Date: Fri, 30 Jul 2021 17:55:35 -0400 Subject: [PATCH 5/7] Fix Invalid Type exception --- .../io/sharedprefs/JavaSharedPreferences.kt | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/AndroidCompat/src/main/java/xyz/nulldev/androidcompat/io/sharedprefs/JavaSharedPreferences.kt b/AndroidCompat/src/main/java/xyz/nulldev/androidcompat/io/sharedprefs/JavaSharedPreferences.kt index 1c7f3ea8..bbcde062 100644 --- a/AndroidCompat/src/main/java/xyz/nulldev/androidcompat/io/sharedprefs/JavaSharedPreferences.kt +++ b/AndroidCompat/src/main/java/xyz/nulldev/androidcompat/io/sharedprefs/JavaSharedPreferences.kt @@ -6,7 +6,6 @@ import com.russhwolf.settings.ExperimentalSettingsImplementation import com.russhwolf.settings.JvmPreferencesSettings import com.russhwolf.settings.serialization.decodeValue import com.russhwolf.settings.serialization.encodeValue -import com.russhwolf.settings.set import kotlinx.serialization.ExperimentalSerializationApi import kotlinx.serialization.SerializationException import kotlinx.serialization.builtins.SetSerializer @@ -138,9 +137,12 @@ class JavaSharedPreferences(key: String) : SharedPreferences { @Suppress("UNCHECKED_CAST") when (value) { is Set<*> -> preferences.encodeValue(SetSerializer(String.serializer()), key, value as Set) - else -> { - preferences[key] = value - } + is String -> preferences.putString(key, value) + is Int -> preferences.putInt(key, value) + is Long -> preferences.putLong(key, value) + is Float -> preferences.putFloat(key, value) + is Double -> preferences.putDouble(key, value) + is Boolean -> preferences.putBoolean(key, value) } } } From bfaf88afd69fd40a46da3e8a7cd42de2e256f19b Mon Sep 17 00:00:00 2001 From: Aria Moradi Date: Sat, 31 Jul 2021 02:49:15 +0430 Subject: [PATCH 6/7] put back syer's implementation --- .../androidimpl/CustomContext.java | 22 +++++++++---------- 1 file changed, 10 insertions(+), 12 deletions(-) diff --git a/AndroidCompat/src/main/java/xyz/nulldev/androidcompat/androidimpl/CustomContext.java b/AndroidCompat/src/main/java/xyz/nulldev/androidcompat/androidimpl/CustomContext.java index 4050320f..8c136d21 100644 --- a/AndroidCompat/src/main/java/xyz/nulldev/androidcompat/androidimpl/CustomContext.java +++ b/AndroidCompat/src/main/java/xyz/nulldev/androidcompat/androidimpl/CustomContext.java @@ -38,7 +38,7 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import xyz.nulldev.androidcompat.info.ApplicationInfoImpl; import xyz.nulldev.androidcompat.io.AndroidFiles; -import xyz.nulldev.androidcompat.io.sharedprefs.JsonSharedPreferences; +import xyz.nulldev.androidcompat.io.sharedprefs.JavaSharedPreferences; import xyz.nulldev.androidcompat.service.ServiceSupport; import xyz.nulldev.androidcompat.util.KodeinGlobalHelper; @@ -50,10 +50,9 @@ import java.util.Map; /** * Custom context implementation. * - * TODO Deal with packagemanager for extension sources */ public class CustomContext extends Context implements DIAware { - private DI kodein; + private final DI kodein; public CustomContext() { this(KodeinGlobalHelper.kodein()); } @@ -165,23 +164,22 @@ public class CustomContext extends Context implements DIAware { /** Fake shared prefs! **/ private Map prefs = new HashMap<>(); //Cache - private File sharedPrefsFileFromString(String s) { - return new File(androidFiles.getPrefsDir(), s + ".json"); - } - @Override public synchronized SharedPreferences getSharedPreferences(String s, int i) { SharedPreferences preferences = prefs.get(s); //Create new shared preferences if one does not exist if(preferences == null) { - preferences = getSharedPreferences(sharedPrefsFileFromString(s), i); + preferences = new JavaSharedPreferences(s); prefs.put(s, preferences); } return preferences; } - public SharedPreferences getSharedPreferences(File file, int mode) { - return new JsonSharedPreferences(file); + @Override + public SharedPreferences getSharedPreferences(@NotNull File file, int mode) { + String path = file.getAbsolutePath().replace('\\', '/'); + int firstSlash = path.indexOf("/"); + return new JavaSharedPreferences(path.substring(firstSlash)); } @Override @@ -191,8 +189,8 @@ public class CustomContext extends Context implements DIAware { @Override public boolean deleteSharedPreferences(String name) { - prefs.remove(name); - return sharedPrefsFileFromString(name).delete(); + JavaSharedPreferences item = (JavaSharedPreferences) prefs.remove(name); + return item.deleteAll(); } @Override From e349d0cef3682a462273eee16c1e84181f3074ee Mon Sep 17 00:00:00 2001 From: Aria Moradi Date: Sat, 31 Jul 2021 03:50:41 +0430 Subject: [PATCH 7/7] add pereference change --- .../manga/{TachideskAPI.kt => MangaAPI.kt} | 13 +++- .../suwayomi/tachidesk/manga/impl/Source.kt | 62 ++++++++++++++----- .../manga/impl/util/GetHttpSource.kt | 4 ++ .../suwayomi/tachidesk/server/JavalinSetup.kt | 4 +- 4 files changed, 62 insertions(+), 21 deletions(-) rename server/src/main/kotlin/suwayomi/tachidesk/manga/{TachideskAPI.kt => MangaAPI.kt} (96%) diff --git a/server/src/main/kotlin/suwayomi/tachidesk/manga/TachideskAPI.kt b/server/src/main/kotlin/suwayomi/tachidesk/manga/MangaAPI.kt similarity index 96% rename from server/src/main/kotlin/suwayomi/tachidesk/manga/TachideskAPI.kt rename to server/src/main/kotlin/suwayomi/tachidesk/manga/MangaAPI.kt index e70ce519..77ddce49 100644 --- a/server/src/main/kotlin/suwayomi/tachidesk/manga/TachideskAPI.kt +++ b/server/src/main/kotlin/suwayomi/tachidesk/manga/MangaAPI.kt @@ -28,9 +28,11 @@ import suwayomi.tachidesk.manga.impl.Page.getPageImage import suwayomi.tachidesk.manga.impl.Search.sourceFilters import suwayomi.tachidesk.manga.impl.Search.sourceGlobalSearch import suwayomi.tachidesk.manga.impl.Search.sourceSearch +import suwayomi.tachidesk.manga.impl.Source.SourcePreferenceChange import suwayomi.tachidesk.manga.impl.Source.getSource import suwayomi.tachidesk.manga.impl.Source.getSourceList import suwayomi.tachidesk.manga.impl.Source.getSourcePreferences +import suwayomi.tachidesk.manga.impl.Source.setSourcePreference import suwayomi.tachidesk.manga.impl.backup.BackupFlags import suwayomi.tachidesk.manga.impl.backup.legacy.LegacyBackupExport.createLegacyBackup import suwayomi.tachidesk.manga.impl.backup.legacy.LegacyBackupImport.restoreLegacyBackup @@ -45,7 +47,7 @@ import suwayomi.tachidesk.server.impl.About import java.text.SimpleDateFormat import java.util.Date -object TachideskAPI { +object MangaAPI { fun defineEndpoints(app: Javalin) { // list all extensions app.get("/api/v1/extension/list") { ctx -> @@ -111,11 +113,18 @@ object TachideskAPI { } // fetch preferences of source with id `sourceId` - app.get("/api/v1/source/:sourceId/preference-screen") { ctx -> + app.get("/api/v1/source/:sourceId/preference") { ctx -> val sourceId = ctx.pathParam("sourceId").toLong() ctx.json(getSourcePreferences(sourceId)) } + // fetch preferences of source with id `sourceId` + app.post("/api/v1/source/:sourceId/preference") { ctx -> + val sourceId = ctx.pathParam("sourceId").toLong() + val preferenceChange = ctx.bodyAsClass(SourcePreferenceChange::class.java) + ctx.json(setSourcePreference(sourceId, preferenceChange)) + } + // popular mangas from source with id `sourceId` app.get("/api/v1/source/:sourceId/popular/:pageNum") { ctx -> val sourceId = ctx.pathParam("sourceId").toLong() diff --git a/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/Source.kt b/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/Source.kt index b4bc4f36..886d378b 100644 --- a/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/Source.kt +++ b/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/Source.kt @@ -18,6 +18,7 @@ import org.kodein.di.conf.global import org.kodein.di.instance import suwayomi.tachidesk.manga.impl.extension.Extension.getExtensionIconUrl import suwayomi.tachidesk.manga.impl.util.GetHttpSource.getHttpSource +import suwayomi.tachidesk.manga.impl.util.GetHttpSource.invalidateSourceCache import suwayomi.tachidesk.manga.model.dataclass.SourceDataClass import suwayomi.tachidesk.manga.model.table.ExtensionTable import suwayomi.tachidesk.manga.model.table.SourceTable @@ -30,12 +31,12 @@ object Source { return transaction { SourceTable.selectAll().map { SourceDataClass( - it[SourceTable.id].value.toString(), - it[SourceTable.name], - it[SourceTable.lang], - getExtensionIconUrl(ExtensionTable.select { ExtensionTable.id eq it[SourceTable.extension] }.first()[ExtensionTable.apkName]), - getHttpSource(it[SourceTable.id].value).supportsLatest, - getHttpSource(it[SourceTable.id].value) is ConfigurableSource + it[SourceTable.id].value.toString(), + it[SourceTable.name], + it[SourceTable.lang], + getExtensionIconUrl(ExtensionTable.select { ExtensionTable.id eq it[SourceTable.extension] }.first()[ExtensionTable.apkName]), + getHttpSource(it[SourceTable.id].value).supportsLatest, + getHttpSource(it[SourceTable.id].value) is ConfigurableSource ) } } @@ -46,12 +47,12 @@ object Source { val source = SourceTable.select { SourceTable.id eq sourceId }.firstOrNull() SourceDataClass( - sourceId.toString(), - source?.get(SourceTable.name), - source?.get(SourceTable.lang), - source?.let { ExtensionTable.select { ExtensionTable.id eq source[SourceTable.extension] }.first()[ExtensionTable.iconUrl] }, - source?.let { getHttpSource(sourceId).supportsLatest }, - source?.let { getHttpSource(sourceId) is ConfigurableSource }, + sourceId.toString(), + source?.get(SourceTable.name), + source?.get(SourceTable.lang), + source?.let { ExtensionTable.select { ExtensionTable.id eq source[SourceTable.extension] }.first()[ExtensionTable.iconUrl] }, + source?.let { getHttpSource(sourceId).supportsLatest }, + source?.let { getHttpSource(sourceId) is ConfigurableSource }, ) } } @@ -59,22 +60,24 @@ object Source { private val context by DI.global.instance() data class PreferenceObject( - val type: String, - val props: Any + val type: String, + val props: Any ) - var lastPreferenceScreen: PreferenceScreen? = null + var preferenceScreenMap: MutableMap = mutableMapOf() + /** + * Gets a source's PreferenceScreen, puts the result into [preferenceScreenMap] + */ fun getSourcePreferences(sourceId: Long): List { val source = getHttpSource(sourceId) if (source is ConfigurableSource) { val screen = PreferenceScreen(context) - lastPreferenceScreen = screen source.setupPreferenceScreen(screen) - screen.preferences.first().callChangeListener("yo") + preferenceScreenMap[sourceId] = screen return screen.preferences.map { PreferenceObject(it::class.java.name, it) @@ -82,4 +85,29 @@ object Source { } return emptyList() } + + data class SourcePreferenceChange( + val position: Int, + val type: String, + val value: String + ) + + fun setSourcePreference(sourceId: Long, change: SourcePreferenceChange ) { + val screen = preferenceScreenMap[sourceId]!! + + val newValue = when(change.type) { + "String" -> change.value + "Int" -> change.value.toInt() + "Long" -> change.value.toLong() + "Float" -> change.value.toLong() + "Double" -> change.value.toDouble() + "Boolean" -> change.value.toBoolean() + else -> throw RuntimeException("Unsupported type conversion") + } + + screen.preferences[change.position].callChangeListener(newValue) + + // must reload the source cache because a preference was changed + invalidateSourceCache(sourceId) + } } diff --git a/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/util/GetHttpSource.kt b/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/util/GetHttpSource.kt index 09ec499c..e2a98d9c 100644 --- a/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/util/GetHttpSource.kt +++ b/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/util/GetHttpSource.kt @@ -54,4 +54,8 @@ object GetHttpSource { } return sourceCache[sourceId]!! } + + fun invalidateSourceCache(sourceId: Long) { + sourceCache.remove(sourceId) + } } diff --git a/server/src/main/kotlin/suwayomi/tachidesk/server/JavalinSetup.kt b/server/src/main/kotlin/suwayomi/tachidesk/server/JavalinSetup.kt index 1a6ca2e8..5aa19b73 100644 --- a/server/src/main/kotlin/suwayomi/tachidesk/server/JavalinSetup.kt +++ b/server/src/main/kotlin/suwayomi/tachidesk/server/JavalinSetup.kt @@ -14,7 +14,7 @@ import kotlinx.coroutines.SupervisorJob import kotlinx.coroutines.future.future import mu.KotlinLogging import suwayomi.tachidesk.anime.AnimeAPI -import suwayomi.tachidesk.manga.TachideskAPI +import suwayomi.tachidesk.manga.MangaAPI import suwayomi.tachidesk.server.util.Browser import java.io.IOException import java.util.concurrent.CompletableFuture @@ -75,7 +75,7 @@ object JavalinSetup { ctx.result(e.message ?: "Internal Server Error") } - TachideskAPI.defineEndpoints(app) + MangaAPI.defineEndpoints(app) AnimeAPI.defineEndpoints(app) } }