sinatra-s3 0.98
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/README +23 -0
- data/Rakefile +51 -0
- data/bin/sinatra-s3 +30 -0
- data/db/migrate/001_create_bits.rb +28 -0
- data/db/migrate/002_create_users.rb +24 -0
- data/db/migrate/003_create_bits_users.rb +16 -0
- data/db/migrate/004_create_torrents.rb +22 -0
- data/db/migrate/005_create_torrent_peers.rb +26 -0
- data/examples/README +9 -0
- data/examples/wiki.rb +199 -0
- data/examples/wiki.ru +5 -0
- data/examples/wikicloth/MIT-LICENSE +20 -0
- data/examples/wikicloth/README +81 -0
- data/examples/wikicloth/Rakefile +23 -0
- data/examples/wikicloth/init.rb +1 -0
- data/examples/wikicloth/install.rb +0 -0
- data/examples/wikicloth/lib/core_ext.rb +43 -0
- data/examples/wikicloth/lib/wiki_buffer/html_element.rb +237 -0
- data/examples/wikicloth/lib/wiki_buffer/link.rb +70 -0
- data/examples/wikicloth/lib/wiki_buffer/table.rb +159 -0
- data/examples/wikicloth/lib/wiki_buffer/var.rb +77 -0
- data/examples/wikicloth/lib/wiki_buffer.rb +279 -0
- data/examples/wikicloth/lib/wiki_cloth.rb +61 -0
- data/examples/wikicloth/lib/wiki_link_handler.rb +138 -0
- data/examples/wikicloth/lib/wikicloth.rb +5 -0
- data/examples/wikicloth/run_tests.rb +48 -0
- data/examples/wikicloth/sample_documents/air_force_one.wiki +170 -0
- data/examples/wikicloth/sample_documents/cheatsheet.wiki +205 -0
- data/examples/wikicloth/sample_documents/default.css +34 -0
- data/examples/wikicloth/sample_documents/elements.wiki +7 -0
- data/examples/wikicloth/sample_documents/george_washington.wiki +526 -0
- data/examples/wikicloth/sample_documents/images.wiki +15 -0
- data/examples/wikicloth/sample_documents/lists.wiki +421 -0
- data/examples/wikicloth/sample_documents/pipe_trick.wiki +68 -0
- data/examples/wikicloth/sample_documents/random.wiki +55 -0
- data/examples/wikicloth/sample_documents/tv.wiki +312 -0
- data/examples/wikicloth/sample_documents/wiki.png +0 -0
- data/examples/wikicloth/sample_documents/wiki_tables.wiki +410 -0
- data/examples/wikicloth/tasks/wikicloth_tasks.rake +0 -0
- data/examples/wikicloth/test/test_helper.rb +3 -0
- data/examples/wikicloth/test/wiki_cloth_test.rb +8 -0
- data/examples/wikicloth/uninstall.rb +0 -0
- data/examples/wikicloth/wikicloth-0.1.3.gem +0 -0
- data/examples/wikicloth/wikicloth.gemspec +69 -0
- data/lib/sinatra-s3/admin.rb +626 -0
- data/lib/sinatra-s3/base.rb +526 -0
- data/lib/sinatra-s3/errors.rb +51 -0
- data/lib/sinatra-s3/ext.rb +20 -0
- data/lib/sinatra-s3/helpers/acp.rb +100 -0
- data/lib/sinatra-s3/helpers/admin.rb +41 -0
- data/lib/sinatra-s3/helpers/tracker.rb +42 -0
- data/lib/sinatra-s3/helpers/versioning.rb +27 -0
- data/lib/sinatra-s3/helpers.rb +79 -0
- data/lib/sinatra-s3/models/bit.rb +180 -0
- data/lib/sinatra-s3/models/bucket.rb +81 -0
- data/lib/sinatra-s3/models/file_info.rb +3 -0
- data/lib/sinatra-s3/models/git_bucket.rb +3 -0
- data/lib/sinatra-s3/models/slot.rb +47 -0
- data/lib/sinatra-s3/models/torrent.rb +6 -0
- data/lib/sinatra-s3/models/torrent_peer.rb +5 -0
- data/lib/sinatra-s3/models/user.rb +35 -0
- data/lib/sinatra-s3/s3.rb +57 -0
- data/lib/sinatra-s3/tasks.rb +62 -0
- data/lib/sinatra-s3/tracker.rb +134 -0
- data/lib/sinatra-s3.rb +1 -0
- data/public/css/control.css +225 -0
- data/public/css/wiki.css +47 -0
- data/public/images/external-link.gif +0 -0
- data/public/js/prototype.js +2539 -0
- data/public/js/upload_status.js +117 -0
- data/public/test.html +8 -0
- data/s3.yml.example +17 -0
- data/test/s3api_test.rb +121 -0
- data/test/test_helper.rb +25 -0
- metadata +156 -0
@@ -0,0 +1,41 @@
|
|
1
|
+
require 'aws/s3'
|
2
|
+
|
3
|
+
module S3
|
4
|
+
module AdminHelpers
|
5
|
+
|
6
|
+
def login_required
|
7
|
+
@user = User.find(session[:user_id]) unless session[:user_id].nil?
|
8
|
+
redirect '/control/login' if @user.nil?
|
9
|
+
end
|
10
|
+
|
11
|
+
def number_to_human_size(size)
|
12
|
+
case
|
13
|
+
when size < 1.kilobyte: '%d Bytes' % size
|
14
|
+
when size < 1.megabyte: '%.1f KB' % (size / 1.0.kilobyte)
|
15
|
+
when size < 1.gigabyte: '%.1f MB' % (size / 1.0.megabyte)
|
16
|
+
when size < 1.terabyte: '%.1f GB' % (size / 1.0.gigabyte)
|
17
|
+
else '%.1f TB' % (size / 1.0.terabyte)
|
18
|
+
end.sub('.0', '')
|
19
|
+
rescue
|
20
|
+
nil
|
21
|
+
end
|
22
|
+
|
23
|
+
def signed_url(path)
|
24
|
+
url = "#{path}?"
|
25
|
+
url + AWS::S3::Authentication::QueryString.new(Net::HTTP::Get.new(path), @user.key, @user.secret)
|
26
|
+
end
|
27
|
+
|
28
|
+
def errors_for(model)
|
29
|
+
ret = ""
|
30
|
+
if model.errors.size > 0
|
31
|
+
ret += "<ul class=\"errors\">"
|
32
|
+
model.errors.each_full do |error|
|
33
|
+
ret += "<li>#{error}</li>"
|
34
|
+
end
|
35
|
+
ret += "</ul>"
|
36
|
+
end
|
37
|
+
ret
|
38
|
+
end
|
39
|
+
|
40
|
+
end
|
41
|
+
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
require 'aws/s3'
|
2
|
+
|
3
|
+
module S3
|
4
|
+
module TrackerHelper
|
5
|
+
|
6
|
+
def torrent(bit)
|
7
|
+
mi = bit.metainfo
|
8
|
+
mi.announce = URI("http://#{env['HTTP_HOST']}/tracker/announce")
|
9
|
+
mi.url_list = URI("http://#{env['HTTP_HOST']}/#{bit.parent.name}/#{bit.name}")
|
10
|
+
mi.created_by = "Served by Sinatra-S3/0.1a"
|
11
|
+
mi.creation_date = Time.now
|
12
|
+
t = Torrent.find_by_bit_id bit.id
|
13
|
+
info_hash = Digest::SHA1.digest(mi.info.to_bencoding).to_hex_s
|
14
|
+
unless t and t.info_hash == info_hash
|
15
|
+
t ||= Torrent.new
|
16
|
+
t.update_attributes(:info_hash => info_hash, :bit_id => bit.id, :metainfo => mi.to_bencoding)
|
17
|
+
end
|
18
|
+
status 200
|
19
|
+
headers 'Content-Disposition' => "attachment; filename=#{bit.name}.torrent;", 'Content-Type' => 'application/x-bittorrent'
|
20
|
+
body mi.to_bencoding
|
21
|
+
end
|
22
|
+
|
23
|
+
def torrent_list(info_hash)
|
24
|
+
params = {:order => 'seeders DESC, leechers DESC', :include => :bit}
|
25
|
+
params[:conditions] = ['info_hash = ?', info_hash.to_hex_s] if info_hash
|
26
|
+
Torrent.find :all, params
|
27
|
+
end
|
28
|
+
|
29
|
+
def tracker_reply(params)
|
30
|
+
status 200
|
31
|
+
headers 'Content-Type' => 'text/plain'
|
32
|
+
body params.merge('interval' => TRACKER_INTERVAL).to_bencoding
|
33
|
+
end
|
34
|
+
|
35
|
+
def tracker_error(msg)
|
36
|
+
status 200
|
37
|
+
headers 'Content-Type' => 'text/plain'
|
38
|
+
body ({'failure reason' => msg}.to_bencoding)
|
39
|
+
end
|
40
|
+
|
41
|
+
end
|
42
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
module S3
|
2
|
+
module Helpers
|
3
|
+
module Versioning
|
4
|
+
|
5
|
+
def versioning_response_for(bit)
|
6
|
+
xml do |x|
|
7
|
+
x.VersioningConfiguration :xmlns => "http://s3.amazonaws.com/doc/2006-03-01/" do
|
8
|
+
x.Versioning bit.versioning_enabled? ? 'Enabled' : 'Suspended' if File.exists?(File.join(bit.fullpath, '.git'))
|
9
|
+
end
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
def manage_versioning(bucket)
|
14
|
+
raise NotImplemented unless defined?(Git)
|
15
|
+
only_can_write_acp bucket
|
16
|
+
|
17
|
+
env['rack.input'].rewind
|
18
|
+
data = env['rack.input'].read
|
19
|
+
xml_request = REXML::Document.new(data).root
|
20
|
+
|
21
|
+
bucket.git_init() if !bucket.versioning_enabled? && xml_request.elements['Status'].text == 'Enabled'
|
22
|
+
bucket.git_destroy() if bucket.versioning_enabled? && xml_request.elements['Status'].text == 'Suspended'
|
23
|
+
end
|
24
|
+
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,79 @@
|
|
1
|
+
Dir["#{File.dirname(__FILE__)}/helpers/*.rb"].each {|r| require r }
|
2
|
+
|
3
|
+
module S3
|
4
|
+
module Helpers
|
5
|
+
|
6
|
+
include ACP
|
7
|
+
include Versioning
|
8
|
+
|
9
|
+
# Kick out anonymous users.
|
10
|
+
def only_authorized; raise S3::AccessDenied unless @user end
|
11
|
+
# Kick out any users which do not have read access to a certain resource.
|
12
|
+
def only_can_read bit; raise S3::AccessDenied unless bit.readable_by? @user end
|
13
|
+
# Kick out any users which do not have write access to a certain resource.
|
14
|
+
def only_can_write bit; raise S3::AccessDenied unless bit.writable_by? @user end
|
15
|
+
# Kick out any users which do not own a certain resource.
|
16
|
+
def only_owner_of bit; raise S3::AccessDenied unless bit.owned_by? @user end
|
17
|
+
# Kick out any non-superusers
|
18
|
+
def only_superusers; raise S3::AccessDenied unless @user.superuser? end
|
19
|
+
|
20
|
+
protected
|
21
|
+
def load_buckets
|
22
|
+
@buckets = Bucket.find_by_sql [%{
|
23
|
+
SELECT b.*, COUNT(c.id) AS total_children
|
24
|
+
FROM bits b LEFT JOIN bits c ON c.parent_id = b.id AND c.deleted = 0
|
25
|
+
WHERE b.deleted = 0 AND b.parent_id IS NULL AND b.owner_id = ?
|
26
|
+
GROUP BY b.id ORDER BY b.name}, @user.id]
|
27
|
+
@bucket = Bucket.new(:owner_id => @user.id, :access => CANNED_ACLS['private'])
|
28
|
+
end
|
29
|
+
|
30
|
+
def xml
|
31
|
+
xml = Builder::XmlMarkup.new
|
32
|
+
xml.instruct! :xml, :version=>"1.0", :encoding=>"UTF-8"
|
33
|
+
yield xml
|
34
|
+
content_type 'text/xml'
|
35
|
+
body xml.target!
|
36
|
+
end
|
37
|
+
|
38
|
+
# Convenient method for generating a SHA1 digest.
|
39
|
+
def hmac_sha1(key, s)
|
40
|
+
ipad = [].fill(0x36, 0, 64)
|
41
|
+
opad = [].fill(0x5C, 0, 64)
|
42
|
+
key = key.unpack("C*")
|
43
|
+
if key.length < 64 then
|
44
|
+
key += [].fill(0, 0, 64-key.length)
|
45
|
+
end
|
46
|
+
|
47
|
+
inner = []
|
48
|
+
64.times { |i| inner.push(key[i] ^ ipad[i]) }
|
49
|
+
inner += s.unpack("C*")
|
50
|
+
|
51
|
+
outer = []
|
52
|
+
64.times { |i| outer.push(key[i] ^ opad[i]) }
|
53
|
+
outer = outer.pack("c*")
|
54
|
+
outer += Digest::SHA1.digest(inner.pack("c*"))
|
55
|
+
|
56
|
+
return Base64::encode64(Digest::SHA1.digest(outer)).chomp
|
57
|
+
end
|
58
|
+
|
59
|
+
def generate_secret
|
60
|
+
abc = %{ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyz}
|
61
|
+
(1..40).map { abc[rand(abc.size),1] }.join
|
62
|
+
end
|
63
|
+
|
64
|
+
def generate_key
|
65
|
+
abc = %{ABCDEF0123456789}
|
66
|
+
(1..20).map { abc[rand(abc.size),1] }.join
|
67
|
+
end
|
68
|
+
|
69
|
+
def get_prefix(c)
|
70
|
+
c.name.sub(params['prefix'], '').split(params['delimiter'])[0] + params['delimiter']
|
71
|
+
end
|
72
|
+
|
73
|
+
def r(name, title, layout = :layout)
|
74
|
+
@title = title
|
75
|
+
haml name, :layout => layout
|
76
|
+
end
|
77
|
+
|
78
|
+
end
|
79
|
+
end
|
@@ -0,0 +1,180 @@
|
|
1
|
+
class Bit < ActiveRecord::Base
|
2
|
+
|
3
|
+
has_many :bits_users
|
4
|
+
|
5
|
+
serialize :meta
|
6
|
+
serialize :obj
|
7
|
+
|
8
|
+
belongs_to :parent, :class_name => 'Bit', :foreign_key => 'parent_id'
|
9
|
+
belongs_to :owner, :class_name => 'User', :foreign_key => 'owner_id'
|
10
|
+
|
11
|
+
validates_length_of :name, :within => 3..255
|
12
|
+
|
13
|
+
has_one :torrent
|
14
|
+
|
15
|
+
def git_repository
|
16
|
+
versioning_enabled? ? Git.open(git_repository_path) : nil
|
17
|
+
end
|
18
|
+
|
19
|
+
def fullpath; File.join(S3::STORAGE_PATH, name) end
|
20
|
+
|
21
|
+
def git_repository_path
|
22
|
+
self.obj ? File.join(File.dirname(self.fullpath)) : self.fullpath
|
23
|
+
end
|
24
|
+
|
25
|
+
def versioning_enabled?
|
26
|
+
return false if !defined?(Git) || !File.exists?(File.join(git_repository_path,'.git'))
|
27
|
+
return false if File.exists?(File.join(git_repository_path,'.git', 'disable_versioning'))
|
28
|
+
return true
|
29
|
+
end
|
30
|
+
|
31
|
+
def git_object
|
32
|
+
git_repository.log.path(File.basename(self.obj.path)).first if versioning_enabled? && self.obj
|
33
|
+
end
|
34
|
+
|
35
|
+
def objectish
|
36
|
+
git_repository.gcommit(version).gtree.blobs[File.basename(fullpath)].objectish
|
37
|
+
end
|
38
|
+
|
39
|
+
def diff(to)
|
40
|
+
to = Bit.find_by_version(to) if to.instance_of?(String)
|
41
|
+
git_repository.diff(objectish, to.objectish)
|
42
|
+
end
|
43
|
+
|
44
|
+
def self.diff(from,to)
|
45
|
+
from = Bit.find_by_version(from)
|
46
|
+
to = Bit.find_by_version(to)
|
47
|
+
from.git_repository.diff(from.objectish, to.objectish)
|
48
|
+
end
|
49
|
+
|
50
|
+
def acl_list
|
51
|
+
bit_perms = self.access.nil? ? "000" : self.access.to_s(8)
|
52
|
+
acls = { :owner => { :id => self.owner.key, :accessnum => 7, :type => "CanonicalUser", :name => self.owner.login, :access => "FULL_ACCESS" },
|
53
|
+
:anonymous => { :id => nil, :accessnum => bit_perms[2,1], :access => acl_label(bit_perms[2,1]),
|
54
|
+
:type => "Group", :uri => "http://acs.amazonaws.com/groups/global/AllUsers" },
|
55
|
+
:authenticated => { :id => nil, :accessnum => bit_perms[1,1], :access => acl_label(bit_perms[1,1]),
|
56
|
+
:type => "Group", :uri => "http://acs.amazonaws.com/groups/global/AuthenticatedUsers" }
|
57
|
+
}.merge(get_acls_for_bin)
|
58
|
+
acls.delete_if { |key,value| value[:access] == "NONE" || (key == :authenticated && (!acls[:anonymous].nil? && value[:accessnum] <= acls[:anonymous][:accessnum])) }
|
59
|
+
end
|
60
|
+
|
61
|
+
def get_acls_for_bin
|
62
|
+
ret = {}
|
63
|
+
for a in self.bits_users
|
64
|
+
ret[a.user.key] = { :type => "CanonicalUser", :id => a.user.key, :name => a.user.login, :access => acl_label(a.access.to_s(8)[0,1]),
|
65
|
+
:accessnum => a.access.to_s(8)[0,1] }
|
66
|
+
end
|
67
|
+
ret
|
68
|
+
end
|
69
|
+
|
70
|
+
def self.acl_text
|
71
|
+
{ 0 => "NONE", 1 => "NONE", 2 => "NONE", 3 => "NONE", 4 => "READ", 5 => "READ_ACP", 6 => "WRITE", 7 => "WRITE_ACP" }
|
72
|
+
end
|
73
|
+
|
74
|
+
def acl_label(num)
|
75
|
+
Bit.acl_text[num.to_i]
|
76
|
+
end
|
77
|
+
|
78
|
+
def grant hsh
|
79
|
+
if hsh[:access]
|
80
|
+
self.access = hsh[:access]
|
81
|
+
self.save
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
def access_readable
|
86
|
+
name, _ = S3::CANNED_ACLS.find { |k, v| v == self.access }
|
87
|
+
if name
|
88
|
+
name
|
89
|
+
else
|
90
|
+
[0100, 0010, 0001].map do |i|
|
91
|
+
[[4, 'r'], [2, 'w'], [1, 'x']].map do |k, v|
|
92
|
+
(self.access & (i * k) == 0 ? '-' : v )
|
93
|
+
end
|
94
|
+
end.join
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
def acp_readable_by? user
|
99
|
+
# if owner
|
100
|
+
return true if user && user == owner
|
101
|
+
# if can write or better
|
102
|
+
return true if user && acl_list[user.key] && acl_list[user.key][:accessnum].to_i >= 5
|
103
|
+
# if authenticated
|
104
|
+
return true if user && acl_list[:authenticated] && acl_list[:authenticated][:accessnum].to_i >= 5
|
105
|
+
# if anonymous
|
106
|
+
return true if acl_list[:anonymous] && acl_list[:anonymous][:accessnum].to_i >= 5
|
107
|
+
end
|
108
|
+
|
109
|
+
def acp_writable_by? user
|
110
|
+
# if owner
|
111
|
+
return true if user && user == owner
|
112
|
+
# if can write or better
|
113
|
+
return true if user && acl_list[user.key] && acl_list[user.key][:accessnum].to_i == 7
|
114
|
+
# if authenticated
|
115
|
+
return true if user && acl_list[:authenticated] && acl_list[:authenticated][:accessnum].to_i == 7
|
116
|
+
# if anonymous
|
117
|
+
return true if acl_list[:anonymous] && acl_list[:anonymous][:accessnum].to_i == 7
|
118
|
+
end
|
119
|
+
|
120
|
+
def readable_by? user
|
121
|
+
return true if user && acl_list[user.key] && acl_list[user.key][:accessnum].to_i >= 4
|
122
|
+
check_access(user, S3::READABLE_BY_AUTH, S3::READABLE)
|
123
|
+
end
|
124
|
+
|
125
|
+
def writable_by? user
|
126
|
+
return true if user && acl_list[user.key] && acl_list[user.key][:accessnum].to_i >= 6
|
127
|
+
check_access(user, S3::WRITABLE_BY_AUTH, S3::WRITABLE)
|
128
|
+
end
|
129
|
+
|
130
|
+
def check_access user, group_perm, user_perm
|
131
|
+
!!( if owned_by?(user) or (user and access & group_perm > 0) or (access & user_perm > 0)
|
132
|
+
true
|
133
|
+
elsif user
|
134
|
+
acl = users.find(user.id) rescue nil
|
135
|
+
acl and acl.access.to_i & user_perm
|
136
|
+
end )
|
137
|
+
end
|
138
|
+
|
139
|
+
def owned_by? user
|
140
|
+
user and owner_id == user.id
|
141
|
+
end
|
142
|
+
|
143
|
+
def git_update
|
144
|
+
# update git info so we can serve it over http
|
145
|
+
base_dir = File.join(self.git_repository_path,'.git')
|
146
|
+
if File.exists?(base_dir)
|
147
|
+
File.open(File.join(base_dir,'HEAD'),'w') { |f| f.write("ref: refs/heads/master") }
|
148
|
+
if File.exists?(File.join(base_dir,'refs/heads/master'))
|
149
|
+
File.open(File.join(base_dir,'info/refs'),'w') { |f|
|
150
|
+
ref = File.open(File.join(base_dir,'refs/heads/master')) { |re| re.read }
|
151
|
+
f.write("#{ref.chomp}\trefs/heads/master\n")
|
152
|
+
}
|
153
|
+
end
|
154
|
+
end
|
155
|
+
end
|
156
|
+
|
157
|
+
def each_piece(files, length)
|
158
|
+
buf = ""
|
159
|
+
files.each do |f|
|
160
|
+
File.open(f) do |fh|
|
161
|
+
begin
|
162
|
+
read = fh.read(length - buf.length)
|
163
|
+
if (buf.length + read.length) == length
|
164
|
+
yield(buf + read)
|
165
|
+
buf = ""
|
166
|
+
else
|
167
|
+
buf += read
|
168
|
+
end
|
169
|
+
end until fh.eof?
|
170
|
+
end
|
171
|
+
end
|
172
|
+
yield buf
|
173
|
+
end
|
174
|
+
|
175
|
+
end
|
176
|
+
|
177
|
+
class BitsUser < ActiveRecord::Base
|
178
|
+
belongs_to :bit
|
179
|
+
belongs_to :user
|
180
|
+
end
|
@@ -0,0 +1,81 @@
|
|
1
|
+
class Bucket < Bit
|
2
|
+
|
3
|
+
named_scope :user_buckets, lambda { |uid| { :conditions => ['parent_id IS NULL AND owner_id = ?', uid ], :order => "name" } }
|
4
|
+
named_scope :root, lambda { |name| { :conditions => ['deleted = 0 AND parent_id IS NULL AND name = ?', name] } }
|
5
|
+
|
6
|
+
def items(marker,prefix)
|
7
|
+
Slot.bucket(self).items(marker,prefix)
|
8
|
+
end
|
9
|
+
|
10
|
+
def self.find_root(bucket_name)
|
11
|
+
root(bucket_name).find(:first) or raise S3::NoSuchBucket
|
12
|
+
end
|
13
|
+
|
14
|
+
def find_slot(oid)
|
15
|
+
Slot.find(:first, :conditions => ['deleted = 0 AND parent_id = ? AND name = ?', self.id, oid]) or raise S3::NoSuchKey
|
16
|
+
end
|
17
|
+
|
18
|
+
def remove_from_filesystem
|
19
|
+
bucket_dir = File.join(S3::STORAGE_PATH, self.name)
|
20
|
+
FileUtils.rm_rf bucket_dir if File.directory?(bucket_dir) && Dir.empty?(bucket_dir)
|
21
|
+
end
|
22
|
+
|
23
|
+
def git_disable
|
24
|
+
FileUtils.touch(File.join(self.fullpath, '.git', 'disable_versioning'))
|
25
|
+
end
|
26
|
+
|
27
|
+
def add_child(bit)
|
28
|
+
bit.update_attributes(:parent_id => self.id)
|
29
|
+
end
|
30
|
+
|
31
|
+
def git_init
|
32
|
+
disable_file = File.join(self.fullpath, '.git', 'disable_versioning')
|
33
|
+
FileUtils.rm_f(disable_file) and return if File.exists?(disable_file)
|
34
|
+
|
35
|
+
begin
|
36
|
+
FileUtils.mkdir_p(self.fullpath) unless File.exists?(self.fullpath)
|
37
|
+
dir_empty = !Dir.foreach(self.fullpath) {|n| break true unless /\A\.\.?\z/ =~ n}
|
38
|
+
g = Git.init(self.fullpath)
|
39
|
+
g.config('user.name', self.owner.login)
|
40
|
+
g.config('user.email', self.owner.email)
|
41
|
+
# if directory is not empty we need to add the files
|
42
|
+
# into version control
|
43
|
+
unless dir_empty
|
44
|
+
g.add('.')
|
45
|
+
g.commit_all("Enabling versioning for bucket #{self.name}.")
|
46
|
+
self.git_update
|
47
|
+
end
|
48
|
+
self.type = "GitBucket"
|
49
|
+
self.save()
|
50
|
+
self.git_update
|
51
|
+
rescue Git::GitExecuteError => error_message
|
52
|
+
puts "[#{Time.now}] GIT: #{error_message}"
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
def metainfo
|
57
|
+
children = self.all_children
|
58
|
+
mii = RubyTorrent::MetaInfoInfo.new
|
59
|
+
mii.name = self.name
|
60
|
+
mii.piece_length = 512.kilobytes
|
61
|
+
mii.files, files = [], []
|
62
|
+
mii.pieces = ""
|
63
|
+
i = 0
|
64
|
+
Slot.find(:all, :conditions => ['parent_id = ?', self.id]).each do |slot|
|
65
|
+
miif = RubyTorrent::MetaInfoInfoFile.new
|
66
|
+
miif.length = slot.obj.size
|
67
|
+
miif.md5sum = slot.obj.md5
|
68
|
+
miif.path = File.split(slot.name)
|
69
|
+
mii.files << miif
|
70
|
+
files << slot.fullpath
|
71
|
+
end
|
72
|
+
each_piece(files, mii.piece_length) do |piece|
|
73
|
+
mii.pieces += Digest::SHA1.digest(piece)
|
74
|
+
i += 1
|
75
|
+
end
|
76
|
+
mi = RubyTorrent::MetaInfo.new
|
77
|
+
mi.info = mii
|
78
|
+
mi
|
79
|
+
end
|
80
|
+
|
81
|
+
end
|
@@ -0,0 +1,47 @@
|
|
1
|
+
class Slot < Bit
|
2
|
+
|
3
|
+
named_scope :bucket, lambda { |bucket| { :conditions => [ 'bits.deleted = 0 AND parent_id = ?', bucket.id ], :order => "name" } }
|
4
|
+
named_scope :items, lambda { |marker,prefix| { :conditions => condition_string(marker,prefix) } }
|
5
|
+
|
6
|
+
def fullpath; File.join(S3::STORAGE_PATH, obj.path) end
|
7
|
+
|
8
|
+
def etag
|
9
|
+
if self.obj.respond_to? :etag
|
10
|
+
self.obj.etag
|
11
|
+
elsif self.obj.respond_to? :md5
|
12
|
+
self.obj.md5
|
13
|
+
else
|
14
|
+
%{"#{MD5.md5(self.obj)}"}
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
def remove_from_filesystem
|
19
|
+
FileUtils.rm_f fullpath
|
20
|
+
end
|
21
|
+
|
22
|
+
def metainfo
|
23
|
+
mii = RubyTorrent::MetaInfoInfo.new
|
24
|
+
mii.name = self.name
|
25
|
+
mii.length = self.obj.size
|
26
|
+
mii.md5sum = self.obj.md5
|
27
|
+
mii.piece_length = 512.kilobytes
|
28
|
+
mii.pieces = ""
|
29
|
+
i = 0
|
30
|
+
each_piece([self.fullpath], mii.piece_length) do |piece|
|
31
|
+
mii.pieces += Digest::SHA1.digest(piece)
|
32
|
+
i += 1
|
33
|
+
end
|
34
|
+
mi = RubyTorrent::MetaInfo.new
|
35
|
+
mi.info = mii
|
36
|
+
mi
|
37
|
+
end
|
38
|
+
|
39
|
+
protected
|
40
|
+
def self.condition_string(marker,prefix)
|
41
|
+
conditions = []
|
42
|
+
conditions << "name LIKE '#{prefix.gsub(/\\/, '\&\&').gsub(/'/, "''")}%'" unless prefix.blank?
|
43
|
+
conditions << "name > '#{marker.gsub(/\\/, '\&\&').gsub(/'/, "''")}'" unless marker.blank?
|
44
|
+
conditions.empty? ? nil : conditions.join(" AND ")
|
45
|
+
end
|
46
|
+
|
47
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
require File.join(File.dirname(__FILE__), '..', 'helpers.rb')
|
2
|
+
|
3
|
+
class User < ActiveRecord::Base
|
4
|
+
|
5
|
+
include S3::Helpers
|
6
|
+
|
7
|
+
has_many :bits, :foreign_key => 'owner_id'
|
8
|
+
has_many :bits_users
|
9
|
+
|
10
|
+
validates_length_of :login, :within => 3..40
|
11
|
+
validates_uniqueness_of :login
|
12
|
+
validates_uniqueness_of :key
|
13
|
+
validates_presence_of :password
|
14
|
+
validates_confirmation_of :password
|
15
|
+
|
16
|
+
def destroy
|
17
|
+
self.deleted = 1
|
18
|
+
self.save
|
19
|
+
end
|
20
|
+
|
21
|
+
attr_accessor :skip_before_save
|
22
|
+
|
23
|
+
protected
|
24
|
+
def before_save
|
25
|
+
unless self.skip_before_save
|
26
|
+
@password_clean = self.password
|
27
|
+
self.password = hmac_sha1(self.password, self.secret)
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
def after_save
|
32
|
+
self.password = @password_clean
|
33
|
+
end
|
34
|
+
|
35
|
+
end
|
@@ -0,0 +1,57 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'activerecord'
|
3
|
+
|
4
|
+
require 'sinatra/base'
|
5
|
+
require 'openssl'
|
6
|
+
require 'digest/sha1'
|
7
|
+
require 'md5'
|
8
|
+
require 'haml'
|
9
|
+
|
10
|
+
begin
|
11
|
+
require "git"
|
12
|
+
puts "-- Git support found, versioning support enabled." if $VERBOSE
|
13
|
+
rescue LoadError
|
14
|
+
puts "-- Git support not found, versioning support disabled."
|
15
|
+
end
|
16
|
+
|
17
|
+
begin
|
18
|
+
require 'exifr'
|
19
|
+
puts "-- EXIFR found, JPEG metadata enabled." if $VERBOSE
|
20
|
+
rescue LoadError
|
21
|
+
puts "-- EXIFR not found, JPEG metadata disabled."
|
22
|
+
end
|
23
|
+
|
24
|
+
begin
|
25
|
+
require 'rubytorrent'
|
26
|
+
puts "-- RubyTorrent support found, bittorrent support enabled." if $VERBOSE
|
27
|
+
rescue LoadError
|
28
|
+
puts "-- RubyTorrent support not found, bittorrent support disabled."
|
29
|
+
end
|
30
|
+
|
31
|
+
module S3
|
32
|
+
VERSION = "0.98"
|
33
|
+
DEFAULT_PASSWORD = 'pass@word1'
|
34
|
+
|
35
|
+
BUFSIZE = (4 * 1024)
|
36
|
+
ROOT_DIR = File.expand_path(File.join(File.dirname(__FILE__), '..', '..'))
|
37
|
+
PUBLIC_PATH = File.join(ROOT_DIR, 'public')
|
38
|
+
STORAGE_PATH = File.expand_path('storage') unless defined?(STORAGE_PATH)
|
39
|
+
RESOURCE_TYPES = %w[acl versioning torrent]
|
40
|
+
CANNED_ACLS = {
|
41
|
+
'private' => 0600,
|
42
|
+
'public-read' => 0644,
|
43
|
+
'public-read-write' => 0666,
|
44
|
+
'authenticated-read' => 0640,
|
45
|
+
'authenticated-read-write' => 0660
|
46
|
+
}
|
47
|
+
READABLE = 0004
|
48
|
+
WRITABLE = 0002
|
49
|
+
READABLE_BY_AUTH = 0040
|
50
|
+
WRITABLE_BY_AUTH = 0020
|
51
|
+
|
52
|
+
POST = %{if(!this.title||confirm(this.title+'?')){var f = document.createElement('form'); this.parentNode.appendChild(f); f.method = 'POST'; f.action = this.href; f.submit();}return false;}
|
53
|
+
POPUP = %{window.open(this.href,'changelog','height=600,width=500,scrollbars=1');return false;}
|
54
|
+
end
|
55
|
+
|
56
|
+
%w(bit bucket git_bucket slot user file_info torrent torrent_peer).each {|r| require "#{File.dirname(__FILE__)}/models/#{r}" }
|
57
|
+
%w(ext helpers errors admin base tracker).each {|r| require "#{File.dirname(__FILE__)}/#{r}"}
|
@@ -0,0 +1,62 @@
|
|
1
|
+
require 'rake'
|
2
|
+
require 'rake/testtask'
|
3
|
+
require 'rake/gempackagetask'
|
4
|
+
require File.join(File.dirname(__FILE__), 's3')
|
5
|
+
|
6
|
+
namespace :db do
|
7
|
+
task :environment do
|
8
|
+
ActiveRecord::Base.establish_connection(S3.config[:db])
|
9
|
+
end
|
10
|
+
|
11
|
+
desc "Migrate the database"
|
12
|
+
task(:migrate => :environment) do
|
13
|
+
ActiveRecord::Base.logger = Logger.new(STDOUT)
|
14
|
+
ActiveRecord::Migration.verbose = true
|
15
|
+
|
16
|
+
out_dir = File.dirname(S3.config[:db][:database])
|
17
|
+
FileUtils.mkdir_p(out_dir) unless File.exists?(out_dir)
|
18
|
+
|
19
|
+
ActiveRecord::Migrator.migrate(File.join(S3::ROOT_DIR, 'db', 'migrate'))
|
20
|
+
num_users = User.count || 0
|
21
|
+
if num_users == 0
|
22
|
+
puts "** No users found, creating the `admin' user."
|
23
|
+
class S3KeyGen
|
24
|
+
include S3::Helpers
|
25
|
+
def secret() generate_secret(); end;
|
26
|
+
def key() generate_key(); end;
|
27
|
+
end
|
28
|
+
User.create :login => "admin", :password => S3::DEFAULT_PASSWORD,
|
29
|
+
:email => "admin@parkplace.net", :key => S3KeyGen.new.key(), :secret => S3KeyGen.new.secret(),
|
30
|
+
:activated_at => Time.now, :superuser => 1
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
namespace :setup do
|
36
|
+
task :wiki do
|
37
|
+
begin
|
38
|
+
Bucket.find_root('wiki')
|
39
|
+
rescue S3::NoSuchBucket
|
40
|
+
wiki_owner = User.find_by_login('wiki')
|
41
|
+
if wiki_owner.nil?
|
42
|
+
class S3KeyGen
|
43
|
+
include S3::Helpers
|
44
|
+
def secret() generate_secret(); end;
|
45
|
+
def key() generate_key(); end;
|
46
|
+
end
|
47
|
+
puts "** No wiki user found, creating the `wiki' user."
|
48
|
+
wiki_owner = User.create :login => "wiki", :password => S3::DEFAULT_PASSWORD,
|
49
|
+
:email => "wiki@parkplace.net", :key => S3KeyGen.new.key(), :secret => S3KeyGen.new.secret(),
|
50
|
+
:activated_at => Time.now
|
51
|
+
end
|
52
|
+
wiki_bucket = Bucket.create(:name => 'wiki', :owner_id => wiki_owner.id, :access => 438)
|
53
|
+
templates_bucket = Bucket.create(:name => 'templates', :owner_id => wiki_owner.id, :access => 438)
|
54
|
+
if defined?(Git)
|
55
|
+
wiki_bucket.git_init
|
56
|
+
templates_bucket.git_init
|
57
|
+
else
|
58
|
+
puts "Git support not found therefore Wiki history is disabled."
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|