calligraphy 0.2.1 → 0.3.1
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 +5 -5
- data/README.md +1 -1
- data/lib/calligraphy.rb +23 -17
- data/lib/calligraphy/rails/mapper.rb +150 -142
- data/lib/calligraphy/rails/web_dav_methods.rb +67 -0
- data/lib/calligraphy/rails/web_dav_preconditions.rb +114 -0
- data/lib/calligraphy/rails/web_dav_requests_controller.rb +72 -180
- data/lib/calligraphy/resource/file_resource.rb +377 -194
- data/lib/calligraphy/resource/resource.rb +192 -32
- data/lib/calligraphy/utils.rb +23 -6
- data/lib/calligraphy/version.rb +3 -1
- data/lib/calligraphy/{copy.rb → web_dav_request/copy.rb} +13 -8
- data/lib/calligraphy/{delete.rb → web_dav_request/delete.rb} +6 -2
- data/lib/calligraphy/web_dav_request/get.rb +18 -0
- data/lib/calligraphy/web_dav_request/lock.rb +89 -0
- data/lib/calligraphy/{mkcol.rb → web_dav_request/mkcol.rb} +7 -2
- data/lib/calligraphy/web_dav_request/move.rb +56 -0
- data/lib/calligraphy/web_dav_request/propfind.rb +24 -0
- data/lib/calligraphy/web_dav_request/proppatch.rb +29 -0
- data/lib/calligraphy/web_dav_request/put.rb +16 -0
- data/lib/calligraphy/{unlock.rb → web_dav_request/unlock.rb} +6 -1
- data/lib/calligraphy/web_dav_request/web_dav_request.rb +43 -0
- data/lib/calligraphy/xml/builder.rb +83 -117
- data/lib/calligraphy/xml/namespace.rb +12 -6
- data/lib/calligraphy/xml/node.rb +24 -10
- data/lib/calligraphy/xml/utils.rb +22 -11
- data/lib/calligraphy/xml/web_dav_elements.rb +92 -0
- data/lib/generators/calligraphy/install_generator.rb +4 -0
- data/lib/generators/templates/calligraphy.rb +2 -0
- metadata +109 -22
- data/lib/calligraphy/get.rb +0 -12
- data/lib/calligraphy/lock.rb +0 -52
- data/lib/calligraphy/move.rb +0 -31
- data/lib/calligraphy/propfind.rb +0 -18
- data/lib/calligraphy/proppatch.rb +0 -20
- data/lib/calligraphy/put.rb +0 -12
- data/lib/calligraphy/web_dav_request.rb +0 -31
- data/spec/spec_helper.rb +0 -46
@@ -0,0 +1,114 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Calligraphy
|
4
|
+
module Rails
|
5
|
+
# Provides methods to handle checking and validating WebDAV request
|
6
|
+
# preconditions.
|
7
|
+
module WebDavPreconditions
|
8
|
+
private
|
9
|
+
|
10
|
+
def check_preconditions
|
11
|
+
return true unless request.headers['If'].present?
|
12
|
+
|
13
|
+
evaluate_if_header
|
14
|
+
end
|
15
|
+
|
16
|
+
def evaluate_if_header
|
17
|
+
conditions_met = false
|
18
|
+
condition_lists = if_conditions
|
19
|
+
|
20
|
+
condition_lists.each do |list|
|
21
|
+
conditions = parse_preconditions list
|
22
|
+
|
23
|
+
conditions_met = evaluate_preconditions conditions
|
24
|
+
break if conditions_met
|
25
|
+
end
|
26
|
+
|
27
|
+
conditions_met
|
28
|
+
end
|
29
|
+
|
30
|
+
def if_conditions
|
31
|
+
if request.headers['If'][0] == '<'
|
32
|
+
request.headers['If'].split Calligraphy::TAGGED_LIST_REGEX
|
33
|
+
else
|
34
|
+
request.headers['If'].split Calligraphy::UNTAGGAGED_LIST_REGEX
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
def parse_preconditions(list)
|
39
|
+
conditions = conditions_hash
|
40
|
+
conditions[:dav_no_lock] = match_dav_no_lock list
|
41
|
+
conditions[:resource] = scan_for_resource list
|
42
|
+
conditions[:lock_token] = scan_for_lock_token list
|
43
|
+
conditions[:etag] = scan_for_etag list
|
44
|
+
conditions
|
45
|
+
end
|
46
|
+
|
47
|
+
def conditions_hash
|
48
|
+
{
|
49
|
+
dav_no_lock: nil,
|
50
|
+
etag: nil,
|
51
|
+
lock_token: nil,
|
52
|
+
resource: nil
|
53
|
+
}
|
54
|
+
end
|
55
|
+
|
56
|
+
def match_dav_no_lock(list)
|
57
|
+
return nil unless list =~ Calligraphy::DAV_NO_LOCK_REGEX
|
58
|
+
|
59
|
+
list =~ Calligraphy::DAV_NOT_NO_LOCK_REGEX ? nil : true
|
60
|
+
end
|
61
|
+
|
62
|
+
def scan_for_resource(list)
|
63
|
+
return nil unless list =~ Calligraphy::RESOURCE_REGEX
|
64
|
+
|
65
|
+
list.scan(Calligraphy::RESOURCE_REGEX).flatten[0]
|
66
|
+
end
|
67
|
+
|
68
|
+
def scan_for_lock_token(list)
|
69
|
+
return nil unless list =~ Calligraphy::LOCK_TOKEN_REGEX
|
70
|
+
|
71
|
+
list.scan(Calligraphy::LOCK_TOKEN_REGEX).flatten[0]
|
72
|
+
end
|
73
|
+
|
74
|
+
def scan_for_etag(list)
|
75
|
+
return nil unless list =~ Calligraphy::ETAG_IF_REGEX
|
76
|
+
|
77
|
+
list.scan(Calligraphy::ETAG_IF_REGEX).flatten[0]
|
78
|
+
end
|
79
|
+
|
80
|
+
def evaluate_preconditions(conditions)
|
81
|
+
conditions_met = true
|
82
|
+
|
83
|
+
if conditions[:etag]
|
84
|
+
conditions_met = false unless evaluate_etag_condition conditions
|
85
|
+
end
|
86
|
+
|
87
|
+
conditions_met = false if conditions[:dav_no_lock]
|
88
|
+
conditions_met
|
89
|
+
end
|
90
|
+
|
91
|
+
def target_resource(conditions)
|
92
|
+
if conditions[:resource]
|
93
|
+
@resource_class.new(
|
94
|
+
resource: conditions[:resource],
|
95
|
+
mount: @resource.mount_point
|
96
|
+
)
|
97
|
+
else
|
98
|
+
@resource
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
def evaluate_etag_condition(conditions)
|
103
|
+
validators = [@resource.etag, '']
|
104
|
+
validate_etag validators, conditions[:etag]
|
105
|
+
end
|
106
|
+
|
107
|
+
def validate_etag(etag_validators, validate_against)
|
108
|
+
cache_key = ActiveSupport::Cache.expand_cache_key etag_validators
|
109
|
+
|
110
|
+
validate_against == "W/\"#{Digest::MD5.hexdigest(cache_key)}\""
|
111
|
+
end
|
112
|
+
end
|
113
|
+
end
|
114
|
+
end
|
@@ -1,213 +1,105 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
status
|
20
|
-
else
|
21
|
-
status = :method_not_allowed
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Calligraphy
|
4
|
+
module Rails
|
5
|
+
# Controller for all WebDAV requests.
|
6
|
+
class WebDavRequestsController < ActionController::Base
|
7
|
+
include Calligraphy::Rails::WebDavMethods
|
8
|
+
include Calligraphy::Rails::WebDavPreconditions
|
9
|
+
|
10
|
+
before_action :verify_resource_scope
|
11
|
+
before_action :authenticate_with_digest_authentiation
|
12
|
+
before_action :set_resource
|
13
|
+
|
14
|
+
# Entry-point for all WebDAV requests. Handles checking and validating
|
15
|
+
# preconditions, directing of requests to the proper WebDAV action
|
16
|
+
# method, and composing responses to send back to the client.
|
17
|
+
def invoke_method
|
18
|
+
unless check_preconditions
|
19
|
+
return send_response(status: :precondition_failed)
|
22
20
|
end
|
23
21
|
|
22
|
+
method = request.request_method.downcase
|
23
|
+
status, body = make_request method
|
24
|
+
|
24
25
|
send_response status: status, body: body
|
25
|
-
else
|
26
|
-
send_response status: :precondition_failed
|
27
26
|
end
|
28
|
-
end
|
29
27
|
|
30
|
-
|
28
|
+
private
|
31
29
|
|
32
|
-
|
33
|
-
|
34
|
-
|
30
|
+
def verify_resource_scope
|
31
|
+
# Prevent any request with `.` or `..` as part of the resource.
|
32
|
+
head :forbidden if %w[. ..].any? do |seg|
|
33
|
+
params[:resource].include? seg
|
34
|
+
end
|
35
|
+
end
|
35
36
|
|
36
|
-
|
37
|
-
|
37
|
+
def authenticate_with_digest_authentiation
|
38
|
+
return unless digest_enabled?
|
38
39
|
|
39
|
-
|
40
|
+
realm = Calligraphy.http_authentication_realm
|
40
41
|
|
41
|
-
|
42
|
-
|
42
|
+
authenticate_or_request_with_http_digest(realm) do |username|
|
43
|
+
Calligraphy.digest_password_procedure.call(username)
|
44
|
+
end
|
43
45
|
end
|
44
|
-
end
|
45
46
|
|
46
|
-
|
47
|
-
|
48
|
-
[params[:resource], params[:format]].join '.'
|
49
|
-
else
|
50
|
-
params[:resource]
|
47
|
+
def digest_enabled?
|
48
|
+
Calligraphy.enable_digest_authentication
|
51
49
|
end
|
52
50
|
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
end
|
57
|
-
|
58
|
-
def check_preconditions
|
59
|
-
return true unless request.headers['If'].present?
|
60
|
-
|
61
|
-
evaluate_if_header
|
62
|
-
end
|
63
|
-
|
64
|
-
def evaluate_if_header
|
65
|
-
conditions_met = false
|
66
|
-
condition_lists = get_if_conditions
|
67
|
-
|
68
|
-
condition_lists.each do |list|
|
69
|
-
conditions = parse_preconditions list
|
51
|
+
def set_resource
|
52
|
+
@resource_class = params[:resource_class] || Calligraphy::Resource
|
53
|
+
@resource_root_path = params[:resource_root_path]
|
70
54
|
|
71
|
-
|
72
|
-
|
55
|
+
@resource = @resource_class.new(
|
56
|
+
resource: resource_id,
|
57
|
+
req: request,
|
58
|
+
root_dir: @resource_root_path
|
59
|
+
)
|
73
60
|
end
|
74
61
|
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
else
|
82
|
-
request.headers['If'].split Calligraphy::UNTAGGAGED_LIST_REGEX
|
62
|
+
def resource_id
|
63
|
+
if params[:format]
|
64
|
+
[params[:resource], params[:format]].join '.'
|
65
|
+
else
|
66
|
+
params[:resource]
|
67
|
+
end
|
83
68
|
end
|
84
69
|
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
conditions[:dav_no_lock] = if list =~ Calligraphy::DAV_NO_LOCK_REGEX
|
92
|
-
list =~ Calligraphy::DAV_NOT_NO_LOCK_REGEX ? nil : true
|
93
|
-
end
|
70
|
+
def make_request(method)
|
71
|
+
if method == 'head'
|
72
|
+
status = get head: true
|
73
|
+
elsif Calligraphy.allowed_http_methods.include? method
|
74
|
+
resource_client_nonce(method) if digest_enabled?
|
94
75
|
|
95
|
-
|
96
|
-
|
97
|
-
|
76
|
+
status, body = send method
|
77
|
+
else
|
78
|
+
status = :method_not_allowed
|
79
|
+
end
|
98
80
|
|
99
|
-
|
100
|
-
conditions[:lock_token] = list.scan(Calligraphy::LOCK_TOKEN_REGEX).flatten[0]
|
81
|
+
[status, body]
|
101
82
|
end
|
102
83
|
|
103
|
-
|
104
|
-
|
84
|
+
def resource_client_nonce(_method)
|
85
|
+
@resource.client_nonce = client_nonce
|
105
86
|
end
|
106
87
|
|
107
|
-
|
108
|
-
|
88
|
+
def client_nonce
|
89
|
+
auth_header = request.headers['HTTP_AUTHORIZATION']
|
90
|
+
digest = ::ActionController::HttpAuthentication::Digest
|
109
91
|
|
110
|
-
|
111
|
-
|
112
|
-
target = if conditions[:resource]
|
113
|
-
@resource_class.new(
|
114
|
-
resource: conditions[:resource],
|
115
|
-
mount: @resource.mount_point
|
116
|
-
)
|
117
|
-
else
|
118
|
-
@resource
|
92
|
+
auth = digest.decode_credentials auth_header
|
93
|
+
auth[:cnonce]
|
119
94
|
end
|
120
95
|
|
121
|
-
|
122
|
-
if
|
123
|
-
|
96
|
+
def send_response(status:, body: nil)
|
97
|
+
if body.nil?
|
98
|
+
head status
|
124
99
|
else
|
125
|
-
|
100
|
+
render body: body, status: status
|
126
101
|
end
|
127
102
|
end
|
128
|
-
|
129
|
-
if conditions[:etag]
|
130
|
-
validators = [@resource.etag, ""]
|
131
|
-
conditions_met = false unless validate_etag validators, conditions[:etag]
|
132
|
-
end
|
133
|
-
|
134
|
-
conditions_met = false if conditions[:dav_no_lock]
|
135
|
-
conditions_met
|
136
|
-
end
|
137
|
-
|
138
|
-
def validate_etag(etag_validators, validate_against)
|
139
|
-
cache_key = ActiveSupport::Cache.expand_cache_key etag_validators
|
140
|
-
"W/\"#{Digest::MD5.hexdigest(cache_key)}\"" == validate_against
|
141
|
-
end
|
142
|
-
|
143
|
-
def web_dav_request
|
144
|
-
{ headers: request.headers, request: request, resource: @resource, response: response }
|
145
|
-
end
|
146
|
-
|
147
|
-
def options
|
148
|
-
response.headers['DAV'] = '1, 2, 3'
|
149
|
-
:ok
|
150
|
-
end
|
151
|
-
|
152
|
-
def get(head: false)
|
153
|
-
fresh_when(@resource, etag: @resource.etag) if @resource.readable?
|
154
|
-
|
155
|
-
Calligraphy::Get.new(web_dav_request).request(head: head)
|
156
|
-
end
|
157
|
-
|
158
|
-
def put
|
159
|
-
Calligraphy::Put.new(web_dav_request).request
|
160
|
-
end
|
161
|
-
|
162
|
-
def delete
|
163
|
-
Calligraphy::Delete.new(web_dav_request).request
|
164
|
-
end
|
165
|
-
|
166
|
-
def copy
|
167
|
-
Calligraphy::Copy.new(web_dav_request).request
|
168
|
-
end
|
169
|
-
|
170
|
-
def move
|
171
|
-
Calligraphy::Move.new(web_dav_request).request
|
172
|
-
end
|
173
|
-
|
174
|
-
def mkcol
|
175
|
-
Calligraphy::Mkcol.new(web_dav_request).request
|
176
|
-
end
|
177
|
-
|
178
|
-
def propfind
|
179
|
-
Calligraphy::Propfind.new(web_dav_request).request
|
180
|
-
end
|
181
|
-
|
182
|
-
def proppatch
|
183
|
-
Calligraphy::Proppatch.new(web_dav_request).request
|
184
|
-
end
|
185
|
-
|
186
|
-
def lock
|
187
|
-
Calligraphy::Lock.new(web_dav_request).request
|
188
|
-
end
|
189
|
-
|
190
|
-
def unlock
|
191
|
-
Calligraphy::Unlock.new(web_dav_request).request
|
192
|
-
end
|
193
|
-
|
194
|
-
def send_response(status:, body: nil)
|
195
|
-
if body.nil?
|
196
|
-
head status
|
197
|
-
else
|
198
|
-
render body: body, status: status
|
199
|
-
end
|
200
|
-
end
|
201
|
-
|
202
|
-
def set_resource_client_nonce(method)
|
203
|
-
@resource.client_nonce = get_client_nonce
|
204
|
-
end
|
205
|
-
|
206
|
-
def get_client_nonce
|
207
|
-
auth_header = request.headers["HTTP_AUTHORIZATION"]
|
208
|
-
|
209
|
-
auth = ::ActionController::HttpAuthentication::Digest.decode_credentials auth_header
|
210
|
-
auth[:cnonce]
|
211
103
|
end
|
212
104
|
end
|
213
105
|
end
|
@@ -1,13 +1,22 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'pstore'
|
2
4
|
|
3
5
|
module Calligraphy
|
6
|
+
# Resource responsible for writing and deleting directories and files to disk.
|
4
7
|
class FileResource < Resource
|
8
|
+
DAV_PROPERTY_METHODS = %w[
|
9
|
+
creationdate displayname getcontentlanguage getcontentlength
|
10
|
+
getcontenttype getetag getlastmodified lockdiscovery resourcetype
|
11
|
+
supportedlock
|
12
|
+
].freeze
|
13
|
+
|
5
14
|
include Calligraphy::Utils
|
6
15
|
|
16
|
+
#:nodoc:
|
7
17
|
def initialize(resource: nil, req: nil, mount: nil, root_dir: Dir.pwd)
|
8
18
|
super
|
9
19
|
|
10
|
-
@root_dir = root_dir || Dir.pwd
|
11
20
|
@src_path = join_paths @root_dir, @request_path
|
12
21
|
|
13
22
|
if exists?
|
@@ -19,90 +28,97 @@ module Calligraphy
|
|
19
28
|
set_ancestors
|
20
29
|
end
|
21
30
|
|
31
|
+
# Responsible for returning a boolean value indicating if an ancestor
|
32
|
+
# exists for the resource.
|
33
|
+
#
|
34
|
+
# Used in COPY and MKCOL requests.
|
22
35
|
def ancestor_exist?
|
23
36
|
File.exist? @ancestor_path
|
24
37
|
end
|
25
38
|
|
26
|
-
|
27
|
-
|
39
|
+
# Responsible for returning a boolean value indicating if the resource
|
40
|
+
# is a collection.
|
41
|
+
#
|
42
|
+
# Used in DELETE, MKCOL, MOVE, and PUT requests.
|
43
|
+
def collection?
|
44
|
+
File.directory? @src_path
|
45
|
+
end
|
28
46
|
|
29
|
-
|
30
|
-
|
31
|
-
|
47
|
+
# Responsible for returning a hash with keys indicating if the resource
|
48
|
+
# can be copied, if an ancestor exists, or if the copy destinatin is
|
49
|
+
# locked.
|
50
|
+
#
|
51
|
+
# Return hash should contain `can_copy`, `ancestor_exist`, and `locked`
|
52
|
+
# keys with boolean values.
|
53
|
+
#
|
54
|
+
# Used in COPY and MOVE (which inherits from COPY) requests.
|
55
|
+
def copy_options(options)
|
56
|
+
copy_options = { can_copy: false, ancestor_exist: false, locked: false }
|
32
57
|
|
58
|
+
destination = copy_destination options
|
33
59
|
to_path = join_paths @root_dir, destination
|
34
|
-
|
35
|
-
|
36
|
-
copy_options[:locked] = if to_path_exist
|
37
|
-
if destination_locked? to_path
|
38
|
-
true
|
39
|
-
else
|
40
|
-
to_path_parent = split_and_pop(path: to_path).join '/'
|
41
|
-
common_ancestor = common_path_ancestors(to_path, @ancestors).first
|
42
|
-
to_path_ancestors = ancestors_from_path_to_ancestor to_path, common_ancestor
|
43
|
-
|
44
|
-
locking_ancestor? to_path_parent, to_path_ancestors
|
45
|
-
end
|
46
|
-
else
|
47
|
-
false
|
48
|
-
end
|
49
|
-
|
50
|
-
if copy_options[:ancestor_exist]
|
51
|
-
if !overwrite && to_path_exist
|
52
|
-
copy_options[:can_copy] = false
|
53
|
-
else
|
54
|
-
copy_options[:can_copy] = true
|
55
|
-
end
|
56
|
-
end
|
60
|
+
to_path_exists = File.exist? to_path
|
57
61
|
|
62
|
+
copy_options[:ancestor_exist] = File.exist? parent_path destination
|
63
|
+
copy_options[:locked] = can_copy_locked_option to_path, to_path_exists
|
64
|
+
copy_options = can_copy_option copy_options, options, to_path_exists
|
58
65
|
copy_options
|
59
66
|
end
|
60
67
|
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
68
|
+
# Responsible for creating a duplicate of the resource in
|
69
|
+
# `options[:destination]` (see section 9.8 of RFC4918).
|
70
|
+
#
|
71
|
+
# Used in COPY and MOVE (which inherits from COPY) requests.
|
65
72
|
def copy(options)
|
66
|
-
destination = options
|
67
|
-
preserve_existing = is_false? options[:overwrite]
|
68
|
-
|
73
|
+
destination = copy_destination options
|
69
74
|
to_path = join_paths @root_dir, destination
|
70
75
|
to_path_exists = File.exist? to_path
|
71
76
|
|
72
|
-
|
73
|
-
FileUtils.cp_r @src_path, to_path, preserve: preserve_existing
|
74
|
-
else
|
75
|
-
FileUtils.cp @src_path, to_path, preserve: preserve_existing
|
76
|
-
end
|
77
|
-
|
78
|
-
if store_exist? && preserve_existing
|
79
|
-
dest_store_path = collection? ? "#{to_path}/#{@name}" : to_path
|
80
|
-
dest_store_path += ".pstore"
|
77
|
+
preserve_existing = false? options[:overwrite]
|
81
78
|
|
82
|
-
|
83
|
-
|
79
|
+
copy_resource_to_path to_path, preserve_existing
|
80
|
+
copy_pstore_to_path to_path, preserve_existing
|
84
81
|
|
85
82
|
to_path_exists
|
86
83
|
end
|
87
84
|
|
85
|
+
# Responsible for creating a new collection based on the resource (see
|
86
|
+
# section 9.3 of RFC4918).
|
87
|
+
#
|
88
|
+
# Used in MKCOL requests.
|
88
89
|
def create_collection
|
89
90
|
Dir.mkdir @src_path
|
90
91
|
end
|
91
92
|
|
93
|
+
# Responsible for deleting a resource collection (see section 9.6 of
|
94
|
+
# RFC4918).
|
95
|
+
#
|
96
|
+
# Used in DELETE and MOVE requests.
|
92
97
|
def delete_collection
|
93
98
|
FileUtils.rm_r @src_path
|
94
99
|
FileUtils.rm_r @store_path if store_exist?
|
95
100
|
end
|
96
101
|
|
102
|
+
# Responsible for returning unique identifier used to create an etag.
|
103
|
+
#
|
104
|
+
# Used in precondition validation, as well as GET, HEAD, and PROPFIND
|
105
|
+
# requests.
|
97
106
|
def etag
|
98
107
|
[@updated_at.to_i, @stats[:inode], @stats[:size]].join('-').to_s
|
99
108
|
end
|
100
109
|
|
110
|
+
# Responsible for indicating if the resource already exists.
|
111
|
+
#
|
112
|
+
# Used in DELETE, LOCK, MKCOL, and MOVE requests.
|
101
113
|
def exists?
|
102
114
|
File.exist? @src_path
|
103
115
|
end
|
104
116
|
|
105
|
-
|
117
|
+
# Responsible for creating a lock on the resource (see section 9.10 of
|
118
|
+
# RFC4918).
|
119
|
+
#
|
120
|
+
# Used in LOCK requests.
|
121
|
+
def lock(nodes, depth = 'infinity')
|
106
122
|
properties = {}
|
107
123
|
|
108
124
|
nodes.each do |node|
|
@@ -110,30 +126,32 @@ module Calligraphy
|
|
110
126
|
properties[node.name.to_sym] = node
|
111
127
|
end
|
112
128
|
|
113
|
-
unless exists?
|
114
|
-
write ''
|
115
|
-
@name = File.basename @src_path
|
116
|
-
init_pstore
|
117
|
-
end
|
129
|
+
create_blank_file unless exists?
|
118
130
|
|
119
131
|
create_lock properties, depth
|
132
|
+
fetch_lock_info
|
120
133
|
end
|
121
134
|
|
135
|
+
# Responsible for indicating if a resource lock is exclusive.
|
136
|
+
#
|
137
|
+
# Used in LOCK requests.
|
122
138
|
def lock_is_exclusive?
|
123
139
|
lockscope == 'exclusive'
|
124
140
|
end
|
125
141
|
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
end
|
130
|
-
|
142
|
+
# Responsible for indicating if a resource is current locked.
|
143
|
+
#
|
144
|
+
# Used in LOCK requests.
|
131
145
|
def locked?
|
132
|
-
|
146
|
+
fetch_lock_info
|
147
|
+
|
133
148
|
obj_exists_and_is_not_type? obj: @lock_info, type: []
|
134
149
|
end
|
135
150
|
|
136
|
-
|
151
|
+
# Responsible for indicating if a resource is locked to the current user.
|
152
|
+
#
|
153
|
+
# Used in DELETE, LOCK, MOVE, PROPPATCH, and PUT requests.
|
154
|
+
def locked_to_user?(headers = nil)
|
137
155
|
if locked?
|
138
156
|
!can_unlock? headers
|
139
157
|
else
|
@@ -141,6 +159,10 @@ module Calligraphy
|
|
141
159
|
end
|
142
160
|
end
|
143
161
|
|
162
|
+
# Responsible for handling the retrieval of properties defined on the
|
163
|
+
# resource (see section 9.1 of RFC4918).
|
164
|
+
#
|
165
|
+
# Used in PROPFIND requests.
|
144
166
|
def propfind(nodes)
|
145
167
|
properties = { found: [], not_found: [] }
|
146
168
|
|
@@ -150,96 +172,73 @@ module Calligraphy
|
|
150
172
|
|
151
173
|
value = get_property prop
|
152
174
|
|
153
|
-
|
154
|
-
properties[:not_found].push prop
|
155
|
-
elsif value.is_a? Hash
|
156
|
-
value.each_key do |key|
|
157
|
-
properties[:found].push value[key]
|
158
|
-
end
|
159
|
-
else
|
160
|
-
properties[:found].push value
|
161
|
-
end
|
175
|
+
update_found_properties properties, prop, value
|
162
176
|
end
|
163
177
|
end
|
164
178
|
|
165
179
|
properties
|
166
180
|
end
|
167
181
|
|
182
|
+
# Responsible for handling the addition and/or removal of properties
|
183
|
+
# defined on the resource through a PROPPATCH request (see section 9.2 of
|
184
|
+
# RFC4918).
|
185
|
+
#
|
186
|
+
# Used in PROPPATCH requests.
|
168
187
|
def proppatch(nodes)
|
169
188
|
actions = { set: [], remove: [] }
|
170
189
|
|
171
190
|
@store.transaction do
|
172
191
|
@store[:properties] = {} unless @store[:properties].is_a? Hash
|
173
192
|
|
174
|
-
nodes
|
175
|
-
if node.name == 'set'
|
176
|
-
node.children.each do |prop|
|
177
|
-
prop.children.each do |property|
|
178
|
-
prop_sym = property.name.to_sym
|
179
|
-
node = Calligraphy::XML::Node.new property
|
180
|
-
|
181
|
-
if @store[:properties][prop_sym]
|
182
|
-
if @store[:properties][prop_sym].is_a? Array
|
183
|
-
unless matching_namespace? @store[:properties][prop_sym], node
|
184
|
-
@store[:properties][prop_sym].push node
|
185
|
-
end
|
186
|
-
else
|
187
|
-
if !same_namespace? @store[:properties][prop_sym], node
|
188
|
-
@store[:properties][prop_sym] = [@store[:properties][prop_sym]]
|
189
|
-
@store[:properties][prop_sym].push node
|
190
|
-
else
|
191
|
-
@store[:properties][prop_sym] = node
|
192
|
-
end
|
193
|
-
end
|
194
|
-
else
|
195
|
-
@store[:properties][prop_sym] = node
|
196
|
-
end
|
197
|
-
|
198
|
-
actions[:set].push property
|
199
|
-
end
|
200
|
-
end
|
201
|
-
elsif node.name == 'remove'
|
202
|
-
node.children.each do |prop|
|
203
|
-
prop.children.each do |property|
|
204
|
-
@store[:properties].delete property.name.to_sym
|
205
|
-
|
206
|
-
actions[:remove].push property
|
207
|
-
end
|
208
|
-
end
|
209
|
-
end
|
210
|
-
end
|
193
|
+
add_remove_properties nodes, actions
|
211
194
|
end
|
212
195
|
|
213
196
|
get_custom_property nil
|
214
197
|
actions
|
215
198
|
end
|
216
199
|
|
200
|
+
# Responsible for setting and returning the contents of a resource
|
201
|
+
# if it is readable (see section 9.4 of RFC4918).
|
202
|
+
#
|
203
|
+
# Used in GET requests.
|
217
204
|
def read
|
218
205
|
@contents ||= File.read @src_path if readable?
|
219
206
|
end
|
220
207
|
|
208
|
+
# Responsible for refreshing locks (see section 9.10.2 of RFC4918).
|
209
|
+
#
|
210
|
+
# Used in LOCK requests.
|
221
211
|
def refresh_lock
|
222
212
|
if locked?
|
223
213
|
@store.transaction do
|
224
214
|
@store[:lockdiscovery][-1][:timeout] = timeout_node
|
225
215
|
end
|
226
216
|
|
227
|
-
|
217
|
+
fetch_lock_info
|
228
218
|
else
|
229
219
|
refresh_ancestor_locks @ancestor_path, @ancestors.dup
|
230
220
|
end
|
231
221
|
end
|
232
222
|
|
223
|
+
# Responsible for unlocking a resource lock (see section 9.11 of RFC4918).
|
224
|
+
#
|
225
|
+
# Used in UNLOCK requests.
|
233
226
|
def unlock(token)
|
234
227
|
if lock_tokens.include? token
|
235
228
|
remove_lock token
|
229
|
+
@lock_info = nil
|
230
|
+
|
236
231
|
:no_content
|
237
232
|
else
|
238
233
|
:forbidden
|
239
234
|
end
|
240
235
|
end
|
241
236
|
|
242
|
-
|
237
|
+
# Responsible for writing contents to a resource (see section 9.7 of
|
238
|
+
# RFC4918).
|
239
|
+
#
|
240
|
+
# Used in PUT requests.
|
241
|
+
def write(contents = @request_body.to_s)
|
243
242
|
@contents = contents
|
244
243
|
|
245
244
|
File.open(@src_path, 'w') do |file|
|
@@ -261,7 +260,7 @@ module Calligraphy
|
|
261
260
|
@stats = {
|
262
261
|
created_at: file_stats.ctime,
|
263
262
|
inode: file_stats.ino,
|
264
|
-
size: file_stats.size
|
263
|
+
size: file_stats.size
|
265
264
|
}
|
266
265
|
@updated_at = file_stats.mtime
|
267
266
|
end
|
@@ -275,6 +274,34 @@ module Calligraphy
|
|
275
274
|
join_paths @root_dir, split_and_pop(path: path)
|
276
275
|
end
|
277
276
|
|
277
|
+
def copy_destination(options)
|
278
|
+
options[:destination].tap { |s| s.slice! @mount_point }
|
279
|
+
end
|
280
|
+
|
281
|
+
def can_copy_locked_option(to_path, to_path_exists)
|
282
|
+
return false unless to_path_exists
|
283
|
+
return true if destination_locked? to_path
|
284
|
+
|
285
|
+
to_path_parent = split_and_pop(path: to_path).join '/'
|
286
|
+
common_ancestor = common_path_ancestors(to_path, @ancestors).first
|
287
|
+
to_path_ancestors = ancestors_from_path_to_ancestor(to_path,
|
288
|
+
common_ancestor)
|
289
|
+
|
290
|
+
locking_ancestor? to_path_parent, to_path_ancestors
|
291
|
+
end
|
292
|
+
|
293
|
+
def can_copy_option(copy_options, options, to_path_exists)
|
294
|
+
return copy_options unless copy_options[:ancestor_exist]
|
295
|
+
|
296
|
+
copy_options[:can_copy] = if false?(options[:overwrite]) && to_path_exists
|
297
|
+
false
|
298
|
+
else
|
299
|
+
true
|
300
|
+
end
|
301
|
+
|
302
|
+
copy_options
|
303
|
+
end
|
304
|
+
|
278
305
|
def destination_locked?(path)
|
279
306
|
store = PStore.new "#{path}.pstore"
|
280
307
|
lock = store.transaction(true) { store[:lockdiscovery] }
|
@@ -295,36 +322,51 @@ module Calligraphy
|
|
295
322
|
path = split_and_pop path: path
|
296
323
|
ancestors = []
|
297
324
|
|
298
|
-
until path.last == stop_at_ancestor
|
299
|
-
ancestors.push path.pop
|
300
|
-
end
|
301
|
-
|
325
|
+
ancestors.push path.pop until path.last == stop_at_ancestor
|
302
326
|
ancestors.push stop_at_ancestor
|
303
327
|
ancestors.reverse
|
304
328
|
end
|
305
329
|
|
330
|
+
def copy_resource_to_path(to_path, preserve_existing)
|
331
|
+
if collection?
|
332
|
+
FileUtils.cp_r @src_path, to_path, preserve: preserve_existing
|
333
|
+
else
|
334
|
+
FileUtils.cp @src_path, to_path, preserve: preserve_existing
|
335
|
+
end
|
336
|
+
end
|
337
|
+
|
338
|
+
def copy_pstore_to_path(to_path, preserve_existing)
|
339
|
+
return unless store_exist? && preserve_existing
|
340
|
+
|
341
|
+
dest_store_path = collection? ? "#{to_path}/#{@name}" : to_path
|
342
|
+
dest_store_path += '.pstore'
|
343
|
+
|
344
|
+
FileUtils.cp @store_path, dest_store_path, preserve: preserve_existing
|
345
|
+
end
|
346
|
+
|
306
347
|
def store_exist?
|
307
348
|
File.exist? @store_path
|
308
349
|
end
|
309
350
|
|
351
|
+
def create_blank_file
|
352
|
+
write ''
|
353
|
+
@name = File.basename @src_path
|
354
|
+
init_pstore
|
355
|
+
end
|
356
|
+
|
310
357
|
def create_lock(properties, depth)
|
311
358
|
@store.transaction do
|
312
359
|
@store[:lockcreator] = client_nonce
|
313
|
-
@store[:lockdiscovery] = [] unless @store[:lockdiscovery].is_a? Array
|
314
360
|
@store[:lockdepth] = depth
|
361
|
+
@store[:lockdiscovery] = [] unless @store[:lockdiscovery].is_a? Array
|
315
362
|
|
316
|
-
|
317
|
-
|
318
|
-
|
319
|
-
|
320
|
-
properties.each_key do |prop|
|
321
|
-
activelock[prop] = Calligraphy::XML::Node.new properties[prop]
|
322
|
-
end
|
363
|
+
@store[:lockdiscovery].push({}.tap do |activelock|
|
364
|
+
activelock[:locktoken] = create_lock_token
|
365
|
+
activelock[:timeout] = timeout_node
|
323
366
|
|
324
|
-
|
367
|
+
add_lock_properties activelock, properties
|
368
|
+
end)
|
325
369
|
end
|
326
|
-
|
327
|
-
get_lock_info
|
328
370
|
end
|
329
371
|
|
330
372
|
def create_lock_token
|
@@ -346,7 +388,13 @@ module Calligraphy
|
|
346
388
|
end
|
347
389
|
end
|
348
390
|
|
349
|
-
def
|
391
|
+
def add_lock_properties(activelock, properties)
|
392
|
+
properties.each_key do |prop|
|
393
|
+
activelock[prop] = Calligraphy::XML::Node.new properties[prop]
|
394
|
+
end
|
395
|
+
end
|
396
|
+
|
397
|
+
def fetch_lock_info
|
350
398
|
return nil if @store.nil?
|
351
399
|
|
352
400
|
@lock_info = @store.transaction(true) { @store[:lockdiscovery] }
|
@@ -357,94 +405,153 @@ module Calligraphy
|
|
357
405
|
@lock_info[-1][:lockscope].children[0].name
|
358
406
|
end
|
359
407
|
|
360
|
-
def can_unlock?(headers=nil)
|
408
|
+
def can_unlock?(headers = nil)
|
361
409
|
token = unless headers.nil?
|
362
|
-
|
363
|
-
|
410
|
+
extract_lock_token(headers['If']) if headers['If']
|
411
|
+
end
|
364
412
|
|
365
413
|
lock_tokens.include? token
|
366
414
|
end
|
367
415
|
|
368
|
-
def
|
416
|
+
def lock_tokens
|
417
|
+
fetch_lock_info
|
418
|
+
@lock_info&.each { |x| x }&.map { |k| k[:locktoken].children[0].text }
|
419
|
+
end
|
420
|
+
|
421
|
+
def locking_ancestor?(ancestor_path, ancestors, headers = nil)
|
422
|
+
ancestor_info = ancestor_lock_info headers
|
369
423
|
ancestor_store_path = "#{ancestor_path}/#{ancestors[-1]}.pstore"
|
370
|
-
check_lock_creator = Calligraphy.enable_digest_authentication
|
371
|
-
blocking_lock = false
|
372
|
-
unlockable = true
|
373
424
|
|
374
425
|
ancestors.pop
|
375
426
|
|
376
|
-
|
377
|
-
ancestor_store = PStore.new ancestor_store_path
|
427
|
+
check_for_ancestor ancestor_info, ancestor_store_path
|
378
428
|
|
379
|
-
|
380
|
-
|
381
|
-
ancestor_lock_depth = nil
|
429
|
+
if ancestor_info[:blocking] || ancestors.empty?
|
430
|
+
assign_locking_ancestor ancestor_info
|
382
431
|
|
383
|
-
|
384
|
-
|
385
|
-
ancestor_lock_depth = ancestor_store[:lockdepth]
|
432
|
+
return ancestor_info[:unlockable] ? false : true
|
433
|
+
end
|
386
434
|
|
387
|
-
|
388
|
-
|
389
|
-
|
390
|
-
|
435
|
+
next_ancestor = split_and_pop(path: ancestor_path).join '/'
|
436
|
+
locking_ancestor? next_ancestor, ancestors, ancestor_info[:headers]
|
437
|
+
end
|
438
|
+
|
439
|
+
def ancestor_lock_info(headers)
|
440
|
+
{
|
441
|
+
blocking: false,
|
442
|
+
check_creator: Calligraphy.enable_digest_authentication,
|
443
|
+
creator: nil,
|
444
|
+
depth: nil,
|
445
|
+
headers: headers || nil,
|
446
|
+
lock: nil,
|
447
|
+
unlockable: true
|
448
|
+
}
|
449
|
+
end
|
391
450
|
|
392
|
-
|
451
|
+
def check_for_ancestor(ancestor_info, store_path)
|
452
|
+
return unless File.exist? store_path
|
453
|
+
ancestor_lock_from_store ancestor_info, store_path
|
393
454
|
|
394
|
-
|
395
|
-
|
396
|
-
|
397
|
-
|
455
|
+
ancestor_info[:blocking] = obj_exists_and_is_not_type?(
|
456
|
+
obj: ancestor_info[:lock],
|
457
|
+
type: []
|
458
|
+
)
|
398
459
|
|
399
|
-
|
400
|
-
|
401
|
-
|
460
|
+
blocking_lock_unlockable? ancestor_info if ancestor_info[:blocking]
|
461
|
+
end
|
462
|
+
|
463
|
+
def ancestor_lock_from_store(lock_info, store_path)
|
464
|
+
ancestor_store = PStore.new store_path
|
465
|
+
|
466
|
+
ancestor_store.transaction(true) do
|
467
|
+
lock_info[:lock] = ancestor_store[:lockdiscovery]
|
468
|
+
lock_info[:depth] = ancestor_store[:lockdepth]
|
402
469
|
|
403
|
-
|
404
|
-
|
470
|
+
if lock_info[:check_creator]
|
471
|
+
lock_info[:creator] = ancestor_store[:lockcreator]
|
405
472
|
end
|
406
473
|
end
|
474
|
+
end
|
407
475
|
|
408
|
-
|
409
|
-
|
410
|
-
depth: ancestor_lock_depth,
|
411
|
-
info: ancestor_lock
|
412
|
-
}
|
476
|
+
def blocking_lock_unlockable?(lock_info)
|
477
|
+
headers = lock_info[:headers]
|
413
478
|
|
414
|
-
|
415
|
-
|
479
|
+
token = unless headers.nil?
|
480
|
+
extract_lock_token(headers['If']) if headers['If']
|
481
|
+
end
|
416
482
|
|
417
|
-
|
418
|
-
|
483
|
+
ancestor_tokens = lock_info[:lock]
|
484
|
+
|
485
|
+
lock_info[:unlockable] =
|
486
|
+
ancestor_tokens.include?(token) ||
|
487
|
+
(lock_info[:check_creator] && (lock_info[:creator] == client_nonce))
|
488
|
+
end
|
489
|
+
|
490
|
+
def ancestor_lock_tokens(lock_info)
|
491
|
+
lock_info[:lock].each { |x| x }.map { |k| k[:locktoken].children[0].text }
|
492
|
+
end
|
493
|
+
|
494
|
+
def assign_locking_ancestor(ancestor_info)
|
495
|
+
@locking_ancestor = {
|
496
|
+
depth: ancestor_info[:depth],
|
497
|
+
info: ancestor_info[:lock]
|
498
|
+
}
|
419
499
|
end
|
420
500
|
|
421
501
|
def get_property(prop)
|
422
502
|
case prop.name
|
423
|
-
when 'creationdate'
|
424
|
-
prop.content = @stats[:created_at]
|
425
|
-
when 'displayname'
|
426
|
-
prop.content = @name
|
427
|
-
when 'getcontentlanguage'
|
428
|
-
prop.content = nil
|
429
|
-
when 'getcontentlength'
|
430
|
-
prop.content = @stats[:size]
|
431
|
-
when 'getcontenttype'
|
432
|
-
prop.content = nil
|
433
|
-
when 'getetag'
|
434
|
-
prop.content = nil
|
435
|
-
when 'getlastmodified'
|
436
|
-
prop.content = @updated_at
|
437
503
|
when 'lockdiscovery'
|
438
|
-
|
439
|
-
when
|
440
|
-
prop.content =
|
441
|
-
|
442
|
-
prop.content = nil
|
504
|
+
fetch_lock_info
|
505
|
+
when *DAV_PROPERTY_METHODS
|
506
|
+
prop.content = send prop.name
|
507
|
+
prop
|
443
508
|
else
|
444
|
-
|
509
|
+
get_custom_property prop.name
|
445
510
|
end
|
511
|
+
end
|
512
|
+
|
513
|
+
def creationdate
|
514
|
+
@stats[:created_at]
|
515
|
+
end
|
516
|
+
|
517
|
+
def displayname
|
518
|
+
get_custom_property(:displayname) || @name
|
519
|
+
end
|
520
|
+
|
521
|
+
def getcontentlanguage
|
522
|
+
get_custom_property :contentlanguage
|
523
|
+
end
|
524
|
+
|
525
|
+
def getcontentlength
|
526
|
+
@stats[:size]
|
527
|
+
end
|
528
|
+
|
529
|
+
def getcontenttype
|
530
|
+
get_custom_property :contenttype
|
531
|
+
end
|
532
|
+
|
533
|
+
def getetag
|
534
|
+
cache_key = ActiveSupport::Cache.expand_cache_key [@resource.etag, '']
|
535
|
+
"W/\"#{Digest::MD5.hexdigest(cache_key)}\""
|
536
|
+
end
|
537
|
+
|
538
|
+
def getlastmodified
|
539
|
+
@updated_at
|
540
|
+
end
|
541
|
+
|
542
|
+
def lockdiscovery
|
543
|
+
fetch_lock_info
|
544
|
+
end
|
446
545
|
|
447
|
-
|
546
|
+
def resourcetype
|
547
|
+
'collection' if collection?
|
548
|
+
end
|
549
|
+
|
550
|
+
def supportedlock
|
551
|
+
exclusive_write = lockentry_hash('exclusive', 'write')
|
552
|
+
shared_write = lockentry_hash('shared', 'write')
|
553
|
+
|
554
|
+
JSON.generate [exclusive_write, shared_write]
|
448
555
|
end
|
449
556
|
|
450
557
|
def get_custom_property(prop)
|
@@ -452,12 +559,85 @@ module Calligraphy
|
|
452
559
|
@store_properties[prop.to_sym] unless @store_properties.nil? || prop.nil?
|
453
560
|
end
|
454
561
|
|
455
|
-
def
|
456
|
-
|
562
|
+
def update_found_properties(properties, prop, value)
|
563
|
+
if value.nil?
|
564
|
+
properties[:not_found].push prop
|
565
|
+
elsif value.is_a? Hash
|
566
|
+
value.each_key do |key|
|
567
|
+
properties[:found].push value[key]
|
568
|
+
end
|
569
|
+
else
|
570
|
+
properties[:found].push value
|
571
|
+
end
|
572
|
+
end
|
573
|
+
|
574
|
+
def add_remove_properties(nodes, actions)
|
575
|
+
nodes.each do |node|
|
576
|
+
if node.name == 'set'
|
577
|
+
add_properties node, actions
|
578
|
+
elsif node.name == 'remove'
|
579
|
+
remove_properties node, actions
|
580
|
+
end
|
581
|
+
end
|
582
|
+
end
|
583
|
+
|
584
|
+
def add_properties(node, actions)
|
585
|
+
node.children.each do |prop|
|
586
|
+
prop.children.each do |property|
|
587
|
+
node = Calligraphy::XML::Node.new property
|
588
|
+
prop_sym = property.name.to_sym
|
589
|
+
|
590
|
+
store_property_node node, prop_sym
|
591
|
+
|
592
|
+
actions[:set].push property
|
593
|
+
end
|
594
|
+
end
|
595
|
+
end
|
596
|
+
|
597
|
+
def store_property_node(node, prop)
|
598
|
+
# Property does not exist yet so we can just store the property node.
|
599
|
+
return @store[:properties][prop] = node unless @store[:properties][prop]
|
600
|
+
|
601
|
+
if @store[:properties][prop].is_a? Array
|
602
|
+
store_mismatch_namespace_property_node node, prop
|
603
|
+
elsif same_namespace? @store[:properties][prop], node
|
604
|
+
# If stored property and node have the same namespace, we can just
|
605
|
+
# overwrite the previously stored property node.
|
606
|
+
@store[:properties][prop] = node
|
607
|
+
else
|
608
|
+
# If stored property and node DO NOT have the same namespace, create
|
609
|
+
# an array for the stored property and push the new property node.
|
610
|
+
store_mismatch_namespace_property_nodes node, prop
|
611
|
+
end
|
612
|
+
end
|
613
|
+
|
614
|
+
def store_mismatch_namespace_property_node(node, prop)
|
615
|
+
node_arr = @store[:properties][prop]
|
616
|
+
|
617
|
+
namespace_mismatch = node_arr.select do |x|
|
618
|
+
x.namespace.href == node.namespace.href
|
619
|
+
end.length.positive?
|
620
|
+
|
621
|
+
@store[:properties][prop].push node unless namespace_mismatch
|
457
622
|
end
|
458
623
|
|
459
624
|
def same_namespace?(node1, node2)
|
460
|
-
node1.namespace
|
625
|
+
node1.namespace&.href == node2.namespace&.href
|
626
|
+
end
|
627
|
+
|
628
|
+
def store_mismatch_namespace_property_nodes(node, prop)
|
629
|
+
@store[:properties][prop] = [@store[:properties][prop]]
|
630
|
+
@store[:properties][prop].push node
|
631
|
+
end
|
632
|
+
|
633
|
+
def remove_properties(node, actions)
|
634
|
+
node.children.each do |prop|
|
635
|
+
prop.children.each do |property|
|
636
|
+
@store[:properties].delete property.name.to_sym
|
637
|
+
|
638
|
+
actions[:remove].push property
|
639
|
+
end
|
640
|
+
end
|
461
641
|
end
|
462
642
|
|
463
643
|
def refresh_ancestor_locks(ancestor_path, ancestors)
|
@@ -465,11 +645,7 @@ module Calligraphy
|
|
465
645
|
ancestors.pop
|
466
646
|
|
467
647
|
if File.exist? ancestor_store_path
|
468
|
-
|
469
|
-
ancestor_lock = ancestor_store.transaction do
|
470
|
-
ancestor_store[:lockdiscovery][-1][:timeout] = timeout_node
|
471
|
-
ancestor_store[:lockdiscovery]
|
472
|
-
end
|
648
|
+
ancestor_lock = refresh_ancestor_lock ancestor_store_path
|
473
649
|
|
474
650
|
return map_array_of_hashes ancestor_lock
|
475
651
|
end
|
@@ -478,6 +654,15 @@ module Calligraphy
|
|
478
654
|
refresh_ancestor_locks next_ancestor, ancestors
|
479
655
|
end
|
480
656
|
|
657
|
+
def refresh_ancestor_lock(ancestor_store_path)
|
658
|
+
ancestor_store = PStore.new ancestor_store_path
|
659
|
+
|
660
|
+
ancestor_store.transaction do
|
661
|
+
ancestor_store[:lockdiscovery][-1][:timeout] = timeout_node
|
662
|
+
ancestor_store[:lockdiscovery]
|
663
|
+
end
|
664
|
+
end
|
665
|
+
|
481
666
|
def remove_lock(token)
|
482
667
|
@store.transaction do
|
483
668
|
@store.delete :lockcreator
|
@@ -490,8 +675,6 @@ module Calligraphy
|
|
490
675
|
end
|
491
676
|
end
|
492
677
|
end
|
493
|
-
|
494
|
-
@lock_info = nil
|
495
678
|
end
|
496
679
|
end
|
497
680
|
end
|