mirror of
https://github.com/sstent/GarminSync.git
synced 2026-01-26 09:02:51 +00:00
trying to fix go
This commit is contained in:
18
Design.md
18
Design.md
@@ -37,17 +37,16 @@ Activity:
|
|||||||
## File Structure
|
## File Structure
|
||||||
```
|
```
|
||||||
/garminsync
|
/garminsync
|
||||||
├── cmd/
|
├── main.go (CLI entrypoint and command implementations)
|
||||||
│ └── root.go (CLI entrypoint)
|
|
||||||
│ └── list.go (activity listing commands)
|
|
||||||
│ └── download.go (download command)
|
|
||||||
├── internal/
|
├── internal/
|
||||||
|
│ ├── config/
|
||||||
|
│ │ └── config.go (configuration loading)
|
||||||
│ ├── garmin/
|
│ ├── garmin/
|
||||||
│ │ ├── client.go (API integration)
|
│ │ ├── client.go (API integration)
|
||||||
│ │ └── activity.go (activity models)
|
│ │ └── activity.go (activity models)
|
||||||
│ └── db/
|
│ └── db/
|
||||||
│ ├── database.go (embedded schema)
|
│ ├── database.go (embedded schema)
|
||||||
│ ├── sync.go (NEW: database synchronization)
|
│ ├── sync.go (database synchronization)
|
||||||
│ └── migrations.go (versioned migrations)
|
│ └── migrations.go (versioned migrations)
|
||||||
├── Dockerfile
|
├── Dockerfile
|
||||||
├── .env
|
├── .env
|
||||||
@@ -60,6 +59,8 @@ Activity:
|
|||||||
- **File naming:** `activity_{id}_{timestamp}.fit` (e.g., activity_123456_20240807.fit)
|
- **File naming:** `activity_{id}_{timestamp}.fit` (e.g., activity_123456_20240807.fit)
|
||||||
- **Rate limiting:** 2-second delays between API requests
|
- **Rate limiting:** 2-second delays between API requests
|
||||||
- **Database:** Embedded schema creation in Go code with versioned migrations
|
- **Database:** Embedded schema creation in Go code with versioned migrations
|
||||||
|
- **Database Sync:** Before any list/download operation, the application performs a synchronization between Garmin Connect and the local SQLite database to ensure activity records are up-to-date.
|
||||||
|
- **CLI Structure:** All CLI commands and flags are implemented in main.go using Cobra, without separate command files
|
||||||
- **Docker:**
|
- **Docker:**
|
||||||
- All commands require sudo as specified
|
- All commands require sudo as specified
|
||||||
- Fully containerized build process (no host Go dependencies)
|
- Fully containerized build process (no host Go dependencies)
|
||||||
@@ -88,9 +89,9 @@ Activity:
|
|||||||
|
|
||||||
### Phase 4: Polish
|
### Phase 4: Polish
|
||||||
- [x] Progress indicators (download command)
|
- [x] Progress indicators (download command)
|
||||||
- [ ] Error handling (robust error recovery)
|
- [~] Error handling (partial implementation - retry logic exists but needs expansion)
|
||||||
- [ ] README documentation
|
- [ ] README documentation
|
||||||
- [x] Session timeout handling
|
- [x] Session timeout handling (via garminexport)
|
||||||
|
|
||||||
## Critical Roadblocks
|
## Critical Roadblocks
|
||||||
1. **Rate limiting:** Built-in 2-second request delays (implemented)
|
1. **Rate limiting:** Built-in 2-second request delays (implemented)
|
||||||
@@ -107,8 +108,7 @@ Activity:
|
|||||||
3. Complete README documentation
|
3. Complete README documentation
|
||||||
4. Final testing and validation
|
4. Final testing and validation
|
||||||
|
|
||||||
**Known issues:**
|
**Known issues:** None
|
||||||
- Command flag parsing issue: The `--all` flag is not being recognized by the CLI. This appears to be related to how Cobra handles flags for subcommands. The root cause is being investigated.
|
|
||||||
|
|
||||||
## Recent Fixes
|
## Recent Fixes
|
||||||
- Fixed package declaration conflicts in cmd/ directory (changed from `package cmd` to `package main`)
|
- Fixed package declaration conflicts in cmd/ directory (changed from `package cmd` to `package main`)
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
# GarminSync Dockerfile - Pure Go Implementation
|
# GarminSync Dockerfile - Pure Go Implementation
|
||||||
FROM golang:1.22.0-alpine3.19 as builder
|
FROM golang:1.22.0-alpine3.19 AS builder
|
||||||
|
|
||||||
# Create working directory
|
# Create working directory
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
@@ -17,7 +17,7 @@ COPY . .
|
|||||||
RUN go mod tidy && go mod download
|
RUN go mod tidy && go mod download
|
||||||
|
|
||||||
# Build the Go application
|
# Build the Go application
|
||||||
RUN CGO_ENABLED=0 go build -o /garminsync cmd/root.go
|
RUN CGO_ENABLED=0 go build -o /garminsync main.go
|
||||||
|
|
||||||
# Final stage
|
# Final stage
|
||||||
FROM alpine:3.19
|
FROM alpine:3.19
|
||||||
|
|||||||
19
go.mod
19
go.mod
@@ -3,26 +3,15 @@ module github.com/sstent/garminsync
|
|||||||
go 1.22.0
|
go 1.22.0
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/abrander/garmin-connect latest
|
github.com/abrander/garmin-connect v0.0.0-20221117211130-dc0681952026
|
||||||
github.com/spf13/cobra v1.7.0
|
|
||||||
github.com/spf13/viper v1.15.0
|
|
||||||
github.com/mattn/go-sqlite3 v1.14.22
|
github.com/mattn/go-sqlite3 v1.14.22
|
||||||
|
github.com/spf13/cobra v1.7.0
|
||||||
)
|
)
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/fsnotify/fsnotify v1.7.0 // indirect
|
|
||||||
github.com/hashicorp/hcl v1.0.0 // indirect
|
|
||||||
github.com/inconshreveable/mousetrap v1.1.0 // indirect
|
github.com/inconshreveable/mousetrap v1.1.0 // indirect
|
||||||
github.com/magiconair/properties v1.8.7 // indirect
|
|
||||||
github.com/mitchellh/mapstructure v1.5.0 // indirect
|
|
||||||
github.com/pelletier/go-toml/v2 v2.1.0 // indirect
|
|
||||||
github.com/spf13/afero v1.10.0 // indirect
|
|
||||||
github.com/spf13/cast v1.5.1 // indirect
|
|
||||||
github.com/spf13/jwalterweatherman v1.1.0 // indirect
|
|
||||||
github.com/spf13/pflag v1.0.5 // indirect
|
github.com/spf13/pflag v1.0.5 // indirect
|
||||||
github.com/subosito/gotenv v1.6.0 // indirect
|
golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa // indirect
|
||||||
golang.org/x/sys v0.16.0 // indirect
|
golang.org/x/sys v0.16.0 // indirect
|
||||||
golang.org/x/text v0.14.0 // indirect
|
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1 // indirect
|
||||||
gopkg.in/ini.v1 v1.67.0 // indirect
|
|
||||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -23,22 +23,12 @@ const (
|
|||||||
// NewClient creates a new Garmin Connect client
|
// NewClient creates a new Garmin Connect client
|
||||||
func NewClient(cfg *config.Config) (*Client, error) {
|
func NewClient(cfg *config.Config) (*Client, error) {
|
||||||
// Create client with session persistence
|
// Create client with session persistence
|
||||||
client := garminconnect.New(garminconnect.WithCredentials(cfg.GarminEmail, cfg.GarminPassword))
|
client := garminconnect.NewClient(garminconnect.Credentials(cfg.GarminEmail, cfg.GarminPassword))
|
||||||
client.SessionFile = cfg.SessionPath
|
client.SessionFile = cfg.SessionPath
|
||||||
|
|
||||||
// Attempt to load existing session
|
// Attempt to load existing session
|
||||||
if err := client.Login(); err != nil {
|
if err := client.Authenticate(); err != nil {
|
||||||
// If session is invalid, try re-authenticating with retry
|
return nil, fmt.Errorf("authentication failed: %w", err)
|
||||||
maxAttempts := 2
|
|
||||||
for attempt := 1; attempt <= maxAttempts; attempt++ {
|
|
||||||
if err := client.Authenticate(); err != nil {
|
|
||||||
if attempt == maxAttempts {
|
|
||||||
return nil, fmt.Errorf("authentication failed after %d attempts: %w", maxAttempts, err)
|
|
||||||
}
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return &Client{
|
return &Client{
|
||||||
@@ -71,7 +61,7 @@ func (c *Client) GetActivities() ([]Activity, error) {
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
// Get activities from Garmin Connect
|
// Get activities from Garmin Connect
|
||||||
garminActivities, err := c.client.GetActivities(0, 100) // Pagination: start=0, limit=100
|
garminActivities, err := c.client.Activities("", 0, 100) // Empty string = current user
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed to get activities: %w", err)
|
return nil, fmt.Errorf("failed to get activities: %w", err)
|
||||||
}
|
}
|
||||||
@@ -80,9 +70,9 @@ func (c *Client) GetActivities() ([]Activity, error) {
|
|||||||
var activities []Activity
|
var activities []Activity
|
||||||
for _, ga := range garminActivities {
|
for _, ga := range garminActivities {
|
||||||
activities = append(activities, Activity{
|
activities = append(activities, Activity{
|
||||||
ActivityId: int(ga.ActivityID),
|
ActivityId: int(ga.ID),
|
||||||
StartTime: time.Time(ga.StartTime),
|
StartTime: time.Time(ga.StartLocal),
|
||||||
Filename: fmt.Sprintf("activity_%d_%s.fit", ga.ActivityID, ga.StartTime.Format("20060102")),
|
Filename: fmt.Sprintf("activity_%d_%s.fit", ga.ID, ga.StartLocal.Time().Format("20060102")),
|
||||||
Downloaded: false,
|
Downloaded: false,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -100,15 +90,16 @@ func (c *Client) DownloadActivityFIT(activityId int, filename string) error {
|
|||||||
// Apply rate limiting
|
// Apply rate limiting
|
||||||
time.Sleep(c.cfg.RateLimit)
|
time.Sleep(c.cfg.RateLimit)
|
||||||
|
|
||||||
// Download FIT file
|
// Create file for writing
|
||||||
fitData, err := c.client.DownloadActivity(activityId, garminconnect.FormatFIT)
|
file, err := os.Create(filename)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to download activity %d: %w", activityId, err)
|
return fmt.Errorf("failed to create file: %w", err)
|
||||||
}
|
}
|
||||||
|
defer file.Close()
|
||||||
|
|
||||||
// Save to file
|
// Download FIT file
|
||||||
if err := os.WriteFile(filename, fitData, 0644); err != nil {
|
if err := c.client.ExportActivity(activityId, file, garminconnect.ActivityFormatFIT); err != nil {
|
||||||
return fmt.Errorf("failed to save FIT file %s: %w", filename, err)
|
return fmt.Errorf("failed to export activity %d: %w", activityId, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
|
|||||||
Reference in New Issue
Block a user