private void applyChildrenChangeEvents(VirtualFile parent, List<VFileEvent> events) { final NewVirtualFileSystem delegate = getDelegate(parent); TIntArrayList childrenIdsUpdated = new TIntArrayList(); List<VirtualFile> childrenToBeUpdated = new SmartList<VirtualFile>(); assert parent != null && parent != mySuperRoot; final int parentId = getFileId(parent); assert parentId != 0; TIntHashSet parentChildrenIds = new TIntHashSet(FSRecords.list(parentId)); boolean hasRemovedChildren = false; for (VFileEvent event : events) { if (event instanceof VFileCreateEvent) { String name = ((VFileCreateEvent) event).getChildName(); final VirtualFile fake = new FakeVirtualFile(parent, name); final FileAttributes attributes = delegate.getAttributes(fake); if (attributes != null) { final int childId = createAndFillRecord(delegate, fake, parentId, attributes); assert parent instanceof VirtualDirectoryImpl : parent; final VirtualDirectoryImpl dir = (VirtualDirectoryImpl) parent; VirtualFileSystemEntry child = dir.createChild(name, childId, dir.getFileSystem()); childrenToBeUpdated.add(child); childrenIdsUpdated.add(childId); parentChildrenIds.add(childId); } } else if (event instanceof VFileDeleteEvent) { VirtualFile file = ((VFileDeleteEvent) event).getFile(); if (!file.exists()) { LOG.error("Deleting a file, which does not exist: " + file.getPath()); continue; } hasRemovedChildren = true; int id = getFileId(file); childrenToBeUpdated.add(file); childrenIdsUpdated.add(-id); parentChildrenIds.remove(id); } } FSRecords.updateList(parentId, parentChildrenIds.toArray()); if (hasRemovedChildren) clearIdCache(); VirtualDirectoryImpl parentImpl = (VirtualDirectoryImpl) parent; for (int i = 0, len = childrenIdsUpdated.size(); i < len; ++i) { final int childId = childrenIdsUpdated.get(i); final VirtualFile childFile = childrenToBeUpdated.get(i); if (childId > 0) { parentImpl.addChild((VirtualFileSystemEntry) childFile); } else { FSRecords.deleteRecordRecursively(-childId); parentImpl.removeChild(childFile); invalidateSubtree(childFile); } } }
@NotNull private VirtualFilePointer create( @Nullable("null means the pointer will be created from the (not null) url") VirtualFile file, @Nullable("null means url has to be computed from the (not-null) file path") String url, @NotNull Disposable parentDisposable, @Nullable VirtualFilePointerListener listener) { VirtualFileSystem fileSystem; String protocol; String path; if (file == null) { int protocolEnd = url.indexOf(URLUtil.SCHEME_SEPARATOR); if (protocolEnd == -1) { protocol = null; fileSystem = null; } else { protocol = url.substring(0, protocolEnd); fileSystem = myVirtualFileManager.getFileSystem(protocol); } path = url.substring(protocolEnd + URLUtil.SCHEME_SEPARATOR.length()); } else { fileSystem = file.getFileSystem(); protocol = fileSystem.getProtocol(); path = file.getPath(); url = VirtualFileManager.constructUrl(protocol, path); } if (fileSystem == TEMP_FILE_SYSTEM) { // for tests, recreate always VirtualFile found = file == null ? VirtualFileManager.getInstance().findFileByUrl(url) : file; return new IdentityVirtualFilePointer(found, url); } boolean isJar = fileSystem == JAR_FILE_SYSTEM; if (fileSystem != LOCAL_FILE_SYSTEM && !isJar) { // we are unable to track alien file systems for now VirtualFile found = fileSystem == null ? null : file != null ? file : VirtualFileManager.getInstance().findFileByUrl(url); // if file is null, this pointer will never be alive return getOrCreateIdentity(url, found); } if (file == null) { String cleanPath = cleanupPath(path, isJar); // if newly created path is the same as substringed from url one then the url did not change, // we can reuse it //noinspection StringEquality if (cleanPath != path) { url = VirtualFileManager.constructUrl(protocol, cleanPath); path = cleanPath; } } // else url has come from VirtualFile.getPath() and is good enough VirtualFilePointerImpl pointer = getOrCreate(parentDisposable, listener, path, Pair.create(file, url)); DelegatingDisposable.registerDisposable(parentDisposable, pointer); return pointer; }
@NotNull private VirtualFilePointer create( @Nullable VirtualFile file, @NotNull String url, @NotNull final Disposable parentDisposable, @Nullable VirtualFilePointerListener listener) { String protocol; VirtualFileSystem fileSystem; if (file == null) { protocol = VirtualFileManager.extractProtocol(url); fileSystem = myVirtualFileManager.getFileSystem(protocol); } else { protocol = null; fileSystem = file.getFileSystem(); } if (fileSystem == TempFileSystem.getInstance()) { // for tests, recreate always since VirtualFile found = fileSystem == null ? null : file != null ? file : VirtualFileManager.getInstance().findFileByUrl(url); return new IdentityVirtualFilePointer(found, url); } if (fileSystem != LocalFileSystem.getInstance() && fileSystem != JarFileSystem.getInstance()) { // we are unable to track alien file systems for now VirtualFile found = fileSystem == null ? null : file != null ? file : VirtualFileManager.getInstance().findFileByUrl(url); // if file is null, this pointer will never be alive return getOrCreateIdentity(url, found); } String path; if (file == null) { path = VirtualFileManager.extractPath(url); path = cleanupPath(path, protocol); url = VirtualFileManager.constructUrl(protocol, path); } else { path = file.getPath(); // url has come from VirtualFile.getUrl() and is good enough } VirtualFilePointerImpl pointer = getOrCreate(file, url, parentDisposable, listener, path); int newCount = pointer.incrementUsageCount(); if (newCount == 1) { Disposer.register(parentDisposable, pointer); } else { // already registered register(parentDisposable, pointer); } return pointer; }
@Override protected char[] appendPathOnFileSystem(int accumulatedPathLength, int[] positionRef) { String parentPath = myParentLocalFile.getPath(); char[] chars = new char [parentPath.length() + JarFileSystem.JAR_SEPARATOR.length() + accumulatedPathLength]; positionRef[0] = copyString(chars, positionRef[0], myParentLocalFile.getPath()); positionRef[0] = copyString(chars, positionRef[0], JarFileSystem.JAR_SEPARATOR); return chars; }
@Override public VirtualFile createChildFile( Object requestor, @NotNull VirtualFile parent, @NotNull String file) throws IOException { getDelegate(parent).createChildFile(requestor, parent, file); processEvent(new VFileCreateEvent(requestor, parent, file, false, false)); final VirtualFile child = parent.findChild(file); if (child == null) { throw new IOException("Cannot create child file '" + file + "' at " + parent.getPath()); } return child; }
@Override public VirtualFile createChildDirectory( Object requestor, @NotNull VirtualFile parent, @NotNull String dir) throws IOException { getDelegate(parent).createChildDirectory(requestor, parent, dir); processEvent(new VFileCreateEvent(requestor, parent, dir, true, false)); final VirtualFile child = parent.findChild(dir); if (child == null) { throw new IOException("Cannot create child directory '" + dir + "' at " + parent.getPath()); } return child; }
private void executeMove(@NotNull VirtualFile file, @NotNull VirtualFile newParent) { clearIdCache(); final int fileId = getFileId(file); final int newParentId = getFileId(newParent); final int oldParentId = getFileId(file.getParent()); removeIdFromParentList(oldParentId, fileId, file.getParent(), file); FSRecords.setParent(fileId, newParentId); appendIdToParentList(newParentId, fileId); ((VirtualFileSystemEntry) file).setParent(newParent); }
private void delete(File file) throws IOException { VirtualFile vFile = myFileSystem.findFileByIoFile(file); if (vFile != null) { AccessToken token = ApplicationManager.getApplication().acquireWriteActionLock(getClass()); try { vFile.delete(this); } finally { token.finish(); } } if (file.exists()) { FileUtil.delete(file); } }
@NotNull private VirtualFilePointer create( @Nullable VirtualFile file, @NotNull String url, @NotNull final Disposable parentDisposable, @Nullable VirtualFilePointerListener listener) { String protocol; IVirtualFileSystem fileSystem; if (file == null) { protocol = VirtualFileManager.extractProtocol(url); fileSystem = myVirtualFileManager.getFileSystem(protocol); } else { protocol = null; fileSystem = file.getFileSystem(); } if (fileSystem == myTempFileSystem) { // for tests, recreate always VirtualFile found = file == null ? VirtualFileManager.getInstance().findFileByUrl(url) : file; return new IdentityVirtualFilePointer(found, url); } if (fileSystem != myLocalFileSystem && !(fileSystem instanceof ArchiveFileSystem)) { // we are unable to track alien file systems for now VirtualFile found = fileSystem == null ? null : file != null ? file : VirtualFileManager.getInstance().findFileByUrl(url); // if file is null, this pointer will never be alive return getOrCreateIdentity(url, found); } String path; if (file == null) { path = VirtualFileManager.extractPath(url); path = cleanupPath(path, protocol, fileSystem); url = VirtualFileManager.constructUrl(protocol, path); } else { path = file.getPath(); // url has come from VirtualFile.getUrl() and is good enough } VirtualFilePointerImpl pointer = getOrCreate(parentDisposable, listener, path, Pair.create(file, url)); DelegatingDisposable.registerDisposable(parentDisposable, pointer); return pointer; }
@Override @NotNull public synchronized VirtualFilePointer create( @NotNull VirtualFile file, @NotNull Disposable parent, @Nullable VirtualFilePointerListener listener) { return create(file, file.getUrl(), parent, listener); }
@NotNull private static List<VFileEvent> validateEvents(@NotNull List<VFileEvent> events) { final List<EventWrapper> deletionEvents = ContainerUtil.newArrayList(); for (int i = 0, size = events.size(); i < size; i++) { final VFileEvent event = events.get(i); if (event instanceof VFileDeleteEvent && event.isValid()) { deletionEvents.add(new EventWrapper((VFileDeleteEvent) event, i)); } } final TIntHashSet invalidIDs; if (deletionEvents.isEmpty()) { invalidIDs = EmptyIntHashSet.INSTANCE; } else { ContainerUtil.quickSort(deletionEvents, DEPTH_COMPARATOR); invalidIDs = new TIntHashSet(deletionEvents.size()); final Set<VirtualFile> dirsToBeDeleted = new THashSet<VirtualFile>(deletionEvents.size()); nextEvent: for (EventWrapper wrapper : deletionEvents) { final VirtualFile candidate = wrapper.event.getFile(); VirtualFile parent = candidate; while (parent != null) { if (dirsToBeDeleted.contains(parent)) { invalidIDs.add(wrapper.id); continue nextEvent; } parent = parent.getParent(); } if (candidate.isDirectory()) { dirsToBeDeleted.add(candidate); } } } final List<VFileEvent> filtered = new ArrayList<VFileEvent>(events.size() - invalidIDs.size()); for (int i = 0, size = events.size(); i < size; i++) { final VFileEvent event = events.get(i); if (event.isValid() && !(event instanceof VFileDeleteEvent && invalidIDs.contains(i))) { filtered.add(event); } } return filtered; }
@Override public void renameFile( final Object requestor, @NotNull final VirtualFile file, @NotNull final String newName) throws IOException { getDelegate(file).renameFile(requestor, file, newName); processEvent( new VFilePropertyChangeEvent( requestor, file, VirtualFile.PROP_NAME, file.getName(), newName, false)); }
@Override @NotNull public byte[] contentsToByteArray(@NotNull final VirtualFile file, boolean cacheContent) throws IOException { InputStream contentStream = null; boolean reloadFromDelegate; boolean outdated; int fileId; synchronized (myInputLock) { fileId = getFileId(file); outdated = checkFlag(fileId, MUST_RELOAD_CONTENT) || FSRecords.getLength(fileId) == -1L; reloadFromDelegate = outdated || (contentStream = readContent(file)) == null; } if (reloadFromDelegate) { final NewVirtualFileSystem delegate = getDelegate(file); final byte[] content; if (outdated) { // in this case, file can have out-of-date length. so, update it first (it's needed for // correct contentsToByteArray() work) // see IDEA-90813 for possible bugs FSRecords.setLength(fileId, delegate.getLength(file)); content = delegate.contentsToByteArray(file); } else { // a bit of optimization content = delegate.contentsToByteArray(file); FSRecords.setLength(fileId, content.length); } ApplicationEx application = (ApplicationEx) ApplicationManager.getApplication(); // we should cache every local files content // because the local history feature is currently depends on this cache, // perforce offline mode as well if ((!delegate.isReadOnly() || // do not cache archive content unless asked cacheContent && !application.isInternal() && !application.isUnitTestMode()) && content.length <= PersistentFSConstants.FILE_LENGTH_TO_CACHE_THRESHOLD) { synchronized (myInputLock) { writeContent(file, new ByteSequence(content), delegate.isReadOnly()); setFlag(file, MUST_RELOAD_CONTENT, false); } } return content; } else { try { final int length = (int) file.getLength(); assert length >= 0 : file; return FileUtil.loadBytes(contentStream, length); } catch (IOException e) { throw FSRecords.handleError(e); } } }
private ArchiveRoot( @NotNull NewVirtualFileSystem fs, int id, VfsData.Segment segment, VfsData.DirectoryData data, VirtualFile parentLocalFile) { super(id, segment, data, fs); myParentLocalFile = parentLocalFile; myParentPath = myParentLocalFile.getPath(); }
private void executeDelete(@NotNull VirtualFile file) { if (!file.exists()) { LOG.error("Deleting a file, which does not exist: " + file.getPath()); return; } clearIdCache(); int id = getFileId(file); final VirtualFile parent = file.getParent(); final int parentId = parent == null ? 0 : getFileId(parent); if (parentId == 0) { String rootUrl = normalizeRootUrl(file.getPath(), (NewVirtualFileSystem) file.getFileSystem()); myRootsLock.writeLock().lock(); try { myRoots.remove(rootUrl); myRootsById.remove(id); FSRecords.deleteRootRecord(id); } finally { myRootsLock.writeLock().unlock(); } } else { removeIdFromParentList(parentId, id, parent, file); VirtualDirectoryImpl directory = (VirtualDirectoryImpl) file.getParent(); assert directory != null : file; directory.removeChild(file); } FSRecords.deleteRecordRecursively(id); invalidateSubtree(file); }
private void assertEvent(Class<? extends VFileEvent> type, String... paths) { List<VFileEvent> events = getEvents(type.getSimpleName(), null); assertEquals(events.toString(), paths.length, events.size()); Set<String> pathSet = ContainerUtil.map2Set( paths, new Function<String, String>() { @Override public String fun(final String path) { return FileUtil.toSystemIndependentName(path); } }); for (VFileEvent event : events) { assertTrue(event.toString(), type.isInstance(event)); VirtualFile eventFile = event.getFile(); assertNotNull(event.toString(), eventFile); assertTrue( eventFile + " not in " + Arrays.toString(paths), pathSet.remove(eventFile.getPath())); } }
@Override public VirtualFile copyFile( Object requestor, @NotNull VirtualFile file, @NotNull VirtualFile parent, @NotNull String name) throws IOException { getDelegate(file).copyFile(requestor, file, parent, name); processEvent(new VFileCopyEvent(requestor, file, parent, name)); final VirtualFile child = parent.findChild(name); if (child == null) { throw new IOException("Cannot create child"); } return child; }
private static boolean writeAttributesToRecord( final int id, final int parentId, @NotNull VirtualFile file, @NotNull NewVirtualFileSystem fs, @NotNull FileAttributes attributes) { String name = file.getName(); if (!name.isEmpty()) { if (namesEqual(fs, name, FSRecords.getName(id))) return false; // TODO: Handle root attributes change. } else { if (areChildrenLoaded(id)) return false; // TODO: hack } FSRecords.writeAttributesToRecord(id, parentId, attributes, name); return true; }
@Override @Nullable public VirtualFileSystemEntry findRoot( @NotNull String basePath, @NotNull NewVirtualFileSystem fs) { if (basePath.isEmpty()) { LOG.error("Invalid root, fs=" + fs); return null; } String rootUrl = normalizeRootUrl(basePath, fs); myRootsLock.readLock().lock(); try { VirtualFileSystemEntry root = myRoots.get(rootUrl); if (root != null) return root; } finally { myRootsLock.readLock().unlock(); } final VirtualFileSystemEntry newRoot; int rootId = FSRecords.findRootRecord(rootUrl); VfsData.Segment segment = VfsData.getSegment(rootId, true); VfsData.DirectoryData directoryData = new VfsData.DirectoryData(); if (fs instanceof ArchiveFileSystem) { String parentPath = basePath.substring(0, basePath.indexOf(ArchiveFileSystem.ARCHIVE_SEPARATOR)); VirtualFile parentFile = LocalFileSystem.getInstance().findFileByPath(parentPath); if (parentFile == null) return null; FileType type = FileTypeRegistry.getInstance().getFileTypeByFileName(parentFile.getName()); if (!(type instanceof ArchiveFileType)) return null; newRoot = new ArchiveRoot(fs, rootId, segment, directoryData, parentFile); } else { newRoot = new FsRoot(fs, rootId, segment, directoryData, basePath); } FileAttributes attributes = fs.getAttributes( new StubVirtualFile() { @NotNull @Override public String getPath() { return newRoot.getPath(); } @Nullable @Override public VirtualFile getParent() { return null; } }); if (attributes == null || !attributes.isDirectory()) { return null; } boolean mark = false; myRootsLock.writeLock().lock(); try { VirtualFileSystemEntry root = myRoots.get(rootUrl); if (root != null) return root; VfsData.initFile(rootId, segment, -1, directoryData); mark = writeAttributesToRecord(rootId, 0, newRoot, fs, attributes); myRoots.put(rootUrl, newRoot); myRootsById.put(rootId, newRoot); } finally { myRootsLock.writeLock().unlock(); } if (!mark && attributes.lastModified != FSRecords.getTimestamp(rootId)) { newRoot.markDirtyRecursively(); } LOG.assertTrue( rootId == newRoot.getId(), "root=" + newRoot + " expected=" + rootId + " actual=" + newRoot.getId()); return newRoot; }
@NotNull @Override public CharSequence getNameSequence() { return myParentLocalFile.getName(); }
@NotNull private static NewVirtualFileSystem getDelegate(@NotNull VirtualFile file) { return (NewVirtualFileSystem) file.getFileSystem(); }
@Override public void before(@NotNull final List<? extends VFileEvent> events) { cleanContainerCaches(); List<VirtualFilePointer> toFireEvents = new ArrayList<VirtualFilePointer>(); List<String> toUpdateUrl = new ArrayList<String>(); synchronized (VirtualFilePointerManagerImpl.this) { for (VFileEvent event : events) { if (event instanceof VFileDeleteEvent) { final VFileDeleteEvent deleteEvent = (VFileDeleteEvent) event; String url = deleteEvent.getFile().getPath(); addPointersUnder(url, true, toFireEvents); } else if (event instanceof VFileCreateEvent) { final VFileCreateEvent createEvent = (VFileCreateEvent) event; String url = createEvent.getPath(); addPointersUnder(url, false, toFireEvents); } else if (event instanceof VFileCopyEvent) { final VFileCopyEvent copyEvent = (VFileCopyEvent) event; String url = copyEvent.getNewParent().getPath() + "/" + copyEvent.getFile().getName(); addPointersUnder(url, false, toFireEvents); } else if (event instanceof VFileMoveEvent) { final VFileMoveEvent moveEvent = (VFileMoveEvent) event; List<VirtualFilePointer> pointers = new ArrayList<VirtualFilePointer>(); addPointersUnder(moveEvent.getFile().getPath(), false, pointers); for (VirtualFilePointer pointer : pointers) { VirtualFile file = pointer.getFile(); if (file != null) { toUpdateUrl.add(file.getPath()); } } } else if (event instanceof VFilePropertyChangeEvent) { final VFilePropertyChangeEvent change = (VFilePropertyChangeEvent) event; if (VirtualFile.PROP_NAME.equals(change.getPropertyName())) { List<VirtualFilePointer> pointers = new ArrayList<VirtualFilePointer>(); addPointersUnder(change.getFile().getPath(), false, pointers); for (VirtualFilePointer pointer : pointers) { VirtualFile file = pointer.getFile(); if (file != null) { toUpdateUrl.add(file.getPath()); } } } } } myEvents = new ArrayList<EventDescriptor>(); for (VirtualFilePointerListener listener : myUrlToPointerMaps.keySet()) { if (listener == null) continue; EventDescriptor event = new EventDescriptor(listener, toFireEvents); myEvents.add(event); } } for (EventDescriptor event : myEvents) { event.fireBefore(); } if (!toFireEvents.isEmpty()) { VirtualFilePointer[] arr = toFireEvents.toArray(new VirtualFilePointer[toFireEvents.size()]); myBus.syncPublisher(VirtualFilePointerListener.TOPIC).beforeValidityChanged(arr); } myPointersToUpdate = toFireEvents; myUrlsToUpdate = toUpdateUrl; }
@NotNull @Override public String getName() { return myParentLocalFile.getName(); }
@Override public void before(@NotNull final List<? extends VFileEvent> events) { List<FilePointerPartNode> toFireEvents = new ArrayList<FilePointerPartNode>(); List<FilePointerPartNode> toUpdateUrl = new ArrayList<FilePointerPartNode>(); VirtualFilePointer[] toFirePointers; synchronized (this) { incModificationCount(); for (VFileEvent event : events) { if (event instanceof VFileDeleteEvent) { final VFileDeleteEvent deleteEvent = (VFileDeleteEvent) event; addPointersUnder(deleteEvent.getFile(), false, "", toFireEvents); } else if (event instanceof VFileCreateEvent) { final VFileCreateEvent createEvent = (VFileCreateEvent) event; addPointersUnder(createEvent.getParent(), true, createEvent.getChildName(), toFireEvents); } else if (event instanceof VFileCopyEvent) { final VFileCopyEvent copyEvent = (VFileCopyEvent) event; addPointersUnder( copyEvent.getNewParent(), true, copyEvent.getFile().getName(), toFireEvents); } else if (event instanceof VFileMoveEvent) { final VFileMoveEvent moveEvent = (VFileMoveEvent) event; VirtualFile eventFile = moveEvent.getFile(); addPointersUnder(moveEvent.getNewParent(), true, eventFile.getName(), toFireEvents); List<FilePointerPartNode> nodes = new ArrayList<FilePointerPartNode>(); addPointersUnder(eventFile, false, "", nodes); for (FilePointerPartNode pair : nodes) { VirtualFile file = pair.leaf.getFile(); if (file != null) { toUpdateUrl.add(pair); } } } else if (event instanceof VFilePropertyChangeEvent) { final VFilePropertyChangeEvent change = (VFilePropertyChangeEvent) event; if (VirtualFile.PROP_NAME.equals(change.getPropertyName())) { VirtualFile eventFile = change.getFile(); VirtualFile parent = eventFile.getParent(); // e.g. for LightVirtualFiles addPointersUnder(parent, true, change.getNewValue().toString(), toFireEvents); List<FilePointerPartNode> nodes = new ArrayList<FilePointerPartNode>(); addPointersUnder(eventFile, false, "", nodes); for (FilePointerPartNode pair : nodes) { VirtualFile file = pair.leaf.getFile(); if (file != null) { toUpdateUrl.add(pair); } } } } } myEvents = new ArrayList<EventDescriptor>(); toFirePointers = toPointers(toFireEvents); for (final VirtualFilePointerListener listener : myPointers.keySet()) { if (listener == null) continue; List<VirtualFilePointer> filtered = ContainerUtil.filter( toFirePointers, new Condition<VirtualFilePointer>() { @Override public boolean value(VirtualFilePointer pointer) { return ((VirtualFilePointerImpl) pointer).getListener() == listener; } }); if (!filtered.isEmpty()) { EventDescriptor event = new EventDescriptor( listener, filtered.toArray(new VirtualFilePointer[filtered.size()])); myEvents.add(event); } } } for (EventDescriptor descriptor : myEvents) { descriptor.fireBefore(); } if (!toFireEvents.isEmpty()) { myBus.syncPublisher(VirtualFilePointerListener.TOPIC).beforeValidityChanged(toFirePointers); } myPointersToFire = toFireEvents; myPointersToUpdateUrl = toUpdateUrl; }