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
;
40 import java
.io
.IOException
;
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!)
73 myFSRootPath
= parentFile
.getCanonicalPath();
75 catch (IOException e
) {
76 myFSRootPath
= rootPath
; //need something
81 myFSRootPath
= rootPath
.replace('/', File
.separatorChar
);
84 myRootPath
= myFSRootPath
.replace(File
.separatorChar
, '/');
87 myRootPath
= rootPath
.replace(File
.separatorChar
, '/');
88 myFSRootPath
= rootPath
.replace('/', File
.separatorChar
);
93 public String
getRootPath() {
98 public String
getFileSystemRootPath() {
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() {
129 public void cleanupForNextTest() throws IOException
{
130 ApplicationManager
.getApplication().runWriteAction(new Runnable() {
132 FileDocumentManager
.getInstance().saveAllDocuments();
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
>();
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
)) {
171 else if (FileUtil
.startsWith(otherRootPath
, rootPath
) && (recursively
|| !otherRecursively
)) {
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
;
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();
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() {
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());
268 myFlatRoots
.add(root
.getFileSystemRootPath());
272 myWatcher
.setWatchRoots(myRecursiveRoots
, myFlatRoots
);
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
);
294 final Application application
= ApplicationManager
.getApplication();
295 if (application
== null || application
.isDisposed()) break;
297 storeRefreshStatusToFiles();
301 catch (InterruptedException e
) {
309 public String
getComponentName() {
310 return "LocalFileSystem";
313 public WatchRequest
addRootToWatch(@NotNull String rootPath
, boolean toWatchRecursively
) {
314 if (rootPath
.length() == 0) return null;
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;
340 private boolean isAlreadyWatched(final WatchRequest request
) {
341 for (final WatchRequest current
: normalizeRootsForRefresh()) {
342 if (current
.dominates(request
)) return true;
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
>();
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
);
365 myRootsToWatch
.add(request
); //add in any case, safe to add inplace without copying myRootsToWatch before the loop
368 myCachedNormalizedRequests
= null;
375 if (!ApplicationManager
.getApplication().isUnitTestMode() && !filesToSynchronize
.isEmpty()) {
376 refreshFiles(filesToSynchronize
, toWatchRecursively
, true);
382 public void removeWatchedRoot(@NotNull final WatchRequest watchRequest
) {
385 if (myRootsToWatch
.remove(watchRequest
)) {
386 myCachedNormalizedRequests
= null;
395 public void removeWatchedRoots(@NotNull final Collection
<WatchRequest
> rootsToWatch
) {
398 if (myRootsToWatch
.removeAll(rootsToWatch
)) {
399 myCachedNormalizedRequests
= null;
408 public boolean isReadOnly() {
413 public String
toString() {
414 return "LocalFileSystem";
418 public void refreshWithoutFileWatcher(final boolean asynchronous
) {
419 Runnable heavyRefresh
= new Runnable() {
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));