Viewing File: /usr/local/cpanel/share/libraries/cjt2/src/services/componentSettingSaverFactory.js

/*
 * cjt2/src/services/componentSettingSaverFactory.js
 *                                                  Copyright 2022 cPanel, L.L.C.
 *                                                           All rights reserved.
 * copyright@cpanel.net                                         http://cpanel.net
 * This code is subject to the cPanel license. Unauthorized copying is prohibited
 */

/**
 * Factory module that generates componentSettingSaverServices for the
 * current application environment.
 *
 * @module cjt/services/componentSettingSaverFactory
 *
 */

define(
    [
        "angular",
        "cjt/core",
        "cjt/util/locale",
        "cjt/services/pageIdentifierService"
    ],
    function(angular, CJT, LOCALE) {
        "use strict";

        /**
         * Factory method to generate the componentSettingSaverService for the
         * context.
         *
         * @method makeService
         * @param  {String} moduleName angularjs module name for the generated service
         * @return {ComponentSettingSaverService}
         */
        return function makeService(moduleName, dependencies) {

            // Retrieve the current application
            var module = angular.module(moduleName, dependencies);

            return module.factory("componentSettingSaverService", [
                "nvDataService",
                "$log",
                "$q",
                "$window",
                "pageIdentifierService",
                function(nvDataService, $log, $q, $window, pageIdentifierService) {
                    return {

                        /**
                         * The most recent promise fetching the nvdata if any.
                         * If null, there is no outstanding promise.
                         *
                         * @name _currentGetPromise
                         * @private
                         * @type {?Promise}
                         */
                        _currentGetPromise: null,


                        /**
                         * The last time an update was requested
                         *
                         * @name _lastUpdateRequest
                         * @private
                         * @type {DateTime}
                         */
                        _lastUpdateRequest: -1,

                        /**
                         * Last time a save was requested
                         *
                         * @name _lastSavedRequest
                         * @private
                         * @type {DateTime}
                         */
                        _lastSavedRequest: 0,

                        /**
                         * The collection of components that have been fetched or set. All the data is
                         * stored as a JSON serialized version of this object. Each key is a component
                         * and each value is the settings stored for that component.
                         *
                         * @name _components
                         * @private
                         * @type {Object}
                         */
                        _components: {},


                        /**
                         * The collection of components that are managed with this service.
                         * Each key is a component name. All these components are associated
                         * with the page identifier so are page specific.
                         *
                         * @name _registeredComponents
                         * @private
                         * @type {Object}
                         */
                        _registeredComponents: {},


                        /**
                         * Use for registering new saveable components. When a component name is registered
                         * a get request is automatically started.
                         *
                         * @method register
                         * @async
                         * @param  {String} componentName page unique identifier for the saveable component
                         * @return {Promise.<NameValuePair|Object.<NameValuePair>>} if componentName is specified, it will return just that setting, otherwise it returns all the settings for the specific _pageIdentifier
                         * @throws {Promise.<String>} If the api call fails, the request times out or the request fails
                         */
                        register: function registerComponent(componentName) {

                            if (!componentName) {
                                throw new Error(LOCALE.maketext("The [asis,register] method requires the [asis,componentName]."));
                            }

                            var pageId = pageIdentifierService.getPageIdentifier();
                            if ( !pageId ) {
                                throw new Error(LOCALE.maketext("[asis,ComponentSettingSaverService] failed to register the component “[_1]”. You must set the page identifier.", componentName));
                            }

                            // register as a deletable NVData file if function is available to do so.
                            // TODO: Replace with something portable from cjt2
                            if (!angular.isFunction($window["register_interfacecfg_nvdata"])) {
                                $log.warn(LOCALE.maketext("The system could not register the page for the interface settings reset. Is the [asis,interfacereset.js] file missing?"));
                            } else {

                                // TODO: Alter register_interfacecfg_nvdata() to prevent duplicate registration
                                $window.register_interfacecfg_nvdata(this._pageIdentifier);
                            }

                            // QUESTION: From what I can tell the only thing that the register() is used for is
                            // to prevent you from using the same componentName twice, but register() is really
                            // just a call to get(). What is the reason we want to prevent calling get() twice with
                            // the same name. It already has the promise cache to prevent multiple backend requests.
                            if (this._registeredComponents[componentName]) {
                                throw new Error(LOCALE.maketext("[asis,ComponentSettingSaverService] failed to register the component “[_1]”. A component with the same identifier already exists.", componentName));
                            }

                            this._registeredComponents[componentName] = componentName;

                            return this.get(componentName);

                        },

                        /**
                         * Unregisters a saveable component. Single Page Apps may try to register the same component name
                         * more than once when navigating from one view to another and back again. Use this method to free
                         * up the component key when it's no longer needed for a view.
                         *
                         * @method unregisterComponent
                         * @param  {String} componentName   The unique identifier for the component.
                         * @return {Boolean}                True if it succeeds, false if it fails.
                         */
                        unregister: function unregisterComponent(componentName) {

                            // QUESTION: Should unregistering a component also clear it data from the _components
                            // structure since its not part of the other view?
                            // QUESTION: It seems like this tool needs to have a way to pick up the view name also?
                            // NOTE: If we remove register(), then unregister can be removed too.

                            if (this._registeredComponents[componentName]) {
                                delete this._registeredComponents[componentName];
                                return true;
                            } else {
                                $log.error(LOCALE.maketext("[asis,ComponentSettingSaverService] failed to unregister the component “[_1]”. No such component exists.", componentName));
                                return false;
                            }

                        },

                        /**
                         * Set the value of a component
                         *
                         * @method set
                         * @async
                         * @param  {String} componentName page unique identifier for the saveable component. must be registered
                         * @param  {Object|Array} settings JSON encodeable object representing the component settings
                         * @return {Promise.<Array.<SavedNameValuePair>>} returns a promise call that is setting the values via api
                         * @throws {Promise.<String>} If the api call fails, the request times out or the request fails
                         */
                        set: function setComponentSettings(componentName, settings) {

                            var pageId = pageIdentifierService.getPageIdentifier();
                            if (!pageId) {
                                $log.error(LOCALE.maketext("[asis,ComponentSettingSaverService] failed to save the component settings for “[_1]”. You must set the page identifier.", componentName));
                                return false; // TODO: It's complicated to return both a promise and a bool. Should probably throw or return a $q.defer().reject()
                            }

                            this._components[componentName] = settings;

                            var nvdata = {};
                            nvdata[pageId] = JSON.stringify(this._components);

                            this._lastSavedRequest = new Date().getTime();

                            return nvDataService.setObject(nvdata);

                        },

                        /**
                         * Used for getting the updated values of a component as stored in the NVData
                         *
                         * @method get
                         * @async
                         * @param  {String} componentName page unique identifier for the saveable component. must be registered through .register()
                         * @return {Promise.<NameValuePair|Object.<NameValuePair>>} if componentName is specified, it will return just that setting, otherwise it returns all the settings for the specific _pageIdentifier
                         * @throws {Promise.<String>} If the api call fails, the request times out or the request fails
                         */
                        get: function getComponentSettings(componentName) {
                            var self = this;

                            // If we are in the process of getting an updated file, just use the existing promise
                            if (self._currentGetPromise) {
                                return self._currentGetPromise;
                            }

                            // If we haven't saved anything new since we last saved this page, don't retrieve again.
                            if (self._lastUpdateRequest > self._lastSavedRequest) {
                                var deferred = $q.defer();
                                if (componentName) {
                                    deferred.resolve(self._components[componentName]);
                                } else {
                                    deferred.resolve(self._components);
                                }
                                return deferred.promise;
                            }

                            self._lastUpdateRequest = new Date().getTime();

                            var pageId = pageIdentifierService.getPageIdentifier();
                            if (!pageId) {
                                $log.error(LOCALE.maketext("[asis,ComponentSettingSaverService] failed to retrieve the requested component settings. You must set the page identifier."));
                                return false;
                            }

                            self._currentGetPromise = nvDataService.get(pageId).then(
                                function(pairs) {
                                    var pair = pairs.pop();
                                    var value = pair.value;
                                    if (value) {
                                        try {
                                            value = JSON.parse(value);
                                        } catch (e) {
                                            var pageId = pageIdentifierService.getPageIdentifier();
                                            $log.error(LOCALE.maketext("[asis,ComponentSettingSaverService] failed to parse the stored [asis,NVData] file for this page “[_1]”.", pageId));
                                            value = {};
                                        }
                                        self._components = value;
                                    }

                                    // Need to clear this before calling again so it resolves properly
                                    self._currentGetPromise = null;
                                    return self.get(componentName);
                                }).finally(function() {
                                self._currentGetPromise = null;
                            });

                            return self._currentGetPromise;

                        },

                        /**
                         * Get the cached settings for a component. This is useful when you are not willing to wait on
                         * the network request and just want to get the currently known value.
                         *
                         * @param {string} componentName   The component whose values we want to retrieve.
                         * @returns {object}   An object containing cachedValue and requestInProgress keys.
                         */
                        getCached: function getCachedComponentSettings(componentName) {
                            return {
                                cachedValue: componentName ? this._components[componentName] : this._components,
                                requestInProgress: Boolean( this._currentGetPromise ),
                            };
                        },
                    };
                }
            ]);
        };
    }
);
Back to Directory File Manager