MultiDownload.java
| Index Score | ||
|---|---|---|
![]() |
![]() |
xnap.net |
![]() |
![]() |
XNap 2 |
View: Reasons, Metrics, Source Code
These are the metrics that contribute to the Enerjy Score for this file, ranked by impact. So the metrics listed at the top influence the score to a greater extent that the metrics listed at the bottom.
/*
* XNap
*
* A pure java file sharing client.
*
* See AUTHORS for copyright information.
*
* 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 2 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, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*
*/
package xnap.net;
import xnap.XNap;
import xnap.io.*;
import xnap.net.event.*;
import xnap.util.*;
import java.beans.*;
import java.io.*;
import java.net.*;
import java.text.*;
import java.util.*;
/**
* Downloads a file.
* Currently this class is limited to a single running download. This
* will be fixed in the future.
*
* Variables:
*
* <pre>
*
* offset initialsize (= resumeFile.length)
* |------|-------|-----------------| resumeFile.getFinalSize()
* |--| bytesTransferred
* |--------------| totalBytesTransferred
*
* </pre>
*
* The dlQueue and runQueue must not be modified without obtaining lock.
*/
public class MultiDownload extends AbstractTransferContainer
implements IDownloadContainer
{
//--- Constant(s) ---
public static final String XNAP3_RESUME_FILENAME_PREFIX
= ".xnap-openap-resume";
/**
* Retry remotely queued downloads.
*/
public static final int WAKEUP_INTERVAL = 30 * 1000;
//= Preferences.getInstance().getDownloadRetryInterval() * 1000;
/**
* Maximum retry count per download.
*/
public static final int MAX_TRIES
= Preferences.getInstance().getDownloadMaxTries();
//--- Data field(s) ---
private LinkedList dlQueue = new LinkedList();
private LinkedList runQueue = new LinkedList();
private UserContainer users = new UserContainer();
// FIX: this needs to be synchronized
private HashSet queued = new HashSet();
/**
* Currently running download.
*/
private IDownload download;
private ResumeFile3 resumeFile;
private Object lock = new Object();
/**
* Maximum concurrent downloads.
*/
private int maxDownloads;
private long offset;
private FileOutputStream out;
/**
* Milliseconds until next wake up.
*/
private long nextWakeUp = 0;
private File xnap3ResumeFile;
private static Hashtable stateTable = new Hashtable();
static {
State[][] table = new State[][] {
{ State.CONNECTING,
State.ABORTING, State.DOWNLOADING, State.FAILED, },
{ State.LOCALLY_QUEUED,
State.ABORTED, State.CONNECTING, },
{ State.NOT_STARTED,
State.CONNECTING, State.LOCALLY_QUEUED, State.NOT_STARTED, },
};
stateTable = FiniteStateMachine.createStateTable(table);
}
private FiniteStateMachine fsm
= new FiniteStateMachine(State.NOT_STARTED, stateTable);
//--- Constructor(s) ---
public MultiDownload(/*int maxDownloads*/)
{
//this.maxDownloads = maxDownloads;
maxDownloads = 1;
}
//--- Method(s) ---
public void die()
{
wakeup();
}
public boolean add(IDownload d)
{
if (d == null) {
return false;
}
if (d.getUser().getMaxDownloads() == IUser.TRANSFER_NEVER) {
logger.debug("not adding because is blocked: " + d);
return false;
}
synchronized (lock) {
if (!dlQueue.contains(d) && !runQueue.contains(d)
&& d.getTryCount() < MAX_TRIES) {
logger.debug("add " + file.getName());
dlQueue.addLast(d);
users.add(d.getUser());
wakeup();
return true;
}
}
return false;
}
public void clear()
{
synchronized (lock) {
dlQueue.clear();
runQueue.clear();
users.clear();
}
}
public void delete()
{
super.delete();
if (xnap3ResumeFile != null && xnap3ResumeFile.exists()) {
xnap3ResumeFile.delete();
xnap3ResumeFile = null;
}
}
/**
* Download is ready to rumble.
*/
public void start(IDownload d)
{
synchronized (lock) {
dlQueue.remove(d);
runQueue.addLast(d);
}
wakeup();
}
public void setQueuePos(IDownload d, int pos)
{
if (pos <= 0) {
queued.remove(d);
}
else {
logger.debug(this + " queued " + d + " at pos " + pos);
queued.add(d);
wakeup();
}
}
public void setFile(File newValue)
{
if (resumeFile != null) {
resumeFile = new ResumeFile3(newValue, resumeFile.getFinalSize(),
resumeFile.getFilterData());
super.setFile(resumeFile);
}
else {
super.setFile(newValue);
}
}
public String getFilename()
{
return (resumeFile != null) ? resumeFile.getFinalFilename() : "";
}
/**
* Returns the final filesize or -1 if no resume file is set.
*/
public long getFilesize()
{
return (resumeFile != null) ? resumeFile.getFinalSize() : -1;
}
public ResumeFile3 getResumeFile()
{
return resumeFile;
}
public void setResumeFile(ResumeFile3 newValue)
{
setFile(newValue);
resumeFile = newValue;
totalBytesTransferred = resumeFile.length();
xnap3ResumeFile = new File
(Preferences.getInstance().getIncompleteDir()
+ XNAP3_RESUME_FILENAME_PREFIX + "." + newValue.getName() + ".xnap2");
if (!xnap3ResumeFile.exists()) {
try {
OutputStream out
= new BufferedOutputStream(new FileOutputStream(xnap3ResumeFile));
try {
Properties p = new Properties();
p.setProperty("filename", newValue.getFinalFilename());
p.setProperty("filesize", newValue.getFinalSize() + "");
SearchFilterData data = newValue.getFilterData();
if (data != null && data.searchText != null) {
p.setProperty("searchText", data.searchText);
}
// FIX: the search realm is lost
p.setProperty("segment.0.filename", newValue.getAbsolutePath());
p.setProperty("segment.0.start", "0");
p.setProperty("segment.0.mergeFailCount", "0");
p.store(out, "Automatically generated XNap 2.5 OpenNap resume file - do not modify");
}
finally {
try {
out.close();
}
catch (IOException e) {
// this exception gets lost
}
}
}
catch (IOException e) {
logger.warn("Could not write XNap 3 resume file", e);
}
}
}
public long getTotalBytesTransferred()
{
return totalBytesTransferred;
}
public IUser getUser()
{
users.show((download != null) ? download.getUser() : null);
return users;
}
public int getQueueSize()
{
synchronized (lock) {
return dlQueue.size() + runQueue.size();
}
}
public String getStatusText()
{
if (getStatus() == STATUS_WAITING) {
long diff = (nextWakeUp - System.currentTimeMillis()) / 1000;
if (diff > 0) {
return MessageFormat.format(XNap.tr("retrying in {0} s"),
new Object[] { new Long(diff) });
}
}
return super.getStatusText();
}
public boolean isBusy()
{
synchronized (lock) {
return runQueue.size() > 0;
}
}
public boolean isFinished()
{
return totalBytesTransferred == getFilesize();
}
/**
* Adds download objects from <code>d</code> to our <code>dlQueue</code>.
*/
public void merge(MultiDownload d)
{
for (Iterator i = d.dlQueue.iterator(); i.hasNext();) {
add((IDownload)i.next());
}
// delete file created by AutoDownload in constructor
d.delete();
}
public void remove(IDownload d)
{
d.dequeue();
queued.remove(d);
synchronized (lock) {
dlQueue.remove(d);
users.remove(d.getUser());
}
logger.debug("removed download: " + d);
wakeup();
}
/**
* Resets all download counters.
*/
public void reset()
{
synchronized (lock) {
for (Iterator i = dlQueue.iterator(); i.hasNext();) {
IDownload d = (IDownload)i.next();
d.reset();
}
}
}
public void skip()
{
if (download != null) {
download.close();
}
}
public void run()
{
// shorten file by one byte, sometimes the last byte is not written
// to disk correctly if Java crashes. (observed on Debian GNU/Linux,
// Blackdown JDK 1.3.1)
if (resumeFile.length() < resumeFile.getFinalSize()) {
FileHelper.shorten(resumeFile, 1);
totalBytesTransferred = resumeFile.length();
}
try {
// append
out = new FileOutputStream(file.getAbsolutePath(), true);
}
catch (IOException e) {
setStatus(STATUS_FAILED, "Could not write file " + file.getName());
return;
}
download();
try {
out.flush();
out.close();
if (!die) {
if (isFinished()) {
try {
String dir = FileHelper.getDownloadDirFromExtension
(resumeFile.getFinalFilename());
File newFile = FileHelper.moveUnique
(file, dir, resumeFile.getFinalFilename());
setFile(newFile);
if (xnap3ResumeFile != null && xnap3ResumeFile.exists()) {
xnap3ResumeFile.delete();
xnap3ResumeFile = null;
}
setStatus(STATUS_SUCCESS);
}
catch (IOException e) {
logger.debug("could not rename finished file", e);
setStatus(STATUS_FAILED, XNap.tr("Could not create file (check download dir)"));
}
}
else {
setStatus(STATUS_FAILED, XNap.tr("incomplete"));
}
}
}
catch (IOException e) {
setStatus(STATUS_FAILED, XNap.tr("Could not close file"));
}
died();
}
public String toString()
{
return "MultiDownload " + getFilename();
}
/**
* This is called from within run.
*/
private void download()
{
while (!die && !isFinished()) {
long elapsedTime = 0;
synchronized (lock) {
// send enqueue for all dls
for (Iterator i = dlQueue.iterator(); i.hasNext();) {
IDownload d = (IDownload)i.next();
if (canStart(d)) {
d.enqueue(this);
}
else {
long diff
= System.currentTimeMillis() - d.getLastTry();
elapsedTime = Math.max(elapsedTime, diff);
}
setQueuePos(d, d.getQueuePos());
}
}
if (!die && !isFinished()) {
waitForAdd(elapsedTime);
}
boolean quit = false;
while (!die && !isFinished() && !quit) {
synchronized (lock) {
if (runQueue.size() == 0) {
quit = true;
}
else {
download = (IDownload)runQueue.getFirst();
logger.debug("MultiDownload: start " + download);
}
}
if (!quit) {
startDownload();
synchronized (lock) {
runQueue.removeFirst();
}
if (isFinished()) {
// readd
add(download);
}
else {
synchronized (lock) {
download = null;
}
}
// FIX ME
elapsedTime = WAKEUP_INTERVAL;
}
}
} // main while loop
dequeueAll();
logger.debug("finished");
}
/**
* Dequeues all downloads in <code>dlQueue</code>.
*/
private void dequeueAll()
{
synchronized (lock) {
// send enqueue for all dls
for (Iterator i = dlQueue.iterator(); i.hasNext();) {
IDownload d = (IDownload)i.next();
d.dequeue();
}
}
queued.clear();
}
private boolean canStart(IDownload d)
{
return (System.currentTimeMillis() - d.getLastTry()
> WAKEUP_INTERVAL);
}
private void startDownload()
{
logger.debug("starting download:" + download);
totalRate = -1;
offset = Math.max(totalBytesTransferred - 100, 0);
download.getUser().modifyLocalDownloadCount(1);
setStatus(STATUS_CONNECTING);
try {
if (download.connect(offset)) {
boolean retry = writeFile();
download.close();
totalRate = getAverageRate();
stopDownload(!isFinished() && retry);
}
else if (download.getTryCount() >= MAX_TRIES) {
remove(download);
}
else {
// requeue as last
synchronized(lock) {
dlQueue.addLast(download);
}
}
}
catch (IOException e) {
remove(download);
logger.warn("connect failed: " + e.getMessage());
}
download.getUser().modifyLocalDownloadCount(-1);
}
private void stopDownload(boolean requeue)
{
synchronized (lock) {
if (requeue) {
if (!dlQueue.contains(download)
&& download.getTryCount() < MAX_TRIES) {
dlQueue.addLast(download);
}
}
else {
remove(download);
}
}
}
/**
* @return false, if the download failed fataly and should not be retried
*/
public boolean writeFile()
{
setStatus(STATUS_DOWNLOADING);
// we need to catch aborts for slow connects quickly
byte[] data = new byte[512];
startTransfer();
try {
while (offset + bytesTransferred < getFilesize()) {
// wait for data
int byteCount = 0;
long startTime = System.currentTimeMillis();
while (byteCount == 0) {
if (die) {
throw new InterruptedException();
}
else if (System.currentTimeMillis() - startTime
> TRANSFER_TIMEOUT) {
throw (new IOException(XNap.tr("socket timeout")));
}
try {
// compute the number of bytes to read
long toRead = getFilesize() - offset - bytesTransferred;
int len = (int)Math.min(toRead, data.length);
// blocks for at most SOCKET_TIMEOUT
byteCount = download.read(data, 0, len);
}
catch (InterruptedIOException e) {
}
}
if (byteCount == -1) {
break;
}
if (offset + bytesTransferred < totalBytesTransferred) {
long diff = totalBytesTransferred - bytesTransferred;
int count = (int)Math.min(byteCount, diff - offset);
if (! checkFile(data, count)) {
logger.warn("resume failed");
return false;
}
if (count < byteCount) {
// write remainder
int len = (int)Math.min(byteCount - count,
getFilesize()
- totalBytesTransferred);
out.write(data, count, len);
totalBytesTransferred += len;
}
}
else {
int len = (int)Math.min(byteCount,
getFilesize()
- totalBytesTransferred);
out.write(data, 0, len);
totalBytesTransferred += len;
}
//
bytesTransferred += byteCount;
}
}
catch (IOException e) {
logger.warn("download failed " + e.getMessage());
}
catch (InterruptedException e) {
}
return true;
}
/**
* Compares <code>byteCount</code> bytes from incomplete file with newly
* downloaded bytes in <code>data</code>.
*/
private boolean checkFile(byte[] data, int byteCount)
{
RandomAccessFile raf;
try {
raf = new RandomAccessFile(file, "r");
}
catch (IOException e) {
logger.debug("file not found");
return false;
}
try {
byte[] array = new byte[byteCount];
try {
raf.seek(offset + bytesTransferred);
raf.readFully(array);
}
catch (IOException e) {
logger.debug("wrong number of bytes");
return false;
}
for (int i = 0; i < byteCount; i++) {
if (data[i] != array[i]) {
logger.debug("resume failed at "
+ (offset + bytesTransferred + i));
return false;
}
}
return true;
}
finally {
try {
raf.close();
}
catch (IOException e) {
}
}
}
public boolean equals(Object obj)
{
if (obj instanceof MultiDownload) {
MultiDownload d = (MultiDownload)obj;
if (d.getResumeFile() == null || getResumeFile() == null) {
return (this == obj);
}
return ((d.getResumeFile().getFinalSize()
== getResumeFile().getFinalSize())
&& (d.getResumeFile().getFilterData().searchText.equals
(getResumeFile().getFilterData().searchText)));
}
return false;
}
private void setWaitStatus(long elapsedTime)
{
if (runQueue.size() == 0) {
if (dlQueue.size() == 0) {
if (getStatus() != STATUS_SEARCHING) {
setStatus(STATUS_WAITING);
}
}
else {
if (queued.size() > 0) {
StringBuffer sb = new StringBuffer();
sb.append(MessageFormat.format
(XNap.tr("{0} queued"),
new Object[] {
new Integer(queued.size()) }));
sb.append(": ");
for (Iterator i = queued.iterator(); i.hasNext();) {
IDownload d = (IDownload)i.next();
sb.append(d.getUser().getName());
sb.append("@");
sb.append(d.getQueuePos());
if (i.hasNext()) {
sb.append(", ");
}
}
nextWakeUp = 0;
setStatus(STATUS_WAITING, sb.toString());
}
else {
nextWakeUp = (System.currentTimeMillis()
+ (WAKEUP_INTERVAL - elapsedTime));
setStatus(STATUS_WAITING);
}
}
}
}
/**
* Waits for add to runQueue or returns after elapsedTime ms.
*/
protected void waitForAdd(long elapsedTime)
{
setWaitStatus(elapsedTime);
// wait, dls will wake us up if something happened
synchronized (lock) {
if (runQueue.size() == 0) {
if (dlQueue.size() == 0) {
logger.debug("waiting for add" );
nextWakeUp = 0;
waitLock();
}
else {
logger.debug("waiting " + (WAKEUP_INTERVAL - elapsedTime));
waitLock(WAKEUP_INTERVAL - elapsedTime);
}
}
}
}
protected void waitLock(long time)
{
if (time <= 0) {
return;
}
synchronized (lock) {
try {
lock.wait(time);
}
catch (InterruptedException e) {
}
}
}
protected void waitLock()
{
synchronized (lock) {
try {
lock.wait();
}
catch (InterruptedException e) {
}
}
}
protected void wakeup()
{
synchronized (lock) {
lock.notify();
}
}
}
The table below shows all metrics for MultiDownload.java.




