/* * Copyright 2000-2017 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.history.integration; import com.intellij.history.core.LocalHistoryFacade; import com.intellij.history.core.StoredContent; import com.intellij.history.core.tree.Entry; import com.intellij.openapi.Disposable; import com.intellij.openapi.command.CommandEvent; import com.intellij.openapi.command.CommandListener; import com.intellij.openapi.util.Key; import com.intellij.openapi.util.Pair; import com.intellij.openapi.vfs.*; import com.intellij.openapi.vfs.impl.BulkVirtualFileListenerAdapter; import com.intellij.openapi.vfs.newvfs.BulkFileListener; import com.intellij.openapi.vfs.newvfs.events.VFileEvent; import com.intellij.util.EventDispatcher; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import java.util.List; public class LocalHistoryEventDispatcher implements VirtualFileManagerListener, CommandListener, BulkFileListener, VirtualFileListener { private static final Key<Boolean> WAS_VERSIONED_KEY = Key.create(LocalHistoryEventDispatcher.class.getSimpleName() + ".WAS_VERSIONED_KEY"); private final LocalHistoryFacade myVcs; private final IdeaGateway myGateway; private final EventDispatcher<VirtualFileListener> myVfsEventsDispatcher = EventDispatcher.create(VirtualFileListener.class); public LocalHistoryEventDispatcher(LocalHistoryFacade vcs, IdeaGateway gw) { myVcs = vcs; myGateway = gw; myVfsEventsDispatcher.addListener(this); } @Override public void beforeRefreshStart(boolean asynchronous) { beginChangeSet(); } @Override public void afterRefreshFinish(boolean asynchronous) { endChangeSet(LocalHistoryBundle.message("system.label.external.change")); } @Override public void commandStarted(CommandEvent e) { beginChangeSet(); } @Override public void commandFinished(CommandEvent e) { endChangeSet(e.getCommandName()); } public void startAction() { myGateway.registerUnsavedDocuments(myVcs); myVcs.forceBeginChangeSet(); } public void finishAction(String name) { myGateway.registerUnsavedDocuments(myVcs); endChangeSet(name); } private void beginChangeSet() { myVcs.beginChangeSet(); } private void endChangeSet(String name) { myVcs.endChangeSet(name); } @Override public void fileCreated(@NotNull VirtualFileEvent e) { beginChangeSet(); createRecursively(e.getFile()); endChangeSet(null); } private void createRecursively(VirtualFile f) { VfsUtilCore.visitChildrenRecursively(f, new VirtualFileVisitor() { @Override public boolean visitFile(@NotNull VirtualFile f) { if (isVersioned(f)) { myVcs.created(f.getPath(), f.isDirectory()); } return true; } @Nullable @Override public Iterable<VirtualFile> getChildrenIterable(@NotNull VirtualFile f) { // For unversioned files we try to get cached children in hope that they are already generated by content root manager: // cached children may mean that there are versioned sub-folders or sub-files. return myGateway.isVersioned(f, true) ? IdeaGateway.loadAndIterateChildren(f) : IdeaGateway.iterateDBChildren(f); } }); } @Override public void beforeContentsChange(@NotNull VirtualFileEvent e) { if (!areContentChangesVersioned(e)) return; VirtualFile f = e.getFile(); Pair<StoredContent, Long> content = myGateway.acquireAndUpdateActualContent(f, null); if (content != null) { myVcs.contentChanged(f.getPath(), content.first, content.second); } } @Override public void beforePropertyChange(@NotNull VirtualFilePropertyEvent e) { if (VirtualFile.PROP_NAME.equals(e.getPropertyName())) { VirtualFile f = e.getFile(); f.putUserData(WAS_VERSIONED_KEY, myGateway.isVersioned(f)); } } @Override public void propertyChanged(@NotNull VirtualFilePropertyEvent e) { if (VirtualFile.PROP_NAME.equals(e.getPropertyName())) { VirtualFile f = e.getFile(); boolean isVersioned = myGateway.isVersioned(f); Boolean wasVersioned = f.getUserData(WAS_VERSIONED_KEY); if (wasVersioned == null) return; f.putUserData(WAS_VERSIONED_KEY, null); if (!wasVersioned && !isVersioned) return; String oldName = (String)e.getOldValue(); myVcs.renamed(f.getPath(), oldName); } else if (VirtualFile.PROP_WRITABLE.equals(e.getPropertyName())) { if (!isVersioned(e.getFile())) return; VirtualFile f = e.getFile(); if (!f.isDirectory()) { myVcs.readOnlyStatusChanged(f.getPath(), !(Boolean)e.getOldValue()); } } } @Override public void beforeFileMovement(@NotNull VirtualFileMoveEvent e) { VirtualFile f = e.getFile(); f.putUserData(WAS_VERSIONED_KEY, myGateway.isVersioned(f)); } @Override public void fileMoved(@NotNull VirtualFileMoveEvent e) { VirtualFile f = e.getFile(); boolean isVersioned = myGateway.isVersioned(f); Boolean wasVersioned = f.getUserData(WAS_VERSIONED_KEY); if (wasVersioned == null) return; f.putUserData(WAS_VERSIONED_KEY, null); if (!wasVersioned && !isVersioned) return; myVcs.moved(f.getPath(), e.getOldParent().getPath()); } @Override public void beforeFileDeletion(@NotNull VirtualFileEvent e) { VirtualFile f = e.getFile(); Entry entry = myGateway.createEntryForDeletion(f); if (entry != null) { myVcs.deleted(f.getPath(), entry); } } private boolean isVersioned(VirtualFile f) { return myGateway.isVersioned(f); } private boolean areContentChangesVersioned(VirtualFileEvent e) { return myGateway.areContentChangesVersioned(e.getFile()); } @Override public void before(@NotNull List<? extends VFileEvent> events) { myGateway.runWithVfsEventsDispatchContext(events, true, () -> { for (VFileEvent event : events) { BulkVirtualFileListenerAdapter.fireBefore(myVfsEventsDispatcher.getMulticaster(), event); } }); } @Override public void after(@NotNull List<? extends VFileEvent> events) { myGateway.runWithVfsEventsDispatchContext(events, false, () -> { for (VFileEvent event : events) { BulkVirtualFileListenerAdapter.fireAfter(myVfsEventsDispatcher.getMulticaster(), event); } }); } public void addVirtualFileListener(VirtualFileListener virtualFileListener, Disposable disposable) { myVfsEventsDispatcher.addListener(virtualFileListener, disposable); } }