mirror of
https://github.com/null2264/yokai.git
synced 2025-06-21 10:44:42 +00:00
refactor: Use DB instead of file to store custom manga info
This commit is contained in:
parent
716cc1fac8
commit
87b8430089
16 changed files with 354 additions and 61 deletions
|
@ -17,3 +17,4 @@ Please backup your data before updating to this version.
|
||||||
|
|
||||||
## Other
|
## Other
|
||||||
- Migrate to SQLDelight
|
- Migrate to SQLDelight
|
||||||
|
- Custom manga info is now stored in the database
|
||||||
|
|
|
@ -2,6 +2,7 @@ package dev.yokai.core.di
|
||||||
|
|
||||||
import dev.yokai.domain.extension.repo.ExtensionRepoRepository
|
import dev.yokai.domain.extension.repo.ExtensionRepoRepository
|
||||||
import dev.yokai.data.extension.repo.ExtensionRepoRepositoryImpl
|
import dev.yokai.data.extension.repo.ExtensionRepoRepositoryImpl
|
||||||
|
import dev.yokai.data.library.custom.CustomMangaRepositoryImpl
|
||||||
import dev.yokai.domain.extension.interactor.TrustExtension
|
import dev.yokai.domain.extension.interactor.TrustExtension
|
||||||
import dev.yokai.domain.extension.repo.interactor.CreateExtensionRepo
|
import dev.yokai.domain.extension.repo.interactor.CreateExtensionRepo
|
||||||
import dev.yokai.domain.extension.repo.interactor.DeleteExtensionRepo
|
import dev.yokai.domain.extension.repo.interactor.DeleteExtensionRepo
|
||||||
|
@ -9,6 +10,10 @@ import dev.yokai.domain.extension.repo.interactor.GetExtensionRepo
|
||||||
import dev.yokai.domain.extension.repo.interactor.GetExtensionRepoCount
|
import dev.yokai.domain.extension.repo.interactor.GetExtensionRepoCount
|
||||||
import dev.yokai.domain.extension.repo.interactor.ReplaceExtensionRepo
|
import dev.yokai.domain.extension.repo.interactor.ReplaceExtensionRepo
|
||||||
import dev.yokai.domain.extension.repo.interactor.UpdateExtensionRepo
|
import dev.yokai.domain.extension.repo.interactor.UpdateExtensionRepo
|
||||||
|
import dev.yokai.domain.library.custom.CustomMangaRepository
|
||||||
|
import dev.yokai.domain.library.custom.interactor.CreateCustomManga
|
||||||
|
import dev.yokai.domain.library.custom.interactor.DeleteCustomManga
|
||||||
|
import dev.yokai.domain.library.custom.interactor.GetCustomManga
|
||||||
import uy.kohesive.injekt.api.InjektModule
|
import uy.kohesive.injekt.api.InjektModule
|
||||||
import uy.kohesive.injekt.api.InjektRegistrar
|
import uy.kohesive.injekt.api.InjektRegistrar
|
||||||
import uy.kohesive.injekt.api.addFactory
|
import uy.kohesive.injekt.api.addFactory
|
||||||
|
@ -17,6 +22,8 @@ import uy.kohesive.injekt.api.get
|
||||||
|
|
||||||
class DomainModule : InjektModule {
|
class DomainModule : InjektModule {
|
||||||
override fun InjektRegistrar.registerInjectables() {
|
override fun InjektRegistrar.registerInjectables() {
|
||||||
|
addFactory { TrustExtension(get(), get()) }
|
||||||
|
|
||||||
addSingletonFactory<ExtensionRepoRepository> { ExtensionRepoRepositoryImpl(get()) }
|
addSingletonFactory<ExtensionRepoRepository> { ExtensionRepoRepositoryImpl(get()) }
|
||||||
addFactory { CreateExtensionRepo(get()) }
|
addFactory { CreateExtensionRepo(get()) }
|
||||||
addFactory { DeleteExtensionRepo(get()) }
|
addFactory { DeleteExtensionRepo(get()) }
|
||||||
|
@ -25,6 +32,9 @@ class DomainModule : InjektModule {
|
||||||
addFactory { ReplaceExtensionRepo(get()) }
|
addFactory { ReplaceExtensionRepo(get()) }
|
||||||
addFactory { UpdateExtensionRepo(get(), get()) }
|
addFactory { UpdateExtensionRepo(get(), get()) }
|
||||||
|
|
||||||
addFactory { TrustExtension(get(), get()) }
|
addSingletonFactory<CustomMangaRepository> { CustomMangaRepositoryImpl(get()) }
|
||||||
|
addFactory { CreateCustomManga(get()) }
|
||||||
|
addFactory { DeleteCustomManga(get()) }
|
||||||
|
addFactory { GetCustomManga(get()) }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,75 @@
|
||||||
|
package dev.yokai.data.library.custom
|
||||||
|
|
||||||
|
import android.database.sqlite.SQLiteException
|
||||||
|
import dev.yokai.data.DatabaseHandler
|
||||||
|
import dev.yokai.domain.library.custom.CustomMangaRepository
|
||||||
|
import dev.yokai.domain.library.custom.exception.SaveCustomMangaException
|
||||||
|
import dev.yokai.domain.library.custom.model.CustomMangaInfo
|
||||||
|
import kotlinx.coroutines.flow.Flow
|
||||||
|
import timber.log.Timber
|
||||||
|
|
||||||
|
class CustomMangaRepositoryImpl(private val handler: DatabaseHandler) : CustomMangaRepository {
|
||||||
|
override fun subscribeAll(): Flow<List<CustomMangaInfo>> =
|
||||||
|
handler.subscribeToList { custom_manga_infoQueries.findAll(::mapCustomMangaInfo) }
|
||||||
|
|
||||||
|
override suspend fun getAll(): List<CustomMangaInfo> =
|
||||||
|
handler.awaitList { custom_manga_infoQueries.findAll(::mapCustomMangaInfo) }
|
||||||
|
|
||||||
|
override suspend fun insertCustomManga(
|
||||||
|
mangaId: Long,
|
||||||
|
title: String?,
|
||||||
|
author: String?,
|
||||||
|
artist: String?,
|
||||||
|
description: String?,
|
||||||
|
genre: String?,
|
||||||
|
status: Int?
|
||||||
|
) {
|
||||||
|
try {
|
||||||
|
handler.await { custom_manga_infoQueries.insert(mangaId, title, author, artist, description, genre, status?.toLong()) }
|
||||||
|
} catch (exc: SQLiteException) {
|
||||||
|
Timber.e(exc)
|
||||||
|
throw SaveCustomMangaException(exc)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun insertBulkCustomManga(mangaList: List<CustomMangaInfo>) {
|
||||||
|
try {
|
||||||
|
handler.await(true) {
|
||||||
|
for (customMangaInfo in mangaList) {
|
||||||
|
custom_manga_infoQueries.insert(
|
||||||
|
customMangaInfo.mangaId,
|
||||||
|
customMangaInfo.title,
|
||||||
|
customMangaInfo.author,
|
||||||
|
customMangaInfo.artist,
|
||||||
|
customMangaInfo.description,
|
||||||
|
customMangaInfo.genre,
|
||||||
|
customMangaInfo.status?.toLong(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (exc: SQLiteException) {
|
||||||
|
Timber.e(exc)
|
||||||
|
throw SaveCustomMangaException(exc)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun deleteCustomManga(mangaId: Long) =
|
||||||
|
handler.await { custom_manga_infoQueries.delete(mangaId) }
|
||||||
|
|
||||||
|
override suspend fun deleteBulkCustomManga(mangaIds: List<Long>) =
|
||||||
|
handler.await(true) {
|
||||||
|
for (mangaId in mangaIds) {
|
||||||
|
custom_manga_infoQueries.delete(mangaId)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun mapCustomMangaInfo(
|
||||||
|
mangaId: Long,
|
||||||
|
title: String?,
|
||||||
|
author: String?,
|
||||||
|
artist: String?,
|
||||||
|
description: String?,
|
||||||
|
genre: String?,
|
||||||
|
status: Long?
|
||||||
|
): CustomMangaInfo = CustomMangaInfo(mangaId, title, author, artist, description, genre, status?.toInt())
|
||||||
|
}
|
|
@ -0,0 +1,31 @@
|
||||||
|
package dev.yokai.domain.library.custom
|
||||||
|
|
||||||
|
import dev.yokai.domain.library.custom.model.CustomMangaInfo
|
||||||
|
import kotlinx.coroutines.flow.Flow
|
||||||
|
|
||||||
|
interface CustomMangaRepository {
|
||||||
|
fun subscribeAll(): Flow<List<CustomMangaInfo>>
|
||||||
|
suspend fun getAll(): List<CustomMangaInfo>
|
||||||
|
suspend fun insertCustomManga(
|
||||||
|
mangaId: Long,
|
||||||
|
title: String? = null,
|
||||||
|
author: String? = null,
|
||||||
|
artist: String? = null,
|
||||||
|
description: String? = null,
|
||||||
|
genre: String? = null,
|
||||||
|
status: Int? = null,
|
||||||
|
)
|
||||||
|
suspend fun insertCustomManga(mangaInfo: CustomMangaInfo) =
|
||||||
|
insertCustomManga(
|
||||||
|
mangaInfo.mangaId,
|
||||||
|
mangaInfo.title,
|
||||||
|
mangaInfo.author,
|
||||||
|
mangaInfo.artist,
|
||||||
|
mangaInfo.description,
|
||||||
|
mangaInfo.genre,
|
||||||
|
mangaInfo.status,
|
||||||
|
)
|
||||||
|
suspend fun insertBulkCustomManga(mangaList: List<CustomMangaInfo>)
|
||||||
|
suspend fun deleteCustomManga(mangaId: Long)
|
||||||
|
suspend fun deleteBulkCustomManga(mangaIds: List<Long>)
|
||||||
|
}
|
|
@ -0,0 +1,10 @@
|
||||||
|
package dev.yokai.domain.library.custom.exception
|
||||||
|
|
||||||
|
import java.io.IOException
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Exception to abstract over SQLiteException and SQLiteConstraintException for multiplatform.
|
||||||
|
*
|
||||||
|
* @param throwable the source throwable to include for tracing.
|
||||||
|
*/
|
||||||
|
class SaveCustomMangaException(throwable: Throwable) : IOException("Error Saving Custom Manga Info to Database", throwable)
|
|
@ -0,0 +1,32 @@
|
||||||
|
package dev.yokai.domain.library.custom.interactor
|
||||||
|
|
||||||
|
import dev.yokai.domain.library.custom.CustomMangaRepository
|
||||||
|
import dev.yokai.domain.library.custom.exception.SaveCustomMangaException
|
||||||
|
import dev.yokai.domain.library.custom.model.CustomMangaInfo
|
||||||
|
|
||||||
|
class CreateCustomManga(
|
||||||
|
private val customMangaRepository: CustomMangaRepository,
|
||||||
|
) {
|
||||||
|
suspend fun await(mangaInfo: CustomMangaInfo): Result {
|
||||||
|
try {
|
||||||
|
customMangaRepository.insertCustomManga(mangaInfo)
|
||||||
|
return Result.Success
|
||||||
|
} catch (exc: SaveCustomMangaException) {
|
||||||
|
return Result.Error
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun bulk(mangaList: List<CustomMangaInfo>): Result {
|
||||||
|
try {
|
||||||
|
customMangaRepository.insertBulkCustomManga(mangaList)
|
||||||
|
return Result.Success
|
||||||
|
} catch (exc: SaveCustomMangaException) {
|
||||||
|
return Result.Error
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
sealed interface Result {
|
||||||
|
data object Success : Result
|
||||||
|
data object Error : Result
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,10 @@
|
||||||
|
package dev.yokai.domain.library.custom.interactor
|
||||||
|
|
||||||
|
import dev.yokai.domain.library.custom.CustomMangaRepository
|
||||||
|
|
||||||
|
class DeleteCustomManga(
|
||||||
|
private val customMangaRepository: CustomMangaRepository,
|
||||||
|
) {
|
||||||
|
suspend fun await(mangaId: Long) = customMangaRepository.deleteCustomManga(mangaId)
|
||||||
|
suspend fun bulk(mangaIds: List<Long>) = customMangaRepository.deleteBulkCustomManga(mangaIds)
|
||||||
|
}
|
|
@ -0,0 +1,11 @@
|
||||||
|
package dev.yokai.domain.library.custom.interactor
|
||||||
|
|
||||||
|
import dev.yokai.domain.library.custom.CustomMangaRepository
|
||||||
|
|
||||||
|
class GetCustomManga(
|
||||||
|
private val customMangaRepository: CustomMangaRepository,
|
||||||
|
) {
|
||||||
|
fun subscribeAll() = customMangaRepository.subscribeAll()
|
||||||
|
|
||||||
|
suspend fun getAll() = customMangaRepository.getAll()
|
||||||
|
}
|
|
@ -0,0 +1,37 @@
|
||||||
|
package dev.yokai.domain.library.custom.model
|
||||||
|
|
||||||
|
import eu.kanade.tachiyomi.data.database.models.Manga
|
||||||
|
import eu.kanade.tachiyomi.data.database.models.MangaImpl
|
||||||
|
|
||||||
|
data class CustomMangaInfo(
|
||||||
|
var mangaId: Long,
|
||||||
|
val title: String? = null,
|
||||||
|
val author: String? = null,
|
||||||
|
val artist: String? = null,
|
||||||
|
val description: String? = null,
|
||||||
|
val genre: String? = null,
|
||||||
|
val status: Int? = null,
|
||||||
|
) {
|
||||||
|
fun toManga() = MangaImpl().apply {
|
||||||
|
id = this@CustomMangaInfo.mangaId
|
||||||
|
title = this@CustomMangaInfo.title ?: ""
|
||||||
|
author = this@CustomMangaInfo.author
|
||||||
|
artist = this@CustomMangaInfo.artist
|
||||||
|
description = this@CustomMangaInfo.description
|
||||||
|
genre = this@CustomMangaInfo.genre
|
||||||
|
status = this@CustomMangaInfo.status ?: -1
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
fun Manga.getMangaInfo() =
|
||||||
|
CustomMangaInfo(
|
||||||
|
mangaId = id!!,
|
||||||
|
title = title,
|
||||||
|
author = author,
|
||||||
|
artist = artist,
|
||||||
|
description = description,
|
||||||
|
genre = genre,
|
||||||
|
status = status,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
|
@ -2,6 +2,7 @@ package eu.kanade.tachiyomi.data.backup
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
|
import dev.yokai.domain.library.custom.model.CustomMangaInfo
|
||||||
import dev.yokai.domain.ui.settings.ReaderPreferences
|
import dev.yokai.domain.ui.settings.ReaderPreferences
|
||||||
import dev.yokai.domain.ui.settings.ReaderPreferences.CutoutBehaviour
|
import dev.yokai.domain.ui.settings.ReaderPreferences.CutoutBehaviour
|
||||||
import eu.kanade.tachiyomi.R
|
import eu.kanade.tachiyomi.R
|
||||||
|
@ -37,6 +38,7 @@ import eu.kanade.tachiyomi.util.BackupUtil
|
||||||
import eu.kanade.tachiyomi.util.chapter.ChapterUtil
|
import eu.kanade.tachiyomi.util.chapter.ChapterUtil
|
||||||
import eu.kanade.tachiyomi.util.manga.MangaUtil
|
import eu.kanade.tachiyomi.util.manga.MangaUtil
|
||||||
import eu.kanade.tachiyomi.util.system.createFileInCacheDir
|
import eu.kanade.tachiyomi.util.system.createFileInCacheDir
|
||||||
|
import eu.kanade.tachiyomi.util.system.launchNow
|
||||||
import kotlinx.coroutines.coroutineScope
|
import kotlinx.coroutines.coroutineScope
|
||||||
import kotlinx.coroutines.isActive
|
import kotlinx.coroutines.isActive
|
||||||
import kotlinx.serialization.protobuf.ProtoBuf
|
import kotlinx.serialization.protobuf.ProtoBuf
|
||||||
|
@ -198,7 +200,7 @@ class BackupRestorer(val context: Context, val notifier: BackupNotifier) {
|
||||||
tracks: List<Track>,
|
tracks: List<Track>,
|
||||||
backupCategories: List<BackupCategory>,
|
backupCategories: List<BackupCategory>,
|
||||||
filteredScanlators: List<String>,
|
filteredScanlators: List<String>,
|
||||||
customManga: CustomMangaManager.ComicList.ComicInfoYokai?,
|
customManga: CustomMangaInfo?,
|
||||||
) {
|
) {
|
||||||
val fetchedManga = manga.also {
|
val fetchedManga = manga.also {
|
||||||
it.initialized = it.description != null
|
it.initialized = it.description != null
|
||||||
|
@ -218,7 +220,7 @@ class BackupRestorer(val context: Context, val notifier: BackupNotifier) {
|
||||||
tracks: List<Track>,
|
tracks: List<Track>,
|
||||||
backupCategories: List<BackupCategory>,
|
backupCategories: List<BackupCategory>,
|
||||||
filteredScanlators: List<String>,
|
filteredScanlators: List<String>,
|
||||||
customManga: CustomMangaManager.ComicList.ComicInfoYokai?,
|
customManga: CustomMangaInfo?,
|
||||||
) {
|
) {
|
||||||
restoreChapters(backupManga, chapters)
|
restoreChapters(backupManga, chapters)
|
||||||
restoreExtras(backupManga, categories, history, tracks, backupCategories, filteredScanlators, customManga)
|
restoreExtras(backupManga, categories, history, tracks, backupCategories, filteredScanlators, customManga)
|
||||||
|
@ -258,14 +260,18 @@ class BackupRestorer(val context: Context, val notifier: BackupNotifier) {
|
||||||
tracks: List<Track>,
|
tracks: List<Track>,
|
||||||
backupCategories: List<BackupCategory>,
|
backupCategories: List<BackupCategory>,
|
||||||
filteredScanlators: List<String>,
|
filteredScanlators: List<String>,
|
||||||
customManga: CustomMangaManager.ComicList.ComicInfoYokai?,
|
customManga: CustomMangaInfo?,
|
||||||
) {
|
) {
|
||||||
restoreCategories(manga, categories, backupCategories)
|
restoreCategories(manga, categories, backupCategories)
|
||||||
restoreHistoryForManga(history)
|
restoreHistoryForManga(history)
|
||||||
restoreTrackForManga(manga, tracks)
|
restoreTrackForManga(manga, tracks)
|
||||||
restoreFilteredScanlatorsForManga(manga, filteredScanlators)
|
restoreFilteredScanlatorsForManga(manga, filteredScanlators)
|
||||||
customManga?.id = manga.id!!
|
customManga?.let {
|
||||||
customManga?.let { customMangaManager.saveMangaInfo(it) }
|
it.mangaId = manga.id!!
|
||||||
|
launchNow {
|
||||||
|
customMangaManager.saveMangaInfo(it)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -2,6 +2,7 @@ package eu.kanade.tachiyomi.data.backup.models
|
||||||
|
|
||||||
import dev.yokai.core.metadata.ComicInfo
|
import dev.yokai.core.metadata.ComicInfo
|
||||||
import dev.yokai.core.metadata.ComicInfoPublishingStatus
|
import dev.yokai.core.metadata.ComicInfoPublishingStatus
|
||||||
|
import dev.yokai.domain.library.custom.model.CustomMangaInfo
|
||||||
import eu.kanade.tachiyomi.data.database.models.ChapterImpl
|
import eu.kanade.tachiyomi.data.database.models.ChapterImpl
|
||||||
import eu.kanade.tachiyomi.data.database.models.Manga
|
import eu.kanade.tachiyomi.data.database.models.Manga
|
||||||
import eu.kanade.tachiyomi.data.database.models.MangaImpl
|
import eu.kanade.tachiyomi.data.database.models.MangaImpl
|
||||||
|
@ -85,7 +86,7 @@ data class BackupManga(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getCustomMangaInfo(): CustomMangaManager.ComicList.ComicInfoYokai? {
|
fun getCustomMangaInfo(): CustomMangaInfo? {
|
||||||
if (customTitle != null ||
|
if (customTitle != null ||
|
||||||
customArtist != null ||
|
customArtist != null ||
|
||||||
customAuthor != null ||
|
customAuthor != null ||
|
||||||
|
@ -93,13 +94,13 @@ data class BackupManga(
|
||||||
customGenre != null ||
|
customGenre != null ||
|
||||||
customStatus != 0
|
customStatus != 0
|
||||||
) {
|
) {
|
||||||
return CustomMangaManager.ComicList.ComicInfoYokai.create(
|
return CustomMangaInfo(
|
||||||
id = 0L,
|
mangaId= 0L,
|
||||||
title = customTitle,
|
title = customTitle,
|
||||||
author = customAuthor,
|
author = customAuthor,
|
||||||
artist = customArtist,
|
artist = customArtist,
|
||||||
description = customDescription,
|
description = customDescription,
|
||||||
genre = customGenre?.toTypedArray(),
|
genre = customGenre?.joinToString(),
|
||||||
status = customStatus,
|
status = customStatus,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,9 +6,16 @@ import dev.yokai.core.metadata.COMIC_INFO_EDITS_FILE
|
||||||
import dev.yokai.core.metadata.ComicInfo
|
import dev.yokai.core.metadata.ComicInfo
|
||||||
import dev.yokai.core.metadata.ComicInfoPublishingStatus
|
import dev.yokai.core.metadata.ComicInfoPublishingStatus
|
||||||
import dev.yokai.core.metadata.copyFromComicInfo
|
import dev.yokai.core.metadata.copyFromComicInfo
|
||||||
|
import dev.yokai.domain.library.custom.interactor.CreateCustomManga
|
||||||
|
import dev.yokai.domain.library.custom.interactor.DeleteCustomManga
|
||||||
|
import dev.yokai.domain.library.custom.interactor.GetCustomManga
|
||||||
|
import dev.yokai.domain.library.custom.model.CustomMangaInfo
|
||||||
|
import dev.yokai.domain.library.custom.model.CustomMangaInfo.Companion.getMangaInfo
|
||||||
import eu.kanade.tachiyomi.data.database.models.Manga
|
import eu.kanade.tachiyomi.data.database.models.Manga
|
||||||
import eu.kanade.tachiyomi.data.database.models.MangaImpl
|
import eu.kanade.tachiyomi.data.database.models.MangaImpl
|
||||||
import eu.kanade.tachiyomi.util.system.writeText
|
import kotlinx.coroutines.CoroutineScope
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
import kotlinx.serialization.Serializable
|
import kotlinx.serialization.Serializable
|
||||||
import kotlinx.serialization.json.Json
|
import kotlinx.serialization.json.Json
|
||||||
import kotlinx.serialization.json.decodeFromStream
|
import kotlinx.serialization.json.decodeFromStream
|
||||||
|
@ -17,44 +24,46 @@ import nl.adaptivity.xmlutil.serialization.XML
|
||||||
import nl.adaptivity.xmlutil.serialization.XmlSerialName
|
import nl.adaptivity.xmlutil.serialization.XmlSerialName
|
||||||
import nl.adaptivity.xmlutil.serialization.XmlValue
|
import nl.adaptivity.xmlutil.serialization.XmlValue
|
||||||
import uy.kohesive.injekt.injectLazy
|
import uy.kohesive.injekt.injectLazy
|
||||||
import java.io.InputStream
|
|
||||||
import java.nio.charset.StandardCharsets
|
import java.nio.charset.StandardCharsets
|
||||||
|
|
||||||
class CustomMangaManager(val context: Context) {
|
class CustomMangaManager(val context: Context) {
|
||||||
|
private val scope = CoroutineScope(Dispatchers.IO)
|
||||||
|
|
||||||
private val xml: XML by injectLazy()
|
private val xml: XML by injectLazy()
|
||||||
|
|
||||||
private val externalDir = UniFile.fromFile(context.getExternalFilesDir(null))
|
private val externalDir = UniFile.fromFile(context.getExternalFilesDir(null))
|
||||||
|
|
||||||
|
private var removedCustomManga = mutableListOf<Long>()
|
||||||
private var customMangaMap = mutableMapOf<Long, Manga>()
|
private var customMangaMap = mutableMapOf<Long, Manga>()
|
||||||
|
|
||||||
|
private val createCustomManga: CreateCustomManga by injectLazy()
|
||||||
|
private val deleteCustomManga: DeleteCustomManga by injectLazy()
|
||||||
|
private val getCustomManga: GetCustomManga by injectLazy()
|
||||||
|
|
||||||
init {
|
init {
|
||||||
fetchCustomData()
|
scope.launch {
|
||||||
|
fetchCustomData()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
const val EDIT_JSON_FILE = "edits.json"
|
const val EDIT_JSON_FILE = "edits.json"
|
||||||
|
|
||||||
fun Manga.toComicInfo(): ComicList.ComicInfoYokai {
|
|
||||||
return ComicList.ComicInfoYokai.create(
|
|
||||||
id = id!!,
|
|
||||||
title = title,
|
|
||||||
author = author,
|
|
||||||
artist = artist,
|
|
||||||
description = description,
|
|
||||||
genre = genre.orEmpty(),
|
|
||||||
status = status,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getManga(manga: Manga): Manga? = customMangaMap[manga.id]
|
fun getManga(manga: Manga): Manga? = customMangaMap[manga.id]
|
||||||
|
|
||||||
private fun fetchCustomData() {
|
private suspend fun fetchCustomData() {
|
||||||
val comicInfoEdits = externalDir?.findFile(COMIC_INFO_EDITS_FILE)
|
val comicInfoEdits = externalDir?.findFile(COMIC_INFO_EDITS_FILE)
|
||||||
val editJson = externalDir?.findFile(EDIT_JSON_FILE)
|
val editJson = externalDir?.findFile(EDIT_JSON_FILE)
|
||||||
|
|
||||||
|
val dbMangaInfo = getCustomManga.getAll()
|
||||||
|
customMangaMap = dbMangaInfo.associate { info ->
|
||||||
|
val id = info.mangaId
|
||||||
|
id to info.toManga()
|
||||||
|
}.toMutableMap()
|
||||||
|
|
||||||
if (comicInfoEdits != null && comicInfoEdits.exists() && comicInfoEdits.isFile) {
|
if (comicInfoEdits != null && comicInfoEdits.exists() && comicInfoEdits.isFile) {
|
||||||
fetchFromComicInfo(comicInfoEdits.openInputStream())
|
fetchFromComicInfo(comicInfoEdits)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -65,20 +74,22 @@ class CustomMangaManager(val context: Context) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun fetchFromComicInfo(stream: InputStream) {
|
private suspend fun fetchFromComicInfo(comicInfoFile: UniFile) {
|
||||||
val comicInfoEdits = AndroidXmlReader(stream, StandardCharsets.UTF_8.name()).use {
|
val comicInfoEdits = AndroidXmlReader(comicInfoFile.openInputStream(), StandardCharsets.UTF_8.name()).use {
|
||||||
xml.decodeFromReader<ComicList>(it)
|
xml.decodeFromReader<ComicList>(it)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (comicInfoEdits.comics == null) return
|
if (comicInfoEdits.comics == null) return
|
||||||
|
|
||||||
customMangaMap = comicInfoEdits.comics.mapNotNull { obj ->
|
comicInfoEdits.comics.mapNotNull { obj ->
|
||||||
val id = obj.id ?: return@mapNotNull null
|
val id = obj.id ?: return@mapNotNull null
|
||||||
id to mangaFromComicInfoObject(id, obj.value)
|
customMangaMap[id] = mangaFromComicInfoObject(id, obj.value)
|
||||||
}.toMap().toMutableMap()
|
}
|
||||||
|
|
||||||
|
saveCustomInfo { comicInfoFile.delete() }
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun fetchFromLegacyJson(jsonFile: UniFile) {
|
private suspend fun fetchFromLegacyJson(jsonFile: UniFile) {
|
||||||
val json = try {
|
val json = try {
|
||||||
Json.decodeFromStream<MangaList>(jsonFile.openInputStream())
|
Json.decodeFromStream<MangaList>(jsonFile.openInputStream())
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
|
@ -86,36 +97,41 @@ class CustomMangaManager(val context: Context) {
|
||||||
} ?: return
|
} ?: return
|
||||||
|
|
||||||
val mangasJson = json.mangas ?: return
|
val mangasJson = json.mangas ?: return
|
||||||
customMangaMap = mangasJson.mapNotNull { mangaObject ->
|
mangasJson.mapNotNull { mangaObject ->
|
||||||
val id = mangaObject.id ?: return@mapNotNull null
|
val id = mangaObject.id ?: return@mapNotNull null
|
||||||
id to mangaObject.toManga()
|
customMangaMap[id] = mangaObject.toManga()
|
||||||
}.toMap().toMutableMap()
|
}
|
||||||
|
|
||||||
saveCustomInfo { jsonFile.delete() }
|
saveCustomInfo { jsonFile.delete() }
|
||||||
}
|
}
|
||||||
|
|
||||||
fun saveMangaInfo(manga: ComicList.ComicInfoYokai) {
|
suspend fun saveMangaInfo(manga: CustomMangaInfo) {
|
||||||
val mangaId = manga.id ?: return
|
val mangaId = manga.mangaId ?: return
|
||||||
if (manga.value.series == null &&
|
if (manga.title == null &&
|
||||||
manga.value.writer == null &&
|
manga.author == null &&
|
||||||
manga.value.penciller == null &&
|
manga.artist == null &&
|
||||||
manga.value.summary == null &&
|
manga.description == null &&
|
||||||
manga.value.genre == null &&
|
manga.genre == null &&
|
||||||
(manga.value.publishingStatus?.value ?: "Invalid") == "Invalid"
|
(manga.status ?: -1) == -1
|
||||||
) {
|
) {
|
||||||
customMangaMap.remove(mangaId)
|
customMangaMap[mangaId]?.let {
|
||||||
|
removedCustomManga.add(mangaId)
|
||||||
|
customMangaMap.remove(mangaId)
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
customMangaMap[mangaId] = mangaFromComicInfoObject(mangaId, manga.value)
|
customMangaMap[mangaId] = manga.toManga()
|
||||||
}
|
}
|
||||||
saveCustomInfo()
|
saveCustomInfo()
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun saveCustomInfo(onComplete: () -> Unit = {}) {
|
private suspend fun saveCustomInfo(onComplete: () -> Unit = {}) {
|
||||||
val comicInfoEdits = externalDir?.createFile(COMIC_INFO_EDITS_FILE) ?: return
|
deleteCustomManga.bulk(removedCustomManga)
|
||||||
|
removedCustomManga = mutableListOf()
|
||||||
|
|
||||||
val edits = customMangaMap.values.map { it.toComicInfo() }
|
val edits = customMangaMap.values.map { it.getMangaInfo() }
|
||||||
if (edits.isNotEmpty() && comicInfoEdits.exists()) {
|
if (edits.isNotEmpty()) {
|
||||||
comicInfoEdits.writeText(xml.encodeToString(ComicList.serializer(), ComicList(edits)), onComplete = onComplete)
|
createCustomManga.bulk(edits)
|
||||||
|
onComplete()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -10,6 +10,7 @@ import coil3.request.CachePolicy
|
||||||
import coil3.request.ImageRequest
|
import coil3.request.ImageRequest
|
||||||
import coil3.request.SuccessResult
|
import coil3.request.SuccessResult
|
||||||
import com.hippo.unifile.UniFile
|
import com.hippo.unifile.UniFile
|
||||||
|
import dev.yokai.domain.library.custom.model.CustomMangaInfo
|
||||||
import dev.yokai.domain.storage.StorageManager
|
import dev.yokai.domain.storage.StorageManager
|
||||||
import eu.kanade.tachiyomi.BuildConfig
|
import eu.kanade.tachiyomi.BuildConfig
|
||||||
import eu.kanade.tachiyomi.R
|
import eu.kanade.tachiyomi.R
|
||||||
|
@ -55,6 +56,7 @@ import eu.kanade.tachiyomi.util.storage.DiskUtil
|
||||||
import eu.kanade.tachiyomi.util.system.ImageUtil
|
import eu.kanade.tachiyomi.util.system.ImageUtil
|
||||||
import eu.kanade.tachiyomi.util.system.executeOnIO
|
import eu.kanade.tachiyomi.util.system.executeOnIO
|
||||||
import eu.kanade.tachiyomi.util.system.launchIO
|
import eu.kanade.tachiyomi.util.system.launchIO
|
||||||
|
import eu.kanade.tachiyomi.util.system.launchNow
|
||||||
import eu.kanade.tachiyomi.util.system.launchUI
|
import eu.kanade.tachiyomi.util.system.launchUI
|
||||||
import eu.kanade.tachiyomi.util.system.withUIContext
|
import eu.kanade.tachiyomi.util.system.withUIContext
|
||||||
import eu.kanade.tachiyomi.widget.TriStateCheckBox
|
import eu.kanade.tachiyomi.widget.TriStateCheckBox
|
||||||
|
@ -712,13 +714,13 @@ class MangaDetailsPresenter(
|
||||||
fun confirmDeletion() {
|
fun confirmDeletion() {
|
||||||
launchIO {
|
launchIO {
|
||||||
coverCache.deleteFromCache(manga)
|
coverCache.deleteFromCache(manga)
|
||||||
customMangaManager.saveMangaInfo(CustomMangaManager.ComicList.ComicInfoYokai.create(
|
customMangaManager.saveMangaInfo(CustomMangaInfo(
|
||||||
id = manga.id!!,
|
mangaId = manga.id!!,
|
||||||
title = null,
|
title = null,
|
||||||
author = null,
|
author = null,
|
||||||
artist = null,
|
artist = null,
|
||||||
description = null,
|
description = null,
|
||||||
genre = null as String?,
|
genre = null,
|
||||||
status = null,
|
status = null,
|
||||||
))
|
))
|
||||||
downloadManager.deleteManga(manga, source)
|
downloadManager.deleteManga(manga, source)
|
||||||
|
@ -801,20 +803,22 @@ class MangaDetailsPresenter(
|
||||||
null
|
null
|
||||||
}
|
}
|
||||||
if (seriesType != null) {
|
if (seriesType != null) {
|
||||||
genre = setSeriesType(seriesType, genre?.joinToString(", "))
|
genre = setSeriesType(seriesType, genre?.joinToString())
|
||||||
manga.viewer_flags = -1
|
manga.viewer_flags = -1
|
||||||
db.updateViewerFlags(manga).executeAsBlocking()
|
db.updateViewerFlags(manga).executeAsBlocking()
|
||||||
}
|
}
|
||||||
val manga = CustomMangaManager.ComicList.ComicInfoYokai.create(
|
val manga = CustomMangaInfo(
|
||||||
id = manga.id!!,
|
mangaId = manga.id!!,
|
||||||
title?.trimOrNull(),
|
title?.trimOrNull(),
|
||||||
author?.trimOrNull(),
|
author?.trimOrNull(),
|
||||||
artist?.trimOrNull(),
|
artist?.trimOrNull(),
|
||||||
description?.trimOrNull(),
|
description?.trimOrNull(),
|
||||||
genre,
|
genre?.joinToString(),
|
||||||
if (status != this.manga.originalStatus) status else null,
|
if (status != this.manga.originalStatus) status else null,
|
||||||
)
|
)
|
||||||
customMangaManager.saveMangaInfo(manga)
|
launchNow {
|
||||||
|
customMangaManager.saveMangaInfo(manga)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (uri != null) {
|
if (uri != null) {
|
||||||
editCoverWithStream(uri)
|
editCoverWithStream(uri)
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
package eu.kanade.tachiyomi.ui.migration.manga.process
|
package eu.kanade.tachiyomi.ui.migration.manga.process
|
||||||
|
|
||||||
import android.view.MenuItem
|
import android.view.MenuItem
|
||||||
|
import dev.yokai.domain.library.custom.model.CustomMangaInfo.Companion.getMangaInfo
|
||||||
import dev.yokai.domain.ui.UiPreferences
|
import dev.yokai.domain.ui.UiPreferences
|
||||||
import eu.davidea.flexibleadapter.FlexibleAdapter
|
import eu.davidea.flexibleadapter.FlexibleAdapter
|
||||||
import eu.kanade.tachiyomi.data.cache.CoverCache
|
import eu.kanade.tachiyomi.data.cache.CoverCache
|
||||||
|
@ -9,13 +10,13 @@ import eu.kanade.tachiyomi.data.database.models.History
|
||||||
import eu.kanade.tachiyomi.data.database.models.Manga
|
import eu.kanade.tachiyomi.data.database.models.Manga
|
||||||
import eu.kanade.tachiyomi.data.database.models.MangaCategory
|
import eu.kanade.tachiyomi.data.database.models.MangaCategory
|
||||||
import eu.kanade.tachiyomi.data.library.CustomMangaManager
|
import eu.kanade.tachiyomi.data.library.CustomMangaManager
|
||||||
import eu.kanade.tachiyomi.data.library.CustomMangaManager.Companion.toComicInfo
|
|
||||||
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
|
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
|
||||||
import eu.kanade.tachiyomi.data.track.EnhancedTrackService
|
import eu.kanade.tachiyomi.data.track.EnhancedTrackService
|
||||||
import eu.kanade.tachiyomi.data.track.TrackManager
|
import eu.kanade.tachiyomi.data.track.TrackManager
|
||||||
import eu.kanade.tachiyomi.source.Source
|
import eu.kanade.tachiyomi.source.Source
|
||||||
import eu.kanade.tachiyomi.source.SourceManager
|
import eu.kanade.tachiyomi.source.SourceManager
|
||||||
import eu.kanade.tachiyomi.ui.migration.MigrationFlags
|
import eu.kanade.tachiyomi.ui.migration.MigrationFlags
|
||||||
|
import eu.kanade.tachiyomi.util.system.launchNow
|
||||||
import eu.kanade.tachiyomi.util.system.launchUI
|
import eu.kanade.tachiyomi.util.system.launchUI
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.cancel
|
import kotlinx.coroutines.cancel
|
||||||
|
@ -229,7 +230,9 @@ class MigrationProcessAdapter(
|
||||||
}
|
}
|
||||||
customMangaManager.getManga(prevManga)?.let { customManga ->
|
customMangaManager.getManga(prevManga)?.let { customManga ->
|
||||||
customManga.id = manga.id!!
|
customManga.id = manga.id!!
|
||||||
customMangaManager.saveMangaInfo(customManga.toComicInfo())
|
launchNow {
|
||||||
|
customMangaManager.saveMangaInfo(customManga.getMangaInfo())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
34
app/src/main/sqldelight/tachiyomi/data/custom_manga_info.sq
Normal file
34
app/src/main/sqldelight/tachiyomi/data/custom_manga_info.sq
Normal file
|
@ -0,0 +1,34 @@
|
||||||
|
CREATE TABLE custom_manga_info (
|
||||||
|
manga_id INTEGER NOT NULL PRIMARY KEY,
|
||||||
|
title TEXT,
|
||||||
|
author TEXT,
|
||||||
|
artist TEXT,
|
||||||
|
description TEXT,
|
||||||
|
genre TEXT,
|
||||||
|
status INTEGER,
|
||||||
|
UNIQUE (manga_id) ON CONFLICT REPLACE,
|
||||||
|
FOREIGN KEY(manga_id) REFERENCES mangas (_id)
|
||||||
|
ON DELETE CASCADE
|
||||||
|
);
|
||||||
|
|
||||||
|
findAll:
|
||||||
|
SELECT *
|
||||||
|
FROM custom_manga_info;
|
||||||
|
|
||||||
|
insert:
|
||||||
|
INSERT INTO custom_manga_info(manga_id, title, author, artist, description, genre, status)
|
||||||
|
VALUES (:manga_id, :title, :author, :artist, :description, :genre, :status)
|
||||||
|
ON CONFLICT (manga_id)
|
||||||
|
DO UPDATE
|
||||||
|
SET
|
||||||
|
title = :title,
|
||||||
|
author = :author,
|
||||||
|
artist = :artist,
|
||||||
|
description = :description,
|
||||||
|
genre = :genre,
|
||||||
|
status = :status
|
||||||
|
WHERE manga_id = :manga_id;
|
||||||
|
|
||||||
|
delete:
|
||||||
|
DELETE FROM custom_manga_info
|
||||||
|
WHERE manga_id = :manga_id;
|
12
app/src/main/sqldelight/tachiyomi/migrations/19.sqm
Normal file
12
app/src/main/sqldelight/tachiyomi/migrations/19.sqm
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
CREATE TABLE custom_manga_info (
|
||||||
|
manga_id INTEGER NOT NULL PRIMARY KEY,
|
||||||
|
title TEXT,
|
||||||
|
author TEXT,
|
||||||
|
artist TEXT,
|
||||||
|
description TEXT,
|
||||||
|
genre TEXT,
|
||||||
|
status INTEGER,
|
||||||
|
UNIQUE (manga_id) ON CONFLICT REPLACE,
|
||||||
|
FOREIGN KEY(manga_id) REFERENCES mangas (_id)
|
||||||
|
ON DELETE CASCADE
|
||||||
|
);
|
Loading…
Add table
Add a link
Reference in a new issue