Compare commits

...

5 Commits

Author SHA1 Message Date
Koitharu
e3a80b5a6d Enhance download cancellation in blocking io tasks #90 2022-01-23 10:31:50 +02:00
Koitharu
66dc5a9597 Fix MangaTown licensed chapters 2022-01-23 10:31:50 +02:00
Koitharu
cb6bf91dd3 Fix missing fragment crash #91 2022-01-23 10:31:50 +02:00
Koitharu
fb815abad0 Fix widgets #86 2022-01-23 10:31:50 +02:00
Koitharu
8ef7580097 Fix MangaRead parse #87 2022-01-23 10:31:50 +02:00
11 changed files with 79 additions and 42 deletions

View File

@@ -13,8 +13,8 @@ android {
applicationId 'org.koitharu.kotatsu' applicationId 'org.koitharu.kotatsu'
minSdkVersion 21 minSdkVersion 21
targetSdkVersion 31 targetSdkVersion 31
versionCode 378 versionCode 379
versionName '2.1.2' versionName '2.1.3'
generatedDensities = [] generatedDensities = []
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"

View File

@@ -7,5 +7,6 @@
public static void checkParameterIsNotNull(...); public static void checkParameterIsNotNull(...);
public static void checkNotNullParameter(...); public static void checkNotNullParameter(...);
} }
-keep public class ** extends org.koitharu.kotatsu.base.ui.BaseFragment
-keep class org.koitharu.kotatsu.core.db.entity.* { *; } -keep class org.koitharu.kotatsu.core.db.entity.* { *; }
-dontwarn okhttp3.internal.platform.ConscryptPlatform -dontwarn okhttp3.internal.platform.ConscryptPlatform

View File

@@ -53,8 +53,10 @@ abstract class RemoteMangaRepository(
if (subdomain != null) { if (subdomain != null) {
append(subdomain) append(subdomain)
append('.') append('.')
append(conf.getDomain(defaultDomain).removePrefix("www."))
} else {
append(conf.getDomain(defaultDomain))
} }
append(conf.getDomain(defaultDomain))
append(this@withDomain) append(this@withDomain)
} }
else -> this else -> this

View File

@@ -128,7 +128,7 @@ class MangaTownRepository(loaderContext: MangaLoaderContext) :
scanlator = null, scanlator = null,
branch = null, branch = null,
) )
} } ?: bypassLicensedChapters(manga)
) )
} }
@@ -191,6 +191,32 @@ class MangaTownRepository(loaderContext: MangaLoaderContext) :
map[SourceSettings.KEY_USE_SSL] = true map[SourceSettings.KEY_USE_SSL] = true
} }
private suspend fun bypassLicensedChapters(manga: Manga): List<MangaChapter> {
val doc = loaderContext.httpGet(manga.url.withDomain("m")).parseHtml()
val list = doc.body().selectFirst("ul.detail-ch-list") ?: return emptyList()
val dateFormat = SimpleDateFormat("MMM dd,yyyy", Locale.US)
return list.select("li").asReversed().mapIndexedNotNull { i, li ->
val a = li.selectFirst("a") ?: return@mapIndexedNotNull null
val href = a.relUrl("href")
val name = a.selectFirst("span.vol")?.text().orEmpty().ifEmpty {
a.ownText()
}
MangaChapter(
id = generateUid(href),
url = href,
source = MangaSource.MANGATOWN,
number = i + 1,
uploadDate = parseChapterDate(
dateFormat,
li.selectFirst("span.time")?.text()
),
name = name.ifEmpty { "${manga.title} - ${i + 1}" },
scanlator = null,
branch = null,
)
}
}
private fun String.parseTagKey() = split('/').findLast { TAG_REGEX matches it } private fun String.parseTagKey() = split('/').findLast { TAG_REGEX matches it }
private companion object { private companion object {

View File

@@ -151,8 +151,10 @@ class MangareadRepository(
?.selectFirst("div.reading-content") ?.selectFirst("div.reading-content")
?: throw ParseException("Root not found") ?: throw ParseException("Root not found")
return root.select("div.page-break").map { div -> return root.select("div.page-break").map { div ->
val img = div.selectFirst("img") val img = div.selectFirst("img") ?: parseFailed("Page image not found")
val url = img?.relUrl("src") ?: parseFailed("Page image not found") val url = img.relUrl("data-src").ifEmpty {
img.relUrl("src")
}
MangaPage( MangaPage(
id = generateUid(url), id = generateUid(url),
url = url, url = url,

View File

@@ -145,7 +145,7 @@ class DownloadManager(
while (true) { while (true) {
try { try {
val response = call.clone().await() val response = call.clone().await()
withContext(Dispatchers.IO) { runInterruptible(Dispatchers.IO) {
file.outputStream().use { out -> file.outputStream().use { out ->
checkNotNull(response.body).byteStream().copyTo(out) checkNotNull(response.body).byteStream().copyTo(out)
} }

View File

@@ -31,7 +31,7 @@ class MangaZip(val file: File) {
return writableCbz.flush() return writableCbz.flush()
} }
fun addCover(file: File, ext: String) { suspend fun addCover(file: File, ext: String) {
val name = buildString { val name = buildString {
append(FILENAME_PATTERN.format(0, 0)) append(FILENAME_PATTERN.format(0, 0))
if (ext.isNotEmpty() && ext.length <= 4) { if (ext.isNotEmpty() && ext.length <= 4) {
@@ -39,11 +39,11 @@ class MangaZip(val file: File) {
append(ext) append(ext)
} }
} }
writableCbz[name] = file writableCbz.put(name, file)
index.setCoverEntry(name) index.setCoverEntry(name)
} }
fun addPage(chapter: MangaChapter, file: File, pageNumber: Int, ext: String) { suspend fun addPage(chapter: MangaChapter, file: File, pageNumber: Int, ext: String) {
val name = buildString { val name = buildString {
append(FILENAME_PATTERN.format(chapter.number, pageNumber)) append(FILENAME_PATTERN.format(chapter.number, pageNumber))
if (ext.isNotEmpty() && ext.length <= 4) { if (ext.isNotEmpty() && ext.length <= 4) {
@@ -51,7 +51,7 @@ class MangaZip(val file: File) {
append(ext) append(ext)
} }
} }
writableCbz[name] = file writableCbz.put(name, file)
index.addChapter(chapter) index.addChapter(chapter)
} }

View File

@@ -1,8 +1,7 @@
package org.koitharu.kotatsu.local.data package org.koitharu.kotatsu.local.data
import androidx.annotation.CheckResult import androidx.annotation.CheckResult
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.*
import kotlinx.coroutines.withContext
import java.io.File import java.io.File
import java.io.FileInputStream import java.io.FileInputStream
import java.io.FileOutputStream import java.io.FileOutputStream
@@ -27,11 +26,13 @@ class WritableCbzFile(private val file: File) {
} }
ZipInputStream(FileInputStream(file)).use { zip -> ZipInputStream(FileInputStream(file)).use { zip ->
var entry = zip.nextEntry var entry = zip.nextEntry
while (entry != null) { while (entry != null && currentCoroutineContext().isActive) {
val target = File(dir.path + File.separator + entry.name) val target = File(dir.path + File.separator + entry.name)
target.parentFile?.mkdirs() runInterruptible {
target.outputStream().use { out -> target.parentFile?.mkdirs()
zip.copyTo(out) target.outputStream().use { out ->
zip.copyTo(out)
}
} }
zip.closeEntry() zip.closeEntry()
entry = zip.nextEntry entry = zip.nextEntry
@@ -51,11 +52,13 @@ class WritableCbzFile(private val file: File) {
tempFile.delete() tempFile.delete()
} }
try { try {
ZipOutputStream(FileOutputStream(tempFile)).use { zip -> runInterruptible {
dir.listFiles()?.forEach { ZipOutputStream(FileOutputStream(tempFile)).use { zip ->
zipFile(it, it.name, zip) dir.listFiles()?.forEach {
zipFile(it, it.name, zip)
}
zip.flush()
} }
zip.flush()
} }
tempFile.renameTo(file) tempFile.renameTo(file)
} finally { } finally {
@@ -67,29 +70,26 @@ class WritableCbzFile(private val file: File) {
operator fun get(name: String) = File(dir, name) operator fun get(name: String) = File(dir, name)
operator fun set(name: String, file: File) { suspend fun put(name: String, file: File) = runInterruptible(Dispatchers.IO) {
file.copyTo(this[name], overwrite = true) file.copyTo(this[name], overwrite = true)
} }
companion object { private fun zipFile(fileToZip: File, fileName: String, zipOut: ZipOutputStream) {
if (fileToZip.isDirectory) {
private fun zipFile(fileToZip: File, fileName: String, zipOut: ZipOutputStream) { if (fileName.endsWith("/")) {
if (fileToZip.isDirectory) { zipOut.putNextEntry(ZipEntry(fileName))
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 { } else {
FileInputStream(fileToZip).use { fis -> zipOut.putNextEntry(ZipEntry("$fileName/"))
val zipEntry = ZipEntry(fileName) }
zipOut.putNextEntry(zipEntry) zipOut.closeEntry()
fis.copyTo(zipOut) 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)
} }
} }
} }

View File

@@ -10,4 +10,10 @@ object PendingIntentCompat {
} else { } else {
0 0
} }
val FLAG_MUTABLE = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
PendingIntent.FLAG_MUTABLE
} else {
0
}
} }

View File

@@ -31,7 +31,7 @@ class RecentWidgetProvider : AppWidgetProvider() {
context, context,
0, 0,
intent, intent,
PendingIntent.FLAG_UPDATE_CURRENT or PendingIntentCompat.FLAG_IMMUTABLE PendingIntent.FLAG_UPDATE_CURRENT or PendingIntentCompat.FLAG_MUTABLE
) )
) )
views.setEmptyView(R.id.stackView, R.id.textView_holder) views.setEmptyView(R.id.stackView, R.id.textView_holder)

View File

@@ -31,7 +31,7 @@ class ShelfWidgetProvider : AppWidgetProvider() {
context, context,
0, 0,
intent, intent,
PendingIntent.FLAG_UPDATE_CURRENT or PendingIntentCompat.FLAG_IMMUTABLE PendingIntent.FLAG_UPDATE_CURRENT or PendingIntentCompat.FLAG_MUTABLE
) )
) )
views.setEmptyView(R.id.gridView, R.id.textView_holder) views.setEmptyView(R.id.gridView, R.id.textView_holder)