extensions/net.sf.basedb.torrent/trunk/src/main/net/sf/basedb/clients/torrent/service/TorrentManager.java

Code
Comments
Other
Rev Date Author Line
1255 22 Oct 10 nicklas 1 /**
1255 22 Oct 10 nicklas 2   $Id $
1255 22 Oct 10 nicklas 3
1255 22 Oct 10 nicklas 4   Copyright (C) 2010 Nicklas Nordborg
1255 22 Oct 10 nicklas 5
1255 22 Oct 10 nicklas 6   This file is part of Bittorent download service for BASE.
1255 22 Oct 10 nicklas 7   Available at http://baseplugins.thep.lu.se/
1255 22 Oct 10 nicklas 8
1255 22 Oct 10 nicklas 9   BASE is free software; you can redistribute it and/or
1255 22 Oct 10 nicklas 10   modify it under the terms of the GNU General Public License
1255 22 Oct 10 nicklas 11   as published by the Free Software Foundation; either version 2
1255 22 Oct 10 nicklas 12   of the License, or (at your option) any later version.
1255 22 Oct 10 nicklas 13
1255 22 Oct 10 nicklas 14   BASE is distributed in the hope that it will be useful,
1255 22 Oct 10 nicklas 15   but WITHOUT ANY WARRANTY; without even the implied warranty of
1255 22 Oct 10 nicklas 16   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
1255 22 Oct 10 nicklas 17   GNU General Public License for more details.
1255 22 Oct 10 nicklas 18
1255 22 Oct 10 nicklas 19   You should have received a copy of the GNU General Public License
1255 22 Oct 10 nicklas 20   along with this program; if not, write to the Free Software
1255 22 Oct 10 nicklas 21   Foundation, Inc., 59 Temple Place - Suite 330,
1255 22 Oct 10 nicklas 22   Boston, MA  02111-1307, USA.
1255 22 Oct 10 nicklas 23 */
1255 22 Oct 10 nicklas 24 package net.sf.basedb.clients.torrent.service;
1255 22 Oct 10 nicklas 25
1255 22 Oct 10 nicklas 26
1255 22 Oct 10 nicklas 27 import hpbtc.protocol.torrent.Torrent;
1255 22 Oct 10 nicklas 28
1255 22 Oct 10 nicklas 29 import java.io.File;
1255 22 Oct 10 nicklas 30 import java.io.IOException;
1255 22 Oct 10 nicklas 31 import java.io.InputStream;
1255 22 Oct 10 nicklas 32 import java.io.OutputStream;
1304 03 Mar 11 nicklas 33 import java.io.PrintWriter;
1304 03 Mar 11 nicklas 34 import java.io.Writer;
1255 22 Oct 10 nicklas 35 import java.util.Arrays;
1304 03 Mar 11 nicklas 36 import java.util.Collection;
1304 03 Mar 11 nicklas 37 import java.util.Collections;
1299 25 Feb 11 nicklas 38 import java.util.List;
1304 03 Mar 11 nicklas 39 import java.util.Properties;
1255 22 Oct 10 nicklas 40
1255 22 Oct 10 nicklas 41 import org.slf4j.Logger;
1255 22 Oct 10 nicklas 42 import org.slf4j.LoggerFactory;
1255 22 Oct 10 nicklas 43
1255 22 Oct 10 nicklas 44 import net.sf.basedb.core.DbControl;
1299 25 Feb 11 nicklas 45 import net.sf.basedb.core.Directory;
1255 22 Oct 10 nicklas 46 import net.sf.basedb.core.Job;
1304 03 Mar 11 nicklas 47 import net.sf.basedb.core.signal.Signal;
1304 03 Mar 11 nicklas 48 import net.sf.basedb.core.signal.SignalException;
1304 03 Mar 11 nicklas 49 import net.sf.basedb.core.signal.SignalHandler;
1299 25 Feb 11 nicklas 50 import net.sf.basedb.util.ChainedProgressReporter;
1255 22 Oct 10 nicklas 51 import net.sf.basedb.util.FileUtil;
1299 25 Feb 11 nicklas 52 import net.sf.basedb.util.Values;
1255 22 Oct 10 nicklas 53
1255 22 Oct 10 nicklas 54 /**
1255 22 Oct 10 nicklas 55   Manager class for handling the download and upload of files in a single torrent.
1255 22 Oct 10 nicklas 56   The manager starts out in {@link TorrentState#INITIAL} state. When it has been
1255 22 Oct 10 nicklas 57   completely configured it is submitted to the service by .... and
1255 22 Oct 10 nicklas 58   {@link TorrentState#DOWNLOADING} starts. Once the download is complete
1255 22 Oct 10 nicklas 59   the service detects this and starts with {@link TorrentState#UPLOADING}
1255 22 Oct 10 nicklas 60   the files to the BASE file system.
1255 22 Oct 10 nicklas 61   
1255 22 Oct 10 nicklas 62   @author Nicklas
1255 22 Oct 10 nicklas 63   @since 1.0
1255 22 Oct 10 nicklas 64 */
1255 22 Oct 10 nicklas 65 public class TorrentManager 
1304 03 Mar 11 nicklas 66   implements SignalHandler
1255 22 Oct 10 nicklas 67 {
1255 22 Oct 10 nicklas 68   
1255 22 Oct 10 nicklas 69   private static final Logger log = LoggerFactory.getLogger(TorrentManager.class);
1255 22 Oct 10 nicklas 70   
1304 03 Mar 11 nicklas 71   private final Collection<Signal> supportedSignals;
1255 22 Oct 10 nicklas 72   private final TorrentService service;
1255 22 Oct 10 nicklas 73   private final File workDir;
1255 22 Oct 10 nicklas 74   private final String name;
1255 22 Oct 10 nicklas 75   
1304 03 Mar 11 nicklas 76   private volatile TorrentState state;
1255 22 Oct 10 nicklas 77   private int jobId;
1255 22 Oct 10 nicklas 78   private Torrent torrent;
1304 03 Mar 11 nicklas 79   private volatile Thread workerThread;
1304 03 Mar 11 nicklas 80   private volatile Signal receivedSignal;
1255 22 Oct 10 nicklas 81   
1255 22 Oct 10 nicklas 82   /**
1255 22 Oct 10 nicklas 83     Create a new torrent manager.
1255 22 Oct 10 nicklas 84     @see TorrentService#newTorrentManager(String)
1255 22 Oct 10 nicklas 85   */
1255 22 Oct 10 nicklas 86   TorrentManager(TorrentService service, File workDir, String name)
1255 22 Oct 10 nicklas 87   {
1255 22 Oct 10 nicklas 88     this.service = service;
1255 22 Oct 10 nicklas 89     this.workDir = workDir;
1255 22 Oct 10 nicklas 90     this.name = name;
1255 22 Oct 10 nicklas 91     this.state = TorrentState.INITIAL;
1304 03 Mar 11 nicklas 92     this.supportedSignals = Collections.unmodifiableList(Arrays.asList(Signal.ABORT, Signal.SHUTDOWN));
1255 22 Oct 10 nicklas 93   }
1255 22 Oct 10 nicklas 94
1255 22 Oct 10 nicklas 95   /**
1304 03 Mar 11 nicklas 96     Create a new torrent manager for an already existing job. The manager will be
1304 03 Mar 11 nicklas 97     set in READY_TO_DOWNLOAD state and the download should automatically be
1304 03 Mar 11 nicklas 98     resumed where it was aborted. If the download is already completed this will be
1304 03 Mar 11 nicklas 99     detected and the files will be uploaded to BASE.
1304 03 Mar 11 nicklas 100   */
1304 03 Mar 11 nicklas 101   TorrentManager(TorrentService service, File workDir, String name, int jobId)
1304 03 Mar 11 nicklas 102   {
1304 03 Mar 11 nicklas 103     this(service, workDir, name);
1304 03 Mar 11 nicklas 104     this.jobId = jobId;
1304 03 Mar 11 nicklas 105     this.state = TorrentState.READY_TO_DOWNLOAD;
1304 03 Mar 11 nicklas 106   }
1304 03 Mar 11 nicklas 107   
1304 03 Mar 11 nicklas 108   /*
1304 03 Mar 11 nicklas 109     From the SignalHandler interface
1304 03 Mar 11 nicklas 110     --------------------------------
1304 03 Mar 11 nicklas 111   */
1304 03 Mar 11 nicklas 112   @Override
1304 03 Mar 11 nicklas 113   public Collection<Signal> getSupportedSignals() 
1304 03 Mar 11 nicklas 114   {
1304 03 Mar 11 nicklas 115     return supportedSignals;
1304 03 Mar 11 nicklas 116   }
1304 03 Mar 11 nicklas 117
1304 03 Mar 11 nicklas 118   @Override
1304 03 Mar 11 nicklas 119   public void handleSignal(Signal signal) 
1304 03 Mar 11 nicklas 120   {
1304 03 Mar 11 nicklas 121     Thread worker = workerThread;
1304 03 Mar 11 nicklas 122     if (signal == Signal.ABORT)
1304 03 Mar 11 nicklas 123     {
1304 03 Mar 11 nicklas 124       if (worker != null && worker.isAlive()) 
1304 03 Mar 11 nicklas 125       {
1304 03 Mar 11 nicklas 126         // Interrupt the worker thread and assume that it takes care
1304 03 Mar 11 nicklas 127         // of exception handling
1304 03 Mar 11 nicklas 128         receivedSignal = signal;
1304 03 Mar 11 nicklas 129         worker.interrupt();
1304 03 Mar 11 nicklas 130       }
1304 03 Mar 11 nicklas 131       else
1304 03 Mar 11 nicklas 132       {
1304 03 Mar 11 nicklas 133         // Close, report an error to BASE and cleanup.
1304 03 Mar 11 nicklas 134         close(new SignalException("Aborted by user."));
1304 03 Mar 11 nicklas 135       }
1304 03 Mar 11 nicklas 136     }
1304 03 Mar 11 nicklas 137     else if (signal == Signal.SHUTDOWN)
1304 03 Mar 11 nicklas 138     {
1304 03 Mar 11 nicklas 139       // We need to notify the worker thread
1304 03 Mar 11 nicklas 140       if (worker != null && worker.isAlive())
1304 03 Mar 11 nicklas 141       {
1304 03 Mar 11 nicklas 142         receivedSignal = signal;
1304 03 Mar 11 nicklas 143         worker.interrupt();
1304 03 Mar 11 nicklas 144       }
1304 03 Mar 11 nicklas 145     }
1304 03 Mar 11 nicklas 146   }
1304 03 Mar 11 nicklas 147
1304 03 Mar 11 nicklas 148   @Override
1304 03 Mar 11 nicklas 149   public boolean supports(Signal signal) 
1304 03 Mar 11 nicklas 150   {
1304 03 Mar 11 nicklas 151     return supportedSignals.contains(signal);
1304 03 Mar 11 nicklas 152   }
1304 03 Mar 11 nicklas 153   // ------------------------------------
1304 03 Mar 11 nicklas 154   /**
1255 22 Oct 10 nicklas 155     Get the current state of the torrent.
1255 22 Oct 10 nicklas 156   */
1255 22 Oct 10 nicklas 157   public TorrentState getState()
1255 22 Oct 10 nicklas 158   {
1255 22 Oct 10 nicklas 159     if (state == TorrentState.DOWNLOADING && isDownloadComplete())
1255 22 Oct 10 nicklas 160     {
1255 22 Oct 10 nicklas 161       state = TorrentState.DOWNLOAD_COMPLETE;
1255 22 Oct 10 nicklas 162     }
1255 22 Oct 10 nicklas 163     return state;
1255 22 Oct 10 nicklas 164   }
1255 22 Oct 10 nicklas 165   
1255 22 Oct 10 nicklas 166   /**
1255 22 Oct 10 nicklas 167     Get the name of the torrent.
1255 22 Oct 10 nicklas 168   */
1255 22 Oct 10 nicklas 169   public String getName()
1255 22 Oct 10 nicklas 170   {
1255 22 Oct 10 nicklas 171     return name;
1255 22 Oct 10 nicklas 172   }
1255 22 Oct 10 nicklas 173   
1255 22 Oct 10 nicklas 174   /**
1255 22 Oct 10 nicklas 175     Get the working directory for this torrent.
1255 22 Oct 10 nicklas 176   */
1255 22 Oct 10 nicklas 177   public File getWorkDir()
1255 22 Oct 10 nicklas 178   {
1255 22 Oct 10 nicklas 179     return workDir;
1255 22 Oct 10 nicklas 180   }
1255 22 Oct 10 nicklas 181   
1255 22 Oct 10 nicklas 182   /**
1255 22 Oct 10 nicklas 183     Get the file object pointing to the torrent file.
1255 22 Oct 10 nicklas 184     The torrent file is stored as 'torrent' in the working
1255 22 Oct 10 nicklas 185     directory. The object may point to a non-existing file
1255 22 Oct 10 nicklas 186     if {@link #setTorrent(InputStream)} has not been called 
1255 22 Oct 10 nicklas 187     yet.
1255 22 Oct 10 nicklas 188   */
1255 22 Oct 10 nicklas 189   public File getTorrentFile()
1255 22 Oct 10 nicklas 190   {
1255 22 Oct 10 nicklas 191     return new File(getWorkDir(), "torrent");
1255 22 Oct 10 nicklas 192   }
1255 22 Oct 10 nicklas 193
1255 22 Oct 10 nicklas 194   /**
1255 22 Oct 10 nicklas 195     Get the directory where downloaded files are stored.
1255 22 Oct 10 nicklas 196     This is a subdirectory to the working directory (/files).
1255 22 Oct 10 nicklas 197     The object mey point to a non-existing location
1255 22 Oct 10 nicklas 198     if the download hasn't started yet.
1255 22 Oct 10 nicklas 199   */
1255 22 Oct 10 nicklas 200   public File getDownloadDir()
1255 22 Oct 10 nicklas 201   {
1255 22 Oct 10 nicklas 202     return new File(workDir, "files");
1255 22 Oct 10 nicklas 203   }
1255 22 Oct 10 nicklas 204
1255 22 Oct 10 nicklas 205   /**
1255 22 Oct 10 nicklas 206     Get the id of the BASE job that is associated with this torrent.
1255 22 Oct 10 nicklas 207   */
1255 22 Oct 10 nicklas 208   public int getJobId()
1255 22 Oct 10 nicklas 209   {
1255 22 Oct 10 nicklas 210     return jobId;
1255 22 Oct 10 nicklas 211   }
1255 22 Oct 10 nicklas 212   
1255 22 Oct 10 nicklas 213   /**
1255 22 Oct 10 nicklas 214     Get the job that is associated with this torrent.
1255 22 Oct 10 nicklas 215     @return The job or null if no job has been associated with yet
1255 22 Oct 10 nicklas 216   */
1255 22 Oct 10 nicklas 217   public Job getJob(DbControl dc)
1255 22 Oct 10 nicklas 218   {
1255 22 Oct 10 nicklas 219     return jobId == 0 ? null : Job.getById(dc, jobId);
1255 22 Oct 10 nicklas 220   }
1255 22 Oct 10 nicklas 221   
1255 22 Oct 10 nicklas 222   /**
1255 22 Oct 10 nicklas 223     Set the job that should be associated with this torrent.
1255 22 Oct 10 nicklas 224     This method can only be called when the state is 
1255 22 Oct 10 nicklas 225     {@link TorrentState#INITIAL}, and it must be called before the
1255 22 Oct 10 nicklas 226     download is started.
1255 22 Oct 10 nicklas 227     @param job A BASE job that has been saved to the database
1255 22 Oct 10 nicklas 228     @see #queueDownload()
1255 22 Oct 10 nicklas 229   */
1255 22 Oct 10 nicklas 230   public void setJob(Job job)
1255 22 Oct 10 nicklas 231   {
1255 22 Oct 10 nicklas 232     if (getState() != TorrentState.INITIAL)
1255 22 Oct 10 nicklas 233     {
1255 22 Oct 10 nicklas 234       throw new IllegalStateException("Job can only be set when when state = INITIAL");
1255 22 Oct 10 nicklas 235     }
1255 22 Oct 10 nicklas 236     if (job == null) throw new NullPointerException("job");
1255 22 Oct 10 nicklas 237     if (!job.isInDatabase()) throw new IllegalArgumentException("Please save job before calling this method");
1255 22 Oct 10 nicklas 238     this.jobId = job.getId();
1255 22 Oct 10 nicklas 239     log.debug("Setting job on torrent manager '" + getName() + "': " + job);
1255 22 Oct 10 nicklas 240   }
1255 22 Oct 10 nicklas 241   
1255 22 Oct 10 nicklas 242   /**
1255 22 Oct 10 nicklas 243     Set the torrent file that has information for this download. The stream
1255 22 Oct 10 nicklas 244     will be copied to working directory. This method can only be called when 
1255 22 Oct 10 nicklas 245     the state is {@link TorrentState#INITIAL}, and it must be called before the
1255 22 Oct 10 nicklas 246     download is started.
1255 22 Oct 10 nicklas 247     
1255 22 Oct 10 nicklas 248     @param torrent A stream with torrent data
1255 22 Oct 10 nicklas 249     @throws IOException
1255 22 Oct 10 nicklas 250     @see #queueDownload()
1255 22 Oct 10 nicklas 251   */
1255 22 Oct 10 nicklas 252   public void setTorrent(InputStream torrent)
1255 22 Oct 10 nicklas 253     throws IOException
1255 22 Oct 10 nicklas 254   {
1255 22 Oct 10 nicklas 255     if (getState() != TorrentState.INITIAL)
1255 22 Oct 10 nicklas 256     {
1255 22 Oct 10 nicklas 257       throw new IllegalStateException("Torrent can only be set when when state = INITIAL");
1255 22 Oct 10 nicklas 258     }
1255 22 Oct 10 nicklas 259     
1255 22 Oct 10 nicklas 260     if (torrent == null) throw new NullPointerException("torrent");
1255 22 Oct 10 nicklas 261     
1255 22 Oct 10 nicklas 262     // Store the stream to 'subDir/torrent'
1255 22 Oct 10 nicklas 263     File torrentFile = getTorrentFile();
1255 22 Oct 10 nicklas 264     log.debug("Copying torrent file for manager '" + getName() + "': " + torrentFile);
1255 22 Oct 10 nicklas 265     OutputStream copyTo = FileUtil.getOutputStream(torrentFile);
1255 22 Oct 10 nicklas 266     try
1255 22 Oct 10 nicklas 267     {
1255 22 Oct 10 nicklas 268       FileUtil.copy(torrent, copyTo);
1255 22 Oct 10 nicklas 269     }
1255 22 Oct 10 nicklas 270     catch (IOException ex)
1255 22 Oct 10 nicklas 271     {
1255 22 Oct 10 nicklas 272       log.error(ex.getMessage(), ex);
1255 22 Oct 10 nicklas 273       throw ex;
1255 22 Oct 10 nicklas 274     }
1255 22 Oct 10 nicklas 275     finally
1255 22 Oct 10 nicklas 276     {
1255 22 Oct 10 nicklas 277       FileUtil.close(copyTo);
1255 22 Oct 10 nicklas 278     }
1255 22 Oct 10 nicklas 279   }
1255 22 Oct 10 nicklas 280   
1255 22 Oct 10 nicklas 281   /**
1255 22 Oct 10 nicklas 282     Add this torrent to the download queue. Before this method is called a
1255 22 Oct 10 nicklas 283     valid job and torrent must have been specified. See {@link #setJob(Job)}
1255 22 Oct 10 nicklas 284     and {@link #setTorrent(InputStream)}. This method will change the
1255 22 Oct 10 nicklas 285     state of the torrent from {@link TorrentState#INITIAL} to 
1255 22 Oct 10 nicklas 286     {@link TorrentState#READY_TO_DOWNLOAD}.
1255 22 Oct 10 nicklas 287     @throws IllegalStateException If the torrent is not in INITIAL state or if
1255 22 Oct 10 nicklas 288       not all required configuration parameters has been set
1255 22 Oct 10 nicklas 289   */
1255 22 Oct 10 nicklas 290   public void queueDownload()
1304 03 Mar 11 nicklas 291     throws IOException
1255 22 Oct 10 nicklas 292   {
1255 22 Oct 10 nicklas 293     if (getState() != TorrentState.INITIAL)
1255 22 Oct 10 nicklas 294     {
1255 22 Oct 10 nicklas 295       throw new IllegalStateException("Download can only be started when state = INITIAL");
1255 22 Oct 10 nicklas 296     }
1255 22 Oct 10 nicklas 297     
1255 22 Oct 10 nicklas 298     // Verify that a job and torrent has been set
1255 22 Oct 10 nicklas 299     if (jobId == 0)
1255 22 Oct 10 nicklas 300     {
1255 22 Oct 10 nicklas 301       throw new IllegalStateException("Please call setJob() before starting the download");
1255 22 Oct 10 nicklas 302     }
1255 22 Oct 10 nicklas 303     
1255 22 Oct 10 nicklas 304     File torrentFile = getTorrentFile();
1255 22 Oct 10 nicklas 305     if (!torrentFile.exists() || !torrentFile.isFile())
1255 22 Oct 10 nicklas 306     {
1255 22 Oct 10 nicklas 307       throw new IllegalStateException("Please call setTorrent() before starting the download");
1255 22 Oct 10 nicklas 308     }
1255 22 Oct 10 nicklas 309   
1304 03 Mar 11 nicklas 310     writeProperties();
1304 03 Mar 11 nicklas 311     
1255 22 Oct 10 nicklas 312     log.debug("Torrent manager '" + getName() + "' is READY_TO_DOWNLOAD");
1255 22 Oct 10 nicklas 313     this.state = TorrentState.READY_TO_DOWNLOAD;
1255 22 Oct 10 nicklas 314   }
1255 22 Oct 10 nicklas 315
1304 03 Mar 11 nicklas 316   private void writeProperties()
1304 03 Mar 11 nicklas 317     throws IOException
1304 03 Mar 11 nicklas 318   {
1304 03 Mar 11 nicklas 319     // Create job.properties file to store info about the related job
1304 03 Mar 11 nicklas 320     Writer writer = new PrintWriter(new File(getWorkDir(), "job.properties"), "UTF-8");
1304 03 Mar 11 nicklas 321     try
1304 03 Mar 11 nicklas 322     {
1304 03 Mar 11 nicklas 323       Properties p = new Properties();
1304 03 Mar 11 nicklas 324       p.setProperty("job.id", Integer.toString(jobId));
1304 03 Mar 11 nicklas 325       p.setProperty("original-filename", getName());
1304 03 Mar 11 nicklas 326       p.store(writer, null);
1304 03 Mar 11 nicklas 327     }
1304 03 Mar 11 nicklas 328     finally
1304 03 Mar 11 nicklas 329     {
1304 03 Mar 11 nicklas 330       if (writer != null) writer.close();
1304 03 Mar 11 nicklas 331     }
1304 03 Mar 11 nicklas 332   }
1304 03 Mar 11 nicklas 333
1304 03 Mar 11 nicklas 334   
1255 22 Oct 10 nicklas 335   /**
1255 22 Oct 10 nicklas 336     Called by the TorrentService when the download has been started.
1255 22 Oct 10 nicklas 337     Changes the state of this manager to DOWNLOADING.
1255 22 Oct 10 nicklas 338   */
1255 22 Oct 10 nicklas 339   void setTorrent(Torrent torrent)
1255 22 Oct 10 nicklas 340   {
1255 22 Oct 10 nicklas 341     this.torrent = torrent;
1255 22 Oct 10 nicklas 342     this.state = TorrentState.DOWNLOADING;
1255 22 Oct 10 nicklas 343   }
1255 22 Oct 10 nicklas 344   
1255 22 Oct 10 nicklas 345   Torrent getTorrent()
1255 22 Oct 10 nicklas 346   {
1255 22 Oct 10 nicklas 347     return torrent;
1255 22 Oct 10 nicklas 348   }
1255 22 Oct 10 nicklas 349   
1255 22 Oct 10 nicklas 350   /**
1255 22 Oct 10 nicklas 351     Check if all files in the torrent have been completely downloaded.
1255 22 Oct 10 nicklas 352   */
1255 22 Oct 10 nicklas 353   public boolean isDownloadComplete()
1255 22 Oct 10 nicklas 354   {
1255 22 Oct 10 nicklas 355     return torrent != null && torrent.getRemainingBytes() == 0;
1255 22 Oct 10 nicklas 356   }
1255 22 Oct 10 nicklas 357
1255 22 Oct 10 nicklas 358   
1255 22 Oct 10 nicklas 359   /**
1299 25 Feb 11 nicklas 360     Report download progress to the BASE job.
1299 25 Feb 11 nicklas 361     The download is considered to use between 0-90% of
1299 25 Feb 11 nicklas 362     the total job time. If the torrent isn't currently
1299 25 Feb 11 nicklas 363     downloading, nothing is done.
1299 25 Feb 11 nicklas 364   */
1304 03 Mar 11 nicklas 365   public synchronized void reportDownloadProgress(DbControl dc)
1299 25 Feb 11 nicklas 366   {
1299 25 Feb 11 nicklas 367     TorrentState state = getState();
1299 25 Feb 11 nicklas 368     if (state == TorrentState.DOWNLOADING || state == TorrentState.DOWNLOAD_COMPLETE)
1299 25 Feb 11 nicklas 369     {
1299 25 Feb 11 nicklas 370       Torrent t = getTorrent();
1299 25 Feb 11 nicklas 371       // 0-90% for downloading
1299 25 Feb 11 nicklas 372       long size = t.getFileLength();
1299 25 Feb 11 nicklas 373       long remain = t.getRemainingBytes();
1299 25 Feb 11 nicklas 374       if (log.isDebugEnabled())
1299 25 Feb 11 nicklas 375       {
1299 25 Feb 11 nicklas 376         log.debug("torrent=" + getName() + "; state=" + state + "; size=" + size + "; remain="+remain + "; tracker: " + t.getTracker().getInterval() + ";" + t.getTracker().getMinInterval() + ";" + t.getTracker().getRemainingMillisUntilUpdate());
1299 25 Feb 11 nicklas 377       }
1299 25 Feb 11 nicklas 378       int completed = (int)(90 * (size - remain) / size);
1299 25 Feb 11 nicklas 379       Job job = getJob(dc);
1299 25 Feb 11 nicklas 380       if (remain == 0)
1299 25 Feb 11 nicklas 381       {
1299 25 Feb 11 nicklas 382         job.setProgress(90, "Download complete");
1299 25 Feb 11 nicklas 383       }
1299 25 Feb 11 nicklas 384       else
1299 25 Feb 11 nicklas 385       {
1299 25 Feb 11 nicklas 386         job.setProgress(completed, "Downloaded " + 
1299 25 Feb 11 nicklas 387             Values.formatBytes(size - remain) + " of " + Values.formatBytes(size));
1299 25 Feb 11 nicklas 388       }
1299 25 Feb 11 nicklas 389     }
1299 25 Feb 11 nicklas 390   }
1299 25 Feb 11 nicklas 391   
1299 25 Feb 11 nicklas 392   /**
1304 03 Mar 11 nicklas 393     Copy the downloaded files to BASE. This method is synchronized but may
1304 03 Mar 11 nicklas 394     be interrupted by calling {@link Thread#interrupt()} on the worker
1304 03 Mar 11 nicklas 395     thread. If the torrent hasn't been completely download nothing
1304 03 Mar 11 nicklas 396     is done.
1299 25 Feb 11 nicklas 397   */
1304 03 Mar 11 nicklas 398   public synchronized void copyToBase(DbControl dc)
1299 25 Feb 11 nicklas 399     throws IOException
1299 25 Feb 11 nicklas 400   {
1299 25 Feb 11 nicklas 401     TorrentState state = getState();
1299 25 Feb 11 nicklas 402     if (state != TorrentState.DOWNLOAD_COMPLETE) return;
1299 25 Feb 11 nicklas 403     
1304 03 Mar 11 nicklas 404     try
1304 03 Mar 11 nicklas 405     {
1304 03 Mar 11 nicklas 406       this.state = TorrentState.UPLOADING;
1304 03 Mar 11 nicklas 407       this.workerThread = Thread.currentThread();
1304 03 Mar 11 nicklas 408
1304 03 Mar 11 nicklas 409       File downloadDir = getDownloadDir();
1304 03 Mar 11 nicklas 410       Job job = getJob(dc);
1304 03 Mar 11 nicklas 411       Directory uploadDir = (Directory)job.getParameterValue("directory");
1304 03 Mar 11 nicklas 412       ChainedProgressReporter progress = new ChainedProgressReporter(job.getProgressReporter(null));
1304 03 Mar 11 nicklas 413       progress.setRange(90, 100);
1304 03 Mar 11 nicklas 414       List files = FileUtil.uploadFiles(dc, uploadDir, downloadDir, null, true, progress);
1304 03 Mar 11 nicklas 415       dc.refreshItem(job); // Needed since the progress reporter has modified the state in the database
1304 03 Mar 11 nicklas 416       job.doneOk(files.size() + " file(s) uploaded successfully");
1304 03 Mar 11 nicklas 417     }
1304 03 Mar 11 nicklas 418     finally
1304 03 Mar 11 nicklas 419     {
1304 03 Mar 11 nicklas 420       this.workerThread = null;
1304 03 Mar 11 nicklas 421     }
1299 25 Feb 11 nicklas 422   }
1299 25 Feb 11 nicklas 423   
1304 03 Mar 11 nicklas 424   /**
1304 03 Mar 11 nicklas 425     Copy the downloaded files to BASE asynchronoulsy. This method will start a
1304 03 Mar 11 nicklas 426     new thread that calls {@link #copyToBase(DbControl)} and then immediately
1304 03 Mar 11 nicklas 427     return. To abort the upload set the interrupt flag on the thread using
1304 03 Mar 11 nicklas 428     {@link Thread#interrupt()}, or send the {@link Signal#ABORT} to
1304 03 Mar 11 nicklas 429     {@link #handleSignal(Signal)}.
1304 03 Mar 11 nicklas 430   */
1304 03 Mar 11 nicklas 431   public Thread copyToBaseAsync()
1299 25 Feb 11 nicklas 432   {
1299 25 Feb 11 nicklas 433     Thread t = new Thread(new Runnable()
1299 25 Feb 11 nicklas 434     {
1299 25 Feb 11 nicklas 435       @Override
1299 25 Feb 11 nicklas 436       public void run() 
1299 25 Feb 11 nicklas 437       {
1299 25 Feb 11 nicklas 438         DbControl dc = service.newDbControl();
1299 25 Feb 11 nicklas 439         try
1299 25 Feb 11 nicklas 440         {
1299 25 Feb 11 nicklas 441           copyToBase(dc);
1299 25 Feb 11 nicklas 442           dc.commit();
1299 25 Feb 11 nicklas 443           state = TorrentState.UPLOAD_COMPLETE;
1299 25 Feb 11 nicklas 444         }
1299 25 Feb 11 nicklas 445         catch (Exception ex)
1299 25 Feb 11 nicklas 446         {
1304 03 Mar 11 nicklas 447           // Is it an error or ABORT signal?
1304 03 Mar 11 nicklas 448           if (receivedSignal == null || receivedSignal == Signal.ABORT)
1304 03 Mar 11 nicklas 449           {
1304 03 Mar 11 nicklas 450             close(ex);
1304 03 Mar 11 nicklas 451           }
1304 03 Mar 11 nicklas 452           else if (receivedSignal == Signal.SHUTDOWN)
1304 03 Mar 11 nicklas 453           {
1304 03 Mar 11 nicklas 454             // Do nothing
1304 03 Mar 11 nicklas 455           }
1299 25 Feb 11 nicklas 456         }
1299 25 Feb 11 nicklas 457         finally
1299 25 Feb 11 nicklas 458         {
1299 25 Feb 11 nicklas 459           if (dc != null) dc.close();
1299 25 Feb 11 nicklas 460         }
1299 25 Feb 11 nicklas 461
1299 25 Feb 11 nicklas 462       }
1299 25 Feb 11 nicklas 463     });
1299 25 Feb 11 nicklas 464     t.start();
1304 03 Mar 11 nicklas 465     return t;
1299 25 Feb 11 nicklas 466   }
1299 25 Feb 11 nicklas 467   
1299 25 Feb 11 nicklas 468   /**
1255 22 Oct 10 nicklas 469     Close this torrent manager and remove all temporary working files.
1304 03 Mar 11 nicklas 470     This method can be called any time, but it is intended to be used
1255 22 Oct 10 nicklas 471     after a successful download and upload of the torrent files. If
1255 22 Oct 10 nicklas 472     the download is being aborted due to an error, use
1255 22 Oct 10 nicklas 473     {@link #close(Throwable)} instead.
1255 22 Oct 10 nicklas 474   */
1304 03 Mar 11 nicklas 475   public synchronized void close()
1255 22 Oct 10 nicklas 476   {
1255 22 Oct 10 nicklas 477     log.debug("Closing torrent manager '" + getName() + "'");
1255 22 Oct 10 nicklas 478     
1255 22 Oct 10 nicklas 479     // Remove the torrent from the service
1304 03 Mar 11 nicklas 480     service.removeTorrentManager(this);
1255 22 Oct 10 nicklas 481     
1255 22 Oct 10 nicklas 482     // Remote working directory
1255 22 Oct 10 nicklas 483     removeWorkingDirectory();
1255 22 Oct 10 nicklas 484     
1255 22 Oct 10 nicklas 485     this.state = TorrentState.DONE;
1255 22 Oct 10 nicklas 486   }
1255 22 Oct 10 nicklas 487
1255 22 Oct 10 nicklas 488   /**
1255 22 Oct 10 nicklas 489     Close this torrent due to an error. The error will be
1255 22 Oct 10 nicklas 490     reported to the associated BASE job. All temporary
1255 22 Oct 10 nicklas 491     working files will be deleted.
1255 22 Oct 10 nicklas 492   */
1304 03 Mar 11 nicklas 493   public synchronized void close(Throwable t)
1255 22 Oct 10 nicklas 494   {
1255 22 Oct 10 nicklas 495     log.debug("Closing torrent manager due to an error '" + getName() + "'", t);
1255 22 Oct 10 nicklas 496     
1255 22 Oct 10 nicklas 497     // Set error on the BASE job
1255 22 Oct 10 nicklas 498     setErrorOnJob(t);
1255 22 Oct 10 nicklas 499   
1255 22 Oct 10 nicklas 500     // Remove the torrent from the service
1304 03 Mar 11 nicklas 501     service.removeTorrentManager(this);
1255 22 Oct 10 nicklas 502     
1255 22 Oct 10 nicklas 503     // Remote working directory
1255 22 Oct 10 nicklas 504     removeWorkingDirectory();
1255 22 Oct 10 nicklas 505     
1255 22 Oct 10 nicklas 506     this.state = TorrentState.ERROR;
1255 22 Oct 10 nicklas 507   }
1255 22 Oct 10 nicklas 508
1255 22 Oct 10 nicklas 509   
1255 22 Oct 10 nicklas 510   /**
1255 22 Oct 10 nicklas 511     Remove the working directory with temporary files.
1255 22 Oct 10 nicklas 512   */
1255 22 Oct 10 nicklas 513   private void removeWorkingDirectory()
1255 22 Oct 10 nicklas 514   {
1255 22 Oct 10 nicklas 515     File tmp = getWorkDir();
1255 22 Oct 10 nicklas 516     log.debug("Cleaning working directory for torrent manager '" + getName() + "': " + tmp);
1255 22 Oct 10 nicklas 517     int numDeleted = FileUtil.deleteTempDirectory(tmp);
1255 22 Oct 10 nicklas 518     log.debug("Removed " + numDeleted + " files/directories from: " + tmp);
1255 22 Oct 10 nicklas 519   }
1255 22 Oct 10 nicklas 520
1255 22 Oct 10 nicklas 521   /**
1255 22 Oct 10 nicklas 522     Report an error condition to the associated BASE job.
1255 22 Oct 10 nicklas 523   */
1255 22 Oct 10 nicklas 524   private void setErrorOnJob(Throwable t)
1255 22 Oct 10 nicklas 525   {
1255 22 Oct 10 nicklas 526     DbControl dc = service.newDbControl();
1255 22 Oct 10 nicklas 527     try
1255 22 Oct 10 nicklas 528     {
1255 22 Oct 10 nicklas 529       Job job = getJob(dc);
1255 22 Oct 10 nicklas 530       if (job != null) job.doneError(t.getMessage(), Arrays.asList(t));
1255 22 Oct 10 nicklas 531       dc.commit();
1255 22 Oct 10 nicklas 532     }
1255 22 Oct 10 nicklas 533     catch (Exception ex)
1255 22 Oct 10 nicklas 534     {
1255 22 Oct 10 nicklas 535       log.error("Could not set error condition on job", ex);
1255 22 Oct 10 nicklas 536     }
1255 22 Oct 10 nicklas 537     finally
1255 22 Oct 10 nicklas 538     {
1255 22 Oct 10 nicklas 539       if (dc != null) dc.close();
1255 22 Oct 10 nicklas 540     }
1255 22 Oct 10 nicklas 541   }
1255 22 Oct 10 nicklas 542
1255 22 Oct 10 nicklas 543 }