/*
 * Copyright 2000-2017 JetBrains s.r.o.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.intellij.openapi.fileChooser.tree;

import com.intellij.openapi.Disposable;
import com.intellij.openapi.vfs.LocalFileSystem;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.openapi.vfs.VirtualFileSystem;
import com.intellij.openapi.vfs.newvfs.RefreshQueue;
import com.intellij.openapi.vfs.newvfs.RefreshSession;

import java.util.ArrayList;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.atomic.AtomicBoolean;

import static com.intellij.util.concurrency.AppExecutorUtil.createBoundedScheduledExecutorService;
import static java.util.concurrent.TimeUnit.SECONDS;

/**
 * This class is intended to refresh virtual files periodically.
 *
 * @author Sergey.Malenkov
 */
public class FileRefresher implements Disposable {
  private final ScheduledExecutorService executor = createBoundedScheduledExecutorService("FileRefresher", 1);
  private final boolean recursive;
  private final long delay;
  private final ArrayList<Object> watchers = new ArrayList<>();
  private final ArrayList<VirtualFile> files = new ArrayList<>();
  private final AtomicBoolean scheduled = new AtomicBoolean();
  private final AtomicBoolean launched = new AtomicBoolean();
  private final AtomicBoolean paused = new AtomicBoolean();
  private final AtomicBoolean disposed = new AtomicBoolean();
  private RefreshSession session; // synchronized by files

  /**
   * @param recursive {@code true} if files should be considered as roots
   * @param delay     an amount of seconds before refreshing files
   * @throws IllegalArgumentException if the specified delay is not positive
   */
  public FileRefresher(boolean recursive, long delay) {
    if (delay <= 0) throw new IllegalArgumentException("delay");
    this.recursive = recursive;
    this.delay = delay;
  }

  /**
   * @return {@code true} if files should be considered as roots
   */
  public boolean isRecursive() {
    return recursive;
  }

  /**
   * Starts watching the specified file, which was added before.
   *
   * @param file      a file to watch
   * @param recursive {@code true} if a file should be considered as root
   * @return an object that allows to stop watching the specified file
   */
  protected Object watch(VirtualFile file, boolean recursive) {
    VirtualFileSystem fs = file.getFileSystem();
    if (fs instanceof LocalFileSystem) {
      return LocalFileSystem.getInstance().addRootToWatch(file.getPath(), recursive);
    }
    return null;
  }

  /**
   * Stops watching file, which was added before.
   *
   * @param watcher an object that allows to stop watching a file
   */
  protected void unwatch(Object watcher) {
    if (watcher instanceof LocalFileSystem.WatchRequest) {
      LocalFileSystem.getInstance().removeWatchedRoot((LocalFileSystem.WatchRequest)watcher);
    }
  }

  /**
   * Registers the specified file to watch and to refresh.
   *
   * @param file a file to watch and to refresh
   */
  public final void register(VirtualFile file) {
    if (file != null && !disposed.get()) {
      Object watcher = watch(file, recursive);
      if (watcher != null) {
        synchronized (watchers) {
          watchers.add(watcher);
        }
      }
      synchronized (files) {
        files.add(file);
      }
      if (!paused.get()) schedule();
    }
  }

  /**
   * Pauses files refreshing.
   */
  public final void pause() {
    paused.set(true);
  }

  /**
   * Starts files refreshing immediately.
   * If files are refreshing now, it will be restarted when finished.
   */
  public final void start() {
    paused.set(false);
    launch();
  }

  private void schedule() {
    if (disposed.get() || scheduled.getAndSet(true)) return;
    synchronized (files) {
      if (session != null || files.isEmpty()) return;
    }
    executor.schedule(this::launch, delay, SECONDS);
  }

  private void launch() {
    if (disposed.get() || launched.getAndSet(true)) return;
    RefreshSession session;
    synchronized (files) {
      if (this.session != null || files.isEmpty()) return;
      session = RefreshQueue.getInstance().createSession(true, recursive, this::finish);
      session.addAllFiles(files);
      this.session = session;
    }
    scheduled.set(false);
    launched.set(false);
    session.launch();
  }

  private void finish() {
    synchronized (files) {
      session = null;
    }
    if (launched.getAndSet(false)) {
      launch();
    }
    else if (scheduled.getAndSet(false) || !paused.get()) {
      schedule();
    }
  }

  @Override
  public void dispose() {
    if (!disposed.getAndSet(true)) {
      synchronized (watchers) {
        watchers.forEach(this::unwatch);
        watchers.clear();
      }
      RefreshSession session;
      synchronized (files) {
        files.clear();
        session = this.session;
        this.session = null;
      }
      if (session != null) RefreshQueue.getInstance().cancelSession(session.getId());
    }
  }
}

@@ -678,24 +678,71 @@
 Disposable;%0A
+import com.intellij.openapi.diagnostic.Logger;%0A
 import com.i
@@ -1423,16 +1423,93 @@
 sable %7B%0A
+  private static final Logger LOG = Logger.getInstance(FileRefresher.class);%0A
   privat
@@ -3745,24 +3745,96 @@
 ed.get()) %7B%0A
+      LOG.debug(%22add file to watch recursive=%22, recursive, %22: %22, file);%0A
       Object
@@ -4160,24 +4160,48 @@
 d pause() %7B%0A
+    LOG.debug(%22pause%22);%0A
     paused.s
@@ -4365,24 +4365,48 @@
 d start() %7B%0A
+    LOG.debug(%22start%22);%0A
     paused.s
@@ -4459,24 +4459,51 @@
 chedule() %7B%0A
+    LOG.debug(%22schedule%22);%0A
     if (disp
@@ -4634,24 +4634,76 @@
 turn;%0A    %7D%0A
+    LOG.debug(%22scheduled for %22, delay, %22 seconds%22);%0A
     executor
@@ -4770,24 +4770,49 @@
  launch() %7B%0A
+    LOG.debug(%22launch%22);%0A
     if (disp
@@ -5191,56 +5191,174 @@
     
-session.launch();%0A  %7D%0A%0A  private void finish() %7B
+LOG.debug(%22launched at %22, System.currentTimeMillis());%0A    session.launch();%0A  %7D%0A%0A  private void finish() %7B%0A    LOG.debug(%22finished at %22, System.currentTimeMillis());
 %0A   
@@ -5591,24 +5591,50 @@
 dispose() %7B%0A
+    LOG.debug(%22dispose%22);%0A
     if (!dis

add logging