240 lines
8.8 KiB
Go
240 lines
8.8 KiB
Go
package usecases
|
||
|
||
import (
|
||
"fmt"
|
||
"path/filepath"
|
||
|
||
"audio-catalyst/internal/domain/entities"
|
||
"audio-catalyst/internal/domain/repositories"
|
||
"audio-catalyst/internal/domain/services"
|
||
)
|
||
|
||
// ProcessAudioBooksUseCase обрабатывает аудиокниги
|
||
type ProcessAudioBooksUseCase struct {
|
||
audioBookRepo repositories.AudioBookRepository
|
||
rutrackerRepo repositories.RuTrackerRepository
|
||
logger repositories.Logger
|
||
audioBookSvc *services.AudioBookService
|
||
metadataSvc *services.MetadataService
|
||
// Репортер прогресса
|
||
progress func(entities.ProcessingStatus)
|
||
}
|
||
|
||
// NewProcessAudioBooksUseCase создает новый use case
|
||
func NewProcessAudioBooksUseCase(
|
||
audioBookRepo repositories.AudioBookRepository,
|
||
rutrackerRepo repositories.RuTrackerRepository,
|
||
logger repositories.Logger,
|
||
) *ProcessAudioBooksUseCase {
|
||
return &ProcessAudioBooksUseCase{
|
||
audioBookRepo: audioBookRepo,
|
||
rutrackerRepo: rutrackerRepo,
|
||
logger: logger,
|
||
audioBookSvc: services.NewAudioBookService(),
|
||
metadataSvc: services.NewMetadataService(),
|
||
}
|
||
}
|
||
|
||
// SetProgressReporter задает функцию репортинга прогресса
|
||
func (uc *ProcessAudioBooksUseCase) SetProgressReporter(f func(entities.ProcessingStatus)) {
|
||
uc.progress = f
|
||
}
|
||
|
||
// Execute выполняет обработку аудиокниг
|
||
func (uc *ProcessAudioBooksUseCase) Execute(config *entities.Config) error {
|
||
uc.logger.Info("🚀 Начало процесса обработки аудиокниг")
|
||
uc.logger.Info("📁 Исходная директория: %s", config.Scanner.SourceDirectory)
|
||
|
||
// Сканирование папок с аудиокнигами
|
||
audioBooks, err := uc.audioBookRepo.ScanDirectory(config.Scanner.SourceDirectory)
|
||
if err != nil {
|
||
uc.logger.Error("❌ Критическая ошибка сканирования: %v", err)
|
||
if uc.progress != nil {
|
||
uc.progress(entities.ProcessingStatus{Current: 0, Total: 0, Status: "Ошибка сканирования", Error: err})
|
||
}
|
||
return fmt.Errorf("ошибка сканирования: %w", err)
|
||
}
|
||
|
||
total := len(audioBooks)
|
||
uc.logger.Info("📊 Найдено %d аудиокниг", total)
|
||
|
||
if total == 0 {
|
||
uc.logger.Warning("⚠️ Аудиокниги не найдены")
|
||
if uc.progress != nil {
|
||
uc.progress(entities.ProcessingStatus{Current: 0, Total: 0, Status: "Книги не найдены"})
|
||
}
|
||
return nil
|
||
}
|
||
|
||
// Сообщаем общее количество до начала обработки
|
||
if uc.progress != nil {
|
||
uc.progress(entities.ProcessingStatus{Current: 0, Total: total, Status: "Начинаем обработку"})
|
||
}
|
||
|
||
// Авторизация в RuTracker
|
||
if err := uc.rutrackerRepo.Login(); err != nil {
|
||
uc.logger.Error("❌ Ошибка авторизации в RuTracker: %v", err)
|
||
if uc.progress != nil {
|
||
uc.progress(entities.ProcessingStatus{Current: 0, Total: total, Status: "Ошибка авторизации", Error: err})
|
||
}
|
||
return fmt.Errorf("ошибка авторизации: %w", err)
|
||
}
|
||
uc.logger.Success("✅ Авторизация в RuTracker успешна")
|
||
|
||
// Обработка каждой аудиокниги
|
||
for i, book := range audioBooks {
|
||
uc.logger.Info("📋 Обработка книги %d/%d: \"%s\"", i+1, total, book.Title)
|
||
|
||
if err := uc.processAudioBook(book); err != nil {
|
||
uc.logger.Error("❌ Ошибка обработки книги \"%s\": %v", book.Title, err)
|
||
}
|
||
|
||
// Увеличиваем прогресс после завершения обработки книги (успешно или нет)
|
||
if uc.progress != nil {
|
||
uc.progress(entities.ProcessingStatus{Current: i + 1, Total: total, Status: fmt.Sprintf("Готово %d/%d", i+1, total)})
|
||
}
|
||
|
||
uc.logger.Success("✅ Книга \"%s\" обработана", book.Title)
|
||
}
|
||
|
||
if uc.progress != nil {
|
||
uc.progress(entities.ProcessingStatus{Current: total, Total: total, Status: "Обработка завершена"})
|
||
}
|
||
|
||
uc.logger.Success("🎉 Обработка завершена успешно!")
|
||
return nil
|
||
}
|
||
|
||
// processAudioBook обрабатывает одну аудиокнигу
|
||
func (uc *ProcessAudioBooksUseCase) processAudioBook(book entities.AudioBook) error {
|
||
// Поиск торрентов
|
||
torrents, err := uc.rutrackerRepo.Search(book.Title, 1)
|
||
if err != nil {
|
||
uc.logger.Warning("⚠️ Ошибка поиска торрентов для \"%s\": %v", book.Title, err)
|
||
return nil // переходим к следующей книге
|
||
}
|
||
|
||
if len(torrents) == 0 {
|
||
uc.logger.Warning("⚠️ Не найдено на RuTracker: \"%s\" — пропускаем", book.Title)
|
||
return nil // переходим к следующей книге
|
||
}
|
||
|
||
// Используем первый торрент
|
||
bestTorrent := torrents[0]
|
||
uc.logger.Info("📦 Выбран торрент: %s", bestTorrent.Title)
|
||
|
||
// Получаем метаданные
|
||
rutrackerResult, err := uc.rutrackerRepo.GetTopicMetadata(bestTorrent.ID)
|
||
if err != nil {
|
||
uc.logger.Warning("⚠️ Ошибка получения метаданных: %v", err)
|
||
return nil // переходим к следующей книге
|
||
}
|
||
|
||
if rutrackerResult.CoverURL != "" {
|
||
uc.logger.Info("🖼️ Найдена обложка: %s", rutrackerResult.CoverURL)
|
||
} else {
|
||
uc.logger.Debug("Обложка на странице не найдена")
|
||
}
|
||
|
||
// Создаем метаданные
|
||
metadata := uc.createMetadataFromRuTracker(book, rutrackerResult)
|
||
|
||
// Переименуем папку книги под Subtitle, если он есть
|
||
bookPath := book.Path
|
||
if metadata.Subtitle != "" && metadata.Subtitle != filepath.Base(book.Path) {
|
||
if newPath, err := uc.audioBookRepo.RenameBookFolder(book.Path, metadata.Subtitle); err != nil {
|
||
uc.logger.Warning("⚠️ Не удалось переименовать папку: %v", err)
|
||
} else {
|
||
uc.logger.Success("📁 Папка переименована: %s", filepath.Base(newPath))
|
||
bookPath = newPath
|
||
}
|
||
}
|
||
|
||
// Если есть CoverURL — пробуем скачать обложку
|
||
if rutrackerResult.CoverURL != "" {
|
||
if err := uc.audioBookRepo.DownloadCover(rutrackerResult.CoverURL, bookPath); err != nil {
|
||
uc.logger.Warning("⚠️ Не удалось загрузить обложку: %v", err)
|
||
} else {
|
||
uc.logger.Success("🖼️ Обложка загружена")
|
||
}
|
||
}
|
||
|
||
// Сохраняем метаданные
|
||
if err := uc.audioBookRepo.SaveMetadata(bookPath, metadata); err != nil {
|
||
return fmt.Errorf("ошибка сохранения метаданных: %w", err)
|
||
}
|
||
|
||
uc.logger.Success("💾 Метаданные сохранены")
|
||
|
||
// Организация в библиотеке organized
|
||
if len(metadata.Authors) > 0 {
|
||
author := metadata.Authors[0]
|
||
targetRoot := "./organized"
|
||
if newPath, err := uc.audioBookRepo.OrganizeBookFolder(bookPath, author, targetRoot); err != nil {
|
||
uc.logger.Warning("⚠️ Не удалось организовать папку: %v", err)
|
||
} else {
|
||
uc.logger.Success("📚 Папка перемещена в библиотеку: %s", newPath)
|
||
}
|
||
}
|
||
return nil
|
||
}
|
||
|
||
// createBasicMetadata создает базовые метаданные
|
||
func (uc *ProcessAudioBooksUseCase) createBasicMetadata(book entities.AudioBook) error {
|
||
metadata := &entities.AudioBookMetadata{
|
||
Tags: []string{},
|
||
Chapters: uc.audioBookSvc.CreateChapters(book.MP3Files),
|
||
Title: book.Title,
|
||
Subtitle: book.Title,
|
||
Authors: []string{},
|
||
Narrators: []string{},
|
||
Series: []string{},
|
||
Genres: []string{},
|
||
Description: book.Description,
|
||
Language: "ru",
|
||
Explicit: false,
|
||
Abridged: false,
|
||
}
|
||
|
||
return uc.audioBookRepo.SaveMetadata(book.Path, metadata)
|
||
}
|
||
|
||
// createMetadataFromRuTracker создает метаданные на основе данных RuTracker
|
||
func (uc *ProcessAudioBooksUseCase) createMetadataFromRuTracker(book entities.AudioBook, rutrackerResult *entities.RuTrackerResult) *entities.AudioBookMetadata {
|
||
metadata := &entities.AudioBookMetadata{
|
||
Tags: []string{},
|
||
Chapters: uc.audioBookSvc.CreateChapters(book.MP3Files),
|
||
Title: rutrackerResult.Title,
|
||
Subtitle: rutrackerResult.Subtitle,
|
||
Authors: rutrackerResult.Authors,
|
||
Narrators: rutrackerResult.Narrators,
|
||
Series: rutrackerResult.Series,
|
||
Genres: rutrackerResult.Genres,
|
||
Description: rutrackerResult.Description,
|
||
Language: "ru",
|
||
Explicit: false,
|
||
Abridged: false,
|
||
}
|
||
|
||
if rutrackerResult.Year != nil {
|
||
metadata.PublishedYear = rutrackerResult.Year
|
||
}
|
||
|
||
if rutrackerResult.Publisher != nil {
|
||
metadata.Publisher = rutrackerResult.Publisher
|
||
}
|
||
|
||
if metadata.Title == "" {
|
||
metadata.Title = book.Title
|
||
}
|
||
if metadata.Subtitle == "" {
|
||
metadata.Subtitle = metadata.Title
|
||
}
|
||
|
||
if metadata.Description == "" && book.Description != "" {
|
||
metadata.Description = book.Description
|
||
}
|
||
|
||
return metadata
|
||
}
|