diff --git a/app/src/main/java/org/bspeice/minimalbible/activity/viewer/BibleViewerModules.java b/app/src/main/java/org/bspeice/minimalbible/activity/viewer/BibleViewerModules.java index 182d766..b28da15 100644 --- a/app/src/main/java/org/bspeice/minimalbible/activity/viewer/BibleViewerModules.java +++ b/app/src/main/java/org/bspeice/minimalbible/activity/viewer/BibleViewerModules.java @@ -2,7 +2,8 @@ package org.bspeice.minimalbible.activity.viewer; import android.util.Log; -import org.bspeice.minimalbible.service.book.VerseLookupModules; +import org.bspeice.minimalbible.service.lookup.DefaultVerseLookup; +import org.bspeice.minimalbible.service.lookup.VerseLookup; import org.bspeice.minimalbible.service.manager.BookManager; import org.crosswire.jsword.book.Book; @@ -26,8 +27,7 @@ import rx.functions.Func1; BibleViewer.class, BookFragment.class, BookChapterNavFragment.class - }, - includes = VerseLookupModules.class + } ) @SuppressWarnings("unused") public class BibleViewerModules { @@ -96,4 +96,10 @@ public class BibleViewerModules { BookManager bookManager() { return new BookManager(); } + + @Provides + @Singleton + VerseLookup verseLookup(@Named("MainBook") Book b) { + return new DefaultVerseLookup(b); + } } \ No newline at end of file diff --git a/app/src/main/java/org/bspeice/minimalbible/activity/viewer/BookFragment.java b/app/src/main/java/org/bspeice/minimalbible/activity/viewer/BookFragment.java index a62a32a..8b62c9b 100644 --- a/app/src/main/java/org/bspeice/minimalbible/activity/viewer/BookFragment.java +++ b/app/src/main/java/org/bspeice/minimalbible/activity/viewer/BookFragment.java @@ -12,7 +12,7 @@ import android.webkit.WebView; import org.bspeice.minimalbible.Injector; import org.bspeice.minimalbible.R; import org.bspeice.minimalbible.activity.BaseFragment; -import org.bspeice.minimalbible.service.book.VerseLookupService; +import org.bspeice.minimalbible.service.lookup.VerseLookup; import org.crosswire.jsword.book.Book; import javax.inject.Inject; @@ -20,7 +20,6 @@ import javax.inject.Named; import butterknife.ButterKnife; import butterknife.InjectView; -import dagger.Lazy; import rx.android.schedulers.AndroidSchedulers; import rx.functions.Action1; import rx.subjects.PublishSubject; @@ -35,18 +34,15 @@ public class BookFragment extends BaseFragment { Injector i; @Inject @Named("MainBook") - Lazy mBook; + Book mBook; + @Inject + VerseLookup verseLookup; @InjectView(R.id.book_content) WebView mainContent; PublishSubject titleReceiver = PublishSubject.create(); - public BookFragment() { - // We can't initialize the lookupService here since the fragment hasn't been tied - // to the parent activity yet. - } - /** * Returns a new instance of this fragment for the given book. */ @@ -76,7 +72,7 @@ public class BookFragment extends BaseFragment { // TODO: Load initial text from SharedPreferences, rather than getting the actual book. - displayBook(mBook.get()); + displayBook(mBook); return rootView; } @@ -103,8 +99,7 @@ public class BookFragment extends BaseFragment { ((BibleViewer)getActivity()).setActionBarTitle(b.getInitials()); mainContent.loadUrl(getString(R.string.book_html)); - VerseLookupService lookupService = new VerseLookupService(i, mBook.get()); - BibleViewClient client = new BibleViewClient(b, lookupService, titleReceiver); + BibleViewClient client = new BibleViewClient(b, verseLookup, titleReceiver); titleReceiver .observeOn(AndroidSchedulers.mainThread()) .subscribe(new Action1() { diff --git a/app/src/main/java/org/bspeice/minimalbible/exception/NoBooksInstalledException.java b/app/src/main/java/org/bspeice/minimalbible/exception/NoBooksInstalledException.java deleted file mode 100644 index a77b0de..0000000 --- a/app/src/main/java/org/bspeice/minimalbible/exception/NoBooksInstalledException.java +++ /dev/null @@ -1,7 +0,0 @@ -package org.bspeice.minimalbible.exception; - -/** - * Error to be thrown when no books are currently installed. - */ -public class NoBooksInstalledException extends Exception { -} diff --git a/app/src/main/java/org/bspeice/minimalbible/service/book/VerseLookupModules.java b/app/src/main/java/org/bspeice/minimalbible/service/book/VerseLookupModules.java deleted file mode 100644 index 26976c7..0000000 --- a/app/src/main/java/org/bspeice/minimalbible/service/book/VerseLookupModules.java +++ /dev/null @@ -1,25 +0,0 @@ -package org.bspeice.minimalbible.service.book; - -import android.support.v4.util.LruCache; - -import dagger.Module; -import dagger.Provides; - -/** - * Created by bspeice on 9/1/14. - */ -@Module(injects = VerseLookupService.class) -public class VerseLookupModules { - private static final int MAX_SIZE = 1000000; // 1MB - - /** - * Create a new LruCache. We're free to create new ones since they're all backed by the file - * system anyways. - * - * @return The LruCache to use - */ - @Provides - LruCache getLruCache() { - return new LruCache(MAX_SIZE); - } -} diff --git a/app/src/main/java/org/bspeice/minimalbible/service/book/VerseLookupService.java b/app/src/main/java/org/bspeice/minimalbible/service/book/VerseLookupService.java deleted file mode 100644 index 86fbb3b..0000000 --- a/app/src/main/java/org/bspeice/minimalbible/service/book/VerseLookupService.java +++ /dev/null @@ -1,148 +0,0 @@ -package org.bspeice.minimalbible.service.book; - -import android.support.v4.util.LruCache; - -import org.bspeice.minimalbible.Injector; -import org.bspeice.minimalbible.service.format.osisparser.OsisParser; -import org.crosswire.common.xml.SAXEventProvider; -import org.crosswire.jsword.book.Book; -import org.crosswire.jsword.book.BookData; -import org.crosswire.jsword.book.BookException; -import org.crosswire.jsword.passage.Verse; -import org.xml.sax.SAXException; - -import javax.inject.Inject; - -import rx.functions.Action1; -import rx.schedulers.Schedulers; -import rx.subjects.PublishSubject; - -/** - * This class has a simple purpose, but implements the dirty work needed to make it happen. - * The idea is this: someone wants the text for a verse, we look it up quickly. - * This means aggressive caching, cache prediction, and classes letting us know that some verses - * may be needed in the future (i.e. searching). - *

- * There is one VerseLookupService per Book, but multiple VerseLookupServices can work with - * the same book. Because the actual caching mechanism is disk-based, we're safe. - *

- * TODO: Statistics on cache hits/misses vs. verses cached - */ -public class VerseLookupService implements Action1 { - - Book book; - - @Inject - LruCache cache; - /** - * The listener is responsible for delegating calls to cache verses. - * This way, @notifyVerse can just tell the listener what's what, - * and the listener can delegate to another thread. - */ - private PublishSubject listener = PublishSubject.create(); - - public VerseLookupService(Injector i, Book b) { - listener.subscribeOn(Schedulers.io()) - .subscribe(this); - this.book = b; - i.inject(this); - } - - /** - * Get the text for a corresponding verse - * First, check the cache. If that doesn't exist, manually get the verse. - * In all cases, notify that we're looking up a verse so we can get the surrounding ones. - * - * @param v The verse to look up - * @return The JSON object for this verse (\) - */ - public String getJsonVerse(Verse v) { - if (contains(v)) { - return cache.get(getEntryName(v)); - } else { - // The awkward method calls below are so notifyVerse doesn't - // call the same doVerseLookup - String verseContent = doVerseLookup(v); - notifyVerse(v); - return verseContent; - } - } - - /** - * Perform the ugly work of getting the actual data for a verse - * Note that we build the verse object, JS should be left to determine how - * it is displayed. - * - * @param v The verse to look up - * @return The JSON content of this verse - */ - public String doVerseLookup(Verse v) { - BookData bookData = new BookData(book, v); - try { - SAXEventProvider provider = bookData.getSAXEventProvider(); - OsisParser handler = new OsisParser(); - handler.setVerse(v); - provider.provideSAXEvents(handler); - return handler.getVerseContent().toJson(); - } catch (BookException e) { - e.printStackTrace(); - return "Unable to locate " + v.toString() + "!"; - } catch (SAXException e) { - e.printStackTrace(); - } - return null; - } - - /** - * Not necessary, but helpful if you let us know ahead of time we should pre-cache a verse. - * For example, if something showed up in search results, it'd be helpful to start - * looking up some of the results. - * - * @param v The verse we should pre-cache - */ - public void notifyVerse(Verse v) { - listener.onNext(v); - } - - /** - * Let someone know if the cache contains a verse we want - * Also provides a nice wrapper if the underlying cache isn't working properly. - * - * @param v The verse to check - * @return Whether we can retrieve the verse from our cache - */ - public boolean contains(Verse v) { - return cache.get(getEntryName(v)) != null; - } - - /** - * Given a verse, what should it's name in the cache be? - * Example: Matthew 7:7 becomes: - * MAT_7_7 - * - * @param v The verse we need to generate a name for - * @return The name this verse should have in the cache - */ - private String getEntryName(Verse v) { - return v.getBook().toString() + "_" + - v.getChapter() + "_" + - v.getVerse(); - } - - /*------------------------------------------------------------------------ - IO Thread operations below - ------------------------------------------------------------------------*/ - - /** - * The listener has let us know that we need to look up a verse. So, look up - * that one first, and get its surrounding verses as well just in case. - * We can safely assume we are not on the main thread. - * - * @param verse The verse we need to look up - */ - - @Override - public void call(Verse verse) { - - } -} diff --git a/app/src/main/kotlin/org/bspeice/minimalbible/activity/viewer/BibleViewClient.kt b/app/src/main/kotlin/org/bspeice/minimalbible/activity/viewer/BibleViewClient.kt index 1dd0b43..b06df2c 100644 --- a/app/src/main/kotlin/org/bspeice/minimalbible/activity/viewer/BibleViewClient.kt +++ b/app/src/main/kotlin/org/bspeice/minimalbible/activity/viewer/BibleViewClient.kt @@ -1,35 +1,34 @@ package org.bspeice.minimalbible.activity.viewer import org.crosswire.jsword.passage.Verse -import org.bspeice.minimalbible.service.book.VerseLookupService import android.webkit.WebViewClient import android.webkit.JavascriptInterface import org.crosswire.jsword.book.Book -import java.util.ArrayList import android.util.Log import rx.subjects.PublishSubject import org.crosswire.jsword.book.getVersification +import org.bspeice.minimalbible.service.lookup.VerseLookup /** * Created by bspeice on 9/14/14. */ -class BibleViewClient(val b: Book, val lookup: VerseLookupService, +class BibleViewClient(val b: Book, val lookup: VerseLookup, val subject: PublishSubject?) : WebViewClient() { // We can receive and return only primitives and Strings. Still means we can use JSON :) JavascriptInterface fun getVerse(ordinal: Int): String { val v = Verse(b.getVersification(), ordinal) // TODO: WebView should notify us what verse it's on - subject?.onNext(v.getBook().toString() + " " + v.getChapter() + ":" + v.getVerse()) - return lookup.getJsonVerse(v) + subject?.onNext("${v.getBook()} ${v.getChapter()}:${v.getVerse()}") + return lookup getJson v } JavascriptInterface fun getVerses(first: Int, count: Int): String { - Log.e("getVerses", "First: " + first + " count: " + count) - val verses: MutableList = ArrayList() - var trueCount: Int - var trueFirst: Int + Log.e("getVerses", "First: $first count: $count") + val verses: MutableList = linkedListOf() + val trueCount: Int + val trueFirst: Int when { first < 0 - count -> return "" first < 0 -> { @@ -45,7 +44,7 @@ class BibleViewClient(val b: Book, val lookup: VerseLookupService, for (i in trueFirst..trueFirst + trueCount - 1) { verses.add(getVerse(i)) } - Log.e("getVerses", "return verses size: " + verses.size.toString()) + Log.e("getVerses", "return verses size: ${verses.size}") return verses.toString() } } \ No newline at end of file diff --git a/app/src/main/kotlin/org/bspeice/minimalbible/exception/NoBooksInstalledException.kt b/app/src/main/kotlin/org/bspeice/minimalbible/exception/NoBooksInstalledException.kt new file mode 100644 index 0000000..e4e5f31 --- /dev/null +++ b/app/src/main/kotlin/org/bspeice/minimalbible/exception/NoBooksInstalledException.kt @@ -0,0 +1,3 @@ +package org.bspeice.minimalbible.exception + +class NoBooksInstalledException() : Exception("No books currently installed!") {} \ No newline at end of file diff --git a/app/src/main/kotlin/org/bspeice/minimalbible/service/format/osisparser/OsisParser.kt b/app/src/main/kotlin/org/bspeice/minimalbible/service/format/osisparser/OsisParser.kt index a826670..962ca56 100644 --- a/app/src/main/kotlin/org/bspeice/minimalbible/service/format/osisparser/OsisParser.kt +++ b/app/src/main/kotlin/org/bspeice/minimalbible/service/format/osisparser/OsisParser.kt @@ -22,6 +22,8 @@ class OsisParser() : DefaultHandler() { // TODO: Implement a stack to keep min API 8 val doWrite = ArrayDeque() + fun getJson() = verseContent.toJson() + override fun startElement(uri: String, localName: String, qName: String, attributes: Attributes) { when (localName) { diff --git a/app/src/main/kotlin/org/bspeice/minimalbible/service/lookup/VerseLookup.kt b/app/src/main/kotlin/org/bspeice/minimalbible/service/lookup/VerseLookup.kt new file mode 100644 index 0000000..b34493f --- /dev/null +++ b/app/src/main/kotlin/org/bspeice/minimalbible/service/lookup/VerseLookup.kt @@ -0,0 +1,84 @@ +package org.bspeice.minimalbible.service.lookup + +import org.crosswire.jsword.book.Book +import android.support.v4.util.LruCache +import rx.functions.Action1 +import org.crosswire.jsword.passage.Verse +import rx.subjects.PublishSubject +import rx.schedulers.Schedulers +import org.crosswire.jsword.book.BookData +import org.bspeice.minimalbible.service.format.osisparser.OsisParser + +/** + * Created by bspeice on 11/12/14. + */ +open class VerseLookup(val b: Book, + val cache: LruCache = LruCache(1000000)) : Action1 { + /** + * The listener servers to let other objects notify us we should pre-cache verses + */ + val listener: PublishSubject = PublishSubject.create(); + + { + listener.observeOn(Schedulers.io()) + .subscribe(this) + } + + fun getVerseId(v: Verse) = v.getOrdinal() + + fun getJson(v: Verse): String = + if (contains(v)) + cache[getVerseId(v)] + else { + val content = doLookup(v) + notify(v) + content + } + + /** + * Perform the ugly work of getting the actual data for a verse + * Note that we build the verse object, JS should be left to determine how + * it is displayed. + * + * @param v The verse to look up + * @return The JSON content of this verse + */ + fun doLookup(v: Verse): String { + val data = BookData(b, v) + val provider = data.getSAXEventProvider() + val handler = OsisParser() + handler.verse = v + provider provideSAXEvents handler + return handler.getJson() + } + + /** + * Not necessary, but helpful if you let us know ahead of time we should pre-cache a verse. + * For example, if something showed up in search results, it'd be helpful to start + * looking up some of the results. + * + * @param v The verse we should pre-cache + */ + fun notify(v: Verse) = listener onNext v + + /** + * Let someone know if the cache contains a verse we want + * Also provides a nice wrapper if the underlying cache isn't working properly. + * + * @param v The verse to check + * @return Whether we can retrieve the verse from our cache + */ + fun contains(v: Verse) = cache[v.getOrdinal()] != null + + // IO Thread operations begin here + + /** + * Someone was nice enough to let us know that a verse was recently called, + * we should probably cache its neighbors! + */ + override fun call(t1: Verse?) { + + } +} + +class DefaultVerseLookup(b: Book) : VerseLookup(b) {}