OSDN Git Service

efd7da078462b3702d98e5988a264009e6504e98
[applistation/AppliStation.git] / na-get-lib / NaGet.SubCommands.SubTask / DownloadSubTask.cs
1 using System;
2 using System.Diagnostics;
3 using System.Net;
4 using System.Net.Mime;
5 using System.IO;
6 using System.Threading;
7 using NaGet.Tasks;
8
9 namespace NaGet.SubCommands.SubTask
10 {
11         /// <summary>
12         /// ダウンロードタスク
13         /// </summary>
14         public class DownloadSubTask : NaGetSubTask
15         {
16                 /// <summary>
17                 /// アクセスURL
18                 /// </summary>
19                 protected Uri url;
20                 
21                 /// <summary>
22                 /// プロキシ
23                 /// </summary>
24                 protected IWebProxy proxy;
25                 
26                 /// <summary>
27                 /// 保存先
28                 /// </summary>
29                 protected string filepath;
30                 
31                 /// <summary>
32                 /// リクエストオブジェクト
33                 /// </summary>
34                 protected WebRequest request;
35                 
36                 /// <summary>
37                 /// レスポンスオブジェクト。応答がくるまではnullである。
38                 /// </summary>
39                 protected WebResponse response;
40                 
41                 /// <summary>
42                 /// ダウンロード時にHTTPヘッダなどから取得した本来のファイル名
43                 /// </summary>
44                 private string downloadedFileName = null;
45                 
46                 /// <summary>
47                 /// ダウンロード要求時のキャッシュレベル。デフォルトではキャッシュ無視
48                 /// </summary>
49                 public System.Net.Cache.RequestCacheLevel CacheLevel = System.Net.Cache.RequestCacheLevel.NoCacheNoStore;
50                 
51                 /// <summary>
52                 /// キャンセルが呼ばれたか否か。
53                 /// </summary>
54                 private bool cancelCalled = false;
55                 
56                 /// <summary>
57                 /// コンストラクタ
58                 /// </summary>
59                 /// <param name="url">ダウンロード先URL</param>
60                 /// <param name="filepath">保存ファイルパス</param>
61                 /// <param name="proxy">プロキシ</param>
62                 public DownloadSubTask(Uri url, string filepath, IWebProxy proxy)
63                 {
64                         this.url = url;
65                         this.filepath = filepath;
66                         this.proxy = proxy;
67                         
68                         this.request = null;
69                         this.response = null;
70                         this.downloadedFileName = null;
71                 }
72                 
73                 /// <summary>
74                 /// コンストラクタ
75                 /// </summary>
76                 /// <param name="url">ダウンロード先URL</param>
77                 /// <param name="filepath">保存ファイルパス</param>
78                 public DownloadSubTask(Uri url, string filepath)
79                         : this(url, filepath, NaGet.Env.WebProxy)
80                 {
81                 }
82         
83                 /// <summary>
84                 /// ダウンロード時にHTTPヘッダなどから取得した本来のファイル名
85                 /// </summary>
86                 public string DownloadedFileName {
87                         get { return downloadedFileName; }
88                 }
89                 
90                 public override void Run()
91                 {
92                         NotifyStarted();
93                         RaiseTaskSetEvent(TaskEventType.STARTED, string.Format("ダウンロード:{0}", this.url), 0);
94                         
95                         try {
96                                 runBuildRequest();
97                                 runHandleCancelTrigger();
98                                 
99                                 RaiseTaskSetEvent(TaskEventType.PING, string.Format("接続中...{0}", this.url.Host), -1);
100                                 
101                                 runAcquireResponse();
102                                 runHandleCancelTrigger();
103                                 
104                                 runPrepareFile();
105                                 runDownloadToFile();
106                                 runHandleCancelTrigger();
107                                 
108                                 runPostprocess();
109                         } finally {
110                                 runReleaseResponse();
111                                 
112                                 if (cancelCalled) {
113                                         NotifyCancelled();
114                                 } else {
115                                         NotifyCompleted();
116                                         RaiseTaskSetEvent(TaskEventType.COMPLETED, "ダウンロード終了", 100);
117                                 }
118                         }
119                 }
120                 
121                 /// <summary>
122                 /// キャンセルされたかを確認して、キャンセル要求があるのならば TaskCanceledException を投げる
123                 /// </summary>
124                 private void runHandleCancelTrigger()
125                 {
126                         if (this.cancelCalled) {
127                                 throw new TaskCanceledException(string.Empty);
128                         }
129                 }
130                 
131                 /// <summary>
132                 /// requestの構築。
133                 /// </summary>
134                 private void runBuildRequest()
135                 {
136                         request = WebRequest.Create(url);
137                         request.Proxy = this.proxy;
138                         request.CachePolicy = new System.Net.Cache.RequestCachePolicy(CacheLevel);
139                         
140                         HttpWebRequest httpRequest = request as HttpWebRequest;
141                         if (httpRequest != null) {
142                                 httpRequest.AutomaticDecompression = DecompressionMethods.Deflate | DecompressionMethods.GZip;  
143                                 // TODO User-Agent
144                                 httpRequest.UserAgent = "AppliStation/1.3";
145                         }
146                 }
147                 
148                 /// <summary>
149                 /// Responseを得る
150                 /// </summary>
151                 private void runAcquireResponse()
152                 {
153                         try {
154                                 response = request.GetResponse();
155                         } catch (WebException e) {
156                                 if (cancelCalled) { // キャンセル時
157                                         throw new TaskCanceledException(string.Empty, e);
158                                 } else {
159                                         throw new WebException(e.Message, e);
160                                 }
161                         }
162                 }
163                 
164                 /// <summary>
165                 /// ダウンロード先ファイル名の決定
166                 /// </summary>
167                 private void runPrepareFile()
168                 {
169                         try {
170                                 downloadedFileName = getFileNameFromWebResponse(response);
171                         } catch (Exception) {
172                         }
173                         
174                         if (File.Exists(filepath)) { // ファイルが存在するとき削除
175                                 File.Delete(filepath);
176                         }
177                 }
178                 
179                 private void runDownloadToFile()
180                 {
181                         Stopwatch stopwatch = new Stopwatch();
182                         
183                         using (Stream stream = response.GetResponseStream() )
184                         using (FileStream fs = new FileStream(filepath,
185                                                     FileMode.Create,
186                                                     FileAccess.Write) ) {
187                                 try {
188                                         File.SetAttributes(filepath, FileAttributes.Hidden);
189                                         long contentLength = response.ContentLength;
190         
191                                         stopwatch.Start();
192                                         RaiseDownloadProgressTaskSetEvent(0, contentLength, 0);
193         
194                                         Timer timer = new Timer(new TimerCallback(
195                                                 delegate(object obj) {
196                                                         try {
197                                                                 RaiseDownloadProgressTaskSetEvent(fs.Position, contentLength, stopwatch.ElapsedMilliseconds);
198                                                         } catch (ObjectDisposedException) {
199                                                         }
200                                                 }), null, 0, 1000);
201                                         
202                                         try {
203                                                 byte[] data = new byte[4096];
204                                                 int size = 0;
205                                                 while ((size = stream.Read(data,0,data.Length)) > 0) {
206                                                         fs.Write(data, 0, size);
207                                                         
208                                                         if (cancelCalled) {
209                                                                 throw new TaskCanceledException(string.Empty);
210                                                         }
211                                                 }
212                                         } finally {
213                                                 timer.Dispose();
214                                         }
215                                         
216                                         File.SetAttributes(filepath, FileAttributes.Normal);
217                                 } catch (IOException ex) {
218                                         if (cancelCalled) {
219                                                 throw new TaskCanceledException(string.Empty);
220                                         } else {
221                                                 throw new IOException(ex.Message, ex);
222                                         }
223                                 } finally {
224                                         if (stopwatch != null) {
225                                                 stopwatch.Stop();
226                                                 stopwatch = null;
227                                         }
228                                 }
229                         }
230                 }
231                 
232                 private void runPostprocess()
233                 {                       
234                         // 更新日を補完
235                         if (File.Exists(filepath)) {
236                                 HttpWebResponse httpResponse = response as HttpWebResponse;
237                                 FtpWebResponse  ftpResponse      = response as FtpWebResponse;
238                                 
239                                 if (httpResponse != null) {
240                                         File.SetLastWriteTime(filepath, httpResponse.LastModified);
241                                 } else if (ftpResponse != null) {
242                                         File.SetLastWriteTime(filepath, ftpResponse.LastModified);
243                                 }
244                         }
245                 }
246                 
247                 /// <summary>
248                 /// responseの開放
249                 /// </summary>
250                 private void runReleaseResponse()
251                 {
252                         if (response != null) {
253                                 response.Close();
254                         }
255                 }
256                 
257                 /// <summary>
258                 /// ダウンロード処理をキャンセルする
259                 /// </summary>
260                 /// <returns>キャンセルに成功したときtrue</returns>
261                 public override bool Cancel()
262                 {
263                         if (! this.cancelCalled && ! this.Done) {
264                                 this.cancelCalled = true;
265                                 if (request != null) {
266                                         try {
267                                                 request.Abort();
268                                         } catch (WebException) {
269                                         }
270                                 }
271                                 return true;
272                         } else {
273                                 return false;
274                         }
275                 }
276                 
277                 /// <summary>
278                 /// Webレスポンスからダウンロードしたファイルの名前を取得
279                 /// </summary>
280                 /// <remarks>Content-Dispositionヘッダから取得あるいはURLの末尾から推定します</remarks>
281                 /// <param name="response">レスポンスオブジェクト</param>
282                 /// <returns>取得したファイル名</returns>
283                 private static string getFileNameFromWebResponse(WebResponse response)
284                 {
285                         HttpWebResponse httpresp = response as HttpWebResponse;
286                         if (httpresp != null) {
287                                 string contentDisposition = httpresp.Headers["Content-Disposition"];
288                                 
289                                 if (! string.IsNullOrEmpty(contentDisposition)) {
290                                         try {
291                                                 ContentDisposition parser = new ContentDisposition(contentDisposition);
292                                                 if (! string.IsNullOrEmpty(parser.FileName)) {
293                                                         return parser.FileName;
294                                                 }
295                                         } catch (FormatException) {
296                                         }
297                                 }
298                         }
299                         
300                         return NaGet.Utils.Url2filename(response.ResponseUri);
301                 }
302                 
303                 /// <summary>
304                 /// ダウンロード進捗メッセージを生成
305                 /// </summary>
306                 /// <param name="downloadsize">現在ダウンロード済みのサイズ</param>
307                 /// <param name="filesize">全体のファイルサイズ。不明なときはゼロを指定。</param>
308                 /// <param name="elapsedms">ダウンロード開始からの時間をms単位で。</param>
309                 protected virtual void RaiseDownloadProgressTaskSetEvent(long downloadsize, long filesize, long elapsedms)
310                 {
311                         float percent = -1;
312                         TimeSpan eta = new TimeSpan(0);
313                         long byteps = 0;
314                         
315                         // 進捗率の算出
316                         if (filesize > 0) {
317                                 percent = 100 * ((float) downloadsize) / ((float) filesize);
318                         }
319                         
320                         // スループット・残り時間の算出
321                         if (elapsedms > 0) {
322                                 byteps = 1000 * downloadsize / elapsedms;
323                                 if (filesize > 0) {
324                                         eta = TimeSpan.FromSeconds((filesize - downloadsize) / byteps);
325                                 }
326                         }
327                         
328                         System.Text.StringBuilder msgbuilder = new System.Text.StringBuilder();
329                         msgbuilder.AppendFormat("{0} bytes", downloadsize);
330                         if (percent > 0) {
331                                 msgbuilder.AppendFormat(" ({0}%)", percent);
332                         }
333                         if (eta.Ticks > 0) {
334                                 msgbuilder.AppendFormat(" ETA {0}", eta);
335                         }
336                         if (byteps > 0) {
337                                 msgbuilder.AppendFormat(" ({0}/s)", NaGet.Utils.FormatSize(byteps));
338                         }
339                         
340                         RaiseTaskSetEvent(TaskEventType.PING, msgbuilder.ToString(), percent);
341                 }
342         }
343 }