mirror of
https://github.com/tachiyomiorg/tachiyomi.git
synced 2024-11-13 18:17:32 +01:00
Local chapter url relative. Other minor changes
This commit is contained in:
parent
2b73a9d2a4
commit
e8912c5dc9
7 changed files with 95 additions and 62 deletions
|
@ -79,7 +79,7 @@
|
||||||
<provider
|
<provider
|
||||||
android:name="eu.kanade.tachiyomi.util.ZipContentProvider"
|
android:name="eu.kanade.tachiyomi.util.ZipContentProvider"
|
||||||
android:authorities="${applicationId}.zip-provider"
|
android:authorities="${applicationId}.zip-provider"
|
||||||
android:exported="false"></provider>
|
android:exported="false" />
|
||||||
|
|
||||||
<receiver
|
<receiver
|
||||||
android:name=".data.notification.NotificationReceiver"
|
android:name=".data.notification.NotificationReceiver"
|
||||||
|
|
|
@ -4,7 +4,7 @@ class MangaImpl : Manga {
|
||||||
|
|
||||||
override var id: Long? = null
|
override var id: Long? = null
|
||||||
|
|
||||||
override var source: Long = 0
|
override var source: Long = -1
|
||||||
|
|
||||||
override lateinit var url: String
|
override lateinit var url: String
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,29 @@
|
||||||
|
package eu.kanade.tachiyomi.data.glide
|
||||||
|
|
||||||
|
import com.bumptech.glide.Priority
|
||||||
|
import com.bumptech.glide.load.data.DataFetcher
|
||||||
|
import eu.kanade.tachiyomi.data.database.models.Manga
|
||||||
|
import java.io.File
|
||||||
|
import java.io.InputStream
|
||||||
|
|
||||||
|
class MangaFileFetcher(private val fetcher: DataFetcher<InputStream>,
|
||||||
|
private val file: File,
|
||||||
|
private val manga: Manga) : DataFetcher<InputStream> {
|
||||||
|
|
||||||
|
|
||||||
|
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()
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,10 +1,8 @@
|
||||||
package eu.kanade.tachiyomi.data.glide
|
package eu.kanade.tachiyomi.data.glide
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.net.Uri
|
|
||||||
import android.util.LruCache
|
import android.util.LruCache
|
||||||
import com.bumptech.glide.Glide
|
import com.bumptech.glide.Glide
|
||||||
import com.bumptech.glide.Priority
|
|
||||||
import com.bumptech.glide.load.data.DataFetcher
|
import com.bumptech.glide.load.data.DataFetcher
|
||||||
import com.bumptech.glide.load.model.*
|
import com.bumptech.glide.load.model.*
|
||||||
import com.bumptech.glide.load.model.stream.StreamModelLoader
|
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.
|
* 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 RAM LRU.
|
||||||
* - Check in disk LRU.
|
* - Check in disk LRU.
|
||||||
|
@ -32,23 +30,23 @@ class MangaModelLoader(context: Context) : StreamModelLoader<Manga> {
|
||||||
/**
|
/**
|
||||||
* Cover cache where persistent covers are stored.
|
* Cover cache where persistent covers are stored.
|
||||||
*/
|
*/
|
||||||
val coverCache: CoverCache by injectLazy()
|
private val coverCache: CoverCache by injectLazy()
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Source manager.
|
* Source manager.
|
||||||
*/
|
*/
|
||||||
val sourceManager: SourceManager by injectLazy()
|
private val sourceManager: SourceManager by injectLazy()
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Base network loader.
|
* Base network loader.
|
||||||
*/
|
*/
|
||||||
private val baseLoader = Glide.buildModelLoader(GlideUrl::class.java,
|
private val baseUrlLoader = Glide.buildModelLoader(GlideUrl::class.java,
|
||||||
InputStream::class.java, context)
|
InputStream::class.java, context)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Base file loader.
|
* Base file loader.
|
||||||
*/
|
*/
|
||||||
private val baseFileLoader = Glide.buildModelLoader(Uri::class.java,
|
private val baseFileLoader = Glide.buildModelLoader(File::class.java,
|
||||||
InputStream::class.java, context)
|
InputStream::class.java, context)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -74,7 +72,7 @@ class MangaModelLoader(context: Context) : StreamModelLoader<Manga> {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 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 manga the model.
|
||||||
* @param width the width of the view where the resource will be loaded.
|
* @param width the width of the view where the resource will be loaded.
|
||||||
|
@ -86,34 +84,33 @@ class MangaModelLoader(context: Context) : StreamModelLoader<Manga> {
|
||||||
|
|
||||||
// Check thumbnail is not null or empty
|
// Check thumbnail is not null or empty
|
||||||
val url = manga.thumbnail_url
|
val url = manga.thumbnail_url
|
||||||
if (url.isNullOrEmpty()) {
|
if (url == null || url.isEmpty()) {
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
if (url!!.startsWith("file://")) {
|
if (url.startsWith("http")) {
|
||||||
val cover = File(url.substring(7))
|
// Obtain the request url and the file for this url from the LRU cache, or calculate it
|
||||||
val id = url + File.separator + cover.lastModified()
|
// and add them to the cache.
|
||||||
val rf = baseFileLoader.getResourceFetcher(Uri.fromFile(cover), width, height)
|
val (glideUrl, file) = lruCache.get(url) ?:
|
||||||
return object : DataFetcher<InputStream> {
|
Pair(GlideUrl(url, getHeaders(manga)), coverCache.getCoverFile(url)).apply {
|
||||||
override fun cleanup() = rf.cleanup()
|
lruCache.put(url, this)
|
||||||
override fun loadData(priority: Priority?): InputStream = rf.loadData(priority)
|
}
|
||||||
override fun cancel() = rf.cancel()
|
|
||||||
override fun getId() = id
|
// 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)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -18,9 +18,9 @@ import java.io.InputStream
|
||||||
* @param file the file where this cover should be. It may exists or not.
|
* @param file the file where this cover should be. It may exists or not.
|
||||||
* @param manga the manga of the cover to load.
|
* @param manga the manga of the cover to load.
|
||||||
*/
|
*/
|
||||||
class MangaDataFetcher(private val networkFetcher: DataFetcher<InputStream>,
|
class MangaUrlFetcher(private val networkFetcher: DataFetcher<InputStream>,
|
||||||
private val file: File,
|
private val file: File,
|
||||||
private val manga: Manga)
|
private val manga: Manga)
|
||||||
: DataFetcher<InputStream> {
|
: DataFetcher<InputStream> {
|
||||||
|
|
||||||
@Throws(Exception::class)
|
@Throws(Exception::class)
|
|
@ -20,7 +20,6 @@ import java.util.zip.ZipFile
|
||||||
|
|
||||||
class LocalSource(private val context: Context) : CatalogueSource {
|
class LocalSource(private val context: Context) : CatalogueSource {
|
||||||
companion object {
|
companion object {
|
||||||
private val FILE_PROTOCOL = "file://"
|
|
||||||
private val COVER_NAME = "cover.jpg"
|
private val COVER_NAME = "cover.jpg"
|
||||||
private val POPULAR_FILTERS = FilterList(OrderBy())
|
private val POPULAR_FILTERS = FilterList(OrderBy())
|
||||||
private val LATEST_FILTERS = FilterList(OrderBy().apply { state = Filter.Sort.Selection(1, false) })
|
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<File> {
|
private fun getBaseDirectories(context: Context): List<File> {
|
||||||
val c = File.separator + context.getString(R.string.app_name) + File.separator + "local"
|
val c = context.getString(R.string.app_name) + File.separator + "local"
|
||||||
return DiskUtil.getExternalStorages(context).map { File(it.absolutePath + c) }
|
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) }
|
.filter { it.isDirectory || isSupportedFormat(it.extension) }
|
||||||
.map { chapterFile ->
|
.map { chapterFile ->
|
||||||
SChapter.create().apply {
|
SChapter.create().apply {
|
||||||
url = chapterFile.absolutePath
|
url = "${manga.url}/${chapterFile.name}"
|
||||||
val chapName = if (chapterFile.isDirectory) {
|
val chapName = if (chapterFile.isDirectory) {
|
||||||
chapterFile.name
|
chapterFile.name
|
||||||
} else {
|
} else {
|
||||||
|
@ -79,27 +78,37 @@ class LocalSource(private val context: Context) : CatalogueSource {
|
||||||
ChapterRecognition.parseChapterNumber(this, manga)
|
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<List<Page>> {
|
override fun fetchPageList(chapter: SChapter): Observable<List<Page>> {
|
||||||
val chapFile = File(chapter.url)
|
val baseDirs = getBaseDirectories(context)
|
||||||
if (chapFile.isDirectory) {
|
|
||||||
return Observable.just(chapFile.listFiles()
|
for (dir in baseDirs) {
|
||||||
.filter { !it.isDirectory && DiskUtil.isImage(it.name, { FileInputStream(it) }) }
|
val chapFile = File(dir, chapter.url)
|
||||||
.sortedWith(Comparator<File> { t1, t2 -> CaseInsensitiveSimpleNaturalComparator.getInstance<String>().compare(t1.name, t2.name) })
|
if (!chapFile.exists()) continue
|
||||||
.mapIndexed { i, v -> Page(i, FILE_PROTOCOL + v.absolutePath, FILE_PROTOCOL + v.absolutePath, Uri.fromFile(v)).apply { status = Page.READY } })
|
|
||||||
} else {
|
val comparator = CaseInsensitiveSimpleNaturalComparator.getInstance<String>()
|
||||||
val zip = ZipFile(chapFile)
|
|
||||||
return Observable.just(ZipFile(chapFile).entries().toList()
|
val pageList = if (chapFile.isDirectory) {
|
||||||
.filter { !it.isDirectory && DiskUtil.isImage(it.name, { zip.getInputStream(it) }) }
|
chapFile.listFiles()
|
||||||
.sortedWith(Comparator<ZipEntry> { t1, t2 -> CaseInsensitiveSimpleNaturalComparator.getInstance<String>().compare(t1.name, t2.name) })
|
.filter { !it.isDirectory && DiskUtil.isImage(it.name, { FileInputStream(it) }) }
|
||||||
.mapIndexed { i, v ->
|
.sortedWith(Comparator<File> { f1, f2 -> comparator.compare(f1.name, f2.name) })
|
||||||
val path = "content://${ZipContentProvider.PROVIDER}${chapFile.absolutePath}!/${v.name}"
|
.map { Uri.fromFile(it) }
|
||||||
Page(i, path, path, Uri.parse(path)).apply { status = Page.READY }
|
} else {
|
||||||
})
|
val zip = ZipFile(chapFile)
|
||||||
|
zip.entries().toList()
|
||||||
|
.filter { !it.isDirectory && DiskUtil.isImage(it.name, { zip.getInputStream(it) }) }
|
||||||
|
.sortedWith(Comparator<ZipEntry> { 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)
|
override fun fetchPopularManga(page: Int) = fetchSearchManga(page, "", POPULAR_FILTERS)
|
||||||
|
@ -138,7 +147,7 @@ class LocalSource(private val context: Context) : CatalogueSource {
|
||||||
for (dir in baseDirs) {
|
for (dir in baseDirs) {
|
||||||
val cover = File("${dir.absolutePath}/$url", COVER_NAME)
|
val cover = File("${dir.absolutePath}/$url", COVER_NAME)
|
||||||
if (cover.exists()) {
|
if (cover.exists()) {
|
||||||
thumbnail_url = FILE_PROTOCOL + cover.absolutePath
|
thumbnail_url = cover.absolutePath
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -152,7 +161,7 @@ class LocalSource(private val context: Context) : CatalogueSource {
|
||||||
val input = context.contentResolver.openInputStream(Uri.parse(url))
|
val input = context.contentResolver.openInputStream(Uri.parse(url))
|
||||||
try {
|
try {
|
||||||
val dest = updateCover(context, this, input)
|
val dest = updateCover(context, this, input)
|
||||||
thumbnail_url = dest?.let { FILE_PROTOCOL + it.absolutePath }
|
thumbnail_url = dest?.absolutePath
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
Timber.e(e)
|
Timber.e(e)
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,7 +7,6 @@ import android.database.Cursor
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
import android.os.ParcelFileDescriptor
|
import android.os.ParcelFileDescriptor
|
||||||
import eu.kanade.tachiyomi.BuildConfig
|
import eu.kanade.tachiyomi.BuildConfig
|
||||||
import timber.log.Timber
|
|
||||||
import java.io.IOException
|
import java.io.IOException
|
||||||
import java.net.URL
|
import java.net.URL
|
||||||
import java.net.URLConnection
|
import java.net.URLConnection
|
||||||
|
@ -40,11 +39,10 @@ class ZipContentProvider : ContentProvider() {
|
||||||
input.use {
|
input.use {
|
||||||
output.use {
|
output.use {
|
||||||
input.copyTo(output)
|
input.copyTo(output)
|
||||||
output.flush()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (e: IOException) {
|
} catch (e: IOException) {
|
||||||
Timber.e(e)
|
// Ignore
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return AssetFileDescriptor(pipe[0], 0, -1)
|
return AssetFileDescriptor(pipe[0], 0, -1)
|
||||||
|
|
Loading…
Reference in a new issue