Downloads are working and the goroutine is reporting updates appropriately.
This commit is contained in:
@@ -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!",
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
@@ -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
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
}
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
}
|
||||
Reference in New Issue
Block a user