| /* |
| * 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; |
| } |
| } |