From 14c54ee73733bac7e6136ec09bfa64d343892a87 Mon Sep 17 00:00:00 2001 From: Vladimir Zagainov Date: Mon, 23 Jun 2025 21:52:57 +0300 Subject: [PATCH] feat(admin): add cache warming from local directory via /warm --from-dir --- example.env | 8 ++++ internal/admin/handler.go | 95 +++++++++++++++++++++++++++++++++++++-- 2 files changed, 100 insertions(+), 3 deletions(-) create mode 100644 example.env diff --git a/example.env b/example.env new file mode 100644 index 0000000..cc515fa --- /dev/null +++ b/example.env @@ -0,0 +1,8 @@ +TELEGRAM_BOT_TOKEN=aaa:bbb +TELEGRAM_ADMIN_IDS=ccc +TELEGRAM_CACHE_CHAT_ID=ddd +YANDEX_MUSIC_TOKEN=eee +DATABASE_PATH="/data/bot.db" +LOG_LEVEL="info" +PROCESSOR_WORKERS=4 +YANDEX_API_RATE_LIMIT=5 diff --git a/internal/admin/handler.go b/internal/admin/handler.go index a52bf82..54d004f 100644 --- a/internal/admin/handler.go +++ b/internal/admin/handler.go @@ -4,6 +4,9 @@ import ( "context" "fmt" "log/slog" + "os" + "path/filepath" + "strings" "time" "gitea.mrixs.me/Mrixs/yamusic-bot/internal/interfaces" @@ -56,7 +59,8 @@ func (h *Handler) handleHelp(ctx context.Context, chatID int64) { "/help - Показать это сообщение\n" + "/stats - Показать статистику бота\n" + "/find - Найти трек в кэше по ID\n" + - "/warm - \"Прогреть\" кэш для альбома или исполнителя (в разработке)" + "/warm - \"Прогреть\" кэш для альбома или исполнителя (в разработке)\n" + + "/warm --from-dir - Прогреть кэш из локальной директории внутри контейнера" if err := h.telegram.SendMessage(ctx, chatID, helpText); err != nil { slog.Error("Failed to send help message", "error", err, "chat_id", chatID) @@ -110,8 +114,93 @@ func (h *Handler) handleFind(ctx context.Context, chatID int64, trackID string) } } -func (h *Handler) handleWarm(ctx context.Context, chatID int64, url string) { - if err := h.telegram.SendMessage(ctx, chatID, "Команда /warm находится в разработке."); err != nil { +func (h *Handler) handleWarm(ctx context.Context, chatID int64, args string) { + const fromDirPrefix = "--from-dir " + if strings.HasPrefix(args, fromDirPrefix) { + dirPath := strings.TrimPrefix(args, fromDirPrefix) + h.handleWarmFromDir(ctx, chatID, dirPath) + return + } + + // Здесь будет логика для прогрева по URL + if err := h.telegram.SendMessage(ctx, chatID, "Прогрев по URL находится в разработке."); err != nil { slog.Error("Failed to send 'warm in development' message", "error", err, "chat_id", chatID) } } + +// handleWarmFromDir запускает фоновую задачу прогрева кэша из локальной директории. +func (h *Handler) handleWarmFromDir(ctx context.Context, chatID int64, dirPath string) { + msg := fmt.Sprintf("Принято в обработку. Начинаю прогрев кэша из директории: `%s`", dirPath) + if err := h.telegram.SendMessage(ctx, chatID, msg); err != nil { + slog.Error("Failed to send 'warm from dir started' message", "error", err, "chat_id", chatID) + return + } + + go func() { + slog.Info("Starting cache warming from directory", "path", dirPath) + files, err := os.ReadDir(dirPath) + if err != nil { + slog.Error("Failed to read directory for warming", "path", dirPath, "error", err) + errMsg := fmt.Sprintf("Ошибка: не удалось прочитать директорию `%s`. Убедитесь, что она существует и доступна.", dirPath) + _ = h.telegram.SendMessage(context.Background(), chatID, errMsg) + return + } + + var addedCount, skippedCount, errorCount int + totalFiles := len(files) + + for i, file := range files { + if file.IsDir() || !strings.HasSuffix(file.Name(), ".mp3") { + continue + } + + trackID := strings.TrimSuffix(file.Name(), ".mp3") + fullPath := filepath.Join(dirPath, file.Name()) + + // 1. Проверяем, есть ли трек в кэше + _, err := h.storage.Get(ctx, trackID) + if err == nil { + slog.Debug("Skipping already cached track", "track_id", trackID) + skippedCount++ + continue + } + + // 2. Загружаем в Telegram + // Поскольку метатеги уже вшиты, для отображения в кэш-канале можно использовать простые title/performer + slog.Debug("Uploading track to cache channel", "track_id", trackID, "path", fullPath) + fileID, err := h.telegram.SendAudioToCacheChannel(ctx, fullPath, trackID, "Pre-cached") + if err != nil { + slog.Error("Failed to upload pre-cached file", "track_id", trackID, "error", err) + errorCount++ + continue + } + + // 3. Сохраняем в БД + err = h.storage.Set(ctx, trackID, fileID) + if err != nil { + slog.Error("Failed to save pre-cached file to storage", "track_id", trackID, "error", err) + errorCount++ + continue + } + + addedCount++ + slog.Info("Successfully cached track from local file", "track_id", trackID, "file_id", fileID) + + // Опционально: отправляем прогресс каждые N файлов + if (i+1)%1000 == 0 { + progressMsg := fmt.Sprintf("Прогресс: обработано %d из %d файлов...", i+1, totalFiles) + _ = h.telegram.SendMessage(context.Background(), chatID, progressMsg) + } + } + + finalMsg := fmt.Sprintf( + "✅ Прогрев кэша из директории `%s` завершен.\n\n"+ + "Новых треков добавлено: %d\n"+ + "Треков пропущено (уже в кэше): %d\n"+ + "Ошибок при обработке: %d", + dirPath, addedCount, skippedCount, errorCount, + ) + _ = h.telegram.SendMessage(context.Background(), chatID, finalMsg) + slog.Info("Finished cache warming from directory", "path", dirPath, "added", addedCount, "skipped", skippedCount, "errors", errorCount) + }() +}