omgdav 0.0.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.document +3 -0
- data/.gitignore +17 -0
- data/.manifest +53 -0
- data/.wrongdoc.yml +6 -0
- data/COPYING +661 -0
- data/ChangeLog +185 -0
- data/GIT-VERSION-FILE +1 -0
- data/GIT-VERSION-GEN +33 -0
- data/GNUmakefile +35 -0
- data/LATEST +1 -0
- data/NEWS +1 -0
- data/README +154 -0
- data/bin/omgdav-setup +4 -0
- data/bin/omgdav-sync +32 -0
- data/lib/omgdav/app.rb +70 -0
- data/lib/omgdav/copy.rb +100 -0
- data/lib/omgdav/copy_move.rb +54 -0
- data/lib/omgdav/db.rb +258 -0
- data/lib/omgdav/delete.rb +66 -0
- data/lib/omgdav/get.rb +35 -0
- data/lib/omgdav/http_get.rb +146 -0
- data/lib/omgdav/input_wrapper.rb +32 -0
- data/lib/omgdav/migrations/0001_initial.rb +45 -0
- data/lib/omgdav/migrations/0002_contenttype.rb +15 -0
- data/lib/omgdav/migrations/0003_synctmp.rb +14 -0
- data/lib/omgdav/mkcol.rb +28 -0
- data/lib/omgdav/move.rb +74 -0
- data/lib/omgdav/options.rb +21 -0
- data/lib/omgdav/propfind.rb +46 -0
- data/lib/omgdav/propfind_response.rb +150 -0
- data/lib/omgdav/proppatch.rb +116 -0
- data/lib/omgdav/put.rb +110 -0
- data/lib/omgdav/rack_util.rb +56 -0
- data/lib/omgdav/setup.rb +16 -0
- data/lib/omgdav/sync.rb +78 -0
- data/lib/omgdav/version.rb +2 -0
- data/lib/omgdav.rb +27 -0
- data/omgdav.gemspec +35 -0
- data/pkg.mk +175 -0
- data/setup.rb +1586 -0
- data/test/integration.rb +232 -0
- data/test/test_copy.rb +121 -0
- data/test/test_delete.rb +15 -0
- data/test/test_litmus.rb +61 -0
- data/test/test_move.rb +66 -0
- data/test/test_omgdav_app.rb +102 -0
- data/test/test_propfind.rb +30 -0
- data/test/test_proppatch.rb +156 -0
- data/test/test_put.rb +31 -0
- data/test/test_readonly.rb +22 -0
- data/test/test_sync.rb +49 -0
- data/test/test_urlmap.rb +59 -0
- data/test/test_worm.rb +26 -0
- metadata +342 -0
data/lib/omgdav/db.rb
ADDED
@@ -0,0 +1,258 @@
|
|
1
|
+
# -*- encoding: binary -*-
|
2
|
+
# :enddoc:
|
3
|
+
# Copyright (C) 2012, Eric Wong <normalperson@yhbt.net>
|
4
|
+
# License: AGPLv3 or later (https://www.gnu.org/licenses/agpl-3.0.txt)
|
5
|
+
require "omgdav"
|
6
|
+
module OMGDAV::DB
|
7
|
+
|
8
|
+
# returns a domain ID
|
9
|
+
def ensure_domain(domain)
|
10
|
+
domains = @db[:domains]
|
11
|
+
@db.transaction do
|
12
|
+
q = { domain: domain }
|
13
|
+
dom = domains[q]
|
14
|
+
dom ? dom[:id] : domains.insert(q)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
def node_merge_info(node, info)
|
19
|
+
info or return
|
20
|
+
node[:length] = info["length"]
|
21
|
+
end
|
22
|
+
|
23
|
+
def node_update(node, info = nil)
|
24
|
+
node_merge_info(node, info)
|
25
|
+
mtime = node[:mtime] = Time.now.to_i
|
26
|
+
node_id = node.delete(:id)
|
27
|
+
@db[:paths].where(id: node_id).update(node)
|
28
|
+
|
29
|
+
unless node[:collection]
|
30
|
+
@db[:paths].where(id: node[:parent_id]).update(mtime: mtime)
|
31
|
+
end
|
32
|
+
|
33
|
+
node[:id] = node_id
|
34
|
+
end
|
35
|
+
|
36
|
+
def node_update_maybe(node, info)
|
37
|
+
info or return
|
38
|
+
if info && node[:length] != info["length"]
|
39
|
+
node_update(node)
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
def node_ensure(parent_id, name, info = nil)
|
44
|
+
q = { name: name, domain_id: @domain_id, parent_id: parent_id }
|
45
|
+
collection = info ? false : true
|
46
|
+
|
47
|
+
node = @db[:paths][q]
|
48
|
+
if node
|
49
|
+
# node already exists
|
50
|
+
raise OMGDAV::TypeConflict if node[:collection] != collection
|
51
|
+
node_update_maybe(node, info)
|
52
|
+
else
|
53
|
+
# brand new node
|
54
|
+
node = q.dup
|
55
|
+
node[:created] = node[:mtime] = Time.now.to_i
|
56
|
+
node[:collection] = collection
|
57
|
+
node_merge_info(node, info)
|
58
|
+
begin
|
59
|
+
node[:id] = @db[:paths].insert(node)
|
60
|
+
rescue Sequel::DatabaseError
|
61
|
+
# we may conflict on insert if we didn't use a transaction
|
62
|
+
raise if @db.in_transaction?
|
63
|
+
node = @db[:paths][q] or raise
|
64
|
+
raise OMGDAV::TypeConflict if node[:collection] != collection
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
node
|
69
|
+
end
|
70
|
+
|
71
|
+
# ensures the given args maps to a directory, returns a hash for the row
|
72
|
+
def col_ensure(parent_id, colname)
|
73
|
+
node_ensure(parent_id, colname)
|
74
|
+
end
|
75
|
+
|
76
|
+
# ensures the given args maps to a file, returns a hash for the row
|
77
|
+
def file_ensure(parent_id, filename, info)
|
78
|
+
node_ensure(parent_id, filename, info)
|
79
|
+
end
|
80
|
+
|
81
|
+
def node_lookup(parent_id, name)
|
82
|
+
@db[:paths][name: name, domain_id: @domain_id, parent_id: parent_id]
|
83
|
+
end
|
84
|
+
|
85
|
+
def node_to_parts(node, cache)
|
86
|
+
root_id = root_node[:id]
|
87
|
+
return [] if root_id == node[:id] # special case
|
88
|
+
|
89
|
+
orig_name = node[:name]
|
90
|
+
cache_key = node[:parent_id]
|
91
|
+
unless parts = cache[cache_key]
|
92
|
+
parts = []
|
93
|
+
until root_id == node[:parent_id]
|
94
|
+
node = @db[:paths][id: node[:parent_id], domain_id: @domain_id] or break
|
95
|
+
parts.unshift(node[:name].dup.freeze)
|
96
|
+
end
|
97
|
+
|
98
|
+
cache[cache_key] = parts.freeze
|
99
|
+
end
|
100
|
+
|
101
|
+
parts += [ orig_name ]
|
102
|
+
end
|
103
|
+
|
104
|
+
def node_to_key(node, cache)
|
105
|
+
node_to_parts(node, cache).join("/")
|
106
|
+
end
|
107
|
+
|
108
|
+
def col_resolve(parts)
|
109
|
+
node = root_node
|
110
|
+
parts.each do |colpart|
|
111
|
+
node = node_lookup(node[:id], colpart)
|
112
|
+
return unless node
|
113
|
+
raise OMGDAV::TypeConflict unless node[:collection]
|
114
|
+
end
|
115
|
+
raise OMGDAV::TypeConflict unless node[:collection]
|
116
|
+
node
|
117
|
+
end
|
118
|
+
|
119
|
+
def node_resolve(parts)
|
120
|
+
return root_node if parts.empty?
|
121
|
+
parts = parts.dup
|
122
|
+
basename = parts.pop or return
|
123
|
+
|
124
|
+
parent = col_resolve(parts) or return
|
125
|
+
node_lookup(parent[:id], basename)
|
126
|
+
end
|
127
|
+
|
128
|
+
def file_resolve(parts)
|
129
|
+
node = node_resolve(parts) or return
|
130
|
+
raise OMGDAV::TypeConflict if node[:collection]
|
131
|
+
node
|
132
|
+
end
|
133
|
+
|
134
|
+
def col_vivify(parts)
|
135
|
+
col = root_node
|
136
|
+
parts.each do |colname|
|
137
|
+
col = col_ensure(col[:id], colname)
|
138
|
+
end
|
139
|
+
col
|
140
|
+
end
|
141
|
+
|
142
|
+
def node_delete(node)
|
143
|
+
rc = @db[:paths].where(id: node[:id]).delete
|
144
|
+
if rc != 1
|
145
|
+
key = node_to_key(node, {}).inspect
|
146
|
+
warn "expected rc=1 for delete(id=#{node[:id]}) (key=#{key}) got rc=#{rc}"
|
147
|
+
end
|
148
|
+
rc
|
149
|
+
end
|
150
|
+
|
151
|
+
def dead_props_get(node)
|
152
|
+
db_props = node[:dead_props] or return
|
153
|
+
db_props = JSON.parse(db_props)
|
154
|
+
mapping = {}
|
155
|
+
@db[:prop_mappings].each { |row| mapping[row[:id]] = row[:value] }
|
156
|
+
raw_props = {}
|
157
|
+
db_props.each do |ns, hash|
|
158
|
+
tmp = raw_props[mapping[ns.to_i] || ns] = {}
|
159
|
+
hash.each { |i, value| tmp[mapping[i.to_i] || i] = value }
|
160
|
+
end
|
161
|
+
raw_props
|
162
|
+
end
|
163
|
+
|
164
|
+
# returns the new property ID for a given value
|
165
|
+
def prop_mapping_ensure(value)
|
166
|
+
tbl = @db[:prop_mappings]
|
167
|
+
q = { value: value }
|
168
|
+
tbl.insert(q)
|
169
|
+
rescue Sequel::Database::Error
|
170
|
+
raise if @db.in_transaction?
|
171
|
+
mapping = tbl[q] or raise
|
172
|
+
mapping[:id]
|
173
|
+
end
|
174
|
+
|
175
|
+
def dead_props_set(node, raw_props)
|
176
|
+
mapping = {}
|
177
|
+
@db[:prop_mappings].each { |row| mapping[row[:value]] = row[:id] }
|
178
|
+
db_props = {}
|
179
|
+
raw_props.each do |ns, hash|
|
180
|
+
ns_id = mapping[ns] ||= prop_mapping_ensure(ns)
|
181
|
+
tmp = db_props[ns_id] = {}
|
182
|
+
hash.each do |key,value|
|
183
|
+
key_id = mapping[key] ||= prop_mapping_ensure(key)
|
184
|
+
tmp[key_id] = value
|
185
|
+
end
|
186
|
+
end
|
187
|
+
node[:dead_props] = db_props.to_json
|
188
|
+
end
|
189
|
+
|
190
|
+
def root_node
|
191
|
+
q = @root_node and return q
|
192
|
+
# root node always has parent_id:0
|
193
|
+
q = {
|
194
|
+
parent_id: 0,
|
195
|
+
collection: true,
|
196
|
+
name: "",
|
197
|
+
domain_id: @domain_id
|
198
|
+
}
|
199
|
+
node = @db[:paths][q] and return (@root_node = node)
|
200
|
+
q[:mtime] = q[:created] = Time.now.to_i
|
201
|
+
begin
|
202
|
+
q[:id] = @db[:paths].insert(q)
|
203
|
+
q
|
204
|
+
rescue Sequel::DatabaseError
|
205
|
+
# we may conflict on insert if we didn't use a transaction
|
206
|
+
raise if @db.in_transaction?
|
207
|
+
@root_node = @db[:paths][q] or raise
|
208
|
+
end
|
209
|
+
end
|
210
|
+
|
211
|
+
# returns the mime type based on key name
|
212
|
+
def key_mime_type(key) # :nodoc:
|
213
|
+
/(\.[^.]+)\z/ =~ key
|
214
|
+
Rack::Mime.mime_type($1) # nil => 'application/octet-stream'
|
215
|
+
end
|
216
|
+
|
217
|
+
def content_type(node, cache = nil)
|
218
|
+
if ctid = node[:contenttype]
|
219
|
+
if row = @db[:prop_mappings][id: ctid]
|
220
|
+
type = row[:value]
|
221
|
+
else
|
222
|
+
warn "DB consistency error: no property mapping for #{ctid.inspect}"
|
223
|
+
type = key_mime_type(node[:name])
|
224
|
+
end
|
225
|
+
cache ? (cache[ctid] = type) : type
|
226
|
+
else
|
227
|
+
key_mime_type(node[:name])
|
228
|
+
end
|
229
|
+
end
|
230
|
+
|
231
|
+
def check_attr!(name, value)
|
232
|
+
# TODO: check compliance, erring on the side of being too strict here
|
233
|
+
/\A[\w\.\+-]+\z/ =~ value or
|
234
|
+
raise OMGDAV::InvalidContentType, "#{name}=#{value.inspect} invalid"
|
235
|
+
end
|
236
|
+
|
237
|
+
def content_type_id(value)
|
238
|
+
# normalize the content type
|
239
|
+
type_subtype, parameter = value.split(/;\s+/, 2)
|
240
|
+
type, subtype = type_subtype.downcase.split(%r{/}, 2)
|
241
|
+
check_attr!(:type, type)
|
242
|
+
check_attr!(:subtype, subtype)
|
243
|
+
value = "#{type}/#{subtype}"
|
244
|
+
|
245
|
+
if parameter
|
246
|
+
attr_key, attr_val = parameter.split(/=/, 2)
|
247
|
+
attr_val or
|
248
|
+
raise OMGDAV::InvalidContentType,
|
249
|
+
"parameter=#{parameter.inspect} invalid"
|
250
|
+
attr_key.downcase!
|
251
|
+
check_attr!(:parameter, attr_key)
|
252
|
+
value << "; #{attr_key}=#{attr_val}"
|
253
|
+
end
|
254
|
+
|
255
|
+
row = @db[:prop_mappings][value: value] and return row[:id]
|
256
|
+
prop_mapping_ensure(value)
|
257
|
+
end
|
258
|
+
end
|
@@ -0,0 +1,66 @@
|
|
1
|
+
# -*- encoding: binary -*-
|
2
|
+
# :stopdoc:
|
3
|
+
# Copyright (C) 2012, Eric Wong <normalperson@yhbt.net>
|
4
|
+
# License: AGPLv3 or later (https://www.gnu.org/licenses/agpl-3.0.txt)
|
5
|
+
require "omgdav/rack_util"
|
6
|
+
require "omgdav/db"
|
7
|
+
|
8
|
+
module OMGDAV::Delete
|
9
|
+
include OMGDAV::DB
|
10
|
+
include OMGDAV::RackUtil
|
11
|
+
|
12
|
+
def delete_file(node, cache)
|
13
|
+
node_delete(node)
|
14
|
+
key = node_to_key(node, cache)
|
15
|
+
@mogc.delete(key)
|
16
|
+
rescue MogileFS::Backend::UnknownKeyError
|
17
|
+
warn "key=#{key.inspect} missing on delete from domain=#{@mogc.domain}"
|
18
|
+
end
|
19
|
+
|
20
|
+
def delete_collection(node, cache)
|
21
|
+
queue = [ node ]
|
22
|
+
q = { domain_id: @domain_id }
|
23
|
+
|
24
|
+
while cur_node = queue.pop
|
25
|
+
q[:parent_id] = cur_node[:id]
|
26
|
+
|
27
|
+
begin
|
28
|
+
state = :done
|
29
|
+
@db[:paths].where(q).limit(@sql_limit).each do |child_node|
|
30
|
+
if child_node[:collection]
|
31
|
+
queue << cur_node
|
32
|
+
queue << child_node
|
33
|
+
state = :descend
|
34
|
+
break
|
35
|
+
else
|
36
|
+
delete_file(child_node, cache)
|
37
|
+
state = :continue
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end while state == :continue
|
41
|
+
|
42
|
+
node_delete(cur_node) if state == :done
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
def delete_entry(node, cache)
|
47
|
+
if node[:collection]
|
48
|
+
delete_collection(node, cache)
|
49
|
+
else
|
50
|
+
delete_file(node, cache)
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
def call_delete(env)
|
55
|
+
# makes litmus happy with the "delete_fragment" test
|
56
|
+
# FRAGMENT may be set by Mongrel and descendents (unicorn/thin)
|
57
|
+
env["FRAGMENT"] and return r(400)
|
58
|
+
|
59
|
+
parts = path_split(env)
|
60
|
+
node = node_resolve(parts) or return r(404)
|
61
|
+
node[:parent_id] == 0 and return r(403)
|
62
|
+
delete_entry(node, {})
|
63
|
+
|
64
|
+
r(204)
|
65
|
+
end
|
66
|
+
end
|
data/lib/omgdav/get.rb
ADDED
@@ -0,0 +1,35 @@
|
|
1
|
+
# -*- encoding: binary -*-
|
2
|
+
# :enddoc:
|
3
|
+
# Copyright (C) 2012, Eric Wong <normalperson@yhbt.net>
|
4
|
+
# License: AGPLv3 or later (https://www.gnu.org/licenses/agpl-3.0.txt)
|
5
|
+
require "omgdav/rack_util"
|
6
|
+
require "omgdav/db"
|
7
|
+
|
8
|
+
module OMGDAV::Get
|
9
|
+
include OMGDAV::DB
|
10
|
+
include OMGDAV::RackUtil
|
11
|
+
|
12
|
+
def call_get(env)
|
13
|
+
parts = path_split(env)
|
14
|
+
basename = parts.pop
|
15
|
+
parent = col_resolve(parts) or return r(404)
|
16
|
+
node = node_lookup(parent[:id], basename) or return r(404)
|
17
|
+
length = node[:length]
|
18
|
+
parts << basename
|
19
|
+
@mogc.get_uris(parts.join("/"), @get_path_opts).each do |uri|
|
20
|
+
case res = OMGDAV::HttpGet.run(env, uri)
|
21
|
+
when Array
|
22
|
+
headers = res[1]
|
23
|
+
headers["Last-Modified"] = Time.at(node[:mtime]).httpdate
|
24
|
+
headers["Content-Type"] = content_type(node)
|
25
|
+
return res
|
26
|
+
else
|
27
|
+
logger(env).error("#{uri}: #{res.message} (#{res.class})")
|
28
|
+
end
|
29
|
+
end
|
30
|
+
r(500)
|
31
|
+
rescue MogileFS::Backend::UnknownKeyError,
|
32
|
+
MogileFS::Backend::DomainNotFoundError
|
33
|
+
r(404, "")
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,146 @@
|
|
1
|
+
# -*- encoding: binary -*-
|
2
|
+
# :enddoc:
|
3
|
+
# Copyright (C) 2012, Eric Wong <normalperson@yhbt.net>
|
4
|
+
# License: AGPLv3 or later (https://www.gnu.org/licenses/agpl-3.0.txt)
|
5
|
+
require "thread"
|
6
|
+
require "timeout"
|
7
|
+
require "kcar"
|
8
|
+
|
9
|
+
class OMGDAV::HttpGet < Kcar::Response # :nodoc:
|
10
|
+
# support gzip and partial requests if storage nodes support it
|
11
|
+
PASS = %w(Range Accept-Encoding).map! do |x|
|
12
|
+
[ "HTTP_#{x.tr('a-z-', 'A-Z_')}", x ]
|
13
|
+
end
|
14
|
+
|
15
|
+
DEF_ENV = { "REQUEST_METHOD" => "GET" }
|
16
|
+
ADDR_LOCK = Mutex.new
|
17
|
+
ADDR_CACHE = {}
|
18
|
+
POOL_LOCK = Mutex.new
|
19
|
+
POOL = Hash.new { |pool,ip_port| pool[ip_port] = [] }
|
20
|
+
|
21
|
+
def self.reset
|
22
|
+
ADDR_LOCK.synchronize { ADDR_CACHE.clear }
|
23
|
+
POOL_LOCK.synchronize { POOL.clear }
|
24
|
+
end
|
25
|
+
|
26
|
+
class MySocket < Kgio::Socket # :nodoc:
|
27
|
+
attr_writer :expiry
|
28
|
+
|
29
|
+
def wait_time
|
30
|
+
tout = @expiry ? @expiry - Time.now : @timeout
|
31
|
+
raise Timeout::Error if tout < 0
|
32
|
+
tout
|
33
|
+
end
|
34
|
+
|
35
|
+
def readpartial(bytes, buf = Thread.current[:omgdav_buf] ||= "")
|
36
|
+
case kgio_tryread(bytes, buf)
|
37
|
+
when String
|
38
|
+
return buf
|
39
|
+
when :wait_readable
|
40
|
+
kgio_wait_readable(wait_time)
|
41
|
+
when nil
|
42
|
+
raise EOFError, "end of file reached", []
|
43
|
+
end while true
|
44
|
+
end
|
45
|
+
|
46
|
+
def start(buf, timeout)
|
47
|
+
@timeout = timeout
|
48
|
+
@expiry = Time.now + timeout
|
49
|
+
case rv = kgio_trywrite(buf)
|
50
|
+
when :wait_writable
|
51
|
+
kgio_wait_writable(wait_time)
|
52
|
+
when nil
|
53
|
+
return
|
54
|
+
when String
|
55
|
+
buf = rv
|
56
|
+
end while true
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
# Called by the Rack server at the end of a successful response
|
61
|
+
def close
|
62
|
+
reusable = @parser.keepalive? && @parser.body_eof?
|
63
|
+
super
|
64
|
+
POOL_LOCK.synchronize { POOL[@ip_port] << self } if reusable
|
65
|
+
nil
|
66
|
+
end
|
67
|
+
|
68
|
+
def dispatch(req, timeout)
|
69
|
+
@sock.start(req, timeout)
|
70
|
+
end
|
71
|
+
|
72
|
+
# returns true if the socket is still alive, nil if dead
|
73
|
+
def sock_alive?
|
74
|
+
@reused = (:wait_readable == (@sock.kgio_tryread(1) rescue nil)) ?
|
75
|
+
true : @sock.close
|
76
|
+
end
|
77
|
+
|
78
|
+
# returns true if the socket was reused and thus retryable
|
79
|
+
def fail_retryable?
|
80
|
+
@sock.close
|
81
|
+
@reused
|
82
|
+
end
|
83
|
+
|
84
|
+
def initialize(sock, ip_port)
|
85
|
+
super(sock)
|
86
|
+
@reused = false
|
87
|
+
@ip_port = ip_port
|
88
|
+
end
|
89
|
+
|
90
|
+
def self.optional_headers(env)
|
91
|
+
PASS.map { |from, to| tmp = env[from] and "#{to}: #{tmp}\r\n" }.join
|
92
|
+
end
|
93
|
+
|
94
|
+
def self.run(env, uri, timeout = 2)
|
95
|
+
obj = obj_get(uri.host, uri.port)
|
96
|
+
env ||= DEF_ENV
|
97
|
+
m = env["REQUEST_METHOD"]
|
98
|
+
req = "#{m} #{uri.path} HTTP/1.0\r\n" \
|
99
|
+
"Connection: keep-alive\r\n" \
|
100
|
+
"#{optional_headers(env)}\r\n"
|
101
|
+
begin
|
102
|
+
obj.dispatch(req, timeout)
|
103
|
+
status, header, body = res = obj.rack
|
104
|
+
|
105
|
+
case m
|
106
|
+
when "GET"
|
107
|
+
body.sock.expiry = nil
|
108
|
+
when "HEAD"
|
109
|
+
# kcar doesn't know if it's a HEAD or GET response, and HEAD
|
110
|
+
# responses have Content-Length in it which fools kcar..
|
111
|
+
body.parser.body_bytes_left = 0
|
112
|
+
res[1] = header.dup
|
113
|
+
body.close # clobbers original header
|
114
|
+
res[2] = body = []
|
115
|
+
end
|
116
|
+
|
117
|
+
case status.to_i
|
118
|
+
when 206, 200
|
119
|
+
res
|
120
|
+
else
|
121
|
+
OMGDAV::BadResponse.new(status.to_s)
|
122
|
+
end
|
123
|
+
rescue => e
|
124
|
+
retry if obj.fail_retryable?
|
125
|
+
return e
|
126
|
+
end
|
127
|
+
end
|
128
|
+
|
129
|
+
def self.obj_get(ip, port)
|
130
|
+
ip_port = "#{ip}:#{port}"
|
131
|
+
while obj = POOL_LOCK.synchronize { POOL[ip_port].pop }
|
132
|
+
return obj if obj.sock_alive?
|
133
|
+
end
|
134
|
+
|
135
|
+
addr = ADDR_LOCK.synchronize do
|
136
|
+
ADDR_CACHE[ip_port] ||= Socket.sockaddr_in(port, ip)
|
137
|
+
end
|
138
|
+
|
139
|
+
new(MySocket.start(addr), ip_port)
|
140
|
+
end
|
141
|
+
|
142
|
+
def stream_to(io)
|
143
|
+
each { |buf| io.write(buf) }
|
144
|
+
close
|
145
|
+
end
|
146
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
# -*- encoding: binary -*-
|
2
|
+
# :enddoc:
|
3
|
+
# Copyright (C) 2012, Eric Wong <normalperson@yhbt.net>
|
4
|
+
# License: AGPLv3 or later (https://www.gnu.org/licenses/agpl-3.0.txt)
|
5
|
+
class OMGDAV::InputWrapper # :nodoc:
|
6
|
+
attr_reader :bytes
|
7
|
+
|
8
|
+
def initialize(env, max = 65536)
|
9
|
+
@input = env["rack.input"]
|
10
|
+
@bytes = 0
|
11
|
+
@max = max
|
12
|
+
end
|
13
|
+
|
14
|
+
def read(*args)
|
15
|
+
rv = @input.read(*args)
|
16
|
+
if String === rv
|
17
|
+
@bytes += rv.size
|
18
|
+
if @bytes > @max
|
19
|
+
buf = ""
|
20
|
+
while @input.read(16384, buf)
|
21
|
+
# drain input
|
22
|
+
end
|
23
|
+
raise "too many bytes for XML: #@bytes > #@max"
|
24
|
+
end
|
25
|
+
end
|
26
|
+
rv
|
27
|
+
end
|
28
|
+
|
29
|
+
# no-op to fool Nokogiri
|
30
|
+
def close
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
# Copyright (C) 2012, Eric Wong <normalperson@yhbt.net>
|
2
|
+
# License: AGPLv3 or later (https://www.gnu.org/licenses/agpl-3.0.txt)
|
3
|
+
# :enddoc:
|
4
|
+
Sequel.migration do
|
5
|
+
up do
|
6
|
+
create_table(:paths) do
|
7
|
+
primary_key :id, type: Bignum
|
8
|
+
Integer :domain_id, null: false # MogileFS domain
|
9
|
+
TrueClass :collection, default: false
|
10
|
+
Bignum :parent_id, null: false # parent collection
|
11
|
+
Bignum :mtime, null: false
|
12
|
+
Bignum :created, null: false
|
13
|
+
Bignum :length, null: true
|
14
|
+
TrueClass :executable, null: false, default: false
|
15
|
+
String :name, null: false
|
16
|
+
String :dead_props, null: true, text: true
|
17
|
+
unique [ :domain_id, :parent_id, :name ]
|
18
|
+
end
|
19
|
+
|
20
|
+
create_table(:domains) do
|
21
|
+
primary_key :id
|
22
|
+
String :domain, null: false, unique: true
|
23
|
+
end
|
24
|
+
|
25
|
+
create_table(:prop_mappings) do
|
26
|
+
primary_key :id
|
27
|
+
String :value, null: false, unique: true
|
28
|
+
end
|
29
|
+
|
30
|
+
create_table(:copymove_locks) do
|
31
|
+
Integer :domain_id, null: false
|
32
|
+
Bignum :src_id, null: false
|
33
|
+
Bignum :dst_id, null: false
|
34
|
+
Bignum :lock_expire, null: false
|
35
|
+
unique [ :domain_id, :src_id, :dst_id ]
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
down do
|
40
|
+
drop_table(:paths)
|
41
|
+
drop_table(:domains)
|
42
|
+
drop_table(:prop_mappings)
|
43
|
+
drop_table(:copymove_locks)
|
44
|
+
end
|
45
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
# Copyright (C) 2012, Eric Wong <normalperson@yhbt.net>
|
2
|
+
# License: AGPLv3 or later (https://www.gnu.org/licenses/agpl-3.0.txt)
|
3
|
+
# :enddoc:
|
4
|
+
Sequel.migration do
|
5
|
+
up do
|
6
|
+
alter_table(:paths) do
|
7
|
+
add_column :contenttype, Integer, null: true, default: nil
|
8
|
+
end
|
9
|
+
end
|
10
|
+
down do
|
11
|
+
alter_table(:paths) do
|
12
|
+
drop_column :contenttype
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
# Copyright (C) 2012, Eric Wong <normalperson@yhbt.net>
|
2
|
+
# License: AGPLv3 or later (https://www.gnu.org/licenses/agpl-3.0.txt)
|
3
|
+
# :enddoc:
|
4
|
+
Sequel.migration do
|
5
|
+
up do
|
6
|
+
create_table(:synctmp) do
|
7
|
+
primary_key :id, type: Bignum
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
down do
|
12
|
+
drop_table(:synctmp)
|
13
|
+
end
|
14
|
+
end
|
data/lib/omgdav/mkcol.rb
ADDED
@@ -0,0 +1,28 @@
|
|
1
|
+
# -*- encoding: binary -*-
|
2
|
+
# :stopdoc:
|
3
|
+
# Copyright (C) 2012, Eric Wong <normalperson@yhbt.net>
|
4
|
+
# License: AGPLv3 or later (https://www.gnu.org/licenses/agpl-3.0.txt)
|
5
|
+
require "omgdav/rack_util"
|
6
|
+
require "omgdav/db"
|
7
|
+
|
8
|
+
module OMGDAV::Mkcol
|
9
|
+
include OMGDAV::DB
|
10
|
+
include OMGDAV::RackUtil
|
11
|
+
|
12
|
+
def call_mkcol(env)
|
13
|
+
bytes = drain_input(env)
|
14
|
+
return r(415) if bytes > 0
|
15
|
+
parts = path_split(env)
|
16
|
+
return r(405) if col_resolve(parts)
|
17
|
+
basename = parts.pop or return r(403)
|
18
|
+
parent = col_resolve(parts) or return r(409)
|
19
|
+
if node = node_lookup(parent[:id], basename)
|
20
|
+
return r(403) unless node[:collection]
|
21
|
+
else
|
22
|
+
node = col_ensure(parent[:id], basename)
|
23
|
+
end
|
24
|
+
r(201)
|
25
|
+
rescue OMGDAV::TypeConflict
|
26
|
+
r(409)
|
27
|
+
end
|
28
|
+
end
|