Created
March 5, 2021 10:14
-
-
Save pacoorozco/2379489d54a24f4d40368ceabf24c6c6 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package main | |
import ( | |
"context" | |
"encoding/json" | |
"fmt" | |
"io" | |
"log" | |
"net/http" | |
"os" | |
"time" | |
gphotos "github.com/gphotosuploader/google-photos-api-client-go/v2" | |
"github.com/gphotosuploader/google-photos-api-client-go/v2/albums" | |
"golang.org/x/oauth2" | |
"golang.org/x/oauth2/google" | |
) | |
var ( | |
// numberOfRequests is the total amount of request to send to Google Photos | |
numberOfRequests = 10000 | |
// timeBetweenRequests is the number of seconds to wait between two consecutive requests | |
timeBetweenRequests = 1 | |
// appClientID is the Client ID string to identify this app in Google | |
appClientID = "__CLIENT_ID__" | |
// appClientSecret is the Client seret to identify this app in Google | |
appClientSecret = "__CLIENT_SECRET__" | |
// tokenFilePath is the path of the file to store the Google Photos Token | |
tokenFilePath = "token.json" | |
) | |
// Retrieves a token from a local file. | |
func tokenFromFile(file string) (*oauth2.Token, error) { | |
f, err := os.Open(file) | |
if err != nil { | |
return nil, err | |
} | |
defer f.Close() | |
tok := &oauth2.Token{} | |
err = json.NewDecoder(f).Decode(tok) | |
return tok, err | |
} | |
// Saves a token to a file path. | |
func saveToken(path string, token *oauth2.Token) error { | |
log.Printf("Saving credential file to: %s\n", path) | |
f, err := os.OpenFile(path, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0600) | |
if err != nil { | |
log.Fatalf("Unable to cache oauth token: %v", err) | |
} | |
defer f.Close() | |
return json.NewEncoder(f).Encode(token) | |
} | |
// NewOAuth2Client returns a HTTP client authenticated in Google Photos. | |
// NewOAuth2Client will get (from Token Manager) or create the token. | |
func NewOAuth2Client(ctx context.Context) (*http.Client, error) { | |
oauth2Config := oauth2.Config{ | |
ClientID: appClientID, | |
ClientSecret: appClientSecret, | |
Scopes: []string{"https://www.googleapis.com/auth/photoslibrary"}, | |
Endpoint: google.Endpoint, | |
} | |
token, err := tokenFromFile(tokenFilePath) | |
if err != nil { | |
log.Printf("Unable to retrieve token from token manager: %s", err) | |
} | |
switch { | |
case token == nil: | |
log.Println("Getting OAuth2 token from prompt...") | |
token, err = getOfflineOAuth2Token(ctx, oauth2Config) | |
if err != nil { | |
return nil, fmt.Errorf("unable to get token: %s", err) | |
} | |
case !token.Valid(): | |
log.Println("Token has been expired, refreshing it...") | |
token, err = oauth2Config.TokenSource(ctx, token).Token() | |
if err != nil { | |
log.Printf("Unable to refresh the token, err: %s", err) | |
return nil, fmt.Errorf("unable to refresh the token: %s", err) | |
} | |
} | |
log.Printf("Token is valid, expires at %s", token.Expiry.String()) | |
if err := saveToken(tokenFilePath, token); err != nil { | |
log.Printf("Unable to store token: %s", err) | |
} | |
client := oauth2Config.Client(ctx, token) | |
return client, nil | |
} | |
func getOfflineOAuth2Token(ctx context.Context, oauth2Config oauth2.Config) (*oauth2.Token, error) { | |
oauth2Config.RedirectURL = "urn:ietf:wg:oauth:2.0:oob" | |
// Redirect user to consent page to ask for permission for the specified scopes. | |
url := oauth2Config.AuthCodeURL("state", oauth2.AccessTypeOffline) | |
code, err := askForAuthCodeInTerminal(os.Stdin, url) | |
if err != nil { | |
return nil, err | |
} | |
// Use the custom HTTP client with a short timeout when requesting a token. | |
ctx = context.WithValue(ctx, oauth2.HTTPClient, &http.Client{Timeout: 2 * time.Second}) | |
return oauth2Config.Exchange(ctx, code) | |
} | |
func askForAuthCodeInTerminal(r io.Reader, url string) (string, error) { | |
fmt.Printf("\nVisit the following URL in your browser:\n%v\n\n", url) | |
var code string | |
fmt.Print("After completing the authorization flow, enter the authorization code here: ") | |
n, err := fmt.Fscanln(r, &code) | |
if err != nil || n == 0 { | |
return "", fmt.Errorf("unable to read authorization code: %s", err) | |
} | |
return code, nil | |
} | |
type noopCache struct { | |
} | |
// GetAlbum returns Album data from the cache corresponding to the specified title. | |
// It will return ErrCacheMiss if there is no cached Album. | |
func (c noopCache) GetAlbum(ctx context.Context, title string) (albums.Album, error) { | |
return albums.Album{}, albums.ErrCacheMiss | |
} | |
// PutAlbum stores the Album data in the cache using the title as key. | |
// Underlying implementations may use any data storage format, | |
// as long as the reverse operation, GetAlbum, results in the original data. | |
func (c noopCache) PutAlbum(ctx context.Context, album albums.Album) error { | |
return nil | |
} | |
// PutManyAlbums stores many Album data in the cache using the title as key. | |
func (c noopCache) PutManyAlbums(ctx context.Context, albums []albums.Album) error { | |
return nil | |
} | |
// InvalidateAlbum removes the Album data from the cache corresponding to the specified title. | |
// If there's no such Album in the cache, it will return nil. | |
func (c noopCache) InvalidateAlbum(ctx context.Context, title string) error { | |
return nil | |
} | |
// InvalidateAllAlbums removes all key corresponding to albums | |
func (c noopCache) InvalidateAllAlbums(ctx context.Context) error { | |
return nil | |
} | |
func main() { | |
ctx := context.Background() | |
httpClient, err := NewOAuth2Client(ctx) | |
if err != nil { | |
panic(err) | |
} | |
// Disable the cache to hit always the Google Photos Service. | |
c := noopCache{} | |
albumServiceWithoutCache := albums.NewCachedAlbumsService(httpClient, albums.WithCache(c)) | |
client, err := gphotos.NewClient(httpClient, gphotos.WithAlbumsService(albumServiceWithoutCache)) | |
if err != nil { | |
panic(err) | |
} | |
log.Printf("We are going to sent %d requests to Google Photos (time between reqs: %ds)", numberOfRequests, timeBetweenRequests) | |
for i := 0; i < numberOfRequests; i++ { | |
_, err = client.Albums.List(ctx) | |
if err != nil { | |
log.Printf("Error (%d): %s", i, err) | |
os.Exit(1) | |
} | |
log.Printf("Request #%d: success", i) | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment