/*
* Created by Angel Leon (@gubatron), Alden Torres (aldenml)
* Copyright (c) 2011, 2012, FrostWire(TM). All rights reserved.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.bt.download.android.gui;
import java.text.Normalizer;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Locale;
import java.util.Set;
import android.text.Html;
import com.bt.download.android.core.ConfigurationManager;
import com.bt.download.android.core.Constants;
import com.frostwire.util.StringUtils;
import com.frostwire.search.CrawlPagedWebSearchPerformer;
import com.frostwire.search.CrawledSearchResult;
import com.frostwire.search.FileSearchResult;
import com.frostwire.search.SearchManager;
import com.frostwire.search.SearchManagerImpl;
import com.frostwire.search.SearchManagerListener;
import com.frostwire.search.SearchPerformer;
import com.frostwire.search.SearchResult;
import com.frostwire.search.torrent.TorrentSearchResult;
import com.frostwire.search.youtube.YouTubeCrawledSearchResult;
/**
* @author gubatron
* @author aldenml
*
*/
public final class LocalSearchEngine {
private final SearchManager manager;
// filter constants
private final int MIN_SEEDS_TORRENT_RESULT;
private SearchManagerListener listener;
private long currentSearchToken;
private List<String> currentSearchTokens;
private boolean searchFinished;
private String androidId;
private static LocalSearchEngine instance;
public synchronized static void create(String androidId) {
if (instance != null) {
return;
}
instance = new LocalSearchEngine(androidId);
}
public static LocalSearchEngine instance() {
return instance;
}
private LocalSearchEngine(String androidId) {
this.manager = new SearchManagerImpl();
this.manager.registerListener(new ManagerListener());
this.MIN_SEEDS_TORRENT_RESULT = ConfigurationManager.instance().getInt(Constants.PREF_KEY_SEARCH_MIN_SEEDS_FOR_TORRENT_RESULT);
this.androidId = androidId;
}
public String getAndroidId() {
return androidId;
}
public void registerListener(SearchManagerListener listener) {
this.listener = listener;
}
public void performSearch(String query) {
if (StringUtils.isNullOrEmpty(query, true)) {
return;
}
manager.stop();
currentSearchToken = Math.abs(System.nanoTime());
currentSearchTokens = tokenize(query);
searchFinished = false;
for (SearchEngine se : SearchEngine.getEngines()) {
if (se.isEnabled()) {
SearchPerformer p = se.getPerformer(currentSearchToken, query);
manager.perform(p);
}
}
}
public void cancelSearch() {
manager.stop();
currentSearchToken = 0;
currentSearchTokens = null;
searchFinished = true;
}
public boolean isSearchStopped() {
return currentSearchToken == 0;
}
public boolean isSearchFinished() {
return searchFinished;
}
public void clearCache() {
CrawlPagedWebSearchPerformer.clearCache();
}
public long getCacheSize() {
return CrawlPagedWebSearchPerformer.getCacheSize();
}
private void onFinished(long token) {
searchFinished = true;
if (listener != null) {
listener.onFinished(token);
}
}
private List<SearchResult> filter(SearchPerformer performer, List<SearchResult> results) {
List<SearchResult> list;
if (currentSearchTokens == null || currentSearchTokens.isEmpty()) {
list = Collections.emptyList();
} else {
list = filter(results);
}
return list;
}
private List<SearchResult> filter(List<? extends SearchResult> results) {
List<SearchResult> list = new LinkedList<SearchResult>();
try {
for (SearchResult sr : results) {
if (sr instanceof TorrentSearchResult) {
if (((TorrentSearchResult) sr).getSeeds() == -1) {
long creationTime = ((TorrentSearchResult) sr).getCreationTime();
long age = System.currentTimeMillis() - creationTime;
if (age > 31536000000l) {
continue;
}
} else if (((TorrentSearchResult) sr).getSeeds() < MIN_SEEDS_TORRENT_RESULT) {
continue;
}
}
if (sr instanceof CrawledSearchResult) {
if (sr instanceof YouTubeCrawledSearchResult) {
// special case for flv files
if (!((YouTubeCrawledSearchResult) sr).getFilename().endsWith(".flv")) {
list.add(sr);
}
} else if (filter(new LinkedList<String>(currentSearchTokens), sr)) {
list.add(sr);
}
} else {
list.add(sr);
}
}
} catch (Throwable e) {
// possible NPE due to cancel search or some inner error in search results, ignore it and cleanup list
list.clear();
}
return list;
}
private boolean filter(List<String> tokens, SearchResult sr) {
StringBuilder sb = new StringBuilder();
sb.append(sr.getDisplayName());
if (sr instanceof CrawledSearchResult) {
sb.append(((CrawledSearchResult) sr).getParent().getDisplayName());
}
if (sr instanceof FileSearchResult) {
sb.append(((FileSearchResult) sr).getFilename());
}
String str = sanitize(sb.toString());
str = normalize(str);
Iterator<String> it = tokens.iterator();
while (it.hasNext()) {
String token = it.next();
if (str.contains(token)) {
it.remove();
}
}
return tokens.isEmpty();
}
private String sanitize(String str) {
str = Html.fromHtml(str).toString();
str = str.replaceAll("\\.torrent|www\\.|\\.com|\\.net|[\\\\\\/%_;\\-\\.\\(\\)\\[\\]\\n\\rÐ&~{}\\*@\\^'=!,¡|#ÀÁ]", " ");
str = StringUtils.removeDoubleSpaces(str);
return str.trim();
}
private String normalize(String token) {
String norm = Normalizer.normalize(token, Normalizer.Form.NFKD);
norm = norm.replaceAll("\\p{InCombiningDiacriticalMarks}+", "");
norm = norm.toLowerCase(Locale.US);
return norm;
}
private Set<String> normalizeTokens(Set<String> tokens) {
Set<String> normalizedTokens = new HashSet<String>();
for (String token : tokens) {
String norm = normalize(token);
normalizedTokens.add(norm);
}
return normalizedTokens;
}
private List<String> tokenize(String keywords) {
keywords = sanitize(keywords);
Set<String> tokens = new HashSet<String>(Arrays.asList(keywords.toLowerCase(Locale.US).split(" ")));
return new ArrayList<String>(normalizeTokens(tokens));
}
private final class ManagerListener implements SearchManagerListener {
@Override
public void onResults(SearchPerformer performer, List<? extends SearchResult> results) {
if (listener != null && !performer.isStopped()) {
if (performer.getToken() == currentSearchToken) { // one more additional protection
@SuppressWarnings("unchecked")
List<SearchResult> filtered = filter(performer, (List<SearchResult>) results);
if (!filtered.isEmpty()) {
listener.onResults(performer, filtered);
}
} else {
performer.stop(); // why? just in case there is an inner error in an alternative search manager
}
}
}
@Override
public void onFinished(long token) {
if (token == currentSearchToken) {
LocalSearchEngine.this.onFinished(token);
}
}
}
}