From bb2e417454b6dfc1b146641e0d222ee3b6ad6f11 Mon Sep 17 00:00:00 2001 From: Groboclown Date: Wed, 16 Mar 2022 19:11:44 -0500 Subject: [PATCH] Refactored connection and server configuration code to be easier to manage, and to start the process of removing bad code. --- CHANGES.md | 4 +- docs/dev/architecture.md | 121 +++++------------- docs/dev/connection.md | 30 +---- docs/dev/threading.md | 55 ++------ .../p4/server/api/ProjectConfigRegistry.java | 83 ++++-------- .../cache/messagebus/ClientActionMessage.java | 10 +- .../messagebus/VcsRootClientPartsMessage.java | 115 +++++++++++++++++ .../server/api/ProjectConfigRegistryTest.java | 17 ++- .../p4plugin/components/CacheComponent.java | 6 +- .../extension/P4CheckinEnvironment.java | 19 +-- .../groboclown/p4plugin/extension/P4Vcs.java | 2 +- .../PersistentRootConfigModel.java | 64 +++------ .../clientconfig/VcsRootChangeListener.java | 53 ++++++++ .../clientconfig/VcsRootConfigController.java | 97 ++++++++++++++ .../modules/clientconfig/package-info.java | 4 + .../view}/ConfigConnectionController.java | 2 +- .../view}/ConfigConnectionListener.java | 2 +- .../clientconfig/view}/ConfigPartStack.form | 2 +- .../clientconfig/view}/ConfigPartStack.java | 14 +- .../clientconfig/view}/ConfigPartUI.java | 2 +- .../view}/ConfigPartUIFactory.java | 2 +- .../clientconfig/view}/ConfigPartWrapper.form | 2 +- .../clientconfig/view}/ConfigPartWrapper.java | 2 +- .../view}/ListPositionChangeController.java | 2 +- .../clientconfig/view}/P4RootConfigPanel.form | 4 +- .../clientconfig/view}/P4RootConfigPanel.java | 2 +- .../view}/P4VcsRootConfigurable.java | 27 ++-- .../view}/part/ClientNamePartUI.java | 8 +- .../clientconfig/view}/part/EnvPartUI.java | 8 +- .../clientconfig/view}/part/FilePartUI.java | 9 +- .../view}/part/PropertiesPartUI.form | 2 +- .../view}/part/PropertiesPartUI.java | 8 +- .../view}/part/RequirePasswordPartUI.java | 9 +- .../view}/part/ServerFingerprintPartUI.java | 8 +- .../connection/ConfigRegistryStartup.java | 31 +++++ .../ProjectConfigRegistryImpl.java | 44 +++---- .../modules/connection/package-info.java | 16 +++ .../view}/ActiveConnectionPanel.java | 30 ++--- .../view}/ActiveConnectionViewManager.java | 2 +- .../view}/ConnectionTreeCellRenderer.java | 2 +- .../view}/ConnectionTreeRootNode.java | 2 +- .../connection/view}/PendingParentNode.java | 2 +- .../config/P4ConfigurationProjectPanel.java | 1 + .../p4plugin/ui/sync/SyncProjectDialog.java | 2 +- .../src/main/resources/META-INF/plugin.xml | 38 +++--- .../groboclown/p4plugin/P4Bundle.properties | 2 +- .../net/groboclown/p4plugin/PluginSetup.java | 15 +-- .../ProjectConfigRegistryImplTest.java | 56 ++------ 48 files changed, 568 insertions(+), 470 deletions(-) create mode 100644 idea-p4server/api/src/main/java/net/groboclown/p4/server/api/messagebus/VcsRootClientPartsMessage.java rename idea-p4server/impl/src/main/java/net/groboclown/p4/server/impl/config/PersistentRootConfigComponent.java => plugin-v3/src/main/java/net/groboclown/p4plugin/modules/clientconfig/PersistentRootConfigModel.java (80%) create mode 100644 plugin-v3/src/main/java/net/groboclown/p4plugin/modules/clientconfig/VcsRootChangeListener.java create mode 100644 plugin-v3/src/main/java/net/groboclown/p4plugin/modules/clientconfig/VcsRootConfigController.java create mode 100644 plugin-v3/src/main/java/net/groboclown/p4plugin/modules/clientconfig/package-info.java rename plugin-v3/src/main/java/net/groboclown/p4plugin/{ui/vcsroot => modules/clientconfig/view}/ConfigConnectionController.java (97%) rename plugin-v3/src/main/java/net/groboclown/p4plugin/{ui/vcsroot => modules/clientconfig/view}/ConfigConnectionListener.java (94%) rename plugin-v3/src/main/java/net/groboclown/p4plugin/{ui/vcsroot => modules/clientconfig/view}/ConfigPartStack.form (96%) rename plugin-v3/src/main/java/net/groboclown/p4plugin/{ui/vcsroot => modules/clientconfig/view}/ConfigPartStack.java (95%) rename plugin-v3/src/main/java/net/groboclown/p4plugin/{ui/vcsroot => modules/clientconfig/view}/ConfigPartUI.java (95%) rename plugin-v3/src/main/java/net/groboclown/p4plugin/{ui/vcsroot => modules/clientconfig/view}/ConfigPartUIFactory.java (95%) rename plugin-v3/src/main/java/net/groboclown/p4plugin/{ui/vcsroot => modules/clientconfig/view}/ConfigPartWrapper.form (98%) rename plugin-v3/src/main/java/net/groboclown/p4plugin/{ui/vcsroot => modules/clientconfig/view}/ConfigPartWrapper.java (99%) rename plugin-v3/src/main/java/net/groboclown/p4plugin/{ui/vcsroot => modules/clientconfig/view}/ListPositionChangeController.java (92%) rename plugin-v3/src/main/java/net/groboclown/p4plugin/{ui/vcsroot => modules/clientconfig/view}/P4RootConfigPanel.form (96%) rename plugin-v3/src/main/java/net/groboclown/p4plugin/{ui/vcsroot => modules/clientconfig/view}/P4RootConfigPanel.java (99%) rename plugin-v3/src/main/java/net/groboclown/p4plugin/{ui/vcsroot => modules/clientconfig/view}/P4VcsRootConfigurable.java (92%) rename plugin-v3/src/main/java/net/groboclown/p4plugin/{ui/vcsroot => modules/clientconfig/view}/part/ClientNamePartUI.java (96%) rename plugin-v3/src/main/java/net/groboclown/p4plugin/{ui/vcsroot => modules/clientconfig/view}/part/EnvPartUI.java (89%) rename plugin-v3/src/main/java/net/groboclown/p4plugin/{ui/vcsroot => modules/clientconfig/view}/part/FilePartUI.java (93%) rename plugin-v3/src/main/java/net/groboclown/p4plugin/{ui/vcsroot => modules/clientconfig/view}/part/PropertiesPartUI.form (99%) rename plugin-v3/src/main/java/net/groboclown/p4plugin/{ui/vcsroot => modules/clientconfig/view}/part/PropertiesPartUI.java (97%) rename plugin-v3/src/main/java/net/groboclown/p4plugin/{ui/vcsroot => modules/clientconfig/view}/part/RequirePasswordPartUI.java (89%) rename plugin-v3/src/main/java/net/groboclown/p4plugin/{ui/vcsroot => modules/clientconfig/view}/part/ServerFingerprintPartUI.java (92%) create mode 100644 plugin-v3/src/main/java/net/groboclown/p4plugin/modules/connection/ConfigRegistryStartup.java rename {idea-p4server/impl/src/main/java/net/groboclown/p4/server/impl => plugin-v3/src/main/java/net/groboclown/p4plugin/modules/connection}/ProjectConfigRegistryImpl.java (92%) create mode 100644 plugin-v3/src/main/java/net/groboclown/p4plugin/modules/connection/package-info.java rename plugin-v3/src/main/java/net/groboclown/p4plugin/{ui/connection => modules/connection/view}/ActiveConnectionPanel.java (93%) rename plugin-v3/src/main/java/net/groboclown/p4plugin/{ui/connection => modules/connection/view}/ActiveConnectionViewManager.java (96%) rename plugin-v3/src/main/java/net/groboclown/p4plugin/{ui/connection => modules/connection/view}/ConnectionTreeCellRenderer.java (99%) rename plugin-v3/src/main/java/net/groboclown/p4plugin/{ui/connection => modules/connection/view}/ConnectionTreeRootNode.java (98%) rename plugin-v3/src/main/java/net/groboclown/p4plugin/{ui/connection => modules/connection/view}/PendingParentNode.java (93%) rename {idea-p4server/impl/src/test/java/net/groboclown/p4/server/impl => plugin-v3/src/test/java/net/groboclown/p4plugin/modules/connection}/ProjectConfigRegistryImplTest.java (78%) diff --git a/CHANGES.md b/CHANGES.md index cfaf9ce7..b3f67c0c 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -7,7 +7,7 @@ * Bug fixes * Changed supported IDE versions - removed support for all versions of the IDE before v203. -* Add support for compile and test against additional IDE versions. +* Refactored to simplify connection configuration code. This is starting a change to the code * Upgraded referenced libraries. @@ -22,6 +22,8 @@ * Old versions of IntelliJ libraries, used for compiling, are still in the `lib` directory, but prefixed with "o". These were hard to create and, even though they remain in source control, I'm hard-pressed to part with them. * Upgraded compiler compatibility to JDK 11. * Nearly all bundled libraries have been updated. The included `bom.xml` file includes the updated information. +* Refactored to simplify connection configuration code. + * This is starting a change to the code to be organized around specific, distinct bits of functionality, rather than around the IDE extension points. ## ::v0.11.3:: diff --git a/docs/dev/architecture.md b/docs/dev/architecture.md index 442d4612..174a45ab 100644 --- a/docs/dev/architecture.md +++ b/docs/dev/architecture.md @@ -4,130 +4,75 @@ To connect to the server, several layers are used: -* [Connection Information](#connection-information) defines *how* the plugin - contacts the server. These are constructed on a per-VCS root basis. -* [Connection Runner](#connection-runner) performs a specific request against - the server using connection information. +* [Connection Information](#connection-information) defines *how* the plugin contacts the server. These are constructed on a per-VCS root basis. +* [Connection Runner](#connection-runner) performs a specific request against the server using connection information. * [Caches](#caches) maintain an offline data store of the known server state. -* [Error Handlers](#error-handlers) listen to problems reported by the server - and delegate it to responsible parties. +* [Error Handlers](#error-handlers) listen to problems reported by the server and delegate it to responsible parties. ## Connection Information -* `ConfigPart`: describes the connection setting. Multiple of these can be - grouped together in a `MultipleConfigPart`, to allow piecing together - different ways of defining the connection. The parts can change through - user actions, or update themselves by reloading settings from their - source, such as through reload a file or re-reading environment values. -* `ClientConfig`: a snapshot of a `ConfigPart`, which defines a `ServerConfig` - and additional settings on top of it for a specific client workspace. - These objects can be shared across projects to keep internal caches - synchronized. If the underlying `ConfigPart` elements change, then - new `ServerConfig` and `ClientnConfig` instances must be created. -* `ClientConfigState` and `ServerConfigState` keep stateful information about - the configs, such as whether the connection represented by the object - is online. +* `ConfigPart`: describes the connection setting. Multiple of these can be grouped together in a `MultipleConfigPart`, to allow piecing together different ways of defining the connection. The parts can change through user actions, or update themselves by reloading settings from their source, such as through reload a file or re-reading environment values. +* `ClientConfig`: a snapshot of a `ConfigPart`, which defines a `ServerConfig` and additional settings on top of it for a specific client workspace. These objects can be shared across projects to keep internal caches synchronized. If the underlying `ConfigPart` elements change, then new `ServerConfig` and `ClientnConfig` instances must be created. +* `ClientConfigState` and `ServerConfigState` keep stateful information about the configs, such as whether the connection represented by the object is online. + +On top of these configuration state containers, they are managed through two modules: + +* `VcsRootConfigController` is a project-level controller on top of the model. It manages the message bus interaction with the underlying state. + * `PersistentRootConfigComponent` stores the list of user-configured `ConfigPart` objects with each VCS root for the project. This is registered through the IDE component mechanism, and uses its storage mechanism to persist the state. This is the *model* part of the user configuration. + * `P4VcsRootConfigurable` is the UI component for allowing user interaction with the connection configuration. + * Messages to and from this module are at the project level. +* `ProjectConfigRegistry` (and its implementation, `ProjectConfigRegistryImpl`) maintains all active client connections. It initializes itself directly from the `VcsRootConfigController`, but from there it's using the message bus to keep the client connection information up-to-date with the user configuration. + ## Connection Runner -The `P4CommandRunner` interface allows for a unified way to interact with -the server, or a client-side cache on top of the server. The interface itself -is broken into methods that define the different ways to interact with the -server, while a robust type system delegates the actual behavior requested -to other objects. +The `P4CommandRunner` interface allows for a unified way to interact with the server, or a client-side cache on top of the server. The interface itself is broken into methods that define the different ways to interact with the server, while a robust type system delegates the actual behavior requested to other objects. -The runner explicitly works with `Promise`-like objects in order to make clear the -commands which can potentially take a while. Some commands also clearly reference -potential cached values, allowing for more immediate responses. This forces -conformity to the [threading model](threading.md). +The runner explicitly works with `Promise`-like objects in order to make clear the commands which can potentially take a while. Some commands also clearly reference potential cached values, allowing for more immediate responses. This forces conformity to the [threading model](threading.md). -The runner sends the requester very simple errors. The majority of the errors -are passed as messages through the [error handlers](#error-handlers). Code that -directly runs the connection runners only need to deal with simplified errors -to understand that an error happened, and react accordingly. The real work -of communicating the error to the user is done through -[message listeners](#message-listeners). +The runner sends the requester very simple errors. The majority of the errors are passed as messages through the [error handlers](#error-handlers). Code that directly runs the connection runners only need to deal with simplified errors to understand that an error happened, and react accordingly. The real work of communicating the error to the user is done through [message listeners](#message-listeners). ### Server Command Runners -For the command runners that make connections to the server itself, they use a -`ConnectionManager` to handle potential connection pools. +For the command runners that make connections to the server itself, they use a `ConnectionManager` to handle potential connection pools. -The `SimpleConnectionManager` does not maintain a pool, but has the downside that -it does nothing to limit the number of concurrent connections. For some users with -slow connections, this can potentially open too many connections to the server, -causing the server to reject connections. It should only be used for testing -purposes. +The `SimpleConnectionManager` does not maintain a pool, but has the downside that it does nothing to limit the number of concurrent connections. For some users with slow connections, this can potentially open too many connections to the server, +causing the server to reject connections. It should only be used for testing purposes. -The low-level server runners do not maintain caches. However, when they receive -data or requests for actions, they send out the cache update messages. This -restricts the sources for the cache updates, so that the message sending doesn't -need to be remembered to be added to a bunch of different places, but instead -just one (well, each server implementation). +The low-level server runners do not maintain caches. However, when they receive data or requests for actions, they send out the cache update messages. This restricts the sources for the cache updates, so that the message sending doesn't need to be remembered to be added to a bunch of different places, but instead just one (well, each server implementation). ### Cached Command Runners -The cached command runners delegate commands to the server runners. Updates -to the caches is done through the messaging system. The cached server runners -know how to return immediate values for queries. These runners should sit -on top of caches, rather than manage the cache themselves. +The cached command runners delegate commands to the server runners. Updates to the caches is done through the messaging system. The cached server runners know how to return immediate values for queries. These runners should sit on top of caches, rather than manage the cache themselves. ## Caches -Caches maintain a limited copy of information about the server state. -They are [stored at the project level](cache.md). They must be adaptable -enough to understand how pending requests to the server can affect requests -for information. +Caches maintain a limited copy of information about the server state. They are [stored at the project level](cache.md). They must be adaptable enough to understand how pending requests to the server can affect requests for information. -Caches are not expected to store all the server information, but rather just -enough for a client workspace. This means that some requests, like "add a file", -must instead be flexible to run as "add or edit". +Caches are not expected to store all the server information, but rather just enough for a client workspace. This means that some requests, like "add a file", must instead be flexible to run as "add or edit". -Cache updates are sent through the application message bus, so that all open -projects can update their internal data. Pending actions, however, are -only stored in the originating project. This can have a possible down side, -where the same pending action is stored multiple times, but that is considered -a user problem ;) . +Cache updates are sent through the application message bus, so that all open projects can update their internal data. Pending actions, however, are only stored in the originating project. This can have a possible down side, where the same pending action is stored multiple times, but that is considered a user problem ;) . -One potential issue to look out for is the issue of shared state between -components, as seen with the -[Facebook bug](https://www.infoq.com/news/2014/05/facebook-mvc-flux) that -caused them to redesign their client applications using the -[Flux model](https://reactjs.org/docs/lifting-state-up.html). The key take-away -from that bug was that bi-directional data flow can cause fundamental design -issues. Their solution with a dispatcher closely resembles the message bus, -so keeping to the cache model updates through the message bus should mitigate -these kinds of issues. +One potential issue to look out for is the issue of shared state between components, as seen with the [Facebook bug](https://www.infoq.com/news/2014/05/facebook-mvc-flux) that caused them to redesign their client applications using the [Flux model](https://reactjs.org/docs/lifting-state-up.html). The key take-away from that bug was that bi-directional data flow can cause fundamental design issues. Their solution with a dispatcher closely resembles the message bus, so keeping to the cache model updates through the message bus should mitigate these kinds of issues. ## Error Handlers -The `P4RequestErrorHandler` wraps calls to the Perforce Java API and translates -exceptions into how the plugin can deal with them. This can mean broadcasting -error messages to the appropriate message bus, and creating simplified -`ServerResultException` errors. +The `P4RequestErrorHandler` wraps calls to the Perforce Java API and translates exceptions into how the plugin can deal with them. This can mean broadcasting error messages to the appropriate message bus, and creating simplified `ServerResultException` errors. -The `MessageP4RequestErrorHandler` subclass handles all the dirty work of -figuring out what the thrown exception means. +The `MessageP4RequestErrorHandler` subclass handles all the dirty work of figuring out what the thrown exception means. ### Message Listeners -The error handlers broadcast error messages to either the application -message bus or the project message bus. +The error handlers broadcast error messages to either the application message bus or the project message bus. -Application bus error messages are -restricted to information about the connection state of a server, so that all -projects can share the information, rather than having to rediscover it -for themselves. It also means that errors such as password problems are only +Application bus error messages are restricted to information about the connection state of a server, so that all projects can share the information, rather than having to rediscover it for themselves. It also means that errors such as password problems are only communicated to the user once for the entire application (with a big caveat here). -Project bus error messages are all the other issues, such as file write problems -or underlying API errors. These errors relate more to the requested action, -and so are restricted to the project originating the request. +Project bus error messages are all the other issues, such as file write problems or underlying API errors. These errors relate more to the requested action, and so are restricted to the project originating the request. -There are many categories of errors. These are split up in the -`net.groboclown.p4.server.api.messagebus` package. +There are many categories of errors. These are split up in the `net.groboclown.p4.server.api.messagebus` package. diff --git a/docs/dev/connection.md b/docs/dev/connection.md index d6ffac48..103bfaa3 100644 --- a/docs/dev/connection.md +++ b/docs/dev/connection.md @@ -1,30 +1,19 @@ # Configuration and Connection Management -The big idea to keep in mind with connection management is that -the server layer will, by necessity, need to maintain a cache of pending commands. -Also, a user can potentially have multiple projects open that work with the -same workspace. However, [cache data must be owned by the project](cache.md). +The big idea to keep in mind with connection management is that the server layer will, by necessity, need to maintain a cache of pending commands. Also, a user can potentially have multiple projects open that work with the same workspace. However, [cache data must be owned by the project](cache.md). ## Configuration -The user can declare their connection setup as collections of `ConfigPart` instances. -These have the potential to be mutable. For example, a file-based configuration -could change, making the `ConfigPart` reload different values. +The user can declare their connection setup as collections of `ConfigPart` instances. These have the potential to be mutable. For example, a file-based configuration could change, making the `ConfigPart` reload different values. -The stores of connection information must keep immutable configuration objects. -These are stored as `P4ServerName`, `ServerConfig`, and `ClientConfig` instances. -These need to properly handle `equals(Object)` semantics so that they can be shared -and compared between projects. +The stores of connection information must keep immutable configuration objects. These are stored as `P4ServerName`, `ServerConfig`, and `ClientConfig` instances. These need to properly handle `equals(Object)` semantics so that they can be shared and compared between projects. ### Configuration in the UI -With the newer IDEA versions, each root directory for the version control plugin can -have its specific configuration. This needs to be used for our plugin, so that the -user can more easily have different roots point to different servers or workspaces. +With the newer IDEA versions, each root directory for the version control plugin can have its specific configuration. This needs to be used for our plugin, so that the user can more easily have different roots point to different servers or workspaces. -With this change, we can remove support for "relative file" configuration, and instead -just require the user to specify the setup in each root. +With this change, we can remove support for "relative file" configuration, and instead just require the user to specify the setup in each root. ### Requesting the User Password @@ -33,14 +22,9 @@ This directly ties into the [threading model](threading.md). ## Connections -The server will need to maintain a store of connections and their caches that should -be persisted from one execution to the next. This means that care must be taken -to generate messages that distinguish between when a project is closed and it no longer -needs to keep its connections in memory, and when the user removes or changes -connection settings. +The server will need to maintain a store of connections and their caches that should be persisted from one execution to the next. This means that care must be taken to generate messages that distinguish between when a project is closed and it no longer needs to keep its connections in memory, and when the user removes or changes connection settings. ## The Old Way -Connections to the server, and the configuration of the connections caused all -kinds of issues in the old versions. +Connections to the server, and the configuration of the connections caused all kinds of issues in the old versions. diff --git a/docs/dev/threading.md b/docs/dev/threading.md index 16522365..2862f08b 100644 --- a/docs/dev/threading.md +++ b/docs/dev/threading.md @@ -2,71 +2,42 @@ ## EDT vs Worker Threads -The EDT is the *event dispatch thread*, which handles all the Swing events. -The Swing UI events are handled in a single event, all the things from handling -a keystroke to drawing on the screen. This means all execution in the EDT -must be as quick as possible. Anything that takes time to run must execute in -a worker thread. +The EDT is the *event dispatch thread*, which handles all the Swing events. The Swing UI events are handled in a single event, all the things from handling a keystroke to drawing on the screen. This means all execution in the EDT must be as quick as possible. Anything that takes time to run must execute in a worker thread. -Where possible, all API calls should know their execution thread context, -whether they run in the EDT or in a worker thread. +Where possible, all API calls should know their execution thread context, whether they run in the EDT or in a worker thread. ## EDT Required Calls -Some API invoked functions from the IDE forces the plugin code to run in the -EDT. We need to make sure that these are clearly marked, and that the code -paths are closely examined to restrict what they can do. +Some API invoked functions from the IDE forces the plugin code to run in the EDT. We need to make sure that these are clearly marked, and that the code paths are closely examined to restrict what they can do. ## Preventing Accidental Blocks -One way to prevent accidental invocation of blocking code is to force -blocking code to return a Promise instance. This clearly marks that the -command will potentially take a long time, and that the handling of the -invocation will be handled in a worker thread. +One way to prevent accidental invocation of blocking code is to force blocking code to return a Promise instance. This clearly marks that the command will potentially take a long time, and that the handling of the invocation will be handled in a worker thread. -If an EDT equivalent invocation is required, then it should be in a -"sync" version of the method, and should have several modes of invoking: +If an EDT equivalent invocation is required, then it should be in a "sync" version of the method, and should have several modes of invoking: -* *A quick glimpse:* just return the last cached value, if available. - The invocation doesn't really care that much to get a perfect answer. -* *Last and Future:* it returns an object that contains the most recent - cached answer, and a Promise for when the most recent data is - fetched. This has a potential for extreme optimization by sharing a - single promise for all requests while a long running call is in - progress. +* *A quick glimpse:* just return the last cached value, if available. The invocation doesn't really care that much to get a perfect answer. +* *Last and Future:* it returns an object that contains the most recent cached answer, and a Promise for when the most recent data is fetched. This has a potential for extreme optimization by sharing a single promise for all requests while a long running call is in progress. -Some EDT invocations are required, such as to obtain a write lock on a -file. In these cases, the IDEA API should be used to run really small -pieces of work. Don't take advantage of this generosity by throwing -a bunch of work into it in a loop. +Some EDT invocations are required, such as to obtain a write lock on a file. In these cases, the IDEA API should be used to run really small pieces of work. Don't take advantage of this generosity by throwing a bunch of work into it in a loop. ### Promises and the EDT -Promises can help limit the time in the EDT if it's necessary. You can -chain promises together with different pieces of work running in the -EDT and others in worker threads. +Promises can help limit the time in the EDT if it's necessary. You can chain promises together with different pieces of work running in the EDT and others in worker threads. ## Requiring User Interaction -User interaction shouldn't be required, unless the user explicitly declares -that they want to do something and the UI tells them through iconography that -it will require additional interaction, like open a file dialog or typing in -a password. +User interaction shouldn't be required, unless the user explicitly declares that they want to do something and the UI tells them through iconography that it will require additional interaction, like open a file dialog or typing in a password. -If an API call requires immediate response, we *must not* go ask the user. -User preferences are fine, but not a dialog. If, without user input, we -don't know what to do, then report a failure. +If an API call requires immediate response, we *must not* go ask the user. User preferences are fine, but not a dialog. If, without user input, we don't know what to do, then report a failure. -If the API call comes from a UI component that we have some control over, then -we should indicate on the UI component that user feedback is required. +If the API call comes from a UI component that we have some control over, then we should indicate on the UI component that user feedback is required. ## Old Version Bugs -One of the big problems with the old versions of the plugin was requesting -user feedback. This would turn into nightmare scenarios when called from the -EDT. +One of the big problems with the old versions of the plugin was requesting user feedback. This would turn into nightmare scenarios when called from the EDT. diff --git a/idea-p4server/api/src/main/java/net/groboclown/p4/server/api/ProjectConfigRegistry.java b/idea-p4server/api/src/main/java/net/groboclown/p4/server/api/ProjectConfigRegistry.java index 0574d30d..6b8f69d6 100644 --- a/idea-p4server/api/src/main/java/net/groboclown/p4/server/api/ProjectConfigRegistry.java +++ b/idea-p4server/api/src/main/java/net/groboclown/p4/server/api/ProjectConfigRegistry.java @@ -15,16 +15,16 @@ package net.groboclown.p4.server.api; import com.intellij.openapi.Disposable; -import com.intellij.openapi.components.ProjectComponent; +import com.intellij.openapi.components.Service; import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.project.Project; import com.intellij.openapi.util.Disposer; import com.intellij.openapi.vcs.FilePath; -import com.intellij.openapi.vcs.ProjectLevelVcsManager; import com.intellij.openapi.vfs.VirtualFile; import com.perforce.p4java.exception.AuthenticationFailedException; import net.groboclown.p4.server.api.config.ClientConfig; import net.groboclown.p4.server.api.config.OptionalClientServerConfig; +import net.groboclown.p4.server.api.config.part.ConfigPart; import net.groboclown.p4.server.api.messagebus.ClientConfigAddedMessage; import net.groboclown.p4.server.api.messagebus.ClientConfigRemovedMessage; import net.groboclown.p4.server.api.messagebus.ConnectionErrorMessage; @@ -34,20 +34,24 @@ import net.groboclown.p4.server.api.messagebus.ServerConnectedMessage; import net.groboclown.p4.server.api.messagebus.ServerErrorEvent; import net.groboclown.p4.server.api.messagebus.UserSelectedOfflineMessage; +import net.groboclown.p4.server.api.messagebus.VcsRootClientPartsMessage; import net.groboclown.p4.server.api.util.FileTreeUtil; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; +import javax.annotation.Nonnull; import java.util.ArrayList; import java.util.Collection; +import java.util.List; /** * Stores the registered configurations for a specific project. The registry must * also inform the application server connection registry about the configurations, so that * the correct counters can be preserved. */ +@Service(Service.Level.PROJECT) public abstract class ProjectConfigRegistry - implements ProjectComponent, Disposable { + implements Disposable { public static final Class COMPONENT_CLASS = ProjectConfigRegistry.class; public static final String COMPONENT_NAME = COMPONENT_CLASS.getName(); @@ -155,45 +159,7 @@ public Collection getClientConfigRoots() { @Nullable public abstract ClientConfig getRegisteredClientConfigState(@NotNull ClientServerRef ref); - /** - * Registers the client configuration to the project and the application. If a configuration with the same - * client-server reference is already registered, then it will be removed. If that configuration is the exact - * same as the requested added configuration, then it will still be removed then re-added. - * - * FIXME this must instead be first managed through the PersistentRootConfigComponent to handle the list - * of ConfigPart instances, then this class must be notified of the root's update. - * - * @param config configuration to register - * @param vcsRootDir root directory for the configuration. - */ - public abstract void addClientConfig(@NotNull ClientConfig config, @NotNull VirtualFile vcsRootDir); - - /** - * Removes the client configuration registration with the given reference. If it is registered, then - * the appropriate messages will be sent out. - * - * FIXME this must instead be first managed through the PersistentRootConfigComponent to handle the list - * of ConfigPart instances, then this class must be notified of the root's update. - * - * @param vcsRootDir the VCS root directory to de-register; if it is not exactly a known root directory, this will - * do nothing. - * @return true if it was registered, false if not. - */ - public abstract boolean removeClientConfigAt(@NotNull VirtualFile vcsRootDir); - - @Override - public void projectOpened() { - // Initial startup needs to read in the list of roots. - updateVcsRoots(); - } - - @Override - public final void projectClosed() { - disposeComponent(); - } - - @Override - public void initComponent() { + public void initializeService() { LoginFailureMessage.addListener(applicationBusClient, this, new LoginFailureMessage.AllErrorListener() { @Override public void onLoginFailure(@NotNull ServerErrorEvent.ServerConfigErrorEvent e) { @@ -242,10 +208,20 @@ public void reconnectToClient(@NotNull ReconnectRequestMessage.ReconnectEvent e) } }); + VcsRootClientPartsMessage.addListener(projectBusClient, this, new VcsRootClientPartsMessage.Listener() { + @Override + public void vcsRootClientPartsRemoved( + @Nonnull VcsRootClientPartsMessage.VcsRootClientPartsRemovedEvent event) { + removeClientConfigAt(event.getVcsRoot()); + } + + @Override + public void vcsRootUpdated(@Nonnull VcsRootClientPartsMessage.VcsRootClientPartsUpdatedEvent event) { + updateClientConfigAt(event.getVcsRoot(), event.getParts()); + } + }); - // Explicitly avoid the ProjectMessage, because this is an event owned by the IDE. - projectBusClient.add(ProjectLevelVcsManager.VCS_CONFIGURATION_CHANGED, this::updateVcsRoots); - projectBusClient.add(ProjectLevelVcsManager.VCS_CONFIGURATION_CHANGED_IN_PLUGIN, this::updateVcsRoots); + initializeRoots(); } public boolean isDisposed() { @@ -260,17 +236,6 @@ public void dispose() { } } - @Override - public final void disposeComponent() { - dispose(); - } - - @NotNull - @Override - public final String getComponentName() { - return COMPONENT_NAME; - } - @NotNull protected final Project getProject() { return project; @@ -319,5 +284,9 @@ protected final void sendClientAdded(@Nullable ClientConfigRoot state) { protected abstract void onUserSelectedAllOnline(); - protected abstract void updateVcsRoots(); + protected abstract void updateClientConfigAt(@NotNull VirtualFile vcsRoot, @NotNull List parts); + + protected abstract void removeClientConfigAt(@NotNull VirtualFile vcsRootDir); + + protected abstract void initializeRoots(); } diff --git a/idea-p4server/api/src/main/java/net/groboclown/p4/server/api/cache/messagebus/ClientActionMessage.java b/idea-p4server/api/src/main/java/net/groboclown/p4/server/api/cache/messagebus/ClientActionMessage.java index c61999ce..db249511 100644 --- a/idea-p4server/api/src/main/java/net/groboclown/p4/server/api/cache/messagebus/ClientActionMessage.java +++ b/idea-p4server/api/src/main/java/net/groboclown/p4/server/api/cache/messagebus/ClientActionMessage.java @@ -47,13 +47,13 @@ public enum ActionState { public static class Event extends AbstractCacheUpdateEvent { - private final P4CommandRunner.ClientAction action; + private final P4CommandRunner.ClientAction action; private final P4CommandRunner.ClientResult result; private final ActionState state; private final P4CommandRunner.ServerResultException error; public Event(@NotNull ClientServerRef ref, - @NotNull P4CommandRunner.ClientAction action) { + @NotNull P4CommandRunner.ClientAction action) { super(ref); this.action = action; this.result = null; @@ -62,7 +62,7 @@ public Event(@NotNull ClientServerRef ref, } public Event(@NotNull ClientServerRef ref, - @NotNull P4CommandRunner.ClientAction action, + @NotNull P4CommandRunner.ClientAction action, @NotNull P4CommandRunner.ClientResult result) { super(ref); this.action = action; @@ -72,7 +72,7 @@ public Event(@NotNull ClientServerRef ref, } public Event(@NotNull ClientServerRef ref, - @NotNull P4CommandRunner.ClientAction action, + @NotNull P4CommandRunner.ClientAction action, @NotNull P4CommandRunner.ServerResultException error) { super(ref); this.action = action; @@ -81,7 +81,7 @@ public Event(@NotNull ClientServerRef ref, this.error = error; } - public P4CommandRunner.ClientAction getAction() { + public P4CommandRunner.ClientAction getAction() { return action; } diff --git a/idea-p4server/api/src/main/java/net/groboclown/p4/server/api/messagebus/VcsRootClientPartsMessage.java b/idea-p4server/api/src/main/java/net/groboclown/p4/server/api/messagebus/VcsRootClientPartsMessage.java new file mode 100644 index 00000000..4a2c070e --- /dev/null +++ b/idea-p4server/api/src/main/java/net/groboclown/p4/server/api/messagebus/VcsRootClientPartsMessage.java @@ -0,0 +1,115 @@ +/* + * 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 net.groboclown.p4.server.api.messagebus; + +import com.intellij.openapi.project.Project; +import com.intellij.openapi.vfs.VirtualFile; +import com.intellij.util.messages.Topic; +import net.groboclown.p4.server.api.config.part.ConfigPart; +import org.jetbrains.annotations.NotNull; + +import javax.annotation.Nonnull; +import java.util.List; + +/** + * Indicates that a Vcs Root user configuration of the client parts was either added or changed. + */ +public class VcsRootClientPartsMessage + extends ProjectMessage { + private static final String DISPLAY_NAME = "p4ic4idea:vcs root client parts"; + private static final Topic TOPIC = new Topic<>( + DISPLAY_NAME, + VcsRootClientPartsMessage.Listener.class, + Topic.BroadcastDirection.TO_CHILDREN); + private static final VcsRootClientPartsMessage.Listener + DEFAULT_LISTENER = new VcsRootClientPartsMessage.ListenerAdapter(); + + /** + * Event object describing the vcs root client part. + */ + public static class VcsRootClientPartsRemovedEvent { + private final VirtualFile vcsRoot; + + public VcsRootClientPartsRemovedEvent(@Nonnull VirtualFile vcsRoot) { + this.vcsRoot = vcsRoot; + } + + public VirtualFile getVcsRoot() { + return vcsRoot; + } + } + + /** + * Event object describing the updated client parts for the vcs root. + */ + public static class VcsRootClientPartsUpdatedEvent { + private final VirtualFile vcsRoot; + private final List parts; + + public VcsRootClientPartsUpdatedEvent(@Nonnull VirtualFile vcsRoot, @Nonnull List parts) { + this.vcsRoot = vcsRoot; + this.parts = List.copyOf(parts); + } + + @Nonnull + public VirtualFile getVcsRoot() { + return vcsRoot; + } + + @Nonnull + public List getParts() { + return parts; + } + } + + + public interface Listener { + /** The VCS root's associated config parts have been removed. */ + void vcsRootClientPartsRemoved(@NotNull VcsRootClientPartsRemovedEvent event); + + /** The VCS root was either added or the associated parts with the root were updated. */ + void vcsRootUpdated(@NotNull VcsRootClientPartsUpdatedEvent event); + } + + public static class ListenerAdapter implements Listener { + @Override + public void vcsRootClientPartsRemoved(@Nonnull VcsRootClientPartsRemovedEvent event) { + } + + @Override + public void vcsRootUpdated(@Nonnull VcsRootClientPartsUpdatedEvent event) { + } + } + + public static void sendVcsRootClientPartsRemoved(@NotNull Project project, @Nonnull VirtualFile vcsRoot) { + if (canSendMessage(project)) { + getListener(project, TOPIC, DEFAULT_LISTENER).vcsRootClientPartsRemoved( + new VcsRootClientPartsRemovedEvent(vcsRoot)); + } + } + + public static void sendVcsRootClientPartsUpdated( + @NotNull Project project, @Nonnull VirtualFile vcsRoot, @Nonnull List parts) { + if (canSendMessage(project)) { + getListener(project, TOPIC, DEFAULT_LISTENER).vcsRootUpdated( + new VcsRootClientPartsUpdatedEvent(vcsRoot, parts)); + } + } + + public static void addListener(@NotNull MessageBusClient.ProjectClient client, + @NotNull Object listenerOwner, @NotNull VcsRootClientPartsMessage.Listener listener) { + addListener(client, TOPIC, listener, VcsRootClientPartsMessage.Listener.class, listenerOwner); + } +} diff --git a/idea-p4server/api/src/test/java/net/groboclown/p4/server/api/ProjectConfigRegistryTest.java b/idea-p4server/api/src/test/java/net/groboclown/p4/server/api/ProjectConfigRegistryTest.java index 6b939e13..ef8facf3 100644 --- a/idea-p4server/api/src/test/java/net/groboclown/p4/server/api/ProjectConfigRegistryTest.java +++ b/idea-p4server/api/src/test/java/net/groboclown/p4/server/api/ProjectConfigRegistryTest.java @@ -21,6 +21,7 @@ import net.groboclown.p4.server.api.config.ClientConfig; import net.groboclown.p4.server.api.config.OptionalClientServerConfig; import net.groboclown.p4.server.api.config.ServerConfig; +import net.groboclown.p4.server.api.config.part.ConfigPart; import net.groboclown.p4.server.api.messagebus.ServerConnectedMessage; import net.groboclown.p4.server.api.messagebus.UserSelectedOfflineMessage; import net.groboclown.p4.server.api.config.part.MockConfigPart; @@ -98,12 +99,17 @@ public ClientConfig getRegisteredClientConfigState(@NotNull ClientServerRef ref) } @Override - public void addClientConfig(@NotNull ClientConfig config, @NotNull VirtualFile vcsRootDir) { - states.add(new MockClientConfigRoot(config, vcsRootDir)); + protected void updateClientConfigAt(@Nonnull VirtualFile vcsRoot, @Nonnull List parts) { + throw new IllegalStateException(); } @Override - public boolean removeClientConfigAt(@NotNull VirtualFile ref) { + public void removeClientConfigAt(@NotNull VirtualFile ref) { + throw new IllegalStateException(); + } + + @Override + protected void initializeRoots() { throw new IllegalStateException(); } @@ -149,9 +155,8 @@ protected void onUserSelectedAllOnline() { throw new IllegalStateException(); } - @Override - protected void updateVcsRoots() { - throw new IllegalStateException(); + void addClientConfig(@NotNull ClientConfig cc, @NotNull VirtualFile root) { + states.add(new MockClientConfigRoot(cc, root)); } } diff --git a/plugin-v3/src/main/java/net/groboclown/p4plugin/components/CacheComponent.java b/plugin-v3/src/main/java/net/groboclown/p4plugin/components/CacheComponent.java index 177402f4..bf344ca7 100644 --- a/plugin-v3/src/main/java/net/groboclown/p4plugin/components/CacheComponent.java +++ b/plugin-v3/src/main/java/net/groboclown/p4plugin/components/CacheComponent.java @@ -53,11 +53,7 @@ @State( name = "p4-ProjectCache", - storages = { - @Storage( - file = StoragePathMacros.WORKSPACE_FILE - ) - } + storages = @Storage(StoragePathMacros.WORKSPACE_FILE) ) public class CacheComponent implements ProjectComponent, PersistentStateComponent, diff --git a/plugin-v3/src/main/java/net/groboclown/p4plugin/extension/P4CheckinEnvironment.java b/plugin-v3/src/main/java/net/groboclown/p4plugin/extension/P4CheckinEnvironment.java index 51c9c446..092fe206 100644 --- a/plugin-v3/src/main/java/net/groboclown/p4plugin/extension/P4CheckinEnvironment.java +++ b/plugin-v3/src/main/java/net/groboclown/p4plugin/extension/P4CheckinEnvironment.java @@ -61,7 +61,6 @@ import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; -import javax.annotation.Nonnull; import javax.swing.*; import java.util.ArrayList; import java.util.Collection; @@ -93,7 +92,7 @@ public class P4CheckinEnvironment implements CheckinEnvironment, CommitExecutor @Override @Deprecated public RefreshableOnComponent createAdditionalOptionsPanel( - @Nonnull CheckinProjectPanel panel, @Nonnull PairConsumer additionalDataConsumer) { + @NotNull CheckinProjectPanel panel, @NotNull PairConsumer additionalDataConsumer) { // #52 - we could be able to monitor panel.getCommitMessage(); to ensure // that there's a message, and when there isn't, disable the submit // button. @@ -111,7 +110,7 @@ public RefreshableOnComponent createAdditionalOptionsPanel( @Nullable @Override - public String getDefaultMessageFor(@Nonnull FilePath[] filesToCheckin) { + public String getDefaultMessageFor(@NotNull FilePath[] filesToCheckin) { return null; } @@ -127,20 +126,23 @@ public String getCheckinOperationName() { } @Override - public @Nullable List commit(@NotNull List changes, + @Nullable + public List commit(@NotNull List changes, @NotNull @NlsSafe String preparedComment) { return CheckinEnvironment.super.commit(changes, preparedComment); } @Override - public @Nullable List commit(@NotNull List changes, + @Nullable + public List commit(@NotNull List changes, @NotNull @NlsSafe String commitMessage, @NotNull CommitContext commitContext, @NotNull Set feedback) { return CheckinEnvironment.super.commit(changes, commitMessage, commitContext, feedback); } @Override - public @Nullable List commit(@NotNull List changes, @NotNull String preparedComment, + @Nullable + public List commit(@NotNull List changes, @NotNull String preparedComment, @NotNull NullableFunction parametersHolder, @NotNull Set feedback) { return CheckinEnvironment.super.commit(changes, preparedComment, parametersHolder, feedback); @@ -264,6 +266,7 @@ private P4ChangelistId getActiveChangelistFor(ClientConfigRoot root, Map changes, String commitMessage) { + public JComponent getAdditionalConfigurationUI(@NotNull Collection changes, String commitMessage) { model.setSelectedCurrentChanges(changes); return new SubmitPanel(model).getRoot(); } @@ -326,7 +329,7 @@ public boolean canExecute(Collection changes, String commitMessage) { } @Override - public void execute(@Nonnull Collection changes, String commitMessage) { + public void execute(@NotNull Collection changes, String commitMessage) { List changeList; if (changes instanceof List) { changeList = (List) changes; diff --git a/plugin-v3/src/main/java/net/groboclown/p4plugin/extension/P4Vcs.java b/plugin-v3/src/main/java/net/groboclown/p4plugin/extension/P4Vcs.java index cc5dccbc..434522cc 100644 --- a/plugin-v3/src/main/java/net/groboclown/p4plugin/extension/P4Vcs.java +++ b/plugin-v3/src/main/java/net/groboclown/p4plugin/extension/P4Vcs.java @@ -55,7 +55,7 @@ import net.groboclown.p4plugin.messages.CompatibilityCheck; import net.groboclown.p4plugin.ui.ColorUtil; import net.groboclown.p4plugin.ui.config.P4ProjectConfigurable; -import net.groboclown.p4plugin.ui.vcsroot.P4VcsRootConfigurable; +import net.groboclown.p4plugin.modules.clientconfig.view.P4VcsRootConfigurable; import org.jetbrains.annotations.NonNls; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; diff --git a/idea-p4server/impl/src/main/java/net/groboclown/p4/server/impl/config/PersistentRootConfigComponent.java b/plugin-v3/src/main/java/net/groboclown/p4plugin/modules/clientconfig/PersistentRootConfigModel.java similarity index 80% rename from idea-p4server/impl/src/main/java/net/groboclown/p4/server/impl/config/PersistentRootConfigComponent.java rename to plugin-v3/src/main/java/net/groboclown/p4plugin/modules/clientconfig/PersistentRootConfigModel.java index 53969ebd..83c6d558 100644 --- a/idea-p4server/impl/src/main/java/net/groboclown/p4/server/impl/config/PersistentRootConfigComponent.java +++ b/plugin-v3/src/main/java/net/groboclown/p4plugin/modules/clientconfig/PersistentRootConfigModel.java @@ -12,10 +12,9 @@ * limitations under the License. */ -package net.groboclown.p4.server.impl.config; +package net.groboclown.p4plugin.modules.clientconfig; import com.intellij.openapi.components.PersistentStateComponent; -import com.intellij.openapi.components.ProjectComponent; import com.intellij.openapi.components.State; import com.intellij.openapi.components.Storage; import com.intellij.openapi.components.StoragePathMacros; @@ -34,49 +33,40 @@ import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; -import javax.annotation.Nonnull; import java.util.ArrayList; -import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; /** - * Storage for configuration per VCS root. + * Storage for configuration per VCS root. WARNING this should only be accessed by the controller + * ({@link VcsRootConfigController}). */ @State( name = "p4-PersistentRootConfigComponent", - storages = { - @Storage( - file = StoragePathMacros.WORKSPACE_FILE - ) - } + storages = @Storage(StoragePathMacros.WORKSPACE_FILE) ) -public class PersistentRootConfigComponent - implements ProjectComponent, PersistentStateComponent { - private static final Logger LOG = Logger.getInstance(PersistentRootConfigComponent.class); - public static final Class COMPONENT_CLASS = - PersistentRootConfigComponent.class; +public class PersistentRootConfigModel + implements PersistentStateComponent { + private static final Logger LOG = Logger.getInstance(PersistentRootConfigModel.class); + public static final Class COMPONENT_CLASS = + PersistentRootConfigModel.class; private final Project project; private final Object sync = new Object(); private final Map> rootPartMap = new HashMap<>(); - public PersistentRootConfigComponent(@NotNull Project project) { + public PersistentRootConfigModel(@NotNull Project project) { this.project = project; } @Nullable - public static PersistentRootConfigComponent getInstance(@NotNull Project project) { + static PersistentRootConfigModel getInstance(@NotNull Project project) { return project.getComponent(COMPONENT_CLASS); } - boolean hasConfigPartsForRoot(@NotNull VirtualFile root) { - return getConfigPartsForRoot(root) != null; - } - void setConfigPartsForRoot(@NotNull VirtualFile root, @NotNull List parts) { - List copy = Collections.unmodifiableList(new ArrayList<>(parts)); + List copy = List.copyOf(parts); synchronized (sync) { rootPartMap.put(root, copy); if (LOG.isDebugEnabled()) { @@ -100,28 +90,12 @@ List getConfigPartsForRoot(@NotNull VirtualFile root) { } @NotNull - @Override - public String getComponentName() { - return COMPONENT_CLASS.getName(); - } - - @Override - public void projectOpened() { - // do nothing - } - - @Override - public void projectClosed() { - disposeComponent(); - } - - @Override - public void initComponent() { - // do nothing explicit - } - - @Override - public void disposeComponent() { + List getRegisteredRoots() { + List res; + synchronized (sync) { + res = List.copyOf(rootPartMap.keySet()); + } + return res; } @Nullable @@ -145,7 +119,7 @@ public Element getState() { } @Override - public void loadState(@Nonnull Element element) { + public void loadState(@NotNull Element element) { if (element == null || element.getChildren().isEmpty()) { LOG.warn("Loaded null or empty state"); return; diff --git a/plugin-v3/src/main/java/net/groboclown/p4plugin/modules/clientconfig/VcsRootChangeListener.java b/plugin-v3/src/main/java/net/groboclown/p4plugin/modules/clientconfig/VcsRootChangeListener.java new file mode 100644 index 00000000..7111deab --- /dev/null +++ b/plugin-v3/src/main/java/net/groboclown/p4plugin/modules/clientconfig/VcsRootChangeListener.java @@ -0,0 +1,53 @@ +/* + * 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 net.groboclown.p4plugin.modules.clientconfig; + +import com.intellij.openapi.project.Project; +import com.intellij.openapi.vcs.ProjectLevelVcsManager; +import com.intellij.openapi.vcs.VcsListener; +import com.intellij.openapi.vfs.VirtualFile; + +import java.util.HashSet; +import java.util.Set; + +/** + * Listens to VCS root chang events, and updates the corresponding configuration data. + * Both VCS_CONFIGURATION_CHANGED and VCS_CONFIGURATION_CHANGED_IN_PLUGIN topics should be + * covered by this. + *

+ * This listener is registered via the plugin.xml file. + */ +public class VcsRootChangeListener implements VcsListener { + private final Project project; + + public VcsRootChangeListener(Project project) { + this.project = project; + } + + @Override + public void directoryMappingChanged() { + if (project != null) { + final Set currentRoots = + new HashSet<>(VcsRootConfigController.getInstance().getRegisteredRoots(project)); + for (VirtualFile root: ProjectLevelVcsManager.getInstance(project).getAllVersionedRoots()) { + currentRoots.remove(root); + } + // whatever remains is extra. + for (VirtualFile root: currentRoots) { + VcsRootConfigController.getInstance().removeRoot(project, root); + } + } + } +} diff --git a/plugin-v3/src/main/java/net/groboclown/p4plugin/modules/clientconfig/VcsRootConfigController.java b/plugin-v3/src/main/java/net/groboclown/p4plugin/modules/clientconfig/VcsRootConfigController.java new file mode 100644 index 00000000..c7cc6e27 --- /dev/null +++ b/plugin-v3/src/main/java/net/groboclown/p4plugin/modules/clientconfig/VcsRootConfigController.java @@ -0,0 +1,97 @@ +/* + * 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 net.groboclown.p4plugin.modules.clientconfig; + +import com.intellij.openapi.project.Project; +import com.intellij.openapi.vfs.VirtualFile; +import net.groboclown.p4.server.api.config.part.ConfigPart; +import net.groboclown.p4.server.api.messagebus.VcsRootClientPartsMessage; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.Collection; +import java.util.Collections; +import java.util.List; +import java.util.function.Function; + +/** + * Manages access to the model. Does not monitor the VCS roots status - that is handled in the + * + */ +public class VcsRootConfigController { + private static final VcsRootConfigController INSTANCE = new VcsRootConfigController(); + + public static VcsRootConfigController getInstance() { + return INSTANCE; + } + + private VcsRootConfigController() { + // utility class + } + + + /** Does the root have parts? If false, then that means the root is not registered. */ + public boolean hasConfigPartsForRoot(@NotNull final Project project, @NotNull final VirtualFile root) { + return withModel(project, false, + (m) -> m.getConfigPartsForRoot(root) != null); + } + + /** + * Gets the list of config parts for the given VCS root. If the root is not registered, + * then null is returned. + */ + @Nullable + public List getConfigPartsForRoot(@NotNull final Project project, @NotNull final VirtualFile root) { + return withModel(project, null, + (m) -> m.getConfigPartsForRoot(root)); + } + + /** + * Return a collection of all the VCS roots with registered configurations. + */ + @NotNull + public Collection getRegisteredRoots(@NotNull final Project project) { + return withModel(project, Collections.emptyList(), PersistentRootConfigModel::getRegisteredRoots); + } + + /** + * Register or update a VCS root with the configuration parts. + */ + public void setRootConfigParts(@NotNull final Project project, @NotNull final VirtualFile root, + @NotNull final List parts) { + withModel(project, null, (m) -> { + m.setConfigPartsForRoot(root, parts); + VcsRootClientPartsMessage.sendVcsRootClientPartsUpdated(project, root, parts); + return null; + }); + } + + /** + * Remove the VCS root from the registration store. + */ + public void removeRoot(@NotNull final Project project, @NotNull final VirtualFile root) { + + } + + + private T withModel(@NotNull Project project, T defaultValue, + @NotNull Function func) { + PersistentRootConfigModel model = PersistentRootConfigModel.getInstance(project); + if (model == null) { + return defaultValue; + } + return func.apply(model); + } +} diff --git a/plugin-v3/src/main/java/net/groboclown/p4plugin/modules/clientconfig/package-info.java b/plugin-v3/src/main/java/net/groboclown/p4plugin/modules/clientconfig/package-info.java new file mode 100644 index 00000000..36de278f --- /dev/null +++ b/plugin-v3/src/main/java/net/groboclown/p4plugin/modules/clientconfig/package-info.java @@ -0,0 +1,4 @@ +/** + * + */ +package net.groboclown.p4plugin.modules.clientconfig; \ No newline at end of file diff --git a/plugin-v3/src/main/java/net/groboclown/p4plugin/ui/vcsroot/ConfigConnectionController.java b/plugin-v3/src/main/java/net/groboclown/p4plugin/modules/clientconfig/view/ConfigConnectionController.java similarity index 97% rename from plugin-v3/src/main/java/net/groboclown/p4plugin/ui/vcsroot/ConfigConnectionController.java rename to plugin-v3/src/main/java/net/groboclown/p4plugin/modules/clientconfig/view/ConfigConnectionController.java index a9221e54..8ea26fcb 100644 --- a/plugin-v3/src/main/java/net/groboclown/p4plugin/ui/vcsroot/ConfigConnectionController.java +++ b/plugin-v3/src/main/java/net/groboclown/p4plugin/modules/clientconfig/view/ConfigConnectionController.java @@ -12,7 +12,7 @@ * limitations under the License. */ -package net.groboclown.p4plugin.ui.vcsroot; +package net.groboclown.p4plugin.modules.clientconfig.view; import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.project.Project; diff --git a/plugin-v3/src/main/java/net/groboclown/p4plugin/ui/vcsroot/ConfigConnectionListener.java b/plugin-v3/src/main/java/net/groboclown/p4plugin/modules/clientconfig/view/ConfigConnectionListener.java similarity index 94% rename from plugin-v3/src/main/java/net/groboclown/p4plugin/ui/vcsroot/ConfigConnectionListener.java rename to plugin-v3/src/main/java/net/groboclown/p4plugin/modules/clientconfig/view/ConfigConnectionListener.java index 469d768b..b28d2e70 100644 --- a/plugin-v3/src/main/java/net/groboclown/p4plugin/ui/vcsroot/ConfigConnectionListener.java +++ b/plugin-v3/src/main/java/net/groboclown/p4plugin/modules/clientconfig/view/ConfigConnectionListener.java @@ -12,7 +12,7 @@ * limitations under the License. */ -package net.groboclown.p4plugin.ui.vcsroot; +package net.groboclown.p4plugin.modules.clientconfig.view; import com.intellij.openapi.project.Project; import net.groboclown.p4.server.api.config.ClientConfig; diff --git a/plugin-v3/src/main/java/net/groboclown/p4plugin/ui/vcsroot/ConfigPartStack.form b/plugin-v3/src/main/java/net/groboclown/p4plugin/modules/clientconfig/view/ConfigPartStack.form similarity index 96% rename from plugin-v3/src/main/java/net/groboclown/p4plugin/ui/vcsroot/ConfigPartStack.form rename to plugin-v3/src/main/java/net/groboclown/p4plugin/modules/clientconfig/view/ConfigPartStack.form index 5cfb3d3e..cd9f64c6 100644 --- a/plugin-v3/src/main/java/net/groboclown/p4plugin/ui/vcsroot/ConfigPartStack.form +++ b/plugin-v3/src/main/java/net/groboclown/p4plugin/modules/clientconfig/view/ConfigPartStack.form @@ -1,5 +1,5 @@ -

+ diff --git a/plugin-v3/src/main/java/net/groboclown/p4plugin/ui/vcsroot/ConfigPartStack.java b/plugin-v3/src/main/java/net/groboclown/p4plugin/modules/clientconfig/view/ConfigPartStack.java similarity index 95% rename from plugin-v3/src/main/java/net/groboclown/p4plugin/ui/vcsroot/ConfigPartStack.java rename to plugin-v3/src/main/java/net/groboclown/p4plugin/modules/clientconfig/view/ConfigPartStack.java index 61d847dd..a53e71e9 100644 --- a/plugin-v3/src/main/java/net/groboclown/p4plugin/ui/vcsroot/ConfigPartStack.java +++ b/plugin-v3/src/main/java/net/groboclown/p4plugin/modules/clientconfig/view/ConfigPartStack.java @@ -12,7 +12,7 @@ * limitations under the License. */ -package net.groboclown.p4plugin.ui.vcsroot; +package net.groboclown.p4plugin.modules.clientconfig.view; import com.intellij.icons.AllIcons; import com.intellij.openapi.ui.VerticalFlowLayout; @@ -27,12 +27,12 @@ import net.groboclown.p4.server.api.config.part.ConfigPart; import net.groboclown.p4plugin.P4Bundle; import net.groboclown.p4plugin.ui.SwingUtil; -import net.groboclown.p4plugin.ui.vcsroot.part.ClientNamePartUI; -import net.groboclown.p4plugin.ui.vcsroot.part.EnvPartUI; -import net.groboclown.p4plugin.ui.vcsroot.part.FilePartUI; -import net.groboclown.p4plugin.ui.vcsroot.part.PropertiesPartUI; -import net.groboclown.p4plugin.ui.vcsroot.part.RequirePasswordPartUI; -import net.groboclown.p4plugin.ui.vcsroot.part.ServerFingerprintPartUI; +import net.groboclown.p4plugin.modules.clientconfig.view.part.ClientNamePartUI; +import net.groboclown.p4plugin.modules.clientconfig.view.part.EnvPartUI; +import net.groboclown.p4plugin.modules.clientconfig.view.part.FilePartUI; +import net.groboclown.p4plugin.modules.clientconfig.view.part.PropertiesPartUI; +import net.groboclown.p4plugin.modules.clientconfig.view.part.RequirePasswordPartUI; +import net.groboclown.p4plugin.modules.clientconfig.view.part.ServerFingerprintPartUI; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; diff --git a/plugin-v3/src/main/java/net/groboclown/p4plugin/ui/vcsroot/ConfigPartUI.java b/plugin-v3/src/main/java/net/groboclown/p4plugin/modules/clientconfig/view/ConfigPartUI.java similarity index 95% rename from plugin-v3/src/main/java/net/groboclown/p4plugin/ui/vcsroot/ConfigPartUI.java rename to plugin-v3/src/main/java/net/groboclown/p4plugin/modules/clientconfig/view/ConfigPartUI.java index 486e891c..c89117f5 100644 --- a/plugin-v3/src/main/java/net/groboclown/p4plugin/ui/vcsroot/ConfigPartUI.java +++ b/plugin-v3/src/main/java/net/groboclown/p4plugin/modules/clientconfig/view/ConfigPartUI.java @@ -12,7 +12,7 @@ * limitations under the License. */ -package net.groboclown.p4plugin.ui.vcsroot; +package net.groboclown.p4plugin.modules.clientconfig.view; import net.groboclown.p4.server.api.config.part.ConfigPart; import org.jetbrains.annotations.Nls; diff --git a/plugin-v3/src/main/java/net/groboclown/p4plugin/ui/vcsroot/ConfigPartUIFactory.java b/plugin-v3/src/main/java/net/groboclown/p4plugin/modules/clientconfig/view/ConfigPartUIFactory.java similarity index 95% rename from plugin-v3/src/main/java/net/groboclown/p4plugin/ui/vcsroot/ConfigPartUIFactory.java rename to plugin-v3/src/main/java/net/groboclown/p4plugin/modules/clientconfig/view/ConfigPartUIFactory.java index c1c58ef7..fa848c27 100644 --- a/plugin-v3/src/main/java/net/groboclown/p4plugin/ui/vcsroot/ConfigPartUIFactory.java +++ b/plugin-v3/src/main/java/net/groboclown/p4plugin/modules/clientconfig/view/ConfigPartUIFactory.java @@ -12,7 +12,7 @@ * limitations under the License. */ -package net.groboclown.p4plugin.ui.vcsroot; +package net.groboclown.p4plugin.modules.clientconfig.view; import com.intellij.openapi.vfs.VirtualFile; import net.groboclown.p4.server.api.config.part.ConfigPart; diff --git a/plugin-v3/src/main/java/net/groboclown/p4plugin/ui/vcsroot/ConfigPartWrapper.form b/plugin-v3/src/main/java/net/groboclown/p4plugin/modules/clientconfig/view/ConfigPartWrapper.form similarity index 98% rename from plugin-v3/src/main/java/net/groboclown/p4plugin/ui/vcsroot/ConfigPartWrapper.form rename to plugin-v3/src/main/java/net/groboclown/p4plugin/modules/clientconfig/view/ConfigPartWrapper.form index 86d0e691..e8deb7ef 100644 --- a/plugin-v3/src/main/java/net/groboclown/p4plugin/ui/vcsroot/ConfigPartWrapper.form +++ b/plugin-v3/src/main/java/net/groboclown/p4plugin/modules/clientconfig/view/ConfigPartWrapper.form @@ -1,5 +1,5 @@ - + diff --git a/plugin-v3/src/main/java/net/groboclown/p4plugin/ui/vcsroot/ConfigPartWrapper.java b/plugin-v3/src/main/java/net/groboclown/p4plugin/modules/clientconfig/view/ConfigPartWrapper.java similarity index 99% rename from plugin-v3/src/main/java/net/groboclown/p4plugin/ui/vcsroot/ConfigPartWrapper.java rename to plugin-v3/src/main/java/net/groboclown/p4plugin/modules/clientconfig/view/ConfigPartWrapper.java index 4e0862cf..cfc4d313 100644 --- a/plugin-v3/src/main/java/net/groboclown/p4plugin/ui/vcsroot/ConfigPartWrapper.java +++ b/plugin-v3/src/main/java/net/groboclown/p4plugin/modules/clientconfig/view/ConfigPartWrapper.java @@ -12,7 +12,7 @@ * limitations under the License. */ -package net.groboclown.p4plugin.ui.vcsroot; +package net.groboclown.p4plugin.modules.clientconfig.view; import com.intellij.icons.AllIcons; import com.jgoodies.forms.layout.CellConstraints; diff --git a/plugin-v3/src/main/java/net/groboclown/p4plugin/ui/vcsroot/ListPositionChangeController.java b/plugin-v3/src/main/java/net/groboclown/p4plugin/modules/clientconfig/view/ListPositionChangeController.java similarity index 92% rename from plugin-v3/src/main/java/net/groboclown/p4plugin/ui/vcsroot/ListPositionChangeController.java rename to plugin-v3/src/main/java/net/groboclown/p4plugin/modules/clientconfig/view/ListPositionChangeController.java index 9fe898de..9be55112 100644 --- a/plugin-v3/src/main/java/net/groboclown/p4plugin/ui/vcsroot/ListPositionChangeController.java +++ b/plugin-v3/src/main/java/net/groboclown/p4plugin/modules/clientconfig/view/ListPositionChangeController.java @@ -12,7 +12,7 @@ * limitations under the License. */ -package net.groboclown.p4plugin.ui.vcsroot; +package net.groboclown.p4plugin.modules.clientconfig.view; public interface ListPositionChangeController { void moveUpPosition(); diff --git a/plugin-v3/src/main/java/net/groboclown/p4plugin/ui/vcsroot/P4RootConfigPanel.form b/plugin-v3/src/main/java/net/groboclown/p4plugin/modules/clientconfig/view/P4RootConfigPanel.form similarity index 96% rename from plugin-v3/src/main/java/net/groboclown/p4plugin/ui/vcsroot/P4RootConfigPanel.form rename to plugin-v3/src/main/java/net/groboclown/p4plugin/modules/clientconfig/view/P4RootConfigPanel.form index e3c6b041..81a1f46e 100644 --- a/plugin-v3/src/main/java/net/groboclown/p4plugin/ui/vcsroot/P4RootConfigPanel.form +++ b/plugin-v3/src/main/java/net/groboclown/p4plugin/modules/clientconfig/view/P4RootConfigPanel.form @@ -1,5 +1,5 @@ - + @@ -68,7 +68,7 @@ - + diff --git a/plugin-v3/src/main/java/net/groboclown/p4plugin/ui/vcsroot/P4RootConfigPanel.java b/plugin-v3/src/main/java/net/groboclown/p4plugin/modules/clientconfig/view/P4RootConfigPanel.java similarity index 99% rename from plugin-v3/src/main/java/net/groboclown/p4plugin/ui/vcsroot/P4RootConfigPanel.java rename to plugin-v3/src/main/java/net/groboclown/p4plugin/modules/clientconfig/view/P4RootConfigPanel.java index d8a57851..9e07d445 100644 --- a/plugin-v3/src/main/java/net/groboclown/p4plugin/ui/vcsroot/P4RootConfigPanel.java +++ b/plugin-v3/src/main/java/net/groboclown/p4plugin/modules/clientconfig/view/P4RootConfigPanel.java @@ -12,7 +12,7 @@ * limitations under the License. */ -package net.groboclown.p4plugin.ui.vcsroot; +package net.groboclown.p4plugin.modules.clientconfig.view; import com.intellij.icons.AllIcons; import com.intellij.openapi.project.Project; diff --git a/plugin-v3/src/main/java/net/groboclown/p4plugin/ui/vcsroot/P4VcsRootConfigurable.java b/plugin-v3/src/main/java/net/groboclown/p4plugin/modules/clientconfig/view/P4VcsRootConfigurable.java similarity index 92% rename from plugin-v3/src/main/java/net/groboclown/p4plugin/ui/vcsroot/P4VcsRootConfigurable.java rename to plugin-v3/src/main/java/net/groboclown/p4plugin/modules/clientconfig/view/P4VcsRootConfigurable.java index 2a9cf876..23ba8b6c 100644 --- a/plugin-v3/src/main/java/net/groboclown/p4plugin/ui/vcsroot/P4VcsRootConfigurable.java +++ b/plugin-v3/src/main/java/net/groboclown/p4plugin/modules/clientconfig/view/P4VcsRootConfigurable.java @@ -12,7 +12,7 @@ * limitations under the License. */ -package net.groboclown.p4plugin.ui.vcsroot; +package net.groboclown.p4plugin.modules.clientconfig.view; import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.options.ConfigurationException; @@ -28,23 +28,23 @@ import net.groboclown.p4.server.api.config.ConfigProblem; import net.groboclown.p4.server.api.config.ConfigPropertiesUtil; import net.groboclown.p4.server.api.config.OptionalClientServerConfig; -import net.groboclown.p4.server.api.config.P4VcsRootSettings; import net.groboclown.p4.server.api.config.ServerConfig; import net.groboclown.p4.server.api.config.part.ConfigPart; import net.groboclown.p4.server.api.config.part.MultipleConfigPart; import net.groboclown.p4plugin.P4Bundle; import net.groboclown.p4plugin.components.P4ServerComponent; +import net.groboclown.p4plugin.modules.clientconfig.VcsRootConfigController; import net.groboclown.p4plugin.ui.WrapperPanel; -import net.groboclown.p4.server.impl.util.RootSettingsUtil; import org.jetbrains.annotations.Nls; -import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import javax.swing.*; import java.util.Collection; import java.util.List; -import java.util.stream.Collectors; +/** + * Manages the state with the module's controller. + */ public class P4VcsRootConfigurable implements UnnamedConfigurable { private static final Logger LOG = Logger.getInstance(P4VcsRootConfigurable.class); @@ -56,7 +56,6 @@ public class P4VcsRootConfigurable implements UnnamedConfigurable { public P4VcsRootConfigurable(Project project, VcsDirectoryMapping mapping) { this.project = project; - this.settings = RootSettingsUtil.getFixedRootSettings(project, mapping); this.vcsRoot = VcsUtil.getVirtualFile(mapping.getDirectory()); if (LOG.isDebugEnabled()) { LOG.debug("Creating configurable for vcs root " + vcsRoot); @@ -71,10 +70,7 @@ public JComponent createComponent() { LOG.debug("Creating component for root " + vcsRoot + "; mapping " + mapping); VcsRootSettings settings = mapping.getRootSettings(); LOG.debug("VCS Settings: " + (settings == null ? null : settings.getClass().getName())); - if (settings instanceof P4VcsRootSettings) { - P4VcsRootSettings p4settings = (P4VcsRootSettings) settings; - LOG.debug("P4 Settings for " + mapping.getDirectory() + ": " + p4settings.getConfigParts()); - } + LOG.debug("P4 Settings for " + mapping.getDirectory() + ": " + loadConfigFromSettings()); } controller = new Controller(project); panel = new P4RootConfigPanel(vcsRoot, controller); @@ -95,7 +91,8 @@ public void apply() if (isModified()) { LOG.info("Updating root configuration for " + vcsRoot); MultipleConfigPart parentPart = loadParentPartFromUI(); - settings.setConfigParts(parentPart.getChildren()); + VcsRootConfigController.getInstance().setRootConfigParts( + project, vcsRoot, parentPart.getChildren()); if (parentPart.hasError()) { problems = parentPart.getConfigProblems(); @@ -135,8 +132,7 @@ public void disposeUIResources() { } private List loadPartsFromSettings() { - P4VcsRootSettings settings = getRootSettings(); - return settings.getConfigParts().stream().map(ConfigPart::copy).collect(Collectors.toList()); + return VcsRootConfigController.getInstance().getConfigPartsForRoot(project, vcsRoot); } private ConfigPart loadParentPartFromSettings() { @@ -171,11 +167,6 @@ public ClientConfig loadConfigFromSettings() { return null; } - @NotNull - private P4VcsRootSettings getRootSettings() { - return RootSettingsUtil.getFixedRootSettings(project, mapping, vcsRoot); - } - @Nls(capitalization = Nls.Capitalization.Sentence) private String toMessage(Collection problems) { StringBuilder sb = new StringBuilder(); diff --git a/plugin-v3/src/main/java/net/groboclown/p4plugin/ui/vcsroot/part/ClientNamePartUI.java b/plugin-v3/src/main/java/net/groboclown/p4plugin/modules/clientconfig/view/part/ClientNamePartUI.java similarity index 96% rename from plugin-v3/src/main/java/net/groboclown/p4plugin/ui/vcsroot/part/ClientNamePartUI.java rename to plugin-v3/src/main/java/net/groboclown/p4plugin/modules/clientconfig/view/part/ClientNamePartUI.java index e3f20b96..99e4b51b 100644 --- a/plugin-v3/src/main/java/net/groboclown/p4plugin/ui/vcsroot/part/ClientNamePartUI.java +++ b/plugin-v3/src/main/java/net/groboclown/p4plugin/modules/clientconfig/view/part/ClientNamePartUI.java @@ -12,7 +12,7 @@ * limitations under the License. */ -package net.groboclown.p4plugin.ui.vcsroot.part; +package net.groboclown.p4plugin.modules.clientconfig.view.part; import com.intellij.icons.AllIcons; import com.intellij.openapi.project.Project; @@ -28,9 +28,9 @@ import net.groboclown.p4plugin.P4Bundle; import net.groboclown.p4plugin.components.P4ServerComponent; import net.groboclown.p4plugin.ui.SwingUtil; -import net.groboclown.p4plugin.ui.vcsroot.ConfigConnectionController; -import net.groboclown.p4plugin.ui.vcsroot.ConfigPartUI; -import net.groboclown.p4plugin.ui.vcsroot.ConfigPartUIFactory; +import net.groboclown.p4plugin.modules.clientconfig.view.ConfigConnectionController; +import net.groboclown.p4plugin.modules.clientconfig.view.ConfigPartUI; +import net.groboclown.p4plugin.modules.clientconfig.view.ConfigPartUIFactory; import org.jetbrains.annotations.Nls; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; diff --git a/plugin-v3/src/main/java/net/groboclown/p4plugin/ui/vcsroot/part/EnvPartUI.java b/plugin-v3/src/main/java/net/groboclown/p4plugin/modules/clientconfig/view/part/EnvPartUI.java similarity index 89% rename from plugin-v3/src/main/java/net/groboclown/p4plugin/ui/vcsroot/part/EnvPartUI.java rename to plugin-v3/src/main/java/net/groboclown/p4plugin/modules/clientconfig/view/part/EnvPartUI.java index 09931f1a..d0918504 100644 --- a/plugin-v3/src/main/java/net/groboclown/p4plugin/ui/vcsroot/part/EnvPartUI.java +++ b/plugin-v3/src/main/java/net/groboclown/p4plugin/modules/clientconfig/view/part/EnvPartUI.java @@ -12,15 +12,15 @@ * limitations under the License. */ -package net.groboclown.p4plugin.ui.vcsroot.part; +package net.groboclown.p4plugin.modules.clientconfig.view.part; import com.intellij.openapi.vfs.VirtualFile; import net.groboclown.p4.server.api.config.part.ConfigPart; import net.groboclown.p4.server.impl.config.part.EnvCompositePart; import net.groboclown.p4plugin.P4Bundle; -import net.groboclown.p4plugin.ui.vcsroot.ConfigConnectionController; -import net.groboclown.p4plugin.ui.vcsroot.ConfigPartUI; -import net.groboclown.p4plugin.ui.vcsroot.ConfigPartUIFactory; +import net.groboclown.p4plugin.modules.clientconfig.view.ConfigConnectionController; +import net.groboclown.p4plugin.modules.clientconfig.view.ConfigPartUI; +import net.groboclown.p4plugin.modules.clientconfig.view.ConfigPartUIFactory; import org.jetbrains.annotations.Nls; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; diff --git a/plugin-v3/src/main/java/net/groboclown/p4plugin/ui/vcsroot/part/FilePartUI.java b/plugin-v3/src/main/java/net/groboclown/p4plugin/modules/clientconfig/view/part/FilePartUI.java similarity index 93% rename from plugin-v3/src/main/java/net/groboclown/p4plugin/ui/vcsroot/part/FilePartUI.java rename to plugin-v3/src/main/java/net/groboclown/p4plugin/modules/clientconfig/view/part/FilePartUI.java index 714d3f41..ef4cb2f6 100644 --- a/plugin-v3/src/main/java/net/groboclown/p4plugin/ui/vcsroot/part/FilePartUI.java +++ b/plugin-v3/src/main/java/net/groboclown/p4plugin/modules/clientconfig/view/part/FilePartUI.java @@ -12,7 +12,7 @@ * limitations under the License. */ -package net.groboclown.p4plugin.ui.vcsroot.part; +package net.groboclown.p4plugin.modules.clientconfig.view.part; import com.intellij.openapi.fileChooser.FileChooserDescriptor; import com.intellij.openapi.ui.TextFieldWithBrowseButton; @@ -22,16 +22,15 @@ import net.groboclown.p4.server.impl.config.part.FileConfigPart; import net.groboclown.p4plugin.P4Bundle; import net.groboclown.p4plugin.ui.SwingUtil; -import net.groboclown.p4plugin.ui.vcsroot.ConfigConnectionController; -import net.groboclown.p4plugin.ui.vcsroot.ConfigPartUI; -import net.groboclown.p4plugin.ui.vcsroot.ConfigPartUIFactory; +import net.groboclown.p4plugin.modules.clientconfig.view.ConfigConnectionController; +import net.groboclown.p4plugin.modules.clientconfig.view.ConfigPartUI; +import net.groboclown.p4plugin.modules.clientconfig.view.ConfigPartUIFactory; import org.jetbrains.annotations.Nls; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import javax.swing.*; import java.awt.*; -import java.io.File; public class FilePartUI extends ConfigPartUI { diff --git a/plugin-v3/src/main/java/net/groboclown/p4plugin/ui/vcsroot/part/PropertiesPartUI.form b/plugin-v3/src/main/java/net/groboclown/p4plugin/modules/clientconfig/view/part/PropertiesPartUI.form similarity index 99% rename from plugin-v3/src/main/java/net/groboclown/p4plugin/ui/vcsroot/part/PropertiesPartUI.form rename to plugin-v3/src/main/java/net/groboclown/p4plugin/modules/clientconfig/view/part/PropertiesPartUI.form index e6c2f307..50832aa9 100644 --- a/plugin-v3/src/main/java/net/groboclown/p4plugin/ui/vcsroot/part/PropertiesPartUI.form +++ b/plugin-v3/src/main/java/net/groboclown/p4plugin/modules/clientconfig/view/part/PropertiesPartUI.form @@ -1,5 +1,5 @@ - + diff --git a/plugin-v3/src/main/java/net/groboclown/p4plugin/ui/vcsroot/part/PropertiesPartUI.java b/plugin-v3/src/main/java/net/groboclown/p4plugin/modules/clientconfig/view/part/PropertiesPartUI.java similarity index 97% rename from plugin-v3/src/main/java/net/groboclown/p4plugin/ui/vcsroot/part/PropertiesPartUI.java rename to plugin-v3/src/main/java/net/groboclown/p4plugin/modules/clientconfig/view/part/PropertiesPartUI.java index cc385bc5..6b357708 100644 --- a/plugin-v3/src/main/java/net/groboclown/p4plugin/ui/vcsroot/part/PropertiesPartUI.java +++ b/plugin-v3/src/main/java/net/groboclown/p4plugin/modules/clientconfig/view/part/PropertiesPartUI.java @@ -12,7 +12,7 @@ * limitations under the License. */ -package net.groboclown.p4plugin.ui.vcsroot.part; +package net.groboclown.p4plugin.modules.clientconfig.view.part; import com.intellij.openapi.fileChooser.FileChooserDescriptorFactory; import com.intellij.openapi.ui.TextFieldWithBrowseButton; @@ -22,9 +22,9 @@ import net.groboclown.p4.server.api.config.part.ConfigPart; import net.groboclown.p4.server.impl.config.part.SimpleDataPart; import net.groboclown.p4plugin.P4Bundle; -import net.groboclown.p4plugin.ui.vcsroot.ConfigConnectionController; -import net.groboclown.p4plugin.ui.vcsroot.ConfigPartUI; -import net.groboclown.p4plugin.ui.vcsroot.ConfigPartUIFactory; +import net.groboclown.p4plugin.modules.clientconfig.view.ConfigConnectionController; +import net.groboclown.p4plugin.modules.clientconfig.view.ConfigPartUI; +import net.groboclown.p4plugin.modules.clientconfig.view.ConfigPartUIFactory; import org.jetbrains.annotations.Nls; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; diff --git a/plugin-v3/src/main/java/net/groboclown/p4plugin/ui/vcsroot/part/RequirePasswordPartUI.java b/plugin-v3/src/main/java/net/groboclown/p4plugin/modules/clientconfig/view/part/RequirePasswordPartUI.java similarity index 89% rename from plugin-v3/src/main/java/net/groboclown/p4plugin/ui/vcsroot/part/RequirePasswordPartUI.java rename to plugin-v3/src/main/java/net/groboclown/p4plugin/modules/clientconfig/view/part/RequirePasswordPartUI.java index 73323a1e..1a0ed836 100644 --- a/plugin-v3/src/main/java/net/groboclown/p4plugin/ui/vcsroot/part/RequirePasswordPartUI.java +++ b/plugin-v3/src/main/java/net/groboclown/p4plugin/modules/clientconfig/view/part/RequirePasswordPartUI.java @@ -12,16 +12,15 @@ * limitations under the License. */ -package net.groboclown.p4plugin.ui.vcsroot.part; +package net.groboclown.p4plugin.modules.clientconfig.view.part; import com.intellij.openapi.vfs.VirtualFile; import net.groboclown.p4.server.api.config.part.ConfigPart; -import net.groboclown.p4.server.impl.config.part.EnvCompositePart; import net.groboclown.p4.server.impl.config.part.RequirePasswordDataPart; import net.groboclown.p4plugin.P4Bundle; -import net.groboclown.p4plugin.ui.vcsroot.ConfigConnectionController; -import net.groboclown.p4plugin.ui.vcsroot.ConfigPartUI; -import net.groboclown.p4plugin.ui.vcsroot.ConfigPartUIFactory; +import net.groboclown.p4plugin.modules.clientconfig.view.ConfigConnectionController; +import net.groboclown.p4plugin.modules.clientconfig.view.ConfigPartUI; +import net.groboclown.p4plugin.modules.clientconfig.view.ConfigPartUIFactory; import org.jetbrains.annotations.Nls; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; diff --git a/plugin-v3/src/main/java/net/groboclown/p4plugin/ui/vcsroot/part/ServerFingerprintPartUI.java b/plugin-v3/src/main/java/net/groboclown/p4plugin/modules/clientconfig/view/part/ServerFingerprintPartUI.java similarity index 92% rename from plugin-v3/src/main/java/net/groboclown/p4plugin/ui/vcsroot/part/ServerFingerprintPartUI.java rename to plugin-v3/src/main/java/net/groboclown/p4plugin/modules/clientconfig/view/part/ServerFingerprintPartUI.java index e87f7cf3..0b620abb 100644 --- a/plugin-v3/src/main/java/net/groboclown/p4plugin/ui/vcsroot/part/ServerFingerprintPartUI.java +++ b/plugin-v3/src/main/java/net/groboclown/p4plugin/modules/clientconfig/view/part/ServerFingerprintPartUI.java @@ -12,7 +12,7 @@ * limitations under the License. */ -package net.groboclown.p4plugin.ui.vcsroot.part; +package net.groboclown.p4plugin.modules.clientconfig.view.part; import com.intellij.openapi.vfs.VirtualFile; import com.jgoodies.forms.layout.CellConstraints; @@ -21,9 +21,9 @@ import net.groboclown.p4.server.impl.config.part.ServerFingerprintDataPart; import net.groboclown.p4plugin.P4Bundle; import net.groboclown.p4plugin.ui.SwingUtil; -import net.groboclown.p4plugin.ui.vcsroot.ConfigConnectionController; -import net.groboclown.p4plugin.ui.vcsroot.ConfigPartUI; -import net.groboclown.p4plugin.ui.vcsroot.ConfigPartUIFactory; +import net.groboclown.p4plugin.modules.clientconfig.view.ConfigConnectionController; +import net.groboclown.p4plugin.modules.clientconfig.view.ConfigPartUI; +import net.groboclown.p4plugin.modules.clientconfig.view.ConfigPartUIFactory; import org.jetbrains.annotations.Nls; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; diff --git a/plugin-v3/src/main/java/net/groboclown/p4plugin/modules/connection/ConfigRegistryStartup.java b/plugin-v3/src/main/java/net/groboclown/p4plugin/modules/connection/ConfigRegistryStartup.java new file mode 100644 index 00000000..e1dadc7a --- /dev/null +++ b/plugin-v3/src/main/java/net/groboclown/p4plugin/modules/connection/ConfigRegistryStartup.java @@ -0,0 +1,31 @@ +/* + * 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 net.groboclown.p4plugin.modules.connection; + +import com.intellij.openapi.project.Project; +import com.intellij.openapi.startup.StartupActivity; +import net.groboclown.p4.server.api.ProjectConfigRegistry; +import org.jetbrains.annotations.NotNull; + +/** + * Sets up the {@link net.groboclown.p4.server.api.ProjectConfigRegistry} after the IDE starts up. + */ +public class ConfigRegistryStartup + implements StartupActivity.Background { + @Override + public void runActivity(@NotNull Project project) { + project.getService(ProjectConfigRegistry.class).initializeService(); + } +} diff --git a/idea-p4server/impl/src/main/java/net/groboclown/p4/server/impl/ProjectConfigRegistryImpl.java b/plugin-v3/src/main/java/net/groboclown/p4plugin/modules/connection/ProjectConfigRegistryImpl.java similarity index 92% rename from idea-p4server/impl/src/main/java/net/groboclown/p4/server/impl/ProjectConfigRegistryImpl.java rename to plugin-v3/src/main/java/net/groboclown/p4plugin/modules/connection/ProjectConfigRegistryImpl.java index 260a88ca..c3087aed 100644 --- a/idea-p4server/impl/src/main/java/net/groboclown/p4/server/impl/ProjectConfigRegistryImpl.java +++ b/plugin-v3/src/main/java/net/groboclown/p4plugin/modules/connection/ProjectConfigRegistryImpl.java @@ -12,7 +12,7 @@ * limitations under the License. */ -package net.groboclown.p4.server.impl; +package net.groboclown.p4plugin.modules.connection; import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.project.Project; @@ -37,6 +37,7 @@ import net.groboclown.p4.server.impl.cache.ClientConfigRootImpl; import net.groboclown.p4.server.impl.cache.ServerStatusImpl; import net.groboclown.p4.server.impl.config.part.EnvCompositePart; +import net.groboclown.p4plugin.modules.clientconfig.VcsRootConfigController; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -56,9 +57,6 @@ * Stores the registered configurations for a specific project. The registry must * also inform the application server connection registry about the configurations, so that * the correct counters can be preserved. - *

- * This synchronizes the state with the underlying data store, - * {@link PersistentRootConfigComponent} */ public class ProjectConfigRegistryImpl extends ProjectConfigRegistry { @@ -107,7 +105,8 @@ public ClientConfig getRegisteredClientConfigState(@NotNull ClientServerRef ref) return client.state; } - @Override + + // this is public only for test support. public void addClientConfig(@NotNull ClientConfig config, @NotNull VirtualFile vcsRootDir) { checkDisposed(); ClientServerRef ref = config.getClientServerRef(); @@ -132,15 +131,9 @@ public void addClientConfig(@NotNull ClientConfig config, @NotNull VirtualFile v sendClientAdded(updated); } - /** - * Removes the client configuration registration with the given reference. If it is registered, then - * the appropriate messages will be sent out. - * - * @param vcsRootDir the reference to de-register - * @return true if it was registered, false if not. - */ + // made public for tests @Override - public boolean removeClientConfigAt(@NotNull VirtualFile vcsRootDir) { + public void removeClientConfigAt(@NotNull VirtualFile vcsRootDir) { checkDisposed(); ClientConfigRoot removed; synchronized (registeredClients) { @@ -154,7 +147,6 @@ public boolean removeClientConfigAt(@NotNull VirtualFile vcsRootDir) { LOG.debug("Removed client config " + vcsRootDir); } } - return registered; } @Override @@ -261,15 +253,10 @@ protected void onUserSelectedAllOnline() { } @Override - protected void updateVcsRoots() { + protected void initializeRoots() { if (LOG.isDebugEnabled()) { LOG.debug("Updating VCS roots"); } - final ProjectConfigRegistry registry = ProjectConfigRegistry.getInstance(getProject()); - if (registry == null) { - LOG.warn("Skipping update; no project configuration registry."); - return; - } synchronized (registeredServers) { final Set oldRoots; synchronized (registeredClients) { @@ -281,23 +268,22 @@ protected void updateVcsRoots() { LOG.info("Skipping VCS directory mapping with no root directory"); continue; } - ClientConfigRoot clientConfig = registry.getClientFor(rootDir); - if (clientConfig == null) { - addClientConfig(createDefaultClientConfig(rootDir), rootDir); - } else { - updateRoot(clientConfig); + final List parts = + VcsRootConfigController.getInstance().getConfigPartsForRoot(getProject(), rootDir); + if (parts != null) { + updateClientConfigAt(rootDir, parts); } + oldRoots.remove(rootDir); } oldRoots.forEach(this::removeClientConfigAt); } } - private void updateRoot(@NotNull ClientConfigRoot clientConfig) { - VirtualFile root = settings.getRootDir(); - List parts = settings.getConfigParts(); + @Override + protected void updateClientConfigAt(@NotNull VirtualFile root, @NotNull List parts) { MultipleConfigPart parentPart = new MultipleConfigPart("Project Registry", parts); if (LOG.isDebugEnabled()) { - LOG.debug("Add mapping for " + root + " -> " + parentPart + " (directory mapping dir [" + directoryMapping.getDirectory() + "])"); + LOG.debug("Add mapping for " + root + " -> " + parentPart); } try { if (ServerConfig.isValidServerConfig(parentPart)) { diff --git a/plugin-v3/src/main/java/net/groboclown/p4plugin/modules/connection/package-info.java b/plugin-v3/src/main/java/net/groboclown/p4plugin/modules/connection/package-info.java new file mode 100644 index 00000000..7b47ebf3 --- /dev/null +++ b/plugin-v3/src/main/java/net/groboclown/p4plugin/modules/connection/package-info.java @@ -0,0 +1,16 @@ +/** + * Manages the client list and the server connection state. + *

+ * This changes the client list based on: initialization of the component (loaded through the clientconfig module); + * and the messages sent from the clientconfig module. + *

+ * Messages used by this module are split into three categories: + *

    + *
  • Project Configuration Updates - listening to changes to the configuration from the clientconfig.
  • + *
  • Server Connection State Changes - listening to whether a server can connect, or reasons why it can't + * connect. These are application-wide ... should they be?
  • + *
  • Changes in clients - as the configuration state is updated, that can make client connections become + * removed or added.
  • + *
+ */ +package net.groboclown.p4plugin.modules.connection; diff --git a/plugin-v3/src/main/java/net/groboclown/p4plugin/ui/connection/ActiveConnectionPanel.java b/plugin-v3/src/main/java/net/groboclown/p4plugin/modules/connection/view/ActiveConnectionPanel.java similarity index 93% rename from plugin-v3/src/main/java/net/groboclown/p4plugin/ui/connection/ActiveConnectionPanel.java rename to plugin-v3/src/main/java/net/groboclown/p4plugin/modules/connection/view/ActiveConnectionPanel.java index 74919742..80f8a8d7 100644 --- a/plugin-v3/src/main/java/net/groboclown/p4plugin/ui/connection/ActiveConnectionPanel.java +++ b/plugin-v3/src/main/java/net/groboclown/p4plugin/modules/connection/view/ActiveConnectionPanel.java @@ -12,7 +12,7 @@ * limitations under the License. */ -package net.groboclown.p4plugin.ui.connection; +package net.groboclown.p4plugin.modules.connection.view; import com.intellij.icons.AllIcons; import com.intellij.openapi.Disposable; @@ -30,10 +30,8 @@ import com.intellij.openapi.project.Project; import com.intellij.openapi.ui.DialogWrapper; import com.intellij.openapi.vcs.VcsDirectoryMapping; -import com.intellij.openapi.vfs.VirtualFile; import com.intellij.ui.components.JBScrollPane; import com.intellij.ui.treeStructure.Tree; -import com.intellij.vcsUtil.VcsUtil; import com.perforce.p4java.exception.AuthenticationFailedException; import net.groboclown.p4.server.api.ClientConfigRoot; import net.groboclown.p4.server.api.P4VcsKey; @@ -53,8 +51,6 @@ import net.groboclown.p4.server.api.messagebus.ServerConnectedMessage; import net.groboclown.p4.server.api.messagebus.ServerErrorEvent; import net.groboclown.p4.server.api.messagebus.UserSelectedOfflineMessage; -import net.groboclown.p4.server.api.util.ProjectUtil; -import net.groboclown.p4.server.impl.config.P4VcsRootSettingsImpl; import net.groboclown.p4.server.impl.util.IntervalPeriodExecution; import net.groboclown.p4plugin.P4Bundle; import net.groboclown.p4plugin.components.CacheComponent; @@ -190,7 +186,7 @@ private ActionGroup createActionGroup() { P4Bundle.getString("active-connection.toolbar.refresh.tooltip"), AllIcons.Actions.Refresh) { @Override - public void actionPerformed(AnActionEvent anActionEvent) { + public void actionPerformed(@NotNull AnActionEvent anActionEvent) { refresh(); } }, @@ -199,7 +195,7 @@ public void actionPerformed(AnActionEvent anActionEvent) { P4Bundle.getString("active-connection.toolbar.expand.tooltip"), AllIcons.Actions.Expandall) { @Override - public void actionPerformed(AnActionEvent anActionEvent) { + public void actionPerformed(@NotNull AnActionEvent anActionEvent) { for (int i = 0; i < connectionTree.getRowCount(); i++) { connectionTree.expandRow(i); } @@ -210,7 +206,7 @@ public void actionPerformed(AnActionEvent anActionEvent) { P4Bundle.getString("active-connection.toolbar.collapse.tooltip"), AllIcons.Actions.Collapseall) { @Override - public void actionPerformed(AnActionEvent anActionEvent) { + public void actionPerformed(@NotNull AnActionEvent anActionEvent) { for (int i = 0; i < connectionTree.getRowCount(); i++) { connectionTree.collapseRow(i); } @@ -221,7 +217,7 @@ public void actionPerformed(AnActionEvent anActionEvent) { P4Bundle.getString("active-connection.toolbar.connect.tooltip"), AllIcons.Actions.Lightning) { // Upload? @Override - public void actionPerformed(AnActionEvent anActionEvent) { + public void actionPerformed(@NotNull AnActionEvent anActionEvent) { final ClientConfigRoot sel = getSelected(ClientConfigRoot.class); if (sel != null && sel.isOffline()) { ReconnectRequestMessage.requestReconnectToClient(project, @@ -241,7 +237,7 @@ boolean isEnabled() { // TODO choose a better icon AllIcons.Actions.Pause) { @Override - public void actionPerformed(AnActionEvent anActionEvent) { + public void actionPerformed(@NotNull AnActionEvent anActionEvent) { final ClientConfigRoot sel = getSelected(ClientConfigRoot.class); if (sel != null && sel.isOnline()) { UserSelectedOfflineMessage.send(project).userSelectedServerOffline( @@ -261,18 +257,13 @@ boolean isEnabled() { P4Bundle.getString("active-connection.toolbar.configure.tooltip"), AllIcons.General.GearPlain) { @Override - public void actionPerformed(AnActionEvent anActionEvent) { + public void actionPerformed(@NotNull AnActionEvent anActionEvent) { final ClientConfigRoot sel = getSelected(ClientConfigRoot.class); boolean shown = false; if (sel != null && sel.getClientRootDir() != null) { String dirName = sel.getClientRootDir().getPath(); // TODO this is not accurate. It does not supply the correct VcsDirectoryMapping instance. - VirtualFile rootDir = VcsUtil.getVirtualFile(dirName); - if (rootDir == null) { - rootDir = ProjectUtil.guessProjectBaseDir(project); - } - VcsDirectoryMapping mapping = new VcsDirectoryMapping(dirName, P4VcsKey.VCS_NAME, - new P4VcsRootSettingsImpl(project, rootDir)); + VcsDirectoryMapping mapping = new VcsDirectoryMapping(dirName, P4VcsKey.VCS_NAME, null); UnnamedConfigurable configurable = P4Vcs.getInstance(project).getRootConfigurable(mapping); if (configurable != null) { new ConfigDialog(project, dirName, configurable) @@ -303,7 +294,7 @@ boolean isEnabled() { } @Override - public void actionPerformed(AnActionEvent anActionEvent) { + public void actionPerformed(@NotNull AnActionEvent anActionEvent) { final ClientConfigRoot selRoot = getSelected(ClientConfigRoot.class); if (selRoot != null) { P4ServerComponent.sendCachedPendingRequests(project, selRoot.getClientConfig()) @@ -317,7 +308,7 @@ public void actionPerformed(AnActionEvent anActionEvent) { P4Bundle.getString("active-connection.toolbar.remove-action.tooltip"), AllIcons.Actions.Cancel) { @Override - public void actionPerformed(AnActionEvent anActionEvent) { + public void actionPerformed(@NotNull AnActionEvent anActionEvent) { final ClientConfigRoot selRoot = getSelected(ClientConfigRoot.class); final ActionChoice sel = getSelected(ActionChoice.class); final CacheComponent cache = CacheComponent.getInstance(project); @@ -399,6 +390,7 @@ private static class ConfigDialog extends DialogWrapper { centerRelativeToParent(); } + @Override protected JComponent createCenterPanel() { if (ui == null) { ui = config.createComponent(); diff --git a/plugin-v3/src/main/java/net/groboclown/p4plugin/ui/connection/ActiveConnectionViewManager.java b/plugin-v3/src/main/java/net/groboclown/p4plugin/modules/connection/view/ActiveConnectionViewManager.java similarity index 96% rename from plugin-v3/src/main/java/net/groboclown/p4plugin/ui/connection/ActiveConnectionViewManager.java rename to plugin-v3/src/main/java/net/groboclown/p4plugin/modules/connection/view/ActiveConnectionViewManager.java index cca9cede..f157337d 100644 --- a/plugin-v3/src/main/java/net/groboclown/p4plugin/ui/connection/ActiveConnectionViewManager.java +++ b/plugin-v3/src/main/java/net/groboclown/p4plugin/modules/connection/view/ActiveConnectionViewManager.java @@ -12,7 +12,7 @@ * limitations under the License. */ -package net.groboclown.p4plugin.ui.connection; +package net.groboclown.p4plugin.modules.connection.view; import com.intellij.openapi.Disposable; import com.intellij.openapi.project.Project; diff --git a/plugin-v3/src/main/java/net/groboclown/p4plugin/ui/connection/ConnectionTreeCellRenderer.java b/plugin-v3/src/main/java/net/groboclown/p4plugin/modules/connection/view/ConnectionTreeCellRenderer.java similarity index 99% rename from plugin-v3/src/main/java/net/groboclown/p4plugin/ui/connection/ConnectionTreeCellRenderer.java rename to plugin-v3/src/main/java/net/groboclown/p4plugin/modules/connection/view/ConnectionTreeCellRenderer.java index b6d4bcd9..81dd30c7 100644 --- a/plugin-v3/src/main/java/net/groboclown/p4plugin/ui/connection/ConnectionTreeCellRenderer.java +++ b/plugin-v3/src/main/java/net/groboclown/p4plugin/modules/connection/view/ConnectionTreeCellRenderer.java @@ -12,7 +12,7 @@ * limitations under the License. */ -package net.groboclown.p4plugin.ui.connection; +package net.groboclown.p4plugin.modules.connection.view; import com.intellij.openapi.vcs.FilePath; import com.intellij.ui.ColoredTreeCellRenderer; diff --git a/plugin-v3/src/main/java/net/groboclown/p4plugin/ui/connection/ConnectionTreeRootNode.java b/plugin-v3/src/main/java/net/groboclown/p4plugin/modules/connection/view/ConnectionTreeRootNode.java similarity index 98% rename from plugin-v3/src/main/java/net/groboclown/p4plugin/ui/connection/ConnectionTreeRootNode.java rename to plugin-v3/src/main/java/net/groboclown/p4plugin/modules/connection/view/ConnectionTreeRootNode.java index a5fd7272..d4cfa92f 100644 --- a/plugin-v3/src/main/java/net/groboclown/p4plugin/ui/connection/ConnectionTreeRootNode.java +++ b/plugin-v3/src/main/java/net/groboclown/p4plugin/modules/connection/view/ConnectionTreeRootNode.java @@ -12,7 +12,7 @@ * limitations under the License. */ -package net.groboclown.p4plugin.ui.connection; +package net.groboclown.p4plugin.modules.connection.view; import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.project.Project; diff --git a/plugin-v3/src/main/java/net/groboclown/p4plugin/ui/connection/PendingParentNode.java b/plugin-v3/src/main/java/net/groboclown/p4plugin/modules/connection/view/PendingParentNode.java similarity index 93% rename from plugin-v3/src/main/java/net/groboclown/p4plugin/ui/connection/PendingParentNode.java rename to plugin-v3/src/main/java/net/groboclown/p4plugin/modules/connection/view/PendingParentNode.java index 8c68c455..bc146046 100644 --- a/plugin-v3/src/main/java/net/groboclown/p4plugin/ui/connection/PendingParentNode.java +++ b/plugin-v3/src/main/java/net/groboclown/p4plugin/modules/connection/view/PendingParentNode.java @@ -12,7 +12,7 @@ * limitations under the License. */ -package net.groboclown.p4plugin.ui.connection; +package net.groboclown.p4plugin.modules.connection.view; class PendingParentNode { private int pendingCount; diff --git a/plugin-v3/src/main/java/net/groboclown/p4plugin/ui/config/P4ConfigurationProjectPanel.java b/plugin-v3/src/main/java/net/groboclown/p4plugin/ui/config/P4ConfigurationProjectPanel.java index f3f93c0e..64fe28af 100644 --- a/plugin-v3/src/main/java/net/groboclown/p4plugin/ui/config/P4ConfigurationProjectPanel.java +++ b/plugin-v3/src/main/java/net/groboclown/p4plugin/ui/config/P4ConfigurationProjectPanel.java @@ -67,6 +67,7 @@ synchronized JPanel getPanel(@NotNull UserProjectPreferences preferences) { return wrappedPanel; } + @Override public synchronized void dispose() { // TODO is there a dispose to call on this panel? //if (myMainPanel != null) { diff --git a/plugin-v3/src/main/java/net/groboclown/p4plugin/ui/sync/SyncProjectDialog.java b/plugin-v3/src/main/java/net/groboclown/p4plugin/ui/sync/SyncProjectDialog.java index 2c683bf7..d154f803 100644 --- a/plugin-v3/src/main/java/net/groboclown/p4plugin/ui/sync/SyncProjectDialog.java +++ b/plugin-v3/src/main/java/net/groboclown/p4plugin/ui/sync/SyncProjectDialog.java @@ -30,7 +30,7 @@ import net.groboclown.p4plugin.P4Bundle; import net.groboclown.p4plugin.extension.P4Vcs; import net.groboclown.p4plugin.ui.SwingUtil; -import net.groboclown.p4plugin.ui.vcsroot.P4VcsRootConfigurable; +import net.groboclown.p4plugin.modules.clientconfig.view.P4VcsRootConfigurable; import org.jetbrains.annotations.Nls; import org.jetbrains.annotations.NotNull; diff --git a/plugin-v3/src/main/resources/META-INF/plugin.xml b/plugin-v3/src/main/resources/META-INF/plugin.xml index 2f7f33d3..dc9f7e62 100644 --- a/plugin-v3/src/main/resources/META-INF/plugin.xml +++ b/plugin-v3/src/main/resources/META-INF/plugin.xml @@ -1,7 +1,7 @@ Perforce IDEA Community Integration PerforceIC - 0.12.0-b1 + 0.12.0-b2 VCS Integration @@ -87,14 +87,14 @@ - - @@ -121,7 +121,7 @@ use-shortcut-of="ChangesView.AddUnversioned"/> - @@ -166,7 +166,7 @@ - @@ -178,7 +178,7 @@ @@ -186,12 +186,27 @@ serviceInterface="net.groboclown.p4.server.api.ApplicationPasswordRegistry" serviceImplementation="net.groboclown.p4plugin.components.ApplicationPasswordRegistryComponent"/> - + + + + + + + + + + @@ -204,11 +219,6 @@ net.groboclown.p4plugin.components.CacheComponent - - net.groboclown.p4.server.api.ProjectConfigRegistry - net.groboclown.p4.server.impl.ProjectConfigRegistryImpl - - net.groboclown.p4plugin.ui.VcsDockedComponent @@ -222,10 +232,6 @@ net.groboclown.p4plugin.components.UserErrorComponent - - net.groboclown.p4.server.impl.config.PersistentRootConfigComponent - - net.groboclown.p4plugin.components.CacheViewRefreshComponent diff --git a/plugin-v3/src/main/resources/net/groboclown/p4plugin/P4Bundle.properties b/plugin-v3/src/main/resources/net/groboclown/p4plugin/P4Bundle.properties index bcfe977a..3b59eec4 100644 --- a/plugin-v3/src/main/resources/net/groboclown/p4plugin/P4Bundle.properties +++ b/plugin-v3/src/main/resources/net/groboclown/p4plugin/P4Bundle.properties @@ -337,7 +337,7 @@ error.job-refresh.title=Problem loading job {0} error.job-refresh=Problem loading job {0} %% error.primary.client.root.null=Perforce reports the primary client root directory is null configuration.client-mismatch-ask=The client name last read ({0}) does not match the client name reported by Perforce. Do you want to check your connection? -configuration.error.title=Error in configuration setup +configuration.error.title=Error in Configuration Setup error.sync.title=Error synchronizing files error.sync=Failed to synchronize files with server %% error.delete.title=Problem trying to delete a file diff --git a/plugin-v3/src/test/java/net/groboclown/p4plugin/PluginSetup.java b/plugin-v3/src/test/java/net/groboclown/p4plugin/PluginSetup.java index 5e7fa054..1c795faa 100644 --- a/plugin-v3/src/test/java/net/groboclown/p4plugin/PluginSetup.java +++ b/plugin-v3/src/test/java/net/groboclown/p4plugin/PluginSetup.java @@ -14,7 +14,6 @@ package net.groboclown.p4plugin; -import com.intellij.lifecycle.PeriodicalTasksCloser; import com.intellij.openapi.command.CommandProcessor; import com.intellij.openapi.editor.colors.EditorColorsManager; import com.intellij.openapi.editor.colors.EditorColorsScheme; @@ -44,7 +43,7 @@ import net.groboclown.p4.server.api.messagebus.UserSelectedOfflineMessage; import net.groboclown.p4.server.api.values.P4ChangelistId; import net.groboclown.p4.server.api.values.P4ChangelistType; -import net.groboclown.p4.server.impl.ProjectConfigRegistryImpl; +import net.groboclown.p4plugin.modules.connection.ProjectConfigRegistryImpl; import net.groboclown.p4.server.impl.cache.store.ClientQueryCacheStore; import net.groboclown.p4.server.impl.cache.store.ClientServerRefStore; import net.groboclown.p4.server.impl.cache.store.IdeChangelistCacheStore; @@ -77,7 +76,7 @@ public class PluginSetup implements BeforeEachCallback, AfterEachCallback { public IdeaLightweightExtension idea; public P4Vcs vcs; - public ProjectConfigRegistry registry; + public ProjectConfigRegistryImpl registry; public P4ServerComponent server; public CacheComponent cacheComponent; public MockConnectionManager connectionManager; @@ -97,7 +96,7 @@ public void beforeEach(ExtensionContext extensionContext) server = new CustomP4ServerComponent(idea.getMockProject(), connectionManager); cacheComponent = new CacheComponent(idea.getMockProject()); - idea.registerProjectComponent(ProjectConfigRegistry.COMPONENT_NAME, registry); + idea.registerProjectService(ProjectConfigRegistry.class, registry); idea.registerProjectComponent(ProjectConfigRegistry.class, registry); idea.registerProjectComponent(P4ServerComponent.COMPONENT_NAME, server); idea.registerProjectComponent(P4ServerComponent.class, server); @@ -108,7 +107,7 @@ public void beforeEach(ExtensionContext extensionContext) when(mockVcsMgr.checkVcsIsActive(VCS_NAME)).thenReturn(true); vcs.doStart(); - registry.initComponent(); + registry.initializeService(); server.initComponent(); vcs.doActivate(); } @@ -120,7 +119,7 @@ public void afterEach(ExtensionContext extensionContext) vcs.doShutdown(); vcs = null; - registry.disposeComponent(); + registry.dispose(); registry = null; server.disposeComponent(); @@ -183,6 +182,7 @@ public P4ChangelistId addNewChangelist(ClientConfigRoot root, int p4ChangelistId changelist.shelvedFiles = new ArrayList<>(); changelist.containedFiles = new ArrayList<>(); changelist.jobs = new ArrayList<>(); + changelist.comment = description; if (state.clientState == null) { state.clientState = new ArrayList<>(); @@ -265,9 +265,6 @@ private void setupIdeRequirements(IdeaLightweightExtension idea) { when(mgr.getSchemeForCurrentUITheme()).thenReturn(scheme); when(scheme.getColor(any())).thenReturn(JBColor.BLACK); - PeriodicalTasksCloser closer = new PeriodicalTasksCloser(); - idea.registerApplicationComponent(PeriodicalTasksCloser.class, closer); - ChangeListManager clMgr = mock(ChangeListManager.class); idea.registerProjectComponent(ChangeListManager.class, clMgr); diff --git a/idea-p4server/impl/src/test/java/net/groboclown/p4/server/impl/ProjectConfigRegistryImplTest.java b/plugin-v3/src/test/java/net/groboclown/p4plugin/modules/connection/ProjectConfigRegistryImplTest.java similarity index 78% rename from idea-p4server/impl/src/test/java/net/groboclown/p4/server/impl/ProjectConfigRegistryImplTest.java rename to plugin-v3/src/test/java/net/groboclown/p4plugin/modules/connection/ProjectConfigRegistryImplTest.java index 11310435..1662b7eb 100644 --- a/idea-p4server/impl/src/test/java/net/groboclown/p4/server/impl/ProjectConfigRegistryImplTest.java +++ b/plugin-v3/src/test/java/net/groboclown/p4plugin/modules/connection/ProjectConfigRegistryImplTest.java @@ -11,7 +11,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package net.groboclown.p4.server.impl; +package net.groboclown.p4plugin.modules.connection; import com.intellij.openapi.vfs.VirtualFile; import net.groboclown.idea.extensions.IdeaLightweightExtension; @@ -46,7 +46,7 @@ void getRegisteredClientConfig() { @Test void addClientConfig_existing() { - ProjectConfigRegistry registry = new ProjectConfigRegistryImpl(idea.getMockProject()); + ProjectConfigRegistryImpl registry = new ProjectConfigRegistryImpl(idea.getMockProject()); final List added = new ArrayList<>(); final List removed = new ArrayList<>(); MessageBusClient.ProjectClient client = MessageBusClient.forProject(idea.getMockProject(), idea.getMockProject()); @@ -101,7 +101,7 @@ void addClientConfig_existing() { @Test void addClientConfig_new() { - ProjectConfigRegistry registry = new ProjectConfigRegistryImpl(idea.getMockProject()); + ProjectConfigRegistryImpl registry = new ProjectConfigRegistryImpl(idea.getMockProject()); final List added = new ArrayList<>(); MessageBusClient.ProjectClient client = MessageBusClient.forProject(idea.getMockProject(), idea.getMockProject()); ClientConfigAddedMessage.addListener(client, this, e -> added.add(e.getClientConfig())); @@ -121,7 +121,7 @@ void addClientConfig_new() { @Test void removeClientConfig_notRegistered() { - ProjectConfigRegistry registry = new ProjectConfigRegistryImpl(idea.getMockProject()); + ProjectConfigRegistryImpl registry = new ProjectConfigRegistryImpl(idea.getMockProject()); final List removed = new ArrayList<>(); MessageBusClient.ProjectClient client = MessageBusClient.forProject(idea.getMockProject(), idea.getMockProject()); ClientConfigAddedMessage.addListener(client, this, @@ -138,7 +138,7 @@ void removeClientConfig_notRegistered() { @Test void removeClientConfig_registered() { - ProjectConfigRegistry registry = new ProjectConfigRegistryImpl(idea.getMockProject()); + ProjectConfigRegistryImpl registry = new ProjectConfigRegistryImpl(idea.getMockProject()); final List added = new ArrayList<>(); final List removed = new ArrayList<>(); MessageBusClient.ProjectClient client = MessageBusClient.forProject(idea.getMockProject(), idea.getMockProject()); @@ -159,43 +159,11 @@ void removeClientConfig_registered() { assertNull(fetchedState); } - // projectOpened - nothing to do - // initComponent - nothing to do - - @Test - void projectClosed() { - ProjectConfigRegistry registry = new ProjectConfigRegistryImpl(idea.getMockProject()); - final List added = new ArrayList<>(); - final List removed = new ArrayList<>(); - MessageBusClient.ProjectClient client = MessageBusClient.forProject(idea.getMockProject(), idea.getMockProject()); - ClientConfigAddedMessage.addListener(client, this, e -> added.add(e.getClientConfig())); - ClientConfigRemovedMessage.addListener(client, this, (e) -> removed.add(e.getClientConfig())); - ClientConfig config = createClientConfig(); - VirtualFile root = MockVirtualFileSystem.createRoot(); - registry.addClientConfig(config, root); - assertEquals(1, added.size()); - added.clear(); - - registry.projectClosed(); - - assertEquals(0, added.size()); - assertEquals(1, removed.size()); - assertSame(config, removed.get(0)); - ClientConfig fetchedState = registry.getRegisteredClientConfigState(config.getClientServerRef()); - assertNull(fetchedState); - - // closing the project should turn off further registration - removed.clear(); - assertThrows(Throwable.class, () -> registry.addClientConfig(config, root)); - assertEquals(0, added.size()); - - assertThrows(Throwable.class, () -> registry.removeClientConfigAt(root)); - assertEquals(0, removed.size()); - } + // initService - nothing to do @Test - void disposeComponent() { - ProjectConfigRegistry registry = new ProjectConfigRegistryImpl(idea.getMockProject()); + void dispose() { + ProjectConfigRegistryImpl registry = new ProjectConfigRegistryImpl(idea.getMockProject()); final List added = new ArrayList<>(); final List removed = new ArrayList<>(); MessageBusClient.ProjectClient client = MessageBusClient.forProject(idea.getMockProject(), idea.getMockProject()); @@ -207,7 +175,7 @@ void disposeComponent() { assertEquals(1, added.size()); added.clear(); - registry.disposeComponent(); + registry.dispose(); assertEquals(0, added.size()); assertEquals(1, removed.size()); @@ -225,12 +193,6 @@ void disposeComponent() { assertEquals(0, removed.size()); } - @Test - void getComponentName() { - ProjectConfigRegistry registry = new ProjectConfigRegistryImpl(idea.getMockProject()); - assertSame(ProjectConfigRegistry.COMPONENT_NAME, registry.getComponentName()); - } - private ClientConfig createClientConfig() { return createClientConfig("my-client"); }