OSDN Git Service

initial build for opensource
[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                                         // TODO ハックな実装?\r
157                                         if (File.Exists("archive-inst.exe")) {\r
158                                                 string argument = string.Format("-i \"{0}\" \"{1}\"", installerfile, this.InstalledPackage.Name);\r
159                                                 ProcessStartInfo procInfo = new ProcessStartInfo(Path.GetFullPath("archive-inst.exe"), argument);\r
160                                                 procInfo.UseShellExecute = false;\r
161                                                 procInfo.CreateNoWindow = true;\r
162                                                 procInfo.WorkingDirectory = Environment.CurrentDirectory;\r
163                                                 \r
164                                                 hProcess = NaGet.Utils.ProcessStartWithOutputCapture(procInfo, this.OutputDataReceived, this.ErrorDataReceived);\r
165                                                 hProcess.WaitForExit();\r
166                                                 break;\r
167                                         }\r
168                                         throw new NotImplementedException("Not implemented archive installation yet");\r
169                                         //break;\r
170                                 default:\r
171                                         throw new NotImplementedException("Not implemented archive installation yet");\r
172                                 }\r
173                                 \r
174                                 return hProcess.ExitCode;\r
175                         } finally {\r
176                                 if (hProcess != null) {\r
177                                         hProcess.Close();\r
178                                 }\r
179                         }\r
180                 }\r
181 \r
182                 /// <summary>\r
183                 /// インストーラ等を起動してインストール作業を行う\r
184                 /// </summary>\r
185                 /// <returns>インストーラの終了コード</returns>\r
186                 public int Install()\r
187                 {\r
188                         string installerFile = this.InstallerFile;\r
189                         string tempDir = Path.Combine(Path.GetTempPath(), Path.GetRandomFileName());\r
190                         \r
191                         // アーカイブされているなら一旦展開\r
192                         if (InstalledPackage.ArchivedInstaller) {\r
193                                 Directory.CreateDirectory(tempDir);\r
194                                 \r
195                                 // 64bit対策で展開も別プロセスで\r
196                                 // TODO エラーとかの出力を取得できたりとかできるように\r
197                                 if (File.Exists("archive-inst.exe")) {\r
198                                         string argument = string.Format("-t \"{0}\" \"{1}\"", installerFile, tempDir);\r
199                                         ProcessStartInfo info = new ProcessStartInfo(Path.GetFullPath("archive-inst.exe"), argument);\r
200                                         info.UseShellExecute = false;\r
201                                         info.WorkingDirectory = Environment.CurrentDirectory;\r
202                                         \r
203                                         using (Process hProcess = Process.Start(info)) {\r
204                                                 hProcess.WaitForExit();\r
205                                         \r
206                                                 if (hProcess.ExitCode != 0) {\r
207                                                         throw new ApplicationException("Extract error " + installerFile + " to " + tempDir);\r
208                                                 }\r
209                                         }\r
210                                 } else {\r
211                                         throw new ApplicationException(string.Format("archive-inst.exe not found in {0}!", Environment.CurrentDirectory));\r
212                                 }\r
213                                 \r
214 //                              System.Text.StringBuilder output = new System.Text.StringBuilder(1024);\r
215 //                              int res = NaGet.InteropServices.CommonArchiverExtracter.ExtractArchive(installerFile, tempDir, output, IntPtr.Zero);\r
216 //                              if (res != 0) {\r
217 //                                      throw new ApplicationException("Extract error\n" + output.ToString());\r
218 //                              }\r
219                                 \r
220                                 installerFile = seekInstallerFile(tempDir, InstalledPackage.Type);\r
221                                 if (installerFile == null && Directory.GetDirectories(tempDir).Length == 1) {\r
222                                         installerFile = seekInstallerFile(Path.Combine(tempDir, Directory.GetDirectories(tempDir)[0]), InstalledPackage.Type);\r
223                                 }\r
224                         }\r
225                         \r
226                         \r
227                         int exitCode = invokeInstaller(installerFile, InstalledPackage.Type);\r
228                         \r
229                         installed = true;\r
230                         \r
231                         return exitCode;\r
232                 }\r
233                 \r
234                 /// <summary>\r
235                 /// ダウンロード済みであるか否か\r
236                 /// </summary>\r
237                 public bool Downloaded {\r
238                         get {\r
239                                 return File.Exists(InstallerFile) && ((File.GetAttributes(InstallerFile) & FileAttributes.Hidden) != FileAttributes.Hidden);\r
240                         }\r
241                 }\r
242                 \r
243                 /// <summary>\r
244                 /// インストール作業済みであるか否か。実際にインストールされたかどうかではありません。\r
245                 /// </summary>\r
246                 public bool Installed {\r
247                         get { return installed; }\r
248                 }\r
249                 \r
250                 /// <summary>\r
251                 /// インストーラの処理が成功してインストールされたプログラムが確認できるか否か。\r
252                 /// </summary>\r
253                 public bool InstallSuccessed {\r
254                         get {\r
255                                 switch (Type) {\r
256                                         case InstallerType.ARCHIVE: // アーカイブインストーラはフォルダの確認\r
257                                                 return Directory.Exists(Path.Combine(NaGet.Env.ArchiveProgramFiles, InstalledPackage.Name));\r
258                                         case InstallerType.EXEC_INSTALLER:\r
259                                         case InstallerType.MSI_PACKAGE:\r
260                                                 return RegistriedUninstallers.GetInstalledPackageFor(InstalledPackage) != null;\r
261                                         default:\r
262                                                 return false;\r
263                                 }\r
264                         }\r
265                 }\r
266                 \r
267                 /// <summary>\r
268                 /// インストーラの種類を返す\r
269                 /// </summary>\r
270                 public InstallerType Type {\r
271                         get {\r
272                                 return InstalledPackage.Type;\r
273                         }\r
274                 }\r
275                 \r
276                 /// <summary>\r
277                 /// もっともふさわしいインストーラ番号を返す\r
278                 /// </summary>\r
279                 /// <returns></returns>\r
280                 public static int GetPreferInstallerIndex(Package InstalledPackage)\r
281                 {\r
282                         if (InstalledPackage.Type == InstallerType.CANNOT_INSTALL) { // インストール不能パッケージ\r
283                                 return -1;\r
284                         }\r
285                         \r
286                         int best = -1;\r
287                         int bestVal = 0;\r
288                         \r
289                         for (int i = 0; i < InstalledPackage.Installer.Length; i++) {\r
290                                 Platform platform = InstalledPackage.Installer[i].Platform;\r
291                                 \r
292                                 int pts = 0;\r
293                                 if (platform != null) {\r
294                                         pts = (platform.IsRunnable())? 10 : 0;\r
295                                         pts += (platform.IsRunnableArchOnWow64() && platform.IsRunnableOS())? 1 : 0;\r
296                                 } else { // if (platform == null) {\r
297                                         pts = 1; // null の場合は動作プラットホーム扱い\r
298                                 }\r
299                                 if (pts > bestVal) {\r
300                                         bestVal = pts;\r
301                                         best = i;\r
302                                 }\r
303                         }\r
304                         \r
305                         return best;\r
306                 }\r
307                 \r
308                 /// <summary>\r
309                 /// インストーラの一時保存先パスを生成\r
310                 /// </summary>\r
311                 private string getArchiveFilePath()\r
312                 {\r
313                         Package package = this.InstalledPackage;\r
314                         \r
315                         string folderName = string.Format("{0}({1})", package.Name, package.Version);\r
316                         string fileName = NaGet.Utils.Url2filename(package.Installer[0].Url.Href);\r
317                         \r
318                         string folder = Path.Combine(NaGet.Env.ArchiveFolderPath, folderName);\r
319                         \r
320                         if (! File.Exists(Path.Combine(folder, fileName))) {\r
321                                 if (Directory.Exists(folder)) {\r
322                                         if (! File.Exists(Path.Combine(folder, fileName))) {\r
323                                                 fileName = seekInstallerFile(folder, package.Type) ?? fileName;\r
324                                         }\r
325                                 } else {\r
326                                         Directory.CreateDirectory(folder);\r
327                                 }\r
328                         }\r
329                         \r
330                         return Path.Combine(folder, fileName);\r
331                 }\r
332                 \r
333                 /// <summary>\r
334                 /// インストーラファイルを探す\r
335                 /// </summary>\r
336                 /// <param name="basedir">探すフォルダ</param>\r
337                 /// <param name="type">インストーラの種類</param>\r
338                 /// <returns>探し出されたインストーラファイルのフルパス。存在しないならばnull</returns>\r
339                 private static string seekInstallerFile(string basedir, InstallerType type)\r
340                 {\r
341                         if (! Directory.Exists(basedir)) {\r
342                                 return null;\r
343                         }\r
344                         \r
345                         System.Collections.Generic.List<string> list = new System.Collections.Generic.List<string>();\r
346                         switch (type) {\r
347                                 case InstallerType.MSI_PACKAGE:\r
348                                         list.AddRange(Directory.GetFiles(basedir, "*.msi"));\r
349                                         break;\r
350                                 case InstallerType.EXEC_INSTALLER:\r
351                                         list.AddRange(Directory.GetFiles(basedir, "*.exe"));\r
352                                         break;\r
353                                 case InstallerType.ARCHIVE:\r
354                                         list.AddRange(Directory.GetFiles(basedir, "*.zip"));\r
355                                         list.AddRange(Directory.GetFiles(basedir, "*.lzh"));\r
356                                         list.AddRange(Directory.GetFiles(basedir, "*.cab"));\r
357                                         list.AddRange(Directory.GetFiles(basedir, "*.7z"));\r
358                                         list.AddRange(Directory.GetFiles(basedir, "*.tar*"));\r
359                                         break;\r
360                                 default:\r
361                                         return null;\r
362                         }\r
363                         \r
364                         // 存在しないファイルを削除\r
365                         list.RemoveAll(\r
366                                 delegate(string file) {\r
367                                         return ! File.Exists(file);\r
368                                 }\r
369                         );\r
370                         \r
371                         // "setup"の語が入ったファイルはインストーラとみなし、優先選択\r
372                         foreach (string path in list) {\r
373                                 if (Path.GetFileName(path).ToLower().IndexOf("setup") >= 0) {\r
374                                         return path;\r
375                                 }\r
376                         }\r
377                         \r
378                         // それ以外なら一つ目を返す\r
379                         return (list.Count > 0)? list[0] : null;\r
380                 }\r
381                 \r
382                 public override string ToString()\r
383                 {\r
384                         return string.Format("{0}({1})", InstalledPackage.Name, InstalledPackage.Version);\r
385                 }\r
386                 \r
387                 public static string ToString(Installation[] installations)\r
388                 {\r
389                         string[] strs = new string[installations.Length];\r
390                         for (int i = 0; i < installations.Length; i++) {\r
391                                 strs[i] = installations[i].ToString();\r
392                         }\r
393                         return string.Join(" ", strs);\r
394                 }\r
395                 \r
396                 /// <summary>\r
397                 /// パッケージ配列をインストール処理配列に変換する便利メソッド\r
398                 /// </summary>\r
399                 /// <param name="pkgs">パッケージ配列</param>\r
400                 /// <returns>変換されたインストール処理配列</returns>\r
401                 public static Installation[] ConvertInstallations(Package[] pkgs)\r
402                 {\r
403                         Installation[] insts = new Installation[pkgs.Length];\r
404                         for (int i = 0; i < pkgs.Length; i++) {\r
405                                 insts[i] = new Installation(pkgs[i]);\r
406                         }\r
407                         return insts;\r
408                 }\r
409         }\r
410 }\r