Fix downloading
This commit is contained in:
@@ -5,6 +5,7 @@ import androidx.core.content.ContextCompat
|
|||||||
import androidx.core.view.isVisible
|
import androidx.core.view.isVisible
|
||||||
import com.hannesdorfmann.adapterdelegates4.dsl.adapterDelegateViewBinding
|
import com.hannesdorfmann.adapterdelegates4.dsl.adapterDelegateViewBinding
|
||||||
import org.koitharu.kotatsu.R
|
import org.koitharu.kotatsu.R
|
||||||
|
import org.koitharu.kotatsu.core.model.formatNumber
|
||||||
import org.koitharu.kotatsu.core.ui.list.AdapterDelegateClickListenerAdapter
|
import org.koitharu.kotatsu.core.ui.list.AdapterDelegateClickListenerAdapter
|
||||||
import org.koitharu.kotatsu.core.ui.list.OnListItemClickListener
|
import org.koitharu.kotatsu.core.ui.list.OnListItemClickListener
|
||||||
import org.koitharu.kotatsu.core.util.ext.drawableStart
|
import org.koitharu.kotatsu.core.util.ext.drawableStart
|
||||||
|
|||||||
@@ -193,12 +193,12 @@ class DownloadWorker @AssistedInject constructor(
|
|||||||
val chapters = getChapters(mangaDetails, includedIds)
|
val chapters = getChapters(mangaDetails, includedIds)
|
||||||
for ((chapterIndex, chapter) in chapters.withIndex()) {
|
for ((chapterIndex, chapter) in chapters.withIndex()) {
|
||||||
checkIsPaused()
|
checkIsPaused()
|
||||||
if (chaptersToSkip.remove(chapter.id)) {
|
if (chaptersToSkip.remove(chapter.value.id)) {
|
||||||
publishState(currentState.copy(downloadedChapters = currentState.downloadedChapters + 1))
|
publishState(currentState.copy(downloadedChapters = currentState.downloadedChapters + 1))
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
val pages = runFailsafe {
|
val pages = runFailsafe {
|
||||||
repo.getPages(chapter)
|
repo.getPages(chapter.value)
|
||||||
} ?: continue
|
} ?: continue
|
||||||
val pageCounter = AtomicInteger(0)
|
val pageCounter = AtomicInteger(0)
|
||||||
channelFlow {
|
channelFlow {
|
||||||
@@ -237,7 +237,7 @@ class DownloadWorker @AssistedInject constructor(
|
|||||||
),
|
),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
if (output.flushChapter(chapter)) {
|
if (output.flushChapter(chapter.value)) {
|
||||||
runCatchingCancellable {
|
runCatchingCancellable {
|
||||||
localStorageChanges.emit(LocalMangaInput.of(output.rootFile).getManga())
|
localStorageChanges.emit(LocalMangaInput.of(output.rootFile).getManga())
|
||||||
}.onFailure(Throwable::printStackTraceDebug)
|
}.onFailure(Throwable::printStackTraceDebug)
|
||||||
@@ -377,19 +377,26 @@ class DownloadWorker @AssistedInject constructor(
|
|||||||
private fun getChapters(
|
private fun getChapters(
|
||||||
manga: Manga,
|
manga: Manga,
|
||||||
includedIds: LongArray?,
|
includedIds: LongArray?,
|
||||||
): List<MangaChapter> {
|
): List<IndexedValue<MangaChapter>> {
|
||||||
val chapters = checkNotNull(manga.chapters) {
|
val chapters = checkNotNull(manga.chapters) { "Chapters list must not be null" }
|
||||||
"Chapters list must not be null"
|
val chaptersIdsSet = includedIds?.toMutableSet()
|
||||||
}.toMutableList()
|
val result = ArrayList<IndexedValue<MangaChapter>>((chaptersIdsSet ?: chapters).size)
|
||||||
if (includedIds != null) {
|
val counters = HashMap<String?, Int>()
|
||||||
val chaptersIdsSet = includedIds.toMutableSet()
|
for (chapter in chapters) {
|
||||||
chapters.retainAll { x -> chaptersIdsSet.remove(x.id) }
|
val index = counters[chapter.branch] ?: 0
|
||||||
|
counters[chapter.branch] = index + 1
|
||||||
|
if (chaptersIdsSet != null && !chaptersIdsSet.remove(chapter.id)) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
result.add(IndexedValue(index, chapter))
|
||||||
|
}
|
||||||
|
if (chaptersIdsSet != null) {
|
||||||
check(chaptersIdsSet.isEmpty()) {
|
check(chaptersIdsSet.isEmpty()) {
|
||||||
"${chaptersIdsSet.size} of ${includedIds.size} requested chapters not found in manga"
|
"${chaptersIdsSet.size} of ${includedIds.size} requested chapters not found in manga"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
check(chapters.isNotEmpty()) { "Chapters list must not be empty" }
|
check(result.isNotEmpty()) { "Chapters list must not be empty" }
|
||||||
return chapters
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
private suspend inline fun <T> withMangaLock(manga: Manga, block: () -> T) = try {
|
private suspend inline fun <T> withMangaLock(manga: Manga, block: () -> T) = try {
|
||||||
|
|||||||
@@ -88,20 +88,20 @@ class MangaIndex(source: String?) {
|
|||||||
|
|
||||||
fun getCoverEntry(): String? = json.getStringOrNull("cover_entry")
|
fun getCoverEntry(): String? = json.getStringOrNull("cover_entry")
|
||||||
|
|
||||||
fun addChapter(chapter: MangaChapter, filename: String?) {
|
fun addChapter(chapter: IndexedValue<MangaChapter>, filename: String?) {
|
||||||
val chapters = json.getJSONObject("chapters")
|
val chapters = json.getJSONObject("chapters")
|
||||||
if (!chapters.has(chapter.id.toString())) {
|
if (!chapters.has(chapter.value.id.toString())) {
|
||||||
val jo = JSONObject()
|
val jo = JSONObject()
|
||||||
jo.put("number", chapter.number)
|
jo.put("number", chapter.value.number)
|
||||||
jo.put("volume", chapter.volume)
|
jo.put("volume", chapter.value.volume)
|
||||||
jo.put("url", chapter.url)
|
jo.put("url", chapter.value.url)
|
||||||
jo.put("name", chapter.name)
|
jo.put("name", chapter.value.name)
|
||||||
jo.put("uploadDate", chapter.uploadDate)
|
jo.put("uploadDate", chapter.value.uploadDate)
|
||||||
jo.put("scanlator", chapter.scanlator)
|
jo.put("scanlator", chapter.value.scanlator)
|
||||||
jo.put("branch", chapter.branch)
|
jo.put("branch", chapter.value.branch)
|
||||||
jo.put("entries", "%08d_%03d\\d{3}".format(chapter.branch.hashCode(), chapter.number))
|
jo.put("entries", "%08d_%03d\\d{3}".format(chapter.value.branch.hashCode(), chapter.index + 1))
|
||||||
jo.put("file", filename)
|
jo.put("file", filename)
|
||||||
chapters.put(chapter.id.toString(), jo)
|
chapters.put(chapter.value.id.toString(), jo)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import androidx.core.net.toFile
|
|||||||
import androidx.core.net.toUri
|
import androidx.core.net.toUri
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.runInterruptible
|
import kotlinx.coroutines.runInterruptible
|
||||||
|
import org.koitharu.kotatsu.core.util.AlphanumComparator
|
||||||
import org.koitharu.kotatsu.core.util.ext.longHashCode
|
import org.koitharu.kotatsu.core.util.ext.longHashCode
|
||||||
import org.koitharu.kotatsu.core.util.ext.readText
|
import org.koitharu.kotatsu.core.util.ext.readText
|
||||||
import org.koitharu.kotatsu.core.util.ext.toListSorted
|
import org.koitharu.kotatsu.core.util.ext.toListSorted
|
||||||
@@ -71,7 +72,7 @@ class LocalMangaZipInput(root: File) : LocalMangaInput(root) {
|
|||||||
publicUrl = fileUri,
|
publicUrl = fileUri,
|
||||||
source = MangaSource.LOCAL,
|
source = MangaSource.LOCAL,
|
||||||
coverUrl = zipUri(root, findFirstImageEntry(zip.entries())?.name.orEmpty()),
|
coverUrl = zipUri(root, findFirstImageEntry(zip.entries())?.name.orEmpty()),
|
||||||
chapters = chapters.sortedWith(org.koitharu.kotatsu.core.util.AlphanumComparator())
|
chapters = chapters.sortedWith(AlphanumComparator())
|
||||||
.mapIndexed { i, s ->
|
.mapIndexed { i, s ->
|
||||||
MangaChapter(
|
MangaChapter(
|
||||||
id = "$i$s".longHashCode(),
|
id = "$i$s".longHashCode(),
|
||||||
@@ -127,7 +128,7 @@ class LocalMangaZipInput(root: File) : LocalMangaInput(root) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
entries
|
entries
|
||||||
.toListSorted(compareBy(org.koitharu.kotatsu.core.util.AlphanumComparator()) { x -> x.name })
|
.toListSorted(compareBy(AlphanumComparator()) { x -> x.name })
|
||||||
.map { x ->
|
.map { x ->
|
||||||
val entryUri = zipUri(file, x.name)
|
val entryUri = zipUri(file, x.name)
|
||||||
MangaPage(
|
MangaPage(
|
||||||
@@ -143,7 +144,7 @@ class LocalMangaZipInput(root: File) : LocalMangaInput(root) {
|
|||||||
private fun findFirstImageEntry(entries: Enumeration<out ZipEntry>): ZipEntry? {
|
private fun findFirstImageEntry(entries: Enumeration<out ZipEntry>): ZipEntry? {
|
||||||
val list = entries.toList()
|
val list = entries.toList()
|
||||||
.filterNot { it.isDirectory }
|
.filterNot { it.isDirectory }
|
||||||
.sortedWith(compareBy(org.koitharu.kotatsu.core.util.AlphanumComparator()) { x -> x.name })
|
.sortedWith(compareBy(AlphanumComparator()) { x -> x.name })
|
||||||
val map = MimeTypeMap.getSingleton()
|
val map = MimeTypeMap.getSingleton()
|
||||||
return list.firstOrNull {
|
return list.firstOrNull {
|
||||||
map.getMimeTypeFromExtension(it.name.substringAfterLast('.'))
|
map.getMimeTypeFromExtension(it.name.substringAfterLast('.'))
|
||||||
|
|||||||
@@ -4,7 +4,6 @@ import kotlinx.coroutines.Dispatchers
|
|||||||
import kotlinx.coroutines.runInterruptible
|
import kotlinx.coroutines.runInterruptible
|
||||||
import kotlinx.coroutines.sync.Mutex
|
import kotlinx.coroutines.sync.Mutex
|
||||||
import kotlinx.coroutines.sync.withLock
|
import kotlinx.coroutines.sync.withLock
|
||||||
import org.koitharu.kotatsu.core.model.findById
|
|
||||||
import org.koitharu.kotatsu.core.model.isLocal
|
import org.koitharu.kotatsu.core.model.isLocal
|
||||||
import org.koitharu.kotatsu.core.util.ext.deleteAwait
|
import org.koitharu.kotatsu.core.util.ext.deleteAwait
|
||||||
import org.koitharu.kotatsu.core.util.ext.takeIfReadable
|
import org.koitharu.kotatsu.core.util.ext.takeIfReadable
|
||||||
@@ -47,12 +46,12 @@ class LocalMangaDirOutput(
|
|||||||
flushIndex()
|
flushIndex()
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun addPage(chapter: MangaChapter, file: File, pageNumber: Int, ext: String) = mutex.withLock {
|
override suspend fun addPage(chapter: IndexedValue<MangaChapter>, file: File, pageNumber: Int, ext: String) = mutex.withLock {
|
||||||
val output = chaptersOutput.getOrPut(chapter) {
|
val output = chaptersOutput.getOrPut(chapter.value) {
|
||||||
ZipOutput(File(rootFile, chapterFileName(chapter) + SUFFIX_TMP))
|
ZipOutput(File(rootFile, chapterFileName(chapter) + SUFFIX_TMP))
|
||||||
}
|
}
|
||||||
val name = buildString {
|
val name = buildString {
|
||||||
append(FILENAME_PATTERN.format(chapter.branch.hashCode(), chapter.number, pageNumber))
|
append(FILENAME_PATTERN.format(chapter.value.branch.hashCode(), chapter.index + 1, pageNumber))
|
||||||
if (ext.isNotEmpty() && ext.length <= 4) {
|
if (ext.isNotEmpty() && ext.length <= 4) {
|
||||||
append('.')
|
append('.')
|
||||||
append(ext)
|
append(ext)
|
||||||
@@ -92,9 +91,9 @@ class LocalMangaDirOutput(
|
|||||||
}
|
}
|
||||||
|
|
||||||
suspend fun deleteChapter(chapterId: Long) = mutex.withLock {
|
suspend fun deleteChapter(chapterId: Long) = mutex.withLock {
|
||||||
val chapter = checkNotNull(index.getMangaInfo()?.chapters) {
|
val chapter = checkNotNull(index.getMangaInfo()?.chapters?.withIndex()) {
|
||||||
"No chapters found"
|
"No chapters found"
|
||||||
}.findById(chapterId) ?: error("Chapter not found")
|
}.find { x -> x.value.id == chapterId } ?: error("Chapter not found")
|
||||||
val chapterDir = File(rootFile, chapterFileName(chapter))
|
val chapterDir = File(rootFile, chapterFileName(chapter))
|
||||||
chapterDir.deleteAwait()
|
chapterDir.deleteAwait()
|
||||||
index.removeChapter(chapterId)
|
index.removeChapter(chapterId)
|
||||||
@@ -111,11 +110,11 @@ class LocalMangaDirOutput(
|
|||||||
file.renameTo(resFile)
|
file.renameTo(resFile)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun chapterFileName(chapter: MangaChapter): String {
|
private fun chapterFileName(chapter: IndexedValue<MangaChapter>): String {
|
||||||
index.getChapterFileName(chapter.id)?.let {
|
index.getChapterFileName(chapter.value.id)?.let {
|
||||||
return it
|
return it
|
||||||
}
|
}
|
||||||
val baseName = "${chapter.number}_${chapter.name.toFileNameSafe()}".take(18)
|
val baseName = "${chapter.index}_${chapter.value.name.toFileNameSafe()}".take(18)
|
||||||
var i = 0
|
var i = 0
|
||||||
while (true) {
|
while (true) {
|
||||||
val name = (if (i == 0) baseName else baseName + "_$i") + ".cbz"
|
val name = (if (i == 0) baseName else baseName + "_$i") + ".cbz"
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ sealed class LocalMangaOutput(
|
|||||||
|
|
||||||
abstract suspend fun addCover(file: File, ext: String)
|
abstract suspend fun addCover(file: File, ext: String)
|
||||||
|
|
||||||
abstract suspend fun addPage(chapter: MangaChapter, file: File, pageNumber: Int, ext: String)
|
abstract suspend fun addPage(chapter: IndexedValue<MangaChapter>, file: File, pageNumber: Int, ext: String)
|
||||||
|
|
||||||
abstract suspend fun flushChapter(chapter: MangaChapter): Boolean
|
abstract suspend fun flushChapter(chapter: MangaChapter): Boolean
|
||||||
|
|
||||||
|
|||||||
@@ -52,9 +52,9 @@ class LocalMangaZipOutput(
|
|||||||
index.setCoverEntry(name)
|
index.setCoverEntry(name)
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun addPage(chapter: MangaChapter, file: File, pageNumber: Int, ext: String) = mutex.withLock {
|
override suspend fun addPage(chapter: IndexedValue<MangaChapter>, file: File, pageNumber: Int, ext: String) = mutex.withLock {
|
||||||
val name = buildString {
|
val name = buildString {
|
||||||
append(FILENAME_PATTERN.format(chapter.branch.hashCode(), chapter.number, pageNumber))
|
append(FILENAME_PATTERN.format(chapter.value.branch.hashCode(), chapter.index + 1, pageNumber))
|
||||||
if (ext.isNotEmpty() && ext.length <= 4) {
|
if (ext.isNotEmpty() && ext.length <= 4) {
|
||||||
append('.')
|
append('.')
|
||||||
append(ext)
|
append(ext)
|
||||||
@@ -104,7 +104,7 @@ class LocalMangaZipOutput(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
otherIndex?.getMangaInfo()?.chapters?.let { chapters ->
|
otherIndex?.getMangaInfo()?.chapters?.withIndex()?.let { chapters ->
|
||||||
for (chapter in chapters) {
|
for (chapter in chapters) {
|
||||||
index.addChapter(chapter, null)
|
index.addChapter(chapter, null)
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user