From f75a6f04dd822c7eb88f66792c663a1f4068a407 Mon Sep 17 00:00:00 2001 From: Valentin Korenjak Date: Fri, 27 Mar 2026 21:03:46 +0100 Subject: [PATCH] Server-side events (SSE) namesto pollinga #14 --- projector.py | 4 +++- web/server.py | 42 +++++++++++++++++++++++++++++++++++++++++- web/static/script.js | 23 +++++++++++++++++++---- 3 files changed, 63 insertions(+), 6 deletions(-) diff --git a/projector.py b/projector.py index 130a76b..333076c 100755 --- a/projector.py +++ b/projector.py @@ -29,7 +29,7 @@ import subprocess import sys import ctypes import tkinter.messagebox as messagebox -from web.server import start_server_thread +from web.server import start_server_thread, notify_clients import urllib.request import tempfile from db_schema import create_tables @@ -476,6 +476,7 @@ class SongProjector: self.song_info_label.lift() else: self.song_info_label.config(text="") + notify_clients() # ------------------------------------------------------ # Navigacija po straneh @@ -504,6 +505,7 @@ class SongProjector: self.color_frame.config(bg="black", width=self.color_width, height=self.screen_height) self.color_frame.place(relx=0.5, rely=0.5, anchor="center") self.song_info_label.config(text="") + notify_clients() # ------------------------------------------------------ # Iskanje po naslovu diff --git a/web/server.py b/web/server.py index cce6bc3..9ba3294 100644 --- a/web/server.py +++ b/web/server.py @@ -28,8 +28,10 @@ than being generated inside Python code. import threading import os import json +import queue +import time -from flask import Flask, render_template, request, jsonify +from flask import Flask, render_template, request, jsonify, Response # create Flask app with proper folders relative to this file app = Flask(__name__, static_folder="static", template_folder="templates") @@ -37,6 +39,17 @@ app = Flask(__name__, static_folder="static", template_folder="templates") # Globalna referenca na SongProjector aplikaciju _projector_app = None +# List of queues for SSE clients +_sse_clients = [] +_sse_lock = threading.Lock() + + +def notify_clients(): + """Notify all connected SSE clients to refresh content""" + with _sse_lock: + for q in _sse_clients: + q.put("refresh content") + def set_projector_app(projector_app): """Postavi referenco na SongProjector aplikacijo""" @@ -97,6 +110,26 @@ def get_state(): }) +@app.route('/api/events') +def sse_events(): + """SSE endpoint for real-time updates""" + def event_stream(): + q = queue.Queue() + with _sse_lock: + _sse_clients.append(q) + try: + # Send initial refresh command on connection + yield "data: refresh content\n\n" + while True: + msg = q.get() + yield f"data: {msg}\n\n" + finally: + with _sse_lock: + _sse_clients.remove(q) + + return Response(event_stream(), mimetype="text/event-stream") + + @app.route('/api/load_song', methods=['POST']) def load_song(): """Naloži pesem po številki""" @@ -111,6 +144,7 @@ def load_song(): _projector_app.load_song() if hasattr(_projector_app, 'show_page'): _projector_app.show_page() + notify_clients() return jsonify({'status': 'ok'}) @@ -120,6 +154,7 @@ def next_page(): """Naslednja stran""" if _projector_app is not None: _projector_app.next_page() + notify_clients() return jsonify({'status': 'ok'}) @@ -129,6 +164,7 @@ def prev_page(): """Prejšnja stran""" if _projector_app is not None: _projector_app.prev_page() + notify_clients() return jsonify({'status': 'ok'}) @@ -138,6 +174,7 @@ def clear_screen(): """Zatemniti ekran""" if _projector_app is not None: _projector_app.clear_screen() + notify_clients() return jsonify({'status': 'ok'}) @@ -148,6 +185,7 @@ def toggle_caps(): if _projector_app is not None: _projector_app.all_caps_mode = not _projector_app.all_caps_mode _projector_app.show_page() + notify_clients() return jsonify({'status': 'ok'}) @@ -157,6 +195,7 @@ def toggle_split(): """Preklop med načinom preloma po kiticah in prostim prelomom""" if _projector_app is not None: _projector_app.toggle_split_mode() + notify_clients() return jsonify({'status': 'ok'}) @@ -283,6 +322,7 @@ def update_song(): if str(_projector_app.song_number_last) == str(song_id): _projector_app.song_number = str(song_id) _projector_app.load_song() + notify_clients() return jsonify({'status': 'ok'}) except Exception as e: diff --git a/web/static/script.js b/web/static/script.js index 59a5092..a864c37 100644 --- a/web/static/script.js +++ b/web/static/script.js @@ -648,9 +648,24 @@ document.addEventListener('keydown', (e) => { updateState(true); requestWakeLock(); -// osveževanje za sinhronizacijo med več napravami -setInterval(() => { - updateState(false); -}, 1000); +// SSE osveževanje za sinhronizacijo med več napravami +function setupSSE() { + const evtSource = new EventSource("/api/events"); + + evtSource.onmessage = function(event) { + console.log("SSE dogodek:", event.data); + if (event.data === "refresh content") { + updateState(false); + } + }; + + evtSource.onerror = function(err) { + console.error("SSE napaka, ponovni poskus čez 5s...", err); + evtSource.close(); + setTimeout(setupSSE, 5000); + }; +} + +setupSSE(); console.log('JavaScript inicializacija zaključena'); \ No newline at end of file