Tuesday, August 19, 2014

Google Drive API - searching

Part1

Search for an item by name

List files

Google Drive API is a REST API. One have a resources which can be created, changed or removed.
In Google Drive both files and directories are represented as a file. To look for a file with a specific name we have to use List method of a resource File.
Documentation: Files.List
When programming in Go, it's worth to look at implementation: Drive API in Go
One is interested in FilesService and search for a method List on it. The method returns FilesListCall which have methods allowing to set different query parameters.
Simple code getting all files:
func GetAllFiles(srv *drive.Service) ([]*drive.File, error) {
  return d.Files.List().Do()
}
According to the documentation, the List method by default returns all files on Drive limited by a maxResults parameter. One can change the limit by calling: d.Files.List().MaxResults(10). The default value is 100, and possible values are between 0 and 1000. If there are more files to list, the method returns a valid PageToken string in reponse which can be used in a following requests.
One can copy from documentation an example code in Go which handles PageToken:
// AllFiles fetches and displays all files
func AllFiles(d *drive.Service) ([]*drive.File, error) {
  var fs []*drive.File
  pageToken := ""
  for {
    q := d.Files.List()
    // If we have a pageToken set, apply it to the query
    if pageToken != "" {
      q = q.PageToken(pageToken)
    }
    r, err := q.Do()
    if err != nil {
      fmt.Printf("An error occurred: %v\n", err)
      return fs, err
    }
    fs = append(fs, r.Items...)
    pageToken = r.NextPageToken
    if pageToken == "" {
      break
    }
  }
  return fs, nil
}
Few words about results. The API returns []*drive.File. It's worth to take a look at a documentation: File and a code: type File struct in API source code.
The final code looks like: gist.github.com/orian/6a0d7883ca3678cb30ea

Search for a file with a specific name

Files don't have a name per se, the name shown in a drive.google.com is an attribute title of a resource File: File reference.
To search only files with a specific name we need to use q parameter. Go API allows to do that through Q(string) method. Example code below:
func FindFile(srv *drive.Service, name string) ([]*drive.File, error) {
  q := fmt.Sprintf("title = '%s'", name)
  return Files.List().Q(q).Do()
}

Search for a directory

The directory is a Google drive file with a special mimetype: 'application/vnd.google-apps.folder'. To search for a directory with a specific name we need to extend the previous code and a query parameter by "mimeType = 'application/vnd.google-apps.folder'".
func FindDir(srv *drive.Service, name string) ([]*drive.File, error) {
  q := fmt.Sprintf("mimeType = 'application/vnd.google-apps.folder' and title = '%s'", name)
  return Files.List().Q(q).Do()
}

Search for a directory knowing its parent id

If a name is not an identifier of file on Drive than what? FileId is an unique id given to each file on Google Drive. It's available as Id field of struct drive.File. If we look at search documentation: search parameters we can find parents property on which we can use operator in. E.g. when we have a folder id 1234 we can require a file to be in folder by writing '1234 in parents' as query.
func FindSubDir(srv *drive.Service, name, parentId string) ([]*drive.File, error) {
  subq := []string{
      "mimeType = 'application/vnd.google-apps.folder'", 
      fmt.Sprintf("title = '%s'", name),
      fmt.Sprintf("'%s' in parents", parentId),
  }
  q := strings.Join(subq, " and ")
  return Files.List().Q(q).Do()
}

Friday, August 15, 2014

Writing Google Drive upload application

This day has to come. I came back from holidays and have few thousands photos. Many of them to throw away but most of them to keep, share and print.
The photos were took by a standalone camera and backed up on a hard drive.
The tries of using Google Photos Backup for Mac OS, plus.google.com, drive.google.com were for me dissapointing. The first was stalled after few dozen of photos and no restart helped. The other ones also crashed and were rather slow.

After creating an app I was able to upload without significant problems over 2 thousand photos and counting.
The app works as follow:

  • Authorize
  • Find a directory on Google Drive, if not exist then create one.
  • Get a list of all files from a destination directory.
  • Scan a local directory to find files and for each:
    • check if a name matches a pattern
    • check if not already on Google Drive
    • upload

Authentication & authorization

Authentication identifies your application and informs Google that you are you. To create simple authentication and authorization code follow: Google Drive API - Quickstart for Go
This bootstrap our efforts so we have working program which authenticats and authorizes itself with a help of user.

Caution! The quickstart example (I guess some dependencies) requires Go version 1.3. It doesn't work with 1.2 and earlier (default version in Ubuntu 14.04 package repo as August 2014).

Authorization gives your app a credential to act as a specific Google user. Google is recommending using OAuth2.0. The data you want to access is covered by a scope. In a case of Google Drive and accessing/modifying content it's 'https://www.googleapis.com/auth/drive'.

This is already done by a quickstart app from the above tutorial. The only think we want to modify is to cache an access token so we don't have to ask user for Drive permission every time.

Caching the user's access token

The OAuth2 library we are using already have a support for saving a token. There is a interface Cache and a simple implementation CacheFile:
https://godoc.org/code.google.com/p/goauth2/oauth#Cache

First, we will separate code responsible for a creating token and transport.
func GetNewToken() (*oauth.Token, *oauth.Transport) {
 // Generate a URL to visit for authorization.
 authUrl := config.AuthCodeURL("state")
 log.Printf("Go to the following link in your browser: %v\n", authUrl)
 // Read the code, and exchange it for a token.
 log.Printf("Enter verification code: ")
 var code string
 fmt.Scanln(&code)

 t := &oauth.Transport{
  Config:    config,
  Transport: http.DefaultTransport,
 }
 token, err := t.Exchange(code)
 if err != nil {
  log.Fatalf("An error occurred exchanging the code: %v\n", err)
 }
 return token, t
}
The example which tries to load a token from file and if cannot then request a new one may looks as follow:
var cache oauth.Cache = oauth.CacheFile("access_token.json")
token, err := cache.Token()
var t *oauth.Transport
if err != nil {
 log.Printf("Need a new token. Cannot load old one.")
 token, t = GetNewToken()
 cache.PutToken(token)
} else {
 t = &oauth.Transport{
  Config:    config,
  Token:     token,
  Transport: http.DefaultTransport,
 }
}
Full source code: gist.github.com/orian/96b5140b66363f4dee65