package jp.co.worksap.workspace.packagemanagement.chocolatey;
import static com.google.common.base.Preconditions.checkNotNull;
import java.io.File;
import java.io.IOException;
import java.util.List;
import javax.annotation.Nonnull;
import jp.co.worksap.workspace.common.PathWalker;
import jp.co.worksap.workspace.common.PathWalker.PathFindStrategy;
import jp.co.worksap.workspace.common.PipingDaemon;
import jp.co.worksap.workspace.packagemanagement.FailToInstallException;
import jp.co.worksap.workspace.packagemanagement.Package;
import jp.co.worksap.workspace.packagemanagement.PackageManager;
import lombok.extern.slf4j.Slf4j;
import com.google.common.base.Joiner;
import com.google.common.base.Optional;
import com.google.common.collect.Lists;
/**
* <p>Core class for package management of Windows.</p>
* @author Kengo TODA (toda_k@worksap.co.jp)
*/
@Slf4j public class Chocolatey implements PackageManager {
private static final Joiner COMMAND_JOINER = Joiner.on(' ');
private PackageManager nextManager;
private PathWalker pathWalker;
public Chocolatey(@Nonnull PackageManager nextManager) throws IOException {
this(nextManager, new PathFindStrategy());
}
Chocolatey(@Nonnull PackageManager nextManager, @Nonnull PathFindStrategy pathFinder) throws IOException {
this.nextManager = checkNotNull(nextManager);
this.pathWalker = new PathWalker(pathFinder);
}
@Override
public void install(@Nonnull Package packageToBeInstalled) throws IOException {
checkNotNull(packageToBeInstalled);
Optional<File> binary = findInstalledBinary();
if (!binary.isPresent()) {
throw new IllegalStateException("Chocolatey does not exist, please install it first.");
}
String packageName = packageToBeInstalled.getName();
Optional<String> version = packageToBeInstalled.getVersion();
log.debug("Installing {}:{}", packageName, version.or("latest"));
Process process = startProcess(binary, packageName, version);
try {
recordStdoutOf(process);
recordStderrOf(process);
process.getOutputStream().close();
final int statusCode = process.waitFor();
log.debug("command returned status code: {}", statusCode);
if (statusCode != 0) {
log.warn("Fail to install {}:{} (status code is {}), try to other package manager",
packageName,
version.or("latest"),
statusCode);
nextManager.install(packageToBeInstalled);
}
} catch (InterruptedException e) {
throw new FailToInstallException("Chocolatey process has been interrupted", e);
} finally {
process.destroy();
}
}
Optional<File> findInstalledBinary() throws IOException {
return pathWalker.findOnPath("choco.bat");
}
private void recordStdoutOf(final Process process) throws IOException {
Thread daemon = PipingDaemon.createThread(process.getInputStream(), "Chocolatey", "stdout");
daemon.start();
}
private void recordStderrOf(final Process process) throws IOException {
Thread daemon = PipingDaemon.createThread(process.getErrorStream(), "Chocolatey", "stderr");
daemon.start();
}
private Process startProcess(Optional<File> binary, String packageName,
Optional<String> version) throws IOException {
List<String> command = Lists.newArrayList("cmd.exe", "/C", "CALL", binary.get().getAbsolutePath(), "install", packageName);
if (version.isPresent()) {
command.add("-version");
command.add(version.get());
}
log.debug("Following command will be executed: {}", COMMAND_JOINER.join(command));
ProcessBuilder builder = new ProcessBuilder(command);
return builder.start();
}
}