OSDN Git Service

na-get-lib,all-getについてアップデート処理を新方式(NaGetUpdate2)に変更。
[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                         } finally {
147                                 runReleaseResponse();
148                                 
149                                 if (cancelCalled) {
150                                         NotifyCancelled();
151                                 } else {
152                                         NotifyCompleted();
153                                         RaiseTaskSetEvent(TaskEventType.COMPLETED, "ダウンロード終了", 100);
154                                 }
155                         }
156                 }
157                 
158                 /// <summary>
159                 /// キャンセルされたかを確認して、キャンセル要求があるのならば TaskCanceledException を投げる
160                 /// </summary>
161                 private void runHandleCancelTrigger()
162                 {
163                         if (this.cancelCalled) {
164                                 throw new TaskCanceledException(string.Empty);
165                         }
166                 }
167                 
168                 /// <summary>
169                 /// requestの構築。
170                 /// </summary>
171                 private void runBuildRequest()
172                 {
173                         request = WebRequest.Create(url);
174                         request.Proxy = this.proxy;
175                         request.CachePolicy = new System.Net.Cache.RequestCachePolicy(CacheLevel);
176                         
177                         HttpWebRequest httpRequest = request as HttpWebRequest;
178                         if (httpRequest != null) {
179                                 httpRequest.AutomaticDecompression = DecompressionMethods.Deflate | DecompressionMethods.GZip;  
180                                 // TODO User-Agent
181                                 httpRequest.UserAgent = "AppliStation/1.3";
182                         }
183                 }
184                 
185                 /// <summary>
186                 /// Responseを得る
187                 /// </summary>
188                 private void runAcquireResponse()
189                 {
190                         try {
191                                 response = request.GetResponse();
192                         } catch (WebException e) {
193                                 if (cancelCalled) { // キャンセル時
194                                         throw new TaskCanceledException(string.Empty, e);
195                                 } else {
196                                         throw new WebException(e.Message, e);
197                                 }
198                         }
199                 }
200                 
201                 /// <summary>
202                 /// ダウンロード先ファイル名の決定
203                 /// </summary>
204                 private void runPrepareFile()
205                 {
206                         try {
207                                 downloadedFileName = getFileNameFromWebResponse(response);
208                         } catch (Exception) {
209                         }
210                         
211                         if (File.Exists(filepath)) { // ファイルが存在するとき削除
212                                 File.Delete(filepath);
213                         }
214                 }
215                 
216                 private void runDownloadToFile()
217                 {
218                         Stopwatch stopwatch = new Stopwatch();
219                         
220                         using (Stream stream = response.GetResponseStream() )
221                         using (FileStream fs = new FileStream(filepath,
222                                                     FileMode.Create,
223                                                     FileAccess.Write) ) {
224                                 try {
225                                         File.SetAttributes(filepath, FileAttributes.Hidden);
226                                         long contentLength = response.ContentLength;
227         
228                                         stopwatch.Start();
229                                         RaiseDownloadProgressTaskSetEvent(0, contentLength, 0);
230         
231                                         Timer timer = new Timer(new TimerCallback(
232                                                 delegate(object obj) {
233                                                         try {
234                                                                 RaiseDownloadProgressTaskSetEvent(fs.Position, contentLength, stopwatch.ElapsedMilliseconds);
235                                                         } catch (ObjectDisposedException) {
236                                                         }
237                                                 }), null, 0, 1000);
238                                         
239                                         try {
240                                                 byte[] data = new byte[4096];
241                                                 int size = 0;
242                                                 while ((size = stream.Read(data,0,data.Length)) > 0) {
243                                                         fs.Write(data, 0, size);
244                                                         
245                                                         if (cancelCalled) {
246                                                                 throw new TaskCanceledException(string.Empty);
247                                                         }
248                                                 }
249                                         } finally {
250                                                 timer.Dispose();
251                                         }
252                                         
253                                         File.SetAttributes(filepath, FileAttributes.Normal);
254                                 } catch (IOException ex) {
255                                         if (cancelCalled) {
256                                                 throw new TaskCanceledException(string.Empty);
257                                         } else {
258                                                 throw new IOException(ex.Message, ex);
259                                         }
260                                 } finally {
261                                         if (stopwatch != null) {
262                                                 stopwatch.Stop();
263                                                 stopwatch = null;
264                                         }
265                                 }
266                         }
267                 }
268                 
269                 private void runPostprocess()
270                 {                       
271                         // 更新日を補完
272                         if (File.Exists(filepath)) {
273                                 HttpWebResponse httpResponse = response as HttpWebResponse;
274                                 FtpWebResponse  ftpResponse      = response as FtpWebResponse;
275                                 
276                                 if (httpResponse != null) {
277                                         File.SetLastWriteTime(filepath, httpResponse.LastModified);
278                                 } else if (ftpResponse != null) {
279                                         File.SetLastWriteTime(filepath, ftpResponse.LastModified);
280                                 }
281                         }
282                 }
283                 
284                 /// <summary>
285                 /// responseの開放
286                 /// </summary>
287                 private void runReleaseResponse()
288                 {
289                         if (response != null) {
290                                 response.Close();
291                         }
292                 }
293                 
294                 /// <summary>
295                 /// Webレスポンスからダウンロードしたファイルの名前を取得
296                 /// </summary>
297                 /// <remarks>Content-Dispositionヘッダから取得あるいはURLの末尾から推定します</remarks>
298                 /// <param name="response">レスポンスオブジェクト</param>
299                 /// <returns>取得したファイル名</returns>
300                 private static string getFileNameFromWebResponse(WebResponse response)
301                 {
302                         HttpWebResponse httpresp = response as HttpWebResponse;
303                         if (httpresp != null) {
304                                 string contentDisposition = httpresp.Headers["Content-Disposition"];
305                                 
306                                 if (! string.IsNullOrEmpty(contentDisposition)) {
307                                         try {
308                                                 ContentDisposition parser = new ContentDisposition(contentDisposition);
309                                                 if (! string.IsNullOrEmpty(parser.FileName)) {
310                                                         return parser.FileName;
311                                                 }
312                                         } catch (FormatException) {
313                                         }
314                                 }
315                         }
316                         
317                         return NaGet.Utils.Url2filename(response.ResponseUri);
318                 }
319                 
320                 /// <summary>
321                 /// ダウンロード進捗メッセージを生成
322                 /// </summary>
323                 /// <param name="downloadsize">現在ダウンロード済みのサイズ</param>
324                 /// <param name="filesize">全体のファイルサイズ。不明なときはゼロを指定。</param>
325                 /// <param name="elapsedms">ダウンロード開始からの時間をms単位で。</param>
326                 protected virtual void RaiseDownloadProgressTaskSetEvent(long downloadsize, long filesize, long elapsedms)
327                 {
328                         float percent = -1;
329                         TimeSpan eta = new TimeSpan(0);
330                         long byteps = 0;
331                         
332                         // 進捗率の算出
333                         if (filesize > 0) {
334                                 percent = 100 * ((float) downloadsize) / ((float) filesize);
335                         }
336                         
337                         // スループット・残り時間の算出
338                         if (elapsedms > 0) {
339                                 byteps = 1000 * downloadsize / elapsedms;
340                                 if (filesize > 0) {
341                                         eta = TimeSpan.FromSeconds((filesize - downloadsize) / byteps);
342                                 }
343                         }
344                         
345                         System.Text.StringBuilder msgbuilder = new System.Text.StringBuilder();
346                         msgbuilder.AppendFormat("{0} bytes", downloadsize);
347                         if (percent > 0) {
348                                 msgbuilder.AppendFormat(" ({0}%)", percent);
349                         }
350                         if (eta.Ticks > 0) {
351                                 msgbuilder.AppendFormat(" ETA {0}", eta);
352                         }
353                         if (byteps > 0) {
354                                 msgbuilder.AppendFormat(" ({0}/s)", NaGet.Utils.FormatSize(byteps));
355                         }
356                         
357                         RaiseTaskSetEvent(TaskEventType.PING, msgbuilder.ToString(), percent);
358                 }
359         }
360 }