Add favorite entry alternative handling, allowing parennt versions to take priority for favorites sync
This commit is contained in:
parent
e9a3463455
commit
282a0c4e16
@ -52,6 +52,7 @@ import tachiyomi.domain.manga.interactor.InsertFlatMetadata
|
||||
import tachiyomi.domain.manga.interactor.InsertMergedReference
|
||||
import tachiyomi.domain.manga.interactor.SetCustomMangaInfo
|
||||
import tachiyomi.domain.manga.interactor.SetMangaFilteredScanlators
|
||||
import tachiyomi.domain.manga.interactor.InsertFavoriteEntryAlternative
|
||||
import tachiyomi.domain.manga.interactor.UpdateMergedSettings
|
||||
import tachiyomi.domain.manga.repository.CustomMangaRepository
|
||||
import tachiyomi.domain.manga.repository.FavoritesEntryRepository
|
||||
@ -140,6 +141,7 @@ class SYDomainModule : InjektModule {
|
||||
addFactory { GetFavoriteEntries(get()) }
|
||||
addFactory { InsertFavoriteEntries(get()) }
|
||||
addFactory { DeleteFavoriteEntries(get()) }
|
||||
addFactory { InsertFavoriteEntryAlternative(get()) }
|
||||
|
||||
addSingletonFactory<SavedSearchRepository> { SavedSearchRepositoryImpl(get()) }
|
||||
addFactory { GetSavedSearchById(get()) }
|
||||
|
@ -2,6 +2,7 @@ package exh.eh
|
||||
|
||||
import android.content.Context
|
||||
import eu.kanade.domain.manga.interactor.UpdateManga
|
||||
import exh.metadata.metadata.EHentaiSearchMetadata
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.async
|
||||
import kotlinx.coroutines.coroutineScope
|
||||
@ -18,6 +19,8 @@ import tachiyomi.domain.history.interactor.UpsertHistory
|
||||
import tachiyomi.domain.history.model.History
|
||||
import tachiyomi.domain.history.model.HistoryUpdate
|
||||
import tachiyomi.domain.manga.interactor.GetManga
|
||||
import tachiyomi.domain.manga.interactor.InsertFavoriteEntryAlternative
|
||||
import tachiyomi.domain.manga.model.FavoriteEntryAlternative
|
||||
import tachiyomi.domain.manga.model.Manga
|
||||
import tachiyomi.domain.manga.model.MangaUpdate
|
||||
import uy.kohesive.injekt.injectLazy
|
||||
@ -41,6 +44,7 @@ class EHentaiUpdateHelper(context: Context) {
|
||||
private val upsertHistory: UpsertHistory by injectLazy()
|
||||
private val removeHistory: RemoveHistory by injectLazy()
|
||||
private val getHistoryByMangaId: GetHistoryByMangaId by injectLazy()
|
||||
private val insertFavoriteEntryAlternative: InsertFavoriteEntryAlternative by injectLazy()
|
||||
|
||||
/**
|
||||
* @param chapters Cannot be an empty list!
|
||||
@ -123,6 +127,12 @@ class EHentaiUpdateHelper(context: Context) {
|
||||
upsertHistory.await(it)
|
||||
}
|
||||
|
||||
// Update favorites entry database
|
||||
val favoriteEntryUpdate = getFavoriteEntryAlternative(accepted, toDiscard)
|
||||
if (favoriteEntryUpdate != null) {
|
||||
insertFavoriteEntryAlternative.await(favoriteEntryUpdate)
|
||||
}
|
||||
|
||||
// Copy categories from all chains to accepted manga
|
||||
|
||||
val newCategories = rootsToMutate.flatMap { chapterChain ->
|
||||
@ -145,7 +155,24 @@ class EHentaiUpdateHelper(context: Context) {
|
||||
}
|
||||
}
|
||||
|
||||
fun getHistory(
|
||||
private fun getFavoriteEntryAlternative(
|
||||
accepted: ChapterChain,
|
||||
toDiscard: List<ChapterChain>,
|
||||
): FavoriteEntryAlternative? {
|
||||
val favorite = toDiscard.find { it.manga.favorite } ?: return null
|
||||
|
||||
val gid = EHentaiSearchMetadata.galleryId(accepted.manga.url)
|
||||
val token = EHentaiSearchMetadata.galleryToken(accepted.manga.url)
|
||||
|
||||
return FavoriteEntryAlternative(
|
||||
otherGid = gid,
|
||||
otherToken = token,
|
||||
gid = EHentaiSearchMetadata.galleryId(favorite.manga.url),
|
||||
token = EHentaiSearchMetadata.galleryToken(favorite.manga.url),
|
||||
)
|
||||
}
|
||||
|
||||
private fun getHistory(
|
||||
currentChapters: List<Chapter>,
|
||||
chainsAsChapters: List<Chapter>,
|
||||
chainsAsHistory: List<History>,
|
||||
|
@ -19,14 +19,16 @@ import tachiyomi.domain.manga.interactor.GetFavorites
|
||||
import tachiyomi.domain.manga.interactor.InsertFavoriteEntries
|
||||
import tachiyomi.domain.manga.model.FavoriteEntry
|
||||
import tachiyomi.domain.manga.model.Manga
|
||||
import uy.kohesive.injekt.injectLazy
|
||||
import uy.kohesive.injekt.Injekt
|
||||
import uy.kohesive.injekt.api.get
|
||||
|
||||
class LocalFavoritesStorage {
|
||||
private val getFavorites: GetFavorites by injectLazy()
|
||||
private val getCategories: GetCategories by injectLazy()
|
||||
private val deleteFavoriteEntries: DeleteFavoriteEntries by injectLazy()
|
||||
private val getFavoriteEntries: GetFavoriteEntries by injectLazy()
|
||||
private val insertFavoriteEntries: InsertFavoriteEntries by injectLazy()
|
||||
class LocalFavoritesStorage(
|
||||
private val getFavorites: GetFavorites = Injekt.get(),
|
||||
private val getCategories: GetCategories = Injekt.get(),
|
||||
private val deleteFavoriteEntries: DeleteFavoriteEntries = Injekt.get(),
|
||||
private val getFavoriteEntries: GetFavoriteEntries = Injekt.get(),
|
||||
private val insertFavoriteEntries: InsertFavoriteEntries = Injekt.get(),
|
||||
) {
|
||||
|
||||
suspend fun getChangedDbEntries() = getFavorites.await()
|
||||
.asFlow()
|
||||
@ -67,27 +69,29 @@ class LocalFavoritesStorage {
|
||||
|
||||
val databaseEntries = getFavoriteEntries.await()
|
||||
|
||||
val added = terminated.filter {
|
||||
queryListForEntry(databaseEntries, it) == null
|
||||
}
|
||||
val added = terminated.groupBy { it.gid to it.token }
|
||||
.filter { (_, values) ->
|
||||
values.all { queryListForEntry(databaseEntries, it) == null }
|
||||
}
|
||||
.map { it.value.first() }
|
||||
|
||||
val removed = databaseEntries
|
||||
.filter {
|
||||
queryListForEntry(terminated, it) == null
|
||||
} /*.map {
|
||||
todo see what this does
|
||||
realm.copyFromRealm(it)
|
||||
}*/
|
||||
.groupBy { it.gid to it.token }
|
||||
.filter { (_, values) ->
|
||||
values.all { queryListForEntry(terminated, it) == null }
|
||||
}
|
||||
.map { it.value.first() }
|
||||
|
||||
return ChangeSet(added, removed)
|
||||
}
|
||||
|
||||
private fun FavoriteEntry.urlEquals(other: FavoriteEntry) = (gid == other.gid && token == other.token) ||
|
||||
(otherGid != null && otherToken != null && (otherGid == other.gid && otherToken == other.token)) ||
|
||||
(other.otherGid != null && other.otherToken != null && (gid == other.otherGid && token == other.otherToken)) ||
|
||||
(otherGid != null && otherToken != null && other.otherGid != null && other.otherToken != null && otherGid == other.otherGid && otherToken == other.otherToken)
|
||||
|
||||
private fun queryListForEntry(list: List<FavoriteEntry>, entry: FavoriteEntry) =
|
||||
list.find {
|
||||
it.gid == entry.gid &&
|
||||
it.token == entry.token &&
|
||||
it.category == entry.category
|
||||
}
|
||||
list.find { it.urlEquals(entry) && it.category == entry.category }
|
||||
|
||||
private suspend fun Flow<Manga>.loadDbCategories(): Flow<Pair<Int, Manga>> {
|
||||
val dbCategories = getCategories.await()
|
||||
|
178
app/src/test/kotlin/Tester.kt
Normal file
178
app/src/test/kotlin/Tester.kt
Normal file
@ -0,0 +1,178 @@
|
||||
|
||||
import eu.kanade.tachiyomi.data.backup.models.BackupSerializer
|
||||
import eu.kanade.tachiyomi.source.model.SManga
|
||||
import eu.kanade.tachiyomi.source.online.all.EHentai
|
||||
import exh.favorites.LocalFavoritesStorage
|
||||
import exh.metadata.metadata.EHentaiSearchMetadata
|
||||
import exh.source.EXH_SOURCE_ID
|
||||
import io.kotest.inspectors.shouldForAll
|
||||
import io.mockk.coEvery
|
||||
import io.mockk.mockk
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import kotlinx.serialization.protobuf.ProtoBuf
|
||||
import okio.buffer
|
||||
import okio.gzip
|
||||
import okio.sink
|
||||
import okio.source
|
||||
import org.junit.jupiter.api.BeforeAll
|
||||
import org.junit.jupiter.api.Disabled
|
||||
import org.junit.jupiter.api.Test
|
||||
import tachiyomi.domain.category.interactor.GetCategories
|
||||
import tachiyomi.domain.category.model.Category
|
||||
import tachiyomi.domain.manga.interactor.GetCustomMangaInfo
|
||||
import tachiyomi.domain.manga.interactor.GetFavoriteEntries
|
||||
import tachiyomi.domain.manga.interactor.GetFavorites
|
||||
import tachiyomi.domain.manga.model.CustomMangaInfo
|
||||
import tachiyomi.domain.manga.model.FavoriteEntry
|
||||
import tachiyomi.domain.manga.model.Manga
|
||||
import tachiyomi.domain.manga.repository.CustomMangaRepository
|
||||
import uy.kohesive.injekt.Injekt
|
||||
import uy.kohesive.injekt.api.addSingletonFactory
|
||||
import java.io.File
|
||||
|
||||
class Tester {
|
||||
|
||||
@Disabled
|
||||
@Test
|
||||
fun stripBackup() {
|
||||
val bytes = File("D:\\Downloads\\pacthiyomi_2023-05-08_13-30.proto (1).gz")
|
||||
.inputStream().source().buffer()
|
||||
.gzip().buffer()
|
||||
.readByteArray()
|
||||
val backup = ProtoBuf.decodeFromByteArray(BackupSerializer, bytes)
|
||||
val newBytes = ProtoBuf.encodeToByteArray(
|
||||
BackupSerializer,
|
||||
backup.copy(
|
||||
backupManga = backup.backupManga.filter { it.favorite },
|
||||
),
|
||||
)
|
||||
File("D:\\Downloads\\pacthiyomi_2023-05-08_13-30 (2).proto.gz").outputStream().sink().gzip().buffer().use {
|
||||
it.write(newBytes)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun localFavoritesStorageTester(): Unit = runBlocking {
|
||||
val favorites = listOf(
|
||||
Manga.create().copy(
|
||||
id = 1,
|
||||
favorite = true,
|
||||
source = EXH_SOURCE_ID,
|
||||
url = "/g/gid/token",
|
||||
),
|
||||
// an alias for gid2/token2
|
||||
Manga.create().copy(
|
||||
id = 3,
|
||||
favorite = true,
|
||||
source = EXH_SOURCE_ID,
|
||||
url = "/g/gid3/token3",
|
||||
),
|
||||
// add this one to library
|
||||
Manga.create().copy(
|
||||
id = 3,
|
||||
favorite = true,
|
||||
source = EXH_SOURCE_ID,
|
||||
url = "/g/gid4/token4",
|
||||
),
|
||||
)
|
||||
val categories = listOf(
|
||||
Category(
|
||||
id = 1,
|
||||
name = "a",
|
||||
order = 1,
|
||||
flags = 0,
|
||||
),
|
||||
)
|
||||
val favoriteEntries = listOf(
|
||||
FavoriteEntry(
|
||||
gid = "gid",
|
||||
token = "token",
|
||||
title = "a",
|
||||
category = 0,
|
||||
),
|
||||
FavoriteEntry(
|
||||
gid = "gid2",
|
||||
token = "token2",
|
||||
title = "a",
|
||||
category = 0,
|
||||
),
|
||||
// the alias for gid2/token2
|
||||
FavoriteEntry(
|
||||
gid = "gid2",
|
||||
token = "token2",
|
||||
title = "a",
|
||||
category = 0,
|
||||
otherGid = "gid3",
|
||||
otherToken = "token3",
|
||||
),
|
||||
// removed on remote and local
|
||||
FavoriteEntry(
|
||||
gid = "gid6",
|
||||
token = "token6",
|
||||
title = "a",
|
||||
category = 0,
|
||||
),
|
||||
)
|
||||
|
||||
val getFavorites = mockk<GetFavorites>()
|
||||
coEvery { getFavorites.await() } returns favorites
|
||||
|
||||
val getCategories = mockk<GetCategories>()
|
||||
coEvery { getCategories.await() } returns categories
|
||||
coEvery { getCategories.await(any()) } returns categories
|
||||
|
||||
val getFavoriteEntries = mockk<GetFavoriteEntries>()
|
||||
coEvery { getFavoriteEntries.await() } returns favoriteEntries
|
||||
|
||||
val storage = LocalFavoritesStorage(
|
||||
getFavorites = getFavorites,
|
||||
getCategories = getCategories,
|
||||
deleteFavoriteEntries = mockk(),
|
||||
getFavoriteEntries = getFavoriteEntries,
|
||||
insertFavoriteEntries = mockk(),
|
||||
)
|
||||
|
||||
val (added, removed) = storage.getChangedDbEntries()
|
||||
added.shouldForAll { it.gid == "gid4" && it.token == "token4" }
|
||||
removed.shouldForAll { it.gid == "gid6" && it.token == "token6" }
|
||||
|
||||
val (remoteAdded, remoteRemoved) = storage.getChangedRemoteEntries(
|
||||
listOf(
|
||||
EHentai.ParsedManga(
|
||||
0,
|
||||
SManga("/g/gid/token", "a"),
|
||||
EHentaiSearchMetadata(),
|
||||
),
|
||||
EHentai.ParsedManga(
|
||||
0,
|
||||
SManga("/g/gid2/token2", "a"),
|
||||
EHentaiSearchMetadata(),
|
||||
),
|
||||
// added on remote
|
||||
EHentai.ParsedManga(
|
||||
0,
|
||||
SManga("/g/gid5/token5", "a"),
|
||||
EHentaiSearchMetadata(),
|
||||
),
|
||||
),
|
||||
)
|
||||
|
||||
remoteAdded.shouldForAll { it.gid == "gid5" && it.token == "token5" }
|
||||
remoteRemoved.shouldForAll { it.gid == "gid6" && it.token == "token6" }
|
||||
}
|
||||
|
||||
companion object {
|
||||
@JvmStatic
|
||||
@BeforeAll
|
||||
fun before() {
|
||||
Injekt.addSingletonFactory {
|
||||
GetCustomMangaInfo(
|
||||
object : CustomMangaRepository {
|
||||
override fun get(mangaId: Long) = null
|
||||
override fun set(mangaInfo: CustomMangaInfo) = Unit
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -2,13 +2,14 @@ package tachiyomi.data.manga
|
||||
|
||||
import tachiyomi.domain.manga.model.FavoriteEntry
|
||||
|
||||
val favoriteEntryMapper: (Long, String, String, String, Long) -> FavoriteEntry =
|
||||
{ id, title, gid, token, category ->
|
||||
val favoriteEntryMapper: (String, String, String, Long, String?, String?) -> FavoriteEntry =
|
||||
{ gid, token, title, category, otherGid, otherToken ->
|
||||
FavoriteEntry(
|
||||
id = id,
|
||||
title = title,
|
||||
gid = gid,
|
||||
token = token,
|
||||
title = title,
|
||||
category = category.toInt(),
|
||||
otherGid = otherGid,
|
||||
otherToken = otherToken,
|
||||
)
|
||||
}
|
||||
|
@ -2,6 +2,7 @@ package tachiyomi.data.manga
|
||||
|
||||
import tachiyomi.data.DatabaseHandler
|
||||
import tachiyomi.domain.manga.model.FavoriteEntry
|
||||
import tachiyomi.domain.manga.model.FavoriteEntryAlternative
|
||||
import tachiyomi.domain.manga.repository.FavoritesEntryRepository
|
||||
|
||||
class FavoritesEntryRepositoryImpl(
|
||||
@ -14,7 +15,12 @@ class FavoritesEntryRepositoryImpl(
|
||||
override suspend fun insertAll(favoriteEntries: List<FavoriteEntry>) {
|
||||
handler.await(true) {
|
||||
favoriteEntries.forEach {
|
||||
eh_favoritesQueries.insertEhFavorites(it.id, it.title, it.gid, it.token, it.category.toLong())
|
||||
eh_favoritesQueries.insertEhFavorites(
|
||||
title = it.title,
|
||||
gid = it.gid,
|
||||
token = it.token,
|
||||
category = it.category.toLong(),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -22,4 +28,15 @@ class FavoritesEntryRepositoryImpl(
|
||||
override suspend fun selectAll(): List<FavoriteEntry> {
|
||||
return handler.awaitList { eh_favoritesQueries.selectAll(favoriteEntryMapper) }
|
||||
}
|
||||
|
||||
override suspend fun addAlternative(favoriteEntryAlternative: FavoriteEntryAlternative) {
|
||||
handler.await {
|
||||
eh_favoritesQueries.addAlternative(
|
||||
otherGid = favoriteEntryAlternative.otherGid,
|
||||
otherToken = favoriteEntryAlternative.otherToken,
|
||||
gid = favoriteEntryAlternative.gid,
|
||||
token = favoriteEntryAlternative.token,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,16 +1,34 @@
|
||||
CREATE TABLE eh_favorites (
|
||||
_id INTEGER NOT NULL PRIMARY KEY,
|
||||
title TEXT NOT NULL,
|
||||
gid TEXT NOT NULL,
|
||||
token TEXT NOT NULL,
|
||||
category INTEGER NOT NULL
|
||||
title TEXT NOT NULL,
|
||||
category INTEGER NOT NULL,
|
||||
PRIMARY KEY (gid, token)
|
||||
);
|
||||
|
||||
CREATE TABLE eh_favorites_alternatives (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
gid TEXT NOT NULL,
|
||||
token TEXT NOT NULL,
|
||||
otherGid TEXT NOT NULL,
|
||||
otherToken TEXT NOT NULL,
|
||||
FOREIGN KEY (gid, token) REFERENCES eh_favorites(gid, token)
|
||||
);
|
||||
|
||||
CREATE INDEX eh_favorites_alternatives_gid_token_index ON eh_favorites_alternatives(gid, token);
|
||||
CREATE INDEX eh_favorites_alternatives_other_gid_token_index ON eh_favorites_alternatives(otherGid, otherToken);
|
||||
|
||||
selectAll:
|
||||
SELECT * FROM eh_favorites;
|
||||
SELECT f.gid, f.token, f.title, f.category, a.otherGid, a.otherToken
|
||||
FROM eh_favorites AS f
|
||||
LEFT JOIN eh_favorites_alternatives AS a ON f.gid = a.gid AND f.token = a.token;
|
||||
|
||||
insertEhFavorites:
|
||||
INSERT INTO eh_favorites (_id, title, gid, token, category) VALUES (?, ?, ?, ?, ?);
|
||||
INSERT INTO eh_favorites (title, gid, token, category) VALUES (?, ?, ?, ?);
|
||||
|
||||
deleteAll:
|
||||
DELETE FROM eh_favorites;
|
||||
DELETE FROM eh_favorites;
|
||||
|
||||
addAlternative:
|
||||
INSERT INTO eh_favorites_alternatives (gid, token, otherGid, otherToken)
|
||||
VALUES (:gid, :token, :otherGid, :otherToken);
|
25
data/src/main/sqldelight/tachiyomi/migrations/26.sqm
Normal file
25
data/src/main/sqldelight/tachiyomi/migrations/26.sqm
Normal file
@ -0,0 +1,25 @@
|
||||
ALTER TABLE eh_favorites RENAME TO eh_favorites_temp;
|
||||
CREATE TABLE eh_favorites (
|
||||
gid TEXT NOT NULL,
|
||||
token TEXT NOT NULL,
|
||||
title TEXT NOT NULL,
|
||||
category INTEGER NOT NULL,
|
||||
PRIMARY KEY (gid, token)
|
||||
);
|
||||
INSERT INTO eh_favorites
|
||||
SELECT gid, token, title, category
|
||||
FROM eh_favorites_temp;
|
||||
|
||||
DROP TABLE IF EXISTS eh_favorites_temp;
|
||||
|
||||
CREATE TABLE eh_favorites_alternatives (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
gid TEXT NOT NULL,
|
||||
token TEXT NOT NULL,
|
||||
otherGid TEXT NOT NULL,
|
||||
otherToken TEXT NOT NULL,
|
||||
FOREIGN KEY (gid, token) REFERENCES eh_favorites(gid, token)
|
||||
);
|
||||
|
||||
CREATE INDEX eh_favorites_alternatives_gid_token_index ON eh_favorites_alternatives(gid, token);
|
||||
CREATE INDEX eh_favorites_alternatives_other_gid_token_index ON eh_favorites_alternatives(otherGid, otherToken);
|
@ -0,0 +1,13 @@
|
||||
package tachiyomi.domain.manga.interactor
|
||||
|
||||
import tachiyomi.domain.manga.model.FavoriteEntryAlternative
|
||||
import tachiyomi.domain.manga.repository.FavoritesEntryRepository
|
||||
|
||||
class InsertFavoriteEntryAlternative(
|
||||
private val favoriteEntryRepository: FavoritesEntryRepository,
|
||||
) {
|
||||
|
||||
suspend fun await(entry: FavoriteEntryAlternative) {
|
||||
return favoriteEntryRepository.addAlternative(entry)
|
||||
}
|
||||
}
|
@ -3,7 +3,6 @@ package tachiyomi.domain.manga.model
|
||||
import exh.metadata.metadata.EHentaiSearchMetadata
|
||||
|
||||
data class FavoriteEntry(
|
||||
val id: Long? = null,
|
||||
|
||||
val title: String,
|
||||
|
||||
@ -11,6 +10,10 @@ data class FavoriteEntry(
|
||||
|
||||
val token: String,
|
||||
|
||||
val otherGid: String? = null,
|
||||
|
||||
val otherToken: String? = null,
|
||||
|
||||
val category: Int = -1,
|
||||
) {
|
||||
fun getUrl() = EHentaiSearchMetadata.idAndTokenToUrl(gid, token)
|
||||
|
@ -0,0 +1,8 @@
|
||||
package tachiyomi.domain.manga.model
|
||||
|
||||
data class FavoriteEntryAlternative(
|
||||
val otherGid: String,
|
||||
val otherToken: String,
|
||||
val gid: String,
|
||||
val token: String,
|
||||
)
|
@ -1,6 +1,7 @@
|
||||
package tachiyomi.domain.manga.repository
|
||||
|
||||
import tachiyomi.domain.manga.model.FavoriteEntry
|
||||
import tachiyomi.domain.manga.model.FavoriteEntryAlternative
|
||||
|
||||
interface FavoritesEntryRepository {
|
||||
suspend fun deleteAll()
|
||||
@ -8,4 +9,6 @@ interface FavoritesEntryRepository {
|
||||
suspend fun insertAll(favoriteEntries: List<FavoriteEntry>)
|
||||
|
||||
suspend fun selectAll(): List<FavoriteEntry>
|
||||
|
||||
suspend fun addAlternative(favoriteEntryAlternative: FavoriteEntryAlternative)
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user