This commit is contained in:
Gustavo Henrique Miranda 2025-07-17 01:50:33 +00:00 committed by GitHub
commit 9d259d2119
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
11 changed files with 235 additions and 74 deletions

View File

@ -5,7 +5,7 @@
[project] [project]
name = "Pilgrim" name = "Pilgrim"
version = "0.0.3" version = "0.0.4"
authors = [ authors = [
{ name="Gustavo Henrique Santos Souza de Miranda", email="gustavohssmiranda@gmail.com" } { name="Gustavo Henrique Santos Souza de Miranda", email="gustavohssmiranda@gmail.com" }
] ]
@ -20,6 +20,9 @@
dependencies = [ dependencies = [
"sqlalchemy", "sqlalchemy",
"textual", "textual",
"tomli",
"tomli_w"
] ]
[template.plugins.default] [template.plugins.default]

View File

@ -1,19 +1,21 @@
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 pilgrim.utils import DirectoryManager from pilgrim.utils import ConfigManager
class Application: class Application:
def __init__(self): def __init__(self):
self.config_dir = DirectoryManager.get_config_directory() self.config_manager = ConfigManager()
self.database = Database() self.config_manager.read_config() # Chamar antes de criar o Database
self.database = Database(self.config_manager)
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, self.config_manager)
def run(self): def run(self):
print(f"URL do banco: {self.config_manager.database_url}")
self.database.create() self.database.create()
self.ui.run() self.ui.run()

View File

@ -5,38 +5,24 @@ from pathlib import Path
import os import os
import shutil import shutil
from pilgrim.utils import ConfigManager
Base = declarative_base() Base = declarative_base()
def get_database_path() -> Path:
"""
Get the database file path following XDG Base Directory specification.
Creates the directory if it doesn't exist.
"""
# Get home directory
home = Path.home()
# Create .pilgrim directory if it doesn't exist
pilgrim_dir = home / ".pilgrim"
pilgrim_dir.mkdir(exist_ok=True)
# Database file path
db_path = pilgrim_dir / "database.db"
# If database doesn't exist in new location but exists in current directory,
# migrate it
if not db_path.exists():
current_db = Path("database.db")
if current_db.exists():
shutil.copy2(current_db, db_path)
print(f"Database migrated from {current_db} to {db_path}")
return db_path
class Database: class Database:
def __init__(self):
db_path = get_database_path() def __init__(self, config_manager: ConfigManager):
self.db_path = config_manager.database_url
# Garantir que o diretório existe
db_dir = os.path.dirname(self.db_path)
if not os.path.exists(db_dir):
os.makedirs(db_dir, exist_ok=True)
self.engine = create_engine( self.engine = create_engine(
f"sqlite:///{db_path}", f"sqlite:///{self.db_path}",
echo=False, echo=False,
connect_args={"check_same_thread": False}, connect_args={"check_same_thread": False},
) )

View File

@ -3,7 +3,7 @@ from textual.binding import Binding
from textual.screen import Screen from textual.screen import Screen
from textual.widgets import Header, Footer, Button, Label, TextArea from textual.widgets import Header, Footer, Button, Label, TextArea
from textual.containers import Container from textual.containers import Container
from importlib.metadata import version
class AboutScreen(Screen[bool]): class AboutScreen(Screen[bool]):
"""Screen to display application information.""" """Screen to display application information."""
@ -20,7 +20,7 @@ class AboutScreen(Screen[bool]):
self.app_title = Label("Pilgrim", id="AboutScreen_AboutTitle",classes="AboutScreen_AboutTitle") self.app_title = Label("Pilgrim", id="AboutScreen_AboutTitle",classes="AboutScreen_AboutTitle")
self.content = Label("A TUI Based Travel Diary Application", id="AboutScreen_AboutContent", self.content = Label("A TUI Based Travel Diary Application", id="AboutScreen_AboutContent",
classes="AboutScreen_AboutContent") classes="AboutScreen_AboutContent")
self.version = Label("Version: 0.0.1", id="AboutScreen_AboutVersion", self.version = Label(f"Version: {version('Pilgrim')}", id="AboutScreen_AboutVersion",
classes="AboutScreen_AboutVersion") classes="AboutScreen_AboutVersion")
self.developer = Label("Developed By: Gustavo Henrique Miranda ", id="AboutScreen_AboutAuthor") self.developer = Label("Developed By: Gustavo Henrique Miranda ", id="AboutScreen_AboutAuthor")
self.contact = Label("git.gustavomiranda.xyz", id="AboutScreen_AboutContact", self.contact = Label("git.gustavomiranda.xyz", id="AboutScreen_AboutContact",

View File

@ -206,29 +206,18 @@ class DiaryListScreen(Screen):
"""Action to create new diary""" """Action to create new diary"""
self.app.push_screen(NewDiaryModal(),self._on_new_diary_submitted) self.app.push_screen(NewDiaryModal(),self._on_new_diary_submitted)
def _on_new_diary_submitted(self,result): def _on_new_diary_submitted(self, result):
self.notify(str(result)) """Callback after diary creation"""
if result: if result: # Se result não é string vazia, o diário foi criado
self.notify(f"Creating Diary:{result}'...") self.notify(f"Returning to diary list...")
self.call_later(self._async_create_diary,result) # Atualiza a lista de diários
self.refresh_diaries()
else: else:
self.notify(f"Canceled...") self.notify(f"Creation canceled...")
async def _async_create_diary(self,name: str):
try:
service = self.app.service_manager.get_travel_diary_service()
created_diary = await service.async_create(name)
if created_diary:
self.diary_id_map[created_diary.id] = created_diary.id
await self.async_refresh_diaries()
self.notify(f"Diary: '{name}' created!")
else:
self.notify("Error Creating the diary")
except Exception as e:
self.notify(f"Exception on creating the diary: {str(e)}")
def _on_screen_resume(self) -> None:
super()._on_screen_resume()
self.refresh_diaries()
def action_edit_selected_diary(self): def action_edit_selected_diary(self):
"""Action to edit selected diary""" """Action to edit selected diary"""

View File

@ -8,6 +8,7 @@ from textual.widgets import Label, Input, Button
class EditDiaryModal(ModalScreen[tuple[int,str]]): class EditDiaryModal(ModalScreen[tuple[int,str]]):
BINDINGS = [ BINDINGS = [
Binding("escape", "cancel", "Cancel"), Binding("escape", "cancel", "Cancel"),
Binding("enter", "edit_diary", "Save",priority=True),
] ]
def __init__(self, diary_id: int): def __init__(self, diary_id: int):
@ -32,6 +33,15 @@ class EditDiaryModal(ModalScreen[tuple[int,str]]):
def on_button_pressed(self, event: Button.Pressed) -> None: def on_button_pressed(self, event: Button.Pressed) -> None:
if event.button.id == "save_diary_button": if event.button.id == "save_diary_button":
self.action_edit_diary()
elif event.button.id == "cancel_button":
self.dismiss(None)
def on_key(self, event):
if event.key == "enter":
self.action_edit_diary()
event.prevent_default()
def action_edit_diary(self) -> None:
new_diary_name = self.name_input.value.strip() new_diary_name = self.name_input.value.strip()
if new_diary_name and new_diary_name != self.current_diary_name: if new_diary_name and new_diary_name != self.current_diary_name:
self.dismiss((self.diary_id, new_diary_name)) self.dismiss((self.diary_id, new_diary_name))
@ -41,8 +51,10 @@ class EditDiaryModal(ModalScreen[tuple[int,str]]):
else: else:
self.notify("Diary name cannot be empty.", severity="warning") self.notify("Diary name cannot be empty.", severity="warning")
self.name_input.focus() self.name_input.focus()
elif event.button.id == "cancel_button":
self.dismiss(None) def on_input_submitted(self, event: Input.Submitted) -> None:
if event.input.id == "edit_diary_name_input":
self.action_edit_diary()
def action_cancel(self) -> None: def action_cancel(self) -> None:
self.dismiss(None) self.dismiss(None)

View File

@ -4,13 +4,17 @@ 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
from pilgrim.ui.screens.edit_entry_screen import EditEntryScreen
class NewDiaryModal(ModalScreen[str]): class NewDiaryModal(ModalScreen[str]):
BINDINGS = [ BINDINGS = [
Binding("escape", "cancel", "Cancel"), Binding("escape", "cancel", "Cancel"),
Binding("enter", "create_diary", "Create",priority=True),
] ]
def __init__(self): def __init__(self):
super().__init__() super().__init__()
self.auto_open = self.app.config_manager.auto_open_new_diary
self.name_input = Input(id="NewDiaryModal-NameInput",classes="NewDiaryModal-NameInput") # This ID is fine, it's specific to the input self.name_input = Input(id="NewDiaryModal-NameInput",classes="NewDiaryModal-NameInput") # This ID is fine, it's specific to the input
def compose(self) -> ComposeResult: def compose(self) -> ComposeResult:
@ -31,15 +35,46 @@ class NewDiaryModal(ModalScreen[str]):
def on_button_pressed(self, event: Button.Pressed) -> None: def on_button_pressed(self, event: Button.Pressed) -> None:
"""Handles button clicks.""" """Handles button clicks."""
if event.button.id == "create_diary_button": if event.button.id == "create_diary_button":
diary_name = self.name_input.value.strip() self.action_create_diary()
if diary_name:
self.dismiss(diary_name)
else:
self.notify("Diary name cannot be empty.", severity="warning")
self.name_input.focus()
elif event.button.id == "cancel_button": elif event.button.id == "cancel_button":
self.dismiss("") self.dismiss()
def action_cancel(self) -> None: def action_cancel(self) -> None:
"""Action to cancel the modal.""" """Action to cancel the modal."""
self.dismiss("") self.dismiss("")
def action_create_diary(self) -> None:
diary_name = self.name_input.value.strip()
if diary_name:
self.call_later(self._async_create_diary, diary_name)
else:
self.notify("Diary name cannot be empty.", severity="warning")
self.name_input.focus()
def on_input_submitted(self, event: Input.Submitted) -> None:
if event.input.id == "NewDiaryModal-NameInput":
self.action_create_diary()
async def _async_create_diary(self, name: str):
try:
service = self.app.service_manager.get_travel_diary_service()
created_diary = await service.async_create(name)
if created_diary:
self.dismiss(name)
if self.auto_open:
self.app.push_screen(EditEntryScreen(diary_id=created_diary.id))
self.notify(f"Diary: '{name}' created!")
else:
self.notify("Error Creating the diary")
except Exception as e:
self.notify(f"Exception on creating the diary: {str(e)}")

View File

@ -9,6 +9,7 @@ from pilgrim.service.servicemanager import ServiceManager
from pilgrim.ui.screens.about_screen import AboutScreen from pilgrim.ui.screens.about_screen import AboutScreen
from pilgrim.ui.screens.diary_list_screen import DiaryListScreen from pilgrim.ui.screens.diary_list_screen import DiaryListScreen
from pilgrim.ui.screens.edit_entry_screen import EditEntryScreen from pilgrim.ui.screens.edit_entry_screen import EditEntryScreen
from pilgrim.utils import ConfigManager
CSS_FILE_PATH = Path(__file__).parent / "styles" / "pilgrim.css" CSS_FILE_PATH = Path(__file__).parent / "styles" / "pilgrim.css"
@ -16,9 +17,10 @@ CSS_FILE_PATH = Path(__file__).parent / "styles" / "pilgrim.css"
class UIApp(App): class UIApp(App):
CSS_PATH = CSS_FILE_PATH CSS_PATH = CSS_FILE_PATH
def __init__(self,service_manager: ServiceManager, **kwargs): def __init__(self,service_manager: ServiceManager, config_manager: ConfigManager, **kwargs):
super().__init__(**kwargs) super().__init__(**kwargs)
self.service_manager = service_manager self.service_manager = service_manager
self.config_manager = config_manager
def on_mount(self) -> None: def on_mount(self) -> None:

View File

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

View File

@ -0,0 +1,107 @@
import os.path
from os import PathLike
from threading import Lock
import tomli
import tomli_w
from pilgrim.utils import DirectoryManager
class SingletonMeta(type):
_instances = {}
_lock: Lock = Lock()
def __call__(cls, *args, **kwargs):
with cls._lock:
if cls not in cls._instances:
instance = super().__call__(*args, **kwargs)
cls._instances[cls] = instance
return cls._instances[cls]
class ConfigManager(metaclass=SingletonMeta):
def __init__(self):
self.database_url = None
self.database_type = None
self.auto_open_diary = None
self.auto_open_new_diary = None
self.config_dir = DirectoryManager.get_config_directory()
self.__data = None
def read_config(self):
if os.path.exists(f"{DirectoryManager.get_config_directory()}/config.toml"):
try:
with open(f"{DirectoryManager.get_config_directory()}/config.toml", "rb") as f:
data = tomli.load(f)
except tomli.TOMLDecodeError as e:
raise ValueError(f"Invalid TOML configuration: {e}")
except Exception as e:
raise RuntimeError(f"Error reading configuration: {e}")
self.__data = data
self.database_url = self.__data["database"]["url"]
self.database_type = self.__data["database"]["type"]
if self.__data["settings"]["diary"]["auto_open_diary_on_startup"] == "":
self.auto_open_diary = None
self.auto_open_new_diary = self.__data["settings"]["diary"]["auto_open_on_creation"]
else:
print("Error: config.toml not found.")
self.create_config()
self.read_config()
def create_config(self, config: dict = None):
# Garantir que o diretório de configuração existe
config_dir = DirectoryManager.get_config_directory()
if not os.path.exists(config_dir):
os.makedirs(config_dir, exist_ok=True)
default = {
"database": {
"url": f"{config_dir}/database.db",
"type": "sqlite"
},
"settings": {
"diary": {
"auto_open_diary_on_startup": "",
"auto_open_on_creation": False
}
}
}
if config is None:
config = default
try:
with open(f"{config_dir}/config.toml", "wb") as f:
tomli_w.dump(config, f)
except Exception as e:
raise RuntimeError(f"Error creating configuration: {e}")
def save_config(self):
if self.__data is None:
self.read_config()
if self.__data is None:
raise RuntimeError("Error reading configuration.")
self.__data["database"]["url"] = self.database_url
self.__data["database"]["type"] = self.database_type
self.__data["settings"]["diary"]["auto_open_diary_on_startup"] = self.auto_open_diary or ""
self.__data["settings"]["diary"]["auto_open_on_creation"] = self.auto_open_new_diary
try:
self.create_config(self.__data)
except Exception as e:
raise RuntimeError(f"Error saving configuration: {e}")
def set_config_dir(self, value):
self.config_dir = value
def set_database_url(self, value: str):
self.database_url = value
def set_auto_open_diary(self, value: str):
self.auto_open_diary = value
def set_auto_open_new_diary(self, value: bool):
self.auto_open_new_diary = value

View File

@ -1,4 +1,5 @@
import os import os
import shutil
from pathlib import Path from pathlib import Path
@ -37,3 +38,26 @@ class DirectoryManager:
def get_diary_images_directory(directory_name: str) -> Path: def get_diary_images_directory(directory_name: str) -> Path:
"""Returns the images directory path for a specific diary.""" """Returns the images directory path for a specific diary."""
return DirectoryManager.get_diary_data_directory(directory_name) / "images" return DirectoryManager.get_diary_data_directory(directory_name) / "images"
@staticmethod
def get_database_path() -> Path:
"""
Get the database file path following XDG Base Directory specification.
Creates the directory if it doesn't exist.
"""
pilgrim_dir = DirectoryManager.get_config_directory()
db_path = pilgrim_dir / "database.db"
# If database doesn't exist in new location but exists in current directory,
# migrate it
if not db_path.exists():
current_db = Path("database.db")
if current_db.exists():
try:
shutil.copy2(current_db, db_path)
# Consider using logging instead of print
print(f"Database migrated from {current_db} to {db_path}")
except (OSError, shutil.Error) as e:
raise RuntimeError(f"Failed to migrate database: {e}")
return db_path