Compare commits

...

10 Commits

Author SHA1 Message Date
Gustavo Henrique Miranda d13750f725
Merge pull request #23 from gmbrax/fix/wrong-database-access
Fix/wrong database access
2025-07-06 03:28:27 -03:00
Gustavo Henrique Santos Souza de Miranda 4dd5126d41 Changed back to use the entry_service.py and not call the database directly from the ui. 2025-07-06 03:26:06 -03:00
Gustavo Henrique Miranda 0b9b3c8cf0
Merge pull request #22 from gmbrax/fix/binding-not-working
Merging fix/binding-not-working into development
2025-07-06 03:11:17 -03:00
Gustavo Henrique Miranda 42c5471ba7
Merge branch 'development' into fix/binding-not-working 2025-07-06 03:10:24 -03:00
Gustavo Henrique Santos Souza de Miranda 8a10fddb14 Refactored bindings to use `Binding` class for consistency across modal and screen components. 2025-07-06 03:04:18 -03:00
Gustavo Henrique Miranda ad9d6ae3b5
Merge pull request #20 from gmbrax/feat/photo-copying-system-dir
Merging feat/photo-copying-system-dir to staging
2025-07-06 01:37:30 -03:00
Gustavo Henrique Miranda 20c56e2c1b
Merge pull request #19 from gmbrax/feat/photo-copying-system-dir
Feat/photo copying system dir
2025-07-06 01:18:12 -03:00
Gustavo Henrique Santos Souza de Miranda 090bbeda1a Implemented directory management and improved TravelDiary and Photo handling logic.
- Added `DirectoryManager` utility for consistent directory operations.
- Introduced directory name sanitization and uniqueness enforcement for `TravelDiary`.
- Updated `TravelDiaryService` with enhanced creation, update, and deletion workflows, including filesystem management.
- Improved `PhotoService` to hash files, copy photos to specific diary directories, and manage updates safely.
- Refined error handling and cascaded changes across related entities (e.g., `Entry` relationships).
2025-07-06 01:07:25 -03:00
Gustavo Henrique Miranda b1e83aabbb
Merge pull request #18 from gmbrax/feat/XDG-Compliance
Applying the XDG Compliance Branch to the Stagin Branch
2025-07-05 09:23:52 -03:00
Gustavo Henrique Miranda eb511ad756
Merge pull request #17 from gmbrax/feat/photo-reference-system
Feat/photo reference system
2025-07-05 09:19:02 -03:00
11 changed files with 326 additions and 114 deletions

View File

@ -1,47 +1,18 @@
from pilgrim.database import Database from pilgrim.database import Database
from pilgrim.service.servicemanager import ServiceManager from pilgrim.service.servicemanager import ServiceManager
from pilgrim.ui.ui import UIApp from pilgrim.ui.ui import UIApp
from pathlib import Path from pilgrim.utils import DirectoryManager
import os
import sys
class Application: class Application:
def __init__(self): def __init__(self):
self.config_dir = self._ensure_config_directory() self.config_dir = DirectoryManager.get_config_directory()
self.database = Database() self.database = Database()
session = self.database.session() session = self.database.session()
session_manager = ServiceManager() session_manager = ServiceManager()
session_manager.set_session(session) session_manager.set_session(session)
self.ui = UIApp(session_manager) self.ui = UIApp(session_manager)
def _ensure_config_directory(self) -> Path:
"""
Ensures the ~/.pilgrim directory exists and has the correct permissions.
Creates it if it doesn't exist.
Returns the Path object for the config directory.
"""
home = Path.home()
config_dir = home / ".pilgrim"
try:
# Create directory if it doesn't exist
config_dir.mkdir(exist_ok=True)
# Ensure correct permissions (rwx for user only)
os.chmod(config_dir, 0o700)
# Create an empty .gitignore if it doesn't exist
gitignore = config_dir / ".gitignore"
if not gitignore.exists():
gitignore.write_text("*\n")
return config_dir
except Exception as e:
print(f"Error setting up Pilgrim configuration directory: {str(e)}", file=sys.stderr)
sys.exit(1)
def run(self): def run(self):
self.database.create() self.database.create()
self.ui.run() self.ui.run()

View File

@ -1,9 +1,9 @@
from typing import Any from typing import Any
from pilgrim.models.photo_in_entry import photo_entry_association
from sqlalchemy import Column, Integer, String, ForeignKey, DateTime from sqlalchemy import Column, Integer, String, ForeignKey, DateTime
from sqlalchemy.orm import relationship from sqlalchemy.orm import relationship
from pilgrim.models.photo_in_entry import photo_entry_association
from ..database import Base from ..database import Base
@ -17,7 +17,9 @@ class Entry(Base):
"Photo", "Photo",
secondary=photo_entry_association, secondary=photo_entry_association,
back_populates="entries") back_populates="entries")
fk_travel_diary_id = Column(Integer, ForeignKey("travel_diaries.id"),nullable=False) fk_travel_diary_id = Column(Integer, ForeignKey("travel_diaries.id"), nullable=False)
travel_diary = relationship("TravelDiary", back_populates="entries")
def __init__(self, title: str, text: str, date: str, travel_diary_id: int, **kw: Any): def __init__(self, title: str, text: str, date: str, travel_diary_id: int, **kw: Any):
super().__init__(**kw) super().__init__(**kw)
self.title = title self.title = title

View File

@ -1,14 +1,26 @@
from typing import Any from typing import Any
from sqlalchemy import Column, String, Integer from sqlalchemy import Column, Integer, String, UniqueConstraint
from sqlalchemy.orm import relationship
from ..database import Base from .. import database
class TravelDiary(Base):
class TravelDiary(database.Base):
__tablename__ = "travel_diaries" __tablename__ = "travel_diaries"
id = Column(Integer, primary_key=True) id = Column(Integer, primary_key=True)
name = Column(String) name = Column(String, nullable=False)
directory_name = Column(String, nullable=False, unique=True)
entries = relationship("Entry", back_populates="travel_diary", cascade="all, delete-orphan")
def __init__(self, name: str, **kw: Any): __table_args__ = (
UniqueConstraint('directory_name', name='uq_travel_diary_directory_name'),
)
def __init__(self, name: str, directory_name: str = None, **kw: Any):
super().__init__(**kw) super().__init__(**kw)
self.name = name self.name = name
self.directory_name = directory_name # Será definido pelo service
def __repr__(self):
return f"<TravelDiary(id={self.id}, name='{self.name}', directory_name='{self.directory_name}')>"

View File

@ -1,26 +1,76 @@
import hashlib
import os
import shutil
from datetime import datetime
from pathlib import Path from pathlib import Path
from typing import List from typing import List
from datetime import datetime
import hashlib
from pilgrim.models.photo import Photo from pilgrim.models.photo import Photo
from pilgrim.models.travel_diary import TravelDiary from pilgrim.models.travel_diary import TravelDiary
from pilgrim.utils import DirectoryManager
class PhotoService: class PhotoService:
def __init__(self, session): def __init__(self, session):
self.session = session self.session = session
def _hash_file(self,filepath):
def _hash_file(self, filepath: Path) -> str:
"""Calculate hash of a file using SHA3-384."""
hash_func = hashlib.new('sha3_384') hash_func = hashlib.new('sha3_384')
with open(filepath, 'rb') as f: with open(filepath, 'rb') as f:
while chunk := f.read(8192): while chunk := f.read(8192):
hash_func.update(chunk) hash_func.update(chunk)
return hash_func.hexdigest() return hash_func.hexdigest()
def _ensure_images_directory(self, travel_diary: TravelDiary) -> Path:
"""
Ensures the images directory exists for the given diary.
Returns the path to the images directory.
"""
images_dir = DirectoryManager.get_diary_images_directory(travel_diary.directory_name)
if not images_dir.exists():
images_dir.mkdir(parents=True)
os.chmod(images_dir, 0o700) # Ensure correct permissions
return images_dir
def _copy_photo_to_diary(self, source_path: Path, travel_diary: TravelDiary) -> Path:
"""
Copies a photo to the diary's images directory.
Returns the path to the copied file.
"""
images_dir = self._ensure_images_directory(travel_diary)
# Get original filename and extension
original_name = Path(source_path).name
# Create destination path
dest_path = images_dir / original_name
# If file with same name exists, add a number
counter = 1
while dest_path.exists():
name_parts = original_name.rsplit('.', 1)
if len(name_parts) > 1:
dest_path = images_dir / f"{name_parts[0]}_{counter}.{name_parts[1]}"
else:
dest_path = images_dir / f"{original_name}_{counter}"
counter += 1
# Copy the file
shutil.copy2(source_path, dest_path)
os.chmod(dest_path, 0o600) # Read/write for owner only
return dest_path
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()
if not travel_diary: if not travel_diary:
return None return None
# Copy photo to diary's images directory
copied_path = self._copy_photo_to_diary(filepath, travel_diary)
# Convert addition_date string to datetime if needed # Convert addition_date string to datetime if needed
if isinstance(addition_date, str): if isinstance(addition_date, str):
@ -28,20 +78,24 @@ class PhotoService:
addition_date = datetime.strptime(addition_date, "%Y-%m-%d %H:%M:%S") addition_date = datetime.strptime(addition_date, "%Y-%m-%d %H:%M:%S")
except ValueError: except ValueError:
addition_date = None addition_date = None
# Calculate hash from the copied file
photo_hash = self._hash_file(copied_path)
new_photo = Photo( new_photo = Photo(
filepath=filepath, filepath=str(copied_path), # Store the path to the copied file
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) photo_hash=photo_hash
) )
self.session.add(new_photo) self.session.add(new_photo)
self.session.commit() self.session.commit()
self.session.refresh(new_photo) self.session.refresh(new_photo)
return new_photo return new_photo
def read_by_id(self, photo_id:int) -> Photo: def read_by_id(self, photo_id:int) -> Photo:
return self.session.query(Photo).get(photo_id) return self.session.query(Photo).get(photo_id)
@ -51,15 +105,30 @@ class PhotoService:
def update(self, photo_src: Photo, photo_dst: Photo) -> Photo | None: def update(self, photo_src: Photo, photo_dst: Photo) -> Photo | None:
original: Photo = self.read_by_id(photo_src.id) original: Photo = self.read_by_id(photo_src.id)
if original: if original:
original.filepath = photo_dst.filepath # If filepath changed, need to copy new file
if str(photo_dst.filepath) != str(original.filepath):
travel_diary = self.session.query(TravelDiary).filter(
TravelDiary.id == original.fk_travel_diary_id).first()
if travel_diary:
# Copy new photo
new_path = self._copy_photo_to_diary(Path(photo_dst.filepath), travel_diary)
# Delete old photo if it exists in our images directory
old_path = Path(original.filepath)
if old_path.exists() and str(DirectoryManager.get_diaries_root()) in str(old_path):
old_path.unlink()
original.filepath = str(new_path)
# Update hash based on the new copied file
original.photo_hash = self._hash_file(new_path)
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 = []
original.entries = photo_dst.entries # Replace instead of extend original.entries = photo_dst.entries # Replace instead of extend
self.session.commit() self.session.commit()
self.session.refresh(original) self.session.refresh(original)
return original return original
@ -78,8 +147,12 @@ class PhotoService:
id=excluded.id, id=excluded.id,
photo_hash=excluded.photo_hash, photo_hash=excluded.photo_hash,
entries=excluded.entries, entries=excluded.entries,
) )
# Delete the physical file if it exists in our images directory
file_path = Path(excluded.filepath)
if file_path.exists() and str(DirectoryManager.get_diaries_root()) in str(file_path):
file_path.unlink()
self.session.delete(excluded) self.session.delete(excluded)
self.session.commit() self.session.commit()

View File

@ -1,30 +1,132 @@
import os
import re
import shutil
from pathlib import Path
from pilgrim.utils import DirectoryManager
from sqlalchemy.exc import IntegrityError
from ..models.travel_diary import TravelDiary from ..models.travel_diary import TravelDiary
import asyncio
class TravelDiaryService: class TravelDiaryService:
def __init__(self,session): def __init__(self, session):
self.session = session self.session = session
async def async_create(self, name:str):
new_travel_diary = TravelDiary(name)
self.session.add(new_travel_diary)
self.session.commit()
self.session.refresh(new_travel_diary)
return new_travel_diary def _sanitize_directory_name(self, name: str) -> str:
"""
Sanitizes a diary name for use as a directory name.
- Removes special characters
- Replaces spaces with underscores
- Ensures name is unique by adding a suffix if needed
"""
# Remove special characters and replace spaces
safe_name = re.sub(r'[^\w\s-]', '', name)
safe_name = safe_name.strip().replace(' ', '_').lower()
def read_by_id(self, travel_id:int): # Ensure we have a valid name
return self.session.query(TravelDiary).get(travel_id) if not safe_name:
safe_name = "unnamed_diary"
# Check if name is already used in database
base_name = safe_name
counter = 1
while self.session.query(TravelDiary).filter_by(directory_name=safe_name).first() is not None:
safe_name = f"{base_name}_{counter}"
counter += 1
return safe_name
def _get_diary_directory(self, diary: TravelDiary) -> Path:
"""Returns the directory path for a diary."""
return DirectoryManager.get_diary_directory(diary.directory_name)
def _get_diary_data_directory(self, diary: TravelDiary) -> Path:
"""Returns the data directory path for a diary."""
return DirectoryManager.get_diary_data_directory(diary.directory_name)
def _ensure_diary_directory(self, diary: TravelDiary) -> Path:
"""
Creates and returns the directory structure for a diary:
~/.pilgrim/diaries/{directory_name}/data/
"""
# Create diary directory
diary_dir = self._get_diary_directory(diary)
diary_dir.mkdir(exist_ok=True)
os.chmod(diary_dir, 0o700)
# Create data subdirectory
data_dir = self._get_diary_data_directory(diary)
data_dir.mkdir(exist_ok=True)
os.chmod(data_dir, 0o700)
return data_dir
def _cleanup_diary_directory(self, diary: TravelDiary):
"""Removes the diary directory and all its contents."""
diary_dir = self._get_diary_directory(diary)
if diary_dir.exists():
shutil.rmtree(diary_dir)
async def async_create(self, name: str):
# Generate safe directory name
directory_name = self._sanitize_directory_name(name)
# Create diary with directory name
new_travel_diary = TravelDiary(name=name, directory_name=directory_name)
try:
self.session.add(new_travel_diary)
self.session.commit()
self.session.refresh(new_travel_diary)
# Create directory structure for the new diary
self._ensure_diary_directory(new_travel_diary)
return new_travel_diary
except IntegrityError:
self.session.rollback()
raise ValueError(f"Could not create diary: directory name '{directory_name}' already exists")
def read_by_id(self, travel_id: int):
diary = self.session.query(TravelDiary).get(travel_id)
if diary:
# Ensure directory exists when reading
self._ensure_diary_directory(diary)
return diary
def read_all(self): def read_all(self):
return self.session.query(TravelDiary).all() diaries = self.session.query(TravelDiary).all()
# Ensure directories exist for all diaries
for diary in diaries:
self._ensure_diary_directory(diary)
return diaries
def update(self, travel_diary_id: int, name: str): def update(self, travel_diary_id: int, name: str):
original = self.read_by_id(travel_diary_id) original = self.read_by_id(travel_diary_id)
if original is not None: if original is not None:
original.name = name try:
self.session.commit() # Generate new directory name
self.session.refresh(original) new_directory_name = self._sanitize_directory_name(name)
return original old_directory = self._get_diary_directory(original)
# Update diary
original.name = name
original.directory_name = new_directory_name
self.session.commit()
self.session.refresh(original)
# Rename directory if it exists
new_directory = self._get_diary_directory(original)
if old_directory.exists() and old_directory != new_directory:
old_directory.rename(new_directory)
return original
except IntegrityError:
self.session.rollback()
raise ValueError(f"Could not update diary: directory name '{new_directory_name}' already exists")
return None
async def async_update(self, travel_diary_id: int, name: str): async def async_update(self, travel_diary_id: int, name: str):
return self.update(travel_diary_id, name) return self.update(travel_diary_id, name)
@ -32,7 +134,14 @@ class TravelDiaryService:
def delete(self, travel_diary_id: TravelDiary): def delete(self, travel_diary_id: TravelDiary):
excluded = self.read_by_id(travel_diary_id.id) excluded = self.read_by_id(travel_diary_id.id)
if excluded is not None: if excluded is not None:
self.session.delete(travel_diary_id) try:
self.session.commit() # First delete the directory
return excluded self._cleanup_diary_directory(excluded)
# Then delete from database
self.session.delete(travel_diary_id)
self.session.commit()
return excluded
except Exception as e:
self.session.rollback()
raise ValueError(f"Could not delete diary: {str(e)}")
return None return None

View File

@ -1,4 +1,5 @@
from textual.app import ComposeResult from textual.app import ComposeResult
from textual.binding import Binding
from textual.containers import Vertical, Horizontal from textual.containers import Vertical, Horizontal
from textual.screen import ModalScreen from textual.screen import ModalScreen
from textual.widgets import Label, Input, Button from textual.widgets import Label, Input, Button
@ -6,7 +7,7 @@ from textual.widgets import Label, Input, Button
class EditDiaryModal(ModalScreen[tuple[int,str]]): class EditDiaryModal(ModalScreen[tuple[int,str]]):
BINDINGS = [ BINDINGS = [
("escape", "cancel", "Cancel"), Binding("escape", "cancel", "Cancel"),
] ]
def __init__(self, diary_id: int): def __init__(self, diary_id: int):

View File

@ -1,40 +1,36 @@
from typing import Optional, List import re
import asyncio
from datetime import datetime from datetime import datetime
from pathlib import Path from pathlib import Path
import hashlib from typing import Optional, List
import re
import time
from textual.app import ComposeResult
from textual.screen import Screen
from textual.widgets import Header, Footer, Static, TextArea, OptionList, Input, Button
from textual.binding import Binding
from textual.containers import Container, Horizontal, Vertical, ScrollableContainer
from pilgrim.models.entry import Entry from pilgrim.models.entry import Entry
from pilgrim.models.travel_diary import TravelDiary
from pilgrim.models.photo import Photo from pilgrim.models.photo import Photo
from pilgrim.models.travel_diary import TravelDiary
from pilgrim.ui.screens.modals.add_photo_modal import AddPhotoModal from pilgrim.ui.screens.modals.add_photo_modal import AddPhotoModal
from pilgrim.ui.screens.modals.edit_photo_modal import EditPhotoModal
from pilgrim.ui.screens.modals.confirm_delete_modal import ConfirmDeleteModal from pilgrim.ui.screens.modals.confirm_delete_modal import ConfirmDeleteModal
from pilgrim.ui.screens.modals.edit_photo_modal import EditPhotoModal
from pilgrim.ui.screens.modals.file_picker_modal import FilePickerModal from pilgrim.ui.screens.modals.file_picker_modal import FilePickerModal
from pilgrim.ui.screens.rename_entry_modal import RenameEntryModal from pilgrim.ui.screens.rename_entry_modal import RenameEntryModal
from textual.app import ComposeResult
from textual.binding import Binding
from textual.containers import Container, Horizontal, Vertical
from textual.screen import Screen
from textual.widgets import Header, Footer, Static, TextArea, OptionList
class EditEntryScreen(Screen): class EditEntryScreen(Screen):
TITLE = "Pilgrim - Edit" TITLE = "Pilgrim - Edit"
BINDINGS = [ BINDINGS = [
("ctrl+q", "quit", "Quit"), Binding("ctrl+q", "quit", "Quit"),
("ctrl+s", "save", "Save"), Binding("ctrl+s", "save", "Save"),
("ctrl+n", "new_entry", "New Entry"), Binding("ctrl+n", "new_entry", "New Entry"),
("ctrl+shift+n", "next_entry", "Next Entry"), Binding("ctrl+shift+n", "next_entry", "Next Entry"),
("ctrl+shift+p", "prev_entry", "Previous Entry"), Binding("ctrl+shift+p", "prev_entry", "Previous Entry"),
("ctrl+r", "rename_entry", "Rename Entry"), Binding("ctrl+r", "rename_entry", "Rename Entry"),
("f8", "toggle_sidebar", "Toggle Photos"), Binding("f8", "toggle_sidebar", "Toggle Photos"),
("f9", "toggle_focus", "Toggle Focus"), Binding("f9", "toggle_focus", "Toggle Focus"),
("escape", "back_to_list", "Back to List"), Binding("escape", "back_to_list", "Back to List"),
] ]
def __init__(self, diary_id: int = 1): def __init__(self, diary_id: int = 1):
@ -333,9 +329,11 @@ class EditEntryScreen(Screen):
all_photos = photo_service.read_all() all_photos = photo_service.read_all()
self.cached_photos = [photo for photo in all_photos if photo.fk_travel_diary_id == diary_id] self.cached_photos = [photo for photo in all_photos if photo.fk_travel_diary_id == diary_id]
self.cached_photos.sort(key=lambda x: x.id) self.cached_photos.sort(key=lambda x: x.id)
return self.cached_photos
except Exception as e: except Exception as e:
self.notify(f"Error loading photos: {str(e)}") self.notify(f"Error loading photos: {str(e)}")
return []
def action_toggle_sidebar(self): def action_toggle_sidebar(self):
@ -913,13 +911,11 @@ class EditEntryScreen(Screen):
self.call_later(self._async_update_entry, content, photos_to_link) self.call_later(self._async_update_entry, content, photos_to_link)
async def _async_create_entry(self, content: str, photos_to_link: List[Photo]): async def _async_create_entry(self, content: str, photos_to_link: List[Photo]):
"""Cria uma nova entrada e associa as fotos referenciadas.""" """Creates a new entry and links the referenced photos."""
service_manager = self.app.service_manager
db_session = service_manager.get_db_session()
try: try:
service_manager = self.app.service_manager
entry_service = service_manager.get_entry_service() entry_service = service_manager.get_entry_service()
# O service.create deve criar o objeto em memória, mas NÃO fazer o commit ainda.
new_entry = entry_service.create( new_entry = entry_service.create(
travel_diary_id=self.diary_id, travel_diary_id=self.diary_id,
title=self.new_entry_title, title=self.new_entry_title,
@ -929,7 +925,6 @@ class EditEntryScreen(Screen):
) )
if new_entry: if new_entry:
# A partir daqui, é só atualizar a UI como você já fazia
self.entries.append(new_entry) self.entries.append(new_entry)
self.entries.sort(key=lambda x: x.id) self.entries.sort(key=lambda x: x.id)
@ -940,47 +935,51 @@ class EditEntryScreen(Screen):
self.is_new_entry = False self.is_new_entry = False
self.has_unsaved_changes = False self.has_unsaved_changes = False
self._original_content = new_entry.text # Pode ser o texto com hashes curtos self._original_content = new_entry.text
self.new_entry_title = "New Entry" self.new_entry_title = "New Entry"
self.next_entry_id = max(entry.id for entry in self.entries) + 1 self.next_entry_id = max(entry.id for entry in self.entries) + 1
self._update_entry_display() self._update_entry_display()
self.notify(f"✅ New Entry: '{new_entry.title}' Successfully saved") self.notify(f"Entry '{new_entry.title}' saved successfully!")
else: else:
self.notify("Error creating the Entry") self.notify("Error creating entry")
except Exception as e: except Exception as e:
self.notify(f"Error creating the entry: {str(e)}") self.notify(f"Error creating entry: {str(e)}")
async def _async_update_entry(self, updated_content: str, photos_to_link: List[Photo]): async def _async_update_entry(self, updated_content: str, photos_to_link: List[Photo]):
"""Atualiza uma entrada existente e sua associação de fotos.""" """Updates an existing entry and its photo links."""
service_manager = self.app.service_manager
try: try:
if not self.entries: if not self.entries:
self.notify("No Entry to update") self.notify("No entry to update")
return return
service_manager = self.app.service_manager
entry_service = service_manager.get_entry_service() entry_service = service_manager.get_entry_service()
current_entry = self.entries[self.current_entry_index] current_entry = self.entries[self.current_entry_index]
entry_result : Entry = Entry(
entry_result = Entry(
id=current_entry.id,
title=current_entry.title, title=current_entry.title,
text=updated_content, text=updated_content,
photos=photos_to_link, photos=photos_to_link,
date=current_entry.date, date=current_entry.date,
travel_diary_id=self.diary_id travel_diary_id=self.diary_id,
fk_travel_diary_id=self.diary_id
) )
entry_service.update(current_entry, entry_result)
# A partir daqui, é só atualizar a UI result = entry_service.update(current_entry, entry_result)
self.has_unsaved_changes = False
self._original_content = updated_content # Pode ser o texto com hashes curtos if result:
self._update_sub_header() self.has_unsaved_changes = False
self.notify(f"✅ Entry: '{current_entry.title}' sucesfully saved") self._original_content = updated_content
self._update_sub_header()
self.notify(f"Entry '{current_entry.title}' saved successfully!")
else:
self.notify("Error updating entry")
except Exception as e: except Exception as e:
# Desfaz as mudanças em caso de erro self.notify(f"Error updating entry: {str(e)}")
self.notify(f"❌ Error on updating the entry:: {str(e)}")
def on_key(self, event): def on_key(self, event):

View File

@ -1,11 +1,13 @@
from textual.app import ComposeResult from textual.app import ComposeResult
from textual.binding import Binding
from textual.containers import Vertical, Horizontal from textual.containers import Vertical, Horizontal
from textual.screen import ModalScreen from textual.screen import ModalScreen
from textual.widgets import Label, Input, Button from textual.widgets import Label, Input, Button
class NewDiaryModal(ModalScreen[str]): class NewDiaryModal(ModalScreen[str]):
BINDINGS = [ BINDINGS = [
("escape", "cancel", "Cancel"), Binding("escape", "cancel", "Cancel"),
] ]
def __init__(self): def __init__(self):
super().__init__() super().__init__()

View File

@ -1,4 +1,5 @@
from textual.app import ComposeResult from textual.app import ComposeResult
from textual.binding import Binding
from textual.containers import Vertical, Horizontal from textual.containers import Vertical, Horizontal
from textual.screen import ModalScreen from textual.screen import ModalScreen
from textual.widgets import Label, Input, Button from textual.widgets import Label, Input, Button
@ -8,7 +9,7 @@ class RenameEntryModal(ModalScreen[str]):
"""A modal screen to rename a diary entry.""" """A modal screen to rename a diary entry."""
BINDINGS = [ BINDINGS = [
("escape", "cancel", "Cancel"), Binding("escape", "cancel", "Cancel"),
] ]
def __init__(self, current_name: str): def __init__(self, current_name: str):

View File

@ -0,0 +1,3 @@
from .directory_manager import DirectoryManager
__all__ = ['DirectoryManager']

View File

@ -0,0 +1,39 @@
import os
from pathlib import Path
class DirectoryManager:
@staticmethod
def get_config_directory() -> Path:
"""
Get the ~/.pilgrim directory path.
Creates it if it doesn't exist.
"""
home = Path.home()
config_dir = home / ".pilgrim"
config_dir.mkdir(exist_ok=True)
os.chmod(config_dir, 0o700)
return config_dir
@staticmethod
def get_diaries_root() -> Path:
"""Returns the path to the diaries directory."""
diaries_dir = DirectoryManager.get_config_directory() / "diaries"
diaries_dir.mkdir(exist_ok=True)
os.chmod(diaries_dir, 0o700)
return diaries_dir
@staticmethod
def get_diary_directory(directory_name: str) -> Path:
"""Returns the directory path for a specific diary."""
return DirectoryManager.get_diaries_root() / directory_name
@staticmethod
def get_diary_data_directory(directory_name: str) -> Path:
"""Returns the data directory path for a specific diary."""
return DirectoryManager.get_diary_directory(directory_name) / "data"
@staticmethod
def get_diary_images_directory(directory_name: str) -> Path:
"""Returns the images directory path for a specific diary."""
return DirectoryManager.get_diary_data_directory(directory_name) / "images"