extra diagnostic for VFS refresh (https://jetbrains.zendesk.com/agent/tickets/80159)
[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.Disposable;
19 import com.intellij.openapi.application.ApplicationManager;
20 import com.intellij.openapi.application.ModalityState;
21 import com.intellij.openapi.application.impl.LaterInvocator;
22 import com.intellij.openapi.diagnostic.Logger;
23 import com.intellij.openapi.fileEditor.FileDocumentManager;
24 import com.intellij.openapi.fileEditor.FileEditorManager;
25 import com.intellij.openapi.fileEditor.impl.FileDocumentManagerImpl;
26 import com.intellij.openapi.progress.ProgressManager;
27 import com.intellij.openapi.project.Project;
28 import com.intellij.openapi.project.ProjectManager;
29 import com.intellij.openapi.project.ex.ProjectManagerEx;
30 import com.intellij.openapi.vfs.VirtualFile;
31 import com.intellij.openapi.vfs.newvfs.ManagingFS;
32 import com.intellij.openapi.vfs.newvfs.NewVirtualFile;
33 import com.intellij.openapi.vfs.newvfs.RefreshQueue;
34 import com.intellij.openapi.vfs.newvfs.RefreshSession;
35 import com.intellij.util.SingleAlarm;
36 import com.intellij.util.containers.ContainerUtil;
37 import org.jetbrains.annotations.NotNull;
38
39 import java.beans.PropertyChangeEvent;
40 import java.beans.PropertyChangeListener;
41 import java.util.List;
42 import java.util.concurrent.atomic.AtomicInteger;
43
44 /**
45  * @author Anton Katilin
46  * @author Vladimir Kondratyev
47  */
48 public class SaveAndSyncHandlerImpl extends SaveAndSyncHandler implements Disposable {
49   private static final Logger LOG = Logger.getInstance(SaveAndSyncHandler.class);
50
51   private final Runnable myIdleListener;
52   private final PropertyChangeListener myGeneralSettingsListener;
53   private final GeneralSettings mySettings;
54   private final ProgressManager myProgressManager;
55   private final SingleAlarm myRefreshDelayAlarm = new SingleAlarm(new Runnable() {
56     @Override
57     public void run() {
58       if (canSyncOrSave()) {
59         refreshOpenFiles();
60       }
61       maybeRefresh(ModalityState.NON_MODAL);
62     }
63   }, 300, this);
64
65   private final AtomicInteger myBlockSaveOnFrameDeactivationCount = new AtomicInteger();
66   private final AtomicInteger myBlockSyncOnFrameActivationCount = new AtomicInteger();
67   private volatile long myRefreshSessionId = 0;
68
69   public SaveAndSyncHandlerImpl(@NotNull GeneralSettings generalSettings,
70                                 @NotNull ProgressManager progressManager,
71                                 @NotNull FrameStateManager frameStateManager,
72                                 @NotNull final FileDocumentManager fileDocumentManager) {
73     mySettings = generalSettings;
74     myProgressManager = progressManager;
75
76     myIdleListener = new Runnable() {
77       @Override
78       public void run() {
79         if (mySettings.isAutoSaveIfInactive() && canSyncOrSave()) {
80           ((FileDocumentManagerImpl)fileDocumentManager).saveAllDocuments(false);
81         }
82       }
83     };
84     IdeEventQueue.getInstance().addIdleListener(myIdleListener, mySettings.getInactiveTimeout() * 1000);
85
86     myGeneralSettingsListener = new PropertyChangeListener() {
87       @Override
88       public void propertyChange(@NotNull PropertyChangeEvent e) {
89         if (GeneralSettings.PROP_INACTIVE_TIMEOUT.equals(e.getPropertyName())) {
90           IdeEventQueue eventQueue = IdeEventQueue.getInstance();
91           eventQueue.removeIdleListener(myIdleListener);
92           Integer timeout = (Integer)e.getNewValue();
93           eventQueue.addIdleListener(myIdleListener, timeout.intValue() * 1000);
94         }
95       }
96     };
97     mySettings.addPropertyChangeListener(myGeneralSettingsListener);
98
99     frameStateManager.addListener(new FrameStateListener() {
100       @Override
101       public void onFrameDeactivated() {
102         LOG.debug("save(): enter");
103         if (canSyncOrSave()) {
104           saveProjectsAndDocuments();
105         }
106         LOG.debug("save(): exit");
107       }
108
109       @Override
110       public void onFrameActivated() {
111         if (!ApplicationManager.getApplication().isDisposed() && mySettings.isSyncOnFrameActivation()) {
112           scheduleRefresh();
113         }
114       }
115     });
116   }
117
118   @Override
119   public void dispose() {
120     RefreshQueue.getInstance().cancelSession(myRefreshSessionId);
121     mySettings.removePropertyChangeListener(myGeneralSettingsListener);
122     IdeEventQueue.getInstance().removeIdleListener(myIdleListener);
123   }
124
125   private boolean canSyncOrSave() {
126     return !LaterInvocator.isInModalContext() && !myProgressManager.hasModalProgressIndicator();
127   }
128
129   @Override
130   public void saveProjectsAndDocuments() {
131     if (!ApplicationManager.getApplication().isDisposed() &&
132         mySettings.isSaveOnFrameDeactivation() &&
133         myBlockSaveOnFrameDeactivationCount.get() == 0) {
134       doSaveDocumentsAndProjectsAndApp();
135     }
136   }
137
138   public static void doSaveDocumentsAndProjectsAndApp() {
139     LOG.debug("saving documents");
140     FileDocumentManager.getInstance().saveAllDocuments();
141
142     for (Project project : ProjectManagerEx.getInstanceEx().getOpenProjects()) {
143       if (LOG.isDebugEnabled()) {
144         LOG.debug("saving project: " + project);
145       }
146       project.save();
147     }
148
149     LOG.debug("saving application settings");
150     ApplicationManager.getApplication().saveSettings();
151   }
152
153   @Override
154   public void scheduleRefresh() {
155     myRefreshDelayAlarm.cancelAndRequest();
156   }
157
158   public void maybeRefresh(@NotNull ModalityState modalityState) {
159     if (myBlockSyncOnFrameActivationCount.get() == 0 && mySettings.isSyncOnFrameActivation()) {
160       RefreshQueue queue = RefreshQueue.getInstance();
161       queue.cancelSession(myRefreshSessionId);
162
163       RefreshSession session = queue.createSession(true, true, null, modalityState);
164       session.addAllFiles(ManagingFS.getInstance().getLocalRoots());
165       myRefreshSessionId = session.getId();
166       session.launch();
167       LOG.debug("vfs refreshed");
168     }
169     else if (LOG.isDebugEnabled()) {
170       LOG.debug("vfs refresh rejected, blocked: " + (myBlockSyncOnFrameActivationCount.get() != 0)
171                 + ", isSyncOnFrameActivation: " + mySettings.isSyncOnFrameActivation());
172     }
173   }
174
175   @Override
176   public void refreshOpenFiles() {
177     List<VirtualFile> files = ContainerUtil.newArrayList();
178
179     for (Project project : ProjectManager.getInstance().getOpenProjects()) {
180       for (VirtualFile file : FileEditorManager.getInstance(project).getSelectedFiles()) {
181         if (file instanceof NewVirtualFile) {
182           files.add(file);
183         }
184       }
185     }
186
187     if (!files.isEmpty()) {
188       // refresh open files synchronously so it doesn't wait for potentially longish refresh request in the queue to finish
189       RefreshQueue.getInstance().refresh(false, false, null, files);
190     }
191   }
192
193   @Override
194   public void blockSaveOnFrameDeactivation() {
195     LOG.debug("save blocked");
196     myBlockSaveOnFrameDeactivationCount.incrementAndGet();
197   }
198
199   @Override
200   public void unblockSaveOnFrameDeactivation() {
201     myBlockSaveOnFrameDeactivationCount.decrementAndGet();
202     LOG.debug("save unblocked");
203   }
204
205   @Override
206   public void blockSyncOnFrameActivation() {
207     LOG.debug("sync blocked");
208     myBlockSyncOnFrameActivationCount.incrementAndGet();
209   }
210
211   @Override
212   public void unblockSyncOnFrameActivation() {
213     myBlockSyncOnFrameActivationCount.decrementAndGet();
214     LOG.debug("sync unblocked");
215   }
216 }