
import java.net.*;
import java.io.*;
import java.util.*;
import java.applet.*;

//--------------------------------------------
/**
 *  This class represents a download of a web file.
 *  This class will store data about the process of attempting
 *  to download the resource from the internet. The class
 *  will also be responsable for actually initiating the download
 *  process by opening a connection to the resource etc.
 *  
 *  This was adapted from the SoundDownload class
 *  @author matth3wbishop<at>yahoo<dot>com
 */ 

public class WebDownload extends Object
{
  //--------------------------------------------
  /** the resource to get */
  private String url;
  //--------------------------------------------
  /** The time at which the attempted download was begun */
  private Date startTime;
  //--------------------------------------------
  /** The time at which the attempted download was terminated
   *  whether successfully or unsuccessfully */
  private Date finishTime;
  //--------------------------------------------
  /** A place to store the downloaded resource,  */
  private String fileName;
  //--------------------------------------------
  /** will store error messages produced when attempting to download
   *  the file, such as those produced by the Url class in
   *  attempting to make a connection to the resource. */ 
  private StringBuffer errorMessage;

  //--------------------------------------------
  /** The protocol of the url  */
  private String protocol;
  //--------------------------------------------
  private int httpResponseCode;
  //--------------------------------------------
  private String httpResponseMessage;
  //--------------------------------------------
  private String httpContentType;
  //--------------------------------------------
  private int httpContentLength;
  //--------------------------------------------
  private String httpContentEncoding;

  //--------------------------------------------
  /** The number of bytes already read from the resource */
  private long bytesRead;
  //--------------------------------------------
  /** a chunk for reading from the target, in bytes */
  private static int CHUNK_SIZE = 10000;

  //--------------------------------------------
  /** the reason the download failed  */
  private String failureCause;
  //--------------------------------------------
  private boolean wasSuccessful;
  //--------------------------------------------
  /** whether the download has started */
  private boolean hasStarted;
  //--------------------------------------------
  private boolean hasFinished;
  //--------------------------------------------
  private boolean showProgress;
  //--------------------------------------------
  public static String NEWLINE = System.getProperty("line.separator");

  //--------------------------------------------
  //-- CONSTRUCTORS

  public WebDownload()
  {
    this.protocol = "";
    this.httpContentType = "";
    this.httpContentLength = 0;
    this.httpContentEncoding = "";
    this.httpResponseCode = 0;
    this.httpResponseMessage = "";
    this.bytesRead = 0;

    this.failureCause = "";
    this.errorMessage = new StringBuffer("");    
    this.hasStarted = false;
    this.wasSuccessful = false;
    this.hasFinished = false;
    this.showProgress = false;
    this.url = new String("");
    this.startTime = new Date(0);
    this.finishTime = new Date(0);
  } //-- constr: ()

  //--------------------------------------------
  public WebDownload(String sUrl)
  {
    this();
    this.url = sUrl;

  } //-- constr: (string)


  //-- METHODS

  //--------------------------------------------
  /* advises whether the attempted download was carried out successfully */
  public boolean wasSuccessful() 
    { return this.wasSuccessful; }

  //--------------------------------------------
  public void setShowProgress(boolean bShow) 
    { this.showProgress = bShow; }

  //--------------------------------------------
  public boolean showProgress() 
    { return this.showProgress; }

  //--------------------------------------------
  /* informs of the average download speed */
  public int getAverageSpeed() 
  {
    int iAverageSpeed = 0;
    if (!this.wasSuccessful)
      { return 0; }
 
    //-- 
    iAverageSpeed = (int)(this.bytesRead/this.getDownloadDuration());
    return iAverageSpeed;
  }

  //--------------------------------------------
  /* return the target to download */
  public String getUrl()
  {
    return this.url;
  }

  //--------------------------------------------
  /** attempt the download and fail with dignity and grace */
  public void download()
  {
    //-- this variable name should be changed to uWebResource or something   
     URL uSoundUrl;
    URLConnection urlc;
    HttpURLConnection httpc;

    this.hasStarted = true;
    this.startTime = new Date();

    try
    {
      uSoundUrl = new URL(this.url); 	  
      this.protocol = uSoundUrl.getProtocol();
    }
    catch (MalformedURLException e)
    {
      this.failureCause = "Illegal Url";
      this.errorMessage.append(e);
      this.wasSuccessful = false;
      this.finishTime = new Date();
      this.hasFinished = true; 
      return;
    }  //-- try
      
    try
    {
      urlc = uSoundUrl.openConnection();
    }     
    catch (IOException e)
    {
      this.failureCause = "Couldnt create a connection";
      //this.errorMessage.append(e.getMessage());
      this.errorMessage.append(e);
      this.wasSuccessful = false;
      this.finishTime = new Date();
      this.hasFinished = true; 
      return;
    }  //-- try


    try
    {
      urlc.connect();
    }     
    catch (UnknownHostException e)
    {
      this.failureCause = "The host '" + uSoundUrl.getHost() + "' could not be found";
      //this.errorMessage.append(e.getMessage());
      this.errorMessage.append(e);
      this.wasSuccessful = false;
      this.finishTime = new Date();
      this.hasFinished = true; 
      return;
    } 
    catch (IOException e)
    {
      this.failureCause = "Couldnt establish a connection to URL";
      //this.errorMessage.append(e.getMessage());
      this.errorMessage.append(e);
      this.wasSuccessful = false;
      this.finishTime = new Date();
      this.hasFinished = true; 
      return;
    } //-- try


    /* cast the connection object so as to find out the precise cause
       of any download problems */
    if (uSoundUrl.getProtocol().equalsIgnoreCase("http"))
    {
      httpc = (HttpURLConnection)urlc;
      try
      {
        this.httpResponseCode = httpc.getResponseCode();
        this.httpResponseMessage = httpc.getResponseMessage();
      }
      catch (IOException e)
      {     
      }

      if (this.httpResponseCode == 404)
      {
        this.finishTime = new Date();
        this.wasSuccessful = false;
        this.hasFinished = true;
        this.failureCause = "file not found on server";
        this.errorMessage.append(
          "Message from host '" + uSoundUrl.getHost() + "'" + NEWLINE +
          this.httpResponseCode + " " + this.httpResponseMessage);
        return;
      }


      if ((this.httpResponseCode < 200) || (this.httpResponseCode > 299))
      {
        this.finishTime = new Date();
        this.wasSuccessful = false;
        this.hasFinished = true;
        this.failureCause = "server refused request";
        this.errorMessage.append(NEWLINE +
          "Server refused request : " + NEWLINE +
          "Server response code   : " + this.httpResponseCode + NEWLINE +
          "Server response message: " + this.httpResponseMessage + NEWLINE);
        return;
      }
     
      this.httpContentType = httpc.getContentType();
      this.httpContentLength = httpc.getContentLength();
      this.httpContentEncoding = httpc.getContentEncoding();


      //-- the static method below may be useful to do further checks 
      //-- on the content type of the resource.
      //URLConnection.getContentTypeFromStream(inputstream);
    } //-- if is http


    try
    {

      File fSaveFile;
      String sFileName;

      StringBuffer sbUrlPath = new StringBuffer(uSoundUrl.getPath());
      if (sbUrlPath.toString().trim().endsWith("/"))
      {
        sbUrlPath.append("dir-index.html");
      }

      if (sbUrlPath.toString().trim().equalsIgnoreCase(""))
      {
        sbUrlPath.append("dir-index.html");
      }

      //-- get rid of the url path to just leave the file name
      sFileName = (new File(sbUrlPath.toString())).getName();
      StringBuffer sbFileName = new StringBuffer(sFileName);
      
      //-- get a file name which doesnt exist already
      int ii = 0;
      while ((new File(sbFileName.toString())).exists())
      {
        ii++;
        sbFileName.setLength(0);
        sbFileName.append(sFileName);
        sbFileName.append("." + Integer.toString(ii));
      }

      this.fileName = sbFileName.toString();

      fSaveFile = new File(sbFileName.toString());
      if (fSaveFile.isDirectory())
      {
      }

      InputStream isResource;
      FileOutputStream fout;
      isResource = uSoundUrl.openConnection().getInputStream();    
      fout = new FileOutputStream(fSaveFile);
      int iByte;
      int iCounter = 0;
      iByte = isResource.read();
      while (iByte != -1)
      {
        while ((iByte != -1) && (iCounter < this.CHUNK_SIZE))
        {
        
          fout.write(iByte);
          //this.bytesRead++;
          iCounter++;
          iByte = isResource.read();
        }
        if (iByte != -1)
          { this.bytesRead += this.CHUNK_SIZE; }
        iCounter = 0;
        if (this.showProgress)
         { System.out.print("."); }
      }

      isResource.close();
      fout.close();


      /*
      some example code

      File inputFile = new File("farrago.txt");
      File outputFile = new File("outagain.txt");
      FileInputStream in = new FileInputStream(inputFile);
      FileOutputStream out = new FileOutputStream(outputFile);
      int c;

       while ((c = in.read()) != -1)
          out.write(c);
       in.close();
       out.close();

      */ 
      //BufferedReader br = ddf
      //  new BufferedReader(new InputStreamReader(ccccc
	//  uSoundUrl.openConnection().getInputStream()));
      this.finishTime = new Date();
      this.wasSuccessful = true;
      this.hasFinished = true;
    }
    catch (Exception e)
    {
      this.errorMessage.append(e);
      this.wasSuccessful = false;
      this.finishTime = new Date();
      this.hasFinished = true; 
    }
    finally
    {
    }

  } //-- method: download()

  //--------------------------------------------
  /** prints information such as the start time, the finish
   *  time, the duration and the success or failure of the 
   *  download. */
  public String printStatistics()
  { 
    StringBuffer sbOutput = new StringBuffer("");
    sbOutput.append("Attempted to get resource >");
    sbOutput.append(this.url);
    sbOutput.append(NEWLINE);
    sbOutput.append("And saving to file name   >");
    sbOutput.append(this.fileName);
    sbOutput.append(NEWLINE);
    sbOutput.append("Start Time   >");
    sbOutput.append(this.startTime);
    sbOutput.append(NEWLINE);
    sbOutput.append("Finish Time  >");
    sbOutput.append(this.finishTime);
    sbOutput.append(NEWLINE);
    sbOutput.append("Resource downloaded successfully >");
    sbOutput.append(this.wasSuccessful);
    sbOutput.append(NEWLINE);
    sbOutput.append("Download duration (mls)          >");
    sbOutput.append(this.getDownloadDuration());
    sbOutput.append(NEWLINE);
    sbOutput.append("Total bytes read                 >");
    sbOutput.append(this.bytesRead);
    sbOutput.append(NEWLINE);
    sbOutput.append("Average download speed (kbytes/s >");
    sbOutput.append(this.getAverageSpeed());
    sbOutput.append(NEWLINE);


    if (!this.failureCause.equals(""))
    {
      sbOutput.append("Cause of download failure        >");
      sbOutput.append(this.failureCause);
      sbOutput.append(NEWLINE);
    }

    sbOutput.append("Protocol used         >");
    sbOutput.append(this.protocol);
    sbOutput.append(NEWLINE); 
    sbOutput.append("Http Content Type     >");
    sbOutput.append(this.httpContentType);
    sbOutput.append(NEWLINE);
    sbOutput.append("Http Content Length   >");
    sbOutput.append(this.httpContentLength);
    sbOutput.append(NEWLINE);
    sbOutput.append("Http Content Encoding >");
    sbOutput.append(this.httpContentEncoding);
    sbOutput.append(NEWLINE);

    sbOutput.append("Error Messages generated during the download >");
    sbOutput.append(NEWLINE);
    sbOutput.append(this.errorMessage);
    sbOutput.append(NEWLINE);

    sbOutput.append("");
    sbOutput.append("");
    sbOutput.append("");
    return sbOutput.toString();    
  }
  
  //--------------------------------------------
  public String getFileName()
  {
    return this.fileName;
  }


  //--------------------------------------------
  /* return the number of milliseconds taken to download the file. But why
     milliseconds? why not something more pleasant such as seconds? good 
     question. just because I dont want to divide by 1000. thats why */

  public long getDownloadDuration()
  {
    if (this.finishTime.getTime() == 0) { return 0; }
    if (this.startTime.getTime() == 0) { return 0; }
    return this.finishTime.getTime() - this.startTime.getTime();
  } 


  //--------------------------------------------
  /* how is this different from toString */
  public String print()
  { 
    return "";
  }

  //--------------------------------------------
  public String toString()
  {
    StringBuffer sbOutput = new StringBuffer();
    return this.printStatistics();
  }  

  //--------------------------------------------
  /** to allow for testing and one off downloads */
  public static void main(String[] args) throws Exception
  {
    String sUsageMessage = "usage: java WebDownload [url]";

    if (args.length == 0)
    {	    
      System.out.println(sUsageMessage);
      System.exit(-1);
    }

    StringBuffer sOutput = new StringBuffer("");

    String sTest = new String(args[0]);

    WebDownload wdTest = new WebDownload(sTest);
    wdTest.setShowProgress(true);
    System.out.println("Attempting to get " + wdTest.getUrl());
    wdTest.download();

    System.out.println(wdTest.printStatistics());

  } //-- main()
} //-- WebDownload class
