Win32 filesystem
[fedora-idea.git] / platform / platform-impl / src / com / intellij / openapi / vfs / impl / local / LocalFileSystemImpl.java
blob31c2208ee8c8bc8a92150ae1df5b38badd5a7b27
1 /*
2 * Copyright 2000-2009 JetBrains s.r.o.
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
8 * http://www.apache.org/licenses/LICENSE-2.0
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
16 package com.intellij.openapi.vfs.impl.local;
18 import com.intellij.openapi.application.Application;
19 import com.intellij.openapi.application.ApplicationManager;
20 import com.intellij.openapi.components.ApplicationComponent;
21 import com.intellij.openapi.fileEditor.FileDocumentManager;
22 import com.intellij.openapi.util.SystemInfo;
23 import com.intellij.openapi.util.io.FileUtil;
24 import com.intellij.openapi.vfs.JarFileSystem;
25 import com.intellij.openapi.vfs.VirtualFile;
26 import com.intellij.openapi.vfs.newvfs.ManagingFS;
27 import com.intellij.openapi.vfs.newvfs.NewVirtualFile;
28 import com.intellij.openapi.vfs.newvfs.RefreshQueue;
29 import com.intellij.openapi.vfs.newvfs.impl.VirtualDirectoryImpl;
30 import com.intellij.openapi.vfs.newvfs.persistent.PersistentFS;
31 import com.intellij.util.concurrency.JBLock;
32 import com.intellij.util.concurrency.JBReentrantReadWriteLock;
33 import com.intellij.util.concurrency.LockFactory;
34 import com.intellij.util.containers.HashSet;
35 import org.jetbrains.annotations.NonNls;
36 import org.jetbrains.annotations.NotNull;
37 import org.jetbrains.annotations.TestOnly;
39 import java.io.File;
40 import java.io.IOException;
41 import java.util.*;
43 public final class LocalFileSystemImpl extends LocalFileSystemBase implements ApplicationComponent {
45 private final JBReentrantReadWriteLock LOCK = LockFactory.createReadWriteLock();
46 final JBLock READ_LOCK = LOCK.readLock();
47 final JBLock WRITE_LOCK = LOCK.writeLock();
49 private final List<WatchRequest> myRootsToWatch = new ArrayList<WatchRequest>();
50 private WatchRequest[] myCachedNormalizedRequests = null;
52 private final FileWatcher myWatcher;
54 private static class WatchRequestImpl implements WatchRequest {
55 public final String myRootPath;
57 public String myFSRootPath;
58 public final boolean myToWatchRecursively;
60 public WatchRequestImpl(String rootPath, final boolean toWatchRecursively) {
61 myToWatchRecursively = toWatchRecursively;
62 final int index = rootPath.indexOf(JarFileSystem.JAR_SEPARATOR);
63 if (index >= 0) rootPath = rootPath.substring(0, index);
64 final File file = new File(rootPath.replace('/', File.separatorChar));
65 if (!file.isDirectory()) {
66 final File parentFile = file.getParentFile();
67 if (parentFile != null) {
68 if (SystemInfo.isFileSystemCaseSensitive) {
69 myFSRootPath = parentFile.getAbsolutePath(); // fixes problem with symlinks under Unix (however does not under Windows!)
71 else {
72 try {
73 myFSRootPath = parentFile.getCanonicalPath();
75 catch (IOException e) {
76 myFSRootPath = rootPath; //need something
80 else {
81 myFSRootPath = rootPath.replace('/', File.separatorChar);
84 myRootPath = myFSRootPath.replace(File.separatorChar, '/');
86 else {
87 myRootPath = rootPath.replace(File.separatorChar, '/');
88 myFSRootPath = rootPath.replace('/', File.separatorChar);
92 @NotNull
93 public String getRootPath() {
94 return myRootPath;
97 @NotNull
98 public String getFileSystemRootPath() {
99 return myFSRootPath;
102 public boolean isToWatchRecursively() {
103 return myToWatchRecursively;
106 public boolean dominates(WatchRequest other) {
107 if (myToWatchRecursively) {
108 return other.getRootPath().startsWith(myRootPath);
111 return !other.isToWatchRecursively() && myRootPath.equals(other.getRootPath());
115 public LocalFileSystemImpl() {
116 myWatcher = FileWatcher.getInstance();
117 if (myWatcher.isOperational()) {
118 new StoreRefreshStatusThread().start();
122 public void initComponent() {
125 public void disposeComponent() {
128 @TestOnly
129 public void cleanupForNextTest() throws IOException {
130 ApplicationManager.getApplication().runWriteAction(new Runnable() {
131 public void run() {
132 FileDocumentManager.getInstance().saveAllDocuments();
133 refresh(false);
136 ((PersistentFS)ManagingFS.getInstance()).clearIdCache();
138 final VirtualFile[] roots = ManagingFS.getInstance().getRoots(this);
139 for (VirtualFile root : roots) {
140 if (root instanceof VirtualDirectoryImpl) {
141 final VirtualDirectoryImpl directory = (VirtualDirectoryImpl)root;
142 directory.cleanupCachedChildren();
146 myRootsToWatch.clear();
148 final File file = new File(FileUtil.getTempDirectory());
149 String path = file.getCanonicalPath().replace(File.separatorChar, '/');
150 addRootToWatch(path, true);
153 private WatchRequest[] normalizeRootsForRefresh() {
154 if (myCachedNormalizedRequests != null) return myCachedNormalizedRequests;
155 List<WatchRequest> result = new ArrayList<WatchRequest>();
156 WRITE_LOCK.lock();
157 try {
158 NextRoot:
159 for (WatchRequest request : myRootsToWatch) {
160 String rootPath = request.getRootPath();
161 boolean recursively = request.isToWatchRecursively();
163 for (Iterator<WatchRequest> iterator1 = result.iterator(); iterator1.hasNext();) {
164 final WatchRequest otherRequest = iterator1.next();
165 final String otherRootPath = otherRequest.getRootPath();
166 final boolean otherRecursively = otherRequest.isToWatchRecursively();
167 if ((rootPath.equals(otherRootPath) && (!recursively || otherRecursively)) ||
168 (FileUtil.startsWith(rootPath, otherRootPath) && otherRecursively)) {
169 continue NextRoot;
171 else if (FileUtil.startsWith(otherRootPath, rootPath) && (recursively || !otherRecursively)) {
172 iterator1.remove();
175 result.add(request);
178 finally {
179 WRITE_LOCK.unlock();
182 myCachedNormalizedRequests = result.toArray(new WatchRequest[result.size()]);
183 return myCachedNormalizedRequests;
186 private void storeRefreshStatusToFiles() {
187 if (FileWatcher.getInstance().isOperational()) {
188 // TODO: different ways to marky dirty for all these cases
189 markPathsDirty(FileWatcher.getInstance().getDirtyPaths());
190 markFlatDirsDirty(FileWatcher.getInstance().getDirtyDirs());
191 markRecursiveDirsDirty(FileWatcher.getInstance().getDirtyRecursivePaths());
195 private void markPathsDirty(final List<String> dirtyFiles) {
196 for (String dirtyFile : dirtyFiles) {
197 String path = dirtyFile.replace(File.separatorChar, '/');
198 VirtualFile file = findFileByPathIfCached(path);
199 if (file instanceof NewVirtualFile) {
200 ((NewVirtualFile)file).markDirty();
205 private void markFlatDirsDirty(final List<String> dirtyFiles) {
206 for (String dirtyFile : dirtyFiles) {
207 String path = dirtyFile.replace(File.separatorChar, '/');
208 VirtualFile file = findFileByPathIfCached(path);
209 if (file instanceof NewVirtualFile) {
210 final NewVirtualFile nvf = (NewVirtualFile)file;
211 nvf.markDirty();
212 for (VirtualFile child : nvf.getCachedChildren()) {
213 ((NewVirtualFile)child).markDirty();
219 private void markRecursiveDirsDirty(final List<String> dirtyFiles) {
220 for (String dirtyFile : dirtyFiles) {
221 String path = dirtyFile.replace(File.separatorChar, '/');
222 VirtualFile file = findFileByPathIfCached(path);
223 if (file instanceof NewVirtualFile) {
224 ((NewVirtualFile)file).markDirtyRecursively();
229 public void markSuspicousFilesDirty(List<VirtualFile> files) {
230 storeRefreshStatusToFiles();
232 if (myWatcher.isOperational()) {
233 for (String root : myWatcher.getManualWatchRoots()) {
234 final VirtualFile suspicousRoot = findFileByPathIfCached(root);
235 if (suspicousRoot != null) {
236 ((NewVirtualFile)suspicousRoot).markDirtyRecursively();
240 else {
241 for (VirtualFile file : files) {
242 if (file.getFileSystem() == this) {
243 ((NewVirtualFile)file).markDirtyRecursively();
249 private void setUpFileWatcher() {
250 final Application application = ApplicationManager.getApplication();
252 if (application.isDisposeInProgress()) return;
254 if (myWatcher.isOperational()) {
255 application.runReadAction(new Runnable() {
256 public void run() {
257 WRITE_LOCK.lock();
258 try {
259 final WatchRequest[] watchRequests = normalizeRootsForRefresh();
260 List<String> myRecursiveRoots = new ArrayList<String>();
261 List<String> myFlatRoots = new ArrayList<String>();
263 for (WatchRequest root : watchRequests) {
264 if (root.isToWatchRecursively()) {
265 myRecursiveRoots.add(root.getFileSystemRootPath());
267 else {
268 myFlatRoots.add(root.getFileSystemRootPath());
272 myWatcher.setWatchRoots(myRecursiveRoots, myFlatRoots);
274 finally {
275 WRITE_LOCK.unlock();
282 private class StoreRefreshStatusThread extends Thread {
283 private static final long PERIOD = 1000;
285 public StoreRefreshStatusThread() {
286 //noinspection HardCodedStringLiteral
287 super("StoreRefreshStatusThread");
288 setPriority(MIN_PRIORITY);
289 setDaemon(true);
292 public void run() {
293 while (true) {
294 final Application application = ApplicationManager.getApplication();
295 if (application == null || application.isDisposed()) break;
297 storeRefreshStatusToFiles();
298 try {
299 sleep(PERIOD);
301 catch (InterruptedException e) {
302 //normal situation
308 @NotNull
309 public String getComponentName() {
310 return "LocalFileSystem";
313 public WatchRequest addRootToWatch(@NotNull String rootPath, boolean toWatchRecursively) {
314 if (rootPath.length() == 0) return null;
316 WRITE_LOCK.lock();
317 try {
318 final WatchRequestImpl result = new WatchRequestImpl(rootPath, toWatchRecursively);
319 final VirtualFile existingFile = findFileByPathIfCached(rootPath);
320 if (existingFile != null) {
321 if (!isAlreadyWatched(result)) {
322 existingFile.refresh(true, toWatchRecursively);
323 if (existingFile.isDirectory() && !toWatchRecursively && existingFile instanceof NewVirtualFile) {
324 for (VirtualFile child : ((NewVirtualFile)existingFile).getCachedChildren()) {
325 child.refresh(true, false);
330 myRootsToWatch.add(result);
331 myCachedNormalizedRequests = null;
332 setUpFileWatcher();
333 return result;
335 finally {
336 WRITE_LOCK.unlock();
340 private boolean isAlreadyWatched(final WatchRequest request) {
341 for (final WatchRequest current : normalizeRootsForRefresh()) {
342 if (current.dominates(request)) return true;
344 return false;
347 @NotNull
348 public Set<WatchRequest> addRootsToWatch(@NotNull final Collection<String> rootPaths, final boolean toWatchRecursively) {
349 Set<WatchRequest> result = new HashSet<WatchRequest>();
350 Set<VirtualFile> filesToSynchronize = new HashSet<VirtualFile>();
352 WRITE_LOCK.lock();
353 try {
354 for (String rootPath : rootPaths) {
355 LOG.assertTrue(rootPath != null);
356 if (rootPath.length() > 0) {
357 final WatchRequestImpl request = new WatchRequestImpl(rootPath, toWatchRecursively);
358 final VirtualFile existingFile = findFileByPathIfCached(rootPath);
359 if (existingFile != null) {
360 if (!isAlreadyWatched(request)) {
361 filesToSynchronize.add(existingFile);
364 result.add(request);
365 myRootsToWatch.add(request); //add in any case, safe to add inplace without copying myRootsToWatch before the loop
368 myCachedNormalizedRequests = null;
369 setUpFileWatcher();
371 finally {
372 WRITE_LOCK.unlock();
375 if (!ApplicationManager.getApplication().isUnitTestMode() && !filesToSynchronize.isEmpty()) {
376 refreshFiles(filesToSynchronize, toWatchRecursively, true);
379 return result;
382 public void removeWatchedRoot(@NotNull final WatchRequest watchRequest) {
383 WRITE_LOCK.lock();
384 try {
385 if (myRootsToWatch.remove(watchRequest)) {
386 myCachedNormalizedRequests = null;
387 setUpFileWatcher();
390 finally {
391 WRITE_LOCK.unlock();
395 public void removeWatchedRoots(@NotNull final Collection<WatchRequest> rootsToWatch) {
396 WRITE_LOCK.lock();
397 try {
398 if (myRootsToWatch.removeAll(rootsToWatch)) {
399 myCachedNormalizedRequests = null;
400 setUpFileWatcher();
403 finally {
404 WRITE_LOCK.unlock();
408 public boolean isReadOnly() {
409 return false;
412 @NonNls
413 public String toString() {
414 return "LocalFileSystem";
418 public void refreshWithoutFileWatcher(final boolean asynchronous) {
419 Runnable heavyRefresh = new Runnable() {
420 public void run() {
421 for (VirtualFile root : ManagingFS.getInstance().getRoots(LocalFileSystemImpl.this)) {
422 ((NewVirtualFile)root).markDirtyRecursively();
425 refresh(asynchronous);
429 if (asynchronous && myWatcher.isOperational()) {
430 RefreshQueue.getInstance().refresh(true, true, heavyRefresh, ManagingFS.getInstance().getRoots(this));
432 else {
433 heavyRefresh.run();