mirror of
https://github.com/sstent/go-garth.git
synced 2025-12-06 08:01:42 +00:00
feat: Implement remaining tasks in Phase 1B and 1C
This commit is contained in:
@@ -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
7
go.mod
@@ -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
9
go.sum
@@ -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=
|
||||
|
||||
10
phase1.md
10
phase1.md
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user