Skip to content

Commit

Permalink
Fix destination detection
Browse files Browse the repository at this point in the history
  • Loading branch information
Koitharu committed Oct 15, 2024
1 parent 10ec4a5 commit f7cccf0
Show file tree
Hide file tree
Showing 4 changed files with 86 additions and 45 deletions.
2 changes: 1 addition & 1 deletion src/main/kotlin/org/koitharu/kotatsu/dl/Main.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down
Original file line number Diff line number Diff line change
@@ -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
Expand Down Expand Up @@ -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 }) {
DownloadFormat.CBZ
} else {
DownloadFormat.DIR
}
var file = if (target.isDirectory || (!target.exists() && targetFormat == DownloadFormat.DIR)) {
if (!target.exists()) {
target.mkdirs()
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")
}
val baseName = manga.title.toFileNameSafe()
when (targetFormat) {
DownloadFormat.CBZ -> File(target, "$baseName.cbz")
DownloadFormat.ZIP -> File(target, "$baseName.zip")
DownloadFormat.DIR -> File(target, baseName)
// 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)
}
} else {
target.parentFile?.run {
if (!exists()) mkdirs()
// 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))
}
target

else -> throw UsageError(
message = "Unable to determine destination file or directory. Please specify it explicitly",
paramName = "--destination"
)
}
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 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
}
}

private fun File.isMangaDir(): Boolean {
return list()?.contains("index.json") == true
}
}
}
12 changes: 7 additions & 5 deletions src/main/kotlin/org/koitharu/kotatsu/dl/util/AppCommand.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -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)
}

Expand Down
20 changes: 19 additions & 1 deletion src/main/kotlin/org/koitharu/kotatsu/dl/util/Util.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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)

Expand All @@ -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
}
}
}

0 comments on commit f7cccf0

Please sign in to comment.