diff --git a/.gitignore b/.gitignore index 06b69c4..4a2735d 100644 --- a/.gitignore +++ b/.gitignore @@ -4,4 +4,4 @@ /build __pycache__/ *.iss -*.spec \ No newline at end of file +UFF-Search.spec \ No newline at end of file diff --git a/README.md b/README.md index f415993..7293b14 100644 --- a/README.md +++ b/README.md @@ -2,10 +2,6 @@ # UFF Search - Unsorted Folder Full-Text Search -![GitHub stars](https://img.shields.io/github/stars/BildoBeucklin/unsorted-folder-full-text-search?style=social) -![GitHub forks](https://img.shields.io/github/forks/BildoBeucklin/unsorted-folder-full-text-search?style=social) -![GitHub license](https://img.shields.io/github/license/BildoBeucklin/unsorted-folder-full-text-search) - UFF Search is a powerful desktop application for Windows that allows you to perform fast, intelligent, and fuzzy full-text searches on your local files, including searching inside ZIP archives. It builds a local search index for the folders you specify, allowing you to quickly find documents based on their meaning (semantic search) and specific keywords, even with typos in your search query. @@ -68,12 +64,18 @@ To create a standalone executable from the source code, you can use `pyinstaller 2. **Build the executable:** ```bash - pyinstaller --name "UFF_Search" --windowed --onefile --icon="favicon.ico" --add-data "assets;assets" main.py + pyinstaller --noconfirm --onedir --windowed --add-data "assets;assets" --icon "assets/favicon.ico" main.py ``` -This command will create a single executable file in the `dist` folder. + Or: + ```bash + pyinstaller main.spec + ``` + +Both of these commands will create a single executable file in the `dist` folder. It may take some time to build. + ## Usage - +(texts are only in german) 1. Start the application. 2. Click **" + Hinzufügen"** (Add) to select a folder you want to index. The application will start scanning it immediately. 3. Once indexing is complete, type your search query into the search bar and press Enter or click **"Suchen"** (Search). @@ -94,6 +96,7 @@ This command will create a single executable file in the `dist` folder. * `openpyxl` for `.xlsx` files. * `python-pptx` for `.pptx` files. * **Index Location:** The search index database (`uff_index.db`) is stored in `%LOCALAPPDATA%\UFF_Search` on Windows. +* **Size:** (ca. 400-600 MB) ## License diff --git a/UFF_Search_Installer_v3.exe b/UFF_Search_Installer_v3.exe deleted file mode 100644 index 9f81e68..0000000 Binary files a/UFF_Search_Installer_v3.exe and /dev/null differ diff --git a/favicon.ico b/assets/favicon.ico similarity index 100% rename from favicon.ico rename to assets/favicon.ico diff --git a/config.py b/config.py index 46939b6..24a21a0 100644 --- a/config.py +++ b/config.py @@ -15,22 +15,32 @@ if not os.path.exists(APP_DATA_DIR): DB_NAME = os.path.join(APP_DATA_DIR, "uff_index.db") LOG_FILE = os.path.join(APP_DATA_DIR, "uff.log") +def resource_path(relative_path): + """ + Holt den absoluten Pfad zu Ressourcen. + Funktioniert für Dev-Modus UND für PyInstaller EXE (_MEIPASS). + """ + try: + # PyInstaller erstellt temporären Ordner _MEIPASS + base_path = sys._MEIPASS + except Exception: + base_path = os.path.abspath(".") + + return os.path.join(base_path, relative_path) + # --- LOGGING KLASSE --- class Logger(object): def __init__(self): - # "w" überschreibt bei jedem Start. Nutze "a" für anhängen (append). - self.terminal = sys.stdout # Optional: Falls du es AUCH im Terminal sehen willst + self.terminal = sys.stdout self.log = open(LOG_FILE, "w", encoding="utf-8") def write(self, message): - # Optional: ins Terminal schreiben (auskommentieren, wenn du nur Logfile willst) - # self.terminal.write(message) + self.log.write(message) self.log.flush() def flush(self): - # self.terminal.flush() self.log.flush() # --- AKTIVIERUNG DES LOGGERS --- diff --git a/main.py b/main.py index cc94d14..95c4a0c 100644 --- a/main.py +++ b/main.py @@ -7,7 +7,7 @@ from PyQt6.QtGui import QPixmap, QFont, QIcon from PyQt6.QtCore import qInstallMessageHandler, QTimer, Qt # Config zuerst! -from config import qt_message_handler, LOG_FILE +from config import qt_message_handler, LOG_FILE, resource_path from ui import UffWindow, ModernSplashScreen, ModelLoaderThread @@ -19,15 +19,14 @@ if __name__ == "__main__": app = QApplication(sys.argv) app.setFont(QFont("Segoe UI", 10)) - # 1. ICON SETZEN (Für die ganze App) - # Wenn assets/icon.png existiert, wird es genutzt. - if os.path.exists("assets/icon.png"): - app_icon = QIcon("assets/icon.png") + icon_path = resource_path("assets/uff_icon.jpeg") # <--- HIER + if os.path.exists(icon_path): + app_icon = QIcon(icon_path) app.setWindowIcon(app_icon) - # 2. SPLASH SCREEN ERSTELLEN - splash_pix = QPixmap("assets/uff_banner.jpeg") - # Falls kein Bild da ist, nehmen wir ein leeres (damit es nicht crasht) + # SPLASH LADEN (Mit resource_path) + banner_path = resource_path("assets/uff_banner.jpeg") + splash_pix = QPixmap(banner_path) if splash_pix.isNull(): splash_pix = QPixmap(600, 400) splash_pix.fill(Qt.GlobalColor.white) @@ -35,8 +34,6 @@ if __name__ == "__main__": splash = ModernSplashScreen(splash_pix) splash.show() - # 3. LADEN SIMULIEREN & STARTEN - # Wir nutzen einen kleinen Trick, um den Start visuell zu "begleiten" splash.set_progress(10, "Lade Konfiguration...") app.processEvents() diff --git a/main.spec b/main.spec new file mode 100644 index 0000000..45a3a1f --- /dev/null +++ b/main.spec @@ -0,0 +1,72 @@ +# -*- mode: python ; coding: utf-8 -*- +from PyInstaller.utils.hooks import collect_all + +# --- 1. SPEZIELLE BIBLIOTHEKEN SAMMELN --- +# sentence_transformers und rapidfuzz sind komplex, wir holen alles automatisch +datas = [('assets', 'assets')] +binaries = [] +hiddenimports = [ + 'docx', + 'openpyxl', + 'pptx', + 'pdfplumber', + 'rapidfuzz', + 'sentence_transformers', + 'numpy' +] + +# Sammle alle Daten für die KI-Bibliothek (verhindert Import-Fehler) +tmp_ret = collect_all('sentence_transformers') +datas += tmp_ret[0]; binaries += tmp_ret[1]; hiddenimports += tmp_ret[2] + +# Sammle rapidfuzz sicherheitshalber auch komplett +tmp_ret = collect_all('rapidfuzz') +datas += tmp_ret[0]; binaries += tmp_ret[1]; hiddenimports += tmp_ret[2] + + +# --- 2. ANALYSE --- +a = Analysis( + ['main.py'], + pathex=[], + binaries=binaries, + datas=datas, + hiddenimports=hiddenimports, + hookspath=[], + hooksconfig={}, + runtime_hooks=[], + excludes=[], + noarchive=False, + optimize=0, +) +pyz = PYZ(a.pure) + +# --- 3. EXE ERSTELLEN --- +exe = EXE( + pyz, + a.scripts, + [], + exclude_binaries=True, + name='UFF_Search', # Name der Datei (UFF_Search.exe) + debug=False, + bootloader_ignore_signals=False, + strip=False, + upx=True, + console=False, + disable_windowed_traceback=False, + argv_emulation=False, + target_arch=None, + codesign_identity=None, + entitlements_file=None, + icon='assets\\favicon.ico', # Pfad zum Icon +) + +# --- 4. ORDNER ZUSAMMENSTELLEN --- +coll = COLLECT( + exe, + a.binaries, + a.datas, + strip=False, + upx=True, + upx_exclude=[], + name='UFF_Search', # Name des Ordners +) \ No newline at end of file diff --git a/ui.py b/ui.py index b741a7d..64cf56d 100644 --- a/ui.py +++ b/ui.py @@ -1,6 +1,6 @@ # ui.py import os -from PyQt6.QtWidgets import (QMainWindow, QWidget, QVBoxLayout, QHBoxLayout, +from PyQt6.QtWidgets import (QApplication, QMainWindow, QWidget, QVBoxLayout, QHBoxLayout, QLineEdit, QPushButton, QLabel, QFileDialog, QProgressBar, QMessageBox, QListWidget, QListWidgetItem, QSplitter, QFrame, QScrollArea, QStyle, QGraphicsDropShadowEffect, @@ -133,10 +133,7 @@ class UffWindow(QMainWindow): self.db = DatabaseHandler() self.initUI() - # NEU: Icon setzen - if os.path.exists("assets/uff_icon.jpeg"): - self.setWindowIcon(QIcon("assets/uff_icon.jpeg")) - + self.load_saved_folders() def initUI(self):