Add an option to refresh all tracking metadata

This commit is contained in:
inorichi 2017-05-17 13:36:42 +02:00
parent 097d4fe34c
commit 67678cd49e
8 changed files with 93 additions and 65 deletions

View File

@ -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) }

View File

@ -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)
} }
/** /**

View File

@ -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.
* *

View File

@ -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) }
} }
} }

View File

@ -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;
}
}

View File

@ -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 }
}

View File

@ -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>

View File

@ -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)