GUACAMOLE-1571: Merge properly wrap stream errors, and check available translations instead of hard-coding.
diff --git a/guacamole/src/main/frontend/src/app/client/directives/guacClientNotification.js b/guacamole/src/main/frontend/src/app/client/directives/guacClientNotification.js
index e1a08eb..03344b6 100644
--- a/guacamole/src/main/frontend/src/app/client/directives/guacClientNotification.js
+++ b/guacamole/src/main/frontend/src/app/client/directives/guacClientNotification.js
@@ -34,16 +34,16 @@
/**
* The client whose status should be displayed.
- *
+ *
* @type ManagedClient
*/
client : '='
-
+
};
directive.controller = ['$scope', '$injector', '$element',
function guacClientNotificationController($scope, $injector, $element) {
-
+
// Required types
const ManagedClient = $injector.get('ManagedClient');
const ManagedClientState = $injector.get('ManagedClientState');
@@ -53,6 +53,7 @@
const $location = $injector.get('$location');
const authenticationService = $injector.get('authenticationService');
const guacClientManager = $injector.get('guacClientManager');
+ const guacTranslate = $injector.get('guacTranslate');
const requestService = $injector.get('requestService');
const userPageService = $injector.get('userPageService');
@@ -66,26 +67,6 @@
$scope.status = false;
/**
- * All client error codes handled and passed off for translation. Any error
- * code not present in this list will be represented by the "DEFAULT"
- * translation.
- */
- const CLIENT_ERRORS = {
- 0x0201: true,
- 0x0202: true,
- 0x0203: true,
- 0x0207: true,
- 0x0208: true,
- 0x0209: true,
- 0x020A: true,
- 0x020B: true,
- 0x0301: true,
- 0x0303: true,
- 0x0308: true,
- 0x031D: true
- };
-
- /**
* All error codes for which automatic reconnection is appropriate when a
* client error occurs.
*/
@@ -98,26 +79,7 @@
0x0301: true,
0x0308: true
};
-
- /**
- * All tunnel error codes handled and passed off for translation. Any error
- * code not present in this list will be represented by the "DEFAULT"
- * translation.
- */
- const TUNNEL_ERRORS = {
- 0x0201: true,
- 0x0202: true,
- 0x0203: true,
- 0x0204: true,
- 0x0205: true,
- 0x0207: true,
- 0x0208: true,
- 0x0301: true,
- 0x0303: true,
- 0x0308: true,
- 0x031D: true
- };
-
+
/**
* All error codes for which automatic reconnection is appropriate when a
* tunnel error occurs.
@@ -239,7 +201,7 @@
// Get any associated status code
const status = $scope.client.clientState.statusCode;
- // Connecting
+ // Connecting
if (connectionState === ManagedClientState.ConnectionState.CONNECTING
|| connectionState === ManagedClientState.ConnectionState.WAITING) {
$scope.status = {
@@ -254,44 +216,58 @@
// Client error
else if (connectionState === ManagedClientState.ConnectionState.CLIENT_ERROR) {
- // Determine translation name of error
- const errorName = (status in CLIENT_ERRORS) ? status.toString(16).toUpperCase() : "DEFAULT";
+ // Translation IDs for this error code
+ const errorPrefix = "CLIENT.ERROR_CLIENT_";
+ const errorId = errorPrefix + status.toString(16).toUpperCase();
+ const defaultErrorId = errorPrefix + "DEFAULT";
// Determine whether the reconnect countdown applies
const countdown = (status in CLIENT_AUTO_RECONNECT) ? RECONNECT_COUNTDOWN : null;
- // Show error status
- notifyConnectionClosed({
- className : "error",
- title : "CLIENT.DIALOG_HEADER_CONNECTION_ERROR",
- text : {
- key : "CLIENT.ERROR_CLIENT_" + errorName
- },
- countdown : countdown,
- actions : actions
- });
+ // Use the guacTranslate service to determine if there is a translation for
+ // this error code; if not, use the default
+ guacTranslate(errorId, defaultErrorId).then(
+
+ // Show error status
+ translationResult => notifyConnectionClosed({
+ className : "error",
+ title : "CLIENT.DIALOG_HEADER_CONNECTION_ERROR",
+ text : {
+ key : translationResult.id
+ },
+ countdown : countdown,
+ actions : actions
+ })
+ );
}
// Tunnel error
else if (connectionState === ManagedClientState.ConnectionState.TUNNEL_ERROR) {
- // Determine translation name of error
- const errorName = (status in TUNNEL_ERRORS) ? status.toString(16).toUpperCase() : "DEFAULT";
+ // Translation IDs for this error code
+ const errorPrefix = "CLIENT.ERROR_TUNNEL_";
+ const errorId = errorPrefix + status.toString(16).toUpperCase();
+ const defaultErrorId = errorPrefix + "DEFAULT";
// Determine whether the reconnect countdown applies
const countdown = (status in TUNNEL_AUTO_RECONNECT) ? RECONNECT_COUNTDOWN : null;
- // Show error status
- notifyConnectionClosed({
- className : "error",
- title : "CLIENT.DIALOG_HEADER_CONNECTION_ERROR",
- text : {
- key : "CLIENT.ERROR_TUNNEL_" + errorName
- },
- countdown : countdown,
- actions : actions
- });
+ // Use the guacTranslate service to determine if there is a translation for
+ // this error code; if not, use the default
+ guacTranslate(errorId, defaultErrorId).then(
+
+ // Show error status
+ translationResult => notifyConnectionClosed({
+ className : "error",
+ title : "CLIENT.DIALOG_HEADER_CONNECTION_ERROR",
+ text : {
+ key : translationResult.id
+ },
+ countdown : countdown,
+ actions : actions
+ })
+ );
}
diff --git a/guacamole/src/main/frontend/src/app/client/directives/guacFileTransfer.js b/guacamole/src/main/frontend/src/app/client/directives/guacFileTransfer.js
index a9c09bc..d016a72 100644
--- a/guacamole/src/main/frontend/src/app/client/directives/guacFileTransfer.js
+++ b/guacamole/src/main/frontend/src/app/client/directives/guacFileTransfer.js
@@ -30,7 +30,7 @@
/**
* The file transfer to display.
- *
+ *
* @type ManagedFileUpload|ManagedFileDownload
*/
transfer : '='
@@ -40,28 +40,13 @@
templateUrl: 'app/client/templates/guacFileTransfer.html',
controller: ['$scope', '$injector', function guacFileTransferController($scope, $injector) {
+ // Required services
+ const guacTranslate = $injector.get('guacTranslate');
+
// Required types
var ManagedFileTransferState = $injector.get('ManagedFileTransferState');
/**
- * All upload error codes handled and passed off for translation.
- * Any error code not present in this list will be represented by
- * the "DEFAULT" translation.
- */
- var UPLOAD_ERRORS = {
- 0x0100: true,
- 0x0201: true,
- 0x0202: true,
- 0x0203: true,
- 0x0204: true,
- 0x0205: true,
- 0x0301: true,
- 0x0303: true,
- 0x0308: true,
- 0x031D: true
- };
-
- /**
* Returns the unit string that is most appropriate for the
* number of bytes transferred thus far - either 'gb', 'mb', 'kb',
* or 'b'.
@@ -193,7 +178,7 @@
return;
// Save file
- saveAs($scope.transfer.blob, $scope.transfer.filename);
+ saveAs($scope.transfer.blob, $scope.transfer.filename);
};
@@ -210,23 +195,20 @@
return $scope.transfer.transferState.streamState === ManagedFileTransferState.StreamState.ERROR;
};
- /**
- * Returns the text of the current error as a translation string.
- *
- * @returns {String}
- * The name of the translation string containing the text
- * associated with the current error.
- */
- $scope.getErrorText = function getErrorText() {
+ // The translated error message for the current status code
+ $scope.translatedErrorMessage = '';
+
+ $scope.$watch('transfer.transferState.statusCode', function statusCodeChanged(statusCode) {
// Determine translation name of error
- var status = $scope.transfer.transferState.statusCode;
- var errorName = (status in UPLOAD_ERRORS) ? status.toString(16).toUpperCase() : "DEFAULT";
+ const errorName = 'CLIENT.ERROR_UPLOAD_' + statusCode.toString(16).toUpperCase();
- // Return translation string
- return 'CLIENT.ERROR_UPLOAD_' + errorName;
+ // Use translation string, or the default if no translation is found for this error code
+ guacTranslate(errorName, 'CLIENT.ERROR_UPLOAD_DEFAULT').then(
+ translationResult => $scope.translatedErrorMessage = translationResult.message
+ );
- };
+ });
}] // end file transfer controller
diff --git a/guacamole/src/main/frontend/src/app/client/services/guacTranslate.js b/guacamole/src/main/frontend/src/app/client/services/guacTranslate.js
new file mode 100644
index 0000000..c7fe8e9
--- /dev/null
+++ b/guacamole/src/main/frontend/src/app/client/services/guacTranslate.js
@@ -0,0 +1,82 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+/**
+ * A wrapper around the angular-translate $translate service that offers a
+ * convenient way to fall back to a default translation if the requested
+ * translation is not available.
+ */
+ angular.module('client').factory('guacTranslate', ['$injector', function guacTranslate($injector) {
+
+ // Required services
+ const $q = $injector.get('$q');
+ const $translate = $injector.get('$translate');
+
+ // Required types
+ const TranslationResult = $injector.get('TranslationResult');
+
+ /**
+ * Returns a promise that will be resolved with a TranslationResult containg either the
+ * requested ID and message (if translated), or the default ID and message if translated,
+ * or the literal value of `defaultTranslationId` for both the ID and message if neither
+ * is translated.
+ *
+ * @param {String} translationId
+ * The requested translation ID, which may or may not be translated.
+ *
+ * @param {Sting} defaultTranslationId
+ * The translation ID that will be used if no translation is found for `translationId`.
+ *
+ * @returns {Promise.<TranslationResult>}
+ * A promise which resolves with a TranslationResult containing the results from
+ * the translation attempt.
+ */
+ var translateWithFallback = function translateWithFallback(translationId, defaultTranslationId) {
+ const deferredTranslation = $q.defer();
+
+ // Attempt to translate the requested translation ID
+ $translate(translationId).then(
+
+ // If the requested translation is available, use that
+ translation => deferredTranslation.resolve(new TranslationResult({
+ id: translationId, message: translation
+ })),
+
+ // Otherwise, try the default translation ID
+ () => $translate(defaultTranslationId).then(
+
+ // Default translation worked, so use that
+ defaultTranslation =>
+ deferredTranslation.resolve(new TranslationResult({
+ id: defaultTranslationId, message: defaultTranslation
+ })),
+
+ // Neither translation is available; as a fallback, return default ID for both
+ () => deferredTranslation.resolve(new TranslationResult({
+ id: defaultTranslationId, message: defaultTranslationId
+ })),
+ )
+ );
+
+ return deferredTranslation.promise;
+ };
+
+ return translateWithFallback;
+
+}]);
diff --git a/guacamole/src/main/frontend/src/app/client/templates/guacFileTransfer.html b/guacamole/src/main/frontend/src/app/client/templates/guacFileTransfer.html
index dd96baa..32ead84 100644
--- a/guacamole/src/main/frontend/src/app/client/templates/guacFileTransfer.html
+++ b/guacamole/src/main/frontend/src/app/client/templates/guacFileTransfer.html
@@ -10,7 +10,7 @@
</div>
<!-- Error text -->
- <p class="error-text">{{getErrorText() | translate}}</p>
+ <p class="error-text">{{translatedErrorMessage}}</p>
</div>
diff --git a/guacamole/src/main/frontend/src/app/client/types/TranslationResult.js b/guacamole/src/main/frontend/src/app/client/types/TranslationResult.js
new file mode 100644
index 0000000..0a81511
--- /dev/null
+++ b/guacamole/src/main/frontend/src/app/client/types/TranslationResult.js
@@ -0,0 +1,59 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+/**
+ * Provides the TranslationResult class used by the guacTranslate service. This class contains
+ * both the translated message and the translation ID that generated the message, in the case
+ * where it's unknown whether a translation is defined or not.
+ */
+ angular.module('client').factory('TranslationResult', [function defineTranslationResult() {
+
+ /**
+ * Object which represents the result of a translation as returned from
+ * the guacTranslate service.
+ *
+ * @constructor
+ * @param {TranslationResult|Object} [template={}]
+ * The object whose properties should be copied within the new
+ * TranslationResult.
+ */
+ const TranslationResult = function TranslationResult(template) {
+
+ // Use empty object by default
+ template = template || {};
+
+ /**
+ * The translation ID.
+ *
+ * @type {String}
+ */
+ this.id = template.id;
+
+ /**
+ * The translated message.
+ *
+ * @type {String}
+ */
+ this.message = template.message;
+
+ };
+
+ return TranslationResult;
+
+}]);
\ No newline at end of file
diff --git a/guacamole/src/main/frontend/src/app/rest/services/tunnelService.js b/guacamole/src/main/frontend/src/app/rest/services/tunnelService.js
index 1f0cde5..33a8d71 100644
--- a/guacamole/src/main/frontend/src/app/rest/services/tunnelService.js
+++ b/guacamole/src/main/frontend/src/app/rest/services/tunnelService.js
@@ -316,7 +316,7 @@
// Parse and reject with resulting JSON error
else if (xhr.getResponseHeader('Content-Type') === 'application/json')
- deferred.reject(angular.fromJson(xhr.responseText));
+ deferred.reject(new Error(angular.fromJson(xhr.responseText)));
// Warn of lack of permission of a proxy rejects the upload
else if (xhr.status >= 400 && xhr.status < 500)