// backend\server.go package main import ( "context" "fmt" "net" "net/http" "os" "os/exec" "os/signal" "runtime" "strings" "sync" "syscall" "time" ) func escapePowerShell(s string) string { return strings.ReplaceAll(s, "'", "''") } func escapeAppleScript(s string) string { s = strings.ReplaceAll(s, "\\", "\\\\") s = strings.ReplaceAll(s, `"`, `\"`) return s } func showAppNotification(title, message string) error { switch runtime.GOOS { case "windows": ps := fmt.Sprintf(` Add-Type -AssemblyName System.Windows.Forms Add-Type -AssemblyName System.Drawing $n = New-Object System.Windows.Forms.NotifyIcon $n.Icon = [System.Drawing.SystemIcons]::Information $n.BalloonTipTitle = '%s' $n.BalloonTipText = '%s' $n.Visible = $true $n.ShowBalloonTip(3000) Start-Sleep -Milliseconds 3500 $n.Dispose() `, escapePowerShell(title), escapePowerShell(message)) cmd := exec.Command( "powershell", "-NoProfile", "-NonInteractive", "-WindowStyle", "Hidden", "-ExecutionPolicy", "Bypass", "-Command", ps, ) cmd.SysProcAttr = &syscall.SysProcAttr{ HideWindow: true, CreationFlags: 0x08000000, // CREATE_NO_WINDOW } return cmd.Run() case "darwin": return exec.Command( "osascript", "-e", fmt.Sprintf( `display notification "%s" with title "%s"`, escapeAppleScript(message), escapeAppleScript(title), ), ).Run() case "linux": return exec.Command("notify-send", title, message).Run() default: return nil } } func buildStartupNotificationMessage() string { return "Die App läuft im Hintergrund." } // --- main --- func main() { loadSettings() // Nur eine Instanz erlauben: appLn, err := net.Listen("tcp", ":9999") if err != nil { _ = showAppNotification( "Recorder", "Die Anwendung wird bereits ausgeführt.", ) fmt.Println("⚠️ Die App läuft bereits oder Port 9999 ist bereits belegt.") os.Exit(0) } defer appLn.Close() // ✅ Hier: alte manuelle Autostart-Pause beim echten App-Start zurücksetzen. // Disk-Notbremse bleibt trotzdem aktiv, weil isAutostartPaused() // zusätzlich diskEmergency prüft. resetAutostartPauseOnStartup() fixKeepRootFilesIntoModelSubdirs() postWorkQ.StartWorkers(1) enrichQ.StartWorkers(1) startPostWorkStatusRefresher() startEnrichStatusRefresher() go startGeneratedGarbageCollector() if err := initNSFWDetector(); err != nil { fmt.Println("❌ NSFW-ONNX Fehler:", err) os.Exit(1) } var closeNSFWOnce sync.Once closeNSFW := func() { closeNSFWOnce.Do(func() { _ = closeNSFWDetector() }) } defer closeNSFW() mux := http.NewServeMux() auth, err := NewAuthManager() if err != nil { fmt.Println("❌ auth init:", err) closeNSFW() os.Exit(1) } store := registerRoutes(mux, auth) setChaturbateOnlineModelStore(store) if err := clearAllPendingAutoStartOnStartup(); err != nil { fmt.Println("⚠️ [pending-autostart] startup clear failed:", err) } // Hintergrund-Worker go startDiskSpaceGuard() go startChaturbateOnlinePoller(store) go startChaturbateAutoStartWorker(store) go startMyFreeCamsAutoStartWorker(store) // optional: Bootstrap nur im Hintergrund if getSettings().UseChaturbateAPI { go func() { ctx, cancel := context.WithTimeout(context.Background(), 20*time.Second) defer cancel() if _, err := refreshChaturbateSnapshotNow(ctx); err != nil { fmt.Println("⚠️ Chaturbate API failed:", err) } else { fmt.Println("✅ Chaturbate API geladen") } }() } if _, err := ensureCoversDir(); err != nil { fmt.Println("⚠️ covers dir:", err) } fmt.Println("🌐 HTTP-API aktiv: http://localhost:9999") handler := withCORS(mux) srv := &http.Server{ Addr: ":9999", Handler: handler, } var shutdownOnce sync.Once shutdown := func() { shutdownOnce.Do(func() { fmt.Println("🛑 Beende Server...") ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) defer cancel() _ = srv.Shutdown(ctx) closeNSFW() }) } stopSig := make(chan os.Signal, 1) signal.Notify(stopSig, os.Interrupt, syscall.SIGTERM) go func() { <-stopSig shutdown() }() serverErrCh := make(chan error, 1) go func() { if err := srv.Serve(appLn); err != nil && err != http.ErrServerClosed { serverErrCh <- err return } serverErrCh <- nil }() go func() { _ = showAppNotification( "Recorder gestartet", buildStartupNotificationMessage(), ) }() go func() { err := <-serverErrCh if err != nil { fmt.Println("❌ HTTP-Server Fehler:", err) shutdown() } }() runTray(shutdown, buildTrayStats) shutdown() }