Files
radiantwave-legacy/internal/download/Download.go
T
2026-06-27 12:30:23 -05:00

215 lines
5.6 KiB
Go

package download
import (
"fmt"
"io"
"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 {
Max int64 // Total bytes to download
Cur int64 // Total bytes downloaded
Msg string // Status message
Err error // Error, if any
}
// ensureDir checks if a dire
func ensureDir(path string) error {
info, err := os.Stat(path)
if err == nil {
if !info.IsDir() {
return fmt.Errorf("path exists but is not a directory: %s", path)
}
fmt.Printf("Directory already exists: %s\n", path)
return nil
}
if !os.IsNotExist(err) {
return fmt.Errorf("error checking path: %w", err)
}
// 0760 = rwxrw---- (owner: rwx, group: rw-, others: ---)
if err := os.MkdirAll(path, 0760); err != nil {
return fmt.Errorf("failed to create directory: %w", err)
}
logger.Info("Directory created: %s\n", path)
return nil
}
// DownloadAudioFiles downloads multiple audio files and sends progress updates
// to the provided channel. The channel is closed when all downloads are complete.
func DownloadAudioFiles(progressChan chan<- DownloadStatus, filenames []string, destDir string) {
defer close(progressChan)
logger.Info("Starting downloads")
if err := ensureDir(destDir); err != nil {
logger.Error("failed to ensureDir %s %v", destDir, err)
progressChan <- DownloadStatus{
Msg: fmt.Sprintf("Failed to ensure directory exists: %v", err),
Err: err,
}
}
urlBase := "https://files.radiantwavetech.com/audio/"
// First, calculate the total size of files that need to be downloaded
totalSize := int64(0)
filesToDownload := []string{}
totalDownloaded := int64(0)
for _, filename := range filenames {
logger.Debug("Downloading %s", filename)
destPath := filepath.Join(destDir, filename)
// Check if the file already exists
url := urlBase + filename
resp, err := http.Head(url)
if err != nil {
logger.Warning("Failed to get size for %s: %v", filename, err)
progressChan <- DownloadStatus{
Msg: fmt.Sprintf("Failed to get size for %s: %v", filename, err),
Err: err,
}
return
}
if resp.StatusCode != http.StatusOK {
logger.Error("Failed to get size for %s: status %s", filename, resp.Status)
progressChan <- DownloadStatus{
Msg: fmt.Sprintf("Failed to get size for %s: status %s", filename, resp.Status),
Err: fmt.Errorf("bad status: %s", resp.Status),
}
return
}
fileStat, err := os.Stat(destPath)
// If the file is incomplete, or does not exist
if os.IsNotExist(err) || fileStat.Size() != resp.ContentLength {
// log a warning if the file is not complete
if !os.IsNotExist(err) {
logger.Warning("%s corrupt. Adding to download queue.", filename)
} else {
logger.Debug("%s not found. Adding to download queue.", filename)
}
// Append it to the list to download
filesToDownload = append(filesToDownload, filename)
} else if err != nil {
logger.Error("unknown error: %v", err)
} else {
logger.Warning("File %s exists, skipping...", filename)
totalDownloaded += fileStat.Size()
}
// Calculate the total of ALL files to give the illusion of
// "picking up where you left off" to the user
totalSize += resp.ContentLength
resp.Body.Close()
}
// Send initial status
progressChan <- DownloadStatus{
Max: totalSize,
Cur: 0,
Msg: "Starting download...",
}
// Download each file that doesn't exist
for _, filename := range filesToDownload {
url := urlBase + filename
destPath := filepath.Join(destDir, filename)
// Ensure the destination directory exists
// Create the destination file
out, err := os.Create(destPath)
if err != nil {
logger.Error("Failed to create file %s: %v", destPath, err)
progressChan <- DownloadStatus{
Msg: fmt.Sprintf("Failed to create file %s: %v", destPath, err),
Err: err,
}
return
}
// Get the file
resp, err := http.Get(url)
if err != nil {
out.Close()
os.Remove(destPath)
logger.Error("Failed to download %s: %v", filename, err)
progressChan <- DownloadStatus{
Msg: fmt.Sprintf("Failed to download %s: %v", filename, err),
Err: err,
}
return
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
out.Close()
os.Remove(destPath)
logger.Error("Failed to download %s: status %s", filename, resp.Status)
progressChan <- DownloadStatus{
Msg: fmt.Sprintf("Failed to download %s: status %s", filename, resp.Status),
Err: fmt.Errorf("bad status: %s", resp.Status),
}
return
}
// Use a buffered reader for efficiency
buf := make([]byte, 8192)
for {
n, err := resp.Body.Read(buf)
if err != nil && err != io.EOF {
out.Close()
os.Remove(destPath)
logger.Error("Failed to read %s: %v", filename, err)
progressChan <- DownloadStatus{
Msg: fmt.Sprintf("Failed to read %s: %v", filename, err),
Err: err,
}
return
}
if n == 0 {
break
}
// Write to file
if _, err := out.Write(buf[:n]); err != nil {
out.Close()
os.Remove(destPath)
logger.Error("Failed to write %s: %v", filename, err)
progressChan <- DownloadStatus{
Msg: fmt.Sprintf("Failed to write %s: %v", filename, err),
Err: err,
}
return
}
// Update progress
totalDownloaded += int64(n)
progressChan <- DownloadStatus{
Max: totalSize,
Cur: totalDownloaded,
Msg: fmt.Sprintf("%s", filename),
}
}
out.Close()
}
// Send final status
progressChan <- DownloadStatus{
Max: totalSize,
Cur: totalDownloaded,
Msg: "All audio downloads completed!",
}
logger.Info("All audio downloads complete!")
}