blob: c28a39bf1056096bb78e1a7740f5962e94f0ebf6 [file] [log] [blame]
/*
* Copyright 2000-2014 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.history;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.application.ModalityState;
import com.intellij.openapi.progress.ProcessCanceledException;
import com.intellij.openapi.progress.ProgressIndicator;
import com.intellij.openapi.progress.ProgressManager;
import com.intellij.openapi.progress.Task;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.ThrowableComputable;
import com.intellij.openapi.vcs.*;
import com.intellij.openapi.vcs.changes.BackgroundFromStartOption;
import com.intellij.openapi.vcs.diff.DiffProvider;
import com.intellij.openapi.vcs.diff.ItemLatestState;
import com.intellij.openapi.vcs.impl.BackgroundableActionEnabledHandler;
import com.intellij.openapi.vcs.impl.ProjectLevelVcsManagerImpl;
import com.intellij.openapi.vcs.impl.VcsBackgroundableActions;
import com.intellij.openapi.vcs.impl.VcsBackgroundableComputable;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.util.Consumer;
import com.intellij.vcsUtil.VcsUtil;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;
/**
* also uses memory cache
*/
public class VcsHistoryProviderBackgroundableProxy {
private final Project myProject;
private final DiffProvider myDiffProvider;
private final VcsHistoryProvider myDelegate;
private VcsHistoryCache myVcsHistoryCache;
private boolean myCachesHistory;
private final HistoryComputerFactory myHistoryComputerFactory;
private final VcsType myType;
private VcsConfiguration myConfiguration;
public VcsHistoryProviderBackgroundableProxy(final AbstractVcs vcs, final VcsHistoryProvider delegate, DiffProvider diffProvider) {
myDelegate = delegate;
myProject = vcs.getProject();
myConfiguration = VcsConfiguration.getInstance(myProject);
myCachesHistory = myDelegate instanceof VcsCacheableHistorySessionFactory;
myDiffProvider = diffProvider;
myVcsHistoryCache = ProjectLevelVcsManager.getInstance(myProject).getVcsHistoryCache();
myType = vcs.getType();
myHistoryComputerFactory = new HistoryComputerFactory() {
@Override
public ThrowableComputable<VcsHistorySession, VcsException> create(FilePath filePath,
Consumer<VcsHistorySession> consumer,
VcsKey vcsKey) {
if (myCachesHistory) {
return new CachingHistoryComputer(filePath, consumer, vcsKey);
} else {
return new SimpelHistoryComputer(filePath, consumer);
}
}
};
}
public void createSessionFor(final VcsKey vcsKey, final FilePath filePath, final Consumer<VcsHistorySession> continuation,
@Nullable VcsBackgroundableActions actionKey,
final boolean silent,
@Nullable final Consumer<VcsHistorySession> backgroundSpecialization) {
final ThrowableComputable<VcsHistorySession, VcsException> throwableComputable =
myHistoryComputerFactory.create(filePath, backgroundSpecialization, vcsKey);
final VcsBackgroundableActions resultingActionKey = actionKey == null ? VcsBackgroundableActions.CREATE_HISTORY_SESSION : actionKey;
final Object key = VcsBackgroundableActions.keyFrom(filePath);
if (silent) {
VcsBackgroundableComputable.createAndRunSilent(myProject, resultingActionKey, key, VcsBundle.message("loading.file.history.progress"),
throwableComputable, continuation);
} else {
VcsBackgroundableComputable.createAndRun(myProject, resultingActionKey, key, VcsBundle.message("loading.file.history.progress"),
VcsBundle.message("message.title.could.not.load.file.history"), throwableComputable, continuation, null);
}
}
public void executeAppendableSession(final VcsKey vcsKey, final FilePath filePath, final VcsAppendableHistorySessionPartner partner,
@Nullable VcsBackgroundableActions actionKey, boolean canUseCache, boolean canUseLastRevisionCheck) {
if (myCachesHistory && canUseCache) {
final VcsAbstractHistorySession session = getFullHistoryFromCache(vcsKey, filePath);
if (session != null) {
partner.reportCreatedEmptySession(session);
partner.finished();
partner.forceRefresh();
return;
}
}
final ProjectLevelVcsManagerImpl vcsManager = (ProjectLevelVcsManagerImpl) ProjectLevelVcsManager.getInstance(myProject);
final VcsBackgroundableActions resultingActionKey = actionKey == null ? VcsBackgroundableActions.CREATE_HISTORY_SESSION : actionKey;
final BackgroundableActionEnabledHandler handler;
handler = vcsManager.getBackgroundableActionHandler(resultingActionKey);
// fo not start same action twice
if (handler.isInProgress(resultingActionKey)) return;
handler.register(resultingActionKey);
final VcsAppendableHistorySessionPartner cachedPartner;
if (myCachesHistory) {
cachedPartner = new HistoryPartnerProxy(partner, new Consumer<VcsAbstractHistorySession>() {
@Override
public void consume(VcsAbstractHistorySession session) {
if (session == null) return;
final FilePath correctedPath =
((VcsCacheableHistorySessionFactory<Serializable, VcsAbstractHistorySession>)myDelegate).getUsedFilePath(session);
myVcsHistoryCache.put(filePath, correctedPath, vcsKey, (VcsAbstractHistorySession)session.copy(),
(VcsCacheableHistorySessionFactory<Serializable,VcsAbstractHistorySession>) myDelegate, true);
}
});
} else {
cachedPartner = partner;
}
reportHistory(filePath, vcsKey, resultingActionKey, handler, cachedPartner, canUseLastRevisionCheck);
}
private VcsAbstractHistorySession getFullHistoryFromCache(VcsKey vcsKey, FilePath filePath) {
VcsAbstractHistorySession full =
myVcsHistoryCache.getFull(filePath, vcsKey, (VcsCacheableHistorySessionFactory<Serializable, VcsAbstractHistorySession>)myDelegate);
if (full != null) {
if (myConfiguration.LIMIT_HISTORY) {
if (myConfiguration.MAXIMUM_HISTORY_ROWS < full.getRevisionList().size()) {
final List<VcsFileRevision> list = full.getRevisionList();
final List<VcsFileRevision> was = new ArrayList<VcsFileRevision>(list.subList(0, myConfiguration.MAXIMUM_HISTORY_ROWS));
list.clear();
list.addAll(was);
}
}
}
return full;
}
private void reportHistory(final FilePath filePath, final VcsKey vcsKey,
final VcsBackgroundableActions resultingActionKey,
final BackgroundableActionEnabledHandler handler,
final VcsAppendableHistorySessionPartner cachedPartner, final boolean canUseLastRevisionCheck) {
ProgressManager.getInstance().run(new Task.Backgroundable(myProject, VcsBundle.message("loading.file.history.progress"),
true, BackgroundFromStartOption.getInstance()) {
public void run(@NotNull ProgressIndicator indicator) {
if (indicator != null) {
indicator.setText(VcsUtil.getPathForProgressPresentation(filePath.getIOFile()));
}
try {
VcsHistorySession cachedSession = null;
if (canUseLastRevisionCheck && myCachesHistory && ((cachedSession = getSessionFromCacheWithLastRevisionCheck(filePath, vcsKey))) != null) {
cachedPartner.reportCreatedEmptySession((VcsAbstractHistorySession)cachedSession);
} else {
myDelegate.reportAppendableHistory(filePath, cachedPartner);
}
}
catch (VcsException e) {
cachedPartner.reportException(e);
}
finally {
cachedPartner.finished();
ApplicationManager.getApplication().invokeLater(new Runnable() {
public void run() {
handler.completed(resultingActionKey);
}
}, ModalityState.NON_MODAL);
}
}
});
}
private static class HistoryPartnerProxy implements VcsAppendableHistorySessionPartner {
private final VcsAppendableHistorySessionPartner myPartner;
private final Consumer<VcsAbstractHistorySession> myFinish;
private VcsAbstractHistorySession myCopy;
private HistoryPartnerProxy(VcsAppendableHistorySessionPartner partner, final Consumer<VcsAbstractHistorySession> finish) {
myPartner = partner;
myFinish = finish;
}
@Override
public void reportCreatedEmptySession(VcsAbstractHistorySession session) {
myCopy = (VcsAbstractHistorySession) session.copy();
myPartner.reportCreatedEmptySession(session);
}
@Override
public void acceptRevision(VcsFileRevision revision) {
myCopy.appendRevision(revision);
myPartner.acceptRevision(revision);
}
@Override
public void reportException(VcsException exception) {
myPartner.reportException(exception);
}
@Override
public void finished() {
myPartner.finished();
myFinish.consume(myCopy);
}
@Override
public void beforeRefresh() {
myPartner.beforeRefresh();
}
@Override
public void forceRefresh() {
myPartner.forceRefresh();
}
}
private interface HistoryComputerFactory {
ThrowableComputable<VcsHistorySession, VcsException> create(FilePath filePath, Consumer<VcsHistorySession> consumer, VcsKey vcsKey);
}
private class SimpelHistoryComputer implements ThrowableComputable<VcsHistorySession, VcsException> {
private final FilePath myFilePath;
private final Consumer<VcsHistorySession> myConsumer;
private SimpelHistoryComputer(FilePath filePath, Consumer<VcsHistorySession> consumer) {
myFilePath = filePath;
myConsumer = consumer;
}
@Override
public VcsHistorySession compute() throws VcsException {
VcsHistorySession session = createSessionWithLimitCheck(myFilePath);
if (myConsumer != null) {
myConsumer.consume(session);
}
return session;
}
}
private VcsHistorySession createSessionWithLimitCheck(final FilePath filePath) throws VcsException {
final LimitHistoryCheck check = new LimitHistoryCheck(myProject, filePath.getPath());
final VcsAppendableHistoryPartnerAdapter partner = new VcsAppendableHistoryPartnerAdapter() {
@Override
public void acceptRevision(VcsFileRevision revision) {
check.checkNumber();
super.acceptRevision(revision);
}
};
try {
myDelegate.reportAppendableHistory(filePath, partner);
} catch (ProcessCanceledException e) {
if (! check.isOver()) throw e;
}
return partner.getSession();
}
private class CachingHistoryComputer implements ThrowableComputable<VcsHistorySession, VcsException> {
private final FilePath myFilePath;
private final Consumer<VcsHistorySession> myConsumer;
private final VcsKey myVcsKey;
private CachingHistoryComputer(FilePath filePath, Consumer<VcsHistorySession> consumer, VcsKey vcsKey) {
myFilePath = filePath;
myConsumer = consumer;
myVcsKey = vcsKey;
}
@Override
public VcsHistorySession compute() throws VcsException {
VcsHistorySession session = null;
// we check for the last revision, since requests to this exact method at the moment only request history once, and no refresh is possible later
session = getSessionFromCacheWithLastRevisionCheck(myFilePath, myVcsKey);
if (session == null) {
session = createSessionWithLimitCheck(myFilePath);
final FilePath correctedPath =
((VcsCacheableHistorySessionFactory<Serializable, VcsAbstractHistorySession>)myDelegate).getUsedFilePath(
(VcsAbstractHistorySession)session);
myVcsHistoryCache.put(myFilePath, correctedPath, myVcsKey, (VcsAbstractHistorySession)((VcsAbstractHistorySession) session).copy(),
(VcsCacheableHistorySessionFactory<Serializable,VcsAbstractHistorySession>) myDelegate, true);
}
if (myConsumer != null) {
myConsumer.consume(session);
}
return session;
}
}
@Nullable
private VcsHistorySession getSessionFromCacheWithLastRevisionCheck(final FilePath filePath, final VcsKey vcsKey) {
final ProgressIndicator pi = ProgressManager.getInstance().getProgressIndicator();
if (pi != null) {
pi.setText2("Checking last revision");
}
final VcsAbstractHistorySession cached = getFullHistoryFromCache(vcsKey, filePath);
if (cached == null) return null;
final FilePath correctedFilePath =
((VcsCacheableHistorySessionFactory<Serializable, VcsAbstractHistorySession>)myDelegate).getUsedFilePath(cached);
if (VcsType.distributed.equals(myType)) {
final FilePath path = correctedFilePath != null ? correctedFilePath : filePath;
path.hardRefresh();
final VirtualFile virtualFile = path.getVirtualFile();
if (virtualFile != null) {
final VcsRevisionNumber currentRevision = myDiffProvider.getCurrentRevision(virtualFile);
final List<VcsFileRevision> revisionList = cached.getRevisionList();
if (! revisionList.isEmpty() && revisionList.get(0).getRevisionNumber().equals(currentRevision)) {
return cached;
}
}
} else {
final ItemLatestState lastRevision = myDiffProvider.getLastRevision(correctedFilePath != null ? correctedFilePath : filePath);
if (lastRevision != null && ! lastRevision.isDefaultHead() && lastRevision.isItemExists()) {
final List<VcsFileRevision> revisionList = cached.getRevisionList();
if (! revisionList.isEmpty() && revisionList.get(0).getRevisionNumber().equals(lastRevision.getNumber())) {
return cached;
}
}
}
return null;
}
}