Issue #5086 Review and rework o.e.j.util.Scanner
Signed-off-by: Jan Bartel <janb@webtide.com>
This commit is contained in:
parent
7b77a532a8
commit
4883332593
|
@ -24,6 +24,7 @@ import java.net.MalformedURLException;
|
|||
import java.util.ArrayList;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
import org.eclipse.jetty.ant.types.Connector;
|
||||
import org.eclipse.jetty.ant.types.ContextHandlers;
|
||||
|
@ -135,7 +136,7 @@ public class ServerProxyImpl implements ServerProxy
|
|||
}
|
||||
|
||||
@Override
|
||||
public void filesChanged(List<String> changedFileNames)
|
||||
public void filesChanged(Set<String> changedFileNames)
|
||||
{
|
||||
boolean isScanned = false;
|
||||
try
|
||||
|
|
|
@ -140,7 +140,6 @@ public abstract class ScanningAppProvider extends ContainerLifeCycle implements
|
|||
_scanner = new Scanner();
|
||||
_scanner.setScanDirs(files);
|
||||
_scanner.setScanInterval(_scanInterval);
|
||||
_scanner.setRecursive(_recursive);
|
||||
_scanner.setFilenameFilter(_filenameFilter);
|
||||
_scanner.setReportDirs(true);
|
||||
_scanner.setScanDepth(1); //consider direct dir children of monitored dir
|
||||
|
|
|
@ -76,10 +76,15 @@ public class ScanningAppProviderRuntimeUpdatesTest
|
|||
if (provider instanceof ScanningAppProvider)
|
||||
{
|
||||
_providers++;
|
||||
((ScanningAppProvider)provider).addScannerListener(new Scanner.ScanListener()
|
||||
((ScanningAppProvider)provider).addScannerListener(new Scanner.ScanCycleListener()
|
||||
{
|
||||
@Override
|
||||
public void scan()
|
||||
public void scanStarted(int cycle)
|
||||
{
|
||||
}
|
||||
|
||||
@Override
|
||||
public void scanEnded(int cycle)
|
||||
{
|
||||
_scans.incrementAndGet();
|
||||
}
|
||||
|
|
|
@ -22,7 +22,7 @@ import java.io.File;
|
|||
import java.nio.file.Path;
|
||||
import java.nio.file.PathMatcher;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
import org.apache.maven.artifact.Artifact;
|
||||
import org.apache.maven.plugin.MojoExecutionException;
|
||||
|
@ -191,7 +191,7 @@ public class JettyRunMojo extends AbstractUnassembledWebAppMojo
|
|||
}
|
||||
scanner.addListener(new Scanner.BulkListener()
|
||||
{
|
||||
public void filesChanged(List<String> changes)
|
||||
public void filesChanged(Set<String> changes)
|
||||
{
|
||||
try
|
||||
{
|
||||
|
|
|
@ -22,7 +22,7 @@ import java.io.IOException;
|
|||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
import org.apache.maven.plugin.MojoExecutionException;
|
||||
import org.apache.maven.plugins.annotations.Execute;
|
||||
|
@ -197,7 +197,7 @@ public class JettyRunWarMojo extends AbstractWebAppMojo
|
|||
configureScanTargetPatterns(scanner);
|
||||
scanner.addListener(new Scanner.BulkListener()
|
||||
{
|
||||
public void filesChanged(List<String> changes)
|
||||
public void filesChanged(Set<String> changes)
|
||||
{
|
||||
try
|
||||
{
|
||||
|
|
|
@ -28,22 +28,21 @@ import java.nio.file.Files;
|
|||
import java.nio.file.Path;
|
||||
import java.nio.file.PathMatcher;
|
||||
import java.nio.file.attribute.BasicFileAttributes;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.EnumSet;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
import java.util.Set;
|
||||
import java.util.Timer;
|
||||
import java.util.TimerTask;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.CopyOnWriteArrayList;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import java.util.function.Predicate;
|
||||
|
||||
import org.eclipse.jetty.util.component.AbstractLifeCycle;
|
||||
import org.eclipse.jetty.util.thread.AutoLock;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
|
@ -67,27 +66,27 @@ public class Scanner extends AbstractLifeCycle
|
|||
private static final Logger LOG = LoggerFactory.getLogger(Scanner.class);
|
||||
private static int __scannerId = 0;
|
||||
|
||||
private final AutoLock _lock = new AutoLock();
|
||||
private int _scanInterval;
|
||||
private int _scanCount = 0;
|
||||
private final List<Listener> _listeners = new ArrayList<>();
|
||||
private final Map<String, TimeNSize> _prevScan = new HashMap<>();
|
||||
private final Map<String, TimeNSize> _currentScan = new HashMap<>();
|
||||
private AtomicInteger _scanCount = new AtomicInteger(0);
|
||||
private final List<Listener> _listeners = new CopyOnWriteArrayList<>();
|
||||
private Map<String, MetaData> _prevScan;
|
||||
private FilenameFilter _filter;
|
||||
private final Map<Path, IncludeExcludeSet<PathMatcher, Path>> _scannables = new HashMap<>();
|
||||
private volatile boolean _running = false;
|
||||
private final Map<Path, IncludeExcludeSet<PathMatcher, Path>> _scannables = new ConcurrentHashMap<>();
|
||||
private boolean _reportExisting = true;
|
||||
private boolean _reportDirs = true;
|
||||
private Timer _timer;
|
||||
private TimerTask _task;
|
||||
private int _scanDepth = DEFAULT_SCAN_DEPTH;
|
||||
|
||||
public enum Status
|
||||
{
|
||||
ADDED, CHANGED, REMOVED, STABLE
|
||||
}
|
||||
|
||||
public enum Notification
|
||||
{
|
||||
ADDED, CHANGED, REMOVED
|
||||
}
|
||||
|
||||
private final Map<String, Notification> _notifications = new HashMap<>();
|
||||
|
||||
/**
|
||||
* PathMatcherSet
|
||||
|
@ -110,42 +109,43 @@ public class Scanner extends AbstractLifeCycle
|
|||
}
|
||||
|
||||
/**
|
||||
* TimeNSize
|
||||
* MetaData
|
||||
*
|
||||
* Metadata about a file: Last modified time and file size.
|
||||
* Metadata about a file: Last modified time, file size and
|
||||
* last file status (ADDED, CHANGED, DELETED)
|
||||
*/
|
||||
static class TimeNSize
|
||||
static class MetaData
|
||||
{
|
||||
final long _lastModified;
|
||||
final long _size;
|
||||
Status _status;
|
||||
|
||||
public TimeNSize(long lastModified, long size)
|
||||
public MetaData(long lastModified, long size)
|
||||
{
|
||||
_lastModified = lastModified;
|
||||
_size = size;
|
||||
}
|
||||
|
||||
public void setStatus(Status status)
|
||||
{
|
||||
_status = status;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode()
|
||||
{
|
||||
return (int)_lastModified ^ (int)_size;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o)
|
||||
|
||||
public boolean isModified(MetaData m)
|
||||
{
|
||||
if (o instanceof TimeNSize)
|
||||
{
|
||||
TimeNSize tns = (TimeNSize)o;
|
||||
return tns._lastModified == _lastModified && tns._size == _size;
|
||||
}
|
||||
return false;
|
||||
return m._lastModified != _lastModified || m._size != _size;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString()
|
||||
{
|
||||
return "[lm=" + _lastModified + ",s=" + _size + "]";
|
||||
return "[lm=" + _lastModified + ",sz=" + _size + ",s=" + _status + "]";
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -157,11 +157,11 @@ public class Scanner extends AbstractLifeCycle
|
|||
*/
|
||||
class Visitor implements FileVisitor<Path>
|
||||
{
|
||||
Map<String, TimeNSize> scanInfoMap;
|
||||
Map<String, MetaData> scanInfoMap;
|
||||
IncludeExcludeSet<PathMatcher,Path> rootIncludesExcludes;
|
||||
Path root;
|
||||
|
||||
public Visitor(Path root, IncludeExcludeSet<PathMatcher,Path> rootIncludesExcludes, Map<String, TimeNSize> scanInfoMap)
|
||||
public Visitor(Path root, IncludeExcludeSet<PathMatcher,Path> rootIncludesExcludes, Map<String, MetaData> scanInfoMap)
|
||||
{
|
||||
this.root = root;
|
||||
this.rootIncludesExcludes = rootIncludesExcludes;
|
||||
|
@ -195,7 +195,7 @@ public class Scanner extends AbstractLifeCycle
|
|||
|
||||
if (accepted)
|
||||
{
|
||||
scanInfoMap.put(f.getCanonicalPath(), new TimeNSize(f.lastModified(), f.isDirectory() ? 0 : f.length()));
|
||||
scanInfoMap.put(f.getCanonicalPath(), new MetaData(f.lastModified(), f.isDirectory() ? 0 : f.length()));
|
||||
if (LOG.isDebugEnabled()) LOG.debug("scan accepted dir {} mod={}", f, f.lastModified());
|
||||
}
|
||||
}
|
||||
|
@ -227,7 +227,7 @@ public class Scanner extends AbstractLifeCycle
|
|||
|
||||
if (accepted)
|
||||
{
|
||||
scanInfoMap.put(f.getCanonicalPath(), new TimeNSize(f.lastModified(), f.isDirectory() ? 0 : f.length()));
|
||||
scanInfoMap.put(f.getCanonicalPath(), new MetaData(f.lastModified(), f.isDirectory() ? 0 : f.length()));
|
||||
if (LOG.isDebugEnabled()) LOG.debug("scan accepted {} mod={}", f, f.lastModified());
|
||||
}
|
||||
|
||||
|
@ -257,11 +257,10 @@ public class Scanner extends AbstractLifeCycle
|
|||
{
|
||||
}
|
||||
|
||||
public interface ScanListener extends Listener
|
||||
{
|
||||
public void scan();
|
||||
}
|
||||
|
||||
/**
|
||||
* Notification of exact file changes in the last scan.
|
||||
*
|
||||
*/
|
||||
public interface DiscreteListener extends Listener
|
||||
{
|
||||
public void fileChanged(String filename) throws Exception;
|
||||
|
@ -271,9 +270,13 @@ public class Scanner extends AbstractLifeCycle
|
|||
public void fileRemoved(String filename) throws Exception;
|
||||
}
|
||||
|
||||
/**
|
||||
* Notification of files that changed in the last scan.
|
||||
*
|
||||
*/
|
||||
public interface BulkListener extends Listener
|
||||
{
|
||||
public void filesChanged(List<String> filenames) throws Exception;
|
||||
public void filesChanged(Set<String> filenames) throws Exception;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -300,10 +303,7 @@ public class Scanner extends AbstractLifeCycle
|
|||
*/
|
||||
public int getScanInterval()
|
||||
{
|
||||
try (AutoLock l = _lock.lock())
|
||||
{
|
||||
return _scanInterval;
|
||||
}
|
||||
return _scanInterval;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -312,44 +312,31 @@ public class Scanner extends AbstractLifeCycle
|
|||
* @param scanInterval pause between scans in seconds, or 0 for no scan after the initial scan.
|
||||
*/
|
||||
public void setScanInterval(int scanInterval)
|
||||
{
|
||||
try (AutoLock l = _lock.lock())
|
||||
{
|
||||
_scanInterval = scanInterval;
|
||||
schedule();
|
||||
}
|
||||
{
|
||||
if (isRunning())
|
||||
throw new IllegalStateException("Scanner started");
|
||||
|
||||
_scanInterval = scanInterval;
|
||||
schedule();
|
||||
}
|
||||
|
||||
public void setScanDirs(List<File> dirs)
|
||||
public void setScanDirs(List<File> dirs) throws IOException
|
||||
{
|
||||
if (isRunning())
|
||||
throw new IllegalStateException("Scanner started");
|
||||
|
||||
_scannables.clear();
|
||||
if (dirs == null)
|
||||
return;
|
||||
|
||||
for (File f:dirs)
|
||||
for (File f :dirs)
|
||||
{
|
||||
addScanDir(f);
|
||||
}
|
||||
}
|
||||
|
||||
@Deprecated
|
||||
public void addScanDir(File dir)
|
||||
{
|
||||
if (dir == null)
|
||||
return;
|
||||
try (AutoLock l = _lock.lock())
|
||||
{
|
||||
if (dir.isDirectory())
|
||||
addDirectory(dir.toPath());
|
||||
if (f.isDirectory())
|
||||
addDirectory(f.toPath());
|
||||
else
|
||||
addFile(dir.toPath());
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
LOG.warn("Unable to add: {}", dir, e);
|
||||
addFile(f.toPath());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Add a file to be scanned. The file must not be null, and must exist.
|
||||
*
|
||||
|
@ -358,6 +345,9 @@ public class Scanner extends AbstractLifeCycle
|
|||
*/
|
||||
public void addFile(Path p) throws IOException
|
||||
{
|
||||
if (isRunning())
|
||||
throw new IllegalStateException("Scanner started");
|
||||
|
||||
if (p == null)
|
||||
throw new IllegalStateException("Null path");
|
||||
|
||||
|
@ -365,10 +355,7 @@ public class Scanner extends AbstractLifeCycle
|
|||
if (!f.exists() || f.isDirectory())
|
||||
throw new IllegalStateException("Not file or doesn't exist: " + f.getCanonicalPath());
|
||||
|
||||
try (AutoLock l = _lock.lock())
|
||||
{
|
||||
_scannables.put(p, null);
|
||||
}
|
||||
_scannables.putIfAbsent(p, null);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -378,80 +365,33 @@ public class Scanner extends AbstractLifeCycle
|
|||
* @return an IncludeExcludeSet to which the caller can add PathMatcher patterns to match
|
||||
* @throws IOException
|
||||
*/
|
||||
public IncludeExcludeSet<PathMatcher, Path> addDirectory(Path p) throws IOException
|
||||
public IncludeExcludeSet<PathMatcher, Path> addDirectory(Path p)
|
||||
{
|
||||
if (isRunning())
|
||||
throw new IllegalStateException("Scanner started");
|
||||
|
||||
if (p == null)
|
||||
throw new IllegalStateException("Null path");
|
||||
|
||||
File f = p.toFile();
|
||||
if (!f.exists() || !f.isDirectory())
|
||||
throw new IllegalStateException("Not directory or doesn't exist: " + f.getCanonicalPath());
|
||||
throw new IllegalStateException("Not directory or doesn't exist: " + p);
|
||||
|
||||
try (AutoLock l = _lock.lock())
|
||||
try
|
||||
{
|
||||
IncludeExcludeSet<PathMatcher, Path> includesExcludes = _scannables.get(p);
|
||||
if (includesExcludes == null)
|
||||
{
|
||||
includesExcludes = new IncludeExcludeSet<>(PathMatcherSet.class);
|
||||
_scannables.put(p.toRealPath(), includesExcludes);
|
||||
}
|
||||
Path real = p.toRealPath();
|
||||
IncludeExcludeSet<PathMatcher, Path> includesExcludes = new IncludeExcludeSet<>(PathMatcherSet.class);
|
||||
IncludeExcludeSet<PathMatcher, Path> prev = _scannables.putIfAbsent(real, includesExcludes);
|
||||
if (prev != null)
|
||||
includesExcludes = prev;
|
||||
return includesExcludes;
|
||||
}
|
||||
}
|
||||
|
||||
@Deprecated
|
||||
public List<File> getScanDirs()
|
||||
{
|
||||
ArrayList<File> files = new ArrayList<>();
|
||||
for (Path p : _scannables.keySet())
|
||||
files.add(p.toFile());
|
||||
return Collections.unmodifiableList(files);
|
||||
catch (IOException e)
|
||||
{
|
||||
throw new IllegalStateException(e);
|
||||
}
|
||||
}
|
||||
|
||||
public Set<Path> getScannables()
|
||||
{
|
||||
return _scannables.keySet();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param recursive True if scanning is recursive
|
||||
* @see #setScanDepth(int)
|
||||
*/
|
||||
@Deprecated
|
||||
public void setRecursive(boolean recursive)
|
||||
{
|
||||
_scanDepth = recursive ? Integer.MAX_VALUE : 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return True if scanning is recursive
|
||||
* @see #getScanDepth()
|
||||
*/
|
||||
@Deprecated
|
||||
public boolean getRecursive()
|
||||
{
|
||||
return _scanDepth > 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the scanDepth.
|
||||
*
|
||||
* @return the scanDepth
|
||||
*/
|
||||
public int getScanDepth()
|
||||
{
|
||||
return _scanDepth;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the scanDepth.
|
||||
*
|
||||
* @param scanDepth the scanDepth to set
|
||||
*/
|
||||
public void setScanDepth(int scanDepth)
|
||||
{
|
||||
_scanDepth = scanDepth;
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply a filter to files found in the scan directory.
|
||||
|
@ -476,6 +416,34 @@ public class Scanner extends AbstractLifeCycle
|
|||
return _filter;
|
||||
}
|
||||
|
||||
public Set<Path> getScannables()
|
||||
{
|
||||
return Collections.unmodifiableSet(_scannables.keySet());
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the scanDepth.
|
||||
*
|
||||
* @return the scanDepth
|
||||
*/
|
||||
public int getScanDepth()
|
||||
{
|
||||
return _scanDepth;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the scanDepth.
|
||||
*
|
||||
* @param scanDepth the scanDepth to set
|
||||
*/
|
||||
public void setScanDepth(int scanDepth)
|
||||
{
|
||||
if (isRunning())
|
||||
throw new IllegalStateException("Scanner started");
|
||||
|
||||
_scanDepth = scanDepth;
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether or not an initial scan will report all files as being
|
||||
* added.
|
||||
|
@ -485,6 +453,8 @@ public class Scanner extends AbstractLifeCycle
|
|||
*/
|
||||
public void setReportExistingFilesOnStartup(boolean reportExisting)
|
||||
{
|
||||
if (isRunning())
|
||||
throw new IllegalStateException("Scanner started");
|
||||
_reportExisting = reportExisting;
|
||||
}
|
||||
|
||||
|
@ -500,6 +470,8 @@ public class Scanner extends AbstractLifeCycle
|
|||
*/
|
||||
public void setReportDirs(boolean dirs)
|
||||
{
|
||||
if (isRunning())
|
||||
throw new IllegalStateException("Scanner started");
|
||||
_reportDirs = dirs;
|
||||
}
|
||||
|
||||
|
@ -517,10 +489,7 @@ public class Scanner extends AbstractLifeCycle
|
|||
{
|
||||
if (listener == null)
|
||||
return;
|
||||
try (AutoLock l = _lock.lock())
|
||||
{
|
||||
_listeners.add(listener);
|
||||
}
|
||||
_listeners.add(listener);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -532,10 +501,7 @@ public class Scanner extends AbstractLifeCycle
|
|||
{
|
||||
if (listener == null)
|
||||
return;
|
||||
try (AutoLock l = _lock.lock())
|
||||
{
|
||||
_listeners.remove(listener);
|
||||
}
|
||||
_listeners.remove(listener);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -544,30 +510,22 @@ public class Scanner extends AbstractLifeCycle
|
|||
@Override
|
||||
public void doStart()
|
||||
{
|
||||
try (AutoLock l = _lock.lock())
|
||||
if (LOG.isDebugEnabled())
|
||||
LOG.debug("Scanner start: rprtExists={}, depth={}, rprtDirs={}, interval={}, filter={}, scannables={}",
|
||||
_reportExisting, _scanDepth, _reportDirs, _scanInterval, _filter, _scannables);
|
||||
|
||||
if (_reportExisting)
|
||||
{
|
||||
if (_running)
|
||||
return;
|
||||
|
||||
_running = true;
|
||||
if (LOG.isDebugEnabled())
|
||||
LOG.debug("Scanner start: rprtExists={}, depth={}, rprtDirs={}, interval={}, filter={}, scannables={}",
|
||||
_reportExisting, _scanDepth, _reportDirs, _scanInterval, _filter, _scannables);
|
||||
|
||||
if (_reportExisting)
|
||||
{
|
||||
// if files exist at startup, report them
|
||||
scan();
|
||||
scan(); // scan twice so files reported as stable
|
||||
}
|
||||
else
|
||||
{
|
||||
//just register the list of existing files and only report changes
|
||||
scanFiles();
|
||||
_prevScan.putAll(_currentScan);
|
||||
}
|
||||
schedule();
|
||||
// if files exist at startup, report them
|
||||
scan();
|
||||
scan(); // scan twice so files reported as stable
|
||||
}
|
||||
else
|
||||
{
|
||||
//just register the list of existing files and only report changes
|
||||
_prevScan = scanFiles();
|
||||
}
|
||||
schedule();
|
||||
}
|
||||
|
||||
public TimerTask newTimerTask()
|
||||
|
@ -589,7 +547,7 @@ public class Scanner extends AbstractLifeCycle
|
|||
|
||||
public void schedule()
|
||||
{
|
||||
if (_running)
|
||||
if (isRunning())
|
||||
{
|
||||
if (_timer != null)
|
||||
_timer.cancel();
|
||||
|
@ -610,21 +568,14 @@ public class Scanner extends AbstractLifeCycle
|
|||
@Override
|
||||
public void doStop()
|
||||
{
|
||||
try (AutoLock l = _lock.lock())
|
||||
{
|
||||
if (_running)
|
||||
{
|
||||
_running = false;
|
||||
if (_timer != null)
|
||||
_timer.cancel();
|
||||
if (_task != null)
|
||||
_task.cancel();
|
||||
_task = null;
|
||||
_timer = null;
|
||||
}
|
||||
}
|
||||
if (_timer != null)
|
||||
_timer.cancel();
|
||||
if (_task != null)
|
||||
_task.cancel();
|
||||
_task = null;
|
||||
_timer = null;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Clear the list of scannables. The scanner must first
|
||||
* be in the stopped state.
|
||||
|
@ -633,13 +584,12 @@ public class Scanner extends AbstractLifeCycle
|
|||
{
|
||||
if (!isStopped())
|
||||
throw new IllegalStateException("Not stopped");
|
||||
|
||||
|
||||
//clear the scannables
|
||||
_scannables.clear();
|
||||
|
||||
|
||||
//clear the previous scans
|
||||
_currentScan.clear();
|
||||
_prevScan.clear();
|
||||
_prevScan = null;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -661,146 +611,116 @@ public class Scanner extends AbstractLifeCycle
|
|||
*/
|
||||
public void scan()
|
||||
{
|
||||
try (AutoLock l = _lock.lock())
|
||||
{
|
||||
reportScanStart(++_scanCount);
|
||||
scanFiles();
|
||||
reportDifferences(_currentScan, _prevScan);
|
||||
_prevScan.clear();
|
||||
_prevScan.putAll(_currentScan);
|
||||
reportScanEnd(_scanCount);
|
||||
|
||||
for (Listener listener : _listeners)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (listener instanceof ScanListener)
|
||||
((ScanListener)listener).scan();
|
||||
}
|
||||
catch (Throwable e)
|
||||
{
|
||||
LOG.warn("Unable to scan", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
int cycle = _scanCount.incrementAndGet();
|
||||
reportScanStart(cycle);
|
||||
Map<String, MetaData> currentScan = scanFiles();
|
||||
reportDifferences(currentScan, _prevScan == null ? Collections.emptyMap() : Collections.unmodifiableMap(_prevScan));
|
||||
_prevScan = currentScan;
|
||||
reportScanEnd(cycle);
|
||||
}
|
||||
|
||||
/**
|
||||
* Scan all of the given paths.
|
||||
*/
|
||||
public void scanFiles()
|
||||
public Map<String, MetaData> scanFiles()
|
||||
{
|
||||
try (AutoLock l = _lock.lock())
|
||||
Map<String, MetaData> currentScan = new HashMap<>();
|
||||
for (Path p : _scannables.keySet())
|
||||
{
|
||||
_currentScan.clear();
|
||||
for (Path p : _scannables.keySet())
|
||||
try
|
||||
{
|
||||
try
|
||||
{
|
||||
Files.walkFileTree(p, EnumSet.allOf(FileVisitOption.class),_scanDepth, new Visitor(p, _scannables.get(p), _currentScan));
|
||||
}
|
||||
catch (IOException e)
|
||||
{
|
||||
LOG.warn("Error scanning files.", e);
|
||||
}
|
||||
Files.walkFileTree(p, EnumSet.allOf(FileVisitOption.class),_scanDepth, new Visitor(p, _scannables.get(p), currentScan));
|
||||
}
|
||||
catch (IOException e)
|
||||
{
|
||||
LOG.warn("Error scanning files.", e);
|
||||
}
|
||||
}
|
||||
return currentScan;
|
||||
}
|
||||
|
||||
/**
|
||||
* Report the adds/changes/removes to the registered listeners
|
||||
*
|
||||
* Only report an add or change once a file has stablilized in size.
|
||||
*
|
||||
* @param currentScan the info from the most recent pass
|
||||
* @param oldScan info from the previous pass
|
||||
*/
|
||||
private void reportDifferences(Map<String, TimeNSize> currentScan, Map<String, TimeNSize> oldScan)
|
||||
private void reportDifferences(Map<String, MetaData> currentScan, Map<String, MetaData> oldScan)
|
||||
{
|
||||
try (AutoLock l = _lock.lock())
|
||||
Map<String, Notification> changes = new HashMap<>();
|
||||
|
||||
//Handle deleted files
|
||||
Set<String> oldScanKeys = new HashSet<>(oldScan.keySet());
|
||||
oldScanKeys.removeAll(currentScan.keySet());
|
||||
for (String file : oldScanKeys)
|
||||
{
|
||||
// scan the differences and add what was found to the map of notifications:
|
||||
Set<String> oldScanKeys = new HashSet<>(oldScan.keySet());
|
||||
|
||||
// Look for new and changed files
|
||||
for (Entry<String, TimeNSize> entry : currentScan.entrySet())
|
||||
{
|
||||
String file = entry.getKey();
|
||||
if (!oldScanKeys.contains(file))
|
||||
{
|
||||
Notification old = _notifications.put(file, Notification.ADDED);
|
||||
if (old != null)
|
||||
{
|
||||
switch (old)
|
||||
{
|
||||
case REMOVED:
|
||||
case CHANGED:
|
||||
_notifications.put(file, Notification.CHANGED);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (!oldScan.get(file).equals(currentScan.get(file)))
|
||||
{
|
||||
Notification old = _notifications.put(file, Notification.CHANGED);
|
||||
if (old == Notification.ADDED)
|
||||
_notifications.put(file, Notification.ADDED);
|
||||
}
|
||||
}
|
||||
|
||||
// Look for deleted files
|
||||
for (String file : oldScan.keySet())
|
||||
{
|
||||
if (!currentScan.containsKey(file))
|
||||
{
|
||||
Notification old = _notifications.put(file, Notification.REMOVED);
|
||||
if (old == Notification.ADDED)
|
||||
_notifications.remove(file);
|
||||
}
|
||||
}
|
||||
|
||||
if (LOG.isDebugEnabled())
|
||||
LOG.debug("scanned {}: {}", _scannables.keySet(), _notifications);
|
||||
|
||||
// Process notifications
|
||||
// Only process notifications that are for stable files (ie same in old and current scan).
|
||||
List<String> bulkChanges = new ArrayList<>();
|
||||
for (Iterator<Entry<String, Notification>> iter = _notifications.entrySet().iterator(); iter.hasNext(); )
|
||||
{
|
||||
|
||||
Entry<String, Notification> entry = iter.next();
|
||||
String file = entry.getKey();
|
||||
// Is the file stable?
|
||||
if (oldScan.containsKey(file))
|
||||
{
|
||||
if (!oldScan.get(file).equals(currentScan.get(file)))
|
||||
continue;
|
||||
}
|
||||
else if (currentScan.containsKey(file))
|
||||
continue;
|
||||
|
||||
// File is stable so notify
|
||||
Notification notification = entry.getValue();
|
||||
iter.remove();
|
||||
bulkChanges.add(file);
|
||||
switch (notification)
|
||||
{
|
||||
case ADDED:
|
||||
reportAddition(file);
|
||||
break;
|
||||
case CHANGED:
|
||||
reportChange(file);
|
||||
break;
|
||||
case REMOVED:
|
||||
reportRemoval(file);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!bulkChanges.isEmpty())
|
||||
reportBulkChanges(bulkChanges);
|
||||
changes.put(file, Notification.REMOVED);
|
||||
}
|
||||
|
||||
// Handle new and changed files
|
||||
for (String file : currentScan.keySet())
|
||||
{
|
||||
MetaData current = currentScan.get(file);
|
||||
MetaData previous = oldScan.get(file);
|
||||
|
||||
if (previous == null)
|
||||
{
|
||||
//New file - don't immediately
|
||||
//notify this, wait until the size has
|
||||
//settled down then notify the add.
|
||||
current._status = Status.ADDED;
|
||||
}
|
||||
else if (current.isModified(previous))
|
||||
{
|
||||
//Changed file - handle case where file
|
||||
//that was added on previous scan has since
|
||||
//been modified. We need to retain status
|
||||
//as added, so we send the ADDED event once
|
||||
//the file has settled down.
|
||||
if (previous._status == Status.ADDED)
|
||||
current._status = Status.ADDED;
|
||||
else
|
||||
current._status = Status.CHANGED;
|
||||
}
|
||||
else
|
||||
{
|
||||
//Unchanged file: if it was previously
|
||||
//ADDED, we can now send the ADDED event.
|
||||
if (previous._status == Status.ADDED)
|
||||
changes.put(file, Notification.ADDED);
|
||||
else if (previous._status == Status.CHANGED)
|
||||
changes.put(file, Notification.CHANGED);
|
||||
|
||||
current._status = Status.STABLE;
|
||||
}
|
||||
}
|
||||
|
||||
if (LOG.isDebugEnabled())
|
||||
LOG.debug("scanned {}", _scannables.keySet());
|
||||
|
||||
//Call the DiscreteListeners
|
||||
for (Map.Entry<String, Notification> entry : changes.entrySet())
|
||||
{
|
||||
switch (entry.getValue())
|
||||
{
|
||||
case ADDED:
|
||||
reportAddition(entry.getKey());
|
||||
break;
|
||||
case CHANGED:
|
||||
reportChange(entry.getKey());
|
||||
break;
|
||||
case REMOVED:
|
||||
reportRemoval(entry.getKey());
|
||||
break;
|
||||
default:
|
||||
LOG.warn("Unknown file change: {}", entry.getValue());
|
||||
break;
|
||||
}
|
||||
}
|
||||
//Call the BulkListeners
|
||||
reportBulkChanges(changes.keySet());
|
||||
}
|
||||
|
||||
private void warn(Object listener, String filename, Throwable th)
|
||||
|
@ -857,6 +777,9 @@ public class Scanner extends AbstractLifeCycle
|
|||
*/
|
||||
private void reportChange(String filename)
|
||||
{
|
||||
if (filename == null)
|
||||
return;
|
||||
|
||||
for (Listener l : _listeners)
|
||||
{
|
||||
try
|
||||
|
@ -876,8 +799,11 @@ public class Scanner extends AbstractLifeCycle
|
|||
*
|
||||
* @param filenames names of all files added/changed/removed
|
||||
*/
|
||||
private void reportBulkChanges(List<String> filenames)
|
||||
private void reportBulkChanges(Set<String> filenames)
|
||||
{
|
||||
if (filenames == null || filenames.isEmpty())
|
||||
return;
|
||||
|
||||
for (Listener l : _listeners)
|
||||
{
|
||||
try
|
||||
|
@ -902,9 +828,7 @@ public class Scanner extends AbstractLifeCycle
|
|||
try
|
||||
{
|
||||
if (listener instanceof ScanCycleListener)
|
||||
{
|
||||
((ScanCycleListener)listener).scanStarted(cycle);
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
|
@ -923,9 +847,8 @@ public class Scanner extends AbstractLifeCycle
|
|||
try
|
||||
{
|
||||
if (listener instanceof ScanCycleListener)
|
||||
{
|
||||
((ScanCycleListener)listener).scanEnded(cycle);
|
||||
}
|
||||
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
|
|
|
@ -77,7 +77,7 @@ public class KeyStoreScanner extends ContainerLifeCycle implements Scanner.Discr
|
|||
throw new IllegalArgumentException("error obtaining keystore dir");
|
||||
|
||||
_scanner = new Scanner();
|
||||
_scanner.setScanDirs(Collections.singletonList(parentFile));
|
||||
_scanner.addDirectory(parentFile.toPath());
|
||||
_scanner.setScanInterval(1);
|
||||
_scanner.setReportDirs(false);
|
||||
_scanner.setReportExistingFilesOnStartup(false);
|
||||
|
|
|
@ -24,7 +24,7 @@ import java.io.IOException;
|
|||
import java.io.OutputStream;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.PathMatcher;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.BlockingQueue;
|
||||
import java.util.concurrent.LinkedBlockingQueue;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
@ -42,6 +42,7 @@ import org.junit.jupiter.api.condition.DisabledOnOs;
|
|||
import static org.hamcrest.MatcherAssert.assertThat;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
||||
import static org.junit.jupiter.api.Assertions.assertNull;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
import static org.junit.jupiter.api.condition.OS.WINDOWS;
|
||||
|
||||
|
@ -49,8 +50,8 @@ public class ScannerTest
|
|||
{
|
||||
static File _directory;
|
||||
static Scanner _scanner;
|
||||
static BlockingQueue<Event> _queue = new LinkedBlockingQueue<Event>();
|
||||
static BlockingQueue<List<String>> _bulk = new LinkedBlockingQueue<List<String>>();
|
||||
static BlockingQueue<Event> _queue = new LinkedBlockingQueue<>();
|
||||
static BlockingQueue<Set<String>> _bulk = new LinkedBlockingQueue<>();
|
||||
|
||||
@BeforeAll
|
||||
public static void setUpBeforeClass() throws Exception
|
||||
|
@ -63,7 +64,7 @@ public class ScannerTest
|
|||
_directory = testDir.toPath().toRealPath().toFile();
|
||||
|
||||
_scanner = new Scanner();
|
||||
_scanner.addScanDir(_directory);
|
||||
_scanner.addDirectory(_directory.toPath());
|
||||
_scanner.setScanInterval(0);
|
||||
_scanner.setReportDirs(false);
|
||||
_scanner.setReportExistingFilesOnStartup(false);
|
||||
|
@ -90,7 +91,7 @@ public class ScannerTest
|
|||
_scanner.addListener(new Scanner.BulkListener()
|
||||
{
|
||||
@Override
|
||||
public void filesChanged(List<String> filenames) throws Exception
|
||||
public void filesChanged(Set<String> filenames) throws Exception
|
||||
{
|
||||
_bulk.add(filenames);
|
||||
}
|
||||
|
@ -296,9 +297,9 @@ public class ScannerTest
|
|||
// not stable after 1 scan so should not be seen yet.
|
||||
_scanner.scan();
|
||||
event = _queue.poll();
|
||||
assertTrue(event == null);
|
||||
assertNull(event);
|
||||
|
||||
// Keep a2 unstable and remove a3 before it stabalized
|
||||
// Keep a2 unstable and remove a3 before it stabilized
|
||||
Thread.sleep(1100); // make sure time in seconds changes
|
||||
touch("a2");
|
||||
delete("a3");
|
||||
|
@ -306,21 +307,26 @@ public class ScannerTest
|
|||
// only a1 is stable so it should be seen.
|
||||
_scanner.scan();
|
||||
event = _queue.poll();
|
||||
assertTrue(event != null);
|
||||
assertNotNull(event);
|
||||
assertEquals(_directory + "/a1", event._filename);
|
||||
assertEquals(Notification.ADDED, event._notification);
|
||||
assertTrue(_queue.isEmpty());
|
||||
|
||||
//TODO: behaviour change, we should see an immediate
|
||||
//delete for a3
|
||||
event = _queue.poll();
|
||||
assertNotNull(event);
|
||||
assertEquals(_directory + "/a3", event._filename);
|
||||
assertEquals(Notification.REMOVED, event._notification);
|
||||
assertTrue(_queue.isEmpty());
|
||||
|
||||
// Now a2 is stable
|
||||
_scanner.scan();
|
||||
event = _queue.poll();
|
||||
assertTrue(event != null);
|
||||
assertNotNull(event);
|
||||
assertEquals(_directory + "/a2", event._filename);
|
||||
assertEquals(Notification.ADDED, event._notification);
|
||||
assertTrue(_queue.isEmpty());
|
||||
|
||||
// We never see a3 as it was deleted before it stabalised
|
||||
|
||||
// touch a1 and a2
|
||||
Thread.sleep(1100); // make sure time in seconds changes
|
||||
touch("a1");
|
||||
|
@ -337,7 +343,7 @@ public class ScannerTest
|
|||
// only a1 is stable so it should be seen.
|
||||
_scanner.scan();
|
||||
event = _queue.poll();
|
||||
assertTrue(event != null);
|
||||
assertNotNull(event);
|
||||
assertEquals(_directory + "/a1", event._filename);
|
||||
assertEquals(Notification.CHANGED, event._notification);
|
||||
assertTrue(_queue.isEmpty());
|
||||
|
@ -345,7 +351,7 @@ public class ScannerTest
|
|||
// Now a2 is stable
|
||||
_scanner.scan();
|
||||
event = _queue.poll();
|
||||
assertTrue(event != null);
|
||||
assertNotNull(event);
|
||||
assertEquals(_directory + "/a2", event._filename);
|
||||
assertEquals(Notification.CHANGED, event._notification);
|
||||
assertTrue(_queue.isEmpty());
|
||||
|
@ -353,28 +359,37 @@ public class ScannerTest
|
|||
// delete a1 and a2
|
||||
delete("a1");
|
||||
delete("a2");
|
||||
// not stable after 1scan so nothing should not be seen yet.
|
||||
|
||||
//TODO: behaviour change, now we get immediate notification of
|
||||
//deletes.
|
||||
_scanner.scan();
|
||||
|
||||
event = _queue.poll();
|
||||
assertTrue(event == null);
|
||||
|
||||
// readd a2
|
||||
touch("a2");
|
||||
|
||||
// only a1 is stable so it should be seen.
|
||||
_scanner.scan();
|
||||
event = _queue.poll();
|
||||
assertTrue(event != null);
|
||||
assertNotNull(event);
|
||||
assertEquals(_directory + "/a1", event._filename);
|
||||
assertEquals(Notification.REMOVED, event._notification);
|
||||
event = _queue.poll();
|
||||
assertNotNull(event);
|
||||
assertEquals(_directory + "/a2", event._filename);
|
||||
assertEquals(Notification.REMOVED, event._notification);
|
||||
assertTrue(_queue.isEmpty());
|
||||
|
||||
// recreate a2
|
||||
touch("a2");
|
||||
|
||||
// a2 not stable yet, shouldn't be seen
|
||||
_scanner.scan();
|
||||
event = _queue.poll();
|
||||
assertNull(event);
|
||||
assertTrue(_queue.isEmpty());
|
||||
|
||||
// Now a2 is stable and is a changed file rather than a remove
|
||||
//TODO: behaviour change, now a2 is reported as ADDED. Previously, a2
|
||||
//delete was not reported, but was reported as CHANGED when re-added.
|
||||
_scanner.scan();
|
||||
event = _queue.poll();
|
||||
assertTrue(event != null);
|
||||
assertEquals(_directory + "/a2", event._filename);
|
||||
assertEquals(Notification.CHANGED, event._notification);
|
||||
assertEquals(Notification.ADDED, event._notification);
|
||||
assertTrue(_queue.isEmpty());
|
||||
}
|
||||
|
||||
|
@ -386,9 +401,9 @@ public class ScannerTest
|
|||
_scanner.scan();
|
||||
_scanner.scan();
|
||||
|
||||
// takes 2s to notice tsc0 and check that it is stable. This syncs us with the scan
|
||||
// takes 2 scans to notice tsc0 and check that it is stable.
|
||||
Event event = _queue.poll();
|
||||
assertTrue(event != null);
|
||||
assertNotNull(event);
|
||||
assertEquals(_directory + "/tsc0", event._filename);
|
||||
assertEquals(Notification.ADDED, event._notification);
|
||||
|
||||
|
@ -404,7 +419,7 @@ public class ScannerTest
|
|||
// Not stable yet so no notification.
|
||||
_scanner.scan();
|
||||
event = _queue.poll();
|
||||
assertTrue(event == null);
|
||||
assertNull(event);
|
||||
|
||||
// Modify size only
|
||||
out.write('x');
|
||||
|
@ -414,12 +429,12 @@ public class ScannerTest
|
|||
// Still not stable yet so no notification.
|
||||
_scanner.scan();
|
||||
event = _queue.poll();
|
||||
assertTrue(event == null);
|
||||
assertNull(event);
|
||||
|
||||
// now stable so finally see the ADDED
|
||||
_scanner.scan();
|
||||
event = _queue.poll();
|
||||
assertTrue(event != null);
|
||||
assertNotNull(event);
|
||||
assertEquals(_directory + "/st", event._filename);
|
||||
assertEquals(Notification.ADDED, event._notification);
|
||||
|
||||
|
@ -431,12 +446,12 @@ public class ScannerTest
|
|||
// Still not stable yet so no notification.
|
||||
_scanner.scan();
|
||||
event = _queue.poll();
|
||||
assertTrue(event == null);
|
||||
assertNull(event);
|
||||
|
||||
// now stable so finally see the ADDED
|
||||
_scanner.scan();
|
||||
event = _queue.poll();
|
||||
assertTrue(event != null);
|
||||
assertNotNull(event);
|
||||
assertEquals(_directory + "/st", event._filename);
|
||||
assertEquals(Notification.CHANGED, event._notification);
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue