[skip ci] Formatting

This commit is contained in:
Syer10
2024-09-03 21:37:18 -04:00
parent e968a2195a
commit 6c1fbfa63b
220 changed files with 2493 additions and 2519 deletions

View File

@@ -32,7 +32,9 @@ import uy.kohesive.injekt.api.addSingleton
import uy.kohesive.injekt.api.addSingletonFactory
import uy.kohesive.injekt.api.get
class AppModule(val app: Application) : InjektModule {
class AppModule(
val app: Application,
) : InjektModule {
override fun InjektRegistrar.registerInjectables() {
addSingleton(app)

View File

@@ -49,7 +49,9 @@ class MemoryCookieJar : CookieJar {
}
}
class WrappedCookie private constructor(val cookie: Cookie) {
class WrappedCookie private constructor(
val cookie: Cookie,
) {
fun unwrap() = cookie
fun isExpired() = cookie.expiresAt < System.currentTimeMillis()

View File

@@ -30,7 +30,9 @@ import java.net.CookieManager
import java.net.CookiePolicy
import java.util.concurrent.TimeUnit
class NetworkHelper(context: Context) {
class NetworkHelper(
context: Context,
) {
// private val preferences: PreferencesHelper by injectLazy()
// private val cacheDir = File(context.cacheDir, "network_cache")
@@ -53,9 +55,7 @@ class NetworkHelper(context: Context) {
"AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36",
)
fun defaultUserAgentProvider(): String {
return userAgent.value
}
fun defaultUserAgentProvider(): String = userAgent.value
init {
@OptIn(DelicateCoroutinesApi::class)
@@ -63,14 +63,14 @@ class NetworkHelper(context: Context) {
.drop(1)
.onEach {
GetCatalogueSource.unregisterAllCatalogueSources() // need to reset the headers
}
.launchIn(GlobalScope)
}.launchIn(GlobalScope)
}
private val baseClientBuilder: OkHttpClient.Builder
get() {
val builder =
OkHttpClient.Builder()
OkHttpClient
.Builder()
.cookieJar(PersistentCookieJar(cookieStore))
.connectTimeout(30, TimeUnit.SECONDS)
.readTimeout(30, TimeUnit.SECONDS)
@@ -80,8 +80,7 @@ class NetworkHelper(context: Context) {
directory = File.createTempFile("tachidesk_network_cache", null),
maxSize = 5L * 1024 * 1024, // 5 MiB
),
)
.addInterceptor(UncaughtExceptionInterceptor())
).addInterceptor(UncaughtExceptionInterceptor())
.addInterceptor(UserAgentInterceptor(::defaultUserAgentProvider))
.addNetworkInterceptor(IgnoreGzipInterceptor())
.addNetworkInterceptor(BrotliInterceptor)

View File

@@ -50,9 +50,7 @@ fun Call.asObservable(): Observable<Response> {
// call.cancel()
}
override fun isUnsubscribed(): Boolean {
return call.isCanceled()
}
override fun isUnsubscribed(): Boolean = call.isCanceled()
}
subscriber.add(requestArbiter)
@@ -60,15 +58,14 @@ fun Call.asObservable(): Observable<Response> {
}
}
fun Call.asObservableSuccess(): Observable<Response> {
return asObservable()
fun Call.asObservableSuccess(): Observable<Response> =
asObservable()
.doOnNext { response ->
if (!response.isSuccessful) {
response.close()
throw HttpException(response.code)
}
}
}
// Based on https://github.com/gildor/kotlin-coroutines-okhttp
@OptIn(ExperimentalCoroutinesApi::class)
@@ -135,29 +132,28 @@ fun OkHttpClient.newCachelessCallWithProgress(
.cache(null)
.addNetworkInterceptor { chain ->
val originalResponse = chain.proceed(chain.request())
originalResponse.newBuilder()
originalResponse
.newBuilder()
.body(ProgressResponseBody(originalResponse.body, listener))
.build()
}
.build()
}.build()
return progressClient.newCall(request)
}
context(Json)
inline fun <reified T> Response.parseAs(): T {
return decodeFromJsonResponse(serializer(), this)
}
inline fun <reified T> Response.parseAs(): T = decodeFromJsonResponse(serializer(), this)
context(Json)
@OptIn(ExperimentalSerializationApi::class)
fun <T> decodeFromJsonResponse(
deserializer: DeserializationStrategy<T>,
response: Response,
): T {
return response.body.source().use {
): T =
response.body.source().use {
decodeFromBufferedSource(deserializer, it)
}
}
class HttpException(val code: Int) : IllegalStateException("HTTP error $code")
class HttpException(
val code: Int,
) : IllegalStateException("HTTP error $code")

View File

@@ -5,7 +5,9 @@ import okhttp3.CookieJar
import okhttp3.HttpUrl
// from TachiWeb-Server
class PersistentCookieJar(private val store: PersistentCookieStore) : CookieJar {
class PersistentCookieJar(
private val store: PersistentCookieStore,
) : CookieJar {
override fun saveFromResponse(
url: HttpUrl,
cookies: List<Cookie>,
@@ -13,7 +15,5 @@ class PersistentCookieJar(private val store: PersistentCookieStore) : CookieJar
store.addAll(url, cookies)
}
override fun loadForRequest(url: HttpUrl): List<Cookie> {
return store.get(url)
}
override fun loadForRequest(url: HttpUrl): List<Cookie> = store.get(url)
}

View File

@@ -15,7 +15,9 @@ import kotlin.time.Duration.Companion.milliseconds
import kotlin.time.Duration.Companion.seconds
// from TachiWeb-Server
class PersistentCookieStore(context: Context) : CookieStore {
class PersistentCookieStore(
context: Context,
) : CookieStore {
private val cookieMap = ConcurrentHashMap<String, List<Cookie>>()
private val prefs = context.getSharedPreferences("cookie_store", Context.MODE_PRIVATE)
@@ -23,7 +25,8 @@ class PersistentCookieStore(context: Context) : CookieStore {
init {
val domains =
prefs.all.keys.map { it.substringBeforeLast(".") }
prefs.all.keys
.map { it.substringBeforeLast(".") }
.toSet()
domains.forEach { domain ->
val cookies = prefs.getStringSet(domain, emptySet())
@@ -31,7 +34,8 @@ class PersistentCookieStore(context: Context) : CookieStore {
try {
val url = "http://$domain".toHttpUrlOrNull() ?: return@forEach
val nonExpiredCookies =
cookies.mapNotNull { Cookie.parse(url, it) }
cookies
.mapNotNull { Cookie.parse(url, it) }
.filter { !it.hasExpired() }
cookieMap[domain] = nonExpiredCookies
} catch (e: Exception) {
@@ -63,14 +67,13 @@ class PersistentCookieStore(context: Context) : CookieStore {
}
}
override fun removeAll(): Boolean {
return lock.withLock {
override fun removeAll(): Boolean =
lock.withLock {
val wasNotEmpty = cookieMap.isEmpty()
prefs.edit().clear().apply()
cookieMap.clear()
wasNotEmpty
}
}
fun remove(uri: URI) {
val url = uri.toURL()
@@ -87,9 +90,7 @@ class PersistentCookieStore(context: Context) : CookieStore {
}
}
fun get(url: HttpUrl): List<Cookie> {
return get(url.host)
}
fun get(url: HttpUrl): List<Cookie> = get(url.host)
override fun add(
uri: URI?,
@@ -105,19 +106,17 @@ class PersistentCookieStore(context: Context) : CookieStore {
}
}
override fun getCookies(): List<HttpCookie> {
return cookieMap.values.flatMap {
override fun getCookies(): List<HttpCookie> =
cookieMap.values.flatMap {
it.map {
it.toHttpCookie()
}
}
}
override fun getURIs(): List<URI> {
return cookieMap.keys().toList().map {
override fun getURIs(): List<URI> =
cookieMap.keys().toList().map {
URI("http://$it")
}
}
override fun remove(
uri: URI?,
@@ -145,9 +144,7 @@ class PersistentCookieStore(context: Context) : CookieStore {
}
}
private fun get(url: String): List<Cookie> {
return cookieMap[url].orEmpty().filter { !it.hasExpired() }
}
private fun get(url: String): List<Cookie> = cookieMap[url].orEmpty().filter { !it.hasExpired() }
private fun saveToDisk(url: URL) {
// Get cookies to be stored in disk
@@ -165,7 +162,8 @@ class PersistentCookieStore(context: Context) : CookieStore {
private fun Cookie.hasExpired() = System.currentTimeMillis() >= expiresAt
private fun HttpCookie.toCookie(uri: URI) =
Cookie.Builder()
Cookie
.Builder()
.name(name)
.value(value)
.domain(uri.toURL().host)
@@ -176,22 +174,19 @@ class PersistentCookieStore(context: Context) : CookieStore {
} else {
it.expiresAt(Long.MAX_VALUE)
}
}
.let {
}.let {
if (secure) {
it.secure()
} else {
it
}
}
.let {
}.let {
if (isHttpOnly) {
it.httpOnly()
} else {
it
}
}
.build()
}.build()
private fun Cookie.toHttpCookie(): HttpCookie {
val it = this

View File

@@ -9,22 +9,19 @@ import okio.Source
import okio.buffer
import java.io.IOException
class ProgressResponseBody(private val responseBody: ResponseBody, private val progressListener: ProgressListener) : ResponseBody() {
class ProgressResponseBody(
private val responseBody: ResponseBody,
private val progressListener: ProgressListener,
) : ResponseBody() {
private val bufferedSource: BufferedSource by lazy {
source(responseBody.source()).buffer()
}
override fun contentType(): MediaType? {
return responseBody.contentType()
}
override fun contentType(): MediaType? = responseBody.contentType()
override fun contentLength(): Long {
return responseBody.contentLength()
}
override fun contentLength(): Long = responseBody.contentLength()
override fun source(): BufferedSource {
return bufferedSource
}
override fun source(): BufferedSource = bufferedSource
private fun source(source: Source): Source {
return object : ForwardingSource(source) {

View File

@@ -18,13 +18,13 @@ fun GET(
url: String,
headers: Headers = DEFAULT_HEADERS,
cache: CacheControl = DEFAULT_CACHE_CONTROL,
): Request {
return Request.Builder()
): Request =
Request
.Builder()
.url(url)
.headers(headers)
.cacheControl(cache)
.build()
}
/**
* @since extensions-lib 1.4
@@ -33,52 +33,52 @@ fun GET(
url: HttpUrl,
headers: Headers = DEFAULT_HEADERS,
cache: CacheControl = DEFAULT_CACHE_CONTROL,
): Request {
return Request.Builder()
): Request =
Request
.Builder()
.url(url)
.headers(headers)
.cacheControl(cache)
.build()
}
fun POST(
url: String,
headers: Headers = DEFAULT_HEADERS,
body: RequestBody = DEFAULT_BODY,
cache: CacheControl = DEFAULT_CACHE_CONTROL,
): Request {
return Request.Builder()
): Request =
Request
.Builder()
.url(url)
.post(body)
.headers(headers)
.cacheControl(cache)
.build()
}
fun PUT(
url: String,
headers: Headers = DEFAULT_HEADERS,
body: RequestBody = DEFAULT_BODY,
cache: CacheControl = DEFAULT_CACHE_CONTROL,
): Request {
return Request.Builder()
): Request =
Request
.Builder()
.url(url)
.put(body)
.headers(headers)
.cacheControl(cache)
.build()
}
fun DELETE(
url: String,
headers: Headers = DEFAULT_HEADERS,
body: RequestBody = DEFAULT_BODY,
cache: CacheControl = DEFAULT_CACHE_CONTROL,
): Request {
return Request.Builder()
): Request =
Request
.Builder()
.url(url)
.delete(body)
.headers(headers)
.cacheControl(cache)
.build()
}

View File

@@ -117,12 +117,12 @@ object CFClearance {
serverConfig.flareSolverrTimeout
.map { timeoutInt ->
val timeout = timeoutInt.seconds
network.client.newBuilder()
network.client
.newBuilder()
.callTimeout(timeout.plus(10.seconds).toJavaDuration())
.readTimeout(timeout.plus(5.seconds).toJavaDuration())
.build()
}
.stateIn(GlobalScope, SharingStarted.Eagerly, network.client)
}.stateIn(GlobalScope, SharingStarted.Eagerly, network.client)
}
private val json: Json by injectLazy()
private val jsonMediaType = "application/json".toMediaType()
@@ -190,26 +190,29 @@ object CFClearance {
return with(json) {
mutex.withLock {
client.value.newCall(
POST(
url = serverConfig.flareSolverrUrl.value.removeSuffix("/") + "/v1",
body =
Json.encodeToString(
FlareSolverRequest(
"request.get",
originalRequest.url.toString(),
session = serverConfig.flareSolverrSessionName.value,
sessionTtlMinutes = serverConfig.flareSolverrSessionTtl.value,
cookies =
network.cookieStore.get(originalRequest.url).map {
FlareSolverCookie(it.name, it.value)
},
returnOnlyCookies = onlyCookies,
maxTimeout = timeout.inWholeMilliseconds.toInt(),
),
).toRequestBody(jsonMediaType),
),
).awaitSuccess().parseAs<FlareSolverResponse>()
client.value
.newCall(
POST(
url = serverConfig.flareSolverrUrl.value.removeSuffix("/") + "/v1",
body =
Json
.encodeToString(
FlareSolverRequest(
"request.get",
originalRequest.url.toString(),
session = serverConfig.flareSolverrSessionName.value,
sessionTtlMinutes = serverConfig.flareSolverrSessionTtl.value,
cookies =
network.cookieStore.get(originalRequest.url).map {
FlareSolverCookie(it.name, it.value)
},
returnOnlyCookies = onlyCookies,
maxTimeout = timeout.inWholeMilliseconds.toInt(),
),
).toRequestBody(jsonMediaType),
),
).awaitSuccess()
.parseAs<FlareSolverResponse>()
}
}
}
@@ -224,7 +227,8 @@ object CFClearance {
val cookies =
flareSolverResponse.solution.cookies
.map { cookie ->
Cookie.Builder()
Cookie
.Builder()
.name(cookie.name)
.value(cookie.value)
.domain(cookie.domain.removePrefix("."))
@@ -233,13 +237,12 @@ object CFClearance {
if (cookie.httpOnly != null && cookie.httpOnly) it.httpOnly()
if (cookie.secure != null && cookie.secure) it.secure()
if (!cookie.path.isNullOrEmpty()) it.path(cookie.path)
}
.build()
}
.groupBy { it.domain }
}.build()
}.groupBy { it.domain }
.flatMap { (domain, cookies) ->
network.cookieStore.addAll(
HttpUrl.Builder()
HttpUrl
.Builder()
.scheme("http")
.host(domain.removePrefix("."))
.build(),
@@ -254,7 +257,8 @@ object CFClearance {
"${it.name}=${it.value}"
}
logger.trace { "Final cookies\n$finalCookies" }
return originalRequest.newBuilder()
return originalRequest
.newBuilder()
.header("Cookie", finalCookies)
.header("User-Agent", flareSolverResponse.solution.userAgent)
.build()

View File

@@ -13,8 +13,8 @@ import java.io.IOException
* See https://square.github.io/okhttp/4.x/okhttp/okhttp3/-interceptor/
*/
class UncaughtExceptionInterceptor : Interceptor {
override fun intercept(chain: Interceptor.Chain): Response {
return try {
override fun intercept(chain: Interceptor.Chain): Response =
try {
chain.proceed(chain.request())
} catch (e: Exception) {
if (e is IOException) {
@@ -23,5 +23,4 @@ class UncaughtExceptionInterceptor : Interceptor {
throw IOException(e)
}
}
}
}

View File

@@ -3,7 +3,9 @@ package eu.kanade.tachiyomi.network.interceptor
import okhttp3.Interceptor
import okhttp3.Response
class UserAgentInterceptor(private val userAgentProvider: () -> String) : Interceptor {
class UserAgentInterceptor(
private val userAgentProvider: () -> String,
) : Interceptor {
override fun intercept(chain: Interceptor.Chain): Response {
val originalRequest = chain.request()

View File

@@ -23,9 +23,7 @@ interface CatalogueSource : Source {
* @param page the page number to retrieve.
*/
@Suppress("DEPRECATION")
suspend fun getPopularManga(page: Int): MangasPage {
return fetchPopularManga(page).awaitSingle()
}
suspend fun getPopularManga(page: Int): MangasPage = fetchPopularManga(page).awaitSingle()
/**
* Get a page with a list of manga.
@@ -40,9 +38,7 @@ interface CatalogueSource : Source {
page: Int,
query: String,
filters: FilterList,
): MangasPage {
return fetchSearchManga(page, query, filters).awaitSingle()
}
): MangasPage = fetchSearchManga(page, query, filters).awaitSingle()
/**
* Get a page with a list of latest manga updates.
@@ -51,9 +47,7 @@ interface CatalogueSource : Source {
* @param page the page number to retrieve.
*/
@Suppress("DEPRECATION")
suspend fun getLatestUpdates(page: Int): MangasPage {
return fetchLatestUpdates(page).awaitSingle()
}
suspend fun getLatestUpdates(page: Int): MangasPage = fetchLatestUpdates(page).awaitSingle()
/**
* Returns the list of filters for the source.

View File

@@ -31,9 +31,7 @@ interface Source {
* @return the updated manga.
*/
@Suppress("DEPRECATION")
suspend fun getMangaDetails(manga: SManga): SManga {
return fetchMangaDetails(manga).awaitSingle()
}
suspend fun getMangaDetails(manga: SManga): SManga = fetchMangaDetails(manga).awaitSingle()
/**
* Get all the available chapters for a manga.
@@ -43,9 +41,7 @@ interface Source {
* @return the chapters for the manga.
*/
@Suppress("DEPRECATION")
suspend fun getChapterList(manga: SManga): List<SChapter> {
return fetchChapterList(manga).awaitSingle()
}
suspend fun getChapterList(manga: SManga): List<SChapter> = fetchChapterList(manga).awaitSingle()
/**
* Get the list of pages a chapter has. Pages should be returned
@@ -56,9 +52,7 @@ interface Source {
* @return the pages for the chapter.
*/
@Suppress("DEPRECATION")
suspend fun getPageList(chapter: SChapter): List<Page> {
return fetchPageList(chapter).awaitSingle()
}
suspend fun getPageList(chapter: SChapter): List<Page> = fetchPageList(chapter).awaitSingle()
@Deprecated(
"Use the non-RxJava API instead",

View File

@@ -59,7 +59,8 @@ import com.github.junrar.Archive as JunrarArchive
class LocalSource(
private val fileSystem: LocalSourceFileSystem,
private val coverManager: LocalCoverManager,
) : CatalogueSource, UnmeteredSource {
) : CatalogueSource,
UnmeteredSource {
private val json: Json by injectLazy()
private val xml: XML by injectLazy()
@@ -93,7 +94,8 @@ class LocalSource(
// Filter out files that are hidden and is not a folder
.filter { it.isDirectory && !it.name.startsWith('.') }
.distinctBy { it.name }
.filter { // Filter by query or last modified
.filter {
// Filter by query or last modified
if (lastModifiedLimit == 0L) {
it.name.contains(query, ignoreCase = true)
} else {
@@ -134,7 +136,8 @@ class LocalSource(
url = mangaDir.name
// Try to find the cover
coverManager.find(mangaDir.name)
coverManager
.find(mangaDir.name)
?.takeIf(File::exists)
?.let { thumbnail_url = it.absolutePath }
}
@@ -238,7 +241,7 @@ class LocalSource(
for (chapter in chapterArchives) {
when (Format.valueOf(chapter)) {
is Format.Zip -> {
ZipFile(chapter).use { zip: ZipFile ->
ZipFile.builder().setFile(chapter).get().use { zip: ZipFile ->
zip.getEntry(COMIC_INFO_FILE)?.let { comicInfoFile ->
zip.getInputStream(comicInfoFile).buffered().use { stream ->
return copyComicInfoFile(stream, folderPath)
@@ -264,13 +267,12 @@ class LocalSource(
private fun copyComicInfoFile(
comicInfoFileStream: InputStream,
folderPath: String?,
): File {
return File("$folderPath/$COMIC_INFO_FILE").apply {
): File =
File("$folderPath/$COMIC_INFO_FILE").apply {
outputStream().use { outputStream ->
comicInfoFileStream.use { it.copyTo(outputStream) }
}
}
}
@OptIn(ExperimentalXmlUtilApi::class)
private fun setMangaDetailsFromComicInfoFile(
@@ -286,8 +288,9 @@ class LocalSource(
}
// Chapters
override suspend fun getChapterList(manga: SManga): List<SChapter> {
return fileSystem.getFilesInMangaDirectory(manga.url)
override suspend fun getChapterList(manga: SManga): List<SChapter> =
fileSystem
.getFilesInMangaDirectory(manga.url)
// Only keep supported formats
.filter { it.isDirectory || Archive.isSupported(it) }
.map { chapterFile ->
@@ -312,22 +315,21 @@ class LocalSource(
}
}
}
}
.sortedWith { c1, c2 ->
}.sortedWith { c1, c2 ->
val c = c2.chapter_number.compareTo(c1.chapter_number)
if (c == 0) c2.name.compareToCaseInsensitiveNaturalOrder(c1.name) else c
}
.toList()
}
}.toList()
// Filters
override fun getFilterList() = FilterList(OrderBy.Popular())
// TODO Fix Memory Leak
override suspend fun getPageList(chapter: SChapter): List<Page> {
return when (val format = getFormat(chapter)) {
override suspend fun getPageList(chapter: SChapter): List<Page> =
when (val format = getFormat(chapter)) {
is Format.Directory -> {
format.file.listFiles().orEmpty()
format.file
.listFiles()
.orEmpty()
.filter { !it.isDirectory && ImageUtil.isImage(it.name, it::inputStream) }
.sortedWith { f1, f2 -> f1.name.compareToCaseInsensitiveNaturalOrder(f2.name) }
.mapIndexed { index, page ->
@@ -359,11 +361,11 @@ class LocalSource(
pages
}
}
}
fun getFormat(chapter: SChapter): Format {
try {
return fileSystem.getBaseDirectories()
return fileSystem
.getBaseDirectories()
.map { dir -> File(dir, chapter.url) }
.find { it.exists() }
?.let(Format.Companion::valueOf)
@@ -378,21 +380,23 @@ class LocalSource(
private fun updateCover(
chapter: SChapter,
manga: SManga,
): File? {
return try {
): File? =
try {
when (val format = getFormat(chapter)) {
is Format.Directory -> {
val entry =
format.file.listFiles()
format.file
.listFiles()
?.sortedWith { f1, f2 -> f1.name.compareToCaseInsensitiveNaturalOrder(f2.name) }
?.find { !it.isDirectory && ImageUtil.isImage(it.name) { FileInputStream(it) } }
entry?.let { coverManager.update(manga, it.inputStream()) }
}
is Format.Zip -> {
ZipFile(format.file).use { zip ->
ZipFile.builder().setFile(format.file).get().use { zip ->
val entry =
zip.entries.toList()
zip.entries
.toList()
.sortedWith { f1, f2 -> f1.name.compareToCaseInsensitiveNaturalOrder(f2.name) }
.find { !it.isDirectory && ImageUtil.isImage(it.name) { zip.getInputStream(it) } }
@@ -412,7 +416,8 @@ class LocalSource(
is Format.Epub -> {
EpubFile(format.file).use { epub ->
val entry =
epub.getImagesFromPages()
epub
.getImagesFromPages()
.firstOrNull()
?.let { epub.getEntry(it) }
@@ -424,7 +429,6 @@ class LocalSource(
logger.error(e) { "Error updating cover for ${manga.title}" }
null
}
}
companion object {
const val ID = 0L

View File

@@ -2,12 +2,14 @@ package eu.kanade.tachiyomi.source.local.filter
import eu.kanade.tachiyomi.source.model.Filter
sealed class OrderBy(selection: Selection) : Filter.Sort(
"Order by",
arrayOf("Title", "Date"),
selection,
) {
class Popular() : OrderBy(Selection(0, true))
sealed class OrderBy(
selection: Selection,
) : Filter.Sort(
"Order by",
arrayOf("Title", "Date"),
selection,
) {
class Popular : OrderBy(Selection(0, true))
class Latest() : OrderBy(Selection(1, false))
class Latest : OrderBy(Selection(1, false))
}

View File

@@ -11,15 +11,15 @@ private const val DEFAULT_COVER_NAME = "cover.jpg"
class LocalCoverManager(
private val fileSystem: LocalSourceFileSystem,
) {
fun find(mangaUrl: String): File? {
return fileSystem.getFilesInMangaDirectory(mangaUrl)
fun find(mangaUrl: String): File? =
fileSystem
.getFilesInMangaDirectory(mangaUrl)
// Get all file whose names start with 'cover'
.filter { it.isFile && it.nameWithoutExtension.equals("cover", ignoreCase = true) }
// Get the first actual image
.firstOrNull {
ImageUtil.isImage(it.name) { it.inputStream() }
}
}
fun update(
manga: SManga,

View File

@@ -3,13 +3,21 @@ package eu.kanade.tachiyomi.source.local.io
import java.io.File
sealed interface Format {
data class Directory(val file: File) : Format
data class Directory(
val file: File,
) : Format
data class Zip(val file: File) : Format
data class Zip(
val file: File,
) : Format
data class Rar(val file: File) : Format
data class Rar(
val file: File,
) : Format
data class Epub(val file: File) : Format
data class Epub(
val file: File,
) : Format
class UnknownFormatException : Exception()

View File

@@ -6,27 +6,22 @@ import java.io.File
class LocalSourceFileSystem(
private val applicationDirs: ApplicationDirs,
) {
fun getBaseDirectories(): Sequence<File> {
return sequenceOf(File(applicationDirs.localMangaRoot))
}
fun getBaseDirectories(): Sequence<File> = sequenceOf(File(applicationDirs.localMangaRoot))
fun getFilesInBaseDirectories(): Sequence<File> {
return getBaseDirectories()
fun getFilesInBaseDirectories(): Sequence<File> =
getBaseDirectories()
// Get all the files inside all baseDir
.flatMap { it.listFiles().orEmpty().toList() }
}
fun getMangaDirectory(name: String): File? {
return getFilesInBaseDirectories()
fun getMangaDirectory(name: String): File? =
getFilesInBaseDirectories()
// Get the first mangaDir or null
.firstOrNull { it.isDirectory && it.name == name }
}
fun getFilesInMangaDirectory(name: String): Sequence<File> {
return getFilesInBaseDirectories()
fun getFilesInMangaDirectory(name: String): Sequence<File> =
getFilesInBaseDirectories()
// Filter out ones that are not related to the manga and is not a directory
.filter { it.isDirectory && it.name == name }
// Get all the files inside the filtered folders
.flatMap { it.listFiles().orEmpty().toList() }
}
}

View File

@@ -6,18 +6,20 @@ import java.io.File
/**
* Loader used to load a chapter from a .epub file.
*/
class EpubPageLoader(file: File) : PageLoader {
class EpubPageLoader(
file: File,
) : PageLoader {
private val epub = EpubFile(file)
override suspend fun getPages(): List<ReaderPage> {
return epub.getImagesFromPages()
override suspend fun getPages(): List<ReaderPage> =
epub
.getImagesFromPages()
.mapIndexed { i, path ->
val streamFn = { epub.getInputStream(epub.getEntry(path)!!) }
ReaderPage(i).apply {
stream = streamFn
}
}
}
override fun recycle() {
epub.close()

View File

@@ -12,20 +12,21 @@ import java.io.PipedOutputStream
/**
* Loader used to load a chapter from a .rar or .cbr file.
*/
class RarPageLoader(file: File) : PageLoader {
class RarPageLoader(
file: File,
) : PageLoader {
private val rar = Archive(file)
override suspend fun getPages(): List<ReaderPage> {
return rar.fileHeaders.asSequence()
override suspend fun getPages(): List<ReaderPage> =
rar.fileHeaders
.asSequence()
.filter { !it.isDirectory && ImageUtil.isImage(it.fileName) { rar.getInputStream(it) } }
.sortedWith { f1, f2 -> f1.fileName.compareToCaseInsensitiveNaturalOrder(f2.fileName) }
.mapIndexed { i, header ->
ReaderPage(i).apply {
stream = { getStream(rar, header) }
}
}
.toList()
}
}.toList()
override fun recycle() {
rar.close()

View File

@@ -8,20 +8,21 @@ import java.io.File
/**
* Loader used to load a chapter from a .zip or .cbz file.
*/
class ZipPageLoader(file: File) : PageLoader {
private val zip = ZipFile(file)
class ZipPageLoader(
file: File,
) : PageLoader {
private val zip = ZipFile.builder().setFile(file).get()
override suspend fun getPages(): List<ReaderPage> {
return zip.entries.asSequence()
override suspend fun getPages(): List<ReaderPage> =
zip.entries
.asSequence()
.filter { !it.isDirectory && ImageUtil.isImage(it.name) { zip.getInputStream(it) } }
.sortedWith { f1, f2 -> f1.name.compareToCaseInsensitiveNaturalOrder(f2.name) }
.mapIndexed { i, entry ->
ReaderPage(i).apply {
stream = { zip.getInputStream(entry) }
}
}
.toList()
}
}.toList()
override fun recycle() {
zip.close()

View File

@@ -17,8 +17,7 @@ fun SManga.copyFromComicInfo(comicInfo: ComicInfo) {
comicInfo.genre?.value,
comicInfo.tags?.value,
comicInfo.categories?.value,
)
.distinct()
).distinct()
.joinToString(", ") { it.trim() }
.takeIf { it.isNotEmpty() }
?.let { genre = it }
@@ -29,8 +28,7 @@ fun SManga.copyFromComicInfo(comicInfo: ComicInfo) {
comicInfo.colorist?.value,
comicInfo.letterer?.value,
comicInfo.coverArtist?.value,
)
.flatMap { it.split(", ") }
).flatMap { it.split(", ") }
.distinct()
.joinToString(", ") { it.trim() }
.takeIf { it.isNotEmpty() }
@@ -202,14 +200,12 @@ enum class ComicInfoPublishingStatus(
;
companion object {
fun toComicInfoValue(value: Long): String {
return entries.firstOrNull { it.sMangaModelValue == value.toInt() }?.comicInfoValue
fun toComicInfoValue(value: Long): String =
entries.firstOrNull { it.sMangaModelValue == value.toInt() }?.comicInfoValue
?: UNKNOWN.comicInfoValue
}
fun toSMangaValue(value: String?): Int {
return entries.firstOrNull { it.comicInfoValue == value }?.sMangaModelValue
fun toSMangaValue(value: String?): Int =
entries.firstOrNull { it.comicInfoValue == value }?.sMangaModelValue
?: UNKNOWN.sMangaModelValue
}
}
}

View File

@@ -2,20 +2,40 @@ package eu.kanade.tachiyomi.source.model
// The class is originally sealed, Tachidesk adds new subclasses for serialization
// sealed class Filter<T>(val name: String, var state: T) {
open class Filter<T>(val name: String, var state: T) {
open class Header(name: String) : Filter<Any>(name, 0)
open class Filter<T>(
val name: String,
var state: T,
) {
open class Header(
name: String,
) : Filter<Any>(name, 0)
open class Separator(name: String = "") : Filter<Any>(name, 0)
open class Separator(
name: String = "",
) : Filter<Any>(name, 0)
abstract class Select<V>(name: String, val values: Array<V>, state: Int = 0) : Filter<Int>(name, state) {
abstract class Select<V>(
name: String,
val values: Array<V>,
state: Int = 0,
) : Filter<Int>(name, state) {
val displayValues get() = values.map { it.toString() }
}
abstract class Text(name: String, state: String = "") : Filter<String>(name, state)
abstract class Text(
name: String,
state: String = "",
) : Filter<String>(name, state)
abstract class CheckBox(name: String, state: Boolean = false) : Filter<Boolean>(name, state)
abstract class CheckBox(
name: String,
state: Boolean = false,
) : Filter<Boolean>(name, state)
abstract class TriState(name: String, state: Int = STATE_IGNORE) : Filter<Int>(name, state) {
abstract class TriState(
name: String,
state: Int = STATE_IGNORE,
) : Filter<Int>(name, state) {
fun isIgnored() = state == STATE_IGNORE
fun isIncluded() = state == STATE_INCLUDE
@@ -29,11 +49,20 @@ open class Filter<T>(val name: String, var state: T) {
}
}
abstract class Group<V>(name: String, state: List<V>) : Filter<List<V>>(name, state)
abstract class Group<V>(
name: String,
state: List<V>,
) : Filter<List<V>>(name, state)
abstract class Sort(name: String, val values: Array<String>, state: Selection? = null) :
Filter<Sort.Selection?>(name, state) {
data class Selection(val index: Int, val ascending: Boolean)
abstract class Sort(
name: String,
val values: Array<String>,
state: Selection? = null,
) : Filter<Sort.Selection?>(name, state) {
data class Selection(
val index: Int,
val ascending: Boolean,
)
}
override fun equals(other: Any?): Boolean {

View File

@@ -1,5 +1,7 @@
package eu.kanade.tachiyomi.source.model
data class FilterList(val list: List<Filter<*>>) : List<Filter<*>> by list {
data class FilterList(
val list: List<Filter<*>>,
) : List<Filter<*>> by list {
constructor(vararg fs: Filter<*>) : this(if (fs.isNotEmpty()) fs.asList() else emptyList())
}

View File

@@ -1,3 +1,6 @@
package eu.kanade.tachiyomi.source.model
data class MangasPage(val mangas: List<SManga>, val hasNextPage: Boolean)
data class MangasPage(
val mangas: List<SManga>,
val hasNextPage: Boolean,
)

View File

@@ -24,8 +24,6 @@ interface SChapter : Serializable {
}
companion object {
fun create(): SChapter {
return SChapterImpl()
}
fun create(): SChapter = SChapterImpl()
}
}

View File

@@ -62,9 +62,7 @@ interface SManga : Serializable {
const val CANCELLED = 5
const val ON_HIATUS = 6
fun create(): SManga {
return SMangaImpl()
}
fun create(): SManga = SMangaImpl()
}
}

View File

@@ -66,9 +66,7 @@ abstract class HttpSource : CatalogueSource {
open val client: OkHttpClient
get() = network.client
private fun generateId(): Long {
return generateId("${name.lowercase()}/$lang/$versionId")
}
private fun generateId(): Long = generateId("${name.lowercase()}/$lang/$versionId")
/**
* Generates a unique ID for the source based on the provided [name], [lang] and
@@ -121,13 +119,13 @@ abstract class HttpSource : CatalogueSource {
* @param page the page number to retrieve.
*/
@Deprecated("Use the non-RxJava API instead", replaceWith = ReplaceWith("getPopularManga"))
override fun fetchPopularManga(page: Int): Observable<MangasPage> {
return client.newCall(popularMangaRequest(page))
override fun fetchPopularManga(page: Int): Observable<MangasPage> =
client
.newCall(popularMangaRequest(page))
.asObservableSuccess()
.map { response ->
popularMangaParse(response)
}
}
/**
* Returns the request for the popular manga given the page.
@@ -156,20 +154,19 @@ abstract class HttpSource : CatalogueSource {
page: Int,
query: String,
filters: FilterList,
): Observable<MangasPage> {
return Observable.defer {
try {
client.newCall(searchMangaRequest(page, query, filters)).asObservableSuccess()
} catch (e: NoClassDefFoundError) {
// RxJava doesn't handle Errors, which tends to happen during global searches
// if an old extension using non-existent classes is still around
throw RuntimeException(e)
}
}
.map { response ->
): Observable<MangasPage> =
Observable
.defer {
try {
client.newCall(searchMangaRequest(page, query, filters)).asObservableSuccess()
} catch (e: NoClassDefFoundError) {
// RxJava doesn't handle Errors, which tends to happen during global searches
// if an old extension using non-existent classes is still around
throw RuntimeException(e)
}
}.map { response ->
searchMangaParse(response)
}
}
/**
* Returns the request for the search manga given the page.
@@ -197,13 +194,13 @@ abstract class HttpSource : CatalogueSource {
* @param page the page number to retrieve.
*/
@Deprecated("Use the non-RxJava API instead", replaceWith = ReplaceWith("getLatestUpdates"))
override fun fetchLatestUpdates(page: Int): Observable<MangasPage> {
return client.newCall(latestUpdatesRequest(page))
override fun fetchLatestUpdates(page: Int): Observable<MangasPage> =
client
.newCall(latestUpdatesRequest(page))
.asObservableSuccess()
.map { response ->
latestUpdatesParse(response)
}
}
/**
* Returns the request for latest manga given the page.
@@ -227,18 +224,16 @@ abstract class HttpSource : CatalogueSource {
* @return the updated manga.
*/
@Suppress("DEPRECATION")
override suspend fun getMangaDetails(manga: SManga): SManga {
return fetchMangaDetails(manga).awaitSingle()
}
override suspend fun getMangaDetails(manga: SManga): SManga = fetchMangaDetails(manga).awaitSingle()
@Deprecated("Use the non-RxJava API instead", replaceWith = ReplaceWith("getMangaDetails"))
override fun fetchMangaDetails(manga: SManga): Observable<SManga> {
return client.newCall(mangaDetailsRequest(manga))
override fun fetchMangaDetails(manga: SManga): Observable<SManga> =
client
.newCall(mangaDetailsRequest(manga))
.asObservableSuccess()
.map { response ->
mangaDetailsParse(response).apply { initialized = true }
}
}
/**
* Returns the request for the details of a manga. Override only if it's needed to change the
@@ -246,9 +241,7 @@ abstract class HttpSource : CatalogueSource {
*
* @param manga the manga to be updated.
*/
open fun mangaDetailsRequest(manga: SManga): Request {
return GET(baseUrl + manga.url, headers)
}
open fun mangaDetailsRequest(manga: SManga): Request = GET(baseUrl + manga.url, headers)
/**
* Parses the response from the site and returns the details of a manga.
@@ -275,9 +268,10 @@ abstract class HttpSource : CatalogueSource {
}
@Deprecated("Use the non-RxJava API instead", replaceWith = ReplaceWith("getChapterList"))
override fun fetchChapterList(manga: SManga): Observable<List<SChapter>> {
return if (manga.status != SManga.LICENSED) {
client.newCall(chapterListRequest(manga))
override fun fetchChapterList(manga: SManga): Observable<List<SChapter>> =
if (manga.status != SManga.LICENSED) {
client
.newCall(chapterListRequest(manga))
.asObservableSuccess()
.map { response ->
chapterListParse(response)
@@ -285,7 +279,6 @@ abstract class HttpSource : CatalogueSource {
} else {
Observable.error(LicensedMangaChaptersException())
}
}
/**
* Returns the request for updating the chapter list. Override only if it's needed to override
@@ -293,9 +286,7 @@ abstract class HttpSource : CatalogueSource {
*
* @param manga the manga to look for chapters.
*/
protected open fun chapterListRequest(manga: SManga): Request {
return GET(baseUrl + manga.url, headers)
}
protected open fun chapterListRequest(manga: SManga): Request = GET(baseUrl + manga.url, headers)
/**
* Parses the response from the site and returns a list of chapters.
@@ -312,18 +303,16 @@ abstract class HttpSource : CatalogueSource {
* @return the pages for the chapter.
*/
@Suppress("DEPRECATION")
override suspend fun getPageList(chapter: SChapter): List<Page> {
return fetchPageList(chapter).awaitSingle()
}
override suspend fun getPageList(chapter: SChapter): List<Page> = fetchPageList(chapter).awaitSingle()
@Deprecated("Use the non-RxJava API instead", replaceWith = ReplaceWith("getPageList"))
override fun fetchPageList(chapter: SChapter): Observable<List<Page>> {
return client.newCall(pageListRequest(chapter))
override fun fetchPageList(chapter: SChapter): Observable<List<Page>> =
client
.newCall(pageListRequest(chapter))
.asObservableSuccess()
.map { response ->
pageListParse(response)
}
}
/**
* Returns the request for getting the page list. Override only if it's needed to override the
@@ -331,9 +320,7 @@ abstract class HttpSource : CatalogueSource {
*
* @param chapter the chapter whose page list has to be fetched.
*/
protected open fun pageListRequest(chapter: SChapter): Request {
return GET(baseUrl + chapter.url, headers)
}
protected open fun pageListRequest(chapter: SChapter): Request = GET(baseUrl + chapter.url, headers)
/**
* Parses the response from the site and returns a list of pages.
@@ -350,16 +337,14 @@ abstract class HttpSource : CatalogueSource {
* @param page the page whose source image has to be fetched.
*/
@Suppress("DEPRECATION")
open suspend fun getImageUrl(page: Page): String {
return fetchImageUrl(page).awaitSingle()
}
open suspend fun getImageUrl(page: Page): String = fetchImageUrl(page).awaitSingle()
@Deprecated("Use the non-RxJava API instead", replaceWith = ReplaceWith("getImageUrl"))
open fun fetchImageUrl(page: Page): Observable<String> {
return client.newCall(imageUrlRequest(page))
open fun fetchImageUrl(page: Page): Observable<String> =
client
.newCall(imageUrlRequest(page))
.asObservableSuccess()
.map { imageUrlParse(it) }
}
/**
* Returns the request for getting the url to the source image. Override only if it's needed to
@@ -367,9 +352,7 @@ abstract class HttpSource : CatalogueSource {
*
* @param page the chapter whose page list has to be fetched
*/
protected open fun imageUrlRequest(page: Page): Request {
return GET(page.url, headers)
}
protected open fun imageUrlRequest(page: Page): Request = GET(page.url, headers)
/**
* Parses the response from the site and returns the absolute url to the source image.
@@ -385,10 +368,10 @@ abstract class HttpSource : CatalogueSource {
* @since extensions-lib 1.5
* @param page the page whose source image has to be downloaded.
*/
open suspend fun getImage(page: Page): Response {
return client.newCachelessCallWithProgress(imageRequest(page), page)
open suspend fun getImage(page: Page): Response =
client
.newCachelessCallWithProgress(imageRequest(page), page)
.awaitSuccess()
}
/**
* Returns the request for getting the source image. Override only if it's needed to override
@@ -396,9 +379,7 @@ abstract class HttpSource : CatalogueSource {
*
* @param page the chapter whose page list has to be fetched
*/
protected open fun imageRequest(page: Page): Request {
return GET(page.imageUrl!!, headers)
}
protected open fun imageRequest(page: Page): Request = GET(page.imageUrl!!, headers)
/**
* Assigns the url of the chapter without the scheme and domain. It saves some redundancy from
@@ -425,8 +406,8 @@ abstract class HttpSource : CatalogueSource {
*
* @param orig the full url.
*/
private fun getUrlWithoutDomain(orig: String): String {
return try {
private fun getUrlWithoutDomain(orig: String): String =
try {
val uri = URI(orig.replace(" ", "%20"))
var out = uri.path
if (uri.query != null) {
@@ -439,7 +420,6 @@ abstract class HttpSource : CatalogueSource {
} catch (e: URISyntaxException) {
orig
}
}
/**
* Returns the url of the provided manga
@@ -448,9 +428,7 @@ abstract class HttpSource : CatalogueSource {
* @param manga the manga
* @return url of the manga
*/
open fun getMangaUrl(manga: SManga): String {
return mangaDetailsRequest(manga).url.toString()
}
open fun getMangaUrl(manga: SManga): String = mangaDetailsRequest(manga).url.toString()
/**
* Returns the url of the provided chapter
@@ -459,9 +437,7 @@ abstract class HttpSource : CatalogueSource {
* @param chapter the chapter
* @return url of the chapter
*/
open fun getChapterUrl(chapter: SChapter): String {
return pageListRequest(chapter).url.toString()
}
open fun getChapterUrl(chapter: SChapter): String = pageListRequest(chapter).url.toString()
/**
* Called before inserting a new chapter into database. Use it if you need to override chapter

View File

@@ -138,9 +138,7 @@ abstract class ParsedHttpSource : HttpSource() {
*
* @param response the response from the site.
*/
override fun mangaDetailsParse(response: Response): SManga {
return mangaDetailsParse(response.asJsoup())
}
override fun mangaDetailsParse(response: Response): SManga = mangaDetailsParse(response.asJsoup())
/**
* Returns the details of the manga from the given [document].
@@ -176,9 +174,7 @@ abstract class ParsedHttpSource : HttpSource() {
*
* @param response the response from the site.
*/
override fun pageListParse(response: Response): List<Page> {
return pageListParse(response.asJsoup())
}
override fun pageListParse(response: Response): List<Page> = pageListParse(response.asJsoup())
/**
* Returns a page list from the given document.
@@ -192,9 +188,7 @@ abstract class ParsedHttpSource : HttpSource() {
*
* @param response the response from the site.
*/
override fun imageUrlParse(response: Response): String {
return imageUrlParse(response.asJsoup())
}
override fun imageUrlParse(response: Response): String = imageUrlParse(response.asJsoup())
/**
* Returns the absolute url to the source image from the document.

View File

@@ -8,25 +8,17 @@ import org.jsoup.nodes.Element
fun Element.selectText(
css: String,
defaultValue: String? = null,
): String? {
return select(css).first()?.text() ?: defaultValue
}
): String? = select(css).first()?.text() ?: defaultValue
fun Element.selectInt(
css: String,
defaultValue: Int = 0,
): Int {
return select(css).first()?.text()?.toInt() ?: defaultValue
}
): Int = select(css).first()?.text()?.toInt() ?: defaultValue
fun Element.attrOrText(css: String): String {
return if (css != "text") attr(css) else text()
}
fun Element.attrOrText(css: String): String = if (css != "text") attr(css) else text()
/**
* Returns a Jsoup document for this response.
* @param html the body of the response. Use only if the body was read before calling this method.
*/
fun Response.asJsoup(html: String? = null): Document {
return Jsoup.parse(html ?: body.string(), request.url.toString())
}
fun Response.asJsoup(html: String? = null): Document = Jsoup.parse(html ?: body.string(), request.url.toString())

View File

@@ -68,15 +68,14 @@ object ChapterRecognition {
* @param match result of regex
* @return chapter number if found else null
*/
private fun getChapterNumberFromMatch(match: MatchResult): Double {
return match.let {
private fun getChapterNumberFromMatch(match: MatchResult): Double =
match.let {
val initial = it.groups[1]?.value?.toDouble()!!
val subChapterDecimal = it.groups[2]?.value
val subChapterAlpha = it.groups[3]?.value
val addition = checkForDecimal(subChapterDecimal, subChapterAlpha)
initial.plus(addition)
}
}
/**
* Check for decimal in received strings

View File

@@ -1,11 +1,10 @@
package eu.kanade.tachiyomi.util.chapter
object ChapterSanitizer {
fun String.sanitize(title: String): String {
return trim()
fun String.sanitize(title: String): String =
trim()
.removePrefix(title)
.trim(*CHAPTER_TRIM_CHARS)
}
private val CHAPTER_TRIM_CHARS =
arrayOf(

View File

@@ -5,29 +5,35 @@ import java.security.MessageDigest
object Hash {
private val chars =
charArrayOf(
'0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
'a', 'b', 'c', 'd', 'e', 'f',
'0',
'1',
'2',
'3',
'4',
'5',
'6',
'7',
'8',
'9',
'a',
'b',
'c',
'd',
'e',
'f',
)
private val MD5 get() = MessageDigest.getInstance("MD5")
private val SHA256 get() = MessageDigest.getInstance("SHA-256")
fun sha256(bytes: ByteArray): String {
return encodeHex(SHA256.digest(bytes))
}
fun sha256(bytes: ByteArray): String = encodeHex(SHA256.digest(bytes))
fun sha256(string: String): String {
return sha256(string.toByteArray())
}
fun sha256(string: String): String = sha256(string.toByteArray())
fun md5(bytes: ByteArray): String {
return encodeHex(MD5.digest(bytes))
}
fun md5(bytes: ByteArray): String = encodeHex(MD5.digest(bytes))
fun md5(string: String): String {
return md5(string.toByteArray())
}
fun md5(string: String): String = md5(string.toByteArray())
private fun encodeHex(data: ByteArray): String {
val l = data.size

View File

@@ -10,13 +10,12 @@ import kotlin.math.floor
fun String.chop(
count: Int,
replacement: String = "",
): String {
return if (length > count) {
): String =
if (length > count) {
take(count - replacement.length) + replacement
} else {
this
}
}
/**
* Replaces the given string to have at most [count] characters using [replacement] near the center.
@@ -46,9 +45,7 @@ fun String.compareToCaseInsensitiveNaturalOrder(other: String): Int {
/**
* Returns the size of the string as the number of bytes.
*/
fun String.byteSize(): Int {
return toByteArray(Charsets.UTF_8).size
}
fun String.byteSize(): Int = toByteArray(Charsets.UTF_8).size
/**
* Returns a string containing the first [n] bytes from this string, or the entire string if this

View File

@@ -11,11 +11,13 @@ import java.io.InputStream
/**
* Wrapper over ZipFile to load files in epub format.
*/
class EpubFile(file: File) : Closeable {
class EpubFile(
file: File,
) : Closeable {
/**
* Zip file of this epub.
*/
private val zip = ZipFile(file)
private val zip = ZipFile.builder().setFile(file).get()
/**
* Path separator used by this epub.
@@ -32,16 +34,12 @@ class EpubFile(file: File) : Closeable {
/**
* Returns an input stream for reading the contents of the specified zip file entry.
*/
fun getInputStream(entry: ZipArchiveEntry): InputStream {
return zip.getInputStream(entry)
}
fun getInputStream(entry: ZipArchiveEntry): InputStream = zip.getInputStream(entry)
/**
* Returns the zip file entry for the specified name, or null if not found.
*/
fun getEntry(name: String): ZipArchiveEntry? {
return zip.getEntry(name)
}
fun getEntry(name: String): ZipArchiveEntry? = zip.getEntry(name)
/**
* Returns the path of all the images found in the epub file.
@@ -81,7 +79,8 @@ class EpubFile(file: File) : Closeable {
*/
fun getPagesFromDocument(document: Document): List<String> {
val pages =
document.select("manifest > item")
document
.select("manifest > item")
.filter { node -> "application/xhtml+xml" == node.attr("media-type") }
.associateBy { it.attr("id") }