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 {
|
dependencies {
|
||||||
compile project(path: ':jsword-minimalbible', configuration: 'buildJSword')
|
compile project(path: ':jsword-minimalbible', configuration: 'buildJSword')
|
||||||
|
|
||||||
compile 'com.squareup.dagger:dagger:1.2.1'
|
compile 'com.squareup.dagger:dagger:+'
|
||||||
provided 'com.squareup.dagger:dagger-compiler:1.2.1'
|
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 'com.jayway.awaitility:awaitility:1.6.0'
|
||||||
androidTestCompile 'org.mockito:mockito-core:+'
|
androidTestCompile 'org.mockito:mockito-core:+'
|
||||||
androidTestCompile 'com.google.dexmaker:dexmaker:+'
|
androidTestCompile 'com.google.dexmaker:dexmaker:+'
|
||||||
androidTestCompile 'com.google.dexmaker:dexmaker-mockito:+'
|
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"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
package="org.bspeice.minimalbible" >
|
package="org.bspeice.minimalbible">
|
||||||
|
|
||||||
<application
|
<application
|
||||||
|
android:name=".MinimalBible"
|
||||||
android:allowBackup="true"
|
android:allowBackup="true"
|
||||||
android:icon="@drawable/ic_launcher"
|
android:icon="@drawable/ic_launcher"
|
||||||
android:label="@string/app_name"
|
android:label="@string/app_name"
|
||||||
android:theme="@style/AppTheme"
|
android:theme="@style/AppTheme">
|
||||||
android:name=".MinimalBible" >
|
|
||||||
<activity
|
<activity
|
||||||
android:name=".activity.downloader.DownloadActivity"
|
android:name=".activity.downloader.DownloadActivity"
|
||||||
android:label="@string/app_name" >
|
android:label="@string/app_name"></activity>
|
||||||
</activity>
|
<activity
|
||||||
|
android:name=".activity.viewer.BibleViewer"
|
||||||
<activity android:name=".activity.viewer.BibleViewer"
|
android:label="@string/app_name">
|
||||||
android:label="@string/app_name" >
|
|
||||||
<intent-filter>
|
<intent-filter>
|
||||||
<action android:name="android.intent.action.MAIN" />
|
<action android:name="android.intent.action.MAIN" />
|
||||||
|
|
||||||
<category android:name="android.intent.category.LAUNCHER" />
|
<category android:name="android.intent.category.LAUNCHER" />
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
|
@ -12,9 +12,11 @@ import android.webkit.WebViewClient;
|
|||||||
import org.bspeice.minimalbible.Injector;
|
import org.bspeice.minimalbible.Injector;
|
||||||
import org.bspeice.minimalbible.R;
|
import org.bspeice.minimalbible.R;
|
||||||
import org.bspeice.minimalbible.activity.BaseFragment;
|
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.Book;
|
||||||
import org.crosswire.jsword.book.BookMetaData;
|
import org.crosswire.jsword.passage.Verse;
|
||||||
import org.crosswire.jsword.versification.Versification;
|
import org.crosswire.jsword.versification.BibleBook;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
@ -23,22 +25,31 @@ import javax.inject.Named;
|
|||||||
|
|
||||||
import butterknife.ButterKnife;
|
import butterknife.ButterKnife;
|
||||||
import butterknife.InjectView;
|
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.
|
* A placeholder fragment containing a simple view.
|
||||||
*/
|
*/
|
||||||
public class BookFragment extends BaseFragment {
|
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)
|
@InjectView(R.id.book_content)
|
||||||
WebView mainContent;
|
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) {
|
public static BookFragment newInstance(String bookName) {
|
||||||
BookFragment fragment = new BookFragment();
|
BookFragment fragment = new BookFragment();
|
||||||
@ -48,9 +59,6 @@ public class BookFragment extends BaseFragment {
|
|||||||
return fragment;
|
return fragment;
|
||||||
}
|
}
|
||||||
|
|
||||||
public BookFragment() {
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onCreate(Bundle state) {
|
public void onCreate(Bundle state) {
|
||||||
super.onCreate(state);
|
super.onCreate(state);
|
||||||
@ -63,28 +71,35 @@ public class BookFragment extends BaseFragment {
|
|||||||
View rootView = inflater.inflate(R.layout.fragment_viewer_main, container,
|
View rootView = inflater.inflate(R.layout.fragment_viewer_main, container,
|
||||||
false);
|
false);
|
||||||
((Injector)getActivity()).inject(this);
|
((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);
|
ButterKnife.inject(this, rootView);
|
||||||
mainContent.getSettings().setJavaScriptEnabled(true);
|
mainContent.getSettings().setJavaScriptEnabled(true);
|
||||||
|
|
||||||
// TODO: Load initial text from SharedPreferences
|
// TODO: Load initial text from SharedPreferences, rather than getting the actual book.
|
||||||
|
|
||||||
displayBook(mBook);
|
displayBook(mBook.get());
|
||||||
|
|
||||||
Log.d("BookFragment", getVersification(mBook).toString());
|
|
||||||
|
|
||||||
return rootView;
|
return rootView;
|
||||||
}
|
}
|
||||||
|
|
||||||
private Versification getVersification(Book b) {
|
|
||||||
return instance().getVersification((String) b.getBookMetaData().getProperty(BookMetaData.KEY_VERSIFICATION));
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: Remove?
|
// TODO: Remove?
|
||||||
@Override
|
@Override
|
||||||
public void onAttach(Activity activity) {
|
public void onAttach(Activity activity) {
|
||||||
super.onAttach(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) {
|
private void displayBook(Book b) {
|
||||||
Log.d("BookFragment", b.getName());
|
Log.d("BookFragment", b.getName());
|
||||||
((BibleViewer)getActivity()).setActionBarTitle(b.getInitials());
|
((BibleViewer)getActivity()).setActionBarTitle(b.getInitials());
|
||||||
@ -92,13 +107,30 @@ public class BookFragment extends BaseFragment {
|
|||||||
mainContent.setWebViewClient(new WebViewClient(){
|
mainContent.setWebViewClient(new WebViewClient(){
|
||||||
@Override
|
@Override
|
||||||
public void onPageFinished(WebView view, String url) {
|
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);
|
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) {
|
private void invokeJavascript(String function, Object arg) {
|
||||||
mainContent.loadUrl("javascript:" + function + "('" + arg.toString() + "')");
|
mainContent.loadUrl("javascript:" + function + "('" + arg.toString() + "')");
|
||||||
}
|
}
|
||||||
@ -106,20 +138,4 @@ public class BookFragment extends BaseFragment {
|
|||||||
private void invokeJavascript(String function, List<Object> args) {
|
private void invokeJavascript(String function, List<Object> args) {
|
||||||
mainContent.loadUrl("javascript:" + function + "(" + joinString(",", args.toArray()) + ")");
|
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
|
@Inject
|
||||||
BookManager() {
|
BookManager() {
|
||||||
|
// TODO: Any way this can be sped up goes straight to the initialization time.
|
||||||
installedBooks = Observable.from(Books.installed().getBooks())
|
installedBooks = Observable.from(Books.installed().getBooks())
|
||||||
.cache();
|
.cache();
|
||||||
installedBooks.subscribeOn(Schedulers.io())
|
installedBooks.subscribeOn(Schedulers.io())
|
||||||
@ -46,5 +47,4 @@ public class BookManager {
|
|||||||
public Boolean isRefreshComplete() {
|
public Boolean isRefreshComplete() {
|
||||||
return refreshComplete;
|
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