diff --git a/deploy/README.md b/deploy/README.md new file mode 100644 index 0000000..4133bf3 --- /dev/null +++ b/deploy/README.md @@ -0,0 +1,197 @@ +# YourChat Deployment Scripts + +Diese Scripts ermöglichen das einfache Deployment von YourChat auf Ubuntu 22.04 Servern. + +## Voraussetzungen + +- Ubuntu 22.04 LTS Server +- Sudo-Rechte +- Internetverbindung für Paket-Downloads + +## Scripts Übersicht + +### 1. `deploy.sh` - Vollständiges Deployment +**Verwendung:** `./deploy/deploy.sh` + +Führt alle Deploy-Schritte automatisch aus: +- Installation der Abhängigkeiten +- Bauen der Anwendung +- Installation als Systemdienst +- Konfigurations-Setup + +### 2. `install_dependencies.sh` - Abhängigkeiten installieren +**Verwendung:** `./deploy/install_dependencies.sh` + +Installiert alle benötigten Pakete: +- GCC-13 (als Standard-Compiler) +- CMake, Build-Tools +- OpenSSL, jsoncpp, pqxx +- PostgreSQL (optional) + +### 3. `build.sh` - Anwendung bauen +**Verwendung:** `./deploy/build.sh` + +Baut die Anwendung mit aktiviertem Debug-Flag: +- Erstellt `build/` Verzeichnis +- Konfiguriert CMake mit `YC_DEBUG=ON` +- Kompiliert mit allen verfügbaren CPU-Kernen + +### 4. `install.sh` - Anwendung installieren +**Verwendung:** `./deploy/install.sh` + +Installiert die Anwendung als Systemdienst: +- Erstellt `/opt/yourchat/` Verzeichnis +- Kopiert Binaries und Konfiguration +- Erstellt systemd Service +- Erstellt `yourchat` Benutzer +- Aktiviert Service + +### 5. `update_config.sh` - Konfiguration aktualisieren +**Verwendung:** `./deploy/update_config.sh` + +Aktualisiert die Konfiguration sicher: +- Überschreibt **keine** bestehenden Einträge +- Ergänzt nur fehlende Konfigurationsoptionen +- Erstellt automatisch Backups +- Zeigt Änderungen an + +### 6. `update.sh` - Anwendung aktualisieren +**Verwendung:** `./deploy/update.sh` + +Für zukünftige Updates: +- Stoppt Service +- Baut neue Version +- Aktualisiert Binaries +- Aktualisiert Konfiguration +- Startet Service neu + +## Schnellstart + +1. **Repository klonen/übertragen:** + ```bash + # Auf dem Ubuntu Server + git clone + cd YourChat + ``` + +2. **Vollständiges Deployment:** + ```bash + chmod +x deploy/*.sh + ./deploy/deploy.sh + ``` + +3. **Service starten:** + ```bash + sudo systemctl start yourchat + sudo systemctl status yourchat + ``` + +## Konfiguration + +Die Konfiguration wird unter `/opt/yourchat/config/chatconfig.json` installiert. + +**Wichtige Einstellungen:** +- `server.port`: Port für den Chat-Server (Standard: 1235) +- `database.*`: Datenbankverbindung +- `rooms`: Vordefinierte Chat-Räume + +**Konfiguration bearbeiten:** +```bash +sudo nano /opt/yourchat/config/chatconfig.json +``` + +## Service-Verwaltung + +```bash +# Service starten +sudo systemctl start yourchat + +# Service stoppen +sudo systemctl stop yourchat + +# Service neu starten +sudo systemctl restart yourchat + +# Status prüfen +sudo systemctl status yourchat + +# Logs anzeigen +sudo journalctl -u yourchat -f + +# Service deaktivieren (startet nicht automatisch) +sudo systemctl disable yourchat +``` + +## Firewall + +Falls eine Firewall aktiv ist, den Port freigeben: +```bash +sudo ufw allow 1235/tcp +``` + +## Troubleshooting + +### Service startet nicht +```bash +# Logs prüfen +sudo journalctl -u yourchat -n 50 + +# Konfiguration prüfen +sudo cat /opt/yourchat/config/chatconfig.json | python3 -m json.tool +``` + +### Port bereits belegt +```bash +# Prüfen welcher Prozess den Port verwendet +sudo netstat -tlnp | grep 1235 + +# Port in Konfiguration ändern +sudo nano /opt/yourchat/config/chatconfig.json +``` + +### Berechtigungsprobleme +```bash +# Berechtigungen korrigieren +sudo chown -R yourchat:yourchat /opt/yourchat +sudo chmod 755 /opt/yourchat +sudo chmod 644 /opt/yourchat/config/chatconfig.json +``` + +## Backup & Wiederherstellung + +### Backup erstellen +```bash +# Konfiguration +sudo cp /opt/yourchat/config/chatconfig.json /backup/chatconfig.json.backup + +# Datenbank (falls lokal) +sudo pg_dump yourchat > /backup/database.backup +``` + +### Wiederherstellung +```bash +# Konfiguration +sudo cp /backup/chatconfig.json.backup /opt/yourchat/config/chatconfig.json +sudo chown yourchat:yourchat /opt/yourchat/config/chatconfig.json + +# Service neu starten +sudo systemctl restart yourchat +``` + +## Updates + +Für zukünftige Updates: +```bash +# Code aktualisieren +git pull + +# Anwendung updaten +./deploy/update.sh +``` + +## Sicherheit + +- Die Anwendung läuft als dedizierter `yourchat` Benutzer +- Konfigurationsdateien haben restriktive Berechtigungen +- Service startet nicht automatisch nach Reboot (kann mit `systemctl enable` aktiviert werden) +- Logs werden über systemd journal verwaltet diff --git a/deploy/build.sh b/deploy/build.sh new file mode 100755 index 0000000..b0548f0 --- /dev/null +++ b/deploy/build.sh @@ -0,0 +1,36 @@ +#!/bin/bash + +# YourChat Build Script +# Baut die Anwendung mit Debug-Flag + +set -e # Beende bei Fehlern + +echo "=== YourChat - Build Script ===" + +# Prüfe ob wir im richtigen Verzeichnis sind +if [ ! -f "CMakeLists.txt" ]; then + echo "Fehler: CMakeLists.txt nicht gefunden. Bitte im YourChat-Root-Verzeichnis ausführen." + exit 1 +fi + +# Build-Verzeichnis erstellen +echo "=== Build-Verzeichnis erstellen ===" +mkdir -p build +cd build + +# CMake konfigurieren mit Debug-Flag +echo "=== CMake konfigurieren ===" +cmake .. -DYC_DEBUG=ON + +# Anwendung bauen +echo "=== Anwendung bauen ===" +make -j$(nproc) + +echo "=== Build abgeschlossen ===" +echo "Ausführbare Dateien:" +ls -la yourchat ws_probe + +# Zurück zum Root-Verzeichnis +cd .. + +echo "=== Build erfolgreich abgeschlossen! ===" diff --git a/deploy/deploy.sh b/deploy/deploy.sh new file mode 100755 index 0000000..54cf52b --- /dev/null +++ b/deploy/deploy.sh @@ -0,0 +1,67 @@ +#!/bin/bash + +# YourChat Haupt-Deploy Script +# Führt alle Deploy-Schritte automatisch aus + +set -e # Beende bei Fehlern + +echo "=== YourChat - Vollständiges Deployment Script ===" +echo "Dieses Script führt alle Deploy-Schritte automatisch aus." +echo "" + +# Prüfe ob wir im richtigen Verzeichnis sind +if [ ! -f "CMakeLists.txt" ]; then + echo "Fehler: CMakeLists.txt nicht gefunden. Bitte im YourChat-Root-Verzeichnis ausführen." + exit 1 +fi + +# Prüfe ob deploy-Verzeichnis existiert +if [ ! -d "deploy" ]; then + echo "Fehler: deploy-Verzeichnis nicht gefunden." + exit 1 +fi + +echo "=== Schritt 1: Abhängigkeiten installieren ===" +echo "Führe install_dependencies.sh aus..." +./deploy/install_dependencies.sh + +echo "" +echo "=== Schritt 2: Anwendung bauen ===" +echo "Führe build.sh aus..." +./deploy/build.sh + +echo "" +echo "=== Schritt 3: Anwendung installieren ===" +echo "Führe install.sh aus..." +./deploy/install.sh + +echo "" +echo "=== Schritt 4: Konfiguration aktualisieren ===" +echo "Führe update_config.sh aus..." +./deploy/update_config.sh + +echo "" +echo "=== Deployment abgeschlossen! ===" +echo "" +echo "=== Nächste Schritte: ===" +echo "1. Konfiguration anpassen (falls nötig):" +echo " sudo nano /opt/yourchat/config/chatconfig.json" +echo "" +echo "2. Service starten:" +echo " sudo systemctl start yourchat" +echo "" +echo "3. Status prüfen:" +echo " sudo systemctl status yourchat" +echo "" +echo "4. Logs anzeigen:" +echo " sudo journalctl -u yourchat -f" +echo "" +echo "5. Firewall konfigurieren (falls nötig):" +echo " sudo ufw allow 1235/tcp" +echo "" +echo "=== Nützliche Befehle: ===" +echo "- Service stoppen: sudo systemctl stop yourchat" +echo "- Service neu starten: sudo systemctl restart yourchat" +echo "- Service deaktivieren: sudo systemctl disable yourchat" +echo "- Logs anzeigen: sudo journalctl -u yourchat -f" +echo "- Konfiguration bearbeiten: sudo nano /opt/yourchat/config/chatconfig.json" diff --git a/deploy/install.sh b/deploy/install.sh new file mode 100755 index 0000000..5bcfa27 --- /dev/null +++ b/deploy/install.sh @@ -0,0 +1,86 @@ +#!/bin/bash + +# YourChat Installations Script +# Installiert die Anwendung als Systemdienst + +set -e # Beende bei Fehlern + +echo "=== YourChat - Installation Script ===" + +# Prüfe ob Build existiert +if [ ! -f "build/yourchat" ]; then + echo "Fehler: Build nicht gefunden. Bitte zuerst ./deploy/build.sh ausführen." + exit 1 +fi + +# Installationsverzeichnis erstellen +echo "=== Installationsverzeichnis erstellen ===" +sudo mkdir -p /opt/yourchat +sudo mkdir -p /opt/yourchat/config +sudo mkdir -p /opt/yourchat/logs + +# Anwendung installieren +echo "=== Anwendung installieren ===" +sudo cp build/yourchat /opt/yourchat/ +sudo cp build/ws_probe /opt/yourchat/ +sudo chmod +x /opt/yourchat/yourchat +sudo chmod +x /opt/yourchat/ws_probe + +# Konfiguration installieren (nur wenn nicht vorhanden) +echo "=== Konfiguration installieren ===" +if [ ! -f "/opt/yourchat/config/chatconfig.json" ]; then + sudo cp config/chatconfig.json /opt/yourchat/config/ + echo "Konfigurationsdatei installiert." +else + echo "Konfigurationsdatei existiert bereits - wird nicht überschrieben." +fi + +# Systemd Service erstellen +echo "=== Systemd Service erstellen ===" +sudo tee /etc/systemd/system/yourchat.service > /dev/null </dev/null; then + sudo useradd -r -s /bin/false -d /opt/yourchat yourchat + echo "Benutzer 'yourchat' erstellt." +else + echo "Benutzer 'yourchat' existiert bereits." +fi + +# Berechtigungen setzen +echo "=== Berechtigungen setzen ===" +sudo chown -R yourchat:yourchat /opt/yourchat +sudo chmod 755 /opt/yourchat +sudo chmod 644 /opt/yourchat/config/chatconfig.json + +# Systemd Service aktivieren +echo "=== Systemd Service aktivieren ===" +sudo systemctl daemon-reload +sudo systemctl enable yourchat.service + +echo "=== Installation abgeschlossen! ===" +echo "" +echo "Nächste Schritte:" +echo "1. Konfiguration anpassen: sudo nano /opt/yourchat/config/chatconfig.json" +echo "2. Service starten: sudo systemctl start yourchat" +echo "3. Status prüfen: sudo systemctl status yourchat" +echo "4. Logs anzeigen: sudo journalctl -u yourchat -f" diff --git a/deploy/install_dependencies.sh b/deploy/install_dependencies.sh new file mode 100755 index 0000000..5801161 --- /dev/null +++ b/deploy/install_dependencies.sh @@ -0,0 +1,49 @@ +#!/bin/bash + +# YourChat Deployment Script - Abhängigkeiten installieren +# Für Ubuntu 22.04 LTS + +set -e # Beende bei Fehlern + +echo "=== YourChat - Installation der Abhängigkeiten ===" +echo "System wird aktualisiert..." + +# System aktualisieren +sudo apt update +sudo apt upgrade -y + +echo "=== GCC-13 Repository hinzufügen ===" +# GCC-13 Repository hinzufügen +sudo add-apt-repository ppa:ubuntu-toolchain-r/test -y +sudo apt update + +echo "=== Grundlegende Build-Tools installieren ===" +# Grundlegende Build-Tools +sudo apt install -y build-essential cmake pkg-config + +echo "=== GCC-13 installieren ===" +# GCC-13 installieren +sudo apt install -y gcc-13 g++-13 + +echo "=== GCC-13 als Standard setzen ===" +# GCC-13 als Standard setzen +sudo update-alternatives --install /usr/bin/gcc gcc /usr/bin/gcc-13 130 --slave /usr/bin/g++ g++ /usr/bin/g++-13 +sudo update-alternatives --install /usr/bin/cc cc /usr/bin/gcc-13 130 +sudo update-alternatives --install /usr/bin/c++ c++ /usr/bin/g++-13 130 + +echo "=== Bibliotheken installieren ===" +# Benötigte Bibliotheken +sudo apt install -y libssl-dev libjsoncpp-dev libpqxx-dev + +echo "=== PostgreSQL installieren (falls nicht vorhanden) ===" +# PostgreSQL (falls Datenbank lokal laufen soll) +sudo apt install -y postgresql postgresql-contrib + +echo "=== Überprüfung der Installation ===" +echo "GCC Version:" +gcc --version +echo "" +echo "CMake Version:" +cmake --version +echo "" +echo "Installation der Abhängigkeiten abgeschlossen!" diff --git a/deploy/update.sh b/deploy/update.sh new file mode 100755 index 0000000..d098c1e --- /dev/null +++ b/deploy/update.sh @@ -0,0 +1,52 @@ +#!/bin/bash + +# YourChat Update Script +# Für zukünftige Updates der Anwendung + +set -e # Beende bei Fehlern + +echo "=== YourChat - Update Script ===" +echo "Dieses Script aktualisiert eine bestehende Installation." +echo "" + +# Prüfe ob wir im richtigen Verzeichnis sind +if [ ! -f "CMakeLists.txt" ]; then + echo "Fehler: CMakeLists.txt nicht gefunden. Bitte im YourChat-Root-Verzeichnis ausführen." + exit 1 +fi + +# Prüfe ob Anwendung installiert ist +if [ ! -f "/opt/yourchat/yourchat" ]; then + echo "Fehler: YourChat ist nicht installiert. Bitte zuerst ./deploy/deploy.sh ausführen." + exit 1 +fi + +echo "=== Service stoppen ===" +sudo systemctl stop yourchat || true + +echo "=== Anwendung bauen ===" +./deploy/build.sh + +echo "=== Anwendung aktualisieren ===" +echo "Kopiere neue Binaries..." +sudo cp build/yourchat /opt/yourchat/ +sudo cp build/ws_probe /opt/yourchat/ +sudo chmod +x /opt/yourchat/yourchat +sudo chmod +x /opt/yourchat/ws_probe +sudo chown yourchat:yourchat /opt/yourchat/yourchat +sudo chown yourchat:yourchat /opt/yourchat/ws_probe + +echo "=== Konfiguration aktualisieren ===" +./deploy/update_config.sh + +echo "=== Service neu starten ===" +sudo systemctl start yourchat + +echo "=== Update abgeschlossen! ===" +echo "" +echo "Status prüfen:" +sudo systemctl status yourchat + +echo "" +echo "Logs anzeigen:" +echo "sudo journalctl -u yourchat -f" diff --git a/deploy/update_config.sh b/deploy/update_config.sh new file mode 100755 index 0000000..db424db --- /dev/null +++ b/deploy/update_config.sh @@ -0,0 +1,160 @@ +#!/bin/bash + +# YourChat Konfigurations-Update Script +# Ergänzt fehlende Einträge in der Konfigurationsdatei ohne bestehende zu überschreiben + +set -e # Beende bei Fehlern + +echo "=== YourChat - Konfigurations-Update Script ===" + +CONFIG_SOURCE="config/chatconfig.json" +CONFIG_TARGET="/opt/yourchat/config/chatconfig.json" + +# Prüfe ob Quell-Konfiguration existiert +if [ ! -f "$CONFIG_SOURCE" ]; then + echo "Fehler: Quell-Konfiguration $CONFIG_SOURCE nicht gefunden." + exit 1 +fi + +# Prüfe ob Ziel-Konfiguration existiert +if [ ! -f "$CONFIG_TARGET" ]; then + echo "Ziel-Konfiguration nicht gefunden. Kopiere komplette Konfiguration..." + sudo cp "$CONFIG_SOURCE" "$CONFIG_TARGET" + sudo chown yourchat:yourchat "$CONFIG_TARGET" + sudo chmod 644 "$CONFIG_TARGET" + echo "Konfiguration kopiert." + exit 0 +fi + +echo "=== Aktualisiere Konfiguration (fehlende Einträge ergänzen) ===" + +# Temporäre Dateien erstellen +TEMP_SOURCE=$(mktemp) +TEMP_TARGET=$(mktemp) +TEMP_MERGED=$(mktemp) + +# JSON-Dateien in temporäre Dateien kopieren +cp "$CONFIG_SOURCE" "$TEMP_SOURCE" +cp "$CONFIG_TARGET" "$TEMP_TARGET" + +# Python-Script für JSON-Merge erstellen +cat > /tmp/merge_config.py << 'EOF' +#!/usr/bin/env python3 +import json +import sys + +def merge_json(source_file, target_file, output_file): + """Führt JSON-Dateien zusammen, ohne bestehende Einträge zu überschreiben""" + + with open(source_file, 'r') as f: + source_data = json.load(f) + + with open(target_file, 'r') as f: + target_data = json.load(f) + + def merge_dict(source, target): + """Rekursiv Dictionaries zusammenführen""" + result = target.copy() + + for key, value in source.items(): + if key not in result: + # Neuer Schlüssel - hinzufügen + result[key] = value + elif isinstance(value, dict) and isinstance(result[key], dict): + # Beide sind Dictionaries - rekursiv zusammenführen + result[key] = merge_dict(value, result[key]) + elif isinstance(value, list) and isinstance(result[key], list): + # Beide sind Listen - nur neue Einträge hinzufügen + existing_items = set() + for item in result[key]: + if isinstance(item, dict) and 'name' in item: + existing_items.add(item['name']) + else: + existing_items.add(str(item)) + + for item in value: + if isinstance(item, dict) and 'name' in item: + if item['name'] not in existing_items: + result[key].append(item) + else: + if str(item) not in existing_items: + result[key].append(item) + # Für andere Typen: bestehenden Wert beibehalten + + return result + + merged_data = merge_dict(source_data, target_data) + + with open(output_file, 'w') as f: + json.dump(merged_data, f, indent=4, ensure_ascii=False) + + return merged_data + +if __name__ == "__main__": + if len(sys.argv) != 4: + print("Verwendung: python3 merge_config.py ") + sys.exit(1) + + source_file = sys.argv[1] + target_file = sys.argv[2] + output_file = sys.argv[3] + + try: + merged_data = merge_json(source_file, target_file, output_file) + print("Konfiguration erfolgreich zusammengeführt!") + + # Zeige Änderungen an + print("\nNeue/aktualisierte Einträge:") + with open(target_file, 'r') as f: + original_data = json.load(f) + + def show_changes(source, target, path=""): + for key, value in source.items(): + current_path = f"{path}.{key}" if path else key + + if key not in target: + print(f" + {current_path}: {value}") + elif isinstance(value, dict) and isinstance(target[key], dict): + show_changes(value, target[key], current_path) + elif isinstance(value, list) and isinstance(target[key], list): + # Zeige neue Listeneinträge + existing_names = set() + for item in target[key]: + if isinstance(item, dict) and 'name' in item: + existing_names.add(item['name']) + + for item in value: + if isinstance(item, dict) and 'name' in item: + if item['name'] not in existing_names: + print(f" + {current_path}: {item}") + + show_changes(merged_data, original_data) + + except Exception as e: + print(f"Fehler beim Zusammenführen: {e}") + sys.exit(1) +EOF + +# Python-Script ausführbar machen und ausführen +chmod +x /tmp/merge_config.py + +echo "Führe Konfigurationen zusammen..." +python3 /tmp/merge_config.py "$TEMP_SOURCE" "$TEMP_TARGET" "$TEMP_MERGED" + +# Backup der alten Konfiguration erstellen +echo "Erstelle Backup der alten Konfiguration..." +sudo cp "$CONFIG_TARGET" "${CONFIG_TARGET}.backup.$(date +%Y%m%d_%H%M%S)" + +# Neue Konfiguration installieren +echo "Installiere neue Konfiguration..." +sudo cp "$TEMP_MERGED" "$CONFIG_TARGET" +sudo chown yourchat:yourchat "$CONFIG_TARGET" +sudo chmod 644 "$CONFIG_TARGET" + +# Aufräumen +rm -f "$TEMP_SOURCE" "$TEMP_TARGET" "$TEMP_MERGED" /tmp/merge_config.py + +echo "=== Konfigurations-Update abgeschlossen! ===" +echo "Backup erstellt: ${CONFIG_TARGET}.backup.*" +echo "" +echo "Service neu starten mit: sudo systemctl restart yourchat" diff --git a/src/core/chat_room.cpp b/src/core/chat_room.cpp index 0ed4db5..3115432 100755 --- a/src/core/chat_room.cpp +++ b/src/core/chat_room.cpp @@ -94,13 +94,7 @@ namespace Yc newUser->start(); Json::Value roomList = _parent->jsonRoomList(); newUser->sendMsg(ChatUser::roomList, roomList, "", ""); - // Private Rückmeldung an den User: In welchem Raum befindet er/sie sich jetzt? - { - Json::Value msg = Json::objectValue; - msg["tr"] = "room_entered"; - msg["to"] = _name; - newUser->sendMsg(ChatUser::system, msg, "", ""); - } + // Sende aktuelle Userliste an den neuen User Json::Value currentUserList = userList(); newUser->sendMsg(ChatUser::userListe, currentUserList, "", ""); @@ -112,8 +106,11 @@ namespace Yc } } - // Broadcast an andere Nutzer: Benutzer X hat den Raum betreten (mit Farbinfo) - addMessage(ChatUser::system, "user_entered_room", newUser->name(), newUser->color()); + // Broadcast an andere Nutzer: Benutzer X hat den Chat betreten (mit Farbinfo) + #ifdef YC_DEBUG + std::cout << "[Debug] addUser: Sending 'user_entered_chat' message for user: " << newUser->name() << std::endl; + #endif + addMessage(ChatUser::system, "user_entered_chat", newUser->name(), newUser->color()); _initRound(); return true; } @@ -174,6 +171,20 @@ namespace Yc } } + void ChatRoom::removeUserDisconnected(std::shared_ptr userToRemove) + { + for (auto it = _users.begin(); it != _users.end(); ++it) + { + if (*it == userToRemove) + { + // Spezielle Nachricht für Verbindungsabbrüche + addMessage(ChatUser::system, std::string("user_disconnected"), (*it)->name(), (*it)->color()); + _users.erase(it); + break; + } + } + } + void ChatRoom::setStop() { _stop = true; diff --git a/src/core/chat_room.h b/src/core/chat_room.h index 0b158cf..7125550 100755 --- a/src/core/chat_room.h +++ b/src/core/chat_room.h @@ -45,6 +45,7 @@ namespace Yc bool userNameExists(std::string userName); void removeUser(std::string _token, bool silent = false); void removeUser(std::shared_ptr user, bool silent = false); + void removeUserDisconnected(std::shared_ptr userToRemove); void setStop(); void addMessage(ChatUser::MsgType type, const char *messageText, std::string userName = "", std::string color = ""); void addMessage(ChatUser::MsgType type, std::string messageText, std::string userName = "", std::string color = ""); diff --git a/src/core/chat_user.cpp b/src/core/chat_user.cpp index 9468177..8d1d131 100644 --- a/src/core/chat_user.cpp +++ b/src/core/chat_user.cpp @@ -111,6 +111,11 @@ namespace Yc return _name; } + std::string ChatUser::getToken() const + { + return _token; + } + bool ChatUser::validateToken(std::string token) { return (token == _token); @@ -155,16 +160,57 @@ namespace Yc void ChatUser::checkerTask() { try { + // Heartbeat-Intervall: Alle 10 Sekunden Verbindung prüfen + const int HEARTBEAT_INTERVAL = 2; + int heartbeatCounter = 0; + while (!_stop) { - fd_set readSd; - FD_ZERO(&readSd); - FD_SET(_socket, &readSd); - timeval tv; - tv.tv_sec = 0; - tv.tv_usec = 500; - int selectResult = select(_socket + 1, &readSd, NULL, NULL, &tv); - if (selectResult == 1 && FD_ISSET(_socket, &readSd) == 1) + std::this_thread::sleep_for(std::chrono::seconds(1)); + + // Heartbeat-Check alle 10 Sekunden + heartbeatCounter++; + if (heartbeatCounter >= HEARTBEAT_INTERVAL) { + heartbeatCounter = 0; + + // Prüfe Verbindung mit MSG_PEEK (nicht-blockierend) + char peek; + ssize_t r = recv(_socket, &peek, 1, MSG_PEEK | MSG_DONTWAIT); + + if (r == 0) { + #ifdef YC_DEBUG + std::cout << "[Debug] Verbindung zum Client abgebrochen (Token: )" << std::endl; + #endif + _parent->removeUserDisconnected(shared_from_this()); + _stop = true; + if (thread.joinable() && std::this_thread::get_id() == thread.get_id()) { + thread.detach(); + } + return; + } else if (r < 0) { + // EINTR = Interrupted system call (normal), EAGAIN/EWOULDBLOCK = No data available (normal) + // Andere Fehler bedeuten Verbindungsabbruch + if (errno != EINTR && errno != EAGAIN && errno != EWOULDBLOCK) { + #ifdef YC_DEBUG + std::cout << "[Debug] Socket-Fehler: " << strerror(errno) << " (Token: )" << std::endl; + #endif + _parent->removeUserDisconnected(shared_from_this()); + _stop = true; + if (thread.joinable() && std::this_thread::get_id() == thread.get_id()) { + thread.detach(); + } + return; + } + } + + // Optional: Sende Heartbeat-Ping an Client + // (kann helfen, NAT/Firewall-Verbindungen aktiv zu halten) + #ifdef YC_DEBUG + std::cout << "[Debug] Heartbeat check passed for user: " << _name << std::endl; + #endif + } + + // Ursprüngliche Verbindungsprüfung (alle 1 Sekunde) { char peek; ssize_t r = recv(_socket, &peek, 1, MSG_PEEK); @@ -172,7 +218,7 @@ namespace Yc #ifdef YC_DEBUG std::cout << "[Debug] Verbindung zum Client abgebrochen (Token: )" << std::endl; #endif - _parent->removeUser(_token); + _parent->removeUserDisconnected(shared_from_this()); _stop = true; if (thread.joinable() && std::this_thread::get_id() == thread.get_id()) { thread.detach(); @@ -180,6 +226,7 @@ namespace Yc return; } } + std::string msg = readSocket(_socket); if (msg == "") { @@ -347,7 +394,7 @@ namespace Yc } else if (jsonTree["type"].asString() == "join") { - changeRoom(jsonTree["newroom"].asString(), jsonTree["password"].asString()); + changeRoom(jsonTree["room"].asString(), jsonTree["password"].asString()); } else if (jsonTree["type"].asString() == "userlist") { diff --git a/src/core/chat_user.h b/src/core/chat_user.h index 8275bac..7d3d199 100644 --- a/src/core/chat_user.h +++ b/src/core/chat_user.h @@ -36,6 +36,7 @@ namespace Yc ChatUser(std::shared_ptr parent, std::string name, std::string color, int socket); ~ChatUser(); std::string name() const; + std::string getToken() const; bool validateToken(std::string token); bool isUser(std::shared_ptr toValidate); void sendMsg(MsgType type, std::string message, std::string userName, std::string color); diff --git a/src/core/server.cpp b/src/core/server.cpp index 510247f..49fea4a 100755 --- a/src/core/server.cpp +++ b/src/core/server.cpp @@ -147,11 +147,29 @@ namespace Yc { } bool Server::roomAllowed(std::string roomName, std::string userName, std::string password){ + #ifdef YC_DEBUG + std::cout << "[Debug] roomAllowed called with roomName: '" << roomName << "', userName: '" << userName << "'" << std::endl; + std::cout << "[Debug] Available rooms: "; for (auto &room: _rooms) { - if (room->name() == roomName && room->accessAllowed(userName, password)) { + std::cout << "'" << room->name() << "' "; + } + std::cout << std::endl; + #endif + + for (auto &room: _rooms) { + #ifdef YC_DEBUG + std::cout << "[Debug] Checking room: '" << room->name() << "' against requested: '" << roomName << "'" << std::endl; + #endif + if (room->name() == roomName && room->accessAllowed(userName, password)) { + #ifdef YC_DEBUG + std::cout << "[Debug] Room found and access allowed!" << std::endl; + #endif return true; } } + #ifdef YC_DEBUG + std::cout << "[Debug] Room not found or access denied" << std::endl; + #endif return false; } @@ -159,42 +177,97 @@ namespace Yc { if (!roomAllowed(newRoom, user->name(), password)) { return false; } + + std::string oldRoomName = ""; + // Finde den aktuellen Raum des Users + for (auto &room: _rooms) { + if (room->userIsInRoom(user->name())) { + oldRoomName = room->name(); + break; + } + } + Json::Value userMsg = Json::objectValue; userMsg["tr"] = "room_change_user"; userMsg["to"] = newRoom; + + // Nur Nachrichten senden, wenn der User bereits in einem Raum ist (Raumwechsel) + if (!oldRoomName.empty()) { + for (auto &room: _rooms) { + if (room->name() == oldRoomName) { + // Sende Nachricht an alle User im alten Raum, dass der User den Raum verlassen hat + Json::Value leaveMsg = Json::objectValue; + leaveMsg["tr"] = "user_left_room"; + leaveMsg["userName"] = user->name(); + leaveMsg["userColor"] = user->color(); + leaveMsg["destination"] = newRoom; + room->addMessage(ChatUser::system, leaveMsg, "", ""); + } + } + } + + // Entferne User aus dem alten Raum for (auto &room: _rooms) { if (room->userIsInRoom(user->name())) { - room->removeUser(user); - Json::Value msg = Json::objectValue; - msg["tr"] = "room_change_to"; - msg["to"] = newRoom; - userMsg["from"] = room->name(); - room->addMessage(ChatUser::system, msg, user->name(), user->color()); + room->removeUser(user->getToken(), true); // silent = true, da wir eigene Nachrichten senden + break; } } - user->sendMsg(ChatUser::system, userMsg, "", ""); + + // Füge User zum neuen Raum hinzu for (auto &room: _rooms) { if (room->name() == newRoom) { - Json::Value msg = Json::objectValue; - msg["tr"] = "room_change_to"; - msg["from"] = userMsg["from"]; - room->addMessage(ChatUser::system, msg, user->name(), user->color()); + #ifdef YC_DEBUG + std::cout << "[Debug] changeRoom: Adding user '" << user->name() << "' to room '" << newRoom << "'" << std::endl; + std::cout << "[Debug] changeRoom: oldRoomName = '" << (oldRoomName.empty() ? "EMPTY" : oldRoomName) << "'" << std::endl; + #endif + room->addUserWhenQueueEmpty(user); + + // Nur bei Raumwechsel (nicht beim ersten Beitritt) die Nachricht senden + if (!oldRoomName.empty()) { + #ifdef YC_DEBUG + std::cout << "[Debug] changeRoom: Sending 'user_entered_room' message (room change)" << std::endl; + #endif + Json::Value joinMsg = Json::objectValue; + joinMsg["tr"] = "user_entered_room"; + joinMsg["userName"] = user->name(); + joinMsg["userColor"] = user->color(); + joinMsg["origin"] = oldRoomName; + room->addMessage(ChatUser::system, joinMsg, "", ""); + } else { + #ifdef YC_DEBUG + std::cout << "[Debug] changeRoom: NOT sending 'user_entered_room' message (first join)" << std::endl; + #endif + } } } + return true; } void Server::createRooms(Json::Value roomList) { auto self = shared_from_this(); bool created = false; + + #ifdef YC_DEBUG + std::cout << "[Debug] createRooms called with roomList size: " << roomList.size() << std::endl; + std::cout << "[Debug] roomList content: " << roomList << std::endl; + #endif + try { + #ifdef YC_DEBUG + std::cout << "[Debug] Attempting to load rooms from database..." << std::endl; + #endif std::string query = R"( SELECT r.id, r.title, r.password_hash, r.room_type_id, r.is_public, r.owner_id, r.min_age, r.max_age, r.created_at, r.updated_at, rt.tr as room_type FROM chat.room r LEFT JOIN chat.room_type rt ON r.room_type_id = rt.id )"; auto result = _database->exec(query); + #ifdef YC_DEBUG + std::cout << "[Debug] Database query result size: " << result.size() << std::endl; + #endif for (const auto& row : result) { Json::Value room; room["id"] = row["id"].as(); @@ -214,19 +287,41 @@ namespace Yc { auto newRoom = std::make_shared(self, room); _rooms.push_back(newRoom); created = true; + #ifdef YC_DEBUG + std::cout << "[Debug] Created room from database: " << room["name"].asString() << std::endl; + #endif } + } catch (const std::exception& e) { + #ifdef YC_DEBUG + std::cout << "[Debug] Database error: " << e.what() << std::endl; + #endif } catch (...) { - // ignore DB errors, fallback below + #ifdef YC_DEBUG + std::cout << "[Debug] Unknown database error occurred" << std::endl; + #endif } + if (!created) { + #ifdef YC_DEBUG + std::cout << "[Debug] Database loading failed, trying fallback rooms..." << std::endl; + #endif // fallback to provided JSON room list (if any) if (roomList.isArray() && roomList.size() > 0) { + #ifdef YC_DEBUG + std::cout << "[Debug] Loading " << roomList.size() << " fallback rooms from config" << std::endl; + #endif for (const auto& room : roomList) { + #ifdef YC_DEBUG + std::cout << "[Debug] Creating fallback room: " << room["name"].asString() << std::endl; + #endif auto newRoom = std::make_shared(self, room); _rooms.push_back(newRoom); created = true; } } else { + #ifdef YC_DEBUG + std::cout << "[Debug] No fallback rooms in config, creating default room" << std::endl; + #endif // final fallback: builtin default room Json::Value room; room["name"] = "Halle"; @@ -239,6 +334,13 @@ namespace Yc { created = true; } } + + #ifdef YC_DEBUG + std::cout << "[Debug] Total rooms created: " << _rooms.size() << std::endl; + for (const auto& room : _rooms) { + std::cout << "[Debug] Room: " << room->name() << std::endl; + } + #endif } void Server::handleRequest() { @@ -260,10 +362,36 @@ namespace Yc { std::cout << "[YourChat] Verbindung akzeptiert von " << clientIP << ":" << ntohs(sockAddr.sin_port) << " (fd=" << userSock << ")" << std::endl; int flags = 1; setsockopt(userSock, IPPROTO_TCP, TCP_NODELAY, (void *)&flags, sizeof(flags)); - int ka2 = 1; setsockopt(userSock, SOL_SOCKET, SO_KEEPALIVE, &ka2, sizeof(ka2)); + + // Aggressive TCP Keep-Alive für schnelle Verbindungsabbruch-Erkennung + int ka2 = 1; + setsockopt(userSock, SOL_SOCKET, SO_KEEPALIVE, &ka2, sizeof(ka2)); + + // Keep-Alive Parameter: 5 Sekunden bis zum ersten Probe, dann alle 2 Sekunden + int keepalive_time = 5; // 5 Sekunden bis zum ersten Probe + int keepalive_intvl = 2; // 2 Sekunden zwischen Probes + int keepalive_probes = 3; // 3 Probes bevor Verbindung als tot betrachtet wird + + setsockopt(userSock, IPPROTO_TCP, TCP_KEEPIDLE, &keepalive_time, sizeof(keepalive_time)); + setsockopt(userSock, IPPROTO_TCP, TCP_KEEPINTVL, &keepalive_intvl, sizeof(keepalive_intvl)); + setsockopt(userSock, IPPROTO_TCP, TCP_KEEPCNT, &keepalive_probes, sizeof(keepalive_probes)); + // Begrenze Blockierzeit beim Senden, um langsame Clients nicht alle zu verzögern timeval sendTimeout; sendTimeout.tv_sec = 0; sendTimeout.tv_usec = 500000; // 500ms setsockopt(userSock, SOL_SOCKET, SO_SNDTIMEO, &sendTimeout, sizeof(sendTimeout)); + + // Socket-Optionen für schnellere Fehlererkennung + int reuseAddr = 1; + setsockopt(userSock, SOL_SOCKET, SO_REUSEADDR, &reuseAddr, sizeof(reuseAddr)); + + // LINGER-Option: Sofort schließen bei Verbindungsabbruch + struct linger linger_opt; + linger_opt.l_onoff = 1; + linger_opt.l_linger = 0; // 0 Sekunden = sofort schließen + setsockopt(userSock, SOL_SOCKET, SO_LINGER, &linger_opt, sizeof(linger_opt)); + + // TCP_NODELAY bereits gesetzt (oben) + // TCP Keep-Alive bereits konfiguriert (oben) std::string msg = readSocket(userSock); #ifdef YC_DEBUG std::cout << "[Debug] Neue Anfrage erhalten: " << msg << std::endl;