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