kidd / org-gcal.el

Org sync with Google Calendar. (active maintained project as of 2019-11-06)
435 stars 47 forks source link

Move from deferred.el to another asynchronous execution framework #159

Open telotortium opened 3 years ago

telotortium commented 3 years ago

I want to accomplish several things with this:

telotortium commented 3 years ago

emacs-aio looks promising - see https://nullprogram.com/blog/2019/03/10/. Unfortunately, you can't fully preserve stack traces in Emacs, but perhaps this will still make the code easier to read and not prone to stack overflows.

telotortium commented 3 years ago

I've decided to go with emacs-aio - since it uses the Emacs generator framework, code written using this framework looks just like async/await code in many modern languages, so it will be a lot easier to read than others. I've decided to go with emacs-aio instead of emacs-async-await because it uses Emacs errors instead of a separate reject callback for the error case, which I think it easier to read and preserves some semblance of a stack trace. I'm particularly impressed by the easy imperative code pattern:

(aio-defun org-gcal--get-event-aio (calendar-id event-id)
  "Retrieves a Google Calendar event given a CALENDAR-ID and EVENT-ID.

Refreshes the access token if needed.
Returns an ‘aio-promise’ for a ‘request-response' object."
  (let* ((a-token (org-gcal--get-access-token))
         (response
          (aio-await
           (org-gcal--aio-request
            (concat
             (org-gcal-events-url calendar-id)
             (concat "/" event-id))
            :type "GET"
            :headers
            `(("Accept" . "application/json")
              ("Authorization" . ,(format "Bearer %s" a-token)))
            :parser 'org-gcal--json-read)))
         (_data (request-response-data response))
         (status-code (request-response-status-code response))
         (error-thrown (request-response-error-thrown response)))
        (cond
         ;; If there is no network connectivity, the response will not
         ;; include a status code.
         ((eq status-code nil)
          (org-gcal--notify
           "Got Error"
           "Could not contact remote service. Please check your network connectivity.")
          (error "Network connectivity issue"))
         ((eq 401 (or (plist-get (plist-get (request-response-data response) :error) :code)
                      status-code))
          (org-gcal--notify
           "Received HTTP 401"
           "OAuth token expired. Now trying to refresh token.")
          (aio-await (org-gcal--refresh-token-aio))
          (aio-await (org-gcal--get-event-aio calendar-id event-id)))
         ;; Generic error-handler meant to provide useful information about
         ;; failure cases not otherwise explicitly specified.
         ((not (eq error-thrown nil))
          (org-gcal--notify
           (concat "Status code: " (number-to-string status-code))
           (pp-to-string error-thrown))
          (error "Got error %S: %S" status-code error-thrown))
         ;; Fetch was successful.
         (t response))))

(aio-defun org-gcal--refresh-token-aio ()
  "Refresh OAuth access and return the new access token as a deferred object."
  (let* ((response
          (aio-await
           (org-gcal--aio-request
            org-gcal-token-url
            :type "POST"
            :data `(("client_id" . ,org-gcal-client-id)
                    ("client_secret" . ,org-gcal-client-secret)
                    ("refresh_token" . ,(org-gcal--get-refresh-token))
                    ("grant_type" . "refresh_token"))
            :parser 'org-gcal--json-read)))
         (data (request-response-data response))
         (status-code (request-response-status-code response))
         (error-thrown (request-response-error-thrown response)))
    (cond
     ((eq error-thrown nil)
      (plist-put org-gcal-token-plist
                 :access_token
                 (plist-get data :access_token))
      (org-gcal--save-sexp org-gcal-token-plist org-gcal-token-file)
      (let ((token (plist-get org-gcal-token-plist :access_token)))
        token))
     (t
      (error "Got error %S: %S" status-code error-thrown)))))

org-gcal--aio-request can be found in #160, where I'm doing the aio rewrite.