feat: Add support for backing up and storing source's feeds along with saved searches

This commit is contained in:
Cuong-Tran 2024-07-03 13:01:51 +07:00 committed by Tran M. Cuong
parent 9d36c1ae15
commit 7c63f84f5f
7 changed files with 66 additions and 26 deletions

View file

@ -1,6 +1,7 @@
package eu.kanade.tachiyomi.data.backup.create.creators
import eu.kanade.tachiyomi.data.backup.models.BackupSavedSearch
import eu.kanade.tachiyomi.data.backup.models.backupFeedMapper
import eu.kanade.tachiyomi.data.backup.models.backupSavedSearchMapper
import tachiyomi.data.DatabaseHandler
import uy.kohesive.injekt.Injekt
@ -11,6 +12,23 @@ class SavedSearchBackupCreator(
) {
suspend fun backupSavedSearches(): List<BackupSavedSearch> {
return handler.awaitList { saved_searchQueries.selectAll(backupSavedSearchMapper) }
// KMK -->
// return handler.awaitList { saved_searchQueries.selectAll(backupSavedSearchMapper) }
val savedSearches = handler.awaitList { saved_searchQueries.selectAll() }
val feedSavedSearches =
handler.awaitList { feed_saved_searchQueries.selectAllFeedHasSavedSearch() }
return savedSearches.map { savedSearch ->
val feeds = feedSavedSearches.filter { it.saved_search == savedSearch._id }
backupSavedSearchMapper(
savedSearch._id,
savedSearch.source,
savedSearch.name,
savedSearch.query,
savedSearch.filters_json,
feeds.map { it.backupFeedMapper() },
)
}
// KMK <--
}
}

View file

@ -2,6 +2,7 @@ package eu.kanade.tachiyomi.data.backup.models
import kotlinx.serialization.Serializable
import kotlinx.serialization.protobuf.ProtoNumber
import tachiyomi.data.SelectAllFeedHasSavedSearch
@Serializable
data class BackupFeed(
@ -16,3 +17,5 @@ val backupFeedMapper =
global = global,
)
}
fun SelectAllFeedHasSavedSearch.backupFeedMapper() = backupFeedMapper(_id, source, saved_search, global)

View file

@ -12,14 +12,20 @@ data class BackupSavedSearch(
@ProtoNumber(2) val query: String = "",
@ProtoNumber(3) val filterList: String = "",
@ProtoNumber(4) val source: Long = 0,
// KMK -->
@ProtoNumber(9) var backupFeeds: List<BackupFeed> = emptyList(),
// KMK <--
)
val backupSavedSearchMapper =
{ _: Long, source: Long, name: String, query: String?, filtersJson: String? ->
{ _: Long, source: Long, name: String, query: String?, filtersJson: String?, backupFeeds: List<BackupFeed> ->
BackupSavedSearch(
source = source,
name = name,
query = query.orEmpty(),
filterList = filtersJson ?: "[]",
// KMK -->
backupFeeds = backupFeeds,
// KMK <--
)
}

View file

@ -8,18 +8,14 @@ import uy.kohesive.injekt.api.get
class FeedRestorer(
private val handler: DatabaseHandler = Injekt.get(),
) {
/**
* Restore global Popular/Latest feeds
*/
suspend fun restoreFeeds(backupFeeds: List<BackupFeed>) {
suspend fun restoreFeeds(backupFeeds: List<BackupFeed>, savedSearchId: Long? = null) {
if (backupFeeds.isEmpty()) return
val currentFeeds = handler.awaitList {
feed_saved_searchQueries.selectAllGlobalNonSavedSearch()
}
handler.await(true) {
val currentFeeds = handler.awaitList {
feed_saved_searchQueries.selectAllGlobalNonSavedSearch()
}
handler.await {
backupFeeds.filter { backupFeed ->
// Filter if source's global Popular/Latest feed already existed
currentFeeds.none {
@ -28,7 +24,7 @@ class FeedRestorer(
}.forEach {
feed_saved_searchQueries.insert(
sourceId = it.source,
savedSearch = null,
savedSearch = savedSearchId,
global = it.global,
)
}

View file

@ -8,25 +8,39 @@ import uy.kohesive.injekt.api.get
class SavedSearchRestorer(
private val handler: DatabaseHandler = Injekt.get(),
// KMK -->
private val feedRestorer: FeedRestorer = FeedRestorer(),
// KMK <--
) {
suspend fun restoreSavedSearches(backupSavedSearches: List<BackupSavedSearch>) {
if (backupSavedSearches.isEmpty()) return
val currentSavedSearches = handler.awaitList {
saved_searchQueries.selectNamesAndSources()
}
// KMK -->
handler.await(true) {
// KMK <--
val currentSavedSearches = handler.awaitList {
saved_searchQueries.selectNamesAndSources()
}
handler.await {
backupSavedSearches.filter { backupSavedSearch ->
currentSavedSearches.none { it.source == backupSavedSearch.source && it.name == backupSavedSearch.name }
}.forEach {
saved_searchQueries.insert(
source = it.source,
name = it.name,
query = it.query.nullIfBlank(),
filtersJson = it.filterList.nullIfBlank()
?.takeUnless { it == "[]" },
)
}.forEach { backupSavedSearch ->
// KMK -->
val savedSearchId = handler.awaitOneExecutable(true) {
// KMK <--
saved_searchQueries.insert(
source = backupSavedSearch.source,
name = backupSavedSearch.name,
query = backupSavedSearch.query.nullIfBlank(),
filtersJson = backupSavedSearch.filterList.nullIfBlank()
?.takeUnless { it == "[]" },
)
// KMK -->
saved_searchQueries.selectLastInsertedRowId()
}
feedRestorer.restoreFeeds(backupSavedSearch.backupFeeds, savedSearchId)
// KMK <--
}
}
}

View file

@ -57,4 +57,7 @@ selectLastInsertedRowId:
SELECT last_insert_rowid();
selectAllGlobalNonSavedSearch:
SELECT * FROM feed_saved_search WHERE global = 1 AND saved_search IS NULL;
SELECT * FROM feed_saved_search WHERE global = 1 AND saved_search IS NULL;
selectAllFeedHasSavedSearch:
SELECT * FROM feed_saved_search WHERE saved_search IS NOT NULL;

View file

@ -10,6 +10,6 @@ data class FeedSavedSearch(
// If null then get latest/popular, if set get the saved search
val savedSearch: Long?,
// If the feed is a global or source specific feed
// If the feed is a global (FeedScreen) or source specific feed (SourceFeedScreen)
val global: Boolean,
)