679ef27a990f315434d51444cd055ffb23c995ef
[idea/community.git] / platform / util / src / com / intellij / util / Restarter.java
1 /*
2  * Copyright 2000-2014 JetBrains s.r.o.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 package com.intellij.util;
17
18 import com.intellij.openapi.application.PathManager;
19 import com.intellij.openapi.util.SystemInfo;
20 import com.intellij.openapi.util.io.FileUtilRt;
21 import com.intellij.openapi.util.io.StreamUtil;
22 import com.intellij.openapi.util.text.StringUtil;
23 import com.sun.jna.Native;
24 import com.sun.jna.Pointer;
25 import com.sun.jna.WString;
26 import com.sun.jna.ptr.IntByReference;
27 import com.sun.jna.win32.StdCallLibrary;
28 import org.jetbrains.annotations.NotNull;
29
30 import java.io.File;
31 import java.io.IOException;
32 import java.io.InputStream;
33 import java.io.OutputStream;
34 import java.util.ArrayList;
35 import java.util.Collections;
36 import java.util.List;
37
38 @SuppressWarnings({"UseOfSystemOutOrSystemErr", "CallToPrintStackTrace"})
39 public class Restarter {
40   private Restarter() {
41   }
42
43   private static int getRestartCode() {
44     String s = System.getProperty("jb.restart.code");
45     if (s != null) {
46       try {
47         return Integer.parseInt(s);
48       }
49       catch (NumberFormatException ignore) {
50       }
51     }
52     return 0;
53   }
54
55   public static boolean isSupported() {
56     return getRestartCode() != 0 || SystemInfo.isWindows || SystemInfo.isMac;
57   }
58
59   public static int scheduleRestart(@NotNull String... beforeRestart) throws IOException {
60     try {
61       int restartCode = getRestartCode();
62       if (restartCode != 0) {
63         runCommand(beforeRestart);
64         return restartCode;
65       }
66       else if (SystemInfo.isWindows) {
67         restartOnWindows(beforeRestart);
68         return 0;
69       }
70       else if (SystemInfo.isMac) {
71         restartOnMac(beforeRestart);
72         return 0;
73       }
74     }
75     catch (Throwable t) {
76       throw new IOException("Cannot restart application: " + t.getMessage(), t);
77     }
78
79     runCommand(beforeRestart);
80     throw new IOException("Cannot restart application: not supported.");
81   }
82
83   private static void runCommand(String... beforeRestart) throws IOException {
84     if (beforeRestart.length == 0) return;
85
86     try {
87       Process process = Runtime.getRuntime().exec(beforeRestart);
88
89       Thread outThread = new Thread(new StreamRedirector(process.getInputStream(), System.out));
90       Thread errThread = new Thread(new StreamRedirector(process.getErrorStream(), System.err));
91       outThread.start();
92       errThread.start();
93
94       try {
95         process.waitFor();
96       }
97       finally {
98         outThread.join();
99         errThread.join();
100       }
101     }
102     catch (InterruptedException ignore) { }
103   }
104
105   private static void restartOnWindows(@NotNull final String... beforeRestart) throws IOException {
106     Kernel32 kernel32 = (Kernel32)Native.loadLibrary("kernel32", Kernel32.class);
107     Shell32 shell32 = (Shell32)Native.loadLibrary("shell32", Shell32.class);
108
109     final int pid = kernel32.GetCurrentProcessId();
110     final IntByReference argc = new IntByReference();
111     Pointer argv_ptr = shell32.CommandLineToArgvW(kernel32.GetCommandLineW(), argc);
112     final String[] argv = argv_ptr.getStringArray(0, argc.getValue(), true);
113     kernel32.LocalFree(argv_ptr);
114
115     doScheduleRestart(new File(PathManager.getBinPath(), "restarter.exe"), new Consumer<List<String>>() {
116       @Override
117       public void consume(List<String> commands) {
118         Collections.addAll(commands, String.valueOf(pid), String.valueOf(beforeRestart.length));
119         Collections.addAll(commands, beforeRestart);
120         Collections.addAll(commands, String.valueOf(argc.getValue()));
121         Collections.addAll(commands, argv);
122       }
123     });
124
125     // Since the process ID is passed through the command line, we want to make sure that we don't exit before the "restarter"
126     // process has a chance to open the handle to our process, and that it doesn't wait for the termination of an unrelated
127     // process which happened to have the same process ID.
128     TimeoutUtil.sleep(500);
129   }
130
131   private static void restartOnMac(@NotNull final String... beforeRestart) throws IOException {
132     final String homePath = PathManager.getHomePath().substring(0, PathManager.getHomePath().indexOf( ".app" ) + 4);
133     if (!StringUtil.endsWithIgnoreCase(homePath, ".app")) throw new IOException("Application bundle not found: " + homePath);
134
135     doScheduleRestart(new File(PathManager.getBinPath(), "restarter"), new Consumer<List<String>>() {
136       @Override
137       public void consume(List<String> commands) {
138         Collections.addAll(commands, homePath);
139         Collections.addAll(commands, beforeRestart);
140       }
141     });
142   }
143
144   private static void doScheduleRestart(File restarterFile, Consumer<List<String>> argumentsBuilder) throws IOException {
145     List<String> commands = new ArrayList<String>();
146     commands.add(createTempExecutable(restarterFile).getPath());
147     argumentsBuilder.consume(commands);
148     Runtime.getRuntime().exec(commands.toArray(new String[commands.size()]));
149   }
150
151   public static File createTempExecutable(File executable) throws IOException {
152     File executableDir = new File(System.getProperty("user.home") + "/." + System.getProperty("idea.paths.selector") + "/restart");
153     File copy = new File(executableDir.getPath() + "/" + executable.getName());
154     if (!FileUtilRt.ensureCanCreateFile(copy) || (copy.exists() && !copy.delete())) {
155        String ext = FileUtilRt.getExtension(executable.getName());
156        copy = FileUtilRt.createTempFile(executableDir, FileUtilRt.getNameWithoutExtension(copy.getName()),
157                                         StringUtil.isEmptyOrSpaces(ext) ? ".tmp" : ("." + ext),
158                                         true, false);
159     }
160     FileUtilRt.copy(executable, copy);
161     if (!copy.setExecutable(executable.canExecute())) throw new IOException("Cannot make file executable: " + copy);
162     return copy;
163   }
164
165   private interface Kernel32 extends StdCallLibrary {
166     int GetCurrentProcessId();
167
168     WString GetCommandLineW();
169
170     Pointer LocalFree(Pointer pointer);
171   }
172
173   private interface Shell32 extends StdCallLibrary {
174     Pointer CommandLineToArgvW(WString command_line, IntByReference argc);
175   }
176
177   private static class StreamRedirector implements Runnable {
178     private final InputStream myIn;
179     private final OutputStream myOut;
180
181     private StreamRedirector(InputStream in, OutputStream out) {
182       myIn = in;
183       myOut = out;
184     }
185
186     @Override
187     public void run() {
188       try {
189         StreamUtil.copyStreamContent(myIn, myOut);
190       }
191       catch (IOException ignore) {
192       }
193     }
194   }
195 }