Skip to main content
aboutsummaryrefslogtreecommitdiffstats
blob: 588c34f9a190047cd2514712f11d30011171f870 (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
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
/*******************************************************************************
 * Copyright (c) 2000, 2007 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.ui.subscriber;

import java.lang.reflect.InvocationTargetException;
import java.util.*;

import org.eclipse.compare.structuremergeviewer.IDiffElement;
import org.eclipse.core.resources.*;
import org.eclipse.core.runtime.*;
import org.eclipse.jface.window.Window;
import org.eclipse.osgi.util.NLS;
import org.eclipse.team.core.TeamException;
import org.eclipse.team.core.synchronize.SyncInfo;
import org.eclipse.team.core.synchronize.SyncInfoSet;
import org.eclipse.team.core.variants.IResourceVariant;
import org.eclipse.team.internal.ccvs.core.*;
import org.eclipse.team.internal.ccvs.core.client.PruneFolderVisitor;
import org.eclipse.team.internal.ccvs.core.resources.CVSWorkspaceRoot;
import org.eclipse.team.internal.ccvs.core.resources.EclipseSynchronizer;
import org.eclipse.team.internal.ccvs.ui.CVSUIMessages;
import org.eclipse.team.internal.ccvs.ui.CVSUIPlugin;
import org.eclipse.team.internal.ccvs.ui.Policy;
import org.eclipse.team.internal.ui.TeamUIPlugin;
import org.eclipse.team.ui.synchronize.ISynchronizePageConfiguration;
import org.eclipse.team.ui.synchronize.SynchronizeModelOperation;

public abstract class CVSSubscriberOperation extends SynchronizeModelOperation {
	
	protected CVSSubscriberOperation(ISynchronizePageConfiguration configuration, IDiffElement[] elements) {
		super(configuration, elements);
	}

	@Override
	public void run(IProgressMonitor monitor) throws InvocationTargetException, InterruptedException {
		// Divide the sync info by project
		final Map projectSyncInfos = getProjectSyncInfoSetMap();
		monitor.beginTask(null, projectSyncInfos.size() * 100);
		for (Iterator iter = projectSyncInfos.keySet().iterator(); iter.hasNext(); ) {
			final IProject project = (IProject) iter.next();
			run(projectSyncInfos, project, monitor);
		}
		monitor.done();
	}

	/**
	 * Run the operation for the sync infos from the given project. By default, a lock
	 * is acquired on the project.
	 * @param projectSyncInfos the project syncInfos
	 * @param project the project
	 * @param monitor a progress monitor
	 * @throws InvocationTargetException
	 */
	protected void run(final Map projectSyncInfos, final IProject project, IProgressMonitor monitor) throws InvocationTargetException {
		try {
			// Pass the scheduling rule to the synchronizer so that sync change events
			// and cache commits to disk are batched
			EclipseSynchronizer.getInstance().run(
				project,
					monitor1 -> {
						try {
							CVSSubscriberOperation.this.runWithProjectRule(project,
									(SyncInfoSet) projectSyncInfos.get(project), monitor1);
						} catch (TeamException e) {
							throw CVSException.wrapException(e);
					}
				}, Policy.subMonitorFor(monitor, 100));
		} catch (TeamException e) {
			throw new InvocationTargetException(e);
		}
	}

	/**
	 * Run the operation on the sync info in the given set. The sync info will be all
	 * from the same project. Also, a scheduling rule on the project will be
	 * held when this method is invoked.
	 * @param project the project that contaisn the sync info.
	 * @param set the sync info set
	 * @param monitor a progress monitor
	 */
	protected abstract void runWithProjectRule(IProject project, SyncInfoSet set, IProgressMonitor monitor) throws TeamException;

	/*
	 * Indicate that the resource is out of sync if the sync state is not IN_SYNC
	 * or if the local doesn't exist but the remote does.
	 */
	protected boolean isOutOfSync(SyncInfo resource) {
		if (resource == null) return false;
		return (!(resource.getKind() == 0) || 
				(! resource.getLocal().exists() && resource.getRemote() != null));
	}
	
	protected void makeInSync(SyncInfo[] folders, IProgressMonitor monitor) throws TeamException {
		// If a node has a parent that is an incoming folder creation, we have to 
		// create that folder locally and set its sync info before we can get the
		// node itself. We must do this for all incoming folder creations (recursively)
		// in the case where there are multiple levels of incoming folder creations.
		monitor.beginTask(null, folders.length);
		for (int i = 0; i < folders.length; i++) {
			SyncInfo resource = folders[i];
			makeInSync(resource);
			monitor.worked(1);
		}
		monitor.done();
	}
	
	protected boolean makeInSync(SyncInfo info) throws TeamException {
		if (isOutOfSync(info)) {
			SyncInfo parent = getParent(info);
			if (parent == null) {
				if (info.getLocal().getType() == IResource.ROOT) {
					// ROOT should be null
					return true;
				} else {
					// No other ancestors should be null. Log the problem.
					CVSUIPlugin.log(IStatus.WARNING, NLS.bind(CVSUIMessages.CVSSubscriberAction_0, new String[] { info.getLocal().getFullPath().toString() }), null); 
					return false;
				}
			} else {
				if (!makeInSync(parent)) {
					// The failed makeInSync will log any errors
					return false;
				}
			}
			if (info instanceof CVSSyncInfo) {
				CVSSyncInfo cvsInfo= (CVSSyncInfo) info;
				IStatus status = cvsInfo.makeInSync();
				if (status.getSeverity() == IStatus.ERROR) {
					logError(status);
					return false;
				}
				return true;
			}
			return false;
		} else {
			return true;
		}
	}
	
	protected void makeOutgoing(SyncInfo[] folders, IProgressMonitor monitor) throws TeamException {
		// If a node has a parent that is an incoming folder creation, we have to 
		// create that folder locally and set its sync info before we can get the
		// node itself. We must do this for all incoming folder creations (recursively)
		// in the case where there are multiple levels of incoming folder creations.
		monitor.beginTask(null, 100 * folders.length);
		for (int i = 0; i < folders.length; i++) {
			SyncInfo info = folders[i];
			makeOutgoing(info, Policy.subMonitorFor(monitor, 100));
		}
		monitor.done();
	}
	
	private void makeOutgoing(SyncInfo info, IProgressMonitor monitor) throws TeamException {
		if (info == null) return;
		if (info instanceof CVSSyncInfo) {
			CVSSyncInfo cvsInfo= (CVSSyncInfo) info;
			IStatus status = cvsInfo.makeOutgoing(monitor);
			if (status.getSeverity() == IStatus.ERROR) {
				logError(status);
			}
		}
	}

	/**
	 * Log an error associated with an operation.
	 * @param status
	 */
	protected void logError(IStatus status) {
		CVSUIPlugin.log(status);
	}

	/**
	 * Handle the exception by showing an error dialog to the user.
	 * Sync actions seem to need to be sync-execed to work
	 * @param t
	 */
	protected void handle(Exception t) {
		CVSUIPlugin.openError(getShell(), getErrorTitle(), null, t, CVSUIPlugin.PERFORM_SYNC_EXEC | CVSUIPlugin.LOG_NONTEAM_EXCEPTIONS);
	}

	/**
	 * Return the error title that will appear in any error dialogs shown to the user
	 * @return
	 */
	protected String getErrorTitle() {
		return null;
	}

	@Override
	protected boolean canRunAsJob() {
		return true;
	}
	
	protected void pruneEmptyParents(SyncInfo[] nodes) throws CVSException {
		// TODO: A more explicit tie in to the pruning mechanism would be prefereable.
		// i.e. I don't like referencing the option and visitor directly
		if (!CVSProviderPlugin.getPlugin().getPruneEmptyDirectories()) return;
		ICVSResource[] cvsResources = new ICVSResource[nodes.length];
		for (int i = 0; i < cvsResources.length; i++) {
			cvsResources[i] = CVSWorkspaceRoot.getCVSResourceFor(nodes[i].getLocal());
		}
		new PruneFolderVisitor().visit(
			CVSWorkspaceRoot.getCVSFolderFor(ResourcesPlugin.getWorkspace().getRoot()),
			cvsResources);
	}
	
	public CVSSyncInfo getCVSSyncInfo(SyncInfo info) {
		if (info instanceof CVSSyncInfo) {
			return (CVSSyncInfo)info;
		}
		return null;
	}
	
	protected SyncInfo getParent(SyncInfo info) throws TeamException {
		return ((CVSSyncInfo)info).getSubscriber().getSyncInfo(info.getLocal().getParent());
	}

	protected IResource[] getIResourcesFrom(SyncInfo[] nodes) {
		List resources = new ArrayList(nodes.length);
		for (int i = 0; i < nodes.length; i++) {
			resources.add(nodes[i].getLocal());
		}
		return (IResource[]) resources.toArray(new IResource[resources.size()]);
	}

	/**
	 * Prompt to overwrite those resources that could not be safely updated
	 * Note: This method is designed to be overridden by test cases.
	 * 
	 * @return whether to perform the overwrite
	 */
	protected boolean promptForOverwrite(final SyncInfoSet syncSet) {
		final int[] result = new int[] {Window.CANCEL};
		TeamUIPlugin.getStandardDisplay().syncExec(() -> {
			UpdateDialog dialog = new UpdateDialog(getShell(), syncSet);
			result[0] = dialog.open();
		});
		return (result[0] == UpdateDialog.YES);
	}
	
	/**
	 * Make the contents of the local resource match that of the remote
	 * without modifying the sync info of the local resource.
	 * If called on a new folder, the sync info will be copied.
	 */
	protected void makeRemoteLocal(SyncInfo info, IProgressMonitor monitor) throws TeamException {
		IResourceVariant remote = info.getRemote();
		IResource local = info.getLocal();
		try {
			if(remote==null) {
				if (local.exists()) {
					local.delete(IResource.KEEP_HISTORY, monitor);
				}
			} else {
				if(remote.isContainer()) {
					ensureContainerExists(info);
				} else {
					monitor.beginTask(null, 200);
					try {
						IFile localFile = (IFile)local;
						if(local.exists()) {
							localFile.setContents(remote.getStorage(Policy.subMonitorFor(monitor, 100)).getContents(), false /*don't force*/, true /*keep history*/, Policy.subMonitorFor(monitor, 100));
						} else {
							ensureContainerExists(getParent(info));
							localFile.create(remote.getStorage(Policy.subMonitorFor(monitor, 100)).getContents(), false /*don't force*/, Policy.subMonitorFor(monitor, 100));
						}
					} finally {
						monitor.done();
					}
				}
			}
		} catch(CoreException e) {
			IStatus status = new CVSStatus(IStatus.ERROR, CVSStatus.ERROR, CVSUIMessages.UpdateMergeActionProblems_merging_remote_resources_into_workspace_1,e, local);
			throw new CVSException(status);
		}
	}
	
	private boolean ensureContainerExists(SyncInfo info) throws TeamException {
		IResource local = info.getLocal();
		// make sure that the parent exists
		if (!local.exists()) {
			if (!ensureContainerExists(getParent(info))) {
				return false;
			}
		}
		// make sure that the folder sync info is set;
		if (isOutOfSync(info)) {
			if (info instanceof CVSSyncInfo) {
				CVSSyncInfo cvsInfo = (CVSSyncInfo)info;
				IStatus status = cvsInfo.makeInSync();
				if (status.getSeverity() == IStatus.ERROR) {
					logError(status);
					return false;
				}
			}
		}
		// create the folder if it doesn't exist
		ICVSFolder cvsFolder = CVSWorkspaceRoot.getCVSFolderFor((IContainer)local);
		if (!cvsFolder.exists()) {
			cvsFolder.mkdir();
		}
		return true;
	}
	
	/*
	 * Divide the sync info for the operation by project
	 */
	private Map getProjectSyncInfoSetMap() {
		Map map = new HashMap();
		SyncInfoSet all = getSyncInfoSet();
		SyncInfo[] infos = all.getSyncInfos();
		for (int i = 0; i < infos.length; i++) {
			SyncInfo info = infos[i];
			IProject project = info.getLocal().getProject();
			SyncInfoSet set = (SyncInfoSet)map.get(project);
			if (set == null) {
				set = new SyncInfoSet();
				map.put(project, set);
			}
			set.add(info);
		}
		return map;
	}
}

Back to the top