diff --git a/internal/download/Download.go b/internal/download/Download.go new file mode 100644 index 0000000..38da210 --- /dev/null +++ b/internal/download/Download.go @@ -0,0 +1,160 @@ +package download + +import ( + "fmt" + "io" + "net/http" + "os" + "path/filepath" +) + +// 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 + Msg string // Status message + Err error // Error, if any +} + +// 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) + + urlBase := "https://files.rhemawavetech.com/audio/" + totalDownloaded := int64(0) + totalSize := int64(0) + + // First, get the total size of all files + for _, filename := range filenames { + url := urlBase + filename + resp, err := http.Head(url) + if err != nil { + progressChan <- DownloadStatus{ + Msg: fmt.Sprintf("Failed to get size for %s: %v", filename, err), + Err: err, + } + 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), + Err: fmt.Errorf("bad status: %s", resp.Status), + } + return + } + totalSize += resp.ContentLength + } + + // Send initial status + progressChan <- DownloadStatus{ + Min: 0, + Max: totalSize, + Cur: 0, + Msg: "Starting download...", + } + + // Download each file + for _, filename := range filenames { + url := urlBase + filename + destPath := filepath.Join(destDir, filename) + + // Create the destination file + out, err := os.Create(destPath) + if err != nil { + 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) + 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) + progressChan <- DownloadStatus{ + Msg: fmt.Sprintf("Failed to download %s: status %s", filename, resp.Status), + Err: fmt.Errorf("bad status: %s", resp.Status), + } + return + } + + // Track progress for this file + fileSize := resp.ContentLength + downloaded := int64(0) + + // 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) + 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) + progressChan <- DownloadStatus{ + Msg: fmt.Sprintf("Failed to write %s: %v", filename, err), + Err: err, + } + return + } + + // Update progress + downloaded += int64(n) + totalDownloaded += int64(n) + progressChan <- DownloadStatus{ + Min: downloaded, + Max: fileSize, + Cur: totalDownloaded, + Msg: fmt.Sprintf("Downloading %s...", filename), + } + } + + out.Close() + + // Send completion for this file + progressChan <- DownloadStatus{ + Min: fileSize, + Max: fileSize, + Cur: totalDownloaded, + Msg: fmt.Sprintf("Finished downloading %s", filename), + } + } + + // Send final status + progressChan <- DownloadStatus{ + Min: totalSize, + Max: totalSize, + Cur: totalDownloaded, + Msg: "All downloads completed!", + } +}