Skip to main content
aboutsummaryrefslogtreecommitdiffstats
blob: 1c0ee061ba4d95b62607b13c7dd7ebdd472dd752 (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
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
/*******************************************************************************
 * Copyright (c) 2000, 2009 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.resources;

import java.util.*;

import org.eclipse.core.runtime.*;
import org.eclipse.osgi.util.NLS;
import org.eclipse.team.internal.ccvs.core.*;
import org.eclipse.team.internal.ccvs.core.client.*;
import org.eclipse.team.internal.ccvs.core.client.Command.*;
import org.eclipse.team.internal.ccvs.core.client.listeners.*;
import org.eclipse.team.internal.ccvs.core.connection.CVSServerException;
import org.eclipse.team.internal.ccvs.core.util.Util;

/**
 * Fetch the children for the given parent folder. When fetchMembers is invoked,
 * the children of the folder will be fetched from the server and assigned to
 * the children of the parent folder.
 */
public class RemoteFolderMemberFetcher implements IUpdateMessageListener, IStatusListener {
	
	private final RemoteFolder parentFolder;
	private CVSTag tag;
	
	List<RemoteFolder> folders = new ArrayList<>();
	List<RemoteFile> files = new ArrayList<>();
	boolean exists = true;
	List<CVSException> exceptions = new ArrayList<>();
	
	protected RemoteFolderMemberFetcher(RemoteFolder parentFolder, CVSTag tag) {
		this.tag = tag;
		this.parentFolder = parentFolder;
	}
	
	/**
	 * Fetch the members for a given tag and returns them.
	 * During the execution of this method, the instance variable children
	 * will be used to contain the children. However, the variable is reset
	 * and the result returned. Thus, instances of RemoteFolder do not
	 * persist the children. Subclasses (namely RemoteFolderTree) may
	 * persist the children.
	 */
	public void fetchMembers(IProgressMonitor monitor) throws CVSException {
		fetchMembers(monitor, tag);
	}
	public void fetchMembers(IProgressMonitor monitor, CVSTag tag) throws CVSException {
		final IProgressMonitor progress = Policy.monitorFor(monitor);
		progress.beginTask(CVSMessages.RemoteFolder_getMembers, 100); 
		try {
			// Update the parent folder children so there are no children
			updateParentFolderChildren();
			// Perform an update to retrieve the child files and folders
			IStatus status = performUpdate(Policy.subMonitorFor(progress, 50), tag);
			// Update the parent folder with the new children
			updateParentFolderChildren();
			Policy.checkCanceled(monitor);
			
			// Handle any errors that were identified by the listener
			performErrorCheck(status, CVSMessages.RemoteFolder_errorFetchingMembers); 
			
			// Get the revision numbers for the files
			ICVSFile[] remoteFiles = getFiles();
			if (remoteFiles.length > 0) {
				updateFileRevisions(remoteFiles, Policy.subMonitorFor(progress, 50));
			} else {
				progress.worked(50);
			}
		} catch (CVSServerException e) {
			if ( ! e.isNoTagException() && e.containsErrors())
				throw e;
			if (tag == null)
				throw e;
			// we now know that this is an exception caused by a cvs bug.
			// if the folder has no files in it (just subfolders) cvs does not respond with the subfolders...
			// workaround: retry the request with no tag to get the directory names (if any)
			Policy.checkCanceled(progress);
			fetchMembers(Policy.subMonitorFor(progress, 50), null);
		} finally {
			progress.done();
		}
	}

	protected IStatus performUpdate(IProgressMonitor progress, CVSTag tag) throws CVSException {
		progress.beginTask(null, 100);
		Session session = new Session(parentFolder.getRepository(), parentFolder, false /* output to console */);
		session.open(Policy.subMonitorFor(progress, 10), false /* read-only */);
		try {
			// Build the local options
			final List<LocalOption> localOptions = new ArrayList<>();
			localOptions.add(Update.RETRIEVE_ABSENT_DIRECTORIES);
			if (tag != null) localOptions.add(Update.makeTagOption(tag));
			
			return Command.UPDATE.execute(
				session,
				new GlobalOption[] { Command.DO_NOT_CHANGE },
				(LocalOption[])localOptions.toArray(new LocalOption[localOptions.size()]),
				new ICVSResource[] { parentFolder },
				new UpdateListener(this),
			Policy.subMonitorFor(progress, 90));
		} finally {
			session.close();
		}
	}
	
	protected void updateFileRevisions(final ICVSFile[] files, IProgressMonitor monitor) throws CVSException {
			
		// Perform a "cvs status..." with a listener
		monitor = Policy.monitorFor(monitor);
		monitor.beginTask(null, 100);
		QuietOption quietness = CVSProviderPlugin.getPlugin().getQuietness();
		try {
			CVSProviderPlugin.getPlugin().setQuietness(Command.VERBOSE);
			Session session = new Session(parentFolder.getRepository(), parentFolder, false /* output to console */);
			session.open(Policy.subMonitorFor(monitor, 10), false /* read-only */);
			try {
				IStatus status = Command.STATUS.execute(
					session,
					Command.NO_GLOBAL_OPTIONS,
					Command.NO_LOCAL_OPTIONS,
					files,
					new StatusListener(this),
					Policy.subMonitorFor(monitor, 90));
				performErrorCheck(status, CVSMessages.RemoteFolder_errorFetchingRevisions); 
				// TODO: Ensure all files have a revision?
			} finally {
				session.close();
			}
		} finally {
			CVSProviderPlugin.getPlugin().setQuietness(quietness);
		}
	}

	private void performErrorCheck(IStatus status, String errorTitle) throws CVSException {
		if (status.getCode() == CVSStatus.SERVER_ERROR) {
			// Only throw the exception if no files or folders were found
			if (folders.size() + files.size() == 0) {
				throw new CVSServerException(status);
			} else {
				CVSProviderPlugin.log(new CVSServerException(status));
			}	
		}
		if (!exists) {
			IStatus notExistStatus = new CVSStatus(IStatus.ERROR, CVSStatus.DOES_NOT_EXIST, NLS.bind(CVSMessages.RemoteFolder_doesNotExist, new String[] { this.parentFolder.getRepositoryRelativePath() }), parentFolder);
			throw new CVSException(notExistStatus); 
		}
		
		// Report any internal exceptions that occurred fetching the members
		if ( ! exceptions.isEmpty()) {
			if (exceptions.size() == 1) {
				throw (CVSException)exceptions.get(0);
			} else {
				MultiStatus multi = new MultiStatus(CVSProviderPlugin.ID, 0, errorTitle, null);
				for (int i = 0; i < exceptions.size(); i++) {
					multi.merge(((CVSException)exceptions.get(i)).getStatus());
				}
				throw new CVSException(multi);
			}
		}
	}
	
	@Override
	public void directoryInformation(ICVSFolder commandRoot, String stringPath, boolean newDirectory) {
		try {
			IPath path = this.parentFolder.getRelativePathFromRootRelativePath(commandRoot, new Path(null, stringPath));
			if (path.segmentCount() == 1) {
			    String pathName = path.lastSegment();
			    if (!pathName.equals(".")) { //$NON-NLS-1$
			        recordFolder(path.lastSegment());
			    }
			}
		} catch (CVSException e) {
			exceptions.add(e);
		}
	}

	@Override
	public void directoryDoesNotExist(ICVSFolder parent, String stringPath) {
		try {
			IPath path = this.parentFolder.getRelativePathFromRootRelativePath(parent, new Path(null, stringPath));
			if (path.isEmpty()) {
				parentDoesNotExist();
			}
		} catch (CVSException e) {
			exceptions.add(e);
		}
	}

	@Override
	public void fileInformation(int type, ICVSFolder parent, String filename) {
		try {
			IPath filePath = new Path(null, filename);
			filePath = this.parentFolder.getRelativePathFromRootRelativePath(parent, filePath);	
			if( filePath.segmentCount() == 1 ) {
				String properFilename = filePath.lastSegment();
				recordFile(properFilename);
			}
		} catch (CVSException e) {
			exceptions.add(e);
		}
	}

	@Override
	public void fileDoesNotExist(ICVSFolder parent, String filename) {
	}

	@Override
	public void fileStatus(ICVSFolder commandRoot, String path, String remoteRevision) {
		if (remoteRevision == IStatusListener.FOLDER_REVISION)
			// Ignore any folders
			return;
		try {
			((RemoteFile)parentFolder.getChild(Util.getLastSegment(path))).setRevision(remoteRevision);
		} catch (CVSException e) {
			exceptions.add(e);
		}
	}
	
	/**
	 * This method is invoked for each child folder as the responses are being received from
	 * the server. Default behavior is to record the folder for later retrieval using <code>getChilren()</code>. 
	 * Subclasses may override but should invoke the inherited method to ensure the folder gets recorded.
	 * @param name the name of the child folder
	 */
	protected RemoteFolder recordFolder(String name) {
		RemoteFolder folder = new RemoteFolder(
			parentFolder, 
			parentFolder.getRepository(), 
			Util.appendPath(parentFolder.getRepositoryRelativePath(), name), 
			tag);
		folders.add(folder);
		return folder;
	}

	/**
	 * This method is invoked for each child file as the responses are being received from
	 * the server. Default behavior is to record the file for later retrieval using <code>getChildren()</code>. 
	 * Subclasses may override but should invoke the inherited method to ensure the file gets recorded.
	 * This is important because the file revisions for any files are fetched subsequent to the fetching
	 * of the children.
	 * @param name the name of the child folder
	 */
	protected RemoteFile recordFile(String name) {
		RemoteFile file = new RemoteFile(
			parentFolder, 
			Update.STATE_NONE, 
			name, 
			null, /* revision unknown */
			null, /* keyword mode unknown */
			tag);
		files.add(file);
		return file;
	}
	
	/**
	 * This method is invoked to indicate that the parent being queried for children
	 * does not exist. Subclasses may override to get early notification of this but 
	 * should still invoke the inherited method.
	 */
	protected void parentDoesNotExist() {
		exists = false;
	}

	/**
	 * Update the parent folder such that it's children are the
	 * children that have been fetched by the receiver.
	 */
	protected void updateParentFolderChildren() {
		parentFolder.setChildren(getFetchedChildren());
	}
	
	/**
	 * Return the child files fetched from the server.
	 * @return
	 */
	protected ICVSFile[] getFiles() {
		return (ICVSFile[]) files.toArray(new ICVSFile[files.size()]);
	}
	
	/**
	 * Return an array of all fetched children.
	 * @return
	 */
	public ICVSRemoteResource[] getFetchedChildren() {
		ICVSRemoteResource[] resources = new ICVSRemoteResource[folders.size() + files.size()];
		int count = 0;
		for (Iterator iter = folders.iterator(); iter.hasNext();) {
			ICVSRemoteResource resource = (ICVSRemoteResource) iter.next();
			resources[count++] = resource;
		}
		for (Iterator iter = files.iterator(); iter.hasNext();) {
			ICVSRemoteResource resource = (ICVSRemoteResource) iter.next();
			resources[count++] = resource;
		}
		return resources;
	}

}

Back to the top