novacloud_client 0.1.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 +7 -0
- data/.rubocop_todo.yml +5 -0
- data/CHANGELOG.md +5 -0
- data/LICENSE.txt +21 -0
- data/README.md +142 -0
- data/Rakefile +12 -0
- data/lib/novacloud_client/client.rb +110 -0
- data/lib/novacloud_client/configuration.rb +31 -0
- data/lib/novacloud_client/errors.rb +26 -0
- data/lib/novacloud_client/middleware/authentication.rb +49 -0
- data/lib/novacloud_client/middleware/error_handler.rb +62 -0
- data/lib/novacloud_client/objects/base.rb +49 -0
- data/lib/novacloud_client/objects/control_log_entry.rb +21 -0
- data/lib/novacloud_client/objects/control_result.rb +55 -0
- data/lib/novacloud_client/objects/player.rb +37 -0
- data/lib/novacloud_client/objects/player_status.rb +29 -0
- data/lib/novacloud_client/objects/queued_request.rb +17 -0
- data/lib/novacloud_client/objects/screen.rb +21 -0
- data/lib/novacloud_client/objects/screen_detail.rb +29 -0
- data/lib/novacloud_client/objects/screen_monitor.rb +12 -0
- data/lib/novacloud_client/objects/solutions/offline_export_result.rb +80 -0
- data/lib/novacloud_client/objects/solutions/over_spec_detection_result.rb +80 -0
- data/lib/novacloud_client/objects/solutions/publish_result.rb +32 -0
- data/lib/novacloud_client/resources/base.rb +29 -0
- data/lib/novacloud_client/resources/concerns/payload_serializer.rb +46 -0
- data/lib/novacloud_client/resources/control.rb +250 -0
- data/lib/novacloud_client/resources/logs.rb +39 -0
- data/lib/novacloud_client/resources/players.rb +120 -0
- data/lib/novacloud_client/resources/scheduled_control.rb +221 -0
- data/lib/novacloud_client/resources/screens.rb +66 -0
- data/lib/novacloud_client/resources/solutions.rb +154 -0
- data/lib/novacloud_client/support/key_transform.rb +32 -0
- data/lib/novacloud_client/version.rb +5 -0
- data/lib/novacloud_client.rb +9 -0
- data/sig/novacloud_client.rbs +4 -0
- metadata +97 -0
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "base"
|
|
4
|
+
require_relative "../objects/player"
|
|
5
|
+
require_relative "../objects/player_status"
|
|
6
|
+
require_relative "../objects/queued_request"
|
|
7
|
+
|
|
8
|
+
module NovacloudClient
|
|
9
|
+
module Resources
|
|
10
|
+
# Resource wrapper for player-related endpoints.
|
|
11
|
+
#
|
|
12
|
+
# @example Fetch the first page of configured players
|
|
13
|
+
# client.players.list(count: 50)
|
|
14
|
+
#
|
|
15
|
+
# @example Fetch online status by player IDs
|
|
16
|
+
# client.players.statuses(player_ids: ["player-1", "player-2"])
|
|
17
|
+
#
|
|
18
|
+
# @example Request running status callbacks
|
|
19
|
+
# client.players.running_status(
|
|
20
|
+
# player_ids: ["player-1"],
|
|
21
|
+
# commands: ["screenshot"],
|
|
22
|
+
# notice_url: "https://example.com/callback"
|
|
23
|
+
# )
|
|
24
|
+
class Players < Base
|
|
25
|
+
MAX_BATCH = 100
|
|
26
|
+
CONFIG_STATUS_COMMANDS = %w[
|
|
27
|
+
volumeValue
|
|
28
|
+
brightnessValue
|
|
29
|
+
videoSourceValue
|
|
30
|
+
timeValue
|
|
31
|
+
screenPowerStatus
|
|
32
|
+
syncPlayStatus
|
|
33
|
+
powerStatus
|
|
34
|
+
].freeze
|
|
35
|
+
|
|
36
|
+
# Retrieve players with optional pagination and fuzzy name filtering.
|
|
37
|
+
#
|
|
38
|
+
# @param start [Integer] pagination offset provided by NovaCloud (defaults to 0)
|
|
39
|
+
# @param count [Integer] number of records to request (NovaCloud default is 20)
|
|
40
|
+
# @param name [String, nil] optional fuzzy match on the player name
|
|
41
|
+
# @return [Array<NovacloudClient::Objects::Player>]
|
|
42
|
+
def list(start: 0, count: 20, name: nil)
|
|
43
|
+
params = { start: start, count: count }
|
|
44
|
+
params[:name] = name if name
|
|
45
|
+
|
|
46
|
+
response = get("/v2/player/list", params: params)
|
|
47
|
+
rows = response.fetch("rows", [])
|
|
48
|
+
rows.map { |attrs| Objects::Player.new(attrs) }
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
# Retrieve current online status for a set of players.
|
|
52
|
+
#
|
|
53
|
+
# @param player_ids [Array<String>, nil] NovaCloud player identifiers (max #{MAX_BATCH})
|
|
54
|
+
# @param player_sns [Array<String>, nil] player serial numbers (max #{MAX_BATCH})
|
|
55
|
+
# @return [Array<NovacloudClient::Objects::PlayerStatus>]
|
|
56
|
+
# @raise [ArgumentError] when no identifiers are provided
|
|
57
|
+
def statuses(player_ids: nil, player_sns: nil)
|
|
58
|
+
payload = build_status_payload(player_ids: player_ids, player_sns: player_sns)
|
|
59
|
+
response = post("/v2/player/current/online-status", params: payload)
|
|
60
|
+
Array(response).map { |attrs| Objects::PlayerStatus.new(attrs) }
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
# Enqueue a running-status command and receive a queued request reference.
|
|
64
|
+
#
|
|
65
|
+
# @param player_ids [Array<String>] NovaCloud player identifiers (max #{MAX_BATCH})
|
|
66
|
+
# @param commands [Array<String>] running status command names defined by NovaCloud
|
|
67
|
+
# @param notice_url [String] HTTPS callback endpoint for async results
|
|
68
|
+
# @return [NovacloudClient::Objects::QueuedRequest]
|
|
69
|
+
# @raise [ArgumentError] when validation fails
|
|
70
|
+
def running_status(player_ids:, commands:, notice_url:)
|
|
71
|
+
validate_player_ids!(player_ids, max: MAX_BATCH)
|
|
72
|
+
raise ArgumentError, "commands cannot be empty" if commands.nil? || commands.empty?
|
|
73
|
+
raise ArgumentError, "notice_url is required" if notice_url.to_s.strip.empty?
|
|
74
|
+
|
|
75
|
+
payload = {
|
|
76
|
+
playerIds: player_ids,
|
|
77
|
+
commands: commands,
|
|
78
|
+
noticeUrl: notice_url
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
response = post("/v2/player/current/running-status", params: payload)
|
|
82
|
+
Objects::QueuedRequest.new(response)
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
# Convenience wrapper around `running_status` for configuration polling.
|
|
86
|
+
# Uses NovaCloud's recommended command set by default.
|
|
87
|
+
def config_status(player_ids:, notice_url:, commands: CONFIG_STATUS_COMMANDS)
|
|
88
|
+
running_status(player_ids: player_ids, commands: commands, notice_url: notice_url)
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
# Delegate to the control resource for NTP time synchronization settings.
|
|
92
|
+
def ntp_sync(player_ids:, server:, enable:)
|
|
93
|
+
client.control.ntp_sync(player_ids: player_ids, server: server, enable: enable)
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
# Delegate to the control resource for synchronous playback toggles.
|
|
97
|
+
def synchronous_playback(player_ids:, option:)
|
|
98
|
+
client.control.synchronous_playback(player_ids: player_ids, option: option)
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
private
|
|
102
|
+
|
|
103
|
+
def build_status_payload(player_ids:, player_sns:)
|
|
104
|
+
payload = {}
|
|
105
|
+
if player_ids
|
|
106
|
+
validate_player_ids!(player_ids, max: MAX_BATCH)
|
|
107
|
+
payload[:playerIds] = player_ids
|
|
108
|
+
end
|
|
109
|
+
if player_sns
|
|
110
|
+
validate_player_ids!(player_sns, max: MAX_BATCH)
|
|
111
|
+
payload[:playerSns] = player_sns
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
raise ArgumentError, "provide player_ids or player_sns" if payload.empty?
|
|
115
|
+
|
|
116
|
+
payload
|
|
117
|
+
end
|
|
118
|
+
end
|
|
119
|
+
end
|
|
120
|
+
end
|
|
@@ -0,0 +1,221 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "base"
|
|
4
|
+
require_relative "../objects/control_result"
|
|
5
|
+
require_relative "../support/key_transform"
|
|
6
|
+
|
|
7
|
+
module NovacloudClient
|
|
8
|
+
module Resources
|
|
9
|
+
# Resource wrapper for scheduled control operations.
|
|
10
|
+
class ScheduledControl < Base
|
|
11
|
+
MAX_BATCH = 100
|
|
12
|
+
|
|
13
|
+
# Configure scheduled screen status plans for players.
|
|
14
|
+
def screen_status(player_ids:, schedules:)
|
|
15
|
+
normalized = normalize_schedules(schedules) do |base, original|
|
|
16
|
+
wk = fetch_optional(original, :week_days, "week_days", :weekDays, "weekDays")
|
|
17
|
+
base[:week_days] = Array(wk || [])
|
|
18
|
+
|
|
19
|
+
base[:exec_time] = fetch_required(original, :exec_time, "exec_time", :execTime, "execTime")
|
|
20
|
+
|
|
21
|
+
status = fetch_required(original, :status, "status")
|
|
22
|
+
base[:status] = normalize_screen_status(status)
|
|
23
|
+
base
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
schedule_request(
|
|
27
|
+
endpoint: "/v2/player/scheduled-control/screen-status",
|
|
28
|
+
player_ids: player_ids,
|
|
29
|
+
schedules: normalized
|
|
30
|
+
)
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
# Configure scheduled reboots for players.
|
|
34
|
+
def reboot(player_ids:, schedules:)
|
|
35
|
+
normalized = normalize_schedules(schedules) do |base, original|
|
|
36
|
+
base[:exec_time] = fetch_required(original, :exec_time, "exec_time", :execTime, "execTime")
|
|
37
|
+
base
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
schedule_request(
|
|
41
|
+
endpoint: "/v2/player/scheduled-control/reboot",
|
|
42
|
+
player_ids: player_ids,
|
|
43
|
+
schedules: normalized
|
|
44
|
+
)
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
# Configure scheduled volume adjustments for players.
|
|
48
|
+
def volume(player_ids:, schedules:)
|
|
49
|
+
normalized = normalize_schedules(schedules) do |base, original|
|
|
50
|
+
base[:exec_time] = fetch_required(original, :exec_time, "exec_time", :execTime, "execTime")
|
|
51
|
+
|
|
52
|
+
value = fetch_required(original, :value, "value")
|
|
53
|
+
validate_percentage!(value, "value")
|
|
54
|
+
base[:value] = value.to_i
|
|
55
|
+
|
|
56
|
+
wk = fetch_optional(original, :week_days, "week_days", :weekDays, "weekDays")
|
|
57
|
+
base[:week_days] = Array(wk || [])
|
|
58
|
+
base
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
schedule_request(
|
|
62
|
+
endpoint: "/v2/player/scheduled-control/volume",
|
|
63
|
+
player_ids: player_ids,
|
|
64
|
+
schedules: normalized
|
|
65
|
+
)
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
# Configure scheduled brightness adjustments for players.
|
|
69
|
+
# @param auto_profile [Hash, nil] optional auto brightness profile
|
|
70
|
+
def brightness(player_ids:, schedules:, auto_profile: nil)
|
|
71
|
+
normalized = normalize_schedules(schedules) do |base, original|
|
|
72
|
+
base[:exec_time] = fetch_required(original, :exec_time, "exec_time", :execTime, "execTime")
|
|
73
|
+
|
|
74
|
+
type = fetch_required(original, :type, "type")
|
|
75
|
+
base[:type] = type.to_i
|
|
76
|
+
|
|
77
|
+
value = fetch_optional(original, :value, "value")
|
|
78
|
+
base[:value] = value.to_i if value
|
|
79
|
+
base
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
extra_payload = {}
|
|
83
|
+
extra_payload[:auto_profile] = auto_profile if auto_profile
|
|
84
|
+
|
|
85
|
+
schedule_request(
|
|
86
|
+
endpoint: "/v2/player/scheduled-control/brightness",
|
|
87
|
+
player_ids: player_ids,
|
|
88
|
+
schedules: normalized,
|
|
89
|
+
extra_payload: extra_payload
|
|
90
|
+
)
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
# Configure scheduled video source switching for players.
|
|
94
|
+
def video_source(player_ids:, schedules:)
|
|
95
|
+
normalized = normalize_schedules(schedules) do |base, original|
|
|
96
|
+
base[:exec_time] = fetch_required(original, :exec_time, "exec_time", :execTime, "execTime")
|
|
97
|
+
|
|
98
|
+
source = fetch_required(original, :source, "source")
|
|
99
|
+
base[:source] = source
|
|
100
|
+
|
|
101
|
+
wk = fetch_optional(original, :week_days, "week_days", :weekDays, "weekDays")
|
|
102
|
+
base[:week_days] = Array(wk) unless wk.nil?
|
|
103
|
+
|
|
104
|
+
base
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
schedule_request(
|
|
108
|
+
endpoint: "/v2/player/scheduled-control/video-source",
|
|
109
|
+
player_ids: player_ids,
|
|
110
|
+
schedules: normalized
|
|
111
|
+
)
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
private
|
|
115
|
+
|
|
116
|
+
def normalize_schedules(schedules)
|
|
117
|
+
raise ArgumentError, "schedules must be provided" if schedules.nil?
|
|
118
|
+
|
|
119
|
+
array = schedules.is_a?(Array) ? schedules : [schedules]
|
|
120
|
+
raise ArgumentError, "schedules cannot be empty" if array.empty?
|
|
121
|
+
|
|
122
|
+
array.map do |entry|
|
|
123
|
+
base, original = build_base_schedule(entry)
|
|
124
|
+
block_given? ? yield(base, original) : base
|
|
125
|
+
end
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
def build_base_schedule(entry)
|
|
129
|
+
hash = entry.respond_to?(:to_h) ? entry.to_h : entry
|
|
130
|
+
raise ArgumentError, "schedule entries must be hash-like" unless hash.is_a?(Hash)
|
|
131
|
+
|
|
132
|
+
# Only set date fields; callers append exec_time, values, and week_days
|
|
133
|
+
# for each endpoint-specific payload requirement.
|
|
134
|
+
base = {
|
|
135
|
+
start_date: fetch_required(hash, :start_date, "start_date", :startDate, "startDate"),
|
|
136
|
+
end_date: fetch_required(hash, :end_date, "end_date", :endDate, "endDate")
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
[base, hash]
|
|
140
|
+
end
|
|
141
|
+
|
|
142
|
+
def schedule_request(endpoint:, player_ids:, schedules:, extra_payload: {})
|
|
143
|
+
validate_player_ids!(player_ids, max: MAX_BATCH)
|
|
144
|
+
|
|
145
|
+
payload = {
|
|
146
|
+
player_ids: player_ids,
|
|
147
|
+
schedules: schedules
|
|
148
|
+
}
|
|
149
|
+
payload.merge!(extra_payload) if extra_payload && !extra_payload.empty?
|
|
150
|
+
cleaned = cleanup_schedule_payload(endpoint, payload)
|
|
151
|
+
|
|
152
|
+
response = post(endpoint, params: Support::KeyTransform.camelize_component(cleaned))
|
|
153
|
+
Objects::ControlResult.new(response)
|
|
154
|
+
end
|
|
155
|
+
|
|
156
|
+
# Remove empty weekDays for endpoints that expect them omitted in tests/docs
|
|
157
|
+
def cleanup_schedule_payload(endpoint, payload)
|
|
158
|
+
return payload unless omit_empty_week_days?(endpoint)
|
|
159
|
+
|
|
160
|
+
cleaned = Marshal.load(Marshal.dump(payload))
|
|
161
|
+
Array(cleaned[:schedules]).each do |schedule|
|
|
162
|
+
next unless schedule.key?(:week_days)
|
|
163
|
+
|
|
164
|
+
schedule.delete(:week_days) if blank_week_days?(schedule[:week_days])
|
|
165
|
+
end
|
|
166
|
+
|
|
167
|
+
cleaned
|
|
168
|
+
end
|
|
169
|
+
|
|
170
|
+
def omit_empty_week_days?(endpoint)
|
|
171
|
+
[
|
|
172
|
+
"/v2/player/scheduled-control/brightness",
|
|
173
|
+
"/v2/player/scheduled-control/video-source"
|
|
174
|
+
].include?(endpoint.to_s)
|
|
175
|
+
end
|
|
176
|
+
|
|
177
|
+
def blank_week_days?(values)
|
|
178
|
+
return true if values.nil?
|
|
179
|
+
return values.empty? if values.respond_to?(:empty?)
|
|
180
|
+
|
|
181
|
+
false
|
|
182
|
+
end
|
|
183
|
+
|
|
184
|
+
def fetch_required(hash, *keys)
|
|
185
|
+
value = fetch_optional(hash, *keys)
|
|
186
|
+
raise ArgumentError, "#{keys.first} is required" if value.nil? || value.to_s.strip.empty?
|
|
187
|
+
|
|
188
|
+
value
|
|
189
|
+
end
|
|
190
|
+
|
|
191
|
+
def fetch_optional(hash, *keys, default: nil)
|
|
192
|
+
keys.each do |key|
|
|
193
|
+
string_key = key.to_s
|
|
194
|
+
return hash[key] if hash.key?(key)
|
|
195
|
+
return hash[string_key] if hash.key?(string_key)
|
|
196
|
+
|
|
197
|
+
symbol_key = string_key.to_sym
|
|
198
|
+
return hash[symbol_key] if hash.key?(symbol_key)
|
|
199
|
+
end
|
|
200
|
+
|
|
201
|
+
default
|
|
202
|
+
end
|
|
203
|
+
|
|
204
|
+
def validate_percentage!(value, field)
|
|
205
|
+
int_value = value.to_i
|
|
206
|
+
return if int_value.between?(0, 100)
|
|
207
|
+
|
|
208
|
+
raise ArgumentError, "#{field} must be between 0 and 100"
|
|
209
|
+
end
|
|
210
|
+
|
|
211
|
+
def normalize_screen_status(status)
|
|
212
|
+
case status
|
|
213
|
+
when :open, "open", "OPEN" then "OPEN"
|
|
214
|
+
when :close, "close", "CLOSE", :closed then "CLOSE"
|
|
215
|
+
else
|
|
216
|
+
raise ArgumentError, "status must be OPEN or CLOSE"
|
|
217
|
+
end
|
|
218
|
+
end
|
|
219
|
+
end
|
|
220
|
+
end
|
|
221
|
+
end
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "base"
|
|
4
|
+
require_relative "../objects/screen"
|
|
5
|
+
require_relative "../objects/screen_monitor"
|
|
6
|
+
require_relative "../objects/screen_detail"
|
|
7
|
+
|
|
8
|
+
module NovacloudClient
|
|
9
|
+
module Resources
|
|
10
|
+
# Resource wrapper around VNNOXCare screen inventory endpoints.
|
|
11
|
+
class Screens < Base
|
|
12
|
+
# Retrieve paginated list of screens and monitoring metadata.
|
|
13
|
+
#
|
|
14
|
+
# @param start [Integer] pagination offset provided by VNNOXCare (defaults to 0)
|
|
15
|
+
# @param count [Integer] number of records to request (defaults to 20)
|
|
16
|
+
# @param status [Integer, nil] optional status filter defined by VNNOXCare (e.g., 1 for online)
|
|
17
|
+
# @return [Array<NovacloudClient::Objects::Screen>]
|
|
18
|
+
def list(start: 0, count: 20, status: nil)
|
|
19
|
+
params = { start: start, count: count }
|
|
20
|
+
params[:status] = status if status
|
|
21
|
+
|
|
22
|
+
response = get("/v2/device-status-monitor/screen/list", params: params)
|
|
23
|
+
items = response.fetch("items", [])
|
|
24
|
+
items.map { |attrs| Objects::Screen.new(attrs) }
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
# Retrieve detailed monitoring metrics for a single screen by serial number.
|
|
28
|
+
#
|
|
29
|
+
# @param sn [String] screen serial number registered in VNNOXCare
|
|
30
|
+
# @return [NovacloudClient::Objects::ScreenMonitor]
|
|
31
|
+
# @raise [ArgumentError] when serial number is blank
|
|
32
|
+
def monitor(serial_number: nil, **kwargs)
|
|
33
|
+
serial = serial_number || kwargs[:sn]
|
|
34
|
+
validate_presence!(serial, "serial_number")
|
|
35
|
+
|
|
36
|
+
response = get("/v2/device-status-monitor/screen/monitor/#{serial}")
|
|
37
|
+
Objects::ScreenMonitor.new(response)
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
# Retrieve deep detail payloads for up to 10 screens at once.
|
|
41
|
+
#
|
|
42
|
+
# @param sn_list [Array<String>] list of screen serial numbers (max 10)
|
|
43
|
+
# @return [Array<NovacloudClient::Objects::ScreenDetail>]
|
|
44
|
+
# @raise [ArgumentError] when validation fails
|
|
45
|
+
def detail(sn_list:)
|
|
46
|
+
validate_sn_list!(sn_list)
|
|
47
|
+
|
|
48
|
+
response = post("/v2/device-status-monitor/all", params: { snList: sn_list })
|
|
49
|
+
values = response.fetch("value", [])
|
|
50
|
+
values.map { |attrs| Objects::ScreenDetail.new(attrs) }
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
private
|
|
54
|
+
|
|
55
|
+
def validate_sn_list!(sn_list)
|
|
56
|
+
validate_presence!(sn_list, "sn_list")
|
|
57
|
+
raise ArgumentError, "sn_list cannot be empty" if sn_list.empty?
|
|
58
|
+
raise ArgumentError, "maximum 10 screen serial numbers allowed" if sn_list.size > 10
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def validate_presence!(value, field)
|
|
62
|
+
raise ArgumentError, "#{field} is required" if value.to_s.strip.empty?
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
end
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "base"
|
|
4
|
+
require_relative "../objects/solutions/publish_result"
|
|
5
|
+
require_relative "../objects/solutions/offline_export_result"
|
|
6
|
+
require_relative "../objects/solutions/over_spec_detection_result"
|
|
7
|
+
require_relative "../support/key_transform"
|
|
8
|
+
|
|
9
|
+
module NovacloudClient
|
|
10
|
+
module Resources
|
|
11
|
+
# Resource wrapper for solution publishing workflows.
|
|
12
|
+
class Solutions < Base
|
|
13
|
+
# Publish a single-page emergency insertion program.
|
|
14
|
+
#
|
|
15
|
+
# @param player_ids [Array<String>] target players
|
|
16
|
+
# @param attribute [Hash, #to_h] emergency-specific metadata
|
|
17
|
+
# @param page [Hash, #to_h] definition of the single emergency page
|
|
18
|
+
# @return [NovacloudClient::Objects::Solutions::PublishResult]
|
|
19
|
+
def emergency_page(player_ids:, attribute:, page:)
|
|
20
|
+
validate_player_ids!(player_ids)
|
|
21
|
+
validate_presence!(attribute, "attribute")
|
|
22
|
+
validate_presence!(page, "page")
|
|
23
|
+
|
|
24
|
+
payload = Support::KeyTransform.camelize_component(
|
|
25
|
+
playerIds: player_ids,
|
|
26
|
+
attribute: attribute,
|
|
27
|
+
page: page
|
|
28
|
+
)
|
|
29
|
+
|
|
30
|
+
response = post("/v2/player/emergency-program/page", params: payload)
|
|
31
|
+
build_publish_result(response)
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
# Cancel any active emergency program for the provided players.
|
|
35
|
+
#
|
|
36
|
+
# @param player_ids [Array<String>]
|
|
37
|
+
# @return [NovacloudClient::Objects::Solutions::PublishResult]
|
|
38
|
+
def cancel_emergency(player_ids:)
|
|
39
|
+
validate_player_ids!(player_ids)
|
|
40
|
+
|
|
41
|
+
payload = Support::KeyTransform.camelize_component(playerIds: player_ids)
|
|
42
|
+
response = post("/v2/player/emergency-program/cancel", params: payload)
|
|
43
|
+
build_publish_result(response)
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
# Publish a normal (common) solution schedule to the given players.
|
|
47
|
+
#
|
|
48
|
+
# @param player_ids [Array<String>] target players
|
|
49
|
+
# @param pages [Array<Hash, #to_h>] program pages to publish
|
|
50
|
+
# @param schedule [Hash, #to_h, nil] optional schedule definition
|
|
51
|
+
# @return [NovacloudClient::Objects::Solutions::PublishResult]
|
|
52
|
+
def common_solution(player_ids:, pages:, schedule: nil)
|
|
53
|
+
validate_player_ids!(player_ids)
|
|
54
|
+
validate_presence!(pages, "pages")
|
|
55
|
+
|
|
56
|
+
payload = Support::KeyTransform.camelize_component(
|
|
57
|
+
playerIds: player_ids,
|
|
58
|
+
pages: pages
|
|
59
|
+
)
|
|
60
|
+
payload[:schedule] = Support::KeyTransform.camelize_component(schedule) if schedule
|
|
61
|
+
|
|
62
|
+
response = post("/v2/player/program/normal", params: payload)
|
|
63
|
+
build_publish_result(response)
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
# Export an offline program bundle without publishing it to devices.
|
|
67
|
+
#
|
|
68
|
+
# @param program_type [Integer] NovaCloud program type identifier
|
|
69
|
+
# @param pages [Array<Hash, #to_h>] program pages to include
|
|
70
|
+
# @param plan_version [String, nil] optional plan version (defaults to V2 when provided)
|
|
71
|
+
# @param schedule [Hash, #to_h, nil] optional playback schedule definition
|
|
72
|
+
# @param options [Hash] additional top-level attributes supported by the API
|
|
73
|
+
# @return [NovacloudClient::Objects::Solutions::OfflineExportResult]
|
|
74
|
+
def offline_export(program_type:, pages:, plan_version: nil, schedule: nil, **options)
|
|
75
|
+
validate_presence!(pages, "pages")
|
|
76
|
+
raise ArgumentError, "program_type is required" if program_type.nil?
|
|
77
|
+
|
|
78
|
+
payload = {
|
|
79
|
+
program_type: program_type,
|
|
80
|
+
pages: pages,
|
|
81
|
+
plan_version: plan_version,
|
|
82
|
+
schedule: schedule
|
|
83
|
+
}.merge(options)
|
|
84
|
+
|
|
85
|
+
camelized = Support::KeyTransform.camelize_component(compact_hash(payload))
|
|
86
|
+
|
|
87
|
+
response = post("/v2/player/program/offline-export", params: camelized)
|
|
88
|
+
Objects::Solutions::OfflineExportResult.new(response || {})
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
# Enable or disable over-specification detection on the selected players.
|
|
92
|
+
#
|
|
93
|
+
# @param player_ids [Array<String>] target players
|
|
94
|
+
# @param enable [Boolean] whether detection should be enabled
|
|
95
|
+
# @return [NovacloudClient::Objects::Solutions::PublishResult]
|
|
96
|
+
def set_over_spec_detection(player_ids:, enable:)
|
|
97
|
+
validate_player_ids!(player_ids)
|
|
98
|
+
validate_boolean!(enable, "enable")
|
|
99
|
+
|
|
100
|
+
payload = {
|
|
101
|
+
playerIds: player_ids,
|
|
102
|
+
enable: enable
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
response = post("/v2/player/immediateControl/over-specification-options", params: payload)
|
|
106
|
+
build_publish_result(response)
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
# Validate whether a program will exceed the hardware specifications.
|
|
110
|
+
#
|
|
111
|
+
# @param player_ids [Array<String>] players against which to test
|
|
112
|
+
# @param pages [Array<Hash, #to_h>] the program definition to analyse
|
|
113
|
+
# @return [NovacloudClient::Objects::Solutions::OverSpecDetectionResult]
|
|
114
|
+
def program_over_spec_detection(player_ids:, pages:)
|
|
115
|
+
validate_player_ids!(player_ids)
|
|
116
|
+
validate_presence!(pages, "pages")
|
|
117
|
+
|
|
118
|
+
payload = Support::KeyTransform.camelize_component(
|
|
119
|
+
playerIds: player_ids,
|
|
120
|
+
pages: pages
|
|
121
|
+
)
|
|
122
|
+
|
|
123
|
+
response = post("/v2/player/program/over-specification-check", params: payload)
|
|
124
|
+
Objects::Solutions::OverSpecDetectionResult.new(response || {})
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
private
|
|
128
|
+
|
|
129
|
+
def compact_hash(hash)
|
|
130
|
+
hash.each_with_object({}) do |(key, value), result|
|
|
131
|
+
next if value.nil?
|
|
132
|
+
|
|
133
|
+
result[key] = value
|
|
134
|
+
end
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
def build_publish_result(response)
|
|
138
|
+
Objects::Solutions::PublishResult.new(response || {})
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
def validate_presence!(value, field)
|
|
142
|
+
return unless value.nil? || (value.respond_to?(:empty?) && value.empty?)
|
|
143
|
+
|
|
144
|
+
raise ArgumentError, "#{field} is required"
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
def validate_boolean!(value, field)
|
|
148
|
+
return if [true, false].include?(value)
|
|
149
|
+
|
|
150
|
+
raise ArgumentError, "#{field} must be true or false"
|
|
151
|
+
end
|
|
152
|
+
end
|
|
153
|
+
end
|
|
154
|
+
end
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module NovacloudClient
|
|
4
|
+
module Support
|
|
5
|
+
# Utilities for converting snake_case hashes to camelCase payloads.
|
|
6
|
+
module KeyTransform
|
|
7
|
+
module_function
|
|
8
|
+
|
|
9
|
+
def camelize_component(component)
|
|
10
|
+
case component
|
|
11
|
+
when Hash
|
|
12
|
+
component.each_with_object({}) do |(key, value), result|
|
|
13
|
+
result[camelize_key(key)] = camelize_component(value)
|
|
14
|
+
end
|
|
15
|
+
when Array
|
|
16
|
+
component.map { |item| camelize_component(item) }
|
|
17
|
+
else
|
|
18
|
+
component.respond_to?(:to_h) ? camelize_component(component.to_h) : component
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def camelize_key(key)
|
|
23
|
+
return key unless key.is_a?(String) || key.is_a?(Symbol)
|
|
24
|
+
|
|
25
|
+
string = key.to_s
|
|
26
|
+
normalized = string.gsub(/([a-z\d])([A-Z])/, "\\1_\\2").gsub("-", "_").downcase
|
|
27
|
+
parts = normalized.split("_")
|
|
28
|
+
parts.map.with_index { |segment, index| index.zero? ? segment : segment.capitalize }.join
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|