mirror of
https://github.com/mihonapp/mihon.git
synced 2026-10-16 13:48:09 -07:00
Rework database queries for the new schema (#4022)
* query - category Key every single-row statement on :id and list columns on one line. A lookup by primary key no longer needs LIMIT 1, and resetting every category's flags takes a value rather than a nullable nobody passed. Assisted-by: Claude:claude-opus-5 * query - chapter List columns in the table's order and name bind and mapper parameters after the columns they fill. Inserts pass their arguments by name, since reordering the columns put the upload and fetch dates side by side with the same type. With (manga_id, remote_url) unique, looking a chapter up by them no longer needs LIMIT 1. Available scanlators come back distinct instead of one row per chapter, which is all the only caller wanted. insert was insertReturningId without the RETURNING, so restore uses the latter and the former goes, along with the unused getChapterByUrl. Inserting skips a chapter that a sync running alongside stored first, rather than failing the whole batch and losing every new chapter of that update. Assisted-by: Claude:claude-opus-5 * query - excluded_scanlator An entry excludes a scanlator at most once now. Inserting one that is already excluded does nothing, so restoring a backup that lists one twice drops the repeat instead of failing the entry, and restore no longer reads the stored exclusions first to skip them itself. Saving an unchanged selection no longer runs an empty delete, which still made every chapter query run again. Assisted-by: Claude:claude-opus-5 * query - extension_store Take the upsert's new values from excluded and drop a WHERE its conflict target already implies. That also retires a `signing_key =: signingKey` that only parsed by accident. Assisted-by: Claude:claude-opus-5 * query - history Restoring an entry's history looked up the history row and, failing that, the chapter for every item in the backup, one query each. It now reads the entry's chapters and history once and matches them in memory, which leaves getHistoryByChapterUrlAndMangaId without callers. Creating a backup looked up the chapter of every history row one at a time and failed on a row whose chapter was gone. It reads the entry's chapters once and leaves such a row out. The upsert takes its new values from excluded, drops a trailing WHERE its conflict target already pins, and names its parameters after the columns they fill. Assisted-by: Claude:claude-opus-5 * query - manga List columns in the table's order and name bind and mapper parameters after the columns they fill, forwarding mapper arguments by name. With (source_id, remote_url) unique, looking an entry up by them no longer needs LIMIT 1, and fetching a network entry becomes a single upsert that refreshes an existing row only while it is outside the library, instead of an insert guarded by NOT EXISTS followed by an update of the same row. "Is anything read" checks use EXISTS rather than IN over a DISTINCT subquery, category membership drops a coalesce on a column that is never null, and the unused getAllManga goes. Applying the default chapter flags to the library is one transaction instead of one per entry, and restore checks backup entries against the stored urls as a set rather than a list. Assisted-by: Claude:claude-opus-5 * query - manga_category An entry belongs to a category at most once now. Inserting a pairing that already exists does nothing, so setting an entry's categories from a list that names one twice no longer fails, which restore could produce when two backup categories resolve to the same local one. Assisted-by: Claude:claude-opus-5 * query - manga_track List columns in the table's order and name bind and mapper parameters after the columns they fill. That retires the sync-era :syncId, :mediaId, :totalChapter and :trackingUrl. Assisted-by: Claude:claude-opus-5 * query - source List columns in the table's order and name the upsert's parameters after its columns. The upsert takes the conflicting row's values from excluded instead of rebinding them, and drops a WHERE its conflict target already implies. The call passes its two string arguments by name, since swapping them would still compile. Assisted-by: Claude:claude-opus-5 * query - historyView The latest read entry is read from the tables through the index on history's read time instead of aggregating the whole view for one row: ~50ms to under 1ms on a 114k chapter library. Assisted-by: Claude:claude-opus-5 * query - libraryView List the columns like the other queries. Assisted-by: Claude:claude-opus-5 * query - updatesView Drop a coalesce on category_id, which is never null, from the queries over updatesView, and the unused getRecentUpdates. The view definitions themselves are unchanged, so no migration is needed. The read and unread lookups become separate queries with the status written into each. SQLite only uses a partial index when the query's own condition matches it, which a bound parameter never does, so the unread lookup the widget follows walked nearly the whole fetch date index, ~60ms each time on a 114k chapter library and after every chapter or history write. With the status written in, it's served by the partial index over unread chapters, ~8ms. Assisted-by: Claude:claude-opus-5 * Query browse entries only while they are shown Each loaded entry kept a live query for as long as the screen existed, so every write to manga re-ran all of them. An entry now starts from the row the listing stored and queries only while shown. Assisted-by: Claude:claude-opus-5 * Read library selection categories from memory The change category dialog and category updates read each selected entry's categories from the loaded library instead of querying them one at a time. Assisted-by: Claude:claude-opus-5
This commit is contained in:
+5
-3
@@ -62,9 +62,11 @@ class MangaBackupCreator(
|
|||||||
if (options.history) {
|
if (options.history) {
|
||||||
val historyByMangaId = getHistory.await(manga.id)
|
val historyByMangaId = getHistory.await(manga.id)
|
||||||
if (historyByMangaId.isNotEmpty()) {
|
if (historyByMangaId.isNotEmpty()) {
|
||||||
val history = historyByMangaId.map { history ->
|
val chapterUrlsById = chapterRepository.getChapterByMangaId(manga.id).associate { it.id to it.url }
|
||||||
val chapter = checkNotNull(chapterRepository.getChapterById(history.chapterId))
|
val history = historyByMangaId.mapNotNull { history ->
|
||||||
BackupHistory(chapter.url, history.readAt?.time ?: 0L, history.readDuration)
|
// A chapter removed since its history was read takes that history with it
|
||||||
|
val url = chapterUrlsById[history.chapterId] ?: return@mapNotNull null
|
||||||
|
BackupHistory(url, history.readAt?.time ?: 0L, history.readDuration)
|
||||||
}
|
}
|
||||||
if (history.isNotEmpty()) {
|
if (history.isNotEmpty()) {
|
||||||
mangaObject.history = history
|
mangaObject.history = history
|
||||||
|
|||||||
+3
-1
@@ -32,6 +32,7 @@ import eu.kanade.tachiyomi.util.removeCovers
|
|||||||
import kotlinx.coroutines.flow.MutableStateFlow
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
import kotlinx.coroutines.flow.SharingStarted
|
import kotlinx.coroutines.flow.SharingStarted
|
||||||
import kotlinx.coroutines.flow.StateFlow
|
import kotlinx.coroutines.flow.StateFlow
|
||||||
|
import kotlinx.coroutines.flow.WhileSubscribed
|
||||||
import kotlinx.coroutines.flow.distinctUntilChanged
|
import kotlinx.coroutines.flow.distinctUntilChanged
|
||||||
import kotlinx.coroutines.flow.emptyFlow
|
import kotlinx.coroutines.flow.emptyFlow
|
||||||
import kotlinx.coroutines.flow.filter
|
import kotlinx.coroutines.flow.filter
|
||||||
@@ -56,6 +57,7 @@ import tachiyomi.domain.manga.model.MangaWithChapterCount
|
|||||||
import tachiyomi.domain.source.interactor.GetRemoteManga
|
import tachiyomi.domain.source.interactor.GetRemoteManga
|
||||||
import tachiyomi.domain.source.service.SourceManager
|
import tachiyomi.domain.source.service.SourceManager
|
||||||
import kotlin.time.Clock
|
import kotlin.time.Clock
|
||||||
|
import kotlin.time.Duration.Companion.seconds
|
||||||
import eu.kanade.tachiyomi.source.model.Filter as SourceModelFilter
|
import eu.kanade.tachiyomi.source.model.Filter as SourceModelFilter
|
||||||
|
|
||||||
@AssistedInject
|
@AssistedInject
|
||||||
@@ -133,7 +135,7 @@ class BrowseSourceViewModel(
|
|||||||
pagingData.map { manga ->
|
pagingData.map { manga ->
|
||||||
getManga.subscribe(manga.url, manga.source)
|
getManga.subscribe(manga.url, manga.source)
|
||||||
.map { it ?: manga }
|
.map { it ?: manga }
|
||||||
.stateIn(viewModelScope)
|
.stateIn(viewModelScope, SharingStarted.WhileSubscribed(5.seconds), manga)
|
||||||
}
|
}
|
||||||
.filter { !hideInLibraryItems || !it.value.favorite }
|
.filter { !hideInLibraryItems || !it.value.favorite }
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -458,34 +458,19 @@ class LibraryViewModel(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
private suspend fun getCategoryIds(manga: Manga): Set<Long> {
|
||||||
* Returns the common categories for the given list of manga.
|
return state.value.libraryData.favoritesById[manga.id]
|
||||||
*
|
?.libraryManga
|
||||||
* @param mangas the list of manga.
|
?.categories
|
||||||
*/
|
// The library reports no category as 0
|
||||||
private suspend fun getCommonCategories(mangas: List<Manga>): Collection<Category> {
|
?.filterTo(mutableSetOf()) { it != 0L }
|
||||||
if (mangas.isEmpty()) return emptyList()
|
?: getCategories.await(manga.id).mapTo(mutableSetOf()) { it.id }
|
||||||
return mangas
|
|
||||||
.map { getCategories.await(it.id).toSet() }
|
|
||||||
.reduce { set1, set2 -> set1.intersect(set2) }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun getNextUnreadChapter(manga: Manga): Chapter? {
|
suspend fun getNextUnreadChapter(manga: Manga): Chapter? {
|
||||||
return getChaptersByMangaId.await(manga.id, applyScanlatorFilter = true).getNextUnread(manga, downloadManager)
|
return getChaptersByMangaId.await(manga.id, applyScanlatorFilter = true).getNextUnread(manga, downloadManager)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the mix (non-common) categories for the given list of manga.
|
|
||||||
*
|
|
||||||
* @param mangas the list of manga.
|
|
||||||
*/
|
|
||||||
private suspend fun getMixCategories(mangas: List<Manga>): Collection<Category> {
|
|
||||||
if (mangas.isEmpty()) return emptyList()
|
|
||||||
val mangaCategories = mangas.map { getCategories.await(it.id).toSet() }
|
|
||||||
val common = mangaCategories.reduce { set1, set2 -> set1.intersect(set2) }
|
|
||||||
return mangaCategories.flatten().distinct().subtract(common)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Queues the amount specified of unread chapters from the list of selected manga
|
* Queues the amount specified of unread chapters from the list of selected manga
|
||||||
*/
|
*/
|
||||||
@@ -599,8 +584,7 @@ class LibraryViewModel(
|
|||||||
fun setMangaCategories(mangaList: List<Manga>, addCategories: List<Long>, removeCategories: List<Long>) {
|
fun setMangaCategories(mangaList: List<Manga>, addCategories: List<Long>, removeCategories: List<Long>) {
|
||||||
viewModelScope.launchNonCancellable {
|
viewModelScope.launchNonCancellable {
|
||||||
mangaList.forEach { manga ->
|
mangaList.forEach { manga ->
|
||||||
val categoryIds = getCategories.await(manga.id)
|
val categoryIds = getCategoryIds(manga)
|
||||||
.map { it.id }
|
|
||||||
.subtract(removeCategories.toSet())
|
.subtract(removeCategories.toSet())
|
||||||
.plus(addCategories)
|
.plus(addCategories)
|
||||||
.toList()
|
.toList()
|
||||||
@@ -729,13 +713,12 @@ class LibraryViewModel(
|
|||||||
// Hide the default category because it has a different behavior than the ones from db.
|
// Hide the default category because it has a different behavior than the ones from db.
|
||||||
val categories = state.value.displayedCategories.filter { it.id != 0L }
|
val categories = state.value.displayedCategories.filter { it.id != 0L }
|
||||||
|
|
||||||
// Get indexes of the common categories to preselect.
|
val mangaCategoryIds = mangaList.map { getCategoryIds(it) }
|
||||||
val common = getCommonCategories(mangaList)
|
val common = mangaCategoryIds.reduceOrNull { set1, set2 -> set1 intersect set2 }.orEmpty()
|
||||||
// Get indexes of the mix categories to preselect.
|
val mix = mangaCategoryIds.flatten().toSet() - common
|
||||||
val mix = getMixCategories(mangaList)
|
|
||||||
val preselected = categories
|
val preselected = categories
|
||||||
.map {
|
.map {
|
||||||
when (it) {
|
when (it.id) {
|
||||||
in common -> CheckboxState.State.Checked(it)
|
in common -> CheckboxState.State.Checked(it)
|
||||||
in mix -> CheckboxState.TriState.Exclude(it)
|
in mix -> CheckboxState.TriState.Exclude(it)
|
||||||
else -> CheckboxState.State.None(it)
|
else -> CheckboxState.State.None(it)
|
||||||
|
|||||||
@@ -34,11 +34,12 @@ class RestoreRepositoryImpl(
|
|||||||
private val trackRepository: TrackRepository,
|
private val trackRepository: TrackRepository,
|
||||||
) : RestoreRepository {
|
) : RestoreRepository {
|
||||||
|
|
||||||
override suspend fun getMangaUrlsBySourceId(): Map<Long, List<String>> {
|
override suspend fun getMangaUrlsBySourceId(): Map<Long, Set<String>> {
|
||||||
return database.mangaQueries
|
return database.mangaQueries
|
||||||
.getAllMangaSourceAndUrl()
|
.getAllMangaSourceAndUrl()
|
||||||
.awaitAsList()
|
.awaitAsList()
|
||||||
.groupBy({ it.source_id }, { it.remote_url })
|
.groupBy({ it.source_id }, { it.remote_url })
|
||||||
|
.mapValues { it.value.toSet() }
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun restoreManga(entries: List<RestoredManga>, update: suspend (Manga) -> MangaUpdate) {
|
override suspend fun restoreManga(entries: List<RestoredManga>, update: suspend (Manga) -> MangaUpdate) {
|
||||||
@@ -93,49 +94,49 @@ class RestoreRepositoryImpl(
|
|||||||
|
|
||||||
private suspend fun updateManga(manga: Manga): Manga {
|
private suspend fun updateManga(manga: Manga): Manga {
|
||||||
database.mangaQueries.updateFromBackup(
|
database.mangaQueries.updateFromBackup(
|
||||||
favoriteAt = manga.favoriteAt,
|
userFavoriteAt = manga.favoriteAt,
|
||||||
artist = manga.artist,
|
remoteArtist = manga.artist,
|
||||||
author = manga.author,
|
remoteAuthor = manga.author,
|
||||||
description = manga.description,
|
remoteDescription = manga.description,
|
||||||
genre = manga.genre,
|
remoteGenre = manga.genre,
|
||||||
title = manga.title,
|
remoteTitle = manga.title,
|
||||||
status = manga.status,
|
remoteStatus = manga.status,
|
||||||
thumbnailUrl = manga.thumbnailUrl,
|
remoteCover = manga.thumbnailUrl,
|
||||||
lastUpdate = manga.lastUpdate,
|
stateChapterLastUpdate = manga.lastUpdate,
|
||||||
initialized = manga.initialized,
|
stateInitialized = manga.initialized,
|
||||||
viewer = manga.viewerFlags,
|
userReaderFlags = manga.viewerFlags,
|
||||||
chapterFlags = manga.chapterFlags,
|
userChapterFlags = manga.chapterFlags,
|
||||||
coverLastModified = manga.coverLastModified,
|
stateCoverLastModified = manga.coverLastModified,
|
||||||
mangaId = manga.id,
|
id = manga.id,
|
||||||
updateStrategy = manga.updateStrategy,
|
remoteUpdateStrategy = manga.updateStrategy,
|
||||||
notes = manga.notes,
|
userNotes = manga.notes,
|
||||||
memo = manga.memo,
|
remoteMemo = manga.memo,
|
||||||
)
|
)
|
||||||
return manga
|
return manga
|
||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun insertManga(manga: Manga): Long {
|
private suspend fun insertManga(manga: Manga): Long {
|
||||||
return database.mangaQueries.insertReturningId(
|
return database.mangaQueries.insertReturningId(
|
||||||
favoriteAt = manga.favoriteAt,
|
userFavoriteAt = manga.favoriteAt,
|
||||||
source = manga.source,
|
sourceId = manga.source,
|
||||||
url = manga.url,
|
remoteUrl = manga.url,
|
||||||
artist = manga.artist,
|
remoteArtist = manga.artist,
|
||||||
author = manga.author,
|
remoteAuthor = manga.author,
|
||||||
description = manga.description,
|
remoteDescription = manga.description,
|
||||||
genre = manga.genre,
|
remoteGenre = manga.genre,
|
||||||
title = manga.title,
|
remoteTitle = manga.title,
|
||||||
status = manga.status,
|
remoteStatus = manga.status,
|
||||||
thumbnailUrl = manga.thumbnailUrl,
|
remoteCover = manga.thumbnailUrl,
|
||||||
lastUpdate = manga.lastUpdate,
|
stateChapterLastUpdate = manga.lastUpdate,
|
||||||
nextUpdate = 0L,
|
stateChapterNextUpdate = 0L,
|
||||||
calculateInterval = 0L,
|
stateChapterFetchInterval = 0L,
|
||||||
initialized = manga.initialized,
|
stateInitialized = manga.initialized,
|
||||||
viewerFlags = manga.viewerFlags,
|
userReaderFlags = manga.viewerFlags,
|
||||||
chapterFlags = manga.chapterFlags,
|
userChapterFlags = manga.chapterFlags,
|
||||||
coverLastModified = manga.coverLastModified,
|
stateCoverLastModified = manga.coverLastModified,
|
||||||
updateStrategy = manga.updateStrategy,
|
remoteUpdateStrategy = manga.updateStrategy,
|
||||||
notes = manga.notes,
|
userNotes = manga.notes,
|
||||||
memo = manga.memo,
|
remoteMemo = manga.memo,
|
||||||
)
|
)
|
||||||
.awaitAsOne()
|
.awaitAsOne()
|
||||||
}
|
}
|
||||||
@@ -183,28 +184,29 @@ class RestoreRepositoryImpl(
|
|||||||
.partition { it.id > 0 }
|
.partition { it.id > 0 }
|
||||||
|
|
||||||
newChapters.forEach { chapter ->
|
newChapters.forEach { chapter ->
|
||||||
database.chapterQueries.insert(
|
database.chapterQueries.insertReturningId(
|
||||||
mangaId = chapter.mangaId,
|
mangaId = chapter.mangaId,
|
||||||
url = chapter.url,
|
remoteUrl = chapter.url,
|
||||||
name = chapter.name,
|
remoteName = chapter.name,
|
||||||
scanlator = chapter.scanlator,
|
remoteScanlator = chapter.scanlator,
|
||||||
read = chapter.read,
|
userRead = chapter.read,
|
||||||
bookmark = chapter.bookmark,
|
userBookmark = chapter.bookmark,
|
||||||
lastPageRead = chapter.lastPageRead,
|
userLastPageRead = chapter.lastPageRead,
|
||||||
chapterNumber = chapter.chapterNumber,
|
remoteChapterNumber = chapter.chapterNumber,
|
||||||
sourceOrder = chapter.sourceOrder,
|
remoteOrder = chapter.sourceOrder,
|
||||||
dateFetch = chapter.dateFetch,
|
stateDateFetch = chapter.dateFetch,
|
||||||
dateUpload = chapter.dateUpload,
|
remoteDateUpload = chapter.dateUpload,
|
||||||
memo = chapter.memo,
|
remoteMemo = chapter.memo,
|
||||||
)
|
)
|
||||||
|
.awaitAsOneOrNull()
|
||||||
}
|
}
|
||||||
existingChapters.forEach { chapter ->
|
existingChapters.forEach { chapter ->
|
||||||
database.chapterQueries.updateFromBackup(
|
database.chapterQueries.updateFromBackup(
|
||||||
read = chapter.read,
|
userRead = chapter.read,
|
||||||
bookmark = chapter.bookmark,
|
userBookmark = chapter.bookmark,
|
||||||
lastPageRead = chapter.lastPageRead,
|
userLastPageRead = chapter.lastPageRead,
|
||||||
chapterId = chapter.id,
|
id = chapter.id,
|
||||||
memo = chapter.memo,
|
remoteMemo = chapter.memo,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -213,28 +215,30 @@ class RestoreRepositoryImpl(
|
|||||||
this.copy(id = 0L, mangaId = 0L, dateFetch = 0L, dateUpload = 0L)
|
this.copy(id = 0L, mangaId = 0L, dateFetch = 0L, dateUpload = 0L)
|
||||||
|
|
||||||
private suspend fun restoreHistory(manga: Manga, restoredHistory: List<RestoredHistory>) {
|
private suspend fun restoreHistory(manga: Manga, restoredHistory: List<RestoredHistory>) {
|
||||||
|
if (restoredHistory.isEmpty()) return
|
||||||
|
val chapterIdsByUrl = chapterRepository.getChapterByMangaId(manga.id).associate { it.url to it.id }
|
||||||
|
val dbHistoryByChapterId = database.historyQueries
|
||||||
|
.getHistoryByMangaId(manga.id)
|
||||||
|
.awaitAsList()
|
||||||
|
.associateBy { it.chapter_id }
|
||||||
|
|
||||||
val toUpdate = restoredHistory
|
val toUpdate = restoredHistory
|
||||||
.groupBy { it.chapterUrl }
|
// Chapter doesn't exist; skip
|
||||||
.mapNotNull { (chapterUrl, copies) ->
|
.filter { it.chapterUrl in chapterIdsByUrl }
|
||||||
|
// A backup of a library that still had duplicate chapters carries a history entry for
|
||||||
|
// each copy; they are one chapter now, read as late as any copy and for as long as all
|
||||||
|
.groupBy { chapterIdsByUrl.getValue(it.chapterUrl) }
|
||||||
|
.map { (chapterId, copies) ->
|
||||||
val readAt = copies.maxOf { it.readAt?.time ?: 0L }
|
val readAt = copies.maxOf { it.readAt?.time ?: 0L }
|
||||||
val readDuration = copies.sumOf { it.readDuration }
|
val readDuration = copies.sumOf { it.readDuration }
|
||||||
val dbHistory = database.historyQueries
|
val dbHistory = dbHistoryByChapterId[chapterId]
|
||||||
.getHistoryByChapterUrlAndMangaId(chapterUrl, manga.id)
|
|
||||||
.awaitAsOneOrNull()
|
|
||||||
|
|
||||||
if (dbHistory == null) {
|
|
||||||
val chapter = database.chapterQueries
|
|
||||||
.getChapterByUrlAndMangaId(chapterUrl, manga.id)
|
|
||||||
.awaitAsOneOrNull()
|
|
||||||
// Chapter doesn't exist; skip
|
|
||||||
?: return@mapNotNull null
|
|
||||||
// New history entry
|
// New history entry
|
||||||
return@mapNotNull Triple(chapter.id, Date(readAt), readDuration)
|
?: return@map Triple(chapterId, Date(readAt), readDuration)
|
||||||
}
|
|
||||||
|
|
||||||
// Update history entry
|
// Update history entry. 0 is kept rather than written as NULL, since it marks history
|
||||||
|
// the user removed.
|
||||||
Triple(
|
Triple(
|
||||||
dbHistory.chapter_id,
|
chapterId,
|
||||||
Date(max(readAt, dbHistory.read_at?.time ?: 0L)),
|
Date(max(readAt, dbHistory.read_at?.time ?: 0L)),
|
||||||
max(readDuration, dbHistory.read_duration) - dbHistory.read_duration,
|
max(readDuration, dbHistory.read_duration) - dbHistory.read_duration,
|
||||||
)
|
)
|
||||||
@@ -271,15 +275,15 @@ class RestoreRepositoryImpl(
|
|||||||
existingTracks.forEach { track ->
|
existingTracks.forEach { track ->
|
||||||
database.manga_trackQueries.update(
|
database.manga_trackQueries.update(
|
||||||
mangaId = track.mangaId,
|
mangaId = track.mangaId,
|
||||||
syncId = track.trackerId,
|
trackerId = track.trackerId,
|
||||||
mediaId = track.remoteId,
|
remoteId = track.remoteId,
|
||||||
libraryId = track.libraryId,
|
libraryId = track.libraryId,
|
||||||
title = track.title,
|
title = track.title,
|
||||||
lastChapterRead = track.lastChapterRead,
|
lastChapterRead = track.lastChapterRead,
|
||||||
totalChapter = track.totalChapters,
|
totalChapters = track.totalChapters,
|
||||||
status = track.status,
|
status = track.status,
|
||||||
score = track.score,
|
score = track.score,
|
||||||
trackingUrl = track.remoteUrl,
|
remoteUrl = track.remoteUrl,
|
||||||
startDate = track.startDate,
|
startDate = track.startDate,
|
||||||
finishDate = track.finishDate,
|
finishDate = track.finishDate,
|
||||||
`private` = track.private,
|
`private` = track.private,
|
||||||
@@ -289,11 +293,6 @@ class RestoreRepositoryImpl(
|
|||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun restoreExcludedScanlators(manga: Manga, excludedScanlators: List<String>) {
|
private suspend fun restoreExcludedScanlators(manga: Manga, excludedScanlators: List<String>) {
|
||||||
if (excludedScanlators.isEmpty()) return
|
excludedScanlators.forEach { database.excluded_scanlatorQueries.insert(manga.id, it) }
|
||||||
val existingExcludedScanlators = database.excluded_scanlatorQueries
|
|
||||||
.getExcludedScanlatorsByMangaId(manga.id)
|
|
||||||
.awaitAsList()
|
|
||||||
val toInsert = excludedScanlators.filter { it !in existingExcludedScanlators }
|
|
||||||
toInsert.forEach { database.excluded_scanlatorQueries.insert(manga.id, it) }
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -61,14 +61,14 @@ class CategoryRepositoryImpl(
|
|||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun updateName(categoryId: Long, name: String) {
|
override suspend fun updateName(categoryId: Long, name: String) {
|
||||||
database.categoryQueries.updateName(name = name, categoryId = categoryId)
|
database.categoryQueries.updateName(name = name, id = categoryId)
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun updateFlags(categoryId: Long, flags: Long) {
|
override suspend fun updateFlags(categoryId: Long, flags: Long) {
|
||||||
database.categoryQueries.updateFlags(flags = flags, categoryId = categoryId)
|
database.categoryQueries.updateFlags(flags = flags, id = categoryId)
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun updateAllFlags(flags: Long?) {
|
override suspend fun updateAllFlags(flags: Long) {
|
||||||
database.categoryQueries.updateAllFlags(flags = flags)
|
database.categoryQueries.updateAllFlags(flags = flags)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -77,16 +77,16 @@ class CategoryRepositoryImpl(
|
|||||||
val current = database.categoryQueries.getUserCategoryIds().awaitAsList()
|
val current = database.categoryQueries.getUserCategoryIds().awaitAsList()
|
||||||
val ids = orderedIds.filter { it in current } + current.filterNot { it in orderedIds }
|
val ids = orderedIds.filter { it in current } + current.filterNot { it in orderedIds }
|
||||||
ids.forEachIndexed { index, categoryId ->
|
ids.forEachIndexed { index, categoryId ->
|
||||||
database.categoryQueries.updateOrder(order = -index - 2L, categoryId = categoryId)
|
database.categoryQueries.updateOrder(order = -index - 2L, id = categoryId)
|
||||||
}
|
}
|
||||||
ids.forEachIndexed { index, categoryId ->
|
ids.forEachIndexed { index, categoryId ->
|
||||||
database.categoryQueries.updateOrder(order = index.toLong(), categoryId = categoryId)
|
database.categoryQueries.updateOrder(order = index.toLong(), id = categoryId)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun delete(categoryId: Long) {
|
override suspend fun delete(categoryId: Long) {
|
||||||
database.categoryQueries.delete(categoryId = categoryId)
|
database.categoryQueries.delete(id = categoryId)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun mapCategory(
|
private fun mapCategory(
|
||||||
|
|||||||
@@ -57,30 +57,30 @@ class ChapterRepositoryImpl(
|
|||||||
val stored = added.filter { existing.add(it.mangaId to it.url) }.map { chapter ->
|
val stored = added.filter { existing.add(it.mangaId to it.url) }.map { chapter ->
|
||||||
val chapterId = database.chapterQueries.insertReturningId(
|
val chapterId = database.chapterQueries.insertReturningId(
|
||||||
mangaId = chapter.mangaId,
|
mangaId = chapter.mangaId,
|
||||||
url = chapter.url,
|
remoteUrl = chapter.url,
|
||||||
name = chapter.name,
|
remoteName = chapter.name,
|
||||||
scanlator = chapter.scanlator,
|
remoteScanlator = chapter.scanlator,
|
||||||
read = chapter.read,
|
userRead = chapter.read,
|
||||||
bookmark = chapter.bookmark,
|
userBookmark = chapter.bookmark,
|
||||||
lastPageRead = chapter.lastPageRead,
|
userLastPageRead = chapter.lastPageRead,
|
||||||
chapterNumber = chapter.chapterNumber,
|
remoteChapterNumber = chapter.chapterNumber,
|
||||||
sourceOrder = chapter.sourceOrder,
|
remoteOrder = chapter.sourceOrder,
|
||||||
dateFetch = chapter.dateFetch,
|
stateDateFetch = chapter.dateFetch,
|
||||||
dateUpload = chapter.dateUpload,
|
remoteDateUpload = chapter.dateUpload,
|
||||||
memo = chapter.memo,
|
remoteMemo = chapter.memo,
|
||||||
)
|
)
|
||||||
.awaitAsOne()
|
.awaitAsOne()
|
||||||
chapter.copy(id = chapterId)
|
chapter.copy(id = chapterId)
|
||||||
}
|
}
|
||||||
updated.forEach { chapterUpdate ->
|
updated.forEach { chapterUpdate ->
|
||||||
database.chapterQueries.updateRemote(
|
database.chapterQueries.updateRemote(
|
||||||
name = chapterUpdate.name,
|
remoteName = chapterUpdate.name,
|
||||||
scanlator = chapterUpdate.scanlator,
|
remoteScanlator = chapterUpdate.scanlator,
|
||||||
chapterNumber = chapterUpdate.chapterNumber,
|
remoteChapterNumber = chapterUpdate.chapterNumber,
|
||||||
sourceOrder = chapterUpdate.sourceOrder,
|
remoteOrder = chapterUpdate.sourceOrder,
|
||||||
dateUpload = chapterUpdate.dateUpload,
|
remoteDateUpload = chapterUpdate.dateUpload,
|
||||||
chapterId = chapterUpdate.id,
|
id = chapterUpdate.id,
|
||||||
memo = chapterUpdate.memo,
|
remoteMemo = chapterUpdate.memo,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
stored
|
stored
|
||||||
@@ -91,11 +91,11 @@ class ChapterRepositoryImpl(
|
|||||||
database.transaction {
|
database.transaction {
|
||||||
chapterUpdates.forEach { chapterUpdate ->
|
chapterUpdates.forEach { chapterUpdate ->
|
||||||
database.chapterQueries.update(
|
database.chapterQueries.update(
|
||||||
read = chapterUpdate.read,
|
userRead = chapterUpdate.read,
|
||||||
bookmark = chapterUpdate.bookmark,
|
userBookmark = chapterUpdate.bookmark,
|
||||||
lastPageRead = chapterUpdate.lastPageRead,
|
userLastPageRead = chapterUpdate.lastPageRead,
|
||||||
dateFetch = chapterUpdate.dateFetch,
|
stateDateFetch = chapterUpdate.dateFetch,
|
||||||
chapterId = chapterUpdate.id,
|
id = chapterUpdate.id,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -147,38 +147,37 @@ class ChapterRepositoryImpl(
|
|||||||
|
|
||||||
override suspend fun getChapterByUrlAndMangaId(url: String, mangaId: Long): Chapter? {
|
override suspend fun getChapterByUrlAndMangaId(url: String, mangaId: Long): Chapter? {
|
||||||
return database.chapterQueries
|
return database.chapterQueries
|
||||||
.getChapterByUrlAndMangaId(url, mangaId, ::mapChapter)
|
.getChapterByUrlAndMangaId(mangaId = mangaId, remoteUrl = url, mapper = ::mapChapter)
|
||||||
.awaitAsOneOrNull()
|
.awaitAsOneOrNull()
|
||||||
}
|
}
|
||||||
|
|
||||||
@Suppress("UNUSED_PARAMETER")
|
|
||||||
private fun mapChapter(
|
private fun mapChapter(
|
||||||
id: Long,
|
id: Long,
|
||||||
mangaId: Long,
|
mangaId: Long,
|
||||||
url: String,
|
remoteUrl: String,
|
||||||
name: String,
|
remoteName: String,
|
||||||
scanlator: String?,
|
remoteScanlator: String?,
|
||||||
read: Boolean,
|
remoteChapterNumber: Double,
|
||||||
bookmark: Boolean,
|
remoteDateUpload: Long,
|
||||||
lastPageRead: Long,
|
remoteOrder: Long,
|
||||||
chapterNumber: Double,
|
remoteMemo: JsonObject,
|
||||||
sourceOrder: Long,
|
userRead: Boolean,
|
||||||
dateFetch: Long,
|
userBookmark: Boolean,
|
||||||
dateUpload: Long,
|
userLastPageRead: Long,
|
||||||
memo: JsonObject,
|
stateDateFetch: Long,
|
||||||
): Chapter = Chapter(
|
): Chapter = Chapter(
|
||||||
id = id,
|
id = id,
|
||||||
mangaId = mangaId,
|
mangaId = mangaId,
|
||||||
read = read,
|
read = userRead,
|
||||||
bookmark = bookmark,
|
bookmark = userBookmark,
|
||||||
lastPageRead = lastPageRead,
|
lastPageRead = userLastPageRead,
|
||||||
dateFetch = dateFetch,
|
dateFetch = stateDateFetch,
|
||||||
sourceOrder = sourceOrder,
|
sourceOrder = remoteOrder,
|
||||||
url = url,
|
url = remoteUrl,
|
||||||
name = name,
|
name = remoteName,
|
||||||
dateUpload = dateUpload,
|
dateUpload = remoteDateUpload,
|
||||||
chapterNumber = chapterNumber,
|
chapterNumber = remoteChapterNumber,
|
||||||
scanlator = scanlator,
|
scanlator = remoteScanlator,
|
||||||
memo = memo,
|
memo = remoteMemo,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,72 +9,72 @@ import tachiyomi.domain.manga.model.MangaWithChapterCount
|
|||||||
object MangaMapper {
|
object MangaMapper {
|
||||||
fun mapManga(
|
fun mapManga(
|
||||||
id: Long,
|
id: Long,
|
||||||
source: Long,
|
sourceId: Long,
|
||||||
url: String,
|
remoteUrl: String,
|
||||||
artist: String?,
|
remoteTitle: String,
|
||||||
author: String?,
|
remoteAuthor: String?,
|
||||||
description: String?,
|
remoteArtist: String?,
|
||||||
genre: List<String>?,
|
remoteDescription: String?,
|
||||||
title: String,
|
remoteGenre: List<String>?,
|
||||||
status: Long,
|
remoteStatus: Long,
|
||||||
thumbnailUrl: String?,
|
remoteCover: String?,
|
||||||
lastUpdate: Long?,
|
remoteUpdateStrategy: UpdateStrategy,
|
||||||
nextUpdate: Long?,
|
remoteMemo: JsonObject,
|
||||||
initialized: Boolean,
|
userFavoriteAt: Long?,
|
||||||
viewerFlags: Long,
|
userNotes: String,
|
||||||
chapterFlags: Long,
|
userReaderFlags: Long,
|
||||||
coverLastModified: Long,
|
userChapterFlags: Long,
|
||||||
updateStrategy: UpdateStrategy,
|
stateChapterLastUpdate: Long?,
|
||||||
calculateInterval: Long,
|
stateChapterNextUpdate: Long?,
|
||||||
notes: String,
|
stateChapterFetchInterval: Long,
|
||||||
memo: JsonObject,
|
stateCoverLastModified: Long,
|
||||||
favoriteAt: Long?,
|
stateInitialized: Boolean,
|
||||||
): Manga = Manga(
|
): Manga = Manga(
|
||||||
id = id,
|
id = id,
|
||||||
source = source,
|
source = sourceId,
|
||||||
lastUpdate = lastUpdate ?: 0,
|
favoriteAt = userFavoriteAt,
|
||||||
nextUpdate = nextUpdate ?: 0,
|
lastUpdate = stateChapterLastUpdate ?: 0,
|
||||||
fetchInterval = calculateInterval.toInt(),
|
nextUpdate = stateChapterNextUpdate ?: 0,
|
||||||
favoriteAt = favoriteAt,
|
fetchInterval = stateChapterFetchInterval.toInt(),
|
||||||
viewerFlags = viewerFlags,
|
viewerFlags = userReaderFlags,
|
||||||
chapterFlags = chapterFlags,
|
chapterFlags = userChapterFlags,
|
||||||
coverLastModified = coverLastModified,
|
coverLastModified = stateCoverLastModified,
|
||||||
url = url,
|
url = remoteUrl,
|
||||||
title = title,
|
title = remoteTitle,
|
||||||
artist = artist,
|
artist = remoteArtist,
|
||||||
author = author,
|
author = remoteAuthor,
|
||||||
description = description,
|
description = remoteDescription,
|
||||||
genre = genre,
|
genre = remoteGenre,
|
||||||
status = status,
|
status = remoteStatus,
|
||||||
thumbnailUrl = thumbnailUrl,
|
thumbnailUrl = remoteCover,
|
||||||
updateStrategy = updateStrategy,
|
updateStrategy = remoteUpdateStrategy,
|
||||||
initialized = initialized,
|
initialized = stateInitialized,
|
||||||
notes = notes,
|
notes = userNotes,
|
||||||
memo = memo,
|
memo = remoteMemo,
|
||||||
)
|
)
|
||||||
|
|
||||||
fun mapLibraryManga(
|
fun mapLibraryManga(
|
||||||
id: Long,
|
id: Long,
|
||||||
source: Long,
|
sourceId: Long,
|
||||||
url: String,
|
remoteUrl: String,
|
||||||
artist: String?,
|
remoteTitle: String,
|
||||||
author: String?,
|
remoteAuthor: String?,
|
||||||
description: String?,
|
remoteArtist: String?,
|
||||||
genre: List<String>?,
|
remoteDescription: String?,
|
||||||
title: String,
|
remoteGenre: List<String>?,
|
||||||
status: Long,
|
remoteStatus: Long,
|
||||||
thumbnailUrl: String?,
|
remoteCover: String?,
|
||||||
lastUpdate: Long?,
|
remoteUpdateStrategy: UpdateStrategy,
|
||||||
nextUpdate: Long?,
|
remoteMemo: JsonObject,
|
||||||
initialized: Boolean,
|
userFavoriteAt: Long,
|
||||||
viewerFlags: Long,
|
userNotes: String,
|
||||||
chapterFlags: Long,
|
userReaderFlags: Long,
|
||||||
coverLastModified: Long,
|
userChapterFlags: Long,
|
||||||
updateStrategy: UpdateStrategy,
|
stateChapterLastUpdate: Long?,
|
||||||
calculateInterval: Long,
|
stateChapterNextUpdate: Long?,
|
||||||
notes: String,
|
stateChapterFetchInterval: Long,
|
||||||
memo: JsonObject,
|
stateCoverLastModified: Long,
|
||||||
favoriteAt: Long?,
|
stateInitialized: Boolean,
|
||||||
totalCount: Long,
|
totalCount: Long,
|
||||||
readCount: Double,
|
readCount: Double,
|
||||||
latestUpload: Long,
|
latestUpload: Long,
|
||||||
@@ -85,26 +85,26 @@ object MangaMapper {
|
|||||||
): LibraryManga = LibraryManga(
|
): LibraryManga = LibraryManga(
|
||||||
manga = mapManga(
|
manga = mapManga(
|
||||||
id = id,
|
id = id,
|
||||||
source = source,
|
sourceId = sourceId,
|
||||||
url = url,
|
remoteUrl = remoteUrl,
|
||||||
artist = artist,
|
remoteTitle = remoteTitle,
|
||||||
author = author,
|
remoteAuthor = remoteAuthor,
|
||||||
description = description,
|
remoteArtist = remoteArtist,
|
||||||
genre = genre,
|
remoteDescription = remoteDescription,
|
||||||
title = title,
|
remoteGenre = remoteGenre,
|
||||||
status = status,
|
remoteStatus = remoteStatus,
|
||||||
thumbnailUrl = thumbnailUrl,
|
remoteCover = remoteCover,
|
||||||
lastUpdate = lastUpdate,
|
remoteUpdateStrategy = remoteUpdateStrategy,
|
||||||
nextUpdate = nextUpdate,
|
remoteMemo = remoteMemo,
|
||||||
initialized = initialized,
|
userFavoriteAt = userFavoriteAt,
|
||||||
viewerFlags = viewerFlags,
|
userNotes = userNotes,
|
||||||
chapterFlags = chapterFlags,
|
userReaderFlags = userReaderFlags,
|
||||||
coverLastModified = coverLastModified,
|
userChapterFlags = userChapterFlags,
|
||||||
updateStrategy = updateStrategy,
|
stateChapterLastUpdate = stateChapterLastUpdate,
|
||||||
calculateInterval = calculateInterval,
|
stateChapterNextUpdate = stateChapterNextUpdate,
|
||||||
notes = notes,
|
stateChapterFetchInterval = stateChapterFetchInterval,
|
||||||
memo = memo,
|
stateCoverLastModified = stateCoverLastModified,
|
||||||
favoriteAt = favoriteAt,
|
stateInitialized = stateInitialized,
|
||||||
),
|
),
|
||||||
categories = categories.split(",").map { it.toLong() },
|
categories = categories.split(",").map { it.toLong() },
|
||||||
totalChapters = totalCount,
|
totalChapters = totalCount,
|
||||||
@@ -117,51 +117,51 @@ object MangaMapper {
|
|||||||
|
|
||||||
fun mapMangaWithChapterCount(
|
fun mapMangaWithChapterCount(
|
||||||
id: Long,
|
id: Long,
|
||||||
source: Long,
|
sourceId: Long,
|
||||||
url: String,
|
remoteUrl: String,
|
||||||
artist: String?,
|
remoteTitle: String,
|
||||||
author: String?,
|
remoteAuthor: String?,
|
||||||
description: String?,
|
remoteArtist: String?,
|
||||||
genre: List<String>?,
|
remoteDescription: String?,
|
||||||
title: String,
|
remoteGenre: List<String>?,
|
||||||
status: Long,
|
remoteStatus: Long,
|
||||||
thumbnailUrl: String?,
|
remoteCover: String?,
|
||||||
lastUpdate: Long?,
|
remoteUpdateStrategy: UpdateStrategy,
|
||||||
nextUpdate: Long?,
|
remoteMemo: JsonObject,
|
||||||
initialized: Boolean,
|
userFavoriteAt: Long?,
|
||||||
viewerFlags: Long,
|
userNotes: String,
|
||||||
chapterFlags: Long,
|
userReaderFlags: Long,
|
||||||
coverLastModified: Long,
|
userChapterFlags: Long,
|
||||||
updateStrategy: UpdateStrategy,
|
stateChapterLastUpdate: Long?,
|
||||||
calculateInterval: Long,
|
stateChapterNextUpdate: Long?,
|
||||||
notes: String,
|
stateChapterFetchInterval: Long,
|
||||||
memo: JsonObject,
|
stateCoverLastModified: Long,
|
||||||
favoriteAt: Long?,
|
stateInitialized: Boolean,
|
||||||
totalCount: Long,
|
chapterCount: Long,
|
||||||
): MangaWithChapterCount = MangaWithChapterCount(
|
): MangaWithChapterCount = MangaWithChapterCount(
|
||||||
manga = mapManga(
|
manga = mapManga(
|
||||||
id = id,
|
id = id,
|
||||||
source = source,
|
sourceId = sourceId,
|
||||||
url = url,
|
remoteUrl = remoteUrl,
|
||||||
artist = artist,
|
remoteTitle = remoteTitle,
|
||||||
author = author,
|
remoteAuthor = remoteAuthor,
|
||||||
description = description,
|
remoteArtist = remoteArtist,
|
||||||
genre = genre,
|
remoteDescription = remoteDescription,
|
||||||
title = title,
|
remoteGenre = remoteGenre,
|
||||||
status = status,
|
remoteStatus = remoteStatus,
|
||||||
thumbnailUrl = thumbnailUrl,
|
remoteCover = remoteCover,
|
||||||
lastUpdate = lastUpdate,
|
remoteUpdateStrategy = remoteUpdateStrategy,
|
||||||
nextUpdate = nextUpdate,
|
remoteMemo = remoteMemo,
|
||||||
initialized = initialized,
|
userFavoriteAt = userFavoriteAt,
|
||||||
viewerFlags = viewerFlags,
|
userNotes = userNotes,
|
||||||
chapterFlags = chapterFlags,
|
userReaderFlags = userReaderFlags,
|
||||||
coverLastModified = coverLastModified,
|
userChapterFlags = userChapterFlags,
|
||||||
updateStrategy = updateStrategy,
|
stateChapterLastUpdate = stateChapterLastUpdate,
|
||||||
calculateInterval = calculateInterval,
|
stateChapterNextUpdate = stateChapterNextUpdate,
|
||||||
notes = notes,
|
stateChapterFetchInterval = stateChapterFetchInterval,
|
||||||
memo = memo,
|
stateCoverLastModified = stateCoverLastModified,
|
||||||
favoriteAt = favoriteAt,
|
stateInitialized = stateInitialized,
|
||||||
),
|
),
|
||||||
chapterCount = totalCount,
|
chapterCount = chapterCount,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -48,13 +48,13 @@ class MangaRepositoryImpl(
|
|||||||
|
|
||||||
override suspend fun getMangaByUrlAndSourceId(url: String, sourceId: Long): Manga? {
|
override suspend fun getMangaByUrlAndSourceId(url: String, sourceId: Long): Manga? {
|
||||||
return database.mangaQueries
|
return database.mangaQueries
|
||||||
.getMangaByUrlAndSource(url, sourceId, MangaMapper::mapManga)
|
.getMangaByUrlAndSource(sourceId = sourceId, remoteUrl = url, mapper = MangaMapper::mapManga)
|
||||||
.awaitAsOneOrNull()
|
.awaitAsOneOrNull()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getMangaByUrlAndSourceIdAsFlow(url: String, sourceId: Long): Flow<Manga?> {
|
override fun getMangaByUrlAndSourceIdAsFlow(url: String, sourceId: Long): Flow<Manga?> {
|
||||||
return database.mangaQueries
|
return database.mangaQueries
|
||||||
.getMangaByUrlAndSource(url, sourceId, MangaMapper::mapManga)
|
.getMangaByUrlAndSource(sourceId = sourceId, remoteUrl = url, mapper = MangaMapper::mapManga)
|
||||||
.subscribeToOneOrNull()
|
.subscribeToOneOrNull()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -161,7 +161,11 @@ class MangaRepositoryImpl(
|
|||||||
excludedScanlators.minus(current).forEach { scanlator ->
|
excludedScanlators.minus(current).forEach { scanlator ->
|
||||||
database.excluded_scanlatorQueries.insert(mangaId, scanlator)
|
database.excluded_scanlatorQueries.insert(mangaId, scanlator)
|
||||||
}
|
}
|
||||||
database.excluded_scanlatorQueries.remove(mangaId, current.minus(excludedScanlators))
|
val toRemove = current.minus(excludedScanlators)
|
||||||
|
// An empty delete still tells every chapter query to run again
|
||||||
|
if (toRemove.isNotEmpty()) {
|
||||||
|
database.excluded_scanlatorQueries.remove(mangaId, toRemove)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -189,25 +193,25 @@ class MangaRepositoryImpl(
|
|||||||
return database.transactionWithResult {
|
return database.transactionWithResult {
|
||||||
manga.map {
|
manga.map {
|
||||||
database.mangaQueries.insertNetworkManga(
|
database.mangaQueries.insertNetworkManga(
|
||||||
source = it.source,
|
sourceId = it.source,
|
||||||
url = it.url,
|
remoteUrl = it.url,
|
||||||
artist = it.artist,
|
remoteArtist = it.artist,
|
||||||
author = it.author,
|
remoteAuthor = it.author,
|
||||||
description = it.description,
|
remoteDescription = it.description,
|
||||||
genre = it.genre,
|
remoteGenre = it.genre,
|
||||||
title = it.title,
|
remoteTitle = it.title,
|
||||||
status = it.status,
|
remoteStatus = it.status,
|
||||||
thumbnailUrl = it.thumbnailUrl,
|
remoteCover = it.thumbnailUrl,
|
||||||
favoriteAt = it.favoriteAt,
|
userFavoriteAt = it.favoriteAt,
|
||||||
lastUpdate = it.lastUpdate,
|
stateChapterLastUpdate = it.lastUpdate,
|
||||||
nextUpdate = it.nextUpdate,
|
stateChapterNextUpdate = it.nextUpdate,
|
||||||
calculateInterval = it.fetchInterval.toLong(),
|
stateChapterFetchInterval = it.fetchInterval.toLong(),
|
||||||
initialized = it.initialized,
|
stateInitialized = it.initialized,
|
||||||
viewerFlags = it.viewerFlags,
|
userReaderFlags = it.viewerFlags,
|
||||||
chapterFlags = it.chapterFlags,
|
userChapterFlags = it.chapterFlags,
|
||||||
coverLastModified = it.coverLastModified,
|
stateCoverLastModified = it.coverLastModified,
|
||||||
updateStrategy = it.updateStrategy,
|
remoteUpdateStrategy = it.updateStrategy,
|
||||||
memo = it.memo,
|
remoteMemo = it.memo,
|
||||||
updateTitle = it.title.isNotBlank(),
|
updateTitle = it.title.isNotBlank(),
|
||||||
updateCover = !it.thumbnailUrl.isNullOrBlank(),
|
updateCover = !it.thumbnailUrl.isNullOrBlank(),
|
||||||
updateDetails = it.initialized,
|
updateDetails = it.initialized,
|
||||||
@@ -221,18 +225,18 @@ class MangaRepositoryImpl(
|
|||||||
override suspend fun updateRemote(update: MangaRemoteUpdate): Boolean {
|
override suspend fun updateRemote(update: MangaRemoteUpdate): Boolean {
|
||||||
return try {
|
return try {
|
||||||
database.mangaQueries.updateRemote(
|
database.mangaQueries.updateRemote(
|
||||||
artist = update.artist,
|
remoteArtist = update.artist,
|
||||||
author = update.author,
|
remoteAuthor = update.author,
|
||||||
description = update.description,
|
remoteDescription = update.description,
|
||||||
genre = update.genre,
|
remoteGenre = update.genre,
|
||||||
title = update.title,
|
remoteTitle = update.title,
|
||||||
status = update.status,
|
remoteStatus = update.status,
|
||||||
thumbnailUrl = update.thumbnailUrl,
|
remoteCover = update.thumbnailUrl,
|
||||||
initialized = update.initialized,
|
stateInitialized = update.initialized,
|
||||||
coverLastModified = update.coverLastModified,
|
stateCoverLastModified = update.coverLastModified,
|
||||||
updateStrategy = update.updateStrategy,
|
remoteUpdateStrategy = update.updateStrategy,
|
||||||
memo = update.memo,
|
remoteMemo = update.memo,
|
||||||
mangaId = update.id,
|
id = update.id,
|
||||||
)
|
)
|
||||||
true
|
true
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
@@ -246,16 +250,16 @@ class MangaRepositoryImpl(
|
|||||||
mangaUpdates.forEach { value ->
|
mangaUpdates.forEach { value ->
|
||||||
with(value) {
|
with(value) {
|
||||||
database.mangaQueries.update(
|
database.mangaQueries.update(
|
||||||
favoriteAtSet = isSet(::favoriteAt),
|
userFavoriteAtSet = isSet(::favoriteAt),
|
||||||
favoriteAt = favoriteAt,
|
userFavoriteAt = favoriteAt,
|
||||||
lastUpdate = lastUpdate,
|
stateChapterLastUpdate = lastUpdate,
|
||||||
nextUpdate = nextUpdate,
|
stateChapterNextUpdate = nextUpdate,
|
||||||
calculateInterval = fetchInterval?.toLong(),
|
stateChapterFetchInterval = fetchInterval?.toLong(),
|
||||||
viewer = viewerFlags,
|
userReaderFlags = viewerFlags,
|
||||||
chapterFlags = chapterFlags,
|
userChapterFlags = chapterFlags,
|
||||||
coverLastModified = coverLastModified,
|
stateCoverLastModified = coverLastModified,
|
||||||
mangaId = id,
|
id = id,
|
||||||
notes = notes,
|
userNotes = notes,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -31,12 +31,12 @@ class StubSourceRepositoryImpl(
|
|||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun upsertStubSource(id: Long, lang: String, name: String) {
|
override suspend fun upsertStubSource(id: Long, lang: String, name: String) {
|
||||||
database.sourceQueries.upsert(id = id, lang = lang, name = name)
|
database.sourceQueries.upsert(id = id, name = name, language = lang)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun mapStubSource(
|
private fun mapStubSource(
|
||||||
id: Long,
|
id: Long,
|
||||||
lang: String,
|
|
||||||
name: String,
|
name: String,
|
||||||
|
lang: String,
|
||||||
): StubSource = StubSource(id = id, lang = lang, name = name)
|
): StubSource = StubSource(id = id, lang = lang, name = name)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,22 +6,22 @@ object TrackMapper {
|
|||||||
fun mapTrack(
|
fun mapTrack(
|
||||||
id: Long,
|
id: Long,
|
||||||
mangaId: Long,
|
mangaId: Long,
|
||||||
syncId: Long,
|
trackerId: Long,
|
||||||
remoteId: Long,
|
remoteId: Long,
|
||||||
|
remoteUrl: String,
|
||||||
libraryId: Long?,
|
libraryId: Long?,
|
||||||
title: String,
|
title: String,
|
||||||
lastChapterRead: Double,
|
lastChapterRead: Double,
|
||||||
totalChapters: Long,
|
totalChapters: Long,
|
||||||
status: Long,
|
status: Long,
|
||||||
score: Double,
|
score: Double,
|
||||||
remoteUrl: String,
|
|
||||||
startDate: Long,
|
startDate: Long,
|
||||||
finishDate: Long,
|
finishDate: Long,
|
||||||
private: Boolean,
|
private: Boolean,
|
||||||
): Track = Track(
|
): Track = Track(
|
||||||
id = id,
|
id = id,
|
||||||
mangaId = mangaId,
|
mangaId = mangaId,
|
||||||
trackerId = syncId,
|
trackerId = trackerId,
|
||||||
remoteId = remoteId,
|
remoteId = remoteId,
|
||||||
libraryId = libraryId,
|
libraryId = libraryId,
|
||||||
title = title,
|
title = title,
|
||||||
|
|||||||
@@ -46,7 +46,7 @@ class TrackRepositoryImpl(
|
|||||||
override suspend fun delete(mangaId: Long, trackerId: Long) {
|
override suspend fun delete(mangaId: Long, trackerId: Long) {
|
||||||
database.manga_trackQueries.delete(
|
database.manga_trackQueries.delete(
|
||||||
mangaId = mangaId,
|
mangaId = mangaId,
|
||||||
syncId = trackerId,
|
trackerId = trackerId,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -63,7 +63,7 @@ class TrackRepositoryImpl(
|
|||||||
tracks.forEach { mangaTrack ->
|
tracks.forEach { mangaTrack ->
|
||||||
database.manga_trackQueries.upsert(
|
database.manga_trackQueries.upsert(
|
||||||
mangaId = mangaTrack.mangaId,
|
mangaId = mangaTrack.mangaId,
|
||||||
syncId = mangaTrack.trackerId,
|
trackerId = mangaTrack.trackerId,
|
||||||
remoteId = mangaTrack.remoteId,
|
remoteId = mangaTrack.remoteId,
|
||||||
libraryId = mangaTrack.libraryId,
|
libraryId = mangaTrack.libraryId,
|
||||||
title = mangaTrack.title,
|
title = mangaTrack.title,
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
package tachiyomi.data.updates
|
package tachiyomi.data.updates
|
||||||
|
|
||||||
|
import app.cash.sqldelight.Query
|
||||||
import app.cash.sqldelight.async.coroutines.awaitAsList
|
import app.cash.sqldelight.async.coroutines.awaitAsList
|
||||||
import dev.zacsweers.metro.AppScope
|
import dev.zacsweers.metro.AppScope
|
||||||
import dev.zacsweers.metro.ContributesBinding
|
import dev.zacsweers.metro.ContributesBinding
|
||||||
@@ -25,14 +26,7 @@ class UpdatesRepositoryImpl(
|
|||||||
after: Long,
|
after: Long,
|
||||||
limit: Long,
|
limit: Long,
|
||||||
): List<UpdatesWithRelations> {
|
): List<UpdatesWithRelations> {
|
||||||
return database.updatesViewQueries
|
return getUpdatesByReadStatus(read = read, after = after, limit = limit).awaitAsList()
|
||||||
.getUpdatesByReadStatus(
|
|
||||||
read = read,
|
|
||||||
after = after,
|
|
||||||
limit = limit,
|
|
||||||
mapper = ::mapUpdatesWithRelations,
|
|
||||||
)
|
|
||||||
.awaitAsList()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun subscribeAll(
|
override fun subscribeAll(
|
||||||
@@ -67,14 +61,23 @@ class UpdatesRepositoryImpl(
|
|||||||
after: Long,
|
after: Long,
|
||||||
limit: Long,
|
limit: Long,
|
||||||
): Flow<List<UpdatesWithRelations>> {
|
): Flow<List<UpdatesWithRelations>> {
|
||||||
return database.updatesViewQueries
|
return getUpdatesByReadStatus(read = read, after = after, limit = limit).subscribeToList()
|
||||||
.getUpdatesByReadStatus(
|
}
|
||||||
read = read,
|
|
||||||
|
private fun getUpdatesByReadStatus(read: Boolean, after: Long, limit: Long): Query<UpdatesWithRelations> {
|
||||||
|
return if (read) {
|
||||||
|
database.updatesViewQueries.getReadUpdates(
|
||||||
after = after,
|
after = after,
|
||||||
limit = limit,
|
limit = limit,
|
||||||
mapper = ::mapUpdatesWithRelations,
|
mapper = ::mapUpdatesWithRelations,
|
||||||
)
|
)
|
||||||
.subscribeToList()
|
} else {
|
||||||
|
database.updatesViewQueries.getUnreadUpdates(
|
||||||
|
after = after,
|
||||||
|
limit = limit,
|
||||||
|
mapper = ::mapUpdatesWithRelations,
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Suppress("UNUSED_PARAMETER")
|
@Suppress("UNUSED_PARAMETER")
|
||||||
|
|||||||
@@ -19,24 +19,15 @@ END;
|
|||||||
getCategory:
|
getCategory:
|
||||||
SELECT id, name, `order`, flags
|
SELECT id, name, `order`, flags
|
||||||
FROM category
|
FROM category
|
||||||
WHERE id = :id
|
WHERE id = :id;
|
||||||
LIMIT 1;
|
|
||||||
|
|
||||||
getCategories:
|
getCategories:
|
||||||
SELECT
|
SELECT id, name, `order`, flags
|
||||||
id,
|
|
||||||
name,
|
|
||||||
`order`,
|
|
||||||
flags
|
|
||||||
FROM category
|
FROM category
|
||||||
ORDER BY `order`;
|
ORDER BY `order`;
|
||||||
|
|
||||||
getCategoriesByMangaId:
|
getCategoriesByMangaId:
|
||||||
SELECT
|
SELECT C.id, C.name, C.`order`, C.flags
|
||||||
C.id,
|
|
||||||
C.name,
|
|
||||||
C.`order`,
|
|
||||||
C.flags
|
|
||||||
FROM category C
|
FROM category C
|
||||||
JOIN manga_category MC
|
JOIN manga_category MC
|
||||||
ON C.id = MC.category_id
|
ON C.id = MC.category_id
|
||||||
@@ -44,18 +35,19 @@ WHERE MC.manga_id = :mangaId
|
|||||||
ORDER BY C.`order`;
|
ORDER BY C.`order`;
|
||||||
|
|
||||||
insert:
|
insert:
|
||||||
|
-- The position is taken inside the insert, so two categories created at once cannot claim the same one
|
||||||
INSERT INTO category(name, `order`, flags)
|
INSERT INTO category(name, `order`, flags)
|
||||||
SELECT :name, coalesce(max(`order`), -1) + 1, :flags
|
SELECT :name, coalesce(max(`order`), -1) + 1, :flags
|
||||||
FROM category;
|
FROM category;
|
||||||
|
|
||||||
delete:
|
delete:
|
||||||
DELETE FROM category
|
DELETE FROM category
|
||||||
WHERE id = :categoryId;
|
WHERE id = :id;
|
||||||
|
|
||||||
updateName:
|
updateName:
|
||||||
UPDATE category
|
UPDATE category
|
||||||
SET name = :name
|
SET name = :name
|
||||||
WHERE id = :categoryId;
|
WHERE id = :id;
|
||||||
|
|
||||||
getUserCategoryIds:
|
getUserCategoryIds:
|
||||||
SELECT id
|
SELECT id
|
||||||
@@ -66,13 +58,13 @@ ORDER BY `order`;
|
|||||||
updateOrder:
|
updateOrder:
|
||||||
UPDATE category
|
UPDATE category
|
||||||
SET `order` = :order
|
SET `order` = :order
|
||||||
WHERE id = :categoryId;
|
WHERE id = :id;
|
||||||
|
|
||||||
updateFlags:
|
updateFlags:
|
||||||
UPDATE category
|
UPDATE category
|
||||||
SET flags = :flags
|
SET flags = :flags
|
||||||
WHERE id = :categoryId;
|
WHERE id = :id;
|
||||||
|
|
||||||
updateAllFlags:
|
updateAllFlags:
|
||||||
UPDATE category SET
|
UPDATE category
|
||||||
flags = coalesce(:flags, flags);
|
SET flags = :flags;
|
||||||
|
|||||||
@@ -25,19 +25,17 @@ CREATE INDEX chapter_unread_state_date_fetch_index ON chapter(state_date_fetch)
|
|||||||
|
|
||||||
getChapterById:
|
getChapterById:
|
||||||
SELECT
|
SELECT
|
||||||
id, manga_id, remote_url, remote_name, remote_scanlator,
|
id, manga_id, remote_url, remote_name, remote_scanlator, remote_chapter_number,
|
||||||
user_read, user_bookmark, user_last_page_read, remote_chapter_number, remote_order,
|
remote_date_upload, remote_order, remote_memo, user_read, user_bookmark,
|
||||||
state_date_fetch, remote_date_upload,
|
user_last_page_read, state_date_fetch
|
||||||
remote_memo
|
|
||||||
FROM chapter
|
FROM chapter
|
||||||
WHERE id = :id;
|
WHERE id = :id;
|
||||||
|
|
||||||
getChaptersByMangaId:
|
getChaptersByMangaId:
|
||||||
SELECT
|
SELECT
|
||||||
C.id, C.manga_id, C.remote_url, C.remote_name, C.remote_scanlator,
|
C.id, C.manga_id, C.remote_url, C.remote_name, C.remote_scanlator, C.remote_chapter_number,
|
||||||
C.user_read, C.user_bookmark, C.user_last_page_read, C.remote_chapter_number, C.remote_order,
|
C.remote_date_upload, C.remote_order, C.remote_memo, C.user_read, C.user_bookmark,
|
||||||
C.state_date_fetch, C.remote_date_upload,
|
C.user_last_page_read, C.state_date_fetch
|
||||||
C.remote_memo
|
|
||||||
FROM chapter C
|
FROM chapter C
|
||||||
LEFT JOIN excluded_scanlator ES
|
LEFT JOIN excluded_scanlator ES
|
||||||
ON C.manga_id = ES.manga_id
|
ON C.manga_id = ES.manga_id
|
||||||
@@ -49,75 +47,70 @@ AND (
|
|||||||
);
|
);
|
||||||
|
|
||||||
getScanlatorsByMangaId:
|
getScanlatorsByMangaId:
|
||||||
SELECT remote_scanlator
|
SELECT DISTINCT remote_scanlator
|
||||||
FROM chapter
|
FROM chapter
|
||||||
WHERE manga_id = :mangaId;
|
WHERE manga_id = :mangaId;
|
||||||
|
|
||||||
getBookmarkedChaptersByMangaId:
|
getBookmarkedChaptersByMangaId:
|
||||||
SELECT
|
SELECT
|
||||||
id, manga_id, remote_url, remote_name, remote_scanlator,
|
id, manga_id, remote_url, remote_name, remote_scanlator, remote_chapter_number,
|
||||||
user_read, user_bookmark, user_last_page_read, remote_chapter_number, remote_order,
|
remote_date_upload, remote_order, remote_memo, user_read, user_bookmark,
|
||||||
state_date_fetch, remote_date_upload,
|
user_last_page_read, state_date_fetch
|
||||||
remote_memo
|
|
||||||
FROM chapter
|
FROM chapter
|
||||||
WHERE user_bookmark
|
WHERE manga_id = :mangaId
|
||||||
AND manga_id = :mangaId;
|
AND user_bookmark;
|
||||||
|
|
||||||
getChapterByUrl:
|
|
||||||
SELECT
|
|
||||||
id, manga_id, remote_url, remote_name, remote_scanlator,
|
|
||||||
user_read, user_bookmark, user_last_page_read, remote_chapter_number, remote_order,
|
|
||||||
state_date_fetch, remote_date_upload,
|
|
||||||
remote_memo
|
|
||||||
FROM chapter
|
|
||||||
WHERE remote_url = :chapterUrl;
|
|
||||||
|
|
||||||
getChapterByUrlAndMangaId:
|
getChapterByUrlAndMangaId:
|
||||||
SELECT
|
SELECT
|
||||||
id, manga_id, remote_url, remote_name, remote_scanlator,
|
id, manga_id, remote_url, remote_name, remote_scanlator, remote_chapter_number,
|
||||||
user_read, user_bookmark, user_last_page_read, remote_chapter_number, remote_order,
|
remote_date_upload, remote_order, remote_memo, user_read, user_bookmark,
|
||||||
state_date_fetch, remote_date_upload,
|
user_last_page_read, state_date_fetch
|
||||||
remote_memo
|
|
||||||
FROM chapter
|
FROM chapter
|
||||||
WHERE remote_url = :chapterUrl
|
WHERE manga_id = :mangaId
|
||||||
AND manga_id = :mangaId
|
AND remote_url = :remoteUrl;
|
||||||
LIMIT 1;
|
|
||||||
|
|
||||||
removeChaptersWithIds:
|
removeChaptersWithIds:
|
||||||
DELETE FROM chapter
|
DELETE FROM chapter
|
||||||
WHERE id IN :chapterIds;
|
WHERE id IN :chapterIds;
|
||||||
|
|
||||||
insert:
|
|
||||||
INSERT INTO chapter(manga_id, remote_url, remote_name, remote_scanlator, user_read, user_bookmark, user_last_page_read, remote_chapter_number, remote_order, state_date_fetch, remote_date_upload, remote_memo)
|
|
||||||
VALUES (:mangaId, :url, :name, :scanlator, :read, :bookmark, :lastPageRead, :chapterNumber, :sourceOrder, :dateFetch, :dateUpload, :memo);
|
|
||||||
|
|
||||||
insertReturningId:
|
insertReturningId:
|
||||||
INSERT INTO chapter(manga_id, remote_url, remote_name, remote_scanlator, user_read, user_bookmark, user_last_page_read, remote_chapter_number, remote_order, state_date_fetch, remote_date_upload, remote_memo)
|
INSERT INTO chapter(
|
||||||
VALUES (:mangaId, :url, :name, :scanlator, :read, :bookmark, :lastPageRead, :chapterNumber, :sourceOrder, :dateFetch, :dateUpload, :memo)
|
manga_id, remote_url, remote_name, remote_scanlator, remote_chapter_number,
|
||||||
|
remote_date_upload, remote_order, remote_memo, user_read, user_bookmark,
|
||||||
|
user_last_page_read, state_date_fetch
|
||||||
|
)
|
||||||
|
VALUES (
|
||||||
|
:mangaId, :remoteUrl, :remoteName, :remoteScanlator, :remoteChapterNumber,
|
||||||
|
:remoteDateUpload, :remoteOrder, :remoteMemo, :userRead, :userBookmark,
|
||||||
|
:userLastPageRead, :stateDateFetch
|
||||||
|
)
|
||||||
|
-- Two syncs of the same entry can race; the chapter the other one already stored is skipped rather
|
||||||
|
-- than failing the whole batch
|
||||||
|
ON CONFLICT(manga_id, remote_url) DO NOTHING
|
||||||
RETURNING id;
|
RETURNING id;
|
||||||
|
|
||||||
update:
|
update:
|
||||||
UPDATE chapter
|
UPDATE chapter
|
||||||
SET user_read = coalesce(:read, user_read),
|
SET user_read = coalesce(:userRead, user_read),
|
||||||
user_bookmark = coalesce(:bookmark, user_bookmark),
|
user_bookmark = coalesce(:userBookmark, user_bookmark),
|
||||||
user_last_page_read = coalesce(:lastPageRead, user_last_page_read),
|
user_last_page_read = coalesce(:userLastPageRead, user_last_page_read),
|
||||||
state_date_fetch = coalesce(:dateFetch, state_date_fetch)
|
state_date_fetch = coalesce(:stateDateFetch, state_date_fetch)
|
||||||
WHERE id = :chapterId;
|
WHERE id = :id;
|
||||||
|
|
||||||
updateFromBackup:
|
updateFromBackup:
|
||||||
UPDATE chapter
|
UPDATE chapter
|
||||||
SET user_read = coalesce(:read, user_read),
|
SET user_read = coalesce(:userRead, user_read),
|
||||||
user_bookmark = coalesce(:bookmark, user_bookmark),
|
user_bookmark = coalesce(:userBookmark, user_bookmark),
|
||||||
user_last_page_read = coalesce(:lastPageRead, user_last_page_read),
|
user_last_page_read = coalesce(:userLastPageRead, user_last_page_read),
|
||||||
remote_memo = coalesce(:memo, remote_memo)
|
remote_memo = coalesce(:remoteMemo, remote_memo)
|
||||||
WHERE id = :chapterId;
|
WHERE id = :id;
|
||||||
|
|
||||||
updateRemote:
|
updateRemote:
|
||||||
UPDATE chapter
|
UPDATE chapter
|
||||||
SET remote_name = :name,
|
SET remote_name = :remoteName,
|
||||||
remote_scanlator = coalesce(:scanlator, remote_scanlator),
|
remote_scanlator = coalesce(:remoteScanlator, remote_scanlator),
|
||||||
remote_chapter_number = :chapterNumber,
|
remote_chapter_number = :remoteChapterNumber,
|
||||||
remote_order = :sourceOrder,
|
remote_order = :remoteOrder,
|
||||||
remote_date_upload = coalesce(:dateUpload, remote_date_upload),
|
remote_date_upload = coalesce(:remoteDateUpload, remote_date_upload),
|
||||||
remote_memo = :memo
|
remote_memo = :remoteMemo
|
||||||
WHERE id = :chapterId;
|
WHERE id = :id;
|
||||||
|
|||||||
@@ -9,7 +9,8 @@ CREATE TABLE excluded_scanlator(
|
|||||||
|
|
||||||
insert:
|
insert:
|
||||||
INSERT INTO excluded_scanlator(manga_id, scanlator)
|
INSERT INTO excluded_scanlator(manga_id, scanlator)
|
||||||
VALUES (:mangaId, :scanlator);
|
VALUES (:mangaId, :scanlator)
|
||||||
|
ON CONFLICT(manga_id, scanlator) DO NOTHING;
|
||||||
|
|
||||||
remove:
|
remove:
|
||||||
DELETE FROM excluded_scanlator
|
DELETE FROM excluded_scanlator
|
||||||
|
|||||||
@@ -21,23 +21,26 @@ SELECT
|
|||||||
FROM extension_store;
|
FROM extension_store;
|
||||||
|
|
||||||
getCount:
|
getCount:
|
||||||
SELECT COUNT(*)
|
SELECT count(*)
|
||||||
FROM extension_store;
|
FROM extension_store;
|
||||||
|
|
||||||
upsert:
|
upsert:
|
||||||
INSERT INTO extension_store(index_url, name, badge_label, signing_key, contact_website, contact_discord, is_legacy, extension_list_url)
|
INSERT INTO extension_store(
|
||||||
VALUES (:indexUrl, :name, :badgeLabel, :signingKey, :contactWebsite, :contactDiscord, :isLegacy, :extensionListUrl)
|
index_url, name, badge_label, signing_key, contact_website, contact_discord, is_legacy,
|
||||||
ON CONFLICT(index_url)
|
extension_list_url
|
||||||
DO UPDATE
|
)
|
||||||
SET
|
VALUES (
|
||||||
name = :name,
|
:indexUrl, :name, :badgeLabel, :signingKey, :contactWebsite, :contactDiscord, :isLegacy,
|
||||||
badge_label = :badgeLabel,
|
:extensionListUrl
|
||||||
signing_key =: signingKey,
|
)
|
||||||
contact_website = :contactWebsite,
|
ON CONFLICT(index_url) DO UPDATE SET
|
||||||
contact_discord = :contactDiscord,
|
name = excluded.name,
|
||||||
is_legacy = :isLegacy,
|
badge_label = excluded.badge_label,
|
||||||
extension_list_url = :extensionListUrl
|
signing_key = excluded.signing_key,
|
||||||
WHERE index_url = :indexUrl;
|
contact_website = excluded.contact_website,
|
||||||
|
contact_discord = excluded.contact_discord,
|
||||||
|
is_legacy = excluded.is_legacy,
|
||||||
|
extension_list_url = excluded.extension_list_url;
|
||||||
|
|
||||||
delete:
|
delete:
|
||||||
DELETE FROM extension_store
|
DELETE FROM extension_store
|
||||||
|
|||||||
@@ -16,30 +16,14 @@ CREATE INDEX history_manga_id_index ON history(manga_id);
|
|||||||
CREATE INDEX history_read_at_index ON history(read_at);
|
CREATE INDEX history_read_at_index ON history(read_at);
|
||||||
|
|
||||||
getHistoryByMangaId:
|
getHistoryByMangaId:
|
||||||
SELECT
|
SELECT id, chapter_id, read_at, read_duration
|
||||||
H.id,
|
FROM history
|
||||||
H.chapter_id,
|
WHERE manga_id = :mangaId;
|
||||||
H.read_at,
|
|
||||||
H.read_duration
|
|
||||||
FROM history H
|
|
||||||
WHERE H.manga_id = :mangaId;
|
|
||||||
|
|
||||||
getHistoryByChapterUrlAndMangaId:
|
|
||||||
SELECT
|
|
||||||
H.id,
|
|
||||||
H.chapter_id,
|
|
||||||
H.read_at,
|
|
||||||
H.read_duration
|
|
||||||
FROM history H
|
|
||||||
JOIN chapter C
|
|
||||||
ON H.chapter_id = C.id
|
|
||||||
WHERE C.remote_url = :chapterUrl AND C.manga_id = :mangaId
|
|
||||||
LIMIT 1;
|
|
||||||
|
|
||||||
resetHistoryById:
|
resetHistoryById:
|
||||||
UPDATE history
|
UPDATE history
|
||||||
SET read_at = 0
|
SET read_at = 0
|
||||||
WHERE id = :historyId;
|
WHERE id = :id;
|
||||||
|
|
||||||
resetHistoryByMangaId:
|
resetHistoryByMangaId:
|
||||||
UPDATE history
|
UPDATE history
|
||||||
@@ -55,15 +39,12 @@ WHERE read_at = 0;
|
|||||||
|
|
||||||
upsert:
|
upsert:
|
||||||
INSERT INTO history(manga_id, chapter_id, read_at, read_duration)
|
INSERT INTO history(manga_id, chapter_id, read_at, read_duration)
|
||||||
SELECT C.manga_id, :chapterId, :readAt, :readDuration
|
SELECT manga_id, id, :readAt, :readDuration
|
||||||
FROM chapter C
|
FROM chapter
|
||||||
WHERE C.id = :chapterId
|
WHERE id = :chapterId
|
||||||
ON CONFLICT(chapter_id)
|
ON CONFLICT(chapter_id) DO UPDATE SET
|
||||||
DO UPDATE
|
read_at = excluded.read_at,
|
||||||
SET
|
read_duration = read_duration + excluded.read_duration;
|
||||||
read_at = :readAt,
|
|
||||||
read_duration = read_duration + :readDuration
|
|
||||||
WHERE chapter_id = :chapterId;
|
|
||||||
|
|
||||||
getReadDuration:
|
getReadDuration:
|
||||||
SELECT coalesce(sum(read_duration), 0)
|
SELECT coalesce(sum(read_duration), 0)
|
||||||
|
|||||||
@@ -33,73 +33,68 @@ CREATE INDEX manga_user_favorite_at_index ON manga(user_favorite_at) WHERE user_
|
|||||||
|
|
||||||
getMangaById:
|
getMangaById:
|
||||||
SELECT
|
SELECT
|
||||||
id, source_id, remote_url, remote_artist, remote_author, remote_description, remote_genre, remote_title,
|
id, source_id, remote_url, remote_title, remote_author, remote_artist, remote_description,
|
||||||
remote_status, remote_cover, state_chapter_last_update, state_chapter_next_update, state_initialized,
|
remote_genre, remote_status, remote_cover, remote_update_strategy, remote_memo,
|
||||||
user_reader_flags, user_chapter_flags, state_cover_last_modified, remote_update_strategy,
|
user_favorite_at, user_notes, user_reader_flags, user_chapter_flags,
|
||||||
state_chapter_fetch_interval, user_notes, remote_memo, user_favorite_at
|
state_chapter_last_update, state_chapter_next_update, state_chapter_fetch_interval,
|
||||||
|
state_cover_last_modified, state_initialized
|
||||||
FROM manga
|
FROM manga
|
||||||
WHERE id = :id;
|
WHERE id = :id;
|
||||||
|
|
||||||
-- TODO: this should ideally never really have more than 1 result
|
|
||||||
getMangaByUrlAndSource:
|
getMangaByUrlAndSource:
|
||||||
SELECT
|
SELECT
|
||||||
id, source_id, remote_url, remote_artist, remote_author, remote_description, remote_genre, remote_title,
|
id, source_id, remote_url, remote_title, remote_author, remote_artist, remote_description,
|
||||||
remote_status, remote_cover, state_chapter_last_update, state_chapter_next_update, state_initialized,
|
remote_genre, remote_status, remote_cover, remote_update_strategy, remote_memo,
|
||||||
user_reader_flags, user_chapter_flags, state_cover_last_modified, remote_update_strategy,
|
user_favorite_at, user_notes, user_reader_flags, user_chapter_flags,
|
||||||
state_chapter_fetch_interval, user_notes, remote_memo, user_favorite_at
|
state_chapter_last_update, state_chapter_next_update, state_chapter_fetch_interval,
|
||||||
|
state_cover_last_modified, state_initialized
|
||||||
FROM manga
|
FROM manga
|
||||||
WHERE remote_url = :url
|
WHERE source_id = :sourceId
|
||||||
AND source_id = :source
|
AND remote_url = :remoteUrl;
|
||||||
LIMIT 1;
|
|
||||||
|
|
||||||
getFavorites:
|
getFavorites:
|
||||||
SELECT
|
SELECT
|
||||||
id, source_id, remote_url, remote_artist, remote_author, remote_description, remote_genre, remote_title,
|
id, source_id, remote_url, remote_title, remote_author, remote_artist, remote_description,
|
||||||
remote_status, remote_cover, state_chapter_last_update, state_chapter_next_update, state_initialized,
|
remote_genre, remote_status, remote_cover, remote_update_strategy, remote_memo,
|
||||||
user_reader_flags, user_chapter_flags, state_cover_last_modified, remote_update_strategy,
|
user_favorite_at, user_notes, user_reader_flags, user_chapter_flags,
|
||||||
state_chapter_fetch_interval, user_notes, remote_memo, user_favorite_at
|
state_chapter_last_update, state_chapter_next_update, state_chapter_fetch_interval,
|
||||||
|
state_cover_last_modified, state_initialized
|
||||||
FROM manga
|
FROM manga
|
||||||
WHERE user_favorite_at IS NOT NULL;
|
WHERE user_favorite_at IS NOT NULL;
|
||||||
|
|
||||||
getReadMangaNotInLibrary:
|
getReadMangaNotInLibrary:
|
||||||
SELECT
|
SELECT
|
||||||
id, source_id, remote_url, remote_artist, remote_author, remote_description, remote_genre, remote_title,
|
id, source_id, remote_url, remote_title, remote_author, remote_artist, remote_description,
|
||||||
remote_status, remote_cover, state_chapter_last_update, state_chapter_next_update, state_initialized,
|
remote_genre, remote_status, remote_cover, remote_update_strategy, remote_memo,
|
||||||
user_reader_flags, user_chapter_flags, state_cover_last_modified, remote_update_strategy,
|
user_favorite_at, user_notes, user_reader_flags, user_chapter_flags,
|
||||||
state_chapter_fetch_interval, user_notes, remote_memo, user_favorite_at
|
state_chapter_last_update, state_chapter_next_update, state_chapter_fetch_interval,
|
||||||
|
state_cover_last_modified, state_initialized
|
||||||
FROM manga
|
FROM manga
|
||||||
WHERE user_favorite_at IS NULL AND id IN (
|
WHERE user_favorite_at IS NULL
|
||||||
SELECT DISTINCT chapter.manga_id
|
AND EXISTS (
|
||||||
|
SELECT 1
|
||||||
FROM chapter
|
FROM chapter
|
||||||
WHERE user_read = 1 OR user_last_page_read != 0
|
WHERE chapter.manga_id = manga.id
|
||||||
|
AND (user_read = 1 OR user_last_page_read != 0)
|
||||||
);
|
);
|
||||||
|
|
||||||
getAllManga:
|
|
||||||
SELECT
|
|
||||||
id, source_id, remote_url, remote_artist, remote_author, remote_description, remote_genre, remote_title,
|
|
||||||
remote_status, remote_cover, state_chapter_last_update, state_chapter_next_update, state_initialized,
|
|
||||||
user_reader_flags, user_chapter_flags, state_cover_last_modified, remote_update_strategy,
|
|
||||||
state_chapter_fetch_interval, user_notes, remote_memo, user_favorite_at
|
|
||||||
FROM manga;
|
|
||||||
|
|
||||||
getAllMangaSourceAndUrl:
|
getAllMangaSourceAndUrl:
|
||||||
SELECT source_id, remote_url
|
SELECT source_id, remote_url
|
||||||
FROM manga;
|
FROM manga;
|
||||||
|
|
||||||
getSourceIdWithFavoriteCount:
|
getSourceIdWithFavoriteCount:
|
||||||
SELECT
|
SELECT source_id, count(*)
|
||||||
source_id,
|
|
||||||
count(*)
|
|
||||||
FROM manga
|
FROM manga
|
||||||
WHERE user_favorite_at IS NOT NULL
|
WHERE user_favorite_at IS NOT NULL
|
||||||
GROUP BY source_id;
|
GROUP BY source_id;
|
||||||
|
|
||||||
getFavoriteBySourceId:
|
getFavoriteBySourceId:
|
||||||
SELECT
|
SELECT
|
||||||
id, source_id, remote_url, remote_artist, remote_author, remote_description, remote_genre, remote_title,
|
id, source_id, remote_url, remote_title, remote_author, remote_artist, remote_description,
|
||||||
remote_status, remote_cover, state_chapter_last_update, state_chapter_next_update, state_initialized,
|
remote_genre, remote_status, remote_cover, remote_update_strategy, remote_memo,
|
||||||
user_reader_flags, user_chapter_flags, state_cover_last_modified, remote_update_strategy,
|
user_favorite_at, user_notes, user_reader_flags, user_chapter_flags,
|
||||||
state_chapter_fetch_interval, user_notes, remote_memo, user_favorite_at
|
state_chapter_last_update, state_chapter_next_update, state_chapter_fetch_interval,
|
||||||
|
state_cover_last_modified, state_initialized
|
||||||
FROM manga
|
FROM manga
|
||||||
WHERE user_favorite_at IS NOT NULL
|
WHERE user_favorite_at IS NOT NULL
|
||||||
AND source_id = :sourceId;
|
AND source_id = :sourceId;
|
||||||
@@ -107,57 +102,43 @@ AND source_id = :sourceId;
|
|||||||
getDuplicateLibraryManga:
|
getDuplicateLibraryManga:
|
||||||
WITH
|
WITH
|
||||||
track_dupes AS (
|
track_dupes AS (
|
||||||
SELECT DISTINCT S2.manga_id
|
SELECT DISTINCT T2.manga_id
|
||||||
FROM manga_track S1
|
FROM manga_track T1
|
||||||
INNER JOIN manga_track S2
|
JOIN manga_track T2
|
||||||
ON S1.tracker_id = S2.tracker_id
|
ON T1.tracker_id = T2.tracker_id
|
||||||
AND S1.remote_id = S2.remote_id
|
AND T1.remote_id = T2.remote_id
|
||||||
AND S1.remote_id != 0
|
AND T1.remote_id != 0
|
||||||
AND S1.manga_id != S2.manga_id
|
AND T1.manga_id != T2.manga_id
|
||||||
WHERE S1.manga_id = :id
|
WHERE T1.manga_id = :id
|
||||||
),
|
|
||||||
duplicates AS (
|
|
||||||
SELECT
|
|
||||||
M.id, M.source_id, M.remote_url, M.remote_artist, M.remote_author, M.remote_description, M.remote_genre, M.remote_title,
|
|
||||||
M.remote_status, M.remote_cover, M.state_chapter_last_update, M.state_chapter_next_update, M.state_initialized,
|
|
||||||
M.user_reader_flags, M.user_chapter_flags, M.state_cover_last_modified, M.remote_update_strategy,
|
|
||||||
M.state_chapter_fetch_interval, M.user_notes, M.remote_memo, M.user_favorite_at
|
|
||||||
FROM manga M
|
|
||||||
LEFT JOIN track_dupes D
|
|
||||||
ON D.manga_id = id
|
|
||||||
WHERE user_favorite_at IS NOT NULL
|
|
||||||
AND id != :id
|
|
||||||
AND (lower(remote_title) LIKE '%' || lower(:title) || '%' OR D.manga_id IS NOT NULL)
|
|
||||||
),
|
|
||||||
chapter_counts AS (
|
|
||||||
SELECT
|
|
||||||
M.id AS manga_id,
|
|
||||||
count(*) AS chapter_count
|
|
||||||
FROM duplicates M
|
|
||||||
JOIN chapter C
|
|
||||||
ON M.id = C.manga_id
|
|
||||||
LEFT JOIN excluded_scanlator ES
|
|
||||||
ON C.manga_id = ES.manga_id
|
|
||||||
AND C.remote_scanlator = ES.scanlator
|
|
||||||
WHERE ES.scanlator IS NULL
|
|
||||||
GROUP BY M.id
|
|
||||||
)
|
)
|
||||||
SELECT
|
SELECT
|
||||||
M.id, M.source_id, M.remote_url, M.remote_artist, M.remote_author, M.remote_description, M.remote_genre, M.remote_title,
|
M.id, M.source_id, M.remote_url, M.remote_title, M.remote_author, M.remote_artist,
|
||||||
M.remote_status, M.remote_cover, M.state_chapter_last_update, M.state_chapter_next_update, M.state_initialized,
|
M.remote_description, M.remote_genre, M.remote_status, M.remote_cover,
|
||||||
M.user_reader_flags, M.user_chapter_flags, M.state_cover_last_modified, M.remote_update_strategy,
|
M.remote_update_strategy, M.remote_memo, M.user_favorite_at, M.user_notes,
|
||||||
M.state_chapter_fetch_interval, M.user_notes, M.remote_memo, M.user_favorite_at,
|
M.user_reader_flags, M.user_chapter_flags, M.state_chapter_last_update,
|
||||||
coalesce(CC.chapter_count, 0) AS chapter_count
|
M.state_chapter_next_update, M.state_chapter_fetch_interval,
|
||||||
FROM duplicates M
|
M.state_cover_last_modified, M.state_initialized,
|
||||||
LEFT JOIN chapter_counts CC
|
(
|
||||||
ON M.id = CC.manga_id;
|
SELECT count(*)
|
||||||
|
FROM chapter C
|
||||||
|
LEFT JOIN excluded_scanlator ES
|
||||||
|
ON C.manga_id = ES.manga_id
|
||||||
|
AND C.remote_scanlator = ES.scanlator
|
||||||
|
WHERE C.manga_id = M.id
|
||||||
|
AND ES.scanlator IS NULL
|
||||||
|
) AS chapter_count
|
||||||
|
FROM manga M
|
||||||
|
WHERE M.user_favorite_at IS NOT NULL
|
||||||
|
AND M.id != :id
|
||||||
|
AND (lower(M.remote_title) LIKE '%' || lower(:remoteTitle) || '%' OR M.id IN (SELECT manga_id FROM track_dupes));
|
||||||
|
|
||||||
getUpcomingManga:
|
getUpcomingManga:
|
||||||
SELECT
|
SELECT
|
||||||
id, source_id, remote_url, remote_artist, remote_author, remote_description, remote_genre, remote_title,
|
id, source_id, remote_url, remote_title, remote_author, remote_artist, remote_description,
|
||||||
remote_status, remote_cover, state_chapter_last_update, state_chapter_next_update, state_initialized,
|
remote_genre, remote_status, remote_cover, remote_update_strategy, remote_memo,
|
||||||
user_reader_flags, user_chapter_flags, state_cover_last_modified, remote_update_strategy,
|
user_favorite_at, user_notes, user_reader_flags, user_chapter_flags,
|
||||||
state_chapter_fetch_interval, user_notes, remote_memo, user_favorite_at
|
state_chapter_last_update, state_chapter_next_update, state_chapter_fetch_interval,
|
||||||
|
state_cover_last_modified, state_initialized
|
||||||
FROM manga
|
FROM manga
|
||||||
WHERE state_chapter_next_update >= :startOfDay
|
WHERE state_chapter_next_update >= :startOfDay
|
||||||
AND user_favorite_at IS NOT NULL
|
AND user_favorite_at IS NOT NULL
|
||||||
@@ -168,7 +149,7 @@ AND (
|
|||||||
OR EXISTS (
|
OR EXISTS (
|
||||||
SELECT 1 FROM manga_category
|
SELECT 1 FROM manga_category
|
||||||
WHERE manga_category.manga_id = manga.id
|
WHERE manga_category.manga_id = manga.id
|
||||||
AND COALESCE(manga_category.category_id, 0) IN :includedCategories
|
AND manga_category.category_id IN :includedCategories
|
||||||
)
|
)
|
||||||
OR (0 IN :includedCategories AND NOT EXISTS (
|
OR (0 IN :includedCategories AND NOT EXISTS (
|
||||||
SELECT 1 FROM manga_category WHERE manga_category.manga_id = manga.id)
|
SELECT 1 FROM manga_category WHERE manga_category.manga_id = manga.id)
|
||||||
@@ -181,7 +162,7 @@ AND (
|
|||||||
NOT EXISTS (
|
NOT EXISTS (
|
||||||
SELECT 1 FROM manga_category
|
SELECT 1 FROM manga_category
|
||||||
WHERE manga_category.manga_id = manga.id
|
WHERE manga_category.manga_id = manga.id
|
||||||
AND COALESCE(manga_category.category_id, 0) IN :excludedCategories
|
AND manga_category.category_id IN :excludedCategories
|
||||||
)
|
)
|
||||||
AND (
|
AND (
|
||||||
0 NOT IN :excludedCategories
|
0 NOT IN :excludedCategories
|
||||||
@@ -193,10 +174,11 @@ ORDER BY state_chapter_next_update ASC;
|
|||||||
|
|
||||||
resetViewerFlags:
|
resetViewerFlags:
|
||||||
UPDATE manga
|
UPDATE manga
|
||||||
SET user_reader_flags = 0;
|
SET user_reader_flags = 0
|
||||||
|
WHERE user_reader_flags != 0;
|
||||||
|
|
||||||
getSourceIdsWithNonLibraryManga:
|
getSourceIdsWithNonLibraryManga:
|
||||||
SELECT source_id, COUNT(*) AS manga_count
|
SELECT source_id, count(*) AS manga_count
|
||||||
FROM manga
|
FROM manga
|
||||||
WHERE user_favorite_at IS NULL
|
WHERE user_favorite_at IS NULL
|
||||||
GROUP BY source_id;
|
GROUP BY source_id;
|
||||||
@@ -207,105 +189,119 @@ WHERE user_favorite_at IS NULL
|
|||||||
AND source_id IN :sourceIds
|
AND source_id IN :sourceIds
|
||||||
AND (
|
AND (
|
||||||
:keepReadManga = 0
|
:keepReadManga = 0
|
||||||
OR id NOT IN (
|
OR NOT EXISTS (
|
||||||
SELECT DISTINCT manga_id
|
SELECT 1
|
||||||
FROM chapter
|
FROM chapter
|
||||||
WHERE user_read = 1
|
WHERE chapter.manga_id = manga.id
|
||||||
OR user_last_page_read != 0
|
AND (user_read = 1 OR user_last_page_read != 0)
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
insertReturningId:
|
insertReturningId:
|
||||||
INSERT INTO manga(source_id, remote_url, remote_artist, remote_author, remote_description, remote_genre, remote_title, remote_status, remote_cover, user_favorite_at, state_chapter_last_update, state_chapter_next_update, state_initialized, user_reader_flags, user_chapter_flags, state_cover_last_modified, remote_update_strategy, state_chapter_fetch_interval, user_notes, remote_memo)
|
INSERT INTO manga(
|
||||||
VALUES (:source, :url, :artist, :author, :description, :genre, :title, :status, :thumbnailUrl, :favoriteAt, :lastUpdate, :nextUpdate, :initialized, :viewerFlags, :chapterFlags, :coverLastModified, :updateStrategy, :calculateInterval, :notes, :memo)
|
source_id, remote_url, remote_title, remote_author, remote_artist, remote_description,
|
||||||
|
remote_genre, remote_status, remote_cover, remote_update_strategy, remote_memo,
|
||||||
|
user_favorite_at, user_notes, user_reader_flags, user_chapter_flags,
|
||||||
|
state_chapter_last_update, state_chapter_next_update, state_chapter_fetch_interval,
|
||||||
|
state_cover_last_modified, state_initialized
|
||||||
|
)
|
||||||
|
VALUES (
|
||||||
|
:sourceId, :remoteUrl, :remoteTitle, :remoteAuthor, :remoteArtist, :remoteDescription,
|
||||||
|
:remoteGenre, :remoteStatus, :remoteCover, :remoteUpdateStrategy, :remoteMemo,
|
||||||
|
:userFavoriteAt, :userNotes, :userReaderFlags, :userChapterFlags,
|
||||||
|
:stateChapterLastUpdate, :stateChapterNextUpdate, :stateChapterFetchInterval,
|
||||||
|
:stateCoverLastModified, :stateInitialized
|
||||||
|
)
|
||||||
RETURNING id;
|
RETURNING id;
|
||||||
|
|
||||||
update:
|
update:
|
||||||
UPDATE manga SET
|
UPDATE manga SET
|
||||||
-- NULL is a real value here (not in the library), which coalesce cannot write
|
-- NULL is a real value here (not in the library), which coalesce cannot write
|
||||||
user_favorite_at = CASE WHEN :favoriteAtSet THEN :favoriteAt ELSE user_favorite_at END,
|
user_favorite_at = CASE WHEN :userFavoriteAtSet THEN :userFavoriteAt ELSE user_favorite_at END,
|
||||||
state_chapter_last_update = coalesce(:lastUpdate, state_chapter_last_update),
|
user_notes = coalesce(:userNotes, user_notes),
|
||||||
state_chapter_next_update = coalesce(:nextUpdate, state_chapter_next_update),
|
user_reader_flags = coalesce(:userReaderFlags, user_reader_flags),
|
||||||
user_reader_flags = coalesce(:viewer, user_reader_flags),
|
user_chapter_flags = coalesce(:userChapterFlags, user_chapter_flags),
|
||||||
user_chapter_flags = coalesce(:chapterFlags, user_chapter_flags),
|
state_chapter_last_update = coalesce(:stateChapterLastUpdate, state_chapter_last_update),
|
||||||
state_cover_last_modified = coalesce(:coverLastModified, state_cover_last_modified),
|
state_chapter_next_update = coalesce(:stateChapterNextUpdate, state_chapter_next_update),
|
||||||
state_chapter_fetch_interval = coalesce(:calculateInterval, state_chapter_fetch_interval),
|
state_chapter_fetch_interval = coalesce(:stateChapterFetchInterval, state_chapter_fetch_interval),
|
||||||
user_notes = coalesce(:notes, user_notes)
|
state_cover_last_modified = coalesce(:stateCoverLastModified, state_cover_last_modified)
|
||||||
WHERE id = :mangaId;
|
WHERE id = :id;
|
||||||
|
|
||||||
updateFromBackup:
|
updateFromBackup:
|
||||||
UPDATE manga SET
|
UPDATE manga SET
|
||||||
remote_artist = coalesce(:artist, remote_artist),
|
remote_title = coalesce(:remoteTitle, remote_title),
|
||||||
remote_author = coalesce(:author, remote_author),
|
remote_author = coalesce(:remoteAuthor, remote_author),
|
||||||
remote_description = coalesce(:description, remote_description),
|
remote_artist = coalesce(:remoteArtist, remote_artist),
|
||||||
remote_genre = coalesce(:genre, remote_genre),
|
remote_description = coalesce(:remoteDescription, remote_description),
|
||||||
remote_title = coalesce(:title, remote_title),
|
remote_genre = coalesce(:remoteGenre, remote_genre),
|
||||||
remote_status = coalesce(:status, remote_status),
|
remote_status = coalesce(:remoteStatus, remote_status),
|
||||||
remote_cover = coalesce(:thumbnailUrl, remote_cover),
|
remote_cover = coalesce(:remoteCover, remote_cover),
|
||||||
user_favorite_at = :favoriteAt,
|
remote_update_strategy = coalesce(:remoteUpdateStrategy, remote_update_strategy),
|
||||||
state_chapter_last_update = coalesce(:lastUpdate, state_chapter_last_update),
|
remote_memo = coalesce(:remoteMemo, remote_memo),
|
||||||
state_initialized = coalesce(:initialized, state_initialized),
|
user_favorite_at = :userFavoriteAt,
|
||||||
user_reader_flags = coalesce(:viewer, user_reader_flags),
|
user_notes = coalesce(:userNotes, user_notes),
|
||||||
user_chapter_flags = coalesce(:chapterFlags, user_chapter_flags),
|
user_reader_flags = coalesce(:userReaderFlags, user_reader_flags),
|
||||||
state_cover_last_modified = coalesce(:coverLastModified, state_cover_last_modified),
|
user_chapter_flags = coalesce(:userChapterFlags, user_chapter_flags),
|
||||||
remote_update_strategy = coalesce(:updateStrategy, remote_update_strategy),
|
state_chapter_last_update = coalesce(:stateChapterLastUpdate, state_chapter_last_update),
|
||||||
user_notes = coalesce(:notes, user_notes),
|
state_cover_last_modified = coalesce(:stateCoverLastModified, state_cover_last_modified),
|
||||||
remote_memo = coalesce(:memo, remote_memo)
|
state_initialized = coalesce(:stateInitialized, state_initialized)
|
||||||
WHERE id = :mangaId;
|
WHERE id = :id;
|
||||||
|
|
||||||
updateRemote:
|
updateRemote:
|
||||||
UPDATE manga SET
|
UPDATE manga SET
|
||||||
remote_artist = coalesce(:artist, remote_artist),
|
remote_title = coalesce(:remoteTitle, remote_title),
|
||||||
remote_author = coalesce(:author, remote_author),
|
remote_author = coalesce(:remoteAuthor, remote_author),
|
||||||
remote_description = coalesce(:description, remote_description),
|
remote_artist = coalesce(:remoteArtist, remote_artist),
|
||||||
remote_genre = coalesce(:genre, remote_genre),
|
remote_description = coalesce(:remoteDescription, remote_description),
|
||||||
remote_title = coalesce(:title, remote_title),
|
remote_genre = coalesce(:remoteGenre, remote_genre),
|
||||||
remote_status = :status,
|
remote_status = :remoteStatus,
|
||||||
remote_cover = coalesce(:thumbnailUrl, remote_cover),
|
remote_cover = coalesce(:remoteCover, remote_cover),
|
||||||
state_initialized = :initialized,
|
remote_update_strategy = :remoteUpdateStrategy,
|
||||||
state_cover_last_modified = coalesce(:coverLastModified, state_cover_last_modified),
|
remote_memo = :remoteMemo,
|
||||||
remote_update_strategy = :updateStrategy,
|
state_cover_last_modified = coalesce(:stateCoverLastModified, state_cover_last_modified),
|
||||||
remote_memo = :memo
|
state_initialized = :stateInitialized
|
||||||
WHERE id = :mangaId;
|
WHERE id = :id;
|
||||||
|
|
||||||
insertNetworkManga {
|
insertNetworkManga {
|
||||||
-- Insert the manga if it doesn't exist already
|
-- A new entry is inserted as the source reported it. An existing one takes the fresh details
|
||||||
|
-- only while it is outside the library, so a library entry keeps what the user already has.
|
||||||
INSERT INTO manga(
|
INSERT INTO manga(
|
||||||
source_id, remote_url, remote_artist, remote_author, remote_description, remote_genre, remote_title, remote_status, remote_cover, user_favorite_at,
|
source_id, remote_url, remote_title, remote_author, remote_artist, remote_description,
|
||||||
state_chapter_last_update, state_chapter_next_update, state_initialized, user_reader_flags, user_chapter_flags, state_cover_last_modified,
|
remote_genre, remote_status, remote_cover, remote_update_strategy, remote_memo,
|
||||||
remote_update_strategy, state_chapter_fetch_interval, user_notes, remote_memo
|
user_favorite_at, user_notes, user_reader_flags, user_chapter_flags,
|
||||||
|
state_chapter_last_update, state_chapter_next_update, state_chapter_fetch_interval,
|
||||||
|
state_cover_last_modified, state_initialized
|
||||||
)
|
)
|
||||||
SELECT
|
VALUES (
|
||||||
:source, :url, :artist, :author, :description, :genre, :title, :status, :thumbnailUrl, :favoriteAt,
|
:sourceId, :remoteUrl, :remoteTitle, :remoteAuthor, :remoteArtist, :remoteDescription,
|
||||||
:lastUpdate, :nextUpdate, :initialized, :viewerFlags, :chapterFlags, :coverLastModified,
|
:remoteGenre, :remoteStatus, :remoteCover, :remoteUpdateStrategy, :remoteMemo,
|
||||||
:updateStrategy, :calculateInterval, '', :memo
|
:userFavoriteAt, '', :userReaderFlags, :userChapterFlags,
|
||||||
WHERE NOT EXISTS(SELECT 0 FROM manga WHERE source_id = :source AND remote_url = :url);
|
:stateChapterLastUpdate, :stateChapterNextUpdate, :stateChapterFetchInterval,
|
||||||
|
:stateCoverLastModified, :stateInitialized
|
||||||
|
)
|
||||||
|
ON CONFLICT(source_id, remote_url) DO UPDATE SET
|
||||||
|
remote_title = CASE WHEN :updateTitle THEN excluded.remote_title ELSE remote_title END,
|
||||||
|
remote_cover = CASE WHEN :updateCover THEN excluded.remote_cover ELSE remote_cover END,
|
||||||
|
remote_author = CASE WHEN :updateDetails THEN excluded.remote_author ELSE remote_author END,
|
||||||
|
remote_artist = CASE WHEN :updateDetails THEN excluded.remote_artist ELSE remote_artist END,
|
||||||
|
remote_description = CASE WHEN :updateDetails THEN excluded.remote_description ELSE remote_description END,
|
||||||
|
remote_genre = CASE WHEN :updateDetails THEN excluded.remote_genre ELSE remote_genre END,
|
||||||
|
remote_status = CASE WHEN :updateDetails THEN excluded.remote_status ELSE remote_status END,
|
||||||
|
remote_update_strategy = CASE
|
||||||
|
WHEN :updateDetails THEN excluded.remote_update_strategy
|
||||||
|
ELSE remote_update_strategy
|
||||||
|
END,
|
||||||
|
remote_memo = excluded.remote_memo,
|
||||||
|
state_initialized = :updateDetails
|
||||||
|
WHERE user_favorite_at IS NULL;
|
||||||
|
|
||||||
-- Update the relevant details if applicable and not favorite
|
|
||||||
UPDATE manga
|
|
||||||
SET
|
|
||||||
remote_title = CASE WHEN :updateTitle THEN :title ELSE remote_title END,
|
|
||||||
remote_cover = CASE WHEN :updateCover THEN :thumbnailUrl ELSE remote_cover END,
|
|
||||||
remote_author = CASE WHEN :updateDetails THEN :author ELSE remote_author END,
|
|
||||||
remote_artist = CASE WHEN :updateDetails THEN :artist ELSE remote_artist END,
|
|
||||||
remote_description = CASE WHEN :updateDetails THEN :description ELSE remote_description END,
|
|
||||||
remote_genre = CASE WHEN :updateDetails THEN :genre ELSE remote_genre END,
|
|
||||||
remote_status = CASE WHEN :updateDetails THEN :status ELSE remote_status END,
|
|
||||||
remote_update_strategy = CASE WHEN :updateDetails THEN :updateStrategy ELSE remote_update_strategy END,
|
|
||||||
state_initialized = :updateDetails,
|
|
||||||
remote_memo = :memo
|
|
||||||
WHERE source_id = :source
|
|
||||||
AND remote_url = :url
|
|
||||||
AND user_favorite_at IS NULL;
|
|
||||||
|
|
||||||
-- Finally return the manga
|
|
||||||
SELECT
|
SELECT
|
||||||
id, source_id, remote_url, remote_artist, remote_author, remote_description, remote_genre, remote_title,
|
id, source_id, remote_url, remote_title, remote_author, remote_artist, remote_description,
|
||||||
remote_status, remote_cover, state_chapter_last_update, state_chapter_next_update, state_initialized,
|
remote_genre, remote_status, remote_cover, remote_update_strategy, remote_memo,
|
||||||
user_reader_flags, user_chapter_flags, state_cover_last_modified, remote_update_strategy,
|
user_favorite_at, user_notes, user_reader_flags, user_chapter_flags,
|
||||||
state_chapter_fetch_interval, user_notes, remote_memo, user_favorite_at
|
state_chapter_last_update, state_chapter_next_update, state_chapter_fetch_interval,
|
||||||
|
state_cover_last_modified, state_initialized
|
||||||
FROM manga
|
FROM manga
|
||||||
WHERE source_id = :source
|
WHERE source_id = :sourceId
|
||||||
AND remote_url = :url
|
AND remote_url = :remoteUrl;
|
||||||
LIMIT 1;
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,7 +12,8 @@ CREATE INDEX manga_category_category_id_index ON manga_category(category_id);
|
|||||||
|
|
||||||
insert:
|
insert:
|
||||||
INSERT INTO manga_category(manga_id, category_id)
|
INSERT INTO manga_category(manga_id, category_id)
|
||||||
VALUES (:mangaId, :categoryId);
|
VALUES (:mangaId, :categoryId)
|
||||||
|
ON CONFLICT(manga_id, category_id) DO NOTHING;
|
||||||
|
|
||||||
deleteMangaCategoryByMangaId:
|
deleteMangaCategoryByMangaId:
|
||||||
DELETE FROM manga_category
|
DELETE FROM manga_category
|
||||||
|
|||||||
@@ -24,63 +24,64 @@ CREATE INDEX manga_track_tracker_id_remote_id_index ON manga_track(tracker_id, r
|
|||||||
|
|
||||||
delete:
|
delete:
|
||||||
DELETE FROM manga_track
|
DELETE FROM manga_track
|
||||||
WHERE manga_id = :mangaId AND tracker_id = :syncId;
|
WHERE manga_id = :mangaId
|
||||||
|
AND tracker_id = :trackerId;
|
||||||
|
|
||||||
getTracks:
|
getTracks:
|
||||||
SELECT
|
SELECT
|
||||||
id, manga_id, tracker_id, remote_id, library_id,
|
id, manga_id, tracker_id, remote_id, remote_url, library_id, title, last_chapter_read,
|
||||||
title, last_chapter_read, total_chapters, status, score,
|
total_chapters, status, score, start_date, finish_date, private
|
||||||
remote_url, start_date, finish_date, private
|
|
||||||
FROM manga_track;
|
FROM manga_track;
|
||||||
|
|
||||||
getTrackById:
|
getTrackById:
|
||||||
SELECT
|
SELECT
|
||||||
id, manga_id, tracker_id, remote_id, library_id,
|
id, manga_id, tracker_id, remote_id, remote_url, library_id, title, last_chapter_read,
|
||||||
title, last_chapter_read, total_chapters, status, score,
|
total_chapters, status, score, start_date, finish_date, private
|
||||||
remote_url, start_date, finish_date, private
|
|
||||||
FROM manga_track
|
FROM manga_track
|
||||||
WHERE id = :id;
|
WHERE id = :id;
|
||||||
|
|
||||||
getTracksByMangaId:
|
getTracksByMangaId:
|
||||||
SELECT
|
SELECT
|
||||||
id, manga_id, tracker_id, remote_id, library_id,
|
id, manga_id, tracker_id, remote_id, remote_url, library_id, title, last_chapter_read,
|
||||||
title, last_chapter_read, total_chapters, status, score,
|
total_chapters, status, score, start_date, finish_date, private
|
||||||
remote_url, start_date, finish_date, private
|
|
||||||
FROM manga_track
|
FROM manga_track
|
||||||
WHERE manga_id = :mangaId;
|
WHERE manga_id = :mangaId;
|
||||||
|
|
||||||
upsert:
|
upsert:
|
||||||
INSERT INTO manga_track(manga_id,tracker_id,remote_id,library_id,title,last_chapter_read,total_chapters,status,score,remote_url,start_date,finish_date,private)
|
INSERT INTO manga_track(
|
||||||
VALUES (:mangaId,:syncId,:remoteId,:libraryId,:title,:lastChapterRead,:totalChapters,:status,:score,:remoteUrl,:startDate,:finishDate,:private)
|
manga_id, tracker_id, remote_id, remote_url, library_id, title, last_chapter_read,
|
||||||
ON CONFLICT(manga_id, tracker_id)
|
total_chapters, status, score, start_date, finish_date, private
|
||||||
DO UPDATE
|
)
|
||||||
SET
|
VALUES (
|
||||||
remote_id = :remoteId,
|
:mangaId, :trackerId, :remoteId, :remoteUrl, :libraryId, :title, :lastChapterRead,
|
||||||
library_id = :libraryId,
|
:totalChapters, :status, :score, :startDate, :finishDate, :private
|
||||||
title = :title,
|
)
|
||||||
last_chapter_read = :lastChapterRead,
|
ON CONFLICT(manga_id, tracker_id) DO UPDATE SET
|
||||||
total_chapters = :totalChapters,
|
remote_id = excluded.remote_id,
|
||||||
status = :status,
|
remote_url = excluded.remote_url,
|
||||||
score = :score,
|
library_id = excluded.library_id,
|
||||||
remote_url = :remoteUrl,
|
title = excluded.title,
|
||||||
start_date = :startDate,
|
last_chapter_read = excluded.last_chapter_read,
|
||||||
finish_date = :finishDate,
|
total_chapters = excluded.total_chapters,
|
||||||
private = :private
|
status = excluded.status,
|
||||||
WHERE manga_id = :mangaId AND tracker_id = :syncId;
|
score = excluded.score,
|
||||||
|
start_date = excluded.start_date,
|
||||||
|
finish_date = excluded.finish_date,
|
||||||
|
private = excluded.private;
|
||||||
|
|
||||||
update:
|
update:
|
||||||
UPDATE manga_track
|
UPDATE manga_track
|
||||||
SET
|
SET
|
||||||
manga_id = coalesce(:mangaId, manga_id),
|
manga_id = coalesce(:mangaId, manga_id),
|
||||||
tracker_id = coalesce(:syncId, tracker_id),
|
tracker_id = coalesce(:trackerId, tracker_id),
|
||||||
remote_id = coalesce(:mediaId, remote_id),
|
remote_id = coalesce(:remoteId, remote_id),
|
||||||
|
remote_url = coalesce(:remoteUrl, remote_url),
|
||||||
library_id = coalesce(:libraryId, library_id),
|
library_id = coalesce(:libraryId, library_id),
|
||||||
title = coalesce(:title, title),
|
title = coalesce(:title, title),
|
||||||
last_chapter_read = coalesce(:lastChapterRead, last_chapter_read),
|
last_chapter_read = coalesce(:lastChapterRead, last_chapter_read),
|
||||||
total_chapters = coalesce(:totalChapter, total_chapters),
|
total_chapters = coalesce(:totalChapters, total_chapters),
|
||||||
status = coalesce(:status, status),
|
status = coalesce(:status, status),
|
||||||
score = coalesce(:score, score),
|
score = coalesce(:score, score),
|
||||||
remote_url = coalesce(:trackingUrl, remote_url),
|
|
||||||
start_date = coalesce(:startDate, start_date),
|
start_date = coalesce(:startDate, start_date),
|
||||||
finish_date = coalesce(:finishDate, finish_date),
|
finish_date = coalesce(:finishDate, finish_date),
|
||||||
private = coalesce(:private, private)
|
private = coalesce(:private, private)
|
||||||
|
|||||||
@@ -5,20 +5,17 @@ CREATE TABLE source(
|
|||||||
);
|
);
|
||||||
|
|
||||||
findAll:
|
findAll:
|
||||||
SELECT id, language, name
|
SELECT id, name, language
|
||||||
FROM source;
|
FROM source;
|
||||||
|
|
||||||
findOne:
|
findOne:
|
||||||
SELECT id, language, name
|
SELECT id, name, language
|
||||||
FROM source
|
FROM source
|
||||||
WHERE id = :id;
|
WHERE id = :id;
|
||||||
|
|
||||||
upsert:
|
upsert:
|
||||||
INSERT INTO source(id, language, name)
|
INSERT INTO source(id, name, language)
|
||||||
VALUES (:id, :lang, :name)
|
VALUES (:id, :name, :language)
|
||||||
ON CONFLICT(id)
|
ON CONFLICT(id) DO UPDATE SET
|
||||||
DO UPDATE
|
name = excluded.name,
|
||||||
SET
|
language = excluded.language;
|
||||||
language = :lang,
|
|
||||||
name = :name
|
|
||||||
WHERE id = :id;
|
|
||||||
|
|||||||
@@ -46,18 +46,22 @@ ORDER BY readAt DESC;
|
|||||||
|
|
||||||
getLatestHistory:
|
getLatestHistory:
|
||||||
SELECT
|
SELECT
|
||||||
id,
|
history.id AS id,
|
||||||
mangaId,
|
manga.id AS mangaId,
|
||||||
chapterId,
|
chapter.id AS chapterId,
|
||||||
title,
|
manga.remote_title AS title,
|
||||||
thumbnailUrl,
|
manga.remote_cover AS thumbnailUrl,
|
||||||
sourceId,
|
manga.source_id AS sourceId,
|
||||||
favorite,
|
manga.user_favorite_at IS NOT NULL AS favorite,
|
||||||
coverLastModified,
|
manga.state_cover_last_modified AS coverLastModified,
|
||||||
chapterNumber,
|
chapter.remote_chapter_number AS chapterNumber,
|
||||||
readAt,
|
history.read_at AS readAt,
|
||||||
readDuration
|
history.read_duration AS readDuration
|
||||||
FROM historyView
|
FROM history
|
||||||
WHERE historyView.readAt > 0
|
JOIN manga
|
||||||
ORDER BY readAt DESC
|
ON manga.id = history.manga_id
|
||||||
|
JOIN chapter
|
||||||
|
ON chapter.id = history.chapter_id
|
||||||
|
WHERE history.read_at > 0
|
||||||
|
ORDER BY history.read_at DESC
|
||||||
LIMIT 1;
|
LIMIT 1;
|
||||||
|
|||||||
@@ -40,11 +40,10 @@ WHERE M.user_favorite_at IS NOT NULL;
|
|||||||
|
|
||||||
library:
|
library:
|
||||||
SELECT
|
SELECT
|
||||||
id, source_id, remote_url, remote_artist, remote_author,
|
id, source_id, remote_url, remote_title, remote_author, remote_artist, remote_description,
|
||||||
remote_description, remote_genre, remote_title, remote_status, remote_cover,
|
remote_genre, remote_status, remote_cover, remote_update_strategy, remote_memo,
|
||||||
state_chapter_last_update, state_chapter_next_update, state_initialized, user_reader_flags,
|
user_favorite_at, user_notes, user_reader_flags, user_chapter_flags,
|
||||||
user_chapter_flags, state_cover_last_modified, remote_update_strategy, state_chapter_fetch_interval,
|
state_chapter_last_update, state_chapter_next_update, state_chapter_fetch_interval,
|
||||||
user_notes, remote_memo,
|
state_cover_last_modified, state_initialized, totalCount, readCount, latestUpload,
|
||||||
user_favorite_at, totalCount, readCount, latestUpload, chapterFetchedAt,
|
chapterFetchedAt, lastRead, bookmarkCount, categories
|
||||||
lastRead, bookmarkCount, categories
|
|
||||||
FROM libraryView;
|
FROM libraryView;
|
||||||
|
|||||||
@@ -25,23 +25,11 @@ WHERE user_favorite_at IS NOT NULL
|
|||||||
AND state_date_fetch > user_favorite_at
|
AND state_date_fetch > user_favorite_at
|
||||||
ORDER BY state_date_fetch DESC;
|
ORDER BY state_date_fetch DESC;
|
||||||
|
|
||||||
getRecentUpdates:
|
|
||||||
SELECT
|
|
||||||
mangaId, mangaTitle, chapterId, chapterName,
|
|
||||||
scanlator, chapterUrl, read, bookmark,
|
|
||||||
lastPageRead, sourceId, favorite, thumbnailUrl,
|
|
||||||
coverLastModified, dateUpload, dateFetch, excludedScanlator
|
|
||||||
FROM updatesView
|
|
||||||
WHERE dateUpload > :after
|
|
||||||
ORDER BY dateFetch DESC
|
|
||||||
LIMIT :limit;
|
|
||||||
|
|
||||||
getRecentUpdatesWithFilters:
|
getRecentUpdatesWithFilters:
|
||||||
SELECT
|
SELECT
|
||||||
mangaId, mangaTitle, chapterId, chapterName,
|
mangaId, mangaTitle, chapterId, chapterName, scanlator, chapterUrl, read, bookmark,
|
||||||
scanlator, chapterUrl, read, bookmark,
|
lastPageRead, sourceId, favorite, thumbnailUrl, coverLastModified, dateUpload, dateFetch,
|
||||||
lastPageRead, sourceId, favorite, thumbnailUrl,
|
excludedScanlator
|
||||||
coverLastModified, dateUpload, dateFetch, excludedScanlator
|
|
||||||
FROM updatesView
|
FROM updatesView
|
||||||
WHERE dateUpload > :after
|
WHERE dateUpload > :after
|
||||||
AND (:read IS NULL OR read = :read)
|
AND (:read IS NULL OR read = :read)
|
||||||
@@ -52,19 +40,17 @@ AND (
|
|||||||
OR (:started = 0 AND lastPageRead = 0 AND read = 0)
|
OR (:started = 0 AND lastPageRead = 0 AND read = 0)
|
||||||
)
|
)
|
||||||
AND (:bookmarked IS NULL OR bookmark = :bookmarked)
|
AND (:bookmarked IS NULL OR bookmark = :bookmarked)
|
||||||
AND (
|
AND (excludedScanlator IS NULL OR :hideExcludedScanlators = 0)
|
||||||
(excludedScanlator IS NULL OR :hideExcludedScanlators = 0)
|
|
||||||
)
|
|
||||||
AND (
|
AND (
|
||||||
-- includedEmpty being true expresses "don't care" state and bypasses the membership filter
|
-- includedEmpty being true expresses "don't care" state and bypasses the membership filter
|
||||||
:includedEmpty
|
:includedEmpty
|
||||||
OR EXISTS (
|
OR EXISTS (
|
||||||
SELECT 1 FROM manga_category
|
SELECT 1 FROM manga_category
|
||||||
WHERE manga_category.manga_id = mangaId
|
WHERE manga_category.manga_id = mangaId
|
||||||
AND COALESCE(manga_category.category_id, 0) IN :includedCategories
|
AND manga_category.category_id IN :includedCategories
|
||||||
)
|
)
|
||||||
OR (0 IN :includedCategories AND NOT EXISTS (
|
OR (0 IN :includedCategories AND NOT EXISTS (
|
||||||
SELECT 1 FROM manga_category mc WHERE mc.manga_id = mangaId)
|
SELECT 1 FROM manga_category WHERE manga_category.manga_id = mangaId)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
AND (
|
AND (
|
||||||
@@ -74,7 +60,7 @@ AND (
|
|||||||
NOT EXISTS (
|
NOT EXISTS (
|
||||||
SELECT 1 FROM manga_category
|
SELECT 1 FROM manga_category
|
||||||
WHERE manga_category.manga_id = mangaId
|
WHERE manga_category.manga_id = mangaId
|
||||||
AND COALESCE(manga_category.category_id, 0) IN :excludedCategories
|
AND manga_category.category_id IN :excludedCategories
|
||||||
)
|
)
|
||||||
AND (
|
AND (
|
||||||
0 NOT IN :excludedCategories
|
0 NOT IN :excludedCategories
|
||||||
@@ -85,14 +71,24 @@ AND (
|
|||||||
ORDER BY dateFetch DESC
|
ORDER BY dateFetch DESC
|
||||||
LIMIT :limit;
|
LIMIT :limit;
|
||||||
|
|
||||||
getUpdatesByReadStatus:
|
getReadUpdates:
|
||||||
SELECT
|
SELECT
|
||||||
mangaId, mangaTitle, chapterId, chapterName,
|
mangaId, mangaTitle, chapterId, chapterName, scanlator, chapterUrl, read, bookmark,
|
||||||
scanlator, chapterUrl, read, bookmark,
|
lastPageRead, sourceId, favorite, thumbnailUrl, coverLastModified, dateUpload, dateFetch,
|
||||||
lastPageRead, sourceId, favorite, thumbnailUrl,
|
excludedScanlator
|
||||||
coverLastModified, dateUpload, dateFetch, excludedScanlator
|
|
||||||
FROM updatesView
|
FROM updatesView
|
||||||
WHERE read = :read
|
WHERE read = 1
|
||||||
|
AND dateUpload > :after
|
||||||
|
ORDER BY dateFetch DESC
|
||||||
|
LIMIT :limit;
|
||||||
|
|
||||||
|
getUnreadUpdates:
|
||||||
|
SELECT
|
||||||
|
mangaId, mangaTitle, chapterId, chapterName, scanlator, chapterUrl, read, bookmark,
|
||||||
|
lastPageRead, sourceId, favorite, thumbnailUrl, coverLastModified, dateUpload, dateFetch,
|
||||||
|
excludedScanlator
|
||||||
|
FROM updatesView
|
||||||
|
WHERE read = 0
|
||||||
AND dateUpload > :after
|
AND dateUpload > :after
|
||||||
ORDER BY dateFetch DESC
|
ORDER BY dateFetch DESC
|
||||||
LIMIT :limit;
|
LIMIT :limit;
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import tachiyomi.domain.manga.model.MangaUpdate
|
|||||||
|
|
||||||
interface RestoreRepository {
|
interface RestoreRepository {
|
||||||
|
|
||||||
suspend fun getMangaUrlsBySourceId(): Map<Long, List<String>>
|
suspend fun getMangaUrlsBySourceId(): Map<Long, Set<String>>
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Restores [entries] all together: either every one of them is restored or none is. [update] is what to change on each
|
* Restores [entries] all together: either every one of them is restored or none is. [update] is what to change on each
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ interface CategoryRepository {
|
|||||||
|
|
||||||
suspend fun updateFlags(categoryId: Long, flags: Long)
|
suspend fun updateFlags(categoryId: Long, flags: Long)
|
||||||
|
|
||||||
suspend fun updateAllFlags(flags: Long?)
|
suspend fun updateAllFlags(flags: Long)
|
||||||
|
|
||||||
suspend fun updateAllOrders(orderedIds: List<Long>)
|
suspend fun updateAllOrders(orderedIds: List<Long>)
|
||||||
|
|
||||||
|
|||||||
+16
-12
@@ -16,23 +16,27 @@ class SetMangaDefaultChapterFlags(
|
|||||||
|
|
||||||
suspend fun await(manga: Manga) {
|
suspend fun await(manga: Manga) {
|
||||||
withNonCancellableContext {
|
withNonCancellableContext {
|
||||||
with(libraryPreferences) {
|
setDefaultFlags(listOf(manga.id))
|
||||||
setMangaChapterFlags.awaitSetAllFlags(
|
|
||||||
mangaId = manga.id,
|
|
||||||
unreadFilter = filterChapterByRead.get(),
|
|
||||||
downloadedFilter = filterChapterByDownloaded.get(),
|
|
||||||
bookmarkedFilter = filterChapterByBookmarked.get(),
|
|
||||||
sortingMode = sortChapterBySourceOrNumber.get(),
|
|
||||||
sortingDirection = sortChapterByAscendingOrDescending.get(),
|
|
||||||
displayMode = displayChapterByNameOrNumber.get(),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun awaitAll() {
|
suspend fun awaitAll() {
|
||||||
withNonCancellableContext {
|
withNonCancellableContext {
|
||||||
getFavorites.await().forEach { await(it) }
|
setDefaultFlags(getFavorites.await().map { it.id })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private suspend fun setDefaultFlags(mangaIds: List<Long>) {
|
||||||
|
with(libraryPreferences) {
|
||||||
|
setMangaChapterFlags.awaitSetAllFlags(
|
||||||
|
mangaIds = mangaIds,
|
||||||
|
unreadFilter = filterChapterByRead.get(),
|
||||||
|
downloadedFilter = filterChapterByDownloaded.get(),
|
||||||
|
bookmarkedFilter = filterChapterByBookmarked.get(),
|
||||||
|
sortingMode = sortChapterBySourceOrNumber.get(),
|
||||||
|
sortingDirection = sortChapterByAscendingOrDescending.get(),
|
||||||
|
displayMode = displayChapterByNameOrNumber.get(),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -67,7 +67,7 @@ class SetMangaChapterFlags(
|
|||||||
}
|
}
|
||||||
|
|
||||||
suspend fun awaitSetAllFlags(
|
suspend fun awaitSetAllFlags(
|
||||||
mangaId: Long,
|
mangaIds: List<Long>,
|
||||||
unreadFilter: Long,
|
unreadFilter: Long,
|
||||||
downloadedFilter: Long,
|
downloadedFilter: Long,
|
||||||
bookmarkedFilter: Long,
|
bookmarkedFilter: Long,
|
||||||
@@ -75,16 +75,13 @@ class SetMangaChapterFlags(
|
|||||||
sortingDirection: Long,
|
sortingDirection: Long,
|
||||||
displayMode: Long,
|
displayMode: Long,
|
||||||
): Boolean {
|
): Boolean {
|
||||||
return mangaRepository.update(
|
val flags = 0L.setFlag(unreadFilter, Manga.CHAPTER_UNREAD_MASK)
|
||||||
MangaUpdate(mangaId) {
|
.setFlag(downloadedFilter, Manga.CHAPTER_DOWNLOADED_MASK)
|
||||||
chapterFlags = 0L.setFlag(unreadFilter, Manga.CHAPTER_UNREAD_MASK)
|
.setFlag(bookmarkedFilter, Manga.CHAPTER_BOOKMARKED_MASK)
|
||||||
.setFlag(downloadedFilter, Manga.CHAPTER_DOWNLOADED_MASK)
|
.setFlag(sortingMode, Manga.CHAPTER_SORTING_MASK)
|
||||||
.setFlag(bookmarkedFilter, Manga.CHAPTER_BOOKMARKED_MASK)
|
.setFlag(sortingDirection, Manga.CHAPTER_SORT_DIR_MASK)
|
||||||
.setFlag(sortingMode, Manga.CHAPTER_SORTING_MASK)
|
.setFlag(displayMode, Manga.CHAPTER_DISPLAY_MASK)
|
||||||
.setFlag(sortingDirection, Manga.CHAPTER_SORT_DIR_MASK)
|
return mangaRepository.updateAll(mangaIds.map { MangaUpdate(it) { chapterFlags = flags } })
|
||||||
.setFlag(displayMode, Manga.CHAPTER_DISPLAY_MASK)
|
|
||||||
},
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun Long.setFlag(flag: Long, mask: Long): Long {
|
private fun Long.setFlag(flag: Long, mask: Long): Long {
|
||||||
|
|||||||
Reference in New Issue
Block a user