roku_builder 4.25.5 → 4.26.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (35) hide show
  1. checksums.yaml +4 -4
  2. data/intergration/roku_builder/test_helper.rb +2 -3
  3. data/intergration/roku_builder/test_profiler.rb +1 -1
  4. data/lib/roku_builder/config_parser.rb +4 -5
  5. data/lib/roku_builder/device_manager.rb +100 -0
  6. data/lib/roku_builder/plugins/core.rb +3 -1
  7. data/lib/roku_builder/plugins/inspector.rb +36 -26
  8. data/lib/roku_builder/plugins/line_inspector.rb +27 -6
  9. data/lib/roku_builder/plugins/linker.rb +29 -22
  10. data/lib/roku_builder/plugins/loader.rb +36 -15
  11. data/lib/roku_builder/plugins/monitor.rb +19 -17
  12. data/lib/roku_builder/plugins/navigator.rb +40 -34
  13. data/lib/roku_builder/plugins/packager.rb +109 -92
  14. data/lib/roku_builder/plugins/profiler.rb +24 -15
  15. data/lib/roku_builder/plugins/tester.rb +15 -13
  16. data/lib/roku_builder/util.rb +39 -17
  17. data/lib/roku_builder/version.rb +1 -1
  18. data/lib/roku_builder.rb +3 -19
  19. data/roku_builder.gemspec +3 -2
  20. data/test/roku_builder/plugins/test_analyzer.rb +19 -0
  21. data/test/roku_builder/plugins/test_core.rb +2 -3
  22. data/test/roku_builder/plugins/test_inspector.rb +48 -8
  23. data/test/roku_builder/plugins/test_linker.rb +66 -10
  24. data/test/roku_builder/plugins/test_loader.rb +44 -15
  25. data/test/roku_builder/plugins/test_monitor.rb +33 -16
  26. data/test/roku_builder/plugins/test_navigator.rb +55 -14
  27. data/test/roku_builder/plugins/test_packager.rb +118 -34
  28. data/test/roku_builder/plugins/test_profiler.rb +153 -78
  29. data/test/roku_builder/plugins/test_tester.rb +20 -5
  30. data/test/roku_builder/test_device_manager.rb +187 -0
  31. data/test/roku_builder/test_files/analyzer_test/dont_use_hello_world.json +11 -0
  32. data/test/roku_builder/test_files/analyzer_test/linter_positive_match.json +13 -0
  33. data/test/roku_builder/test_helper.rb +6 -0
  34. data/test/roku_builder/test_roku_builder.rb +5 -42
  35. metadata +27 -6
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 852ae89e38456c17e455675a4901b37914f6109a47202137efecef4e4cb68309
4
- data.tar.gz: 95f9cd039b170352a7f7868ec9ff52663e6e22add7dd3bf883a96112ea9680d4
3
+ metadata.gz: 26fe47ba62ff48e37fe3bd5dda06ff687b79c30dc67bd7dd216747363cf6efbd
4
+ data.tar.gz: cc5480949806270e823d1a177219a48f541abbdde571c949022d9ae16a3cf11a
5
5
  SHA512:
6
- metadata.gz: 14157cf2cfb8268d30f9acee98af461e7b0c500a57bb6ec2e95254e1b7c3859087eb5ed0fd2e57b9ed660aa1c825f1d8623250a6a409c8062a2d77ee8d74f156
7
- data.tar.gz: 6e50b5767da2018fd30550766fb681167a1ded341d85b810eae2ba95517080c16df74f46aad1bc5ec37658e3cb35b94e1c7659b1e36292fd1daae7aef17d4415
6
+ metadata.gz: 7dd09cf68f00832afb02e2797643d6babcb81c6cb943523c883b77b0f2d361bb03fe642c8fe54f685e9565dea175d23974fd1fa9f4ccbbb79be53e559471eb5a
7
+ data.tar.gz: b847c41f764a41db7421c0cba806e7ed264695ec0283f8af28a0a8dad1fdeda731c66c6757bb67faa5ef74e19a45488c33c61f97e3df837d5b9eb1903be12fe3
@@ -15,7 +15,7 @@ require "minitest/autorun"
15
15
  require "minitest/utils"
16
16
  require "securerandom"
17
17
 
18
- ROKU_IP = "192.168.1.127"
18
+ ROKU_IP = "192.168.11.114"
19
19
 
20
20
  module Minitest
21
21
  module Assertions
@@ -114,8 +114,7 @@ module Helpers
114
114
  default: :project1,
115
115
  project1: {
116
116
  directory: root_dir,
117
- folders: ["resources","source","components"],
118
- files: ["manifest","file.tmp"],
117
+ source_files: ["images","source","components", "manifest","file.tmp"],
119
118
  app_name: "App Name",
120
119
  stage_method: :script,
121
120
  stages:{
@@ -48,7 +48,7 @@ module RokuBuilder
48
48
  `#{roku} --sideload --working`
49
49
  assert_log @uuid
50
50
  output = `#{roku} --profile images`
51
- assert_match(/Available memory/, output)
51
+ assert_match(/Available texture memory/, output)
52
52
  end
53
53
  def test_profile_textures
54
54
  `#{roku} --sideload --working`
@@ -21,7 +21,7 @@ module RokuBuilder
21
21
 
22
22
  def parse_config
23
23
  process_in_argument
24
- setup_device
24
+ setup_devices
25
25
  setup_project
26
26
  setup_in_out_file
27
27
  setup_project_config
@@ -35,10 +35,9 @@ module RokuBuilder
35
35
  @options[:in] = File.expand_path(@options[:in]) if @options[:in]
36
36
  end
37
37
 
38
- def setup_device
39
- @options[:device] = @config[:devices][:default] unless @options[:device]
40
- @parsed[:device_config] = @config[:devices][@options[:device].to_sym]
41
- raise ArgumentError, "Unknown device: #{@options[:device]}" unless @parsed[:device_config]
38
+ def setup_devices
39
+ @parsed[:device_default] = @config[:devices][:default]
40
+ @parsed[:devices] = @config[:devices].select{|key, value| :default != key}
42
41
  end
43
42
 
44
43
  def setup_project
@@ -0,0 +1,100 @@
1
+ # ********** Copyright Viacom, Inc. Apache 2.0 **********
2
+
3
+ module RokuBuilder
4
+
5
+ class DeviceManager
6
+
7
+ def initialize(options:, config:)
8
+ @config = config
9
+ @options = options
10
+ @timeout_duration = 3600
11
+ end
12
+
13
+ def reserve_device(no_lock: false)
14
+ message = "No Devices Found"
15
+ if @options[:device]
16
+ device = Device.new(@options[:device], @config.raw[:devices][@options[:device].to_sym])
17
+ return device if device_available!(device: device, no_lock: no_lock)
18
+ message = "Device #{@options[:device]} not found"
19
+ else
20
+ begin
21
+ Timeout::timeout(@timeout_duration) do
22
+ loop do
23
+ device = reserve_any(no_lock: no_lock)
24
+ if device
25
+ Logger.instance.info "Using Device: #{device.to_s}"
26
+ return device
27
+ end
28
+ break unless @options[:device_blocking]
29
+ end
30
+ end
31
+ rescue Timeout::Error
32
+ end
33
+ end
34
+ raise DeviceError, message
35
+ end
36
+
37
+ def release_device(device)
38
+ lock = lock_file(device)
39
+ File.delete(lock) if File.exist?(lock)
40
+ end
41
+
42
+ private
43
+
44
+ def reserve_any(no_lock: false)
45
+ default = @config.device_default
46
+ all_devices = @config.devices.keys.reject{|key, value| default == key}
47
+ all_devices.unshift(default)
48
+ all_devices.each do |device_name|
49
+ device = Device.new(device_name, @config.devices[device_name])
50
+ if device_available!(device: device, no_lock: no_lock)
51
+ return device
52
+ end
53
+ end
54
+ nil
55
+ end
56
+
57
+ def device_available!(device:, no_lock: false)
58
+ return false unless device_ping?(device)
59
+ return true if no_lock
60
+ file = lock_file(device)
61
+ lock = file.flock(File::LOCK_EX|File::LOCK_NB)
62
+ return false if lock == false
63
+ text = file.readlines
64
+ return false if text.count > 0
65
+ file.write($$)
66
+ file.pos = 0
67
+ text = file.readlines
68
+ if text[0] != $$.to_s
69
+ file.flock(File::LOCK_UN)
70
+ return false
71
+ end
72
+ true
73
+ end
74
+
75
+ def device_ping?(device)
76
+ ping = Net::Ping::External.new
77
+ ping.ping? device.ip, 1, 0.2, 1
78
+ end
79
+
80
+ def lock_file(device)
81
+ File.open(File.join(Dir.tmpdir, device.name), File::RDWR | File::APPEND | File::CREAT | File::BINARY | File::SHARE_DELETE) #"a+")
82
+ end
83
+
84
+ end
85
+
86
+ class Device
87
+ attr_accessor :name, :ip, :user, :password
88
+
89
+ def initialize(name, device_config)
90
+ @name = name.to_s
91
+ @ip = device_config[:ip]
92
+ @user = device_config[:user]
93
+ @password = device_config[:password]
94
+ end
95
+
96
+ def to_s
97
+ "#{name}:#{ip}"
98
+ end
99
+ end
100
+ end
@@ -67,7 +67,9 @@ module RokuBuilder
67
67
  end
68
68
  parser.on("-D", "--device ID", "Use a different device corresponding to the given ID") do |d|
69
69
  options[:device] = d
70
- options[:device_given] = true
70
+ end
71
+ parser.on("--device-blocking", "Block and wait for a device if none is ready. Does not work with --device") do
72
+ options[:device_blocking] = true
71
73
  end
72
74
  parser.on("-V", "--verbose", "Print Info message") do
73
75
  options[:verbose] = true
@@ -33,13 +33,15 @@ module RokuBuilder
33
33
  pkg = pkg+".pkg" unless pkg.end_with?(".pkg")
34
34
  # upload new key with password
35
35
  path = "/plugin_inspect"
36
- conn = multipart_connection
37
- payload = {
38
- mysubmit: "Inspect",
39
- passwd: options[:password],
40
- archive: Faraday::UploadIO.new(pkg, 'application/octet-stream')
41
- }
42
- response = conn.post path, payload
36
+ response = nil
37
+ multipart_connection do |conn|
38
+ payload = {
39
+ mysubmit: "Inspect",
40
+ passwd: options[:password],
41
+ archive: Faraday::UploadIO.new(pkg, 'application/octet-stream')
42
+ }
43
+ response = conn.post path, payload
44
+ end
43
45
 
44
46
  app_name = /App Name:\s*<\/td>\s*<td>\s*<font[^>]*>([^<]*)<\/font>\s*<\/td>/.match(response.body)
45
47
  dev_id = nil
@@ -75,30 +77,38 @@ module RokuBuilder
75
77
  # Capture a screencapture for the currently sideloaded app
76
78
  # @return [Boolean] Success
77
79
  def screencapture(options:)
78
- out = @config.out
79
- payload = {
80
- mysubmit: "Screenshot",
81
- passwd: @dev_password,
82
- archive: Faraday::UploadIO.new(File::NULL, 'application/octet-stream')
83
- }
84
- response = multipart_connection.post "/plugin_inspect", payload
80
+ get_device do |device|
81
+ out = @config.out
82
+ payload = {
83
+ mysubmit: "Screenshot",
84
+ passwd: @dev_password,
85
+ archive: Faraday::UploadIO.new(File::NULL, 'application/octet-stream')
86
+ }
87
+ response = nil
88
+ multipart_connection(device: device) do |conn|
89
+ response = conn.post "/plugin_inspect", payload
90
+ end
85
91
 
86
- path = /<img src="([^"]*)">/.match(response.body)
87
- raise ExecutionError, "Failed to capture screen" unless path
88
- path = path[1]
92
+ path = /<img src="([^"]*)">/.match(response.body)
93
+ raise ExecutionError, "Failed to capture screen" unless path
94
+ path = path[1]
89
95
 
90
- unless out[:file]
91
- out[:file] = /time=([^"]*)">/.match(response.body)
92
- out_ext = /dev.([^"]*)\?/.match(response.body)
93
- out[:file] = "dev_#{out[:file][1]}.#{out_ext[1]}" if out[:file]
94
- end
96
+ unless out[:file]
97
+ out[:file] = /time=([^"]*)">/.match(response.body)
98
+ out_ext = /dev.([^"]*)\?/.match(response.body)
99
+ out[:file] = "dev_#{out[:file][1]}.#{out_ext[1]}" if out[:file]
100
+ end
95
101
 
96
- response = simple_connection.get path
102
+ response = nil
103
+ simple_connection(device: device) do |conn|
104
+ response = conn.get path
105
+ end
97
106
 
98
- File.open(File.join(out[:folder], out[:file]), "wb") do |io|
99
- io.write(response.body)
107
+ File.open(File.join(out[:folder], out[:file]), "wb") do |io|
108
+ io.write(response.body)
109
+ end
110
+ @logger.info "Screen captured to #{File.join(out[:folder], out[:file])}"
100
111
  end
101
- @logger.info "Screen captured to #{File.join(out[:folder], out[:file])}"
102
112
  end
103
113
  end
104
114
  RokuBuilder.register_plugin(Inspector)
@@ -47,17 +47,38 @@ module RokuBuilder
47
47
  match = nil
48
48
  start = 0
49
49
  loop do
50
+ stop = to_check.length-1
51
+ pass_match = nil
52
+ if line_inspector[:pass_if_match]
53
+ if line_inspector[:case_sensitive]
54
+ pass_match = /#{line_inspector[:pass_test_regexp]}/.match(to_check[start..stop])
55
+ else
56
+ pass_match = /#{line_inspector[:pass_test_regexp]}/i.match(to_check[start..stop])
57
+ end
58
+ break unless pass_match
59
+ stop = to_check.index("\n", start)
60
+ stop ||= to_check.length-1
61
+ end
50
62
  if line_inspector[:case_sensitive]
51
- match = /#{line_inspector[:regex]}/.match(to_check, start)
63
+ match = /#{line_inspector[:regex]}/.match(to_check[start..stop])
52
64
  else
53
- match = /#{line_inspector[:regex]}/i.match(to_check, start)
65
+ match = /#{line_inspector[:regex]}/i.match(to_check[start..stop])
54
66
  end
55
- if match
56
- start = match.end(0)
57
- line_number = to_check[0..match.begin(0)].split("\n", -1).count - 1
67
+ if (not line_inspector[:pass_if_match] and match) or (line_inspector[:pass_if_match] and not match)
68
+ error_match = match
69
+ if match
70
+ start = match.end(0)
71
+ line_number = to_check[0..match.begin(0)].split("\n", -1).count - 1
72
+ else
73
+ error_match = pass_match
74
+ line_number = to_check[0..start].split("\n", -1).count - 1
75
+ start = stop
76
+ end
58
77
  unless lines_to_ignore.include?(line_number)
59
- add_warning(inspector: line_inspector, file: file_path, line: line_number, match: match)
78
+ add_warning(inspector: line_inspector, file: file_path, line: line_number, match: error_match)
60
79
  end
80
+ elsif line_inspector[:pass_if_match]
81
+ start = stop +1
61
82
  else
62
83
  break
63
84
  end
@@ -36,14 +36,16 @@ module RokuBuilder
36
36
  end
37
37
 
38
38
  # Deeplink to an app
39
- def deeplink(options:)
40
- if options.has_source?
41
- Loader.new(config: @config).sideload(options: options)
39
+ def deeplink(options:, device: nil)
40
+ get_device(device: device) do |device|
41
+ if options.has_source?
42
+ Loader.new(config: @config).sideload(options: options, device: device)
43
+ end
44
+ app_id = options[:app_id]
45
+ app_id ||= "dev"
46
+ path = "/launch/#{app_id}"
47
+ send_options(path: path, options: options[:deeplink], device: device)
42
48
  end
43
- app_id = options[:app_id]
44
- app_id ||= "dev"
45
- path = "/launch/#{app_id}"
46
- send_options(path: path, options: options[:deeplink])
47
49
  end
48
50
 
49
51
  def input(options:)
@@ -54,8 +56,10 @@ module RokuBuilder
54
56
  # @param logger [Logger] System Logger
55
57
  def applist(options:)
56
58
  path = "/query/apps"
57
- conn = multipart_connection(port: 8060)
58
- response = conn.get path
59
+ response = nil
60
+ multipart_connection(port: 8060) do |conn|
61
+ response = conn.get path
62
+ end
59
63
 
60
64
  if response.success?
61
65
  regexp = /id="([^"]*)"\stype="([^"]*)"\sversion="([^"]*)">([^<]*)</
@@ -70,22 +74,25 @@ module RokuBuilder
70
74
 
71
75
  private
72
76
 
73
- def send_options(path:, options:)
77
+ def send_options(path:, options:, device: nil)
74
78
  payload = RokuBuilder.options_parse(options: options)
79
+ get_device(device: device) do |device|
80
+ unless payload.keys.count > 0
81
+ @logger.warn "No options sent to launched app"
82
+ else
83
+ payload = parameterize(payload)
84
+ path = "#{path}?#{payload}"
85
+ @logger.info "Deeplink:"
86
+ @logger.info payload
87
+ @logger.info "CURL:"
88
+ @logger.info "curl -d '' 'http://#{device.ip}:8060#{path}'"
89
+ end
75
90
 
76
- unless payload.keys.count > 0
77
- @logger.warn "No options sent to launched app"
78
- else
79
- payload = parameterize(payload)
80
- path = "#{path}?#{payload}"
81
- @logger.info "Deeplink:"
82
- @logger.info payload
83
- @logger.info "CURL:"
84
- @logger.info "curl -d '' '#{@url}:8060#{path}'"
91
+ multipart_connection(port: 8060, device: device) do |conn|
92
+ response = conn.post path
93
+ @logger.fatal("Failed Deeplinking") unless response.success?
94
+ end
85
95
  end
86
-
87
- response = multipart_connection(port: 8060).post path
88
- @logger.fatal("Failed Deeplinking") unless response.success?
89
96
  end
90
97
 
91
98
  # Parameterize options to be sent to the app
@@ -50,16 +50,20 @@ module RokuBuilder
50
50
  end
51
51
 
52
52
  # Sideload an app onto a roku device
53
- def sideload(options:)
53
+ def sideload(options:, device: nil)
54
54
  did_build = false
55
55
  unless options[:in]
56
56
  did_build = true
57
57
  build(options: options)
58
58
  end
59
59
  keep_build_file = is_build_command(options) and options[:out]
60
- upload(options)
60
+ upload(options: options, device: device)
61
61
  # Cleanup
62
- File.delete(file_path(:in)) if did_build and not keep_build_file
62
+ begin
63
+ File.delete(file_path(:in)) if did_build and not keep_build_file
64
+ rescue Errno::EACCES
65
+ @logger.warn "Unable to delete: " + file_path(:in)
66
+ end
63
67
  end
64
68
 
65
69
 
@@ -76,18 +80,30 @@ module RokuBuilder
76
80
 
77
81
  # Remove the currently sideloaded app
78
82
  def delete(options:, ignoreFailure: false)
79
- payload = {mysubmit: "Delete", archive: ""}
80
- response = multipart_connection.post "/plugin_install", payload
83
+ payload = {
84
+ mysubmit: make_param("Delete"),
85
+ archive: make_param("", "application/octet-stream")
86
+ }
87
+ response = nil
88
+ multipart_connection do |conn|
89
+ response = conn.post "/plugin_install", payload
90
+ end
81
91
  unless response.status == 200 and response.body =~ /Delete Succeeded/ or ignoreFailure
82
92
  raise ExecutionError, "Failed Unloading"
83
93
  end
84
94
  end
85
95
 
86
96
  # Convert sideloaded app to squashfs
87
- def squash(options:, ignoreFailure: false)
88
- payload = {mysubmit: "Convert to squashfs", archive: ""}
89
- response = multipart_connection.post "/plugin_install", payload
90
- unless response.status == 200 and response.body =~ /Conversion succeeded/ or ignoreFailure
97
+ def squash(options:, device: nil, ignoreFailure: false)
98
+ payload = {
99
+ mysubmit: "Convert to squashfs",
100
+ archive: make_param("", "application/octet-stream")
101
+ }
102
+ response = nil
103
+ multipart_connection(device: device) do |conn|
104
+ response = conn.post "/plugin_install", payload
105
+ end
106
+ unless response.status == 200 and response.body =~ /squashfs file in internal memory/ or ignoreFailure
91
107
  raise ExecutionError, "Failed Converting to Squashfs"
92
108
  end
93
109
  end
@@ -104,16 +120,19 @@ module RokuBuilder
104
120
  [:sideload, :build].include? options.command
105
121
  end
106
122
 
107
- def upload(options)
123
+ def upload(options:, device: nil)
108
124
  payload = {
109
125
  mysubmit: "Replace",
110
126
  archive: Faraday::UploadIO.new(file_path(:in), 'application/zip'),
111
127
  }
112
128
  payload["remotedebug"] = "1" if options[:remoteDebug]
113
- response = multipart_connection.post "/plugin_install", payload
129
+ response = nil
130
+ multipart_connection(device: device) do |conn|
131
+ response = conn.post "/plugin_install", payload
132
+ end
114
133
  @logger.debug("Status: #{response.status}, Body: #{response.body}")
115
134
  if response.status==200 and response.body=~/Identical to previous version/
116
- @logger.warn("Sideload identival to previous version")
135
+ @logger.warn("Sideload identical to previous version")
117
136
  elsif not (response.status==200 and response.body=~/Install Success/)
118
137
  raise ExecutionError, "Failed Sideloading"
119
138
  end
@@ -141,11 +160,13 @@ module RokuBuilder
141
160
  end
142
161
 
143
162
  def build_zip(content)
144
- path = file_path(:out)
145
- File.delete(path) if File.exist?(path)
146
- io = Zip::File.open(path, Zip::File::CREATE)
163
+ tmp_path = File.join(build_dir, SecureRandom.uuid+".zip")
164
+ io = Zip::File.open(tmp_path, Zip::File::CREATE)
147
165
  writeEntries(build_dir, content[:source_files], "", content[:excludes], io)
148
166
  io.close()
167
+ path = file_path(:out)
168
+ File.delete(path) if File.exist?(path)
169
+ FileUtils.mv(tmp_path, path)
149
170
  end
150
171
 
151
172
  # Recursively write directory contents to a zip archive
@@ -32,26 +32,28 @@ module RokuBuilder
32
32
 
33
33
  # Monitor a development log on the Roku device
34
34
  def monitor(options:)
35
- type = options[:monitor].to_sym
36
- telnet_config = { 'Host' => @roku_ip_address, 'Port' => @ports[type] }
37
- waitfor_config = { 'Match' => /./, 'Timeout' => false }
38
-
39
- thread = Thread.new(telnet_config, waitfor_config) {|telnet,waitfor|
40
- @logger.info "Monitoring #{type} console(#{telnet['Port']}) on #{telnet['Host'] }"
41
- connection = Net::Telnet.new(telnet)
42
- Thread.current[:connection] = connection
43
- all_text = ""
44
- while true
45
- connection.waitfor(waitfor) do |txt|
46
- all_text = manage_text(all_text: all_text, txt: txt, regexp: options[:regexp])
35
+ get_device(no_lock: true) do |device|
36
+ type = options[:monitor].to_sym
37
+ telnet_config = { 'Host' => device.ip, 'Port' => @ports[type] }
38
+ waitfor_config = { 'Match' => /./, 'Timeout' => false }
39
+
40
+ thread = Thread.new(telnet_config, waitfor_config) {|telnet,waitfor|
41
+ @logger.info "Monitoring #{type} console(#{telnet['Port']}) on #{telnet['Host'] }"
42
+ connection = Net::Telnet.new(telnet)
43
+ Thread.current[:connection] = connection
44
+ all_text = ""
45
+ while true
46
+ connection.waitfor(waitfor) do |txt|
47
+ all_text = manage_text(all_text: all_text, txt: txt, regexp: options[:regexp])
48
+ end
47
49
  end
48
- end
49
- }
50
- thread.abort_on_exception = true
50
+ }
51
+ thread.abort_on_exception = true
51
52
 
52
- init_readline()
53
+ init_readline()
53
54
 
54
- run_prompt(thread: thread)
55
+ run_prompt(thread: thread)
56
+ end
55
57
  end
56
58
 
57
59
  private
@@ -70,48 +70,54 @@ module RokuBuilder
70
70
  # Send a navigation command to the roku device
71
71
  # @param command [Symbol] The smbol of the command to send
72
72
  # @return [Boolean] Success
73
- def nav(options:)
74
- commands = options[:nav].split(/, */).map{|c| c.to_sym}
75
- commands.each do |command|
76
- unless @commands.has_key?(command)
77
- raise ExecutionError, "Unknown Navigation Command"
73
+ def nav(options:, device: nil)
74
+ get_device(device: device, no_lock: true) do |device|
75
+ commands = options[:nav].split(/, */).map{|c| c.to_sym}
76
+ commands.each do |command|
77
+ unless @commands.has_key?(command)
78
+ raise ExecutionError, "Unknown Navigation Command"
79
+ end
80
+ multipart_connection(port: 8060, device: device) do |conn|
81
+ path = "/keypress/#{@commands[command]}"
82
+ @logger.debug("Send Command: "+path)
83
+ response = conn.post path
84
+ raise ExecutionError, "Navigation Failed" unless response.success?
85
+ end
78
86
  end
79
- conn = multipart_connection(port: 8060)
80
- path = "/keypress/#{@commands[command]}"
81
- @logger.debug("Send Command: "+path)
82
- response = conn.post path
83
- raise ExecutionError, "Navigation Failed" unless response.success?
84
87
  end
85
88
  end
86
89
 
87
90
  # Type text on the roku device
88
91
  # @param text [String] The text to type on the device
89
92
  # @return [Boolean] Success
90
- def type(options:)
91
- conn = multipart_connection(port: 8060)
92
- options[:type].split(//).each do |c|
93
- path = "/keypress/LIT_#{CGI::escape(c)}"
94
- @logger.debug("Send Letter: "+path)
95
- response = conn.post path
96
- return false unless response.success?
93
+ def type(options:, device: nil)
94
+ multipart_connection(port: 8060, device: device, no_lock: true) do |conn|
95
+ options[:type].split(//).each do |c|
96
+ path = "/keypress/LIT_#{CGI::escape(c)}"
97
+ @logger.debug("Send Letter: "+path)
98
+ response = conn.post path
99
+ return false unless response.success?
100
+ end
101
+ return true
97
102
  end
98
- return true
99
103
  end
100
104
 
101
105
  def navigate(options:)
102
- running = true
103
- @logger.info("Key Mappings:")
104
- @mappings.each_value {|key|
105
- @logger.info(sprintf("%13s -> %s", key[1], @commands[key[0].to_sym]))
106
- }
107
- @logger.info(sprintf("%13s -> %s", "Ctrl + c", "Exit"))
108
- while running
109
- char = read_char
110
- @logger.debug("Char: #{char.inspect}")
111
- if char == "\u0003"
112
- running = false
113
- else
114
- Thread.new(char) {|character| handle_navigate_input(character)}
106
+ get_device(no_lock: true) do |device|
107
+ running = true
108
+ @logger.info("Key Mappings:")
109
+ @mappings.each_value {|key|
110
+ @logger.info(sprintf("%13s -> %s", key[1], @commands[key[0].to_sym]))
111
+ }
112
+ @logger.info(sprintf("%13s -> %s", "Ctrl + c", "Exit"))
113
+ while running
114
+ char = read_char
115
+ @logger.debug("Char: #{char.inspect}")
116
+ if char == "\u0003"
117
+ running = false
118
+ else
119
+ Thread.new(char, device) {|character,device| handle_navigate_input(character, device)}
120
+ end
115
121
  end
116
122
  end
117
123
  end
@@ -208,11 +214,11 @@ module RokuBuilder
208
214
  end
209
215
  end
210
216
 
211
- def handle_navigate_input(char)
217
+ def handle_navigate_input(char, device)
212
218
  if @mappings[char.to_sym] != nil
213
- nav(options: {nav: @mappings[char.to_sym][0]})
219
+ nav(options: {nav: @mappings[char.to_sym][0]}, device: device)
214
220
  elsif char.inspect.force_encoding("UTF-8").ascii_only?
215
- type(options: {type: char})
221
+ type(options: {type: char}, device: device)
216
222
  end
217
223
  end
218
224