Block parallel requests to install a package
authorAlexander Ebert <ebert@woltlab.com>
Tue, 5 Sep 2023 14:26:22 +0000 (16:26 +0200)
committerAlexander Ebert <ebert@woltlab.com>
Tue, 5 Sep 2023 14:26:22 +0000 (16:26 +0200)
ts/WoltLabSuite/Core/Acp/Component/License.ts
ts/WoltLabSuite/Core/Acp/Ui/Package/PrepareInstallation.ts
ts/WoltLabSuite/Core/Acp/Ui/Package/Search.ts
wcfsetup/install/files/js/WoltLabSuite/Core/Acp/Component/License.js
wcfsetup/install/files/js/WoltLabSuite/Core/Acp/Ui/Package/PrepareInstallation.js
wcfsetup/install/files/js/WoltLabSuite/Core/Acp/Ui/Package/Search.js

index 07d8ea608c31923b14962b4ee95b61b4be3bf037..718d4e59fcefbc2057f99758c51fc073c3be8a2c 100644 (file)
@@ -1,14 +1,25 @@
+/**
+ * Offers to install packages from the list of licensed products.
+ *
+ * @author Alexander Ebert
+ * @copyright 2001-2023 WoltLab GmbH
+ * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @since 6.0
+ */
+
+import { promiseMutex } from "WoltLabSuite/Core/Helper/PromiseMutex";
 import AcpUiPackagePrepareInstallation from "../Ui/Package/PrepareInstallation";
 
-function installPackage(button: HTMLButtonElement): void {
+function installPackage(button: HTMLButtonElement): Promise<void> {
   const installation = new AcpUiPackagePrepareInstallation();
-  installation.start(button.dataset.package!, button.dataset.packageVersion!);
+  return installation.start(button.dataset.package!, button.dataset.packageVersion!);
 }
 
 export function setup(): void {
+  const callback = promiseMutex((button: HTMLButtonElement) => installPackage(button));
   document.querySelectorAll<HTMLButtonElement>(".jsInstallPackage").forEach((button) => {
     button.addEventListener("click", () => {
-      installPackage(button);
+      callback(button);
     });
   });
 }
index e7557fcaada489418a79b13acb4de278a467c620..5fcf8530ba175a880d628073ea2a64a4f5a7e2fb 100644 (file)
@@ -24,12 +24,21 @@ interface AjaxResponse {
 class AcpUiPackagePrepareInstallation {
   private identifier = "";
   private version = "";
+  #resolve?: () => void;
+
+  start(identifier: string, version: string): Promise<void> {
+    if (this.#resolve !== undefined) {
+      throw new Error("There is already a pending installation.");
+    }
 
-  start(identifier: string, version: string): void {
     this.identifier = identifier;
     this.version = version;
 
-    this.prepare({});
+    return new Promise<void>((resolve) => {
+      this.#resolve = resolve;
+
+      this.prepare({});
+    });
   }
 
   private prepare(authData: ArbitraryObject): void {
@@ -82,6 +91,9 @@ class AcpUiPackagePrepareInstallation {
     } else if (data.returnValues.template) {
       UiDialog.open(this, data.returnValues.template);
     }
+
+    this.#resolve!();
+    this.#resolve = undefined;
   }
 
   _ajaxSetup(): ReturnType<AjaxCallbackSetup> {
index 428d2882a6998b3ca20db3498225c5340d7ad364..be8e914d17f146affed30f1cf1be3fb5c80ea240 100644 (file)
@@ -10,6 +10,7 @@ import AcpUiPackagePrepareInstallation from "./PrepareInstallation";
 import * as Ajax from "../../../Ajax";
 import AjaxRequest from "../../../Ajax/Request";
 import { AjaxCallbackObject, AjaxCallbackSetup } from "../../../Ajax/Data";
+import { promiseMutex } from "WoltLabSuite/Core/Helper/PromiseMutex";
 
 interface AjaxResponse {
   actionName: string;
@@ -120,12 +121,16 @@ class AcpUiPackageSearch implements AjaxCallbackObject {
 
           this.setStatus("showResults");
 
+          const callback = promiseMutex((button: HTMLAnchorElement) => {
+            return this.installation.start(button.dataset.package!, button.dataset.packageVersion!);
+          });
           this.resultList.querySelectorAll(".jsInstallPackage").forEach((button: HTMLAnchorElement) => {
             button.addEventListener("click", (event) => {
               event.preventDefault();
-              button.blur();
 
-              this.installation.start(button.dataset.package!, button.dataset.packageVersion!);
+              if (callback(button)) {
+                button.blur();
+              }
             });
           });
         } else {
index 10cce723f88ce6f4306477bb6d85e3ed0bea309f..7663b1bbd84f95db16827ce2b9a6440fec4a9876 100644 (file)
@@ -1,16 +1,25 @@
-define(["require", "exports", "tslib", "../Ui/Package/PrepareInstallation"], function (require, exports, tslib_1, PrepareInstallation_1) {
+/**
+ * Offers to install packages from the list of licensed products.
+ *
+ * @author Alexander Ebert
+ * @copyright 2001-2023 WoltLab GmbH
+ * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @since 6.0
+ */
+define(["require", "exports", "tslib", "WoltLabSuite/Core/Helper/PromiseMutex", "../Ui/Package/PrepareInstallation"], function (require, exports, tslib_1, PromiseMutex_1, PrepareInstallation_1) {
     "use strict";
     Object.defineProperty(exports, "__esModule", { value: true });
     exports.setup = void 0;
     PrepareInstallation_1 = tslib_1.__importDefault(PrepareInstallation_1);
     function installPackage(button) {
         const installation = new PrepareInstallation_1.default();
-        installation.start(button.dataset.package, button.dataset.packageVersion);
+        return installation.start(button.dataset.package, button.dataset.packageVersion);
     }
     function setup() {
+        const callback = (0, PromiseMutex_1.promiseMutex)((button) => installPackage(button));
         document.querySelectorAll(".jsInstallPackage").forEach((button) => {
             button.addEventListener("click", () => {
-                installPackage(button);
+                callback(button);
             });
         });
     }
index 7ec4a005f8fc628f708a064f0b7f29348e1bd1c8..97f0da58bac3b9363db2c4d1879662196fab0bb6 100644 (file)
@@ -15,10 +15,17 @@ define(["require", "exports", "tslib", "../../../Ajax", "../../../Language", "..
     class AcpUiPackagePrepareInstallation {
         identifier = "";
         version = "";
+        #resolve;
         start(identifier, version) {
+            if (this.#resolve !== undefined) {
+                throw new Error("There is already a pending installation.");
+            }
             this.identifier = identifier;
             this.version = version;
-            this.prepare({});
+            return new Promise((resolve) => {
+                this.#resolve = resolve;
+                this.prepare({});
+            });
         }
         prepare(authData) {
             const packages = {};
@@ -66,6 +73,8 @@ define(["require", "exports", "tslib", "../../../Ajax", "../../../Language", "..
             else if (data.returnValues.template) {
                 Dialog_1.default.open(this, data.returnValues.template);
             }
+            this.#resolve();
+            this.#resolve = undefined;
         }
         _ajaxSetup() {
             return {
index 20b3b740010585cd3c17daa11c8e282f63e0716a..a4a4c9c00585102656a5ae7c66725f392f3ed628 100644 (file)
@@ -5,7 +5,7 @@
  * @copyright   2001-2019 WoltLab GmbH
  * @license     GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  */
-define(["require", "exports", "tslib", "./PrepareInstallation", "../../../Ajax"], function (require, exports, tslib_1, PrepareInstallation_1, Ajax) {
+define(["require", "exports", "tslib", "./PrepareInstallation", "../../../Ajax", "WoltLabSuite/Core/Helper/PromiseMutex"], function (require, exports, tslib_1, PrepareInstallation_1, Ajax, PromiseMutex_1) {
     "use strict";
     PrepareInstallation_1 = tslib_1.__importDefault(PrepareInstallation_1);
     Ajax = tslib_1.__importStar(Ajax);
@@ -86,11 +86,15 @@ define(["require", "exports", "tslib", "./PrepareInstallation", "../../../Ajax"]
                         this.resultList.innerHTML = data.returnValues.template;
                         this.resultCounter.textContent = data.returnValues.count.toString();
                         this.setStatus("showResults");
+                        const callback = (0, PromiseMutex_1.promiseMutex)((button) => {
+                            return this.installation.start(button.dataset.package, button.dataset.packageVersion);
+                        });
                         this.resultList.querySelectorAll(".jsInstallPackage").forEach((button) => {
                             button.addEventListener("click", (event) => {
                                 event.preventDefault();
-                                button.blur();
-                                this.installation.start(button.dataset.package, button.dataset.packageVersion);
+                                if (callback(button)) {
+                                    button.blur();
+                                }
                             });
                         });
                     }