scratch-storage
Version:
Load and store project and asset files for Scratch 3.0
177 lines (157 loc) • 6.68 kB
text/typescript
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));
}
}