mirror of https://github.com/gmbrax/Pilgrim.git
Compare commits
3 Commits
d47259fc68
...
3754a68a80
| Author | SHA1 | Date |
|---|---|---|
|
|
3754a68a80 | |
|
|
183e0bc8c7 | |
|
|
8cc42e390a |
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
Loading…
Reference in New Issue