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 } // 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/" // First, calculate the total size of files that need to be downloaded totalSize := int64(0) filesToDownload := []string{} totalDownloaded := int64(0) 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 { progressChan <- DownloadStatus{ Msg: fmt.Sprintf("Failed to get size for %s: %v", filename, err), Err: err, } return } 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 } 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) } else if err != nil { logger.Error("unknown error: %v", err) } else { logger.Error("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) // 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 } // 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 totalDownloaded += int64(n) progressChan <- DownloadStatus{ Max: totalSize, Cur: totalDownloaded, Msg: fmt.Sprintf("%s", filename), } } out.Close() // Send completion for this file progressChan <- DownloadStatus{ Max: totalSize, Cur: totalDownloaded, Msg: "Finished Downloading", } } // Send final status progressChan <- DownloadStatus{ Max: totalSize, Cur: totalDownloaded, Msg: "All audio downloads completed!", } }