@Override public int getId( @NotNull VirtualFile parent, @NotNull String childName, @NotNull NewVirtualFileSystem fs) { int parentId = getFileId(parent); int[] children = FSRecords.list(parentId); if (children.length > 0) { // fast path, check that some child has same nameId as given name, this avoid O(N) on // retrieving names for processing non-cached children int nameId = FSRecords.getNameId(childName); for (final int childId : children) { if (nameId == FSRecords.getNameId(childId)) { return childId; } } // for case sensitive system the above check is exhaustive in consistent state of vfs } for (final int childId : children) { if (namesEqual(fs, childName, FSRecords.getName(childId))) return childId; } final VirtualFile fake = new FakeVirtualFile(parent, childName); final FileAttributes attributes = fs.getAttributes(fake); if (attributes != null) { final int child = createAndFillRecord(fs, fake, parentId, attributes); FSRecords.updateList(parentId, ArrayUtil.append(children, child)); return child; } return 0; }
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 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); } } }
private static void setFlag(final int id, final int mask, final boolean value) { int oldFlags = FSRecords.getFlags(id); int flags = value ? oldFlags | mask : oldFlags & ~mask; if (oldFlags != flags) { FSRecords.setFlags(id, flags, true); } }
@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 static void executeTouch( @NotNull VirtualFile file, boolean reloadContentFromDelegate, long newModificationStamp) { if (reloadContentFromDelegate) { setFlag(file, MUST_RELOAD_CONTENT, true); } final NewVirtualFileSystem delegate = getDelegate(file); final FileAttributes attributes = delegate.getAttributes(file); FSRecords.setLength(getFileId(file), attributes != null ? attributes.length : DEFAULT_LENGTH); FSRecords.setTimestamp( getFileId(file), attributes != null ? attributes.lastModified : DEFAULT_TIMESTAMP); ((VirtualFileSystemEntry) file).setModificationStamp(newModificationStamp); }
@Override public void setTimeStamp(@NotNull final VirtualFile file, final long modStamp) throws IOException { final int id = getFileId(file); FSRecords.setTimestamp(id, modStamp); getDelegate(file).setTimeStamp(file, modStamp); }
@TestOnly public void cleanPersistedContents() { final int[] roots = FSRecords.listRoots(); for (int root : roots) { cleanPersistedContentsRecursively(root); } }
private void performShutdown() { if (myShutDown.compareAndSet(false, true)) { LOG.info("VFS dispose started"); FSRecords.dispose(); LOG.info("VFS dispose completed"); } }
@NotNull private static String[] listPersisted(@NotNull int[] childrenIds) { String[] names = ArrayUtil.newStringArray(childrenIds.length); for (int i = 0; i < childrenIds.length; i++) { names[i] = FSRecords.getName(childrenIds[i]); } return names; }
private synchronized void performShutdown() { if (!myShutDown) { myShutDown = true; LOG.info("VFS dispose started"); FSRecords.dispose(); LOG.info("VFS dispose completed"); } }
private static int createAndFillRecord( @NotNull NewVirtualFileSystem delegateSystem, @NotNull VirtualFile delegateFile, int parentId, @NotNull FileAttributes attributes) { final int childId = FSRecords.createRecord(); writeAttributesToRecord(childId, parentId, delegateFile, delegateSystem, attributes); return childId; }
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; }
@TestOnly private void cleanPersistedContentsRecursively(int id) { if (isDirectory(getFileAttributes(id))) { for (int child : FSRecords.list(id)) { cleanPersistedContentsRecursively(child); } } else { setFlag(id, MUST_RELOAD_CONTENT, true); } }
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); }
@Override @NotNull public FSRecords.NameId[] listAll(@NotNull VirtualFile parent) { final int parentId = getFileId(parent); FSRecords.NameId[] nameIds = FSRecords.listAll(parentId); if (!areChildrenLoaded(parentId)) { return persistAllChildren(parent, parentId, nameIds); } return nameIds; }
private static void removeIdFromParentList( final int parentId, final int id, @NotNull VirtualFile parent, VirtualFile file) { int[] childList = FSRecords.list(parentId); int index = ArrayUtil.indexOf(childList, id); if (index == -1) { throw new RuntimeException( "Cannot find child (" + id + ")" + file + "\n\tin (" + parentId + ")" + parent + "\n\tactual children:" + Arrays.toString(childList)); } childList = ArrayUtil.remove(childList, index); FSRecords.updateList(parentId, childList); }
@Override public long getLength(@NotNull final VirtualFile file) { long len; if (mustReloadContent(file)) { len = reloadLengthFromDelegate(file, getDelegate(file)); } else { final int id = getFileId(file); len = FSRecords.getLength(id); } return len; }
@Override @NotNull public String[] list(@NotNull final VirtualFile file) { int id = getFileId(file); FSRecords.NameId[] nameIds = FSRecords.listAll(id); if (!areChildrenLoaded(id)) { nameIds = persistAllChildren(file, id, nameIds); } return ContainerUtil.map2Array( nameIds, String.class, new Function<FSRecords.NameId, String>() { @Override public String fun(FSRecords.NameId id) { return id.name.toString(); } }); }
@NotNull private FSRecords.NameId[] persistAllChildren( @NotNull final VirtualFile file, final int id, @NotNull FSRecords.NameId[] current) { assert file != mySuperRoot; final NewVirtualFileSystem fs = replaceWithNativeFS(getDelegate(file)); String[] delegateNames = VfsUtil.filterNames(fs.list(file)); if (delegateNames.length == 0 && current.length > 0) { return current; } Set<String> toAdd = ContainerUtil.newHashSet(delegateNames); for (FSRecords.NameId nameId : current) { toAdd.remove(nameId.name); } final TIntArrayList childrenIds = new TIntArrayList(current.length + toAdd.size()); final List<FSRecords.NameId> nameIds = ContainerUtil.newArrayListWithExpectedSize(current.length + toAdd.size()); for (FSRecords.NameId nameId : current) { childrenIds.add(nameId.id); nameIds.add(nameId); } for (String newName : toAdd) { FakeVirtualFile child = new FakeVirtualFile(file, newName); FileAttributes attributes = fs.getAttributes(child); if (attributes != null) { int childId = createAndFillRecord(fs, child, id, attributes); childrenIds.add(childId); nameIds.add(new FSRecords.NameId(childId, FileNameCache.storeName(newName), newName)); } } FSRecords.updateList(id, childrenIds.toNativeArray()); setChildrenCached(id); return nameIds.toArray(new FSRecords.NameId[nameIds.size()]); }
@Override public void initComponent() { FSRecords.connect(); }
@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; }
@Override public int getCurrentContentId(@NotNull VirtualFile file) { return FSRecords.getContentId(getFileId(file)); }
@Override public void releaseContent(int contentId) { FSRecords.releaseContent(contentId); }
@Override public int acquireContent(@NotNull VirtualFile file) { return FSRecords.acquireFileContent(getFileId(file)); }
private static boolean mustReloadContent(@NotNull VirtualFile file) { int fileId = getFileId(file); return checkFlag(fileId, MUST_RELOAD_CONTENT) || FSRecords.getLength(fileId) == -1L; }
private static long reloadLengthFromDelegate( @NotNull VirtualFile file, @NotNull NewVirtualFileSystem delegate) { final long len = delegate.getLength(file); FSRecords.setLength(getFileId(file), len); return len; }
@Override public int getFileAttributes(int id) { assert id > 0; //noinspection MagicConstant return FSRecords.getFlags(id); }
private static int getParent(final int id) { assert id > 0; return FSRecords.getParent(id); }
@Override public long getTimeStamp(@NotNull final VirtualFile file) { return FSRecords.getTimestamp(getFileId(file)); }