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.
- data/.document +5 -0
- data/Gemfile +13 -0
- data/LICENSE.txt +20 -0
- data/README.md +51 -0
- data/Rakefile +55 -0
- data/VERSION +1 -0
- data/bin/ib +109 -0
- data/lib/immutablebox.rb +303 -0
- data/test.sh +54 -0
- data/test/helper.rb +18 -0
- data/test/test_immutablebox.rb +7 -0
- metadata +153 -0
data/.document
ADDED
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
|
data/LICENSE.txt
ADDED
@@ -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.
|
data/README.md
ADDED
@@ -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`
|
data/Rakefile
ADDED
@@ -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
|
data/lib/immutablebox.rb
ADDED
@@ -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
|
data/test/helper.rb
ADDED
@@ -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
|
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
|