using System;
using System.Net;
using System.Net.Mime;
using System.IO;
using System.Threading;
using NaGet.Tasks;
namespace NaGet.Net
{
///
/// ダウンロードイベントオブジェクト
///
public class DownloadEventArgs : TaskEventArgs
{
///
/// イベントの種類
///
public DownloadEventType DownloadTaskType;
///
/// ダウンロード済みのバイト数
///
public long DownloadSize;
///
/// ダウンロードする総バイト数
///
public long MaxSize;
///
/// コンストラクタ
///
/// イベントの種類
/// イベントメッセージ
/// ダウンロード済みのバイト数
/// ダウンロードする総バイト数
public DownloadEventArgs(DownloadEventType type, string msg, long pos, long max)
{
DownloadTaskType = type;
DownloadSize = pos;
MaxSize = max;
TaskMessage = msg;
ProgressPercent = (max > 0)? (100.0f * pos / max) : -1;
}
}
///
/// DownloadEventArgsでのイベントの種類
///
public enum DownloadEventType {
INITED,
CONNECTED,
STARTED,
DOWNLOADING,
ERROR,
COMPLETED
}
///
/// ダウンロード処理を行うクラス
///
public class Downloader : Task
{
public IWebProxy proxy;
///
/// イベントハンドラ
///
public event EventHandler DownloadEventRaised;
///
/// アクセスURL
///
protected Uri url;
///
/// 保存先
///
protected string filepath;
///
/// リクエストオブジェクト
///
protected WebRequest request;
///
/// レスポンスオブジェクト。応答がくるまではnullである。
///
protected WebResponse response;
///
/// ダウンロード要求時のキャッシュレベル。デフォルトではキャッシュ無視
///
public System.Net.Cache.RequestCacheLevel CacheLevel = System.Net.Cache.RequestCacheLevel.NoCacheNoStore;
private bool cancelCalled = false;
private bool done = false;
///
/// ダウンロード時にHTTPヘッダなどから取得した本来のファイル名
///
private string downloadedFileName = null;
///
/// ウェブアクセスに使うプロキシ
///
public IWebProxy Proxy {
get { return proxy ?? NaGet.Env.WebProxy; }
set { proxy = value; }
}
///
/// ダウンロード時にHTTPヘッダなどから取得した本来のファイル名
///
public string DownloadedFileName {
get { return downloadedFileName; }
}
/* ダウンロードの処理時間計測器 */
private System.Diagnostics.Stopwatch stopwatch;
///
/// ダウンロードを行う
///
/// ダウンロードするリソースのURL
/// 保存先ファイルパス
public void Download(string url, string filepath)
{
this.url = new Uri(url);
this.filepath = filepath;
try {
Run();
} catch (NotSupportedException e) {
throw new NotSupportedException("Not supported download location for downloader.", e);
}
}
///
/// 処理が終了したか否かのフラグ
///
public override bool Done {
get { return done; }
}
///
/// 現在ダウンロード処理実行中であるかのフラグ
///
public override bool Running {
get { return request != null && (!done); }
}
///
/// ダウンロード処理を実行する
///
public override void Run()
{
RaiseDownloadEvent(DownloadEventType.INITED, 0, -1);
try {
request = WebRequest.Create(url);
request.Proxy = this.Proxy;
request.CachePolicy = new System.Net.Cache.RequestCachePolicy(CacheLevel);
HttpWebRequest httpRequest = request as HttpWebRequest;
if (httpRequest != null) {
httpRequest.AutomaticDecompression = DecompressionMethods.Deflate | DecompressionMethods.GZip;
httpRequest.UserAgent = "AppliStation/1.3";
}
if (cancelCalled) {
throw new TaskCanceledException(string.Empty);
}
try {
response = request.GetResponse();
} catch (WebException e) {
if (cancelCalled) { // キャンセル時
throw new TaskCanceledException(string.Empty, e);
} else {
throw new WebException(e.Message, e);
}
}
if (cancelCalled) {
throw new TaskCanceledException(string.Empty);
}
try {
downloadedFileName = getFileNameFromWebResponse(response);
} catch (Exception) {
}
if (File.Exists(filepath)) { // ファイルが存在するとき削除
File.Delete(filepath);
}
RaiseDownloadEvent(DownloadEventType.CONNECTED, 0, -1);
using (Stream stream = response.GetResponseStream() )
using (FileStream fs = new FileStream(filepath,
FileMode.Create,
FileAccess.Write) ) {
try {
File.SetAttributes(filepath, FileAttributes.Hidden);
long contentLength = response.ContentLength;
RaiseDownloadEvent(DownloadEventType.STARTED, 0, contentLength);
stopwatch = new System.Diagnostics.Stopwatch();
stopwatch.Start();
Timer timer = new Timer(new TimerCallback(
delegate(object obj) {
try {
RaiseDownloadEvent(DownloadEventType.DOWNLOADING, fs.Position, contentLength);
} catch (ObjectDisposedException) {
}
}), null, 0, 1000);
try {
byte[] data = new byte[4096];
int size = 0;
while ((size = stream.Read(data,0,data.Length)) > 0) {
fs.Write(data, 0, size);
if (cancelCalled) {
throw new TaskCanceledException(string.Empty);
}
}
} finally {
timer.Dispose();
}
File.SetAttributes(filepath, FileAttributes.Normal);
RaiseDownloadEvent(DownloadEventType.COMPLETED, fs.Position, contentLength);
} catch (IOException ex) {
if (cancelCalled) {
throw new TaskCanceledException(string.Empty);
} else {
RaiseDownloadEvent(DownloadEventType.ERROR, 0, 0);
throw new IOException(ex.Message, ex);
}
} finally {
if (stopwatch != null) {
stopwatch.Stop();
stopwatch = null;
}
}
}
// 更新日を補完
if (File.Exists(filepath)) {
HttpWebResponse httpResponse = response as HttpWebResponse;
FtpWebResponse ftpResponse = response as FtpWebResponse;
if (httpResponse != null) {
File.SetLastWriteTime(filepath, httpResponse.LastModified);
} else if (ftpResponse != null) {
File.SetLastWriteTime(filepath, ftpResponse.LastModified);
}
}
} finally {
if (response != null) {
response.Close();
}
request = null;
done = true;
}
}
protected void RaiseDownloadEvent(DownloadEventType type, long pos, long max)
{
if (DownloadEventRaised != null) {
DownloadEventArgs e = new DownloadEventArgs(type, string.Empty, pos, max);
switch (e.DownloadTaskType) {
case DownloadEventType.CONNECTED:
e.TaskMessage = "接続しました";
break;
case DownloadEventType.STARTED:
case DownloadEventType.DOWNLOADING:
case DownloadEventType.COMPLETED:
if (e.ProgressPercent >= 0) {
e.TaskMessage = string.Format("{0} bytes ({1} %)", e.DownloadSize, (int) e.ProgressPercent);
} else {
e.TaskMessage = string.Format("{0} bytes", e.DownloadSize);
}
if (stopwatch != null && stopwatch.IsRunning && stopwatch.ElapsedMilliseconds > 3000) {
long bpers = e.DownloadSize * 1000 / stopwatch.ElapsedMilliseconds;
if ((e.ProgressPercent >= 0) && (bpers > 0)) {
TimeSpan rest = TimeSpan.FromSeconds((max - e.DownloadSize) / bpers);
e.TaskMessage += string.Format(" 推定残り時間:{0} ({1}/s)", rest, NaGet.Utils.FormatSize(bpers));
} else {
e.TaskMessage += string.Format(" ({0}/s)", NaGet.Utils.FormatSize(bpers));
}
}
break;
}
DownloadEventRaised(this, e);
}
}
///
/// キャンセル可能かを返す
///
public override bool Cancelable {
get { return !(this.Done || cancelCalled); }
}
///
/// ダウンロード処理をキャンセルする
///
/// キャンセルに成功したときtrue
public override bool Cancel()
{
if (this.Done || cancelCalled) {
return false;
}
cancelCalled = true;
if (request != null) {
try {
request.Abort();
} catch (WebException) {
}
}
return true;
}
///
/// Webレスポンスからダウンロードしたファイルの名前を取得
///
/// Content-Dispositionヘッダから取得あるいはURLの末尾から推定します
/// レスポンスオブジェクト
/// 取得したファイル名
private static string getFileNameFromWebResponse(WebResponse response)
{
HttpWebResponse httpresp = response as HttpWebResponse;
if (httpresp != null) {
string contentDisposition = httpresp.Headers["Content-Disposition"];
if (! string.IsNullOrEmpty(contentDisposition)) {
try {
ContentDisposition parser = new ContentDisposition(contentDisposition);
if (! string.IsNullOrEmpty(parser.FileName)) {
return parser.FileName;
}
} catch (FormatException) {
}
}
}
return NaGet.Utils.Url2filename(response.ResponseUri);
}
}
}