Database queries are now separated by table. Improve how the app creates downloads

This commit is contained in:
len
2016-05-02 22:33:36 +02:00
parent af2b886599
commit 5e24054a0b
20 changed files with 657 additions and 532 deletions

View File

@@ -4,6 +4,7 @@ import android.animation.Animator
import android.animation.AnimatorListenerAdapter
import android.content.Intent
import android.os.Bundle
import android.support.v4.app.DialogFragment
import android.support.v7.view.ActionMode
import android.view.*
import com.afollestad.materialdialogs.MaterialDialog
@@ -11,7 +12,6 @@ import eu.davidea.flexibleadapter.FlexibleAdapter
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.database.models.Chapter
import eu.kanade.tachiyomi.data.database.models.Manga
import eu.kanade.tachiyomi.data.download.DownloadService
import eu.kanade.tachiyomi.data.download.model.Download
import eu.kanade.tachiyomi.ui.base.adapter.FlexibleViewHolder
import eu.kanade.tachiyomi.ui.base.decoration.DividerItemDecoration
@@ -21,12 +21,11 @@ import eu.kanade.tachiyomi.ui.reader.ReaderActivity
import eu.kanade.tachiyomi.util.getCoordinates
import eu.kanade.tachiyomi.util.getResourceDrawable
import eu.kanade.tachiyomi.util.toast
import eu.kanade.tachiyomi.widget.DeletingChaptersDialog
import eu.kanade.tachiyomi.widget.NpaLinearLayoutManager
import kotlinx.android.synthetic.main.fragment_manga_chapters.*
import nucleus.factory.RequiresPresenter
import rx.Observable
import rx.android.schedulers.AndroidSchedulers
import rx.schedulers.Schedulers
import timber.log.Timber
@RequiresPresenter(ChaptersPresenter::class)
class ChaptersFragment : BaseRxFragment<ChaptersPresenter>(), ActionMode.Callback, FlexibleViewHolder.OnListItemClickListener {
@@ -40,6 +39,7 @@ class ChaptersFragment : BaseRxFragment<ChaptersPresenter>(), ActionMode.Callbac
fun newInstance(): ChaptersFragment {
return ChaptersFragment()
}
}
/**
@@ -73,7 +73,7 @@ class ChaptersFragment : BaseRxFragment<ChaptersPresenter>(), ActionMode.Callbac
swipe_refresh.setOnRefreshListener { fetchChapters() }
fab.setOnClickListener { v ->
fab.setOnClickListener {
val chapter = presenter.getNextUnreadChapter()
if (chapter != null) {
// Create animation listener
@@ -252,7 +252,7 @@ class ChaptersFragment : BaseRxFragment<ChaptersPresenter>(), ActionMode.Callbac
chapters = chapters.subList(0, 10)
}
}
onDownload(Observable.from(chapters))
downloadChapters(chapters)
}
}
.show()
@@ -278,11 +278,11 @@ class ChaptersFragment : BaseRxFragment<ChaptersPresenter>(), ActionMode.Callbac
override fun onActionItemClicked(mode: ActionMode, item: MenuItem): Boolean {
when (item.itemId) {
R.id.action_select_all -> onSelectAll()
R.id.action_mark_as_read -> onMarkAsRead(getSelectedChapters())
R.id.action_mark_as_unread -> onMarkAsUnread(getSelectedChapters())
R.id.action_download -> onDownload(getSelectedChapters())
R.id.action_delete -> onDelete(getSelectedChapters())
R.id.action_select_all -> selectAll()
R.id.action_mark_as_read -> markAsRead(getSelectedChapters())
R.id.action_mark_as_unread -> markAsUnread(getSelectedChapters())
R.id.action_download -> downloadChapters(getSelectedChapters())
R.id.action_delete -> deleteChapters(getSelectedChapters())
else -> return false
}
return true
@@ -294,66 +294,57 @@ class ChaptersFragment : BaseRxFragment<ChaptersPresenter>(), ActionMode.Callbac
actionMode = null
}
fun getSelectedChapters(): Observable<Chapter> {
val chapters = adapter.selectedItems.map { adapter.getItem(it) }
return Observable.from(chapters)
fun getSelectedChapters(): List<Chapter> {
return adapter.selectedItems.map { adapter.getItem(it) }
}
fun destroyActionModeIfNeeded() {
actionMode?.finish()
}
protected fun onSelectAll() {
fun selectAll() {
adapter.selectAll()
setContextTitle(adapter.selectedItemCount)
}
fun onMarkAsRead(chapters: Observable<Chapter>) {
fun markAsRead(chapters: List<Chapter>) {
presenter.markChaptersRead(chapters, true)
if (presenter.preferences.removeAfterMarkedAsRead()) {
deleteChapters(chapters)
}
}
fun onMarkAsUnread(chapters: Observable<Chapter>) {
fun markAsUnread(chapters: List<Chapter>) {
presenter.markChaptersRead(chapters, false)
}
fun onMarkPreviousAsRead(chapter: Chapter) {
fun markPreviousAsRead(chapter: Chapter) {
presenter.markPreviousChaptersAsRead(chapter)
}
fun onDownload(chapters: Observable<Chapter>) {
DownloadService.start(activity)
val observable = chapters.doOnCompleted { adapter.notifyDataSetChanged() }
presenter.downloadChapters(observable)
fun downloadChapters(chapters: List<Chapter>) {
destroyActionModeIfNeeded()
presenter.downloadChapters(chapters)
}
fun onDelete(chapters: Observable<Chapter>) {
val size = adapter.selectedItemCount
val dialog = MaterialDialog.Builder(activity)
.title(R.string.deleting)
.progress(false, size, true)
.cancelable(false)
.show()
val observable = chapters
.concatMap { chapter ->
presenter.deleteChapter(chapter)
Observable.just(chapter)
}
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.doOnNext { chapter ->
dialog.incrementProgress(1)
chapter.status = Download.NOT_DOWNLOADED
}
.doOnCompleted { adapter.notifyDataSetChanged() }
.doAfterTerminate { dialog.dismiss() }
presenter.deleteChapters(observable)
fun deleteChapters(chapters: List<Chapter>) {
destroyActionModeIfNeeded()
DeletingChaptersDialog().show(childFragmentManager, DeletingChaptersDialog.TAG)
presenter.deleteChapters(chapters)
}
fun onChaptersDeleted() {
dismissDeletingDialog()
adapter.notifyDataSetChanged()
}
fun onChaptersDeletedError(error: Throwable) {
dismissDeletingDialog()
Timber.e(error, error.message)
}
fun dismissDeletingDialog() {
(childFragmentManager.findFragmentByTag(DeletingChaptersDialog.TAG) as? DialogFragment)?.dismiss()
}
override fun onListItemClick(position: Int): Boolean {

View File

@@ -10,14 +10,16 @@ import eu.kanade.tachiyomi.data.download.model.Download
import eu.kanade.tachiyomi.ui.base.adapter.FlexibleViewHolder
import eu.kanade.tachiyomi.util.getResourceColor
import kotlinx.android.synthetic.main.item_chapter.view.*
import rx.Observable
import java.text.DateFormat
import java.text.DecimalFormat
import java.text.DecimalFormatSymbols
import java.util.*
class ChaptersHolder(private val view: View, private val adapter: ChaptersAdapter, listener: FlexibleViewHolder.OnListItemClickListener) :
FlexibleViewHolder(view, adapter, listener) {
class ChaptersHolder(
private val view: View,
private val adapter: ChaptersAdapter,
listener: FlexibleViewHolder.OnListItemClickListener)
: FlexibleViewHolder(view, adapter, listener) {
private val readColor = view.context.theme.getResourceColor(android.R.attr.textColorHint)
private val unreadColor = view.context.theme.getResourceColor(android.R.attr.textColorPrimary)
@@ -27,7 +29,10 @@ class ChaptersHolder(private val view: View, private val adapter: ChaptersAdapte
private var item: Chapter? = null
init {
view.chapter_menu.setOnClickListener { v -> v.post { showPopupMenu(v) } }
// We need to post a Runnable to show the popup to make sure that the PopupMenu is
// correctly positioned. The reason being that the view may change position before the
// PopupMenu is shown.
view.chapter_menu.setOnClickListener { it.post { showPopupMenu(it) } }
}
fun onSetValues(chapter: Chapter, manga: Manga?) = with(view) {
@@ -101,14 +106,14 @@ class ChaptersHolder(private val view: View, private val adapter: ChaptersAdapte
// Set a listener so we are notified if a menu item is clicked
popup.setOnMenuItemClickListener { menuItem ->
val chapter = Observable.just(item)
val chapter = listOf(item)
when (menuItem.itemId) {
R.id.action_download -> adapter.fragment.onDownload(chapter)
R.id.action_delete -> adapter.fragment.onDelete(chapter)
R.id.action_mark_as_read -> adapter.fragment.onMarkAsRead(chapter)
R.id.action_mark_as_unread -> adapter.fragment.onMarkAsUnread(chapter)
R.id.action_mark_previous_as_read -> adapter.fragment.onMarkPreviousAsRead(item)
R.id.action_download -> adapter.fragment.downloadChapters(chapter)
R.id.action_delete -> adapter.fragment.deleteChapters(chapter)
R.id.action_mark_as_read -> adapter.fragment.markAsRead(chapter)
R.id.action_mark_as_unread -> adapter.fragment.markAsUnread(chapter)
R.id.action_mark_previous_as_read -> adapter.fragment.markPreviousAsRead(item)
}
true
}

View File

@@ -6,14 +6,13 @@ import eu.kanade.tachiyomi.data.database.DatabaseHelper
import eu.kanade.tachiyomi.data.database.models.Chapter
import eu.kanade.tachiyomi.data.database.models.Manga
import eu.kanade.tachiyomi.data.download.DownloadManager
import eu.kanade.tachiyomi.data.download.DownloadService
import eu.kanade.tachiyomi.data.download.model.Download
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
import eu.kanade.tachiyomi.data.source.SourceManager
import eu.kanade.tachiyomi.data.source.base.Source
import eu.kanade.tachiyomi.event.ChapterCountEvent
import eu.kanade.tachiyomi.event.DownloadChaptersEvent
import eu.kanade.tachiyomi.event.MangaEvent
import eu.kanade.tachiyomi.event.ReaderEvent
import eu.kanade.tachiyomi.ui.base.presenter.BasePresenter
import eu.kanade.tachiyomi.util.SharedData
import rx.Observable
@@ -163,50 +162,59 @@ class ChaptersPresenter : BasePresenter<ChaptersFragment>() {
return db.getNextUnreadChapter(manga).executeAsBlocking()
}
fun markChaptersRead(selectedChapters: Observable<Chapter>, read: Boolean) {
add(selectedChapters.subscribeOn(Schedulers.io())
fun markChaptersRead(selectedChapters: List<Chapter>, read: Boolean) {
Observable.from(selectedChapters)
.doOnNext { chapter ->
chapter.read = read
if (!read) chapter.last_page_read = 0
// Delete chapter when marked as read if desired by user.
if (preferences.removeAfterMarkedAsRead() && read) {
deleteChapter(chapter)
if (!read) {
chapter.last_page_read = 0
}
}
.toList()
.flatMap { chapters -> db.insertChapters(chapters).asRxObservable() }
.observeOn(AndroidSchedulers.mainThread())
.subscribe())
.flatMap { db.insertChapters(it).asRxObservable() }
.subscribeOn(Schedulers.io())
.subscribe()
}
fun markPreviousChaptersAsRead(selected: Chapter) {
Observable.from(chapters)
.filter { c -> c.chapter_number > -1 && c.chapter_number < selected.chapter_number }
.doOnNext { c -> c.read = true }
.filter { it.chapter_number > -1 && it.chapter_number < selected.chapter_number }
.doOnNext { it.read = true }
.toList()
.flatMap { chapters -> db.insertChapters(chapters).asRxObservable() }
.flatMap { db.insertChapters(it).asRxObservable() }
.subscribe()
}
fun downloadChapters(selectedChapters: Observable<Chapter>) {
add(selectedChapters.toList()
fun downloadChapters(chapters: List<Chapter>) {
DownloadService.start(context)
downloadManager.downloadChapters(manga, chapters)
}
fun deleteChapters(chapters: List<Chapter>) {
val wasRunning = downloadManager.isRunning
if (wasRunning) {
DownloadService.stop(context)
}
Observable.from(chapters)
.doOnNext { deleteChapter(it) }
.toList()
.doOnNext { if (onlyDownloaded()) refreshChapters() }
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe { downloadManager.onDownloadChaptersEvent(DownloadChaptersEvent(manga, it)) })
.subscribeFirst({ view, result ->
view.onChaptersDeleted()
if (wasRunning) {
DownloadService.start(context)
}
}, { view, error ->
view.onChaptersDeletedError(error)
})
}
fun deleteChapters(selectedChapters: Observable<Chapter>) {
add(selectedChapters.subscribe(
{ chapter -> downloadManager.queue.del(chapter) },
{ error -> Timber.e(error.message) },
{
if (onlyDownloaded())
refreshChapters()
}))
}
fun deleteChapter(chapter: Chapter) {
private fun deleteChapter(chapter: Chapter) {
downloadManager.queue.del(chapter)
downloadManager.deleteChapter(source, manga, chapter)
chapter.status = Download.NOT_DOWNLOADED
}
fun revertSortOrder() {

View File

@@ -336,7 +336,7 @@ class ReaderPresenter : BasePresenter<ReaderActivity>() {
}
}
}
db.insertChapter(chapter).asRxObservable().subscribe()
db.updateChapterProgress(chapter).asRxObservable().subscribe()
}
/**

View File

@@ -1,14 +1,12 @@
package eu.kanade.tachiyomi.ui.recent
import android.os.Bundle
import android.support.v4.app.DialogFragment
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.database.models.Chapter
import eu.kanade.tachiyomi.data.database.models.Manga
import eu.kanade.tachiyomi.data.database.models.MangaChapter
import eu.kanade.tachiyomi.data.download.DownloadService
import eu.kanade.tachiyomi.data.download.model.Download
import eu.kanade.tachiyomi.ui.base.adapter.FlexibleViewHolder
import eu.kanade.tachiyomi.ui.base.decoration.DividerItemDecoration
@@ -16,12 +14,11 @@ import eu.kanade.tachiyomi.ui.base.fragment.BaseRxFragment
import eu.kanade.tachiyomi.ui.main.MainActivity
import eu.kanade.tachiyomi.ui.reader.ReaderActivity
import eu.kanade.tachiyomi.util.getResourceDrawable
import eu.kanade.tachiyomi.widget.DeletingChaptersDialog
import eu.kanade.tachiyomi.widget.NpaLinearLayoutManager
import kotlinx.android.synthetic.main.fragment_recent_chapters.*
import nucleus.factory.RequiresPresenter
import rx.Observable
import rx.android.schedulers.AndroidSchedulers
import rx.schedulers.Schedulers
import timber.log.Timber
/**
* Fragment that shows recent chapters.
@@ -143,78 +140,57 @@ class RecentChaptersFragment : BaseRxFragment<RecentChaptersPresenter>(), Flexib
}
/**
* Start downloading chapter
* @param chapters selected chapters
* @param manga manga that belongs to chapter
* @return true
* Mark chapter as read
*
* @param item selected chapter with manga
*/
fun onDownload(chapters: Observable<Chapter>, manga: Manga): Boolean {
// Start the download service.
DownloadService.start(activity)
fun markAsRead(item: MangaChapter) {
presenter.markChapterRead(item.chapter, true)
if (presenter.preferences.removeAfterMarkedAsRead()) {
deleteChapter(item)
}
}
// Refresh data on download competition.
val observable = chapters
.doOnCompleted({
adapter.notifyDataSetChanged()
presenter.start(presenter.CHAPTER_STATUS_CHANGES)
})
/**
* Mark chapter as unread
*
* @param item selected chapter with manga
*/
fun markAsUnread(item: MangaChapter) {
presenter.markChapterRead(item.chapter, false)
}
// Download chapter.
presenter.downloadChapter(observable, manga)
return true
/**
* Start downloading chapter
*
* @param item selected chapter with manga
*/
fun downloadChapter(item: MangaChapter) {
presenter.downloadChapter(item)
}
/**
* Start deleting chapter
*
* @param chapters selected chapters
* @param manga manga that belongs to chapter
* @return success of deletion.
* @param item selected chapter with manga
*/
fun onDelete(chapters: Observable<Chapter>, manga: Manga): Boolean {
//Create observable
val observable = chapters
.concatMap { chapter ->
presenter.deleteChapter(chapter, manga)
Observable.just(chapter)
}
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.doOnNext { chapter ->
chapter.status = Download.NOT_DOWNLOADED
}
.doOnCompleted { adapter.notifyDataSetChanged() }
// Delete chapters with observable
presenter.deleteChapters(observable)
return true
fun deleteChapter(item: MangaChapter) {
DeletingChaptersDialog().show(childFragmentManager, DeletingChaptersDialog.TAG)
presenter.deleteChapter(item)
}
/**
* Mark chapter as read
* @param chapters selected chapter
* @return true
*/
fun onMarkAsRead(chapters: Observable<Chapter>, manga : Manga): Boolean {
// Set marked as read
presenter.markChaptersRead(chapters, manga, true)
return true
fun onChaptersDeleted() {
dismissDeletingDialog()
adapter.notifyDataSetChanged()
}
/**
* Mark chapter as unread
* @param chapters selected chapter
* @return true
*/
fun onMarkAsUnread(chapters: Observable<Chapter> , manga : Manga): Boolean {
// Set marked as unread
presenter.markChaptersRead(chapters, manga, false)
return true
fun onChaptersDeletedError(error: Throwable) {
dismissDeletingDialog()
Timber.e(error, error.message)
}
fun dismissDeletingDialog() {
(childFragmentManager.findFragmentByTag(DeletingChaptersDialog.TAG) as? DialogFragment)?.dismiss()
}
}

View File

@@ -1,16 +1,13 @@
package eu.kanade.tachiyomi.ui.recent
import android.content.Context
import android.view.View
import android.widget.PopupMenu
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.database.models.Chapter
import eu.kanade.tachiyomi.data.database.models.MangaChapter
import eu.kanade.tachiyomi.data.download.model.Download
import eu.kanade.tachiyomi.ui.base.adapter.FlexibleViewHolder
import eu.kanade.tachiyomi.util.getResourceColor
import kotlinx.android.synthetic.main.item_recent_chapter.view.*
import rx.Observable
/**
* Holder that contains chapter item
@@ -32,7 +29,7 @@ class RecentChaptersHolder(view: View, private val adapter: RecentChaptersAdapte
/**
* Color of unread chapter
*/
private var unreadColor = view.context.theme.getResourceColor(android.R.attr.textColorPrimary)
private var unreadColor = view.context.theme.getResourceColor(android.R.attr.textColorPrimary)
/**
* Object containing chapter information
@@ -40,9 +37,10 @@ class RecentChaptersHolder(view: View, private val adapter: RecentChaptersAdapte
private var mangaChapter: MangaChapter? = null
init {
//Set OnClickListener for download menu
itemView.chapterMenu.setOnClickListener { v -> v.post({ showPopupMenu(v) }) }
// We need to post a Runnable to show the popup to make sure that the PopupMenu is
// correctly positioned. The reason being that the view may change position before the
// PopupMenu is shown.
itemView.chapterMenu.setOnClickListener { it.post({ showPopupMenu(it) }) }
}
/**
@@ -120,15 +118,14 @@ class RecentChaptersHolder(view: View, private val adapter: RecentChaptersAdapte
// Set a listener so we are notified if a menu item is clicked
popup.setOnMenuItemClickListener { menuItem ->
val chapterObservable = Observable.just<Chapter>(it.chapter)
when (menuItem.itemId) {
R.id.action_download -> adapter.fragment.onDownload(chapterObservable, it.manga)
R.id.action_delete -> adapter.fragment.onDelete(chapterObservable, it.manga)
R.id.action_mark_as_read -> adapter.fragment.onMarkAsRead(chapterObservable, it.manga);
R.id.action_mark_as_unread -> adapter.fragment.onMarkAsUnread(chapterObservable, it.manga);
R.id.action_download -> adapter.fragment.downloadChapter(it)
R.id.action_delete -> adapter.fragment.deleteChapter(it)
R.id.action_mark_as_read -> adapter.fragment.markAsRead(it)
R.id.action_mark_as_unread -> adapter.fragment.markAsUnread(it)
}
false
true
}
}

View File

@@ -6,10 +6,10 @@ import eu.kanade.tachiyomi.data.database.models.Chapter
import eu.kanade.tachiyomi.data.database.models.Manga
import eu.kanade.tachiyomi.data.database.models.MangaChapter
import eu.kanade.tachiyomi.data.download.DownloadManager
import eu.kanade.tachiyomi.data.download.DownloadService
import eu.kanade.tachiyomi.data.download.model.Download
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
import eu.kanade.tachiyomi.data.source.SourceManager
import eu.kanade.tachiyomi.event.DownloadChaptersEvent
import eu.kanade.tachiyomi.ui.base.presenter.BasePresenter
import rx.Observable
import rx.android.schedulers.AndroidSchedulers
@@ -250,59 +250,69 @@ class RecentChaptersPresenter : BasePresenter<RecentChaptersFragment>() {
}
/**
* Download selected chapter
* @param selectedChapter chapter that is selected
* *
* @param manga manga that belongs to chapter
* Mark selected chapter as read
*
* @param chapter selected chapter
* @param read read status
*/
fun downloadChapter(selectedChapter: Observable<Chapter>, manga: Manga) {
add(selectedChapter.toList()
.observeOn(AndroidSchedulers.mainThread())
.subscribe { downloadManager.onDownloadChaptersEvent(DownloadChaptersEvent(manga, it)) })
fun markChapterRead(chapter: Chapter, read: Boolean) {
Observable.just(chapter)
.doOnNext { chapter ->
chapter.read = read
if (!read) {
chapter.last_page_read = 0
}
}
.flatMap { db.updateChapterProgress(it).asRxObservable() }
.subscribeOn(Schedulers.io())
.subscribe()
}
/**
* Download selected chapter
*
* @param item chapter that is selected
*/
fun downloadChapter(item: MangaChapter) {
DownloadService.start(context)
downloadManager.downloadChapters(item.manga, listOf(item.chapter))
}
/**
* Delete selected chapter
*
* @param item chapter that are selected
*/
fun deleteChapter(item: MangaChapter) {
val wasRunning = downloadManager.isRunning
if (wasRunning) {
DownloadService.stop(context)
}
Observable.just(item)
.doOnNext { deleteChapter(it.chapter, it.manga) }
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribeFirst({ view, result ->
view.onChaptersDeleted()
if (wasRunning) {
DownloadService.start(context)
}
}, { view, error ->
view.onChaptersDeletedError(error)
})
}
/**
* Delete selected chapter
*
* @param chapter chapter that is selected
* *
* @param manga manga that belongs to chapter
*/
fun deleteChapter(chapter: Chapter, manga: Manga) {
val source = sourceManager.get(manga.source)!!
private fun deleteChapter(chapter: Chapter, manga: Manga) {
val source = sourceManager.get(manga.source) ?: return
downloadManager.queue.del(chapter)
downloadManager.deleteChapter(source, manga, chapter)
chapter.status = Download.NOT_DOWNLOADED
}
/**
* Delete selected chapter observable
* @param selectedChapters chapter that are selected
*/
fun deleteChapters(selectedChapters: Observable<Chapter>) {
add(selectedChapters
.subscribe(
{ chapter -> downloadManager.queue.del(chapter) })
{ error -> Timber.e(error.message) })
}
/**
* Mark selected chapter as read
* @param selectedChapters chapter that is selected
* *
* @param read read status
*/
fun markChaptersRead(selectedChapters: Observable<Chapter>, manga: Manga, read: Boolean) {
add(selectedChapters.subscribeOn(Schedulers.io())
.doOnNext { chapter ->
chapter.read = read
if (!read) chapter.last_page_read = 0
// Delete chapter when marked as read if desired by user.
if (preferences.removeAfterMarkedAsRead() && read) {
deleteChapter(chapter,manga)
}
}
.toList()
.flatMap { chapters -> db.insertChapters(chapters).asRxObservable() }
.observeOn(AndroidSchedulers.mainThread())
.subscribe())
}
}