mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-09-27 10:03:11 +07:00
File watcher: fix subst mapping; remove symlink mapping; clean locks; test
This commit is contained in:
@@ -20,34 +20,32 @@ import com.intellij.notification.Notification;
|
||||
import com.intellij.notification.NotificationListener;
|
||||
import com.intellij.notification.NotificationType;
|
||||
import com.intellij.notification.Notifications;
|
||||
import com.intellij.openapi.application.Application;
|
||||
import com.intellij.openapi.application.ApplicationManager;
|
||||
import com.intellij.openapi.application.PathManager;
|
||||
import com.intellij.openapi.diagnostic.Logger;
|
||||
import com.intellij.openapi.util.Pair;
|
||||
import com.intellij.openapi.util.SystemInfo;
|
||||
import com.intellij.openapi.util.io.FileSystemUtil;
|
||||
import com.intellij.openapi.util.io.FileUtil;
|
||||
import com.intellij.openapi.vfs.VirtualFile;
|
||||
import com.intellij.openapi.vfs.newvfs.ManagingFS;
|
||||
import com.intellij.openapi.vfs.newvfs.NewVirtualFile;
|
||||
import com.intellij.openapi.vfs.watcher.ChangeKind;
|
||||
import com.intellij.util.PairFunction;
|
||||
import org.jetbrains.annotations.NonNls;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.jetbrains.annotations.TestOnly;
|
||||
|
||||
import javax.swing.event.HyperlinkEvent;
|
||||
import java.io.*;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.locks.Lock;
|
||||
import java.util.concurrent.locks.ReentrantLock;
|
||||
|
||||
/**
|
||||
* @author max
|
||||
*/
|
||||
public class FileWatcher {
|
||||
@NonNls public static final String PROPERTY_WATCHER_DISABLED = "filewatcher.disabled";
|
||||
@NonNls private static final String PROPERTY_WATCHER_EXECUTABLE_PATH = "idea.filewatcher.executable.path";
|
||||
@NonNls public static final String PROPERTY_WATCHER_DISABLED = "idea.filewatcher.disabled";
|
||||
@NonNls public static final String PROPERTY_WATCHER_EXECUTABLE_PATH = "idea.filewatcher.executable.path";
|
||||
|
||||
private static final Logger LOG = Logger.getInstance("#com.intellij.openapi.vfs.impl.local.FileWatcher");
|
||||
|
||||
@@ -59,15 +57,7 @@ public class FileWatcher {
|
||||
@NonNls private static final String EXIT_COMMAND = "EXIT";
|
||||
@NonNls private static final String MESSAGE_COMMAND = "MESSAGE";
|
||||
|
||||
private static final PairFunction<String,String,Boolean> PATH_COMPARATOR = new PairFunction<String, String, Boolean>() {
|
||||
@Override
|
||||
public Boolean fun(final String s1, final String s2) {
|
||||
return SystemInfo.isFileSystemCaseSensitive ? s1.equals(s2) : s1.equalsIgnoreCase(s2);
|
||||
}
|
||||
};
|
||||
|
||||
private final Object LOCK = new Object();
|
||||
private final Lock SET_ROOTS_LOCK = new ReentrantLock(true);
|
||||
|
||||
private List<String> myDirtyPaths = new ArrayList<String>();
|
||||
private List<String> myDirtyRecursivePaths = new ArrayList<String>();
|
||||
@@ -75,21 +65,23 @@ public class FileWatcher {
|
||||
private List<String> myManualWatchRoots = new ArrayList<String>();
|
||||
|
||||
private final List<Pair<String, String>> myMapping = new ArrayList<Pair<String, String>>();
|
||||
private List<Pair<String, String>> myCanonicalMapping = new ArrayList<Pair<String, String>>();
|
||||
|
||||
private List<String> myRecursiveWatchRoots = new ArrayList<String>();
|
||||
private List<String> myFlatWatchRoots = new ArrayList<String>();
|
||||
|
||||
private final Collection<String> myAllPaths = new ArrayList<String>(2);
|
||||
private final Collection<String> myWatchedPaths = new ArrayList<String>(2);
|
||||
|
||||
private File executable;
|
||||
private volatile Process notifierProcess;
|
||||
private volatile BufferedReader notifierReader;
|
||||
private volatile BufferedWriter notifierWriter;
|
||||
|
||||
private volatile BufferedWriter notifierWriter;
|
||||
private boolean myFailureShownToTheUser = false;
|
||||
private int attemptCount = 0;
|
||||
private static final int MAX_PROCESS_LAUNCH_ATTEMPT_COUNT = 10;
|
||||
private boolean isShuttingDown = false;
|
||||
private final ManagingFS myManagingFS;
|
||||
|
||||
private final ManagingFS myManagingFS;
|
||||
private static final FileWatcher ourInstance = new FileWatcher();
|
||||
|
||||
public static FileWatcher getInstance() {
|
||||
@@ -140,7 +132,6 @@ public class FileWatcher {
|
||||
myDirtyRecursivePaths = new ArrayList<String>();
|
||||
return result;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public List<String> getDirtyDirs() {
|
||||
@@ -158,26 +149,16 @@ public class FileWatcher {
|
||||
}
|
||||
|
||||
public void setWatchRoots(final List<String> recursive, final List<String> flat) {
|
||||
SET_ROOTS_LOCK.lock();
|
||||
try {
|
||||
synchronized (LOCK) {
|
||||
if (myRecursiveWatchRoots.equals(recursive) && myFlatWatchRoots.equals(flat)) return;
|
||||
}
|
||||
|
||||
final List<Pair<String, String>> mapping = new ArrayList<Pair<String, String>>();
|
||||
long t = System.nanoTime();
|
||||
final List<String> checkedRecursive = checkPaths(recursive, mapping);
|
||||
final List<String> checkedFlat = checkPaths(flat, mapping);
|
||||
t = (System.nanoTime() - t) / 1000;
|
||||
LOG.info((recursive.size() + flat.size()) + " paths checked, " + mapping.size() + " mapped, " + t + " mks");
|
||||
synchronized (LOCK) {
|
||||
if (myRecursiveWatchRoots.equals(recursive) && myFlatWatchRoots.equals(flat)) return;
|
||||
|
||||
if (isAlive()) {
|
||||
try {
|
||||
writeLine(ROOTS_COMMAND);
|
||||
for (String path : checkedRecursive) {
|
||||
for (String path : recursive) {
|
||||
writeLine(path);
|
||||
}
|
||||
for (String path : checkedFlat) {
|
||||
for (String path : flat) {
|
||||
writeLine("|" + path);
|
||||
}
|
||||
writeLine("#");
|
||||
@@ -187,37 +168,10 @@ public class FileWatcher {
|
||||
}
|
||||
}
|
||||
|
||||
synchronized (LOCK) {
|
||||
myRecursiveWatchRoots = recursive;
|
||||
myFlatWatchRoots = flat;
|
||||
myMapping.clear();
|
||||
myCanonicalMapping = mapping;
|
||||
}
|
||||
myRecursiveWatchRoots = recursive;
|
||||
myFlatWatchRoots = flat;
|
||||
myMapping.clear();
|
||||
}
|
||||
finally {
|
||||
SET_ROOTS_LOCK.unlock();
|
||||
}
|
||||
}
|
||||
|
||||
private static List<String> checkPaths(final List<String> paths, final List<Pair<String, String>> mapping) {
|
||||
if (!SystemInfo.areSymLinksSupported) return paths;
|
||||
|
||||
final List<String> checkedPaths = new ArrayList<String>(paths.size());
|
||||
for (String path : paths) {
|
||||
String watched = path;
|
||||
final String canonical = getCanonicalPath(path);
|
||||
//noinspection ConstantConditions
|
||||
if (!PATH_COMPARATOR.fun(path, canonical)) {
|
||||
mapping.add(Pair.create((watched = canonical), path));
|
||||
}
|
||||
checkedPaths.add(watched);
|
||||
}
|
||||
return checkedPaths;
|
||||
}
|
||||
|
||||
private static String getCanonicalPath(final String path) {
|
||||
final String realPath = FileSystemUtil.resolveSymLink(path);
|
||||
return realPath != null ? realPath : path;
|
||||
}
|
||||
|
||||
private boolean isAlive() {
|
||||
@@ -247,43 +201,34 @@ public class FileWatcher {
|
||||
|
||||
shutdownProcess();
|
||||
|
||||
String execPath = null;
|
||||
if (executable == null) {
|
||||
executable = getExecutable();
|
||||
|
||||
final String altExecPath = System.getProperty(PROPERTY_WATCHER_EXECUTABLE_PATH);
|
||||
if (altExecPath != null && new File(altExecPath).isFile()) {
|
||||
execPath = FileUtil.toSystemDependentName(altExecPath);
|
||||
}
|
||||
|
||||
if (execPath == null) {
|
||||
final String execName;
|
||||
execName = getExecutableName();
|
||||
if (execName == null) {
|
||||
if (executable == null) {
|
||||
myFailureShownToTheUser = true; // ignore unsupported platforms
|
||||
return;
|
||||
}
|
||||
execPath = PathManager.getBinPath() + File.separatorChar + execName;
|
||||
|
||||
if (!executable.exists()) {
|
||||
notifyOnFailure("File watcher is not found at path: " + executable, null);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!executable.canExecute()) {
|
||||
final String message = "File watcher is not executable: <a href=\"" + executable + "\">" + executable + "</a>";
|
||||
final File exec = executable;
|
||||
notifyOnFailure(message, new NotificationListener() {
|
||||
@Override
|
||||
public void hyperlinkUpdate(@NotNull Notification notification, @NotNull HyperlinkEvent event) {
|
||||
ShowFilePathAction.openFile(exec);
|
||||
}
|
||||
});
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
final File exec = new File(execPath);
|
||||
if (!exec.exists()) {
|
||||
notifyOnFailure("File watcher is not found at path: " + execPath, null);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!exec.canExecute()) {
|
||||
notifyOnFailure("File watcher is not executable: <a href=\"" + execPath + "\">" + execPath +"</a>", new NotificationListener() {
|
||||
@Override
|
||||
public void hyperlinkUpdate(@NotNull Notification notification, @NotNull HyperlinkEvent event) {
|
||||
ShowFilePathAction.openFile(exec);
|
||||
}
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
LOG.info("Starting file watcher: " + execPath);
|
||||
|
||||
notifierProcess = Runtime.getRuntime().exec(new String[]{execPath});
|
||||
|
||||
LOG.info("Starting file watcher: " + executable);
|
||||
notifierProcess = Runtime.getRuntime().exec(executable.getAbsolutePath());
|
||||
notifierReader = new BufferedReader(new InputStreamReader(notifierProcess.getInputStream()));
|
||||
notifierWriter = new BufferedWriter(new OutputStreamWriter(notifierProcess.getOutputStream()));
|
||||
|
||||
@@ -299,15 +244,43 @@ public class FileWatcher {
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private static String getExecutableName() {
|
||||
private static File getExecutable() {
|
||||
String execPath = null;
|
||||
|
||||
final String altExecPath = System.getProperty(PROPERTY_WATCHER_EXECUTABLE_PATH);
|
||||
if (altExecPath != null && new File(altExecPath).isFile()) {
|
||||
execPath = FileUtil.toSystemDependentName(altExecPath);
|
||||
}
|
||||
|
||||
if (execPath == null) {
|
||||
final String execName = getExecutableName(false);
|
||||
if (execName == null) {
|
||||
return null;
|
||||
}
|
||||
execPath = FileUtil.join(PathManager.getBinPath(), execName);
|
||||
}
|
||||
|
||||
File exec = new File(execPath);
|
||||
if (!exec.exists()) {
|
||||
String homePath = PathManager.getHomePath();
|
||||
if (new File(homePath, "community").exists()) {
|
||||
homePath += File.separator + "community";
|
||||
}
|
||||
exec = new File(FileUtil.join(homePath, "bin", getExecutableName(true)));
|
||||
}
|
||||
return exec;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private static String getExecutableName(final boolean withSubDir) {
|
||||
if (SystemInfo.isWindows) {
|
||||
return "fsnotifier.exe";
|
||||
return (withSubDir ? "win" + File.separator : "") + "fsnotifier.exe";
|
||||
}
|
||||
else if (SystemInfo.isMac) {
|
||||
return "fsnotifier";
|
||||
return (withSubDir ? "mac" + File.separator : "") + "fsnotifier";
|
||||
}
|
||||
else if (SystemInfo.isLinux) {
|
||||
return SystemInfo.isAMD64 ? "fsnotifier64" : "fsnotifier";
|
||||
return (withSubDir ? "linux" + File.separator : "") + (SystemInfo.isAMD64 ? "fsnotifier64" : "fsnotifier");
|
||||
}
|
||||
|
||||
return null;
|
||||
@@ -316,7 +289,8 @@ public class FileWatcher {
|
||||
private void notifyOnFailure(String cause, @Nullable NotificationListener listener) {
|
||||
if (!myFailureShownToTheUser) {
|
||||
myFailureShownToTheUser = true;
|
||||
Notifications.Bus.notify(new Notification(Notifications.SYSTEM_MESSAGES_GROUP_ID, "External file sync may be slow", cause, NotificationType.WARNING, listener));
|
||||
Notifications.Bus.notify(new Notification(Notifications.SYSTEM_MESSAGES_GROUP_ID, "External file sync may be slow",
|
||||
cause, NotificationType.WARNING, listener));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -339,6 +313,32 @@ public class FileWatcher {
|
||||
return notifierProcess != null;
|
||||
}
|
||||
|
||||
@TestOnly
|
||||
public void startup() throws IOException {
|
||||
final Application app = ApplicationManager.getApplication();
|
||||
assert app != null && app.isUnitTestMode() : app;
|
||||
|
||||
myFailureShownToTheUser = true;
|
||||
attemptCount = 0;
|
||||
startupProcess(false);
|
||||
attemptCount = 2 * MAX_PROCESS_LAUNCH_ATTEMPT_COUNT;
|
||||
if (notifierProcess != null) {
|
||||
new WatchForChangesThread().start();
|
||||
}
|
||||
}
|
||||
|
||||
@TestOnly
|
||||
public void shutdown() throws InterruptedException {
|
||||
final Application app = ApplicationManager.getApplication();
|
||||
assert app != null && app.isUnitTestMode() : app;
|
||||
|
||||
final Process process = notifierProcess;
|
||||
if (process != null) {
|
||||
shutdownProcess();
|
||||
process.waitFor();
|
||||
}
|
||||
}
|
||||
|
||||
private class WatchForChangesThread extends Thread {
|
||||
public WatchForChangesThread() {
|
||||
//noinspection HardCodedStringLiteral
|
||||
@@ -396,7 +396,7 @@ public class FileWatcher {
|
||||
final String pathB = readLine();
|
||||
if (pathB == null || "#".equals(pathB)) break;
|
||||
|
||||
pairs.add(new Pair<String, String>(ensureEndsWithSlash(pathA), ensureEndsWithSlash(pathB)));
|
||||
pairs.add(Pair.create(preparePathForMapping(pathA), preparePathForMapping(pathB)));
|
||||
}
|
||||
while (true);
|
||||
|
||||
@@ -414,10 +414,10 @@ public class FileWatcher {
|
||||
}
|
||||
|
||||
synchronized (LOCK) {
|
||||
final String watchedPath = checkWatchable(path);
|
||||
if (watchedPath != null) {
|
||||
final Collection<String> watchedPaths = checkWatchable(path);
|
||||
if (!watchedPaths.isEmpty()) {
|
||||
try {
|
||||
onPathChange(ChangeKind.valueOf(command), watchedPath);
|
||||
onPathChange(ChangeKind.valueOf(command), watchedPaths);
|
||||
}
|
||||
catch (IllegalArgumentException e) {
|
||||
LOG.error("Illegal watcher command: " + command);
|
||||
@@ -438,9 +438,9 @@ public class FileWatcher {
|
||||
}
|
||||
}
|
||||
|
||||
private static String ensureEndsWithSlash(String path) {
|
||||
if (path.endsWith("/") || path.endsWith(File.separator)) return path;
|
||||
return path + '/';
|
||||
private static String preparePathForMapping(final String path) {
|
||||
final String localPath = FileUtil.toSystemDependentName(path);
|
||||
return localPath.endsWith(File.separator) ? localPath : localPath + File.separator;
|
||||
}
|
||||
|
||||
private void writeLine(String line) throws IOException {
|
||||
@@ -486,64 +486,76 @@ public class FileWatcher {
|
||||
return line;
|
||||
}
|
||||
|
||||
public boolean isWatched(VirtualFile file) {
|
||||
return isOperational() && checkWatchable(file.getPresentableUrl()) != null;
|
||||
public boolean isWatched(@NotNull final VirtualFile file) {
|
||||
if (isOperational()) {
|
||||
synchronized (LOCK) {
|
||||
return !checkWatchable(file.getPresentableUrl()).isEmpty();
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private String checkWatchable(String path) {
|
||||
if (path == null) return null;
|
||||
@NotNull
|
||||
private Collection<String> checkWatchable(final String reportedPath) {
|
||||
if (reportedPath == null) return Collections.emptyList();
|
||||
|
||||
for (Pair<String, String> mapping : myCanonicalMapping) {
|
||||
if (path.startsWith(mapping.first)) {
|
||||
path = mapping.second + path.substring(mapping.first.length());
|
||||
break;
|
||||
myAllPaths.clear();
|
||||
myAllPaths.add(reportedPath);
|
||||
for (Pair<String, String> map : myMapping) {
|
||||
if (FileUtil.startsWith(reportedPath, map.first)) {
|
||||
myAllPaths.add(map.second + reportedPath.substring(map.first.length()));
|
||||
}
|
||||
else if (FileUtil.startsWith(reportedPath, map.second)) {
|
||||
myAllPaths.add(map.first + reportedPath.substring(map.second.length()));
|
||||
}
|
||||
}
|
||||
|
||||
for (String root : myRecursiveWatchRoots) {
|
||||
if (FileUtil.startsWith(path, root)) {
|
||||
return path;
|
||||
myWatchedPaths.clear();
|
||||
ext:
|
||||
for (String path : myAllPaths) {
|
||||
for (String root : myRecursiveWatchRoots) {
|
||||
if (FileUtil.startsWith(path, root)) {
|
||||
myWatchedPaths.add(path);
|
||||
continue ext;
|
||||
}
|
||||
}
|
||||
|
||||
for (String root : myFlatWatchRoots) {
|
||||
if (FileUtil.pathsEqual(path, root)) {
|
||||
myWatchedPaths.add(path);
|
||||
continue ext;
|
||||
}
|
||||
final File parentFile = new File(path).getParentFile();
|
||||
if (parentFile != null && FileUtil.pathsEqual(parentFile.getPath(), root)) {
|
||||
myWatchedPaths.add(path);
|
||||
continue ext;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (String root : myFlatWatchRoots) {
|
||||
if (FileUtil.pathsEqual(path, root)) {
|
||||
return path;
|
||||
}
|
||||
final File parentFile = new File(path).getParentFile();
|
||||
if (parentFile != null && FileUtil.pathsEqual(parentFile.getPath(), root)) {
|
||||
return path;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
return myWatchedPaths;
|
||||
}
|
||||
|
||||
private void onPathChange(final ChangeKind changeKind, final String path) {
|
||||
private void onPathChange(final ChangeKind changeKind, final Collection<String> paths) {
|
||||
switch (changeKind) {
|
||||
case STATS:
|
||||
case CHANGE:
|
||||
addPath(path, myDirtyPaths);
|
||||
myDirtyPaths.addAll(paths);
|
||||
break;
|
||||
|
||||
case CREATE:
|
||||
case DELETE:
|
||||
final File parentFile = new File(path).getParentFile();
|
||||
if (parentFile != null) {
|
||||
addPath(parentFile.getPath(), myDirtyPaths);
|
||||
}
|
||||
else {
|
||||
addPath(path, myDirtyPaths);
|
||||
for (String path : paths) {
|
||||
final File parent = new File(path).getParentFile();
|
||||
myDirtyPaths.add(parent != null ? parent.getPath() : path);
|
||||
}
|
||||
break;
|
||||
|
||||
case DIRTY:
|
||||
addPath(path, myDirtyDirs);
|
||||
myDirtyDirs.addAll(paths);
|
||||
break;
|
||||
|
||||
case RECDIRTY:
|
||||
addPath(path, myDirtyRecursivePaths);
|
||||
myDirtyRecursivePaths.addAll(paths);
|
||||
break;
|
||||
|
||||
case RESET:
|
||||
@@ -552,19 +564,6 @@ public class FileWatcher {
|
||||
}
|
||||
}
|
||||
|
||||
private void addPath(String path, List<String> list) {
|
||||
list.add(path);
|
||||
|
||||
for (Pair<String, String> map : myMapping) {
|
||||
if (FileUtil.startsWith(path, map.getFirst())) {
|
||||
list.add(map.getSecond() + path.substring(map.getFirst().length()));
|
||||
}
|
||||
else if (FileUtil.startsWith(path, map.getSecond())) {
|
||||
list.add(map.getFirst() + path.substring(map.getSecond().length()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void reset() {
|
||||
synchronized (LOCK) {
|
||||
myDirtyPaths.clear();
|
||||
|
||||
@@ -0,0 +1,438 @@
|
||||
/*
|
||||
* Copyright 2000-2012 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.vfs.local;
|
||||
|
||||
import com.intellij.execution.configurations.GeneralCommandLine;
|
||||
import com.intellij.idea.Bombed;
|
||||
import com.intellij.openapi.application.ApplicationManager;
|
||||
import com.intellij.openapi.util.SystemInfo;
|
||||
import com.intellij.openapi.util.io.FileUtil;
|
||||
import com.intellij.openapi.vfs.*;
|
||||
import com.intellij.openapi.vfs.impl.local.FileWatcher;
|
||||
import com.intellij.openapi.vfs.newvfs.BulkFileListener;
|
||||
import com.intellij.openapi.vfs.newvfs.events.VFileContentChangeEvent;
|
||||
import com.intellij.openapi.vfs.newvfs.events.VFileCreateEvent;
|
||||
import com.intellij.openapi.vfs.newvfs.events.VFileDeleteEvent;
|
||||
import com.intellij.openapi.vfs.newvfs.events.VFileEvent;
|
||||
import com.intellij.testFramework.PlatformLangTestCase;
|
||||
import com.intellij.util.Function;
|
||||
import com.intellij.util.TimeoutUtil;
|
||||
import com.intellij.util.containers.ContainerUtil;
|
||||
import com.intellij.util.messages.MessageBusConnection;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.*;
|
||||
|
||||
public class FileWatcherTest extends PlatformLangTestCase {
|
||||
private static final int NATIVE_PROCESS_DELAY = 500; // time to event to be caught by native watcher and passed to watcher thread
|
||||
|
||||
private FileWatcher myWatcher;
|
||||
private LocalFileSystem myFileSystem;
|
||||
private MessageBusConnection myConnection;
|
||||
private final List<VFileEvent> myEvents = new ArrayList<VFileEvent>();
|
||||
|
||||
@Override
|
||||
protected void setUp() throws Exception {
|
||||
super.setUp();
|
||||
|
||||
myWatcher = FileWatcher.getInstance();
|
||||
assertNotNull(myWatcher);
|
||||
assertFalse(myWatcher.isOperational());
|
||||
myWatcher.startup();
|
||||
assertTrue(myWatcher.isOperational());
|
||||
|
||||
myFileSystem = LocalFileSystem.getInstance();
|
||||
assertNotNull(myFileSystem);
|
||||
|
||||
myConnection = ApplicationManager.getApplication().getMessageBus().connect();
|
||||
myConnection.subscribe(VirtualFileManager.VFS_CHANGES, new BulkFileListener() {
|
||||
@Override
|
||||
public void before(@NotNull List<? extends VFileEvent> events) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void after(@NotNull List<? extends VFileEvent> events) {
|
||||
synchronized (myEvents) {
|
||||
myEvents.addAll(events);
|
||||
myEvents.notifyAll();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void tearDown() throws Exception {
|
||||
try {
|
||||
myConnection.disconnect();
|
||||
myWatcher.shutdown();
|
||||
}
|
||||
finally {
|
||||
myFileSystem = null;
|
||||
myWatcher = null;
|
||||
super.tearDown();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public void testFileRoot() throws Exception {
|
||||
final File file = FileUtil.createTempFile("test.", ".txt");
|
||||
refresh(file);
|
||||
final LocalFileSystem.WatchRequest request = watch(file);
|
||||
try {
|
||||
FileUtil.writeToFile(file, "new content");
|
||||
assertEvent(VFileContentChangeEvent.class, file.getAbsolutePath());
|
||||
|
||||
FileUtil.delete(file);
|
||||
assertEvent(VFileDeleteEvent.class, file.getAbsolutePath());
|
||||
|
||||
FileUtil.writeToFile(file, "re-creation");
|
||||
assertEvent(VFileCreateEvent.class, file.getAbsolutePath());
|
||||
}
|
||||
finally {
|
||||
myFileSystem.removeWatchedRoot(request);
|
||||
FileUtil.delete(file);
|
||||
}
|
||||
}
|
||||
|
||||
public void testNonCanonicallyNamedFileRoot() throws Exception {
|
||||
if (SystemInfo.isFileSystemCaseSensitive) {
|
||||
System.out.println("Ignored: case-insensitive FS required");
|
||||
return;
|
||||
}
|
||||
|
||||
final File file = FileUtil.createTempFile("test.", ".txt");
|
||||
refresh(file);
|
||||
|
||||
final String watchRoot = file.getAbsolutePath().toUpperCase(Locale.US);
|
||||
final LocalFileSystem.WatchRequest request = watch(new File(watchRoot));
|
||||
try {
|
||||
FileUtil.writeToFile(file, "new content");
|
||||
assertEvent(VFileContentChangeEvent.class, file.getAbsolutePath());
|
||||
|
||||
FileUtil.delete(file);
|
||||
assertEvent(VFileDeleteEvent.class, file.getAbsolutePath());
|
||||
|
||||
FileUtil.writeToFile(file, "re-creation");
|
||||
assertEvent(VFileCreateEvent.class, file.getAbsolutePath());
|
||||
}
|
||||
finally {
|
||||
myFileSystem.removeWatchedRoot(request);
|
||||
FileUtil.delete(file);
|
||||
}
|
||||
}
|
||||
|
||||
public void testDirectoryRecursive() throws Exception {
|
||||
final File topDir = FileUtil.createTempDirectory("top.", null);
|
||||
refresh(topDir);
|
||||
|
||||
final LocalFileSystem.WatchRequest request = watch(topDir);
|
||||
try {
|
||||
final File subDir = FileUtil.createTempDirectory(topDir, "sub.", null);
|
||||
assertEvent(VFileCreateEvent.class, subDir.getAbsolutePath());
|
||||
refresh(subDir);
|
||||
|
||||
final File file = FileUtil.createTempFile(subDir, "test.", ".txt", true, false);
|
||||
assertEvent(VFileCreateEvent.class, file.getAbsolutePath());
|
||||
|
||||
FileUtil.writeToFile(file, "new content");
|
||||
assertEvent(VFileContentChangeEvent.class, file.getAbsolutePath());
|
||||
|
||||
FileUtil.delete(file);
|
||||
assertEvent(VFileDeleteEvent.class, file.getAbsolutePath());
|
||||
|
||||
FileUtil.writeToFile(file, "re-creation");
|
||||
assertEvent(VFileCreateEvent.class, file.getAbsolutePath());
|
||||
}
|
||||
finally {
|
||||
myFileSystem.removeWatchedRoot(request);
|
||||
FileUtil.delete(topDir);
|
||||
}
|
||||
}
|
||||
|
||||
public void testDirectoryFlat() throws Exception {
|
||||
final File topDir = FileUtil.createTempDirectory("top.", null);
|
||||
final File watchedFile = FileUtil.createTempFile(topDir, "test.", ".txt", true, false);
|
||||
final File subDir = FileUtil.createTempDirectory(topDir, "sub.", null);
|
||||
final File unwatchedFile = FileUtil.createTempFile(subDir, "test.", ".txt", true, false);
|
||||
refresh(topDir);
|
||||
|
||||
final LocalFileSystem.WatchRequest request = watch(topDir, false);
|
||||
try {
|
||||
FileUtil.writeToFile(watchedFile, "new content");
|
||||
assertEvent(VFileContentChangeEvent.class, watchedFile.getAbsolutePath());
|
||||
|
||||
FileUtil.writeToFile(unwatchedFile, "new content");
|
||||
assertEvent(VFileEvent.class);
|
||||
}
|
||||
finally {
|
||||
myFileSystem.removeWatchedRoot(request);
|
||||
FileUtil.delete(topDir);
|
||||
}
|
||||
}
|
||||
|
||||
public void testDirectoryNonExisting() throws Exception {
|
||||
final File topDir = FileUtil.createTempDirectory("top.", null);
|
||||
final File subDir = new File(topDir, "subDir");
|
||||
final File file = new File(subDir, "file.txt");
|
||||
refresh(topDir);
|
||||
|
||||
final LocalFileSystem.WatchRequest request = watch(subDir);
|
||||
try {
|
||||
assertTrue(subDir.toString(), subDir.mkdir());
|
||||
assertEvent(VFileCreateEvent.class, subDir.getAbsolutePath());
|
||||
refresh(subDir);
|
||||
|
||||
FileUtil.writeToFile(file, "new content");
|
||||
assertEvent(VFileCreateEvent.class, file.getAbsolutePath());
|
||||
}
|
||||
finally {
|
||||
myFileSystem.removeWatchedRoot(request);
|
||||
FileUtil.delete(topDir);
|
||||
}
|
||||
}
|
||||
|
||||
public void testDirectoryOverlapping() throws Exception {
|
||||
final File topDir = FileUtil.createTempDirectory("top.", null);
|
||||
final File file1 = FileUtil.createTempFile(topDir, "file1.", ".txt", true, false);
|
||||
final File subDir = FileUtil.createTempDirectory(topDir, "sub.", null);
|
||||
final File file2 = FileUtil.createTempFile(subDir, "file2.", ".txt", true, false);
|
||||
final File sideDir = FileUtil.createTempDirectory("side.", null);
|
||||
final File file3 = FileUtil.createTempFile(sideDir, "file3.", ".txt", true, false);
|
||||
refresh(topDir);
|
||||
refresh(sideDir);
|
||||
|
||||
final LocalFileSystem.WatchRequest request1 = watch(subDir);
|
||||
final LocalFileSystem.WatchRequest request2 = watch(sideDir);
|
||||
try {
|
||||
FileUtil.writeToFile(file1, "new content");
|
||||
FileUtil.writeToFile(file2, "new content");
|
||||
FileUtil.writeToFile(file3, "new content");
|
||||
assertEvent(VFileContentChangeEvent.class, file2.getAbsolutePath(), file3.getAbsolutePath());
|
||||
|
||||
final LocalFileSystem.WatchRequest request3 = watch(topDir);
|
||||
try {
|
||||
FileUtil.writeToFile(file1, "newer content");
|
||||
FileUtil.writeToFile(file2, "newer content");
|
||||
FileUtil.writeToFile(file3, "newer content");
|
||||
assertEvent(VFileContentChangeEvent.class, file1.getAbsolutePath(), file2.getAbsolutePath(), file3.getAbsolutePath());
|
||||
}
|
||||
finally {
|
||||
unwatch(request3);
|
||||
}
|
||||
|
||||
FileUtil.writeToFile(file1, "newest content");
|
||||
FileUtil.writeToFile(file2, "newest content");
|
||||
FileUtil.writeToFile(file3, "newest content");
|
||||
assertEvent(VFileContentChangeEvent.class, file2.getAbsolutePath(), file3.getAbsolutePath());
|
||||
|
||||
FileUtil.delete(file1);
|
||||
FileUtil.delete(file2);
|
||||
FileUtil.delete(file3);
|
||||
assertEvent(VFileDeleteEvent.class, file1.getAbsolutePath(), file2.getAbsolutePath(), file3.getAbsolutePath());
|
||||
}
|
||||
finally {
|
||||
myFileSystem.removeWatchedRoots(Arrays.asList(request1, request2));
|
||||
FileUtil.delete(topDir);
|
||||
}
|
||||
}
|
||||
|
||||
@Bombed(user = "roman.shevchenko", year = 2012, month = Calendar.MAY, day = 1)
|
||||
public void testSymlinkAboveWatchRoot() throws Exception {
|
||||
final File topDir = FileUtil.createTempDirectory("top.", null);
|
||||
final File topLink = SymlinkHandlingTest.createTempLink(topDir.getAbsolutePath(), "link");
|
||||
final File subDir = FileUtil.createTempDirectory(topDir, "sub.", null);
|
||||
final File file = FileUtil.createTempFile(subDir, "test.", ".txt", true, false);
|
||||
final File fileLink = new File(new File(topLink, subDir.getName()), file.getName());
|
||||
refresh(topDir);
|
||||
refresh(topLink);
|
||||
|
||||
final LocalFileSystem.WatchRequest request = watch(topLink);
|
||||
try {
|
||||
FileUtil.writeToFile(file, "new content");
|
||||
assertEvent(VFileContentChangeEvent.class, fileLink.getAbsolutePath());
|
||||
|
||||
FileUtil.delete(file);
|
||||
assertEvent(VFileDeleteEvent.class, fileLink.getAbsolutePath());
|
||||
|
||||
FileUtil.writeToFile(file, "re-creation");
|
||||
assertEvent(VFileCreateEvent.class, fileLink.getAbsolutePath());
|
||||
}
|
||||
finally {
|
||||
myFileSystem.removeWatchedRoot(request);
|
||||
FileUtil.delete(topLink);
|
||||
FileUtil.delete(topDir);
|
||||
}
|
||||
}
|
||||
|
||||
@Bombed(user = "roman.shevchenko", year = 2012, month = Calendar.MAY, day = 1)
|
||||
public void testSymlinkBelowWatchRoot() throws Exception {
|
||||
final File targetDir = FileUtil.createTempDirectory("top.", null);
|
||||
final File file = FileUtil.createTempFile(targetDir, "test.", ".txt", true, false);
|
||||
final File linkDir = FileUtil.createTempDirectory("link.", null);
|
||||
final File link = new File(linkDir, "link");
|
||||
SymlinkHandlingTest.createTempLink(targetDir.getAbsolutePath(), link.getAbsolutePath());
|
||||
final File fileLink = new File(link, file.getName());
|
||||
refresh(targetDir);
|
||||
refresh(linkDir);
|
||||
|
||||
final LocalFileSystem.WatchRequest request = watch(linkDir);
|
||||
try {
|
||||
FileUtil.writeToFile(file, "new content");
|
||||
assertEvent(VFileContentChangeEvent.class, fileLink.getAbsolutePath());
|
||||
|
||||
FileUtil.delete(file);
|
||||
assertEvent(VFileDeleteEvent.class, fileLink.getAbsolutePath());
|
||||
|
||||
FileUtil.writeToFile(file, "re-creation");
|
||||
assertEvent(VFileCreateEvent.class, fileLink.getAbsolutePath());
|
||||
}
|
||||
finally {
|
||||
myFileSystem.removeWatchedRoot(request);
|
||||
FileUtil.delete(linkDir);
|
||||
FileUtil.delete(targetDir);
|
||||
}
|
||||
}
|
||||
|
||||
public void testSubst() throws Exception {
|
||||
if (!SystemInfo.isWindows) {
|
||||
System.out.println("Ignored: Windows required");
|
||||
return;
|
||||
}
|
||||
|
||||
final Set<Character> roots = ContainerUtil.map2Set(File.listRoots(), new Function<File, Character>() {
|
||||
@Override
|
||||
public Character fun(File root) {
|
||||
return root.getPath().toLowerCase(Locale.US).charAt(0);
|
||||
}
|
||||
});
|
||||
char subst = 0;
|
||||
for (char c = 'e'; c <= 'z'; c++) {
|
||||
if (!roots.contains(c)) {
|
||||
subst = c;
|
||||
break;
|
||||
}
|
||||
}
|
||||
assertFalse("Occupied: " + roots.toString(), subst == 0);
|
||||
|
||||
final File targetDir = FileUtil.createTempDirectory("top.", null);
|
||||
final File subDir = FileUtil.createTempDirectory(targetDir, "sub.", null);
|
||||
final File file = FileUtil.createTempFile(subDir, "test.", ".txt", true, false);
|
||||
final int rv = new GeneralCommandLine("subst", subst + ":", targetDir.getAbsolutePath()).createProcess().waitFor();
|
||||
assertEquals(0, rv);
|
||||
|
||||
final File substDir = new File((subst + ":\\").toUpperCase(Locale.US), subDir.getName());
|
||||
final File substFile = new File(substDir, file.getName());
|
||||
refresh(targetDir);
|
||||
refresh(substDir);
|
||||
|
||||
final LocalFileSystem.WatchRequest request = watch(substDir);
|
||||
try {
|
||||
FileUtil.writeToFile(file, "new content");
|
||||
assertEvent(VFileContentChangeEvent.class, substFile.getAbsolutePath());
|
||||
|
||||
final LocalFileSystem.WatchRequest request2 = watch(targetDir);
|
||||
try {
|
||||
FileUtil.delete(file);
|
||||
assertEvent(VFileDeleteEvent.class, file.getAbsolutePath(), substFile.getAbsolutePath());
|
||||
}
|
||||
finally {
|
||||
unwatch(request2);
|
||||
}
|
||||
|
||||
FileUtil.writeToFile(file, "re-creation");
|
||||
assertEvent(VFileCreateEvent.class, substFile.getAbsolutePath());
|
||||
}
|
||||
finally {
|
||||
myFileSystem.removeWatchedRoot(request);
|
||||
new GeneralCommandLine("subst", subst + ":", "/d").createProcess().waitFor();
|
||||
FileUtil.delete(targetDir);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private List<VFileEvent> getEvents() throws InterruptedException {
|
||||
TimeoutUtil.sleep(NATIVE_PROCESS_DELAY);
|
||||
myFileSystem.refresh(false);
|
||||
|
||||
synchronized (myEvents) {
|
||||
final ArrayList<VFileEvent> result = new ArrayList<VFileEvent>(myEvents);
|
||||
myEvents.clear();
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
private void clearEvents() {
|
||||
myFileSystem.refresh(false);
|
||||
synchronized (myEvents) {
|
||||
myEvents.clear();
|
||||
}
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private LocalFileSystem.WatchRequest watch(final File watchFile) throws InterruptedException {
|
||||
return watch(watchFile, true);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private LocalFileSystem.WatchRequest watch(final File watchFile, final boolean recursive) throws InterruptedException {
|
||||
final LocalFileSystem.WatchRequest request = myFileSystem.addRootToWatch(watchFile.getAbsolutePath(), recursive);
|
||||
assertNotNull(request);
|
||||
TimeoutUtil.sleep(NATIVE_PROCESS_DELAY);
|
||||
clearEvents();
|
||||
return request;
|
||||
}
|
||||
|
||||
private void unwatch(final LocalFileSystem.WatchRequest request) throws InterruptedException {
|
||||
myFileSystem.removeWatchedRoot(request);
|
||||
TimeoutUtil.sleep(NATIVE_PROCESS_DELAY);
|
||||
clearEvents();
|
||||
}
|
||||
|
||||
private VirtualFile refresh(final File file) {
|
||||
final VirtualFile vFile = myFileSystem.refreshAndFindFileByIoFile(file);
|
||||
assertNotNull(file.toString(), vFile);
|
||||
VfsUtilCore.visitChildrenRecursively(vFile, new VirtualFileVisitor() {
|
||||
@Override
|
||||
public boolean visitFile(@NotNull VirtualFile file) {
|
||||
file.getChildren();
|
||||
return true;
|
||||
}
|
||||
});
|
||||
return vFile;
|
||||
}
|
||||
|
||||
private void assertEvent(final Class<? extends VFileEvent> type, final String... paths) throws InterruptedException {
|
||||
final List<VFileEvent> events = getEvents();
|
||||
assertEquals(events.toString(), paths.length, events.size());
|
||||
|
||||
final Set<String> pathSet = ContainerUtil.map2Set(paths, new Function<String, String>() {
|
||||
@Override
|
||||
public String fun(final String path) {
|
||||
return FileUtil.toSystemIndependentName(path);
|
||||
}
|
||||
});
|
||||
|
||||
for (final VFileEvent event : events) {
|
||||
assertTrue(event.toString(), type.isInstance(event));
|
||||
|
||||
final VirtualFile eventFile = event.getFile();
|
||||
assertNotNull(event.toString(), eventFile);
|
||||
|
||||
assertTrue(eventFile + " not in " + Arrays.toString(paths), pathSet.remove(eventFile.getPath()));
|
||||
}
|
||||
}
|
||||
}
|
||||
+1
-1
@@ -266,7 +266,7 @@ public class SymlinkHandlingTest extends LightPlatformLangTestCase {
|
||||
}
|
||||
|
||||
// todo[r.sh] use NIO2 API after migration to JDK 7
|
||||
private static File createTempLink(final String target, final String link) throws InterruptedException, ExecutionException {
|
||||
public static File createTempLink(final String target, final String link) throws InterruptedException, ExecutionException {
|
||||
final boolean isAbsolute = SystemInfo.isUnix && StringUtil.startsWithChar(link, '/') ||
|
||||
SystemInfo.isWindows && link.matches("^[c-zC-Z]:[/\\\\].*$");
|
||||
final File linkFile = isAbsolute ? new File(link) : new File(FileUtil.getTempDirectory(), link);
|
||||
|
||||
Reference in New Issue
Block a user