nsfwapp/backend/record_helpers_paths.go
2026-02-09 12:29:19 +01:00

196 lines
4.5 KiB
Go

// backend\record_helpers_paths.go
package main
import (
"fmt"
"net/http"
"os"
"path/filepath"
"strings"
)
func resolvePathRelativeToApp(p string) (string, error) {
p = strings.TrimSpace(p)
if p == "" {
return "", nil
}
p = filepath.Clean(filepath.FromSlash(p))
if filepath.IsAbs(p) {
return p, nil
}
exe, err := os.Executable()
if err == nil {
exeDir := filepath.Dir(exe)
low := strings.ToLower(exeDir)
// Heuristik: go run / tests -> exe liegt in Temp/go-build
isTemp := strings.Contains(low, `\appdata\local\temp`) ||
strings.Contains(low, `\temp\`) ||
strings.Contains(low, `\tmp\`) ||
strings.Contains(low, `\go-build`) ||
strings.Contains(low, `/tmp/`) ||
strings.Contains(low, `/go-build`)
if !isTemp {
return filepath.Join(exeDir, p), nil
}
}
// Fallback: Working Directory (Dev)
wd, err := os.Getwd()
if err != nil {
return "", err
}
return filepath.Join(wd, p), nil
}
func getRecordingsDir() string {
s := getSettings()
abs, err := resolvePathRelativeToApp(s.RecordDir)
if err == nil && strings.TrimSpace(abs) != "" {
return abs
}
// Fallback (falls resolve fehlschlägt)
return strings.TrimSpace(s.RecordDir)
}
func getKeepDir() string {
s := getSettings()
doneAbs, err := resolvePathRelativeToApp(s.DoneDir)
if err != nil || strings.TrimSpace(doneAbs) == "" {
doneAbs = strings.TrimSpace(s.DoneDir)
}
if strings.TrimSpace(doneAbs) == "" {
return ""
}
return filepath.Join(doneAbs, "keep")
}
func getDoneDir() string {
s := getSettings()
doneAbs, err := resolvePathRelativeToApp(s.DoneDir)
if err == nil && strings.TrimSpace(doneAbs) != "" {
return doneAbs
}
return strings.TrimSpace(s.DoneDir)
}
func findVideoPath(file string) (string, error) {
base := filepath.Base(file) // verhindert path traversal
// TODO: passe diese Root-Dirs an deine echten Pfade an:
roots := []string{
getRecordingsDir(), // z.B. downloads/output root
getDoneDir(), // ✅ NEU: fertige Dateien liegen typischerweise hier
getKeepDir(), // keep root
}
// 1) direkt in den Roots
for _, root := range roots {
root = strings.TrimSpace(root)
if root == "" {
continue
}
p := filepath.Join(root, base)
if st, err := os.Stat(p); err == nil && !st.IsDir() {
return p, nil
}
}
// 2) 1 Ebene Unterordner: root/*/file
for _, root := range roots {
root = strings.TrimSpace(root)
if root == "" {
continue
}
matches, _ := filepath.Glob(filepath.Join(root, "*", base))
for _, p := range matches {
if st, err := os.Stat(p); err == nil && !st.IsDir() {
return p, nil
}
}
}
return "", os.ErrNotExist
}
func setNoStoreHeaders(w http.ResponseWriter) {
// verhindert Browser/Proxy Caching (wichtig für Logs/Status)
w.Header().Set("Cache-Control", "no-store, max-age=0")
w.Header().Set("Pragma", "no-cache")
w.Header().Set("Expires", "0")
}
func findFileInDirOrOneLevelSubdirs(root string, file string, skipDirName string) (string, os.FileInfo, bool) {
// direct
p := filepath.Join(root, file)
if fi, err := os.Stat(p); err == nil && !fi.IsDir() && fi.Size() > 0 {
return p, fi, true
}
entries, err := os.ReadDir(root)
if err != nil {
return "", nil, false
}
for _, e := range entries {
if !e.IsDir() {
continue
}
if skipDirName != "" && e.Name() == skipDirName {
continue
}
pp := filepath.Join(root, e.Name(), file)
if fi, err := os.Stat(pp); err == nil && !fi.IsDir() && fi.Size() > 0 {
return pp, fi, true
}
}
return "", nil, false
}
func resolveDoneFileByName(doneAbs string, file string) (full string, from string, fi os.FileInfo, err error) {
// 1) done (root + /done/<subdir>/) — "keep" wird übersprungen
if p, fi, ok := findFileInDirOrOneLevelSubdirs(doneAbs, file, "keep"); ok {
return p, "done", fi, nil
}
// 2) keep (root + /done/keep/<subdir>/)
keepDir := filepath.Join(doneAbs, "keep")
if p, fi, ok := findFileInDirOrOneLevelSubdirs(keepDir, file, ""); ok {
return p, "keep", fi, nil
}
return "", "", nil, fmt.Errorf("not found")
}
func isTrashPath(p string) bool {
p = strings.ReplaceAll(p, "\\", "/")
return strings.Contains(p, "/.trash/") || strings.HasSuffix(p, "/.trash")
}
func durationFromMetaIfFresh(videoPath, assetDir string, fi os.FileInfo) (float64, bool) {
metaPath := filepath.Join(assetDir, "meta.json")
return readVideoMetaDuration(metaPath, fi)
}
func durationSecondsCacheOnly(path string, fi os.FileInfo) float64 {
durCache.mu.Lock()
e, ok := durCache.m[path]
durCache.mu.Unlock()
if ok && e.size == fi.Size() && e.mod.Equal(fi.ModTime()) && e.sec > 0 {
return e.sec
}
return 0
}