# HG changeset patch # Parent ac0db620d93a8a0dc441a6f02a343c541b77a16b #225558: Files does not refresh in project view with symlinks diff --git a/masterfs/src/org/netbeans/modules/masterfs/watcher/Watcher.java b/masterfs/src/org/netbeans/modules/masterfs/watcher/Watcher.java --- a/masterfs/src/org/netbeans/modules/masterfs/watcher/Watcher.java +++ b/masterfs/src/org/netbeans/modules/masterfs/watcher/Watcher.java @@ -74,6 +74,14 @@ @ServiceProvider(service=Watcher.class) }) public final class Watcher extends AnnotationProvider { + + /** + * Flag indicating whether the user wishes to watch targets of symbolic + * links. By default, this is disabled, due to performance issues. + */ + private static final boolean WATCH_SYMLINKS = Boolean.getBoolean( + "nb.fs.watch.symlinks"); //NOI18N + static final Logger LOG = Logger.getLogger(Watcher.class.getName()); private static final Map MODIFIED = new WeakHashMap(); private final Ext ext; @@ -181,6 +189,10 @@ private final Notifier impl; private final Object LOCK = new Object(); private final Set references = new HashSet(); + private final Map> aliases + = WATCH_SYMLINKS ? new WeakHashMap>() : null; + private final Map aliasToRealMap = WATCH_SYMLINKS + ? new WeakHashMap() : null; private final Thread watcher; private volatile boolean shutdown; @@ -233,6 +245,9 @@ } catch (IOException ex) { LOG.log(Level.INFO, "Exception while clearing the queue", ex); } + + fo = registerSymlinkAlias(fo); + synchronized (LOCK) { NotifierKeyRef kr = new NotifierKeyRef(fo, null, null, impl); if (getReferences().contains(kr)) { @@ -250,8 +265,47 @@ } } + /** + * Check whether the file is a symlink and register it as an alias for + * its real file (if watching of symlinks is enabled). + * + * @return The real file if the passed file is a symlink, watching + * symlinks is enabled and the alias was successfully registered. + * Otherwise, the passed file is returned. + */ + private FileObject registerSymlinkAlias(FileObject fo) { + if (WATCH_SYMLINKS) { + try { + FileObject real = fo.getRealFileObject(); + if (real != fo) { + synchronized (aliases) { + Set symlinked = aliases.get(real); + if (symlinked == null) { + symlinked = new WeakSet(); + aliases.put(real, symlinked); + } + symlinked.add(fo); + // prevent early garbage collection of real objects + aliasToRealMap.put(fo, real); + } + } + return real; + } catch (IOException ex) { + LOG.log(Level.INFO, null, ex); + return fo; // reading symlink target failed + } + } else { + return fo; // watching of symlinks is disabled + } + } + final void unregister(FileObject fo) { assert !fo.isValid() || fo.isFolder() : "If valid, it should be a folder: " + fo + " clazz: " + fo.getClass(); + + fo = unregisterSymlinkAlias(fo); + if (fo == null) { + return; + } synchronized (LOCK) { final NotifierKeyRef[] equalOne = new NotifierKeyRef[1]; NotifierKeyRef kr = new NotifierKeyRef(fo, null, null, impl) { @@ -284,6 +338,51 @@ } } + /** + * Check if the path to the file contains some symlink, and if so, + * unregister the alias. + * + * @param fo The file object to unregister, a real file object or an + * alias. + * + * @return The real file object to unregister, or null if there is some + * alias registered and the real file object should not be unregistered. + */ + private FileObject unregisterSymlinkAlias(FileObject fo) { + if (WATCH_SYMLINKS) { + try { + FileObject real = fo.getRealFileObject(); + synchronized (aliases) { + if (real != fo) { + Set symlinked = aliases.get(real); + if (symlinked != null) { + symlinked.remove(fo); + if (symlinked.isEmpty()) { + aliases.remove(real); + aliasToRealMap.remove(fo); + return real; // last alias, can unregister + } else { + return null; // aliases still registered + } + } else { + return real; // odd, but no aliases registered + } + } else { + Set symlinked = aliases.get(real); + return symlinked == null || symlinked.isEmpty() + ? real // no alias registered + : null; // cannot unregister + } + } + } catch (IOException ex) { + LOG.log(Level.INFO, null, ex); + return fo; // symlink reading failed + } + } else { + return fo; // watching of symlinks is disabled + } + } + final void clearQueue() throws IOException { for (;;) { NotifierKeyRef kr = (NotifierKeyRef)REF.poll(); @@ -312,6 +411,7 @@ set.add(ref); } } + addAllAliasesToSet(set); } enqueueAll(set); } else { @@ -328,6 +428,7 @@ if (getReferences().contains(kr)) { enqueue(fo); } + enqueueFileAliases(fo); } } } @@ -343,6 +444,37 @@ } } + /** + * Enqueue all registered aliases of a real file. + */ + private void enqueueFileAliases(FileObject fo) { + if (WATCH_SYMLINKS) { + synchronized (aliases) { + Set fos = aliases.get(fo); + if (fos != null) { + for (FileObject alias : fos) { + enqueue(alias); + } + } + } + } + } + + /** + * Add all registered file aliases to a set. + */ + private void addAllAliasesToSet(Set set) { + if (WATCH_SYMLINKS) { + synchronized (aliases) { + for (Set s : aliases.values()) { + for (FileObject f : s) { + set.add(f); + } + } + } + } + } + final void shutdown() throws IOException, InterruptedException { shutdown = true; watcher.interrupt();