Various fixes
This commit is contained in:
@@ -65,6 +65,7 @@ abstract class BaseViewModel : ViewModel() {
|
||||
}
|
||||
|
||||
protected fun <T> Flow<T>.withErrorHandling() = catch { error ->
|
||||
error.printStackTraceDebug()
|
||||
errorEvent.call(error)
|
||||
}
|
||||
|
||||
|
||||
@@ -97,3 +97,14 @@ fun LongSet.toSet(): Set<Long> = toCollection(ArraySet<Long>(size))
|
||||
fun <R : MutableCollection<Long>> LongSet.toCollection(out: R): R = out.also { result ->
|
||||
forEach(result::add)
|
||||
}
|
||||
|
||||
fun <T, R> Collection<T>.mapSortedByCount(isDescending: Boolean = true, mapper: (T) -> R): List<R> {
|
||||
val grouped = groupBy(mapper).toList()
|
||||
val sortSelector: (Pair<R, List<T>>) -> Int = { it.second.size }
|
||||
val sorted = if (isDescending) {
|
||||
grouped.sortedByDescending(sortSelector)
|
||||
} else {
|
||||
grouped.sortedBy(sortSelector)
|
||||
}
|
||||
return sorted.map { it.first }
|
||||
}
|
||||
|
||||
@@ -13,7 +13,6 @@ import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.callbackFlow
|
||||
import kotlinx.coroutines.flow.combine
|
||||
import kotlinx.coroutines.flow.distinctUntilChanged
|
||||
import kotlinx.coroutines.flow.flatMapLatest
|
||||
import kotlinx.coroutines.flow.map
|
||||
import kotlinx.coroutines.flow.onStart
|
||||
import org.koitharu.kotatsu.BuildConfig
|
||||
@@ -50,14 +49,13 @@ class MangaSourcesRepository @Inject constructor(
|
||||
private val dao: MangaSourcesDao
|
||||
get() = db.getSourcesDao()
|
||||
|
||||
private val remoteSources = EnumSet.allOf(MangaParserSource::class.java).apply {
|
||||
if (!BuildConfig.DEBUG) {
|
||||
remove(MangaParserSource.DUMMY)
|
||||
}
|
||||
}
|
||||
|
||||
val allMangaSources: Set<MangaParserSource>
|
||||
get() = Collections.unmodifiableSet(remoteSources)
|
||||
val allMangaSources: Set<MangaParserSource> = Collections.unmodifiableSet(
|
||||
EnumSet.allOf(MangaParserSource::class.java).apply {
|
||||
if (!BuildConfig.DEBUG) {
|
||||
remove(MangaParserSource.DUMMY)
|
||||
}
|
||||
},
|
||||
)
|
||||
|
||||
suspend fun getEnabledSources(): List<MangaSource> {
|
||||
assimilateNewSources()
|
||||
@@ -86,7 +84,7 @@ class MangaSourcesRepository @Inject constructor(
|
||||
|
||||
suspend fun getDisabledSources(): Set<MangaSource> {
|
||||
assimilateNewSources()
|
||||
val result = EnumSet.copyOf(remoteSources)
|
||||
val result = EnumSet.copyOf(allMangaSources)
|
||||
val enabled = dao.findAllEnabledNames()
|
||||
for (name in enabled) {
|
||||
val source = name.toMangaSourceOrNull() ?: continue
|
||||
@@ -182,7 +180,7 @@ class MangaSourcesRepository @Inject constructor(
|
||||
val result = ArrayList<Pair<MangaSource, Boolean>>(entities.size)
|
||||
for (entity in entities) {
|
||||
val source = entity.source.toMangaSourceOrNull() ?: continue
|
||||
if (source in remoteSources) {
|
||||
if (source in allMangaSources) {
|
||||
result.add(source to entity.isEnabled)
|
||||
}
|
||||
}
|
||||
@@ -199,7 +197,7 @@ class MangaSourcesRepository @Inject constructor(
|
||||
suspend fun setSourcesEnabledExclusive(sources: Set<MangaSource>) {
|
||||
db.withTransaction {
|
||||
assimilateNewSources()
|
||||
for (s in remoteSources) {
|
||||
for (s in allMangaSources) {
|
||||
dao.setEnabled(s.name, s in sources)
|
||||
}
|
||||
}
|
||||
@@ -222,7 +220,7 @@ class MangaSourcesRepository @Inject constructor(
|
||||
|
||||
fun observeHasNewSources(): Flow<Boolean> = observeIsNsfwDisabled().map { skipNsfw ->
|
||||
val sources = dao.findAllFromVersion(BuildConfig.VERSION_CODE).toSources(skipNsfw, null)
|
||||
sources.isNotEmpty() && sources.size != remoteSources.size
|
||||
sources.isNotEmpty() && sources.size != allMangaSources.size
|
||||
}.onStart { assimilateNewSources() }
|
||||
|
||||
fun observeHasNewSourcesForBadge(): Flow<Boolean> = combine(
|
||||
@@ -295,7 +293,7 @@ class MangaSourcesRepository @Inject constructor(
|
||||
|
||||
private suspend fun getNewSources(): MutableSet<out MangaSource> {
|
||||
val entities = dao.findAll()
|
||||
val result = EnumSet.copyOf(remoteSources)
|
||||
val result = EnumSet.copyOf(allMangaSources)
|
||||
for (e in entities) {
|
||||
result.remove(e.source.toMangaSourceOrNull() ?: continue)
|
||||
}
|
||||
@@ -361,7 +359,7 @@ class MangaSourcesRepository @Inject constructor(
|
||||
if (skipNsfwSources && source.isNsfw()) {
|
||||
continue
|
||||
}
|
||||
if (source in remoteSources) {
|
||||
if (source in allMangaSources) {
|
||||
result.add(
|
||||
MangaSourceInfo(
|
||||
mangaSource = source,
|
||||
|
||||
@@ -2,7 +2,6 @@ package org.koitharu.kotatsu.favourites.domain
|
||||
|
||||
import dagger.Reusable
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.mapLatest
|
||||
import org.koitharu.kotatsu.core.db.MangaDatabase
|
||||
import org.koitharu.kotatsu.core.db.entity.toManga
|
||||
import org.koitharu.kotatsu.core.db.entity.toMangaTags
|
||||
@@ -18,24 +17,20 @@ import javax.inject.Inject
|
||||
class LocalFavoritesObserver @Inject constructor(
|
||||
localMangaIndex: LocalMangaIndex,
|
||||
private val db: MangaDatabase,
|
||||
) : LocalObserveMapper<FavouriteManga, Manga>(localMangaIndex, limitStep = 10) {
|
||||
) : LocalObserveMapper<FavouriteManga, Manga>(localMangaIndex) {
|
||||
|
||||
fun observeAll(
|
||||
order: ListSortOrder,
|
||||
filterOptions: Set<ListFilterOption>,
|
||||
limit: Int
|
||||
): Flow<List<Manga>> = db.getFavouritesDao().observeAll(order, filterOptions, limit).mapLatest {
|
||||
it.mapToLocal()
|
||||
}
|
||||
): Flow<List<Manga>> = db.getFavouritesDao().observeAll(order, filterOptions, limit).mapToLocal()
|
||||
|
||||
fun observeAll(
|
||||
categoryId: Long,
|
||||
order: ListSortOrder,
|
||||
filterOptions: Set<ListFilterOption>,
|
||||
limit: Int
|
||||
): Flow<List<Manga>> = db.getFavouritesDao().observeAll(categoryId, order, filterOptions, limit).mapLatest {
|
||||
it.mapToLocal()
|
||||
}
|
||||
): Flow<List<Manga>> = db.getFavouritesDao().observeAll(categoryId, order, filterOptions, limit).mapToLocal()
|
||||
|
||||
override fun toManga(e: FavouriteManga) = e.manga.toManga(e.tags.toMangaTags())
|
||||
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
package org.koitharu.kotatsu.history.data
|
||||
|
||||
import dagger.Reusable
|
||||
import kotlinx.coroutines.flow.mapLatest
|
||||
import org.koitharu.kotatsu.core.db.MangaDatabase
|
||||
import org.koitharu.kotatsu.core.db.entity.toManga
|
||||
import org.koitharu.kotatsu.core.db.entity.toMangaTags
|
||||
@@ -17,15 +16,13 @@ import javax.inject.Inject
|
||||
class HistoryLocalObserver @Inject constructor(
|
||||
localMangaIndex: LocalMangaIndex,
|
||||
private val db: MangaDatabase,
|
||||
) : LocalObserveMapper<HistoryWithManga, MangaWithHistory>(localMangaIndex, limitStep = 10) {
|
||||
) : LocalObserveMapper<HistoryWithManga, MangaWithHistory>(localMangaIndex) {
|
||||
|
||||
fun observeAll(
|
||||
order: ListSortOrder,
|
||||
filterOptions: Set<ListFilterOption>,
|
||||
limit: Int
|
||||
) = db.getHistoryDao().observeAll(order, filterOptions, limit).mapLatest {
|
||||
it.mapToLocal()
|
||||
}
|
||||
) = db.getHistoryDao().observeAll(order, filterOptions, limit).mapToLocal()
|
||||
|
||||
override fun toManga(e: HistoryWithManga) = e.manga.toManga(e.tags.toMangaTags())
|
||||
|
||||
|
||||
@@ -4,19 +4,15 @@ import android.content.Context
|
||||
import androidx.core.content.edit
|
||||
import androidx.room.withTransaction
|
||||
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.flow.FlowCollector
|
||||
import kotlinx.coroutines.runInterruptible
|
||||
import kotlinx.coroutines.sync.Mutex
|
||||
import kotlinx.coroutines.sync.withLock
|
||||
import org.koitharu.kotatsu.core.db.MangaDatabase
|
||||
import org.koitharu.kotatsu.core.parser.MangaDataRepository
|
||||
import org.koitharu.kotatsu.core.util.ext.printStackTraceDebug
|
||||
import org.koitharu.kotatsu.local.data.LocalMangaRepository
|
||||
import org.koitharu.kotatsu.local.data.LocalStorageManager
|
||||
import org.koitharu.kotatsu.local.data.input.LocalMangaInput
|
||||
import org.koitharu.kotatsu.local.domain.model.LocalManga
|
||||
import org.koitharu.kotatsu.parsers.model.MangaTag
|
||||
import org.koitharu.kotatsu.parsers.util.runCatchingCancellable
|
||||
import java.io.File
|
||||
import javax.inject.Inject
|
||||
@@ -27,7 +23,6 @@ import javax.inject.Singleton
|
||||
class LocalMangaIndex @Inject constructor(
|
||||
private val mangaDataRepository: MangaDataRepository,
|
||||
private val db: MangaDatabase,
|
||||
private val localStorageManager: LocalStorageManager,
|
||||
@ApplicationContext context: Context,
|
||||
private val localMangaRepositoryProvider: Provider<LocalMangaRepository>,
|
||||
) : FlowCollector<LocalManga?> {
|
||||
@@ -35,9 +30,9 @@ class LocalMangaIndex @Inject constructor(
|
||||
private val prefs = context.getSharedPreferences(PREF_NAME, Context.MODE_PRIVATE)
|
||||
private val mutex = Mutex()
|
||||
|
||||
private var previousHash: Long
|
||||
get() = prefs.getLong(KEY_HASH, 0L)
|
||||
set(value) = prefs.edit { putLong(KEY_HASH, value) }
|
||||
private var currentVersion: Int
|
||||
get() = prefs.getInt(KEY_VERSION, 0)
|
||||
set(value) = prefs.edit { putInt(KEY_VERSION, value) }
|
||||
|
||||
override suspend fun emit(value: LocalManga?) {
|
||||
if (value != null) {
|
||||
@@ -45,22 +40,25 @@ class LocalMangaIndex @Inject constructor(
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun update(): Boolean = mutex.withLock {
|
||||
val newHash = computeHash()
|
||||
if (newHash == previousHash) {
|
||||
return false
|
||||
}
|
||||
suspend fun update() = mutex.withLock {
|
||||
db.withTransaction {
|
||||
val dao = db.getLocalMangaIndexDao()
|
||||
dao.clear()
|
||||
localMangaRepositoryProvider.get().getRawListAsFlow()
|
||||
.collect { dao.upsert(it.toEntity()) }
|
||||
localMangaRepositoryProvider.get()
|
||||
.getRawListAsFlow()
|
||||
.collect { upsert(it) }
|
||||
}
|
||||
currentVersion = VERSION
|
||||
}
|
||||
|
||||
suspend fun updateIfRequired() {
|
||||
if (isUpdateRequired()) {
|
||||
update()
|
||||
}
|
||||
previousHash = newHash
|
||||
return true
|
||||
}
|
||||
|
||||
suspend fun get(mangaId: Long): LocalManga? {
|
||||
updateIfRequired()
|
||||
var path = db.getLocalMangaIndexDao().findPath(mangaId)
|
||||
if (path == null && mutex.isLocked) { // wait for updating complete
|
||||
path = mutex.withLock { db.getLocalMangaIndexDao().findPath(mangaId) }
|
||||
@@ -77,8 +75,7 @@ class LocalMangaIndex @Inject constructor(
|
||||
|
||||
suspend fun put(manga: LocalManga) = mutex.withLock {
|
||||
db.withTransaction {
|
||||
mangaDataRepository.storeManga(manga.manga)
|
||||
db.getLocalMangaIndexDao().upsert(manga.toEntity())
|
||||
upsert(manga)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -90,27 +87,22 @@ class LocalMangaIndex @Inject constructor(
|
||||
return db.getLocalMangaIndexDao().findTags()
|
||||
}
|
||||
|
||||
private suspend fun upsert(manga: LocalManga) {
|
||||
mangaDataRepository.storeManga(manga.manga)
|
||||
db.getLocalMangaIndexDao().upsert(manga.toEntity())
|
||||
}
|
||||
|
||||
private fun LocalManga.toEntity() = LocalMangaIndexEntity(
|
||||
mangaId = manga.id,
|
||||
path = file.path,
|
||||
)
|
||||
|
||||
private suspend fun computeHash(): Long {
|
||||
return runCatchingCancellable {
|
||||
localStorageManager.getReadableDirs()
|
||||
.fold(0L) { acc, file -> acc + file.computeHash() }
|
||||
}.onFailure {
|
||||
it.printStackTraceDebug()
|
||||
}.getOrDefault(0L)
|
||||
}
|
||||
|
||||
private suspend fun File.computeHash(): Long = runInterruptible(Dispatchers.IO) {
|
||||
lastModified() // TODO size
|
||||
}
|
||||
private fun isUpdateRequired() = currentVersion < VERSION
|
||||
|
||||
companion object {
|
||||
|
||||
private const val PREF_NAME = "_local_index"
|
||||
private const val KEY_HASH = "hash"
|
||||
private const val KEY_VERSION = "ver"
|
||||
private const val VERSION = 1
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,16 +4,24 @@ import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.async
|
||||
import kotlinx.coroutines.awaitAll
|
||||
import kotlinx.coroutines.coroutineScope
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.mapLatest
|
||||
import kotlinx.coroutines.flow.onStart
|
||||
import org.koitharu.kotatsu.core.model.isLocal
|
||||
import org.koitharu.kotatsu.local.data.index.LocalMangaIndex
|
||||
import org.koitharu.kotatsu.parsers.model.Manga
|
||||
|
||||
abstract class LocalObserveMapper<E : Any, R : Any>(
|
||||
private val localMangaIndex: LocalMangaIndex,
|
||||
private val limitStep: Int,
|
||||
) {
|
||||
|
||||
protected suspend fun List<E>.mapToLocal(): List<R> = coroutineScope {
|
||||
protected fun Flow<Collection<E>>.mapToLocal() = onStart {
|
||||
localMangaIndex.updateIfRequired()
|
||||
}.mapLatest {
|
||||
it.mapToLocal()
|
||||
}
|
||||
|
||||
private suspend fun Collection<E>.mapToLocal(): List<R> = coroutineScope {
|
||||
val dispatcher = Dispatchers.IO.limitedParallelism(6)
|
||||
map { item ->
|
||||
val m = toManga(item)
|
||||
|
||||
@@ -9,6 +9,7 @@ import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import org.koitharu.kotatsu.core.ui.BaseViewModel
|
||||
import org.koitharu.kotatsu.core.util.LocaleComparator
|
||||
import org.koitharu.kotatsu.core.util.ext.mapSortedByCount
|
||||
import org.koitharu.kotatsu.core.util.ext.sortedWithSafe
|
||||
import org.koitharu.kotatsu.core.util.ext.toList
|
||||
import org.koitharu.kotatsu.core.util.ext.toLocale
|
||||
@@ -43,15 +44,20 @@ class WelcomeViewModel @Inject constructor(
|
||||
|
||||
val types = MutableStateFlow(
|
||||
FilterProperty(
|
||||
availableItems = ContentType.entries.toList(),
|
||||
availableItems = listOf(ContentType.MANGA),
|
||||
selectedItems = setOf(ContentType.MANGA),
|
||||
isLoading = false,
|
||||
isLoading = true,
|
||||
error = null,
|
||||
),
|
||||
)
|
||||
|
||||
init {
|
||||
updateJob = launchJob(Dispatchers.Default) {
|
||||
val contentTypes = allSources.mapSortedByCount { it.contentType }
|
||||
types.value = types.value.copy(
|
||||
availableItems = contentTypes,
|
||||
isLoading = false,
|
||||
)
|
||||
val languages = localesGroups.keys.associateBy { x -> x.language }
|
||||
val selectedLocales = HashSet<Locale>(2)
|
||||
ConfigurationCompat.getLocales(context.resources.configuration).toList()
|
||||
|
||||
@@ -14,20 +14,18 @@ import kotlinx.coroutines.plus
|
||||
import org.koitharu.kotatsu.R
|
||||
import org.koitharu.kotatsu.core.db.MangaDatabase
|
||||
import org.koitharu.kotatsu.core.db.TABLE_SOURCES
|
||||
import org.koitharu.kotatsu.core.model.isNsfw
|
||||
import org.koitharu.kotatsu.core.prefs.AppSettings
|
||||
import org.koitharu.kotatsu.core.ui.BaseViewModel
|
||||
import org.koitharu.kotatsu.core.ui.util.ReversibleAction
|
||||
import org.koitharu.kotatsu.core.util.ext.MutableEventFlow
|
||||
import org.koitharu.kotatsu.core.util.ext.call
|
||||
import org.koitharu.kotatsu.core.util.ext.mapSortedByCount
|
||||
import org.koitharu.kotatsu.explore.data.MangaSourcesRepository
|
||||
import org.koitharu.kotatsu.explore.data.SourcesSortOrder
|
||||
import org.koitharu.kotatsu.list.ui.model.ListModel
|
||||
import org.koitharu.kotatsu.list.ui.model.LoadingState
|
||||
import org.koitharu.kotatsu.parsers.model.ContentType
|
||||
import org.koitharu.kotatsu.parsers.model.MangaParserSource
|
||||
import org.koitharu.kotatsu.parsers.model.MangaSource
|
||||
import java.util.EnumMap
|
||||
import java.util.EnumSet
|
||||
import java.util.Locale
|
||||
import javax.inject.Inject
|
||||
@@ -139,13 +137,11 @@ class SourcesCatalogViewModel @Inject constructor(
|
||||
|
||||
@WorkerThread
|
||||
private fun getContentTypes(isNsfwDisabled: Boolean): List<ContentType> {
|
||||
val map = EnumMap<ContentType, Int>(ContentType::class.java)
|
||||
for (e in MangaParserSource.entries) {
|
||||
if (isNsfwDisabled && e.isNsfw()) {
|
||||
continue
|
||||
}
|
||||
map[e.contentType] = map.getOrDefault(e.contentType, 0) + 1
|
||||
val result = repository.allMangaSources.mapSortedByCount { it.contentType }
|
||||
return if (isNsfwDisabled) {
|
||||
result.filterNot { it == ContentType.HENTAI }
|
||||
} else {
|
||||
result
|
||||
}
|
||||
return map.entries.sortedByDescending { it.value }.map { it.key }
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user