commit 1821d51fcf98ecbb36825ffcadd0aa4a39c78ce1 Author: Konstantin Date: Thu Jan 8 16:53:23 2026 +0100 Refactor code structure for improved readability and maintainability diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..b694934 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +.venv \ No newline at end of file diff --git a/uff_app.py b/uff_app.py new file mode 100644 index 0000000..5062595 --- /dev/null +++ b/uff_app.py @@ -0,0 +1,238 @@ +import sys +import os +import sqlite3 +from pypdf import PdfReader + +# PyQt6 Module +from PyQt6.QtWidgets import (QApplication, QMainWindow, QWidget, QVBoxLayout, + QHBoxLayout, QLineEdit, QPushButton, QLabel, + QFileDialog, QTextBrowser, QProgressBar, QMessageBox) +from PyQt6.QtCore import Qt, QThread, pyqtSignal, QUrl +from PyQt6.QtGui import QDesktopServices, QIcon + +# --- 1. BACKEND (Unverändert, nur ausgelagert) --- + +class DatabaseHandler: + def __init__(self, db_name="uff_index.db"): + self.db_name = db_name + self.init_db() + + def init_db(self): + conn = sqlite3.connect(self.db_name) + cursor = conn.cursor() + cursor.execute(""" + CREATE VIRTUAL TABLE IF NOT EXISTS documents + USING fts5(filename, path, content); + """) + conn.commit() + conn.close() + + def search(self, query): + conn = sqlite3.connect(self.db_name) + cursor = conn.cursor() + safe_query = query.replace('"', '""') + sql = """ + SELECT filename, path, snippet(documents, 2, '', '', '...', 15) + FROM documents + WHERE documents MATCH ? + ORDER BY rank LIMIT 50 + """ + try: + results = cursor.execute(sql, (f"{safe_query}*",)).fetchall() + except sqlite3.OperationalError: + results = [] + conn.close() + return results + +# --- 2. WORKER THREAD (Damit die UI beim Scannen nicht einfriert) --- + +class IndexerThread(QThread): + progress_signal = pyqtSignal(str) # Sendet Text an UI + finished_signal = pyqtSignal(int, int) # Sendet Statistiken (indexed, skipped) + + def __init__(self, folder_path, db_name="uff_index.db"): + super().__init__() + self.folder_path = folder_path + self.db_name = db_name + + def _extract_text(self, filepath): + ext = os.path.splitext(filepath)[1].lower() + try: + if ext == ".pdf": + reader = PdfReader(filepath) + text = "" + for page in reader.pages: + text_page = page.extract_text() + if text_page: text += text_page + "\n" + return text + elif ext in [".txt", ".md", ".py", ".json", ".csv", ".html", ".log", ".ini"]: + with open(filepath, "r", encoding="utf-8", errors="ignore") as f: + return f.read() + return None + except Exception: + return None + + def run(self): + conn = sqlite3.connect(self.db_name) + cursor = conn.cursor() + + # Alten Index leeren + cursor.execute("DELETE FROM documents") + conn.commit() + + indexed = 0 + skipped = 0 + + for root, dirs, files in os.walk(self.folder_path): + for file in files: + self.progress_signal.emit(f"Scanne: {file}...") + path = os.path.join(root, file) + + content = self._extract_text(path) + + if content and len(content.strip()) > 0: + cursor.execute( + "INSERT INTO documents (filename, path, content) VALUES (?, ?, ?)", + (file, path, content) + ) + indexed += 1 + else: + skipped += 1 + + conn.commit() + conn.close() + self.finished_signal.emit(indexed, skipped) + +# --- 3. FRONTEND (Das PyQt Fenster) --- + +class UffWindow(QMainWindow): + def __init__(self): + super().__init__() + self.db = DatabaseHandler() + self.initUI() + + def initUI(self): + self.setWindowTitle("UFF Text Search - PyQt Edition") + self.resize(800, 600) + + # Haupt-Container + central_widget = QWidget() + self.setCentralWidget(central_widget) + layout = QVBoxLayout(central_widget) + + # --- Header --- + title = QLabel("UFF Text Search") + title.setStyleSheet("font-size: 24px; font-weight: bold; color: #333;") + layout.addWidget(title) + + # --- Ordner Auswahl --- + folder_layout = QHBoxLayout() + self.btn_folder = QPushButton("Ordner wählen") + self.btn_folder.setCursor(Qt.CursorShape.PointingHandCursor) + self.btn_folder.clicked.connect(self.select_folder) + + self.lbl_folder = QLabel("Kein Ordner gewählt") + self.lbl_folder.setStyleSheet("color: gray; font-style: italic;") + + folder_layout.addWidget(self.btn_folder) + folder_layout.addWidget(self.lbl_folder) + folder_layout.addStretch() + layout.addLayout(folder_layout) + + # --- Suche --- + search_layout = QHBoxLayout() + self.input_search = QLineEdit() + self.input_search.setPlaceholderText("Suchbegriff eingeben und Enter drücken...") + self.input_search.returnPressed.connect(self.perform_search) + + self.btn_search = QPushButton("Suchen") + self.btn_search.clicked.connect(self.perform_search) + + search_layout.addWidget(self.input_search) + search_layout.addWidget(self.btn_search) + layout.addLayout(search_layout) + + # --- Status & Progress --- + self.lbl_status = QLabel("Bereit.") + self.progress_bar = QProgressBar() + self.progress_bar.setRange(0, 0) # Infinite Loading Animation + self.progress_bar.hide() + layout.addWidget(self.lbl_status) + layout.addWidget(self.progress_bar) + + # --- Ergebnisse (Browser Engine für HTML Support) --- + self.result_browser = QTextBrowser() + self.result_browser.setOpenExternalLinks(False) # Wir handeln Links selbst + self.result_browser.anchorClicked.connect(self.link_clicked) + self.result_browser.setStyleSheet("background-color: white; padding: 10px; font-size: 14px;") + layout.addWidget(self.result_browser) + + def select_folder(self): + folder = QFileDialog.getExistingDirectory(self, "Ordner wählen") + if folder: + self.lbl_folder.setText(folder) + self.start_indexing(folder) + + def start_indexing(self, folder): + self.btn_folder.setEnabled(False) + self.input_search.setEnabled(False) + self.progress_bar.show() + + # Thread starten + self.indexer_thread = IndexerThread(folder) + self.indexer_thread.progress_signal.connect(self.update_status) + self.indexer_thread.finished_signal.connect(self.indexing_finished) + self.indexer_thread.start() + + def update_status(self, msg): + self.lbl_status.setText(msg) + + def indexing_finished(self, indexed, skipped): + self.progress_bar.hide() + self.btn_folder.setEnabled(True) + self.input_search.setEnabled(True) + self.lbl_status.setText(f"Fertig! {indexed} Dateien indiziert ({skipped} übersprungen).") + QMessageBox.information(self, "Scan beendet", f"{indexed} Dateien wurden erfolgreich indiziert.") + + def perform_search(self): + query = self.input_search.text() + if not query: return + + results = self.db.search(query) + self.lbl_status.setText(f"{len(results)} Treffer gefunden.") + + # HTML bauen für die Anzeige + html_content = "" + if not results: + html_content = "

Keine Ergebnisse gefunden.

" + + for filename, filepath, snippet in results: + # Wir nutzen den Dateipfad als Link-URL + file_url = QUrl.fromLocalFile(filepath).toString() + + html_content += f""" +
+ + 📄 {filename} + +
+ ...{snippet}... +
+
+ {filepath} +
+
+ """ + + self.result_browser.setHtml(html_content) + + def link_clicked(self, url): + # Öffnet die Datei mit dem Standard-Programm des Betriebssystems + QDesktopServices.openUrl(url) + +# --- APP START --- +if __name__ == "__main__": + app = QApplication(sys.argv) + window = UffWindow() + window.show() + sys.exit(app.exec()) \ No newline at end of file diff --git a/uff_index.db b/uff_index.db new file mode 100644 index 0000000..97eaa6e Binary files /dev/null and b/uff_index.db differ