mirror of
				https://github.com/MinimalBible/MinimalBible
				synced 2025-11-03 18:10:27 -05:00 
			
		
		
		
	Kotlin migration is done!
There will of course be refactoring and whatnot, but I consider this an accomplishment.
This commit is contained in:
		@ -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);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@ -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<Book> mBook;
 | 
			
		||||
    Book mBook;
 | 
			
		||||
    @Inject
 | 
			
		||||
    VerseLookup verseLookup;
 | 
			
		||||
 | 
			
		||||
    @InjectView(R.id.book_content)
 | 
			
		||||
    WebView mainContent;
 | 
			
		||||
 | 
			
		||||
    PublishSubject<String> 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<String>() {
 | 
			
		||||
 | 
			
		||||
@ -1,7 +0,0 @@
 | 
			
		||||
package org.bspeice.minimalbible.exception;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Error to be thrown when no books are currently installed.
 | 
			
		||||
 */
 | 
			
		||||
public class NoBooksInstalledException extends Exception {
 | 
			
		||||
}
 | 
			
		||||
@ -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<String, String> getLruCache() {
 | 
			
		||||
        return new LruCache<String, String>(MAX_SIZE);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@ -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).
 | 
			
		||||
 * <p/>
 | 
			
		||||
 * 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.
 | 
			
		||||
 * <p/>
 | 
			
		||||
 * TODO: Statistics on cache hits/misses vs. verses cached
 | 
			
		||||
 */
 | 
			
		||||
public class VerseLookupService implements Action1<Verse> {
 | 
			
		||||
 | 
			
		||||
    Book book;
 | 
			
		||||
 | 
			
		||||
    @Inject
 | 
			
		||||
    LruCache<String, String> 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<Verse> 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 (\<p\/>)
 | 
			
		||||
     */
 | 
			
		||||
    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) {
 | 
			
		||||
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@ -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<String>?) : 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<String> = ArrayList<String>()
 | 
			
		||||
        var trueCount: Int
 | 
			
		||||
        var trueFirst: Int
 | 
			
		||||
        Log.e("getVerses", "First: $first count: $count")
 | 
			
		||||
        val verses: MutableList<String> = 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()
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@ -0,0 +1,3 @@
 | 
			
		||||
package org.bspeice.minimalbible.exception
 | 
			
		||||
 | 
			
		||||
class NoBooksInstalledException() : Exception("No books currently installed!") {}
 | 
			
		||||
@ -22,6 +22,8 @@ class OsisParser() : DefaultHandler() {
 | 
			
		||||
    // TODO: Implement a stack to keep min API 8
 | 
			
		||||
    val doWrite = ArrayDeque<Boolean>()
 | 
			
		||||
 | 
			
		||||
    fun getJson() = verseContent.toJson()
 | 
			
		||||
 | 
			
		||||
    override fun startElement(uri: String, localName: String,
 | 
			
		||||
                              qName: String, attributes: Attributes) {
 | 
			
		||||
        when (localName) {
 | 
			
		||||
 | 
			
		||||
@ -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<Int, String> = LruCache(1000000)) : Action1<Verse> {
 | 
			
		||||
    /**
 | 
			
		||||
     * The listener servers to let other objects notify us we should pre-cache verses
 | 
			
		||||
     */
 | 
			
		||||
    val listener: PublishSubject<Verse> = 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) {}
 | 
			
		||||
		Reference in New Issue
	
	Block a user