mirror of
https://github.com/Suwayomi/Tachidesk.git
synced 2025-12-10 14:52:05 +01:00
Feature/decouple thumbnail downloads and cache (#581)
* Rename "DownloadedFilesProvider" to "ChaptersFilesProvider" * Move files into sub packages * Further abstract "DownloadedFilesProvider" * Rename "getCachedImageResponse" to "getImageResponse" * Extract getting cached image response into new function * Decouple thumbnail cache and download * Download and delete permanent thumbnails When adding/removing manga from/to the library make sure the permanent thumbnail files will get handled properly * Move thumbnail cache to actual temp folder * Rename "mangaDownloadsRoot" to "downloadRoot" * Move manga downloads into "mangas" subfolder * Clear downloaded thumbnail
This commit is contained in:
@@ -8,6 +8,7 @@ import org.jetbrains.exposed.sql.transactions.transaction
|
|||||||
import org.jetbrains.exposed.sql.update
|
import org.jetbrains.exposed.sql.update
|
||||||
import suwayomi.tachidesk.graphql.types.MangaMetaType
|
import suwayomi.tachidesk.graphql.types.MangaMetaType
|
||||||
import suwayomi.tachidesk.graphql.types.MangaType
|
import suwayomi.tachidesk.graphql.types.MangaType
|
||||||
|
import suwayomi.tachidesk.manga.impl.Library
|
||||||
import suwayomi.tachidesk.manga.impl.Manga
|
import suwayomi.tachidesk.manga.impl.Manga
|
||||||
import suwayomi.tachidesk.manga.model.table.MangaMetaTable
|
import suwayomi.tachidesk.manga.model.table.MangaMetaTable
|
||||||
import suwayomi.tachidesk.manga.model.table.MangaTable
|
import suwayomi.tachidesk.manga.model.table.MangaTable
|
||||||
@@ -44,7 +45,7 @@ class MangaMutation {
|
|||||||
val patch: UpdateMangaPatch
|
val patch: UpdateMangaPatch
|
||||||
)
|
)
|
||||||
|
|
||||||
private fun updateMangas(ids: List<Int>, patch: UpdateMangaPatch) {
|
private suspend fun updateMangas(ids: List<Int>, patch: UpdateMangaPatch) {
|
||||||
transaction {
|
transaction {
|
||||||
if (patch.inLibrary != null) {
|
if (patch.inLibrary != null) {
|
||||||
MangaTable.update({ MangaTable.id inList ids }) { update ->
|
MangaTable.update({ MangaTable.id inList ids }) { update ->
|
||||||
@@ -53,37 +54,47 @@ class MangaMutation {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}.apply {
|
||||||
|
if (patch.inLibrary != null) {
|
||||||
|
ids.forEach {
|
||||||
|
Library.handleMangaThumbnail(it, patch.inLibrary)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun updateManga(input: UpdateMangaInput): UpdateMangaPayload {
|
fun updateManga(input: UpdateMangaInput): CompletableFuture<UpdateMangaPayload> {
|
||||||
val (clientMutationId, id, patch) = input
|
val (clientMutationId, id, patch) = input
|
||||||
|
|
||||||
updateMangas(listOf(id), patch)
|
return future {
|
||||||
|
updateMangas(listOf(id), patch)
|
||||||
|
}.thenApply {
|
||||||
|
val manga = transaction {
|
||||||
|
MangaType(MangaTable.select { MangaTable.id eq id }.first())
|
||||||
|
}
|
||||||
|
|
||||||
val manga = transaction {
|
UpdateMangaPayload(
|
||||||
MangaType(MangaTable.select { MangaTable.id eq id }.first())
|
clientMutationId = clientMutationId,
|
||||||
|
manga = manga
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
return UpdateMangaPayload(
|
|
||||||
clientMutationId = clientMutationId,
|
|
||||||
manga = manga
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun updateMangas(input: UpdateMangasInput): UpdateMangasPayload {
|
fun updateMangas(input: UpdateMangasInput): CompletableFuture<UpdateMangasPayload> {
|
||||||
val (clientMutationId, ids, patch) = input
|
val (clientMutationId, ids, patch) = input
|
||||||
|
|
||||||
updateMangas(ids, patch)
|
return future {
|
||||||
|
updateMangas(ids, patch)
|
||||||
|
}.thenApply {
|
||||||
|
val mangas = transaction {
|
||||||
|
MangaTable.select { MangaTable.id inList ids }.map { MangaType(it) }
|
||||||
|
}
|
||||||
|
|
||||||
val mangas = transaction {
|
UpdateMangasPayload(
|
||||||
MangaTable.select { MangaTable.id inList ids }.map { MangaType(it) }
|
clientMutationId = clientMutationId,
|
||||||
|
mangas = mangas
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
return UpdateMangasPayload(
|
|
||||||
clientMutationId = clientMutationId,
|
|
||||||
mangas = mangas
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
data class FetchMangaInput(
|
data class FetchMangaInput(
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
package suwayomi.tachidesk.manga.impl
|
package suwayomi.tachidesk.manga.impl
|
||||||
|
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import suwayomi.tachidesk.manga.impl.download.ArchiveProvider
|
import suwayomi.tachidesk.manga.impl.download.fileProvider.ChaptersFilesProvider
|
||||||
import suwayomi.tachidesk.manga.impl.download.DownloadedFilesProvider
|
import suwayomi.tachidesk.manga.impl.download.fileProvider.impl.ArchiveProvider
|
||||||
import suwayomi.tachidesk.manga.impl.download.FolderProvider
|
import suwayomi.tachidesk.manga.impl.download.fileProvider.impl.FolderProvider
|
||||||
import suwayomi.tachidesk.manga.impl.download.model.DownloadChapter
|
import suwayomi.tachidesk.manga.impl.download.model.DownloadChapter
|
||||||
import suwayomi.tachidesk.manga.impl.util.getChapterCbzPath
|
import suwayomi.tachidesk.manga.impl.util.getChapterCbzPath
|
||||||
import suwayomi.tachidesk.manga.impl.util.getChapterDownloadPath
|
import suwayomi.tachidesk.manga.impl.util.getChapterDownloadPath
|
||||||
@@ -13,7 +13,7 @@ import java.io.InputStream
|
|||||||
|
|
||||||
object ChapterDownloadHelper {
|
object ChapterDownloadHelper {
|
||||||
fun getImage(mangaId: Int, chapterId: Int, index: Int): Pair<InputStream, String> {
|
fun getImage(mangaId: Int, chapterId: Int, index: Int): Pair<InputStream, String> {
|
||||||
return provider(mangaId, chapterId).getImage(index)
|
return provider(mangaId, chapterId).getImage().execute(index)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun delete(mangaId: Int, chapterId: Int): Boolean {
|
fun delete(mangaId: Int, chapterId: Int): Boolean {
|
||||||
@@ -27,11 +27,11 @@ object ChapterDownloadHelper {
|
|||||||
scope: CoroutineScope,
|
scope: CoroutineScope,
|
||||||
step: suspend (DownloadChapter?, Boolean) -> Unit
|
step: suspend (DownloadChapter?, Boolean) -> Unit
|
||||||
): Boolean {
|
): Boolean {
|
||||||
return provider(mangaId, chapterId).download(download, scope, step)
|
return provider(mangaId, chapterId).download().execute(download, scope, step)
|
||||||
}
|
}
|
||||||
|
|
||||||
// return the appropriate provider based on how the download was saved. For the logic is simple but will evolve when new types of downloads are available
|
// return the appropriate provider based on how the download was saved. For the logic is simple but will evolve when new types of downloads are available
|
||||||
private fun provider(mangaId: Int, chapterId: Int): DownloadedFilesProvider {
|
private fun provider(mangaId: Int, chapterId: Int): ChaptersFilesProvider {
|
||||||
val chapterFolder = File(getChapterDownloadPath(mangaId, chapterId))
|
val chapterFolder = File(getChapterDownloadPath(mangaId, chapterId))
|
||||||
val cbzFile = File(getChapterCbzPath(mangaId, chapterId))
|
val cbzFile = File(getChapterCbzPath(mangaId, chapterId))
|
||||||
if (cbzFile.exists()) return ArchiveProvider(mangaId, chapterId)
|
if (cbzFile.exists()) return ArchiveProvider(mangaId, chapterId)
|
||||||
|
|||||||
@@ -7,6 +7,10 @@ package suwayomi.tachidesk.manga.impl
|
|||||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
|
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
|
||||||
|
|
||||||
|
import kotlinx.coroutines.CoroutineScope
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.SupervisorJob
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
import org.jetbrains.exposed.sql.and
|
import org.jetbrains.exposed.sql.and
|
||||||
import org.jetbrains.exposed.sql.insert
|
import org.jetbrains.exposed.sql.insert
|
||||||
import org.jetbrains.exposed.sql.select
|
import org.jetbrains.exposed.sql.select
|
||||||
@@ -19,6 +23,8 @@ import suwayomi.tachidesk.manga.model.table.MangaTable
|
|||||||
import java.time.Instant
|
import java.time.Instant
|
||||||
|
|
||||||
object Library {
|
object Library {
|
||||||
|
private val scope = CoroutineScope(SupervisorJob() + Dispatchers.Default)
|
||||||
|
|
||||||
suspend fun addMangaToLibrary(mangaId: Int) {
|
suspend fun addMangaToLibrary(mangaId: Int) {
|
||||||
val manga = getManga(mangaId)
|
val manga = getManga(mangaId)
|
||||||
if (!manga.inLibrary) {
|
if (!manga.inLibrary) {
|
||||||
@@ -41,6 +47,8 @@ object Library {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}.apply {
|
||||||
|
handleMangaThumbnail(mangaId, true)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -52,6 +60,22 @@ object Library {
|
|||||||
MangaTable.update({ MangaTable.id eq manga.id }) {
|
MangaTable.update({ MangaTable.id eq manga.id }) {
|
||||||
it[inLibrary] = false
|
it[inLibrary] = false
|
||||||
}
|
}
|
||||||
|
}.apply {
|
||||||
|
handleMangaThumbnail(mangaId, false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun handleMangaThumbnail(mangaId: Int, inLibrary: Boolean) {
|
||||||
|
scope.launch {
|
||||||
|
try {
|
||||||
|
if (inLibrary) {
|
||||||
|
ThumbnailDownloadHelper.download(mangaId)
|
||||||
|
} else {
|
||||||
|
ThumbnailDownloadHelper.delete(mangaId)
|
||||||
|
}
|
||||||
|
} catch (e: Exception) {
|
||||||
|
// ignore
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -25,12 +25,13 @@ import org.kodein.di.conf.global
|
|||||||
import org.kodein.di.instance
|
import org.kodein.di.instance
|
||||||
import suwayomi.tachidesk.manga.impl.MangaList.proxyThumbnailUrl
|
import suwayomi.tachidesk.manga.impl.MangaList.proxyThumbnailUrl
|
||||||
import suwayomi.tachidesk.manga.impl.Source.getSource
|
import suwayomi.tachidesk.manga.impl.Source.getSource
|
||||||
|
import suwayomi.tachidesk.manga.impl.download.fileProvider.impl.MissingThumbnailException
|
||||||
import suwayomi.tachidesk.manga.impl.util.network.await
|
import suwayomi.tachidesk.manga.impl.util.network.await
|
||||||
import suwayomi.tachidesk.manga.impl.util.source.GetCatalogueSource.getCatalogueSourceOrNull
|
import suwayomi.tachidesk.manga.impl.util.source.GetCatalogueSource.getCatalogueSourceOrNull
|
||||||
import suwayomi.tachidesk.manga.impl.util.source.GetCatalogueSource.getCatalogueSourceOrStub
|
import suwayomi.tachidesk.manga.impl.util.source.GetCatalogueSource.getCatalogueSourceOrStub
|
||||||
import suwayomi.tachidesk.manga.impl.util.source.StubSource
|
import suwayomi.tachidesk.manga.impl.util.source.StubSource
|
||||||
import suwayomi.tachidesk.manga.impl.util.storage.ImageResponse.clearCachedImage
|
import suwayomi.tachidesk.manga.impl.util.storage.ImageResponse.clearCachedImage
|
||||||
import suwayomi.tachidesk.manga.impl.util.storage.ImageResponse.getCachedImageResponse
|
import suwayomi.tachidesk.manga.impl.util.storage.ImageResponse.getImageResponse
|
||||||
import suwayomi.tachidesk.manga.impl.util.storage.ImageUtil
|
import suwayomi.tachidesk.manga.impl.util.storage.ImageUtil
|
||||||
import suwayomi.tachidesk.manga.impl.util.updateMangaDownloadDir
|
import suwayomi.tachidesk.manga.impl.util.updateMangaDownloadDir
|
||||||
import suwayomi.tachidesk.manga.model.dataclass.MangaDataClass
|
import suwayomi.tachidesk.manga.model.dataclass.MangaDataClass
|
||||||
@@ -126,7 +127,7 @@ object Manga {
|
|||||||
if (!sManga.thumbnail_url.isNullOrEmpty() && sManga.thumbnail_url != mangaEntry[MangaTable.thumbnail_url]) {
|
if (!sManga.thumbnail_url.isNullOrEmpty() && sManga.thumbnail_url != mangaEntry[MangaTable.thumbnail_url]) {
|
||||||
it[MangaTable.thumbnail_url] = sManga.thumbnail_url
|
it[MangaTable.thumbnail_url] = sManga.thumbnail_url
|
||||||
it[MangaTable.thumbnailUrlLastFetched] = Instant.now().epochSecond
|
it[MangaTable.thumbnailUrlLastFetched] = Instant.now().epochSecond
|
||||||
clearMangaThumbnailCache(mangaId)
|
clearThumbnail(mangaId)
|
||||||
}
|
}
|
||||||
|
|
||||||
it[MangaTable.realUrl] = runCatching {
|
it[MangaTable.realUrl] = runCatching {
|
||||||
@@ -232,15 +233,16 @@ object Manga {
|
|||||||
|
|
||||||
private val applicationDirs by DI.global.instance<ApplicationDirs>()
|
private val applicationDirs by DI.global.instance<ApplicationDirs>()
|
||||||
private val network: NetworkHelper by injectLazy()
|
private val network: NetworkHelper by injectLazy()
|
||||||
suspend fun getMangaThumbnail(mangaId: Int): Pair<InputStream, String> {
|
|
||||||
val cacheSaveDir = applicationDirs.thumbnailsRoot
|
suspend fun fetchMangaThumbnail(mangaId: Int): Pair<InputStream, String> {
|
||||||
|
val cacheSaveDir = applicationDirs.tempThumbnailCacheRoot
|
||||||
val fileName = mangaId.toString()
|
val fileName = mangaId.toString()
|
||||||
|
|
||||||
val mangaEntry = transaction { MangaTable.select { MangaTable.id eq mangaId }.first() }
|
val mangaEntry = transaction { MangaTable.select { MangaTable.id eq mangaId }.first() }
|
||||||
val sourceId = mangaEntry[MangaTable.sourceReference]
|
val sourceId = mangaEntry[MangaTable.sourceReference]
|
||||||
|
|
||||||
return when (val source = getCatalogueSourceOrStub(sourceId)) {
|
return when (val source = getCatalogueSourceOrStub(sourceId)) {
|
||||||
is HttpSource -> getCachedImageResponse(cacheSaveDir, fileName) {
|
is HttpSource -> getImageResponse(cacheSaveDir, fileName) {
|
||||||
val thumbnailUrl = mangaEntry[MangaTable.thumbnail_url]
|
val thumbnailUrl = mangaEntry[MangaTable.thumbnail_url]
|
||||||
?: if (!mangaEntry[MangaTable.initialized]) {
|
?: if (!mangaEntry[MangaTable.initialized]) {
|
||||||
// initialize then try again
|
// initialize then try again
|
||||||
@@ -272,7 +274,7 @@ object Manga {
|
|||||||
imageFile.inputStream() to contentType
|
imageFile.inputStream() to contentType
|
||||||
}
|
}
|
||||||
|
|
||||||
is StubSource -> getCachedImageResponse(cacheSaveDir, fileName) {
|
is StubSource -> getImageResponse(cacheSaveDir, fileName) {
|
||||||
val thumbnailUrl = mangaEntry[MangaTable.thumbnail_url]
|
val thumbnailUrl = mangaEntry[MangaTable.thumbnail_url]
|
||||||
?: throw NullPointerException("No thumbnail found")
|
?: throw NullPointerException("No thumbnail found")
|
||||||
network.client.newCall(
|
network.client.newCall(
|
||||||
@@ -284,10 +286,25 @@ object Manga {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun clearMangaThumbnailCache(mangaId: Int) {
|
suspend fun getMangaThumbnail(mangaId: Int): Pair<InputStream, String> {
|
||||||
val saveDir = applicationDirs.thumbnailsRoot
|
val mangaEntry = transaction { MangaTable.select { MangaTable.id eq mangaId }.first() }
|
||||||
|
|
||||||
|
if (mangaEntry[MangaTable.inLibrary]) {
|
||||||
|
return try {
|
||||||
|
ThumbnailDownloadHelper.getImage(mangaId)
|
||||||
|
} catch (_: MissingThumbnailException) {
|
||||||
|
ThumbnailDownloadHelper.download(mangaId)
|
||||||
|
ThumbnailDownloadHelper.getImage(mangaId)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return fetchMangaThumbnail(mangaId)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun clearThumbnail(mangaId: Int) {
|
||||||
val fileName = mangaId.toString()
|
val fileName = mangaId.toString()
|
||||||
|
|
||||||
clearCachedImage(saveDir, fileName)
|
clearCachedImage(applicationDirs.tempThumbnailCacheRoot, fileName)
|
||||||
|
clearCachedImage(applicationDirs.thumbnailDownloadsRoot, fileName)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ import org.jetbrains.exposed.sql.update
|
|||||||
import suwayomi.tachidesk.manga.impl.util.getChapterCachePath
|
import suwayomi.tachidesk.manga.impl.util.getChapterCachePath
|
||||||
import suwayomi.tachidesk.manga.impl.util.lang.awaitSingle
|
import suwayomi.tachidesk.manga.impl.util.lang.awaitSingle
|
||||||
import suwayomi.tachidesk.manga.impl.util.source.GetCatalogueSource.getCatalogueSourceOrStub
|
import suwayomi.tachidesk.manga.impl.util.source.GetCatalogueSource.getCatalogueSourceOrStub
|
||||||
import suwayomi.tachidesk.manga.impl.util.storage.ImageResponse.getCachedImageResponse
|
import suwayomi.tachidesk.manga.impl.util.storage.ImageResponse.getImageResponse
|
||||||
import suwayomi.tachidesk.manga.impl.util.storage.ImageUtil
|
import suwayomi.tachidesk.manga.impl.util.storage.ImageUtil
|
||||||
import suwayomi.tachidesk.manga.model.table.ChapterTable
|
import suwayomi.tachidesk.manga.model.table.ChapterTable
|
||||||
import suwayomi.tachidesk.manga.model.table.MangaTable
|
import suwayomi.tachidesk.manga.model.table.MangaTable
|
||||||
@@ -99,7 +99,7 @@ object Page {
|
|||||||
val cacheSaveDir = getChapterCachePath(mangaId, chapterId)
|
val cacheSaveDir = getChapterCachePath(mangaId, chapterId)
|
||||||
|
|
||||||
// Note: don't care about invalidating cache because OS cache is not permanent
|
// Note: don't care about invalidating cache because OS cache is not permanent
|
||||||
return getCachedImageResponse(cacheSaveDir, fileName) {
|
return getImageResponse(cacheSaveDir, fileName) {
|
||||||
source.fetchImage(tachiyomiPage).awaitSingle()
|
source.fetchImage(tachiyomiPage).awaitSingle()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,23 @@
|
|||||||
|
package suwayomi.tachidesk.manga.impl
|
||||||
|
|
||||||
|
import suwayomi.tachidesk.manga.impl.download.fileProvider.impl.ThumbnailFileProvider
|
||||||
|
import java.io.InputStream
|
||||||
|
|
||||||
|
object ThumbnailDownloadHelper {
|
||||||
|
fun getImage(mangaId: Int): Pair<InputStream, String> {
|
||||||
|
return provider(mangaId).getImage().execute()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun delete(mangaId: Int): Boolean {
|
||||||
|
return provider(mangaId).delete()
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun download(mangaId: Int): Boolean {
|
||||||
|
return provider(mangaId).download().execute()
|
||||||
|
}
|
||||||
|
|
||||||
|
// return the appropriate provider based on how the download was saved. For the logic is simple but will evolve when new types of downloads are available
|
||||||
|
private fun provider(mangaId: Int): ThumbnailFileProvider {
|
||||||
|
return ThumbnailFileProvider(mangaId)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,20 +0,0 @@
|
|||||||
package suwayomi.tachidesk.manga.impl.download
|
|
||||||
|
|
||||||
import kotlinx.coroutines.CoroutineScope
|
|
||||||
import suwayomi.tachidesk.manga.impl.download.model.DownloadChapter
|
|
||||||
import java.io.InputStream
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Base class for downloaded chapter files provider, example: Folder, Archive
|
|
||||||
* */
|
|
||||||
abstract class DownloadedFilesProvider(val mangaId: Int, val chapterId: Int) {
|
|
||||||
abstract fun getImage(index: Int): Pair<InputStream, String>
|
|
||||||
|
|
||||||
abstract suspend fun download(
|
|
||||||
download: DownloadChapter,
|
|
||||||
scope: CoroutineScope,
|
|
||||||
step: suspend (DownloadChapter?, Boolean) -> Unit
|
|
||||||
): Boolean
|
|
||||||
|
|
||||||
abstract fun delete(): Boolean
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,40 @@
|
|||||||
|
package suwayomi.tachidesk.manga.impl.download.fileProvider
|
||||||
|
|
||||||
|
import kotlinx.coroutines.CoroutineScope
|
||||||
|
import suwayomi.tachidesk.manga.impl.download.model.DownloadChapter
|
||||||
|
import java.io.InputStream
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Base class for downloaded chapter files provider, example: Folder, Archive
|
||||||
|
* */
|
||||||
|
abstract class ChaptersFilesProvider(val mangaId: Int, val chapterId: Int) : DownloadedFilesProvider {
|
||||||
|
abstract fun getImageImpl(index: Int): Pair<InputStream, String>
|
||||||
|
|
||||||
|
override fun getImage(): RetrieveFile1Args<Int> {
|
||||||
|
return object : RetrieveFile1Args<Int> {
|
||||||
|
override fun execute(a: Int): Pair<InputStream, String> {
|
||||||
|
return getImageImpl(a)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
abstract suspend fun downloadImpl(
|
||||||
|
download: DownloadChapter,
|
||||||
|
scope: CoroutineScope,
|
||||||
|
step: suspend (DownloadChapter?, Boolean) -> Unit
|
||||||
|
): Boolean
|
||||||
|
|
||||||
|
override fun download(): FileDownload3Args<DownloadChapter, CoroutineScope, suspend (DownloadChapter?, Boolean) -> Unit> {
|
||||||
|
return object : FileDownload3Args<DownloadChapter, CoroutineScope, suspend (DownloadChapter?, Boolean) -> Unit> {
|
||||||
|
override suspend fun execute(
|
||||||
|
a: DownloadChapter,
|
||||||
|
b: CoroutineScope,
|
||||||
|
c: suspend (DownloadChapter?, Boolean) -> Unit
|
||||||
|
): Boolean {
|
||||||
|
return downloadImpl(a, b, c)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
abstract override fun delete(): Boolean
|
||||||
|
}
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
package suwayomi.tachidesk.manga.impl.download.fileProvider
|
||||||
|
|
||||||
|
interface DownloadedFilesProvider : FileDownloader, FileRetriever {
|
||||||
|
fun delete(): Boolean
|
||||||
|
}
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
package suwayomi.tachidesk.manga.impl.download.fileProvider
|
||||||
|
|
||||||
|
@FunctionalInterface
|
||||||
|
interface FileDownload {
|
||||||
|
suspend fun executeDownload(vararg args: Any): Boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
@FunctionalInterface
|
||||||
|
interface FileDownload0Args : FileDownload {
|
||||||
|
suspend fun execute(): Boolean
|
||||||
|
|
||||||
|
override suspend fun executeDownload(vararg args: Any): Boolean {
|
||||||
|
return execute()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@FunctionalInterface
|
||||||
|
interface FileDownload3Args<A, B, C> : FileDownload {
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@FunctionalInterface
|
||||||
|
interface FileDownloader {
|
||||||
|
fun download(): FileDownload
|
||||||
|
}
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
package suwayomi.tachidesk.manga.impl.download.fileProvider
|
||||||
|
|
||||||
|
import java.io.InputStream
|
||||||
|
|
||||||
|
@FunctionalInterface
|
||||||
|
interface RetrieveFile {
|
||||||
|
fun executeGetImage(vararg args: Any): Pair<InputStream, String>
|
||||||
|
}
|
||||||
|
|
||||||
|
@FunctionalInterface
|
||||||
|
interface RetrieveFile0Args : RetrieveFile {
|
||||||
|
fun execute(): Pair<InputStream, String>
|
||||||
|
|
||||||
|
override fun executeGetImage(vararg args: Any): Pair<InputStream, String> {
|
||||||
|
return execute()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@FunctionalInterface
|
||||||
|
interface RetrieveFile1Args<A> : RetrieveFile {
|
||||||
|
fun execute(a: A): Pair<InputStream, String>
|
||||||
|
|
||||||
|
override fun executeGetImage(vararg args: Any): Pair<InputStream, String> {
|
||||||
|
return execute(args[0] as A)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@FunctionalInterface
|
||||||
|
interface FileRetriever {
|
||||||
|
fun getImage(): RetrieveFile
|
||||||
|
}
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
package suwayomi.tachidesk.manga.impl.download
|
package suwayomi.tachidesk.manga.impl.download.fileProvider.impl
|
||||||
|
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
@@ -7,14 +7,15 @@ import org.apache.commons.compress.archivers.zip.ZipArchiveEntry
|
|||||||
import org.apache.commons.compress.archivers.zip.ZipArchiveInputStream
|
import org.apache.commons.compress.archivers.zip.ZipArchiveInputStream
|
||||||
import org.apache.commons.compress.archivers.zip.ZipArchiveOutputStream
|
import org.apache.commons.compress.archivers.zip.ZipArchiveOutputStream
|
||||||
import org.apache.commons.compress.archivers.zip.ZipFile
|
import org.apache.commons.compress.archivers.zip.ZipFile
|
||||||
|
import suwayomi.tachidesk.manga.impl.download.fileProvider.ChaptersFilesProvider
|
||||||
import suwayomi.tachidesk.manga.impl.download.model.DownloadChapter
|
import suwayomi.tachidesk.manga.impl.download.model.DownloadChapter
|
||||||
import suwayomi.tachidesk.manga.impl.util.getChapterCbzPath
|
import suwayomi.tachidesk.manga.impl.util.getChapterCbzPath
|
||||||
import suwayomi.tachidesk.manga.impl.util.getChapterDownloadPath
|
import suwayomi.tachidesk.manga.impl.util.getChapterDownloadPath
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import java.io.InputStream
|
import java.io.InputStream
|
||||||
|
|
||||||
class ArchiveProvider(mangaId: Int, chapterId: Int) : DownloadedFilesProvider(mangaId, chapterId) {
|
class ArchiveProvider(mangaId: Int, chapterId: Int) : ChaptersFilesProvider(mangaId, chapterId) {
|
||||||
override fun getImage(index: Int): Pair<InputStream, String> {
|
override fun getImageImpl(index: Int): Pair<InputStream, String> {
|
||||||
val cbzPath = getChapterCbzPath(mangaId, chapterId)
|
val cbzPath = getChapterCbzPath(mangaId, chapterId)
|
||||||
val zipFile = ZipFile(cbzPath)
|
val zipFile = ZipFile(cbzPath)
|
||||||
val zipEntry = zipFile.entries.toList().sortedWith(compareBy({ it.name }, { it.name }))[index]
|
val zipEntry = zipFile.entries.toList().sortedWith(compareBy({ it.name }, { it.name }))[index]
|
||||||
@@ -23,7 +24,7 @@ class ArchiveProvider(mangaId: Int, chapterId: Int) : DownloadedFilesProvider(ma
|
|||||||
return Pair(inputStream.buffered(), "image/$fileType")
|
return Pair(inputStream.buffered(), "image/$fileType")
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun download(
|
override suspend fun downloadImpl(
|
||||||
download: DownloadChapter,
|
download: DownloadChapter,
|
||||||
scope: CoroutineScope,
|
scope: CoroutineScope,
|
||||||
step: suspend (DownloadChapter?, Boolean) -> Unit
|
step: suspend (DownloadChapter?, Boolean) -> Unit
|
||||||
@@ -33,7 +34,7 @@ class ArchiveProvider(mangaId: Int, chapterId: Int) : DownloadedFilesProvider(ma
|
|||||||
val chapterFolder = File(chapterDir)
|
val chapterFolder = File(chapterDir)
|
||||||
if (outputFile.exists()) handleExistingCbzFile(outputFile, chapterFolder)
|
if (outputFile.exists()) handleExistingCbzFile(outputFile, chapterFolder)
|
||||||
|
|
||||||
FolderProvider(mangaId, chapterId).download(download, scope, step)
|
FolderProvider(mangaId, chapterId).download().execute(download, scope, step)
|
||||||
|
|
||||||
withContext(Dispatchers.IO) {
|
withContext(Dispatchers.IO) {
|
||||||
outputFile.createNewFile()
|
outputFile.createNewFile()
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
package suwayomi.tachidesk.manga.impl.download
|
package suwayomi.tachidesk.manga.impl.download.fileProvider.impl
|
||||||
|
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.coroutines.FlowPreview
|
import kotlinx.coroutines.FlowPreview
|
||||||
@@ -9,6 +9,7 @@ import kotlinx.coroutines.flow.onEach
|
|||||||
import kotlinx.coroutines.flow.sample
|
import kotlinx.coroutines.flow.sample
|
||||||
import suwayomi.tachidesk.manga.impl.Page
|
import suwayomi.tachidesk.manga.impl.Page
|
||||||
import suwayomi.tachidesk.manga.impl.Page.getPageName
|
import suwayomi.tachidesk.manga.impl.Page.getPageName
|
||||||
|
import suwayomi.tachidesk.manga.impl.download.fileProvider.ChaptersFilesProvider
|
||||||
import suwayomi.tachidesk.manga.impl.download.model.DownloadChapter
|
import suwayomi.tachidesk.manga.impl.download.model.DownloadChapter
|
||||||
import suwayomi.tachidesk.manga.impl.util.getChapterDownloadPath
|
import suwayomi.tachidesk.manga.impl.util.getChapterDownloadPath
|
||||||
import suwayomi.tachidesk.manga.impl.util.storage.ImageResponse
|
import suwayomi.tachidesk.manga.impl.util.storage.ImageResponse
|
||||||
@@ -19,8 +20,8 @@ import java.io.InputStream
|
|||||||
/*
|
/*
|
||||||
* Provides downloaded files when pages were downloaded into folders
|
* Provides downloaded files when pages were downloaded into folders
|
||||||
* */
|
* */
|
||||||
class FolderProvider(mangaId: Int, chapterId: Int) : DownloadedFilesProvider(mangaId, chapterId) {
|
class FolderProvider(mangaId: Int, chapterId: Int) : ChaptersFilesProvider(mangaId, chapterId) {
|
||||||
override fun getImage(index: Int): Pair<InputStream, String> {
|
override fun getImageImpl(index: Int): Pair<InputStream, String> {
|
||||||
val chapterDir = getChapterDownloadPath(mangaId, chapterId)
|
val chapterDir = getChapterDownloadPath(mangaId, chapterId)
|
||||||
val folder = File(chapterDir)
|
val folder = File(chapterDir)
|
||||||
folder.mkdirs()
|
folder.mkdirs()
|
||||||
@@ -30,7 +31,7 @@ class FolderProvider(mangaId: Int, chapterId: Int) : DownloadedFilesProvider(man
|
|||||||
}
|
}
|
||||||
|
|
||||||
@OptIn(FlowPreview::class)
|
@OptIn(FlowPreview::class)
|
||||||
override suspend fun download(
|
override suspend fun downloadImpl(
|
||||||
download: DownloadChapter,
|
download: DownloadChapter,
|
||||||
scope: CoroutineScope,
|
scope: CoroutineScope,
|
||||||
step: suspend (DownloadChapter?, Boolean) -> Unit
|
step: suspend (DownloadChapter?, Boolean) -> Unit
|
||||||
@@ -0,0 +1,87 @@
|
|||||||
|
package suwayomi.tachidesk.manga.impl.download.fileProvider.impl
|
||||||
|
|
||||||
|
import org.kodein.di.DI
|
||||||
|
import org.kodein.di.conf.global
|
||||||
|
import org.kodein.di.instance
|
||||||
|
import suwayomi.tachidesk.manga.impl.Manga
|
||||||
|
import suwayomi.tachidesk.manga.impl.download.fileProvider.DownloadedFilesProvider
|
||||||
|
import suwayomi.tachidesk.manga.impl.download.fileProvider.FileDownload0Args
|
||||||
|
import suwayomi.tachidesk.manga.impl.download.fileProvider.RetrieveFile0Args
|
||||||
|
import suwayomi.tachidesk.manga.impl.util.getThumbnailDownloadPath
|
||||||
|
import suwayomi.tachidesk.manga.impl.util.storage.ImageResponse
|
||||||
|
import suwayomi.tachidesk.manga.impl.util.storage.ImageResponse.getCachedImageResponse
|
||||||
|
import suwayomi.tachidesk.server.ApplicationDirs
|
||||||
|
import java.io.File
|
||||||
|
import java.io.InputStream
|
||||||
|
|
||||||
|
class MissingThumbnailException : Exception("No thumbnail found")
|
||||||
|
|
||||||
|
private val applicationDirs by DI.global.instance<ApplicationDirs>()
|
||||||
|
|
||||||
|
class ThumbnailFileProvider(val mangaId: Int) : DownloadedFilesProvider {
|
||||||
|
private fun getFilePath(): String? {
|
||||||
|
val thumbnailDir = applicationDirs.thumbnailDownloadsRoot
|
||||||
|
val fileName = mangaId.toString()
|
||||||
|
return ImageResponse.findFileNameStartingWith(thumbnailDir, fileName)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getImageImpl(): Pair<InputStream, String> {
|
||||||
|
val filePathWithoutExt = getThumbnailDownloadPath(mangaId)
|
||||||
|
val filePath = getFilePath()
|
||||||
|
|
||||||
|
if (filePath.isNullOrEmpty()) {
|
||||||
|
throw MissingThumbnailException()
|
||||||
|
}
|
||||||
|
|
||||||
|
return getCachedImageResponse(filePath, filePathWithoutExt)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getImage(): RetrieveFile0Args {
|
||||||
|
return object : RetrieveFile0Args {
|
||||||
|
override fun execute(): Pair<InputStream, String> {
|
||||||
|
return getImageImpl()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun downloadImpl(): Boolean {
|
||||||
|
val isExistingFile = getFilePath() != null
|
||||||
|
if (isExistingFile) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
Manga.fetchMangaThumbnail(mangaId).first.use { image ->
|
||||||
|
makeSureDownloadDirExists()
|
||||||
|
val filePath = getThumbnailDownloadPath(mangaId)
|
||||||
|
ImageResponse.saveImage(filePath, image)
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun download(): FileDownload0Args {
|
||||||
|
return object : FileDownload0Args {
|
||||||
|
override suspend fun execute(): Boolean {
|
||||||
|
return downloadImpl()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun delete(): Boolean {
|
||||||
|
val filePath = getFilePath()
|
||||||
|
if (filePath.isNullOrEmpty()) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
return File(filePath).delete()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun makeSureDownloadDirExists() {
|
||||||
|
val downloadDirPath = applicationDirs.thumbnailDownloadsRoot
|
||||||
|
val downloadDir = File(downloadDirPath)
|
||||||
|
|
||||||
|
if (!downloadDir.exists()) {
|
||||||
|
downloadDir.mkdir()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -40,7 +40,7 @@ import suwayomi.tachidesk.manga.impl.util.PackageTools.getPackageInfo
|
|||||||
import suwayomi.tachidesk.manga.impl.util.PackageTools.loadExtensionSources
|
import suwayomi.tachidesk.manga.impl.util.PackageTools.loadExtensionSources
|
||||||
import suwayomi.tachidesk.manga.impl.util.network.await
|
import suwayomi.tachidesk.manga.impl.util.network.await
|
||||||
import suwayomi.tachidesk.manga.impl.util.source.GetCatalogueSource
|
import suwayomi.tachidesk.manga.impl.util.source.GetCatalogueSource
|
||||||
import suwayomi.tachidesk.manga.impl.util.storage.ImageResponse.getCachedImageResponse
|
import suwayomi.tachidesk.manga.impl.util.storage.ImageResponse.getImageResponse
|
||||||
import suwayomi.tachidesk.manga.model.table.ExtensionTable
|
import suwayomi.tachidesk.manga.model.table.ExtensionTable
|
||||||
import suwayomi.tachidesk.manga.model.table.SourceTable
|
import suwayomi.tachidesk.manga.model.table.SourceTable
|
||||||
import suwayomi.tachidesk.server.ApplicationDirs
|
import suwayomi.tachidesk.server.ApplicationDirs
|
||||||
@@ -329,7 +329,7 @@ object Extension {
|
|||||||
|
|
||||||
val cacheSaveDir = "${applicationDirs.extensionsRoot}/icon"
|
val cacheSaveDir = "${applicationDirs.extensionsRoot}/icon"
|
||||||
|
|
||||||
return getCachedImageResponse(cacheSaveDir, apkName) {
|
return getImageResponse(cacheSaveDir, apkName) {
|
||||||
network.client.newCall(
|
network.client.newCall(
|
||||||
GET(iconUrl)
|
GET(iconUrl)
|
||||||
).await()
|
).await()
|
||||||
|
|||||||
@@ -44,6 +44,10 @@ private fun getChapterDir(mangaId: Int, chapterId: Int): String {
|
|||||||
return getMangaDir(mangaId) + "/$chapterDir"
|
return getMangaDir(mangaId) + "/$chapterDir"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun getThumbnailDownloadPath(mangaId: Int): String {
|
||||||
|
return applicationDirs.thumbnailDownloadsRoot + "/$mangaId"
|
||||||
|
}
|
||||||
|
|
||||||
fun getChapterDownloadPath(mangaId: Int, chapterId: Int): String {
|
fun getChapterDownloadPath(mangaId: Int, chapterId: Int): String {
|
||||||
return applicationDirs.mangaDownloadsRoot + "/" + getChapterDir(mangaId, chapterId)
|
return applicationDirs.mangaDownloadsRoot + "/" + getChapterDir(mangaId, chapterId)
|
||||||
}
|
}
|
||||||
@@ -66,8 +70,8 @@ fun updateMangaDownloadDir(mangaId: Int, newTitle: String): Boolean {
|
|||||||
|
|
||||||
val newMangaDir = SafePath.buildValidFilename(newTitle)
|
val newMangaDir = SafePath.buildValidFilename(newTitle)
|
||||||
|
|
||||||
val oldDir = "${applicationDirs.mangaDownloadsRoot}/$sourceDir/$mangaDir"
|
val oldDir = "${applicationDirs.downloadsRoot}/$sourceDir/$mangaDir"
|
||||||
val newDir = "${applicationDirs.mangaDownloadsRoot}/$sourceDir/$newMangaDir"
|
val newDir = "${applicationDirs.downloadsRoot}/$sourceDir/$newMangaDir"
|
||||||
|
|
||||||
val oldDirFile = File(oldDir)
|
val oldDirFile = File(oldDir)
|
||||||
val newDirFile = File(newDir)
|
val newDirFile = File(newDir)
|
||||||
|
|||||||
@@ -30,6 +30,14 @@ object ImageResponse {
|
|||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun getCachedImageResponse(cachedFile: String, filePath: String): Pair<InputStream, String> {
|
||||||
|
val fileType = cachedFile.substringAfter("$filePath.")
|
||||||
|
return Pair(
|
||||||
|
pathToInputStream(cachedFile),
|
||||||
|
"image/$fileType"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get a cached image response
|
* Get a cached image response
|
||||||
*
|
*
|
||||||
@@ -38,7 +46,7 @@ object ImageResponse {
|
|||||||
* @param cacheSavePath where to save the cached image. Caller should decide to use perma cache or temp cache (OS temp dir)
|
* @param cacheSavePath where to save the cached image. Caller should decide to use perma cache or temp cache (OS temp dir)
|
||||||
* @param fileName what the saved cache file should be named
|
* @param fileName what the saved cache file should be named
|
||||||
*/
|
*/
|
||||||
suspend fun getCachedImageResponse(
|
suspend fun getImageResponse(
|
||||||
saveDir: String,
|
saveDir: String,
|
||||||
fileName: String,
|
fileName: String,
|
||||||
fetcher: suspend () -> Response
|
fetcher: suspend () -> Response
|
||||||
@@ -50,11 +58,7 @@ object ImageResponse {
|
|||||||
|
|
||||||
// in case the cached file is a ".tmp" file something went wrong with the previous download, and it has to be downloaded again
|
// in case the cached file is a ".tmp" file something went wrong with the previous download, and it has to be downloaded again
|
||||||
if (cachedFile != null && !cachedFile.endsWith(".tmp")) {
|
if (cachedFile != null && !cachedFile.endsWith(".tmp")) {
|
||||||
val fileType = cachedFile.substringAfter("$filePath.")
|
return getCachedImageResponse(cachedFile, filePath)
|
||||||
return Pair(
|
|
||||||
pathToInputStream(cachedFile),
|
|
||||||
"image/$fileType"
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
val response = fetcher()
|
val response = fetcher()
|
||||||
|
|||||||
@@ -45,13 +45,16 @@ class ApplicationDirs(
|
|||||||
) {
|
) {
|
||||||
val cacheRoot = System.getProperty("java.io.tmpdir") + "/tachidesk"
|
val cacheRoot = System.getProperty("java.io.tmpdir") + "/tachidesk"
|
||||||
val extensionsRoot = "$dataRoot/extensions"
|
val extensionsRoot = "$dataRoot/extensions"
|
||||||
val thumbnailsRoot = "$dataRoot/thumbnails"
|
val downloadsRoot = serverConfig.downloadsPath.ifBlank { "$dataRoot/downloads" }
|
||||||
val mangaDownloadsRoot = serverConfig.downloadsPath.ifBlank { "$dataRoot/downloads" }
|
|
||||||
val localMangaRoot = serverConfig.localSourcePath.ifBlank { "$dataRoot/local" }
|
val localMangaRoot = serverConfig.localSourcePath.ifBlank { "$dataRoot/local" }
|
||||||
val webUIRoot = "$dataRoot/webUI"
|
val webUIRoot = "$dataRoot/webUI"
|
||||||
val automatedBackupRoot = serverConfig.backupPath.ifBlank { "$dataRoot/backups" }
|
val automatedBackupRoot = serverConfig.backupPath.ifBlank { "$dataRoot/backups" }
|
||||||
|
|
||||||
|
val tempThumbnailCacheRoot = "$tempRoot/thumbnails"
|
||||||
val tempMangaCacheRoot = "$tempRoot/manga-cache"
|
val tempMangaCacheRoot = "$tempRoot/manga-cache"
|
||||||
|
|
||||||
|
val thumbnailDownloadsRoot = "$downloadsRoot/thumbnails"
|
||||||
|
val mangaDownloadsRoot = "$downloadsRoot/mangas"
|
||||||
}
|
}
|
||||||
|
|
||||||
val serverConfig: ServerConfig by lazy { GlobalConfigManager.module() }
|
val serverConfig: ServerConfig by lazy { GlobalConfigManager.module() }
|
||||||
@@ -96,7 +99,7 @@ fun applicationSetup() {
|
|||||||
logger.debug("Data Root directory is set to: ${applicationDirs.dataRoot}")
|
logger.debug("Data Root directory is set to: ${applicationDirs.dataRoot}")
|
||||||
|
|
||||||
// Migrate Directories from old versions
|
// Migrate Directories from old versions
|
||||||
File("$ApplicationRootDir/manga-thumbnails").renameTo(applicationDirs.thumbnailsRoot)
|
File("$ApplicationRootDir/manga-thumbnails").renameTo(applicationDirs.tempThumbnailCacheRoot)
|
||||||
File("$ApplicationRootDir/manga-local").renameTo(applicationDirs.localMangaRoot)
|
File("$ApplicationRootDir/manga-local").renameTo(applicationDirs.localMangaRoot)
|
||||||
File("$ApplicationRootDir/anime-thumbnails").delete()
|
File("$ApplicationRootDir/anime-thumbnails").delete()
|
||||||
|
|
||||||
@@ -105,8 +108,8 @@ fun applicationSetup() {
|
|||||||
applicationDirs.dataRoot,
|
applicationDirs.dataRoot,
|
||||||
applicationDirs.extensionsRoot,
|
applicationDirs.extensionsRoot,
|
||||||
applicationDirs.extensionsRoot + "/icon",
|
applicationDirs.extensionsRoot + "/icon",
|
||||||
applicationDirs.thumbnailsRoot,
|
applicationDirs.tempThumbnailCacheRoot,
|
||||||
applicationDirs.mangaDownloadsRoot,
|
applicationDirs.downloadsRoot,
|
||||||
applicationDirs.localMangaRoot
|
applicationDirs.localMangaRoot
|
||||||
).forEach {
|
).forEach {
|
||||||
File(it).mkdirs()
|
File(it).mkdirs()
|
||||||
|
|||||||
@@ -74,8 +74,8 @@ open class ApplicationTest {
|
|||||||
applicationDirs.dataRoot,
|
applicationDirs.dataRoot,
|
||||||
applicationDirs.extensionsRoot,
|
applicationDirs.extensionsRoot,
|
||||||
applicationDirs.extensionsRoot + "/icon",
|
applicationDirs.extensionsRoot + "/icon",
|
||||||
applicationDirs.thumbnailsRoot,
|
applicationDirs.tempThumbnailCacheRoot,
|
||||||
applicationDirs.mangaDownloadsRoot,
|
applicationDirs.downloadsRoot,
|
||||||
applicationDirs.localMangaRoot
|
applicationDirs.localMangaRoot
|
||||||
).forEach {
|
).forEach {
|
||||||
File(it).mkdirs()
|
File(it).mkdirs()
|
||||||
|
|||||||
Reference in New Issue
Block a user