Load pages from cbz
This commit is contained in:
@@ -10,6 +10,7 @@ import org.koitharu.kotatsu.core.model.*
|
|||||||
import org.koitharu.kotatsu.domain.MangaLoaderContext
|
import org.koitharu.kotatsu.domain.MangaLoaderContext
|
||||||
import org.koitharu.kotatsu.domain.local.MangaIndex
|
import org.koitharu.kotatsu.domain.local.MangaIndex
|
||||||
import org.koitharu.kotatsu.domain.local.MangaZip
|
import org.koitharu.kotatsu.domain.local.MangaZip
|
||||||
|
import org.koitharu.kotatsu.utils.AlphanumComparator
|
||||||
import org.koitharu.kotatsu.utils.ext.longHashCode
|
import org.koitharu.kotatsu.utils.ext.longHashCode
|
||||||
import org.koitharu.kotatsu.utils.ext.readText
|
import org.koitharu.kotatsu.utils.ext.readText
|
||||||
import org.koitharu.kotatsu.utils.ext.safe
|
import org.koitharu.kotatsu.utils.ext.safe
|
||||||
@@ -43,7 +44,7 @@ class LocalMangaRepository(loaderContext: MangaLoaderContext) : BaseMangaReposit
|
|||||||
.filter { x -> !x.isDirectory && x.name.substringBefore('.').matches(pattern) }
|
.filter { x -> !x.isDirectory && x.name.substringBefore('.').matches(pattern) }
|
||||||
} else {
|
} else {
|
||||||
zip.entries().asSequence().filter { x -> !x.isDirectory }
|
zip.entries().asSequence().filter { x -> !x.isDirectory }
|
||||||
}
|
}.toList().sortedWith(compareBy(AlphanumComparator()) { x -> x.name})
|
||||||
return entries.map { x ->
|
return entries.map { x ->
|
||||||
val uri = zipUri(file, x.name)
|
val uri = zipUri(file, x.name)
|
||||||
MangaPage(
|
MangaPage(
|
||||||
@@ -51,7 +52,7 @@ class LocalMangaRepository(loaderContext: MangaLoaderContext) : BaseMangaReposit
|
|||||||
url = uri,
|
url = uri,
|
||||||
source = MangaSource.LOCAL
|
source = MangaSource.LOCAL
|
||||||
)
|
)
|
||||||
}.toList()
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun getDetails(file: File): Manga {
|
private fun getDetails(file: File): Manga {
|
||||||
|
|||||||
@@ -11,7 +11,8 @@ import org.koitharu.kotatsu.core.model.MangaPage
|
|||||||
import org.koitharu.kotatsu.ui.common.list.BaseViewHolder
|
import org.koitharu.kotatsu.ui.common.list.BaseViewHolder
|
||||||
import org.koitharu.kotatsu.utils.ext.getDisplayMessage
|
import org.koitharu.kotatsu.utils.ext.getDisplayMessage
|
||||||
|
|
||||||
class PageHolder(parent: ViewGroup, private val loader: PageLoader) : BaseViewHolder<MangaPage, Unit>(parent, R.layout.item_page),
|
class PageHolder(parent: ViewGroup, private val loader: PageLoader) :
|
||||||
|
BaseViewHolder<MangaPage, Unit>(parent, R.layout.item_page),
|
||||||
SubsamplingScaleImageView.OnImageEventListener {
|
SubsamplingScaleImageView.OnImageEventListener {
|
||||||
|
|
||||||
init {
|
init {
|
||||||
@@ -26,16 +27,20 @@ class PageHolder(parent: ViewGroup, private val loader: PageLoader) : BaseViewHo
|
|||||||
progressBar.isVisible = true
|
progressBar.isVisible = true
|
||||||
ssiv.recycle()
|
ssiv.recycle()
|
||||||
loader.load(data.url) {
|
loader.load(data.url) {
|
||||||
ssiv.setImage(ImageSource.uri(it.toUri()))
|
val uri = it.getOrNull()?.toUri()
|
||||||
|
if (uri != null) {
|
||||||
|
ssiv.setImage(ImageSource.uri(uri))
|
||||||
|
}
|
||||||
|
val error = it.exceptionOrNull()
|
||||||
|
if (error != null) {
|
||||||
|
onError(error)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onReady() = Unit
|
override fun onReady() = Unit
|
||||||
|
|
||||||
override fun onImageLoadError(e: Exception) {
|
override fun onImageLoadError(e: Exception) = onError(e)
|
||||||
textView_error.text = e.getDisplayMessage(context.resources)
|
|
||||||
layout_error.isVisible = true
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onImageLoaded() {
|
override fun onImageLoaded() {
|
||||||
progressBar.isVisible = false
|
progressBar.isVisible = false
|
||||||
@@ -46,4 +51,10 @@ class PageHolder(parent: ViewGroup, private val loader: PageLoader) : BaseViewHo
|
|||||||
override fun onPreviewReleased() = Unit
|
override fun onPreviewReleased() = Unit
|
||||||
|
|
||||||
override fun onPreviewLoadError(e: Exception?) = Unit
|
override fun onPreviewLoadError(e: Exception?) = Unit
|
||||||
|
|
||||||
|
private fun onError(e: Throwable) {
|
||||||
|
textView_error.text = e.getDisplayMessage(context.resources)
|
||||||
|
layout_error.isVisible = true
|
||||||
|
progressBar.isVisible = false
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
package org.koitharu.kotatsu.ui.reader
|
package org.koitharu.kotatsu.ui.reader
|
||||||
|
|
||||||
import android.content.Context
|
import android.net.Uri
|
||||||
import kotlinx.coroutines.*
|
import kotlinx.coroutines.*
|
||||||
import okhttp3.OkHttpClient
|
import okhttp3.OkHttpClient
|
||||||
import okhttp3.Request
|
import okhttp3.Request
|
||||||
@@ -9,9 +9,10 @@ import org.koin.core.inject
|
|||||||
import org.koitharu.kotatsu.core.local.PagesCache
|
import org.koitharu.kotatsu.core.local.PagesCache
|
||||||
import org.koitharu.kotatsu.utils.ext.await
|
import org.koitharu.kotatsu.utils.ext.await
|
||||||
import java.io.File
|
import java.io.File
|
||||||
|
import java.util.zip.ZipFile
|
||||||
import kotlin.coroutines.CoroutineContext
|
import kotlin.coroutines.CoroutineContext
|
||||||
|
|
||||||
class PageLoader(context: Context) : KoinComponent, CoroutineScope, DisposableHandle {
|
class PageLoader : KoinComponent, CoroutineScope, DisposableHandle {
|
||||||
|
|
||||||
private val job = SupervisorJob()
|
private val job = SupervisorJob()
|
||||||
private val tasks = HashMap<String, Job>()
|
private val tasks = HashMap<String, Job>()
|
||||||
@@ -21,9 +22,11 @@ class PageLoader(context: Context) : KoinComponent, CoroutineScope, DisposableHa
|
|||||||
override val coroutineContext: CoroutineContext
|
override val coroutineContext: CoroutineContext
|
||||||
get() = Dispatchers.Main + job
|
get() = Dispatchers.Main + job
|
||||||
|
|
||||||
fun load(url: String, callback: (File) -> Unit) = launch {
|
fun load(url: String, force: Boolean = false, callback: (Result<File>) -> Unit) = launch {
|
||||||
val result = withContext(Dispatchers.IO) {
|
val result = runCatching {
|
||||||
loadFile(url, false)
|
withContext(Dispatchers.IO) {
|
||||||
|
loadFile(url, force)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
callback(result)
|
callback(result)
|
||||||
}
|
}
|
||||||
@@ -31,17 +34,27 @@ class PageLoader(context: Context) : KoinComponent, CoroutineScope, DisposableHa
|
|||||||
private suspend fun loadFile(url: String, force: Boolean): File {
|
private suspend fun loadFile(url: String, force: Boolean): File {
|
||||||
if (!force) {
|
if (!force) {
|
||||||
cache[url]?.let {
|
cache[url]?.let {
|
||||||
|
|
||||||
return it
|
return it
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
val request = Request.Builder()
|
val uri = Uri.parse(url)
|
||||||
.url(url)
|
return if (uri.scheme == "cbz") {
|
||||||
.get()
|
val zip = ZipFile(uri.schemeSpecificPart)
|
||||||
.build()
|
val entry = zip.getEntry(uri.fragment)
|
||||||
return okHttp.newCall(request).await().use { response ->
|
zip.getInputStream(entry).use {
|
||||||
cache.put(url) { out ->
|
cache.put(url) { out ->
|
||||||
response.body!!.byteStream().copyTo(out)
|
it.copyTo(out)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
val request = Request.Builder()
|
||||||
|
.url(url)
|
||||||
|
.get()
|
||||||
|
.build()
|
||||||
|
okHttp.newCall(request).await().use { response ->
|
||||||
|
cache.put(url) { out ->
|
||||||
|
response.body!!.byteStream().copyTo(out)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -56,7 +56,7 @@ class ReaderActivity : BaseFullscreenActivity(), ReaderView, GridTouchHelper.OnG
|
|||||||
insets
|
insets
|
||||||
}
|
}
|
||||||
|
|
||||||
loader = PageLoader(this)
|
loader = PageLoader()
|
||||||
adapter = PagesAdapter(loader)
|
adapter = PagesAdapter(loader)
|
||||||
pager.adapter = adapter
|
pager.adapter = adapter
|
||||||
presenter.loadChapter(state)
|
presenter.loadChapter(state)
|
||||||
|
|||||||
@@ -0,0 +1,63 @@
|
|||||||
|
package org.koitharu.kotatsu.utils
|
||||||
|
|
||||||
|
class AlphanumComparator : Comparator<String> {
|
||||||
|
|
||||||
|
override fun compare(s1: String?, s2: String?): Int {
|
||||||
|
if (s1 == null || s2 == null) {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
var thisMarker = 0
|
||||||
|
var thatMarker = 0
|
||||||
|
val s1Length = s1.length
|
||||||
|
val s2Length = s2.length
|
||||||
|
while (thisMarker < s1Length && thatMarker < s2Length) {
|
||||||
|
val thisChunk = getChunk(s1, s1Length, thisMarker)
|
||||||
|
thisMarker += thisChunk.length
|
||||||
|
val thatChunk = getChunk(s2, s2Length, thatMarker)
|
||||||
|
thatMarker += thatChunk.length
|
||||||
|
// If both chunks contain numeric characters, sort them numerically
|
||||||
|
var result = 0
|
||||||
|
if (thisChunk[0].isDigit() && thatChunk[0].isDigit()) { // Simple chunk comparison by length.
|
||||||
|
val thisChunkLength = thisChunk.length
|
||||||
|
result = thisChunkLength - thatChunk.length
|
||||||
|
// If equal, the first different number counts
|
||||||
|
if (result == 0) {
|
||||||
|
for (i in 0 until thisChunkLength) {
|
||||||
|
result = thisChunk[i] - thatChunk[i]
|
||||||
|
if (result != 0) {
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
result = thisChunk.compareTo(thatChunk)
|
||||||
|
}
|
||||||
|
if (result != 0) return result
|
||||||
|
}
|
||||||
|
return s1Length - s2Length
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun getChunk(s: String, slength: Int, marker: Int): String {
|
||||||
|
var marker = marker
|
||||||
|
val chunk = StringBuilder()
|
||||||
|
var c = s[marker]
|
||||||
|
chunk.append(c)
|
||||||
|
marker++
|
||||||
|
if (c.isDigit()) {
|
||||||
|
while (marker < slength) {
|
||||||
|
c = s[marker]
|
||||||
|
if (!c.isDigit()) break
|
||||||
|
chunk.append(c)
|
||||||
|
marker++
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
while (marker < slength) {
|
||||||
|
c = s[marker]
|
||||||
|
if (c.isDigit()) break
|
||||||
|
chunk.append(c)
|
||||||
|
marker++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return chunk.toString()
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user