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.
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