You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

237 lines
8.5 KiB

import { Platform } from 'expo-modules-core';
import { getAssetByID } from './AssetRegistry';
import { selectAssetSource } from './AssetSources';
import * as AssetUris from './AssetUris';
import * as ImageAssets from './ImageAssets';
import { getLocalAssetUri } from './LocalAssets';
import { downloadAsync, IS_ENV_WITH_UPDATES_ENABLED } from './PlatformUtils';
import resolveAssetSource from './resolveAssetSource';
// @needsAudit
/**
* The `Asset` class represents an asset in your app. It gives metadata about the asset (such as its
* name and type) and provides facilities to load the asset data.
*/
export class Asset {
/**
* @private
*/
static byHash = {};
/**
* @private
*/
static byUri = {};
/**
* The name of the asset file without the extension. Also without the part from `@` onward in the
* filename (used to specify scale factor for images).
*/
name;
/**
* The extension of the asset filename.
*/
type;
/**
* The MD5 hash of the asset's data.
*/
hash = null;
/**
* A URI that points to the asset's data on the remote server. When running the published version
* of your app, this refers to the location on Expo's asset server where Expo has stored your
* asset. When running the app from Expo CLI during development, this URI points to Expo CLI's
* server running on your computer and the asset is served directly from your computer.
*/
uri;
/**
* If the asset has been downloaded (by calling [`downloadAsync()`](#downloadasync)), the
* `file://` URI pointing to the local file on the device that contains the asset data.
*/
localUri = null;
/**
* If the asset is an image, the width of the image data divided by the scale factor. The scale
* factor is the number after `@` in the filename, or `1` if not present.
*/
width = null;
/**
* If the asset is an image, the height of the image data divided by the scale factor. The scale factor is the number after `@` in the filename, or `1` if not present.
*/
height = null;
// @docsMissing
downloading = false;
// @docsMissing
downloaded = false;
/**
* @private
*/
_downloadCallbacks = [];
constructor({ name, type, hash = null, uri, width, height }) {
this.name = name;
this.type = type;
this.hash = hash;
this.uri = uri;
if (typeof width === 'number') {
this.width = width;
}
if (typeof height === 'number') {
this.height = height;
}
if (hash) {
this.localUri = getLocalAssetUri(hash, type);
if (this.localUri) {
this.downloaded = true;
}
}
if (Platform.OS === 'web') {
if (!name) {
this.name = AssetUris.getFilename(uri);
}
if (!type) {
this.type = AssetUris.getFileExtension(uri);
}
}
}
// @needsAudit
/**
* A helper that wraps `Asset.fromModule(module).downloadAsync` for convenience.
* @param moduleId An array of `require('path/to/file')` or external network URLs. Can also be
* just one module or URL without an Array.
* @return Returns a Promise that fulfills with an array of `Asset`s when the asset(s) has been
* saved to disk.
* @example
* ```ts
* const [{ localUri }] = await Asset.loadAsync(require('./assets/snack-icon.png'));
* ```
*/
static loadAsync(moduleId) {
const moduleIds = Array.isArray(moduleId) ? moduleId : [moduleId];
return Promise.all(moduleIds.map((moduleId) => Asset.fromModule(moduleId).downloadAsync()));
}
// @needsAudit
/**
* Returns the [`Asset`](#asset) instance representing an asset given its module or URL.
* @param virtualAssetModule The value of `require('path/to/file')` for the asset or external
* network URL
* @return The [`Asset`](#asset) instance for the asset.
*/
static fromModule(virtualAssetModule) {
if (typeof virtualAssetModule === 'string') {
return Asset.fromURI(virtualAssetModule);
}
const meta = getAssetByID(virtualAssetModule);
if (!meta) {
throw new Error(`Module "${virtualAssetModule}" is missing from the asset registry`);
}
// Outside of the managed env we need the moduleId to initialize the asset
// because resolveAssetSource depends on it
if (!IS_ENV_WITH_UPDATES_ENABLED) {
const { uri } = resolveAssetSource(virtualAssetModule);
const asset = new Asset({
name: meta.name,
type: meta.type,
hash: meta.hash,
uri,
width: meta.width,
height: meta.height,
});
// TODO: FileSystem should probably support 'downloading' from drawable
// resources But for now it doesn't (it only supports raw resources) and
// React Native's Image works fine with drawable resource names for
// images.
if (Platform.OS === 'android' && !uri.includes(':') && (meta.width || meta.height)) {
asset.localUri = asset.uri;
asset.downloaded = true;
}
Asset.byHash[meta.hash] = asset;
return asset;
}
return Asset.fromMetadata(meta);
}
// @docsMissing
static fromMetadata(meta) {
// The hash of the whole asset, not to be confused with the hash of a specific file returned
// from `selectAssetSource`
const metaHash = meta.hash;
if (Asset.byHash[metaHash]) {
return Asset.byHash[metaHash];
}
const { uri, hash } = selectAssetSource(meta);
const asset = new Asset({
name: meta.name,
type: meta.type,
hash,
uri,
width: meta.width,
height: meta.height,
});
Asset.byHash[metaHash] = asset;
return asset;
}
// @docsMissing
static fromURI(uri) {
if (Asset.byUri[uri]) {
return Asset.byUri[uri];
}
// Possibly a Base64-encoded URI
let type = '';
if (uri.indexOf(';base64') > -1) {
type = uri.split(';')[0].split('/')[1];
}
else {
const extension = AssetUris.getFileExtension(uri);
type = extension.startsWith('.') ? extension.substring(1) : extension;
}
const asset = new Asset({
name: '',
type,
hash: null,
uri,
});
Asset.byUri[uri] = asset;
return asset;
}
// @needsAudit
/**
* Downloads the asset data to a local file in the device's cache directory. Once the returned
* promise is fulfilled without error, the [`localUri`](#assetlocaluri) field of this asset points
* to a local file containing the asset data. The asset is only downloaded if an up-to-date local
* file for the asset isn't already present due to an earlier download. The downloaded `Asset`
* will be returned when the promise is resolved.
* @return Returns a Promise which fulfills with an `Asset` instance.
*/
async downloadAsync() {
if (this.downloaded) {
return this;
}
if (this.downloading) {
await new Promise((resolve, reject) => {
this._downloadCallbacks.push({ resolve, reject });
});
return this;
}
this.downloading = true;
try {
if (Platform.OS === 'web') {
if (ImageAssets.isImageType(this.type)) {
const { width, height, name } = await ImageAssets.getImageInfoAsync(this.uri);
this.width = width;
this.height = height;
this.name = name;
}
else {
this.name = AssetUris.getFilename(this.uri);
}
}
this.localUri = await downloadAsync(this.uri, this.hash, this.type, this.name);
this.downloaded = true;
this._downloadCallbacks.forEach(({ resolve }) => resolve());
}
catch (e) {
this._downloadCallbacks.forEach(({ reject }) => reject(e));
throw e;
}
finally {
this.downloading = false;
this._downloadCallbacks = [];
}
return this;
}
}
//# sourceMappingURL=Asset.js.map