Switch to a new Ktlint Formatter (#705)

* Switch to new Ktlint plugin

* Add ktlintCheck to PR builds

* Run formatter

* Put ktlint version in libs toml

* Fix lint

* Use Zip4Java from libs.toml
This commit is contained in:
Mitchell Syer
2023-10-06 23:38:39 -04:00
committed by GitHub
parent 3cd3cb0186
commit 849acfca3d
277 changed files with 6709 additions and 5090 deletions

View File

@@ -57,16 +57,16 @@ object DownloadManager {
private val downloadQueue = CopyOnWriteArrayList<DownloadChapter>()
private val downloaders = ConcurrentHashMap<String, Downloader>()
private const val downloadQueueKey = "downloadQueueKey"
private const val DOWNLOAD_QUEUE_KEY = "downloadQueueKey"
private val sharedPreferences =
Injekt.get<Application>().getSharedPreferences(DownloadManager::class.jvmName, Context.MODE_PRIVATE)
private fun loadDownloadQueue(): List<Int> {
return sharedPreferences.getStringSet(downloadQueueKey, emptySet())?.mapNotNull { it.toInt() } ?: emptyList()
return sharedPreferences.getStringSet(DOWNLOAD_QUEUE_KEY, emptySet())?.mapNotNull { it.toInt() } ?: emptyList()
}
private fun saveDownloadQueue() {
sharedPreferences.edit().putStringSet(downloadQueueKey, downloadQueue.map { it.chapter.id.toString() }.toSet())
sharedPreferences.edit().putStringSet(DOWNLOAD_QUEUE_KEY, downloadQueue.map { it.chapter.id.toString() }.toSet())
.apply()
}
@@ -95,30 +95,32 @@ object DownloadManager {
fun notifyClient(ctx: WsContext) {
ctx.send(
getStatus()
getStatus(),
)
}
fun handleRequest(ctx: WsMessageContext) {
when (ctx.message()) {
"STATUS" -> notifyClient(ctx)
else -> ctx.send(
"""
else ->
ctx.send(
"""
|Invalid command.
|Supported commands are:
| - STATUS
| sends the current download status
|
""".trimMargin()
)
""".trimMargin(),
)
}
}
private val notifyFlow = MutableSharedFlow<Unit>(extraBufferCapacity = 1, onBufferOverflow = BufferOverflow.DROP_OLDEST)
val status = notifyFlow.sample(1.seconds)
.onStart { emit(Unit) }
.map { getStatus() }
val status =
notifyFlow.sample(1.seconds)
.onStart { emit(Unit) }
.map { getStatus() }
init {
scope.launch {
@@ -129,6 +131,7 @@ object DownloadManager {
}
private val saveQueueFlow = MutableSharedFlow<Unit>(extraBufferCapacity = 1, onBufferOverflow = BufferOverflow.DROP_OLDEST)
init {
saveQueueFlow.onEach { saveDownloadQueue() }.launchIn(scope)
}
@@ -159,11 +162,12 @@ object DownloadManager {
} else {
Status.Started
},
downloadQueue.toList()
downloadQueue.toList(),
)
}
private val downloaderWatch = MutableSharedFlow<Unit>(extraBufferCapacity = 1, onBufferOverflow = BufferOverflow.DROP_OLDEST)
init {
serverConfig.subscribeTo(serverConfig.maxSourcesInParallel, { maxSourcesInParallel ->
val runningDownloaders = downloaders.values.filter { it.isActive }
@@ -186,14 +190,18 @@ object DownloadManager {
val runningDownloaders = downloaders.values.filter { it.isActive }
val availableDownloads = downloadQueue.filter { it.state != Error }
logger.info { "Running: ${runningDownloaders.size}, Queued: ${availableDownloads.size}, Failed: ${downloadQueue.size - availableDownloads.size}" }
logger.info {
"Running: ${runningDownloaders.size}, " +
"Queued: ${availableDownloads.size}, " +
"Failed: ${downloadQueue.size - availableDownloads.size}"
}
if (runningDownloaders.size < serverConfig.maxSourcesInParallel.value) {
availableDownloads.asSequence()
.map { it.manga.sourceId }
.distinct()
.minus(
runningDownloaders.map { it.sourceId }.toSet()
runningDownloaders.map { it.sourceId }.toSet(),
)
.take(serverConfig.maxSourcesInParallel.value - runningDownloaders.size)
.map { getDownloader(it) }
@@ -212,24 +220,29 @@ object DownloadManager {
}
}
private fun getDownloader(sourceId: String) = downloaders.getOrPut(sourceId) {
Downloader(
scope = scope,
sourceId = sourceId,
downloadQueue = downloadQueue,
notifier = ::notifyAllClients,
onComplete = ::refreshDownloaders,
onDownloadFinished = ::triggerSaveDownloadQueue
)
}
fun enqueueWithChapterIndex(mangaId: Int, chapterIndex: Int) {
val chapter = transaction {
ChapterTable
.slice(ChapterTable.id)
.select { ChapterTable.manga.eq(mangaId) and ChapterTable.sourceOrder.eq(chapterIndex) }
.first()
private fun getDownloader(sourceId: String) =
downloaders.getOrPut(sourceId) {
Downloader(
scope = scope,
sourceId = sourceId,
downloadQueue = downloadQueue,
notifier = ::notifyAllClients,
onComplete = ::refreshDownloaders,
onDownloadFinished = ::triggerSaveDownloadQueue,
)
}
fun enqueueWithChapterIndex(
mangaId: Int,
chapterIndex: Int,
) {
val chapter =
transaction {
ChapterTable
.slice(ChapterTable.id)
.select { ChapterTable.manga.eq(mangaId) and ChapterTable.sourceOrder.eq(chapterIndex) }
.first()
}
enqueue(EnqueueInput(chapterIds = listOf(chapter[ChapterTable.id].value)))
}
@@ -237,35 +250,38 @@ object DownloadManager {
// Input might have additional formats in the future, such as "All for mangaID" or "Unread for categoryID"
// Having this input format is just future-proofing
data class EnqueueInput(
val chapterIds: List<Int>?
val chapterIds: List<Int>?,
)
fun enqueue(input: EnqueueInput) {
if (input.chapterIds.isNullOrEmpty()) return
val chapters = transaction {
(ChapterTable innerJoin MangaTable)
.select { ChapterTable.id inList input.chapterIds }
.orderBy(ChapterTable.manga)
.orderBy(ChapterTable.sourceOrder)
.toList()
}
val mangas = transaction {
chapters.distinctBy { chapter -> chapter[MangaTable.id] }
.map { MangaTable.toDataClass(it) }
.associateBy { it.id }
}
val inputPairs = transaction {
chapters.map {
Pair(
// this should be safe because mangas is created above from chapters
mangas[it[ChapterTable.manga].value]!!,
ChapterTable.toDataClass(it)
)
val chapters =
transaction {
(ChapterTable innerJoin MangaTable)
.select { ChapterTable.id inList input.chapterIds }
.orderBy(ChapterTable.manga)
.orderBy(ChapterTable.sourceOrder)
.toList()
}
val mangas =
transaction {
chapters.distinctBy { chapter -> chapter[MangaTable.id] }
.map { MangaTable.toDataClass(it) }
.associateBy { it.id }
}
val inputPairs =
transaction {
chapters.map {
Pair(
// this should be safe because mangas is created above from chapters
mangas[it[ChapterTable.manga].value]!!,
ChapterTable.toDataClass(it),
)
}
}
}
addMultipleToQueue(inputPairs)
}
@@ -289,17 +305,21 @@ object DownloadManager {
* Tries to add chapter to queue.
* If chapter is added, returns the created DownloadChapter, otherwise returns null
*/
private fun addToQueue(manga: MangaDataClass, chapter: ChapterDataClass): DownloadChapter? {
private fun addToQueue(
manga: MangaDataClass,
chapter: ChapterDataClass,
): DownloadChapter? {
val downloadChapter = downloadQueue.firstOrNull { it.mangaId == manga.id && it.chapterIndex == chapter.index }
val addToQueue = downloadChapter == null
if (addToQueue) {
val newDownloadChapter = DownloadChapter(
chapter.index,
manga.id,
chapter,
manga
)
val newDownloadChapter =
DownloadChapter(
chapter.index,
manga.id,
chapter,
manga,
)
downloadQueue.add(newDownloadChapter)
triggerSaveDownloadQueue()
logger.debug { "Added chapter ${chapter.id} to download queue ($newDownloadChapter)" }
@@ -325,11 +345,17 @@ object DownloadManager {
dequeue(downloadQueue.filter { it.chapter.id in input.chapterIds }.toSet())
}
fun dequeue(chapterIndex: Int, mangaId: Int) {
fun dequeue(
chapterIndex: Int,
mangaId: Int,
) {
dequeue(downloadQueue.filter { it.mangaId == mangaId && it.chapterIndex == chapterIndex }.toSet())
}
fun dequeue(mangaIds: List<Int>, chaptersToIgnore: List<Int> = emptyList()) {
fun dequeue(
mangaIds: List<Int>,
chaptersToIgnore: List<Int> = emptyList(),
) {
dequeue(downloadQueue.filter { it.mangaId in mangaIds && it.chapter.id !in chaptersToIgnore }.toSet())
}
@@ -342,21 +368,33 @@ object DownloadManager {
notifyAllClients()
}
fun reorder(chapterIndex: Int, mangaId: Int, to: Int) {
val download = downloadQueue.find { it.mangaId == mangaId && it.chapterIndex == chapterIndex }
?: return
fun reorder(
chapterIndex: Int,
mangaId: Int,
to: Int,
) {
val download =
downloadQueue.find { it.mangaId == mangaId && it.chapterIndex == chapterIndex }
?: return
reorder(download, to)
}
fun reorder(chapterId: Int, to: Int) {
val download = downloadQueue.find { it.chapter.id == chapterId }
?: return
fun reorder(
chapterId: Int,
to: Int,
) {
val download =
downloadQueue.find { it.chapter.id == chapterId }
?: return
reorder(download, to)
}
private fun reorder(download: DownloadChapter, to: Int) {
private fun reorder(
download: DownloadChapter,
to: Int,
) {
require(to >= 0) { "'to' must be over or equal to 0" }
logger.debug { "reorder download $download from ${downloadQueue.indexOf(download)} to $to" }
@@ -400,5 +438,5 @@ object DownloadManager {
enum class DownloaderState(val state: Int) {
Stopped(0),
Running(1),
Paused(2)
Paused(2),
}

View File

@@ -36,7 +36,7 @@ class Downloader(
private val downloadQueue: CopyOnWriteArrayList<DownloadChapter>,
private val notifier: (immediate: Boolean) -> Unit,
private val onComplete: () -> Unit,
private val onDownloadFinished: () -> Unit
private val onDownloadFinished: () -> Unit,
) {
private val logger = KotlinLogging.logger("${Downloader::class.java.name} source($sourceId)")
@@ -45,9 +45,13 @@ class Downloader(
get() = downloadQueue.filter { it.manga.sourceId == sourceId }
class StopDownloadException : Exception("Cancelled download")
class PauseDownloadException : Exception("Pause download")
private suspend fun step(download: DownloadChapter?, immediate: Boolean) {
private suspend fun step(
download: DownloadChapter?,
immediate: Boolean,
) {
notifier(immediate)
currentCoroutineContext().ensureActive()
if (download != null && download != availableSourceDownloads.firstOrNull { it.state != Error }) {
@@ -64,16 +68,17 @@ class Downloader(
fun start() {
if (!isActive) {
job = scope.launch {
run()
}.also { job ->
job.invokeOnCompletion {
if (it !is CancellationException) {
logger.debug { "completed" }
onComplete()
job =
scope.launch {
run()
}.also { job ->
job.invokeOnCompletion {
if (it !is CancellationException) {
logger.debug { "completed" }
onComplete()
}
}
}
}
logger.debug { "started" }
notifier(false)
}
@@ -84,7 +89,10 @@ class Downloader(
logger.debug { "stopped" }
}
private suspend fun finishDownload(logger: KLogger, download: DownloadChapter) {
private suspend fun finishDownload(
logger: KLogger,
download: DownloadChapter,
) {
downloadQueue.removeIf { it.mangaId == download.mangaId && it.chapterIndex == download.chapterIndex }
step(null, false)
logger.debug { "finished" }
@@ -93,9 +101,10 @@ class Downloader(
private suspend fun run() {
while (downloadQueue.isNotEmpty() && currentCoroutineContext().isActive) {
val download = availableSourceDownloads.firstOrNull {
(it.state == Queued || it.state == Finished || (it.state == Error && it.tries < 3)) // 3 re-tries per download
} ?: break
val download =
availableSourceDownloads.firstOrNull {
(it.state == Queued || it.state == Finished || (it.state == Error && it.tries < 3)) // 3 re-tries per download
} ?: break
val logContext = "${logger.name} - downloadChapter($download))"
val downloadLogger = KotlinLogging.logger(logContext)
@@ -120,7 +129,9 @@ class Downloader(
ChapterDownloadHelper.download(download.mangaId, download.chapter.id, download, scope, this::step)
download.state = Finished
transaction {
ChapterTable.update({ (ChapterTable.manga eq download.mangaId) and (ChapterTable.sourceOrder eq download.chapterIndex) }) {
ChapterTable.update(
{ (ChapterTable.manga eq download.mangaId) and (ChapterTable.sourceOrder eq download.chapterIndex) },
) {
it[isDownloaded] = true
}
}

View File

@@ -27,7 +27,7 @@ abstract class ChaptersFilesProvider(val mangaId: Int, val chapterId: Int) : Dow
open suspend fun downloadImpl(
download: DownloadChapter,
scope: CoroutineScope,
step: suspend (DownloadChapter?, Boolean) -> Unit
step: suspend (DownloadChapter?, Boolean) -> Unit,
): Boolean {
val pageCount = download.chapter.pageCount
val chapterDir = getChapterCachePath(mangaId, chapterId)
@@ -42,16 +42,17 @@ abstract class ChaptersFilesProvider(val mangaId: Int, val chapterId: Int) : Dow
Page.getPageImage(
mangaId = download.mangaId,
chapterIndex = download.chapterIndex,
index = pageNum
index = pageNum,
) { flow ->
pageProgressJob = flow
.sample(100)
.distinctUntilChanged()
.onEach {
download.progress = (pageNum.toFloat() + (it.toFloat() * 0.01f)) / pageCount
step(null, false) // don't throw on canceled download here since we can't do anything
}
.launchIn(scope)
pageProgressJob =
flow
.sample(100)
.distinctUntilChanged()
.onEach {
download.progress = (pageNum.toFloat() + (it.toFloat() * 0.01f)) / pageCount
step(null, false) // don't throw on canceled download here since we can't do anything
}
.launchIn(scope)
}
} finally {
// always cancel the page progress job even if it throws an exception to avoid memory leaks

View File

@@ -13,7 +13,11 @@ fun interface FileDownload0Args : FileDownload {
}
fun interface FileDownload3Args<A, B, C> : FileDownload {
suspend fun execute(a: A, b: B, c: C): Boolean
suspend fun execute(
a: A,
b: B,
c: C,
): Boolean
override suspend fun executeDownload(vararg args: Any): Boolean {
return execute(args[0] as A, args[1] as B, args[2] as C)

View File

@@ -28,7 +28,7 @@ class ArchiveProvider(mangaId: Int, chapterId: Int) : ChaptersFilesProvider(mang
override suspend fun downloadImpl(
download: DownloadChapter,
scope: CoroutineScope,
step: suspend (DownloadChapter?, Boolean) -> Unit
step: suspend (DownloadChapter?, Boolean) -> Unit,
): Boolean {
val mangaDownloadFolder = File(getMangaDownloadDir(mangaId))
val outputFile = File(getChapterCbzPath(mangaId, chapterId))
@@ -71,7 +71,10 @@ class ArchiveProvider(mangaId: Int, chapterId: Int) : ChaptersFilesProvider(mang
return false
}
private fun handleExistingCbzFile(cbzFile: File, chapterFolder: File) {
private fun handleExistingCbzFile(
cbzFile: File,
chapterFolder: File,
) {
if (!chapterFolder.exists()) chapterFolder.mkdirs()
ZipArchiveInputStream(cbzFile.inputStream()).use { zipInputStream ->
var zipEntry = zipInputStream.nextEntry

View File

@@ -25,7 +25,7 @@ class FolderProvider(mangaId: Int, chapterId: Int) : ChaptersFilesProvider(manga
override suspend fun downloadImpl(
download: DownloadChapter,
scope: CoroutineScope,
step: suspend (DownloadChapter?, Boolean) -> Unit
step: suspend (DownloadChapter?, Boolean) -> Unit,
): Boolean {
val chapterDir = getChapterDownloadPath(mangaId, chapterId)
val folder = File(chapterDir)
@@ -51,10 +51,14 @@ class FolderProvider(mangaId: Int, chapterId: Int) : ChaptersFilesProvider(manga
return File(chapterDir).deleteRecursively()
}
private fun isExistingFile(folder: File, fileName: String): Boolean {
val existingFile = folder.listFiles { file ->
file.isFile && file.name.startsWith(fileName)
}?.firstOrNull()
private fun isExistingFile(
folder: File,
fileName: String,
): Boolean {
val existingFile =
folder.listFiles { file ->
file.isFile && file.name.startsWith(fileName)
}?.firstOrNull()
return existingFile?.exists() == true
}
}

View File

@@ -18,7 +18,7 @@ class DownloadChapter(
var manga: MangaDataClass,
var state: DownloadState = Queued,
var progress: Float = 0f,
var tries: Int = 0
var tries: Int = 0,
) {
override fun toString(): String {
return "${manga.title} ($mangaId) - ${chapter.name} (${chapter.id}) | state= $state, tries= $tries, progress= $progress"

View File

@@ -11,5 +11,5 @@ enum class DownloadState(val state: Int) {
Queued(0),
Downloading(1),
Finished(2),
Error(3)
Error(3),
}

View File

@@ -8,10 +8,11 @@ package suwayomi.tachidesk.manga.impl.download.model
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
enum class Status {
Stopped, Started;
Stopped,
Started,
}
data class DownloadStatus(
val status: Status,
val queue: List<DownloadChapter>
val queue: List<DownloadChapter>,
)