'use strict'; /** * The Pad Manager is a Factory for pad Objects */ /* * 2011 Peter 'Pita' Martischka (Primary Technology Ltd) * * Licensed 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. */ const CustomError = require('../utils/customError'); const Pad = require('../db/Pad'); const db = require('./DB'); /** * A cache of all loaded Pads. * * Provides "get" and "set" functions, * which should be used instead of indexing with brackets. These prepend a * colon to the key, to avoid conflicting with built-in Object methods or with * these functions themselves. * * If this is needed in other places, it would be wise to make this a prototype * that's defined somewhere more sensible. */ const globalPads = { get(name) { return this[`:${name}`]; }, set(name, value) { this[`:${name}`] = value; }, remove(name) { delete this[`:${name}`]; }, }; /** * A cache of the list of all pads. * * Updated without db access as new pads are created/old ones removed. */ const padList = new class { constructor() { this._cachedList = null; this._list = new Set(); this._loaded = null; } /** * Returns all pads in alphabetical order as array. */ async getPads() { if (!this._loaded) { this._loaded = (async () => { const dbData = await db.findKeys('pad:*', '*:*:*'); if (dbData == null) return; for (const val of dbData) this.addPad(val.replace(/^pad:/, '')); })(); } await this._loaded; if (!this._cachedList) this._cachedList = [...this._list].sort(); return this._cachedList; } addPad(name) { if (this._list.has(name)) return; this._list.add(name); this._cachedList = null; } removePad(name) { if (!this._list.has(name)) return; this._list.delete(name); this._cachedList = null; } }(); // initialises the all-knowing data structure /** * Returns a Pad Object with the callback * @param id A String with the id of the pad * @param {string} [text] - Optional initial pad text if creating a new pad. * @param {string} [authorId] - Optional author ID of the user that initiated the pad creation (if * applicable). */ exports.getPad = async (id, text, authorId = '') => { // check if this is a valid padId if (!exports.isValidPadId(id)) { throw new CustomError(`${id} is not a valid padId`, 'apierror'); } // check if this is a valid text if (text != null) { // check if text is a string if (typeof text !== 'string') { throw new CustomError('text is not a string', 'apierror'); } // check if text is less than 100k chars if (text.length > 100000) { throw new CustomError('text must be less than 100k chars', 'apierror'); } } let pad = globalPads.get(id); // return pad if it's already loaded if (pad != null) { return pad; } // try to load pad pad = new Pad.Pad(id); // initialize the pad await pad.init(text, authorId); globalPads.set(id, pad); padList.addPad(id); return pad; }; exports.listAllPads = async () => { const padIDs = await padList.getPads(); return {padIDs}; }; // checks if a pad exists exports.doesPadExist = async (padId) => { const value = await db.get(`pad:${padId}`); return (value != null && value.atext); }; // alias for backwards compatibility exports.doesPadExists = exports.doesPadExist; /** * An array of padId transformations. These represent changes in pad name policy over * time, and allow us to "play back" these changes so legacy padIds can be found. */ const padIdTransforms = [ [/\s+/g, '_'], [/:+/g, '_'], ]; // returns a sanitized padId, respecting legacy pad id formats exports.sanitizePadId = async (padId) => { for (let i = 0, n = padIdTransforms.length; i < n; ++i) { const exists = await exports.doesPadExist(padId); if (exists) { return padId; } const [from, to] = padIdTransforms[i]; padId = padId.replace(from, to); } // we're out of possible transformations, so just return it return padId; }; exports.isValidPadId = (padId) => /^(g.[a-zA-Z0-9]{16}\$)?[^$]{1,50}$/.test(padId); /** * Removes the pad from database and unloads it. */ exports.removePad = async (padId) => { const p = db.remove(`pad:${padId}`); exports.unloadPad(padId); padList.removePad(padId); await p; }; // removes a pad from the cache exports.unloadPad = (padId) => { globalPads.remove(padId); };