diff options
-rw-r--r-- | IBRAHIM_MKUSA.md | 158 |
1 files changed, 158 insertions, 0 deletions
diff --git a/IBRAHIM_MKUSA.md b/IBRAHIM_MKUSA.md new file mode 100644 index 0000000..a24560c --- /dev/null +++ b/IBRAHIM_MKUSA.md @@ -0,0 +1,158 @@ +# 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 "<email me for secret if you want to use my API>")) + ``` + + 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))))))) +``` |