IDEA-138072 'Save' operation does nothing if there are unread 'could not save project' messages in Event Log

This commit is contained in:
Vladimir Krivosheev
2015-03-25 14:32:24 +01:00
parent 2792dd8fd9
commit 8aa8c822b7
4 changed files with 109 additions and 102 deletions
@@ -185,7 +185,6 @@ public abstract class ComponentStoreImpl implements IComponentStore.Reloadable {
errors = executeSave(session, readonlyFiles, errors);
}
}
return errors;
}
@@ -18,6 +18,7 @@ package com.intellij.openapi.components.impl.stores;
import com.intellij.CommonBundle;
import com.intellij.ide.highlighter.ProjectFileType;
import com.intellij.ide.highlighter.WorkspaceFileType;
import com.intellij.notification.Notifications;
import com.intellij.notification.NotificationsManager;
import com.intellij.openapi.application.*;
import com.intellij.openapi.components.*;
@@ -30,6 +31,7 @@ import com.intellij.openapi.project.Project;
import com.intellij.openapi.project.ProjectBundle;
import com.intellij.openapi.project.impl.ProjectImpl;
import com.intellij.openapi.project.impl.ProjectManagerImpl;
import com.intellij.openapi.project.impl.ProjectManagerImpl.UnableToSaveProjectNotification;
import com.intellij.openapi.ui.MessageDialogBuilder;
import com.intellij.openapi.ui.Messages;
import com.intellij.openapi.util.Pair;
@@ -446,51 +448,69 @@ class ProjectStoreImpl extends BaseFileConfigurableStoreImpl implements IProject
@Override
protected final List<Throwable> doSave(@Nullable List<SaveSession> saveSessions, @NotNull List<Pair<SaveSession, VirtualFile>> readonlyFiles, @Nullable List<Throwable> errors) {
ProjectImpl.UnableToSaveProjectNotification[] notifications =
NotificationsManager.getNotificationsManager().getNotificationsOfType(ProjectImpl.UnableToSaveProjectNotification.class, myProject);
if (notifications.length > 0) {
throw new SaveCancelledException();
}
beforeSave(readonlyFiles);
super.doSave(saveSessions, readonlyFiles, errors);
if (!readonlyFiles.isEmpty()) {
ReadonlyStatusHandler.OperationStatus status;
AccessToken token = ReadAction.start();
try {
status = ReadonlyStatusHandler.getInstance(myProject).ensureFilesWritable(getFilesList(readonlyFiles));
UnableToSaveProjectNotification[] notifications =
NotificationsManager.getNotificationsManager().getNotificationsOfType(UnableToSaveProjectNotification.class, myProject);
if (readonlyFiles.isEmpty()) {
if (notifications.length > 0) {
for (UnableToSaveProjectNotification notification : notifications) {
notification.expire();
}
}
finally {
token.finish();
return errors;
}
if (notifications.length > 0) {
throw new SaveCancelledException();
}
ReadonlyStatusHandler.OperationStatus status;
AccessToken token = ReadAction.start();
try {
status = ReadonlyStatusHandler.getInstance(myProject).ensureFilesWritable(getFilesList(readonlyFiles));
}
finally {
token.finish();
}
if (status.hasReadonlyFiles()) {
dropUnableToSaveProjectNotification(myProject, status.getReadonlyFiles());
throw new SaveCancelledException();
}
else {
List<Pair<SaveSession, VirtualFile>> oldList = new ArrayList<Pair<SaveSession, VirtualFile>>(readonlyFiles);
readonlyFiles.clear();
for (Pair<SaveSession, VirtualFile> entry : oldList) {
errors = executeSave(entry.first, readonlyFiles, errors);
}
if (status.hasReadonlyFiles()) {
ProjectImpl.dropUnableToSaveProjectNotification(myProject, status.getReadonlyFiles());
if (errors != null) {
CompoundRuntimeException.doThrow(errors);
}
if (!readonlyFiles.isEmpty()) {
dropUnableToSaveProjectNotification(myProject, getFilesList(readonlyFiles));
throw new SaveCancelledException();
}
else {
List<Pair<SaveSession, VirtualFile>> oldList = new ArrayList<Pair<SaveSession, VirtualFile>>(readonlyFiles);
readonlyFiles.clear();
for (Pair<SaveSession, VirtualFile> entry : oldList) {
errors = executeSave(entry.first, readonlyFiles, errors);
}
if (errors != null) {
CompoundRuntimeException.doThrow(errors);
}
if (!readonlyFiles.isEmpty()) {
ProjectImpl.dropUnableToSaveProjectNotification(myProject, getFilesList(readonlyFiles));
throw new SaveCancelledException();
}
}
}
return errors;
}
private static void dropUnableToSaveProjectNotification(@NotNull Project project, @NotNull VirtualFile[] readOnlyFiles) {
UnableToSaveProjectNotification[] notifications =
NotificationsManager.getNotificationsManager().getNotificationsOfType(UnableToSaveProjectNotification.class, project);
if (notifications.length == 0) {
Notifications.Bus.notify(new UnableToSaveProjectNotification(project, readOnlyFiles), project);
}
else {
notifications[0].myFiles = readOnlyFiles;
}
}
protected void beforeSave(@NotNull List<Pair<SaveSession, VirtualFile>> readonlyFiles) {
}
@@ -1,5 +1,5 @@
/*
* Copyright 2000-2014 JetBrains s.r.o.
* Copyright 2000-2015 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.
@@ -19,7 +19,7 @@ import com.intellij.ide.RecentProjectsManager;
import com.intellij.ide.plugins.IdeaPluginDescriptor;
import com.intellij.ide.plugins.PluginManagerCore;
import com.intellij.ide.startup.StartupManagerEx;
import com.intellij.notification.*;
import com.intellij.notification.NotificationsManager;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.application.PathMacros;
import com.intellij.openapi.application.ex.ApplicationEx;
@@ -54,9 +54,7 @@ import com.intellij.openapi.util.text.StringUtil;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.openapi.wm.WindowManager;
import com.intellij.openapi.wm.impl.FrameTitleBuilder;
import com.intellij.util.Function;
import com.intellij.util.TimedReference;
import com.intellij.util.containers.ContainerUtil;
import com.intellij.util.pico.ConstructorInjectionComponentAdapter;
import gnu.trove.THashSet;
import org.jetbrains.annotations.NonNls;
@@ -65,7 +63,6 @@ import org.jetbrains.annotations.Nullable;
import org.picocontainer.*;
import javax.swing.*;
import javax.swing.event.HyperlinkEvent;
import java.io.File;
import java.util.*;
import java.util.concurrent.atomic.AtomicBoolean;
@@ -520,59 +517,4 @@ public class ProjectImpl extends PlatformComponentManagerImpl implements Project
protected boolean logSlowComponents() {
return super.logSlowComponents() || ApplicationInfoImpl.getShadowInstance().isEAP();
}
public static void dropUnableToSaveProjectNotification(@NotNull final Project project, final VirtualFile[] readOnlyFiles) {
final UnableToSaveProjectNotification[] notifications =
NotificationsManager.getNotificationsManager().getNotificationsOfType(UnableToSaveProjectNotification.class, project);
if (notifications.length == 0) {
Notifications.Bus.notify(new UnableToSaveProjectNotification(project, readOnlyFiles), project);
}
}
public static class UnableToSaveProjectNotification extends Notification {
private Project myProject;
private final String[] myFileNames;
private UnableToSaveProjectNotification(@NotNull final Project project, final VirtualFile[] readOnlyFiles) {
super("Project Settings", "Could not save project!", buildMessage(), NotificationType.ERROR, new NotificationListener() {
@Override
public void hyperlinkUpdate(@NotNull Notification notification, @NotNull HyperlinkEvent event) {
final UnableToSaveProjectNotification unableToSaveProjectNotification = (UnableToSaveProjectNotification)notification;
final Project _project = unableToSaveProjectNotification.getProject();
notification.expire();
if (_project != null && !_project.isDisposed()) {
_project.save();
}
}
});
myProject = project;
myFileNames = ContainerUtil.map(readOnlyFiles, new Function<VirtualFile, String>() {
@Override
public String fun(VirtualFile file) {
return file.getPresentableUrl();
}
}, new String[readOnlyFiles.length]);
}
public String[] getFileNames() {
return myFileNames;
}
private static String buildMessage() {
return "<p>Unable to save project files. Please ensure project files are writable and you have permissions to modify them." +
" <a href=\"\">Try to save project again</a>.</p>";
}
public Project getProject() {
return myProject;
}
@Override
public void expire() {
myProject = null;
super.expire();
}
}
}
@@ -23,6 +23,9 @@ import com.intellij.ide.RecentProjectsManager;
import com.intellij.ide.impl.ProjectUtil;
import com.intellij.ide.plugins.PluginManager;
import com.intellij.ide.startup.impl.StartupManagerImpl;
import com.intellij.notification.Notification;
import com.intellij.notification.NotificationListener;
import com.intellij.notification.NotificationType;
import com.intellij.notification.NotificationsManager;
import com.intellij.openapi.Disposable;
import com.intellij.openapi.application.*;
@@ -42,7 +45,6 @@ import com.intellij.openapi.startup.StartupManager;
import com.intellij.openapi.ui.Messages;
import com.intellij.openapi.util.*;
import com.intellij.openapi.util.io.FileUtil;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.openapi.vfs.LocalFileSystem;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.openapi.vfs.VirtualFileEvent;
@@ -67,6 +69,7 @@ import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.annotations.TestOnly;
import javax.swing.event.HyperlinkEvent;
import java.io.File;
import java.io.IOException;
import java.util.*;
@@ -976,17 +979,30 @@ public class ProjectManagerImpl extends ProjectManagerEx implements PersistentSt
return true;
}
private static boolean ensureCouldCloseIfUnableToSave(@NotNull final Project project) {
final ProjectImpl.UnableToSaveProjectNotification[] notifications =
NotificationsManager.getNotificationsManager().getNotificationsOfType(ProjectImpl.UnableToSaveProjectNotification.class, project);
if (notifications.length == 0) return true;
private static boolean ensureCouldCloseIfUnableToSave(@NotNull Project project) {
UnableToSaveProjectNotification[] notifications =
NotificationsManager.getNotificationsManager().getNotificationsOfType(UnableToSaveProjectNotification.class, project);
if (notifications.length == 0) {
return true;
}
final String fileNames = StringUtil.join(notifications[0].getFileNames(), "\n");
StringBuilder message = new StringBuilder();
message.append(String.format("%s was unable to save some project files,\nare you sure you want to close this project anyway?",
ApplicationNamesInfo.getInstance().getProductName()));
final String msg = String.format("%s was unable to save some project files,\nare you sure you want to close this project anyway?",
ApplicationNamesInfo.getInstance().getProductName());
return Messages.showDialog(project, msg, "Unsaved Project", "Read-only files:\n\n" + fileNames, new String[]{"Yes", "No"}, 0, 1,
Messages.getWarningIcon()) == 0;
message.append("\n\nRead-only files:\n");
int count = 0;
VirtualFile[] files = notifications[0].myFiles;
for (VirtualFile file : files) {
if (count == 10) {
message.append('\n').append("and ").append(files.length - count).append(" more").append('\n');
}
else {
message.append(file.getPath()).append('\n');
count++;
}
}
return Messages.showYesNoDialog(project, message.toString(), "Unsaved Project", Messages.getWarningIcon()) == Messages.YES;
}
@@ -1039,4 +1055,34 @@ public class ProjectManagerImpl extends ProjectManagerEx implements PersistentSt
public String getPresentableName() {
return ProjectBundle.message("project.default.settings");
}
public static class UnableToSaveProjectNotification extends Notification {
private Project myProject;
public VirtualFile[] myFiles;
public UnableToSaveProjectNotification(@NotNull final Project project, final VirtualFile[] readOnlyFiles) {
super("Project Settings", "Could not save project", "Unable to save project files. Please ensure project files are writable and you have permissions to modify them." +
" <a href=\"\">Try to save project again</a>.", NotificationType.ERROR, new NotificationListener() {
@Override
public void hyperlinkUpdate(@NotNull Notification notification, @NotNull HyperlinkEvent event) {
final UnableToSaveProjectNotification unableToSaveProjectNotification = (UnableToSaveProjectNotification)notification;
final Project _project = unableToSaveProjectNotification.myProject;
notification.expire();
if (_project != null && !_project.isDisposed()) {
_project.save();
}
}
});
myProject = project;
myFiles = readOnlyFiles;
}
@Override
public void expire() {
myProject = null;
super.expire();
}
}
}