UNPKG

scratch-storage

Version:

Load and store project and asset files for Scratch 3.0

177 lines (157 loc) 6.68 kB
import md5 from 'js-md5'; import log from './log'; import Asset, {AssetData, AssetId} from './Asset'; import {AssetType} from './AssetType'; import {DataFormat} from './DataFormat'; import Helper from './Helper'; import defaultImageBitmap from './builtins/defaultBitmap.png?arrayBuffer'; import defaultSound from './builtins/defaultSound.wav?arrayBuffer'; import defaultImageVector from './builtins/defaultVector.svg?arrayBuffer'; import {Buffer} from 'buffer/'; import {ScratchStorage} from './ScratchStorage'; /** * @typedef {object} BuiltinAssetRecord * @property {AssetType} type - The type of the asset. * @property {DataFormat} format - The format of the asset's data. * @property {?string} id - The asset's unique ID. * @property {Buffer} data - The asset's data. */ interface BuiltinAssetRecord { type: AssetType, format: DataFormat, id: AssetId | null, data: AssetData } /** * @type {BuiltinAssetRecord[]} */ const DefaultAssets: BuiltinAssetRecord[] = [ { type: AssetType.ImageBitmap, format: DataFormat.PNG, id: null, data: Buffer.from(defaultImageBitmap) }, { type: AssetType.Sound, format: DataFormat.WAV, id: null, data: Buffer.from(defaultSound) }, { type: AssetType.ImageVector, format: DataFormat.SVG, id: null, data: Buffer.from(defaultImageVector) } ]; /** * @type {BuiltinAssetRecord[]} */ const BuiltinAssets = DefaultAssets.concat([ ]); export default class BuiltinHelper extends Helper { public assets: Record<string, BuiltinAssetRecord>; constructor (parent: ScratchStorage) { super(parent); /** * In-memory storage for all built-in assets. * @type {{[assetType: string]: AssetIdMap}} Maps asset type to a map of asset ID to actual assets. * @typedef {{[id: string]: BuiltinAssetRecord}} AssetIdMap - Maps asset ID to asset. */ this.assets = {}; BuiltinAssets.forEach(assetRecord => { assetRecord.id = this._store(assetRecord.type, assetRecord.format, assetRecord.data, assetRecord.id); }); } /** * Call `setDefaultAssetId` on the parent `ScratchStorage` instance to register all built-in default assets. */ registerDefaultAssets (): void { const numAssets = DefaultAssets.length; for (let assetIndex = 0; assetIndex < numAssets; ++assetIndex) { const assetRecord = DefaultAssets[assetIndex]; this.parent.setDefaultAssetId(assetRecord.type, assetRecord.id!); } } /** * Synchronously fetch a cached asset for a given asset id. Returns null if not found. * @param {string} assetId - The id for the asset to fetch. * @returns {?Asset} The asset for assetId, if it exists. */ get (assetId: AssetId): Asset | null { let asset: Asset | null = null; if (Object.prototype.hasOwnProperty.call(this.assets, assetId)) { /** @type {BuiltinAssetRecord} */ const assetRecord = this.assets[assetId]; asset = new Asset(assetRecord.type, assetRecord.id!, assetRecord.format, assetRecord.data); } return asset; } /** * Alias for store (old name of store) * @deprecated Use BuiltinHelper.store * @param {AssetType} assetType - The type of the asset to cache. * @param {DataFormat} dataFormat - The dataFormat of the data for the cached asset. * @param {Buffer} data - The data for the cached asset. * @param {string} id - The id for the cached asset. * @returns {string} The calculated id of the cached asset, or the supplied id if the asset is mutable. */ cache (assetType: AssetType, dataFormat: DataFormat, data: AssetData, id: AssetId): AssetId { log.warn('Deprecation: BuiltinHelper.cache has been replaced with BuiltinHelper.store.'); return this.store(assetType, dataFormat, data, id); } /** * Deprecated external API for _store * @deprecated Not for external use. Create assets and keep track of them outside of the storage instance. * @param {AssetType} assetType - The type of the asset to cache. * @param {DataFormat} dataFormat - The dataFormat of the data for the cached asset. * @param {Buffer} data - The data for the cached asset. * @param {(string|number)} id - The id for the cached asset. * @returns {string} The calculated id of the cached asset, or the supplied id if the asset is mutable. */ store (assetType: AssetType, dataFormat: DataFormat, data: AssetData, id: AssetId): AssetId { log.warn('Deprecation: use Storage.createAsset. BuiltinHelper is for internal use only.'); return this._store(assetType, dataFormat, data, id); } /** * Cache an asset for future lookups by ID. * @param {AssetType} assetType - The type of the asset to cache. * @param {DataFormat} dataFormat - The dataFormat of the data for the cached asset. * @param {Buffer} data - The data for the cached asset. * @param {(string|number)} id - The id for the cached asset. * @returns {string} The calculated id of the cached asset, or the supplied id if the asset is mutable. */ _store (assetType: AssetType, dataFormat: DataFormat, data: AssetData, id?: AssetId | null): AssetId { let assetId = id; if (!dataFormat) throw new Error('Data cached without specifying its format'); if (assetId !== '' && assetId !== null && typeof assetId !== 'undefined') { if (Object.prototype.hasOwnProperty.call(this.assets, assetId) && assetType.immutable) return assetId; } else if (assetType.immutable) { assetId = md5(data); } else { throw new Error('Tried to cache data without an id'); } this.assets[assetId!] = { type: assetType, format: dataFormat, id: assetId!, data: data }; return assetId!; } /** * Fetch an asset but don't process dependencies. * @param {AssetType} assetType - The type of asset to fetch. * @param {string} assetId - The ID of the asset to fetch: a project ID, MD5, etc. * @returns {?Promise.<Asset>} A promise for the contents of the asset. */ load (assetType: AssetType, assetId: AssetId): Promise<Asset | null> | null { if (!this.get(assetId)) { // Return null immediately so Storage can quickly move to trying the // next helper. return null; } return Promise.resolve(this.get(assetId)); } }