manymo 2.0.0.beta

Sign up to get free protection for your applications and to get access to all the features.
@@ -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: []