/******************************************************************************* * 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.ui.synchronize; import java.util.Arrays; import org.eclipse.core.resources.IResource; import org.eclipse.core.runtime.IProgressMonitor; import org.eclipse.core.runtime.IStatus; import org.eclipse.core.runtime.jobs.Job; import org.eclipse.jface.util.IPropertyChangeListener; import org.eclipse.jface.util.PropertyChangeEvent; import org.eclipse.jface.viewers.IBasicPropertyConstants; import org.eclipse.osgi.util.NLS; import org.eclipse.swt.widgets.Shell; import org.eclipse.team.core.TeamException; import org.eclipse.team.core.subscribers.Subscriber; import org.eclipse.team.core.synchronize.SyncInfoFilter; import org.eclipse.team.core.synchronize.SyncInfoTree; import org.eclipse.team.internal.core.subscribers.SubscriberSyncInfoCollector; import org.eclipse.team.internal.ui.IPreferenceIds; import org.eclipse.team.internal.ui.TeamUIMessages; import org.eclipse.team.internal.ui.TeamUIPlugin; import org.eclipse.team.internal.ui.Utils; import org.eclipse.team.internal.ui.synchronize.IRefreshSubscriberListener; import org.eclipse.team.internal.ui.synchronize.IRefreshable; import org.eclipse.team.internal.ui.synchronize.RefreshParticipantJob; import org.eclipse.team.internal.ui.synchronize.RefreshSubscriberParticipantJob; import org.eclipse.team.internal.ui.synchronize.RefreshUserNotificationPolicy; import org.eclipse.team.internal.ui.synchronize.RefreshUserNotificationPolicyInModalDialog; import org.eclipse.team.internal.ui.synchronize.SubscriberParticipantPage; import org.eclipse.team.internal.ui.synchronize.SubscriberRefreshSchedule; import org.eclipse.team.internal.ui.synchronize.SynchronizePageConfiguration; import org.eclipse.team.ui.TeamUI; import org.eclipse.ui.IMemento; import org.eclipse.ui.IWorkbenchPart; import org.eclipse.ui.IWorkbenchSite; import org.eclipse.ui.PartInitException; import org.eclipse.ui.part.IPageBookViewPage; import org.eclipse.ui.progress.IProgressConstants2; /** * A synchronize participant that displays synchronization information for local resources that are * managed via a {@link Subscriber}. It maintains a dynamic collection of all out-of-sync resources * by listening to workspace resource changes and remote changes thus creating a live view of * changes in the workspace. *

* The subscriber can be configured to be synchronized in the background based on a schedule. This * effectively refreshes the subscriber and updates the dynamic sync set. *

* Subclasses will typically want to override the following methods:

* * This class is intended to be subclassed. *

* @since 3.0 */ public abstract class SubscriberParticipant extends AbstractSynchronizeParticipant implements IPropertyChangeListener { /* * Collects and maintains set of all out-of-sync resources of the subscriber */ private SubscriberSyncInfoCollector collector; /* * Controls the automatic synchronization of this participant */ private SubscriberRefreshSchedule refreshSchedule; /* * Provides the resource scope for this participant */ private ISynchronizeScope scope; /* * Key for settings in memento */ private static final String CTX_SUBSCRIBER_PARTICIPANT_SETTINGS = TeamUIPlugin.ID + ".TEAMSUBSRCIBERSETTINGS"; //$NON-NLS-1$ /* * Key for schedule in memento */ private static final String CTX_SUBSCRIBER_SCHEDULE_SETTINGS = TeamUIPlugin.ID + ".TEAMSUBSRCIBER_REFRESHSCHEDULE"; //$NON-NLS-1$ /** * Constructor initializes the schedule. Subclasses must call this method. */ public SubscriberParticipant() { refreshSchedule = new SubscriberRefreshSchedule(createRefreshable()); } private IRefreshable createRefreshable() { return new IRefreshable() { @Override public RefreshParticipantJob createJob(String interval) { return new RefreshSubscriberParticipantJob(SubscriberParticipant.this, TeamUIMessages.RefreshSchedule_14, NLS.bind(TeamUIMessages.RefreshSchedule_15, new String[] { SubscriberParticipant.this.getName(), interval }), getResources(), new RefreshUserNotificationPolicy(SubscriberParticipant.this)); } @Override public ISynchronizeParticipant getParticipant() { return SubscriberParticipant.this; } @Override public void setRefreshSchedule(SubscriberRefreshSchedule schedule) { SubscriberParticipant.this.setRefreshSchedule(schedule); } @Override public SubscriberRefreshSchedule getRefreshSchedule() { return SubscriberParticipant.this.getRefreshSchedule(); } }; } /** * Constructor which should be called when creating a participant whose resources * are to be scoped. * * @param scope a synchronize scope */ public SubscriberParticipant(ISynchronizeScope scope) { this(); this.scope = scope; scope.addPropertyChangeListener(this); } @Override public final IPageBookViewPage createPage(ISynchronizePageConfiguration configuration) { validateConfiguration(configuration); return new SubscriberParticipantPage(configuration, getSubscriberSyncInfoCollector()); } /** * Returns the resources supervised by this participant. It will * either be the roots of the subscriber or the resource scope * provided when the subscriber was set. * * @return the resources supervised by this participant. */ public IResource[] getResources() { return collector.getRoots(); } /* * Set the resources supervised by this participant. If null, * the participant will include all roots of its subscriber * * @param roots the root resources to consider or null * to consider all roots of the subscriber */ private void setResources(IResource[] roots) { collector.setRoots(roots); } /** * Refresh this participants synchronization state and displays the result in a model dialog. * @param shell * * @param resources the resources to be refreshed. * @param jobName * @param taskName the task name to be shown to the user * @param configuration * @param site the site in which to run the refresh */ public final void refreshInDialog(Shell shell, IResource[] resources, String jobName, String taskName, ISynchronizePageConfiguration configuration, IWorkbenchSite site) { IRefreshSubscriberListener listener = new RefreshUserNotificationPolicyInModalDialog(shell, taskName, configuration, this); internalRefresh(resources, jobName, taskName, site, listener); } /** * Refresh a participant in the background the result of the refresh are shown in the progress view. Refreshing * can also be considered synchronizing, or refreshing the synchronization state. Basically this is a long * running operation that will update the participants sync info sets with new changes detected on the * server. Either or both of the shortTaskName and longTaskName can be null * in which case, the default values for these are returned by the methods getShortTaskName() and * getLongTaskName(IResource[]) will be used. * * @param resources the resources to be refreshed. * @param shortTaskName the taskName of the background job that will run the synchronize or null * if the default job name is desired. * @param longTaskName the taskName of the progress monitor running the synchronize or null * if the default job name is desired. * @param site the workbench site the synchronize is running from. This can be used to notify the site * that a job is running. */ public final void refresh(IResource[] resources, String shortTaskName, String longTaskName, IWorkbenchSite site) { IRefreshSubscriberListener listener = new RefreshUserNotificationPolicy(this); internalRefresh(resources, shortTaskName, longTaskName, site, listener); } /** * Refresh a participant. The returned status describes the result of the refresh. * @param resources * @param taskName * @param monitor * @return a status */ public final IStatus refreshNow(IResource[] resources, String taskName, IProgressMonitor monitor) { Job.getJobManager().cancel(this); RefreshParticipantJob job = new RefreshSubscriberParticipantJob(this, taskName, taskName, resources, null); return job.run(monitor); } @Override public void dispose() { Job.getJobManager().cancel(this); refreshSchedule.dispose(); TeamUI.removePropertyChangeListener(this); collector.dispose(); scope.dispose(); } @Override public String getName() { String name = super.getName(); return NLS.bind(TeamUIMessages.SubscriberParticipant_namePattern, new String[] { name, scope.getName() }); } /** * Return the name of the participant as specified in the plugin manifest file. * This method is provided to give access to this name since it is masked by * the getName() method defined in this class. * @return the name of the participant as specified in the plugin manifest file * @since 3.1 */ protected final String getShortName() { return super.getName(); } /** * Returns the SyncInfoTree for this participant. This set * contains the out-of-sync resources supervised by this participant. * * @return the sync info set that contains the out-of-sync resources * for this participant. */ public SyncInfoTree getSyncInfoSet() { return getSubscriberSyncInfoCollector().getSyncInfoSet(); } /** * Return the Subscriber associated with this this participant. This * method will only return null if the participant has not been initialized * yet. * * @return the Subscriber associated with this this participant. */ public Subscriber getSubscriber() { if (collector == null) return null; return collector.getSubscriber(); } /** * Returns a participant that matches the given resource scoping * * @param ID the type id of participants to match * @param resources the resources to match in the scope * @return a participant that matches the given resource scoping */ public static SubscriberParticipant getMatchingParticipant(String ID, IResource[] resources) { ISynchronizeParticipantReference[] refs = TeamUI.getSynchronizeManager().getSynchronizeParticipants(); for (int i = 0; i < refs.length; i++) { ISynchronizeParticipantReference reference = refs[i]; if(reference.getId().equals(ID)) { SubscriberParticipant p; try { p = (SubscriberParticipant)reference.getParticipant(); } catch (TeamException e) { continue; } IResource[] roots = p.getResources(); Arrays.sort(resources, Utils.resourceComparator); Arrays.sort(roots, Utils.resourceComparator); if (Arrays.equals(resources, roots)) { return p; } } } return null; } @Override public void propertyChange(PropertyChangeEvent event) { if (event.getProperty().equals(TeamUI.GLOBAL_IGNORES_CHANGED)) { collector.reset(); } if (event.getProperty().equals(ISynchronizeScope.ROOTS)) { setResources(scope.getRoots()); } if (event.getProperty().equals(ISynchronizeScope.NAME)) { // Force a name change event, which will cause this classes getName to be called // and updated with the correct working set name. firePropertyChange(this, IBasicPropertyConstants.P_TEXT, null, getName()); } } @Override public void init(String secondaryId, IMemento memento) throws PartInitException { super.init(secondaryId, memento); if(memento != null) { IMemento settings = memento.getChild(CTX_SUBSCRIBER_PARTICIPANT_SETTINGS); if(settings != null) { SubscriberRefreshSchedule schedule = SubscriberRefreshSchedule.init(settings.getChild(CTX_SUBSCRIBER_SCHEDULE_SETTINGS), createRefreshable()); setRefreshSchedule(schedule); this.scope = AbstractSynchronizeScope.createScope(settings); scope.addPropertyChangeListener(this); } } } @Override public void saveState(IMemento memento) { super.saveState(memento); IMemento settings = memento.createChild(CTX_SUBSCRIBER_PARTICIPANT_SETTINGS); refreshSchedule.saveState(settings.createChild(CTX_SUBSCRIBER_SCHEDULE_SETTINGS)); AbstractSynchronizeScope.saveScope(scope, settings); } /** * Reset the sync set of the participant by repopulating it from scratch. */ public void reset() { getSubscriberSyncInfoCollector().reset(); } /** * Return the SubscriberSyncInfoCollector for the participant. * This collector maintains the set of all out-of-sync resources for the * subscriber. * * @return the SubscriberSyncInfoCollector for this participant * @noreference This method is not intended to be referenced by clients. * @nooverride This method is not intended to be re-implemented or extended * by clients. */ public SubscriberSyncInfoCollector getSubscriberSyncInfoCollector() { return collector; } /** * @noreference This method is not intended to be referenced by clients. * @nooverride This method is not intended to be re-implemented or extended by clients. */ public void setRefreshSchedule(SubscriberRefreshSchedule schedule) { if (refreshSchedule != schedule) { if (refreshSchedule != null) { refreshSchedule.dispose(); } this.refreshSchedule = schedule; } // Always fir the event since the schedule may have been changed firePropertyChange(this, AbstractSynchronizeParticipant.P_SCHEDULED, schedule, schedule); } /** * @noreference This method is not intended to be referenced by clients. * @nooverride This method is not intended to be re-implemented or extended by clients. */ public SubscriberRefreshSchedule getRefreshSchedule() { return refreshSchedule; } @Override protected void initializeConfiguration(ISynchronizePageConfiguration configuration) { configuration.setProperty(SynchronizePageConfiguration.P_PARTICIPANT_SYNC_INFO_SET, collector.getSyncInfoSet()); } @Override public void run(IWorkbenchPart part) { refresh(getResources(), null, null, part != null ? part.getSite() : null); } /** * Returns the short task name (e.g. no more than 25 characters) to describe the behavior of the * refresh operation to the user. This is typically shown in the status line when this subscriber is refreshed * in the background. When refreshed in the foreground, only the long task name is shown. * * @return the short task name to show in the status line. */ protected String getShortTaskName() { return TeamUIMessages.Participant_synchronizing; } /** * Returns the long task name to describe the behavior of the * refresh operation to the user. This is typically shown in the status line when this subscriber is refreshed * in the background. * * @return the long task name * @deprecated use getLongTaskName(IResource[]) instead */ @Deprecated protected String getLongTaskName() { return TeamUIMessages.Participant_synchronizing; } /** * Returns the long task name to describe the behavior of the * refresh operation to the user. This is typically shown in the status line when this subscriber is refreshed * in the background. * @param resources * @return the long task name * @since 3.1 */ protected String getLongTaskName(IResource[] resources) { int resourceCount = 0; if (getResources().length == resources.length) { // Assume that the resources are the same as the roots. // If we are wrong, the message may no mention the specific resources which is OK ISynchronizeScope scope = getScope(); if (scope instanceof ResourceScope) { resourceCount = scope.getRoots().length; } } else { resourceCount = resources.length; } if (resourceCount == 1) { return NLS.bind(TeamUIMessages.Participant_synchronizingMoreDetails, new String[] { getShortName(), resources[0].getFullPath().toString() }); } else if (resourceCount > 1) { return NLS.bind(TeamUIMessages.Participant_synchronizingResources, new String[] { getShortName(), Integer.toString(resourceCount) }); } // A resource count of zero means that it is a non-resource scope so we can print the scope name return NLS.bind(TeamUIMessages.Participant_synchronizingDetails, new String[] { getName() }); } /** * This method is invoked before the given configuration is used to * create the page (see createPage(ISynchronizePageConfiguration)). * The configuration would have been initialized by * initializeConfiguration(ISynchronizePageConfiguration) * but may have also been tailored further. This method gives the participant * a chance to validate those changes before the page is created. * * @param configuration the page configuration that is about to be used to create a page. */ protected void validateConfiguration(ISynchronizePageConfiguration configuration) { // Do nothing by default } /** * Subclasses must call this method to initialize the participant. Typically this * method is called in {@link #init(String, IMemento)}. This method will initialize * the sync info collector. * * @param subscriber the subscriber to associate with this participant. */ protected void setSubscriber(Subscriber subscriber) { if (scope == null) { scope = new WorkspaceScope(); } collector = new SubscriberSyncInfoCollector(subscriber, scope.getRoots()); // listen for global ignore changes TeamUI.addPropertyChangeListener(this); // Start collecting changes collector.start(); // Start the refresh now that a subscriber has been added SubscriberRefreshSchedule schedule = getRefreshSchedule(); if(schedule.isEnabled()) { getRefreshSchedule().startJob(); } } /** * Provide a filter that is used to filter the contents of the sync info set for the participant. Normally, all out-of-sync * resources from the subscriber will be included in the participant's set. However, a filter can be used to exclude * some of these out-of-sync resources, if desired. *

* Subclasses can invoke this method any time after setSubscriber has been invoked. *

* @param filter a sync info filter */ protected void setSyncInfoFilter(SyncInfoFilter filter) { collector.setFilter(filter); } /* * Create and schedule a subscriber refresh job. * * @param resources resources to be synchronized * @param taskName the task name to be shown to the user * @param site the site in which to run the refresh * @param listener the listener to handle the refresh workflow */ private void internalRefresh(IResource[] resources, String jobName, String taskName, IWorkbenchSite site, IRefreshSubscriberListener listener) { if (jobName == null) jobName = getShortTaskName(); if (taskName == null) taskName = getLongTaskName(resources); Job.getJobManager().cancel(this); RefreshParticipantJob job = new RefreshSubscriberParticipantJob(this, jobName, taskName, resources, listener); job.setUser(true); job.setProperty(IProgressConstants2.SHOW_IN_TASKBAR_ICON_PROPERTY, Boolean.TRUE); Utils.schedule(job, site); // Remember the last participant synchronized TeamUIPlugin.getPlugin().getPreferenceStore().setValue(IPreferenceIds.SYNCHRONIZING_DEFAULT_PARTICIPANT, getId()); TeamUIPlugin.getPlugin().getPreferenceStore().setValue(IPreferenceIds.SYNCHRONIZING_DEFAULT_PARTICIPANT_SEC_ID, getSecondaryId()); } /** * Return the scope that defines the resources displayed by this participant. * * @return Returns the scope. */ public ISynchronizeScope getScope() { return scope; } @SuppressWarnings("unchecked") @Override public T getAdapter(Class adapter) { if (adapter == IRefreshable.class && refreshSchedule != null) { return (T) refreshSchedule.getRefreshable(); } return super.getAdapter(adapter); } }