diff --git a/Ghidra/Features/Base/src/main/help/help/topics/VersionControl/images/CheckedOut.png b/Ghidra/Features/Base/src/main/help/help/topics/VersionControl/images/CheckedOut.png index 21e4a0db03..faefad10a4 100644 Binary files a/Ghidra/Features/Base/src/main/help/help/topics/VersionControl/images/CheckedOut.png and b/Ghidra/Features/Base/src/main/help/help/topics/VersionControl/images/CheckedOut.png differ diff --git a/Ghidra/Features/Base/src/main/help/help/topics/VersionControl/project_repository.htm b/Ghidra/Features/Base/src/main/help/help/topics/VersionControl/project_repository.htm index 4718cd2bb4..63c40ce23e 100644 --- a/Ghidra/Features/Base/src/main/help/help/topics/VersionControl/project_repository.htm +++ b/Ghidra/Features/Base/src/main/help/help/topics/VersionControl/project_repository.htm @@ -40,27 +40,38 @@
-When you open a shared project, Ghidra attempts to connect to the server that is - associated with the shared project. Depending on what user authentication mode the server is - using, you may have to enter a password. If the server is not running, you are still able to - work with your checked out files while you are offline. Other versioned files not checked out - are not accessible. When the server comes up, Ghidra will reconnect as necessary. You can - also attempt to connect "manually" by selecting the connection status button
- -on the Ghidra Project Window or on the Project Info dialog. When the - connection is successful, the connection status button changes to
.
If you lose the connection to the server after having started Ghidra, shared files not - checked out "disappear" from the Ghidra Project Window, as they are unavailable. Private - files remain intact and are not affected by the server connection.
+When you open or view a shared project, Ghidra attempts to connect to the corresponding server. + Depending on which user authentication mode the server is using, you may have to enter a password. + If you choose not to connect or lose the connection to the repository server after opening + or viewing a project, shared files not + checked out will not be shown within the Ghidra Project Window, as they are unavailable for use. + Local project private files are not affected by the repository server connection and will always + be shown. If you subsequently connect to the repository server, Ghidra will refresh + the project views to reflect the current state.
+ +++ ++
++ The root folder node of the Project Data Tree view of a shared project will convey the + current connection status with green (connected) or red (disconnected) indicator.
If applicable, and + not currently connected to the shared repository server, a manual connection may be re-attempted by + clicking the Connect Shared Repository popup action on the root node of a shared project. + For the active project there is also a Connect status button in the lower-right corner of the + project window. When this button shows the disconnected state +
it may be clicked to attempt a connection. + This may also be done from the Project + Info dialog. When the active project repository connection is successful, the connection + status button changes to
.
+ Successfully connecting to a Ghidra Server which corresponds to multiple named repositories will cause + all associated viewed projects within Ghidra to become connected or automatically connect + when subsequently opened.
- You are authenticated only once per - Ghidra session; so if you open other project repositories managed by the same Ghidra Server, - you will be prompted only once for a password, as required.
Troubleshooting a Failed Connection
diff --git a/Ghidra/Framework/Project/certification.manifest b/Ghidra/Framework/Project/certification.manifest index cbe20f1f1e..fa416755e6 100644 --- a/Ghidra/Framework/Project/certification.manifest +++ b/Ghidra/Framework/Project/certification.manifest @@ -26,6 +26,7 @@ src/main/resources/images/checkNotLatest.gif||GHIDRA||reviewed||END| src/main/resources/images/checkex.png||GHIDRA||reviewed||END| src/main/resources/images/connected.gif||GHIDRA||reviewed||END| src/main/resources/images/disconnected.gif||GHIDRA||reviewed||END| +src/main/resources/images/green_can.png||GHIDRA||||END| src/main/resources/images/link.png||Crystal Clear Icons - LGPL 2.1||||END| src/main/resources/images/lock.png||FAMFAMFAM Icons - CC 2.5|||famfamfam silk icon set|END| src/main/resources/images/monitor.png||FAMFAMFAM Icons - CC 2.5|||silk|END| @@ -34,6 +35,7 @@ src/main/resources/images/page_delete.png||FAMFAMFAM Icons - CC 2.5||||END| src/main/resources/images/page_edit.png||FAMFAMFAM Icons - CC 2.5||||END| src/main/resources/images/plasma.png||Oxygen Icons - LGPL 3.0|||Oxygen icon theme (dual license; LGPL or CC-SA-3.0)|END| src/main/resources/images/plugin.png||GHIDRA||reviewed||END| +src/main/resources/images/red_can.png||GHIDRA||||END| src/main/resources/images/small_hijack.gif||GHIDRA||reviewed||END| src/main/resources/images/undo_hijack.png||GHIDRA||reviewed||END| src/main/resources/images/unknownFile.gif||GHIDRA||reviewed||END| diff --git a/Ghidra/Framework/Project/data/project.icons.theme.properties b/Ghidra/Framework/Project/data/project.icons.theme.properties index cf81d7ad06..84f26f93e8 100644 --- a/Ghidra/Framework/Project/data/project.icons.theme.properties +++ b/Ghidra/Framework/Project/data/project.icons.theme.properties @@ -9,6 +9,12 @@ icon.project.data.file.ghidra.hijacked = small_hijack.gif icon.project.data.file.ghidra.read.only = user-busy.png [size(8,8)] icon.project.data.file.ghidra.not.latest = checkNotLatest.gif +icon.project.root.repo.connected = green_can.png +icon.project.root.repo.connected.overlay = EMPTY_ICON[size(18,16)]{icon.project.root.repo.connected[move(12,8)]} // lower-left of 16x16 icon + +icon.project.root.repo.disconnected = red_can.png +icon.project.root.repo.disconnected.overlay = EMPTY_ICON[size(18,16)]{icon.project.root.repo.disconnected[move(12,8)]} // lower-left of 16x16 icon + icon.content.handler.link = link.png icon.content.handler.link.overlay = EMPTY_ICON[size(16,16)]{icon.content.handler.link[move(0,8)]} // lower-left of 16x16 icon diff --git a/Ghidra/Framework/Project/src/main/java/ghidra/framework/main/FrontEndPlugin.java b/Ghidra/Framework/Project/src/main/java/ghidra/framework/main/FrontEndPlugin.java index 1a2ccb7c6c..de4ae05c1d 100644 --- a/Ghidra/Framework/Project/src/main/java/ghidra/framework/main/FrontEndPlugin.java +++ b/Ghidra/Framework/Project/src/main/java/ghidra/framework/main/FrontEndPlugin.java @@ -129,6 +129,7 @@ public class FrontEndPlugin extends Plugin private FrontEndProvider frontEndProvider; + private ProjectRepoConnectAction repoConnectAction; private ProjectDataCutAction cutAction; private ClearCutAction clearCutAction; private ProjectDataCopyAction copyAction; @@ -221,6 +222,7 @@ public class FrontEndPlugin extends Plugin String owner = getName(); // Top of popup menu actions - no group + repoConnectAction = new ProjectRepoConnectAction(this, null); openAction = new ProjectDataOpenDefaultToolAction(owner, null); followLinkAction = new ProjectDataFollowLinkAction(this, null); selectRealFileOrFolderAction = new ProjectDataSelectRealFileOrFolderAction(this, null); @@ -251,6 +253,7 @@ public class FrontEndPlugin extends Plugin groupName = "XRefresh"; refreshAction = new ProjectDataRefreshAction(owner, groupName); + tool.addAction(repoConnectAction); tool.addAction(newFolderAction); tool.addAction(cutAction); tool.addAction(clearCutAction); diff --git a/Ghidra/Framework/Project/src/main/java/ghidra/framework/main/ProjectRepoConnectAction.java b/Ghidra/Framework/Project/src/main/java/ghidra/framework/main/ProjectRepoConnectAction.java new file mode 100644 index 0000000000..9b31acd9d2 --- /dev/null +++ b/Ghidra/Framework/Project/src/main/java/ghidra/framework/main/ProjectRepoConnectAction.java @@ -0,0 +1,93 @@ +/* ### + * IP: GHIDRA + * + * 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 ghidra.framework.main; + +import java.io.IOException; + +import javax.swing.Icon; + +import docking.action.MenuData; +import generic.theme.GIcon; +import ghidra.framework.client.*; +import ghidra.framework.main.datatable.FrontendProjectTreeAction; +import ghidra.framework.main.datatable.ProjectDataContext; +import ghidra.framework.main.datatree.DataTree; +import ghidra.framework.model.DomainFolder; +import ghidra.framework.model.ProjectData; +import ghidra.util.HelpLocation; + +/** + * {@link ProjectRepoConnectAction} action allows the user to initiate a shared repository + * connection for a root shared project data tree node that is not currently connected. + */ +public class ProjectRepoConnectAction extends FrontendProjectTreeAction { + + private static final Icon CONNECT_ICON = new GIcon("icon.frontend.project.connected"); + + private FrontEndPlugin plugin; + + public ProjectRepoConnectAction(FrontEndPlugin plugin, String group) { + super("Connect Shared Repository", plugin.getName()); + this.plugin = plugin; + setPopupMenuData( + new MenuData(new String[] { "Connect Shared Repository" }, CONNECT_ICON, group)); + setHelpLocation(new HelpLocation("VersionControl", "Connect_Shared_Repository")); + } + + @Override + protected void actionPerformed(ProjectDataContext context) { + RepositoryAdapter repository = getDisconnectedRepository(context); + if (repository != null) { + try { + repository.connect(); + } + catch (NotConnectedException e) { + // don't think this can happen + } + catch (IOException e) { + ClientUtil.handleException(repository, e, "Repository Connection", + plugin.getTool().getToolFrame()); + } + } + } + + @Override + protected boolean isEnabledForContext(ProjectDataContext context) { + return getDisconnectedRepository(context) != null; + } + + private RepositoryAdapter getDisconnectedRepository(ProjectDataContext context) { + if (!(context.getComponent() instanceof DataTree)) { + return null; + } + if (context.getFolderCount() != 1 || context.getFileCount() != 0) { + return null; + } + DomainFolder domainFolder = context.getSelectedFolders().get(0); + if (domainFolder.getParent() != null) { + return null; + } + ProjectData projectData = domainFolder.getProjectData(); + if (projectData.getProjectLocator().isTransient()) { + return null; // Transient projects are always connected + } + RepositoryAdapter repository = projectData.getRepository(); + if (repository != null && !repository.isConnected()) { + return repository; + } + return null; + } +} diff --git a/Ghidra/Framework/Project/src/main/java/ghidra/framework/main/datatree/DomainFolderRootNode.java b/Ghidra/Framework/Project/src/main/java/ghidra/framework/main/datatree/DomainFolderRootNode.java index 1a855660fe..98bf690955 100644 --- a/Ghidra/Framework/Project/src/main/java/ghidra/framework/main/datatree/DomainFolderRootNode.java +++ b/Ghidra/Framework/Project/src/main/java/ghidra/framework/main/datatree/DomainFolderRootNode.java @@ -4,9 +4,9 @@ * 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. @@ -21,23 +21,81 @@ import javax.swing.Icon; import docking.tool.ToolConstants; import generic.theme.GIcon; +import ghidra.framework.client.RemoteAdapterListener; import ghidra.framework.client.RepositoryAdapter; import ghidra.framework.model.*; +import resources.MultiIcon; + +public class DomainFolderRootNode extends DomainFolderNode implements RemoteAdapterListener { -public class DomainFolderRootNode extends DomainFolderNode { private static final Icon CLOSED_PROJECT = new GIcon("icon.datatree.node.domain.folder.closed"); private static final Icon OPEN_PROJECT = new GIcon("icon.datatree.node.domain.folder.open"); + private static final Icon CONNECTED_OVERLAY = + new GIcon("icon.project.root.repo.connected.overlay"); + private static final Icon DISCONNECTED_OVERLAY = + new GIcon("icon.project.root.repo.disconnected.overlay"); + + private static enum Status { + OPEN(true), + CLOSED(false), + OPEN_CONNECTED(true, true), + CLOSED_CONNECTED(false, true), + OPEN_DISCONNECTED(true, false), + CLOSED_DISCONNECTED(false, false); + + final Icon icon; + + private Status(boolean isOpen) { + icon = isOpen ? OPEN_PROJECT : CLOSED_PROJECT; + } + + private Status(boolean isOpen, boolean isConnected) { + MultiIcon multiIcon = new MultiIcon(isOpen ? OPEN_PROJECT : CLOSED_PROJECT); + multiIcon.addIcon(isConnected ? CONNECTED_OVERLAY : DISCONNECTED_OVERLAY); + icon = multiIcon; + } + + static Status getStatus(boolean isOpen, RepositoryAdapter repository) { + if (isOpen) { + if (repository == null) { + return OPEN; + } + return repository.isConnected() ? OPEN_CONNECTED : OPEN_DISCONNECTED; + } + if (repository == null) { + return CLOSED; + } + return repository.isConnected() ? CLOSED_CONNECTED : CLOSED_DISCONNECTED; + } + } + private String projectName; + private RepositoryAdapter repository; + + private Status status; private String toolTipText; DomainFolderRootNode(String projectName, DomainFolder rootFolder, ProjectData projectData, DomainFileFilter filter) { super(rootFolder, filter); this.projectName = projectName; + this.repository = getProjectData().getRepository(); + if (repository != null) { + repository.addListener(this); + } + toolTipText = getToolTip(projectData); } + @Override + public void dispose() { + if (repository != null) { + repository.removeListener(this); + } + super.dispose(); + } + @Override public String getName() { if (projectName == null) { @@ -58,21 +116,25 @@ public class DomainFolderRootNode extends DomainFolderNode { @Override public Icon getIcon(boolean expanded) { - return expanded ? OPEN_PROJECT : CLOSED_PROJECT; + status = Status.getStatus(expanded, repository); + return status.icon; } private String getToolTip(ProjectData projectData) { - RepositoryAdapter repository = projectData.getRepository(); - File dir = projectData.getProjectLocator().getProjectDir(); + ProjectLocator projectLocator = projectData.getProjectLocator(); + File dir = projectLocator.getProjectDir(); String toolTip = dir.getAbsolutePath(); - if (!getDomainFolder().isInWritableProject() && repository != null) { + if (!projectLocator.isTransient() && repository != null) { ServerInfo info = repository.getServerInfo(); - String serverName = ""; - if (info != null) { - serverName = info.getServerName() + ", "; - } - toolTip += " [" + serverName + repository.getName() + "]"; + String serverName = info.getServerName() + ":"; + String statusText = repository.isConnected() ? "connected" : "disconnected"; + toolTip += " [" + serverName + repository.getName() + ", " + statusText + "]"; } return toolTip; } + + @Override + public void connectionStateChanged(Object adapter) { + toolTipText = getToolTip(getProjectData()); + } } diff --git a/Ghidra/Framework/Project/src/main/resources/images/green_can.png b/Ghidra/Framework/Project/src/main/resources/images/green_can.png new file mode 100644 index 0000000000..a4363a8fc5 Binary files /dev/null and b/Ghidra/Framework/Project/src/main/resources/images/green_can.png differ diff --git a/Ghidra/Framework/Project/src/main/resources/images/red_can.png b/Ghidra/Framework/Project/src/main/resources/images/red_can.png new file mode 100644 index 0000000000..b3a57d32ba Binary files /dev/null and b/Ghidra/Framework/Project/src/main/resources/images/red_can.png differ diff --git a/Ghidra/Test/IntegrationTest/src/screen/java/help/screenshot/VersionControlScreenShots.java b/Ghidra/Test/IntegrationTest/src/screen/java/help/screenshot/VersionControlScreenShots.java index 9588524b5e..3cf825ac02 100644 --- a/Ghidra/Test/IntegrationTest/src/screen/java/help/screenshot/VersionControlScreenShots.java +++ b/Ghidra/Test/IntegrationTest/src/screen/java/help/screenshot/VersionControlScreenShots.java @@ -4,9 +4,9 @@ * 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. @@ -76,6 +76,7 @@ public class VersionControlScreenShots extends GhidraScreenShotGenerator { UndoActionDialog d = waitForDialogComponent(UndoActionDialog.class); captureDialog(d); + close(d); } @Test @@ -95,6 +96,7 @@ public class VersionControlScreenShots extends GhidraScreenShotGenerator { UndoActionDialog d = waitForDialogComponent(UndoActionDialog.class); captureDialog(d); + close(d); } @Test