1
0
Fork 0

Data files: save all data files as a single string

Separate regular and storage files by a semicolon
master
Richard de Boer 2020-04-15 21:30:44 +02:00
parent 217d198154
commit 9e0fd91339
5 changed files with 52 additions and 28 deletions

View File

@ -202,13 +202,11 @@ and which gives information about the app for the Launcher.
"files:"file1,file2,file3", "files:"file1,file2,file3",
// added by BangleApps loader on upload - lists all files // added by BangleApps loader on upload - lists all files
// that belong to the app so it can be deleted // that belong to the app so it can be deleted
"dataFiles":"appid.data.json,appid.data?.json" "data":"appid.data.json,appid.data?.json;appidStorageFile,appidStorageFile*"
// added by BangleApps loader on upload - lists files that // added by BangleApps loader on upload - lists files that
// the app might write, so they can be deleted on uninstall // the app might write, so they can be deleted on uninstall
// typically these files are not uploaded, but created by the app // typically these files are not uploaded, but created by the app
// these can include '*' or '?' wildcards // these can include '*' or '?' wildcards
"storageFiles":"
// same as "dataFiles", except the app handles these as storageFile
} }
``` ```

View File

@ -39,9 +39,10 @@ try{
const APP_KEYS = [ const APP_KEYS = [
'id', 'name', 'shortName', 'version', 'icon', 'description', 'tags', 'type', 'id', 'name', 'shortName', 'version', 'icon', 'description', 'tags', 'type',
'sortorder', 'readme', 'custom', 'interface', 'storage', 'allow_emulator', 'sortorder', 'readme', 'custom', 'interface', 'storage', 'data', 'allow_emulator',
]; ];
const STORAGE_KEYS = ['name', 'url', 'content', 'evaluate']; const STORAGE_KEYS = ['name', 'url', 'content', 'evaluate'];
const DATA_KEYS = ['name', 'wildcard', 'storageFile'];
apps.forEach((app,appIdx) => { apps.forEach((app,appIdx) => {
if (!app.id) ERROR(`App ${appIdx} has no id`); if (!app.id) ERROR(`App ${appIdx} has no id`);
@ -142,7 +143,7 @@ apps.forEach((app,appIdx) => {
if ('storageFile' in data && typeof data.storageFile !== 'boolean') if ('storageFile' in data && typeof data.storageFile !== 'boolean')
ERROR(`App ${app.id} data file ${data.name||data.wildcard} has non-boolean value for "storageFile"`); ERROR(`App ${app.id} data file ${data.name||data.wildcard} has non-boolean value for "storageFile"`);
for (const key in data) { for (const key in data) {
if (!['name','wildcard','storageFile'].includes(key)) if (!DATA_KEYS.includes(key))
ERROR(`App ${app.id} data file ${data.name||data.wildcard} has unknown property "${key}"`); ERROR(`App ${app.id} data file ${data.name||data.wildcard} has unknown property "${key}"`);
} }
}); });

View File

@ -69,26 +69,51 @@ var AppInfo = {
var fileList = fileContents.map(storageFile=>storageFile.name); var fileList = fileContents.map(storageFile=>storageFile.name);
fileList.unshift(appJSONName); // do we want this? makes life easier! fileList.unshift(appJSONName); // do we want this? makes life easier!
json.files = fileList.join(","); json.files = fileList.join(",");
let dataFileList = [], storageFileList = []; let data = {dataFiles: [], storageFiles: []};
if ('data' in app) { if ('data' in app) {
// add "data" files to appropriate list // add "data" files to appropriate list
app.data.forEach(d=>{ app.data.forEach(d=>{
if (d.storageFile) storageFileList.push(d.name||d.wildcard) if (d.storageFile) data.storageFiles.push(d.name||d.wildcard)
else dataFileList.push(d.name||d.wildcard) else data.dataFiles.push(d.name||d.wildcard)
}) })
} else if (json.settings) { } else if (json.settings) {
// settings but no data files: assume app uses <appid>.settings.json file // settings but no data files: assume app uses <appid>.settings.json file
dataFileList.push(app.id + '.settings.json') data.dataFiles.push(app.id + '.settings.json')
} }
if (dataFileList.length) json.dataFiles = dataFileList.join(","); const dataString = AppInfo.makeDataString(data)
if (storageFileList.length) json.storageFiles = storageFileList.join(","); if (dataString) json.data = dataString
fileContents.push({ fileContents.push({
name : appJSONName, name : appJSONName,
content : JSON.stringify(json) content : JSON.stringify(json)
}); });
resolve(fileContents); resolve(fileContents);
}); });
} },
// (<appid>.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) if ("undefined"!=typeof module)

View File

@ -94,20 +94,22 @@ getInstalledApps : () => {
}); });
}, },
removeApp : app => { // expects an appid.info structure (i.e. with `files`) removeApp : app => { // expects an appid.info structure (i.e. with `files`)
if (!app.files && !app.dataFiles && !app.storageFiles) return Promise.resolve(); // nothing to erase if (!app.files && !app.data) return Promise.resolve(); // nothing to erase
Progress.show({title:`Removing ${app.name}`,sticky:true}); Progress.show({title:`Removing ${app.name}`,sticky:true});
let cmds = '\x10const s=require("Storage");\n'; let cmds = '\x10const s=require("Storage");\n';
// remove App files (regular files, exact names only) // remove App files: regular files, exact names only
cmds += app.files.split(',').map(file => `\x10s.erase(${toJS(file)});\n`).join(""); cmds += app.files.split(',').map(file => `\x10s.erase(${toJS(file)});\n`).join("");
// remove Data files (regular files, can use wildcards) // remove app Data: (dataFiles and storageFiles)
cmds += (app.dataFiles||[]).split(',').map(file => { const data = AppInfo.parseDataString(app.data)
// regular files, can use wildcards
cmds += data.dataFiles.map(file => {
const isGlob = (file.includes('*') || file.includes('?')) const isGlob = (file.includes('*') || file.includes('?'))
if (!isGlob) return `\x10s.erase(${toJS(file)});\n`; if (!isGlob) return `\x10s.erase(${toJS(file)});\n`;
const regex = new RegExp(globToRegex(file)) const regex = new RegExp(globToRegex(file))
return `\x10s.list(${regex}).forEach(f=>s.erase(f));\n`; return `\x10s.list(${regex}).forEach(f=>s.erase(f));\n`;
}).join(""); }).join("");
// remove Storage files (storageFiles, can use wildcards) // storageFiles, can use wildcards
cmds += (app.storageFiles||[]).split(',').map(file => { cmds += data.storageFiles.map(file => {
const isGlob = (file.includes('*') || file.includes('?')) const isGlob = (file.includes('*') || file.includes('?'))
if (!isGlob) return `\x10s.open(${toJS(file)},'r').erase();\n`; if (!isGlob) return `\x10s.open(${toJS(file)},'r').erase();\n`;
// storageFiles have a chunk number appended to their real name // storageFiles have a chunk number appended to their real name

View File

@ -349,19 +349,17 @@ function updateApp(app) {
.filter(f => f !== app.id + '.info') .filter(f => f !== app.id + '.info')
.filter(f => !app.storage.some(s => s.name === f)) .filter(f => !app.storage.some(s => s.name === f))
.join(','); .join(',');
let dataFiles = (remove.dataFiles||'').split(','), let data = AppInfo.parseDataString(remove.data)
storageFiles = (remove.storageFiles||'').split(',')
if ('data' in app) { if ('data' in app) {
// keep declared (in new version) data files // only remove data files which are no longer declared in new app version
dataFiles = dataFiles.filter(f => app.data.some(d => (d.name||d.wildcard) === f)) const removeData = (f) => !app.data.some(d => (d.name || d.wildcard)===f)
storageFiles = storageFiles.filter(f => app.data.some(d => (d.name||d.wildcard) === f)) data.dataFiles = data.dataFiles.filter(removeData)
} data.storageFiles = data.storageFiles.filter(removeData)
else if (remove.settings || app.settings) { } else if (remove.settings || app.settings) {
// app with settings but no data files declared: keep <appid>.settings.json // app with settings but no data files declared: keep <appid>.settings.json
dataFiles = dataFiles.filter(f => f !== (app.id + '.settings.json')) data.dataFiles = data.dataFiles.filter(f => f!==(app.id+'.settings.json'))
} }
if (dataFiles.length) remove.dataFiles = dataFiles.join(','); remove.data = AppInfo.makeDataString(data)
if (storageFiles.length) remove.storageFiles = storageFiles.join(',')
return Comms.removeApp(remove); return Comms.removeApp(remove);
}).then(()=>{ }).then(()=>{
showToast(`Updating ${app.name}...`); showToast(`Updating ${app.name}...`);