From dfa413da6f2962de3965b26944faf4a5f94c4701 Mon Sep 17 00:00:00 2001 From: Koitharu Date: Mon, 18 Jul 2022 09:55:49 +0300 Subject: [PATCH] Observe database updates using InvalidationTracker --- .../java/org/koitharu/kotatsu/KotatsuApp.kt | 29 ++++++++++----- .../org/koitharu/kotatsu/core/db/Tables.kt | 9 +++++ .../kotatsu/core/db/entity/MangaEntity.kt | 5 +-- .../kotatsu/core/db/entity/MangaTagsEntity.kt | 6 ++-- .../kotatsu/core/db/entity/TagEntity.kt | 5 +-- .../data/FavouriteCategoryEntity.kt | 3 +- .../favourites/data/FavouriteEntity.kt | 7 ++-- .../kotatsu/history/data/HistoryEntity.kt | 3 +- .../org/koitharu/kotatsu/main/MainModule.kt | 6 ++-- .../kotatsu/settings/SettingsModule.kt | 7 ++++ .../kotatsu/settings/backup/BackupObserver.kt | 22 ++++++++++++ .../kotatsu/widget/AppWidgetModule.kt | 5 +++ .../koitharu/kotatsu/widget/WidgetUpdater.kt | 36 +++++++------------ 13 files changed, 99 insertions(+), 44 deletions(-) create mode 100644 app/src/main/java/org/koitharu/kotatsu/core/db/Tables.kt create mode 100644 app/src/main/java/org/koitharu/kotatsu/settings/backup/BackupObserver.kt diff --git a/app/src/main/java/org/koitharu/kotatsu/KotatsuApp.kt b/app/src/main/java/org/koitharu/kotatsu/KotatsuApp.kt index d7f25396e..fcbc79742 100644 --- a/app/src/main/java/org/koitharu/kotatsu/KotatsuApp.kt +++ b/app/src/main/java/org/koitharu/kotatsu/KotatsuApp.kt @@ -5,16 +5,18 @@ import android.content.Context import android.os.StrictMode import androidx.appcompat.app.AppCompatDelegate import androidx.fragment.app.strictmode.FragmentStrictMode +import androidx.room.InvalidationTracker import org.acra.ReportField import org.acra.config.dialog import org.acra.config.mailSender import org.acra.data.StringFormat import org.acra.ktx.initAcra import org.koin.android.ext.android.get +import org.koin.android.ext.android.getKoin import org.koin.android.ext.koin.androidContext import org.koin.core.context.startKoin -import org.koitharu.kotatsu.base.ui.util.ActivityRecreationHandle import org.koitharu.kotatsu.bookmarks.bookmarksModule +import org.koitharu.kotatsu.core.db.MangaDatabase import org.koitharu.kotatsu.core.db.databaseModule import org.koitharu.kotatsu.core.github.githubModule import org.koitharu.kotatsu.core.network.networkModule @@ -27,7 +29,6 @@ import org.koitharu.kotatsu.local.data.PagesCache import org.koitharu.kotatsu.local.domain.LocalMangaRepository import org.koitharu.kotatsu.local.localModule import org.koitharu.kotatsu.main.mainModule -import org.koitharu.kotatsu.main.ui.protect.AppProtectHelper import org.koitharu.kotatsu.parsers.MangaLoaderContext import org.koitharu.kotatsu.reader.readerModule import org.koitharu.kotatsu.remotelist.remoteListModule @@ -36,7 +37,6 @@ import org.koitharu.kotatsu.search.searchModule import org.koitharu.kotatsu.settings.settingsModule import org.koitharu.kotatsu.suggestions.suggestionsModule import org.koitharu.kotatsu.tracker.trackerModule -import org.koitharu.kotatsu.widget.WidgetUpdater import org.koitharu.kotatsu.widget.appWidgetModule class KotatsuApp : Application() { @@ -48,11 +48,8 @@ class KotatsuApp : Application() { } initKoin() AppCompatDelegate.setDefaultNightMode(get().theme) - registerActivityLifecycleCallbacks(get()) - registerActivityLifecycleCallbacks(get()) - val widgetUpdater = WidgetUpdater(applicationContext) - widgetUpdater.subscribeToFavourites(get()) - widgetUpdater.subscribeToHistory(get()) + setupActivityLifecycleCallbacks() + setupDatabaseObservers() } private fun initKoin() { @@ -112,6 +109,22 @@ class KotatsuApp : Application() { } } + private fun setupDatabaseObservers() { + val observers = getKoin().getAll() + val database = get() + val tracker = database.invalidationTracker + observers.forEach { + tracker.addObserver(it) + } + } + + private fun setupActivityLifecycleCallbacks() { + val callbacks = getKoin().getAll() + callbacks.forEach { + registerActivityLifecycleCallbacks(it) + } + } + private fun enableStrictMode() { StrictMode.setThreadPolicy( StrictMode.ThreadPolicy.Builder() diff --git a/app/src/main/java/org/koitharu/kotatsu/core/db/Tables.kt b/app/src/main/java/org/koitharu/kotatsu/core/db/Tables.kt new file mode 100644 index 000000000..28920a626 --- /dev/null +++ b/app/src/main/java/org/koitharu/kotatsu/core/db/Tables.kt @@ -0,0 +1,9 @@ +package org.koitharu.kotatsu.core.db + + +const val TABLE_FAVOURITES = "favourites" +const val TABLE_MANGA = "manga" +const val TABLE_TAGS = "tags" +const val TABLE_FAVOURITE_CATEGORIES = "favourite_categories" +const val TABLE_HISTORY = "history" +const val TABLE_MANGA_TAGS = "manga_tags" diff --git a/app/src/main/java/org/koitharu/kotatsu/core/db/entity/MangaEntity.kt b/app/src/main/java/org/koitharu/kotatsu/core/db/entity/MangaEntity.kt index bfe64375f..36e534c71 100644 --- a/app/src/main/java/org/koitharu/kotatsu/core/db/entity/MangaEntity.kt +++ b/app/src/main/java/org/koitharu/kotatsu/core/db/entity/MangaEntity.kt @@ -3,8 +3,9 @@ package org.koitharu.kotatsu.core.db.entity import androidx.room.ColumnInfo import androidx.room.Entity import androidx.room.PrimaryKey +import org.koitharu.kotatsu.core.db.TABLE_MANGA -@Entity(tableName = "manga") +@Entity(tableName = TABLE_MANGA) data class MangaEntity( @PrimaryKey(autoGenerate = false) @ColumnInfo(name = "manga_id") val id: Long, @@ -18,5 +19,5 @@ data class MangaEntity( @ColumnInfo(name = "large_cover_url") val largeCoverUrl: String?, @ColumnInfo(name = "state") val state: String?, @ColumnInfo(name = "author") val author: String?, - @ColumnInfo(name = "source") val source: String + @ColumnInfo(name = "source") val source: String, ) \ No newline at end of file diff --git a/app/src/main/java/org/koitharu/kotatsu/core/db/entity/MangaTagsEntity.kt b/app/src/main/java/org/koitharu/kotatsu/core/db/entity/MangaTagsEntity.kt index d3ee401a6..e7a59c5d0 100644 --- a/app/src/main/java/org/koitharu/kotatsu/core/db/entity/MangaTagsEntity.kt +++ b/app/src/main/java/org/koitharu/kotatsu/core/db/entity/MangaTagsEntity.kt @@ -3,9 +3,11 @@ package org.koitharu.kotatsu.core.db.entity import androidx.room.ColumnInfo import androidx.room.Entity import androidx.room.ForeignKey +import org.koitharu.kotatsu.core.db.TABLE_MANGA_TAGS @Entity( - tableName = "manga_tags", primaryKeys = ["manga_id", "tag_id"], + tableName = TABLE_MANGA_TAGS, + primaryKeys = ["manga_id", "tag_id"], foreignKeys = [ ForeignKey( entity = MangaEntity::class, @@ -23,5 +25,5 @@ import androidx.room.ForeignKey ) class MangaTagsEntity( @ColumnInfo(name = "manga_id", index = true) val mangaId: Long, - @ColumnInfo(name = "tag_id", index = true) val tagId: Long + @ColumnInfo(name = "tag_id", index = true) val tagId: Long, ) \ No newline at end of file diff --git a/app/src/main/java/org/koitharu/kotatsu/core/db/entity/TagEntity.kt b/app/src/main/java/org/koitharu/kotatsu/core/db/entity/TagEntity.kt index 8c5e927c4..7f147c992 100644 --- a/app/src/main/java/org/koitharu/kotatsu/core/db/entity/TagEntity.kt +++ b/app/src/main/java/org/koitharu/kotatsu/core/db/entity/TagEntity.kt @@ -3,12 +3,13 @@ package org.koitharu.kotatsu.core.db.entity import androidx.room.ColumnInfo import androidx.room.Entity import androidx.room.PrimaryKey +import org.koitharu.kotatsu.core.db.TABLE_TAGS -@Entity(tableName = "tags") +@Entity(tableName = TABLE_TAGS) data class TagEntity( @PrimaryKey(autoGenerate = false) @ColumnInfo(name = "tag_id") val id: Long, @ColumnInfo(name = "title") val title: String, @ColumnInfo(name = "key") val key: String, - @ColumnInfo(name = "source") val source: String + @ColumnInfo(name = "source") val source: String, ) \ No newline at end of file diff --git a/app/src/main/java/org/koitharu/kotatsu/favourites/data/FavouriteCategoryEntity.kt b/app/src/main/java/org/koitharu/kotatsu/favourites/data/FavouriteCategoryEntity.kt index 5cc62dbae..4f4f594e9 100644 --- a/app/src/main/java/org/koitharu/kotatsu/favourites/data/FavouriteCategoryEntity.kt +++ b/app/src/main/java/org/koitharu/kotatsu/favourites/data/FavouriteCategoryEntity.kt @@ -3,8 +3,9 @@ package org.koitharu.kotatsu.favourites.data import androidx.room.ColumnInfo import androidx.room.Entity import androidx.room.PrimaryKey +import org.koitharu.kotatsu.core.db.TABLE_FAVOURITE_CATEGORIES -@Entity(tableName = "favourite_categories") +@Entity(tableName = TABLE_FAVOURITE_CATEGORIES) data class FavouriteCategoryEntity( @PrimaryKey(autoGenerate = true) @ColumnInfo(name = "category_id") val categoryId: Int, diff --git a/app/src/main/java/org/koitharu/kotatsu/favourites/data/FavouriteEntity.kt b/app/src/main/java/org/koitharu/kotatsu/favourites/data/FavouriteEntity.kt index 95ae66e87..860465814 100644 --- a/app/src/main/java/org/koitharu/kotatsu/favourites/data/FavouriteEntity.kt +++ b/app/src/main/java/org/koitharu/kotatsu/favourites/data/FavouriteEntity.kt @@ -3,10 +3,13 @@ package org.koitharu.kotatsu.favourites.data import androidx.room.ColumnInfo import androidx.room.Entity import androidx.room.ForeignKey +import org.koitharu.kotatsu.core.db.TABLE_FAVOURITES import org.koitharu.kotatsu.core.db.entity.MangaEntity @Entity( - tableName = "favourites", primaryKeys = ["manga_id", "category_id"], foreignKeys = [ + tableName = TABLE_FAVOURITES, + primaryKeys = ["manga_id", "category_id"], + foreignKeys = [ ForeignKey( entity = MangaEntity::class, parentColumns = ["manga_id"], @@ -24,5 +27,5 @@ import org.koitharu.kotatsu.core.db.entity.MangaEntity data class FavouriteEntity( @ColumnInfo(name = "manga_id", index = true) val mangaId: Long, @ColumnInfo(name = "category_id", index = true) val categoryId: Long, - @ColumnInfo(name = "created_at") val createdAt: Long + @ColumnInfo(name = "created_at") val createdAt: Long, ) \ No newline at end of file diff --git a/app/src/main/java/org/koitharu/kotatsu/history/data/HistoryEntity.kt b/app/src/main/java/org/koitharu/kotatsu/history/data/HistoryEntity.kt index 4842a36a7..38e2daa7b 100644 --- a/app/src/main/java/org/koitharu/kotatsu/history/data/HistoryEntity.kt +++ b/app/src/main/java/org/koitharu/kotatsu/history/data/HistoryEntity.kt @@ -4,10 +4,11 @@ import androidx.room.ColumnInfo import androidx.room.Entity import androidx.room.ForeignKey import androidx.room.PrimaryKey +import org.koitharu.kotatsu.core.db.TABLE_HISTORY import org.koitharu.kotatsu.core.db.entity.MangaEntity @Entity( - tableName = "history", + tableName = TABLE_HISTORY, foreignKeys = [ ForeignKey( entity = MangaEntity::class, diff --git a/app/src/main/java/org/koitharu/kotatsu/main/MainModule.kt b/app/src/main/java/org/koitharu/kotatsu/main/MainModule.kt index 7bcca45d6..c85c04f26 100644 --- a/app/src/main/java/org/koitharu/kotatsu/main/MainModule.kt +++ b/app/src/main/java/org/koitharu/kotatsu/main/MainModule.kt @@ -1,7 +1,9 @@ package org.koitharu.kotatsu.main +import android.app.Application import org.koin.android.ext.koin.androidContext import org.koin.androidx.viewmodel.dsl.viewModel +import org.koin.dsl.bind import org.koin.dsl.module import org.koitharu.kotatsu.base.ui.util.ActivityRecreationHandle import org.koitharu.kotatsu.core.os.ShortcutsRepository @@ -11,8 +13,8 @@ import org.koitharu.kotatsu.main.ui.protect.ProtectViewModel val mainModule get() = module { - single { AppProtectHelper(get()) } - single { ActivityRecreationHandle() } + single { AppProtectHelper(get()) } bind Application.ActivityLifecycleCallbacks::class + single { ActivityRecreationHandle() } bind Application.ActivityLifecycleCallbacks::class factory { ShortcutsRepository(androidContext(), get(), get(), get()) } viewModel { MainViewModel(get(), get()) } viewModel { ProtectViewModel(get(), get()) } diff --git a/app/src/main/java/org/koitharu/kotatsu/settings/SettingsModule.kt b/app/src/main/java/org/koitharu/kotatsu/settings/SettingsModule.kt index a4fff5d93..ca3fd8a2d 100644 --- a/app/src/main/java/org/koitharu/kotatsu/settings/SettingsModule.kt +++ b/app/src/main/java/org/koitharu/kotatsu/settings/SettingsModule.kt @@ -1,11 +1,14 @@ package org.koitharu.kotatsu.settings import android.net.Uri +import android.os.Build +import androidx.room.InvalidationTracker import org.koin.android.ext.koin.androidContext import org.koin.androidx.viewmodel.dsl.viewModel import org.koin.dsl.module import org.koitharu.kotatsu.core.backup.BackupRepository import org.koitharu.kotatsu.core.prefs.AppSettings +import org.koitharu.kotatsu.settings.backup.BackupObserver import org.koitharu.kotatsu.settings.backup.BackupViewModel import org.koitharu.kotatsu.settings.backup.RestoreViewModel import org.koitharu.kotatsu.settings.newsources.NewSourcesViewModel @@ -16,6 +19,10 @@ import org.koitharu.kotatsu.settings.sources.SourcesSettingsViewModel val settingsModule get() = module { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { + single { BackupObserver(androidContext()) } + } + factory { BackupRepository(get()) } single(createdAtStart = true) { AppSettings(androidContext()) } diff --git a/app/src/main/java/org/koitharu/kotatsu/settings/backup/BackupObserver.kt b/app/src/main/java/org/koitharu/kotatsu/settings/backup/BackupObserver.kt new file mode 100644 index 000000000..807e63e56 --- /dev/null +++ b/app/src/main/java/org/koitharu/kotatsu/settings/backup/BackupObserver.kt @@ -0,0 +1,22 @@ +package org.koitharu.kotatsu.settings.backup + +import android.app.backup.BackupManager +import android.content.Context +import android.os.Build +import androidx.annotation.RequiresApi +import androidx.room.InvalidationTracker +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 + +@RequiresApi(Build.VERSION_CODES.M) +class BackupObserver( + context: Context, +) : InvalidationTracker.Observer(arrayOf(TABLE_HISTORY, TABLE_FAVOURITES, TABLE_FAVOURITE_CATEGORIES)) { + + private val backupManager = BackupManager(context) + + override fun onInvalidated(tables: MutableSet) { + backupManager.dataChanged() + } +} \ No newline at end of file diff --git a/app/src/main/java/org/koitharu/kotatsu/widget/AppWidgetModule.kt b/app/src/main/java/org/koitharu/kotatsu/widget/AppWidgetModule.kt index 3023da8b0..fda8aba02 100644 --- a/app/src/main/java/org/koitharu/kotatsu/widget/AppWidgetModule.kt +++ b/app/src/main/java/org/koitharu/kotatsu/widget/AppWidgetModule.kt @@ -1,10 +1,15 @@ package org.koitharu.kotatsu.widget +import androidx.room.InvalidationTracker +import org.koin.android.ext.koin.androidContext import org.koin.androidx.viewmodel.dsl.viewModel import org.koin.dsl.module import org.koitharu.kotatsu.widget.shelf.ShelfConfigViewModel val appWidgetModule get() = module { + + single { WidgetUpdater(androidContext()) } + viewModel { ShelfConfigViewModel(get()) } } \ No newline at end of file diff --git a/app/src/main/java/org/koitharu/kotatsu/widget/WidgetUpdater.kt b/app/src/main/java/org/koitharu/kotatsu/widget/WidgetUpdater.kt index ee11b02c6..185d4d5b2 100644 --- a/app/src/main/java/org/koitharu/kotatsu/widget/WidgetUpdater.kt +++ b/app/src/main/java/org/koitharu/kotatsu/widget/WidgetUpdater.kt @@ -4,36 +4,24 @@ import android.appwidget.AppWidgetManager import android.content.ComponentName import android.content.Context import android.content.Intent -import kotlinx.coroutines.CancellationException -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.flow.launchIn -import kotlinx.coroutines.flow.onEach -import kotlinx.coroutines.flow.retry -import kotlinx.coroutines.plus -import org.koitharu.kotatsu.favourites.domain.FavouritesRepository -import org.koitharu.kotatsu.history.domain.HistoryRepository -import org.koitharu.kotatsu.parsers.model.SortOrder -import org.koitharu.kotatsu.utils.ext.processLifecycleScope +import androidx.room.InvalidationTracker +import org.koitharu.kotatsu.core.db.TABLE_FAVOURITES +import org.koitharu.kotatsu.core.db.TABLE_HISTORY import org.koitharu.kotatsu.widget.recent.RecentWidgetProvider import org.koitharu.kotatsu.widget.shelf.ShelfWidgetProvider -class WidgetUpdater(private val context: Context) { +class WidgetUpdater(private val context: Context) : InvalidationTracker.Observer(TABLE_HISTORY, TABLE_FAVOURITES) { - fun subscribeToFavourites(repository: FavouritesRepository) { - repository.observeAll(SortOrder.NEWEST) - .onEach { updateWidget(ShelfWidgetProvider::class.java) } - .retry { error -> error !is CancellationException } - .launchIn(processLifecycleScope + Dispatchers.Default) + override fun onInvalidated(tables: MutableSet) { + if (TABLE_HISTORY in tables) { + updateWidgets(RecentWidgetProvider::class.java) + } + if (TABLE_FAVOURITES in tables) { + updateWidgets(ShelfWidgetProvider::class.java) + } } - fun subscribeToHistory(repository: HistoryRepository) { - repository.observeAll() - .onEach { updateWidget(RecentWidgetProvider::class.java) } - .retry { error -> error !is CancellationException } - .launchIn(processLifecycleScope + Dispatchers.Default) - } - - private fun updateWidget(cls: Class<*>) { + private fun updateWidgets(cls: Class<*>) { val intent = Intent(context, cls) intent.action = AppWidgetManager.ACTION_APPWIDGET_UPDATE val ids = AppWidgetManager.getInstance(context)