isomorfeus-data 2.5.5 → 22.9.0.rc1

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 73175d7dc425cc54e235ee9719bacaaebe93b60753794949506cb044d05a5599
4
- data.tar.gz: 1af87ebf47618289399a3cdf7539e533577f09ae0757cae9e5f7b873e3f05b59
3
+ metadata.gz: 700fca91209424c5ecca86fe50ca2a5eec4edc824032dfd811bf354082b2d465
4
+ data.tar.gz: 3dcf6385c0894f78345aacb8872f83eeaed23409bddeaf0d3253afceabc60f23
5
5
  SHA512:
6
- metadata.gz: 4fe59f656951f87f9382f4dfd2b86b00ba835644e69b729d51cc1eef6dbec9ca13a05e683d655cb085e536d1f13d0e738c42b15309e4ba681102e1d54bbca5ad
7
- data.tar.gz: c2403b92cb3c88e1f38d05270afef98610694d8edd8cfca274735e4b456cfae2d2902db9512737dbd7316427dd7579d8f2275c691e64d6ac31e4c77affb725c4
6
+ metadata.gz: 5811fe438c1ecd96f1f41da3352273c9d96c1b851e95e80496baacd9d9a905101b05288127aeaaf2b0fd458fdb989afa7de6dff200aa88e47c895428fd9f70a5
7
+ data.tar.gz: b283f5c47616218aa5c688418ff7e444e03346286a4f1968b527dd35d7de4d04bf548f17e3669f491c33f984944cb46ef19b31e259391d3e364c41f1e47d564c
@@ -1,12 +1,9 @@
1
1
  module Isomorfeus
2
2
  # available settings
3
- class << self
4
3
  if RUBY_ENGINE == 'opal'
5
- def instance_from_sid(sid)
6
- data_class = cached_data_class(sid[0])
7
- data_class.new(key: sid[1], _loading: true)
8
- end
4
+ add_client_option(:api_files_path)
9
5
 
6
+ class << self
10
7
  def cached_data_classes
11
8
  @cached_data_classes ||= `{}`
12
9
  end
@@ -16,12 +13,9 @@ module Isomorfeus
16
13
  return cached_data_classes.JS[class_name] if cached_data_classes.JS[class_name]
17
14
  cached_data_classes.JS[class_name] = "::#{class_name}".constantize
18
15
  end
19
- else
20
- def instance_from_sid(sid)
21
- data_class = cached_data_class(sid[0])
22
- data_class.new(key: sid[1])
23
- end
24
-
16
+ end
17
+ else
18
+ class << self
25
19
  def cached_data_classes
26
20
  @cached_data_classes ||= {}
27
21
  end
@@ -44,18 +38,6 @@ module Isomorfeus
44
38
  valid_data_classes[raw_class_name(klass)] = true
45
39
  end
46
40
 
47
- def valid_file_classes
48
- @valid_file_classes ||= {}
49
- end
50
-
51
- def valid_file_class_name?(class_name)
52
- valid_file_classes.key?(class_name)
53
- end
54
-
55
- def add_valid_file_class(klass)
56
- valid_file_classes[raw_class_name(klass)] = true
57
- end
58
-
59
41
  attr_accessor :storage_path
60
42
  attr_accessor :files_path
61
43
 
@@ -64,10 +46,9 @@ module Isomorfeus
64
46
 
65
47
  attr_accessor :data_object_envs_path
66
48
  attr_accessor :data_object_idxs_path
49
+ attr_accessor :api_files_path
67
50
  end
68
- end
69
51
 
70
- if RUBY_ENGINE != 'opal'
71
52
  self.storage_path = File.expand_path(File.join(Isomorfeus.root, 'storage', Isomorfeus.env))
72
53
  self.files_path = File.expand_path(File.join(self.storage_path, 'files'))
73
54
 
@@ -75,4 +56,6 @@ module Isomorfeus
75
56
  self.ferret_path = File.expand_path(File.join(self.storage_path, 'ferret'))
76
57
  self.data_documents_path = File.expand_path(File.join(self.ferret_path, 'documents'))
77
58
  end
59
+
60
+ self.api_files_path = '/files/'
78
61
  end
@@ -0,0 +1,291 @@
1
+ module Isomorfeus
2
+ module Data
3
+ module Enhancer
4
+ if RUBY_ENGINE == 'opal'
5
+ OBJECT_ACTION_TYPES = %w[DATA_LOAD DATA_SAVE DATA_CREATE DATA_SEARCH DATA_DESTROY]
6
+ OBJECT_MERGE_TYPES = %w[DATA_SAVE DATA_CREATE]
7
+
8
+ CLIENT_OBJECT_ENHANCER = proc do |action, next_enh|
9
+ if OBJECT_ACTION_TYPES.include?(action[:type])
10
+ Isomorfeus.store.promise_action(action)
11
+ if OBJECT_MERGE_TYPES.include?(action[:type])
12
+ action = { type: 'DATA_MERGE', data: { action[:class_name] => { action[:key] => { fields: action[:fields], revision: action[:revision] }}}} # optimistic
13
+ end
14
+ end
15
+ next_enh.call(action)
16
+ end
17
+
18
+ FILE_ACTION_TYPES = %w[FILE_LOAD FILE_SAVE FILE_CREATE FILE_DESTROY]
19
+ FILE_MERGE_TYPES = %w[FILE_SAVE FILE_CREATE]
20
+
21
+ CLIENT_FILE_ENHANCER = proc do |action, next_enh|
22
+ if FILE_ACTION_TYPES.include?(action[:type])
23
+ Isomorfeus.store.promise_action(action)
24
+ if FILE_MERGE_TYPES.include?(action[:type])
25
+ action = { type: 'DATA_MERGE', data: { action[:class_name] => { action[:key] => { data_uri: action[:data_uri], revision: action[:revision] }}}} # optimistic
26
+ end
27
+ end
28
+ next_enh.call(action)
29
+ end
30
+
31
+ def self.add_enhancer_to_store
32
+ ::Redux::Store.add_middleware(CLIENT_OBJECT_ENHANCER)
33
+ ::Redux::Store.add_middleware(CLIENT_FILE_ENHANCER)
34
+ end
35
+ else # RUBY_ENGINE
36
+ SERVER_OBJECT_ENHANCER = proc do |action, next_enh|
37
+ action_type = action[:type]
38
+ begin
39
+ if action_type == 'DATA_LOAD'
40
+ item_class_name = action[:class_name]
41
+ item_key = action[:key]
42
+ item_class = get_verified_class(item_class_name)
43
+ authorization_error!(action) unless Isomorfeus.current_user.authorized?(item_class, :load, key: item_key)
44
+ sid_s = SID.new(item_class_name, item_key).to_s
45
+ loaded_data = item_class.object_accelerator.load(sid_s: sid_s) || {}
46
+ revision = loaded_data.delete(:_revision)
47
+ item_class.field_conditions.each do |field, options|
48
+ loaded_data.delete(field) if options[:server_only]
49
+ end
50
+ action = { type: 'DATA_MERGE', data: { item_class_name => { item_key => { fields: loaded_data, revision: revision }}}}
51
+ elsif action_type == 'DATA_SAVE'
52
+ item_class_name = action[:class_name]
53
+ item_key = action[:key]
54
+ item_class = get_verified_class(item_class_name)
55
+ item_fields = action[:fields]
56
+ item_fields.transform_keys!(&:to_sym)
57
+ authorization_error!(action) unless Isomorfeus.current_user.authorized?(item_class, :save, { key: item_key, fields: item_fields })
58
+ sid_s = SID.new(item_class_name, item_key).to_s
59
+ stored_revision = item_class.object_accelerator.revision(sid_s: sid_s)
60
+ if stored_revision == action[:revision]
61
+ revision = stored_revision + 1
62
+ instance_uuid = action[:instance_uuid]
63
+ if item_class.before_save_block
64
+ res = item_class.before_save_block.call(key: item_key, fields: item_fields)
65
+ if res.is_a?(Hash)
66
+ item_fields = res
67
+ item_fields.transform_keys!(&:to_sym)
68
+ convert_sids(item_class, item_fields)
69
+ end
70
+ end
71
+ if item_class.object_accelerator.save(sid_s: sid_s, fields: item_fields, revision: revision)
72
+ item_class.after_save_block.call(key: item_key, fields: item_fields) if item_class.after_save_block
73
+ else
74
+ instance_uuid = nil
75
+ end
76
+ else
77
+ revision = 0
78
+ instance_uuid = nil
79
+ end
80
+ loaded_data = item_class.object_accelerator.load(sid_s: sid_s)
81
+ revision = loaded_data.delete(:_revision)
82
+ item_class.field_conditions.each do |field, options|
83
+ loaded_data.delete(field) if options[:server_only]
84
+ end
85
+ action = { type: 'DATA_MERGE', data: { item_class_name => { item_key => { fields: loaded_data, revision: revision, instance_uuid: instance_uuid }}}}
86
+ elsif action_type == 'DATA_CREATE'
87
+ item_class_name = action[:class_name]
88
+ item_key = action[:key]
89
+ item_class = get_verified_class(item_class_name)
90
+ item_fields = action[:fields]
91
+ item_fields.transform_keys!(&:to_sym)
92
+ authorization_error!(action) unless Isomorfeus.current_user.authorized?(item_class, :create, { key: item_key, fields: item_fields })
93
+ sid_s = SID.new(item_class_name, item_key).to_s
94
+ stored_revision = item_class.object_accelerator.revision(sid_s: sid_s)
95
+ if stored_revision.nil?
96
+ revision = 1
97
+ instance_uuid = action[:instance_uuid]
98
+ if item_class.before_create_block
99
+ res = item_class.before_create_block.call(key: item_key, fields: item_fields)
100
+ if res.is_a?(Hash)
101
+ item_fields = res
102
+ item_fields.transform_keys!(&:to_sym)
103
+ convert_sids(item_class, item_fields)
104
+ end
105
+ end
106
+ if item_class.object_accelerator.create(sid_s: sid_s, fields: item_fields)
107
+ item_class.after_create_block.call(key: item_key, fields: item_fields) if item_class.after_create_block
108
+ else
109
+ stored_revision = 0
110
+ instance_uuid = nil
111
+ end
112
+ else
113
+ revision = stored_revision
114
+ instance_uuid = nil
115
+ end
116
+ loaded_data = item_class.object_accelerator.load(sid_s: sid_s)
117
+ revision = loaded_data.delete(:_revision)
118
+ item_class.field_conditions.each do |field, options|
119
+ loaded_data.delete(field) if options[:server_only]
120
+ end
121
+ action = { type: 'DATA_MERGE', data: { item_class_name => { item_key => { fields: loaded_data, revision: revision, instance_uuid: instance_uuid }}}}
122
+ elsif action_type == 'DATA_SEARCH'
123
+ item_class_name = action[:class_name]
124
+ item_class = get_verified_class(item_class_name)
125
+ query = action[:query].to_sym
126
+ params = action[:params]
127
+ raise "Invalid params #{params}!" unless params.nil? || params.is_a?(Hash)
128
+ authorization_error!(action) unless Isomorfeus.current_user.authorized?(item_class, :search, { query: query, params: params })
129
+ result = []
130
+ query_string = item_class.queries.dig(query, :query_string)
131
+ raise "Invalid query '#{query}'!" unless query_string
132
+ options = item_class.queries[query][:options] || {}
133
+ oa = item_class.object_accelerator
134
+ full_query = if params.is_a?(Hash)
135
+ if options[:escape_params]
136
+ escaped_params = params.to_h do |k,v|
137
+ [k, oa.escape(v)]
138
+ end
139
+ query_string % escaped_params
140
+ else
141
+ query_string % params
142
+ end
143
+ else
144
+ query_string
145
+ end
146
+ oa.search_each(full_query, options) do |id|
147
+ sid_s = oa.load_sid_s(id: id)
148
+ result << sid_s if sid_s
149
+ end
150
+ action = { type: 'DATA_MERGE', data: { queries: { item_class_name => { query => { Oj.dump(params, mode: :strict) => result }}}}}
151
+ elsif action_type == 'DATA_DESTROY'
152
+ item_class_name = action[:class_name]
153
+ item_key = action[:key]
154
+ item_class = get_verified_class(item_class_name)
155
+ authorization_error!(action) unless Isomorfeus.current_user.authorized?(item_class, :destroy, key: item_key)
156
+ item_class.before_destroy_block.call(key: item_key) if item_class.before_destroy_block
157
+ item_class.object_accelerator.destroy(sid_s: SID.new(item_class_name, item_key).to_s)
158
+ item_class.after_destroy_block.call(key: item_key) if item_class.after_destroy_block
159
+ action = { type: 'DATA_SET', class_name: item_class_name, key: item_key, data: { revision: -1 }}
160
+ end
161
+ rescue => e
162
+ STDERR.puts "#{e.message}\n#{e.backtrace&.join("\n")}"
163
+ # do nothing
164
+ end
165
+ next_enh.call(action)
166
+ end
167
+
168
+ SERVER_FILE_ENHANCER = proc do |action, next_enh|
169
+ action_type = action[:type]
170
+ begin
171
+ if action_type == 'FILE_LOAD'
172
+ item_class_name = action[:class_name]
173
+ item_key = action[:key]
174
+ item_class = get_verified_class(item_class_name)
175
+ authorization_error!(action) unless Isomorfeus.current_user.authorized?(item_class, :load, key: item_key)
176
+ sid = SID.new(item_class_name, item_key)
177
+ data, meta = item_class.file_accelerator.load(sid: sid)
178
+ item_hash = {}
179
+ if data
180
+ content_type = Marcel::MimeType.for(data)
181
+ data_uri = item_class.format_data_uri(content_type, data)
182
+ revision = meta[:revision]
183
+ item_hash = { data_uri: data_uri, revision: revision }
184
+ end
185
+ action = { type: 'DATA_MERGE', data: { item_class_name => { item_key => item_hash }}}
186
+ elsif action_type == 'FILE_SAVE'
187
+ item_class_name = action[:class_name]
188
+ item_key = action[:key]
189
+ item_class = get_verified_class(item_class_name)
190
+ data_uri = action[:data_uri]
191
+ authorization_error!(action) unless Isomorfeus.current_user.authorized?(item_class, :save, { key: item_key, data_uri: data_uri })
192
+ revision = action[:revision]
193
+ sid = SID.new(item_class_name, item_key)
194
+ meta = item_class.file_accelerator.meta(sid: sid)
195
+ stored_revision = meta[:revision]
196
+ if stored_revision == action[:revision]
197
+ revision = stored_revision + 1
198
+ instance_uuid = action[:instance_uuid]
199
+ if item_class.before_save_block
200
+ res = item_class.before_save_block.call(key: item_key, data_uri: data_uri)
201
+ data_uri = res[:data_uri] if res.is_a?(Hash) && res.key?(:data_uri)
202
+ end
203
+ if item_class.file_accelerator.save(sid: sid, data: URI::Data.new(data_uri).data, meta: { revision: revision })
204
+ item_class.after_save_block.call(key: item_key, data_uri: data_uri) if item_class.after_save_block
205
+ else
206
+ instance_uuid = nil
207
+ end
208
+ else
209
+ revision = 0
210
+ instance_uuid = nil
211
+ end
212
+ action = { type: 'DATA_MERGE', data: { item_class_name => { item_key => { data_uri: data_uri, revision: revision, instance_uuid: instance_uuid }}}}
213
+ elsif action_type == 'FILE_CREATE'
214
+ item_class_name = action[:class_name]
215
+ item_key = action[:key]
216
+ item_class = get_verified_class(item_class_name)
217
+ data_uri = action[:data_uri]
218
+ authorization_error!(action) unless Isomorfeus.current_user.authorized?(item_class, :create, { key: item_key, data_uri: data_uri })
219
+ sid = SID.new(item_class_name, item_key)
220
+ meta = item_class.file_accelerator.meta(sid: sid)
221
+ stored_revision = meta ? meta[:revision] : nil
222
+ if stored_revision.nil?
223
+ revision = 1
224
+ instance_uuid = action[:instance_uuid]
225
+ if item_class.before_create_block
226
+ res = item_class.before_create_block.call(key: item_key, data_uri: data_uri)
227
+ data_uri = res[:data_uri] if res.is_a?(Hash) && res.key?(:data_uri)
228
+ end
229
+ if item_class.file_accelerator.create(sid: sid, data: URI::Data.new(data_uri).data, meta: { revision: revision })
230
+ item_class.after_create_block.call(key: item_key, data_uri: data_uri) if item_class.after_create_block
231
+ else
232
+ stored_revision = 0
233
+ instance_uuid = nil
234
+ end
235
+ else
236
+ revision = stored_revision
237
+ instance_uuid = nil
238
+ end
239
+ action = { type: 'DATA_MERGE', data: { item_class_name => { item_key => { data_uri: data_uri, revision: revision, instance_uuid: instance_uuid }}}}
240
+ elsif action_type == 'FILE_DESTROY'
241
+ item_class_name = action[:class_name]
242
+ item_key = action[:key]
243
+ item_class = get_verified_class(item_class_name)
244
+ authorization_error!(action) unless Isomorfeus.current_user.authorized?(item_class, :destroy, key: item_key)
245
+ item_class.before_destroy_block.call(key: item_key) if item_class.before_destroy_block
246
+ item_class.file_accelerator.destroy(sid: SID.new(item_class_name, item_key))
247
+ item_class.after_destroy_block.call(key: item_key) if item_class.after_destroy_block
248
+ action = { type: 'DATA_SET', class_name: item_class_name, key: item_key, data: { revision: -1 }}
249
+ end
250
+ rescue => e
251
+ STDERR.puts "#{e.message}\n#{e.backtrace&.join("\n")}"
252
+ # do nothing
253
+ end
254
+ next_enh.call(action)
255
+ end
256
+
257
+ class << self
258
+ def convert_sids(item_class, item_fields)
259
+ item_class.field_types.each do |field, type|
260
+ if type == :object
261
+ field_s = field.to_s
262
+ v = item_fields[field_s]
263
+ if v.is_a?(SID)
264
+ item_fields[field_s] = v.to_s
265
+ elsif v.is_a?(Array)
266
+ item_fields[field_s].map! do |o|
267
+ o.is_a?(SID) ? o.to_s : o
268
+ end
269
+ end
270
+ end
271
+ end
272
+ end
273
+
274
+ def get_verified_class(item_class_name)
275
+ raise "Invalid data class!" unless Isomorfeus.valid_data_class_name?(item_class_name)
276
+ item_class = Isomorfeus.cached_data_class(item_class_name)
277
+ end
278
+
279
+ def authorization_error!(action)
280
+ raise "Not authorized!#{" (action: #{action})" if Isomorfeus.development?}"
281
+ end
282
+
283
+ def add_enhancer_to_store
284
+ ::Redux::Store.add_middleware(SERVER_OBJECT_ENHANCER)
285
+ ::Redux::Store.add_middleware(SERVER_FILE_ENHANCER)
286
+ end
287
+ end
288
+ end # RUBY_ENGINE
289
+ end
290
+ end
291
+ end
@@ -0,0 +1,95 @@
1
+ module Isomorfeus
2
+ module Data
3
+ class FileAccelerator
4
+ attr_reader :object_class, :object_class_name
5
+
6
+ def initialize(ruby_class)
7
+ @object_class = ruby_class
8
+ @object_class_name = ruby_class.name
9
+ @store_path = File.expand_path(File.join(Isomorfeus.files_path, "#{@object_class_name.underscore}"))
10
+ FileUtils.mkdir_p(@store_path) unless Dir.exist?(@store_path)
11
+ end
12
+
13
+ def serialize(o)
14
+ Oj.dump(o, mode: :strict)
15
+ end
16
+
17
+ def unserialize(s)
18
+ Oj.load(s, mode: :strict)
19
+ end
20
+
21
+ def destroy_store
22
+ close_store
23
+ FileUtils.rm_rf(@store_path)
24
+ end
25
+
26
+ def meta(sid:)
27
+ _path, meta_path = check_and_prepare_paths(key: sid.key)
28
+ if File.exist?(meta_path)
29
+ meta = unserialize(File.binread(meta_path))
30
+ meta.transform_keys!(&:to_sym)
31
+ end
32
+ end
33
+
34
+ def exist?(sid:)
35
+ path, meta_path = check_and_prepare_paths(key: sid.key)
36
+ File.exist?(path)
37
+ end
38
+
39
+ def create(sid:, data:, meta:)
40
+ path, meta_path = check_and_prepare_paths(key: sid.key)
41
+ File.binwrite(path, data)
42
+ File.binwrite(meta_path, serialize(meta))
43
+ true
44
+ end
45
+ alias save create
46
+
47
+ def destroy(sid:)
48
+ path, meta_path = check_and_prepare_paths(key: sid.key)
49
+ FileUtils.rm_f([path, meta_path])
50
+ true
51
+ end
52
+
53
+ def load(sid:)
54
+ path, meta_path = check_and_prepare_paths(key: sid.key)
55
+ begin
56
+ if File.exist?(meta_path)
57
+ meta = unserialize(File.binread(meta_path))
58
+ meta.transform_keys!(&:to_sym)
59
+ else
60
+ meta = {}
61
+ end
62
+ [File.binread(path), meta]
63
+ rescue Errno::ENOENT
64
+ [nil, nil]
65
+ end
66
+ end
67
+
68
+ def load_data(sid:)
69
+ path, _meta_path = check_and_prepare_paths(key: sid.key)
70
+ begin
71
+ File.binread(path)
72
+ rescue Errno::ENOENT
73
+ nil
74
+ end
75
+ end
76
+
77
+ private
78
+
79
+ def check_and_prepare_paths(key:)
80
+ Isomorfeus.raise_error(message: 'Invalid key (contains ".." or "\\")') if key.include?('..') || key.include?('\\')
81
+ elements = key.split('/')
82
+ Isomorfeus.raise_error(message: 'Invalid key (contains more than 2 slashes "/")') if elements.size > 3
83
+ file = elements.pop
84
+ path = if elements.size > 0
85
+ dir_path = ::File.expand_path(::File.join(@store_path, *elements))
86
+ FileUtils.mkdir_p(dir_path) unless Dir.exist?(dir_path)
87
+ File.join(dir_path, file)
88
+ else
89
+ File.join(@store_path, file)
90
+ end
91
+ [path, "#{path}__meta__"]
92
+ end
93
+ end
94
+ end
95
+ end