/*******************************************************************************
* Copyright (c) 2014 Mentor Graphics and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Mentor Graphics - initial API and implementation
*******************************************************************************/
package com.codesourcery.internal.installer;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.text.MessageFormat;
import java.util.ArrayList;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.Path;
import org.eclipse.swt.widgets.Shell;
import com.codesourcery.installer.IInstallPlatform;
import com.codesourcery.installer.IInstallPlatformActions;
import com.codesourcery.installer.Installer;
/**
* Install platform
*/
public class InstallPlatform implements IInstallPlatform {
/** Windows short-cut file extension */
private static final String WINDOWS_SHORTCUT_EXTENSION = "lnk";
/** Windows redistributable driver installer binary name */
private static final String WINDOWS_DPINST = "dpinst";
/** Path to install monitor binary */
private String path;
/** Path to the redistributables directory */
private String redistPath;
/** Installer process identifier */
private String installerPid;
/** Log path */
private String logPath;
/** Response file for commands */
private File responseFile = null;
/** Platform dependent actions */
private IInstallPlatformActions osActions;
/**
* Constructor
*
* @param path Path to install monitor binary
* @param redistPath Path to the directory containing redistrubutables or
* <code>null</code>
* @param installerPid Process identifier for installer
* @param logPath Path to log file or <code>null</code> to not log
* @param useResponseFile <code>true</code> to use response file
*/
public InstallPlatform(String path, String redistPath, String installerPid, String logPath) throws IOException {
this.path = path;
this.redistPath = redistPath;
this.installerPid = installerPid;
this.logPath = logPath;
// Create temporary response file to avoid command line length
// limitations
responseFile = File.createTempFile("instmon", null);
}
/**
* Returns the platform actions.
*
* @return Platform actions
*/
private IInstallPlatformActions getPlatformActions() {
if (osActions == null) {
osActions = ContributorRegistry.getDefault().getPlatformActions();
}
return osActions;
}
/**
* Returns the path to the install monitor binary.
*
* @return Path to binary
*/
private String getPath() {
return path;
}
/**
* Returns the path to the redistributables directory.
*
* @return Path to redistributable directory or <code>null</code>
*/
private String getRedistPath() {
return redistPath;
}
/**w
* Returns the installer process identifier.
*
* @return Process identifier
*/
private String getInstallerPid() {
return installerPid;
}
/**
* Returns the path to the log file.
*
* @return Path to log file or <code>null</code>
*/
private String getLogPath() {
return logPath;
}
/**
* Convenience method to run install monitor with a specified set of
* options and wait for result.
*
* @param options Options
* @return Command result
* @throws CoreException
*/
private String run(String[] options) throws CoreException {
return run(options, true);
}
/**
* Runs the install monitor with a specified set of options.
*
* @param options Options
* @param waitForResult <code>true</code> to wait for monitor and
* return result, <code>false</code> to not wait for monitor and return
* <code>null</code>.
* @return Command result
* @throws CoreException on failure
*/
private String run(String[] options, boolean waitForResult) throws CoreException {
BufferedWriter writer = null;
StreamHandler outputHandler = null;
StreamHandler errorHandler = null;
if (options.length == 0) {
return null;
}
try {
// Create response file for options to avoid command line
// length limitations
writer = new BufferedWriter(new FileWriter(responseFile, false));
for (String option : options) {
writer.write(option);
writer.newLine();
}
// Add log option
if (getLogPath() != null) {
writer.write("-log \"" + getLogPath() + "\"");
writer.newLine();
}
writer.close();
ArrayList<String> args = new ArrayList<String>();
// Monitor executable
args.add(getPath());
// Use response file to avoid any command line length limitations
args.add("-tempfile");
args.add("\"" + responseFile.getAbsolutePath() + "\"");
// Create monitor process
ProcessBuilder builder = new ProcessBuilder(args);
Process process = builder.start();
// Wait for result
if (waitForResult) {
// Get output
outputHandler = new StreamHandler(process.getInputStream());
outputHandler.start();
// Get error
errorHandler = new StreamHandler(process.getErrorStream());
errorHandler.start();
// Wait for process
process.waitFor();
// Handle error
String error = errorHandler.getOutput();
if (!error.isEmpty()) {
Installer.fail(error);
}
}
}
catch (Exception e) {
StringBuffer opts = new StringBuffer();
for (String option : options) {
opts.append(option);
opts.append(' ');
}
Installer.fail(InstallMessages.Error_MonitorCommand + opts.toString(), e);
}
finally {
if (writer != null) {
try {
writer.close();
} catch (IOException e) {
// Ignore
}
}
}
return (outputHandler != null) ? outputHandler.getOutput() : null;
}
@Override
public void dispose(String[] directories, String[] emptyDirectories)
throws CoreException
{
try {
boolean removeDirectories = false;
ArrayList<String> commands = new ArrayList<String>();
StringBuffer arg;
// Add directories
if ((directories != null) && (directories.length > 0)) {
arg = new StringBuffer("-removeDir ");
arg.append('\"');
for (int index = 0; index < directories.length; index++) {
if (index != 0) {
arg.append(',');
}
arg.append(directories[index]);
}
arg.append('\"');
commands.add(arg.toString());
removeDirectories = true;
}
// Add empty directories
if ((emptyDirectories != null) && (emptyDirectories.length > 0)) {
arg = new StringBuffer("-removeEmptyDir ");
arg.append('\"');
for (int index = 0; index < emptyDirectories.length; index++) {
if (index != 0) {
arg.append(',');
}
arg.append(emptyDirectories[index]);
}
arg.append('\"');
commands.add(arg.toString());
removeDirectories = true;
}
if (removeDirectories) {
// Add PID of installer
commands.add("-pid " + getInstallerPid());
// Add time to wait
commands.add("-wait 10");
}
// Delete platform binary
commands.add("-deleteOnExit");
run(commands.toArray(new String[commands.size()]), false);
}
catch (Exception e) {
StringBuffer dirs = new StringBuffer();
if (directories != null) {
for (String dir : directories)
dirs.append(dir);
}
if (emptyDirectories != null) {
for (String dir : emptyDirectories)
dirs.append(dir);
}
Installer.fail(InstallMessages.Error_FailedToRemoveDirectories + dirs.toString(), e);
}
}
@Override
public void deleteDirectory(String path, boolean onlyIfEmpty) throws CoreException {
run(new String[] {
(onlyIfEmpty ? "-removeEmptyDir" : "-removeDir") +
MessageFormat.format(" \"{0}\"", new Object[] {
path,
})
});
}
/**
* Sets a registry value.
*
* @param key Fully qualified registry key
* @param name Value name
* @param value Value
* @param type Value type ("string" or "dword")
* @throws CoreException on failure
* @throws UnsupportedOperationException If operation is not supported
* by operating system.
*/
private void setRegistryValue(String key, String name, String value, String type)
throws CoreException, UnsupportedOperationException {
if (!Installer.isWindows())
throw new UnsupportedOperationException();
run(new String[] {
MessageFormat.format("-regSetValue \"{0},{1},{2},{3}\"", new Object[] {
key,
name,
value,
type
})
});
}
@Override
public void setWindowsRegistryValue(String key, String name, String value)
throws CoreException, UnsupportedOperationException {
setRegistryValue(key, name, value, "string");
}
@Override
public void setWindowsRegistryValue(String key, String name, int value)
throws CoreException, UnsupportedOperationException {
setRegistryValue(key, name, Integer.toString(value), "dword");
}
@Override
public void deleteWindowsRegistryKey(String key) throws CoreException, UnsupportedOperationException {
if (!Installer.isWindows())
throw new UnsupportedOperationException();
run(new String[] {
MessageFormat.format("-regDeleteKey \"{0}\"", new Object[] {
key,
})
});
}
@Override
public void deleteWindowsRegistryValue(String key, String name)
throws CoreException, UnsupportedOperationException {
if (!Installer.isWindows())
throw new UnsupportedOperationException();
run(new String[] {
MessageFormat.format("-regDeleteValue \"{0},{1}\"", new Object[] {
key,
name
})
});
}
/**
* Returns a Windows folder identifier for a
* named folder.
*
* @param folder Folder name
* @return Folder identifier
*/
private String getWindowsFolderId(ShortcutFolder folder) {
if (folder == ShortcutFolder.PROGRAMS) {
return "CSIDL_PROGRAMS";
}
else if (folder == ShortcutFolder.DESKTOP) {
return "CSIDL_DESKTOP";
}
else {
return null;
}
}
/**
* Returns the Windows System folder. A Typical path is C:\Windows\System32.
*
* Only supported on Windows.
*
* @return Folder path
* @throws CoreException on failure
* @throws UnsupportedOperationException if operation is not supported by
* the operating system.
*/
public IPath getWindowsSystemFolder() throws CoreException, UnsupportedOperationException {
if (!Installer.isWindows())
throw new UnsupportedOperationException();
String folderId = "CSIDL_SYSTEM";
String path = run(new String[] {
MessageFormat.format("-getSpecialFolder \"{0}\"", new Object[] { folderId })
});
return new Path(path);
}
/**
* Returns the path to a special folder.
* Only supported on Windows.
*
* @param folder Folder
* @return Folder path
* @throws CoreException on failure
*/
private IPath getWindowsSpecialFolderPath(ShortcutFolder folder) throws CoreException {
String folderId = getWindowsFolderId(folder);
String path = run(new String[] {
MessageFormat.format("-getSpecialFolder \"{0}\"", new Object[] { folderId })
});
return new Path(path);
}
@Override
public void createShortcut(IPath path, String linkName,
IPath targetFile, String arguments, IPath workingDirectory, IPath iconPath,
int iconIndex)
throws CoreException {
run(new String[] {
MessageFormat.format("-createShortcut \"{0},{1},{2},{3},{4},{5},{6},{7},{8}\"", new Object[] {
path.toOSString(),
linkName,
targetFile.toOSString(),
arguments != null ? arguments : "",
"",
"-1",
workingDirectory != null ? workingDirectory.toOSString() : "",
(iconPath == null) ? targetFile.toOSString() : iconPath.toOSString(),
Integer.toString(iconIndex)
})
});
}
@Override
public void deleteShortcut(IPath path, String linkName) throws CoreException {
path = path.append(linkName);
if (Installer.isWindows())
path = path.addFileExtension(WINDOWS_SHORTCUT_EXTENSION);
if (!path.toFile().delete())
Installer.fail("Failed to delete short-cut: " + path.toOSString());
}
@Override
public String getWindowsRegistryValue(String key, String name)
throws CoreException {
return run(new String[] {
MessageFormat.format("-regGetValue \"{0},{1}\"", new Object[] { key, name })
});
}
/**
* Monitor stream handler.
*/
private class StreamHandler extends Thread {
/** Input stream */
private InputStream stream;
/** Stream buffer */
private StringBuffer buffer = new StringBuffer();
/**
* Constructor
*
* @param stream Stream
*/
public StreamHandler(InputStream stream) {
this.stream = stream;
}
/**
* Returns the current output of the stream.
*
* @return Output
*/
public String getOutput() {
return buffer.toString();
}
@Override
public void run() {
BufferedReader reader = null;
try {
InputStreamReader streamReader = new InputStreamReader(stream);
reader = new BufferedReader(streamReader);
String line = null;
while ((line = reader.readLine()) != null) {
buffer.append(line);
}
}
catch (Exception e) {
}
}
}
@Override
public IPath getShortcutFolder(ShortcutFolder folder) throws CoreException {
IPath path = null;
if (Installer.isWindows()) {
path = getWindowsSpecialFolderPath(folder);
}
else {
if (folder == ShortcutFolder.DESKTOP) {
path = new Path(System.getProperty("user.home"));
path = path.append("Desktop");
}
else if (folder == ShortcutFolder.PROGRAMS) {
path = new Path(System.getProperty("user.home"));
}
}
return path;
}
public boolean desktopIsUnity() {
// Need a more robust check here.
String session = System.getenv("DESKTOP_SESSION"); //$NON-NLS-1$
return session != null && session.contains("ubuntu"); //$NON-NLS-1$
}
@Override
public int getWindowsMajorVersion() throws CoreException, UnsupportedOperationException {
if (!Installer.isWindows())
throw new UnsupportedOperationException();
String majorVersion = run(new String[] { "-getOsProperty WIN_MAJOR_VERSION" });
if (!majorVersion.isEmpty()) {
try {
return Integer.parseInt(majorVersion);
}
catch (NumberFormatException e) {
Installer.log(e);
return -1;
}
}
else
return -1;
}
@Override
public int getWindowsMinorVersion() throws CoreException, UnsupportedOperationException {
if (!Installer.isWindows())
throw new UnsupportedOperationException();
String minorVersion = run(new String[] { "-getOsProperty WIN_MINOR_VERSION" });
if (!minorVersion.isEmpty()) {
try {
return Integer.parseInt(minorVersion);
}
catch (NumberFormatException e) {
Installer.log(e);
return -1;
}
}
else
return -1;
}
@Override
public String launchProgram(String path, String[] arguments)
throws CoreException {
StreamHandler outputHandler = null;
StreamHandler errorHandler = null;
try {
ArrayList<String> args = new ArrayList<String>();
// Add program to run
args.add(path);
// Add program arguments
for (String argument : arguments) {
args.add(argument);
}
// Launch process
ProcessBuilder builder = new ProcessBuilder(args);
Process process = builder.start();
// Get output
outputHandler = new StreamHandler(process.getInputStream());
outputHandler.start();
// Get error
errorHandler = new StreamHandler(process.getErrorStream());
errorHandler.start();
// Wait for process
process.waitFor();
}
catch (Exception e) {
Installer.fail(e.getMessage());
}
// Handle error
if (errorHandler != null) {
String error = errorHandler.getOutput();
if (!error.isEmpty()) {
Installer.fail(error);
}
}
// Return output
if (outputHandler != null) {
String output = outputHandler.getOutput();
return output;
}
else {
return "";
}
}
/**
* Returns if the installer is running on 64-bit Windows operating system.
*
* @return <code>true</code> if running on 64-bit Windows
*/
public boolean is64BitWindows() {
boolean is64Bit = false;
// Windows
if (Installer.isWindows()) {
// x86 Program Files environment variable is only set
// for 64bit Windows
is64Bit = (System.getenv("ProgramFiles(x86)") != null);
}
return is64Bit;
}
@Override
public void installWindowsDriver(String path) throws CoreException, UnsupportedOperationException {
if (!Installer.isWindows())
throw new UnsupportedOperationException();
File driverDir = new File(path);
if (!driverDir.exists())
Installer.fail(path + " was not found.");
try {
IPath redistPath = new Path(getRedistPath()).append(is64BitWindows() ? "64" : "32").
append(WINDOWS_DPINST).addFileExtension(IInstallConstants.EXTENSION_EXE);
// Run the DPINST utility with administrator rights
// /SW = Suppress the installation wizard
// /path = Path to the directory containing drivers to be installed
run(new String[] { "-runAdmin " + redistPath.toOSString() + ",/SW /path '" + path + "'" });
}
catch (Exception e) {
Installer.fail("Failed to install driver: " + path, e);
}
}
@Override
public String updateWindowsSystemEnvironment(int timeout)
throws CoreException, UnsupportedOperationException {
// Operation is only supported on Windows.
if (!Installer.isWindows())
throw new UnsupportedOperationException();
return run(new String[] { "-updateEnvironment " + Integer.toString(timeout) });
}
@Override
public void bringShellToFront(Shell shell) {
IInstallPlatformActions actions = getPlatformActions();
if (actions != null) {
actions.bringToFront(shell);
}
}
}