package com.quran.labs.androidquran.model.bookmark; import android.support.annotation.UiThread; import com.quran.labs.androidquran.dao.RecentPage; import com.quran.labs.androidquran.data.Constants; import com.quran.labs.androidquran.database.BookmarksDBAdapter; import java.util.List; import javax.inject.Inject; import javax.inject.Singleton; import io.reactivex.Observable; import io.reactivex.Single; import io.reactivex.android.schedulers.AndroidSchedulers; import io.reactivex.observers.DisposableSingleObserver; import io.reactivex.schedulers.Schedulers; import io.reactivex.subjects.BehaviorSubject; import io.reactivex.subjects.PublishSubject; import io.reactivex.subjects.Subject; @Singleton public class RecentPageModel { private final BookmarksDBAdapter bookmarksDBAdapter; private final Subject<Integer> lastPageSubject; private DisposableSingleObserver<List<RecentPage>> initialDataSubscription; private final Observable<Boolean> recentPagesUpdatedObservable; private final Subject<PersistRecentPagesRequest> recentWriterSubject; @Inject public RecentPageModel(BookmarksDBAdapter adapter) { this.bookmarksDBAdapter = adapter; this.lastPageSubject = BehaviorSubject.create(); this.recentWriterSubject = PublishSubject.create(); recentPagesUpdatedObservable = this.recentWriterSubject.hide() .observeOn(Schedulers.io()) .map(update -> { if (update.deleteRangeStart != null) { bookmarksDBAdapter.replaceRecentRangeWithPage( update.deleteRangeStart, update.deleteRangeEnd, update.page); } else { bookmarksDBAdapter.addRecentPage(update.page); } return true; }).share(); // there needs to always be one subscriber in order for us to properly be able // to write updates to the database (even when no one else is subscribed). recentPagesUpdatedObservable.subscribe(); // fetch the first set of "recent pages" initialDataSubscription = getRecentPagesObservable() // this is only to avoid serializing lastPageSubject .observeOn(AndroidSchedulers.mainThread()) .subscribeWith(new DisposableSingleObserver<List<RecentPage>>() { @Override public void onSuccess(List<RecentPage> recentPages) { int page = recentPages.size() > 0 ? recentPages.get(0).page : Constants.NO_PAGE; lastPageSubject.onNext(page); initialDataSubscription = null; } @Override public void onError(Throwable e) { } }); } @UiThread public void updateLatestPage(int page) { if (initialDataSubscription != null) { // it is possible (though unlikely) for a page update to come in while we're still waiting // for the initial query of recent pages from the database. if so, unsubscribe from the initial // query since its data will be stale relative to this data. initialDataSubscription.dispose(); } lastPageSubject.onNext(page); } @UiThread public void persistLatestPage(int minimumPage, int maximumPage, int lastPage) { Integer min = minimumPage == maximumPage ? null : minimumPage; Integer max = min == null ? null : maximumPage; recentWriterSubject.onNext(new PersistRecentPagesRequest(lastPage, min, max)); } /** * Returns an observable of the very latest pages visited * * Note that this stream never terminates, and will return pages even when they have not yet been * persisted to the database. This is basically a stream of every page as it gets visited. * * @return an Observable of the latest pages visited */ public Observable<Integer> getLatestPageObservable() { return lastPageSubject.hide(); } /** * Returns an observable of observing when recent pages change in the database * * Note that this stream never terminates, and will emit onNext when the database has been * updated. Note that writes to the database are typically batched, and as thus, this observable * will fire a lot less than {@link #getLatestPageObservable()}. * * @return an observable that receives events whenever recent pages is persisted */ Observable<Boolean> getRecentPagesUpdatedObservable() { return recentPagesUpdatedObservable; } Single<List<RecentPage>> getRecentPagesObservable() { return Single.fromCallable(bookmarksDBAdapter::getRecentPages) .subscribeOn(Schedulers.io()); } private static class PersistRecentPagesRequest { final int page; final Integer deleteRangeStart; final Integer deleteRangeEnd; PersistRecentPagesRequest(int page, Integer deleteRangeStart, Integer deleteRangeEnd) { this.page = page; this.deleteRangeStart = deleteRangeStart; this.deleteRangeEnd = deleteRangeEnd; } } }