Allow to refresh the entire library info (fixing empty covers after restoring backups). Closes #462

This commit is contained in:
len 2016-10-06 19:23:59 +02:00
parent 500eedaab7
commit 1f70be688a
9 changed files with 83 additions and 15 deletions

View File

@ -71,18 +71,18 @@ class LibraryUpdateService : Service() {
private val notificationId: Int private val notificationId: Int
get() = Constants.NOTIFICATION_LIBRARY_ID get() = Constants.NOTIFICATION_LIBRARY_ID
companion object { companion object {
/**
* Key for manual library update.
*/
const val UPDATE_IS_MANUAL = "is_manual"
/** /**
* Key for category to update. * Key for category to update.
*/ */
const val UPDATE_CATEGORY = "category" const val UPDATE_CATEGORY = "category"
/**
* Key for updating the details instead of the chapters.
*/
const val UPDATE_DETAILS = "details"
/** /**
* Returns the status of the service. * Returns the status of the service.
* *
@ -98,13 +98,13 @@ class LibraryUpdateService : Service() {
* running. * running.
* *
* @param context the application context. * @param context the application context.
* @param isManual whether the update has been manually triggered.
* @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.
*/ */
fun start(context: Context, isManual: Boolean = false, category: Category? = null) { fun start(context: Context, category: Category? = null, details: Boolean = false) {
if (!isRunning(context)) { if (!isRunning(context)) {
val intent = Intent(context, LibraryUpdateService::class.java).apply { val intent = Intent(context, LibraryUpdateService::class.java).apply {
putExtra(UPDATE_IS_MANUAL, isManual) putExtra(UPDATE_DETAILS, details)
category?.let { putExtra(UPDATE_CATEGORY, it.id) } category?.let { putExtra(UPDATE_CATEGORY, it.id) }
} }
context.startService(intent) context.startService(intent)
@ -164,7 +164,16 @@ class LibraryUpdateService : Service() {
subscription?.unsubscribe() subscription?.unsubscribe()
// 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.defer { updateMangaList(getMangaToUpdate(intent)) } subscription = Observable
.defer {
val mangaList = getMangaToUpdate(intent)
// Update either chapter list or manga details.
if (!intent.getBooleanExtra(UPDATE_DETAILS, false))
updateChapterList(mangaList)
else
updateDetails(mangaList)
}
.subscribeOn(Schedulers.io()) .subscribeOn(Schedulers.io())
.subscribe({ .subscribe({
}, { }, {
@ -216,7 +225,7 @@ class LibraryUpdateService : Service() {
* @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.
*/ */
fun updateMangaList(mangaToUpdate: List<Manga>): Observable<Manga> { fun updateChapterList(mangaToUpdate: List<Manga>): Observable<Manga> {
// Initialize the variables holding the progress of the updates. // Initialize the variables holding the progress of the updates.
val count = AtomicInteger(0) val count = AtomicInteger(0)
val newUpdates = ArrayList<Manga>() val newUpdates = ArrayList<Manga>()
@ -266,6 +275,41 @@ class LibraryUpdateService : Service() {
.map { syncChaptersWithSource(db, it, manga, source) } .map { syncChaptersWithSource(db, it, manga, source) }
} }
/**
* 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.
* For each manga it calls [updateManga] and updates the notification showing the current
* progress.
*
* @param mangaToUpdate the list to update
* @return an observable delivering the progress of each update.
*/
fun updateDetails(mangaToUpdate: List<Manga>): Observable<Manga> {
// Initialize the variables holding the progress of the updates.
val count = AtomicInteger(0)
val cancelIntent = PendingIntent.getBroadcast(this, 0,
Intent(this, CancelUpdateReceiver::class.java), 0)
// Emit each manga and update it sequentially.
return Observable.from(mangaToUpdate)
// Notify manga that will update.
.doOnNext { showProgressNotification(it, count.andIncrement, mangaToUpdate.size, cancelIntent) }
// Update the details of the manga.
.concatMap { manga ->
val source = sourceManager.get(manga.source) as? OnlineSource
?: return@concatMap Observable.empty<Manga>()
source.fetchMangaDetails(manga).doOnNext { networkManga ->
manga.copyFrom(networkManga)
db.insertManga(manga).executeAsBlocking()
}
}
.doOnCompleted {
cancelNotification()
}
}
/** /**
* Returns the text that will be displayed in the notification when there are new chapters. * Returns the text that will be displayed in the notification when there are new chapters.
* *

View File

@ -101,7 +101,7 @@ class LibraryCategoryView @JvmOverloads constructor(context: Context, attrs: Att
swipe_refresh.setDistanceToTriggerSync((2 * 64 * resources.displayMetrics.density).toInt()) swipe_refresh.setDistanceToTriggerSync((2 * 64 * resources.displayMetrics.density).toInt())
swipe_refresh.setOnRefreshListener { swipe_refresh.setOnRefreshListener {
if (!LibraryUpdateService.isRunning(context)) { if (!LibraryUpdateService.isRunning(context)) {
LibraryUpdateService.start(context, true, category) LibraryUpdateService.start(context, category)
context.toast(R.string.updating_category) context.toast(R.string.updating_category)
} }
// It can be a very long operation, so we disable swipe refresh and show a toast. // It can be a very long operation, so we disable swipe refresh and show a toast.

View File

@ -241,7 +241,7 @@ class LibraryFragment : BaseRxFragment<LibraryPresenter>(), ActionMode.Callback
} }
R.id.action_library_display_mode -> swapDisplayMode() R.id.action_library_display_mode -> swapDisplayMode()
R.id.action_update_library -> { R.id.action_update_library -> {
LibraryUpdateService.start(activity, true) LibraryUpdateService.start(activity)
} }
R.id.action_edit_categories -> { R.id.action_edit_categories -> {
val intent = CategoryActivity.newIntent(activity) val intent = CategoryActivity.newIntent(activity)

View File

@ -7,6 +7,7 @@ import com.afollestad.materialdialogs.MaterialDialog
import eu.kanade.tachiyomi.R 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.network.NetworkHelper import eu.kanade.tachiyomi.data.network.NetworkHelper
import eu.kanade.tachiyomi.util.plusAssign import eu.kanade.tachiyomi.util.plusAssign
import eu.kanade.tachiyomi.util.toast import eu.kanade.tachiyomi.util.toast
@ -38,6 +39,8 @@ class SettingsAdvancedFragment : SettingsFragment() {
private val clearCookies by lazy { findPreference(getString(R.string.pref_clear_cookies_key)) } private val clearCookies by lazy { findPreference(getString(R.string.pref_clear_cookies_key)) }
private val refreshMetadata by lazy { findPreference(getString(R.string.pref_refresh_library_metadata_key)) }
override fun onViewCreated(view: View, savedState: Bundle?) { override fun onViewCreated(view: View, savedState: Bundle?) {
super.onViewCreated(view, savedState) super.onViewCreated(view, savedState)
@ -57,6 +60,11 @@ class SettingsAdvancedFragment : SettingsFragment() {
clearDatabase() clearDatabase()
true true
} }
refreshMetadata.setOnPreferenceClickListener {
LibraryUpdateService.start(context, details = true)
true
}
} }
private fun clearChapterCache() { private fun clearChapterCache() {

View File

@ -15,6 +15,7 @@ import eu.kanade.tachiyomi.widget.preference.LibraryColumnsDialog
import eu.kanade.tachiyomi.widget.preference.SimpleDialogPreference import eu.kanade.tachiyomi.widget.preference.SimpleDialogPreference
import net.xpece.android.support.preference.MultiSelectListPreference import net.xpece.android.support.preference.MultiSelectListPreference
import rx.Observable import rx.Observable
import rx.android.schedulers.AndroidSchedulers
import uy.kohesive.injekt.injectLazy import uy.kohesive.injekt.injectLazy
class SettingsGeneralFragment : SettingsFragment(), class SettingsGeneralFragment : SettingsFragment(),
@ -76,6 +77,15 @@ class SettingsGeneralFragment : SettingsFragment(),
true true
} }
updateRestriction.setOnPreferenceChangeListener { preference, newValue ->
// Post to event looper to allow the preference to be updated.
subscriptions += Observable.fromCallable {
LibraryUpdateTrigger.setupTask(context)
}.subscribeOn(AndroidSchedulers.mainThread()).subscribe()
true
}
val dbCategories = db.getCategories().executeAsBlocking() val dbCategories = db.getCategories().executeAsBlocking()
categoryUpdate.apply { categoryUpdate.apply {
entries = dbCategories.map { it.name }.toTypedArray() entries = dbCategories.map { it.name }.toTypedArray()

View File

@ -57,6 +57,7 @@
<string name="pref_clear_chapter_cache_key">pref_clear_chapter_cache_key</string> <string name="pref_clear_chapter_cache_key">pref_clear_chapter_cache_key</string>
<string name="pref_clear_database_key">pref_clear_database_key</string> <string name="pref_clear_database_key">pref_clear_database_key</string>
<string name="pref_clear_cookies_key">pref_clear_cookies_key</string> <string name="pref_clear_cookies_key">pref_clear_cookies_key</string>
<string name="pref_refresh_library_metadata_key">refresh_library_metadata</string>
<string name="pref_version">pref_version</string> <string name="pref_version">pref_version</string>
<string name="pref_build_time">pref_build_time</string> <string name="pref_build_time">pref_build_time</string>

View File

@ -176,8 +176,8 @@
<string name="pref_clear_database_summary">Delete manga and chapters that are not in your library</string> <string name="pref_clear_database_summary">Delete manga and chapters that are not in your library</string>
<string name="clear_database_confirmation">Are you sure? Read chapters and progress of non-library manga will be lost</string> <string name="clear_database_confirmation">Are you sure? Read chapters and progress of non-library manga will be lost</string>
<string name="clear_database_completed">Entries deleted</string> <string name="clear_database_completed">Entries deleted</string>
<string name="pref_show_warning_message">Show warnings</string> <string name="pref_refresh_library_metadata">Refresh library metadata</string>
<string name="pref_show_warning_message_summary">Show warning messages during library sync </string> <string name="pref_refresh_library_metadata_summary">Updates covers, genres, description and manga status information</string>
<string name="pref_reencode">Reencode images</string> <string name="pref_reencode">Reencode images</string>
<string name="pref_reencode_summary">Enable reencoding if images can\'t be decoded. Expect best results with Skia</string> <string name="pref_reencode_summary">Enable reencoding if images can\'t be decoded. Expect best results with Skia</string>

View File

@ -20,6 +20,11 @@
android:summary="@string/pref_clear_database_summary" android:summary="@string/pref_clear_database_summary"
android:title="@string/pref_clear_database"/> android:title="@string/pref_clear_database"/>
<Preference
android:key="@string/pref_refresh_library_metadata_key"
android:summary="@string/pref_refresh_library_metadata_summary"
android:title="@string/pref_refresh_library_metadata"/>
<SwitchPreference <SwitchPreference
android:defaultValue="false" android:defaultValue="false"
android:key="@string/pref_reencode_key" android:key="@string/pref_reencode_key"

View File

@ -95,7 +95,7 @@ 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.updateMangaList(service.getMangaToUpdate(intent)).subscribe() service.updateChapterList(service.getMangaToUpdate(intent)).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)