rack-webdav 0.4.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.
- checksums.yaml +7 -0
- data/.gitignore +8 -0
- data/LICENSE +42 -0
- data/README.rdoc +336 -0
- data/bin/rack-webdav +126 -0
- data/lib/rack-webdav.rb +14 -0
- data/lib/rack-webdav/controller.rb +601 -0
- data/lib/rack-webdav/file.rb +37 -0
- data/lib/rack-webdav/file_resource_lock.rb +169 -0
- data/lib/rack-webdav/handler.rb +63 -0
- data/lib/rack-webdav/http_status.rb +108 -0
- data/lib/rack-webdav/interceptor.rb +22 -0
- data/lib/rack-webdav/interceptor_resource.rb +119 -0
- data/lib/rack-webdav/lock.rb +40 -0
- data/lib/rack-webdav/lock_store.rb +61 -0
- data/lib/rack-webdav/logger.rb +30 -0
- data/lib/rack-webdav/remote_file.rb +148 -0
- data/lib/rack-webdav/resource.rb +485 -0
- data/lib/rack-webdav/resources/file_resource.rb +379 -0
- data/lib/rack-webdav/resources/mongo_resource.rb +341 -0
- data/lib/rack-webdav/utils.rb +40 -0
- data/lib/rack-webdav/version.rb +17 -0
- data/rack-webdav.gemspec +31 -0
- metadata +206 -0
@@ -0,0 +1,379 @@
|
|
1
|
+
# encoding: UTF-8
|
2
|
+
|
3
|
+
require 'pstore'
|
4
|
+
require 'webrick/httputils'
|
5
|
+
require 'rack-webdav/file_resource_lock'
|
6
|
+
|
7
|
+
module RackWebDAV
|
8
|
+
|
9
|
+
class FileResource < Resource
|
10
|
+
|
11
|
+
include WEBrick::HTTPUtils
|
12
|
+
include RackWebDAV::Utils
|
13
|
+
|
14
|
+
# If this is a collection, return the child resources.
|
15
|
+
def children
|
16
|
+
Dir[file_path + '/*'].map do |path|
|
17
|
+
child ::File.basename(path)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
# Is this resource a collection?
|
22
|
+
def collection?
|
23
|
+
::File.directory?(file_path)
|
24
|
+
end
|
25
|
+
|
26
|
+
# Does this recource exist?
|
27
|
+
def exist?
|
28
|
+
::File.exist?(file_path)
|
29
|
+
end
|
30
|
+
|
31
|
+
# Return the creation time.
|
32
|
+
def creation_date
|
33
|
+
stat.ctime
|
34
|
+
end
|
35
|
+
|
36
|
+
# Return the time of last modification.
|
37
|
+
def last_modified
|
38
|
+
stat.mtime
|
39
|
+
end
|
40
|
+
|
41
|
+
# Set the time of last modification.
|
42
|
+
def last_modified=(time)
|
43
|
+
::File.utime(Time.now, time, file_path)
|
44
|
+
end
|
45
|
+
|
46
|
+
# Return an Etag, an unique hash value for this resource.
|
47
|
+
def etag
|
48
|
+
sprintf('%x-%x-%x', stat.ino, stat.size, stat.mtime.to_i)
|
49
|
+
end
|
50
|
+
|
51
|
+
# Return the mime type of this resource.
|
52
|
+
def content_type
|
53
|
+
if stat.directory?
|
54
|
+
"text/html"
|
55
|
+
else
|
56
|
+
mime_type(file_path, DefaultMimeTypes)
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
# Return the size in bytes for this resource.
|
61
|
+
def content_length
|
62
|
+
stat.size
|
63
|
+
end
|
64
|
+
|
65
|
+
# HTTP GET request.
|
66
|
+
#
|
67
|
+
# Write the content of the resource to the response.body.
|
68
|
+
def get(request, response)
|
69
|
+
raise NotFound unless exist?
|
70
|
+
if stat.directory?
|
71
|
+
response.body = ""
|
72
|
+
Rack::Directory.new(root).call(request.env)[2].each do |line|
|
73
|
+
response.body << line
|
74
|
+
end
|
75
|
+
response['Content-Length'] = response.body.bytesize.to_s
|
76
|
+
else
|
77
|
+
file = Rack::File.new(root)
|
78
|
+
status, headers, body = file.call(request.env)
|
79
|
+
|
80
|
+
if body.respond_to?(:to_path)
|
81
|
+
response.body = ::File.open(body.to_path, 'rb').read
|
82
|
+
response['Content-Length'] = response.body.bytesize.to_s
|
83
|
+
else
|
84
|
+
response.body = ""
|
85
|
+
total_bytes = 0
|
86
|
+
body.each { |part|
|
87
|
+
response.body += part
|
88
|
+
total_bytes += part.bytesize
|
89
|
+
}
|
90
|
+
response['Content-Length'] = total_bytes
|
91
|
+
end
|
92
|
+
end
|
93
|
+
OK
|
94
|
+
end
|
95
|
+
|
96
|
+
# HTTP PUT request.
|
97
|
+
#
|
98
|
+
# Save the content of the request.body.
|
99
|
+
def put(request, response)
|
100
|
+
write(request.body)
|
101
|
+
Created
|
102
|
+
end
|
103
|
+
|
104
|
+
# HTTP POST request.
|
105
|
+
#
|
106
|
+
# Usually forbidden.
|
107
|
+
def post(request, response)
|
108
|
+
raise HTTPStatus::Forbidden
|
109
|
+
end
|
110
|
+
|
111
|
+
# HTTP DELETE request.
|
112
|
+
#
|
113
|
+
# Delete this resource.
|
114
|
+
def delete
|
115
|
+
if stat.directory?
|
116
|
+
FileUtils.rm_rf(file_path)
|
117
|
+
else
|
118
|
+
::File.unlink(file_path)
|
119
|
+
end
|
120
|
+
::File.unlink(prop_path) if ::File.exists?(prop_path)
|
121
|
+
@_prop_hash = nil
|
122
|
+
NoContent
|
123
|
+
end
|
124
|
+
|
125
|
+
# HTTP COPY request.
|
126
|
+
#
|
127
|
+
# Copy this resource to given destination resource.
|
128
|
+
# Copy this resource to given destination resource.
|
129
|
+
def copy(dest, overwrite)
|
130
|
+
if(collection?)
|
131
|
+
if(dest.exist?)
|
132
|
+
if(dest.collection? && overwrite)
|
133
|
+
FileUtils.cp_r(file_path, dest.send(:file_path))
|
134
|
+
Created
|
135
|
+
else
|
136
|
+
if(overwrite)
|
137
|
+
FileUtils.rm(dest.send(:file_path))
|
138
|
+
FileUtils.cp_r(file_path, dest.send(:file_path))
|
139
|
+
NoContent
|
140
|
+
else
|
141
|
+
PreconditionFailed
|
142
|
+
end
|
143
|
+
end
|
144
|
+
else
|
145
|
+
FileUtils.cp_r(file_path, dest.send(:file_path))
|
146
|
+
Created
|
147
|
+
end
|
148
|
+
else
|
149
|
+
if(dest.exist? && !overwrite)
|
150
|
+
PreconditionFailed
|
151
|
+
else
|
152
|
+
if(::File.directory?(::File.dirname(dest.send(:file_path))))
|
153
|
+
new = !dest.exist?
|
154
|
+
if(dest.collection? && dest.exist?)
|
155
|
+
FileUtils.rm_rf(dest.send(:file_path))
|
156
|
+
end
|
157
|
+
FileUtils.cp(file_path, dest.send(:file_path).sub(/\/$/, ''))
|
158
|
+
FileUtils.cp(prop_path, dest.prop_path) if ::File.exist? prop_path
|
159
|
+
new ? Created : NoContent
|
160
|
+
else
|
161
|
+
Conflict
|
162
|
+
end
|
163
|
+
end
|
164
|
+
end
|
165
|
+
end
|
166
|
+
|
167
|
+
# HTTP MOVE request.
|
168
|
+
#
|
169
|
+
# Move this resource to given destination resource.
|
170
|
+
def move(*args)
|
171
|
+
result = copy(*args)
|
172
|
+
delete if [Created, NoContent].include?(result)
|
173
|
+
result
|
174
|
+
end
|
175
|
+
|
176
|
+
# HTTP MKCOL request.
|
177
|
+
#
|
178
|
+
# Create this resource as collection.
|
179
|
+
def make_collection
|
180
|
+
if(request.body.read.to_s == '')
|
181
|
+
if(::File.directory?(file_path))
|
182
|
+
MethodNotAllowed
|
183
|
+
else
|
184
|
+
if(::File.directory?(::File.dirname(file_path)) && !::File.exists?(file_path))
|
185
|
+
Dir.mkdir(file_path)
|
186
|
+
Created
|
187
|
+
else
|
188
|
+
Conflict
|
189
|
+
end
|
190
|
+
end
|
191
|
+
else
|
192
|
+
UnsupportedMediaType
|
193
|
+
end
|
194
|
+
end
|
195
|
+
|
196
|
+
# Write to this resource from given IO.
|
197
|
+
def write(io)
|
198
|
+
tempfile = "#{file_path}.#{Process.pid}.#{object_id}"
|
199
|
+
open(tempfile, "wb") do |file|
|
200
|
+
while part = io.read(8192)
|
201
|
+
file << part
|
202
|
+
end
|
203
|
+
end
|
204
|
+
::File.rename(tempfile, file_path)
|
205
|
+
ensure
|
206
|
+
::File.unlink(tempfile) rescue nil
|
207
|
+
end
|
208
|
+
|
209
|
+
# name:: String - Property name
|
210
|
+
# Returns the value of the given property
|
211
|
+
def get_property(name)
|
212
|
+
begin
|
213
|
+
super
|
214
|
+
rescue NotImplemented
|
215
|
+
custom_props(name)
|
216
|
+
end
|
217
|
+
end
|
218
|
+
|
219
|
+
# name:: String - Property name
|
220
|
+
# value:: New value
|
221
|
+
# Set the property to the given value
|
222
|
+
def set_property(name, value)
|
223
|
+
begin
|
224
|
+
super
|
225
|
+
rescue NotImplemented
|
226
|
+
set_custom_props(name,value)
|
227
|
+
end
|
228
|
+
end
|
229
|
+
|
230
|
+
def remove_property(element)
|
231
|
+
prop_hash.transaction do
|
232
|
+
prop_hash.delete(to_element_key(element))
|
233
|
+
prop_hash.commit
|
234
|
+
end
|
235
|
+
val = prop_hash.transaction{ prop_hash[to_element_key(element)] }
|
236
|
+
end
|
237
|
+
|
238
|
+
def lock(args)
|
239
|
+
unless(parent_exists?)
|
240
|
+
Conflict
|
241
|
+
else
|
242
|
+
lock_check(args[:type])
|
243
|
+
lock = FileResourceLock.explicit_locks(@path, root, :scope => args[:scope], :kind => args[:type], :user => @user)
|
244
|
+
unless(lock)
|
245
|
+
token = UUIDTools::UUID.random_create.to_s
|
246
|
+
lock = FileResourceLock.generate(@path, @user, token, root)
|
247
|
+
lock.scope = args[:scope]
|
248
|
+
lock.kind = args[:type]
|
249
|
+
lock.owner = args[:owner]
|
250
|
+
lock.depth = args[:depth]
|
251
|
+
if(args[:timeout])
|
252
|
+
lock.timeout = args[:timeout] <= @max_timeout && args[:timeout] > 0 ? args[:timeout] : @max_timeout
|
253
|
+
else
|
254
|
+
lock.timeout = @default_timeout
|
255
|
+
end
|
256
|
+
lock.save
|
257
|
+
end
|
258
|
+
begin
|
259
|
+
lock_check(args[:type])
|
260
|
+
rescue RackWebDAV::LockFailure => lock_failure
|
261
|
+
lock.destroy
|
262
|
+
raise lock_failure
|
263
|
+
rescue HTTPStatus::Status => status
|
264
|
+
status
|
265
|
+
end
|
266
|
+
[lock.remaining_timeout, lock.token]
|
267
|
+
end
|
268
|
+
end
|
269
|
+
|
270
|
+
def unlock(token)
|
271
|
+
token = token.slice(1, token.length - 2)
|
272
|
+
if(token.nil? || token.empty?)
|
273
|
+
BadRequest
|
274
|
+
else
|
275
|
+
lock = FileResourceLock.find_by_token(token, root)
|
276
|
+
if lock.nil?
|
277
|
+
Forbidden
|
278
|
+
elsif(lock.path !~ /^#{Regexp.escape(@path)}.*$/)
|
279
|
+
Conflict
|
280
|
+
else
|
281
|
+
lock.destroy
|
282
|
+
NoContent
|
283
|
+
end
|
284
|
+
end
|
285
|
+
end
|
286
|
+
|
287
|
+
protected
|
288
|
+
|
289
|
+
def lock_check(lock_type=nil)
|
290
|
+
if(FileResourceLock.explicitly_locked?(@path, root))
|
291
|
+
raise Locked if lock_type && lock_type == 'exclusive'
|
292
|
+
#raise Locked if FileResourceLock.explicit_locks(@path, root).find(:all, :conditions => ["scope = 'exclusive' AND user_id != ?", @user.id]).size > 0
|
293
|
+
elsif(FileResourceLock.implicitly_locked?(@path, root))
|
294
|
+
if(lock_type.to_s == 'exclusive')
|
295
|
+
locks = FileResourceLock.implicit_locks(@path)
|
296
|
+
failure = RackWebDAV::LockFailure.new("Failed to lock: #{@path}")
|
297
|
+
locks.each do |lock|
|
298
|
+
failure.add_failure(@path, Locked)
|
299
|
+
end
|
300
|
+
raise failure
|
301
|
+
else
|
302
|
+
locks = FileResourceLock.implict_locks(@path).find(:all, :conditions => ["scope = 'exclusive' AND user_id != ?", @user.id])
|
303
|
+
if(locks.size > 0)
|
304
|
+
failure = LockFailure.new("Failed to lock: #{@path}")
|
305
|
+
locks.each do |lock|
|
306
|
+
failure.add_failure(@path, Locked)
|
307
|
+
end
|
308
|
+
raise failure
|
309
|
+
end
|
310
|
+
end
|
311
|
+
end
|
312
|
+
end
|
313
|
+
|
314
|
+
def set_custom_props(element, val)
|
315
|
+
prop_hash.transaction do
|
316
|
+
prop_hash[to_element_key(element)] = val
|
317
|
+
prop_hash.commit
|
318
|
+
end
|
319
|
+
end
|
320
|
+
|
321
|
+
def custom_props(element)
|
322
|
+
val = prop_hash.transaction(true) do
|
323
|
+
prop_hash[to_element_key(element)]
|
324
|
+
end
|
325
|
+
raise NotFound unless val
|
326
|
+
val
|
327
|
+
end
|
328
|
+
|
329
|
+
def store_directory
|
330
|
+
path = ::File.join(root, '.attrib_store')
|
331
|
+
unless(::File.directory?(::File.dirname(path)))
|
332
|
+
FileUtils.mkdir_p(::File.dirname(path))
|
333
|
+
end
|
334
|
+
path
|
335
|
+
end
|
336
|
+
|
337
|
+
def prop_path
|
338
|
+
path = ::File.join(store_directory, "#{::File.join(::File.dirname(file_path), ::File.basename(file_path)).gsub('/', '_')}.pstore")
|
339
|
+
unless(::File.directory?(::File.dirname(path)))
|
340
|
+
FileUtils.mkdir_p(::File.dirname(path))
|
341
|
+
end
|
342
|
+
path
|
343
|
+
end
|
344
|
+
|
345
|
+
def lock_path
|
346
|
+
path = ::File.join(store_directory, 'locks.pstore')
|
347
|
+
unless(::File.directory?(::File.dirname(path)))
|
348
|
+
FileUtils.mkdir_p(::File.dirname(path))
|
349
|
+
end
|
350
|
+
path
|
351
|
+
end
|
352
|
+
|
353
|
+
def prop_hash
|
354
|
+
@_prop_hash ||= IS_18 ? PStore.new(prop_path) : PStore.new(prop_path, true)
|
355
|
+
end
|
356
|
+
|
357
|
+
def authenticate(user, pass)
|
358
|
+
if(options[:username])
|
359
|
+
options[:username] == user && options[:password] == pass
|
360
|
+
else
|
361
|
+
true
|
362
|
+
end
|
363
|
+
end
|
364
|
+
|
365
|
+
def root
|
366
|
+
@options[:root]
|
367
|
+
end
|
368
|
+
|
369
|
+
def file_path
|
370
|
+
::File.join(root, path)
|
371
|
+
end
|
372
|
+
|
373
|
+
def stat
|
374
|
+
@stat ||= ::File.stat(file_path)
|
375
|
+
end
|
376
|
+
|
377
|
+
end
|
378
|
+
|
379
|
+
end
|
@@ -0,0 +1,341 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
require 'mime/types'
|
3
|
+
|
4
|
+
module RackWebDAV
|
5
|
+
|
6
|
+
class MongoResource < RackWebDAV::Resource
|
7
|
+
|
8
|
+
# @@logger = Rails.logger
|
9
|
+
|
10
|
+
def initialize(public_path, path, request, response, options)
|
11
|
+
# 'ASCII-8BIT'で渡される場合があるので'UTF-8'を指定しておく
|
12
|
+
_force_encoding!(public_path)
|
13
|
+
_force_encoding!(path)
|
14
|
+
super(public_path, path, request, response, options)
|
15
|
+
@filesystem = Mongo::GridFileSystem.new(Mongoid.database)
|
16
|
+
@collection = Mongoid.database.collection('fs.files')
|
17
|
+
if options[:bson]
|
18
|
+
@bson = options[:bson]
|
19
|
+
elsif path.length <= 1
|
20
|
+
# ルートの場合 (''の場合と'/'の場合がある)
|
21
|
+
@bson = {'filename' => root + '/'}
|
22
|
+
else
|
23
|
+
# ファイルかディレクトリが、パラメータだけでは判断できない。ので \/? が必要。
|
24
|
+
# だから、ディレクトリと同名のファイルは、作成できない。
|
25
|
+
@bson = @collection.find_one({:filename => /^#{Regexp.escape(file_path)}\/?$/}) rescue nil
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
def child(bson)
|
30
|
+
path = remove(bson['filename'], root)
|
31
|
+
public_path = @options[:root_uri_path] + path
|
32
|
+
@options[:bson] = bson
|
33
|
+
self.class.new(public_path, path, @request, @response, @options)
|
34
|
+
end
|
35
|
+
|
36
|
+
# If this is a collection, return the child resources.
|
37
|
+
def children
|
38
|
+
# Dir[file_path + '/*'].map do |path|
|
39
|
+
# child File.basename(path)
|
40
|
+
# end
|
41
|
+
@collection.find({:filename => /^#{Regexp.escape(@bson['filename'])}[^\/]+\/?$/}).map do |bson|
|
42
|
+
child bson
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
# Is this resource a collection?
|
47
|
+
def collection?
|
48
|
+
# File.directory?(file_path)
|
49
|
+
@bson && _collection?(@bson['filename'])
|
50
|
+
end
|
51
|
+
|
52
|
+
# Does this recource exist?
|
53
|
+
def exist?
|
54
|
+
# File.exist?(file_path)
|
55
|
+
@bson
|
56
|
+
end
|
57
|
+
|
58
|
+
# Return the creation time.
|
59
|
+
def creation_date
|
60
|
+
# stat.ctime
|
61
|
+
@bson['uploadDate'] || Date.new
|
62
|
+
end
|
63
|
+
|
64
|
+
# Return the time of last modification.
|
65
|
+
def last_modified
|
66
|
+
# stat.mtime
|
67
|
+
@bson['uploadDate'] || Date.new
|
68
|
+
end
|
69
|
+
|
70
|
+
# Set the time of last modification.
|
71
|
+
def last_modified=(time)
|
72
|
+
# File.utime(Time.now, time, file_path)
|
73
|
+
end
|
74
|
+
|
75
|
+
# Return an Etag, an unique hash value for this resource.
|
76
|
+
def etag
|
77
|
+
# sprintf('%x-%x-%x', stat.ino, stat.size, stat.mtime.to_i)
|
78
|
+
@bson['_id'].to_s
|
79
|
+
end
|
80
|
+
|
81
|
+
# Return the mime type of this resource.
|
82
|
+
def content_type
|
83
|
+
# if stat.directory?
|
84
|
+
# "text/html"
|
85
|
+
# else
|
86
|
+
# mime_type(file_path, DefaultMimeTypes)
|
87
|
+
# end
|
88
|
+
@bson['contentType'] || "text/html"
|
89
|
+
end
|
90
|
+
|
91
|
+
# Return the size in bytes for this resource.
|
92
|
+
def content_length
|
93
|
+
# stat.size
|
94
|
+
@bson['length'] || 0
|
95
|
+
end
|
96
|
+
|
97
|
+
# HTTP GET request.
|
98
|
+
#
|
99
|
+
# Write the content of the resource to the response.body.
|
100
|
+
def get(request, response)
|
101
|
+
raise NotFound unless exist?
|
102
|
+
# if stat.directory?
|
103
|
+
# response.body = ""
|
104
|
+
# Rack::Directory.new(root).call(request.env)[2].each do |line|
|
105
|
+
# response.body << line
|
106
|
+
# end
|
107
|
+
# response['Content-Length'] = response.body.size.to_s
|
108
|
+
# else
|
109
|
+
# file = Rack::File.new(root)
|
110
|
+
# response.body = file
|
111
|
+
# end
|
112
|
+
if collection?
|
113
|
+
response.body = "<html>"
|
114
|
+
response.body << "<h2>" + file_path.html_safe + "</h2>"
|
115
|
+
children.each do |child|
|
116
|
+
name = child.file_path.html_safe
|
117
|
+
path = child.public_path
|
118
|
+
response.body << "<a href='" + path + "'>" + name + "</a>"
|
119
|
+
response.body << "</br>"
|
120
|
+
end
|
121
|
+
response.body << "</html>"
|
122
|
+
response['Content-Length'] = response.body.size.to_s
|
123
|
+
response['Content-Type'] = 'text/html'
|
124
|
+
else
|
125
|
+
@filesystem.open(file_path, 'r') do |f|
|
126
|
+
response.body = f
|
127
|
+
response['Content-Type'] = @bson['contentType']
|
128
|
+
end
|
129
|
+
end
|
130
|
+
|
131
|
+
end
|
132
|
+
|
133
|
+
# HTTP PUT request.
|
134
|
+
#
|
135
|
+
# Save the content of the request.body.
|
136
|
+
def put(request, response)
|
137
|
+
write(request.body)
|
138
|
+
Created
|
139
|
+
end
|
140
|
+
|
141
|
+
# HTTP POST request.
|
142
|
+
#
|
143
|
+
# Usually forbidden.
|
144
|
+
def post(request, response)
|
145
|
+
raise HTTPStatus::Forbidden
|
146
|
+
end
|
147
|
+
|
148
|
+
# HTTP DELETE request.
|
149
|
+
#
|
150
|
+
# Delete this resource.
|
151
|
+
def delete
|
152
|
+
# if stat.directory?
|
153
|
+
# FileUtils.rm_rf(file_path)
|
154
|
+
# else
|
155
|
+
# File.unlink(file_path)
|
156
|
+
# end
|
157
|
+
if collection?
|
158
|
+
@collection.find({:filename => /^#{Regexp.escape(@bson['filename'])}/}).each do |bson|
|
159
|
+
@collection.remove(bson)
|
160
|
+
end
|
161
|
+
else
|
162
|
+
@collection.remove(@bson)
|
163
|
+
end
|
164
|
+
NoContent
|
165
|
+
end
|
166
|
+
|
167
|
+
# HTTP COPY request.
|
168
|
+
#
|
169
|
+
# Copy this resource to given destination resource.
|
170
|
+
def copy(dest, overwrite = false)
|
171
|
+
# if(dest.path == path)
|
172
|
+
# Conflict
|
173
|
+
# elsif(stat.directory?)
|
174
|
+
# dest.make_collection
|
175
|
+
# FileUtils.cp_r("#{file_path}/.", "#{dest.send(:file_path)}/")
|
176
|
+
# OK
|
177
|
+
# else
|
178
|
+
# exists = File.exists?(file_path)
|
179
|
+
# if(exists && !overwrite)
|
180
|
+
# PreconditionFailed
|
181
|
+
# else
|
182
|
+
# open(file_path, "rb") do |file|
|
183
|
+
# dest.write(file)
|
184
|
+
# end
|
185
|
+
# exists ? NoContent : Created
|
186
|
+
# end
|
187
|
+
# end
|
188
|
+
|
189
|
+
# ディレクトリなら末尾に「/」をつける。
|
190
|
+
# (dstにもともと「/」が付いているかどうか、クライアントに依存している)
|
191
|
+
# CarotDAV : 「/」が付いていない
|
192
|
+
# TeamFile : 「/」が付いている
|
193
|
+
dest.collection! if collection?
|
194
|
+
|
195
|
+
src = @bson['filename']
|
196
|
+
dst = dest.file_path
|
197
|
+
exists = nil
|
198
|
+
|
199
|
+
@collection.find({:filename => /^#{Regexp.escape(src)}/}).each do |bson|
|
200
|
+
src_name = bson['filename']
|
201
|
+
dst_name = dst + src_name.slice(src.length, src_name.length)
|
202
|
+
|
203
|
+
exists = @collection.find_one({:filename => dst_name}) rescue nil
|
204
|
+
|
205
|
+
return PreconditionFailed if (exists && !overwrite && !collection?)
|
206
|
+
|
207
|
+
@filesystem.open(src_name, "r") do |src|
|
208
|
+
@filesystem.open(dst_name, "w") do |dst|
|
209
|
+
dst.write(src) if src.file_length > 0
|
210
|
+
end
|
211
|
+
end
|
212
|
+
|
213
|
+
@collection.remove(exists) if exists
|
214
|
+
end
|
215
|
+
|
216
|
+
collection? ? Created : (exists ? NoContent : Created)
|
217
|
+
end
|
218
|
+
|
219
|
+
# HTTP MOVE request.
|
220
|
+
#
|
221
|
+
# Move this resource to given destination resource.
|
222
|
+
def move(dest, overwrite = false)
|
223
|
+
|
224
|
+
# ディレクトリなら末尾に「/」をつける。
|
225
|
+
# (dstにもともと「/」が付いているかどうか、クライアントに依存している)
|
226
|
+
# CarotDAV : 「/」が付いていない
|
227
|
+
# TeamFile : 「/」が付いている
|
228
|
+
dest.collection! if collection?
|
229
|
+
|
230
|
+
src = @bson['filename']
|
231
|
+
dst = dest.file_path
|
232
|
+
exists = nil
|
233
|
+
|
234
|
+
@collection.find({:filename => /^#{Regexp.escape(src)}/}).each do |bson|
|
235
|
+
src_name = bson['filename']
|
236
|
+
dst_name = dst + src_name.slice(src.length, src_name.length)
|
237
|
+
|
238
|
+
exists = @collection.find_one({:filename => dst_name}) rescue nil
|
239
|
+
|
240
|
+
# http://mongoid.org/docs/persistence/atomic.html
|
241
|
+
# http://rubydoc.info/github/mongoid/mongoid/master/Mongoid/Collection#update-instance_method
|
242
|
+
@collection.update({'_id' => bson['_id']}, {'$set' => {'filename' => dst_name}}, :safe => true)
|
243
|
+
|
244
|
+
@collection.remove(exists) if exists
|
245
|
+
end
|
246
|
+
|
247
|
+
collection? ? Created : (exists ? NoContent : Created)
|
248
|
+
end
|
249
|
+
|
250
|
+
# HTTP MKCOL request.
|
251
|
+
#
|
252
|
+
# Create this resource as collection.
|
253
|
+
def make_collection
|
254
|
+
# Dir.mkdir(file_path)
|
255
|
+
# Created
|
256
|
+
|
257
|
+
# ディレクトリなら末尾に「/」をつける。
|
258
|
+
# (dstにもともと「/」が付いているかどうか、クライアントに依存している)
|
259
|
+
# CarotDAV : 「/」が付いていない
|
260
|
+
# TeamFile : 「/」が付いている
|
261
|
+
collection!
|
262
|
+
|
263
|
+
bson = @collection.find_one({:filename => file_path}) rescue nil
|
264
|
+
|
265
|
+
# 0バイトのファイルを作成しディレクトリの代わりとする
|
266
|
+
@filesystem.open(file_path, "w") { |f| } if !bson
|
267
|
+
|
268
|
+
# @@logger.error('make_collection : ' + file_path)
|
269
|
+
|
270
|
+
Created
|
271
|
+
end
|
272
|
+
|
273
|
+
# Write to this resource from given IO.
|
274
|
+
def write(io)
|
275
|
+
# tempfile = "#{file_path}.#{Process.pid}.#{object_id}"
|
276
|
+
# open(tempfile, "wb") do |file|
|
277
|
+
# while part = io.read(8192)
|
278
|
+
# file << part
|
279
|
+
# end
|
280
|
+
# end
|
281
|
+
# File.rename(tempfile, file_path)
|
282
|
+
# ensure
|
283
|
+
# File.unlink(tempfile) rescue nil
|
284
|
+
|
285
|
+
# 同名のファイルができないように
|
286
|
+
bson = @collection.find_one({:filename => file_path}) rescue nil
|
287
|
+
|
288
|
+
@filesystem.open(file_path, "w", :content_type => _content_type(file_path)) { |f| f.write(io) }
|
289
|
+
|
290
|
+
# 同名のファイルができないように
|
291
|
+
@collection.remove(bson) if bson
|
292
|
+
|
293
|
+
end
|
294
|
+
|
295
|
+
protected
|
296
|
+
|
297
|
+
def file_path
|
298
|
+
root + path
|
299
|
+
end
|
300
|
+
|
301
|
+
# ファイル名の末尾に「/」を付加してディレクトリ(コレクション)とする
|
302
|
+
def collection!
|
303
|
+
path << '/' if !_collection?(path)
|
304
|
+
end
|
305
|
+
|
306
|
+
private
|
307
|
+
|
308
|
+
def _content_type(filename)
|
309
|
+
MIME::Types.type_for(filename).first.to_s || 'text/html'
|
310
|
+
end
|
311
|
+
|
312
|
+
def authenticate(user, pass)
|
313
|
+
if(options[:username])
|
314
|
+
options[:username] == user && options[:password] == pass
|
315
|
+
else
|
316
|
+
true
|
317
|
+
end
|
318
|
+
end
|
319
|
+
|
320
|
+
# path1の先頭からpath2を取り除く
|
321
|
+
def remove(path1, path2)
|
322
|
+
path1.slice(path2.length, path1.length)
|
323
|
+
end
|
324
|
+
|
325
|
+
def root
|
326
|
+
@options[:root]
|
327
|
+
end
|
328
|
+
|
329
|
+
# ファイル名の末尾が「/」のファイルをディレクトリ(コレクション)とする
|
330
|
+
def _collection?(path)
|
331
|
+
path && path[-1].chr == '/'
|
332
|
+
end
|
333
|
+
|
334
|
+
# 'ASCII-8BIT'で渡される場合があるので'UTF-8'を指定しておく
|
335
|
+
def _force_encoding!(str)
|
336
|
+
str.force_encoding('UTF-8')
|
337
|
+
end
|
338
|
+
|
339
|
+
end
|
340
|
+
|
341
|
+
end
|