-
Notifications
You must be signed in to change notification settings - Fork 140
/
Copy pathScratchStorage.ts
267 lines (236 loc) · 10.6 KB
/
ScratchStorage.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
import log from './log';
import BuiltinHelper from './BuiltinHelper';
import WebHelper, {UrlFunction} from './WebHelper';
import _Asset, {AssetData, AssetId} from './Asset';
import {AssetType as _AssetType, AssetType} from './AssetType';
import {DataFormat as _DataFormat, DataFormat} from './DataFormat';
import _scratchFetch from './scratchFetch';
import Helper from './Helper';
interface HelperWithPriority {
helper: Helper,
priority: number
}
export class ScratchStorage {
public defaultAssetId: Record<AssetType['name'], AssetId>;
public builtinHelper: BuiltinHelper;
public webHelper: WebHelper;
private _helpers: HelperWithPriority[];
constructor () {
this.defaultAssetId = {};
this.builtinHelper = new BuiltinHelper(this);
this.webHelper = new WebHelper(this);
this.builtinHelper.registerDefaultAssets();
this._helpers = [
{
helper: this.builtinHelper,
priority: 100
},
{
helper: this.webHelper,
priority: -100
}
];
}
/**
* @return {Asset} - the `Asset` class constructor.
* @constructor
*/
get Asset () {
return _Asset;
}
/**
* @return {AssetType} - the list of supported asset types.
* @constructor
*/
get AssetType () {
return _AssetType;
}
/**
* @return {DataFormat} - the list of supported data formats.
* @constructor
*/
get DataFormat () {
return _DataFormat;
}
/**
* Access the `scratchFetch` module within this library.
* @return {module} the scratchFetch module, with properties for `scratchFetch`, `setMetadata`, etc.
*/
get scratchFetch () {
return _scratchFetch;
}
/**
* @deprecated Please use the `Asset` member of a storage instance instead.
* @return {Asset} - the `Asset` class constructor.
* @constructor
*/
static get Asset () {
return _Asset;
}
/**
* @deprecated Please use the `AssetType` member of a storage instance instead.
* @return {AssetType} - the list of supported asset types.
* @constructor
*/
static get AssetType () {
return _AssetType;
}
/**
* Add a storage helper to this manager. Helpers with a higher priority number will be checked first when loading
* or storing assets. For comparison, the helper for built-in assets has `priority=100` and the default web helper
* has `priority=-100`. The relative order of helpers with equal priorities is undefined.
* @param {Helper} helper - the helper to be added.
* @param {number} [priority] - the priority for this new helper (default: 0).
*/
addHelper (helper: Helper, priority: number = 0) {
this._helpers.push({helper, priority});
this._helpers.sort((a, b) => b.priority - a.priority);
}
/**
* Synchronously fetch a cached asset from built-in storage. Assets are cached when they are loaded.
* @param {string} assetId - The id of the asset to fetch.
* @returns {?Asset} The asset, if it exists.
*/
get (assetId: string): _Asset | null {
return this.builtinHelper.get(assetId);
}
/**
* Deprecated API for caching built-in assets. Use createAsset.
* @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: Storage.cache is deprecated. Use Storage.createAsset, and store assets externally.');
return this.builtinHelper._store(assetType, dataFormat, data, id);
}
/**
* Construct an Asset, and optionally generate an md5 hash of its data to create an 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} [id] - The id for the cached asset.
* @param {bool} [generateId] - flag to set id to an md5 hash of data if `id` isn't supplied
* @returns {Asset} generated Asset with `id` attribute set if not supplied
*/
createAsset (
assetType: AssetType,
dataFormat: DataFormat,
data: AssetData,
id: AssetId,
generateId: boolean
): _Asset {
if (!dataFormat) throw new Error('Tried to create asset without a dataFormat');
return new _Asset(assetType, id, dataFormat, data, generateId);
}
/**
* Register a web-based source for assets. Sources will be checked in order of registration.
* @param {Array.<AssetType>} types - The types of asset provided by this source.
* @param {UrlFunction} getFunction - A function which computes a GET URL from an Asset.
* @param {UrlFunction} createFunction - A function which computes a POST URL for asset data.
* @param {UrlFunction} updateFunction - A function which computes a PUT URL for asset data.
*/
addWebStore (
types: AssetType[],
getFunction: UrlFunction,
createFunction?: UrlFunction,
updateFunction?: UrlFunction
): void {
this.webHelper.addStore(types, getFunction, createFunction, updateFunction);
}
/**
* Register a web-based source for assets. Sources will be checked in order of registration.
* @deprecated Please use addWebStore
* @param {Array.<AssetType>} types - The types of asset provided by this source.
* @param {UrlFunction} urlFunction - A function which computes a GET URL from an Asset.
*/
addWebSource (types: AssetType[], urlFunction: UrlFunction): void {
log.warn('Deprecation: Storage.addWebSource has been replaced by addWebStore.');
this.addWebStore(types, urlFunction);
}
/**
* TODO: Should this be removed in favor of requesting an asset with `null` as the ID?
* @param {AssetType} type - Get the default ID for assets of this type.
* @return {?string} The ID of the default asset of the given type, if any.
*/
getDefaultAssetId (type: AssetType): AssetId | undefined {
if (Object.prototype.hasOwnProperty.call(this.defaultAssetId, type.name)) {
return this.defaultAssetId[type.name];
}
}
/**
* Set the default ID for a particular type of asset. This default asset will be used if a requested asset cannot
* be found and automatic fallback is enabled. Ideally this should be an asset that is available locally or even
* one built into this module.
* TODO: Should this be removed in favor of requesting an asset with `null` as the ID?
* @param {AssetType} type - The type of asset for which the default will be set.
* @param {string} id - The default ID to use for this type of asset.
*/
setDefaultAssetId (type: AssetType, id: AssetId): void {
this.defaultAssetId[type.name] = id;
}
/**
* 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 resolved with non-null, the value is the requested asset.
* If the promise is resolved 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: AssetType, assetId: AssetId, dataFormat: DataFormat): Promise<_Asset | null> {
const helpers = this._helpers.map(x => x.helper);
const errors: unknown[] = [];
dataFormat = dataFormat || assetType.runtimeFormat;
let helperIndex = 0;
let helper: Helper;
const tryNextHelper = (err?: unknown): Promise<_Asset | null> => {
if (err) { // Track the error, but continue looking
errors.push(err);
}
helper = helpers[helperIndex++];
if (helper) {
const loading = helper.load(assetType, assetId, dataFormat);
if (loading === null) {
return tryNextHelper();
}
// Note that other attempts may have logged errors; if this succeeds they will be suppressed.
return loading
// TODO: maybe some types of error should prevent trying the next helper?
.catch(tryNextHelper);
} else if (errors.length > 0) {
// We looked through all the helpers and couldn't find the asset, AND
// at least one thing went wrong while we were looking.
return Promise.reject(errors);
}
// Nothing went wrong but we couldn't find the asset.
return Promise.resolve(null);
};
return tryNextHelper();
}
/**
* Store an asset by type & ID.
* @param {AssetType} assetType - The type of asset to fetch. This also determines which asset store to use.
* @param {?DataFormat} [dataFormat] - Optional: load this format instead of the AssetType's default.
* @param {Buffer} data - Data to store for the asset
* @param {?string} [assetId] - The ID of the asset to fetch: a project ID, MD5, etc.
* @return {Promise.<object>} A promise for asset metadata
*/
store (assetType: AssetType, dataFormat: DataFormat | null | undefined, data: AssetData, assetId?: AssetId) {
dataFormat = dataFormat || assetType.runtimeFormat;
return this.webHelper.store(assetType, dataFormat, data, assetId)
.then(body => {
// The previous logic here ignored that the body can be a string (if it's not a JSON),
// so just ignore that case.
// Also, having undefined was the previous behavior
// eslint-disable-next-line no-undefined
const id = typeof body === 'string' ? undefined : body.id;
this.builtinHelper._store(assetType, dataFormat, data, id);
return body;
});
}
}