Renamed; added root, config, version calls

This commit is contained in:
Jenga Phoenix 2019-05-24 00:08:19 -05:00
parent d0706840db
commit ea09e38f67
3 changed files with 236 additions and 38 deletions

View File

@ -1,7 +1,7 @@
(defsystem "ipfs-gno"
(defsystem "cl-ipfs-api²"
:version "0.1"
:author "Jaidyn Ann <jadedctrl@teknik.io>"
:license "Cooperative Software License"
:license "AGPLv3"
:depends-on ("drakma" "yason" "arnesi")
:components ((:file "package")
(:file "main")))

247
main.lisp
View File

@ -1,59 +1,240 @@
(in-package :ipfs-gno)
(in-package :cl-ipfs-api2)
(defparameter *api-host* "http://127.0.0.1:5001")
(defparameter *api-root* "/api/v0/")
;; STRING LIST … → (STRING/VARYING RETURN-CODE HEADER-LIST …)
(defun api-call (call arguments &key (method :get) (parameters nil)
(want-stream nil))
(let ((call-url (string+ *api-host* "/api/v0/" call))
(first-arg T))
;; STRING LIST [:LIST :BOOLEAN :SYMBOL] → STRING | HASHTABLE | (NIL STRING)
(defun ipfs-call (call arguments &key (parameters nil) (want-stream nil)
(method :GET))
"Make an IPFS HTTP API call. Quite commonly used.
Some calls return strings/raw data, and others return JSON.
When strings/arbitrary data are recieved, they're returned verbatim.
But, when JSON is returned, it is parsed into a hashtable.
If the JSON is 'error JSON', I.E., it signals that an error has been
recieved, two values are returned: NIL and the string-error-message."
(let ((result
(drakma:http-request
(make-call-url *api-host* *api-root* call arguments)
:method method
:parameters parameters
:want-stream want-stream)))
(cond (want-stream result)
((stringp result) result)
((vectorp result)
(let ((json (yason:parse (flexi-streams:octets-to-string result))))
(cond ((equal "error" (gethash "Type" json))
(values nil (gethash "Message" json)))
('T json)))))))
;; STRING STRING LIST → STRING
(defun make-call-url (host root call arguments)
"Create the URL of an API call, as per the given arguments.
Symbols are assumed to be something like 'T (so boolean), nil likewise.
Arguments should look like this:
(('recursive' nil)('name' 'xabbu'))"
(let ((call-url (string+ host root call))
(first-arg 'T))
(mapcar (lambda (arg-pair)
(format t "FIRST: ~A~%" first-arg)
(if arg-pair
(progn (setq call-url
(string+ call-url (if first-arg "?" "&")
(car arg-pair) "=" (cadr arg-pair)))
(setq first-arg nil))))
(when arg-pair
(setq call-url
(string+
call-url
(if first-arg "?" "&")
(first arg-pair) "="
(cond ((not (second arg-pair))
"false")
((symbolp (second arg-pair))
"true")
('T (second arg-pair))))))
(setq first-arg nil))
arguments)
(drakma:http-request call-url :method method :parameters parameters
:want-stream want-stream)))
call-url))
;; STRING :NUMBER :NUMBER → STRING
;; FORM FORM → FORM
(defmacro bind-api-result (call form)
"Wrap around an #'ipfs-call form; if #'call returns an error, then
return NIL and the error message-- (NIL STRING)-- otherwise, execute
#'form.
Binds the result of the API call to you guessed it, the variable 'result'.
The error message is assigned to 'message', if such a thing exists."
`(multiple-value-bind (result message)
,call
(if message
(values nil message)
,form)))
;; -------------------------------------
;; / CALLS
;; PATHNAME → (HASH-STRING SIZE-NUMBER) || (NIL STRING)
(defun add (pathname &key (pin 't) (only-hash nil))
"Add a file to IPFS, return it's hash.
http://127.0.0.1:8080/ipns/docs.ipfs.io/reference/api/http/#api-v0-add"
(bind-api-result
(ipfs-call "add" `(("pin" ,pin) ("only-hash" ,only-hash))
:method :post :parameters `(("file" . ,pathname)))
(values (gethash "Hash" result)
(parse-integer (gethash "Size" result))
(gethash "Name" result))))
;; STRING :NUMBER :NUMBER → STRING || (NIL STRING)
(defun cat (ipfs-path &key (offset nil) (length nil))
"Return a string of the data at the given IPFS path."
(api-call "cat"
`(("arg" ,ipfs-path)
,(if offset `("offset" ,offset))
,(if length `("length" ,length)))))
"Return a string of the data at the given IPFS path.
/ipns/docs.ipfs.io/reference/api/http/#api-v0-cat"
(bind-api-result
(ipfs-call "cat"
`(("arg" ,ipfs-path)
,(if offset `("offset" ,offset))
,(if length `("length" ,length))))
result))
;; STRING [:BOOLEAN :BOOLEAN] → (LIST LIST LIST LIST) || (NIL STRING)
(defun ls (ipfs-path &key (resolve-type 't) (size 't))
"Returns all sub-objects (IPFS hashes) under a given IPFS/IPNS directory
path. Returns four lists; hashes, sizes, names, types. They are related
positionally-- aka, item #2 in the hash-list, will have a size of #2 in the
size-list, etc.
/ipns/docs.ipfs.io/reference/api/http/#api-v0-ls"
(bind-api-result
(ipfs-call "ls" `(("arg" ,ipfs-path)
("resolve-type" ,resolve-type) ("size" ,size)))
;; STRING PATHNAME --> NIL
(let ((links (gethash "Links"
(car (gethash "Objects" result)))))
(values (mapcar (lambda (link) (gethash "Hash" link)) links)
(mapcar (lambda (link) (gethash "Size" link)) links)
(mapcar (lambda (link) (gethash "Name" link)) links)
(mapcar (lambda (link) (gethash "Type" link)) links)))))
;; STRING PATHNAME → NIL
(defun dl (ipfs-path out-file)
"Write an IPFS file directly to a file on the local file-system.
Non-recursive, in the case of directories.
(Thanks to this thread ) https://stackoverflow.com/a/12607423"
Non-recursive, in the case of directories for now.
(Thanks to this thread : https://stackoverflow.com/a/12607423)
Is a general replacement for the 'get' API call, but actually just uses
the 'cat' call, due to some issues with using 'get'.
Will not actually return NIL when an error is reached (like other functions)
with an error-message, it'lll just write the error JSON to the file.
Whoops."
(with-open-file (out-stream out-file :direction :output
:element-type '(unsigned-byte 8)
:if-exists :overwrite :if-does-not-exist :create)
(let ((in-stream
(api-call "cat" `(("arg" ,ipfs-path)) :want-stream 'T)))
(ipfs-call "cat" `(("arg" ,ipfs-path)) :want-stream 'T)))
(awhile (read-byte in-stream nil nil)
(write-byte it out-stream))
(close in-stream))))
;; -----------------
;; PATHNAME → STRING
(defun add (file-path)
"Add a file to IPFS, return it's hash. Does not work recursively."
(gethash "Hash"
(yason:parse
(flexi-streams:octets-to-string
(api-call "add" '() :method :post
:parameters `(("file" . ,file-path)))))))
;; [STRING] → (STRING LIST STRING STRING STRING)
(defun id (&key (peer-id nil))
"Return info on a node by ID. Has multiple return-values, as follows:
public-key, address-list, agent-version, protocol-version, and peer ID.
The last might be redundant if it was specified as an argument; otherwise,
it tells you your own node's ID.
/ipns/docs.ipfs.io/reference/api/http/#api-v0-id"
(bind-api-result
(ipfs-call "id" `(,(if peer-id (list "arg" peer-id))))
(values (gethash "PublicKey" result)
(gethash "Addresses" result)
(gethash "AgentVersion" result)
(gethash "ProtocolVersion" result)
(gethash "ID" result))))
;; -----------------
;; STRING → (STRING || (NIL STRING)
(defun dns (domain &key (recursive 't))
"Resolve a domain into a path (usually /ipfs/).
http://127.0.0.1:8080/ipns/docs.ipfs.io/reference/api/http/#api-v0-dns"
(bind-api-result
(ipfs-call "dns" `(("arg" ,domain) ("recursive" ,recursive)))
(gethash "Path" result)))
;; STRING [:BOOLEAN :NUMBER :NUMBER] → STRING || (NIL STRING)
(defun resolve (ipfs-path &key (recursive 't) (dht-record-count nil)
(dht-timeout 30))
"Resolve a given name to an IPFS path."
(bind-api-result
(ipfs-call "resolve" `(("arg" ,ipfs-path) ("recursive" ,recursive)
,(if dht-record-count
(list "dht-record-count" dht-record-count))
("dht-timeout" ,(string+ dht-timeout "s"))))
(gethash "Path" result)))
;; -----------------
;; NIL → NIL
(defun shutdown ()
"Shut down the connected IPFS node.
/ipns/docs.ipfs.io/reference/api/http/#api-v0-shutdown"
(ipfs-call "shutdown" '()))
;; -------------------------------------
;; / CONFIG CALLS
;; STRING [:STRING :BOOLEAN :BOOLEAN] → STRING || (NIL STRING)
(defun config (key &key (value nil) (bool nil) (json nil))
"Get/set a config key's value.
http://127.0.0.1:8080/ipns/docs.ipfs.io/reference/api/http/#api-v0-config"
(bind-api-result
(ipfs-call "config" `(("arg" ,key) ,(if value (list "value" value))
("bool" ,bool) ("json" ,json)))
`(,(gethash "Key" result) ,(gethash "Value" result))))
;; NIL → HASH-TABLE
(defun config/show ()
"Return the config file's contents, in a Yason, JSON-parsed hashtable.
Doesn't quite line up with #api-v0-config-show"
(bind-api-result
(ipfs-call "config/show" '())
result))
;; -------------------------------------
;; / VERSION CALLS
;; NIL → (STRING STRING STRING STRING STRING)
(defun version ()
"Return versioning information on this IPFS node
/ipns/docs.ipfs.io/reference/api/http/#api-v0-version"
(bind-api-result
(ipfs-call "version" '())
(values (gethash "Version" result)
(gethash "Commit" result)
(gethash "Repo" result)
(gethash "System" result)
(gethash "Golang" result))))
;; NIL → (STRING STRING STRING STRING)
(defun version/deps ()
"Return info about dependencies used for build.
/ipns/docs.ipfs.io/reference/api/http/#api-v0-version"
(bind-api-result
(ipfs-call "version/deps" '())
(values (gethash "Version" result)
(gethash "Path" result)
(gethash "ReplacedBy" result)
(gethash "Sum" result))))
;; -------------------------------------
;; UTIL
;; STRING-A STRING-B … STRING-N → STRING
(defun string+ (&rest strings)

View File

@ -1,9 +1,26 @@
(defpackage :ipfs-gno
(defpackage :cl-ipfs-api²
(:use :cl :arnesi)
(:nicknames :cl-ipfs :ipfs :cl-ipfs-api2)
(:export
*api-host*
*api-root*
;; / calls
:dl
:cat
:add))
:add
:dns
:id
:ls
:resolve
:shutdown
(in-package :ipfs-gno)
;; / config calls
:config
config/show
;; /version calls
:version
:version/deps))
(in-package :cl-ipfs-api²)