From d10df7a638a03a7446bc4069dc3ebb34a7a614e2 Mon Sep 17 00:00:00 2001 From: Gordon Williams Date: Tue, 1 Sep 2020 11:37:38 +0100 Subject: [PATCH] Moving to git submodule for code app loader code --- .gitmodules | 3 + core | 1 + core/img/github-icon-sml.png | Bin 472 -> 0 bytes core/img/github-icon.png | Bin 926 -> 0 bytes core/js/.eslintrc.json | 49 - core/js/appinfo.js | 164 - core/js/comms.js | 291 -- core/js/index.js | 754 ---- core/js/pwa.js | 53 - core/js/service-worker.js | 14 - core/js/ui.js | 144 - core/js/utils.js | 103 - core/lib/.eslintrc.json | 26 - core/lib/customize.js | 25 - core/lib/espruinotools.js | 6809 ---------------------------------- core/lib/heatshrink.js | 100 - core/lib/imageconverter.js | 462 --- core/lib/interface.js | 96 - core/lib/marked.min.js | 6 - core/lib/qrcode.min.js | 1 - index.html | 2 +- 21 files changed, 5 insertions(+), 9098 deletions(-) create mode 100644 .gitmodules create mode 160000 core delete mode 100644 core/img/github-icon-sml.png delete mode 100644 core/img/github-icon.png delete mode 100644 core/js/.eslintrc.json delete mode 100644 core/js/appinfo.js delete mode 100644 core/js/comms.js delete mode 100644 core/js/index.js delete mode 100644 core/js/pwa.js delete mode 100644 core/js/service-worker.js delete mode 100644 core/js/ui.js delete mode 100644 core/js/utils.js delete mode 100644 core/lib/.eslintrc.json delete mode 100644 core/lib/customize.js delete mode 100644 core/lib/espruinotools.js delete mode 100644 core/lib/heatshrink.js delete mode 100644 core/lib/imageconverter.js delete mode 100644 core/lib/interface.js delete mode 100644 core/lib/marked.min.js delete mode 100644 core/lib/qrcode.min.js diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 000000000..7bd7bd699 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "EspruinoAppLoaderCore"] + path = core + url = git@github.com:espruino/EspruinoAppLoaderCore.git diff --git a/core b/core new file mode 160000 index 000000000..20a09f4f2 --- /dev/null +++ b/core @@ -0,0 +1 @@ +Subproject commit 20a09f4f225ad0edae5e9b52b98900ebe8ef97cc diff --git a/core/img/github-icon-sml.png b/core/img/github-icon-sml.png deleted file mode 100644 index 81ca8f22e36d1dc2c0c3969d8cc483394a2578d9..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 472 zcmV;}0Vn>6P) zf{17vXd_XXl(Vvm-4stB4}AF5?>3aHL&-#S832l=odN zj0WI`IB#}5iYOO4%%}(cgk$E5&k^N#W<6l=80YW~JGhAnzBj;Oe8G(-Zv{^x%El4x7EBwNYmM)u!hQO;ym;|3N{R`HH O0000zDul11m{H zK~z}7#n)YkRaF!R@ZX$9$Y|_?n8v3jiXH+DQxOjK(9jnW=b#q!-t_el1PX;cR0LzB zN%f*c(Q6N*b+!aTVi0H&Bep2Io}_sI7z?7}NWNN4*Z zkZ0vJ*osGcA=)4?r2m!ylKS3;tQ7TbMK~zVR{;7_Cz#I9;P3$+L2FP{`5ONR8=q zg2N5pJutxNn|vKk8rmdm$g^^I0MQR)LxXuH!QKYrQfwJO^jo5@I@vpvXXOZv;i`s3 z(|EcF>9I?Ud@R$Jj6cN7xT{h^AK)X*VzvnBxtgtBi92u~9>jt! z9Cs8UeX3oCn=v1MZ^R1Ri_fBN0UpL%@fJJZ&^@2UcC5V!_5~a(B_DF!9+BL1C{reijdC51e(P2xF4T(aeSx;@B`H;sdFeD z!+zWnRnHV5ed7`m?^^uUWyyC~T}|k^vHYf3nzu&Pegyo2F}z-cbiNM)-{PJkq(?ei zapzec!7A*Ds?~TE@5Vg2vX5S{2dg^4YY9|_h&6aDM&7R+$KzPjG~5h#xx-s{JI~7Z z@I-vB$Js7`XYnge#FE`ng!D|;Kc}r$%l|2V0}nF%L!VxtWB>pF07*qoM6N<$f)y98 AFaQ7m diff --git a/core/js/.eslintrc.json b/core/js/.eslintrc.json deleted file mode 100644 index cb816d7b5..000000000 --- a/core/js/.eslintrc.json +++ /dev/null @@ -1,49 +0,0 @@ -{ - "parserOptions": { - "ecmaVersion": 6, - "sourceType": "script" - }, - "rules": { - "indent": [ - "warn", - 2, - { - "SwitchCase": 1 - } - ], - "no-undef": "warn", - "no-redeclare": "warn", - "no-var": "warn", - "no-unused-vars":"off" // we define stuff to use in other scripts - }, - "env": { - "browser": true, - "node": true - }, - "extends": "eslint:recommended", - "globals": { - "btoa": "writable", - "Espruino": "writable", - - "htmlElement": "readonly", - "Puck": "readonly", - "escapeHtml": "readonly", - "htmlToArray": "readonly", - "heatshrink": "readonly", - "Puck": "readonly", - "Promise": "readonly", - "Comms": "readonly", - "Progress": "readonly", - "showToast": "readonly", - "showPrompt": "readonly", - "httpGet": "readonly", - "getVersionInfo": "readonly", - "AppInfo": "readonly", - "marked": "readonly", - "appSorter": "readonly", - "Uint8Array" : "readonly", - "SETTINGS" : "readonly", - "globToRegex" : "readonly", - "toJS" : "readonly" - } -} diff --git a/core/js/appinfo.js b/core/js/appinfo.js deleted file mode 100644 index 54f403f91..000000000 --- a/core/js/appinfo.js +++ /dev/null @@ -1,164 +0,0 @@ -if (typeof btoa==="undefined") { - // Don't define btoa as a function here because Apple's - // iOS browser defines the function even though it's in - // an IF statement that is never executed - btoa = function(d) { return Buffer.from(d,'binary').toString('base64'); } -} - -// Converts a string into most efficient way to send to Espruino (either json, base64, or compressed base64) -function toJS(txt) { - let isBinary = false; - for (let i=0;i127) isBinary=true; - } - let json = JSON.stringify(txt); - let b64 = "atob("+JSON.stringify(btoa(txt))+")"; - let js = (isBinary || (b64.length < json.length)) ? b64 : json; - - if (typeof heatshrink !== "undefined") { - let ua = new Uint8Array(txt.length); - for (let i=0;i { - return new Promise((resolve,reject) => { - // Load all files - Promise.all(app.storage.map(storageFile => { - if (storageFile.content!==undefined) - return Promise.resolve(storageFile); - else if (storageFile.url) - return options.fileGetter(`apps/${app.id}/${storageFile.url}`).then(content => { - if (storageFile.url.endsWith(".js") && !storageFile.url.endsWith(".min.js")) { // if original file ends in '.js'... - return Espruino.transform(content, { - SET_TIME_ON_WRITE : false, - PRETOKENISE : options.settings.pretokenise, - //MINIFICATION_LEVEL : "ESPRIMA", // disable due to https://github.com/espruino/BangleApps/pull/355#issuecomment-620124162 - builtinModules : "Flash,Storage,heatshrink,tensorflow,locale,notify" - }); - } else - return content; - }).then(content => { - return { - name : storageFile.name, - content : content, - evaluate : storageFile.evaluate - }}); - else return Promise.resolve(); - })).then(fileContents => { // now we just have a list of files + contents... - // filter out empty files - fileContents = fileContents.filter(x=>x!==undefined); - // What about minification? - // Add app's info JSON - return AppInfo.createAppJSON(app, fileContents); - }).then(fileContents => { - // then map each file to a command to load into storage - fileContents.forEach(storageFile => { - // format ready for Espruino - if (storageFile.evaluate) { - let js = storageFile.content.trim(); - if (js.endsWith(";")) - js = js.slice(0,-1); - storageFile.cmd = `\x10require('Storage').write(${JSON.stringify(storageFile.name)},${js});`; - } else { - let code = storageFile.content; - // write code in chunks, in case it is too big to fit in RAM (fix #157) - let CHUNKSIZE = 4096; - storageFile.cmd = `\x10require('Storage').write(${JSON.stringify(storageFile.name)},${toJS(code.substr(0,CHUNKSIZE))},0,${code.length});`; - for (let i=CHUNKSIZE;i reject(err)); - }); - }, - createAppJSON : (app, fileContents) => { - return new Promise((resolve,reject) => { - let appJSONName = app.id+".info"; - // Check we don't already have a JSON file! - let appJSONFile = fileContents.find(f=>f.name==appJSONName); - if (appJSONFile) reject("App JSON file explicitly specified!"); - // Now actually create the app JSON - let json = { - id : app.id - }; - if (app.shortName) json.name = app.shortName; - else json.name = app.name; - if (app.type && app.type!="app") json.type = app.type; - if (fileContents.find(f=>f.name==app.id+".app.js")) - json.src = app.id+".app.js"; - if (fileContents.find(f=>f.name==app.id+".img")) - json.icon = app.id+".img"; - if (app.sortorder) json.sortorder = app.sortorder; - if (app.version) json.version = app.version; - let fileList = fileContents.map(storageFile=>storageFile.name); - fileList.unshift(appJSONName); // do we want this? makes life easier! - json.files = fileList.join(","); - if ('data' in app) { - let data = {dataFiles: [], storageFiles: []}; - // add "data" files to appropriate list - app.data.forEach(d=>{ - if (d.storageFile) data.storageFiles.push(d.name||d.wildcard) - else data.dataFiles.push(d.name||d.wildcard) - }) - const dataString = AppInfo.makeDataString(data) - if (dataString) json.data = dataString - } - fileContents.push({ - name : appJSONName, - content : JSON.stringify(json) - }); - resolve(fileContents); - }); - }, - // (.info).data holds filenames of data: both regular and storageFiles - // These are stored as: (note comma vs semicolons) - // "fil1,file2", "file1,file2;storageFileA,storageFileB" or ";storageFileA" - /** - * Convert appid.info "data" to object with file names/patterns - * Passing in undefined works - * @param data "data" as stored in appid.info - * @returns {{storageFiles:[], dataFiles:[]}} - */ - parseDataString(data) { - data = data || ''; - let [files = [], storage = []] = data.split(';').map(d => d.split(',')) - return {dataFiles: files, storageFiles: storage} - }, - /** - * Convert object with file names/patterns to appid.info "data" string - * Passing in an incomplete object will not work - * @param data {{storageFiles:[], dataFiles:[]}} - * @returns {string} "data" to store in appid.info - */ - makeDataString(data) { - if (!data.dataFiles.length && !data.storageFiles.length) { return '' } - if (!data.storageFiles.length) { return data.dataFiles.join(',') } - return [data.dataFiles.join(','),data.storageFiles.join(',')].join(';') - }, -}; - -if ("undefined"!=typeof module) - module.exports = AppInfo; diff --git a/core/js/comms.js b/core/js/comms.js deleted file mode 100644 index d828c9bef..000000000 --- a/core/js/comms.js +++ /dev/null @@ -1,291 +0,0 @@ -//Puck.debug=3; -console.log("=============================================") -console.log("Type 'Puck.debug=3' for full BLE debug info") -console.log("=============================================") - -// FIXME: use UART lib so that we handle errors properly -const Comms = { - reset : (opt) => new Promise((resolve,reject) => { - let tries = 8; - console.log(" reset"); - Puck.write(`\x03\x10reset(${opt=="wipe"?"1":""});\n`,function rstHandler(result) { - console.log(" reset: got "+JSON.stringify(result)); - if (result===null) return reject("Connection failed"); - if (result=="" && (tries-- > 0)) { - console.log(` reset: no response. waiting ${tries}...`); - Puck.write("\x03",rstHandler); - } else { - console.log(` reset: complete.`); - setTimeout(resolve,250); - } - }); - }), - uploadApp : (app,skipReset) => { // expects an apps.json structure (i.e. with `storage`) - Progress.show({title:`Uploading ${app.name}`,sticky:true}); - return AppInfo.getFiles(app, { - fileGetter : httpGet, - settings : SETTINGS - }).then(fileContents => { - return new Promise((resolve,reject) => { - console.log(" uploadApp:",fileContents.map(f=>f.name).join(", ")); - let maxBytes = fileContents.reduce((b,f)=>b+f.cmd.length, 0)||1; - let currentBytes = 0; - - let appInfoFileName = app.id+".info"; - let appInfoFile = fileContents.find(f=>f.name==appInfoFileName); - if (!appInfoFile) reject(`${appInfoFileName} not found`); - let appInfo = JSON.parse(appInfoFile.content); - - // Upload each file one at a time - function doUploadFiles() { - // No files left - print 'reboot' message - if (fileContents.length==0) { - Puck.write(`\x10E.showMessage('Hold BTN3\\nto reload')\n`,(result) => { - Progress.hide({sticky:true}); - if (result===null) return reject(""); - resolve(appInfo); - }); - return; - } - let f = fileContents.shift(); - console.log(` Upload ${f.name} => ${JSON.stringify(f.content)}`); - // Chould check CRC here if needed instead of returning 'OK'... - // E.CRC32(require("Storage").read(${JSON.stringify(app.name)})) - let cmds = f.cmd.split("\n"); - function uploadCmd() { - if (!cmds.length) return doUploadFiles(); - let cmd = cmds.shift(); - Progress.show({ - min:currentBytes / maxBytes, - max:(currentBytes+cmd.length) / maxBytes}); - currentBytes += cmd.length; - Puck.write(`${cmd};Bluetooth.println("OK")\n`,(result) => { - if (!result || result.trim()!="OK") { - Progress.hide({sticky:true}); - return reject("Unexpected response "+(result||"")); - } - uploadCmd(); - }, true); // wait for a newline - } - uploadCmd(); - } - // Start the upload - function doUpload() { - Puck.write(`\x10E.showMessage('Uploading\\n${app.id}...')\n`,(result) => { - if (result===null) { - Progress.hide({sticky:true}); - return reject(""); - } - doUploadFiles(); - }); - } - if (skipReset) { - doUpload(); - } else { - // reset to ensure we have enough memory to upload what we need to - Comms.reset().then(doUpload, reject) - } - }); - }); - }, - getInstalledApps : () => { - Progress.show({title:`Getting app list...`,sticky:true}); - return new Promise((resolve,reject) => { - Puck.write("\x03",(result) => { - if (result===null) { - Progress.hide({sticky:true}); - return reject(""); - } - Puck.write('\x10Bluetooth.print("[");require("Storage").list(/\\.info$/).forEach(f=>{var j=require("Storage").readJSON(f,1)||{};j.id=f.slice(0,-5);Bluetooth.print(JSON.stringify(j)+",")});Bluetooth.println("0]")\n', (appList,err) => { - Progress.hide({sticky:true}); - try { - appList = JSON.parse(appList); - // remove last element since we added a final '0' - // to make things easy on the Bangle.js side - appList = appList.slice(0,-1); - } catch (e) { - appList = null; - err = e.toString(); - } - if (appList===null) return reject(err || ""); - console.log(" getInstalledApps", appList); - resolve(appList); - }, true /* callback on newline */); - }); - }); - }, - removeApp : app => { // expects an appid.info structure (i.e. with `files`) - if (!app.files && !app.data) return Promise.resolve(); // nothing to erase - Progress.show({title:`Removing ${app.name}`,sticky:true}); - let cmds = '\x10const s=require("Storage");\n'; - // remove App files: regular files, exact names only - cmds += app.files.split(',').map(file => `\x10s.erase(${toJS(file)});\n`).join(""); - // remove app Data: (dataFiles and storageFiles) - const data = AppInfo.parseDataString(app.data) - const isGlob = f => /[?*]/.test(f) - // regular files, can use wildcards - cmds += data.dataFiles.map(file => { - if (!isGlob(file)) return `\x10s.erase(${toJS(file)});\n`; - const regex = new RegExp(globToRegex(file)) - return `\x10s.list(${regex}).forEach(f=>s.erase(f));\n`; - }).join(""); - // storageFiles, can use wildcards - cmds += data.storageFiles.map(file => { - if (!isGlob(file)) return `\x10s.open(${toJS(file)},'r').erase();\n`; - // storageFiles have a chunk number appended to their real name - const regex = globToRegex(file+'\u0001') - // open() doesn't want the chunk number though - let cmd = `\x10s.list(${regex}).forEach(f=>s.open(f.substring(0,f.length-1),'r').erase());\n` - // using a literal \u0001 char fails (not sure why), so escape it - return cmd.replace('\u0001', '\\x01') - }).join(""); - console.log(" removeApp", cmds); - return Comms.reset().then(() => new Promise((resolve,reject) => { - Puck.write(`\x03\x10E.showMessage('Erasing\\n${app.id}...')${cmds}\x10E.showMessage('Hold BTN3\\nto reload')\n`,(result) => { - Progress.hide({sticky:true}); - if (result===null) return reject(""); - resolve(); - }); - })).catch(function(reason) { - Progress.hide({sticky:true}); - return Promise.reject(reason); - }); - }, - removeAllApps : () => { - console.log(" removeAllApps start"); - Progress.show({title:"Removing all apps",percent:"animate",sticky:true}); - return new Promise((resolve,reject) => { - let timeout = 5; - function handleResult(result,err) { - console.log(" removeAllApps: received "+JSON.stringify(result)); - if (result=="" && (timeout--)) { - console.log(" removeAllApps: no result - waiting some more ("+timeout+")."); - // send space and delete - so it's something, but it should just cancel out - Puck.write(" \u0008", handleResult, true /* wait for newline */); - } else { - Progress.hide({sticky:true}); - if (!result || result.trim()!="OK") { - if (!result) result = "No response"; - else result = "Got "+JSON.stringify(result.trim()); - return reject(err || result); - } else resolve(); - } - } - // Use write with newline here so we wait for it to finish - let cmd = '\x10E.showMessage("Erasing...");require("Storage").eraseAll();Bluetooth.println("OK");reset()\n'; - Puck.write(cmd, handleResult, true /* wait for newline */); - }); - }, - setTime : () => { - return new Promise((resolve,reject) => { - let d = new Date(); - let tz = d.getTimezoneOffset()/-60 - let cmd = '\x03\x10setTime('+(d.getTime()/1000)+');'; - // in 1v93 we have timezones too - cmd += 'E.setTimeZone('+tz+');'; - cmd += "(s=>{s&&(s.timezone="+tz+")&&require('Storage').write('setting.json',s);})(require('Storage').readJSON('setting.json',1))\n"; - Puck.write(cmd, (result) => { - if (result===null) return reject(""); - resolve(); - }); - }); - }, - disconnectDevice: () => { - let connection = Puck.getConnection(); - - if (!connection) return; - - connection.close(); - }, - watchConnectionChange : cb => { - let connected = Puck.isConnected(); - - //TODO Switch to an event listener when Puck will support it - let interval = setInterval(() => { - if (connected === Puck.isConnected()) return; - - connected = Puck.isConnected(); - cb(connected); - }, 1000); - - //stop watching - return () => { - clearInterval(interval); - }; - }, - listFiles : () => { - return new Promise((resolve,reject) => { - Puck.write("\x03",(result) => { - if (result===null) return reject(""); - //use encodeURIComponent to serialize octal sequence of append files - Puck.eval('require("Storage").list().map(encodeURIComponent)', (files,err) => { - if (files===null) return reject(err || ""); - files = files.map(decodeURIComponent); - console.log(" listFiles", files); - resolve(files); - }); - }); - }); - }, - readFile : (file) => { - return new Promise((resolve,reject) => { - //encode name to avoid serialization issue due to octal sequence - const name = encodeURIComponent(file); - Puck.write("\x03",(result) => { - if (result===null) return reject(""); - //TODO: big files will not fit in RAM. - //we should loop and read chunks one by one. - //Use btoa for binary content - Puck.eval(`btoa(require("Storage").read(decodeURIComponent("${name}"))))`, (content,err) => { - if (content===null) return reject(err || ""); - resolve(atob(content)); - }); - }); - }); - }, - readStorageFile : (filename) => { // StorageFiles are different to normal storage entries - return new Promise((resolve,reject) => { - // Use "\xFF" to signal end of file (can't occur in files anyway) - let fileContent = ""; - let fileSize = undefined; - let connection = Puck.getConnection(); - connection.received = ""; - connection.cb = function(d) { - let finished = false; - let eofIndex = d.indexOf("\xFF"); - if (eofIndex>=0) { - finished = true; - d = d.substr(0,eofIndex); - } - fileContent += d; - if (fileSize === undefined) { - let newLineIdx = fileContent.indexOf("\n"); - if (newLineIdx>=0) { - fileSize = parseInt(fileContent.substr(0,newLineIdx)); - console.log(" readStorageFile size is "+fileSize); - fileContent = fileContent.substr(newLineIdx+1); - } - } else { - Progress.show({percent:100*fileContent.length / (fileSize||1000000)}); - } - if (finished) { - Progress.hide(); - connection.received = ""; - connection.cb = undefined; - resolve(fileContent); - } - }; - console.log(` readStorageFile ${JSON.stringify(filename)}`); - connection.write(`\x03\x10(function() { - var f = require("Storage").open(${JSON.stringify(filename)},"r"); - Bluetooth.println(f.getLength()); - var l = f.readLine(); - while (l!==undefined) { Bluetooth.print(l); l = f.readLine(); } - Bluetooth.print("\xFF"); - })()\n`,() => { - Progress.show({title:`Reading ${JSON.stringify(filename)}`,percent:0}); - console.log(` StorageFile read started...`); - }); - }); - } -}; diff --git a/core/js/index.js b/core/js/index.js deleted file mode 100644 index 1e86518ae..000000000 --- a/core/js/index.js +++ /dev/null @@ -1,754 +0,0 @@ -let appJSON = []; // List of apps and info from apps.json -let appsInstalled = []; // list of app JSON -let appSortInfo = {}; // list of data to sort by, from appdates.csv { created, modified } -let files = []; // list of files on the Espruimo Device -let DEFAULTSETTINGS = { - pretokenise : true, - favourites : ["boot","launch","setting"] -}; -let SETTINGS = JSON.parse(JSON.stringify(DEFAULTSETTINGS)); // clone - -httpGet("apps.json").then(apps=>{ - try { - appJSON = JSON.parse(apps); - } catch(e) { - console.log(e); - showToast("App List Corrupted","error"); - } - refreshLibrary(); - refreshFilter(); -}); - -httpGet("appdates.csv").then(csv=>{ - document.querySelector(".sort-nav").classList.remove("hidden"); - csv.split("\n").forEach(line=>{ - let l = line.split(","); - appSortInfo[l[0]] = { - created : Date.parse(l[1]), - modified : Date.parse(l[2]) - }; - }); -}).catch(err=>{ - console.log("No recent.csv - app sort disabled"); -}); - -// =========================================== Top Navigation -function showChangeLog(appid) { - let app = appNameToApp(appid); - function show(contents) { - showPrompt(app.name+" Change Log",contents,{ok:true}).catch(()=>{}); - } - httpGet(`apps/${appid}/ChangeLog`). - then(show).catch(()=>show("No Change Log available")); -} -function showReadme(appid) { - let app = appNameToApp(appid); - let appPath = `apps/${appid}/`; - let markedOptions = { baseUrl : appPath }; - function show(contents) { - if (!contents) return; - showPrompt(app.name + " Documentation", marked(contents, markedOptions), {ok: true}, false).catch(() => {}); - } - httpGet(appPath+app.readme).then(show).catch(()=>show("Failed to load README.")); -} -function getAppDescription(app) { - let appPath = `apps/${app.id}/`; - let markedOptions = { baseUrl : appPath }; - return marked(app.description, markedOptions); -} -function handleCustomApp(appTemplate) { - // Pops up an IFRAME that allows an app to be customised - if (!appTemplate.custom) throw new Error("App doesn't have custom HTML"); - return new Promise((resolve,reject) => { - let modal = htmlElement(`