From 7ecb90b63b6142090ada98ff2aac19d0c6f964c3 Mon Sep 17 00:00:00 2001 From: NeilClack Date: Thu, 18 Jun 2026 21:28:16 -0500 Subject: [PATCH] Downloads are working and the goroutine is reporting updates appropriately. --- internal/download/Download.go | 61 ++++++---- internal/page/Message.go | 29 +++++ internal/page/home.go | 68 +++++++++-- internal/page/message.go | 111 ------------------ internal/page/router.go | 6 +- internal/page/updateCheckPage.go | 188 ------------------------------- 6 files changed, 126 insertions(+), 337 deletions(-) create mode 100644 internal/page/Message.go delete mode 100644 internal/page/message.go delete mode 100644 internal/page/updateCheckPage.go diff --git a/internal/download/Download.go b/internal/download/Download.go index 38da210..3d4da08 100644 --- a/internal/download/Download.go +++ b/internal/download/Download.go @@ -6,15 +6,16 @@ import ( "net/http" "os" "path/filepath" + + "rhemawavetech.com/rhemawave/internal/logger" ) // DownloadStatus holds information related to downloads and is // the container for information returned via channels within this // package. type DownloadStatus struct { - Min int64 // Current file's bytes downloaded - Max int64 // Current file's total size - Cur int64 // Overall bytes downloaded + Max int64 // Total bytes to download + Cur int64 // Total bytes downloaded Msg string // Status message Err error // Error, if any } @@ -25,11 +26,14 @@ func DownloadAudioFiles(progressChan chan<- DownloadStatus, filenames []string, defer close(progressChan) urlBase := "https://files.rhemawavetech.com/audio/" - totalDownloaded := int64(0) - totalSize := int64(0) - // First, get the total size of all files + // First, calculate the total size of files that need to be downloaded + totalSize := int64(0) + filesToDownload := []string{} + for _, filename := range filenames { + destPath := filepath.Join(destDir, filename) + // Check if the file already exists url := urlBase + filename resp, err := http.Head(url) if err != nil { @@ -39,7 +43,6 @@ func DownloadAudioFiles(progressChan chan<- DownloadStatus, filenames []string, } return } - resp.Body.Close() if resp.StatusCode != http.StatusOK { progressChan <- DownloadStatus{ Msg: fmt.Sprintf("Failed to get size for %s: status %s", filename, resp.Status), @@ -47,19 +50,39 @@ func DownloadAudioFiles(progressChan chan<- DownloadStatus, filenames []string, } return } - totalSize += resp.ContentLength + fileStat, err := os.Stat(destPath) + // If the file is incomplete, or does not exist + if os.IsNotExist(err) || fileStat.Size() != resp.ContentLength { + // Oh, and log a warning if the file is not complete + if !os.IsNotExist(err) { + logger.Warning("%s incomplete/corrupt. Adding to download queue.", filename) + } else { + logger.Info("%s not found. Adding to download queue.", filename) + } + + // Append it to the list to download + filesToDownload = append(filesToDownload, filename) + // And increase the total bytes to download + totalSize += resp.ContentLength + } else if err != nil { + logger.Error("unknown error: %v", err) + } else { + logger.Error("File %s exists, skipping...", filename) + } + resp.Body.Close() } // Send initial status progressChan <- DownloadStatus{ - Min: 0, Max: totalSize, Cur: 0, Msg: "Starting download...", } - // Download each file - for _, filename := range filenames { + totalDownloaded := int64(0) + + // Download each file that doesn't exist + for _, filename := range filesToDownload { url := urlBase + filename destPath := filepath.Join(destDir, filename) @@ -96,10 +119,6 @@ func DownloadAudioFiles(progressChan chan<- DownloadStatus, filenames []string, return } - // Track progress for this file - fileSize := resp.ContentLength - downloaded := int64(0) - // Use a buffered reader for efficiency buf := make([]byte, 8192) for { @@ -129,13 +148,11 @@ func DownloadAudioFiles(progressChan chan<- DownloadStatus, filenames []string, } // Update progress - downloaded += int64(n) totalDownloaded += int64(n) progressChan <- DownloadStatus{ - Min: downloaded, - Max: fileSize, + Max: totalSize, Cur: totalDownloaded, - Msg: fmt.Sprintf("Downloading %s...", filename), + Msg: fmt.Sprintf("%s", filename), } } @@ -143,8 +160,7 @@ func DownloadAudioFiles(progressChan chan<- DownloadStatus, filenames []string, // Send completion for this file progressChan <- DownloadStatus{ - Min: fileSize, - Max: fileSize, + Max: totalSize, Cur: totalDownloaded, Msg: fmt.Sprintf("Finished downloading %s", filename), } @@ -152,9 +168,8 @@ func DownloadAudioFiles(progressChan chan<- DownloadStatus, filenames []string, // Send final status progressChan <- DownloadStatus{ - Min: totalSize, Max: totalSize, Cur: totalDownloaded, - Msg: "All downloads completed!", + Msg: "All audio downloads completed!", } } diff --git a/internal/page/Message.go b/internal/page/Message.go new file mode 100644 index 0000000..c010f22 --- /dev/null +++ b/internal/page/Message.go @@ -0,0 +1,29 @@ +package page + +import ( + "github.com/veandco/go-sdl2/sdl" +) + +type MessagePage struct { + Msg string +} + +func (p *MessagePage) Init(ctx *PageCtx) error { + return nil +} + +func (p *MessagePage) HandleEvent(event *sdl.Event) error { + return nil +} + +func (p *MessagePage) Update(dt float32) error { + return nil +} + +func (p *MessagePage) Render() error { + return nil +} + +func (p *MessagePage) Destroy() error { + return nil +} diff --git a/internal/page/home.go b/internal/page/home.go index f55baab..4f03722 100644 --- a/internal/page/home.go +++ b/internal/page/home.go @@ -2,12 +2,14 @@ package page import ( "fmt" + "path/filepath" "time" "github.com/go-gl/gl/v3.3-core/gl" "github.com/go-gl/mathgl/mgl32" "github.com/veandco/go-sdl2/sdl" "rhemawavetech.com/rhemawave/internal/colors" + "rhemawavetech.com/rhemawave/internal/download" "rhemawavetech.com/rhemawave/internal/fontManager" "rhemawavetech.com/rhemawave/internal/shaderManager" ) @@ -16,16 +18,20 @@ var GitVersion = "1.0" type Home struct { Base - title StringItem - version StringItem - prompt StringItem - note StringItem - licenseInfoHeading StringItem - emailDisplay StringItem - licenseKeyDisplay StringItem - hasLicenseInfo bool - lastDisplayUpdate time.Time - baseFontSize int // cached from db at init + fm *fontManager.FontManager + font *fontManager.FontEntry + title StringItem + version StringItem + prompt StringItem + note StringItem + message StringItem + downloadProgressChan chan download.DownloadStatus + licenseInfoHeading StringItem + emailDisplay StringItem + licenseKeyDisplay StringItem + hasLicenseInfo bool + lastDisplayUpdate time.Time + baseFontSize int // cached from db at init } // Init performs basic page initialization and creation of static assets. @@ -40,11 +46,12 @@ func (p *Home) Init(ctx *PageCtx) error { } // Get font - fm := fontManager.Get() - font, ok := fm.GetFont("Roboto-Regular") + p.fm = fontManager.Get() + font, ok := p.fm.GetFont("Roboto-Regular") if !ok { return fmt.Errorf("unable to fetch font") } + p.font = font // Create title texture var err error @@ -72,6 +79,9 @@ func (p *Home) Init(ctx *PageCtx) error { return fmt.Errorf("unable to create note string texture: %w", err) } + // Create download channel and message + p.downloadProgressChan = make(chan download.DownloadStatus) + // Create license info textures if data exists if p.Context.CfgPtr.EmailAddress != "" || p.Context.CfgPtr.LicenseKey != "" { p.hasLicenseInfo = true @@ -123,6 +133,25 @@ func (p *Home) Init(ctx *PageCtx) error { } } + audioFileNames := []string{ + "OriginalAudio.wav", + "174Hz.wav", + "396Hz.wav", + "417Hz.wav", + "432Hz.wav", + "639Hz.wav", + "741Hz.wav", + "852Hz.wav", + "963Hz.wav", + "1111Hz.wav", + "14Hz_Binaural.wav", + "40Hz_Binaural.wav", + "AlphaWaves.wav", + "PinealGland.wav", + } + + go download.DownloadAudioFiles(p.downloadProgressChan, audioFileNames, filepath.Join(ctx.CfgPtr.AssetsDir, "audio")) + return nil } @@ -133,6 +162,21 @@ func (p *Home) HandleEvent(event *sdl.Event) error { // Update updates animations and changing textures and other changes using `dt` which represents a time delta in float32. func (p *Home) Update(dt float32) error { + var err error + + select { + case progressUpdate := <-p.downloadProgressChan: + if progressUpdate.Err != nil { + p.message, err = NewStringItem(progressUpdate.Msg, p.font, colors.White) + if err != nil { + return err + } + } else { + p.message, err = NewStringItem(progressUpdate.Err.Error(), p.font, colors.Red) + } + default: + return nil + } return nil } diff --git a/internal/page/message.go b/internal/page/message.go deleted file mode 100644 index 43a81d0..0000000 --- a/internal/page/message.go +++ /dev/null @@ -1,111 +0,0 @@ -package page - -import ( - "fmt" - - "github.com/go-gl/gl/v3.3-core/gl" - "github.com/go-gl/mathgl/mgl32" - "github.com/veandco/go-sdl2/sdl" - "rhemawavetech.com/rhemawave/internal/colors" - "rhemawavetech.com/rhemawave/internal/fontManager" - "rhemawavetech.com/rhemawave/internal/shaderManager" -) - -type MessagePage struct { - Base - message StringItem - instructions StringItem - msgDim mgl32.Vec2 - instDim mgl32.Vec2 - textShader *shaderManager.Shader - msgPos mgl32.Vec2 - instPos mgl32.Vec2 -} - -func (p *MessagePage) Init(ctx *PageCtx) error { - var err error - var ok bool - - if ctx.Msg == "" { - ctx.Msg = "This is an example message because no message was passed" - } - - if ctx.Inst == "" { - ctx.Inst = "Press F1 to exit" - } - - if err = p.Base.Init(ctx); err != nil { - return fmt.Errorf("failed to init Base page: %w", err) - } - - // Fetch and cache shader - sm := shaderManager.Get() - p.textShader, ok = sm.Get("text") - if !ok { - return fmt.Errorf("failed to fetch text shader") - } - - fm := fontManager.Get() - font, ok := fm.GetFont("Roboto-Regular") - if !ok { - return fmt.Errorf("unable to fetch font") - } - - p.message, err = NewStringItem(ctx.Msg, font, colors.Orange) - if err != nil { - return fmt.Errorf("unable to create message string texture: %w", err) - } - - p.instructions, err = NewStringItem(ctx.Inst, font, colors.White) - if err != nil { - return fmt.Errorf("unable to create instructions string texture: %w", err) - } - - baseFontSize := float32(p.Context.CfgPtr.StandardFontSize) - p.message.Scale(float32(48) / baseFontSize) - p.instructions.Scale(float32(28) / baseFontSize) - - // Cache scaled dimensions — never recomputed unless textures are replaced - p.msgDim = p.message.RawDimensions() - p.instDim = p.instructions.RawDimensions() - - p.msgPos = mgl32.Vec2{ - float32(p.Base.ScreenCenterX) - p.msgDim[0]/2, - float32(p.Base.ScreenCenterY) - p.msgDim[1]/2, - } - p.instPos = mgl32.Vec2{ - float32(p.Base.ScreenCenterX) - p.instDim[0]/2, - float32(20), - } - - return nil -} - -func (p *MessagePage) HandleEvent(event *sdl.Event) error { - return nil -} - -func (p *MessagePage) Update(dt float32) error { - return nil -} - -func (p *MessagePage) Render() error { - p.Base.RenderTexture(p.textShader, p.message.ID, p.msgPos, p.msgDim, colors.Orange) - p.Base.RenderTexture(p.textShader, p.instructions.ID, p.instPos, p.instDim, colors.White) - - return nil -} - -func (p *MessagePage) Destroy() error { - gl.DeleteTextures(1, &p.message.ID) - gl.DeleteTextures(1, &p.instructions.ID) - return p.Base.Destroy() -} - -func (p *MessagePage) SetMessage(msg string) error { - return nil -} - -func (p *MessagePage) SetInstructions(inst string) error { - return nil -} diff --git a/internal/page/router.go b/internal/page/router.go index bff8fa8..24c5ebd 100644 --- a/internal/page/router.go +++ b/internal/page/router.go @@ -17,8 +17,8 @@ const ( LICENSE_KEY EMAIL_ADDRESS LOGS - CHECK_UPDATES MESSAGE + DOWNLOADING_ASSETS ) type PageCtx struct { @@ -69,10 +69,10 @@ func (r *Router) NavigateTo(p pageType) { r.ActivePage = &EmailAddressPage{} case LOGS: r.ActivePage = &LogViewerPage{} - case CHECK_UPDATES: - r.ActivePage = &UpdateCheckPage{} case MESSAGE: r.ActivePage = &MessagePage{} + case DOWNLOADING_ASSETS: + r.ActivePage = &DownloadingAssets{} } r.ActivePage.Init(&r.Context) diff --git a/internal/page/updateCheckPage.go b/internal/page/updateCheckPage.go deleted file mode 100644 index 53065d7..0000000 --- a/internal/page/updateCheckPage.go +++ /dev/null @@ -1,188 +0,0 @@ -package page - -import ( - "fmt" - "os/exec" - "strings" - - "github.com/go-gl/gl/v3.3-core/gl" - "github.com/veandco/go-sdl2/sdl" - "rhemawavetech.com/rhemawave/internal/colors" - "rhemawavetech.com/rhemawave/internal/fontManager" - "rhemawavetech.com/rhemawave/internal/shaderManager" -) - -type updateCheckState int - -const ( - updateCheckStateChecking updateCheckState = iota - updateCheckStateDone -) - -type UpdateCheckPage struct { - Base - textShader *shaderManager.Shader - - state updateCheckState - - // "Checking for Updates." / ".." / "..." - checkingTextures [3]StringItem - - // "No updates available" and "Press F3 to return" - doneMessage StringItem - doneHint StringItem - - // Error message if service failed to start - errorMessage StringItem - hasError bool - - pollTimer float32 - dotIndex int - dotTimer float32 -} - -func (p *UpdateCheckPage) Init(ctx *PageCtx) error { - var err error - if err := p.Base.Init(ctx); err != nil { - return fmt.Errorf("failed base page initialization: %w", err) - } - - fm := fontManager.Get() - sm := shaderManager.Get() - - font, ok := fm.GetFont("Roboto-Regular") - if !ok { - return fmt.Errorf("failed to get font") - } - - p.textShader, ok = sm.Get("text") - if !ok { - return fmt.Errorf("failed to get text shader") - } - - // Get scale - baseFontSize := ctx.CfgPtr.StandardFontSize - scale := float32(48) / float32(baseFontSize) - - // Create checking textures with animated dots - dots := []string{"Checking for Updates.", "Checking for Updates..", "Checking for Updates..."} - for i, text := range dots { - item, err := NewStringItem(text, font, colors.White) - if err != nil { - return fmt.Errorf("creating checking texture %d: %w", i, err) - } - item.Scale(scale) - p.checkingTextures[i] = item - } - - // Create done textures - hintScale := float32(32) / float32(baseFontSize) - - p.doneMessage, err = NewStringItem("No updates available", font, colors.White) - if err != nil { - return fmt.Errorf("creating done message: %w", err) - } - p.doneMessage.Scale(scale) - - p.doneHint, err = NewStringItem("Press F3 to return", font, colors.White) - if err != nil { - return fmt.Errorf("creating done hint: %w", err) - } - p.doneHint.Scale(hintScale) - - // Start the update service - p.state = updateCheckStateChecking - if err := exec.Command("systemctl", "start", "nixos-upgrade.service").Run(); err != nil { - p.errorMessage, _ = NewStringItem("Update service unavailable", font, colors.White) - p.errorMessage.Scale(scale) - p.hasError = true - p.state = updateCheckStateDone - } - - return nil -} - -func (p *UpdateCheckPage) Update(dt float32) error { - if p.state != updateCheckStateChecking { - return nil - } - - // Animate dots - p.dotTimer += dt - if p.dotTimer >= 0.5 { - p.dotTimer -= 0.5 - p.dotIndex = (p.dotIndex + 1) % 3 - } - - // Poll service status every 2 seconds - p.pollTimer += dt - if p.pollTimer >= 2.0 { - p.pollTimer -= 2.0 - - output, _ := exec.Command("systemctl", "is-active", "rhemawave-update.service").Output() - status := strings.TrimSpace(string(output)) - if status != "active" && status != "activating" { - p.state = updateCheckStateDone - } - } - - return nil -} - -func (p *UpdateCheckPage) HandleEvent(event *sdl.Event) error { - if p.state != updateCheckStateDone { - return nil - } - - switch e := (*event).(type) { - case *sdl.KeyboardEvent: - if e.Type != sdl.KEYDOWN { - return nil - } - switch e.Keysym.Sym { - case sdl.K_F3, sdl.K_BACKSPACE: - p.Context.NavigateTo(SETTINGS) - } - } - return nil -} - -func (p *UpdateCheckPage) Render() error { - switch p.state { - case updateCheckStateChecking: - item := p.checkingTextures[p.dotIndex] - item.X = p.ScreenCenterX - item.W/2 - item.Y = p.ScreenCenterY - item.H/2 - p.RenderTexture(p.textShader, item.ID, item.Position(), item.RawDimensions(), item.Color) - - case updateCheckStateDone: - var msg *StringItem - if p.hasError { - msg = &p.errorMessage - } else { - msg = &p.doneMessage - } - - msg.X = p.ScreenCenterX - msg.W/2 - msg.Y = p.ScreenCenterY + msg.H/2 - p.RenderTexture(p.textShader, msg.ID, msg.Position(), msg.RawDimensions(), msg.Color) - - p.doneHint.X = p.ScreenCenterX - p.doneHint.W/2 - p.doneHint.Y = msg.Y - p.doneHint.H - 20 - p.RenderTexture(p.textShader, p.doneHint.ID, p.doneHint.Position(), p.doneHint.RawDimensions(), p.doneHint.Color) - } - - return nil -} - -func (p *UpdateCheckPage) Destroy() error { - for i := range p.checkingTextures { - gl.DeleteTextures(1, &p.checkingTextures[i].ID) - } - gl.DeleteTextures(1, &p.doneMessage.ID) - gl.DeleteTextures(1, &p.doneHint.ID) - if p.hasError { - gl.DeleteTextures(1, &p.errorMessage.ID) - } - return nil -}