213 lines
6 KiB
Go
213 lines
6 KiB
Go
package server
|
|
|
|
import (
|
|
"bytes"
|
|
"context"
|
|
"errors"
|
|
"fmt"
|
|
"log/slog"
|
|
"net/http"
|
|
"os"
|
|
"path/filepath"
|
|
"slices"
|
|
|
|
"git.lastassault.de/speatzle/morffix/constants"
|
|
"github.com/jackc/pgx/v5"
|
|
)
|
|
|
|
var videoFileExtensions = []string{".mkv", ".mp4", ".webm", ".flv", ".avi"}
|
|
|
|
func handleScan(w http.ResponseWriter, r *http.Request) {
|
|
if r.Method == "GET" {
|
|
scanStatus(w, r)
|
|
return
|
|
}
|
|
|
|
id := r.PathValue("id")
|
|
if id == "" {
|
|
http.Error(w, "No ID Set", http.StatusBadRequest)
|
|
return
|
|
}
|
|
|
|
var name string
|
|
var path string
|
|
var enabled bool
|
|
err := db.QueryRow(r.Context(), "SELECT name, path, enable FROM libraries WHERE id = $1", id).Scan(&name, &path, &enabled)
|
|
if err != nil {
|
|
slog.ErrorContext(r.Context(), "Get Library", "err", err)
|
|
http.Error(w, "Error Get Library: "+err.Error(), http.StatusInternalServerError)
|
|
return
|
|
}
|
|
|
|
scanCtx := context.Background()
|
|
go scan(scanCtx, id)
|
|
|
|
message := "Scan Started"
|
|
|
|
buf := bytes.Buffer{}
|
|
err = templates.ExecuteTemplate(&buf, constants.MESSAGE_TEMPLATE_NAME, message)
|
|
if err != nil {
|
|
slog.ErrorContext(r.Context(), "Executing Library Template", "err", err)
|
|
http.Error(w, "Error Executing Template: "+err.Error(), http.StatusInternalServerError)
|
|
return
|
|
}
|
|
|
|
_, err = w.Write(buf.Bytes())
|
|
if err != nil {
|
|
slog.ErrorContext(r.Context(), "Writing http Response", "err", err)
|
|
}
|
|
}
|
|
|
|
func scanStatus(w http.ResponseWriter, r *http.Request) {
|
|
message := "TODO"
|
|
|
|
buf := bytes.Buffer{}
|
|
err := templates.ExecuteTemplate(&buf, constants.MESSAGE_TEMPLATE_NAME, message)
|
|
if err != nil {
|
|
slog.ErrorContext(r.Context(), "Executing Library Template", "err", err)
|
|
http.Error(w, "Error Executing Template: "+err.Error(), http.StatusInternalServerError)
|
|
return
|
|
}
|
|
|
|
_, err = w.Write(buf.Bytes())
|
|
if err != nil {
|
|
slog.ErrorContext(r.Context(), "Writing http Response", "err", err)
|
|
}
|
|
}
|
|
|
|
func scan(ctx context.Context, id string) {
|
|
slog.InfoContext(ctx, "Starting Scan", "id", id)
|
|
|
|
// TODO Scan settings:
|
|
// - Auto Queue Healthcheck for Changed Files
|
|
// - Auto Queue Healthcheck for New Files
|
|
// - Auto Queue Transcode for New Files
|
|
// - Auto Queue Transcode for Changed Files (? Instead have library setting to queue transcode for changed files on healthcheck success)
|
|
// - Auto Queue Health/Transcode for Unkown Status ? (might result in requeue loop)
|
|
// - Schedule Scans Periodically
|
|
// - Add File Monitoring for Setting Changed status and triggering tasks
|
|
|
|
var name string
|
|
var lpath string
|
|
var enabled bool
|
|
err := db.QueryRow(ctx, "SELECT name, path, enable FROM libraries WHERE id = $1", id).Scan(&name, &lpath, &enabled)
|
|
if err != nil {
|
|
slog.ErrorContext(ctx, "Get Library", "err", err)
|
|
return
|
|
}
|
|
|
|
if !enabled {
|
|
slog.ErrorContext(ctx, "Scan Aborted, Library not Enabled", "id", id)
|
|
return
|
|
}
|
|
|
|
dirInfo, err := os.Stat(lpath)
|
|
if err != nil {
|
|
slog.ErrorContext(ctx, "Stating Library Path", "id", id, "path", lpath, "err", err)
|
|
return
|
|
}
|
|
|
|
if !dirInfo.IsDir() {
|
|
slog.ErrorContext(ctx, "Library Path is not a Folder", "id", id, "path", lpath)
|
|
return
|
|
}
|
|
|
|
tx, err := db.Begin(ctx)
|
|
if err != nil {
|
|
slog.ErrorContext(ctx, "Begin Transaction", "err", err)
|
|
return
|
|
}
|
|
defer tx.Rollback(ctx)
|
|
|
|
slog.InfoContext(ctx, "Checking Files...", "id", id, "path", lpath)
|
|
|
|
// Mark all Files as Missing
|
|
_, err = tx.Exec(ctx, "UPDATE files SET status = $2 where library_id = $1", id, constants.FILE_STATUS_MISSING)
|
|
if err != nil {
|
|
slog.ErrorContext(ctx, "Setting Missing Status", "err", err)
|
|
return
|
|
}
|
|
|
|
err = filepath.Walk(lpath,
|
|
func(fullPath string, info os.FileInfo, err error) error {
|
|
if errors.Is(err, os.ErrPermission) {
|
|
slog.WarnContext(ctx, "Permission Denied While Scanning File", "path", fullPath)
|
|
return nil
|
|
} else if err != nil {
|
|
return err
|
|
}
|
|
|
|
if info.IsDir() {
|
|
// We don't care about folders
|
|
return nil
|
|
}
|
|
|
|
if !slices.Contains(videoFileExtensions, filepath.Ext(fullPath)) {
|
|
slog.InfoContext(ctx, "Skipping non video file", "path", fullPath)
|
|
return nil
|
|
}
|
|
/*
|
|
slog.InfoContext(ctx, "Hashing File", "path", fullPath, "size", info.Size())
|
|
|
|
file, err := os.Open(fullPath)
|
|
if err != nil {
|
|
return fmt.Errorf("Opening File: %w", err)
|
|
}
|
|
|
|
hash := md5.New()
|
|
if _, err := io.Copy(hash, file); err != nil {
|
|
return fmt.Errorf("Reading File: %w", err)
|
|
}
|
|
newMD5 := hash.Sum(nil)
|
|
|
|
slog.InfoContext(ctx, "File MD5", "path", fullPath, "size", info.Size(), "md5", newMD5)
|
|
*/
|
|
fPath, err := filepath.Rel(lpath, fullPath)
|
|
if err != nil {
|
|
return fmt.Errorf("Getting Relative Path: %w", err)
|
|
}
|
|
|
|
var fileID int
|
|
var size uint
|
|
var health constants.FileHealth
|
|
err = tx.QueryRow(ctx, "SELECT id, size, health FROM files WHERE library_id = $1 AND path = $2", id, fPath).Scan(&fileID, &size, &health)
|
|
if errors.Is(err, pgx.ErrNoRows) {
|
|
// File Does not Exist Yet
|
|
|
|
slog.InfoContext(ctx, "File is New", "path", fullPath)
|
|
_, err = tx.Exec(ctx, "INSERT INTO files (library_id, path, size, status, health) VALUES ($1, $2, $3, $4, $5)", id, fPath, info.Size(), constants.FILE_STATUS_NEW, constants.FILE_HEALTH_UNKNOWN)
|
|
if err != nil {
|
|
return fmt.Errorf("Add New File to DB: %w", err)
|
|
}
|
|
return nil
|
|
} else if err != nil {
|
|
return fmt.Errorf("Getting File: %w", err)
|
|
}
|
|
|
|
/*
|
|
if slices.Compare[[]byte](newMD5, oldMD5) != 0 {
|
|
// File has changed on disk so reset health
|
|
health = constants.FILE_HEALTH_UNKNOWN
|
|
}
|
|
*/
|
|
// File Exists so update Size, status and hash
|
|
_, err = tx.Exec(ctx, "UPDATE files SET size = $2, status = $3, health = $4 WHERE id = $1", fileID, info.Size(), constants.FILE_STATUS_EXISTS, health)
|
|
if err != nil {
|
|
return fmt.Errorf("Updating File in DB: %w", err)
|
|
}
|
|
return nil
|
|
})
|
|
if err != nil {
|
|
slog.ErrorContext(ctx, "Error Walking Library", "err", err)
|
|
return
|
|
}
|
|
|
|
err = tx.Commit(ctx)
|
|
if err != nil {
|
|
slog.ErrorContext(ctx, "Error Committing Changes", "err", err)
|
|
return
|
|
}
|
|
|
|
// TODO, create health and transcode tasks if requested
|
|
slog.InfoContext(ctx, "Scan Done", "id", id)
|
|
}
|