feat(document-sync): enhance DocumentSync with file status checks and hash-based change detection; add thumbnail generation and metadata update methods
This commit is contained in:
@@ -162,6 +162,11 @@ class DocumentSync:
|
||||
"""
|
||||
Entscheidet ob ein Document zu xAI synchronisiert werden muss
|
||||
|
||||
Prüft:
|
||||
1. Datei-Status Feld ("Neu", "Geändert")
|
||||
2. Hash-Werte für Change Detection
|
||||
3. Related Entities mit xAI Collections
|
||||
|
||||
Args:
|
||||
document: Vollständiges Document Entity von EspoCRM
|
||||
|
||||
@@ -178,9 +183,38 @@ class DocumentSync:
|
||||
xai_file_id = document.get('xaiFileId')
|
||||
xai_collections = document.get('xaiCollections') or []
|
||||
|
||||
# Datei-Status und Hash-Felder
|
||||
datei_status = document.get('dateiStatus') or document.get('fileStatus')
|
||||
file_md5 = document.get('md5') or document.get('fileMd5')
|
||||
file_sha = document.get('sha') or document.get('fileSha')
|
||||
xai_synced_hash = document.get('xaiSyncedHash') # Hash beim letzten xAI-Sync
|
||||
|
||||
self._log(f"📋 Document Analysis: {doc_name} (ID: {doc_id})")
|
||||
self._log(f" xaiFileId: {xai_file_id or 'N/A'}")
|
||||
self._log(f" xaiCollections: {xai_collections}")
|
||||
self._log(f" Datei-Status: {datei_status or 'N/A'}")
|
||||
self._log(f" MD5: {file_md5[:16] if file_md5 else 'N/A'}...")
|
||||
self._log(f" SHA: {file_sha[:16] if file_sha else 'N/A'}...")
|
||||
self._log(f" xaiSyncedHash: {xai_synced_hash[:16] if xai_synced_hash else 'N/A'}...")
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
# PRIORITY CHECK: Datei-Status "Neu" oder "Geändert"
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
if datei_status in ['Neu', 'Geändert', 'neu', 'geändert', 'New', 'Changed']:
|
||||
self._log(f"🆕 Datei-Status: '{datei_status}' → xAI-Sync ERFORDERLICH")
|
||||
|
||||
# Hole Collections (entweder existierende oder von Related Entities)
|
||||
if xai_collections:
|
||||
target_collections = xai_collections
|
||||
else:
|
||||
target_collections = await self._get_required_collections_from_relations(doc_id)
|
||||
|
||||
if target_collections:
|
||||
return (True, target_collections, f"Datei-Status: {datei_status}")
|
||||
else:
|
||||
# Datei ist neu/geändert aber keine Collections gefunden
|
||||
self._log(f"⚠️ Datei-Status '{datei_status}' aber keine Collections gefunden - überspringe Sync")
|
||||
return (False, [], f"Datei-Status: {datei_status}, aber keine Collections")
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
# FALL 1: Document ist bereits in xAI UND Collections sind gesetzt
|
||||
@@ -188,8 +222,19 @@ class DocumentSync:
|
||||
if xai_file_id and xai_collections:
|
||||
self._log(f"✅ Document bereits in xAI gesynct mit {len(xai_collections)} Collection(s)")
|
||||
|
||||
# Prüfe ob Update nötig (z.B. wenn File selbst geändert wurde)
|
||||
# TODO: Implementiere File-Hash-Vergleich für Update-Erkennung
|
||||
# Prüfe ob File-Inhalt geändert wurde (Hash-Vergleich)
|
||||
current_hash = file_md5 or file_sha
|
||||
|
||||
if current_hash and xai_synced_hash:
|
||||
if current_hash != xai_synced_hash:
|
||||
self._log(f"🔄 Hash-Änderung erkannt! RESYNC erforderlich")
|
||||
self._log(f" Alt: {xai_synced_hash[:16]}...")
|
||||
self._log(f" Neu: {current_hash[:16]}...")
|
||||
return (True, xai_collections, "File-Inhalt geändert (Hash-Mismatch)")
|
||||
else:
|
||||
self._log(f"✅ Hash identisch - keine Änderung")
|
||||
else:
|
||||
self._log(f"⚠️ Keine Hash-Werte verfügbar für Vergleich")
|
||||
|
||||
return (False, xai_collections, "Bereits gesynct, keine Änderung erkannt")
|
||||
|
||||
@@ -316,3 +361,97 @@ class DocumentSync:
|
||||
except Exception as e:
|
||||
self._log(f"❌ Fehler beim Laden von Download-Info: {e}", level='error')
|
||||
return None
|
||||
|
||||
async def generate_thumbnail(self, file_path: str, mime_type: str) -> Optional[bytes]:
|
||||
"""
|
||||
Generiert Vorschaubild (Thumbnail) für ein Document
|
||||
|
||||
Unterstützt:
|
||||
- PDF: Erste Seite als Bild
|
||||
- DOCX/DOC: Konvertierung zu PDF, dann erste Seite
|
||||
- Images: Resize auf Thumbnail-Größe
|
||||
- Andere: Platzhalter-Icon basierend auf MIME-Type
|
||||
|
||||
Args:
|
||||
file_path: Pfad zur Datei (lokal oder Download-URL)
|
||||
mime_type: MIME-Type des Documents
|
||||
|
||||
Returns:
|
||||
Thumbnail als bytes (PNG/JPEG) oder None bei Fehler
|
||||
"""
|
||||
self._log(f"🖼️ Thumbnail-Generierung für {mime_type}")
|
||||
|
||||
# TODO: Implementierung
|
||||
#
|
||||
# Benötigte Libraries:
|
||||
# - pdf2image (für PDF → Image)
|
||||
# - python-docx + docx2pdf (für DOCX → PDF → Image)
|
||||
# - Pillow (PIL) für Image-Processing
|
||||
# - poppler-utils (System-Dependency für pdf2image)
|
||||
#
|
||||
# Implementierungs-Schritte:
|
||||
#
|
||||
# 1. PDF-Handling:
|
||||
# from pdf2image import convert_from_path
|
||||
# images = convert_from_path(file_path, first_page=1, last_page=1)
|
||||
# thumbnail = images[0].resize((200, 280))
|
||||
# return thumbnail_to_bytes(thumbnail)
|
||||
#
|
||||
# 2. DOCX-Handling:
|
||||
# - Konvertiere zu temporärem PDF
|
||||
# - Dann wie PDF behandeln
|
||||
#
|
||||
# 3. Image-Handling:
|
||||
# from PIL import Image
|
||||
# img = Image.open(file_path)
|
||||
# img.thumbnail((200, 280))
|
||||
# return image_to_bytes(img)
|
||||
#
|
||||
# 4. Fallback:
|
||||
# - Generic file-type icon basierend auf MIME-Type
|
||||
|
||||
self._log(f"⚠️ Thumbnail-Generierung noch nicht implementiert", level='warn')
|
||||
return None
|
||||
|
||||
async def update_sync_metadata(
|
||||
self,
|
||||
document_id: str,
|
||||
xai_file_id: str,
|
||||
collection_ids: List[str],
|
||||
file_hash: Optional[str] = None,
|
||||
thumbnail_data: Optional[bytes] = None
|
||||
) -> None:
|
||||
"""
|
||||
Updated Document-Metadaten nach erfolgreichem xAI-Sync
|
||||
|
||||
Args:
|
||||
document_id: EspoCRM Document ID
|
||||
xai_file_id: xAI File ID
|
||||
collection_ids: Liste der xAI Collection IDs
|
||||
file_hash: MD5/SHA Hash des gesyncten Files
|
||||
thumbnail_data: Vorschaubild als bytes
|
||||
"""
|
||||
try:
|
||||
update_data = {
|
||||
'xaiFileId': xai_file_id,
|
||||
'xaiCollections': collection_ids,
|
||||
'dateiStatus': 'Gesynct', # Status zurücksetzen
|
||||
}
|
||||
|
||||
# Hash speichern für zukünftige Change Detection
|
||||
if file_hash:
|
||||
update_data['xaiSyncedHash'] = file_hash
|
||||
|
||||
# Thumbnail als Attachment hochladen (falls vorhanden)
|
||||
if thumbnail_data:
|
||||
# TODO: Implementiere Thumbnail-Upload zu EspoCRM
|
||||
# EspoCRM unterstützt Preview-Images für Documents
|
||||
# Muss als separates Attachment hochgeladen werden
|
||||
self._log(f"⚠️ Thumbnail-Upload noch nicht implementiert", level='warn')
|
||||
|
||||
await self.espocrm.update_entity('Document', document_id, update_data)
|
||||
self._log(f"✅ Sync-Metadaten aktualisiert für Document {document_id}")
|
||||
|
||||
except Exception as e:
|
||||
self._log(f"❌ Fehler beim Update von Sync-Metadaten: {e}", level='error')
|
||||
raise
|
||||
|
||||
Reference in New Issue
Block a user