blob: e836ca18b35769c4f9ec5384d8c56308855c0d2b [file] [log] [blame]
/*
* Copyright 2000-2013 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.openapi.vcs.changes;
import com.intellij.openapi.Disposable;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.vcs.*;
import com.intellij.openapi.vcs.annotate.FileAnnotation;
import com.intellij.openapi.vcs.diff.DiffProvider;
import com.intellij.openapi.vcs.history.VcsRevisionNumber;
import com.intellij.openapi.vfs.LocalFileSystem;
import com.intellij.openapi.vfs.VfsUtilCore;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.util.Alarm;
import com.intellij.util.containers.MultiMap;
import com.intellij.util.messages.MessageBusConnection;
import java.io.File;
import java.util.*;
/**
* Created with IntelliJ IDEA.
* User: Irina.Chernushina
* Date: 11/20/12
* Time: 11:31 AM
*/
public class VcsAnnotationLocalChangesListenerImpl implements Disposable, VcsAnnotationLocalChangesListener {
private final ZipperUpdater myUpdater;
private final MessageBusConnection myConnection;
private final Runnable myUpdateStuff;
private final Set<String> myDirtyPaths;
private final Set<VirtualFile> myDirtyFiles;
private final Map<String, VcsRevisionNumber> myDirtyChanges;
private final LocalFileSystem myLocalFileSystem;
private final ProjectLevelVcsManager myVcsManager;
private final Set<VcsKey> myVcsKeySet;
private final Object myLock;
private final MultiMap<VirtualFile, FileAnnotation> myFileAnnotationMap;
public VcsAnnotationLocalChangesListenerImpl(Project project, final ProjectLevelVcsManager vcsManager) {
myLock = new Object();
myUpdateStuff = createUpdateStuff();
myUpdater = new ZipperUpdater(ApplicationManager.getApplication().isUnitTestMode() ? 10 : 300, Alarm.ThreadToUse.POOLED_THREAD, project);
myConnection = project.getMessageBus().connect();
myLocalFileSystem = LocalFileSystem.getInstance();
VcsAnnotationRefresher handler = createHandler();
myDirtyPaths = new HashSet<String>();
myDirtyChanges = new HashMap<String, VcsRevisionNumber>();
myDirtyFiles = new HashSet<VirtualFile>();
myFileAnnotationMap = MultiMap.createSet();
myVcsManager = vcsManager;
myVcsKeySet = new HashSet<VcsKey>();
myConnection.subscribe(VcsAnnotationRefresher.LOCAL_CHANGES_CHANGED, handler);
}
private Runnable createUpdateStuff() {
return new Runnable() {
@Override
public void run() {
final Set<String> paths = new HashSet<String>();
final Map<String, VcsRevisionNumber> changes = new HashMap<String, VcsRevisionNumber>();
final Set<VirtualFile> files = new HashSet<VirtualFile>();
Set<VcsKey> vcsToRefresh;
synchronized (myLock) {
vcsToRefresh = new HashSet<VcsKey>(myVcsKeySet);
paths.addAll(myDirtyPaths);
changes.putAll(myDirtyChanges);
files.addAll(myDirtyFiles);
myDirtyPaths.clear();
myDirtyChanges.clear();
myVcsKeySet.clear();
myDirtyFiles.clear();
}
closeForVcs(vcsToRefresh);
checkByDirtyScope(paths, changes, files);
}
};
}
private void checkByDirtyScope(Set<String> removed, Map<String, VcsRevisionNumber> refresh, Set<VirtualFile> files) {
for (String path : removed) {
refreshForPath(path, null);
}
for (Map.Entry<String, VcsRevisionNumber> entry : refresh.entrySet()) {
refreshForPath(entry.getKey(), entry.getValue());
}
for (VirtualFile file : files) {
processUnderFile(file);
}
}
private void processUnderFile(VirtualFile file) {
final MultiMap<VirtualFile, FileAnnotation> annotations = new MultiMap<VirtualFile, FileAnnotation>();
synchronized (myLock) {
for (VirtualFile virtualFile : myFileAnnotationMap.keySet()) {
if (VfsUtilCore.isAncestor(file, virtualFile, true)) {
final Collection<FileAnnotation> values = myFileAnnotationMap.get(virtualFile);
for (FileAnnotation value : values) {
annotations.putValue(virtualFile, value);
}
}
}
}
if (! annotations.isEmpty()) {
for (Map.Entry<VirtualFile, Collection<FileAnnotation>> entry : annotations.entrySet()) {
final VirtualFile key = entry.getKey();
final VcsRevisionNumber number = fromDiffProvider(key);
if (number == null) continue;
final Collection<FileAnnotation> fileAnnotations = entry.getValue();
for (FileAnnotation annotation : fileAnnotations) {
if (annotation.isBaseRevisionChanged(number)) {
annotation.close();
}
}
}
}
}
private void refreshForPath(String path, VcsRevisionNumber number) {
final File file = new File(path);
VirtualFile vf = myLocalFileSystem.findFileByIoFile(file);
if (vf == null) {
vf = myLocalFileSystem.refreshAndFindFileByIoFile(file);
}
if (vf == null) return;
processFile(number, vf);
}
private void processFile(VcsRevisionNumber number, VirtualFile vf) {
final Collection<FileAnnotation> annotations;
synchronized (myLock) {
annotations = myFileAnnotationMap.get(vf);
}
if (! annotations.isEmpty()) {
if (number == null) {
number = fromDiffProvider(vf);
}
if (number == null) return;
for (FileAnnotation annotation : annotations) {
if (annotation.isBaseRevisionChanged(number)) {
annotation.close();
}
}
}
}
private VcsRevisionNumber fromDiffProvider(final VirtualFile vf) {
final VcsRoot vcsRoot = myVcsManager.getVcsRootObjectFor(vf);
DiffProvider diffProvider;
if (vcsRoot != null && vcsRoot.getVcs() != null && (diffProvider = vcsRoot.getVcs().getDiffProvider()) != null) {
return diffProvider.getCurrentRevision(vf);
}
return null;
}
private void closeForVcs(final Set<VcsKey> refresh) {
if (refresh.isEmpty()) return;
final Set<FileAnnotation> copy = new HashSet<FileAnnotation>();
synchronized (myLock) {
for (FileAnnotation annotation : myFileAnnotationMap.values()) {
final VcsKey key = annotation.getVcsKey();
if (key != null && refresh.contains(key)) {
copy.add(annotation);
}
}
}
for (FileAnnotation annotation : copy) {
annotation.close();
}
}
// annotations for already committed revisions should not register with this method - they are not subject to refresh
@Override
public void registerAnnotation(final VirtualFile file, final FileAnnotation annotation) {
synchronized (myLock) {
myFileAnnotationMap.putValue(file, annotation);
}
}
@Override
public void unregisterAnnotation(final VirtualFile file, final FileAnnotation annotation) {
synchronized (myLock) {
final Collection<FileAnnotation> annotations = myFileAnnotationMap.get(file);
if (!annotations.isEmpty()) {
annotations.remove(annotation);
}
if (annotations.isEmpty()) {
myFileAnnotationMap.remove(file);
}
}
}
@Override
public void dispose() {
myConnection.disconnect();
myUpdater.stop();
}
private VcsAnnotationRefresher createHandler() {
return new VcsAnnotationRefresher() {
@Override
public void dirtyUnder(VirtualFile file) {
if (file == null) return;
synchronized (myLock) {
myDirtyFiles.add(file);
}
myUpdater.queue(myUpdateStuff);
}
@Override
public void dirty(BaseRevision currentRevision) {
synchronized (myLock) {
myDirtyChanges.put(currentRevision.getPath(), currentRevision.getRevision());
}
myUpdater.queue(myUpdateStuff);
}
@Override
public void dirty(String path) {
synchronized (myLock) {
myDirtyPaths.add(path);
}
myUpdater.queue(myUpdateStuff);
}
@Override
public void configurationChanged(VcsKey vcsKey) {
synchronized (myLock) {
myVcsKeySet.add(vcsKey);
}
myUpdater.queue(myUpdateStuff);
}
};
}
}