Advertisement
Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- ;;; naria2-jsonrpc.el --- Control aria2 in Emacs via WebSocket -*- lexical-binding: t -*-
- ;; Copyright (C) 2019-2020 Zhu Zihao
- ;; Author: Zhu Zihao <all_but_last@163.com>
- ;; URL: https://github.com/cireu/emacs-naria2
- ;; Version: 0.0.1
- ;; Package-Requires: ((emacs "25.2") (jsonrpc "1.0.7") (websocket "1.11.1"))
- ;; Keywords: conn, lisp
- ;; This file is NOT part of GNU Emacs.
- ;; This file is free software; you can redistribute it and/or modify
- ;; it under the terms of the GNU General Public License as published by
- ;; the Free Software Foundation; either version 3, or (at your option)
- ;; any later version.
- ;; This program is distributed in the hope that it will be useful,
- ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
- ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- ;; GNU General Public License for more details.
- ;; For a full copy of the GNU General Public License
- ;; see <https://www.gnu.org/licenses/>.
- ;;; Commentary:
- ;; * Introduction
- ;; This package allow you to control your [[https://github.com/aria2/aria2][aria2]] instance via WebSocket and JSONRPC
- ;; in Emacs. Some low-level API was wrapped for usage.
- ;; * Installation
- ;; The suggested way to install is =package.el=.
- ;; * Examples
- ;; #+begin_src emacs-lisp
- ;; (require 'naria2-jsonrpc)
- ;; ;; Sync API
- ;; ;; Notice that closing a connection in explicit is *always* unnecessary,
- ;; ;; because `naria2-jsonrpc-connection' is a RAII object, so the release of
- ;; ;; resource can be delegated to GC.
- ;; (letrec ((inst (naria2-jsonrpc-connect "localhost" 6800 nil "tokenfoobar"))
- ;; (gid (naria2-jsonrpc-sync-call inst "addUri"
- ;; [["http://example.org/file"]]))
- ;; (cancelback (naria2-jsonrpc-on inst "onDownloadComplete"
- ;; (lambda (_data)
- ;; (message "Task completed")
- ;; (funcall cancelback)))))
- ;; (message "The GID of download task is %s" gid))
- ;; ;; Async API
- ;; (let ((inst (naria2-jsonrpc-connect "localhost" 6800 nil nil)))
- ;; (naria2-jsonrpc-async-call
- ;; inst "addUri"
- ;; :success-fn (lambda (data)
- ;; (message "The GID of download task is %s" gid))))
- ;; #+end_src
- ;; * Reference
- ;; - [[https://aria2.github.io/manual/en/html/aria2c.html][API manual of Aria2]]
- ;;; Code:
- (require 'jsonrpc)
- (require 'websocket)
- (require 'eieio)
- (eval-when-compile
- (require 'cl-lib)
- (require 'subr-x)
- (require 'pcase))
- ;;; Utilities
- (defun naria2-jsonrpc-parse-string (str)
- "Parse STR into a JSON object."
- (with-temp-buffer
- (insert str)
- (goto-char (point-min))
- (jsonrpc--json-read)))
- ;;; JSONRPC
- (defclass naria2-jsonrpc-connection (jsonrpc-connection)
- ((-secret
- :initform nil
- :documentation
- "The authorization token for RPC call.")
- (-socket
- :documentation
- "The websocket connection.")
- (-notification-handlers-table
- :initform (make-hash-table :test #'equal)
- :documentation
- "The hash table to store handlers of different notifications from aria2.")
- (-finalizer
- :documentation
- "The object returned by `make-finalizer'."))
- "The websocket JSONRPC connection to aria2 download server.")
- ;; JSONRPC Implementation
- (cl-defmethod jsonrpc-connection-send ((conn naria2-jsonrpc-connection)
- &rest args &key method &allow-other-keys)
- (when method
- (let* ((msg `(:jsonrpc "2.0" ,@args))
- (json-str (jsonrpc--json-encode msg))
- (socket (oref conn -socket)))
- (websocket-send-text socket json-str)
- (jsonrpc--log-event conn msg 'client))))
- (cl-defmethod jsonrpc-shutdown ((conn naria2-jsonrpc-connection))
- (websocket-close (oref conn -socket)))
- (cl-defmethod jsonrpc-running-p ((conn naria2-jsonrpc-connection))
- (websocket-openp (oref conn -socket)))
- (cl-defmethod jsonrpc-connection-ready-p ((conn naria2-jsonrpc-connection)
- _what)
- (eq (websocket-ready-state (oref conn -socket)) 'open))
- ;;; API
- (defun naria2-jsonrpc-connect (host port &optional secure? secret)
- "Connect to aria2 websocket server on PORT at HOST.
- If SECURE? is non-nil, use secure connection instead of plain one.
- SECRET must be a string if provided, it should match the `--rpc-secret' option
- of remote aria2c instance."
- (cl-labels ((make-message-handler (conn-ref)
- (lambda (_ws frame)
- (let* ((text (websocket-frame-text frame))
- (msg (naria2-jsonrpc-parse-string text))
- (conn (gethash t conn-ref)))
- (jsonrpc-connection-receive conn msg)))))
- (let* ((proto (if secure? "wss" "ws"))
- (base-url (format "%s://%s:%d" proto host port))
- (name (concat "ARIA2 " base-url))
- (conn (make-instance 'naria2-jsonrpc-connection
- :name name
- :notification-dispatcher
- #'naria2-jsonrpc--do-notification-dispatch))
- (weak-ref
- (let ((ht (make-hash-table :weakness 'value :test #'eq)))
- (puthash t conn ht)
- ht))
- (finalizer (lambda ()
- (jsonrpc-shutdown conn))))
- (let* ((handshake-url (concat base-url "/jsonrpc"))
- (socket (websocket-open
- handshake-url
- :on-message (make-message-handler weak-ref))))
- (setf (oref conn -socket) socket)
- (setf (oref conn -secret) secret)
- (setf (oref conn -finalizer) (make-finalizer finalizer)))
- conn)))
- (defun naria2-jsonrpc--do-notification-dispatch (conn event params)
- "Run all handlers of EVENT from CONN with PARAMS."
- (let* ((fullname (symbol-name event))
- (name (string-remove-prefix "aria2." fullname))
- (method-table (oref conn -notification-handlers-table)))
- (dolist (fn (gethash name method-table))
- (funcall fn params))))
- (defun naria2-jsonrpc-on (conn event handler)
- "Listen an EVENT from CONN with HANDLER.
- Return a function run with no arguments to cancel the HANDLER."
- (with-slots ((method-table -notification-handlers-table)) conn
- (cl-callf nconc (gethash event method-table) (list handler))
- (lambda ()
- (cl-callf2 delq handler method-table))))
- (defun naria2-jsonrpc--normalize-callbody (conn method params)
- "Normalize the body of a call via CONN to remote METHOD with PARAMS.
- Return (FINAL-METHOD . FINAL-PARAMS).
- FINAL-METHOD is a string in format `aria2.%s'. where `%s' will be
- replaced by the stringified METHOD.
- FINAL-PARAMS is PARAMS itself by default, but if a secret from CONN
- is provided, a secret token will be inserted at the head position of PARAMS."
- (cl-flet ((stringify (elem)
- (cl-etypecase elem
- (keyword (substring (symbol-name elem) 1))
- (symbol (symbol-name elem))
- (string elem))))
- (let* ((final-method (concat "aria2." (stringify method)))
- (secret (oref conn -secret))
- ;; Tho weired, but see `aria2c(1)'.
- (final-params `[,@(if secret (list (format "token:%s" secret)))
- ,@params]))
- (cons final-method final-params))))
- (defun naria2-jsonrpc-async-call (conn method params &rest args)
- "Call remote aria2 METHOD with PARAMS via CONN asynchronously.
- See `jsonrpc-async-request' for the documentation of ARGS.
- \(fn CONN METHOD PARAMS &key SUCCESS-FN ERROR-FN TIMEOUT-FN TIMEOUT DEFERRED)"
- (pcase-let ((`(,method . ,params)
- (naria2-jsonrpc--normalize-callbody conn method params)))
- (apply #'jsonrpc-async-request conn method params args)))
- (defun naria2-jsonrpc-sync-call (conn method params &rest args)
- "Call remote aria2 METHOD with PARAMS via CONN synchronously.
- See `jsonrpc-request' for the documentation of ARGS.
- \(fn CONN METHOD PARAMS &key DEFERRED TIMEOUT CANCEL-ON-INPUT \
- CANCEL-ON-INPUT-RETVAL)"
- (pcase-let ((`(,method . ,params)
- (naria2-jsonrpc--normalize-callbody conn method params)))
- (apply #'jsonrpc-request conn method params args)))
- (defun naria2-jsonrpc-notify (conn method params)
- "Call remote aria2 METHOD with PARAMS via CONN, don't expect a return value."
- (pcase-let ((`(,method . ,params)
- (naria2-jsonrpc--normalize-callbody conn method params)))
- (jsonrpc-notify conn method params)))
- (provide 'naria2-jsonrpc)
- ;; Local Variables:
- ;; coding: utf-8
- ;; End:
- ;;; naria2-jsonrpc.el ends here
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement