Files
silo-server/internal/api/handlers/playback_missing.go
T

124 lines
2.9 KiB
Go

package handlers
import (
"context"
"errors"
"fmt"
"log/slog"
"net/http"
"os"
"time"
evt "github.com/Silo-Server/silo-server/internal/events"
"github.com/Silo-Server/silo-server/internal/models"
"github.com/Silo-Server/silo-server/internal/playback"
"github.com/Silo-Server/silo-server/internal/scanner"
)
// MissingFileMarker marks media files as missing when playback discovers they
// no longer exist on disk.
type MissingFileMarker interface {
MarkMissing(ctx context.Context, id int, since time.Time) error
}
type playbackFilePreflightError struct {
notFound bool
err error
}
func (e *playbackFilePreflightError) Error() string {
if e == nil {
return ""
}
if e.notFound {
return "source media file is missing"
}
if e.err != nil {
return fmt.Sprintf("failed to access media file: %v", e.err)
}
return "failed to access media file"
}
func (e *playbackFilePreflightError) Unwrap() error {
if e == nil {
return nil
}
return e.err
}
func isPlaybackFileMissing(err error) bool {
var target *playbackFilePreflightError
return errors.As(err, &target) && target.notFound
}
func isPlaybackFileLookupMissing(err error) bool {
return errors.Is(err, scanner.ErrFileNotFound) || errors.Is(err, playback.ErrFileNotFound)
}
func writePlaybackFilePreflightError(w http.ResponseWriter, err error) {
if isPlaybackFileMissing(err) {
writeError(w, http.StatusNotFound, "not_found", "Source media file is missing")
return
}
writeError(w, http.StatusInternalServerError, "internal_error", "Failed to access source media file")
}
const playbackSessionNotFoundErrorCode = "playback_session_not_found"
func writePlaybackSessionNotFound(w http.ResponseWriter) {
writeError(w, http.StatusNotFound, playbackSessionNotFoundErrorCode, "Playback session not found")
}
func preflightPlaybackFile(
ctx context.Context,
file *models.MediaFile,
marker MissingFileMarker,
eventsHub *evt.Hub,
) error {
if file == nil {
return &playbackFilePreflightError{notFound: true}
}
if file.MissingSince != nil {
return &playbackFilePreflightError{notFound: true}
}
if _, err := os.Stat(file.FilePath); err != nil {
if os.IsNotExist(err) {
markPlaybackFileMissing(ctx, file, marker, eventsHub)
return &playbackFilePreflightError{notFound: true, err: err}
}
return &playbackFilePreflightError{err: err}
}
return nil
}
func markPlaybackFileMissing(
ctx context.Context,
file *models.MediaFile,
marker MissingFileMarker,
eventsHub *evt.Hub,
) {
if file == nil {
return
}
since := time.Now().UTC()
file.MissingSince = &since
if marker != nil {
if err := marker.MarkMissing(ctx, file.ID, since); err != nil {
slog.Warn("failed to mark playback file missing",
"file_id", file.ID,
"path", file.FilePath,
"error", err,
)
}
}
contentID := playbackProgressTarget(file)
if contentID != "" && eventsHub != nil {
publishEventMetadataUpdate(ctx, eventsHub, file.MediaFolderID, contentID)
}
}