ipfs-api 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/LICENSE +21 -0
- data/README.md +59 -0
- data/Rakefile +103 -0
- data/examples/basic.rb +4 -0
- data/examples/data/hello.txt +1 -0
- data/examples/upload.rb +15 -0
- data/ipfs-api.gemspec +48 -0
- data/lib/ipfs-api.rb +12 -0
- data/lib/ipfs-api/connection.rb +150 -0
- data/lib/ipfs-api/io.rb +39 -0
- data/lib/ipfs-api/upload.rb +182 -0
- data/lib/ipfs-api/version.rb +4 -0
- data/test/common.rb +4 -0
- data/test/samples.rb +84 -0
- data/test/test_cmd_add.rb +40 -0
- data/test/test_cmd_cat.rb +19 -0
- data/test/test_cmd_get.rb +19 -0
- data/test/test_cmd_id.rb +17 -0
- data/test/test_cmd_ls.rb +40 -0
- data/test/test_cmd_name.rb +16 -0
- data/test/test_io.rb +20 -0
- data/test/test_upload.rb +20 -0
- metadata +72 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: f5f308798c9180262f9aa758b6010b910142a0d9
|
4
|
+
data.tar.gz: 13fde3fde504683f14df8e5d891d5291ac3624ba
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 9932b1d74862af972ded69d873a42b719b3be8dbbd8cc2977106e746b87282d0eb84efa97f9e288b60e13ef04290b1d42eead4cdaf4f88b072b88420e7a264c6
|
7
|
+
data.tar.gz: e0ddc8dd3188cdce71cb4f64f738f71fc33f78d92521b03c154d6b861d427bffc77230cfa77f42111bf5154d21a803635321e496a0dbaad12b66f186851bedc9
|
data/LICENSE
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
The MIT License (MIT)
|
2
|
+
|
3
|
+
Copyright (C) 2015 Holger Joest <holger(at)joest.org>
|
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
|
13
|
+
all 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
|
21
|
+
THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,59 @@
|
|
1
|
+
# Overview
|
2
|
+
|
3
|
+
IPFS4R is a client library to access the [Interplanetary Filesystem (IPFS)](https://ipfs.io) from Ruby.
|
4
|
+
|
5
|
+
You can find more examples in the
|
6
|
+
[examples directory](https://github.com/hjoest/ruby-ipfs-api/tree/master/examples).
|
7
|
+
|
8
|
+
## Installation
|
9
|
+
|
10
|
+
Use ``gem`` to install it
|
11
|
+
|
12
|
+
```bash
|
13
|
+
gem install ipfs-api
|
14
|
+
```
|
15
|
+
|
16
|
+
or simply add this line to your ``Gemfile``
|
17
|
+
|
18
|
+
```ruby
|
19
|
+
gem 'ipfs-api', '~> 0.1.0'
|
20
|
+
```
|
21
|
+
|
22
|
+
## Basic examples
|
23
|
+
|
24
|
+
This example will add a directory to *IPFS*. The directory ``data``
|
25
|
+
must exist or otherwise an ``Errno::ENOENT`` error will be raised.
|
26
|
+
|
27
|
+
```ruby
|
28
|
+
require 'ipfs-api'
|
29
|
+
|
30
|
+
ipfs = IPFS::Connection.new
|
31
|
+
ipfs.add Dir.new('data')
|
32
|
+
```
|
33
|
+
|
34
|
+
## Advanced
|
35
|
+
|
36
|
+
Dynamically add folders and files to *IPFS*, without creating them
|
37
|
+
on the local file system:
|
38
|
+
|
39
|
+
```ruby
|
40
|
+
require 'ipfs-api'
|
41
|
+
|
42
|
+
ipfs = IPFS::Connection.new
|
43
|
+
folder = IPFS::Upload.folder('test') do |test|
|
44
|
+
test.add_file('hello.txt') do |fd|
|
45
|
+
fd.write 'Hello'
|
46
|
+
end
|
47
|
+
test.add_file('world.txt') do |fd|
|
48
|
+
fd.write 'World'
|
49
|
+
end
|
50
|
+
end
|
51
|
+
ipfs.add folder do |node|
|
52
|
+
# display each uploaded node:
|
53
|
+
print "#{node.name}: #{node.hash}\n" if node.finished?
|
54
|
+
end
|
55
|
+
```
|
56
|
+
|
57
|
+
## License
|
58
|
+
|
59
|
+
This library is distributed under the [MIT License](https://github.com/hjoest/ruby-ipfs-api/tree/master/LICENSE).
|
data/Rakefile
ADDED
@@ -0,0 +1,103 @@
|
|
1
|
+
require 'rake'
|
2
|
+
require 'rake/clean'
|
3
|
+
require 'rake/testtask'
|
4
|
+
require 'rdoc/task'
|
5
|
+
require 'rubygems/package_task'
|
6
|
+
|
7
|
+
$:.unshift 'lib'
|
8
|
+
require 'ipfs-api/version'
|
9
|
+
|
10
|
+
PKG_NAME = 'ipfs-api'
|
11
|
+
PKG_VERSION = IPFS::VERSION
|
12
|
+
AUTHORS = ['Holger Joest']
|
13
|
+
EMAIL = 'holger@joest.org'
|
14
|
+
HOMEPAGE = 'http://ruby-ipfs-api.github.io'
|
15
|
+
SUMMARY = 'Interplanetary File System for Ruby'
|
16
|
+
DESCRIPTION = 'This is a client library to access the IPFS from Ruby'
|
17
|
+
RDOC_OPTIONS = [ '--title', SUMMARY, '--quiet', '--main', 'lib/ipfs-api.rb' ]
|
18
|
+
BUILD_FILES = [ 'Rakefile', 'ipfs-api.gemspec' ].sort
|
19
|
+
RDOC_FILES = [ 'README.md', 'LICENSE' ].sort
|
20
|
+
PKG_FILES = (BUILD_FILES + RDOC_FILES + Dir['{lib,test,examples}/**/*']).reject { |f| File.directory?(f) }.sort
|
21
|
+
|
22
|
+
task :default => [:test]
|
23
|
+
|
24
|
+
Rake::TestTask.new do |t|
|
25
|
+
t.libs << 'test'
|
26
|
+
t.test_files = ['test/test_*.rb']
|
27
|
+
end
|
28
|
+
|
29
|
+
RDoc::Task.new do |rd|
|
30
|
+
rd.rdoc_dir = 'rdoc'
|
31
|
+
rd.rdoc_files.include(RDOC_FILES, "lib/**/*.rb")
|
32
|
+
rd.options = RDOC_OPTIONS
|
33
|
+
end
|
34
|
+
|
35
|
+
CLEAN.include [ "*.gem*", "pkg", "rdoc" ]
|
36
|
+
|
37
|
+
spec = Gem::Specification.new do |s|
|
38
|
+
s.name = PKG_NAME
|
39
|
+
s.version = PKG_VERSION
|
40
|
+
s.authors = AUTHORS
|
41
|
+
s.email = EMAIL
|
42
|
+
s.homepage = HOMEPAGE
|
43
|
+
s.rubyforge_project = PKG_NAME
|
44
|
+
s.summary = SUMMARY
|
45
|
+
s.description = DESCRIPTION
|
46
|
+
s.platform = Gem::Platform::RUBY
|
47
|
+
s.licenses = ["MIT"]
|
48
|
+
s.require_path = 'lib'
|
49
|
+
s.executables = []
|
50
|
+
s.files = PKG_FILES
|
51
|
+
s.test_files = []
|
52
|
+
s.has_rdoc = true
|
53
|
+
s.extra_rdoc_files = RDOC_FILES
|
54
|
+
s.rdoc_options = RDOC_OPTIONS
|
55
|
+
s.required_ruby_version = ">= 1.9.3"
|
56
|
+
end
|
57
|
+
|
58
|
+
# also keep the gemspec up to date each time we package a tarball or gem
|
59
|
+
task :package => ['gem:update_gemspec']
|
60
|
+
task :gem => ['gem:update_gemspec']
|
61
|
+
|
62
|
+
Gem::PackageTask.new(spec) do |pkg|
|
63
|
+
pkg.gem_spec = spec
|
64
|
+
pkg.need_tar = true
|
65
|
+
pkg.need_zip = true
|
66
|
+
end
|
67
|
+
|
68
|
+
namespace :gem do
|
69
|
+
|
70
|
+
# thanks to the Merb project for this code.
|
71
|
+
desc "Update Github Gemspec"
|
72
|
+
task :update_gemspec do
|
73
|
+
skip_fields = %w(new_platform original_platform date cache_dir cache_file loaded)
|
74
|
+
|
75
|
+
result = "# WARNING : RAKE AUTO-GENERATED FILE. DO NOT MANUALLY EDIT!\n"
|
76
|
+
result << "# RUN : 'rake gem:update_gemspec'\n\n"
|
77
|
+
result << "Gem::Specification.new do |s|\n"
|
78
|
+
spec.instance_variables.sort.each do |ivar|
|
79
|
+
value = spec.instance_variable_get(ivar)
|
80
|
+
name = ivar.to_s.split("@").last
|
81
|
+
next if skip_fields.include?(name) || value.nil? || value == "" || (value.respond_to?(:empty?) && value.empty?)
|
82
|
+
if name == "dependencies"
|
83
|
+
value.each do |d|
|
84
|
+
dep, *ver = d.to_s.split(" ")
|
85
|
+
result << " s.add_dependency #{dep.inspect}, #{ver.join(" ").inspect.gsub(/[()]/, "")}\n"
|
86
|
+
end
|
87
|
+
else
|
88
|
+
case value
|
89
|
+
when Array
|
90
|
+
value = name != "files" ? value.inspect : value.sort.uniq.inspect.split(",").join(",\n")
|
91
|
+
when String, Fixnum, true, false
|
92
|
+
value = value.inspect
|
93
|
+
else
|
94
|
+
value = value.to_s.inspect
|
95
|
+
end
|
96
|
+
result << " s.#{name} = #{value}\n"
|
97
|
+
end
|
98
|
+
end
|
99
|
+
result << "end"
|
100
|
+
File.open(File.join(File.dirname(__FILE__), "#{spec.name}.gemspec"), "w"){|f| f << result}
|
101
|
+
end
|
102
|
+
|
103
|
+
end
|
data/examples/basic.rb
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
Hello World!
|
data/examples/upload.rb
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
require 'ipfs-api'
|
2
|
+
|
3
|
+
ipfs = IPFS::Connection.new
|
4
|
+
folder = IPFS::Upload.folder('test') do |test|
|
5
|
+
test.add_file('hello.txt') do |fd|
|
6
|
+
fd.write 'Hello'
|
7
|
+
end
|
8
|
+
test.add_file('world.txt') do |fd|
|
9
|
+
fd.write 'World'
|
10
|
+
end
|
11
|
+
end
|
12
|
+
ipfs.add folder do |node|
|
13
|
+
# display each uploaded node:
|
14
|
+
print "#{node.name}: #{node.hash}\n" if node.finished?
|
15
|
+
end
|
data/ipfs-api.gemspec
ADDED
@@ -0,0 +1,48 @@
|
|
1
|
+
# WARNING : RAKE AUTO-GENERATED FILE. DO NOT MANUALLY EDIT!
|
2
|
+
# RUN : 'rake gem:update_gemspec'
|
3
|
+
|
4
|
+
Gem::Specification.new do |s|
|
5
|
+
s.activated = false
|
6
|
+
s.authors = ["Holger Joest"]
|
7
|
+
s.bindir = "bin"
|
8
|
+
s.description = "This is a client library to access the IPFS from Ruby"
|
9
|
+
s.email = "holger@joest.org"
|
10
|
+
s.extra_rdoc_files = ["LICENSE", "README.md"]
|
11
|
+
s.files = ["LICENSE",
|
12
|
+
"README.md",
|
13
|
+
"Rakefile",
|
14
|
+
"examples/basic.rb",
|
15
|
+
"examples/data/hello.txt",
|
16
|
+
"examples/upload.rb",
|
17
|
+
"ipfs-api.gemspec",
|
18
|
+
"lib/ipfs-api.rb",
|
19
|
+
"lib/ipfs-api/connection.rb",
|
20
|
+
"lib/ipfs-api/io.rb",
|
21
|
+
"lib/ipfs-api/upload.rb",
|
22
|
+
"lib/ipfs-api/version.rb",
|
23
|
+
"test/common.rb",
|
24
|
+
"test/samples.rb",
|
25
|
+
"test/test_cmd_add.rb",
|
26
|
+
"test/test_cmd_cat.rb",
|
27
|
+
"test/test_cmd_get.rb",
|
28
|
+
"test/test_cmd_id.rb",
|
29
|
+
"test/test_cmd_ls.rb",
|
30
|
+
"test/test_cmd_name.rb",
|
31
|
+
"test/test_io.rb",
|
32
|
+
"test/test_upload.rb"]
|
33
|
+
s.full_name = "ipfs-api-0.1.0"
|
34
|
+
s.has_rdoc = true
|
35
|
+
s.homepage = "http://ruby-ipfs-api.github.io"
|
36
|
+
s.licenses = ["MIT"]
|
37
|
+
s.name = "ipfs-api"
|
38
|
+
s.platform = "ruby"
|
39
|
+
s.rdoc_options = ["--title", "Interplanetary File System for Ruby", "--quiet", "--main", "lib/ipfs-api.rb"]
|
40
|
+
s.require_paths = ["lib"]
|
41
|
+
s.required_ruby_version = ">= 1.9.3"
|
42
|
+
s.required_rubygems_version = ">= 0"
|
43
|
+
s.rubyforge_project = "ipfs-api"
|
44
|
+
s.rubygems_version = "2.4.8"
|
45
|
+
s.specification_version = 4
|
46
|
+
s.summary = "Interplanetary File System for Ruby"
|
47
|
+
s.version = "0.1.0"
|
48
|
+
end
|
data/lib/ipfs-api.rb
ADDED
@@ -0,0 +1,12 @@
|
|
1
|
+
# =IPFS4R - Interplanetary Filesystem Library for Ruby
|
2
|
+
# License:: MIT (see the LICENSE file)
|
3
|
+
# Website:: https://ruby-ipfs-api.github.io
|
4
|
+
#
|
5
|
+
# ==Introduction
|
6
|
+
#
|
7
|
+
# IPFS4R is an IPFS[https://ipfs.io] library for Ruby.
|
8
|
+
#
|
9
|
+
require 'ipfs-api/version'
|
10
|
+
require 'ipfs-api/io'
|
11
|
+
require 'ipfs-api/upload'
|
12
|
+
require 'ipfs-api/connection'
|
@@ -0,0 +1,150 @@
|
|
1
|
+
require 'cgi'
|
2
|
+
require 'json'
|
3
|
+
require 'net/http'
|
4
|
+
|
5
|
+
module IPFS
|
6
|
+
|
7
|
+
class Connection
|
8
|
+
|
9
|
+
def initialize base_url = 'http://127.0.0.1:5001'
|
10
|
+
@base_url = base_url
|
11
|
+
end
|
12
|
+
|
13
|
+
def add nodes, &block
|
14
|
+
boundaries = [ generate_boundary ]
|
15
|
+
headers = {
|
16
|
+
'Content-Disposition' => 'form-data: name="files"',
|
17
|
+
'Content-Type' => "multipart/form-data; boundary=#{boundaries[0]}"
|
18
|
+
}
|
19
|
+
walker = Upload::TreeWalker.depth_first(nodes)
|
20
|
+
node_map = {}
|
21
|
+
stream = IO::ReadFromWriterIO.new do |buf|
|
22
|
+
next if walker.nil?
|
23
|
+
begin
|
24
|
+
node, depth = walker.next
|
25
|
+
node_map[node.path] = node
|
26
|
+
rescue StopIteration
|
27
|
+
depth = -1
|
28
|
+
walker = nil
|
29
|
+
end
|
30
|
+
while boundaries.size > depth+1 && boundary = boundaries.shift
|
31
|
+
buf << %Q{\
|
32
|
+
--#{boundary}--\r\n\
|
33
|
+
\r\n\
|
34
|
+
\r\n\
|
35
|
+
}
|
36
|
+
end
|
37
|
+
next if walker.nil?
|
38
|
+
if node.folder?
|
39
|
+
boundaries.unshift generate_boundary
|
40
|
+
buf << %Q{\
|
41
|
+
--#{boundaries[1]}\r\n\
|
42
|
+
Content-Disposition: form-data; name="file"; filename="#{node.path}"\r\n\
|
43
|
+
Content-Type: multipart/mixed; boundary=#{boundaries[0]}\r\n\
|
44
|
+
\r\n\
|
45
|
+
\r\n\
|
46
|
+
}
|
47
|
+
elsif node.file?
|
48
|
+
buf << %Q{\
|
49
|
+
--#{boundaries[0]}\r\n\
|
50
|
+
Content-Disposition: file; filename="#{node.path}"\r\n\
|
51
|
+
Content-Type: application/octet-stream\r\n\
|
52
|
+
\r\n\
|
53
|
+
#{node.content}\r\n\
|
54
|
+
}
|
55
|
+
else
|
56
|
+
raise "Unknown node type: #{node}"
|
57
|
+
end
|
58
|
+
end
|
59
|
+
nodes = []
|
60
|
+
post("add?encoding=json&r=true&progress=true", stream, headers) do |chunk|
|
61
|
+
next if chunk.empty?
|
62
|
+
upload = JSON.parse(chunk)
|
63
|
+
path, bytes, hash = ['Name', 'Bytes', 'Hash'].map { |p| upload[p] }
|
64
|
+
node = node_map[path]
|
65
|
+
node.bytes = bytes if bytes
|
66
|
+
node.hash = hash if hash
|
67
|
+
if block_given?
|
68
|
+
block.call(node)
|
69
|
+
elsif hash
|
70
|
+
nodes << node
|
71
|
+
end
|
72
|
+
end
|
73
|
+
block_given? ? nil : nodes
|
74
|
+
end
|
75
|
+
|
76
|
+
def cat path
|
77
|
+
result = ''
|
78
|
+
post("cat?arg=#{CGI.escape(path)}") do |chunk|
|
79
|
+
result << chunk
|
80
|
+
end
|
81
|
+
result
|
82
|
+
end
|
83
|
+
|
84
|
+
# FIXME: provides only raw response yet
|
85
|
+
def get path
|
86
|
+
result = ''
|
87
|
+
post("get?arg=#{CGI.escape(path)}") do |chunk|
|
88
|
+
result << chunk
|
89
|
+
end
|
90
|
+
result
|
91
|
+
end
|
92
|
+
|
93
|
+
def id
|
94
|
+
JSON.parse(post('id').body)['ID']
|
95
|
+
end
|
96
|
+
|
97
|
+
def ls path
|
98
|
+
JSON.parse(post("ls?arg=#{CGI.escape(path)}").body)
|
99
|
+
end
|
100
|
+
|
101
|
+
def name
|
102
|
+
NameCommand.new(self)
|
103
|
+
end
|
104
|
+
|
105
|
+
private
|
106
|
+
def post command, stream = nil, headers = {}, params = {}, &block # :nodoc:
|
107
|
+
uri = URI.parse("#{@base_url}/api/v0/#{command}")
|
108
|
+
http = Net::HTTP.new(uri.host, uri.port)
|
109
|
+
#http.set_debug_output $stderr
|
110
|
+
headers['User-Agent'] = "ruby-ipfs-api/#{VERSION}/"
|
111
|
+
headers['Transfer-Encoding'] = 'chunked'
|
112
|
+
request = Net::HTTP::Post.new(uri.request_uri, headers)
|
113
|
+
if stream
|
114
|
+
request.body_stream = stream
|
115
|
+
else
|
116
|
+
params['encoding'] = 'json'
|
117
|
+
params['stream-channels'] = 'true'
|
118
|
+
request.set_form_data(params)
|
119
|
+
end
|
120
|
+
http.request(request) do |response|
|
121
|
+
raise "Request failed: #{response.body}" if !response.kind_of?(Net::HTTPOK)
|
122
|
+
if block
|
123
|
+
response.read_body do |chunk|
|
124
|
+
block.call chunk
|
125
|
+
end
|
126
|
+
end
|
127
|
+
end
|
128
|
+
end
|
129
|
+
|
130
|
+
def generate_boundary # :nodoc:
|
131
|
+
(1..60).map { rand(16).to_s(16) }.join
|
132
|
+
end
|
133
|
+
|
134
|
+
class NameCommand # :nodoc:
|
135
|
+
|
136
|
+
def initialize connection
|
137
|
+
@connection = connection
|
138
|
+
end
|
139
|
+
|
140
|
+
def resolve
|
141
|
+
@connection.instance_exec(self) do
|
142
|
+
JSON.parse(post('name/resolve').body)['Path']
|
143
|
+
end
|
144
|
+
end
|
145
|
+
|
146
|
+
end
|
147
|
+
|
148
|
+
end
|
149
|
+
|
150
|
+
end
|
data/lib/ipfs-api/io.rb
ADDED
@@ -0,0 +1,39 @@
|
|
1
|
+
require 'stringio'
|
2
|
+
|
3
|
+
module IPFS; end
|
4
|
+
module IPFS::IO # :nodoc:
|
5
|
+
|
6
|
+
class ReadFromWriterIO # :nodoc:
|
7
|
+
|
8
|
+
def initialize &block
|
9
|
+
@block = block
|
10
|
+
@stream = StringIO.new
|
11
|
+
fetch_data
|
12
|
+
end
|
13
|
+
|
14
|
+
def read length = nil, outbuf = ''
|
15
|
+
return nil if @stream.size == 0
|
16
|
+
outbuf.slice!(length..-1) if !length.nil?
|
17
|
+
q = 0
|
18
|
+
while @stream.size > 0
|
19
|
+
s = @stream.size - @p
|
20
|
+
s = [length - q, s].min if !length.nil?
|
21
|
+
outbuf[q, s] = @stream.string[@p, s]
|
22
|
+
@p += s
|
23
|
+
q += s
|
24
|
+
break if q == length
|
25
|
+
fetch_data if @stream.size == @p
|
26
|
+
end
|
27
|
+
outbuf
|
28
|
+
end
|
29
|
+
|
30
|
+
private
|
31
|
+
def fetch_data
|
32
|
+
@p = 0
|
33
|
+
@stream.string = ""
|
34
|
+
@block.call @stream if not @stream.closed?
|
35
|
+
end
|
36
|
+
|
37
|
+
end
|
38
|
+
|
39
|
+
end
|
@@ -0,0 +1,182 @@
|
|
1
|
+
module IPFS; end
|
2
|
+
module IPFS::Upload
|
3
|
+
|
4
|
+
##
|
5
|
+
# Define a file with the given name.
|
6
|
+
# file = IPFS::Upload.file('hello.txt') do |fd|
|
7
|
+
# fd.write 'Hello'
|
8
|
+
# end
|
9
|
+
# ipfs.add file do |node|
|
10
|
+
# print "Successfully uploaded #{node.path}\n" if node.finished?
|
11
|
+
# end
|
12
|
+
def file name, content = nil, &block
|
13
|
+
FileNode.new(name, content, &block)
|
14
|
+
end
|
15
|
+
|
16
|
+
##
|
17
|
+
# Define a folder with the given *name*.
|
18
|
+
# folder = IPFS::Upload.folder('test') do |test|
|
19
|
+
# test.add_file('hello.txt') do |fd|
|
20
|
+
# fd.write 'Hello'
|
21
|
+
# end
|
22
|
+
# end
|
23
|
+
# ipfs.add folder do |node|
|
24
|
+
# print "Successfully uploaded #{node.path}\n" if node.finished?
|
25
|
+
# end
|
26
|
+
def folder name, &block
|
27
|
+
FolderNode.new(name, &block)
|
28
|
+
end
|
29
|
+
|
30
|
+
module_function :file, :folder
|
31
|
+
|
32
|
+
private
|
33
|
+
|
34
|
+
class Node # :nodoc:
|
35
|
+
|
36
|
+
attr_accessor :name, :parent, :bytes, :hash
|
37
|
+
|
38
|
+
def initialize name, parent = nil
|
39
|
+
raise "Name #{} must be a String" if not name.is_a?(String)
|
40
|
+
@name = name
|
41
|
+
@parent = parent
|
42
|
+
end
|
43
|
+
|
44
|
+
def path
|
45
|
+
@parent.nil? ? "/#{@name}" : "#{@parent.path}/#{@name}"
|
46
|
+
end
|
47
|
+
|
48
|
+
def file?
|
49
|
+
false
|
50
|
+
end
|
51
|
+
|
52
|
+
def folder?
|
53
|
+
false
|
54
|
+
end
|
55
|
+
|
56
|
+
def to_s
|
57
|
+
inspect
|
58
|
+
end
|
59
|
+
|
60
|
+
def finished?
|
61
|
+
!@hash.nil?
|
62
|
+
end
|
63
|
+
|
64
|
+
def inspect
|
65
|
+
s = "<#{@name}"
|
66
|
+
s << ":#{@bytes}" if @bytes
|
67
|
+
s << ":#{@hash}" if @hash
|
68
|
+
s << '>'
|
69
|
+
end
|
70
|
+
|
71
|
+
end
|
72
|
+
|
73
|
+
class FileNode < Node # :nodoc:
|
74
|
+
|
75
|
+
attr_reader :content
|
76
|
+
|
77
|
+
def initialize name, parent = nil, content = nil
|
78
|
+
super(name, parent)
|
79
|
+
if block_given?
|
80
|
+
@content = ''
|
81
|
+
yield self
|
82
|
+
else
|
83
|
+
@content = content.to_s
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
def write s
|
88
|
+
@content << s
|
89
|
+
end
|
90
|
+
|
91
|
+
def file?
|
92
|
+
true
|
93
|
+
end
|
94
|
+
|
95
|
+
def inspect
|
96
|
+
"file:#{super}"
|
97
|
+
end
|
98
|
+
|
99
|
+
end
|
100
|
+
|
101
|
+
class FolderNode < Node # :nodoc:
|
102
|
+
|
103
|
+
attr_accessor :children
|
104
|
+
|
105
|
+
def initialize name, parent = nil, &block
|
106
|
+
super(name, parent)
|
107
|
+
@children = []
|
108
|
+
block.call(self) if block_given?
|
109
|
+
end
|
110
|
+
|
111
|
+
def folder?
|
112
|
+
true
|
113
|
+
end
|
114
|
+
|
115
|
+
def add_node node
|
116
|
+
node.parent = self
|
117
|
+
(@children << node).last
|
118
|
+
end
|
119
|
+
|
120
|
+
def add_file file, content = nil, &block
|
121
|
+
(@children << FileNode.new(file, self, content, &block)).last
|
122
|
+
end
|
123
|
+
|
124
|
+
def add_folder folder, &block
|
125
|
+
(@children << FolderNode.new(folder, self, &block)).last
|
126
|
+
end
|
127
|
+
|
128
|
+
def inspect
|
129
|
+
"folder:#{super}#{children}"
|
130
|
+
end
|
131
|
+
|
132
|
+
end
|
133
|
+
|
134
|
+
module TreeWalker # :nodoc:
|
135
|
+
|
136
|
+
def depth_first nodes
|
137
|
+
nodes = [ nodes ] if not nodes.is_a?(Array)
|
138
|
+
stack = [ [ nodes.shift, 0 ] ]
|
139
|
+
Enumerator.new do |yielder|
|
140
|
+
while not stack.empty?
|
141
|
+
node, depth = stack.pop
|
142
|
+
node = resolve_node(node)
|
143
|
+
next if not node
|
144
|
+
yielder << [ node, depth ]
|
145
|
+
if node.folder?
|
146
|
+
stack += node.children.reverse.map { |n| [ n, depth + 1 ] }
|
147
|
+
end
|
148
|
+
if stack.empty? and not nodes.empty?
|
149
|
+
stack << [ nodes.shift, 0 ]
|
150
|
+
end
|
151
|
+
end
|
152
|
+
end
|
153
|
+
end
|
154
|
+
|
155
|
+
def resolve_node node
|
156
|
+
if node.is_a?(Node)
|
157
|
+
node
|
158
|
+
elsif node.is_a?(Dir)
|
159
|
+
FolderNode.new(File.basename(node.path)) do |d|
|
160
|
+
node.each do |child|
|
161
|
+
if child != '.' and child != '..'
|
162
|
+
child_path = File.join(node.path, child)
|
163
|
+
if File.directory?(child_path)
|
164
|
+
d.add_node resolve_node(Dir.new(child_path))
|
165
|
+
else
|
166
|
+
d.add_node resolve_node(File.new(child_path))
|
167
|
+
end
|
168
|
+
end
|
169
|
+
end
|
170
|
+
end
|
171
|
+
elsif node.is_a?(File)
|
172
|
+
FileNode.new(File.basename(node.path)) do |d|
|
173
|
+
d.write File.read(node.path)
|
174
|
+
end
|
175
|
+
end
|
176
|
+
end
|
177
|
+
|
178
|
+
module_function :depth_first, :resolve_node
|
179
|
+
|
180
|
+
end
|
181
|
+
|
182
|
+
end
|
data/test/common.rb
ADDED
data/test/samples.rb
ADDED
@@ -0,0 +1,84 @@
|
|
1
|
+
require 'tmpdir'
|
2
|
+
require 'test/common'
|
3
|
+
require 'ipfs-api/upload'
|
4
|
+
|
5
|
+
module Samples
|
6
|
+
|
7
|
+
include IPFS
|
8
|
+
|
9
|
+
def some_virtual_folders
|
10
|
+
fixture = [
|
11
|
+
Upload.folder('a1') { |a1|
|
12
|
+
a1.add_file('hello.txt') { |io|
|
13
|
+
io.write "Hello World!\n"
|
14
|
+
}
|
15
|
+
a1.add_file('foo.txt') { |io|
|
16
|
+
io.write "bar\n"
|
17
|
+
}
|
18
|
+
},
|
19
|
+
Upload.folder('a2') { |a2|
|
20
|
+
a2.add_file 'b1.txt', 'B1'
|
21
|
+
a2.add_folder('b2') { |b2|
|
22
|
+
b2.add_folder('c1') { |c1|
|
23
|
+
c1.add_file 'd1.txt', 'D1'
|
24
|
+
}
|
25
|
+
}
|
26
|
+
a2.add_file 'b3.txt', 'B3'
|
27
|
+
},
|
28
|
+
Upload.folder('a3') { |a3|
|
29
|
+
a3.add_folder('b1') { |b1|
|
30
|
+
b1.add_folder('c1') { |c1|
|
31
|
+
c1.add_folder('d1') { |d1|
|
32
|
+
d1.add_folder('e1') { |e1|
|
33
|
+
e1.add_file('thanks.txt') { |io|
|
34
|
+
io.write 'This is the end.'
|
35
|
+
}
|
36
|
+
}
|
37
|
+
}
|
38
|
+
}
|
39
|
+
}
|
40
|
+
}
|
41
|
+
]
|
42
|
+
expectation = {
|
43
|
+
'/a1' => 'QmcsmfcY8SQzNxJQYGZMHLXCkeTgxDBhASDPJyVEGi8Wrv',
|
44
|
+
'/a1/hello.txt' => 'QmfM2r8seH2GiRaC4esTjeraXEachRt8ZsSeGaWTPLyMoG',
|
45
|
+
'/a1/foo.txt' => 'QmTz3oc4gdpRMKP2sdGUPZTAGRngqjsi99BPoztyP53JMM',
|
46
|
+
'/a2' => 'QmcfeG6dVvXufGXzxe6cBHP3ZbFx15yRzh7DSg8W67soto',
|
47
|
+
'/a2/b1.txt' => 'QmSwyJZAaxRqo8v2itCErP8U4DKa3dkSu7qTpDF1qG64Vw',
|
48
|
+
'/a2/b2' => 'QmQZ7ek8ss65DQFbCxXgFBzZeyJA6ZYJ9SfrTWZDSSQ2jj',
|
49
|
+
'/a2/b2/c1' => 'QmU2K25J3hBJQeKuUTJeXpeCovev8Bp2m2FzFHU2ANznnE',
|
50
|
+
'/a2/b2/c1/d1.txt' => 'QmNPDoyE8HaJWj7Bb7p2h3usA2nmbmUCx2712jXK2nftxz',
|
51
|
+
'/a2/b3.txt' => 'QmdoSx7tA3ybphBXDR9TBNteYPHKKb8aySPLVStNkSaTy2',
|
52
|
+
'/a3' => 'QmeQ7BYviWZynBhhzMCJVZRv5izgCFBfkGMU8fJJtzUA3f',
|
53
|
+
'/a3/b1' => 'QmW7zVapNEqc7Gmhx6ZGkeNzgUMgcRxpjXkXSkWdoyUWd5',
|
54
|
+
'/a3/b1/c1' => 'QmfHyLbTMjfUeD95wCmNNf4h5kJo3D65tEERs54zbRbWvv',
|
55
|
+
'/a3/b1/c1/d1' => 'QmRMzLcUNFghRE9Cn62wBHThKJoS6ChjpgEmJwApJBPoVo',
|
56
|
+
'/a3/b1/c1/d1/e1' => 'QmdJWhD7iU2kW5wTWP2hXoworetabt3n5tEtfEUyBoXCTv',
|
57
|
+
'/a3/b1/c1/d1/e1/thanks.txt' => 'QmWu5tSQetrKPxhDff2AF8owzqcMreXdXqeVjr3LL4WyJX'
|
58
|
+
}
|
59
|
+
yield fixture, expectation
|
60
|
+
end
|
61
|
+
module_function :some_virtual_folders
|
62
|
+
|
63
|
+
def some_filesystem_folders
|
64
|
+
Dir.mktmpdir('ruby-ipfs-api-unit-test-') do |root|
|
65
|
+
a1 = File.join(root, 'a1')
|
66
|
+
Dir.mkdir(a1, 0755)
|
67
|
+
b1 = File.join(a1, 'b1')
|
68
|
+
Dir.mkdir(b1, 0755)
|
69
|
+
hello = File.join(b1, 'hello.txt')
|
70
|
+
File.open hello, 'w' do |fd|
|
71
|
+
fd.write "Hello World!\n"
|
72
|
+
end
|
73
|
+
fixture = [ Dir.new(a1) ]
|
74
|
+
expectation = {
|
75
|
+
'/a1'=>'QmedYJNEKn656faSHaMv5UFVkgfSzwYf9u4zsYoXqgvnch',
|
76
|
+
'/a1/b1' => 'QmSh4Xjoy16v6XmnREE1yCrPM1dnizZc2h6LfrqXsnbBV7',
|
77
|
+
'/a1/b1/hello.txt' => 'QmfM2r8seH2GiRaC4esTjeraXEachRt8ZsSeGaWTPLyMoG'
|
78
|
+
}
|
79
|
+
yield fixture, expectation
|
80
|
+
end
|
81
|
+
end
|
82
|
+
module_function :some_filesystem_folders
|
83
|
+
|
84
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
$:.unshift File.expand_path('../..', __FILE__)
|
2
|
+
|
3
|
+
require 'test/samples'
|
4
|
+
require 'ipfs-api'
|
5
|
+
|
6
|
+
include IPFS
|
7
|
+
|
8
|
+
class CommandAddTest < Minitest::Test
|
9
|
+
|
10
|
+
def test_adding_some_filesystem_folders
|
11
|
+
ipfs = Connection.new
|
12
|
+
Samples.some_filesystem_folders do |fixture, expectation|
|
13
|
+
actual = ipfs.add(fixture)
|
14
|
+
assert_equal expectation, Hash[actual.map { |n| [ n.path, n.hash ] }]
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
def test_adding_some_filesystem_folders_with_block
|
19
|
+
ipfs = Connection.new
|
20
|
+
Samples.some_filesystem_folders do |fixture, expectation|
|
21
|
+
actual = {}
|
22
|
+
ipfs.add fixture do |node|
|
23
|
+
actual[node.path] = node.hash
|
24
|
+
end
|
25
|
+
assert_equal expectation, actual
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
def test_adding_some_virtual_folders
|
30
|
+
ipfs = Connection.new
|
31
|
+
Samples.some_virtual_folders do |fixture, expectation|
|
32
|
+
actual = {}
|
33
|
+
ipfs.add fixture do |node|
|
34
|
+
actual[node.path] = node.hash
|
35
|
+
end
|
36
|
+
assert_equal expectation, actual
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
$:.unshift File.expand_path('../..', __FILE__)
|
2
|
+
|
3
|
+
require 'test/samples'
|
4
|
+
require 'ipfs-api'
|
5
|
+
|
6
|
+
include IPFS
|
7
|
+
|
8
|
+
class CommandCatTest < Minitest::Test
|
9
|
+
|
10
|
+
def test_cat
|
11
|
+
ipfs = Connection.new
|
12
|
+
Samples.some_virtual_folders do |fixture, expectation|
|
13
|
+
ipfs.add fixture
|
14
|
+
actual = ipfs.cat('QmfM2r8seH2GiRaC4esTjeraXEachRt8ZsSeGaWTPLyMoG')
|
15
|
+
assert_equal "Hello World!\n", actual
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
$:.unshift File.expand_path('../..', __FILE__)
|
2
|
+
|
3
|
+
require 'test/samples'
|
4
|
+
require 'ipfs-api'
|
5
|
+
|
6
|
+
include IPFS
|
7
|
+
|
8
|
+
class CommandGetTest < Minitest::Test
|
9
|
+
|
10
|
+
def test_get
|
11
|
+
ipfs = Connection.new
|
12
|
+
Samples.some_virtual_folders do |fixture, expectation|
|
13
|
+
ipfs.add fixture
|
14
|
+
# FIXME: provides only raw response yet
|
15
|
+
actual = ipfs.get('QmedYJNEKn656faSHaMv5UFVkgfSzwYf9u4zsYoXqgvnch')
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
end
|
data/test/test_cmd_id.rb
ADDED
@@ -0,0 +1,17 @@
|
|
1
|
+
$:.unshift File.expand_path('../..', __FILE__)
|
2
|
+
|
3
|
+
require 'test/common'
|
4
|
+
require 'ipfs-api'
|
5
|
+
|
6
|
+
include IPFS
|
7
|
+
|
8
|
+
class CommandIdTest < Minitest::Test
|
9
|
+
|
10
|
+
def test_id
|
11
|
+
ipfs = Connection.new
|
12
|
+
id = ipfs.id
|
13
|
+
assert_equal 46, id.size
|
14
|
+
assert id.start_with?('Qm')
|
15
|
+
end
|
16
|
+
|
17
|
+
end
|
data/test/test_cmd_ls.rb
ADDED
@@ -0,0 +1,40 @@
|
|
1
|
+
$:.unshift File.expand_path('../..', __FILE__)
|
2
|
+
|
3
|
+
require 'test/samples'
|
4
|
+
require 'ipfs-api'
|
5
|
+
|
6
|
+
include IPFS
|
7
|
+
|
8
|
+
class CommandLsTest < Minitest::Test
|
9
|
+
|
10
|
+
def test_ls
|
11
|
+
ipfs = Connection.new
|
12
|
+
Samples.some_virtual_folders do |fixture, expectation|
|
13
|
+
ipfs.add fixture
|
14
|
+
actual = ipfs.ls('QmcsmfcY8SQzNxJQYGZMHLXCkeTgxDBhASDPJyVEGi8Wrv')
|
15
|
+
expectation = {
|
16
|
+
'Objects' => [
|
17
|
+
{
|
18
|
+
'Hash' => 'QmcsmfcY8SQzNxJQYGZMHLXCkeTgxDBhASDPJyVEGi8Wrv',
|
19
|
+
'Links' => [
|
20
|
+
{
|
21
|
+
'Name' => 'foo.txt',
|
22
|
+
'Hash' => 'QmTz3oc4gdpRMKP2sdGUPZTAGRngqjsi99BPoztyP53JMM',
|
23
|
+
'Size' => 12,
|
24
|
+
'Type' => 2
|
25
|
+
},
|
26
|
+
{
|
27
|
+
'Name' => 'hello.txt',
|
28
|
+
'Hash' => 'QmfM2r8seH2GiRaC4esTjeraXEachRt8ZsSeGaWTPLyMoG',
|
29
|
+
'Size' => 21,
|
30
|
+
'Type' => 2
|
31
|
+
}
|
32
|
+
]
|
33
|
+
}
|
34
|
+
]
|
35
|
+
}
|
36
|
+
assert_equal expectation, actual
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
$:.unshift File.expand_path('../..', __FILE__)
|
2
|
+
|
3
|
+
require 'test/common'
|
4
|
+
require 'ipfs-api'
|
5
|
+
|
6
|
+
include IPFS
|
7
|
+
|
8
|
+
class CommandNameTest < Minitest::Test
|
9
|
+
|
10
|
+
def test_name_resolve
|
11
|
+
ipfs = Connection.new
|
12
|
+
resolved = ipfs.name.resolve
|
13
|
+
assert resolved.start_with?('/ipfs/Qm')
|
14
|
+
end
|
15
|
+
|
16
|
+
end
|
data/test/test_io.rb
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
$:.unshift File.expand_path('../..', __FILE__)
|
2
|
+
|
3
|
+
require 'test/samples'
|
4
|
+
require 'ipfs-api/io'
|
5
|
+
|
6
|
+
include IPFS::IO
|
7
|
+
|
8
|
+
class ReadFromWriterIOTest < Minitest::Test
|
9
|
+
|
10
|
+
def test_read_write_io
|
11
|
+
msg = ('a'..'z').to_a.join
|
12
|
+
reader = ReadFromWriterIO.new do |writer|
|
13
|
+
writer << msg
|
14
|
+
writer.close
|
15
|
+
end
|
16
|
+
result = reader.read
|
17
|
+
assert_equal msg, result
|
18
|
+
end
|
19
|
+
|
20
|
+
end
|
data/test/test_upload.rb
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
$:.unshift File.expand_path('../..', __FILE__)
|
2
|
+
|
3
|
+
require 'test/samples'
|
4
|
+
require 'ipfs-api/upload'
|
5
|
+
|
6
|
+
class NodeTest < Minitest::Test
|
7
|
+
|
8
|
+
include IPFS
|
9
|
+
|
10
|
+
def test_tree_walker
|
11
|
+
Samples.some_virtual_folders do |fixture, expectations|
|
12
|
+
walker = Upload::TreeWalker.depth_first(fixture)
|
13
|
+
actual = walker.to_a
|
14
|
+
assert_equal 15, actual.size
|
15
|
+
paths = actual.map { |item| item.first.path }
|
16
|
+
assert_equal expectations.map { |item| item.first }, paths
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
end
|
metadata
ADDED
@@ -0,0 +1,72 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: ipfs-api
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Holger Joest
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2015-10-10 00:00:00.000000000 Z
|
12
|
+
dependencies: []
|
13
|
+
description: This is a client library to access the IPFS from Ruby
|
14
|
+
email: holger@joest.org
|
15
|
+
executables: []
|
16
|
+
extensions: []
|
17
|
+
extra_rdoc_files:
|
18
|
+
- LICENSE
|
19
|
+
- README.md
|
20
|
+
files:
|
21
|
+
- LICENSE
|
22
|
+
- README.md
|
23
|
+
- Rakefile
|
24
|
+
- examples/basic.rb
|
25
|
+
- examples/data/hello.txt
|
26
|
+
- examples/upload.rb
|
27
|
+
- ipfs-api.gemspec
|
28
|
+
- lib/ipfs-api.rb
|
29
|
+
- lib/ipfs-api/connection.rb
|
30
|
+
- lib/ipfs-api/io.rb
|
31
|
+
- lib/ipfs-api/upload.rb
|
32
|
+
- lib/ipfs-api/version.rb
|
33
|
+
- test/common.rb
|
34
|
+
- test/samples.rb
|
35
|
+
- test/test_cmd_add.rb
|
36
|
+
- test/test_cmd_cat.rb
|
37
|
+
- test/test_cmd_get.rb
|
38
|
+
- test/test_cmd_id.rb
|
39
|
+
- test/test_cmd_ls.rb
|
40
|
+
- test/test_cmd_name.rb
|
41
|
+
- test/test_io.rb
|
42
|
+
- test/test_upload.rb
|
43
|
+
homepage: http://ruby-ipfs-api.github.io
|
44
|
+
licenses:
|
45
|
+
- MIT
|
46
|
+
metadata: {}
|
47
|
+
post_install_message:
|
48
|
+
rdoc_options:
|
49
|
+
- "--title"
|
50
|
+
- Interplanetary File System for Ruby
|
51
|
+
- "--quiet"
|
52
|
+
- "--main"
|
53
|
+
- lib/ipfs-api.rb
|
54
|
+
require_paths:
|
55
|
+
- lib
|
56
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
57
|
+
requirements:
|
58
|
+
- - ">="
|
59
|
+
- !ruby/object:Gem::Version
|
60
|
+
version: 1.9.3
|
61
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
62
|
+
requirements:
|
63
|
+
- - ">="
|
64
|
+
- !ruby/object:Gem::Version
|
65
|
+
version: '0'
|
66
|
+
requirements: []
|
67
|
+
rubyforge_project: ipfs-api
|
68
|
+
rubygems_version: 2.4.8
|
69
|
+
signing_key:
|
70
|
+
specification_version: 4
|
71
|
+
summary: Interplanetary File System for Ruby
|
72
|
+
test_files: []
|