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.R
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.preference.PreferencesHelper
import eu.kanade.tachiyomi.data.source.SourceManager
@ -36,38 +37,51 @@ import javax.inject.Inject
*/
class LibraryUpdateService : Service() {
// Dependencies injected through dagger.
/**
* Database helper.
*/
@Inject lateinit var db: DatabaseHelper
/**
* Source manager.
*/
@Inject lateinit var sourceManager: SourceManager
/**
* Preferences.
*/
@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
// Subscription where the update is done.
/**
* Subscription where the update is done.
*/
private var subscription: Subscription? = null
companion object {
val UPDATE_NOTIFICATION_ID = 1
// Intent key for manual library update
val UPDATE_IS_MANUAL = "is_manual"
/**
* Id of the library update notification.
*/
const val UPDATE_NOTIFICATION_ID = 1
/**
* Get the start intent for [LibraryUpdateService].
* @param context the application context.
* @param isManual true when user triggers library update.
* @return the intent of the service.
* Key for manual library update.
*/
fun getIntent(context: Context, isManual: Boolean = false): Intent {
return Intent(context, LibraryUpdateService::class.java).apply {
putExtra(UPDATE_IS_MANUAL, isManual)
}
}
const val UPDATE_IS_MANUAL = "is_manual"
/**
* Key for category to update.
*/
const val UPDATE_CATEGORY = "category"
/**
* Returns the status of the service.
*
* @param context the application context.
* @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
* instance already running.
* Starts the service. It will be started only if there isn't another instance already
* running.
*
* @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, isForced: Boolean = false) {
fun start(context: Context, isManual: Boolean = false, category: Category? = null) {
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) {
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.
*/
override fun onDestroy() {
@ -121,9 +146,9 @@ class LibraryUpdateService : Service() {
return null
}
/**
* Method called when the service receives an intent.
*
* @param intent the start intent from.
* @param flags the flags of the 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 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)) {
if (isManualUpdate) {
@ -174,7 +199,7 @@ class LibraryUpdateService : Service() {
subscription?.unsubscribe()
// 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())
.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
* heavy operations or network calls here.
* Returns the list of manga to be updated.
*
* @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
* progress.
*
* @param mangaToUpdate the list to 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.
val count = AtomicInteger(0)
val newUpdates = ArrayList<Manga>()
@ -203,17 +251,10 @@ class LibraryUpdateService : Service() {
val cancelIntent = PendingIntent.getBroadcast(this, 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.
return Observable.from(toUpdate)
return Observable.from(mangaToUpdate)
// 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.
.concatMap { manga ->
updateManga(manga)
@ -241,6 +282,7 @@ class LibraryUpdateService : Service() {
/**
* Updates the chapters for the given manga and adds them to the database.
*
* @param manga the manga to update.
* @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.
*
* @param updates a list of manga that contains new chapters.
* @param failedUpdates a list of manga that failed to update.
* @return the body of the notification to display.
@ -301,6 +344,7 @@ class LibraryUpdateService : Service() {
/**
* Shows the notification with the given title and body.
*
* @param title the title 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.
*
* @param manga the manga that's being updated.
* @param current the current 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.
*
* @param updates a list of manga with new updates.
* @param failed a list of manga that failed to update.
*/
@ -371,6 +417,7 @@ class LibraryUpdateService : Service() {
class SyncOnConnectionAvailable : BroadcastReceiver() {
/**
* Method called when a network change occurs.
*
* @param context the application context.
* @param intent the intent received.
*/
@ -388,6 +435,7 @@ class LibraryUpdateService : Service() {
class SyncOnPowerConnected: BroadcastReceiver() {
/**
* Method called when AC is connected.
*
* @param context the application context.
* @param intent the intent received.
*/
@ -403,6 +451,7 @@ class LibraryUpdateService : Service() {
class CancelUpdateReceiver : BroadcastReceiver() {
/**
* Method called when user wants a library update.
*
* @param context the application context.
* @param intent the intent received.
*/

View file

@ -156,8 +156,8 @@ class LibraryFragment : BaseRxFragment<LibraryPresenter>(), ActionMode.Callback
searchView.clearFocus()
}
filterDownloadedItem.isChecked = isFilterDownloaded;
filterUnreadItem.isChecked = isFilterUnread;
filterDownloadedItem.isChecked = isFilterDownloaded
filterUnreadItem.isChecked = isFilterUnread
searchView.setOnQueryTextListener(object : SearchView.OnQueryTextListener {
override fun onQueryTextSubmit(query: String): Boolean {
@ -200,7 +200,13 @@ class LibraryFragment : BaseRxFragment<LibraryPresenter>(), ActionMode.Callback
// Apply filter
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 -> {
val intent = CategoryActivity.newIntent(activity)
startActivity(intent)
@ -218,7 +224,7 @@ class LibraryFragment : BaseRxFragment<LibraryPresenter>(), ActionMode.Callback
presenter.updateLibrary()
adapter.notifyDataSetChanged()
adapter.refreshRegisteredAdapters()
activity.supportInvalidateOptionsMenu();
activity.supportInvalidateOptionsMenu()
}
/**
@ -249,12 +255,10 @@ class LibraryFragment : BaseRxFragment<LibraryPresenter>(), ActionMode.Callback
// Get the current active category.
val activeCat = if (adapter.categories != null) view_pager.currentItem else activeCategory
// Add the default category if it contains manga.
if (mangaMap[0] != null) {
setCategories(arrayListOf(Category.createDefault()) + categories)
} else {
setCategories(categories)
}
// Set the categories
adapter.categories = categories
tabs.setupWithViewPager(view_pager)
tabs.visibility = if (categories.size <= 1) View.GONE else View.VISIBLE
// Restore active category.
view_pager.setCurrentItem(activeCat, false)
@ -270,17 +274,6 @@ class LibraryFragment : BaseRxFragment<LibraryPresenter>(), ActionMode.Callback
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.
*

View file

@ -98,7 +98,15 @@ class LibraryPresenter : BasePresenter<LibraryFragment>() {
*/
fun getLibraryObservable(): Observable<Pair<List<Category>, Map<Int, List<Manga>>>> {
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())
}
@ -109,7 +117,6 @@ class LibraryPresenter : BasePresenter<LibraryFragment>() {
*/
fun getCategoriesObservable(): Observable<List<Category>> {
return db.getCategories().asRxObservable()
.doOnNext { categories -> this.categories = categories }
}
/**

View file

@ -30,11 +30,16 @@
app:actionViewClass="android.support.v7.widget.SearchView" />
<item
android:id="@+id/action_refresh"
android:title="@string/action_refresh"
android:id="@+id/action_update_library"
android:title="@string/action_update_library"
android:icon="@drawable/ic_refresh_white_24dp"
app:showAsAction="ifRoom" />
<item
android:id="@+id/action_update_category"
android:title="@string/action_update_category"
app:showAsAction="never" />
<item
android:id="@+id/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_empty">Remove filter</string>
<string name="action_search">Search</string>
<string name="action_refresh">Refresh</string>
<string name="action_select_all">Select all</string>
<string name="action_mark_as_read">Mark as read</string>
<string name="action_mark_as_unread">Mark as unread</string>
@ -28,6 +27,8 @@
<string name="action_download">Download</string>
<string name="action_delete">Delete</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_add_category">Add category</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(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)
verify(source, times(3)).pullChaptersFromNetwork((String)any());