|
|
|
@ -1,6 +1,11 @@
|
|
|
|
package org.koitharu.kotatsu.local.data.output
|
|
|
|
package org.koitharu.kotatsu.local.data.output
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
import kotlinx.coroutines.Dispatchers
|
|
|
|
|
|
|
|
import kotlinx.coroutines.sync.Mutex
|
|
|
|
|
|
|
|
import kotlinx.coroutines.sync.withLock
|
|
|
|
|
|
|
|
import kotlinx.coroutines.withContext
|
|
|
|
import okio.Closeable
|
|
|
|
import okio.Closeable
|
|
|
|
|
|
|
|
import org.koitharu.kotatsu.local.data.input.LocalMangaInput
|
|
|
|
import org.koitharu.kotatsu.parsers.model.Manga
|
|
|
|
import org.koitharu.kotatsu.parsers.model.Manga
|
|
|
|
import org.koitharu.kotatsu.parsers.model.MangaChapter
|
|
|
|
import org.koitharu.kotatsu.parsers.model.MangaChapter
|
|
|
|
import org.koitharu.kotatsu.parsers.util.toFileNameSafe
|
|
|
|
import org.koitharu.kotatsu.parsers.util.toFileNameSafe
|
|
|
|
@ -26,25 +31,63 @@ sealed class LocalMangaOutput(
|
|
|
|
|
|
|
|
|
|
|
|
const val ENTRY_NAME_INDEX = "index.json"
|
|
|
|
const val ENTRY_NAME_INDEX = "index.json"
|
|
|
|
const val SUFFIX_TMP = ".tmp"
|
|
|
|
const val SUFFIX_TMP = ".tmp"
|
|
|
|
|
|
|
|
private val mutex = Mutex()
|
|
|
|
|
|
|
|
|
|
|
|
fun getOrCreate(root: File, manga: Manga): LocalMangaOutput {
|
|
|
|
suspend fun getOrCreate(root: File, manga: Manga): LocalMangaOutput = withContext(Dispatchers.IO) {
|
|
|
|
return checkNotNull(getImpl(root, manga, onlyIfExists = false))
|
|
|
|
val preferSingleCbz = manga.chapters.let {
|
|
|
|
|
|
|
|
it != null && it.size <= 3
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
checkNotNull(getImpl(root, manga, onlyIfExists = false, preferSingleCbz))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
fun get(root: File, manga: Manga): LocalMangaOutput? {
|
|
|
|
suspend fun get(root: File, manga: Manga): LocalMangaOutput? = withContext(Dispatchers.IO) {
|
|
|
|
return getImpl(root, manga, onlyIfExists = true)
|
|
|
|
getImpl(root, manga, onlyIfExists = true, preferSingleCbz = false)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private fun getImpl(root: File, manga: Manga, onlyIfExists: Boolean): LocalMangaOutput? {
|
|
|
|
private suspend fun getImpl(
|
|
|
|
val fileName = manga.title.toFileNameSafe()
|
|
|
|
root: File,
|
|
|
|
val dir = File(root, fileName)
|
|
|
|
manga: Manga,
|
|
|
|
val zip = File(root, "$fileName.cbz")
|
|
|
|
onlyIfExists: Boolean,
|
|
|
|
return when {
|
|
|
|
preferSingleCbz: Boolean,
|
|
|
|
dir.isDirectory -> LocalMangaDirOutput(dir, manga)
|
|
|
|
): LocalMangaOutput? {
|
|
|
|
zip.isFile -> LocalMangaZipOutput(zip, manga)
|
|
|
|
mutex.withLock {
|
|
|
|
!onlyIfExists -> LocalMangaDirOutput(dir, manga)
|
|
|
|
var i = 0
|
|
|
|
else -> null
|
|
|
|
val baseName = manga.title.toFileNameSafe()
|
|
|
|
|
|
|
|
while (true) {
|
|
|
|
|
|
|
|
val fileName = if (i == 0) baseName else baseName + "_$i"
|
|
|
|
|
|
|
|
val dir = File(root, fileName)
|
|
|
|
|
|
|
|
val zip = File(root, "$fileName.cbz")
|
|
|
|
|
|
|
|
i++
|
|
|
|
|
|
|
|
return when {
|
|
|
|
|
|
|
|
dir.isDirectory -> {
|
|
|
|
|
|
|
|
if (canWriteTo(dir, manga)) {
|
|
|
|
|
|
|
|
LocalMangaDirOutput(dir, manga)
|
|
|
|
|
|
|
|
} else {
|
|
|
|
|
|
|
|
continue
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
zip.isFile -> if (canWriteTo(zip, manga)) {
|
|
|
|
|
|
|
|
LocalMangaZipOutput(zip, manga)
|
|
|
|
|
|
|
|
} else {
|
|
|
|
|
|
|
|
continue
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
!onlyIfExists -> if (preferSingleCbz) {
|
|
|
|
|
|
|
|
LocalMangaZipOutput(zip, manga)
|
|
|
|
|
|
|
|
} else {
|
|
|
|
|
|
|
|
LocalMangaDirOutput(dir, manga)
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
else -> null
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
private suspend fun canWriteTo(file: File, manga: Manga): Boolean {
|
|
|
|
|
|
|
|
val info = LocalMangaInput.of(file).getMangaInfo() ?: return false
|
|
|
|
|
|
|
|
return info.id == manga.id
|
|
|
|
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|