mirror of
https://github.com/sstent/go-garth.git
synced 2026-01-25 16:42:28 +00:00
This commit implements the package structure refactoring as outlined in phase1.md (Task 1A.1). Key changes include: - Reorganized packages into `pkg/garmin` for public API and `internal/` for internal implementations. - Updated all import paths to reflect the new structure. - Consolidated types and client logic into their respective new packages. - Updated `cmd/garth/main.go` to use the new public API. - Fixed various compilation and test issues encountered during the refactoring process. - Converted `internal/api/client/auth_test.go` to a functional test. This establishes a solid foundation for future enhancements and improves maintainability.
131 lines
3.4 KiB
Go
131 lines
3.4 KiB
Go
package data
|
|
|
|
import (
|
|
"errors"
|
|
"sync"
|
|
"time"
|
|
|
|
"garmin-connect/internal/api/client"
|
|
"garmin-connect/internal/utils"
|
|
)
|
|
|
|
// Data defines the interface for Garmin Connect data types.
|
|
// Concrete data types (BodyBattery, HRV, Sleep, etc.) must implement this interface.
|
|
//
|
|
// The Get method retrieves data for a single day.
|
|
// The List method concurrently retrieves data for a range of days.
|
|
type Data interface {
|
|
Get(day time.Time, c *client.Client) (interface{}, error)
|
|
List(end time.Time, days int, c *client.Client, maxWorkers int) ([]interface{}, error)
|
|
}
|
|
|
|
// BaseData provides a reusable implementation for data types to embed.
|
|
// It handles the concurrent List() implementation while allowing concrete types
|
|
// to focus on implementing the Get() method for their specific data structure.
|
|
//
|
|
// Usage:
|
|
//
|
|
// type BodyBatteryData struct {
|
|
// data.BaseData
|
|
// // ... additional fields
|
|
// }
|
|
//
|
|
// func NewBodyBatteryData() *BodyBatteryData {
|
|
// bb := &BodyBatteryData{}
|
|
// bb.GetFunc = bb.get // Assign the concrete Get implementation
|
|
// return bb
|
|
// }
|
|
//
|
|
// func (bb *BodyBatteryData) get(day time.Time, c *client.Client) (interface{}, error) {
|
|
// // Implementation specific to body battery data
|
|
// }
|
|
type BaseData struct {
|
|
// GetFunc must be set by concrete types to implement the Get method.
|
|
// This function pointer allows BaseData to call the concrete implementation.
|
|
GetFunc func(day time.Time, c *client.Client) (interface{}, error)
|
|
}
|
|
|
|
// Get implements the Data interface by calling the configured GetFunc.
|
|
// Returns an error if GetFunc is not set.
|
|
func (b *BaseData) Get(day time.Time, c *client.Client) (interface{}, error) {
|
|
if b.GetFunc == nil {
|
|
return nil, errors.New("GetFunc not implemented for this data type")
|
|
}
|
|
return b.GetFunc(day, c)
|
|
}
|
|
|
|
// List implements concurrent data fetching using a worker pool pattern.
|
|
// This method efficiently retrieves data for multiple days by distributing
|
|
// work across a configurable number of workers (goroutines).
|
|
//
|
|
// Parameters:
|
|
//
|
|
// end: The end date of the range (inclusive)
|
|
// days: Number of days to fetch (going backwards from end date)
|
|
// c: Client instance for API access
|
|
// maxWorkers: Maximum concurrent workers (minimum 1)
|
|
//
|
|
// Returns:
|
|
//
|
|
// []interface{}: Slice of results (order matches date range)
|
|
// []error: Slice of errors encountered during processing
|
|
func (b *BaseData) List(end time.Time, days int, c *client.Client, maxWorkers int) ([]interface{}, []error) {
|
|
if maxWorkers < 1 {
|
|
maxWorkers = 10 // Match Python's MAX_WORKERS
|
|
}
|
|
|
|
dates := utils.DateRange(end, days)
|
|
|
|
// Define result type for channel
|
|
type result struct {
|
|
data interface{}
|
|
err error
|
|
}
|
|
|
|
var wg sync.WaitGroup
|
|
workCh := make(chan time.Time, days)
|
|
resultsCh := make(chan result, days)
|
|
|
|
// Worker function
|
|
worker := func() {
|
|
defer wg.Done()
|
|
for date := range workCh {
|
|
data, err := b.Get(date, c)
|
|
resultsCh <- result{data: data, err: err}
|
|
}
|
|
}
|
|
|
|
// Start workers
|
|
wg.Add(maxWorkers)
|
|
for i := 0; i < maxWorkers; i++ {
|
|
go worker()
|
|
}
|
|
|
|
// Send work
|
|
go func() {
|
|
for _, date := range dates {
|
|
workCh <- date
|
|
}
|
|
close(workCh)
|
|
}()
|
|
|
|
// Close results channel when workers are done
|
|
go func() {
|
|
wg.Wait()
|
|
close(resultsCh)
|
|
}()
|
|
|
|
var results []interface{}
|
|
var errs []error
|
|
|
|
for r := range resultsCh {
|
|
if r.err != nil {
|
|
errs = append(errs, r.err)
|
|
} else if r.data != nil {
|
|
results = append(results, r.data)
|
|
}
|
|
}
|
|
|
|
return results, errs
|
|
}
|