mirror of https://github.com/perkeep/perkeep.git
Merge "picasa importer: remove Run-private HTTPClient"
This commit is contained in:
commit
6b65adbbdd
|
@ -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 {
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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(),
|
||||
|
|
Loading…
Reference in New Issue