aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--IBRAHIM_MKUSA.md272
1 files changed, 175 insertions, 97 deletions
diff --git a/IBRAHIM_MKUSA.md b/IBRAHIM_MKUSA.md
index a24560c..48a7ed1 100644
--- a/IBRAHIM_MKUSA.md
+++ b/IBRAHIM_MKUSA.md
@@ -1,36 +1,39 @@
-# Interface to Google Drive in Racket
+# Hermes - A chat server and client written in Racket
-## Fred Martin
-### April 22, 2017
+## Ibrahim Mkusa
+### April 30, 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*.
+Hermes is a chat server and client written in Racket. One can run the Hermes
+server on any machine that is internet accessible. The Hermes clients then
+connect to the server from anywhere on the internet. It's inspired by chat
+systems and clients like irc.
-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.
+The goal in building Hermes was to expose myself to several concepts integral to
+systems like networking, synchronization, and multitasking.
-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:
+Most libraries and utilities used are part of base Drracket installation and
+therefore do not need to be imported.
+
+The date and time modules were used for various time related queries.
+The tcp module was used for communication via Transmission Control Protocol.
+Concurrency and synchronization modules that provide threads, and semaphores
+were also used.
+
+Below are libraries that were not part of base system:
```
-(require net/url)
-(require (planet ryanc/webapi:1:=1/oauth2))
-(require json)
-(require net/uri-codec)
+(require racket/gui/base)
+(require math/base)
```
-* 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.
+* The ```racket/gui/base``` library used to build graphical user interface.
+* The ```math/base``` library was used for testing purposes. It was used to
+generated random numbers.
# Key Code Excerpts
@@ -39,116 +42,187 @@ 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
+## 1. Tracking client connections using an object and closures.
-The following code creates a global object, ```drive-client``` that is used in each of the subsequent API calls:
+The following code defines and creates a global object, ```make-connections```
+that abstracts client connections. It also creates a semaphore to control access
+to ```make-connections``` object.
```
-(define drive-client
- (oauth2-client
- #:id "548798434144-6s8abp8aiqh99bthfptv1cc4qotlllj6.apps.googleusercontent.com"
- #:secret "<email me for secret if you want to use my API>"))
+(define (make-connections connections)
+ (define (null-cons?)
+ (null? connections))
+ (define (add username in out)
+ (set! connections (append connections (list (list username in out))))
+ connections)
+ (define (cons-list)
+ connections)
+ (define (remove-ports in out)
+ (set! connections
+ (filter
+ (lambda (ports)
+ (if (and (eq? in (get-input-port ports))
+ (eq? out (get-output-port ports)))
+ #f
+ #t))
+ connections)))
+ (define (dispatch m)
+ (cond [(eq? m 'null-cons) null-cons?]
+ [(eq? m 'cons-list) cons-list]
+ [(eq? m 'remove-ports) remove-ports]
+ [(eq? m 'add) add]))
+ dispatch)
+
+(define c-connections (make-connections '()))
+
+(define connections-s (make-semaphore 1)) ;; control access to connections
```
+ When the tcp-listener accepts a connection from a client, the associated input
+ output ports along with username are added as an entry in ```make-connections``` via ```add``` function.
+ External functions can operate on the connections by securing the semaphore,
+ and then calling ```cons-list``` to expose the underlying list of connections.
+ ```remove-ports``` method is also available to remove input output ports from
+ managed connections.
+
+
- 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.
+## 2. Tracking received messages via objects and closures.
-```folder?``` accepts a ```drive-file```, inspects its ```mimeType```, and returns ```#t``` or ```#f```:
+The code below manages broadcast messages from one client to the rest. It wraps
+a list of strings inside an object that has functions similar to ```make-connections``` for
+exposing and manipulating the list from external functions. The code creates
+```make-messages``` global object and a semaphore to control access to it from
+various threads of execution.
```
-(define (folder? drive-file)
- (string=? (hash-ref drive-file 'mimeType "nope") "application/vnd.google-apps.folder"))
+(define (make-messages messages)
+ (define (add message)
+ (set! messages (append messages (list message)))
+ messages)
+ (define (mes-list)
+ messages)
+ (define (remove-top)
+ (set! messages (rest messages))
+ messages)
+ (define (dispatch m)
+ (cond [(eq? m 'add) add]
+ [(eq? m 'mes-list) mes-list]
+ [(eq? m 'remove-top) remove-top]))
+ dispatch)
+
+(define c-messages (make-messages '()))
+
+(define messages-s (make-semaphore 1)) ;; control access to messages
```
-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.
+## 3. Using map to broadcast messages from client to clients
-```get-files``` retrieves a list of the files themselves, and ```get-id``` retrieves the unique ID
-associated with a ```drive#fileList``` object:
+The ```broadcast``` function is called repeatedly in a loop to extract a message
+from ```make-messages``` object, and send it to every other client. It uses the
+```make-connections``` objects to extract output port of a client. The ```map```
+routine is called on every client in the connections object to send it
+a message.
```
-(define (get-files obj)
- (hash-ref obj 'files))
-
-(define (get-id obj)
- (hash-ref obj 'id))
+(define broadcast
+ (lambda ()
+ (semaphore-wait messages-s)
+ (cond [(not (null? ((c-messages 'mes-list))))
+ (map
+ (lambda (ports)
+ (if (not (port-closed? (get-output-port ports)))
+ (begin
+ (displayln (first ((c-messages 'mes-list))) (get-output-port ports))
+ (flush-output (get-output-port ports)))
+ (displayln-safe "Failed to broadcast. Port not open." error-out-s error-out)))
+ ((c-connections 'cons-list)))
+ (displayln-safe (first ((c-messages 'mes-list))) convs-out-s convs-out)
+ ;; remove top message from "queue" after broadcasting
+ ((c-messages 'remove-top))
+ ; debugging displayln below
+ ; (displayln "Message broadcasted")
+ ]) ; end of cond
+ (semaphore-post messages-s)))
```
-## 3. Using Recursion to Accumulate Results
+After the message is send, the message is removed from the "queue" via the
+```remove-top```.
-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.
+The code snippet below creates a thread that iteratively calls ```broadcast```
+every interval, where interval(in secs) is defined by ```sleep-t```.
-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:
+** note ** : ```sleep``` is very important for making Hermes behave gracefully
+in a system. Without it, it would be called at the rate derived from cpu clock
+rate. This raises cpu temperatures substantially, and make cause a pre-mature
+system shutdown.
```
-(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)))
+(thread (lambda ()
+ (displayln-safe "Broadcast thread started!")
+ (let loopb []
+ (sleep sleep-t) ;; wait 0.2 ~ 0.5 secs before beginning to broadcast
+ (broadcast)
+ (loopb))))
```
-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.
+## 4. Filtering a List of connections to find recipient of a whisper
-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).
+I implemented a whisper functionality, where a user can whisper to any user in
+the chat room. The whisper message is only sent to specified user. To implement
+this i used ```filter``` over the connections, where the predicate tested whether the
+current list item matched that of a specific user.
-Ultimately, when there are no more pages to be had, the routine terminates and returns the current page.
+'''
+(define whisper (regexp-match #px"(.*)/whisper\\s+(\\w+)\\s+(.*)" evt-t0))
-This then generates a recursive process from the recursive definition.
+[whisper
+ (semaphore-wait connections-s)
+ ; get output port for user
+ ; this might be null
+ (define that-user-ports
+ (filter
+ (lambda (ports)
+ (if (string=? (whisper-to whisper) (get-username ports))
+ #t
+ #f))
+ ((c-connections 'cons-list))))
+ ; try to send that user the whisper
+ (if (and (null? that-user-ports)
+ #t) ; #t is placeholder for further checks
+ (begin
+ (displayln "User is unavailable. /color blue" out)
+ (flush-output out))
+ (begin
+ (displayln (string-append "(whisper) "
+ (whisper-info whisper) (whisper-message whisper))
+ (get-output-port (car that-user-ports)))
+ (flush-output (get-output-port (car that-user-ports)))))
+ (semaphore-post connections-s)]
+'''
-```
-(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 snippet above is part of cond statement that tests contents of input from
+clients to determine what the client is trying wants/trying to do. The top-line
+is using regexes to determine whether the received message is a whisper or not.
-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. Selectors for dealing with content of a whisper from clients
-## 5. Recursive Descent on a Folder Hierarchy
+Below are are three selectors that help abstract the contents of a whisper
+message.
-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:
```
+; whisper selector for the username and message
+(define (whisper-info exp)
+ (cadr exp))
+
+(define (whisper-to exp)
+ (caddr exp))
+
+(define (whisper-message exp)
+ (cadddr exp))
(define (list-all-folders folder-id)
(let ((this-level (list-folders folder-id)))
(begin
@@ -156,3 +230,7 @@ hierarchy.
(append this-level
(flatten (map list-all-folders (map get-id this-level)))))))
```
+
+```whisper-info``` retrieves the date-time and username info.
+```whisper-to``` retrieves the username of the intented recipient of a whisper.
+```whisper-message``` retrieves the actual whisper.