rsmp 0.51.3 → 0.52.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +16 -0
- data/Gemfile.lock +1 -1
- data/documentation/collecting_message.md +112 -0
- data/documentation/configuration.md +12 -0
- data/lib/rsmp/collect/collector.rb +6 -0
- data/lib/rsmp/collect/queue.rb +4 -0
- data/lib/rsmp/collect/status_collector.rb +2 -2
- data/lib/rsmp/options/options.rb +8 -4
- data/lib/rsmp/options/schemas/required_settings.json +30 -0
- data/lib/rsmp/options/schemas/required_supervisor_settings.json +12 -0
- data/lib/rsmp/options/site_options.rb +6 -1
- data/lib/rsmp/options/supervisor_options.rb +9 -0
- data/lib/rsmp/proxy/modules/acknowledgements.rb +2 -0
- data/lib/rsmp/proxy/proxy.rb +8 -2
- data/lib/rsmp/proxy/site/modules/status.rb +45 -9
- data/lib/rsmp/tlc/modules/inputs.rb +2 -0
- data/lib/rsmp/version.rb +1 -1
- metadata +3 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: cfba3255efe256eae701725584e74d80a7ba66fe94708ef9bdc76cbd678c7700
|
|
4
|
+
data.tar.gz: b0aebcd26383d1ed77fbcdd18351fecc0a7afcc962d582f17a3f488a53aaeb22
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: f7796d54dbed77a9d230a4e49f3ddadd22b3833b29fa05c882f9d7a6c540ed64ae55745f5b8dbe38a9fe095fa49b45684f9ebaf0a954d9fa021d892f2bdc5cec
|
|
7
|
+
data.tar.gz: 5bf194b21059fd35d82e41093c68658585fe083cc061f5759e98d95df1f45e5fbd0958051cb3dbac8b771f915fc0511c056f72c887339f1b5df6c60f73d6a57f
|
data/CHANGELOG.md
CHANGED
|
@@ -719,3 +719,19 @@ Initial release.
|
|
|
719
719
|
|
|
720
720
|
## 0.51.1
|
|
721
721
|
- cleanup semantic versioning handling of version strings and use canonical schema versions internally
|
|
722
|
+
|
|
723
|
+
## 0.51.2
|
|
724
|
+
- bumb patch version for test purposes, no changes othewise
|
|
725
|
+
|
|
726
|
+
## 0.51.3
|
|
727
|
+
- ensure socket shutdown when stopping node
|
|
728
|
+
|
|
729
|
+
## 0.51.4
|
|
730
|
+
- don't update subscription list until unsubscribe is acknowledged
|
|
731
|
+
|
|
732
|
+
## 0.52.0
|
|
733
|
+
- allow the proxy's `collect:` option to accept a Proc that builds a collector or queue before the handshake
|
|
734
|
+
- add optional `predicate:` filtering to collectors, including status collectors
|
|
735
|
+
- add `start` and `stop` aliases to queues for use with proxy collection factories
|
|
736
|
+
- fix status collectors to honor `component:` and `responses:`; the misspelled `reponses:` option is no longer used
|
|
737
|
+
|
data/Gemfile.lock
CHANGED
|
@@ -112,6 +112,118 @@ result = collector.collect do |message|
|
|
|
112
112
|
end
|
|
113
113
|
```
|
|
114
114
|
|
|
115
|
+
### Message predicates
|
|
116
|
+
|
|
117
|
+
Use `predicate:` for conditions beyond the message type, direction, and component
|
|
118
|
+
handled by `RSMP::Filter`. The Proc receives a whole message after the filter
|
|
119
|
+
accepts it. Returning `false` or `nil` skips the message before collection
|
|
120
|
+
callbacks or state-item matching; earlier item matches remain intact.
|
|
121
|
+
|
|
122
|
+
```ruby
|
|
123
|
+
collector = RSMP::Collector.new(
|
|
124
|
+
proxy,
|
|
125
|
+
filter: RSMP::Filter.new(type: 'StatusUpdate', ingoing: true, outgoing: false),
|
|
126
|
+
predicate: ->(message) { message.is_a?(RSMP::StatusUpdate) && message.attribute('sS').any? },
|
|
127
|
+
num: 1,
|
|
128
|
+
timeout: 5
|
|
129
|
+
)
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
Predicates also work with `StatusCollector`. They do not suppress failures such
|
|
133
|
+
as a rejection of the initiating request or an invalid peer message. `Filter`
|
|
134
|
+
admits `MessageNotAck` even when a different message type is requested, so a
|
|
135
|
+
predicate that accesses type-specific attributes should check the message type.
|
|
136
|
+
|
|
137
|
+
### Status collection
|
|
138
|
+
|
|
139
|
+
`StatusCollector` matches wanted status items within incoming messages. Both
|
|
140
|
+
`StatusUpdate` and `StatusResponse` are accepted by default. Set `responses: false`
|
|
141
|
+
to accept updates only, or `updates: false` to accept responses only.
|
|
142
|
+
|
|
143
|
+
```ruby
|
|
144
|
+
want = [{ 'sCI' => 'S0001', 'n' => 'signalgroupstatus' }]
|
|
145
|
+
collector = RSMP::StatusCollector.new(
|
|
146
|
+
proxy, want, component: 'C1', responses: false, timeout: 5
|
|
147
|
+
)
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
`component:` filters the message's `cId`; the wanted items describe entries in
|
|
151
|
+
`sS`, which do not have their own component ID. Collection completes when all
|
|
152
|
+
wanted items match, rather than after a fixed number of messages.
|
|
153
|
+
|
|
154
|
+
### Collection during proxy setup
|
|
155
|
+
|
|
156
|
+
The proxy's `collect:` option accepts the existing collector options hash:
|
|
157
|
+
|
|
158
|
+
```ruby
|
|
159
|
+
proxy_options = {
|
|
160
|
+
collect: {
|
|
161
|
+
filter: RSMP::Filter.new(type: 'StatusUpdate', ingoing: true, outgoing: false),
|
|
162
|
+
num: 1,
|
|
163
|
+
timeout: 5
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
It also accepts a Proc that receives the new proxy and returns a collector or
|
|
169
|
+
queue. The proxy calls `start` on the returned object and exposes it through
|
|
170
|
+
`proxy.collector`. This starts receiving before the handshake can deliver
|
|
171
|
+
messages. Return an unstarted object; the factory runs before version negotiation
|
|
172
|
+
and should not rely on the proxy being fully initialized or connected.
|
|
173
|
+
|
|
174
|
+
```ruby
|
|
175
|
+
proxy_options = {
|
|
176
|
+
collect: proc do |proxy|
|
|
177
|
+
RSMP::StatusCollector.new(
|
|
178
|
+
proxy,
|
|
179
|
+
[{ 'sCI' => 'S0001', 'n' => 'signalgroupstatus' }],
|
|
180
|
+
component: 'C1', responses: false, timeout: 5
|
|
181
|
+
)
|
|
182
|
+
end
|
|
183
|
+
}
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
Pass these options when creating the proxy. Use `proxy.collector.wait` or
|
|
187
|
+
`wait!` for a collector, or `wait_for_message` or `wait_for_message!` for a queue.
|
|
188
|
+
|
|
189
|
+
## Queues
|
|
190
|
+
|
|
191
|
+
`RSMP::Queue` retains accepted messages until consumed, including messages that
|
|
192
|
+
arrive before the caller starts waiting. Each wait returns one message, rather
|
|
193
|
+
than a collection. `start` and `stop` are aliases for `start_receiving` and
|
|
194
|
+
`stop_receiving`, so a queue can also be returned by a proxy's `collect:` factory.
|
|
195
|
+
|
|
196
|
+
```ruby
|
|
197
|
+
queue = RSMP::Queue.new(proxy, filter: RSMP::Filter.new(type: 'StatusUpdate'))
|
|
198
|
+
queue.start
|
|
199
|
+
begin
|
|
200
|
+
result = queue.wait_for_message(timeout: 5)
|
|
201
|
+
puts result.value if result.success?
|
|
202
|
+
ensure
|
|
203
|
+
queue.stop
|
|
204
|
+
end
|
|
205
|
+
```
|
|
206
|
+
|
|
207
|
+
A queue stays registered until stopped. Its `timeout:` applies to an individual
|
|
208
|
+
wait: expiry returns a failure with code `:timeout`, or raises
|
|
209
|
+
`RSMP::OperationError` through `wait_for_message!`.
|
|
210
|
+
|
|
211
|
+
An enclosing Async timeout raised while waiting in the queue is converted in
|
|
212
|
+
the same way, even when no queue timeout is supplied:
|
|
213
|
+
|
|
214
|
+
```ruby
|
|
215
|
+
begin
|
|
216
|
+
message = Async::Task.current.with_timeout(5) { queue.wait_for_message! }
|
|
217
|
+
rescue RSMP::OperationError => error
|
|
218
|
+
raise unless error.failure.code == :timeout
|
|
219
|
+
|
|
220
|
+
warn 'The caller deadline expired'
|
|
221
|
+
end
|
|
222
|
+
```
|
|
223
|
+
|
|
224
|
+
This example assumes the queue is already started. Both timeout sources use the
|
|
225
|
+
same failure code; `failure.cause` retains the original Async exception.
|
|
226
|
+
|
|
115
227
|
## Sending and collecting atomically
|
|
116
228
|
|
|
117
229
|
High-level send methods follow the same non-bang/bang convention:
|
|
@@ -16,6 +16,18 @@ RSMP uses option classes to handle configuration for sites and supervisors. Thes
|
|
|
16
16
|
|
|
17
17
|
Each class applies defaults and validates the configuration against a JSON Schema located in `lib/rsmp/options/schemas/`.
|
|
18
18
|
|
|
19
|
+
Field types and unknown keys are checked before defaults are applied. The `required_settings.json`
|
|
20
|
+
and `required_supervisor_settings.json` schemas then check required settings after defaults and
|
|
21
|
+
supervisor site inheritance are resolved:
|
|
22
|
+
|
|
23
|
+
- Every configured signal plan must have a `cycle_time`.
|
|
24
|
+
- Configured components must include exactly one `main` component. Sites supply a default main
|
|
25
|
+
component when omitted; supervisor site settings can inherit it from `default`.
|
|
26
|
+
- A site in `server` role must have a `port`, either explicitly or from its first supervisor endpoint.
|
|
27
|
+
With `supervisors: []`, the port must be explicit.
|
|
28
|
+
|
|
29
|
+
`inputs.programming: {}` is allowed and means no programmed input actions.
|
|
30
|
+
|
|
19
31
|
## Version strings
|
|
20
32
|
|
|
21
33
|
Core 3.3.0 and later use exact `MAJOR.MINOR.PATCH` version strings for both Core and SXL negotiation.
|
|
@@ -7,6 +7,9 @@ module RSMP
|
|
|
7
7
|
|
|
8
8
|
attr_reader :messages, :m_id, :initiator
|
|
9
9
|
|
|
10
|
+
# :predicate optionally receives each message accepted by the filter. A falsey
|
|
11
|
+
# result skips the message before collection callbacks or item matching.
|
|
12
|
+
# Rejection and invalid-message failures are handled independently of it.
|
|
10
13
|
def initialize(distributor, options = {})
|
|
11
14
|
initialize_receiver distributor, filter: options[:filter]
|
|
12
15
|
@options = {
|
|
@@ -17,6 +20,7 @@ module RSMP
|
|
|
17
20
|
}.deep_merge(options)
|
|
18
21
|
@timeout = options[:timeout]
|
|
19
22
|
@num = options[:num]
|
|
23
|
+
@predicate = options[:predicate]
|
|
20
24
|
@initiator = options[:initiator]
|
|
21
25
|
@m_id = options[:m_id] || @initiator&.attributes&.dig('mId')
|
|
22
26
|
make_title(options[:title])
|
|
@@ -131,6 +135,8 @@ module RSMP
|
|
|
131
135
|
def perform_match?(message)
|
|
132
136
|
return false if reject_not_ack?(message)
|
|
133
137
|
return false unless acceptable?(message)
|
|
138
|
+
return false if @predicate && !@predicate.call(message)
|
|
139
|
+
return false unless active?
|
|
134
140
|
|
|
135
141
|
if @block
|
|
136
142
|
status = Array(@block.call(message))
|
data/lib/rsmp/collect/queue.rb
CHANGED
|
@@ -7,6 +7,10 @@ module RSMP
|
|
|
7
7
|
|
|
8
8
|
attr_reader :messages
|
|
9
9
|
|
|
10
|
+
# Allow queues to be started by a proxy's collect factory, like collectors.
|
|
11
|
+
alias start start_receiving
|
|
12
|
+
alias stop stop_receiving
|
|
13
|
+
|
|
10
14
|
def initialize(distributor, filter: nil)
|
|
11
15
|
initialize_receiver distributor, filter: filter
|
|
12
16
|
@condition = Async::Notification.new
|
|
@@ -4,11 +4,11 @@ module RSMP
|
|
|
4
4
|
def initialize(proxy, want, options = {})
|
|
5
5
|
type = []
|
|
6
6
|
type << 'StatusUpdate' unless options[:updates] == false
|
|
7
|
-
type << 'StatusResponse' unless options[:
|
|
7
|
+
type << 'StatusResponse' unless options[:responses] == false
|
|
8
8
|
|
|
9
9
|
super(proxy, want, options.merge(
|
|
10
10
|
title: 'status response',
|
|
11
|
-
filter: RSMP::Filter.new(ingoing: true, outgoing: false, type: type)
|
|
11
|
+
filter: RSMP::Filter.new(ingoing: true, outgoing: false, type: type, component: options[:component])
|
|
12
12
|
))
|
|
13
13
|
end
|
|
14
14
|
|
data/lib/rsmp/options/options.rb
CHANGED
|
@@ -34,7 +34,9 @@ module RSMP
|
|
|
34
34
|
validate_log_settings! if validate
|
|
35
35
|
config = normalize_config(options || {})
|
|
36
36
|
validate!(config) if validate
|
|
37
|
-
|
|
37
|
+
config = apply_defaults(config)
|
|
38
|
+
validate_effective!(config) if validate
|
|
39
|
+
@data = normalize(config)
|
|
38
40
|
end
|
|
39
41
|
|
|
40
42
|
def defaults = {}
|
|
@@ -56,10 +58,10 @@ module RSMP
|
|
|
56
58
|
raise RSMP::ConfigurationError, "Invalid log configuration#{source_suffix}:\n#{message}"
|
|
57
59
|
end
|
|
58
60
|
|
|
59
|
-
def validate!(data = @data)
|
|
60
|
-
return unless
|
|
61
|
+
def validate!(data = @data, path: schema_path)
|
|
62
|
+
return unless path && File.exist?(path)
|
|
61
63
|
|
|
62
|
-
schemer = JSONSchemer.schema(Pathname.new(
|
|
64
|
+
schemer = JSONSchemer.schema(Pathname.new(path))
|
|
63
65
|
errors = schemer.validate(data).to_a
|
|
64
66
|
return if errors.empty?
|
|
65
67
|
|
|
@@ -86,6 +88,8 @@ module RSMP
|
|
|
86
88
|
|
|
87
89
|
private
|
|
88
90
|
|
|
91
|
+
def validate_effective!(_config); end
|
|
92
|
+
|
|
89
93
|
def apply_defaults(options)
|
|
90
94
|
defaults.deep_merge(options)
|
|
91
95
|
end
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
3
|
+
"$comment": "Validate after applying defaults and inherited site settings; structural validation uses the other config schemas.",
|
|
4
|
+
"type": "object",
|
|
5
|
+
"properties": {
|
|
6
|
+
"components": {
|
|
7
|
+
"type": "object",
|
|
8
|
+
"required": ["main"],
|
|
9
|
+
"properties": {
|
|
10
|
+
"main": {
|
|
11
|
+
"type": "object",
|
|
12
|
+
"minProperties": 1,
|
|
13
|
+
"maxProperties": 1
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
},
|
|
17
|
+
"signal_plans": {
|
|
18
|
+
"type": "object",
|
|
19
|
+
"additionalProperties": {
|
|
20
|
+
"type": "object",
|
|
21
|
+
"required": ["cycle_time"]
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
},
|
|
25
|
+
"if": {
|
|
26
|
+
"required": ["connection_role"],
|
|
27
|
+
"properties": { "connection_role": { "const": "server" } }
|
|
28
|
+
},
|
|
29
|
+
"then": { "required": ["port"] }
|
|
30
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
3
|
+
"$comment": "Validate each site after merging supervisor defaults, without changing the stored overrides.",
|
|
4
|
+
"type": "object",
|
|
5
|
+
"properties": {
|
|
6
|
+
"default": { "$ref": "required_settings.json" },
|
|
7
|
+
"sites": {
|
|
8
|
+
"type": "object",
|
|
9
|
+
"additionalProperties": { "$ref": "required_settings.json" }
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
}
|
|
@@ -23,6 +23,10 @@ module RSMP
|
|
|
23
23
|
|
|
24
24
|
private
|
|
25
25
|
|
|
26
|
+
def validate_effective!(config)
|
|
27
|
+
validate!(config, path: File.join(SCHEMAS_PATH, 'required_settings.json'))
|
|
28
|
+
end
|
|
29
|
+
|
|
26
30
|
def default_message_buffer
|
|
27
31
|
{
|
|
28
32
|
'max_messages' => 10_000,
|
|
@@ -54,7 +58,8 @@ module RSMP
|
|
|
54
58
|
defaults = defaults()
|
|
55
59
|
defaults['components']['main'] = options['components']['main'] if options.dig('components', 'main')
|
|
56
60
|
data = defaults.deep_merge(options)
|
|
57
|
-
|
|
61
|
+
port = data.dig('supervisors', 0, 'port')
|
|
62
|
+
data['port'] ||= port if port
|
|
58
63
|
data
|
|
59
64
|
end
|
|
60
65
|
end
|
|
@@ -28,6 +28,15 @@ module RSMP
|
|
|
28
28
|
def schema_file
|
|
29
29
|
'supervisor.json'
|
|
30
30
|
end
|
|
31
|
+
|
|
32
|
+
private
|
|
33
|
+
|
|
34
|
+
def validate_effective!(config)
|
|
35
|
+
base = config['default'] || {}
|
|
36
|
+
sites = (config['sites'] || {}).transform_values { |settings| base.deep_merge(settings) }
|
|
37
|
+
effective = config.merge('sites' => sites)
|
|
38
|
+
validate!(effective, path: File.join(SCHEMAS_PATH, 'required_supervisor_settings.json'))
|
|
39
|
+
end
|
|
31
40
|
end
|
|
32
41
|
end
|
|
33
42
|
end
|
data/lib/rsmp/proxy/proxy.rb
CHANGED
|
@@ -106,9 +106,15 @@ module RSMP
|
|
|
106
106
|
@rejected_sxls = []
|
|
107
107
|
@receive_alarms = true
|
|
108
108
|
@site_settings = nil # can't pick until we know the site id
|
|
109
|
-
|
|
109
|
+
collect = options[:collect]
|
|
110
|
+
return unless collect
|
|
111
|
+
|
|
112
|
+
@collector = if collect.is_a?(Proc)
|
|
113
|
+
collect.call(self)
|
|
114
|
+
else
|
|
115
|
+
RSMP::Collector.new self, collect
|
|
116
|
+
end
|
|
110
117
|
|
|
111
|
-
@collector = RSMP::Collector.new self, options[:collect]
|
|
112
118
|
@collector.start
|
|
113
119
|
end
|
|
114
120
|
|
|
@@ -3,6 +3,16 @@ module RSMP
|
|
|
3
3
|
module Modules
|
|
4
4
|
# Handles status requests, responses, subscriptions and updates
|
|
5
5
|
module Status
|
|
6
|
+
def clear
|
|
7
|
+
super
|
|
8
|
+
@pending_status_unsubscriptions = {}
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def process_not_ack(message)
|
|
12
|
+
@pending_status_unsubscriptions.delete(message.attribute('oMId'))
|
|
13
|
+
super
|
|
14
|
+
end
|
|
15
|
+
|
|
6
16
|
# Build and send a StatusRequest. Returns Result<StatusRequest>.
|
|
7
17
|
def request_status(status_list, component: nil, m_id: nil, validate: true)
|
|
8
18
|
readiness = validate_ready 'request status'
|
|
@@ -75,9 +85,9 @@ module RSMP
|
|
|
75
85
|
subscribe_list.each do |item|
|
|
76
86
|
code = item['sCI']
|
|
77
87
|
name = item['n']
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
88
|
+
ensure_subscription_path(component_id, code, name)
|
|
89
|
+
# Replace the record so an older unsubscribe ACK cannot remove it.
|
|
90
|
+
@status_subscriptions[component_id][code][name] = item.slice('uRt', 'sOc')
|
|
81
91
|
end
|
|
82
92
|
end
|
|
83
93
|
|
|
@@ -147,20 +157,46 @@ module RSMP
|
|
|
147
157
|
def unsubscribe_to_status(status_list, component: nil, validate: nil)
|
|
148
158
|
component ||= main.c_id
|
|
149
159
|
|
|
150
|
-
status_list.each do |item|
|
|
151
|
-
remove_subscription_item(component, item['sCI'], item['n'])
|
|
152
|
-
end
|
|
153
|
-
|
|
154
160
|
# Local subscription state must be cleaned up even when the peer has
|
|
155
161
|
# already disconnected. In that case there is nothing left to send.
|
|
156
|
-
|
|
162
|
+
unless ready?
|
|
163
|
+
status_list.each do |item|
|
|
164
|
+
remove_subscription_item(component, item['sCI'], item['n'])
|
|
165
|
+
end
|
|
166
|
+
return Result.success(nil)
|
|
167
|
+
end
|
|
157
168
|
|
|
158
169
|
message = RSMP::StatusUnsubscribe.new({
|
|
159
170
|
'cId' => component,
|
|
160
171
|
'sS' => status_list
|
|
161
172
|
})
|
|
162
173
|
apply_nts_message_attributes message
|
|
163
|
-
|
|
174
|
+
track_status_unsubscribe(message)
|
|
175
|
+
delivery = send_message(message, validate: validate)
|
|
176
|
+
delivery.map(&:message)
|
|
177
|
+
ensure
|
|
178
|
+
@pending_status_unsubscriptions.delete(message.m_id) if message && !delivery&.success?
|
|
179
|
+
end
|
|
180
|
+
|
|
181
|
+
def track_status_unsubscribe(message)
|
|
182
|
+
component = message.attribute('cId')
|
|
183
|
+
@pending_status_unsubscriptions[message.m_id] = message.attribute('sS').map do |item|
|
|
184
|
+
code = item['sCI']
|
|
185
|
+
name = item['n']
|
|
186
|
+
[code, name, @status_subscriptions.dig(component, code, name)]
|
|
187
|
+
end
|
|
188
|
+
end
|
|
189
|
+
|
|
190
|
+
def status_unsubscribe_acknowledged(original)
|
|
191
|
+
component = original.attribute('cId')
|
|
192
|
+
subscriptions = @pending_status_unsubscriptions.delete(original.m_id)
|
|
193
|
+
return unless subscriptions
|
|
194
|
+
|
|
195
|
+
subscriptions.each do |code, name, subscription|
|
|
196
|
+
next unless @status_subscriptions.dig(component, code, name).equal?(subscription)
|
|
197
|
+
|
|
198
|
+
remove_subscription_item(component, code, name)
|
|
199
|
+
end
|
|
164
200
|
end
|
|
165
201
|
|
|
166
202
|
def unsubscribe_to_status!(...)
|
data/lib/rsmp/version.rb
CHANGED
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: rsmp
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.52.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Emil Tin
|
|
@@ -218,6 +218,8 @@ files:
|
|
|
218
218
|
- lib/rsmp/options/options.rb
|
|
219
219
|
- lib/rsmp/options/schemas/config_common.json
|
|
220
220
|
- lib/rsmp/options/schemas/log.json
|
|
221
|
+
- lib/rsmp/options/schemas/required_settings.json
|
|
222
|
+
- lib/rsmp/options/schemas/required_supervisor_settings.json
|
|
221
223
|
- lib/rsmp/options/schemas/site.json
|
|
222
224
|
- lib/rsmp/options/schemas/supervisor.json
|
|
223
225
|
- lib/rsmp/options/schemas/supervisor_site.json
|