package uc.files.downloadqueue;
import helpers.GH;
import helpers.Observable;
import helpers.PreferenceChangedAdapter;
import helpers.StatusObject;
import helpers.StatusObject.ChangeType;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.Future;
import logger.LoggerFactory;
import org.apache.log4j.Logger;
import uc.DCClient;
import uc.IStoppable;
import uc.LanguageKeys;
import uc.PI;
import uc.crypto.HashValue;
import uc.database.IDatabase;
import uc.files.IDownloadable;
import uc.files.IDownloadable.IDownloadableFile;
import uc.files.downloadqueue.Block.FileChannelManager;
import uc.files.filelist.FileList;
public class DownloadQueue extends Observable<StatusObject> implements IStoppable {
public static final int FILEDQE_BLOCKSTATUSCHANGED = 1,
DQE_TRANSFER_STARTED = 2,
DQE_TRANSFER_FINISHED = 3,
DQE_FIRST_TRANSFER_STARTED = 4,
DQE_LAST_TRANSFER_FINISHED = 5;
private static final Logger logger = LoggerFactory.make();
private final DCClient dcc;
private final FileChannelManager fileChannelManager;
private final PreferenceChangedAdapter pfa;
public DownloadQueue(DCClient dcclient) {
this.dcc = dcclient;
pfa = new PreferenceChangedAdapter(PI.get(),PI.tempDownloadDirectory){
@Override
public void preferenceChanged(String preference,String oldvalue,String newValue) {
if (!tthRoots.isEmpty()) { //restart if the DownloadQueue is not empty..
logger.debug("restarting workbench");
dcc.restart();
}
}
};
fileChannelManager = new FileChannelManager();
}
private final DownloadQueueFolder root =
new DownloadQueueFolder(null, ""); //A tree structure for viewing all the DQEs.
//the DQEs mapped to its hash or Userid in case of a FileList
private Map<HashValue,AbstractDownloadQueueEntry> tthRoots
= Collections.synchronizedMap(new HashMap<HashValue,AbstractDownloadQueueEntry>());
private final CopyOnWriteArrayList<File> recommendedFolders = new CopyOnWriteArrayList<File>();
public FileChannelManager getFileChannelManager() {
return fileChannelManager;
}
/**
* loads the download queue from the disc ..
* or database
*/
public Future<?> loadDQ() {
//move files if needed
dcc.logEvent("Loading DownloadQueue");
String oldDir = PI.get(PI.changedTempDownloadDirectory);
if (GH.isNullOrEmpty(oldDir)) {
oldDir = PI.get(PI.tempDownloadDirectory);
PI.put(PI.changedTempDownloadDirectory,oldDir);
logger.debug("written oldDir: "+oldDir);
}
logger.debug("current oldDir: "+oldDir);
if (!PI.get(PI.tempDownloadDirectory).equals(oldDir)) {
File old = new File(oldDir);
File actual = PI.getTempDownloadDirectory();
dcc.logEvent("Moving files to new temp Download directory");
move(old,actual);
dcc.logEvent("Finished moving files");
//delete moving notification
PI.put(PI.changedTempDownloadDirectory,PI.get(PI.tempDownloadDirectory));
}
//load persistence data from the db
final Set<DQEDAO> dqes = getDatabase().loadDQEsAndUsers();
synchronized (dqes) {
logger.debug("dqes loaded: "+dqes.size());
}
Future<?> fut;
fut = dcc.getSchedulerDir().submit(new Runnable() {
public void run() {
synchronized(dqes) {
logger.debug("Start adding");
Set<String> presentTargetPaths = new HashSet<String>();
try {
for (DQEDAO dqedao : dqes) {
if (presentTargetPaths.add(dqedao.getPath())) {
FileDQE.restore(dqedao,DownloadQueue.this);
} else {
logger.info("Redundant TargetPath "+dqedao.getPath()+" "+dqedao.getTTHRoot());
dcc.getDatabase().modifyDQEDAO(dqedao,ChangeType.REMOVED);
}
}
} catch(Exception e) {
logger.error(e,e);
}
logger.debug("end adding");
}
}
});
return fut;
}
/**
* deletes all FileList files from the list..
* and removes listener..
*
*/
public void stop() {
pfa.dispose();
List<AbstractDownloadQueueEntry> adqes;
synchronized(tthRoots) {
adqes = new ArrayList<AbstractDownloadQueueEntry>(tthRoots.values());
}
for (AbstractDownloadQueueEntry adqe: adqes) {
switch(adqe.getType()) {
case FILELIST:
adqe.remove();
break;
case FILE:
FileDQE fdqe = (FileDQE)adqe;
fdqe.storeRestoreInfo();
break;
case TTHL: // do nothing
}
}
}
/**
* Adds a DownloadqueueEntry to the queue
*
* @param dqe the item
*
* may only be called by the DoenloadQueEntry..
*/
void addDownloadQueueEntry(AbstractDownloadQueueEntry dqe) {
logger.debug("called addDownloadQueueEntry("+dqe+")");
//test existence of the DQE then add
AbstractDownloadQueueEntry existing = tthRoots.get(dqe.getID());
if (existing == null) {
// if it doesn't exist add the dqe to "filesystem" and to the tth-dqe mapping
tthRoots.put( dqe.getID() , dqe );
synchronized(root) {
adddqeToTree(dqe, dqe.getTargetPath().getPath() , root);
}
notifyObservers(new StatusObject(dqe,ChangeType.ADDED));
logger.debug("dqe was dded to tree");
} else {
throw new IllegalArgumentException("dqe already existed in the queue");
}
}
/**
* tests if the provided id is already present
* @param id - the TTH for a FileDQE or the userId for a FileList
* @return true if already present
*/
public boolean containsDQE(HashValue id) {
return tthRoots.containsKey(id);
}
/**
* checks if exactly the provided dqe is in the Queue
* @param adqe
* @return true if its in the DownloadQueue
*/
public boolean containsDQE(AbstractDownloadQueueEntry adqe) {
return adqe.equals(tthRoots.get(adqe.getID()));
}
/**
* makes a substring search in all DQE (case insensitive)
*
* @param search - on search
* @return all DQEs that match search
*/
public List<AbstractDownloadQueueEntry> search(String search) {
search = search.toLowerCase();
List<AbstractDownloadQueueEntry> adqes = new ArrayList<AbstractDownloadQueueEntry>();
for (AbstractDownloadQueueEntry adqe: tthRoots.values()) {
if (adqe.getTargetPath().getPath().toLowerCase().contains(search)) {
adqes.add(adqe);
}
}
return adqes;
}
private void adddqeToTree(AbstractDownloadQueueEntry dqe , String path , DownloadQueueFolder current ){
int i = path.indexOf(java.io.File.separatorChar,1);
if (i == -1) {
current.add(dqe);
} else {
DownloadQueueFolder next = (DownloadQueueFolder)current.getFolder(path.substring(0, i));
if ( next == null) {
next = new DownloadQueueFolder(current,path.substring(0, i) );
}
adddqeToTree(dqe, path.substring(i+1), next );
}
}
/**
* removes a file/DownloadqueueEntry by its tthRoot
* may only be called by the dqe's remove() method!!!
* @param dqe the dqe that calls this method and is removed..
*/
void removeFile(AbstractDownloadQueueEntry dqe ){
if (dqe == null) {
throw new IllegalArgumentException("called with null argument");
}
tthRoots.remove(dqe.getID());
synchronized(root) {
removedqeFromTree(dqe,dqe.getTargetPath().getPath(),root);
}
notifyObservers(new StatusObject(dqe,ChangeType.REMOVED));
}
/**
* recursive method for removing the file from the treestructured Folder/filesystem...
* called from above
*
* @param dqe
* @param path
* @param current
*/
private void removedqeFromTree(AbstractDownloadQueueEntry dqe, String path, DownloadQueueFolder current ){
int i = path.indexOf(java.io.File.separatorChar,1);
if(i == -1){
//first remove the dqe itself
current.removeFromDQE(dqe.getFileName());
//then remove all now empty parent folders
DownloadQueueFolder above;
while (current.getChildren().length==0) {
above = current.getParent();
if (above!=null) {
above.removeFromFolder(current.getName());
current = above;
} else {
break;
}
}
} else {
DownloadQueueFolder next = (DownloadQueueFolder)current.getFolder(path.substring(0, i));
if( next == null) {
return;
} else {
removedqeFromTree(dqe, path.substring(i+1), next );
}
}
}
/**
* matches a FileList..
* the User is added to each file he shares..
* @param f
*/
public void match(FileList f) {
int count = 0 ;
synchronized(tthRoots) {
for (HashValue h: tthRoots.keySet()) {
if (f.search(h) != null) {
get(h).addUser(f.getUsr());
count++;
}
}
}
dcc.logEvent(String.format(LanguageKeys.MatchedXFilesWithUserY,count,f.getUsr().getNick()));
}
/**
* Retrieves recommended Target paths
* specialized for the provided downloadable
*
* @param downloadable a file for which we want a path recommended
* @return all paths where the file is recommended be downloaded to..
* may be empty..
*
*
*/
public List<File> getPathRecommendation(IDownloadable downloadable) {
List<File> found = new ArrayList<File>();
if (downloadable.isFile()) {
IDownloadableFile idf= (IDownloadableFile)downloadable;
if (containsDQE(idf.getTTHRoot())) {
found.add(get(idf.getTTHRoot()).getTargetPath() );
}
// try {
File f = dcc.getFilelist().getFile(idf.getTTHRoot());
if (f != null) {
found.add(f);
}
// } catch(FilelistNotReadyException fnre) {
// logger.debug(fnre,fnre);
// }
// for (File path : recommendedFolders) {
// found.add(new File(path,downloadable.getName()));
// }
} //else {
for (File path : recommendedFolders) {
found.add(new File(path,downloadable.getName()));
}
//}
return found;
}
public void addPathForRecommendation(File path) {
recommendedFolders.addIfAbsent(path);
if (recommendedFolders.size() > 5) {
recommendedFolders.remove(0);
}
}
public IDatabase getDatabase() {
return dcc.getDatabase();
}
DCClient getDcc() {
return dcc;
}
/**
* @return the root
*/
public DownloadQueueFolder getRoot() {
return root;
}
public AbstractDownloadQueueEntry get(HashValue arg0) {
return tthRoots.get(arg0);
}
/**
* calculates how much bytes are left for downloading in total...
*/
public long getTotalSize() {
long totalsize = 0;
synchronized (tthRoots) {
for (AbstractDownloadQueueEntry adqe: tthRoots.values()) {
if (adqe instanceof AbstractFileDQE) {
AbstractFileDQE afdqe = (AbstractFileDQE)adqe;
totalsize += afdqe.getSize();
}
}
}
return totalsize;
}
/**
* @return the total number of files found..
*/
public int getTotalNrOfFiles() {
synchronized(tthRoots) {
return tthRoots.size();
}
}
public List<AbstractFileDQE> getAllFileDQE() {
List<AbstractFileDQE> files = new ArrayList<AbstractFileDQE>();
synchronized(tthRoots) {
for (AbstractDownloadQueueEntry adqe: tthRoots.values()) {
if (adqe instanceof AbstractFileDQE) {
files.add((AbstractFileDQE)adqe);
}
}
}
return files;
}
public Set<AbstractDownloadQueueEntry> getAllDQE() {
return new HashSet<AbstractDownloadQueueEntry>(tthRoots.values());
}
/**
*
* @return add DQE that have a transfer running..
*/
public Set<AbstractDownloadQueueEntry> getAllRunningDQE() {
HashSet<AbstractDownloadQueueEntry> hs = new HashSet<AbstractDownloadQueueEntry>();
synchronized(tthRoots) {
for (AbstractDownloadQueueEntry adqe: tthRoots.values()) {
if (adqe.getNrOfRunningDownloads() > 0) {
hs.add(adqe);
}
}
}
return hs;
}
private void move(File sourceFolder , File targetFolder) {
if (sourceFolder.isDirectory()) {
for (File f:sourceFolder.listFiles()) {
if (f.isFile()) {
logger.debug("sourcefile: "+f);
try {
AbstractDownloadQueueEntry.moveFile(f, new File(targetFolder,f.getName()),dcc);
} catch(IOException ioe) {
logger.warn(ioe, ioe);
}
}
}
}
}
}