feat: Preparing sync

This commit is contained in:
Ahmad Ansori Palembani 2024-06-10 10:54:22 +07:00
parent 4521857a52
commit ed1b2326ed
Signed by: null2264
GPG key ID: BA64F8B60AF3EFB6
9 changed files with 278 additions and 9 deletions

View file

@ -0,0 +1,72 @@
package dev.yokai.data.sync.models
import androidx.annotation.StringRes
import eu.kanade.tachiyomi.R
import kotlinx.collections.immutable.persistentListOf
data class SyncTriggerOptions(
val syncOnChapterRead: Boolean = false,
val syncOnChapterOpen: Boolean = false,
val syncOnAppStart: Boolean = false,
val syncOnAppResume: Boolean = false,
val syncOnLibraryUpdate: Boolean = false,
) {
fun asBooleanArray() = booleanArrayOf(
syncOnChapterRead,
syncOnChapterOpen,
syncOnAppStart,
syncOnAppResume,
syncOnLibraryUpdate,
)
fun anyEnabled() = syncOnChapterRead ||
syncOnChapterOpen ||
syncOnAppStart ||
syncOnAppResume ||
syncOnLibraryUpdate
companion object {
val mainOptions = persistentListOf(
Entry(
label = R.string.sync_on_chapter_read,
getter = SyncTriggerOptions::syncOnChapterRead,
setter = { options, enabled -> options.copy(syncOnChapterRead = enabled) },
),
Entry(
label = R.string.sync_on_chapter_open,
getter = SyncTriggerOptions::syncOnChapterOpen,
setter = { options, enabled -> options.copy(syncOnChapterOpen = enabled) },
),
Entry(
label = R.string.sync_on_app_start,
getter = SyncTriggerOptions::syncOnAppStart,
setter = { options, enabled -> options.copy(syncOnAppStart = enabled) },
),
Entry(
label = R.string.sync_on_app_resume,
getter = SyncTriggerOptions::syncOnAppResume,
setter = { options, enabled -> options.copy(syncOnAppResume = enabled) },
),
Entry(
label = R.string.sync_on_library_update,
getter = SyncTriggerOptions::syncOnLibraryUpdate,
setter = { options, enabled -> options.copy(syncOnLibraryUpdate = enabled) },
),
)
fun fromBooleanArray(array: BooleanArray) = SyncTriggerOptions(
syncOnChapterRead = array[0],
syncOnChapterOpen = array[1],
syncOnAppStart = array[2],
syncOnAppResume = array[3],
syncOnLibraryUpdate = array[4],
)
}
data class Entry(
@StringRes val label: Int,
val getter: (SyncTriggerOptions) -> Boolean,
val setter: (SyncTriggerOptions, Boolean) -> SyncTriggerOptions,
val enabled: (SyncTriggerOptions) -> Boolean = { true },
)
}

View file

@ -0,0 +1,92 @@
package dev.yokai.domain.sync
import dev.yokai.data.sync.models.SyncTriggerOptions
import dev.yokai.domain.sync.models.SyncSettings
import eu.kanade.tachiyomi.core.preference.Preference.Companion.appStateKey
import eu.kanade.tachiyomi.core.preference.PreferenceStore
import java.util.*
class SyncPreferences(
private val preferenceStore: PreferenceStore,
) {
fun clientHost() = preferenceStore.getString("sync_client_host", "https://sync.tachiyomi.org")
fun clientAPIKey() = preferenceStore.getString("sync_client_api_key", "")
fun lastSyncTimestamp() = preferenceStore.getLong(appStateKey("last_sync_timestamp"), 0L)
fun syncInterval() = preferenceStore.getInt("sync_interval", 0)
fun syncService() = preferenceStore.getInt("sync_service", 0)
fun googleDriveAccessToken() = preferenceStore.getString(
appStateKey("google_drive_access_token"),
"",
)
fun googleDriveRefreshToken() = preferenceStore.getString(
appStateKey("google_drive_refresh_token"),
"",
)
fun uniqueDeviceID(): String {
val uniqueIDPreference = preferenceStore.getString(appStateKey("unique_device_id"), "")
// Retrieve the current value of the preference
var uniqueID = uniqueIDPreference.get()
if (uniqueID.isBlank()) {
uniqueID = UUID.randomUUID().toString()
uniqueIDPreference.set(uniqueID)
}
return uniqueID
}
fun isSyncEnabled(): Boolean {
return syncService().get() != 0
}
fun getSyncSettings(): SyncSettings {
return SyncSettings(
libraryEntries = preferenceStore.getBoolean("library_entries", true).get(),
categories = preferenceStore.getBoolean("categories", true).get(),
chapters = preferenceStore.getBoolean("chapters", true).get(),
tracking = preferenceStore.getBoolean("tracking", true).get(),
history = preferenceStore.getBoolean("history", true).get(),
appSettings = preferenceStore.getBoolean("appSettings", true).get(),
sourceSettings = preferenceStore.getBoolean("sourceSettings", true).get(),
privateSettings = preferenceStore.getBoolean("privateSettings", true).get(),
)
}
fun setSyncSettings(syncSettings: SyncSettings) {
preferenceStore.getBoolean("library_entries", true).set(syncSettings.libraryEntries)
preferenceStore.getBoolean("categories", true).set(syncSettings.categories)
preferenceStore.getBoolean("chapters", true).set(syncSettings.chapters)
preferenceStore.getBoolean("tracking", true).set(syncSettings.tracking)
preferenceStore.getBoolean("history", true).set(syncSettings.history)
preferenceStore.getBoolean("appSettings", true).set(syncSettings.appSettings)
preferenceStore.getBoolean("sourceSettings", true).set(syncSettings.sourceSettings)
preferenceStore.getBoolean("privateSettings", true).set(syncSettings.privateSettings)
}
fun getSyncTriggerOptions(): SyncTriggerOptions {
return SyncTriggerOptions(
syncOnChapterRead = preferenceStore.getBoolean("sync_on_chapter_read", false).get(),
syncOnChapterOpen = preferenceStore.getBoolean("sync_on_chapter_open", false).get(),
syncOnAppStart = preferenceStore.getBoolean("sync_on_app_start", false).get(),
syncOnAppResume = preferenceStore.getBoolean("sync_on_app_resume", false).get(),
syncOnLibraryUpdate = preferenceStore.getBoolean("sync_on_library_update", false).get(),
)
}
fun setSyncTriggerOptions(syncTriggerOptions: SyncTriggerOptions) {
preferenceStore.getBoolean("sync_on_chapter_read", false)
.set(syncTriggerOptions.syncOnChapterRead)
preferenceStore.getBoolean("sync_on_chapter_open", false)
.set(syncTriggerOptions.syncOnChapterOpen)
preferenceStore.getBoolean("sync_on_app_start", false)
.set(syncTriggerOptions.syncOnAppStart)
preferenceStore.getBoolean("sync_on_app_resume", false)
.set(syncTriggerOptions.syncOnAppResume)
preferenceStore.getBoolean("sync_on_library_update", false)
.set(syncTriggerOptions.syncOnLibraryUpdate)
}
}

View file

@ -0,0 +1,12 @@
package dev.yokai.domain.sync.models
data class SyncSettings(
val libraryEntries: Boolean = true,
val categories: Boolean = true,
val chapters: Boolean = true,
val tracking: Boolean = true,
val history: Boolean = true,
val appSettings: Boolean = true,
val sourceSettings: Boolean = true,
val privateSettings: Boolean = false,
)

View file

@ -22,6 +22,7 @@ import eu.kanade.tachiyomi.data.database.tables.MangaTable.COL_GENRE
import eu.kanade.tachiyomi.data.database.tables.MangaTable.COL_HIDE_TITLE
import eu.kanade.tachiyomi.data.database.tables.MangaTable.COL_ID
import eu.kanade.tachiyomi.data.database.tables.MangaTable.COL_INITIALIZED
import eu.kanade.tachiyomi.data.database.tables.MangaTable.COL_IS_SYNCING
import eu.kanade.tachiyomi.data.database.tables.MangaTable.COL_LAST_UPDATE
import eu.kanade.tachiyomi.data.database.tables.MangaTable.COL_SOURCE
import eu.kanade.tachiyomi.data.database.tables.MangaTable.COL_STATUS
@ -29,9 +30,11 @@ import eu.kanade.tachiyomi.data.database.tables.MangaTable.COL_THUMBNAIL_URL
import eu.kanade.tachiyomi.data.database.tables.MangaTable.COL_TITLE
import eu.kanade.tachiyomi.data.database.tables.MangaTable.COL_UPDATE_STRATEGY
import eu.kanade.tachiyomi.data.database.tables.MangaTable.COL_URL
import eu.kanade.tachiyomi.data.database.tables.MangaTable.COL_VERSION
import eu.kanade.tachiyomi.data.database.tables.MangaTable.COL_VIEWER
import eu.kanade.tachiyomi.data.database.tables.MangaTable.TABLE
import eu.kanade.tachiyomi.data.database.updateStrategyAdapter
import eu.kanade.tachiyomi.util.system.toBoolean
class MangaTypeMapping : SQLiteTypeMapping<Manga>(
MangaPutResolver(),
@ -71,6 +74,8 @@ class MangaPutResolver : DefaultPutResolver<Manga>() {
put(COL_DATE_ADDED, obj.date_added)
put(COL_FILTERED_SCANLATORS, obj.filtered_scanlators)
put(COL_UPDATE_STRATEGY, obj.update_strategy.let(updateStrategyAdapter::encode))
put(COL_VERSION, obj.version)
put(COL_IS_SYNCING, obj.isSyncing)
}
}
@ -86,17 +91,19 @@ interface BaseMangaGetResolver {
title = cursor.getString(cursor.getColumnIndex(COL_TITLE))
status = cursor.getInt(cursor.getColumnIndex(COL_STATUS))
thumbnail_url = cursor.getString(cursor.getColumnIndex(COL_THUMBNAIL_URL))
favorite = cursor.getInt(cursor.getColumnIndex(COL_FAVORITE)) == 1
favorite = cursor.getInt(cursor.getColumnIndex(COL_FAVORITE)).toBoolean()
last_update = cursor.getLong(cursor.getColumnIndex(COL_LAST_UPDATE))
initialized = cursor.getInt(cursor.getColumnIndex(COL_INITIALIZED)) == 1
initialized = cursor.getInt(cursor.getColumnIndex(COL_INITIALIZED)).toBoolean()
viewer_flags = cursor.getInt(cursor.getColumnIndex(COL_VIEWER))
chapter_flags = cursor.getInt(cursor.getColumnIndex(COL_CHAPTER_FLAGS))
hide_title = cursor.getInt(cursor.getColumnIndex(COL_HIDE_TITLE)) == 1
hide_title = cursor.getInt(cursor.getColumnIndex(COL_HIDE_TITLE)).toBoolean()
date_added = cursor.getLong(cursor.getColumnIndex(COL_DATE_ADDED))
filtered_scanlators = cursor.getString(cursor.getColumnIndex(COL_FILTERED_SCANLATORS))
update_strategy = cursor.getInt(cursor.getColumnIndex(COL_UPDATE_STRATEGY)).let(
updateStrategyAdapter::decode,
)
version = cursor.getInt(cursor.getColumnIndex(COL_VERSION))
isSyncing = cursor.getInt(cursor.getColumnIndex(COL_IS_SYNCING)).toBoolean()
}
}

View file

@ -10,9 +10,10 @@ import eu.kanade.tachiyomi.source.model.SManga
import eu.kanade.tachiyomi.ui.reader.settings.OrientationType
import eu.kanade.tachiyomi.ui.reader.settings.ReadingModeType
import eu.kanade.tachiyomi.util.manga.MangaCoverMetadata
import eu.kanade.tachiyomi.util.system.toBoolean
import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get
import java.util.Locale
import java.util.*
interface Manga : SManga {
@ -34,6 +35,10 @@ interface Manga : SManga {
var filtered_scanlators: String?
var version: Int
var isSyncing: Boolean
fun isBlank() = id == Long.MIN_VALUE
fun isHidden() = status == -1
@ -359,7 +364,9 @@ interface Manga : SManga {
chapterFlags: Long,
dateAdded: Long?,
filteredScanlators: String?,
updateStrategy: Long
updateStrategy: Long,
version: Long,
isSyncing: Long,
): Manga = create(source).apply {
this.id = id
this.url = url
@ -370,15 +377,17 @@ interface Manga : SManga {
this.title = title
this.status = status.toInt()
this.thumbnail_url = thumbnailUrl
this.favorite = favorite > 0
this.favorite = favorite.toBoolean()
this.last_update = lastUpdate ?: 0L
this.initialized = initialized
this.viewer_flags = viewerFlags.toInt()
this.chapter_flags = chapterFlags.toInt()
this.hide_title = hideTitle > 0
this.hide_title = hideTitle.toBoolean()
this.date_added = dateAdded ?: 0L
this.filtered_scanlators = filteredScanlators
this.update_strategy = updateStrategy.toInt().let(updateStrategyAdapter::decode)
this.version = version.toInt()
this.isSyncing = isSyncing.toBoolean()
}
}
}

View file

@ -42,4 +42,7 @@ object MangaTable {
const val COL_UPDATE_STRATEGY = "update_strategy"
const val COL_VERSION = "version"
const val COL_IS_SYNCING = "is_syncing"
}

View file

@ -1,4 +1,4 @@
package eu.kanade.tachiyomi.util.system
fun Boolean.toInt() = if (this) 1 else 0
fun Int.toBoolean() = this == 1
fun Number.toBoolean() = this == 1

View file

@ -847,6 +847,60 @@
</plurals>
<string name="not_logged_into_">Not logged into %1$s</string>
<string name="backup_private_pref">Include sensitive settings (e.g. tracker login tokens)</string>
<string name="label_sync">Sync</string>
<string name="label_triggers">Triggers</string>
<string name="pref_backup_and_sync_summary">Manual &amp; automatic backups and sync</string>
<!-- Sync section -->
<string name="syncing_library">Syncing library</string>
<string name="library_sync_complete">Library sync complete</string>
<string name="sync_error">Syncing library failed</string>
<string name="sync_complete">Syncing library complete</string>
<string name="sync_in_progress">Sync is already in progress</string>
<string name="pref_sync_host">Host</string>
<string name="pref_sync_host_summ">Enter the host address for synchronizing your library</string>
<string name="pref_sync_api_key">API key</string>
<string name="pref_sync_api_key_summ">Enter the API key to synchronize your library</string>
<string name="pref_sync_now_group_title">Sync Actions</string>
<string name="pref_sync_now">Sync now</string>
<string name="pref_sync_confirmation_title">Sync confirmation</string>
<string name="pref_sync_now_subtitle">Initiate immediate synchronization of your data</string>
<string name="pref_sync_confirmation_message">Syncing will overwrite your local library with the remote library. Are you sure you want to continue?</string>
<string name="pref_sync_service">Service</string>
<string name="pref_sync_service_summ">Select the service to sync your library with</string>
<string name="pref_sync_service_category">Sync</string>
<string name="pref_sync_automatic_category">Automatic Synchronization</string>
<string name="pref_sync_interval">Synchronization frequency</string>
<string name="pref_choose_what_to_sync">Choose what to sync</string>
<string name="success_reset_sync_timestamp">Last sync timestamp reset</string>
<string name="syncyomi">SyncYomi</string>
<string name="sync_completed_message">Done in %1$s</string>
<string name="last_synchronization">Last Synchronization: %1$s</string>
<string name="google_drive">Google Drive</string>
<string name="pref_google_drive_sign_in">Sign in</string>
<string name="google_drive_sign_in_success">Signed in successfully</string>
<string name="google_drive_sign_in_failed">Sign in failed</string>
<string name="authentication">Authentication</string>
<string name="pref_google_drive_purge_sync_data">Clear Sync Data from Google Drive</string>
<string name="google_drive_sync_data_purged">Sync data purged from Google Drive</string>
<string name="google_drive_sync_data_not_found">No sync data found in Google Drive</string>
<string name="google_drive_sync_data_purge_error">Error purging sync data from Google Drive, Try to sign in again.</string>
<string name="google_drive_login_success">Logged in to Google Drive</string>
<string name="google_drive_login_failed">Failed to log in to Google Drive: %s</string>
<string name="google_drive_not_signed_in">Not signed in to Google Drive</string>
<string name="error_uploading_sync_data">Error uploading sync data to Google Drive</string>
<string name="error_deleting_google_drive_lock_file">Error Deleting Google Drive Lock File</string>
<string name="error_before_sync_gdrive">Error before sync: %s</string>
<string name="pref_purge_confirmation_title">Purge confirmation</string>
<string name="pref_purge_confirmation_message">Purging sync data will delete all your sync data from Google Drive. Are you sure you want to continue?</string>
<string name="pref_sync_options">Create sync triggers</string>
<string name="pref_sync_options_summ">Can be used to set sync triggers</string>
<string name="sync_on_chapter_read">Sync on Chapter Read</string>
<string name="sync_on_chapter_open">Sync on Chapter Open</string>
<string name="sync_on_app_start">Sync on App Start</string>
<string name="sync_on_app_resume">Sync on App Resume</string>
<string name="sync_on_library_update">Sync on Library Update</string>
<string name="sync_library">Sync library</string>
<!-- Advanced section -->
<string name="clear_chapter_cache">Clear chapter cache</string>
@ -1097,6 +1151,9 @@
<!-- Time -->
<string name="manual">Manual</string>
<string name="every_30_min">Every 30 minutes</string>
<string name="every_1_hour">Every hour</string>
<string name="every_3_hour">Every 3 hours</string>
<string name="every_6_hours">Every 6 hours</string>
<string name="every_12_hours">Every 12 hours</string>
<string name="daily">Daily</string>

View file

@ -20,12 +20,29 @@ CREATE TABLE mangas(
chapter_flags INTEGER NOT NULL,
date_added INTEGER AS Long,
filtered_scanlators TEXT,
update_strategy INTEGER NOT NULL DEFAULT 0
update_strategy INTEGER NOT NULL DEFAULT 0,
version INTEGER NOT NULL DEFAULT 0,
is_syncing INTEGER NOT NULL DEFAULT 0
);
CREATE INDEX mangas_url_index ON mangas(url);
CREATE INDEX library_favorite_index ON mangas(favorite) WHERE favorite = 1;
CREATE TRIGGER update_manga_version AFTER UPDATE ON mangas
BEGIN
UPDATE mangas SET version = version + 1
WHERE _id = new._id AND new.is_syncing = 0 AND (
new.url != old.url OR
new.description != old.description OR
new.favorite != old.favorite
);
END;
findAll:
SELECT *
FROM mangas;
resetIsSyncing:
UPDATE mangas
SET is_syncing = 0
WHERE is_syncing = 1;