OSDN Git Service

na-get-lib,ダウンロードしたファイルの更新日時をWebリソースのそれと合わせるように
[applistation/AppliStation.git] / na-get-lib / NaGet.Net / Downloader.cs
1 using System;\r
2 using System.Net;\r
3 using System.IO;\r
4 using System.Collections;\r
5 using System.Threading;\r
6 using NaGet.SubCommands;\r
7 \r
8 namespace NaGet.Net\r
9 {\r
10 \r
11 /// <summary>\r
12 /// ダウンロードイベントオブジェクト\r
13 /// </summary>\r
14 public class DownloadEventArgs : NaGetEventArgs\r
15 {\r
16         /// <summary>\r
17         /// イベントの種類\r
18         /// </summary>\r
19         public DownloadEventType Type;\r
20 \r
21         /// <summary>\r
22         /// ダウンロード済みのバイト数\r
23         /// </summary>\r
24         public long DownloadSize;\r
25 \r
26         /// <summary>\r
27         /// ダウンロードする総バイト数\r
28         /// </summary>\r
29         public long MaxSize;\r
30         \r
31         /// <summary>\r
32         /// コンストラクタ\r
33         /// </summary>\r
34         /// <param name="type">イベントの種類</param>\r
35         /// <param name="msg">イベントメッセージ</param>\r
36         /// <param name="pos">ダウンロード済みのバイト数</param>\r
37         /// <param name="max">ダウンロードする総バイト数</param>\r
38         public DownloadEventArgs(DownloadEventType type, string msg, long pos, long max)\r
39         {\r
40                 Type = type;\r
41                 DownloadSize = pos;\r
42                 MaxSize = max;\r
43                 \r
44                 TaskMessage = msg;\r
45                 TaskProgressPercent = (max > 0)? (100.0f * pos / max) : -1;\r
46         }\r
47 }\r
48 \r
49 /// <summary>\r
50 /// DownloadEventArgsでのイベントの種類\r
51 /// </summary>\r
52 public enum DownloadEventType {\r
53         INITED,\r
54         CONNECTED,\r
55         STARTED,\r
56         DOWNLOADING,\r
57         ERROR,\r
58         COMPLETED\r
59 }\r
60 \r
61 /// <summary>\r
62 /// ダウンロード処理を行うクラス\r
63 /// </summary>\r
64 public class Downloader : NaGetTask\r
65 {\r
66         /// <summary>\r
67         /// デフォルトで使うプロキシ\r
68         /// </summary>\r
69         public static IWebProxy DefaultProxy = WebRequest.GetSystemWebProxy();\r
70         \r
71         /// <summary>\r
72         /// 通信に使うプロキシ\r
73         /// </summary>\r
74         public IWebProxy Proxy;\r
75         \r
76         /// <summary>\r
77         /// イベントハンドラ\r
78         /// </summary>\r
79         public event EventHandler<DownloadEventArgs> DownloadEventRaised;\r
80         \r
81         protected string url;\r
82         \r
83         protected string filepath;\r
84         \r
85         protected WebRequest request;\r
86         \r
87         protected WebResponse response;\r
88         \r
89         private bool cancelCalled = false;\r
90         \r
91         private bool done = false;\r
92         \r
93         /// <summary>\r
94         /// ダウンロード時にHTTPヘッダなどから取得した本来のファイル名\r
95         /// </summary>\r
96         private string downloadedFileName = null;\r
97         \r
98         /// <summary>\r
99         /// ダウンロード時にHTTPヘッダなどから取得した本来のファイル名\r
100         /// </summary>\r
101         public string DownloadedFileName {\r
102                 get { return downloadedFileName; }\r
103         }\r
104         \r
105         /* ダウンロードの処理時間計測器 */\r
106         private System.Diagnostics.Stopwatch stopwatch;\r
107 \r
108         /// <summary>\r
109         /// ダウンロードを行う\r
110         /// </summary>\r
111         /// <param name="url">ダウンロードするリソースのURL</param>\r
112         /// <param name="filepath">保存先ファイルパス</param>\r
113         public void Download(string url, string filepath)\r
114         {\r
115                 this.url = url;\r
116                 this.filepath = filepath;\r
117                 \r
118                 try {\r
119                         Run();\r
120                 } catch (NotSupportedException e) {\r
121                         throw new NotSupportedException("Not supported download location for downloader.", e);\r
122                 }\r
123         }\r
124         \r
125         public override bool Done {\r
126                 get { return done; }\r
127         }\r
128         \r
129         public override bool Running {\r
130                 get { return request != null && (!done); }\r
131         }\r
132 \r
133         public override void Run()\r
134         {\r
135                 RaiseDownloadEvent(DownloadEventType.INITED, 0, -1);\r
136                 \r
137                 try {\r
138                         request = WebRequest.Create(url);\r
139                         request.Proxy = (Proxy == null)? DefaultProxy : Proxy;    \r
140                         if (request is HttpWebRequest) {\r
141                                 request.CachePolicy = new System.Net.Cache.HttpRequestCachePolicy(\r
142                                         System.Net.Cache.HttpRequestCacheLevel.NoCacheNoStore);\r
143                         }\r
144                         \r
145                         if (cancelCalled) {\r
146                                 throw new NaGetTaskCanceledException(string.Empty);\r
147                         }\r
148                         \r
149                         try {\r
150                                 response = request.GetResponse();\r
151                         } catch (WebException e) {\r
152                                 if (cancelCalled) { // キャンセル時\r
153                                         throw new NaGetTaskCanceledException(string.Empty, e);\r
154                                 } else {\r
155                                         throw new WebException(e.Message, e);\r
156                                 }\r
157                         }\r
158                         \r
159                         if (cancelCalled) {\r
160                                 throw new NaGetTaskCanceledException(string.Empty);\r
161                         }\r
162                         \r
163                         try {\r
164                                 downloadedFileName = getFileNameFromWebResponse(response);\r
165                         } catch (Exception) {\r
166                         }\r
167                         \r
168                         if (File.Exists(filepath)) { // ファイルが存在するとき削除\r
169                                 File.Delete(filepath);\r
170                         }\r
171                         \r
172                         RaiseDownloadEvent(DownloadEventType.CONNECTED, 0, -1);\r
173                         \r
174                         using (Stream stream = response.GetResponseStream() )\r
175                         using (FileStream fs = new FileStream(filepath,\r
176                                                     FileMode.Create,\r
177                                                     FileAccess.Write) ) {\r
178                                 try {\r
179                                         File.SetAttributes(filepath, FileAttributes.Hidden);\r
180                                         long contentLength = response.ContentLength;\r
181         \r
182                                         RaiseDownloadEvent(DownloadEventType.STARTED, 0, contentLength);\r
183                                         \r
184                                         stopwatch = new System.Diagnostics.Stopwatch();\r
185                                         stopwatch.Start();\r
186         \r
187                                         Timer timer = new Timer(new TimerCallback(\r
188                                                 delegate(object obj) {\r
189                                                         try {\r
190                                                                 RaiseDownloadEvent(DownloadEventType.DOWNLOADING, fs.Position, contentLength);\r
191                                                         } catch (ObjectDisposedException) {\r
192                                                         }\r
193                                                 }), null, 0, 1000);\r
194                                         \r
195                                         try {\r
196                                                 byte[] data = new byte[4096];\r
197                                                 int size = 0;\r
198                                                 while ((size = stream.Read(data,0,data.Length)) > 0) {\r
199                                                         fs.Write(data, 0, size);\r
200                                                         \r
201                                                         if (cancelCalled) {\r
202                                                                 throw new NaGetTaskCanceledException(string.Empty);\r
203                                                         }\r
204                                                 }\r
205                                         } finally {\r
206                                                 timer.Dispose();\r
207                                         }\r
208                                         \r
209                                         File.SetAttributes(filepath, FileAttributes.Normal);\r
210                                         \r
211                                         RaiseDownloadEvent(DownloadEventType.COMPLETED, fs.Position, contentLength);\r
212                                 } catch (IOException ex) {\r
213                                         if (cancelCalled) {\r
214                                                 throw new NaGetTaskCanceledException(string.Empty);\r
215                                         } else {\r
216                                                 RaiseDownloadEvent(DownloadEventType.ERROR, 0, 0);\r
217                                                 throw new IOException(ex.Message, ex);\r
218                                         }\r
219                                 } finally {\r
220                                         if (stopwatch != null) {\r
221                                                 stopwatch.Stop();\r
222                                                 stopwatch = null;\r
223                                         }\r
224                                 }\r
225                         }\r
226                         \r
227                         // 更新日を補完\r
228                         if (File.Exists(filepath)) {\r
229                                 if (response is HttpWebResponse) {\r
230                                         File.SetLastWriteTime(filepath, ((HttpWebResponse) response).LastModified);\r
231                                 } else if (response is FtpWebResponse) {\r
232                                         File.SetLastWriteTime(filepath, ((FtpWebResponse) response).LastModified);\r
233                                 }\r
234                         }\r
235                 } finally {\r
236                         if (response != null) {\r
237                                 response.Close();\r
238                         }\r
239                         \r
240                         request = null;\r
241                         done = true;\r
242                 }\r
243         }\r
244         \r
245         protected void RaiseDownloadEvent(DownloadEventType type, long pos, long max)\r
246         {\r
247                 if (DownloadEventRaised != null) {\r
248                         DownloadEventArgs e = new DownloadEventArgs(type, string.Empty, pos, max);\r
249                         \r
250                         switch (e.Type) {\r
251                                 case DownloadEventType.CONNECTED:\r
252                                         e.TaskMessage = "接続しました";\r
253                                         break;\r
254                                 case DownloadEventType.STARTED:\r
255                                 case DownloadEventType.DOWNLOADING:\r
256                                 case DownloadEventType.COMPLETED:\r
257                                         try {\r
258                                                 e.TaskMessage = string.Format("{0} bytes ({1} %)", e.DownloadSize, (int) e.TaskProgressPercent);\r
259                                         } catch (DivideByZeroException) {\r
260                                                 e.TaskMessage = string.Format("{0} bytes", e.DownloadSize);\r
261                                         }\r
262                                         \r
263                                         \r
264                                         if (stopwatch != null && stopwatch.IsRunning && stopwatch.ElapsedMilliseconds > 3000) {\r
265                                                 long bpers = e.DownloadSize * 1000 / stopwatch.ElapsedMilliseconds;\r
266                                                 try {\r
267                                                         TimeSpan rest = TimeSpan.FromSeconds((max - e.DownloadSize) / bpers);\r
268                                                         e.TaskMessage += string.Format(" 推定残り時間:{0} ({1}/s)", rest, NaGet.Utils.FormatSize(bpers));\r
269                                                 } catch {\r
270                                                         e.TaskMessage += string.Format(" ({0}/s)", NaGet.Utils.FormatSize(bpers));\r
271                                                 }\r
272                                         }\r
273                                         \r
274                                         break;\r
275                         }\r
276                         \r
277                         DownloadEventRaised(this, e);\r
278                 }\r
279         }\r
280         \r
281         public override bool Cancelable {\r
282                 get { return !(this.Done || cancelCalled); }\r
283         }\r
284         \r
285         public override bool Cancel()\r
286         {\r
287                 if (this.Done || cancelCalled) {\r
288                         return false;\r
289                 }\r
290                 \r
291                 cancelCalled = true;\r
292                 if (request != null) {\r
293                         try {\r
294                                 request.Abort();\r
295                         } catch (WebException) {\r
296                         }\r
297                 }\r
298                 return true;\r
299         }\r
300         \r
301         /// <summary>\r
302         /// Webレスポンスからダウンロードしたファイルの名前を取得\r
303         /// </summary>\r
304         /// <param name="response"></param>\r
305         /// <returns></returns>\r
306         private string getFileNameFromWebResponse(WebResponse response)\r
307         {\r
308                 if (response is HttpWebResponse) {\r
309                         string contentDisposition = ((HttpWebResponse) response).Headers["Content-Disposition"];\r
310                         \r
311                         // TODO check license for http://www.atmarkit.co.jp/fdotnet/dotnettips/618downnoname/downnoname.html\r
312                         if (! string.IsNullOrEmpty(contentDisposition)) {\r
313                                 System.Text.RegularExpressions.Regex re = new System.Text.RegularExpressions.Regex(\r
314                                         @"filename\s*=\s*(?:""(?<filename>[^""]*)""|(?<filename>[^;]*))",\r
315                                         System.Text.RegularExpressions.RegexOptions.IgnoreCase);\r
316                                 \r
317                                 System.Text.RegularExpressions.Match m = re.Match(contentDisposition);\r
318                                 if (m.Success) {\r
319                                         return m.Groups["filename"].Value;\r
320                                 }\r
321                         }\r
322                 }\r
323                 \r
324                 return NaGet.Utils.Url2filename(response.ResponseUri.ToString());\r
325         }\r
326 }\r
327 \r
328 }\r
329 \r
330 \r