Local manga index in database
parent
66644d55a4
commit
169e31e9ba
@ -0,0 +1,11 @@
|
|||||||
|
package org.koitharu.kotatsu.core.db.migrations
|
||||||
|
|
||||||
|
import androidx.room.migration.Migration
|
||||||
|
import androidx.sqlite.db.SupportSQLiteDatabase
|
||||||
|
|
||||||
|
class Migration22To23 : Migration(22, 23) {
|
||||||
|
|
||||||
|
override fun migrate(db: SupportSQLiteDatabase) {
|
||||||
|
db.execSQL("CREATE TABLE IF NOT EXISTS `local_index` (`manga_id` INTEGER NOT NULL, `path` TEXT NOT NULL, PRIMARY KEY(`manga_id`), FOREIGN KEY(`manga_id`) REFERENCES `manga`(`manga_id`) ON UPDATE NO ACTION ON DELETE CASCADE )")
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,32 +0,0 @@
|
|||||||
package org.koitharu.kotatsu.local.data
|
|
||||||
|
|
||||||
import androidx.collection.MutableLongObjectMap
|
|
||||||
import org.koitharu.kotatsu.core.util.ext.printStackTraceDebug
|
|
||||||
import org.koitharu.kotatsu.local.data.input.LocalMangaInput
|
|
||||||
import org.koitharu.kotatsu.local.domain.model.LocalManga
|
|
||||||
import org.koitharu.kotatsu.parsers.util.runCatchingCancellable
|
|
||||||
import java.io.File
|
|
||||||
|
|
||||||
class LocalMangaMappingCache {
|
|
||||||
|
|
||||||
private val map = MutableLongObjectMap<File>()
|
|
||||||
|
|
||||||
suspend fun get(mangaId: Long): LocalManga? {
|
|
||||||
val file = synchronized(this) {
|
|
||||||
map[mangaId]
|
|
||||||
} ?: return null
|
|
||||||
return runCatchingCancellable {
|
|
||||||
LocalMangaInput.of(file).getManga()
|
|
||||||
}.onFailure {
|
|
||||||
it.printStackTraceDebug()
|
|
||||||
}.getOrNull()
|
|
||||||
}
|
|
||||||
|
|
||||||
operator fun set(mangaId: Long, localManga: LocalManga?) = synchronized(this) {
|
|
||||||
if (localManga == null) {
|
|
||||||
map.remove(mangaId)
|
|
||||||
} else {
|
|
||||||
map[mangaId] = localManga.file
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -0,0 +1,100 @@
|
|||||||
|
package org.koitharu.kotatsu.local.data.index
|
||||||
|
|
||||||
|
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 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.util.runCatchingCancellable
|
||||||
|
import java.io.File
|
||||||
|
import javax.inject.Inject
|
||||||
|
import javax.inject.Provider
|
||||||
|
import javax.inject.Singleton
|
||||||
|
|
||||||
|
@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?> {
|
||||||
|
|
||||||
|
private val prefs = context.getSharedPreferences(PREF_NAME, Context.MODE_PRIVATE)
|
||||||
|
|
||||||
|
private var previousHash: Long
|
||||||
|
get() = prefs.getLong(KEY_HASH, 0L)
|
||||||
|
set(value) = prefs.edit { putLong(KEY_HASH, value) }
|
||||||
|
|
||||||
|
override suspend fun emit(value: LocalManga?) {
|
||||||
|
if (value != null) {
|
||||||
|
put(value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun update(): Boolean {
|
||||||
|
val newHash = computeHash()
|
||||||
|
if (newHash == previousHash) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
db.withTransaction {
|
||||||
|
val dao = db.getLocalMangaIndexDao()
|
||||||
|
dao.clear()
|
||||||
|
localMangaRepositoryProvider.get().getRawListAsFlow()
|
||||||
|
.collect { dao.upsert(it.toEntity()) }
|
||||||
|
}
|
||||||
|
previousHash = newHash
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun get(mangaId: Long): LocalManga? {
|
||||||
|
val path = db.getLocalMangaIndexDao().findPath(mangaId) ?: return null
|
||||||
|
return runCatchingCancellable {
|
||||||
|
LocalMangaInput.of(File(path)).getManga()
|
||||||
|
}.onFailure {
|
||||||
|
it.printStackTraceDebug()
|
||||||
|
}.getOrNull()
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun put(manga: LocalManga) = db.withTransaction {
|
||||||
|
mangaDataRepository.storeManga(manga.manga)
|
||||||
|
db.getLocalMangaIndexDao().upsert(manga.toEntity())
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun delete(mangaId: Long) {
|
||||||
|
db.getLocalMangaIndexDao().delete(mangaId)
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
|
||||||
|
private const val PREF_NAME = "_local_index"
|
||||||
|
private const val KEY_HASH = "hash"
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,22 @@
|
|||||||
|
package org.koitharu.kotatsu.local.data.index
|
||||||
|
|
||||||
|
import androidx.room.Dao
|
||||||
|
import androidx.room.Query
|
||||||
|
import androidx.room.Upsert
|
||||||
|
import org.koitharu.kotatsu.core.db.entity.TagEntity
|
||||||
|
|
||||||
|
@Dao
|
||||||
|
interface LocalMangaIndexDao {
|
||||||
|
|
||||||
|
@Query("SELECT path FROM local_index WHERE manga_id = :mangaId")
|
||||||
|
suspend fun findPath(mangaId: Long): String?
|
||||||
|
|
||||||
|
@Upsert
|
||||||
|
suspend fun upsert(entity: LocalMangaIndexEntity)
|
||||||
|
|
||||||
|
@Query("DELETE FROM local_index WHERE manga_id = :mangaId")
|
||||||
|
suspend fun delete(mangaId: Long)
|
||||||
|
|
||||||
|
@Query("DELETE FROM local_index")
|
||||||
|
suspend fun clear()
|
||||||
|
}
|
||||||
@ -0,0 +1,24 @@
|
|||||||
|
package org.koitharu.kotatsu.local.data.index
|
||||||
|
|
||||||
|
import androidx.room.ColumnInfo
|
||||||
|
import androidx.room.Entity
|
||||||
|
import androidx.room.ForeignKey
|
||||||
|
import androidx.room.PrimaryKey
|
||||||
|
import org.koitharu.kotatsu.core.db.entity.MangaEntity
|
||||||
|
|
||||||
|
@Entity(
|
||||||
|
tableName = "local_index",
|
||||||
|
foreignKeys = [
|
||||||
|
ForeignKey(
|
||||||
|
entity = MangaEntity::class,
|
||||||
|
parentColumns = ["manga_id"],
|
||||||
|
childColumns = ["manga_id"],
|
||||||
|
onDelete = ForeignKey.CASCADE,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
class LocalMangaIndexEntity(
|
||||||
|
@PrimaryKey(autoGenerate = false)
|
||||||
|
@ColumnInfo(name = "manga_id") val mangaId: Long,
|
||||||
|
@ColumnInfo(name = "path") val path: String,
|
||||||
|
)
|
||||||
@ -0,0 +1,20 @@
|
|||||||
|
package org.koitharu.kotatsu.local.ui
|
||||||
|
|
||||||
|
import android.content.Intent
|
||||||
|
import dagger.hilt.android.AndroidEntryPoint
|
||||||
|
import org.koitharu.kotatsu.core.ui.CoroutineIntentService
|
||||||
|
import org.koitharu.kotatsu.local.data.index.LocalMangaIndex
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
@AndroidEntryPoint
|
||||||
|
class LocalIndexUpdateService : CoroutineIntentService() {
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
lateinit var localMangaIndex: LocalMangaIndex
|
||||||
|
|
||||||
|
override suspend fun processIntent(startId: Int, intent: Intent) {
|
||||||
|
localMangaIndex.update()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onError(startId: Int, error: Throwable) = Unit
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue