diff --git a/Web/app.py b/Web/app.py index 6cc2388..97ae008 100755 --- a/Web/app.py +++ b/Web/app.py @@ -5828,7 +5828,9 @@ def upload_item(): if item_id: success_msg = f'Element wurde erfolgreich hinzugefügt ({len(created_item_ids)} erstellt)' + fs = get_gridfs() # Deine GridFS Verbindung + cleanup_orphaned_images(fs, dry_run=True) if upload_mode == 'library': try: _append_audit_event_standalone( diff --git a/Web/modules/database/items.py b/Web/modules/database/items.py index fc24e13..4f27630 100755 --- a/Web/modules/database/items.py +++ b/Web/modules/database/items.py @@ -25,6 +25,7 @@ import datetime import Web.modules.database.settings as cfg from Web.modules.database.settings import MongoClient import Web.modules.inventarsystem.data_protection as dp +import logging def is_library_item(item): @@ -1292,4 +1293,137 @@ def sync_group_codes(primary_obj_id, base_code, individual_codes_list): return True except Exception as e: print(f"Error syncing group codes: {e}") - return False \ No newline at end of file + return False + + +logging.basicConfig(level=logging.INFO, format='%(levelname)s: %(message)s') + + +def cleanup_orphaned_images(fs, dry_run=True): + """ + Finds images in GridFS that are no longer referenced by any item + and optionally deletes them. + + Supported item fields: + - book_cover_image + - image + - images + + The fields are expected to contain GridFS file ObjectIds. + + :param fs: GridFS instance, e.g. gridfs.GridFS(db) + :param dry_run: If True, only reports what would be deleted. + If False, actually deletes the files. + """ + + logging.info("Starte Cleanup-Skript...") + + client = MongoClient(cfg.MONGODB_HOST, cfg.MONGODB_PORT) + + try: + db = client[cfg.MONGODB_DB] + items_collection = db["items"] + + referenced_files = set() + + for item in items_collection.find( + {}, + { + "Images": 1 + } + ): + images = item.get("Images") + + if isinstance(images, list): + for img in images: + if img: + referenced_files.add(img) + + referenced_files.discard(None) + + logging.info( + f"{len(referenced_files)} referenzierte GridFS-Dateien gefunden." + ) + + orphaned_files = [] + + for grid_file in fs.find(): + file_id = grid_file._id + filename = grid_file.filename or "" + + if not filename.lower().endswith( + (".jpg", ".jpeg", ".png", ".gif", ".webp") + ): + continue + + if file_id not in referenced_files: + orphaned_files.append( + { + "_id": file_id, + "filename": filename, + "upload_date": grid_file.upload_date, + } + ) + + logging.info( + f"Gefundene verwaiste Bilder: {len(orphaned_files)}" + ) + + if dry_run: + logging.info( + "--- DRY RUN AKTIV - Es wird nichts gelöscht ---" + ) + + for file in orphaned_files: + logging.info( + f"Würde löschen: " + f"{file['filename']} " + f"(ID: {file['_id']}, " + f"Hochgeladen: {file['upload_date']})" + ) + + logging.info( + "--- Setze dry_run=False, um physisch zu löschen ---" + ) + + else: + logging.warning("--- LÖSCHVORGANG AKTIV ---") + + deleted_count = 0 + failed_count = 0 + + for file in orphaned_files: + try: + fs.delete(file["_id"]) + + deleted_count += 1 + + logging.info( + f"Gelöscht: {file['filename']} " + f"(ID: {file['_id']})" + ) + + except Exception as e: + failed_count += 1 + + logging.error( + f"Fehler beim Löschen von " + f"{file['filename']} " + f"(ID: {file['_id']}): {e}" + ) + + logging.info( + f"Cleanup beendet. " + f"{deleted_count} Bilder gelöscht, " + f"{failed_count} Fehler." + ) + + return { + "status": "success", + "referenced_count": len(referenced_files), + "orphaned_count": len(orphaned_files), + "dry_run": dry_run, + } + + finally: + client.close() \ No newline at end of file