blob: 0b534f48b3e5a1a7fbddd3c56faeb66ad9688dce [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2004, 2015 IBM Corporation and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* IBM - Initial API and implementation
* James Blackburn (Broadcom Corp.) - ongoing development
* Lars Vogel <Lars.Vogel@vogella.com> - Bug 473427, 483862
*******************************************************************************/
package org.eclipse.core.internal.refresh;
import java.util.*;
import org.eclipse.core.internal.localstore.PrefixPool;
import org.eclipse.core.internal.utils.Messages;
import org.eclipse.core.internal.utils.Policy;
import org.eclipse.core.resources.*;
import org.eclipse.core.runtime.*;
/**
* The <code>RefreshJob</code> class maintains a list of resources that
* need to be refreshed, and periodically schedules itself to perform the
* refreshes in the background.
*
* @since 3.0
*/
public class RefreshJob extends WorkspaceJob {
private static final long UPDATE_DELAY = 200;
/**
* List of refresh requests. Requests are processed in order from
* the end of the list. Requests can be added to either the beginning
* or the end of the list depending on whether they are explicit user
* requests or background refresh requests.
*/
private final List<IResource> fRequests;
/**
* The history of path prefixes visited during this refresh job invocation.
* This is used to prevent infinite refresh loops caused by symbolic links in the file system.
*/
private PrefixPool pathPrefixHistory, rootPathHistory;
public RefreshJob() {
super(Messages.refresh_jobName);
fRequests = new ArrayList<>(1);
}
/**
* Adds the given resource to the set of resources that need refreshing.
* Synchronized in order to protect the collection during add.
* @param resource
*/
private synchronized void addRequest(IResource resource) {
IPath toAdd = resource.getFullPath();
for (Iterator<IResource> it = fRequests.iterator(); it.hasNext();) {
IPath request = it.next().getFullPath();
//discard any existing requests the same or below the resource to be added
if (toAdd.isPrefixOf(request))
it.remove();
//nothing to do if the resource to be added is a child of an existing request
else if (request.isPrefixOf(toAdd))
return;
}
//finally add the new request to the front of the queue
fRequests.add(resource);
}
private synchronized void addRequests(List<IResource> list) {
//add requests to the end of the queue
fRequests.addAll(0, list);
}
@Override
public boolean belongsTo(Object family) {
return family == ResourcesPlugin.FAMILY_AUTO_REFRESH;
}
/**
* This method adds all members at the specified depth from the resource
* to the provided list.
*/
private List<IResource> collectChildrenToDepth(IResource resource, ArrayList<IResource> children, int depth) {
if (resource.getType() == IResource.FILE)
return children;
IResource[] members;
try {
members = ((IContainer) resource).members();
} catch (CoreException e) {
//resource is not accessible - just return what we have
return children;
}
for (int i = 0; i < members.length; i++) {
if (members[i].getType() == IResource.FILE)
continue;
if (depth <= 1)
children.add(members[i]);
else
collectChildrenToDepth(members[i], children, depth - 1);
}
return children;
}
/**
* Returns the path prefixes visited by this job so far.
*/
public PrefixPool getPathPrefixHistory() {
if (pathPrefixHistory == null)
pathPrefixHistory = new PrefixPool(20);
return pathPrefixHistory;
}
/**
* Returns the root paths visited by this job so far.
*/
public PrefixPool getRootPathHistory() {
if (rootPathHistory == null)
rootPathHistory = new PrefixPool(20);
return rootPathHistory;
}
/**
* Returns the next item to refresh, or <code>null</code> if there are no requests
*/
private synchronized IResource nextRequest() {
// synchronized: in order to atomically obtain and clear requests
int len = fRequests.size();
if (len == 0)
return null;
return fRequests.remove(len - 1);
}
/**
* @see org.eclipse.core.resources.refresh.IRefreshResult#refresh
*/
public void refresh(IResource resource) {
if (resource == null)
return;
addRequest(resource);
schedule(UPDATE_DELAY);
}
@Override
public IStatus runInWorkspace(IProgressMonitor monitor) {
long start = System.currentTimeMillis();
String msg = Messages.refresh_refreshErr;
MultiStatus errors = new MultiStatus(ResourcesPlugin.PI_RESOURCES, 1, msg, null);
long longestRefresh = 0;
SubMonitor subMonitor = SubMonitor.convert(monitor);
try {
if (Policy.DEBUG_AUTO_REFRESH)
Policy.debug(RefreshManager.DEBUG_PREFIX + " starting refresh job"); //$NON-NLS-1$
int refreshCount = 0;
int depth = 2;
IResource toRefresh;
while ((toRefresh = nextRequest()) != null) {
try {
subMonitor.setWorkRemaining(Math.max(fRequests.size(), 100));
refreshCount++;
long refreshTime = -System.currentTimeMillis();
toRefresh.refreshLocal(1000 + depth, subMonitor.split(1));
refreshTime += System.currentTimeMillis();
if (refreshTime > longestRefresh)
longestRefresh = refreshTime;
//show occasional progress
if (refreshCount % 1000 == 0) {
//be polite to other threads (no effect on some platforms)
Thread.yield();
//throttle depth if it takes too long
if (longestRefresh > 2000 && depth > 1) {
depth = 1;
}
if (longestRefresh < 1000) {
depth *= 2;
}
longestRefresh = 0;
}
addRequests(collectChildrenToDepth(toRefresh, new ArrayList<IResource>(), depth));
} catch (CoreException e) {
errors.merge(new Status(IStatus.ERROR, ResourcesPlugin.PI_RESOURCES, 1, errors.getMessage(), e));
}
}
} finally {
pathPrefixHistory = null;
rootPathHistory = null;
if (Policy.DEBUG_AUTO_REFRESH)
Policy.debug(RefreshManager.DEBUG_PREFIX + " finished refresh job in: " + (System.currentTimeMillis() - start) + "ms"); //$NON-NLS-1$ //$NON-NLS-2$
}
if (!errors.isOK())
return errors;
return Status.OK_STATUS;
}
@Override
public synchronized boolean shouldRun() {
return !fRequests.isEmpty();
}
/**
* Starts the refresh job
*/
public void start() {
if (Policy.DEBUG_AUTO_REFRESH)
Policy.debug(RefreshManager.DEBUG_PREFIX + " enabling auto-refresh"); //$NON-NLS-1$
}
/**
* Stops the refresh job
*/
public void stop() {
if (Policy.DEBUG_AUTO_REFRESH)
Policy.debug(RefreshManager.DEBUG_PREFIX + " disabling auto-refresh"); //$NON-NLS-1$
cancel();
}
}