150 lines
3.9 KiB
Go
150 lines
3.9 KiB
Go
package monitor
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"time"
|
|
|
|
"zwift-activity-loader/internal/client"
|
|
"zwift-activity-loader/internal/state"
|
|
"zwift-activity-loader/internal/storage"
|
|
|
|
"github.com/bzimmer/activity/zwift"
|
|
"go.uber.org/zap"
|
|
)
|
|
|
|
// Monitor handles activity monitoring and downloading
|
|
type Monitor struct {
|
|
client *client.Client
|
|
state *state.State
|
|
storage *storage.Storage
|
|
logger *zap.Logger
|
|
athleteID int64 // Cached athlete ID
|
|
}
|
|
|
|
// New creates a new Monitor instance
|
|
func New(client *client.Client, state *state.State, storage *storage.Storage, logger *zap.Logger) *Monitor {
|
|
return &Monitor{
|
|
client: client,
|
|
state: state,
|
|
storage: storage,
|
|
logger: logger,
|
|
}
|
|
}
|
|
|
|
// CheckAndDownload checks for new activities and downloads them
|
|
func (m *Monitor) CheckAndDownload(ctx context.Context) error {
|
|
m.logger.Info("Checking for new activities...")
|
|
|
|
// Get athlete ID (cached after first call)
|
|
if m.athleteID == 0 {
|
|
athleteID, err := m.client.GetProfile(ctx)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to get profile: %w", err)
|
|
}
|
|
m.athleteID = athleteID
|
|
m.logger.Debug("Got athlete profile", zap.Int64("athlete_id", athleteID))
|
|
}
|
|
|
|
// List recent activities (last 100)
|
|
activities, err := m.client.ListRecentActivities(ctx, m.athleteID, 100)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to list activities: %w", err)
|
|
}
|
|
|
|
m.logger.Debug("Listed recent activities", zap.Int("count", len(activities)))
|
|
|
|
// Filter new activities
|
|
newActivities := m.filterNewActivities(activities)
|
|
if len(newActivities) == 0 {
|
|
m.logger.Info("No new activities found")
|
|
if err := m.state.UpdateLastCheck(); err != nil {
|
|
m.logger.Warn("Failed to update last check time", zap.Error(err))
|
|
}
|
|
return nil
|
|
}
|
|
|
|
m.logger.Info("Found new activities", zap.Int("count", len(newActivities)))
|
|
|
|
// Download new activities
|
|
downloaded := 0
|
|
for _, act := range newActivities {
|
|
if err := m.downloadActivity(ctx, act); err != nil {
|
|
m.logger.Error("Failed to download activity",
|
|
zap.Int64("activity_id", act.ID),
|
|
zap.String("name", act.Name),
|
|
zap.Error(err))
|
|
continue
|
|
}
|
|
downloaded++
|
|
}
|
|
|
|
m.logger.Info("Download complete",
|
|
zap.Int("downloaded", downloaded),
|
|
zap.Int("total_new", len(newActivities)))
|
|
|
|
return nil
|
|
}
|
|
|
|
// filterNewActivities filters activities that haven't been downloaded yet
|
|
func (m *Monitor) filterNewActivities(activities []*zwift.Activity) []*zwift.Activity {
|
|
var newActivities []*zwift.Activity
|
|
|
|
for _, act := range activities {
|
|
if !m.state.IsDownloaded(act.ID) {
|
|
newActivities = append(newActivities, act)
|
|
}
|
|
}
|
|
|
|
return newActivities
|
|
}
|
|
|
|
// downloadActivity downloads a single activity
|
|
func (m *Monitor) downloadActivity(ctx context.Context, act *zwift.Activity) error {
|
|
m.logger.Info("Downloading activity",
|
|
zap.Int64("activity_id", act.ID),
|
|
zap.String("name", act.Name),
|
|
zap.Time("date", act.StartDate.Time))
|
|
|
|
// Download FIT file
|
|
data, filename, err := m.client.DownloadFIT(ctx, m.athleteID, act.ID)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to download FIT file: %w", err)
|
|
}
|
|
|
|
m.logger.Debug("Downloaded FIT file",
|
|
zap.Int64("activity_id", act.ID),
|
|
zap.String("filename", filename),
|
|
zap.Int("size_bytes", len(data)))
|
|
|
|
// Save to storage
|
|
filepath, err := m.storage.Save(act.ID, act.Name, act.StartDate.Time, data)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to save FIT file: %w", err)
|
|
}
|
|
|
|
// Mark as downloaded in state
|
|
info := state.ActivityInfo{
|
|
ID: act.ID,
|
|
Name: act.Name,
|
|
Date: act.StartDate.Time,
|
|
FilePath: filepath,
|
|
DownloadedAt: time.Now(),
|
|
SportType: act.Sport,
|
|
}
|
|
|
|
if err := m.state.MarkDownloaded(info); err != nil {
|
|
m.logger.Error("Failed to mark activity as downloaded",
|
|
zap.Int64("activity_id", act.ID),
|
|
zap.Error(err))
|
|
// Don't return error here - file is saved, just state update failed
|
|
}
|
|
|
|
m.logger.Info("Successfully downloaded activity",
|
|
zap.Int64("activity_id", act.ID),
|
|
zap.String("name", act.Name),
|
|
zap.String("filepath", filepath))
|
|
|
|
return nil
|
|
}
|