Downloads are working and the goroutine is reporting updates appropriately.

This commit is contained in:
NeilClack
2026-06-18 21:28:16 -05:00
parent 397d4c9f19
commit 7ecb90b63b
6 changed files with 126 additions and 337 deletions
+38 -23
View File
@@ -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!",
}
}
+29
View File
@@ -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
}
+56 -12
View File
@@ -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
}
-111
View File
@@ -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
}
+3 -3
View File
@@ -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)
-188
View File
@@ -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
}