All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.swoval.files.PollingPathWatcher Maven / Gradle / Ivy

There is a newer version: 2.1.12
Show newest version
package com.swoval.files;

import static com.swoval.functional.Filters.AllPass;

import com.swoval.files.FileTreeDataViews.CacheObserver;
import com.swoval.files.FileTreeDataViews.Converter;
import com.swoval.files.FileTreeViews.Observer;
import com.swoval.files.PathWatchers.Event;
import com.swoval.files.PathWatchers.Event.Kind;
import com.swoval.functional.Either;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.NotDirectoryException;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;

class PollingPathWatcher implements PathWatcher {
  private final AtomicBoolean isClosed = new AtomicBoolean(false);
  private final boolean followLinks;
  private final DirectoryRegistry registry = new DirectoryRegistryImpl();
  private final Observers observers = new Observers<>();
  private Map> oldEntries;
  private final PeriodicTask periodicTask;
  private final Converter converter;

  PollingPathWatcher(
      final Converter converter,
      final boolean followLinks,
      final long pollInterval,
      final TimeUnit timeUnit)
      throws InterruptedException {
    this.converter = converter;
    this.followLinks = followLinks;
    oldEntries = getEntries();
    periodicTask = new PeriodicTask(new PollingRunnable(), timeUnit.toMillis(pollInterval));
  }

  PollingPathWatcher(final boolean followLinks, final long pollInterval, final TimeUnit timeUnit)
      throws InterruptedException {
    this(
        new Converter() {
          @Override
          public Long apply(final TypedPath typedPath) {
            try {
              return Files.getLastModifiedTime(typedPath.getPath()).toMillis();
            } catch (final Exception e) {
              return 0L;
            }
          }
        },
        followLinks,
        pollInterval,
        timeUnit);
  }

  @Override
  public Either register(final Path path, final int maxDepth) {
    final Path absolutePath = path.isAbsolute() ? path : path.toAbsolutePath();
    boolean result;
    final List> entries = getEntries(absolutePath, maxDepth);
    synchronized (this) {
      addAll(oldEntries, entries);
      result = registry.addDirectory(absolutePath, maxDepth);
    }
    return Either.right(result);
  }

  @Override
  public void unregister(final Path path) {
    final Path absolutePath = path.isAbsolute() ? path : path.toAbsolutePath();
    registry.removeDirectory(absolutePath);
  }

  @Override
  public void close() {
    if (isClosed.compareAndSet(false, true)) {
      registry.close();
      try {
        periodicTask.close();
      } catch (final InterruptedException e) {
        e.printStackTrace(System.err);
      }
    }
  }

  @Override
  public int addObserver(Observer observer) {
    return observers.addObserver(observer);
  }

  @Override
  public void removeObserver(final int handle) {
    observers.removeObserver(handle);
  }

  private void addAll(
      final Map> map,
      final List> list) {
    final Iterator> it = list.iterator();
    while (it.hasNext()) {
      final FileTreeDataViews.Entry entry = it.next();
      map.put(entry.getTypedPath().getPath(), entry);
    }
  }

  private List> getEntries(final Path path, final int maxDepth) {
    try {
      final DirectoryDataView view =
          FileTreeDataViews.cached(path, converter, maxDepth, followLinks);
      final List> entries = view.listEntries(-1, AllPass);
      entries.addAll(view.listEntries(maxDepth, AllPass));
      return entries;
    } catch (final NotDirectoryException e) {
      final List> result = new ArrayList<>();
      final TypedPath typedPath = TypedPaths.get(path);
      result.add(Entries.get(typedPath, converter, typedPath));
      return result;
    } catch (final IOException e) {
      return Collections.emptyList();
    }
  }

  private Map> getEntries() {
    // I have to use putAll because scala.js doesn't handle new HashMap(registry.registered()).
    final Map map = new ConcurrentHashMap<>();
    synchronized (this) {
      map.putAll(registry.registered());
    }
    final Iterator> it = map.entrySet().iterator();
    final Map> result = new ConcurrentHashMap<>();
    while (it.hasNext()) {
      final Entry entry = it.next();
      final List> entries =
          getEntries(entry.getKey(), entry.getValue());
      addAll(result, entries);
    }
    return result;
  }

  private class PollingRunnable implements Runnable {
    final CacheObserver cacheObserver =
        new CacheObserver() {
          @Override
          public void onCreate(final FileTreeDataViews.Entry newEntry) {
            observers.onNext(new Event(newEntry.getTypedPath(), Kind.Create));
          }

          @Override
          public void onDelete(final FileTreeDataViews.Entry oldEntry) {
            observers.onNext(new Event(oldEntry.getTypedPath(), Kind.Delete));
          }

          @Override
          public void onUpdate(
              final FileTreeDataViews.Entry oldEntry,
              FileTreeDataViews.Entry newEntry) {
            if (!oldEntry.getValue().equals(newEntry.getValue())) {
              observers.onNext(new Event(newEntry.getTypedPath(), Kind.Modify));
            }
          }

          @Override
          public void onError(final IOException exception) {
            observers.onError(exception);
          }
        };

    @Override
    public void run() {
      final Map> newEntries = getEntries();
      MapOps.diffDirectoryEntries(oldEntries, newEntries, cacheObserver);
      synchronized (this) {
        oldEntries = newEntries;
      }
    }
  }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy