diff --git a/app/src/main/java/eu/kanade/data/source/FeedSavedSearchRepositoryImpl.kt b/app/src/main/java/eu/kanade/data/source/FeedSavedSearchRepositoryImpl.kt new file mode 100644 index 000000000..d53d4d3b0 --- /dev/null +++ b/app/src/main/java/eu/kanade/data/source/FeedSavedSearchRepositoryImpl.kt @@ -0,0 +1,73 @@ +package eu.kanade.data.source + +import eu.kanade.data.DatabaseHandler +import eu.kanade.data.exh.feedSavedSearchMapper +import eu.kanade.data.exh.savedSearchMapper +import eu.kanade.domain.source.repository.FeedSavedSearchRepository +import exh.savedsearches.models.FeedSavedSearch +import exh.savedsearches.models.SavedSearch +import kotlinx.coroutines.flow.Flow + +class FeedSavedSearchRepositoryImpl( + private val handler: DatabaseHandler, +) : FeedSavedSearchRepository { + + override suspend fun getGlobal(): List { + return handler.awaitList { feed_saved_searchQueries.selectAllGlobal(feedSavedSearchMapper) } + } + + override fun getGlobalAsFlow(): Flow> { + return handler.subscribeToList { feed_saved_searchQueries.selectAllGlobal(feedSavedSearchMapper) } + } + + override suspend fun getGlobalFeedSavedSearch(): List { + return handler.awaitList { feed_saved_searchQueries.selectGlobalFeedSavedSearch(savedSearchMapper) } + } + + override suspend fun countGlobal(): Long { + return handler.awaitOne { feed_saved_searchQueries.countGlobal() } + } + + override suspend fun getBySourceId(sourceId: Long): List { + return handler.awaitList { feed_saved_searchQueries.selectBySource(sourceId, feedSavedSearchMapper) } + } + + override fun getBySourceIdAsFlow(sourceId: Long): Flow> { + return handler.subscribeToList { feed_saved_searchQueries.selectBySource(sourceId, feedSavedSearchMapper) } + } + + override suspend fun getBySourceIdFeedSavedSearch(sourceId: Long): List { + return handler.awaitList { feed_saved_searchQueries.selectSourceFeedSavedSearch(sourceId, savedSearchMapper) } + } + + override suspend fun countBySourceId(sourceId: Long): Long { + return handler.awaitOne { feed_saved_searchQueries.countSourceFeedSavedSearch(sourceId) } + } + + override suspend fun delete(feedSavedSearchId: Long) { + handler.await { feed_saved_searchQueries.deleteById(feedSavedSearchId) } + } + + override suspend fun insert(feedSavedSearch: FeedSavedSearch): Long { + return handler.awaitOne(true) { + feed_saved_searchQueries.insert( + feedSavedSearch.source, + feedSavedSearch.savedSearch, + feedSavedSearch.global, + ) + feed_saved_searchQueries.selectLastInsertedRowId() + } + } + + override suspend fun insertAll(feedSavedSearch: List) { + return handler.await(true) { + feedSavedSearch.forEach { + feed_saved_searchQueries.insert( + it.source, + it.savedSearch, + it.global, + ) + } + } + } +} diff --git a/app/src/main/java/eu/kanade/data/source/SavedSearchRepositoryImpl.kt b/app/src/main/java/eu/kanade/data/source/SavedSearchRepositoryImpl.kt new file mode 100644 index 000000000..92cadc5c4 --- /dev/null +++ b/app/src/main/java/eu/kanade/data/source/SavedSearchRepositoryImpl.kt @@ -0,0 +1,53 @@ +package eu.kanade.data.source + +import eu.kanade.data.DatabaseHandler +import eu.kanade.data.exh.savedSearchMapper +import eu.kanade.domain.source.repository.SavedSearchRepository +import exh.savedsearches.models.SavedSearch +import kotlinx.coroutines.flow.Flow + +class SavedSearchRepositoryImpl( + private val handler: DatabaseHandler, +) : SavedSearchRepository { + + override suspend fun getById(savedSearchId: Long): SavedSearch? { + return handler.awaitOneOrNull { saved_searchQueries.selectById(savedSearchId, savedSearchMapper) } + } + + override suspend fun getBySourceId(sourceId: Long): List { + return handler.awaitList { saved_searchQueries.selectBySource(sourceId, savedSearchMapper) } + } + + override fun getBySourceIdAsFlow(sourceId: Long): Flow> { + return handler.subscribeToList { saved_searchQueries.selectBySource(sourceId, savedSearchMapper) } + } + + override suspend fun delete(savedSearchId: Long) { + handler.await { saved_searchQueries.deleteById(savedSearchId) } + } + + override suspend fun insert(savedSearch: SavedSearch): Long { + return handler.awaitOne(true) { + saved_searchQueries.insert( + savedSearch.source, + savedSearch.name, + savedSearch.query, + savedSearch.filtersJson, + ) + saved_searchQueries.selectLastInsertedRowId() + } + } + + override suspend fun insertAll(savedSearch: List) { + handler.await(true) { + savedSearch.forEach { + saved_searchQueries.insert( + it.source, + it.name, + it.query, + it.filtersJson, + ) + } + } + } +} diff --git a/app/src/main/java/eu/kanade/domain/SYDomainModule.kt b/app/src/main/java/eu/kanade/domain/SYDomainModule.kt index b7e3ed377..0bf9b4f05 100644 --- a/app/src/main/java/eu/kanade/domain/SYDomainModule.kt +++ b/app/src/main/java/eu/kanade/domain/SYDomainModule.kt @@ -3,6 +3,8 @@ package eu.kanade.domain import eu.kanade.data.manga.FavoritesEntryRepositoryImpl import eu.kanade.data.manga.MangaMergeRepositoryImpl import eu.kanade.data.manga.MangaMetadataRepositoryImpl +import eu.kanade.data.source.FeedSavedSearchRepositoryImpl +import eu.kanade.data.source.SavedSearchRepositoryImpl import eu.kanade.domain.chapter.interactor.DeleteChapters import eu.kanade.domain.chapter.interactor.GetMergedChapterByMangaId import eu.kanade.domain.manga.interactor.DeleteByMergeId @@ -19,6 +21,7 @@ import eu.kanade.domain.manga.interactor.GetMergedManga import eu.kanade.domain.manga.interactor.GetMergedMangaById import eu.kanade.domain.manga.interactor.GetMergedMangaForDownloading import eu.kanade.domain.manga.interactor.GetMergedReferencesById +import eu.kanade.domain.manga.interactor.GetSearchMetadata import eu.kanade.domain.manga.interactor.GetSearchTags import eu.kanade.domain.manga.interactor.GetSearchTitles import eu.kanade.domain.manga.interactor.InsertFavoriteEntries @@ -29,16 +32,32 @@ import eu.kanade.domain.manga.interactor.UpdateMergedSettings import eu.kanade.domain.manga.repository.FavoritesEntryRepository import eu.kanade.domain.manga.repository.MangaMergeRepository import eu.kanade.domain.manga.repository.MangaMetadataRepository +import eu.kanade.domain.source.interactor.CountFeedSavedSearchBySourceId +import eu.kanade.domain.source.interactor.CountFeedSavedSearchGlobal +import eu.kanade.domain.source.interactor.DeleteFeedSavedSearchById +import eu.kanade.domain.source.interactor.DeleteSavedSearchById +import eu.kanade.domain.source.interactor.GetExhSavedSearch +import eu.kanade.domain.source.interactor.GetFeedSavedSearchBySourceId +import eu.kanade.domain.source.interactor.GetFeedSavedSearchGlobal +import eu.kanade.domain.source.interactor.GetSavedSearchById +import eu.kanade.domain.source.interactor.GetSavedSearchBySourceId +import eu.kanade.domain.source.interactor.GetSavedSearchBySourceIdFeed +import eu.kanade.domain.source.interactor.GetSavedSearchGlobalFeed import eu.kanade.domain.source.interactor.GetShowLatest import eu.kanade.domain.source.interactor.GetSourceCategories +import eu.kanade.domain.source.interactor.InsertFeedSavedSearch +import eu.kanade.domain.source.interactor.InsertSavedSearch import eu.kanade.domain.source.interactor.SetSourceCategories import eu.kanade.domain.source.interactor.ToggleExcludeFromDataSaver import eu.kanade.domain.source.interactor.ToggleSources +import eu.kanade.domain.source.repository.FeedSavedSearchRepository +import eu.kanade.domain.source.repository.SavedSearchRepository import uy.kohesive.injekt.api.InjektModule import uy.kohesive.injekt.api.InjektRegistrar import uy.kohesive.injekt.api.addFactory import uy.kohesive.injekt.api.addSingletonFactory import uy.kohesive.injekt.api.get +import xyz.nulldev.ts.api.http.serializer.FilterSerializer class SYDomainModule : InjektModule { @@ -53,11 +72,13 @@ class SYDomainModule : InjektModule { addFactory { GetMangaBySource(get()) } addFactory { DeleteChapters(get()) } addFactory { DeleteMangaById(get()) } + addFactory { FilterSerializer() } addSingletonFactory { MangaMetadataRepositoryImpl(get()) } addFactory { GetFlatMetadataById(get()) } addFactory { InsertFlatMetadata(get()) } addFactory { GetExhFavoriteMangaWithMetadata(get()) } + addFactory { GetSearchMetadata(get()) } addFactory { GetSearchTags(get()) } addFactory { GetSearchTitles(get()) } addFactory { GetIdsOfFavoriteMangaWithMetadata(get()) } @@ -77,5 +98,22 @@ class SYDomainModule : InjektModule { addFactory { GetFavoriteEntries(get()) } addFactory { InsertFavoriteEntries(get()) } addFactory { DeleteFavoriteEntries(get()) } + + addSingletonFactory { SavedSearchRepositoryImpl(get()) } + addFactory { GetSavedSearchById(get()) } + addFactory { GetSavedSearchBySourceId(get()) } + addFactory { DeleteSavedSearchById(get()) } + addFactory { InsertSavedSearch(get()) } + addFactory { GetExhSavedSearch(get(), get(), get()) } + + addSingletonFactory { FeedSavedSearchRepositoryImpl(get()) } + addFactory { InsertFeedSavedSearch(get()) } + addFactory { DeleteFeedSavedSearchById(get()) } + addFactory { GetFeedSavedSearchGlobal(get()) } + addFactory { GetFeedSavedSearchBySourceId(get()) } + addFactory { CountFeedSavedSearchGlobal(get()) } + addFactory { CountFeedSavedSearchBySourceId(get()) } + addFactory { GetSavedSearchGlobalFeed(get()) } + addFactory { GetSavedSearchBySourceIdFeed(get()) } } } diff --git a/app/src/main/java/eu/kanade/domain/source/interactor/CountFeedSavedSearchBySourceId.kt b/app/src/main/java/eu/kanade/domain/source/interactor/CountFeedSavedSearchBySourceId.kt new file mode 100644 index 000000000..a5f9a6e9d --- /dev/null +++ b/app/src/main/java/eu/kanade/domain/source/interactor/CountFeedSavedSearchBySourceId.kt @@ -0,0 +1,12 @@ +package eu.kanade.domain.source.interactor + +import eu.kanade.domain.source.repository.FeedSavedSearchRepository + +class CountFeedSavedSearchBySourceId( + private val feedSavedSearchRepository: FeedSavedSearchRepository, +) { + + suspend fun await(sourceId: Long): Long { + return feedSavedSearchRepository.countBySourceId(sourceId) + } +} diff --git a/app/src/main/java/eu/kanade/domain/source/interactor/CountFeedSavedSearchGlobal.kt b/app/src/main/java/eu/kanade/domain/source/interactor/CountFeedSavedSearchGlobal.kt new file mode 100644 index 000000000..2fbe775d2 --- /dev/null +++ b/app/src/main/java/eu/kanade/domain/source/interactor/CountFeedSavedSearchGlobal.kt @@ -0,0 +1,12 @@ +package eu.kanade.domain.source.interactor + +import eu.kanade.domain.source.repository.FeedSavedSearchRepository + +class CountFeedSavedSearchGlobal( + private val feedSavedSearchRepository: FeedSavedSearchRepository, +) { + + suspend fun await(): Long { + return feedSavedSearchRepository.countGlobal() + } +} diff --git a/app/src/main/java/eu/kanade/domain/source/interactor/DeleteFeedSavedSearchById.kt b/app/src/main/java/eu/kanade/domain/source/interactor/DeleteFeedSavedSearchById.kt new file mode 100644 index 000000000..c94786101 --- /dev/null +++ b/app/src/main/java/eu/kanade/domain/source/interactor/DeleteFeedSavedSearchById.kt @@ -0,0 +1,12 @@ +package eu.kanade.domain.source.interactor + +import eu.kanade.domain.source.repository.FeedSavedSearchRepository + +class DeleteFeedSavedSearchById( + private val feedSavedSearchRepository: FeedSavedSearchRepository, +) { + + suspend fun await(feedSavedSearchId: Long) { + return feedSavedSearchRepository.delete(feedSavedSearchId) + } +} diff --git a/app/src/main/java/eu/kanade/domain/source/interactor/DeleteSavedSearchById.kt b/app/src/main/java/eu/kanade/domain/source/interactor/DeleteSavedSearchById.kt new file mode 100644 index 000000000..2b866e35a --- /dev/null +++ b/app/src/main/java/eu/kanade/domain/source/interactor/DeleteSavedSearchById.kt @@ -0,0 +1,12 @@ +package eu.kanade.domain.source.interactor + +import eu.kanade.domain.source.repository.SavedSearchRepository + +class DeleteSavedSearchById( + private val savedSearchRepository: SavedSearchRepository, +) { + + suspend fun await(savedSearchId: Long) { + return savedSearchRepository.delete(savedSearchId) + } +} diff --git a/app/src/main/java/eu/kanade/domain/source/interactor/GetExhSavedSearch.kt b/app/src/main/java/eu/kanade/domain/source/interactor/GetExhSavedSearch.kt new file mode 100644 index 000000000..47e665a4b --- /dev/null +++ b/app/src/main/java/eu/kanade/domain/source/interactor/GetExhSavedSearch.kt @@ -0,0 +1,70 @@ +package eu.kanade.domain.source.interactor + +import eu.kanade.tachiyomi.source.model.FilterList +import eu.kanade.tachiyomi.util.lang.withIOContext +import exh.log.xLogE +import exh.savedsearches.EXHSavedSearch +import exh.savedsearches.models.SavedSearch +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.flowOn +import kotlinx.coroutines.flow.map +import kotlinx.serialization.decodeFromString +import kotlinx.serialization.json.Json +import kotlinx.serialization.json.JsonArray +import xyz.nulldev.ts.api.http.serializer.FilterSerializer + +class GetExhSavedSearch( + private val getSavedSearchById: GetSavedSearchById, + private val getSavedSearchBySourceId: GetSavedSearchBySourceId, + private val filterSerializer: FilterSerializer, +) { + + suspend fun awaitOne(savedSearchId: Long, getFilterList: () -> FilterList): EXHSavedSearch? { + val search = getSavedSearchById.awaitOrNull(savedSearchId) ?: return null + return withIOContext { loadSearch(search, getFilterList) } + } + + suspend fun await(sourceId: Long, getFilterList: () -> FilterList): List { + return withIOContext { loadSearches(getSavedSearchBySourceId.await(sourceId), getFilterList) } + } + + fun subscribe(sourceId: Long, getFilterList: () -> FilterList): Flow> { + return getSavedSearchBySourceId.subscribe(sourceId) + .map { loadSearches(it, getFilterList) } + .flowOn(Dispatchers.IO) + } + + private fun loadSearches(searches: List, getFilterList: () -> FilterList): List { + return searches.map { loadSearch(it, getFilterList) } + } + + private fun loadSearch(search: SavedSearch, getFilterList: () -> FilterList): EXHSavedSearch { + val filters = getFilters(search.filtersJson) + + return EXHSavedSearch( + id = search.id, + name = search.name, + query = search.query.orEmpty(), + filterList = filters?.let { deserializeFilters(it, getFilterList) }, + ) + } + + private fun getFilters(filtersJson: String?): JsonArray? { + return runCatching { + filtersJson?.let { Json.decodeFromString(it) } + }.onFailure { + xLogE("Failed to load saved search!", it) + }.getOrNull() + } + + private fun deserializeFilters(filters: JsonArray, getFilterList: () -> FilterList): FilterList? { + return runCatching { + val originalFilters = getFilterList() + filterSerializer.deserialize(originalFilters, filters) + originalFilters + }.onFailure { + xLogE("Failed to load saved search!", it) + }.getOrNull() + } +} diff --git a/app/src/main/java/eu/kanade/domain/source/interactor/GetFeedSavedSearchBySourceId.kt b/app/src/main/java/eu/kanade/domain/source/interactor/GetFeedSavedSearchBySourceId.kt new file mode 100644 index 000000000..ceaa79ad4 --- /dev/null +++ b/app/src/main/java/eu/kanade/domain/source/interactor/GetFeedSavedSearchBySourceId.kt @@ -0,0 +1,18 @@ +package eu.kanade.domain.source.interactor + +import eu.kanade.domain.source.repository.FeedSavedSearchRepository +import exh.savedsearches.models.FeedSavedSearch +import kotlinx.coroutines.flow.Flow + +class GetFeedSavedSearchBySourceId( + private val feedSavedSearchRepository: FeedSavedSearchRepository, +) { + + suspend fun await(sourceId: Long): List { + return feedSavedSearchRepository.getBySourceId(sourceId) + } + + fun subscribe(sourceId: Long): Flow> { + return feedSavedSearchRepository.getBySourceIdAsFlow(sourceId) + } +} diff --git a/app/src/main/java/eu/kanade/domain/source/interactor/GetFeedSavedSearchGlobal.kt b/app/src/main/java/eu/kanade/domain/source/interactor/GetFeedSavedSearchGlobal.kt new file mode 100644 index 000000000..05dbd982a --- /dev/null +++ b/app/src/main/java/eu/kanade/domain/source/interactor/GetFeedSavedSearchGlobal.kt @@ -0,0 +1,18 @@ +package eu.kanade.domain.source.interactor + +import eu.kanade.domain.source.repository.FeedSavedSearchRepository +import exh.savedsearches.models.FeedSavedSearch +import kotlinx.coroutines.flow.Flow + +class GetFeedSavedSearchGlobal( + private val feedSavedSearchRepository: FeedSavedSearchRepository, +) { + + suspend fun await(): List { + return feedSavedSearchRepository.getGlobal() + } + + fun subscribe(): Flow> { + return feedSavedSearchRepository.getGlobalAsFlow() + } +} diff --git a/app/src/main/java/eu/kanade/domain/source/interactor/GetSavedSearchById.kt b/app/src/main/java/eu/kanade/domain/source/interactor/GetSavedSearchById.kt new file mode 100644 index 000000000..1053874e0 --- /dev/null +++ b/app/src/main/java/eu/kanade/domain/source/interactor/GetSavedSearchById.kt @@ -0,0 +1,17 @@ +package eu.kanade.domain.source.interactor + +import eu.kanade.domain.source.repository.SavedSearchRepository +import exh.savedsearches.models.SavedSearch + +class GetSavedSearchById( + private val savedSearchRepository: SavedSearchRepository, +) { + + suspend fun await(savedSearchId: Long): SavedSearch { + return savedSearchRepository.getById(savedSearchId)!! + } + + suspend fun awaitOrNull(savedSearchId: Long): SavedSearch? { + return savedSearchRepository.getById(savedSearchId) + } +} diff --git a/app/src/main/java/eu/kanade/domain/source/interactor/GetSavedSearchBySourceId.kt b/app/src/main/java/eu/kanade/domain/source/interactor/GetSavedSearchBySourceId.kt new file mode 100644 index 000000000..d3068785f --- /dev/null +++ b/app/src/main/java/eu/kanade/domain/source/interactor/GetSavedSearchBySourceId.kt @@ -0,0 +1,18 @@ +package eu.kanade.domain.source.interactor + +import eu.kanade.domain.source.repository.SavedSearchRepository +import exh.savedsearches.models.SavedSearch +import kotlinx.coroutines.flow.Flow + +class GetSavedSearchBySourceId( + private val savedSearchRepository: SavedSearchRepository, +) { + + suspend fun await(sourceId: Long): List { + return savedSearchRepository.getBySourceId(sourceId) + } + + fun subscribe(sourceId: Long): Flow> { + return savedSearchRepository.getBySourceIdAsFlow(sourceId) + } +} diff --git a/app/src/main/java/eu/kanade/domain/source/interactor/GetSavedSearchBySourceIdFeed.kt b/app/src/main/java/eu/kanade/domain/source/interactor/GetSavedSearchBySourceIdFeed.kt new file mode 100644 index 000000000..bf4409d18 --- /dev/null +++ b/app/src/main/java/eu/kanade/domain/source/interactor/GetSavedSearchBySourceIdFeed.kt @@ -0,0 +1,13 @@ +package eu.kanade.domain.source.interactor + +import eu.kanade.domain.source.repository.FeedSavedSearchRepository +import exh.savedsearches.models.SavedSearch + +class GetSavedSearchBySourceIdFeed( + private val feedSavedSearchRepository: FeedSavedSearchRepository, +) { + + suspend fun await(sourceId: Long): List { + return feedSavedSearchRepository.getBySourceIdFeedSavedSearch(sourceId) + } +} diff --git a/app/src/main/java/eu/kanade/domain/source/interactor/GetSavedSearchGlobalFeed.kt b/app/src/main/java/eu/kanade/domain/source/interactor/GetSavedSearchGlobalFeed.kt new file mode 100644 index 000000000..d2c3d99d6 --- /dev/null +++ b/app/src/main/java/eu/kanade/domain/source/interactor/GetSavedSearchGlobalFeed.kt @@ -0,0 +1,13 @@ +package eu.kanade.domain.source.interactor + +import eu.kanade.domain.source.repository.FeedSavedSearchRepository +import exh.savedsearches.models.SavedSearch + +class GetSavedSearchGlobalFeed( + private val feedSavedSearchRepository: FeedSavedSearchRepository, +) { + + suspend fun await(): List { + return feedSavedSearchRepository.getGlobalFeedSavedSearch() + } +} diff --git a/app/src/main/java/eu/kanade/domain/source/interactor/InsertFeedSavedSearch.kt b/app/src/main/java/eu/kanade/domain/source/interactor/InsertFeedSavedSearch.kt new file mode 100644 index 000000000..d0973f69a --- /dev/null +++ b/app/src/main/java/eu/kanade/domain/source/interactor/InsertFeedSavedSearch.kt @@ -0,0 +1,29 @@ +package eu.kanade.domain.source.interactor + +import eu.kanade.domain.source.repository.FeedSavedSearchRepository +import eu.kanade.tachiyomi.util.system.logcat +import exh.savedsearches.models.FeedSavedSearch +import logcat.LogPriority +import logcat.asLog + +class InsertFeedSavedSearch( + private val feedSavedSearchRepository: FeedSavedSearchRepository, +) { + + suspend fun await(feedSavedSearch: FeedSavedSearch): Long? { + return try { + feedSavedSearchRepository.insert(feedSavedSearch) + } catch (e: Exception) { + logcat(LogPriority.ERROR) { e.asLog() } + null + } + } + + suspend fun awaitAll(feedSavedSearch: List) { + try { + feedSavedSearchRepository.insertAll(feedSavedSearch) + } catch (e: Exception) { + logcat(LogPriority.ERROR) { e.asLog() } + } + } +} diff --git a/app/src/main/java/eu/kanade/domain/source/interactor/InsertSavedSearch.kt b/app/src/main/java/eu/kanade/domain/source/interactor/InsertSavedSearch.kt new file mode 100644 index 000000000..0dbeeb390 --- /dev/null +++ b/app/src/main/java/eu/kanade/domain/source/interactor/InsertSavedSearch.kt @@ -0,0 +1,29 @@ +package eu.kanade.domain.source.interactor + +import eu.kanade.domain.source.repository.SavedSearchRepository +import eu.kanade.tachiyomi.util.system.logcat +import exh.savedsearches.models.SavedSearch +import logcat.LogPriority +import logcat.asLog + +class InsertSavedSearch( + private val savedSearchRepository: SavedSearchRepository, +) { + + suspend fun await(savedSearch: SavedSearch): Long? { + return try { + savedSearchRepository.insert(savedSearch) + } catch (e: Exception) { + logcat(LogPriority.ERROR) { e.asLog() } + null + } + } + + suspend fun awaitAll(savedSearch: List) { + try { + savedSearchRepository.insertAll(savedSearch) + } catch (e: Exception) { + logcat(LogPriority.ERROR) { e.asLog() } + } + } +} diff --git a/app/src/main/java/eu/kanade/domain/source/repository/FeedSavedSearchRepository.kt b/app/src/main/java/eu/kanade/domain/source/repository/FeedSavedSearchRepository.kt new file mode 100644 index 000000000..20fad43e5 --- /dev/null +++ b/app/src/main/java/eu/kanade/domain/source/repository/FeedSavedSearchRepository.kt @@ -0,0 +1,30 @@ +package eu.kanade.domain.source.repository + +import exh.savedsearches.models.FeedSavedSearch +import exh.savedsearches.models.SavedSearch +import kotlinx.coroutines.flow.Flow + +interface FeedSavedSearchRepository { + + suspend fun getGlobal(): List + + fun getGlobalAsFlow(): Flow> + + suspend fun getGlobalFeedSavedSearch(): List + + suspend fun countGlobal(): Long + + suspend fun getBySourceId(sourceId: Long): List + + fun getBySourceIdAsFlow(sourceId: Long): Flow> + + suspend fun getBySourceIdFeedSavedSearch(sourceId: Long): List + + suspend fun countBySourceId(sourceId: Long): Long + + suspend fun delete(feedSavedSearchId: Long) + + suspend fun insert(feedSavedSearch: FeedSavedSearch): Long? + + suspend fun insertAll(feedSavedSearch: List) +} diff --git a/app/src/main/java/eu/kanade/domain/source/repository/SavedSearchRepository.kt b/app/src/main/java/eu/kanade/domain/source/repository/SavedSearchRepository.kt new file mode 100644 index 000000000..621b1b4ce --- /dev/null +++ b/app/src/main/java/eu/kanade/domain/source/repository/SavedSearchRepository.kt @@ -0,0 +1,19 @@ +package eu.kanade.domain.source.repository + +import exh.savedsearches.models.SavedSearch +import kotlinx.coroutines.flow.Flow + +interface SavedSearchRepository { + + suspend fun getById(savedSearchId: Long): SavedSearch? + + suspend fun getBySourceId(sourceId: Long): List + + fun getBySourceIdAsFlow(sourceId: Long): Flow> + + suspend fun delete(savedSearchId: Long) + + suspend fun insert(savedSearch: SavedSearch): Long? + + suspend fun insertAll(savedSearch: List) +} diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/backup/full/FullBackupManager.kt b/app/src/main/java/eu/kanade/tachiyomi/data/backup/full/FullBackupManager.kt index 88c932fb0..f7ee085db 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/backup/full/FullBackupManager.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/backup/full/FullBackupManager.kt @@ -500,12 +500,11 @@ class FullBackupManager(context: Context) : AbstractBackupManager(context) { backupSavedSearches.filter { backupSavedSearch -> currentSavedSearches.none { it.source == backupSavedSearch.source && it.name == backupSavedSearch.name } }.forEach { - saved_searchQueries.insertSavedSearch( - _id = null, + saved_searchQueries.insert( source = it.source, name = it.name, query = it.query.nullIfBlank(), - filters_json = it.filterList.nullIfBlank() + filtersJson = it.filterList.nullIfBlank() ?.takeUnless { it == "[]" }, ) } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/browse/feed/FeedController.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/browse/feed/FeedController.kt index a89159cdb..b6b7a0664 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/browse/feed/FeedController.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/browse/feed/FeedController.kt @@ -91,7 +91,7 @@ open class FeedController : private fun addFeedSearch(source: CatalogueSource) { viewScope.launchUI { - val items = presenter.getSourceSavedSearches(source) + val items = presenter.getSourceSavedSearches(source.id) val itemsStrings = listOf(activity!!.getString(R.string.latest)) + items.map { it.name } var selectedIndex = 0 diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/browse/feed/FeedItem.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/browse/feed/FeedItem.kt index 9a1c46c25..f0422ddbb 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/browse/feed/FeedItem.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/browse/feed/FeedItem.kt @@ -73,6 +73,6 @@ class FeedItem( * @return hashcode */ override fun hashCode(): Int { - return feed.id!!.toInt() + return feed.id.toInt() } } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/browse/feed/FeedPresenter.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/browse/feed/FeedPresenter.kt index 0b620bedb..4ad32d518 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/browse/feed/FeedPresenter.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/browse/feed/FeedPresenter.kt @@ -1,14 +1,17 @@ package eu.kanade.tachiyomi.ui.browse.feed import android.os.Bundle -import eu.kanade.data.DatabaseHandler -import eu.kanade.data.exh.feedSavedSearchMapper -import eu.kanade.data.exh.savedSearchMapper import eu.kanade.domain.manga.interactor.GetManga import eu.kanade.domain.manga.interactor.InsertManga import eu.kanade.domain.manga.interactor.UpdateManga import eu.kanade.domain.manga.model.toDbManga import eu.kanade.domain.manga.model.toMangaUpdate +import eu.kanade.domain.source.interactor.CountFeedSavedSearchGlobal +import eu.kanade.domain.source.interactor.DeleteFeedSavedSearchById +import eu.kanade.domain.source.interactor.GetFeedSavedSearchGlobal +import eu.kanade.domain.source.interactor.GetSavedSearchBySourceId +import eu.kanade.domain.source.interactor.GetSavedSearchGlobalFeed +import eu.kanade.domain.source.interactor.InsertFeedSavedSearch import eu.kanade.tachiyomi.data.database.models.Manga import eu.kanade.tachiyomi.data.database.models.toDomainManga import eu.kanade.tachiyomi.data.database.models.toMangaInfo @@ -46,16 +49,20 @@ import xyz.nulldev.ts.api.http.serializer.FilterSerializer * Function calls should be done from here. UI calls should be done from the controller. * * @param sourceManager manages the different sources. - * @param handler manages the database calls. * @param preferences manages the preference calls. */ open class FeedPresenter( val sourceManager: SourceManager = Injekt.get(), - val handler: DatabaseHandler = Injekt.get(), val preferences: PreferencesHelper = Injekt.get(), private val getManga: GetManga = Injekt.get(), private val insertManga: InsertManga = Injekt.get(), private val updateManga: UpdateManga = Injekt.get(), + private val getFeedSavedSearchGlobal: GetFeedSavedSearchGlobal = Injekt.get(), + private val getSavedSearchGlobalFeed: GetSavedSearchGlobalFeed = Injekt.get(), + private val countFeedSavedSearchGlobal: CountFeedSavedSearchGlobal = Injekt.get(), + private val getSavedSearchBySourceId: GetSavedSearchBySourceId = Injekt.get(), + private val insertFeedSavedSearch: InsertFeedSavedSearch = Injekt.get(), + private val deleteFeedSavedSearchById: DeleteFeedSavedSearchById = Injekt.get(), ) : BasePresenter() { /** @@ -76,9 +83,9 @@ open class FeedPresenter( override fun onCreate(savedState: Bundle?) { super.onCreate(savedState) - handler.subscribeToList { feed_saved_searchQueries.selectAllGlobal() } + getFeedSavedSearchGlobal.subscribe() .onEach { - getFeed() + getFeed(it) } .launchIn(presenterScope) } @@ -90,7 +97,7 @@ open class FeedPresenter( } suspend fun hasTooManyFeeds(): Boolean { - return handler.awaitOne { feed_saved_searchQueries.countGlobal() } > 10 + return countFeedSavedSearchGlobal.await() > 10 } fun getEnabledSources(): List { @@ -104,36 +111,33 @@ open class FeedPresenter( return list.sortedBy { it.id.toString() !in pinnedSources } } - suspend fun getSourceSavedSearches(source: CatalogueSource): List { - return handler.awaitList { saved_searchQueries.selectBySource(source.id, savedSearchMapper) } + suspend fun getSourceSavedSearches(sourceId: Long): List { + return getSavedSearchBySourceId.await(sourceId) } fun createFeed(source: CatalogueSource, savedSearch: SavedSearch?) { launchIO { - handler.await { - feed_saved_searchQueries.insertFeedSavedSearch( - _id = null, + insertFeedSavedSearch.await( + FeedSavedSearch( + id = -1, source = source.id, - saved_search = savedSearch?.id, + savedSearch = savedSearch?.id, global = true, - ) - } + ), + ) } } fun deleteFeed(feed: FeedSavedSearch) { launchIO { - handler.await { - feed_saved_searchQueries.deleteById(feed.id ?: return@await) - } + deleteFeedSavedSearchById.await(feed.id) } } - private suspend fun getSourcesToGetFeed(): List> { - val savedSearches = handler.awaitList { - feed_saved_searchQueries.selectGlobalFeedSavedSearch(savedSearchMapper) - }.associateBy { it.id } - return handler.awaitList { feed_saved_searchQueries.selectAllGlobal(feedSavedSearchMapper) } + private suspend fun getSourcesToGetFeed(feedSavedSearch: List): List> { + val savedSearches = getSavedSearchGlobalFeed.await() + .associateBy { it.id } + return feedSavedSearch .map { it to savedSearches[it.savedSearch] } } @@ -152,12 +156,12 @@ open class FeedPresenter( /** * Initiates get manga per feed. */ - suspend fun getFeed() { + private suspend fun getFeed(feedSavedSearch: List) { // Create image fetch subscription initializeFetchImageSubscription() // Create items with the initial state - val initialItems = getSourcesToGetFeed().map { (feed, savedSearch) -> + val initialItems = getSourcesToGetFeed(feedSavedSearch).map { (feed, savedSearch) -> createCatalogueSearchItem( feed, savedSearch, @@ -168,7 +172,7 @@ open class FeedPresenter( var items = initialItems fetchSourcesSubscription?.unsubscribe() - fetchSourcesSubscription = Observable.from(getSourcesToGetFeed()) + fetchSourcesSubscription = Observable.from(getSourcesToGetFeed(feedSavedSearch)) .flatMap( { (feed, savedSearch) -> val source = sourceManager.get(feed.source) as? CatalogueSource diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/browse/BrowseSourcePresenter.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/browse/BrowseSourcePresenter.kt index 66c027d08..fe18b1e95 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/browse/BrowseSourcePresenter.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/browse/BrowseSourcePresenter.kt @@ -2,8 +2,6 @@ package eu.kanade.tachiyomi.ui.browse.source.browse import android.os.Bundle import eu.davidea.flexibleadapter.items.IFlexible -import eu.kanade.data.DatabaseHandler -import eu.kanade.data.exh.savedSearchMapper import eu.kanade.domain.category.interactor.GetCategories import eu.kanade.domain.category.interactor.SetMangaCategories import eu.kanade.domain.chapter.interactor.GetChapterByMangaId @@ -14,6 +12,9 @@ import eu.kanade.domain.manga.interactor.InsertManga import eu.kanade.domain.manga.interactor.UpdateManga import eu.kanade.domain.manga.model.toDbManga import eu.kanade.domain.manga.model.toMangaUpdate +import eu.kanade.domain.source.interactor.DeleteSavedSearchById +import eu.kanade.domain.source.interactor.GetExhSavedSearch +import eu.kanade.domain.source.interactor.InsertSavedSearch import eu.kanade.domain.track.interactor.InsertTrack import eu.kanade.domain.track.model.toDomainTrack import eu.kanade.tachiyomi.data.cache.CoverCache @@ -48,12 +49,9 @@ import eu.kanade.tachiyomi.ui.browse.source.filter.TriStateItem import eu.kanade.tachiyomi.ui.browse.source.filter.TriStateSectionItem import eu.kanade.tachiyomi.util.chapter.ChapterSettingsHelper import eu.kanade.tachiyomi.util.lang.launchIO -import eu.kanade.tachiyomi.util.lang.withIOContext import eu.kanade.tachiyomi.util.lang.withUIContext import eu.kanade.tachiyomi.util.removeCovers import eu.kanade.tachiyomi.util.system.logcat -import exh.log.xLogE -import exh.savedsearches.EXHSavedSearch import exh.savedsearches.models.SavedSearch import exh.source.isEhBasedSource import exh.util.nullIfBlank @@ -90,7 +88,6 @@ open class BrowseSourcePresenter( private val savedSearch: Long? = null, // SY <-- private val sourceManager: SourceManager = Injekt.get(), - private val database: DatabaseHandler = Injekt.get(), private val prefs: PreferencesHelper = Injekt.get(), private val coverCache: CoverCache = Injekt.get(), private val getManga: GetManga = Injekt.get(), @@ -102,6 +99,11 @@ open class BrowseSourcePresenter( private val updateManga: UpdateManga = Injekt.get(), private val insertTrack: InsertTrack = Injekt.get(), private val syncChaptersWithTrackServiceTwoWay: SyncChaptersWithTrackServiceTwoWay = Injekt.get(), + // SY --> + private val deleteSavedSearchById: DeleteSavedSearchById = Injekt.get(), + private val insertSavedSearch: InsertSavedSearch = Injekt.get(), + private val getExhSavedSearch: GetExhSavedSearch = Injekt.get(), + // SY <-- ) : BasePresenter() { /** @@ -161,18 +163,12 @@ open class BrowseSourcePresenter( val savedSearchFilters = savedSearch val jsonFilters = filters if (savedSearchFilters != null) { - runCatching { - val savedSearch = runBlocking { - database.awaitOneOrNull { - saved_searchQueries.selectById(savedSearchFilters, savedSearchMapper) - } - } ?: return@runCatching - query = savedSearch.query.orEmpty() - val filtersJson = savedSearch.filtersJson - ?: return@runCatching - val filters = Json.decodeFromString(filtersJson) - filterSerializer.deserialize(sourceFilters, filters) - appliedFilters = sourceFilters + val savedSearch = runBlocking { getExhSavedSearch.awaitOne(savedSearchFilters) { sourceFilters } } + if (savedSearch != null) { + query = savedSearch.query + if (savedSearch.filterList != null) { + appliedFilters = savedSearch.filterList + } } } else if (jsonFilters != null) { runCatching { @@ -182,8 +178,7 @@ open class BrowseSourcePresenter( } } - database.subscribeToList { saved_searchQueries.selectBySource(source.id, savedSearchMapper) } - .map { loadSearches(it) } + getExhSavedSearch.subscribe(source.id, source::getFilterList) .onEach { withUIContext { view?.setSavedSearches(it) @@ -529,92 +524,28 @@ open class BrowseSourcePresenter( // EXH --> fun saveSearch(name: String, query: String, filterList: FilterList) { launchIO { - kotlin.runCatching { - database.await { - saved_searchQueries.insertSavedSearch( - _id = null, - source = source.id, - name = name.trim(), - query = query.nullIfBlank(), - filters_json = filterSerializer.serialize(filterList).ifEmpty { null }?.let { Json.encodeToString(it) }, - ) - } - } - } - } - - fun deleteSearch(searchId: Long) { - launchIO { - database.await { saved_searchQueries.deleteById(searchId) } - } - } - - suspend fun loadSearch(searchId: Long): EXHSavedSearch? { - return withIOContext { - val search = database.awaitOneOrNull { - saved_searchQueries.selectById(searchId, savedSearchMapper) - } ?: return@withIOContext null - EXHSavedSearch( - id = search.id!!, - name = search.name, - query = search.query.orEmpty(), - filterList = runCatching { - val originalFilters = source.getFilterList() - filterSerializer.deserialize( - filters = originalFilters, - json = search.filtersJson - ?.let { Json.decodeFromString(it) } - ?: return@runCatching null, - ) - originalFilters - }.getOrNull(), + insertSavedSearch.await( + SavedSearch( + id = -1, + source = source.id, + name = name.trim(), + query = query.nullIfBlank(), + filtersJson = runCatching { filterSerializer.serialize(filterList).ifEmpty { null }?.let { Json.encodeToString(it) } }.getOrNull(), + ), ) } } - suspend fun loadSearches(searches: List? = null): List { - return withIOContext { - (searches ?: (database.awaitList { saved_searchQueries.selectBySource(source.id, savedSearchMapper) })) - .map { - val filtersJson = it.filtersJson ?: return@map EXHSavedSearch( - id = it.id!!, - name = it.name, - query = it.query.orEmpty(), - filterList = null, - ) - val filters = try { - Json.decodeFromString(filtersJson) - } catch (e: Exception) { - xLogE("Failed to load saved search!", e) - null - } ?: return@map EXHSavedSearch( - id = it.id!!, - name = it.name, - query = it.query.orEmpty(), - filterList = null, - ) - - try { - val originalFilters = source.getFilterList() - filterSerializer.deserialize(originalFilters, filters) - EXHSavedSearch( - id = it.id!!, - name = it.name, - query = it.query.orEmpty(), - filterList = originalFilters, - ) - } catch (t: RuntimeException) { - // Load failed - xLogE("Failed to load saved search!", t) - EXHSavedSearch( - id = it.id!!, - name = it.name, - query = it.query.orEmpty(), - filterList = null, - ) - } - } + fun deleteSearch(savedSearchId: Long) { + launchIO { + deleteSavedSearchById.await(savedSearchId) } } + + suspend fun loadSearch(searchId: Long) = + getExhSavedSearch.awaitOne(searchId, source::getFilterList) + + suspend fun loadSearches() = + getExhSavedSearch.await(source.id, source::getFilterList) // EXH <-- } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/feed/SourceFeedPresenter.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/feed/SourceFeedPresenter.kt index 5b76c7e31..81e6a8544 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/feed/SourceFeedPresenter.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/feed/SourceFeedPresenter.kt @@ -2,14 +2,18 @@ package eu.kanade.tachiyomi.ui.browse.source.feed import android.os.Bundle import eu.davidea.flexibleadapter.items.IFlexible -import eu.kanade.data.DatabaseHandler -import eu.kanade.data.exh.feedSavedSearchMapper -import eu.kanade.data.exh.savedSearchMapper import eu.kanade.domain.manga.interactor.GetManga import eu.kanade.domain.manga.interactor.InsertManga import eu.kanade.domain.manga.interactor.UpdateManga import eu.kanade.domain.manga.model.toDbManga import eu.kanade.domain.manga.model.toMangaUpdate +import eu.kanade.domain.source.interactor.CountFeedSavedSearchBySourceId +import eu.kanade.domain.source.interactor.DeleteFeedSavedSearchById +import eu.kanade.domain.source.interactor.GetExhSavedSearch +import eu.kanade.domain.source.interactor.GetFeedSavedSearchBySourceId +import eu.kanade.domain.source.interactor.GetSavedSearchBySourceId +import eu.kanade.domain.source.interactor.GetSavedSearchBySourceIdFeed +import eu.kanade.domain.source.interactor.InsertFeedSavedSearch import eu.kanade.tachiyomi.data.database.models.Manga import eu.kanade.tachiyomi.data.database.models.toDomainManga import eu.kanade.tachiyomi.data.database.models.toMangaInfo @@ -24,19 +28,14 @@ import eu.kanade.tachiyomi.ui.base.presenter.BasePresenter import eu.kanade.tachiyomi.ui.browse.source.browse.BrowseSourcePresenter.Companion.toItems import eu.kanade.tachiyomi.util.lang.launchIO import eu.kanade.tachiyomi.util.lang.runAsObservable -import eu.kanade.tachiyomi.util.lang.withIOContext import eu.kanade.tachiyomi.util.system.logcat -import exh.log.xLogE -import exh.savedsearches.EXHSavedSearch import exh.savedsearches.models.FeedSavedSearch import exh.savedsearches.models.SavedSearch -import kotlinx.coroutines.CancellationException import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.runBlocking import kotlinx.serialization.decodeFromString import kotlinx.serialization.json.Json -import kotlinx.serialization.json.JsonArray import logcat.LogPriority import rx.Observable import rx.Subscription @@ -63,11 +62,17 @@ sealed class SourceFeed { */ open class SourceFeedPresenter( val source: CatalogueSource, - val handler: DatabaseHandler = Injekt.get(), val preferences: PreferencesHelper = Injekt.get(), private val getManga: GetManga = Injekt.get(), private val insertManga: InsertManga = Injekt.get(), private val updateManga: UpdateManga = Injekt.get(), + private val getFeedSavedSearchBySourceId: GetFeedSavedSearchBySourceId = Injekt.get(), + private val getSavedSearchBySourceIdFeed: GetSavedSearchBySourceIdFeed = Injekt.get(), + private val countFeedSavedSearchBySourceId: CountFeedSavedSearchBySourceId = Injekt.get(), + private val getSavedSearchBySourceId: GetSavedSearchBySourceId = Injekt.get(), + private val insertFeedSavedSearch: InsertFeedSavedSearch = Injekt.get(), + private val deleteFeedSavedSearchById: DeleteFeedSavedSearchById = Injekt.get(), + private val getExhSavedSearch: GetExhSavedSearch = Injekt.get(), ) : BasePresenter() { /** @@ -105,9 +110,9 @@ open class SourceFeedPresenter( sourceFilters = source.getFilterList() - handler.subscribeToList { feed_saved_searchQueries.selectSourceFeedSavedSearch(source.id, savedSearchMapper) } + getFeedSavedSearchBySourceId.subscribe(source.id) .onEach { - getFeed() + getFeed(it) } .launchIn(presenterScope) } @@ -119,46 +124,42 @@ open class SourceFeedPresenter( } suspend fun hasTooManyFeeds(): Boolean { - return withIOContext { - handler.awaitList { - feed_saved_searchQueries.selectSourceFeedSavedSearch(source.id) - }.size > 10 - } + return countFeedSavedSearchBySourceId.await(source.id) > 10 } suspend fun getSourceSavedSearches(): List { - return handler.awaitList { saved_searchQueries.selectBySource(source.id, savedSearchMapper) } + return getSavedSearchBySourceId.await(source.id) } fun createFeed(savedSearchId: Long) { launchIO { - handler.await { - feed_saved_searchQueries.insertFeedSavedSearch( - _id = null, + insertFeedSavedSearch.await( + FeedSavedSearch( + id = -1, source = source.id, - saved_search = savedSearchId, + savedSearch = savedSearchId, global = false, - ) - } + ), + ) } } fun deleteFeed(feed: FeedSavedSearch) { launchIO { - handler.await { feed_saved_searchQueries.deleteById(feed.id ?: return@await) } + deleteFeedSavedSearchById.await(feed.id) } } - private suspend fun getSourcesToGetFeed(): List { - val savedSearches = handler.awaitList { feed_saved_searchQueries.selectSourceFeedSavedSearch(source.id, savedSearchMapper) } - .associateBy { it.id!! } + private suspend fun getSourcesToGetFeed(feedSavedSearch: List): List { + val savedSearches = getSavedSearchBySourceIdFeed.await(source.id) + .associateBy { it.id } return listOfNotNull( if (source.supportsLatest) { SourceFeed.Latest } else null, SourceFeed.Browse, - ) + handler.awaitList { feed_saved_searchQueries.selectBySource(source.id, feedSavedSearchMapper) } + ) + feedSavedSearch .map { SourceFeed.SourceSavedSearch(it, savedSearches[it.savedSearch]!!) } } @@ -175,12 +176,12 @@ open class SourceFeedPresenter( /** * Initiates get manga per feed. */ - suspend fun getFeed() { + private suspend fun getFeed(feedSavedSearch: List) { // Create image fetch subscription initializeFetchImageSubscription() // Create items with the initial state - val initialItems = getSourcesToGetFeed().map { + val initialItems = getSourcesToGetFeed(feedSavedSearch).map { createCatalogueSearchItem( it, null, @@ -189,7 +190,7 @@ open class SourceFeedPresenter( var items = initialItems fetchSourcesSubscription?.unsubscribe() - fetchSourcesSubscription = Observable.from(getSourcesToGetFeed()) + fetchSourcesSubscription = Observable.from(getSourcesToGetFeed(feedSavedSearch)) .flatMap( { sourceFeed -> Observable.defer { @@ -323,70 +324,9 @@ open class SourceFeedPresenter( return localManga?.toDbManga()!! } - suspend fun loadSearch(searchId: Long): EXHSavedSearch? { - return withIOContext { - val search = handler.awaitOneOrNull { - saved_searchQueries.selectById(searchId, savedSearchMapper) - } ?: return@withIOContext null - EXHSavedSearch( - id = search.id!!, - name = search.name, - query = search.query.orEmpty(), - filterList = runCatching { - val originalFilters = source.getFilterList() - filterSerializer.deserialize( - filters = originalFilters, - json = search.filtersJson - ?.let { Json.decodeFromString(it) } - ?: return@runCatching null, - ) - originalFilters - }.getOrNull(), - ) - } - } + suspend fun loadSearch(searchId: Long) = + getExhSavedSearch.awaitOne(searchId, source::getFilterList) - suspend fun loadSearches(): List { - return withIOContext { - handler.awaitList { saved_searchQueries.selectBySource(source.id, savedSearchMapper) }.map { - val filtersJson = it.filtersJson ?: return@map EXHSavedSearch( - id = it.id!!, - name = it.name, - query = it.query.orEmpty(), - filterList = null, - ) - val filters = try { - Json.decodeFromString(filtersJson) - } catch (e: Exception) { - if (e is CancellationException) throw e - null - } ?: return@map EXHSavedSearch( - id = it.id!!, - name = it.name, - query = it.query.orEmpty(), - filterList = null, - ) - - try { - val originalFilters = source.getFilterList() - filterSerializer.deserialize(originalFilters, filters) - EXHSavedSearch( - id = it.id!!, - name = it.name, - query = it.query.orEmpty(), - filterList = originalFilters, - ) - } catch (t: RuntimeException) { - // Load failed - xLogE("Failed to load saved search!", t) - EXHSavedSearch( - id = it.id!!, - name = it.name, - query = it.query.orEmpty(), - filterList = null, - ) - } - } - } - } + suspend fun loadSearches() = + getExhSavedSearch.await(source.id, source::getFilterList) } diff --git a/app/src/main/java/exh/EXHMigrations.kt b/app/src/main/java/exh/EXHMigrations.kt index b51ac72f8..1c8108ee4 100644 --- a/app/src/main/java/exh/EXHMigrations.kt +++ b/app/src/main/java/exh/EXHMigrations.kt @@ -14,6 +14,8 @@ import eu.kanade.domain.manga.interactor.GetMangaBySource import eu.kanade.domain.manga.interactor.InsertMergedReference import eu.kanade.domain.manga.interactor.UpdateManga import eu.kanade.domain.manga.model.MangaUpdate +import eu.kanade.domain.source.interactor.InsertFeedSavedSearch +import eu.kanade.domain.source.interactor.InsertSavedSearch import eu.kanade.tachiyomi.BuildConfig import eu.kanade.tachiyomi.data.backup.BackupCreatorJob import eu.kanade.tachiyomi.data.database.models.Manga @@ -40,6 +42,8 @@ import eu.kanade.tachiyomi.util.system.DeviceUtil import exh.eh.EHentaiUpdateWorker import exh.log.xLogE import exh.merged.sql.models.MergedMangaReference +import exh.savedsearches.models.FeedSavedSearch +import exh.savedsearches.models.SavedSearch import exh.source.BlacklistedSources import exh.source.EH_SOURCE_ID import exh.source.HBROWSE_SOURCE_ID @@ -76,6 +80,8 @@ object EXHMigrations { private val updateChapter: UpdateChapter by injectLazy() private val deleteChapters: DeleteChapters by injectLazy() private val insertMergedReference: InsertMergedReference by injectLazy() + private val insertSavedSearch: InsertSavedSearch by injectLazy() + private val insertFeedSavedSearch: InsertFeedSavedSearch by injectLazy() /** * Performs a migration when the application is updated. @@ -360,29 +366,32 @@ object EXHMigrations { } if (oldVersion under 31) { runBlocking { - handler.await(true) { - prefs.getStringSet("eh_saved_searches", emptySet())?.forEach { - kotlin.runCatching { - val content = Json.decodeFromString(it.substringAfter(':')) - saved_searchQueries.insertSavedSearch( - _id = null, - source = it.substringBefore(':').toLongOrNull() ?: return@forEach, - name = content["name"]!!.jsonPrimitive.content, - query = content["query"]!!.jsonPrimitive.contentOrNull?.nullIfBlank(), - filters_json = Json.encodeToString(content["filters"]!!.jsonArray), - ) - } - } - } - handler.await(true) { - prefs.getStringSet("latest_tab_sources", emptySet())?.forEach { - feed_saved_searchQueries.insertFeedSavedSearch( - _id = null, - source = it.toLong(), - saved_search = null, - global = true, + val savedSearch = prefs.getStringSet("eh_saved_searches", emptySet())?.mapNotNull { + runCatching { + val content = Json.decodeFromString(it.substringAfter(':')) + SavedSearch( + id = -1, + source = it.substringBefore(':').toLongOrNull() + ?: return@runCatching null, + name = content["name"]!!.jsonPrimitive.content, + query = content["query"]!!.jsonPrimitive.contentOrNull?.nullIfBlank(), + filtersJson = Json.encodeToString(content["filters"]!!.jsonArray), ) - } + }.getOrNull() + } + if (!savedSearch.isNullOrEmpty()) { + insertSavedSearch.awaitAll(savedSearch) + } + val feedSavedSearch = prefs.getStringSet("latest_tab_sources", emptySet())?.map { + FeedSavedSearch( + id = -1, + source = it.toLong(), + savedSearch = null, + global = true, + ) + } + if (!feedSavedSearch.isNullOrEmpty()) { + insertFeedSavedSearch.awaitAll(feedSavedSearch) } } prefs.edit(commit = true) { diff --git a/app/src/main/java/exh/favorites/FavoritesSyncHelper.kt b/app/src/main/java/exh/favorites/FavoritesSyncHelper.kt index 842197fd6..b09c642a1 100644 --- a/app/src/main/java/exh/favorites/FavoritesSyncHelper.kt +++ b/app/src/main/java/exh/favorites/FavoritesSyncHelper.kt @@ -3,11 +3,11 @@ package exh.favorites import android.content.Context import android.net.wifi.WifiManager import android.os.PowerManager -import eu.kanade.data.AndroidDatabaseHandler import eu.kanade.data.DatabaseHandler import eu.kanade.domain.category.interactor.GetCategories import eu.kanade.domain.category.interactor.SetMangaCategories import eu.kanade.domain.category.model.Category +import eu.kanade.domain.manga.interactor.GetLibraryManga import eu.kanade.domain.manga.interactor.GetManga import eu.kanade.domain.manga.interactor.UpdateManga import eu.kanade.domain.manga.model.Manga @@ -49,6 +49,7 @@ import kotlin.time.Duration.Companion.seconds // TODO only apply database changes after sync class FavoritesSyncHelper(val context: Context) { private val handler: DatabaseHandler by injectLazy() + private val getLibraryManga: GetLibraryManga by injectLazy() private val getCategories: GetCategories by injectLazy() private val getManga: GetManga by injectLazy() private val updateManga: UpdateManga by injectLazy() @@ -96,7 +97,7 @@ class FavoritesSyncHelper(val context: Context) { // Validate library state status.value = FavoritesSyncStatus.Processing(context.getString(R.string.favorites_sync_verifying_library), context = context) - val libraryManga = handler.awaitList { (handler as AndroidDatabaseHandler).getLibraryQuery() } + val libraryManga = getLibraryManga.await() val seenManga = HashSet(libraryManga.size) libraryManga.forEach { if (!it.isEhBasedManga()) return@forEach diff --git a/app/src/main/java/exh/savedsearches/models/FeedSavedSearch.kt b/app/src/main/java/exh/savedsearches/models/FeedSavedSearch.kt index a91ca82ca..d6aedd1c4 100644 --- a/app/src/main/java/exh/savedsearches/models/FeedSavedSearch.kt +++ b/app/src/main/java/exh/savedsearches/models/FeedSavedSearch.kt @@ -2,14 +2,14 @@ package exh.savedsearches.models data class FeedSavedSearch( // Tag identifier, unique - var id: Long?, + val id: Long, // Source for the saved search - var source: Long, + val source: Long, // If -1 then get latest, if set get the saved search - var savedSearch: Long?, + val savedSearch: Long?, // If the feed is a global or source specific feed - var global: Boolean, + val global: Boolean, ) diff --git a/app/src/main/java/exh/savedsearches/models/SavedSearch.kt b/app/src/main/java/exh/savedsearches/models/SavedSearch.kt index 6015d0f76..20515afd0 100644 --- a/app/src/main/java/exh/savedsearches/models/SavedSearch.kt +++ b/app/src/main/java/exh/savedsearches/models/SavedSearch.kt @@ -2,17 +2,17 @@ package exh.savedsearches.models data class SavedSearch( // Tag identifier, unique - var id: Long?, + val id: Long, // The source the saved search is for - var source: Long, + val source: Long, // If false the manga will not grab chapter updates - var name: String, + val name: String, // The query if there is any - var query: String?, + val query: String?, // The filter list - var filtersJson: String?, + val filtersJson: String?, ) diff --git a/app/src/main/sqldelight/data/feed_saved_search.sq b/app/src/main/sqldelight/data/feed_saved_search.sq index c9ddc6f31..a91fdec7c 100644 --- a/app/src/main/sqldelight/data/feed_saved_search.sq +++ b/app/src/main/sqldelight/data/feed_saved_search.sq @@ -16,13 +16,13 @@ countGlobal: SELECT count(*) FROM feed_saved_search WHERE global = 1; selectBySource: -SELECT * FROM feed_saved_search WHERE source = ? AND global = 0; +SELECT * FROM feed_saved_search WHERE source = :sourceId AND global = 0; -insertFeedSavedSearch: -INSERT INTO feed_saved_search (_id, source, saved_search, global) VALUES (?, ?, ?, ?); +insert: +INSERT INTO feed_saved_search (source, saved_search, global) VALUES (:sourceId, :savedSearch, :global); deleteById: -DELETE FROM feed_saved_search WHERE _id = ?; +DELETE FROM feed_saved_search WHERE _id = :id; deleteAll: DELETE FROM feed_saved_search; @@ -38,7 +38,7 @@ ON saved_search._id = M.saved_search; selectSourceFeedSavedSearch: SELECT saved_search.* FROM ( - SELECT saved_search FROM feed_saved_search WHERE global = 0 AND source = ? + SELECT saved_search FROM feed_saved_search WHERE global = 0 AND source = :sourceId ) AS M JOIN saved_search ON saved_search._id = M.saved_search; @@ -46,7 +46,10 @@ ON saved_search._id = M.saved_search; countSourceFeedSavedSearch: SELECT count(*) FROM ( - SELECT saved_search FROM feed_saved_search WHERE global = 0 AND source = ? + SELECT saved_search FROM feed_saved_search WHERE global = 0 AND source = :sourceId ) AS M JOIN saved_search -ON saved_search._id = M.saved_search; \ No newline at end of file +ON saved_search._id = M.saved_search; + +selectLastInsertedRowId: +SELECT last_insert_rowid(); \ No newline at end of file diff --git a/app/src/main/sqldelight/data/saved_search.sq b/app/src/main/sqldelight/data/saved_search.sq index 5f4f55f1d..8b4313c92 100644 --- a/app/src/main/sqldelight/data/saved_search.sq +++ b/app/src/main/sqldelight/data/saved_search.sq @@ -7,30 +7,33 @@ CREATE TABLE saved_search( ); selectBySource: -SELECT * FROM saved_search WHERE source = ?; +SELECT * FROM saved_search WHERE source = :sourceId; deleteBySource: -DELETE FROM saved_search WHERE source = ?; +DELETE FROM saved_search WHERE source = :sourceId; selectAll: SELECT * FROM saved_search; selectById: -SELECT * FROM saved_search WHERE _id = ?; +SELECT * FROM saved_search WHERE _id = :id; selectByIds: -SELECT * FROM saved_search WHERE _id IN ?; +SELECT * FROM saved_search WHERE _id IN :ids; selectNamesAndSources: SELECT source, name FROM saved_search; -insertSavedSearch: -INSERT INTO saved_search (_id, source, name, query, filters_json) -VALUES (?, ?, ?, ?, ?); +insert: +INSERT INTO saved_search (source, name, query, filters_json) +VALUES (:source, :name, :query, :filtersJson); deleteById: -DELETE FROM saved_search WHERE _id = ?; +DELETE FROM saved_search WHERE _id = :id; deleteAll: -DELETE FROM saved_search; \ No newline at end of file +DELETE FROM saved_search; + +selectLastInsertedRowId: +SELECT last_insert_rowid(); \ No newline at end of file