/*
* To change this template, choose Tools | Templates
* and open the template in the editor.
*/
package pdfdb.indexing.fileservice;
import pdfdb.data.db.*;
import java.io.File;
import java.io.IOException;
import java.sql.SQLException;
import java.util.*;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import pdfdb.indexing.plugins.Indexer;
import pdfdb.indexing.plugins.IndexingProvider;
import pdfdb.settings.IgnoredFileManager;
import pdfdb.structure.*;
/** A concrete implementation of the FileChecker for checking changes to the
* database. The Filechecker is concretely implemented in this class by passing
* itself as an argument to the FileChecker. Additional house keeping
* functionality for the database is also kept in this class.
* @author ug22cmg */
public class DbFileChangeMonitor extends AbstractFileChangeMonitor
{
private boolean inShutdown = false;
private boolean alteringFile = false;
private String directory = null;
private ExecutorService pool = Executors.newCachedThreadPool();
private final Vector<Observer> observers = new Vector<Observer>();
private final static Object LOCK = new Object();
/** Initializes the DatabaseChangeManager with an initial directory.
* @param directory The initial directory.
* @throws java.sql.SQLException If an error occurs reading the
* embedded database. */
public DbFileChangeMonitor(String directory) throws SQLException
{
this.directory = directory;
}
/** Adds an observer to the file change provider.
* @param observer non null observer. */
public void addObserver(Observer observer)
{
if (observer == null || observers.contains(observer))
{
throw new IllegalArgumentException();
}
getObservers().add(observer);
}
/** Removes an observer of the file change provider.
* @param observer A non null observer. */
public void removeObserver(Observer observer)
{
if (observer == null)
{
throw new IllegalArgumentException();
}
getObservers().remove(observer);
}
/** Gets whether the indexing service is still running in an active
* state. Once all execution has completed and the system is
* shutdown, this will return false.
* @return True if running in active state */
@Override
public synchronized boolean isActive()
{
return super.isActive() || alteringFile;
}
/** Updates the database with the specified file change in a multithreaded
* way ensuring the requirement for prompt response is met.
* @param path The updated file path.
* @param type The update type. */
@Override
public void fileChange(String path, ChangeType type)
{
pool.execute(new Updater(path, type));
}
/** Performs an update based on a change type.
* @param type The type of change that has occured.
* @param path The path of the file.
* @throws java.sql.SQLException If a problem with accessing the database
* occurs.
* @throws java.io.IOException If a problem with accessing the file
* occurs. */
private void performUpdate(ChangeType type, String path)
throws SQLException, IOException
{
boolean error = false;
switch (type)
{
case ADDED:
case UPDATED:
IndexableFile file = FileProvider.addFile(path);
if (file == null)
{
IgnoredFileManager manager = IgnoredFileManager.getInstance();
manager.set(path, "Failed to index.");
manager.save();
error = true;
}
break;
case REMOVED:
FileProvider.deleteFile(path);
break;
}
if (!isInShutdown() && !error) // if the indexer errors out
{
for (Observer observer : getObservers())
{
pool.execute(new Notifier(observer, path, type));
}
}
}
/** Gets the list of observers in a thread-safe way.
* @return The list of observers. */
private synchronized Vector<Observer> getObservers()
{
return observers;
}
/** Gets whether the file has changed.
* @param path Path of the file to check.
* @return True if the file is considered changed. */
@Override
public boolean hasChanged(String path)
{
return getChangeType(path) != ChangeType.NO_CHANGE;
}
/** Gets whether the specified file is used by the system, i.e.
* if the system has not ignored it and if there is a valid
* indexer for the system.
* @param path The path to check.
* @return True if the file should be considered. */
@Override
protected boolean isUsed(String path)
{
Indexer i = IndexingProvider.getIndexer(path);
IgnoredFileManager manager = IgnoredFileManager.getInstance();
return i != null && manager.get(path) == null;
}
/** Gets the type of change that has occured since the last database index.
* @param path The path to the file
* @return An instance of ChangeType. */
@Override
protected ChangeType getChangeType(String path)
{
IndexableFile file = null;
try
{
file = FileProvider.getFile(path);
if (file == null)
{
return ChangeType.ADDED;
}
else
{
File actualFile = new File(path);
long lastIndexed = file.getLastIndexed().getTime();
if (!actualFile.exists())
{
return ChangeType.REMOVED;
}
if (actualFile.lastModified() > lastIndexed)
{
return ChangeType.UPDATED;
}
return ChangeType.NO_CHANGE;
}
}
catch(SQLException se)
{
System.err.println(se.getErrorCode());
return ChangeType.NO_CHANGE;
}
finally
{
file = null;
}
}
/** Gets whether the indexing is in shutdown mode.
* @return True if the system is in shutdown. */
private synchronized boolean isInShutdown()
{
return inShutdown;
}
/** Blocks until the active thread that is under the control of the
* FileChangeManager has completed. If the change manager is part of
* a thread pool, the pool must be in shutdown state before executing
* this method. */
@Override
public synchronized void shutdown()
{
inShutdown = true;
super.shutdown();
}
/** Thread for notifying the observers. Used to ensure the notification
* thread is not hung up. */
private class Notifier implements Runnable
{
private Observer o;
private String path;
private ChangeType type;
/** Initializes the Notifier.
* @param o The observer to be notified.
* @param path The path that has been updated.
* @param type The change type. */
public Notifier(Observer o, String path, ChangeType type)
{
this.o = o;
this.path = path;
this.type = type;
}
/** Runs the update on a separate thread. */
@Override
public void run()
{
try
{
// Quite slow
IndexableFile file = null;
if (path != null) file = FileProvider.getFile(path);
o.update(file, type);
}
catch (SQLException ex)
{
}
}
}
/** Command to update the database in a separate thread. */
private class Updater implements Runnable
{
private String path;
private ChangeType type;
/** Initializes a new Updater command.
* @param path The path of the file.
* @param type The change type.*/
public Updater(String path, ChangeType type)
{
if (type == null || type == ChangeType.NO_CHANGE)
{
throw new IllegalArgumentException();
}
if (path == null)
{
throw new IllegalArgumentException();
}
this.type = type;
this.path = path;
}
/** Runs the update */
@Override
public void run()
{
try
{
synchronized (LOCK)
{
alteringFile = true;
}
performUpdate(type, path);
}
catch (Throwable ex)
{
}
finally
{
alteringFile = false;
}
}
}
}