Skip to content

Instantly share code, notes, and snippets.

@pacoorozco
Created March 5, 2021 10:14
Show Gist options
  • Save pacoorozco/2379489d54a24f4d40368ceabf24c6c6 to your computer and use it in GitHub Desktop.
Save pacoorozco/2379489d54a24f4d40368ceabf24c6c6 to your computer and use it in GitHub Desktop.
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