├── .gitignore ├── .rspec ├── .rubocop.yml ├── .ruby-version ├── .travis.yml ├── CONTRIBUTING.md ├── Gemfile ├── Gemfile.lock ├── LICENSE ├── README.md ├── Rakefile ├── Vagrantfile ├── lib ├── ipfs.rb └── ruby-ipfs-http-client │ ├── api │ ├── command.rb │ ├── files │ │ ├── add.rb │ │ ├── cat.rb │ │ └── ls.rb │ └── generic │ │ ├── id.rb │ │ └── version.rb │ ├── base.rb │ ├── client.rb │ ├── connection │ ├── base.rb │ ├── default.rb │ ├── ipfs_config.rb │ └── unreachable.rb │ ├── dagstream.rb │ ├── errors.rb │ ├── file.rb │ ├── multihash.rb │ ├── request │ ├── basic_request.rb │ ├── file_upload_request.rb │ └── request.rb │ └── version.rb ├── ruby-ipfs-http-client.gemspec ├── spec ├── api │ ├── files │ │ ├── add_spec.rb │ │ ├── cat_spec.rb │ │ └── ls_spec.rb │ └── generic │ │ ├── id_spec.rb │ │ └── version_spec.rb ├── base_spec.rb ├── client_spec.rb ├── connection │ ├── base_spec.rb │ ├── default_spec.rb │ ├── ipfs_config_spec.rb │ └── unreachable_spec.rb ├── dagstream_spec.rb ├── file_spec.rb ├── fixtures │ ├── api │ │ └── files │ │ │ ├── add_response.json │ │ │ ├── cat_invalid_dag_stream.json │ │ │ ├── cat_invalid_ipfs_ref.json │ │ │ ├── ls_invalid_dag_node.json │ │ │ ├── ls_invalid_ipfs_ref.json │ │ │ ├── ls_output.json │ │ │ └── sample │ ├── dune.txt │ ├── id.json │ ├── ipfs │ │ └── config │ └── version.json ├── multihash_spec.rb ├── request │ ├── basic_request_spec.rb │ ├── file_upload_request_spec.rb │ └── request_spec.rb └── spec_helper.rb └── var ├── bin ├── clear_garbage_collector.sh ├── install_go.sh ├── install_ipfs.sh ├── install_ruby.sh ├── ipfsd ├── provision.sh └── remove_pinned_objects.sh └── conf └── ipfs_upstart.conf /.gitignore: -------------------------------------------------------------------------------- 1 | # compiled package 2 | *.gem 3 | 4 | # floobits 5 | .floo* 6 | 7 | # coveralls 8 | coverage 9 | var/test/ 10 | -------------------------------------------------------------------------------- /.rspec: -------------------------------------------------------------------------------- 1 | --color 2 | --require spec_helper 3 | --format documentation -------------------------------------------------------------------------------- /.rubocop.yml: -------------------------------------------------------------------------------- 1 | Style/Documentation: 2 | Enabled: false 3 | 4 | Style/MutableConstant: 5 | Enabled: false 6 | 7 | Style/FrozenStringLiteralComment: 8 | Enabled: false 9 | 10 | Style/NestedParenthesizedCalls: 11 | Enabled: false 12 | 13 | Lint/AmbiguousOperator: 14 | Enabled: false 15 | 16 | Metrics/BlockLength: 17 | Enabled: false 18 | 19 | 20 | Style/BlockDelimiters: 21 | Enabled: false 22 | 23 | Style/MultilineIfModifier: 24 | Enabled: false -------------------------------------------------------------------------------- /.ruby-version: -------------------------------------------------------------------------------- 1 | 2.6.1 2 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: ruby 2 | 3 | before_install: 4 | - docker pull ipfs/go-ipfs 5 | - sh ./var/bin/ipfsd 6 | 7 | rvm: 8 | - 2.6.1 -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Getting a working development environment 2 | 3 | ## Ipfs 4 | 5 | This library uses an ipfs instance inside a Docker container. 6 | 7 | ### Getting the Docker image 8 | 9 | ```bash 10 | docker pull ipfs/go-ipfs 11 | ``` 12 | 13 | Then to initialize it, execute the following 14 | 15 | ```bash 16 | docker run --name ipfsd -p 5001:5001 -d ipfs/go-ipfs 17 | ``` 18 | 19 | or, 20 | 21 | ```bash 22 | var/bin/ipfsd 23 | ``` 24 | 25 | These commands also start the container. 26 | 27 | 28 | ### Manage the Ipfs daemon lifecycle through Docker 29 | 30 | To start it: 31 | 32 | ```bash 33 | docker start ipfsd 34 | ``` 35 | 36 | To stop it: 37 | 38 | ```bash 39 | docker stop ipfsd 40 | ``` 41 | 42 | ## Ruby 43 | 44 | ### Rbenv 45 | 46 | If you don't have rbenv (or another ruby version manager) already. 47 | 48 | To install it: 49 | 50 | ```bash 51 | git clone https://github.com/rbenv/rbenv.git ${HOME}/.rbenv 52 | ``` 53 | 54 | Add the following lines in your shell .rc file 55 | 56 | ```bash 57 | export PATH="$HOME/.rbenv/bin:$PATH" 58 | eval "$(rbenv init -)" 59 | ``` 60 | 61 | Reload your shell! 62 | 63 | 64 | ### ruby-build 65 | 66 | `ruby-build` is an `rbenv` plugin that makes the installation of rubies easier 67 | 68 | ```bash 69 | mkdir ${HOME}/.rbenv/plugins 70 | git clone https://github.com/rbenv/ruby-build.git ${HOME}/.rbenv/plugins/ruby-build 71 | ``` 72 | 73 | ### Ruby 74 | 75 | Go to the root of the project. 76 | 77 | You can find the currently used ruby version inside the `.ruby-version` file. 78 | 79 | ```bash 80 | rbenv install `cat .ruby-version` 81 | rbenv local `cat .ruby-version` 82 | ``` 83 | 84 | This will install and set the correct ruby version to use with the project 85 | without messing up the one that you use in your system. 86 | 87 | 88 | ### The project's dependencies 89 | 90 | We go through bundler to install the project dependencies 91 | 92 | ```sh 93 | gem install bundler 94 | bundle install 95 | ``` 96 | 97 | ## Testing the setup 98 | 99 | What a better way than running the specs to test if the setup is 100 | complete and working. 101 | 102 | So, still at the root of the project: 103 | 104 | ```bash 105 | bundle exec rspec 106 | ``` 107 | 108 | or, 109 | 110 | ```bash 111 | bundle exec rake test 112 | ``` 113 | 114 | # Library Design 115 | 116 | The library mostly revolves around one entity: the **CLIENT**. 117 | 118 | It is the one that will route **REQUESTS** to Ipfs. Those **REQUESTS** 119 | are built by low-level entities known as **COMMANDS**. 120 | 121 | But, none of the above objects are intended to be used directly (by 122 | the library's user). The low-level mechanism is hidden to the user and 123 | instead, objects such as **File** and **Directory** are exposed. 124 | 125 | We tend to follow the following [implementation](https://github.com/ipfs/interface-ipfs-core/tree/master/SPEC), given by 126 | the Ipfs' team. 127 | 128 | ## Client 129 | 130 | The two main purposes of the **CLIENT** are: 131 | 132 | - maintaining a connection with Ipfs through the program execution 133 | - to route all the **REQUESTS** and their corresponding responses. 134 | 135 | It's worth knowing that, in the entire library, it's the only object 136 | that is allowed to communicate with the outside world, in our case, 137 | Ipfs. 138 | 139 | One of the main advantages of its implementation is to concentrate all 140 | the side-effects and the dependencies of external libraries, such as 141 | [`HTTP.rb`](https://github.com/httprb/http) to a single entity. 142 | 143 | His implementation, as a singleton class, allows us to use it the 144 | same way we would use a Database Connector. 145 | 146 | For example, it is absolutely mandatory that at the runtime, an Ipfs 147 | daemon is present, otherwise the execution stops. 148 | 149 | ### Using the client 150 | 151 | To use the **CLIENT** in the project, simply pass a **COMMAND** and 152 | its arguments to the `execute` method. 153 | 154 | Example from `ruby-http-client/lib/ruby-http-client/file.rb:19` 155 | 156 | ```ruby 157 | Ipfs::Client.execute(Command::Add, @path).tap { |response| 158 | ... 159 | } 160 | ``` 161 | 162 | In this example, we ask the **CLIENT** to execute the Ipfs **ADD** 163 | command with a path to a file. Then, we handle the response according 164 | to our needs. 165 | 166 | ## Commands 167 | 168 | **COMMANDS** are entities that are used to create requests and 169 | handle their responses at a low-level. 170 | 171 | They tend to be as close as possible to the Ipfs implementation and 172 | to be used in the same way. However, even, if their design allow it, 173 | they are not intended to be used directly. 174 | 175 | Their implementation mostly depends of the needs given by their 176 | execution context. 177 | 178 | For example, the **ADD** method should allow us to be called either on 179 | a file and either on a directory. The main difference between the two 180 | is to pass a `recursive` option. 181 | 182 | The manipulation of **COMMANDS** are done exclusively by the 183 | **CLIENT**. For that reason, **COMMANDS** MUST implement two methods 184 | `build_request` and `parse_response`. 185 | 186 | ### Execution steps 187 | 188 | A **COMMAND**, in order to be executed, is sent to the **CLIENT**. 189 | 190 | As an orchestrator, the **CLIENT** will ask the **COMMAND** to prepare 191 | its **REQUEST**. Right after, the **CLIENT** will use the **REQUEST** 192 | to call Ipfs and sends back the response to the **COMMAND**. 193 | 194 | ### Requests 195 | 196 | **REQUESTS** objects are here to encapsulate information that will be 197 | given to Ipfs. They are built by the commands (and only by them) and 198 | are passed to client. 199 | 200 | All **REQUESTS** objects inherits from an interface named `Ipfs::Request`. 201 | 202 | ## API 203 | 204 | In this section, we'll talk about interfaces which the user will 205 | interact with. 206 | 207 | ### File 208 | 209 | **FILE** allows to `add` and `cat` content to Ipfs. Those two methods 210 | make a call to the API with the help of **COMMANDS** and the **CLIENT**. 211 | 212 | The `add` method has side-effects. Upon call, it completes information 213 | about the file. 214 | 215 | ### Directory 216 | 217 | [not implemented yet] 218 | 219 | **DIRECTORY** allows to `ls` directories to Ipfs. 220 | 221 | 222 | The `ls` method has side-effects. Upon call, the object creates as many 223 | as necessary **FILE** and/or **DIRECTORY** objects that are saved as 224 | attributes. This method is recursive. 225 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source 'https://rubygems.org' 2 | 3 | ruby '2.6.1' 4 | 5 | gem 'http', '~> 4.0.5' 6 | gem 'rake', '~> 12.3.3' 7 | gem 'yard', '~> 0.9.20' 8 | gem 'rubocop', '~> 0.65.0', require: false 9 | 10 | gemspec 11 | -------------------------------------------------------------------------------- /Gemfile.lock: -------------------------------------------------------------------------------- 1 | PATH 2 | remote: . 3 | specs: 4 | ruby-ipfs-http-client (0.5.1) 5 | 6 | GEM 7 | remote: https://rubygems.org/ 8 | specs: 9 | addressable (2.5.2) 10 | public_suffix (>= 2.0.2, < 4.0) 11 | ast (2.4.0) 12 | crack (0.4.3) 13 | safe_yaml (~> 1.0.0) 14 | diff-lcs (1.3) 15 | domain_name (0.5.20180417) 16 | unf (>= 0.0.5, < 1.0.0) 17 | hashdiff (0.3.8) 18 | http (4.0.5) 19 | addressable (~> 2.3) 20 | http-cookie (~> 1.0) 21 | http-form_data (~> 2.0) 22 | http_parser.rb (~> 0.6.0) 23 | http-cookie (1.0.3) 24 | domain_name (~> 0.5) 25 | http-form_data (2.1.1) 26 | http_parser.rb (0.6.0) 27 | jaro_winkler (1.5.2) 28 | parallel (1.13.0) 29 | parser (2.6.0.0) 30 | ast (~> 2.4.0) 31 | powerpack (0.1.2) 32 | psych (3.1.0) 33 | public_suffix (3.0.0) 34 | rainbow (3.0.0) 35 | rake (12.3.3) 36 | rspec (3.8.0) 37 | rspec-core (~> 3.8.0) 38 | rspec-expectations (~> 3.8.0) 39 | rspec-mocks (~> 3.8.0) 40 | rspec-core (3.8.0) 41 | rspec-support (~> 3.8.0) 42 | rspec-expectations (3.8.2) 43 | diff-lcs (>= 1.2.0, < 2.0) 44 | rspec-support (~> 3.8.0) 45 | rspec-mocks (3.8.0) 46 | diff-lcs (>= 1.2.0, < 2.0) 47 | rspec-support (~> 3.8.0) 48 | rspec-support (3.8.0) 49 | rubocop (0.65.0) 50 | jaro_winkler (~> 1.5.1) 51 | parallel (~> 1.10) 52 | parser (>= 2.5, != 2.5.1.1) 53 | powerpack (~> 0.1) 54 | psych (>= 3.1.0) 55 | rainbow (>= 2.2.2, < 4.0) 56 | ruby-progressbar (~> 1.7) 57 | unicode-display_width (~> 1.4.0) 58 | ruby-progressbar (1.10.0) 59 | safe_yaml (1.0.4) 60 | unf (0.1.4) 61 | unf_ext 62 | unf_ext (0.0.7.5) 63 | unicode-display_width (1.4.1) 64 | webmock (3.5.1) 65 | addressable (>= 2.3.6) 66 | crack (>= 0.3.2) 67 | hashdiff 68 | yard (0.9.20) 69 | 70 | PLATFORMS 71 | ruby 72 | 73 | DEPENDENCIES 74 | http (~> 4.0.5) 75 | rake (~> 12.3.3) 76 | rspec (~> 3.8.0) 77 | rubocop (~> 0.65.0) 78 | ruby-ipfs-http-client! 79 | webmock (~> 3.5.1) 80 | yard (~> 0.9.20) 81 | 82 | RUBY VERSION 83 | ruby 2.6.1p33 84 | 85 | BUNDLED WITH 86 | 1.17.2 87 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Thomas Nieto 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Gem Version](https://badge.fury.io/rb/ruby-ipfs-http-client.svg)](https://badge.fury.io/rb/ruby-ipfs-http-client) 2 | [![Build Status](https://travis-ci.org/tbenett/ruby-ipfs-http-client.svg?branch=master)](https://travis-ci.org/tbenett/ruby-ipfs-http-client) 3 | 4 | # ruby-ipfs-http-client 5 | 6 | > A client library for the IPFS HTTP API, implemented in Ruby. 7 | 8 | Summary: 9 | 10 | Make sure the Ipfs daemon is running, otherwise 11 | the client will not be able to connect. 12 | 13 | You'll get an error `Ipfs::UnreachableDaemon` and the program 14 | execution will stop if daemon is not present. 15 | 16 | The client will make a persistent connection to the API. 17 | 18 | To access the library from your source file: 19 | 20 | ```ruby 21 | require 'ipfs' 22 | ``` 23 | 24 | > TODO: use a configuration file and/or environment variables to specify the http http-client url. 25 | Those are hard-coded at the moment :( 26 | 27 | ## Ipfs::File 28 | 29 | This class is intended to manipulate files through Ipfs. 30 | 31 | Methods are documented [here](https://www.rubydoc.info/gems/ruby-ipfs-http-client/Ipfs/File) 32 | 33 | # Want to Contribute 34 | 35 | You can have an overview of the library's design in [CONTRIBUTING.md](./CONTRIBUTING.md). -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | require 'bundler' 2 | 3 | Bundler::GemHelper.install_tasks 4 | 5 | task default: %w[test] 6 | 7 | task :test do 8 | sh 'rspec' 9 | end -------------------------------------------------------------------------------- /Vagrantfile: -------------------------------------------------------------------------------- 1 | # -*- mode: ruby -*- 2 | # vi: set ft=ruby : 3 | 4 | # All Vagrant configuration is done below. The "2" in Vagrant.configure 5 | # configures the configuration version (we support older styles for 6 | # backwards compatibility). Please don't change it unless you know what 7 | # you're doing. 8 | Vagrant.configure("2") do |config| 9 | # The most common configuration options are documented and commented below. 10 | # For a complete reference, please see the online documentation at 11 | # https://docs.vagrantup.com. 12 | 13 | # Every Vagrant development environment requires a box. You can search for 14 | # boxes at https://vagrantcloud.com/search. 15 | config.vm.box = "ubuntu/trusty64" 16 | 17 | # Disable automatic box update checking. If you disable this, then 18 | # boxes will only be checked for updates when the user runs 19 | # `vagrant box outdated`. This is not recommended. 20 | # config.vm.box_check_update = false 21 | 22 | # Create a forwarded port mapping which allows access to a specific port 23 | # within the machine from a port on the host machine. In the example below, 24 | # accessing "localhost:8080" will access port 80 on the guest machine. 25 | # NOTE: This will enable public access to the opened port 26 | # config.vm.network "forwarded_port", guest: 80, host: 8080 27 | 28 | # Create a forwarded port mapping which allows access to a specific port 29 | # within the machine from a port on the host machine and only allow access 30 | # via 127.0.0.1 to disable public access 31 | # config.vm.network "forwarded_port", guest: 80, host: 8080, host_ip: "127.0.0.1" 32 | 33 | # Create a private network, which allows host-only access to the machine 34 | # using a specific IP. 35 | # config.vm.network "private_network", ip: "192.168.33.10" 36 | 37 | # Create a public network, which generally matched to bridged network. 38 | # Bridged networks make the machine appear as another physical device on 39 | # your network. 40 | # config.vm.network "public_network" 41 | 42 | # Share an additional folder to the guest VM. The first argument is 43 | # the path on the host to the actual folder. The second argument is 44 | # the path on the guest to mount the folder. And the optional third 45 | # argument is a set of non-required options. 46 | config.vm.synced_folder '.', "/home/vagrant/#{Dir.pwd.split('/').last}", type: 'rsync', 47 | rsync__exclude: '.git/', 48 | rsync__auto: true 49 | 50 | # Provider-specific configuration so you can fine-tune various 51 | # backing providers for Vagrant. These expose provider-specific options. 52 | # Example for VirtualBox: 53 | # 54 | # config.vm.provider "virtualbox" do |vb| 55 | # # Display the VirtualBox GUI when booting the machine 56 | # vb.gui = true 57 | # 58 | # # Customize the amount of memory on the VM: 59 | # vb.memory = "1024" 60 | # end 61 | # 62 | # View the documentation for the provider you are using for more 63 | # information on available options. 64 | 65 | # Enable provisioning with a shell script. Additional provisioners such as 66 | # Puppet, Chef, Ansible, Salt, and Docker are also available. Please see the 67 | # documentation for more information about their specific syntax and use. 68 | config.vm.provision "shell", path: './var/bin/provision.sh' 69 | end 70 | -------------------------------------------------------------------------------- /lib/ipfs.rb: -------------------------------------------------------------------------------- 1 | require_relative './ruby-ipfs-http-client/client' 2 | require_relative './ruby-ipfs-http-client/file' 3 | 4 | module Ipfs 5 | end 6 | -------------------------------------------------------------------------------- /lib/ruby-ipfs-http-client/api/command.rb: -------------------------------------------------------------------------------- 1 | require_relative '../multihash' 2 | 3 | require_relative '../request/basic_request' 4 | require_relative '../request/file_upload_request' 5 | 6 | require_relative './generic/id' 7 | require_relative './generic/version' 8 | require_relative './files/cat' 9 | require_relative './files/ls' 10 | require_relative './files/add' 11 | 12 | module Ipfs 13 | module Command 14 | def self.build_request(path, **arguments) 15 | keys = arguments.keys 16 | 17 | if keys.include?(:multihash) 18 | BasicRequest.new(path, multihash: arguments[:multihash]) 19 | elsif keys.include?(:filepath) 20 | FileUploadRequest.new(path, arguments[:filepath]) 21 | else 22 | BasicRequest.new(path) 23 | end 24 | end 25 | end 26 | end -------------------------------------------------------------------------------- /lib/ruby-ipfs-http-client/api/files/add.rb: -------------------------------------------------------------------------------- 1 | module Ipfs 2 | module Command 3 | class Add 4 | PATH = '/add' 5 | 6 | def self.build_request(filepath) 7 | Command.build_request(PATH, filepath: filepath) 8 | end 9 | 10 | def self.parse_response(response) 11 | JSON.parse response 12 | end 13 | end 14 | end 15 | end 16 | -------------------------------------------------------------------------------- /lib/ruby-ipfs-http-client/api/files/cat.rb: -------------------------------------------------------------------------------- 1 | require_relative '../../dagstream' 2 | 3 | module Ipfs 4 | module Command 5 | class Cat 6 | PATH = '/cat' 7 | 8 | def self.build_request(multihash) 9 | Command.build_request(PATH, multihash: multihash) 10 | end 11 | 12 | def self.parse_response(response) 13 | DagStream.new response 14 | end 15 | end 16 | end 17 | end -------------------------------------------------------------------------------- /lib/ruby-ipfs-http-client/api/files/ls.rb: -------------------------------------------------------------------------------- 1 | module Ipfs 2 | module Command 3 | class Ls 4 | PATH = '/ls' 5 | 6 | def self.build_request(multihash) 7 | Command.build_request(PATH, multihash: Ipfs::Multihash.new(multihash)) 8 | end 9 | 10 | def self.parse_response(response) 11 | JSON.parse(response.body)['Objects'][0]['Links'] 12 | end 13 | end 14 | end 15 | end 16 | -------------------------------------------------------------------------------- /lib/ruby-ipfs-http-client/api/generic/id.rb: -------------------------------------------------------------------------------- 1 | module Ipfs 2 | module Command 3 | class Id 4 | PATH = '/id' 5 | 6 | def self.build_request 7 | Command.build_request(PATH) 8 | end 9 | 10 | def self.parse_response(response) 11 | JSON.parse response 12 | end 13 | end 14 | end 15 | end -------------------------------------------------------------------------------- /lib/ruby-ipfs-http-client/api/generic/version.rb: -------------------------------------------------------------------------------- 1 | module Ipfs 2 | module Command 3 | class Version 4 | PATH = '/version' 5 | 6 | def self.build_request 7 | Command.build_request(PATH) 8 | end 9 | 10 | def self.parse_response(response) 11 | JSON.parse response 12 | end 13 | end 14 | end 15 | end 16 | -------------------------------------------------------------------------------- /lib/ruby-ipfs-http-client/base.rb: -------------------------------------------------------------------------------- 1 | module Ipfs 2 | class Base58 3 | ALPHABET = '123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz' 4 | BASE = ALPHABET.length 5 | 6 | def self.decode(number) 7 | valid?(number) \ 8 | ? to_base10(number) 9 | : 0 10 | end 11 | 12 | def self.to_base10(base58_number) 13 | base58_number 14 | .reverse 15 | .split(//) 16 | .each_with_index 17 | .reduce(0) do |base10_number, (base58_numeral, index)| 18 | base10_number + ALPHABET.index(base58_numeral) * (BASE**index) 19 | end 20 | end 21 | 22 | def self.valid?(number) 23 | number.match?(/\A[#{ALPHABET}]+\z/) 24 | end 25 | 26 | def self.encode(base10_number) 27 | base10_number.is_a?(Integer) \ 28 | ? to_base58(base10_number) \ 29 | : '' 30 | end 31 | 32 | def self.to_base58(base10_number) 33 | base58_number = '' 34 | 35 | begin 36 | base58_number << ALPHABET[base10_number % BASE] 37 | base10_number /= BASE 38 | end while base10_number > 0 39 | 40 | base58_number.reverse 41 | end 42 | end 43 | end -------------------------------------------------------------------------------- /lib/ruby-ipfs-http-client/client.rb: -------------------------------------------------------------------------------- 1 | require 'http' 2 | require 'uri' 3 | 4 | require_relative './errors' 5 | require_relative './api/command' 6 | require_relative './api/generic/id' 7 | 8 | require_relative './connection/default' 9 | require_relative './connection/ipfs_config' 10 | require_relative './connection/unreachable' 11 | 12 | module Ipfs 13 | # The client is not intended to be manipulated. It is a singleton class used 14 | # to route commands and their corresponding requests. 15 | # 16 | # However, it contains certain, read-only, information that can be useful for 17 | # debugging purposes. 18 | class Client 19 | # @api private 20 | DEFAULT_BASE_PATH = '/api/v0' 21 | # @api private 22 | CONNECTION_METHODS = [ 23 | Connection::Default, 24 | Connection::IpfsConfig, 25 | Connection::Unreachable 26 | ] 27 | 28 | # @api private 29 | class << self 30 | def initialize 31 | attempt_connection 32 | 33 | retrieve_ids 34 | retrieve_daemon_version 35 | 36 | ObjectSpace.define_finalizer(self, proc { @@connection.close }) 37 | end 38 | 39 | 40 | # @api private 41 | def execute(command, *args) 42 | command.parse_response call command.build_request *args 43 | end 44 | 45 | # Various debugging information concerning the Ipfs node itself 46 | # 47 | # @example 48 | # Ipfs::Client.id 49 | # #=> { 50 | # peer_id: 'QmVnLbr9Jktjwx...', 51 | # addresses: [ 52 | # "/ip4/127.0.0.1/tcp/4001/ipfs/QmVwxnW4Z8JVMDfo1jeFMNqQor5naiStUPooCdf2Yu23Gi", 53 | # "/ip4/192.168.1.16/tcp/4001/ipfs/QmVwxnW4Z8JVMDfo1jeFMNqQor5naiStUPooCdf2Yu23Gi", 54 | # "/ip6/::1/tcp/4001/ipfs/QmVwxnW4Z8JVMDfo1jeFMNqQor5naiStUPooCdf2Yu23Gi", 55 | # "/ip6/2a01:e34:ef8d:2940:8f7:c616:...5naiStUPooCdf2Yu23Gi", 56 | # "/ip6/2a01:e34:ef8d:2940:...5naiStUPooCdf2Yu23Gi", 57 | # "/ip4/78.248.210.148/tcp/13684/ipfs/Qm...o1jeFMNqQor5naiStUPooCdf2Yu23Gi" 58 | # ], 59 | # public_key: "CAASpgIwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwgg...AgMBAAE=", 60 | # agent_version: "go-ipfs/0.4.13/3b16b74" 61 | # } 62 | # 63 | # @return [Hash{Symbol => String, Array}] 64 | def id 65 | @@id 66 | end 67 | 68 | # Various debugging information concerning the running Ipfs daemon 69 | # and this library 70 | # 71 | # @example 72 | # Ipfs::Client.daemon 73 | # #=> { 74 | # :version: "0.4.13", 75 | # commit: "cc01b7f", 76 | # repo: "6", 77 | # system: "amd64/darwin", 78 | # golang: "go1.9.2" 79 | # } 80 | # 81 | # @return [Hash{Symbol => String}] 82 | def daemon 83 | @@daemon 84 | end 85 | 86 | private 87 | 88 | def call(command) 89 | begin 90 | @@connection.request( 91 | command.verb, 92 | "#{DEFAULT_BASE_PATH}#{command.path}", 93 | command.options 94 | ) 95 | rescue HTTP::ConnectionError 96 | raise Ipfs::Error::UnreachableDaemon, "IPFS is not reachable." 97 | end 98 | end 99 | 100 | def attempt_connection 101 | find_up = ->(connections) { 102 | connections.each { |connection| 103 | co = connection.new 104 | 105 | return co if co.up? 106 | } 107 | } 108 | 109 | @@connection = find_up.call(CONNECTION_METHODS).make_persistent 110 | end 111 | 112 | def retrieve_ids 113 | (execute Command::Id).tap do |ids| 114 | @@id = { 115 | peer_id: ids['ID'], 116 | addresses: ids['Addresses'], 117 | public_key: ids['PublicKey'], 118 | agent_version: ids['AgentVersion'], 119 | } 120 | end 121 | end 122 | 123 | def retrieve_daemon_version 124 | (execute Command::Version).tap do |version| 125 | @@daemon = { 126 | version: version['Version'], 127 | commit: version['Commit'], 128 | repo: version['Repo'], 129 | system: version['System'], 130 | golang: version['Golang'], 131 | api: DEFAULT_BASE_PATH.split('/')[-1] 132 | } 133 | end 134 | end 135 | end 136 | 137 | initialize 138 | 139 | private_class_method :new 140 | end 141 | end 142 | -------------------------------------------------------------------------------- /lib/ruby-ipfs-http-client/connection/base.rb: -------------------------------------------------------------------------------- 1 | require 'http' 2 | require 'uri' 3 | 4 | module Ipfs 5 | module Connection 6 | class Base 7 | DEFAULT_BASE_PATH = '/api/v0' 8 | attr_reader :host, :port 9 | 10 | def build_uri 11 | URI::HTTP.build(host: @host, port: @port) 12 | end 13 | 14 | def up? 15 | begin 16 | HTTP.post("http://#{@host}:#{@port}#{DEFAULT_BASE_PATH}/id") 17 | true 18 | rescue HTTP::ConnectionError 19 | false 20 | end 21 | end 22 | 23 | def make_persistent 24 | HTTP.persistent build_uri 25 | end 26 | end 27 | end 28 | end -------------------------------------------------------------------------------- /lib/ruby-ipfs-http-client/connection/default.rb: -------------------------------------------------------------------------------- 1 | require_relative './base' 2 | 3 | module Ipfs 4 | module Connection 5 | class Default < Base 6 | def initialize 7 | @host = 'localhost' 8 | @port = 5001 9 | end 10 | end 11 | end 12 | end -------------------------------------------------------------------------------- /lib/ruby-ipfs-http-client/connection/ipfs_config.rb: -------------------------------------------------------------------------------- 1 | require_relative './base' 2 | 3 | module Ipfs 4 | module Connection 5 | class IpfsConfig < Base 6 | CONFIG_FILEPATH = "#{ENV['HOME']}/.ipfs/config" 7 | 8 | def initialize 9 | parse_config.tap { |location| 10 | @host = location[:host] 11 | @port = location[:port] 12 | } 13 | end 14 | 15 | private 16 | 17 | def parse_config 18 | %r{.*API.*/ip4/(.*)/tcp/(\d+)}.match(::File.read CONFIG_FILEPATH) do |matched_data| 19 | { 20 | host: matched_data[1], 21 | port: matched_data[2].to_i 22 | } 23 | end 24 | end 25 | end 26 | end 27 | end -------------------------------------------------------------------------------- /lib/ruby-ipfs-http-client/connection/unreachable.rb: -------------------------------------------------------------------------------- 1 | require_relative './base' 2 | require_relative '../errors' 3 | 4 | module Ipfs 5 | module Connection 6 | class Unreachable < Base 7 | def up? 8 | raise Ipfs::Error::UnreachableDaemon, "IPFS is not reachable." 9 | end 10 | end 11 | end 12 | end -------------------------------------------------------------------------------- /lib/ruby-ipfs-http-client/dagstream.rb: -------------------------------------------------------------------------------- 1 | require_relative './errors' 2 | 3 | module Ipfs 4 | class DagStream 5 | attr_reader :content 6 | 7 | def initialize(response) 8 | if response.status.code == 200 9 | @content = response.body.to_s 10 | else 11 | raise Error::InvalidDagStream, JSON.parse(response.body)['Message'] 12 | end 13 | end 14 | 15 | def to_s 16 | @content 17 | end 18 | 19 | alias to_str to_s 20 | end 21 | end 22 | -------------------------------------------------------------------------------- /lib/ruby-ipfs-http-client/errors.rb: -------------------------------------------------------------------------------- 1 | module Ipfs 2 | module Error 3 | class InvalidDagStream < StandardError 4 | end 5 | 6 | class InvalidMultihash < StandardError 7 | end 8 | 9 | class UnreachableDaemon < StandardError 10 | end 11 | end 12 | end 13 | -------------------------------------------------------------------------------- /lib/ruby-ipfs-http-client/file.rb: -------------------------------------------------------------------------------- 1 | require_relative './multihash' 2 | require_relative './api/files/add' 3 | require_relative './api/files/cat' 4 | 5 | module Ipfs 6 | # @attr_reader [String] path The file's path. 7 | # @attr_reader [Ipfs::Multihash] multihash The file's multihash as returned by Ipfs. 8 | # @attr_reader [Integer] size The file's size in bytes as returned by Ipfs. 9 | # @attr_reader [String] name The file's name as returned by Ipfs. 10 | class File 11 | attr_reader :path, :multihash, :size, :name 12 | 13 | # Create an Ipfs file object, either from a Multihash or from a filepath 14 | # allowing a file to be added to and be retrieved from Ipfs. 15 | # 16 | # @example given a filepath 17 | # Ipfs::File.new(path: 'path/to/file') 18 | # #=> # 19 | # @example given a multihash 20 | # Ipfs::File.new(multihash: 'QmVfpW2rKzzahcxt5LfYyNnnKvo1L7XyRF8Ykmhttcyztv') 21 | # #=> #> 22 | # 23 | # @param attributes [Hash{Symbol => String}] 24 | # 25 | # @return [Ipfs::File] 26 | # 27 | # @raise [Error::InvalidMultihash, Errno::ENOENT] Whether the path leads to 28 | # a non-file entity or the multihash may be invalid, 29 | # an error is thrown. 30 | def initialize(**attributes) 31 | attributes.each { |name, value| 32 | instance_variable_set("@#{name}".to_sym, send("init_#{name}", value)) 33 | } 34 | 35 | @added = false 36 | end 37 | 38 | # Add a file to the Ipfs' node. 39 | # 40 | # @note the call to Ipfs completes data about the added file. 41 | # See {#multihash}, {#size} and {#name}. 42 | # 43 | # An {#Ipfs::File} instantiated from a multihash will not be added to Ipfs 44 | # (as the presence of the multihash already suppose its addition to a node). 45 | # In such case, the object is still returned but no call to Ipfs occurs. 46 | # 47 | # @example file not being added to Ipfs 48 | # file = Ipfs::File.new(path: 'path/to/file') 49 | # file.cat 50 | # #=> '' 51 | # file.multihash 52 | # #=> nil 53 | # file.name 54 | # #=> nil 55 | # file.size 56 | # #=> nil 57 | # @example file being added 58 | # file = Ipfs::File.new(path: 'path/to/file').add 59 | # file.cat 60 | # #=> 'file content' 61 | # file.multihash 62 | # #=> # 63 | # file.name 64 | # #=> 'file' 65 | # file.size 66 | # #=> 20 67 | # 68 | # @return [Ipfs::File] Returns the object on which the method was call. 69 | def add 70 | tap { 71 | Ipfs::Client.execute(Command::Add, @path).tap { |response| 72 | @added = true 73 | 74 | @multihash = init_multihash(response['Hash']) 75 | @size = response['Size'].to_i 76 | @name = response['Name'] 77 | } if !@added 78 | } 79 | end 80 | 81 | # Use the {#multihash} to get the content of a file from Ipfs and returns it. 82 | # 83 | # @note the file must be added first or have a multihash. See {#add} and {#multihash}. 84 | # 85 | # @example 86 | # Ipfs::File.new(path: 'path/to/file').add.cat 87 | # #=> 'file content' 88 | # 89 | # @return [String] The content is returned. 90 | def cat 91 | begin 92 | Ipfs::Client.execute(Command::Cat, @multihash).to_s if @multihash 93 | rescue Ipfs::Error::InvalidDagStream 94 | '' 95 | end 96 | end 97 | 98 | private 99 | 100 | def init_multihash(multihash) 101 | multihash.is_a?(Multihash) ? multihash : Multihash.new(multihash) 102 | end 103 | 104 | def init_path(path) 105 | if ::File.file? path 106 | path 107 | else 108 | raise Errno::ENOENT, 'no such file or directory' 109 | end 110 | end 111 | end 112 | end -------------------------------------------------------------------------------- /lib/ruby-ipfs-http-client/multihash.rb: -------------------------------------------------------------------------------- 1 | require_relative './base' 2 | require_relative './errors' 3 | 4 | module Ipfs 5 | class Multihash 6 | attr_reader :hash_func_type, :digest_length 7 | 8 | FUNCTIONS = [ 9 | { name: :sha256, type_code: 0x12, digest_length: 0x20 } 10 | ] 11 | 12 | def initialize(multihash) 13 | @base58_encoded = multihash 14 | 15 | raise Error::InvalidMultihash, 16 | "The hash '#{@base58_encoded}' is invalid." unless @base58_encoded.is_a?(String) 17 | 18 | @bytes_encoded = to_bytes 19 | 20 | @function = find_hash_function(@bytes_encoded[0]) 21 | 22 | raise Error::InvalidMultihash, "The hash func type could not be found" if @function.nil? 23 | 24 | @hash_func_type = @function[:name] 25 | @digest_length = @function[:digest_length] 26 | 27 | raise Error::InvalidMultihash, 28 | "The hash '#{@base58_encoded}' is invalid." unless correct_length? 29 | end 30 | 31 | def to_bytes 32 | [Base58.decode(@base58_encoded).to_s(16)] 33 | .pack('H*') 34 | .unpack('C*') 35 | end 36 | 37 | def raw 38 | @base58_encoded 39 | end 40 | 41 | alias to_s raw 42 | 43 | private 44 | 45 | def find_hash_function(func_type_code) 46 | FUNCTIONS.find { |function| function[:type_code] == func_type_code } 47 | end 48 | 49 | def correct_length? 50 | @digest_length == @bytes_encoded[2..-1].length 51 | end 52 | end 53 | end -------------------------------------------------------------------------------- /lib/ruby-ipfs-http-client/request/basic_request.rb: -------------------------------------------------------------------------------- 1 | require_relative './request' 2 | 3 | module Ipfs 4 | class BasicRequest < Request 5 | def initialize(path, **arguments) 6 | super(path, :post) 7 | 8 | @multihash = arguments[:multihash] 9 | end 10 | 11 | def options 12 | @multihash \ 13 | ? { params: { arg: @multihash.raw } } \ 14 | : {} 15 | end 16 | end 17 | end -------------------------------------------------------------------------------- /lib/ruby-ipfs-http-client/request/file_upload_request.rb: -------------------------------------------------------------------------------- 1 | require_relative './request' 2 | 3 | module Ipfs 4 | class FileUploadRequest < Request 5 | def initialize(path, filepath) 6 | super(path, :post) 7 | 8 | @filepath = filepath 9 | end 10 | 11 | def options 12 | { 13 | form: { 14 | arg: HTTP::FormData::File.new(@filepath) 15 | } 16 | } 17 | end 18 | end 19 | end -------------------------------------------------------------------------------- /lib/ruby-ipfs-http-client/request/request.rb: -------------------------------------------------------------------------------- 1 | module Ipfs 2 | class Request 3 | attr_reader :path, :verb 4 | 5 | def initialize(path, verb) 6 | @path = path 7 | @verb = verb 8 | end 9 | 10 | def options 11 | {} 12 | end 13 | end 14 | end -------------------------------------------------------------------------------- /lib/ruby-ipfs-http-client/version.rb: -------------------------------------------------------------------------------- 1 | module Ipfs 2 | VERSION = '0.5.1' 3 | end 4 | -------------------------------------------------------------------------------- /ruby-ipfs-http-client.gemspec: -------------------------------------------------------------------------------- 1 | require 'date' 2 | 3 | require_relative './lib/ruby-ipfs-http-client/version' 4 | 5 | Gem::Specification.new do |s| 6 | s.name = 'ruby-ipfs-http-client' 7 | s.version = Ipfs::VERSION 8 | s.date = Date.today.to_s 9 | s.summary = 'ipfs client' 10 | s.description = 'A client library for the IPFS HTTP API, implemented in Ruby' 11 | s.authors = ['Tom Benett', 'Neil Nilou'] 12 | s.email = 'tom@benett.io' 13 | s.files = Dir['lib/**/*.rb'] 14 | s.homepage = 15 | 'https://github.com/tbenett/ruby-ipfs-http-client' 16 | s.license = 'MIT' 17 | 18 | s.required_ruby_version = '>= 2.4' 19 | 20 | s.add_development_dependency 'rspec', '~> 3.8.0' 21 | s.add_development_dependency 'webmock', '~>3.5.1' 22 | end 23 | 24 | -------------------------------------------------------------------------------- /spec/api/files/add_spec.rb: -------------------------------------------------------------------------------- 1 | require_relative '../../../lib/ruby-ipfs-http-client/api/command' 2 | require_relative '../../../lib/ruby-ipfs-http-client/api/files/add' 3 | 4 | describe Ipfs::Command::Add do 5 | it 'has the default path' do 6 | expect(described_class::PATH).to eq '/add' 7 | end 8 | 9 | describe '.build_request' do 10 | let(:filepath) { File.join('spec', 'fixtures', 'api', 'files', 'sample') } 11 | let(:request) { described_class.build_request filepath } 12 | 13 | it 'returns a request' do 14 | expect(request).to be_a_kind_of Ipfs::Request 15 | end 16 | 17 | it 'has a request where the path is the commands one' do 18 | expect(request.path).to eq described_class::PATH 19 | end 20 | 21 | it 'has a request where the verb is POST' do 22 | expect(request.verb).to eq :post 23 | end 24 | 25 | it 'has a request options containing the FormData object' do 26 | expect(request.options).to match form: { 27 | arg: an_instance_of(HTTP::FormData::File) 28 | } 29 | end 30 | end 31 | 32 | describe '.parse_response' do 33 | let(:add_response) { 34 | File.read File.join('spec', 'fixtures', 'api', 'files', 'add_response.json') 35 | } 36 | let(:response) { described_class.parse_response add_response } 37 | 38 | it 'has the correct file name' do 39 | expect(response['Name']).to eq JSON.parse(add_response)['Name'] 40 | end 41 | 42 | it 'has the correct multihash' do 43 | expect(response['Hash']).to eq JSON.parse(add_response)['Hash'] 44 | end 45 | 46 | it 'has the correct size' do 47 | expect(response['Size']).to eq JSON.parse(add_response)['Size'] 48 | end 49 | end 50 | end -------------------------------------------------------------------------------- /spec/api/files/cat_spec.rb: -------------------------------------------------------------------------------- 1 | require_relative '../../../lib/ruby-ipfs-http-client/api/command' 2 | require_relative '../../../lib/ruby-ipfs-http-client/api/files/cat' 3 | 4 | describe Ipfs::Command::Cat do 5 | it 'has the default path' do 6 | expect(described_class::PATH).to eq '/cat' 7 | end 8 | 9 | describe '.build_request' do 10 | let(:multihash) { 11 | double('Ipfs::Multihash', 12 | raw: 'QmYqt8otasXXSrqEw32CwfAK7BFdciW9E9oej52JnVabfW') 13 | } 14 | 15 | let(:request) { described_class.build_request multihash } 16 | 17 | it 'returns a request' do 18 | expect(request).to be_a_kind_of Ipfs::Request 19 | end 20 | 21 | it 'has a request where the path is the commands one' do 22 | expect(request.path).to eq described_class::PATH 23 | end 24 | 25 | it 'has a request where the verb is POST' do 26 | expect(request.verb).to eq :post 27 | end 28 | 29 | it 'has a request options containing the multihash' do 30 | expect(request.options).to eq params: { arg: multihash.raw } 31 | end 32 | end 33 | 34 | describe '.parse_response' do 35 | let(:response) { double('HTTP::Response') } 36 | 37 | it 'provides the response as a DagStream' do 38 | allow(response).to receive_message_chain(:status, :code) { 200 } 39 | allow(response).to receive(:body) { 'A content' } 40 | 41 | expect(described_class.parse_response response).to be_an Ipfs::DagStream 42 | end 43 | end 44 | end 45 | -------------------------------------------------------------------------------- /spec/api/files/ls_spec.rb: -------------------------------------------------------------------------------- 1 | require_relative '../../../lib/ruby-ipfs-http-client/api/command' 2 | require_relative '../../../lib/ruby-ipfs-http-client/api/files/ls' 3 | 4 | describe Ipfs::Command::Ls do 5 | it 'has the default path' do 6 | expect(described_class::PATH).to eq '/ls' 7 | end 8 | 9 | describe '.build_request' do 10 | context 'multihash is valid' do 11 | let(:multihash) { 'QmYqt8otasXXSrqEw32CwfAK7BFdciW9E9oej52JnVabfW' } 12 | let(:request) { described_class.build_request multihash } 13 | 14 | it 'returns a request' do 15 | expect(request).to be_a_kind_of Ipfs::Request 16 | end 17 | 18 | it 'has a request where the path is the commands one' do 19 | expect(request.path).to eq described_class::PATH 20 | end 21 | 22 | it 'has a request where the verb is POST' do 23 | expect(request.verb).to eq :post 24 | end 25 | 26 | it 'has a request options containing the multihash' do 27 | expect(request.options).to eq params: { arg: multihash } 28 | end 29 | end 30 | 31 | context 'multihash is invalid' do 32 | it 'cannot perform the request' do 33 | expect { described_class.build_request '' }.to raise_error Ipfs::Error::InvalidMultihash 34 | end 35 | end 36 | end 37 | 38 | describe '.parse_response' do 39 | context 'multihash given point to a directory' do 40 | let(:api_response) { double('HTTP::Response') } 41 | let(:parsed_response) { described_class.parse_response api_response } 42 | 43 | before do 44 | allow(api_response).to receive_message_chain(:status, :code) { 200 } 45 | allow(api_response).to receive(:body) { 46 | File.read File.join('spec', 'fixtures', 'api', 'files', 'ls_output.json') 47 | } 48 | end 49 | 50 | it 'returns a collection containing links to Ipfs objects' do 51 | expect(parsed_response).to be_an Array 52 | expect(parsed_response[0].keys).to eq %w(Name Hash Size Type) 53 | end 54 | end 55 | end 56 | end -------------------------------------------------------------------------------- /spec/api/generic/id_spec.rb: -------------------------------------------------------------------------------- 1 | require_relative '../../../lib/ruby-ipfs-http-client/api/command' 2 | require_relative '../../../lib/ruby-ipfs-http-client/api/generic/id' 3 | 4 | describe Ipfs::Command::Id do 5 | let(:client_id) { File.read File.join('spec', 'fixtures', 'id.json') } 6 | 7 | it 'has the default path' do 8 | expect(described_class::PATH).to eq '/id' 9 | end 10 | 11 | describe '.build_request' do 12 | let(:request) { described_class.build_request } 13 | 14 | it 'returns a request' do 15 | expect(request).to be_a_kind_of Ipfs::Request 16 | end 17 | 18 | it 'has a request where the path is the commands one' do 19 | expect(request.path).to eq described_class::PATH 20 | end 21 | 22 | it 'has a request where the verb is POST' do 23 | expect(request.verb).to eq :post 24 | end 25 | 26 | it 'has a request without options' do 27 | expect(request.options).to eq Hash.new 28 | end 29 | end 30 | 31 | describe '.parse_response' do 32 | let(:response) { described_class.parse_response client_id } 33 | 34 | it 'parse the response from a json formatted data' do 35 | expect(response).to be_a Hash 36 | end 37 | end 38 | end 39 | -------------------------------------------------------------------------------- /spec/api/generic/version_spec.rb: -------------------------------------------------------------------------------- 1 | require_relative '../../../lib/ruby-ipfs-http-client/api/command' 2 | require_relative '../../../lib/ruby-ipfs-http-client/api/generic/version' 3 | 4 | describe Ipfs::Command::Version do 5 | let(:daemon_version) { File.read File.join('spec', 'fixtures', 'version.json') } 6 | 7 | it 'has the default path' do 8 | expect(described_class::PATH).to eq '/version' 9 | end 10 | 11 | describe '.build_request' do 12 | let(:request) { described_class.build_request } 13 | 14 | it 'returns a request' do 15 | expect(request).to be_a_kind_of Ipfs::Request 16 | end 17 | 18 | it 'has a request where the path is the commands one' do 19 | expect(request.path).to eq described_class::PATH 20 | end 21 | 22 | it 'has a request where the verb is POST' do 23 | expect(request.verb).to eq :post 24 | end 25 | 26 | it 'has a request without options' do 27 | expect(request.options).to eq Hash.new 28 | end 29 | end 30 | 31 | describe '.parse_response' do 32 | let(:response) { described_class.parse_response daemon_version } 33 | 34 | it 'parse the response from a json formatted data' do 35 | expect(response).to be_a Hash 36 | end 37 | end 38 | end 39 | -------------------------------------------------------------------------------- /spec/base_spec.rb: -------------------------------------------------------------------------------- 1 | require_relative '../lib/ruby-ipfs-http-client/base' 2 | 3 | RSpec.describe Ipfs::Base58 do 4 | it 'has the bitcoin alphabet' do 5 | expect(described_class::ALPHABET).to eq '123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz' 6 | end 7 | 8 | it 'has the base set to 58' do 9 | expect(described_class::BASE).to eq 58 10 | end 11 | 12 | describe '.decode' do 13 | describe 'the base 58 encoded number is passed as sequence of characters' do 14 | context 'when the sequence is invalid' do 15 | it 'returns a base 10 number equal to 0' do 16 | expect(described_class.decode '').to eq 0 17 | expect(described_class.decode ';)').to eq 0 18 | expect(described_class.decode ";#{described_class::ALPHABET}").to eq 0 19 | expect(described_class.decode "#{described_class::ALPHABET};").to eq 0 20 | end 21 | end 22 | 23 | context 'when the sequence is a valid base 58 number' do 24 | it 'returns the corresponding byte collection' do 25 | expect(described_class.decode 'j').to eq 42 26 | end 27 | end 28 | end 29 | end 30 | 31 | describe '.encode' do 32 | describe 'a base 10 number is given' do 33 | context 'when the number is not an integer' do 34 | it 'returns an empty sequence of characters if the collection is empty' do 35 | expect(described_class.encode '').to eq '' 36 | end 37 | end 38 | 39 | context 'when the byte array is valid' do 40 | it 'returns the number encoded in base 58' do 41 | expect(described_class.encode 42).to eq 'j' 42 | end 43 | end 44 | end 45 | end 46 | end -------------------------------------------------------------------------------- /spec/client_spec.rb: -------------------------------------------------------------------------------- 1 | require_relative '../lib/ipfs' 2 | 3 | RSpec.describe Ipfs::Client do 4 | let(:id_command_url) { 'http://localhost:5001/api/v0/id' } 5 | 6 | describe 'attempting connection to API using different methods' do 7 | it 'knows two methods to connect to the api' do 8 | expect(described_class::CONNECTION_METHODS.length).to eq 3 9 | end 10 | 11 | describe '#attempt_connection' do 12 | context 'an attempt succeeded' do 13 | it 'is connected to Ipfs' do 14 | expect(described_class.send(:attempt_connection)).to_not be_nil 15 | end 16 | end 17 | 18 | context 'all attempt have failed' do 19 | # program terminates 20 | end 21 | end 22 | end 23 | 24 | describe '#call' do 25 | let(:id_command) { double('Ipfs::Request', verb: :get, path: '/id', options: {}) } 26 | 27 | context 'when IPFS is unreachable' do 28 | before do 29 | stub_request(:get, id_command_url) 30 | .to_raise(HTTP::ConnectionError.new) 31 | end 32 | 33 | it 'fails to perform call the Ipfs API' do 34 | expect { 35 | described_class.send(:call, id_command) 36 | }.to raise_error Ipfs::Error::UnreachableDaemon 37 | end 38 | end 39 | 40 | context 'when IPFS is reachable' do 41 | it 'can call the Ipfs API' do 42 | expect { described_class.send(:call, id_command) }.not_to raise_error 43 | end 44 | end 45 | end 46 | 47 | describe '#id' do 48 | let(:id) { described_class.id } 49 | 50 | it 'returns the id' do 51 | expect(id[:peer_id]).to be_a String 52 | expect(id[:addresses]).to match a_collection_including(String) 53 | expect(id[:public_key]).to be_a String 54 | expect(id[:agent_version]).to be_a String 55 | end 56 | end 57 | 58 | describe '#daemon' do 59 | let(:daemon) { described_class.daemon } 60 | 61 | it 'returns the daemon informations' do 62 | expect(daemon[:version]).to be_a String 63 | expect(daemon[:commit]).to be_a String 64 | expect(daemon[:repo]).to be_a String 65 | expect(daemon[:system]).to be_a String 66 | expect(daemon[:golang]).to be_a String 67 | expect(daemon[:api]).to be_a String 68 | end 69 | end 70 | end -------------------------------------------------------------------------------- /spec/connection/base_spec.rb: -------------------------------------------------------------------------------- 1 | require_relative '../../lib/ruby-ipfs-http-client/connection/base' 2 | 3 | RSpec.describe Ipfs::Connection::Base do 4 | let(:connection) { described_class.new } 5 | 6 | it 'has a default base path' do 7 | expect(described_class::DEFAULT_BASE_PATH).to eq '/api/v0' 8 | end 9 | 10 | it 'has no host' do 11 | expect(connection.host).to be_nil 12 | end 13 | 14 | it 'has no port' do 15 | expect(connection.port).to be_nil 16 | end 17 | 18 | describe '#build_uri' do 19 | it 'returns a well formatted URI' do 20 | expect(connection.build_uri).to be_a URI::HTTP 21 | end 22 | end 23 | end -------------------------------------------------------------------------------- /spec/connection/default_spec.rb: -------------------------------------------------------------------------------- 1 | require_relative '../../lib/ruby-ipfs-http-client/connection/default' 2 | 3 | RSpec.describe Ipfs::Connection::Default do 4 | let(:location) { { host: 'localhost', port: 5001 } } 5 | let(:connection) { described_class.new } 6 | 7 | it 'is a Connection' do 8 | expect(connection).to be_a Ipfs::Connection::Base 9 | end 10 | 11 | it 'has set a host' do 12 | expect(connection.host).to eq location[:host] 13 | end 14 | 15 | it 'has set a port' do 16 | expect(connection.port).to eq location[:port] 17 | end 18 | 19 | describe '#up?' do 20 | context 'the connection fails' do 21 | before do 22 | stub_request(:post, "http://#{location[:host]}:#{location[:port]}/api/v0/id") 23 | .to_raise HTTP::ConnectionError 24 | end 25 | 26 | it 'returns false' do 27 | expect(connection.up?).to eq false 28 | end 29 | end 30 | 31 | context 'the connection succeed' do 32 | before do 33 | stub_request(:post, "http://#{location[:host]}:#{location[:port]}/api/v0/id") 34 | end 35 | 36 | it 'returns true' do 37 | expect(connection.up?).to eq true 38 | end 39 | end 40 | end 41 | 42 | describe '#make_persistent' do 43 | it 'makes a persistent connection' do 44 | expect(connection.make_persistent).to be_a HTTP::Client 45 | end 46 | end 47 | end -------------------------------------------------------------------------------- /spec/connection/ipfs_config_spec.rb: -------------------------------------------------------------------------------- 1 | require_relative '../../lib/ruby-ipfs-http-client/connection/ipfs_config' 2 | 3 | RSpec.describe Ipfs::Connection::IpfsConfig do 4 | let(:location) { { host: '127.0.0.1', port: 5001 } } 5 | let(:connection) { described_class.new } 6 | 7 | before do 8 | allow(::File).to receive(:read) 9 | .with(described_class::CONFIG_FILEPATH) 10 | .and_return(::File.read ::File.join('spec', 'fixtures', 'ipfs', 'config')) 11 | end 12 | 13 | it 'is a Connection' do 14 | expect(connection).to be_a Ipfs::Connection::Base 15 | end 16 | 17 | it 'knows the Ipfs configuration file path' do 18 | expect(described_class::CONFIG_FILEPATH).to eq "#{ENV['HOME']}/.ipfs/config" 19 | end 20 | 21 | describe '#parse_config' do 22 | it 'retrieves API location from the configuration file' do 23 | expect(connection.send(:parse_config)).to match location 24 | end 25 | end 26 | 27 | describe '#up?' do 28 | context 'the connection fails' do 29 | before do 30 | stub_request(:post, "http://#{location[:host]}:#{location[:port]}/api/v0/id") 31 | .to_raise HTTP::ConnectionError 32 | end 33 | 34 | it 'returns false' do 35 | expect(connection.up?).to eq false 36 | end 37 | end 38 | 39 | context 'the connection succeed' do 40 | before do 41 | stub_request(:post, "http://#{location[:host]}:#{location[:port]}/api/v0/id") 42 | end 43 | 44 | it 'returns true' do 45 | expect(connection.up?).to eq true 46 | end 47 | end 48 | end 49 | 50 | describe '#make_persistent' do 51 | it 'makes a persistent connection' do 52 | expect(connection.make_persistent).to be_a HTTP::Client 53 | end 54 | end 55 | end -------------------------------------------------------------------------------- /spec/connection/unreachable_spec.rb: -------------------------------------------------------------------------------- 1 | require_relative '../../lib/ruby-ipfs-http-client/connection/unreachable' 2 | 3 | RSpec.describe Ipfs::Connection::Unreachable do 4 | let(:connection) { described_class.new } 5 | 6 | it 'is a Connection' do 7 | expect(connection).to be_a Ipfs::Connection::Base 8 | end 9 | 10 | it 'has no host' do 11 | expect(connection.host).to be_nil 12 | end 13 | 14 | it 'has no port' do 15 | expect(connection.port).to be_nil 16 | end 17 | 18 | describe '#up?' do 19 | it 'can`t reach API' do 20 | expect { connection.up? }.to raise_error Ipfs::Error::UnreachableDaemon 21 | end 22 | end 23 | end -------------------------------------------------------------------------------- /spec/dagstream_spec.rb: -------------------------------------------------------------------------------- 1 | require_relative '../lib/ruby-ipfs-http-client/dagstream' 2 | 3 | RSpec.describe Ipfs::DagStream do 4 | describe '.initialize' do 5 | context 'when response from Ipfs API is leading to a directory' do 6 | let(:response) { double('HTTP::Response') } 7 | 8 | it 'throws an error informing that the dag node was invalid' do 9 | allow(response).to receive_message_chain(:status, :code) { 500 } 10 | allow(response).to receive(:body) { 11 | File.read \ 12 | File.join('spec', 'fixtures', 'api', 'files', 'cat_invalid_dag_stream.json') 13 | } 14 | 15 | expect { 16 | described_class.new response 17 | }.to raise_error(Ipfs::Error::InvalidDagStream, 'this dag node is a directory') 18 | end 19 | end 20 | 21 | context 'when response from Ipfs API is leading to a content' do 22 | before do 23 | allow(response).to receive_message_chain(:status, :code) { 200 } 24 | allow(response).to receive(:body) { 'A content' } 25 | end 26 | 27 | let(:response) { double('HTTP::Response') } 28 | let(:dag_stream) { described_class.new response } 29 | 30 | it 'instantiates a dag stream' do 31 | expect(dag_stream).to be_a Ipfs::DagStream 32 | end 33 | 34 | describe '#to_s' do 35 | it 'returns the content' do 36 | expect(dag_stream.to_s).to eq 'A content' 37 | end 38 | end 39 | 40 | describe '#to_str' do 41 | it 'returns the content' do 42 | expect(dag_stream.to_str).to eq 'A content' 43 | end 44 | end 45 | end 46 | end 47 | end 48 | -------------------------------------------------------------------------------- /spec/file_spec.rb: -------------------------------------------------------------------------------- 1 | require_relative '../lib/ruby-ipfs-http-client/file' 2 | 3 | RSpec.describe Ipfs::File do 4 | let(:path) { ::File.join('spec', 'fixtures', 'dune.txt') } 5 | 6 | describe '.initialize' do 7 | context 'given a file path' do 8 | it 'can creates an Ipfs file from a filepath if the file exists' do 9 | expect(described_class.new path: path).to be_a described_class 10 | end 11 | 12 | it 'raises an error if the path given does not lead to a file' do 13 | expect { described_class.new path: '' }.to raise_error Errno::ENOENT 14 | end 15 | 16 | it 'has no metadata associated with the file given by Ipfs' do 17 | file = described_class.new path: path 18 | 19 | expect(file.name).to be_nil 20 | expect(file.size).to be_nil 21 | expect(file.multihash).to be_nil 22 | end 23 | end 24 | 25 | context 'given a multihash' do 26 | context 'when multihash is a string' do 27 | let(:multihash) { 'QmcUewL8t3B4aW1VTwbLLgxQLi4hMFrb1ASwLsM1uebSs5' } 28 | let(:file) { described_class.new multihash: multihash } 29 | 30 | it 'can create an Ipfs file if the multihash is valid' do 31 | expect(file).to be_a described_class 32 | end 33 | 34 | it 'cannot create the file if the multihash is invalid' do 35 | expect{ 36 | described_class.new multihash: '' 37 | }.to raise_error Ipfs::Error::InvalidMultihash 38 | end 39 | 40 | it 'has the multihash set' do 41 | expect(file.multihash).to be_a Ipfs::Multihash 42 | end 43 | 44 | it 'size and name are not set' do 45 | expect(file.name).to be_nil 46 | expect(file.size).to be_nil 47 | end 48 | end 49 | 50 | context 'when multihash is an object' do 51 | let(:multihash) { 52 | Ipfs::Multihash.new('QmcUewL8t3B4aW1VTwbLLgxQLi4hMFrb1ASwLsM1uebSs5') 53 | } 54 | let(:file) { described_class.new multihash: multihash} 55 | 56 | it 'can create an Ipfs file' do 57 | expect(file).to be_a described_class 58 | end 59 | 60 | it 'has the multihash set' do 61 | expect(file.multihash).to eq multihash 62 | end 63 | 64 | it 'size and name are not set' do 65 | expect(file.name).to be_nil 66 | expect(file.size).to be_nil 67 | end 68 | end 69 | 70 | context 'when multihash is an unexpected object' do 71 | it 'raises an error' do 72 | expect { 73 | described_class.new multihash: 93 74 | }.to raise_error Ipfs::Error::InvalidMultihash 75 | end 76 | end 77 | end 78 | end 79 | 80 | describe '#add' do 81 | context 'when the file was never added to Ipfs' do 82 | let(:file) { described_class.new path: path } 83 | 84 | before do 85 | expect(Ipfs::Client) 86 | .to receive(:execute) 87 | .with(Ipfs::Command::Add, path) 88 | .and_call_original 89 | end 90 | 91 | it 'adds the file to Ipfs' do 92 | file.add 93 | end 94 | 95 | it 'has metadata associated with the file given by Ipfs' do 96 | file.add 97 | 98 | expect(file.name).to eq ::File.basename(path) 99 | expect(file.size).to be_an Integer 100 | expect(file.multihash).to be_a Ipfs::Multihash 101 | end 102 | end 103 | 104 | context 'the file has already been added to Ipfs' do 105 | let(:file) { described_class.new path: path } 106 | 107 | before do 108 | file.add 109 | 110 | expect(Ipfs::Client) 111 | .to_not receive(:execute) 112 | .with(Ipfs::Command::Add, path) 113 | .and_call_original 114 | end 115 | 116 | it 'does not call the client twice' do 117 | file.add 118 | end 119 | 120 | it 'has metadata associated with the file given by Ipfs' do 121 | expect(file.name).to eq ::File.basename(path) 122 | expect(file.size).to be_an Integer 123 | expect(file.multihash).to be_a Ipfs::Multihash 124 | end 125 | end 126 | end 127 | 128 | describe '#cat' do 129 | context 'when the hash is known' do 130 | let(:multihash) { Ipfs::File.new(path: path).add.multihash } 131 | let(:file) { Ipfs::File.new(multihash: multihash) } 132 | 133 | it 'returns the file`s content' do 134 | expect(file.cat).to eq ::File.read path 135 | end 136 | end 137 | 138 | context 'when the hash is unknown' do 139 | let(:file) { Ipfs::File.new(path: path) } 140 | 141 | it 'returns a null value' do 142 | expect(file.cat).to be_nil 143 | end 144 | end 145 | 146 | context 'when the hash is unknown' do 147 | let(:file) { Ipfs::File.new(path: path) } 148 | 149 | it 'returns a null value' do 150 | expect(file.cat).to be_nil 151 | end 152 | end 153 | end 154 | end -------------------------------------------------------------------------------- /spec/fixtures/api/files/add_response.json: -------------------------------------------------------------------------------- 1 | { 2 | "Name": "README.md", 3 | "Hash": "QmYqt8otasXXSrqEw32CwfAK7BFdciW9E9oej52JnVabfW", 4 | "Size": "1935" 5 | } -------------------------------------------------------------------------------- /spec/fixtures/api/files/cat_invalid_dag_stream.json: -------------------------------------------------------------------------------- 1 | {"Message":"this dag node is a directory","Code":0} 2 | -------------------------------------------------------------------------------- /spec/fixtures/api/files/cat_invalid_ipfs_ref.json: -------------------------------------------------------------------------------- 1 | {"Message":"invalid 'ipfs ref' path","Code":0} 2 | -------------------------------------------------------------------------------- /spec/fixtures/api/files/ls_invalid_dag_node.json: -------------------------------------------------------------------------------- 1 | {"Message":"merkledag node was not a directory or shard","Code":0} 2 | -------------------------------------------------------------------------------- /spec/fixtures/api/files/ls_invalid_ipfs_ref.json: -------------------------------------------------------------------------------- 1 | {"Message":"invalid 'ipfs ref' path","Code":0} 2 | -------------------------------------------------------------------------------- /spec/fixtures/api/files/ls_output.json: -------------------------------------------------------------------------------- 1 | {"Objects":[{"Hash":"QmTwSz3dmhMrJHWTBoLkQbUtYtzeGwrkSpaHVzCno87AbK","Links":[{"Name":"tata","Hash":"QmZAyfP3mSDG4ohavN1cYzKmqBkHQ6shhEACjBrKP6PMeW","Size":66,"Type":1}]}]} 2 | -------------------------------------------------------------------------------- /spec/fixtures/api/files/sample: -------------------------------------------------------------------------------- 1 | sample data -------------------------------------------------------------------------------- /spec/fixtures/dune.txt: -------------------------------------------------------------------------------- 1 | A very good book from Frank Herbert. 2 | 3 | 4 | You should read it if you haven't already ;) -------------------------------------------------------------------------------- /spec/fixtures/id.json: -------------------------------------------------------------------------------- 1 | { 2 | "ID": "QmfKeymyKySoRHK6yVopcszYbisew9FRTqGHjasbo2oSeY", 3 | "PublicKey": "CAASpgIwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDLJApNPZT1nnsy9WxFckuvEuPPCFw/G7TVakCL3XZ8JJQgALBF0baopGFHV+XQCa+QTF0UFv5HwggNdas/UrXUW4abJ1u/0d82G+UEqwTnFJpPIRfw6tLVN79Ta/4AwnZWjA2HBVo4TDh1EayOv0uDuWhC5Qc2dvvnf0sawEwvAR4ZlRUbiZw5cCP8pfTT0uTpaFr63zzvhZKvTqonyqpTd+MqceI1oLyAsPYZqQrqLUp4Z59LCnw4JPlZWMAKSTc/UHmE/4bpfrNu6l/IeIm5tjvhH6ytzowRdE+zLEMN4JFO0UsTBgeLrRrG3YGRsrRxhqUaeCSCiZ636hjxnFZtAgMBAAE=", 4 | "Addresses": [ 5 | "/ip6/::1/tcp/4001/ipfs/QmfKeymyKySoRHK6yVopcszYbisew9FRTqGHjasbo2oSeY", 6 | "/ip4/127.0.0.1/tcp/4001/ipfs/QmfKeymyKySoRHK6yVopcszYbisew9FRTqGHjasbo2oSeY", 7 | "/ip4/192.168.1.78/tcp/4001/ipfs/QmfKeymyKySoRHK6yVopcszYbisew9FRTqGHjasbo2oSeY", 8 | "/ip4/176.159.112.200/tcp/4001/ipfs/QmfKeymyKySoRHK6yVopcszYbisew9FRTqGHjasbo2oSeY", 9 | "/ip4/176.159.112.200/tcp/39561/ipfs/QmfKeymyKySoRHK6yVopcszYbisew9FRTqGHjasbo2oSeY" 10 | ], 11 | "AgentVersion": "go-ipfs/0.4.9/d6983801d", 12 | "ProtocolVersion": "ipfs/0.1.0" 13 | } 14 | -------------------------------------------------------------------------------- /spec/fixtures/ipfs/config: -------------------------------------------------------------------------------- 1 | { 2 | "Identity": { 3 | "PeerID": "peerid", 4 | "PrivKey": "privkey" }, 5 | "Datastore": { 6 | "StorageMax": "10GB", 7 | "StorageGCWatermark": 90, 8 | "GCPeriod": "1h", 9 | "Spec": { 10 | "mounts": [ 11 | { 12 | "child": { 13 | "path": "blocks", 14 | "shardFunc": "/repo/flatfs/shard/v1/next-to-last/2", 15 | "sync": true, 16 | "type": "flatfs" 17 | }, 18 | "mountpoint": "/blocks", 19 | "prefix": "flatfs.datastore", 20 | "type": "measure" 21 | }, 22 | { 23 | "child": { 24 | "compression": "none", 25 | "path": "datastore", 26 | "type": "levelds" 27 | }, 28 | "mountpoint": "/", 29 | "prefix": "leveldb.datastore", 30 | "type": "measure" 31 | } 32 | ], 33 | "type": "mount" 34 | }, 35 | "HashOnRead": false, 36 | "BloomFilterSize": 0 37 | }, 38 | "Addresses": { 39 | "Swarm": [ 40 | "/ip4/0.0.0.0/tcp/4001", 41 | "/ip6/::/tcp/4001" 42 | ], 43 | "Announce": [], 44 | "NoAnnounce": [], 45 | "API": "/ip4/127.0.0.1/tcp/5001", 46 | "Gateway": "/ip4/127.0.0.1/tcp/8080" 47 | }, 48 | "Mounts": { 49 | "IPFS": "/ipfs", 50 | "IPNS": "/ipns", 51 | "FuseAllowOther": false 52 | }, 53 | "Discovery": { 54 | "MDNS": { 55 | "Enabled": true, 56 | "Interval": 10 57 | } 58 | }, 59 | "Ipns": { 60 | "RepublishPeriod": "", 61 | "RecordLifetime": "", 62 | "ResolveCacheSize": 128 63 | }, 64 | "Bootstrap": [ 65 | "/dnsaddr/bootstrap.libp2p.io/ipfs/hash", 66 | "/dnsaddr/bootstrap.libp2p.io/ipfs/hash", 67 | "/dnsaddr/bootstrap.libp2p.io/ipfs/hash", 68 | "/dnsaddr/bootstrap.libp2p.io/ipfs/hash", 69 | "/ip4/104.131.131.82/tcp/4001/ipfs/hash", 70 | "/ip4/104.236.179.241/tcp/4001/ipfs/hash", 71 | "/ip4/128.199.219.111/tcp/4001/ipfs/hash", 72 | "/ip4/104.236.76.40/tcp/4001/ipfs/hash", 73 | "/ip4/178.62.158.247/tcp/4001/ipfs/hash", 74 | "/ip6/2604:a880:1:20::203:d001/tcp/4001/ipfs/hash", 75 | "/ip6/2400:6180:0:d0::151:6001/tcp/4001/ipfs/hash", 76 | "/ip6/2604:a880:800:10::4a:5001/tcp/4001/ipfs/hash", 77 | "/ip6/2a03:b0c0:0:1010::23:1001/tcp/4001/ipfs/hash" 78 | ], 79 | "Gateway": { 80 | "HTTPHeaders": { 81 | "Access-Control-Allow-Headers": [ 82 | "X-Requested-With", 83 | "Range" 84 | ], 85 | "Access-Control-Allow-Methods": [ 86 | "GET" 87 | ], 88 | "Access-Control-Allow-Origin": [ 89 | "*" 90 | ] 91 | }, 92 | "RootRedirect": "", 93 | "Writable": false, 94 | "PathPrefixes": [] 95 | }, 96 | "API": { 97 | "HTTPHeaders": null 98 | }, 99 | "Swarm": { 100 | "AddrFilters": null, 101 | "DisableBandwidthMetrics": false, 102 | "DisableNatPortMap": false, 103 | "DisableRelay": false, 104 | "EnableRelayHop": false, 105 | "ConnMgr": { 106 | "Type": "basic", 107 | "LowWater": 600, 108 | "HighWater": 900, 109 | "GracePeriod": "20s" 110 | } 111 | }, 112 | "Reprovider": { 113 | "Interval": "12h", 114 | "Strategy": "all" 115 | }, 116 | "Experimental": { 117 | "FilestoreEnabled": false, 118 | "ShardingEnabled": false, 119 | "Libp2pStreamMounting": false 120 | } 121 | } 122 | -------------------------------------------------------------------------------- /spec/fixtures/version.json: -------------------------------------------------------------------------------- 1 | {"Version":"0.4.9","Commit":"d6983801d","Repo":"5","System":"amd64/linux","Golang":"go1.8.1"} 2 | -------------------------------------------------------------------------------- /spec/multihash_spec.rb: -------------------------------------------------------------------------------- 1 | require_relative '../lib/ruby-ipfs-http-client/multihash' 2 | 3 | RSpec.describe Ipfs::Multihash do 4 | let(:invalid_multihash) { '122041dd7b6443542e75701aa98a0c235951a28a0d851b11564d20022ab11d2589a8' } 5 | let(:valid_multihash) { 'QmYtUc4iTCbbfVSDNKvtQqrfyezPPnFvE33wFmutw9PBBk' } 6 | 7 | context 'the digest is computed using SHA2-256 algorithm' do 8 | it 'has SHA2-256 function type' do 9 | expect(described_class.new(valid_multihash).hash_func_type).to eq :sha256 10 | end 11 | 12 | it 'has a digest length of 32 bytes' do 13 | expect(described_class.new(valid_multihash).digest_length).to eq 32 14 | end 15 | end 16 | 17 | describe '#raw' do 18 | it 'returns the multihash' do 19 | expect(described_class.new(valid_multihash).raw).to eq valid_multihash 20 | end 21 | end 22 | 23 | describe '#initialize' do 24 | context 'given a raw representation of a multihash' do 25 | it 'instantiates a new object' do 26 | expect(described_class.new(valid_multihash)).to be_a described_class 27 | end 28 | 29 | it 'throws an error if the given hash is invalid' do 30 | expect { 31 | described_class.new invalid_multihash 32 | }.to raise_error(Ipfs::Error::InvalidMultihash, "The hash func type could not be found") 33 | end 34 | end 35 | 36 | context 'multihash is an unexpected object' do 37 | it 'throws an error if the given hash is invalid' do 38 | expect { described_class.new 34 }.to raise_error Ipfs::Error::InvalidMultihash 39 | end 40 | end 41 | end 42 | end -------------------------------------------------------------------------------- /spec/request/basic_request_spec.rb: -------------------------------------------------------------------------------- 1 | require_relative '../../lib/ruby-ipfs-http-client/request/basic_request' 2 | 3 | RSpec.describe Ipfs::BasicRequest do 4 | let(:id_path) { '/id' } 5 | let(:multihash) { double("Ipfs::Multihash", raw: 'Qmfootruc') } 6 | 7 | describe '.initialize' do 8 | it 'needs at least a path to be instantiated' do 9 | expect(described_class.new id_path).to be_a described_class 10 | end 11 | 12 | it 'is a request' do 13 | expect(described_class.new id_path).to be_a_kind_of Ipfs::Request 14 | end 15 | 16 | it 'can take an optional multihash' do 17 | expect(described_class.new id_path, multihash: multihash).to be_a described_class 18 | end 19 | end 20 | 21 | describe '#path' do 22 | let(:id_request) { described_class.new id_path } 23 | 24 | it 'gives back the path passed at the instantiation' do 25 | expect(id_request.path).to eq id_path 26 | end 27 | end 28 | 29 | describe '#verb' do 30 | let(:id_request) { described_class.new id_path } 31 | 32 | it 'gives back the POST HTTP verb' do 33 | expect(id_request.verb).to eq :post 34 | end 35 | end 36 | 37 | describe '#options' do 38 | context 'request instantiated without multihash' do 39 | let(:id_request) { described_class.new id_path } 40 | 41 | it 'gives back an empty hash' do 42 | expect(id_request.options).to eq Hash.new 43 | end 44 | end 45 | 46 | context 'request instantiated with a multihash' do 47 | let(:id_request) { described_class.new id_path, multihash: multihash } 48 | 49 | it 'gives back the multihash wrapped in a format understandable for HTTP library' do 50 | expect(id_request.options).to eq params: { arg: multihash.raw } 51 | end 52 | end 53 | end 54 | end -------------------------------------------------------------------------------- /spec/request/file_upload_request_spec.rb: -------------------------------------------------------------------------------- 1 | require_relative '../../lib/ruby-ipfs-http-client/request/file_upload_request' 2 | 3 | RSpec.describe Ipfs::FileUploadRequest do 4 | let(:add_path) { '/add' } 5 | let(:filepath) { 'path/to/file' } 6 | 7 | describe '.initialize' do 8 | 9 | it 'needs a command path and a file path to be instantiated' do 10 | expect(described_class.new add_path, filepath).to be_a described_class 11 | end 12 | 13 | it 'is a request' do 14 | expect(described_class.new add_path, filepath).to be_a_kind_of Ipfs::Request 15 | end 16 | end 17 | 18 | describe '#path' do 19 | let(:add_request) { described_class.new add_path, filepath } 20 | 21 | it 'gives back the path passed at the instantiation' do 22 | expect(add_request.path).to eq add_path 23 | end 24 | end 25 | 26 | describe '#verb' do 27 | let(:add_request) { described_class.new add_path, filepath } 28 | 29 | it 'gives back the POST HTTP verb' do 30 | expect(add_request.verb).to eq :post 31 | end 32 | end 33 | 34 | describe '#options' do 35 | let(:add_request) { described_class.new add_path, file_upload: filepath } 36 | let(:request_options) { add_request.options } 37 | 38 | it 'gives back a hash containing the file prepared for an HTTP upload' do 39 | expect(request_options).to match form: { 40 | arg: an_instance_of(HTTP::FormData::File) 41 | } 42 | end 43 | end 44 | end -------------------------------------------------------------------------------- /spec/request/request_spec.rb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tomibennett/ruby-ipfs-http-client/0c26a066f748ace19eadbefc60e97636bb3d075d/spec/request/request_spec.rb -------------------------------------------------------------------------------- /spec/spec_helper.rb: -------------------------------------------------------------------------------- 1 | require 'webmock/rspec' 2 | 3 | RSpec.configure do |config| 4 | config.expect_with :rspec do |expectations| 5 | expectations.include_chain_clauses_in_custom_matcher_descriptions = true 6 | expectations.syntax = :expect 7 | end 8 | 9 | begin 10 | config.order = :random 11 | 12 | # # clean up Ipfs cache after running specs 13 | # config.after(:all) do 14 | # %x(var/bin/clear_garbage_collector.sh) 15 | # end 16 | # 17 | # # clean up Ipfs local storage after each spec 18 | # config.after do 19 | # %x(var/bin/remove_pinned_objects.sh) 20 | # end 21 | 22 | Kernel.srand config.seed 23 | end 24 | 25 | WebMock.disable_net_connect!(allow_localhost: true) 26 | end 27 | 28 | require_relative '../lib/ruby-ipfs-http-client/client' -------------------------------------------------------------------------------- /var/bin/clear_garbage_collector.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | docker exec ipfs_daemon ipfs repo gc -------------------------------------------------------------------------------- /var/bin/install_go.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | golang_version='1.9.2' 4 | golang_checksum='de874549d9a8d8d8062be05808509c09a88a248e77ec14eb77453530829ac02b' 5 | golang_package=go${golang_version}.linux-amd64.tar.gz 6 | golang_install_path=/usr/local 7 | go_path='export GOPATH=${HOME}/go' 8 | golang_export_path='export PATH="/usr/local/go/bin:${GOPATH}/bin:${PATH}"' 9 | 10 | cat > /tmp/${golang_package}_checksum < /dev/null 21 | 22 | echo 'Checking go installation...' 23 | 24 | [[ ! `shasum -a 256 -c /tmp/${golang_package}_checksum &> /dev/null` ]] \ 25 | && tar -C ${golang_install_path} -xzf /tmp/${golang_package} 26 | done 27 | 28 | [[ ! `grep "go/bin" ${HOME}/.bashrc` ]] \ 29 | && echo ${go_path} >> ${HOME}/.bashrc \ 30 | && echo ${golang_export_path} >> ${HOME}/.bashrc 31 | -------------------------------------------------------------------------------- /var/bin/install_ipfs.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | install_path=/usr/local 4 | ipfs_version='v0.4.13' 5 | 6 | echo 'Downloading ipfs-update, this may take a while...' 7 | [[ ! -d /root/go ]] \ 8 | && ${install_path}/go/bin/go get -u github.com/ipfs/ipfs-update &> /dev/null 9 | 10 | echo "Downloading IPFS ${ifps_version}, this may take a while..." 11 | /root/go/bin/ipfs-update install ${ipfs_version} 12 | 13 | echo 'init ipfs' 14 | su vagrant -c "${install_path}/bin/ipfs init &> /dev/null" 15 | -------------------------------------------------------------------------------- /var/bin/install_ruby.sh: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env bash 2 | 3 | rbenv_root="${HOME}/.rbenv" 4 | 5 | if [[ ! -d ${rbenv_root} ]] 6 | then 7 | echo 'Downloading rbenv...' 8 | git clone https://github.com/rbenv/rbenv.git ${rbenv_root} &> /dev/null 9 | 10 | [[ ! `echo ${PATH} | grep '.rbenv'` ]] \ 11 | && echo 'export PATH="$HOME/.rbenv/bin:$PATH"' >> ~/.bashrc \ 12 | && echo 'eval "$(rbenv init -)"' >> ~/.bashrc 13 | 14 | echo 'Downloading ruby-build...' 15 | [[ ! -d ${rbenv_root}/plugins ]] \ 16 | && mkdir -p ${rbenv_root}/plugins \ 17 | && git clone https://github.com/rbenv/ruby-build.git ${rbenv_root}/plugins/ruby-build &> /dev/null 18 | 19 | echo 'Installing ruby...' 20 | ${rbenv_root}/bin/rbenv install `cat ${HOME}/ruby-ipfs-api/.ruby-version` &> /dev/null 21 | ${rbenv_root}/bin/rbenv global `cat ${HOME}/ruby-ipfs-api/.ruby-version` &> /dev/null 22 | fi 23 | -------------------------------------------------------------------------------- /var/bin/ipfsd: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | docker run --name ipfsd -p 5001:5001 -d ipfs/go-ipfs daemon --init 4 | -------------------------------------------------------------------------------- /var/bin/provision.sh: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env bash 2 | 3 | project_root=/home/vagrant/ruby-ipfs-api 4 | 5 | # Set the clock 6 | 7 | # Set motd 8 | cat > /etc/motd <" 2 | 3 | description "start ipfs daemon at startup" 4 | 5 | env USER=vagrant 6 | 7 | start on runlevel [2345] 8 | 9 | stop on runlevel [016] 10 | 11 | respawn 12 | 13 | pre-stop script 14 | # clean up all refs & object in Ipfs 15 | ipfs pin rm `ipfs pin ls --type=recursive | cut -d ' ' -f 1` 16 | ipfs repo gc 17 | end script 18 | 19 | 20 | exec start-stop-daemon --start --chuid $USER --exec '/usr/local/bin/ipfs --offline' -- daemon >> /var/log/ipfs.log 2>&1 21 | --------------------------------------------------------------------------------