/******************************************************************************* * Copyright (c) 2004, 2010 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 *******************************************************************************/ 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.*; import org.eclipse.osgi.util.NLS; /** * 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 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 it= fRequests.iterator(); it.hasNext();) { IPath request= ((IResource)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 list) { //add requests to the end of the queue fRequests.addAll(0, list); } /* (non-Javadoc) * @see org.eclipse.core.runtime.jobs.Job#belongsTo(Object) */ 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 collectChildrenToDepth(IResource resource, ArrayList 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 (IResource)fRequests.remove(len - 1); } /* (non-Javadoc) * @see org.eclipse.core.resources.refresh.IRefreshResult#refresh */ public void refresh(IResource resource) { if (resource == null) return; addRequest(resource); schedule(UPDATE_DELAY); } /* (non-Javadoc) * @see WorkspaceJob#runInWorkspace */ 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; try { if (RefreshManager.DEBUG) Policy.debug(RefreshManager.DEBUG_PREFIX + " starting refresh job"); //$NON-NLS-1$ int refreshCount= 0; int depth= 2; monitor.beginTask("", IProgressMonitor.UNKNOWN); //$NON-NLS-1$ IResource toRefresh; while ((toRefresh= nextRequest()) != null) { if (monitor.isCanceled()) throw new OperationCanceledException(); try { refreshCount++; long refreshTime= -System.currentTimeMillis(); toRefresh.refreshLocal(1000 + depth, Policy.subMonitorFor(monitor, 0)); refreshTime+= System.currentTimeMillis(); if (refreshTime > longestRefresh) longestRefresh= refreshTime; //show occasional progress if (refreshCount % 100 == 0) monitor.subTask(NLS.bind(Messages.refresh_task, Integer.toString(fRequests.size()))); 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(), depth)); } catch (CoreException e) { errors.merge(new Status(IStatus.ERROR, ResourcesPlugin.PI_RESOURCES, 1, errors.getMessage(), e)); } } } finally { pathPrefixHistory= null; rootPathHistory= null; monitor.done(); if (RefreshManager.DEBUG) System.out.println(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; } /* (non-Javadoc) * @see org.eclipse.core.runtime.jobs.Job#shouldRun() */ public synchronized boolean shouldRun() { return !fRequests.isEmpty(); } /** * Starts the refresh job */ public void start() { if (RefreshManager.DEBUG) System.out.println(RefreshManager.DEBUG_PREFIX + " enabling auto-refresh"); //$NON-NLS-1$ } /** * Stops the refresh job */ public void stop() { if (RefreshManager.DEBUG) System.out.println(RefreshManager.DEBUG_PREFIX + " disabling auto-refresh"); //$NON-NLS-1$ cancel(); } }