2 using System.Diagnostics;
6 using System.Threading;
9 namespace NaGet.SubCommands.SubTask
14 public class DownloadSubTask : NaGetSubTask
24 protected IWebProxy proxy;
29 protected string filepath;
34 protected WebRequest request;
37 /// レスポンスオブジェクト。応答がくるまではnullである。
39 protected WebResponse response;
42 /// ダウンロード時にHTTPヘッダなどから取得した本来のファイル名
44 private string downloadedFileName = null;
47 /// ダウンロード要求時のキャッシュレベル。デフォルトではキャッシュ無視
49 public System.Net.Cache.RequestCacheLevel CacheLevel = System.Net.Cache.RequestCacheLevel.NoCacheNoStore;
52 /// ダウンロード時に downloadedFileName に改名するか否か。
54 protected bool enableChangeFileName = false;
59 private bool cancelCalled = false;
62 /// ダウンロード中のファイル名につける接尾辞
64 private static readonly string PartialFilePostfix = ".part";
69 /// <param name="url">ダウンロード先URL</param>
70 /// <param name="filepath">保存ファイルパス</param>
71 /// <param name="proxy">プロキシ</param>
72 public DownloadSubTask(Uri url, string filepath, IWebProxy proxy)
75 this.filepath = filepath;
80 this.downloadedFileName = null;
83 public DownloadSubTask(string url, string filepath, IWebProxy proxy)
84 : this(new Uri(url), filepath, proxy)
91 /// <param name="url">ダウンロード先URL</param>
92 /// <param name="filepath">保存ファイルパス</param>
93 public DownloadSubTask(Uri url, string filepath)
94 : this(url, filepath, NaGet.Env.WebProxy)
98 public DownloadSubTask(string url, string filepath)
99 : this(new Uri(url), filepath, NaGet.Env.WebProxy)
104 /// ダウンロード時にHTTPヘッダなどから取得した本来のファイル名
106 public string DownloadedFileName {
107 get { return downloadedFileName; }
111 /// 保存先ファイル名を本来のファイル名に変えるか。
113 public bool EnableChangeFileName {
114 get { return enableChangeFileName; }
115 set { enableChangeFileName = value; }
121 public string Filepath {
122 get { return filepath; }
128 public override bool Cancelable {
129 get { return !this.cancelCalled; }
135 /// <returns>キャンセルに成功したときtrue</returns>
136 public override bool Cancel()
138 if (! this.cancelCalled && ! this.Done) {
139 this.cancelCalled = true;
140 if (request != null) {
143 } catch (WebException) {
152 public override void Run()
155 RaiseTaskSetEvent(TaskEventType.STARTED, string.Format("ダウンロード:{0}", this.url), 0);
159 runHandleCancelTrigger();
161 RaiseTaskSetEvent(TaskEventType.PING, string.Format("接続中...{0}", this.url.Host), -1);
163 runAcquireResponse();
164 runHandleCancelTrigger();
168 runHandleCancelTrigger();
172 RaiseTaskSetEvent(TaskEventType.COMPLETED, "ダウンロード終了", 100);
173 } catch (System.Net.WebException e) {
174 if (System.Net.NetworkInformation.NetworkInterface.GetIsNetworkAvailable()) {
175 RaiseTaskSetEvent(TaskEventType.WARNING, "ネットワークに接続されていません。", -1);
177 RaiseTaskSetEvent(TaskEventType.WARNING, "ネットワークに接続できませんでした。ネットワークが切断されているか、ファイアウォールによって遮断された可能性があります。", -1);
179 throw new System.Net.WebException(e.Message, e);
181 runReleaseResponse();
192 /// キャンセルされたかを確認して、キャンセル要求があるのならば TaskCanceledException を投げる
194 private void runHandleCancelTrigger()
196 if (this.cancelCalled) {
197 throw new TaskCanceledException(string.Empty);
204 private void runBuildRequest()
206 request = WebRequest.Create(url);
207 request.Proxy = this.proxy;
208 request.CachePolicy = new System.Net.Cache.RequestCachePolicy(CacheLevel);
210 HttpWebRequest httpRequest = request as HttpWebRequest;
211 if (httpRequest != null) {
212 httpRequest.AutomaticDecompression = DecompressionMethods.Deflate | DecompressionMethods.GZip;
213 httpRequest.UserAgent = NaGet.Env.UserAgentString;
220 private void runAcquireResponse()
223 response = request.GetResponse();
224 } catch (WebException e) {
225 if (cancelCalled) { // キャンセル時
226 throw new TaskCanceledException(string.Empty, e);
228 throw new WebException(e.Message, e);
236 private void runPrepareFile()
239 downloadedFileName = getFileNameFromWebResponse(response);
240 } catch (Exception) {
243 // パス名を変えるときは、HTTPヘッダから取得したファイル名に変更する。
244 if (enableChangeFileName && (!string.IsNullOrEmpty(downloadedFileName))) {
245 filepath = Path.Combine(Path.GetDirectoryName(filepath), downloadedFileName);
249 if (File.Exists(filepath)) {
250 File.Delete(filepath);
252 // 部分ファイルが存在するときも削除 TODO レジューム処理
253 if (File.Exists(filepath + PartialFilePostfix)) {
254 File.Delete(filepath + PartialFilePostfix);
258 private void runDownloadToFile()
260 Stopwatch stopwatch = new Stopwatch();
261 string partialFilepath = filepath + PartialFilePostfix;
263 using (Stream stream = response.GetResponseStream() )
264 using (FileStream fs = new FileStream(partialFilepath,
266 FileAccess.Write) ) {
269 File.SetAttributes(partialFilepath, FileAttributes.Hidden);
270 long contentLength = response.ContentLength;
273 RaiseDownloadProgressTaskSetEvent(0, contentLength, 0);
275 Timer timer = new Timer(new TimerCallback(
276 delegate(object obj) {
278 RaiseDownloadProgressTaskSetEvent(fs.Position, contentLength, stopwatch.ElapsedMilliseconds);
279 } catch (ObjectDisposedException) {
284 byte[] data = new byte[4096];
286 while ((size = stream.Read(data,0,data.Length)) > 0) {
287 fs.Write(data, 0, size);
290 throw new TaskCanceledException(string.Empty);
296 } catch (IOException ex) {
298 throw new TaskCanceledException(string.Empty);
300 throw new IOException(ex.Message, ex);
303 if (stopwatch != null) {
310 if (File.Exists(partialFilepath)) {
311 File.Move(partialFilepath, filepath);
312 File.SetAttributes(filepath, FileAttributes.Normal);
316 private void runPostprocess()
319 if (File.Exists(filepath)) {
320 HttpWebResponse httpResponse = response as HttpWebResponse;
321 FtpWebResponse ftpResponse = response as FtpWebResponse;
323 if (httpResponse != null) {
324 File.SetLastWriteTime(filepath, httpResponse.LastModified);
325 } else if (ftpResponse != null) {
326 File.SetLastWriteTime(filepath, ftpResponse.LastModified);
334 private void runReleaseResponse()
336 if (response != null) {
342 /// Webレスポンスからダウンロードしたファイルの名前を取得
344 /// <remarks>Content-Dispositionヘッダから取得あるいはURLの末尾から推定します</remarks>
345 /// <param name="response">レスポンスオブジェクト</param>
346 /// <returns>取得したファイル名</returns>
347 private static string getFileNameFromWebResponse(WebResponse response)
349 HttpWebResponse httpresp = response as HttpWebResponse;
350 if (httpresp != null) {
351 string contentDisposition = httpresp.Headers["Content-Disposition"];
353 if (! string.IsNullOrEmpty(contentDisposition)) {
355 ContentDisposition parser = new ContentDisposition(contentDisposition);
356 if (! string.IsNullOrEmpty(parser.FileName)) {
357 return parser.FileName;
359 } catch (FormatException) {
364 return NaGet.Utils.Url2filename(response.ResponseUri);
370 /// <param name="downloadsize">現在ダウンロード済みのサイズ</param>
371 /// <param name="filesize">全体のファイルサイズ。不明なときはゼロを指定。</param>
372 /// <param name="elapsedms">ダウンロード開始からの時間をms単位で。</param>
373 protected virtual void RaiseDownloadProgressTaskSetEvent(long downloadsize, long filesize, long elapsedms)
376 TimeSpan eta = new TimeSpan(0);
381 percent = 100 * ((float) downloadsize) / ((float) filesize);
386 byteps = 1000 * downloadsize / elapsedms;
387 if (filesize > 0 && byteps > 0) {
388 eta = TimeSpan.FromSeconds((filesize - downloadsize) / byteps);
392 System.Text.StringBuilder msgbuilder = new System.Text.StringBuilder();
393 msgbuilder.AppendFormat("{0} bytes", downloadsize);
395 msgbuilder.AppendFormat(" ({0:F2}%)", percent);
398 msgbuilder.AppendFormat(" ETA {0}", eta);
401 msgbuilder.AppendFormat(" ({0}/s)", NaGet.Utils.FormatSize(byteps));
404 RaiseTaskSetEvent(TaskEventType.PING, msgbuilder.ToString(), percent);