blob: 85d9aff4b82423e3996ed8511d8d224fa7074aed [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2000, 2014 IBM Corporation and others.
*
* This program and the accompanying materials
* are made available under the terms of the Eclipse Public License 2.0
* which accompanies this distribution, and is available at
* https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*
* Contributors:
* IBM Corporation - initial API and implementation
*******************************************************************************/
package org.eclipse.team.internal.ccvs.core;
import java.io.*;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.*;
import org.eclipse.core.resources.*;
import org.eclipse.core.runtime.*;
import org.eclipse.core.runtime.Status;
import org.eclipse.core.runtime.preferences.InstanceScope;
import org.eclipse.jsch.core.IJSchService;
import org.eclipse.osgi.service.debug.DebugOptions;
import org.eclipse.osgi.service.debug.DebugOptionsListener;
import org.eclipse.osgi.util.NLS;
import org.eclipse.team.core.Team;
import org.eclipse.team.core.TeamException;
import org.eclipse.team.internal.ccvs.core.client.*;
import org.eclipse.team.internal.ccvs.core.client.Command.KSubstOption;
import org.eclipse.team.internal.ccvs.core.client.Command.QuietOption;
import org.eclipse.team.internal.ccvs.core.client.listeners.IConsoleListener;
import org.eclipse.team.internal.ccvs.core.mapping.CVSActiveChangeSetCollector;
import org.eclipse.team.internal.ccvs.core.resources.FileModificationManager;
import org.eclipse.team.internal.ccvs.core.util.*;
import org.eclipse.team.internal.core.subscribers.ActiveChangeSetManager;
import org.osgi.framework.BundleContext;
import org.osgi.framework.ServiceRegistration;
import org.osgi.util.tracker.ServiceTracker;
public class CVSProviderPlugin extends Plugin {
// preference names
public static final String READ_ONLY = "cvs.read.only"; //$NON-NLS-1$
public static final String ENABLE_WATCH_ON_EDIT = "cvs.watch.on.edit"; //$NON-NLS-1$
// external command to run for ext connection method
public static final String DEFAULT_CVS_RSH = "ssh"; //$NON-NLS-1$
// external command parameters
public static final String DEFAULT_CVS_RSH_PARAMETERS = "-l {user} {host}"; //$NON-NLS-1$
// remote command to run for ext connection method
public static final String DEFAULT_CVS_SERVER = "cvs"; //$NON-NLS-1$
// determines if empty directories received from the server should be pruned.
public static final boolean DEFAULT_PRUNE = true;
// determines if the user is prompted for confirmation before moving tags during a tag operation.
public static final boolean DEFAULT_CONFIRM_MOVE_TAG = true;
// determines if new directories should be discovered during update.
public static final boolean DEFAULT_FETCH = true;
// communication timeout with the server
public static final int DEFAULT_TIMEOUT = 60;
// file transfer compression level (0 - 9)
public static final int DEFAULT_COMPRESSION_LEVEL = 0;
// default text keyword substitution mode
public static final KSubstOption DEFAULT_TEXT_KSUBST_OPTION = Command.KSUBST_TEXT_EXPAND;
// cvs plugin extension points and ids
public static final String ID = "org.eclipse.team.cvs.core"; //$NON-NLS-1$
public static final QualifiedName CVS_WORKSPACE_SUBSCRIBER_ID = new QualifiedName("org.eclipse.team.cvs.ui.cvsworkspace-participant", "syncparticipant"); //$NON-NLS-1$ //$NON-NLS-2$
public static final String PT_AUTHENTICATOR = "authenticator"; //$NON-NLS-1$
public static final String PT_CONNECTIONMETHODS = "connectionmethods"; //$NON-NLS-1$
public static final String PT_FILE_MODIFICATION_VALIDATOR = "filemodificationvalidator"; //$NON-NLS-1$
private QuietOption quietness;
private int compressionLevel = DEFAULT_COMPRESSION_LEVEL;
private KSubstOption defaultTextKSubstOption = DEFAULT_TEXT_KSUBST_OPTION;
private boolean usePlatformLineend = true;
private int communicationsTimeout = DEFAULT_TIMEOUT;
private boolean pruneEmptyDirectories = DEFAULT_PRUNE;
private boolean fetchAbsentDirectories = DEFAULT_FETCH;
private boolean replaceUnmanaged = true;
private boolean repositoriesAreBinary = false;
private String cvsRshCommand = DEFAULT_CVS_RSH;
private String cvsRshParameters = DEFAULT_CVS_RSH_PARAMETERS;
private String cvsServer = DEFAULT_CVS_SERVER;
private boolean determineVersionEnabled = true;
private static volatile CVSProviderPlugin instance;
// CVS specific resource delta listeners
private BuildCleanupListener addDeleteMoveListener;
private FileModificationManager fileModificationManager;
private SyncFileChangeListener metaFileSyncListener;
private static final String REPOSITORIES_STATE_FILE = ".cvsProviderState"; //$NON-NLS-1$
// version numbers for the state file (a positive number indicates version 1)
private static final int REPOSITORIES_STATE_FILE_VERSION_2 = -1;
private static List decoratorEnablementListeners = new ArrayList();
private CVSWorkspaceSubscriber cvsWorkspaceSubscriber;
/**
* The identifier for the CVS nature
* (value <code>"org.eclipse.team.cvs.core.nature"</code>).
* The presence of this nature on a project indicates that it is
* CVS-capable.
*
* @see org.eclipse.core.resources.IProject#hasNature
*/
private static final String NATURE_ID = ID + ".cvsnature"; //$NON-NLS-1$
// File used to idicate if the workbench was shut down properly or not
private static final String CRASH_INDICATION_FILE = ".running"; //$NON-NLS-1$
private boolean crash;
private boolean autoShareOnImport;
private boolean useProxy;
public static final String PROXY_TYPE_HTTP = "HTTP"; //$NON-NLS-1$
public static final String PROXY_TYPE_SOCKS5 = "SOCKS5"; //$NON-NLS-1$
public static final String HTTP_DEFAULT_PORT = "80"; //$NON-NLS-1$
public static final String SOCKS5_DEFAULT_PORT = "1080"; //$NON-NLS-1$
private String proxyType;
private String proxyHost;
private String proxyPort;
private boolean useProxyAuth;
private CVSActiveChangeSetCollector changeSetManager;
private ServiceRegistration debugRegistration;
private ServiceTracker tracker;
private static final String INFO_PROXY_USER = "org.eclipse.team.cvs.core.proxy.user"; //$NON-NLS-1$
private static final String INFO_PROXY_PASS = "org.eclipse.team.cvs.core.proxy.pass"; //$NON-NLS-1$
private static final URL FAKE_URL;
static {
URL temp = null;
try {
temp = new URL("http://org.eclipse.team.cvs.proxy.auth");//$NON-NLS-1$
} catch (MalformedURLException e) {
// Should never fail
}
FAKE_URL = temp;
}
public synchronized CVSWorkspaceSubscriber getCVSWorkspaceSubscriber() {
if (cvsWorkspaceSubscriber == null) {
cvsWorkspaceSubscriber = new CVSWorkspaceSubscriber(
CVS_WORKSPACE_SUBSCRIBER_ID,
CVSMessages.CVSProviderPlugin_20);
}
return cvsWorkspaceSubscriber;
}
/**
* Constructor for CVSProviderPlugin.
* @param descriptor
*/
public CVSProviderPlugin() {
super();
instance = this;
}
/**
* Convenience method for logging CoreExceptions to the plugin log
*/
public static void log(CoreException e) {
log(e.getStatus().getSeverity(), e.getMessage(), e);
}
/**
* Log the given exception along with the provided message and severity indicator
*/
public static void log(int severity, String message, Throwable e) {
log(new Status(severity, ID, 0, message, e));
}
/**
* Log the given status. Do not use this method for the IStatus from a CoreException.
* Use<code>log(CoreException)</code> instead so the stack trace is not lost.
*/
public static void log(IStatus status) {
getPlugin().getLog().log(status);
}
/**
* Returns the singleton plug-in instance.
*
* @return the plugin instance
*/
public static CVSProviderPlugin getPlugin() {
return instance;
}
/**
* Answers the repository provider type id for the cvs plugin
*/
public static String getTypeId() {
return NATURE_ID;
}
/**
* Sets the file transfer compression level. (if supported)
* Valid levels are: 0 (disabled), 1 (worst/fastest) - 9 (best/slowest)
*/
public void setCompressionLevel(int level) {
compressionLevel = level;
}
/**
* Gets the file transfer compression level.
*/
public int getCompressionLevel() {
return compressionLevel;
}
/**
* Sets the default keyword substitution mode for text files.
*/
public void setDefaultTextKSubstOption(KSubstOption ksubst) {
defaultTextKSubstOption = ksubst;
}
/**
* Gets the default keyword substitution mode for text files.
*/
public KSubstOption getDefaultTextKSubstOption() {
return defaultTextKSubstOption;
}
/**
* Should the CVS adapter prune empty directories
*/
public boolean getPruneEmptyDirectories() {
return pruneEmptyDirectories;
}
/**
* Set whether the CVS adapter should prune empty directories
*/
public void setPruneEmptyDirectories(boolean prune) {
pruneEmptyDirectories = prune;
}
/**
* Get the communications timeout value in seconds
*/
public int getTimeout() {
return communicationsTimeout;
}
/**
* Set the timeout value for communications to a value in seconds.
* The value must be greater than or equal 0. If is it 0, there is no timeout.
*/
public void setTimeout(int timeout) {
this.communicationsTimeout = Math.max(0, timeout);
}
/**
* Set the quietness option to use with cvs commands.
* Can be "", "-q" or "-Q"
*/
public void setQuietness(QuietOption option) {
this.quietness = option;
}
/**
* Get the quietness option for commands
*/
public QuietOption getQuietness() {
return quietness;
}
/**
* Set the console listener for commands.
* @param consoleListener the listener
*/
public void setConsoleListener(IConsoleListener consoleListener) {
ConsoleListeners.getInstance().addListener(consoleListener);
}
@Override
public void start(BundleContext context) throws Exception {
super.start(context);
// register debug options listener
Hashtable properties = new Hashtable(2);
properties.put(DebugOptions.LISTENER_SYMBOLICNAME, ID);
debugRegistration = context.registerService(DebugOptionsListener.class, Policy.DEBUG_OPTIONS_LISTENER, properties);
// load the state which includes the known repositories
loadOldState();
crash = createCrashFile();
// Initialize CVS change listeners. Note that the report type is important.
IWorkspace workspace = ResourcesPlugin.getWorkspace();
addDeleteMoveListener = new BuildCleanupListener();
fileModificationManager = new FileModificationManager();
metaFileSyncListener = new SyncFileChangeListener();
workspace.addResourceChangeListener(addDeleteMoveListener, IResourceChangeEvent.POST_BUILD);
workspace.addResourceChangeListener(metaFileSyncListener, IResourceChangeEvent.POST_CHANGE);
workspace.addResourceChangeListener(fileModificationManager, IResourceChangeEvent.POST_CHANGE);
getCVSWorkspaceSubscriber();
// Must load the change set manager on startup since it listens to deltas
getChangeSetManager();
tracker = new ServiceTracker(getBundle().getBundleContext(), IJSchService.class.getName(), null);
tracker.open();
}
@Override
public void stop(BundleContext context) throws Exception {
try {
// unregister debug options listener
debugRegistration.unregister();
debugRegistration = null;
savePluginPreferences();
// remove listeners
IWorkspace workspace = ResourcesPlugin.getWorkspace();
workspace.removeResourceChangeListener(metaFileSyncListener);
workspace.removeResourceChangeListener(fileModificationManager);
workspace.removeResourceChangeListener(addDeleteMoveListener);
// remove all of this plugin's save participants. This is easier than having
// each class that added itself as a participant to have to listen to shutdown.
workspace.removeSaveParticipant(this);
getChangeSetManager().dispose();
tracker.close();
deleteCrashFile();
} finally {
super.stop(context);
}
}
@Override
protected void initializeDefaultPluginPreferences(){
Preferences store = getPluginPreferences();
store.setDefault(READ_ONLY, false);
store.setDefault(ENABLE_WATCH_ON_EDIT, false);
}
/**
* Gets the cvsRshCommand.
* @return Returns a String
*/
public String getCvsRshCommand() {
return cvsRshCommand;
}
/**
* Sets the cvsRshCommand.
* @param cvsRshCommand The cvsRshCommand to set
*/
public void setCvsRshCommand(String cvsRshCommand) {
this.cvsRshCommand = cvsRshCommand;
}
/**
* Returns the cvsRshParameters.
* @return String
*/
public String getCvsRshParameters() {
return cvsRshParameters;
}
/**
* Sets the cvsRshParameters.
* @param cvsRshParameters The cvsRshParameters to set
*/
public void setCvsRshParameters(String cvsRshParameters) {
this.cvsRshParameters = cvsRshParameters;
}
/**
* Gets the cvsServer.
* @return Returns a String
*/
public String getCvsServer() {
return cvsServer;
}
/**
* Sets the cvsServer.
* @param cvsServer The cvsServer to set
*/
public void setCvsServer(String cvsServer) {
this.cvsServer = cvsServer;
}
/**
* Gets the etchAbsentDirectories.
* @return Returns a boolean
*/
public boolean getFetchAbsentDirectories() {
return fetchAbsentDirectories;
}
public boolean getRepositoriesAreBinary() {
return repositoriesAreBinary;
}
/**
* Sets the fetchAbsentDirectories.
* @param etchAbsentDirectories The etchAbsentDirectories to set
*/
public void setFetchAbsentDirectories(boolean fetchAbsentDirectories) {
this.fetchAbsentDirectories = fetchAbsentDirectories;
}
public void setRepositoriesAreBinary(boolean binary) {
repositoriesAreBinary = binary;
}
public static void broadcastDecoratorEnablementChanged(final boolean enabled) {
ICVSDecoratorEnablementListener[] listeners;
synchronized(decoratorEnablementListeners) {
listeners = (ICVSDecoratorEnablementListener[]) decoratorEnablementListeners.toArray(new ICVSDecoratorEnablementListener[decoratorEnablementListeners.size()]);
}
for (int i = 0; i < listeners.length; i++) {
final ICVSDecoratorEnablementListener listener = listeners[i];
ISafeRunnable code = new ISafeRunnable() {
public void run() throws Exception {
listener.decoratorEnablementChanged(enabled);
}
public void handleException(Throwable e) {
// don't log the exception....it is already being logged in Platform#run
}
};
Platform.run(code);
}
}
/**
* Gets the replaceUnmanaged.
* @return Returns a boolean
*/
public boolean isReplaceUnmanaged() {
return replaceUnmanaged;
}
/**
* Sets the replaceUnmanaged.
* @param replaceUnmanaged The replaceUnmanaged to set
*/
public void setReplaceUnmanaged(boolean replaceUnmanaged) {
this.replaceUnmanaged = replaceUnmanaged;
}
/**
* Register to receive notification of repository creation and disposal
*/
public void addRepositoryListener(ICVSListener listener) {
KnownRepositories.getInstance().addRepositoryListener(listener);
}
/**
* Register to receive notification of enablement of sync info decoration requirements. This
* can be useful for providing lazy initialization of caches that are only required for decorating
* resource with CVS information.
*/
public void addDecoratorEnablementListener(ICVSDecoratorEnablementListener listener) {
synchronized(decoratorEnablementListeners) {
decoratorEnablementListeners.add(listener);
}
}
/**
* De-register a listener
*/
public void removeRepositoryListener(ICVSListener listener) {
KnownRepositories.getInstance().removeRepositoryListener(listener);
}
/**
* De-register the decorator enablement listener.
*/
public void removeDecoratorEnablementListener(ICVSDecoratorEnablementListener listener) {
synchronized(decoratorEnablementListeners) {
decoratorEnablementListeners.remove(listener);
}
}
/**
* Return a list of the know repository locations. This is left
* here to isolate the RelEng tools plugin from changes in CVS core.
*/
public ICVSRepositoryLocation[] getKnownRepositories() {
return KnownRepositories.getInstance().getRepositories();
}
private void loadOldState() {
try {
IPath pluginStateLocation = CVSProviderPlugin.getPlugin().getStateLocation().append(REPOSITORIES_STATE_FILE);
File file = pluginStateLocation.toFile();
if (file.exists()) {
try {
DataInputStream dis = new DataInputStream(new FileInputStream(file));
readOldState(dis);
dis.close();
// The file is no longer needed as the state is
// persisted in the user settings
file.delete();
} catch (IOException e) {
throw new TeamException(new Status(IStatus.ERROR, CVSProviderPlugin.ID, TeamException.UNABLE, CVSMessages.CVSProvider_ioException, e));
}
}
} catch (TeamException e) {
Util.logError(CVSMessages.CVSProvider_errorLoading, e);
}
}
private void readOldState(DataInputStream dis) throws IOException, CVSException {
KnownRepositories instance = KnownRepositories.getInstance();
int count = dis.readInt();
if (count >= 0) {
// this is the version 1 format of the state file
for (int i = 0; i < count; i++) {
ICVSRepositoryLocation location = instance.getRepository(dis.readUTF());
instance.addRepository(location, false /* no need to broadcast on startup */);
}
} else if (count == REPOSITORIES_STATE_FILE_VERSION_2) {
count = dis.readInt();
for (int i = 0; i < count; i++) {
ICVSRepositoryLocation location = instance.getRepository(dis.readUTF());
instance.addRepository(location, false /* no need to broadcast on startup */);
// Read the next field which is no longer used
dis.readUTF();
}
} else {
Util.logError(NLS.bind(CVSMessages.CVSProviderPlugin_unknownStateFileVersion, new String[] { Integer.valueOf(count).toString() }), null);
}
}
public static boolean isText(IFile file) {
if (CVSProviderPlugin.getPlugin().getRepositoriesAreBinary()) return false;
return Team.getFileContentManager().getType(file) == Team.TEXT;
}
/**
* Gets the determineVersionEnabled.
* @return boolean
*/
public boolean isDetermineVersionEnabled() {
return determineVersionEnabled;
}
/**
* Sets the determineVersionEnabled.
* @param determineVersionEnabled The determineVersionEnabled to set
*/
public void setDetermineVersionEnabled(boolean determineVersionEnabled) {
this.determineVersionEnabled = determineVersionEnabled;
}
/**
* Returns the fileModificationManager.
* @return FileModificationManager
*/
public FileModificationManager getFileModificationManager() {
return fileModificationManager;
}
/**
* @return boolean
*/
public boolean isWatchEditEnabled() {
return getPluginPreferences().getBoolean(CVSProviderPlugin.READ_ONLY);
}
public void setDebugProtocol(boolean value) {
Policy.DEBUG_CVS_PROTOCOL = value;
}
public boolean isDebugProtocol() {
return Policy.DEBUG_CVS_PROTOCOL;
}
/*
* Create the crash indicator file. This method returns true if the file
* already existed, indicating that a crash occurred on the last invokation of
* the platform.
*/
private boolean createCrashFile() {
IPath pluginStateLocation = CVSProviderPlugin.getPlugin().getStateLocation();
File crashFile = pluginStateLocation.append(CRASH_INDICATION_FILE).toFile();
if (crashFile.exists()) {
return true;
}
try {
crashFile.createNewFile();
} catch (IOException e) {
CVSProviderPlugin.log(IStatus.ERROR, e.getMessage(), e);
}
return false;
}
private void deleteCrashFile() {
IPath pluginStateLocation = CVSProviderPlugin.getPlugin().getStateLocation();
File crashFile = pluginStateLocation.append(CRASH_INDICATION_FILE).toFile();
crashFile.delete();
}
public boolean crashOnLastRun() {
return crash;
}
/**
* Return the CVS preferences node in the instance scope
*/
public org.osgi.service.prefs.Preferences getInstancePreferences() {
return InstanceScope.INSTANCE.getNode(getBundle().getSymbolicName());
}
/**
* @return Returns the usePlatformLineend.
*/
public boolean isUsePlatformLineend() {
return usePlatformLineend;
}
/**
* @param usePlatformLineend The usePlatformLineend to set.
*/
public void setUsePlatformLineend(boolean usePlatformLineend) {
this.usePlatformLineend = usePlatformLineend;
}
public void setAutoshareOnImport(boolean autoShareOnImport) {
this.autoShareOnImport = autoShareOnImport;
}
public boolean isAutoshareOnImport() {
return autoShareOnImport;
}
/**
* @return Returns the watchOnEdit.
*/
public boolean isWatchOnEdit() {
return getPluginPreferences().getBoolean(CVSProviderPlugin.ENABLE_WATCH_ON_EDIT);
}
// proxy configuration
public void setUseProxy(boolean useProxy) {
this.useProxy = useProxy;
}
public boolean isUseProxy() {
return this.useProxy;
}
public void setProxyType(String proxyType) {
this.proxyType = proxyType;
}
public String getProxyType() {
return this.proxyType;
}
public void setProxyHost(String proxyHost) {
this.proxyHost = proxyHost;
}
public String getProxyHost() {
return this.proxyHost;
}
public void setProxyPort(String proxyPort) {
this.proxyPort = proxyPort;
}
public String getProxyPort() {
return this.proxyPort;
}
public void setUseProxyAuth(boolean useProxyAuth) {
this.useProxyAuth = useProxyAuth;
}
public boolean isUseProxyAuth() {
return this.useProxyAuth;
}
public String getProxyUser() {
Object user = getAuthInfo().get(INFO_PROXY_USER);
return user==null ? "" : (String) user; //$NON-NLS-1$
}
public String getProxyPassword() {
Object pass = getAuthInfo().get(INFO_PROXY_PASS);
return pass==null ? "" : (String) pass; //$NON-NLS-1$
}
private Map getAuthInfo() {
// Retrieve username and password from keyring.
Map authInfo = Platform.getAuthorizationInfo(FAKE_URL, "proxy", ""); //$NON-NLS-1$ //$NON-NLS-2$
return authInfo!=null ? authInfo : Collections.EMPTY_MAP;
}
public void setProxyAuth(String proxyUser, String proxyPass) {
Map authInfo = getAuthInfo();
if (authInfo.size()==0) {
authInfo = new java.util.HashMap(4);
}
if (proxyUser != null) {
authInfo.put(INFO_PROXY_USER, proxyUser);
}
if (proxyPass != null) {
authInfo.put(INFO_PROXY_PASS, proxyPass);
}
try {
Platform.addAuthorizationInfo(FAKE_URL, "proxy", "", authInfo); //$NON-NLS-1$ //$NON-NLS-2$
} catch (CoreException e) {
// We should probably wrap the CoreException here!
CVSProviderPlugin.log(e);
}
}
public synchronized ActiveChangeSetManager getChangeSetManager() {
if (changeSetManager == null) {
changeSetManager = new CVSActiveChangeSetCollector(CVSProviderPlugin.getPlugin().getCVSWorkspaceSubscriber());
}
return changeSetManager;
}
public IJSchService getJSchService() {
return (IJSchService)tracker.getService();
}
}