From d855f41b6e59d9ade407dfa37f9113857251f111 Mon Sep 17 00:00:00 2001 From: irengrig Date: Tue, 2 Oct 2012 15:23:08 +0400 Subject: [PATCH] When SVN authentication is failed, suggest to clear authentication cache or change configuration directory right in the error message for IDEA-92220 "Click to fix" link doesn't work if invalid SVN credentials was entered --- .../vcs/ui/VcsBalloonProblemNotifier.java | 60 +++++++++++++--- .../idea/svn/SvnAuthenticationNotifier.java | 29 +++++++- .../jetbrains/idea/svn/SvnBundle.properties | 1 + .../jetbrains/idea/svn/SvnConfigurable.java | 69 ++++++++++++------- .../jetbrains/idea/svn/SvnConfiguration.java | 2 +- 5 files changed, 127 insertions(+), 34 deletions(-) diff --git a/platform/vcs-impl/src/com/intellij/openapi/vcs/ui/VcsBalloonProblemNotifier.java b/platform/vcs-impl/src/com/intellij/openapi/vcs/ui/VcsBalloonProblemNotifier.java index 8adad110695a..e9d5c2e90bd7 100644 --- a/platform/vcs-impl/src/com/intellij/openapi/vcs/ui/VcsBalloonProblemNotifier.java +++ b/platform/vcs-impl/src/com/intellij/openapi/vcs/ui/VcsBalloonProblemNotifier.java @@ -15,7 +15,10 @@ */ package com.intellij.openapi.vcs.ui; +import com.intellij.notification.Notification; import com.intellij.notification.NotificationGroup; +import com.intellij.notification.NotificationListener; +import com.intellij.notification.NotificationType; import com.intellij.openapi.application.Application; import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.project.Project; @@ -23,11 +26,14 @@ import com.intellij.openapi.ui.MessageType; import com.intellij.openapi.ui.popup.Balloon; import com.intellij.openapi.ui.popup.BalloonBuilder; import com.intellij.openapi.ui.popup.JBPopupFactory; +import com.intellij.openapi.util.NamedRunnable; import com.intellij.openapi.vcs.changes.ui.ChangesViewContentManager; import com.intellij.ui.awt.RelativePoint; import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; import javax.swing.*; +import javax.swing.event.HyperlinkEvent; import java.awt.*; import java.util.concurrent.TimeUnit; @@ -43,32 +49,37 @@ public class VcsBalloonProblemNotifier implements Runnable { private final String myMessage; private final MessageType myMessageType; private final boolean myShowOverChangesView; + @Nullable private final NamedRunnable[] myNotificationListener; public VcsBalloonProblemNotifier(@NotNull final Project project, @NotNull final String message, final MessageType messageType) { - this(project, message, messageType, true); + this(project, message, messageType, true, null); } - public VcsBalloonProblemNotifier(@NotNull final Project project, @NotNull final String message, final MessageType messageType, boolean showOverChangesView) { + public VcsBalloonProblemNotifier(@NotNull final Project project, @NotNull final String message, final MessageType messageType, boolean showOverChangesView, + @Nullable final NamedRunnable[] notificationListener) { myProject = project; myMessage = message; myMessageType = messageType; myShowOverChangesView = showOverChangesView; + myNotificationListener = notificationListener; } - public static void showOverChangesView(@NotNull final Project project, @NotNull final String message, final MessageType type) { - show(project, message, type, true); + public static void showOverChangesView(@NotNull final Project project, @NotNull final String message, final MessageType type, + final NamedRunnable... notificationListener) { + show(project, message, type, true, notificationListener); } public static void showOverVersionControlView(@NotNull final Project project, @NotNull final String message, final MessageType type) { - show(project, message, type, false); + show(project, message, type, false, null); } - private static void show(final Project project, final String message, final MessageType type, final boolean showOverChangesView) { + private static void show(final Project project, final String message, final MessageType type, final boolean showOverChangesView, + @Nullable final NamedRunnable[] notificationListener) { final Application application = ApplicationManager.getApplication(); if (application.isHeadlessEnvironment()) return; final Runnable showErrorAction = new Runnable() { public void run() { - new VcsBalloonProblemNotifier(project, message, type, showOverChangesView).run(); + new VcsBalloonProblemNotifier(project, message, type, showOverChangesView, notificationListener).run(); } }; if (application.isDispatchThread()) { @@ -80,7 +91,40 @@ public class VcsBalloonProblemNotifier implements Runnable { } public void run() { - NOTIFICATION_GROUP.createNotification(myMessage, myMessageType).notify(myProject.isDefault() ? null : myProject); + final Notification notification; + if (myNotificationListener != null && myNotificationListener.length > 0) { + final NotificationType type = myMessageType.toNotificationType(); + final StringBuilder sb = new StringBuilder(myMessage); + for (NamedRunnable runnable : myNotificationListener) { + final String name = runnable.toString(); + sb.append("
").append(name).append(""); + } + notification = NOTIFICATION_GROUP.createNotification(type.name(), sb.toString(), myMessageType.toNotificationType(), + new NotificationListener() { + @Override + public void hyperlinkUpdate(@NotNull Notification notification, @NotNull HyperlinkEvent event) { + if (HyperlinkEvent.EventType.ACTIVATED.equals(event.getEventType())) { + if (myNotificationListener.length == 1) { + myNotificationListener[0].run(); + } else { + final String description = event.getDescription(); + if (description != null) { + for (NamedRunnable runnable : myNotificationListener) { + if (description.equals(runnable.toString())) { + runnable.run(); + break; + } + } + } + } + notification.expire(); + } + } + }); + } else { + notification = NOTIFICATION_GROUP.createNotification(myMessage, myMessageType); + } + notification.notify(myProject.isDefault() ? null : myProject); } public static void showBalloonForComponent(@NotNull JComponent component, @NotNull final String message, final MessageType type, diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/SvnAuthenticationNotifier.java b/plugins/svn4idea/src/org/jetbrains/idea/svn/SvnAuthenticationNotifier.java index 70fff2d294f6..1e823136398f 100644 --- a/plugins/svn4idea/src/org/jetbrains/idea/svn/SvnAuthenticationNotifier.java +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn/SvnAuthenticationNotifier.java @@ -25,6 +25,7 @@ import com.intellij.openapi.project.Project; import com.intellij.openapi.ui.MessageType; import com.intellij.openapi.ui.popup.Balloon; import com.intellij.openapi.ui.popup.JBPopupFactory; +import com.intellij.openapi.util.NamedRunnable; import com.intellij.openapi.util.Ref; import com.intellij.openapi.util.text.StringUtil; import com.intellij.openapi.vcs.impl.GenericNotifierImpl; @@ -33,6 +34,7 @@ import com.intellij.openapi.vfs.VirtualFile; import com.intellij.openapi.wm.IdeFrame; import com.intellij.openapi.wm.ex.WindowManagerEx; import com.intellij.ui.awt.RelativePoint; +import com.intellij.util.Consumer; import com.intellij.util.ThreeState; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -321,14 +323,37 @@ public class SvnAuthenticationNotifier extends GenericNotifierImpl() { + @Override + public void consume(String s) { + configuration.setConfigurationDirectory(s); + } + }, project, null); + } + }); + } + }, ModalityState.NON_MODAL, project.getDisposed()); } return false; /// !!!! any exception means user should be notified that authorization failed } diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/SvnBundle.properties b/plugins/svn4idea/src/org/jetbrains/idea/svn/SvnBundle.properties index 9db0ae7d40f8..ba6e615f19ff 100644 --- a/plugins/svn4idea/src/org/jetbrains/idea/svn/SvnBundle.properties +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn/SvnBundle.properties @@ -154,6 +154,7 @@ status.group.name.locked=Locked checkbox.configure.use.system.default.configuration.directory=&Use system default Subversion configuration directory label.configuration.configuration.directory=Subversion configuration &directory: dialog.title.select.configuration.directory=Select Configuration Directory +action.title.select.configuration.directory=Change Configuration Directory dialog.description.select.configuration.directory=Select Subversion configuration directory or create new one button.text.clear.authentication.cache=&Clear Auth Cache label.text.delete.stored.credentials=Delete all stored credentials for 'http', 'svn' and 'svn+ssh' protocols diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/SvnConfigurable.java b/plugins/svn4idea/src/org/jetbrains/idea/svn/SvnConfigurable.java index f2eff8947c0e..2a07a18bccd1 100644 --- a/plugins/svn4idea/src/org/jetbrains/idea/svn/SvnConfigurable.java +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn/SvnConfigurable.java @@ -32,8 +32,11 @@ import com.intellij.openapi.util.Comparing; import com.intellij.openapi.vfs.VirtualFile; import com.intellij.openapi.vfs.VirtualFileManager; import com.intellij.ui.MultiLineTooltipUI; +import com.intellij.util.Consumer; import com.intellij.util.ui.UIUtil; import org.jetbrains.annotations.NonNls; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; import org.jetbrains.idea.svn.config.ConfigureProxiesListener; import javax.swing.*; @@ -93,37 +96,21 @@ public class SvnConfigurable implements Configurable { myClearAuthButton.addActionListener(new ActionListener(){ public void actionPerformed(final ActionEvent e) { - String path = myConfigurationDirectoryText.getText(); - if (path != null) { - int result = Messages.showYesNoDialog(myComponent, SvnBundle.message("confirmation.text.delete.stored.authentication.information"), - SvnBundle.message("confirmation.title.clear.authentication.cache"), - Messages.getWarningIcon()); - if (result == 0) { - SvnConfiguration.RUNTIME_AUTH_CACHE.clear(); - SvnConfiguration.getInstance(myProject).clearAuthenticationDirectory(myProject); - } - } - + clearAuthenticationCache(myProject, myComponent, myConfigurationDirectoryText.getText()); } }); - final FileChooserDescriptor descriptor = createFileDescriptor(); myConfigurationDirectoryText.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { @NonNls String path = myConfigurationDirectoryText.getText().trim(); - path = "file://" + path.replace(File.separatorChar, '/'); - VirtualFile root = VirtualFileManager.getInstance().findFileByUrl(path); - - String oldValue = PropertiesComponent.getInstance().getValue("FileChooser.showHiddens"); - PropertiesComponent.getInstance().setValue("FileChooser.showHiddens", Boolean.TRUE.toString()); - VirtualFile file = FileChooser.chooseFile(descriptor, myComponent, myProject, root); - PropertiesComponent.getInstance().setValue("FileChooser.showHiddens", oldValue); - if (file == null) { - return; - } - myConfigurationDirectoryText.setText(file.getPath().replace('/', File.separatorChar)); + selectConfigirationDirectory(path, new Consumer() { + @Override + public void consume(String s) { + myConfigurationDirectoryText.setText(s); + } + }, myProject, myComponent); } }); myConfigurationDirectoryText.setEditable(false); @@ -150,6 +137,42 @@ public class SvnConfigurable implements Configurable { myJavaHLInfo.setVisible(internal); } + public static void selectConfigirationDirectory(@NotNull String path, @NotNull final Consumer dirConsumer, + final Project project, @Nullable final Component component) { + final FileChooserDescriptor descriptor = createFileDescriptor(); + path = "file://" + path.replace(File.separatorChar, '/'); + VirtualFile root = VirtualFileManager.getInstance().findFileByUrl(path); + + String oldValue = PropertiesComponent.getInstance().getValue("FileChooser.showHiddens"); + PropertiesComponent.getInstance().setValue("FileChooser.showHiddens", Boolean.TRUE.toString()); + VirtualFile file = FileChooser.chooseFile(descriptor, component, project, root); + PropertiesComponent.getInstance().setValue("FileChooser.showHiddens", oldValue); + if (file == null) { + return; + } + final String resultPath = file.getPath().replace('/', File.separatorChar); + dirConsumer.consume(resultPath); + } + + public static void clearAuthenticationCache(@NotNull final Project project, final Component component, final String configDirPath) { + if (configDirPath != null) { + int result; + if (component == null) { + result = Messages.showYesNoDialog(project, SvnBundle.message("confirmation.text.delete.stored.authentication.information"), + SvnBundle.message("confirmation.title.clear.authentication.cache"), + Messages.getWarningIcon()); + } else { + result = Messages.showYesNoDialog(component, SvnBundle.message("confirmation.text.delete.stored.authentication.information"), + SvnBundle.message("confirmation.title.clear.authentication.cache"), + Messages.getWarningIcon()); + } + if (result == 0) { + SvnConfiguration.RUNTIME_AUTH_CACHE.clear(); + SvnConfiguration.getInstance(project).clearAuthenticationDirectory(project); + } + } + } + private static FileChooserDescriptor createFileDescriptor() { final FileChooserDescriptor descriptor = FileChooserDescriptorFactory.createSingleFolderDescriptor(); descriptor.setShowFileSystemRoots(true); diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/SvnConfiguration.java b/plugins/svn4idea/src/org/jetbrains/idea/svn/SvnConfiguration.java index 4a2d3639c9cb..e4e98fde84aa 100644 --- a/plugins/svn4idea/src/org/jetbrains/idea/svn/SvnConfiguration.java +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn/SvnConfiguration.java @@ -488,7 +488,7 @@ public class SvnConfiguration implements PersistentStateComponent { public static class AuthStorage implements ISVNAuthenticationStorage { - private final Map myStorage = new Hashtable(); + private final Map myStorage = Collections.synchronizedMap(new HashMap()); public void clear() { myStorage.clear();