Merge "picasa importer: remove Run-private HTTPClient"

This commit is contained in:
Brad Fitzpatrick 2014-07-21 18:40:00 +00:00 committed by Gerrit Code Review
commit 6b65adbbdd
3 changed files with 80 additions and 26 deletions

View File

@ -22,6 +22,9 @@ import (
"log"
"net/http"
"net/url"
"strconv"
"strings"
"time"
"camlistore.org/pkg/blob"
"camlistore.org/pkg/context"
@ -65,9 +68,9 @@ func (im ExtendedOAuth2) CallbackURLParameters(acctRef blob.Ref) url.Values {
return url.Values{}
}
// notOAuthTransport returns c's Transport, or its underlying transport if c.Transport
// NotOAuthTransport returns c's Transport, or its underlying transport if c.Transport
// is an OAuth Transport.
func notOAuthTransport(c *http.Client) (tr http.RoundTripper) {
func NotOAuthTransport(c *http.Client) (tr http.RoundTripper) {
tr = c.Transport
if otr, ok := tr.(*oauth.Transport); ok {
tr = otr.Transport
@ -101,7 +104,7 @@ func (im ExtendedOAuth2) ServeCallback(w http.ResponseWriter, r *http.Request, c
// needs to have the access token that is obtained during Exchange.
transport := &oauth.Transport{
Config: oauthConfig,
Transport: notOAuthTransport(ctx.HTTPClient()),
Transport: NotOAuthTransport(ctx.HTTPClient()),
}
token, err := transport.Exchange(code)
log.Printf("Token = %#v, error %v", token, err)
@ -111,7 +114,7 @@ func (im ExtendedOAuth2) ServeCallback(w http.ResponseWriter, r *http.Request, c
return
}
picagoCtx := ctx.Context.New(context.WithHTTPClient(&http.Client{Transport: transport}))
picagoCtx := ctx.Context.New(context.WithHTTPClient(transport.Client()))
defer picagoCtx.Cancel()
userInfo, err := im.getUserInfo(picagoCtx, token.AccessToken)
@ -125,7 +128,7 @@ func (im ExtendedOAuth2) ServeCallback(w http.ResponseWriter, r *http.Request, c
AcctAttrUserID, userInfo.ID,
AcctAttrGivenName, userInfo.FirstName,
AcctAttrFamilyName, userInfo.LastName,
AcctAttrAccessToken, token.AccessToken,
AcctAttrOAuthToken, encodeToken(token),
); err != nil {
httputil.ServeError(w, r, fmt.Errorf("Error setting attribute: %v", err))
return
@ -133,6 +136,46 @@ func (im ExtendedOAuth2) ServeCallback(w http.ResponseWriter, r *http.Request, c
http.Redirect(w, r, ctx.AccountURL(), http.StatusFound)
}
// encodeToken encodes the oauth.Token as
// AccessToken + " " + RefreshToken + " " + Expiry.Unix()
func encodeToken(token *oauth.Token) string {
if token == nil {
return " 0"
}
var seconds int64
if !token.Expiry.IsZero() {
seconds = token.Expiry.Unix()
}
return token.AccessToken + " " + token.RefreshToken + " " + strconv.FormatInt(seconds, 10)
}
// DecodeToken decodes from format of access + " " + refresh + " " + expiry
// to oauth.Token.
// It does not return an error, just decodes till it can.
func DecodeToken(encoded string) oauth.Token {
var token oauth.Token
i := strings.IndexByte(encoded, ' ')
if i < 0 {
token.AccessToken = encoded
return token
}
token.AccessToken, encoded = encoded[:i], encoded[i+1:]
i = strings.IndexByte(encoded, ' ')
if i < 0 {
token.RefreshToken = encoded
return token
}
token.RefreshToken, encoded = encoded[:i], encoded[i+1:]
if len(encoded) == 0 {
return token
}
seconds, err := strconv.ParseInt(encoded, 10, 64)
if err == nil {
token.Expiry = time.Unix(seconds, 0)
}
return token
}
func (im ExtendedOAuth2) auth(ctx *SetupContext) (*oauth.Config, error) {
clientId, secret, err := ctx.Credentials()
if err != nil {

View File

@ -32,6 +32,8 @@ const (
AcctAttrTempSecret = "oauthTempSecret"
AcctAttrAccessToken = "oauthAccessToken"
AcctAttrAccessTokenSecret = "oauthAccessTokenSecret"
// AcctAttrOAuthToken stores `access + " " + refresh + " " + expiry`
AcctAttrOAuthToken = "oauthToken"
)
// OAuth1 provides methods that the importer implementations can use to

View File

@ -50,10 +50,24 @@ type imp struct {
importer.ExtendedOAuth2
}
var baseOAuthConfig = oauth.Config{
AuthURL: authURL,
TokenURL: tokenURL,
Scope: scopeURL,
// AccessType needs to be "offline", as the user is not here all the time;
// ApprovalPrompt needs to be "force" to be able to get a RefreshToken
// everytime, even for Re-logins, too.
//
// Source: https://developers.google.com/youtube/v3/guides/authentication#server-side-apps
AccessType: "offline",
ApprovalPrompt: "force",
}
func newImporter() *imp {
return &imp{
importer.NewExtendedOAuth2(
oauth.Config{AuthURL: authURL, TokenURL: tokenURL, Scope: scopeURL},
baseOAuthConfig,
func(ctx *context.Context, accessToken string) (*importer.UserInfo, error) {
u, err := getUserInfo(ctx, accessToken)
if err != nil {
@ -93,8 +107,7 @@ and click "CREATE PROJECT".</p>
// A run is our state for a given run of the importer.
type run struct {
*importer.RunContext
im *imp
client *http.Client
im *imp
}
func (im *imp) Run(ctx *importer.RunContext) error {
@ -102,20 +115,16 @@ func (im *imp) Run(ctx *importer.RunContext) error {
if err != nil {
return err
}
client := ctx.Host.HTTPClient()
client.Transport = &oauth.Transport{
Config: &oauth.Config{
ClientId: clientId,
ClientSecret: secret,
AuthURL: authURL,
TokenURL: tokenURL,
Scope: scopeURL,
},
Token: &oauth.Token{
AccessToken: ctx.AccountNode().Attr(importer.AcctAttrAccessToken),
},
ocfg := baseOAuthConfig
ocfg.ClientId, ocfg.ClientSecret = clientId, secret
token := importer.DecodeToken(ctx.AccountNode().Attr(importer.AcctAttrOAuthToken))
transport := &oauth.Transport{
Config: &ocfg,
Token: &token,
Transport: importer.NotOAuthTransport(ctx.HTTPClient()),
}
r := &run{RunContext: ctx, im: im, client: client}
ctx.Context = ctx.Context.New(context.WithHTTPClient(transport.Client()))
r := &run{RunContext: ctx, im: im}
if err := r.importAlbums(); err != nil {
return err
}
@ -123,16 +132,16 @@ func (im *imp) Run(ctx *importer.RunContext) error {
}
func (r *run) importAlbums() error {
albums, err := picago.GetAlbums(r.client, "default")
albums, err := picago.GetAlbums(r.HTTPClient(), "default")
if err != nil {
return err
return fmt.Errorf("importAlbums: error listing albums: %v", err)
}
albumsNode, err := r.getTopLevelNode("albums", "Albums")
for _, album := range albums {
if r.Context.IsCanceled() {
return context.ErrCanceled
}
if err := r.importAlbum(albumsNode, album, r.client); err != nil {
if err := r.importAlbum(albumsNode, album, r.HTTPClient()); err != nil {
return fmt.Errorf("picasa importer: error importing album %s: %v", album, err)
}
}
@ -142,7 +151,7 @@ func (r *run) importAlbums() error {
func (r *run) importAlbum(albumsNode *importer.Object, album picago.Album, client *http.Client) error {
albumNode, err := albumsNode.ChildPathObject(album.Name)
if err != nil {
return err
return fmt.Errorf("importAlbum: error listing album: %v", err)
}
// Data reference: https://developers.google.com/picasa-web/docs/2.0/reference
@ -222,7 +231,7 @@ func (r *run) importAlbum(albumsNode *importer.Object, album picago.Album, clien
func (r *run) importPhoto(albumNode *importer.Object, photo picago.Photo, client *http.Client) (*importer.Object, error) {
body, err := picago.DownloadPhoto(client, photo.URL)
if err != nil {
return nil, err
return nil, fmt.Errorf("importPhoto: DownloadPhoto error: %v", err)
}
fileRef, err := schema.WriteFileFromReader(
r.Host.Target(),