manymo 2.0.0.beta

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.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 821c784a5b085a534bc0589b61a990ab57544780
4
+ data.tar.gz: 05416b27426bf6c3c54cff2979a0ec446ed3c017
5
+ SHA512:
6
+ metadata.gz: ace13b861a68a2f7a2420662cf8851e533639acccc22d8e07cb3d1ddeb0afaef29e4aa7d64da131eefc79a2c82e84f2d2297e4445f8fe4c001f882074e177f1b
7
+ data.tar.gz: 2a254bf0950e742c68b6eb3cf8570e4a1e501ef216463948cb150f940757819aac78057c178c71416d65c779fea3ddd4d6dc3525c965233568f2c11ae2734700
@@ -0,0 +1,17 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in manymo.gemspec
4
+ gemspec
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2014 Pete Schwamb
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,37 @@
1
+ # Manymo
2
+
3
+ TODO: Write a gem description
4
+
5
+ ## Dependencies
6
+
7
+ You will need adb (from the [Android SDK](http://developer.android.com/sdk)) in your PATH.
8
+
9
+ ## Installation
10
+
11
+ $ gem install manymo
12
+
13
+ ## Usage
14
+
15
+ ```
16
+ manymo [options] COMMAND [ARGUMENTS]
17
+
18
+ Commands:
19
+ launch EMULATORNAME Launch a headless emulator and make it appear like a local device
20
+ list List emulators; use the name attribute with the launch command
21
+ shutdown [SERIALNUMBER] Shutdown specified headless emulator or tunnel, or all if serial number omitted
22
+ token Display a prompt to enter authorization token
23
+ tunnel TUNNELKEY Make an in-browser emulator appear like a local device
24
+
25
+ Options:
26
+ --adb-path PATH_TO_ADB Specify path to adb executable; otherwise uses the one in your path
27
+ ```
28
+
29
+ More documentation is at https://www.manymo.com/pages/documentation/manymo-command-line-tool
30
+
31
+ ## Contributing
32
+
33
+ 1. Fork it
34
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
35
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
36
+ 4. Push to the branch (`git push origin my-new-feature`)
37
+ 5. Create new Pull Request
@@ -0,0 +1 @@
1
+ require "bundler/gem_tasks"
@@ -0,0 +1,5 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'manymo'
4
+
5
+ Manymo::Command.start(ARGV)
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env ruby
2
+
@@ -0,0 +1,12 @@
1
+ require "manymo/version"
2
+ require "manymo/adb"
3
+ require "manymo/adb_connection_verifier"
4
+ require "manymo/adb_packet"
5
+ require "manymo/adb_stream"
6
+ require "manymo/adb_tunnel"
7
+ require "manymo/console_tunnel"
8
+ require "manymo/command"
9
+ require "manymo/service"
10
+ require "manymo/tunnel"
11
+ require "manymo/tunnel_close_event"
12
+
@@ -0,0 +1,33 @@
1
+
2
+ module Manymo
3
+ class ADB
4
+ attr_accessor :path
5
+ def initialize(path = nil)
6
+ @path = path
7
+ end
8
+
9
+ def path
10
+ @path || 'adb'
11
+ end
12
+
13
+ def ensure_available
14
+ if @path
15
+ if !File.exists?(@path)
16
+ STDERR.puts "Specified ADB executable (#{@path}) not found."
17
+ exit 1
18
+ end
19
+ else
20
+ found_adb = false
21
+ begin
22
+ `adb version`
23
+ found_adb = true
24
+ rescue Errno::ENOENT
25
+ end
26
+ if (!found_adb)
27
+ STDERR.puts "Could not find adb. Please install the Android SDK and make sure its platform-tools directory is in your PATH."
28
+ exit 1
29
+ end
30
+ end
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,37 @@
1
+ require 'eventmachine'
2
+
3
+ module Manymo
4
+ class ADBConnectionVerifier
5
+ include EM::Deferrable
6
+
7
+ module ADBConnection
8
+ def initialize(verifier)
9
+ @verifier = verifier
10
+ @packet = ADBPacket.new
11
+ end
12
+
13
+ def post_init
14
+ #puts "Local adb socket opened. Sending CNXN packet."
15
+ send_data ADBPacket.build("CNXN", 0x01000000, 0x00001000, "host::\x00").data
16
+ end
17
+
18
+ def receive_data(data)
19
+ #puts "Client receive data!"
20
+ remainder = @packet.consume(data)
21
+ while @packet.complete? do
22
+ @verifier.succeed
23
+ @packet = ADBPacket.new
24
+ remainder = @packet.consume(remainder)
25
+ end
26
+ end
27
+
28
+ def unbind
29
+ #@verifier.fail("Could not connect to adb over tunnel.")
30
+ end
31
+ end
32
+
33
+ def initialize(port)
34
+ EM.connect('127.0.0.1', port+1, ADBConnection, self)
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,90 @@
1
+
2
+ module Manymo
3
+ class ADBPacket
4
+ attr_accessor :data
5
+
6
+ def initialize(data = "")
7
+ @data = data
8
+ end
9
+
10
+ def self.build(cmd, arg0, arg1, body = "")
11
+ # Should be 0x00000232 for a CNXN packet
12
+ new(cmd + [arg0, arg1, body.length, compute_checksum(body), compute_magic(cmd)].pack("V*") + body)
13
+ end
14
+
15
+ def self.compute_magic(cmd)
16
+ cmd.unpack("V").first ^ 0xffffffff
17
+ end
18
+
19
+ def consume(data)
20
+ @data = @data + data
21
+ remainder = ""
22
+ if @data.length >= 16 && @data.length > data_length
23
+ remainder = @data[data_length..-1]
24
+ @data = @data[0,data_length]
25
+ end
26
+ remainder
27
+ end
28
+
29
+ def complete?
30
+ @data.length >= 16 && @data.length == data_length
31
+ end
32
+
33
+ def command
34
+ @data[0,4]
35
+ end
36
+
37
+ def arg0
38
+ @data[4,4].unpack('V').first
39
+ end
40
+
41
+ def arg1
42
+ @data[8,4].unpack('V').first
43
+ end
44
+
45
+ def payload_size
46
+ @data[12,4].unpack('V').first
47
+ end
48
+
49
+ def checksum
50
+ @data[16,4].unpack('V').first
51
+ end
52
+
53
+ def body
54
+ @data[header_size..-1]
55
+ end
56
+
57
+ def magic
58
+ @data[20,4].unpack('V').first
59
+ end
60
+
61
+ def self.compute_checksum(data)
62
+ # Not really a crc32
63
+ data.bytes.inject{|sum,x| sum + x } || 0
64
+ end
65
+
66
+ def computed_checksum
67
+ self.class.compute_checksum(body)
68
+ end
69
+
70
+ def header_size
71
+ 24
72
+ end
73
+
74
+ def data_length
75
+ payload_size + header_size
76
+ end
77
+
78
+ def checksum_ok?
79
+ computed_checksum == checksum
80
+ end
81
+
82
+ def hex_str(v)
83
+ "0x%08x" % v unless v.nil?
84
+ end
85
+
86
+ def to_s
87
+ "#{command}(#{hex_str(arg0)}, #{hex_str(arg1)}), payload_size=#{payload_size}, checksum=#{hex_str(checksum)}, computed_checksum=#{hex_str(computed_checksum)}, magic=#{hex_str(magic)}, body=#{body.inspect}"
88
+ end
89
+ end
90
+ end
@@ -0,0 +1,18 @@
1
+ module Manymo
2
+ class ADBStream
3
+ attr_accessor :local_id, :remote_id
4
+ attr_accessor :systemtype, :banner
5
+ attr_accessor :window_length, :spoofed_ack_count
6
+
7
+ def initialize(local_id, open_data)
8
+ @spoofed_ack_count = 0
9
+ @local_id = local_id
10
+ @system_type, @banner = open_data.split(':')
11
+ end
12
+
13
+ def quick_ack
14
+ @spoofed_ack_count += 1
15
+ ADBPacket.build("OKAY", remote_id, local_id)
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,132 @@
1
+ require 'faye'
2
+
3
+ module Manymo
4
+ module ADBTunnel
5
+
6
+ def initialize(server, display, password)
7
+ @server = server
8
+ @display = display
9
+ @password = password
10
+ @connected = false
11
+ @q = []
12
+ @in_packet = ADBPacket.new
13
+ @out_packet = ADBPacket.new
14
+ @out_streams = {}
15
+ @local_port, @local_ip = Socket.unpack_sockaddr_in(get_sockname)
16
+ @peer_port, @peer_ip = Socket.unpack_sockaddr_in(get_peername)
17
+ end
18
+
19
+ def onclose(&blk)
20
+ @onclose = blk
21
+ end
22
+
23
+ def log_packet(prefix, pkt)
24
+ #puts prefix + ": " + pkt.to_s
25
+ end
26
+
27
+ # Packet from local socket out to websocket
28
+ def handle_outgoing_packet(p)
29
+ log_packet "#{@peer_port} -> ws", p
30
+ @ws.send p.data.bytes.to_a
31
+ case p.command
32
+ when "OPEN"
33
+ @out_streams[p.arg0] = ADBStream.new(p.arg0, p.body)
34
+ when "WRTE"
35
+ stream = @out_streams[p.arg0]
36
+ if stream
37
+ ack = stream.quick_ack
38
+ log_packet "+ws -> #{@peer_port}", ack
39
+ send_data ack.data
40
+ end
41
+ end
42
+ end
43
+
44
+ # Packet from remote websocket to local socket
45
+ def handle_incoming_packet(p)
46
+ should_send = true
47
+ case p.command
48
+ when "CLSE"
49
+ @out_streams.delete p.arg1
50
+ when "OKAY"
51
+ stream = @out_streams[p.arg1]
52
+ if stream
53
+ if stream.spoofed_ack_count > 0
54
+ stream.spoofed_ack_count -= 1
55
+ should_send = false
56
+ log_packet "-ws -> #{@peer_port}", p
57
+ else
58
+ stream.remote_id = p.arg0
59
+ end
60
+ end
61
+ end
62
+ if should_send
63
+ log_packet "ws -> #{@peer_port}", p
64
+ send_data p.data
65
+ end
66
+ end
67
+
68
+ def post_init
69
+ #puts "opening ws wss://#{@server}/adb_tunnel?display=#{@display}&password=#{@password}"
70
+ @ws = Faye::WebSocket::Client.new("wss://#{@server}/adb_tunnel?display=#{@display}&password=#{@password}", nil, {ping: 10})
71
+ @ws.on :open do
72
+ @connected = true
73
+ flush
74
+ end
75
+
76
+ @ws.on :message do |msg|
77
+ data = msg.data.pack('c*')
78
+ remainder = @in_packet.consume(data)
79
+ while @in_packet.complete? do
80
+ handle_incoming_packet(@in_packet)
81
+ @in_packet = ADBPacket.new
82
+ remainder = @in_packet.consume(remainder)
83
+ end
84
+ end
85
+
86
+ @ws.on :close do |event|
87
+ puts "remote adb ws closed connection: #{event.code}"
88
+ if @onclose
89
+ close_event = TunnelCloseEvent.new(:websocket)
90
+ close_event.websocket_event = event
91
+ @onclose.call(close_event)
92
+ @onclose = nil
93
+ end
94
+ close_connection
95
+ end
96
+ end
97
+
98
+ def unbind
99
+ #puts "unbind local adb"
100
+ if @onclose
101
+ close_event = TunnelCloseEvent.new(:local)
102
+ @onclose.call(close_event)
103
+ @onclose = nil
104
+ end
105
+
106
+ if @ws
107
+ @ws.close
108
+ @ws = nil
109
+ end
110
+ end
111
+
112
+ def flush
113
+ if @connected
114
+ @q.each do |data|
115
+ remainder = @out_packet.consume(data)
116
+ while @out_packet.complete? do
117
+ handle_outgoing_packet(@out_packet)
118
+ @out_packet = ADBPacket.new
119
+ remainder = @out_packet.consume(remainder)
120
+ end
121
+ end
122
+ @q = []
123
+ end
124
+ end
125
+
126
+ def receive_data(data)
127
+ #puts "local data: #{data.inspect}"
128
+ @q << data
129
+ flush
130
+ end
131
+ end
132
+ end
@@ -0,0 +1,106 @@
1
+ require "optparse"
2
+
3
+ module Manymo
4
+ class Command
5
+ class <<self
6
+ def start(argv)
7
+ @argv = argv
8
+ @options = {}
9
+ @parser = OptionParser.new do |opts|
10
+ opts.banner = <<EOT
11
+ manymo - Manymo Command Line Tool (Version #{Manymo::VERSION})
12
+
13
+ Full documentation at https://www.manymo.com/pages/documentation/manymo-command-line-tool
14
+
15
+ Usage:
16
+
17
+ manymo [options] COMMAND [ARGUMENTS]
18
+
19
+ Commands:
20
+ launch EMULATORNAME Launch a headless emulator and make it appear like a local device
21
+ list List emulators; use the name attribute with the launch command
22
+ token Display a prompt to enter authorization token
23
+ tunnel TUNNELKEY Make an in-browser emulator appear like a local device
24
+
25
+ Options:
26
+ EOT
27
+
28
+ opts.on("--adb-path PATH_TO_ADB", "Specify path to adb executable; otherwise uses the one in your path") do |v|
29
+ @options[:adb_path] = v
30
+ end
31
+ end
32
+
33
+ begin
34
+ @parser.parse!
35
+ rescue
36
+ usage
37
+ exit
38
+ end
39
+
40
+ command = @argv[0]
41
+
42
+ @adb = ADB.new(@options[:adb_path])
43
+
44
+ @adb.ensure_available
45
+
46
+ @service = Service.new
47
+
48
+ case command
49
+ when /token/
50
+ get_auth_token(true)
51
+ when /tunnel/
52
+ args = @argv[1].split(':')
53
+ if args.count == 3
54
+ start_tunnel(args[0], args[1], args[2])
55
+ else
56
+ usage
57
+ end
58
+ when /list/
59
+ list_emulators
60
+ when /launch/
61
+ if @argv[1]
62
+ launch(@argv[1])
63
+ else
64
+ usage
65
+ end
66
+ when /version/
67
+ puts "Version #{Manymo::VERSION}"
68
+ else
69
+ usage
70
+ end
71
+ end
72
+
73
+ def usage
74
+ puts "#{@parser.banner}#{@parser.summarize.join("\n")}"
75
+ end
76
+
77
+
78
+ def list_emulators
79
+ puts @service.get("/emulators")
80
+ end
81
+
82
+ def launch(name)
83
+ hostname, emulator_console_port, password = @service.get("/emulators/launch_emulator/#{name}").split(":")
84
+ start_tunnel(hostname, emulator_console_port, password)
85
+ end
86
+
87
+ def start_tunnel(server, port, password)
88
+ EM.run {
89
+ # hit Control + C to stop
90
+ Signal.trap("INT") { EventMachine.stop }
91
+ Signal.trap("TERM") { EventMachine.stop }
92
+ EM
93
+ tunnel = Tunnel.new(server, port, password, @adb)
94
+
95
+ EM.add_shutdown_hook {
96
+ tunnel.shutdown("Process terminated")
97
+ }
98
+ tunnel.errback{ |message|
99
+ STDERR.puts message
100
+ exit 1
101
+ }
102
+ }
103
+ end
104
+ end
105
+ end
106
+ end
@@ -0,0 +1,68 @@
1
+ module Manymo
2
+ module ConsoleTunnel
3
+ def initialize(server, display, password)
4
+ @server = server
5
+ @display = display
6
+ @password = password
7
+ @connected = false
8
+ @q = []
9
+ end
10
+
11
+ def onclose(&blk)
12
+ @onclose = blk
13
+ end
14
+
15
+ def post_init
16
+ #puts "opening ws wss://#{@server}/console_tunnel?display=#{@display}&password=#{@password}"
17
+ @ws = Faye::WebSocket::Client.new("wss://#{@server}/console_tunnel?display=#{@display}&password=#{@password}")
18
+ @ws.on :open do
19
+ @connected = true
20
+ flush
21
+ end
22
+
23
+ @ws.on :message do |msg|
24
+ #puts "console incoming: #{msg.data}"
25
+ send_data msg.data
26
+ end
27
+
28
+ @ws.on :close do |event|
29
+ if @onclose
30
+ close_event = TunnelCloseEvent.new(:websocket)
31
+ close_event.websocket_event = event
32
+ @onclose.call(close_event)
33
+ @onclose = nil
34
+ end
35
+ @ws = nil
36
+ close_connection
37
+ end
38
+ end
39
+
40
+ def unbind
41
+ if @onclose
42
+ close_event = TunnelCloseEvent.new(:local)
43
+ @onclose.call(close_event)
44
+ @onclose = nil
45
+ end
46
+ if @ws
47
+ @ws.close
48
+ @ws = nil
49
+ end
50
+ end
51
+
52
+ def flush
53
+ if @connected
54
+ @q.each do |msg|
55
+ @ws.send msg.bytes.to_a
56
+ end
57
+ @q = []
58
+ end
59
+ end
60
+
61
+ def receive_data(data)
62
+ #puts "console outgoing: #{data.inspect}"
63
+ @q << data
64
+ flush
65
+ end
66
+ end
67
+ end
68
+
@@ -0,0 +1,71 @@
1
+ require "net/https"
2
+ require "uri"
3
+
4
+ module Manymo
5
+ class Service
6
+ HOST= ENV["MANYMO_API_HOST"] || "www.manymo.com"
7
+ BASE_URL="https://#{HOST}/api/v1"
8
+ def get_auth_token(force = false)
9
+ manymo_config_dir = File.expand_path('~/.manymo')
10
+ auth_token_path = manymo_config_dir + '/auth_token'
11
+ if ! force and File.exists?(auth_token_path)
12
+ return File.read(auth_token_path)
13
+ else
14
+ print "Please visit https://#{HOST}/user/client_applications to get your authorization token. Enter it here: "
15
+ STDOUT.flush
16
+ auth_token = STDIN.gets.chomp
17
+ if auth_token.empty?
18
+ STDERR.puts "No token supplied. Exiting."
19
+ exit 1
20
+ else
21
+ begin
22
+ if !File.exists?(manymo_config_dir)
23
+ FileUtils.mkdir(manymo_config_dir)
24
+ end
25
+ File.open(auth_token_path, "w") do |auth_token_path_file_handle|
26
+ auth_token_path_file_handle.write(auth_token)
27
+ end
28
+ rescue Errno::EACCES
29
+ STDERR.puts "Unable to store api token in #{auth_token_path}. You will be prompted again, next time this command is run."
30
+ end
31
+ end
32
+ return auth_token
33
+ end
34
+ end
35
+
36
+ def get(endpoint)
37
+ auth_token = get_auth_token
38
+ #puts "Auth token: #{auth_token}"
39
+ uri = URI.parse(BASE_URL + endpoint)
40
+ http = Net::HTTP.new(uri.host, uri.port)
41
+ http.verify_mode = OpenSSL::SSL::VERIFY_NONE
42
+ if uri.scheme == 'https'
43
+ http.use_ssl = true
44
+ end
45
+
46
+ request = Net::HTTP::Get.new(uri.request_uri)
47
+ request["Authorization"] = "OAuth token=#{auth_token}"
48
+ request["Accept"] = "text/plain"
49
+
50
+ begin
51
+ response = http.request(request)
52
+ if response.code.to_i / 100 == 2
53
+ response.body
54
+ elsif response.code.to_i == 401
55
+ puts "Invalid authorization token."
56
+ get_auth_token(true)
57
+ get(endpoint)
58
+ else
59
+ STDERR.puts "Error #{response.code}: #{response.body}"
60
+ exit 1
61
+ end
62
+ rescue SystemExit, Interrupt
63
+ exit 1
64
+ rescue Exception => e
65
+ STDERR.puts "Error: #{e.inspect}"
66
+ STDERR.puts "Please check that your network is connected and that no firewall rules are blocking #{uri.scheme} requests."
67
+ exit 1
68
+ end
69
+ end
70
+ end
71
+ end
@@ -0,0 +1,145 @@
1
+ require 'eventmachine'
2
+ require "socket"
3
+ require 'fileutils'
4
+ require 'shellwords'
5
+
6
+ module Manymo
7
+ class Tunnel
8
+ include EM::Deferrable
9
+ attr_accessor :serial_number
10
+
11
+ def initialize(server, port, password, adb)
12
+ @shutdown = false
13
+ @server = server
14
+ @port = port
15
+ @password = password
16
+ @display = (port.to_i - 5554) / 2
17
+ @adb = adb
18
+ EM::next_tick {
19
+ start
20
+ }
21
+ @adb_tunnels = []
22
+ end
23
+
24
+ def lockfile_for_port(port)
25
+ File.expand_path("~/.manymo/.tunnel_#{port}.lock")
26
+ end
27
+
28
+ def create_lockfile(port)
29
+ # Raises Errno::EEXIST if lockfile cannot be created
30
+ tunnel_lockfile = lockfile_for_port(port)
31
+ if File.exists?(tunnel_lockfile) && (Time.new - File.mtime(tunnel_lockfile)) > 60
32
+ File.unlink(tunnel_lockfile)
33
+ end
34
+ File.open(tunnel_lockfile, File::WRONLY|File::CREAT|File::EXCL) { |file| file.write $$ }
35
+ end
36
+
37
+ def remove_lockfile(port)
38
+ begin
39
+ File.unlink(lockfile_for_port(port))
40
+ rescue Errno::ENOENT
41
+ end
42
+ end
43
+
44
+ def find_available_local_emulator_port
45
+ (5554...5754).step(2) do |local_port|
46
+ created_lock = false
47
+ begin
48
+ create_lockfile(local_port)
49
+ created_lock = true
50
+ socket = Socket.new(Socket::AF_INET, Socket::SOCK_STREAM, 0)
51
+ socket.bind(Socket.pack_sockaddr_in( local_port, '127.0.0.1' ))
52
+ socket.close
53
+ return local_port
54
+ rescue Errno::EADDRINUSE, Errno::EEXIST
55
+ remove_lockfile(local_port) if created_lock
56
+ end
57
+ end
58
+ nil
59
+ end
60
+
61
+ def is_device_listed?(console_port)
62
+ devices_output = `#{@adb.path.shellescape} devices`
63
+ #puts "output #{devices_output}"
64
+ devices_output.match(/emulator-#{console_port}/)
65
+ end
66
+
67
+ def start_tunnels_with_local_port(local_port)
68
+ #puts "Starting tunnels for #{local_port}"
69
+ EventMachine::start_server '0.0.0.0', local_port, ConsoleTunnel, @server, @display, @password do |tunnel|
70
+ tunnel.onclose { |event|
71
+ if event.authorization_denied?
72
+ self.fail("Authorization denied for console connection.")
73
+ end
74
+ }
75
+ end
76
+ EventMachine::start_server '0.0.0.0', local_port+1, ADBTunnel, @server, @display, @password do |tunnel|
77
+ @adb_tunnels << tunnel
78
+ tunnel.onclose { |event|
79
+ #puts "adb closed: #{event}"
80
+ @adb_tunnels.delete(tunnel)
81
+ if event.authorization_denied?
82
+ shutdown("Authorization denied for adb connection.")
83
+ elsif event.emulator_terminated?
84
+ shutdown("Emulator terminated.")
85
+ elsif @adb_tunnels.empty? && !@shutdown
86
+ # retry
87
+ puts "adb tunnel closed. retrying..."
88
+ connect_emulator_to_adb_server(local_port)
89
+ end
90
+ }
91
+ end
92
+ connect_emulator_to_adb_server(local_port)
93
+
94
+ @timeout_timer = EM::Timer.new(50) do
95
+ shutdown("Timed out attempting to connect to emulator.")
96
+ end
97
+ end
98
+
99
+ def connect_emulator_to_adb_server(port)
100
+ connection_verifier = ADBConnectionVerifier.new(port)
101
+ connection_verifier.callback {
102
+ @serial_number = "emulator-#{port}"
103
+
104
+ # This will start adb server if it isn't started
105
+ listed = is_device_listed?(port)
106
+
107
+ if !listed
108
+ s = TCPSocket.open('localhost', 5037)
109
+ #s.puts("0012host:emulator:#{port+1}")
110
+ # Check again
111
+ listed = is_device_listed?(port)
112
+ listed = true
113
+ end
114
+
115
+ if listed
116
+ @timeout_timer.cancel if @timeout_timer
117
+ puts "Tunnel established; local serial number is: " + @serial_number
118
+ else
119
+ shutdown("Error connecting emulator to adb server.")
120
+ end
121
+ }
122
+ connection_verifier.errback {
123
+ shutdown("Error connecting to emulator emulator.")
124
+ }
125
+ end
126
+
127
+ def shutdown(reason)
128
+ @shutdown = true
129
+ self.fail(reason)
130
+ end
131
+
132
+ def start
133
+ local_port = find_available_local_emulator_port
134
+ if local_port
135
+ begin
136
+ start_tunnels_with_local_port(local_port)
137
+ ensure
138
+ remove_lockfile(local_port)
139
+ end
140
+ else
141
+ self.fail("Unable to allocate local port in range 5554-5754 for tunnel")
142
+ end
143
+ end
144
+ end
145
+ end
@@ -0,0 +1,23 @@
1
+ module Manymo
2
+ class TunnelCloseEvent
3
+ attr_accessor :reason
4
+ attr_accessor :websocket_event
5
+
6
+ def initialize(reason)
7
+ @reason = reason
8
+ end
9
+
10
+ def normal_closure?
11
+ @reason == :websocket && @websocket_event.code == 1000
12
+ end
13
+
14
+ def authorization_denied?
15
+ @reason == :websocket && @websocket_event.code == 4008
16
+ end
17
+
18
+ def emulator_terminated?
19
+ @reason == :websocket && @websocket_event.code == 4009
20
+ end
21
+ end
22
+
23
+ end
@@ -0,0 +1,3 @@
1
+ module Manymo
2
+ VERSION = "2.0.0.beta"
3
+ end
@@ -0,0 +1,24 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'manymo/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "manymo"
8
+ spec.version = Manymo::VERSION
9
+ spec.authors = ["Pete Schwamb"]
10
+ spec.email = ["pete@manymo.com"]
11
+ spec.description = %q{Manymo client tool.}
12
+ spec.summary = %q{A command line utility for connecting to remote emulators.}
13
+ spec.homepage = ""
14
+ spec.license = "MIT"
15
+
16
+ spec.files = `git ls-files`.split($/)
17
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
18
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
19
+ spec.require_paths = ["lib"]
20
+
21
+ spec.add_runtime_dependency 'faye', '~> 1.0.1'
22
+ spec.add_development_dependency "bundler", "~> 1.3"
23
+ spec.add_development_dependency "rake"
24
+ end
metadata ADDED
@@ -0,0 +1,108 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: manymo
3
+ version: !ruby/object:Gem::Version
4
+ version: 2.0.0.beta
5
+ platform: ruby
6
+ authors:
7
+ - Pete Schwamb
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2014-04-28 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: faye
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ~>
18
+ - !ruby/object:Gem::Version
19
+ version: 1.0.1
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ~>
25
+ - !ruby/object:Gem::Version
26
+ version: 1.0.1
27
+ - !ruby/object:Gem::Dependency
28
+ name: bundler
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ~>
32
+ - !ruby/object:Gem::Version
33
+ version: '1.3'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ~>
39
+ - !ruby/object:Gem::Version
40
+ version: '1.3'
41
+ - !ruby/object:Gem::Dependency
42
+ name: rake
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - '>='
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - '>='
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ description: Manymo client tool.
56
+ email:
57
+ - pete@manymo.com
58
+ executables:
59
+ - manymo
60
+ - tunnel_test.rb
61
+ extensions: []
62
+ extra_rdoc_files: []
63
+ files:
64
+ - .gitignore
65
+ - Gemfile
66
+ - LICENSE.txt
67
+ - README.md
68
+ - Rakefile
69
+ - bin/manymo
70
+ - bin/tunnel_test.rb
71
+ - lib/manymo.rb
72
+ - lib/manymo/adb.rb
73
+ - lib/manymo/adb_connection_verifier.rb
74
+ - lib/manymo/adb_packet.rb
75
+ - lib/manymo/adb_stream.rb
76
+ - lib/manymo/adb_tunnel.rb
77
+ - lib/manymo/command.rb
78
+ - lib/manymo/console_tunnel.rb
79
+ - lib/manymo/service.rb
80
+ - lib/manymo/tunnel.rb
81
+ - lib/manymo/tunnel_close_event.rb
82
+ - lib/manymo/version.rb
83
+ - manymo.gemspec
84
+ homepage: ''
85
+ licenses:
86
+ - MIT
87
+ metadata: {}
88
+ post_install_message:
89
+ rdoc_options: []
90
+ require_paths:
91
+ - lib
92
+ required_ruby_version: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - '>='
95
+ - !ruby/object:Gem::Version
96
+ version: '0'
97
+ required_rubygems_version: !ruby/object:Gem::Requirement
98
+ requirements:
99
+ - - '>'
100
+ - !ruby/object:Gem::Version
101
+ version: 1.3.1
102
+ requirements: []
103
+ rubyforge_project:
104
+ rubygems_version: 2.1.11
105
+ signing_key:
106
+ specification_version: 4
107
+ summary: A command line utility for connecting to remote emulators.
108
+ test_files: []