IDEA-CR-1259 (project properties check separated from initial VFS refresh)
[idea/community.git] / platform / platform-impl / src / com / intellij / ide / startup / impl / StartupManagerImpl.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.startup.impl;
17
18 import com.intellij.ide.caches.CacheUpdater;
19 import com.intellij.ide.startup.StartupManagerEx;
20 import com.intellij.notification.Notification;
21 import com.intellij.notification.NotificationListener;
22 import com.intellij.notification.NotificationType;
23 import com.intellij.notification.Notifications;
24 import com.intellij.openapi.application.AccessToken;
25 import com.intellij.openapi.application.Application;
26 import com.intellij.openapi.application.ApplicationBundle;
27 import com.intellij.openapi.application.ApplicationManager;
28 import com.intellij.openapi.diagnostic.Logger;
29 import com.intellij.openapi.extensions.Extensions;
30 import com.intellij.openapi.progress.ProcessCanceledException;
31 import com.intellij.openapi.progress.ProgressIndicator;
32 import com.intellij.openapi.progress.ProgressManager;
33 import com.intellij.openapi.project.*;
34 import com.intellij.openapi.project.impl.ProjectLifecycleListener;
35 import com.intellij.openapi.roots.ProjectRootManager;
36 import com.intellij.openapi.startup.StartupActivity;
37 import com.intellij.openapi.util.SystemInfo;
38 import com.intellij.openapi.util.io.FileUtil;
39 import com.intellij.openapi.util.registry.Registry;
40 import com.intellij.openapi.vfs.LocalFileSystem;
41 import com.intellij.openapi.vfs.VirtualFile;
42 import com.intellij.openapi.vfs.VirtualFileManager;
43 import com.intellij.openapi.vfs.impl.local.FileWatcher;
44 import com.intellij.openapi.vfs.impl.local.LocalFileSystemImpl;
45 import com.intellij.openapi.vfs.newvfs.RefreshQueue;
46 import com.intellij.util.SmartList;
47 import com.intellij.util.containers.ContainerUtil;
48 import com.intellij.util.io.storage.HeavyProcessLatch;
49 import com.intellij.util.messages.MessageBusConnection;
50 import com.intellij.util.ui.UIUtil;
51 import org.jetbrains.annotations.NotNull;
52 import org.jetbrains.annotations.TestOnly;
53
54 import java.io.FileNotFoundException;
55 import java.util.Collections;
56 import java.util.LinkedList;
57 import java.util.List;
58
59 public class StartupManagerImpl extends StartupManagerEx {
60   private static final Logger LOG = Logger.getInstance("#com.intellij.ide.startup.impl.StartupManagerImpl");
61
62   private final List<Runnable> myPreStartupActivities = Collections.synchronizedList(new LinkedList<Runnable>());
63   private final List<Runnable> myStartupActivities = Collections.synchronizedList(new LinkedList<Runnable>());
64
65   private final List<Runnable> myDumbAwarePostStartupActivities = Collections.synchronizedList(new LinkedList<Runnable>());
66   private final List<Runnable> myNotDumbAwarePostStartupActivities = Collections.synchronizedList(new LinkedList<Runnable>());
67   private boolean myPostStartupActivitiesPassed = false; // guarded by this
68
69   @SuppressWarnings("deprecation") private final List<CacheUpdater> myCacheUpdaters = ContainerUtil.newLinkedList();
70
71   private volatile boolean myPreStartupActivitiesPassed = false;
72   private volatile boolean myStartupActivitiesRunning = false;
73   private volatile boolean myStartupActivitiesPassed = false;
74
75   private final Project myProject;
76
77   public StartupManagerImpl(Project project) {
78     myProject = project;
79   }
80
81   @Override
82   public void registerPreStartupActivity(@NotNull Runnable runnable) {
83     LOG.assertTrue(!myPreStartupActivitiesPassed, "Registering pre startup activity that will never be run");
84     myPreStartupActivities.add(runnable);
85   }
86
87   @Override
88   public void registerStartupActivity(@NotNull Runnable runnable) {
89     LOG.assertTrue(!myStartupActivitiesPassed, "Registering startup activity that will never be run");
90     myStartupActivities.add(runnable);
91   }
92
93   @Override
94   public synchronized void registerPostStartupActivity(@NotNull Runnable runnable) {
95     LOG.assertTrue(!myPostStartupActivitiesPassed, "Registering post-startup activity that will never be run");
96     (DumbService.isDumbAware(runnable) ? myDumbAwarePostStartupActivities : myNotDumbAwarePostStartupActivities).add(runnable);
97   }
98
99   @SuppressWarnings("deprecation")
100   @Override
101   public void registerCacheUpdater(@NotNull CacheUpdater updater) {
102     LOG.assertTrue(!myStartupActivitiesPassed, CacheUpdater.class.getSimpleName() + " must be registered before startup activity finished");
103     myCacheUpdaters.add(updater);
104   }
105
106   @Override
107   public boolean startupActivityRunning() {
108     return myStartupActivitiesRunning;
109   }
110
111   @Override
112   public boolean startupActivityPassed() {
113     return myStartupActivitiesPassed;
114   }
115
116   @Override
117   public synchronized boolean postStartupActivityPassed() {
118     return myPostStartupActivitiesPassed;
119   }
120
121   public void runStartupActivities() {
122     ApplicationManager.getApplication().runReadAction(new Runnable() {
123       @Override
124       @SuppressWarnings("SynchronizeOnThis")
125       public void run() {
126         AccessToken token = HeavyProcessLatch.INSTANCE.processStarted("Running Startup Activities");
127         try {
128           runActivities(myPreStartupActivities);
129
130           // to avoid atomicity issues if runWhenProjectIsInitialized() is run at the same time
131           synchronized (StartupManagerImpl.this) {
132             myPreStartupActivitiesPassed = true;
133             myStartupActivitiesRunning = true;
134           }
135
136           runActivities(myStartupActivities);
137
138           synchronized (StartupManagerImpl.this) {
139             myStartupActivitiesRunning = false;
140             myStartupActivitiesPassed = true;
141           }
142         }
143         finally {
144           token.finish();
145         }
146       }
147     });
148   }
149
150   public void runPostStartupActivitiesFromExtensions() {
151     for (final StartupActivity extension : Extensions.getExtensions(StartupActivity.POST_STARTUP_ACTIVITY)) {
152       final Runnable runnable = new Runnable() {
153         @Override
154         public void run() {
155           if (!myProject.isDisposed()) {
156             extension.runActivity(myProject);
157           }
158         }
159       };
160       if (extension instanceof DumbAware) {
161         runActivity(runnable);
162       }
163       else {
164         queueSmartModeActivity(runnable);
165       }
166     }
167   }
168
169   // queue each activity in smart mode separately so that if one of them starts dumb mode, the next ones just wait for it to finish
170   private void queueSmartModeActivity(final Runnable activity) {
171     DumbService.getInstance(myProject).runWhenSmart(new Runnable() {
172       @Override
173       public void run() {
174         runActivity(activity);
175       }
176     });
177   }
178
179   public void runPostStartupActivities() {
180     if (postStartupActivityPassed()) {
181       return;
182     }
183
184     final Application app = ApplicationManager.getApplication();
185
186     if (!app.isHeadlessEnvironment()) {
187       checkFsSanity();
188       checkProjectRoots();
189     }
190
191     runActivities(myDumbAwarePostStartupActivities);
192
193     DumbService.getInstance(myProject).runWhenSmart(new Runnable() {
194       @Override
195       public void run() {
196         app.assertIsDispatchThread();
197
198         // myDumbAwarePostStartupActivities might be non-empty if new activities were registered during dumb mode
199         runActivities(myDumbAwarePostStartupActivities);
200
201         //noinspection SynchronizeOnThis
202         synchronized (StartupManagerImpl.this) {
203           if (!myNotDumbAwarePostStartupActivities.isEmpty()) {
204             while (!myNotDumbAwarePostStartupActivities.isEmpty()) {
205               queueSmartModeActivity(myNotDumbAwarePostStartupActivities.remove(0));
206             }
207
208             // return here later to set myPostStartupActivitiesPassed
209             DumbService.getInstance(myProject).runWhenSmart(this);
210           }
211           else {
212             myPostStartupActivitiesPassed = true;
213           }
214         }
215       }
216     });
217
218     Registry.get("ide.firstStartup").setValue(false);
219   }
220
221   public void scheduleInitialVfsRefresh() {
222     UIUtil.invokeLaterIfNeeded(new Runnable() {
223       @Override
224       public void run() {
225         if (myProject.isDisposed()) return;
226
227         Application app = ApplicationManager.getApplication();
228         if (!app.isHeadlessEnvironment()) {
229           final long sessionId = VirtualFileManager.getInstance().asyncRefresh(null);
230           final MessageBusConnection connection = app.getMessageBus().connect();
231           connection.subscribe(ProjectLifecycleListener.TOPIC, new ProjectLifecycleListener.Adapter() {
232             @Override
233             public void afterProjectClosed(@NotNull Project project) {
234               RefreshQueue.getInstance().cancelSession(sessionId);
235               connection.disconnect();
236             }
237           });
238         }
239         else {
240           VirtualFileManager.getInstance().syncRefresh();
241         }
242       }
243     });
244   }
245
246   private void checkFsSanity() {
247     try {
248       String path = myProject.getProjectFilePath();
249       boolean actual = FileUtil.isFileSystemCaseSensitive(path);
250       LOG.info(path + " case-sensitivity: " + actual);
251       if (actual != SystemInfo.isFileSystemCaseSensitive) {
252         int prefix = SystemInfo.isFileSystemCaseSensitive ? 1 : 0;  // IDE=true -> FS=false -> prefix='in'
253         String title = ApplicationBundle.message("fs.case.sensitivity.mismatch.title");
254         String text = ApplicationBundle.message("fs.case.sensitivity.mismatch.message", prefix);
255         Notifications.Bus.notify(
256           new Notification(Notifications.SYSTEM_MESSAGES_GROUP_ID, title, text, NotificationType.WARNING, NotificationListener.URL_OPENING_LISTENER),
257           myProject);
258       }
259     }
260     catch (FileNotFoundException e) {
261       LOG.warn(e);
262     }
263   }
264
265   private void checkProjectRoots() {
266     LocalFileSystem fs = LocalFileSystem.getInstance();
267     if (!(fs instanceof LocalFileSystemImpl)) return;
268     FileWatcher watcher = ((LocalFileSystemImpl)fs).getFileWatcher();
269     if (!watcher.isOperational()) return;
270     List<String> manualWatchRoots = watcher.getManualWatchRoots();
271     if (manualWatchRoots.isEmpty()) return;
272     VirtualFile[] roots = ProjectRootManager.getInstance(myProject).getContentRoots();
273     if (roots.length == 0) return;
274
275     List<String> nonWatched = new SmartList<String>();
276     for (VirtualFile root : roots) {
277       if (!(root.getFileSystem() instanceof LocalFileSystem)) continue;
278       String rootPath = root.getPath();
279       for (String manualWatchRoot : manualWatchRoots) {
280         if (FileUtil.isAncestor(manualWatchRoot, rootPath, false)) {
281           nonWatched.add(rootPath);
282         }
283       }
284     }
285
286     if (!nonWatched.isEmpty()) {
287       String message = ApplicationBundle.message("watcher.non.watchable.project");
288       watcher.notifyOnFailure(message, null);
289       LOG.info("unwatched roots: " + nonWatched);
290       LOG.info("manual watches: " + manualWatchRoots);
291     }
292   }
293
294   public void startCacheUpdate() {
295     try {
296       DumbServiceImpl dumbService = DumbServiceImpl.getInstance(myProject);
297
298       if (!ApplicationManager.getApplication().isUnitTestMode()) {
299         // pre-startup activities have registered dumb tasks that load VFS (scanning files to index)
300         // only after these tasks pass does VFS refresh make sense
301         dumbService.queueTask(new DumbModeTask() {
302           @Override
303           public void performInDumbMode(@NotNull ProgressIndicator indicator) {
304             scheduleInitialVfsRefresh();
305           }
306
307           @Override
308           public String toString() {
309             return "initial refresh";
310           }
311         });
312       }
313
314       if (!myCacheUpdaters.isEmpty()) {
315         dumbService.queueCacheUpdateInDumbMode(myCacheUpdaters);
316       }
317     }
318     catch (ProcessCanceledException e) {
319       throw e;
320     }
321     catch (Throwable e) {
322       LOG.error(e);
323     }
324   }
325
326   private static void runActivities(@NotNull List<Runnable> activities) {
327     while (!activities.isEmpty()) {
328       runActivity(activities.remove(0));
329     }
330   }
331
332   private static void runActivity(Runnable runnable) {
333     ProgressManager.checkCanceled();
334
335     try {
336       runnable.run();
337     }
338     catch (ProcessCanceledException e) {
339       throw e;
340     }
341     catch (Throwable ex) {
342       LOG.error(ex);
343     }
344   }
345
346   @Override
347   public void runWhenProjectIsInitialized(@NotNull final Runnable action) {
348     final Application application = ApplicationManager.getApplication();
349     if (application == null) return;
350
351     //noinspection SynchronizeOnThis
352     synchronized (this) {
353       // in tests which simulate project opening, post-startup activities could have been run already.
354       // Then we should act as if the project was initialized
355       boolean initialized = myProject.isInitialized() || application.isUnitTestMode() && myPostStartupActivitiesPassed;
356       if (!initialized) {
357         registerPostStartupActivity(action);
358         return;
359       }
360     }
361
362     UIUtil.invokeLaterIfNeeded(new Runnable() {
363       @Override
364       public void run() {
365         if (!myProject.isDisposed()) {
366           action.run();
367         }
368       }
369     });
370   }
371
372   @TestOnly
373   public synchronized void prepareForNextTest() {
374     myPreStartupActivities.clear();
375     myStartupActivities.clear();
376     myDumbAwarePostStartupActivities.clear();
377     myNotDumbAwarePostStartupActivities.clear();
378     myCacheUpdaters.clear();
379   }
380
381   @TestOnly
382   public synchronized void checkCleared() {
383     try {
384       assert myStartupActivities.isEmpty() : "Activities: " + myStartupActivities;
385       assert myDumbAwarePostStartupActivities.isEmpty() : "DumbAware Post Activities: " + myDumbAwarePostStartupActivities;
386       assert myNotDumbAwarePostStartupActivities.isEmpty() : "Post Activities: " + myNotDumbAwarePostStartupActivities;
387       assert myPreStartupActivities.isEmpty() : "Pre Activities: " + myPreStartupActivities;
388     }
389     finally {
390       prepareForNextTest();
391     }
392   }
393 }