Skip to content

Commit

Permalink
feat(gui): add download tracking interface on bot launch
Browse files Browse the repository at this point in the history
fix(astor): bad struct field access on msg listening
  • Loading branch information
hbollon committed Aug 17, 2021
1 parent 674a522 commit 61c4531
Show file tree
Hide file tree
Showing 5 changed files with 224 additions and 19 deletions.
96 changes: 91 additions & 5 deletions dependencies.go
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,18 @@ type file struct {
browser bool
}

type downloadsTracking map[string]*downloadStatus

type downloadStatus struct {
TotalSize int64
DownloadedSize int64
Progress float64
Speed int64
Started bool
Completed bool
Failed bool
}

var (
files = []file{
{
Expand Down Expand Up @@ -332,9 +344,13 @@ func DownloadDependencies(downloadBrowsers, downloadLatest, forceDl bool) {
)

var wg sync.WaitGroup
var filesToDl []file
dlTracking := make(downloadsTracking)
for _, file := range files {
if file.os == "" || file.os == runtime.GOOS {
wg.Add(1)
dlTracking[file.name] = &downloadStatus{}
filesToDl = append(filesToDl, file)
bar := p.Add(0,
mpb.NewBarFiller("[=>-|"),
mpb.BarFillerClearOnComplete(),
Expand All @@ -351,18 +367,26 @@ func DownloadDependencies(downloadBrowsers, downloadLatest, forceDl bool) {
file := file
go func() {
time.Sleep(2 * time.Second)
if err := handleFile(bar, file, downloadBrowsers, forceDl); err != nil {
if err := handleFile(bar, dlTracking, file, downloadBrowsers, forceDl); err != nil {
log.Fatalf("Error handling %s: %s", file.name, err)
}
wg.Done()
}()
}
}
if IsElectronRunning() {
done := make(chan bool)
go followUpDownloads(dlTracking, filesToDl, done)
defer func(done chan bool) {
done <- true
close(done)
}(done)
}
p.Wait()
wg.Wait()
}

func handleFile(bar *mpb.Bar, file file, downloadBrowsers, forceDl bool) error {
func handleFile(bar *mpb.Bar, dlTracking downloadsTracking, file file, downloadBrowsers, forceDl bool) error {
if file.browser && !downloadBrowsers {
log.Infof("Skipping %q because --download_browser is not set.", file.name)
bar.Abort(true)
Expand All @@ -372,7 +396,7 @@ func handleFile(bar *mpb.Bar, file file, downloadBrowsers, forceDl bool) error {
log.Debugf("Skipping file %q which has already been downloaded.", file.name)
bar.Abort(true)
} else {
if err := downloadFile(bar, file); err != nil {
if err := downloadFile(bar, dlTracking, file); err != nil {
bar.Abort(true)
return err
}
Expand Down Expand Up @@ -419,7 +443,7 @@ func extractFile(file file) error {
return nil
}

func downloadFile(bar *mpb.Bar, file file) (err error) {
func downloadFile(bar *mpb.Bar, dlTracking downloadsTracking, file file) (err error) {
f, err := os.Create(file.path)
if err != nil {
return fmt.Errorf("error creating %q: %v", file.path, err)
Expand All @@ -431,11 +455,16 @@ func downloadFile(bar *mpb.Bar, file file) (err error) {
}()

resp, err := http.Get(file.url)
bar.SetTotal(resp.ContentLength, false)
if err != nil {
return fmt.Errorf("%s: error downloading %q: %v", file.name, file.url, err)
}
defer resp.Body.Close()

bar.SetTotal(resp.ContentLength, false)
if track, ok := dlTracking[file.name]; ok {
track.TotalSize = resp.ContentLength
}

if file.hash != "" {
var h hash.Hash
switch strings.ToLower(file.hashType) {
Expand All @@ -446,20 +475,77 @@ func downloadFile(bar *mpb.Bar, file file) (err error) {
default:
h = sha256.New()
}
dlTracking[file.name].Started = true
if _, err := io.Copy(io.MultiWriter(f, h), bar.ProxyReader(resp.Body)); err != nil {
return fmt.Errorf("%s: error downloading %q: %v", file.name, file.url, err)
}
if h := hex.EncodeToString(h.Sum(nil)); h != file.hash {
return fmt.Errorf("%s: got %s hash %q, want %q", file.name, file.hashType, h, file.hash)
}
} else {
dlTracking[file.name].Started = true
if _, err := io.Copy(f, bar.ProxyReader(resp.Body)); err != nil {
return fmt.Errorf("%s: error downloading %q: %v", file.name, file.url, err)
}
}
return nil
}

// Will regulrary check downloads status and calculate progress pencentages
// to send it to Electron GUI if it's running
func followUpDownloads(dlTracking downloadsTracking, srcFiles []file, done chan bool) {
time.Sleep(2 * time.Second)
for {
select {
case <-done:
msg := MessageOut{
Status: SUCCESS,
Msg: "downloads done",
}
SendMessageToElectron(msg)
log.Infof("Downloads finished")
return
default:
for _, srcFile := range srcFiles {
if track, ok := dlTracking[srcFile.name]; ok {
if track.Started && !track.Completed && !track.Failed {
file, err := os.Open(srcFile.path)
if err != nil {
log.Error(err)
}

fi, err := file.Stat()
if err != nil {
log.Error(err)
}

size := fi.Size()
if size == 0 {
size = 1
}

track.DownloadedSize = size
track.Progress = float64(size) / float64(track.TotalSize) * 100
if track.Progress == 100 {
track.Completed = true
}
}
} else {
log.Errorf("%s: download tracking not found", srcFile.name)
}
}

msg := MessageOut{
Status: INFO,
Msg: "downloads tracking",
Payload: dlTracking,
}
SendMessageToElectron(msg)
}
time.Sleep(2 * time.Second)
}
}

func fileSameHash(file file) bool {
if _, err := os.Stat(file.path); err != nil {
return false
Expand Down
18 changes: 13 additions & 5 deletions gui_messages.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,12 @@ import (
"github.com/sirupsen/logrus"
)

type SucessState string
type MsgState string

const (
SUCCESS SucessState = "Success"
ERROR SucessState = "Error"
SUCCESS MsgState = "Success"
ERROR MsgState = "Error"
INFO MsgState = "Info"
)

var (
Expand Down Expand Up @@ -47,7 +48,7 @@ var CallbackMap = map[string]func(*MessageIn) MessageOut{

// MessageOut represents a message for electron (going out)
type MessageOut struct {
Status SucessState `json:"status"`
Status MsgState `json:"status"`
Msg string `json:"msg"`
Payload interface{} `json:"payload,omitempty"`
}
Expand All @@ -58,10 +59,17 @@ type MessageIn struct {
Payload json.RawMessage `json:"payload,omitempty"`
}

// IsElectronRunning checks if electron is running
func IsElectronRunning() bool {
return window != nil
}

// SendMessageToElectron will send a message to Electron Gui and execute a callback
// Callback function is optional
func SendMessageToElectron(msg MessageOut, callbacks ...astilectron.CallbackMessage) {
window.SendMessage(msg, callbacks...)
if IsElectronRunning() {
window.SendMessage(msg, callbacks...)
}
}

// HandleMessages is handling function for incoming messages
Expand Down
98 changes: 98 additions & 0 deletions resources/static/vue-igopher/src/components/DownloadTracking.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
<template>
<div
class="modal fade"
id="dlModal"
tabindex="-1"
aria-labelledby="dlModalLabel"
aria-hidden="true"
>
<div class="modal-dialog modal-dialog-centered" style="width: 40vw; height: 50vw;">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="dlModalLabel">Dependencies Manager</h5>
</div>
<div class="modal-body text-center align-middle">
Downloading required dependencies, please wait...
<hr>
<div
class="row my-2 px-1"
v-for="(dl, filename) in downloadTracking"
:key="filename"
>
<div class="col-3" style="font-size: 12px">
{{ filename }}
</div>
<div class="col-9 ps-2">
<div class="progress" style="height: 20px;">
<div
:id="'bar-' + filename"
class="progress-bar progress-bar-striped progress-bar-animated"
role="progressbar"
style="width: 0%;"
:aria-valuenow="dl.progress"
aria-valuemin="0"
aria-valuemax="100"
>
{{ dl.progress }}%
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</template>

<script lang="ts">
import { Vue, Options } from "vue-class-component";
import { inject } from "vue";
import { Astor } from "../plugins/astilectron";
import { bootstrap } from "@/config";
@Options({
data() {
return {
astor: Astor,
dlmodal: {},
downloadTracking: {},
};
},
methods: {
updateProgress(payload: any) {
this.downloadTracking = payload.payload;
for (const key in this.downloadTracking) {
const progress = Math.floor(this.downloadTracking[key].Progress);
const bar = document.getElementById("bar-" + key);
bar.setAttribute("aria-valuenow", "" + progress);
bar.style.width = progress + "%";
bar.innerHTML = progress + "%";
}
},
closeModal() {
this.dlModal.toggle();
this.dlModal.dispose();
this.astor.remove("downloads tracking", this.updateProgress);
this.$emit("done");
},
},
mounted() {
this.astor = inject("astor");
this.astor.listen(
"downloads tracking",
this.updateProgress.bind(this),
false
);
this.astor.listen("downloads done", this.closeModal.bind(this), true);
this.dlModal = new bootstrap.Modal(document.getElementById("dlModal"), {
backdrop: "static",
keyboard: false,
});
this.dlModal.toggle();
},
})
export default class DownloadTracking extends Vue {}
</script>

<style></style>
2 changes: 1 addition & 1 deletion resources/static/vue-igopher/src/plugins/astilectron.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ export class Astor {
if (Array.prototype.slice.call(arguments).length == 1) { // eslint-disable-line
if (message) {
this.log('GO -> Vue', message);
this.emit(message.name, message);
this.emit(message.msg, message);
}
} else {
const identifier = message;
Expand Down
29 changes: 21 additions & 8 deletions resources/static/vue-igopher/src/views/DmAutomation.vue
Original file line number Diff line number Diff line change
@@ -1,24 +1,41 @@
<template>
<DownloadTracking v-if="this.downloading" @done="this.dissmissModalComp()"/>
<DmAutomationPanel />
</template>

<script lang="ts">
import { Options, Vue } from "vue-class-component";
import { inject } from "vue";
import DmAutomationPanel from "@/components/DmAutomationPanel.vue";
import DownloadTracking from "@/components/DownloadTracking.vue";
import * as config from "@/config";
import { Astor } from "@/plugins/astilectron";
@Options({
title: "DM Automation",
components: {
DownloadTracking,
DmAutomationPanel,
},
data() {
return {
downloading: false
}
},
methods: {
dissmissModalComp() {
this.downloading = false;
config.Toast.fire({
icon: "success",
title: "Bot successfully launched!",
});
},
},
mounted() {
const astor: Astor = inject("astor");
config.ready(() => {
astor.onIsReady(function() {
astor.onIsReady(() => {
config.getIgopherConfig(astor, fillInputs);
const dmBotLaunchBtn = document.querySelector("#dmBotLaunchBtn");
const dmBotLaunchIcon = document.querySelector("#dmBotLaunchIcon");
Expand All @@ -43,15 +60,11 @@ import { Astor } from "@/plugins/astilectron";
}
/// Buttons
dmBotLaunchBtn.addEventListener("click", function() {
dmBotLaunchBtn.addEventListener("click", () => {
if (dmBotRunning === "false" || dmBotRunning === null) {
astor.trigger("launchDmBot", {}, function(message: any) {
astor.trigger("launchDmBot", {}, (message: any) => {
if (message.status === config.SUCCESS) {
config.Toast.fire({
icon: "success",
title: message.msg,
});
this.downloading = true;
dmBotRunning = "true";
dmBotLaunchBtn.classList.add("btn-danger");
dmBotLaunchBtn.classList.remove("btn-success");
Expand Down

0 comments on commit 61c4531

Please sign in to comment.