Пример #1
0
 it("can set values in any of the patterns", function () {
     var data = {
         "**.html": {
             spaceUnits: 2
         },
         "lib/*.js": {
             spaceUnits: 3
         }
     };
     
     var originalData = _.clone(data, true);
     
     var layer = new PreferencesBase.PathLayer("/.brackets.json");
     expect(layer.set(data, "spaceUnits", 10, {
         filename: "/foo.txt"
     })).toBe(false);
     
     expect(data).toEqual(originalData);
     
     expect(layer.set(data, "spaceUnits", 11, {
         filename: "/index.html"
     })).toBe(true);
     expect(data).toEqual({
         "**.html": {
             spaceUnits: 11
         },
         "lib/*.js": {
             spaceUnits: 3
         }
     });
     
     expect(layer.set(data, "spaceUnits", 12, {
         filename: "/index.html"
     }, "lib/*.js")).toBe(true);
     
     expect(data).toEqual({
         "**.html": {
             spaceUnits: 11
         },
         "lib/*.js": {
             spaceUnits: 12
         }
     });
     
     expect(layer.set(data, "spaceUnits", 13, {}, "**.md")).toBe(true);
 });
Пример #2
0
 it("can retrieve the location of the pref value", function () {
     var data = {
         "**.html": {
             spaceUnits: 2
         },
         "lib/*.js": {
             soaceUnits: 3
         }
     };
     
     var layer = new PreferencesBase.PathLayer("/.brackets.json");
     expect(layer.getPreferenceLocation(data, "spaceUnits", {
         filename: "/foo.txt"
     })).toBeUndefined();
     
     expect(layer.getPreferenceLocation(data, "spaceUnits", {
         filename: "/index.html"
     })).toEqual("**.html");
     
     expect(layer.getPreferenceLocation(data, "spaceUnits", {
         filename: "/lib/brackets.js"
     })).toEqual("lib/*.js");
 });
Пример #3
0
 it("handles a variety of glob patterns", function () {
     var data = {
         "**.html": {
             spaceUnits: 2
         },
         "lib/*.js": {
             spaceUnits: 3
         },
         "lib/**.css": {
             spaceUnits: 4
         },
         "*.{md,txt}": {
             spaceUnits: 5
         }
     };
     
     var layer = new PreferencesBase.PathLayer("/.brackets.json");
     
     expect(layer.get(data, "spaceUnits", {
         filename: "/public/index.html"
     })).toBe(2);
     
     expect(layer.get(data, "spaceUnits", {
         filename: "/lib/script.js"
     })).toBe(3);
     
     expect(layer.get(data, "spaceUnits", {
         filename: "/lib/foo/script.js"
     })).toBeUndefined();
     
     expect(layer.get(data, "spaceUnits", {
         filename: "/lib/foo/styles.css"
     })).toBe(4);
     
     expect(layer.get(data, "spaceUnits", {
         filename: "/README.md"
     })).toBe(5);
     
     expect(layer.get(data, "spaceUnits", {
         filename: "foo.js"
     })).toBeUndefined();
 });
Пример #4
0
define(function (require, exports, module) {
    "use strict";
    
    var OldPreferenceStorage = require("preferences/PreferenceStorage").PreferenceStorage,
        FileUtils         = require("file/FileUtils"),
        ExtensionLoader   = require("utils/ExtensionLoader"),
        PreferencesBase   = require("preferences/PreferencesBase"),
        FileSystem        = require("filesystem/FileSystem"),
        _                 = require("thirdparty/lodash");
    
    /**
     * The local storage ID
     * @const
     * @type {string}
     */
    var PREFERENCES_CLIENT_ID = "com.adobe.brackets.preferences";
    
    /**
     * The prefix used in the generated client ID
     * @const
     * @type {string}
     */
    var CLIENT_ID_PREFIX = "com.adobe.brackets.";
    
    
    // Private Properties
    var preferencesKey,
        prefStorage,
        persistentStorage,
        extensionPaths,
        doLoadPreferences   = false;
    
    
    /**
     * @private
     * Returns an array with the extension paths used in Brackets. The result is stored on a
     * private variable on the first call and used to return the value on the next calls.
     * @return {Array.<string>}
     */
    function _getExtensionPaths() {
        if (!extensionPaths) {
            var dirPath = FileUtils.getNativeBracketsDirectoryPath();
            
            extensionPaths = [
                dirPath + "/extensions/default/",
                dirPath + "/extensions/dev/",
                ExtensionLoader.getUserExtensionPath() + "/"
            ];
        }
        return extensionPaths;
    }

    /**
     * This method returns a standardized ClientID for a given requireJS module object
     * @param {!{id: string, uri: string}} module - A requireJS module object
     * @return {string} The ClientID
     */
    function getClientID(module) {
        var paths = exports._getExtensionPaths();
        var pathExp, pathUrl, clientID;

        paths.some(function (path) {
            if (module.uri.toLocaleLowerCase().indexOf(path.toLocaleLowerCase()) === 0) {
                pathUrl = path;
                return true;
            }
        });

        if (pathUrl) {
            clientID = CLIENT_ID_PREFIX + module.uri.replace(pathUrl, "");
        } else {
            clientID = CLIENT_ID_PREFIX + module.id;
        }
        return clientID;
    }
    
    /**
     * Retreive the preferences data for the given clientID.
     * @param {string|{id: string, uri: string}} clientID - A unique identifier or a requireJS module object
     * @param {string=} defaults - Default preferences stored as JSON
     * @param {boolean=} _doNotCreate Do not create the storage if it does not already exist. Used for conversion.
     * @return {PreferenceStorage}
     */
    function getPreferenceStorage(clientID, defaults, _doNotCreate) {
        if (!clientID || (typeof clientID === "object" && (!clientID.id || !clientID.uri))) {
            console.error("Invalid clientID");
            return;
        }
        if (typeof clientID === "object") {
            clientID = getClientID(clientID);
        }

        var prefs = prefStorage[clientID];

        if (prefs === undefined) {
            if (_doNotCreate) {
                return;
            }
            // create a new empty preferences object
            prefs = (defaults && JSON.stringify(defaults)) ? defaults : {};
            prefStorage[clientID] = prefs;
        } else if (defaults) {
            // add new defaults
            _.forEach(defaults, function (value, key) {
                if (prefs[key] === undefined) {
                    prefs[key] = value;
                }
            });
        }

        return new OldPreferenceStorage(clientID, prefs);
    }

    /**
     * Save all preference clients.
     */
    function savePreferences() {
        // save all preferences
        persistentStorage.setItem(preferencesKey, JSON.stringify(prefStorage));
    }

    /**
     * @private
     * Reset preferences and callbacks
     */
    function _reset() {
        prefStorage = {};

        // Note that storage.clear() is not used. Production and unit test code
        // both rely on the same backing storage but unique item keys.
        persistentStorage.setItem(preferencesKey, JSON.stringify(prefStorage));
    }

    /**
     * @private
     * Initialize persistent storage implementation
     */
    function _initStorage(storage) {
        persistentStorage = storage;

        if (doLoadPreferences) {
            prefStorage = JSON.parse(persistentStorage.getItem(preferencesKey));
        }

        // initialize empty preferences if none were found in storage
        if (!prefStorage) {
            _reset();
        }
    }
    
    // Check localStorage for a preferencesKey. Production and unit test keys
    // are used to keep preferences separate within the same storage implementation.
    preferencesKey = localStorage.getItem("preferencesKey");
    
    if (!preferencesKey) {
        // use default key if none is found
        preferencesKey = PREFERENCES_CLIENT_ID;
        doLoadPreferences = true;
    } else {
        // using a non-default key, check for additional settings
        doLoadPreferences = !!(localStorage.getItem("doLoadPreferences"));
    }

    // Use localStorage by default
    _initStorage(localStorage);
    
    
    // Public API
    exports.getPreferenceStorage    = getPreferenceStorage;
    exports.savePreferences         = savePreferences;
    exports.getClientID             = getClientID;


    // Unit test use only
    exports._reset                  = _reset;
    exports._getExtensionPaths      = _getExtensionPaths;
    
    // New code follows. The code above (with the exception of the imports) is
    // deprecated.
    
    // The SETTINGS_FILENAME is used with a preceding "." within user projects
    var SETTINGS_FILENAME = "brackets.json",
        STATE_FILENAME    = "state.json";
    
    // User-level preferences
    var userPrefFile = brackets.app.getApplicationSupportDirectory() + "/" + SETTINGS_FILENAME;
    
    /**
     * Get the full path to the user-level preferences file.
     * 
     * @return {string} Path to the preferences file
     */
    function getUserPrefFile() {
        return userPrefFile;
    }
    
    var preferencesManager = new PreferencesBase.PreferencesSystem();
    
    var userScopeLoading = preferencesManager.addScope("user", new PreferencesBase.FileStorage(userPrefFile, true));
    
    // Set up the .brackets.json file handling
    userScopeLoading.done(function () {
        // Session Scope is for storing prefs in memory only but with the highest precedence.
        preferencesManager.addScope("session", new PreferencesBase.MemoryStorage());
    });
    
    // Create a Project scope
    var projectStorage          = new PreferencesBase.FileStorage(undefined, true),
        projectScope            = new PreferencesBase.Scope(projectStorage),
        projectPathLayer        = new PreferencesBase.PathLayer(),
        projectDirectory        = null,
        currentEditedFile       = null,
        projectScopeIsIncluded  = true;
    
    projectScope.addLayer(projectPathLayer);
    
    preferencesManager.addScope("project", projectScope, {
        before: "user"
    });
    
    /**
     * @private
     * 
     * Determines whether the project Scope should be included based on whether
     * the currently edited file is within the project.
     * 
     * @param {string=} filename Full path to edited file
     * @return {boolean} true if the project Scope should be included.
     */
    function _includeProjectScope(filename) {
        filename = filename || currentEditedFile;
        if (!filename || !projectDirectory) {
            return false;
        }
        return FileUtils.getRelativeFilename(projectDirectory, filename) ? true : false;
    }
    
    /**
     * @private
     * 
     * Adds or removes the project Scope as needed based on whether the currently
     * edited file is within the project.
     */
    function _toggleProjectScope() {
        if (_includeProjectScope() === projectScopeIsIncluded) {
            return;
        }
        if (projectScopeIsIncluded) {
            preferencesManager.removeFromScopeOrder("project");
        } else {
            preferencesManager.addToScopeOrder("project", "user");
        }
        projectScopeIsIncluded = !projectScopeIsIncluded;
    }
    
    /**
     * @private
     * 
     * This is used internally within Brackets for the ProjectManager to signal
     * which file contains the project-level preferences.
     * 
     * @param {string} settingsFile Full path to the project's settings file
     */
    function _setProjectSettingsFile(settingsFile) {
        projectDirectory = FileUtils.getDirectoryPath(settingsFile);
        _toggleProjectScope();
        projectPathLayer.setPrefFilePath(settingsFile);
        projectStorage.setPath(settingsFile);
    }
    
    /**
     * @private
     * 
     * This is used internally within Brackets for the EditorManager to signal
     * to the preferences what the currently edited file is.
     * 
     * @param {string} currentFile Full path to currently edited file
     */
    function _setCurrentEditingFile(currentFile) {
        currentEditedFile = currentFile;
        _toggleProjectScope();
        preferencesManager.setDefaultFilename(currentFile);
    }
    
    /**
     * Creates an extension-specific preferences manager using the prefix given.
     * A `.` character will be appended to the prefix. So, a preference named `foo`
     * with a prefix of `myExtension` will be stored as `myExtension.foo` in the
     * preferences files.
     * 
     * @param {string} prefix Prefix to be applied
     */
    function getExtensionPrefs(prefix) {
        return preferencesManager.getPrefixedSystem(prefix);
    }
    
    /**
     * Converts from the old localStorage-based preferences to the new-style
     * preferences according to the "rules" given.
     * 
     * `rules` is an object, the keys of which refer to the preference names.
     * The value tells the converter what to do. The following values are available:
     * 
     * * `user`: convert to a user-level preference
     * * `user newkey`: convert to a user-level preference, changing the key to newkey
     * 
     * Once a key has been converted, it will not be converted again.
     * 
     * @param {string|Object} clientID ClientID used in the old preferences
     * @param {Object} rules Rules for conversion (as defined above)
     */
    function convertPreferences(clientID, rules) {
        userScopeLoading.done(function () {
            var prefs = getPreferenceStorage(clientID, null, true);
            
            if (!prefs) {
                return;
            }
            
            var prefsID = getClientID(clientID);
            if (prefStorage.convertedKeysMap === undefined) {
                prefStorage.convertedKeysMap = {};
            }
            var convertedKeysMap = prefStorage.convertedKeysMap;
            
            prefs.convert(rules, convertedKeysMap[prefsID]).done(function (complete, convertedKeys) {
                prefStorage.convertedKeysMap[prefsID] = convertedKeys;
                savePreferences();
            });
        }).fail(function (error) {
            console.error("Error while converting ", getClientID(clientID));
            console.error(error);
        });
    }

    // "State" is stored like preferences but it is not generally intended to be user-editable.
    // It's for more internal, implicit things like window size, working set, etc.
    var stateManager = new PreferencesBase.PreferencesSystem();
    var userStateFile = brackets.app.getApplicationSupportDirectory() + "/" + STATE_FILENAME;
    
    stateManager.addScope("user", new PreferencesBase.FileStorage(userStateFile, true));
    
    // Constants for preference lookup contexts.
    
    /**
     * Context to look up preferences in the current project.
     * @type {Object}
     */
    var CURRENT_PROJECT = {};
    
    /**
     * Context to look up preferences for the currently edited file.
     * This is undefined because this is the default behavior of PreferencesSystem.get.
     * 
     * @type {Object}
     */
    var CURRENT_FILE;
    
    /**
     * Cached copy of the scopeOrder with the project Scope
     */
    var scopeOrderWithProject = null;
    
    /**
     * Cached copy of the scopeOrder without the project Scope
     */
    var scopeOrderWithoutProject = null;
    
    /**
     * @private
     * 
     * Adjusts scopeOrder to have the project Scope if necessary.
     * Returns a new array if changes are needed, otherwise returns
     * the original array.
     * 
     * @param {Array.<string>} scopeOrder initial scopeOrder
     * @param {boolean} includeProject Whether the project Scope should be included
     * @return {Array.<string>} array with or without project Scope as needed.
     */
    function _adjustScopeOrderForProject(scopeOrder, includeProject) {
        var hasProject = scopeOrder.indexOf("project") > -1;
        
        if (hasProject === includeProject) {
            return scopeOrder;
        }
        
        var newScopeOrder;
        
        if (includeProject) {
            var before = scopeOrder.indexOf("user");
            if (before === -1) {
                before = scopeOrder.length - 2;
            }
            newScopeOrder = _.first(scopeOrder, before);
            newScopeOrder.push("project");
            newScopeOrder.push.apply(newScopeOrder, _.rest(scopeOrder, before));
        } else {
            newScopeOrder = _.without(scopeOrder, "project");
        }
        return newScopeOrder;
    }
    
    /**
     * @private
     * 
     * Normalizes the context object to be something that the PreferencesSystem
     * understands. This is how we support CURRENT_FILE and CURRENT_PROJECT
     * preferences.
     * 
     * @param {Object|string} context CURRENT_FILE, CURRENT_PROJECT or a filename
     */
    function _normalizeContext(context) {
        if (typeof context === "string") {
            context = {
                filename: context
            };
            context.scopeOrder = _includeProjectScope(context.filename) ?
                                    scopeOrderWithProject :
                                    scopeOrderWithoutProject;
        }
        return context;
    }
    
    /**
     * @private
     * 
     * Updates the CURRENT_PROJECT context to have the correct scopes.
     */
    function _updateCurrentProjectContext() {
        var context = preferencesManager.buildContext({});
        delete context.filename;
        scopeOrderWithProject = _adjustScopeOrderForProject(context.scopeOrder, true);
        scopeOrderWithoutProject = _adjustScopeOrderForProject(context.scopeOrder, false);
        CURRENT_PROJECT.scopeOrder = scopeOrderWithProject;
    }
    
    _updateCurrentProjectContext();
    
    preferencesManager.on("scopeOrderChange", _updateCurrentProjectContext);
    
    /**
     * Look up a preference in the given context. The default is 
     * CURRENT_FILE (preferences as they would be applied to the
     * currently edited file).
     * 
     * @param {string} id Preference ID to retrieve the value of
     * @param {Object|string=} context CURRENT_FILE, CURRENT_PROJECT or a filename
     */
    function get(id, context) {
        context = _normalizeContext(context);
        return preferencesManager.get(id, context);
    }
    
    /**
     * Sets a preference and notifies listeners that there may
     * have been a change. By default, the preference is set in the same location in which
     * it was defined except for the "default" scope. If the current value of the preference
     * comes from the "default" scope, the new value will be set at the level just above
     * default.
     * 
     * As with the `get()` function, the context can be a filename,
     * CURRENT_FILE, CURRENT_PROJECT or a full context object as supported by
     * PreferencesSystem.
     * 
     * @param {string} id Identifier of the preference to set
     * @param {Object} value New value for the preference
     * @param {{location: ?Object, context: ?Object|string}=} options Specific location in which to set the value or the context to use when setting the value
     * @return {boolean} true if a value was set
     */
    function set(id, value, options) {
        if (options && options.context) {
            options.context = _normalizeContext(options.context);
        }
        return preferencesManager.set(id, value, options);
    }

    /**
     * Convenience function that sets a preference and then saves the file, mimicking the
     * old behavior a bit more closely.
     * 
     * @param {string} id preference to set
     * @param {*} value new value for the preference
     * @param {{location: ?Object, context: ?Object|string}=} options Specific location in which to set the value or the context to use when setting the value
     * @return {boolean} true if a value was set
     */
    function setValueAndSave(id, value, options) {
        var changed = set(id, value, options);
        preferencesManager.save();
        return changed;
    }
    
    
    // Private API for unit testing and use elsewhere in Brackets core
    exports._manager                = preferencesManager;
    exports._setCurrentEditingFile  = _setCurrentEditingFile;
    exports._setProjectSettingsFile = _setProjectSettingsFile;
    
    // Public API
    
    // Context names for preference lookups
    exports.CURRENT_FILE        = CURRENT_FILE;
    exports.CURRENT_PROJECT     = CURRENT_PROJECT;
    
    exports.getUserPrefFile     = getUserPrefFile;
    exports.get                 = get;
    exports.set                 = set;
    exports.save                = preferencesManager.save.bind(preferencesManager);
    exports.on                  = preferencesManager.on.bind(preferencesManager);
    exports.off                 = preferencesManager.off.bind(preferencesManager);
    exports.getPreference       = preferencesManager.getPreference.bind(preferencesManager);
    exports.getExtensionPrefs   = getExtensionPrefs;
    exports.setValueAndSave     = setValueAndSave;
    exports.addScope            = preferencesManager.addScope.bind(preferencesManager);
    exports.stateManager        = stateManager;
    exports.FileStorage         = PreferencesBase.FileStorage;
    exports.SETTINGS_FILENAME   = SETTINGS_FILENAME;
    exports.definePreference    = preferencesManager.definePreference.bind(preferencesManager);
    exports.fileChanged         = preferencesManager.fileChanged.bind(preferencesManager);
    exports.convertPreferences  = convertPreferences;
});
Пример #5
0
 /**
  * @private
  * 
  * This is used internally within Brackets for the ProjectManager to signal
  * which file contains the project-level preferences.
  * 
  * @param {string} settingsFile Full path to the project's settings file
  */
 function _setProjectSettingsFile(settingsFile) {
     projectDirectory = FileUtils.getDirectoryPath(settingsFile);
     _toggleProjectScope();
     projectPathLayer.setPrefFilePath(settingsFile);
     projectStorage.setPath(settingsFile);
 }