blob: 72379bb7c560046dbd0f828d067c2a567a81272a [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2000, 2017 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.core.subscribers;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.resources.ResourcesPlugin;
import org.eclipse.core.resources.mapping.ResourceTraversal;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.OperationCanceledException;
import org.eclipse.core.runtime.SubProgressMonitor;
import org.eclipse.team.core.ITeamStatus;
import org.eclipse.team.core.TeamException;
import org.eclipse.team.core.TeamStatus;
import org.eclipse.team.core.mapping.ISynchronizationScope;
import org.eclipse.team.core.subscribers.Subscriber;
import org.eclipse.team.core.synchronize.SyncInfo;
import org.eclipse.team.core.synchronize.SyncInfoSet;
import org.eclipse.team.core.synchronize.SyncInfoTree;
import org.eclipse.team.internal.core.Messages;
import org.eclipse.team.internal.core.TeamPlugin;
/**
* An event handler that collects {@link SyncInfo} in a {@link SyncInfoTree}.
*/
public class SubscriberSyncInfoEventHandler extends SubscriberEventHandler {
// The set that receives notification when the resource synchronization state
// has been calculated by the job.
private final SyncSetInputFromSubscriber syncSetInput;
private class SubscriberSyncInfoEvent extends SubscriberEvent {
private final SyncInfo result;
public SubscriberSyncInfoEvent(IResource resource, int type, int depth, SyncInfo result) {
super(resource, type, depth);
this.result = result;
}
public SyncInfo getResult() {
return result;
}
}
public static ISynchronizationScope createScope(IResource[] roots, Subscriber subscriber) {
if (roots == null)
roots = subscriber.roots();
return new RootResourceSynchronizationScope(roots);
}
/**
* Create the event handler for the given subscriber and roots
* @param subscriber the subscriber
* @param roots the roots or <code>null</code> if the roots from the subscriber
* should be used.
*/
public SubscriberSyncInfoEventHandler(final Subscriber subscriber, IResource[] roots) {
super(subscriber, createScope(roots, subscriber));
this.syncSetInput = new SyncSetInputFromSubscriber(subscriber, this);
}
@Override
protected void handleException(CoreException e, IResource resource, int code, String message) {
super.handleException(e, resource, code, message);
syncSetInput.handleError(new TeamStatus(IStatus.ERROR, TeamPlugin.ID, code, message, e, resource));
}
@Override
protected void handleCancel(OperationCanceledException e) {
super.handleCancel(e);
syncSetInput.handleError(new TeamStatus(IStatus.ERROR, TeamPlugin.ID, ITeamStatus.SYNC_INFO_SET_CANCELLATION, Messages.SubscriberEventHandler_12, e, ResourcesPlugin.getWorkspace().getRoot()));
}
/**
* Return the sync set input that was created by this event handler
* @return the sync set input
*/
public SyncSetInputFromSubscriber getSyncSetInput() {
return syncSetInput;
}
@Override
protected void handleChange(IResource resource) throws TeamException {
SyncInfo info = syncSetInput.getSubscriber().getSyncInfo(resource);
// resource is no longer under the subscriber control
if (info == null) {
queueDispatchEvent(
new SubscriberEvent(resource, SubscriberEvent.REMOVAL, IResource.DEPTH_ZERO));
} else {
queueDispatchEvent(
new SubscriberSyncInfoEvent(resource, SubscriberEvent.CHANGE, IResource.DEPTH_ZERO, info));
}
}
@Override
protected void collectAll(
IResource resource,
int depth,
IProgressMonitor monitor) {
monitor.beginTask(null, IProgressMonitor.UNKNOWN);
try {
// Create a monitor that will handle preemption and dispatch if required
IProgressMonitor collectionMonitor = new SubProgressMonitor(monitor, IProgressMonitor.UNKNOWN) {
boolean dispatching = false;
@Override
public void subTask(String name) {
dispatch();
super.subTask(name);
}
private void dispatch() {
if (dispatching) return;
try {
dispatching = true;
handlePreemptiveEvents(this);
handlePendingDispatch(this);
} finally {
dispatching = false;
}
}
@Override
public void worked(int work) {
dispatch();
super.worked(work);
}
};
// Create a sync set that queues up resources and errors for dispatch
SyncInfoSet collectionSet = new SyncInfoSet() {
@Override
public void add(SyncInfo info) {
super.add(info);
queueDispatchEvent(
new SubscriberSyncInfoEvent(info.getLocal(), SubscriberEvent.CHANGE, IResource.DEPTH_ZERO, info));
}
@Override
public void addError(ITeamStatus status) {
if (status instanceof TeamStatus) {
TeamStatus ts = (TeamStatus) status;
IResource resource = ts.getResource();
if (resource != null && !resource.getProject().isAccessible()) {
// The project was closed while we were collecting sync info.
// The close delta will cause us to clean up properly
return;
}
}
super.addError(status);
TeamPlugin.getPlugin().getLog().log(status);
syncSetInput.handleError(status);
}
@Override
public void remove(IResource resource) {
super.remove(resource);
queueDispatchEvent(
new SubscriberEvent(resource, SubscriberEvent.REMOVAL, IResource.DEPTH_ZERO));
}
};
syncSetInput.getSubscriber().collectOutOfSync(new IResource[] { resource }, depth, collectionSet, collectionMonitor);
} finally {
monitor.done();
}
}
@Override
protected void dispatchEvents(SubscriberEvent[] events, IProgressMonitor monitor) {
// this will batch the following set changes until endInput is called.
SubscriberSyncInfoSet syncSet = syncSetInput.getSyncSet();
try {
syncSet.beginInput();
for (int i = 0; i < events.length; i++) {
SubscriberEvent event = events[i];
switch (event.getType()) {
case SubscriberEvent.CHANGE :
if (event instanceof SubscriberSyncInfoEvent) {
SubscriberSyncInfoEvent se = (SubscriberSyncInfoEvent) event;
syncSetInput.collect(se.getResult(), monitor);
}
break;
case SubscriberEvent.REMOVAL :
syncSet.remove(event.getResource(), event.getDepth());
break;
}
}
} finally {
syncSet.endInput(monitor);
}
}
/**
* Initialize all resources for the subscriber associated with the set. This
* will basically recalculate all synchronization information for the
* subscriber.
* <p>
* This method is synchronized with the queueEvent method to ensure that the
* two events queued by this method are back-to-back.
*
* @param roots
* the new roots or <code>null</code> if the roots from the
* subscriber should be used.
*/
public void reset(IResource[] roots) {
RootResourceSynchronizationScope scope = (RootResourceSynchronizationScope)getScope();
if (roots == null)
roots = getSubscriber().roots();
scope.setRoots(roots);
}
@Override
protected synchronized void reset(ResourceTraversal[] oldTraversals, ResourceTraversal[] newTraversals) {
// First, reset the sync set input to clear the sync set
run(monitor -> syncSetInput.reset(monitor), false /* keep ordering the same */);
// Then, prime the set from the subscriber
super.reset(oldTraversals, newTraversals);
}
}