diff --git a/server/src/main/java/eu/kanade/tachiyomi/BuildConfig.java b/server/src/main/java/eu/kanade/tachiyomi/BuildConfig.java new file mode 100644 index 0000000000..a2574018b8 --- /dev/null +++ b/server/src/main/java/eu/kanade/tachiyomi/BuildConfig.java @@ -0,0 +1,9 @@ +package eu.kanade.tachiyomi; + +public class BuildConfig { + /** should be something like 74 */ + public static final int VERSION_CODE = Integer.parseInt(suwayomi.tachidesk.server.BuildConfig.REVISION.substring(1)); + + /** should be something like "0.13.1" */ + public static final String VERSION_NAME = suwayomi.tachidesk.server.BuildConfig.VERSION.substring(1); +} diff --git a/server/src/main/kotlin/eu/kanade/tachiyomi/AppInfo.kt b/server/src/main/kotlin/eu/kanade/tachiyomi/AppInfo.kt new file mode 100644 index 0000000000..7ec80cdb67 --- /dev/null +++ b/server/src/main/kotlin/eu/kanade/tachiyomi/AppInfo.kt @@ -0,0 +1,11 @@ +package eu.kanade.tachiyomi + +/** + * Used by extensions. + * + * @since extension-lib 1.3 + */ +object AppInfo { + fun getVersionCode() = BuildConfig.VERSION_CODE + fun getVersionName() = BuildConfig.VERSION_NAME +} diff --git a/server/src/main/kotlin/eu/kanade/tachiyomi/BuildConfig.java b/server/src/main/kotlin/eu/kanade/tachiyomi/BuildConfig.java deleted file mode 100644 index d42a96f25a..0000000000 --- a/server/src/main/kotlin/eu/kanade/tachiyomi/BuildConfig.java +++ /dev/null @@ -1,6 +0,0 @@ -package eu.kanade.tachiyomi; - -public class BuildConfig { - public static final int VERSION_CODE = -1; - public static final String VERSION_NAME = "stub"; -} diff --git a/server/src/main/kotlin/eu/kanade/tachiyomi/network/interceptor/RateLimitInterceptor.kt b/server/src/main/kotlin/eu/kanade/tachiyomi/network/interceptor/RateLimitInterceptor.kt new file mode 100644 index 0000000000..a776f2b5f7 --- /dev/null +++ b/server/src/main/kotlin/eu/kanade/tachiyomi/network/interceptor/RateLimitInterceptor.kt @@ -0,0 +1,67 @@ +package eu.kanade.tachiyomi.network.interceptor + +import android.os.SystemClock +import okhttp3.Interceptor +import okhttp3.OkHttpClient +import okhttp3.Response +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(permits, period, unit)) + +private class RateLimitInterceptor( + private val permits: Int, + period: Long, + unit: TimeUnit, +) : Interceptor { + + private val requestQueue = ArrayList(permits) + private val rateLimitMillis = unit.toMillis(period) + + override fun intercept(chain: Interceptor.Chain): Response { + synchronized(requestQueue) { + val now = SystemClock.elapsedRealtime() + val waitTime = if (requestQueue.size < permits) { + 0 + } else { + val oldestReq = requestQueue[0] + val newestReq = requestQueue[permits - 1] + + if (newestReq - oldestReq > rateLimitMillis) { + 0 + } else { + oldestReq + rateLimitMillis - now // Remaining time + } + } + + if (requestQueue.size == permits) { + requestQueue.removeAt(0) + } + if (waitTime > 0) { + requestQueue.add(now + waitTime) + Thread.sleep(waitTime) // Sleep inside synchronized to pause queued requests + } else { + requestQueue.add(now) + } + } + + return chain.proceed(chain.request()) + } +} diff --git a/server/src/main/kotlin/eu/kanade/tachiyomi/network/interceptor/SpecificHostRateLimitInterceptor.kt b/server/src/main/kotlin/eu/kanade/tachiyomi/network/interceptor/SpecificHostRateLimitInterceptor.kt new file mode 100644 index 0000000000..ec7a8fdb1d --- /dev/null +++ b/server/src/main/kotlin/eu/kanade/tachiyomi/network/interceptor/SpecificHostRateLimitInterceptor.kt @@ -0,0 +1,75 @@ +package eu.kanade.tachiyomi.network.interceptor + +import android.os.SystemClock +import okhttp3.HttpUrl +import okhttp3.Interceptor +import okhttp3.OkHttpClient +import okhttp3.Response +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(SpecificHostRateLimitInterceptor(httpUrl, permits, period, unit)) + +class SpecificHostRateLimitInterceptor( + httpUrl: HttpUrl, + private val permits: Int, + period: Long, + unit: TimeUnit, +) : Interceptor { + + private val requestQueue = ArrayList(permits) + private val rateLimitMillis = unit.toMillis(period) + private val host = httpUrl.host + + override fun intercept(chain: Interceptor.Chain): Response { + if (chain.request().url.host != host) { + return chain.proceed(chain.request()) + } + synchronized(requestQueue) { + val now = SystemClock.elapsedRealtime() + val waitTime = if (requestQueue.size < permits) { + 0 + } else { + val oldestReq = requestQueue[0] + val newestReq = requestQueue[permits - 1] + + if (newestReq - oldestReq > rateLimitMillis) { + 0 + } else { + oldestReq + rateLimitMillis - now // Remaining time + } + } + + if (requestQueue.size == permits) { + requestQueue.removeAt(0) + } + if (waitTime > 0) { + requestQueue.add(now + waitTime) + Thread.sleep(waitTime) // Sleep inside synchronized to pause queued requests + } else { + requestQueue.add(now) + } + } + + return chain.proceed(chain.request()) + } +} diff --git a/server/src/main/kotlin/eu/kanade/tachiyomi/source/UnmeteredSource.kt b/server/src/main/kotlin/eu/kanade/tachiyomi/source/UnmeteredSource.kt new file mode 100644 index 0000000000..7536ccb0ba --- /dev/null +++ b/server/src/main/kotlin/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/server/src/main/kotlin/eu/kanade/tachiyomi/source/model/SManga.kt b/server/src/main/kotlin/eu/kanade/tachiyomi/source/model/SManga.kt index 8b7d74d69a..28fd48a8c5 100644 --- a/server/src/main/kotlin/eu/kanade/tachiyomi/source/model/SManga.kt +++ b/server/src/main/kotlin/eu/kanade/tachiyomi/source/model/SManga.kt @@ -55,6 +55,9 @@ interface SManga : Serializable { const val ONGOING = 1 const val COMPLETED = 2 const val LICENSED = 3 + const val PUBLISHING_FINISHED = 4 + const val CANCELLED = 5 + const val ON_HIATUS = 6 fun create(): SManga { return SMangaImpl() diff --git a/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/util/PackageTools.kt b/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/util/PackageTools.kt index 19938d1732..a2ba85ce04 100644 --- a/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/util/PackageTools.kt +++ b/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/util/PackageTools.kt @@ -41,7 +41,7 @@ object PackageTools { const val METADATA_SOURCE_FACTORY = "tachiyomi.extension.factory" const val METADATA_NSFW = "tachiyomi.extension.nsfw" const val LIB_VERSION_MIN = 1.2 - const val LIB_VERSION_MAX = 1.2 + const val LIB_VERSION_MAX = 1.3 private const val officialSignature = "7ce04da7773d41b489f4693a366c36bcd0a11fc39b547168553c285bd7348e23" // inorichi's key private const val unofficialSignature = "64feb21075ba97ebc9cc981243645b331595c111cef1b0d084236a0403b00581" // ArMor's key