diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 22cd05ecf8..fbbcbc1792 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -79,7 +79,7 @@ + android:exported="false" /> , + private val file: File, + private val manga: Manga) : DataFetcher { + + + override fun loadData(priority: Priority?): InputStream? { + return fetcher.loadData(priority) + } + + override fun getId(): String { + return manga.thumbnail_url + file.lastModified() + } + + override fun cancel() { + fetcher.cancel() + } + + override fun cleanup() { + fetcher.cleanup() + } +} \ No newline at end of file diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/glide/MangaModelLoader.kt b/app/src/main/java/eu/kanade/tachiyomi/data/glide/MangaModelLoader.kt index 32abfb49aa..e155471a00 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/glide/MangaModelLoader.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/glide/MangaModelLoader.kt @@ -1,10 +1,8 @@ package eu.kanade.tachiyomi.data.glide import android.content.Context -import android.net.Uri import android.util.LruCache import com.bumptech.glide.Glide -import com.bumptech.glide.Priority import com.bumptech.glide.load.data.DataFetcher import com.bumptech.glide.load.model.* import com.bumptech.glide.load.model.stream.StreamModelLoader @@ -18,7 +16,7 @@ import java.io.InputStream /** * A class for loading a cover associated with a [Manga] that can be present in our own cache. - * Coupled with [MangaDataFetcher], this class allows to implement the following flow: + * Coupled with [MangaUrlFetcher], this class allows to implement the following flow: * * - Check in RAM LRU. * - Check in disk LRU. @@ -32,23 +30,23 @@ class MangaModelLoader(context: Context) : StreamModelLoader { /** * Cover cache where persistent covers are stored. */ - val coverCache: CoverCache by injectLazy() + private val coverCache: CoverCache by injectLazy() /** * Source manager. */ - val sourceManager: SourceManager by injectLazy() + private val sourceManager: SourceManager by injectLazy() /** * Base network loader. */ - private val baseLoader = Glide.buildModelLoader(GlideUrl::class.java, + private val baseUrlLoader = Glide.buildModelLoader(GlideUrl::class.java, InputStream::class.java, context) /** * Base file loader. */ - private val baseFileLoader = Glide.buildModelLoader(Uri::class.java, + private val baseFileLoader = Glide.buildModelLoader(File::class.java, InputStream::class.java, context) /** @@ -74,7 +72,7 @@ class MangaModelLoader(context: Context) : StreamModelLoader { } /** - * Returns a [MangaDataFetcher] for the given manga or null if the url is empty. + * Returns a fetcher for the given manga or null if the url is empty. * * @param manga the model. * @param width the width of the view where the resource will be loaded. @@ -86,34 +84,33 @@ class MangaModelLoader(context: Context) : StreamModelLoader { // Check thumbnail is not null or empty val url = manga.thumbnail_url - if (url.isNullOrEmpty()) { + if (url == null || url.isEmpty()) { return null } - if (url!!.startsWith("file://")) { - val cover = File(url.substring(7)) - val id = url + File.separator + cover.lastModified() - val rf = baseFileLoader.getResourceFetcher(Uri.fromFile(cover), width, height) - return object : DataFetcher { - override fun cleanup() = rf.cleanup() - override fun loadData(priority: Priority?): InputStream = rf.loadData(priority) - override fun cancel() = rf.cancel() - override fun getId() = id - } + if (url.startsWith("http")) { + // Obtain the request url and the file for this url from the LRU cache, or calculate it + // and add them to the cache. + val (glideUrl, file) = lruCache.get(url) ?: + Pair(GlideUrl(url, getHeaders(manga)), coverCache.getCoverFile(url)).apply { + lruCache.put(url, this) + } + + // Get the resource fetcher for this request url. + val networkFetcher = baseUrlLoader.getResourceFetcher(glideUrl, width, height) + + // Return an instance of the fetcher providing the needed elements. + return MangaUrlFetcher(networkFetcher, file, manga) + } else { + // Get the file from the url, removing the scheme if present. + val file = File(url.substringAfter("file://")) + + // Get the resource fetcher for the given file. + val fileFetcher = baseFileLoader.getResourceFetcher(file, width, height) + + // Return an instance of the fetcher providing the needed elements. + return MangaFileFetcher(fileFetcher, file, manga) } - - // Obtain the request url and the file for this url from the LRU cache, or calculate it - // and add them to the cache. - val (glideUrl, file) = lruCache.get(url) ?: - Pair(GlideUrl(url, getHeaders(manga)), coverCache.getCoverFile(url!!)).apply { - lruCache.put(url, this) - } - - // Get the network fetcher for this request url. - val networkFetcher = baseLoader.getResourceFetcher(glideUrl, width, height) - - // Return an instance of our fetcher providing the needed elements. - return MangaDataFetcher(networkFetcher, file, manga) } /** diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/glide/MangaDataFetcher.kt b/app/src/main/java/eu/kanade/tachiyomi/data/glide/MangaUrlFetcher.kt similarity index 94% rename from app/src/main/java/eu/kanade/tachiyomi/data/glide/MangaDataFetcher.kt rename to app/src/main/java/eu/kanade/tachiyomi/data/glide/MangaUrlFetcher.kt index 2853fea7ed..9893377d22 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/glide/MangaDataFetcher.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/glide/MangaUrlFetcher.kt @@ -18,9 +18,9 @@ import java.io.InputStream * @param file the file where this cover should be. It may exists or not. * @param manga the manga of the cover to load. */ -class MangaDataFetcher(private val networkFetcher: DataFetcher, - private val file: File, - private val manga: Manga) +class MangaUrlFetcher(private val networkFetcher: DataFetcher, + private val file: File, + private val manga: Manga) : DataFetcher { @Throws(Exception::class) diff --git a/app/src/main/java/eu/kanade/tachiyomi/source/LocalSource.kt b/app/src/main/java/eu/kanade/tachiyomi/source/LocalSource.kt index 0ff0349416..b46190c555 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/source/LocalSource.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/source/LocalSource.kt @@ -20,7 +20,6 @@ import java.util.zip.ZipFile class LocalSource(private val context: Context) : CatalogueSource { companion object { - private val FILE_PROTOCOL = "file://" private val COVER_NAME = "cover.jpg" private val POPULAR_FILTERS = FilterList(OrderBy()) private val LATEST_FILTERS = FilterList(OrderBy().apply { state = Filter.Sort.Selection(1, false) }) @@ -46,8 +45,8 @@ class LocalSource(private val context: Context) : CatalogueSource { } private fun getBaseDirectories(context: Context): List { - val c = File.separator + context.getString(R.string.app_name) + File.separator + "local" - return DiskUtil.getExternalStorages(context).map { File(it.absolutePath + c) } + val c = context.getString(R.string.app_name) + File.separator + "local" + return DiskUtil.getExternalStorages(context).map { File(it.absolutePath, c) } } } @@ -67,7 +66,7 @@ class LocalSource(private val context: Context) : CatalogueSource { .filter { it.isDirectory || isSupportedFormat(it.extension) } .map { chapterFile -> SChapter.create().apply { - url = chapterFile.absolutePath + url = "${manga.url}/${chapterFile.name}" val chapName = if (chapterFile.isDirectory) { chapterFile.name } else { @@ -79,27 +78,37 @@ class LocalSource(private val context: Context) : CatalogueSource { ChapterRecognition.parseChapterNumber(this, manga) } } + .sortedByDescending { it.chapter_number } - return Observable.just(chapters.sortedByDescending { it.chapter_number }) + return Observable.just(chapters) } override fun fetchPageList(chapter: SChapter): Observable> { - val chapFile = File(chapter.url) - if (chapFile.isDirectory) { - return Observable.just(chapFile.listFiles() - .filter { !it.isDirectory && DiskUtil.isImage(it.name, { FileInputStream(it) }) } - .sortedWith(Comparator { t1, t2 -> CaseInsensitiveSimpleNaturalComparator.getInstance().compare(t1.name, t2.name) }) - .mapIndexed { i, v -> Page(i, FILE_PROTOCOL + v.absolutePath, FILE_PROTOCOL + v.absolutePath, Uri.fromFile(v)).apply { status = Page.READY } }) - } else { - val zip = ZipFile(chapFile) - return Observable.just(ZipFile(chapFile).entries().toList() - .filter { !it.isDirectory && DiskUtil.isImage(it.name, { zip.getInputStream(it) }) } - .sortedWith(Comparator { t1, t2 -> CaseInsensitiveSimpleNaturalComparator.getInstance().compare(t1.name, t2.name) }) - .mapIndexed { i, v -> - val path = "content://${ZipContentProvider.PROVIDER}${chapFile.absolutePath}!/${v.name}" - Page(i, path, path, Uri.parse(path)).apply { status = Page.READY } - }) + val baseDirs = getBaseDirectories(context) + + for (dir in baseDirs) { + val chapFile = File(dir, chapter.url) + if (!chapFile.exists()) continue + + val comparator = CaseInsensitiveSimpleNaturalComparator.getInstance() + + val pageList = if (chapFile.isDirectory) { + chapFile.listFiles() + .filter { !it.isDirectory && DiskUtil.isImage(it.name, { FileInputStream(it) }) } + .sortedWith(Comparator { f1, f2 -> comparator.compare(f1.name, f2.name) }) + .map { Uri.fromFile(it) } + } else { + val zip = ZipFile(chapFile) + zip.entries().toList() + .filter { !it.isDirectory && DiskUtil.isImage(it.name, { zip.getInputStream(it) }) } + .sortedWith(Comparator { f1, f2 -> comparator.compare(f1.name, f2.name) }) + .map { Uri.parse("content://${ZipContentProvider.PROVIDER}${chapFile.absolutePath}!/${it.name}") } + }.mapIndexed { i, uri -> Page(i, uri = uri).apply { status = Page.READY } } + + return Observable.just(pageList) } + + return Observable.error(Exception("Chapter not found")) } override fun fetchPopularManga(page: Int) = fetchSearchManga(page, "", POPULAR_FILTERS) @@ -138,7 +147,7 @@ class LocalSource(private val context: Context) : CatalogueSource { for (dir in baseDirs) { val cover = File("${dir.absolutePath}/$url", COVER_NAME) if (cover.exists()) { - thumbnail_url = FILE_PROTOCOL + cover.absolutePath + thumbnail_url = cover.absolutePath break } } @@ -152,7 +161,7 @@ class LocalSource(private val context: Context) : CatalogueSource { val input = context.contentResolver.openInputStream(Uri.parse(url)) try { val dest = updateCover(context, this, input) - thumbnail_url = dest?.let { FILE_PROTOCOL + it.absolutePath } + thumbnail_url = dest?.absolutePath } catch (e: Exception) { Timber.e(e) } diff --git a/app/src/main/java/eu/kanade/tachiyomi/util/ZipContentProvider.kt b/app/src/main/java/eu/kanade/tachiyomi/util/ZipContentProvider.kt index b95cc5b390..737007720b 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/util/ZipContentProvider.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/util/ZipContentProvider.kt @@ -7,7 +7,6 @@ import android.database.Cursor import android.net.Uri import android.os.ParcelFileDescriptor import eu.kanade.tachiyomi.BuildConfig -import timber.log.Timber import java.io.IOException import java.net.URL import java.net.URLConnection @@ -40,11 +39,10 @@ class ZipContentProvider : ContentProvider() { input.use { output.use { input.copyTo(output) - output.flush() } } } catch (e: IOException) { - Timber.e(e) + // Ignore } } return AssetFileDescriptor(pipe[0], 0, -1)