Skip to main content
aboutsummaryrefslogtreecommitdiffstats
blob: 1ee6bbb038abd6c27b3c1cc1bf331657b332abd4 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
/*******************************************************************************
 * Copyright (c) 2000, 2004 IBM Corporation and others.
 * All rights reserved. This program and the accompanying materials 
 * are made available under the terms of the Common Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/cpl-v10.html
 * 
 * Contributors:
 *     IBM Corporation - initial API and implementation
 *******************************************************************************/
package org.eclipse.team.internal.ccvs.core.util;

import java.util.*;

import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.ResourcesPlugin;
import org.eclipse.core.runtime.*;
import org.eclipse.team.core.RepositoryProvider;
import org.eclipse.team.internal.ccvs.core.*;
import org.eclipse.team.internal.ccvs.core.connection.CVSRepositoryLocation;
import org.eclipse.team.internal.ccvs.core.resources.CVSWorkspaceRoot;
import org.eclipse.team.internal.ccvs.core.syncinfo.FolderSyncInfo;
import org.osgi.service.prefs.BackingStoreException;
import org.osgi.service.prefs.Preferences;
import org.eclipse.team.internal.ccvs.core.Policy;

/**
 * This class keeps track of the CVS repository locations that are known to
 * the CVS plugin.
 */
public class KnownRepositories {

	private List repositoryListeners = new ArrayList();
	private Map repositories;

	private static KnownRepositories instance;
	
	public static synchronized KnownRepositories getInstance() {
		if (instance == null) {
			instance = new KnownRepositories();
		}
		return instance;
	}
	
	/*
	 * Private class used to safely notify listeners of resouce sync info changes. 
	 * Subclass override the notify(IResourceStateChangeListener) method to
	 * fire specific events inside an ISafeRunnable.
	 */
	private abstract class Notification implements ISafeRunnable {
		private ICVSListener listener;
		public void handleException(Throwable exception) {
			// don't log the exception....it is already being logged in Platform#run
		}
		public void run(ICVSListener listener) {
			this.listener = listener;
			Platform.run(this);
		}
		public void run() throws Exception {
			notify(listener);
		}
		/**
		 * Subsclasses overide this method to send an event safely to a lsistener
		 * @param listener
		 */
		protected abstract void notify(ICVSListener listener);
	}
	
	/**
	 * Register to receive notification of repository creation and disposal
	 */
	public void addRepositoryListener(ICVSListener listener) {
		synchronized(repositoryListeners) {
			repositoryListeners.add(listener);
		}
	}
	
	/**
	 * De-register a listener
	 */
	public void removeRepositoryListener(ICVSListener listener) {
		synchronized(repositoryListeners) {
			repositoryListeners.remove(listener);
		}
	}

	/**
	 * Add the repository to the receiver's list of known repositories. Doing this will enable
	 * password caching accross platform invokations.
	 */
	public ICVSRepositoryLocation addRepository(final ICVSRepositoryLocation repository, boolean broadcast) {
		// Check the cache for an equivalent instance and if there is one, just update the cache
		CVSRepositoryLocation existingLocation = internalGetRepository(repository.getLocation(false));
		if (existingLocation == null) {
			// Store the location
			store((CVSRepositoryLocation)repository);
			existingLocation = (CVSRepositoryLocation)repository;
		}
		// Notify no matter what since it may not have been broadcast before
		if (broadcast) {
			((CVSRepositoryLocation)repository).updateCache();
			fireNotification(new Notification() {
				public void notify(ICVSListener listener) {
					listener.repositoryAdded(repository);
				}
			});
		}
		return existingLocation;
	}
	
	/**
	 * Dispose of the repository location
	 * 
	 * Removes any cached information about the repository such as a remembered password.
	 */
	public void disposeRepository(final ICVSRepositoryLocation repository) {
		((CVSRepositoryLocation)repository).dispose();
		if (getRepositoriesMap().remove(repository.getLocation(false)) != null) {
			fireNotification(new Notification() {
				public void notify(ICVSListener listener) {
					listener.repositoryRemoved(repository);
				}
			});
		}
	}

	/**
	 * Answer whether the provided repository location is known by the provider or not.
	 * The location string corresponds to the Strin returned by ICVSRepositoryLocation#getLocation()
	 */
	public boolean isKnownRepository(String location) {
		return internalGetRepository(location) != null;
	}

	/** 
	 * Return a list of the know repository locations
	 */
	public ICVSRepositoryLocation[] getRepositories() {
		return (ICVSRepositoryLocation[])getRepositoriesMap().values().toArray(new ICVSRepositoryLocation[getRepositoriesMap().size()]);
	}
	
	/**
	 * Get the repository instance which matches the given String. The format of the String is
	 * the same as that returned by ICVSRepositoryLocation#getLocation().
	 * The format is:
	 * 
	 *   connection:user[:password]@host[#port]:root
	 * 
	 * where [] indicates optional and the identier meanings are:
	 * 
	 * 	 connection The connection method to be used
	 *   user The username for the connection
	 *   password The password used for the connection (optional)
	 *   host The host where the repository resides
	 *   port The port to connect to (optional)
	 *   root The server directory where the repository is located
	 * 
	 * If the repository is already registered, the cahced instance is returned.
	 * Otherwise, a new uncached instance is returned.
	 * 
	 * WARNING: Providing the password as part of the String will result in the password being part
	 * of the location permanently. This means that it cannot be modified by the authenticator. 
	 */
	public ICVSRepositoryLocation getRepository(String location) throws CVSException {
		ICVSRepositoryLocation repository = internalGetRepository(location);
		if (repository == null) {
			repository = CVSRepositoryLocation.fromString(location);
		}
		return repository;
	}
	
	private CVSRepositoryLocation internalGetRepository(String location) {
		return (CVSRepositoryLocation)getRepositoriesMap().get(location);
	}
	
	/*
	 * Cache the location and store it in the preferences for persistance
	 */
	private void store(CVSRepositoryLocation location) {
		// Cache the location instance for later retrieval
		getRepositoriesMap().put(location.getLocation(), location);
		location.storePreferences();
	}
	
	private Map getRepositoriesMap() {
		if (repositories == null) {
			// Load the repositories from the preferences
			repositories = new HashMap();
			Preferences prefs = CVSRepositoryLocation.getParentPreferences();
			try {
				String[] keys = prefs.childrenNames();
				for (int i = 0; i < keys.length; i++) {
					String key = keys[i];
					try {
						Preferences node = prefs.node(key);
						String location = node.get(CVSRepositoryLocation.PREF_LOCATION, null);
						if (location != null) {
							repositories.put(location, CVSRepositoryLocation.fromString(location));
						} else {
							node.removeNode();
							prefs.flush();
						}
					} catch (CVSException e) {
						// Log and continue
						CVSProviderPlugin.log(e);
					}
				}
				if (repositories.isEmpty()) {
					getRepositoriesFromProjects();
				}
			} catch (BackingStoreException e) {
				// Log and continue (although all repos will be missing)
				CVSProviderPlugin.log(IStatus.ERROR, Policy.bind("KnownRepositories.0"), e); //$NON-NLS-1$
			} catch (CVSException e) {
				CVSProviderPlugin.log(e);
			}
		}
		return repositories;
	}
	
	private void getRepositoriesFromProjects() throws CVSException {
		// If the file did not exist, then prime the list of repositories with
		// the providers with which the projects in the workspace are shared.
		IProject[] projects = ResourcesPlugin.getWorkspace().getRoot().getProjects();
		for (int i = 0; i < projects.length; i++) {
			RepositoryProvider provider = RepositoryProvider.getProvider(projects[i], CVSProviderPlugin.getTypeId());
			if (provider!=null) {
				ICVSFolder folder = (ICVSFolder)CVSWorkspaceRoot.getCVSResourceFor(projects[i]);
				FolderSyncInfo info = folder.getFolderSyncInfo();
				if (info != null) {
					addRepository(getRepository(info.getRoot()), false);
				}
			}
		}
	}
	
	private ICVSListener[] getListeners() {
		synchronized(repositoryListeners) {
			return (ICVSListener[]) repositoryListeners.toArray(new ICVSListener[repositoryListeners.size()]);
		}
	}
	
	private void fireNotification(Notification notification) {
		// Get a snapshot of the listeners so the list doesn't change while we're firing
		ICVSListener[] listeners = getListeners();
		// Notify each listener in a safe manner (i.e. so their exceptions don't kill us)
		for (int i = 0; i < listeners.length; i++) {
			ICVSListener listener = listeners[i];
			notification.run(listener);
		}
	}
}

Back to the top