/*
* 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);
}
}
}
}