Github: rewrite AuthDataHolder
authorAleksey Pivovarov <Aleksey.Pivovarov@jetbrains.com>
Wed, 5 Feb 2014 14:11:39 +0000 (18:11 +0400)
committerAleksey Pivovarov <Aleksey.Pivovarov@jetbrains.com>
Thu, 27 Feb 2014 11:18:15 +0000 (15:18 +0400)
encapsulate lock-critical parts

plugins/github/src/org/jetbrains/plugins/github/ui/GithubBasicLoginDialog.java
plugins/github/src/org/jetbrains/plugins/github/ui/GithubLoginDialog.java
plugins/github/src/org/jetbrains/plugins/github/util/GithubAuthData.java
plugins/github/src/org/jetbrains/plugins/github/util/GithubAuthDataHolder.java
plugins/github/src/org/jetbrains/plugins/github/util/GithubUtil.java

index c02b6c2648d807ecbc59a47d6aa7428b6217a156..80acc787d6c6b9c3437a4d934e87f7191539b4e4 100644 (file)
@@ -18,15 +18,14 @@ package org.jetbrains.plugins.github.ui;
 import com.intellij.openapi.project.Project;
 import org.jetbrains.annotations.NotNull;
 import org.jetbrains.plugins.github.util.GithubAuthData;
-import org.jetbrains.plugins.github.util.GithubAuthDataHolder;
 
 /**
  * @author Aleksey Pivovarov
  */
 public class GithubBasicLoginDialog extends GithubLoginDialog {
 
-  public GithubBasicLoginDialog(@NotNull Project project, @NotNull GithubAuthDataHolder authHolder, @NotNull String host) {
-    super(project, authHolder);
+  public GithubBasicLoginDialog(@NotNull Project project, @NotNull GithubAuthData oldAuthData, @NotNull String host) {
+    super(project, oldAuthData);
     myGithubLoginPanel.lockAuthType(GithubAuthData.AuthType.BASIC);
     myGithubLoginPanel.lockHost(host);
   }
index 85d35a9dcb3c4f69eab52e5b339d0347b6f724ac..c4e33f6e136c4c4bac7ffce824fdef17f3384c62 100644 (file)
@@ -7,6 +7,7 @@ import com.intellij.openapi.ui.DialogWrapper;
 import com.intellij.util.ThrowableConvertor;
 import org.jetbrains.annotations.NotNull;
 import org.jetbrains.plugins.github.api.GithubUser;
+import org.jetbrains.plugins.github.util.GithubAuthData;
 import org.jetbrains.plugins.github.util.GithubAuthDataHolder;
 import org.jetbrains.plugins.github.util.GithubSettings;
 import org.jetbrains.plugins.github.util.GithubUtil;
@@ -25,21 +26,24 @@ public class GithubLoginDialog extends DialogWrapper {
   protected final GithubLoginPanel myGithubLoginPanel;
   protected final GithubSettings mySettings;
 
-  protected final GithubAuthDataHolder myAuthHolder;
   protected final Project myProject;
 
-  public GithubLoginDialog(@NotNull final Project project, @NotNull GithubAuthDataHolder authHolder) {
+  protected GithubAuthData myAuthData;
+
+  public GithubLoginDialog(@NotNull final Project project, @NotNull GithubAuthData oldAuthData) {
     super(project, true);
     myProject = project;
-    myAuthHolder = authHolder;
 
     myGithubLoginPanel = new GithubLoginPanel(this);
 
-    mySettings = GithubSettings.getInstance();
-    myGithubLoginPanel.setHost(mySettings.getHost());
-    myGithubLoginPanel.setLogin(mySettings.getLogin());
-    myGithubLoginPanel.setAuthType(mySettings.getAuthType());
+    myGithubLoginPanel.setHost(oldAuthData.getHost());
+    myGithubLoginPanel.setAuthType(oldAuthData.getAuthType());
+    GithubAuthData.BasicAuth basicAuth = oldAuthData.getBasicAuth();
+    if (basicAuth != null) {
+      myGithubLoginPanel.setLogin(basicAuth.getLogin());
+    }
 
+    mySettings = GithubSettings.getInstance();
     if (mySettings.isSavePasswordMakesSense()) {
       myGithubLoginPanel.setSavePasswordSelected(mySettings.isSavePassword());
     }
@@ -74,7 +78,6 @@ public class GithubLoginDialog extends DialogWrapper {
 
   @Override
   protected void doOKAction() {
-    // aware of recursive synchronization in getTwoFactorAuthData from modal thread
     final GithubAuthDataHolder authHolder = new GithubAuthDataHolder(myGithubLoginPanel.getAuthData());
     try {
       GithubUtil.computeValueInModal(myProject, "Access to GitHub", new ThrowableConvertor<ProgressIndicator, GithubUser, IOException>() {
@@ -85,7 +88,7 @@ public class GithubLoginDialog extends DialogWrapper {
         }
       });
 
-      myAuthHolder.setAuthData(authHolder.getAuthData());
+      myAuthData = authHolder.getAuthData();
 
       if (mySettings.isSavePasswordMakesSense()) {
         mySettings.setSavePassword(myGithubLoginPanel.isSavePasswordSelected());
@@ -102,6 +105,14 @@ public class GithubLoginDialog extends DialogWrapper {
     return myGithubLoginPanel.isSavePasswordSelected();
   }
 
+  @NotNull
+  public GithubAuthData getAuthData() {
+    if (myAuthData == null) {
+      throw new IllegalStateException("AuthData is not set");
+    }
+    return myAuthData;
+  }
+
   public void clearErrors() {
     setErrorText(null);
   }
index 5066f828d621b9cb1974187fe490ef1035c3fc43..f1104442215413ac40dbfa9c7c12c681c59cadd7 100644 (file)
@@ -107,6 +107,15 @@ public class GithubAuthData {
     return myUseProxy;
   }
 
+  @NotNull
+  public GithubAuthData copyWithTwoFactorCode(@NotNull String code) {
+    if (myBasicAuth == null) {
+      throw new IllegalStateException("Two factor authentication can be used only with Login/Password");
+    }
+
+    return createBasicAuthTF(getHost(), myBasicAuth.getLogin(), myBasicAuth.getPassword(), code);
+  }
+
   public static class BasicAuth {
     @NotNull private final String myLogin;
     @NotNull private final String myPassword;
index 2d873ad26f480c52d197fc5102179f37c1f74c7f..7a2959375a4d6a765f7e05f65e3af07595588952 100644 (file)
  */
 package org.jetbrains.plugins.github.util;
 
+import com.intellij.openapi.util.ThrowableComputable;
 import org.jetbrains.annotations.NotNull;
 
 
 public class GithubAuthDataHolder {
-  @NotNull public final Object myLock = new Object();
-
-  @NotNull private GithubAuthData myAuthData;
+  @NotNull private volatile GithubAuthData myAuthData;
 
   public GithubAuthDataHolder(@NotNull GithubAuthData auth) {
     myAuthData = auth;
   }
 
   @NotNull
-  public GithubAuthData getAuthData() {
+  public synchronized GithubAuthData getAuthData() {
     return myAuthData;
   }
 
-  public void setAuthData(@NotNull GithubAuthData auth) {
-    myAuthData = auth;
-  }
-
-  public void setTwoFactorCode(@NotNull String code) {
-    GithubAuthData auth = getAuthData();
-    GithubAuthData.BasicAuth basicAuth = auth.getBasicAuth();
-    if (basicAuth == null) {
-      throw new IllegalStateException("Can't set two factor code for non-basic auth");
+  public synchronized <T extends Throwable> void runTransaction(@NotNull GithubAuthData expected,
+                                                                @NotNull ThrowableComputable<GithubAuthData, T> task) throws T {
+    if (expected != myAuthData) {
+      return;
     }
-    setAuthData(GithubAuthData.createBasicAuthTF(auth.getHost(), basicAuth.getLogin(), basicAuth.getPassword(), code));
+
+    myAuthData = task.compute();
   }
 
   public static GithubAuthDataHolder createFromSettings() {
index c75c4630921986a280ce4baa793935c68f633bd5..ef02186bf728a2cd2be2b2620103e20413372012 100644 (file)
@@ -23,6 +23,7 @@ import com.intellij.openapi.progress.ProgressManager;
 import com.intellij.openapi.progress.Task;
 import com.intellij.openapi.project.Project;
 import com.intellij.openapi.util.Pair;
+import com.intellij.openapi.util.ThrowableComputable;
 import com.intellij.openapi.util.text.StringUtil;
 import com.intellij.openapi.vcs.VcsException;
 import com.intellij.openapi.vfs.VirtualFile;
@@ -142,91 +143,95 @@ public class GithubUtil {
     }
   }
 
-  public static void getValidAuthData(@NotNull Project project,
-                                      @NotNull GithubAuthDataHolder authHolder,
-                                      @NotNull ProgressIndicator indicator,
-                                      @NotNull GithubAuthData oldAuth) throws GithubOperationCanceledException {
-    synchronized (authHolder.myLock) {
-      if (authHolder.getAuthData() != oldAuth) {
-        return;
-      }
-
-      final GithubLoginDialog dialog = new GithubLoginDialog(project, authHolder);
-      ApplicationManager.getApplication().invokeAndWait(new Runnable() {
-        @Override
-        public void run() {
-          DialogManager.show(dialog);
+  public static void getValidAuthData(@NotNull final Project project,
+                                      @NotNull final GithubAuthDataHolder authHolder,
+                                      @NotNull final ProgressIndicator indicator,
+                                      @NotNull final GithubAuthData oldAuth) throws GithubOperationCanceledException {
+    authHolder.runTransaction(oldAuth, new ThrowableComputable<GithubAuthData, GithubOperationCanceledException>() {
+      @Override
+      @NotNull
+      public GithubAuthData compute() throws GithubOperationCanceledException {
+        final GithubLoginDialog dialog = new GithubLoginDialog(project, oldAuth);
+        ApplicationManager.getApplication().invokeAndWait(new Runnable() {
+          @Override
+          public void run() {
+            DialogManager.show(dialog);
+          }
+        }, indicator.getModalityState());
+        if (!dialog.isOK()) {
+          throw new GithubOperationCanceledException("Can't get valid credentials");
         }
-      }, indicator.getModalityState());
-      if (!dialog.isOK()) {
-        throw new GithubOperationCanceledException("Can't get valid credentials");
-      }
-
-      GithubSettings.getInstance().setAuthData(authHolder.getAuthData(), dialog.isSavePasswordSelected());
-    }
-  }
+        GithubAuthData authData = dialog.getAuthData();
 
-  public static void getValidBasicAuthDataForHost(@NotNull Project project,
-                                                  @NotNull GithubAuthDataHolder authHolder,
-                                                  @NotNull ProgressIndicator indicator,
-                                                  @NotNull GithubAuthData oldAuth,
-                                                  @NotNull String host) throws GithubOperationCanceledException {
-    synchronized (authHolder.myLock) {
-      if (authHolder.getAuthData() != oldAuth) {
-        return;
+        GithubSettings.getInstance().setAuthData(authData, dialog.isSavePasswordSelected());
+        return authData;
       }
+    });
+  }
 
-      final GithubLoginDialog dialog = new GithubBasicLoginDialog(project, authHolder, host);
-      ApplicationManager.getApplication().invokeAndWait(new Runnable() {
-        @Override
-        public void run() {
-          DialogManager.show(dialog);
+  public static void getValidBasicAuthDataForHost(@NotNull final Project project,
+                                                  @NotNull final GithubAuthDataHolder authHolder,
+                                                  @NotNull final ProgressIndicator indicator,
+                                                  @NotNull final GithubAuthData oldAuth,
+                                                  @NotNull final String host) throws GithubOperationCanceledException {
+    authHolder.runTransaction(oldAuth, new ThrowableComputable<GithubAuthData, GithubOperationCanceledException>() {
+      @Override
+      @NotNull
+      public GithubAuthData compute() throws GithubOperationCanceledException {
+        final GithubLoginDialog dialog = new GithubBasicLoginDialog(project, oldAuth, host);
+        ApplicationManager.getApplication().invokeAndWait(new Runnable() {
+          @Override
+          public void run() {
+            DialogManager.show(dialog);
+          }
+        }, indicator.getModalityState());
+        if (!dialog.isOK()) {
+          throw new GithubOperationCanceledException("Can't get valid credentials");
         }
-      }, indicator.getModalityState());
-      if (!dialog.isOK()) {
-        throw new GithubOperationCanceledException("Can't get valid credentials");
-      }
+        GithubAuthData authData = dialog.getAuthData();
 
-      final GithubSettings settings = GithubSettings.getInstance();
-      if (settings.getAuthType() != GithubAuthData.AuthType.TOKEN) {
-        GithubSettings.getInstance().setAuthData(authHolder.getAuthData(), dialog.isSavePasswordSelected());
+        final GithubSettings settings = GithubSettings.getInstance();
+        if (settings.getAuthType() != GithubAuthData.AuthType.TOKEN) {
+          GithubSettings.getInstance().setAuthData(authData, dialog.isSavePasswordSelected());
+        }
+        return authData;
       }
-    }
+    });
   }
 
-  private static void getTwoFactorAuthData(@NotNull Project project,
-                                           @NotNull GithubAuthDataHolder authHolder,
-                                           @NotNull ProgressIndicator indicator,
-                                           @NotNull GithubAuthData oldAuth) throws GithubOperationCanceledException {
-    synchronized (authHolder.myLock) {
-      if (authHolder.getAuthData() != oldAuth) {
-        return;
-      }
-
-      if (authHolder.getAuthData().getAuthType() != GithubAuthData.AuthType.BASIC) {
-        throw new GithubOperationCanceledException("Two factor authentication can be used only with Login/Password");
-      }
+  private static void getTwoFactorAuthData(@NotNull final Project project,
+                                           @NotNull final GithubAuthDataHolder authHolder,
+                                           @NotNull final ProgressIndicator indicator,
+                                           @NotNull final GithubAuthData oldAuth) throws GithubOperationCanceledException {
+    authHolder.runTransaction(oldAuth, new ThrowableComputable<GithubAuthData, GithubOperationCanceledException>() {
+      @Override
+      @NotNull
+      public GithubAuthData compute() throws GithubOperationCanceledException {
+        if (authHolder.getAuthData().getAuthType() != GithubAuthData.AuthType.BASIC) {
+          throw new GithubOperationCanceledException("Two factor authentication can be used only with Login/Password");
+        }
 
-      GithubApiUtil.askForTwoFactorCodeSMS(oldAuth);
+        GithubApiUtil.askForTwoFactorCodeSMS(oldAuth);
 
-      final GithubTwoFactorDialog dialog = new GithubTwoFactorDialog(project);
-      ApplicationManager.getApplication().invokeAndWait(new Runnable() {
-        @Override
-        public void run() {
-          DialogManager.show(dialog);
+        final GithubTwoFactorDialog dialog = new GithubTwoFactorDialog(project);
+        ApplicationManager.getApplication().invokeAndWait(new Runnable() {
+          @Override
+          public void run() {
+            DialogManager.show(dialog);
+          }
+        }, indicator.getModalityState());
+        if (!dialog.isOK()) {
+          throw new GithubOperationCanceledException("Can't get two factor authentication code");
         }
-      }, indicator.getModalityState());
-      if (!dialog.isOK()) {
-        throw new GithubOperationCanceledException("Can't get two factor authentication code");
-      }
 
-      authHolder.setTwoFactorCode(dialog.getCode());
-    }
+        return oldAuth.copyWithTwoFactorCode(dialog.getCode());
+      }
+    });
   }
 
   @NotNull
-  public static GithubAuthDataHolder getValidAuthDataHolderFromConfig(@NotNull Project project,
-                                                                      @NotNull ProgressIndicator indicator) throws IOException {
+  public static GithubAuthDataHolder getValidAuthDataHolderFromConfig(@NotNull Project project, @NotNull ProgressIndicator indicator)
+    throws IOException {
     GithubAuthData auth = GithubAuthData.createFromSettings();
     GithubAuthDataHolder authHolder = new GithubAuthDataHolder(auth);
     try {