store_agent 1.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.
- checksums.yaml +7 -0
- data/.gitignore +14 -0
- data/.rspec +3 -0
- data/Gemfile +4 -0
- data/Guardfile +5 -0
- data/LICENSE +202 -0
- data/README.md +433 -0
- data/Rakefile +2 -0
- data/lib/store_agent.rb +31 -0
- data/lib/store_agent/config.rb +152 -0
- data/lib/store_agent/data_encoder.rb +32 -0
- data/lib/store_agent/data_encoder/gzip_encoder.rb +50 -0
- data/lib/store_agent/data_encoder/openssl_aes_256_cbc_encoder.rb +65 -0
- data/lib/store_agent/exceptions.rb +87 -0
- data/lib/store_agent/node.rb +26 -0
- data/lib/store_agent/node/attachment.rb +93 -0
- data/lib/store_agent/node/attachment/metadata.rb +120 -0
- data/lib/store_agent/node/attachment/permission.rb +121 -0
- data/lib/store_agent/node/object.rb +233 -0
- data/lib/store_agent/node/object/directory_object.rb +264 -0
- data/lib/store_agent/node/object/file_object.rb +197 -0
- data/lib/store_agent/node/object/virtual_object.rb +25 -0
- data/lib/store_agent/node/prepend_module/locker.rb +125 -0
- data/lib/store_agent/node/prepend_module/path_validator.rb +138 -0
- data/lib/store_agent/node/prepend_module/permission_checker.rb +96 -0
- data/lib/store_agent/user.rb +111 -0
- data/lib/store_agent/validator.rb +60 -0
- data/lib/store_agent/version.rb +19 -0
- data/lib/store_agent/version_manager.rb +101 -0
- data/lib/store_agent/version_manager/ruby_git.rb +100 -0
- data/lib/store_agent/version_manager/rugged_git.rb +133 -0
- data/lib/store_agent/workspace.rb +88 -0
- data/spec/spec_helper.rb +47 -0
- data/spec/store_agent/data_encoder/encoder_shared_context.rb +74 -0
- data/spec/store_agent/data_encoder/gzip_encoder_spec.rb +41 -0
- data/spec/store_agent/data_encoder/openssl_aes_256_cbc_encoder_spec.rb +42 -0
- data/spec/store_agent/data_encoder_spec.rb +78 -0
- data/spec/store_agent/node/directory_object_spec.rb +563 -0
- data/spec/store_agent/node/file_object_spec.rb +379 -0
- data/spec/store_agent/node/locker_spec.rb +191 -0
- data/spec/store_agent/node/metadata_spec.rb +339 -0
- data/spec/store_agent/node/object_spec.rb +73 -0
- data/spec/store_agent/node/path_validator_spec.rb +121 -0
- data/spec/store_agent/node/permission_spec.rb +232 -0
- data/spec/store_agent/user_spec.rb +127 -0
- data/spec/store_agent/version_manager/git_shared_context.rb +286 -0
- data/spec/store_agent/version_manager/ruby_git_spec.rb +32 -0
- data/spec/store_agent/version_manager/rugged_git_spec.rb +32 -0
- data/spec/store_agent/workspace_spec.rb +107 -0
- data/spec/store_agent_spec.rb +53 -0
- data/store_agent.gemspec +33 -0
- metadata +252 -0
|
@@ -0,0 +1,264 @@
|
|
|
1
|
+
#--
|
|
2
|
+
# Copyright 2015 realglobe, Inc.
|
|
3
|
+
#
|
|
4
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
5
|
+
# you may not use this file except in compliance with the License.
|
|
6
|
+
# You may obtain a copy of the License at
|
|
7
|
+
#
|
|
8
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
9
|
+
#
|
|
10
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
11
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
12
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
13
|
+
# See the License for the specific language governing permissions and
|
|
14
|
+
# limitations under the License.
|
|
15
|
+
#++
|
|
16
|
+
|
|
17
|
+
module StoreAgent
|
|
18
|
+
module Node
|
|
19
|
+
# ディレクトリ
|
|
20
|
+
class DirectoryObject < Object
|
|
21
|
+
def initialize(params) # :nodoc:
|
|
22
|
+
super
|
|
23
|
+
if !@path.end_with?("/")
|
|
24
|
+
@path = "#{@path}/"
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def create
|
|
29
|
+
super do
|
|
30
|
+
if block_given?
|
|
31
|
+
yield self
|
|
32
|
+
end
|
|
33
|
+
FileUtils.mkdir(storage_object_path)
|
|
34
|
+
workspace.version_manager.add("#{storage_object_path}.keep")
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
# TODO
|
|
39
|
+
def read(revision: nil)
|
|
40
|
+
super do
|
|
41
|
+
filenames =
|
|
42
|
+
if revision.nil?
|
|
43
|
+
current_children_filenames
|
|
44
|
+
else
|
|
45
|
+
workspace.version_manager.read(path: storage_object_path, revision: revision)
|
|
46
|
+
end
|
|
47
|
+
filenames - StoreAgent.reserved_filenames
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def update
|
|
52
|
+
raise "cannot update directory"
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def delete(*)
|
|
56
|
+
super do
|
|
57
|
+
success, errors = call_for_children do |child|
|
|
58
|
+
child.delete(recursive: false)
|
|
59
|
+
end
|
|
60
|
+
if success
|
|
61
|
+
FileUtils.remove_dir(storage_object_path)
|
|
62
|
+
else
|
|
63
|
+
raise StoreAgent::PermissionDeniedError.new(errors: errors)
|
|
64
|
+
end
|
|
65
|
+
workspace.version_manager.remove(storage_object_path, directory: true)
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
def touch(*, recursive: false)
|
|
70
|
+
super do
|
|
71
|
+
FileUtils.touch("#{storage_object_path}.keep")
|
|
72
|
+
workspace.version_manager.add("#{storage_object_path}.keep")
|
|
73
|
+
if recursive
|
|
74
|
+
success, errors = call_for_children do |child|
|
|
75
|
+
child.touch(recursive: true)
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
def copy(dest_path = nil, *)
|
|
82
|
+
super do
|
|
83
|
+
dest_directory = build_dest_directory(dest_path).create
|
|
84
|
+
success, errors = call_for_children do |child|
|
|
85
|
+
child.copy("#{dest_directory.path}#{File.basename(child.path)}")
|
|
86
|
+
end
|
|
87
|
+
end
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
def move(dest_path = nil, *)
|
|
91
|
+
super do
|
|
92
|
+
dest_directory = build_dest_directory(dest_path)
|
|
93
|
+
disk_usage = metadata.disk_usage
|
|
94
|
+
file_count = directory_file_count
|
|
95
|
+
FileUtils.mv(storage_object_path, dest_directory.storage_object_path)
|
|
96
|
+
FileUtils.mv(metadata.base_path, dest_directory.metadata.base_path)
|
|
97
|
+
FileUtils.mv(permission.base_path, dest_directory.permission.base_path)
|
|
98
|
+
dest_directory.touch(recursive: true)
|
|
99
|
+
dest_directory.parent_directory.metadata.update(disk_usage: disk_usage, directory_file_count: 1, tree_file_count: file_count + 1, recursive: true)
|
|
100
|
+
parent_directory.metadata.update(disk_usage: -disk_usage, directory_file_count: -1, tree_file_count: -(file_count + 1), recursive: true)
|
|
101
|
+
|
|
102
|
+
[storage_object_path, metadata.base_path, permission.base_path].each do |dir_path|
|
|
103
|
+
workspace.version_manager.remove(dir_path, directory: true)
|
|
104
|
+
end
|
|
105
|
+
end
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
def get_metadata(*) # :nodoc:
|
|
109
|
+
super do
|
|
110
|
+
end
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
def get_permissions(*) # :nodoc:
|
|
114
|
+
super do
|
|
115
|
+
end
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
def chown(*, identifier: nil, recursive: false)
|
|
119
|
+
super do
|
|
120
|
+
if recursive
|
|
121
|
+
success, errors = call_for_children do |child|
|
|
122
|
+
child.chown(identifier: identifier, recursive: recursive)
|
|
123
|
+
end
|
|
124
|
+
end
|
|
125
|
+
end
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
def set_permission(identifier: nil, permission_values: {}, recursive: false)
|
|
129
|
+
super do
|
|
130
|
+
if recursive
|
|
131
|
+
success, errors = call_for_children do |child|
|
|
132
|
+
child.set_permission(identifier: identifier, permission_values: permission_values)
|
|
133
|
+
end
|
|
134
|
+
end
|
|
135
|
+
end
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
def unset_permission(identifier: nil, permission_names: [], recursive: false)
|
|
139
|
+
super do
|
|
140
|
+
if recursive
|
|
141
|
+
success, errors = call_for_children do |child|
|
|
142
|
+
child.unset_permission(identifier: identifier, permission_names: permission_names)
|
|
143
|
+
end
|
|
144
|
+
end
|
|
145
|
+
end
|
|
146
|
+
end
|
|
147
|
+
|
|
148
|
+
# 引数を現在のパスからの相対パスとして解釈し、オブジェクトのインスタンスを返す
|
|
149
|
+
def find_object(path)
|
|
150
|
+
object = StoreAgent::Node::Object.new(workspace: workspace, path: namespaced_absolute_path(path))
|
|
151
|
+
case object.exists? && object.filetype
|
|
152
|
+
when false
|
|
153
|
+
virtual(path)
|
|
154
|
+
when "directory"
|
|
155
|
+
directory(path)
|
|
156
|
+
when "file"
|
|
157
|
+
file(path)
|
|
158
|
+
else
|
|
159
|
+
raise "unknown filetype"
|
|
160
|
+
end
|
|
161
|
+
end
|
|
162
|
+
|
|
163
|
+
def virtual(path) # :nodoc:
|
|
164
|
+
StoreAgent::Node::VirtualObject.new(workspace: workspace, path: namespaced_absolute_path(path))
|
|
165
|
+
end
|
|
166
|
+
|
|
167
|
+
# 現在のパスからの相対パスで、ディレクトリオブジェクトのインスタンスを返す
|
|
168
|
+
def directory(path)
|
|
169
|
+
StoreAgent::Node::DirectoryObject.new(workspace: workspace, path: namespaced_absolute_path(path))
|
|
170
|
+
end
|
|
171
|
+
|
|
172
|
+
# 現在のパスからの相対パスで、ファイルオブジェクトのインスタンスを返す
|
|
173
|
+
def file(path)
|
|
174
|
+
StoreAgent::Node::FileObject.new(workspace: workspace, path: namespaced_absolute_path(path))
|
|
175
|
+
end
|
|
176
|
+
|
|
177
|
+
# 現在のディレクトリの直下にあるオブジェクトの一覧を返す
|
|
178
|
+
def children
|
|
179
|
+
(current_children_filenames - StoreAgent.reserved_filenames).map{|filename|
|
|
180
|
+
find_object(filename)
|
|
181
|
+
}
|
|
182
|
+
end
|
|
183
|
+
|
|
184
|
+
def default_metadata # :nodoc:
|
|
185
|
+
super.merge(directory_metadata)
|
|
186
|
+
end
|
|
187
|
+
|
|
188
|
+
def directory_metadata # :nodoc:
|
|
189
|
+
{
|
|
190
|
+
"is_dir" => true,
|
|
191
|
+
"directory_size" => StoreAgent::Node::Metadata.datasize_format(initial_bytesize),
|
|
192
|
+
"directory_bytes" => initial_bytesize,
|
|
193
|
+
"directory_size_limit" => StoreAgent::Node::Metadata.datasize_format(StoreAgent.config.default_directory_bytesize_limit),
|
|
194
|
+
"directory_bytes_limit" => StoreAgent.config.default_directory_bytesize_limit,
|
|
195
|
+
"directory_file_count" => 0,
|
|
196
|
+
"tree_file_count" => 0
|
|
197
|
+
}
|
|
198
|
+
end
|
|
199
|
+
|
|
200
|
+
# ディレクトリ直下にあるファイル数
|
|
201
|
+
def directory_file_count
|
|
202
|
+
metadata["directory_file_count"]
|
|
203
|
+
end
|
|
204
|
+
|
|
205
|
+
# ディレクトリ以下のツリー全体でのファイル数
|
|
206
|
+
def tree_file_count
|
|
207
|
+
metadata["tree_file_count"]
|
|
208
|
+
end
|
|
209
|
+
|
|
210
|
+
# TODO auth
|
|
211
|
+
def update_limit=(limit)
|
|
212
|
+
metadata["directory_bytes_limit"] = limit
|
|
213
|
+
end
|
|
214
|
+
|
|
215
|
+
def directory? # :nodoc:
|
|
216
|
+
true
|
|
217
|
+
end
|
|
218
|
+
|
|
219
|
+
private
|
|
220
|
+
|
|
221
|
+
def namespaced_absolute_path(path)
|
|
222
|
+
"#{@path}#{sanitize_path(path)}"
|
|
223
|
+
end
|
|
224
|
+
|
|
225
|
+
def initial_bytesize
|
|
226
|
+
File.size(storage_object_path)
|
|
227
|
+
end
|
|
228
|
+
|
|
229
|
+
def build_dest_directory(dest_path)
|
|
230
|
+
dest_object = workspace.find_object(dest_path)
|
|
231
|
+
case
|
|
232
|
+
when dest_object.file?
|
|
233
|
+
raise InvalidNodeTypeError.new(src_object: self, dest_object: dest_object)
|
|
234
|
+
when dest_object.directory?
|
|
235
|
+
sub_directory_object = dest_object.find_object(File.basename(path))
|
|
236
|
+
if sub_directory_object.exists?
|
|
237
|
+
raise InvalidPathError, "object already exists: #{sub_directory_object.path}"
|
|
238
|
+
end
|
|
239
|
+
dest_object.directory(File.basename(path))
|
|
240
|
+
else
|
|
241
|
+
workspace.directory(dest_path)
|
|
242
|
+
end
|
|
243
|
+
end
|
|
244
|
+
|
|
245
|
+
def current_children_filenames
|
|
246
|
+
FileUtils.cd(storage_object_path) do
|
|
247
|
+
return Dir.glob("*", File::FNM_DOTMATCH)
|
|
248
|
+
end
|
|
249
|
+
end
|
|
250
|
+
|
|
251
|
+
def call_for_children
|
|
252
|
+
errors = []
|
|
253
|
+
children.each do |child|
|
|
254
|
+
begin
|
|
255
|
+
yield child
|
|
256
|
+
rescue StoreAgent::PermissionDeniedError => e
|
|
257
|
+
errors << e
|
|
258
|
+
end
|
|
259
|
+
end
|
|
260
|
+
return errors.empty?, errors
|
|
261
|
+
end
|
|
262
|
+
end
|
|
263
|
+
end
|
|
264
|
+
end
|
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
#--
|
|
2
|
+
# Copyright 2015 realglobe, Inc.
|
|
3
|
+
#
|
|
4
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
5
|
+
# you may not use this file except in compliance with the License.
|
|
6
|
+
# You may obtain a copy of the License at
|
|
7
|
+
#
|
|
8
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
9
|
+
#
|
|
10
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
11
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
12
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
13
|
+
# See the License for the specific language governing permissions and
|
|
14
|
+
# limitations under the License.
|
|
15
|
+
#++
|
|
16
|
+
|
|
17
|
+
module StoreAgent
|
|
18
|
+
module Node
|
|
19
|
+
# ファイル
|
|
20
|
+
class FileObject < Object
|
|
21
|
+
attr_writer :body
|
|
22
|
+
|
|
23
|
+
def create(*params, &block)
|
|
24
|
+
super do
|
|
25
|
+
set_body(*params, &block)
|
|
26
|
+
save
|
|
27
|
+
workspace.version_manager.add(storage_object_path)
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def read(revision: nil)
|
|
32
|
+
super do
|
|
33
|
+
if revision.nil?
|
|
34
|
+
encoded_data = open(storage_object_path) do |f|
|
|
35
|
+
f.read
|
|
36
|
+
end
|
|
37
|
+
else
|
|
38
|
+
encoded_data = workspace.version_manager.read(path: storage_object_path, revision: revision)
|
|
39
|
+
end
|
|
40
|
+
StoreAgent.config.attachment_data_encoders.reverse.inject(encoded_data) do |data, encoder|
|
|
41
|
+
encoder.decode(data)
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def update(*params, &block)
|
|
47
|
+
super do
|
|
48
|
+
set_body(*params, &block)
|
|
49
|
+
if @body.nil?
|
|
50
|
+
raise "file body required"
|
|
51
|
+
end
|
|
52
|
+
save
|
|
53
|
+
disk_usage_diff = @body.length - metadata.disk_usage
|
|
54
|
+
metadata.update(disk_usage: disk_usage_diff, recursive: true)
|
|
55
|
+
workspace.version_manager.add(storage_object_path)
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def delete(*)
|
|
60
|
+
super do
|
|
61
|
+
FileUtils.rm(storage_object_path)
|
|
62
|
+
workspace.version_manager.remove(storage_object_path)
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
def touch(*)
|
|
67
|
+
super do
|
|
68
|
+
FileUtils.touch(storage_object_path)
|
|
69
|
+
workspace.version_manager.add(storage_object_path)
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
def copy(dest_path = nil, *)
|
|
74
|
+
super do
|
|
75
|
+
file_body = read
|
|
76
|
+
dest_file = build_dest_file(dest_path)
|
|
77
|
+
if dest_file.exists?
|
|
78
|
+
dest_file.update(file_body)
|
|
79
|
+
else
|
|
80
|
+
dest_file.create(read)
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
def move(dest_path = nil, *)
|
|
86
|
+
super do
|
|
87
|
+
dest_file = build_dest_file(dest_path)
|
|
88
|
+
if dest_file.exists?
|
|
89
|
+
disk_usage_diff = metadata.disk_usage - dest_file.metadata.disk_usage
|
|
90
|
+
file_count = 0
|
|
91
|
+
else
|
|
92
|
+
disk_usage_diff = metadata.disk_usage
|
|
93
|
+
file_count = 1
|
|
94
|
+
end
|
|
95
|
+
FileUtils.mv(storage_object_path, dest_file.storage_object_path)
|
|
96
|
+
FileUtils.mv(metadata.file_path, dest_file.metadata.file_path)
|
|
97
|
+
FileUtils.mv(permission.file_path, dest_file.permission.file_path)
|
|
98
|
+
dest_file.touch
|
|
99
|
+
dest_file.parent_directory.metadata.update(disk_usage: disk_usage_diff, directory_file_count: file_count, tree_file_count: file_count, recursive: true)
|
|
100
|
+
parent_directory.metadata.update(disk_usage: -dest_file.metadata.disk_usage, directory_file_count: -1, tree_file_count: -1, recursive: true)
|
|
101
|
+
|
|
102
|
+
[storage_object_path, metadata.file_path, permission.file_path].each do |file_path|
|
|
103
|
+
workspace.version_manager.remove(file_path)
|
|
104
|
+
end
|
|
105
|
+
end
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
def get_metadata(*) # :nodoc:
|
|
109
|
+
super do
|
|
110
|
+
end
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
def get_permissions(*) # :nodoc:
|
|
114
|
+
super do
|
|
115
|
+
end
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
def chown(*)
|
|
119
|
+
super do
|
|
120
|
+
end
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
def set_permission(*)
|
|
124
|
+
super do
|
|
125
|
+
end
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
def unset_permission(*)
|
|
129
|
+
super do
|
|
130
|
+
end
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
def find_object(_) # :nodoc:
|
|
134
|
+
raise "#{@path} is not directory"
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
def directory(_) # :nodoc:
|
|
138
|
+
raise "#{@path} is not directory"
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
def file(_) # :nodoc:
|
|
142
|
+
raise "#{@path} is not directory"
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
def save
|
|
146
|
+
encoded_data = StoreAgent.config.storage_data_encoders.inject(@body) do |data, encoder|
|
|
147
|
+
encoder.encode(data)
|
|
148
|
+
end
|
|
149
|
+
open(storage_object_path, "w") do |f|
|
|
150
|
+
f.write encoded_data
|
|
151
|
+
end
|
|
152
|
+
end
|
|
153
|
+
|
|
154
|
+
def children # :nodoc:
|
|
155
|
+
[]
|
|
156
|
+
end
|
|
157
|
+
|
|
158
|
+
def file? # :nodoc:
|
|
159
|
+
true
|
|
160
|
+
end
|
|
161
|
+
|
|
162
|
+
private
|
|
163
|
+
|
|
164
|
+
def initial_bytesize
|
|
165
|
+
(@body || "").size
|
|
166
|
+
end
|
|
167
|
+
|
|
168
|
+
def set_body(*params)
|
|
169
|
+
case
|
|
170
|
+
when block_given?
|
|
171
|
+
yield self
|
|
172
|
+
when (options = params.first).is_a?(String)
|
|
173
|
+
@body = options
|
|
174
|
+
when options.is_a?(Symbol)
|
|
175
|
+
@body = options.to_s
|
|
176
|
+
when options.is_a?(Hash)
|
|
177
|
+
@body = options["body"] || options[:body]
|
|
178
|
+
else
|
|
179
|
+
@body = nil
|
|
180
|
+
end
|
|
181
|
+
end
|
|
182
|
+
|
|
183
|
+
def build_dest_file(dest_path)
|
|
184
|
+
dest_object = workspace.find_object(dest_path)
|
|
185
|
+
if dest_object.directory?
|
|
186
|
+
sub_directory_object = dest_object.find_object(File.basename(path))
|
|
187
|
+
if sub_directory_object.directory?
|
|
188
|
+
raise InvalidNodeTypeError.new(src_object: self, dest_object: sub_directory_object)
|
|
189
|
+
end
|
|
190
|
+
dest_object.file(File.basename(path))
|
|
191
|
+
else
|
|
192
|
+
workspace.file(dest_path)
|
|
193
|
+
end
|
|
194
|
+
end
|
|
195
|
+
end
|
|
196
|
+
end
|
|
197
|
+
end
|