From 57a584a820b7b3c4547ebdefb3111e7476f796b2 Mon Sep 17 00:00:00 2001 From: Finnley Somdahl <87634197+rebelonion@users.noreply.github.com> Date: Wed, 18 Oct 2023 23:52:03 -0500 Subject: [PATCH] lots of background work for manga extensions --- app/src/main/AndroidManifest.xml | 3 +- app/src/main/java/ani/dantotsu/App.kt | 4 +- .../main/java/ani/dantotsu/MainActivity.kt | 18 +- .../main/java/ani/dantotsu/aniyomi/LICENSE | 176 --------- .../main/java/ani/dantotsu/aniyomi/NOTICE.md | 3 - .../ani/dantotsu/aniyomi/anime/custom/App.kt | 30 -- .../aniyomi/anime/custom/InjektModules.kt | 13 +- .../aniyomi/util/srcapi/RxExtension.kt | 3 - .../dantotsu/media/MediaDetailsViewModel.kt | 2 +- .../ani/dantotsu/media/manga/MangaChapter.kt | 4 +- .../main/java/ani/dantotsu/others/Jikan.kt | 2 +- .../java/ani/dantotsu/parsers/AnimeSources.kt | 32 +- .../ani/dantotsu/parsers/AniyomiAdapter.kt | 184 ++++++++- .../java/ani/dantotsu/parsers/BaseParser.kt | 9 +- .../java/ani/dantotsu/parsers/BaseSources.kt | 13 +- .../java/ani/dantotsu/parsers/MangaParser.kt | 21 +- .../java/ani/dantotsu/parsers/MangaSources.kt | 24 +- .../dantotsu/settings/ExtensionsActivity.kt | 20 +- .../subcriptions/SubscriptionHelper.kt | 5 +- .../core/preference/PreferenceMutableState.kt | 38 ++ .../kanade}/domain/base/BasePreferences.kt | 4 +- .../base/ExtensionInstallerPreference.kt | 14 +- .../source/service/SetMigrateSorting.kt | 2 +- .../source/service/SourcePreferences.kt | 10 +- .../domain/source/service/ToggleLanguage.kt | 15 + .../kanade/tachiyomi}/PreferenceScreen.kt | 2 +- .../animesource/AnimeCatalogueSource.kt | 2 +- .../tachiyomi}/animesource/AnimeSource.kt | 5 +- .../animesource/AnimeSourceFactory.kt | 2 +- .../animesource/ConfigurableAnimeSource.kt | 3 +- .../animesource/model/AnimeFilter.kt | 0 .../animesource/model/AnimeFilterList.kt | 0 .../animesource/model/AnimesPage.kt | 0 .../tachiyomi}/animesource/model/SAnime.kt | 2 +- .../animesource/model/SAnimeImpl.kt | 2 +- .../tachiyomi}/animesource/model/SEpisode.kt | 0 .../animesource/model/SEpisodeImpl.kt | 0 .../tachiyomi}/animesource/model/Video.kt | 2 +- .../animesource/online/AnimeHttpSource.kt | 2 +- .../kanade/tachiyomi}/core/Constants.kt | 2 +- .../core/preference/AndroidPreference.kt | 3 +- .../core/preference/AndroidPreferenceStore.kt | 18 +- .../notification}/NotificationReceiver.kt | 4 +- .../data/notification}/Notifications.kt | 6 +- .../extension/ExtensionUpdateNotifier.kt | 8 +- .../tachiyomi}/extension/InstallStep.kt | 2 +- .../extension}/anime/AnimeExtensionManager.kt | 86 ++--- .../anime/api/AnimeExtensionGithubApi.kt | 40 +- .../anime/installer/InstallerAnime.kt | 6 +- .../PackageInstallerInstallerAnime.kt | 12 +- .../extension}/anime/model/AnimeExtension.kt | 6 +- .../extension}/anime/model/AnimeLoadResult.kt | 2 +- .../util/AnimeExtensionInstallActivity.kt | 10 +- .../util/AnimeExtensionInstallReceiver.kt | 10 +- .../util/AnimeExtensionInstallService.kt | 18 +- .../anime/util/AnimeExtensionInstaller.kt | 14 +- .../anime/util/AnimeExtensionLoader.kt | 22 +- .../extension/manga/MangaExtensionManager.kt | 365 ++++++++++++++++++ .../manga/api/MangaExtensionGithubApi.kt | 186 +++++++++ .../manga/installer/InstallerManga.kt | 170 ++++++++ .../PackageInstallerInstallerManga.kt | 107 +++++ .../extension/manga/model/MangaExtension.kt | 79 ++++ .../extension/manga/model/MangaLoadResult.kt | 7 + .../util/MangaExtensionInstallActivity.kt | 78 ++++ .../util/MangaExtensionInstallReceiver.kt | 130 +++++++ .../util/MangaExtensionInstallService.kt | 82 ++++ .../manga/util/MangaExtensionInstaller.kt | 266 +++++++++++++ .../manga/util/MangaExtensionLoader.kt | 232 +++++++++++ .../tachiyomi}/network/AndroidCookieJar.kt | 2 +- .../kanade/tachiyomi}/network/DohProviders.kt | 2 +- .../tachiyomi}/network/NetworkHelper.kt | 14 +- .../tachiyomi}/network/OkHttpExtensions.kt | 4 +- .../tachiyomi}/network/ProgressListener.kt | 2 +- .../network/ProgressResponseBody.kt | 2 +- .../kanade/tachiyomi}/network/Requests.kt | 4 +- .../interceptor/CloudflareInterceptor.kt | 10 +- .../interceptor/RateLimitInterceptor.kt | 105 +++++ .../SpecificHostRateLimitInterceptor.kt | 27 ++ .../UncaughtExceptionInterceptor.kt | 0 .../interceptor/UserAgentInterceptor.kt | 0 .../network/interceptor/WebViewInterceptor.kt | 12 +- .../tachiyomi}/source/CatalogueSource.kt | 6 +- .../tachiyomi/source/ConfigurableSource.kt | 8 + .../kanade/tachiyomi}/source/MangaSource.kt | 10 +- .../kanade/tachiyomi/source/SourceFactory.kt | 12 + .../tachiyomi/source/UnmeteredSource.kt | 8 + .../kanade/tachiyomi}/source/model/Filter.kt | 2 +- .../tachiyomi}/source/model/FilterList.kt | 2 +- .../tachiyomi}/source/model/MangasPage.kt | 2 +- .../kanade/tachiyomi}/source/model/Page.kt | 10 +- .../tachiyomi}/source/model/SChapter.kt | 2 +- .../tachiyomi}/source/model/SChapterImpl.kt | 2 +- .../kanade/tachiyomi}/source/model/SManga.kt | 2 +- .../tachiyomi}/source/model/SMangaImpl.kt | 2 +- .../tachiyomi}/source/model/UpdateStrategy.kt | 2 +- .../tachiyomi/source/online/HttpSource.kt | 12 +- .../source/online/HttpSourceFetcher.kt | 25 ++ .../source/online/ParsedHttpSource.kt | 201 ++++++++++ .../kanade/tachiyomi}/util/JsoupExtensions.kt | 0 .../kanade/tachiyomi}/util/RxExtension.kt | 2 +- .../util/lang/CloseableExtensions.kt | 2 +- .../kanade/tachiyomi}/util/lang/Hash.kt | 2 +- .../tachiyomi}/util/lang/RxCoroutineBridge.kt | 2 +- .../tachiyomi}/util/lang/StringExtensions.kt | 2 +- .../util/preference/PreferenceExtensions.kt | 31 ++ .../tachiyomi}/util/storage/FileExtensions.kt | 2 +- .../util/system/ContextExtensions.kt | 6 +- .../tachiyomi}/util/system/DeviceUtil.kt | 4 +- .../util/system/IntentExtensions.kt | 2 +- .../tachiyomi}/util/system/LocaleHelper.kt | 2 +- .../util/system/NotificationExtensions.kt | 2 +- .../tachiyomi/util/system}/ToastExtensions.kt | 2 +- .../util/system/WebViewClientCompat.kt | 2 +- .../tachiyomi}/util/system/WebViewUtil.kt | 4 +- .../core/preference/Preference.kt | 2 +- .../core/preference/PreferenceStore.kt | 2 +- .../core/util/lang}/CoroutinesExtensions.kt | 2 +- .../core/util/system}/LogcatExtensions.kt | 2 +- .../domain/category/model/Category.kt | 2 +- .../domain/library/model/Flag.kt | 2 +- .../library/model/LibraryDisplayMode.kt | 4 +- .../source/anime/model/AnimeSourceData.kt | 2 +- .../source/manga/model/MangaSourceData.kt | 10 + 123 files changed, 2676 insertions(+), 553 deletions(-) delete mode 100644 app/src/main/java/ani/dantotsu/aniyomi/LICENSE delete mode 100644 app/src/main/java/ani/dantotsu/aniyomi/NOTICE.md delete mode 100644 app/src/main/java/ani/dantotsu/aniyomi/anime/custom/App.kt delete mode 100644 app/src/main/java/ani/dantotsu/aniyomi/util/srcapi/RxExtension.kt create mode 100644 app/src/main/java/eu/kanade/core/preference/PreferenceMutableState.kt rename app/src/main/java/{ani/dantotsu/aniyomi => eu/kanade}/domain/base/BasePreferences.kt (90%) rename app/src/main/java/{ani/dantotsu/aniyomi => eu/kanade}/domain/base/ExtensionInstallerPreference.kt (81%) rename app/src/main/java/{ani/dantotsu/aniyomi => eu/kanade}/domain/source/service/SetMigrateSorting.kt (88%) rename app/src/main/java/{ani/dantotsu/aniyomi => eu/kanade}/domain/source/service/SourcePreferences.kt (92%) create mode 100644 app/src/main/java/eu/kanade/domain/source/service/ToggleLanguage.kt rename app/src/main/java/{ani/dantotsu/aniyomi => eu/kanade/tachiyomi}/PreferenceScreen.kt (69%) rename app/src/main/java/{ani/dantotsu/aniyomi => eu/kanade/tachiyomi}/animesource/AnimeCatalogueSource.kt (96%) rename app/src/main/java/{ani/dantotsu/aniyomi => eu/kanade/tachiyomi}/animesource/AnimeSource.kt (94%) rename app/src/main/java/{ani/dantotsu/aniyomi => eu/kanade/tachiyomi}/animesource/AnimeSourceFactory.kt (84%) rename app/src/main/java/{ani/dantotsu/aniyomi => eu/kanade/tachiyomi}/animesource/model/AnimeFilter.kt (100%) rename app/src/main/java/{ani/dantotsu/aniyomi => eu/kanade/tachiyomi}/animesource/model/AnimeFilterList.kt (100%) rename app/src/main/java/{ani/dantotsu/aniyomi => eu/kanade/tachiyomi}/animesource/model/AnimesPage.kt (100%) rename app/src/main/java/{ani/dantotsu/aniyomi => eu/kanade/tachiyomi}/animesource/model/SAnime.kt (97%) rename app/src/main/java/{ani/dantotsu/aniyomi => eu/kanade/tachiyomi}/animesource/model/SAnimeImpl.kt (90%) rename app/src/main/java/{ani/dantotsu/aniyomi => eu/kanade/tachiyomi}/animesource/model/SEpisode.kt (100%) rename app/src/main/java/{ani/dantotsu/aniyomi => eu/kanade/tachiyomi}/animesource/model/SEpisodeImpl.kt (100%) rename app/src/main/java/{ani/dantotsu/aniyomi => eu/kanade/tachiyomi}/animesource/model/Video.kt (97%) rename app/src/main/java/{ani/dantotsu/aniyomi => eu/kanade/tachiyomi}/core/Constants.kt (96%) rename app/src/main/java/{ani/dantotsu/aniyomi => eu/kanade/tachiyomi}/core/preference/AndroidPreference.kt (98%) rename app/src/main/java/{ani/dantotsu/aniyomi => eu/kanade/tachiyomi}/core/preference/AndroidPreferenceStore.kt (77%) rename app/src/main/java/{ani/dantotsu/aniyomi/data => eu/kanade/tachiyomi/data/notification}/NotificationReceiver.kt (92%) rename app/src/main/java/{ani/dantotsu/aniyomi/data => eu/kanade/tachiyomi/data/notification}/Notifications.kt (97%) rename app/src/main/java/{ani/dantotsu/aniyomi/util => eu/kanade/tachiyomi}/extension/ExtensionUpdateNotifier.kt (79%) rename app/src/main/java/{ani/dantotsu/aniyomi/util => eu/kanade/tachiyomi}/extension/InstallStep.kt (81%) rename app/src/main/java/{ani/dantotsu/aniyomi => eu/kanade/tachiyomi/extension}/anime/AnimeExtensionManager.kt (82%) rename app/src/main/java/{ani/dantotsu/aniyomi => eu/kanade/tachiyomi/extension}/anime/api/AnimeExtensionGithubApi.kt (84%) rename app/src/main/java/{ani/dantotsu/aniyomi => eu/kanade/tachiyomi/extension}/anime/installer/InstallerAnime.kt (97%) rename app/src/main/java/{ani/dantotsu/aniyomi => eu/kanade/tachiyomi/extension}/anime/installer/PackageInstallerInstallerAnime.kt (93%) rename app/src/main/java/{ani/dantotsu/aniyomi => eu/kanade/tachiyomi/extension}/anime/model/AnimeExtension.kt (93%) rename app/src/main/java/{ani/dantotsu/aniyomi => eu/kanade/tachiyomi/extension}/anime/model/AnimeLoadResult.kt (82%) rename app/src/main/java/{ani/dantotsu/aniyomi => eu/kanade/tachiyomi/extension}/anime/util/AnimeExtensionInstallActivity.kt (90%) rename app/src/main/java/{ani/dantotsu/aniyomi => eu/kanade/tachiyomi/extension}/anime/util/AnimeExtensionInstallReceiver.kt (94%) rename app/src/main/java/{ani/dantotsu/aniyomi => eu/kanade/tachiyomi/extension}/anime/util/AnimeExtensionInstallService.kt (80%) rename app/src/main/java/{ani/dantotsu/aniyomi => eu/kanade/tachiyomi/extension}/anime/util/AnimeExtensionInstaller.kt (96%) rename app/src/main/java/{ani/dantotsu/aniyomi => eu/kanade/tachiyomi/extension}/anime/util/AnimeExtensionLoader.kt (93%) create mode 100644 app/src/main/java/eu/kanade/tachiyomi/extension/manga/MangaExtensionManager.kt create mode 100644 app/src/main/java/eu/kanade/tachiyomi/extension/manga/api/MangaExtensionGithubApi.kt create mode 100644 app/src/main/java/eu/kanade/tachiyomi/extension/manga/installer/InstallerManga.kt create mode 100644 app/src/main/java/eu/kanade/tachiyomi/extension/manga/installer/PackageInstallerInstallerManga.kt create mode 100644 app/src/main/java/eu/kanade/tachiyomi/extension/manga/model/MangaExtension.kt create mode 100644 app/src/main/java/eu/kanade/tachiyomi/extension/manga/model/MangaLoadResult.kt create mode 100644 app/src/main/java/eu/kanade/tachiyomi/extension/manga/util/MangaExtensionInstallActivity.kt create mode 100644 app/src/main/java/eu/kanade/tachiyomi/extension/manga/util/MangaExtensionInstallReceiver.kt create mode 100644 app/src/main/java/eu/kanade/tachiyomi/extension/manga/util/MangaExtensionInstallService.kt create mode 100644 app/src/main/java/eu/kanade/tachiyomi/extension/manga/util/MangaExtensionInstaller.kt create mode 100644 app/src/main/java/eu/kanade/tachiyomi/extension/manga/util/MangaExtensionLoader.kt rename app/src/main/java/{ani/dantotsu/aniyomi/util => eu/kanade/tachiyomi}/network/AndroidCookieJar.kt (97%) rename app/src/main/java/{ani/dantotsu/aniyomi/util => eu/kanade/tachiyomi}/network/DohProviders.kt (99%) rename app/src/main/java/{ani/dantotsu/aniyomi/util => eu/kanade/tachiyomi}/network/NetworkHelper.kt (83%) rename app/src/main/java/{ani/dantotsu/aniyomi/util => eu/kanade/tachiyomi}/network/OkHttpExtensions.kt (97%) rename app/src/main/java/{ani/dantotsu/aniyomi/util => eu/kanade/tachiyomi}/network/ProgressListener.kt (70%) rename app/src/main/java/{ani/dantotsu/aniyomi/util => eu/kanade/tachiyomi}/network/ProgressResponseBody.kt (96%) rename app/src/main/java/{ani/dantotsu/aniyomi/util => eu/kanade/tachiyomi}/network/Requests.kt (95%) rename app/src/main/java/{ani/dantotsu/aniyomi/util => eu/kanade/tachiyomi}/network/interceptor/CloudflareInterceptor.kt (94%) create mode 100644 app/src/main/java/eu/kanade/tachiyomi/network/interceptor/RateLimitInterceptor.kt create mode 100644 app/src/main/java/eu/kanade/tachiyomi/network/interceptor/SpecificHostRateLimitInterceptor.kt rename app/src/main/java/{ani/dantotsu/aniyomi/util => eu/kanade/tachiyomi}/network/interceptor/UncaughtExceptionInterceptor.kt (100%) rename app/src/main/java/{ani/dantotsu/aniyomi/util => eu/kanade/tachiyomi}/network/interceptor/UserAgentInterceptor.kt (100%) rename app/src/main/java/{ani/dantotsu/aniyomi/util => eu/kanade/tachiyomi}/network/interceptor/WebViewInterceptor.kt (92%) rename app/src/main/java/{ani/dantotsu/aniyomi => eu/kanade/tachiyomi}/source/CatalogueSource.kt (89%) create mode 100644 app/src/main/java/eu/kanade/tachiyomi/source/ConfigurableSource.kt rename app/src/main/java/{ani/dantotsu/aniyomi => eu/kanade/tachiyomi}/source/MangaSource.kt (90%) create mode 100644 app/src/main/java/eu/kanade/tachiyomi/source/SourceFactory.kt create mode 100644 app/src/main/java/eu/kanade/tachiyomi/source/UnmeteredSource.kt rename app/src/main/java/{ani/dantotsu/aniyomi => eu/kanade/tachiyomi}/source/model/Filter.kt (97%) rename app/src/main/java/{ani/dantotsu/aniyomi => eu/kanade/tachiyomi}/source/model/FilterList.kt (88%) rename app/src/main/java/{ani/dantotsu/aniyomi => eu/kanade/tachiyomi}/source/model/MangasPage.kt (64%) rename app/src/main/java/{ani/dantotsu/aniyomi => eu/kanade/tachiyomi}/source/model/Page.kt (84%) rename app/src/main/java/{ani/dantotsu/aniyomi => eu/kanade/tachiyomi}/source/model/SChapter.kt (92%) rename app/src/main/java/{ani/dantotsu/aniyomi => eu/kanade/tachiyomi}/source/model/SChapterImpl.kt (85%) rename app/src/main/java/{ani/dantotsu/aniyomi => eu/kanade/tachiyomi}/source/model/SManga.kt (97%) rename app/src/main/java/{ani/dantotsu/aniyomi => eu/kanade/tachiyomi}/source/model/SMangaImpl.kt (92%) rename app/src/main/java/{ani/dantotsu/aniyomi => eu/kanade/tachiyomi}/source/model/UpdateStrategy.kt (93%) create mode 100644 app/src/main/java/eu/kanade/tachiyomi/source/online/HttpSourceFetcher.kt create mode 100644 app/src/main/java/eu/kanade/tachiyomi/source/online/ParsedHttpSource.kt rename app/src/main/java/{ani/dantotsu/aniyomi => eu/kanade/tachiyomi}/util/JsoupExtensions.kt (100%) rename app/src/main/java/{ani/dantotsu/aniyomi => eu/kanade/tachiyomi}/util/RxExtension.kt (62%) rename app/src/main/java/{ani/dantotsu/aniyomi => eu/kanade/tachiyomi}/util/lang/CloseableExtensions.kt (95%) rename app/src/main/java/{ani/dantotsu/aniyomi => eu/kanade/tachiyomi}/util/lang/Hash.kt (96%) rename app/src/main/java/{ani/dantotsu/aniyomi => eu/kanade/tachiyomi}/util/lang/RxCoroutineBridge.kt (98%) rename app/src/main/java/{ani/dantotsu/aniyomi => eu/kanade/tachiyomi}/util/lang/StringExtensions.kt (98%) create mode 100644 app/src/main/java/eu/kanade/tachiyomi/util/preference/PreferenceExtensions.kt rename app/src/main/java/{ani/dantotsu/aniyomi => eu/kanade/tachiyomi}/util/storage/FileExtensions.kt (93%) rename app/src/main/java/{ani/dantotsu/aniyomi => eu/kanade/tachiyomi}/util/system/ContextExtensions.kt (97%) rename app/src/main/java/{ani/dantotsu/aniyomi => eu/kanade/tachiyomi}/util/system/DeviceUtil.kt (96%) rename app/src/main/java/{ani/dantotsu/aniyomi => eu/kanade/tachiyomi}/util/system/IntentExtensions.kt (97%) rename app/src/main/java/{ani/dantotsu/aniyomi => eu/kanade/tachiyomi}/util/system/LocaleHelper.kt (97%) rename app/src/main/java/{ani/dantotsu/aniyomi => eu/kanade/tachiyomi}/util/system/NotificationExtensions.kt (98%) rename app/src/main/java/{ani/dantotsu/aniyomi/util => eu/kanade/tachiyomi/util/system}/ToastExtensions.kt (95%) rename app/src/main/java/{ani/dantotsu/aniyomi => eu/kanade/tachiyomi}/util/system/WebViewClientCompat.kt (98%) rename app/src/main/java/{ani/dantotsu/aniyomi => eu/kanade/tachiyomi}/util/system/WebViewUtil.kt (96%) rename app/src/main/java/{ani/dantotsu/aniyomi => tachiyomi}/core/preference/Preference.kt (91%) rename app/src/main/java/{ani/dantotsu/aniyomi => tachiyomi}/core/preference/PreferenceStore.kt (96%) rename app/src/main/java/{ani/dantotsu/aniyomi/util => tachiyomi/core/util/lang}/CoroutinesExtensions.kt (98%) rename app/src/main/java/{ani/dantotsu/aniyomi/util => tachiyomi/core/util/system}/LogcatExtensions.kt (91%) rename app/src/main/java/{ani/dantotsu/aniyomi => tachiyomi}/domain/category/model/Category.kt (85%) rename app/src/main/java/{ani/dantotsu/aniyomi => tachiyomi}/domain/library/model/Flag.kt (93%) rename app/src/main/java/{ani/dantotsu/aniyomi => tachiyomi}/domain/library/model/LibraryDisplayMode.kt (93%) rename app/src/main/java/{ani/dantotsu/aniyomi => tachiyomi}/domain/source/anime/model/AnimeSourceData.kt (74%) create mode 100644 app/src/main/java/tachiyomi/domain/source/manga/model/MangaSourceData.kt diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index da9dac0d..d87bab8f 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -9,8 +9,7 @@ + android:maxSdkVersion="32" /> diff --git a/app/src/main/java/ani/dantotsu/App.kt b/app/src/main/java/ani/dantotsu/App.kt index 9b57909c..b5c7ebad 100644 --- a/app/src/main/java/ani/dantotsu/App.kt +++ b/app/src/main/java/ani/dantotsu/App.kt @@ -8,8 +8,8 @@ import androidx.multidex.MultiDex import androidx.multidex.MultiDexApplication import ani.dantotsu.aniyomi.anime.custom.AppModule import ani.dantotsu.aniyomi.anime.custom.PreferenceModule -import ani.dantotsu.aniyomi.data.Notifications -import ani.dantotsu.aniyomi.util.logcat +import eu.kanade.tachiyomi.data.notification.Notifications +import tachiyomi.core.util.system.logcat import ani.dantotsu.others.DisabledReports import com.google.firebase.crashlytics.ktx.crashlytics import com.google.firebase.ktx.Firebase diff --git a/app/src/main/java/ani/dantotsu/MainActivity.kt b/app/src/main/java/ani/dantotsu/MainActivity.kt index edbf4dcc..e81b05d2 100644 --- a/app/src/main/java/ani/dantotsu/MainActivity.kt +++ b/app/src/main/java/ani/dantotsu/MainActivity.kt @@ -24,7 +24,7 @@ import androidx.fragment.app.FragmentManager import androidx.lifecycle.Lifecycle import androidx.lifecycle.lifecycleScope import androidx.viewpager2.adapter.FragmentStateAdapter -import ani.dantotsu.aniyomi.anime.AnimeExtensionManager +import eu.kanade.tachiyomi.extension.anime.AnimeExtensionManager import ani.dantotsu.connections.anilist.Anilist import ani.dantotsu.connections.anilist.AnilistHomeViewModel import ani.dantotsu.databinding.ActivityMainBinding @@ -37,13 +37,16 @@ import ani.dantotsu.home.NoInternet import ani.dantotsu.media.MediaDetailsActivity import ani.dantotsu.others.CustomBottomDialog import ani.dantotsu.parsers.AnimeSources +import ani.dantotsu.parsers.MangaSources import ani.dantotsu.settings.UserInterfaceSettings import ani.dantotsu.subcriptions.Subscription.Companion.startSubscription +import eu.kanade.tachiyomi.extension.manga.MangaExtensionManager import io.noties.markwon.Markwon import io.noties.markwon.SoftBreakAddsNewLinePlugin import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.delay +import kotlinx.coroutines.flow.first import kotlinx.coroutines.launch import kotlinx.coroutines.withContext import nl.joery.animatedbottombar.AnimatedBottomBar @@ -58,6 +61,7 @@ class MainActivity : AppCompatActivity() { private var uiSettings = UserInterfaceSettings() private val animeExtensionManager: AnimeExtensionManager by injectLazy() + private val mangaExtensionManager: MangaExtensionManager by injectLazy() override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) @@ -65,11 +69,17 @@ class MainActivity : AppCompatActivity() { binding = ActivityMainBinding.inflate(layoutInflater) setContentView(binding.root) - val myScope = CoroutineScope(Dispatchers.Default) - myScope.launch { + val animeScope = CoroutineScope(Dispatchers.Default) + animeScope.launch { animeExtensionManager.findAvailableExtensions() + logger("Anime Extensions: ${animeExtensionManager.installedExtensionsFlow.first()}") AnimeSources.init(animeExtensionManager.installedExtensionsFlow) - + } + val mangaScope = CoroutineScope(Dispatchers.Default) + mangaScope.launch { + mangaExtensionManager.findAvailableExtensions() + logger("Manga Extensions: ${mangaExtensionManager.installedExtensionsFlow.first()}") + MangaSources.init(mangaExtensionManager.installedExtensionsFlow) } var doubleBackToExitPressedOnce = false diff --git a/app/src/main/java/ani/dantotsu/aniyomi/LICENSE b/app/src/main/java/ani/dantotsu/aniyomi/LICENSE deleted file mode 100644 index 2bb9ad24..00000000 --- a/app/src/main/java/ani/dantotsu/aniyomi/LICENSE +++ /dev/null @@ -1,176 +0,0 @@ - Apache License - Version 2.0, January 2004 - http://www.apache.org/licenses/ - - TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - - 1. Definitions. - - "License" shall mean the terms and conditions for use, reproduction, - and distribution as defined by Sections 1 through 9 of this document. - - "Licensor" shall mean the copyright owner or entity authorized by - the copyright owner that is granting the License. - - "Legal Entity" shall mean the union of the acting entity and all - other entities that control, are controlled by, or are under common - control with that entity. For the purposes of this definition, - "control" means (i) the power, direct or indirect, to cause the - direction or management of such entity, whether by contract or - otherwise, or (ii) ownership of fifty percent (50%) or more of the - outstanding shares, or (iii) beneficial ownership of such entity. - - "You" (or "Your") shall mean an individual or Legal Entity - exercising permissions granted by this License. - - "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation - source, and configuration files. - - "Object" form shall mean any form resulting from mechanical - transformation or translation of a Source form, including but - not limited to compiled object code, generated documentation, - and conversions to other media types. - - "Work" shall mean the work of authorship, whether in Source or - Object form, made available under the License, as indicated by a - copyright notice that is included in or attached to the work - (an example is provided in the Appendix below). - - "Derivative Works" shall mean any work, whether in Source or Object - form, that is based on (or derived from) the Work and for which the - editorial revisions, annotations, elaborations, or other modifications - represent, as a whole, an original work of authorship. For the purposes - of this License, Derivative Works shall not include works that remain - separable from, or merely link (or bind by name) to the interfaces of, - the Work and Derivative Works thereof. - - "Contribution" shall mean any work of authorship, including - the original version of the Work and any modifications or additions - to that Work or Derivative Works thereof, that is intentionally - submitted to Licensor for inclusion in the Work by the copyright owner - or by an individual or Legal Entity authorized to submit on behalf of - the copyright owner. For the purposes of this definition, "submitted" - means any form of electronic, verbal, or written communication sent - to the Licensor or its representatives, including but not limited to - communication on electronic mailing lists, source code control systems, - and issue tracking systems that are managed by, or on behalf of, the - Licensor for the purpose of discussing and improving the Work, but - excluding communication that is conspicuously marked or otherwise - designated in writing by the copyright owner as "Not a Contribution." - - "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and - subsequently incorporated within the Work. - - 2. Grant of Copyright License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - copyright license to reproduce, prepare Derivative Works of, - publicly display, publicly perform, sublicense, and distribute the - Work and such Derivative Works in Source or Object form. - - 3. Grant of Patent License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - (except as stated in this section) patent license to make, have made, - use, offer to sell, sell, import, and otherwise transfer the Work, - where such license applies only to those patent claims licensable - by such Contributor that are necessarily infringed by their - Contribution(s) alone or by combination of their Contribution(s) - with the Work to which such Contribution(s) was submitted. If You - institute patent litigation against any entity (including a - cross-claim or counterclaim in a lawsuit) alleging that the Work - or a Contribution incorporated within the Work constitutes direct - or contributory patent infringement, then any patent licenses - granted to You under this License for that Work shall terminate - as of the date such litigation is filed. - - 4. Redistribution. You may reproduce and distribute copies of the - Work or Derivative Works thereof in any medium, with or without - modifications, and in Source or Object form, provided that You - meet the following conditions: - - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and - - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works - that You distribute, all copyright, patent, trademark, and - attribution notices from the Source form of the Work, - excluding those notices that do not pertain to any part of - the Derivative Works; and - - (d) If the Work includes a "NOTICE" text file as part of its - distribution, then any Derivative Works that You distribute must - include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not - pertain to any part of the Derivative Works, in at least one - of the following places: within a NOTICE text file distributed - as part of the Derivative Works; within the Source form or - documentation, if provided along with the Derivative Works; or, - within a display generated by the Derivative Works, if and - wherever such third-party notices normally appear. The contents - of the NOTICE file are for informational purposes only and - do not modify the License. You may add Your own attribution - notices within Derivative Works that You distribute, alongside - or as an addendum to the NOTICE text from the Work, provided - that such additional attribution notices cannot be construed - as modifying the License. - - You may add Your own copyright statement to Your modifications and - may provide additional or different license terms and conditions - for use, reproduction, or distribution of Your modifications, or - for any such Derivative Works as a whole, provided Your use, - reproduction, and distribution of the Work otherwise complies with - the conditions stated in this License. - - 5. Submission of Contributions. Unless You explicitly state otherwise, - any Contribution intentionally submitted for inclusion in the Work - by You to the Licensor shall be under the terms and conditions of - this License, without any additional terms or conditions. - Notwithstanding the above, nothing herein shall supersede or modify - the terms of any separate license agreement you may have executed - with Licensor regarding such Contributions. - - 6. Trademarks. This License does not grant permission to use the trade - names, trademarks, service marks, or product names of the Licensor, - except as required for reasonable and customary use in describing the - origin of the Work and reproducing the content of the NOTICE file. - - 7. Disclaimer of Warranty. Unless required by applicable law or - agreed to in writing, Licensor provides the Work (and each - Contributor provides its Contributions) on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied, including, without limitation, any warranties or conditions - of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A - PARTICULAR PURPOSE. You are solely responsible for determining the - appropriateness of using or redistributing the Work and assume any - risks associated with Your exercise of permissions under this License. - - 8. Limitation of Liability. In no event and under no legal theory, - whether in tort (including negligence), contract, or otherwise, - unless required by applicable law (such as deliberate and grossly - negligent acts) or agreed to in writing, shall any Contributor be - liable to You for damages, including any direct, indirect, special, - incidental, or consequential damages of any character arising as a - result of this License or out of the use or inability to use the - Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses), even if such Contributor - has been advised of the possibility of such damages. - - 9. Accepting Warranty or Additional Liability. While redistributing - the Work or Derivative Works thereof, You may choose to offer, - and charge a fee for, acceptance of support, warranty, indemnity, - or other liability obligations and/or rights consistent with this - License. However, in accepting such obligations, You may act only - on Your own behalf and on Your sole responsibility, not on behalf - of any other Contributor, and only if You agree to indemnify, - defend, and hold each Contributor harmless for any liability - incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. - - END OF TERMS AND CONDITIONS \ No newline at end of file diff --git a/app/src/main/java/ani/dantotsu/aniyomi/NOTICE.md b/app/src/main/java/ani/dantotsu/aniyomi/NOTICE.md deleted file mode 100644 index 5a81820b..00000000 --- a/app/src/main/java/ani/dantotsu/aniyomi/NOTICE.md +++ /dev/null @@ -1,3 +0,0 @@ -NOTICE - -This software includes code modified from Aniyomi, available at https://github.com/aniyomiorg/aniyomi/. \ No newline at end of file diff --git a/app/src/main/java/ani/dantotsu/aniyomi/anime/custom/App.kt b/app/src/main/java/ani/dantotsu/aniyomi/anime/custom/App.kt deleted file mode 100644 index 876e20c7..00000000 --- a/app/src/main/java/ani/dantotsu/aniyomi/anime/custom/App.kt +++ /dev/null @@ -1,30 +0,0 @@ -package ani.dantotsu.aniyomi.anime.custom -/* -import android.app.Application -import ani.dantotsu.aniyomi.data.Notifications -import ani.dantotsu.aniyomi.util.logcat -import logcat.AndroidLogcatLogger -import logcat.LogPriority -import logcat.LogcatLogger -import uy.kohesive.injekt.Injekt - -class App : Application() { - override fun onCreate() { - super.onCreate() - Injekt.importModule(AppModule(this)) - Injekt.importModule(PreferenceModule(this)) - - setupNotificationChannels() - if (!LogcatLogger.isInstalled) { - LogcatLogger.install(AndroidLogcatLogger(LogPriority.VERBOSE)) - } - } - - private fun setupNotificationChannels() { - try { - Notifications.createChannels(this) - } catch (e: Exception) { - logcat(LogPriority.ERROR, e) { "Failed to modify notification channels" } - } - } -}*/ \ No newline at end of file diff --git a/app/src/main/java/ani/dantotsu/aniyomi/anime/custom/InjektModules.kt b/app/src/main/java/ani/dantotsu/aniyomi/anime/custom/InjektModules.kt index e4b3e8eb..bff74eeb 100644 --- a/app/src/main/java/ani/dantotsu/aniyomi/anime/custom/InjektModules.kt +++ b/app/src/main/java/ani/dantotsu/aniyomi/anime/custom/InjektModules.kt @@ -1,11 +1,12 @@ package ani.dantotsu.aniyomi.anime.custom import android.app.Application -import ani.dantotsu.aniyomi.anime.AnimeExtensionManager -import ani.dantotsu.aniyomi.core.preference.PreferenceStore -import ani.dantotsu.aniyomi.domain.base.BasePreferences -import ani.dantotsu.aniyomi.domain.source.service.SourcePreferences -import ani.dantotsu.aniyomi.core.preference.AndroidPreferenceStore +import eu.kanade.tachiyomi.extension.anime.AnimeExtensionManager +import tachiyomi.core.preference.PreferenceStore +import eu.kanade.domain.base.BasePreferences +import eu.kanade.domain.source.service.SourcePreferences +import eu.kanade.tachiyomi.core.preference.AndroidPreferenceStore +import eu.kanade.tachiyomi.extension.manga.MangaExtensionManager import eu.kanade.tachiyomi.network.NetworkHelper import kotlinx.serialization.json.Json import uy.kohesive.injekt.api.InjektModule @@ -22,6 +23,8 @@ class AppModule(val app: Application) : InjektModule { addSingletonFactory { AnimeExtensionManager(app) } + addSingletonFactory { MangaExtensionManager(app) } + addSingletonFactory { Json { ignoreUnknownKeys = true diff --git a/app/src/main/java/ani/dantotsu/aniyomi/util/srcapi/RxExtension.kt b/app/src/main/java/ani/dantotsu/aniyomi/util/srcapi/RxExtension.kt deleted file mode 100644 index 220e6185..00000000 --- a/app/src/main/java/ani/dantotsu/aniyomi/util/srcapi/RxExtension.kt +++ /dev/null @@ -1,3 +0,0 @@ -package ani.dantotsu.aniyomi.util.srcapi - -//actual suspend fun Observable.awaitSingle(): T = awaitSingle() diff --git a/app/src/main/java/ani/dantotsu/media/MediaDetailsViewModel.kt b/app/src/main/java/ani/dantotsu/media/MediaDetailsViewModel.kt index 6d71b69c..ea00c624 100644 --- a/app/src/main/java/ani/dantotsu/media/MediaDetailsViewModel.kt +++ b/app/src/main/java/ani/dantotsu/media/MediaDetailsViewModel.kt @@ -238,7 +238,7 @@ class MediaDetailsViewModel : ViewModel() { suspend fun loadMangaChapterImages(chapter: MangaChapter, selected: Selected, post: Boolean = true): Boolean { return tryWithSuspend(true) { chapter.addImages( - mangaReadSources?.get(selected.source)?.loadImages(chapter.link) ?: return@tryWithSuspend false + mangaReadSources?.get(selected.source)?.loadImages(chapter.link, chapter.sChapter) ?: return@tryWithSuspend false ) if (post) mangaChapter.postValue(chapter) true diff --git a/app/src/main/java/ani/dantotsu/media/manga/MangaChapter.kt b/app/src/main/java/ani/dantotsu/media/manga/MangaChapter.kt index d16af066..16e51797 100644 --- a/app/src/main/java/ani/dantotsu/media/manga/MangaChapter.kt +++ b/app/src/main/java/ani/dantotsu/media/manga/MangaChapter.kt @@ -2,6 +2,7 @@ package ani.dantotsu.media.manga import ani.dantotsu.parsers.MangaChapter import ani.dantotsu.parsers.MangaImage +import eu.kanade.tachiyomi.source.model.SChapter import java.io.Serializable import kotlin.math.floor @@ -10,8 +11,9 @@ data class MangaChapter( var link: String, var title: String? = null, var description: String? = null, + var sChapter: SChapter ) : Serializable { - constructor(chapter: MangaChapter) : this(chapter.number, chapter.link, chapter.title, chapter.description) + constructor(chapter: MangaChapter) : this(chapter.number, chapter.link, chapter.title, chapter.description, chapter.sChapter) private val images = mutableListOf() fun images(): List = images diff --git a/app/src/main/java/ani/dantotsu/others/Jikan.kt b/app/src/main/java/ani/dantotsu/others/Jikan.kt index eb56e8c0..e08231a0 100644 --- a/app/src/main/java/ani/dantotsu/others/Jikan.kt +++ b/app/src/main/java/ani/dantotsu/others/Jikan.kt @@ -25,7 +25,7 @@ object Jikan { val ep = it.malID.toString() eps[ep] = Episode(ep, title = it.title, //Personal revenge with 34566 :prayge: - filler = if(malId!=34566) it.filler else true + filler = if(malId!=34566) it.filler else true, ) } hasNextPage = res?.pagination?.hasNextPage == true diff --git a/app/src/main/java/ani/dantotsu/parsers/AnimeSources.kt b/app/src/main/java/ani/dantotsu/parsers/AnimeSources.kt index 45431e79..1b66928c 100644 --- a/app/src/main/java/ani/dantotsu/parsers/AnimeSources.kt +++ b/app/src/main/java/ani/dantotsu/parsers/AnimeSources.kt @@ -1,34 +1,11 @@ package ani.dantotsu.parsers import ani.dantotsu.Lazier -import ani.dantotsu.aniyomi.anime.model.AnimeExtension +import eu.kanade.tachiyomi.extension.anime.model.AnimeExtension import ani.dantotsu.lazyList -//import ani.dantotsu.parsers.anime.AllAnime -//import ani.dantotsu.parsers.anime.AnimeDao -//import ani.dantotsu.parsers.anime.AnimePahe -//import ani.dantotsu.parsers.anime.Gogo -//import ani.dantotsu.parsers.anime.Haho -//import ani.dantotsu.parsers.anime.HentaiFF -//import ani.dantotsu.parsers.anime.HentaiMama -//import ani.dantotsu.parsers.anime.HentaiStream -//import ani.dantotsu.parsers.anime.Marin -//import ani.dantotsu.parsers.anime.AniWave -//import ani.dantotsu.parsers.anime.Kaido import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.first -/* -object AnimeSources_old : WatchSources() { - override val list: List> = lazyList( - "AllAnime" to ::AllAnime, - "Gogo" to ::Gogo, - "Kaido" to ::Kaido, - "Marin" to ::Marin, - "AnimePahe" to ::AnimePahe, - "AniWave" to ::AniWave, - "AnimeDao" to ::AnimeDao, - ) -} -*/ + object AnimeSources : WatchSources() { override var list: List> = emptyList() @@ -52,13 +29,8 @@ object AnimeSources : WatchSources() { } - object HAnimeSources : WatchSources() { private val aList: List> = lazyList( - //"HentaiMama" to ::HentaiMama, - //"Haho" to ::Haho, - //"HentaiStream" to ::HentaiStream, - //"HentaiFF" to ::HentaiFF, ) override val list = listOf(aList,AnimeSources.list).flatten() diff --git a/app/src/main/java/ani/dantotsu/parsers/AniyomiAdapter.kt b/app/src/main/java/ani/dantotsu/parsers/AniyomiAdapter.kt index 4fab3116..a93607c6 100644 --- a/app/src/main/java/ani/dantotsu/parsers/AniyomiAdapter.kt +++ b/app/src/main/java/ani/dantotsu/parsers/AniyomiAdapter.kt @@ -1,10 +1,18 @@ package ani.dantotsu.parsers +import android.content.ContentResolver +import android.content.ContentValues +import android.graphics.Bitmap +import android.graphics.BitmapFactory +import android.net.Uri +import android.os.Build +import android.os.Environment +import android.provider.MediaStore import android.widget.Toast import ani.dantotsu.FileUrl -import ani.dantotsu.aniyomi.anime.model.AnimeExtension -import ani.dantotsu.aniyomi.animesource.AnimeCatalogueSource -import ani.dantotsu.aniyomi.util.network.interceptor.CloudflareBypassException +import eu.kanade.tachiyomi.extension.anime.model.AnimeExtension +import eu.kanade.tachiyomi.animesource.AnimeCatalogueSource +import eu.kanade.tachiyomi.network.interceptor.CloudflareBypassException import ani.dantotsu.currContext import ani.dantotsu.logger import eu.kanade.tachiyomi.animesource.model.SEpisode @@ -13,6 +21,22 @@ import eu.kanade.tachiyomi.animesource.model.AnimesPage import eu.kanade.tachiyomi.animesource.model.SAnime import eu.kanade.tachiyomi.animesource.model.Track import eu.kanade.tachiyomi.animesource.model.Video +import eu.kanade.tachiyomi.extension.manga.model.MangaExtension +import eu.kanade.tachiyomi.source.CatalogueSource +import eu.kanade.tachiyomi.source.model.FilterList +import eu.kanade.tachiyomi.source.model.MangasPage +import eu.kanade.tachiyomi.source.model.Page +import eu.kanade.tachiyomi.source.model.SChapter +import eu.kanade.tachiyomi.source.model.SManga +import eu.kanade.tachiyomi.source.online.HttpSource +import eu.kanade.tachiyomi.util.lang.awaitSingle +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext +import java.io.File +import java.io.FileOutputStream +import java.io.OutputStream import java.net.URL import java.net.URLDecoder @@ -91,7 +115,7 @@ class DynamicAnimeParser(extension: AnimeExtension.Installed) : AnimeParser() { return emptyList() // Return an empty list if source is not an AnimeCatalogueSource } - fun convertAnimesPageToShowResponse(animesPage: AnimesPage): List { + private fun convertAnimesPageToShowResponse(animesPage: AnimesPage): List { return animesPage.animes.map { sAnime -> // Extract required fields from sAnime val name = sAnime.title @@ -106,34 +130,160 @@ class DynamicAnimeParser(extension: AnimeExtension.Installed) : AnimeParser() { } } - fun SEpisodeToEpisode(sEpisode: SEpisode): Episode { - val episode = Episode( - sEpisode.episode_number.toString(), + private fun SEpisodeToEpisode(sEpisode: SEpisode): Episode { + //if the float episode number is a whole number, convert it to an int + val episodeNumberInt = + if (sEpisode.episode_number % 1 == 0f) { + sEpisode.episode_number.toInt() + } else { + sEpisode.episode_number + } + return Episode( + episodeNumberInt.toString(), sEpisode.url, sEpisode.name, null, null, false, null, - sEpisode) - return episode + sEpisode + ) } - fun VideoToVideoServer(video: Video): VideoServer { - val videoServer = VideoServer( + private fun VideoToVideoServer(video: Video): VideoServer { + return VideoServer( video.quality, video.url, null, - video) - return videoServer + video + ) } } -class VideoServerPassthrough : VideoExtractor{ - val videoServer: VideoServer - constructor(videoServer: VideoServer) { - this.videoServer = videoServer +class DynamicMangaParser(extension: MangaExtension.Installed) : MangaParser() { + val extension: MangaExtension.Installed + init { + this.extension = extension } + override val name = extension.name + override val saveName = extension.name + override val hostUrl = extension.sources.first().name + + override suspend fun loadChapters(mangaLink: String, extra: Map?, sManga: SManga): List { + val source = extension.sources.first() + if (source is CatalogueSource) { + try { + val res = source.getChapterList(sManga) + var chapterList: List = emptyList() + for (chapter in res) { + chapterList += SChapterToMangaChapter(chapter) + } + logger("chapterList size: ${chapterList.size}") + return chapterList + } + catch (e: Exception) { + logger("loadChapters Exception: $e") + } + return emptyList() + } + return emptyList() // Return an empty list if source is not a catalogueSource + } + + override suspend fun loadImages(chapterLink: String, sChapter: SChapter): List { + val source = extension.sources.first() + if (source is HttpSource) { + //try { + val res = source.getPageList(sChapter) + var chapterList: List = emptyList() + for (page in res) { + println("page: $page") + currContext()?.let { fetchAndProcessImage(page, source, it.contentResolver) } + logger("new image url: ${page.imageUrl}") + chapterList += PageToMangaImage(page) + } + logger("image url: chapterList size: ${chapterList.size}") + return chapterList + //} + //catch (e: Exception) { + // logger("loadImages Exception: $e") + //} + return emptyList() + } + return emptyList() // Return an empty list if source is not a CatalogueSource + } + + + override suspend fun search(query: String): List { + val source = extension.sources.first() + if (source is HttpSource) { + var res: MangasPage? = null + try { + res = source.fetchSearchManga(1, query, FilterList()).toBlocking().first() + logger("res observable: $res") + } + catch (e: CloudflareBypassException) { + logger("Exception in search: $e") + Toast.makeText(currContext(), "Failed to bypass Cloudflare", Toast.LENGTH_SHORT).show() + } + + val conv = convertMangasPageToShowResponse(res!!) + return conv + } + return emptyList() // Return an empty list if source is not a CatalogueSource + } + + private fun convertMangasPageToShowResponse(mangasPage: MangasPage): List { + return mangasPage.mangas.map { sManga -> + // Extract required fields from sManga + val name = sManga.title + val link = sManga.url + val coverUrl = sManga.thumbnail_url ?: "" + val otherNames = emptyList() // Populate as needed + val total = 20 + val extra: Map? = null // Populate as needed + + // Create a new ShowResponse + ShowResponse(name, link, coverUrl, sManga) + } + } + + private fun PageToMangaImage(page: Page): MangaImage { + //find and move any headers from page.imageUrl to headersMap + val headersMap: Map = page.imageUrl?.split("&")?.mapNotNull { + val idx = it.indexOf("=") + if (idx != -1) { + val key = URLDecoder.decode(it.substring(0, idx), "UTF-8") + val value = URLDecoder.decode(it.substring(idx + 1), "UTF-8") + Pair(key, value) + } else { + null // Or some other default value + } + }?.toMap() ?: mapOf() + val urlWithoutHeaders = page.imageUrl?.split("&")?.get(0) ?: "" + val url = page.imageUrl ?: "" + logger("Pageurl: $url") + logger("regularurl: ${page.url}") + logger("regularurl: ${page.status}") + return MangaImage( + FileUrl(url, headersMap), + false, + page + ) + } + + private fun SChapterToMangaChapter(sChapter: SChapter): MangaChapter { + return MangaChapter( + sChapter.name, + sChapter.url, + sChapter.name, + null, + sChapter + ) + } + +} + +class VideoServerPassthrough(val videoServer: VideoServer) : VideoExtractor() { override val server: VideoServer get() { return videoServer diff --git a/app/src/main/java/ani/dantotsu/parsers/BaseParser.kt b/app/src/main/java/ani/dantotsu/parsers/BaseParser.kt index b6bb5063..5aa826b1 100644 --- a/app/src/main/java/ani/dantotsu/parsers/BaseParser.kt +++ b/app/src/main/java/ani/dantotsu/parsers/BaseParser.kt @@ -3,6 +3,7 @@ package ani.dantotsu.parsers import ani.dantotsu.* import ani.dantotsu.media.Media import eu.kanade.tachiyomi.animesource.model.SAnime +import eu.kanade.tachiyomi.source.model.SManga import java.io.Serializable import java.net.URLDecoder import java.net.URLEncoder @@ -141,7 +142,10 @@ data class ShowResponse( val extra : Map?=null, //SAnime object from Aniyomi - val sAnime: SAnime?=null + val sAnime: SAnime? = null, + + //SManga object from Aniyomi + val sManga: SManga? = null ) : Serializable { constructor(name: String, link: String, coverUrl: String, otherNames: List = listOf(), total: Int? = null, extra: Map?=null) : this(name, link, FileUrl(coverUrl), otherNames, total, extra) @@ -157,6 +161,9 @@ data class ShowResponse( constructor(name: String, link: String, coverUrl: String, sAnime: SAnime) : this(name, link, FileUrl(coverUrl), sAnime = sAnime) + + constructor(name: String, link: String, coverUrl: String, sManga: SManga) + : this(name, link, FileUrl(coverUrl), sManga = sManga) } diff --git a/app/src/main/java/ani/dantotsu/parsers/BaseSources.kt b/app/src/main/java/ani/dantotsu/parsers/BaseSources.kt index 285630a5..fb612bb3 100644 --- a/app/src/main/java/ani/dantotsu/parsers/BaseSources.kt +++ b/app/src/main/java/ani/dantotsu/parsers/BaseSources.kt @@ -1,6 +1,7 @@ package ani.dantotsu.parsers import ani.dantotsu.Lazier +import ani.dantotsu.logger import ani.dantotsu.media.anime.Episode import ani.dantotsu.media.manga.MangaChapter import ani.dantotsu.media.Media @@ -52,11 +53,17 @@ abstract class MangaReadSources : BaseSources() { suspend fun loadChapters(i: Int, show: ShowResponse): MutableMap { val map = mutableMapOf() val parser = get(i) - tryWithSuspend(true) { - parser.loadChapters(show.link, show.extra).forEach { - map[it.number] = MangaChapter(it) + show.sManga?.let { sManga -> + tryWithSuspend(true) { + parser.loadChapters(show.link, show.extra, sManga).forEach { + map[it.number] = MangaChapter(it) + } } } + if(show.sManga == null) { + logger("sManga is null") + } + logger("map size ${map.size}") return map } } diff --git a/app/src/main/java/ani/dantotsu/parsers/MangaParser.kt b/app/src/main/java/ani/dantotsu/parsers/MangaParser.kt index 670fbda2..81de230b 100644 --- a/app/src/main/java/ani/dantotsu/parsers/MangaParser.kt +++ b/app/src/main/java/ani/dantotsu/parsers/MangaParser.kt @@ -3,6 +3,9 @@ package ani.dantotsu.parsers import ani.dantotsu.FileUrl import ani.dantotsu.media.Media import com.bumptech.glide.load.resource.bitmap.BitmapTransformation +import eu.kanade.tachiyomi.source.model.Page +import eu.kanade.tachiyomi.source.model.SChapter +import eu.kanade.tachiyomi.source.model.SManga import java.io.Serializable abstract class MangaParser : BaseParser() { @@ -10,7 +13,7 @@ abstract class MangaParser : BaseParser() { /** * Takes ShowResponse.link and ShowResponse.extra (if any) as arguments & gives a list of total chapters present on the site. * **/ - abstract suspend fun loadChapters(mangaLink: String, extra: Map?): List + abstract suspend fun loadChapters(mangaLink: String, extra: Map?, sManga: SManga): List /** * Takes ShowResponse.link, ShowResponse.extra & the Last Largest Chapter Number known by app as arguments @@ -18,8 +21,8 @@ abstract class MangaParser : BaseParser() { * Returns the latest chapter (If overriding, Make sure the chapter is actually the latest chapter) * Returns null, if no latest chapter is found. * **/ - open suspend fun getLatestChapter(mangaLink: String, extra: Map?, latest: Float): MangaChapter? { - return loadChapters(mangaLink, extra) + open suspend fun getLatestChapter(mangaLink: String, extra: Map?, sManga: SManga, latest: Float): MangaChapter? { + return loadChapters(mangaLink, extra, sManga) .maxByOrNull { it.number.toFloatOrNull() ?: 0f } ?.takeIf { latest < (it.number.toFloatOrNull() ?: 0.001f) } } @@ -27,7 +30,7 @@ abstract class MangaParser : BaseParser() { /** * Takes MangaChapter.link as an argument & returns a list of MangaImages with their Url (with headers & transformations, if needed) * **/ - abstract suspend fun loadImages(chapterLink: String): List + abstract suspend fun loadImages(chapterLink: String, sChapter: SChapter): List override suspend fun autoSearch(mediaObj: Media): ShowResponse? { var response = loadSavedShowResponse(mediaObj.id) @@ -65,6 +68,8 @@ data class MangaChapter( //Self-Descriptive val title: String? = null, val description: String? = null, + + val sChapter: SChapter, ) data class MangaImage( @@ -75,8 +80,10 @@ data class MangaImage( * **/ val url: FileUrl, - val useTransformation: Boolean = false + val useTransformation: Boolean = false, + + val page: Page ) : Serializable{ - constructor(url: String,useTransformation: Boolean=false) - : this(FileUrl(url),useTransformation) + constructor(url: String,useTransformation: Boolean=false, page: Page) + : this(FileUrl(url),useTransformation, page) } diff --git a/app/src/main/java/ani/dantotsu/parsers/MangaSources.kt b/app/src/main/java/ani/dantotsu/parsers/MangaSources.kt index cffca693..c25e0369 100644 --- a/app/src/main/java/ani/dantotsu/parsers/MangaSources.kt +++ b/app/src/main/java/ani/dantotsu/parsers/MangaSources.kt @@ -2,10 +2,30 @@ package ani.dantotsu.parsers import ani.dantotsu.Lazier import ani.dantotsu.lazyList +import eu.kanade.tachiyomi.extension.manga.model.MangaExtension +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.first object MangaSources : MangaReadSources() { - override val list: List> = lazyList( - ) + override var list: List> = emptyList() + + suspend fun init(fromExtensions: StateFlow>) { + // Initialize with the first value from StateFlow + val initialExtensions = fromExtensions.first() + list = createParsersFromExtensions(initialExtensions) + + // Update as StateFlow emits new values + fromExtensions.collect { extensions -> + list = createParsersFromExtensions(extensions) + } + } + + private fun createParsersFromExtensions(extensions: List): List> { + return extensions.map { extension -> + val name = extension.name + Lazier({ DynamicMangaParser(extension) }, name) + } + } } object HMangaSources : MangaReadSources() { diff --git a/app/src/main/java/ani/dantotsu/settings/ExtensionsActivity.kt b/app/src/main/java/ani/dantotsu/settings/ExtensionsActivity.kt index 76c6c47a..8f4a8184 100644 --- a/app/src/main/java/ani/dantotsu/settings/ExtensionsActivity.kt +++ b/app/src/main/java/ani/dantotsu/settings/ExtensionsActivity.kt @@ -3,13 +3,11 @@ package ani.dantotsu.settings import android.annotation.SuppressLint import android.app.NotificationManager import android.content.Context -import android.content.Intent +import android.content.pm.PackageManager import android.graphics.drawable.Drawable -import android.net.Uri import android.os.Build.* import android.os.Build.VERSION.* import android.os.Bundle -import android.provider.Settings import android.view.LayoutInflater import android.view.View import android.view.ViewGroup @@ -19,16 +17,19 @@ import android.widget.SearchView import android.widget.TextView import androidx.activity.OnBackPressedCallback import androidx.appcompat.app.AppCompatActivity +import androidx.core.app.ActivityCompat import androidx.core.app.NotificationCompat +import androidx.core.content.ContextCompat import androidx.core.view.updateLayoutParams import androidx.lifecycle.lifecycleScope import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.RecyclerView import ani.dantotsu.* -import ani.dantotsu.aniyomi.anime.AnimeExtensionManager -import ani.dantotsu.aniyomi.anime.model.AnimeExtension +import eu.kanade.tachiyomi.extension.anime.AnimeExtensionManager +import eu.kanade.tachiyomi.extension.anime.model.AnimeExtension import ani.dantotsu.databinding.ActivityExtensionsBinding import com.bumptech.glide.Glide +import eu.kanade.tachiyomi.data.notification.Notifications import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.flow.combine @@ -60,7 +61,7 @@ class ExtensionsActivity : AppCompatActivity() { .subscribe( { installStep -> val builder = NotificationCompat.Builder(this, - ani.dantotsu.aniyomi.data.Notifications.CHANNEL_DOWNLOADER_PROGRESS + Notifications.CHANNEL_DOWNLOADER_PROGRESS ) .setSmallIcon(R.drawable.ic_round_sync_24) .setContentTitle("Installing extension") @@ -70,7 +71,7 @@ class ExtensionsActivity : AppCompatActivity() { }, { error -> val builder = NotificationCompat.Builder(this, - ani.dantotsu.aniyomi.data.Notifications.CHANNEL_DOWNLOADER_ERROR + Notifications.CHANNEL_DOWNLOADER_ERROR ) .setSmallIcon(R.drawable.ic_round_info_24) .setContentTitle("Installation failed") @@ -80,7 +81,7 @@ class ExtensionsActivity : AppCompatActivity() { }, { val builder = NotificationCompat.Builder(this, - ani.dantotsu.aniyomi.data.Notifications.CHANNEL_DOWNLOADER_PROGRESS) + Notifications.CHANNEL_DOWNLOADER_PROGRESS) .setSmallIcon(androidx.media3.ui.R.drawable.exo_ic_check) .setContentTitle("Installation complete") .setContentText("The extension has been successfully installed.") @@ -164,8 +165,11 @@ class ExtensionsActivity : AppCompatActivity() { onBackPressedDispatcher.onBackPressed() } + } + + private class ExtensionsAdapter(private val onUninstallClicked: (String) -> Unit) : RecyclerView.Adapter() { private var extensions: List = emptyList() diff --git a/app/src/main/java/ani/dantotsu/subcriptions/SubscriptionHelper.kt b/app/src/main/java/ani/dantotsu/subcriptions/SubscriptionHelper.kt index b9d5b4fa..038f6598 100644 --- a/app/src/main/java/ani/dantotsu/subcriptions/SubscriptionHelper.kt +++ b/app/src/main/java/ani/dantotsu/subcriptions/SubscriptionHelper.kt @@ -66,7 +66,10 @@ class SubscriptionHelper { val chp = withTimeoutOrNull(10 * 1000) { tryWithSuspend { val show = parser.loadSavedShowResponse(id) ?: throw Exception(currContext()?.getString(R.string.failed_to_load_data, id)) - parser.getLatestChapter(show.link, show.extra, selected.latest) + show.sManga?.let { + parser.getLatestChapter(show.link, show.extra, + it, selected.latest) + } } } diff --git a/app/src/main/java/eu/kanade/core/preference/PreferenceMutableState.kt b/app/src/main/java/eu/kanade/core/preference/PreferenceMutableState.kt new file mode 100644 index 00000000..2c641ccc --- /dev/null +++ b/app/src/main/java/eu/kanade/core/preference/PreferenceMutableState.kt @@ -0,0 +1,38 @@ +package eu.kanade.core.preference + +import androidx.compose.runtime.MutableState +import androidx.compose.runtime.mutableStateOf +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.flow.launchIn +import kotlinx.coroutines.flow.onEach +import tachiyomi.core.preference.Preference + +class PreferenceMutableState( + private val preference: Preference, + scope: CoroutineScope, +) : MutableState { + + private val state = mutableStateOf(preference.get()) + + init { + preference.changes() + .onEach { state.value = it } + .launchIn(scope) + } + + override var value: T + get() = state.value + set(value) { + preference.set(value) + } + + override fun component1(): T { + return state.value + } + + override fun component2(): (T) -> Unit { + return { preference.set(it) } + } +} + +fun Preference.asState(scope: CoroutineScope) = PreferenceMutableState(this, scope) diff --git a/app/src/main/java/ani/dantotsu/aniyomi/domain/base/BasePreferences.kt b/app/src/main/java/eu/kanade/domain/base/BasePreferences.kt similarity index 90% rename from app/src/main/java/ani/dantotsu/aniyomi/domain/base/BasePreferences.kt rename to app/src/main/java/eu/kanade/domain/base/BasePreferences.kt index c46c3c61..176322d4 100644 --- a/app/src/main/java/ani/dantotsu/aniyomi/domain/base/BasePreferences.kt +++ b/app/src/main/java/eu/kanade/domain/base/BasePreferences.kt @@ -1,9 +1,9 @@ -package ani.dantotsu.aniyomi.domain.base +package eu.kanade.domain.base import android.content.Context import android.content.pm.PackageManager import android.os.Build -import ani.dantotsu.aniyomi.core.preference.PreferenceStore +import tachiyomi.core.preference.PreferenceStore class BasePreferences( val context: Context, diff --git a/app/src/main/java/ani/dantotsu/aniyomi/domain/base/ExtensionInstallerPreference.kt b/app/src/main/java/eu/kanade/domain/base/ExtensionInstallerPreference.kt similarity index 81% rename from app/src/main/java/ani/dantotsu/aniyomi/domain/base/ExtensionInstallerPreference.kt rename to app/src/main/java/eu/kanade/domain/base/ExtensionInstallerPreference.kt index e9c4592a..14dbe10e 100644 --- a/app/src/main/java/ani/dantotsu/aniyomi/domain/base/ExtensionInstallerPreference.kt +++ b/app/src/main/java/eu/kanade/domain/base/ExtensionInstallerPreference.kt @@ -1,13 +1,13 @@ -package ani.dantotsu.aniyomi.domain.base +package eu.kanade.domain.base import android.content.Context -import ani.dantotsu.aniyomi.util.system.hasMiuiPackageInstaller -import ani.dantotsu.aniyomi.domain.base.BasePreferences.ExtensionInstaller -import ani.dantotsu.aniyomi.util.system.isShizukuInstalled +import eu.kanade.tachiyomi.util.system.hasMiuiPackageInstaller +import eu.kanade.domain.base.BasePreferences.ExtensionInstaller +import eu.kanade.tachiyomi.util.system.isShizukuInstalled import kotlinx.coroutines.CoroutineScope -import ani.dantotsu.aniyomi.core.preference.Preference -import ani.dantotsu.aniyomi.core.preference.PreferenceStore -import ani.dantotsu.aniyomi.core.preference.getEnum +import tachiyomi.core.preference.Preference +import tachiyomi.core.preference.PreferenceStore +import tachiyomi.core.preference.getEnum class ExtensionInstallerPreference( private val context: Context, diff --git a/app/src/main/java/ani/dantotsu/aniyomi/domain/source/service/SetMigrateSorting.kt b/app/src/main/java/eu/kanade/domain/source/service/SetMigrateSorting.kt similarity index 88% rename from app/src/main/java/ani/dantotsu/aniyomi/domain/source/service/SetMigrateSorting.kt rename to app/src/main/java/eu/kanade/domain/source/service/SetMigrateSorting.kt index bafcb2f7..eb09eb42 100644 --- a/app/src/main/java/ani/dantotsu/aniyomi/domain/source/service/SetMigrateSorting.kt +++ b/app/src/main/java/eu/kanade/domain/source/service/SetMigrateSorting.kt @@ -1,4 +1,4 @@ -package ani.dantotsu.aniyomi.domain.source.service +package eu.kanade.domain.source.service class SetMigrateSorting( private val preferences: SourcePreferences, diff --git a/app/src/main/java/ani/dantotsu/aniyomi/domain/source/service/SourcePreferences.kt b/app/src/main/java/eu/kanade/domain/source/service/SourcePreferences.kt similarity index 92% rename from app/src/main/java/ani/dantotsu/aniyomi/domain/source/service/SourcePreferences.kt rename to app/src/main/java/eu/kanade/domain/source/service/SourcePreferences.kt index 2835b77f..22a24479 100644 --- a/app/src/main/java/ani/dantotsu/aniyomi/domain/source/service/SourcePreferences.kt +++ b/app/src/main/java/eu/kanade/domain/source/service/SourcePreferences.kt @@ -1,9 +1,9 @@ -package ani.dantotsu.aniyomi.domain.source.service +package eu.kanade.domain.source.service -import ani.dantotsu.aniyomi.core.preference.PreferenceStore -import ani.dantotsu.aniyomi.util.system.LocaleHelper -import ani.dantotsu.aniyomi.core.preference.getEnum -import ani.dantotsu.aniyomi.domain.library.model.LibraryDisplayMode +import eu.kanade.tachiyomi.util.system.LocaleHelper +import tachiyomi.core.preference.PreferenceStore +import tachiyomi.core.preference.getEnum +import tachiyomi.domain.library.model.LibraryDisplayMode class SourcePreferences( private val preferenceStore: PreferenceStore, diff --git a/app/src/main/java/eu/kanade/domain/source/service/ToggleLanguage.kt b/app/src/main/java/eu/kanade/domain/source/service/ToggleLanguage.kt new file mode 100644 index 00000000..3ebb3091 --- /dev/null +++ b/app/src/main/java/eu/kanade/domain/source/service/ToggleLanguage.kt @@ -0,0 +1,15 @@ +package eu.kanade.domain.source.service + +import tachiyomi.core.preference.getAndSet + +class ToggleLanguage( + val preferences: SourcePreferences, +) { + + fun await(language: String) { + val isEnabled = language in preferences.enabledLanguages().get() + preferences.enabledLanguages().getAndSet { enabled -> + if (isEnabled) enabled.minus(language) else enabled.plus(language) + } + } +} diff --git a/app/src/main/java/ani/dantotsu/aniyomi/PreferenceScreen.kt b/app/src/main/java/eu/kanade/tachiyomi/PreferenceScreen.kt similarity index 69% rename from app/src/main/java/ani/dantotsu/aniyomi/PreferenceScreen.kt rename to app/src/main/java/eu/kanade/tachiyomi/PreferenceScreen.kt index 0eb65a07..8a867ba3 100644 --- a/app/src/main/java/ani/dantotsu/aniyomi/PreferenceScreen.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/PreferenceScreen.kt @@ -1,3 +1,3 @@ -package ani.dantotsu.aniyomi +package eu.kanade.tachiyomi typealias PreferenceScreen = androidx.preference.PreferenceScreen diff --git a/app/src/main/java/ani/dantotsu/aniyomi/animesource/AnimeCatalogueSource.kt b/app/src/main/java/eu/kanade/tachiyomi/animesource/AnimeCatalogueSource.kt similarity index 96% rename from app/src/main/java/ani/dantotsu/aniyomi/animesource/AnimeCatalogueSource.kt rename to app/src/main/java/eu/kanade/tachiyomi/animesource/AnimeCatalogueSource.kt index 412bce7b..67d99256 100644 --- a/app/src/main/java/ani/dantotsu/aniyomi/animesource/AnimeCatalogueSource.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/animesource/AnimeCatalogueSource.kt @@ -1,4 +1,4 @@ -package ani.dantotsu.aniyomi.animesource +package eu.kanade.tachiyomi.animesource import eu.kanade.tachiyomi.animesource.model.AnimeFilterList import eu.kanade.tachiyomi.animesource.model.AnimesPage diff --git a/app/src/main/java/ani/dantotsu/aniyomi/animesource/AnimeSource.kt b/app/src/main/java/eu/kanade/tachiyomi/animesource/AnimeSource.kt similarity index 94% rename from app/src/main/java/ani/dantotsu/aniyomi/animesource/AnimeSource.kt rename to app/src/main/java/eu/kanade/tachiyomi/animesource/AnimeSource.kt index c422c411..210e04df 100644 --- a/app/src/main/java/ani/dantotsu/aniyomi/animesource/AnimeSource.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/animesource/AnimeSource.kt @@ -1,10 +1,9 @@ -package ani.dantotsu.aniyomi.animesource +package eu.kanade.tachiyomi.animesource import eu.kanade.tachiyomi.animesource.model.SAnime import eu.kanade.tachiyomi.animesource.model.SEpisode import eu.kanade.tachiyomi.animesource.model.Video -//import ani.dantotsu.aniyomi.util.awaitSingle -import ani.dantotsu.aniyomi.util.lang.awaitSingle +import eu.kanade.tachiyomi.util.lang.awaitSingle import rx.Observable /** diff --git a/app/src/main/java/ani/dantotsu/aniyomi/animesource/AnimeSourceFactory.kt b/app/src/main/java/eu/kanade/tachiyomi/animesource/AnimeSourceFactory.kt similarity index 84% rename from app/src/main/java/ani/dantotsu/aniyomi/animesource/AnimeSourceFactory.kt rename to app/src/main/java/eu/kanade/tachiyomi/animesource/AnimeSourceFactory.kt index 14b550fe..ca5755b6 100644 --- a/app/src/main/java/ani/dantotsu/aniyomi/animesource/AnimeSourceFactory.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/animesource/AnimeSourceFactory.kt @@ -1,4 +1,4 @@ -package ani.dantotsu.aniyomi.animesource +package eu.kanade.tachiyomi.animesource /** * A factory for creating sources at runtime. diff --git a/app/src/main/java/eu/kanade/tachiyomi/animesource/ConfigurableAnimeSource.kt b/app/src/main/java/eu/kanade/tachiyomi/animesource/ConfigurableAnimeSource.kt index 11b88011..64f9e8a2 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/animesource/ConfigurableAnimeSource.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/animesource/ConfigurableAnimeSource.kt @@ -1,7 +1,6 @@ package eu.kanade.tachiyomi.animesource -import ani.dantotsu.aniyomi.animesource.AnimeSource -import ani.dantotsu.aniyomi.PreferenceScreen +import eu.kanade.tachiyomi.PreferenceScreen interface ConfigurableAnimeSource : AnimeSource { diff --git a/app/src/main/java/ani/dantotsu/aniyomi/animesource/model/AnimeFilter.kt b/app/src/main/java/eu/kanade/tachiyomi/animesource/model/AnimeFilter.kt similarity index 100% rename from app/src/main/java/ani/dantotsu/aniyomi/animesource/model/AnimeFilter.kt rename to app/src/main/java/eu/kanade/tachiyomi/animesource/model/AnimeFilter.kt diff --git a/app/src/main/java/ani/dantotsu/aniyomi/animesource/model/AnimeFilterList.kt b/app/src/main/java/eu/kanade/tachiyomi/animesource/model/AnimeFilterList.kt similarity index 100% rename from app/src/main/java/ani/dantotsu/aniyomi/animesource/model/AnimeFilterList.kt rename to app/src/main/java/eu/kanade/tachiyomi/animesource/model/AnimeFilterList.kt diff --git a/app/src/main/java/ani/dantotsu/aniyomi/animesource/model/AnimesPage.kt b/app/src/main/java/eu/kanade/tachiyomi/animesource/model/AnimesPage.kt similarity index 100% rename from app/src/main/java/ani/dantotsu/aniyomi/animesource/model/AnimesPage.kt rename to app/src/main/java/eu/kanade/tachiyomi/animesource/model/AnimesPage.kt diff --git a/app/src/main/java/ani/dantotsu/aniyomi/animesource/model/SAnime.kt b/app/src/main/java/eu/kanade/tachiyomi/animesource/model/SAnime.kt similarity index 97% rename from app/src/main/java/ani/dantotsu/aniyomi/animesource/model/SAnime.kt rename to app/src/main/java/eu/kanade/tachiyomi/animesource/model/SAnime.kt index 607c00fc..80ee2c4d 100644 --- a/app/src/main/java/ani/dantotsu/aniyomi/animesource/model/SAnime.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/animesource/model/SAnime.kt @@ -1,6 +1,6 @@ package eu.kanade.tachiyomi.animesource.model -import ani.dantotsu.aniyomi.source.model.UpdateStrategy +import eu.kanade.tachiyomi.source.model.UpdateStrategy import java.io.Serializable interface SAnime : Serializable { diff --git a/app/src/main/java/ani/dantotsu/aniyomi/animesource/model/SAnimeImpl.kt b/app/src/main/java/eu/kanade/tachiyomi/animesource/model/SAnimeImpl.kt similarity index 90% rename from app/src/main/java/ani/dantotsu/aniyomi/animesource/model/SAnimeImpl.kt rename to app/src/main/java/eu/kanade/tachiyomi/animesource/model/SAnimeImpl.kt index 05874e39..540522fc 100644 --- a/app/src/main/java/ani/dantotsu/aniyomi/animesource/model/SAnimeImpl.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/animesource/model/SAnimeImpl.kt @@ -1,6 +1,6 @@ package eu.kanade.tachiyomi.animesource.model -import ani.dantotsu.aniyomi.source.model.UpdateStrategy +import eu.kanade.tachiyomi.source.model.UpdateStrategy class SAnimeImpl : SAnime { diff --git a/app/src/main/java/ani/dantotsu/aniyomi/animesource/model/SEpisode.kt b/app/src/main/java/eu/kanade/tachiyomi/animesource/model/SEpisode.kt similarity index 100% rename from app/src/main/java/ani/dantotsu/aniyomi/animesource/model/SEpisode.kt rename to app/src/main/java/eu/kanade/tachiyomi/animesource/model/SEpisode.kt diff --git a/app/src/main/java/ani/dantotsu/aniyomi/animesource/model/SEpisodeImpl.kt b/app/src/main/java/eu/kanade/tachiyomi/animesource/model/SEpisodeImpl.kt similarity index 100% rename from app/src/main/java/ani/dantotsu/aniyomi/animesource/model/SEpisodeImpl.kt rename to app/src/main/java/eu/kanade/tachiyomi/animesource/model/SEpisodeImpl.kt diff --git a/app/src/main/java/ani/dantotsu/aniyomi/animesource/model/Video.kt b/app/src/main/java/eu/kanade/tachiyomi/animesource/model/Video.kt similarity index 97% rename from app/src/main/java/ani/dantotsu/aniyomi/animesource/model/Video.kt rename to app/src/main/java/eu/kanade/tachiyomi/animesource/model/Video.kt index 1b077dbc..e1a8c60e 100644 --- a/app/src/main/java/ani/dantotsu/aniyomi/animesource/model/Video.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/animesource/model/Video.kt @@ -1,7 +1,7 @@ package eu.kanade.tachiyomi.animesource.model import android.net.Uri -import ani.dantotsu.aniyomi.util.network.ProgressListener +import eu.kanade.tachiyomi.network.ProgressListener import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.asStateFlow import okhttp3.Headers diff --git a/app/src/main/java/eu/kanade/tachiyomi/animesource/online/AnimeHttpSource.kt b/app/src/main/java/eu/kanade/tachiyomi/animesource/online/AnimeHttpSource.kt index 31cfaf75..f8a38196 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/animesource/online/AnimeHttpSource.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/animesource/online/AnimeHttpSource.kt @@ -1,6 +1,6 @@ package eu.kanade.tachiyomi.animesource.online -import ani.dantotsu.aniyomi.animesource.AnimeCatalogueSource +import eu.kanade.tachiyomi.animesource.AnimeCatalogueSource import eu.kanade.tachiyomi.animesource.model.AnimeFilterList import eu.kanade.tachiyomi.animesource.model.AnimesPage import eu.kanade.tachiyomi.animesource.model.SAnime diff --git a/app/src/main/java/ani/dantotsu/aniyomi/core/Constants.kt b/app/src/main/java/eu/kanade/tachiyomi/core/Constants.kt similarity index 96% rename from app/src/main/java/ani/dantotsu/aniyomi/core/Constants.kt rename to app/src/main/java/eu/kanade/tachiyomi/core/Constants.kt index 3c03bde5..f829cb7d 100644 --- a/app/src/main/java/ani/dantotsu/aniyomi/core/Constants.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/core/Constants.kt @@ -1,4 +1,4 @@ -package ani.dantotsu.aniyomi.core +package eu.kanade.tachiyomi.core object Constants { const val URL_HELP = "https://aniyomi.org/help/" diff --git a/app/src/main/java/ani/dantotsu/aniyomi/core/preference/AndroidPreference.kt b/app/src/main/java/eu/kanade/tachiyomi/core/preference/AndroidPreference.kt similarity index 98% rename from app/src/main/java/ani/dantotsu/aniyomi/core/preference/AndroidPreference.kt rename to app/src/main/java/eu/kanade/tachiyomi/core/preference/AndroidPreference.kt index 9eac6943..a62f16d7 100644 --- a/app/src/main/java/ani/dantotsu/aniyomi/core/preference/AndroidPreference.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/core/preference/AndroidPreference.kt @@ -1,4 +1,4 @@ -package ani.dantotsu.aniyomi.core.preference +package eu.kanade.tachiyomi.core.preference import android.content.SharedPreferences import android.content.SharedPreferences.Editor @@ -12,6 +12,7 @@ import kotlinx.coroutines.flow.filter import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.onStart import kotlinx.coroutines.flow.stateIn +import tachiyomi.core.preference.Preference sealed class AndroidPreference( private val preferences: SharedPreferences, diff --git a/app/src/main/java/ani/dantotsu/aniyomi/core/preference/AndroidPreferenceStore.kt b/app/src/main/java/eu/kanade/tachiyomi/core/preference/AndroidPreferenceStore.kt similarity index 77% rename from app/src/main/java/ani/dantotsu/aniyomi/core/preference/AndroidPreferenceStore.kt rename to app/src/main/java/eu/kanade/tachiyomi/core/preference/AndroidPreferenceStore.kt index 74bb4c2a..61c2e2e0 100644 --- a/app/src/main/java/ani/dantotsu/aniyomi/core/preference/AndroidPreferenceStore.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/core/preference/AndroidPreferenceStore.kt @@ -1,18 +1,20 @@ -package ani.dantotsu.aniyomi.core.preference +package eu.kanade.tachiyomi.core.preference import android.content.Context import android.content.SharedPreferences import androidx.preference.PreferenceManager -import ani.dantotsu.aniyomi.core.preference.AndroidPreference.BooleanPrimitive -import ani.dantotsu.aniyomi.core.preference.AndroidPreference.FloatPrimitive -import ani.dantotsu.aniyomi.core.preference.AndroidPreference.IntPrimitive -import ani.dantotsu.aniyomi.core.preference.AndroidPreference.LongPrimitive -import ani.dantotsu.aniyomi.core.preference.AndroidPreference.StringPrimitive -import ani.dantotsu.aniyomi.core.preference.AndroidPreference.StringSetPrimitive -import ani.dantotsu.aniyomi.core.preference.AndroidPreference.Object +import eu.kanade.tachiyomi.core.preference.AndroidPreference.BooleanPrimitive +import eu.kanade.tachiyomi.core.preference.AndroidPreference.FloatPrimitive +import eu.kanade.tachiyomi.core.preference.AndroidPreference.IntPrimitive +import eu.kanade.tachiyomi.core.preference.AndroidPreference.LongPrimitive +import eu.kanade.tachiyomi.core.preference.AndroidPreference.Object +import eu.kanade.tachiyomi.core.preference.AndroidPreference.StringPrimitive +import eu.kanade.tachiyomi.core.preference.AndroidPreference.StringSetPrimitive import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.channels.awaitClose import kotlinx.coroutines.flow.callbackFlow +import tachiyomi.core.preference.Preference +import tachiyomi.core.preference.PreferenceStore class AndroidPreferenceStore( context: Context, diff --git a/app/src/main/java/ani/dantotsu/aniyomi/data/NotificationReceiver.kt b/app/src/main/java/eu/kanade/tachiyomi/data/notification/NotificationReceiver.kt similarity index 92% rename from app/src/main/java/ani/dantotsu/aniyomi/data/NotificationReceiver.kt rename to app/src/main/java/eu/kanade/tachiyomi/data/notification/NotificationReceiver.kt index c5c30d2d..e4e9f0e6 100644 --- a/app/src/main/java/ani/dantotsu/aniyomi/data/NotificationReceiver.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/notification/NotificationReceiver.kt @@ -1,11 +1,11 @@ -package ani.dantotsu.aniyomi.data +package eu.kanade.tachiyomi.data.notification import android.app.PendingIntent import android.content.BroadcastReceiver import android.content.Context import android.content.Intent import ani.dantotsu.MainActivity -import ani.dantotsu.aniyomi.core.Constants +import eu.kanade.tachiyomi.core.Constants /** * Global [BroadcastReceiver] that runs on UI thread * Pending Broadcasts should be made from here. diff --git a/app/src/main/java/ani/dantotsu/aniyomi/data/Notifications.kt b/app/src/main/java/eu/kanade/tachiyomi/data/notification/Notifications.kt similarity index 97% rename from app/src/main/java/ani/dantotsu/aniyomi/data/Notifications.kt rename to app/src/main/java/eu/kanade/tachiyomi/data/notification/Notifications.kt index d9d6609f..6696d3cf 100644 --- a/app/src/main/java/ani/dantotsu/aniyomi/data/Notifications.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/notification/Notifications.kt @@ -1,12 +1,12 @@ -package ani.dantotsu.aniyomi.data +package eu.kanade.tachiyomi.data.notification import android.content.Context import androidx.core.app.NotificationManagerCompat import androidx.core.app.NotificationManagerCompat.IMPORTANCE_DEFAULT import androidx.core.app.NotificationManagerCompat.IMPORTANCE_HIGH import androidx.core.app.NotificationManagerCompat.IMPORTANCE_LOW -import ani.dantotsu.aniyomi.util.system.buildNotificationChannel -import ani.dantotsu.aniyomi.util.system.buildNotificationChannelGroup +import eu.kanade.tachiyomi.util.system.buildNotificationChannel +import eu.kanade.tachiyomi.util.system.buildNotificationChannelGroup /** * Class to manage the basic information of all the notifications used in the app. diff --git a/app/src/main/java/ani/dantotsu/aniyomi/util/extension/ExtensionUpdateNotifier.kt b/app/src/main/java/eu/kanade/tachiyomi/extension/ExtensionUpdateNotifier.kt similarity index 79% rename from app/src/main/java/ani/dantotsu/aniyomi/util/extension/ExtensionUpdateNotifier.kt rename to app/src/main/java/eu/kanade/tachiyomi/extension/ExtensionUpdateNotifier.kt index 3f7f7540..84acd4b3 100644 --- a/app/src/main/java/ani/dantotsu/aniyomi/util/extension/ExtensionUpdateNotifier.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/extension/ExtensionUpdateNotifier.kt @@ -1,11 +1,11 @@ -package ani.dantotsu.aniyomi.util.extension +package eu.kanade.tachiyomi.extension import android.content.Context import androidx.core.app.NotificationCompat import ani.dantotsu.R -import ani.dantotsu.aniyomi.data.NotificationReceiver -import ani.dantotsu.aniyomi.data.Notifications -import ani.dantotsu.aniyomi.util.system.notify +import eu.kanade.tachiyomi.data.notification.NotificationReceiver +import eu.kanade.tachiyomi.data.notification.Notifications +import eu.kanade.tachiyomi.util.system.notify class ExtensionUpdateNotifier(private val context: Context) { diff --git a/app/src/main/java/ani/dantotsu/aniyomi/util/extension/InstallStep.kt b/app/src/main/java/eu/kanade/tachiyomi/extension/InstallStep.kt similarity index 81% rename from app/src/main/java/ani/dantotsu/aniyomi/util/extension/InstallStep.kt rename to app/src/main/java/eu/kanade/tachiyomi/extension/InstallStep.kt index 3fd33dbb..3fba10fe 100644 --- a/app/src/main/java/ani/dantotsu/aniyomi/util/extension/InstallStep.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/extension/InstallStep.kt @@ -1,4 +1,4 @@ -package ani.dantotsu.aniyomi.util.extension +package eu.kanade.tachiyomi.extension enum class InstallStep { Idle, Pending, Downloading, Installing, Installed, Error; diff --git a/app/src/main/java/ani/dantotsu/aniyomi/anime/AnimeExtensionManager.kt b/app/src/main/java/eu/kanade/tachiyomi/extension/anime/AnimeExtensionManager.kt similarity index 82% rename from app/src/main/java/ani/dantotsu/aniyomi/anime/AnimeExtensionManager.kt rename to app/src/main/java/eu/kanade/tachiyomi/extension/anime/AnimeExtensionManager.kt index c77120f9..68bcfd88 100644 --- a/app/src/main/java/ani/dantotsu/aniyomi/anime/AnimeExtensionManager.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/extension/anime/AnimeExtensionManager.kt @@ -1,27 +1,30 @@ -package ani.dantotsu.aniyomi.anime +package eu.kanade.tachiyomi.extension.anime import android.content.Context import android.graphics.drawable.Drawable -import ani.dantotsu.aniyomi.domain.source.anime.model.AnimeSourceData -import ani.dantotsu.aniyomi.util.extension.InstallStep -import ani.dantotsu.aniyomi.util.launchNow -import ani.dantotsu.aniyomi.anime.api.AnimeExtensionGithubApi -import ani.dantotsu.aniyomi.anime.model.AnimeExtension -import ani.dantotsu.aniyomi.anime.model.AnimeLoadResult -import ani.dantotsu.aniyomi.anime.util.AnimeExtensionInstallReceiver -import ani.dantotsu.aniyomi.anime.util.AnimeExtensionInstaller -import ani.dantotsu.aniyomi.anime.util.AnimeExtensionLoader -import ani.dantotsu.aniyomi.animesource.AnimeCatalogueSource -//import eu.kanade.tachiyomi.util.preference.plusAssign -import ani.dantotsu.aniyomi.util.toast +import eu.kanade.domain.source.service.SourcePreferences +import eu.kanade.tachiyomi.extension.InstallStep +import eu.kanade.tachiyomi.extension.anime.api.AnimeExtensionGithubApi +import eu.kanade.tachiyomi.extension.anime.model.AnimeExtension +import eu.kanade.tachiyomi.extension.anime.model.AnimeLoadResult +import eu.kanade.tachiyomi.extension.anime.model.AvailableAnimeSources +import eu.kanade.tachiyomi.extension.anime.util.AnimeExtensionInstallReceiver +import eu.kanade.tachiyomi.extension.anime.util.AnimeExtensionInstaller +import eu.kanade.tachiyomi.extension.anime.util.AnimeExtensionLoader +import eu.kanade.tachiyomi.util.preference.plusAssign +import eu.kanade.tachiyomi.util.system.toast import kotlinx.coroutines.async import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.asStateFlow import logcat.LogPriority import rx.Observable -import ani.dantotsu.aniyomi.util.logcat -import ani.dantotsu.aniyomi.util.withUIContext -import ani.dantotsu.logger +import tachiyomi.core.util.lang.launchNow +import tachiyomi.core.util.lang.withUIContext +import tachiyomi.core.util.system.logcat +import tachiyomi.domain.source.anime.model.AnimeSourceData +import uy.kohesive.injekt.Injekt +import uy.kohesive.injekt.api.get +import java.util.Locale /** * The manager of anime extensions installed as another apk which extend the available sources. It handles @@ -35,6 +38,7 @@ import ani.dantotsu.logger */ class AnimeExtensionManager( private val context: Context, + private val preferences: SourcePreferences = Injekt.get(), ) { var isInitialized = false @@ -55,7 +59,7 @@ class AnimeExtensionManager( private val _installedAnimeExtensionsFlow = MutableStateFlow(emptyList()) val installedExtensionsFlow = _installedAnimeExtensionsFlow.asStateFlow() - private var subLanguagesEnabledOnFirstRun = false + private var subLanguagesEnabledOnFirstRun = preferences.enabledLanguages().isSet() fun getAppIconForSource(sourceId: Long): Drawable? { val pkgName = _installedAnimeExtensionsFlow.value.find { ext -> ext.sources.any { it.id == sourceId } }?.pkgName @@ -92,41 +96,6 @@ class AnimeExtensionManager( */ private fun initAnimeExtensions() { val animeextensions = AnimeExtensionLoader.loadExtensions(context) - logcat { "Loaded ${animeextensions.size} anime extensions" } - for (result in animeextensions) { - when (result) { - is AnimeLoadResult.Success -> { - logcat { "Loaded: ${result.extension.pkgName}" } - for(source in result.extension.sources) { - logcat { "Loaded: ${source.name}" } - } - val sc = result.extension.sources.first() - if (sc is AnimeCatalogueSource) { - //val res = sc.fetchSearchAnime(1, "spy x family", AnimeFilterList()).toBlocking().first() - /*val newScope = CoroutineScope(Dispatchers.IO) - newScope.launch { - println("fetching popular anime") - try { - val res = sc.fetchPopularAnime(1).toBlocking().first() - println("res111: $res") - } - catch (e: Exception) { - println("Exception111: $e") - } - - }*/ - - - }else{ - println("${sc.name} is not AnimeCatalogueSource") - } - } - - else -> { - logcat(LogPriority.ERROR) { "Error loading anime extension: $result." } - } - } - } _installedAnimeExtensionsFlow.value = animeextensions .filterIsInstance() @@ -147,14 +116,13 @@ class AnimeExtensionManager( api.findExtensions() } catch (e: Exception) { logcat(LogPriority.ERROR, e) - withUIContext { context.toast("Could not update anime extensions") } + withUIContext { context.toast("Failed to get extensions list") } emptyList() } enableAdditionalSubLanguages(extensions) _availableAnimeExtensionsFlow.value = extensions - println("AnimeExtensions: $extensions") updatedInstalledAnimeExtensionsStatuses(extensions) setupAvailableAnimeExtensionsSourcesDataMap(extensions) } @@ -174,7 +142,7 @@ class AnimeExtensionManager( } // Use the source lang as some aren't present on the animeextension level. - /*val availableLanguages = animeextensions + val availableLanguages = animeextensions .flatMap(AnimeExtension.Available::sources) .distinctBy(AvailableAnimeSources::lang) .map(AvailableAnimeSources::lang) @@ -185,7 +153,7 @@ class AnimeExtensionManager( it != deviceLanguage && it.startsWith(deviceLanguage) } - preferences.enabledLanguages().set(defaultLanguages + languagesToEnable)*/ + preferences.enabledLanguages().set(defaultLanguages + languagesToEnable) subLanguagesEnabledOnFirstRun = true } @@ -196,7 +164,7 @@ class AnimeExtensionManager( */ private fun updatedInstalledAnimeExtensionsStatuses(availableAnimeExtensions: List) { if (availableAnimeExtensions.isEmpty()) { - //preferences.animeExtensionUpdatesCount().set(0) + preferences.animeExtensionUpdatesCount().set(0) return } @@ -286,7 +254,7 @@ class AnimeExtensionManager( if (signature !in untrustedSignatures) return AnimeExtensionLoader.trustedSignatures += signature - //preferences.trustedSignatures() += signature + preferences.trustedSignatures() += signature val nowTrustedAnimeExtensions = _untrustedAnimeExtensionsFlow.value.filter { it.signatureHash == signature } _untrustedAnimeExtensionsFlow.value -= nowTrustedAnimeExtensions @@ -392,6 +360,6 @@ class AnimeExtensionManager( } private fun updatePendingUpdatesCount() { - //preferences.animeExtensionUpdatesCount().set(_installedAnimeExtensionsFlow.value.count { it.hasUpdate }) + preferences.animeExtensionUpdatesCount().set(_installedAnimeExtensionsFlow.value.count { it.hasUpdate }) } } diff --git a/app/src/main/java/ani/dantotsu/aniyomi/anime/api/AnimeExtensionGithubApi.kt b/app/src/main/java/eu/kanade/tachiyomi/extension/anime/api/AnimeExtensionGithubApi.kt similarity index 84% rename from app/src/main/java/ani/dantotsu/aniyomi/anime/api/AnimeExtensionGithubApi.kt rename to app/src/main/java/eu/kanade/tachiyomi/extension/anime/api/AnimeExtensionGithubApi.kt index 83925bcf..8a6ffbfa 100644 --- a/app/src/main/java/ani/dantotsu/aniyomi/anime/api/AnimeExtensionGithubApi.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/extension/anime/api/AnimeExtensionGithubApi.kt @@ -1,13 +1,12 @@ -package ani.dantotsu.aniyomi.anime.api +package eu.kanade.tachiyomi.extension.anime.api import android.content.Context -import ani.dantotsu.aniyomi.util.extension.ExtensionUpdateNotifier -import ani.dantotsu.aniyomi.anime.AnimeExtensionManager -import ani.dantotsu.aniyomi.anime.model.AnimeExtension -import ani.dantotsu.aniyomi.anime.model.AnimeLoadResult -import ani.dantotsu.aniyomi.anime.model.AvailableAnimeSources -import ani.dantotsu.aniyomi.anime.util.AnimeExtensionLoader -import ani.dantotsu.aniyomi.core.preference.PreferenceStore +import eu.kanade.tachiyomi.extension.ExtensionUpdateNotifier +import eu.kanade.tachiyomi.extension.anime.AnimeExtensionManager +import eu.kanade.tachiyomi.extension.anime.model.AnimeExtension +import eu.kanade.tachiyomi.extension.anime.model.AnimeLoadResult +import eu.kanade.tachiyomi.extension.anime.model.AvailableAnimeSources +import eu.kanade.tachiyomi.extension.anime.util.AnimeExtensionLoader import eu.kanade.tachiyomi.network.GET import eu.kanade.tachiyomi.network.NetworkHelper import eu.kanade.tachiyomi.network.awaitSuccess @@ -15,11 +14,13 @@ import eu.kanade.tachiyomi.network.parseAs import kotlinx.serialization.Serializable import kotlinx.serialization.json.Json import logcat.LogPriority -//import ani.dantotsu.aniyomi.core.preference.Preference -//import ani.dantotsu.aniyomi.core.preference.PreferenceStore -import ani.dantotsu.aniyomi.util.withIOContext -import ani.dantotsu.aniyomi.util.logcat +import tachiyomi.core.preference.Preference +import tachiyomi.core.preference.PreferenceStore +import tachiyomi.core.util.lang.withIOContext +import tachiyomi.core.util.system.logcat import uy.kohesive.injekt.injectLazy +import java.util.Date +import kotlin.time.Duration.Companion.days internal class AnimeExtensionGithubApi { @@ -28,10 +29,9 @@ internal class AnimeExtensionGithubApi { private val animeExtensionManager: AnimeExtensionManager by injectLazy() private val json: Json by injectLazy() - //private val lastExtCheck: Preference by lazy { - // preferenceStore.getLong("last_ext_check", 0) - //} - private val lastExtCheck: Long = 0 + private val lastExtCheck: Preference by lazy { + preferenceStore.getLong("last_ext_check", 0) + } private var requiresFallbackSource = false @@ -75,14 +75,14 @@ internal class AnimeExtensionGithubApi { suspend fun checkForUpdates(context: Context, fromAvailableExtensionList: Boolean = false): List? { // Limit checks to once a day at most - //if (fromAvailableExtensionList && Date().time < lastExtCheck.get() + 1.days.inWholeMilliseconds) { - // return null - //} + if (fromAvailableExtensionList && Date().time < lastExtCheck.get() + 1.days.inWholeMilliseconds) { + return null + } val extensions = if (fromAvailableExtensionList) { animeExtensionManager.availableExtensionsFlow.value } else { - findExtensions().also { }//lastExtCheck.set(Date().time) } + findExtensions().also { lastExtCheck.set(Date().time) } } val installedExtensions = AnimeExtensionLoader.loadExtensions(context) diff --git a/app/src/main/java/ani/dantotsu/aniyomi/anime/installer/InstallerAnime.kt b/app/src/main/java/eu/kanade/tachiyomi/extension/anime/installer/InstallerAnime.kt similarity index 97% rename from app/src/main/java/ani/dantotsu/aniyomi/anime/installer/InstallerAnime.kt rename to app/src/main/java/eu/kanade/tachiyomi/extension/anime/installer/InstallerAnime.kt index e41d5261..eaa6c45e 100644 --- a/app/src/main/java/ani/dantotsu/aniyomi/anime/installer/InstallerAnime.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/extension/anime/installer/InstallerAnime.kt @@ -1,4 +1,4 @@ -package ani.dantotsu.aniyomi.anime.installer +package eu.kanade.tachiyomi.extension.anime.installer import android.app.Service import android.content.BroadcastReceiver @@ -8,8 +8,8 @@ import android.content.IntentFilter import android.net.Uri import androidx.annotation.CallSuper import androidx.localbroadcastmanager.content.LocalBroadcastManager -import ani.dantotsu.aniyomi.util.extension.InstallStep -import ani.dantotsu.aniyomi.anime.AnimeExtensionManager +import eu.kanade.tachiyomi.extension.InstallStep +import eu.kanade.tachiyomi.extension.anime.AnimeExtensionManager import uy.kohesive.injekt.injectLazy import java.util.Collections import java.util.concurrent.atomic.AtomicReference diff --git a/app/src/main/java/ani/dantotsu/aniyomi/anime/installer/PackageInstallerInstallerAnime.kt b/app/src/main/java/eu/kanade/tachiyomi/extension/anime/installer/PackageInstallerInstallerAnime.kt similarity index 93% rename from app/src/main/java/ani/dantotsu/aniyomi/anime/installer/PackageInstallerInstallerAnime.kt rename to app/src/main/java/eu/kanade/tachiyomi/extension/anime/installer/PackageInstallerInstallerAnime.kt index 9141129a..fb6bfb11 100644 --- a/app/src/main/java/ani/dantotsu/aniyomi/anime/installer/PackageInstallerInstallerAnime.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/extension/anime/installer/PackageInstallerInstallerAnime.kt @@ -1,4 +1,4 @@ -package ani.dantotsu.aniyomi.anime.installer +package eu.kanade.tachiyomi.extension.anime.installer import android.app.PendingIntent import android.app.Service @@ -8,12 +8,12 @@ import android.content.Intent import android.content.IntentFilter import android.content.pm.PackageInstaller import android.os.Build -import ani.dantotsu.aniyomi.util.extension.InstallStep -import ani.dantotsu.aniyomi.util.lang.use -import ani.dantotsu.aniyomi.util.system.getParcelableExtraCompat -import ani.dantotsu.aniyomi.util.system.getUriSize +import eu.kanade.tachiyomi.extension.InstallStep +import eu.kanade.tachiyomi.util.lang.use +import eu.kanade.tachiyomi.util.system.getParcelableExtraCompat +import eu.kanade.tachiyomi.util.system.getUriSize import logcat.LogPriority -import ani.dantotsu.aniyomi.util.logcat +import tachiyomi.core.util.system.logcat class PackageInstallerInstallerAnime(private val service: Service) : InstallerAnime(service) { diff --git a/app/src/main/java/ani/dantotsu/aniyomi/anime/model/AnimeExtension.kt b/app/src/main/java/eu/kanade/tachiyomi/extension/anime/model/AnimeExtension.kt similarity index 93% rename from app/src/main/java/ani/dantotsu/aniyomi/anime/model/AnimeExtension.kt rename to app/src/main/java/eu/kanade/tachiyomi/extension/anime/model/AnimeExtension.kt index a0446979..4f552879 100644 --- a/app/src/main/java/ani/dantotsu/aniyomi/anime/model/AnimeExtension.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/extension/anime/model/AnimeExtension.kt @@ -1,8 +1,8 @@ -package ani.dantotsu.aniyomi.anime.model +package eu.kanade.tachiyomi.extension.anime.model import android.graphics.drawable.Drawable -import ani.dantotsu.aniyomi.animesource.AnimeSource -import ani.dantotsu.aniyomi.domain.source.anime.model.AnimeSourceData +import eu.kanade.tachiyomi.animesource.AnimeSource +import tachiyomi.domain.source.anime.model.AnimeSourceData sealed class AnimeExtension { diff --git a/app/src/main/java/ani/dantotsu/aniyomi/anime/model/AnimeLoadResult.kt b/app/src/main/java/eu/kanade/tachiyomi/extension/anime/model/AnimeLoadResult.kt similarity index 82% rename from app/src/main/java/ani/dantotsu/aniyomi/anime/model/AnimeLoadResult.kt rename to app/src/main/java/eu/kanade/tachiyomi/extension/anime/model/AnimeLoadResult.kt index ad1090bd..0f646c1f 100644 --- a/app/src/main/java/ani/dantotsu/aniyomi/anime/model/AnimeLoadResult.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/extension/anime/model/AnimeLoadResult.kt @@ -1,4 +1,4 @@ -package ani.dantotsu.aniyomi.anime.model +package eu.kanade.tachiyomi.extension.anime.model sealed class AnimeLoadResult { class Success(val extension: AnimeExtension.Installed) : AnimeLoadResult() diff --git a/app/src/main/java/ani/dantotsu/aniyomi/anime/util/AnimeExtensionInstallActivity.kt b/app/src/main/java/eu/kanade/tachiyomi/extension/anime/util/AnimeExtensionInstallActivity.kt similarity index 90% rename from app/src/main/java/ani/dantotsu/aniyomi/anime/util/AnimeExtensionInstallActivity.kt rename to app/src/main/java/eu/kanade/tachiyomi/extension/anime/util/AnimeExtensionInstallActivity.kt index 26a50483..692f52bb 100644 --- a/app/src/main/java/ani/dantotsu/aniyomi/anime/util/AnimeExtensionInstallActivity.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/extension/anime/util/AnimeExtensionInstallActivity.kt @@ -1,12 +1,12 @@ -package ani.dantotsu.aniyomi.anime.util +package eu.kanade.tachiyomi.extension.anime.util import android.app.Activity import android.content.Intent import android.os.Bundle -import ani.dantotsu.aniyomi.util.extension.InstallStep -import ani.dantotsu.aniyomi.anime.AnimeExtensionManager -import ani.dantotsu.aniyomi.util.system.hasMiuiPackageInstaller -import ani.dantotsu.aniyomi.util.toast +import eu.kanade.tachiyomi.extension.InstallStep +import eu.kanade.tachiyomi.extension.anime.AnimeExtensionManager +import eu.kanade.tachiyomi.util.system.hasMiuiPackageInstaller +import eu.kanade.tachiyomi.util.system.toast import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.api.get import kotlin.time.Duration.Companion.seconds diff --git a/app/src/main/java/ani/dantotsu/aniyomi/anime/util/AnimeExtensionInstallReceiver.kt b/app/src/main/java/eu/kanade/tachiyomi/extension/anime/util/AnimeExtensionInstallReceiver.kt similarity index 94% rename from app/src/main/java/ani/dantotsu/aniyomi/anime/util/AnimeExtensionInstallReceiver.kt rename to app/src/main/java/eu/kanade/tachiyomi/extension/anime/util/AnimeExtensionInstallReceiver.kt index 66c3d72d..3f1ef0ea 100644 --- a/app/src/main/java/ani/dantotsu/aniyomi/anime/util/AnimeExtensionInstallReceiver.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/extension/anime/util/AnimeExtensionInstallReceiver.kt @@ -1,18 +1,18 @@ -package ani.dantotsu.aniyomi.anime.util +package eu.kanade.tachiyomi.extension.anime.util import android.content.BroadcastReceiver import android.content.Context import android.content.Intent import android.content.IntentFilter -import ani.dantotsu.aniyomi.anime.model.AnimeExtension -import ani.dantotsu.aniyomi.anime.model.AnimeLoadResult +import eu.kanade.tachiyomi.extension.anime.model.AnimeExtension +import eu.kanade.tachiyomi.extension.anime.model.AnimeLoadResult import kotlinx.coroutines.CoroutineStart import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.GlobalScope import kotlinx.coroutines.async import logcat.LogPriority -import ani.dantotsu.aniyomi.util.launchNow -import ani.dantotsu.aniyomi.util.logcat +import tachiyomi.core.util.lang.launchNow +import tachiyomi.core.util.system.logcat /** * Broadcast receiver that listens for the system's packages installed, updated or removed, and only diff --git a/app/src/main/java/ani/dantotsu/aniyomi/anime/util/AnimeExtensionInstallService.kt b/app/src/main/java/eu/kanade/tachiyomi/extension/anime/util/AnimeExtensionInstallService.kt similarity index 80% rename from app/src/main/java/ani/dantotsu/aniyomi/anime/util/AnimeExtensionInstallService.kt rename to app/src/main/java/eu/kanade/tachiyomi/extension/anime/util/AnimeExtensionInstallService.kt index 0f9d4584..62ad05c4 100644 --- a/app/src/main/java/ani/dantotsu/aniyomi/anime/util/AnimeExtensionInstallService.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/extension/anime/util/AnimeExtensionInstallService.kt @@ -1,20 +1,20 @@ -package ani.dantotsu.aniyomi.anime.util +package eu.kanade.tachiyomi.extension.anime.util import android.app.Service import android.content.Context import android.content.Intent import android.net.Uri import android.os.IBinder +import eu.kanade.domain.base.BasePreferences import ani.dantotsu.R -import ani.dantotsu.aniyomi.domain.base.BasePreferences -import ani.dantotsu.aniyomi.data.Notifications -import ani.dantotsu.aniyomi.anime.installer.InstallerAnime -import ani.dantotsu.aniyomi.anime.installer.PackageInstallerInstallerAnime -import ani.dantotsu.aniyomi.anime.util.AnimeExtensionInstaller.Companion.EXTRA_DOWNLOAD_ID -import ani.dantotsu.aniyomi.util.system.getSerializableExtraCompat -import ani.dantotsu.aniyomi.util.system.notificationBuilder +import eu.kanade.tachiyomi.data.notification.Notifications +import eu.kanade.tachiyomi.extension.anime.installer.InstallerAnime +import eu.kanade.tachiyomi.extension.anime.installer.PackageInstallerInstallerAnime +import eu.kanade.tachiyomi.extension.anime.util.AnimeExtensionInstaller.Companion.EXTRA_DOWNLOAD_ID +import eu.kanade.tachiyomi.util.system.getSerializableExtraCompat +import eu.kanade.tachiyomi.util.system.notificationBuilder import logcat.LogPriority -import ani.dantotsu.aniyomi.util.logcat +import tachiyomi.core.util.system.logcat class AnimeExtensionInstallService : Service() { diff --git a/app/src/main/java/ani/dantotsu/aniyomi/anime/util/AnimeExtensionInstaller.kt b/app/src/main/java/eu/kanade/tachiyomi/extension/anime/util/AnimeExtensionInstaller.kt similarity index 96% rename from app/src/main/java/ani/dantotsu/aniyomi/anime/util/AnimeExtensionInstaller.kt rename to app/src/main/java/eu/kanade/tachiyomi/extension/anime/util/AnimeExtensionInstaller.kt index bc6372dd..fc555129 100644 --- a/app/src/main/java/ani/dantotsu/aniyomi/anime/util/AnimeExtensionInstaller.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/extension/anime/util/AnimeExtensionInstaller.kt @@ -1,4 +1,4 @@ -package ani.dantotsu.aniyomi.anime.util +package eu.kanade.tachiyomi.extension.anime.util import android.app.DownloadManager import android.content.BroadcastReceiver @@ -11,15 +11,15 @@ import androidx.core.content.ContextCompat import androidx.core.content.getSystemService import androidx.core.net.toUri import com.jakewharton.rxrelay.PublishRelay -import ani.dantotsu.aniyomi.util.extension.InstallStep -import ani.dantotsu.aniyomi.anime.installer.InstallerAnime -import ani.dantotsu.aniyomi.anime.model.AnimeExtension -import ani.dantotsu.aniyomi.domain.base.BasePreferences -import ani.dantotsu.aniyomi.util.storage.getUriCompat +import eu.kanade.domain.base.BasePreferences +import eu.kanade.tachiyomi.extension.InstallStep +import eu.kanade.tachiyomi.extension.anime.installer.InstallerAnime +import eu.kanade.tachiyomi.extension.anime.model.AnimeExtension +import eu.kanade.tachiyomi.util.storage.getUriCompat import logcat.LogPriority import rx.Observable import rx.android.schedulers.AndroidSchedulers -import ani.dantotsu.aniyomi.util.logcat +import tachiyomi.core.util.system.logcat import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.api.get import java.io.File diff --git a/app/src/main/java/ani/dantotsu/aniyomi/anime/util/AnimeExtensionLoader.kt b/app/src/main/java/eu/kanade/tachiyomi/extension/anime/util/AnimeExtensionLoader.kt similarity index 93% rename from app/src/main/java/ani/dantotsu/aniyomi/anime/util/AnimeExtensionLoader.kt rename to app/src/main/java/eu/kanade/tachiyomi/extension/anime/util/AnimeExtensionLoader.kt index 1356860b..745366ed 100644 --- a/app/src/main/java/ani/dantotsu/aniyomi/anime/util/AnimeExtensionLoader.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/extension/anime/util/AnimeExtensionLoader.kt @@ -1,4 +1,4 @@ -package ani.dantotsu.aniyomi.anime.util +package eu.kanade.tachiyomi.extension.anime.util import android.annotation.SuppressLint import android.content.Context @@ -7,18 +7,18 @@ import android.content.pm.PackageManager import android.os.Build import androidx.core.content.pm.PackageInfoCompat import dalvik.system.PathClassLoader -import ani.dantotsu.aniyomi.domain.source.service.SourcePreferences -import ani.dantotsu.aniyomi.animesource.AnimeCatalogueSource -import ani.dantotsu.aniyomi.animesource.AnimeSource -import ani.dantotsu.aniyomi.animesource.AnimeSourceFactory -import ani.dantotsu.aniyomi.anime.model.AnimeExtension -import ani.dantotsu.aniyomi.anime.model.AnimeLoadResult -import ani.dantotsu.aniyomi.util.lang.Hash -import ani.dantotsu.aniyomi.util.system.getApplicationIcon +import eu.kanade.domain.source.service.SourcePreferences +import eu.kanade.tachiyomi.animesource.AnimeCatalogueSource +import eu.kanade.tachiyomi.animesource.AnimeSource +import eu.kanade.tachiyomi.animesource.AnimeSourceFactory +import eu.kanade.tachiyomi.extension.anime.model.AnimeExtension +import eu.kanade.tachiyomi.extension.anime.model.AnimeLoadResult +import eu.kanade.tachiyomi.util.lang.Hash +import eu.kanade.tachiyomi.util.system.getApplicationIcon import kotlinx.coroutines.async import kotlinx.coroutines.runBlocking import logcat.LogPriority -import ani.dantotsu.aniyomi.util.logcat +import tachiyomi.core.util.system.logcat import uy.kohesive.injekt.injectLazy /** @@ -130,7 +130,7 @@ internal object AnimeExtensionLoader { if (libVersion == null || libVersion < LIB_VERSION_MIN || libVersion > LIB_VERSION_MAX) { logcat(LogPriority.WARN) { "Lib version is $libVersion, while only versions " + - "$LIB_VERSION_MIN to $LIB_VERSION_MAX are allowed" + "$LIB_VERSION_MIN to $LIB_VERSION_MAX are allowed" } return AnimeLoadResult.Error } diff --git a/app/src/main/java/eu/kanade/tachiyomi/extension/manga/MangaExtensionManager.kt b/app/src/main/java/eu/kanade/tachiyomi/extension/manga/MangaExtensionManager.kt new file mode 100644 index 00000000..549ba550 --- /dev/null +++ b/app/src/main/java/eu/kanade/tachiyomi/extension/manga/MangaExtensionManager.kt @@ -0,0 +1,365 @@ +package eu.kanade.tachiyomi.extension.manga + +import android.content.Context +import android.graphics.drawable.Drawable +import eu.kanade.domain.source.service.SourcePreferences +import eu.kanade.tachiyomi.extension.InstallStep +import eu.kanade.tachiyomi.extension.manga.api.MangaExtensionGithubApi +import eu.kanade.tachiyomi.extension.manga.model.AvailableMangaSources +import eu.kanade.tachiyomi.extension.manga.model.MangaExtension +import eu.kanade.tachiyomi.extension.manga.model.MangaLoadResult +import eu.kanade.tachiyomi.extension.manga.util.MangaExtensionInstallReceiver +import eu.kanade.tachiyomi.extension.manga.util.MangaExtensionInstaller +import eu.kanade.tachiyomi.extension.manga.util.MangaExtensionLoader +import eu.kanade.tachiyomi.util.preference.plusAssign +import eu.kanade.tachiyomi.util.system.toast +import kotlinx.coroutines.async +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.asStateFlow +import logcat.LogPriority +import rx.Observable +import tachiyomi.core.util.lang.launchNow +import tachiyomi.core.util.lang.withUIContext +import tachiyomi.core.util.system.logcat +import tachiyomi.domain.source.manga.model.MangaSourceData +import uy.kohesive.injekt.Injekt +import uy.kohesive.injekt.api.get +import java.util.Locale + +/** + * The manager of extensions installed as another apk which extend the available sources. It handles + * the retrieval of remotely available extensions as well as installing, updating and removing them. + * To avoid malicious distribution, every extension must be signed and it will only be loaded if its + * signature is trusted, otherwise the user will be prompted with a warning to trust it before being + * loaded. + * + * @param context The application context. + * @param preferences The application preferences. + */ +class MangaExtensionManager( + private val context: Context, + private val preferences: SourcePreferences = Injekt.get(), +) { + + var isInitialized = false + private set + + /** + * API where all the available extensions can be found. + */ + private val api = MangaExtensionGithubApi() + + /** + * The installer which installs, updates and uninstalls the extensions. + */ + private val installer by lazy { MangaExtensionInstaller(context) } + + private val iconMap = mutableMapOf() + + private val _installedExtensionsFlow = MutableStateFlow(emptyList()) + val installedExtensionsFlow = _installedExtensionsFlow.asStateFlow() + + private var subLanguagesEnabledOnFirstRun = preferences.enabledLanguages().isSet() + + fun getAppIconForSource(sourceId: Long): Drawable? { + val pkgName = _installedExtensionsFlow.value.find { ext -> ext.sources.any { it.id == sourceId } }?.pkgName + if (pkgName != null) { + return iconMap[pkgName] ?: iconMap.getOrPut(pkgName) { context.packageManager.getApplicationIcon(pkgName) } + } + return null + } + + private val _availableExtensionsFlow = MutableStateFlow(emptyList()) + val availableExtensionsFlow = _availableExtensionsFlow.asStateFlow() + + private var availableExtensionsSourcesData: Map = emptyMap() + + private fun setupAvailableExtensionsSourcesDataMap(extensions: List) { + if (extensions.isEmpty()) return + availableExtensionsSourcesData = extensions + .flatMap { ext -> ext.sources.map { it.toSourceData() } } + .associateBy { it.id } + } + + fun getSourceData(id: Long) = availableExtensionsSourcesData[id] + + private val _untrustedExtensionsFlow = MutableStateFlow(emptyList()) + val untrustedExtensionsFlow = _untrustedExtensionsFlow.asStateFlow() + + init { + initExtensions() + MangaExtensionInstallReceiver(InstallationListener()).register(context) + } + + /** + * Loads and registers the installed extensions. + */ + private fun initExtensions() { + val extensions = MangaExtensionLoader.loadMangaExtensions(context) + + _installedExtensionsFlow.value = extensions + .filterIsInstance() + .map { it.extension } + + _untrustedExtensionsFlow.value = extensions + .filterIsInstance() + .map { it.extension } + + isInitialized = true + } + + /** + * Finds the available extensions in the [api] and updates [availableExtensions]. + */ + suspend fun findAvailableExtensions() { + val extensions: List = try { + api.findExtensions() + } catch (e: Exception) { + logcat(LogPriority.ERROR, e) + withUIContext { context.toast("Failed to get manga extensions") } + emptyList() + } + + enableAdditionalSubLanguages(extensions) + + _availableExtensionsFlow.value = extensions + updatedInstalledExtensionsStatuses(extensions) + setupAvailableExtensionsSourcesDataMap(extensions) + } + + /** + * Enables the additional sub-languages in the app first run. This addresses + * the issue where users still need to enable some specific languages even when + * the device language is inside that major group. As an example, if a user + * has a zh device language, the app will also enable zh-Hans and zh-Hant. + * + * If the user have already changed the enabledLanguages preference value once, + * the new languages will not be added to respect the user enabled choices. + */ + private fun enableAdditionalSubLanguages(extensions: List) { + if (subLanguagesEnabledOnFirstRun || extensions.isEmpty()) { + return + } + + // Use the source lang as some aren't present on the extension level. + val availableLanguages = extensions + .flatMap(MangaExtension.Available::sources) + .distinctBy(AvailableMangaSources::lang) + .map(AvailableMangaSources::lang) + + val deviceLanguage = Locale.getDefault().language + val defaultLanguages = preferences.enabledLanguages().defaultValue() + val languagesToEnable = availableLanguages.filter { + it != deviceLanguage && it.startsWith(deviceLanguage) + } + + preferences.enabledLanguages().set(defaultLanguages + languagesToEnable) + subLanguagesEnabledOnFirstRun = true + } + + /** + * Sets the update field of the installed extensions with the given [availableExtensions]. + * + * @param availableExtensions The list of extensions given by the [api]. + */ + private fun updatedInstalledExtensionsStatuses(availableExtensions: List) { + if (availableExtensions.isEmpty()) { + preferences.mangaExtensionUpdatesCount().set(0) + return + } + + val mutInstalledExtensions = _installedExtensionsFlow.value.toMutableList() + var changed = false + + for ((index, installedExt) in mutInstalledExtensions.withIndex()) { + val pkgName = installedExt.pkgName + val availableExt = availableExtensions.find { it.pkgName == pkgName } + + if (!installedExt.isUnofficial && availableExt == null && !installedExt.isObsolete) { + mutInstalledExtensions[index] = installedExt.copy(isObsolete = true) + changed = true + } else if (availableExt != null) { + val hasUpdate = installedExt.updateExists(availableExt) + + if (installedExt.hasUpdate != hasUpdate) { + mutInstalledExtensions[index] = installedExt.copy(hasUpdate = hasUpdate) + changed = true + } + } + } + if (changed) { + _installedExtensionsFlow.value = mutInstalledExtensions + } + updatePendingUpdatesCount() + } + + /** + * Returns an observable of the installation process for the given extension. It will complete + * once the extension is installed or throws an error. The process will be canceled if + * unsubscribed before its completion. + * + * @param extension The extension to be installed. + */ + fun installExtension(extension: MangaExtension.Available): Observable { + return installer.downloadAndInstall(api.getApkUrl(extension), extension) + } + + /** + * Returns an observable of the installation process for the given extension. It will complete + * once the extension is updated or throws an error. The process will be canceled if + * unsubscribed before its completion. + * + * @param extension The extension to be updated. + */ + fun updateExtension(extension: MangaExtension.Installed): Observable { + val availableExt = _availableExtensionsFlow.value.find { it.pkgName == extension.pkgName } + ?: return Observable.empty() + return installExtension(availableExt) + } + + fun cancelInstallUpdateExtension(extension: MangaExtension) { + installer.cancelInstall(extension.pkgName) + } + + /** + * Sets to "installing" status of an extension installation. + * + * @param downloadId The id of the download. + */ + fun setInstalling(downloadId: Long) { + installer.updateInstallStep(downloadId, InstallStep.Installing) + } + + fun updateInstallStep(downloadId: Long, step: InstallStep) { + installer.updateInstallStep(downloadId, step) + } + + /** + * Uninstalls the extension that matches the given package name. + * + * @param pkgName The package name of the application to uninstall. + */ + fun uninstallExtension(pkgName: String) { + installer.uninstallApk(pkgName) + } + + /** + * Adds the given signature to the list of trusted signatures. It also loads in background the + * extensions that match this signature. + * + * @param signature The signature to whitelist. + */ + fun trustSignature(signature: String) { + val untrustedSignatures = _untrustedExtensionsFlow.value.map { it.signatureHash }.toSet() + if (signature !in untrustedSignatures) return + + MangaExtensionLoader.trustedSignatures += signature + preferences.trustedSignatures() += signature + + val nowTrustedExtensions = _untrustedExtensionsFlow.value.filter { it.signatureHash == signature } + _untrustedExtensionsFlow.value -= nowTrustedExtensions + + val ctx = context + launchNow { + nowTrustedExtensions + .map { extension -> + async { MangaExtensionLoader.loadMangaExtensionFromPkgName(ctx, extension.pkgName) } + } + .map { it.await() } + .forEach { result -> + if (result is MangaLoadResult.Success) { + registerNewExtension(result.extension) + } + } + } + } + + /** + * Registers the given extension in this and the source managers. + * + * @param extension The extension to be registered. + */ + private fun registerNewExtension(extension: MangaExtension.Installed) { + _installedExtensionsFlow.value += extension + } + + /** + * Registers the given updated extension in this and the source managers previously removing + * the outdated ones. + * + * @param extension The extension to be registered. + */ + private fun registerUpdatedExtension(extension: MangaExtension.Installed) { + val mutInstalledExtensions = _installedExtensionsFlow.value.toMutableList() + val oldExtension = mutInstalledExtensions.find { it.pkgName == extension.pkgName } + if (oldExtension != null) { + mutInstalledExtensions -= oldExtension + } + mutInstalledExtensions += extension + _installedExtensionsFlow.value = mutInstalledExtensions + } + + /** + * Unregisters the extension in this and the source managers given its package name. Note this + * method is called for every uninstalled application in the system. + * + * @param pkgName The package name of the uninstalled application. + */ + private fun unregisterExtension(pkgName: String) { + val installedExtension = _installedExtensionsFlow.value.find { it.pkgName == pkgName } + if (installedExtension != null) { + _installedExtensionsFlow.value -= installedExtension + } + val untrustedExtension = _untrustedExtensionsFlow.value.find { it.pkgName == pkgName } + if (untrustedExtension != null) { + _untrustedExtensionsFlow.value -= untrustedExtension + } + } + + /** + * Listener which receives events of the extensions being installed, updated or removed. + */ + private inner class InstallationListener : MangaExtensionInstallReceiver.Listener { + + override fun onExtensionInstalled(extension: MangaExtension.Installed) { + registerNewExtension(extension.withUpdateCheck()) + updatePendingUpdatesCount() + } + + override fun onExtensionUpdated(extension: MangaExtension.Installed) { + registerUpdatedExtension(extension.withUpdateCheck()) + updatePendingUpdatesCount() + } + + override fun onExtensionUntrusted(extension: MangaExtension.Untrusted) { + _untrustedExtensionsFlow.value += extension + } + + override fun onPackageUninstalled(pkgName: String) { + unregisterExtension(pkgName) + updatePendingUpdatesCount() + } + } + + /** + * Extension method to set the update field of an installed extension. + */ + private fun MangaExtension.Installed.withUpdateCheck(): MangaExtension.Installed { + return if (updateExists()) { + copy(hasUpdate = true) + } else { + this + } + } + + private fun MangaExtension.Installed.updateExists(availableExtension: MangaExtension.Available? = null): Boolean { + val availableExt = availableExtension ?: _availableExtensionsFlow.value.find { it.pkgName == pkgName } + if (isUnofficial || availableExt == null) return false + + return (availableExt.versionCode > versionCode || availableExt.libVersion > libVersion) + } + + private fun updatePendingUpdatesCount() { + preferences.mangaExtensionUpdatesCount().set(_installedExtensionsFlow.value.count { it.hasUpdate }) + } +} diff --git a/app/src/main/java/eu/kanade/tachiyomi/extension/manga/api/MangaExtensionGithubApi.kt b/app/src/main/java/eu/kanade/tachiyomi/extension/manga/api/MangaExtensionGithubApi.kt new file mode 100644 index 00000000..dd4540c2 --- /dev/null +++ b/app/src/main/java/eu/kanade/tachiyomi/extension/manga/api/MangaExtensionGithubApi.kt @@ -0,0 +1,186 @@ +package eu.kanade.tachiyomi.extension.manga.api + +import android.content.Context +import eu.kanade.tachiyomi.extension.ExtensionUpdateNotifier +import eu.kanade.tachiyomi.extension.manga.MangaExtensionManager +import eu.kanade.tachiyomi.extension.manga.model.AvailableMangaSources +import eu.kanade.tachiyomi.extension.manga.model.MangaExtension +import eu.kanade.tachiyomi.extension.manga.model.MangaLoadResult +import eu.kanade.tachiyomi.extension.manga.util.MangaExtensionLoader +import eu.kanade.tachiyomi.network.GET +import eu.kanade.tachiyomi.network.NetworkHelper +import eu.kanade.tachiyomi.network.awaitSuccess +import eu.kanade.tachiyomi.network.parseAs +import kotlinx.serialization.Serializable +import kotlinx.serialization.json.Json +import logcat.LogPriority +import tachiyomi.core.preference.Preference +import tachiyomi.core.preference.PreferenceStore +import tachiyomi.core.util.lang.withIOContext +import tachiyomi.core.util.system.logcat +import uy.kohesive.injekt.injectLazy +import java.util.Date +import kotlin.time.Duration.Companion.days + +internal class MangaExtensionGithubApi { + + private val networkService: NetworkHelper by injectLazy() + private val preferenceStore: PreferenceStore by injectLazy() + private val extensionManager: MangaExtensionManager by injectLazy() + private val json: Json by injectLazy() + + private val lastExtCheck: Preference by lazy { + preferenceStore.getLong("last_ext_check", 0) + } + + private var requiresFallbackSource = false + + suspend fun findExtensions(): List { + return withIOContext { + val githubResponse = if (requiresFallbackSource) { + null + } else { + try { + networkService.client + .newCall(GET("${REPO_URL_PREFIX}index.min.json")) + .awaitSuccess() + } catch (e: Throwable) { + logcat(LogPriority.ERROR, e) { "Failed to get extensions from GitHub" } + requiresFallbackSource = true + null + } + } + + val response = githubResponse ?: run { + networkService.client + .newCall(GET("${FALLBACK_REPO_URL_PREFIX}index.min.json")) + .awaitSuccess() + } + + val extensions = with(json) { + response + .parseAs>() + .toExtensions() + } + + // Sanity check - a small number of extensions probably means something broke + // with the repo generator + if (extensions.size < 100) { + throw Exception() + } + + extensions + } + } + + suspend fun checkForUpdates(context: Context, fromAvailableExtensionList: Boolean = false): List? { + // Limit checks to once a day at most + if (fromAvailableExtensionList && Date().time < lastExtCheck.get() + 1.days.inWholeMilliseconds) { + return null + } + + val extensions = if (fromAvailableExtensionList) { + extensionManager.availableExtensionsFlow.value + } else { + findExtensions().also { lastExtCheck.set(Date().time) } + } + + val installedExtensions = MangaExtensionLoader.loadMangaExtensions(context) + .filterIsInstance() + .map { it.extension } + + val extensionsWithUpdate = mutableListOf() + for (installedExt in installedExtensions) { + val pkgName = installedExt.pkgName + val availableExt = extensions.find { it.pkgName == pkgName } ?: continue + val hasUpdatedVer = availableExt.versionCode > installedExt.versionCode + val hasUpdatedLib = availableExt.libVersion > installedExt.libVersion + val hasUpdate = installedExt.isUnofficial.not() && (hasUpdatedVer || hasUpdatedLib) + if (hasUpdate) { + extensionsWithUpdate.add(installedExt) + } + } + + if (extensionsWithUpdate.isNotEmpty()) { + ExtensionUpdateNotifier(context).promptUpdates(extensionsWithUpdate.map { it.name }) + } + + return extensionsWithUpdate + } + + private fun List.toExtensions(): List { + return this + .filter { + val libVersion = it.extractLibVersion() + libVersion >= MangaExtensionLoader.LIB_VERSION_MIN && libVersion <= MangaExtensionLoader.LIB_VERSION_MAX + } + .map { + MangaExtension.Available( + name = it.name.substringAfter("Tachiyomi: "), + pkgName = it.pkg, + versionName = it.version, + versionCode = it.code, + libVersion = it.extractLibVersion(), + lang = it.lang, + isNsfw = it.nsfw == 1, + hasReadme = it.hasReadme == 1, + hasChangelog = it.hasChangelog == 1, + sources = it.sources?.toExtensionSources().orEmpty(), + apkName = it.apk, + iconUrl = "${getUrlPrefix()}icon/${it.apk.replace(".apk", ".png")}", + ) + } + } + + private fun List.toExtensionSources(): List { + return this.map { + AvailableMangaSources( + id = it.id, + lang = it.lang, + name = it.name, + baseUrl = it.baseUrl, + ) + } + } + + fun getApkUrl(extension: MangaExtension.Available): String { + return "${getUrlPrefix()}apk/${extension.apkName}" + } + + private fun getUrlPrefix(): String { + return if (requiresFallbackSource) { + FALLBACK_REPO_URL_PREFIX + } else { + REPO_URL_PREFIX + } + } + + private fun ExtensionJsonObject.extractLibVersion(): Double { + return version.substringBeforeLast('.').toDouble() + } +} + +private const val REPO_URL_PREFIX = "https://raw.githubusercontent.com/tachiyomiorg/tachiyomi-extensions/repo/" +private const val FALLBACK_REPO_URL_PREFIX = "https://gcore.jsdelivr.net/gh/tachiyomiorg/tachiyomi-extensions@repo/" + +@Serializable +private data class ExtensionJsonObject( + val name: String, + val pkg: String, + val apk: String, + val lang: String, + val code: Long, + val version: String, + val nsfw: Int, + val hasReadme: Int = 0, + val hasChangelog: Int = 0, + val sources: List?, +) + +@Serializable +private data class ExtensionSourceJsonObject( + val id: Long, + val lang: String, + val name: String, + val baseUrl: String, +) diff --git a/app/src/main/java/eu/kanade/tachiyomi/extension/manga/installer/InstallerManga.kt b/app/src/main/java/eu/kanade/tachiyomi/extension/manga/installer/InstallerManga.kt new file mode 100644 index 00000000..37c9edbb --- /dev/null +++ b/app/src/main/java/eu/kanade/tachiyomi/extension/manga/installer/InstallerManga.kt @@ -0,0 +1,170 @@ +package eu.kanade.tachiyomi.extension.manga.installer + +import android.app.Service +import android.content.BroadcastReceiver +import android.content.Context +import android.content.Intent +import android.content.IntentFilter +import android.net.Uri +import androidx.annotation.CallSuper +import androidx.localbroadcastmanager.content.LocalBroadcastManager +import eu.kanade.tachiyomi.extension.InstallStep +import eu.kanade.tachiyomi.extension.manga.MangaExtensionManager +import uy.kohesive.injekt.injectLazy +import java.util.Collections +import java.util.concurrent.atomic.AtomicReference + +/** + * Base implementation class for extension installer. To be used inside a foreground [Service]. + */ +abstract class InstallerManga(private val service: Service) { + + private val extensionManager: MangaExtensionManager by injectLazy() + + private var waitingInstall = AtomicReference(null) + private val queue = Collections.synchronizedList(mutableListOf()) + + private val cancelReceiver = object : BroadcastReceiver() { + override fun onReceive(context: Context, intent: Intent) { + val downloadId = intent.getLongExtra(EXTRA_DOWNLOAD_ID, -1).takeIf { it >= 0 } ?: return + cancelQueue(downloadId) + } + } + + /** + * Installer readiness. If false, queue check will not run. + * + * @see checkQueue + */ + abstract var ready: Boolean + + /** + * Add an item to install queue. + * + * @param downloadId Download ID as known by [MangaExtensionManager] + * @param uri Uri of APK to install + */ + fun addToQueue(downloadId: Long, uri: Uri) { + queue.add(Entry(downloadId, uri)) + checkQueue() + } + + /** + * Proceeds to install the APK of this entry inside this method. Call [continueQueue] + * when the install process for this entry is finished to continue the queue. + * + * @param entry The [Entry] of item to process + * @see continueQueue + */ + @CallSuper + open fun processEntry(entry: Entry) { + extensionManager.setInstalling(entry.downloadId) + } + + /** + * Called before queue continues. Override this to handle when the removed entry is + * currently being processed. + * + * @return true if this entry can be removed from queue. + */ + open fun cancelEntry(entry: Entry): Boolean { + return true + } + + /** + * Tells the queue to continue processing the next entry and updates the install step + * of the completed entry ([waitingInstall]) to [MangaExtensionManager]. + * + * @param resultStep new install step for the processed entry. + * @see waitingInstall + */ + fun continueQueue(resultStep: InstallStep) { + val completedEntry = waitingInstall.getAndSet(null) + if (completedEntry != null) { + extensionManager.updateInstallStep(completedEntry.downloadId, resultStep) + checkQueue() + } + } + + /** + * Checks the queue. The provided service will be stopped if the queue is empty. + * Will not be run when not ready. + * + * @see ready + */ + fun checkQueue() { + if (!ready) { + return + } + if (queue.isEmpty()) { + service.stopSelf() + return + } + val nextEntry = queue.first() + if (waitingInstall.compareAndSet(null, nextEntry)) { + queue.removeFirst() + processEntry(nextEntry) + } + } + + /** + * Call this method when the provided service is destroyed. + */ + @CallSuper + open fun onDestroy() { + LocalBroadcastManager.getInstance(service).unregisterReceiver(cancelReceiver) + queue.forEach { extensionManager.updateInstallStep(it.downloadId, InstallStep.Error) } + queue.clear() + waitingInstall.set(null) + } + + protected fun getActiveEntry(): Entry? = waitingInstall.get() + + /** + * Cancels queue for the provided download ID if exists. + * + * @param downloadId Download ID as known by [MangaExtensionManager] + */ + private fun cancelQueue(downloadId: Long) { + val waitingInstall = this.waitingInstall.get() + val toCancel = queue.find { it.downloadId == downloadId } ?: waitingInstall ?: return + if (cancelEntry(toCancel)) { + queue.remove(toCancel) + if (waitingInstall == toCancel) { + // Currently processing removed entry, continue queue + this.waitingInstall.set(null) + checkQueue() + } + extensionManager.updateInstallStep(downloadId, InstallStep.Idle) + } + } + + /** + * Install item to queue. + * + * @param downloadId Download ID as known by [MangaExtensionManager] + * @param uri Uri of APK to install + */ + data class Entry(val downloadId: Long, val uri: Uri) + + init { + val filter = IntentFilter(ACTION_CANCEL_QUEUE) + LocalBroadcastManager.getInstance(service).registerReceiver(cancelReceiver, filter) + } + + companion object { + private const val ACTION_CANCEL_QUEUE = "Installer.action.CANCEL_QUEUE" + private const val EXTRA_DOWNLOAD_ID = "Installer.extra.DOWNLOAD_ID" + + /** + * Attempts to cancel the installation entry for the provided download ID. + * + * @param downloadId Download ID as known by [MangaExtensionManager] + */ + fun cancelInstallQueue(context: Context, downloadId: Long) { + val intent = Intent(ACTION_CANCEL_QUEUE) + intent.putExtra(EXTRA_DOWNLOAD_ID, downloadId) + LocalBroadcastManager.getInstance(context).sendBroadcast(intent) + } + } +} diff --git a/app/src/main/java/eu/kanade/tachiyomi/extension/manga/installer/PackageInstallerInstallerManga.kt b/app/src/main/java/eu/kanade/tachiyomi/extension/manga/installer/PackageInstallerInstallerManga.kt new file mode 100644 index 00000000..6eb719ee --- /dev/null +++ b/app/src/main/java/eu/kanade/tachiyomi/extension/manga/installer/PackageInstallerInstallerManga.kt @@ -0,0 +1,107 @@ +package eu.kanade.tachiyomi.extension.manga.installer + +import android.app.PendingIntent +import android.app.Service +import android.content.BroadcastReceiver +import android.content.Context +import android.content.Intent +import android.content.IntentFilter +import android.content.pm.PackageInstaller +import android.os.Build +import eu.kanade.tachiyomi.extension.InstallStep +import eu.kanade.tachiyomi.util.lang.use +import eu.kanade.tachiyomi.util.system.getParcelableExtraCompat +import eu.kanade.tachiyomi.util.system.getUriSize +import logcat.LogPriority +import tachiyomi.core.util.system.logcat + +class PackageInstallerInstallerManga(private val service: Service) : InstallerManga(service) { + + private val packageInstaller = service.packageManager.packageInstaller + + private val packageActionReceiver = object : BroadcastReceiver() { + override fun onReceive(context: Context, intent: Intent) { + when (intent.getIntExtra(PackageInstaller.EXTRA_STATUS, PackageInstaller.STATUS_FAILURE)) { + PackageInstaller.STATUS_PENDING_USER_ACTION -> { + val userAction = intent.getParcelableExtraCompat(Intent.EXTRA_INTENT) + if (userAction == null) { + logcat(LogPriority.ERROR) { "Fatal error for $intent" } + continueQueue(InstallStep.Error) + return + } + userAction.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) + service.startActivity(userAction) + } + PackageInstaller.STATUS_FAILURE_ABORTED -> { + continueQueue(InstallStep.Idle) + } + PackageInstaller.STATUS_SUCCESS -> continueQueue(InstallStep.Installed) + else -> continueQueue(InstallStep.Error) + } + } + } + + private var activeSession: Pair? = null + + // Always ready + override var ready = true + + override fun processEntry(entry: Entry) { + super.processEntry(entry) + activeSession = null + try { + val installParams = PackageInstaller.SessionParams(PackageInstaller.SessionParams.MODE_FULL_INSTALL) + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { + installParams.setRequireUserAction(PackageInstaller.SessionParams.USER_ACTION_NOT_REQUIRED) + } + activeSession = entry to packageInstaller.createSession(installParams) + val fileSize = service.getUriSize(entry.uri) ?: throw IllegalStateException() + installParams.setSize(fileSize) + + val inputStream = service.contentResolver.openInputStream(entry.uri) ?: throw IllegalStateException() + val session = packageInstaller.openSession(activeSession!!.second) + val outputStream = session.openWrite(entry.downloadId.toString(), 0, fileSize) + session.use { + arrayOf(inputStream, outputStream).use { + inputStream.copyTo(outputStream) + session.fsync(outputStream) + } + + val intentSender = PendingIntent.getBroadcast( + service, + activeSession!!.second, + Intent(INSTALL_ACTION), + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) PendingIntent.FLAG_MUTABLE else 0, + ).intentSender + session.commit(intentSender) + } + } catch (e: Exception) { + logcat(LogPriority.ERROR) { "Failed to install extension ${entry.downloadId} ${entry.uri}" } + activeSession?.let { (_, sessionId) -> + packageInstaller.abandonSession(sessionId) + } + continueQueue(InstallStep.Error) + } + } + + override fun cancelEntry(entry: Entry): Boolean { + activeSession?.let { (activeEntry, sessionId) -> + if (activeEntry == entry) { + packageInstaller.abandonSession(sessionId) + return false + } + } + return true + } + + override fun onDestroy() { + service.unregisterReceiver(packageActionReceiver) + super.onDestroy() + } + + init { + service.registerReceiver(packageActionReceiver, IntentFilter(INSTALL_ACTION)) + } +} + +private const val INSTALL_ACTION = "PackageInstallerInstaller.INSTALL_ACTION" diff --git a/app/src/main/java/eu/kanade/tachiyomi/extension/manga/model/MangaExtension.kt b/app/src/main/java/eu/kanade/tachiyomi/extension/manga/model/MangaExtension.kt new file mode 100644 index 00000000..d0b6d448 --- /dev/null +++ b/app/src/main/java/eu/kanade/tachiyomi/extension/manga/model/MangaExtension.kt @@ -0,0 +1,79 @@ +package eu.kanade.tachiyomi.extension.manga.model + +import android.graphics.drawable.Drawable +import eu.kanade.tachiyomi.source.MangaSource +import tachiyomi.domain.source.manga.model.MangaSourceData + +sealed class MangaExtension { + + abstract val name: String + abstract val pkgName: String + abstract val versionName: String + abstract val versionCode: Long + abstract val libVersion: Double + abstract val lang: String? + abstract val isNsfw: Boolean + abstract val hasReadme: Boolean + abstract val hasChangelog: Boolean + + data class Installed( + override val name: String, + override val pkgName: String, + override val versionName: String, + override val versionCode: Long, + override val libVersion: Double, + override val lang: String, + override val isNsfw: Boolean, + override val hasReadme: Boolean, + override val hasChangelog: Boolean, + val pkgFactory: String?, + val sources: List, + val icon: Drawable?, + val hasUpdate: Boolean = false, + val isObsolete: Boolean = false, + val isUnofficial: Boolean = false, + ) : MangaExtension() + + data class Available( + override val name: String, + override val pkgName: String, + override val versionName: String, + override val versionCode: Long, + override val libVersion: Double, + override val lang: String, + override val isNsfw: Boolean, + override val hasReadme: Boolean, + override val hasChangelog: Boolean, + val sources: List, + val apkName: String, + val iconUrl: String, + ) : MangaExtension() + + data class Untrusted( + override val name: String, + override val pkgName: String, + override val versionName: String, + override val versionCode: Long, + override val libVersion: Double, + val signatureHash: String, + override val lang: String? = null, + override val isNsfw: Boolean = false, + override val hasReadme: Boolean = false, + override val hasChangelog: Boolean = false, + ) : MangaExtension() +} + +data class AvailableMangaSources( + val id: Long, + val lang: String, + val name: String, + val baseUrl: String, +) { + fun toSourceData(): MangaSourceData { + return MangaSourceData( + id = this.id, + lang = this.lang, + name = this.name, + ) + } +} diff --git a/app/src/main/java/eu/kanade/tachiyomi/extension/manga/model/MangaLoadResult.kt b/app/src/main/java/eu/kanade/tachiyomi/extension/manga/model/MangaLoadResult.kt new file mode 100644 index 00000000..fa5af78d --- /dev/null +++ b/app/src/main/java/eu/kanade/tachiyomi/extension/manga/model/MangaLoadResult.kt @@ -0,0 +1,7 @@ +package eu.kanade.tachiyomi.extension.manga.model + +sealed class MangaLoadResult { + class Success(val extension: MangaExtension.Installed) : MangaLoadResult() + class Untrusted(val extension: MangaExtension.Untrusted) : MangaLoadResult() + object Error : MangaLoadResult() +} diff --git a/app/src/main/java/eu/kanade/tachiyomi/extension/manga/util/MangaExtensionInstallActivity.kt b/app/src/main/java/eu/kanade/tachiyomi/extension/manga/util/MangaExtensionInstallActivity.kt new file mode 100644 index 00000000..743c8f27 --- /dev/null +++ b/app/src/main/java/eu/kanade/tachiyomi/extension/manga/util/MangaExtensionInstallActivity.kt @@ -0,0 +1,78 @@ +package eu.kanade.tachiyomi.extension.manga.util + +import android.app.Activity +import android.content.Intent +import android.os.Bundle +import eu.kanade.tachiyomi.extension.InstallStep +import eu.kanade.tachiyomi.extension.manga.MangaExtensionManager +import eu.kanade.tachiyomi.util.system.hasMiuiPackageInstaller +import eu.kanade.tachiyomi.util.system.toast +import uy.kohesive.injekt.Injekt +import uy.kohesive.injekt.api.get +import kotlin.time.Duration.Companion.seconds + +/** + * Activity used to install extensions, because we can only receive the result of the installation + * with [startActivityForResult], which we need to update the UI. + */ +class MangaExtensionInstallActivity : Activity() { + + // MIUI package installer bug workaround + private var ignoreUntil = 0L + private var ignoreResult = false + private var hasIgnoredResult = false + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + + val installIntent = Intent(Intent.ACTION_INSTALL_PACKAGE) + .setDataAndType(intent.data, intent.type) + .putExtra(Intent.EXTRA_RETURN_RESULT, true) + .setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION) + + if (hasMiuiPackageInstaller) { + ignoreResult = true + ignoreUntil = System.nanoTime() + 1.seconds.inWholeNanoseconds + } + + try { + startActivityForResult(installIntent, INSTALL_REQUEST_CODE) + } catch (error: Exception) { + // Either install package can't be found (probably bots) or there's a security exception + // with the download manager. Nothing we can workaround. + toast(error.message) + } + } + + override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { + if (ignoreResult && System.nanoTime() < ignoreUntil) { + hasIgnoredResult = true + return + } + if (requestCode == INSTALL_REQUEST_CODE) { + checkInstallationResult(resultCode) + } + finish() + } + + override fun onStart() { + super.onStart() + if (hasIgnoredResult) { + checkInstallationResult(RESULT_CANCELED) + finish() + } + } + + private fun checkInstallationResult(resultCode: Int) { + val downloadId = intent.extras!!.getLong(MangaExtensionInstaller.EXTRA_DOWNLOAD_ID) + val extensionManager = Injekt.get() + val newStep = when (resultCode) { + RESULT_OK -> InstallStep.Installed + RESULT_CANCELED -> InstallStep.Idle + else -> InstallStep.Error + } + extensionManager.updateInstallStep(downloadId, newStep) + } +} + +private const val INSTALL_REQUEST_CODE = 500 diff --git a/app/src/main/java/eu/kanade/tachiyomi/extension/manga/util/MangaExtensionInstallReceiver.kt b/app/src/main/java/eu/kanade/tachiyomi/extension/manga/util/MangaExtensionInstallReceiver.kt new file mode 100644 index 00000000..17dac9b4 --- /dev/null +++ b/app/src/main/java/eu/kanade/tachiyomi/extension/manga/util/MangaExtensionInstallReceiver.kt @@ -0,0 +1,130 @@ +package eu.kanade.tachiyomi.extension.manga.util + +import android.content.BroadcastReceiver +import android.content.Context +import android.content.Intent +import android.content.IntentFilter +import eu.kanade.tachiyomi.extension.manga.model.MangaExtension +import eu.kanade.tachiyomi.extension.manga.model.MangaLoadResult +import kotlinx.coroutines.CoroutineStart +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.GlobalScope +import kotlinx.coroutines.async +import logcat.LogPriority +import tachiyomi.core.util.lang.launchNow +import tachiyomi.core.util.system.logcat + +/** + * Broadcast receiver that listens for the system's packages installed, updated or removed, and only + * notifies the given [listener] when the package is an extension. + * + * @param listener The listener that should be notified of extension installation events. + */ +internal class MangaExtensionInstallReceiver(private val listener: Listener) : + BroadcastReceiver() { + + /** + * Registers this broadcast receiver + */ + fun register(context: Context) { + context.registerReceiver(this, filter) + } + + /** + * Returns the intent filter this receiver should subscribe to. + */ + private val filter + get() = IntentFilter().apply { + addAction(Intent.ACTION_PACKAGE_ADDED) + addAction(Intent.ACTION_PACKAGE_REPLACED) + addAction(Intent.ACTION_PACKAGE_REMOVED) + addDataScheme("package") + } + + /** + * Called when one of the events of the [filter] is received. When the package is an extension, + * it's loaded in background and it notifies the [listener] when finished. + */ + override fun onReceive(context: Context, intent: Intent?) { + if (intent == null) return + + when (intent.action) { + Intent.ACTION_PACKAGE_ADDED -> { + if (isReplacing(intent)) return + + launchNow { + when (val result = getExtensionFromIntent(context, intent)) { + is MangaLoadResult.Success -> listener.onExtensionInstalled(result.extension) + + is MangaLoadResult.Untrusted -> listener.onExtensionUntrusted(result.extension) + else -> {} + } + } + } + Intent.ACTION_PACKAGE_REPLACED -> { + launchNow { + when (val result = getExtensionFromIntent(context, intent)) { + is MangaLoadResult.Success -> listener.onExtensionUpdated(result.extension) + // Not needed as a package can't be upgraded if the signature is different + // is LoadResult.Untrusted -> {} + else -> {} + } + } + } + Intent.ACTION_PACKAGE_REMOVED -> { + if (isReplacing(intent)) return + + val pkgName = getPackageNameFromIntent(intent) + if (pkgName != null) { + listener.onPackageUninstalled(pkgName) + } + } + } + } + + /** + * Returns true if this package is performing an update. + * + * @param intent The intent that triggered the event. + */ + private fun isReplacing(intent: Intent): Boolean { + return intent.getBooleanExtra(Intent.EXTRA_REPLACING, false) + } + + /** + * Returns the extension triggered by the given intent. + * + * @param context The application context. + * @param intent The intent containing the package name of the extension. + */ + private suspend fun getExtensionFromIntent(context: Context, intent: Intent?): MangaLoadResult { + val pkgName = getPackageNameFromIntent(intent) + if (pkgName == null) { + logcat(LogPriority.WARN) { "Package name not found" } + return MangaLoadResult.Error + } + return GlobalScope.async(Dispatchers.Default, CoroutineStart.DEFAULT) { + MangaExtensionLoader.loadMangaExtensionFromPkgName( + context, + pkgName, + ) + }.await() + } + + /** + * Returns the package name of the installed, updated or removed application. + */ + private fun getPackageNameFromIntent(intent: Intent?): String? { + return intent?.data?.encodedSchemeSpecificPart ?: return null + } + + /** + * Listener that receives extension installation events. + */ + interface Listener { + fun onExtensionInstalled(extension: MangaExtension.Installed) + fun onExtensionUpdated(extension: MangaExtension.Installed) + fun onExtensionUntrusted(extension: MangaExtension.Untrusted) + fun onPackageUninstalled(pkgName: String) + } +} diff --git a/app/src/main/java/eu/kanade/tachiyomi/extension/manga/util/MangaExtensionInstallService.kt b/app/src/main/java/eu/kanade/tachiyomi/extension/manga/util/MangaExtensionInstallService.kt new file mode 100644 index 00000000..e4f01c2b --- /dev/null +++ b/app/src/main/java/eu/kanade/tachiyomi/extension/manga/util/MangaExtensionInstallService.kt @@ -0,0 +1,82 @@ +package eu.kanade.tachiyomi.extension.manga.util + +import android.app.Service +import android.content.Context +import android.content.Intent +import android.net.Uri +import android.os.IBinder +import ani.dantotsu.R +import eu.kanade.domain.base.BasePreferences +import eu.kanade.tachiyomi.data.notification.Notifications +import eu.kanade.tachiyomi.extension.manga.installer.InstallerManga +import eu.kanade.tachiyomi.extension.manga.installer.PackageInstallerInstallerManga +import eu.kanade.tachiyomi.extension.manga.util.MangaExtensionInstaller.Companion.EXTRA_DOWNLOAD_ID +import eu.kanade.tachiyomi.util.system.getSerializableExtraCompat +import eu.kanade.tachiyomi.util.system.notificationBuilder +import logcat.LogPriority +import tachiyomi.core.util.system.logcat + +class MangaExtensionInstallService : Service() { + + private var installer: InstallerManga? = null + + override fun onCreate() { + val notification = notificationBuilder(Notifications.CHANNEL_EXTENSIONS_UPDATE) { + setSmallIcon(R.drawable.spinner_icon) + setAutoCancel(false) + setOngoing(true) + setShowWhen(false) + setContentTitle("Installing manga extension...") + setProgress(100, 100, true) + }.build() + startForeground(Notifications.ID_EXTENSION_INSTALLER, notification) + } + + override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int { + val uri = intent?.data + val id = intent?.getLongExtra(EXTRA_DOWNLOAD_ID, -1)?.takeIf { it != -1L } + val installerUsed = intent?.getSerializableExtraCompat( + EXTRA_INSTALLER, + ) + if (uri == null || id == null || installerUsed == null) { + stopSelf() + return START_NOT_STICKY + } + + if (installer == null) { + installer = when (installerUsed) { + BasePreferences.ExtensionInstaller.PACKAGEINSTALLER -> PackageInstallerInstallerManga(this) + else -> { + logcat(LogPriority.ERROR) { "Not implemented for installer $installerUsed" } + stopSelf() + return START_NOT_STICKY + } + } + } + installer!!.addToQueue(id, uri) + return START_NOT_STICKY + } + + override fun onDestroy() { + installer?.onDestroy() + installer = null + } + + override fun onBind(i: Intent?): IBinder? = null + + companion object { + private const val EXTRA_INSTALLER = "EXTRA_INSTALLER" + + fun getIntent( + context: Context, + downloadId: Long, + uri: Uri, + installer: BasePreferences.ExtensionInstaller, + ): Intent { + return Intent(context, MangaExtensionInstallService::class.java) + .setDataAndType(uri, MangaExtensionInstaller.APK_MIME) + .putExtra(EXTRA_DOWNLOAD_ID, downloadId) + .putExtra(EXTRA_INSTALLER, installer) + } + } +} diff --git a/app/src/main/java/eu/kanade/tachiyomi/extension/manga/util/MangaExtensionInstaller.kt b/app/src/main/java/eu/kanade/tachiyomi/extension/manga/util/MangaExtensionInstaller.kt new file mode 100644 index 00000000..88b2b422 --- /dev/null +++ b/app/src/main/java/eu/kanade/tachiyomi/extension/manga/util/MangaExtensionInstaller.kt @@ -0,0 +1,266 @@ +package eu.kanade.tachiyomi.extension.manga.util + +import android.app.DownloadManager +import android.content.BroadcastReceiver +import android.content.Context +import android.content.Intent +import android.content.IntentFilter +import android.net.Uri +import android.os.Environment +import androidx.core.content.ContextCompat +import androidx.core.content.getSystemService +import androidx.core.net.toUri +import com.jakewharton.rxrelay.PublishRelay +import eu.kanade.domain.base.BasePreferences +import eu.kanade.tachiyomi.extension.InstallStep +import eu.kanade.tachiyomi.extension.manga.installer.InstallerManga +import eu.kanade.tachiyomi.extension.manga.model.MangaExtension +import eu.kanade.tachiyomi.util.storage.getUriCompat +import logcat.LogPriority +import rx.Observable +import rx.android.schedulers.AndroidSchedulers +import tachiyomi.core.util.system.logcat +import uy.kohesive.injekt.Injekt +import uy.kohesive.injekt.api.get +import java.io.File +import java.util.concurrent.TimeUnit + +/** + * The installer which installs, updates and uninstalls the extensions. + * + * @param context The application context. + */ +internal class MangaExtensionInstaller(private val context: Context) { + + /** + * The system's download manager + */ + private val downloadManager = context.getSystemService()!! + + /** + * The broadcast receiver which listens to download completion events. + */ + private val downloadReceiver = DownloadCompletionReceiver() + + /** + * The currently requested downloads, with the package name (unique id) as key, and the id + * returned by the download manager. + */ + private val activeDownloads = hashMapOf() + + /** + * Relay used to notify the installation step of every download. + */ + private val downloadsRelay = PublishRelay.create>() + + private val extensionInstaller = Injekt.get().extensionInstaller() + + /** + * Adds the given extension to the downloads queue and returns an observable containing its + * step in the installation process. + * + * @param url The url of the apk. + * @param extension The extension to install. + */ + fun downloadAndInstall(url: String, extension: MangaExtension) = Observable.defer { + val pkgName = extension.pkgName + + val oldDownload = activeDownloads[pkgName] + if (oldDownload != null) { + deleteDownload(pkgName) + } + + // Register the receiver after removing (and unregistering) the previous download + downloadReceiver.register() + + val downloadUri = url.toUri() + val request = DownloadManager.Request(downloadUri) + .setTitle(extension.name) + .setMimeType(APK_MIME) + .setDestinationInExternalFilesDir(context, Environment.DIRECTORY_DOWNLOADS, downloadUri.lastPathSegment) + .setNotificationVisibility(DownloadManager.Request.VISIBILITY_VISIBLE_NOTIFY_COMPLETED) + + val id = downloadManager.enqueue(request) + activeDownloads[pkgName] = id + + downloadsRelay.filter { it.first == id } + .map { it.second } + // Poll download status + .mergeWith(pollStatus(id)) + // Stop when the application is installed or errors + .takeUntil { it.isCompleted() } + // Always notify on main thread + .observeOn(AndroidSchedulers.mainThread()) + // Always remove the download when unsubscribed + .doOnUnsubscribe { deleteDownload(pkgName) } + } + + /** + * Returns an observable that polls the given download id for its status every second, as the + * manager doesn't have any notification system. It'll stop once the download finishes. + * + * @param id The id of the download to poll. + */ + private fun pollStatus(id: Long): Observable { + val query = DownloadManager.Query().setFilterById(id) + + return Observable.interval(0, 1, TimeUnit.SECONDS) + // Get the current download status + .map { + downloadManager.query(query).use { cursor -> + cursor.moveToFirst() + cursor.getInt(cursor.getColumnIndexOrThrow(DownloadManager.COLUMN_STATUS)) + } + } + // Ignore duplicate results + .distinctUntilChanged() + // Stop polling when the download fails or finishes + .takeUntil { it == DownloadManager.STATUS_SUCCESSFUL || it == DownloadManager.STATUS_FAILED } + // Map to our model + .flatMap { status -> + when (status) { + DownloadManager.STATUS_PENDING -> Observable.just(InstallStep.Pending) + DownloadManager.STATUS_RUNNING -> Observable.just(InstallStep.Downloading) + else -> Observable.empty() + } + } + } + + /** + * Starts an intent to install the extension at the given uri. + * + * @param uri The uri of the extension to install. + */ + fun installApk(downloadId: Long, uri: Uri) { + when (val installer = extensionInstaller.get()) { + BasePreferences.ExtensionInstaller.LEGACY -> { + val intent = Intent(context, MangaExtensionInstallActivity::class.java) + .setDataAndType(uri, APK_MIME) + .putExtra(EXTRA_DOWNLOAD_ID, downloadId) + .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_GRANT_READ_URI_PERMISSION) + + context.startActivity(intent) + } + else -> { + val intent = + MangaExtensionInstallService.getIntent(context, downloadId, uri, installer) + ContextCompat.startForegroundService(context, intent) + } + } + } + + /** + * Cancels extension install and remove from download manager and installer. + */ + fun cancelInstall(pkgName: String) { + val downloadId = activeDownloads.remove(pkgName) ?: return + downloadManager.remove(downloadId) + InstallerManga.cancelInstallQueue(context, downloadId) + } + + /** + * Starts an intent to uninstall the extension by the given package name. + * + * @param pkgName The package name of the extension to uninstall + */ + fun uninstallApk(pkgName: String) { + val intent = Intent(Intent.ACTION_UNINSTALL_PACKAGE, "package:$pkgName".toUri()) + .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK) + + context.startActivity(intent) + } + + /** + * Sets the step of the installation of an extension. + * + * @param downloadId The id of the download. + * @param step New install step. + */ + fun updateInstallStep(downloadId: Long, step: InstallStep) { + downloadsRelay.call(downloadId to step) + } + + /** + * Deletes the download for the given package name. + * + * @param pkgName The package name of the download to delete. + */ + private fun deleteDownload(pkgName: String) { + val downloadId = activeDownloads.remove(pkgName) + if (downloadId != null) { + downloadManager.remove(downloadId) + } + if (activeDownloads.isEmpty()) { + downloadReceiver.unregister() + } + } + + /** + * Receiver that listens to download status events. + */ + private inner class DownloadCompletionReceiver : BroadcastReceiver() { + + /** + * Whether this receiver is currently registered. + */ + private var isRegistered = false + + /** + * Registers this receiver if it's not already. + */ + fun register() { + if (isRegistered) return + isRegistered = true + + val filter = IntentFilter(DownloadManager.ACTION_DOWNLOAD_COMPLETE) + context.registerReceiver(this, filter) + } + + /** + * Unregisters this receiver if it's not already. + */ + fun unregister() { + if (!isRegistered) return + isRegistered = false + + context.unregisterReceiver(this) + } + + /** + * Called when a download event is received. It looks for the download in the current active + * downloads and notifies its installation step. + */ + override fun onReceive(context: Context, intent: Intent?) { + val id = intent?.getLongExtra(DownloadManager.EXTRA_DOWNLOAD_ID, 0) ?: return + + // Avoid events for downloads we didn't request + if (id !in activeDownloads.values) return + + val uri = downloadManager.getUriForDownloadedFile(id) + + // Set next installation step + if (uri == null) { + logcat(LogPriority.ERROR) { "Couldn't locate downloaded APK" } + downloadsRelay.call(id to InstallStep.Error) + return + } + + val query = DownloadManager.Query().setFilterById(id) + downloadManager.query(query).use { cursor -> + if (cursor.moveToFirst()) { + val localUri = cursor.getString( + cursor.getColumnIndexOrThrow(DownloadManager.COLUMN_LOCAL_URI), + ).removePrefix(FILE_SCHEME) + + installApk(id, File(localUri).getUriCompat(context)) + } + } + } + } + + companion object { + const val APK_MIME = "application/vnd.android.package-archive" + const val EXTRA_DOWNLOAD_ID = "ExtensionInstaller.extra.DOWNLOAD_ID" + const val FILE_SCHEME = "file://" + } +} diff --git a/app/src/main/java/eu/kanade/tachiyomi/extension/manga/util/MangaExtensionLoader.kt b/app/src/main/java/eu/kanade/tachiyomi/extension/manga/util/MangaExtensionLoader.kt new file mode 100644 index 00000000..e5f1e5a0 --- /dev/null +++ b/app/src/main/java/eu/kanade/tachiyomi/extension/manga/util/MangaExtensionLoader.kt @@ -0,0 +1,232 @@ +package eu.kanade.tachiyomi.extension.manga.util + +import android.annotation.SuppressLint +import android.content.Context +import android.content.pm.PackageInfo +import android.content.pm.PackageManager +import android.os.Build +import androidx.core.content.pm.PackageInfoCompat +import dalvik.system.PathClassLoader +import eu.kanade.domain.source.service.SourcePreferences +import eu.kanade.tachiyomi.extension.manga.model.MangaExtension +import eu.kanade.tachiyomi.extension.manga.model.MangaLoadResult +import eu.kanade.tachiyomi.source.CatalogueSource +import eu.kanade.tachiyomi.source.MangaSource +import eu.kanade.tachiyomi.source.SourceFactory +import eu.kanade.tachiyomi.util.lang.Hash +import eu.kanade.tachiyomi.util.system.getApplicationIcon +import kotlinx.coroutines.async +import kotlinx.coroutines.runBlocking +import logcat.LogPriority +import tachiyomi.core.util.system.logcat +import uy.kohesive.injekt.injectLazy + +/** + * Class that handles the loading of the extensions installed in the system. + */ +@SuppressLint("PackageManagerGetSignatures") +internal object MangaExtensionLoader { + + private val preferences: SourcePreferences by injectLazy() + private val loadNsfwSource by lazy { + preferences.showNsfwSource().get() + } + + private const val EXTENSION_FEATURE = "tachiyomi.extension" + private const val METADATA_SOURCE_CLASS = "tachiyomi.extension.class" + private const val METADATA_SOURCE_FACTORY = "tachiyomi.extension.factory" + private const val METADATA_NSFW = "tachiyomi.extension.nsfw" + private const val METADATA_HAS_README = "tachiyomi.extension.hasReadme" + private const val METADATA_HAS_CHANGELOG = "tachiyomi.extension.hasChangelog" + const val LIB_VERSION_MIN = 1.2 + const val LIB_VERSION_MAX = 1.5 + + private const val PACKAGE_FLAGS = PackageManager.GET_CONFIGURATIONS or PackageManager.GET_SIGNATURES + + // inorichi's key + private const val officialSignature = "7ce04da7773d41b489f4693a366c36bcd0a11fc39b547168553c285bd7348e23" + + /** + * List of the trusted signatures. + */ + var trustedSignatures = mutableSetOf() + preferences.trustedSignatures().get() + officialSignature + + /** + * Return a list of all the installed extensions initialized concurrently. + * + * @param context The application context. + */ + fun loadMangaExtensions(context: Context): List { + val pkgManager = context.packageManager + + @Suppress("DEPRECATION") + val installedPkgs = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { + pkgManager.getInstalledPackages(PackageManager.PackageInfoFlags.of(PACKAGE_FLAGS.toLong())) + } else { + pkgManager.getInstalledPackages(PACKAGE_FLAGS) + } + + val extPkgs = installedPkgs.filter { isPackageAnExtension(it) } + + if (extPkgs.isEmpty()) return emptyList() + + // Load each extension concurrently and wait for completion + return runBlocking { + val deferred = extPkgs.map { + async { loadMangaExtension(context, it.packageName, it) } + } + deferred.map { it.await() } + } + } + + /** + * Attempts to load an extension from the given package name. It checks if the extension + * contains the required feature flag before trying to load it. + */ + fun loadMangaExtensionFromPkgName(context: Context, pkgName: String): MangaLoadResult { + val pkgInfo = try { + context.packageManager.getPackageInfo(pkgName, PACKAGE_FLAGS) + } catch (error: PackageManager.NameNotFoundException) { + // Unlikely, but the package may have been uninstalled at this point + logcat(LogPriority.ERROR, error) + return MangaLoadResult.Error + } + if (!isPackageAnExtension(pkgInfo)) { + logcat(LogPriority.WARN) { "Tried to load a package that wasn't a extension ($pkgName)" } + return MangaLoadResult.Error + } + return loadMangaExtension(context, pkgName, pkgInfo) + } + + /** + * Loads an extension given its package name. + * + * @param context The application context. + * @param pkgName The package name of the extension to load. + * @param pkgInfo The package info of the extension. + */ + private fun loadMangaExtension(context: Context, pkgName: String, pkgInfo: PackageInfo): MangaLoadResult { + val pkgManager = context.packageManager + + val appInfo = try { + pkgManager.getApplicationInfo(pkgName, PackageManager.GET_META_DATA) + } catch (error: PackageManager.NameNotFoundException) { + // Unlikely, but the package may have been uninstalled at this point + logcat(LogPriority.ERROR, error) + return MangaLoadResult.Error + } + + val extName = pkgManager.getApplicationLabel(appInfo).toString().substringAfter("Tachiyomi: ") + val versionName = pkgInfo.versionName + val versionCode = PackageInfoCompat.getLongVersionCode(pkgInfo) + + if (versionName.isNullOrEmpty()) { + logcat(LogPriority.WARN) { "Missing versionName for extension $extName" } + return MangaLoadResult.Error + } + + // Validate lib version + val libVersion = versionName.substringBeforeLast('.').toDoubleOrNull() + if (libVersion == null || libVersion < LIB_VERSION_MIN || libVersion > LIB_VERSION_MAX) { + logcat(LogPriority.WARN) { + "Lib version is $libVersion, while only versions " + + "$LIB_VERSION_MIN to $LIB_VERSION_MAX are allowed" + } + return MangaLoadResult.Error + } + + val signatureHash = getSignatureHash(pkgInfo) + + if (signatureHash == null) { + logcat(LogPriority.WARN) { "Package $pkgName isn't signed" } + return MangaLoadResult.Error + } else if (signatureHash !in trustedSignatures) { + val extension = MangaExtension.Untrusted(extName, pkgName, versionName, versionCode, libVersion, signatureHash) + logcat(LogPriority.WARN) { "Extension $pkgName isn't trusted" } + return MangaLoadResult.Untrusted(extension) + } + + val isNsfw = appInfo.metaData.getInt(METADATA_NSFW) == 1 + if (!loadNsfwSource && isNsfw) { + logcat(LogPriority.WARN) { "NSFW extension $pkgName not allowed" } + return MangaLoadResult.Error + } + + val hasReadme = appInfo.metaData.getInt(METADATA_HAS_README, 0) == 1 + val hasChangelog = appInfo.metaData.getInt(METADATA_HAS_CHANGELOG, 0) == 1 + + val classLoader = PathClassLoader(appInfo.sourceDir, null, context.classLoader) + + val sources = appInfo.metaData.getString(METADATA_SOURCE_CLASS)!! + .split(";") + .map { + val sourceClass = it.trim() + if (sourceClass.startsWith(".")) { + pkgInfo.packageName + sourceClass + } else { + sourceClass + } + } + .flatMap { + try { + when (val obj = Class.forName(it, false, classLoader).newInstance()) { + is MangaSource -> listOf(obj) + is SourceFactory -> obj.createSources() + else -> throw Exception("Unknown source class type! ${obj.javaClass}") + } + } catch (e: Throwable) { + logcat(LogPriority.ERROR, e) { "Extension load error: $extName ($it)" } + return MangaLoadResult.Error + } + } + + val langs = sources.filterIsInstance() + .map { it.lang } + .toSet() + val lang = when (langs.size) { + 0 -> "" + 1 -> langs.first() + else -> "all" + } + + val extension = MangaExtension.Installed( + name = extName, + pkgName = pkgName, + versionName = versionName, + versionCode = versionCode, + libVersion = libVersion, + lang = lang, + isNsfw = isNsfw, + hasReadme = hasReadme, + hasChangelog = hasChangelog, + sources = sources, + pkgFactory = appInfo.metaData.getString(METADATA_SOURCE_FACTORY), + isUnofficial = signatureHash != officialSignature, + icon = context.getApplicationIcon(pkgName), + ) + return MangaLoadResult.Success(extension) + } + + /** + * Returns true if the given package is an extension. + * + * @param pkgInfo The package info of the application. + */ + private fun isPackageAnExtension(pkgInfo: PackageInfo): Boolean { + return pkgInfo.reqFeatures.orEmpty().any { it.name == EXTENSION_FEATURE } + } + + /** + * Returns the signature hash of the package or null if it's not signed. + * + * @param pkgInfo The package info of the application. + */ + private fun getSignatureHash(pkgInfo: PackageInfo): String? { + val signatures = pkgInfo.signatures + return if (signatures != null && signatures.isNotEmpty()) { + Hash.sha256(signatures.first().toByteArray()) + } else { + null + } + } +} diff --git a/app/src/main/java/ani/dantotsu/aniyomi/util/network/AndroidCookieJar.kt b/app/src/main/java/eu/kanade/tachiyomi/network/AndroidCookieJar.kt similarity index 97% rename from app/src/main/java/ani/dantotsu/aniyomi/util/network/AndroidCookieJar.kt rename to app/src/main/java/eu/kanade/tachiyomi/network/AndroidCookieJar.kt index bfaf034e..f9322e84 100644 --- a/app/src/main/java/ani/dantotsu/aniyomi/util/network/AndroidCookieJar.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/network/AndroidCookieJar.kt @@ -1,4 +1,4 @@ -package ani.dantotsu.aniyomi.util.network +package eu.kanade.tachiyomi.network import android.webkit.CookieManager import okhttp3.Cookie diff --git a/app/src/main/java/ani/dantotsu/aniyomi/util/network/DohProviders.kt b/app/src/main/java/eu/kanade/tachiyomi/network/DohProviders.kt similarity index 99% rename from app/src/main/java/ani/dantotsu/aniyomi/util/network/DohProviders.kt rename to app/src/main/java/eu/kanade/tachiyomi/network/DohProviders.kt index e77c1871..95f448f1 100644 --- a/app/src/main/java/ani/dantotsu/aniyomi/util/network/DohProviders.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/network/DohProviders.kt @@ -1,4 +1,4 @@ -package ani.dantotsu.aniyomi.util.network +package eu.kanade.tachiyomi.network import okhttp3.HttpUrl.Companion.toHttpUrl import okhttp3.OkHttpClient diff --git a/app/src/main/java/ani/dantotsu/aniyomi/util/network/NetworkHelper.kt b/app/src/main/java/eu/kanade/tachiyomi/network/NetworkHelper.kt similarity index 83% rename from app/src/main/java/ani/dantotsu/aniyomi/util/network/NetworkHelper.kt rename to app/src/main/java/eu/kanade/tachiyomi/network/NetworkHelper.kt index 8368289c..8f5afb9f 100644 --- a/app/src/main/java/ani/dantotsu/aniyomi/util/network/NetworkHelper.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/network/NetworkHelper.kt @@ -1,12 +1,12 @@ package eu.kanade.tachiyomi.network import android.content.Context -import ani.dantotsu.aniyomi.util.network.AndroidCookieJar -import ani.dantotsu.aniyomi.util.network.PREF_DOH_CLOUDFLARE -import ani.dantotsu.aniyomi.util.network.PREF_DOH_GOOGLE -import ani.dantotsu.aniyomi.util.network.dohCloudflare -import ani.dantotsu.aniyomi.util.network.dohGoogle -import ani.dantotsu.aniyomi.util.network.interceptor.CloudflareInterceptor +import eu.kanade.tachiyomi.network.AndroidCookieJar +import eu.kanade.tachiyomi.network.PREF_DOH_CLOUDFLARE +import eu.kanade.tachiyomi.network.PREF_DOH_GOOGLE +import eu.kanade.tachiyomi.network.dohCloudflare +import eu.kanade.tachiyomi.network.dohGoogle +import eu.kanade.tachiyomi.network.interceptor.CloudflareInterceptor import eu.kanade.tachiyomi.network.interceptor.UncaughtExceptionInterceptor import eu.kanade.tachiyomi.network.interceptor.UserAgentInterceptor import okhttp3.Cache @@ -75,5 +75,5 @@ class NetworkHelper( .build() } - fun defaultUserAgentProvider() = "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:110.0) Gecko/20100101 Firefox/110.0"//preferences.defaultUserAgent().get().trim() + fun defaultUserAgentProvider() = "Mozilla/5.0 (Linux; Android %s; %s) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/100.0.4896.127 Mobile Safari/537.36"//preferences.defaultUserAgent().get().trim() } diff --git a/app/src/main/java/ani/dantotsu/aniyomi/util/network/OkHttpExtensions.kt b/app/src/main/java/eu/kanade/tachiyomi/network/OkHttpExtensions.kt similarity index 97% rename from app/src/main/java/ani/dantotsu/aniyomi/util/network/OkHttpExtensions.kt rename to app/src/main/java/eu/kanade/tachiyomi/network/OkHttpExtensions.kt index 5e241e03..aa523e87 100644 --- a/app/src/main/java/ani/dantotsu/aniyomi/util/network/OkHttpExtensions.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/network/OkHttpExtensions.kt @@ -1,7 +1,7 @@ package eu.kanade.tachiyomi.network -import ani.dantotsu.aniyomi.util.network.ProgressListener -import ani.dantotsu.aniyomi.util.network.ProgressResponseBody +import eu.kanade.tachiyomi.network.ProgressListener +import eu.kanade.tachiyomi.network.ProgressResponseBody import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.suspendCancellableCoroutine import kotlinx.serialization.DeserializationStrategy diff --git a/app/src/main/java/ani/dantotsu/aniyomi/util/network/ProgressListener.kt b/app/src/main/java/eu/kanade/tachiyomi/network/ProgressListener.kt similarity index 70% rename from app/src/main/java/ani/dantotsu/aniyomi/util/network/ProgressListener.kt rename to app/src/main/java/eu/kanade/tachiyomi/network/ProgressListener.kt index 9347fbad..2e219895 100644 --- a/app/src/main/java/ani/dantotsu/aniyomi/util/network/ProgressListener.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/network/ProgressListener.kt @@ -1,4 +1,4 @@ -package ani.dantotsu.aniyomi.util.network +package eu.kanade.tachiyomi.network interface ProgressListener { fun update(bytesRead: Long, contentLength: Long, done: Boolean) diff --git a/app/src/main/java/ani/dantotsu/aniyomi/util/network/ProgressResponseBody.kt b/app/src/main/java/eu/kanade/tachiyomi/network/ProgressResponseBody.kt similarity index 96% rename from app/src/main/java/ani/dantotsu/aniyomi/util/network/ProgressResponseBody.kt rename to app/src/main/java/eu/kanade/tachiyomi/network/ProgressResponseBody.kt index bd5f3b31..72248f17 100644 --- a/app/src/main/java/ani/dantotsu/aniyomi/util/network/ProgressResponseBody.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/network/ProgressResponseBody.kt @@ -1,4 +1,4 @@ -package ani.dantotsu.aniyomi.util.network +package eu.kanade.tachiyomi.network import okhttp3.MediaType import okhttp3.ResponseBody diff --git a/app/src/main/java/ani/dantotsu/aniyomi/util/network/Requests.kt b/app/src/main/java/eu/kanade/tachiyomi/network/Requests.kt similarity index 95% rename from app/src/main/java/ani/dantotsu/aniyomi/util/network/Requests.kt rename to app/src/main/java/eu/kanade/tachiyomi/network/Requests.kt index 6adb0de8..43b1d467 100644 --- a/app/src/main/java/ani/dantotsu/aniyomi/util/network/Requests.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/network/Requests.kt @@ -18,7 +18,9 @@ fun GET( headers: Headers = DEFAULT_HEADERS, cache: CacheControl = DEFAULT_CACHE_CONTROL, ): Request { - return GET(url.toHttpUrl(), headers, cache) + val nUrl = url.toHttpUrl() + val g = GET(nUrl, headers, cache) + return g } /** diff --git a/app/src/main/java/ani/dantotsu/aniyomi/util/network/interceptor/CloudflareInterceptor.kt b/app/src/main/java/eu/kanade/tachiyomi/network/interceptor/CloudflareInterceptor.kt similarity index 94% rename from app/src/main/java/ani/dantotsu/aniyomi/util/network/interceptor/CloudflareInterceptor.kt rename to app/src/main/java/eu/kanade/tachiyomi/network/interceptor/CloudflareInterceptor.kt index 97b55b79..2d8202ea 100644 --- a/app/src/main/java/ani/dantotsu/aniyomi/util/network/interceptor/CloudflareInterceptor.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/network/interceptor/CloudflareInterceptor.kt @@ -1,14 +1,14 @@ -package ani.dantotsu.aniyomi.util.network.interceptor +package eu.kanade.tachiyomi.network.interceptor import android.annotation.SuppressLint import android.content.Context import android.webkit.WebView import android.widget.Toast import androidx.core.content.ContextCompat -import ani.dantotsu.aniyomi.util.network.AndroidCookieJar -import ani.dantotsu.aniyomi.util.system.WebViewClientCompat -import ani.dantotsu.aniyomi.util.system.isOutdated -import ani.dantotsu.aniyomi.util.toast +import eu.kanade.tachiyomi.network.AndroidCookieJar +import eu.kanade.tachiyomi.util.system.WebViewClientCompat +import eu.kanade.tachiyomi.util.system.isOutdated +import eu.kanade.tachiyomi.util.system.toast import okhttp3.Cookie import okhttp3.HttpUrl.Companion.toHttpUrl import okhttp3.Interceptor diff --git a/app/src/main/java/eu/kanade/tachiyomi/network/interceptor/RateLimitInterceptor.kt b/app/src/main/java/eu/kanade/tachiyomi/network/interceptor/RateLimitInterceptor.kt new file mode 100644 index 00000000..6b101e35 --- /dev/null +++ b/app/src/main/java/eu/kanade/tachiyomi/network/interceptor/RateLimitInterceptor.kt @@ -0,0 +1,105 @@ +package eu.kanade.tachiyomi.network.interceptor + +import android.os.SystemClock +import okhttp3.Interceptor +import okhttp3.OkHttpClient +import okhttp3.Response +import java.io.IOException +import java.util.ArrayDeque +import java.util.concurrent.Semaphore +import java.util.concurrent.TimeUnit + +/** + * An OkHttp interceptor that handles rate limiting. + * + * Examples: + * + * permits = 5, period = 1, unit = seconds => 5 requests per second + * permits = 10, period = 2, unit = minutes => 10 requests per 2 minutes + * + * @since extension-lib 1.3 + * + * @param permits {Int} Number of requests allowed within a period of units. + * @param period {Long} The limiting duration. Defaults to 1. + * @param unit {TimeUnit} The unit of time for the period. Defaults to seconds. + */ +fun OkHttpClient.Builder.rateLimit( + permits: Int, + period: Long = 1, + unit: TimeUnit = TimeUnit.SECONDS, +) = addInterceptor(RateLimitInterceptor(null, permits, period, unit)) + +/** We can probably accept domains or wildcards by comparing with [endsWith], etc. */ +@Suppress("PLATFORM_CLASS_MAPPED_TO_KOTLIN") +internal class RateLimitInterceptor( + private val host: String?, + private val permits: Int, + period: Long, + unit: TimeUnit, +) : Interceptor { + + private val requestQueue = ArrayDeque(permits) + private val rateLimitMillis = unit.toMillis(period) + private val fairLock = Semaphore(1, true) + + override fun intercept(chain: Interceptor.Chain): Response { + val call = chain.call() + if (call.isCanceled()) throw IOException("Canceled") + + val request = chain.request() + when (host) { + null, request.url.host -> {} // need rate limit + else -> return chain.proceed(request) + } + + try { + fairLock.acquire() + } catch (e: InterruptedException) { + throw IOException(e) + } + + val requestQueue = this.requestQueue + val timestamp: Long + + try { + synchronized(requestQueue) { + while (requestQueue.size >= permits) { // queue is full, remove expired entries + val periodStart = SystemClock.elapsedRealtime() - rateLimitMillis + var hasRemovedExpired = false + while (requestQueue.isEmpty().not() && requestQueue.first <= periodStart) { + requestQueue.removeFirst() + hasRemovedExpired = true + } + if (call.isCanceled()) { + throw IOException("Canceled") + } else if (hasRemovedExpired) { + break + } else { + try { // wait for the first entry to expire, or notified by cached response + (requestQueue as Object).wait(requestQueue.first - periodStart) + } catch (_: InterruptedException) { + continue + } + } + } + + // add request to queue + timestamp = SystemClock.elapsedRealtime() + requestQueue.addLast(timestamp) + } + } finally { + fairLock.release() + } + + val response = chain.proceed(request) + if (response.networkResponse == null) { // response is cached, remove it from queue + synchronized(requestQueue) { + if (requestQueue.isEmpty() || timestamp < requestQueue.first) return@synchronized + requestQueue.removeFirstOccurrence(timestamp) + (requestQueue as Object).notifyAll() + } + } + + return response + } +} diff --git a/app/src/main/java/eu/kanade/tachiyomi/network/interceptor/SpecificHostRateLimitInterceptor.kt b/app/src/main/java/eu/kanade/tachiyomi/network/interceptor/SpecificHostRateLimitInterceptor.kt new file mode 100644 index 00000000..5687c9f5 --- /dev/null +++ b/app/src/main/java/eu/kanade/tachiyomi/network/interceptor/SpecificHostRateLimitInterceptor.kt @@ -0,0 +1,27 @@ +package eu.kanade.tachiyomi.network.interceptor + +import okhttp3.HttpUrl +import okhttp3.OkHttpClient +import java.util.concurrent.TimeUnit + +/** + * An OkHttp interceptor that handles given url host's rate limiting. + * + * Examples: + * + * httpUrl = "api.manga.com".toHttpUrlOrNull(), permits = 5, period = 1, unit = seconds => 5 requests per second to api.manga.com + * httpUrl = "imagecdn.manga.com".toHttpUrlOrNull(), permits = 10, period = 2, unit = minutes => 10 requests per 2 minutes to imagecdn.manga.com + * + * @since extension-lib 1.3 + * + * @param httpUrl {HttpUrl} The url host that this interceptor should handle. Will get url's host by using HttpUrl.host() + * @param permits {Int} Number of requests allowed within a period of units. + * @param period {Long} The limiting duration. Defaults to 1. + * @param unit {TimeUnit} The unit of time for the period. Defaults to seconds. + */ +fun OkHttpClient.Builder.rateLimitHost( + httpUrl: HttpUrl, + permits: Int, + period: Long = 1, + unit: TimeUnit = TimeUnit.SECONDS, +) = addInterceptor(RateLimitInterceptor(httpUrl.host, permits, period, unit)) diff --git a/app/src/main/java/ani/dantotsu/aniyomi/util/network/interceptor/UncaughtExceptionInterceptor.kt b/app/src/main/java/eu/kanade/tachiyomi/network/interceptor/UncaughtExceptionInterceptor.kt similarity index 100% rename from app/src/main/java/ani/dantotsu/aniyomi/util/network/interceptor/UncaughtExceptionInterceptor.kt rename to app/src/main/java/eu/kanade/tachiyomi/network/interceptor/UncaughtExceptionInterceptor.kt diff --git a/app/src/main/java/ani/dantotsu/aniyomi/util/network/interceptor/UserAgentInterceptor.kt b/app/src/main/java/eu/kanade/tachiyomi/network/interceptor/UserAgentInterceptor.kt similarity index 100% rename from app/src/main/java/ani/dantotsu/aniyomi/util/network/interceptor/UserAgentInterceptor.kt rename to app/src/main/java/eu/kanade/tachiyomi/network/interceptor/UserAgentInterceptor.kt diff --git a/app/src/main/java/ani/dantotsu/aniyomi/util/network/interceptor/WebViewInterceptor.kt b/app/src/main/java/eu/kanade/tachiyomi/network/interceptor/WebViewInterceptor.kt similarity index 92% rename from app/src/main/java/ani/dantotsu/aniyomi/util/network/interceptor/WebViewInterceptor.kt rename to app/src/main/java/eu/kanade/tachiyomi/network/interceptor/WebViewInterceptor.kt index b322e962..5629f016 100644 --- a/app/src/main/java/ani/dantotsu/aniyomi/util/network/interceptor/WebViewInterceptor.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/network/interceptor/WebViewInterceptor.kt @@ -1,20 +1,20 @@ -package ani.dantotsu.aniyomi.util.network.interceptor +package eu.kanade.tachiyomi.network.interceptor import android.content.Context import android.os.Build import android.webkit.WebSettings import android.webkit.WebView import android.widget.Toast -import ani.dantotsu.aniyomi.util.system.DeviceUtil -import ani.dantotsu.aniyomi.util.system.WebViewUtil -import ani.dantotsu.aniyomi.util.system.setDefaultSettings -import ani.dantotsu.aniyomi.util.toast +import eu.kanade.tachiyomi.util.system.DeviceUtil +import eu.kanade.tachiyomi.util.system.WebViewUtil +import eu.kanade.tachiyomi.util.system.setDefaultSettings +import eu.kanade.tachiyomi.util.system.toast import kotlinx.coroutines.DelicateCoroutinesApi import okhttp3.Headers import okhttp3.Interceptor import okhttp3.Request import okhttp3.Response -import ani.dantotsu.aniyomi.util.launchUI +import tachiyomi.core.util.lang.launchUI import java.util.Locale import java.util.concurrent.CountDownLatch import java.util.concurrent.TimeUnit diff --git a/app/src/main/java/ani/dantotsu/aniyomi/source/CatalogueSource.kt b/app/src/main/java/eu/kanade/tachiyomi/source/CatalogueSource.kt similarity index 89% rename from app/src/main/java/ani/dantotsu/aniyomi/source/CatalogueSource.kt rename to app/src/main/java/eu/kanade/tachiyomi/source/CatalogueSource.kt index 559e94e6..de4137be 100644 --- a/app/src/main/java/ani/dantotsu/aniyomi/source/CatalogueSource.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/source/CatalogueSource.kt @@ -1,7 +1,7 @@ -package ani.dantotsu.aniyomi.source +package eu.kanade.tachiyomi.source -import ani.dantotsu.aniyomi.source.model.FilterList -import ani.dantotsu.aniyomi.source.model.MangasPage +import eu.kanade.tachiyomi.source.model.FilterList +import eu.kanade.tachiyomi.source.model.MangasPage import rx.Observable interface CatalogueSource : MangaSource { diff --git a/app/src/main/java/eu/kanade/tachiyomi/source/ConfigurableSource.kt b/app/src/main/java/eu/kanade/tachiyomi/source/ConfigurableSource.kt new file mode 100644 index 00000000..430cc5bf --- /dev/null +++ b/app/src/main/java/eu/kanade/tachiyomi/source/ConfigurableSource.kt @@ -0,0 +1,8 @@ +package eu.kanade.tachiyomi.source + +import eu.kanade.tachiyomi.PreferenceScreen + +interface ConfigurableSource : MangaSource { + + fun setupPreferenceScreen(screen: PreferenceScreen) +} diff --git a/app/src/main/java/ani/dantotsu/aniyomi/source/MangaSource.kt b/app/src/main/java/eu/kanade/tachiyomi/source/MangaSource.kt similarity index 90% rename from app/src/main/java/ani/dantotsu/aniyomi/source/MangaSource.kt rename to app/src/main/java/eu/kanade/tachiyomi/source/MangaSource.kt index 9583b595..fabc4590 100644 --- a/app/src/main/java/ani/dantotsu/aniyomi/source/MangaSource.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/source/MangaSource.kt @@ -1,9 +1,9 @@ -package ani.dantotsu.aniyomi.source +package eu.kanade.tachiyomi.source -import ani.dantotsu.aniyomi.source.model.Page -import ani.dantotsu.aniyomi.source.model.SChapter -import ani.dantotsu.aniyomi.source.model.SManga -import ani.dantotsu.aniyomi.util.lang.awaitSingle +import eu.kanade.tachiyomi.source.model.Page +import eu.kanade.tachiyomi.source.model.SChapter +import eu.kanade.tachiyomi.source.model.SManga +import eu.kanade.tachiyomi.util.lang.awaitSingle import rx.Observable /** diff --git a/app/src/main/java/eu/kanade/tachiyomi/source/SourceFactory.kt b/app/src/main/java/eu/kanade/tachiyomi/source/SourceFactory.kt new file mode 100644 index 00000000..f02df265 --- /dev/null +++ b/app/src/main/java/eu/kanade/tachiyomi/source/SourceFactory.kt @@ -0,0 +1,12 @@ +package eu.kanade.tachiyomi.source + +/** + * A factory for creating sources at runtime. + */ +interface SourceFactory { + /** + * Create a new copy of the sources + * @return The created sources + */ + fun createSources(): List +} diff --git a/app/src/main/java/eu/kanade/tachiyomi/source/UnmeteredSource.kt b/app/src/main/java/eu/kanade/tachiyomi/source/UnmeteredSource.kt new file mode 100644 index 00000000..7536ccb0 --- /dev/null +++ b/app/src/main/java/eu/kanade/tachiyomi/source/UnmeteredSource.kt @@ -0,0 +1,8 @@ +package eu.kanade.tachiyomi.source + +/** + * A source that explicitly doesn't require traffic considerations. + * + * This typically applies for self-hosted sources. + */ +interface UnmeteredSource diff --git a/app/src/main/java/ani/dantotsu/aniyomi/source/model/Filter.kt b/app/src/main/java/eu/kanade/tachiyomi/source/model/Filter.kt similarity index 97% rename from app/src/main/java/ani/dantotsu/aniyomi/source/model/Filter.kt rename to app/src/main/java/eu/kanade/tachiyomi/source/model/Filter.kt index 8e4d4fbf..f30b2f52 100644 --- a/app/src/main/java/ani/dantotsu/aniyomi/source/model/Filter.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/source/model/Filter.kt @@ -1,4 +1,4 @@ -package ani.dantotsu.aniyomi.source.model +package eu.kanade.tachiyomi.source.model sealed class Filter(val name: String, var state: T) { open class Header(name: String) : Filter(name, 0) diff --git a/app/src/main/java/ani/dantotsu/aniyomi/source/model/FilterList.kt b/app/src/main/java/eu/kanade/tachiyomi/source/model/FilterList.kt similarity index 88% rename from app/src/main/java/ani/dantotsu/aniyomi/source/model/FilterList.kt rename to app/src/main/java/eu/kanade/tachiyomi/source/model/FilterList.kt index f6ca750f..77f339b9 100644 --- a/app/src/main/java/ani/dantotsu/aniyomi/source/model/FilterList.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/source/model/FilterList.kt @@ -1,4 +1,4 @@ -package ani.dantotsu.aniyomi.source.model +package eu.kanade.tachiyomi.source.model data class FilterList(val list: List>) : List> by list { diff --git a/app/src/main/java/ani/dantotsu/aniyomi/source/model/MangasPage.kt b/app/src/main/java/eu/kanade/tachiyomi/source/model/MangasPage.kt similarity index 64% rename from app/src/main/java/ani/dantotsu/aniyomi/source/model/MangasPage.kt rename to app/src/main/java/eu/kanade/tachiyomi/source/model/MangasPage.kt index 27e8a901..a377c36e 100644 --- a/app/src/main/java/ani/dantotsu/aniyomi/source/model/MangasPage.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/source/model/MangasPage.kt @@ -1,3 +1,3 @@ -package ani.dantotsu.aniyomi.source.model +package eu.kanade.tachiyomi.source.model data class MangasPage(val mangas: List, val hasNextPage: Boolean) diff --git a/app/src/main/java/ani/dantotsu/aniyomi/source/model/Page.kt b/app/src/main/java/eu/kanade/tachiyomi/source/model/Page.kt similarity index 84% rename from app/src/main/java/ani/dantotsu/aniyomi/source/model/Page.kt rename to app/src/main/java/eu/kanade/tachiyomi/source/model/Page.kt index 018982e3..fca735f4 100644 --- a/app/src/main/java/ani/dantotsu/aniyomi/source/model/Page.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/source/model/Page.kt @@ -1,19 +1,17 @@ -package ani.dantotsu.aniyomi.source.model +package eu.kanade.tachiyomi.source.model import android.net.Uri -import ani.dantotsu.aniyomi.util.network.ProgressListener +import eu.kanade.tachiyomi.network.ProgressListener import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.asStateFlow -import kotlinx.serialization.Serializable -import kotlinx.serialization.Transient +import java.io.Serializable -@Serializable open class Page( val index: Int, val url: String = "", var imageUrl: String? = null, @Transient var uri: Uri? = null, // Deprecated but can't be deleted due to extensions -) : ProgressListener { +) : Serializable, ProgressListener { val number: Int get() = index + 1 diff --git a/app/src/main/java/ani/dantotsu/aniyomi/source/model/SChapter.kt b/app/src/main/java/eu/kanade/tachiyomi/source/model/SChapter.kt similarity index 92% rename from app/src/main/java/ani/dantotsu/aniyomi/source/model/SChapter.kt rename to app/src/main/java/eu/kanade/tachiyomi/source/model/SChapter.kt index e42a16c1..f53bbe8f 100644 --- a/app/src/main/java/ani/dantotsu/aniyomi/source/model/SChapter.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/source/model/SChapter.kt @@ -1,4 +1,4 @@ -package ani.dantotsu.aniyomi.source.model +package eu.kanade.tachiyomi.source.model import java.io.Serializable diff --git a/app/src/main/java/ani/dantotsu/aniyomi/source/model/SChapterImpl.kt b/app/src/main/java/eu/kanade/tachiyomi/source/model/SChapterImpl.kt similarity index 85% rename from app/src/main/java/ani/dantotsu/aniyomi/source/model/SChapterImpl.kt rename to app/src/main/java/eu/kanade/tachiyomi/source/model/SChapterImpl.kt index db0253ff..4d5e43f1 100644 --- a/app/src/main/java/ani/dantotsu/aniyomi/source/model/SChapterImpl.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/source/model/SChapterImpl.kt @@ -1,4 +1,4 @@ -package ani.dantotsu.aniyomi.source.model +package eu.kanade.tachiyomi.source.model class SChapterImpl : SChapter { diff --git a/app/src/main/java/ani/dantotsu/aniyomi/source/model/SManga.kt b/app/src/main/java/eu/kanade/tachiyomi/source/model/SManga.kt similarity index 97% rename from app/src/main/java/ani/dantotsu/aniyomi/source/model/SManga.kt rename to app/src/main/java/eu/kanade/tachiyomi/source/model/SManga.kt index 3b296ad6..f0a014e2 100644 --- a/app/src/main/java/ani/dantotsu/aniyomi/source/model/SManga.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/source/model/SManga.kt @@ -1,4 +1,4 @@ -package ani.dantotsu.aniyomi.source.model +package eu.kanade.tachiyomi.source.model import java.io.Serializable diff --git a/app/src/main/java/ani/dantotsu/aniyomi/source/model/SMangaImpl.kt b/app/src/main/java/eu/kanade/tachiyomi/source/model/SMangaImpl.kt similarity index 92% rename from app/src/main/java/ani/dantotsu/aniyomi/source/model/SMangaImpl.kt rename to app/src/main/java/eu/kanade/tachiyomi/source/model/SMangaImpl.kt index b181c904..91a7711c 100644 --- a/app/src/main/java/ani/dantotsu/aniyomi/source/model/SMangaImpl.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/source/model/SMangaImpl.kt @@ -1,4 +1,4 @@ -package ani.dantotsu.aniyomi.source.model +package eu.kanade.tachiyomi.source.model class SMangaImpl : SManga { diff --git a/app/src/main/java/ani/dantotsu/aniyomi/source/model/UpdateStrategy.kt b/app/src/main/java/eu/kanade/tachiyomi/source/model/UpdateStrategy.kt similarity index 93% rename from app/src/main/java/ani/dantotsu/aniyomi/source/model/UpdateStrategy.kt rename to app/src/main/java/eu/kanade/tachiyomi/source/model/UpdateStrategy.kt index 8f23b941..91b5f5e2 100644 --- a/app/src/main/java/ani/dantotsu/aniyomi/source/model/UpdateStrategy.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/source/model/UpdateStrategy.kt @@ -1,4 +1,4 @@ -package ani.dantotsu.aniyomi.source.model +package eu.kanade.tachiyomi.source.model /** * Define the update strategy for a single [SManga]. diff --git a/app/src/main/java/eu/kanade/tachiyomi/source/online/HttpSource.kt b/app/src/main/java/eu/kanade/tachiyomi/source/online/HttpSource.kt index f7c981c6..d0aa5b2d 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/source/online/HttpSource.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/source/online/HttpSource.kt @@ -5,12 +5,12 @@ import eu.kanade.tachiyomi.network.NetworkHelper import eu.kanade.tachiyomi.network.asObservableSuccess import eu.kanade.tachiyomi.network.awaitSuccess import eu.kanade.tachiyomi.network.newCachelessCallWithProgress -import ani.dantotsu.aniyomi.source.CatalogueSource -import ani.dantotsu.aniyomi.source.model.FilterList -import ani.dantotsu.aniyomi.source.model.MangasPage -import ani.dantotsu.aniyomi.source.model.Page -import ani.dantotsu.aniyomi.source.model.SChapter -import ani.dantotsu.aniyomi.source.model.SManga +import eu.kanade.tachiyomi.source.CatalogueSource +import eu.kanade.tachiyomi.source.model.FilterList +import eu.kanade.tachiyomi.source.model.MangasPage +import eu.kanade.tachiyomi.source.model.Page +import eu.kanade.tachiyomi.source.model.SChapter +import eu.kanade.tachiyomi.source.model.SManga import okhttp3.Headers import okhttp3.OkHttpClient import okhttp3.Request diff --git a/app/src/main/java/eu/kanade/tachiyomi/source/online/HttpSourceFetcher.kt b/app/src/main/java/eu/kanade/tachiyomi/source/online/HttpSourceFetcher.kt new file mode 100644 index 00000000..76c68e88 --- /dev/null +++ b/app/src/main/java/eu/kanade/tachiyomi/source/online/HttpSourceFetcher.kt @@ -0,0 +1,25 @@ +package eu.kanade.tachiyomi.source.online + +import eu.kanade.tachiyomi.source.model.Page +import rx.Observable + +fun HttpSource.fetchAllImageUrlsFromPageList(pages: List): Observable { + return Observable.from(pages) + .filter { !it.imageUrl.isNullOrEmpty() } + .mergeWith(fetchRemainingImageUrlsFromPageList(pages)) +} + +private fun HttpSource.fetchRemainingImageUrlsFromPageList(pages: List): Observable { + return Observable.from(pages) + .filter { it.imageUrl.isNullOrEmpty() } + .concatMap { getImageUrl(it) } +} + +private fun HttpSource.getImageUrl(page: Page): Observable { + page.status = Page.State.LOAD_PAGE + return fetchImageUrl(page) + .doOnError { page.status = Page.State.ERROR } + .onErrorReturn { null } + .doOnNext { page.imageUrl = it } + .map { page } +} diff --git a/app/src/main/java/eu/kanade/tachiyomi/source/online/ParsedHttpSource.kt b/app/src/main/java/eu/kanade/tachiyomi/source/online/ParsedHttpSource.kt new file mode 100644 index 00000000..34376f84 --- /dev/null +++ b/app/src/main/java/eu/kanade/tachiyomi/source/online/ParsedHttpSource.kt @@ -0,0 +1,201 @@ +package eu.kanade.tachiyomi.source.online + +import eu.kanade.tachiyomi.source.model.MangasPage +import eu.kanade.tachiyomi.source.model.Page +import eu.kanade.tachiyomi.source.model.SChapter +import eu.kanade.tachiyomi.source.model.SManga +import eu.kanade.tachiyomi.util.asJsoup +import okhttp3.Response +import org.jsoup.nodes.Document +import org.jsoup.nodes.Element + +/** + * A simple implementation for sources from a website using Jsoup, an HTML parser. + */ +@Suppress("unused") +abstract class ParsedHttpSource : HttpSource() { + + /** + * Parses the response from the site and returns a [MangasPage] object. + * + * @param response the response from the site. + */ + override fun popularMangaParse(response: Response): MangasPage { + val document = response.asJsoup() + + val mangas = document.select(popularMangaSelector()).map { element -> + popularMangaFromElement(element) + } + + val hasNextPage = popularMangaNextPageSelector()?.let { selector -> + document.select(selector).first() + } != null + + return MangasPage(mangas, hasNextPage) + } + + /** + * Returns the Jsoup selector that returns a list of [Element] corresponding to each manga. + */ + protected abstract fun popularMangaSelector(): String + + /** + * Returns a manga from the given [element]. Most sites only show the title and the url, it's + * totally fine to fill only those two values. + * + * @param element an element obtained from [popularMangaSelector]. + */ + protected abstract fun popularMangaFromElement(element: Element): SManga + + /** + * Returns the Jsoup selector that returns the tag linking to the next page, or null if + * there's no next page. + */ + protected abstract fun popularMangaNextPageSelector(): String? + + /** + * Parses the response from the site and returns a [MangasPage] object. + * + * @param response the response from the site. + */ + override fun searchMangaParse(response: Response): MangasPage { + val document = response.asJsoup() + + val mangas = document.select(searchMangaSelector()).map { element -> + searchMangaFromElement(element) + } + + val hasNextPage = searchMangaNextPageSelector()?.let { selector -> + document.select(selector).first() + } != null + + return MangasPage(mangas, hasNextPage) + } + + /** + * Returns the Jsoup selector that returns a list of [Element] corresponding to each manga. + */ + protected abstract fun searchMangaSelector(): String + + /** + * Returns a manga from the given [element]. Most sites only show the title and the url, it's + * totally fine to fill only those two values. + * + * @param element an element obtained from [searchMangaSelector]. + */ + protected abstract fun searchMangaFromElement(element: Element): SManga + + /** + * Returns the Jsoup selector that returns the tag linking to the next page, or null if + * there's no next page. + */ + protected abstract fun searchMangaNextPageSelector(): String? + + /** + * Parses the response from the site and returns a [MangasPage] object. + * + * @param response the response from the site. + */ + override fun latestUpdatesParse(response: Response): MangasPage { + val document = response.asJsoup() + + val mangas = document.select(latestUpdatesSelector()).map { element -> + latestUpdatesFromElement(element) + } + + val hasNextPage = latestUpdatesNextPageSelector()?.let { selector -> + document.select(selector).first() + } != null + + return MangasPage(mangas, hasNextPage) + } + + /** + * Returns the Jsoup selector that returns a list of [Element] corresponding to each manga. + */ + protected abstract fun latestUpdatesSelector(): String + + /** + * Returns a manga from the given [element]. Most sites only show the title and the url, it's + * totally fine to fill only those two values. + * + * @param element an element obtained from [latestUpdatesSelector]. + */ + protected abstract fun latestUpdatesFromElement(element: Element): SManga + + /** + * Returns the Jsoup selector that returns the tag linking to the next page, or null if + * there's no next page. + */ + protected abstract fun latestUpdatesNextPageSelector(): String? + + /** + * Parses the response from the site and returns the details of a manga. + * + * @param response the response from the site. + */ + override fun mangaDetailsParse(response: Response): SManga { + return mangaDetailsParse(response.asJsoup()) + } + + /** + * Returns the details of the manga from the given [document]. + * + * @param document the parsed document. + */ + protected abstract fun mangaDetailsParse(document: Document): SManga + + /** + * Parses the response from the site and returns a list of chapters. + * + * @param response the response from the site. + */ + override fun chapterListParse(response: Response): List { + val document = response.asJsoup() + return document.select(chapterListSelector()).map { chapterFromElement(it) } + } + + /** + * Returns the Jsoup selector that returns a list of [Element] corresponding to each chapter. + */ + protected abstract fun chapterListSelector(): String + + /** + * Returns a chapter from the given element. + * + * @param element an element obtained from [chapterListSelector]. + */ + protected abstract fun chapterFromElement(element: Element): SChapter + + /** + * Parses the response from the site and returns the page list. + * + * @param response the response from the site. + */ + override fun pageListParse(response: Response): List { + return pageListParse(response.asJsoup()) + } + + /** + * Returns a page list from the given document. + * + * @param document the parsed document. + */ + protected abstract fun pageListParse(document: Document): List + + /** + * Parse the response from the site and returns the absolute url to the source image. + * + * @param response the response from the site. + */ + override fun imageUrlParse(response: Response): String { + return imageUrlParse(response.asJsoup()) + } + + /** + * Returns the absolute url to the source image from the document. + * + * @param document the parsed document. + */ + protected abstract fun imageUrlParse(document: Document): String +} diff --git a/app/src/main/java/ani/dantotsu/aniyomi/util/JsoupExtensions.kt b/app/src/main/java/eu/kanade/tachiyomi/util/JsoupExtensions.kt similarity index 100% rename from app/src/main/java/ani/dantotsu/aniyomi/util/JsoupExtensions.kt rename to app/src/main/java/eu/kanade/tachiyomi/util/JsoupExtensions.kt diff --git a/app/src/main/java/ani/dantotsu/aniyomi/util/RxExtension.kt b/app/src/main/java/eu/kanade/tachiyomi/util/RxExtension.kt similarity index 62% rename from app/src/main/java/ani/dantotsu/aniyomi/util/RxExtension.kt rename to app/src/main/java/eu/kanade/tachiyomi/util/RxExtension.kt index a3b23909..c0fc1ec3 100644 --- a/app/src/main/java/ani/dantotsu/aniyomi/util/RxExtension.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/util/RxExtension.kt @@ -1,3 +1,3 @@ -package ani.dantotsu.aniyomi.util +package eu.kanade.tachiyomi.util //expect suspend fun Observable.awaitSingle(): T diff --git a/app/src/main/java/ani/dantotsu/aniyomi/util/lang/CloseableExtensions.kt b/app/src/main/java/eu/kanade/tachiyomi/util/lang/CloseableExtensions.kt similarity index 95% rename from app/src/main/java/ani/dantotsu/aniyomi/util/lang/CloseableExtensions.kt rename to app/src/main/java/eu/kanade/tachiyomi/util/lang/CloseableExtensions.kt index a2c6e072..647eaab3 100644 --- a/app/src/main/java/ani/dantotsu/aniyomi/util/lang/CloseableExtensions.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/util/lang/CloseableExtensions.kt @@ -1,4 +1,4 @@ -package ani.dantotsu.aniyomi.util.lang +package eu.kanade.tachiyomi.util.lang import java.io.Closeable diff --git a/app/src/main/java/ani/dantotsu/aniyomi/util/lang/Hash.kt b/app/src/main/java/eu/kanade/tachiyomi/util/lang/Hash.kt similarity index 96% rename from app/src/main/java/ani/dantotsu/aniyomi/util/lang/Hash.kt rename to app/src/main/java/eu/kanade/tachiyomi/util/lang/Hash.kt index ed087283..32d2a2d2 100644 --- a/app/src/main/java/ani/dantotsu/aniyomi/util/lang/Hash.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/util/lang/Hash.kt @@ -1,4 +1,4 @@ -package ani.dantotsu.aniyomi.util.lang +package eu.kanade.tachiyomi.util.lang import java.security.MessageDigest diff --git a/app/src/main/java/ani/dantotsu/aniyomi/util/lang/RxCoroutineBridge.kt b/app/src/main/java/eu/kanade/tachiyomi/util/lang/RxCoroutineBridge.kt similarity index 98% rename from app/src/main/java/ani/dantotsu/aniyomi/util/lang/RxCoroutineBridge.kt rename to app/src/main/java/eu/kanade/tachiyomi/util/lang/RxCoroutineBridge.kt index 608d57a1..57f9e9a0 100644 --- a/app/src/main/java/ani/dantotsu/aniyomi/util/lang/RxCoroutineBridge.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/util/lang/RxCoroutineBridge.kt @@ -1,4 +1,4 @@ -package ani.dantotsu.aniyomi.util.lang +package eu.kanade.tachiyomi.util.lang import kotlinx.coroutines.CancellableContinuation import kotlinx.coroutines.InternalCoroutinesApi diff --git a/app/src/main/java/ani/dantotsu/aniyomi/util/lang/StringExtensions.kt b/app/src/main/java/eu/kanade/tachiyomi/util/lang/StringExtensions.kt similarity index 98% rename from app/src/main/java/ani/dantotsu/aniyomi/util/lang/StringExtensions.kt rename to app/src/main/java/eu/kanade/tachiyomi/util/lang/StringExtensions.kt index b100a856..97bb9168 100644 --- a/app/src/main/java/ani/dantotsu/aniyomi/util/lang/StringExtensions.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/util/lang/StringExtensions.kt @@ -1,4 +1,4 @@ -package ani.dantotsu.aniyomi.util.lang +package eu.kanade.tachiyomi.util.lang import androidx.core.text.parseAsHtml import net.greypanther.natsort.CaseInsensitiveSimpleNaturalComparator diff --git a/app/src/main/java/eu/kanade/tachiyomi/util/preference/PreferenceExtensions.kt b/app/src/main/java/eu/kanade/tachiyomi/util/preference/PreferenceExtensions.kt new file mode 100644 index 00000000..c6076b79 --- /dev/null +++ b/app/src/main/java/eu/kanade/tachiyomi/util/preference/PreferenceExtensions.kt @@ -0,0 +1,31 @@ +package eu.kanade.tachiyomi.util.preference + +import android.widget.CompoundButton +import eu.kanade.core.preference.PreferenceMutableState +import kotlinx.coroutines.CoroutineScope +import tachiyomi.core.preference.Preference + +/** + * Binds a checkbox or switch view with a boolean preference. + */ +fun CompoundButton.bindToPreference(pref: Preference) { + isChecked = pref.get() + setOnCheckedChangeListener { _, isChecked -> pref.set(isChecked) } +} + +operator fun Preference>.plusAssign(item: T) { + set(get() + item) +} + +operator fun Preference>.minusAssign(item: T) { + set(get() - item) +} + +fun Preference.toggle(): Boolean { + set(!get()) + return get() +} + +fun Preference.asState(presenterScope: CoroutineScope): PreferenceMutableState { + return PreferenceMutableState(this, presenterScope) +} diff --git a/app/src/main/java/ani/dantotsu/aniyomi/util/storage/FileExtensions.kt b/app/src/main/java/eu/kanade/tachiyomi/util/storage/FileExtensions.kt similarity index 93% rename from app/src/main/java/ani/dantotsu/aniyomi/util/storage/FileExtensions.kt rename to app/src/main/java/eu/kanade/tachiyomi/util/storage/FileExtensions.kt index 5063fc07..4fd00702 100644 --- a/app/src/main/java/ani/dantotsu/aniyomi/util/storage/FileExtensions.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/util/storage/FileExtensions.kt @@ -1,4 +1,4 @@ -package ani.dantotsu.aniyomi.util.storage +package eu.kanade.tachiyomi.util.storage import android.content.Context import android.net.Uri diff --git a/app/src/main/java/ani/dantotsu/aniyomi/util/system/ContextExtensions.kt b/app/src/main/java/eu/kanade/tachiyomi/util/system/ContextExtensions.kt similarity index 97% rename from app/src/main/java/ani/dantotsu/aniyomi/util/system/ContextExtensions.kt rename to app/src/main/java/eu/kanade/tachiyomi/util/system/ContextExtensions.kt index c426a38f..773c8c12 100644 --- a/app/src/main/java/ani/dantotsu/aniyomi/util/system/ContextExtensions.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/util/system/ContextExtensions.kt @@ -1,4 +1,4 @@ -package ani.dantotsu.aniyomi.util.system +package eu.kanade.tachiyomi.util.system import android.app.ActivityManager import android.content.ClipData @@ -21,9 +21,9 @@ import androidx.core.graphics.blue import androidx.core.graphics.green import androidx.core.graphics.red import androidx.core.net.toUri -import ani.dantotsu.aniyomi.util.lang.truncateCenter +import eu.kanade.tachiyomi.util.lang.truncateCenter import logcat.LogPriority -import ani.dantotsu.aniyomi.util.logcat +import tachiyomi.core.util.system.logcat import ani.dantotsu.toast import com.hippo.unifile.UniFile import java.io.File diff --git a/app/src/main/java/ani/dantotsu/aniyomi/util/system/DeviceUtil.kt b/app/src/main/java/eu/kanade/tachiyomi/util/system/DeviceUtil.kt similarity index 96% rename from app/src/main/java/ani/dantotsu/aniyomi/util/system/DeviceUtil.kt rename to app/src/main/java/eu/kanade/tachiyomi/util/system/DeviceUtil.kt index 244e655b..57950eda 100644 --- a/app/src/main/java/ani/dantotsu/aniyomi/util/system/DeviceUtil.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/util/system/DeviceUtil.kt @@ -1,9 +1,9 @@ -package ani.dantotsu.aniyomi.util.system +package eu.kanade.tachiyomi.util.system import android.annotation.SuppressLint import android.os.Build import logcat.LogPriority -import ani.dantotsu.aniyomi.util.logcat +import tachiyomi.core.util.system.logcat object DeviceUtil { diff --git a/app/src/main/java/ani/dantotsu/aniyomi/util/system/IntentExtensions.kt b/app/src/main/java/eu/kanade/tachiyomi/util/system/IntentExtensions.kt similarity index 97% rename from app/src/main/java/ani/dantotsu/aniyomi/util/system/IntentExtensions.kt rename to app/src/main/java/eu/kanade/tachiyomi/util/system/IntentExtensions.kt index d6d6d0c6..5adba5d9 100644 --- a/app/src/main/java/ani/dantotsu/aniyomi/util/system/IntentExtensions.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/util/system/IntentExtensions.kt @@ -1,4 +1,4 @@ -package ani.dantotsu.aniyomi.util.system +package eu.kanade.tachiyomi.util.system import android.content.ClipData import android.content.Context diff --git a/app/src/main/java/ani/dantotsu/aniyomi/util/system/LocaleHelper.kt b/app/src/main/java/eu/kanade/tachiyomi/util/system/LocaleHelper.kt similarity index 97% rename from app/src/main/java/ani/dantotsu/aniyomi/util/system/LocaleHelper.kt rename to app/src/main/java/eu/kanade/tachiyomi/util/system/LocaleHelper.kt index 78958c16..cf0df960 100644 --- a/app/src/main/java/ani/dantotsu/aniyomi/util/system/LocaleHelper.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/util/system/LocaleHelper.kt @@ -1,4 +1,4 @@ -package ani.dantotsu.aniyomi.util.system +package eu.kanade.tachiyomi.util.system import android.content.Context import androidx.core.os.LocaleListCompat diff --git a/app/src/main/java/ani/dantotsu/aniyomi/util/system/NotificationExtensions.kt b/app/src/main/java/eu/kanade/tachiyomi/util/system/NotificationExtensions.kt similarity index 98% rename from app/src/main/java/ani/dantotsu/aniyomi/util/system/NotificationExtensions.kt rename to app/src/main/java/eu/kanade/tachiyomi/util/system/NotificationExtensions.kt index 9d85e88f..d43d7045 100644 --- a/app/src/main/java/ani/dantotsu/aniyomi/util/system/NotificationExtensions.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/util/system/NotificationExtensions.kt @@ -1,4 +1,4 @@ -package ani.dantotsu.aniyomi.util.system +package eu.kanade.tachiyomi.util.system import android.Manifest import android.app.Notification diff --git a/app/src/main/java/ani/dantotsu/aniyomi/util/ToastExtensions.kt b/app/src/main/java/eu/kanade/tachiyomi/util/system/ToastExtensions.kt similarity index 95% rename from app/src/main/java/ani/dantotsu/aniyomi/util/ToastExtensions.kt rename to app/src/main/java/eu/kanade/tachiyomi/util/system/ToastExtensions.kt index 69f58530..4901e746 100644 --- a/app/src/main/java/ani/dantotsu/aniyomi/util/ToastExtensions.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/util/system/ToastExtensions.kt @@ -1,4 +1,4 @@ -package ani.dantotsu.aniyomi.util +package eu.kanade.tachiyomi.util.system import android.content.Context import android.widget.Toast diff --git a/app/src/main/java/ani/dantotsu/aniyomi/util/system/WebViewClientCompat.kt b/app/src/main/java/eu/kanade/tachiyomi/util/system/WebViewClientCompat.kt similarity index 98% rename from app/src/main/java/ani/dantotsu/aniyomi/util/system/WebViewClientCompat.kt rename to app/src/main/java/eu/kanade/tachiyomi/util/system/WebViewClientCompat.kt index 8e081e07..6c6fba4f 100644 --- a/app/src/main/java/ani/dantotsu/aniyomi/util/system/WebViewClientCompat.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/util/system/WebViewClientCompat.kt @@ -1,4 +1,4 @@ -package ani.dantotsu.aniyomi.util.system +package eu.kanade.tachiyomi.util.system import android.annotation.TargetApi import android.os.Build diff --git a/app/src/main/java/ani/dantotsu/aniyomi/util/system/WebViewUtil.kt b/app/src/main/java/eu/kanade/tachiyomi/util/system/WebViewUtil.kt similarity index 96% rename from app/src/main/java/ani/dantotsu/aniyomi/util/system/WebViewUtil.kt rename to app/src/main/java/eu/kanade/tachiyomi/util/system/WebViewUtil.kt index defa5b9e..beef906d 100644 --- a/app/src/main/java/ani/dantotsu/aniyomi/util/system/WebViewUtil.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/util/system/WebViewUtil.kt @@ -1,4 +1,4 @@ -package ani.dantotsu.aniyomi.util.system +package eu.kanade.tachiyomi.util.system import android.annotation.SuppressLint import android.content.Context @@ -7,7 +7,7 @@ import android.webkit.CookieManager import android.webkit.WebSettings import android.webkit.WebView import logcat.LogPriority -import ani.dantotsu.aniyomi.util.logcat +import tachiyomi.core.util.system.logcat object WebViewUtil { const val SPOOF_PACKAGE_NAME = "org.chromium.chrome" diff --git a/app/src/main/java/ani/dantotsu/aniyomi/core/preference/Preference.kt b/app/src/main/java/tachiyomi/core/preference/Preference.kt similarity index 91% rename from app/src/main/java/ani/dantotsu/aniyomi/core/preference/Preference.kt rename to app/src/main/java/tachiyomi/core/preference/Preference.kt index 2b189e5f..c276141e 100644 --- a/app/src/main/java/ani/dantotsu/aniyomi/core/preference/Preference.kt +++ b/app/src/main/java/tachiyomi/core/preference/Preference.kt @@ -1,4 +1,4 @@ -package ani.dantotsu.aniyomi.core.preference +package tachiyomi.core.preference import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.flow.Flow diff --git a/app/src/main/java/ani/dantotsu/aniyomi/core/preference/PreferenceStore.kt b/app/src/main/java/tachiyomi/core/preference/PreferenceStore.kt similarity index 96% rename from app/src/main/java/ani/dantotsu/aniyomi/core/preference/PreferenceStore.kt rename to app/src/main/java/tachiyomi/core/preference/PreferenceStore.kt index 76da52a3..f8cc9f89 100644 --- a/app/src/main/java/ani/dantotsu/aniyomi/core/preference/PreferenceStore.kt +++ b/app/src/main/java/tachiyomi/core/preference/PreferenceStore.kt @@ -1,4 +1,4 @@ -package ani.dantotsu.aniyomi.core.preference +package tachiyomi.core.preference interface PreferenceStore { diff --git a/app/src/main/java/ani/dantotsu/aniyomi/util/CoroutinesExtensions.kt b/app/src/main/java/tachiyomi/core/util/lang/CoroutinesExtensions.kt similarity index 98% rename from app/src/main/java/ani/dantotsu/aniyomi/util/CoroutinesExtensions.kt rename to app/src/main/java/tachiyomi/core/util/lang/CoroutinesExtensions.kt index 1bcb7f55..4573b2d8 100644 --- a/app/src/main/java/ani/dantotsu/aniyomi/util/CoroutinesExtensions.kt +++ b/app/src/main/java/tachiyomi/core/util/lang/CoroutinesExtensions.kt @@ -1,4 +1,4 @@ -package ani.dantotsu.aniyomi.util +package tachiyomi.core.util.lang import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineStart diff --git a/app/src/main/java/ani/dantotsu/aniyomi/util/LogcatExtensions.kt b/app/src/main/java/tachiyomi/core/util/system/LogcatExtensions.kt similarity index 91% rename from app/src/main/java/ani/dantotsu/aniyomi/util/LogcatExtensions.kt rename to app/src/main/java/tachiyomi/core/util/system/LogcatExtensions.kt index 4758a8ce..fb587b07 100644 --- a/app/src/main/java/ani/dantotsu/aniyomi/util/LogcatExtensions.kt +++ b/app/src/main/java/tachiyomi/core/util/system/LogcatExtensions.kt @@ -1,4 +1,4 @@ -package ani.dantotsu.aniyomi.util +package tachiyomi.core.util.system import logcat.LogPriority import logcat.asLog diff --git a/app/src/main/java/ani/dantotsu/aniyomi/domain/category/model/Category.kt b/app/src/main/java/tachiyomi/domain/category/model/Category.kt similarity index 85% rename from app/src/main/java/ani/dantotsu/aniyomi/domain/category/model/Category.kt rename to app/src/main/java/tachiyomi/domain/category/model/Category.kt index 89663c99..46e2af94 100644 --- a/app/src/main/java/ani/dantotsu/aniyomi/domain/category/model/Category.kt +++ b/app/src/main/java/tachiyomi/domain/category/model/Category.kt @@ -1,4 +1,4 @@ -package ani.dantotsu.aniyomi.domain.category.model +package tachiyomi.domain.category.model import java.io.Serializable diff --git a/app/src/main/java/ani/dantotsu/aniyomi/domain/library/model/Flag.kt b/app/src/main/java/tachiyomi/domain/library/model/Flag.kt similarity index 93% rename from app/src/main/java/ani/dantotsu/aniyomi/domain/library/model/Flag.kt rename to app/src/main/java/tachiyomi/domain/library/model/Flag.kt index 659cad19..655d19bf 100644 --- a/app/src/main/java/ani/dantotsu/aniyomi/domain/library/model/Flag.kt +++ b/app/src/main/java/tachiyomi/domain/library/model/Flag.kt @@ -1,4 +1,4 @@ -package ani.dantotsu.aniyomi.domain.library.model +package tachiyomi.domain.library.model interface Flag { val flag: Long diff --git a/app/src/main/java/ani/dantotsu/aniyomi/domain/library/model/LibraryDisplayMode.kt b/app/src/main/java/tachiyomi/domain/library/model/LibraryDisplayMode.kt similarity index 93% rename from app/src/main/java/ani/dantotsu/aniyomi/domain/library/model/LibraryDisplayMode.kt rename to app/src/main/java/tachiyomi/domain/library/model/LibraryDisplayMode.kt index 5db7ee3d..f21eb190 100644 --- a/app/src/main/java/ani/dantotsu/aniyomi/domain/library/model/LibraryDisplayMode.kt +++ b/app/src/main/java/tachiyomi/domain/library/model/LibraryDisplayMode.kt @@ -1,6 +1,6 @@ -package ani.dantotsu.aniyomi.domain.library.model +package tachiyomi.domain.library.model -import ani.dantotsu.aniyomi.domain.category.model.Category +import tachiyomi.domain.category.model.Category sealed class LibraryDisplayMode( override val flag: Long, diff --git a/app/src/main/java/ani/dantotsu/aniyomi/domain/source/anime/model/AnimeSourceData.kt b/app/src/main/java/tachiyomi/domain/source/anime/model/AnimeSourceData.kt similarity index 74% rename from app/src/main/java/ani/dantotsu/aniyomi/domain/source/anime/model/AnimeSourceData.kt rename to app/src/main/java/tachiyomi/domain/source/anime/model/AnimeSourceData.kt index 28895b5d..22bf2fce 100644 --- a/app/src/main/java/ani/dantotsu/aniyomi/domain/source/anime/model/AnimeSourceData.kt +++ b/app/src/main/java/tachiyomi/domain/source/anime/model/AnimeSourceData.kt @@ -1,4 +1,4 @@ -package ani.dantotsu.aniyomi.domain.source.anime.model +package tachiyomi.domain.source.anime.model data class AnimeSourceData( val id: Long, diff --git a/app/src/main/java/tachiyomi/domain/source/manga/model/MangaSourceData.kt b/app/src/main/java/tachiyomi/domain/source/manga/model/MangaSourceData.kt new file mode 100644 index 00000000..9b8571dc --- /dev/null +++ b/app/src/main/java/tachiyomi/domain/source/manga/model/MangaSourceData.kt @@ -0,0 +1,10 @@ +package tachiyomi.domain.source.manga.model + +data class MangaSourceData( + val id: Long, + val lang: String, + val name: String, +) { + + val isMissingInfo: Boolean = name.isBlank() || lang.isBlank() +}