WIP
This commit is contained in:
@@ -0,0 +1,234 @@
|
||||
package services
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
"github.com/fujin/music-agregator/internal/database"
|
||||
"github.com/google/uuid"
|
||||
"github.com/rs/zerolog/log"
|
||||
)
|
||||
|
||||
type ImportResult struct {
|
||||
QueueID string `json:"queue_id"`
|
||||
ArtistName string `json:"artist_name"`
|
||||
AlbumTitle string `json:"album_title"`
|
||||
TargetPath string `json:"target_path"`
|
||||
FilesCopied int `json:"files_copied"`
|
||||
TotalSize int64 `json:"total_size"`
|
||||
Files []string `json:"files"`
|
||||
}
|
||||
|
||||
func ImportCompletedDownload(
|
||||
ctx context.Context,
|
||||
queueID uuid.UUID,
|
||||
basePath string,
|
||||
db *database.DB,
|
||||
torrentService *TorrentService,
|
||||
) (*ImportResult, error) {
|
||||
log.Info().Str("queue_id", queueID.String()).Str("base_path", basePath).Msg("[IMPORT] starting import")
|
||||
|
||||
item, err := db.GetDownloadQueueItem(ctx, queueID)
|
||||
if err != nil {
|
||||
log.Error().Err(err).Str("queue_id", queueID.String()).Msg("[IMPORT] queue item not found")
|
||||
return nil, fmt.Errorf("queue item not found: %w", err)
|
||||
}
|
||||
|
||||
log.Info().Str("title", item.Title).Str("status", item.Status).Msg("[IMPORT] found queue item")
|
||||
|
||||
if item.Status != "completed" && item.Status != "seeding" {
|
||||
log.Error().Str("status", item.Status).Msg("[IMPORT] download not completed")
|
||||
return nil, fmt.Errorf("download not completed, status: %s", item.Status)
|
||||
}
|
||||
|
||||
if item.TorrentHash == nil {
|
||||
log.Error().Msg("[IMPORT] no torrent hash for queue item")
|
||||
return nil, fmt.Errorf("no torrent hash for queue item")
|
||||
}
|
||||
|
||||
log.Info().Str("hash", *item.TorrentHash).Msg("[IMPORT] fetching torrent info")
|
||||
torrent, err := torrentService.GetTorrent(ctx, *item.TorrentHash)
|
||||
if err != nil {
|
||||
log.Error().Err(err).Str("hash", *item.TorrentHash).Msg("[IMPORT] torrent not found")
|
||||
return nil, fmt.Errorf("torrent not found: %w", err)
|
||||
}
|
||||
|
||||
log.Info().Str("name", torrent.Name).Str("save_path", torrent.SavePath).Msg("[IMPORT] torrent info retrieved")
|
||||
|
||||
var artistName, albumTitle string
|
||||
if item.AlbumID != nil {
|
||||
album, err := db.GetAlbumDetailByID(ctx, *item.AlbumID)
|
||||
if err == nil {
|
||||
artistName = album.ArtistName
|
||||
albumTitle = album.Title
|
||||
log.Info().Str("artist", artistName).Str("album", albumTitle).Msg("[IMPORT] resolved from database")
|
||||
}
|
||||
}
|
||||
|
||||
if artistName == "" || albumTitle == "" {
|
||||
parts := strings.SplitN(item.Title, " - ", 2)
|
||||
if len(parts) == 2 {
|
||||
artistName = parts[0]
|
||||
albumTitle = parts[1]
|
||||
} else {
|
||||
artistName = "Unknown Artist"
|
||||
albumTitle = item.Title
|
||||
}
|
||||
log.Info().Str("artist", artistName).Str("album", albumTitle).Msg("[IMPORT] parsed from title")
|
||||
}
|
||||
|
||||
artistName = sanitizePath(artistName)
|
||||
albumTitle = sanitizePath(albumTitle)
|
||||
|
||||
targetDir := filepath.Join(basePath, artistName, albumTitle)
|
||||
log.Info().Str("target_dir", targetDir).Msg("[IMPORT] creating target directory")
|
||||
if err := os.MkdirAll(targetDir, 0755); err != nil {
|
||||
log.Error().Err(err).Str("target_dir", targetDir).Msg("[IMPORT] failed to create target directory")
|
||||
return nil, fmt.Errorf("failed to create target directory: %w", err)
|
||||
}
|
||||
|
||||
sourcePath := filepath.Join(torrent.SavePath, torrent.Name)
|
||||
log.Info().Str("source_path", sourcePath).Msg("[IMPORT] checking source path")
|
||||
|
||||
var filesCopied int
|
||||
var totalSize int64
|
||||
var copiedFiles []string
|
||||
|
||||
sourceInfo, err := os.Stat(sourcePath)
|
||||
if err != nil {
|
||||
log.Error().Err(err).Str("source_path", sourcePath).Msg("[IMPORT] source path not found")
|
||||
return nil, fmt.Errorf("source path not found: %w", err)
|
||||
}
|
||||
|
||||
if sourceInfo.IsDir() {
|
||||
log.Info().Str("source_path", sourcePath).Msg("[IMPORT] source is directory, walking files")
|
||||
err = filepath.Walk(sourcePath, func(path string, info os.FileInfo, err error) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if info.IsDir() {
|
||||
return nil
|
||||
}
|
||||
|
||||
if !isAudioFile(info.Name()) {
|
||||
log.Debug().Str("file", info.Name()).Msg("[IMPORT] skipping non-audio file")
|
||||
return nil
|
||||
}
|
||||
|
||||
relPath, _ := filepath.Rel(sourcePath, path)
|
||||
targetPath := filepath.Join(targetDir, relPath)
|
||||
|
||||
if err := os.MkdirAll(filepath.Dir(targetPath), 0755); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
log.Info().Str("src", path).Str("dst", targetPath).Msg("[IMPORT] copying file")
|
||||
if err := copyFile(path, targetPath); err != nil {
|
||||
log.Warn().Err(err).Str("file", path).Msg("[IMPORT] failed to copy file")
|
||||
return nil
|
||||
}
|
||||
|
||||
filesCopied++
|
||||
totalSize += info.Size()
|
||||
copiedFiles = append(copiedFiles, relPath)
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msg("[IMPORT] failed to copy files")
|
||||
return nil, fmt.Errorf("failed to copy files: %w", err)
|
||||
}
|
||||
} else {
|
||||
if isAudioFile(sourceInfo.Name()) {
|
||||
targetPath := filepath.Join(targetDir, sourceInfo.Name())
|
||||
log.Info().Str("src", sourcePath).Str("dst", targetPath).Msg("[IMPORT] copying single file")
|
||||
if err := copyFile(sourcePath, targetPath); err != nil {
|
||||
log.Error().Err(err).Msg("[IMPORT] failed to copy file")
|
||||
return nil, fmt.Errorf("failed to copy file: %w", err)
|
||||
}
|
||||
filesCopied = 1
|
||||
totalSize = sourceInfo.Size()
|
||||
copiedFiles = append(copiedFiles, sourceInfo.Name())
|
||||
}
|
||||
}
|
||||
|
||||
log.Info().Int("files_copied", filesCopied).Int64("total_size", totalSize).Msg("[IMPORT] file copy completed")
|
||||
|
||||
log.Info().Msg("[IMPORT] updating queue status to imported")
|
||||
if err := db.UpdateDownloadQueueStatus(ctx, queueID, "imported", nil); err != nil {
|
||||
log.Warn().Err(err).Msg("[IMPORT] failed to update queue status to imported")
|
||||
}
|
||||
|
||||
if item.AlbumID != nil {
|
||||
log.Info().Msg("[IMPORT] removing from wanted albums")
|
||||
db.RemoveFromWantedAlbums(ctx, *item.AlbumID)
|
||||
}
|
||||
|
||||
log.Info().
|
||||
Str("artist", artistName).
|
||||
Str("album", albumTitle).
|
||||
Str("target_path", targetDir).
|
||||
Int("files_copied", filesCopied).
|
||||
Msg("[IMPORT] import completed successfully")
|
||||
|
||||
return &ImportResult{
|
||||
QueueID: queueID.String(),
|
||||
ArtistName: artistName,
|
||||
AlbumTitle: albumTitle,
|
||||
TargetPath: targetDir,
|
||||
FilesCopied: filesCopied,
|
||||
TotalSize: totalSize,
|
||||
Files: copiedFiles,
|
||||
}, nil
|
||||
}
|
||||
|
||||
var pathSanitizeRegex = regexp.MustCompile(`[<>:"/\\|?*]`)
|
||||
|
||||
func sanitizePath(s string) string {
|
||||
s = pathSanitizeRegex.ReplaceAllString(s, "_")
|
||||
s = strings.TrimSpace(s)
|
||||
if s == "" {
|
||||
s = "Unknown"
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
func isAudioFile(name string) bool {
|
||||
ext := strings.ToLower(filepath.Ext(name))
|
||||
audioExts := map[string]bool{
|
||||
".flac": true,
|
||||
".mp3": true,
|
||||
".m4a": true,
|
||||
".aac": true,
|
||||
".ogg": true,
|
||||
".opus": true,
|
||||
".wav": true,
|
||||
".wma": true,
|
||||
".alac": true,
|
||||
}
|
||||
return audioExts[ext]
|
||||
}
|
||||
|
||||
func copyFile(src, dst string) error {
|
||||
sourceFile, err := os.Open(src)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer sourceFile.Close()
|
||||
|
||||
destFile, err := os.Create(dst)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer destFile.Close()
|
||||
|
||||
if _, err := io.Copy(destFile, sourceFile); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return destFile.Sync()
|
||||
}
|
||||
Reference in New Issue
Block a user