package uc.files; import helpers.Observable; import helpers.StatusObject; import helpers.StatusObject.ChangeType; import java.io.File; import java.net.InetAddress; import java.util.ArrayList; import java.util.Date; import java.util.HashMap; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.concurrent.CopyOnWriteArrayList; import java.util.concurrent.ScheduledFuture; import java.util.concurrent.TimeUnit; import logger.LoggerFactory; import org.apache.log4j.Logger; import uc.DCClient; import uc.IHasUser; import uc.IUser; import uc.crypto.HashValue; /** * a queue that keeps an eye on who wants to * download what from us, also who has downloaded what from us * and in Future.. who should get a slot next from us * * Though UploadQueue now also manages the Transfers.. * may be better would be moving this both into a new class * and let upload Queue really only be UploadQueue and not Finished * transfers for down as well as up * * * @author Quicksilver * */ public class UploadQueue extends Observable<StatusObject> implements IUploadQueue { private static Logger logger = LoggerFactory.make(); private static final int MAX_TRANSFERRECORD_SIZE = 10000; private final Map<IUser,UploadInfo> requestedFiles = new HashMap<IUser,UploadInfo>(); private final Map<IUser,UserInfo> recordsPerUser = new HashMap<IUser,UserInfo>(); private final List<TransferRecord> transferRecords = new LinkedList<TransferRecord>(); //for average speed private volatile long totalDuration = 0; private volatile long totalSize = 0; private final DCClient dcc; private ScheduledFuture<?> cleaner; public UploadQueue(DCClient dcc) { this.dcc = dcc; } /* (non-Javadoc) * @see uc.files.IUploadQueue#start() */ public void start() { cleaner = dcc.getSchedulerDir().scheduleAtFixedRate(new Runnable() { public void run() { clean(); } }, 24 * 3600, 1*3600, TimeUnit.SECONDS); //hours no longer exists.. } public void stop() { if (cleaner != null) { cleaner.cancel(false); cleaner = null; } } /* (non-Javadoc) * @see uc.files.IUploadQueue#userRequestedFile(uc.IUser, java.lang.String, uc.crypto.HashValue, boolean) */ public void userRequestedFile(IUser usr,String nameOfTransferred,HashValue hash,boolean gotASlot) { if (nameOfTransferred == null) { nameOfTransferred = hash.toString(); } UploadInfo present; boolean added; synchronized(requestedFiles) { present = requestedFiles.get(usr); if (present == null) { present = new UploadInfo(usr,nameOfTransferred,hash,gotASlot); requestedFiles.put(usr, present); added = true; } else { present.requested(nameOfTransferred, hash, gotASlot); added = false; } } notifyObservers(new StatusObject(present, added? ChangeType.ADDED : ChangeType.CHANGED)); } /* (non-Javadoc) * @see uc.files.IUploadQueue#transferFinished(java.io.File, uc.IUser, java.lang.String, uc.crypto.HashValue, long, java.util.Date, long) */ public void transferFinished(File file,IUser usr,String nameOfTransferred,HashValue hashOfTransferred,long bytesServedToUser,Date startTime,long timeNeeded,InetAddress targetIP) { UploadInfo present = null; synchronized(requestedFiles) { present = requestedFiles.get(usr); if (present != null) { present.uploaded(bytesServedToUser); } } if (present != null) { notifyObservers(new StatusObject(present,ChangeType.CHANGED)); } logger.debug(timeNeeded+" time needed" ); TransferRecord up = null ; boolean isNew = true; UserInfo record = recordsPerUser.get(usr); if (record != null) { for (TransferRecord ur:record.records) { if (ur.matches(usr, hashOfTransferred)) { up = ur; isNew = false; break; } } } if (up == null) { up = new TransferRecord(file,nameOfTransferred,hashOfTransferred,bytesServedToUser,startTime,timeNeeded,targetIP,usr); if (record == null) { record = new UserInfo(); recordsPerUser.put(usr, record); } record.records.add(up); } TransferRecord removed = null; synchronized (transferRecords) { if (isNew) { transferRecords.add(up); } else { up.update(nameOfTransferred, bytesServedToUser, startTime, timeNeeded); } totalDuration += timeNeeded; totalSize += bytesServedToUser; record.totalsize+=bytesServedToUser; record.totalTime+= timeNeeded; if (transferRecords.size() >= MAX_TRANSFERRECORD_SIZE) { //Delete what is too much. removed = transferRecords.remove(0); List<TransferRecord> removerecords = recordsPerUser.get(removed.user).records; removerecords.remove(removed); /* if (removerecords.isEmpty()) { recordsPerUser.remove(removed.user); } */ } } if (removed != null) { notifyObservers(new StatusObject(removed,ChangeType.REMOVED)); } notifyObservers(new StatusObject(up,isNew?ChangeType.ADDED:ChangeType.CHANGED)); } /** * cleans once a day old entries.. just for long running clients to prevent * a memory leak.. */ private void clean() { Date oneDayOld = new Date(System.currentTimeMillis()-24*3600 *1000); synchronized(requestedFiles) { logger.debug("in clean"); for (UploadInfo ui: new ArrayList<UploadInfo>(requestedFiles.values())) { if (ui.getLastRequest().before(oneDayOld)) { logger.debug("removing: "+ui.user); requestedFiles.remove(ui.user); } } } } /* (non-Javadoc) * @see uc.files.IUploadQueue#getTransferRecords() */ public List<TransferRecord> getTransferRecords() { synchronized(transferRecords) { return new ArrayList<TransferRecord>(transferRecords); } } /* (non-Javadoc) * @see uc.files.IUploadQueue#getUploadRecordsSize() */ public int getUploadRecordsSize() { synchronized(transferRecords) { return transferRecords.size(); } } /* (non-Javadoc) * @see uc.files.IUploadQueue#getTotalDuration() */ public long getTotalDuration() { return totalDuration; } /* (non-Javadoc) * @see uc.files.IUploadQueue#getTotalSize() */ public long getTotalSize() { return totalSize; } /* (non-Javadoc) * @see uc.files.IUploadQueue#getUploadInfos() */ public List<UploadInfo> getUploadInfos() { synchronized(requestedFiles) { return new ArrayList<UploadInfo>(requestedFiles.values()); } } /* (non-Javadoc) * @see uc.files.IUploadQueue#getTotalTransferredOf(uc.IUser) */ public long getTotalTransferredOf(IUser usr) { synchronized (recordsPerUser) { UserInfo ui = recordsPerUser.get(usr); if (ui != null) { return ui.totalsize; } else { return 0; } } } /* (non-Javadoc) * @see uc.files.IUploadQueue#getTimeNeededOf(uc.IUser) */ public long getTimeNeededOf(IUser usr) { synchronized (recordsPerUser) { UserInfo ui = recordsPerUser.get(usr); if (ui != null) { return ui.totalTime; } else { return 0; } } } public static class UploadInfo implements IHasUser { private final IUser user; /** * the last file he requested */ private volatile String requested; /** * root-hash of the file or interleaves * requested, null if FileList */ private HashValue hash; /** * time of the first request by that user */ private final long firstrequest; /** * time this was requested last.. */ private long lastRequest; /** * how often he requested a file since then */ private int numberOfRequestsSinceThen; /** * how many was uploaded total on bytes to the user */ private long uploadedTotal; /** * if the last request got a slot */ private boolean slot; public UploadInfo(IUser usr,String requested,HashValue hash, boolean slot) { this.user = usr; this.requested = requested; this.hash = hash; this.firstrequest = System.currentTimeMillis(); lastRequest = firstrequest; numberOfRequestsSinceThen = 1; this.slot = slot; } public synchronized void requested(String requested, HashValue hash, boolean slot) { this.requested = requested; this.hash = hash; lastRequest = System.currentTimeMillis(); numberOfRequestsSinceThen++; this.slot = slot; } public synchronized void uploaded(long bytes) { this.uploadedTotal += bytes; } /** * @return the user.. */ public IUser getUser() { return user; } public synchronized String getRequested() { return requested; } public synchronized HashValue getHash() { return hash; } public Date getFirstrequest() { return new Date(firstrequest); } public synchronized Date getLastRequest() { return new Date(lastRequest); } public synchronized int getNumberOfRequestsSinceThen() { return numberOfRequestsSinceThen; } public synchronized long getUploadedTotal() { return uploadedTotal; } public synchronized boolean isSlot() { return slot; } @Override public int hashCode() { final int prime = 31; int result = 1; result = prime * result + ((user == null) ? 0 : user.hashCode()); return result; } @Override public boolean equals(Object obj) { if (this == obj) return true; if (obj == null) return false; if (getClass() != obj.getClass()) return false; UploadInfo other = (UploadInfo) obj; if (user == null) { if (other.user != null) return false; } else if (!user.equals(other.user)) return false; return true; } } public static class TransferRecord implements IHasUser { /** * what was uploaded. */ private volatile String name; /** * null if FileList.. else * the RootHash of the file or TTHL */ private final HashValue root; private volatile long size; private final Date starttime; private volatile Date endTime; private volatile long timeNeeded; private final InetAddress targetIP; public InetAddress getTargetIP() { return targetIP; } /** * to whom we uploaded */ private final IUser user; /** * file transferred.. may be null */ private final File file; public TransferRecord(File file,String name,HashValue what, long size,Date starttime,long timeNeeded,InetAddress targetIP, IUser target) { this.file = file; this.name = name; this.root = what; this.size = size; this.timeNeeded=timeNeeded; this.targetIP = targetIP; this.user = target; this.starttime = new Date(starttime.getTime()); this.endTime = new Date(starttime.getTime()+timeNeeded); } public void update(String name,long size,Date starttime,long timeNeeded) { this.name = name; this.size += size; this.endTime = new Date(starttime.getTime()+timeNeeded); this.timeNeeded+= timeNeeded; } public boolean matches(IUser usr,HashValue what) { if (usr.equals(user)) { if (what == null || root == null) { return what == root; } else { return what.equals(root); } } return false; } public String getName() { return name; } public long getSize() { return size; } public IUser getUser() { return user; } /** * start time on first segment of the file * @return */ public Date getStarttime() { return new Date(starttime.getTime()); } /** * end time of last segment of the file * @return */ public Date getEndTime() { return new Date(endTime.getTime()); } public long getTimeNeeded() { return timeNeeded; } public File getFile() { return file; } } private static class UserInfo { private volatile long totalsize = 0; //totalsize of all records of this user.. private volatile long totalTime =0; //how long it took for all the records.. private final List<TransferRecord> records = new CopyOnWriteArrayList<TransferRecord>(); } }