Skip to content

Commit 2195c3d

Browse files
authored
Downloader Rewrite (#437)
* Downloader rewrite - Rewrite downloader to use coroutines instead of a thread - Remove unused Page functions - Add page progress - Add ProgressResponseBody - Add support for canceling a download in the middle of downloading - Fix clear download queue * Minor fix * Minor improvements - notifyAllClients now launches in another thread and only sends new data every second - Better handling of download queue checker in step() - Minor improvements and fixes * Reorder downloads * Download in parallel by source * Remove TODO
1 parent 119b9db commit 2195c3d

File tree

12 files changed

+273
-115
lines changed

12 files changed

+273
-115
lines changed

server/src/main/kotlin/eu/kanade/tachiyomi/network/OkHttpExtensions.kt

+7-7
Original file line numberDiff line numberDiff line change
@@ -116,13 +116,13 @@ fun Call.asObservableSuccess(): Observable<Response> {
116116
@Suppress("UNUSED_PARAMETER")
117117
fun OkHttpClient.newCallWithProgress(request: Request, listener: ProgressListener): Call {
118118
val progressClient = newBuilder()
119-
// .cache(null)
120-
// .addNetworkInterceptor { chain ->
121-
// val originalResponse = chain.proceed(chain.request())
122-
// originalResponse.newBuilder()
123-
// .body(ProgressResponseBody(originalResponse.body!!, listener))
124-
// .build()
125-
// }
119+
.cache(null)
120+
.addNetworkInterceptor { chain ->
121+
val originalResponse = chain.proceed(chain.request())
122+
originalResponse.newBuilder()
123+
.body(ProgressResponseBody(originalResponse.body!!, listener))
124+
.build()
125+
}
126126
.build()
127127

128128
return progressClient.newCall(request)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
package eu.kanade.tachiyomi.network
2+
3+
import okhttp3.MediaType
4+
import okhttp3.ResponseBody
5+
import okio.Buffer
6+
import okio.BufferedSource
7+
import okio.ForwardingSource
8+
import okio.Source
9+
import okio.buffer
10+
import java.io.IOException
11+
12+
class ProgressResponseBody(private val responseBody: ResponseBody, private val progressListener: ProgressListener) : ResponseBody() {
13+
14+
private val bufferedSource: BufferedSource by lazy {
15+
source(responseBody.source()).buffer()
16+
}
17+
18+
override fun contentType(): MediaType? {
19+
return responseBody.contentType()
20+
}
21+
22+
override fun contentLength(): Long {
23+
return responseBody.contentLength()
24+
}
25+
26+
override fun source(): BufferedSource {
27+
return bufferedSource
28+
}
29+
30+
private fun source(source: Source): Source {
31+
return object : ForwardingSource(source) {
32+
var totalBytesRead = 0L
33+
34+
@Throws(IOException::class)
35+
override fun read(sink: Buffer, byteCount: Long): Long {
36+
val bytesRead = super.read(sink, byteCount)
37+
// read() returns the number of bytes read, or -1 if this source is exhausted.
38+
totalBytesRead += if (bytesRead != -1L) bytesRead else 0
39+
progressListener.update(totalBytesRead, responseBody.contentLength(), bytesRead == -1L)
40+
return bytesRead
41+
}
42+
}
43+
}
44+
}

server/src/main/kotlin/eu/kanade/tachiyomi/source/local/loader/EpubPageLoader.kt

-2
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
package eu.kanade.tachiyomi.source.local.loader
22

3-
import eu.kanade.tachiyomi.source.model.Page
43
import eu.kanade.tachiyomi.util.storage.EpubFile
54
import java.io.File
65

@@ -24,7 +23,6 @@ class EpubPageLoader(file: File) : PageLoader {
2423
val streamFn = { epub.getInputStream(epub.getEntry(path)!!) }
2524
ReaderPage(i).apply {
2625
stream = streamFn
27-
status = Page.READY
2826
}
2927
}
3028
}

server/src/main/kotlin/eu/kanade/tachiyomi/source/local/loader/RarPageLoader.kt

-3
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@ package eu.kanade.tachiyomi.source.local.loader
22

33
import com.github.junrar.Archive
44
import com.github.junrar.rarfile.FileHeader
5-
import eu.kanade.tachiyomi.source.model.Page
65
import eu.kanade.tachiyomi.util.lang.compareToCaseInsensitiveNaturalOrder
76
import suwayomi.tachidesk.manga.impl.util.storage.ImageUtil
87
import java.io.ByteArrayInputStream
@@ -46,7 +45,6 @@ class RarPageLoader(file: File) : PageLoader {
4645

4746
ReaderPage(i).apply {
4847
stream = streamFn
49-
status = Page.READY
5048
}
5149
}
5250
}
@@ -58,7 +56,6 @@ class RarPageLoader(file: File) : PageLoader {
5856

5957
ReaderPage(i).apply {
6058
stream = streamFn
61-
status = Page.READY
6259
}
6360
}
6461
}

server/src/main/kotlin/eu/kanade/tachiyomi/source/local/loader/ZipPageLoader.kt

-2
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
package eu.kanade.tachiyomi.source.local.loader
22

3-
import eu.kanade.tachiyomi.source.model.Page
43
import eu.kanade.tachiyomi.util.lang.compareToCaseInsensitiveNaturalOrder
54
import suwayomi.tachidesk.manga.impl.util.storage.ImageUtil
65
import java.io.File
@@ -24,7 +23,6 @@ class ZipPageLoader(file: File) : PageLoader {
2423
val streamFn = { zip.getInputStream(entry) }
2524
ReaderPage(i).apply {
2625
stream = streamFn
27-
status = Page.READY
2826
}
2927
}
3028
}

server/src/main/kotlin/eu/kanade/tachiyomi/source/model/Page.kt

+5-35
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,8 @@ package eu.kanade.tachiyomi.source.model
22

33
import android.net.Uri
44
import eu.kanade.tachiyomi.network.ProgressListener
5-
import rx.subjects.Subject
5+
import kotlinx.coroutines.flow.MutableStateFlow
6+
import kotlinx.coroutines.flow.asStateFlow
67

78
open class Page(
89
val index: Int,
@@ -11,48 +12,17 @@ open class Page(
1112
@Transient var uri: Uri? = null // Deprecated but can't be deleted due to extensions
1213
) : ProgressListener {
1314

14-
val number: Int
15-
get() = index + 1
16-
17-
@Transient
18-
@Volatile
19-
var status: Int = 0
20-
set(value) {
21-
field = value
22-
statusSubject?.onNext(value)
23-
statusCallback?.invoke(this)
24-
}
25-
26-
@Transient
27-
@Volatile
28-
var progress: Int = 0
29-
set(value) {
30-
field = value
31-
statusCallback?.invoke(this)
32-
}
33-
34-
@Transient
35-
private var statusSubject: Subject<Int, Int>? = null
36-
37-
@Transient
38-
private var statusCallback: ((Page) -> Unit)? = null
15+
private val _progress = MutableStateFlow(0)
16+
val progress = _progress.asStateFlow()
3917

4018
override fun update(bytesRead: Long, contentLength: Long, done: Boolean) {
41-
progress = if (contentLength > 0) {
19+
_progress.value = if (contentLength > 0) {
4220
(100 * bytesRead / contentLength).toInt()
4321
} else {
4422
-1
4523
}
4624
}
4725

48-
fun setStatusSubject(subject: Subject<Int, Int>?) {
49-
this.statusSubject = subject
50-
}
51-
52-
fun setStatusCallback(f: ((Page) -> Unit)?) {
53-
statusCallback = f
54-
}
55-
5626
companion object {
5727
const val QUEUE = 0
5828
const val LOAD_PAGE = 1

server/src/main/kotlin/eu/kanade/tachiyomi/source/online/HttpSourceFetcher.kt

-2
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,7 @@ import eu.kanade.tachiyomi.source.model.Page
44
import rx.Observable
55

66
fun HttpSource.getImageUrl(page: Page): Observable<Page> {
7-
page.status = Page.LOAD_PAGE
87
return fetchImageUrl(page)
9-
.doOnError { page.status = Page.ERROR }
108
.onErrorReturn { null }
119
.doOnNext { page.imageUrl = it }
1210
.map { page }

server/src/main/kotlin/suwayomi/tachidesk/manga/MangaAPI.kt

+2-1
Original file line numberDiff line numberDiff line change
@@ -106,12 +106,13 @@ object MangaAPI {
106106

107107
get("start", DownloadController.start)
108108
get("stop", DownloadController.stop)
109-
get("clear", DownloadController.stop)
109+
get("clear", DownloadController.clear)
110110
}
111111

112112
path("download") {
113113
get("{mangaId}/chapter/{chapterIndex}", DownloadController.queueChapter)
114114
delete("{mangaId}/chapter/{chapterIndex}", DownloadController.unqueueChapter)
115+
patch("{mangaId}/chapter/{chapterIndex}/reorder/{to}", DownloadController.reorderChapter)
115116
post("batch", DownloadController.queueChapters)
116117
}
117118

server/src/main/kotlin/suwayomi/tachidesk/manga/controller/DownloadController.kt

+26-9
Original file line numberDiff line numberDiff line change
@@ -46,10 +46,8 @@ object DownloadController {
4646
description("Start the downloader")
4747
}
4848
},
49-
behaviorOf = { ctx ->
49+
behaviorOf = {
5050
DownloadManager.start()
51-
52-
ctx.status(200)
5351
},
5452
withResults = {
5553
httpCode(HttpCode.OK)
@@ -65,9 +63,9 @@ object DownloadController {
6563
}
6664
},
6765
behaviorOf = { ctx ->
68-
DownloadManager.stop()
69-
70-
ctx.status(200)
66+
ctx.future(
67+
future { DownloadManager.stop() }
68+
)
7169
},
7270
withResults = {
7371
httpCode(HttpCode.OK)
@@ -83,9 +81,9 @@ object DownloadController {
8381
}
8482
},
8583
behaviorOf = { ctx ->
86-
DownloadManager.clear()
87-
88-
ctx.status(200)
84+
ctx.future(
85+
future { DownloadManager.clear() }
86+
)
8987
},
9088
withResults = {
9189
httpCode(HttpCode.OK)
@@ -155,4 +153,23 @@ object DownloadController {
155153
httpCode(HttpCode.OK)
156154
}
157155
)
156+
157+
/** clear download queue */
158+
val reorderChapter = handler(
159+
pathParam<Int>("chapterIndex"),
160+
pathParam<Int>("mangaId"),
161+
pathParam<Int>("to"),
162+
documentWith = {
163+
withOperation {
164+
summary("Downloader reorder chapter")
165+
description("Reorder chapter in download queue")
166+
}
167+
},
168+
behaviorOf = { _, chapterIndex, mangaId, to ->
169+
DownloadManager.reorder(chapterIndex, mangaId, to)
170+
},
171+
withResults = {
172+
httpCode(HttpCode.OK)
173+
}
174+
)
158175
}

server/src/main/kotlin/suwayomi/tachidesk/manga/impl/Page.kt

+3-1
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ package suwayomi.tachidesk.manga.impl
1010
import eu.kanade.tachiyomi.source.local.LocalSource
1111
import eu.kanade.tachiyomi.source.model.Page
1212
import eu.kanade.tachiyomi.source.online.HttpSource
13+
import kotlinx.coroutines.flow.StateFlow
1314
import org.jetbrains.exposed.sql.and
1415
import org.jetbrains.exposed.sql.select
1516
import org.jetbrains.exposed.sql.transactions.transaction
@@ -37,7 +38,7 @@ object Page {
3738
return page.imageUrl!!
3839
}
3940

40-
suspend fun getPageImage(mangaId: Int, chapterIndex: Int, index: Int, useCache: Boolean = true): Pair<InputStream, String> {
41+
suspend fun getPageImage(mangaId: Int, chapterIndex: Int, index: Int, useCache: Boolean = true, progressFlow: ((StateFlow<Int>) -> Unit)? = null): Pair<InputStream, String> {
4142
val mangaEntry = transaction { MangaTable.select { MangaTable.id eq mangaId }.first() }
4243
val source = getCatalogueSourceOrStub(mangaEntry[MangaTable.sourceReference])
4344
val chapterEntry = transaction {
@@ -55,6 +56,7 @@ object Page {
5556
pageEntry[PageTable.url],
5657
pageEntry[PageTable.imageUrl]
5758
)
59+
progressFlow?.invoke(tachiyomiPage.progress)
5860

5961
// we treat Local source differently
6062
if (source.id == LocalSource.ID) {

0 commit comments

Comments
 (0)