Gc database
This commit is contained in:
@@ -36,8 +36,8 @@ abstract class FavouriteCategoriesDao {
|
|||||||
@Query("UPDATE favourite_categories SET sort_key = :sortKey WHERE category_id = :id")
|
@Query("UPDATE favourite_categories SET sort_key = :sortKey WHERE category_id = :id")
|
||||||
abstract suspend fun updateSortKey(id: Long, sortKey: Int)
|
abstract suspend fun updateSortKey(id: Long, sortKey: Int)
|
||||||
|
|
||||||
@Query("DELETE FROM favourite_categories WHERE deleted_at != 0")
|
@Query("DELETE FROM favourite_categories WHERE deleted_at != 0 AND deleted_at < :maxDeletionTime")
|
||||||
abstract suspend fun gc()
|
abstract suspend fun gc(maxDeletionTime: Long)
|
||||||
|
|
||||||
@Query("SELECT MAX(sort_key) FROM favourite_categories WHERE deleted_at = 0")
|
@Query("SELECT MAX(sort_key) FROM favourite_categories WHERE deleted_at = 0")
|
||||||
protected abstract suspend fun getMaxSortKey(): Int?
|
protected abstract suspend fun getMaxSortKey(): Int?
|
||||||
|
|||||||
@@ -91,8 +91,8 @@ abstract class FavouritesDao {
|
|||||||
|
|
||||||
suspend fun recover(categoryId: Long, mangaId: Long) = delete(categoryId, mangaId, 0L)
|
suspend fun recover(categoryId: Long, mangaId: Long) = delete(categoryId, mangaId, 0L)
|
||||||
|
|
||||||
@Query("DELETE FROM favourites WHERE deleted_at != 0")
|
@Query("DELETE FROM favourites WHERE deleted_at != 0 AND deleted_at < :maxDeletionTime")
|
||||||
abstract suspend fun gc()
|
abstract suspend fun gc(maxDeletionTime: Long)
|
||||||
|
|
||||||
@Transaction
|
@Transaction
|
||||||
open suspend fun upsert(entity: FavouriteEntity) {
|
open suspend fun upsert(entity: FavouriteEntity) {
|
||||||
|
|||||||
@@ -66,8 +66,8 @@ abstract class HistoryDao {
|
|||||||
|
|
||||||
suspend fun recover(mangaId: Long) = delete(mangaId, 0L)
|
suspend fun recover(mangaId: Long) = delete(mangaId, 0L)
|
||||||
|
|
||||||
@Query("DELETE FROM history WHERE deleted_at != 0")
|
@Query("DELETE FROM history WHERE deleted_at != 0 AND deleted_at < :maxDeletionTime")
|
||||||
abstract suspend fun gc()
|
abstract suspend fun gc(maxDeletionTime: Long)
|
||||||
|
|
||||||
suspend fun update(entity: HistoryEntity) =
|
suspend fun update(entity: HistoryEntity) =
|
||||||
update(entity.mangaId, entity.page, entity.chapterId, entity.scroll, entity.updatedAt)
|
update(entity.mangaId, entity.page, entity.chapterId, entity.scroll, entity.updatedAt)
|
||||||
|
|||||||
@@ -434,7 +434,7 @@ class MainActivity :
|
|||||||
settings.newSources.isNotEmpty() -> NewSourcesDialogFragment.show(supportFragmentManager)
|
settings.newSources.isNotEmpty() -> NewSourcesDialogFragment.show(supportFragmentManager)
|
||||||
}
|
}
|
||||||
yield()
|
yield()
|
||||||
get<SyncController>().requestFullSync()
|
get<SyncController>().requestFullSyncAndGc(get())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -7,9 +7,13 @@ import android.content.Context
|
|||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.util.ArrayMap
|
import android.util.ArrayMap
|
||||||
import androidx.room.InvalidationTracker
|
import androidx.room.InvalidationTracker
|
||||||
|
import androidx.room.withTransaction
|
||||||
import kotlinx.coroutines.*
|
import kotlinx.coroutines.*
|
||||||
|
import kotlinx.coroutines.sync.Mutex
|
||||||
|
import kotlinx.coroutines.sync.withLock
|
||||||
import org.koitharu.kotatsu.BuildConfig
|
import org.koitharu.kotatsu.BuildConfig
|
||||||
import org.koitharu.kotatsu.R
|
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_FAVOURITES
|
||||||
import org.koitharu.kotatsu.core.db.TABLE_FAVOURITE_CATEGORIES
|
import org.koitharu.kotatsu.core.db.TABLE_FAVOURITE_CATEGORIES
|
||||||
import org.koitharu.kotatsu.core.db.TABLE_HISTORY
|
import org.koitharu.kotatsu.core.db.TABLE_HISTORY
|
||||||
@@ -27,7 +31,10 @@ class SyncController(
|
|||||||
} else {
|
} else {
|
||||||
TimeUnit.MINUTES.toMillis(4)
|
TimeUnit.MINUTES.toMillis(4)
|
||||||
}
|
}
|
||||||
|
private val mutex = Mutex()
|
||||||
private val jobs = ArrayMap<String, Job>(2)
|
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>) {
|
override fun onInvalidated(tables: MutableSet<String>) {
|
||||||
requestSync(
|
requestSync(
|
||||||
@@ -48,36 +55,49 @@ class SyncController(
|
|||||||
}
|
}
|
||||||
|
|
||||||
suspend fun requestFullSync() = withContext(Dispatchers.Default) {
|
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) {
|
private fun requestSync(favourites: Boolean, history: Boolean) = processLifecycleScope.launch(Dispatchers.Default) {
|
||||||
requestSyncImpl(favourites, history)
|
requestSyncImpl(favourites = favourites, history = history, db = null)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Synchronized
|
private suspend fun requestSyncImpl(favourites: Boolean, history: Boolean, db: MangaDatabase?) = mutex.withLock {
|
||||||
private fun requestSyncImpl(favourites: Boolean, history: Boolean) {
|
|
||||||
if (!favourites && !history) {
|
if (!favourites && !history) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
val account = peekAccount() ?: return
|
val account = peekAccount()
|
||||||
if (!ContentResolver.getMasterSyncAutomatically()) {
|
if (account == null || !ContentResolver.getMasterSyncAutomatically()) {
|
||||||
|
db?.gc(favourites, history)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
var gcHistory = false
|
||||||
|
var gcFavourites = false
|
||||||
if (favourites) {
|
if (favourites) {
|
||||||
scheduleSync(account, AUTHORITY_FAVOURITES)
|
if (ContentResolver.getSyncAutomatically(account, AUTHORITY_FAVOURITES)) {
|
||||||
|
scheduleSync(account, AUTHORITY_FAVOURITES)
|
||||||
|
} else {
|
||||||
|
gcFavourites = true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (history) {
|
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) {
|
private fun scheduleSync(account: Account, authority: String) {
|
||||||
if (
|
if (ContentResolver.isSyncActive(account, authority) || ContentResolver.isSyncPending(account, authority)) {
|
||||||
!ContentResolver.getSyncAutomatically(account, AUTHORITY_FAVOURITES) ||
|
|
||||||
ContentResolver.isSyncActive(account, authority) ||
|
|
||||||
ContentResolver.isSyncPending(account, authority)
|
|
||||||
) {
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
val job = jobs[authority]
|
val job = jobs[authority]
|
||||||
@@ -105,4 +125,15 @@ class SyncController(
|
|||||||
private fun peekAccount(): Account? {
|
private fun peekAccount(): Account? {
|
||||||
return am.getAccountsByType(accountType).firstOrNull()
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -21,6 +21,7 @@ import org.koitharu.kotatsu.utils.ext.parseJsonOrNull
|
|||||||
import org.koitharu.kotatsu.utils.ext.toContentValues
|
import org.koitharu.kotatsu.utils.ext.toContentValues
|
||||||
import org.koitharu.kotatsu.utils.ext.toJson
|
import org.koitharu.kotatsu.utils.ext.toJson
|
||||||
import org.koitharu.kotatsu.utils.ext.toRequestBody
|
import org.koitharu.kotatsu.utils.ext.toRequestBody
|
||||||
|
import java.util.concurrent.TimeUnit
|
||||||
|
|
||||||
const val AUTHORITY_HISTORY = "org.koitharu.kotatsu.history"
|
const val AUTHORITY_HISTORY = "org.koitharu.kotatsu.history"
|
||||||
const val AUTHORITY_FAVOURITES = "org.koitharu.kotatsu.favourites"
|
const val AUTHORITY_FAVOURITES = "org.koitharu.kotatsu.favourites"
|
||||||
@@ -43,6 +44,8 @@ class SyncHelper(
|
|||||||
.addInterceptor(GZipInterceptor())
|
.addInterceptor(GZipInterceptor())
|
||||||
.build()
|
.build()
|
||||||
private val baseUrl = context.getString(R.string.url_sync_server)
|
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) {
|
fun syncFavourites(syncResult: SyncResult) {
|
||||||
val data = JSONObject()
|
val data = JSONObject()
|
||||||
@@ -61,6 +64,7 @@ class SyncHelper(
|
|||||||
val favouritesResult = upsertFavourites(response.getJSONArray(TABLE_FAVOURITES), timestamp)
|
val favouritesResult = upsertFavourites(response.getJSONArray(TABLE_FAVOURITES), timestamp)
|
||||||
syncResult.stats.numDeletes += favouritesResult.first().count?.toLong() ?: 0L
|
syncResult.stats.numDeletes += favouritesResult.first().count?.toLong() ?: 0L
|
||||||
syncResult.stats.numInserts += favouritesResult.drop(1).sumOf { it.count?.toLong() ?: 0L }
|
syncResult.stats.numInserts += favouritesResult.drop(1).sumOf { it.count?.toLong() ?: 0L }
|
||||||
|
gcFavourites()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun syncHistory(syncResult: SyncResult) {
|
fun syncHistory(syncResult: SyncResult) {
|
||||||
@@ -78,6 +82,7 @@ class SyncHelper(
|
|||||||
)
|
)
|
||||||
syncResult.stats.numDeletes += result.first().count?.toLong() ?: 0L
|
syncResult.stats.numDeletes += result.first().count?.toLong() ?: 0L
|
||||||
syncResult.stats.numInserts += result.drop(1).sumOf { it.count?.toLong() ?: 0L }
|
syncResult.stats.numInserts += result.drop(1).sumOf { it.count?.toLong() ?: 0L }
|
||||||
|
gcHistory()
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun upsertHistory(json: JSONArray, timestamp: Long): Array<ContentProviderResult> {
|
private fun upsertHistory(json: JSONArray, timestamp: Long): Array<ContentProviderResult> {
|
||||||
@@ -238,6 +243,21 @@ class SyncHelper(
|
|||||||
return requireNotNull(tag)
|
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 {
|
private fun ContentProviderClient.query(authority: String, table: String): Cursor {
|
||||||
val uri = uri(authority, table)
|
val uri = uri(authority, table)
|
||||||
return query(uri, null, null, null, null)
|
return query(uri, null, null, null, null)
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
package org.koitharu.kotatsu.utils
|
package org.koitharu.kotatsu.utils
|
||||||
|
|
||||||
|
import android.util.ArrayMap
|
||||||
import kotlinx.coroutines.CancellableContinuation
|
import kotlinx.coroutines.CancellableContinuation
|
||||||
import kotlinx.coroutines.suspendCancellableCoroutine
|
import kotlinx.coroutines.suspendCancellableCoroutine
|
||||||
import kotlinx.coroutines.sync.Mutex
|
import kotlinx.coroutines.sync.Mutex
|
||||||
@@ -9,7 +10,7 @@ import kotlin.coroutines.resume
|
|||||||
|
|
||||||
class CompositeMutex<T : Any> : Set<T> {
|
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()
|
private val mutex = Mutex()
|
||||||
|
|
||||||
override val size: Int
|
override val size: Int
|
||||||
|
|||||||
Reference in New Issue
Block a user