diff --git a/platform/platform-impl/src/com/intellij/ide/actions/CreateDesktopEntryAction.java b/platform/platform-impl/src/com/intellij/ide/actions/CreateDesktopEntryAction.java index 47a89fd0d6a4..e7a21de10130 100644 --- a/platform/platform-impl/src/com/intellij/ide/actions/CreateDesktopEntryAction.java +++ b/platform/platform-impl/src/com/intellij/ide/actions/CreateDesktopEntryAction.java @@ -15,8 +15,6 @@ */ package com.intellij.ide.actions; -import com.intellij.execution.ExecutionException; -import com.intellij.execution.configurations.GeneralCommandLine; import com.intellij.notification.Notification; import com.intellij.notification.NotificationType; import com.intellij.notification.Notifications; @@ -32,6 +30,7 @@ import com.intellij.openapi.progress.ProgressManager; import com.intellij.openapi.progress.Task; import com.intellij.openapi.util.SystemInfo; import com.intellij.openapi.util.io.FileUtil; +import com.intellij.openapi.util.system.ExecUtil; import com.intellij.openapi.util.text.StringUtil; import com.intellij.ui.AppUIUtil; import com.intellij.util.PlatformUtils; @@ -41,8 +40,9 @@ import org.jetbrains.annotations.Nullable; import javax.swing.*; import java.io.File; import java.io.IOException; -import java.io.InputStream; -import java.io.InputStreamReader; + +import static com.intellij.util.containers.CollectionFactory.hashMap; +import static java.util.Arrays.asList; public class CreateDesktopEntryAction extends AnAction { private static final Logger LOG = Logger.getInstance("#com.intellij.ide.actions.CreateDesktopEntryAction"); @@ -87,7 +87,8 @@ public class CreateDesktopEntryAction extends AnAction { if (!StringUtil.isEmptyOrSpaces(message)) { LOG.warn(e); Notifications.Bus.notify( - new Notification(Notifications.SYSTEM_MESSAGES_GROUP_ID, "Unable to create desktop entry", message, NotificationType.ERROR) + new Notification(Notifications.SYSTEM_MESSAGES_GROUP_ID, "Failed to create desktop entry", message, NotificationType.ERROR), + event.getProject() ); } else { @@ -98,19 +99,9 @@ public class CreateDesktopEntryAction extends AnAction { }); } - private static void check() { - final GeneralCommandLine cmd = new GeneralCommandLine(); - cmd.setExePath("which"); - cmd.addParameter("xdg-desktop-menu"); - - try { - final Process process = cmd.createProcess(); - final int result = process.waitFor(); - if (result != 0) throw new Exception("'" + cmd.getCommandLineString() + "' : " + result); - } - catch (Exception e) { - throw new RuntimeException(ApplicationBundle.message("desktop.entry.xdg.missing"), e); - } + private static void check() throws IOException, InterruptedException { + final int result = ExecUtil.execAndGetResult("which", "xdg-desktop-menu"); + if (result != 0) throw new RuntimeException(ApplicationBundle.message("desktop.entry.xdg.missing")); } private static File prepare() throws IOException { @@ -134,19 +125,13 @@ public class CreateDesktopEntryAction extends AnAction { final String wmClass = AppUIUtil.getFrameClass(); - final InputStream stream = CreateDesktopEntryAction.class.getClassLoader().getResourceAsStream("entry.desktop"); - assert stream != null : CreateDesktopEntryAction.class.getClassLoader(); - @SuppressWarnings("IOResourceOpenedButNotSafelyClosed") - String content = FileUtil.loadTextAndClose(new InputStreamReader(stream)); - content = StringUtil.replace(content, "$NAME$", name); - content = StringUtil.replace(content, "$SCRIPT$", execPath); - content = StringUtil.replace(content, "$ICON$", iconPath); - content = StringUtil.replace(content, "$WM_CLASS$", wmClass); + final String content = ExecUtil.loadTemplate(CreateDesktopEntryAction.class.getClassLoader(), "entry.desktop", + hashMap(asList("$NAME$", "$SCRIPT$", "$ICON$", "$WM_CLASS$"), + asList(name, execPath, iconPath, wmClass))); final String entryName = wmClass + ".desktop"; final File entryFile = new File(FileUtil.getTempDirectory(), entryName); FileUtil.writeToFile(entryFile, content); - return entryFile; } @@ -193,14 +178,10 @@ public class CreateDesktopEntryAction extends AnAction { return null; } - private static void install(final File entryFile) throws ExecutionException, InterruptedException { - final GeneralCommandLine cmd = new GeneralCommandLine(); - cmd.setExePath("xdg-desktop-menu"); - cmd.addParameters("install", "--mode", "user", entryFile.getAbsolutePath()); + private static void install(final File entryFile) throws IOException, InterruptedException { try { - final Process process = cmd.createProcess(); - final int result = process.waitFor(); - if (result != 0) throw new RuntimeException("'" + cmd.getCommandLineString() + "' : " + result); + final int result = ExecUtil.execAndGetResult("xdg-desktop-menu", "install", "--mode", "user", entryFile.getAbsolutePath()); + if (result != 0) throw new RuntimeException("'" + entryFile.getAbsolutePath() + "' : " + result); } finally { if (!entryFile.delete()) LOG.error("Failed to delete temp file '" + entryFile + "'"); diff --git a/platform/platform-impl/src/com/intellij/ide/actions/CreateLauncherScriptAction.java b/platform/platform-impl/src/com/intellij/ide/actions/CreateLauncherScriptAction.java index 2860c4753c9d..a5f0f75118d4 100644 --- a/platform/platform-impl/src/com/intellij/ide/actions/CreateLauncherScriptAction.java +++ b/platform/platform-impl/src/com/intellij/ide/actions/CreateLauncherScriptAction.java @@ -1,5 +1,5 @@ /* - * Copyright 2000-2010 JetBrains s.r.o. + * Copyright 2000-2011 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. @@ -15,11 +15,12 @@ */ package com.intellij.ide.actions; -import com.intellij.execution.ExecutionException; -import com.intellij.execution.configurations.GeneralCommandLine; +import com.intellij.notification.Notification; +import com.intellij.notification.NotificationType; +import com.intellij.notification.Notifications; import com.intellij.openapi.actionSystem.AnAction; import com.intellij.openapi.actionSystem.AnActionEvent; -import com.intellij.openapi.actionSystem.PlatformDataKeys; +import com.intellij.openapi.application.ApplicationBundle; import com.intellij.openapi.application.ApplicationNamesInfo; import com.intellij.openapi.application.PathManager; import com.intellij.openapi.diagnostic.Logger; @@ -27,18 +28,15 @@ import com.intellij.openapi.project.Project; import com.intellij.openapi.ui.DialogWrapper; import com.intellij.openapi.ui.Messages; import com.intellij.openapi.util.SystemInfo; -import com.intellij.openapi.util.io.FileUtil; +import com.intellij.openapi.util.system.ExecUtil; import com.intellij.openapi.util.text.StringUtil; -import com.intellij.openapi.vfs.CharsetToolkit; -import javax.script.ScriptEngine; -import javax.script.ScriptEngineManager; -import javax.script.ScriptException; import javax.swing.*; import java.io.File; import java.io.IOException; -import java.io.InputStream; -import java.io.InputStreamReader; + +import static com.intellij.util.containers.CollectionFactory.hashMap; +import static java.util.Arrays.asList; /** * @author yole @@ -48,8 +46,7 @@ public class CreateLauncherScriptAction extends AnAction { @Override public void actionPerformed(AnActionEvent e) { - Project project = e.getData(PlatformDataKeys.PROJECT); - showDialog(project); + showDialog(e.getProject()); } public static void showDialog(Project project) { @@ -67,7 +64,7 @@ public class CreateLauncherScriptAction extends AnAction { final File scriptFile = createLauncherScriptFile(); final File scriptTarget = new File(pathName); if (scriptTarget.exists()) { - int rc = Messages.showOkCancelDialog(project, "The file " + scriptTarget + " already exists. Would you like to overwrite it?", + int rc = Messages.showOkCancelDialog(project, ApplicationBundle.message("launcher.script.overwrite", scriptTarget), "Create Launcher Script", Messages.getQuestionIcon()); if (rc != 0) { return; @@ -79,94 +76,47 @@ public class CreateLauncherScriptAction extends AnAction { !scriptFile.renameTo(scriptTarget)) { final String launcherScriptContainingDirPath = launcherScriptContainingDir.getCanonicalPath(); final String installationScriptSrc = + "#!/bin/sh\n" + // create all intermediate folders - "mkdir -p " + launcherScriptContainingDirPath + "\n" + - // Copy file & change owner to root - // uid 0 = root - // gid 0 = root || wheel (MacOS) - "install -g 0 -o 0 " + scriptFile.getCanonicalPath() + " " + pathName; - final File installationScript = createTempExecutableScript("launcher_installer", installationScriptSrc); - - sudo(installationScript.getCanonicalPath(), - installationScriptSrc, - "Please enter your password to create a launcher script in a " + launcherScriptContainingDirPath); + "mkdir -p \"" + launcherScriptContainingDirPath + "\"\n" + + // copy file and change ownership to root (UID 0 = root, GID 0 = root (wheel on Macs)) + "install -g 0 -o 0 \"" + scriptFile.getCanonicalPath() + "\" \"" + pathName + "\""; + final File installationScript = ExecUtil.createTempExecutableScript("launcher_installer", ".sh", installationScriptSrc); + ExecUtil.sudo(installationScript.getAbsolutePath(), + ApplicationBundle.message("launcher.script.sudo.prompt", launcherScriptContainingDirPath)); } } catch (Exception e) { - LOG.info(e); - Messages.showErrorDialog(project, "Failed to create launcher script: " + e.getMessage(), "Create Launcher Script"); - } - } - - private static boolean sudo(final String installationScriptPath, - final String installScriptSrc, - final String prompt) throws IOException, ScriptException, ExecutionException { - if (SystemInfo.isMac) { - final ScriptEngine engine = new ScriptEngineManager(null).getEngineByName("AppleScript"); - if (engine == null) { - throw new IOException("Could not find AppleScript engine"); - } - engine.eval("do shell script \"" + installationScriptPath + "\" with administrator privileges"); - } - else { - GeneralCommandLine cmdLine = new GeneralCommandLine(); - if (SystemInfo.isGnome) { - cmdLine.setExePath("gksudo"); - cmdLine.addParameters("--message", prompt); - } - else if (SystemInfo.isKDE) { - cmdLine.setExePath("kdesudo"); + final String message = e.getMessage(); + if (!StringUtil.isEmptyOrSpaces(message)) { + LOG.warn(e); + Notifications.Bus.notify( + new Notification(Notifications.SYSTEM_MESSAGES_GROUP_ID, "Failed to create launcher script", message, NotificationType.ERROR), + project + ); } else { - Messages.showMessageDialog("Unsupported graphical environment. Please execute the following command from the shell:\n" + installScriptSrc, - "Create Launcher Script", - Messages.getInformationIcon()); - return true; + LOG.error(e); } - cmdLine.addParameter(installationScriptPath); - cmdLine.createProcess(); } - return false; } private static File createLauncherScriptFile() throws IOException { - final File tempFile = FileUtil.createTempFile("launcher", ""); - - final InputStream stream = CreateLauncherScriptAction.class.getClassLoader().getResourceAsStream("launcher.py"); - @SuppressWarnings("IOResourceOpenedButNotSafelyClosed") - String launcherContents = FileUtil.loadTextAndClose(new InputStreamReader(stream)); - launcherContents = launcherContents.replace("$CONFIG_PATH$", PathManager.getConfigPath()); - - String homePath = PathManager.getHomePath(); - if (SystemInfo.isMac) { - // Just use "*.app" - launcherContents = launcherContents.replace("$RUN_PATH$", homePath); + String runPath = PathManager.getHomePath(); + if (!SystemInfo.isMac) { + // for Macs just use "*.app" + final String productName = ApplicationNamesInfo.getInstance().getProductName().toLowerCase(); + runPath += "/bin/" + productName + ".sh"; } - else { - String productName = ApplicationNamesInfo.getInstance().getProductName().toLowerCase(); - launcherContents = launcherContents.replace("$RUN_PATH$", homePath + "/bin/" + productName + ".sh"); - } - - FileUtil.writeToFile(tempFile, launcherContents.getBytes(CharsetToolkit.UTF8_CHARSET)); - if (!tempFile.setExecutable(true)) { - throw new IOException("Failed to mark the launcher script as executable"); - } - return tempFile; - } - - private static File createTempExecutableScript(final String fileNamePrefix, - final String source) throws IOException { - final File tempFile = FileUtil.createTempFile(fileNamePrefix, ""); - FileUtil.writeToFile(tempFile, source.getBytes(CharsetToolkit.UTF8_CHARSET)); - if (!tempFile.setExecutable(true)) { - throw new IOException("Failed to mark the launcher installation script as executable: script path " + tempFile.getCanonicalPath()); - } - return tempFile; + final String launcherContents = ExecUtil.loadTemplate(CreateLauncherScriptAction.class.getClassLoader(), "launcher.py", + hashMap(asList("$CONFIG_PATH$", "$RUN_PATH$"), + asList(PathManager.getConfigPath(), runPath))); + return ExecUtil.createTempExecutableScript("launcher", "", launcherContents); } public static String defaultScriptName() { - final String productName = ApplicationNamesInfo.getInstance().getScriptName(); - return StringUtil.isEmptyOrSpaces(productName) ? "idea" : productName; + final String scriptName = ApplicationNamesInfo.getInstance().getScriptName(); + return StringUtil.isEmptyOrSpaces(scriptName) ? "idea" : scriptName; } @Override diff --git a/platform/platform-resources-en/src/messages/ApplicationBundle.properties b/platform/platform-resources-en/src/messages/ApplicationBundle.properties index 49f724906661..d42c7daab8e7 100644 --- a/platform/platform-resources-en/src/messages/ApplicationBundle.properties +++ b/platform/platform-resources-en/src/messages/ApplicationBundle.properties @@ -532,6 +532,8 @@ auto.import=Auto Import checkbox.collapse.suppress.warnings=@SuppressWarnings checkbox.collapse.end.of.line.comments=End of line comments sequence title.other.languages=Other Languages +launcher.script.overwrite=The file ''{0}'' already exists. Would you like to overwrite it? +launcher.script.sudo.prompt=Please enter your password to create a launcher script in a ''{0}'' desktop.entry.success=You may now exit {0} and start it from the system menu. desktop.entry.xdg.missing=Unfortunately, xdg-utils seem to be unavailable on your system. desktop.entry.icon.missing=Unable to locate suitable application icon in ''{0}''. diff --git a/platform/util/src/com/intellij/openapi/util/io/FileUtil.java b/platform/util/src/com/intellij/openapi/util/io/FileUtil.java index 18008f5dc41d..0bad8df44f64 100644 --- a/platform/util/src/com/intellij/openapi/util/io/FileUtil.java +++ b/platform/util/src/com/intellij/openapi/util/io/FileUtil.java @@ -262,6 +262,12 @@ public class FileUtil { return buffer.toByteArray(); } + @NotNull + public static String loadTextAndClose(@NotNull InputStream stream) throws IOException { + //noinspection IOResourceOpenedButNotSafelyClosed + return loadTextAndClose(new InputStreamReader(stream)); + } + @NotNull public static String loadTextAndClose(@NotNull Reader reader) throws IOException { try { diff --git a/platform/util/src/com/intellij/openapi/util/system/ExecUtil.java b/platform/util/src/com/intellij/openapi/util/system/ExecUtil.java new file mode 100644 index 000000000000..61d6f8a94432 --- /dev/null +++ b/platform/util/src/com/intellij/openapi/util/system/ExecUtil.java @@ -0,0 +1,110 @@ +/* + * Copyright 2000-2011 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.openapi.util.system; + +import com.intellij.openapi.util.SystemInfo; +import com.intellij.openapi.util.io.FileUtil; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import javax.script.ScriptEngine; +import javax.script.ScriptEngineManager; +import javax.script.ScriptException; +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.util.Arrays; +import java.util.List; +import java.util.Map; + +public class ExecUtil { + private ExecUtil() { } + + public static int execAndGetResult(final String... command) throws IOException, InterruptedException { + assert command != null && command.length > 0; + return execAndGetResult(Arrays.asList(command)); + } + + public static int execAndGetResult(@NotNull final List command) throws IOException, InterruptedException { + assert command.size() > 0; + final ProcessBuilder processBuilder = new ProcessBuilder(command); + final Process process = processBuilder.start(); + return process.waitFor(); + } + + public static String loadTemplate(@NotNull final ClassLoader loader, + @NotNull final String templateName, + @Nullable final Map variables) throws IOException { + final InputStream stream = loader.getResourceAsStream(templateName); + if (stream == null) { + throw new IOException("Template '" + templateName + "' not found by " + loader); + } + + final StringBuilder template = new StringBuilder(FileUtil.loadTextAndClose(stream)); + if (variables != null) { + for (Map.Entry var : variables.entrySet()) { + final String name = var.getKey(); + final int pos = template.indexOf(name); + if (pos >= 0) { + template.replace(pos, pos + name.length(), var.getValue()); + } + } + } + return template.toString(); + } + + public static File createTempExecutableScript(@NotNull final String prefix, + @NotNull final String suffix, + @NotNull final String source) throws IOException { + final File tempFile = FileUtil.createTempFile(prefix, suffix); + FileUtil.writeToFile(tempFile, source); + if (!tempFile.setExecutable(true, true)) { + throw new IOException("Failed to make temp file executable: " + tempFile); + } + return tempFile; + } + + public static int sudo(@NotNull final String scriptPath, + @NotNull final String prompt) throws IOException, ScriptException, InterruptedException { + if (SystemInfo.isMac) { + final ScriptEngine engine = new ScriptEngineManager(null).getEngineByName("AppleScript"); + if (engine == null) { + throw new IOException("Could not find AppleScript engine"); + } + engine.eval("do shell script \"" + scriptPath + "\" with administrator privileges"); + return 0; + } + else if (SystemInfo.isKDE) { + return execAndGetResult("kdesudo", "--comment", prompt, scriptPath); + } + else if (SystemInfo.isGnome) { + return execAndGetResult("gksudo", "--message", prompt, scriptPath); + } + else if (SystemInfo.isUnix) { + final File sudo = createTempExecutableScript("sudo", ".sh", + "#!/bin/sh\n" + + "echo \"" + prompt + "\"\n" + + "echo\n" + + "sudo \"" + scriptPath + "\"\n" + + "echo\n" + + "read -p \"Press Enter to close this window...\" TEMP\n"); + return execAndGetResult("xterm", "-T", "Install", "-e", sudo.getAbsolutePath()); + } + else { + throw new UnsupportedOperationException("Unsupported OS/desktop: " + System.getProperty("os.name") + '/' + SystemInfo.SUN_DESKTOP); + } + } +} diff --git a/platform/util/src/com/intellij/util/containers/CollectionFactory.java b/platform/util/src/com/intellij/util/containers/CollectionFactory.java index 66c858f4b8e9..9028cff937f4 100644 --- a/platform/util/src/com/intellij/util/containers/CollectionFactory.java +++ b/platform/util/src/com/intellij/util/containers/CollectionFactory.java @@ -1,5 +1,5 @@ /* - * Copyright 2000-2009 JetBrains s.r.o. + * Copyright 2000-2011 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. @@ -23,6 +23,7 @@ import gnu.trove.THashSet; import org.jetbrains.annotations.NotNull; import java.util.*; +import java.util.HashMap; import java.util.Stack; /** @@ -91,6 +92,18 @@ public class CollectionFactory { return Maps.newHashMap(); } + public static Map hashMap(@NotNull final List keys, @NotNull final List values) { + if (keys.size() != values.size()) { + throw new IllegalArgumentException(keys + " should have some length as " + values); + } + + final HashMap map = Maps.newHashMap(); + for (int i = 0; i < keys.size(); ++i) { + map.put(keys.get(i), values.get(i)); + } + return map; + } + public static LinkedHashMap linkedMap() { return Maps.newLinkedHashMap(); }