package net.bible.android.control.search; import android.app.Activity; import android.content.Intent; import android.util.Log; import net.bible.android.SharedConstants; import net.bible.android.activity.R; import net.bible.android.control.ApplicationScope; import net.bible.android.control.navigation.DocumentBibleBooksFactory; import net.bible.android.control.page.CurrentBiblePage; import net.bible.android.control.page.PageControl; import net.bible.android.control.page.window.ActiveWindowPageManagerProvider; import net.bible.android.control.versification.Scripture; import net.bible.android.view.activity.base.CurrentActivityHolder; import net.bible.android.view.activity.base.Dialogs; import net.bible.android.view.activity.search.Search; import net.bible.android.view.activity.search.SearchIndex; import net.bible.service.common.CommonUtils; import net.bible.service.sword.SwordContentFacade; import net.bible.service.sword.SwordDocumentFacade; import org.apache.commons.lang3.StringUtils; import org.crosswire.jsword.book.Book; import org.crosswire.jsword.book.BookException; import org.crosswire.jsword.book.basic.AbstractPassageBook; import org.crosswire.jsword.book.sword.SwordBook; import org.crosswire.jsword.index.IndexStatus; import org.crosswire.jsword.index.lucene.LuceneIndex; import org.crosswire.jsword.index.search.SearchType; import org.crosswire.jsword.passage.Key; import org.crosswire.jsword.passage.Verse; import org.crosswire.jsword.versification.BibleBook; import org.crosswire.jsword.versification.Versification; import javax.inject.Inject; /** Support for the document search functionality * * @author Martin Denham [mjdenham at gmail dot com] * @see gnu.lgpl.License for license details.<br> * The copyright to this program is held by it's author. */ @ApplicationScope public class SearchControl { private boolean isSearchShowingScripture = true; private final SwordDocumentFacade swordDocumentFacade; private final SwordContentFacade swordContentFacade; public enum SearchBibleSection { OT, NT, CURRENT_BOOK, ALL } private static final String SEARCH_OLD_TESTAMENT = "+[Gen-Mal]"; private static final String SEARCH_NEW_TESTAMENT = "+[Mat-Rev]"; public static final String SEARCH_TEXT = "SearchText"; public static final String SEARCH_DOCUMENT = "SearchDocument"; public static final String TARGET_DOCUMENT = "TargetDocument"; private static final String STRONG_COLON_STRING = LuceneIndex.FIELD_STRONG+":"; private static final String STRONG_COLON_STRING_PLACE_HOLDER = LuceneIndex.FIELD_STRONG+"COLON"; public static final int MAX_SEARCH_RESULTS = 1000; private final DocumentBibleBooksFactory documentBibleBooksFactory; private final PageControl pageControl; private final ActiveWindowPageManagerProvider activeWindowPageManagerProvider; private static final String TAG = "SearchControl"; @Inject public SearchControl(SwordDocumentFacade swordDocumentFacade, SwordContentFacade swordContentFacade, DocumentBibleBooksFactory documentBibleBooksFactory, PageControl pageControl, ActiveWindowPageManagerProvider activeWindowPageManagerProvider) { this.swordDocumentFacade = swordDocumentFacade; this.swordContentFacade = swordContentFacade; this.documentBibleBooksFactory = documentBibleBooksFactory; this.pageControl = pageControl; this.activeWindowPageManagerProvider = activeWindowPageManagerProvider; } /** if current document is indexed then go to search else go to download index page * * @return required Intent */ public Intent getSearchIntent(Book document) { IndexStatus indexStatus = document.getIndexStatus(); Log.d(TAG, "Index status:"+indexStatus); Activity currentActivity = CurrentActivityHolder.getInstance().getCurrentActivity(); if (indexStatus.equals(IndexStatus.DONE)) { Log.d(TAG, "Index status is DONE"); return new Intent(currentActivity, Search.class); } else { Log.d(TAG, "Index status is NOT DONE"); return new Intent(currentActivity, SearchIndex.class); } } public boolean validateIndex(Book document) { return document.getIndexStatus().equals(IndexStatus.DONE); } public String getCurrentBookName() { try { CurrentBiblePage currentBiblePage = activeWindowPageManagerProvider.getActiveWindowPageManager().getCurrentBible(); Versification v11n = ((SwordBook) currentBiblePage.getCurrentDocument()).getVersification(); BibleBook book = currentBiblePage.getSingleKey().getBook(); String longName = v11n.getLongName(book); if (StringUtils.isNotBlank(longName) && longName.length() < 14) { return longName; } else { return v11n.getShortName(book); } } catch (Exception nsve) { // This should never occur Log.e(TAG, "Error getting current book name", nsve); return "-"; } } public String decorateSearchString(String searchString, SearchType searchType, SearchBibleSection bibleSection, String currentBookName) { String cleanSearchString = cleanSearchString(searchString); String decorated; // add search type (all/any/phrase) to search string decorated = searchType.decorate(cleanSearchString); // add bible section limitation to search text decorated = getBibleSectionTerm(bibleSection, currentBookName)+" "+decorated; return decorated; } /** do the search query and prepare results in lists ready for display * */ public SearchResultsDto getSearchResults(String document, String searchText) throws BookException { Log.d(TAG, "Preparing search results"); SearchResultsDto searchResults = new SearchResultsDto(); // search the current book Book book = swordDocumentFacade.getDocumentByInitials(document); Key result = swordContentFacade.search(book, searchText); if (result!=null) { int resNum = result.getCardinality(); Log.d(TAG, "Number of results:"+resNum); //if Bible or commentary then filter out any non Scripture keys, otherwise don't filter boolean isBibleOrCommentary = book instanceof AbstractPassageBook; for (int i=0; i<Math.min(resNum, MAX_SEARCH_RESULTS+1); i++) { Key key = result.get(i); boolean isMain = (!isBibleOrCommentary || Scripture.isScripture(((Verse)key).getBook())); searchResults.add(key, isMain); } } return searchResults; } /** get the verse for a search result */ public String getSearchResultVerseText(Key key) { // There is similar functionality in BookmarkControl String verseText = ""; try { verseText = swordContentFacade.getPlainText(activeWindowPageManagerProvider.getActiveWindowPageManager().getCurrentBible().getCurrentDocument(), key, 1); verseText = CommonUtils.limitTextLength(verseText); } catch (Exception e) { Log.e(TAG, "Error getting verse text", e); } return verseText; } /** double spaces, :, and leading or trailing space cause lucene errors */ private String cleanSearchString(String search) { // remove colons but leave Strong lookups // replace "strong:" with a place holder, remove ':', replace "strong:" search = search.replace(STRONG_COLON_STRING, STRONG_COLON_STRING_PLACE_HOLDER); search = search.replace(":", " "); search = search.replace(STRONG_COLON_STRING_PLACE_HOLDER, STRONG_COLON_STRING); return search.replace(" ", " ").trim(); } /** get OT, NT, or all query limitation */ private String getBibleSectionTerm(SearchBibleSection bibleSection, String currentBookName) { switch (bibleSection) { case ALL: return ""; case OT: return SEARCH_OLD_TESTAMENT; case NT: return SEARCH_NEW_TESTAMENT; case CURRENT_BOOK: if (currentBookName==null) { currentBookName = getCurrentBookName(); } return "+[" + currentBookName + "]"; default: Log.e(TAG, "Unexpected radio selection"); return ""; } } /** download index * * @return true if managed to start download in background */ public boolean downloadIndex(Book book) { boolean ok = false; try { if (CommonUtils.getSDCardMegsFree()<SharedConstants.REQUIRED_MEGS_FOR_DOWNLOADS) { Dialogs.getInstance().showErrorMsg(R.string.storage_space_warning); } else if (!CommonUtils.isInternetAvailable()) { Dialogs.getInstance().showErrorMsg(R.string.no_internet_connection); ok = false; } else { if (swordDocumentFacade.isIndexDownloadAvailable(book)) { // this starts a new thread to do the indexing and returns immediately // if index creation is already in progress then nothing will happen swordDocumentFacade.downloadIndex(book); ok = true; } else { Dialogs.getInstance().showErrorMsg(R.string.index_not_available_for_download); ok = false; } } } catch (Exception e) { Log.e(TAG, "error indexing:"+e.getMessage()); e.printStackTrace(); ok = false; } return ok; } /** download index * * @return true if managed to start download in background */ public boolean createIndex(Book book) { boolean ok = false; try { // this starts a new thread to do the indexing and returns immediately // if index creation is already in progress then nothing will happen swordDocumentFacade.ensureIndexCreation(book); ok = true; } catch (Exception e) { Log.e(TAG, "error indexing:"+e.getMessage()); e.printStackTrace(); } return ok; } /** * When navigating books and chapters there should always be a current Passage based book */ private AbstractPassageBook getCurrentPassageDocument() { return activeWindowPageManagerProvider.getActiveWindowPageManager().getCurrentPassageDocument(); } public boolean isCurrentDefaultScripture() { return pageControl.isCurrentPageScripture(); } public boolean currentDocumentContainsNonScripture() { return !documentBibleBooksFactory.getDocumentBibleBooksFor(getCurrentPassageDocument()).isOnlyScripture(); } public boolean isCurrentlyShowingScripture() { return isSearchShowingScripture || !currentDocumentContainsNonScripture(); } }