gridfs-rackdav 0.1.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 +5 -0
- data/.gitignore +21 -0
- data/LICENSE +20 -0
- data/README.md +41 -0
- data/Rakefile +47 -0
- data/VERSION +1 -0
- data/gridfs-rackdav.gemspec +63 -0
- data/lib/gridfs-rackdav.rb +8 -0
- data/lib/gridfs-rackdav/gridfs_model.rb +94 -0
- data/lib/gridfs-rackdav/gridfs_resource.rb +183 -0
- data/spec/gridfs-rackdav_spec.rb +275 -0
- data/spec/spec.opts +1 -0
- data/spec/spec_helper.rb +9 -0
- metadata +98 -0
data/.document
ADDED
data/.gitignore
ADDED
data/LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2009 Mihael Konjević
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,41 @@
|
|
1
|
+
---
|
2
|
+
GridFS-RackDAV - Mongo GridFS resource for RackDAV
|
3
|
+
---
|
4
|
+
|
5
|
+
## Install
|
6
|
+
|
7
|
+
GridFS-RackDAV is hosted at Gemcutter:
|
8
|
+
|
9
|
+
$ sudo gem install gemcutter
|
10
|
+
$ sudo gem tumble
|
11
|
+
$ sudo gem install gridfs-rackdav
|
12
|
+
|
13
|
+
You should also have MongoDB installed.
|
14
|
+
|
15
|
+
## Quickstart
|
16
|
+
|
17
|
+
Use simple rackup script for serving files from GridFS
|
18
|
+
|
19
|
+
@@ruby
|
20
|
+
|
21
|
+
require 'rubygems'
|
22
|
+
require 'rack_dav'
|
23
|
+
require 'gridfs-rackdav'
|
24
|
+
|
25
|
+
connection = Mongo::Connection.new('localhost').db('name-of-your-db')
|
26
|
+
|
27
|
+
use Rack::CommonLogger
|
28
|
+
|
29
|
+
run RackDAV::Handler.new({
|
30
|
+
:root => 'root_of_collection',
|
31
|
+
:connection => connection,
|
32
|
+
:resource_class => GridFSRackDAV::GridFSResource
|
33
|
+
})
|
34
|
+
|
35
|
+
## Specs
|
36
|
+
|
37
|
+
GridFS-RackDAV resource passes all of original specs that are included with RackDAV project.
|
38
|
+
|
39
|
+
## Copyright
|
40
|
+
|
41
|
+
Copyright (c) 2009 Mihael Konjević. See LICENSE for details.
|
data/Rakefile
ADDED
@@ -0,0 +1,47 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'rake'
|
3
|
+
|
4
|
+
begin
|
5
|
+
require 'jeweler'
|
6
|
+
Jeweler::Tasks.new do |gem|
|
7
|
+
gem.name = "gridfs-rackdav"
|
8
|
+
gem.summary = "GridFS resource adapter for RackDAV"
|
9
|
+
gem.description = "gridfs-rackdav enables you to use GridFS as backend for WebDAV collections with RackDAV application"
|
10
|
+
gem.email = "konjevic@gmail.com"
|
11
|
+
gem.homepage = "http://github.com/retro/gridfs-rackdav"
|
12
|
+
gem.authors = ["Mihael Konjević"]
|
13
|
+
gem.add_development_dependency "rspec", ">= 1.2.9"
|
14
|
+
gem.add_dependency('georgi-rack_dav', '>= 0.1.1')
|
15
|
+
gem.add_dependency('mongo', '>= 0.1')
|
16
|
+
# gem is a Gem::Specification... see http://www.rubygems.org/read/chapter/20 for additional settings
|
17
|
+
end
|
18
|
+
Jeweler::GemcutterTasks.new
|
19
|
+
rescue LoadError
|
20
|
+
puts "Jeweler (or a dependency) not available. Install it with: sudo gem install jeweler"
|
21
|
+
end
|
22
|
+
|
23
|
+
require 'spec/rake/spectask'
|
24
|
+
Spec::Rake::SpecTask.new(:spec) do |spec|
|
25
|
+
spec.libs << 'lib' << 'spec'
|
26
|
+
spec.spec_files = FileList['spec/**/*_spec.rb']
|
27
|
+
end
|
28
|
+
|
29
|
+
Spec::Rake::SpecTask.new(:rcov) do |spec|
|
30
|
+
spec.libs << 'lib' << 'spec'
|
31
|
+
spec.pattern = 'spec/**/*_spec.rb'
|
32
|
+
spec.rcov = true
|
33
|
+
end
|
34
|
+
|
35
|
+
task :spec => :check_dependencies
|
36
|
+
|
37
|
+
task :default => :spec
|
38
|
+
|
39
|
+
require 'rake/rdoctask'
|
40
|
+
Rake::RDocTask.new do |rdoc|
|
41
|
+
version = File.exist?('VERSION') ? File.read('VERSION') : ""
|
42
|
+
|
43
|
+
rdoc.rdoc_dir = 'rdoc'
|
44
|
+
rdoc.title = "gridfs-rackdav #{version}"
|
45
|
+
rdoc.rdoc_files.include('README*')
|
46
|
+
rdoc.rdoc_files.include('lib/**/*.rb')
|
47
|
+
end
|
data/VERSION
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
0.1.0
|
@@ -0,0 +1,63 @@
|
|
1
|
+
# Generated by jeweler
|
2
|
+
# DO NOT EDIT THIS FILE DIRECTLY
|
3
|
+
# Instead, edit Jeweler::Tasks in Rakefile, and run the gemspec command
|
4
|
+
# -*- encoding: utf-8 -*-
|
5
|
+
|
6
|
+
Gem::Specification.new do |s|
|
7
|
+
s.name = %q{gridfs-rackdav}
|
8
|
+
s.version = "0.1.0"
|
9
|
+
|
10
|
+
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
11
|
+
s.authors = ["Mihael Konjevi\304\207"]
|
12
|
+
s.date = %q{2010-02-05}
|
13
|
+
s.description = %q{gridfs-rackdav enables you to use GridFS as backend for WebDAV collections with RackDAV application}
|
14
|
+
s.email = %q{konjevic@gmail.com}
|
15
|
+
s.extra_rdoc_files = [
|
16
|
+
"LICENSE",
|
17
|
+
"README.md"
|
18
|
+
]
|
19
|
+
s.files = [
|
20
|
+
".document",
|
21
|
+
".gitignore",
|
22
|
+
"LICENSE",
|
23
|
+
"README.md",
|
24
|
+
"Rakefile",
|
25
|
+
"VERSION",
|
26
|
+
"gridfs-rackdav.gemspec",
|
27
|
+
"lib/gridfs-rackdav.rb",
|
28
|
+
"lib/gridfs-rackdav/gridfs_model.rb",
|
29
|
+
"lib/gridfs-rackdav/gridfs_resource.rb",
|
30
|
+
"spec/gridfs-rackdav_spec.rb",
|
31
|
+
"spec/spec.opts",
|
32
|
+
"spec/spec_helper.rb"
|
33
|
+
]
|
34
|
+
s.homepage = %q{http://github.com/retro/gridfs-rackdav}
|
35
|
+
s.rdoc_options = ["--charset=UTF-8"]
|
36
|
+
s.require_paths = ["lib"]
|
37
|
+
s.rubygems_version = %q{1.3.5}
|
38
|
+
s.summary = %q{GridFS resource adapter for RackDAV}
|
39
|
+
s.test_files = [
|
40
|
+
"spec/gridfs-rackdav_spec.rb",
|
41
|
+
"spec/spec_helper.rb"
|
42
|
+
]
|
43
|
+
|
44
|
+
if s.respond_to? :specification_version then
|
45
|
+
current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
|
46
|
+
s.specification_version = 3
|
47
|
+
|
48
|
+
if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then
|
49
|
+
s.add_development_dependency(%q<rspec>, [">= 1.2.9"])
|
50
|
+
s.add_runtime_dependency(%q<georgi-rack_dav>, [">= 0.1.1"])
|
51
|
+
s.add_runtime_dependency(%q<mongo>, [">= 0.1"])
|
52
|
+
else
|
53
|
+
s.add_dependency(%q<rspec>, [">= 1.2.9"])
|
54
|
+
s.add_dependency(%q<georgi-rack_dav>, [">= 0.1.1"])
|
55
|
+
s.add_dependency(%q<mongo>, [">= 0.1"])
|
56
|
+
end
|
57
|
+
else
|
58
|
+
s.add_dependency(%q<rspec>, [">= 1.2.9"])
|
59
|
+
s.add_dependency(%q<georgi-rack_dav>, [">= 0.1.1"])
|
60
|
+
s.add_dependency(%q<mongo>, [">= 0.1"])
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
@@ -0,0 +1,94 @@
|
|
1
|
+
module GridFSRackDAV
|
2
|
+
class GridFSModel
|
3
|
+
include Mongo
|
4
|
+
include GridFS
|
5
|
+
|
6
|
+
attr_reader :path, :collection, :root, :options
|
7
|
+
|
8
|
+
def initialize(resource_path, options)
|
9
|
+
@connection = options[:connection]
|
10
|
+
@collection = @connection.collection('fs.files')
|
11
|
+
self.root = options[:root]
|
12
|
+
self.path = resource_path
|
13
|
+
@options = options
|
14
|
+
end
|
15
|
+
def path=(path)
|
16
|
+
@path = ('/' + path).gsub(/\/+/, '/').strip
|
17
|
+
@path = self.root + @path if @path[0..root.length-1] != root
|
18
|
+
end
|
19
|
+
def root=(raw_root)
|
20
|
+
raw_root = '' if raw_root.nil?
|
21
|
+
raw_root = "/" + raw_root if raw_root != "/"
|
22
|
+
raw_root = raw_root[0..-2] if raw_root[-1,1] == '/'
|
23
|
+
@root = raw_root
|
24
|
+
end
|
25
|
+
def path_without_root
|
26
|
+
@path[root.length..-1]
|
27
|
+
end
|
28
|
+
def collection?
|
29
|
+
if item.nil?
|
30
|
+
path[-1,1] == '/'
|
31
|
+
else
|
32
|
+
item['filename'][-1,1] == '/'
|
33
|
+
end
|
34
|
+
end
|
35
|
+
def name
|
36
|
+
item.nil? ? path : item['filename']
|
37
|
+
end
|
38
|
+
def item
|
39
|
+
@item ||= @collection.find_one({:filename => /^(#{Regexp.escape(self.path)})\/?$/})
|
40
|
+
if @item.nil? and path == (self.root + '/').gsub(/\/+/, '/')
|
41
|
+
@item = self.write
|
42
|
+
end
|
43
|
+
@item
|
44
|
+
end
|
45
|
+
|
46
|
+
def children_names
|
47
|
+
children_names = []
|
48
|
+
if self.collection? && !item.nil?
|
49
|
+
@collection.find({:filename => /^(#{Regexp.escape(self.item['filename'])})[^\/]+(\/?)$/}).each do |r|
|
50
|
+
children_names << r['filename'] if r['filename'] != self.path
|
51
|
+
end
|
52
|
+
children_names
|
53
|
+
end
|
54
|
+
children_names
|
55
|
+
end
|
56
|
+
|
57
|
+
def children
|
58
|
+
if self.collection? && !item.nil?
|
59
|
+
@collection.find({:filename => /^(#{Regexp.escape(self.item['filename'])})[^\/]+(\/?)$/}).map do |c|
|
60
|
+
self.class.new(c['filename'], options)
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
def get_file_contents
|
66
|
+
GridStore.open(@connection, self.path, 'r') { |f| f.read }
|
67
|
+
end
|
68
|
+
def write(file_contents = '')
|
69
|
+
filename = ('/' + self.path).gsub(/\/+/, '/')
|
70
|
+
GridStore.open(@connection, filename, 'w') do |f|
|
71
|
+
f.content_type = MIME::Types.type_for(filename).first.to_s
|
72
|
+
f.content_type = 'text/html' if f.content_type.empty?
|
73
|
+
f.metadata = {
|
74
|
+
:ctime => Time.now.to_i,
|
75
|
+
:mtime => Time.now.to_i
|
76
|
+
}
|
77
|
+
f.write(file_contents)
|
78
|
+
end
|
79
|
+
@item = nil
|
80
|
+
item
|
81
|
+
end
|
82
|
+
def save
|
83
|
+
@collection.save(self.item)
|
84
|
+
end
|
85
|
+
def delete
|
86
|
+
if collection?
|
87
|
+
@collection.remove({:filename => /^(#{Regexp.escape(item['filename'])}).*/})
|
88
|
+
else
|
89
|
+
@collection.remove({:filename => /^(#{Regexp.escape(item['filename'])})\/?$/})
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
end
|
94
|
+
end
|
@@ -0,0 +1,183 @@
|
|
1
|
+
module GridFSRackDAV
|
2
|
+
class GridFSResource < RackDAV::Resource
|
3
|
+
attr_reader :gridfs_model
|
4
|
+
|
5
|
+
DIR_FILE = "<tr><td class='name'><a href='%s'>%s</a></td><td class='size'>%s</td><td class='type'>%s</td><td class='mtime'>%s</td></tr>"
|
6
|
+
|
7
|
+
DIR_PAGE = <<-PAGE
|
8
|
+
<html><head>
|
9
|
+
<title>%s</title>
|
10
|
+
<meta http-equiv="content-type" content="text/html; charset=utf-8" />
|
11
|
+
<style type='text/css'>
|
12
|
+
table { width:100%%; }
|
13
|
+
.name { text-align:left; }
|
14
|
+
.size, .mtime { text-align:right; }
|
15
|
+
.type { width:11em; }
|
16
|
+
.mtime { width:15em; }
|
17
|
+
</style>
|
18
|
+
</head><body>
|
19
|
+
<h1>%s</h1>
|
20
|
+
<hr />
|
21
|
+
<table>
|
22
|
+
<tr>
|
23
|
+
<th class='name'>Name</th>
|
24
|
+
<th class='size'>Size</th>
|
25
|
+
<th class='type'>Type</th>
|
26
|
+
<th class='mtime'>Last Modified</th>
|
27
|
+
</tr>
|
28
|
+
%s
|
29
|
+
</table>
|
30
|
+
<hr />
|
31
|
+
</body></html>
|
32
|
+
PAGE
|
33
|
+
|
34
|
+
|
35
|
+
def initialize(path, options)
|
36
|
+
raise 'You must provide MongoDB connection in options[:connection]' if options[:connection].nil?
|
37
|
+
raise 'You must provide root of collection in options[:root]' if options[:root].nil?
|
38
|
+
super(path, options)
|
39
|
+
@gridfs_model = GridFSModel.new(path, options)
|
40
|
+
end
|
41
|
+
|
42
|
+
# If this is a collection, return the child resources.
|
43
|
+
def children
|
44
|
+
@gridfs_model.children_names.map { |filename| self.class.new(filename, @options)}
|
45
|
+
end
|
46
|
+
|
47
|
+
# Is this resource a collection?
|
48
|
+
def collection?
|
49
|
+
@gridfs_model.collection?
|
50
|
+
end
|
51
|
+
|
52
|
+
# Does this recource exist?
|
53
|
+
def exist?
|
54
|
+
!@gridfs_model.item.nil?
|
55
|
+
end
|
56
|
+
|
57
|
+
# Return the creation time.
|
58
|
+
def creation_date
|
59
|
+
@gridfs_model.item['metadata'] ? Time.at(@gridfs_model.item['metadata']['mtime'].to_i) : Time.now
|
60
|
+
end
|
61
|
+
|
62
|
+
# Return the time of last modification.
|
63
|
+
def last_modified
|
64
|
+
@gridfs_model.item['metadata'] ? Time.at(@gridfs_model.item['metadata']['mtime'].to_i) : Time.now
|
65
|
+
end
|
66
|
+
|
67
|
+
# Set the time of last modification.
|
68
|
+
def last_modified=(time)
|
69
|
+
@gridfs_model.item['metadata']['mtime'] = Time.parse(time).to_i
|
70
|
+
@gridfs_model.save
|
71
|
+
end
|
72
|
+
|
73
|
+
# Return an Etag, an unique hash value for this resource.
|
74
|
+
def etag
|
75
|
+
Digest::MD5.hexdigest(
|
76
|
+
sprintf('%s-%x-%x', @gridfs_model.item['filename'], @gridfs_model.item['length'], @gridfs_model.item['metadata']['mtime'].to_i)
|
77
|
+
)
|
78
|
+
end
|
79
|
+
|
80
|
+
# Return the resource type.
|
81
|
+
#
|
82
|
+
# If this is a collection, return
|
83
|
+
# REXML::Element.new('D:collection')
|
84
|
+
def resource_type
|
85
|
+
if @gridfs_model.collection?
|
86
|
+
REXML::Element.new('D:collection')
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
# Return the mime type of this resource.
|
91
|
+
def content_type
|
92
|
+
@gridfs_model.item['contentType']
|
93
|
+
end
|
94
|
+
|
95
|
+
# Return the size in bytes for this resource.
|
96
|
+
def content_length
|
97
|
+
@gridfs_model.item['length']
|
98
|
+
end
|
99
|
+
|
100
|
+
# HTTP GET request.
|
101
|
+
#
|
102
|
+
# Write the content of the resource to the response.body.
|
103
|
+
# TODO: Write test for get method for collections
|
104
|
+
def get(request, response)
|
105
|
+
if @gridfs_model.collection?
|
106
|
+
files = []
|
107
|
+
if @gridfs_model.path != @options[:root] + '/'
|
108
|
+
files << DIR_FILE % [@gridfs_model.path_without_root.split('/')[0..-2].join('/') + "/", '../', "", "", ""]
|
109
|
+
end
|
110
|
+
@gridfs_model.children.each do |f|
|
111
|
+
time = Time.at(f.item['metadata']['mtime']).to_s if f.item['metadata'] and f.item['metadata']['mtime']
|
112
|
+
files << DIR_FILE % [f.path_without_root, File.basename(f.item['filename']), f.item['length'], f.item['contentType'], time]
|
113
|
+
end
|
114
|
+
response.body = DIR_PAGE % [@gridfs_model.path_without_root, @gridfs_model.path_without_root, files.join('')]
|
115
|
+
response.body.strip!
|
116
|
+
|
117
|
+
response['Content-Type'] = 'text/html'
|
118
|
+
response['Content-Length'] = response.body.size.to_s
|
119
|
+
else
|
120
|
+
response.body = @gridfs_model.get_file_contents
|
121
|
+
end
|
122
|
+
end
|
123
|
+
|
124
|
+
# HTTP PUT request.
|
125
|
+
#
|
126
|
+
# Save the content of the request.body.
|
127
|
+
def put(request, response)
|
128
|
+
write(request.body)
|
129
|
+
end
|
130
|
+
|
131
|
+
# HTTP POST request.
|
132
|
+
#
|
133
|
+
# Usually forbidden.
|
134
|
+
def post(request, response)
|
135
|
+
raise HTTPStatus::Forbidden
|
136
|
+
end
|
137
|
+
|
138
|
+
# HTTP DELETE request.
|
139
|
+
#
|
140
|
+
# Delete this resource.
|
141
|
+
def delete
|
142
|
+
@gridfs_model.delete
|
143
|
+
end
|
144
|
+
|
145
|
+
# HTTP COPY request.
|
146
|
+
#
|
147
|
+
# Copy this resource to given destination resource.
|
148
|
+
def copy(dest)
|
149
|
+
if collection?
|
150
|
+
dest.gridfs_model.path = dest.gridfs_model.path + '/'
|
151
|
+
dest.make_collection
|
152
|
+
else
|
153
|
+
dest.write(StringIO.new(@gridfs_model.get_file_contents)) unless @gridfs_model.item.nil?
|
154
|
+
end
|
155
|
+
end
|
156
|
+
|
157
|
+
# HTTP MOVE request.
|
158
|
+
#
|
159
|
+
# Move this resource to given destination resource.
|
160
|
+
def move(dest)
|
161
|
+
copy(dest)
|
162
|
+
delete
|
163
|
+
end
|
164
|
+
|
165
|
+
# HTTP MKCOL request.
|
166
|
+
#
|
167
|
+
# Create this resource as collection.
|
168
|
+
def make_collection
|
169
|
+
@gridfs_model.path = @gridfs_model.path + '/'
|
170
|
+
@gridfs_model.write
|
171
|
+
end
|
172
|
+
|
173
|
+
# Write to this resource from given IO.
|
174
|
+
def write(io)
|
175
|
+
file = ''
|
176
|
+
while part = io.read(8192)
|
177
|
+
file << part
|
178
|
+
end
|
179
|
+
@gridfs_model.write(file)
|
180
|
+
end
|
181
|
+
|
182
|
+
end
|
183
|
+
end
|
@@ -0,0 +1,275 @@
|
|
1
|
+
require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
|
2
|
+
require 'rubygems'
|
3
|
+
require 'rack_dav'
|
4
|
+
$:.unshift(File.expand_path(File.dirname(__FILE__) + '/../lib'))
|
5
|
+
|
6
|
+
describe RackDAV::Handler do
|
7
|
+
DOC_ROOT = 'rack_dav_test_collection'
|
8
|
+
METHODS = %w(GET PUT POST DELETE PROPFIND PROPPATCH MKCOL COPY MOVE OPTIONS HEAD LOCK UNLOCK)
|
9
|
+
|
10
|
+
before do
|
11
|
+
@connection = Mongo::Connection.new('localhost').db('gridfs-rackdav')
|
12
|
+
@controller = RackDAV::Handler.new(:root => DOC_ROOT,
|
13
|
+
:connection => @connection,
|
14
|
+
:resource_class => GridFSRackDAV::GridFSResource
|
15
|
+
)
|
16
|
+
end
|
17
|
+
|
18
|
+
after do
|
19
|
+
@connection.collection('fs.files').drop
|
20
|
+
@connection.collection('fs.chunks').drop
|
21
|
+
end
|
22
|
+
|
23
|
+
attr_reader :response
|
24
|
+
|
25
|
+
def request(method, uri, options={})
|
26
|
+
options = {
|
27
|
+
'HTTP_HOST' => 'localhost',
|
28
|
+
'REMOTE_USER' => 'manni'
|
29
|
+
}.merge(options)
|
30
|
+
request = Rack::MockRequest.new(@controller)
|
31
|
+
@response = request.request(method, uri, options)
|
32
|
+
end
|
33
|
+
|
34
|
+
METHODS.each do |method|
|
35
|
+
define_method(method.downcase) do |*args|
|
36
|
+
request(method, *args)
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
def render
|
41
|
+
xml = Builder::XmlMarkup.new
|
42
|
+
xml.instruct! :xml, :version => "1.0", :encoding => "UTF-8"
|
43
|
+
xml.namespace('d') do
|
44
|
+
yield xml
|
45
|
+
end
|
46
|
+
xml.target!
|
47
|
+
end
|
48
|
+
|
49
|
+
def url_escape(string)
|
50
|
+
string.gsub(/([^ a-zA-Z0-9_.-]+)/n) do
|
51
|
+
'%' + $1.unpack('H2' * $1.size).join('%').upcase
|
52
|
+
end.tr(' ', '+')
|
53
|
+
end
|
54
|
+
|
55
|
+
def response_xml
|
56
|
+
REXML::Document.new(@response.body)
|
57
|
+
end
|
58
|
+
|
59
|
+
def multistatus_response(pattern)
|
60
|
+
@response.should be_multi_status
|
61
|
+
REXML::XPath::match(response_xml, "/multistatus/response", '' => 'DAV:').should_not be_empty
|
62
|
+
REXML::XPath::match(response_xml, "/multistatus/response" + pattern, '' => 'DAV:')
|
63
|
+
end
|
64
|
+
|
65
|
+
def propfind_xml(*props)
|
66
|
+
render do |xml|
|
67
|
+
xml.propfind('xmlns:d' => "DAV:") do
|
68
|
+
xml.prop do
|
69
|
+
props.each do |prop|
|
70
|
+
xml.tag! prop
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
it 'should return all options' do
|
78
|
+
options('/').should be_ok
|
79
|
+
|
80
|
+
METHODS.each do |method|
|
81
|
+
response.headers['allow'].should include(method)
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
it 'should return headers' do
|
86
|
+
put('/test.html', :input => '<html/>').should be_ok
|
87
|
+
head('/test.html').should be_ok
|
88
|
+
response.headers['etag'].should_not be_nil
|
89
|
+
response.headers['content-type'].should match(/html/)
|
90
|
+
response.headers['last-modified'].should_not be_nil
|
91
|
+
end
|
92
|
+
|
93
|
+
it 'should not find a nonexistent resource' do
|
94
|
+
get('/not_found').should be_not_found
|
95
|
+
end
|
96
|
+
|
97
|
+
it 'should not allow directory traversal' do
|
98
|
+
get('/../htdocs').should be_forbidden
|
99
|
+
end
|
100
|
+
|
101
|
+
it 'should create a resource and allow its retrieval' do
|
102
|
+
put('/test', :input => 'body').should be_ok
|
103
|
+
get('/test').should be_ok
|
104
|
+
response.body.should == 'body'
|
105
|
+
end
|
106
|
+
|
107
|
+
it 'should create and find a url with escaped characters' do
|
108
|
+
put(url_escape('/a b'), :input => 'body').should be_ok
|
109
|
+
get(url_escape('/a b')).should be_ok
|
110
|
+
response.body.should == 'body'
|
111
|
+
end
|
112
|
+
|
113
|
+
it 'should delete a single resource' do
|
114
|
+
put('/test', :input => 'body').should be_ok
|
115
|
+
delete('/test').should be_no_content
|
116
|
+
end
|
117
|
+
|
118
|
+
it 'should delete recursively' do
|
119
|
+
mkcol('/folder').should be_created
|
120
|
+
put('/folder/a', :input => 'body').should be_ok
|
121
|
+
put('/folder/b', :input => 'body').should be_ok
|
122
|
+
|
123
|
+
delete('/folder').should be_no_content
|
124
|
+
get('/folder').should be_not_found
|
125
|
+
get('/folder/a').should be_not_found
|
126
|
+
get('/folder/b').should be_not_found
|
127
|
+
end
|
128
|
+
|
129
|
+
|
130
|
+
it 'should not allow copy to another domain' do
|
131
|
+
put('/test', :input => 'body').should be_ok
|
132
|
+
copy('http://localhost/', 'HTTP_DESTINATION' => 'http://another/').should be_bad_gateway
|
133
|
+
end
|
134
|
+
|
135
|
+
it 'should not allow copy to the same resource' do
|
136
|
+
put('/test', :input => 'body').should be_ok
|
137
|
+
copy('/test', 'HTTP_DESTINATION' => '/test').should be_forbidden
|
138
|
+
end
|
139
|
+
|
140
|
+
it 'should not allow an invalid destination uri' do
|
141
|
+
put('/test', :input => 'body').should be_ok
|
142
|
+
copy('/test', 'HTTP_DESTINATION' => '%').should be_bad_request
|
143
|
+
end
|
144
|
+
|
145
|
+
it 'should copy a single resource' do
|
146
|
+
put('/test', :input => 'body').should be_ok
|
147
|
+
copy('/test', 'HTTP_DESTINATION' => '/copy').should be_created
|
148
|
+
get('/copy').body.should == 'body'
|
149
|
+
end
|
150
|
+
|
151
|
+
it 'should copy a resource with escaped characters' do
|
152
|
+
put(url_escape('/a b'), :input => 'body').should be_ok
|
153
|
+
copy(url_escape('/a b'), 'HTTP_DESTINATION' => url_escape('/a c')).should be_created
|
154
|
+
get(url_escape('/a c')).should be_ok
|
155
|
+
response.body.should == 'body'
|
156
|
+
end
|
157
|
+
|
158
|
+
it 'should deny a copy without overwrite' do
|
159
|
+
put('/test', :input => 'body').should be_ok
|
160
|
+
put('/copy', :input => 'copy').should be_ok
|
161
|
+
copy('/test', 'HTTP_DESTINATION' => '/copy', 'HTTP_OVERWRITE' => 'F')
|
162
|
+
|
163
|
+
multistatus_response('/href').first.text.should == 'http://localhost/test'
|
164
|
+
multistatus_response('/status').first.text.should match(/412 Precondition Failed/)
|
165
|
+
|
166
|
+
get('/copy').body.should == 'copy'
|
167
|
+
end
|
168
|
+
|
169
|
+
it 'should allow a copy with overwrite' do
|
170
|
+
put('/test', :input => 'body').should be_ok
|
171
|
+
put('/copy', :input => 'copy').should be_ok
|
172
|
+
copy('/test', 'HTTP_DESTINATION' => '/copy', 'HTTP_OVERWRITE' => 'T').should be_no_content
|
173
|
+
get('/copy').body.should == 'body'
|
174
|
+
end
|
175
|
+
|
176
|
+
it 'should copy a collection' do
|
177
|
+
mkcol('/folder').should be_created
|
178
|
+
copy('/folder', 'HTTP_DESTINATION' => '/copy').should be_created
|
179
|
+
propfind('/copy', :input => propfind_xml(:resourcetype))
|
180
|
+
multistatus_response('/propstat/prop/resourcetype/collection').should_not be_empty
|
181
|
+
end
|
182
|
+
|
183
|
+
it 'should copy a collection resursively' do
|
184
|
+
mkcol('/folder').should be_created
|
185
|
+
put('/folder/a', :input => 'A').should be_ok
|
186
|
+
put('/folder/b', :input => 'B').should be_ok
|
187
|
+
|
188
|
+
copy('/folder', 'HTTP_DESTINATION' => '/copy').should be_created
|
189
|
+
propfind('/copy', :input => propfind_xml(:resourcetype))
|
190
|
+
multistatus_response('/propstat/prop/resourcetype/collection').should_not be_empty
|
191
|
+
|
192
|
+
get('/copy/a').body.should == 'A'
|
193
|
+
get('/copy/b').body.should == 'B'
|
194
|
+
end
|
195
|
+
|
196
|
+
it 'should move a collection recursively' do
|
197
|
+
mkcol('/folder').should be_created
|
198
|
+
put('/folder/a', :input => 'A').should be_ok
|
199
|
+
put('/folder/b', :input => 'B').should be_ok
|
200
|
+
|
201
|
+
move('/folder', 'HTTP_DESTINATION' => '/move').should be_created
|
202
|
+
propfind('/move', :input => propfind_xml(:resourcetype))
|
203
|
+
multistatus_response('/propstat/prop/resourcetype/collection').should_not be_empty
|
204
|
+
|
205
|
+
get('/move/a').body.should == 'A'
|
206
|
+
get('/move/b').body.should == 'B'
|
207
|
+
get('/folder/a').should be_not_found
|
208
|
+
get('/folder/b').should be_not_found
|
209
|
+
end
|
210
|
+
|
211
|
+
it 'should create a collection' do
|
212
|
+
mkcol('/folder').should be_created
|
213
|
+
propfind('/folder', :input => propfind_xml(:resourcetype))
|
214
|
+
multistatus_response('/propstat/prop/resourcetype/collection').should_not be_empty
|
215
|
+
end
|
216
|
+
|
217
|
+
it 'should not find properties for nonexistent resources' do
|
218
|
+
propfind('/non').should be_not_found
|
219
|
+
end
|
220
|
+
|
221
|
+
it 'should find all properties' do
|
222
|
+
xml = render do |xml|
|
223
|
+
xml.propfind('xmlns:d' => "DAV:") do
|
224
|
+
xml.allprop
|
225
|
+
end
|
226
|
+
end
|
227
|
+
|
228
|
+
propfind('http://localhost/', :input => xml)
|
229
|
+
|
230
|
+
multistatus_response('/href').first.text.strip.should == 'http://localhost/'
|
231
|
+
|
232
|
+
props = %w(creationdate displayname getlastmodified getetag resourcetype getcontenttype getcontentlength)
|
233
|
+
props.each do |prop|
|
234
|
+
multistatus_response('/propstat/prop/' + prop).should_not be_empty
|
235
|
+
end
|
236
|
+
end
|
237
|
+
|
238
|
+
it 'should find named properties' do
|
239
|
+
put('/test.html', :input => '<html/>').should be_ok
|
240
|
+
propfind('/test.html', :input => propfind_xml(:getcontenttype, :getcontentlength))
|
241
|
+
|
242
|
+
multistatus_response('/propstat/prop/getcontenttype').first.text.should == 'text/html'
|
243
|
+
multistatus_response('/propstat/prop/getcontentlength').first.text.should == '7'
|
244
|
+
end
|
245
|
+
|
246
|
+
it 'should lock a resource' do
|
247
|
+
put('/test', :input => 'body').should be_ok
|
248
|
+
|
249
|
+
xml = render do |xml|
|
250
|
+
xml.lockinfo('xmlns:d' => "DAV:") do
|
251
|
+
xml.lockscope { xml.exclusive }
|
252
|
+
xml.locktype { xml.write }
|
253
|
+
xml.owner { xml.href "http://test.de/" }
|
254
|
+
end
|
255
|
+
end
|
256
|
+
|
257
|
+
lock('/test', :input => xml)
|
258
|
+
|
259
|
+
response.should be_ok
|
260
|
+
|
261
|
+
match = lambda do |pattern|
|
262
|
+
REXML::XPath::match(response_xml, "/prop/lockdiscovery/activelock" + pattern, '' => 'DAV:')
|
263
|
+
end
|
264
|
+
|
265
|
+
match[''].should_not be_empty
|
266
|
+
|
267
|
+
match['/locktype'].should_not be_empty
|
268
|
+
match['/lockscope'].should_not be_empty
|
269
|
+
match['/depth'].should_not be_empty
|
270
|
+
match['/owner'].should_not be_empty
|
271
|
+
match['/timeout'].should_not be_empty
|
272
|
+
match['/locktoken'].should_not be_empty
|
273
|
+
end
|
274
|
+
|
275
|
+
end
|
data/spec/spec.opts
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
--color
|
data/spec/spec_helper.rb
ADDED
metadata
ADDED
@@ -0,0 +1,98 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: gridfs-rackdav
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- "Mihael Konjevi\xC4\x87"
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
|
12
|
+
date: 2010-02-05 00:00:00 +01:00
|
13
|
+
default_executable:
|
14
|
+
dependencies:
|
15
|
+
- !ruby/object:Gem::Dependency
|
16
|
+
name: rspec
|
17
|
+
type: :development
|
18
|
+
version_requirement:
|
19
|
+
version_requirements: !ruby/object:Gem::Requirement
|
20
|
+
requirements:
|
21
|
+
- - ">="
|
22
|
+
- !ruby/object:Gem::Version
|
23
|
+
version: 1.2.9
|
24
|
+
version:
|
25
|
+
- !ruby/object:Gem::Dependency
|
26
|
+
name: georgi-rack_dav
|
27
|
+
type: :runtime
|
28
|
+
version_requirement:
|
29
|
+
version_requirements: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ">="
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: 0.1.1
|
34
|
+
version:
|
35
|
+
- !ruby/object:Gem::Dependency
|
36
|
+
name: mongo
|
37
|
+
type: :runtime
|
38
|
+
version_requirement:
|
39
|
+
version_requirements: !ruby/object:Gem::Requirement
|
40
|
+
requirements:
|
41
|
+
- - ">="
|
42
|
+
- !ruby/object:Gem::Version
|
43
|
+
version: "0.1"
|
44
|
+
version:
|
45
|
+
description: gridfs-rackdav enables you to use GridFS as backend for WebDAV collections with RackDAV application
|
46
|
+
email: konjevic@gmail.com
|
47
|
+
executables: []
|
48
|
+
|
49
|
+
extensions: []
|
50
|
+
|
51
|
+
extra_rdoc_files:
|
52
|
+
- LICENSE
|
53
|
+
- README.md
|
54
|
+
files:
|
55
|
+
- .document
|
56
|
+
- .gitignore
|
57
|
+
- LICENSE
|
58
|
+
- README.md
|
59
|
+
- Rakefile
|
60
|
+
- VERSION
|
61
|
+
- gridfs-rackdav.gemspec
|
62
|
+
- lib/gridfs-rackdav.rb
|
63
|
+
- lib/gridfs-rackdav/gridfs_model.rb
|
64
|
+
- lib/gridfs-rackdav/gridfs_resource.rb
|
65
|
+
- spec/gridfs-rackdav_spec.rb
|
66
|
+
- spec/spec.opts
|
67
|
+
- spec/spec_helper.rb
|
68
|
+
has_rdoc: true
|
69
|
+
homepage: http://github.com/retro/gridfs-rackdav
|
70
|
+
licenses: []
|
71
|
+
|
72
|
+
post_install_message:
|
73
|
+
rdoc_options:
|
74
|
+
- --charset=UTF-8
|
75
|
+
require_paths:
|
76
|
+
- lib
|
77
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
78
|
+
requirements:
|
79
|
+
- - ">="
|
80
|
+
- !ruby/object:Gem::Version
|
81
|
+
version: "0"
|
82
|
+
version:
|
83
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
84
|
+
requirements:
|
85
|
+
- - ">="
|
86
|
+
- !ruby/object:Gem::Version
|
87
|
+
version: "0"
|
88
|
+
version:
|
89
|
+
requirements: []
|
90
|
+
|
91
|
+
rubyforge_project:
|
92
|
+
rubygems_version: 1.3.5
|
93
|
+
signing_key:
|
94
|
+
specification_version: 3
|
95
|
+
summary: GridFS resource adapter for RackDAV
|
96
|
+
test_files:
|
97
|
+
- spec/gridfs-rackdav_spec.rb
|
98
|
+
- spec/spec_helper.rb
|