blob: d5e197604171d5e293c72df626a9351624273033 [file] [log] [blame]
/*
* Copyright 2000-2009 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.project.Project;
import com.intellij.openapi.util.Pair;
import com.intellij.openapi.vcs.*;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.util.containers.MultiMap;
import org.jetbrains.annotations.Nullable;
import java.io.File;
import java.util.*;
public class RemoteRevisionsStateCache implements ChangesOnServerTracker {
private final static long DISCRETE = 3600000;
// All files that were checked during cache update and were not invalidated.
// pair.First - if file is changed (true means changed)
// pair.Second - vcs root where file belongs to
private final Map<String, Pair<Boolean, VcsRoot>> myChanged;
// All files that needs to be checked during next cache update, grouped by vcs root
private final MultiMap<VcsRoot, String> myQueries;
// All vcs roots for which cache update was performed with update timestamp
private final Map<VcsRoot, Long> myTs;
private final Object myLock;
private final ProjectLevelVcsManager myVcsManager;
private final VcsConfiguration myVcsConfiguration;
RemoteRevisionsStateCache(final Project project) {
myVcsManager = ProjectLevelVcsManager.getInstance(project);
myChanged = new HashMap<String, Pair<Boolean, VcsRoot>>();
myQueries = new MultiMap<VcsRoot, String>();
myTs = new HashMap<VcsRoot, Long>();
myLock = new Object();
myVcsConfiguration = VcsConfiguration.getInstance(project);
}
public void invalidate(final Collection<String> paths) {
synchronized (myLock) {
for (String path : paths) {
myChanged.remove(path);
}
}
}
@Nullable
private VirtualFile getRootForPath(final String s) {
return myVcsManager.getVcsRootFor(new FilePathImpl(new File(s), false));
}
public boolean isUpToDate(final Change change) {
final List<File> files = ChangesUtil.getIoFilesFromChanges(Collections.singletonList(change));
synchronized (myLock) {
for (File file : files) {
final String path = file.getAbsolutePath();
final Pair<Boolean, VcsRoot> data = myChanged.get(path);
if (data != null && Boolean.TRUE.equals(data.getFirst())) return false;
}
}
return true;
}
public void plus(final Pair<String, AbstractVcs> pair) {
final VirtualFile root = getRootForPath(pair.getFirst());
if (root == null) return;
synchronized (myLock) {
myQueries.putValue(new VcsRoot(pair.getSecond(), root), pair.getFirst());
}
}
public void minus(Pair<String, AbstractVcs> pair) {
final VirtualFile root = getRootForPath(pair.getFirst());
if (root == null) return;
synchronized (myLock) {
final VcsRoot key = new VcsRoot(pair.getSecond(), root);
if (myQueries.containsKey(key)) {
myQueries.remove(key, pair.getFirst());
}
myChanged.remove(pair.getFirst());
}
}
public void directoryMappingChanged() {
// todo will work?
synchronized (myLock) {
myChanged.clear();
myTs.clear();
}
}
public boolean updateStep() {
final MultiMap<VcsRoot, String> dirty = new MultiMap<VcsRoot, String>();
final long oldPoint = System.currentTimeMillis() - (myVcsConfiguration.CHANGED_ON_SERVER_INTERVAL > 0 ?
myVcsConfiguration.CHANGED_ON_SERVER_INTERVAL * 60000 : DISCRETE);
synchronized (myLock) {
// just copies myQueries MultiMap to dirty MultiMap
for (VcsRoot root : myQueries.keySet()) {
final Collection<String> collection = myQueries.get(root);
for (String s : collection) {
dirty.putValue(root, s);
}
}
myQueries.clear();
// collect roots for which cache update should be performed (by timestamp)
final Set<VcsRoot> roots = new HashSet<VcsRoot>();
for (Map.Entry<VcsRoot, Long> entry : myTs.entrySet()) {
// ignore timestamp, as still remote changes checking is required
// TODO: why not to add in roots anyway??? - as dirty is still checked when adding myChanged files.
if (! dirty.get(entry.getKey()).isEmpty()) continue;
// update only if timeout expired
final Long ts = entry.getValue();
if ((ts == null) || (oldPoint > ts)) {
roots.add(entry.getKey());
}
}
// Add dirty files from those vcs roots, that
// - needs to be update by timestamp criteria
// - that already contain files for update through manually added requests
for (Map.Entry<String, Pair<Boolean, VcsRoot>> entry : myChanged.entrySet()) {
final VcsRoot vcsRoot = entry.getValue().getSecond();
if ((! dirty.get(vcsRoot).isEmpty()) || roots.contains(vcsRoot)) {
dirty.putValue(vcsRoot, entry.getKey());
}
}
}
if (dirty.isEmpty()) return false;
final Map<String, Pair<Boolean, VcsRoot>> results = new HashMap<String, Pair<Boolean, VcsRoot>>();
for (VcsRoot vcsRoot : dirty.keySet()) {
// todo - actually it means nothing since the only known VCS to use this scheme is Git and now it always allow
// todo - background operations. when it changes, develop more flexible behavior here
if (! vcsRoot.getVcs().isVcsBackgroundOperationsAllowed(vcsRoot.getPath())) continue;
final TreeDiffProvider provider = vcsRoot.getVcs().getTreeDiffProvider();
if (provider == null) continue;
final Collection<String> paths = dirty.get(vcsRoot);
final Collection<String> remotelyChanged = provider.getRemotelyChanged(vcsRoot.getPath(), paths);
for (String path : paths) {
// TODO: Contains invoked for each file - better to use Set (implementations just use List)
// TODO: Why to store boolean for changed or not - why not just remove such values from myChanged???
results.put(path, new Pair<Boolean, VcsRoot>(remotelyChanged.contains(path), vcsRoot));
}
}
final long curTime = System.currentTimeMillis();
synchronized (myLock) {
myChanged.putAll(results);
for (VcsRoot vcsRoot : dirty.keySet()) {
myTs.put(vcsRoot, curTime);
}
}
return true;
}
}