cosmos 5.0.2.pre.beta2 → 5.0.4

Sign up to get free protection for your applications and to get access to all the features.
Files changed (84) hide show
  1. checksums.yaml +4 -4
  2. data/bin/cosmos +1 -1
  3. data/data/config/microservice.yaml +47 -35
  4. data/data/config/plugins.yaml +3 -150
  5. data/data/config/target.yaml +70 -0
  6. data/data/config/tool.yaml +37 -31
  7. data/lib/cosmos/api/api.rb +1 -25
  8. data/lib/cosmos/api/cmd_api.rb +17 -6
  9. data/lib/cosmos/api/config_api.rb +10 -4
  10. data/lib/cosmos/api/limits_api.rb +1 -1
  11. data/lib/cosmos/api/settings_api.rb +19 -7
  12. data/lib/cosmos/api/target_api.rb +2 -2
  13. data/lib/cosmos/api/tlm_api.rb +69 -41
  14. data/lib/cosmos/config/config_parser.rb +19 -22
  15. data/lib/cosmos/config/meta_config_parser.rb +1 -1
  16. data/lib/cosmos/conversions/generic_conversion.rb +2 -2
  17. data/lib/cosmos/conversions/polynomial_conversion.rb +5 -8
  18. data/lib/cosmos/conversions/segmented_polynomial_conversion.rb +26 -9
  19. data/lib/cosmos/io/json_drb.rb +5 -1
  20. data/lib/cosmos/logs/log_writer.rb +2 -2
  21. data/lib/cosmos/microservices/cleanup_microservice.rb +28 -29
  22. data/lib/cosmos/microservices/decom_microservice.rb +1 -1
  23. data/lib/cosmos/microservices/interface_microservice.rb +0 -1
  24. data/lib/cosmos/microservices/microservice.rb +3 -3
  25. data/lib/cosmos/microservices/reducer_microservice.rb +12 -10
  26. data/lib/cosmos/models/cvt_model.rb +6 -6
  27. data/lib/cosmos/models/gem_model.rb +3 -3
  28. data/lib/cosmos/models/info_model.rb +1 -1
  29. data/lib/cosmos/models/interface_status_model.rb +1 -1
  30. data/lib/cosmos/models/metadata_model.rb +42 -216
  31. data/lib/cosmos/models/metric_model.rb +2 -2
  32. data/lib/cosmos/models/microservice_model.rb +1 -1
  33. data/lib/cosmos/models/microservice_status_model.rb +1 -1
  34. data/lib/cosmos/models/model.rb +16 -16
  35. data/lib/cosmos/models/note_model.rb +124 -0
  36. data/lib/cosmos/models/ping_model.rb +2 -1
  37. data/lib/cosmos/models/plugin_model.rb +1 -1
  38. data/lib/cosmos/models/process_status_model.rb +1 -1
  39. data/lib/cosmos/models/scope_model.rb +9 -26
  40. data/lib/cosmos/models/settings_model.rb +55 -0
  41. data/lib/cosmos/models/sorted_model.rb +165 -0
  42. data/lib/cosmos/models/target_model.rb +120 -13
  43. data/lib/cosmos/models/tool_config_model.rb +38 -0
  44. data/lib/cosmos/models/tool_model.rb +1 -1
  45. data/lib/cosmos/models/widget_model.rb +1 -1
  46. data/lib/cosmos/operators/microservice_operator.rb +2 -1
  47. data/lib/cosmos/packets/packet.rb +23 -0
  48. data/lib/cosmos/packets/packet_config.rb +2 -2
  49. data/lib/cosmos/packets/packet_item.rb +57 -0
  50. data/lib/cosmos/packets/packet_item_limits.rb +14 -2
  51. data/lib/cosmos/packets/parsers/packet_item_parser.rb +1 -1
  52. data/lib/cosmos/packets/parsers/packet_parser.rb +1 -1
  53. data/lib/cosmos/packets/parsers/xtce_parser.rb +1 -1
  54. data/lib/cosmos/packets/structure_item.rb +10 -1
  55. data/lib/cosmos/script/api_shared.rb +30 -25
  56. data/lib/cosmos/script/calendar.rb +26 -15
  57. data/lib/cosmos/script/commands.rb +5 -7
  58. data/lib/cosmos/script/script.rb +19 -39
  59. data/lib/cosmos/script/storage.rb +92 -105
  60. data/lib/cosmos/system/system.rb +2 -1
  61. data/lib/cosmos/tools/table_manager/table_item.rb +1 -1
  62. data/lib/cosmos/top_level.rb +5 -1
  63. data/lib/cosmos/topics/autonomic_topic.rb +2 -2
  64. data/lib/cosmos/topics/calendar_topic.rb +1 -1
  65. data/lib/cosmos/topics/command_decom_topic.rb +35 -1
  66. data/lib/cosmos/topics/command_topic.rb +6 -4
  67. data/lib/cosmos/topics/interface_topic.rb +8 -8
  68. data/lib/cosmos/topics/limits_event_topic.rb +5 -3
  69. data/lib/cosmos/topics/notifications_topic.rb +1 -1
  70. data/lib/cosmos/topics/router_topic.rb +9 -9
  71. data/lib/cosmos/topics/telemetry_decom_topic.rb +5 -1
  72. data/lib/cosmos/topics/telemetry_topic.rb +1 -1
  73. data/lib/cosmos/topics/timeline_topic.rb +1 -1
  74. data/lib/cosmos/topics/topic.rb +23 -8
  75. data/lib/cosmos/utilities/logger.rb +4 -3
  76. data/lib/cosmos/utilities/metric.rb +32 -26
  77. data/lib/cosmos/utilities/s3.rb +61 -0
  78. data/lib/cosmos/utilities/s3_file_cache.rb +12 -6
  79. data/lib/cosmos/utilities/store.rb +1 -0
  80. data/lib/cosmos/utilities/store_autoload.rb +25 -134
  81. data/lib/cosmos/version.rb +6 -5
  82. data/templates/plugin-template/plugin.gemspec +0 -2
  83. metadata +9 -6
  84. 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
- @@instance = nil
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 @@instance if @@instance
45
+ return @instance if @instance
46
46
 
47
47
  @@instance_mutex.synchronize do
48
- @@instance ||= self.new(pool_size)
49
- return @@instance
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
- return result[0]
145
- end
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
- "0-0"
109
+ return nil
170
110
  end
171
111
  end
172
112
  end
173
113
 
174
- def self.read_topic_last(topic)
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
- # TODO: Currently unused
193
- # def decrement_id(id)
194
- # time, sequence = id.split('-')
195
- # if sequence == '0'
196
- # "#{time.to_i - 1}-18446744073709551615"
197
- # else
198
- # "#{time}-#{sequence.to_i - 1}"
199
- # end
200
- # end
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
- # Execute any Redis command. Args must be an array (e.g. ["KEYS", "*"])
329
- def self.execute_raw(args)
330
- self.instance.execute_raw(args)
331
- end
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
@@ -1,13 +1,14 @@
1
1
  # encoding: ascii-8bit
2
2
 
3
- COSMOS_VERSION = '5.0.2-beta2'
3
+ COSMOS_VERSION = '5.0.4'
4
4
  module Cosmos
5
5
  module Version
6
6
  MAJOR = '5'
7
7
  MINOR = '0'
8
- PATCH = '2'
9
- OTHER = 'beta2'
10
- BUILD = '86e4fc1b82d7941b7f77e9257ecafafeff9fe3fd'
8
+ PATCH = '4'
9
+ OTHER = ''
10
+ BUILD = '84d6dcebd4bc21e5559963be4e5fde2ddb7e9822'
11
11
  end
12
- VERSION = '5.0.2-beta2'
12
+ VERSION = '5.0.4'
13
+ GEM_VERSION = '5.0.4'
13
14
  end
@@ -20,6 +20,4 @@ spec = Gem::Specification.new do |s|
20
20
  s.version = '0.0.0' + ".#{time}"
21
21
  end
22
22
  s.files = Dir.glob("{targets,lib,procedures,tools,microservices}/**/*") + %w(Rakefile README.md plugin.txt)
23
-
24
- s.add_runtime_dependency 'cosmos', '~> 5.0'
25
23
  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.2.pre.beta2
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-04-28 00:00:00.000000000 Z
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/narrative_model.rb
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: 1.3.1
841
+ version: '0'
839
842
  requirements: []
840
- rubygems_version: 3.3.5
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