Compare commits
3 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| b7e4c6b8c0 | |||
| df599e9d50 | |||
| 6009f089e7 |
@@ -16,7 +16,7 @@ android {
|
||||
minSdkVersion 21
|
||||
targetSdkVersion 30
|
||||
versionCode gitCommits
|
||||
versionName '0.5.2'
|
||||
versionName '0.5.3'
|
||||
|
||||
kapt {
|
||||
arguments {
|
||||
@@ -92,7 +92,7 @@ dependencies {
|
||||
implementation 'org.jsoup:jsoup:1.13.1'
|
||||
|
||||
implementation 'org.koin:koin-android:2.2.0-beta-1'
|
||||
implementation 'io.coil-kt:coil:1.0.0-rc2'
|
||||
implementation 'io.coil-kt:coil:1.0.0-rc3'
|
||||
implementation 'com.davemorrissey.labs:subsampling-scale-image-view:3.10.0'
|
||||
implementation 'com.tomclaw.cache:cache:1.0'
|
||||
|
||||
|
||||
@@ -0,0 +1,93 @@
|
||||
package org.koitharu.kotatsu.core.local
|
||||
|
||||
import androidx.annotation.CheckResult
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.withContext
|
||||
import java.io.File
|
||||
import java.io.FileInputStream
|
||||
import java.io.FileOutputStream
|
||||
import java.util.zip.ZipEntry
|
||||
import java.util.zip.ZipInputStream
|
||||
import java.util.zip.ZipOutputStream
|
||||
|
||||
class WritableCbzFile(private val file: File) {
|
||||
|
||||
private val dir = File(file.parentFile, file.nameWithoutExtension)
|
||||
|
||||
@Suppress("BlockingMethodInNonBlockingContext")
|
||||
suspend fun prepare() = withContext(Dispatchers.IO) {
|
||||
check(dir.list().isNullOrEmpty()) {
|
||||
"Dir ${dir.name} is not empty"
|
||||
}
|
||||
if (!dir.exists()) {
|
||||
dir.mkdir()
|
||||
}
|
||||
ZipInputStream(FileInputStream(file)).use { zip ->
|
||||
var entry = zip.nextEntry
|
||||
while (entry != null) {
|
||||
val target = File(dir.path + File.separator + entry.name)
|
||||
target.parentFile?.mkdirs()
|
||||
target.outputStream().use { out ->
|
||||
zip.copyTo(out)
|
||||
}
|
||||
zip.closeEntry()
|
||||
entry = zip.nextEntry
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun cleanup() = withContext(Dispatchers.IO) {
|
||||
dir.deleteRecursively()
|
||||
}
|
||||
|
||||
@CheckResult
|
||||
@Suppress("BlockingMethodInNonBlockingContext")
|
||||
suspend fun flush() = withContext(Dispatchers.IO) {
|
||||
val tempFile = File(file.path + ".tmp")
|
||||
if (tempFile.exists()) {
|
||||
tempFile.delete()
|
||||
}
|
||||
try {
|
||||
ZipOutputStream(FileOutputStream(tempFile)).use { zip ->
|
||||
dir.listFiles()?.forEach {
|
||||
zipFile(it, it.name, zip)
|
||||
}
|
||||
zip.flush()
|
||||
}
|
||||
tempFile.renameTo(file)
|
||||
} finally {
|
||||
if (tempFile.exists()) {
|
||||
tempFile.delete()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
operator fun get(name: String) = File(dir, name)
|
||||
|
||||
operator fun set(name: String, file: File) {
|
||||
file.copyTo(this[name], overwrite = true)
|
||||
}
|
||||
|
||||
companion object {
|
||||
|
||||
private fun zipFile(fileToZip: File, fileName: String, zipOut: ZipOutputStream) {
|
||||
if (fileToZip.isDirectory) {
|
||||
if (fileName.endsWith("/")) {
|
||||
zipOut.putNextEntry(ZipEntry(fileName))
|
||||
} else {
|
||||
zipOut.putNextEntry(ZipEntry("$fileName/"))
|
||||
}
|
||||
zipOut.closeEntry()
|
||||
fileToZip.listFiles()?.forEach { childFile ->
|
||||
zipFile(childFile, "$fileName/${childFile.name}", zipOut)
|
||||
}
|
||||
} else {
|
||||
FileInputStream(fileToZip).use { fis ->
|
||||
val zipEntry = ZipEntry(fileName)
|
||||
zipOut.putNextEntry(zipEntry)
|
||||
fis.copyTo(zipOut)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -95,7 +95,7 @@ open class MangaLibRepository(loaderContext: MangaLoaderContext) :
|
||||
append("/c")
|
||||
append(item.getString("chapter_number"))
|
||||
append('/')
|
||||
append(item.getJSONArray("teams").getJSONObject(0).getString("slug"))
|
||||
append(item.getJSONArray("teams").getJSONObject(0).optString("slug"))
|
||||
}
|
||||
var name = item.getString("chapter_name")
|
||||
if (name.isNullOrBlank() || name == "null") {
|
||||
@@ -142,12 +142,18 @@ open class MangaLibRepository(loaderContext: MangaLoaderContext) :
|
||||
override suspend fun getPages(chapter: MangaChapter): List<MangaPage> {
|
||||
val doc = loaderContext.httpGet(chapter.url).parseHtml()
|
||||
val scripts = doc.head().select("script")
|
||||
val pg = doc.body().getElementById("pg").html().substringAfter('=').substringBeforeLast(';')
|
||||
val pg = doc.body().getElementById("pg").html()
|
||||
.substringAfter('=')
|
||||
.substringBeforeLast(';')
|
||||
val pages = JSONArray(pg)
|
||||
for (script in scripts) {
|
||||
val raw = script.html().trim()
|
||||
if (raw.startsWith("window.__info")) {
|
||||
val json = JSONObject(raw.substringAfter('=').substringBeforeLast(';'))
|
||||
if (raw.contains("window.__info")) {
|
||||
val json = JSONObject(
|
||||
raw.substringAfter("window.__info")
|
||||
.substringAfter('=')
|
||||
.substringBeforeLast(';')
|
||||
)
|
||||
val domain = json.getJSONObject("servers").run {
|
||||
getStringOrNull("main") ?: getString(
|
||||
json.getJSONObject("img").getString("server")
|
||||
|
||||
@@ -1,63 +1,35 @@
|
||||
package org.koitharu.kotatsu.domain.local
|
||||
|
||||
import androidx.annotation.CheckResult
|
||||
import androidx.annotation.WorkerThread
|
||||
import org.koitharu.kotatsu.core.local.WritableCbzFile
|
||||
import org.koitharu.kotatsu.core.model.Manga
|
||||
import org.koitharu.kotatsu.core.model.MangaChapter
|
||||
import org.koitharu.kotatsu.utils.ext.sub
|
||||
import org.koitharu.kotatsu.utils.ext.takeIfReadable
|
||||
import org.koitharu.kotatsu.utils.ext.toFileNameSafe
|
||||
import java.io.File
|
||||
import java.util.zip.ZipEntry
|
||||
import java.util.zip.ZipInputStream
|
||||
import java.util.zip.ZipOutputStream
|
||||
|
||||
@WorkerThread
|
||||
class MangaZip(val file: File) {
|
||||
|
||||
private val dir = file.parentFile?.sub(file.name + ".tmp")?.takeIf { it.mkdir() }
|
||||
?: throw RuntimeException("Cannot create temporary directory")
|
||||
private val writableCbz = WritableCbzFile(file)
|
||||
|
||||
private var index = MangaIndex(null)
|
||||
|
||||
fun prepare(manga: Manga) {
|
||||
extract()
|
||||
index = MangaIndex(dir.sub(INDEX_ENTRY).takeIfReadable()?.readText())
|
||||
suspend fun prepare(manga: Manga) {
|
||||
writableCbz.prepare()
|
||||
index = MangaIndex(writableCbz[INDEX_ENTRY].takeIfReadable()?.readText())
|
||||
index.setMangaInfo(manga, append = true)
|
||||
}
|
||||
|
||||
fun cleanup() {
|
||||
dir.deleteRecursively()
|
||||
suspend fun cleanup() {
|
||||
writableCbz.cleanup()
|
||||
}
|
||||
|
||||
fun compress() {
|
||||
dir.sub(INDEX_ENTRY).writeText(index.toString())
|
||||
ZipOutputStream(file.outputStream()).use { out ->
|
||||
for (file in dir.listFiles().orEmpty()) {
|
||||
val entry = ZipEntry(file.name)
|
||||
out.putNextEntry(entry)
|
||||
file.inputStream().use { stream ->
|
||||
stream.copyTo(out)
|
||||
}
|
||||
out.closeEntry()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun extract() {
|
||||
if (!file.exists()) {
|
||||
return
|
||||
}
|
||||
ZipInputStream(file.inputStream()).use { input ->
|
||||
while (true) {
|
||||
val entry = input.nextEntry ?: return
|
||||
if (!entry.isDirectory) {
|
||||
dir.sub(entry.name).outputStream().use { out ->
|
||||
input.copyTo(out)
|
||||
}
|
||||
}
|
||||
input.closeEntry()
|
||||
}
|
||||
}
|
||||
@CheckResult
|
||||
suspend fun compress(): Boolean {
|
||||
writableCbz[INDEX_ENTRY].writeText(index.toString())
|
||||
return writableCbz.flush()
|
||||
}
|
||||
|
||||
fun addCover(file: File, ext: String) {
|
||||
@@ -68,7 +40,7 @@ class MangaZip(val file: File) {
|
||||
append(ext)
|
||||
}
|
||||
}
|
||||
file.copyTo(dir.sub(name), overwrite = true)
|
||||
writableCbz[name] = file
|
||||
index.setCoverEntry(name)
|
||||
}
|
||||
|
||||
@@ -80,7 +52,7 @@ class MangaZip(val file: File) {
|
||||
append(ext)
|
||||
}
|
||||
}
|
||||
file.copyTo(dir.sub(name), overwrite = true)
|
||||
writableCbz[name] = file
|
||||
index.addChapter(chapter)
|
||||
}
|
||||
|
||||
|
||||
@@ -142,7 +142,9 @@ class DownloadService : BaseService() {
|
||||
notification.setCancelId(0)
|
||||
notification.setPostProcessing()
|
||||
notification.update()
|
||||
output.compress()
|
||||
if (!output.compress()) {
|
||||
throw RuntimeException("Cannot create target file")
|
||||
}
|
||||
val result = MangaProviderFactory.createLocal().getFromFile(output.file)
|
||||
notification.setDone(result)
|
||||
notification.dismiss()
|
||||
|
||||
@@ -5,6 +5,7 @@ import android.content.Intent
|
||||
import android.widget.RemoteViews
|
||||
import android.widget.RemoteViewsService
|
||||
import coil.Coil
|
||||
import coil.executeBlocking
|
||||
import coil.request.ImageRequest
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import org.koitharu.kotatsu.R
|
||||
@@ -37,13 +38,11 @@ class RecentListFactory(private val context: Context) : RemoteViewsService.Remot
|
||||
val views = RemoteViews(context.packageName, R.layout.item_recent)
|
||||
val item = dataSet[position]
|
||||
try {
|
||||
val cover = runBlocking {
|
||||
Coil.execute(
|
||||
ImageRequest.Builder(context)
|
||||
.data(item.coverUrl)
|
||||
.build()
|
||||
).requireBitmap()
|
||||
}
|
||||
val cover = Coil.imageLoader(context).executeBlocking(
|
||||
ImageRequest.Builder(context)
|
||||
.data(item.coverUrl)
|
||||
.build()
|
||||
).requireBitmap()
|
||||
views.setImageViewBitmap(R.id.imageView_cover, cover)
|
||||
} catch (e: IOException) {
|
||||
views.setImageViewResource(R.id.imageView_cover, R.drawable.ic_placeholder)
|
||||
|
||||
@@ -5,6 +5,7 @@ import android.content.Intent
|
||||
import android.widget.RemoteViews
|
||||
import android.widget.RemoteViewsService
|
||||
import coil.Coil
|
||||
import coil.executeBlocking
|
||||
import coil.request.ImageRequest
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import org.koitharu.kotatsu.R
|
||||
@@ -48,13 +49,11 @@ class ShelfListFactory(private val context: Context, widgetId: Int) : RemoteView
|
||||
val item = dataSet[position]
|
||||
views.setTextViewText(R.id.textView_title, item.title)
|
||||
try {
|
||||
val cover = runBlocking {
|
||||
Coil.execute(
|
||||
ImageRequest.Builder(context)
|
||||
.data(item.coverUrl)
|
||||
.build()
|
||||
).requireBitmap()
|
||||
}
|
||||
val cover = Coil.imageLoader(context).executeBlocking(
|
||||
ImageRequest.Builder(context)
|
||||
.data(item.coverUrl)
|
||||
.build()
|
||||
).requireBitmap()
|
||||
views.setImageViewBitmap(R.id.imageView_cover, cover)
|
||||
} catch (e: IOException) {
|
||||
views.setImageViewResource(R.id.imageView_cover, R.drawable.ic_placeholder)
|
||||
|
||||
Reference in New Issue
Block a user