From bd29c643702a7abb986cdc1eb8a0742f43b65cbd Mon Sep 17 00:00:00 2001 From: Koitharu Date: Mon, 16 May 2022 15:07:34 +0300 Subject: [PATCH] Gc database --- .../favourites/data/FavouriteCategoriesDao.kt | 4 +- .../kotatsu/favourites/data/FavouritesDao.kt | 4 +- .../kotatsu/history/data/HistoryDao.kt | 4 +- .../koitharu/kotatsu/main/ui/MainActivity.kt | 2 +- .../kotatsu/sync/domain/SyncController.kt | 57 ++++++++++++++----- .../kotatsu/sync/domain/SyncHelper.kt | 20 +++++++ .../koitharu/kotatsu/utils/CompositeMutex.kt | 3 +- 7 files changed, 73 insertions(+), 21 deletions(-) diff --git a/app/src/main/java/org/koitharu/kotatsu/favourites/data/FavouriteCategoriesDao.kt b/app/src/main/java/org/koitharu/kotatsu/favourites/data/FavouriteCategoriesDao.kt index 1e80daba5..916e04d24 100644 --- a/app/src/main/java/org/koitharu/kotatsu/favourites/data/FavouriteCategoriesDao.kt +++ b/app/src/main/java/org/koitharu/kotatsu/favourites/data/FavouriteCategoriesDao.kt @@ -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? diff --git a/app/src/main/java/org/koitharu/kotatsu/favourites/data/FavouritesDao.kt b/app/src/main/java/org/koitharu/kotatsu/favourites/data/FavouritesDao.kt index a462afeb1..ac7dd88a0 100644 --- a/app/src/main/java/org/koitharu/kotatsu/favourites/data/FavouritesDao.kt +++ b/app/src/main/java/org/koitharu/kotatsu/favourites/data/FavouritesDao.kt @@ -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) { diff --git a/app/src/main/java/org/koitharu/kotatsu/history/data/HistoryDao.kt b/app/src/main/java/org/koitharu/kotatsu/history/data/HistoryDao.kt index 2b11002f4..ba68054e4 100644 --- a/app/src/main/java/org/koitharu/kotatsu/history/data/HistoryDao.kt +++ b/app/src/main/java/org/koitharu/kotatsu/history/data/HistoryDao.kt @@ -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) diff --git a/app/src/main/java/org/koitharu/kotatsu/main/ui/MainActivity.kt b/app/src/main/java/org/koitharu/kotatsu/main/ui/MainActivity.kt index 060781d1b..3c1cebabd 100644 --- a/app/src/main/java/org/koitharu/kotatsu/main/ui/MainActivity.kt +++ b/app/src/main/java/org/koitharu/kotatsu/main/ui/MainActivity.kt @@ -434,7 +434,7 @@ class MainActivity : settings.newSources.isNotEmpty() -> NewSourcesDialogFragment.show(supportFragmentManager) } yield() - get().requestFullSync() + get().requestFullSyncAndGc(get()) } } diff --git a/app/src/main/java/org/koitharu/kotatsu/sync/domain/SyncController.kt b/app/src/main/java/org/koitharu/kotatsu/sync/domain/SyncController.kt index a4596c599..00c7d2b22 100644 --- a/app/src/main/java/org/koitharu/kotatsu/sync/domain/SyncController.kt +++ b/app/src/main/java/org/koitharu/kotatsu/sync/domain/SyncController.kt @@ -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(2) + private val defaultGcPeriod: Long // gc period if sync disabled + get() = TimeUnit.DAYS.toMillis(2) override fun onInvalidated(tables: MutableSet) { 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) + } + } } \ No newline at end of file diff --git a/app/src/main/java/org/koitharu/kotatsu/sync/domain/SyncHelper.kt b/app/src/main/java/org/koitharu/kotatsu/sync/domain/SyncHelper.kt index 1c816cab2..71d4925d8 100644 --- a/app/src/main/java/org/koitharu/kotatsu/sync/domain/SyncHelper.kt +++ b/app/src/main/java/org/koitharu/kotatsu/sync/domain/SyncHelper.kt @@ -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 { @@ -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) diff --git a/app/src/main/java/org/koitharu/kotatsu/utils/CompositeMutex.kt b/app/src/main/java/org/koitharu/kotatsu/utils/CompositeMutex.kt index e66355588..61ca5170c 100644 --- a/app/src/main/java/org/koitharu/kotatsu/utils/CompositeMutex.kt +++ b/app/src/main/java/org/koitharu/kotatsu/utils/CompositeMutex.kt @@ -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 : Set { - private val data = HashMap>>() + private val data = ArrayMap>>() private val mutex = Mutex() override val size: Int