Files
Projekcija/web/server.py

259 lines
7.9 KiB
Python

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""Flask web server for the song projector application.
HTML, CSS and JavaScript are now stored in separate template/static files rather
than being generated inside Python code.
"""
import threading
import os
from flask import Flask, render_template, request, jsonify
# create Flask app with proper folders relative to this file
app = Flask(__name__, static_folder="static", template_folder="templates")
# Globalna referenca na SongProjector aplikaciju
_projector_app = None
def set_projector_app(projector_app):
"""Postavi referenco na SongProjector aplikacijo"""
global _projector_app
_projector_app = projector_app
# ----------------------------------------------------------
# Flask rute
# ----------------------------------------------------------
@app.route("/", methods=["GET"])
def index():
"""Prikaže glavno HTML stranko"""
# predaja nadzora Jinja2, ki poišče web/templates/index.html
return render_template("index.html")
@app.route('/api/state', methods=['GET'])
def get_state():
"""Vrati trenutno stanje aplikacije"""
if _projector_app is None:
return jsonify({
'current_text': 'Napaka: Aplikacija ni inicijalizirana',
'page_info': '',
'caps_mode': False,
'can_prev': False,
'can_next': False
})
current_text = ""
page_info = ""
can_prev = False
can_next = False
if _projector_app.pages:
current_text = _projector_app.pages[_projector_app.current_page_index]
if _projector_app.all_caps_mode:
current_text = current_text.upper()
current_page = _projector_app.current_page_index + 1
total_pages = len(_projector_app.pages)
page_info = f"{_projector_app.song_number_last} {current_page}/{total_pages}"
can_prev = _projector_app.current_page_index > 0
can_next = _projector_app.current_page_index + 1 < len(_projector_app.pages)
else:
current_text = "Pripravljeno. Vpiši številko pesmi ali drugega besedila."
return jsonify({
'current_text': current_text,
'page_info': page_info,
'caps_mode': _projector_app.all_caps_mode,
'can_prev': can_prev,
'can_next': can_next
})
@app.route('/api/load_song', methods=['POST'])
def load_song():
"""Naloži pesem po številki"""
if _projector_app is None:
return jsonify({'status': 'error', 'message': 'Aplikacija ni inicijalizirana'})
data = request.get_json()
song_number = data.get('song_number', '').strip()
if song_number:
_projector_app.song_number = song_number
_projector_app.load_song()
if hasattr(_projector_app, 'show_page'):
_projector_app.show_page()
return jsonify({'status': 'ok'})
@app.route('/api/next_page', methods=['POST'])
def next_page():
"""Naslednja stran"""
if _projector_app is not None:
_projector_app.next_page()
return jsonify({'status': 'ok'})
@app.route('/api/prev_page', methods=['POST'])
def prev_page():
"""Prejšnja stran"""
if _projector_app is not None:
_projector_app.prev_page()
return jsonify({'status': 'ok'})
@app.route('/api/clear_screen', methods=['POST'])
def clear_screen():
"""Zatemniti ekran"""
if _projector_app is not None:
_projector_app.clear_screen()
return jsonify({'status': 'ok'})
@app.route('/api/toggle_caps', methods=['POST'])
def toggle_caps():
"""Preklop med velikimi in malimi črkami"""
if _projector_app is not None:
_projector_app.all_caps_mode = not _projector_app.all_caps_mode
_projector_app.show_page()
return jsonify({'status': 'ok'})
@app.route('/api/search_songs', methods=['POST'])
def search_songs():
"""Iskanje besedil po naslovu"""
if _projector_app is None:
return jsonify({'results': []})
data = request.get_json()
query = data.get('query', '').strip()
if not query:
return jsonify({'results': []})
try:
_projector_app.cursor.execute(
"SELECT id, title FROM songs WHERE title LIKE ? COLLATE NOCASE",
(f"%{query}%",)
)
results = _projector_app.cursor.fetchall()
return jsonify({'results': results})
except Exception as e:
return jsonify({'error': str(e)})
@app.route('/api/get_song_details', methods=['GET'])
def get_song_details():
"""Vrne podrobnosti trenutno naložene pesmi ali pesmi po ID-ju"""
if _projector_app is None:
return jsonify({'status': 'error', 'message': 'Aplikacija ni inicijalizirana'})
song_id_param = request.args.get('id')
if song_id_param:
try:
song_id = int(song_id_param)
except ValueError:
return jsonify({'status': 'error', 'message': 'Neveljaven ID pesmi'})
elif _projector_app.song_number_last:
try:
song_id = int(_projector_app.song_number_last)
except ValueError:
return jsonify({'status': 'error', 'message': 'Neveljavna številka naložene pesmi'})
else:
return jsonify({'status': 'error', 'message': 'Nobena pesem ni naložena'})
try:
_projector_app.cursor.execute("SELECT id, title, lyrics FROM songs WHERE id=?", (song_id,))
result = _projector_app.cursor.fetchone()
if result:
return jsonify({
'status': 'ok',
'song': {
'id': result[0],
'title': result[1],
'lyrics': result[2]
}
})
else:
return jsonify({'status': 'error', 'message': f'Pesem s številko {song_id} ni najdena v bazi'})
except Exception as e:
return jsonify({'status': 'error', 'message': str(e)})
@app.route('/api/update_song', methods=['POST'])
def update_song():
"""Posodobi naslov in besedilo pesmi ali ustvari novo"""
if _projector_app is None:
return jsonify({'status': 'error', 'message': 'Aplikacija ni inicijalizirana'})
data = request.get_json()
song_id = data.get('id')
title = data.get('title', '').strip()
lyrics = data.get('lyrics', '').strip()
if not song_id or not title or not lyrics:
return jsonify({'status': 'error', 'message': 'Manjkajoči podatki'})
try:
if song_id == 'new':
# Pridobi prvo naslednjo prosto številko
_projector_app.cursor.execute("SELECT MAX(id) FROM songs")
max_id = _projector_app.cursor.fetchone()[0]
new_id = (max_id or 0) + 1
_projector_app.cursor.execute(
"INSERT INTO songs (id, title, lyrics) VALUES (?, ?, ?)",
(new_id, title, lyrics)
)
_projector_app.conn.commit()
# Naloži novo pesem
_projector_app.song_number = str(new_id)
_projector_app.load_song()
return jsonify({'status': 'ok', 'new_id': new_id})
# Obstoječa pesem
_projector_app.cursor.execute("UPDATE songs SET title=?, lyrics=? WHERE id=?", (title, lyrics, song_id))
_projector_app.conn.commit()
# Osvežimo trenutno pesem, če je to ta, ki smo jo pravkar uredili
if str(_projector_app.song_number_last) == str(song_id):
_projector_app.song_number = str(song_id)
_projector_app.load_song()
return jsonify({'status': 'ok'})
except Exception as e:
return jsonify({'status': 'error', 'message': str(e)})
def run_server(host='127.0.0.1', port=5000):
"""Zaženi Flask server"""
app.run(host=host, port=port, debug=False, use_reloader=False, threaded=True)
def start_server_thread(projector_app, host='127.0.0.1', port=5000):
"""Zaženi server v zvjenoj niti"""
set_projector_app(projector_app)
server_thread = threading.Thread(
target=run_server,
args=(host, port),
daemon=True
)
server_thread.start()
return server_thread