diff --git a/bundles/org.eclipse.ui.ide.application/META-INF/MANIFEST.MF b/bundles/org.eclipse.ui.ide.application/META-INF/MANIFEST.MF index 85b7c3d682c6..7c3f352aad7e 100644 --- a/bundles/org.eclipse.ui.ide.application/META-INF/MANIFEST.MF +++ b/bundles/org.eclipse.ui.ide.application/META-INF/MANIFEST.MF @@ -8,7 +8,7 @@ Bundle-Localization: plugin Require-Bundle: org.eclipse.ui.ide;bundle-version="[3.21.0,4.0.0)", org.eclipse.core.runtime;bundle-version="[3.29.0,4.0.0)", org.eclipse.core.resources;bundle-version="[3.19.0,4.0.0)", - org.eclipse.ui;bundle-version="[3.208.0,4.0.0)", + org.eclipse.ui;bundle-version="[3.209.0,4.0.0)", org.eclipse.ui.navigator.resources;bundle-version="[3.9.0,4.0.0)", org.eclipse.core.net;bundle-version="[1.5.0,2.0.0)", org.eclipse.core.filesystem;bundle-version="1.10.0", diff --git a/bundles/org.eclipse.ui.ide.application/src/org/eclipse/ui/internal/ide/application/IDEApplication.java b/bundles/org.eclipse.ui.ide.application/src/org/eclipse/ui/internal/ide/application/IDEApplication.java index de109fddcea5..a4bc366a90fa 100644 --- a/bundles/org.eclipse.ui.ide.application/src/org/eclipse/ui/internal/ide/application/IDEApplication.java +++ b/bundles/org.eclipse.ui.ide.application/src/org/eclipse/ui/internal/ide/application/IDEApplication.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2003, 2020 IBM Corporation and others. + * Copyright (c) 2003, 2025 IBM Corporation and others. * * This program and the accompanying materials * are made available under the terms of the Eclipse Public License 2.0 @@ -24,11 +24,9 @@ import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; -import java.io.InputStream; import java.io.OutputStream; import java.net.InetAddress; import java.net.MalformedURLException; -import java.net.URISyntaxException; import java.net.URL; import java.nio.file.Files; import java.nio.file.Path; @@ -42,7 +40,6 @@ import org.eclipse.core.runtime.OperationCanceledException; import org.eclipse.core.runtime.Platform; import org.eclipse.core.runtime.Status; -import org.eclipse.core.runtime.URIUtil; import org.eclipse.core.runtime.jobs.Job; import org.eclipse.core.runtime.preferences.ConfigurationScope; import org.eclipse.equinox.app.IApplication; @@ -65,6 +62,7 @@ import org.eclipse.ui.PlatformUI; import org.eclipse.ui.internal.Workbench; import org.eclipse.ui.internal.WorkbenchPlugin; +import org.eclipse.ui.internal.WorkspaceLock; import org.eclipse.ui.internal.ide.ChooseWorkspaceData; import org.eclipse.ui.internal.ide.ChooseWorkspaceDialog; import org.eclipse.ui.internal.ide.IDEInternalPreferences; @@ -89,19 +87,10 @@ public class IDEApplication implements IApplication, IExecutableExtension { private static final String VERSION_FILENAME = "version.ini"; //$NON-NLS-1$ - private static final Path LOCK_INFO_FILE = Path.of(METADATA_FOLDER, ".lock_info"); //$NON-NLS-1$ - private static final String DISPLAY_VAR = "DISPLAY"; //$NON-NLS-1$ private static final String HOST_NAME_VAR = "HOSTNAME"; //$NON-NLS-1$ - private static final String PROCESS_ID = "process-id"; //$NON-NLS-1$ - - private static final String DISPLAY = "display"; //$NON-NLS-1$ - - private static final String HOST = "host"; //$NON-NLS-1$ - - private static final String USER = "user"; //$NON-NLS-1$ private static final String USER_NAME = "user.name"; //$NON-NLS-1$ @@ -223,12 +212,13 @@ public void setInitializationData(IConfigurationElement config, } /** - * Return null if a valid workspace path has been set and an exit code otherwise. - * Prompt for and set the path if possible and required. + * Returns null if a valid workspace has been selected or locked + * successfully, and an exit code otherwise. Prompts for and sets the workspace + * path if required. * * @param applicationArguments the command line arguments - * @return null if a valid instance location has been set and an exit code - * otherwise + * @return null if a valid instance location has been set and an + * exit code otherwise */ @SuppressWarnings("rawtypes") protected Object checkInstanceLocation(Shell shell, Map applicationArguments) { @@ -273,29 +263,29 @@ protected Object checkInstanceLocation(Shell shell, Map applicationArguments) { return EXIT_WORKSPACE_LOCKED; } - String wsLockedError = NLS.bind(IDEWorkbenchMessages.IDEApplication_workspaceCannotLockMessage, - workspaceDirectory.getAbsolutePath()); // check if there is a lock info then append it to error message. - String lockInfo = getWorkspaceLockInfo(instanceLoc.getURL()); - if (lockInfo != null && !lockInfo.isBlank()) { - wsLockedError = wsLockedError + System.lineSeparator() + System.lineSeparator() - + NLS.bind(IDEWorkbenchMessages.IDEApplication_Ws_Lock_Owner_Message, lockInfo); + String lockInfo = WorkspaceLock.getWorkspaceLockDetails(instanceLoc.getURL()); + if (lockInfo != null) { + hideSplash(shell); + WorkspaceLock.showWorkspaceLockedDialog(shell, workspaceDirectory.getAbsolutePath(), + lockInfo); + } else { + hideSplash(shell); + MessageDialog.openError(shell, + IDEWorkbenchMessages.IDEApplication_workspaceCannotBeSetTitle, + IDEWorkbenchMessages.IDEApplication_workspaceCannotBeSetMessage); } - MessageDialog.openError( - shell, - IDEWorkbenchMessages.IDEApplication_workspaceCannotLockTitle, - wsLockedError); } else { + hideSplash(shell); MessageDialog.openError( shell, IDEWorkbenchMessages.IDEApplication_workspaceCannotBeSetTitle, IDEWorkbenchMessages.IDEApplication_workspaceCannotBeSetMessage); } } catch (IOException e) { - IDEWorkbenchPlugin.log("Could not obtain lock for workspace location", //$NON-NLS-1$ - e); - MessageDialog - .openError( + hideSplash(shell); + IDEWorkbenchPlugin.log("Could not obtain lock for workspace location", e); //$NON-NLS-1$ + MessageDialog.openError( shell, IDEWorkbenchMessages.InternalError, e.getMessage()); @@ -311,12 +301,10 @@ protected Object checkInstanceLocation(Shell shell, Map applicationArguments) { // -data is specified but invalid according to checkValidWorkspace(): re-launch ChooseWorkspaceData launchData = new ChooseWorkspaceData(instanceLoc.getDefault()); - boolean parentShellVisible = false; if (isValid(shell)) { - parentShellVisible = shell.getVisible(); // bug 455162, bug 427393: hide the splash if the workspace // prompt dialog should be opened - if (parentShellVisible && launchData.getShowDialog()) { + if (shell.getVisible() && launchData.getShowDialog()) { shell.setVisible(false); } } @@ -349,7 +337,15 @@ protected Object checkInstanceLocation(Shell shell, Map applicationArguments) { try { if (instanceLoc.isSet()) { // restart with new location - return Workbench.setRestartArguments(workspaceUrl.getFile()); + Object workbenchExitCode = Workbench.setRestartArguments(workspaceUrl.getFile()); + // "null" workbenchExitCode indicates an error: application is running in + // development mode and doesn't support restart. + if (workbenchExitCode == null) { + // No error to show, it was shown in Workbench.setRestartArguments() already. + // Just exit. + return EXIT_OK; + } + return workbenchExitCode; } // the operation will fail if the url is not a valid @@ -360,25 +356,18 @@ protected Object checkInstanceLocation(Shell shell, Map applicationArguments) { writeWsLockInfo(instanceLoc.getURL()); return null; } - } catch (IllegalStateException e) { + } catch (IOException | IllegalStateException e) { MessageDialog .openError( shell, IDEWorkbenchMessages.IDEApplication_workspaceCannotBeSetTitle, IDEWorkbenchMessages.IDEApplication_workspaceCannotBeSetMessage); - return EXIT_OK; - } catch (IOException e) { - MessageDialog - .openError( - shell, - IDEWorkbenchMessages.IDEApplication_workspaceCannotBeSetTitle, - IDEWorkbenchMessages.IDEApplication_workspaceCannotBeSetMessage); } // by this point it has been determined that the workspace is // already in use -- force the user to choose again - String lockInfo = getWorkspaceLockInfo(workspaceUrl); + String lockInfo = WorkspaceLock.getWorkspaceLockDetails(workspaceUrl); MessageDialog dialog = new MessageDialog(null, IDEWorkbenchMessages.IDEApplication_workspaceInUseTitle, null, NLS.bind(IDEWorkbenchMessages.IDEApplication_workspaceInUseMessage, workspaceUrl.getFile()), @@ -412,49 +401,6 @@ protected Control createCustomArea(Composite parent) { } } - /** - * Read workspace lock file and parse all the properties present. Based on the - * eclipse version and operating system some or all the properties may not - * present. In such scenario it will return empty string. - * - * @return Previous lock owner details. - */ - protected String getWorkspaceLockInfo(URL workspaceUrl) { - try { - Path lockFile = getLockInfoFile(workspaceUrl); - if (!Files.exists(lockFile)) { - return null; - } - - StringBuilder sb = new StringBuilder(); - Properties props = new Properties(); - try (InputStream is = Files.newInputStream(lockFile)) { - props.load(is); - String prop = props.getProperty(USER); - if (prop != null) { - sb.append(NLS.bind(IDEWorkbenchMessages.IDEApplication_Ws_Lock_Owner_User, prop)); - } - prop = props.getProperty(HOST); - if (prop != null) { - sb.append(NLS.bind(IDEWorkbenchMessages.IDEApplication_Ws_Lock_Owner_Host, prop)); - } - prop = props.getProperty(DISPLAY); - if (prop != null) { - sb.append(NLS.bind(IDEWorkbenchMessages.IDEApplication_Ws_Lock_Owner_Disp, prop)); - } - prop = props.getProperty(PROCESS_ID); - if (prop != null) { - sb.append(NLS.bind(IDEWorkbenchMessages.IDEApplication_Ws_Lock_Owner_P_Id, prop)); - } - return sb.toString(); - } - } catch (Exception e) { - IDEWorkbenchPlugin.log("Could not read lock info file: ", e); //$NON-NLS-1$ - - } - return null; - } - /** * Write lock owner details onto workspace lock file. Data includes user, host, * display and current java process id. @@ -464,19 +410,19 @@ protected void writeWsLockInfo(URL workspaceUrl) { String user = System.getProperty(USER_NAME); if (user != null) { - props.setProperty(USER, user); + props.setProperty(WorkspaceLock.USER, user); } String host = getHostName(); if (host != null) { - props.setProperty(HOST, host); + props.setProperty(WorkspaceLock.HOST, host); } String display = getDisplay(); if (display != null) { - props.setProperty(DISPLAY, display); + props.setProperty(WorkspaceLock.DISPLAY, display); } String pid = getProcessId(); if (pid != null) { - props.setProperty(PROCESS_ID, pid); + props.setProperty(WorkspaceLock.PROCESS_ID, pid); } if (props.isEmpty()) { @@ -532,20 +478,6 @@ private String getHostName() { return hostName; } - /** - * Returns the .lock_info file. Does not check if it exists. - * - * @param workspaceUrl - * @return .lock_info file. - */ - private static Path getLockInfoFile(URL workspaceUrl) { - try { - return Path.of(URIUtil.toURI(workspaceUrl)).resolve(LOCK_INFO_FILE); - } catch (URISyntaxException e) { - throw new IllegalArgumentException(e); - } - } - /** * Creates the .lock_info file if it does not exist. * @@ -553,7 +485,7 @@ private static Path getLockInfoFile(URL workspaceUrl) { * @return .lock_info file. */ private static Path createLockInfoFile(URL workspaceUrl) throws Exception { - Path lockInfoFile = getLockInfoFile(workspaceUrl); + Path lockInfoFile = WorkspaceLock.getLockInfoFile(workspaceUrl); if (!Files.exists(lockInfoFile)) { Files.createFile(lockInfoFile); } @@ -736,9 +668,7 @@ protected Shell getParentShell() { } }; // hide splash if any - if (isValid(shell)) { - shell.setVisible(false); - } + hideSplash(shell); int returnCode = dialog.open(); if (returnCode == IDialogConstants.RETRY_ID || returnCode == SWT.DEFAULT) { @@ -759,6 +689,12 @@ protected Shell getParentShell() { return ReturnCode.VALID; } + protected void hideSplash(Shell shell) { + if (isValid(shell)) { + shell.setVisible(false); + } + } + /** * Compares the version of the workspace with the specified URL, to the version * of the running IDE. @@ -929,4 +865,4 @@ public void stop() { } }); } -} +} \ No newline at end of file diff --git a/bundles/org.eclipse.ui.ide/src/org/eclipse/ui/internal/ide/IDEWorkbenchMessages.java b/bundles/org.eclipse.ui.ide/src/org/eclipse/ui/internal/ide/IDEWorkbenchMessages.java index 0a14c764d017..bec664b18a5a 100644 --- a/bundles/org.eclipse.ui.ide/src/org/eclipse/ui/internal/ide/IDEWorkbenchMessages.java +++ b/bundles/org.eclipse.ui.ide/src/org/eclipse/ui/internal/ide/IDEWorkbenchMessages.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2005, 2017 IBM Corporation and others. + * Copyright (c) 2005, 2025 IBM Corporation and others. * * This program and the accompanying materials * are made available under the terms of the Eclipse Public License 2.0 @@ -1050,8 +1050,6 @@ public class IDEWorkbenchMessages extends NLS { public static String IDEApplication_workspaceInvalidMessage; public static String IDEApplication_workspaceCannotBeSetTitle; public static String IDEApplication_workspaceCannotBeSetMessage; - public static String IDEApplication_workspaceCannotLockTitle; - public static String IDEApplication_workspaceCannotLockMessage; public static String IDEApplication_versionTitle_newerWorkspace; public static String IDEApplication_versionTitle_olderWorkspace; public static String IDEApplication_versionMessage_newerWorkspace; @@ -1147,13 +1145,7 @@ public class IDEWorkbenchMessages extends NLS { public static String WorkbenchPreference_maxSimultaneousBuilds; public static String WorkbenchPreference_maxSimultaneousBuildIntervalError; - - public static String IDEApplication_Ws_Lock_Owner_User; - public static String IDEApplication_Ws_Lock_Owner_Host; - public static String IDEApplication_Ws_Lock_Owner_Disp; - public static String IDEApplication_Ws_Lock_Owner_P_Id; public static String IDEApplication_Ws_Lock_Owner_Message; - static { // load message values from bundle file NLS.initializeMessages(BUNDLE_NAME, IDEWorkbenchMessages.class); diff --git a/bundles/org.eclipse.ui.ide/src/org/eclipse/ui/internal/ide/actions/OpenWorkspaceAction.java b/bundles/org.eclipse.ui.ide/src/org/eclipse/ui/internal/ide/actions/OpenWorkspaceAction.java index 742864e29185..26b23e36f792 100644 --- a/bundles/org.eclipse.ui.ide/src/org/eclipse/ui/internal/ide/actions/OpenWorkspaceAction.java +++ b/bundles/org.eclipse.ui.ide/src/org/eclipse/ui/internal/ide/actions/OpenWorkspaceAction.java @@ -13,6 +13,8 @@ *******************************************************************************/ package org.eclipse.ui.internal.ide.actions; +import java.net.MalformedURLException; +import java.nio.file.Path; import java.util.ArrayList; import org.eclipse.core.runtime.Platform; @@ -24,6 +26,7 @@ import org.eclipse.jface.action.IMenuCreator; import org.eclipse.jface.action.MenuManager; import org.eclipse.jface.action.Separator; +import org.eclipse.jface.dialogs.MessageDialog; import org.eclipse.swt.SWT; import org.eclipse.swt.widgets.Control; import org.eclipse.swt.widgets.Menu; @@ -31,6 +34,8 @@ import org.eclipse.ui.IWorkbenchWindow; import org.eclipse.ui.actions.ActionFactory; import org.eclipse.ui.internal.Workbench; +import org.eclipse.ui.internal.WorkbenchMessages; +import org.eclipse.ui.internal.WorkspaceLock; import org.eclipse.ui.internal.ide.ChooseWorkspaceData; import org.eclipse.ui.internal.ide.ChooseWorkspaceDialog; import org.eclipse.ui.internal.ide.ChooseWorkspaceWithSettingsDialog; @@ -217,8 +222,34 @@ public void run() { public void restart(String workspacePath) { Object result = Workbench.setRestartArguments(workspacePath); if (result == IApplication.EXIT_RELAUNCH) { - window.getWorkbench().restart(); + if (canRestartWithWorkspace(workspacePath)) { + window.getWorkbench().restart(); + } + } + } + + /** + * Checks if the given workspace is locked by another instance of the workbench. + * Shows error dialog if the workspace is locked or on any other error. + * + * @param workspacePath the path of the workspace to check + * @return true if we can restart with given workspace, + * false if the workspace is locked or on any error + * + */ + private boolean canRestartWithWorkspace(String workspacePath) throws IllegalStateException { + Path selectedWorkspace = Path.of(workspacePath); + try { + String workspaceLockDetails = WorkspaceLock.getWorkspaceLockDetails(selectedWorkspace.toUri().toURL()); + if (workspaceLockDetails == null) { + return true; + } + WorkspaceLock.showWorkspaceLockedDialog(window.getShell(), workspacePath, workspaceLockDetails); + } catch (MalformedURLException e) { + MessageDialog.openError(window.getShell(), WorkbenchMessages.OpenWorkspaceAction_invalidWorkspacePath, + workspacePath); } + return false; } /** diff --git a/bundles/org.eclipse.ui.ide/src/org/eclipse/ui/internal/ide/messages.properties b/bundles/org.eclipse.ui.ide/src/org/eclipse/ui/internal/ide/messages.properties index b79612e23efe..348c34e50b9a 100644 --- a/bundles/org.eclipse.ui.ide/src/org/eclipse/ui/internal/ide/messages.properties +++ b/bundles/org.eclipse.ui.ide/src/org/eclipse/ui/internal/ide/messages.properties @@ -1,5 +1,5 @@ ############################################################################### -# Copyright (c) 2000, 2022 IBM Corporation and others. +# Copyright (c) 2000, 2025 IBM Corporation and others. # # This program and the accompanying materials # are made available under the terms of the Eclipse Public License 2.0 @@ -1074,8 +1074,6 @@ IDEApplication_workspaceInvalidTitle=Invalid Workspace IDEApplication_workspaceInvalidMessage=Selected workspace is not valid; choose a different one. IDEApplication_workspaceCannotBeSetTitle=Workspace Cannot Be Created IDEApplication_workspaceCannotBeSetMessage=Could not launch the product because the specified workspace cannot be created. The specified workspace directory is either invalid or read-only. -IDEApplication_workspaceCannotLockTitle=Workspace Cannot Be Locked -IDEApplication_workspaceCannotLockMessage=Could not launch the product because the associated workspace at ''{0}'' is currently in use by another Eclipse application. IDEApplication_versionTitle_olderWorkspace=Older Workspace Version IDEApplication_versionTitle_newerWorkspace=Newer Workspace Version IDEApplication_versionMessage_olderWorkspace=The ''{0}'' workspace was written with an older version. \ @@ -1152,8 +1150,4 @@ editorAssociationOverride_error_couldNotCreate_message=The ''{0}'' extension fro editorAssociationOverride_error_invalidElementName_message=An extension from plug-in ''{0}'' to the ''org.eclipse.ui.ide.editorAssociationOverride'' extension point was ignored because it contains the following invalid element: ''{1}''. editorAssociationOverride_error_invalidExtension_message=The ''{0}'' extension from plug-in ''{1}'' to the ''org.eclipse.ui.ide.editorAssociationOverride'' extension point will be ignored because it contains invalid attributes. -IDEApplication_Ws_Lock_Owner_User=User:\t\t{0}\n -IDEApplication_Ws_Lock_Owner_Host=Host:\t\t{0}\n -IDEApplication_Ws_Lock_Owner_Disp=Display:\t\t{0}\n -IDEApplication_Ws_Lock_Owner_P_Id=Process ID:\t{0}\n IDEApplication_Ws_Lock_Owner_Message=Workspace lock is currently held by:\n{0} diff --git a/bundles/org.eclipse.ui.workbench/eclipseui/org/eclipse/ui/internal/Workbench.java b/bundles/org.eclipse.ui.workbench/eclipseui/org/eclipse/ui/internal/Workbench.java index d8997df75711..71910c2c7b1f 100644 --- a/bundles/org.eclipse.ui.workbench/eclipseui/org/eclipse/ui/internal/Workbench.java +++ b/bundles/org.eclipse.ui.workbench/eclipseui/org/eclipse/ui/internal/Workbench.java @@ -2694,7 +2694,13 @@ private static String buildCommandLine(String workspace) { * as the workspace location. * * @param workspacePath the new workspace location - * @return {@link IApplication#EXIT_OK} or {@link IApplication#EXIT_RELAUNCH} + * @return null if application was started in development mode and + * the specified workspace path is different from the current one, + * otherwise {@link IApplication#EXIT_RELAUNCH}. + *

+ * In case of null return, a message dialog will be shown, + * so the caller doesn't need to handle the error case via dialog. + *

*/ @SuppressWarnings("restriction") public static Object setRestartArguments(String workspacePath) { @@ -2706,10 +2712,6 @@ public static Object setRestartArguments(String workspacePath) { } String command_line = Workbench.buildCommandLine(workspacePath); - if (command_line == null) { - return IApplication.EXIT_OK; - } - System.setProperty(Workbench.PROP_EXIT_CODE, IApplication.EXIT_RELAUNCH.toString()); System.setProperty(IApplicationContext.EXIT_DATA_PROPERTY, command_line); return IApplication.EXIT_RELAUNCH; @@ -3704,4 +3706,4 @@ public boolean isMonitorSpecificScalingDisabledForIncompatibility() { } } -} +} \ No newline at end of file diff --git a/bundles/org.eclipse.ui.workbench/eclipseui/org/eclipse/ui/internal/WorkbenchMessages.java b/bundles/org.eclipse.ui.workbench/eclipseui/org/eclipse/ui/internal/WorkbenchMessages.java index a2b10045e035..79ac40e91bdb 100644 --- a/bundles/org.eclipse.ui.workbench/eclipseui/org/eclipse/ui/internal/WorkbenchMessages.java +++ b/bundles/org.eclipse.ui.workbench/eclipseui/org/eclipse/ui/internal/WorkbenchMessages.java @@ -1043,4 +1043,14 @@ public class WorkbenchMessages extends NLS { public static String Workbench_RestartButton; public static String Workbench_DontRestartButton; + public static String IDEApplication_workspaceLockOwner; + public static String IDEApplication_workspaceLockHost; + public static String IDEApplication_workspaceLockDisplay; + public static String IDEApplication_workspaceLockPID; + public static String IDEApplication_workspaceLockMessage; + public static String IDEApplication_workspaceCannotLockMessage2; + public static String IDEApplication_workspaceCannotLockTitle; + + public static String OpenWorkspaceAction_invalidWorkspacePath; + } diff --git a/bundles/org.eclipse.ui.workbench/eclipseui/org/eclipse/ui/internal/WorkspaceLock.java b/bundles/org.eclipse.ui.workbench/eclipseui/org/eclipse/ui/internal/WorkspaceLock.java new file mode 100644 index 000000000000..501366f362ab --- /dev/null +++ b/bundles/org.eclipse.ui.workbench/eclipseui/org/eclipse/ui/internal/WorkspaceLock.java @@ -0,0 +1,143 @@ +/******************************************************************************* + * Copyright (c) 2025 IBM Corporation. + * + * This program and the accompanying materials + * are made available under the terms of the Eclipse Public License 2.0 + * which accompanies this distribution, and is available at + * https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * + * Contributors: + * IBM Corporation - initial API and implementation + *******************************************************************************/ +package org.eclipse.ui.internal; + +import java.io.InputStream; +import java.net.URISyntaxException; +import java.net.URL; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.Properties; +import org.eclipse.core.runtime.URIUtil; +import org.eclipse.jface.dialogs.MessageDialog; +import org.eclipse.osgi.util.NLS; +import org.eclipse.swt.widgets.Shell; + +/** + * Utility class for reading and presenting workspace lock information. + * + *

+ * This class is used during two different phases of the Eclipse application + * lifecycle: + *

+ * + * + *

+ * To support both environments, this class does not rely on workbench-specific + * APIs such as {@code PlatformUI.getWorkbench()} or {@code IWorkbenchWindow}, + * nor on any API that requires an initialized workbench window. Only SWT-level + * constructs (for example, {@link org.eclipse.swt.widgets.Display} and + * {@link org.eclipse.swt.widgets.Shell}) and core/runtime APIs are used. + *

+ * + */ +public class WorkspaceLock { + + public static final String PROCESS_ID = "process-id"; //$NON-NLS-1$ + + public static final String DISPLAY = "display"; //$NON-NLS-1$ + + public static final String HOST = "host"; //$NON-NLS-1$ + + public static final String USER = "user"; //$NON-NLS-1$ + + /** + * Read workspace lock file of the selected workspace if it is locked by another + * Eclipse application and parse all the properties present. Based on the + * Eclipse version and operating system some or all the properties may be not + * present. In such scenario empty string may be returned. + * + * @param workspaceUrl the URL of workspace to check for lock + * details + * @return null if workspace is not locked, empty string if + * workspace is locked but no details are available (or lock file is not + * accessible), or a formatted string with lock details + */ + public static String getWorkspaceLockDetails(URL workspaceUrl) { + Path lockFile = getLockInfoFile(workspaceUrl); + if (lockFile == null || !Files.exists(lockFile)) { + return null; + } + StringBuilder lockDetails = new StringBuilder(); + try (InputStream is = Files.newInputStream(lockFile)) { + Properties properties = new Properties(); + properties.load(is); + String prop = properties.getProperty(USER); + if (prop != null) { + lockDetails.append(NLS.bind(WorkbenchMessages.IDEApplication_workspaceLockOwner, prop)); + } + prop = properties.getProperty(HOST); + if (prop != null) { + lockDetails.append(NLS.bind(WorkbenchMessages.IDEApplication_workspaceLockHost, prop)); + } + prop = properties.getProperty(DISPLAY); + if (prop != null) { + lockDetails.append(NLS.bind(WorkbenchMessages.IDEApplication_workspaceLockDisplay, prop)); + } + prop = properties.getProperty(PROCESS_ID); + if (prop != null) { + lockDetails.append(NLS.bind(WorkbenchMessages.IDEApplication_workspaceLockPID, prop)); + } + } catch (Exception e) { + WorkbenchPlugin.log("Could not read lock info file: " + lockFile, e); //$NON-NLS-1$ + } + return lockDetails.toString(); + } + + /** + * Returns the lock file. + * + * @param workspaceUrl the URL of selected workspace + * @return the path to the .lock_info file within the specified + * workspace, or null if the workspace URL cannot be + * converted to a valid URI + */ + public static Path getLockInfoFile(URL workspaceUrl) { + Path lockFile = Path.of(".metadata", ".lock_info"); //$NON-NLS-1$ //$NON-NLS-2$ + try { + return Path.of(URIUtil.toURI(workspaceUrl)).resolve(lockFile); + } catch (URISyntaxException e) { + return null; + } + } + + /** + * Opens an error dialog indicating that the selected workspace is locked by + * another Eclipse instance. + *

+ * This method works in both early startup (before the Workbench is created) and + * in normal runtime (after Workbench windows exist). + *

+ * + * @param shell the parent {@link Shell} for the dialog, or + * {@code null} if no workbench window is available + * @param workspacePath the absolute path of the workspace that could not + * be locked + * @param workspaceLockOwner a formatted description of the existing lock owner + */ + public static void showWorkspaceLockedDialog(Shell shell, String workspacePath, String workspaceLockOwner) { + String lockMessage = NLS.bind(WorkbenchMessages.IDEApplication_workspaceCannotLockMessage2, workspacePath); + String wsLockedError = lockMessage + System.lineSeparator() + System.lineSeparator() + + NLS.bind(WorkbenchMessages.IDEApplication_workspaceLockMessage, workspaceLockOwner); + + MessageDialog.openError(shell, + WorkbenchMessages.IDEApplication_workspaceCannotLockTitle, wsLockedError); + } + +} diff --git a/bundles/org.eclipse.ui.workbench/eclipseui/org/eclipse/ui/internal/messages.properties b/bundles/org.eclipse.ui.workbench/eclipseui/org/eclipse/ui/internal/messages.properties index 3988f2dba815..0200f6f4873a 100644 --- a/bundles/org.eclipse.ui.workbench/eclipseui/org/eclipse/ui/internal/messages.properties +++ b/bundles/org.eclipse.ui.workbench/eclipseui/org/eclipse/ui/internal/messages.properties @@ -1055,3 +1055,14 @@ Workbench_zoomChangedTitle=Zoom Changed Workbench_zoomChangedMessage=Restart for the zoom changes to take full effect? Workbench_RestartButton=&Restart Workbench_DontRestartButton=&Don't restart + +# Workspace lock messages +IDEApplication_workspaceLockOwner=User:\t\t{0}\n +IDEApplication_workspaceLockHost=Host:\t\t{0}\n +IDEApplication_workspaceLockDisplay=Display:\t\t{0}\n +IDEApplication_workspaceLockPID=Process ID:\t{0}\n +IDEApplication_workspaceLockMessage=Workspace lock is currently held by:\n{0} +IDEApplication_workspaceCannotLockMessage2=Could not switch to the selected workspace ''{0}'' because it is currently in use by another Eclipse instance. +IDEApplication_workspaceCannotLockTitle=Workspace Cannot Be Locked + +OpenWorkspaceAction_invalidWorkspacePath=The selected workspace location ''{0}'' is not valid. diff --git a/bundles/org.eclipse.ui/META-INF/MANIFEST.MF b/bundles/org.eclipse.ui/META-INF/MANIFEST.MF index 38046f50c9e4..58e479e7a4b5 100644 --- a/bundles/org.eclipse.ui/META-INF/MANIFEST.MF +++ b/bundles/org.eclipse.ui/META-INF/MANIFEST.MF @@ -2,7 +2,7 @@ Manifest-Version: 1.0 Bundle-ManifestVersion: 2 Bundle-Name: %Plugin.name Bundle-SymbolicName: org.eclipse.ui; singleton:=true -Bundle-Version: 3.208.100.qualifier +Bundle-Version: 3.209.0.qualifier Bundle-Activator: org.eclipse.ui.internal.UIPlugin Bundle-ActivationPolicy: lazy Bundle-Vendor: %Plugin.providerName @@ -11,7 +11,7 @@ Export-Package: org.eclipse.ui.internal;x-friends:="org.eclipse.ui.workbench.tex Require-Bundle: org.eclipse.core.runtime;bundle-version="[3.29.0,4.0.0)", org.eclipse.swt;bundle-version="[3.133.0,4.0.0)";visibility:=reexport, org.eclipse.jface;bundle-version="[3.34.0,4.0.0)";visibility:=reexport, - org.eclipse.ui.workbench;bundle-version="[3.130.0,4.0.0)";visibility:=reexport, + org.eclipse.ui.workbench;bundle-version="[3.138.100,4.0.0)";visibility:=reexport, org.eclipse.core.expressions;bundle-version="[3.4.0,4.0.0)" Bundle-RequiredExecutionEnvironment: JavaSE-21 Automatic-Module-Name: org.eclipse.ui