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

View File

@ -1,6 +1,7 @@
from pathlib import Path from pathlib import Path
from typing import List from typing import List
from datetime import datetime from datetime import datetime
import hashlib
from pilgrim.models.photo import Photo from pilgrim.models.photo import Photo
@ -9,6 +10,12 @@ from pilgrim.models.travel_diary import TravelDiary
class PhotoService: class PhotoService:
def __init__(self, session): def __init__(self, session):
self.session = 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: 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() travel_diary = self.session.query(TravelDiary).filter(TravelDiary.id == travel_diary_id).first()
@ -27,7 +34,8 @@ class PhotoService:
name=name, name=name,
caption=caption, caption=caption,
fk_travel_diary_id=travel_diary_id, 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.add(new_photo)
self.session.commit() self.session.commit()
@ -47,6 +55,7 @@ class PhotoService:
original.name = photo_dst.name original.name = photo_dst.name
original.addition_date = photo_dst.addition_date original.addition_date = photo_dst.addition_date
original.caption = photo_dst.caption original.caption = photo_dst.caption
original.photo_hash = original.photo_hash
if photo_dst.entries and len(photo_dst.entries) > 0: if photo_dst.entries and len(photo_dst.entries) > 0:
if original.entries is None: if original.entries is None:
original.entries = [] original.entries = []
@ -66,7 +75,10 @@ class PhotoService:
addition_date=excluded.addition_date, addition_date=excluded.addition_date,
caption=excluded.caption, caption=excluded.caption,
fk_travel_diary_id=excluded.fk_travel_diary_id, 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) self.session.delete(excluded)

View File

@ -60,6 +60,7 @@ class EditEntryScreen(Screen):
self._last_photo_suggestion_type = None self._last_photo_suggestion_type = None
self._active_notification = None self._active_notification = None
self._notification_timer = None self._notification_timer = None
self.references = []
# Main header # Main header
self.header = Header(name="Pilgrim v6", classes="EditEntryScreen-header") self.header = Header(name="Pilgrim v6", classes="EditEntryScreen-header")
@ -124,55 +125,6 @@ class EditEntryScreen(Screen):
hash_object = hashlib.md5(unique_string.encode()) hash_object = hashlib.md5(unique_string.encode())
return hash_object.hexdigest()[:8] 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 # Add photos to the list with hash
for photo in photos: for photo in photos:
# Show name and hash in the list # Show name and hash in the list
photo_hash = self._generate_photo_hash(photo) photo_hash = str(photo.photo_hash)[:8]
self.photo_list.add_option(f"📷 {photo.name} \\[{photo_hash}\\]") self.photo_list.add_option(f"📷 {photo.name} \\[{photo_hash}\]")
self.photo_info.update(f"📸 {len(photos)} photos in diary") 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 = ( help_text = (
"[b]⌨️ Sidebar Shortcuts[/b]\n" "[b]⌨️ Sidebar Shortcuts[/b]\n"
"[b][green]i[/green][/b]: Insert photo into entry\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") self.notify("Use F8 to open the sidebar first.", severity="warning")
return return
# Get selected photo # Get a selected photo
if self.photo_list.highlighted is None: if self.photo_list.highlighted is None:
self.notify("No photo selected", severity="warning") self.notify("No photo selected", severity="warning")
return return
@ -467,13 +419,13 @@ class EditEntryScreen(Screen):
return return
selected_photo = photos[photo_index] 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 # Insert photo reference using hash format without escaping
# Using raw string to avoid markup conflicts with [[ # Using raw string to avoid markup conflicts with [[
photo_ref = f"[[photo::{photo_hash}]]" photo_ref = f"[[photo::{photo_hash}]]"
# Insert at cursor position # Insert at the cursor position
self.text_entry.insert(photo_ref) self.text_entry.insert(photo_ref)
# Switch focus back to editor # Switch focus back to editor
@ -505,10 +457,15 @@ class EditEntryScreen(Screen):
return return
# Open add photo modal # Open add photo modal
self.app.push_screen( try:
AddPhotoModal(diary_id=self.diary_id), self.notify("Trying to push the modal screen...")
self.handle_add_photo_result 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: def handle_add_photo_result(self, result: dict | None) -> None:
"""Callback that processes the add photo modal result.""" """Callback that processes the add photo modal result."""
@ -690,8 +647,13 @@ class EditEntryScreen(Screen):
if not self.sidebar_visible: if not self.sidebar_visible:
return 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) 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 return
# Adjust index because of 'Ingest Photo' at the top # Adjust index because of 'Ingest Photo' at the top
@ -700,7 +662,7 @@ class EditEntryScreen(Screen):
return return
selected_photo = photos[photo_index] 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}\\]") self.notify(f"Selected photo: {selected_photo.name} \\[{photo_hash}\\]")
# Update photo info with details including hash # Update photo info with details including hash
@ -853,6 +815,8 @@ class EditEntryScreen(Screen):
def action_save(self) -> None: def action_save(self) -> None:
"""Saves the current entry""" """Saves the current entry"""
self._get_all_references()
self._validate_references()
if self.is_new_entry: if self.is_new_entry:
content = self.text_entry.text.strip() content = self.text_entry.text.strip()
if not content: if not content:
@ -937,7 +901,30 @@ class EditEntryScreen(Screen):
except Exception as e: except Exception as e:
self.notify(f"Error updating entry: {str(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): def on_key(self, event):
print("DEBUG: on_key called with", event.key, "sidebar_focused:", self.sidebar_focused, "sidebar_visible:", self.sidebar_visible) print("DEBUG: on_key called with", event.key, "sidebar_focused:", self.sidebar_focused, "sidebar_visible:", self.sidebar_visible)
# Sidebar contextual shortcuts # Sidebar contextual shortcuts