mirror of
https://github.com/tachiyomiorg/tachiyomi.git
synced 2024-12-23 02:51:47 +01:00
Add an option to refresh all tracking metadata
This commit is contained in:
parent
097d4fe34c
commit
67678cd49e
@ -13,7 +13,6 @@ import eu.kanade.tachiyomi.data.backup.models.Backup.CATEGORIES
|
|||||||
import eu.kanade.tachiyomi.data.backup.models.Backup.MANGAS
|
import eu.kanade.tachiyomi.data.backup.models.Backup.MANGAS
|
||||||
import eu.kanade.tachiyomi.data.backup.models.Backup.VERSION
|
import eu.kanade.tachiyomi.data.backup.models.Backup.VERSION
|
||||||
import eu.kanade.tachiyomi.data.database.models.Manga
|
import eu.kanade.tachiyomi.data.database.models.Manga
|
||||||
import eu.kanade.tachiyomi.util.AndroidComponentUtil
|
|
||||||
import eu.kanade.tachiyomi.util.sendLocalBroadcast
|
import eu.kanade.tachiyomi.util.sendLocalBroadcast
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
import eu.kanade.tachiyomi.BuildConfig.APPLICATION_ID as ID
|
import eu.kanade.tachiyomi.BuildConfig.APPLICATION_ID as ID
|
||||||
@ -60,9 +59,6 @@ class BackupCreateService : IntentService(NAME) {
|
|||||||
context.startService(intent)
|
context.startService(intent)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun isRunning(context: Context): Boolean {
|
|
||||||
return AndroidComponentUtil.isServiceRunning(context, BackupCreateService::class.java)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private val backupManager by lazy { BackupManager(this) }
|
private val backupManager by lazy { BackupManager(this) }
|
||||||
|
@ -22,8 +22,8 @@ import eu.kanade.tachiyomi.data.backup.models.DHistory
|
|||||||
import eu.kanade.tachiyomi.data.database.DatabaseHelper
|
import eu.kanade.tachiyomi.data.database.DatabaseHelper
|
||||||
import eu.kanade.tachiyomi.data.database.models.*
|
import eu.kanade.tachiyomi.data.database.models.*
|
||||||
import eu.kanade.tachiyomi.source.Source
|
import eu.kanade.tachiyomi.source.Source
|
||||||
import eu.kanade.tachiyomi.util.AndroidComponentUtil
|
|
||||||
import eu.kanade.tachiyomi.util.chop
|
import eu.kanade.tachiyomi.util.chop
|
||||||
|
import eu.kanade.tachiyomi.util.isServiceRunning
|
||||||
import eu.kanade.tachiyomi.util.sendLocalBroadcast
|
import eu.kanade.tachiyomi.util.sendLocalBroadcast
|
||||||
import rx.Observable
|
import rx.Observable
|
||||||
import rx.Subscription
|
import rx.Subscription
|
||||||
@ -50,7 +50,7 @@ class BackupRestoreService : Service() {
|
|||||||
* @return true if the service is running, false otherwise.
|
* @return true if the service is running, false otherwise.
|
||||||
*/
|
*/
|
||||||
fun isRunning(context: Context): Boolean {
|
fun isRunning(context: Context): Boolean {
|
||||||
return AndroidComponentUtil.isServiceRunning(context, BackupRestoreService::class.java)
|
return context.isServiceRunning(BackupRestoreService::class.java)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -16,12 +16,14 @@ import eu.kanade.tachiyomi.data.database.DatabaseHelper
|
|||||||
import eu.kanade.tachiyomi.data.database.models.Category
|
import eu.kanade.tachiyomi.data.database.models.Category
|
||||||
import eu.kanade.tachiyomi.data.database.models.Chapter
|
import eu.kanade.tachiyomi.data.database.models.Chapter
|
||||||
import eu.kanade.tachiyomi.data.database.models.Manga
|
import eu.kanade.tachiyomi.data.database.models.Manga
|
||||||
|
import eu.kanade.tachiyomi.data.database.models.Track
|
||||||
import eu.kanade.tachiyomi.data.download.DownloadManager
|
import eu.kanade.tachiyomi.data.download.DownloadManager
|
||||||
import eu.kanade.tachiyomi.data.download.DownloadService
|
import eu.kanade.tachiyomi.data.download.DownloadService
|
||||||
import eu.kanade.tachiyomi.data.library.LibraryUpdateService.Companion.start
|
import eu.kanade.tachiyomi.data.library.LibraryUpdateService.Companion.start
|
||||||
import eu.kanade.tachiyomi.data.notification.NotificationReceiver
|
import eu.kanade.tachiyomi.data.notification.NotificationReceiver
|
||||||
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
|
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
|
||||||
import eu.kanade.tachiyomi.data.preference.getOrDefault
|
import eu.kanade.tachiyomi.data.preference.getOrDefault
|
||||||
|
import eu.kanade.tachiyomi.data.track.TrackManager
|
||||||
import eu.kanade.tachiyomi.source.SourceManager
|
import eu.kanade.tachiyomi.source.SourceManager
|
||||||
import eu.kanade.tachiyomi.source.model.SManga
|
import eu.kanade.tachiyomi.source.model.SManga
|
||||||
import eu.kanade.tachiyomi.source.online.HttpSource
|
import eu.kanade.tachiyomi.source.online.HttpSource
|
||||||
@ -48,7 +50,8 @@ class LibraryUpdateService(
|
|||||||
val db: DatabaseHelper = Injekt.get(),
|
val db: DatabaseHelper = Injekt.get(),
|
||||||
val sourceManager: SourceManager = Injekt.get(),
|
val sourceManager: SourceManager = Injekt.get(),
|
||||||
val preferences: PreferencesHelper = Injekt.get(),
|
val preferences: PreferencesHelper = Injekt.get(),
|
||||||
val downloadManager: DownloadManager = Injekt.get()
|
val downloadManager: DownloadManager = Injekt.get(),
|
||||||
|
val trackManager: TrackManager = Injekt.get()
|
||||||
) : Service() {
|
) : Service() {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -85,17 +88,26 @@ class LibraryUpdateService(
|
|||||||
.addAction(R.drawable.ic_clear_grey_24dp_img, getString(android.R.string.cancel), cancelIntent)
|
.addAction(R.drawable.ic_clear_grey_24dp_img, getString(android.R.string.cancel), cancelIntent)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Defines what should be updated within a service execution.
|
||||||
|
*/
|
||||||
|
enum class Target {
|
||||||
|
CHAPTERS, // Manga chapters
|
||||||
|
DETAILS, // Manga metadata
|
||||||
|
TRACKING // Tracking metadata
|
||||||
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Key for category to update.
|
* Key for category to update.
|
||||||
*/
|
*/
|
||||||
const val UPDATE_CATEGORY = "category"
|
const val KEY_CATEGORY = "category"
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Key for updating the details instead of the chapters.
|
* Key that defines what should be updated.
|
||||||
*/
|
*/
|
||||||
const val UPDATE_DETAILS = "details"
|
const val KEY_TARGET = "target"
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the status of the service.
|
* Returns the status of the service.
|
||||||
@ -104,7 +116,7 @@ class LibraryUpdateService(
|
|||||||
* @return true if the service is running, false otherwise.
|
* @return true if the service is running, false otherwise.
|
||||||
*/
|
*/
|
||||||
fun isRunning(context: Context): Boolean {
|
fun isRunning(context: Context): Boolean {
|
||||||
return AndroidComponentUtil.isServiceRunning(context, LibraryUpdateService::class.java)
|
return context.isServiceRunning(LibraryUpdateService::class.java)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -113,13 +125,13 @@ class LibraryUpdateService(
|
|||||||
*
|
*
|
||||||
* @param context the application context.
|
* @param context the application context.
|
||||||
* @param category a specific category to update, or null for global update.
|
* @param category a specific category to update, or null for global update.
|
||||||
* @param details whether to update the details instead of the list of chapters.
|
* @param target defines what should be updated.
|
||||||
*/
|
*/
|
||||||
fun start(context: Context, category: Category? = null, details: Boolean = false) {
|
fun start(context: Context, category: Category? = null, target: Target = Target.CHAPTERS) {
|
||||||
if (!isRunning(context)) {
|
if (!isRunning(context)) {
|
||||||
val intent = Intent(context, LibraryUpdateService::class.java).apply {
|
val intent = Intent(context, LibraryUpdateService::class.java).apply {
|
||||||
putExtra(UPDATE_DETAILS, details)
|
putExtra(KEY_TARGET, target)
|
||||||
category?.let { putExtra(UPDATE_CATEGORY, it.id) }
|
category?.let { putExtra(KEY_CATEGORY, it.id) }
|
||||||
}
|
}
|
||||||
context.startService(intent)
|
context.startService(intent)
|
||||||
}
|
}
|
||||||
@ -176,6 +188,8 @@ class LibraryUpdateService(
|
|||||||
*/
|
*/
|
||||||
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
|
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
|
||||||
if (intent == null) return Service.START_NOT_STICKY
|
if (intent == null) return Service.START_NOT_STICKY
|
||||||
|
val target = intent.getSerializableExtra(KEY_TARGET) as? Target
|
||||||
|
?: return Service.START_NOT_STICKY
|
||||||
|
|
||||||
// Unsubscribe from any previous subscription if needed.
|
// Unsubscribe from any previous subscription if needed.
|
||||||
subscription?.unsubscribe()
|
subscription?.unsubscribe()
|
||||||
@ -183,13 +197,14 @@ class LibraryUpdateService(
|
|||||||
// Update favorite manga. Destroy service when completed or in case of an error.
|
// Update favorite manga. Destroy service when completed or in case of an error.
|
||||||
subscription = Observable
|
subscription = Observable
|
||||||
.defer {
|
.defer {
|
||||||
val mangaList = getMangaToUpdate(intent)
|
val mangaList = getMangaToUpdate(intent, target)
|
||||||
|
|
||||||
// Update either chapter list or manga details.
|
// Update either chapter list or manga details.
|
||||||
if (!intent.getBooleanExtra(UPDATE_DETAILS, false))
|
when (target) {
|
||||||
updateChapterList(mangaList)
|
Target.CHAPTERS -> updateChapterList(mangaList)
|
||||||
else
|
Target.DETAILS -> updateDetails(mangaList)
|
||||||
updateDetails(mangaList)
|
Target.TRACKING -> updateTrackings(mangaList)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
.subscribeOn(Schedulers.io())
|
.subscribeOn(Schedulers.io())
|
||||||
.subscribe({
|
.subscribe({
|
||||||
@ -207,10 +222,11 @@ class LibraryUpdateService(
|
|||||||
* Returns the list of manga to be updated.
|
* Returns the list of manga to be updated.
|
||||||
*
|
*
|
||||||
* @param intent the update intent.
|
* @param intent the update intent.
|
||||||
|
* @param target the target to update.
|
||||||
* @return a list of manga to update
|
* @return a list of manga to update
|
||||||
*/
|
*/
|
||||||
fun getMangaToUpdate(intent: Intent): List<Manga> {
|
fun getMangaToUpdate(intent: Intent, target: Target): List<Manga> {
|
||||||
val categoryId = intent.getIntExtra(UPDATE_CATEGORY, -1)
|
val categoryId = intent.getIntExtra(KEY_CATEGORY, -1)
|
||||||
|
|
||||||
var listToUpdate = if (categoryId != -1)
|
var listToUpdate = if (categoryId != -1)
|
||||||
db.getLibraryMangas().executeAsBlocking().filter { it.category == categoryId }
|
db.getLibraryMangas().executeAsBlocking().filter { it.category == categoryId }
|
||||||
@ -224,7 +240,7 @@ class LibraryUpdateService(
|
|||||||
db.getLibraryMangas().executeAsBlocking().distinctBy { it.id }
|
db.getLibraryMangas().executeAsBlocking().distinctBy { it.id }
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!intent.getBooleanExtra(UPDATE_DETAILS, false) && preferences.updateOnlyNonCompleted()) {
|
if (target == Target.CHAPTERS && preferences.updateOnlyNonCompleted()) {
|
||||||
listToUpdate = listToUpdate.filter { it.status != SManga.COMPLETED }
|
listToUpdate = listToUpdate.filter { it.status != SManga.COMPLETED }
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -328,8 +344,6 @@ class LibraryUpdateService(
|
|||||||
/**
|
/**
|
||||||
* Method that updates the details of the given list of manga. It's called in a background
|
* Method that updates the details of the given list of manga. It's called in a background
|
||||||
* thread, so it's safe to do heavy operations or network calls here.
|
* thread, so it's safe to do heavy operations or network calls here.
|
||||||
* For each manga it calls [updateManga] and updates the notification showing the current
|
|
||||||
* progress.
|
|
||||||
*
|
*
|
||||||
* @param mangaToUpdate the list to update
|
* @param mangaToUpdate the list to update
|
||||||
* @return an observable delivering the progress of each update.
|
* @return an observable delivering the progress of each update.
|
||||||
@ -360,6 +374,42 @@ class LibraryUpdateService(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Method that updates the metadata of the connected tracking services. It's called in a
|
||||||
|
* background thread, so it's safe to do heavy operations or network calls here.
|
||||||
|
*/
|
||||||
|
private fun updateTrackings(mangaToUpdate: List<Manga>): Observable<Manga> {
|
||||||
|
// Initialize the variables holding the progress of the updates.
|
||||||
|
var count = 0
|
||||||
|
|
||||||
|
val loggedServices = trackManager.services.filter { it.isLogged }
|
||||||
|
|
||||||
|
// Emit each manga and update it sequentially.
|
||||||
|
return Observable.from(mangaToUpdate)
|
||||||
|
// Notify manga that will update.
|
||||||
|
.doOnNext { showProgressNotification(it, count++, mangaToUpdate.size) }
|
||||||
|
// Update the tracking details.
|
||||||
|
.concatMap { manga ->
|
||||||
|
val tracks = db.getTracks(manga).executeAsBlocking()
|
||||||
|
|
||||||
|
Observable.from(tracks)
|
||||||
|
.concatMap { track ->
|
||||||
|
val service = trackManager.getService(track.sync_id)
|
||||||
|
if (service != null && service in loggedServices) {
|
||||||
|
service.refresh(track)
|
||||||
|
.doOnNext { db.insertTrack(it).executeAsBlocking() }
|
||||||
|
.onErrorReturn { track }
|
||||||
|
} else {
|
||||||
|
Observable.empty()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.map { manga }
|
||||||
|
}
|
||||||
|
.doOnCompleted {
|
||||||
|
cancelProgressNotification()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Shows the notification containing the currently updating manga and the progress.
|
* Shows the notification containing the currently updating manga and the progress.
|
||||||
*
|
*
|
||||||
|
@ -11,6 +11,7 @@ import eu.kanade.tachiyomi.R
|
|||||||
import eu.kanade.tachiyomi.data.cache.ChapterCache
|
import eu.kanade.tachiyomi.data.cache.ChapterCache
|
||||||
import eu.kanade.tachiyomi.data.database.DatabaseHelper
|
import eu.kanade.tachiyomi.data.database.DatabaseHelper
|
||||||
import eu.kanade.tachiyomi.data.library.LibraryUpdateService
|
import eu.kanade.tachiyomi.data.library.LibraryUpdateService
|
||||||
|
import eu.kanade.tachiyomi.data.library.LibraryUpdateService.Target
|
||||||
import eu.kanade.tachiyomi.network.NetworkHelper
|
import eu.kanade.tachiyomi.network.NetworkHelper
|
||||||
import eu.kanade.tachiyomi.ui.base.controller.DialogController
|
import eu.kanade.tachiyomi.ui.base.controller.DialogController
|
||||||
import eu.kanade.tachiyomi.ui.library.LibraryController
|
import eu.kanade.tachiyomi.ui.library.LibraryController
|
||||||
@ -60,7 +61,13 @@ class SettingsAdvancedController : SettingsController() {
|
|||||||
titleRes = R.string.pref_refresh_library_metadata
|
titleRes = R.string.pref_refresh_library_metadata
|
||||||
summaryRes = R.string.pref_refresh_library_metadata_summary
|
summaryRes = R.string.pref_refresh_library_metadata_summary
|
||||||
|
|
||||||
onClick { LibraryUpdateService.start(context, details = true) }
|
onClick { LibraryUpdateService.start(context, target = Target.DETAILS) }
|
||||||
|
}
|
||||||
|
preference {
|
||||||
|
titleRes = R.string.pref_refresh_library_tracking
|
||||||
|
summaryRes = R.string.pref_refresh_library_tracking_summary
|
||||||
|
|
||||||
|
onClick { LibraryUpdateService.start(context, target = Target.TRACKING) }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,37 +0,0 @@
|
|||||||
package eu.kanade.tachiyomi.util;
|
|
||||||
|
|
||||||
import android.app.ActivityManager;
|
|
||||||
import android.app.ActivityManager.RunningServiceInfo;
|
|
||||||
import android.content.ComponentName;
|
|
||||||
import android.content.Context;
|
|
||||||
import android.content.pm.PackageManager;
|
|
||||||
|
|
||||||
import timber.log.Timber;
|
|
||||||
|
|
||||||
public final class AndroidComponentUtil {
|
|
||||||
|
|
||||||
private AndroidComponentUtil() throws InstantiationException {
|
|
||||||
throw new InstantiationException("This class is not for instantiation");
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void toggleComponent(Context context, Class componentClass, boolean enable) {
|
|
||||||
Timber.i((enable ? "Enabling " : "Disabling ") + componentClass.getSimpleName());
|
|
||||||
ComponentName componentName = new ComponentName(context, componentClass);
|
|
||||||
PackageManager pm = context.getPackageManager();
|
|
||||||
pm.setComponentEnabledSetting(componentName,
|
|
||||||
enable ? PackageManager.COMPONENT_ENABLED_STATE_ENABLED :
|
|
||||||
PackageManager.COMPONENT_ENABLED_STATE_DISABLED,
|
|
||||||
PackageManager.DONT_KILL_APP);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static boolean isServiceRunning(Context context, Class serviceClass) {
|
|
||||||
ActivityManager manager =
|
|
||||||
(ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
|
|
||||||
for (RunningServiceInfo service : manager.getRunningServices(Integer.MAX_VALUE)) {
|
|
||||||
if (serviceClass.getName().equals(service.service.getClassName())) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,5 +1,6 @@
|
|||||||
package eu.kanade.tachiyomi.util
|
package eu.kanade.tachiyomi.util
|
||||||
|
|
||||||
|
import android.app.ActivityManager
|
||||||
import android.app.Notification
|
import android.app.Notification
|
||||||
import android.app.NotificationManager
|
import android.app.NotificationManager
|
||||||
import android.content.BroadcastReceiver
|
import android.content.BroadcastReceiver
|
||||||
@ -135,4 +136,12 @@ fun Context.unregisterLocalReceiver(receiver: BroadcastReceiver) {
|
|||||||
LocalBroadcastManager.getInstance(this).unregisterReceiver(receiver)
|
LocalBroadcastManager.getInstance(this).unregisterReceiver(receiver)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns true if the given service class is running.
|
||||||
|
*/
|
||||||
|
fun Context.isServiceRunning(serviceClass: Class<*>): Boolean {
|
||||||
|
val className = serviceClass.name
|
||||||
|
val manager = getSystemService(Context.ACTIVITY_SERVICE) as ActivityManager
|
||||||
|
return manager.getRunningServices(Integer.MAX_VALUE)
|
||||||
|
.any { className == it.service.className }
|
||||||
|
}
|
@ -239,6 +239,8 @@
|
|||||||
<string name="clear_database_completed">Entries deleted</string>
|
<string name="clear_database_completed">Entries deleted</string>
|
||||||
<string name="pref_refresh_library_metadata">Refresh library metadata</string>
|
<string name="pref_refresh_library_metadata">Refresh library metadata</string>
|
||||||
<string name="pref_refresh_library_metadata_summary">Updates covers, genres, description and manga status information</string>
|
<string name="pref_refresh_library_metadata_summary">Updates covers, genres, description and manga status information</string>
|
||||||
|
<string name="pref_refresh_library_tracking">Refresh tracking metadata</string>
|
||||||
|
<string name="pref_refresh_library_tracking_summary">Updates status, score and last chapter read from the tracking services</string>
|
||||||
|
|
||||||
<!-- About section -->
|
<!-- About section -->
|
||||||
<string name="version">Version</string>
|
<string name="version">Version</string>
|
||||||
|
@ -96,7 +96,8 @@ class LibraryUpdateServiceTest {
|
|||||||
`when`(source.fetchChapterList(favManga[2])).thenReturn(Observable.just(chapters3))
|
`when`(source.fetchChapterList(favManga[2])).thenReturn(Observable.just(chapters3))
|
||||||
|
|
||||||
val intent = Intent()
|
val intent = Intent()
|
||||||
service.updateChapterList(service.getMangaToUpdate(intent)).subscribe()
|
val target = LibraryUpdateService.Target.CHAPTERS
|
||||||
|
service.updateChapterList(service.getMangaToUpdate(intent, target)).subscribe()
|
||||||
|
|
||||||
// There are 3 network attempts and 2 insertions (1 request failed)
|
// There are 3 network attempts and 2 insertions (1 request failed)
|
||||||
assertThat(service.db.getChapters(favManga[0]).executeAsBlocking()).hasSize(2)
|
assertThat(service.db.getChapters(favManga[0]).executeAsBlocking()).hasSize(2)
|
||||||
|
Loading…
Reference in New Issue
Block a user