diff --git a/client/src/App.vue b/client/src/App.vue index 3ceb988..435740a 100644 --- a/client/src/App.vue +++ b/client/src/App.vue @@ -91,7 +91,10 @@ {{ this.computeSizeCheckText() }}
- + {{ this.options.magicmode ? "" : "Choose image to install" }}
@@ -282,7 +285,9 @@ export default { return ""; }, computeMagicButtonText() { - return this.state == "MAGIC" ? "Cancel" : "Magic"; + return this.state == "MAGIC" || this.state == "UPLOADING_MAGIC" + ? "Cancel" + : "Magic"; }, flashDirection() { return this.flash.selectedMethod == 1 ? "left" : "right"; @@ -310,6 +315,8 @@ export default { if (this.selectedMethod.id == 0 && this.selectedRebuildImage) return true; if (this.selectedMethod.id == 1 && this.selectedRefactorImage) return true; + if (this.selectedMethod.id == 2 && this.selectedUploadImage.file) + return true; return false; }, isTransferButtonVisible() { @@ -363,6 +370,65 @@ export default { } }); }, + async startMagicUpload() { + let self = this; + if (this.state == "IDLE") { + this.state = "UPLOADING_MAGIC"; + await axios + .put(`/api/upload_magic_start`, { + filename: self.file.name, + size: self.file.size, + start_time: Date.now(), + }) + .then(function (response) { + self.status = response.data["success"]; + self.magicUploadLocalFile(); + self.checkProgress(); + }); + } else { + this.apiCall("upload_cancel"); + } + }, + async magicUploadLocalFile() { + console.log("magig upload local file"); + const CHUNK_SIZE = 3 * 1024 * 1024; + let self = this; + var reader = new FileReader(); + var offset = 0; + var filesize = this.file.size; + console.log(filesize); + + reader.onload = function () { + console.log("onload"); + var result = reader.result; + var chunk = result; + axios + .post(`/api/upload_magic_chunk`, { + chunk: chunk, + }) + .then(function (response) { + const status = response.data; + if (status.success && self.state == "UPLOADING_MAGIC") { + offset += CHUNK_SIZE; + if (offset <= filesize) { + var slice = self.file.slice(offset, offset + CHUNK_SIZE); + reader.readAsDataURL(slice); + } else { + offset = filesize; + self.apiCall("upload_finish"); + } + } else { + self.apiCall("upload_cancel"); + } + }); + }; + + if (this.file) { + var slice = this.file.slice(offset, offset + CHUNK_SIZE); + reader.readAsDataURL(slice); + self.fileName = this.file.name; + } + }, async uploadSelected() { let self = this; if (this.state == "IDLE") { @@ -379,7 +445,7 @@ export default { self.checkProgress(); }); } else { - this.apiCall('upload_cancel') + this.apiCall("upload_cancel"); } }, async uploadLocalFile() { @@ -405,10 +471,10 @@ export default { reader.readAsDataURL(slice); } else { offset = filesize; - self.apiCall('upload_finish'); + self.apiCall("upload_finish"); } } else { - self.apiCall('upload_cancel') + self.apiCall("upload_cancel"); } }); }; @@ -422,15 +488,19 @@ export default { onMagicButtonClick() { if (this.selectedMethod.id == 0) { this.selectedGithubImage = this.selectedRebuildImage; + this.startMagic(); } else if (this.selectedMethod.id == 1) { this.selectedGithubImage = this.selectedRefactorImage; + this.startMagic(); + } else if (this.selectedMethod.id == 2) { + this.selectedGithubImage = this.selectedLocalImage; + this.startMagicUpload(); } - this.startMagic(); }, async startMagic() { let self = this; if (this.state == "IDLE") { - this.state = "MAGIC" + this.state = "MAGIC"; await axios .put(`/api/start_magic`, { filename: this.selectedGithubImage["name"], @@ -478,6 +548,7 @@ export default { "INSTALLING", "BACKUPING", "MAGIC", + "UPLOADING_MAGIC", ].includes(this.state) ) { this.setProgress({ progress: data.progress }); diff --git a/reflash/go.mod b/reflash/go.mod index e4f72fa..6f308f0 100644 --- a/reflash/go.mod +++ b/reflash/go.mod @@ -10,6 +10,7 @@ require ( require ( github.com/grafana/tail v0.0.0-20230510142333-77b18831edf0 // indirect github.com/pelletier/go-toml/v2 v2.1.1 // indirect + github.com/ulikunitz/xz v0.5.12 // indirect golang.org/x/exp v0.0.0-20240531132922-fd00a4e0eefc // indirect golang.org/x/sys v0.0.0-20220908164124-27713097b956 // indirect gopkg.in/fsnotify/fsnotify.v1 v1.4.7 // indirect diff --git a/reflash/go.sum b/reflash/go.sum index de051a7..2e9eb8d 100644 --- a/reflash/go.sum +++ b/reflash/go.sum @@ -14,6 +14,8 @@ github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpE github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= +github.com/ulikunitz/xz v0.5.12 h1:37Nm15o69RwBkXM0J6A5OlE67RZTfzUxTj8fB3dfcsc= +github.com/ulikunitz/xz v0.5.12/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14= golang.org/x/exp v0.0.0-20240531132922-fd00a4e0eefc h1:O9NuF4s+E/PvMIy+9IUZB9znFwUIXEWSstNjek6VpVg= golang.org/x/exp v0.0.0-20240531132922-fd00a4e0eefc/go.mod h1:XtvwrStGgqGPLc4cjQfWqZHG1YFdYs6swckp8vpsjnc= golang.org/x/image v0.14.0 h1:tNgSxAFe3jC4uYqvZdTr84SZoM1KfwdC9SKIFrLjFn4= diff --git a/reflash/server.go b/reflash/server.go index e558a9e..1e9a51f 100644 --- a/reflash/server.go +++ b/reflash/server.go @@ -19,6 +19,7 @@ import ( "github.com/grafana/tail" "github.com/pelletier/go-toml/v2" + "github.com/ulikunitz/xz" "golang.org/x/exp/slices" ) @@ -88,15 +89,16 @@ type Settings struct { } const ( - IDLE = "IDLE" - DOWNLOADING = "DOWNLOADING" - UPLOADING = "UPLOADING" - INSTALLING = "INSTALLING" - BACKUPING = "BACKUPING" - MAGIC = "MAGIC" - FINISHED = "FINISHED" - CANCELLED = "CANCELLED" - ERROR = "ERROR" + IDLE = "IDLE" + DOWNLOADING = "DOWNLOADING" + UPLOADING = "UPLOADING" + INSTALLING = "INSTALLING" + BACKUPING = "BACKUPING" + MAGIC = "MAGIC" + UPLOADING_MAGIC = "UPLOADING_MAGIC" + FINISHED = "FINISHED" + CANCELLED = "CANCELLED" + ERROR = "ERROR" ) const ( @@ -190,6 +192,8 @@ func ServerInit() { http.HandleFunc("/api/cancel_backup", cancelBackup) http.HandleFunc("/api/start_magic", startMagic) http.HandleFunc("/api/cancel_magic", cancelMagic) + http.HandleFunc("/api/upload_magic_start", uploadMagicStart) + http.HandleFunc("/api/upload_magic_chunk", uploadMagicChunk) http.HandleFunc("/api/get_progress", getProgress) http.HandleFunc("/api/check_file_integrity", checkFileIntegrity) http.HandleFunc("/api/run_install_finished_commands", runInstallFinishedCommands) @@ -342,6 +346,72 @@ func uploadStart(w http.ResponseWriter, r *http.Request) { sendResponse(w, nil) } +func uploadMagicStart(w http.ResponseWriter, r *http.Request) { + var data *Download = &Download{} + reqBody, _ := io.ReadAll(r.Body) + json.Unmarshal(reqBody, &data) + + state.Filename = data.Filename + state.StartTime = data.StartTime + state.BytesNow = 0 + state.BytesTotal = data.Size + state.State = UPLOADING_MAGIC + + timeStart = time.Now() + logInfo("Starting magic upload at " + timeStart.Format("15:04:05")) + logInfo("Filename: " + state.Filename) + + path := "/tmp/decompressed.img" + os.Create(path) + sendResponse(w, nil) +} + +func uploadMagicChunk(w http.ResponseWriter, r *http.Request) { + var chunk *Chunk = &Chunk{} + reqBody, _ := io.ReadAll(r.Body) + json.Unmarshal(reqBody, &chunk) + + decoded, err := base64.StdEncoding.DecodeString(chunk.Encoded[37:]) + + path := "/tmp/decompressed.img" + + if state.State == CANCELLED { + response := map[string]bool{"success": false} + json.NewEncoder(w).Encode(response) + return + } + + reader, err := xz.NewReader(bytes.NewReader(decoded)) + if err != nil { + log.Fatal(err) + } + + var decompressedData bytes.Buffer + if _, err := io.Copy(&decompressedData, reader); err != nil { + log.Fatal("Failed to copy") + log.Fatal(err) + } + + outFile, err := os.OpenFile(path, os.O_APPEND|os.O_WRONLY, 0644) + if err != nil { + log.Fatal("Failed to open file") + log.Fatal(err) + } + + if _, err := outFile.Write(decompressedData.Bytes()); err != nil { + log.Fatal("Failed write") + log.Fatal(err) + } + if err := outFile.Close(); err != nil { + log.Fatal(err) + } + state.BytesNow += len(decoded) + state.Progress = float64(state.BytesNow) * 100 / float64(state.BytesTotal) + + response := map[string]bool{"success": true} + json.NewEncoder(w).Encode(response) +} + func uploadChunk(w http.ResponseWriter, r *http.Request) { var chunk *Chunk = &Chunk{} reqBody, _ := io.ReadAll(r.Body)