using System;
using System.IO;
using System.Diagnostics;
using NaGet.Net;
using NaGet.SubCommands;
namespace NaGet.Packages.Install
{
///
/// ダウンロード・インストール処理をカプセル化するクラス
///
public class Installation
{
///
/// インストールするパッケージ
///
public Package InstalledPackage;
///
/// (保存される)インストーラのファイルのパス
///
public string InstallerFile;
///
/// インストールが完了されたか否かのフラグ
///
private bool installed = false;
///
/// 起動するインストーラのインデックス番号
///
protected int installerIndex = 0;
///
/// 外部アプリのエラー出力の受信ハンドラ
///
public event EventHandler> ErrorDataReceived;
///
/// 外部アプリの標準出力の受信ハンドラ
///
public event EventHandler> OutputDataReceived;
///
/// コンストラクタ
///
/// インストールするパッケージ
public Installation(Package package)
{
InstalledPackage = package;
InstallerFile = getArchiveFilePath();
installerIndex = GetPreferInstallerIndex(package);
}
///
/// コンストラクタ
///
/// インストールするパッケージ
/// (保存される)インストーラのファイルのパス
protected Installation(Package package, string installerfile)
{
InstalledPackage = package;
InstallerFile = installerfile;
installerIndex = GetPreferInstallerIndex(package);
}
///
/// インストール可能か否か
///
public bool IsInstallablePackage()
{
return installerIndex >= 0;
}
///
/// すでにインストールされているパッケージを取得する
///
///
/// インストールドリスト
///
///
/// インストールされているパッケージの情報。インストールされたパッケージが見つからないならばnullを返す
///
public InstalledPackage GetInstalledPackage(PackageList installedPkgs)
{
return installedPkgs.GetPackageForName(InstalledPackage.Name);
}
///
/// ダウンロードを行う。
///
/// ダウンローダオブジェクト
public void Download(Downloader downloader)
{
if (! Installed) {
string url = InstalledPackage.Installer[installerIndex].Url.Href;
downloader.Download(url, InstallerFile);
// サーバ指定のファイル名に変更する
if (! string.IsNullOrEmpty(downloader.DownloadedFileName)) {
string newFile = Path.Combine(Path.GetDirectoryName(InstallerFile), downloader.DownloadedFileName);
File.Move(InstallerFile, newFile);
InstallerFile = newFile;
}
}
}
///
/// ハッシュ検証のためのハッシュの種類の数を返す
///
/// ハッシュの個数
public int GetRegisteredHashCount()
{
HashValue[] hashValues = InstalledPackage.Installer[installerIndex].Hash;
return (hashValues == null)? 0 : hashValues.Length;
}
///
/// ハッシュ検証を行う
///
/// 検証にパスしたらtrue、パスしなかったらfalse
public bool VerifyHashValues()
{
HashValue[] hashValues = InstalledPackage.Installer[installerIndex].Hash;
if (hashValues != null) {
foreach (HashValue hash in hashValues) {
if (! hash.Validate(InstallerFile)) {
return false;
}
}
}
return true;
}
private int invokeInstaller(string installerfile, InstallerType type)
{
if (! File.Exists(installerfile)) {
throw new FileNotFoundException(string.Format("{0} is not found, perhaps failed to download.", installerfile), installerfile);
}
Process hProcess = null;
try {
switch (type) {
case InstallerType.EXEC_INSTALLER:
hProcess = Process.Start(installerfile);
hProcess.WaitForExit();
break;
case InstallerType.MSI_PACKAGE:
hProcess = Process.Start("msiexec", string.Format("/i \"{0}\"", installerfile));
hProcess.WaitForExit();
break;
case InstallerType.ARCHIVE:
// TODO ハックな実装?
if (File.Exists("archive-inst.exe")) {
string argument = string.Format("-i \"{0}\" \"{1}\"", installerfile, this.InstalledPackage.Name);
ProcessStartInfo procInfo = new ProcessStartInfo(Path.GetFullPath("archive-inst.exe"), argument);
procInfo.UseShellExecute = false;
procInfo.CreateNoWindow = true;
procInfo.WorkingDirectory = Environment.CurrentDirectory;
hProcess = NaGet.Utils.ProcessStartWithOutputCapture(procInfo, this.OutputDataReceived, this.ErrorDataReceived);
hProcess.WaitForExit();
break;
}
throw new NotImplementedException("Not implemented archive installation yet");
//break;
default:
throw new NotImplementedException("Not implemented archive installation yet");
}
return hProcess.ExitCode;
} finally {
if (hProcess != null) {
hProcess.Close();
}
}
}
///
/// インストーラ等を起動してインストール作業を行う
///
/// インストーラの終了コード
public int Install()
{
string installerFile = this.InstallerFile;
string tempDir = Path.Combine(Path.GetTempPath(), Path.GetRandomFileName());
// アーカイブされているなら一旦展開
if (InstalledPackage.ArchivedInstaller) {
Directory.CreateDirectory(tempDir);
// 64bit対策で展開も別プロセスで
// TODO エラーとかの出力を取得できたりとかできるように
if (File.Exists("archive-inst.exe")) {
string argument = string.Format("-t \"{0}\" \"{1}\"", installerFile, tempDir);
ProcessStartInfo info = new ProcessStartInfo(Path.GetFullPath("archive-inst.exe"), argument);
info.UseShellExecute = false;
info.WorkingDirectory = Environment.CurrentDirectory;
using (Process hProcess = Process.Start(info)) {
hProcess.WaitForExit();
if (hProcess.ExitCode != 0) {
throw new ApplicationException("Extract error " + installerFile + " to " + tempDir);
}
}
} else {
throw new ApplicationException(string.Format("archive-inst.exe not found in {0}!", Environment.CurrentDirectory));
}
// System.Text.StringBuilder output = new System.Text.StringBuilder(1024);
// int res = NaGet.InteropServices.CommonArchiverExtracter.ExtractArchive(installerFile, tempDir, output, IntPtr.Zero);
// if (res != 0) {
// throw new ApplicationException("Extract error\n" + output.ToString());
// }
installerFile = seekInstallerFile(tempDir, InstalledPackage.Type);
if (installerFile == null && Directory.GetDirectories(tempDir).Length == 1) {
installerFile = seekInstallerFile(Path.Combine(tempDir, Directory.GetDirectories(tempDir)[0]), InstalledPackage.Type);
}
}
int exitCode = invokeInstaller(installerFile, InstalledPackage.Type);
installed = true;
return exitCode;
}
///
/// ダウンロード済みであるか否か
///
public bool Downloaded {
get {
return File.Exists(InstallerFile) && ((File.GetAttributes(InstallerFile) & FileAttributes.Hidden) != FileAttributes.Hidden);
}
}
///
/// インストール作業済みであるか否か。実際にインストールされたかどうかではありません。
///
public bool Installed {
get { return installed; }
}
///
/// インストーラの処理が成功してインストールされたプログラムが確認できるか否か。
///
public bool InstallSuccessed {
get {
switch (Type) {
case InstallerType.ARCHIVE: // アーカイブインストーラはフォルダの確認
return Directory.Exists(Path.Combine(NaGet.Env.ArchiveProgramFiles, InstalledPackage.Name));
case InstallerType.EXEC_INSTALLER:
case InstallerType.MSI_PACKAGE:
return RegistriedUninstallers.GetInstalledPackageFor(InstalledPackage) != null;
default:
return false;
}
}
}
///
/// インストーラの種類を返す
///
public InstallerType Type {
get {
return InstalledPackage.Type;
}
}
///
/// もっともふさわしいインストーラ番号を返す
///
///
public static int GetPreferInstallerIndex(Package InstalledPackage)
{
if (InstalledPackage.Type == InstallerType.CANNOT_INSTALL) { // インストール不能パッケージ
return -1;
}
int best = -1;
int bestVal = 0;
for (int i = 0; i < InstalledPackage.Installer.Length; i++) {
Platform platform = InstalledPackage.Installer[i].Platform;
int pts = 0;
if (platform != null) {
pts = (platform.IsRunnable())? 10 : 0;
pts += (platform.IsRunnableArchOnWow64() && platform.IsRunnableOS())? 1 : 0;
} else { // if (platform == null) {
pts = 1; // null の場合は動作プラットホーム扱い
}
if (pts > bestVal) {
bestVal = pts;
best = i;
}
}
return best;
}
///
/// インストーラの一時保存先パスを生成
///
private string getArchiveFilePath()
{
Package package = this.InstalledPackage;
string folderName = string.Format("{0}({1})", package.Name, package.Version);
string fileName = NaGet.Utils.Url2filename(package.Installer[0].Url.Href);
string folder = Path.Combine(NaGet.Env.ArchiveFolderPath, folderName);
if (! File.Exists(Path.Combine(folder, fileName))) {
if (Directory.Exists(folder)) {
if (! File.Exists(Path.Combine(folder, fileName))) {
fileName = seekInstallerFile(folder, package.Type) ?? fileName;
}
} else {
Directory.CreateDirectory(folder);
}
}
return Path.Combine(folder, fileName);
}
///
/// インストーラファイルを探す
///
/// 探すフォルダ
/// インストーラの種類
/// 探し出されたインストーラファイルのフルパス。存在しないならばnull
private static string seekInstallerFile(string basedir, InstallerType type)
{
if (! Directory.Exists(basedir)) {
return null;
}
System.Collections.Generic.List list = new System.Collections.Generic.List();
switch (type) {
case InstallerType.MSI_PACKAGE:
list.AddRange(Directory.GetFiles(basedir, "*.msi"));
break;
case InstallerType.EXEC_INSTALLER:
list.AddRange(Directory.GetFiles(basedir, "*.exe"));
break;
case InstallerType.ARCHIVE:
list.AddRange(Directory.GetFiles(basedir, "*.zip"));
list.AddRange(Directory.GetFiles(basedir, "*.lzh"));
list.AddRange(Directory.GetFiles(basedir, "*.cab"));
list.AddRange(Directory.GetFiles(basedir, "*.7z"));
list.AddRange(Directory.GetFiles(basedir, "*.tar*"));
break;
default:
return null;
}
// 存在しないファイルを削除
list.RemoveAll(
delegate(string file) {
return ! File.Exists(file);
}
);
// "setup"の語が入ったファイルはインストーラとみなし、優先選択
foreach (string path in list) {
if (Path.GetFileName(path).ToLower().IndexOf("setup") >= 0) {
return path;
}
}
// それ以外なら一つ目を返す
return (list.Count > 0)? list[0] : null;
}
public override string ToString()
{
return string.Format("{0}({1})", InstalledPackage.Name, InstalledPackage.Version);
}
public static string ToString(Installation[] installations)
{
string[] strs = new string[installations.Length];
for (int i = 0; i < installations.Length; i++) {
strs[i] = installations[i].ToString();
}
return string.Join(" ", strs);
}
///
/// パッケージ配列をインストール処理配列に変換する便利メソッド
///
/// パッケージ配列
/// 変換されたインストール処理配列
public static Installation[] ConvertInstallations(Package[] pkgs)
{
Installation[] insts = new Installation[pkgs.Length];
for (int i = 0; i < pkgs.Length; i++) {
insts[i] = new Installation(pkgs[i]);
}
return insts;
}
}
}