nsfwapp/backend/server.go
2026-05-02 11:02:45 +02:00

225 lines
4.5 KiB
Go

// 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()
}