196 lines
4.5 KiB
Go
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
|
|
}
|