diff --git a/backend/dist/nsfwapp-linux-amd64 b/backend/dist/nsfwapp-linux-amd64 index 68a2f06..0b640b1 100644 Binary files a/backend/dist/nsfwapp-linux-amd64 and b/backend/dist/nsfwapp-linux-amd64 differ diff --git a/backend/dist/nsfwapp.exe b/backend/dist/nsfwapp.exe index 72f7573..2820cfc 100644 Binary files a/backend/dist/nsfwapp.exe and b/backend/dist/nsfwapp.exe differ diff --git a/backend/dist/nsfwapp_amd64.deb b/backend/dist/nsfwapp_amd64.deb index 00c0631..14f28c2 100644 Binary files a/backend/dist/nsfwapp_amd64.deb and b/backend/dist/nsfwapp_amd64.deb differ diff --git a/backend/models_store.go b/backend/models_store.go index 0d0fe0d..9060e25 100644 --- a/backend/models_store.go +++ b/backend/models_store.go @@ -453,6 +453,28 @@ func (s *ModelStore) flushPendingStatusUpdates() error { return nil } + var lastErr error + for attempt := 0; attempt < 3; attempt++ { + if attempt > 0 { + time.Sleep(time.Duration(100*attempt) * time.Millisecond) + } + err := s.flushStatusUpdateBatch(lastSeen, roomState) + if err == nil { + if attempt > 0 { + appLogln("models status flush retry ok") + } + return nil + } + if !isRetryablePostgresTxError(err) { + return err + } + lastErr = err + appLogln("models status flush retry:", err) + } + return lastErr +} + +func (s *ModelStore) flushStatusUpdateBatch(lastSeen map[string]modelLastSeenUpdate, roomState map[string]modelRoomStateUpdate) error { s.mu.Lock() defer s.mu.Unlock() @@ -540,6 +562,18 @@ WHERE lower(trim(host)) = lower(trim($7)) return tx.Commit() } +func isRetryablePostgresTxError(err error) bool { + if err == nil { + return false + } + msg := strings.ToLower(err.Error()) + return strings.Contains(msg, "sqlstate 40p01") || + strings.Contains(msg, "deadlock") || + strings.Contains(msg, "verklemmung") || + strings.Contains(msg, "sqlstate 40001") || + strings.Contains(msg, "serialization") +} + func (s *ModelStore) SetChaturbateRoomState( host string, modelKey string, diff --git a/backend/postgres_local.go b/backend/postgres_local.go index 4373381..ed57072 100644 --- a/backend/postgres_local.go +++ b/backend/postgres_local.go @@ -5,6 +5,7 @@ import ( "context" "fmt" "io" + "net" "net/url" "os" "os/exec" @@ -441,7 +442,17 @@ func startLocalPostgresCluster(ctx context.Context, dataDir string, port int) er return appErrorf("pg_ctl wurde nicht gefunden. Installiere PostgreSQL-Server-Tools oder nimm pg_ctl in PATH auf.") } - _, _ = runLocalPostgresTool(ctx, pgCtl, "-D", dataDir, "status") + statusOut, statusErr := runLocalPostgresTool(ctx, pgCtl, "-D", dataDir, "status") + if statusErr == nil { + return nil + } + if strings.Contains(strings.ToLower(statusOut), "server is running") || strings.Contains(strings.ToLower(statusOut), "server running") { + return nil + } + + if err := ensureLocalPostgresPortAvailable(port); err != nil { + return err + } logPath := filepath.Join(dataDir, "postgresql.log") opts := fmt.Sprintf("-p %d -h 127.0.0.1", port) @@ -452,9 +463,41 @@ func startLocalPostgresCluster(ctx context.Context, dataDir string, port int) er if strings.Contains(strings.ToLower(out), "server is running") || strings.Contains(strings.ToLower(out), "server running") { return nil } + logTail := tailTextFile(logPath, 6000) + if logTail != "" { + appLogln("postgresql.log:", logTail) + return appErrorf("lokaler PostgreSQL-Server konnte nicht gestartet werden: %s\n\npostgresql.log:\n%s", out, logTail) + } return appErrorf("lokaler PostgreSQL-Server konnte nicht gestartet werden: %s", out) } +func ensureLocalPostgresPortAvailable(port int) error { + addr := net.JoinHostPort("127.0.0.1", strconv.Itoa(port)) + conn, err := net.DialTimeout("tcp", addr, 300*time.Millisecond) + if err != nil { + return nil + } + _ = conn.Close() + return appErrorf("Port %d ist bereits belegt. Bitte in den Einstellungen einen anderen PostgreSQL-Port wählen.", port) +} + +func tailTextFile(path string, maxBytes int) string { + if maxBytes <= 0 { + maxBytes = 4000 + } + b, err := os.ReadFile(path) + if err != nil || len(b) == 0 { + return "" + } + if len(b) > maxBytes { + b = b[len(b)-maxBytes:] + if idx := bytes.IndexByte(b, '\n'); idx >= 0 && idx+1 < len(b) { + b = b[idx+1:] + } + } + return strings.TrimSpace(string(b)) +} + func ensureLocalPostgresDatabase(ctx context.Context, port int, user, database string) error { psql, err := postgresToolPath("psql") if err != nil {