Gc database

This commit is contained in:
Koitharu
2022-05-16 15:07:34 +03:00
parent a9f3ab259a
commit bd29c64370
7 changed files with 73 additions and 21 deletions

View File

@@ -36,8 +36,8 @@ abstract class FavouriteCategoriesDao {
@Query("UPDATE favourite_categories SET sort_key = :sortKey WHERE category_id = :id")
abstract suspend fun updateSortKey(id: Long, sortKey: Int)
@Query("DELETE FROM favourite_categories WHERE deleted_at != 0")
abstract suspend fun gc()
@Query("DELETE FROM favourite_categories WHERE deleted_at != 0 AND deleted_at < :maxDeletionTime")
abstract suspend fun gc(maxDeletionTime: Long)
@Query("SELECT MAX(sort_key) FROM favourite_categories WHERE deleted_at = 0")
protected abstract suspend fun getMaxSortKey(): Int?

View File

@@ -91,8 +91,8 @@ abstract class FavouritesDao {
suspend fun recover(categoryId: Long, mangaId: Long) = delete(categoryId, mangaId, 0L)
@Query("DELETE FROM favourites WHERE deleted_at != 0")
abstract suspend fun gc()
@Query("DELETE FROM favourites WHERE deleted_at != 0 AND deleted_at < :maxDeletionTime")
abstract suspend fun gc(maxDeletionTime: Long)
@Transaction
open suspend fun upsert(entity: FavouriteEntity) {

View File

@@ -66,8 +66,8 @@ abstract class HistoryDao {
suspend fun recover(mangaId: Long) = delete(mangaId, 0L)
@Query("DELETE FROM history WHERE deleted_at != 0")
abstract suspend fun gc()
@Query("DELETE FROM history WHERE deleted_at != 0 AND deleted_at < :maxDeletionTime")
abstract suspend fun gc(maxDeletionTime: Long)
suspend fun update(entity: HistoryEntity) =
update(entity.mangaId, entity.page, entity.chapterId, entity.scroll, entity.updatedAt)

View File

@@ -434,7 +434,7 @@ class MainActivity :
settings.newSources.isNotEmpty() -> NewSourcesDialogFragment.show(supportFragmentManager)
}
yield()
get<SyncController>().requestFullSync()
get<SyncController>().requestFullSyncAndGc(get())
}
}

View File

@@ -7,9 +7,13 @@ import android.content.Context
import android.os.Bundle
import android.util.ArrayMap
import androidx.room.InvalidationTracker
import androidx.room.withTransaction
import kotlinx.coroutines.*
import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock
import org.koitharu.kotatsu.BuildConfig
import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.core.db.MangaDatabase
import org.koitharu.kotatsu.core.db.TABLE_FAVOURITES
import org.koitharu.kotatsu.core.db.TABLE_FAVOURITE_CATEGORIES
import org.koitharu.kotatsu.core.db.TABLE_HISTORY
@@ -27,7 +31,10 @@ class SyncController(
} else {
TimeUnit.MINUTES.toMillis(4)
}
private val mutex = Mutex()
private val jobs = ArrayMap<String, Job>(2)
private val defaultGcPeriod: Long // gc period if sync disabled
get() = TimeUnit.DAYS.toMillis(2)
override fun onInvalidated(tables: MutableSet<String>) {
requestSync(
@@ -48,36 +55,49 @@ class SyncController(
}
suspend fun requestFullSync() = withContext(Dispatchers.Default) {
requestSyncImpl(favourites = true, history = true)
requestSyncImpl(favourites = true, history = true, db = null)
}
suspend fun requestFullSyncAndGc(database: MangaDatabase) = withContext(Dispatchers.Default) {
requestSyncImpl(favourites = true, history = true, db = database)
}
private fun requestSync(favourites: Boolean, history: Boolean) = processLifecycleScope.launch(Dispatchers.Default) {
requestSyncImpl(favourites, history)
requestSyncImpl(favourites = favourites, history = history, db = null)
}
@Synchronized
private fun requestSyncImpl(favourites: Boolean, history: Boolean) {
private suspend fun requestSyncImpl(favourites: Boolean, history: Boolean, db: MangaDatabase?) = mutex.withLock {
if (!favourites && !history) {
return
}
val account = peekAccount() ?: return
if (!ContentResolver.getMasterSyncAutomatically()) {
val account = peekAccount()
if (account == null || !ContentResolver.getMasterSyncAutomatically()) {
db?.gc(favourites, history)
return
}
var gcHistory = false
var gcFavourites = false
if (favourites) {
scheduleSync(account, AUTHORITY_FAVOURITES)
if (ContentResolver.getSyncAutomatically(account, AUTHORITY_FAVOURITES)) {
scheduleSync(account, AUTHORITY_FAVOURITES)
} else {
gcFavourites = true
}
}
if (history) {
scheduleSync(account, AUTHORITY_HISTORY)
if (ContentResolver.getSyncAutomatically(account, AUTHORITY_HISTORY)) {
scheduleSync(account, AUTHORITY_HISTORY)
} else {
gcHistory = true
}
}
if (db != null && (gcHistory || gcFavourites)) {
db.gc(gcFavourites, gcHistory)
}
}
private fun scheduleSync(account: Account, authority: String) {
if (
!ContentResolver.getSyncAutomatically(account, AUTHORITY_FAVOURITES) ||
ContentResolver.isSyncActive(account, authority) ||
ContentResolver.isSyncPending(account, authority)
) {
if (ContentResolver.isSyncActive(account, authority) || ContentResolver.isSyncPending(account, authority)) {
return
}
val job = jobs[authority]
@@ -105,4 +125,15 @@ class SyncController(
private fun peekAccount(): Account? {
return am.getAccountsByType(accountType).firstOrNull()
}
private suspend fun MangaDatabase.gc(favourites: Boolean, history: Boolean) = withTransaction {
val deletedAt = System.currentTimeMillis() - defaultGcPeriod
if (history) {
historyDao.gc(deletedAt)
}
if (favourites) {
favouritesDao.gc(deletedAt)
favouriteCategoriesDao.gc(deletedAt)
}
}
}

View File

@@ -21,6 +21,7 @@ import org.koitharu.kotatsu.utils.ext.parseJsonOrNull
import org.koitharu.kotatsu.utils.ext.toContentValues
import org.koitharu.kotatsu.utils.ext.toJson
import org.koitharu.kotatsu.utils.ext.toRequestBody
import java.util.concurrent.TimeUnit
const val AUTHORITY_HISTORY = "org.koitharu.kotatsu.history"
const val AUTHORITY_FAVOURITES = "org.koitharu.kotatsu.favourites"
@@ -43,6 +44,8 @@ class SyncHelper(
.addInterceptor(GZipInterceptor())
.build()
private val baseUrl = context.getString(R.string.url_sync_server)
private val defaultGcPeriod: Long // gc period if sync enabled
get() = TimeUnit.DAYS.toMillis(4)
fun syncFavourites(syncResult: SyncResult) {
val data = JSONObject()
@@ -61,6 +64,7 @@ class SyncHelper(
val favouritesResult = upsertFavourites(response.getJSONArray(TABLE_FAVOURITES), timestamp)
syncResult.stats.numDeletes += favouritesResult.first().count?.toLong() ?: 0L
syncResult.stats.numInserts += favouritesResult.drop(1).sumOf { it.count?.toLong() ?: 0L }
gcFavourites()
}
fun syncHistory(syncResult: SyncResult) {
@@ -78,6 +82,7 @@ class SyncHelper(
)
syncResult.stats.numDeletes += result.first().count?.toLong() ?: 0L
syncResult.stats.numInserts += result.drop(1).sumOf { it.count?.toLong() ?: 0L }
gcHistory()
}
private fun upsertHistory(json: JSONArray, timestamp: Long): Array<ContentProviderResult> {
@@ -238,6 +243,21 @@ class SyncHelper(
return requireNotNull(tag)
}
private fun gcFavourites() {
val deletedAt = System.currentTimeMillis() - defaultGcPeriod
val selection = "deleted_at != 0 AND deleted_at < ?"
val args = arrayOf(deletedAt.toString())
provider.delete(uri(AUTHORITY_FAVOURITES, TABLE_FAVOURITES), selection, args)
provider.delete(uri(AUTHORITY_FAVOURITES, TABLE_FAVOURITE_CATEGORIES), selection, args)
}
private fun gcHistory() {
val deletedAt = System.currentTimeMillis() - defaultGcPeriod
val selection = "deleted_at != 0 AND deleted_at < ?"
val args = arrayOf(deletedAt.toString())
provider.delete(uri(AUTHORITY_HISTORY, TABLE_HISTORY), selection, args)
}
private fun ContentProviderClient.query(authority: String, table: String): Cursor {
val uri = uri(authority, table)
return query(uri, null, null, null, null)

View File

@@ -1,5 +1,6 @@
package org.koitharu.kotatsu.utils
import android.util.ArrayMap
import kotlinx.coroutines.CancellableContinuation
import kotlinx.coroutines.suspendCancellableCoroutine
import kotlinx.coroutines.sync.Mutex
@@ -9,7 +10,7 @@ import kotlin.coroutines.resume
class CompositeMutex<T : Any> : Set<T> {
private val data = HashMap<T, MutableList<CancellableContinuation<Unit>>>()
private val data = ArrayMap<T, MutableList<CancellableContinuation<Unit>>>()
private val mutex = Mutex()
override val size: Int