OSDN Git Service

na-get,archive-inst.exeの呼び出しルーチンを一本化
[applistation/AppliStation.git] / na-get-lib / NaGet.Packages.Install / Installation.cs
1 using System;\r
2 using System.IO;\r
3 using System.Diagnostics;\r
4 using NaGet.Net;\r
5 using NaGet.SubCommands;\r
6 \r
7 namespace NaGet.Packages.Install\r
8 {\r
9 \r
10     /// <summary>\r
11     /// ダウンロード・インストール処理をカプセル化するクラス\r
12     /// </summary>\r
13     public class Installation\r
14         {\r
15                 /// <summary>\r
16                 /// インストールするパッケージ\r
17                 /// </summary>\r
18                 public Package InstalledPackage;\r
19 \r
20                 /// <summary>\r
21                 /// (保存される)インストーラのファイルのパス\r
22                 /// </summary>\r
23                 public string InstallerFile;\r
24 \r
25                 /// <summary>\r
26                 /// インストールが完了されたか否かのフラグ\r
27                 /// </summary>\r
28                 private bool installed = false;\r
29           \r
30                 /// <summary>\r
31                 /// 起動するインストーラのインデックス番号\r
32                 /// </summary>\r
33                 protected int installerIndex = 0;\r
34                 \r
35                 /// <summary>\r
36                 /// 外部アプリのエラー出力の受信ハンドラ\r
37                 /// </summary>\r
38                 public event EventHandler<NaGet.Utils.AnyDataEventArgs<string>> ErrorDataReceived;\r
39                 \r
40                 /// <summary>\r
41                 /// 外部アプリの標準出力の受信ハンドラ\r
42                 /// </summary>\r
43                 public event EventHandler<NaGet.Utils.AnyDataEventArgs<string>> OutputDataReceived;\r
44                 \r
45                 /// <summary>\r
46                 /// コンストラクタ\r
47                 /// </summary>\r
48                 /// <param name="package">インストールするパッケージ</param>\r
49                 public Installation(Package package)\r
50                 {\r
51                         InstalledPackage = package;\r
52                         InstallerFile = getArchiveFilePath();\r
53                         installerIndex = GetPreferInstallerIndex(package);\r
54                 }\r
55                 \r
56                 /// <summary>\r
57                 /// コンストラクタ\r
58                 /// </summary>\r
59                 /// <param name="package">インストールするパッケージ</param>\r
60                 /// <param name="installerfile">(保存される)インストーラのファイルのパス</param>\r
61                 protected Installation(Package package, string installerfile)\r
62                 {\r
63                         InstalledPackage = package;\r
64                         InstallerFile = installerfile;\r
65                         installerIndex = GetPreferInstallerIndex(package);\r
66                 }\r
67                 \r
68                 /// <summary>\r
69                 /// インストール可能か否か\r
70                 /// </summary>\r
71                 public bool IsInstallablePackage()\r
72                 {\r
73                         return installerIndex >= 0;\r
74                 }\r
75                 \r
76                 /// <summary>\r
77                 /// すでにインストールされているパッケージを取得する\r
78                 /// </summary>\r
79                 /// <param name="installedPkgs">\r
80                 /// インストールドリスト\r
81                 /// </param>\r
82                 /// <returns>\r
83                 /// インストールされているパッケージの情報。インストールされたパッケージが見つからないならばnullを返す  \r
84                 /// </returns>\r
85                 public InstalledPackage GetInstalledPackage(PackageList<InstalledPackage> installedPkgs)\r
86                 {\r
87                         return installedPkgs.GetPackageForName(InstalledPackage.Name);\r
88                 }\r
89                 \r
90                 /// <summary>\r
91                 /// ダウンロードを行う。\r
92                 /// </summary>\r
93                 /// <param name="downloader">ダウンローダオブジェクト</param>\r
94                 public void Download(Downloader downloader)\r
95                 {\r
96                         if (! Installed) {\r
97                                 string url = InstalledPackage.Installer[installerIndex].Url.Href;\r
98                                 downloader.Download(url, InstallerFile);\r
99                                 \r
100                                 // サーバ指定のファイル名に変更する\r
101                                 if (! string.IsNullOrEmpty(downloader.DownloadedFileName)) {\r
102                                         string newFile = Path.Combine(Path.GetDirectoryName(InstallerFile), downloader.DownloadedFileName);\r
103                                         File.Move(InstallerFile, newFile);\r
104                                         InstallerFile = newFile;\r
105                                 }\r
106                         }\r
107                 }\r
108                 \r
109                 /// <summary>\r
110                 /// ハッシュ検証のためのハッシュの種類の数を返す\r
111                 /// </summary>\r
112                 /// <returns>ハッシュの個数</returns>\r
113                 public int GetRegisteredHashCount()\r
114                 {\r
115                         HashValue[] hashValues = InstalledPackage.Installer[installerIndex].Hash;\r
116                         return (hashValues == null)? 0 : hashValues.Length;\r
117                 }\r
118                 \r
119                 /// <summary>\r
120                 /// ハッシュ検証を行う\r
121                 /// </summary>\r
122                 /// <returns>検証にパスしたらtrue、パスしなかったらfalse</returns>\r
123                 public bool VerifyHashValues()\r
124                 {\r
125                         HashValue[] hashValues = InstalledPackage.Installer[installerIndex].Hash;\r
126                         if (hashValues != null) {\r
127                                 foreach (HashValue hash in hashValues) {\r
128                                         if (! hash.Validate(InstallerFile)) {\r
129                                                 return false;\r
130                                         }\r
131                                 }\r
132                         }\r
133                         \r
134                         return true;\r
135                 }\r
136                 \r
137                 private int invokeInstaller(string installerfile, InstallerType type)\r
138                 {\r
139                         if (! File.Exists(installerfile)) {\r
140                                 throw new FileNotFoundException(string.Format("{0} is not found, perhaps failed to download.", installerfile), installerfile);\r
141                         }\r
142                         \r
143                         Process hProcess = null;\r
144                         try {\r
145                                 switch (type) {\r
146                                 case InstallerType.EXEC_INSTALLER:\r
147                                         hProcess = Process.Start(installerfile);\r
148                                         hProcess.WaitForExit();\r
149                                         \r
150                                         break;\r
151                                 case InstallerType.MSI_PACKAGE:\r
152                                         hProcess = Process.Start("msiexec", string.Format("/i \"{0}\"", installerfile));\r
153                                         hProcess.WaitForExit();\r
154                                         break;\r
155                                 case InstallerType.ARCHIVE:\r
156                                         string argument = string.Format("-i \"{0}\" \"{1}\"", installerfile, this.InstalledPackage.Name);\r
157                                         hProcess = createExtractArchiveProcess(argument,\r
158                                                                                this.OutputDataReceived,\r
159                                                                                this.ErrorDataReceived);\r
160                                         break;\r
161                                 default:\r
162                                         throw new NotImplementedException("Not implemented archive installation yet");\r
163                                 }\r
164                                 \r
165                                 return hProcess.ExitCode;\r
166                         } finally {\r
167                                 if (hProcess != null) {\r
168                                         hProcess.Close();\r
169                                 }\r
170                         }\r
171                 }\r
172                 \r
173                 /// <summary>\r
174                 /// インストーラ等を起動してインストール作業を行う\r
175                 /// </summary>\r
176                 /// <returns>インストーラの終了コード</returns>\r
177                 public int Install()\r
178                 {\r
179                         string installerFile = this.InstallerFile;\r
180                         string tempDir = Path.Combine(Path.GetTempPath(), Path.GetRandomFileName());\r
181                         \r
182                         // アーカイブされているなら一旦展開\r
183                         if (InstalledPackage.ArchivedInstaller) {\r
184                                 Directory.CreateDirectory(tempDir);\r
185                                 \r
186                                 string argument = string.Format("-t \"{0}\" \"{1}\"", installerFile, tempDir);\r
187                                 using (Process hProcess = createExtractArchiveProcess(argument,\r
188                                                                                       this.OutputDataReceived,\r
189                                                                                       this.ErrorDataReceived)) {\r
190                                         hProcess.WaitForExit();\r
191                                 \r
192                                         if (hProcess.ExitCode != 0) {\r
193                                                 throw new ApplicationException("Extract error " + installerFile + " to " + tempDir);\r
194                                         }\r
195                                 }\r
196                                 \r
197 //                              System.Text.StringBuilder output = new System.Text.StringBuilder(1024);\r
198 //                              int res = NaGet.InteropServices.CommonArchiverExtracter.ExtractArchive(installerFile, tempDir, output, IntPtr.Zero);\r
199 //                              if (res != 0) {\r
200 //                                      throw new ApplicationException("Extract error\n" + output.ToString());\r
201 //                              }\r
202                                 \r
203                                 installerFile = seekInstallerFile(tempDir, InstalledPackage.Type);\r
204                                 if (installerFile == null && Directory.GetDirectories(tempDir).Length == 1) {\r
205                                         installerFile = seekInstallerFile(Path.Combine(tempDir, Directory.GetDirectories(tempDir)[0]), InstalledPackage.Type);\r
206                                 }\r
207                         }\r
208                         \r
209                         \r
210                         int exitCode = invokeInstaller(installerFile, InstalledPackage.Type);\r
211                         \r
212                         installed = true;\r
213                         \r
214                         return exitCode;\r
215                 }\r
216                 \r
217                 /// <summary>\r
218                 /// ダウンロード済みであるか否か\r
219                 /// </summary>\r
220                 public bool Downloaded {\r
221                         get {\r
222                                 return File.Exists(InstallerFile) && ((File.GetAttributes(InstallerFile) & FileAttributes.Hidden) != FileAttributes.Hidden);\r
223                         }\r
224                 }\r
225                 \r
226                 /// <summary>\r
227                 /// インストール作業済みであるか否か。実際にインストールされたかどうかではありません。\r
228                 /// </summary>\r
229                 public bool Installed {\r
230                         get { return installed; }\r
231                 }\r
232                 \r
233                 /// <summary>\r
234                 /// インストーラの処理が成功してインストールされたプログラムが確認できるか否か。\r
235                 /// </summary>\r
236                 public bool InstallSuccessed {\r
237                         get {\r
238                                 switch (Type) {\r
239                                         case InstallerType.ARCHIVE: // アーカイブインストーラはフォルダの確認\r
240                                                 return Directory.Exists(Path.Combine(NaGet.Env.ArchiveProgramFiles, InstalledPackage.Name));\r
241                                         case InstallerType.EXEC_INSTALLER:\r
242                                         case InstallerType.MSI_PACKAGE:\r
243                                                 return RegistriedUninstallers.GetInstalledPackageFor(InstalledPackage) != null;\r
244                                         default:\r
245                                                 return false;\r
246                                 }\r
247                         }\r
248                 }\r
249                 \r
250                 /// <summary>\r
251                 /// インストーラの種類を返す\r
252                 /// </summary>\r
253                 public InstallerType Type {\r
254                         get {\r
255                                 return InstalledPackage.Type;\r
256                         }\r
257                 }\r
258                 \r
259                 /// <summary>\r
260                 /// もっともふさわしいインストーラ番号を返す\r
261                 /// </summary>\r
262                 /// <returns></returns>\r
263                 public static int GetPreferInstallerIndex(Package InstalledPackage)\r
264                 {\r
265                         if (InstalledPackage.Type == InstallerType.CANNOT_INSTALL) { // インストール不能パッケージ\r
266                                 return -1;\r
267                         }\r
268                         \r
269                         int best = -1;\r
270                         int bestVal = 0;\r
271                         \r
272                         for (int i = 0; i < InstalledPackage.Installer.Length; i++) {\r
273                                 Platform platform = InstalledPackage.Installer[i].Platform;\r
274                                 \r
275                                 int pts = 0;\r
276                                 if (platform != null) {\r
277                                         pts = (platform.IsRunnable())? 10 : 0;\r
278                                         pts += (platform.IsRunnableArchOnWow64() && platform.IsRunnableOS())? 1 : 0;\r
279                                 } else { // if (platform == null) {\r
280                                         pts = 1; // null の場合は動作プラットホーム扱い\r
281                                 }\r
282                                 if (pts > bestVal) {\r
283                                         bestVal = pts;\r
284                                         best = i;\r
285                                 }\r
286                         }\r
287                         \r
288                         return best;\r
289                 }\r
290                 \r
291                 /// <summary>\r
292                 /// インストーラの一時保存先パスを生成\r
293                 /// </summary>\r
294                 private string getArchiveFilePath()\r
295                 {\r
296                         Package package = this.InstalledPackage;\r
297                         \r
298                         string folderName = string.Format("{0}({1})", package.Name, package.Version);\r
299                         string fileName = NaGet.Utils.Url2filename(package.Installer[0].Url.Href);\r
300                         \r
301                         string folder = Path.Combine(NaGet.Env.ArchiveFolderPath, folderName);\r
302                         \r
303                         if (! File.Exists(Path.Combine(folder, fileName))) {\r
304                                 if (Directory.Exists(folder)) {\r
305                                         if (! File.Exists(Path.Combine(folder, fileName))) {\r
306                                                 fileName = seekInstallerFile(folder, package.Type) ?? fileName;\r
307                                         }\r
308                                 } else {\r
309                                         Directory.CreateDirectory(folder);\r
310                                 }\r
311                         }\r
312                         \r
313                         return Path.Combine(folder, fileName);\r
314                 }\r
315                 \r
316                 /// <summary>\r
317                 /// インストーラファイルを探す\r
318                 /// </summary>\r
319                 /// <param name="basedir">探すフォルダ</param>\r
320                 /// <param name="type">インストーラの種類</param>\r
321                 /// <returns>探し出されたインストーラファイルのフルパス。存在しないならばnull</returns>\r
322                 private static string seekInstallerFile(string basedir, InstallerType type)\r
323                 {\r
324                         if (! Directory.Exists(basedir)) {\r
325                                 return null;\r
326                         }\r
327                         \r
328                         System.Collections.Generic.List<string> list = new System.Collections.Generic.List<string>();\r
329                         switch (type) {\r
330                                 case InstallerType.MSI_PACKAGE:\r
331                                         list.AddRange(Directory.GetFiles(basedir, "*.msi"));\r
332                                         break;\r
333                                 case InstallerType.EXEC_INSTALLER:\r
334                                         list.AddRange(Directory.GetFiles(basedir, "*.exe"));\r
335                                         break;\r
336                                 case InstallerType.ARCHIVE:\r
337                                         list.AddRange(Directory.GetFiles(basedir, "*.zip"));\r
338                                         list.AddRange(Directory.GetFiles(basedir, "*.lzh"));\r
339                                         list.AddRange(Directory.GetFiles(basedir, "*.cab"));\r
340                                         list.AddRange(Directory.GetFiles(basedir, "*.7z"));\r
341                                         list.AddRange(Directory.GetFiles(basedir, "*.tar*"));\r
342                                         break;\r
343                                 default:\r
344                                         return null;\r
345                         }\r
346                         \r
347                         // 存在しないファイルを削除\r
348                         list.RemoveAll(\r
349                                 delegate(string file) {\r
350                                         return ! File.Exists(file);\r
351                                 }\r
352                         );\r
353                         \r
354                         // "setup"の語が入ったファイルはインストーラとみなし、優先選択\r
355                         foreach (string path in list) {\r
356                                 if (Path.GetFileName(path).ToLower().IndexOf("setup") >= 0) {\r
357                                         return path;\r
358                                 }\r
359                         }\r
360                         \r
361                         // それ以外なら一つ目を返す\r
362                         return (list.Count > 0)? list[0] : null;\r
363                 }\r
364                 \r
365                 \r
366                 /// <summary>\r
367                 /// アーカイブファイルの展開・インストールを行う\r
368                 /// </summary>\r
369                 /// <param name="archiveInstArgs">"archive-inst.exe"への引数</param>\r
370                 /// <param name="outputReceived">標準出力用リスナ(null可)</param>\r
371                 /// <param name="errorReceived">エラー出力用リスナ(null可)</param>\r
372                 /// <returns>実行プロセス</returns>\r
373                 private Process createExtractArchiveProcess(string archiveInstArgs,\r
374                                                   EventHandler<NaGet.Utils.AnyDataEventArgs<string>> outputReceived,\r
375                                                   EventHandler<NaGet.Utils.AnyDataEventArgs<string>> errorReceived)\r
376                 {\r
377                         string archiveInstExe = Path.GetFullPath("archive-inst.exe");\r
378                         if (! File.Exists(archiveInstExe)) {\r
379                                 string errMsg = string.Format("\"{0}\" does not found!");\r
380                                 throw new ApplicationException(errMsg,\r
381                                                                new FileNotFoundException(errMsg, archiveInstExe));\r
382                         }\r
383                         \r
384                         ProcessStartInfo procInfo = new ProcessStartInfo(archiveInstExe, archiveInstArgs);\r
385                         procInfo.UseShellExecute = false;\r
386                         procInfo.CreateNoWindow = true;\r
387                         procInfo.WorkingDirectory = Environment.CurrentDirectory;\r
388                         \r
389                         return NaGet.Utils.ProcessStartWithOutputCapture(procInfo, outputReceived, errorReceived);\r
390                 }\r
391                 \r
392                 public override string ToString()\r
393                 {\r
394                         return string.Format("{0}({1})", InstalledPackage.Name, InstalledPackage.Version);\r
395                 }\r
396                 \r
397                 public static string ToString(Installation[] installations)\r
398                 {\r
399                         string[] strs = new string[installations.Length];\r
400                         for (int i = 0; i < installations.Length; i++) {\r
401                                 strs[i] = installations[i].ToString();\r
402                         }\r
403                         return string.Join(" ", strs);\r
404                 }\r
405                 \r
406                 /// <summary>\r
407                 /// パッケージ配列をインストール処理配列に変換する便利メソッド\r
408                 /// </summary>\r
409                 /// <param name="pkgs">パッケージ配列</param>\r
410                 /// <returns>変換されたインストール処理配列</returns>\r
411                 public static Installation[] ConvertInstallations(Package[] pkgs)\r
412                 {\r
413                         Installation[] insts = new Installation[pkgs.Length];\r
414                         for (int i = 0; i < pkgs.Length; i++) {\r
415                                 insts[i] = new Installation(pkgs[i]);\r
416                         }\r
417                         return insts;\r
418                 }\r
419         }\r
420 }\r