roku_builder 4.25.5 → 4.26.1

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