IDEA-136705 (delay post-execution refresh to allow FS changes to reach the IDE)
[idea/community.git] / platform / platform-impl / src / com / intellij / ide / SaveAndSyncHandlerImpl.java
1 /*
2  * Copyright 2000-2015 JetBrains s.r.o.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 package com.intellij.ide;
17
18 import com.intellij.openapi.application.ApplicationManager;
19 import com.intellij.openapi.application.ModalityState;
20 import com.intellij.openapi.application.ex.ApplicationManagerEx;
21 import com.intellij.openapi.application.impl.LaterInvocator;
22 import com.intellij.openapi.components.ApplicationComponent;
23 import com.intellij.openapi.diagnostic.Logger;
24 import com.intellij.openapi.fileEditor.FileDocumentManager;
25 import com.intellij.openapi.fileEditor.FileEditorManager;
26 import com.intellij.openapi.fileEditor.impl.FileDocumentManagerImpl;
27 import com.intellij.openapi.progress.ProgressManager;
28 import com.intellij.openapi.project.Project;
29 import com.intellij.openapi.project.ProjectManager;
30 import com.intellij.openapi.project.ex.ProjectManagerEx;
31 import com.intellij.openapi.vfs.VirtualFile;
32 import com.intellij.openapi.vfs.newvfs.ManagingFS;
33 import com.intellij.openapi.vfs.newvfs.NewVirtualFile;
34 import com.intellij.openapi.vfs.newvfs.RefreshQueue;
35 import com.intellij.openapi.vfs.newvfs.RefreshSession;
36 import com.intellij.util.SingleAlarm;
37 import com.intellij.util.containers.ContainerUtil;
38 import org.jetbrains.annotations.NotNull;
39
40 import java.beans.PropertyChangeEvent;
41 import java.beans.PropertyChangeListener;
42 import java.util.List;
43 import java.util.concurrent.atomic.AtomicInteger;
44
45 /**
46  * @author Anton Katilin
47  * @author Vladimir Kondratyev
48  */
49 public class SaveAndSyncHandlerImpl extends SaveAndSyncHandler implements ApplicationComponent {
50   private static final Logger LOG = Logger.getInstance(SaveAndSyncHandler.class);
51
52   private final Runnable myIdleListener;
53   private final PropertyChangeListener myGeneralSettingsListener;
54   private final GeneralSettings mySettings;
55   private final ProgressManager myProgressManager;
56   private final SingleAlarm myRefreshDelayAlarm;
57
58   private final AtomicInteger myBlockSaveOnFrameDeactivationCount = new AtomicInteger();
59   private final AtomicInteger myBlockSyncOnFrameActivationCount = new AtomicInteger();
60   private volatile long myRefreshSessionId = 0;
61
62   public SaveAndSyncHandlerImpl(@NotNull GeneralSettings generalSettings,
63                                 @NotNull ProgressManager progressManager,
64                                 @NotNull FrameStateManager frameStateManager,
65                                 @NotNull final FileDocumentManager fileDocumentManager) {
66     mySettings = generalSettings;
67     myProgressManager = progressManager;
68
69     myIdleListener = new Runnable() {
70       @Override
71       public void run() {
72         if (mySettings.isAutoSaveIfInactive() && canSyncOrSave()) {
73           ((FileDocumentManagerImpl)fileDocumentManager).saveAllDocuments(false);
74         }
75       }
76     };
77     IdeEventQueue.getInstance().addIdleListener(myIdleListener, mySettings.getInactiveTimeout() * 1000);
78
79     myGeneralSettingsListener = new PropertyChangeListener() {
80       @Override
81       public void propertyChange(@NotNull PropertyChangeEvent e) {
82         if (GeneralSettings.PROP_INACTIVE_TIMEOUT.equals(e.getPropertyName())) {
83           IdeEventQueue eventQueue = IdeEventQueue.getInstance();
84           eventQueue.removeIdleListener(myIdleListener);
85           Integer timeout = (Integer)e.getNewValue();
86           eventQueue.addIdleListener(myIdleListener, timeout.intValue() * 1000);
87         }
88       }
89     };
90     mySettings.addPropertyChangeListener(myGeneralSettingsListener);
91
92     myRefreshDelayAlarm = new SingleAlarm(new Runnable() {
93       @Override
94       public void run() {
95         if (canSyncOrSave()) {
96           refreshOpenFiles();
97         }
98         maybeRefresh(ModalityState.NON_MODAL);
99       }
100     }, 300);
101
102     frameStateManager.addListener(new FrameStateListener() {
103       @Override
104       public void onFrameDeactivated() {
105         if (canSyncOrSave()) {
106           saveProjectsAndDocuments();
107         }
108       }
109
110       @Override
111       public void onFrameActivated() {
112         if (!ApplicationManager.getApplication().isDisposed() && mySettings.isSyncOnFrameActivation()) {
113           scheduleRefresh();
114         }
115       }
116     });
117   }
118
119   @Override
120   @NotNull
121   public String getComponentName() {
122     return "SaveAndSyncHandler";
123   }
124
125   @Override
126   public void initComponent() { }
127
128   @Override
129   public void disposeComponent() {
130     myRefreshDelayAlarm.cancel();
131     RefreshQueue.getInstance().cancelSession(myRefreshSessionId);
132     mySettings.removePropertyChangeListener(myGeneralSettingsListener);
133     IdeEventQueue.getInstance().removeIdleListener(myIdleListener);
134   }
135
136   private boolean canSyncOrSave() {
137     return !LaterInvocator.isInModalContext() && !myProgressManager.hasModalProgressIndicator();
138   }
139
140   @Override
141   public void saveProjectsAndDocuments() {
142     LOG.debug("enter: save()");
143
144     if (!ApplicationManager.getApplication().isDisposed() &&
145         mySettings.isSaveOnFrameDeactivation() &&
146         myBlockSaveOnFrameDeactivationCount.get() == 0) {
147       FileDocumentManager.getInstance().saveAllDocuments();
148
149       for (Project project : ProjectManagerEx.getInstanceEx().getOpenProjects()) {
150         if (LOG.isDebugEnabled()) LOG.debug("saving project: " + project);
151         project.save();
152       }
153
154       LOG.debug("saving application settings");
155       ApplicationManagerEx.getApplicationEx().saveSettings();
156
157       LOG.debug("exit: save()");
158     }
159   }
160
161   @Override
162   public void scheduleRefresh() {
163     myRefreshDelayAlarm.cancelAndRequest();
164   }
165
166   public void maybeRefresh(@NotNull ModalityState modalityState) {
167     if (myBlockSyncOnFrameActivationCount.get() == 0 && mySettings.isSyncOnFrameActivation()) {
168       RefreshQueue queue = RefreshQueue.getInstance();
169       queue.cancelSession(myRefreshSessionId);
170
171       RefreshSession session = queue.createSession(true, true, null, modalityState);
172       session.addAllFiles(ManagingFS.getInstance().getLocalRoots());
173       myRefreshSessionId = session.getId();
174       session.launch();
175     }
176   }
177
178   @Override
179   public void refreshOpenFiles() {
180     List<VirtualFile> files = ContainerUtil.newArrayList();
181
182     for (Project project : ProjectManager.getInstance().getOpenProjects()) {
183       for (VirtualFile file : FileEditorManager.getInstance(project).getSelectedFiles()) {
184         if (file instanceof NewVirtualFile) {
185           files.add(file);
186         }
187       }
188     }
189
190     if (!files.isEmpty()) {
191       // refresh open files synchronously so it doesn't wait for potentially longish refresh request in the queue to finish
192       RefreshQueue.getInstance().refresh(false, false, null, files);
193     }
194   }
195
196   @Override
197   public void blockSaveOnFrameDeactivation() {
198     myBlockSaveOnFrameDeactivationCount.incrementAndGet();
199   }
200
201   @Override
202   public void unblockSaveOnFrameDeactivation() {
203     myBlockSaveOnFrameDeactivationCount.decrementAndGet();
204   }
205
206   @Override
207   public void blockSyncOnFrameActivation() {
208     myBlockSyncOnFrameActivationCount.incrementAndGet();
209   }
210
211   @Override
212   public void unblockSyncOnFrameActivation() {
213     myBlockSyncOnFrameActivationCount.decrementAndGet();
214   }
215 }