From da891dda7adb2db2f06e52a536226417536ba352 Mon Sep 17 00:00:00 2001 From: David Leoni Date: Wed, 12 Aug 2020 20:33:09 +0200 Subject: [PATCH] sprite storage #9: id from md5, custom name --- src/extensions/botch/botch-storage-helper.js | 19 ++++- src/extensions/botch/index.js | 81 ++++++++++++++++---- 2 files changed, 82 insertions(+), 18 deletions(-) diff --git a/src/extensions/botch/botch-storage-helper.js b/src/extensions/botch/botch-storage-helper.js index 4616a2200bc..07e737e39a9 100644 --- a/src/extensions/botch/botch-storage-helper.js +++ b/src/extensions/botch/botch-storage-helper.js @@ -197,7 +197,7 @@ class BotchStorageHelper extends Helper { this.assets = {}; BotchBuiltinAssets.forEach(assetRecord => { - assetRecord.id = this._store(assetRecord.type, assetRecord.format, assetRecord.data, assetRecord.id); + assetRecord.id = this._store(assetRecord.type, assetRecord.format, assetRecord.data, assetRecord.id, assetRecord.name); }); } @@ -254,12 +254,22 @@ class BotchStorageHelper extends Helper { * @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. + * @param {string} name - The name for the cached asset (Botch: we added it) * @returns {string} The calculated id of the cached asset, or the supplied id if the asset is mutable. */ - _store (assetType, dataFormat, data, id) { + _store (assetType, dataFormat, data, id, name) { + if (!name){ + throw new Error(`Missing name:${name}`); + } + if (!name.trim()){ + throw new Error('Provided name is all blank !'); + } if (!dataFormat) throw new Error('Data cached without specifying its format'); if (id !== '' && id !== null && typeof id !== 'undefined') { - if (this.assets.hasOwnProperty(id) && assetType.immutable) return id; + if (this.assets.hasOwnProperty(id) && assetType.immutable) { + console.log('Item already stored !'); + return id; + } } else if (assetType.immutable) { id = md5(data); } else { @@ -269,7 +279,8 @@ class BotchStorageHelper extends Helper { type: assetType, format: dataFormat, id: id, - data: data + data: data, + name: name }; return id; } diff --git a/src/extensions/botch/index.js b/src/extensions/botch/index.js index e5c1012b68c..f6042de42e7 100644 --- a/src/extensions/botch/index.js +++ b/src/extensions/botch/index.js @@ -18,6 +18,7 @@ const svgen = require('../../util/svg-generator'); const {loadCostume} = require('../../import/load-costume.js'); const BotchStorageHelper = require('./botch-storage-helper.js'); +const md5 = require('js-md5'); /* * Create the new costume asset for the VM @@ -538,6 +539,8 @@ class Scratch3Botch { /** Copied from virtual-machine.js + * @param {object} fileDescs serialized jsons + * @param {JSZip} zip zip to add stuff to. * @since botch-0.1 */ _addFileDescsToZip (fileDescs, zip) { @@ -550,31 +553,55 @@ class Scratch3Botch { * * Exports a sprite in the sprite3 format. * @param {string} targetId ID of the target to export - * @param {string=} optZipType Optional type that the resulting - * zip should be outputted in. Options are: base64, binarystring, + * @param {string=} optZipType Optional type that the resulting zip should be outputted in. Options are: base64, binarystring, * array, uint8array, arraybuffer, blob, or nodebuffer. Defaults to * blob if argument not provided. + * @param {string=} newName Optional new name + * * See https://stuk.github.io/jszip/documentation/api_jszip/generate_async.html#type-option * for more information about these options. - * @return {object} A generated zip of the sprite and its assets in the format - * specified by optZipType or blob by default. + * @return {Promise} generated zip of the sprite and its assets, plus + * a monkey patched md5 field. + * NOTE: the md5 is *not* the md5 of the zipped data, because md5 of zips + * is not stable: https://github.com/Stuk/jszip/issues/590 * @since botch-0.1 */ - exportSprite (targetId, optZipType) { + exportSprite (targetId, optZipType, newName = '') { + if (!targetId){ + throw new Error(`Got empty id:${targetId}!`); + } + if (newName){ + if (!newName.trim()){ + throw new Error('Got an all blank name for sprite !'); + } + } const JSZip = require('jszip'); const sb3 = require('../../serialization/sb3'); const {serializeSounds, serializeCostumes} = require('../../serialization/serialize-assets'); const StringUtil = require('../../util/string-util'); const soundDescs = serializeSounds(this.runtime, targetId); + console.log('md5(soundDescs)', md5(soundDescs)); const costumeDescs = serializeCostumes(this.runtime, targetId); - const spriteJson = StringUtil.stringify(sb3.serialize(this.runtime, targetId)); + console.log('md5(costumeDescs)', md5(costumeDescs)); + const serialized = sb3.serialize(this.runtime, targetId); + + if (newName){ + serialized.name = newName; + } + const spriteJson = StringUtil.stringify(serialized); + + console.log('md5(spriteJson)', md5(spriteJson)); + + // Botch: would have been nicer to calculate md5 of the zip + // but md5 varies between zips: https://github.com/Stuk/jszip/issues/590 + const theMd5 = md5(spriteJson + StringUtil.stringify(soundDescs.concat(costumeDescs))); const zip = new JSZip(); zip.file('sprite.json', spriteJson); this._addFileDescsToZip(soundDescs.concat(costumeDescs), zip); - return zip.generateAsync({ + const p = zip.generateAsync({ type: typeof optZipType === 'string' ? optZipType : 'blob', mimeType: 'application/x.scratch.sprite3', compression: 'DEFLATE', @@ -582,30 +609,56 @@ class Scratch3Botch { level: 6 } }); + p.md5 = theMd5; // monkey patching + return p; } - /** Stores a sprite into custom storageHelper + /** Stores a sprite from runtime into custom storageHelper. * + * The sprite is stored with a new id calculated from md5 of the whole sprite + * (json + entire costumes + entire sounds) and provided newName + * If the sprite is a descendent of an existing one, consider reassigning the name. + * + * Does *not* change the current runtime. + * + * @param {string} id the sprite to store + * @param {string} newName new name for the sprite, if unspecified uses existing one. + * @returns {Promise} A promise with an extra md5 field. NOTE: the md5 is *not* the md5 of the zipped data, because zips md5 is not stable: https://github.com/Stuk/jszip/issues/590 * @since botch-0.1 */ - storeSprite (id) { - console.log('Botch: trying to store sprite with id', id); + storeSprite (id, newName = '') { + console.log('Botch: trying to store sprite with original id', id); + + if (!id){ + throw new Error(`Got empty id:${id}!`); + } + if (newName){ + if (!newName.trim()){ + throw new Error('Got all blank name for sprite !'); + } + } + + const p = this.exportSprite(id, 'uint8array', newName); + const newId = p.md5; - const p = this.exportSprite(id, 'uint8array'); return p.then(data => { + + console.log('Botch: using newId from md5:', newId); this.storageHelper._store( this.storage.AssetType.Sprite, this.storage.DataFormat.SB3, data, - id + newId, + newName ? newName : this.runtime.getTargetById(id).sprite.name ); - console.log('Botch: I should emit ', Scratch3Botch.BOTCH_STORAGE_HELPER_UPDATE); + console.log('Botch: emitting ', Scratch3Botch.BOTCH_STORAGE_HELPER_UPDATE); this.runtime.emit(Scratch3Botch.BOTCH_STORAGE_HELPER_UPDATE); - console.log('Botch: stored sprite with id', id); + console.log('Botch: stored sprite with newId', newId); }); } + /** * Quick and dirty test, stores first sprite in the custom storageHelper