Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ var getAssetUrl = function (asset) {
'https://assets.example.com/path/to/assets/',
asset.assetId,
'.',
asset.assetType.runtimeFormat,
asset.dataFormat,
'/get/'
];
return assetUrlParts.join('');
Expand All @@ -71,7 +71,7 @@ If you're using ES6 you may be able to simplify all of the above quite a bit:
```js
storage.addWebSource(
[AssetType.ImageVector, AssetType.ImageBitmap, AssetType.Sound],
asset => `https://assets.example.com/path/to/assets/${asset.assetId}.${asset.assetType.runtimeFormat}/get/`);
asset => `https://assets.example.com/path/to/assets/${asset.assetId}.${asset.dataFormat}/get/`);
```

Once the storage module is aware of the sources you need, you can start loading assets:
Expand Down
2 changes: 1 addition & 1 deletion src/Asset.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ class Asset {
/** @type {string} */
this.assetId = assetId;

this.setData(data, dataFormat);
this.setData(data, dataFormat || assetType.runtimeFormat);

/** @type {Asset[]} */
this.dependencies = [];
Expand Down
4 changes: 2 additions & 2 deletions src/AssetType.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@ const DataFormat = require('./DataFormat');
* @typedef {Object} AssetType - Information about a supported asset type.
* @property {string} contentType - the MIME type associated with this kind of data. Useful for data URIs, etc.
* @property {string} name - The human-readable name of this asset type.
* @property {DataFormat} runtimeFormat - The format used for runtime, in-memory storage of this asset. For example, a
* project stored in SB2 format on disk will be returned as JSON when loaded into memory.
* @property {DataFormat} runtimeFormat - The default format used for runtime, in-memory storage of this asset. For
* example, a project stored in SB2 format on disk will be returned as JSON when loaded into memory.
* @property {boolean} immutable - Indicates if the asset id is determined by the asset content.
*/
const AssetType = {
Expand Down
3 changes: 2 additions & 1 deletion src/DataFormat.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
/**
* Enumeration of the supported data formats.
* @type {Object.<string,string>}
* @enum {string}
*/
const DataFormat = {
JPG: 'jpg',
JSON: 'json',
PNG: 'png',
SB2: 'sb2',
Expand Down
5 changes: 3 additions & 2 deletions src/Helper.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,11 @@ class Helper {
* 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.
* @param {DataFormat} dataFormat - The file format / file extension of the asset to fetch: PNG, JPG, etc.
* @return {Promise.<Asset>} A promise for the contents of the asset.
*/
load (assetType, assetId) {
return Promise.reject(new Error(`No asset of type ${assetType} for ID ${assetId}`));
load (assetType, assetId, dataFormat) {
return Promise.reject(new Error(`No asset of type ${assetType} for ID ${assetId} with format ${dataFormat}`));
}
}

Expand Down
7 changes: 4 additions & 3 deletions src/LocalHelper.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,17 +21,18 @@ class LocalHelper extends Helper {
* 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.
* @param {DataFormat} dataFormat - The file format / file extension of the asset to fetch: PNG, JPG, etc.
* @return {Promise.<Asset>} A promise for the contents of the asset.
*/
load (assetType, assetId) {
load (assetType, assetId, dataFormat) {
return new Promise((fulfill, reject) => {
const fileName = [assetId, assetType.runtimeFormat].join('.');
const fileName = [assetId, dataFormat].join('.');
localforage.getItem(fileName).then(
data => {
if (data === null) {
fulfill(null);
} else {
fulfill(new Asset(assetType, assetId, assetType.runtimeFormat, data));
fulfill(new Asset(assetType, assetId, dataFormat, data));
}
},
error => {
Expand Down
28 changes: 20 additions & 8 deletions src/ScratchStorage.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
const Asset = require('./Asset');
const AssetType = require('./AssetType');
const BuiltinHelper = require('./BuiltinHelper');
const LocalHelper = require('./LocalHelper');
const WebHelper = require('./WebHelper');

const _Asset = require('./Asset');
const _AssetType = require('./AssetType');
const _DataFormat = require('./DataFormat');

class ScratchStorage {
constructor () {
this.defaultAssetId = {};
Expand All @@ -20,15 +22,23 @@ class ScratchStorage {
* @constructor
*/
get Asset () {
return Asset;
return _Asset;
}

/**
* @return {AssetType} - the list of supported asset types.
* @constructor
*/
get AssetType () {
return AssetType;
return _AssetType;
}

/**
* @return {DataFormat} - the list of supported data formats.
* @constructor
*/
get DataFormat () {
return _DataFormat;
}

/**
Expand All @@ -37,7 +47,7 @@ class ScratchStorage {
* @constructor
*/
static get Asset () {
return Asset;
return _Asset;
}

/**
Expand All @@ -46,7 +56,7 @@ class ScratchStorage {
* @constructor
*/
static get AssetType () {
return AssetType;
return _AssetType;
}

/**
Expand Down Expand Up @@ -94,23 +104,25 @@ class ScratchStorage {
* Fetch an asset by type & ID.
* @param {AssetType} assetType - The type of asset to fetch. This also determines which asset store to use.
* @param {string} assetId - The ID of the asset to fetch: a project ID, MD5, etc.
* @param {DataFormat} [dataFormat] - Optional: load this format instead of the AssetType's default.
* @return {Promise.<Asset>} A promise for the requested Asset.
* If the promise is fulfilled with non-null, the value is the requested asset or a fallback.
* If the promise is fulfilled with null, the desired asset could not be found with the current asset sources.
* If the promise is rejected, there was an error on at least one asset source. HTTP 404 does not count as an
* error here, but (for example) HTTP 403 does.
*/
load (assetType, assetId) {
load (assetType, assetId, dataFormat) {
/** @type {Helper[]} */
const helpers = [this.builtinHelper, this.localHelper, this.webHelper];
const errors = [];
let helperIndex = 0;
dataFormat = dataFormat || assetType.runtimeFormat;

return new Promise((fulfill, reject) => {
const tryNextHelper = () => {
if (helperIndex < helpers.length) {
const helper = helpers[helperIndex++];
helper.load(assetType, assetId)
helper.load(assetType, assetId, dataFormat)
.then(
asset => {
if (asset === null) {
Expand Down
7 changes: 4 additions & 3 deletions src/WebHelper.js
Original file line number Diff line number Diff line change
Expand Up @@ -38,14 +38,15 @@ class WebHelper extends Helper {
* 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.
* @param {DataFormat} dataFormat - The file format / file extension of the asset to fetch: PNG, JPG, etc.
* @return {Promise.<Asset>} A promise for the contents of the asset.
*/
load (assetType, assetId) {
load (assetType, assetId, dataFormat) {

/** @type {Array.<{url:string, result:*}>} List of URLs attempted & errors encountered. */
const errors = [];
const sources = this.sources.slice();
const asset = new Asset(assetType, assetId);
const asset = new Asset(assetType, assetId, dataFormat);
let sourceIndex = 0;

return new Promise((fulfill, reject) => {
Expand Down Expand Up @@ -77,7 +78,7 @@ class WebHelper extends Helper {
}
tryNextSource();
} else {
asset.setData(response.body, assetType.runtimeFormat);
asset.setData(response.body, dataFormat);
fulfill(asset);
}
},
Expand Down
26 changes: 22 additions & 4 deletions test/integration/download-known-assets.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ test('constructor', t => {
* @typedef {object} AssetTestInfo
* @property {AssetType} type - The type of the asset.
* @property {string} id - The asset's unique ID.
* @property {DataFormat} [ext] - Optional: the asset's data format / file extension.
*/
const testAssets = [
{
Expand All @@ -33,11 +34,29 @@ const testAssets = [
id: 'f88bf1935daea28f8ca098462a31dbb0', // cat1-a
md5: 'f88bf1935daea28f8ca098462a31dbb0'
},
{
type: storage.AssetType.ImageVector,
id: '6e8bd9ae68fdb02b7e1e3df656a75635', // cat1-b
md5: '6e8bd9ae68fdb02b7e1e3df656a75635',
ext: storage.DataFormat.SVG
},
{
type: storage.AssetType.ImageBitmap,
id: '7e24c99c1b853e52f8e7f9004416fa34', // squirrel
md5: '7e24c99c1b853e52f8e7f9004416fa34'
},
{
type: storage.AssetType.ImageBitmap,
id: '66895930177178ea01d9e610917f8acf', // bus
md5: '66895930177178ea01d9e610917f8acf',
ext: storage.DataFormat.PNG
},
{
type: storage.AssetType.ImageBitmap,
id: 'fe5e3566965f9de793beeffce377d054', // building at MIT
md5: 'fe5e3566965f9de793beeffce377d054',
ext: storage.DataFormat.JPG
},
{
type: storage.AssetType.Sound,
id: '83c36d806dc92327b9e7049a565c6bff', // meow
Expand All @@ -59,7 +78,7 @@ test('addWebSource', t => {
t.doesNotThrow(() => {
storage.addWebSource(
[storage.AssetType.ImageVector, storage.AssetType.ImageBitmap, storage.AssetType.Sound],
asset => `https://cdn.assets.scratch.mit.edu/internalapi/asset/${asset.assetId}.${asset.assetType.runtimeFormat}/get/`
asset => `https://cdn.assets.scratch.mit.edu/internalapi/asset/${asset.assetId}.${asset.dataFormat}/get/`
);
});
t.end();
Expand All @@ -82,12 +101,11 @@ test('load', t => {
for (var i = 0; i < testAssets.length; ++i) {
const assetInfo = testAssets[i];

const promise = storage.load(assetInfo.type, assetInfo.id);
var promise = storage.load(assetInfo.type, assetInfo.id, assetInfo.ext);

This comment was marked as abuse.

This comment was marked as abuse.

This comment was marked as abuse.

t.type(promise, 'Promise');

promise = promise.then(asset => checkAsset(assetInfo, asset));
promises.push(promise);

promise.then(asset => checkAsset(assetInfo, asset));
}

return Promise.all(promises);
Expand Down