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); } } }