mirror of
https://github.com/sstent/GarminSync.git
synced 2026-01-26 09:02:51 +00:00
205 lines
9.1 KiB
Markdown
205 lines
9.1 KiB
Markdown
Of course. The design has been updated to use the `python-garminconnect` library and now includes a dedicated documentation section with links to all key upstream libraries.
|
|
|
|
-----
|
|
|
|
## **GarminSync Application Design (Python Version)**
|
|
|
|
### **Basic Info**
|
|
|
|
**App Name:** GarminSync
|
|
**What it does:** A CLI application that downloads `.fit` files for every activity in Garmin Connect.
|
|
|
|
-----
|
|
|
|
### **Core Features**
|
|
|
|
1. List all activities (`garminsync list --all`)
|
|
2. List activities that have not been downloaded (`garminsync list --missing`)
|
|
3. List activities that have been downloaded (`garminsync list --downloaded`)
|
|
4. Download all missing activities (`garminsync download --missing`)
|
|
|
|
-----
|
|
|
|
### **Tech Stack 🐍**
|
|
|
|
* **Frontend:** CLI (**Python**)
|
|
* **Backend:** **Python**
|
|
* **Database:** SQLite (`garmin.db`)
|
|
* **Hosting:** Docker container
|
|
* **Key Libraries:**
|
|
* **`python-garminconnect`**: The library for Garmin Connect API communication.
|
|
* **`typer`**: A modern and easy-to-use CLI framework (built on `click`).
|
|
* **`python-dotenv`**: For loading credentials from a `.env` file.
|
|
* **`sqlalchemy`**: A robust ORM for database interaction and schema management (using Alembic for migrations). The built-in `sqlite3` module can be used for simpler needs.
|
|
* **`tqdm`**: For creating user-friendly progress bars.
|
|
|
|
-----
|
|
|
|
### **Data Structure**
|
|
|
|
The core database schema remains the same. It can be defined using raw SQL with the `sqlite3` module or, more robustly, with an `SQLAlchemy` model.
|
|
|
|
**SQLAlchemy Model Example (`models.py`):**
|
|
|
|
```python
|
|
from sqlalchemy import create_engine, Column, Integer, String, Boolean
|
|
from sqlalchemy.orm import declarative_base
|
|
|
|
Base = declarative_base()
|
|
|
|
class Activity(Base):
|
|
__tablename__ = 'activities'
|
|
|
|
activity_id = Column(Integer, primary_key=True)
|
|
start_time = Column(String, nullable=False)
|
|
filename = Column(String, unique=True, nullable=True)
|
|
downloaded = Column(Boolean, default=False, nullable=False)
|
|
```
|
|
|
|
-----
|
|
|
|
### **User Flow**
|
|
|
|
1. User launches the container with their credentials: `docker run -it --env-file .env -v $(pwd)/data:/app/data garminsync`
|
|
2. The application presents a help menu generated by `Typer`.
|
|
3. User runs a command, e.g., `garminsync download --missing`.
|
|
4. The application executes the task, showing progress indicators for API calls and downloads.
|
|
5. Upon completion, the application displays a summary of actions taken.
|
|
|
|
-----
|
|
|
|
### **File Structure**
|
|
|
|
A standard Python project structure is recommended.
|
|
|
|
```
|
|
/garminsync
|
|
├── garminsync/ # Main application package
|
|
│ ├── __init__.py
|
|
│ ├── cli.py # Typer CLI commands and entrypoint
|
|
│ ├── config.py # Configuration loading (dotenv)
|
|
│ ├── database.py # SQLAlchemy models and sync logic
|
|
│ └── garmin.py # Wrapper for the python-garminconnect client
|
|
├── data/ # Directory for downloaded .fit files and DB
|
|
├── tests/ # Unit and integration tests
|
|
├── .env # Stores GARMIN_EMAIL/GARMIN_PASSWORD
|
|
├── .gitignore
|
|
├── Dockerfile
|
|
├── README.md
|
|
└── requirements.txt # Pinned Python dependencies
|
|
```
|
|
|
|
-----
|
|
|
|
### **Technical Implementation Notes**
|
|
|
|
* **Architecture:** A Python application using **`Typer`** for the CLI. Logic is separated into modules for configuration, database interaction, and Garmin communication.
|
|
* **Authentication:** Credentials from `GARMIN_EMAIL` and `GARMIN_PASSWORD` environment variables are loaded via **`python-dotenv`** and passed to **`python-garminconnect`**.
|
|
* **File Naming:** Files will be named using Python's f-strings: `f"activity_{activity_id}_{timestamp}.fit"`.
|
|
* **Rate Limiting:** A simple `time.sleep(2)` will be inserted between API requests to avoid overloading Garmin's servers.
|
|
* **Database:** The schema will be managed by **`SQLAlchemy`**. Versioned migrations can be handled by **Alembic**, which integrates with SQLAlchemy.
|
|
* **Database Sync:** Before any operation, a sync function will use **`python-garminconnect`** to fetch the latest activities and reconcile them with the local SQLite database.
|
|
* **Package Management:** Dependencies and their versions will be pinned in `requirements.txt` for reproducible builds (`pip freeze > requirements.txt`).
|
|
* **Session Management:** **`python-garminconnect`** handles authentication and session management.
|
|
* **Docker Permissions:** Mounting a local `./data` directory into the container allows downloaded files and the database to persist. The `sudo` prefix is generally not required if the user is part of the `docker` group.
|
|
|
|
-----
|
|
|
|
### **Development Phases (Proposed for Python)**
|
|
|
|
#### **Phase 1: Core Infrastructure**
|
|
|
|
* [x] `Dockerfile` creation for a Python environment.
|
|
* Uses Python 3.10 slim base image
|
|
* Sets PYTHONDONTWRITEBYTECODE and PYTHONUNBUFFERED
|
|
* Installs build dependencies
|
|
* Copies requirements first for layer caching
|
|
* Sets ENV_FILE for configuration
|
|
* Entrypoint runs application via `python -m garminsync`
|
|
* [x] Setup `python-dotenv` for environment variable handling.
|
|
* Added `python-dotenv` to requirements.txt
|
|
* Implemented environment loading in config.py:
|
|
```python
|
|
from dotenv import load_dotenv
|
|
load_dotenv() # Load .env file
|
|
```
|
|
* Updated Dockerfile to copy .env file during build
|
|
* Added documentation for environment variable setup
|
|
* [x] Initialize the **`Typer`** CLI framework in `cli.py`.
|
|
* Created basic Typer app structure
|
|
* Implemented skeleton commands for `list` and `download`
|
|
* Added proper type hints and docstrings
|
|
* Integrated with config.py for environment variable loading
|
|
* [x] Implement the **`python-garminconnect`** client wrapper (`garmin.py`).
|
|
* Created GarminClient class with authentication
|
|
* Implemented get_activities() with rate limiting
|
|
* Implemented download_activity_fit() method
|
|
* Added error handling for missing credentials
|
|
|
|
#### **Phase 2: Activity Listing**
|
|
|
|
* [x] Implement the `SQLAlchemy` schema and database connection.
|
|
* Created database.py with Activity model
|
|
* Implemented init_db() for database initialization
|
|
* Added get_session() for session management
|
|
* Implemented sync_database() to fetch activities from Garmin and update database
|
|
* [x] Create the `list` commands (`--all`, `--missing`, `--downloaded`).
|
|
* Implemented command logic in cli.py
|
|
* Added database synchronization before listing
|
|
* Added input validation for filter options
|
|
* Used tqdm for progress display
|
|
|
|
#### **Phase 3: Download Pipeline**
|
|
|
|
* [x] Implement the `.fit` file download function using `python-garminconnect`.
|
|
* Added download logic to cli.py
|
|
* Used GarminClient to download FIT files
|
|
* Created filename-safe timestamps
|
|
* Saved files to data directory
|
|
* [x] Ensure downloads are idempotent (don't re-download existing files).
|
|
* Database tracks downloaded status
|
|
* Only missing activities are downloaded
|
|
* [x] Update the database record to `downloaded=True` on success.
|
|
* Updated activity record after successful download
|
|
* Set filename path in database
|
|
|
|
#### **Phase 4: Polish ✨**
|
|
|
|
* [x] Add **`tqdm`** progress bars for the download command.
|
|
* Implemented in both list and download commands
|
|
* [x] Update Dockerfile to:
|
|
* Use multi-stage builds for smaller image size
|
|
* Add health checks
|
|
* Set non-root user for security
|
|
* [ ] Implement robust error handling (e.g., API errors, network issues).
|
|
* Basic error handling implemented but needs improvement
|
|
* [ ] Write comprehensive `README.md` documentation.
|
|
* [ ] Add unit tests for database and utility functions.
|
|
|
|
#### **Current Issues**
|
|
|
|
1. **Docker Build Warnings**
|
|
- Legacy ENV format warnings during build
|
|
- Solution: Update Dockerfile to use `ENV key=value` format
|
|
|
|
2. **Typer Command Parameters**
|
|
- Occasional `Parameter.make_metavar()` errors
|
|
- Resolved by renaming reserved keywords and fixing decorators
|
|
|
|
3. **Configuration Loading**
|
|
- Initial import issues between modules
|
|
- Resolved by restructuring config loading
|
|
|
|
-----
|
|
|
|
### **Documentation 📚**
|
|
|
|
Here are links to the official documentation for the key libraries used in this design.
|
|
|
|
* **Garmin API:** [`python-garminconnect`](https://www.google.com/search?q=%5Bhttps://github.com/cyberjunky/python-garminconnect%5D\(https://github.com/cyberjunky/python-garminconnect\))
|
|
* **CLI Framework:** [`Typer`](https://www.google.com/search?q=%5Bhttps://typer.tiangolo.com/%5D\(https://typer.tiangolo.com/\))
|
|
* **Environment Variables:** [`python-dotenv`](https://www.google.com/search?q=%5Bhttps://github.com/theskumar/python-dotenv%5D\(https://github.com/theskumar/python-dotenv\))
|
|
* **Database ORM:** [`SQLAlchemy`](https://www.google.com/search?q=%5Bhttps://docs.sqlalchemy.org/en/20/%5D\(https://docs.sqlalchemy.org/en/20/\))
|
|
* **Database Migrations:** [`Alembic`](https://www.google.com/search?q=%5Bhttps://alembic.sqlalchemy.org/en/latest/%5D\(https://alembic.sqlalchemy.org/en/latest/\))
|
|
* **Progress Bars:** [`tqdm`](https://www.google.com/search?q=%5Bhttps://github.com/tqdm/tqdm%5D\(https://github.com/tqdm/tqdm\))
|