├── .gitignore ├── src ├── pb │ ├── diagnostics.proto │ ├── crypto.proto │ ├── copy-protos.sh │ ├── namesys.proto │ ├── spipe.proto │ ├── unixfs.proto │ ├── message.proto │ ├── merkledag.proto │ ├── identify.proto │ └── dht.proto ├── with-ipfs-connection.lisp ├── config.lisp ├── command.lisp ├── package.lisp ├── encoding.lisp ├── request-api.lisp ├── util.lisp └── definitions.json ├── cl-ipfs-api-test.asd ├── cl-ipfs-api.asd ├── LICENSE ├── .travis.yml ├── README.md └── t └── cl-ipfs-api.lisp /.gitignore: -------------------------------------------------------------------------------- 1 | *~ 2 | #* 3 | .#* 4 | -------------------------------------------------------------------------------- /src/pb/diagnostics.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto2"; 2 | package diagnostics.pb; 3 | 4 | message Message { 5 | required string DiagID = 1; 6 | optional bytes Data = 2; 7 | optional int64 Timeout = 3; // in nanoseconds 8 | } 9 | -------------------------------------------------------------------------------- /src/pb/crypto.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto2"; 2 | package crypto.pb; 3 | 4 | enum KeyType { 5 | RSA = 0; 6 | } 7 | 8 | message PublicKey { 9 | required KeyType Type = 1; 10 | required bytes Data = 2; 11 | } 12 | 13 | message PrivateKey { 14 | required KeyType Type = 1; 15 | required bytes Data = 2; 16 | } 17 | -------------------------------------------------------------------------------- /src/pb/copy-protos.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | GO_IPFS_DIR=$GO_ROOT/src/github.com/ipfs/go-ipfs 4 | COPY_DIR=$PWD 5 | 6 | for protoFile in $(find $GO_IPFS_DIR -wholename *pb/*.proto) ; do 7 | cp $protoFile $COPY_DIR 8 | sed -i '1isyntax = "proto2";' $COPY_DIR/$(basename $protoFile) 9 | echo $protoFile 10 | done 11 | -------------------------------------------------------------------------------- /src/pb/namesys.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto2"; 2 | package namesys.pb; 3 | 4 | message IpnsEntry { 5 | enum ValidityType { 6 | // setting an EOL says "this record is valid until..." 7 | EOL = 0; 8 | } 9 | required bytes value = 1; 10 | required bytes signature = 2; 11 | 12 | optional ValidityType validityType = 3; 13 | optional bytes validity = 4; 14 | } 15 | -------------------------------------------------------------------------------- /src/pb/spipe.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto2"; 2 | package spipe.pb; 3 | 4 | message Propose { 5 | optional bytes rand = 1; 6 | optional bytes pubkey = 2; 7 | optional string exchanges = 3; 8 | optional string ciphers = 4; 9 | optional string hashes = 5; 10 | } 11 | 12 | message Exchange { 13 | optional bytes epubkey = 1; 14 | optional bytes signature = 2; 15 | } 16 | -------------------------------------------------------------------------------- /src/with-ipfs-connection.lisp: -------------------------------------------------------------------------------- 1 | (cl:in-package :cl-ipfs-api) 2 | 3 | (defmacro with-ipfs-connection ((&key (scheme *scheme*) (host *host*) (port *port*) (api-path *api-path*) (encoding *encoding*) (user-agent *user-agent*)) &body body) 4 | `(let ((*scheme* ,scheme) 5 | (*host* ,host) 6 | (*port* ,port) 7 | (*api-path* ,api-path) 8 | (*encoding* ,encoding) 9 | (*user-agent* ,user-agent)) 10 | ,@body)) 11 | -------------------------------------------------------------------------------- /src/pb/unixfs.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto2"; 2 | package unixfs.pb; 3 | 4 | message Data { 5 | enum DataType { 6 | Raw = 0; 7 | Directory = 1; 8 | File = 2; 9 | Metadata = 3; 10 | Symlink = 4; 11 | } 12 | 13 | required DataType Type = 1; 14 | optional bytes Data = 2; 15 | optional uint64 filesize = 3; 16 | repeated uint64 blocksizes = 4; 17 | } 18 | 19 | message Metadata { 20 | required string MimeType = 1; 21 | } 22 | -------------------------------------------------------------------------------- /cl-ipfs-api-test.asd: -------------------------------------------------------------------------------- 1 | (cl:in-package #:asdf-user) 2 | 3 | (defsystem :cl-ipfs-api-test 4 | :description "cl-ipfs-api unit tests" 5 | :license "MIT" 6 | :depends-on (:babel :cl-ipfs-api :jonathan :prove :prove-asdf) 7 | :defsystem-depends-on (:prove-asdf) 8 | :components ((:module "t" 9 | :components 10 | ((:test-file "cl-ipfs-api")))) 11 | :perform (test-op :after (op c) 12 | (funcall (intern #.(string :run) :prove) c))) 13 | -------------------------------------------------------------------------------- /src/pb/message.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto2"; 2 | package bitswap.message.pb; 3 | 4 | message Message { 5 | 6 | message Wantlist { 7 | 8 | message Entry { 9 | optional string block = 1; // the block key 10 | optional int32 priority = 2; // the priority (normalized). default to 1 11 | optional bool cancel = 3; // whether this revokes an entry 12 | } 13 | 14 | repeated Entry entries = 1; // a list of wantlist entries 15 | optional bool full = 2; // whether this is the full wantlist. default to false 16 | } 17 | 18 | optional Wantlist wantlist = 1; 19 | repeated bytes blocks = 2; 20 | } 21 | -------------------------------------------------------------------------------- /src/config.lisp: -------------------------------------------------------------------------------- 1 | (cl:in-package #:cl-ipfs-api) 2 | 3 | (defvar *api-path* 4 | "/api/v0" 5 | "Path prefix for all API calls") 6 | 7 | (defvar *user-agent* 8 | (concatenate 'string 9 | "/" (asdf:component-name (asdf:find-system :cl-ipfs-api)) 10 | "/" (asdf:component-version (asdf:find-system :cl-ipfs-api))) 11 | "Identifying user-agent string for API calls") 12 | 13 | (defvar *scheme* 14 | "http" 15 | "Scheme for API calls") 16 | 17 | (defvar *host* 18 | "localhost" 19 | "Hostname for API calls") 20 | 21 | (defvar *port* 22 | 5001 23 | "Port for all API calls") 24 | 25 | (defvar *encoding* 26 | "json" 27 | "Default encoding type") 28 | -------------------------------------------------------------------------------- /cl-ipfs-api.asd: -------------------------------------------------------------------------------- 1 | (cl:in-package #:asdf-user) 2 | 3 | (defsystem :cl-ipfs-api 4 | :version "0.0.1" 5 | :description "A client library for the IPFS API" 6 | :author "Cayman Nava" 7 | :license "MIT" 8 | :depends-on (:alexandria :dexador :fast-io :jonathan :multipart-stream :multipart-vfile-tree :path-string :quri :yason) 9 | :components ((:module "src" 10 | :components 11 | ((:file "package") 12 | (:file "config") 13 | (:file "with-ipfs-connection") 14 | (:file "util") 15 | (:file "encoding") 16 | (:static-file "definitions.json") 17 | (:file "request-api" :depends-on ("config" "util" "encoding")) 18 | (:file "command" :depends-on ("request-api" "definitions.json"))))) 19 | :long-description #.(uiop:read-file-string 20 | (uiop:subpathname *load-pathname* "README.md")) 21 | :in-order-to ((test-op (test-op cl-ipfs-api-test)))) 22 | 23 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2015 Cayman Nava 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in 11 | all copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | THE SOFTWARE. 20 | -------------------------------------------------------------------------------- /src/pb/merkledag.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto2"; 2 | package merkledag.pb; 3 | 4 | import "code.google.com/p/gogoprotobuf/gogoproto/gogo.proto"; 5 | 6 | option (gogoproto.gostring_all) = true; 7 | option (gogoproto.equal_all) = true; 8 | option (gogoproto.verbose_equal_all) = true; 9 | option (gogoproto.goproto_stringer_all) = false; 10 | option (gogoproto.stringer_all) = true; 11 | option (gogoproto.populate_all) = true; 12 | option (gogoproto.testgen_all) = true; 13 | option (gogoproto.benchgen_all) = true; 14 | option (gogoproto.marshaler_all) = true; 15 | option (gogoproto.sizer_all) = true; 16 | option (gogoproto.unmarshaler_all) = true; 17 | 18 | 19 | // An IPFS MerkleDAG Link 20 | message PBLink { 21 | 22 | // multihash of the target object 23 | optional bytes Hash = 1; 24 | 25 | // utf string name. should be unique per object 26 | optional string Name = 2; 27 | 28 | // cumulative size of target object 29 | optional uint64 Tsize = 3; 30 | } 31 | 32 | // An IPFS MerkleDAG Node 33 | message PBNode { 34 | 35 | // refs to other objects 36 | repeated PBLink Links = 2; 37 | 38 | // opaque user data 39 | optional bytes Data = 1; 40 | } 41 | -------------------------------------------------------------------------------- /src/command.lisp: -------------------------------------------------------------------------------- 1 | (cl:in-package #:cl-ipfs-api) 2 | 3 | (defmacro define-command (&key name args kwargs input output description &allow-other-keys) 4 | `(defun ,(make-function-name name) 5 | (,@(make-args-lambda-list args) &key ,@(make-kwarg-list kwargs) want-stream) 6 | ,@(when description (list description)) 7 | (request-api 8 | ,(make-command-name name) 9 | ,(when (not (string= input "stream")) 10 | (make-args-as-list args)) 11 | ,(when (string= input "stream") 12 | (make-args-as-list args)) 13 | (list 14 | ,@(when (string= output "stream") '((cons "encoding" "text"))) 15 | ,@(loop for kwarg in kwargs 16 | collect `(cons ,kwarg ,(string-symbol kwarg)))) 17 | want-stream 18 | ,(when output t)))) 19 | 20 | #.(let ((definitions (jonathan:parse 21 | (alexandria:read-file-into-string 22 | (asdf:component-pathname (asdf:find-component '("cl-ipfs-api" "src" "definitions.json") nil))) 23 | :keyword-normalizer #'string-upcase 24 | :normalize-all t))) 25 | (loop for definition in definitions 26 | collect `(define-command ,@definition) into commands 27 | finally (return `(progn ,@commands)))) 28 | -------------------------------------------------------------------------------- /src/pb/identify.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto2"; 2 | package identify.pb; 3 | 4 | message Identify { 5 | 6 | // protocolVersion determines compatibility between peers 7 | optional string protocolVersion = 5; // e.g. ipfs/1.0.0 8 | 9 | // agentVersion is like a UserAgent string in browsers, or client version in bittorrent 10 | // includes the client name and client. 11 | optional string agentVersion = 6; // e.g. go-ipfs/0.1.0 12 | 13 | // publicKey is this node's public key (which also gives its node.ID) 14 | // - may not need to be sent, as secure channel implies it has been sent. 15 | // - then again, if we change / disable secure channel, may still want it. 16 | optional bytes publicKey = 1; 17 | 18 | // listenAddrs are the multiaddrs the sender node listens for open connections on 19 | repeated bytes listenAddrs = 2; 20 | 21 | // oservedAddr is the multiaddr of the remote endpoint that the sender node perceives 22 | // this is useful information to convey to the other side, as it helps the remote endpoint 23 | // determine whether its connection to the local peer goes through NAT. 24 | optional bytes observedAddr = 4; 25 | 26 | // protocols are the services this node is running 27 | repeated string protocols = 3; 28 | } 29 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: common-lisp 2 | sudo: false 3 | 4 | env: 5 | global: 6 | - PATH=~/.roswell/bin:~/ipfs:$PATH 7 | - ROSWELL_INSTALL_DIR=$HOME/.roswell 8 | matrix: 9 | - LISP=sbcl-bin COVERALLS=true 10 | - LISP=ccl-bin 11 | 12 | install: 13 | # Install and run ipfs 14 | - curl -L https://gobuilder.me/get/github.com/ipfs/go-ipfs/cmd/ipfs/ipfs_master_linux-amd64.zip -o ipfs_master_linux-amd64.zip 15 | - unzip -j ipfs_master_linux-amd64.zip -d ~/ipfs 16 | - ipfs init 17 | - ipfs daemon & 18 | # Install roswell 19 | - curl -L https://raw.githubusercontent.com/snmsts/roswell/release/scripts/install-for-ci.sh | sh 20 | # Install dependencies from HEAD, we need most recent 21 | - git clone https://github.com/fukamachi/dexador ~/lisp/dexador 22 | 23 | cache: 24 | directories: 25 | - $HOME/.roswell 26 | - $HOME/.config/common-lisp 27 | 28 | script: 29 | - ros -s prove -s cl-coveralls 30 | -e '(setf *debugger-hook* 31 | (lambda (c h) 32 | (declare (ignore c h)) 33 | (uiop:quit -1)))' 34 | -e '(ql:quickload :cl-ipfs-api)' 35 | -e '(or (coveralls:with-coveralls (:exclude (list "t")) 36 | (prove:run :cl-ipfs-api-test)) 37 | (uiop:quit -1))' 38 | 39 | notifications: 40 | email: 41 | - caymannava@gmail.com 42 | -------------------------------------------------------------------------------- /src/package.lisp: -------------------------------------------------------------------------------- 1 | (cl:in-package #:cl-user) 2 | 3 | (defpackage #:cl-ipfs-api 4 | (:use #:cl) 5 | (:nicknames #:ipfs-api) 6 | ;; connection macro 7 | (:export #:with-ipfs-connection) 8 | ;; config 9 | (:export #:*api-path* 10 | #:*user-agent* 11 | #:*host* 12 | #:*port* 13 | #:*encoding*) 14 | ;; low level api 15 | (:export #:request-api) 16 | ;; ipfs api 17 | (:export ;; basic commands 18 | #:add 19 | #:cat 20 | #:ls 21 | #:refs 22 | #:refs-local 23 | ;; data structure commands 24 | #:block-stat 25 | #:block-get 26 | #:block-put 27 | #:object-new 28 | #:object-data 29 | #:object-links 30 | #:object-get 31 | #:object-put 32 | #:object-stat 33 | #:object-patch 34 | #:file-ls 35 | ;; advanced commands 36 | #:resolve 37 | #:name-publish 38 | #:name-resolve 39 | #:dns 40 | #:pin-add 41 | #:pin-rm 42 | #:pin-ls 43 | #:repo-gc 44 | ;; network commands 45 | #:id 46 | #:bootstrap 47 | #:bootstrap-add 48 | #:bootstrap-rm 49 | #:swarm-peers 50 | #:swarm-addrs 51 | #:swarm-connect 52 | #:swarm-disconnect 53 | #:swarm-filters 54 | #:swarm-filters-add 55 | #:swarm-filters-rm 56 | #:dht-query 57 | #:dht-findprovs 58 | #:dht-findpeer 59 | #:dht-get 60 | #:dht-put 61 | #:ping 62 | ;; tool commands 63 | #:config 64 | #:config-show 65 | #:config-replace 66 | #:version)) 67 | -------------------------------------------------------------------------------- /src/pb/dht.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto2"; 2 | package dht.pb; 3 | 4 | //run `protoc --go_out=. *.proto` to generate 5 | 6 | message Message { 7 | enum MessageType { 8 | PUT_VALUE = 0; 9 | GET_VALUE = 1; 10 | ADD_PROVIDER = 2; 11 | GET_PROVIDERS = 3; 12 | FIND_NODE = 4; 13 | PING = 5; 14 | } 15 | 16 | enum ConnectionType { 17 | // sender does not have a connection to peer, and no extra information (default) 18 | NOT_CONNECTED = 0; 19 | 20 | // sender has a live connection to peer 21 | CONNECTED = 1; 22 | 23 | // sender recently connected to peer 24 | CAN_CONNECT = 2; 25 | 26 | // sender recently tried to connect to peer repeatedly but failed to connect 27 | // ("try" here is loose, but this should signal "made strong effort, failed") 28 | CANNOT_CONNECT = 3; 29 | } 30 | 31 | message Peer { 32 | // ID of a given peer. 33 | optional string id = 1; 34 | 35 | // multiaddrs for a given peer 36 | repeated bytes addrs = 2; 37 | 38 | // used to signal the sender's connection capabilities to the peer 39 | optional ConnectionType connection = 3; 40 | } 41 | 42 | // defines what type of message it is. 43 | optional MessageType type = 1; 44 | 45 | // defines what coral cluster level this query/response belongs to. 46 | optional int32 clusterLevelRaw = 10; 47 | 48 | // Used to specify the key associated with this message. 49 | // PUT_VALUE, GET_VALUE, ADD_PROVIDER, GET_PROVIDERS 50 | optional string key = 2; 51 | 52 | // Used to return a value 53 | // PUT_VALUE, GET_VALUE 54 | optional Record record = 3; 55 | 56 | // Used to return peers closer to a key in a query 57 | // GET_VALUE, GET_PROVIDERS, FIND_NODE 58 | repeated Peer closerPeers = 8; 59 | 60 | // Used to return Providers 61 | // GET_VALUE, ADD_PROVIDER, GET_PROVIDERS 62 | repeated Peer providerPeers = 9; 63 | } 64 | 65 | // Record represents a dht record that contains a value 66 | // for a key value pair 67 | message Record { 68 | // The key that references this record 69 | optional string key = 1; 70 | 71 | // The actual value this record is storing 72 | optional bytes value = 2; 73 | 74 | // hash of the authors public key 75 | optional string author = 3; 76 | 77 | // A PKI signature for the key+value+author 78 | optional bytes signature = 4; 79 | } 80 | -------------------------------------------------------------------------------- /src/encoding.lisp: -------------------------------------------------------------------------------- 1 | (cl:in-package #:cl-ipfs-api) 2 | 3 | (defclass encoding () 4 | ((%name :accessor encoding-name :initarg :name))) 5 | 6 | (defgeneric parse (encoding command stream)) 7 | (defgeneric encode (encoding command stream)) 8 | 9 | 10 | (defclass json-encoding (encoding) 11 | ((%name :initform "json"))) 12 | 13 | (defmethod parse ((encoding json-encoding) command (stream string)) 14 | (let ((stream (make-string-input-stream stream))) 15 | (parse encoding command stream))) 16 | 17 | (defmethod parse ((encoding json-encoding) command (stream stream)) 18 | (let ((value (loop for json = (handler-case 19 | (yason:parse stream :object-as :alist) 20 | (error (c) c)) 21 | while (not (or (null json) (typep json 'error))) 22 | collect json))) 23 | (if (= 1 (length value)) 24 | (car value) 25 | value))) 26 | 27 | (defclass text-encoding (encoding) 28 | ((%name :initform "text"))) 29 | 30 | (defmethod parse ((encoding text-encoding) command stream) 31 | stream) 32 | 33 | ; TODO: rename this encoding 34 | (defmethod parse ((encoding text-encoding) command (stream chunga:chunked-input-stream)) 35 | (loop for buffer = (make-array 1024 :element-type '(unsigned-byte 8)) 36 | for n of-type fixnum = (read-sequence buffer stream) 37 | until (zerop n) 38 | collect (if (= 1024 n) 39 | buffer 40 | (subseq buffer 0 n)) into pieces 41 | finally (return (apply #'concatenate '(vector (unsigned-byte 8)) pieces)))) 42 | 43 | (defmethod parse ((encoding text-encoding) command (stream stream)) 44 | (loop for line = (handler-case 45 | (read-line stream) 46 | (error nil nil)) 47 | while (not (null line)) 48 | collect line)) 49 | 50 | (defclass bin-encoding (encoding) 51 | ((%name :initform "text"))) 52 | 53 | (defmethod parse ((encoding bin-encoding) command stream) 54 | (fast-io:with-fast-input (buffer stream) 55 | buffer)) 56 | 57 | (defclass protobuf-encoding (encoding) 58 | ((%name :initform "protobuf"))) 59 | 60 | (defclass xml-encoding (encoding) 61 | ((%name :initform "xml"))) 62 | 63 | 64 | (defparameter *encodings* 65 | `(("json" . ,(make-instance 'json-encoding)) 66 | ("text" . ,(make-instance 'text-encoding)) 67 | ("bin" . ,(make-instance 'bin-encoding)) 68 | ("protobuf" . ,(make-instance 'protobuf-encoding)) 69 | ("xml" . ,(make-instance 'xml-encoding))) 70 | "Alist of supported IPFS Encodings") 71 | 72 | (defun get-encoding (encoding) 73 | (alexandria:if-let ((decoder (cdr (assoc encoding *encodings* :test #'equal)))) 74 | decoder 75 | (error "No encoding found"))) 76 | -------------------------------------------------------------------------------- /src/request-api.lisp: -------------------------------------------------------------------------------- 1 | (cl:in-package #:cl-ipfs-api) 2 | 3 | (defun request-api (command args files opts want-stream use-stream) 4 | (flet ((handle-request (uri headers content encoding) 5 | (handler-case 6 | (multiple-value-bind (body status headers uri connection) 7 | (dex:request uri 8 | :method (if content 9 | :post 10 | :get) 11 | :content content 12 | :headers headers 13 | :use-connection-pool nil 14 | :want-stream (or 15 | want-stream 16 | nil)) 17 | ;cuse-stream)) 18 | (declare (ignore status uri)) 19 | (close connection) 20 | (let ((content-type (gethash "content-type" headers)) 21 | (stream-output-p (gethash "x-stream-output" headers))) 22 | (cond (want-stream 23 | body) 24 | (content-type 25 | (parse encoding command body)) 26 | (use-stream 27 | (if stream-output-p 28 | (parse (get-encoding "text") command body) 29 | (parse encoding command body))) 30 | (t 31 | body)))) 32 | (dex:http-request-failed (e) 33 | (error (if (or want-stream use-stream) 34 | (car (parse (if (cdr (assoc "encoding" opts :test #'string=)) 35 | encoding 36 | (get-encoding "text")) 37 | command (dexador.error:response-body e))) 38 | (dexador.error:response-body e))))))) 39 | (declare (inline handle-request)) 40 | (let* ((file-p files) 41 | (path (concatenate 'string *api-path* command)) 42 | (encoding (get-encoding (or 43 | (cdr (assoc "encoding" opts :test #'string=)) 44 | *encoding*))) 45 | (uri (quri:make-uri :scheme *scheme* :host *host* :port *port* :path path)) 46 | (headers `(("User-Agent" . ,*user-agent*) 47 | ("Connection" . "keep-alive"))) 48 | (opts `(("stream-channels" . "true") 49 | ,@(remove nil opts :key #'cdr)))) 50 | (if file-p 51 | (let* ((content (get-contents files opts)) 52 | (boundary (multipart-stream:make-boundary)) 53 | (headers `(("Content-Disposition" . "form-data: name=\"files\"") 54 | ("Content-Type" . ,(format nil "multipart/form-data; boundary=~A" boundary)) 55 | ,@headers))) 56 | (mapc (lambda (file) 57 | (setf (multipart-top-level-p file) t)) 58 | content) 59 | (setf (quri:uri-query-params uri) opts) 60 | (with-open-stream (content (apply #'multipart-stream:make-multipart-stream boundary content)) 61 | (handle-request uri headers content encoding))) 62 | (let ((opts (append (args-to-opts args) opts))) 63 | (setf (quri:uri-query-params uri) opts) 64 | (handle-request uri headers nil encoding)))))) 65 | -------------------------------------------------------------------------------- /src/util.lisp: -------------------------------------------------------------------------------- 1 | (cl:in-package #:cl-ipfs-api) 2 | 3 | ;;; define-command utils 4 | 5 | (defun make-function-name (list) 6 | (alexandria:symbolicate 7 | (format nil "~{~A~^-~}" (mapcar #'string-upcase list)))) 8 | 9 | (defun make-command-name (list) 10 | (format nil "~{/~A~}" list)) 11 | 12 | (defun make-args-lambda-list (args) 13 | (loop for arg in args 14 | collect (string-symbol 15 | (cdr (assoc :name (alexandria:plist-alist arg)))))) 16 | 17 | (defun make-args-as-list (args) 18 | (let ((args (loop for arg in args 19 | collect (alexandria:plist-alist arg))) 20 | (some-variadic (loop for arg in args 21 | if (cdr (assoc :variadic args)) 22 | return t))) 23 | (cond 24 | ((= 0 (length args)) 25 | nil) 26 | ((= 1 (length args)) 27 | (string-symbol 28 | (cdr (assoc :name (car args))))) 29 | ((not some-variadic) 30 | `(list ,@(loop for arg in args 31 | collect (string-symbol 32 | (cdr (assoc :name arg)))))) 33 | (t 34 | `(concatenate 'list 35 | ,@(loop for arg in args 36 | if (cdr (assoc :variadic arg)) 37 | collect `(if (consp ,(string-symbol 38 | (cdr (assoc :name arg)))) 39 | ,(string-symbol 40 | (cdr (assoc :name arg))) 41 | (list ,(string-symbol 42 | (cdr (assoc :name arg))))) 43 | else 44 | collect `(list ,(string-symbol 45 | (cdr (assoc :name arg)))))))))) 46 | 47 | 48 | (defun make-kwarg-list (kwargs) 49 | (loop for kwarg in kwargs 50 | if (equal kwarg "encoding") 51 | collect (list (string-symbol kwarg) '*encoding*) 52 | else 53 | collect (string-symbol kwarg))) 54 | 55 | (defun string-symbol (string) 56 | (alexandria:symbolicate (string-upcase string))) 57 | 58 | ;;; request-api utils 59 | 60 | (defun string-keyword (string) 61 | (unless (null string) 62 | (alexandria:make-keyword (string-upcase string)))) 63 | 64 | (defun symbol-downcase (keyword) 65 | (string-downcase (symbol-name keyword))) 66 | 67 | (defun make-random-string () 68 | (format nil "~X~X~X~X~X~X" (random 4096) (random 4096) (random 4096) (random 4096) (random 4096) (random 4096))) 69 | 70 | (defclass ipfs-multipart-node (multipart-vfile-tree:multipart-vfile-node) 71 | ((%top-level-p 72 | :initarg :multipart-top-level-p 73 | :accessor multipart-top-level-p 74 | :initform nil))) 75 | 76 | (defclass ipfs-multipart-directory-node (multipart-vfile-tree:multipart-vfile-directory-node ipfs-multipart-node) ()) 77 | 78 | (defmethod multipart-stream:multipart-headers ((n ipfs-multipart-directory-node)) 79 | (list 80 | `("Content-Disposition" . ,(format nil "~A; filename=~S" 81 | (if (multipart-top-level-p n) 82 | "form-data; name=\"file\"" 83 | "file") 84 | (multipart-vfile-tree::clean-path n))) 85 | `("Content-Type" . ,(format nil "multipart/mixed; boundary=~A" (multipart-vfile-tree:multipart-vfile-boundary n))))) 86 | 87 | (defun get-contents (args opts) 88 | (labels ((make-multipart-dummy-file (item) 89 | (let ((filename (path-string:join "/tmp" (make-random-string)))) 90 | (make-instance 'multipart-vfile-tree:multipart-vfile-node 91 | :path filename 92 | :base "/tmp" 93 | :contents item))) 94 | (make-multipart (item recurse-p) 95 | (typecase item 96 | (pathname 97 | (multipart-vfile-tree:make-multipart-vfile-tree (namestring item) :recurse-p recurse-p :file-class 'ipfs-multipart-node :directory-class 'ipfs-multipart-directory-node)) 98 | (string 99 | (if (or (uiop:file-exists-p item) (uiop:directory-exists-p item)) 100 | (multipart-vfile-tree:make-multipart-vfile-tree item :recurse-p recurse-p :file-class 'ipfs-multipart-node :directory-class 'ipfs-multipart-directory-node) 101 | (make-multipart-dummy-file item))) 102 | (otherwise 103 | (make-multipart-dummy-file item))))) 104 | (let ((recursive-p (or (assoc "r" opts :test #'string=) 105 | (assoc "recursive" opts :test #'string=)))) 106 | (if (listp args) 107 | (mapcar (lambda (arg) 108 | (make-multipart arg recursive-p)) 109 | args) 110 | (list (make-multipart args recursive-p)))))) 111 | 112 | (defun args-to-opts (args) 113 | (cond ((null args) 114 | nil) 115 | ((atom args) 116 | `(("arg" . ,args))) 117 | (t 118 | (loop for arg in args 119 | if (not (null arg)) 120 | collect (cons "arg" arg))))) 121 | 122 | (defun content-body-args-p (args) 123 | (flet ((content-body-arg-p (arg) 124 | (or (pathnamep arg) 125 | (subtypep (type-of arg) '(vector (unsigned-byte 8)))))) 126 | (if (atom args) 127 | (content-body-arg-p args) 128 | (some #'content-body-arg-p args)))) 129 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # cl-ipfs-api 2 | [![Build Status](https://travis-ci.org/wemeetagain/cl-ipfs-api.svg?branch=master)](https://travis-ci.org/wemeetagain/cl-ipfs-api) 3 | [![Coverage Status](https://coveralls.io/repos/wemeetagain/cl-ipfs-api/badge.svg?branch=master&service=github)](https://coveralls.io/github/wemeetagain/cl-ipfs-api?branch=master) 4 | 5 | ![](https://ipfs.io/ipfs/QmQJ68PFMDdAsgCZvA1UVzzn18asVcf7HVvCDgpjiSCAse) 6 | 7 | > A client library for the IPFS API. 8 | 9 | ## Examples 10 | 11 | ## Documentation 12 | 13 | ### [CL-IPFS-API](#CL-IPFS-API) (IPFS-API) 14 | 15 | ##### [special] [\***USER-AGENT**\*](#CL-IPFS-API:*USER-AGENT*) 16 | 17 | Identifying user-agent string for API calls 18 | 19 | ##### [special] [\***HOST**\*](#CL-IPFS-API:*HOST*) 20 | 21 | Hostname for API calls 22 | 23 | ##### [special] [\***ENCODING**\*](#CL-IPFS-API:\*ENCODING\*) 24 | 25 | Default encoding type 26 | 27 | ##### [special] [\***PORT**\*](#CL-IPFS-API:*PORT*) 28 | 29 | Port for all API calls 30 | 31 | ##### [special] [\***API-PATH**\*](#CL-IPFS-API:*API-PATH*) 32 | 33 | Path prefix for all API calls 34 | 35 | ##### [macro] [**WITH-IPFS-CONNECTION**](#CL-IPFS-API:WITH-IPFS-CONNECTION) (&REST ARGS) 36 | 37 | 38 | ##### [function] [**ADD**](#CL-IPFS-API:ADD) (PATH &KEY RECURSIVE QUIET PROGRESS TRICKLE ONLY-HASH WRAP-WITH-DIRECTORY HIDDEN CHUNKER TIMEOUT WANT-STREAM) 39 | 40 | Add an object to ipfs. 41 | 42 | ##### [function] [**CAT**](#CL-IPFS-API:CAT) (IPFS-PATH &KEY TIMEOUT WANT-STREAM) 43 | 44 | Show IPFS object data. 45 | 46 | ##### [function] [**LS**](#CL-IPFS-API:LS) (IPFS-PATH &KEY HEADERS (ENCODING \*ENCODING\*) TIMEOUT WANT-STREAM) 47 | 48 | List links from an object. 49 | 50 | ##### [function] [**FILE-LS**](#CL-IPFS-API:FILE-LS) (IPFS-PATH &KEY (ENCODING \*ENCODING\*) TIMEOUT WANT-STREAM) 51 | 52 | List directory contents for Unix-filesystem objects. 53 | 54 | ##### [function] [**BLOCK-GET**](#CL-IPFS-API:BLOCK-GET) (KEY &KEY TIMEOUT WANT-STREAM) 55 | 56 | Get a raw IPFS block 57 | 58 | ##### [function] [**BLOCK-PUT**](#CL-IPFS-API:BLOCK-PUT) (DATA &KEY (ENCODING \*ENCODING\*) TIMEOUT WANT-STREAM) 59 | 60 | Store input as an IPFS block. 61 | 62 | ##### [function] [**BLOCK-STAT**](#CL-IPFS-API:BLOCK-STAT) (KEY &KEY (ENCODING \*ENCODING\*) TIMEOUT WANT-STREAM) 63 | 64 | Print information of a raw IPFS block. 65 | 66 | ##### [function] [**OBJECT-GET**](#CL-IPFS-API:OBJECT-GET) (KEY &KEY (ENCODING \*ENCODING\*) TIMEOUT WANT-STREAM) 67 | 68 | Get and serialize the DAG node named by KEY. 69 | 70 | ##### [function] [**OBJECT-PUT**](#CL-IPFS-API:OBJECT-PUT) (DATA &KEY INPUTENC (ENCODING \*ENCODING\*) TIMEOUT WANT-STREAM) 71 | 72 | stores input as a DAG object, outputs its key. 73 | 74 | ##### [function] [**OBJECT-STAT**](#CL-IPFS-API:OBJECT-STAT) (KEY &KEY (ENCODING \*ENCODING\*) TIMEOUT WANT-STREAM) 75 | 76 | Get stats for the DAG node named by KEY. 77 | 78 | ##### [function] [**OBJECT-DATA**](#CL-IPFS-API:OBJECT-DATA) (KEY &KEY TIMEOUT WANT-STREAM) 79 | 80 | Output the raw bytes in an IPFS object. 81 | 82 | ##### [function] [**OBJECT-LINKS**](#CL-IPFS-API:OBJECT-LINKS) (KEY &KEY (ENCODING \*ENCODING\*) TIMEOUT WANT-STREAM) 83 | 84 | Output the links pointed to by the specified object. 85 | 86 | ##### [function] [**OBJECT-NEW**](#CL-IPFS-API:OBJECT-NEW) (TEMPLATE &KEY (ENCODING \*ENCODING\*) TIMEOUT WANT-STREAM) 87 | 88 | Create a new object from an ipfs template. 89 | 90 | ##### [function] [**OBJECT-PATCH**](#CL-IPFS-API:OBJECT-PATCH) (ROOT COMMAND ARGS &KEY CREATE (ENCODING \*ENCODING\*) TIMEOUT WANT-STREAM) 91 | 92 | Create a new DAG node based on an existing one. 93 | 94 | ##### [function] [**PIN-ADD**](#CL-IPFS-API:PIN-ADD) (IPFS-PATH &KEY RECURSIVE (ENCODING \*ENCODING\*) TIMEOUT WANT-STREAM) 95 | 96 | Pin objects to local storage. 97 | 98 | ##### [function] [**PIN-RM**](#CL-IPFS-API:PIN-RM) (IPFS-PATH &KEY RECURSIVE (ENCODING \*ENCODING\*) TIMEOUT WANT-STREAM) 99 | 100 | Unpin an object from local storage. 101 | 102 | ##### [function] [**PIN-LS**](#CL-IPFS-API:PIN-LS) (&KEY TYPE COUNT QUIET (ENCODING \*ENCODING\*) TIMEOUT WANT-STREAM) 103 | 104 | List objects pinned to local storage. 105 | 106 | ##### [function] [**REPO-GC**](#CL-IPFS-API:REPO-GC) (&KEY QUIET (ENCODING \*ENCODING\*) TIMEOUT WANT-STREAM) 107 | 108 | Perform a garbage collection sweep on non-pinned objects. 109 | 110 | ##### [function] [**SWARM-ADDRS**](#CL-IPFS-API:SWARM-ADDRS) (&KEY (ENCODING \*ENCODING\*) TIMEOUT WANT-STREAM) 111 | 112 | List known addresses. 113 | 114 | ##### [function] [**SWARM-PEERS**](#CL-IPFS-API:SWARM-PEERS) (&KEY (ENCODING \*ENCODING\*) TIMEOUT WANT-STREAM) 115 | 116 | List peers with open connections. 117 | 118 | ##### [function] [**SWARM-CONNECT**](#CL-IPFS-API:SWARM-CONNECT) (ADDRESS &KEY (ENCODING \*ENCODING\*) TIMEOUT WANT-STREAM) 119 | 120 | Open connection to a given address. 121 | 122 | ##### [function] [**SWARM-DISCONNECT**](#CL-IPFS-API:SWARM-DISCONNECT) (ADDRESS &KEY (ENCODING \*ENCODING\*) TIMEOUT WANT-STREAM) 123 | 124 | Close connection to a given address. 125 | 126 | ##### [function] [**SWARM-FILTERS**](#CL-IPFS-API:SWARM-FILTERS) (&KEY (ENCODING \*ENCODING\*) TIMEOUT WANT-STREAM) 127 | 128 | List currently applied filters. 129 | 130 | ##### [function] [**SWARM-FILTERS-ADD**](#CL-IPFS-API:SWARM-FILTERS-ADD) (ADDRESS &KEY (ENCODING \*ENCODING\*) TIMEOUT WANT-STREAM) 131 | 132 | Add an address filter. 133 | 134 | ##### [function] [**SWARM-FILTERS-RM**](#CL-IPFS-API:SWARM-FILTERS-RM) (ADDRESS &KEY (ENCODING \*ENCODING\*) TIMEOUT WANT-STREAM) 135 | 136 | Remove an address filter. 137 | 138 | ##### [function] [**BOOTSTRAP**](#CL-IPFS-API:BOOTSTRAP) (&KEY (ENCODING \*ENCODING\*) TIMEOUT WANT-STREAM) 139 | 140 | Show peers in the bootstrap list. 141 | 142 | ##### [function] [**BOOTSTRAP-ADD**](#CL-IPFS-API:BOOTSTRAP-ADD) (PEERS &KEY DEFAULT (ENCODING \*ENCODING\*) TIMEOUT WANT-STREAM) 143 | 144 | Add peers to the bootstrap list. 145 | 146 | ##### [function] [**BOOTSTRAP-RM**](#CL-IPFS-API:BOOTSTRAP-RM) (PEERS &KEY ALL (ENCODING \*ENCODING\*) TIMEOUT WANT-STREAM) 147 | 148 | Remove peers from the bootstrap list. 149 | 150 | ##### [function] [**REFS**](#CL-IPFS-API:REFS) (IPFS-PATH &KEY FORMAT EDGES UNIQUE RECURSIVE (ENCODING \*ENCODING\*) TIMEOUT WANT-STREAM) 151 | 152 | List links (references) from an object. 153 | 154 | ##### [function] [**REFS-LOCAL**](#CL-IPFS-API:REFS-LOCAL) (&KEY (ENCODING \*ENCODING\*) TIMEOUT WANT-STREAM) 155 | 156 | List all local references. 157 | 158 | ##### [function] [**RESOLVE**](#CL-IPFS-API:RESOLVE) (NAME &KEY RECURSIVE (ENCODING \*ENCODING\*) TIMEOUT WANT-STREAM) 159 | 160 | Resolve the value of names to IPFS. 161 | 162 | ##### [function] [**NAME-PUBLISH**](#CL-IPFS-API:NAME-PUBLISH) (NAME IPFS-PATH &KEY (ENCODING \*ENCODING\*) TIMEOUT WANT-STREAM) 163 | 164 | Publish an object to IPNS. 165 | 166 | ##### [function] [**NAME-RESOLVE**](#CL-IPFS-API:NAME-RESOLVE) (NAME &KEY RECURSIVE WANT-STREAM) 167 | 168 | Get the value currently published at an IPNS name. 169 | 170 | ##### [function] [**DNS**](#CL-IPFS-API:DNS) (DOMAIN-NAME &KEY RECURSIVE (ENCODING \*ENCODING\*) TIMEOUT WANT-STREAM) 171 | 172 | DNS link resolver. 173 | 174 | 175 | ##### [function] [**DHT-QUERY**](#CL-IPFS-API:DHT-QUERY) (PEER-ID &KEY VERBOSE (ENCODING \*ENCODING\*) TIMEOUT WANT-STREAM) 176 | 177 | Run a 'FindClosestPeers' query through the DHT. 178 | 179 | ##### [function] [**DHT-GET**](#CL-IPFS-API:DHT-GET) (KEY &KEY VERBOSE (ENCODING \*ENCODING\*) TIMEOUT WANT-STREAM) 180 | 181 | Run a 'GetValue' query through the DHT. 182 | 183 | ##### [function] [**DHT-PUT**](#CL-IPFS-API:DHT-PUT) (KEY VALUE &KEY VERBOSE (ENCODING \*ENCODING\*) TIMEOUT WANT-STREAM) 184 | 185 | Run a 'PutValue' query through the DHT. 186 | 187 | ##### [function] [**DHT-FINDPEER**](#CL-IPFS-API:DHT-FINDPEER) (PEER-ID &KEY (ENCODING \*ENCODING\*) TIMEOUT WANT-STREAM) 188 | 189 | Run a 'FindPeer' query through the DHT. 190 | 191 | ##### [function] [**DHT-FINDPROVS**](#CL-IPFS-API:DHT-FINDPROVS) (KEY &KEY VERBOSE (ENCODING \*ENCODING\*) TIMEOUT WANT-STREAM) 192 | 193 | Run a 'FindProviders' query through the DHT. 194 | 195 | ##### [function] [**PING**](#CL-IPFS-API:PING) (PEER-ID &KEY (ENCODING \*ENCODING\*) TIMEOUT WANT-STREAM) 196 | 197 | Send echo request packets to IPFS hosts. 198 | 199 | ##### [function] [**CONFIG**](#CL-IPFS-API:CONFIG) (KEY VALUE &KEY BOOL JSON (ENCODING \*ENCODING\*) TIMEOUT WANT-STREAM) 200 | 201 | Get and set IPFS config values. 202 | 203 | ##### [function] [**CONFIG-SHOW**](#CL-IPFS-API:CONFIG-SHOW) (&KEY TIMEOUT WANT-STREAM) 204 | 205 | Outputs the content of the config file. 206 | 207 | ##### [function] [**CONFIG-REPLACE**](#CL-IPFS-API:CONFIG-REPLACE) (FILE &KEY (ENCODING \*ENCODING\*) TIMEOUT WANT-STREAM) 208 | 209 | Replace the config with FILE. 210 | 211 | ##### [function] [**ID**](#CL-IPFS-API:ID) (&KEY FORMAT (ENCODING \*ENCODING\*) TIMEOUT WANT-STREAM) 212 | 213 | Show IPFS Node ID information 214 | 215 | ##### [function] [**VERSION**](#CL-IPFS-API:VERSION) (&KEY NUMBER (ENCODING \*ENCODING\*) TIMEOUT WANT-STREAM) 216 | 217 | Show IPFS version information. 218 | 219 | ## Testing 220 | 221 | ```lisp 222 | (asdf:test-system :cl-ipfs-api) 223 | ``` 224 | 225 | ## License 226 | 227 | MIT 228 | -------------------------------------------------------------------------------- /t/cl-ipfs-api.lisp: -------------------------------------------------------------------------------- 1 | (cl:in-package #:cl-user) 2 | 3 | (defpackage #:cl-ipfs-api-test 4 | (:use #:cl #:prove)) 5 | (in-package #:cl-ipfs-api-test) 6 | 7 | (defun ipfs-command (name args opts) 8 | (let ((output (uiop:run-program (concatenate 9 | 'string 10 | "ipfs " 11 | (format nil "~{~A ~}" name) 12 | (format nil "~:{--~A ~A ~}" opts) 13 | (format nil "~{~A ~}" (mapcar (lambda (x) 14 | (if (null x) 15 | "" 16 | x)) 17 | args))) 18 | :output :string))) 19 | (if (string= (or (cadr (assoc "encoding" opts :test #'string=)) 20 | "") 21 | "json") 22 | (jonathan:parse output :as :alist) 23 | output))) 24 | 25 | (defvar *test-dir* "testdir/") 26 | (defvar *test-file* "file1.txt") 27 | (defvar *test-file-data* "hello test") 28 | (defvar *test-dir-path* (pathname *test-dir*)) 29 | (defvar *test-file-path* (pathname (concatenate 'string 30 | *test-dir* 31 | *test-file*))) 32 | (defvar *test-file-hash* "QmRv6FrRUqB8WkSn5FGa4QrsmBvyKTPrGamF43vYEAmPo4") 33 | (defvar *test-dir-hash* "QmNXT2U3VybnKDwwBSKWDwsLdyA1EQLYa6CVnPSTDSb9XA") 34 | 35 | (defun make-test-data () 36 | (uiop:run-program (concatenate 'string 37 | "mkdir -p " 38 | *test-dir*)) 39 | (uiop:run-program (concatenate 'string 40 | "printf " 41 | "\"" *test-file-data* "\"" 42 | " >" 43 | *test-dir* 44 | *test-file*))) 45 | 46 | (defun clean-test-data () 47 | (uiop:run-program (concatenate 'string 48 | "rm -r " 49 | *test-dir*))) 50 | 51 | (defun make-ipfs-name (name) 52 | (intern (format nil "~{~A~^-~}" 53 | (loop for token in name 54 | collect (string-upcase token))) 55 | 'cl-ipfs-api)) 56 | 57 | (defun make-ipfs-opts (opts) 58 | (loop for (k v) in opts 59 | append (list (alexandria:make-keyword (string-upcase k)) v))) 60 | 61 | ;;; expect the same output from the ipfs cli and cl-ipfs-api 62 | (defun is-api-cli (name args opts) 63 | (is (apply (make-ipfs-name name) `(,@args ,@(make-ipfs-opts opts))) 64 | (ipfs-command name args opts))) 65 | 66 | (plan nil) 67 | 68 | (subtest "init tests" 69 | (make-test-data)) 70 | 71 | (subtest "command expansion" 72 | ;; test normal 73 | (is-expand (cl-ipfs-api::define-command 74 | :name ("test" "command") 75 | :args ((:name "data" :required t)) 76 | :kwargs ("encoding") 77 | :description "test description") 78 | (defun test-command (data &key (encoding cl-ipfs-api:*encoding*) cl-ipfs-api::want-stream) 79 | "test description" 80 | (cl-ipfs-api:request-api "/test/command" 81 | data 82 | nil 83 | (list (cons "encoding" encoding)) 84 | cl-ipfs-api::want-stream 85 | nil))) 86 | ;; variadic args 87 | (is-expand (cl-ipfs-api::define-command 88 | :name ("test" "command") 89 | :args ((:name "data" :required nil)) 90 | :kwargs ("encoding") 91 | :description "test description") 92 | (defun test-command (data &key (encoding cl-ipfs-api:*encoding*) cl-ipfs-api::want-stream) 93 | "test description" 94 | (cl-ipfs-api:request-api "/test/command" 95 | data 96 | nil 97 | (list (cons "encoding" encoding)) 98 | cl-ipfs-api::want-stream 99 | nil))) 100 | ;; stream output 101 | (is-expand (cl-ipfs-api::define-command 102 | :name ("test" "command") 103 | :args ((:name "data" :required t)) 104 | :kwargs () 105 | :description "test description" 106 | :output "stream") 107 | (defun test-command (data &key cl-ipfs-api::want-stream) 108 | "test description" 109 | (cl-ipfs-api:request-api "/test/command" 110 | data 111 | nil 112 | (list (cons "encoding" "text")) 113 | cl-ipfs-api::want-stream 114 | t)))) 115 | 116 | (subtest "add" 117 | (is (cdr (assoc "Hash" (car (cl-ipfs-api:add *test-file-path*)) :test #'string=)) 118 | (cadr (split-sequence:split-sequence #\Space (ipfs-command '("add") `(,*test-file-path*) nil))))) 119 | 120 | (subtest "cat" 121 | (is (cl-ipfs-api:cat *test-file-hash*) 122 | (ipfs-command '("cat") `(,*test-file-hash*) nil))) 123 | 124 | (subtest "ls" 125 | (is-api-cli '("ls") `(,*test-dir-hash*) '(("encoding" "json"))) 126 | (is-api-cli '("ls") `(,*test-dir-hash*) '(("encoding" "text")))) 127 | 128 | (subtest "refs" 129 | (is (cl-ipfs-api:refs *test-dir-hash* :encoding "json") 130 | (ipfs-command '("refs") `(,*test-dir-hash*) '(("encoding" "json"))))) 131 | 132 | (subtest "refs local" 133 | (is (cl-ipfs-api:refs-local :encoding "text") 134 | (ipfs-command '("refs" "local") nil nil))) 135 | 136 | (subtest "block stat" 137 | (is-api-cli '("block" "stat") `(,*test-file-hash*) '(("encoding" "json"))) 138 | (is-api-cli '("block" "stat") `(,*test-file-hash*) '(("encoding" "text")))) 139 | 140 | (subtest "block get" 141 | (is (cl-ipfs-api:block-get *test-file-hash*) 142 | (ipfs-command '("block" "get") `(,*test-file-hash*) nil))) 143 | 144 | (subtest "block put" 145 | (is-api-cli '("block" "put") `(,*test-file-path*) '(("encoding" "json"))) 146 | (is-api-cli '("block" "put") `(,*test-file-path*) '(("encoding" "text")))) 147 | 148 | (subtest "object new" 149 | (is (cl-ipfs-api:object-new nil :encoding "json") 150 | (ipfs-command '("object" "new") nil '(("encoding" "json")))) 151 | (is (cl-ipfs-api:object-new nil :encoding "text") 152 | (ipfs-command '("object" "new") nil '(("encoding" "text"))))) 153 | 154 | (subtest "object data" 155 | (is (cl-ipfs-api:object-data *test-file-hash*) 156 | (ipfs-command '("object" "data") `(,*test-file-hash*) nil))) 157 | 158 | (subtest "object links" 159 | (is-api-cli '("object" "links") `(,*test-dir-hash*) '(("encoding" "json"))) 160 | (is-api-cli '("object" "links") `(,*test-dir-hash*) '(("encoding" "text")))) 161 | 162 | (subtest "object get" 163 | (is-api-cli '("object" "get") `(,*test-file-hash*) '(("encoding" "json")))) 164 | 165 | ;(subtest "object put") 166 | 167 | (subtest "object stat" 168 | (is-api-cli '("object" "stat") `(,*test-file-hash*) '(("encoding" "json"))) 169 | (is-api-cli '("object" "stat") `(,*test-file-hash*) '(("encoding" "text")))) 170 | 171 | ;(subtest "object patch") 172 | 173 | (subtest "file ls" 174 | (is-api-cli '("file" "ls") `(,*test-dir-hash*) '(("encoding" "json"))) 175 | (is-api-cli '("file" "ls") `(,*test-dir-hash*) '(("encoding" "text")))) 176 | 177 | ;(subtest "resolve") 178 | 179 | ;(subtest "name publish") 180 | 181 | ;(subtest "name resolve") 182 | 183 | (subtest "dns" 184 | (is-api-cli '("dns") '("ipfs.io") '(("encoding" "json"))) 185 | (is-api-cli '("dns") '("ipfs.io") '(("encoding" "text")))) 186 | 187 | (subtest "pin add" 188 | (ignore-errors (cl-ipfs-api:pin-rm *test-file-hash* :encoding "json" :recursive "true")) 189 | (is (cl-ipfs-api:pin-add *test-file-hash*) 190 | '(("Pinned" "QmRv6FrRUqB8WkSn5FGa4QrsmBvyKTPrGamF43vYEAmPo4"))) 191 | (cl-ipfs-api:pin-rm *test-file-hash* :encoding "json" :recursive "true")) 192 | 193 | 194 | (subtest "pin rm" 195 | (is (progn 196 | (cl-ipfs-api:pin-add *test-file-hash*) 197 | (cl-ipfs-api:pin-rm *test-file-hash* :encoding "json")) 198 | (progn 199 | (cl-ipfs-api:pin-add *test-file-hash*) 200 | (ipfs-command '("pin" "rm") `(,*test-file-hash*) '(("encoding" "json"))))) 201 | (is (progn 202 | (cl-ipfs-api:pin-add *test-file-hash*) 203 | (cl-ipfs-api:pin-rm *test-file-hash* :encoding "text")) 204 | (progn 205 | (cl-ipfs-api:pin-add *test-file-hash*) 206 | (ipfs-command '("pin" "rm") `(,*test-file-hash*) '(("encoding" "text")))))) 207 | 208 | (subtest "pin ls" 209 | (cl-ipfs-api:pin-add *test-file-hash*) 210 | (is-api-cli '("pin" "ls") nil '(("encoding" "json"))) 211 | (cl-ipfs-api:pin-rm *test-file-hash*)) 212 | 213 | ;(subtest "repo gc") 214 | 215 | (subtest "id" 216 | (is-api-cli '("id") nil '(("encoding" "json"))) 217 | (is-api-cli '("id") nil '(("encoding" "text")))) 218 | 219 | (subtest "bootstrap" 220 | (is-api-cli '("bootstrap") nil '(("encoding" "json"))) 221 | (is-api-cli '("bootstrap") nil '(("encoding" "text")))) 222 | 223 | ;(subtest "bootstrap add") 224 | 225 | ;(subtest "bootstrap rm") 226 | 227 | (subtest "swarm peers" 228 | (is-api-cli '("swarm" "peers") nil '(("encoding" "json"))) 229 | (is-api-cli '("swarm" "peers") nil '(("encoding" "text")))) 230 | 231 | (subtest "swarm addrs" 232 | (is-api-cli '("swarm" "addrs") nil '(("encoding" "json"))) 233 | (is-api-cli '("swarm" "addrs") nil '(("encoding" "text")))) 234 | 235 | (subtest "swarm addrs local" 236 | (is-api-cli '("swarm" "addrs" "local") nil '(("encoding" "json"))) 237 | (is-api-cli '("swarm" "addrs" "local") nil '(("encoding" "text")))) 238 | 239 | ;(subtest "swarm connect") 240 | 241 | ;(subtest "swarm disconnect") 242 | 243 | (subtest "swarm filters" 244 | (is-api-cli '("swarm" "filters") nil '(("encoding" "json"))) 245 | (is-api-cli '("swarm" "filters") nil '(("encoding" "text")))) 246 | 247 | ;(subtest "swarm filters add") 248 | 249 | ;(subtest "swarm filters rm") 250 | 251 | ;(subtest "dht query") 252 | 253 | ;(subtest "dht findprovs") 254 | 255 | ;(subtest "dht findpeer") 256 | 257 | ;(subtest "dht get") 258 | 259 | ;(subtest "dht put") 260 | 261 | ;(subtest "ping") 262 | 263 | (subtest "config" 264 | (is-api-cli '("config") '("Datastore.Path" nil) '(("encoding" "json"))) 265 | (is-api-cli '("config") '("Datastore.Path" nil) '(("encoding" "text")))) 266 | 267 | (subtest "config show" 268 | (is (cl-ipfs-api:config-show) 269 | (ipfs-command '("config" "show") nil nil))) 270 | 271 | ;(subtest "config replace") 272 | 273 | (subtest "version" 274 | (is-api-cli '("version") nil '(("encoding" "json"))) 275 | (is-api-cli '("version") nil '(("encoding" "text")))) 276 | 277 | (subtest "clean tests" 278 | (clean-test-data)) 279 | 280 | (finalize) 281 | -------------------------------------------------------------------------------- /src/definitions.json: -------------------------------------------------------------------------------- 1 | [{ 2 | "name": ["add"], 3 | "description": "Add an object to ipfs.", 4 | "args": [{ 5 | "name":"path", 6 | "type":"file", 7 | "required": true, 8 | "variadic": true 9 | }], 10 | "kwargs": ["recursive","quiet","progress","trickle","only-hash","wrap-with-directory","hidden","chunker","timeout"], 11 | "input": "stream", 12 | "output": "chunked" 13 | },{ 14 | "name": ["cat"], 15 | "description": "Show IPFS object data.", 16 | "args": [{ 17 | "name": "ipfs-path", 18 | "type": "multiaddr", 19 | "required": true, 20 | "variadic": true 21 | }], 22 | "kwargs": ["timeout"], 23 | "output": "stream" 24 | },{ 25 | "name": ["ls"], 26 | "description": "List links from an object.", 27 | "args": [{ 28 | "name": "ipfs-path", 29 | "type": "multiaddr", 30 | "required": true, 31 | "variadic": true 32 | }], 33 | "kwargs": ["headers","encoding","timeout"] 34 | },{ 35 | "name": ["refs"], 36 | "description": "List links (references) from an object.", 37 | "args": [{ 38 | "name": "ipfs-path", 39 | "type": "multiaddr", 40 | "required": true, 41 | "variadic": true 42 | }], 43 | "kwargs": ["format","edges","unique","recursive","encoding","timeout"], 44 | "output": "chunked" 45 | },{ 46 | "name": ["refs","local"], 47 | "description": "List all local references.", 48 | "args": [], 49 | "kwargs": ["encoding","timeout"], 50 | "output": "stream" 51 | },{ 52 | "name": ["block","stat"], 53 | "description": "Print information of a raw IPFS block.", 54 | "args": [{ 55 | "name": "key", 56 | "type": "multihash", 57 | "required": true, 58 | "variadic": false 59 | }], 60 | "kwargs": ["encoding","timeout"] 61 | },{ 62 | "name": ["block","get"], 63 | "description": "Get a raw IPFS block", 64 | "args": [{ 65 | "name": "key", 66 | "type": "multihash", 67 | "required": true, 68 | "variadic": false 69 | }], 70 | "kwargs": ["timeout"], 71 | "output": "stream" 72 | },{ 73 | "name": ["block","put"], 74 | "description": "Store input as an IPFS block.", 75 | "args": [{ 76 | "name": "data", 77 | "type": "file", 78 | "required": true, 79 | "variadic": false 80 | }], 81 | "kwargs": ["encoding","timeout"], 82 | "input": "stream" 83 | },{ 84 | "name": ["object","new"], 85 | "description": "Create a new object from an ipfs template.", 86 | "args": [{ 87 | "name": "template", 88 | "type": "string", 89 | "required": false, 90 | "variadic": false 91 | }], 92 | "kwargs": ["encoding","timeout"] 93 | },{ 94 | "name": ["object","data"], 95 | "description": "Output the raw bytes in an IPFS object.", 96 | "args": [{ 97 | "name": "key", 98 | "type": "multihash", 99 | "required": true, 100 | "variadic": false 101 | }], 102 | "kwargs": ["timeout"], 103 | "output": "stream" 104 | },{ 105 | "name": ["object","links"], 106 | "description": "Output the links pointed to by the specified object.", 107 | "args": [{ 108 | "name": "key", 109 | "type": "multihash", 110 | "required": true, 111 | "variadic": false 112 | }], 113 | "kwargs": ["encoding","timeout"] 114 | },{ 115 | "name": ["object","get"], 116 | "description": "Get and serialize the DAG node named by KEY.", 117 | "args": [{ 118 | "name": "key", 119 | "type": "multihash", 120 | "required": true, 121 | "variadic": false 122 | }], 123 | "kwargs": ["encoding","timeout"] 124 | },{ 125 | "name": ["object","put"], 126 | "description": "stores input as a DAG object, outputs its key.", 127 | "args": [{ 128 | "name": "data", 129 | "type": "file", 130 | "required": true, 131 | "variadic": false 132 | }], 133 | "kwargs": ["inputenc","encoding","timeout"], 134 | "input": "stream" 135 | },{ 136 | "name": ["object","stat"], 137 | "description": "Get stats for the DAG node named by KEY.", 138 | "args": [{ 139 | "name": "key", 140 | "type": "multihash", 141 | "required": true, 142 | "variadic": false 143 | }], 144 | "kwargs": ["encoding","timeout"] 145 | },{ 146 | "name": ["object","patch"], 147 | "description": "Create a new DAG node based on an existing one.", 148 | "args": [{ 149 | "name": "root", 150 | "type": "string", 151 | "required": true, 152 | "variadic": false 153 | },{ 154 | "name": "command", 155 | "type": "string", 156 | "required": true, 157 | "variadic": false 158 | },{ 159 | "name": "args", 160 | "type": "string", 161 | "required": true, 162 | "variadic": true 163 | }], 164 | "kwargs": ["create","encoding","timeout"] 165 | },{ 166 | "name": ["file","ls"], 167 | "description": "List directory contents for Unix-filesystem objects.", 168 | "args": [{ 169 | "name": "ipfs-path", 170 | "type": "multiaddr", 171 | "required": true, 172 | "variadic": true 173 | }], 174 | "kwargs": ["encoding","timeout"] 175 | },{ 176 | "name": ["resolve"], 177 | "description": "Resolve the value of names to IPFS.", 178 | "args": [{ 179 | "name": "name", 180 | "type": "multiaddr", 181 | "required": true, 182 | "variadic": false 183 | }], 184 | "kwargs": ["recursive","encoding","timeout"] 185 | },{ 186 | "name": ["name","publish"], 187 | "description": "Publish an object to IPNS.", 188 | "args": [{ 189 | "name": "name", 190 | "type": "multiaddr", 191 | "required": false, 192 | "variadic": false 193 | },{ 194 | "name": "ipfs-path", 195 | "type": "multiaddr", 196 | "required": true, 197 | "variadic": false 198 | }], 199 | "kwargs": ["encoding","timeout"] 200 | },{ 201 | "name": ["name","resolve"], 202 | "description": "Get the value currently published at an IPNS name.", 203 | "args": [{ 204 | "name": "name", 205 | "type": "multihash", 206 | "required": true, 207 | "variadic": false 208 | }], 209 | "kwargs": ["recursive"] 210 | },{ 211 | "name": ["dns"], 212 | "description": "DNS link resolver.", 213 | "args": [{ 214 | "name": "domain-name", 215 | "type": "string", 216 | "required": true, 217 | "variadic": false 218 | }], 219 | "kwargs": ["recursive","encoding","timeout"] 220 | },{ 221 | "name": ["pin","add"], 222 | "description": "Pin objects to local storage.", 223 | "args": [{ 224 | "name": "ipfs-path", 225 | "type": "multiaddr", 226 | "required": true, 227 | "variadic": true 228 | }], 229 | "kwargs": ["recursive","encoding","timeout"] 230 | },{ 231 | "name": ["pin","rm"], 232 | "description": "Unpin an object from local storage.", 233 | "args": [{ 234 | "name": "ipfs-path", 235 | "type": "multiaddr", 236 | "required": true, 237 | "variadic": true 238 | }], 239 | "kwargs": ["recursive","encoding","timeout"] 240 | },{ 241 | "name": ["pin","ls"], 242 | "description": "List objects pinned to local storage.", 243 | "args": [], 244 | "kwargs": ["type","count","quiet","encoding","timeout"] 245 | },{ 246 | "name": ["repo","gc"], 247 | "description": "Perform a garbage collection sweep on non-pinned objects.", 248 | "args": [], 249 | "kwargs": ["quiet","encoding","timeout"], 250 | "output": "chunked" 251 | },{ 252 | "name": ["id"], 253 | "description": "Show IPFS Node ID information", 254 | "args": [], 255 | "kwargs": ["format","encoding","timeout"] 256 | },{ 257 | "name": ["bootstrap"], 258 | "description": "Show peers in the bootstrap list.", 259 | "args": [], 260 | "kwargs": ["encoding","timeout"], 261 | "aliases": [["bootrap","list"]] 262 | },{ 263 | "name": ["bootstrap","add"], 264 | "description": "Add peers to the bootstrap list.", 265 | "args": [{ 266 | "name": "peers", 267 | "type": "multiaddr", 268 | "required": true, 269 | "variadic": true 270 | }], 271 | "kwargs": ["default","encoding","timeout"] 272 | },{ 273 | "name": ["bootstrap","rm"], 274 | "description": "Remove peers from the bootstrap list.", 275 | "args": [{ 276 | "name": "peers", 277 | "type": "multiaddr", 278 | "required": true, 279 | "variadic": true 280 | }], 281 | "kwargs": ["all","encoding","timeout"] 282 | },{ 283 | "name": ["swarm","peers"], 284 | "description": "List peers with open connections.", 285 | "args": [], 286 | "kwargs": ["encoding","timeout"] 287 | },{ 288 | "name": ["swarm","addrs"], 289 | "description": "List known addresses.", 290 | "args": [], 291 | "kwargs": ["encoding","timeout"] 292 | },{ 293 | "name": ["swarm","addrs","local"], 294 | "description": "List local addresses.", 295 | "args": [], 296 | "kwargs": ["encoding","timeout"] 297 | },{ 298 | "name": ["swarm","connect"], 299 | "description": "Open connection to a given address.", 300 | "args": [{ 301 | "name": "address", 302 | "type": "multiaddr", 303 | "required": true, 304 | "variadic": true 305 | }], 306 | "kwargs": ["encoding","timeout"] 307 | },{ 308 | "name": ["swarm","disconnect"], 309 | "description": "Close connection to a given address.", 310 | "args": [{ 311 | "name": "address", 312 | "type": "multiaddr", 313 | "required": true, 314 | "variadic": true 315 | }], 316 | "kwargs": ["encoding","timeout"] 317 | },{ 318 | "name": ["swarm","filters"], 319 | "description": "List currently applied filters.", 320 | "args": [], 321 | "kwargs": ["encoding","timeout"] 322 | },{ 323 | "name": ["swarm","filters","add"], 324 | "description": "Add an address filter.", 325 | "args": [{ 326 | "name": "address", 327 | "type": "multiaddr", 328 | "required": true, 329 | "variadic": true 330 | }], 331 | "kwargs": ["encoding","timeout"] 332 | },{ 333 | "name": ["swarm","filters","rm"], 334 | "description": "Remove an address filter.", 335 | "args": [{ 336 | "name": "address", 337 | "type": "multiaddr", 338 | "required": true, 339 | "variadic": true 340 | }], 341 | "kwargs": ["encoding","timeout"] 342 | },{ 343 | "name": ["dht","query"], 344 | "description": "Run a 'FindClosestPeers' query through the DHT.", 345 | "args": [{ 346 | "name":"peer-id", 347 | "type":"multihash", 348 | "required": true, 349 | "variadic": true 350 | }], 351 | "kwargs": ["verbose","encoding","timeout"], 352 | "output": "chunked" 353 | },{ 354 | "name": ["dht","findprovs"], 355 | "description": "Run a 'FindProviders' query through the DHT.", 356 | "args": [{ 357 | "name":"key", 358 | "type":"multihash", 359 | "required": true, 360 | "variadic": true 361 | }], 362 | "kwargs": ["verbose","encoding","timeout"], 363 | "output": "chunked" 364 | },{ 365 | "name": ["dht","findpeer"], 366 | "description": "Run a 'FindPeer' query through the DHT.", 367 | "args": [{ 368 | "name":"peer-id", 369 | "type":"multihash", 370 | "required": true, 371 | "variadic": true 372 | }], 373 | "kwargs": ["encoding","timeout"], 374 | "output": "chunked" 375 | },{ 376 | "name": ["dht","get"], 377 | "description": "Run a 'GetValue' query through the DHT.", 378 | "args": [{ 379 | "name":"key", 380 | "type":"multihash", 381 | "required": true, 382 | "variadic": true 383 | }], 384 | "kwargs": ["verbose","encoding","timeout"], 385 | "output": "chunked" 386 | },{ 387 | "name": ["dht","put"], 388 | "description": "Run a 'PutValue' query through the DHT.", 389 | "args": [{ 390 | "name":"key", 391 | "type":"multihash", 392 | "required": true, 393 | "variadic": false 394 | },{ 395 | "name":"value", 396 | "type":"string", 397 | "required": true, 398 | "variadic": false 399 | }], 400 | "kwargs": ["verbose","encoding","timeout"], 401 | "output": "chunked" 402 | },{ 403 | "name": ["ping"], 404 | "description": "Send echo request packets to IPFS hosts.", 405 | "args": [{ 406 | "name":"peer-id", 407 | "type":"multihash", 408 | "required": true, 409 | "variadic": true 410 | }], 411 | "kwargs": ["encoding","timeout"], 412 | "output": "chunked" 413 | },{ 414 | "name": ["config"], 415 | "description": "Get and set IPFS config values.", 416 | "args": [{ 417 | "name":"key", 418 | "type":"multihash", 419 | "required": true, 420 | "variadic": false 421 | },{ 422 | "name":"value", 423 | "type":"string", 424 | "required": false, 425 | "variadic": false 426 | }], 427 | "kwargs": ["bool","json","encoding","timeout"] 428 | },{ 429 | "name": ["config","show"], 430 | "description": "Outputs the content of the config file.", 431 | "args": [], 432 | "kwargs": ["timeout"], 433 | "output": "stream" 434 | },{ 435 | "name": ["config","replace"], 436 | "description": "Replace the config with FILE.", 437 | "args": [{ 438 | "name": "file", 439 | "type": "file", 440 | "required": true, 441 | "variadic": false 442 | }], 443 | "kwargs": ["encoding","timeout"], 444 | "input": "stream" 445 | },{ 446 | "name": ["version"], 447 | "description": "Show IPFS version information.", 448 | "args": [], 449 | "kwargs": ["number","encoding","timeout"] 450 | }] 451 | --------------------------------------------------------------------------------