using System;
using System.Collections;
using System.Diagnostics;
using System.IO;
using System.Threading;
namespace SharpDownload{
/// <summary>
/// The File Download class controls and monitors progress of downloading a file.
/// </summary>
public class FileDownload
{
private string localFolder;
private DownloadInfo downloadInfo = new DownloadInfo();
private FileDownloadModeEnum fileMode = FileDownloadModeEnum.BinaryMode;
private ArrayList fileParts;
private bool deletePartsAfterCombine = false;
private FileDownloadCommandEnum downloadCommand = FileDownloadCommandEnum.NoCommand;
private const long MIN_SPLIT_SIZE = 20000;
private const int MAX_FILE_PARTS = 4;
#region ENUM
/// <summary>
/// File download mode type
/// Used to indicate Binary or ASCII mode
/// </summary>
public enum FileDownloadModeEnum
{
/// <summary>
/// Binary mode download
/// </summary>
BinaryMode,
/// <summary>
/// ASCII mode download
/// </summary>
ASCIIMode
}
/// <summary>
/// File download command
/// Use to send commands to an already running downloading
/// </summary>
public enum FileDownloadCommandEnum
{
/// <summary>
/// No command (default)
/// </summary>
NoCommand,
/// <summary>
/// Pause the download
/// </summary>
Pause,
/// <summary>
/// Stop the download
/// </summary>
Stop
}
#endregion
/// <summary>
/// Default constructor
/// </summary>
public FileDownload()
{
}
/// <summary>
/// Creates a number of file parts
/// </summary>
/// <param name="numParts">The number of file parts to create</param>
public void CreateFileParts(int numParts)
{
// Make sure valid number of parts
if (numParts<=0)
{
throw new ApplicationException("Invalid number of file parts. You must have at least one file part.");
}
// Create an array list and fill with FileParts
fileParts = new ArrayList();
for (int i=0; i<numParts; i++)
{
fileParts.Add(new FilePart(i));
}
}
/// <summary>
/// Add a file part
/// </summary>
public void AddFilePart()
{
// Check if arraylist has been set up
if (fileParts==null)
{
// Create a single file part
CreateFileParts(1);
}
else
{
// Add a file part to the arraylist
fileParts.Add(new FilePart(fileParts.Count));
}
}
/// <summary>
/// Default indexer - returns a FilePart
/// </summary>
public FilePart this [int index]
{
get
{
// Check that index is in bounds
if (index<0 || index>=fileParts.Count)
{
throw new IndexOutOfRangeException();
}
return (FilePart)fileParts[index];
}
set
{
// Check that index is in bounds
if (index<0 || index>=fileParts.Count)
{
throw new IndexOutOfRangeException();
}
fileParts[index] = value;
}
}
/// <summary>
/// Source address property. Setting this property automatically
/// decomposes the URL into its component parts
/// </summary>
public string SourceAddress
{
get
{
return downloadInfo.URL;
}
set
{
downloadInfo.URL = value;
}
}
/// <summary>
/// Destination folder to download the file to
/// </summary>
public string LocalFolder
{
get
{
return localFolder;
}
set
{
localFolder = value;
downloadInfo.LocalPath = value;
}
}
/// <summary>
/// Download mode for FTP downloads
/// </summary>
public FileDownloadModeEnum FileDownloadMode
{
get
{
return fileMode;
}
set
{
fileMode = value;
}
}
/// <summary>
/// Number of bytes downloaded. This is the sum of the file parts
/// </summary>
public long BytesDownloaded
{
get
{
if (fileParts.Count==0)
{
return 0;
}
else
{
long amountDownloaded = 0;
// Count the amount downloaded
for (int i=0; i<fileParts.Count; i++)
{
// Get the amount downloaded by the file part
FilePart tempFilePart = (FilePart)fileParts[i];
amountDownloaded += tempFilePart.BytesDownloaded;
}
return amountDownloaded;
}
}
}
/// <summary>
/// Number of file parts in this file download
/// </summary>
public int NumFileParts
{
get
{
return fileParts.Count;
}
}
/// <summary>
/// The download info component
/// </summary>
public DownloadInfo DownloadInfo
{
get
{
return downloadInfo;
}
}
/// <summary>
/// Public property to set/get the download command
/// </summary>
public FileDownloadCommandEnum Command
{
get
{
return downloadCommand;
}
set
{
downloadCommand = value;
if (fileParts==null) return;
foreach (FilePart part in fileParts)
{
if (part != null)
{
part.Command = value;
}
}
}
}
/// <summary>
/// Asynchronous download call
/// </summary>
/// <returns>true if download started successfully; false otherwise</returns>
public Thread AsyncDownload()
{
try
{
// Start a new thread
Thread downloadThread = new Thread(new ThreadStart(StartDownload));
Debug.WriteLine("FileDownload: Download thread started");
return downloadThread;
}
catch (Exception eThread)
{
OnDownloadFailedEvent(new DownloadFailedEventArgs(eThread.Message));
Debug.WriteLine("FileDownload: Download thread FAILED");
return null;
}
}
/// <summary>
/// Start download function
/// </summary>
/// <returns></returns>
public void StartDownload()
{
ProtocolClient protoClient = null;
try
{
// Set up the download
protoClient = ProtocolClient.GetProtocolClient(downloadInfo);
// Check if logged in
if (!protoClient.IsLoggedIn)
{
protoClient.Login();
}
// Get the file size
try
{
downloadInfo.FileSize = protoClient.GetFileSize(downloadInfo.Directory + "/" + downloadInfo.FileName);
}
catch (HttpClient.HttpRedirectException eRedirect)
{
// Close the current client
protoClient.Close();
// Change the download info to reflect the redirect
downloadInfo.URL = eRedirect.URL;
// Let's try the download again
this.StartDownload();
return;
}
}
catch (ProtocolClient.ProtocolException e)
{
// Exception occurred - exit for now
// TODO: Fix exception handling here!
OnDownloadFailedEvent(new DownloadFailedEventArgs(e.Message));
return;
}
// Check if resume is supported
downloadInfo.ResumeSupported = protoClient.CanResume(downloadInfo.Directory + "/" + downloadInfo.FileName);
// Close temporary protocol client
protoClient.Close();
protoClient = null;
if (downloadInfo.ResumeSupported)
{
// Split up the file
long totalFileSize = downloadInfo.FileSize;
long partLeftOver = 0;
long partFileSize = 0;
if (totalFileSize>MIN_SPLIT_SIZE)
{
// Calculate part sizes
partLeftOver = totalFileSize % MAX_FILE_PARTS;
partFileSize = (long)(totalFileSize - partLeftOver) / MAX_FILE_PARTS;
// Create the file parts
this.CreateFileParts(MAX_FILE_PARTS);
for (int i=0; i<MAX_FILE_PARTS; i++)
{
// Set the file details
this[i].SourceFile = downloadInfo.Directory + "/" + downloadInfo.FileName;
this[i].DestinationFile = downloadInfo.LocalPathFile + "." + i.ToString();
// Set the offset
this[i].ByteOffset = (i * partFileSize);
// Check if this part is the last part
if (i!=MAX_FILE_PARTS-1)
{
// Set part size
this[i].PartSize = partFileSize;
}
else
{
// Set part size and "left-over"
this[i].PartSize = partFileSize + partLeftOver;
// Flag as last part
this[i].IsLastPart = true;
}
}
}
else
{
// Don't need to split file up - create one download part
this.CreateFileParts(1);
// Set the file details
this[0].SourceFile = downloadInfo.Directory + "/" + downloadInfo.FileName;
this[0].DestinationFile = downloadInfo.LocalPathFile + ".0";
this[0].PartSize = downloadInfo.FileSize;
this[0].ByteOffset = 0;
}
}
else
{
// Server doesn't support resume - download one part
this.CreateFileParts(1);
// Set the file details
this[0].SourceFile = downloadInfo.Directory + "/" + downloadInfo.FileName;
this[0].DestinationFile = downloadInfo.LocalPathFile + ".0";
this[0].PartSize = downloadInfo.FileSize;
this[0].ByteOffset = 0;
}
// Loop through the file parts
for (int i=0; i<this.NumFileParts; i++)
{
// Link event handlers
this[i].PartDownloadStartedEvent += new FilePart.PartDownloadStartedEventHandler(PartDownloadStarted);
this[i].PartDataReceivedEvent += new FilePart.PartDataReceivedEventHandler(PartDataReceived);
this[i].PartDownloadStoppedEvent += new FilePart.PartDownloadStoppedEventHandler(PartDownloadStopped);
this[i].PartDownloadFinishedEvent += new FilePart.PartDownloadFinishedEventHandler(PartDownloadFinished);
// Start the async download
this[i].AsyncDownload(downloadInfo);
}
// Raise a download started event
OnDownloadStartedEvent(new DownloadStartedEventArgs(downloadInfo.FileSize));
}
#region Event Handlers
// Part download started event handler
private void PartDownloadStarted(FilePart sender, FilePart.PartDownloadStartedEventArgs e)
{
try
{
OnPartDownloadStartedEvent(new PartDownloadStartedEventArgs(e.PartNumber, this[e.PartNumber].PartSize));
}
catch (Exception ePartDownloadStarted)
{
Debug.WriteLine("FileDownload: PartDownloadStarted event handler error[" + ePartDownloadStarted.Message + "]");
}
}
// Data received event handler
private void PartDataReceived(FilePart sender, FilePart.PartDataReceivedEventArgs e)
{
try
{
// Raise a data received event
OnDataReceivedEvent(new DataReceivedEventArgs(sender.PartNumber, sender.BytesDownloaded));
}
catch (Exception ePartDataReceived)
{
Debug.WriteLine("FileDownload: PartDataReceived event handler error [" + ePartDataReceived.Message + "]");
}
}
// Data stopped event handler
private void PartDownloadStopped(FilePart sender, FilePart.PartDownloadStoppedEventArgs e)
{
try
{
// Raise a data stopped event
OnDownloadStoppedEvent(new DownloadStoppedEventArgs(sender.PartNumber));
}
catch (Exception ePartDataStopped)
{
Debug.WriteLine("FileDownload: PartDataStopped event handler error [" + ePartDataStopped.Message + "]");
}
}
// Download finished callback
private void PartDownloadFinished(FilePart sender, FilePart.PartDownloadFinishedEventArgs e)
{
try
{
// Raise a download finished event
OnDownloadFinishedEvent(new DownloadFinishedEventArgs(sender.PartNumber, sender.PartSize));
// Check if all downloads are complete
foreach (FilePart part in this.fileParts)
{
// Check if any parts haven't finished
if (!part.IsDownloadComplete)
{
return;
}
}
// All downloads complete - combine
FileStream fsWrite = new FileStream(this.LocalFolder + downloadInfo.FileName, FileMode.CreateNew, FileAccess.Write);
BinaryWriter w = new BinaryWriter(fsWrite);
// Loop through file parts
foreach (FilePart partToCombine in this.fileParts)
{
// Create the read stream
FileStream fsRead = new FileStream(partToCombine.DestinationFile, FileMode.Open, FileAccess.Read);
BinaryReader r = new BinaryReader(fsRead);
byte[] buffer = new Byte[fsRead.Length];
// TODO: Perform read/write op better
// A large file might produce an enormous buffer, should
// be done in smaller pieces i.e. buffer[1024]?
r.Read(buffer, 0, (int)fsRead.Length);
r.Close();
fsRead.Close();
w.Write(buffer);
}
w.Close();
fsWrite.Close();
// Delete individual file parts
// TODO: Make delete file parts a choice...
if (deletePartsAfterCombine)
{
// Loop through each file part and remove
foreach (FilePart partToDelete in this.fileParts)
{
File.Delete(partToDelete.DestinationFile);
}
}
// Raise a download complete event
OnDownloadCompletedEvent(new DownloadCompletedEventArgs());
}
catch (Exception ePartDownloadFinished)
{
Debug.WriteLine("FileDownload: PartDownloadFinished event handler error [" + ePartDownloadFinished.Message + "]");
}
}
#endregion
/* public static void Join(string dest, int bufsize, out int counter, params string[] src)
{
counter = 0;
int origbufsize = bufsize;
byte[] inbyte = new byte[bufsize];
BufferedStream output = new BufferedStream(File.Create(dest), bufsize);
foreach (string srcfile in src)
{
bufsize = origbufsize;
BufferedStream input = new BufferedStream(File.OpenRead(srcfile), bufsize);
while ( (bufsize = input.Read(inbyte, 0, bufsize)) != 0)
{
counter = (int)(input.Position*100/input.Length);
output.Write(inbyte, 0, bufsize);
}
input.Close();
}
output.Close();
}
*/
#region PartDownloadStarted Event
/// <summary>
/// Part Download Started event arguments
/// </summary>
public class PartDownloadStartedEventArgs : EventArgs
{
private readonly int partNumber;
private readonly long partSize;
/// <summary>
/// PartDownloadStarted event arguments
/// </summary>
/// <param name="PartNumber">The download part number that raised the event</param>
/// <param name="PartSize">The size of the part in Bytes</param>
public PartDownloadStartedEventArgs (int PartNumber, long PartSize)
{
partNumber = PartNumber;
partSize = PartSize;
}
/// <summary>
/// Public read-only property for the part number
/// </summary>
public int PartNumber
{
get
{
return partNumber;
}
}
/// <summary>
/// Public read-only property for the size of the part in Bytes
/// </summary>
public long PartSize
{
get
{
return partSize;
}
}
}
/// <summary>
/// Delegate for Part Download Started Event
/// </summary>
public delegate void PartDownloadStartedEventHandler(PartDownloadStartedEventArgs e);
/// <summary>
/// The Part Download Started Event is raised when the part download starts
/// </summary>
public event PartDownloadStartedEventHandler PartDownloadStartedEvent;
/// <summary>
/// OnPartDownloadStartedEvent
/// </summary>
/// <param name="e">PartDownloadStartedEventArgs</param>
protected virtual void OnPartDownloadStartedEvent(PartDownloadStartedEventArgs e)
{
if (PartDownloadStartedEvent != null)
{
PartDownloadStartedEvent(e);
}
}
#endregion
#region DownloadStarted Event
/// <summary>
/// Download started event arguments
/// </summary>
public class DownloadStartedEventArgs : EventArgs
{
private readonly long fileSize;
/// <summary>
/// DownloadStarted event arguments
/// </summary>
/// <param name="FileSize">The size of the file in Bytes</param>
public DownloadStartedEventArgs (long FileSize)
{
fileSize = FileSize;
}
/// <summary>
/// Public read-only property for the size of the file in Bytes
/// </summary>
public long FileSize
{
get
{
return fileSize;
}
}
}
/// <summary>
/// Delegate for Download Started Event
/// </summary>
public delegate void DownloadStartedEventHandler(DownloadStartedEventArgs e);
/// <summary>
/// The Download Started Event is raised when the download starts
/// </summary>
public event DownloadStartedEventHandler DownloadStartedEvent;
/// <summary>
/// OnDownloadStartedEvent
/// </summary>
/// <param name="e">DownloadStartedEventArgs</param>
protected virtual void OnDownloadStartedEvent(DownloadStartedEventArgs e)
{
if (DownloadStartedEvent != null)
{
DownloadStartedEvent(e);
}
}
#endregion
#region DataReceived Event
/// <summary>
/// Data received event arguments
/// </summary>
public class DataReceivedEventArgs : EventArgs
{
private readonly int partNumber;
private readonly long bytesDownloaded;
/// <summary>
/// DataReceived event arguments
/// </summary>
/// <param name="PartNumber">The download part number that raised the event</param>
/// <param name="BytesDownloaded">The number of bytes downloaded</param>
public DataReceivedEventArgs (int PartNumber, long BytesDownloaded)
{
partNumber = PartNumber;
bytesDownloaded = BytesDownloaded;
}
/// <summary>
/// Public read-only property
/// The download part number that raised the event
/// </summary>
public int PartNumber
{
get
{
return partNumber;
}
}
/// <summary>
/// Public read-only property
/// The number of Bytes downloaded
/// </summary>
public long BytesDownloaded
{
get
{
return bytesDownloaded;
}
}
}
/// <summary>
/// Delegate for Download Started Event
/// </summary>
public delegate void DataReceivedEventHandler(DataReceivedEventArgs e);
/// <summary>
/// The Download Started Event is raised when the download starts
/// </summary>
public event DataReceivedEventHandler DataReceivedEvent;
/// <summary>
/// OnDataReceivedEvent
/// </summary>
/// <param name="e">DataReceivedEventArgs</param>
protected virtual void OnDataReceivedEvent(DataReceivedEventArgs e)
{
if (DataReceivedEvent != null)
{
DataReceivedEvent(e);
}
}
#endregion
#region DownloadStopped Event
/// <summary>
/// Download stopped event arguments
/// </summary>
public class DownloadStoppedEventArgs : EventArgs
{
private readonly int partNumber;
/// <summary>
/// DownloadStopped event arguments
/// </summary>
/// <param name="PartNumber">The part number that raised the event</param>
/// <param name="PartSize">The size of the file part in Bytes</param>
public DownloadStoppedEventArgs (int PartNumber)
{
partNumber = PartNumber;
}
/// <summary>
/// Public read-only property
/// The download part number
/// </summary>
public int PartNumber
{
get
{
return partNumber;
}
}
}
/// <summary>
/// Delegate for Download Stopped Event
/// </summary>
public delegate void DownloadStoppedEventHandler(DownloadStoppedEventArgs e);
/// <summary>
/// The Download Sopped Event is raised when the download starts
/// </summary>
public event DownloadStoppedEventHandler DownloadStoppedEvent;
/// <summary>
/// OnDownloadStoppedEvent
/// </summary>
/// <param name="e">DownloadStoppedEventArgs</param>
protected virtual void OnDownloadStoppedEvent(DownloadStoppedEventArgs e)
{
if (DownloadStoppedEvent != null)
{
DownloadStoppedEvent(e);
}
}
#endregion
#region DownloadFinished Event
/// <summary>
/// Download started event arguments
/// </summary>
public class DownloadFinishedEventArgs : EventArgs
{
private readonly int partNumber;
private readonly long partSize;
/// <summary>
/// DownloadFinished event arguments
/// </summary>
/// <param name="PartNumber">The part number that raised the event</param>
/// <param name="PartSize">The size of the file part in Bytes</param>
public DownloadFinishedEventArgs (int PartNumber, long PartSize)
{
partNumber = PartNumber;
partSize = PartSize;
}
/// <summary>
/// Public read-only property
/// The download part number
/// </summary>
public int PartNumber
{
get
{
return partNumber;
}
}
/// <summary>
/// The size of the download part in Bytes
/// </summary>
public long PartSize
{
get
{
return partSize;
}
}
}
/// <summary>
/// Delegate for Download Started Event
/// </summary>
public delegate void DownloadFinishedEventHandler(DownloadFinishedEventArgs e);
/// <summary>
/// The Download Started Event is raised when the download starts
/// </summary>
public event DownloadFinishedEventHandler DownloadFinishedEvent;
/// <summary>
/// OnDownloadFinishedEvent
/// </summary>
/// <param name="e">DownloadFinishedEventArgs</param>
protected virtual void OnDownloadFinishedEvent(DownloadFinishedEventArgs e)
{
if (DownloadFinishedEvent != null)
{
DownloadFinishedEvent(e);
}
}
#endregion
#region DownloadCompleted Event
/// <summary>
/// Download started event arguments
/// </summary>
public class DownloadCompletedEventArgs : EventArgs
{
/// <summary>
/// Public default constructor
/// </summary>
public DownloadCompletedEventArgs ()
{
}
}
/// <summary>
/// Delegate for Download Started Event
/// </summary>
public delegate void DownloadCompletedEventHandler(DownloadCompletedEventArgs e);
/// <summary>
/// The Download Started Event is raised when the download starts
/// </summary>
public event DownloadCompletedEventHandler DownloadCompletedEvent;
/// <summary>
/// OnDownloadCompletedEvent
/// </summary>
/// <param name="e">DownloadCompletedEventArgs</param>
protected virtual void OnDownloadCompletedEvent(DownloadCompletedEventArgs e)
{
if (DownloadCompletedEvent != null)
{
DownloadCompletedEvent(e);
}
}
#endregion
#region DownloadFailed Event
/// <summary>
/// Download failed event arguments
/// </summary>
public class DownloadFailedEventArgs : EventArgs
{
private string message = string.Empty;
/// <summary>
/// DownloadFailed event arguments
/// </summary>
/// <param name="Message">The reason for the download failure (typically the error message raised)</param>
public DownloadFailedEventArgs (string Message)
{
message = Message;
}
/// <summary>
/// Public read-only error message
/// </summary>
public string Message
{
get
{
return message;
}
}
}
/// <summary>
/// Delegate for Download Started Event
/// </summary>
public delegate void DownloadFailedEventHandler(DownloadFailedEventArgs e);
/// <summary>
/// The Download Started Event is raised when the download starts
/// </summary>
public event DownloadFailedEventHandler DownloadFailedEvent;
/// <summary>
/// OnDownloadFailedEvent
/// </summary>
/// <param name="e">DownloadFailedEventArgs</param>
protected virtual void OnDownloadFailedEvent(DownloadFailedEventArgs e)
{
if (DownloadFailedEvent != null)
{
DownloadFailedEvent(e);
}
}
#endregion
}
}
|