/
BrightSign Asset Pool Example

BrightSign Asset Pool Example

This script shows how the BrightSign Asset Pool can be used from JavaScript. It works from both NodeJS and Chromium. It is not intended as a full application and it doesn’t do anything with the assets that are downloaded.

// MIT License // // Copyright (c) 2020 BrightSign // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in all // copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE // SOFTWARE. // Customise these settings for your environment const storagePath = "/storage/sd/"; const poolPath = storagePath + "examplePool"; const serverPrefix = "https://github.com/brightsign/javascript-assetpoolfetcher/blob/video-training/"; const fs = require('fs'); const AssetPool = require("@brightsign/assetpool"); const AssetPoolFiles = require("@brightsign/assetpoolfiles"); const AssetFetcher = require("@brightsign/assetfetcher"); const AssetRealizer = require("@brightsign/assetrealizer"); // Create the asset collection array to be used by the rest of the // program. The asset collection will usually come either directly or // indirectly from a server somewhere rather than being generated in // code like this. function makeAssetCollection() { // Assets must have a name and a link. Everything else is // optional. You can add your own properties if required. For // example, the "osUpdate" property here is only used by this // script. let videoAsset1 = { name: "brightlink.mp4", hash: { method: "SHA256", hex: "82e5e800ff4b2608ae996b6c9fce78c0d84b82cba0da270251846324f48fb076" }, link: serverPrefix + "brightlink%201min.mp4?raw=true", size: 17219989, }; let videoAsset2 = { name: "CMScontrol.mp4", hash: { method: "SHA512", hex: "d476296b114fda612917b1568a9f7ffe6f54f3f927b2d1ecc932816e912619eeef980e1adc16d8b1d7914d3f172cd6a2ef17172ea3251ddcdbb3d151faa6818f" }, link: serverPrefix + "CMS%2BControl%201%20min.mp4?raw=true", size: 5633877, }; // We don't want to risk actually changing the OS version on the // BrightSign running this example cript, so this is not a real // BrightSignOS file. It will be realized into the root of the // storage device, but the OS will fail to find the expected // header and rename it to placeholder.bsfw_invalid. let osUpdateAsset = { name: "placeholder.bsfw", hash: { method: "SHA1", hex: "20e89c9ba0491590e3c34bc704171c0c02e643c3" }, link: serverPrefix + "placeholder.bsfw?raw=true", size: 43, osUpdate: true, }; let assetCollection = [ videoAsset1, videoAsset2, osUpdateAsset, ]; return assetCollection; } // Convert a progress event to a useful string for reporting function progressString(event) { if (event.currentFileTotal === undefined) { // If the size of the asset was not specified in the asset collection, then the total size may not be reported // during the fetch. return event.currentFileTransferred.toString() + " of unknown"; } else { return event.currentFileTransferred.toString() + " of " + event.currentFileTotal.toString() + " " + (100*event.currentFileTransferred / event.currentFileTotal).toFixed(0) + "%"; } } // Download any assets that aren't already in the pool into the pool // whilst reporting progress. async function fetchAssets(assetPool, assetCollection) { console.log("Fetch: " + JSON.stringify(assetCollection.map(asset => asset.name))); let assetFetcher = new AssetFetcher(assetPool); assetFetcher.addEventListener("fileevent", (event) => { // This is called each time the fetcher has finished trying to // download an asset, whether successful or not. It is not // called for any assets that are already in the pool. console.log("ASSET [" + (event.index + 1).toString() + "] " + event.fileName + " complete: " + event.responseCode.toString() + " " + event.error); }); assetFetcher.addEventListener("progressevent", (event) => { // This is called at approximately the progress interval // specified in the options to indicate how far through the // download console.log("ASSET [" + (event.index + 1).toString() + "/" + event.total.toString() + "] " + event.fileName + " progress: " + progressString(event)); }); const fetchOptions = { // receive asset progress events about every five seconds. progressInterval: 5, // try to download each asset three times before giving up. fileRetryCount: 3, // Give up if we fail to download at least 1024 bytes in each // ten second period. minimumTransferRate: { bytesPerSecond: 1024, periodInSeconds: 10 }, }; try { await assetFetcher.start(assetCollection, fetchOptions); } catch (err) { console.log("FETCH FAILED: " + err.message); throw(err); } } // In order to make use of an asset from the pool you need to look up // its pool filename so you can refer to it there. async function useAssets(assetPool, assetCollection) { let files = new AssetPoolFiles(assetPool, assetCollection); for (const fileName of [ 'brightlink.mp4', 'CMScontrol.mp4' ]) { const path = await files.getPath(fileName); console.log("Asset " + fileName + " is at " + path); } } // Some files need to appear in the filesystem outside the pool. For // example, BrightSignOS update files must be written to the root of // a storage device for them to be found. Realizing will copy files, // so can be slow on large files. async function realizeAssets(assetPool, assetCollection) { let realizer = new AssetRealizer(assetPool, storagePath); // We only want to realize the files that we have to const assetsToRealize = assetCollection.filter(asset => asset.osUpdate); console.log("Realize: " + JSON.stringify(assetsToRealize.map(asset => asset.name))); await realizer.realize(assetsToRealize); } function ensureDirectoryExists(path) { try { fs.mkdirSync(path); } catch (err) { if (err.code != 'EEXIST') throw(err); } } function exceptionToString(err) { if (err instanceof Error) return err.name + ":" + err.message; else if (typeof(err) === "string") return err; else return JSON.stringify(err); } async function runExample() { console.log("Start"); ensureDirectoryExists(poolPath); // Only one AssetPool instance should be created for a given pool // path. Having multiple instances risks them disagreeing over // which assets are protected during pruning. let assetPool = new AssetPool(poolPath); // Don't let the pool grow any larger than 500MiB await assetPool.setMaximumPoolSize(500 * 1024 * 1024); // Don't let free space on the storage device fall below 100MiB await assetPool.reserveStorage(100 * 1024 * 1024); const assetCollection = makeAssetCollection(); // We need to stop the fetcher from pruning any of the assets we // currently care about in order to make space for fetching new // assets or realizing existing ones. Assets are protected until // the AssetPool instance is destroyed or unprotectAssets is // called for the same name. await assetPool.protectAssets("collection1", assetCollection); await fetchAssets(assetPool, assetCollection); // In this case any failure to fetch the assets will cause // fetchAssets to have thrown an exception, so we won't get this // far. However, in a larger script it may be more convenient to // call areAssetsReady to determine whether the asset collection // is ready for use. if (await assetPool.areAssetsReady(assetCollection)) { await useAssets(assetPool, assetCollection); await realizeAssets(assetPool, assetCollection); } else { console.log("Assets were not downloaded successfully"); } } runExample() .then(() => { console.log("Complete"); process.exit(0); }) .catch((err) => { console.log("Failed " + exceptionToString(err)); process.exit(1); });