vocab-backend/deck_endpoints.py

325 lines
12 KiB
Python

# 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/<deckname>', 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/<bildname>', 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/<bildname>', 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()