123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288 |
- package eu.kanade.tachiyomi.ui.library
- import android.os.Bundle
- import android.util.Pair
- import eu.kanade.tachiyomi.data.cache.CoverCache
- 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.MangaCategory
- import eu.kanade.tachiyomi.data.download.DownloadManager
- import eu.kanade.tachiyomi.data.preference.PreferencesHelper
- import eu.kanade.tachiyomi.data.preference.getOrDefault
- import eu.kanade.tachiyomi.data.source.SourceManager
- import eu.kanade.tachiyomi.ui.base.presenter.BasePresenter
- import rx.Observable
- import rx.android.schedulers.AndroidSchedulers
- import rx.schedulers.Schedulers
- import rx.subjects.BehaviorSubject
- import java.io.IOException
- import java.io.InputStream
- import java.util.*
- import javax.inject.Inject
- /**
- * Presenter of [LibraryFragment].
- */
- class LibraryPresenter : BasePresenter<LibraryFragment>() {
- /**
- * Categories of the library.
- */
- lateinit var categories: List<Category>
- /**
- * Currently selected manga.
- */
- var selectedMangas = mutableListOf<Manga>()
- /**
- * Search query of the library.
- */
- val searchSubject = BehaviorSubject.create<String>()
- /**
- * Subject to notify the library's viewpager for updates.
- */
- val libraryMangaSubject = BehaviorSubject.create<LibraryMangaEvent>()
- /**
- * Database.
- */
- @Inject lateinit var db: DatabaseHelper
- /**
- * Preferences.
- */
- @Inject lateinit var preferences: PreferencesHelper
- /**
- * Cover cache.
- */
- @Inject lateinit var coverCache: CoverCache
- /**
- * Source manager.
- */
- @Inject lateinit var sourceManager: SourceManager
- /**
- * Download manager.
- */
- @Inject lateinit var downloadManager: DownloadManager
- companion object {
- /**
- * Id of the restartable that listens for library updates.
- */
- const val GET_LIBRARY = 1
- }
- override fun onCreate(savedState: Bundle?) {
- super.onCreate(savedState)
- restartableLatestCache(GET_LIBRARY,
- { getLibraryObservable() },
- { view, pair -> view.onNextLibraryUpdate(pair.first, pair.second) })
- if (savedState == null) {
- start(GET_LIBRARY)
- }
- }
- /**
- * Get the categories and all its manga from the database.
- *
- * @return an observable of the categories and its manga.
- */
- fun getLibraryObservable(): Observable<Pair<List<Category>, Map<Int, List<Manga>>>> {
- return Observable.combineLatest(getCategoriesObservable(), getLibraryMangasObservable(),
- { dbCategories, libraryManga ->
- val categories = if (libraryManga.containsKey(0))
- arrayListOf(Category.createDefault()) + dbCategories
- else
- dbCategories
- this.categories = categories
- Pair(categories, libraryManga)
- })
- .observeOn(AndroidSchedulers.mainThread())
- }
- /**
- * Get the categories from the database.
- *
- * @return an observable of the categories.
- */
- fun getCategoriesObservable(): Observable<List<Category>> {
- return db.getCategories().asRxObservable()
- }
- /**
- * Get the manga grouped by categories.
- *
- * @return an observable containing a map with the category id as key and a list of manga as the
- * value.
- */
- fun getLibraryMangasObservable(): Observable<Map<Int, List<Manga>>> {
- return db.getLibraryMangas().asRxObservable()
- .flatMap { mangas ->
- Observable.from(mangas)
- // Filter library by options
- .filter { filterManga(it) }
- .groupBy { it.category }
- .flatMap { group -> group.toList().map { Pair(group.key, it) } }
- .toMap({ it.first }, { it.second })
- }
- }
- /**
- * Resubscribes to library if needed.
- */
- fun subscribeLibrary() {
- if (isUnsubscribed(GET_LIBRARY)) {
- start(GET_LIBRARY)
- }
- }
- /**
- * Resubscribes to library.
- */
- fun updateLibrary() {
- start(GET_LIBRARY)
- }
- /**
- * Filters an entry of the library.
- *
- * @param manga a favorite manga from the database.
- * @return true if the entry is included, false otherwise.
- */
- fun filterManga(manga: Manga): Boolean {
- // Filter out manga without source
- val source = sourceManager.get(manga.source) ?: return false
- val prefFilterDownloaded = preferences.filterDownloaded().getOrDefault()
- val prefFilterUnread = preferences.filterUnread().getOrDefault()
- // Check if filter option is selected
- if (prefFilterDownloaded || prefFilterUnread) {
- // Does it have downloaded chapters.
- var hasDownloaded = false
- var hasUnread = false
- if (prefFilterUnread) {
- // Does it have unread chapters.
- hasUnread = manga.unread > 0
- }
- if (prefFilterDownloaded) {
- val mangaDir = downloadManager.getAbsoluteMangaDirectory(source, manga)
- if (mangaDir.exists()) {
- for (file in mangaDir.listFiles()) {
- if (file.isDirectory && file.listFiles().isNotEmpty()) {
- hasDownloaded = true
- break
- }
- }
- }
- }
- // Return correct filter status
- if (prefFilterDownloaded && prefFilterUnread) {
- return (hasDownloaded && hasUnread)
- } else {
- return (hasDownloaded || hasUnread)
- }
- } else {
- return true
- }
- }
- /**
- * Called when a manga is opened.
- */
- fun onOpenManga() {
- // Avoid further db updates for the library when it's not needed
- stop(GET_LIBRARY)
- }
- /**
- * Sets the selection for a given manga.
- *
- * @param manga the manga whose selection has changed.
- * @param selected whether it's now selected or not.
- */
- fun setSelection(manga: Manga, selected: Boolean) {
- if (selected) {
- selectedMangas.add(manga)
- } else {
- selectedMangas.remove(manga)
- }
- }
- /**
- * Returns the common categories for the given list of manga.
- *
- * @param mangas the list of manga.
- */
- fun getCommonCategories(mangas: List<Manga>) = mangas.toSet()
- .map { db.getCategoriesForManga(it).executeAsBlocking() }
- .reduce { set1: Iterable<Category>, set2 -> set1.intersect(set2) }
- /**
- * Remove the selected manga from the library.
- */
- fun deleteMangas() {
- // Create a set of the list
- val mangaToDelete = selectedMangas.toSet()
- Observable.from(mangaToDelete)
- .subscribeOn(Schedulers.io())
- .doOnNext {
- it.favorite = false
- coverCache.deleteFromCache(it.thumbnail_url)
- }
- .toList()
- .flatMap { db.insertMangas(it).asRxObservable() }
- .subscribe()
- }
- /**
- * Move the given list of manga to categories.
- *
- * @param categories the selected categories.
- * @param mangas the list of manga to move.
- */
- fun moveMangasToCategories(categories: List<Category>, mangas: List<Manga>) {
- val mc = ArrayList<MangaCategory>()
- for (manga in mangas) {
- for (cat in categories) {
- mc.add(MangaCategory.create(manga, cat))
- }
- }
- db.setMangaCategories(mc, mangas)
- }
- /**
- * Update cover with local file.
- *
- * @param inputStream the new cover.
- * @param manga the manga edited.
- * @return true if the cover is updated, false otherwise
- */
- @Throws(IOException::class)
- fun editCoverWithStream(inputStream: InputStream, manga: Manga): Boolean {
- if (manga.thumbnail_url != null && manga.favorite) {
- coverCache.copyToCache(manga.thumbnail_url, inputStream)
- return true
- }
- return false
- }
- }
|