smartdust-client 1.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/.gitignore +103 -0
- data/.rspec +5 -0
- data/.simplecov +10 -0
- data/.travis.yml +3 -0
- data/Gemfile +4 -0
- data/Gemfile.lock +90 -0
- data/LICENSE.txt +21 -0
- data/README.md +68 -0
- data/Rakefile +9 -0
- data/bin/setup +6 -0
- data/bin/smartdust-client +2 -0
- data/lib/di.rb +86 -0
- data/lib/stf/client.rb +109 -0
- data/lib/stf/interactor/add_adb_public_key.rb +20 -0
- data/lib/stf/interactor/get_keys_interactor.rb +28 -0
- data/lib/stf/interactor/get_values_interactor.rb +29 -0
- data/lib/stf/interactor/remove_all_user_devices_interactor.rb +12 -0
- data/lib/stf/interactor/start_debug_session_interactor.rb +156 -0
- data/lib/stf/interactor/start_one_debug_session_interactor.rb +76 -0
- data/lib/stf/interactor/stop_all_debug_sessions_interactor.rb +27 -0
- data/lib/stf/interactor/stop_debug_session_interactor.rb +54 -0
- data/lib/stf/log/log.rb +18 -0
- data/lib/stf/model/device.rb +75 -0
- data/lib/stf/model/device_enhancer.rb +17 -0
- data/lib/stf/model/device_list.rb +80 -0
- data/lib/stf/system/demonizer.rb +20 -0
- data/lib/stf/validate/uri_validator.rb +7 -0
- data/lib/stf/version.rb +3 -0
- data/lib/stf/view/cli.rb +121 -0
- data/lib/stf.rb +7 -0
- data/smartdust-client.gemspec +34 -0
- data/smartdust-logo-text-2021.png +0 -0
- metadata +272 -0
@@ -0,0 +1,156 @@
|
|
1
|
+
require 'ADB'
|
2
|
+
|
3
|
+
require 'stf/client'
|
4
|
+
require 'stf/log/log'
|
5
|
+
require 'stf/model/device_list'
|
6
|
+
|
7
|
+
module Stf
|
8
|
+
class StartDebugSessionInteractor
|
9
|
+
|
10
|
+
include Log
|
11
|
+
include ADB
|
12
|
+
|
13
|
+
def execute(opts = {})
|
14
|
+
all_flag = opts[:all]
|
15
|
+
nodaemon_flag = opts[:nodaemon]
|
16
|
+
filter = opts[:filter]
|
17
|
+
max_n = opts[:n].to_i > 0 ? opts[:n].to_i : 1
|
18
|
+
start_timeout = opts[:starttime].to_i > 0 ? opts[:starttime].to_i : 120
|
19
|
+
session = opts[:worktime].to_i > 0 ? opts[:session].to_i : 10800
|
20
|
+
min_n = opts[:min].to_s.empty? ? (max_n + 1) / 2 : [opts[:min].to_i, max_n].min
|
21
|
+
healthcheck = opts[:health]
|
22
|
+
force_filter = opts[:forcefilter]
|
23
|
+
|
24
|
+
DI[:demonizer].kill unless opts[:nokill]
|
25
|
+
|
26
|
+
wanted = nodaemon_flag ? max_n : min_n
|
27
|
+
|
28
|
+
begin
|
29
|
+
connect_loop(all_flag: all_flag,
|
30
|
+
wanted: wanted,
|
31
|
+
filter: filter,
|
32
|
+
force_filter: force_filter,
|
33
|
+
healthcheck: healthcheck,
|
34
|
+
delay: 5,
|
35
|
+
timeout: start_timeout)
|
36
|
+
|
37
|
+
rescue SignalException => e
|
38
|
+
logger.info "Caught signal \"#{e.message}\""
|
39
|
+
DI[:stop_all_debug_sessions_interactor].execute
|
40
|
+
return false
|
41
|
+
rescue Exception => e
|
42
|
+
logger.info "Exception \"#{e.message}\" during initial connect loop"
|
43
|
+
DI[:stop_all_debug_sessions_interactor].execute
|
44
|
+
return false
|
45
|
+
end
|
46
|
+
|
47
|
+
connected_count = count_connected_devices(filter)
|
48
|
+
logger.info "Lower quantity achieved, already connected #{connected_count}"
|
49
|
+
|
50
|
+
return true if nodaemon_flag
|
51
|
+
|
52
|
+
# will be daemon here
|
53
|
+
DI[:demonizer].run do
|
54
|
+
connect_loop(all_flag: all_flag,
|
55
|
+
wanted: max_n,
|
56
|
+
filter: filter,
|
57
|
+
force_filter: force_filter,
|
58
|
+
healthcheck: healthcheck,
|
59
|
+
daemon_mode: true,
|
60
|
+
delay: 30,
|
61
|
+
timeout: session)
|
62
|
+
|
63
|
+
DI[:stop_all_debug_sessions_interactor].execute(byFilter: filter, nokill: true)
|
64
|
+
end
|
65
|
+
|
66
|
+
return true
|
67
|
+
end
|
68
|
+
|
69
|
+
def connect_loop(all_flag: false,
|
70
|
+
wanted: 1,
|
71
|
+
|
72
|
+
filter: nil,
|
73
|
+
force_filter: false,
|
74
|
+
healthcheck: nil,
|
75
|
+
|
76
|
+
daemon_mode: false,
|
77
|
+
delay: 5,
|
78
|
+
timeout: 120)
|
79
|
+
finish_time = Time.now + timeout
|
80
|
+
one_time_mode = !daemon_mode
|
81
|
+
|
82
|
+
while true do
|
83
|
+
cleanup_disconnected_devices(filter, force_filter, healthcheck)
|
84
|
+
|
85
|
+
if one_time_mode && Time.now > finish_time
|
86
|
+
raise "Connect loop timeout reached"
|
87
|
+
end
|
88
|
+
|
89
|
+
all_devices = DeviceList.new(DI[:stf].get_devices)
|
90
|
+
stf_devices = all_devices.select_ready_to_connect
|
91
|
+
stf_devices = stf_devices.by_filter(filter) if filter
|
92
|
+
stf_devices = stf_devices.select_healthy_for_connect(healthcheck) if healthcheck
|
93
|
+
|
94
|
+
if all_flag
|
95
|
+
to_connect = stf_devices.size
|
96
|
+
else
|
97
|
+
connected = devices & all_devices.as_connect_url_list
|
98
|
+
to_connect = wanted - connected.size
|
99
|
+
end
|
100
|
+
|
101
|
+
return if one_time_mode && to_connect <= 0
|
102
|
+
|
103
|
+
if to_connect > 0
|
104
|
+
if stf_devices.empty?
|
105
|
+
logger.error 'There is no available devices with criteria ' + filter
|
106
|
+
else
|
107
|
+
random_device = stf_devices.asArray.sample
|
108
|
+
DI[:start_one_debug_session_interactor].execute(random_device)
|
109
|
+
next
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
113
|
+
sleep delay
|
114
|
+
end
|
115
|
+
end
|
116
|
+
|
117
|
+
def count_connected_devices(filter)
|
118
|
+
stf_devices = DeviceList.new(DI[:stf].get_user_devices)
|
119
|
+
stf_devices = stf_devices.by_filter(filter) if filter
|
120
|
+
connected = devices & stf_devices.as_connect_url_list
|
121
|
+
connected.size
|
122
|
+
end
|
123
|
+
|
124
|
+
def cleanup_disconnected_devices(filter, force_filter, healthcheck)
|
125
|
+
to_disconnect = []
|
126
|
+
stf_devices = DeviceList.new(DI[:stf].get_user_devices)
|
127
|
+
|
128
|
+
if filter && force_filter
|
129
|
+
disconnect_because_filter = stf_devices.except_filter(filter).as_connect_url_list
|
130
|
+
unless disconnect_because_filter.empty?
|
131
|
+
logger.info 'will be disconnected by filter: ' + disconnect_because_filter.join(',')
|
132
|
+
to_disconnect += disconnect_because_filter
|
133
|
+
end
|
134
|
+
end
|
135
|
+
|
136
|
+
if healthcheck
|
137
|
+
disconnect_by_health = stf_devices.select_not_healthy(healthcheck).as_connect_url_list
|
138
|
+
unless disconnect_by_health.empty?
|
139
|
+
logger.info 'will be disconnected by health check: ' + disconnect_by_health.join(',')
|
140
|
+
to_disconnect += disconnect_by_health
|
141
|
+
end
|
142
|
+
end
|
143
|
+
|
144
|
+
dead_persons = stf_devices.as_connect_url_list - devices
|
145
|
+
unless dead_persons.empty?
|
146
|
+
logger.info 'will be disconnected because not present locally: ' + dead_persons.join(',')
|
147
|
+
to_disconnect += dead_persons
|
148
|
+
end
|
149
|
+
|
150
|
+
to_disconnect.reject {|url| url.to_s.empty?}.uniq.each do |url|
|
151
|
+
logger.info 'Cleanup the device ' + url.to_s
|
152
|
+
DI[:stop_debug_session_interactor].execute(url)
|
153
|
+
end
|
154
|
+
end
|
155
|
+
end
|
156
|
+
end
|
@@ -0,0 +1,76 @@
|
|
1
|
+
require 'di'
|
2
|
+
require 'ADB'
|
3
|
+
|
4
|
+
require 'stf/client'
|
5
|
+
require 'stf/log/log'
|
6
|
+
|
7
|
+
module Stf
|
8
|
+
class StartOneDebugSessionInteractor
|
9
|
+
|
10
|
+
include Log
|
11
|
+
include ADB
|
12
|
+
|
13
|
+
def execute(device)
|
14
|
+
return false if device.nil?
|
15
|
+
serial = device.serial
|
16
|
+
|
17
|
+
begin
|
18
|
+
success = DI[:stf].add_device serial
|
19
|
+
if success
|
20
|
+
logger.info "Device added #{serial}"
|
21
|
+
else
|
22
|
+
logger.error "Can't add device #{serial}"
|
23
|
+
raise
|
24
|
+
end
|
25
|
+
|
26
|
+
result = DI[:stf].start_debug serial
|
27
|
+
if result.success
|
28
|
+
logger.info "Debug started #{serial}"
|
29
|
+
else
|
30
|
+
logger.error "Can't start debugging session for device #{serial}"
|
31
|
+
raise
|
32
|
+
end
|
33
|
+
|
34
|
+
provider = device.getValue "provider"
|
35
|
+
provider_ip = provider["ip"]
|
36
|
+
remote_connect_url_split = result.remoteConnectUrl.split ":"
|
37
|
+
remote_connect_port = remote_connect_url_split[1]
|
38
|
+
result = DI[:stf].open_tunnel provider_ip, remote_connect_port.to_i
|
39
|
+
if result.success
|
40
|
+
logger.info "Opened tunnel to provider with IP #{provider_ip}"
|
41
|
+
else
|
42
|
+
logger.error "Can't open tunnel to provider with IP #{provider_ip}"
|
43
|
+
raise
|
44
|
+
end
|
45
|
+
remote_connect_tunneled_url = DI[:device_enhancer].get_tunneled_remote_connect_url(device)
|
46
|
+
logger.info remote_connect_tunneled_url
|
47
|
+
execute_adb_with 30, "connect #{remote_connect_tunneled_url}"
|
48
|
+
|
49
|
+
shell('echo adbtest', {serial: "#{remote_connect_tunneled_url}"}, 30)
|
50
|
+
raise ADBError, "Could not execute shell test" unless stdout_contains "adbtest"
|
51
|
+
|
52
|
+
return true
|
53
|
+
|
54
|
+
rescue StandardError, SignalException => e
|
55
|
+
begin
|
56
|
+
# we will try clean anyway
|
57
|
+
DI[:stf].remove_device serial
|
58
|
+
if test ?d, '/custom-metrics'
|
59
|
+
File.open('/custom-metrics/openstf_connect_fail', 'a') do |f|
|
60
|
+
message = (!e.nil? || !e.message.nil?) ? e.message : ""
|
61
|
+
f.write("openstf_connect_fail,reason=\"#{escape(message)}\",serial=\"#{escape(serial)}\" count=1i #{Time.now.to_i}\n")
|
62
|
+
end
|
63
|
+
end
|
64
|
+
rescue
|
65
|
+
end
|
66
|
+
|
67
|
+
logger.error "Failed to connect to #{serial}: " + e&.message
|
68
|
+
return false
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
def escape(s)
|
73
|
+
s.gsub(/["]/, '\"').gsub(/[ ]/, '\ ').gsub(/[=]/, '\=').gsub(/[,]/, '\,')
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
require 'di'
|
2
|
+
require 'ADB'
|
3
|
+
|
4
|
+
require 'stf/client'
|
5
|
+
require 'stf/log/log'
|
6
|
+
require 'stf/interactor/stop_debug_session_interactor'
|
7
|
+
require 'stf/model/device_list'
|
8
|
+
|
9
|
+
module Stf
|
10
|
+
class StopAllDebugSessionsInteractor
|
11
|
+
include Log
|
12
|
+
include ADB
|
13
|
+
|
14
|
+
# byFilter:
|
15
|
+
def execute(options = {})
|
16
|
+
DI[:demonizer].kill unless options[:nokill]
|
17
|
+
|
18
|
+
stf_devices = DeviceList.new(DI[:stf].get_user_devices)
|
19
|
+
|
20
|
+
stf_devices = stf_devices.by_filter options[:byFilter] if options[:byFilter]
|
21
|
+
|
22
|
+
pending_disconnect = stf_devices.as_connect_url_list
|
23
|
+
|
24
|
+
pending_disconnect.each {|d| DI[:stop_debug_session_interactor].execute d}
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,54 @@
|
|
1
|
+
require 'di'
|
2
|
+
require 'ADB'
|
3
|
+
|
4
|
+
require 'stf/client'
|
5
|
+
require 'stf/log/log'
|
6
|
+
|
7
|
+
module Stf
|
8
|
+
class StopDebugSessionInteractor
|
9
|
+
include Log
|
10
|
+
include ADB
|
11
|
+
|
12
|
+
def execute(remote_connect_url)
|
13
|
+
remote_devices = DI[:stf].get_user_devices
|
14
|
+
device = remote_devices.find {|d| d.remoteConnect == true && DI[:device_enhancer].get_tunneled_remote_connect_url(d).eql?(remote_connect_url)}
|
15
|
+
|
16
|
+
# try to disconnect anyway
|
17
|
+
execute_adb_with 30, "disconnect #{remote_connect_url}"
|
18
|
+
|
19
|
+
if device.nil?
|
20
|
+
logger.error "Device #{remote_connect_url} is not available"
|
21
|
+
return false
|
22
|
+
end
|
23
|
+
|
24
|
+
success = false
|
25
|
+
|
26
|
+
1..10.times do
|
27
|
+
begin
|
28
|
+
success = DI[:stf].stop_debug(device.serial)
|
29
|
+
break if success
|
30
|
+
rescue
|
31
|
+
end
|
32
|
+
|
33
|
+
logger.error 'Can\'t stop debug session. Retrying'
|
34
|
+
end
|
35
|
+
|
36
|
+
1..10.times do
|
37
|
+
begin
|
38
|
+
success = DI[:stf].remove_device(device.serial)
|
39
|
+
break if success
|
40
|
+
rescue
|
41
|
+
end
|
42
|
+
logger.error 'Can\'t remove device from user devices. Retrying'
|
43
|
+
end
|
44
|
+
|
45
|
+
if success
|
46
|
+
logger.info "Successfully removed #{remote_connect_url}"
|
47
|
+
else
|
48
|
+
logger.error "Error removing #{remote_connect_url}"
|
49
|
+
end
|
50
|
+
|
51
|
+
success
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
data/lib/stf/log/log.rb
ADDED
@@ -0,0 +1,18 @@
|
|
1
|
+
require 'logger'
|
2
|
+
|
3
|
+
module Stf
|
4
|
+
module Log
|
5
|
+
|
6
|
+
@@logger = Logger.new(STDOUT)
|
7
|
+
@@logger.level = Logger::INFO
|
8
|
+
|
9
|
+
def logger
|
10
|
+
@@logger
|
11
|
+
end
|
12
|
+
|
13
|
+
def self.verbose(enable)
|
14
|
+
@@logger.level = enable ? Logger::DEBUG : Logger::INFO
|
15
|
+
end
|
16
|
+
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,75 @@
|
|
1
|
+
module Stf
|
2
|
+
require 'facets/boolean'
|
3
|
+
class Device < OpenStruct
|
4
|
+
def getValue(key)
|
5
|
+
getValueFromObject(self, key)
|
6
|
+
end
|
7
|
+
|
8
|
+
def getKeys
|
9
|
+
getKeysNextLevel('', self)
|
10
|
+
end
|
11
|
+
|
12
|
+
# more pessimistic decision
|
13
|
+
def healthy_for_connect?(pattern)
|
14
|
+
return true if pattern.nil?
|
15
|
+
health = healthy?(pattern)
|
16
|
+
ppp = pattern.split(',')
|
17
|
+
ppp.each do |p|
|
18
|
+
health &&= getValue('battery.temp').to_i < 30 if ['t', 'temp', 'temperature'].include? p
|
19
|
+
health &&= getValue('battery.level').to_f > 30.0 if ['b', 'batt', 'battery'].include? p
|
20
|
+
end
|
21
|
+
health
|
22
|
+
end
|
23
|
+
|
24
|
+
def healthy?(pattern)
|
25
|
+
return true if pattern.nil?
|
26
|
+
ppp = pattern.split(',')
|
27
|
+
health = true
|
28
|
+
ppp.each do |p|
|
29
|
+
health &&= getValue('battery.temp').to_i < 32 if ['t', 'temp', 'temperature'].include? p
|
30
|
+
health &&= getValue('battery.level').to_f > 20.0 if ['b', 'batt', 'battery'].include? p
|
31
|
+
health &&= getValue('network.connected') if ['n', 'net', 'network'].include? p
|
32
|
+
health &&= getValue('network.type') == 'VPN' if ['vpn'].include? p
|
33
|
+
health &&= getValue('network.type') == 'WIFI' if ['wifi'].include? p
|
34
|
+
end
|
35
|
+
health
|
36
|
+
end
|
37
|
+
|
38
|
+
def checkFilter?(filter)
|
39
|
+
return true if filter.nil?
|
40
|
+
key, value = filter.split(':', 2)
|
41
|
+
if value == 'true' || value == 'false'
|
42
|
+
value = value.to_b
|
43
|
+
end
|
44
|
+
getValue(key) == value
|
45
|
+
end
|
46
|
+
|
47
|
+
def getKeysNextLevel(prefix, o)
|
48
|
+
return [] if o.nil?
|
49
|
+
|
50
|
+
o.each_pair.flat_map do |k, v|
|
51
|
+
if v.is_a? OpenStruct
|
52
|
+
getKeysNextLevel(concat(prefix, k.to_s), v)
|
53
|
+
else
|
54
|
+
[concat(prefix, k.to_s)]
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
def concat(prefix, key)
|
60
|
+
prefix.to_s.empty? ? key : prefix + '.' + key
|
61
|
+
end
|
62
|
+
|
63
|
+
def getValueFromObject(obj, key)
|
64
|
+
keys = key.split('.', 2)
|
65
|
+
if keys[1].nil?
|
66
|
+
obj[key]
|
67
|
+
else
|
68
|
+
getValueFromObject(obj[keys[0]], keys[1])
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
private :getValueFromObject, :concat, :getKeysNextLevel
|
73
|
+
|
74
|
+
end
|
75
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
module Stf
|
2
|
+
class DeviceEnhancer
|
3
|
+
def get_tunneled_remote_connect_url(device)
|
4
|
+
provider = device.provider
|
5
|
+
provider_ip = provider["ip"]
|
6
|
+
result = DI[:stf].start_debug device.serial
|
7
|
+
remote_connect_url_split = result.remoteConnectUrl.split ":"
|
8
|
+
remote_connect_port = remote_connect_url_split[1]
|
9
|
+
result = DI[:stf].check_tunnel provider_ip, remote_connect_port
|
10
|
+
if result.success
|
11
|
+
remote_connect_hostname = remote_connect_url_split[0]
|
12
|
+
remote_connect_hostname + ":" + result.port.to_s
|
13
|
+
else nil
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,80 @@
|
|
1
|
+
require 'stf/model/device'
|
2
|
+
|
3
|
+
module Stf
|
4
|
+
# can not inherite from Array because http://words.steveklabnik.com/beware-subclassing-ruby-core-classes
|
5
|
+
class DeviceList
|
6
|
+
def initialize(devices)
|
7
|
+
if devices.nil?
|
8
|
+
@devices = Array.new
|
9
|
+
else
|
10
|
+
@devices = devices.map {|d| (d.kind_of? Device) ? d : Device.new(d)}
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
def by_filter(filter)
|
15
|
+
filter ? select {|d| d.checkFilter?(filter)} : []
|
16
|
+
end
|
17
|
+
|
18
|
+
def except_filter(filter)
|
19
|
+
filter ? reject {|d| d.checkFilter?(filter)} : this
|
20
|
+
end
|
21
|
+
|
22
|
+
def select_healthy(pattern)
|
23
|
+
pattern ? select { |d| d.healthy?(pattern) } : this
|
24
|
+
end
|
25
|
+
|
26
|
+
# more pessimistic than healthy()
|
27
|
+
def select_healthy_for_connect(pattern)
|
28
|
+
pattern ? select { |d| d.healthy_for_connect?(pattern) } : this
|
29
|
+
end
|
30
|
+
|
31
|
+
def select_not_healthy(pattern)
|
32
|
+
pattern ? reject { |d| d.healthy?(pattern) } : []
|
33
|
+
end
|
34
|
+
|
35
|
+
def select_ready_to_connect
|
36
|
+
# https://github.com/openstf/stf/blob/93d9d7fe859bb7ca71669f375d841d94fa47d751/lib/wire/wire.proto#L170
|
37
|
+
# enum DeviceStatus {
|
38
|
+
# OFFLINE = 1;
|
39
|
+
# UNAUTHORIZED = 2;
|
40
|
+
# ONLINE = 3;
|
41
|
+
# CONNECTING = 4;
|
42
|
+
# AUTHORIZING = 5;
|
43
|
+
# }
|
44
|
+
#
|
45
|
+
# https://github.com/openstf/stf/blob/93d9d7fe859bb7ca71669f375d841d94fa47d751/res/app/components/stf/device/enhance-device/enhance-device-service.js
|
46
|
+
select {|d|
|
47
|
+
d.present == true &&
|
48
|
+
d.status == 3 &&
|
49
|
+
d.ready == true &&
|
50
|
+
d.using == false &&
|
51
|
+
d.owner.nil?
|
52
|
+
}
|
53
|
+
end
|
54
|
+
|
55
|
+
def as_connect_url_list
|
56
|
+
@devices.reject { |d| d.remoteConnectUrl.nil? || d.remoteConnectUrl.empty? }
|
57
|
+
.map{|d| DI[:device_enhancer].get_tunneled_remote_connect_url(d)}
|
58
|
+
end
|
59
|
+
|
60
|
+
def select
|
61
|
+
DeviceList.new(@devices.select {|d| yield(d)})
|
62
|
+
end
|
63
|
+
|
64
|
+
def reject
|
65
|
+
DeviceList.new(@devices.select {|d| !yield(d)})
|
66
|
+
end
|
67
|
+
|
68
|
+
def empty?
|
69
|
+
@devices.empty?
|
70
|
+
end
|
71
|
+
|
72
|
+
def size
|
73
|
+
@devices.size
|
74
|
+
end
|
75
|
+
|
76
|
+
def asArray
|
77
|
+
@devices
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
module Stf
|
2
|
+
class Demonizer
|
3
|
+
def initialize(dante, opts = {})
|
4
|
+
@dante = dante
|
5
|
+
|
6
|
+
@pid_path = opts[:pid_path].to_s.empty? ? '/tmp/stf-client.pid' : opts[:pid_path]
|
7
|
+
@log_path = opts[:log_path].to_s.empty? ? '/tmp/stf-client.log' : opts[:log_path]
|
8
|
+
end
|
9
|
+
|
10
|
+
def run
|
11
|
+
@dante.execute(daemonize: true,
|
12
|
+
pid_path: @pid_path,
|
13
|
+
log_path: @log_path) {yield}
|
14
|
+
end
|
15
|
+
|
16
|
+
def kill
|
17
|
+
@dante.execute(kill: true, pid_path: @pid_path)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
data/lib/stf/version.rb
ADDED
data/lib/stf/view/cli.rb
ADDED
@@ -0,0 +1,121 @@
|
|
1
|
+
module Stf
|
2
|
+
module CLI
|
3
|
+
require 'di'
|
4
|
+
require 'gli'
|
5
|
+
|
6
|
+
include GLI::App
|
7
|
+
|
8
|
+
extend self
|
9
|
+
|
10
|
+
program_desc "Smartdust Lab client (version #{Stf::VERSION})"
|
11
|
+
|
12
|
+
desc 'Be verbose'
|
13
|
+
switch [:v, :verbose]
|
14
|
+
|
15
|
+
desc 'PID file'
|
16
|
+
flag [:pid]
|
17
|
+
|
18
|
+
desc 'Log file'
|
19
|
+
flag [:log]
|
20
|
+
|
21
|
+
desc 'Authorization token, can also be set by environment variable SD_TOKEN'
|
22
|
+
flag [:t, :token]
|
23
|
+
|
24
|
+
desc 'URL to Smartdust Lab, can also be set by environment variable SD_URL'
|
25
|
+
flag [:u, :url]
|
26
|
+
|
27
|
+
pre do |global_options, command, options, args|
|
28
|
+
|
29
|
+
global_options[:url] = ENV['SD_URL'] if global_options[:url].nil?
|
30
|
+
global_options[:token] = ENV['SD_TOKEN'] if global_options[:token].nil?
|
31
|
+
|
32
|
+
help_now!('Smartdust Lab url is required') if global_options[:url].nil?
|
33
|
+
help_now!('Authorization token is required') if global_options[:token].nil?
|
34
|
+
|
35
|
+
Log::verbose(global_options[:verbose])
|
36
|
+
|
37
|
+
DI.init(global_options)
|
38
|
+
|
39
|
+
help_now!('Valid Smartdust Lab url is required, e.g. https://public.smartdust.me') if !DI[:uri_validator].validate(global_options[:url])
|
40
|
+
true
|
41
|
+
end
|
42
|
+
|
43
|
+
desc 'Search for a device available in Smartdust Lab and attach it to local adb server'
|
44
|
+
command :connect do |c|
|
45
|
+
c.desc 'Connect to all available devices'
|
46
|
+
c.switch [:all]
|
47
|
+
c.desc 'Required quantity of devices'
|
48
|
+
c.flag [:n]
|
49
|
+
c.desc 'Minimal quantity of devices, n/2 by default'
|
50
|
+
c.flag [:min]
|
51
|
+
c.desc 'Filter key:value for devices'
|
52
|
+
c.flag [:f, :filter]
|
53
|
+
c.desc 'Force filter check for connected devices'
|
54
|
+
c.switch [:forcefilter, :ff]
|
55
|
+
c.desc 'Check selected health parameters, could be any of the: battery,temperature,network,vpn,wifi'
|
56
|
+
c.flag [:health]
|
57
|
+
c.desc 'Maximum session duration in seconds, 10800 (3h) by default'
|
58
|
+
c.flag [:session]
|
59
|
+
c.desc 'Maximum time to connect minimal quantity of devices in seconds, 120 (2m) by default'
|
60
|
+
c.flag [:starttime]
|
61
|
+
c.desc 'Do not start daemon'
|
62
|
+
c.switch [:nodaemon]
|
63
|
+
|
64
|
+
c.action do |_, options, _|
|
65
|
+
unless DI[:start_debug_session_interactor].execute(options)
|
66
|
+
raise GLI::CustomExit.new('Connect failed', 1)
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
desc 'Show available keys for filtering'
|
72
|
+
command :keys do |c|
|
73
|
+
c.action {puts DI[:get_keys_interactor].execute}
|
74
|
+
end
|
75
|
+
|
76
|
+
desc 'Show known values for the filtering key'
|
77
|
+
command :values do |c|
|
78
|
+
c.action do |_, _, args|
|
79
|
+
exit_now!('Please specify one key') if args.empty?
|
80
|
+
|
81
|
+
puts DI[:get_values_interactor].execute(args.first)
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
desc 'Disconnect device(s) from local adb server and remove device(s) from user devices in Smartdust Lab'
|
86
|
+
command :disconnect do |c|
|
87
|
+
c.desc '(optional) ADB connection url of the device'
|
88
|
+
c.switch [:all]
|
89
|
+
|
90
|
+
c.action do |_, options, args|
|
91
|
+
if args.empty? && options[:all] == true
|
92
|
+
DI[:stop_all_debug_sessions_interactor].execute
|
93
|
+
elsif !args.empty? && options[:all] == false
|
94
|
+
DI[:stop_debug_session_interactor].execute(args.first)
|
95
|
+
elsif exit_now!('Please specify one device or mode --all')
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
desc 'Frees all devices that are assigned to current user in Smartdust Lab. Doesn\'t modify local adb'
|
101
|
+
command :clean do |c|
|
102
|
+
c.action {DI[:remove_all_user_devices_interactor].execute}
|
103
|
+
end
|
104
|
+
|
105
|
+
desc 'Add adb public key into Smartdust Lab'
|
106
|
+
command :trustme do |c|
|
107
|
+
c.desc 'Location of adb public key'
|
108
|
+
c.flag [:k, :adb_public_key_location]
|
109
|
+
c.default_value '~/.android/adbkey.pub'
|
110
|
+
|
111
|
+
c.action do |_, options, _|
|
112
|
+
filename = File.expand_path(options[:adb_public_key_location])
|
113
|
+
exit_now!("File does not exist: '#{options[:adb_public_key_location]}'") unless File.exist? filename
|
114
|
+
DI[:add_adb_public_key_interactor].execute(options[:adb_public_key_location])
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
exit run(ARGV)
|
119
|
+
end
|
120
|
+
end
|
121
|
+
|