Renamed; added root, config, version calls
This commit is contained in:
parent
d0706840db
commit
ea09e38f67
|
@ -1,7 +1,7 @@
|
||||||
(defsystem "ipfs-gno"
|
(defsystem "cl-ipfs-api²"
|
||||||
:version "0.1"
|
:version "0.1"
|
||||||
:author "Jaidyn Ann <jadedctrl@teknik.io>"
|
:author "Jaidyn Ann <jadedctrl@teknik.io>"
|
||||||
:license "Cooperative Software License"
|
:license "AGPLv3"
|
||||||
:depends-on ("drakma" "yason" "arnesi")
|
:depends-on ("drakma" "yason" "arnesi")
|
||||||
:components ((:file "package")
|
:components ((:file "package")
|
||||||
(:file "main")))
|
(:file "main")))
|
243
main.lisp
243
main.lisp
|
@ -1,59 +1,240 @@
|
||||||
(in-package :ipfs-gno)
|
(in-package :cl-ipfs-api2)
|
||||||
|
|
||||||
(defparameter *api-host* "http://127.0.0.1:5001")
|
(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))
|
|
||||||
(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))))
|
|
||||||
arguments)
|
|
||||||
|
|
||||||
(drakma:http-request call-url :method method :parameters parameters
|
;; 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)))
|
: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 :NUMBER :NUMBER → STRING
|
|
||||||
|
;; 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)
|
||||||
|
(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)
|
||||||
|
call-url))
|
||||||
|
|
||||||
|
|
||||||
|
;; 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))
|
(defun cat (ipfs-path &key (offset nil) (length nil))
|
||||||
"Return a string of the data at the given IPFS path."
|
"Return a string of the data at the given IPFS path.
|
||||||
(api-call "cat"
|
/ipns/docs.ipfs.io/reference/api/http/#api-v0-cat"
|
||||||
|
(bind-api-result
|
||||||
|
(ipfs-call "cat"
|
||||||
`(("arg" ,ipfs-path)
|
`(("arg" ,ipfs-path)
|
||||||
,(if offset `("offset" ,offset))
|
,(if offset `("offset" ,offset))
|
||||||
,(if length `("length" ,length)))))
|
,(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)
|
(defun dl (ipfs-path out-file)
|
||||||
"Write an IPFS file directly to a file on the local file-system.
|
"Write an IPFS file directly to a file on the local file-system.
|
||||||
Non-recursive, in the case of directories.
|
Non-recursive, in the case of directories… for now.
|
||||||
(Thanks to this thread ♥) https://stackoverflow.com/a/12607423"
|
(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
|
(with-open-file (out-stream out-file :direction :output
|
||||||
:element-type '(unsigned-byte 8)
|
:element-type '(unsigned-byte 8)
|
||||||
:if-exists :overwrite :if-does-not-exist :create)
|
:if-exists :overwrite :if-does-not-exist :create)
|
||||||
(let ((in-stream
|
(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)
|
(awhile (read-byte in-stream nil nil)
|
||||||
(write-byte it out-stream))
|
(write-byte it out-stream))
|
||||||
(close in-stream))))
|
(close in-stream))))
|
||||||
|
|
||||||
|
;; -----------------
|
||||||
|
|
||||||
;; PATHNAME → STRING
|
;; [STRING] → (STRING LIST STRING STRING STRING)
|
||||||
(defun add (file-path)
|
(defun id (&key (peer-id nil))
|
||||||
"Add a file to IPFS, return it's hash. Does not work recursively."
|
"Return info on a node by ID. Has multiple return-values, as follows:
|
||||||
(gethash "Hash"
|
public-key, address-list, agent-version, protocol-version, and peer ID.
|
||||||
(yason:parse
|
The last might be redundant if it was specified as an argument; otherwise,
|
||||||
(flexi-streams:octets-to-string
|
it tells you your own node's ID.
|
||||||
(api-call "add" '() :method :post
|
/ipns/docs.ipfs.io/reference/api/http/#api-v0-id"
|
||||||
:parameters `(("file" . ,file-path)))))))
|
(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
|
;; STRING-A STRING-B … STRING-N → STRING
|
||||||
(defun string+ (&rest strings)
|
(defun string+ (&rest strings)
|
||||||
|
|
23
package.lisp
23
package.lisp
|
@ -1,9 +1,26 @@
|
||||||
(defpackage :ipfs-gno
|
(defpackage :cl-ipfs-api²
|
||||||
(:use :cl :arnesi)
|
(:use :cl :arnesi)
|
||||||
|
(:nicknames :cl-ipfs :ipfs :cl-ipfs-api2)
|
||||||
(:export
|
(:export
|
||||||
*api-host*
|
*api-host*
|
||||||
|
*api-root*
|
||||||
|
|
||||||
|
;; / calls
|
||||||
:dl
|
:dl
|
||||||
:cat
|
: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²)
|
||||||
|
|
Ŝarĝante…
Reference in New Issue