Synchronizing file uploads between browser windows
For PiCockpit, I am currently developing the Digital Nose app.
This app requires an upload for the BSEC configuration file – which is binary data.
Once the user uploads the file, it gets published via MQTT, and picockpit-client can use it to configure the BSEC AI algorithm to do gas detection.
Here is a little preview of part of the webinterface:
Vuetify ships with a built-in GUI element, v-file-input:
<v-file-input
v-model=”bsecFile”
label=”BSEC Configuration File from AI Studio (.config)”
placeholder=”Upload your BSEC configuration file here”
prepend-icon=”mdi-paperclip”
outlined
dense
show-size
accept=”.config”
@change=”doUpload”
>
</v-file-input>
In this case, bsecFile is my model.
Note: Since it is a model, it can also be written to!
Publishing on MQTT
This is how the handler code doUpload looks like (still including debug info):
doUpload(){
console.log(“model:”);
console.log(this.bsecFile);
if (this.bsecFile){
// the code below does not work reliably, although it should in theory
// if (this.bsecFile.name.split(“.”)[1] != “config”){
// console.log(“The filename does not include .config – resetting.”);
// this.bsecFile = null;
// }
// else {
console.log(“the variable seems to be OK, trying to publish”);
var reader = new FileReader();
reader.addEventListener(‘load’, (event) => {
console.log(“File read successfully! publishing (now with Uint8Array).”);
// fn = filename, fs = file size, fd = file (binary) data
const msg = {
fn: this.bsecFile.name,
fs: this.bsecFile.size,
fd: new Uint8Array (event.target.result)
}
console.log(“message is:”);
console.log(msg);
this.$emit(“bsec-upload-event”, true);
myMqtt.sendWBasePath(sensorSetBsecPath, msg, 1, true);
});
reader.readAsArrayBuffer(this.bsecFile);
}
else {
console.log(“the file was just cleared, so we should remove it … sending $nofile as true”);
const msg = {
“$nofile”: true
}
console.log(“message is:”);
console.log(msg);
this.$emit(“bsec-upload-event”, false);
myMqtt.sendWBasePath(sensorSetBsecPath, msg, 1, true);
}
}
Note that I publish a message which includes binary data.
I use a constructor for a new Uint8Array to which I pass event.target.result – an ArrayBuffer.
This way, the data gets passed on precisely as it is.
We use MsgPack to pack the JavaScript Object into a message – which supports binary data.
On the other side, PiCockpit Client takes the binary message, and can reinstate a (in that case) Python object, including the binary data.
Making it work for synchronizing across browser windows
A second task is to synchronize the state across different browser windows and also sessions of the user.
When the user returns, we would like to present the file as already having been uploaded to them.
What I want to do is essentially to build a new File Object instance in JavaScript, from my Uint8Array to recreate the file from the MQTT message!
We subscribe & listen to the MQTT message, of course. And then we monitor the variable for changes, and modify the model bsecFile as follows:
globalBsecFileWrapped(val){
// this will ONLY be updated if we were not responsible for sending the message in the first place.
console.log(“watch > globalBsecFileWrapped > contents:”);
console.log(this.globalBsecFileWrapped);
console.log(“we’re going to set this as a file now … “);
if (this.globalBsecFileWrapped.hasOwnProperty(“$nofile”)){
if (this.globalBsecFileWrapped[‘$nofile’]){
// update it to be null – it was reset from a different browser.
this.bsecFile = null;
}
}
else if (this.globalBsecFileWrapped.hasOwnProperty(“fd”) && this.globalBsecFileWrapped.hasOwnProperty(“fn”)) {
// attempt 2
// this.bsecFile = new File(this.globalBsecFileWrapped.fd, this.globalBsecFileWrapped.fn);
this.bsecFile = new Blob([this.globalBsecFileWrapped.fd], {type: “application/octet-stream”});
this.bsecFile.name = this.globalBsecFileWrapped.fn;
// debug
var reader = new FileReader();
reader.addEventListener(‘load’, (event) => {
console.log(“Debug >> File read successfully! publishing (now with Uint8Array).”);
// fn = filename, fs = file size, fd = file (binary) data
const msg = {
fn: this.bsecFile.name,
fs: this.bsecFile.size,
fd: new Uint8Array (event.target.result)
}
console.log(“message is:”);
console.log(msg);
});
reader.readAsArrayBuffer(this.bsecFile);
}
}
note the important parts here:
this.bsecFile = new Blob([this.globalBsecFileWrapped.fd], {type: “application/octet-stream”});
this.bsecFile.name = this.globalBsecFileWrapped.fn;
So, in order to recreate the file (and have the vuetify v-file-input update to the correct file (showing size appropriately, and filename) we are constructing a new Blob.
Important: the Blob needs to have the type of “application/octet-stream” – otherwise it is going to be interpreted as something different and the filesize will not match anymore!
And finally, we set the new name – that’s it!