mirror of
https://github.com/MinimalBible/MinimalBible
synced 2024-11-04 23:28:19 -05:00
Initial verse lookup and caching.
Having some weird issues on the build, syncing to Github so I can test elsewhere. Things are likely broken right now.
This commit is contained in:
parent
0caca67f44
commit
ad33ed9619
@ -55,20 +55,19 @@ android {
|
||||
dependencies {
|
||||
compile project(path: ':jsword-minimalbible', configuration: 'buildJSword')
|
||||
|
||||
compile 'com.squareup.dagger:dagger:1.2.1'
|
||||
provided 'com.squareup.dagger:dagger-compiler:1.2.1'
|
||||
compile 'com.squareup.dagger:dagger:+'
|
||||
provided 'com.squareup.dagger:dagger-compiler:+'
|
||||
|
||||
provided 'de.devland.esperandro:esperandro:1.1.2'
|
||||
compile 'de.devland.esperandro:esperandro-api:+'
|
||||
provided 'de.devland.esperandro:esperandro:+'
|
||||
|
||||
compile 'com.jakewharton:butterknife:+'
|
||||
compile 'com.readystatesoftware.systembartint:systembartint:+'
|
||||
compile 'com.netflix.rxjava:rxjava-android:+'
|
||||
compile 'com.android.support:appcompat-v7:20.+'
|
||||
|
||||
androidTestCompile 'com.jayway.awaitility:awaitility:1.6.0'
|
||||
androidTestCompile 'org.mockito:mockito-core:+'
|
||||
androidTestCompile 'com.google.dexmaker:dexmaker:+'
|
||||
androidTestCompile 'com.google.dexmaker:dexmaker-mockito:+'
|
||||
// androidTestProvided 'com.squareup.dagger:dagger-compiler:1.2.0'
|
||||
|
||||
compile 'com.jakewharton:butterknife:5.0.1'
|
||||
compile 'de.devland.esperandro:esperandro-api:1.1.2'
|
||||
compile 'com.readystatesoftware.systembartint:systembartint:1.0.3'
|
||||
compile 'com.netflix.rxjava:rxjava-android:0.19.0'
|
||||
compile 'com.android.support:appcompat-v7:20.+'
|
||||
}
|
||||
|
@ -1,22 +1,21 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="org.bspeice.minimalbible" >
|
||||
package="org.bspeice.minimalbible">
|
||||
|
||||
<application
|
||||
android:name=".MinimalBible"
|
||||
android:allowBackup="true"
|
||||
android:icon="@drawable/ic_launcher"
|
||||
android:label="@string/app_name"
|
||||
android:theme="@style/AppTheme"
|
||||
android:name=".MinimalBible" >
|
||||
android:theme="@style/AppTheme">
|
||||
<activity
|
||||
android:name=".activity.downloader.DownloadActivity"
|
||||
android:label="@string/app_name" >
|
||||
</activity>
|
||||
|
||||
<activity android:name=".activity.viewer.BibleViewer"
|
||||
android:label="@string/app_name" >
|
||||
android:label="@string/app_name"></activity>
|
||||
<activity
|
||||
android:name=".activity.viewer.BibleViewer"
|
||||
android:label="@string/app_name">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN" />
|
||||
<action android:name="android.intent.action.MAIN" />
|
||||
|
||||
<category android:name="android.intent.category.LAUNCHER" />
|
||||
</intent-filter>
|
||||
|
@ -12,9 +12,11 @@ import android.webkit.WebViewClient;
|
||||
import org.bspeice.minimalbible.Injector;
|
||||
import org.bspeice.minimalbible.R;
|
||||
import org.bspeice.minimalbible.activity.BaseFragment;
|
||||
import org.bspeice.minimalbible.activity.viewer.bookutil.VersificationUtil;
|
||||
import org.bspeice.minimalbible.service.book.VerseLookupService;
|
||||
import org.crosswire.jsword.book.Book;
|
||||
import org.crosswire.jsword.book.BookMetaData;
|
||||
import org.crosswire.jsword.versification.Versification;
|
||||
import org.crosswire.jsword.passage.Verse;
|
||||
import org.crosswire.jsword.versification.BibleBook;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@ -23,22 +25,31 @@ import javax.inject.Named;
|
||||
|
||||
import butterknife.ButterKnife;
|
||||
import butterknife.InjectView;
|
||||
import dagger.Lazy;
|
||||
|
||||
import static org.crosswire.jsword.versification.system.Versifications.instance;
|
||||
import static org.bspeice.minimalbible.util.StringUtil.joinString;
|
||||
|
||||
/**
|
||||
* A placeholder fragment containing a simple view.
|
||||
*/
|
||||
public class BookFragment extends BaseFragment {
|
||||
@Inject @Named("MainBook") Book mBook;
|
||||
|
||||
private static final String ARG_BOOK_NAME = "book_name";
|
||||
@Inject
|
||||
@Named("MainBook")
|
||||
Lazy<Book> mBook;
|
||||
@Inject
|
||||
VersificationUtil vUtil;
|
||||
// TODO: Factory?
|
||||
VerseLookupService lookupService;
|
||||
@InjectView(R.id.book_content)
|
||||
WebView mainContent;
|
||||
|
||||
private static final String ARG_BOOK_NAME = "book_name";
|
||||
public BookFragment() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a new instance of this fragment for the given section number.
|
||||
* Returns a new instance of this fragment for the given book.
|
||||
*/
|
||||
public static BookFragment newInstance(String bookName) {
|
||||
BookFragment fragment = new BookFragment();
|
||||
@ -48,9 +59,6 @@ public class BookFragment extends BaseFragment {
|
||||
return fragment;
|
||||
}
|
||||
|
||||
public BookFragment() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle state) {
|
||||
super.onCreate(state);
|
||||
@ -63,28 +71,35 @@ public class BookFragment extends BaseFragment {
|
||||
View rootView = inflater.inflate(R.layout.fragment_viewer_main, container,
|
||||
false);
|
||||
((Injector)getActivity()).inject(this);
|
||||
// TODO: Defer lookup until after webview created? When exactly is WebView created?
|
||||
this.lookupService = new VerseLookupService(mBook.get(), this.getActivity());
|
||||
ButterKnife.inject(this, rootView);
|
||||
mainContent.getSettings().setJavaScriptEnabled(true);
|
||||
|
||||
// TODO: Load initial text from SharedPreferences
|
||||
// TODO: Load initial text from SharedPreferences, rather than getting the actual book.
|
||||
|
||||
displayBook(mBook);
|
||||
|
||||
Log.d("BookFragment", getVersification(mBook).toString());
|
||||
displayBook(mBook.get());
|
||||
|
||||
return rootView;
|
||||
}
|
||||
|
||||
private Versification getVersification(Book b) {
|
||||
return instance().getVersification((String) b.getBookMetaData().getProperty(BookMetaData.KEY_VERSIFICATION));
|
||||
}
|
||||
|
||||
// TODO: Remove?
|
||||
@Override
|
||||
public void onAttach(Activity activity) {
|
||||
super.onAttach(activity);
|
||||
}
|
||||
|
||||
/*----------------------------------------
|
||||
Here be all the methods you want to spend time with
|
||||
----------------------------------------
|
||||
*/
|
||||
|
||||
/**
|
||||
* Do the initial work of displaying a book. Requires setting up WebView, etc.
|
||||
* TODO: Get initial content from cache?
|
||||
*
|
||||
* @param b
|
||||
*/
|
||||
private void displayBook(Book b) {
|
||||
Log.d("BookFragment", b.getName());
|
||||
((BibleViewer)getActivity()).setActionBarTitle(b.getInitials());
|
||||
@ -92,13 +107,30 @@ public class BookFragment extends BaseFragment {
|
||||
mainContent.setWebViewClient(new WebViewClient(){
|
||||
@Override
|
||||
public void onPageFinished(WebView view, String url) {
|
||||
// TODO: Restore this verse from a SharedPref
|
||||
Verse initial = new Verse(vUtil.getVersification(mBook.get()),
|
||||
BibleBook.GEN, 1, 1);
|
||||
super.onPageFinished(view, url);
|
||||
invokeJavascript("set_content", BookFragment.this.mBook.getName());
|
||||
invokeJavascript("set_content", lookupService.getHTMLVerse(initial));
|
||||
}
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Do the heavy listing of getting the actual text for a verse
|
||||
*
|
||||
* @param v
|
||||
*/
|
||||
public void displayVerse(Verse v) {
|
||||
Book b = mBook.get();
|
||||
lookupService.getHTMLVerse(v);
|
||||
}
|
||||
|
||||
/*-----------------------------------------
|
||||
Here be the methods you wish didn't have to exist.
|
||||
-----------------------------------------
|
||||
*/
|
||||
|
||||
private void invokeJavascript(String function, Object arg) {
|
||||
mainContent.loadUrl("javascript:" + function + "('" + arg.toString() + "')");
|
||||
}
|
||||
@ -106,20 +138,4 @@ public class BookFragment extends BaseFragment {
|
||||
private void invokeJavascript(String function, List<Object> args) {
|
||||
mainContent.loadUrl("javascript:" + function + "(" + joinString(",", args.toArray()) + ")");
|
||||
}
|
||||
|
||||
// Convenience from http://stackoverflow.com/a/17795110/1454178
|
||||
public static String joinString(String join, Object... strings) {
|
||||
if (strings == null || strings.length == 0) {
|
||||
return "";
|
||||
} else if (strings.length == 1) {
|
||||
return strings[0].toString();
|
||||
} else {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append(strings[0]);
|
||||
for (int i = 1; i < strings.length; i++) {
|
||||
sb.append(join).append(strings[i].toString());
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -22,6 +22,7 @@ public class BookManager {
|
||||
|
||||
@Inject
|
||||
BookManager() {
|
||||
// TODO: Any way this can be sped up goes straight to the initialization time.
|
||||
installedBooks = Observable.from(Books.installed().getBooks())
|
||||
.cache();
|
||||
installedBooks.subscribeOn(Schedulers.io())
|
||||
@ -46,5 +47,4 @@ public class BookManager {
|
||||
public Boolean isRefreshComplete() {
|
||||
return refreshComplete;
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -0,0 +1,155 @@
|
||||
package org.bspeice.minimalbible.service.book;
|
||||
|
||||
import android.content.Context;
|
||||
import android.support.v4.util.LruCache;
|
||||
|
||||
import org.crosswire.common.xml.Converter;
|
||||
import org.crosswire.common.xml.TransformingSAXEventProvider;
|
||||
import org.crosswire.common.xml.XMLUtil;
|
||||
import org.crosswire.jsword.book.Book;
|
||||
import org.crosswire.jsword.book.BookData;
|
||||
import org.crosswire.jsword.book.BookException;
|
||||
import org.crosswire.jsword.book.BookMetaData;
|
||||
import org.crosswire.jsword.passage.Verse;
|
||||
import org.crosswire.jsword.util.ConverterFactory;
|
||||
import org.xml.sax.SAXException;
|
||||
|
||||
import javax.xml.transform.TransformerException;
|
||||
|
||||
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> {
|
||||
|
||||
private static final int MAX_SIZE = 1000000; // 1MB
|
||||
Book book;
|
||||
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(Book b, Context ctx) {
|
||||
listener.subscribeOn(Schedulers.io())
|
||||
.subscribe(this);
|
||||
this.book = b;
|
||||
this.cache = new LruCache<String, String>(MAX_SIZE);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 HTML text for this verse (\<p\/>)
|
||||
*/
|
||||
public String getHTMLVerse(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
|
||||
*/
|
||||
public String doVerseLookup(Verse v) {
|
||||
BookData bookData = new BookData(book, v);
|
||||
|
||||
String verseHTML = null;
|
||||
|
||||
try {
|
||||
Converter styler = ConverterFactory.getConverter();
|
||||
TransformingSAXEventProvider htmlsep = (TransformingSAXEventProvider)
|
||||
styler.convert(bookData.getSAXEventProvider());
|
||||
BookMetaData bmd = book.getBookMetaData();
|
||||
boolean direction = bmd.isLeftToRight();
|
||||
htmlsep.setParameter("direction", direction ? "ltr" : "rtl");
|
||||
|
||||
verseHTML = XMLUtil.writeToString(htmlsep);
|
||||
} catch (TransformerException e) {
|
||||
e.printStackTrace();
|
||||
} catch (BookException e) {
|
||||
e.printStackTrace();
|
||||
} catch (SAXException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
return verseHTML;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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) {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append(v.getBook().toString() + "_");
|
||||
sb.append(v.getChapter() + "_");
|
||||
sb.append(v.getVerse());
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
/*------------------------------------------------------------------------
|
||||
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) {
|
||||
|
||||
}
|
||||
}
|
@ -0,0 +1,23 @@
|
||||
package org.bspeice.minimalbible.util;
|
||||
|
||||
/**
|
||||
* Created by bspeice on 8/3/14.
|
||||
*/
|
||||
public class StringUtil {
|
||||
|
||||
// Convenience from http://stackoverflow.com/a/17795110/1454178
|
||||
public static String joinString(String join, Object... strings) {
|
||||
if (strings == null || strings.length == 0) {
|
||||
return "";
|
||||
} else if (strings.length == 1) {
|
||||
return strings[0].toString();
|
||||
} else {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append(strings[0]);
|
||||
for (int i = 1; i < strings.length; i++) {
|
||||
sb.append(join).append(strings[i].toString());
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user