Compare commits

...

3 Commits

3 changed files with 65 additions and 64 deletions

View File

@ -16,6 +16,7 @@ class Photo(Base):
name = Column(String)
addition_date = Column(DateTime, default=datetime.now)
caption = Column(String)
photo_hash = Column(String,name='hash')
entries = relationship(
"Entry",
secondary=photo_entry_association,
@ -24,7 +25,7 @@ class Photo(Base):
fk_travel_diary_id = Column(Integer, ForeignKey("travel_diaries.id"),nullable=False)
def __init__(self, filepath, name, addition_date=None, caption=None, entries=None, fk_travel_diary_id=None, **kw: Any):
def __init__(self, filepath, name, photo_hash, addition_date=None, caption=None, entries=None, fk_travel_diary_id=None, **kw: Any):
super().__init__(**kw)
# Convert Path to string if needed
if isinstance(filepath, Path):
@ -34,6 +35,7 @@ class Photo(Base):
self.name = name
self.addition_date = addition_date if addition_date is not None else datetime.now()
self.caption = caption
self.photo_hash = photo_hash
self.entries = entries if entries is not None else []
if fk_travel_diary_id is not None:
self.fk_travel_diary_id = fk_travel_diary_id

View File

@ -1,6 +1,7 @@
from pathlib import Path
from typing import List
from datetime import datetime
import hashlib
from pilgrim.models.photo import Photo
@ -9,6 +10,12 @@ from pilgrim.models.travel_diary import TravelDiary
class PhotoService:
def __init__(self, session):
self.session = session
def _hash_file(self,filepath):
hash_func = hashlib.new('sha3_384')
with open(filepath, 'rb') as f:
while chunk := f.read(8192):
hash_func.update(chunk)
return hash_func.hexdigest()
def create(self, filepath: Path, name: str, travel_diary_id: int, caption=None, addition_date=None) -> Photo | None:
travel_diary = self.session.query(TravelDiary).filter(TravelDiary.id == travel_diary_id).first()
@ -27,7 +34,8 @@ class PhotoService:
name=name,
caption=caption,
fk_travel_diary_id=travel_diary_id,
addition_date=addition_date
addition_date=addition_date,
photo_hash=self._hash_file(filepath)
)
self.session.add(new_photo)
self.session.commit()
@ -47,6 +55,7 @@ class PhotoService:
original.name = photo_dst.name
original.addition_date = photo_dst.addition_date
original.caption = photo_dst.caption
original.photo_hash = original.photo_hash
if photo_dst.entries and len(photo_dst.entries) > 0:
if original.entries is None:
original.entries = []
@ -66,7 +75,10 @@ class PhotoService:
addition_date=excluded.addition_date,
caption=excluded.caption,
fk_travel_diary_id=excluded.fk_travel_diary_id,
id=excluded.id
id=excluded.id,
photo_hash=excluded.photo_hash,
entries=excluded.entries,
)
self.session.delete(excluded)

View File

@ -60,6 +60,7 @@ class EditEntryScreen(Screen):
self._last_photo_suggestion_type = None
self._active_notification = None
self._notification_timer = None
self.references = []
# Main header
self.header = Header(name="Pilgrim v6", classes="EditEntryScreen-header")
@ -124,55 +125,6 @@ class EditEntryScreen(Screen):
hash_object = hashlib.md5(unique_string.encode())
return hash_object.hexdigest()[:8]
def _fuzzy_search(self, query: str, photos: List[Photo]) -> List[Photo]:
"""Fuzzy search for photos by name or hash"""
if not query:
return []
query = query.lower()
results = []
for photo in photos:
photo_hash = self._generate_photo_hash(photo)
photo_name = photo.name.lower()
# Check if query is in name (substring match)
if query in photo_name:
results.append((photo, 1, f"Name match: {query} in {photo.name}"))
continue
# Check if query is in hash (substring match)
if query in photo_hash:
results.append((photo, 2, f"Hash match: {query} in {photo_hash}"))
continue
# Fuzzy match for name (check if all characters are present in order)
if self._fuzzy_match(query, photo_name):
results.append((photo, 3, f"Fuzzy name match: {query} in {photo.name}"))
continue
# Fuzzy match for hash
if self._fuzzy_match(query, photo_hash):
results.append((photo, 4, f"Fuzzy hash match: {query} in {photo_hash}"))
continue
# Sort by priority (lower number = higher priority)
results.sort(key=lambda x: x[1])
return [photo for photo, _, _ in results]
def _fuzzy_match(self, query: str, text: str) -> bool:
"""Check if query characters appear in text in order (fuzzy match)"""
if not query:
return True
query_idx = 0
for char in text:
if query_idx < len(query) and char == query[query_idx]:
query_idx += 1
if query_idx == len(query):
return True
return False
@ -354,12 +306,12 @@ class EditEntryScreen(Screen):
# Add photos to the list with hash
for photo in photos:
# Show name and hash in the list
photo_hash = self._generate_photo_hash(photo)
self.photo_list.add_option(f"📷 {photo.name} \\[{photo_hash}\\]")
photo_hash = str(photo.photo_hash)[:8]
self.photo_list.add_option(f"📷 {photo.name} \\[{photo_hash}\]")
self.photo_info.update(f"📸 {len(photos)} photos in diary")
# Updated help text with hash information
# Updated help a text with hash information
help_text = (
"[b]⌨️ Sidebar Shortcuts[/b]\n"
"[b][green]i[/green][/b]: Insert photo into entry\n"
@ -453,7 +405,7 @@ class EditEntryScreen(Screen):
self.notify("Use F8 to open the sidebar first.", severity="warning")
return
# Get selected photo
# Get a selected photo
if self.photo_list.highlighted is None:
self.notify("No photo selected", severity="warning")
return
@ -467,13 +419,13 @@ class EditEntryScreen(Screen):
return
selected_photo = photos[photo_index]
photo_hash = self._generate_photo_hash(selected_photo)
photo_hash = selected_photo.photo_hash[:8]
# Insert photo reference using hash format without escaping
# Using raw string to avoid markup conflicts with [[
photo_ref = f"[[photo::{photo_hash}]]"
# Insert at cursor position
# Insert at the cursor position
self.text_entry.insert(photo_ref)
# Switch focus back to editor
@ -505,10 +457,15 @@ class EditEntryScreen(Screen):
return
# Open add photo modal
self.app.push_screen(
AddPhotoModal(diary_id=self.diary_id),
self.handle_add_photo_result
)
try:
self.notify("Trying to push the modal screen...")
self.app.push_screen(
AddPhotoModal(diary_id=self.diary_id),
self.handle_add_photo_result
)
except Exception as e:
self.notify(f"Error: {str(e)}", severity="error")
self.app.notify("Error: {str(e)}", severity="error")
def handle_add_photo_result(self, result: dict | None) -> None:
"""Callback that processes the add photo modal result."""
@ -690,8 +647,13 @@ class EditEntryScreen(Screen):
if not self.sidebar_visible:
return
# Handle "Ingest Photo" option
if event.option_index == 0: # First option is "Ingest Photo"
self.action_ingest_new_photo()
return
photos = self._load_photos_for_diary(self.diary_id)
if not photos or event.option_index <= 0: # Skip 'Ingest Photo' option
if not photos:
return
# Adjust index because of 'Ingest Photo' at the top
@ -700,7 +662,7 @@ class EditEntryScreen(Screen):
return
selected_photo = photos[photo_index]
photo_hash = self._generate_photo_hash(selected_photo)
photo_hash = selected_photo.photo_hash[:8]
self.notify(f"Selected photo: {selected_photo.name} \\[{photo_hash}\\]")
# Update photo info with details including hash
@ -853,6 +815,8 @@ class EditEntryScreen(Screen):
def action_save(self) -> None:
"""Saves the current entry"""
self._get_all_references()
self._validate_references()
if self.is_new_entry:
content = self.text_entry.text.strip()
if not content:
@ -937,7 +901,30 @@ class EditEntryScreen(Screen):
except Exception as e:
self.notify(f"Error updating entry: {str(e)}")
def _get_all_references(self):
text_content = self.text_entry.text
matches = re.findall("(\[\[photo::?(?:\w|\s)*\]\])", text_content)
for match in matches:
if re.match(r"\[\[photo::\w+\]\]", match):
if {'type': 'hash','value':match.replace("[[photo::", "").replace("]]", "").strip()} not in self.references:
self.references.append(
{'type': 'hash', 'value': match.replace("[[photo::", "").replace("]]", "").strip()})
elif re.match(r"\[\[photo:\w+\]\]", match):
if {'type': 'name', 'value': match.replace("[[photo:", "").replace("]]", "").strip()} not in self.references:
self.references.append(
{'type': 'name', 'value': match.replace("[[photo:", "").replace("]]", "").strip()})
else:
self.references.append({'type': 'unknown', 'value': match})
self.notify(f"🔍 Referências encontradas: {str(self.references)}", markup=False)
def _validate_references(self):
photo_service = self.app.service_manager.get_photo_service()
for reference in self.references:
if reference['type'] == 'hash':
pass
elif reference['type'] == 'name':
self.notify("name")
def on_key(self, event):
print("DEBUG: on_key called with", event.key, "sidebar_focused:", self.sidebar_focused, "sidebar_visible:", self.sidebar_visible)
# Sidebar contextual shortcuts