# Interface to Google Drive in Racket ## Fred Martin ### April 22, 2017 # Overview This set of code provides an interface to searching through one's Google Drive account. Its most important feature is that it provides a *folder-delimited search*. The essential model of files in Google Drive is that they are in one big “pile.” So you can't directly find a file in a given folder. This code recursively collects all folders found within a given folder, and then construct a search query that includes a list of all the subfolders (flattened into a single list). This then allows you to perform a folder-delimited search. **Authorship note:** All of the code described here was written by myself. # Libraries Used The code uses four libraries: ``` (require net/url) (require (planet ryanc/webapi:1:=1/oauth2)) (require json) (require net/uri-codec) ``` * The ```net/url``` library provides the ability to make REST-style https queries to the Google Drive API. * Ryan Culpepper's ```webapi``` library is used to provide the ```oauth2``` interface required for authentication. * The ```json``` library is used to parse the replies from the Google Drive API. * The ```net/uri-codec``` library is used to format parameters provided in API calls into an ASCII encoding used by Google Drive. # Key Code Excerpts Here is a discussion of the most essential procedures, including a description of how they embody ideas from UMass Lowell's COMP.3010 Organization of Programming languages course. Five examples are shown and they are individually numbered. ## 1. Initialization using a Global Object The following code creates a global object, ```drive-client``` that is used in each of the subsequent API calls: ``` (define drive-client (oauth2-client #:id "548798434144-6s8abp8aiqh99bthfptv1cc4qotlllj6.apps.googleusercontent.com" #:secret "")) ``` While using global objects is not a central theme in the course, it's necessary to show this code to understand the later examples. ## 2. Selectors and Predicates using Procedural Abstraction A set of procedures was created to operate on the core ```drive-file``` object. Drive-files may be either actual file objects or folder objects. In Racket, they are represented as a hash table. ```folder?``` accepts a ```drive-file```, inspects its ```mimeType```, and returns ```#t``` or ```#f```: ``` (define (folder? drive-file) (string=? (hash-ref drive-file 'mimeType "nope") "application/vnd.google-apps.folder")) ``` Another object produced by the Google Drive API is a list of drive-file objects ("```drive#fileList```"). When converted by the JSON library, this list appears as hash map. ```get-files``` retrieves a list of the files themselves, and ```get-id``` retrieves the unique ID associated with a ```drive#fileList``` object: ``` (define (get-files obj) (hash-ref obj 'files)) (define (get-id obj) (hash-ref obj 'id)) ``` ## 3. Using Recursion to Accumulate Results The low-level routine for interacting with Google Drive is named ```list-children```. This accepts an ID of a folder object, and optionally, a token for which page of results to produce. A lot of the work here has to do with pagination. Because it's a web interface, one can only obtain a page of results at a time. So it's necessary to step through each page. When a page is returned, it includes a token for getting the next page. The ```list-children``` just gets one page: ``` (define (list-children folder-id . next-page-token) (read-json (get-pure-port (string->url (string-append "https://www.googleapis.com/drive/v3/files?" "q='" folder-id "'+in+parents" "&key=" (send drive-client get-id) (if (= 1 (length next-page-token)) (string-append "&pageToken=" (car next-page-token)) "") ; "&pageSize=5" )) token))) ``` The interesting routine is ```list-all-children```. This routine is directly invoked by the user. It optionally accepts a page token; when it's used at top level this parameter will be null. The routine uses ```let*``` to retrieve one page of results (using the above ```list-children``` procedure) and also possibly obtain a token for the next page. If there is a need to get more pages, the routine uses ```append``` to pre-pend the current results with a recursive call to get the next page (and possibly more pages). Ultimately, when there are no more pages to be had, the routine terminates and returns the current page. This then generates a recursive process from the recursive definition. ``` (define (list-all-children folder-id . next-page-token) (let* ((this-page (if (= 0 (length next-page-token)) (list-children folder-id) (list-children folder-id (car next-page-token)))) (page-token (hash-ref this-page 'nextPageToken #f))) (if page-token (append (get-files this-page) (list-all-children folder-id page-token)) (get-files this-page)))) ``` ## 4. Filtering a List of File Objects for Only Those of Folder Type The ```list-all-children``` procedure creates a list of all objects contained within a given folder. These objects include the files themselves and other folders. The ```filter``` abstraction is then used with the ```folder?``` predicate to make a list of subfolders contained in a given folder: ``` (define (list-folders folder-id) (filter folder? (list-all-children folder-id))) ``` ## 5. Recursive Descent on a Folder Hierarchy These procedures are used together in ```list-all-folders```, which accepts a folder ID and recursively obtains the folders at the current level and then recursively calls itself to descend completely into the folder hierarchy. ```map``` and ```flatten``` are used to accomplish the recursive descent: ``` (define (list-all-folders folder-id) (let ((this-level (list-folders folder-id))) (begin (display (length this-level)) (display "... ") (append this-level (flatten (map list-all-folders (map get-id this-level))))))) ```