diff --git a/app/index.html b/app/index.html index 2036ac6fddeb2a0da2dcefd3b56d698552406ffb..dccdbb2098e7b0231a561d0ebc87aaf822147952 100644 --- a/app/index.html +++ b/app/index.html @@ -380,6 +380,25 @@

+
+
+ + +
+ + +

diff --git a/app/src/controller.manager.js b/app/src/controller.manager.js index bfe32a731f9603d3f4be2d111ef9e11af8ab6ec9..337ad1272b1bcb48e515b088403bff73f3db35ce 100644 --- a/app/src/controller.manager.js +++ b/app/src/controller.manager.js @@ -413,7 +413,7 @@ export class Controller { setResources(resources) { this.resources = resources; if (this.resources.steps) { - this.steps.push(new Step("downloading", "download", false)); + this.steps.push(new Step("downloading", "download", true)); this.steps.push( ...this.resources.steps.map((step) => { return new Step( @@ -428,4 +428,12 @@ export class Controller { } this.deviceManager.setResources(this.resources.folder, this.steps); } + + setLocalZip(file) { + this.deviceManager.setLocalZipFile(file); + } + + clearLocalZip() { + this.deviceManager.clearLocalZipFile(); + } } diff --git a/app/src/controller/device.manager.js b/app/src/controller/device.manager.js index 74aff737139366989a321faacb6fa9f4f8c7ecf9..f86ace72dd77d76d995b56c250e8eba3367fdb00 100644 --- a/app/src/controller/device.manager.js +++ b/app/src/controller/device.manager.js @@ -27,6 +27,18 @@ export class DeviceManager { this.wasConnected = false; } + setLocalZipFile(file) { + this.downloader.setLocalZip(file); + } + + clearLocalZipFile() { + this.downloader.clearLocalZip(); + } + + hasLocalZipFile() { + return this.downloader.hasLocalZip(); + } + async init() { await this.bootloader.init(); await this.adb.init(); @@ -187,13 +199,23 @@ export class DeviceManager { async downloadAll(onProgress, onUnzip, onVerify) { try { - await this.downloader.downloadAndUnzipFolder( - this.files, - this.folder, - onProgress, - onUnzip, - onVerify, - ); + if (this.downloader.hasLocalZip()) { + await this.downloader.ingestLocalZip( + this.files, + this.folder, + onProgress, + onUnzip, + onVerify, + ); + } else { + await this.downloader.downloadAndUnzipFolder( + this.files, + this.folder, + onProgress, + onUnzip, + onVerify, + ); + } } catch (e) { throw new Error(`downloadAll error ${e.message || e}`); } diff --git a/app/src/controller/downloader.manager.js b/app/src/controller/downloader.manager.js index 4a2937cf281aab51fa2c56d634b09db9ee48af7e..ee732eb014bcb5def1a0c1f00b94cee89b4a4f2f 100644 --- a/app/src/controller/downloader.manager.js +++ b/app/src/controller/downloader.manager.js @@ -15,6 +15,7 @@ export class Downloader { constructor() { this.db = null; this.stored = {}; + this.localZipFile = null; } async init() { @@ -25,6 +26,18 @@ export class Downloader { this.quota = await navigator.storage.estimate(); } + setLocalZip(file) { + this.localZipFile = file; + } + + clearLocalZip() { + this.localZipFile = null; + } + + hasLocalZip() { + return !!this.localZipFile; + } + /* * */ async downloadAndUnzipFolder( @@ -34,6 +47,8 @@ export class Downloader { onUnzipProgress, onVerifyProgress, ) { + await this.clearDBStore(); + this.stored = {}; let current_file; try { for (let i = 0; i < folder.length; i++) { @@ -117,6 +132,54 @@ export class Downloader { } } + async ingestLocalZip( + filesRequired, + folder, + onDownloadProgress, + onUnzipProgress, + onVerifyProgress, + ) { + await this.clearDBStore(); + this.stored = {}; + if (!this.localZipFile) { + throw new Error("No local zip file selected"); + } + const zipDescriptor = folder.find((f) => f.unzip) || folder[0]; + if (!zipDescriptor) { + throw new Error("No zip resource found to map local file"); + } + + onDownloadProgress( + this.localZipFile.size, + this.localZipFile.size, + this.localZipFile.name || zipDescriptor.name || "local.zip", + ); + + const zipReader = new ZipReader(new BlobReader(this.localZipFile)); + const filesEntries = await zipReader.getEntries(); + for (let i = 0; i < filesEntries.length; i++) { + const unzippedEntry = await this.getFileFromZip(filesEntries[i], (value, total) => { + onUnzipProgress(value, total, filesEntries[i].filename); + }); + let filename = this.getMappedName( + filesEntries[i].filename, + zipDescriptor.mapping, + ); + if (filesRequired.includes(filename)) { + await this.setInDBStore(unzippedEntry.blob, filename); + this.stored[filename] = true; + const fileSHA = await this.computeSha256( + unzippedEntry.blob, + (loaded, total) => { + onVerifyProgress(loaded, total, filename); + }, + ); + console.log(`File: ${unzippedEntry.name} SHA256: ${fileSHA}`); + } + } + await zipReader.close(); + } + async getFileFromZip(file, onProgress) { const name = file.filename; const blob = await file.getData(new BlobWriter(), { diff --git a/app/src/viewManager.js b/app/src/viewManager.js index d122881e6c0b1f41614e44467b731af1ba30d643..3133a25d3e8c48b3ec39de051942b3674d3795f5 100644 --- a/app/src/viewManager.js +++ b/app/src/viewManager.js @@ -32,12 +32,16 @@ export default class ViewManager { $copyStep.id = step.id; $copyStep.classList.add("active"); $copyStep.classList.remove("inactive"); - const $button = $copyStep.querySelector("button"); - if ($button) { - $button.addEventListener("click", async (event) => { - event.stopPropagation(); - await this.executeStep($button, step.name); - }); + if (step.name === "downloading") { + this.bindDownloadChoice($copyStep, step); + } else { + const $button = $copyStep.querySelector("button"); + if ($button) { + $button.addEventListener("click", async (event) => { + event.stopPropagation(); + await this.executeStep($button, step.name); + }); + } } let $processCtn = document.getElementById("process-ctn"); if ($processCtn) { @@ -222,6 +226,57 @@ export default class ViewManager { } // /CONTROLLER EVENTS + bindDownloadChoice($copyStep, step) { + const downloadBtn = $copyStep.querySelector(".download-build-button"); + const localBtn = $copyStep.querySelector(".use-local-zip-button"); + const fileInput = $copyStep.querySelector(".local-zip-input"); + const errorEl = $copyStep.querySelector(".local-zip-error"); + + if (downloadBtn) { + downloadBtn.addEventListener("click", async (event) => { + event.stopPropagation(); + if (errorEl) { + errorEl.style.display = "none"; + errorEl.innerText = ""; + } + this.controller.clearLocalZip(); + await this.executeStep(downloadBtn, step.name); + }); + } + + if (localBtn && fileInput) { + localBtn.addEventListener("click", (event) => { + event.stopPropagation(); + if (errorEl) { + errorEl.style.display = "none"; + errorEl.innerText = ""; + } + fileInput.value = ""; + fileInput.click(); + }); + + fileInput.addEventListener("change", async (evt) => { + const file = evt.target.files[0]; + if (!file) { + return; + } + if (!file.name.toLowerCase().endsWith(".zip")) { + if (errorEl) { + errorEl.innerText = "Please select a .zip file."; + errorEl.style.display = "block"; + } + fileInput.value = ""; + return; + } + if (errorEl) { + errorEl.style.display = "none"; + errorEl.innerText = ""; + } + this.controller.setLocalZip(file); + await this.executeStep(localBtn, step.name); + }); + } + } } document.addEventListener("DOMContentLoaded", async () => { @@ -230,11 +285,12 @@ document.addEventListener("DOMContentLoaded", async () => { let elts = document.querySelectorAll(".card button"); for (let elt of elts) { - if (elt.parentElement.parentElement.className.includes("inactive")) { + const card = elt.closest(".card"); + if (!card || card.className.includes("inactive")) { continue; } elt.addEventListener("click", async () => { - VIEW.executeStep(elt, elt.parentElement.parentElement.id); + VIEW.executeStep(elt, card.id); }); } });