Ready to implement a loading bar
This commit is contained in:
+118
-22
@@ -2,7 +2,11 @@ package page
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"math"
|
||||
"net/http"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/go-gl/gl/v3.3-core/gl"
|
||||
@@ -23,6 +27,87 @@ const (
|
||||
defaultProclamationPlayCount int = 1
|
||||
)
|
||||
|
||||
// ProgressUpdate is sent over a channel as bytes are read from the network
|
||||
// during a download. Read/Total let a consumer compute percentage; Done
|
||||
// signals a clean finish; Err signals failure. The channel carrying these
|
||||
// is closed after the final update (whether that's a Done or an Err), so
|
||||
// ranging over it is sufficient to know when the download has finished.
|
||||
type ProgressUpdate struct {
|
||||
Read int64
|
||||
Total int64
|
||||
Done bool
|
||||
Err error
|
||||
}
|
||||
|
||||
// ProgressReader wraps an io.Reader and emits a ProgressUpdate on every
|
||||
// Read() call. It never blocks on the send: if nobody is currently
|
||||
// listening on updates (or the consumer is behind), the update is dropped
|
||||
// rather than stalling the download itself.
|
||||
type ProgressReader struct {
|
||||
Reader io.Reader
|
||||
Total int64
|
||||
read int64
|
||||
updates chan<- ProgressUpdate
|
||||
}
|
||||
|
||||
func (pr *ProgressReader) Read(p []byte) (int, error) {
|
||||
n, err := pr.Reader.Read(p)
|
||||
if n > 0 {
|
||||
pr.read += int64(n)
|
||||
select {
|
||||
case pr.updates <- ProgressUpdate{Read: pr.read, Total: pr.Total}:
|
||||
default:
|
||||
}
|
||||
}
|
||||
return n, err
|
||||
}
|
||||
|
||||
// downloadFile starts the download in its own goroutine and returns
|
||||
// immediately with a channel of progress updates. The caller ranges over
|
||||
// the returned channel to observe progress; the channel is closed when the
|
||||
// download finishes, successfully or not, so the loop ends naturally.
|
||||
func downloadFile(url, destPath string) <-chan ProgressUpdate {
|
||||
updates := make(chan ProgressUpdate, 16)
|
||||
|
||||
go func() {
|
||||
defer close(updates)
|
||||
|
||||
resp, err := http.Get(url)
|
||||
if err != nil {
|
||||
updates <- ProgressUpdate{Err: err}
|
||||
return
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
updates <- ProgressUpdate{Err: fmt.Errorf("bad status: %s", resp.Status)}
|
||||
return
|
||||
}
|
||||
|
||||
out, err := os.Create(destPath)
|
||||
if err != nil {
|
||||
updates <- ProgressUpdate{Err: err}
|
||||
return
|
||||
}
|
||||
defer out.Close()
|
||||
|
||||
pr := &ProgressReader{
|
||||
Reader: resp.Body,
|
||||
Total: resp.ContentLength, // -1 if server didn't send it
|
||||
updates: updates,
|
||||
}
|
||||
|
||||
if _, err := io.Copy(out, pr); err != nil {
|
||||
updates <- ProgressUpdate{Err: err}
|
||||
return
|
||||
}
|
||||
|
||||
updates <- ProgressUpdate{Read: pr.read, Total: pr.Total, Done: true}
|
||||
}()
|
||||
|
||||
return updates
|
||||
}
|
||||
|
||||
type ScrollerProclamation struct {
|
||||
db.Proclamation
|
||||
colorPattern.ColorPattern
|
||||
@@ -260,31 +345,42 @@ func (p *ScrollerPage) Init(ctx *PageCtx) error {
|
||||
}
|
||||
}
|
||||
|
||||
audioFilePath := p.Context.DbPtr.Find
|
||||
var selectedAudio db.AudioFile
|
||||
|
||||
// Download if it doesn't exist
|
||||
fmt.Printf("%s", audioFilePath)
|
||||
if err := p.Context.DbPtr.Where("Selected = ?", true).First(&selectedAudio).Error; err != nil {
|
||||
logger.Error("error querying database for selected audio: %v", err)
|
||||
} else {
|
||||
_, err = os.Stat(selectedAudio.Path)
|
||||
if err != nil { // If err == nil, then the file was most likely not found
|
||||
url := "https://files.radiantwavetech.com/audio/" + filepath.Base(selectedAudio.Path)
|
||||
|
||||
// // Check if the track has previously been downloaded
|
||||
// // TODO: Download audio via a goroutine
|
||||
// // this is a path
|
||||
// // figure out how to get the filename, not the path, from the path.
|
||||
// _, err = os.Stat(audioFilePath)
|
||||
// if err != nil {
|
||||
// filename := filepath.Base(audioFilePath)
|
||||
// url := "https://files.radiantwavetech.com/audio/" + filename
|
||||
// logger.Info("%s not present. Downloading...", entry.DisplayName)
|
||||
// // Download the track if not, display loading bar
|
||||
// if err = netreq.Download(filename, filepath.Join(p.Base.Context.CfgPtr.AssetsDir, "audio"), url); err != nil {
|
||||
// logger.Error("Failed to download file: %v", err)
|
||||
// return
|
||||
// }
|
||||
// logger.Info("Downloaded %s", entry.DisplayName)
|
||||
// }
|
||||
updates := downloadFile(url, selectedAudio.Path)
|
||||
var downloadErr error
|
||||
for u := range updates {
|
||||
if u.Err != nil {
|
||||
downloadErr = u.Err
|
||||
break
|
||||
}
|
||||
if u.Total > 0 {
|
||||
pct := float64(u.Read) / float64(u.Total) * 100
|
||||
fmt.Printf("\rDownloaded %d / %d bytes (%.1f%%)", u.Read, u.Total, pct)
|
||||
} else {
|
||||
fmt.Printf("\rDownloaded %d bytes", u.Read)
|
||||
}
|
||||
if u.Done {
|
||||
fmt.Println()
|
||||
}
|
||||
}
|
||||
if downloadErr != nil {
|
||||
return fmt.Errorf("failed to downoad audio file: %v", downloadErr)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// if err := mixer.Play(audioFile, true, p.Context.CfgPtr); err != nil {
|
||||
// logger.Error("Mixer returned an error: %v", err)
|
||||
// }
|
||||
logger.Info("Playing %s", selectedAudio.Path)
|
||||
if err := mixer.Play(selectedAudio.Path, true, p.Context.CfgPtr); err != nil {
|
||||
logger.Error("Mixer returned an error: %v", err)
|
||||
}
|
||||
|
||||
currentDevice := mixer.CurrentDevice()
|
||||
if currentDevice == "" {
|
||||
|
||||
Reference in New Issue
Block a user