103 lines
2.7 KiB
Go
103 lines
2.7 KiB
Go
// backend/upload_handler.go
|
|
package main
|
|
|
|
import (
|
|
"fmt"
|
|
"log"
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
|
|
"github.com/tus/tusd/pkg/filestore"
|
|
handler "github.com/tus/tusd/pkg/handler"
|
|
"github.com/tus/tusd/pkg/memorylocker"
|
|
|
|
"scm.bstein.dev/bstein/Pegasus/backend/internal"
|
|
)
|
|
|
|
func newTusHandler(um *internal.UserMap, jf jellyfinClient) *handler.UnroutedHandler {
|
|
store := filestore.FileStore{Path: tusDir}
|
|
locker := memorylocker.New()
|
|
composer := handler.NewStoreComposer()
|
|
store.UseIn(composer)
|
|
locker.UseIn(composer)
|
|
|
|
config := handler.Config{
|
|
BasePath: "/tus/",
|
|
StoreComposer: composer,
|
|
NotifyCompleteUploads: true,
|
|
MaxSize: 8 * 1024 * 1024 * 1024,
|
|
RespectForwardedHeaders: true,
|
|
}
|
|
tusHandler, err := handler.NewUnroutedHandler(config)
|
|
must(err, "init tus handler")
|
|
|
|
go watchUploadCompletions(tusHandler.CompleteUploads, um, jf)
|
|
return tusHandler
|
|
}
|
|
|
|
func watchUploadCompletions(completeC <-chan handler.HookEvent, um *internal.UserMap, jf jellyfinClient) {
|
|
for ev := range completeC {
|
|
if err := processCompletedUpload(ev, um, jf); err != nil {
|
|
log.Printf("tus upload processing failed: %v", err)
|
|
}
|
|
}
|
|
}
|
|
|
|
func processCompletedUpload(ev handler.HookEvent, um *internal.UserMap, jf jellyfinClient) error {
|
|
claims, err := claimsFromHook(ev)
|
|
if err != nil {
|
|
internal.Logf("tus: no session: %v", err)
|
|
return err
|
|
}
|
|
|
|
meta := ev.Upload.MetaData
|
|
desc := strings.TrimSpace(meta["desc"])
|
|
date := strings.TrimSpace(meta["date"])
|
|
subdir := strings.Trim(strings.TrimSpace(meta["subdir"]), "/")
|
|
lib := strings.Trim(strings.TrimSpace(meta["lib"]), "/")
|
|
orig := meta["filename"]
|
|
if orig == "" {
|
|
orig = "upload.bin"
|
|
}
|
|
|
|
ext := strings.ToLower(strings.TrimPrefix(filepath.Ext(orig), "."))
|
|
isVideo := map[string]bool{
|
|
"mp4": true, "mkv": true, "mov": true, "avi": true, "m4v": true,
|
|
"webm": true, "mpg": true, "mpeg": true, "ts": true, "m2ts": true,
|
|
}[ext]
|
|
if isVideo && desc == "" {
|
|
return fmt.Errorf("missing desc for video")
|
|
}
|
|
if desc == "" {
|
|
desc = "upload"
|
|
}
|
|
|
|
rootRel, err := resolveLibraryRoot(um, claims.Username, lib)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
libRoot, _ := internal.SafeJoin(mediaRoot, rootRel)
|
|
if fi, err := os.Stat(libRoot); err != nil || !fi.IsDir() {
|
|
return fmt.Errorf("library root missing or not accessible")
|
|
}
|
|
|
|
destRoot := libRoot
|
|
if subdir != "" {
|
|
subdir = sanitizeSegment(subdir)
|
|
destRoot = filepath.Join(libRoot, subdir)
|
|
if err := os.MkdirAll(destRoot, 0o2775); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
finalName := composeFinalName(date, desc, orig)
|
|
dst := filepath.Join(destRoot, finalName)
|
|
if err := moveFromTus(ev, dst); err != nil {
|
|
return err
|
|
}
|
|
|
|
jf.RefreshLibrary(claims.JFToken)
|
|
return nil
|
|
}
|