Allow to update one category

This commit is contained in:
len 2016-05-05 00:37:03 +02:00
parent 5e24054a0b
commit 1226023dc2
6 changed files with 120 additions and 65 deletions

View File

@ -14,6 +14,7 @@ import com.github.pwittchen.reactivenetwork.library.ReactiveNetwork
import eu.kanade.tachiyomi.App import eu.kanade.tachiyomi.App
import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.database.DatabaseHelper import eu.kanade.tachiyomi.data.database.DatabaseHelper
import eu.kanade.tachiyomi.data.database.models.Category
import eu.kanade.tachiyomi.data.database.models.Manga import eu.kanade.tachiyomi.data.database.models.Manga
import eu.kanade.tachiyomi.data.preference.PreferencesHelper import eu.kanade.tachiyomi.data.preference.PreferencesHelper
import eu.kanade.tachiyomi.data.source.SourceManager import eu.kanade.tachiyomi.data.source.SourceManager
@ -36,38 +37,51 @@ import javax.inject.Inject
*/ */
class LibraryUpdateService : Service() { class LibraryUpdateService : Service() {
// Dependencies injected through dagger. /**
* Database helper.
*/
@Inject lateinit var db: DatabaseHelper @Inject lateinit var db: DatabaseHelper
/**
* Source manager.
*/
@Inject lateinit var sourceManager: SourceManager @Inject lateinit var sourceManager: SourceManager
/**
* Preferences.
*/
@Inject lateinit var preferences: PreferencesHelper @Inject lateinit var preferences: PreferencesHelper
// Wake lock that will be held until the service is destroyed. /**
* Wake lock that will be held until the service is destroyed.
*/
private lateinit var wakeLock: PowerManager.WakeLock private lateinit var wakeLock: PowerManager.WakeLock
// Subscription where the update is done. /**
* Subscription where the update is done.
*/
private var subscription: Subscription? = null private var subscription: Subscription? = null
companion object { companion object {
val UPDATE_NOTIFICATION_ID = 1 /**
* Id of the library update notification.
// Intent key for manual library update */
val UPDATE_IS_MANUAL = "is_manual" const val UPDATE_NOTIFICATION_ID = 1
/** /**
* Get the start intent for [LibraryUpdateService]. * Key for manual library update.
* @param context the application context.
* @param isManual true when user triggers library update.
* @return the intent of the service.
*/ */
fun getIntent(context: Context, isManual: Boolean = false): Intent { const val UPDATE_IS_MANUAL = "is_manual"
return Intent(context, LibraryUpdateService::class.java).apply {
putExtra(UPDATE_IS_MANUAL, isManual) /**
} * Key for category to update.
} */
const val UPDATE_CATEGORY = "category"
/** /**
* Returns the status of the service. * Returns the status of the service.
*
* @param context the application context. * @param context the application context.
* @return true if the service is running, false otherwise. * @return true if the service is running, false otherwise.
*/ */
@ -76,19 +90,30 @@ class LibraryUpdateService : Service() {
} }
/** /**
* Static method to start the service. It will be started only if there isn't another * Starts the service. It will be started only if there isn't another instance already
* instance already 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 all in the library.
*/ */
@JvmStatic fun start(context: Context, isManual: Boolean = false, category: Category? = null) {
fun start(context: Context, isForced: Boolean = false) {
if (!isRunning(context)) { if (!isRunning(context)) {
context.startService(getIntent(context, isForced)) val intent = Intent(context, LibraryUpdateService::class.java).apply {
putExtra(UPDATE_IS_MANUAL, isManual)
category?.let { putExtra(UPDATE_CATEGORY, it.id) }
}
context.startService(intent)
} }
} }
/**
* Stops the service.
*
* @param context the application context.
*/
fun stop(context: Context) { fun stop(context: Context) {
context.stopService(getIntent(context)) context.stopService(Intent(context, LibraryUpdateService::class.java))
} }
} }
@ -104,7 +129,7 @@ class LibraryUpdateService : Service() {
} }
/** /**
* Method called when the service is destroyed. It destroy the running subscription, resets * Method called when the service is destroyed. It destroys the running subscription, resets
* the alarm and release the wake lock. * the alarm and release the wake lock.
*/ */
override fun onDestroy() { override fun onDestroy() {
@ -121,9 +146,9 @@ class LibraryUpdateService : Service() {
return null return null
} }
/** /**
* Method called when the service receives an intent. * Method called when the service receives an intent.
*
* @param intent the start intent from. * @param intent the start intent from.
* @param flags the flags of the command. * @param flags the flags of the command.
* @param startId the start id of this command. * @param startId the start id of this command.
@ -145,7 +170,7 @@ class LibraryUpdateService : Service() {
// Check if device has internet connection // Check if device has internet connection
// Check if device has wifi connection if only wifi is enabled // Check if device has wifi connection if only wifi is enabled
if (connection == ConnectivityStatus.OFFLINE || ("wifi" in restrictions if (connection == ConnectivityStatus.OFFLINE || (!isManualUpdate && "wifi" in restrictions
&& connection != ConnectivityStatus.WIFI_CONNECTED_HAS_INTERNET)) { && connection != ConnectivityStatus.WIFI_CONNECTED_HAS_INTERNET)) {
if (isManualUpdate) { if (isManualUpdate) {
@ -174,7 +199,7 @@ 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 { updateLibrary() } subscription = Observable.defer { updateMangaList(getMangaToUpdate(intent)) }
.subscribeOn(Schedulers.io()) .subscribeOn(Schedulers.io())
.subscribe({}, .subscribe({},
{ {
@ -188,13 +213,36 @@ class LibraryUpdateService : Service() {
} }
/** /**
* Method that updates the library. It's called in a background thread, so it's safe to do * Returns the list of manga to be updated.
* heavy operations or network calls here. *
* @param intent the update intent.
* @return a list of manga to update
*/
fun getMangaToUpdate(intent: Intent?): List<Manga> {
val categoryId = intent?.getIntExtra(UPDATE_CATEGORY, -1) ?: -1
var toUpdate = if (categoryId != -1)
db.getLibraryMangas().executeAsBlocking().filter { it.category == categoryId }
else
db.getFavoriteMangas().executeAsBlocking()
if (preferences.updateOnlyNonCompleted()) {
toUpdate = toUpdate.filter { it.status != Manga.COMPLETED }
}
return toUpdate
}
/**
* Method that updates 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 * For each manga it calls [updateManga] and updates the notification showing the current
* progress. * progress.
*
* @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 updateLibrary(): Observable<Manga> { fun updateMangaList(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>()
@ -203,17 +251,10 @@ class LibraryUpdateService : Service() {
val cancelIntent = PendingIntent.getBroadcast(this, 0, val cancelIntent = PendingIntent.getBroadcast(this, 0,
Intent(this, CancelUpdateReceiver::class.java), 0) Intent(this, CancelUpdateReceiver::class.java), 0)
// Get the manga list that is going to be updated.
val allLibraryMangas = db.getFavoriteMangas().executeAsBlocking()
val toUpdate = if (!preferences.updateOnlyNonCompleted())
allLibraryMangas
else
allLibraryMangas.filter { it.status != Manga.COMPLETED }
// Emit each manga and update it sequentially. // Emit each manga and update it sequentially.
return Observable.from(toUpdate) return Observable.from(mangaToUpdate)
// Notify manga that will update. // Notify manga that will update.
.doOnNext { showProgressNotification(it, count.andIncrement, toUpdate.size, cancelIntent) } .doOnNext { showProgressNotification(it, count.andIncrement, mangaToUpdate.size, cancelIntent) }
// Update the chapters of the manga. // Update the chapters of the manga.
.concatMap { manga -> .concatMap { manga ->
updateManga(manga) updateManga(manga)
@ -241,6 +282,7 @@ class LibraryUpdateService : Service() {
/** /**
* Updates the chapters for the given manga and adds them to the database. * Updates the chapters for the given manga and adds them to the database.
*
* @param manga the manga to update. * @param manga the manga to update.
* @return a pair of the inserted and removed chapters. * @return a pair of the inserted and removed chapters.
*/ */
@ -253,6 +295,7 @@ class LibraryUpdateService : Service() {
/** /**
* 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.
*
* @param updates a list of manga that contains new chapters. * @param updates a list of manga that contains new chapters.
* @param failedUpdates a list of manga that failed to update. * @param failedUpdates a list of manga that failed to update.
* @return the body of the notification to display. * @return the body of the notification to display.
@ -301,6 +344,7 @@ class LibraryUpdateService : Service() {
/** /**
* Shows the notification with the given title and body. * Shows the notification with the given title and body.
*
* @param title the title of the notification. * @param title the title of the notification.
* @param body the body of the notification. * @param body the body of the notification.
*/ */
@ -314,6 +358,7 @@ class LibraryUpdateService : Service() {
/** /**
* Shows the notification containing the currently updating manga and the progress. * Shows the notification containing the currently updating manga and the progress.
*
* @param manga the manga that's being updated. * @param manga the manga that's being updated.
* @param current the current progress. * @param current the current progress.
* @param total the total progress. * @param total the total progress.
@ -331,6 +376,7 @@ class LibraryUpdateService : Service() {
/** /**
* Shows the notification containing the result of the update done by the service. * Shows the notification containing the result of the update done by the service.
*
* @param updates a list of manga with new updates. * @param updates a list of manga with new updates.
* @param failed a list of manga that failed to update. * @param failed a list of manga that failed to update.
*/ */
@ -371,6 +417,7 @@ class LibraryUpdateService : Service() {
class SyncOnConnectionAvailable : BroadcastReceiver() { class SyncOnConnectionAvailable : BroadcastReceiver() {
/** /**
* Method called when a network change occurs. * Method called when a network change occurs.
*
* @param context the application context. * @param context the application context.
* @param intent the intent received. * @param intent the intent received.
*/ */
@ -388,6 +435,7 @@ class LibraryUpdateService : Service() {
class SyncOnPowerConnected: BroadcastReceiver() { class SyncOnPowerConnected: BroadcastReceiver() {
/** /**
* Method called when AC is connected. * Method called when AC is connected.
*
* @param context the application context. * @param context the application context.
* @param intent the intent received. * @param intent the intent received.
*/ */
@ -403,6 +451,7 @@ class LibraryUpdateService : Service() {
class CancelUpdateReceiver : BroadcastReceiver() { class CancelUpdateReceiver : BroadcastReceiver() {
/** /**
* Method called when user wants a library update. * Method called when user wants a library update.
*
* @param context the application context. * @param context the application context.
* @param intent the intent received. * @param intent the intent received.
*/ */

View File

@ -156,8 +156,8 @@ class LibraryFragment : BaseRxFragment<LibraryPresenter>(), ActionMode.Callback
searchView.clearFocus() searchView.clearFocus()
} }
filterDownloadedItem.isChecked = isFilterDownloaded; filterDownloadedItem.isChecked = isFilterDownloaded
filterUnreadItem.isChecked = isFilterUnread; filterUnreadItem.isChecked = isFilterUnread
searchView.setOnQueryTextListener(object : SearchView.OnQueryTextListener { searchView.setOnQueryTextListener(object : SearchView.OnQueryTextListener {
override fun onQueryTextSubmit(query: String): Boolean { override fun onQueryTextSubmit(query: String): Boolean {
@ -200,7 +200,13 @@ class LibraryFragment : BaseRxFragment<LibraryPresenter>(), ActionMode.Callback
// Apply filter // Apply filter
onFilterCheckboxChanged() onFilterCheckboxChanged()
} }
R.id.action_refresh -> LibraryUpdateService.start(activity, true) // Force refresh R.id.action_update_library -> {
LibraryUpdateService.start(activity, true)
}
R.id.action_update_category -> {
val category = presenter.categories[view_pager.currentItem]
LibraryUpdateService.start(activity, true, category)
}
R.id.action_edit_categories -> { R.id.action_edit_categories -> {
val intent = CategoryActivity.newIntent(activity) val intent = CategoryActivity.newIntent(activity)
startActivity(intent) startActivity(intent)
@ -218,7 +224,7 @@ class LibraryFragment : BaseRxFragment<LibraryPresenter>(), ActionMode.Callback
presenter.updateLibrary() presenter.updateLibrary()
adapter.notifyDataSetChanged() adapter.notifyDataSetChanged()
adapter.refreshRegisteredAdapters() adapter.refreshRegisteredAdapters()
activity.supportInvalidateOptionsMenu(); activity.supportInvalidateOptionsMenu()
} }
/** /**
@ -249,12 +255,10 @@ class LibraryFragment : BaseRxFragment<LibraryPresenter>(), ActionMode.Callback
// Get the current active category. // Get the current active category.
val activeCat = if (adapter.categories != null) view_pager.currentItem else activeCategory val activeCat = if (adapter.categories != null) view_pager.currentItem else activeCategory
// Add the default category if it contains manga. // Set the categories
if (mangaMap[0] != null) { adapter.categories = categories
setCategories(arrayListOf(Category.createDefault()) + categories) tabs.setupWithViewPager(view_pager)
} else { tabs.visibility = if (categories.size <= 1) View.GONE else View.VISIBLE
setCategories(categories)
}
// Restore active category. // Restore active category.
view_pager.setCurrentItem(activeCat, false) view_pager.setCurrentItem(activeCat, false)
@ -270,17 +274,6 @@ class LibraryFragment : BaseRxFragment<LibraryPresenter>(), ActionMode.Callback
presenter.libraryMangaSubject.onNext(LibraryMangaEvent(mangaMap)) presenter.libraryMangaSubject.onNext(LibraryMangaEvent(mangaMap))
} }
/**
* Sets the categories in the adapter and the tab layout.
*
* @param categories the categories to set.
*/
private fun setCategories(categories: List<Category>) {
adapter.categories = categories
tabs.setupWithViewPager(view_pager)
tabs.visibility = if (categories.size <= 1) View.GONE else View.VISIBLE
}
/** /**
* Sets the title of the action mode. * Sets the title of the action mode.
* *

View File

@ -98,7 +98,15 @@ class LibraryPresenter : BasePresenter<LibraryFragment>() {
*/ */
fun getLibraryObservable(): Observable<Pair<List<Category>, Map<Int, List<Manga>>>> { fun getLibraryObservable(): Observable<Pair<List<Category>, Map<Int, List<Manga>>>> {
return Observable.combineLatest(getCategoriesObservable(), getLibraryMangasObservable(), return Observable.combineLatest(getCategoriesObservable(), getLibraryMangasObservable(),
{ a, b -> Pair(a, b) }) { dbCategories, libraryManga ->
val categories = if (libraryManga.containsKey(0))
arrayListOf(Category.createDefault()) + dbCategories
else
dbCategories
this.categories = categories
Pair(categories, libraryManga)
})
.observeOn(AndroidSchedulers.mainThread()) .observeOn(AndroidSchedulers.mainThread())
} }
@ -109,7 +117,6 @@ class LibraryPresenter : BasePresenter<LibraryFragment>() {
*/ */
fun getCategoriesObservable(): Observable<List<Category>> { fun getCategoriesObservable(): Observable<List<Category>> {
return db.getCategories().asRxObservable() return db.getCategories().asRxObservable()
.doOnNext { categories -> this.categories = categories }
} }
/** /**

View File

@ -30,11 +30,16 @@
app:actionViewClass="android.support.v7.widget.SearchView" /> app:actionViewClass="android.support.v7.widget.SearchView" />
<item <item
android:id="@+id/action_refresh" android:id="@+id/action_update_library"
android:title="@string/action_refresh" android:title="@string/action_update_library"
android:icon="@drawable/ic_refresh_white_24dp" android:icon="@drawable/ic_refresh_white_24dp"
app:showAsAction="ifRoom" /> app:showAsAction="ifRoom" />
<item
android:id="@+id/action_update_category"
android:title="@string/action_update_category"
app:showAsAction="never" />
<item <item
android:id="@+id/action_edit_categories" android:id="@+id/action_edit_categories"
android:title="@string/action_edit_categories" android:title="@string/action_edit_categories"

View File

@ -20,7 +20,6 @@
<string name="action_filter_unread">Unread</string> <string name="action_filter_unread">Unread</string>
<string name="action_filter_empty">Remove filter</string> <string name="action_filter_empty">Remove filter</string>
<string name="action_search">Search</string> <string name="action_search">Search</string>
<string name="action_refresh">Refresh</string>
<string name="action_select_all">Select all</string> <string name="action_select_all">Select all</string>
<string name="action_mark_as_read">Mark as read</string> <string name="action_mark_as_read">Mark as read</string>
<string name="action_mark_as_unread">Mark as unread</string> <string name="action_mark_as_unread">Mark as unread</string>
@ -28,6 +27,8 @@
<string name="action_download">Download</string> <string name="action_download">Download</string>
<string name="action_delete">Delete</string> <string name="action_delete">Delete</string>
<string name="action_update">Update</string> <string name="action_update">Update</string>
<string name="action_update_library">Update library</string>
<string name="action_update_category">Update active category</string>
<string name="action_edit">Edit</string> <string name="action_edit">Edit</string>
<string name="action_add_category">Add category</string> <string name="action_add_category">Add category</string>
<string name="action_edit_categories">Edit categories</string> <string name="action_edit_categories">Edit categories</string>

View File

@ -95,7 +95,7 @@ public class LibraryUpdateServiceTest {
when(service.db.insertOrRemoveChapters(manga1, chapters, source)).thenReturn(Observable.just(Pair.create(2, 0))); when(service.db.insertOrRemoveChapters(manga1, chapters, source)).thenReturn(Observable.just(Pair.create(2, 0)));
when(service.db.insertOrRemoveChapters(manga3, chapters, source)).thenReturn(Observable.just(Pair.create(2, 0))); when(service.db.insertOrRemoveChapters(manga3, chapters, source)).thenReturn(Observable.just(Pair.create(2, 0)));
service.updateLibrary().subscribe(); service.updateMangaList(service.getMangaToUpdate(null)).subscribe();
// There are 3 network attempts and 2 insertions (1 request failed) // There are 3 network attempts and 2 insertions (1 request failed)
verify(source, times(3)).pullChaptersFromNetwork((String)any()); verify(source, times(3)).pullChaptersFromNetwork((String)any());