X-Git-Url: https://git.octo.it/?a=blobdiff_plain;f=gfitsync.go;h=7f28bb07be7422368922eed56747a32bb3d02d3a;hb=6c7b039b319152555c9529d8d5a2c2483cf65887;hp=000739540a6f246e6bc6670bd34e936435bfa241;hpb=000f6f002f24e742f85ca20503a838c198637125;p=kraftakt.git
diff --git a/gfitsync.go b/gfitsync.go
index 0007395..7f28bb0 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))
}
@@ -40,10 +44,6 @@ func (hndl ContextHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
type AuthenticatedHandler func(context.Context, http.ResponseWriter, *http.Request, *app.User) error
-type User struct {
- ID string
-}
-
func (hndl AuthenticatedHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
ctx := appengine.NewContext(r)
@@ -75,25 +75,46 @@ 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.Fprint(w, "- Fitbit: ")
+ if haveFitbitToken {
fmt.Fprint(w, `Authorized`)
} else {
- fmt.Fprint(w, `Not authorized (Authorize)`)
+ fmt.Fprint(w, `Not authorized (Authorize)`)
}
- fmt.Fprintln(w, "")
+ fmt.Fprintln(w, "
")
+
+ fmt.Fprint(w, "- Google Fit: ")
+ if haveGoogleToken {
+ fmt.Fprint(w, `Authorized`)
+ } else {
+ fmt.Fprint(w, `Not authorized (Authorize)`)
+ }
+ fmt.Fprintln(w, "
")
+
+ fmt.Fprintln(w, "
")
fmt.Fprintln(w, "")
return nil
}
-func setupHandler(w http.ResponseWriter, r *http.Request) {
- url := fitbit.AuthURL()
- http.Redirect(w, r, url, http.StatusTemporaryRedirect)
+func fitbitSetupHandler(w http.ResponseWriter, r *http.Request) {
+ http.Redirect(w, r, fitbit.AuthURL(), http.StatusTemporaryRedirect)
}
func fitbitGrantHandler(ctx context.Context, w http.ResponseWriter, r *http.Request, u *app.User) error {
@@ -109,6 +130,27 @@ func fitbitGrantHandler(ctx context.Context, w http.ResponseWriter, r *http.Requ
return fmt.Errorf("c.Subscribe() = %v", err)
}
+ if err := c.Subscribe(ctx, "sleep"); err != nil {
+ return fmt.Errorf("c.Subscribe() = %v", err)
+ }
+
+ redirectURL := r.URL
+ redirectURL.Path = "/"
+ redirectURL.RawQuery = ""
+ redirectURL.Fragment = ""
+ http.Redirect(w, r, redirectURL.String(), http.StatusTemporaryRedirect)
+ 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 = ""
@@ -185,21 +227,132 @@ 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(%q) = %+v", s.OwnerID, 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() {
+ defer wg.Done()
+
+ 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))
+ return
+ }
+ }()
+
+ 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
}