OSDN Git Service

AppliStation-All,インストール処理を新方式(NaGetInstall2)に変更。
[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                 public DownloadSubTask(string url, string filepath, IWebProxy proxy)
74                         : this(new Uri(url), filepath, proxy)
75                 {
76                 }
77                 
78                 /// <summary>
79                 /// コンストラクタ
80                 /// </summary>
81                 /// <param name="url">ダウンロード先URL</param>
82                 /// <param name="filepath">保存ファイルパス</param>
83                 public DownloadSubTask(Uri url, string filepath)
84                         : this(url, filepath, NaGet.Env.WebProxy)
85                 {
86                 }
87                 
88                 public DownloadSubTask(string url, string filepath)
89                         : this(new Uri(url), filepath, NaGet.Env.WebProxy)
90                 {
91                 }
92         
93                 /// <summary>
94                 /// ダウンロード時にHTTPヘッダなどから取得した本来のファイル名
95                 /// </summary>
96                 public string DownloadedFileName {
97                         get { return downloadedFileName; }
98                 }
99                 
100                 /// <summary>
101                 /// キャンセル可能
102                 /// </summary>
103                 public override bool Cancelable {
104                         get { return !this.cancelCalled; }
105                 }
106                 
107                 /// <summary>
108                 /// ダウンロード処理をキャンセルする
109                 /// </summary>
110                 /// <returns>キャンセルに成功したときtrue</returns>
111                 public override bool Cancel()
112                 {
113                         if (! this.cancelCalled && ! this.Done) {
114                                 this.cancelCalled = true;
115                                 if (request != null) {
116                                         try {
117                                                 request.Abort();
118                                         } catch (WebException) {
119                                         }
120                                 }
121                                 return true;
122                         } else {
123                                 return false;
124                         }
125                 }
126                 
127                 public override void Run()
128                 {
129                         NotifyStarted();
130                         RaiseTaskSetEvent(TaskEventType.STARTED, string.Format("ダウンロード:{0}", this.url), 0);
131                         
132                         try {
133                                 runBuildRequest();
134                                 runHandleCancelTrigger();
135                                 
136                                 RaiseTaskSetEvent(TaskEventType.PING, string.Format("接続中...{0}", this.url.Host), -1);
137                                 
138                                 runAcquireResponse();
139                                 runHandleCancelTrigger();
140                                 
141                                 runPrepareFile();
142                                 runDownloadToFile();
143                                 runHandleCancelTrigger();
144                                 
145                                 runPostprocess();
146                                 
147                                 RaiseTaskSetEvent(TaskEventType.COMPLETED, "ダウンロード終了", 100);
148                         } catch (System.Net.WebException e) {
149                                 if (System.Net.NetworkInformation.NetworkInterface.GetIsNetworkAvailable()) {
150                                         RaiseTaskSetEvent(TaskEventType.WARNING, "ネットワークに接続されていません。", -1);
151                                 } else {
152                                         RaiseTaskSetEvent(TaskEventType.WARNING, "ネットワークに接続できませんでした。ネットワークが切断されているか、ファイアウォールによって遮断された可能性があります。", -1);
153                                 }
154                                 throw new System.Net.WebException(e.Message, e);
155                         } finally {
156                                 runReleaseResponse();
157                                 
158                                 if (cancelCalled) {
159                                         NotifyCancelled();
160                                 } else {
161                                         NotifyCompleted();
162                                 }
163                         }
164                 }
165                 
166                 /// <summary>
167                 /// キャンセルされたかを確認して、キャンセル要求があるのならば TaskCanceledException を投げる
168                 /// </summary>
169                 private void runHandleCancelTrigger()
170                 {
171                         if (this.cancelCalled) {
172                                 throw new TaskCanceledException(string.Empty);
173                         }
174                 }
175                 
176                 /// <summary>
177                 /// requestの構築。
178                 /// </summary>
179                 private void runBuildRequest()
180                 {
181                         request = WebRequest.Create(url);
182                         request.Proxy = this.proxy;
183                         request.CachePolicy = new System.Net.Cache.RequestCachePolicy(CacheLevel);
184                         
185                         HttpWebRequest httpRequest = request as HttpWebRequest;
186                         if (httpRequest != null) {
187                                 httpRequest.AutomaticDecompression = DecompressionMethods.Deflate | DecompressionMethods.GZip;  
188                                 // TODO User-Agent
189                                 httpRequest.UserAgent = "AppliStation/1.3";
190                         }
191                 }
192                 
193                 /// <summary>
194                 /// Responseを得る
195                 /// </summary>
196                 private void runAcquireResponse()
197                 {
198                         try {
199                                 response = request.GetResponse();
200                         } catch (WebException e) {
201                                 if (cancelCalled) { // キャンセル時
202                                         throw new TaskCanceledException(string.Empty, e);
203                                 } else {
204                                         throw new WebException(e.Message, e);
205                                 }
206                         }
207                 }
208                 
209                 /// <summary>
210                 /// ダウンロード先ファイル名の決定
211                 /// </summary>
212                 private void runPrepareFile()
213                 {
214                         try {
215                                 downloadedFileName = getFileNameFromWebResponse(response);
216                         } catch (Exception) {
217                         }
218                         
219                         if (File.Exists(filepath)) { // ファイルが存在するとき削除
220                                 File.Delete(filepath);
221                         }
222                 }
223                 
224                 private void runDownloadToFile()
225                 {
226                         Stopwatch stopwatch = new Stopwatch();
227                         
228                         using (Stream stream = response.GetResponseStream() )
229                         using (FileStream fs = new FileStream(filepath,
230                                                     FileMode.Create,
231                                                     FileAccess.Write) ) {
232                                 try {
233                                         File.SetAttributes(filepath, FileAttributes.Hidden);
234                                         long contentLength = response.ContentLength;
235         
236                                         stopwatch.Start();
237                                         RaiseDownloadProgressTaskSetEvent(0, contentLength, 0);
238         
239                                         Timer timer = new Timer(new TimerCallback(
240                                                 delegate(object obj) {
241                                                         try {
242                                                                 RaiseDownloadProgressTaskSetEvent(fs.Position, contentLength, stopwatch.ElapsedMilliseconds);
243                                                         } catch (ObjectDisposedException) {
244                                                         }
245                                                 }), null, 0, 1000);
246                                         
247                                         try {
248                                                 byte[] data = new byte[4096];
249                                                 int size = 0;
250                                                 while ((size = stream.Read(data,0,data.Length)) > 0) {
251                                                         fs.Write(data, 0, size);
252                                                         
253                                                         if (cancelCalled) {
254                                                                 throw new TaskCanceledException(string.Empty);
255                                                         }
256                                                 }
257                                         } finally {
258                                                 timer.Dispose();
259                                         }
260                                         
261                                         File.SetAttributes(filepath, FileAttributes.Normal);
262                                 } catch (IOException ex) {
263                                         if (cancelCalled) {
264                                                 throw new TaskCanceledException(string.Empty);
265                                         } else {
266                                                 throw new IOException(ex.Message, ex);
267                                         }
268                                 } finally {
269                                         if (stopwatch != null) {
270                                                 stopwatch.Stop();
271                                                 stopwatch = null;
272                                         }
273                                 }
274                         }
275                 }
276                 
277                 private void runPostprocess()
278                 {                       
279                         // 更新日を補完
280                         if (File.Exists(filepath)) {
281                                 HttpWebResponse httpResponse = response as HttpWebResponse;
282                                 FtpWebResponse  ftpResponse      = response as FtpWebResponse;
283                                 
284                                 if (httpResponse != null) {
285                                         File.SetLastWriteTime(filepath, httpResponse.LastModified);
286                                 } else if (ftpResponse != null) {
287                                         File.SetLastWriteTime(filepath, ftpResponse.LastModified);
288                                 }
289                         }
290                 }
291                 
292                 /// <summary>
293                 /// responseの開放
294                 /// </summary>
295                 private void runReleaseResponse()
296                 {
297                         if (response != null) {
298                                 response.Close();
299                         }
300                 }
301                 
302                 /// <summary>
303                 /// Webレスポンスからダウンロードしたファイルの名前を取得
304                 /// </summary>
305                 /// <remarks>Content-Dispositionヘッダから取得あるいはURLの末尾から推定します</remarks>
306                 /// <param name="response">レスポンスオブジェクト</param>
307                 /// <returns>取得したファイル名</returns>
308                 private static string getFileNameFromWebResponse(WebResponse response)
309                 {
310                         HttpWebResponse httpresp = response as HttpWebResponse;
311                         if (httpresp != null) {
312                                 string contentDisposition = httpresp.Headers["Content-Disposition"];
313                                 
314                                 if (! string.IsNullOrEmpty(contentDisposition)) {
315                                         try {
316                                                 ContentDisposition parser = new ContentDisposition(contentDisposition);
317                                                 if (! string.IsNullOrEmpty(parser.FileName)) {
318                                                         return parser.FileName;
319                                                 }
320                                         } catch (FormatException) {
321                                         }
322                                 }
323                         }
324                         
325                         return NaGet.Utils.Url2filename(response.ResponseUri);
326                 }
327                 
328                 /// <summary>
329                 /// ダウンロード進捗メッセージを生成
330                 /// </summary>
331                 /// <param name="downloadsize">現在ダウンロード済みのサイズ</param>
332                 /// <param name="filesize">全体のファイルサイズ。不明なときはゼロを指定。</param>
333                 /// <param name="elapsedms">ダウンロード開始からの時間をms単位で。</param>
334                 protected virtual void RaiseDownloadProgressTaskSetEvent(long downloadsize, long filesize, long elapsedms)
335                 {
336                         float percent = -1;
337                         TimeSpan eta = new TimeSpan(0);
338                         long byteps = 0;
339                         
340                         // 進捗率の算出
341                         if (filesize > 0) {
342                                 percent = 100 * ((float) downloadsize) / ((float) filesize);
343                         }
344                         
345                         // スループット・残り時間の算出
346                         if (elapsedms > 0) {
347                                 byteps = 1000 * downloadsize / elapsedms;
348                                 if (filesize > 0 && byteps > 0) {
349                                         eta = TimeSpan.FromSeconds((filesize - downloadsize) / byteps);
350                                 }
351                         }
352                         
353                         System.Text.StringBuilder msgbuilder = new System.Text.StringBuilder();
354                         msgbuilder.AppendFormat("{0} bytes", downloadsize);
355                         if (percent > 0) {
356                                 msgbuilder.AppendFormat(" ({0:F2}%)", percent);
357                         }
358                         if (eta.Ticks > 0) {
359                                 msgbuilder.AppendFormat(" ETA {0}", eta);
360                         }
361                         if (byteps > 0) {
362                                 msgbuilder.AppendFormat(" ({0}/s)", NaGet.Utils.FormatSize(byteps));
363                         }
364                         
365                         RaiseTaskSetEvent(TaskEventType.PING, msgbuilder.ToString(), percent);
366                 }
367         }
368 }