Parallel downloading

This commit is contained in:
Koitharu
2024-10-15 13:02:55 +03:00
parent 0c67a1cc79
commit 10ec4a5fe7
3 changed files with 44 additions and 27 deletions

View File

@@ -3,12 +3,12 @@ package org.koitharu.kotatsu.dl
import com.github.ajalt.clikt.command.main import com.github.ajalt.clikt.command.main
import com.github.ajalt.clikt.core.ProgramResult import com.github.ajalt.clikt.core.ProgramResult
import com.github.ajalt.clikt.parameters.arguments.argument import com.github.ajalt.clikt.parameters.arguments.argument
import com.github.ajalt.clikt.parameters.options.convert import com.github.ajalt.clikt.parameters.options.*
import com.github.ajalt.clikt.parameters.options.flag
import com.github.ajalt.clikt.parameters.options.option
import com.github.ajalt.clikt.parameters.options.validate
import com.github.ajalt.clikt.parameters.types.enum import com.github.ajalt.clikt.parameters.types.enum
import com.github.ajalt.clikt.parameters.types.file import com.github.ajalt.clikt.parameters.types.file
import com.github.ajalt.clikt.parameters.types.int
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import org.koitharu.kotatsu.dl.download.DownloadFormat import org.koitharu.kotatsu.dl.download.DownloadFormat
import org.koitharu.kotatsu.dl.download.MangaDownloader import org.koitharu.kotatsu.dl.download.MangaDownloader
import org.koitharu.kotatsu.dl.parsers.MangaLoaderContextImpl import org.koitharu.kotatsu.dl.parsers.MangaLoaderContextImpl
@@ -41,6 +41,12 @@ class Main : AppCommand(name = "kotatsu-dl") {
ignoreCase = true, ignoreCase = true,
key = { it.name.lowercase() }, key = { it.name.lowercase() },
) )
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") {
it in 1..10
}
private val throttle: Boolean by option( private val throttle: Boolean by option(
names = arrayOf("--throttle"), names = arrayOf("--throttle"),
help = "Slow down downloading to avoid blocking your IP address by server", help = "Slow down downloading to avoid blocking your IP address by server",
@@ -60,7 +66,7 @@ class Main : AppCommand(name = "kotatsu-dl") {
override suspend fun invoke(): Int { override suspend fun invoke(): Int {
val context = MangaLoaderContextImpl() val context = MangaLoaderContextImpl()
val linkResolver = context.newLinkResolver(link) val linkResolver = context.newLinkResolver(link)
print("Resolving link...") print("Resolving link")
val source = linkResolver.getSource() val source = linkResolver.getSource()
if (source == null) { if (source == null) {
println() println()
@@ -112,8 +118,11 @@ class Main : AppCommand(name = "kotatsu-dl") {
format = format, format = format,
throttle = throttle, throttle = throttle,
verbose = verbose, verbose = verbose,
parallelism = parallelism,
) )
val file = downloader.download() val file = withContext(Dispatchers.Default) {
downloader.download()
}
colored { colored {
print("Done.".green.bold) print("Done.".green.bold)
print(" Saved to ") print(" Saved to ")

View File

@@ -1,9 +1,9 @@
package org.koitharu.kotatsu.dl.download package org.koitharu.kotatsu.dl.download
import androidx.collection.MutableIntList import androidx.collection.MutableIntList
import kotlinx.coroutines.NonCancellable import kotlinx.coroutines.*
import kotlinx.coroutines.delay import kotlinx.coroutines.sync.Semaphore
import kotlinx.coroutines.withContext import kotlinx.coroutines.sync.withPermit
import me.tongfei.progressbar.ProgressBar import me.tongfei.progressbar.ProgressBar
import me.tongfei.progressbar.ProgressBarBuilder import me.tongfei.progressbar.ProgressBarBuilder
import me.tongfei.progressbar.ProgressBarStyle import me.tongfei.progressbar.ProgressBarStyle
@@ -35,12 +35,13 @@ class MangaDownloader(
private val format: DownloadFormat?, private val format: DownloadFormat?,
private val throttle: Boolean, private val throttle: Boolean,
private val verbose: Boolean, private val verbose: Boolean,
private val parallelism: Int,
) { ) {
private val progressBarStyle = ProgressBarStyle.builder() private val progressBarStyle = ProgressBarStyle.builder()
.rightBracket("]") .rightBracket("]")
.leftBracket("[") .leftBracket("[")
.colorCode(ColoredConsole.BRIGHT_YELLOW.toByte()) .colorCode(if (ColoredConsole.isSupported()) ColoredConsole.BRIGHT_YELLOW.toByte() else 0)
.block('#') .block('#')
.build() .build()
@@ -57,7 +58,7 @@ class MangaDownloader(
.setTaskName("Downloading") .setTaskName("Downloading")
.clearDisplayOnFinish() .clearDisplayOnFinish()
.build() .build()
progressBar.setExtraMessage("Preparing...") progressBar.setExtraMessage("Preparing")
val tempDir = Files.createTempDirectory("kdl_").toFile() val tempDir = Files.createTempDirectory("kdl_").toFile()
val counters = MutableIntList() val counters = MutableIntList()
val totalChapters = chaptersRange.size(chapters) val totalChapters = chaptersRange.size(chapters)
@@ -70,6 +71,7 @@ class MangaDownloader(
file.delete() file.delete()
} }
} }
val semaphore = Semaphore(parallelism)
for (chapter in chapters.withIndex()) { for (chapter in chapters.withIndex()) {
progressBar.setExtraMessage(chapter.value.name) progressBar.setExtraMessage(chapter.value.name)
if (chapter.index !in chaptersRange) { if (chapter.index !in chaptersRange) {
@@ -78,25 +80,31 @@ class MangaDownloader(
val pages = runFailsafe(progressBar) { parser.getPages(chapter.value) } val pages = runFailsafe(progressBar) { parser.getPages(chapter.value) }
counters.add(pages.size) counters.add(pages.size)
progressBar.maxHint(counters.sum().toLong() + (totalChapters - counters.size) * pages.size) progressBar.maxHint(counters.sum().toLong() + (totalChapters - counters.size) * pages.size)
for ((pageIndex, page) in pages.withIndex()) { coroutineScope {
runFailsafe(progressBar) { pages.mapIndexed { pageIndex, page ->
val url = parser.getPageUrl(page) launch {
val file = downloadFile(url, tempDir, parser.source) semaphore.withPermit {
output.addPage( runFailsafe(progressBar) {
chapter = chapter, val url = parser.getPageUrl(page)
file = file, val file = downloadFile(url, tempDir, parser.source)
pageNumber = pageIndex, output.addPage(
ext = getFileExtensionFromUrl(url).orEmpty(), chapter = chapter,
) file = file,
progressBar.step() pageNumber = pageIndex,
if (file.extension == "tmp") { ext = getFileExtensionFromUrl(url).orEmpty(),
file.delete() )
progressBar.step()
if (file.extension == "tmp") {
file.delete()
}
}
}
} }
} }.joinAll()
} }
output.flushChapter(chapter.value) output.flushChapter(chapter.value)
} }
progressBar.setExtraMessage("Finalizing...") progressBar.setExtraMessage("Finalizing")
output.mergeWithExisting() output.mergeWithExisting()
output.finish() output.finish()
progressBar.close() progressBar.close()

View File

@@ -39,7 +39,7 @@ abstract class AppCommand(name: String) : CoreSuspendingCliktCommand(name) {
val exitCode = runCatchingCancellable { val exitCode = runCatchingCancellable {
invoke() invoke()
}.onFailure { e -> }.onFailure { e ->
System.err.println(e.message) e.printStackTrace()
}.getOrDefault(1) }.getOrDefault(1)
throw ProgramResult(exitCode) throw ProgramResult(exitCode)
} }