package com.github.drapostolos.rdp4j; import static java.lang.String.format; import java.io.IOException; import java.util.ArrayList; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.Set; import java.util.concurrent.Callable; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.github.drapostolos.rdp4j.spi.FileElement; import com.github.drapostolos.rdp4j.spi.PolledDirectory; class Poller implements Callable<Object> { private static final Logger LOG = LoggerFactory.getLogger(ScheduledRunnable.class); final PolledDirectory directory; private final List<CachedFileElement> modifiedFiles = new ArrayList<CachedFileElement>(); private final FileFilter filter; private final ListenerNotifier notifier; private boolean isFirstPollCycle = true; private boolean isFileSystemUnaccessible = true; private HashMapComparer<String, CachedFileElement> mapComparer; private final Map<String, CachedFileElement> currentListedFiles; private final Map<String, CachedFileElement> previousListedFiles; private final DirectoryPoller dp; Poller(DirectoryPoller dp, PolledDirectory directory) { this(dp, directory, Util.newLinkedHashMap(), Util.newLinkedHashMap()); } /* * Use this constructor when letting client provide snapshot of last poll, * from a previous DirectoryPoller instance. */ private Poller(DirectoryPoller dp, PolledDirectory directory, Map<String, CachedFileElement> currentListedFiles, Map<String, CachedFileElement> previousListedFiles) { this.dp = dp; this.directory = directory; this.filter = dp.getDefaultFileFilter(); this.notifier = dp.notifier; this.currentListedFiles = currentListedFiles; this.previousListedFiles = previousListedFiles; } @Override public Object call() throws InterruptedException { collectCurrentFilesAndNotifyListenersIfIoErrorRaisedOrCeased(); if (isFilesystemAccessible()) { detectAndCollectModifiedFiles(); setComparerForCurrentVersusPreviousListedFiles(); if (isFirstPollCycle) { doActionsSpecificForFirstPollCycle(); isFirstPollCycle = false; } else { doActionsForRemainingPollCycles(); } if (isDirectoryModified()) { copyCurrentListedFilesToPrevious(); } } return null; } private void doActionsSpecificForFirstPollCycle() throws InterruptedException { if (dp.fileAddedEventEnabledForInitialContent) { // make sure this events fires before InitialContentEvent notifyListenersWithRemovedAddedModifiedFiles(); } dp.notifier.initialContent(new InitialContentEvent(dp, directory, currentListedFiles)); } private void doActionsForRemainingPollCycles() throws InterruptedException { notifyListenersWithRemovedAddedModifiedFiles(); } private boolean isFilesystemAccessible() { return isFileSystemUnaccessible; } private void setComparerForCurrentVersusPreviousListedFiles() { mapComparer = new HashMapComparer<String, CachedFileElement>(previousListedFiles, currentListedFiles); } private void detectAndCollectModifiedFiles() { modifiedFiles.clear(); for (CachedFileElement f : currentListedFiles.values()) { if (isFileModified(f)) { modifiedFiles.add(f); } } } private boolean isFileModified(CachedFileElement file) { if (previousListedFiles.containsKey(file.getName())) { long current = file.lastModified(); long previous = previousListedFiles.get(file.getName()).lastModified(); return current != previous; } return false; } private void collectCurrentFilesAndNotifyListenersIfIoErrorRaisedOrCeased() throws InterruptedException { try { Set<FileElement> files = directory.listFiles(); if (files == null) { String message = "Unknown underlying IO-error when listing files " + "in directory: '%s'. Method listFiles() returned null."; throw new IOException(String.format(message, directory)); } Map<String, CachedFileElement> temp = filterFiles(files); if (isFilesystemUnaccessible()) { notifier.ioErrorCeased(new IoErrorCeasedEvent(dp, directory)); isFileSystemUnaccessible = true; } currentListedFiles.clear(); currentListedFiles.putAll(temp); } catch (IOException e) { if (isFilesystemAccessible()) { isFileSystemUnaccessible = false; notifier.ioErrorRaised(new IoErrorRaisedEvent(dp, directory, e)); } } catch (DirectoryPollerException e) { // Silently wait fore next poll. } catch (RuntimeException e) { dp.stopAsync(); String message = "DirectoryPoller will be stopped " + "due to unexpected crash in PolledDirectory implementation '%s'. " + "See underlying exception for more information."; message = String.format(message, directory.getClass().getName()); LOG.error(message, e); throw new IllegalStateException(message, e); } } private Map<String, CachedFileElement> filterFiles(Set<FileElement> files) throws IOException { Map<String, CachedFileElement> result = new LinkedHashMap<String, CachedFileElement>(); for (FileElement file : files) { if (filter.accept(file)) { long lastModified = file.lastModified(); if (lastModified == 0L) { String message = "Unknown underlying IO-Error. " + "Method 'lastModified()' returned '0L' for file '%s'"; throw new IOException(format(message, file)); } String name = file.getName(); result.put(name, new CachedFileElement(file, name, lastModified)); } } return result; } private boolean isFilesystemUnaccessible() { return !isFilesystemAccessible(); } private boolean isDirectoryModified() { return !modifiedFiles.isEmpty() || mapComparer.hasDiff(); } private void notifyListenersWithRemovedAddedModifiedFiles() throws InterruptedException { for (CachedFileElement file : mapComparer.getRemoved().values()) { notifier.fileRemoved(new FileRemovedEvent(dp, directory, file.fileElement)); } for (CachedFileElement file : mapComparer.getAdded().values()) { notifier.fileAdded(new FileAddedEvent(dp, directory, file.fileElement)); } for (CachedFileElement file : modifiedFiles) { notifier.fileModified(new FileModifiedEvent(dp, directory, file.fileElement)); } } private void copyCurrentListedFilesToPrevious() { previousListedFiles.clear(); previousListedFiles.putAll(currentListedFiles); } @Override public int hashCode() { final int prime = 31; int result = 1; result = prime * result + ((directory == null) ? 0 : directory.hashCode()); return result; } @Override public boolean equals(Object obj) { if (this == obj) return true; if (obj == null) return false; if (getClass() != obj.getClass()) return false; Poller other = (Poller) obj; if (directory == null) { if (other.directory != null) return false; } else if (!directory.equals(other.directory)) return false; return true; } }