# deck_endpoints.py from flask import Blueprint, request, jsonify import sqlite3 import os import shutil import logging deck_bp = Blueprint('deck_bp', __name__) DATABASE = 'mydatabase.db' # Logger konfigurieren (angenommen, ocr_server3.py konfiguriert das Logging) logger = logging.getLogger(__name__) def get_db_connection(): conn = sqlite3.connect(DATABASE) conn.row_factory = sqlite3.Row return conn # Erstellen der Tabelle Deck, falls sie nicht existiert def init_db(): conn = get_db_connection() cursor = conn.cursor() # Tabelle Deck erstellen cursor.execute(''' CREATE TABLE IF NOT EXISTS Deck ( id INTEGER PRIMARY KEY AUTOINCREMENT, deckname TEXT NOT NULL, bildname TEXT, iconindex INTEGER, x1 REAL, x2 REAL, y1 REAL, y2 REAL ) ''') conn.commit() conn.close() def clean_debug_directories(): """ Löscht alle Verzeichnisse unter 'debug_images', die keinen Eintrag in der Deck-Tabelle mit bildname haben. """ debug_base_dir = 'debug_images' if not os.path.exists(debug_base_dir): logger.info(f"Debug-Verzeichnis '{debug_base_dir}' existiert nicht. Nichts zu bereinigen.") return try: conn = get_db_connection() cursor = conn.cursor() cursor.execute('SELECT DISTINCT bildname FROM Deck WHERE bildname IS NOT NULL') bildnames = {row['bildname'] for row in cursor.fetchall()} conn.close() except sqlite3.Error as e: logger.error(f"Fehler beim Abrufen der bildname aus der Datenbank: {e}") return # Durchlaufe alle Verzeichnisse unter 'debug_images' for dir_name in os.listdir(debug_base_dir): dir_path = os.path.join(debug_base_dir, dir_name) if os.path.isdir(dir_path): if dir_name not in bildnames: try: shutil.rmtree(dir_path) logger.info(f"Nicht verwendetes Debug-Verzeichnis gelöscht: {dir_path}") except Exception as e: logger.error(f"Fehler beim Löschen des Verzeichnisses '{dir_path}': {e}") else: logger.debug(f"Debug-Verzeichnis behalten: {dir_path}") logger.info("Bereinigung der Debug-Verzeichnisse abgeschlossen.") # ------ # Endpoints # ------ # ------ # Deck - POST, GET, DELETE # ------ @deck_bp.route('/api/decks', methods=['POST']) def create_deck(): data = request.get_json() if not data or 'deckname' not in data: return jsonify({'error': 'No deckname provided'}), 400 deckname = data['deckname'] conn = get_db_connection() cursor = conn.cursor() try: # Ein neuer Eintrag für das Deck ohne Bilddaten cursor.execute('INSERT INTO Deck (deckname) VALUES (?)', (deckname,)) conn.commit() deck_id = cursor.lastrowid conn.close() return jsonify({'status': 'success', 'deck_id': deck_id}), 201 except sqlite3.Error as e: conn.close() logger.error(f"Datenbankfehler beim Erstellen des Decks: {e}") return jsonify({'error': 'Database error', 'details': str(e)}), 500 @deck_bp.route('/api/decks', methods=['GET']) def get_decks(): conn = get_db_connection() cursor = conn.cursor() # Alle Einträge abrufen entries = cursor.execute('SELECT * FROM Deck').fetchall() conn.close() decks = {} for entry in entries: deckname = entry['deckname'] if deckname not in decks: decks[deckname] = { 'name': deckname, 'images': [] } if entry['bildname']: image = { 'name': entry['bildname'], 'iconindex': entry['iconindex'], 'x1': entry['x1'], 'x2': entry['x2'], 'y1': entry['y1'], 'y2': entry['y2'] } decks[deckname]['images'].append(image) deck_list = list(decks.values()) return jsonify(deck_list) @deck_bp.route('/api/decks/', methods=['DELETE']) def delete_deck(deckname): conn = get_db_connection() cursor = conn.cursor() try: # Überprüfen, ob das Deck existiert cursor.execute('SELECT COUNT(*) as count FROM Deck WHERE deckname = ?', (deckname,)) result = cursor.fetchone() if result['count'] == 0: conn.close() return jsonify({'error': 'Deck not found'}), 404 # Löschen aller Einträge mit dem gegebenen deckname cursor.execute('DELETE FROM Deck WHERE deckname = ?', (deckname,)) conn.commit() conn.close() return jsonify({'status': 'success'}), 200 except sqlite3.Error as e: conn.close() logger.error(f"Datenbankfehler beim Löschen des Decks '{deckname}': {e}") return jsonify({'error': 'Database error', 'details': str(e)}), 500 # ------ # Image - POST, GET, DELETE # ------ @deck_bp.route('/api/decks/image', methods=['POST']) def update_image(): data = request.get_json() if not data: return jsonify({'error': 'No data provided'}), 400 # Überprüfen, ob die erforderlichen Felder vorhanden sind required_fields = ['deckname', 'image', 'boxes'] if not all(field in data for field in required_fields): return jsonify({'error': 'Missing fields in data'}), 400 deckname = data['deckname'] bildname = data['image'] boxes = data['boxes'] # Überprüfen, ob 'boxes' eine Liste ist und mindestens ein Box-Element enthält if not isinstance(boxes, list) or len(boxes) == 0: return jsonify({'error': "'boxes' must be a non-empty list"}), 400 # Verbindung zur Datenbank herstellen conn = get_db_connection() cursor = conn.cursor() try: # Überprüfen, ob das Deck existiert cursor.execute('SELECT COUNT(*) as count FROM Deck WHERE deckname = ?', (deckname,)) result = cursor.fetchone() if result['count'] == 0: conn.close() return jsonify({'error': 'Deck not found'}), 404 # 1. Lösche alle Einträge für das Deck ohne bildname cursor.execute('DELETE FROM Deck WHERE deckname = ? AND bildname IS NULL', (deckname,)) inserted_image_ids = [] # 2. Füge neue Image-Einträge hinzu for index, box in enumerate(boxes): # Überprüfen, ob alle erforderlichen Koordinaten vorhanden sind box_fields = ['x1', 'x2', 'y1', 'y2'] if not all(field in box for field in box_fields): conn.close() return jsonify({'error': 'Missing fields in one of the boxes'}), 400 x1 = box['x1'] x2 = box['x2'] y1 = box['y1'] y2 = box['y2'] # Setzen des iconindex auf den aktuellen Index der Box iconindex = index cursor.execute(''' INSERT INTO Deck (deckname, bildname, iconindex, x1, x2, y1, y2) VALUES (?, ?, ?, ?, ?, ?, ?) ''', (deckname, bildname, iconindex, x1, x2, y1, y2)) inserted_image_ids.append(cursor.lastrowid) conn.commit() return jsonify({'status': 'success', 'inserted_image_ids': inserted_image_ids}), 201 except sqlite3.Error as e: conn.rollback() logger.error(f"Datenbankfehler beim Aktualisieren der Images für Deck '{deckname}': {e}") return jsonify({'error': 'Database error', 'details': str(e)}), 500 finally: conn.close() @deck_bp.route('/api/decks/image/', methods=['GET']) def get_images_by_bildname(bildname): conn = get_db_connection() cursor = conn.cursor() images = cursor.execute('SELECT * FROM Deck WHERE bildname = ?', (bildname,)).fetchall() conn.close() image_list = [dict(image) for image in images] return jsonify(image_list) @deck_bp.route('/api/decks/image/', methods=['DELETE']) def delete_images_by_bildname(bildname): """ Löscht alle Einträge in der Deck-Tabelle für den gegebenen bildname. Falls es das letzte Image für ein Deck ist, wird ein neuer Deck-Eintrag erstellt. Optional: Löscht das zugehörige Debug-Verzeichnis, wenn keine weiteren Einträge bestehen. """ try: # Sicherheitsmaßnahme: Nur erlaubte Zeichen im bildname if not all(c.isalnum() or c in ('_', '-') for c in bildname): logger.warning(f"Ungültiger bildname angefordert: {bildname}") return jsonify({'error': 'Invalid image name'}), 400 conn = get_db_connection() cursor = conn.cursor() # Start einer Transaktion conn.execute('BEGIN') # Schritt 1: Identifizieren der betroffenen Decks cursor.execute('SELECT DISTINCT deckname FROM Deck WHERE bildname = ?', (bildname,)) affected_decks = {row['deckname'] for row in cursor.fetchall()} if not affected_decks: conn.close() return jsonify({'error': 'No entries found for the given image name'}), 404 # Schritt 2: Löschen der Image-Einträge cursor.execute('DELETE FROM Deck WHERE bildname = ?', (bildname,)) logger.info(f"Alle Einträge für bildname '{bildname}' wurden gelöscht.") # Schritt 3: Überprüfen und Wiederherstellen der Deck-Einträge for deckname in affected_decks: # Überprüfen, ob noch andere Image-Einträge für dieses Deck existieren cursor.execute('SELECT COUNT(*) as count FROM Deck WHERE deckname = ? AND bildname IS NOT NULL', (deckname,)) result = cursor.fetchone() image_count = result['count'] if result else 0 if image_count == 0: # Überprüfen, ob bereits ein Deck-Eintrag existiert cursor.execute('SELECT COUNT(*) as count FROM Deck WHERE deckname = ? AND bildname IS NULL', (deckname,)) deck_entry = cursor.fetchone() if deck_entry['count'] == 0: # Ein neuer Deck-Eintrag wird erstellt cursor.execute('INSERT INTO Deck (deckname) VALUES (?)', (deckname,)) logger.info(f"Neuer Deck-Eintrag für '{deckname}' erstellt, da keine weiteren Images vorhanden sind.") # Commit der Transaktion conn.commit() # Schritt 4: Optionales Löschen des Debug-Verzeichnisses debug_dir = os.path.join('debug_images', bildname) if os.path.exists(debug_dir): try: shutil.rmtree(debug_dir) logger.info(f"Debug-Verzeichnis gelöscht: {debug_dir}") except Exception as e: logger.error(f"Fehler beim Löschen des Debug-Verzeichnisses '{debug_dir}': {e}") # Eine Warnung wird zurückgegeben, aber die Anfrage wird als erfolgreich betrachtet return jsonify({ 'status': 'success', 'message': 'Database entries deleted, but failed to delete debug directory.', 'details': str(e) }), 200 return jsonify({'status': 'success', 'message': f'All entries for image "{bildname}" have been deleted.'}), 200 except sqlite3.Error as e: # Rollback der Transaktion bei einem Datenbankfehler conn.rollback() logger.error(f"Datenbankfehler beim Löschen der Image-Einträge für '{bildname}': {e}") return jsonify({'error': 'Database error', 'details': str(e)}), 500 except Exception as e: # Rollback der Transaktion bei einem unerwarteten Fehler conn.rollback() logger.error(f"Unerwarteter Fehler beim Löschen der Image-Einträge für '{bildname}': {e}") return jsonify({'error': 'Failed to delete image entries', 'details': str(e)}), 500 finally: conn.close() # Sicherstellen, dass die Datenbank existiert if not os.path.exists(DATABASE): init_db() clean_debug_directories()