mirror of
https://github.com/sstent/GarminSync.git
synced 2026-01-26 09:02:51 +00:00
working again stable
This commit is contained in:
@@ -1,111 +1,189 @@
|
||||
"""Database module for GarminSync application."""
|
||||
|
||||
import os
|
||||
from sqlalchemy import create_engine, Column, Integer, String, Boolean, Float
|
||||
from sqlalchemy.orm import declarative_base, sessionmaker
|
||||
from datetime import datetime
|
||||
|
||||
from sqlalchemy import Boolean, Column, Float, Integer, String, create_engine
|
||||
from sqlalchemy.exc import SQLAlchemyError
|
||||
from sqlalchemy.orm import declarative_base, sessionmaker
|
||||
|
||||
Base = declarative_base()
|
||||
|
||||
|
||||
class Activity(Base):
|
||||
__tablename__ = 'activities'
|
||||
|
||||
"""Activity model representing a Garmin activity record."""
|
||||
|
||||
__tablename__ = "activities"
|
||||
|
||||
activity_id = Column(Integer, primary_key=True)
|
||||
start_time = Column(String, nullable=False)
|
||||
activity_type = Column(String, nullable=True) # NEW
|
||||
duration = Column(Integer, nullable=True) # NEW (seconds)
|
||||
distance = Column(Float, nullable=True) # NEW (meters)
|
||||
max_heart_rate = Column(Integer, nullable=True) # NEW
|
||||
avg_power = Column(Float, nullable=True) # NEW
|
||||
calories = Column(Integer, nullable=True) # NEW
|
||||
activity_type = Column(String, nullable=True)
|
||||
duration = Column(Integer, nullable=True)
|
||||
distance = Column(Float, nullable=True)
|
||||
max_heart_rate = Column(Integer, nullable=True)
|
||||
avg_power = Column(Float, nullable=True)
|
||||
calories = Column(Integer, nullable=True)
|
||||
filename = Column(String, unique=True, nullable=True)
|
||||
downloaded = Column(Boolean, default=False, nullable=False)
|
||||
created_at = Column(String, nullable=False)
|
||||
last_sync = Column(String, nullable=True) # ISO timestamp of last sync
|
||||
last_sync = Column(String, nullable=True)
|
||||
|
||||
@classmethod
|
||||
def get_paginated(cls, page=1, per_page=10):
|
||||
"""Get paginated list of activities.
|
||||
|
||||
Args:
|
||||
page: Page number (1-based)
|
||||
per_page: Number of items per page
|
||||
|
||||
Returns:
|
||||
Pagination object with activities
|
||||
"""
|
||||
session = get_session()
|
||||
try:
|
||||
query = session.query(cls).order_by(cls.start_time.desc())
|
||||
page = int(page)
|
||||
per_page = int(per_page)
|
||||
pagination = query.paginate(page=page, per_page=per_page, error_out=False)
|
||||
return pagination
|
||||
finally:
|
||||
session.close()
|
||||
|
||||
def to_dict(self):
|
||||
"""Convert activity to dictionary representation.
|
||||
|
||||
Returns:
|
||||
Dictionary with activity data
|
||||
"""
|
||||
return {
|
||||
"id": self.activity_id,
|
||||
"name": self.filename or "Unnamed Activity",
|
||||
"distance": self.distance,
|
||||
"duration": self.duration,
|
||||
"start_time": self.start_time,
|
||||
"activity_type": self.activity_type,
|
||||
"max_heart_rate": self.max_heart_rate,
|
||||
"avg_power": self.avg_power,
|
||||
"calories": self.calories,
|
||||
}
|
||||
|
||||
|
||||
class DaemonConfig(Base):
|
||||
__tablename__ = 'daemon_config'
|
||||
|
||||
"""Daemon configuration model."""
|
||||
|
||||
__tablename__ = "daemon_config"
|
||||
|
||||
id = Column(Integer, primary_key=True, default=1)
|
||||
enabled = Column(Boolean, default=True, nullable=False)
|
||||
schedule_cron = Column(String, default="0 */6 * * *", nullable=False) # Every 6 hours
|
||||
schedule_cron = Column(String, default="0 */6 * * *", nullable=False)
|
||||
last_run = Column(String, nullable=True)
|
||||
next_run = Column(String, nullable=True)
|
||||
status = Column(String, default="stopped", nullable=False) # stopped, running, error
|
||||
status = Column(String, default="stopped", nullable=False)
|
||||
|
||||
|
||||
class SyncLog(Base):
|
||||
__tablename__ = 'sync_logs'
|
||||
|
||||
"""Sync log model for tracking sync operations."""
|
||||
|
||||
__tablename__ = "sync_logs"
|
||||
|
||||
id = Column(Integer, primary_key=True, autoincrement=True)
|
||||
timestamp = Column(String, nullable=False)
|
||||
operation = Column(String, nullable=False) # sync, download, daemon_start, daemon_stop
|
||||
status = Column(String, nullable=False) # success, error, partial
|
||||
operation = Column(String, nullable=False)
|
||||
status = Column(String, nullable=False)
|
||||
message = Column(String, nullable=True)
|
||||
activities_processed = Column(Integer, default=0, nullable=False)
|
||||
activities_downloaded = Column(Integer, default=0, nullable=False)
|
||||
|
||||
|
||||
def init_db():
|
||||
"""Initialize database connection and create tables"""
|
||||
"""Initialize database connection and create tables.
|
||||
|
||||
Returns:
|
||||
SQLAlchemy engine instance
|
||||
"""
|
||||
db_path = os.path.join(os.getenv("DATA_DIR", "data"), "garmin.db")
|
||||
engine = create_engine(f"sqlite:///{db_path}")
|
||||
Base.metadata.create_all(engine)
|
||||
return engine
|
||||
|
||||
|
||||
def get_session():
|
||||
"""Create a new database session"""
|
||||
"""Create a new database session.
|
||||
|
||||
Returns:
|
||||
SQLAlchemy session instance
|
||||
"""
|
||||
engine = init_db()
|
||||
Session = sessionmaker(bind=engine)
|
||||
return Session()
|
||||
|
||||
|
||||
def sync_database(garmin_client):
|
||||
"""Sync local database with Garmin Connect activities"""
|
||||
from datetime import datetime
|
||||
"""Sync local database with Garmin Connect activities.
|
||||
|
||||
Args:
|
||||
garmin_client: GarminClient instance for API communication
|
||||
"""
|
||||
session = get_session()
|
||||
try:
|
||||
# Fetch activities from Garmin Connect
|
||||
activities = garmin_client.get_activities(0, 1000)
|
||||
|
||||
# Process activities and update database
|
||||
|
||||
if not activities:
|
||||
print("No activities returned from Garmin API")
|
||||
return
|
||||
|
||||
for activity in activities:
|
||||
activity_id = activity["activityId"]
|
||||
start_time = activity["startTimeLocal"]
|
||||
|
||||
# Check if activity exists in database
|
||||
existing = session.query(Activity).filter_by(activity_id=activity_id).first()
|
||||
# Check if activity is a dictionary and has required fields
|
||||
if not isinstance(activity, dict):
|
||||
print(f"Invalid activity data: {activity}")
|
||||
continue
|
||||
|
||||
# Safely access dictionary keys
|
||||
activity_id = activity.get("activityId")
|
||||
start_time = activity.get("startTimeLocal")
|
||||
|
||||
if not activity_id or not start_time:
|
||||
print(f"Missing required fields in activity: {activity}")
|
||||
continue
|
||||
|
||||
existing = (
|
||||
session.query(Activity).filter_by(activity_id=activity_id).first()
|
||||
)
|
||||
if not existing:
|
||||
new_activity = Activity(
|
||||
activity_id=activity_id,
|
||||
start_time=start_time,
|
||||
downloaded=False,
|
||||
created_at=datetime.now().isoformat(), # Add this line
|
||||
last_sync=datetime.now().isoformat()
|
||||
created_at=datetime.now().isoformat(),
|
||||
last_sync=datetime.now().isoformat(),
|
||||
)
|
||||
session.add(new_activity)
|
||||
|
||||
|
||||
session.commit()
|
||||
except SQLAlchemyError as e:
|
||||
session.rollback()
|
||||
raise e
|
||||
finally:
|
||||
session.close()
|
||||
|
||||
|
||||
|
||||
def get_offline_stats():
|
||||
"""Return statistics about cached data without API calls"""
|
||||
"""Return statistics about cached data without API calls.
|
||||
|
||||
Returns:
|
||||
Dictionary with activity statistics
|
||||
"""
|
||||
session = get_session()
|
||||
try:
|
||||
total = session.query(Activity).count()
|
||||
downloaded = session.query(Activity).filter_by(downloaded=True).count()
|
||||
missing = total - downloaded
|
||||
# Get most recent sync timestamp
|
||||
last_sync = session.query(Activity).order_by(Activity.last_sync.desc()).first()
|
||||
return {
|
||||
'total': total,
|
||||
'downloaded': downloaded,
|
||||
'missing': missing,
|
||||
'last_sync': last_sync.last_sync if last_sync else 'Never synced'
|
||||
"total": total,
|
||||
"downloaded": downloaded,
|
||||
"missing": missing,
|
||||
"last_sync": last_sync.last_sync if last_sync else "Never synced",
|
||||
}
|
||||
finally:
|
||||
session.close()
|
||||
|
||||
# Example usage:
|
||||
# from .garmin import GarminClient
|
||||
# client = GarminClient()
|
||||
# sync_database(client)
|
||||
|
||||
Reference in New Issue
Block a user