1
1
Files
audio-catalyst/internal/application/usecases/process_audiobooks.go
Dmitriy Fofanov 72a66f1664 Добавить тесты репозитория файловой системы и реализовать функциональность журналирования файлов.
- Реализовать тесты для поиска MP3-файлов и переименования/организации папок книг в репозитории файловой системы.
- Создать FileLogger для записи сообщений в файл с поддержкой различных уровней журналирования и управления размером файлов.
- Разработать репозиторий RuTracker для обработки поиска торрентов, получения метаданных и загрузки торрент-файлов.
- Добавить тесты для нормализации URL в репозиторий RuTracker.
- Реализовать адаптер логгера TUI для отображения логов в терминальном интерфейсе и, при необходимости, для записи логов в базовый логгер.
- Создать менеджер TUI для управления пользовательским интерфейсом приложения, включая главное меню, экран обработки, настройки и отображение результатов.
2025-09-29 20:40:05 +03:00

210 lines
7.5 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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
}
// 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(),
}
}
// 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)
return fmt.Errorf("ошибка сканирования: %w", err)
}
uc.logger.Info("📊 Найдено %d аудиокниг", len(audioBooks))
if len(audioBooks) == 0 {
uc.logger.Warning("⚠️ Аудиокниги не найдены")
return nil
}
// Авторизация в RuTracker
if err := uc.rutrackerRepo.Login(); err != nil {
uc.logger.Error("❌ Ошибка авторизации в RuTracker: %v", err)
return fmt.Errorf("ошибка авторизации: %w", err)
}
uc.logger.Success("✅ Авторизация в RuTracker успешна")
// Обработка каждой аудиокниги
for i, book := range audioBooks {
uc.logger.Info("📋 Обработка книги %d/%d: \"%s\"", i+1, len(audioBooks), book.Title)
if err := uc.processAudioBook(book); err != nil {
uc.logger.Error("❌ Ошибка обработки книги \"%s\": %v", book.Title, err)
continue
}
uc.logger.Success("✅ Книга \"%s\" обработана", book.Title)
}
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
}