immutablebox 0.0.1

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.
@@ -0,0 +1,5 @@
1
+ lib/**/*.rb
2
+ bin/*
3
+ -
4
+ features/**/*.feature
5
+ LICENSE.txt
data/Gemfile ADDED
@@ -0,0 +1,13 @@
1
+ source "http://rubygems.org"
2
+ # Add dependencies required to use your gem here.
3
+ # Example:
4
+ # gem "activesupport", ">= 2.3.5"
5
+
6
+ # Add dependencies to develop your gem here.
7
+ # Include everything needed to run rake, tests, features, etc.
8
+ group :development do
9
+ gem "shoulda", ">= 0"
10
+ gem "bundler", "~> 1.0.0"
11
+ gem "jeweler", "~> 1.5.2"
12
+ gem "rcov", ">= 0"
13
+ end
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2011 OHASHI Hideya
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,51 @@
1
+ Immutablebox
2
+ ============
3
+
4
+ A Dropbox clone.
5
+
6
+ * Manage small and big files.
7
+ * Sync files with other directories.
8
+ * Store files as many pieces like BitTorrent.
9
+ * Replicate pieces to other directories.
10
+ * You can choose directories as you like. (NFS, Samba, FUSE(FTP, WebDAV), ...)
11
+
12
+ Installation
13
+ ------------
14
+
15
+ TODO
16
+
17
+ * `gem install immutablebox`
18
+
19
+ Usage
20
+ -----
21
+
22
+ * `ib commit` commit all outstanding changes
23
+ * `ib init` create a new repository in the given directory
24
+ * `ib log` show revision history of entire repository
25
+ * `ib status` show changed files in the working directory
26
+ * `ib update` update working directory (or switch revisions)
27
+
28
+ Below are not implemented yet.
29
+
30
+ * `ib pull` pull changes from the specified source
31
+ * `ib push` push changes to the specified destination
32
+ * `ib replicate` replicate pieces with other repository
33
+ * `ib verify` verify all pieces of the repository
34
+
35
+ Tutorial
36
+ --------
37
+
38
+ * `mkdir MyBox`
39
+ * `cd MyBox`
40
+ * `ib init`
41
+ * `mv ../YourFolder1 ../YourFile.txt .`
42
+ * `ib commit`
43
+ * `ib log`
44
+ * `vi YourFile.txt`
45
+ * `ib status`
46
+ * `ib commit`
47
+ * `ib log`
48
+ * `rm YourFile.txt`
49
+ * `ib status`
50
+ * `ib update`
51
+ * `vi YourFile.txt`
@@ -0,0 +1,55 @@
1
+ require 'rubygems'
2
+ require 'bundler'
3
+ begin
4
+ Bundler.setup(:default, :development)
5
+ rescue Bundler::BundlerError => e
6
+ $stderr.puts e.message
7
+ $stderr.puts "Run `bundle install` to install missing gems"
8
+ exit e.status_code
9
+ end
10
+ require 'rake'
11
+
12
+ require 'jeweler'
13
+ Jeweler::Tasks.new do |gem|
14
+ # gem is a Gem::Specification... see http://docs.rubygems.org/read/chapter/20 for more options
15
+ gem.name = "immutablebox"
16
+ gem.homepage = "http://github.com/ohac/immutablebox"
17
+ gem.license = "MIT"
18
+ gem.summary = %Q{Social folder sharing/backup tool which using BitTorrent.}
19
+ gem.description = %Q{Social folder sharing/backup tool which using BitTorrent.}
20
+ gem.email = "ohachige@gmail.com"
21
+ gem.authors = ["OHASHI Hideya"]
22
+ # Include your dependencies below. Runtime dependencies are required when using your gem,
23
+ # and development dependencies are only needed for development (ie running rake tasks, tests, etc)
24
+ # gem.add_runtime_dependency 'jabber4r', '> 0.1'
25
+ # gem.add_development_dependency 'rspec', '> 1.2.3'
26
+ gem.add_runtime_dependency 'base32', '>= 0.1.2'
27
+ gem.add_runtime_dependency 'bencode', '>= 0.6.0'
28
+ end
29
+ Jeweler::RubygemsDotOrgTasks.new
30
+
31
+ require 'rake/testtask'
32
+ Rake::TestTask.new(:test) do |test|
33
+ test.libs << 'lib' << 'test'
34
+ test.pattern = 'test/**/test_*.rb'
35
+ test.verbose = true
36
+ end
37
+
38
+ require 'rcov/rcovtask'
39
+ Rcov::RcovTask.new do |test|
40
+ test.libs << 'test'
41
+ test.pattern = 'test/**/test_*.rb'
42
+ test.verbose = true
43
+ end
44
+
45
+ task :default => :test
46
+
47
+ require 'rake/rdoctask'
48
+ Rake::RDocTask.new do |rdoc|
49
+ version = File.exist?('VERSION') ? File.read('VERSION') : ""
50
+
51
+ rdoc.rdoc_dir = 'rdoc'
52
+ rdoc.title = "immutablebox #{version}"
53
+ rdoc.rdoc_files.include('README*')
54
+ rdoc.rdoc_files.include('lib/**/*.rb')
55
+ end
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.0.1
data/bin/ib ADDED
@@ -0,0 +1,109 @@
1
+ #!/usr/bin/env ruby
2
+ # -*- coding: utf-8 -*-
3
+ # vim: filetype=ruby
4
+
5
+ self_file =
6
+ if File.symlink?(__FILE__)
7
+ require 'pathname'
8
+ Pathname.new(__FILE__).realpath
9
+ else
10
+ __FILE__
11
+ end
12
+ $:.unshift(File.dirname(self_file) + "/../lib")
13
+
14
+ require 'immutablebox'
15
+
16
+ tracker = 'udp://tracker.publicbt.com:80/announce'
17
+ priv = false
18
+ name = File.basename(FileUtils.pwd)
19
+ IB_DIR_GAP = "#{IB_DIR}/gap"
20
+ IB_DIR_TORRENTS = "#{IB_DIR}/torrents/#{name}"
21
+ IB_DIR_PIECES = "#{IB_DIR}/pieces"
22
+ storage = LocalStorage.new(IB_DIR_PIECES)
23
+
24
+ def get_last_torrent
25
+ return unless File.directory? IB_DIR_TORRENTS
26
+ torrent = Dir.entries(IB_DIR_TORRENTS).sort_by{|dir| dir.split.first.to_i}
27
+ return if torrent.size == 2 # '.', '..'
28
+ File.join(IB_DIR_TORRENTS, torrent.last)
29
+ end
30
+
31
+ command = (ARGV.shift or 'help').to_sym
32
+ case command
33
+ when :commit, :ci
34
+ last_infohash = begin
35
+ torrent = get_last_torrent
36
+ if torrent
37
+ torrentobj = Torrent.new(File.read(torrent))
38
+ torrentobj.info.infohash
39
+ end
40
+ end
41
+ gap = !ARGV.shift.nil?
42
+ img = make_torrent(name, '.', tracker, priv, gap)
43
+ if last_infohash
44
+ new_infohash = Torrent.new(img).info.infohash
45
+ exit if last_infohash == new_infohash
46
+ end
47
+ torrentfile = "#{IB_DIR_TORRENTS}/#{Time.now.to_i}.torrent"
48
+ File.open(torrentfile, 'wb') do |fd|
49
+ fd.write(img)
50
+ end
51
+ begin
52
+ storage.open
53
+ load_torrent(torrentfile) do |piece_hash, piece|
54
+ storage.put(piece_hash, piece)
55
+ end
56
+ ensure
57
+ storage.close
58
+ end
59
+ when :init
60
+ FileUtils.mkdir_p IB_DIR_GAP
61
+ FileUtils.mkdir_p IB_DIR_TORRENTS
62
+ FileUtils.mkdir_p IB_DIR_PIECES
63
+ when :log
64
+ exit unless File.directory? IB_DIR_TORRENTS
65
+ Dir.entries(IB_DIR_TORRENTS).each do |dir|
66
+ next unless /\.torrent\z/ === dir
67
+ puts Time.at(dir.split.first.to_i)
68
+ end
69
+ when :status, :st
70
+ torrent = get_last_torrent
71
+ exit unless torrent
72
+ torrentobj = Torrent.new(File.read(torrent))
73
+ begin
74
+ storage.open
75
+ changes = load_torrent(torrent) do |piece_hash, piece|
76
+ end
77
+ changes.each do |file|
78
+ puts "M #{file}"
79
+ end
80
+ trackedfiles = torrentobj.info.files.map do |file|
81
+ if file['path'][0] != IB_DIR
82
+ file['path'].join('/')
83
+ end
84
+ end
85
+ allfiles = []
86
+ walk('.') do |file|
87
+ next if file.index("./#{IB_DIR}/") == 0
88
+ filename = file.split('/', 2).last
89
+ puts "? #{filename}" unless trackedfiles.include?(filename)
90
+ end
91
+ ensure
92
+ storage.close
93
+ end
94
+ when :update, :up
95
+ torrent = get_last_torrent
96
+ exit unless torrent
97
+ begin
98
+ storage.open
99
+ changes = load_torrent(torrent) do |piece_hash, piece|
100
+ end
101
+ save_torrent(torrent, storage, changes)
102
+ ensure
103
+ storage.close
104
+ end
105
+ when :verify
106
+ p :verify
107
+ else
108
+ p :help
109
+ end
@@ -0,0 +1,303 @@
1
+ require 'rubygems'
2
+ require 'bencode'
3
+ require 'base32'
4
+ require 'cgi'
5
+ require 'digest/sha1'
6
+ require 'fileutils'
7
+ require 'zlib'
8
+
9
+ IB_DIR = '.ib'
10
+
11
+ class Torrent
12
+ def self.str2hex(str)
13
+ str.unpack('C*').map{|v|"%02x" % v}.join
14
+ end
15
+
16
+ class Info
17
+ def initialize(info)
18
+ @info = info
19
+ @info_hash = Digest::SHA1.digest(BEncode.dump(info))
20
+ @piece_length = info['piece length']
21
+ @name = info['name']
22
+ @files = info['files'] || [{
23
+ 'length' => info['length'], 'path' => [@name]
24
+ }]
25
+ pieces = info['pieces']
26
+ @pieces = (pieces.size / 20).times.map{|i| pieces[i * 20, 20]}
27
+ end
28
+
29
+ attr :info
30
+ attr :info_hash
31
+ attr :files
32
+ attr :piece_length
33
+ attr :name
34
+ attr :pieces
35
+
36
+ def magnet
37
+ "magnet:?xt=urn:btih:%s" % Base32.encode(@info_hash)
38
+ end
39
+
40
+ def infohash
41
+ Torrent.str2hex(@info_hash)
42
+ end
43
+ end
44
+
45
+ def initialize(img)
46
+ torrent = BEncode.load(img)
47
+ @announce = torrent['announce']
48
+ @creation_date = torrent['creation date']
49
+ @info = Info.new(torrent['info'])
50
+ end
51
+
52
+ attr :info
53
+ attr :creation_date
54
+ attr :announce
55
+
56
+ def to_s
57
+ "%s&tr=%s" % [@info.to_s, CGI.escape(@announce)]
58
+ end
59
+
60
+ def inspect
61
+ @info.inspect
62
+ end
63
+ end
64
+
65
+ def load_torrent(tname)
66
+ modifiedfiles = []
67
+ torrent = Torrent.new(File.read(tname))
68
+ info = torrent.info
69
+ pieces = info.pieces.clone
70
+ piece_length = info.piece_length
71
+
72
+ files = []
73
+ piece = nil
74
+ info.files.each do |file|
75
+ file_size = file['length']
76
+ fd = if file['path'][0] != IB_DIR
77
+ filename = file['path'].join('/')
78
+ files << filename
79
+ if File.exist?(filename)
80
+ File.open(filename, 'rb')
81
+ else
82
+ StringIO.new(' ' * file_size)
83
+ end
84
+ end
85
+ begin
86
+ loop do
87
+ if fd
88
+ if piece.nil?
89
+ piece = fd.read(piece_length)
90
+ unless piece
91
+ files = []
92
+ break
93
+ end
94
+ else
95
+ piece += fd.read(piece_length - piece.size)
96
+ end
97
+ else
98
+ unless piece
99
+ files = []
100
+ break
101
+ end
102
+ piece += "\000" * file_size
103
+ end
104
+ break if piece.size < piece_length
105
+ piece_hash = Digest::SHA1.digest(piece)
106
+ if pieces.shift == piece_hash # good piece
107
+ yield(piece_hash, piece)
108
+ else
109
+ modifiedfiles += files
110
+ end
111
+ piece = nil
112
+ end
113
+ ensure
114
+ fd.close if fd
115
+ end
116
+ end
117
+
118
+ if piece
119
+ piece_hash = Digest::SHA1.digest(piece)
120
+ if pieces.shift == piece_hash # good piece
121
+ yield(piece_hash, piece)
122
+ else
123
+ modifiedfiles += files
124
+ end
125
+ end
126
+ modifiedfiles.uniq
127
+ end
128
+
129
+ def save_torrent(tname, storage, files)
130
+ torrent = Torrent.new(File.read(tname))
131
+ info = torrent.info
132
+ pieces = info.pieces.clone
133
+ piece_length = info.piece_length
134
+ info.files.each do |file|
135
+ next if file['path'][0] == IB_DIR
136
+ file_size = file['length']
137
+ filename = file['path'].join('/')
138
+ if files.include?(filename)
139
+ File.open(filename, 'wb') do |fd|
140
+ n = file_size / piece_length
141
+ mod = file_size % piece_length
142
+ n.times do |i|
143
+ piece_hash = pieces.shift
144
+ piece = storage.get(piece_hash)
145
+ fd.write(piece)
146
+ end
147
+ if mod > 0
148
+ piece_hash = pieces.shift
149
+ piece = storage.get(piece_hash)
150
+ fd.write(piece[0, mod])
151
+ end
152
+ end
153
+ else
154
+ piece_hash = pieces.shift((file_size + piece_length - 1) / piece_length)
155
+ end
156
+ end
157
+ end
158
+
159
+ def walk(path, &block)
160
+ Dir.entries(path).select{|d| !(/\A\.+\z/.match d)}.each do |e|
161
+ file = File.join(path, e)
162
+ if File.directory?(file)
163
+ walk(file, &block)
164
+ else
165
+ yield file
166
+ end
167
+ end
168
+ end
169
+
170
+ def split_path(path)
171
+ d = path
172
+ rv = []
173
+ loop do
174
+ d, f = File.split(d)
175
+ rv.insert(0, f)
176
+ break if d == '.'
177
+ end
178
+ rv
179
+ end
180
+
181
+ TORRENT_PIECE_SIZE = 2 ** 18
182
+
183
+ def make_torrent(name, path, tracker, priv, gap = false)
184
+ torrent_pieces = []
185
+ piece = ''
186
+ gapn = 0
187
+ files = []
188
+ walk(path) do |file|
189
+ next if file.index("./#{IB_DIR}/") == 0
190
+ fileinfo = { 'path' => split_path(file) }
191
+ files << fileinfo
192
+ filesize = 0
193
+ File.open(file, 'rb') do |fd|
194
+ loop do
195
+ data = fd.read(TORRENT_PIECE_SIZE - piece.size)
196
+ break if data.nil?
197
+ piece << data
198
+ if piece.size == TORRENT_PIECE_SIZE
199
+ torrent_pieces << Digest::SHA1.digest(piece)
200
+ filesize += piece.size
201
+ piece = ''
202
+ end
203
+ end
204
+ end
205
+ if piece.size > 0
206
+ filesize += piece.size
207
+ fileinfo['length'] = filesize
208
+ gapsize = TORRENT_PIECE_SIZE - piece.size
209
+ gapfile = "#{IB_DIR}/gap/#{gapn}"
210
+ gapimage = "\000" * gapsize
211
+ fileinfo = { 'length' => gapsize, 'path' => gapfile.split('/') }
212
+ files << fileinfo
213
+ gapn += 1
214
+ piece << gapimage
215
+ torrent_pieces << Digest::SHA1.digest(piece)
216
+ piece = ''
217
+ if gap
218
+ File.open(gapfile, 'wb'){|fd| fd.write(gapimage)}
219
+ end
220
+ else
221
+ fileinfo['length'] = filesize
222
+ end
223
+ end
224
+
225
+ torrent = {
226
+ 'announce' => tracker,
227
+ 'created by' => 'statictorrent 0.0.0',
228
+ 'creation date' => Time.now.to_i,
229
+ 'info' => {
230
+ 'length' => TORRENT_PIECE_SIZE * torrent_pieces.size,
231
+ 'name' => name,
232
+ 'private' => priv ? 1 : 0,
233
+ 'pieces' => torrent_pieces.join,
234
+ 'piece length' => TORRENT_PIECE_SIZE,
235
+ 'files' => files,
236
+ }
237
+ }
238
+ BEncode.dump(torrent)
239
+ end
240
+
241
+ class Storage
242
+ def open
243
+ end
244
+ def close
245
+ end
246
+ def put
247
+ end
248
+ def get
249
+ end
250
+ end
251
+
252
+ class LocalStorage < Storage
253
+ def initialize(dir)
254
+ @dir = dir
255
+ end
256
+ def open
257
+ FileUtils.mkdir_p(@dir)
258
+ end
259
+ def compress?(piece)
260
+ limit = 3 * TORRENT_PIECE_SIZE / 256
261
+ [' ', "\n", "\000"].map{|c| piece.count(c)}.max < limit
262
+ end
263
+ def getfilename(piece_hash)
264
+ "#{@dir}/#{Torrent.str2hex(piece_hash)}"
265
+ end
266
+ def put(piece_hash, piece)
267
+ filename = getfilename(piece_hash)
268
+ return if File.exist?(filename)
269
+ File.open(filename, 'wb') do |fd|
270
+ fd.write(compress?(piece) ? piece : Zlib::Deflate.deflate(piece))
271
+ end
272
+ end
273
+ def get(piece_hash)
274
+ filename = getfilename(piece_hash)
275
+ return unless File.exist?(filename)
276
+ piece = File.open(filename, 'rb'){|fd| fd.read}
277
+ if piece.size == TORRENT_PIECE_SIZE
278
+ piece
279
+ else
280
+ Zlib::Inflate.inflate(piece)
281
+ end
282
+ end
283
+ end
284
+
285
+ class DistributedStorage < Storage
286
+ def initialize
287
+ @storages = []
288
+ end
289
+ def <<(storage)
290
+ @storages << storage
291
+ self
292
+ end
293
+ def open
294
+ @storages.each(&:open)
295
+ end
296
+ def close
297
+ @storages.each(&:close)
298
+ end
299
+ def put(piece_hash, piece)
300
+ dice = piece_hash.unpack('L').first % @storages.size
301
+ @storages[dice].put(piece_hash, piece)
302
+ end
303
+ end
data/test.sh ADDED
@@ -0,0 +1,54 @@
1
+ #!/bin/sh
2
+ rm -fr sandbox
3
+ mkdir sandbox
4
+ cd sandbox
5
+
6
+ ../bin/ib init
7
+ echo 1 >1
8
+ echo 2 >2
9
+ echo 3 >3
10
+
11
+ ../bin/ib st >actual
12
+ cat <<EOF >.ib/expected
13
+ ? 1
14
+ ? 2
15
+ ? 3
16
+ EOF
17
+ diff -u .ib/expected actual
18
+
19
+ ACTUAL=`../bin/ib log | wc -l`
20
+ if [ $ACTUAL -ne 0 ]; then
21
+ echo $ACTUAL
22
+ fi
23
+
24
+ ../bin/ib ci
25
+
26
+ ACTUAL=`../bin/ib log | wc -l`
27
+ if [ $ACTUAL -ne 1 ]; then
28
+ echo $ACTUAL
29
+ fi
30
+
31
+ ../bin/ib ci
32
+
33
+ ACTUAL=`../bin/ib log | wc -l`
34
+ if [ $ACTUAL -ne 1 ]; then
35
+ echo $ACTUAL
36
+ fi
37
+
38
+ ACTUAL=`../bin/ib st | wc -l`
39
+ if [ $ACTUAL -ne 0 ]; then
40
+ echo $ACTUAL
41
+ fi
42
+
43
+ rm 2
44
+ ../bin/ib st >actual
45
+ cat <<EOF >.ib/expected
46
+ M 2
47
+ EOF
48
+ diff -u .ib/expected actual
49
+
50
+ ../bin/ib up
51
+ ACTUAL=`../bin/ib st | wc -l`
52
+ if [ $ACTUAL -ne 0 ]; then
53
+ echo $ACTUAL
54
+ fi
@@ -0,0 +1,18 @@
1
+ require 'rubygems'
2
+ require 'bundler'
3
+ begin
4
+ Bundler.setup(:default, :development)
5
+ rescue Bundler::BundlerError => e
6
+ $stderr.puts e.message
7
+ $stderr.puts "Run `bundle install` to install missing gems"
8
+ exit e.status_code
9
+ end
10
+ require 'test/unit'
11
+ require 'shoulda'
12
+
13
+ $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
14
+ $LOAD_PATH.unshift(File.dirname(__FILE__))
15
+ require 'immutablebox'
16
+
17
+ class Test::Unit::TestCase
18
+ end
@@ -0,0 +1,7 @@
1
+ require 'helper'
2
+
3
+ class TestImmutablebox < Test::Unit::TestCase
4
+ should "probably rename this file and start testing for real" do
5
+ true
6
+ end
7
+ end
metadata ADDED
@@ -0,0 +1,153 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: immutablebox
3
+ version: !ruby/object:Gem::Version
4
+ prerelease: false
5
+ segments:
6
+ - 0
7
+ - 0
8
+ - 1
9
+ version: 0.0.1
10
+ platform: ruby
11
+ authors:
12
+ - OHASHI Hideya
13
+ autorequire:
14
+ bindir: bin
15
+ cert_chain: []
16
+
17
+ date: 2011-04-15 00:00:00 +09:00
18
+ default_executable: ib
19
+ dependencies:
20
+ - !ruby/object:Gem::Dependency
21
+ type: :development
22
+ name: shoulda
23
+ version_requirements: &id001 !ruby/object:Gem::Requirement
24
+ requirements:
25
+ - - ">="
26
+ - !ruby/object:Gem::Version
27
+ segments:
28
+ - 0
29
+ version: "0"
30
+ requirement: *id001
31
+ prerelease: false
32
+ - !ruby/object:Gem::Dependency
33
+ type: :development
34
+ name: bundler
35
+ version_requirements: &id002 !ruby/object:Gem::Requirement
36
+ requirements:
37
+ - - ~>
38
+ - !ruby/object:Gem::Version
39
+ segments:
40
+ - 1
41
+ - 0
42
+ - 0
43
+ version: 1.0.0
44
+ requirement: *id002
45
+ prerelease: false
46
+ - !ruby/object:Gem::Dependency
47
+ type: :development
48
+ name: jeweler
49
+ version_requirements: &id003 !ruby/object:Gem::Requirement
50
+ requirements:
51
+ - - ~>
52
+ - !ruby/object:Gem::Version
53
+ segments:
54
+ - 1
55
+ - 5
56
+ - 2
57
+ version: 1.5.2
58
+ requirement: *id003
59
+ prerelease: false
60
+ - !ruby/object:Gem::Dependency
61
+ type: :development
62
+ name: rcov
63
+ version_requirements: &id004 !ruby/object:Gem::Requirement
64
+ requirements:
65
+ - - ">="
66
+ - !ruby/object:Gem::Version
67
+ segments:
68
+ - 0
69
+ version: "0"
70
+ requirement: *id004
71
+ prerelease: false
72
+ - !ruby/object:Gem::Dependency
73
+ type: :runtime
74
+ name: base32
75
+ version_requirements: &id005 !ruby/object:Gem::Requirement
76
+ requirements:
77
+ - - ">="
78
+ - !ruby/object:Gem::Version
79
+ segments:
80
+ - 0
81
+ - 1
82
+ - 2
83
+ version: 0.1.2
84
+ requirement: *id005
85
+ prerelease: false
86
+ - !ruby/object:Gem::Dependency
87
+ type: :runtime
88
+ name: bencode
89
+ version_requirements: &id006 !ruby/object:Gem::Requirement
90
+ requirements:
91
+ - - ">="
92
+ - !ruby/object:Gem::Version
93
+ segments:
94
+ - 0
95
+ - 6
96
+ - 0
97
+ version: 0.6.0
98
+ requirement: *id006
99
+ prerelease: false
100
+ description: Social folder sharing/backup tool which using BitTorrent.
101
+ email: ohachige@gmail.com
102
+ executables:
103
+ - ib
104
+ extensions: []
105
+
106
+ extra_rdoc_files:
107
+ - LICENSE.txt
108
+ - README.md
109
+ files:
110
+ - .document
111
+ - Gemfile
112
+ - LICENSE.txt
113
+ - README.md
114
+ - Rakefile
115
+ - VERSION
116
+ - bin/ib
117
+ - lib/immutablebox.rb
118
+ - test.sh
119
+ - test/helper.rb
120
+ - test/test_immutablebox.rb
121
+ has_rdoc: true
122
+ homepage: http://github.com/ohac/immutablebox
123
+ licenses:
124
+ - MIT
125
+ post_install_message:
126
+ rdoc_options: []
127
+
128
+ require_paths:
129
+ - lib
130
+ required_ruby_version: !ruby/object:Gem::Requirement
131
+ requirements:
132
+ - - ">="
133
+ - !ruby/object:Gem::Version
134
+ segments:
135
+ - 0
136
+ version: "0"
137
+ required_rubygems_version: !ruby/object:Gem::Requirement
138
+ requirements:
139
+ - - ">="
140
+ - !ruby/object:Gem::Version
141
+ segments:
142
+ - 0
143
+ version: "0"
144
+ requirements: []
145
+
146
+ rubyforge_project:
147
+ rubygems_version: 1.3.6
148
+ signing_key:
149
+ specification_version: 3
150
+ summary: Social folder sharing/backup tool which using BitTorrent.
151
+ test_files:
152
+ - test/helper.rb
153
+ - test/test_immutablebox.rb