implemennted downloader

This commit is contained in:
2025-06-23 09:45:42 +03:00
parent 4db033dc7d
commit 610fb3da11
4 changed files with 170 additions and 0 deletions

103
internal/processor/track.go Normal file
View File

@@ -0,0 +1,103 @@
package processor
import (
"context"
"errors"
"fmt"
"log/slog"
"os"
"gitea.mrixs.me/Mrixs/yamusic-bot/internal/interfaces"
"gitea.mrixs.me/Mrixs/yamusic-bot/internal/model"
"gitea.mrixs.me/Mrixs/yamusic-bot/internal/storage"
)
// TrackProcessor инкапсулирует всю логику обработки одного трека.
type TrackProcessor struct {
storage interfaces.TrackStorage
yandex interfaces.YandexMusicClient
downloader interfaces.FileDownloader
tagger interfaces.Tagger
telegram interfaces.TelegramClient
}
// NewTrackProcessor создает новый процессор.
func NewTrackProcessor(
storage interfaces.TrackStorage,
yandex interfaces.YandexMusicClient,
downloader interfaces.FileDownloader,
tagger interfaces.Tagger,
telegram interfaces.TelegramClient,
) *TrackProcessor {
return &TrackProcessor{
storage: storage,
yandex: yandex,
downloader: downloader,
tagger: tagger,
telegram: telegram,
}
}
// Process получает информацию о треке, обрабатывает его (если нужно) и возвращает Telegram File ID.
func (p *TrackProcessor) Process(ctx context.Context, trackInfo *model.TrackInfo) (string, error) {
const op = "processor.TrackProcessor.Process"
// 1. Проверяем кэш в БД
fileID, err := p.storage.Get(ctx, trackInfo.YandexTrackID)
if err == nil {
slog.Info("Cache hit", "track_id", trackInfo.YandexTrackID, "title", trackInfo.Title)
return fileID, nil
}
// Если ошибка - это не "не найдено", то это проблема
if !errors.Is(err, storage.ErrNotFound) {
return "", fmt.Errorf("%s: failed to get from storage: %w", op, err)
}
// 2. Cache Miss: начинаем полный цикл обработки
slog.Info("Cache miss, processing track", "track_id", trackInfo.YandexTrackID, "title", trackInfo.Title)
// 2a. Получаем URL для скачивания
downloadURL, err := p.yandex.GetDownloadURL(ctx, trackInfo.YandexTrackID)
if err != nil {
return "", fmt.Errorf("%s: failed to get download url: %w", op, err)
}
trackInfo.DownloadURL = downloadURL
// 2b. Скачиваем аудиофайл и обложку
audioPath, err := p.downloader.Download(ctx, trackInfo.DownloadURL)
if err != nil {
return "", fmt.Errorf("%s: failed to download audio: %w", op, err)
}
defer os.Remove(audioPath) // Гарантированное удаление временного аудиофайла
var coverPath string
if trackInfo.CoverURL != "" {
coverPath, err = p.downloader.Download(ctx, trackInfo.CoverURL)
if err != nil {
slog.Warn("Failed to download cover, proceeding without it", "url", trackInfo.CoverURL, "error", err)
} else {
defer os.Remove(coverPath) // Гарантированное удаление временной обложки
}
}
// 2c. Записываем теги
if err := p.tagger.WriteTags(audioPath, coverPath, trackInfo); err != nil {
// Не фатальная ошибка, просто логируем и продолжаем
slog.Warn("Failed to write tags, proceeding without them", "track_id", trackInfo.YandexTrackID, "error", err)
}
// 2d. Загружаем в Telegram (в кэш-канал)
newFileID, err := p.telegram.SendAudioToCacheChannel(ctx, audioPath, trackInfo.Title, trackInfo.Artist)
if err != nil {
return "", fmt.Errorf("%s: failed to upload to telegram: %w", op, err)
}
// 2e. Сохраняем в нашу БД
if err := p.storage.Set(ctx, trackInfo.YandexTrackID, newFileID); err != nil {
// Не фатальная ошибка для пользователя, но критичная для нас. Логируем как ошибку.
slog.Error("Failed to save track to cache storage", "track_id", trackInfo.YandexTrackID, "error", err)
}
return newFileID, nil
}