add workflow to build
This commit is contained in:
@@ -0,0 +1,142 @@
|
||||
package preview
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/user/ytdlp-navidrome/internal/config"
|
||||
"github.com/user/ytdlp-navidrome/internal/ytdlp"
|
||||
)
|
||||
|
||||
// State represents the per-user dialog state.
|
||||
type State int
|
||||
|
||||
const (
|
||||
StateIdle State = iota
|
||||
StateSearchResults
|
||||
StatePreviewSent
|
||||
)
|
||||
|
||||
// UserSession holds the current dialog state for a Matrix user.
|
||||
type UserSession struct {
|
||||
State State
|
||||
Query string
|
||||
Results []ytdlp.SearchResult
|
||||
VideoID string
|
||||
PreviewPath string
|
||||
Timestamp time.Time
|
||||
}
|
||||
|
||||
// Manager handles preview lifecycle and user sessions.
|
||||
type Manager struct {
|
||||
mu sync.RWMutex
|
||||
sessions map[string]*UserSession
|
||||
cfg *config.Config
|
||||
}
|
||||
|
||||
func NewManager(cfg *config.Config) *Manager {
|
||||
return &Manager{
|
||||
sessions: make(map[string]*UserSession),
|
||||
cfg: cfg,
|
||||
}
|
||||
}
|
||||
|
||||
// GetSession returns the current session for a user.
|
||||
func (m *Manager) GetSession(userID string) *UserSession {
|
||||
m.mu.RLock()
|
||||
defer m.mu.RUnlock()
|
||||
return m.sessions[userID]
|
||||
}
|
||||
|
||||
// SetSearchResults stores search results for a user.
|
||||
func (m *Manager) SetSearchResults(userID, query string, results []ytdlp.SearchResult) {
|
||||
m.mu.Lock()
|
||||
defer m.mu.Unlock()
|
||||
m.sessions[userID] = &UserSession{
|
||||
State: StateSearchResults,
|
||||
Query: query,
|
||||
Results: results,
|
||||
Timestamp: time.Now(),
|
||||
}
|
||||
}
|
||||
|
||||
// SetPreviewSent records that a preview was sent for a user.
|
||||
func (m *Manager) SetPreviewSent(userID, videoID, previewPath string) {
|
||||
m.mu.Lock()
|
||||
defer m.mu.Unlock()
|
||||
m.sessions[userID] = &UserSession{
|
||||
State: StatePreviewSent,
|
||||
VideoID: videoID,
|
||||
PreviewPath: previewPath,
|
||||
Timestamp: time.Now(),
|
||||
}
|
||||
}
|
||||
|
||||
// ResetSession clears a user session (e.g., on cancel or completion).
|
||||
func (m *Manager) ResetSession(userID string) {
|
||||
m.mu.Lock()
|
||||
defer m.mu.Unlock()
|
||||
delete(m.sessions, userID)
|
||||
}
|
||||
|
||||
// IsExpired checks if a session has exceeded the preview TTL.
|
||||
func (s *UserSession) IsExpired(ttl time.Duration) bool {
|
||||
return time.Since(s.Timestamp) > ttl
|
||||
}
|
||||
|
||||
// SanitizeFilename replaces forbidden characters with underscores.
|
||||
func SanitizeFilename(name string) string {
|
||||
forbidden := []string{"/", "\\", ":", "*", "?", "\"", "<", ">", "|"}
|
||||
result := name
|
||||
for _, ch := range forbidden {
|
||||
result = strings.ReplaceAll(result, ch, "_")
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
// BuildFinalFilename constructs the final filename: "Artist - Title.ext"
|
||||
func BuildFinalFilename(result *ytdlp.SearchResult, ext string) string {
|
||||
artist := result.Channel
|
||||
if artist == "" {
|
||||
artist = "unknown"
|
||||
}
|
||||
title := result.Title
|
||||
if title == "" {
|
||||
title = result.ID
|
||||
}
|
||||
return SanitizeFilename(fmt.Sprintf("%s - %s.%s", artist, title, ext))
|
||||
}
|
||||
|
||||
// CleanupOldPreviews removes expired preview files from tmp dir.
|
||||
func (m *Manager) CleanupOldPreviews(ctx context.Context) {
|
||||
ticker := time.NewTicker(5 * time.Minute)
|
||||
defer ticker.Stop()
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return
|
||||
case <-ticker.C:
|
||||
m.mu.Lock()
|
||||
for userID, session := range m.sessions {
|
||||
if session.State == StatePreviewSent && session.IsExpired(m.cfg.PreviewTTL) {
|
||||
if session.PreviewPath != "" {
|
||||
os.Remove(session.PreviewPath)
|
||||
}
|
||||
delete(m.sessions, userID)
|
||||
}
|
||||
}
|
||||
m.mu.Unlock()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// PreviewFilePath returns the expected preview file path for a video ID.
|
||||
func PreviewFilePath(tmpDir, videoID, ext string) string {
|
||||
return filepath.Join(tmpDir, fmt.Sprintf("%s.%s", videoID, ext))
|
||||
}
|
||||
Reference in New Issue
Block a user