feat: Implement remaining tasks in Phase 1B and 1C

This commit is contained in:
2025-09-18 16:10:08 -07:00
parent 7ced3c908d
commit 47a179d215
5 changed files with 84 additions and 20 deletions

View File

@@ -11,7 +11,7 @@ import (
"sync/atomic"
"time"
"github.com/olekukonko/tablewriter"
"github.com/schollz/progressbar/v3"
"github.com/spf13/cobra"
"github.com/spf13/viper"
@@ -270,7 +270,20 @@ func runDownloadActivity(cmd *cobra.Command, args []string) error {
}
fmt.Printf("Starting download of %d activities...\n", len(activitiesToDownload))
var downloadedCount int64
bar := progressbar.NewOptions(len(activitiesToDownload),
progressbar.OptionEnableColorCodes(true),
progressbar.OptionShowBytes(false),
progressbar.OptionSetWidth(15),
progressbar.OptionSetDescription("Downloading activities..."),
progressbar.OptionSetTheme(progressbar.Theme{
Saucer: "[green]=[reset]",
SaucerPadding: " ",
BarStart: "[ ",
BarEnd: " ]",
}),
)
for _, activity := range activitiesToDownload {
wg.Add(1)
sem <- struct{}{}
@@ -282,6 +295,7 @@ func runDownloadActivity(cmd *cobra.Command, args []string) error {
activityDetail, err := garminClient.GetActivity(activity.ActivityID)
if err != nil {
fmt.Printf("Warning: Failed to get activity details for CSV export for activity %d: %v\n", activity.ActivityID, err)
bar.Add(1)
return
}
@@ -294,6 +308,7 @@ func runDownloadActivity(cmd *cobra.Command, args []string) error {
file, err := os.Create(outputPath)
if err != nil {
fmt.Printf("Warning: Failed to create CSV file for activity %d: %v\n", activity.ActivityID, err)
bar.Add(1)
return
}
defer file.Close()
@@ -323,21 +338,35 @@ func runDownloadActivity(cmd *cobra.Command, args []string) error {
Original: downloadOriginal,
}
fmt.Printf("Downloading activity %d in %s format to %s...\n", activity.ActivityID, downloadFormat, outputDir)
if err := garminClient.DownloadActivity(activity.ActivityID, opts); err != nil {
fmt.Printf("Warning: Failed to download activity %d: %v\n", activity.ActivityID, err)
return
}
outputPath = filepath.Join(outputDir, filename)
}
// Check if file already exists
if _, err := os.Stat(outputPath); err == nil {
fmt.Printf("Skipping activity %d: file already exists at %s\n", activity.ActivityID, outputPath)
bar.Add(1)
return
} else if !os.IsNotExist(err) {
fmt.Printf("Warning: Failed to check existence of file %s for activity %d: %v\n", outputPath, activity.ActivityID, err)
bar.Add(1)
return
}
fmt.Printf("Downloading activity %d in %s format to %s...\n", activity.ActivityID, downloadFormat, outputDir)
if err := garminClient.DownloadActivity(activity.ActivityID, opts); err != nil {
fmt.Printf("Warning: Failed to download activity %d: %v\n", activity.ActivityID, err)
bar.Add(1)
return
}
fmt.Printf("Activity %d downloaded successfully.\n", activity.ActivityID) }
fmt.Printf("Activity %d downloaded successfully.\n", activity.ActivityID)
}
atomic.AddInt64(&downloadedCount, 1)
fmt.Printf("[%d/%d] Downloaded activity %d.\n", downloadedCount, len(activitiesToDownload), activity.ActivityID)
bar.Add(1)
}(activity)
}
wg.Wait()
bar.Finish()
fmt.Println("All downloads finished.")
return nil

7
go.mod
View File

@@ -14,14 +14,16 @@ require (
github.com/go-viper/mapstructure/v2 v2.4.0 // indirect
github.com/inconshreveable/mousetrap v1.1.0 // indirect
github.com/mattn/go-colorable v0.1.13 // indirect
github.com/mattn/go-isatty v0.0.19 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
github.com/mattn/go-runewidth v0.0.16 // indirect
github.com/mitchellh/colorstring v0.0.0-20190213212951-d06e56a500db // indirect
github.com/olekukonko/errors v1.1.0 // indirect
github.com/olekukonko/ll v0.0.9 // indirect
github.com/olekukonko/tablewriter v1.0.9 // indirect
github.com/pelletier/go-toml/v2 v2.2.4 // indirect
github.com/rivo/uniseg v0.2.0 // indirect
github.com/rivo/uniseg v0.4.7 // indirect
github.com/sagikazarmark/locafero v0.11.0 // indirect
github.com/schollz/progressbar/v3 v3.18.0 // indirect
github.com/sourcegraph/conc v0.3.1-0.20240121214520-5f936abd7ae8 // indirect
github.com/spf13/afero v1.15.0 // indirect
github.com/spf13/cast v1.10.0 // indirect
@@ -29,6 +31,7 @@ require (
github.com/subosito/gotenv v1.6.0 // indirect
go.yaml.in/yaml/v3 v3.0.4 // indirect
golang.org/x/sys v0.29.0 // indirect
golang.org/x/term v0.28.0 // indirect
golang.org/x/text v0.28.0 // indirect
)

9
go.sum
View File

@@ -24,8 +24,11 @@ github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovk
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
github.com/mattn/go-isatty v0.0.19 h1:JITubQf0MOLdlGRuRq+jtsDlekdYPia9ZFsB8h/APPA=
github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc=
github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
github.com/mitchellh/colorstring v0.0.0-20190213212951-d06e56a500db h1:62I3jR2EmQ4l5rM/4FEfDWcRD+abF5XlKShorW5LRoQ=
github.com/mitchellh/colorstring v0.0.0-20190213212951-d06e56a500db/go.mod h1:l0dey0ia/Uv7NcFFVbCLtqEBQbrT4OCwCSKTEv6enCw=
github.com/olekukonko/errors v1.1.0 h1:RNuGIh15QdDenh+hNvKrJkmxxjV4hcS50Db478Ou5sM=
github.com/olekukonko/errors v1.1.0/go.mod h1:ppzxA5jBKcO1vIpCXQ9ZqgDh8iwODz6OXIGKU8r5m4Y=
github.com/olekukonko/ll v0.0.9 h1:Y+1YqDfVkqMWuEQMclsF9HUR5+a82+dxJuL1HHSRpxI=
@@ -38,11 +41,15 @@ github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZb
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY=
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ=
github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8=
github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs=
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/sagikazarmark/locafero v0.11.0 h1:1iurJgmM9G3PA/I+wWYIOw/5SyBtxapeHDcg+AAIFXc=
github.com/sagikazarmark/locafero v0.11.0/go.mod h1:nVIGvgyzw595SUSUE6tvCp3YYTeHs15MvlmU87WwIik=
github.com/schollz/progressbar/v3 v3.18.0 h1:uXdoHABRFmNIjUfte/Ex7WtuyVslrw2wVPQmCN62HpA=
github.com/schollz/progressbar/v3 v3.18.0/go.mod h1:IsO3lpbaGuzh8zIMzgY3+J8l4C8GjO0Y9S69eFvNsec=
github.com/sourcegraph/conc v0.3.1-0.20240121214520-5f936abd7ae8 h1:+jumHNA0Wrelhe64i8F6HNlS8pkoyMv5sreGx2Ry5Rw=
github.com/sourcegraph/conc v0.3.1-0.20240121214520-5f936abd7ae8/go.mod h1:3n1Cwaq1E1/1lhQhtRK2ts/ZwZEhjcQeJQ1RuC6Q/8U=
github.com/spf13/afero v1.15.0 h1:b/YBCLWAJdFWJTN9cLhiXXcD7mzKn9Dm86dNnfyQw1I=
@@ -66,6 +73,8 @@ golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBc
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.29.0 h1:TPYlXGxvx1MGTn2GiZDhnjPA9wZzZeGKHHmKhHYvgaU=
golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/term v0.28.0 h1:/Ts8HFuMR2E6IP/jlo7QVLZHggjKQbhu/7H0LJFr3Gg=
golang.org/x/term v0.28.0/go.mod h1:Sw/lC2IAUZ92udQNf3WodGtn4k/XoLyZoh8v/8uiwek=
golang.org/x/text v0.28.0 h1:rhazDwis8INMIwQ4tpjLDzUhx6RlXqZNPEM0huQojng=
golang.org/x/text v0.28.0/go.mod h1:U8nCwOR8jO/marOQ0QbDiOngZVEBB7MAiitBuMjXiNU=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=

View File

@@ -293,13 +293,13 @@ func (c *Client) DownloadActivity(id string, opts *DownloadOptions) error {
- [x] Research Garmin's download endpoints
- [x] Implement format detection and conversion
- [x] Add file writing with proper naming
- [ ] Implement progress indication
- [ ] Add download validation
- [x] Implement progress indication
- [x] Add download validation
- [x] Error handling for failed downloads
**Deliverables:**
- [x] Working download for at least GPX format
- [ ] Progress indication during download
- [x] Progress indication during download
- [x] Proper error handling
#### 1C.2: Multi-Format Support
@@ -314,7 +314,7 @@ func (c *Client) DownloadActivity(id string, opts *DownloadOptions) error {
**Deliverables:**
- [x] Support for GPX, TCX, and CSV formats
- [ ] Format auto-detection
- [x] Format auto-detection
- [x] Format-specific download options
#### 1C.3: Batch Download Features
@@ -331,7 +331,7 @@ garth activities download --from 2024-01-01 --to 2024-01-31
- [x] Add parallel download support
- [x] Progress bars for multiple downloads
- [ ] Resume interrupted downloads
- [ ] Duplicate detection and handling
- [x] Duplicate detection and handling
**Deliverables:**
- [x] Batch download working

View File

@@ -100,7 +100,30 @@ func (c *Client) DownloadActivity(activityID int, opts activities.DownloadOption
outputPath = filepath.Join(opts.OutputDir, filename)
}
return c.Client.Download(fmt.Sprintf("%d", activityID), opts.Format, outputPath)
err := c.Client.Download(fmt.Sprintf("%d", activityID), opts.Format, outputPath)
if err != nil {
return err
}
// Basic validation: check if file is empty
fileInfo, err := os.Stat(outputPath)
if err != nil {
return &errors.IOError{
GarthError: errors.GarthError{
Message: "Failed to get file info after download",
Cause: err,
},
}
}
if fileInfo.Size() == 0 {
return &errors.IOError{
GarthError: errors.GarthError{
Message: "Downloaded file is empty",
},
}
}
return nil
}
// SearchActivities searches for activities by a query string