blob: 1eb1691808bee74d62e9f3db2a89f7528ccfe10c [file] [log] [blame]
/*
* Copyright 2000-2013 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.intellij.util;
import com.intellij.openapi.application.PathManager;
import com.intellij.openapi.util.SystemInfo;
import com.intellij.openapi.util.io.FileUtilRt;
import com.intellij.openapi.util.io.StreamUtil;
import com.intellij.openapi.util.text.StringUtil;
import com.sun.jna.Native;
import com.sun.jna.Pointer;
import com.sun.jna.WString;
import com.sun.jna.ptr.IntByReference;
import com.sun.jna.win32.StdCallLibrary;
import org.jetbrains.annotations.NotNull;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
@SuppressWarnings({"UseOfSystemOutOrSystemErr", "CallToPrintStackTrace"})
public class Restarter {
private Restarter() {
}
private static int getRestartCode() {
String s = System.getProperty("jb.restart.code");
if (s != null) {
try {
return Integer.parseInt(s);
}
catch (NumberFormatException ignore) {
}
}
return 0;
}
public static boolean isSupported() {
return getRestartCode() != 0 || SystemInfo.isWindows || SystemInfo.isMac;
}
public static int scheduleRestart(@NotNull String... beforeRestart) throws IOException {
try {
int restartCode = getRestartCode();
if (restartCode != 0) {
runCommand(beforeRestart);
return restartCode;
}
else if (SystemInfo.isWindows) {
restartOnWindows(beforeRestart);
return 0;
}
else if (SystemInfo.isMac) {
restartOnMac(beforeRestart);
return 0;
}
}
catch (Throwable t) {
throw new IOException("Cannot restart application: " + t.getMessage(), t);
}
runCommand(beforeRestart);
throw new IOException("Cannot restart application: not supported.");
}
private static void runCommand(String... beforeRestart) throws IOException {
if (beforeRestart.length == 0) return;
try {
Process process = Runtime.getRuntime().exec(beforeRestart);
Thread outThread = new Thread(new StreamRedirector(process.getInputStream(), System.out));
Thread errThread = new Thread(new StreamRedirector(process.getErrorStream(), System.err));
outThread.start();
errThread.start();
try {
process.waitFor();
}
finally {
outThread.join();
errThread.join();
}
}
catch (InterruptedException ignore) { }
}
private static void restartOnWindows(@NotNull final String... beforeRestart) throws IOException {
Kernel32 kernel32 = (Kernel32)Native.loadLibrary("kernel32", Kernel32.class);
Shell32 shell32 = (Shell32)Native.loadLibrary("shell32", Shell32.class);
final int pid = kernel32.GetCurrentProcessId();
final IntByReference argc = new IntByReference();
Pointer argv_ptr = shell32.CommandLineToArgvW(kernel32.GetCommandLineW(), argc);
final String[] argv = argv_ptr.getStringArray(0, argc.getValue(), true);
kernel32.LocalFree(argv_ptr);
doScheduleRestart(new File(PathManager.getBinPath(), "restarter.exe"), new Consumer<List<String>>() {
@Override
public void consume(List<String> commands) {
Collections.addAll(commands, String.valueOf(pid), String.valueOf(beforeRestart.length));
Collections.addAll(commands, beforeRestart);
Collections.addAll(commands, String.valueOf(argc.getValue()));
Collections.addAll(commands, argv);
}
});
// Since the process ID is passed through the command line, we want to make sure that we don't exit before the "restarter"
// process has a chance to open the handle to our process, and that it doesn't wait for the termination of an unrelated
// process which happened to have the same process ID.
try {
Thread.sleep(500);
}
catch (InterruptedException ignore) {
}
}
private static void restartOnMac(@NotNull final String... beforeRestart) throws IOException {
final String homePath = PathManager.getHomePath().substring(0, PathManager.getHomePath().indexOf( ".app" ) + 4);
if (!StringUtil.endsWithIgnoreCase(homePath, ".app")) throw new IOException("Application bundle not found: " + homePath);
doScheduleRestart(new File(PathManager.getBinPath(), "restarter"), new Consumer<List<String>>() {
@Override
public void consume(List<String> commands) {
Collections.addAll(commands, homePath);
Collections.addAll(commands, beforeRestart);
}
});
}
private static void doScheduleRestart(File restarterFile, Consumer<List<String>> argumentsBuilder) throws IOException {
List<String> commands = new ArrayList<String>();
commands.add(createTempExecutable(restarterFile).getPath());
argumentsBuilder.consume(commands);
Runtime.getRuntime().exec(commands.toArray(new String[commands.size()]));
}
public static File createTempExecutable(File executable) throws IOException {
File executableDir = new File(System.getProperty("user.home") + "/." + System.getProperty("idea.paths.selector") + "/restart");
File copy = new File(executableDir.getPath() + "/" + executable.getName());
if (!FileUtilRt.ensureCanCreateFile(copy)) {
String ext = FileUtilRt.getExtension(executable.getName());
copy = FileUtilRt.createTempFile(executableDir, FileUtilRt.getNameWithoutExtension(copy.getName()),
StringUtil.isEmptyOrSpaces(ext) ? ".tmp" : ("." + ext),
true, false);
}
FileUtilRt.copy(executable, copy);
if (!copy.setExecutable(executable.canExecute())) throw new IOException("Cannot make file executable: " + copy);
return copy;
}
private interface Kernel32 extends StdCallLibrary {
int GetCurrentProcessId();
WString GetCommandLineW();
Pointer LocalFree(Pointer pointer);
}
private interface Shell32 extends StdCallLibrary {
Pointer CommandLineToArgvW(WString command_line, IntByReference argc);
}
private static class StreamRedirector implements Runnable {
private final InputStream myIn;
private final OutputStream myOut;
private StreamRedirector(InputStream in, OutputStream out) {
myIn = in;
myOut = out;
}
@Override
public void run() {
try {
StreamUtil.copyStreamContent(myIn, myOut);
}
catch (IOException ignore) {
}
}
}
}