cosmos 5.0.2.pre.beta2 → 5.0.4
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 +4 -4
- data/bin/cosmos +1 -1
- data/data/config/microservice.yaml +47 -35
- data/data/config/plugins.yaml +3 -150
- data/data/config/target.yaml +70 -0
- data/data/config/tool.yaml +37 -31
- data/lib/cosmos/api/api.rb +1 -25
- data/lib/cosmos/api/cmd_api.rb +17 -6
- data/lib/cosmos/api/config_api.rb +10 -4
- data/lib/cosmos/api/limits_api.rb +1 -1
- data/lib/cosmos/api/settings_api.rb +19 -7
- data/lib/cosmos/api/target_api.rb +2 -2
- data/lib/cosmos/api/tlm_api.rb +69 -41
- data/lib/cosmos/config/config_parser.rb +19 -22
- data/lib/cosmos/config/meta_config_parser.rb +1 -1
- data/lib/cosmos/conversions/generic_conversion.rb +2 -2
- data/lib/cosmos/conversions/polynomial_conversion.rb +5 -8
- data/lib/cosmos/conversions/segmented_polynomial_conversion.rb +26 -9
- data/lib/cosmos/io/json_drb.rb +5 -1
- data/lib/cosmos/logs/log_writer.rb +2 -2
- data/lib/cosmos/microservices/cleanup_microservice.rb +28 -29
- data/lib/cosmos/microservices/decom_microservice.rb +1 -1
- data/lib/cosmos/microservices/interface_microservice.rb +0 -1
- data/lib/cosmos/microservices/microservice.rb +3 -3
- data/lib/cosmos/microservices/reducer_microservice.rb +12 -10
- data/lib/cosmos/models/cvt_model.rb +6 -6
- data/lib/cosmos/models/gem_model.rb +3 -3
- data/lib/cosmos/models/info_model.rb +1 -1
- data/lib/cosmos/models/interface_status_model.rb +1 -1
- data/lib/cosmos/models/metadata_model.rb +42 -216
- data/lib/cosmos/models/metric_model.rb +2 -2
- data/lib/cosmos/models/microservice_model.rb +1 -1
- data/lib/cosmos/models/microservice_status_model.rb +1 -1
- data/lib/cosmos/models/model.rb +16 -16
- data/lib/cosmos/models/note_model.rb +124 -0
- data/lib/cosmos/models/ping_model.rb +2 -1
- data/lib/cosmos/models/plugin_model.rb +1 -1
- data/lib/cosmos/models/process_status_model.rb +1 -1
- data/lib/cosmos/models/scope_model.rb +9 -26
- data/lib/cosmos/models/settings_model.rb +55 -0
- data/lib/cosmos/models/sorted_model.rb +165 -0
- data/lib/cosmos/models/target_model.rb +120 -13
- data/lib/cosmos/models/tool_config_model.rb +38 -0
- data/lib/cosmos/models/tool_model.rb +1 -1
- data/lib/cosmos/models/widget_model.rb +1 -1
- data/lib/cosmos/operators/microservice_operator.rb +2 -1
- data/lib/cosmos/packets/packet.rb +23 -0
- data/lib/cosmos/packets/packet_config.rb +2 -2
- data/lib/cosmos/packets/packet_item.rb +57 -0
- data/lib/cosmos/packets/packet_item_limits.rb +14 -2
- data/lib/cosmos/packets/parsers/packet_item_parser.rb +1 -1
- data/lib/cosmos/packets/parsers/packet_parser.rb +1 -1
- data/lib/cosmos/packets/parsers/xtce_parser.rb +1 -1
- data/lib/cosmos/packets/structure_item.rb +10 -1
- data/lib/cosmos/script/api_shared.rb +30 -25
- data/lib/cosmos/script/calendar.rb +26 -15
- data/lib/cosmos/script/commands.rb +5 -7
- data/lib/cosmos/script/script.rb +19 -39
- data/lib/cosmos/script/storage.rb +92 -105
- data/lib/cosmos/system/system.rb +2 -1
- data/lib/cosmos/tools/table_manager/table_item.rb +1 -1
- data/lib/cosmos/top_level.rb +5 -1
- data/lib/cosmos/topics/autonomic_topic.rb +2 -2
- data/lib/cosmos/topics/calendar_topic.rb +1 -1
- data/lib/cosmos/topics/command_decom_topic.rb +35 -1
- data/lib/cosmos/topics/command_topic.rb +6 -4
- data/lib/cosmos/topics/interface_topic.rb +8 -8
- data/lib/cosmos/topics/limits_event_topic.rb +5 -3
- data/lib/cosmos/topics/notifications_topic.rb +1 -1
- data/lib/cosmos/topics/router_topic.rb +9 -9
- data/lib/cosmos/topics/telemetry_decom_topic.rb +5 -1
- data/lib/cosmos/topics/telemetry_topic.rb +1 -1
- data/lib/cosmos/topics/timeline_topic.rb +1 -1
- data/lib/cosmos/topics/topic.rb +23 -8
- data/lib/cosmos/utilities/logger.rb +4 -3
- data/lib/cosmos/utilities/metric.rb +32 -26
- data/lib/cosmos/utilities/s3.rb +61 -0
- data/lib/cosmos/utilities/s3_file_cache.rb +12 -6
- data/lib/cosmos/utilities/store.rb +1 -0
- data/lib/cosmos/utilities/store_autoload.rb +25 -134
- data/lib/cosmos/version.rb +6 -5
- data/templates/plugin-template/plugin.gemspec +0 -2
- metadata +9 -6
- data/lib/cosmos/models/narrative_model.rb +0 -280
@@ -31,7 +31,7 @@ end
|
|
31
31
|
module Cosmos
|
32
32
|
class Store
|
33
33
|
# Variable that holds the singleton instance
|
34
|
-
|
34
|
+
@instance = nil
|
35
35
|
|
36
36
|
# Mutex used to ensure that only one instance is created
|
37
37
|
@@instance_mutex = Mutex.new
|
@@ -42,11 +42,11 @@ module Cosmos
|
|
42
42
|
# Get the singleton instance
|
43
43
|
def self.instance(pool_size = 100)
|
44
44
|
# Logger.level = Logger::DEBUG
|
45
|
-
return
|
45
|
+
return @instance if @instance
|
46
46
|
|
47
47
|
@@instance_mutex.synchronize do
|
48
|
-
|
49
|
-
return
|
48
|
+
@instance ||= self.new(pool_size)
|
49
|
+
return @instance
|
50
50
|
end
|
51
51
|
end
|
52
52
|
|
@@ -87,44 +87,10 @@ module Cosmos
|
|
87
87
|
end
|
88
88
|
end
|
89
89
|
|
90
|
-
def get_cmd_item(target_name, packet_name, param_name, type: :WITH_UNITS, scope: $cosmos_scope)
|
91
|
-
msg_id, msg_hash = read_topic_last("#{scope}__DECOMCMD__{#{target_name}}__#{packet_name}")
|
92
|
-
if msg_id
|
93
|
-
# TODO: We now have these reserved items directly on command packets
|
94
|
-
# Do we still calculate from msg_hash['time'] or use the times directly?
|
95
|
-
#
|
96
|
-
# if param_name == 'RECEIVED_TIMESECONDS' || param_name == 'PACKET_TIMESECONDS'
|
97
|
-
# Time.from_nsec_from_epoch(msg_hash['time'].to_i).to_f
|
98
|
-
# elsif param_name == 'RECEIVED_TIMEFORMATTED' || param_name == 'PACKET_TIMEFORMATTED'
|
99
|
-
# Time.from_nsec_from_epoch(msg_hash['time'].to_i).formatted
|
100
|
-
if param_name == 'RECEIVED_COUNT'
|
101
|
-
msg_hash['received_count'].to_i
|
102
|
-
else
|
103
|
-
json = msg_hash['json_data']
|
104
|
-
hash = JSON.parse(json)
|
105
|
-
# Start from the most complex down to the basic raw value
|
106
|
-
value = hash["#{param_name}__U"]
|
107
|
-
return value if value && type == :WITH_UNITS
|
108
|
-
|
109
|
-
value = hash["#{param_name}__F"]
|
110
|
-
return value if value && (type == :WITH_UNITS || type == :FORMATTED)
|
111
|
-
|
112
|
-
value = hash["#{param_name}__C"]
|
113
|
-
return value if value && (type == :WITH_UNITS || type == :FORMATTED || type == :CONVERTED)
|
114
|
-
|
115
|
-
return hash[param_name]
|
116
|
-
end
|
117
|
-
end
|
118
|
-
end
|
119
|
-
|
120
90
|
###########################################################################
|
121
91
|
# Stream APIs
|
122
92
|
###########################################################################
|
123
93
|
|
124
|
-
def self.initialize_streams(topics)
|
125
|
-
self.instance.initialize_streams(topics)
|
126
|
-
end
|
127
|
-
|
128
94
|
def initialize_streams(topics)
|
129
95
|
@redis_pool.with do |redis|
|
130
96
|
topics.each do |topic|
|
@@ -134,48 +100,18 @@ module Cosmos
|
|
134
100
|
end
|
135
101
|
end
|
136
102
|
|
137
|
-
def self.get_oldest_message(topic)
|
138
|
-
self.instance.get_oldest_message(topic)
|
139
|
-
end
|
140
|
-
|
141
103
|
def get_oldest_message(topic)
|
142
104
|
@redis_pool.with do |redis|
|
143
105
|
result = redis.xrange(topic, count: 1)
|
144
|
-
|
145
|
-
|
146
|
-
end
|
147
|
-
|
148
|
-
def self.get_newest_message(topic)
|
149
|
-
self.instance.get_newest_message(topic)
|
150
|
-
end
|
151
|
-
|
152
|
-
def get_newest_message(topic)
|
153
|
-
@redis_pool.with do |redis|
|
154
|
-
result = redis.xrevrange(topic, count: 1)
|
155
|
-
return result[0]
|
156
|
-
end
|
157
|
-
end
|
158
|
-
|
159
|
-
def self.get_last_offset(topic)
|
160
|
-
self.instance.get_last_offset(topic)
|
161
|
-
end
|
162
|
-
|
163
|
-
def get_last_offset(topic)
|
164
|
-
@redis_pool.with do |redis|
|
165
|
-
result = redis.xrevrange(topic, count: 1)
|
166
|
-
if result and result[0] and result[0][0]
|
167
|
-
result[0][0]
|
106
|
+
if result and result.length > 0
|
107
|
+
return result[0]
|
168
108
|
else
|
169
|
-
|
109
|
+
return nil
|
170
110
|
end
|
171
111
|
end
|
172
112
|
end
|
173
113
|
|
174
|
-
def
|
175
|
-
self.instance.read_topic_last(topic)
|
176
|
-
end
|
177
|
-
|
178
|
-
def read_topic_last(topic)
|
114
|
+
def get_newest_message(topic)
|
179
115
|
@redis_pool.with do |redis|
|
180
116
|
# Default in xrevrange is range end '+', start '-' which means get all
|
181
117
|
# elements from higher ID to lower ID and since we're limiting to 1
|
@@ -189,18 +125,15 @@ module Cosmos
|
|
189
125
|
end
|
190
126
|
end
|
191
127
|
|
192
|
-
|
193
|
-
|
194
|
-
|
195
|
-
|
196
|
-
|
197
|
-
|
198
|
-
|
199
|
-
|
200
|
-
|
201
|
-
|
202
|
-
def self.update_topic_offsets(topics)
|
203
|
-
self.instance.update_topic_offsets(topics)
|
128
|
+
def get_last_offset(topic)
|
129
|
+
@redis_pool.with do |redis|
|
130
|
+
result = redis.xrevrange(topic, count: 1)
|
131
|
+
if result and result[0] and result[0][0]
|
132
|
+
result[0][0]
|
133
|
+
else
|
134
|
+
"0-0"
|
135
|
+
end
|
136
|
+
end
|
204
137
|
end
|
205
138
|
|
206
139
|
def update_topic_offsets(topics)
|
@@ -221,15 +154,12 @@ module Cosmos
|
|
221
154
|
return offsets
|
222
155
|
end
|
223
156
|
|
224
|
-
def self.read_topics(topics, offsets = nil, timeout_ms = 1000, &block)
|
225
|
-
self.instance.read_topics(topics, offsets, timeout_ms, &block)
|
226
|
-
end
|
227
157
|
unless $enterprise_cosmos
|
228
|
-
def read_topics(topics, offsets = nil, timeout_ms = 1000)
|
158
|
+
def read_topics(topics, offsets = nil, timeout_ms = 1000, count = nil)
|
229
159
|
# Logger.debug "read_topics: #{topics}, #{offsets} pool:#{@redis_pool}"
|
230
160
|
@redis_pool.with do |redis|
|
231
161
|
offsets = update_topic_offsets(topics) unless offsets
|
232
|
-
result = redis.xread(topics, offsets, block: timeout_ms)
|
162
|
+
result = redis.xread(topics, offsets, block: timeout_ms, count: count)
|
233
163
|
if result and result.length > 0
|
234
164
|
result.each do |topic, messages|
|
235
165
|
messages.each do |msg_id, msg_hash|
|
@@ -244,26 +174,6 @@ module Cosmos
|
|
244
174
|
end
|
245
175
|
end
|
246
176
|
|
247
|
-
# Add new entry to the redis stream.
|
248
|
-
# > https://www.rubydoc.info/github/redis/redis-rb/Redis:xadd
|
249
|
-
#
|
250
|
-
# @example Without options
|
251
|
-
# COSMOS::Store().write_topic('MANGO__TOPIC', {'message' => 'something'})
|
252
|
-
# @example With options
|
253
|
-
# COSMOS::Store().write_topic('MANGO__TOPIC', {'message' => 'something'}, id: '0-0', maxlen: 1000, approximate: false)
|
254
|
-
#
|
255
|
-
# @param topic [String] the stream / topic
|
256
|
-
# @param msg_hash [Hash] one or multiple field-value pairs
|
257
|
-
#
|
258
|
-
# @option opts [String] :id the entry id, default value is `*`, it means auto generation
|
259
|
-
# @option opts [Integer] :maxlen max length of entries
|
260
|
-
# @option opts [Boolean] :approximate whether to add `~` modifier of maxlen or not
|
261
|
-
#
|
262
|
-
# @return [String] the entry id
|
263
|
-
def self.write_topic(topic, msg_hash, id = '*', maxlen = nil, approximate = true)
|
264
|
-
self.instance.write_topic(topic, msg_hash, id, maxlen, approximate)
|
265
|
-
end
|
266
|
-
|
267
177
|
# Add new entry to the redis stream.
|
268
178
|
# > https://www.rubydoc.info/github/redis/redis-rb/Redis:xadd
|
269
179
|
#
|
@@ -289,23 +199,6 @@ module Cosmos
|
|
289
199
|
end
|
290
200
|
end
|
291
201
|
|
292
|
-
# Trims older entries of the redis stream if needed.
|
293
|
-
# > https://www.rubydoc.info/github/redis/redis-rb/Redis:xtrim
|
294
|
-
#
|
295
|
-
# @example Without options
|
296
|
-
# COSMOS::Store.trim_topic('MANGO__TOPIC', 1000)
|
297
|
-
# @example With options
|
298
|
-
# COSMOS::Store.trim_topic('MANGO__TOPIC', 1000, approximate: true, limit: 0)
|
299
|
-
#
|
300
|
-
# @param topic [String] the stream key
|
301
|
-
# @param minid [Integer] max length of entries to trim
|
302
|
-
# @param limit [Boolean] whether to add `~` modifier of maxlen or not
|
303
|
-
#
|
304
|
-
# @return [Integer] the number of entries actually deleted
|
305
|
-
def self.trim_topic(topic, minid, approximate = true, limit: 0)
|
306
|
-
self.instance.trim_topic(topic, minid, approximate, limit: limit)
|
307
|
-
end
|
308
|
-
|
309
202
|
# Trims older entries of the redis stream if needed.
|
310
203
|
# > https://www.rubydoc.info/github/redis/redis-rb/Redis:xtrim
|
311
204
|
#
|
@@ -324,15 +217,13 @@ module Cosmos
|
|
324
217
|
return redis.xtrim_minid(topic, minid, approximate: approximate, limit: limit)
|
325
218
|
end
|
326
219
|
end
|
220
|
+
end
|
327
221
|
|
328
|
-
|
329
|
-
def
|
330
|
-
|
331
|
-
|
332
|
-
|
333
|
-
# Execute any Redis command. Args must be an array (e.g. ["KEYS", "*"])
|
334
|
-
def execute_raw(args)
|
335
|
-
synchronize { |client| client.call(args) }
|
222
|
+
class EphemeralStore < Store
|
223
|
+
def initialize(pool_size = 10)
|
224
|
+
super(pool_size)
|
225
|
+
@redis_url = "redis://#{ENV['COSMOS_REDIS_EPHEMERAL_HOSTNAME']}:#{ENV['COSMOS_REDIS_EPHEMERAL_PORT']}"
|
226
|
+
@redis_pool = ConnectionPool.new(size: pool_size) { build_redis() }
|
336
227
|
end
|
337
228
|
end
|
338
229
|
end
|
data/lib/cosmos/version.rb
CHANGED
@@ -1,13 +1,14 @@
|
|
1
1
|
# encoding: ascii-8bit
|
2
2
|
|
3
|
-
COSMOS_VERSION = '5.0.
|
3
|
+
COSMOS_VERSION = '5.0.4'
|
4
4
|
module Cosmos
|
5
5
|
module Version
|
6
6
|
MAJOR = '5'
|
7
7
|
MINOR = '0'
|
8
|
-
PATCH = '
|
9
|
-
OTHER = '
|
10
|
-
BUILD = '
|
8
|
+
PATCH = '4'
|
9
|
+
OTHER = ''
|
10
|
+
BUILD = '84d6dcebd4bc21e5559963be4e5fde2ddb7e9822'
|
11
11
|
end
|
12
|
-
VERSION = '5.0.
|
12
|
+
VERSION = '5.0.4'
|
13
|
+
GEM_VERSION = '5.0.4'
|
13
14
|
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: cosmos
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 5.0.
|
4
|
+
version: 5.0.4
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Ryan Melton
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2022-
|
12
|
+
date: 2022-05-24 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: bundler
|
@@ -693,7 +693,7 @@ files:
|
|
693
693
|
- lib/cosmos/models/microservice_model.rb
|
694
694
|
- lib/cosmos/models/microservice_status_model.rb
|
695
695
|
- lib/cosmos/models/model.rb
|
696
|
-
- lib/cosmos/models/
|
696
|
+
- lib/cosmos/models/note_model.rb
|
697
697
|
- lib/cosmos/models/notification_model.rb
|
698
698
|
- lib/cosmos/models/ping_model.rb
|
699
699
|
- lib/cosmos/models/plugin_model.rb
|
@@ -703,8 +703,11 @@ files:
|
|
703
703
|
- lib/cosmos/models/router_model.rb
|
704
704
|
- lib/cosmos/models/router_status_model.rb
|
705
705
|
- lib/cosmos/models/scope_model.rb
|
706
|
+
- lib/cosmos/models/settings_model.rb
|
707
|
+
- lib/cosmos/models/sorted_model.rb
|
706
708
|
- lib/cosmos/models/target_model.rb
|
707
709
|
- lib/cosmos/models/timeline_model.rb
|
710
|
+
- lib/cosmos/models/tool_config_model.rb
|
708
711
|
- lib/cosmos/models/tool_model.rb
|
709
712
|
- lib/cosmos/models/trigger_group_model.rb
|
710
713
|
- lib/cosmos/models/trigger_model.rb
|
@@ -833,11 +836,11 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
833
836
|
version: '2.7'
|
834
837
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
835
838
|
requirements:
|
836
|
-
- - "
|
839
|
+
- - ">="
|
837
840
|
- !ruby/object:Gem::Version
|
838
|
-
version:
|
841
|
+
version: '0'
|
839
842
|
requirements: []
|
840
|
-
rubygems_version: 3.3.
|
843
|
+
rubygems_version: 3.3.14
|
841
844
|
signing_key:
|
842
845
|
specification_version: 4
|
843
846
|
summary: Ball Aerospace COSMOS
|
@@ -1,280 +0,0 @@
|
|
1
|
-
# encoding: ascii-8bit
|
2
|
-
|
3
|
-
# Copyright 2022 Ball Aerospace & Technologies Corp.
|
4
|
-
# All Rights Reserved.
|
5
|
-
#
|
6
|
-
# This program is free software; you can modify and/or redistribute it
|
7
|
-
# under the terms of the GNU Affero General Public License
|
8
|
-
# as published by the Free Software Foundation; version 3 with
|
9
|
-
# attribution addendums as found in the LICENSE.txt
|
10
|
-
#
|
11
|
-
# This program is distributed in the hope that it will be useful,
|
12
|
-
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
13
|
-
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
14
|
-
# GNU Affero General Public License for more details.
|
15
|
-
#
|
16
|
-
# This program may also be used under the terms of a commercial or
|
17
|
-
# enterprise edition license of COSMOS if purchased from the
|
18
|
-
# copyright holder
|
19
|
-
|
20
|
-
# https://www.rubydoc.info/gems/redis/Redis/Commands/SortedSets
|
21
|
-
|
22
|
-
require 'cosmos/topics/calendar_topic'
|
23
|
-
|
24
|
-
module Cosmos
|
25
|
-
|
26
|
-
class NarrativeError < StandardError; end
|
27
|
-
|
28
|
-
class NarrativeInputError < NarrativeError; end
|
29
|
-
|
30
|
-
class NarrativeOverlapError < NarrativeError; end
|
31
|
-
|
32
|
-
class NarrativeModel < Model
|
33
|
-
|
34
|
-
CHRONICLE_TYPE = 'narrative'.freeze
|
35
|
-
PRIMARY_KEY = '__NARRATIVE'.freeze
|
36
|
-
|
37
|
-
def self.pk(scope)
|
38
|
-
return "#{scope}#{PRIMARY_KEY}"
|
39
|
-
end
|
40
|
-
|
41
|
-
# @return [Array|nil] Array up to 100 of this model or empty array
|
42
|
-
def self.get(start:, stop:, scope:, limit: 100)
|
43
|
-
if start > stop
|
44
|
-
raise MetadataInputError.new "start: #{start} must be before stop: #{stop}"
|
45
|
-
end
|
46
|
-
pk = self.pk(scope)
|
47
|
-
array = Store.zrangebyscore(pk, start, stop, :limit => [0, limit])
|
48
|
-
ret_array = Array.new
|
49
|
-
array.each do |value|
|
50
|
-
ret_array << JSON.parse(value)
|
51
|
-
end
|
52
|
-
return ret_array
|
53
|
-
end
|
54
|
-
|
55
|
-
# @return [Array<Hash>] Array up to the limit of the models (as Hash objects) stored under the primary key
|
56
|
-
def self.all(scope:, limit: 100)
|
57
|
-
pk = self.pk(scope)
|
58
|
-
array = Store.zrange(pk, 0, -1, :limit => [0, limit])
|
59
|
-
ret_array = Array.new
|
60
|
-
array.each do |value|
|
61
|
-
ret_array << JSON.parse(value)
|
62
|
-
end
|
63
|
-
return ret_array
|
64
|
-
end
|
65
|
-
|
66
|
-
# @return [Integer] count of the members stored under the primary key
|
67
|
-
def self.count(scope:)
|
68
|
-
return Store.zcard(self.pk(scope))
|
69
|
-
end
|
70
|
-
|
71
|
-
# @return [String|nil] String of the saved json or nil if score not found under primary_key
|
72
|
-
def self.score(score:, scope:)
|
73
|
-
pk = self.pk(scope)
|
74
|
-
array = Store.zrangebyscore(pk, score, score, :limit => [0, 1])
|
75
|
-
array.each do |value|
|
76
|
-
return JSON.parse(value)
|
77
|
-
end
|
78
|
-
return nil
|
79
|
-
end
|
80
|
-
|
81
|
-
# Remove member from a sorted set based on the score.
|
82
|
-
# @return [Integer] count of the members removed
|
83
|
-
def self.destroy(scope:, score:)
|
84
|
-
pk = self.pk(scope)
|
85
|
-
Store.zremrangebyscore(pk, score, score)
|
86
|
-
end
|
87
|
-
|
88
|
-
# Remove members from min to max of the sorted set.
|
89
|
-
# @return [Integer] count of the members removed
|
90
|
-
def self.range_destroy(scope:, min:, max:)
|
91
|
-
pk = self.pk(scope)
|
92
|
-
Store.zremrangebyscore(pk, min, max)
|
93
|
-
end
|
94
|
-
|
95
|
-
# @return [NarrativeModel] Model generated from the passed JSON
|
96
|
-
def self.from_json(json, scope:)
|
97
|
-
json = JSON.parse(json) if String === json
|
98
|
-
raise "json data is nil" if json.nil?
|
99
|
-
|
100
|
-
json.transform_keys!(&:to_sym)
|
101
|
-
self.new(**json, scope: scope)
|
102
|
-
end
|
103
|
-
|
104
|
-
attr_reader :start, :stop, :duration, :color, :description, :type
|
105
|
-
|
106
|
-
# @param [String] scope - Cosmos scope to track event to
|
107
|
-
# @param [Integer] start - start of the event in seconds from Epoch
|
108
|
-
# @param [Integer] stop - stop of the event in seconds from Epoch
|
109
|
-
# @param [String] color - The event color
|
110
|
-
# @param [String] description - What the event is about
|
111
|
-
def initialize(
|
112
|
-
scope:,
|
113
|
-
start:,
|
114
|
-
stop:,
|
115
|
-
color:,
|
116
|
-
description:,
|
117
|
-
type: CHRONICLE_TYPE,
|
118
|
-
updated_at: 0,
|
119
|
-
duration: 0
|
120
|
-
)
|
121
|
-
super(NarrativeModel.pk(scope), name: start.to_s, scope: scope)
|
122
|
-
set_input(
|
123
|
-
start: start,
|
124
|
-
stop: stop,
|
125
|
-
color: color,
|
126
|
-
description: description,
|
127
|
-
)
|
128
|
-
@type = type
|
129
|
-
@updated_at = updated_at
|
130
|
-
end
|
131
|
-
|
132
|
-
# validate color
|
133
|
-
def validate_color(color)
|
134
|
-
if color.nil?
|
135
|
-
color = '#%06x' % (rand * 0xffffff)
|
136
|
-
end
|
137
|
-
valid_color = color =~ /(#*)([0-9,a-f,A-f]{6})/
|
138
|
-
if valid_color.nil?
|
139
|
-
raise MetadataInputError.new "invalid color but in hex format. #FF0000"
|
140
|
-
end
|
141
|
-
|
142
|
-
color = "##{color}" unless color.start_with?('#')
|
143
|
-
return color
|
144
|
-
end
|
145
|
-
|
146
|
-
# validate the input to the rules we have created for timelines.
|
147
|
-
# - An entry's start MUST be before the stop.
|
148
|
-
# - An entry's description MUST a String.
|
149
|
-
def validate_input(start:, stop:, color:, description:)
|
150
|
-
begin
|
151
|
-
DateTime.strptime(start.to_s, '%s')
|
152
|
-
DateTime.strptime(stop.to_s, '%s')
|
153
|
-
rescue Date::Error
|
154
|
-
raise NarrativeInputError.new "failed validation input must be seconds: #{start}, #{stop}"
|
155
|
-
end
|
156
|
-
validate_color(color)
|
157
|
-
duration = stop - start
|
158
|
-
if duration <= 0
|
159
|
-
raise NarrativeInputError.new "start: #{start} must be before stop: #{stop}"
|
160
|
-
elsif description.is_a?(String) == false
|
161
|
-
raise NarrativeInputError.new "description must be a String: #{description}"
|
162
|
-
end
|
163
|
-
end
|
164
|
-
|
165
|
-
# Set the values of the instance, @start, @stop, @description...
|
166
|
-
def set_input(start:, stop:, color:, description:)
|
167
|
-
begin
|
168
|
-
DateTime.strptime(start.to_s, '%s')
|
169
|
-
DateTime.strptime(stop.to_s, '%s')
|
170
|
-
rescue ArgumentError
|
171
|
-
raise NarrativeInputError.new "invalid input must be seconds: #{start}, #{stop}"
|
172
|
-
end
|
173
|
-
@start = start
|
174
|
-
@stop = stop
|
175
|
-
@duration = @stop - @start
|
176
|
-
@color = color
|
177
|
-
@description = description
|
178
|
-
end
|
179
|
-
|
180
|
-
# validate_time will be called on create and update this will validate
|
181
|
-
# that no other chronicle event or metadata had been saved for that time.
|
182
|
-
# One event or metadata per second to ensure data can be updated.
|
183
|
-
#
|
184
|
-
# @param [Integer] ignore_score - should be nil unless you want to ignore
|
185
|
-
# a time when doing an update
|
186
|
-
def validate_time(ignore_score: nil)
|
187
|
-
array = Store.zrangebyscore(@primary_key, @start, @start, :limit => [0, 1])
|
188
|
-
array.each do |value|
|
189
|
-
entry = JSON.parse(value)
|
190
|
-
if ignore_score == entry['start']
|
191
|
-
next
|
192
|
-
else
|
193
|
-
return entry
|
194
|
-
end
|
195
|
-
end
|
196
|
-
return nil
|
197
|
-
end
|
198
|
-
|
199
|
-
# Update the Redis hash at primary_key and set the score equal to the start Epoch time
|
200
|
-
# the member is set to the JSON generated via calling as_json
|
201
|
-
def create
|
202
|
-
validate_input(start: @start, stop: @stop, color: @color, description: @description)
|
203
|
-
collision = validate_time()
|
204
|
-
unless collision.nil?
|
205
|
-
raise NarrativeOverlapError.new "no chronicles can overlap, collision: #{collision}"
|
206
|
-
end
|
207
|
-
|
208
|
-
@updated_at = Time.now.to_nsec_from_epoch
|
209
|
-
Store.zadd(@primary_key, @start, JSON.generate(as_json()))
|
210
|
-
notify(kind: 'created')
|
211
|
-
end
|
212
|
-
|
213
|
-
# Update the Redis hash at primary_key and remove the current activity at the current score
|
214
|
-
# and update the score to the new score equal to the start Epoch time this uses a multi
|
215
|
-
# to execute both the remove and create. The member via the JSON generated via calling as_json
|
216
|
-
def update(start:, stop:, color:, description:)
|
217
|
-
validate_input(start: start, stop: stop, color: color, description: description)
|
218
|
-
old_start = @start
|
219
|
-
|
220
|
-
set_input(
|
221
|
-
start: start,
|
222
|
-
stop: stop,
|
223
|
-
color: color,
|
224
|
-
description: description,
|
225
|
-
)
|
226
|
-
@updated_at = Time.now.to_nsec_from_epoch
|
227
|
-
|
228
|
-
collision = validate_time(ignore_score: old_start)
|
229
|
-
unless collision.nil?
|
230
|
-
raise NarrativeOverlapError.new "failed to update #{old_start}, no chronicles can overlap, collision: #{collision}"
|
231
|
-
end
|
232
|
-
|
233
|
-
Store.multi do |multi|
|
234
|
-
multi.zremrangebyscore(@primary_key, old_start, old_start)
|
235
|
-
multi.zadd(@primary_key, @start, JSON.generate(as_json()))
|
236
|
-
end
|
237
|
-
notify(kind: 'updated', extra: old_start)
|
238
|
-
return @start
|
239
|
-
end
|
240
|
-
|
241
|
-
# destroy the activity from the redis database
|
242
|
-
def destroy
|
243
|
-
Store.zremrangebyscore(@primary_key, @start, @start)
|
244
|
-
notify(kind: 'deleted')
|
245
|
-
end
|
246
|
-
|
247
|
-
# @return [] update the redis stream / timeline topic that something has changed
|
248
|
-
def notify(kind:, extra: nil)
|
249
|
-
notification = {
|
250
|
-
'data' => JSON.generate(as_json()),
|
251
|
-
'kind' => kind,
|
252
|
-
'type' => 'calendar',
|
253
|
-
}
|
254
|
-
notification['extra'] = extra unless extra.nil?
|
255
|
-
begin
|
256
|
-
CalendarTopic.write_entry(notification, scope: @scope)
|
257
|
-
rescue StandardError => e
|
258
|
-
raise NarrativeError.new "Failed to write to stream: #{notification}, #{e}"
|
259
|
-
end
|
260
|
-
end
|
261
|
-
|
262
|
-
# @return [Hash] generated from the NarrativeModel
|
263
|
-
def as_json
|
264
|
-
return {
|
265
|
-
'color' => @color,
|
266
|
-
'start' => @start,
|
267
|
-
'stop' => @stop,
|
268
|
-
'description' => @description,
|
269
|
-
'type' => CHRONICLE_TYPE,
|
270
|
-
'scope' => @scope,
|
271
|
-
'updated_at' => @updated_at,
|
272
|
-
}
|
273
|
-
end
|
274
|
-
|
275
|
-
# @return [String] string view of NarrativeModel
|
276
|
-
def to_s
|
277
|
-
return "<NarrativeModel ->: #{@start}, x: #{@stop}, c: #{@color}, d: #{@description}>"
|
278
|
-
end
|
279
|
-
end
|
280
|
-
end
|