Migrate things to use newer data models (#9239)

* Remove old database models from Coil

* Remove old database models from TrackInfoDialogHome

* Remove old database models from Backup Manager

(cherry picked from commit dfdb688b437c38954b1072b7a1df15921a437868)

# Conflicts:
#	app/src/main/java/eu/kanade/tachiyomi/data/backup/BackupManager.kt
#	app/src/main/java/eu/kanade/tachiyomi/data/backup/BackupRestorer.kt
#	app/src/main/java/eu/kanade/tachiyomi/data/backup/models/BackupManga.kt
#	app/src/main/java/eu/kanade/tachiyomi/data/database/models/Manga.kt
#	app/src/main/java/eu/kanade/tachiyomi/data/database/models/MangaImpl.kt
#	app/src/main/java/eu/kanade/tachiyomi/source/model/SMangaExtensions.kt
#	app/src/main/java/eu/kanade/tachiyomi/ui/manga/MangaScreenModel.kt
This commit is contained in:
Andreas 2023-03-19 18:11:58 +01:00 committed by Jobobby04
parent 95f770b39e
commit 0f3bb9f7d7
20 changed files with 276 additions and 482 deletions

View File

@ -2,6 +2,7 @@ package eu.kanade.domain.chapter.model
import eu.kanade.tachiyomi.data.database.models.ChapterImpl import eu.kanade.tachiyomi.data.database.models.ChapterImpl
import eu.kanade.tachiyomi.source.model.SChapter import eu.kanade.tachiyomi.source.model.SChapter
import tachiyomi.data.Chapters
import tachiyomi.domain.chapter.model.Chapter import tachiyomi.domain.chapter.model.Chapter
import eu.kanade.tachiyomi.data.database.models.Chapter as DbChapter import eu.kanade.tachiyomi.data.database.models.Chapter as DbChapter
@ -26,6 +27,16 @@ fun Chapter.copyFromSChapter(sChapter: SChapter): Chapter {
) )
} }
fun Chapter.copyFrom(other: Chapters): Chapter {
return copy(
name = other.name,
url = other.url,
dateUpload = other.date_upload,
chapterNumber = other.chapter_number,
scanlator = other.scanlator?.ifBlank { null },
)
}
fun Chapter.toDbChapter(): DbChapter = ChapterImpl().also { fun Chapter.toDbChapter(): DbChapter = ChapterImpl().also {
it.id = id it.id = id
it.manga_id = mangaId it.manga_id = mangaId

View File

@ -43,6 +43,7 @@ import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import eu.kanade.domain.track.model.toDbTrack
import eu.kanade.presentation.components.DropdownMenu import eu.kanade.presentation.components.DropdownMenu
import eu.kanade.presentation.track.components.TrackLogoIcon import eu.kanade.presentation.track.components.TrackLogoIcon
import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.R
@ -84,10 +85,10 @@ fun TrackInfoDialogHome(
TrackInfoItem( TrackInfoItem(
title = item.track.title, title = item.track.title,
service = item.service, service = item.service,
status = item.service.getStatus(item.track.status), status = item.service.getStatus(item.track.status.toInt()),
onStatusClick = { onStatusClick(item) }, onStatusClick = { onStatusClick(item) },
chapters = "${item.track.last_chapter_read.toInt()}".let { chapters = "${item.track.lastChapterRead.toInt()}".let {
val totalChapters = item.track.total_chapters val totalChapters = item.track.totalChapters
if (totalChapters > 0) { if (totalChapters > 0) {
// Add known total chapter count // Add known total chapter count
"$it / $totalChapters" "$it / $totalChapters"
@ -96,16 +97,16 @@ fun TrackInfoDialogHome(
} }
}, },
onChaptersClick = { onChapterClick(item) }, onChaptersClick = { onChapterClick(item) },
score = item.service.displayScore(item.track) score = item.service.displayScore(item.track.toDbTrack())
.takeIf { supportsScoring && item.track.score != 0F }, .takeIf { supportsScoring && item.track.score != 0F },
onScoreClick = { onScoreClick(item) } onScoreClick = { onScoreClick(item) }
.takeIf { supportsScoring }, .takeIf { supportsScoring },
startDate = remember(item.track.started_reading_date) { dateFormat.format(item.track.started_reading_date) } startDate = remember(item.track.startDate) { dateFormat.format(item.track.startDate) }
.takeIf { supportsReadingDates && item.track.started_reading_date != 0L }, .takeIf { supportsReadingDates && item.track.startDate != 0L },
onStartDateClick = { onStartDateEdit(item) } // TODO onStartDateClick = { onStartDateEdit(item) } // TODO
.takeIf { supportsReadingDates }, .takeIf { supportsReadingDates },
endDate = dateFormat.format(item.track.finished_reading_date) endDate = dateFormat.format(item.track.finishDate)
.takeIf { supportsReadingDates && item.track.finished_reading_date != 0L }, .takeIf { supportsReadingDates && item.track.finishDate != 0L },
onEndDateClick = { onEndDateEdit(item) } onEndDateClick = { onEndDateEdit(item) }
.takeIf { supportsReadingDates }, .takeIf { supportsReadingDates },
onNewSearch = { onNewSearch(item) }, onNewSearch = { onNewSearch(item) },

View File

@ -40,7 +40,6 @@ import eu.kanade.domain.ui.UiPreferences
import eu.kanade.domain.ui.model.setAppCompatDelegateThemeMode import eu.kanade.domain.ui.model.setAppCompatDelegateThemeMode
import eu.kanade.tachiyomi.crash.CrashActivity import eu.kanade.tachiyomi.crash.CrashActivity
import eu.kanade.tachiyomi.crash.GlobalExceptionHandler import eu.kanade.tachiyomi.crash.GlobalExceptionHandler
import eu.kanade.tachiyomi.data.coil.DomainMangaKeyer
import eu.kanade.tachiyomi.data.coil.MangaCoverFetcher import eu.kanade.tachiyomi.data.coil.MangaCoverFetcher
import eu.kanade.tachiyomi.data.coil.MangaCoverKeyer import eu.kanade.tachiyomi.data.coil.MangaCoverKeyer
import eu.kanade.tachiyomi.data.coil.MangaKeyer import eu.kanade.tachiyomi.data.coil.MangaKeyer
@ -169,11 +168,9 @@ class App : Application(), DefaultLifecycleObserver, ImageLoaderFactory {
add(GifDecoder.Factory()) add(GifDecoder.Factory())
} }
add(TachiyomiImageDecoder.Factory()) add(TachiyomiImageDecoder.Factory())
add(MangaCoverFetcher.Factory(lazy(callFactoryInit), lazy(diskCacheInit))) add(MangaCoverFetcher.MangaFactory(lazy(callFactoryInit), lazy(diskCacheInit)))
add(MangaCoverFetcher.DomainMangaFactory(lazy(callFactoryInit), lazy(diskCacheInit)))
add(MangaCoverFetcher.MangaCoverFactory(lazy(callFactoryInit), lazy(diskCacheInit))) add(MangaCoverFetcher.MangaCoverFactory(lazy(callFactoryInit), lazy(diskCacheInit)))
add(MangaKeyer()) add(MangaKeyer())
add(DomainMangaKeyer())
add(MangaCoverKeyer()) add(MangaCoverKeyer())
// SY --> // SY -->
add(PagePreviewKeyer()) add(PagePreviewKeyer())

View File

@ -4,6 +4,7 @@ import android.Manifest
import android.content.Context import android.content.Context
import android.net.Uri import android.net.Uri
import com.hippo.unifile.UniFile import com.hippo.unifile.UniFile
import eu.kanade.domain.chapter.model.copyFrom
import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.backup.BackupConst.BACKUP_CATEGORY import eu.kanade.tachiyomi.data.backup.BackupConst.BACKUP_CATEGORY
import eu.kanade.tachiyomi.data.backup.BackupConst.BACKUP_CATEGORY_MASK import eu.kanade.tachiyomi.data.backup.BackupConst.BACKUP_CATEGORY_MASK
@ -31,15 +32,13 @@ import eu.kanade.tachiyomi.data.backup.models.backupChapterMapper
import eu.kanade.tachiyomi.data.backup.models.backupMergedMangaReferenceMapper import eu.kanade.tachiyomi.data.backup.models.backupMergedMangaReferenceMapper
import eu.kanade.tachiyomi.data.backup.models.backupSavedSearchMapper import eu.kanade.tachiyomi.data.backup.models.backupSavedSearchMapper
import eu.kanade.tachiyomi.data.backup.models.backupTrackMapper import eu.kanade.tachiyomi.data.backup.models.backupTrackMapper
import eu.kanade.tachiyomi.data.database.models.Chapter
import eu.kanade.tachiyomi.data.database.models.Manga
import eu.kanade.tachiyomi.data.database.models.Track
import eu.kanade.tachiyomi.source.model.copyFrom import eu.kanade.tachiyomi.source.model.copyFrom
import eu.kanade.tachiyomi.source.online.MetadataSource import eu.kanade.tachiyomi.source.online.MetadataSource
import eu.kanade.tachiyomi.util.system.hasPermission import eu.kanade.tachiyomi.util.system.hasPermission
import exh.source.MERGED_SOURCE_ID import exh.source.MERGED_SOURCE_ID
import exh.source.getMainSource import exh.source.getMainSource
import exh.util.nullIfBlank import exh.util.nullIfBlank
import exh.util.nullIfEmpty
import kotlinx.serialization.protobuf.ProtoBuf import kotlinx.serialization.protobuf.ProtoBuf
import logcat.LogPriority import logcat.LogPriority
import okio.buffer import okio.buffer
@ -66,13 +65,13 @@ import tachiyomi.domain.manga.interactor.GetMergedManga
import tachiyomi.domain.manga.interactor.InsertFlatMetadata import tachiyomi.domain.manga.interactor.InsertFlatMetadata
import tachiyomi.domain.manga.interactor.SetCustomMangaInfo import tachiyomi.domain.manga.interactor.SetCustomMangaInfo
import tachiyomi.domain.manga.model.CustomMangaInfo import tachiyomi.domain.manga.model.CustomMangaInfo
import tachiyomi.domain.manga.model.Manga
import tachiyomi.domain.source.service.SourceManager import tachiyomi.domain.source.service.SourceManager
import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get import uy.kohesive.injekt.api.get
import java.io.FileOutputStream import java.io.FileOutputStream
import java.util.Date import java.util.Date
import kotlin.math.max import kotlin.math.max
import tachiyomi.domain.manga.model.Manga as DomainManga
class BackupManager( class BackupManager(
private val context: Context, private val context: Context,
@ -176,12 +175,12 @@ class BackupManager(
} }
} }
private fun backupExtensionInfo(mangas: List<DomainManga>): List<BackupSource> { private fun backupExtensionInfo(mangas: List<Manga>): List<BackupSource> {
return mangas return mangas
.asSequence() .asSequence()
.map { it.source } .map(Manga::source)
.distinct() .distinct()
.map { sourceManager.getOrStub(it) } .map(sourceManager::getOrStub)
.map { BackupSource.copyFrom(it) } .map { BackupSource.copyFrom(it) }
.toList() .toList()
} }
@ -202,7 +201,7 @@ class BackupManager(
} }
} }
private suspend fun backupMangas(mangas: List<DomainManga>, flags: Int): List<BackupManga> { private suspend fun backupMangas(mangas: List<Manga>, flags: Int): List<BackupManga> {
return mangas.map { return mangas.map {
backupManga(it, flags) backupManga(it, flags)
} }
@ -226,7 +225,7 @@ class BackupManager(
* @param options options for the backup * @param options options for the backup
* @return [BackupManga] containing manga in a serializable form * @return [BackupManga] containing manga in a serializable form
*/ */
private suspend fun backupManga(manga: DomainManga, options: Int): BackupManga { private suspend fun backupManga(manga: Manga, options: Int): BackupManga {
// Entry for this manga // Entry for this manga
val mangaObject = BackupManga.copyFrom( val mangaObject = BackupManga.copyFrom(
manga, manga,
@ -290,10 +289,11 @@ class BackupManager(
return mangaObject return mangaObject
} }
internal suspend fun restoreExistingManga(manga: Manga, dbManga: Mangas) { internal suspend fun restoreExistingManga(manga: Manga, dbManga: Mangas): Manga {
manga.id = dbManga._id var manga = manga.copy(id = dbManga._id)
manga.copyFrom(dbManga) manga = manga.copyFrom(dbManga)
updateManga(manga) updateManga(manga)
return manga
} }
/** /**
@ -303,10 +303,10 @@ class BackupManager(
* @return Updated manga info. * @return Updated manga info.
*/ */
internal suspend fun restoreNewManga(manga: Manga): Manga { internal suspend fun restoreNewManga(manga: Manga): Manga {
return manga.also { return manga.copy(
it.initialized = it.description != null initialized = manga.description != null,
it.id = insertManga(it) id = insertManga(manga),
} )
} }
/** /**
@ -437,28 +437,28 @@ class BackupManager(
* @param manga the manga whose sync have to be restored. * @param manga the manga whose sync have to be restored.
* @param tracks the track list to restore. * @param tracks the track list to restore.
*/ */
internal suspend fun restoreTracking(manga: Manga, tracks: List<Track>) { internal suspend fun restoreTracking(manga: Manga, tracks: List<tachiyomi.domain.track.model.Track>) {
// Fix foreign keys with the current manga id // Fix foreign keys with the current manga id
tracks.map { it.manga_id = manga.id!! } val tracks = tracks.map { it.copy(mangaId = manga.id!!) }
// Get tracks from database // Get tracks from database
val dbTracks = handler.awaitList { manga_syncQueries.getTracksByMangaId(manga.id!!) } val dbTracks = handler.awaitList { manga_syncQueries.getTracksByMangaId(manga.id!!) }
val toUpdate = mutableListOf<Manga_sync>() val toUpdate = mutableListOf<Manga_sync>()
val toInsert = mutableListOf<Track>() val toInsert = mutableListOf<tachiyomi.domain.track.model.Track>()
tracks.forEach { track -> tracks.forEach { track ->
var isInDatabase = false var isInDatabase = false
for (dbTrack in dbTracks) { for (dbTrack in dbTracks) {
if (track.sync_id == dbTrack.sync_id.toInt()) { if (track.syncId == dbTrack.sync_id) {
// The sync is already in the db, only update its fields // The sync is already in the db, only update its fields
var temp = dbTrack var temp = dbTrack
if (track.media_id != dbTrack.remote_id) { if (track.remoteId != dbTrack.remote_id) {
temp = temp.copy(remote_id = track.media_id) temp = temp.copy(remote_id = track.remoteId)
} }
if (track.library_id != dbTrack.library_id) { if (track.libraryId != dbTrack.library_id) {
temp = temp.copy(library_id = track.library_id) temp = temp.copy(library_id = track.libraryId)
} }
temp = temp.copy(last_chapter_read = max(dbTrack.last_chapter_read, track.last_chapter_read.toDouble())) temp = temp.copy(last_chapter_read = max(dbTrack.last_chapter_read, track.lastChapterRead))
isInDatabase = true isInDatabase = true
toUpdate.add(temp) toUpdate.add(temp)
break break
@ -466,8 +466,7 @@ class BackupManager(
} }
if (!isInDatabase) { if (!isInDatabase) {
// Insert new sync. Let the db assign the id // Insert new sync. Let the db assign the id
track.id = null toInsert.add(track.copy(id = 0))
toInsert.add(track)
} }
} }
// Update database // Update database
@ -496,47 +495,47 @@ class BackupManager(
handler.await(true) { handler.await(true) {
toInsert.forEach { track -> toInsert.forEach { track ->
manga_syncQueries.insert( manga_syncQueries.insert(
track.manga_id, track.mangaId,
track.sync_id.toLong(), track.syncId,
track.media_id, track.remoteId,
track.library_id, track.libraryId,
track.title, track.title,
track.last_chapter_read.toDouble(), track.lastChapterRead,
track.total_chapters.toLong(), track.totalChapters,
track.status.toLong(), track.status,
track.score, track.score,
track.tracking_url, track.remoteUrl,
track.started_reading_date, track.startDate,
track.finished_reading_date, track.finishDate,
) )
} }
} }
} }
} }
internal suspend fun restoreChapters(manga: Manga, chapters: List<Chapter>) { internal suspend fun restoreChapters(manga: Manga, chapters: List<tachiyomi.domain.chapter.model.Chapter>) {
val dbChapters = handler.awaitList { chaptersQueries.getChaptersByMangaId(manga.id!!) } val dbChapters = handler.awaitList { chaptersQueries.getChaptersByMangaId(manga.id!!) }
chapters.forEach { chapter -> val processed = chapters.map { chapter ->
var chapter = chapter
val dbChapter = dbChapters.find { it.url == chapter.url } val dbChapter = dbChapters.find { it.url == chapter.url }
if (dbChapter != null) { if (dbChapter != null) {
chapter.id = dbChapter._id chapter = chapter.copy(id = dbChapter._id)
chapter.copyFrom(dbChapter) chapter = chapter.copyFrom(dbChapter)
if (dbChapter.read && !chapter.read) { if (dbChapter.read && !chapter.read) {
chapter.read = dbChapter.read chapter = chapter.copy(read = dbChapter.read, lastPageRead = dbChapter.last_page_read)
chapter.last_page_read = dbChapter.last_page_read.toInt() } else if (chapter.lastPageRead == 0L && dbChapter.last_page_read != 0L) {
} else if (chapter.last_page_read == 0 && dbChapter.last_page_read != 0L) { chapter = chapter.copy(lastPageRead = dbChapter.last_page_read)
chapter.last_page_read = dbChapter.last_page_read.toInt()
} }
if (!chapter.bookmark && dbChapter.bookmark) { if (!chapter.bookmark && dbChapter.bookmark) {
chapter.bookmark = dbChapter.bookmark chapter = chapter.copy(bookmark = dbChapter.bookmark)
} }
} }
chapter.manga_id = manga.id chapter.copy(mangaId = manga.id ?: -1)
} }
val newChapters = chapters.groupBy { it.id != null } val newChapters = processed.groupBy { it.id > 0 }
newChapters[true]?.let { updateKnownChapters(it) } newChapters[true]?.let { updateKnownChapters(it) }
newChapters[false]?.let { insertChapters(it) } newChapters[false]?.let { insertChapters(it) }
} }
@ -563,22 +562,22 @@ class BackupManager(
artist = manga.artist, artist = manga.artist,
author = manga.author, author = manga.author,
description = manga.description, description = manga.description,
genre = manga.getGenres(), genre = manga.genre,
title = manga.title, title = manga.title,
status = manga.status.toLong(), status = manga.status,
thumbnailUrl = manga.thumbnail_url, thumbnailUrl = manga.thumbnailUrl,
favorite = manga.favorite, favorite = manga.favorite,
lastUpdate = manga.last_update, lastUpdate = manga.lastUpdate,
nextUpdate = 0L, nextUpdate = 0L,
initialized = manga.initialized, initialized = manga.initialized,
viewerFlags = manga.viewer_flags.toLong(), viewerFlags = manga.viewerFlags,
chapterFlags = manga.chapter_flags.toLong(), chapterFlags = manga.chapterFlags,
coverLastModified = manga.cover_last_modified, coverLastModified = manga.coverLastModified,
dateAdded = manga.date_added, dateAdded = manga.dateAdded,
// SY --> // SY -->
filteredScanlators = manga.filtered_scanlators?.nullIfBlank()?.let(listOfStringsAndAdapter::decode), filteredScanlators = manga.filteredScanlators?.nullIfEmpty(),
// SY <-- // SY <--
updateStrategy = manga.update_strategy, updateStrategy = manga.updateStrategy,
) )
mangasQueries.selectLastInsertedRowId() mangasQueries.selectLastInsertedRowId()
} }
@ -592,45 +591,45 @@ class BackupManager(
artist = manga.artist, artist = manga.artist,
author = manga.author, author = manga.author,
description = manga.description, description = manga.description,
genre = manga.genre, genre = manga.genre?.joinToString(separator = ", "),
title = manga.title, title = manga.title,
status = manga.status.toLong(), status = manga.status,
thumbnailUrl = manga.thumbnail_url, thumbnailUrl = manga.thumbnailUrl,
favorite = manga.favorite.toLong(), favorite = manga.favorite.toLong(),
lastUpdate = manga.last_update, lastUpdate = manga.lastUpdate,
initialized = manga.initialized.toLong(), initialized = manga.initialized.toLong(),
viewer = manga.viewer_flags.toLong(), viewer = manga.viewerFlags,
chapterFlags = manga.chapter_flags.toLong(), chapterFlags = manga.chapterFlags,
coverLastModified = manga.cover_last_modified, coverLastModified = manga.coverLastModified,
dateAdded = manga.date_added, dateAdded = manga.dateAdded,
// SY --> // SY -->
filteredScanlators = manga.filtered_scanlators, filteredScanlators = manga.filteredScanlators?.let(listOfStringsAndAdapter::encode),
// SY <-- // SY <--
mangaId = manga.id!!, mangaId = manga.id,
updateStrategy = manga.update_strategy.let(updateStrategyAdapter::encode), updateStrategy = manga.updateStrategy.let(updateStrategyAdapter::encode),
) )
} }
return manga.id!! return manga.id
} }
/** /**
* Inserts list of chapters * Inserts list of chapters
*/ */
private suspend fun insertChapters(chapters: List<Chapter>) { private suspend fun insertChapters(chapters: List<tachiyomi.domain.chapter.model.Chapter>) {
handler.await(true) { handler.await(true) {
chapters.forEach { chapter -> chapters.forEach { chapter ->
chaptersQueries.insert( chaptersQueries.insert(
chapter.manga_id!!, chapter.mangaId,
chapter.url, chapter.url,
chapter.name, chapter.name,
chapter.scanlator, chapter.scanlator,
chapter.read, chapter.read,
chapter.bookmark, chapter.bookmark,
chapter.last_page_read.toLong(), chapter.lastPageRead,
chapter.chapter_number, chapter.chapterNumber,
chapter.source_order.toLong(), chapter.sourceOrder,
chapter.date_fetch, chapter.dateFetch,
chapter.date_upload, chapter.dateUpload,
) )
} }
} }
@ -639,22 +638,22 @@ class BackupManager(
/** /**
* Updates a list of chapters * Updates a list of chapters
*/ */
private suspend fun updateChapters(chapters: List<Chapter>) { private suspend fun updateChapters(chapters: List<tachiyomi.domain.chapter.model.Chapter>) {
handler.await(true) { handler.await(true) {
chapters.forEach { chapter -> chapters.forEach { chapter ->
chaptersQueries.update( chaptersQueries.update(
chapter.manga_id!!, chapter.mangaId,
chapter.url, chapter.url,
chapter.name, chapter.name,
chapter.scanlator, chapter.scanlator,
chapter.read.toLong(), chapter.read.toLong(),
chapter.bookmark.toLong(), chapter.bookmark.toLong(),
chapter.last_page_read.toLong(), chapter.lastPageRead,
chapter.chapter_number.toDouble(), chapter.chapterNumber.toDouble(),
chapter.source_order.toLong(), chapter.sourceOrder,
chapter.date_fetch, chapter.dateFetch,
chapter.date_upload, chapter.dateUpload,
chapter.id!!, chapter.id,
) )
} }
} }
@ -663,7 +662,7 @@ class BackupManager(
/** /**
* Updates a list of chapters with known database ids * Updates a list of chapters with known database ids
*/ */
private suspend fun updateKnownChapters(chapters: List<Chapter>) { private suspend fun updateKnownChapters(chapters: List<tachiyomi.domain.chapter.model.Chapter>) {
handler.await(true) { handler.await(true) {
chapters.forEach { chapter -> chapters.forEach { chapter ->
chaptersQueries.update( chaptersQueries.update(
@ -673,12 +672,12 @@ class BackupManager(
scanlator = null, scanlator = null,
read = chapter.read.toLong(), read = chapter.read.toLong(),
bookmark = chapter.bookmark.toLong(), bookmark = chapter.bookmark.toLong(),
lastPageRead = chapter.last_page_read.toLong(), lastPageRead = chapter.lastPageRead,
chapterNumber = null, chapterNumber = null,
sourceOrder = null, sourceOrder = null,
dateFetch = null, dateFetch = null,
dateUpload = null, dateUpload = null,
chapterId = chapter.id!!, chapterId = chapter.id,
) )
} }
} }

View File

@ -10,16 +10,15 @@ import eu.kanade.tachiyomi.data.backup.models.BackupManga
import eu.kanade.tachiyomi.data.backup.models.BackupMergedMangaReference import eu.kanade.tachiyomi.data.backup.models.BackupMergedMangaReference
import eu.kanade.tachiyomi.data.backup.models.BackupSavedSearch import eu.kanade.tachiyomi.data.backup.models.BackupSavedSearch
import eu.kanade.tachiyomi.data.backup.models.BackupSource import eu.kanade.tachiyomi.data.backup.models.BackupSource
import eu.kanade.tachiyomi.data.database.models.Chapter
import eu.kanade.tachiyomi.data.database.models.Manga
import eu.kanade.tachiyomi.data.database.models.Track
import eu.kanade.tachiyomi.util.BackupUtil import eu.kanade.tachiyomi.util.BackupUtil
import eu.kanade.tachiyomi.util.system.createFileInCacheDir import eu.kanade.tachiyomi.util.system.createFileInCacheDir
import exh.EXHMigrations import exh.EXHMigrations
import exh.source.MERGED_SOURCE_ID import exh.source.MERGED_SOURCE_ID
import kotlinx.coroutines.Job import kotlinx.coroutines.Job
import okio.source import tachiyomi.domain.chapter.model.Chapter
import tachiyomi.domain.manga.model.CustomMangaInfo import tachiyomi.domain.manga.model.CustomMangaInfo
import tachiyomi.domain.manga.model.Manga
import tachiyomi.domain.track.model.Track
import java.io.File import java.io.File
import java.text.SimpleDateFormat import java.text.SimpleDateFormat
import java.util.Date import java.util.Date
@ -133,7 +132,7 @@ class BackupRestorer(
// SY <-- // SY <--
private suspend fun restoreManga(backupManga: BackupManga, backupCategories: List<BackupCategory>) { private suspend fun restoreManga(backupManga: BackupManga, backupCategories: List<BackupCategory>) {
val manga = backupManga.getMangaImpl() var manga = backupManga.getMangaImpl()
val chapters = backupManga.getChaptersImpl() val chapters = backupManga.getChaptersImpl()
val categories = backupManga.categories.map { it.toInt() } val categories = backupManga.categories.map { it.toInt() }
val history = val history =
@ -146,7 +145,7 @@ class BackupRestorer(
// SY <-- // SY <--
// SY --> // SY -->
EXHMigrations.migrateBackupEntry(manga) manga = EXHMigrations.migrateBackupEntry(manga)
// SY <-- // SY <--
try { try {
@ -157,7 +156,7 @@ class BackupRestorer(
} else { } else {
// Manga in database // Manga in database
// Copy information from manga already in database // Copy information from manga already in database
backupManager.restoreExistingManga(manga, dbManga) val manga = backupManager.restoreExistingManga(manga, dbManga)
// Fetch rest of manga information // Fetch rest of manga information
restoreNewManga(manga, chapters, categories, history, tracks, backupCategories/* SY --> */, mergedMangaReferences, flatMetadata, customManga/* SY <-- */) restoreNewManga(manga, chapters, categories, history, tracks, backupCategories/* SY --> */, mergedMangaReferences, flatMetadata, customManga/* SY <-- */)
} }
@ -191,8 +190,6 @@ class BackupRestorer(
// SY <-- // SY <--
) { ) {
val fetchedManga = backupManager.restoreNewManga(manga) val fetchedManga = backupManager.restoreNewManga(manga)
fetchedManga.id ?: return
backupManager.restoreChapters(fetchedManga, chapters) backupManager.restoreChapters(fetchedManga, chapters)
restoreExtras(fetchedManga, categories, history, tracks, backupCategories/* SY --> */, mergedMangaReferences, flatMetadata, customManga/* SY <-- */) restoreExtras(fetchedManga, categories, history, tracks, backupCategories/* SY --> */, mergedMangaReferences, flatMetadata, customManga/* SY <-- */)
} }

View File

@ -1,8 +1,8 @@
package eu.kanade.tachiyomi.data.backup.models package eu.kanade.tachiyomi.data.backup.models
import eu.kanade.tachiyomi.data.database.models.ChapterImpl
import kotlinx.serialization.Serializable import kotlinx.serialization.Serializable
import kotlinx.serialization.protobuf.ProtoNumber import kotlinx.serialization.protobuf.ProtoNumber
import tachiyomi.domain.chapter.model.Chapter
@Serializable @Serializable
data class BackupChapter( data class BackupChapter(
@ -21,19 +21,19 @@ data class BackupChapter(
@ProtoNumber(9) var chapterNumber: Float = 0F, @ProtoNumber(9) var chapterNumber: Float = 0F,
@ProtoNumber(10) var sourceOrder: Long = 0, @ProtoNumber(10) var sourceOrder: Long = 0,
) { ) {
fun toChapterImpl(): ChapterImpl { fun toChapterImpl(): Chapter {
return ChapterImpl().apply { return Chapter.create().copy(
url = this@BackupChapter.url url = this@BackupChapter.url,
name = this@BackupChapter.name name = this@BackupChapter.name,
chapter_number = this@BackupChapter.chapterNumber chapterNumber = this@BackupChapter.chapterNumber,
scanlator = this@BackupChapter.scanlator scanlator = this@BackupChapter.scanlator,
read = this@BackupChapter.read read = this@BackupChapter.read,
bookmark = this@BackupChapter.bookmark bookmark = this@BackupChapter.bookmark,
last_page_read = this@BackupChapter.lastPageRead.toInt() lastPageRead = this@BackupChapter.lastPageRead,
date_fetch = this@BackupChapter.dateFetch dateFetch = this@BackupChapter.dateFetch,
date_upload = this@BackupChapter.dateUpload dateUpload = this@BackupChapter.dateUpload,
source_order = this@BackupChapter.sourceOrder.toInt() sourceOrder = this@BackupChapter.sourceOrder,
} )
} }
} }

View File

@ -1,15 +1,14 @@
package eu.kanade.tachiyomi.data.backup.models package eu.kanade.tachiyomi.data.backup.models
import eu.kanade.tachiyomi.data.database.models.ChapterImpl
import eu.kanade.tachiyomi.data.database.models.MangaImpl
import eu.kanade.tachiyomi.data.database.models.TrackImpl
import eu.kanade.tachiyomi.source.model.UpdateStrategy import eu.kanade.tachiyomi.source.model.UpdateStrategy
import eu.kanade.tachiyomi.ui.reader.setting.ReadingModeType import eu.kanade.tachiyomi.ui.reader.setting.ReadingModeType
import kotlinx.serialization.Serializable import kotlinx.serialization.Serializable
import kotlinx.serialization.protobuf.ProtoNumber import kotlinx.serialization.protobuf.ProtoNumber
import tachiyomi.data.listOfStringsAndAdapter import tachiyomi.data.listOfStringsAndAdapter
import tachiyomi.domain.chapter.model.Chapter
import tachiyomi.domain.manga.model.CustomMangaInfo import tachiyomi.domain.manga.model.CustomMangaInfo
import tachiyomi.domain.manga.model.Manga import tachiyomi.domain.manga.model.Manga
import tachiyomi.domain.track.model.Track
@Suppress("DEPRECATION") @Suppress("DEPRECATION")
@Serializable @Serializable
@ -59,27 +58,29 @@ data class BackupManga(
// Neko specific values // Neko specific values
@ProtoNumber(901) var filtered_scanlators: String? = null, @ProtoNumber(901) var filtered_scanlators: String? = null,
) { ) {
fun getMangaImpl(): MangaImpl { fun getMangaImpl(): Manga {
return MangaImpl().apply { return Manga.create().copy(
url = this@BackupManga.url url = this@BackupManga.url,
title = this@BackupManga.title // SY -->
artist = this@BackupManga.artist ogTitle = this@BackupManga.title,
author = this@BackupManga.author ogArtist = this@BackupManga.artist,
description = this@BackupManga.description ogAuthor = this@BackupManga.author,
genre = this@BackupManga.genre.joinToString() ogDescription = this@BackupManga.description,
status = this@BackupManga.status ogGenre = this@BackupManga.genre,
thumbnail_url = this@BackupManga.thumbnailUrl ogStatus = this@BackupManga.status.toLong(),
favorite = this@BackupManga.favorite // SY <--
source = this@BackupManga.source thumbnailUrl = this@BackupManga.thumbnailUrl,
date_added = this@BackupManga.dateAdded favorite = this@BackupManga.favorite,
viewer_flags = this@BackupManga.viewer_flags ?: this@BackupManga.viewer source = this@BackupManga.source,
chapter_flags = this@BackupManga.chapterFlags dateAdded = this@BackupManga.dateAdded,
update_strategy = this@BackupManga.updateStrategy viewerFlags = (this@BackupManga.viewer_flags ?: this@BackupManga.viewer).toLong(),
filtered_scanlators = this@BackupManga.filtered_scanlators chapterFlags = this@BackupManga.chapterFlags.toLong(),
} updateStrategy = this@BackupManga.updateStrategy,
filteredScanlators = this@BackupManga.filtered_scanlators?.let(listOfStringsAndAdapter::decode),
)
} }
fun getChaptersImpl(): List<ChapterImpl> { fun getChaptersImpl(): List<Chapter> {
return chapters.map { return chapters.map {
it.toChapterImpl() it.toChapterImpl()
} }
@ -108,7 +109,7 @@ data class BackupManga(
} }
// SY <-- // SY <--
fun getTrackingImpl(): List<TrackImpl> { fun getTrackingImpl(): List<Track> {
return tracking.map { return tracking.map {
it.getTrackingImpl() it.getTrackingImpl()
} }

View File

@ -1,8 +1,8 @@
package eu.kanade.tachiyomi.data.backup.models package eu.kanade.tachiyomi.data.backup.models
import eu.kanade.tachiyomi.data.database.models.TrackImpl
import kotlinx.serialization.Serializable import kotlinx.serialization.Serializable
import kotlinx.serialization.protobuf.ProtoNumber import kotlinx.serialization.protobuf.ProtoNumber
import tachiyomi.domain.track.model.Track
@Serializable @Serializable
data class BackupTracking( data class BackupTracking(
@ -29,25 +29,26 @@ data class BackupTracking(
@ProtoNumber(100) var mediaId: Long = 0, @ProtoNumber(100) var mediaId: Long = 0,
) { ) {
fun getTrackingImpl(): TrackImpl { fun getTrackingImpl(): Track {
return TrackImpl().apply { return Track(
sync_id = this@BackupTracking.syncId id = -1,
@Suppress("DEPRECATION") mangaId = -1,
media_id = if (this@BackupTracking.mediaIdInt != 0) { syncId = this@BackupTracking.syncId.toLong(),
remoteId = if (this@BackupTracking.mediaIdInt != 0) {
this@BackupTracking.mediaIdInt.toLong() this@BackupTracking.mediaIdInt.toLong()
} else { } else {
this@BackupTracking.mediaId this@BackupTracking.mediaId
} },
library_id = this@BackupTracking.libraryId libraryId = this@BackupTracking.libraryId,
title = this@BackupTracking.title title = this@BackupTracking.title,
last_chapter_read = this@BackupTracking.lastChapterRead lastChapterRead = this@BackupTracking.lastChapterRead.toDouble(),
total_chapters = this@BackupTracking.totalChapters totalChapters = this@BackupTracking.totalChapters.toLong(),
score = this@BackupTracking.score score = this@BackupTracking.score,
status = this@BackupTracking.status status = this@BackupTracking.status.toLong(),
started_reading_date = this@BackupTracking.startedReadingDate startDate = this@BackupTracking.startedReadingDate,
finished_reading_date = this@BackupTracking.finishedReadingDate finishDate = this@BackupTracking.finishedReadingDate,
tracking_url = this@BackupTracking.trackingUrl remoteUrl = this@BackupTracking.trackingUrl,
} )
} }
} }

View File

@ -12,7 +12,6 @@ import coil.request.Options
import coil.request.Parameters import coil.request.Parameters
import eu.kanade.tachiyomi.data.cache.CoverCache import eu.kanade.tachiyomi.data.cache.CoverCache
import eu.kanade.tachiyomi.data.coil.MangaCoverFetcher.Companion.USE_CUSTOM_COVER import eu.kanade.tachiyomi.data.coil.MangaCoverFetcher.Companion.USE_CUSTOM_COVER
import eu.kanade.tachiyomi.data.database.models.Manga
import eu.kanade.tachiyomi.network.await import eu.kanade.tachiyomi.network.await
import eu.kanade.tachiyomi.source.online.HttpSource import eu.kanade.tachiyomi.source.online.HttpSource
import logcat.LogPriority import logcat.LogPriority
@ -26,11 +25,11 @@ import okio.Source
import okio.buffer import okio.buffer
import okio.sink import okio.sink
import tachiyomi.core.util.system.logcat import tachiyomi.core.util.system.logcat
import tachiyomi.domain.manga.model.Manga
import tachiyomi.domain.manga.model.MangaCover import tachiyomi.domain.manga.model.MangaCover
import tachiyomi.domain.source.service.SourceManager import tachiyomi.domain.source.service.SourceManager
import uy.kohesive.injekt.injectLazy import uy.kohesive.injekt.injectLazy
import java.io.File import java.io.File
import tachiyomi.domain.manga.model.Manga as DomainManga
/** /**
* A [Fetcher] that fetches cover image for [Manga] object. * A [Fetcher] that fetches cover image for [Manga] object.
@ -261,7 +260,7 @@ class MangaCoverFetcher(
File, URL File, URL
} }
class Factory( class MangaFactory(
private val callFactoryLazy: Lazy<Call.Factory>, private val callFactoryLazy: Lazy<Call.Factory>,
private val diskCacheLazy: Lazy<DiskCache>, private val diskCacheLazy: Lazy<DiskCache>,
) : Fetcher.Factory<Manga> { ) : Fetcher.Factory<Manga> {
@ -270,36 +269,13 @@ class MangaCoverFetcher(
private val sourceManager: SourceManager by injectLazy() private val sourceManager: SourceManager by injectLazy()
override fun create(data: Manga, options: Options, imageLoader: ImageLoader): Fetcher { override fun create(data: Manga, options: Options, imageLoader: ImageLoader): Fetcher {
return MangaCoverFetcher(
url = data.thumbnail_url,
isLibraryManga = data.favorite,
options = options,
coverFileLazy = lazy { coverCache.getCoverFile(data.thumbnail_url) },
customCoverFileLazy = lazy { coverCache.getCustomCoverFile(data.id) },
diskCacheKeyLazy = lazy { MangaKeyer().key(data, options) },
sourceLazy = lazy { sourceManager.get(data.source) as? HttpSource },
callFactoryLazy = callFactoryLazy,
diskCacheLazy = diskCacheLazy,
)
}
}
class DomainMangaFactory(
private val callFactoryLazy: Lazy<Call.Factory>,
private val diskCacheLazy: Lazy<DiskCache>,
) : Fetcher.Factory<DomainManga> {
private val coverCache: CoverCache by injectLazy()
private val sourceManager: SourceManager by injectLazy()
override fun create(data: DomainManga, options: Options, imageLoader: ImageLoader): Fetcher {
return MangaCoverFetcher( return MangaCoverFetcher(
url = data.thumbnailUrl, url = data.thumbnailUrl,
isLibraryManga = data.favorite, isLibraryManga = data.favorite,
options = options, options = options,
coverFileLazy = lazy { coverCache.getCoverFile(data.thumbnailUrl) }, coverFileLazy = lazy { coverCache.getCoverFile(data.thumbnailUrl) },
customCoverFileLazy = lazy { coverCache.getCustomCoverFile(data.id) }, customCoverFileLazy = lazy { coverCache.getCustomCoverFile(data.id) },
diskCacheKeyLazy = lazy { DomainMangaKeyer().key(data, options) }, diskCacheKeyLazy = lazy { MangaKeyer().key(data, options) },
sourceLazy = lazy { sourceManager.get(data.source) as? HttpSource }, sourceLazy = lazy { sourceManager.get(data.source) as? HttpSource },
callFactoryLazy = callFactoryLazy, callFactoryLazy = callFactoryLazy,
diskCacheLazy = diskCacheLazy, diskCacheLazy = diskCacheLazy,

View File

@ -4,24 +4,12 @@ import coil.key.Keyer
import coil.request.Options import coil.request.Options
import eu.kanade.domain.manga.model.hasCustomCover import eu.kanade.domain.manga.model.hasCustomCover
import eu.kanade.tachiyomi.data.cache.CoverCache import eu.kanade.tachiyomi.data.cache.CoverCache
import eu.kanade.tachiyomi.data.database.models.Manga
import eu.kanade.tachiyomi.data.database.models.toDomainManga
import tachiyomi.domain.manga.model.MangaCover import tachiyomi.domain.manga.model.MangaCover
import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get import uy.kohesive.injekt.api.get
import tachiyomi.domain.manga.model.Manga as DomainManga import tachiyomi.domain.manga.model.Manga as DomainManga
class MangaKeyer : Keyer<Manga> { class MangaKeyer : Keyer<DomainManga> {
override fun key(data: Manga, options: Options): String {
return if (data.toDomainManga()!!.hasCustomCover()) {
"${data.id};${data.cover_last_modified}"
} else {
"${data.thumbnail_url};${data.cover_last_modified}"
}
}
}
class DomainMangaKeyer : Keyer<DomainManga> {
override fun key(data: DomainManga, options: Options): String { override fun key(data: DomainManga, options: Options): String {
return if (data.hasCustomCover()) { return if (data.hasCustomCover()) {
"${data.id};${data.coverLastModified}" "${data.id};${data.coverLastModified}"
@ -31,9 +19,11 @@ class DomainMangaKeyer : Keyer<DomainManga> {
} }
} }
class MangaCoverKeyer : Keyer<MangaCover> { class MangaCoverKeyer(
private val coverCache: CoverCache = Injekt.get(),
) : Keyer<MangaCover> {
override fun key(data: MangaCover, options: Options): String { override fun key(data: MangaCover, options: Options): String {
return if (Injekt.get<CoverCache>().getCustomCoverFile(data.mangaId).exists()) { return if (coverCache.getCustomCoverFile(data.mangaId).exists()) {
"${data.mangaId};${data.lastModified}" "${data.mangaId};${data.lastModified}"
} else { } else {
"${data.url};${data.lastModified}" "${data.url};${data.lastModified}"

View File

@ -1,77 +0,0 @@
package eu.kanade.tachiyomi.data.database.models
import eu.kanade.tachiyomi.source.model.SManga
import eu.kanade.tachiyomi.ui.reader.setting.OrientationType
import eu.kanade.tachiyomi.ui.reader.setting.ReadingModeType
import exh.util.nullIfEmpty
import tachiyomi.data.listOfStringsAndAdapter
import tachiyomi.domain.manga.model.Manga as DomainManga
interface Manga : SManga {
var id: Long?
var source: Long
var favorite: Boolean
// last time the chapter list changed in any way
var last_update: Long
var date_added: Long
var viewer_flags: Int
var chapter_flags: Int
var cover_last_modified: Long
// SY -->
var filtered_scanlators: String?
fun getOriginalGenres(): List<String>? {
return originalGenre?.split(", ")?.map { it.trim() }
}
// SY <--
private fun setViewerFlags(flag: Int, mask: Int) {
viewer_flags = viewer_flags and mask.inv() or (flag and mask)
}
var readingModeType: Int
get() = viewer_flags and ReadingModeType.MASK
set(readingMode) = setViewerFlags(readingMode, ReadingModeType.MASK)
var orientationType: Int
get() = viewer_flags and OrientationType.MASK
set(rotationType) = setViewerFlags(rotationType, OrientationType.MASK)
}
fun Manga.toDomainManga(): DomainManga? {
val mangaId = id ?: return null
return DomainManga(
id = mangaId,
source = source,
favorite = favorite,
lastUpdate = last_update,
dateAdded = date_added,
viewerFlags = viewer_flags.toLong(),
chapterFlags = chapter_flags.toLong(),
coverLastModified = cover_last_modified,
url = url,
// SY -->
ogTitle = originalTitle,
ogArtist = originalArtist,
ogAuthor = originalAuthor,
ogDescription = originalDescription,
ogGenre = getOriginalGenres(),
ogStatus = originalStatus.toLong(),
// SY <--
thumbnailUrl = thumbnail_url,
updateStrategy = update_strategy,
initialized = initialized,
// SY -->
filteredScanlators = filtered_scanlators?.let(listOfStringsAndAdapter::decode)?.nullIfEmpty(),
// SY <--
)
}

View File

@ -1,133 +0,0 @@
package eu.kanade.tachiyomi.data.database.models
import eu.kanade.tachiyomi.data.download.DownloadManager
import eu.kanade.tachiyomi.source.model.SManga
import eu.kanade.tachiyomi.source.model.UpdateStrategy
import tachiyomi.domain.manga.interactor.GetCustomMangaInfo
import tachiyomi.domain.manga.model.CustomMangaInfo
import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get
import uy.kohesive.injekt.injectLazy
open class MangaImpl : Manga {
override var id: Long? = null
override var source: Long = -1
override lateinit var url: String
// SY -->
private val customManga: CustomMangaInfo?
get() = getCustomMangaInfo.get(id!!)
override var title: String
get() = if (favorite) {
customManga?.title ?: ogTitle
} else {
ogTitle
}
set(value) {
ogTitle = value
}
override var author: String?
get() = if (favorite) customManga?.author ?: ogAuthor else ogAuthor
set(value) { ogAuthor = value }
override var artist: String?
get() = if (favorite) customManga?.artist ?: ogArtist else ogArtist
set(value) { ogArtist = value }
override var description: String?
get() = if (favorite) customManga?.description ?: ogDesc else ogDesc
set(value) { ogDesc = value }
override var genre: String?
get() = if (favorite) customManga?.genre?.joinToString() ?: ogGenre else ogGenre
set(value) { ogGenre = value }
override var status: Int
get() = if (favorite) customManga?.status?.toInt() ?: ogStatus else ogStatus
set(value) { ogStatus = value }
// SY <--
override var thumbnail_url: String? = null
override var favorite: Boolean = false
override var last_update: Long = 0
override var date_added: Long = 0
override var update_strategy: UpdateStrategy = UpdateStrategy.ALWAYS_UPDATE
override var initialized: Boolean = false
override var viewer_flags: Int = 0
override var chapter_flags: Int = 0
override var cover_last_modified: Long = 0
override var filtered_scanlators: String? = null
// SY -->
lateinit var ogTitle: String
private set
var ogAuthor: String? = null
private set
var ogArtist: String? = null
private set
var ogDesc: String? = null
private set
var ogGenre: String? = null
private set
var ogStatus: Int = 0
private set
override val originalTitle: String
get() = ogTitle
override val originalAuthor: String?
get() = ogAuthor ?: author
override val originalArtist: String?
get() = ogArtist ?: artist
override val originalDescription: String?
get() = ogDesc ?: description
override val originalGenre: String?
get() = ogGenre ?: genre
override val originalStatus: Int
get() = ogStatus
// SY <--
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (other == null || javaClass != other.javaClass) return false
val manga = other as Manga
if (url != manga.url) return false
return id == manga.id
}
override fun hashCode(): Int {
return url.hashCode() + id.hashCode()
}
// SY -->
override fun copyFrom(other: SManga) {
// EXH -->
if (other.title.isNotBlank() && originalTitle != other.title) {
val source = (this as? Manga)?.source
if (source != null) {
Injekt.get<DownloadManager>().renameMangaDir(originalTitle, other.originalTitle, source)
}
}
// EXH <--
super.copyFrom(other)
}
companion object {
private val getCustomMangaInfo: GetCustomMangaInfo by injectLazy()
}
// SY <--
}

View File

@ -34,7 +34,6 @@ import eu.kanade.tachiyomi.source.UnmeteredSource
import eu.kanade.tachiyomi.source.model.SManga import eu.kanade.tachiyomi.source.model.SManga
import eu.kanade.tachiyomi.source.model.UpdateStrategy import eu.kanade.tachiyomi.source.model.UpdateStrategy
import eu.kanade.tachiyomi.source.online.all.MergedSource import eu.kanade.tachiyomi.source.online.all.MergedSource
import eu.kanade.tachiyomi.ui.manga.track.TrackItem
import eu.kanade.tachiyomi.util.prepUpdateCover import eu.kanade.tachiyomi.util.prepUpdateCover
import eu.kanade.tachiyomi.util.shouldDownloadNewChapters import eu.kanade.tachiyomi.util.shouldDownloadNewChapters
import eu.kanade.tachiyomi.util.storage.getUriCompat import eu.kanade.tachiyomi.util.storage.getUriCompat
@ -631,11 +630,14 @@ class LibraryUpdateJob(private val context: Context, workerParams: WorkerParamet
val dbTracks = getTracks.await(manga.id) val dbTracks = getTracks.await(manga.id)
// find the mdlist entry if its unfollowed the follow it // find the mdlist entry if its unfollowed the follow it
val tracker = TrackItem(dbTracks.firstOrNull { it.syncId == TrackManager.MDLIST }?.toDbTrack() ?: trackManager.mdList.createInitialTracker(manga), trackManager.mdList) var tracker = dbTracks.firstOrNull { it.syncId == TrackManager.MDLIST }
?: trackManager.mdList.createInitialTracker(manga).toDomainTrack(idRequired = false)
if (tracker.track?.status == FollowStatus.UNFOLLOWED.int) { if (tracker?.status == FollowStatus.UNFOLLOWED.int.toLong()) {
tracker.track.status = FollowStatus.READING.int tracker = tracker.copy(
val updatedTrack = tracker.service.update(tracker.track) status = FollowStatus.READING.int.toLong(),
)
val updatedTrack = trackManager.mdList.update(tracker.toDbTrack())
insertTrack.await(updatedTrack.toDomainTrack(false)!!) insertTrack.await(updatedTrack.toDomainTrack(false)!!)
} }
} }

View File

@ -1,20 +1,12 @@
package eu.kanade.tachiyomi.source.model package eu.kanade.tachiyomi.source.model
import eu.kanade.tachiyomi.data.database.models.Manga
import eu.kanade.tachiyomi.data.download.DownloadManager
import tachiyomi.data.Mangas import tachiyomi.data.Mangas
import uy.kohesive.injekt.Injekt import tachiyomi.domain.manga.model.Manga
import uy.kohesive.injekt.api.get
fun SManga.copyFrom(other: Mangas) { fun SManga.copyFrom(other: Mangas) {
// EXH --> // EXH -->
if (other.title.isNotBlank() && originalTitle != other.title) { if (other.title.isNotBlank() && originalTitle != other.title) {
val oldTitle = originalTitle
title = other.title title = other.title
val source = (this as? Manga)?.source
if (source != null) {
Injekt.get<DownloadManager>().renameMangaDir(oldTitle, other.title, source)
}
} }
// EXH <-- // EXH <--
@ -44,3 +36,33 @@ fun SManga.copyFrom(other: Mangas) {
initialized = other.initialized initialized = other.initialized
} }
} }
fun Manga.copyFrom(other: Mangas): Manga {
var manga = this
if (other.author != null) {
manga = manga.copy(ogAuthor = other.author)
}
if (other.artist != null) {
manga = manga.copy(ogArtist = other.artist)
}
if (other.description != null) {
manga = manga.copy(ogDescription = other.description)
}
if (other.genre != null) {
manga = manga.copy(ogGenre = other.genre)
}
if (other.thumbnail_url != null) {
manga = manga.copy(thumbnailUrl = other.thumbnail_url)
}
manga = manga.copy(ogStatus = other.status)
if (!initialized) {
manga = manga.copy(initialized = other.initialized)
}
return manga
}

View File

@ -20,7 +20,6 @@ import eu.kanade.domain.manga.model.downloadedFilter
import eu.kanade.domain.manga.model.isLocal import eu.kanade.domain.manga.model.isLocal
import eu.kanade.domain.manga.model.toSManga import eu.kanade.domain.manga.model.toSManga
import eu.kanade.domain.source.service.SourcePreferences import eu.kanade.domain.source.service.SourcePreferences
import eu.kanade.domain.track.model.toDbTrack
import eu.kanade.domain.track.model.toDomainTrack import eu.kanade.domain.track.model.toDomainTrack
import eu.kanade.domain.ui.UiPreferences import eu.kanade.domain.ui.UiPreferences
import eu.kanade.presentation.manga.DownloadAction import eu.kanade.presentation.manga.DownloadAction
@ -1381,10 +1380,9 @@ class MangaInfoScreenModel(
getTracks.subscribe(manga.id) getTracks.subscribe(manga.id)
.catch { logcat(LogPriority.ERROR, it) } .catch { logcat(LogPriority.ERROR, it) }
.map { tracks -> .map { tracks ->
val dbTracks = tracks.map { it.toDbTrack() }
loggedServices loggedServices
// Map to TrackItem // Map to TrackItem
.map { service -> TrackItem(dbTracks.find { it.sync_id.toLong() == service.id }, service) } .map { service -> TrackItem(tracks.find { it.syncId == service.id }, service) }
// Show only if the service supports this manga's source // Show only if the service supports this manga's source
.filter { (it.service as? EnhancedTrackService)?.accept(source!!) ?: true } .filter { (it.service as? EnhancedTrackService)?.accept(source!!) ?: true }
} }
@ -1422,7 +1420,7 @@ class MangaInfoScreenModel(
val track = trackManager.mdList.createInitialTracker(state.manga, mdManga) val track = trackManager.mdList.createInitialTracker(state.manga, mdManga)
.toDomainTrack(false)!! .toDomainTrack(false)!!
insertTrack.await(track) insertTrack.await(track)
return TrackItem(getTracks.await(mangaId).first { it.syncId == TrackManager.MDLIST }.toDbTrack(), trackManager.mdList) return TrackItem(getTracks.await(mangaId).first { it.syncId == TrackManager.MDLIST }, trackManager.mdList)
} }
// SY <-- // SY <--
@ -1557,7 +1555,7 @@ sealed class MangaScreenState {
get() = trackItems.isNotEmpty() get() = trackItems.isNotEmpty()
val trackingCount: Int val trackingCount: Int
get() = trackItems.count { it.track != null && ((it.service.id == TrackManager.MDLIST && it.track.status != FollowStatus.UNFOLLOWED.int) || it.service.id != TrackManager.MDLIST) } get() = trackItems.count { it.track != null && ((it.service.id == TrackManager.MDLIST && it.track.status != FollowStatus.UNFOLLOWED.int.toLong()) || it.service.id != TrackManager.MDLIST) }
/** /**
* Applies the view filters to the list of chapters obtained from the database. * Applies the view filters to the list of chapters obtained from the database.

View File

@ -47,7 +47,6 @@ import eu.kanade.presentation.track.TrackServiceSearch
import eu.kanade.presentation.track.TrackStatusSelector import eu.kanade.presentation.track.TrackStatusSelector
import eu.kanade.presentation.util.Screen import eu.kanade.presentation.util.Screen
import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.database.models.Track
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.data.track.TrackService import eu.kanade.tachiyomi.data.track.TrackService
@ -71,6 +70,7 @@ import tachiyomi.domain.source.service.SourceManager
import tachiyomi.domain.track.interactor.DeleteTrack import tachiyomi.domain.track.interactor.DeleteTrack
import tachiyomi.domain.track.interactor.GetTracks import tachiyomi.domain.track.interactor.GetTracks
import tachiyomi.domain.track.interactor.InsertTrack import tachiyomi.domain.track.interactor.InsertTrack
import tachiyomi.domain.track.model.Track
import tachiyomi.presentation.core.components.material.AlertDialogContent import tachiyomi.presentation.core.components.material.AlertDialogContent
import tachiyomi.presentation.core.components.material.padding import tachiyomi.presentation.core.components.material.padding
import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.Injekt
@ -149,7 +149,7 @@ data class TrackInfoDialogHomeScreen(
TrackServiceSearchScreen( TrackServiceSearchScreen(
mangaId = mangaId, mangaId = mangaId,
initialQuery = it.track?.title ?: mangaTitle, initialQuery = it.track?.title ?: mangaTitle,
currentUrl = it.track?.tracking_url, currentUrl = it.track?.remoteUrl,
serviceId = it.service.id, serviceId = it.service.id,
), ),
) )
@ -163,7 +163,7 @@ data class TrackInfoDialogHomeScreen(
* Opens registered tracker url in browser * Opens registered tracker url in browser
*/ */
private fun openTrackerInBrowser(context: Context, trackItem: TrackItem) { private fun openTrackerInBrowser(context: Context, trackItem: TrackItem) {
val url = trackItem.track?.tracking_url ?: return val url = trackItem.track?.remoteUrl ?: return
if (url.isNotBlank()) { if (url.isNotBlank()) {
context.openInBrowser(url) context.openInBrowser(url)
} }
@ -218,7 +218,7 @@ data class TrackInfoDialogHomeScreen(
for (trackItem in trackItems) { for (trackItem in trackItems) {
try { try {
val track = trackItem.track ?: continue val track = trackItem.track ?: continue
val domainTrack = trackItem.service.refresh(track).toDomainTrack() ?: continue val domainTrack = trackItem.service.refresh(track.toDbTrack()).toDomainTrack() ?: continue
insertTrack.await(domainTrack) insertTrack.await(domainTrack)
if (trackItem.service is EnhancedTrackService) { if (trackItem.service is EnhancedTrackService) {
@ -247,13 +247,12 @@ data class TrackInfoDialogHomeScreen(
} }
} }
private fun List<tachiyomi.domain.track.model.Track>.mapToTrackItem(): List<TrackItem> { private fun List<Track>.mapToTrackItem(): List<TrackItem> {
val dbTracks = map { it.toDbTrack() }
val loggedServices = Injekt.get<TrackManager>().services.filter { it.isLogged } val loggedServices = Injekt.get<TrackManager>().services.filter { it.isLogged }
val source = Injekt.get<SourceManager>().getOrStub(sourceId) val source = Injekt.get<SourceManager>().getOrStub(sourceId)
return loggedServices return loggedServices
// Map to TrackItem // Map to TrackItem
.map { service -> TrackItem(dbTracks.find { it.sync_id.toLong() == service.id }, service) } .map { service -> TrackItem(find { it.syncId == service.id }, service) }
// Show only if the service supports this manga's source // Show only if the service supports this manga's source
.filter { (it.service as? EnhancedTrackService)?.accept(source) ?: true } .filter { (it.service as? EnhancedTrackService)?.accept(source) ?: true }
} }
@ -291,7 +290,7 @@ private data class TrackStatusSelectorScreen(
private class Model( private class Model(
private val track: Track, private val track: Track,
private val service: TrackService, private val service: TrackService,
) : StateScreenModel<Model.State>(State(track.status)) { ) : StateScreenModel<Model.State>(State(track.status.toInt())) {
fun getSelections(): Map<Int, Int?> { fun getSelections(): Map<Int, Int?> {
return service.getStatusList().associateWith { service.getStatus(it) } return service.getStatusList().associateWith { service.getStatus(it) }
@ -303,7 +302,7 @@ private data class TrackStatusSelectorScreen(
fun setStatus() { fun setStatus() {
coroutineScope.launchNonCancellable { coroutineScope.launchNonCancellable {
service.setRemoteStatus(track, state.value.selection) service.setRemoteStatus(track.toDbTrack(), state.value.selection)
} }
} }
@ -341,15 +340,15 @@ private data class TrackChapterSelectorScreen(
private class Model( private class Model(
private val track: Track, private val track: Track,
private val service: TrackService, private val service: TrackService,
) : StateScreenModel<Model.State>(State(track.last_chapter_read.toInt())) { ) : StateScreenModel<Model.State>(State(track.lastChapterRead.toInt())) {
fun getRange(): Iterable<Int> { fun getRange(): Iterable<Int> {
val endRange = if (track.total_chapters > 0) { val endRange = if (track.totalChapters > 0) {
track.total_chapters track.totalChapters
} else { } else {
10000 10000
} }
return 0..endRange return 0..endRange.toInt()
} }
fun setSelection(selection: Int) { fun setSelection(selection: Int) {
@ -358,7 +357,7 @@ private data class TrackChapterSelectorScreen(
fun setChapter() { fun setChapter() {
coroutineScope.launchNonCancellable { coroutineScope.launchNonCancellable {
service.setRemoteLastChapterRead(track, state.value.selection) service.setRemoteLastChapterRead(track.toDbTrack(), state.value.selection)
} }
} }
@ -396,7 +395,7 @@ private data class TrackScoreSelectorScreen(
private class Model( private class Model(
private val track: Track, private val track: Track,
private val service: TrackService, private val service: TrackService,
) : StateScreenModel<Model.State>(State(service.displayScore(track))) { ) : StateScreenModel<Model.State>(State(service.displayScore(track.toDbTrack()))) {
fun getSelections(): List<String> { fun getSelections(): List<String> {
return service.getScoreList() return service.getScoreList()
@ -408,7 +407,7 @@ private data class TrackScoreSelectorScreen(
fun setScore() { fun setScore() {
coroutineScope.launchNonCancellable { coroutineScope.launchNonCancellable {
service.setRemoteScore(track, state.value.selection) service.setRemoteScore(track.toDbTrack(), state.value.selection)
} }
} }
@ -436,9 +435,9 @@ private data class TrackDateSelectorScreen(
} }
val canRemove = if (start) { val canRemove = if (start) {
track.started_reading_date > 0 track.startDate > 0
} else { } else {
track.finished_reading_date > 0 track.finishDate > 0
} }
TrackDateSelector( TrackDateSelector(
title = if (start) { title = if (start) {
@ -457,15 +456,15 @@ private data class TrackDateSelectorScreen(
return@TrackDateSelector false return@TrackDateSelector false
} }
if (start && track.finished_reading_date > 0) { if (start && track.finishDate > 0) {
// Disallow start date to be set later than finish date // Disallow start date to be set later than finish date
val dateFinished = Instant.ofEpochMilli(track.finished_reading_date) val dateFinished = Instant.ofEpochMilli(track.finishDate)
.atZone(ZoneId.systemDefault()) .atZone(ZoneId.systemDefault())
.toLocalDate() .toLocalDate()
dateToCheck <= dateFinished dateToCheck <= dateFinished
} else if (!start && track.started_reading_date > 0) { } else if (!start && track.startDate > 0) {
// Disallow end date to be set earlier than start date // Disallow end date to be set earlier than start date
val dateStarted = Instant.ofEpochMilli(track.started_reading_date) val dateStarted = Instant.ofEpochMilli(track.startDate)
.atZone(ZoneId.systemDefault()) .atZone(ZoneId.systemDefault())
.toLocalDate() .toLocalDate()
dateToCheck >= dateStarted dateToCheck >= dateStarted
@ -489,7 +488,7 @@ private data class TrackDateSelectorScreen(
// In UTC // In UTC
val initialSelection: Long val initialSelection: Long
get() { get() {
val millis = (if (start) track.started_reading_date else track.finished_reading_date) val millis = (if (start) track.startDate else track.finishDate)
.takeIf { it != 0L } .takeIf { it != 0L }
?: Instant.now().toEpochMilli() ?: Instant.now().toEpochMilli()
return convertEpochMillisZone(millis, ZoneOffset.systemDefault(), ZoneOffset.UTC) return convertEpochMillisZone(millis, ZoneOffset.systemDefault(), ZoneOffset.UTC)
@ -501,9 +500,9 @@ private data class TrackDateSelectorScreen(
val localMillis = convertEpochMillisZone(millis, ZoneOffset.UTC, ZoneOffset.systemDefault()) val localMillis = convertEpochMillisZone(millis, ZoneOffset.UTC, ZoneOffset.systemDefault())
coroutineScope.launchNonCancellable { coroutineScope.launchNonCancellable {
if (start) { if (start) {
service.setRemoteStartDate(track, localMillis) service.setRemoteStartDate(track.toDbTrack(), localMillis)
} else { } else {
service.setRemoteFinishDate(track, localMillis) service.setRemoteFinishDate(track.toDbTrack(), localMillis)
} }
} }
} }
@ -600,9 +599,9 @@ private data class TrackDateRemoverScreen(
fun removeDate() { fun removeDate() {
coroutineScope.launchNonCancellable { coroutineScope.launchNonCancellable {
if (start) { if (start) {
service.setRemoteStartDate(track, 0) service.setRemoteStartDate(track.toDbTrack(), 0)
} else { } else {
service.setRemoteFinishDate(track, 0) service.setRemoteFinishDate(track.toDbTrack(), 0)
} }
} }
} }
@ -679,7 +678,7 @@ data class TrackServiceSearchScreen(
} }
} }
fun registerTracking(item: Track) { fun registerTracking(item: TrackSearch) {
coroutineScope.launchNonCancellable { service.registerTracking(item, mangaId) } coroutineScope.launchNonCancellable { service.registerTracking(item, mangaId) }
} }

View File

@ -1,6 +1,6 @@
package eu.kanade.tachiyomi.ui.manga.track package eu.kanade.tachiyomi.ui.manga.track
import eu.kanade.tachiyomi.data.database.models.Track
import eu.kanade.tachiyomi.data.track.TrackService import eu.kanade.tachiyomi.data.track.TrackService
import tachiyomi.domain.track.model.Track
data class TrackItem(val track: Track?, val service: TrackService) data class TrackItem(val track: Track?, val service: TrackService)

View File

@ -13,7 +13,6 @@ import eu.kanade.domain.ui.UiPreferences
import eu.kanade.tachiyomi.BuildConfig import eu.kanade.tachiyomi.BuildConfig
import eu.kanade.tachiyomi.core.security.SecurityPreferences import eu.kanade.tachiyomi.core.security.SecurityPreferences
import eu.kanade.tachiyomi.data.backup.BackupCreatorJob import eu.kanade.tachiyomi.data.backup.BackupCreatorJob
import eu.kanade.tachiyomi.data.database.models.Manga
import eu.kanade.tachiyomi.data.library.LibraryUpdateJob import eu.kanade.tachiyomi.data.library.LibraryUpdateJob
import eu.kanade.tachiyomi.data.preference.PreferenceValues import eu.kanade.tachiyomi.data.preference.PreferenceValues
import eu.kanade.tachiyomi.data.track.TrackManager import eu.kanade.tachiyomi.data.track.TrackManager
@ -59,6 +58,7 @@ import tachiyomi.domain.library.service.LibraryPreferences.Companion.MANGA_NON_C
import tachiyomi.domain.manga.interactor.GetManga import tachiyomi.domain.manga.interactor.GetManga
import tachiyomi.domain.manga.interactor.GetMangaBySource import tachiyomi.domain.manga.interactor.GetMangaBySource
import tachiyomi.domain.manga.interactor.InsertMergedReference import tachiyomi.domain.manga.interactor.InsertMergedReference
import tachiyomi.domain.manga.model.Manga
import tachiyomi.domain.manga.model.MangaUpdate import tachiyomi.domain.manga.model.MangaUpdate
import tachiyomi.domain.manga.model.MergedMangaReference import tachiyomi.domain.manga.model.MergedMangaReference
import tachiyomi.domain.manga.model.TriStateFilter import tachiyomi.domain.manga.model.TriStateFilter
@ -73,7 +73,6 @@ import uy.kohesive.injekt.injectLazy
import java.io.File import java.io.File
import java.net.URI import java.net.URI
import java.net.URISyntaxException import java.net.URISyntaxException
import tachiyomi.domain.manga.model.Manga as DomainManga
object EXHMigrations { object EXHMigrations {
private val handler: DatabaseHandler by injectLazy() private val handler: DatabaseHandler by injectLazy()
@ -558,28 +557,39 @@ object EXHMigrations {
return false return false
} }
fun migrateBackupEntry(manga: Manga) { fun migrateBackupEntry(manga: Manga): Manga {
if (manga.source == 6907L) { var newManga = manga
// Migrate the old source to the delegated one if (newManga.source == 6907L) {
manga.source = NHentai.otherId newManga = newManga.copy(
// Migrate nhentai URLs // Migrate the old source to the delegated one
manga.url = getUrlWithoutDomain(manga.url) source = NHentai.otherId,
// Migrate nhentai URLs
url = getUrlWithoutDomain(newManga.url),
)
} }
// Migrate Tsumino source IDs // Migrate Tsumino source IDs
if (manga.source == 6909L) { if (newManga.source == 6909L) {
manga.source = TSUMINO_SOURCE_ID newManga = newManga.copy(
source = TSUMINO_SOURCE_ID,
)
} }
if (manga.source == 6912L) { if (newManga.source == 6912L) {
manga.source = HBROWSE_SOURCE_ID newManga = newManga.copy(
manga.url = manga.url + "/c00001/" source = HBROWSE_SOURCE_ID,
url = newManga.url + "/c00001/",
)
} }
// Allow importing of EHentai extension backups // Allow importing of EHentai extension backups
if (manga.source in BlacklistedSources.EHENTAI_EXT_SOURCES) { if (newManga.source in BlacklistedSources.EHENTAI_EXT_SOURCES) {
manga.source = EH_SOURCE_ID newManga = newManga.copy(
source = EH_SOURCE_ID,
)
} }
return newManga
} }
private fun getUrlWithoutDomain(orig: String): String { private fun getUrlWithoutDomain(orig: String): String {
@ -624,7 +634,7 @@ object EXHMigrations {
} }
} }
private fun readMangaConfig(manga: DomainManga): MangaConfig? { private fun readMangaConfig(manga: Manga): MangaConfig? {
return MangaConfig.readFromUrl(manga.url) return MangaConfig.readFromUrl(manga.url)
} }
@ -650,7 +660,7 @@ object EXHMigrations {
} }
} }
private data class LoadedMangaSource(val source: Source, val manga: DomainManga) private data class LoadedMangaSource(val source: Source, val manga: Manga)
private fun updateSourceId(newId: Long, oldId: Long) { private fun updateSourceId(newId: Long, oldId: Long) {
runBlocking { runBlocking {

View File

@ -17,7 +17,7 @@ class MangaDexFollowsScreenModel(sourceId: Long) : BrowseSourceScreenModel(sourc
return MangaDexFollowsPagingSource(source.getMainSource() as MangaDex) return MangaDexFollowsPagingSource(source.getMainSource() as MangaDex)
} }
override fun Flow<Manga>.combineMetadata(dbManga: Manga, metadata: RaisedSearchMetadata?): Flow<Pair<Manga, RaisedSearchMetadata?>> { override fun Flow<Manga>.combineMetadata(metadata: RaisedSearchMetadata?): Flow<Pair<Manga, RaisedSearchMetadata?>> {
return map { it to metadata } return map { it to metadata }
} }

View File

@ -27,7 +27,7 @@ class MangaDexSimilarScreenModel(
return MangaDexSimilarPagingSource(manga, source.getMainSource() as MangaDex) return MangaDexSimilarPagingSource(manga, source.getMainSource() as MangaDex)
} }
override fun Flow<Manga>.combineMetadata(dbManga: Manga, metadata: RaisedSearchMetadata?): Flow<Pair<Manga, RaisedSearchMetadata?>> { override fun Flow<Manga>.combineMetadata(metadata: RaisedSearchMetadata?): Flow<Pair<Manga, RaisedSearchMetadata?>> {
return map { it to metadata } return map { it to metadata }
} }