package com.perforce.p4simulink; /** * Perforce SCM Integration */ import com.mathworks.cmlink.api.ApplicationInteractor; import com.mathworks.cmlink.api.ConfigurationManagementException; import com.mathworks.cmlink.api.InteractorSupportedFeature; import com.mathworks.cmlink.api.Terminator; import com.mathworks.cmlink.api.customization.CustomizationWidgetFactory; import com.mathworks.cmlink.api.version.r14a.CMInteractor; import com.perforce.p4java.PropertyDefs; import com.perforce.p4java.client.IClient; import com.perforce.p4java.core.file.FileSpecBuilder; import com.perforce.p4java.core.file.IFileSpec; import com.perforce.p4java.exception.P4JavaException; import com.perforce.p4java.impl.generic.client.ClientView; import com.perforce.p4java.impl.mapbased.client.Client; import com.perforce.p4java.impl.mapbased.rpc.RpcPropertyDefs; import com.perforce.p4java.server.IOptionsServer; import com.perforce.p4java.server.ServerFactory; import com.perforce.p4java.server.ServerStatus; import com.perforce.p4simulink.connection.Config; import com.perforce.p4simulink.util.Logging; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import com.perforce.p4simulink.ui.P4ConnectionPane; import com.perforce.p4simulink.ui.P4DepotPathPane; import com.perforce.p4simulink.ui.P4PasswordPane; import javax.swing.*; import java.io.File; import java.io.IOException; import java.net.URISyntaxException; import java.util.*; import java.util.List; public class P4Interactor implements CMInteractor { private static final int OK = 0; private static final int CANCEL = 1; private static final int ERROR = 2; private static final int MAX_TRIES = 5; private static final Properties properties; protected static final Logger log = LogManager.getLogger(P4Interactor.class.getName()); // list of supported features private static final Collection<InteractorSupportedFeature> supportedFeatures = EnumSet.of( InteractorSupportedFeature.CONNECTION ); // static properties for all perforce connections static { properties = System.getProperties(); properties.put(PropertyDefs.PROG_NAME_KEY, "P4Simulink"); properties.put(PropertyDefs.PROG_VERSION_KEY, P4AdapterFactory.VERSION_STRING); // Set up socket pooling to use a single socket properties.put(RpcPropertyDefs.RPC_SOCKET_POOL_SIZE_NICK, "1"); // disable timeout for slow servers / large db lock times properties.put(RpcPropertyDefs.RPC_SOCKET_SO_TIMEOUT_NICK, "0"); } protected IOptionsServer server = null; protected IClient client = null; protected Config config; protected Terminator terminator; protected ApplicationInteractor applicationInteractor; protected File sandboxRoot; protected String depotPath; public P4Interactor(ApplicationInteractor applicationInteractor, Config config) throws P4CMException { // load config this.config = config; if (config != null && config.exists()) { this.config.load(); } // this is a handle we can use to open new dialog/popup windows if needed this.applicationInteractor = applicationInteractor; terminator = applicationInteractor.getTerminator(); } public P4Interactor setDepotPath(String path) { depotPath = path; return this; } public String getDepotPath() { return depotPath; } public void setTerminator(Terminator terminator) { this.terminator = terminator; } public File getSandboxRoot() { return sandboxRoot; } public void setSandboxRoot(File sandboxRoot) { this.sandboxRoot = sandboxRoot; } @Override public void buildCustomActions(CustomizationWidgetFactory customizationWidgetFactory) { log.debug("buildCustomActions()"); } @Override public boolean isFeatureSupported(InteractorSupportedFeature interactorSupportedFeature) { return supportedFeatures.contains(interactorSupportedFeature); } /** * Checks if the integration is ready. As the integration needs to be connected to a Perforce Server, * we check if a IOptionsServer object exists for the session and is connected. If it does, * then we're ready; otherwise, we still need to connect. * * @return is the connection to the Perforce Server ready or not. */ @Override public boolean isReady() { return server != null && server.isConnected(); } /** * Disconnect from the Perforce Server and close the Perforce session. * * @throws ConfigurationManagementException */ @Override public void disconnect() throws ConfigurationManagementException { try { if (isReady()) { server.disconnect(); } } catch (Exception e) { ConfigurationManagementException ex = new ConfigurationManagementException(e); log.error(ex); throw ex; } } /** * @param clientName specified client name * @return IClient client */ public IClient getClient(String clientName) throws ConfigurationManagementException { // if we're already using the correct client, return it if (client != null && client.getName().equalsIgnoreCase(clientName)) { return client; } try { if (server == null) { showNotification("Unable to connect to server."); return null; } if (clientName == null || clientName.isEmpty()) { showNotification("No client specified. Please supply a valid client."); return null; } IClient newClient = server.getClient(clientName); // we need to create the client from scratch if (newClient == null) { newClient = new Client(); newClient.setName(clientName); newClient.setDescription("Created by P4Simulink-" + P4AdapterFactory.VERSION_STRING); newClient.setHostName(""); newClient.setServer(server); server.createClient(newClient); log.debug("Created client '" + clientName + "'"); } // ensure the client root points to the sandbox String root = sandboxRoot.getAbsolutePath(); if (newClient.getRoot() == null || !newClient.getRoot().equalsIgnoreCase(root)) { newClient.setRoot(root); server.updateClient(newClient, false); } // if needed, add a default view that maps the depot path to the client root if (newClient.getClientView() == null || newClient.getClientView().getEntryList().isEmpty()) { if (depotPath == null || depotPath.isEmpty()) { // collect a depot path from the user int tries = MAX_TRIES; int result = showDepotPathDialogue(); while(result == ERROR && tries-- > 0) { result = showDepotPathDialogue(); } } ClientView view = new ClientView(); ClientView.ClientViewMapping mapping = new ClientView.ClientViewMapping(0, depotPath, "//" + clientName + "/..."); view.addEntry(mapping); newClient.setClientView(view); } // set our client to be the current one client = newClient; server.updateClient(client, false); server.setCurrentClient(client); client.setServer(server); return client; } catch (P4JavaException e) { Logging.logException(log, e, true); } return client; } public IClient getClient() throws ConfigurationManagementException { try { return getClient(config.getClient()); } catch (ConfigurationManagementException e) { Logging.logException(log, e, true); } return null; } public IOptionsServer getServer() { return server; } /** * Converts a collection of files into a list of IFileSpec objects. Any directories contained in the input list * are expanded into a list of files contained within them (if any). * * @param files Collection of files for which we want to get file specs. Note that directories are expanded into * the list of files contain in them (if any). * @return List of IFileSpec objects for the input list. */ public List<IFileSpec> toSpec(Collection<File> files) { List<IFileSpec> specs = new ArrayList<>(); try { HashSet<String> filePaths = new HashSet<>(); for (File file : files) { // adding a directory means we should add the files under it if (file.isDirectory()) { filePaths.add(file.getCanonicalPath() + "/..."); } else { // regular file filePaths.add(file.getCanonicalPath()); } } if (!filePaths.isEmpty()) { specs = FileSpecBuilder.makeFileSpecList(filePaths.toArray(new String[filePaths.size()])); } } catch (IOException e) { log.error("IO Exception building list of file specs: " + e.getLocalizedMessage()); } return specs; } public List<IFileSpec> toSpec(File file) { return toSpec(Collections.singletonList(file)); } /** * Get the address from our P4Config object and attempt to connect to that endpoint. If there are any problems * connecting to the server, catch the exception and rethrow as a P4CMException. * * @throws ConfigurationManagementException */ @Override public void connect() throws ConfigurationManagementException { log.debug("connect()"); boolean showDialogue = config == null || config.getP4JavaURI().isEmpty() || config.getUser().isEmpty() || config.getClient().isEmpty(); String info = ""; int tries = MAX_TRIES; // ask the user for enough information to connect to perforce (port, user and workspace) while (showDialogue && tries-- > 0) { int result = showConnectDialogue(info); // if the user hit cancel, let them go if (result == CANCEL) { showDialogue = false; continue; } // the user didn't provide us with enough info to connect, so re-pop the dialogue if (result == ERROR) { showDialogue = true; continue; } // disconnect if we're already connected showDialogue = false; if (server != null && server.isConnected()) { disconnect(); } } showDialogue = true; tries = MAX_TRIES; // handle authentication - check to see if the user is already logged in via ticket, or prompt // for a password if required while(showDialogue && tries-- > 0) { try { // try connecting to the server with the info we have server = ServerFactory.getOptionsServer(config.getP4JavaURI(), properties); server.connect(); server.setUserName(config.getUser()); // attempt to login int result = login(); if (result == CANCEL || result == ERROR) { // user canceled password entry, or there was an error showDialogue = true; } else { // login was successful! showDialogue = false; } } catch (URISyntaxException e) { Logging.logException(log, new ConfigurationManagementException(e), false); // connection failed showDialogue = true; } catch (P4JavaException e) { Logging.logException(log, e, false); // connection failed showDialogue = true; } } // now get our global client, creating it if it doesn't exist if (server != null && server.isConnected() && server.getStatus() == ServerStatus.READY) { client = getClient(config.getClient()); } } private int login() { boolean showDialogue = true; try { if (server.getServerInfo().isUnicodeEnabled()) { server.setCharsetName("utf8"); } while (showDialogue) { switch (config.getAuthType()) { case PASSWORD: String status = server.getLoginStatus(); if (!status.contains("not necessary")) { String pass = config.getPassword(); try { server.login(pass); } catch (P4JavaException e) { // exceptions are thrown when the password is invalid, so swallow it } } break; case TICKETPATH: server.setTicketsFilePath(config.getTicketPath()); break; default: log.error("Unknown Authorization Type: " + config.getAuthType()); return ERROR; } String status = server.getLoginStatus(); if (status.contains("not necessary")) { return OK; } if (status.contains("ticket expires in")) { return OK; } // If there is a broker or something else that swallows the message if (status.isEmpty()) { return OK; } // login failed, so let's show the dialogue int result = showPasswordDialogue(status); if (result == CANCEL) { // don't show it again return CANCEL; } } // if we got here, we could not log in return ERROR; } catch (P4JavaException e) { log.error(e.getLocalizedMessage()); } return ERROR; } private int showDepotPathDialogue() { P4DepotPathPane pane = new P4DepotPathPane(depotPath); int option = JOptionPane.showConfirmDialog( applicationInteractor.getParentFrame(), pane.getFields(), "Depot Path", JOptionPane.OK_CANCEL_OPTION ); if (option == JOptionPane.CANCEL_OPTION) { return CANCEL; } String path = pane.getDepotPath(); // @todo: validity checks? if (!path.startsWith("//")) { path = "//" + path; } if (path.endsWith("/")) { path += "..."; } if (!path.endsWith("/...")) { path += "/..."; } depotPath = path; return OK; } private int showConnectDialogue(String results) { P4ConnectionPane pane = new P4ConnectionPane(config); if (results != null && !results.isEmpty()) { pane.setResults(results); } int option = JOptionPane.showConfirmDialog( applicationInteractor.getParentFrame(), pane.getFields(), "Open Connection", JOptionPane.OK_CANCEL_OPTION ); if (option == JOptionPane.CANCEL_OPTION) { // user hit cancel return CANCEL; } boolean isValid = pane.getServer() != null && !pane.getServer().isEmpty() && pane.getUsername() != null && !pane.getUsername().isEmpty() && pane.getWorkspace() != null && !pane.getWorkspace().isEmpty(); // something was invalid if (!isValid) { return ERROR; } // we're good, so update the config values and save config.setPort(pane.getServer()); config.setClient(pane.getWorkspace()); config.setUser(pane.getUsername()); try { config.save(); } catch (P4CMException e) { log.error("Couldn't save config: " + e.getLocalizedMessage()); } return OK; } private void showNotification(String message) { log.error(message); JOptionPane.showMessageDialog( applicationInteractor.getParentFrame(), message, "P4Simulink Notification", JOptionPane.INFORMATION_MESSAGE ); } private int showPasswordDialogue(String result) { P4PasswordPane pane = new P4PasswordPane(); if (result != null && !result.isEmpty()) { pane.setResults(result); } int option = JOptionPane.showConfirmDialog( applicationInteractor.getParentFrame(), pane.getFields(), "Enter Password", JOptionPane.OK_CANCEL_OPTION ); if (option == JOptionPane.CANCEL_OPTION) { return CANCEL; } // update the config with the new values, if the user clicked ok config.setPassword(pane.getPassword()); return OK; } @Override public String getSystemName() { return "Perforce SCM Integration for Simulink (P4Simulink-" + P4AdapterFactory.VERSION_STRING + ")"; } @Override public String getShortSystemName() { return "P4Simulink-" + P4AdapterFactory.VERSION_STRING; } }
# | Change | User | Description | Committed | |
---|---|---|---|---|---|
#32 | 16676 | Paul Allen |
Look for P4CONFIG file above starting location for 'isDirSandboxForThisAdapter' function. (+ extra error logging) |
||
#31 | 13842 | Paul Allen |
New P4Adapter setup designed to avoid passing the URI. #review-13782 |
||
#30 | 12439 | Paul Allen | Test conflict/resolve | ||
#29 | 12023 | Paul Allen | Copying using p4Simulink_dev->main | ||
#28 | 11681 | Paul Allen |
Fixes to unit tests. - Fixed paths for testMove in OverrideTests - Removed adding of target before a move. - Closing of resources; writer - Added missing serialisation ID - removed unused variables. |
||
#27 | 11672 | Paul Allen |
Tidy up formatting and imports. No functional change. |
||
#26 | 11664 | Paul Allen |
New build for Mathworks to look at. Transferred from p4://perforce.perforce.com:1666@991739 |
||
#25 | 11663 | Paul Allen |
Fixing issues reported by Tim Hosey at Mathworks: fixed check project button by making getFileState function use the client root instead of a list of files for large file sets. Also investigated error messaging formats and determined that they and the "could not create new file" error are both controlled by MATLAB and not P4Simulink. All calls to "p4 reconcile" will strip the "No file(s) to reconcile." message as well. Transferred from p4://perforce.perforce.com:1666@991730 |
||
#24 | 11662 | Paul Allen |
Fix NPE in changelist selector, remove reference to "Simulink project" from error messages. Transferred from p4://perforce.perforce.com:1666@988672 |
||
#23 | 11661 | Paul Allen |
Finishing up unit tests. Transferred from p4://perforce.perforce.com:1666@985194 |
||
#22 | 11659 | Paul Allen |
Performance improvements, continued: improved performance of getFileState() by replacing the client haveList call with a direct server command. Also improved isLatest by removing fstat calls and replacing with simpler calls to diff and sync. Transferred from p4://perforce.perforce.com:1666@983506 |
||
#21 | 11656 | Paul Allen |
Additional functionality from Scania meeting, including current changelist selector (still needs descriptions added) as well as .p4ignore editor. Transferred from p4://perforce.perforce.com:1666@978388 |
||
#20 | 11655 | Paul Allen |
Updating for Scania demo. Transferred from p4://perforce.perforce.com:1666@977338 |
||
#19 | 11653 | Paul Allen |
Fixes for R2014b of MATLAB/Simulink, including: * No longer prompting for connection information in isDirSandboxForThisAdapter * Fixed up commit logic, so it no longer uses the current changelist * Added back logic to walk the directory tree to find a .p4config file, rather than needing one in the sandbox directory * Added new buttons for re-connecting to p4d, and editing .p4config * Investigated conflict issues - fixed some, better logic coming * Removed .p4ignore generation - wrong spot for it Transferred from p4://perforce.perforce.com:1666@976888 |
||
#18 | 11652 | Paul Allen |
Added boilerplate to code, fixed build issues (NPEs on plugin load), removed Mathworks classes from plugin jar, fixed delete vs missing issue on remove() call. Transferred from p4://perforce.perforce.com:1666@970498 |
||
#17 | 11651 | Paul Allen |
Finalizing alpha code changes - adding jar for release. Transferred from p4://perforce.perforce.com:1666@962486 |
||
#16 | 11650 | Paul Allen |
Adding README.txt, added revision number changes for files that have changed in the depot (have/head style) and added build version and type to build process as well as version string within the plugin. Transferred from p4://perforce.perforce.com:1666@959461 |
||
#15 | 11648 | Paul Allen |
Final changes after demo with PM, including removal of depot path from the connection dialogue, and fixing of the p4 print issue. Transferred from p4://perforce.perforce.com:1666@958518 |
||
#14 | 11647 | Paul Allen |
New project creation fixes. Transferred from p4://perforce.perforce.com:1666@958305 |
||
#13 | 11645 | Paul Allen |
Refactoring and additional work on UI: Removed Connection class and went with global server/client objects. Implemented panes for connection, depot path and passwords. Passwords are only requested when required. Transferred from p4://perforce.perforce.com:1666@957149 |
||
#12 | 11644 | Paul Allen |
Paul's changes: Refactor getAddress and getAddressForConfig to getPort and getP4JavaURI. There were a few cases when the wrong value was written + getPort was not working. Password had a null password object (now initilised) P4PaswordPane needs some work - must talk to Perfoce to see if it needs a pass or is already logged in. There are still sync -f (this is very bad) Think I miss understood the checkout method after finding and reading the JavaDoc. You were right to start. Looks like a p4 edit. Would like to set a break point and find out when it is used. Elliot's changes: * Changed export() to use p4 'print' * Connect/login now looks for P4TICKETS env var * Config no longer stores P4PASSWD or P4TICKETS * Removed P4TICKET functionality (was only for P4Jenkins) * Ensured we're using a global IServer object * checkout() now uses editFiles() (opens all files for edit) * Changed all "sync -f" to plain "sync" * Removed final client update with force flag * Removed error dialogues regarding login issues * Investigated use of P4IGNORE - I'll need to write my own filter, no big deal Transferred from p4://perforce.perforce.com:1666@955247 |
||
#11 | 11643 | Paul Allen |
Unshelved from pending changelist '953729': Paul: Added: - getWorkspaceState() -- run a p4 status - getOpenedState() -- run p4 opened - getIgnoreState() -- run p4 add -n Modified: - add() -- seemed to call reopen? - P4FileState -- simple constructor for state Elliot: * global client and changelist object within the adapter * removed repeated client updates and changelist refreshes * removed 'reopen' in add * added calls to 'edit' where appropriate file states are found * made 'checkout' a pure sync, with no edits * commented a bunch of the @Override methods * added a call to haveList in getFileState so we can see versions of files in the project * changed the connection pane to look like p4v's * refactored a bit of the dialogue code, added calls to prompt for password (like p4v) * changed all log.trace() calls to log.debug() so the log file messiness can be controlled more easily Functionality that now works properly: * open project from source control, with properly-configured .p4config file * add new project to source control, with properly-configured .p4config file * add * edit * commit * revert * compare reversions (opens Simulink diff tool) Things to work on: * dialogues (particularily the password one, but the connection one is a bit dodge-y) * remove from project (possibly always making the Simulink project meta-data editable will fix this - it *almost* works) - the current issue is Simulink needs to edit the meta-data, but Perforce has made the files read-only, so Simulink throws up on itself Transferred from p4://perforce.perforce.com:1666@954622 |
||
#10 | 11642 | Paul Allen |
More fixes from discussion with Paul and after looking at other integrations work. Transferred from p4://perforce.perforce.com:1666@953553 |
||
#9 | 11641 | Paul Allen |
Fixes from chat with Paul. Transferred from p4://perforce.perforce.com:1666@953389 |
||
#8 | 11640 | Paul Allen |
Fixes to project integrity checks within Simulink. Transferred from p4://perforce.perforce.com:1666@952945 |
||
#7 | 11639 | Paul Allen |
Integrate code from the P4Jenkins plugin, add ability to authenticate with tickets or ticket file. Worked through some issues with getting current file state. Transferred from p4://perforce.perforce.com:1666@952289 |
||
#6 | 11638 | Paul Allen |
General cleanup, fixed some logging issues, fixed getFileState. Transferred from p4://perforce.perforce.com:1666@950492 |
||
#5 | 11637 | Paul Allen |
Fix up isLatest, uncheckout, add back code that searched for a .p4rc file within the sandbox directory first, then looks in ~/.p4simulink/ and finally ~/. Transferred from p4://perforce.perforce.com:1666@950121 |
||
#4 | 11636 | Paul Allen |
Updates to integrate with Simulink, merging into one directory. Added auto submits to add, remove, etc since this is what Simulink expects. Transferred from p4://perforce.perforce.com:1666@947024 |
||
#3 | 11635 | Paul Allen |
Updated gradle build to create a "fat jar", fixed some issues with the interfaces when it runs in Simulink itself. Added connection.configuration pane, switched the adapter to use a current changelist which gives a more svn-ish feel (which the Adapter is expecting). Added some more unit tests. General re-factoring. Transferred from p4://perforce.perforce.com:1666@946607 |
||
#2 | 11634 | Paul Allen |
Debugging actual adapter functionality and starting to integrate with Simulink. Transferred from p4://perforce.perforce.com:1666@938961 |
||
#1 | 11632 | Paul Allen |
Added commons-exec library, server startup/shutdown for unit tests, fixed a bunch of NPEs. Transferred from p4://perforce.perforce.com:1666@937331 |