Добавить тесты репозитория файловой системы и реализовать функциональность журналирования файлов.
- Реализовать тесты для поиска MP3-файлов и переименования/организации папок книг в репозитории файловой системы. - Создать FileLogger для записи сообщений в файл с поддержкой различных уровней журналирования и управления размером файлов. - Разработать репозиторий RuTracker для обработки поиска торрентов, получения метаданных и загрузки торрент-файлов. - Добавить тесты для нормализации URL в репозиторий RuTracker. - Реализовать адаптер логгера TUI для отображения логов в терминальном интерфейсе и, при необходимости, для записи логов в базовый логгер. - Создать менеджер TUI для управления пользовательским интерфейсом приложения, включая главное меню, экран обработки, настройки и отображение результатов.
This commit is contained in:
209
internal/application/usecases/process_audiobooks.go
Normal file
209
internal/application/usecases/process_audiobooks.go
Normal file
@@ -0,0 +1,209 @@
|
||||
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
|
||||
}
|
||||
Reference in New Issue
Block a user