From 32ee176eece6715cae88a26c9318ac64432cbcf8 Mon Sep 17 00:00:00 2001 From: Florian Forster Date: Fri, 12 Jan 2018 10:53:45 +0100 Subject: [PATCH] Package gfit: API fixes. * Add calculation of DataStreamID(). * Don't treat http.StatusConflict as an error when creating DataSources. --- gfit/gfit.go | 118 ++++++++++++++++++++++++++++++++++++++++++----------------- 1 file changed, 85 insertions(+), 33 deletions(-) diff --git a/gfit/gfit.go b/gfit/gfit.go index ed8f7cb..ff4c26d 100644 --- a/gfit/gfit.go +++ b/gfit/gfit.go @@ -4,16 +4,23 @@ import ( "context" "fmt" "net/http" + "strings" "time" "github.com/octo/gfitsync/app" "golang.org/x/oauth2" oauth2google "golang.org/x/oauth2/google" fitness "google.golang.org/api/fitness/v1" + "google.golang.org/api/googleapi" "google.golang.org/appengine" "google.golang.org/appengine/log" ) +const ( + csrfToken = "@CSRFTOKEN@" + userID = "me" +) + var oauthConfig = &oauth2.Config{ ClientID: "@GOOGLE_CLIENT_ID@", ClientSecret: "@GOOGLE_CLIENT_SECRET@", @@ -25,8 +32,6 @@ var oauthConfig = &oauth2.Config{ }, } -const csrfToken = "@CSRFTOKEN@" - func Application(ctx context.Context) *fitness.Application { return &fitness.Application{ Name: "Fitbit to Google Fit sync", @@ -72,11 +77,76 @@ func NewClient(ctx context.Context, u *app.User) (*Client, error) { }, nil } +func DataStreamID(dataSource *fitness.DataSource) string { + fields := []string{ + dataSource.Type, + dataSource.DataType.Name, + "@PROJECT_NUMBER@", // FIXME + } + + if dev := dataSource.Device; dev != nil { + if dev.Manufacturer != "" { + fields = append(fields, dev.Manufacturer) + } + if dev.Model != "" { + fields = append(fields, dev.Model) + } + if dev.Uid != "" { + fields = append(fields, dev.Uid) + } + } + + return strings.Join(fields, ":") +} + +func (c *Client) DataSourceCreate(ctx context.Context, dataSource *fitness.DataSource) (string, error) { + res, err := c.Service.Users.DataSources.Create(userID, dataSource).Context(ctx).Do() + if err != nil { + if gerr, ok := err.(*googleapi.Error); ok && gerr.Code == http.StatusConflict { + if dataSource.DataStreamId != "" { + return dataSource.DataStreamId, nil + } + return DataStreamID(dataSource), nil + } + log.Errorf(ctx, "c.Service.Users.DataSources.Create() = (%+v, %v)", res, err) + return "", err + } + return res.DataStreamId, nil +} + +func (c *Client) DataSetPatch(ctx context.Context, dataSourceID string, points []*fitness.DataPoint) error { + startTimeNanos, endTimeNanos := int64(-1), int64(-1) + for _, p := range points { + if startTimeNanos == -1 || startTimeNanos > p.StartTimeNanos { + startTimeNanos = p.StartTimeNanos + } + if endTimeNanos == -1 || endTimeNanos < p.EndTimeNanos { + endTimeNanos = p.EndTimeNanos + } + } + datasetID := fmt.Sprintf("%d-%d", startTimeNanos, endTimeNanos) + + dataset := &fitness.Dataset{ + DataSourceId: dataSourceID, + MinStartTimeNs: startTimeNanos, + MaxEndTimeNs: endTimeNanos, + Point: points, + } + + _, err := c.Service.Users.DataSources.Datasets.Patch(userID, dataSourceID, datasetID, dataset).Context(ctx).Do() + if err != nil { + log.Errorf(ctx, "c.Service.Users.DataSources.Datasets.Patch() = %v", err) + return err + } + return nil +} + func (c *Client) SetSteps(ctx context.Context, steps int, date time.Time) error { - const userID = "me" const dataTypeName = "com.google.step_count.delta" - dataSource := &fitness.DataSource{ + + dataSourceID, err := c.DataSourceCreate(ctx, &fitness.DataSource{ Application: Application(ctx), + DataStreamId: "", // COMPUTED DataStreamName: "", // "daily summary"? DataType: &fitness.DataType{ Field: []*fitness.DataTypeField{ @@ -89,40 +159,22 @@ func (c *Client) SetSteps(ctx context.Context, steps int, date time.Time) error }, Name: "Step Count", Type: "raw", - } - - dataSource, err := c.Service.Users.DataSources.Create(userID, dataSource).Context(ctx).Do() + }) if err != nil { - log.Errorf(ctx, "c.Service.Users.DataSources.Create() = (%+v, %v)", dataSource, err) return err } - dataSourceID := dataSource.DataStreamId - startTimeNanos := date.UnixNano() - endTimeNanos := date.Add(86399999999999 * time.Nanosecond).UnixNano() - datasetID := fmt.Sprintf("%d-%d", startTimeNanos, endTimeNanos) - dataset := &fitness.Dataset{ - MinStartTimeNs: startTimeNanos, - MaxEndTimeNs: endTimeNanos, - Point: []*fitness.DataPoint{ - &fitness.DataPoint{ - ComputationTimeMillis: time.Now().UnixNano() / 1000000, - DataTypeName: dataTypeName, - StartTimeNanos: startTimeNanos, - EndTimeNanos: endTimeNanos, - Value: []*fitness.Value{ - &fitness.Value{ - IntVal: int64(steps), - }, + return c.DataSetPatch(ctx, dataSourceID, []*fitness.DataPoint{ + &fitness.DataPoint{ + ComputationTimeMillis: time.Now().UnixNano() / 1000000, + DataTypeName: dataTypeName, + StartTimeNanos: date.UnixNano(), + EndTimeNanos: date.Add(24 * time.Hour).Add(-1 * time.Nanosecond).UnixNano(), + Value: []*fitness.Value{ + &fitness.Value{ + IntVal: int64(steps), }, }, }, - } - - dataset, err = c.Service.Users.DataSources.Datasets.Patch(userID, dataSourceID, datasetID, dataset).Context(ctx).Do() - if err != nil { - log.Errorf(ctx, "c.Service.Users.DataSources.Datasets.Patch() = (%+v, %v)", dataset, err) - return err - } - return nil + }) } -- 2.11.0