Fix destination detection
This commit is contained in:
@@ -44,7 +44,7 @@ class Main : AppCommand(name = "kotatsu-dl") {
|
||||
private val parallelism: Int by option(
|
||||
names = arrayOf("-j", "--jobs"),
|
||||
help = "Number of parallel jobs for downloading",
|
||||
).int().default(1).check("Jobs count should be between 1 and 10") {
|
||||
).int().default(4).check("Jobs count should be between 1 and 10") {
|
||||
it in 1..10
|
||||
}
|
||||
private val throttle: Boolean by option(
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
package org.koitharu.kotatsu.dl.download
|
||||
|
||||
import com.github.ajalt.clikt.core.UsageError
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.runInterruptible
|
||||
import okio.Closeable
|
||||
import org.koitharu.kotatsu.dl.util.getNextAvailable
|
||||
import org.koitharu.kotatsu.parsers.model.Manga
|
||||
import org.koitharu.kotatsu.parsers.model.MangaChapter
|
||||
import org.koitharu.kotatsu.parsers.util.toFileNameSafe
|
||||
@@ -30,53 +32,72 @@ sealed class LocalMangaOutput(
|
||||
const val SUFFIX_TMP = ".tmp"
|
||||
|
||||
suspend fun create(
|
||||
target: File,
|
||||
destination: File,
|
||||
manga: Manga,
|
||||
format: DownloadFormat?,
|
||||
preferredFormat: DownloadFormat?,
|
||||
): LocalMangaOutput = runInterruptible(Dispatchers.IO) {
|
||||
val targetFormat = format ?: if (manga.chapters.let { it != null && it.size <= 3 }) {
|
||||
when {
|
||||
// option 0 - destination is a existing file/dir and we should write manga into id directly
|
||||
destination.exists() && (destination.isFile || destination.isMangaDir()) -> {
|
||||
TODO("Downloading into existing manga destination is not supported yet")
|
||||
}
|
||||
// option 1 - destination is an existing directory and we should create a nested dir/file for manga
|
||||
destination.exists() && destination.isDirectory -> {
|
||||
val baseName = manga.title.toFileNameSafe()
|
||||
val format = preferredFormat ?: detectFormat(manga)
|
||||
val targetFile = File(
|
||||
destination, when (format) {
|
||||
DownloadFormat.CBZ -> "$baseName.cbz"
|
||||
DownloadFormat.ZIP -> "$baseName.zip"
|
||||
DownloadFormat.DIR -> baseName
|
||||
}
|
||||
)
|
||||
createDirectly(targetFile.getNextAvailable(), manga, format)
|
||||
}
|
||||
// option 2 - destination is a non-existing file/dir and we should write manga into id directly
|
||||
!destination.exists() -> {
|
||||
val parentDir: File? = destination.parentFile
|
||||
parentDir?.mkdirs()
|
||||
createDirectly(destination, manga, preferredFormat ?: detectFormat(destination))
|
||||
}
|
||||
|
||||
else -> throw UsageError(
|
||||
message = "Unable to determine destination file or directory. Please specify it explicitly",
|
||||
paramName = "--destination"
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun createDirectly(destination: File, manga: Manga, format: DownloadFormat) = when (format) {
|
||||
DownloadFormat.CBZ,
|
||||
DownloadFormat.ZIP,
|
||||
-> LocalMangaZipOutput(destination, manga)
|
||||
|
||||
DownloadFormat.DIR -> LocalMangaDirOutput(destination, manga)
|
||||
}
|
||||
|
||||
private fun detectFormat(destination: File): DownloadFormat {
|
||||
return when (destination.extension.lowercase()) {
|
||||
"cbz" -> return DownloadFormat.CBZ
|
||||
"zip" -> return DownloadFormat.ZIP
|
||||
"" -> return DownloadFormat.DIR
|
||||
else -> throw UsageError(
|
||||
message = "Unable to determine output format. Please specify it explicitly",
|
||||
paramName = "--format"
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun detectFormat(manga: Manga): DownloadFormat {
|
||||
return if (manga.chapters.let { it != null && it.size <= 5 }) {
|
||||
DownloadFormat.CBZ
|
||||
} else {
|
||||
DownloadFormat.DIR
|
||||
}
|
||||
var file = if (target.isDirectory || (!target.exists() && targetFormat == DownloadFormat.DIR)) {
|
||||
if (!target.exists()) {
|
||||
target.mkdirs()
|
||||
}
|
||||
val baseName = manga.title.toFileNameSafe()
|
||||
when (targetFormat) {
|
||||
DownloadFormat.CBZ -> File(target, "$baseName.cbz")
|
||||
DownloadFormat.ZIP -> File(target, "$baseName.zip")
|
||||
DownloadFormat.DIR -> File(target, baseName)
|
||||
}
|
||||
} else {
|
||||
target.parentFile?.run {
|
||||
if (!exists()) mkdirs()
|
||||
}
|
||||
target
|
||||
}
|
||||
getNextAvailable(file, manga)
|
||||
}
|
||||
|
||||
private fun getNextAvailable(
|
||||
file: File,
|
||||
manga: Manga,
|
||||
): LocalMangaOutput {
|
||||
var i = 0
|
||||
val baseName = file.nameWithoutExtension
|
||||
val ext = file.extension.let { if (it.isNotEmpty()) ".$it" else "" }
|
||||
while (true) {
|
||||
val fileName = (if (i == 0) baseName else baseName + "_$i") + ext
|
||||
val target = File(file.parentFile, fileName)
|
||||
if (target.exists()) {
|
||||
i++
|
||||
} else {
|
||||
return when {
|
||||
target.isDirectory -> LocalMangaDirOutput(target, manga)
|
||||
else -> LocalMangaZipOutput(target, manga)
|
||||
}
|
||||
}
|
||||
}
|
||||
private fun File.isMangaDir(): Boolean {
|
||||
return list()?.contains("index.json") == true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,10 +2,10 @@ package org.koitharu.kotatsu.dl.util
|
||||
|
||||
import com.github.ajalt.clikt.command.CoreSuspendingCliktCommand
|
||||
import com.github.ajalt.clikt.core.FileNotFound
|
||||
import com.github.ajalt.clikt.core.PrintMessage
|
||||
import com.github.ajalt.clikt.core.ProgramResult
|
||||
import com.github.ajalt.clikt.core.context
|
||||
import okio.IOException
|
||||
import org.koitharu.kotatsu.parsers.util.runCatchingCancellable
|
||||
import kotlin.io.path.Path
|
||||
import kotlin.io.path.readText
|
||||
|
||||
@@ -36,11 +36,13 @@ abstract class AppCommand(name: String) : CoreSuspendingCliktCommand(name) {
|
||||
}
|
||||
|
||||
final override suspend fun run() {
|
||||
val exitCode = runCatchingCancellable {
|
||||
val exitCode = try {
|
||||
invoke()
|
||||
}.onFailure { e ->
|
||||
e.printStackTrace()
|
||||
}.getOrDefault(1)
|
||||
} catch (e: IllegalStateException) {
|
||||
throw PrintMessage(e.message.ifNullOrEmpty { GENERIC_ERROR_MSG }, 2, true)
|
||||
} catch (e: NotImplementedError) {
|
||||
throw PrintMessage(e.message.ifNullOrEmpty { GENERIC_ERROR_MSG }, 2, true)
|
||||
}
|
||||
throw ProgramResult(exitCode)
|
||||
}
|
||||
|
||||
|
||||
@@ -5,8 +5,11 @@ import okhttp3.HttpUrl.Companion.toHttpUrlOrNull
|
||||
import okhttp3.Response
|
||||
import okhttp3.internal.closeQuietly
|
||||
import org.jsoup.HttpStatusException
|
||||
import java.io.File
|
||||
import java.net.HttpURLConnection
|
||||
|
||||
const val GENERIC_ERROR_MSG = "An error has occured"
|
||||
|
||||
@Suppress("NOTHING_TO_INLINE")
|
||||
inline operator fun <T> List<T>.component6(): T = get(5)
|
||||
|
||||
@@ -32,4 +35,19 @@ fun IntList.sum(): Int {
|
||||
var result = 0
|
||||
forEach { value -> result += value }
|
||||
return result
|
||||
}
|
||||
}
|
||||
|
||||
fun File.getNextAvailable(): File {
|
||||
var i = 0
|
||||
val baseName = nameWithoutExtension
|
||||
val ext = extension.let { if (it.isNotEmpty()) ".$it" else "" }
|
||||
while (true) {
|
||||
val fileName = (if (i == 0) baseName else baseName + "_$i") + ext
|
||||
val target = File(this.parentFile, fileName)
|
||||
if (target.exists()) {
|
||||
i++
|
||||
} else {
|
||||
return target
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user