package uc.files.search;
import helpers.GH;
import helpers.Observable;
import helpers.StatusObject;
import helpers.StatusObject.ChangeType;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.eclipse.core.runtime.Assert;
import uc.crypto.HashValue;
import uc.crypto.UDPEncryption;
import uc.files.MultiUserAbstractDownloadable;
import uc.files.IDownloadable.IDownloadableFile;
import uc.files.search.SearchResult.ISearchResultListener;
/**
* A command pattern for searches
*
* also accumulates the search results..
*
* Search in jucy works like this:
* SearchEditor creates a Search and puts it in DCClient to start search and listen for results..
*
* UDPHandler and Hub listen for incoming SearchResults when UDPHandler receives a SearchResult
* the hub of the sender is determined over the nick(NMDC, CID in ADC) (and IP:port of the hub)...
* then the search result is forwarded to this hub and treated as received by the hub
*
* the hub gets information from the searchResult string and creates an SR object from it
*
* this sr is then passed to the dcclient that forwards it to all ISearchResultsListener
* registered with the client
*
* @author Quicksilver
*
*/
public class FileSearch extends Observable<StatusObject> implements ISearchResultListener {
/**
* collection containing all results..
*/
private final List<ISearchResult> results = Collections.synchronizedList(
new ArrayList<ISearchResult>());
private final Map<HashValue,ISearchResult> resultFiles = Collections.synchronizedMap(
new HashMap<HashValue,ISearchResult>());
/**
* type enum for search
*/
private final SearchType searchType;
/**
* enum for larger .. or smaller.. or equal size
*/
private final ComparisonEnum comparisonEnum;
/**
* if comparisonEnum sets a comparison
* then this will be to compare against
* -1 if no comparison should be used
*/
private final long size;
/**
* the string the user typed in...
*/
private final String searchString;
private static int SEARCH_NONCE_COUNTER = GH.nextInt(10000);
private static synchronized int getNextNonce() {
SEARCH_NONCE_COUNTER+=GH.nextInt(10000)+1;
return SEARCH_NONCE_COUNTER;
}
private final String token;
private final byte[] encryptionKey ;
private int nrOfResults = 0;
/**
* how long SearchResults will be accepted at most..
* 10 minutes default..
*/
public static final int MAX_SEARCH_TIME = 10*60*1000;
private final long startOfSearch;
/**
* creates a search type
* @param searchstring - the string of the search
* @param searchType - what to search for .. usually ALL or TTH
* @param comparisonEnum - if there is a size restriction ... if size == -1 --> comparison = null
* @param size
*/
public FileSearch(String searchstring , SearchType searchType, ComparisonEnum comparisonEnum, long size) {
this.searchString = searchstring;
this.searchType = searchType;
this.comparisonEnum = comparisonEnum;
Assert.isTrue(comparisonEnum != null || size == -1);
this.size = size;
encryptionKey = UDPEncryption.getRandomKey();
this.token = Integer.toHexString(getNextNonce());
startOfSearch = System.currentTimeMillis();
}
/**
* used for automatic search for alternatives..
* @param tth -the tth root to search for..
*/
public FileSearch(HashValue tth) {
this(tth.toString(),SearchType.TTH,null,-1);
}
/**
*
* @param search - string sequence used for searching
* @return a list of all search result containing the given string sequence
* case insensitive
*/
public List<ISearchResult> searchSubset(String search) {
List<ISearchResult> srs = new ArrayList<ISearchResult>();
search = search.toLowerCase();
for (ISearchResult sr: results) {
if (sr.getPath().toLowerCase().contains(search)) {
srs.add(sr);
}
}
return srs;
}
public ComparisonEnum getComparisonEnum() {
return comparisonEnum;
}
public String getSearchString() {
return searchString;
}
public SearchType getSearchType() {
return searchType;
}
public long getSize() {
return size;
}
/**
* test a search result if it matches this search
*
* @param sr - a SearchResult to test
* @return if the SearchResult matches this search
*/
private boolean matches(ISearchResult sr) {
if (sr.getToken() != null) {
return sr.getToken().equals(token);
}
if (System.currentTimeMillis() - startOfSearch > MAX_SEARCH_TIME ) {
return false;
}
if (sr.isFile() && sr.getTTHRoot().toString().equals(getSearchString())) {
return true;
}
String path = sr.getPath();
String[] words= getSearchString().split(" ");
for (String word: words) { //remove all strings starting with -
if (word.startsWith("-")) {
if (word.length() > 1 && path.toLowerCase().contains(word.substring(1).toLowerCase())) {
return false;
}
} else {
if (!path.toLowerCase().contains(word.toLowerCase()) ) {
return false;
}
}
}
return true;
}
private void add(ISearchResult sr){
synchronized(this) {
nrOfResults++;
}
if (sr.isFile()) {
IDownloadableFile f = (IDownloadableFile)sr;
ISearchResult present = resultFiles.get(sr.getTTHRoot());
if (present != null) {
if (present instanceof MultiUserAbstractDownloadable) {
((MultiUserAbstractDownloadable)present).addFile(f);
fireListener(sr,present,ChangeType.ADDED);
fireListener(present,this,ChangeType.CHANGED);
} else {
results.remove(present);
resultFiles.remove(present.getTTHRoot());
fireListener(present,this,ChangeType.REMOVED);
MultiUserAbstractDownloadable muad = new MultiUserAbstractDownloadable((IDownloadableFile)present);
muad.addFile(f);
results.add(muad);
resultFiles.put(muad.getTTHRoot(), muad);
fireListener(muad,this,ChangeType.ADDED);
fireListener(present,muad,ChangeType.ADDED);
fireListener(sr,muad,ChangeType.ADDED);
}
} else {
results.add(sr);
resultFiles.put(sr.getTTHRoot(), sr);
fireListener(sr,this,ChangeType.ADDED);
}
} else {
results.add(sr);
fireListener(sr,this,ChangeType.ADDED);
}
}
private void fireListener(ISearchResult sr,Object parent,ChangeType ct ) {
notifyObservers(new StatusObject(sr, ct, 0, parent));
}
public List<ISearchResult> getResults() {
return results;
}
/**
* when a sr is received we check if it matches our search and then add it..
*/
public void received(ISearchResult sr) {
if (matches(sr)) {
add(sr);
}
}
public String getToken() {
return token;
}
public int getNrOfResults() {
synchronized(this) {
return nrOfResults;
}
}
public int getNrOfFiles() {
return resultFiles.size();
}
public byte[] getEncryptionKey() {
return encryptionKey;
}
}