225 lines
4.5 KiB
Go
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()
|
|
}
|