immutablebox 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -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