omgdav 0.0.0
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 +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
|