content: /*
   * Copyright (c) 2009 Andrejs Jermakovics.
   * 
   * 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:
   *     Andrejs Jermakovics - initial implementation
   */
  package it.unibz.instasearch.jobs;
  
  import it.unibz.instasearch.InstaSearchPlugin;
  import it.unibz.instasearch.actions.ShowExceptionAction;
  import it.unibz.instasearch.indexing.WorkspaceIndexer;
  import it.unibz.instasearch.indexing.StorageIndexer.IndexChangeListener;
  import it.unibz.instasearch.prefs.PreferenceConstants;
  import it.unibz.instasearch.ui.InstaSearchUI;
  
  import java.io.File;
  import java.io.IOException;
  import java.util.Iterator;
  import java.util.Map;
  import java.util.concurrent.ConcurrentHashMap;
  
  import org.eclipse.core.resources.IFile;
  import org.eclipse.core.resources.IFolder;
  import org.eclipse.core.resources.IResource;
  import org.eclipse.core.resources.IResourceChangeEvent;
  import org.eclipse.core.resources.IResourceChangeListener;
  import org.eclipse.core.resources.IResourceDelta;
  import org.eclipse.core.resources.IResourceDeltaVisitor;
  import org.eclipse.core.resources.IWorkspaceRoot;
  import org.eclipse.core.resources.ResourcesPlugin;
  import org.eclipse.core.resources.WorkspaceJob;
  import org.eclipse.core.runtime.CoreException;
  import org.eclipse.core.runtime.IProgressMonitor;
  import org.eclipse.core.runtime.IStatus;
  import org.eclipse.core.runtime.Path;
  import org.eclipse.core.runtime.Status;
  import org.eclipse.jface.util.IPropertyChangeListener;
  import org.eclipse.jface.util.PropertyChangeEvent;
  import org.eclipse.ui.PlatformUI;
  import org.eclipse.ui.progress.IProgressConstants;
  import org.osgi.framework.BundleEvent;
  import org.osgi.framework.SynchronousBundleListener;
  
  /**
   * Workspace job that periodically updates the index
   */
  public class IndexUpdateJob extends WorkspaceJob implements SynchronousBundleListener, IResourceChangeListener, IPropertyChangeListener {
  	
  	private WorkspaceIndexer indexer;
  	private IndexChangeListener indexChangeListener;
  	
  	private Map<IResource, Integer> changedResources = new ConcurrentHashMap<IResource, Integer>(); // changed by several threads
  	private boolean searchViewVisible = false;
  
  	/**
  	 * @param indexer
  	 * @param indexChangeListener
  	 */
  	public IndexUpdateJob(WorkspaceIndexer indexer, IndexChangeListener indexChangeListener) {
  		super("InstaSearch Index Update");
  		this.indexer = indexer;
  		this.indexChangeListener = indexChangeListener;
  		
  		ResourcesPlugin.getWorkspace().addResourceChangeListener(this, IResourceChangeEvent.POST_CHANGE); // listen to file changes
  		
  		if( InstaSearchPlugin.getDefault() != null )
  			InstaSearchPlugin.getDefault().getBundle().getBundleContext().addBundleListener(this); // listen to plugin stopping
  		
  		setRule(indexer);
  		
  		setPriority(DECORATE);
  		
  		setProperty(IProgressConstants.ICON_PROPERTY, InstaSearchPlugin.getImageDescriptor("syncdb")); 
  		setProperty(IProgressConstants.NO_IMMEDIATE_ERROR_PROMPT_PROPERTY, Boolean.TRUE);
  	}
  	
  	public void resourceChanged(IResourceChangeEvent event)
  	{
  		if( event.getType() == IResourceChangeEvent.PRE_BUILD
  				|| event.getType() == IResourceChangeEvent.POST_BUILD )
  			return; // not change events
  		
  		try 
  		{
  			if( indexer.isIndexed() ) 
  			{
  				if( event.getType() == IResourceChangeEvent.POST_CHANGE )
  				{
  					IResourceDelta delta = event.getDelta();
  					
  					delta.accept(createResourceVisitor(), false);
  				}
  				//else if( event.getResource().getType() == IResource.PROJECT ) // CLOSE,DELETE,REFRESH events for project
  				//changedResources.put( event.getResource(), 0 ); 
  			}
  		} catch (Exception e) {
  			InstaSearchPlugin.log(e);
  		}
  	}
  	
  	private IResourceDeltaVisitor createResourceVisitor()
  	{
  		return new IResourceDeltaVisitor() 
  		{
  			public boolean visit(IResourceDelta delta) throws CoreException
  			{
  				IResource resource = delta.getResource();
  
  				if( resource.getType() == IResource.FILE  )
  				{
  					IFile file = (IFile) resource;
  
  					if( indexer.isIndexable(file) ) {
  						changedResources.put( file, file.getType());
  					}
  
  				}
  				else if( resource.getType() == IResource.FOLDER ) {
  					if( delta.getFlags() == IResourceDelta.DERIVED_CHANGED ) { // we must skip derived resources
  						changedResources.put( resource, resource.getType() );
  					}
  				}
  
  				//else if(resource.getType() == IResource.PROJECT )
  				//	System.out.println("Project change " + resource.toString());
  
  				return true;
  			}
  		};
  	}
  	
  	public void bundleChanged(BundleEvent event)
  	{
  		if( event.getType() == BundleEvent.STOPPING || event.getType() == BundleEvent.STOPPED ) {
  			ResourcesPlugin.getWorkspace().removeResourceChangeListener(this);
  			this.cancel();
  			
  			if( event.getBundle() != null && event.getBundle().getBundleContext() != null )
  				event.getBundle().getBundleContext().removeBundleListener(this);
  		}
  	}
  	
  	@Override
  	protected void canceling() {
  		//ResourcesPlugin.getWorkspace().removeResourceChangeListener(this);
  	}
  	
  	@Override
  	public IStatus runInWorkspace(IProgressMonitor monitor)
  			throws CoreException
  	{	
  		if( InstaSearchPlugin.getDefault() == null || monitor.isCanceled() ) // no plugin (stopped)
  			return Status.CANCEL_STATUS;
  		
  		int indexUpdateInterval = InstaSearchPlugin.getIntPref(PreferenceConstants.P_INDEX_UPDATE_INTERVAL);
  		boolean indexUpdateEnabled = InstaSearchPlugin.getBoolPref(PreferenceConstants.P_INDEX_UPDATE_ENABLED);
  		
  		if (!indexUpdateEnabled){
  			return Status.CANCEL_STATUS;
  		}
  		
  		boolean indexed = false;
  		
  		try {
  			indexed = indexer.isIndexed();
  		} catch (IOException e1) {
  			InstaSearchPlugin.log(e1);
  		}
  		
  		if((!indexed || changedResources.isEmpty()) && !monitor.isCanceled()) { // not indexed or still indexing
  			schedule(indexUpdateInterval); // check later
  			return Status.CANCEL_STATUS;
  		}
  		
  		monitor.beginTask("Updating Search Index", changedResources.size());
  		IStatus returnStatus = Status.OK_STATUS;
  		
  		try
  		{
  			for (Iterator<IResource> iterator = changedResources.keySet().iterator(); 
  									iterator.hasNext() && !monitor.isCanceled();)
  			{
  				IResource resource = iterator.next();
  				monitor.subTask(resource.getName());
  				
  				if( resource.getType() == IResource.FILE )
  					indexer.updateFile( (IFile)resource );
  				else if( resource.getType() == IResource.FOLDER ) {
  					indexer.updateFolder( (IFolder)resource, monitor );
  				}
  				//else if( resource.getType() == IResource.PROJECT )
  				//	indexer.updateProject((IProject)resource);
  				
  				iterator.remove();
  				
  				monitor.worked(1);
  			}
  			
  			indexChangeListener.onIndexUpdate();
  			
  			PlatformUI.getWorkbench().getDisplay().syncExec(new Runnable() {
  				public void run() { 
  					searchViewVisible = InstaSearchUI.isSearchViewVisible(); // must run in UI thread, but this job isn't
  				}
  			});
  			
  			if( ! searchViewVisible ) { // don't optimize when might be searching
  				indexer.optimizeIndex(); 
  			}
  			
  			monitor.done();
  			
  		} catch (Exception e) {
  			monitor.beginTask("Exception", 1);
  			setProperty(IProgressConstants.KEEP_PROPERTY, Boolean.TRUE);
  			setProperty(IProgressConstants.ACTION_PROPERTY, new ShowExceptionAction(e, "Error Updating Index"));
  			InstaSearchPlugin.log(e);
  			
  			returnStatus = Status.CANCEL_STATUS;
  		}
  		
  		if((!monitor.isCanceled() ) && (indexUpdateEnabled)){
  			schedule(indexUpdateInterval);
  		}
  		
  		return returnStatus;
  	}
  
  	public void propertyChange(PropertyChangeEvent event) {
  		String prop = event.getProperty();
  		
  		if( PreferenceConstants.P_EXCLUDE_DIRS.equals(prop) )
  		{
  			IWorkspaceRoot root = ResourcesPlugin.getWorkspace().getRoot();
  			String excludedDirStr = "";
  			
  			if( event.getOldValue() != null )
  				excludedDirStr += event.getOldValue().toString() + File.pathSeparator;
  			
  			if( event.getNewValue() != null )
  				excludedDirStr += event.getNewValue().toString();
  			
  			String[] excludedDirArr = excludedDirStr.split(File.pathSeparator);
  			
  			for (String excludedDir : excludedDirArr) 
  			{
  				if( excludedDir.length() == 0 ) continue;
  				Path path = new Path(excludedDir);
  				if( !root.exists(path) ) continue;
  				
  				IResource res = root.findMember(path);
  				if( res != null )
  					changedResources.put(res, res.getType());// mark excluded as changed for re-indexing
  			}
  		}else if (PreferenceConstants.P_INDEX_UPDATE_ENABLED.equals(prop)){
  			boolean enableFlag = (Boolean)event.getNewValue();
  		
  			if (enableFlag) {
  				int indexUpdateInterval = InstaSearchPlugin.getIntPref(PreferenceConstants.P_INDEX_UPDATE_INTERVAL);
  				// cancel and reschedule job 
  				cancel();
  				schedule(indexUpdateInterval);
  			}
  		}
  	}
  		
  }