use eslint to fix indentation of app loader js
parent
765be3ef40
commit
2fd2caaacc
|
|
@ -0,0 +1 @@
|
||||||
|
espruinotools.js
|
||||||
|
|
@ -4,7 +4,13 @@
|
||||||
"sourceType": "script"
|
"sourceType": "script"
|
||||||
},
|
},
|
||||||
"rules": {
|
"rules": {
|
||||||
|
"indent": [
|
||||||
|
"warn",
|
||||||
|
2,
|
||||||
|
{
|
||||||
|
"SwitchCase": 1
|
||||||
|
}
|
||||||
|
]
|
||||||
},
|
},
|
||||||
"env": {
|
"env": {
|
||||||
"browser": true,
|
"browser": true,
|
||||||
|
|
|
||||||
|
|
@ -59,7 +59,7 @@ var AppInfo = {
|
||||||
name : storageFile.name,
|
name : storageFile.name,
|
||||||
content : content,
|
content : content,
|
||||||
evaluate : storageFile.evaluate
|
evaluate : storageFile.evaluate
|
||||||
}});
|
}});
|
||||||
else return Promise.resolve();
|
else return Promise.resolve();
|
||||||
})).then(fileContents => { // now we just have a list of files + contents...
|
})).then(fileContents => { // now we just have a list of files + contents...
|
||||||
// filter out empty files
|
// filter out empty files
|
||||||
|
|
@ -82,7 +82,7 @@ var AppInfo = {
|
||||||
var CHUNKSIZE = 4096;
|
var CHUNKSIZE = 4096;
|
||||||
storageFile.cmd = `\x10require('Storage').write(${JSON.stringify(storageFile.name)},${toJS(code.substr(0,CHUNKSIZE))},0,${code.length});`;
|
storageFile.cmd = `\x10require('Storage').write(${JSON.stringify(storageFile.name)},${toJS(code.substr(0,CHUNKSIZE))},0,${code.length});`;
|
||||||
for (var i=CHUNKSIZE;i<code.length;i+=CHUNKSIZE)
|
for (var i=CHUNKSIZE;i<code.length;i+=CHUNKSIZE)
|
||||||
storageFile.cmd += `\n\x10require('Storage').write(${JSON.stringify(storageFile.name)},${toJS(code.substr(i,CHUNKSIZE))},${i});`;
|
storageFile.cmd += `\n\x10require('Storage').write(${JSON.stringify(storageFile.name)},${toJS(code.substr(i,CHUNKSIZE))},${i});`;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
resolve(fileContents);
|
resolve(fileContents);
|
||||||
|
|
|
||||||
474
js/comms.js
474
js/comms.js
|
|
@ -2,264 +2,264 @@ Puck.debug=3;
|
||||||
|
|
||||||
// FIXME: use UART lib so that we handle errors properly
|
// FIXME: use UART lib so that we handle errors properly
|
||||||
var Comms = {
|
var Comms = {
|
||||||
reset : (opt) => new Promise((resolve,reject) => {
|
reset : (opt) => new Promise((resolve,reject) => {
|
||||||
Puck.write(`\x03\x10reset(${opt=="wipe"?"1":""});\n`, (result) => {
|
Puck.write(`\x03\x10reset(${opt=="wipe"?"1":""});\n`, (result) => {
|
||||||
if (result===null) return reject("Connection failed");
|
if (result===null) return reject("Connection failed");
|
||||||
setTimeout(resolve,500);
|
setTimeout(resolve,500);
|
||||||
});
|
});
|
||||||
}),
|
}),
|
||||||
uploadApp : (app,skipReset) => { // expects an apps.json structure (i.e. with `storage`)
|
uploadApp : (app,skipReset) => { // expects an apps.json structure (i.e. with `storage`)
|
||||||
Progress.show({title:`Uploading ${app.name}`,sticky:true});
|
Progress.show({title:`Uploading ${app.name}`,sticky:true});
|
||||||
return AppInfo.getFiles(app, {
|
return AppInfo.getFiles(app, {
|
||||||
fileGetter : httpGet,
|
fileGetter : httpGet,
|
||||||
settings : SETTINGS
|
settings : SETTINGS
|
||||||
}).then(fileContents => {
|
}).then(fileContents => {
|
||||||
return new Promise((resolve,reject) => {
|
return new Promise((resolve,reject) => {
|
||||||
console.log("uploadApp",fileContents.map(f=>f.name).join(", "));
|
console.log("uploadApp",fileContents.map(f=>f.name).join(", "));
|
||||||
var maxBytes = fileContents.reduce((b,f)=>b+f.cmd.length, 0)||1;
|
var maxBytes = fileContents.reduce((b,f)=>b+f.cmd.length, 0)||1;
|
||||||
var currentBytes = 0;
|
var currentBytes = 0;
|
||||||
|
|
||||||
var appInfoFileName = app.id+".info";
|
var appInfoFileName = app.id+".info";
|
||||||
var appInfoFile = fileContents.find(f=>f.name==appInfoFileName);
|
var appInfoFile = fileContents.find(f=>f.name==appInfoFileName);
|
||||||
if (!appInfoFile) reject(`${appInfoFileName} not found`);
|
if (!appInfoFile) reject(`${appInfoFileName} not found`);
|
||||||
var appInfo = JSON.parse(appInfoFile.content);
|
var appInfo = JSON.parse(appInfoFile.content);
|
||||||
|
|
||||||
// Upload each file one at a time
|
// Upload each file one at a time
|
||||||
function doUploadFiles() {
|
function doUploadFiles() {
|
||||||
// No files left - print 'reboot' message
|
// No files left - print 'reboot' message
|
||||||
if (fileContents.length==0) {
|
if (fileContents.length==0) {
|
||||||
Puck.write(`\x10E.showMessage('Hold BTN3\\nto reload')\n`,(result) => {
|
Puck.write(`\x10E.showMessage('Hold BTN3\\nto reload')\n`,(result) => {
|
||||||
Progress.hide({sticky:true});
|
|
||||||
if (result===null) return reject("");
|
|
||||||
resolve(appInfo);
|
|
||||||
});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
var 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)}))
|
|
||||||
var cmds = f.cmd.split("\n");
|
|
||||||
function uploadCmd() {
|
|
||||||
if (!cmds.length) return doUploadFiles();
|
|
||||||
var 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});
|
Progress.hide({sticky:true});
|
||||||
return reject("Unexpected response "+(result||""));
|
if (result===null) return reject("");
|
||||||
}
|
resolve(appInfo);
|
||||||
uploadCmd();
|
});
|
||||||
}, true); // wait for a newline
|
return;
|
||||||
}
|
|
||||||
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();
|
var f = fileContents.shift();
|
||||||
});
|
console.log(`Upload ${f.name} => ${JSON.stringify(f.content)}`);
|
||||||
}
|
// Chould check CRC here if needed instead of returning 'OK'...
|
||||||
if (skipReset) {
|
// E.CRC32(require("Storage").read(${JSON.stringify(app.name)}))
|
||||||
doUpload();
|
var cmds = f.cmd.split("\n");
|
||||||
} else {
|
function uploadCmd() {
|
||||||
|
if (!cmds.length) return doUploadFiles();
|
||||||
|
var 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
|
// reset to ensure we have enough memory to upload what we need to
|
||||||
Comms.reset().then(doUpload, reject)
|
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 */);
|
|
||||||
});
|
});
|
||||||
});
|
},
|
||||||
},
|
getInstalledApps : () => {
|
||||||
removeApp : app => { // expects an appid.info structure (i.e. with `files`)
|
Progress.show({title:`Getting app list...`,sticky:true});
|
||||||
if (!app.files && !app.data) return Promise.resolve(); // nothing to erase
|
return new Promise((resolve,reject) => {
|
||||||
Progress.show({title:`Removing ${app.name}`,sticky:true});
|
Puck.write("\x03",(result) => {
|
||||||
let cmds = '\x10const s=require("Storage");\n';
|
if (result===null) {
|
||||||
// remove App files: regular files, exact names only
|
Progress.hide({sticky:true});
|
||||||
cmds += app.files.split(',').map(file => `\x10s.erase(${toJS(file)});\n`).join("");
|
return reject("");
|
||||||
// remove app Data: (dataFiles and storageFiles)
|
}
|
||||||
const data = AppInfo.parseDataString(app.data)
|
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) => {
|
||||||
const isGlob = f => /[?*]/.test(f)
|
Progress.hide({sticky:true});
|
||||||
// regular files, can use wildcards
|
try {
|
||||||
cmds += data.dataFiles.map(file => {
|
appList = JSON.parse(appList);
|
||||||
if (!isGlob(file)) return `\x10s.erase(${toJS(file)});\n`;
|
// remove last element since we added a final '0'
|
||||||
const regex = new RegExp(globToRegex(file))
|
// to make things easy on the Bangle.js side
|
||||||
return `\x10s.list(${regex}).forEach(f=>s.erase(f));\n`;
|
appList = appList.slice(0,-1);
|
||||||
}).join("");
|
} catch (e) {
|
||||||
// storageFiles, can use wildcards
|
appList = null;
|
||||||
cmds += data.storageFiles.map(file => {
|
err = e.toString();
|
||||||
if (!isGlob(file)) return `\x10s.open(${toJS(file)},'r').erase();\n`;
|
}
|
||||||
// storageFiles have a chunk number appended to their real name
|
if (appList===null) return reject(err || "");
|
||||||
const regex = globToRegex(file+'\u0001')
|
console.log("getInstalledApps", appList);
|
||||||
// open() doesn't want the chunk number though
|
resolve(appList);
|
||||||
let cmd = `\x10s.list(${regex}).forEach(f=>s.open(f.substring(0,f.length-1),'r').erase());\n`
|
}, true /* callback on newline */);
|
||||||
// using a literal \u0001 char fails (not sure why), so escape it
|
});
|
||||||
return cmd.replace('\u0001', '\\x01')
|
});
|
||||||
}).join("");
|
},
|
||||||
console.log("removeApp", cmds);
|
removeApp : app => { // expects an appid.info structure (i.e. with `files`)
|
||||||
return Comms.reset().then(new Promise((resolve,reject) => {
|
if (!app.files && !app.data) return Promise.resolve(); // nothing to erase
|
||||||
Puck.write(`\x03\x10E.showMessage('Erasing\\n${app.id}...')${cmds}\x10E.showMessage('Hold BTN3\\nto reload')\n`,(result) => {
|
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});
|
Progress.hide({sticky:true});
|
||||||
if (result===null) return reject("");
|
return Promise.reject(reason);
|
||||||
resolve();
|
|
||||||
});
|
});
|
||||||
})).catch(function(reason) {
|
},
|
||||||
Progress.hide({sticky:true});
|
removeAllApps : () => {
|
||||||
return Promise.reject(reason);
|
Progress.show({title:"Removing all apps",progess:"animate",sticky:true});
|
||||||
});
|
return new Promise((resolve,reject) => {
|
||||||
},
|
|
||||||
removeAllApps : () => {
|
|
||||||
Progress.show({title:"Removing all apps",progess:"animate",sticky:true});
|
|
||||||
return new Promise((resolve,reject) => {
|
|
||||||
// Use write with newline here so we wait for it to finish
|
// Use write with newline here so we wait for it to finish
|
||||||
Puck.write('\x10E.showMessage("Erasing...");require("Storage").eraseAll();Bluetooth.println("OK");reset()\n', (result,err) => {
|
Puck.write('\x10E.showMessage("Erasing...");require("Storage").eraseAll();Bluetooth.println("OK");reset()\n', (result,err) => {
|
||||||
Progress.hide({sticky:true});
|
Progress.hide({sticky:true});
|
||||||
if (!result || result.trim()!="OK") return reject(err || "");
|
if (!result || result.trim()!="OK") return reject(err || "");
|
||||||
resolve();
|
resolve();
|
||||||
}, true /* wait for newline */);
|
}, true /* wait for newline */);
|
||||||
});
|
|
||||||
},
|
|
||||||
setTime : () => {
|
|
||||||
return new Promise((resolve,reject) => {
|
|
||||||
var d = new Date();
|
|
||||||
var tz = d.getTimezoneOffset()/-60
|
|
||||||
var 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();
|
|
||||||
});
|
});
|
||||||
});
|
},
|
||||||
},
|
setTime : () => {
|
||||||
disconnectDevice: () => {
|
return new Promise((resolve,reject) => {
|
||||||
var connection = Puck.getConnection();
|
var d = new Date();
|
||||||
|
var tz = d.getTimezoneOffset()/-60
|
||||||
if (!connection) return;
|
var cmd = '\x03\x10setTime('+(d.getTime()/1000)+');';
|
||||||
|
// in 1v93 we have timezones too
|
||||||
connection.close();
|
cmd += 'E.setTimeZone('+tz+');';
|
||||||
},
|
cmd += "(s=>{s&&(s.timezone="+tz+")&&require('Storage').write('setting.json',s);})(require('Storage').readJSON('setting.json',1))\n";
|
||||||
watchConnectionChange : cb => {
|
Puck.write(cmd, (result) => {
|
||||||
var connected = Puck.isConnected();
|
if (result===null) return reject("");
|
||||||
|
resolve();
|
||||||
//TODO Switch to an event listener when Puck will support it
|
|
||||||
var 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);
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
},
|
||||||
},
|
disconnectDevice: () => {
|
||||||
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)
|
|
||||||
var fileContent = "";
|
|
||||||
var fileSize = undefined;
|
|
||||||
var connection = Puck.getConnection();
|
var connection = Puck.getConnection();
|
||||||
connection.received = "";
|
|
||||||
connection.cb = function(d) {
|
if (!connection) return;
|
||||||
var finished = false;
|
|
||||||
var eofIndex = d.indexOf("\xFF");
|
connection.close();
|
||||||
if (eofIndex>=0) {
|
},
|
||||||
finished = true;
|
watchConnectionChange : cb => {
|
||||||
d = d.substr(0,eofIndex);
|
var connected = Puck.isConnected();
|
||||||
}
|
|
||||||
fileContent += d;
|
//TODO Switch to an event listener when Puck will support it
|
||||||
if (fileSize === undefined) {
|
var interval = setInterval(() => {
|
||||||
var newLineIdx = fileContent.indexOf("\n");
|
if (connected === Puck.isConnected()) return;
|
||||||
if (newLineIdx>=0) {
|
|
||||||
fileSize = parseInt(fileContent.substr(0,newLineIdx));
|
connected = Puck.isConnected();
|
||||||
console.log("File size is "+fileSize);
|
cb(connected);
|
||||||
fileContent = fileContent.substr(newLineIdx+1);
|
}, 1000);
|
||||||
}
|
|
||||||
} else {
|
//stop watching
|
||||||
Progress.show({percent:100*fileContent.length / (fileSize||1000000)});
|
return () => {
|
||||||
}
|
clearInterval(interval);
|
||||||
if (finished) {
|
|
||||||
Progress.hide();
|
|
||||||
connection.received = "";
|
|
||||||
connection.cb = undefined;
|
|
||||||
resolve(fileContent);
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
console.log(`Reading StorageFile ${JSON.stringify(filename)}`);
|
},
|
||||||
connection.write(`\x03\x10(function() {
|
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)
|
||||||
|
var fileContent = "";
|
||||||
|
var fileSize = undefined;
|
||||||
|
var connection = Puck.getConnection();
|
||||||
|
connection.received = "";
|
||||||
|
connection.cb = function(d) {
|
||||||
|
var finished = false;
|
||||||
|
var eofIndex = d.indexOf("\xFF");
|
||||||
|
if (eofIndex>=0) {
|
||||||
|
finished = true;
|
||||||
|
d = d.substr(0,eofIndex);
|
||||||
|
}
|
||||||
|
fileContent += d;
|
||||||
|
if (fileSize === undefined) {
|
||||||
|
var newLineIdx = fileContent.indexOf("\n");
|
||||||
|
if (newLineIdx>=0) {
|
||||||
|
fileSize = parseInt(fileContent.substr(0,newLineIdx));
|
||||||
|
console.log("File 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(`Reading StorageFile ${JSON.stringify(filename)}`);
|
||||||
|
connection.write(`\x03\x10(function() {
|
||||||
var f = require("Storage").open(${JSON.stringify(filename)},"r");
|
var f = require("Storage").open(${JSON.stringify(filename)},"r");
|
||||||
Bluetooth.println(f.getLength());
|
Bluetooth.println(f.getLength());
|
||||||
var l = f.readLine();
|
var l = f.readLine();
|
||||||
while (l!==undefined) { Bluetooth.print(l); l = f.readLine(); }
|
while (l!==undefined) { Bluetooth.print(l); l = f.readLine(); }
|
||||||
Bluetooth.print("\xFF");
|
Bluetooth.print("\xFF");
|
||||||
})()\n`,() => {
|
})()\n`,() => {
|
||||||
Progress.show({title:`Reading ${JSON.stringify(filename)}`,percent:0});
|
Progress.show({title:`Reading ${JSON.stringify(filename)}`,percent:0});
|
||||||
console.log(`StorageFile read started...`);
|
console.log(`StorageFile read started...`);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
}
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
|
||||||
20
js/index.js
20
js/index.js
|
|
@ -40,7 +40,7 @@ function showChangeLog(appid) {
|
||||||
showPrompt(app.name+" Change Log",contents,{ok:true}).catch(()=>{});
|
showPrompt(app.name+" Change Log",contents,{ok:true}).catch(()=>{});
|
||||||
}
|
}
|
||||||
httpGet(`apps/${appid}/ChangeLog`).
|
httpGet(`apps/${appid}/ChangeLog`).
|
||||||
then(show).catch(()=>show("No Change Log available"));
|
then(show).catch(()=>show("No Change Log available"));
|
||||||
}
|
}
|
||||||
function showReadme(appid) {
|
function showReadme(appid) {
|
||||||
var app = appNameToApp(appid);
|
var app = appNameToApp(appid);
|
||||||
|
|
@ -319,9 +319,9 @@ function refreshLibrary() {
|
||||||
} else if (icon.classList.contains("icon-download")) {
|
} else if (icon.classList.contains("icon-download")) {
|
||||||
handleAppInterface(app);
|
handleAppInterface(app);
|
||||||
} else if ( button.innerText == String.fromCharCode(0x2661)) {
|
} else if ( button.innerText == String.fromCharCode(0x2661)) {
|
||||||
changeAppFavourite(true, app);
|
changeAppFavourite(true, app);
|
||||||
} else if ( button.innerText == String.fromCharCode(0x2665) ) {
|
} else if ( button.innerText == String.fromCharCode(0x2665) ) {
|
||||||
changeAppFavourite(false, app);
|
changeAppFavourite(false, app);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
@ -458,13 +458,13 @@ function getAppsToUpdate() {
|
||||||
function refreshMyApps() {
|
function refreshMyApps() {
|
||||||
var panelbody = document.querySelector("#myappscontainer .panel-body");
|
var panelbody = document.querySelector("#myappscontainer .panel-body");
|
||||||
panelbody.innerHTML = appsInstalled.map(appInstalled => {
|
panelbody.innerHTML = appsInstalled.map(appInstalled => {
|
||||||
var app = appNameToApp(appInstalled.id);
|
var app = appNameToApp(appInstalled.id);
|
||||||
var version = getVersionInfo(app, appInstalled);
|
var version = getVersionInfo(app, appInstalled);
|
||||||
var username = "espruino";
|
var username = "espruino";
|
||||||
var githubMatch = window.location.href.match(/\/(\w+)\.github\.io/);
|
var githubMatch = window.location.href.match(/\/(\w+)\.github\.io/);
|
||||||
if(githubMatch) username = githubMatch[1];
|
if(githubMatch) username = githubMatch[1];
|
||||||
var url = `https://github.com/${username}/BangleApps/tree/master/apps/${app.id}`;
|
var url = `https://github.com/${username}/BangleApps/tree/master/apps/${app.id}`;
|
||||||
return `<div class="tile column col-6 col-sm-12 col-xs-12">
|
return `<div class="tile column col-6 col-sm-12 col-xs-12">
|
||||||
<div class="tile-icon">
|
<div class="tile-icon">
|
||||||
<figure class="avatar"><img src="apps/${app.icon?`${app.id}/${app.icon}`:"unknown.png"}" alt="${escapeHtml(app.name)}"></figure>
|
<figure class="avatar"><img src="apps/${app.icon?`${app.id}/${app.icon}`:"unknown.png"}" alt="${escapeHtml(app.name)}"></figure>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
56
js/pwa.js
56
js/pwa.js
|
|
@ -2,41 +2,41 @@ const divInstall = document.getElementById('installContainer');
|
||||||
const butInstall = document.getElementById('butInstall');
|
const butInstall = document.getElementById('butInstall');
|
||||||
|
|
||||||
window.addEventListener('beforeinstallprompt', (event) => {
|
window.addEventListener('beforeinstallprompt', (event) => {
|
||||||
console.log('👍', 'beforeinstallprompt', event);
|
console.log('👍', 'beforeinstallprompt', event);
|
||||||
// Stash the event so it can be triggered later.
|
// Stash the event so it can be triggered later.
|
||||||
window.deferredPrompt = event;
|
window.deferredPrompt = event;
|
||||||
// Remove the 'hidden' class from the install button container
|
// Remove the 'hidden' class from the install button container
|
||||||
divInstall.classList.toggle('hidden', false);
|
divInstall.classList.toggle('hidden', false);
|
||||||
});
|
});
|
||||||
|
|
||||||
butInstall.addEventListener('click', () => {
|
butInstall.addEventListener('click', () => {
|
||||||
console.log('👍', 'butInstall-clicked');
|
console.log('👍', 'butInstall-clicked');
|
||||||
const promptEvent = window.deferredPrompt;
|
const promptEvent = window.deferredPrompt;
|
||||||
if (!promptEvent) {
|
if (!promptEvent) {
|
||||||
// The deferred prompt isn't available.
|
// The deferred prompt isn't available.
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// Show the install prompt.
|
// Show the install prompt.
|
||||||
promptEvent.prompt();
|
promptEvent.prompt();
|
||||||
// Log the result
|
// Log the result
|
||||||
promptEvent.userChoice.then((result) => {
|
promptEvent.userChoice.then((result) => {
|
||||||
console.log('👍', 'userChoice', result);
|
console.log('👍', 'userChoice', result);
|
||||||
// Reset the deferred prompt variable, since
|
// Reset the deferred prompt variable, since
|
||||||
// prompt() can only be called once.
|
// prompt() can only be called once.
|
||||||
window.deferredPrompt = null;
|
window.deferredPrompt = null;
|
||||||
// Hide the install button.
|
// Hide the install button.
|
||||||
divInstall.classList.toggle('hidden', true);
|
divInstall.classList.toggle('hidden', true);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
window.addEventListener('appinstalled', (event) => {
|
window.addEventListener('appinstalled', (event) => {
|
||||||
console.log('👍', 'appinstalled', event);
|
console.log('👍', 'appinstalled', event);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
/* Only register a service worker if it's supported */
|
/* Only register a service worker if it's supported */
|
||||||
if ('serviceWorker' in navigator) {
|
if ('serviceWorker' in navigator) {
|
||||||
navigator.serviceWorker.register('js/service-worker.js');
|
navigator.serviceWorker.register('js/service-worker.js');
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -46,8 +46,8 @@ if ('serviceWorker' in navigator) {
|
||||||
* if the page isn't served over HTTPS, the service worker won't load.
|
* if the page isn't served over HTTPS, the service worker won't load.
|
||||||
*/
|
*/
|
||||||
if (window.location.protocol === 'http:' && window.location.hostname!="localhost") {
|
if (window.location.protocol === 'http:' && window.location.hostname!="localhost") {
|
||||||
const requireHTTPS = document.getElementById('requireHTTPS');
|
const requireHTTPS = document.getElementById('requireHTTPS');
|
||||||
const link = requireHTTPS.querySelector('a');
|
const link = requireHTTPS.querySelector('a');
|
||||||
link.href = window.location.href.replace('http://', 'https://');
|
link.href = window.location.href.replace('http://', 'https://');
|
||||||
requireHTTPS.classList.remove('hidden');
|
requireHTTPS.classList.remove('hidden');
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,14 +1,14 @@
|
||||||
self.addEventListener('install', (event) => {
|
self.addEventListener('install', (event) => {
|
||||||
console.log('👷', 'install', event);
|
console.log('👷', 'install', event);
|
||||||
self.skipWaiting();
|
self.skipWaiting();
|
||||||
});
|
});
|
||||||
|
|
||||||
self.addEventListener('activate', (event) => {
|
self.addEventListener('activate', (event) => {
|
||||||
console.log('👷', 'activate', event);
|
console.log('👷', 'activate', event);
|
||||||
return self.clients.claim();
|
return self.clients.claim();
|
||||||
});
|
});
|
||||||
|
|
||||||
self.addEventListener('fetch', function(event) {
|
self.addEventListener('fetch', function(event) {
|
||||||
// console.log('👷', 'fetch', event);
|
// console.log('👷', 'fetch', event);
|
||||||
event.respondWith(fetch(event.request));
|
event.respondWith(fetch(event.request));
|
||||||
});
|
});
|
||||||
Loading…
Reference in New Issue