X-Git-Url: https://git.octo.it/?a=blobdiff_plain;f=gfitsync.go;h=eb3c953d2b405272a7f4f2a89609ce85254caf86;hb=19a78ce4b915845cf1232c84f17074982f27a4ce;hp=25df282ed03fac72a919ef8b170a1ad7283b491d;hpb=26c56d08d97616b40d7d38d23d02b5d2ccd28e27;p=kraftakt.git diff --git a/gfitsync.go b/gfitsync.go index 25df282..eb3c953 100644 --- a/gfitsync.go +++ b/gfitsync.go @@ -6,10 +6,12 @@ import ( "fmt" "io/ioutil" "net/http" + "sync" "time" "github.com/octo/gfitsync/app" "github.com/octo/gfitsync/fitbit" + "github.com/octo/gfitsync/gfit" "google.golang.org/appengine" "google.golang.org/appengine/datastore" "google.golang.org/appengine/delay" @@ -20,9 +22,11 @@ import ( var delayedHandleNotifications = delay.Func("handleNotifications", handleNotifications) func init() { - http.HandleFunc("/setup", setupHandler) + http.HandleFunc("/fitbit/setup", fitbitSetupHandler) http.Handle("/fitbit/grant", AuthenticatedHandler(fitbitGrantHandler)) http.Handle("/fitbit/notify", ContextHandler(fitbitNotifyHandler)) + http.HandleFunc("/google/setup", googleSetupHandler) + http.Handle("/google/grant", AuthenticatedHandler(googleGrantHandler)) http.Handle("/", AuthenticatedHandler(indexHandler)) } @@ -71,23 +75,45 @@ func indexHandler(ctx context.Context, w http.ResponseWriter, r *http.Request, u if err != nil && err != datastore.ErrNoSuchEntity { return err } - haveToken := err == nil + haveFitbitToken := err == nil + + _, err = u.Token(ctx, "Google") + if err != nil && err != datastore.ErrNoSuchEntity { + return err + } + haveGoogleToken := err == nil + + fmt.Fprintln(w, "Kraftakt") + fmt.Fprintln(w, "

Kraftakt

") + + fmt.Fprintln(w, "

Kraftakt copies your Fitbit data to Google Fit, seconds after you sync.

") - fmt.Fprintln(w, "

Fitbit to Google Fit sync

") fmt.Fprintf(w, "

Hello %s

\n", user.Current(ctx).Email) - fmt.Fprint(w, "

Fitbit: ") - if haveToken { + fmt.Fprintln(w, "

") fmt.Fprintln(w, "") return nil } -func setupHandler(w http.ResponseWriter, r *http.Request) { +func fitbitSetupHandler(w http.ResponseWriter, r *http.Request) { http.Redirect(w, r, fitbit.AuthURL(), http.StatusTemporaryRedirect) } @@ -112,6 +138,23 @@ func fitbitGrantHandler(ctx context.Context, w http.ResponseWriter, r *http.Requ return nil } +func googleSetupHandler(w http.ResponseWriter, r *http.Request) { + http.Redirect(w, r, gfit.AuthURL(), http.StatusTemporaryRedirect) +} + +func googleGrantHandler(ctx context.Context, w http.ResponseWriter, r *http.Request, u *app.User) error { + if err := gfit.ParseToken(ctx, r, u); err != nil { + return err + } + + redirectURL := r.URL + redirectURL.Path = "/" + redirectURL.RawQuery = "" + redirectURL.Fragment = "" + http.Redirect(w, r, redirectURL.String(), http.StatusTemporaryRedirect) + return nil +} + // fitbitNotifyHandler is called by Fitbit whenever there are updates to a // subscription. It verifies the payload, splits it into individual // notifications and adds it to the taskqueue service. @@ -180,21 +223,130 @@ func handleNotification(ctx context.Context, s *fitbit.Subscription) error { if err != nil { return err } - c, err := fitbit.NewClient(ctx, s.OwnerID, u) + + fitbitClient, err := fitbit.NewClient(ctx, s.OwnerID, u) if err != nil { return err } - tm, err := time.Parse("2006-01-02", s.Date) + var ( + wg = &sync.WaitGroup{} + errs appengine.MultiError + summary *fitbit.ActivitySummary + profile *fitbit.Profile + ) + + wg.Add(1) + go func() { + var err error + summary, err = fitbitClient.ActivitySummary(ctx, s.Date) + if err != nil { + errs = append(errs, fmt.Errorf("fitbitClient.ActivitySummary(%q) = %v", s.Date, err)) + } + wg.Done() + }() + + wg.Add(1) + go func() { + var err error + profile, err = fitbitClient.Profile(ctx) + if err != nil { + errs = append(errs, fmt.Errorf("fitbitClient.Profile(%q) = %v", s.Date, err)) + } + wg.Done() + }() + + wg.Wait() + if len(errs) != 0 { + return errs + } + + tm, err := time.ParseInLocation("2006-01-02", s.Date, profile.Timezone) if err != nil { return err } - summary, err := c.ActivitySummary(tm) + log.Debugf(ctx, "%s (%s) took %d steps on %s", + profile.Name, u.Email, summary.Summary.Steps, tm) + + gfitClient, err := gfit.NewClient(ctx, u) if err != nil { return err } - log.Debugf(ctx, "ActivitySummary for %s = %+v", u.Email, summary) + wg.Add(1) + go func() { + if err := gfitClient.SetSteps(ctx, summary.Summary.Steps, tm); err != nil { + errs = append(errs, fmt.Errorf("gfitClient.SetSteps(%d) = %v", summary.Summary.Steps, err)) + } + wg.Done() + }() + + wg.Add(1) + go func() { + if err := gfitClient.SetCalories(ctx, summary.Summary.CaloriesOut, tm); err != nil { + errs = append(errs, fmt.Errorf("gfitClient.SetCalories(%d) = %v", summary.Summary.CaloriesOut, err)) + } + wg.Done() + }() + + wg.Add(1) + go func() { + var distanceMeters float64 + for _, d := range summary.Summary.Distances { + if d.Activity != "total" { + continue + } + distanceMeters = 1000.0 * d.Distance + break + } + if err := gfitClient.SetDistance(ctx, distanceMeters, tm); err != nil { + errs = append(errs, fmt.Errorf("gfitClient.SetDistance(%d) = %v", distanceMeters, err)) + } + wg.Done() + }() + + wg.Add(1) + go func() { + if err := gfitClient.SetHeartRate(ctx, summary.Summary.HeartRateZones, summary.Summary.RestingHeartRate, tm); err != nil { + errs = append(errs, fmt.Errorf("gfitClient.SetHeartRate() = %v", err)) + } + wg.Done() + }() + + wg.Add(1) + go func() { + defer wg.Done() + + var activities []gfit.Activity + for _, a := range summary.Activities { + if !a.HasStartTime { + continue + } + + startTime, err := time.ParseInLocation("2006-01-02T15:04:05", a.StartDate+"T"+a.StartTime, profile.Timezone) + if err != nil { + errs = append(errs, fmt.Errorf("gfitClient.SetActivities() = %v", err)) + return + } + endTime := startTime.Add(time.Duration(a.Duration) * time.Millisecond) + + activities = append(activities, gfit.Activity{ + Start: startTime, + End: endTime, + Type: gfit.ParseFitbitActivity(a.Name), + }) + } + if err := gfitClient.SetActivities(ctx, activities, tm); err != nil { + errs = append(errs, fmt.Errorf("gfitClient.SetActivities() = %v", err)) + return + } + }() + + wg.Wait() + + if len(errs) != 0 { + return errs + } return nil }