Pilgrim/src/pilgrim/service/travel_diary_service.py

183 lines
6.2 KiB
Python

import os
import re
import shutil
from pathlib import Path
from pilgrim.models.entry import Entry
from pilgrim.utils import DirectoryManager
from sqlalchemy.exc import IntegrityError
from pilgrim.models.travel_diary import TravelDiary
from unidecode import unidecode
from pilgrim.service.photo_service import PhotoService
from pilgrim.service.entry_service import EntryService
class TravelDiaryService:
def __init__(self, session):
self.session = session
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
"""
transliterated_name = unidecode(name)
# Remove special characters and replace spaces
safe_name = re.sub(r'[^\w\s-]', '', transliterated_name)
safe_name = safe_name.strip().replace(' ', '_').lower()
# Ensure we have a valid name
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):
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):
original = self.read_by_id(travel_diary_id)
if original is not None:
try:
# Generate new directory name
new_directory_name = self._sanitize_directory_name(name)
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):
return self.update(travel_diary_id, name)
def delete(self, travel_diary_id: TravelDiary):
excluded = self.read_by_id(travel_diary_id.id)
if excluded is not None:
try:
# First delete the directory
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
def delete_all_entries(self,travel_diary: TravelDiary):
diary = self.read_by_id(travel_diary.id)
if diary is not None:
diary.entries = []
self.session.commit()
return True
return False
def delete_all_photos(self,travel_diary: TravelDiary):
diary = self.read_by_id(travel_diary.id)
photo_service = PhotoService(self.session)
entry_service = EntryService(self.session)
if diary is not None:
for entry in list(diary.entries):
entry_service.delete_all_photo_references(entry,commit=False)
for photo in list(diary.photos):
photo_service.delete(photo,commit=False)
self.session.commit()
return True
return False