/*
* Copyright 2000-2016 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.ide;
import com.intellij.openapi.Disposable;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.application.ModalityState;
import com.intellij.openapi.application.TransactionGuard;
import com.intellij.openapi.application.impl.LaterInvocator;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.fileEditor.FileDocumentManager;
import com.intellij.openapi.fileEditor.FileEditorManager;
import com.intellij.openapi.fileEditor.impl.FileDocumentManagerImpl;
import com.intellij.openapi.progress.ProgressManager;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.project.ProjectManager;
import com.intellij.openapi.project.ex.ProjectManagerEx;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.openapi.vfs.newvfs.ManagingFS;
import com.intellij.openapi.vfs.newvfs.NewVirtualFile;
import com.intellij.openapi.vfs.newvfs.RefreshQueue;
import com.intellij.openapi.vfs.newvfs.RefreshSession;
import com.intellij.util.SingleAlarm;
import com.intellij.util.containers.ContainerUtil;
import org.jetbrains.annotations.NotNull;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;
/**
* @author Anton Katilin
* @author Vladimir Kondratyev
*/
public class SaveAndSyncHandlerImpl extends SaveAndSyncHandler implements Disposable {
private static final Logger LOG = Logger.getInstance(SaveAndSyncHandler.class);
private final Runnable myIdleListener;
private final PropertyChangeListener myGeneralSettingsListener;
private final GeneralSettings mySettings;
private final ProgressManager myProgressManager;
private final SingleAlarm myRefreshDelayAlarm = new SingleAlarm(new Runnable() {
@Override
public void run() {
doScheduledRefresh();
}
}, 300, this);
private final AtomicInteger myBlockSaveOnFrameDeactivationCount = new AtomicInteger();
private final AtomicInteger myBlockSyncOnFrameActivationCount = new AtomicInteger();
private volatile long myRefreshSessionId;
public SaveAndSyncHandlerImpl(@NotNull GeneralSettings generalSettings,
@NotNull ProgressManager progressManager,
@NotNull FrameStateManager frameStateManager,
@NotNull final FileDocumentManager fileDocumentManager) {
mySettings = generalSettings;
myProgressManager = progressManager;
myIdleListener = new Runnable() {
@Override
public void run() {
if (mySettings.isAutoSaveIfInactive() && SaveAndSyncHandlerImpl.this.canSyncOrSave()) {
TransactionGuard.submitTransaction(ApplicationManager.getApplication(), new Runnable() {
@Override
public void run() {
((FileDocumentManagerImpl)fileDocumentManager).saveAllDocuments(false);
}
});
}
}
};
IdeEventQueue.getInstance().addIdleListener(myIdleListener, mySettings.getInactiveTimeout() * 1000);
myGeneralSettingsListener = new PropertyChangeListener() {
@Override
public void propertyChange(@NotNull PropertyChangeEvent e) {
if (GeneralSettings.PROP_INACTIVE_TIMEOUT.equals(e.getPropertyName())) {
IdeEventQueue eventQueue = IdeEventQueue.getInstance();
eventQueue.removeIdleListener(myIdleListener);
Integer timeout = (Integer)e.getNewValue();
eventQueue.addIdleListener(myIdleListener, timeout.intValue() * 1000);
}
}
};
mySettings.addPropertyChangeListener(myGeneralSettingsListener);
frameStateManager.addListener(new FrameStateListener() {
@Override
public void onFrameDeactivated() {
LOG.debug("save(): enter");
TransactionGuard.submitTransaction(ApplicationManager.getApplication(), new Runnable() {
@Override
public void run() {
if (canSyncOrSave()) {
saveProjectsAndDocuments();
}
LOG.debug("save(): exit");
}
});
}
@Override
public void onFrameActivated() {
if (!ApplicationManager.getApplication().isDisposed() && mySettings.isSyncOnFrameActivation()) {
scheduleRefresh();
}
}
});
}
@Override
public void dispose() {
RefreshQueue.getInstance().cancelSession(myRefreshSessionId);
mySettings.removePropertyChangeListener(myGeneralSettingsListener);
IdeEventQueue.getInstance().removeIdleListener(myIdleListener);
}
private boolean canSyncOrSave() {
return !LaterInvocator.isInModalContext() && !myProgressManager.hasModalProgressIndicator();
}
@Override
public void saveProjectsAndDocuments() {
if (!ApplicationManager.getApplication().isDisposed() &&
mySettings.isSaveOnFrameDeactivation() &&
myBlockSaveOnFrameDeactivationCount.get() == 0) {
doSaveDocumentsAndProjectsAndApp();
}
}
public static void doSaveDocumentsAndProjectsAndApp() {
LOG.debug("saving documents");
FileDocumentManager.getInstance().saveAllDocuments();
for (Project project : ProjectManagerEx.getInstanceEx().getOpenProjects()) {
if (LOG.isDebugEnabled()) {
LOG.debug("saving project: " + project);
}
project.save();
}
LOG.debug("saving application settings");
ApplicationManager.getApplication().saveSettings();
}
@Override
public void scheduleRefresh() {
myRefreshDelayAlarm.cancelAndRequest();
}
private void doScheduledRefresh() {
if (canSyncOrSave()) {
refreshOpenFiles();
}
maybeRefresh(ModalityState.NON_MODAL);
}
public void maybeRefresh(@NotNull ModalityState modalityState) {
if (myBlockSyncOnFrameActivationCount.get() == 0 && mySettings.isSyncOnFrameActivation()) {
RefreshQueue queue = RefreshQueue.getInstance();
queue.cancelSession(myRefreshSessionId);
RefreshSession session = queue.createSession(true, true, null, modalityState);
session.addAllFiles(ManagingFS.getInstance().getLocalRoots());
myRefreshSessionId = session.getId();
session.launch();
LOG.debug("vfs refreshed");
}
else if (LOG.isDebugEnabled()) {
LOG.debug("vfs refresh rejected, blocked: " + (myBlockSyncOnFrameActivationCount.get() != 0)
+ ", isSyncOnFrameActivation: " + mySettings.isSyncOnFrameActivation());
}
}
@Override
public void refreshOpenFiles() {
List<VirtualFile> files = ContainerUtil.newArrayList();
for (Project project : ProjectManager.getInstance().getOpenProjects()) {
for (VirtualFile file : FileEditorManager.getInstance(project).getSelectedFiles()) {
if (file instanceof NewVirtualFile) {
files.add(file);
}
}
}
if (!files.isEmpty()) {
// refresh open files synchronously so it doesn't wait for potentially longish refresh request in the queue to finish
RefreshQueue.getInstance().refresh(false, false, null, files);
}
}
@Override
public void blockSaveOnFrameDeactivation() {
LOG.debug("save blocked");
myBlockSaveOnFrameDeactivationCount.incrementAndGet();
}
@Override
public void unblockSaveOnFrameDeactivation() {
myBlockSaveOnFrameDeactivationCount.decrementAndGet();
LOG.debug("save unblocked");
}
@Override
public void blockSyncOnFrameActivation() {
LOG.debug("sync blocked");
myBlockSyncOnFrameActivationCount.incrementAndGet();
}
@Override
public void unblockSyncOnFrameActivation() {
myBlockSyncOnFrameActivationCount.decrementAndGet();
LOG.debug("sync unblocked");
}
}