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.
Files changed (52) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +14 -0
  3. data/.rspec +3 -0
  4. data/Gemfile +4 -0
  5. data/Guardfile +5 -0
  6. data/LICENSE +202 -0
  7. data/README.md +433 -0
  8. data/Rakefile +2 -0
  9. data/lib/store_agent.rb +31 -0
  10. data/lib/store_agent/config.rb +152 -0
  11. data/lib/store_agent/data_encoder.rb +32 -0
  12. data/lib/store_agent/data_encoder/gzip_encoder.rb +50 -0
  13. data/lib/store_agent/data_encoder/openssl_aes_256_cbc_encoder.rb +65 -0
  14. data/lib/store_agent/exceptions.rb +87 -0
  15. data/lib/store_agent/node.rb +26 -0
  16. data/lib/store_agent/node/attachment.rb +93 -0
  17. data/lib/store_agent/node/attachment/metadata.rb +120 -0
  18. data/lib/store_agent/node/attachment/permission.rb +121 -0
  19. data/lib/store_agent/node/object.rb +233 -0
  20. data/lib/store_agent/node/object/directory_object.rb +264 -0
  21. data/lib/store_agent/node/object/file_object.rb +197 -0
  22. data/lib/store_agent/node/object/virtual_object.rb +25 -0
  23. data/lib/store_agent/node/prepend_module/locker.rb +125 -0
  24. data/lib/store_agent/node/prepend_module/path_validator.rb +138 -0
  25. data/lib/store_agent/node/prepend_module/permission_checker.rb +96 -0
  26. data/lib/store_agent/user.rb +111 -0
  27. data/lib/store_agent/validator.rb +60 -0
  28. data/lib/store_agent/version.rb +19 -0
  29. data/lib/store_agent/version_manager.rb +101 -0
  30. data/lib/store_agent/version_manager/ruby_git.rb +100 -0
  31. data/lib/store_agent/version_manager/rugged_git.rb +133 -0
  32. data/lib/store_agent/workspace.rb +88 -0
  33. data/spec/spec_helper.rb +47 -0
  34. data/spec/store_agent/data_encoder/encoder_shared_context.rb +74 -0
  35. data/spec/store_agent/data_encoder/gzip_encoder_spec.rb +41 -0
  36. data/spec/store_agent/data_encoder/openssl_aes_256_cbc_encoder_spec.rb +42 -0
  37. data/spec/store_agent/data_encoder_spec.rb +78 -0
  38. data/spec/store_agent/node/directory_object_spec.rb +563 -0
  39. data/spec/store_agent/node/file_object_spec.rb +379 -0
  40. data/spec/store_agent/node/locker_spec.rb +191 -0
  41. data/spec/store_agent/node/metadata_spec.rb +339 -0
  42. data/spec/store_agent/node/object_spec.rb +73 -0
  43. data/spec/store_agent/node/path_validator_spec.rb +121 -0
  44. data/spec/store_agent/node/permission_spec.rb +232 -0
  45. data/spec/store_agent/user_spec.rb +127 -0
  46. data/spec/store_agent/version_manager/git_shared_context.rb +286 -0
  47. data/spec/store_agent/version_manager/ruby_git_spec.rb +32 -0
  48. data/spec/store_agent/version_manager/rugged_git_spec.rb +32 -0
  49. data/spec/store_agent/workspace_spec.rb +107 -0
  50. data/spec/store_agent_spec.rb +53 -0
  51. data/store_agent.gemspec +33 -0
  52. metadata +252 -0
@@ -0,0 +1,93 @@
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
+ # メタデータや権限情報など、オブジェクトに付属する情報<br>
20
+ # データはJSON形式のファイルで保存され、ハッシュ形式のデータとしてアクセスできる
21
+ class Attachment
22
+ extend Forwardable
23
+ include StoreAgent::Validator
24
+
25
+ attr_reader :object
26
+ def_delegators :object, *%w(current_user root? directory?)
27
+
28
+ def initialize(object: nil) # :nodoc:
29
+ @object = object
30
+ validates_to_be_not_nil_value!(:object)
31
+ end
32
+
33
+ # ハッシュ形式のデータにアクセスするためのメソッド
34
+ def data
35
+ @data ||= (load || initial_data)
36
+ end
37
+
38
+ # オブジェクトの作成時に一緒に作成される
39
+ def create
40
+ dirname = File.dirname(file_path)
41
+ if !File.exists?(dirname)
42
+ FileUtils.mkdir(dirname)
43
+ end
44
+ save
45
+ end
46
+
47
+ # オブジェクトの削除時に一緒に削除される
48
+ def delete
49
+ if object.directory?
50
+ FileUtils.remove_dir(File.dirname(file_path))
51
+ else
52
+ FileUtils.rm(file_path)
53
+ end
54
+ object.workspace.version_manager.remove(file_path)
55
+ end
56
+
57
+ # データをファイルに保存するメソッド
58
+ def save
59
+ json_data = Oj.dump(data, mode: :compat, indent: StoreAgent.config.json_indent_level)
60
+ encoded_data = StoreAgent.config.attachment_data_encoders.inject(json_data) do |data, encoder|
61
+ encoder.encode(data)
62
+ end
63
+ open(file_path, File::WRONLY | File::CREAT) do |f|
64
+ f.truncate(0)
65
+ f.write encoded_data
66
+ end
67
+ object.workspace.version_manager.add(file_path)
68
+ reload
69
+ end
70
+
71
+ # データをファイルから読み込むメソッド
72
+ def load
73
+ if File.exists?(file_path)
74
+ encoded_data = open(file_path, "rb").read
75
+ json_data = StoreAgent.config.attachment_data_encoders.reverse.inject(encoded_data) do |data, encoder|
76
+ encoder.decode(data)
77
+ end
78
+ Oj.load(json_data)
79
+ end
80
+ end
81
+
82
+ # 保存されていない変更を破棄する
83
+ def reload
84
+ @data = nil
85
+ self
86
+ end
87
+
88
+ def inspect # :nodoc:
89
+ Oj.dump(data, mode: :compat, indent: 2)
90
+ end
91
+ end
92
+ end
93
+ end
@@ -0,0 +1,120 @@
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 Metadata < Attachment
21
+ def_delegators :data, *%w([] []=)
22
+
23
+ # TODO check disk_usage before save
24
+
25
+ def create
26
+ super
27
+ parent.update(disk_usage: disk_usage, directory_file_count: 1, tree_file_count: 1, recursive: true)
28
+ end
29
+
30
+ def update(disk_usage: 0, directory_file_count: 0, tree_file_count: 0, recursive: false)
31
+ if directory?
32
+ self["directory_file_count"] += directory_file_count
33
+ self["tree_file_count"] += tree_file_count
34
+ end
35
+ self.disk_usage += disk_usage
36
+ save
37
+ if recursive
38
+ parent.update(disk_usage: disk_usage, tree_file_count: tree_file_count, recursive: recursive)
39
+ end
40
+ end
41
+
42
+ def delete
43
+ parent.update(disk_usage: -disk_usage, directory_file_count: -1, tree_file_count: -1, recursive: true)
44
+ super
45
+ end
46
+
47
+ def base_path # :nodoc:
48
+ "#{@object.workspace.metadata_dirname}#{@object.path}"
49
+ end
50
+
51
+ # オブジェクトのメタデータを保存しているファイルの絶対パス
52
+ def file_path
53
+ "#{base_path}#{StoreAgent.config.metadata_extension}"
54
+ end
55
+
56
+ # ディスク使用量をバイトからキロバイトなどの単位に変換するメソッド
57
+ def self.datasize_format(size)
58
+ byte_names = %w(KB MB GB TB PB)
59
+ byte_length = size.abs.to_s(2).length
60
+ if byte_length <= 10
61
+ "#{size} bytes"
62
+ else
63
+ exponent = [byte_names.length, (byte_length - 1) / 10].min
64
+ sprintf("%0.2f%s", size.to_f / (2 ** (10 * exponent)), byte_names[exponent - 1])
65
+ end
66
+ end
67
+
68
+ def disk_usage
69
+ if directory?
70
+ self["directory_bytes"]
71
+ else
72
+ self["bytes"]
73
+ end
74
+ end
75
+
76
+ def disk_usage=(usage)
77
+ usage_string = StoreAgent::Node::Metadata.datasize_format(usage)
78
+ if directory?
79
+ self["directory_size"] = usage_string
80
+ self["directory_bytes"] = usage
81
+ else
82
+ self["size"] = usage_string
83
+ self["bytes"] = usage
84
+ end
85
+ end
86
+
87
+ def owner=(identifier)
88
+ self["owner"] = identifier
89
+ end
90
+
91
+ def updated_at=(time)
92
+ self["updated_at"] = time.to_s
93
+ self["updated_at_unix_timestamp"] = time.to_i
94
+ end
95
+
96
+ private
97
+
98
+ def parent
99
+ if root?
100
+ SuperRootMetadata.new
101
+ else
102
+ object.parent_directory.metadata
103
+ end
104
+ end
105
+
106
+ def initial_data
107
+ object.default_metadata
108
+ end
109
+
110
+ # 最上位階層の親ディレクトリのメタデータとして振る舞うダミーのクラス
111
+ class SuperRootMetadata < Metadata # :nodoc:
112
+ def initialize(*)
113
+ end
114
+
115
+ def update(*)
116
+ end
117
+ end
118
+ end
119
+ end
120
+ end
@@ -0,0 +1,121 @@
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 Permission < Attachment
21
+ # オブジェクトに対して、引数で受け取った権限を持つなら true を返す
22
+ def allow?(permission_name)
23
+ case
24
+ when current_user.super_user?
25
+ true
26
+ when !(permission_value = get_permission_value(permission_name)).nil?
27
+ permission_value
28
+ else
29
+ !!data["guest"][permission_name]
30
+ end
31
+ end
32
+
33
+ # 権限を設定する
34
+ def set!(identifier: nil, permission_values: {})
35
+ return if permission_values.empty?
36
+ user_permission = [identifier].flatten.inject(data["users"]) do |r, id|
37
+ r[id] ||= {}
38
+ end
39
+ permission_values.each do |permission_name, value|
40
+ user_permission[permission_name] = value
41
+ end
42
+ save
43
+ end
44
+
45
+ # 権限を解除する
46
+ def unset!(identifier: nil, permission_names: [])
47
+ identifiers = [identifier].flatten
48
+ permission_names = [permission_names].flatten
49
+ user_permission = find_permission(data["users"], identifiers)
50
+ if user_permission
51
+ user_permission.delete_if do |permission_name, _|
52
+ permission_names.include?(permission_name)
53
+ end
54
+ sweep_permission(data["users"], identifiers)
55
+ save
56
+ end
57
+ end
58
+
59
+ def base_path # :nodoc:
60
+ "#{@object.workspace.permission_dirname}#{@object.path}"
61
+ end
62
+
63
+ # オブジェクトの権限情報を保存しているファイルの絶対パス
64
+ def file_path
65
+ "#{base_path}#{StoreAgent.config.permission_extension}"
66
+ end
67
+
68
+ private
69
+
70
+ def find_permission(data, identifiers)
71
+ identifier = identifiers.first
72
+ next_data = data[identifier]
73
+ next_identifiers = identifiers[1..-1]
74
+ case
75
+ when next_identifiers.empty?
76
+ next_data
77
+ when next_data
78
+ find_permission(next_data, next_identifiers)
79
+ else
80
+ nil
81
+ end
82
+ end
83
+
84
+ def sweep_permission(data, identifiers)
85
+ identifier = identifiers.first
86
+ next_data = data[identifier]
87
+ if next_data
88
+ sweep_permission(next_data, identifiers[1..-1])
89
+ if next_data.empty?
90
+ data.delete(identifier)
91
+ end
92
+ end
93
+ end
94
+
95
+ def get_permission_value(permission_name)
96
+ current_user.identifiers.reverse.each do |identifier|
97
+ user_permission = [identifier].flatten.inject(data["users"]) do |r, id|
98
+ r[id] || break
99
+ end
100
+ if user_permission && user_permission.key?(permission_name)
101
+ return user_permission[permission_name]
102
+ end
103
+ end
104
+ nil
105
+ end
106
+
107
+ def initial_data
108
+ user_permission = {}
109
+ if !(current_user.super_user? || current_user.guest?)
110
+ user_permission = current_user.identifier_array.reverse.inject(@object.initial_permission) do |r, id|
111
+ {id => r}
112
+ end
113
+ end
114
+ {
115
+ "users" => user_permission,
116
+ "guest" => StoreAgent.config.default_guest_permission
117
+ }
118
+ end
119
+ end
120
+ end
121
+ end
@@ -0,0 +1,233 @@
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
+ # ファイルやディレクトリなど、オブジェクトの雛形となるクラス<br>
20
+ # 実際にはファイルやディレクトリなど、このクラスを継承したクラスを使用する
21
+ class Object
22
+ extend Forwardable
23
+ include StoreAgent::Validator
24
+ prepend *[] <<
25
+ StoreAgent::Node::PathValidator <<
26
+ StoreAgent::Node::PermissionChecker <<
27
+ StoreAgent::Node::Locker
28
+
29
+ # def self.inherited(child)
30
+ # child.include StoreAgent::Node::Locker
31
+ # child.include StoreAgent::Node::PermissionChecker
32
+ # child.include StoreAgent::Node::PathValidator
33
+ # end
34
+
35
+ attr_reader :workspace, :path
36
+ def_delegators :workspace, *%w(current_user)
37
+
38
+ def initialize(workspace: nil, path: "/") # :nodoc:
39
+ @workspace = workspace
40
+ validates_to_be_not_nil_value!(:workspace)
41
+ @path = sanitize_path(path)
42
+ end
43
+
44
+ # オブジェクトに紐づくメタデータのインスタンス
45
+ def metadata
46
+ @metadata ||= StoreAgent::Node::Metadata.new(object: self)
47
+ end
48
+
49
+ # オブジェクトに紐づく権限情報のインスタンス
50
+ def permission
51
+ @permission ||= StoreAgent::Node::Permission.new(object: self)
52
+ end
53
+
54
+ def initial_owner # :nodoc:
55
+ @initial_owner ||= current_user.identifier
56
+ end
57
+
58
+ def initial_owner=(owner_identifier) # :nodoc:
59
+ authorize!("chown")
60
+ @initial_owner = owner_identifier
61
+ end
62
+
63
+ def initial_permission # :nodoc:
64
+ @initial_permission ||= StoreAgent.config.default_owner_permission
65
+ end
66
+
67
+ def initial_permission=(permissions) # :nodoc:
68
+ authorize!("chmod")
69
+ @initial_permission = permissions
70
+ end
71
+
72
+ def create(*)
73
+ workspace.version_manager.transaction("created #{path}") do
74
+ yield
75
+ metadata.create
76
+ permission.create
77
+ end
78
+ self
79
+ end
80
+
81
+ def read(*)
82
+ yield
83
+ end
84
+
85
+ def update(*)
86
+ workspace.version_manager.transaction("updated #{path}") do
87
+ yield
88
+ end
89
+ true
90
+ end
91
+
92
+ def delete(*)
93
+ workspace.version_manager.transaction("deleted #{path}") do
94
+ yield
95
+ metadata.delete
96
+ permission.delete
97
+ end
98
+ true
99
+ end
100
+
101
+ def touch(*)
102
+ workspace.version_manager.transaction("touch #{path}") do
103
+ metadata.reload
104
+ yield
105
+ metadata.updated_at = Time.now
106
+ metadata.save
107
+ workspace.version_manager.add(permission.file_path)
108
+ end
109
+ end
110
+
111
+ def copy(dest_path = nil)
112
+ workspace.version_manager.transaction("copy #{path} to #{dest_path}") do
113
+ yield
114
+ end
115
+ end
116
+
117
+ def move(dest_path = nil)
118
+ workspace.version_manager.transaction("move #{path} to #{dest_path}") do
119
+ yield
120
+ end
121
+ end
122
+
123
+ def get_metadata(*) # :nodoc:
124
+ yield
125
+ metadata.data
126
+ end
127
+
128
+ def get_permissions(*) # :nodoc:
129
+ yield
130
+ permission.data
131
+ end
132
+
133
+ def chown(*, identifier: nil, **_)
134
+ workspace.version_manager.transaction("change_owner #{path}") do
135
+ yield
136
+ metadata.owner = identifier
137
+ metadata.save
138
+ end
139
+ end
140
+
141
+ def set_permission(identifier: nil, permission_values: {}, **_)
142
+ workspace.version_manager.transaction("add_permission #{path}") do
143
+ permission.set!(identifier: identifier, permission_values: permission_values)
144
+ yield
145
+ end
146
+ end
147
+
148
+ def unset_permission(identifier: nil, permission_names: [], **_)
149
+ workspace.version_manager.transaction("remove_permission #{path}") do
150
+ permission.unset!(identifier: identifier, permission_names: permission_names)
151
+ yield
152
+ end
153
+ end
154
+
155
+ # 親階層のディレクトリオブジェクトを返す
156
+ def parent_directory
157
+ if !root?
158
+ @parent_directory ||= StoreAgent::Node::DirectoryObject.new(workspace: @workspace, path: File.dirname(@path))
159
+ end
160
+ end
161
+
162
+ # バージョン管理をしている場合、変更があったリビジョンの一覧を返す
163
+ def revisions
164
+ workspace.version_manager.revisions("storage#{path}")
165
+ end
166
+
167
+ # オブジェクトが存在するなら true を返す
168
+ def exists?
169
+ File.exists?(storage_object_path)
170
+ end
171
+
172
+ # ファイルの種類。file、directory など
173
+ def filetype
174
+ File.ftype(storage_object_path)
175
+ end
176
+
177
+ def initial_metadata # :nodoc:
178
+ @initial_metadata ||= {}
179
+ end
180
+
181
+ def default_metadata # :nodoc:
182
+ {
183
+ "size" => StoreAgent::Node::Metadata.datasize_format(initial_bytesize),
184
+ "bytes" => initial_bytesize,
185
+ "owner" => initial_owner,
186
+ "is_dir" => directory?,
187
+ "created_at" => updated_at.to_s,
188
+ "updated_at" => updated_at.to_s,
189
+ "created_at_unix_timestamp" => updated_at.to_i,
190
+ "updated_at_unix_timestamp" => updated_at.to_i,
191
+ }.merge(initial_metadata)
192
+ end
193
+
194
+ # true を返す場合、ファイルツリーの最上位ディレクトリとして認識される
195
+ def root?
196
+ @path == "/"
197
+ end
198
+
199
+ # true を返す場合、ディレクトリとして認識される
200
+ def directory?
201
+ false
202
+ end
203
+
204
+ # true を返す場合、ファイルとして認識される
205
+ def file?
206
+ false
207
+ end
208
+
209
+ # オブジェクトの絶対パス
210
+ def storage_object_path
211
+ "#{@workspace.storage_dirname}#{@path}"
212
+ end
213
+
214
+ private
215
+
216
+ def sanitize_path(path)
217
+ File.absolute_path("/./#{path}")
218
+ end
219
+
220
+ def owner?
221
+ metadata["owner"] == current_user.identifier
222
+ end
223
+
224
+ def initial_bytesize
225
+ 0
226
+ end
227
+
228
+ def updated_at
229
+ File.mtime(storage_object_path)
230
+ end
231
+ end
232
+ end
233
+ end