Download and search indexes

Search currently not yielding results, but everything surrounding it appears to be working.
parser-fixes
Bradlee Speice 2015-01-18 00:37:44 -05:00
parent 902b91776d
commit 3721ef5ee0
13 changed files with 257 additions and 139 deletions

View File

@ -7,6 +7,7 @@ import org.bspeice.minimalbible.Injector;
import org.bspeice.minimalbible.activity.downloader.DownloadPrefs;
import org.bspeice.minimalbible.activity.downloader.manager.BookManager;
import org.bspeice.minimalbible.activity.downloader.manager.DLProgressEvent;
import org.bspeice.minimalbible.activity.downloader.manager.MBIndexManager;
import org.bspeice.minimalbible.activity.downloader.manager.RefreshManager;
import org.crosswire.common.progress.JobManager;
import org.crosswire.common.progress.Progress;
@ -19,6 +20,8 @@ import org.crosswire.jsword.book.Books;
import org.crosswire.jsword.book.BooksEvent;
import org.crosswire.jsword.book.install.InstallManager;
import org.crosswire.jsword.book.install.Installer;
import org.crosswire.jsword.index.IndexManager;
import org.crosswire.jsword.index.IndexManagerFactory;
import org.junit.Before;
import org.junit.Ignore;
import org.junit.Test;
@ -93,9 +96,9 @@ public class BookManagerTest implements Injector {
.subscribe(new Action1<DLProgressEvent>() {
@Override
public void call(DLProgressEvent dlProgressEvent) {
System.out.println(dlProgressEvent.getAverageProgress());
System.out.println(dlProgressEvent.getProgress());
if (dlProgressEvent.getB().getInitials().equals(toInstall.getInitials())
&& dlProgressEvent.getAverageProgress() == DLProgressEvent.PROGRESS_COMPLETE) {
&& dlProgressEvent.getProgress() == DLProgressEvent.PROGRESS_COMPLETE) {
signal.set(true);
}
}
@ -112,7 +115,7 @@ public class BookManagerTest implements Injector {
@Test
public void testJobIdMatch() {
final Book toInstall = installableBooks().toBlocking().first();
final String jobName = bookManager.getJobNames(toInstall).get(0);
final String jobName = bookManager.getJobName(toInstall);
final AtomicBoolean jobNameMatch = new AtomicBoolean(false);
JobManager.addWorkListener(new WorkListener() {
@ -160,7 +163,12 @@ public class BookManagerTest implements Injector {
bookManager.getInstalledBooksList().add(mockBook);
assertTrue(bookManager.getInstalledBooksList().contains(mockBook));
bookManager.removeBook(mockBook, secondMockBook);
try {
bookManager.removeBook(mockBook, secondMockBook);
} catch (NullPointerException e) {
// Nasty NPE shows up when testing because the index
// isn't installed. Suppressing here.
}
assertFalse(bookManager.getInstalledBooksList().contains(mockBook));
verify(driver, times(1)).delete(secondMockBook);
}
@ -173,14 +181,12 @@ public class BookManagerTest implements Injector {
public void testWorkProgressedCorrectProgress() {
Book mockBook = mock(Book.class);
when(mockBook.getInitials()).thenReturn("mockBook");
String bookJobName = bookManager.getJobNames(mockBook).get(0);
String bookJobName = bookManager.getJobName(mockBook);
bookManager.getInProgressJobNames().put(bookJobName, mockBook);
// Percent to degrees
final int workDone = 50; // 50%
// There are two jobs, each comprising 180 degrees.
// Since we are simulating one job being 50% complete, that's 90 degrees
final int circularProgress = 90;
final int circularProgress = 180;
WorkEvent ev = mock(WorkEvent.class);
Progress p = mock(Progress.class);
@ -266,11 +272,25 @@ public class BookManagerTest implements Injector {
return PublishSubject.create();
}
@Provides
@Singleton
IndexManager indexManager() {
return IndexManagerFactory.getIndexManager();
}
@Provides
@Singleton
MBIndexManager mbIndexManager(IndexManager indexManager,
PublishSubject<DLProgressEvent> events) {
return new MBIndexManager(events, indexManager);
}
@Provides
@Singleton
BookManager bookDownloadManager(Books installed, RefreshManager rm,
PublishSubject<DLProgressEvent> eventPublisher) {
return new BookManager(installed, rm, eventPublisher);
PublishSubject<DLProgressEvent> eventPublisher,
MBIndexManager manager) {
return new BookManager(installed, rm, eventPublisher, manager);
}
}
}

View File

@ -12,7 +12,7 @@ class DLProgressEventSpek : Spek() {{
given("a DLProgressEvent created with 50% progress and a mock book") {
val mockBook = mock(javaClass<Book>())
val dlEvent = DLProgressEvent(50, 50, mockBook)
val dlEvent = DLProgressEvent(50, mockBook)
on("getting the progress in degrees") {
val progressDegrees = dlEvent.toCircular()

View File

@ -1,18 +1,26 @@
package org.bspeice.minimalbible;
import android.app.Application;
import android.util.Log;
import org.bspeice.minimalbible.activity.viewer.BibleViewerPreferences;
import org.bspeice.minimalbible.service.manager.BookManager;
import org.crosswire.jsword.book.Book;
import org.crosswire.jsword.book.Books;
import java.util.ArrayList;
import java.util.List;
import java.util.NoSuchElementException;
import java.util.concurrent.atomic.AtomicReference;
import javax.inject.Named;
import javax.inject.Singleton;
import dagger.Module;
import dagger.Provides;
import de.devland.esperandro.Esperandro;
import rx.Observable;
import rx.functions.Action1;
import rx.functions.Func1;
/**
@ -26,7 +34,8 @@ public class MinimalBibleModules {
this.app = app;
}
@Provides @Singleton
@Provides
@Singleton
Application provideApplication() {
return app;
}
@ -34,6 +43,7 @@ public class MinimalBibleModules {
/**
* Provide a list of book names that are known bad. This can be because they trigger NPE,
* or are just missing lots of content, etc.
*
* @return the list of books (by name) to ignore
*/
@Provides
@ -80,4 +90,64 @@ public class MinimalBibleModules {
})
.toList().toBlocking().first();
}
@Provides
@Singleton
BibleViewerPreferences providePrefs() {
return Esperandro.getPreferences(BibleViewerPreferences.class, app);
}
@Provides
@Named("MainBook")
Book provideMainBook(BookManager bookManager, final BibleViewerPreferences prefs) {
final AtomicReference<Book> mBook = new AtomicReference<Book>(null);
bookManager.getInstalledBooks()
.first(new Func1<Book, Boolean>() {
@Override
public Boolean call(Book book) {
return book.getInitials().equals(prefs.defaultBookInitials());
}
})
.subscribe(new Action1<Book>() {
@Override
public void call(Book book) {
mBook.set(book);
}
}, new Action1<Throwable>() {
@Override
public void call(Throwable throwable) {
Log.d("BibleViewerModules", throwable.getLocalizedMessage());
}
});
if (mBook.get() == null) {
try {
Book fallback;
fallback = bookManager.getInstalledBooks()
.onErrorReturn(new Func1<Throwable, Book>() {
@Override
public Book call(Throwable throwable) {
// If there's no book installed, we can't select the main one...
return null;
}
})
.toBlocking().first();
prefs.defaultBookInitials(fallback.getName());
return fallback;
} catch (NoSuchElementException e) {
// If no books are installed, there's really nothing we can do...
Log.d("BibleViewerModules", "No books are installed, so can't select a main book.");
return null;
}
} else {
return mBook.get();
}
}
@Provides
@Singleton
BookManager bookManager(List<String> exclude) {
return new BookManager(exclude);
}
}

View File

@ -28,8 +28,8 @@ import rx.functions.Func1;
import rx.subjects.PublishSubject;
/**
* Created by bspeice on 5/20/14.
*/
* Created by bspeice on 5/20/14.
*/
public class BookItemHolder {
// TODO: The holder should register and unregister itself for DownloadProgress events
@ -46,7 +46,8 @@ public class BookItemHolder {
ProgressWheel downloadProgress;
@Inject
BookManager bookManager;
@Inject @Named("DownloadActivityContext")
@Inject
@Named("DownloadActivityContext")
Context ctx;
@Inject
PublishSubject<DLProgressEvent> downloadProgressEvents;
@ -65,7 +66,7 @@ public class BookItemHolder {
itemName.setText(b.getName());
DLProgressEvent dlProgressEvent = bookManager.getDownloadProgress(b);
if (dlProgressEvent != null) {
displayProgress(dlProgressEvent.toCircular());
displayProgress(dlProgressEvent);
} else if (bookManager.isInstalled(b)) {
displayInstalled();
}
@ -82,7 +83,7 @@ public class BookItemHolder {
.subscribe(new Action1<DLProgressEvent>() {
@Override
public void call(DLProgressEvent event) {
BookItemHolder.this.displayProgress(event.toCircular());
BookItemHolder.this.displayProgress(event);
}
});
}
@ -109,33 +110,31 @@ public class BookItemHolder {
/**
* Display the current progress of this download
* @param progress The progress out of 360 (degrees of a circle)
*
* @param event The event we need to display progress for
*/
private void displayProgress(int progress) {
private void displayProgress(DLProgressEvent event) {
int downloadView;
int downloadView = downloadProgress.getId();
int progress = event.getProgress();
int circular = event.toCircular();
if (progress == DLProgressEvent.PROGRESS_BEGINNING) {
// Download starting
downloadView = downloadProgress.getId();
isDownloaded.setVisibility(View.GONE);
downloadProgress.setVisibility(View.VISIBLE);
downloadProgress.spin();
} else if (progress < 360) {
} else if (progress < DLProgressEvent.PROGRESS_COMPLETE) {
// Download in progress
downloadView = downloadProgress.getId();
isDownloaded.setVisibility(View.GONE);
downloadProgress.setVisibility(View.VISIBLE);
downloadProgress.stopSpinning();
downloadProgress.setProgress(progress);
downloadProgress.setProgress(circular);
} else {
// Download complete
subscription.unsubscribe();
downloadView = downloadProgress.getId();
isDownloaded.setVisibility(View.VISIBLE);
downloadProgress.setVisibility(View.GONE);

View File

@ -8,11 +8,15 @@ import org.bspeice.minimalbible.MinimalBibleModules;
import org.bspeice.minimalbible.activity.downloader.manager.BookManager;
import org.bspeice.minimalbible.activity.downloader.manager.DLProgressEvent;
import org.bspeice.minimalbible.activity.downloader.manager.LocaleManager;
import org.bspeice.minimalbible.activity.downloader.manager.MBIndexManager;
import org.bspeice.minimalbible.activity.downloader.manager.RefreshManager;
import org.crosswire.jsword.book.BookCategory;
import org.crosswire.jsword.book.Books;
import org.crosswire.jsword.book.install.InstallManager;
import org.crosswire.jsword.book.install.Installer;
import org.crosswire.jsword.index.IndexManager;
import org.crosswire.jsword.index.IndexManagerFactory;
import org.crosswire.jsword.index.IndexPolicyAdapter;
import java.util.ArrayList;
import java.util.Collection;
@ -88,8 +92,9 @@ public class DownloadActivityModules {
@Provides
@Singleton
BookManager provideBookDownloadManager(Books installedBooks, RefreshManager rm,
PublishSubject<DLProgressEvent> progressEvents) {
return new BookManager(installedBooks, rm, progressEvents);
PublishSubject<DLProgressEvent> progressEvents,
MBIndexManager mbIndexManager) {
return new BookManager(installedBooks, rm, progressEvents, mbIndexManager);
}
@Provides
@ -123,4 +128,17 @@ public class DownloadActivityModules {
LocaleManager provideLocaleManager(RefreshManager refreshManager) {
return new LocaleManager(refreshManager);
}
@Provides
IndexManager indexManager() {
IndexManager manager = IndexManagerFactory.getIndexManager();
manager.setIndexPolicy(new IndexPolicyAdapter());
return manager;
}
@Provides
MBIndexManager mbIndexManager(PublishSubject<DLProgressEvent> downloadEvents,
IndexManager indexManager) {
return new MBIndexManager(downloadEvents, indexManager);
}
}

View File

@ -10,6 +10,9 @@ import org.bspeice.minimalbible.MinimalBible;
import org.bspeice.minimalbible.OGHolder;
import org.bspeice.minimalbible.R;
import org.bspeice.minimalbible.activity.BaseActivity;
import org.crosswire.jsword.passage.Verse;
import java.util.List;
import javax.inject.Inject;
@ -65,6 +68,7 @@ public class BasicSearch extends BaseActivity
String query = intent.getStringExtra(SearchManager.QUERY);
Toast.makeText(this, "Searching for: " + query, Toast.LENGTH_SHORT).show();
searchProvider.basicTextSearch(query);
List<Verse> results = searchProvider.basicTextSearch(query);
Toast.makeText(this, "Found results: " + results.size(), Toast.LENGTH_SHORT).show();
}
}

View File

@ -1,6 +1,11 @@
package org.bspeice.minimalbible.activity.search;
import org.bspeice.minimalbible.MinimalBibleModules;
import org.crosswire.jsword.book.Book;
import org.crosswire.jsword.index.IndexManager;
import org.crosswire.jsword.index.IndexManagerFactory;
import javax.inject.Named;
import dagger.Module;
import dagger.Provides;
@ -12,7 +17,13 @@ import dagger.Provides;
public class SearchModules {
@Provides
SearchProvider searchProvider() {
return new SearchProvider();
SearchProvider searchProvider(@Named("MainBook") Book book,
IndexManager indexManager) {
return new SearchProvider(book, indexManager);
}
@Provides
IndexManager jswordIndexManager() {
return IndexManagerFactory.getIndexManager();
}
}

View File

@ -1,23 +1,11 @@
package org.bspeice.minimalbible.activity.viewer;
import android.util.Log;
import org.bspeice.minimalbible.MinimalBibleModules;
import org.bspeice.minimalbible.service.manager.BookManager;
import org.crosswire.jsword.book.Book;
import java.util.List;
import java.util.NoSuchElementException;
import java.util.concurrent.atomic.AtomicReference;
import javax.inject.Named;
import javax.inject.Singleton;
import dagger.Module;
import dagger.Provides;
import de.devland.esperandro.Esperandro;
import rx.functions.Action1;
import rx.functions.Func1;
import rx.subjects.PublishSubject;
/**
@ -37,65 +25,6 @@ public class BibleViewerModules {
this.activity = activity;
}
@Provides
@Singleton
BibleViewerPreferences providePrefs() {
return Esperandro.getPreferences(BibleViewerPreferences.class, activity);
}
@Provides
@Named("MainBook")
Book provideMainBook(BookManager bookManager, final BibleViewerPreferences prefs) {
final AtomicReference<Book> mBook = new AtomicReference<Book>(null);
bookManager.getInstalledBooks()
.first(new Func1<Book, Boolean>() {
@Override
public Boolean call(Book book) {
return book.getInitials().equals(prefs.defaultBookInitials());
}
})
.subscribe(new Action1<Book>() {
@Override
public void call(Book book) {
mBook.set(book);
}
}, new Action1<Throwable>() {
@Override
public void call(Throwable throwable) {
Log.d("BibleViewerModules", throwable.getLocalizedMessage());
}
});
if (mBook.get() == null) {
try {
Book fallback;
fallback = bookManager.getInstalledBooks()
.onErrorReturn(new Func1<Throwable, Book>() {
@Override
public Book call(Throwable throwable) {
// If there's no book installed, we can't select the main one...
return null;
}
})
.toBlocking().first();
prefs.defaultBookInitials(fallback.getName());
return fallback;
} catch (NoSuchElementException e) {
// If no books are installed, there's really nothing we can do...
Log.d("BibleViewerModules", "No books are installed, so can't select a main book.");
return null;
}
} else {
return mBook.get();
}
}
@Provides
@Singleton
BookManager bookManager(List<String> exclude) {
return new BookManager(exclude);
}
@Provides
@Singleton

View File

@ -0,0 +1,12 @@
package org.bspeice.minimalbible
/**
* Massive credit over here:
* http://blog.omalley.id.au/2013/07/27/null-handling-in-kotlin.html
*
* The trick is that a non-nullable upper bound is placed on an optional
* nullable object - effectively, you get the real object or throw an exception.
*/
public fun <T : Any> T?.orError(message: String): T {
return if (this == null) throw IllegalArgumentException(message) else this
}

View File

@ -12,7 +12,6 @@ import rx.Observable;
import rx.schedulers.Schedulers;
import rx.subjects.PublishSubject;
import org.crosswire.jsword.book.BookException
import org.crosswire.jsword.util.IndexDownloader
import org.crosswire.common.progress.Progress
/**
@ -22,7 +21,8 @@ import org.crosswire.common.progress.Progress
*/
class BookManager(private val installedBooks: Books,
val rM: RefreshManager,
val downloadEvents: PublishSubject<DLProgressEvent>) :
val downloadEvents: PublishSubject<DLProgressEvent>,
val indexManager: MBIndexManager) :
WorkListener, BooksListener {
private val bookJobNamePrefix = Progress.INSTALL_BOOK.substringBeforeLast("%s")
@ -56,8 +56,7 @@ class BookManager(private val installedBooks: Books,
* @param b The book to predict the download job name of
* @return The name of the job that will/is download/ing this book
*/
fun getJobNames(b: Book) = listOf("${bookJobNamePrefix}${b.getInitials()}",
"${indexJobNamePrefix}${b.getInitials()}")
fun getJobName(b: Book) = "${bookJobNamePrefix}${b.getInitials()}"
fun downloadBook(b: Book) {
// First, look up where the Book came from
@ -68,23 +67,14 @@ class BookManager(private val installedBooks: Books,
// thread is closed when the install event is done
installerObs
.observeOn(Schedulers.newThread())
.subscribe {
// Download the actual book
it subscribe { it install b }
}
installerObs
.observeOn(Schedulers.newThread())
.subscribe {
// Download the book index
it subscribe { IndexDownloader.downloadIndex(b, it) }
}
// Download the actual book
.subscribe { it subscribe { it install b } }
// Then notify everyone that we're starting
downloadEvents onNext DLProgressEvent.beginningEvent(b)
// Finally register the jobs in progress
getJobNames(b).forEach { this.inProgressJobNames[it] = b }
inProgressJobNames[getJobName(b)] = b
}
/**
@ -111,6 +101,10 @@ class BookManager(private val installedBooks: Books,
try {
b.getDriver() delete realBook
installedBooksList remove b
// Order matters for the test suite as this line will trigger NPE during testing
// In production, doesn't make a difference, so leave this below the
// installedBooksList remove
indexManager removeIndex realBook
return true
} catch (e: BookException) {
// Log.e("InstalledManager",
@ -128,22 +122,26 @@ class BookManager(private val installedBooks: Books,
fun isInstalled(b: Book) = installedBooksList contains b
// TODO: I have a strange feeling I can simplify this further...
/**
* This method gets called as progress continues on downloading a book.
* To be honest, I don't know that there's any contract about what thread
* this is called on.
* By any means, if the job hasn't been registered as in progress,
* don't emit an event - we don't know what book we're operating on.
*/
override fun workProgressed(ev: WorkEvent) {
val job = ev.getJob()
val book = inProgressJobNames[job.getJobID()]
if (book == null)
return
val book = inProgressJobNames[job.getJobID()] as Book
val oldEvent = inProgressDownloads[book] ?: DLProgressEvent.beginningEvent(book)
var newEvent: DLProgressEvent
if (job.getJobID().contains(bookJobNamePrefix))
newEvent = oldEvent.copy(bookProgress = job.getWork())
else
newEvent = oldEvent.copy(indexProgress = job.getWork())
val newEvent = oldEvent.copy(progress = job.getWork())
downloadEvents onNext newEvent
if (newEvent.averageProgress == DLProgressEvent.PROGRESS_COMPLETE) {
if (newEvent.progress == DLProgressEvent.PROGRESS_COMPLETE) {
inProgressDownloads remove inProgressJobNames[job.getJobID()]
inProgressJobNames remove job.getJobID()
} else

View File

@ -5,8 +5,7 @@ import org.crosswire.jsword.book.Book
/**
* Created by bspeice on 11/11/14.
*/
data class DLProgressEvent(val bookProgress: Int,
val indexProgress: Int,
data class DLProgressEvent(val progress: Int,
val b: Book) {
class object {
val PROGRESS_COMPLETE = 100
@ -16,12 +15,8 @@ data class DLProgressEvent(val bookProgress: Int,
* Build a DLProgressEvent that is just beginning
* Mostly just a nice shorthand
*/
fun beginningEvent(b: Book) = DLProgressEvent(DLProgressEvent.PROGRESS_BEGINNING,
DLProgressEvent.PROGRESS_BEGINNING, b)
fun beginningEvent(b: Book) = DLProgressEvent(DLProgressEvent.PROGRESS_BEGINNING, b)
}
val averageProgress: Int
get() = (bookProgress + indexProgress) / 2
fun toCircular() = (averageProgress.toFloat() * 360 / 100).toInt()
fun toCircular() = (progress.toFloat() * 360 / 100).toInt()
}

View File

@ -0,0 +1,40 @@
package org.bspeice.minimalbible.activity.downloader.manager
import org.crosswire.jsword.index.IndexManager
import rx.subjects.PublishSubject
import org.crosswire.jsword.book.Book
import rx.Observable
import rx.schedulers.Schedulers
import android.util.Log
/**
* There's already an IndexManager, that's why the funky name
*/
class MBIndexManager(val downloadEvents: PublishSubject<DLProgressEvent>,
val indexManager: IndexManager) {
val subscription = downloadEvents subscribe { handleDlEvent(it) }
fun handleDlEvent(event: DLProgressEvent): Unit =
if (event.progress == DLProgressEvent.PROGRESS_COMPLETE) {
subscription.unsubscribe()
buildIndex(event.b)
}
fun buildIndex(b: Book) {
Observable.just(b)
.observeOn(Schedulers.computation())
.subscribe {
try {
Log.d("MBIndexManager", "Beginning index status: ${b.getIndexStatus()}")
indexManager scheduleIndexCreation b
Log.d("MBIndexManager", "Ending index status: ${b.getIndexStatus()}")
} catch (e: Exception) {
Log.e("MBIndexManager", "Exception building index: ${e}", e)
}
}
Log.d("MBIndexManager", "Building index for ${b.getInitials()}")
}
fun removeIndex(b: Book) = indexManager.deleteIndex(b)
}

View File

@ -1,14 +1,36 @@
package org.bspeice.minimalbible.activity.search
import org.crosswire.jsword.passage.Verse
import org.crosswire.jsword.index.search.SearchType
import org.crosswire.jsword.book.Book
import android.util.Log
import org.crosswire.jsword.index.IndexManager
/**
* This is the entry point for handling the actual bible search. Likely will support
* an "advanced" search in the future, but for now, basicTextSearch is what you get.
*/
class SearchProvider() {
class SearchProvider(val b: Book, val indexManager: IndexManager) {
val defaultSearchType = SearchType.ANY_WORDS
[suppress("UNUSED_PARAMETER")]
public fun basicTextSearch(text: String): List<Verse> =
listOf()
public fun basicTextSearch(text: String): List<Verse> {
if (!isSearchAvailable()) {
Log.w("SearchProvider", "Search unavailable, index status of ${b.getInitials()}: ${b.getIndexStatus()}")
return listOf()
}
val searchText = defaultSearchType.decorate(text)
val results = b.find(searchText)
return results.map { it as Verse }
}
/**
* Handler to check if the index is available - because it doesn't
* seem to register itself properly in the book metadata
*/
public fun isSearchAvailable(): Boolean =
indexManager.isIndexed(b)
}