feat(admin): add cache warming from local directory via /warm --from-dir
All checks were successful
continuous-integration/drone/push Build is passing

This commit is contained in:
2025-06-23 21:52:57 +03:00
parent e13557059c
commit 14c54ee737
2 changed files with 100 additions and 3 deletions

8
example.env Normal file
View File

@@ -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

View File

@@ -4,6 +4,9 @@ import (
"context" "context"
"fmt" "fmt"
"log/slog" "log/slog"
"os"
"path/filepath"
"strings"
"time" "time"
"gitea.mrixs.me/Mrixs/yamusic-bot/internal/interfaces" "gitea.mrixs.me/Mrixs/yamusic-bot/internal/interfaces"
@@ -56,7 +59,8 @@ func (h *Handler) handleHelp(ctx context.Context, chatID int64) {
"/help - Показать это сообщение\n" + "/help - Показать это сообщение\n" +
"/stats - Показать статистику бота\n" + "/stats - Показать статистику бота\n" +
"/find <yandex_track_id> - Найти трек в кэше по ID\n" + "/find <yandex_track_id> - Найти трек в кэше по ID\n" +
"/warm <URL> - \"Прогреть\" кэш для альбома или исполнителя (в разработке)" "/warm <URL> - \"Прогреть\" кэш для альбома или исполнителя (в разработке)\n" +
"/warm --from-dir <path> - Прогреть кэш из локальной директории внутри контейнера"
if err := h.telegram.SendMessage(ctx, chatID, helpText); err != nil { if err := h.telegram.SendMessage(ctx, chatID, helpText); err != nil {
slog.Error("Failed to send help message", "error", err, "chat_id", chatID) 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) { func (h *Handler) handleWarm(ctx context.Context, chatID int64, args string) {
if err := h.telegram.SendMessage(ctx, chatID, "Команда /warm находится в разработке."); err != nil { 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) 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)
}()
}