Merge remote-tracking branch 'upstream/master'

This commit is contained in:
jmir1 2021-11-29 20:59:28 +01:00
commit 737c5c9889
29 changed files with 124 additions and 62 deletions

View file

@ -28,9 +28,6 @@ jobs:
cp .github/runner-files/ci-gradle.properties ~/.gradle/gradle.properties cp .github/runner-files/ci-gradle.properties ~/.gradle/gradle.properties
- name: Build app - name: Build app
uses: gradle/gradle-command-action@v1 uses: gradle/gradle-command-action@v2
with: with:
arguments: assembleStandardRelease arguments: assembleStandardRelease
distributions-cache-enabled: true
dependencies-cache-enabled: true
configuration-cache-enabled: true

View file

@ -34,12 +34,9 @@ jobs:
cp .github/runner-files/ci-gradle.properties ~/.gradle/gradle.properties cp .github/runner-files/ci-gradle.properties ~/.gradle/gradle.properties
- name: Build app - name: Build app
uses: gradle/gradle-command-action@v1 uses: gradle/gradle-command-action@v2
with: with:
arguments: assembleStandardRelease arguments: assembleStandardRelease
distributions-cache-enabled: true
dependencies-cache-enabled: true
configuration-cache-enabled: true
# Sign APK and create release for tags # Sign APK and create release for tags

View file

@ -9,6 +9,6 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: Moderate issues - name: Moderate issues
uses: tachiyomiorg/issue-moderator-action@v1.1 uses: tachiyomiorg/issue-moderator-action@v1
with: with:
repo-token: ${{ secrets.GITHUB_TOKEN }} repo-token: ${{ secrets.GITHUB_TOKEN }}

View file

@ -149,10 +149,10 @@ dependencies {
// AndroidX libraries // AndroidX libraries
implementation("androidx.annotation:annotation:1.3.0") implementation("androidx.annotation:annotation:1.3.0")
implementation("androidx.appcompat:appcompat:1.4.0-rc01") implementation("androidx.appcompat:appcompat:1.4.0")
implementation("androidx.biometric:biometric-ktx:1.2.0-alpha04") implementation("androidx.biometric:biometric-ktx:1.2.0-alpha04")
implementation("androidx.browser:browser:1.4.0") implementation("androidx.browser:browser:1.4.0")
implementation("androidx.constraintlayout:constraintlayout:2.1.1") implementation("androidx.constraintlayout:constraintlayout:2.1.2")
implementation("androidx.coordinatorlayout:coordinatorlayout:1.2.0-beta01") implementation("androidx.coordinatorlayout:coordinatorlayout:1.2.0-beta01")
implementation("androidx.core:core-ktx:1.7.0") implementation("androidx.core:core-ktx:1.7.0")
implementation("androidx.core:core-splashscreen:1.0.0-alpha02") implementation("androidx.core:core-splashscreen:1.0.0-alpha02")

View file

@ -41,23 +41,32 @@ interface AnimeSource : tachiyomi.animesource.AnimeSource {
* *
* @param anime the anime to update. * @param anime the anime to update.
*/ */
@Deprecated("Use getAnimeDetails instead") @Deprecated(
fun fetchAnimeDetails(anime: SAnime): Observable<SAnime> = Observable.empty() "Use the 1.x API instead",
ReplaceWith("getAnimeDetails")
)
fun fetchAnimeDetails(anime: SAnime): Observable<SAnime> = throw IllegalStateException("Not used")
/** /**
* Returns an observable with all the available episodes for an anime. * Returns an observable with all the available episodes for an anime.
* *
* @param anime the anime to update. * @param anime the anime to update.
*/ */
@Deprecated("Use getEpisodeList instead") @Deprecated(
fun fetchEpisodeList(anime: SAnime): Observable<List<SEpisode>> = Observable.empty() "Use the 1.x API instead",
ReplaceWith("getEpisodeList")
)
fun fetchEpisodeList(anime: SAnime): Observable<List<SEpisode>> = throw IllegalStateException("Not used")
/** /**
* Returns an observable with a list of video for the episode of an anime. * Returns an observable with a list of video for the episode of an anime.
* *
* @param episode the episode to get the link for. * @param episode the episode to get the link for.
*/ */
@Deprecated("Use getVideoList instead") @Deprecated(
"Use the 1.x API instead",
ReplaceWith("getVideoList")
)
fun fetchVideoList(episode: SEpisode): Observable<List<Video>> = Observable.empty() fun fetchVideoList(episode: SEpisode): Observable<List<Video>> = Observable.empty()
/** /**

View file

@ -9,6 +9,7 @@ import eu.kanade.tachiyomi.animesource.model.SAnime
import eu.kanade.tachiyomi.animesource.model.SEpisode import eu.kanade.tachiyomi.animesource.model.SEpisode
import eu.kanade.tachiyomi.animesource.model.toEpisodeInfo import eu.kanade.tachiyomi.animesource.model.toEpisodeInfo
import eu.kanade.tachiyomi.animesource.model.toSAnime import eu.kanade.tachiyomi.animesource.model.toSAnime
import eu.kanade.tachiyomi.source.UnmeteredSource
import eu.kanade.tachiyomi.util.episode.EpisodeRecognition import eu.kanade.tachiyomi.util.episode.EpisodeRecognition
import eu.kanade.tachiyomi.util.lang.compareToCaseInsensitiveNaturalOrder import eu.kanade.tachiyomi.util.lang.compareToCaseInsensitiveNaturalOrder
import eu.kanade.tachiyomi.util.storage.DiskUtil import eu.kanade.tachiyomi.util.storage.DiskUtil
@ -30,7 +31,7 @@ import java.io.InputStream
import java.util.Locale import java.util.Locale
import java.util.concurrent.TimeUnit import java.util.concurrent.TimeUnit
class LocalAnimeSource(private val context: Context) : AnimeCatalogueSource { class LocalAnimeSource(private val context: Context) : AnimeCatalogueSource, UnmeteredSource {
companion object { companion object {
const val ID = 0L const val ID = 0L
const val HELP_URL = "https://aniyomi.jmir.xyz/help/guides/local-anime/" const val HELP_URL = "https://aniyomi.jmir.xyz/help/guides/local-anime/"

View file

@ -28,6 +28,7 @@ import eu.kanade.tachiyomi.data.preference.PreferencesHelper
import eu.kanade.tachiyomi.data.track.EnhancedTrackService import eu.kanade.tachiyomi.data.track.EnhancedTrackService
import eu.kanade.tachiyomi.data.track.TrackManager import eu.kanade.tachiyomi.data.track.TrackManager
import eu.kanade.tachiyomi.data.track.TrackService import eu.kanade.tachiyomi.data.track.TrackService
import eu.kanade.tachiyomi.source.UnmeteredSource
import eu.kanade.tachiyomi.util.episode.NoEpisodesException import eu.kanade.tachiyomi.util.episode.NoEpisodesException
import eu.kanade.tachiyomi.util.episode.syncEpisodesWithSource import eu.kanade.tachiyomi.util.episode.syncEpisodesWithSource
import eu.kanade.tachiyomi.util.episode.syncEpisodesWithTrackServiceTwoWay import eu.kanade.tachiyomi.util.episode.syncEpisodesWithTrackServiceTwoWay
@ -267,7 +268,10 @@ class AnimelibUpdateService(
.sortedWith(rankingScheme[selectedScheme]) .sortedWith(rankingScheme[selectedScheme])
// Warn when excessively checking a single source // Warn when excessively checking a single source
val maxUpdatesFromSource = animeToUpdate.groupBy { it.source }.maxOfOrNull { it.value.size } ?: 0 val maxUpdatesFromSource = animeToUpdate
.groupBy { it.source }
.filterKeys { sourceManager.get(it) !is UnmeteredSource }
.maxOfOrNull { it.value.size } ?: 0
if (maxUpdatesFromSource > ANIME_PER_SOURCE_QUEUE_WARNING_THRESHOLD) { if (maxUpdatesFromSource > ANIME_PER_SOURCE_QUEUE_WARNING_THRESHOLD) {
toast(R.string.notification_size_warning, Toast.LENGTH_LONG) toast(R.string.notification_size_warning, Toast.LENGTH_LONG)
} }

View file

@ -26,6 +26,7 @@ import eu.kanade.tachiyomi.data.database.models.Episode
import eu.kanade.tachiyomi.data.download.model.AnimeDownload import eu.kanade.tachiyomi.data.download.model.AnimeDownload
import eu.kanade.tachiyomi.data.download.model.AnimeDownloadQueue import eu.kanade.tachiyomi.data.download.model.AnimeDownloadQueue
import eu.kanade.tachiyomi.data.preference.PreferencesHelper import eu.kanade.tachiyomi.data.preference.PreferencesHelper
import eu.kanade.tachiyomi.source.UnmeteredSource
import eu.kanade.tachiyomi.util.lang.launchIO import eu.kanade.tachiyomi.util.lang.launchIO
import eu.kanade.tachiyomi.util.lang.launchNow import eu.kanade.tachiyomi.util.lang.launchNow
import eu.kanade.tachiyomi.util.lang.plusAssign import eu.kanade.tachiyomi.util.lang.plusAssign
@ -297,7 +298,10 @@ class AnimeDownloader(
// Start downloader if needed // Start downloader if needed
if (autoStart && wasEmpty) { if (autoStart && wasEmpty) {
val maxDownloadsFromSource = queue.groupBy { it.source }.maxOf { it.value.size } val maxDownloadsFromSource = queue
.groupBy { it.source }
.filterKeys { it !is UnmeteredSource }
.maxOf { it.value.size }
if (maxDownloadsFromSource > EPISODES_PER_SOURCE_QUEUE_WARNING_THRESHOLD) { if (maxDownloadsFromSource > EPISODES_PER_SOURCE_QUEUE_WARNING_THRESHOLD) {
withUIContext { withUIContext {
context.toast(R.string.download_queue_size_warning, Toast.LENGTH_LONG) context.toast(R.string.download_queue_size_warning, Toast.LENGTH_LONG)

View file

@ -13,6 +13,7 @@ import eu.kanade.tachiyomi.data.database.models.Manga
import eu.kanade.tachiyomi.data.download.model.Download import eu.kanade.tachiyomi.data.download.model.Download
import eu.kanade.tachiyomi.data.download.model.DownloadQueue import eu.kanade.tachiyomi.data.download.model.DownloadQueue
import eu.kanade.tachiyomi.source.SourceManager import eu.kanade.tachiyomi.source.SourceManager
import eu.kanade.tachiyomi.source.UnmeteredSource
import eu.kanade.tachiyomi.source.model.Page import eu.kanade.tachiyomi.source.model.Page
import eu.kanade.tachiyomi.source.online.HttpSource import eu.kanade.tachiyomi.source.online.HttpSource
import eu.kanade.tachiyomi.source.online.fetchAllImageUrlsFromPageList import eu.kanade.tachiyomi.source.online.fetchAllImageUrlsFromPageList
@ -266,7 +267,10 @@ class Downloader(
// Start downloader if needed // Start downloader if needed
if (autoStart && wasEmpty) { if (autoStart && wasEmpty) {
val maxDownloadsFromSource = queue.groupBy { it.source }.maxOf { it.value.size } val maxDownloadsFromSource = queue
.groupBy { it.source }
.filterKeys { it !is UnmeteredSource }
.maxOf { it.value.size }
if (maxDownloadsFromSource > CHAPTERS_PER_SOURCE_QUEUE_WARNING_THRESHOLD) { if (maxDownloadsFromSource > CHAPTERS_PER_SOURCE_QUEUE_WARNING_THRESHOLD) {
withUIContext { withUIContext {
context.toast(R.string.download_queue_size_warning, Toast.LENGTH_LONG) context.toast(R.string.download_queue_size_warning, Toast.LENGTH_LONG)

View file

@ -25,6 +25,7 @@ import eu.kanade.tachiyomi.data.track.EnhancedTrackService
import eu.kanade.tachiyomi.data.track.TrackManager import eu.kanade.tachiyomi.data.track.TrackManager
import eu.kanade.tachiyomi.data.track.TrackService import eu.kanade.tachiyomi.data.track.TrackService
import eu.kanade.tachiyomi.source.SourceManager import eu.kanade.tachiyomi.source.SourceManager
import eu.kanade.tachiyomi.source.UnmeteredSource
import eu.kanade.tachiyomi.source.model.SManga import eu.kanade.tachiyomi.source.model.SManga
import eu.kanade.tachiyomi.source.model.toSChapter import eu.kanade.tachiyomi.source.model.toSChapter
import eu.kanade.tachiyomi.source.model.toSManga import eu.kanade.tachiyomi.source.model.toSManga
@ -267,7 +268,10 @@ class LibraryUpdateService(
.sortedWith(rankingScheme[selectedScheme]) .sortedWith(rankingScheme[selectedScheme])
// Warn when excessively checking a single source // Warn when excessively checking a single source
val maxUpdatesFromSource = mangaToUpdate.groupBy { it.source }.maxOfOrNull { it.value.size } ?: 0 val maxUpdatesFromSource = mangaToUpdate
.groupBy { it.source }
.filterKeys { sourceManager.get(it) !is UnmeteredSource }
.maxOfOrNull { it.value.size } ?: 0
if (maxUpdatesFromSource > MANGA_PER_SOURCE_QUEUE_WARNING_THRESHOLD) { if (maxUpdatesFromSource > MANGA_PER_SOURCE_QUEUE_WARNING_THRESHOLD) {
toast(R.string.notification_size_warning, Toast.LENGTH_LONG) toast(R.string.notification_size_warning, Toast.LENGTH_LONG)
} }

View file

@ -8,7 +8,7 @@ import eu.kanade.tachiyomi.data.track.model.AnimeTrackSearch
import eu.kanade.tachiyomi.data.track.model.TrackSearch import eu.kanade.tachiyomi.data.track.model.TrackSearch
import eu.kanade.tachiyomi.network.POST import eu.kanade.tachiyomi.network.POST
import eu.kanade.tachiyomi.network.await import eu.kanade.tachiyomi.network.await
import eu.kanade.tachiyomi.network.interceptor.RateLimitInterceptor import eu.kanade.tachiyomi.network.interceptor.rateLimit
import eu.kanade.tachiyomi.network.jsonMime import eu.kanade.tachiyomi.network.jsonMime
import eu.kanade.tachiyomi.network.parseAs import eu.kanade.tachiyomi.network.parseAs
import eu.kanade.tachiyomi.util.lang.withIOContext import eu.kanade.tachiyomi.util.lang.withIOContext
@ -27,13 +27,13 @@ import kotlinx.serialization.json.putJsonObject
import okhttp3.OkHttpClient import okhttp3.OkHttpClient
import okhttp3.RequestBody.Companion.toRequestBody import okhttp3.RequestBody.Companion.toRequestBody
import java.util.Calendar import java.util.Calendar
import java.util.concurrent.TimeUnit.MINUTES import java.util.concurrent.TimeUnit
class AnilistApi(val client: OkHttpClient, interceptor: AnilistInterceptor) { class AnilistApi(val client: OkHttpClient, interceptor: AnilistInterceptor) {
private val authClient = client.newBuilder() private val authClient = client.newBuilder()
.addInterceptor(interceptor) .addInterceptor(interceptor)
.addInterceptor(RateLimitInterceptor(85, 1, MINUTES)) .rateLimit(permits = 85, period = 1, unit = TimeUnit.MINUTES)
.build() .build()
suspend fun addLibManga(track: Track): Track { suspend fun addLibManga(track: Track): Track {

View file

@ -3,6 +3,7 @@ package eu.kanade.tachiyomi.extension
import android.content.Context import android.content.Context
import android.graphics.drawable.Drawable import android.graphics.drawable.Drawable
import com.jakewharton.rxrelay.BehaviorRelay import com.jakewharton.rxrelay.BehaviorRelay
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.animesource.AnimeSource import eu.kanade.tachiyomi.animesource.AnimeSource
import eu.kanade.tachiyomi.animesource.AnimeSourceManager import eu.kanade.tachiyomi.animesource.AnimeSourceManager
import eu.kanade.tachiyomi.data.preference.PreferencesHelper import eu.kanade.tachiyomi.data.preference.PreferencesHelper
@ -15,8 +16,10 @@ import eu.kanade.tachiyomi.extension.util.AnimeExtensionInstallReceiver
import eu.kanade.tachiyomi.extension.util.AnimeExtensionInstaller import eu.kanade.tachiyomi.extension.util.AnimeExtensionInstaller
import eu.kanade.tachiyomi.extension.util.AnimeExtensionLoader import eu.kanade.tachiyomi.extension.util.AnimeExtensionLoader
import eu.kanade.tachiyomi.util.lang.launchNow import eu.kanade.tachiyomi.util.lang.launchNow
import eu.kanade.tachiyomi.util.system.logcat
import eu.kanade.tachiyomi.util.system.toast import eu.kanade.tachiyomi.util.system.toast
import kotlinx.coroutines.async import kotlinx.coroutines.async
import logcat.LogPriority
import rx.Observable import rx.Observable
import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get import uy.kohesive.injekt.api.get
@ -160,7 +163,8 @@ class AnimeExtensionManager(
val extensions: List<AnimeExtension.Available> = try { val extensions: List<AnimeExtension.Available> = try {
api.findExtensions() api.findExtensions()
} catch (e: Exception) { } catch (e: Exception) {
context.toast(e.message) logcat(LogPriority.ERROR, e)
context.toast(R.string.extension_api_error)
emptyList() emptyList()
} }

View file

@ -3,6 +3,7 @@ package eu.kanade.tachiyomi.extension
import android.content.Context import android.content.Context
import android.graphics.drawable.Drawable import android.graphics.drawable.Drawable
import com.jakewharton.rxrelay.BehaviorRelay import com.jakewharton.rxrelay.BehaviorRelay
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.preference.PreferencesHelper import eu.kanade.tachiyomi.data.preference.PreferencesHelper
import eu.kanade.tachiyomi.data.preference.plusAssign import eu.kanade.tachiyomi.data.preference.plusAssign
import eu.kanade.tachiyomi.extension.api.ExtensionGithubApi import eu.kanade.tachiyomi.extension.api.ExtensionGithubApi
@ -15,8 +16,10 @@ import eu.kanade.tachiyomi.extension.util.ExtensionLoader
import eu.kanade.tachiyomi.source.Source import eu.kanade.tachiyomi.source.Source
import eu.kanade.tachiyomi.source.SourceManager import eu.kanade.tachiyomi.source.SourceManager
import eu.kanade.tachiyomi.util.lang.launchNow import eu.kanade.tachiyomi.util.lang.launchNow
import eu.kanade.tachiyomi.util.system.logcat
import eu.kanade.tachiyomi.util.system.toast import eu.kanade.tachiyomi.util.system.toast
import kotlinx.coroutines.async import kotlinx.coroutines.async
import logcat.LogPriority
import rx.Observable import rx.Observable
import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get import uy.kohesive.injekt.api.get
@ -160,7 +163,8 @@ class ExtensionManager(
val extensions: List<Extension.Available> = try { val extensions: List<Extension.Available> = try {
api.findExtensions() api.findExtensions()
} catch (e: Exception) { } catch (e: Exception) {
context.toast(e.message) logcat(LogPriority.ERROR, e)
context.toast(R.string.extension_api_error)
emptyList() emptyList()
} }

View file

@ -22,11 +22,19 @@ internal class AnimeExtensionGithubApi {
suspend fun findExtensions(): List<AnimeExtension.Available> { suspend fun findExtensions(): List<AnimeExtension.Available> {
return withIOContext { return withIOContext {
networkService.client val extensions = networkService.client
.newCall(GET("${REPO_URL_PREFIX}index.min.json")) .newCall(GET("${REPO_URL_PREFIX}index.min.json"))
.await() .await()
.parseAs<List<AnimeExtensionJsonObject>>() .parseAs<List<AnimeExtensionJsonObject>>()
.toExtensions() .toExtensions()
// Sanity check - a small number of extensions probably means something broke
// with the repo generator
if (extensions.size < 10) {
throw Exception()
}
extensions
} }
} }

View file

@ -22,11 +22,19 @@ internal class ExtensionGithubApi {
suspend fun findExtensions(): List<Extension.Available> { suspend fun findExtensions(): List<Extension.Available> {
return withIOContext { return withIOContext {
networkService.client val extensions = networkService.client
.newCall(GET("${REPO_URL_PREFIX}index.min.json")) .newCall(GET("${REPO_URL_PREFIX}index.min.json"))
.await() .await()
.parseAs<List<ExtensionJsonObject>>() .parseAs<List<ExtensionJsonObject>>()
.toExtensions() .toExtensions()
// Sanity check - a small number of extensions probably means something broke
// with the repo generator
if (extensions.size < 100) {
throw Exception()
}
extensions
} }
} }

View file

@ -35,7 +35,7 @@ internal object AnimeExtensionLoader {
private const val METADATA_SOURCE_FACTORY = "tachiyomi.animeextension.factory" private const val METADATA_SOURCE_FACTORY = "tachiyomi.animeextension.factory"
private const val METADATA_NSFW = "tachiyomi.animeextension.nsfw" private const val METADATA_NSFW = "tachiyomi.animeextension.nsfw"
const val LIB_VERSION_MIN = 12 const val LIB_VERSION_MIN = 12
const val LIB_VERSION_MAX = 12 const val LIB_VERSION_MAX = 13
private const val PACKAGE_FLAGS = PackageManager.GET_CONFIGURATIONS or PackageManager.GET_SIGNATURES private const val PACKAGE_FLAGS = PackageManager.GET_CONFIGURATIONS or PackageManager.GET_SIGNATURES

View file

@ -35,7 +35,7 @@ internal object ExtensionLoader {
private const val METADATA_SOURCE_FACTORY = "tachiyomi.extension.factory" private const val METADATA_SOURCE_FACTORY = "tachiyomi.extension.factory"
private const val METADATA_NSFW = "tachiyomi.extension.nsfw" private const val METADATA_NSFW = "tachiyomi.extension.nsfw"
const val LIB_VERSION_MIN = 1.2 const val LIB_VERSION_MIN = 1.2
const val LIB_VERSION_MAX = 1.2 const val LIB_VERSION_MAX = 1.3
private const val PACKAGE_FLAGS = PackageManager.GET_CONFIGURATIONS or PackageManager.GET_SIGNATURES private const val PACKAGE_FLAGS = PackageManager.GET_CONFIGURATIONS or PackageManager.GET_SIGNATURES

View file

@ -2,6 +2,7 @@ package eu.kanade.tachiyomi.network.interceptor
import android.os.SystemClock import android.os.SystemClock
import okhttp3.Interceptor import okhttp3.Interceptor
import okhttp3.OkHttpClient
import okhttp3.Response import okhttp3.Response
import java.util.concurrent.TimeUnit import java.util.concurrent.TimeUnit
@ -17,10 +18,16 @@ import java.util.concurrent.TimeUnit
* @param period {Long} The limiting duration. Defaults to 1. * @param period {Long} The limiting duration. Defaults to 1.
* @param unit {TimeUnit} The unit of time for the period. Defaults to seconds. * @param unit {TimeUnit} The unit of time for the period. Defaults to seconds.
*/ */
class RateLimitInterceptor( fun OkHttpClient.Builder.rateLimit(
permits: Int,
period: Long = 1,
unit: TimeUnit = TimeUnit.SECONDS,
) = addInterceptor(RateLimitInterceptor(permits, period, unit))
private class RateLimitInterceptor(
private val permits: Int, private val permits: Int,
private val period: Long = 1, period: Long,
private val unit: TimeUnit = TimeUnit.SECONDS unit: TimeUnit,
) : Interceptor { ) : Interceptor {
private val requestQueue = ArrayList<Long>(permits) private val requestQueue = ArrayList<Long>(permits)

View file

@ -3,6 +3,7 @@ package eu.kanade.tachiyomi.network.interceptor
import android.os.SystemClock import android.os.SystemClock
import okhttp3.HttpUrl import okhttp3.HttpUrl
import okhttp3.Interceptor import okhttp3.Interceptor
import okhttp3.OkHttpClient
import okhttp3.Response import okhttp3.Response
import java.util.concurrent.TimeUnit import java.util.concurrent.TimeUnit
@ -19,11 +20,18 @@ import java.util.concurrent.TimeUnit
* @param period {Long} The limiting duration. Defaults to 1. * @param period {Long} The limiting duration. Defaults to 1.
* @param unit {TimeUnit} The unit of time for the period. Defaults to seconds. * @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(SpecificHostRateLimitInterceptor(httpUrl, permits, period, unit))
class SpecificHostRateLimitInterceptor( class SpecificHostRateLimitInterceptor(
private val httpUrl: HttpUrl, httpUrl: HttpUrl,
private val permits: Int, private val permits: Int,
private val period: Long = 1, period: Long,
private val unit: TimeUnit = TimeUnit.SECONDS unit: TimeUnit,
) : Interceptor { ) : Interceptor {
private val requestQueue = ArrayList<Long>(permits) private val requestQueue = ArrayList<Long>(permits)

View file

@ -38,7 +38,8 @@ import java.util.Locale
import java.util.concurrent.TimeUnit import java.util.concurrent.TimeUnit
import java.util.zip.ZipFile import java.util.zip.ZipFile
class LocalSource(private val context: Context) : CatalogueSource { class LocalSource(private val context: Context) : CatalogueSource, UnmeteredSource {
companion object { companion object {
const val ID = 0L const val ID = 0L
const val HELP_URL = "https://tachiyomi.org/help/guides/local-manga/" const val HELP_URL = "https://tachiyomi.org/help/guides/local-manga/"

View file

@ -40,23 +40,33 @@ interface Source : tachiyomi.source.Source {
* *
* @param manga the manga to update. * @param manga the manga to update.
*/ */
@Deprecated("Use getMangaDetails instead") @Deprecated(
fun fetchMangaDetails(manga: SManga): Observable<SManga> = Observable.empty() "Use the 1.x API instead",
ReplaceWith("getMangaDetails")
)
fun fetchMangaDetails(manga: SManga): Observable<SManga> = throw IllegalStateException("Not used")
/** /**
* Returns an observable with all the available chapters for a manga. * Returns an observable with all the available chapters for a manga.
* *
* @param manga the manga to update. * @param manga the manga to update.
*/ */
@Deprecated("Use getChapterList instead") @Deprecated(
fun fetchChapterList(manga: SManga): Observable<List<SChapter>> = Observable.empty() "Use the 1.x API instead",
ReplaceWith("getChapterList")
)
fun fetchChapterList(manga: SManga): Observable<List<SChapter>> = throw IllegalStateException("Not used")
// TODO: remove direct usages on this method
/** /**
* Returns an observable with the list of pages a chapter has. * Returns an observable with the list of pages a chapter has.
* *
* @param chapter the chapter. * @param chapter the chapter.
*/ */
@Deprecated("Use getPageList instead") @Deprecated(
"Use the 1.x API instead",
ReplaceWith("getPageList")
)
fun fetchPageList(chapter: SChapter): Observable<List<Page>> = Observable.empty() fun fetchPageList(chapter: SChapter): Observable<List<Page>> = Observable.empty()
/** /**

View file

@ -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

View file

@ -222,7 +222,7 @@ open class GlobalAnimeSearchPresenter(
Observable.from(first) Observable.from(first)
.filter { it.thumbnail_url == null && !it.initialized } .filter { it.thumbnail_url == null && !it.initialized }
.map { Pair(it, source) } .map { Pair(it, source) }
.concatMap { runAsObservable({ getAnimeDetails(it.first, it.second) }) } .concatMap { runAsObservable { getAnimeDetails(it.first, it.second) } }
.map { Pair(source as AnimeCatalogueSource, it) } .map { Pair(source as AnimeCatalogueSource, it) }
} }
.onBackpressureBuffer() .onBackpressureBuffer()

View file

@ -222,7 +222,7 @@ open class GlobalSearchPresenter(
Observable.from(first) Observable.from(first)
.filter { it.thumbnail_url == null && !it.initialized } .filter { it.thumbnail_url == null && !it.initialized }
.map { Pair(it, source) } .map { Pair(it, source) }
.concatMap { runAsObservable({ getMangaDetails(it.first, it.second) }) } .concatMap { runAsObservable { getMangaDetails(it.first, it.second) } }
.map { Pair(source as CatalogueSource, it) } .map { Pair(source as CatalogueSource, it) }
} }
.onBackpressureBuffer() .onBackpressureBuffer()

View file

@ -60,8 +60,8 @@ internal fun <T> CancellableContinuation<T>.unsubscribeOnCancellation(sub: Subsc
invokeOnCancellation { sub.unsubscribe() } invokeOnCancellation { sub.unsubscribe() }
fun <T> runAsObservable( fun <T> runAsObservable(
backpressureMode: Emitter.BackpressureMode = Emitter.BackpressureMode.NONE,
block: suspend () -> T, block: suspend () -> T,
backpressureMode: Emitter.BackpressureMode = Emitter.BackpressureMode.NONE
): Observable<T> { ): Observable<T> {
return Observable.create( return Observable.create(
{ emitter -> { emitter ->

View file

@ -1,6 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<corners android:radius="@dimen/card_radius" />
<solid android:color="?attr/colorSurface" />
</shape>

View file

@ -301,6 +301,7 @@
<string name="untrusted_extension_message">This extension was signed with an untrusted certificate and wasn\'t activated.\n\nA malicious extension could read any login credentials stored in Tachiyomi or execute arbitrary code.\n\nBy trusting this certificate you accept these risks.</string> <string name="untrusted_extension_message">This extension was signed with an untrusted certificate and wasn\'t activated.\n\nA malicious extension could read any login credentials stored in Tachiyomi or execute arbitrary code.\n\nBy trusting this certificate you accept these risks.</string>
<string name="obsolete_extension_message">This extension is no longer available.</string> <string name="obsolete_extension_message">This extension is no longer available.</string>
<string name="unofficial_extension_message">This extension is not from the official Tachiyomi extensions list.</string> <string name="unofficial_extension_message">This extension is not from the official Tachiyomi extensions list.</string>
<string name="extension_api_error">Failed to get extensions list</string>
<string name="ext_version_info">Version: %1$s</string> <string name="ext_version_info">Version: %1$s</string>
<string name="ext_language_info">Language: %1$s</string> <string name="ext_language_info">Language: %1$s</string>
<string name="ext_nsfw_short">18+</string> <string name="ext_nsfw_short">18+</string>

View file

@ -102,15 +102,6 @@
</style> </style>
<style name="Widget.Tachiyomi.Snackbar" parent="Widget.Material3.Snackbar">
<item name="android:background">@drawable/snackbar_background</item>
<item name="actionTextColorAlpha">1</item>
</style>
<style name="Widget.Tachiyomi.Snackbar.TextView" parent="Widget.Material3.Snackbar.TextView">
<item name="android:textColor">?attr/colorOnSurface</item>
</style>
<style name="Widget.Tachiyomi.Chip.Action" parent="Widget.Material3.Chip.Suggestion"> <style name="Widget.Tachiyomi.Chip.Action" parent="Widget.Material3.Chip.Suggestion">
<item name="chipBackgroundColor">?attr/chipBackgroundColor</item> <item name="chipBackgroundColor">?attr/chipBackgroundColor</item>
<item name="android:textColor">?attr/chipTextColor</item> <item name="android:textColor">?attr/chipTextColor</item>

View file

@ -74,8 +74,6 @@
<item name="chipStyle">@style/Widget.Tachiyomi.Chip.Action</item> <item name="chipStyle">@style/Widget.Tachiyomi.Chip.Action</item>
<item name="chipTextColor">?android:attr/textColorPrimary</item> <item name="chipTextColor">?android:attr/textColorPrimary</item>
<item name="chipBackgroundColor">?android:attr/divider</item> <item name="chipBackgroundColor">?android:attr/divider</item>
<item name="snackbarStyle">@style/Widget.Tachiyomi.Snackbar</item>
<item name="snackbarTextViewStyle">@style/Widget.Tachiyomi.Snackbar.TextView</item>
<item name="textInputStyle">@style/Widget.Material3.TextInputLayout.OutlinedBox</item> <item name="textInputStyle">@style/Widget.Material3.TextInputLayout.OutlinedBox</item>
<item name="appBarLayoutStyle">@style/Widget.Material3.AppBarLayout</item> <item name="appBarLayoutStyle">@style/Widget.Material3.AppBarLayout</item>
<item name="toolbarStyle">@style/Widget.Material3.Toolbar.Surface</item> <item name="toolbarStyle">@style/Widget.Material3.Toolbar.Surface</item>