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!") }