diff --git a/app.go b/app.go index a2ed46a..0b008a2 100644 --- a/app.go +++ b/app.go @@ -1,6 +1,7 @@ package main import ( + "context" "fmt" "os" "os/exec" @@ -12,6 +13,7 @@ import ( rlbot "github.com/RLBot/go-interface" "github.com/RLBot/go-interface/flat" "github.com/ncruces/zenity" + "github.com/wailsapp/wails/v3/pkg/application" ) type RawReleaseInfo struct { @@ -21,6 +23,7 @@ type RawReleaseInfo struct { // App struct type App struct { + app *application.App latestReleaseJson []RawReleaseInfo rlbotAddress string } @@ -70,7 +73,11 @@ func (a *App) DownloadBotpack(repo string, installPath string) (string, error) { } } - err = DownloadExtractArchive(downloadUrl, installPath) + if downloadUrl == "" { + return "", fmt.Errorf("Failed to find %s in latest release for %s", fileName, repo) + } + + err = DownloadExtractArchive(a.app.Event, downloadUrl, installPath) if err != nil { return "", err } @@ -78,10 +85,12 @@ func (a *App) DownloadBotpack(repo string, installPath string) (string, error) { return latestRelease.TagName, nil } -func (a *App) RepairBotpack(repo string, installPath string) (string, error) { - err := os.RemoveAll(installPath) - if err != nil { - return "", err +func (a *App) RepairBotpack(repo string, installPath string, clearInstallPath bool) (string, error) { + if clearInstallPath { + err := os.RemoveAll(installPath) + if err != nil { + return "", err + } } return a.DownloadBotpack(repo, installPath) @@ -103,11 +112,19 @@ func NewApp() *App { var latest_release_json []RawReleaseInfo return &App{ + nil, latest_release_json, rlbot_address, } } +func (a *App) ServiceStartup(ctx context.Context, options application.ServiceOptions) error { + // You can access the application instance via ctx + a.app = application.Get() + + return nil +} + func recursiveTomlSearch(root, tomlType string) ([]string, error) { var matches []string err := filepath.Walk(root, func(path string, info os.FileInfo, err error) error { diff --git a/frontend/src/components/PathsViewer.svelte b/frontend/src/components/PathsViewer.svelte index 5263943..d560851 100644 --- a/frontend/src/components/PathsViewer.svelte +++ b/frontend/src/components/PathsViewer.svelte @@ -5,8 +5,13 @@ import closeIcon from "../assets/close.svg"; import repairIcon from "../assets/repair.svg"; import Modal from "./Modal.svelte"; import Switch from "./Switch.svelte"; +import ProgressBar from "./ProgressBar.svelte"; +import { Events } from "@wailsio/runtime"; -const OFFICIAL_BOTPACK_REPO = "VirxEC/botpack-test"; +const OFFICIAL_BOTPACK_REPOS = [ + "VirxEC/botpack-test", + "VirxEC/pytorch-archive", +]; let { visible = $bindable(false), @@ -18,6 +23,7 @@ let { repo: string | null; installPath: string; visible: boolean; + isDependency: boolean; }[]; } = $props(); @@ -34,24 +40,74 @@ async function setDefaultPath() { setDefaultPath(); function removePath(index: number) { - paths.splice(index, 1); + const [removedItem] = paths.splice(index, 1); + + const dependency = paths.findIndex( + (item) => item.installPath === removedItem.installPath, + ); + if (dependency !== -1) removePath(dependency); } -function repairBotpack(index: number) { - const id = toast.loading("Re-downloading botpack..."); +let downloadModalTitle = $state("Downloading & extracting repo/owner"); +let downloadModalVisible = $state(false); +let downloadProgress = $state(0); +let downloadCurrentStep = $state(0); +let downloadTotalSteps = $state(0); - // @ts-ignore - App.RepairBotpack(paths[index].repo, paths[index].installPath) - .then((tagName) => { - paths[index].tagName = tagName; - toast.success("Botpack download successfully!", { id }); - }) - .catch((err) => { +Events.On("monitor:download-progress", (event) => { + const { status, done } = event.data.at(-1); + + if (done) { + downloadProgress = 0; + downloadCurrentStep += 1; + } else { + downloadProgress = status; + } +}); + +async function repairBotpack(index: number) { + const info = paths[index]; + if (!info.repo) { + return; // can't update something that doesn't have a repo + } + + downloadProgress = 0; + downloadCurrentStep = 0; + downloadTotalSteps = 0; + + for (const item of paths) { + if (!item.repo || item.installPath !== info.installPath) continue; + downloadTotalSteps += 2; + } + + let clearInstallPath = true; + for (const item of paths) { + if (!item.repo || item.installPath !== info.installPath) continue; + + downloadModalTitle = `Redownloading & extracting ${item.repo}`; + visible = false; + downloadModalVisible = true; + + let tagName = await App.RepairBotpack( + item.repo, + item.installPath, + clearInstallPath, + ).catch((err) => { toast.error(`Failed to download botpack: ${err}`, { duration: 10000, - id, }); + + return null; }); + if (!tagName) break; + + clearInstallPath = false; + downloadProgress = 0; + downloadCurrentStep += 1; + } + + downloadModalVisible = false; + visible = true; } function openAddBotpackModal() { @@ -60,6 +116,7 @@ function openAddBotpackModal() { } function closeAddBotpackModal() { + downloadModalVisible = false; addBotpackVisible = false; visible = true; selectedBotpackType = "official"; @@ -78,6 +135,7 @@ function addInstallPath(installPath: string) { visible: true, tagName: null, repo: null, + isDependency: false, }); } @@ -100,20 +158,21 @@ function addFile() { addInstallPath(result); }) - .catch((error) => { + .catch((_) => { toast.error( "Failed to add file: Only bot.toml or script.toml files are allowed", ); }); } -function confirmAddBotpack() { +async function confirmAddBotpack() { if (!installPath) { toast.error("Install path cannot be blank"); return; } - let repo = OFFICIAL_BOTPACK_REPO; + let repo: string, dep: string | null; + if (selectedBotpackType === "custom") { if (!customRepo) { toast.error("URL cannot be blank"); @@ -126,32 +185,80 @@ function confirmAddBotpack() { } repo = customRepo; + dep = null; + } else { + [repo, dep] = OFFICIAL_BOTPACK_REPOS; + } + + if (paths.some((x) => x.installPath === installPath)) { + toast.error(`Install path "${installPath}" already in use for ${repo}`); + return; } if (paths.some((x) => x.repo === repo)) { - toast.error("Botpack already added"); + toast.error(`Botpack ${repo} already added`); return; } - if (paths.some((x) => x.installPath === installPath)) { - toast.error("Install path already in use"); + downloadModalTitle = `Downloading & extracting ${repo}`; + downloadProgress = 0; + addBotpackVisible = false; + downloadModalVisible = true; + downloadCurrentStep = 0; + downloadTotalSteps = dep ? 4 : 2; + + const tagName = await App.DownloadBotpack(repo, installPath).catch((err) => { + toast.error(`Failed to download botpack: ${err}`, { + duration: 10000, + }); + + return null; + }); + if (!tagName) { + downloadModalVisible = false; + addBotpackVisible = true; return; } - const id = toast.loading("Downloading botpack..."); - App.DownloadBotpack(repo, installPath) - .then((tagName) => { - toast.success("Botpack downloaded successfully!", { id }); + if (dep) { + downloadModalTitle = `Downloading & extracting ${dep}`; + downloadProgress = 0; + downloadCurrentStep += 1; - paths.push({ installPath, repo, tagName, visible: true }); - closeAddBotpackModal(); - }) - .catch((err) => { - toast.error(`Failed to download botpack: ${err}`, { + // Download possible dependency of the given botpack + const tagName = await App.DownloadBotpack(dep, installPath).catch((err) => { + toast.error(`Failed to download botpack dependency: ${err}`, { duration: 10000, - id, }); + + return null; + }); + if (!tagName) { + downloadModalVisible = false; + addBotpackVisible = true; + return; + } + } + + paths.push({ + installPath, + repo, + tagName, + visible: true, + isDependency: false, + }); + if (dep) { + paths.push({ + installPath, + repo: dep, + tagName, + visible: false, + isDependency: true, }); + } + + toast.success("Botpack downloaded successfully!"); + closeAddBotpackModal(); } @@ -164,20 +271,26 @@ function confirmAddBotpack() { {#each paths as path, i} -
{path.repo ? `${path.repo} @ ${path.installPath}` : path.installPath}
- {#if path.repo}
-