diff --git a/.gitignore b/.gitignore index 30a35b4b..455d6e26 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,3 @@ /node_modules/ -/lib/ /temp/ -/index.js /*.tgz diff --git a/Gruntfile.coffee b/Gruntfile.coffee deleted file mode 100644 index 9d072fc9..00000000 --- a/Gruntfile.coffee +++ /dev/null @@ -1,82 +0,0 @@ -module.exports = (grunt) -> - - grunt.initConfig - pkg: require './package' - coffee: - src: - options: - bare: true - noHeader: true - expand: true - cwd: 'src' - src: '**/*.coffee' - dest: 'lib' - ext: '.js' - index: - src: 'index.coffee' - dest: 'index.js' - clean: - lib: - src: 'lib' - index: - src: 'index.js' - coffeelint: - options: - indentation: - level: 'ignore' - no_backticks: - level: 'ignore' - src: - src: '<%= coffee.src.cwd %>/<%= coffee.src.src %>' - index: - src: '<%= coffee.index.src %>' - test: - src: 'test/**/*.coffee' - tasks: - src: 'tasks/**/*.coffee' - gruntfile: - src: 'Gruntfile.coffee' - jsonlint: - packagejson: - src: 'package.json' - watch: - src: - files: '<%= coffee.src.cwd %>/<%= coffee.src.src %>' - tasks: ['coffeelint:src', 'test'] - index: - files: '<%= coffee.index.src %>' - tasks: ['coffeelint:index', 'test'] - test: - files: '<%= coffeelint.test.src %>', - tasks: ['coffeelint:test', 'test'] - gruntfile: - files: '<%= coffeelint.gruntfile.src %>' - tasks: ['coffeelint:gruntfile'] - packagejson: - files: '<%= jsonlint.packagejson.src %>' - tasks: ['jsonlint:packagejson'] - exec: - mocha: - options: [ - '--compilers coffee:coffee-script/register' - '--reporter spec' - '--colors' - '--recursive' - ], - cmd: './node_modules/.bin/mocha <%= exec.mocha.options.join(" ") %>' - keycode: - generate: - dest: 'src/adb/keycode.coffee' - - grunt.loadNpmTasks 'grunt-contrib-clean' - grunt.loadNpmTasks 'grunt-contrib-coffee' - grunt.loadNpmTasks 'grunt-coffeelint' - grunt.loadNpmTasks 'grunt-jsonlint' - grunt.loadNpmTasks 'grunt-contrib-watch' - grunt.loadNpmTasks 'grunt-notify' - grunt.loadNpmTasks 'grunt-exec' - grunt.loadTasks './tasks' - - grunt.registerTask 'test', ['jsonlint', 'coffeelint', 'exec:mocha'] - grunt.registerTask 'build', ['coffee'] - grunt.registerTask 'default', ['test'] diff --git a/Gruntfile.js b/Gruntfile.js new file mode 100644 index 00000000..33f17fde --- /dev/null +++ b/Gruntfile.js @@ -0,0 +1,67 @@ +(function() { + module.exports = function(grunt) { + grunt.initConfig({ + pkg: require('./package'), + clean: { + lib: { + src: 'lib' + }, + index: { + src: 'index.js' + } + }, + jshint: { + all: ['Gruntfile.js', 'index.js', 'src/**/*.js', 'test/**/*.js', 'tasks/**/*.js', 'bench/**/*.js'] + }, + jsonlint: { + packagejson: { + src: 'package.json' + } + }, + watch: { + src: { + files: '<%= coffee.src.cwd %>/<%= coffee.src.src %>', + tasks: ['coffeelint:src', 'test'] + }, + index: { + files: '<%= coffee.index.src %>', + tasks: ['coffeelint:index', 'test'] + }, + test: { + files: '<%= coffeelint.test.src %>', + tasks: ['coffeelint:test', 'test'] + }, + gruntfile: { + files: '<%= coffeelint.gruntfile.src %>', + tasks: ['coffeelint:gruntfile'] + }, + packagejson: { + files: '<%= jsonlint.packagejson.src %>', + tasks: ['jsonlint:packagejson'] + } + }, + exec: { + mocha: { + options: ['--compilers coffee:coffee-script/register', '--reporter spec', '--colors', '--recursive'], + cmd: './node_modules/.bin/mocha <%= exec.mocha.options.join(" ") %>' + } + }, + keycode: { + generate: { + dest: 'src/adb/keycode.coffee' + } + } + }); + grunt.loadNpmTasks('grunt-contrib-clean'); + grunt.loadNpmTasks('grunt-contrib-jshint'); + grunt.loadNpmTasks('grunt-jsonlint'); + grunt.loadNpmTasks('grunt-contrib-watch'); + grunt.loadNpmTasks('grunt-notify'); + grunt.loadNpmTasks('grunt-exec'); + grunt.loadTasks('./tasks'); + grunt.registerTask('test', ['jsonlint', 'jshint', 'exec:mocha']); + // grunt.registerTask('build', ['coffee']); + return grunt.registerTask('default', ['test']); + }; + +}).call(this); diff --git a/bench/sync/pull.coffee b/bench/sync/pull.coffee deleted file mode 100644 index bc0e97d7..00000000 --- a/bench/sync/pull.coffee +++ /dev/null @@ -1,21 +0,0 @@ -Bench = require 'bench' -{spawn} = require 'child_process' - -Adb = require '../..' - -deviceId = process.env.DEVICE_ID - -module.exports = - compareCount: 3 - compare: - "pull /dev/graphics/fb0 using ADB CLI": (done) -> - proc = spawn 'adb', - ['-s', deviceId, 'pull', '/dev/graphics/fb0', '/dev/null'] - proc.stdout.on 'end', done - "pull /dev/graphics/fb0 using client.pull()": (done) -> - client = Adb.createClient() - client.pull deviceId, '/dev/graphics/fb0', (err, stream) -> - stream.resume() - stream.on 'end', done - -Bench.runMain() diff --git a/bench/sync/pull.js b/bench/sync/pull.js new file mode 100644 index 00000000..e78af024 --- /dev/null +++ b/bench/sync/pull.js @@ -0,0 +1,30 @@ +var Adb, Bench, deviceId, spawn; + +Bench = require('bench'); + +spawn = require('child_process').spawn; + +Adb = require('../..'); + +deviceId = process.env.DEVICE_ID; + +module.exports = { + compareCount: 3, + compare: { + "pull /dev/graphics/fb0 using ADB CLI": function(done) { + var proc; + proc = spawn('adb', ['-s', deviceId, 'pull', '/dev/graphics/fb0', '/dev/null']); + return proc.stdout.on('end', done); + }, + "pull /dev/graphics/fb0 using client.pull()": function(done) { + var client; + client = Adb.createClient(); + return client.pull(deviceId, '/dev/graphics/fb0', function(err, stream) { + stream.resume(); + return stream.on('end', done); + }); + } + } +}; + +Bench.runMain(); diff --git a/index.coffee b/index.coffee deleted file mode 100644 index 3dd01326..00000000 --- a/index.coffee +++ /dev/null @@ -1,5 +0,0 @@ -Path = require 'path' - -module.exports = switch Path.extname __filename - when '.coffee' then require './src/adb' - else require './lib/adb' diff --git a/index.js b/index.js new file mode 100644 index 00000000..a4c08acf --- /dev/null +++ b/index.js @@ -0,0 +1,15 @@ +(function() { + var Path; + + Path = require('path'); + + module.exports = (function() { + switch (Path.extname(__filename)) { + case '.coffee': + return require('./src/adb'); + default: + return require('./lib/adb'); + } + })(); + +}).call(this); diff --git a/package.json b/package.json index 08c0e2e7..78dc9cab 100644 --- a/package.json +++ b/package.json @@ -28,7 +28,7 @@ }, "scripts": { "postpublish": "grunt clean", - "prepublish": "grunt test coffee", + "prepublish": "grunt test", "test": "grunt test" }, "dependencies": { @@ -43,20 +43,17 @@ "devDependencies": { "bench": "~0.3.5", "chai": "~2.2.0", - "coffee-script": "~1.9.1", "grunt": "~0.4.5", "grunt-cli": "~0.1.13", - "grunt-coffeelint": "0.0.13", "grunt-contrib-clean": "~0.6.0", - "grunt-contrib-coffee": "~0.13.0", + "grunt-contrib-jshint": "^2.1.0", "grunt-contrib-watch": "~0.6.1", "grunt-exec": "~0.4.3", "grunt-jsonlint": "~1.0.4", "grunt-notify": "~0.4.1", "mocha": "~2.2.1", "sinon": "~1.14.1", - "sinon-chai": "~2.7.0", - "coffeelint": "~1.9.3" + "sinon-chai": "~2.7.0" }, "engines": { "node": ">= 0.10.4" diff --git a/src/adb.coffee b/src/adb.coffee deleted file mode 100644 index c14cb8cb..00000000 --- a/src/adb.coffee +++ /dev/null @@ -1,15 +0,0 @@ -Client = require './adb/client' -Keycode = require './adb/keycode' -util = require './adb/util' - -class Adb - @createClient: (options = {}) -> - options.host ||= process.env.ADB_HOST - options.port ||= process.env.ADB_PORT - new Client options - -Adb.Keycode = Keycode - -Adb.util = util - -module.exports = Adb diff --git a/src/adb.js b/src/adb.js new file mode 100644 index 00000000..8808d38a --- /dev/null +++ b/src/adb.js @@ -0,0 +1,29 @@ +var Adb, Client, Keycode, util; + +Client = require('./adb/client'); + +Keycode = require('./adb/keycode'); + +util = require('./adb/util'); + +Adb = (function() { + function Adb() {} + + Adb.createClient = function(options) { + if (options == null) { + options = {}; + } + options.host || (options.host = process.env.ADB_HOST); + options.port || (options.port = process.env.ADB_PORT); + return new Client(options); + }; + + return Adb; + +})(); + +Adb.Keycode = Keycode; + +Adb.util = util; + +module.exports = Adb; diff --git a/src/adb/auth.coffee b/src/adb/auth.coffee deleted file mode 100644 index 7de446e2..00000000 --- a/src/adb/auth.coffee +++ /dev/null @@ -1,82 +0,0 @@ -Promise = require 'bluebird' -forge = require 'node-forge' -BigInteger = forge.jsbn.BigInteger - -### -The stucture of an ADB RSAPublicKey is as follows: - - #define RSANUMBYTES 256 // 2048 bit key length - #define RSANUMWORDS (RSANUMBYTES / sizeof(uint32_t)) - - typedef struct RSAPublicKey { - int len; // Length of n[] in number of uint32_t - uint32_t n0inv; // -1 / n[0] mod 2^32 - uint32_t n[RSANUMWORDS]; // modulus as little endian array - uint32_t rr[RSANUMWORDS]; // R^2 as little endian array - int exponent; // 3 or 65537 - } RSAPublicKey; - -### -class Auth - # coffeelint: disable=max_line_length - RE = /^((?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?)\0?( .*|)\s*$/ - # coffeelint: enable=max_line_length - - readPublicKeyFromStruct = (struct, comment) -> - throw new Error "Invalid public key" unless struct.length - - # Keep track of what we've read already - offset = 0 - - # Get len - len = struct.readUInt32LE(offset) * 4 - offset += 4 - - unless struct.length is 4 + 4 + len + len + 4 - throw new Error "Invalid public key" - - # Skip n0inv, we don't need it - offset += 4 - - # Get n - n = new Buffer len - struct.copy(n, 0, offset, offset + len) - [].reverse.call(n) - offset += len - - # Skip rr, we don't need it - offset += len - - # Get e - e = struct.readUInt32LE(offset) - - unless e is 3 or e is 65537 - throw new Error "Invalid exponent #{e}, only 3 and 65537 are supported" - - # Restore the public key - key = forge.pki.setRsaPublicKey( - new BigInteger(n.toString('hex'), 16) - new BigInteger(e.toString(), 10) - ) - - # It will be difficult to retrieve the fingerprint later as it's based - # on the complete struct data, so let's just extend the key with it. - md = forge.md.md5.create() - md.update struct.toString('binary') - key.fingerprint = md.digest().toHex().match(/../g).join(':') - - # Expose comment for the same reason - key.comment = comment - - return key - - @parsePublicKey = (buffer) -> - new Promise (resolve, reject) -> - if match = RE.exec(buffer) - struct = new Buffer(match[1], 'base64') - comment = match[2].trim() - resolve readPublicKeyFromStruct struct, comment - else - reject new Error "Unrecognizable public key format" - -module.exports = Auth diff --git a/src/adb/auth.js b/src/adb/auth.js new file mode 100644 index 00000000..f3b392bc --- /dev/null +++ b/src/adb/auth.js @@ -0,0 +1,78 @@ +var Auth, BigInteger, Promise, forge; + +Promise = require('bluebird'); + +forge = require('node-forge'); + +BigInteger = forge.jsbn.BigInteger; + + +/* +The stucture of an ADB RSAPublicKey is as follows: + + #define RSANUMBYTES 256 // 2048 bit key length + #define RSANUMWORDS (RSANUMBYTES / sizeof(uint32_t)) + + typedef struct RSAPublicKey { + int len; // Length of n[] in number of uint32_t + uint32_t n0inv; // -1 / n[0] mod 2^32 + uint32_t n[RSANUMWORDS]; // modulus as little endian array + uint32_t rr[RSANUMWORDS]; // R^2 as little endian array + int exponent; // 3 or 65537 + } RSAPublicKey; + */ + +Auth = (function() { + var RE, readPublicKeyFromStruct; + + function Auth() {} + + RE = /^((?:[A-Za-z0-9+\/]{4})*(?:[A-Za-z0-9+\/]{2}==|[A-Za-z0-9+\/]{3}=)?)\0?( .*|)\s*$/; + + readPublicKeyFromStruct = function(struct, comment) { + var e, key, len, md, n, offset; + if (!struct.length) { + throw new Error("Invalid public key"); + } + offset = 0; + len = struct.readUInt32LE(offset) * 4; + offset += 4; + if (struct.length !== 4 + 4 + len + len + 4) { + throw new Error("Invalid public key"); + } + offset += 4; + n = new Buffer(len); + struct.copy(n, 0, offset, offset + len); + [].reverse.call(n); + offset += len; + offset += len; + e = struct.readUInt32LE(offset); + if (!(e === 3 || e === 65537)) { + throw new Error("Invalid exponent " + e + ", only 3 and 65537 are supported"); + } + key = forge.pki.setRsaPublicKey(new BigInteger(n.toString('hex'), 16), new BigInteger(e.toString(), 10)); + md = forge.md.md5.create(); + md.update(struct.toString('binary')); + key.fingerprint = md.digest().toHex().match(/../g).join(':'); + key.comment = comment; + return key; + }; + + Auth.parsePublicKey = function(buffer) { + return new Promise(function(resolve, reject) { + var comment, match, struct; + if (match = RE.exec(buffer)) { + struct = new Buffer(match[1], 'base64'); + comment = match[2].trim(); + return resolve(readPublicKeyFromStruct(struct, comment)); + } else { + return reject(new Error("Unrecognizable public key format")); + } + }); + }; + + return Auth; + +})(); + +module.exports = Auth; diff --git a/src/adb/client.coffee b/src/adb/client.coffee deleted file mode 100644 index 66e64a5f..00000000 --- a/src/adb/client.coffee +++ /dev/null @@ -1,500 +0,0 @@ -Monkey = require 'adbkit-monkey' -Logcat = require 'adbkit-logcat' -Promise = require 'bluebird' -debug = require('debug')('adb:client') - -Connection = require './connection' -Sync = require './sync' -Parser = require './parser' -ProcStat = require './proc/stat' - -HostVersionCommand = require './command/host/version' -HostConnectCommand = require './command/host/connect' -HostDevicesCommand = require './command/host/devices' -HostDevicesWithPathsCommand = require './command/host/deviceswithpaths' -HostDisconnectCommand = require './command/host/disconnect' -HostTrackDevicesCommand = require './command/host/trackdevices' -HostKillCommand = require './command/host/kill' -HostTransportCommand = require './command/host/transport' - -ClearCommand = require './command/host-transport/clear' -FrameBufferCommand = require './command/host-transport/framebuffer' -GetFeaturesCommand = require './command/host-transport/getfeatures' -GetPackagesCommand = require './command/host-transport/getpackages' -GetPropertiesCommand = require './command/host-transport/getproperties' -InstallCommand = require './command/host-transport/install' -IsInstalledCommand = require './command/host-transport/isinstalled' -ListReversesCommand = require './command/host-transport/listreverses' -LocalCommand = require './command/host-transport/local' -LogcatCommand = require './command/host-transport/logcat' -LogCommand = require './command/host-transport/log' -MonkeyCommand = require './command/host-transport/monkey' -RebootCommand = require './command/host-transport/reboot' -RemountCommand = require './command/host-transport/remount' -RootCommand = require './command/host-transport/root' -ReverseCommand = require './command/host-transport/reverse' -ScreencapCommand = require './command/host-transport/screencap' -ShellCommand = require './command/host-transport/shell' -StartActivityCommand = require './command/host-transport/startactivity' -StartServiceCommand = require './command/host-transport/startservice' -SyncCommand = require './command/host-transport/sync' -TcpCommand = require './command/host-transport/tcp' -TcpIpCommand = require './command/host-transport/tcpip' -TrackJdwpCommand = require './command/host-transport/trackjdwp' -UninstallCommand = require './command/host-transport/uninstall' -UsbCommand = require './command/host-transport/usb' -WaitBootCompleteCommand = require './command/host-transport/waitbootcomplete' - -ForwardCommand = require './command/host-serial/forward' -GetDevicePathCommand = require './command/host-serial/getdevicepath' -GetSerialNoCommand = require './command/host-serial/getserialno' -GetStateCommand = require './command/host-serial/getstate' -ListForwardsCommand = require './command/host-serial/listforwards' -WaitForDeviceCommand = require './command/host-serial/waitfordevice' - -TcpUsbServer = require './tcpusb/server' - -class Client - constructor: (@options = {}) -> - @options.port ||= 5037 - @options.bin ||= 'adb' - - createTcpUsbBridge: (serial, options) -> - new TcpUsbServer this, serial, options - - connection: -> - resolver = Promise.defer() - conn = new Connection(@options) - .on 'error', errorListener = (err) -> - resolver.reject err - .on 'connect', connectListener = -> - resolver.resolve conn - .connect() - resolver.promise.finally -> - conn.removeListener 'error', errorListener - conn.removeListener 'connect', connectListener - - version: (callback) -> - this.connection() - .then (conn) -> - new HostVersionCommand conn - .execute() - .nodeify callback - - connect: (host, port = 5555, callback) -> - if typeof port is 'function' - callback = port - port = 5555 - if host.indexOf(':') isnt -1 - [host, port] = host.split ':', 2 - this.connection() - .then (conn) -> - new HostConnectCommand conn - .execute host, port - .nodeify callback - - disconnect: (host, port = 5555, callback) -> - if typeof port is 'function' - callback = port - port = 5555 - if host.indexOf(':') isnt -1 - [host, port] = host.split ':', 2 - this.connection() - .then (conn) -> - new HostDisconnectCommand conn - .execute host, port - .nodeify callback - - listDevices: (callback) -> - this.connection() - .then (conn) -> - new HostDevicesCommand conn - .execute() - .nodeify callback - - listDevicesWithPaths: (callback) -> - this.connection() - .then (conn) -> - new HostDevicesWithPathsCommand conn - .execute() - .nodeify callback - - trackDevices: (callback) -> - this.connection() - .then (conn) -> - new HostTrackDevicesCommand conn - .execute() - .nodeify callback - - kill: (callback) -> - this.connection() - .then (conn) -> - new HostKillCommand conn - .execute() - .nodeify callback - - getSerialNo: (serial, callback) -> - this.connection() - .then (conn) -> - new GetSerialNoCommand conn - .execute serial - .nodeify callback - - getDevicePath: (serial, callback) -> - this.connection() - .then (conn) -> - new GetDevicePathCommand conn - .execute serial - .nodeify callback - - getState: (serial, callback) -> - this.connection() - .then (conn) -> - new GetStateCommand conn - .execute serial - .nodeify callback - - getProperties: (serial, callback) -> - this.transport serial - .then (transport) -> - new GetPropertiesCommand transport - .execute() - .nodeify callback - - getFeatures: (serial, callback) -> - this.transport serial - .then (transport) -> - new GetFeaturesCommand transport - .execute() - .nodeify callback - - getPackages: (serial, callback) -> - this.transport serial - .then (transport) -> - new GetPackagesCommand transport - .execute() - .nodeify callback - - getDHCPIpAddress: (serial, iface = 'wlan0', callback) -> - if typeof iface is 'function' - callback = iface - iface = 'wlan0' - this.getProperties(serial) - .then (properties) -> - return ip if ip = properties["dhcp.#{iface}.ipaddress"] - throw new Error "Unable to find ipaddress for '#{iface}'" - - forward: (serial, local, remote, callback) -> - this.connection() - .then (conn) -> - new ForwardCommand conn - .execute serial, local, remote - .nodeify callback - - listForwards: (serial, callback) -> - this.connection() - .then (conn) -> - new ListForwardsCommand conn - .execute serial - .nodeify callback - - reverse: (serial, remote, local, callback) -> - this.transport serial - .then (transport) -> - new ReverseCommand transport - .execute remote, local - .nodeify callback - - listReverses: (serial, callback) -> - this.transport serial - .then (transport) -> - new ListReversesCommand transport - .execute() - .nodeify callback - - transport: (serial, callback) -> - this.connection() - .then (conn) -> - new HostTransportCommand conn - .execute serial - .return conn - .nodeify callback - - shell: (serial, command, callback) -> - this.transport serial - .then (transport) -> - new ShellCommand transport - .execute command - .nodeify callback - - reboot: (serial, callback) -> - this.transport serial - .then (transport) -> - new RebootCommand transport - .execute() - .nodeify callback - - remount: (serial, callback) -> - this.transport serial - .then (transport) -> - new RemountCommand transport - .execute() - .nodeify callback - - root: (serial, callback) -> - this.transport serial - .then (transport) -> - new RootCommand transport - .execute() - .nodeify callback - - trackJdwp: (serial, callback) -> - this.transport serial - .then (transport) -> - new TrackJdwpCommand transport - .execute() - .nodeify callback - - framebuffer: (serial, format = 'raw', callback) -> - if typeof format is 'function' - callback = format - format = 'raw' - this.transport serial - .then (transport) -> - new FrameBufferCommand transport - .execute format - .nodeify callback - - screencap: (serial, callback) -> - this.transport serial - .then (transport) => - new ScreencapCommand transport - .execute() - .catch (err) => - debug "Emulating screencap command due to '#{err}'" - this.framebuffer serial, 'png' - .nodeify callback - - openLocal: (serial, path, callback) -> - this.transport serial - .then (transport) -> - new LocalCommand transport - .execute path - .nodeify callback - - openLog: (serial, name, callback) -> - this.transport serial - .then (transport) -> - new LogCommand transport - .execute name - .nodeify callback - - openTcp: (serial, port, host, callback) -> - if typeof host is 'function' - callback = host - host = undefined - this.transport serial - .then (transport) -> - new TcpCommand transport - .execute port, host - .nodeify callback - - openMonkey: (serial, port = 1080, callback) -> - if typeof port is 'function' - callback = port - port = 1080 - tryConnect = (times) => - this.openTcp serial, port - .then (stream) -> - Monkey.connectStream stream - .catch (err) -> - if times -= 1 - debug "Monkey can't be reached, trying #{times} more times" - Promise.delay 100 - .then -> - tryConnect times - else - throw err - tryConnect 1 - .catch (err) => - this.transport serial - .then (transport) -> - new MonkeyCommand transport - .execute port - .then (out) -> - tryConnect 20 - .then (monkey) -> - monkey.once 'end', -> - out.end() - .nodeify callback - - openLogcat: (serial, options, callback) -> - if typeof options is 'function' - callback = options - options = {} - this.transport serial - .then (transport) -> - new LogcatCommand transport - .execute options - .then (stream) -> - Logcat.readStream stream, - fixLineFeeds: false - .nodeify callback - - openProcStat: (serial, callback) -> - this.syncService serial - .then (sync) -> - new ProcStat sync - .nodeify callback - - clear: (serial, pkg, callback) -> - this.transport serial - .then (transport) -> - new ClearCommand transport - .execute pkg - .nodeify callback - - install: (serial, apk, callback) -> - temp = Sync.temp if typeof apk is 'string' then apk else '_stream.apk' - this.push serial, apk, temp - .then (transfer) => - resolver = Promise.defer() - - transfer.on 'error', errorListener = (err) -> - resolver.reject err - - transfer.on 'end', endListener = => - resolver.resolve this.installRemote serial, temp - - resolver.promise.finally -> - transfer.removeListener 'error', errorListener - transfer.removeListener 'end', endListener - - .nodeify callback - - installRemote: (serial, apk, callback) -> - this.transport serial - .then (transport) => - new InstallCommand transport - .execute apk - .then => - this.shell serial, ['rm', '-f', apk] - .then (stream) -> - new Parser stream - .readAll() - .then (out) -> - true - .nodeify callback - - uninstall: (serial, pkg, callback) -> - this.transport serial - .then (transport) -> - new UninstallCommand transport - .execute pkg - .nodeify callback - - isInstalled: (serial, pkg, callback) -> - this.transport serial - .then (transport) -> - new IsInstalledCommand transport - .execute pkg - .nodeify callback - - startActivity: (serial, options, callback) -> - this.transport serial - .then (transport) -> - new StartActivityCommand transport - .execute options - .catch NoUserOptionError, => - options.user = null - this.startActivity serial, options - .nodeify callback - - startService: (serial, options, callback) -> - this.transport serial - .then (transport) -> - options.user = 0 unless options.user or options.user is null - new StartServiceCommand transport - .execute options - .catch NoUserOptionError, => - options.user = null - this.startService serial, options - .nodeify callback - - syncService: (serial, callback) -> - this.transport serial - .then (transport) -> - new SyncCommand transport - .execute() - .nodeify callback - - stat: (serial, path, callback) -> - this.syncService serial - .then (sync) -> - sync.stat path - .finally -> - sync.end() - .nodeify callback - - readdir: (serial, path, callback) -> - this.syncService serial - .then (sync) -> - sync.readdir path - .finally -> - sync.end() - .nodeify callback - - pull: (serial, path, callback) -> - this.syncService serial - .then (sync) -> - sync.pull path - .on 'end', -> - sync.end() - .nodeify callback - - push: (serial, contents, path, mode, callback) -> - if typeof mode is 'function' - callback = mode - mode = undefined - this.syncService serial - .then (sync) -> - sync.push contents, path, mode - .on 'end', -> - sync.end() - .nodeify callback - - tcpip: (serial, port = 5555, callback) -> - if typeof port is 'function' - callback = port - port = 5555 - this.transport serial - .then (transport) -> - new TcpIpCommand transport - .execute port - .nodeify callback - - usb: (serial, callback) -> - this.transport serial - .then (transport) -> - new UsbCommand transport - .execute() - .nodeify callback - - waitBootComplete: (serial, callback) -> - this.transport serial - .then (transport) -> - new WaitBootCompleteCommand transport - .execute() - .nodeify callback - - waitForDevice: (serial, callback) -> - this.connection() - .then (conn) -> - new WaitForDeviceCommand conn - .execute serial - .nodeify callback - - NoUserOptionError = (err) -> - err.message.indexOf('--user') isnt -1 - -module.exports = Client diff --git a/src/adb/client.js b/src/adb/client.js new file mode 100644 index 00000000..bf10e06a --- /dev/null +++ b/src/adb/client.js @@ -0,0 +1,573 @@ +var ClearCommand, Client, Connection, ForwardCommand, FrameBufferCommand, GetDevicePathCommand, GetFeaturesCommand, GetPackagesCommand, GetPropertiesCommand, GetSerialNoCommand, GetStateCommand, HostConnectCommand, HostDevicesCommand, HostDevicesWithPathsCommand, HostDisconnectCommand, HostKillCommand, HostTrackDevicesCommand, HostTransportCommand, HostVersionCommand, InstallCommand, IsInstalledCommand, ListForwardsCommand, ListReversesCommand, LocalCommand, LogCommand, Logcat, LogcatCommand, Monkey, MonkeyCommand, Parser, ProcStat, Promise, RebootCommand, RemountCommand, ReverseCommand, RootCommand, ScreencapCommand, ShellCommand, StartActivityCommand, StartServiceCommand, Sync, SyncCommand, TcpCommand, TcpIpCommand, TcpUsbServer, TrackJdwpCommand, UninstallCommand, UsbCommand, WaitBootCompleteCommand, WaitForDeviceCommand, debug; + +Monkey = require('adbkit-monkey'); + +Logcat = require('adbkit-logcat'); + +Promise = require('bluebird'); + +debug = require('debug')('adb:client'); + +Connection = require('./connection'); + +Sync = require('./sync'); + +Parser = require('./parser'); + +ProcStat = require('./proc/stat'); + +HostVersionCommand = require('./command/host/version'); + +HostConnectCommand = require('./command/host/connect'); + +HostDevicesCommand = require('./command/host/devices'); + +HostDevicesWithPathsCommand = require('./command/host/deviceswithpaths'); + +HostDisconnectCommand = require('./command/host/disconnect'); + +HostTrackDevicesCommand = require('./command/host/trackdevices'); + +HostKillCommand = require('./command/host/kill'); + +HostTransportCommand = require('./command/host/transport'); + +ClearCommand = require('./command/host-transport/clear'); + +FrameBufferCommand = require('./command/host-transport/framebuffer'); + +GetFeaturesCommand = require('./command/host-transport/getfeatures'); + +GetPackagesCommand = require('./command/host-transport/getpackages'); + +GetPropertiesCommand = require('./command/host-transport/getproperties'); + +InstallCommand = require('./command/host-transport/install'); + +IsInstalledCommand = require('./command/host-transport/isinstalled'); + +ListReversesCommand = require('./command/host-transport/listreverses'); + +LocalCommand = require('./command/host-transport/local'); + +LogcatCommand = require('./command/host-transport/logcat'); + +LogCommand = require('./command/host-transport/log'); + +MonkeyCommand = require('./command/host-transport/monkey'); + +RebootCommand = require('./command/host-transport/reboot'); + +RemountCommand = require('./command/host-transport/remount'); + +RootCommand = require('./command/host-transport/root'); + +ReverseCommand = require('./command/host-transport/reverse'); + +ScreencapCommand = require('./command/host-transport/screencap'); + +ShellCommand = require('./command/host-transport/shell'); + +StartActivityCommand = require('./command/host-transport/startactivity'); + +StartServiceCommand = require('./command/host-transport/startservice'); + +SyncCommand = require('./command/host-transport/sync'); + +TcpCommand = require('./command/host-transport/tcp'); + +TcpIpCommand = require('./command/host-transport/tcpip'); + +TrackJdwpCommand = require('./command/host-transport/trackjdwp'); + +UninstallCommand = require('./command/host-transport/uninstall'); + +UsbCommand = require('./command/host-transport/usb'); + +WaitBootCompleteCommand = require('./command/host-transport/waitbootcomplete'); + +ForwardCommand = require('./command/host-serial/forward'); + +GetDevicePathCommand = require('./command/host-serial/getdevicepath'); + +GetSerialNoCommand = require('./command/host-serial/getserialno'); + +GetStateCommand = require('./command/host-serial/getstate'); + +ListForwardsCommand = require('./command/host-serial/listforwards'); + +WaitForDeviceCommand = require('./command/host-serial/waitfordevice'); + +TcpUsbServer = require('./tcpusb/server'); + +Client = (function() { + var NoUserOptionError; + + function Client(options1) { + var base, base1; + this.options = options1 != null ? options1 : {}; + (base = this.options).port || (base.port = 5037); + (base1 = this.options).bin || (base1.bin = 'adb'); + } + + Client.prototype.createTcpUsbBridge = function(serial, options) { + return new TcpUsbServer(this, serial, options); + }; + + Client.prototype.connection = function() { + var conn, connectListener, errorListener, resolver; + resolver = Promise.defer(); + conn = new Connection(this.options).on('error', errorListener = function(err) { + return resolver.reject(err); + }).on('connect', connectListener = function() { + return resolver.resolve(conn); + }).connect(); + return resolver.promise["finally"](function() { + conn.removeListener('error', errorListener); + return conn.removeListener('connect', connectListener); + }); + }; + + Client.prototype.version = function(callback) { + return this.connection().then(function(conn) { + return new HostVersionCommand(conn).execute(); + }).nodeify(callback); + }; + + Client.prototype.connect = function(host, port, callback) { + var ref; + if (port == null) { + port = 5555; + } + if (typeof port === 'function') { + callback = port; + port = 5555; + } + if (host.indexOf(':') !== -1) { + ref = host.split(':', 2), host = ref[0], port = ref[1]; + } + return this.connection().then(function(conn) { + return new HostConnectCommand(conn).execute(host, port); + }).nodeify(callback); + }; + + Client.prototype.disconnect = function(host, port, callback) { + var ref; + if (port == null) { + port = 5555; + } + if (typeof port === 'function') { + callback = port; + port = 5555; + } + if (host.indexOf(':') !== -1) { + ref = host.split(':', 2), host = ref[0], port = ref[1]; + } + return this.connection().then(function(conn) { + return new HostDisconnectCommand(conn).execute(host, port); + }).nodeify(callback); + }; + + Client.prototype.listDevices = function(callback) { + return this.connection().then(function(conn) { + return new HostDevicesCommand(conn).execute(); + }).nodeify(callback); + }; + + Client.prototype.listDevicesWithPaths = function(callback) { + return this.connection().then(function(conn) { + return new HostDevicesWithPathsCommand(conn).execute(); + }).nodeify(callback); + }; + + Client.prototype.trackDevices = function(callback) { + return this.connection().then(function(conn) { + return new HostTrackDevicesCommand(conn).execute(); + }).nodeify(callback); + }; + + Client.prototype.kill = function(callback) { + return this.connection().then(function(conn) { + return new HostKillCommand(conn).execute(); + }).nodeify(callback); + }; + + Client.prototype.getSerialNo = function(serial, callback) { + return this.connection().then(function(conn) { + return new GetSerialNoCommand(conn).execute(serial); + }).nodeify(callback); + }; + + Client.prototype.getDevicePath = function(serial, callback) { + return this.connection().then(function(conn) { + return new GetDevicePathCommand(conn).execute(serial); + }).nodeify(callback); + }; + + Client.prototype.getState = function(serial, callback) { + return this.connection().then(function(conn) { + return new GetStateCommand(conn).execute(serial); + }).nodeify(callback); + }; + + Client.prototype.getProperties = function(serial, callback) { + return this.transport(serial).then(function(transport) { + return new GetPropertiesCommand(transport).execute(); + }).nodeify(callback); + }; + + Client.prototype.getFeatures = function(serial, callback) { + return this.transport(serial).then(function(transport) { + return new GetFeaturesCommand(transport).execute(); + }).nodeify(callback); + }; + + Client.prototype.getPackages = function(serial, callback) { + return this.transport(serial).then(function(transport) { + return new GetPackagesCommand(transport).execute(); + }).nodeify(callback); + }; + + Client.prototype.getDHCPIpAddress = function(serial, iface, callback) { + if (iface == null) { + iface = 'wlan0'; + } + if (typeof iface === 'function') { + callback = iface; + iface = 'wlan0'; + } + return this.getProperties(serial).then(function(properties) { + var ip; + if (ip = properties["dhcp." + iface + ".ipaddress"]) { + return ip; + } + throw new Error("Unable to find ipaddress for '" + iface + "'"); + }); + }; + + Client.prototype.forward = function(serial, local, remote, callback) { + return this.connection().then(function(conn) { + return new ForwardCommand(conn).execute(serial, local, remote); + }).nodeify(callback); + }; + + Client.prototype.listForwards = function(serial, callback) { + return this.connection().then(function(conn) { + return new ListForwardsCommand(conn).execute(serial); + }).nodeify(callback); + }; + + Client.prototype.reverse = function(serial, remote, local, callback) { + return this.transport(serial).then(function(transport) { + return new ReverseCommand(transport).execute(remote, local).nodeify(callback); + }); + }; + + Client.prototype.listReverses = function(serial, callback) { + return this.transport(serial).then(function(transport) { + return new ListReversesCommand(transport).execute(); + }).nodeify(callback); + }; + + Client.prototype.transport = function(serial, callback) { + return this.connection().then(function(conn) { + return new HostTransportCommand(conn).execute(serial)["return"](conn); + }).nodeify(callback); + }; + + Client.prototype.shell = function(serial, command, callback) { + return this.transport(serial).then(function(transport) { + return new ShellCommand(transport).execute(command); + }).nodeify(callback); + }; + + Client.prototype.reboot = function(serial, callback) { + return this.transport(serial).then(function(transport) { + return new RebootCommand(transport).execute(); + }).nodeify(callback); + }; + + Client.prototype.remount = function(serial, callback) { + return this.transport(serial).then(function(transport) { + return new RemountCommand(transport).execute(); + }).nodeify(callback); + }; + + Client.prototype.root = function(serial, callback) { + return this.transport(serial).then(function(transport) { + return new RootCommand(transport).execute(); + }).nodeify(callback); + }; + + Client.prototype.trackJdwp = function(serial, callback) { + return this.transport(serial).then(function(transport) { + return new TrackJdwpCommand(transport).execute(); + }).nodeify(callback); + }; + + Client.prototype.framebuffer = function(serial, format, callback) { + if (format == null) { + format = 'raw'; + } + if (typeof format === 'function') { + callback = format; + format = 'raw'; + } + return this.transport(serial).then(function(transport) { + return new FrameBufferCommand(transport).execute(format); + }).nodeify(callback); + }; + + Client.prototype.screencap = function(serial, callback) { + return this.transport(serial).then((function(_this) { + return function(transport) { + return new ScreencapCommand(transport).execute()["catch"](function(err) { + debug("Emulating screencap command due to '" + err + "'"); + return _this.framebuffer(serial, 'png'); + }); + }; + })(this)).nodeify(callback); + }; + + Client.prototype.openLocal = function(serial, path, callback) { + return this.transport(serial).then(function(transport) { + return new LocalCommand(transport).execute(path); + }).nodeify(callback); + }; + + Client.prototype.openLog = function(serial, name, callback) { + return this.transport(serial).then(function(transport) { + return new LogCommand(transport).execute(name); + }).nodeify(callback); + }; + + Client.prototype.openTcp = function(serial, port, host, callback) { + if (typeof host === 'function') { + callback = host; + host = void 0; + } + return this.transport(serial).then(function(transport) { + return new TcpCommand(transport).execute(port, host); + }).nodeify(callback); + }; + + Client.prototype.openMonkey = function(serial, port, callback) { + var tryConnect; + if (port == null) { + port = 1080; + } + if (typeof port === 'function') { + callback = port; + port = 1080; + } + tryConnect = (function(_this) { + return function(times) { + return _this.openTcp(serial, port).then(function(stream) { + return Monkey.connectStream(stream); + })["catch"](function(err) { + if (times -= 1) { + debug("Monkey can't be reached, trying " + times + " more times"); + return Promise.delay(100).then(function() { + return tryConnect(times); + }); + } else { + throw err; + } + }); + }; + })(this); + return tryConnect(1)["catch"]((function(_this) { + return function(err) { + return _this.transport(serial).then(function(transport) { + return new MonkeyCommand(transport).execute(port); + }).then(function(out) { + return tryConnect(20).then(function(monkey) { + return monkey.once('end', function() { + return out.end(); + }); + }); + }); + }; + })(this)).nodeify(callback); + }; + + Client.prototype.openLogcat = function(serial, options, callback) { + if (typeof options === 'function') { + callback = options; + options = {}; + } + return this.transport(serial).then(function(transport) { + return new LogcatCommand(transport).execute(options); + }).then(function(stream) { + return Logcat.readStream(stream, { + fixLineFeeds: false + }); + }).nodeify(callback); + }; + + Client.prototype.openProcStat = function(serial, callback) { + return this.syncService(serial).then(function(sync) { + return new ProcStat(sync); + }).nodeify(callback); + }; + + Client.prototype.clear = function(serial, pkg, callback) { + return this.transport(serial).then(function(transport) { + return new ClearCommand(transport).execute(pkg); + }).nodeify(callback); + }; + + Client.prototype.install = function(serial, apk, callback) { + var temp; + temp = Sync.temp(typeof apk === 'string' ? apk : '_stream.apk'); + return this.push(serial, apk, temp).then((function(_this) { + return function(transfer) { + var endListener, errorListener, resolver; + resolver = Promise.defer(); + transfer.on('error', errorListener = function(err) { + return resolver.reject(err); + }); + transfer.on('end', endListener = function() { + return resolver.resolve(_this.installRemote(serial, temp)); + }); + return resolver.promise["finally"](function() { + transfer.removeListener('error', errorListener); + return transfer.removeListener('end', endListener); + }); + }; + })(this)).nodeify(callback); + }; + + Client.prototype.installRemote = function(serial, apk, callback) { + return this.transport(serial).then((function(_this) { + return function(transport) { + return new InstallCommand(transport).execute(apk).then(function() { + return _this.shell(serial, ['rm', '-f', apk]); + }).then(function(stream) { + return new Parser(stream).readAll(); + }).then(function(out) { + return true; + }); + }; + })(this)).nodeify(callback); + }; + + Client.prototype.uninstall = function(serial, pkg, callback) { + return this.transport(serial).then(function(transport) { + return new UninstallCommand(transport).execute(pkg); + }).nodeify(callback); + }; + + Client.prototype.isInstalled = function(serial, pkg, callback) { + return this.transport(serial).then(function(transport) { + return new IsInstalledCommand(transport).execute(pkg); + }).nodeify(callback); + }; + + Client.prototype.startActivity = function(serial, options, callback) { + return this.transport(serial).then(function(transport) { + return new StartActivityCommand(transport).execute(options); + })["catch"](NoUserOptionError, (function(_this) { + return function() { + options.user = null; + return _this.startActivity(serial, options); + }; + })(this)).nodeify(callback); + }; + + Client.prototype.startService = function(serial, options, callback) { + return this.transport(serial).then(function(transport) { + if (!(options.user || options.user === null)) { + options.user = 0; + } + return new StartServiceCommand(transport).execute(options); + })["catch"](NoUserOptionError, (function(_this) { + return function() { + options.user = null; + return _this.startService(serial, options); + }; + })(this)).nodeify(callback); + }; + + Client.prototype.syncService = function(serial, callback) { + return this.transport(serial).then(function(transport) { + return new SyncCommand(transport).execute(); + }).nodeify(callback); + }; + + Client.prototype.stat = function(serial, path, callback) { + return this.syncService(serial).then(function(sync) { + return sync.stat(path)["finally"](function() { + return sync.end(); + }); + }).nodeify(callback); + }; + + Client.prototype.readdir = function(serial, path, callback) { + return this.syncService(serial).then(function(sync) { + return sync.readdir(path)["finally"](function() { + return sync.end(); + }); + }).nodeify(callback); + }; + + Client.prototype.pull = function(serial, path, callback) { + return this.syncService(serial).then(function(sync) { + return sync.pull(path).on('end', function() { + return sync.end(); + }); + }).nodeify(callback); + }; + + Client.prototype.push = function(serial, contents, path, mode, callback) { + if (typeof mode === 'function') { + callback = mode; + mode = void 0; + } + return this.syncService(serial).then(function(sync) { + return sync.push(contents, path, mode).on('end', function() { + return sync.end(); + }); + }).nodeify(callback); + }; + + Client.prototype.tcpip = function(serial, port, callback) { + if (port == null) { + port = 5555; + } + if (typeof port === 'function') { + callback = port; + port = 5555; + } + return this.transport(serial).then(function(transport) { + return new TcpIpCommand(transport).execute(port); + }).nodeify(callback); + }; + + Client.prototype.usb = function(serial, callback) { + return this.transport(serial).then(function(transport) { + return new UsbCommand(transport).execute(); + }).nodeify(callback); + }; + + Client.prototype.waitBootComplete = function(serial, callback) { + return this.transport(serial).then(function(transport) { + return new WaitBootCompleteCommand(transport).execute(); + }).nodeify(callback); + }; + + Client.prototype.waitForDevice = function(serial, callback) { + return this.connection().then(function(conn) { + return new WaitForDeviceCommand(conn).execute(serial); + }).nodeify(callback); + }; + + NoUserOptionError = function(err) { + return err.message.indexOf('--user') !== -1; + }; + + return Client; + +})(); + +module.exports = Client; diff --git a/src/adb/command.coffee b/src/adb/command.coffee deleted file mode 100644 index 261d8fbb..00000000 --- a/src/adb/command.coffee +++ /dev/null @@ -1,43 +0,0 @@ -debug = require('debug')('adb:command') - -Parser = require './parser' -Protocol = require './protocol' - -class Command - RE_SQUOT = /'/g - RE_ESCAPE = /([$`\\!"])/g - - constructor: (@connection) -> - @parser = @connection.parser - @protocol = Protocol - - execute: -> - throw new Exception 'Missing implementation' - - _send: (data) -> - encoded = Protocol.encodeData data - debug "Send '#{encoded}'" - @connection.write encoded - return this - - # Note that this is just for convenience, not security. - _escape: (arg) -> - switch typeof arg - when 'number' - arg - else - "'" + arg.toString().replace(RE_SQUOT, "'\"'\"'") + "'" - - # Note that this is just for convenience, not security. Also, for some - # incomprehensible reason, some Lenovo devices (e.g. Lenovo A806) behave - # differently when arguments are given inside single quotes. See - # https://github.com/openstf/stf/issues/471 for more information. So that's - # why we now use double quotes here. - _escapeCompat: (arg) -> - switch typeof arg - when 'number' - arg - else - '"' + arg.toString().replace(RE_ESCAPE, '\\$1') + '"' - -module.exports = Command diff --git a/src/adb/command.js b/src/adb/command.js new file mode 100644 index 00000000..06bf75d2 --- /dev/null +++ b/src/adb/command.js @@ -0,0 +1,56 @@ +var Command, Parser, Protocol, debug; + +debug = require('debug')('adb:command'); + +Parser = require('./parser'); + +Protocol = require('./protocol'); + +Command = (function() { + var RE_ESCAPE, RE_SQUOT; + + RE_SQUOT = /'/g; + + RE_ESCAPE = /([$`\\!"])/g; + + function Command(connection) { + this.connection = connection; + this.parser = this.connection.parser; + this.protocol = Protocol; + } + + Command.prototype.execute = function() { + throw new Exception('Missing implementation'); + }; + + Command.prototype._send = function(data) { + var encoded; + encoded = Protocol.encodeData(data); + debug("Send '" + encoded + "'"); + this.connection.write(encoded); + return this; + }; + + Command.prototype._escape = function(arg) { + switch (typeof arg) { + case 'number': + return arg; + default: + return "'" + arg.toString().replace(RE_SQUOT, "'\"'\"'") + "'"; + } + }; + + Command.prototype._escapeCompat = function(arg) { + switch (typeof arg) { + case 'number': + return arg; + default: + return '"' + arg.toString().replace(RE_ESCAPE, '\\$1') + '"'; + } + }; + + return Command; + +})(); + +module.exports = Command; diff --git a/src/adb/command/host-serial/forward.coffee b/src/adb/command/host-serial/forward.coffee deleted file mode 100644 index 4cb1ca30..00000000 --- a/src/adb/command/host-serial/forward.coffee +++ /dev/null @@ -1,25 +0,0 @@ -Command = require '../../command' -Protocol = require '../../protocol' - -class ForwardCommand extends Command - execute: (serial, local, remote) -> - this._send "host-serial:#{serial}:forward:#{local};#{remote}" - @parser.readAscii 4 - .then (reply) => - switch reply - when Protocol.OKAY - @parser.readAscii 4 - .then (reply) => - switch reply - when Protocol.OKAY - true - when Protocol.FAIL - @parser.readError() - else - @parser.unexpected reply, 'OKAY or FAIL' - when Protocol.FAIL - @parser.readError() - else - @parser.unexpected reply, 'OKAY or FAIL' - -module.exports = ForwardCommand diff --git a/src/adb/command/host-serial/forward.js b/src/adb/command/host-serial/forward.js new file mode 100644 index 00000000..f2966cc0 --- /dev/null +++ b/src/adb/command/host-serial/forward.js @@ -0,0 +1,45 @@ +var Command, ForwardCommand, Protocol, + extend = function(child, parent) { for (var key in parent) { if (hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; }, + hasProp = {}.hasOwnProperty; + +Command = require('../../command'); + +Protocol = require('../../protocol'); + +ForwardCommand = (function(superClass) { + extend(ForwardCommand, superClass); + + function ForwardCommand() { + return ForwardCommand.__super__.constructor.apply(this, arguments); + } + + ForwardCommand.prototype.execute = function(serial, local, remote) { + this._send("host-serial:" + serial + ":forward:" + local + ";" + remote); + return this.parser.readAscii(4).then((function(_this) { + return function(reply) { + switch (reply) { + case Protocol.OKAY: + return _this.parser.readAscii(4).then(function(reply) { + switch (reply) { + case Protocol.OKAY: + return true; + case Protocol.FAIL: + return _this.parser.readError(); + default: + return _this.parser.unexpected(reply, 'OKAY or FAIL'); + } + }); + case Protocol.FAIL: + return _this.parser.readError(); + default: + return _this.parser.unexpected(reply, 'OKAY or FAIL'); + } + }; + })(this)); + }; + + return ForwardCommand; + +})(Command); + +module.exports = ForwardCommand; diff --git a/src/adb/command/host-serial/getdevicepath.coffee b/src/adb/command/host-serial/getdevicepath.coffee deleted file mode 100644 index f835a1c8..00000000 --- a/src/adb/command/host-serial/getdevicepath.coffee +++ /dev/null @@ -1,19 +0,0 @@ -Command = require '../../command' -Protocol = require '../../protocol' - -class GetDevicePathCommand extends Command - execute: (serial) -> - this._send "host-serial:#{serial}:get-devpath" - @parser.readAscii 4 - .then (reply) => - switch reply - when Protocol.OKAY - @parser.readValue() - .then (value) -> - value.toString() - when Protocol.FAIL - @parser.readError() - else - @parser.unexpected reply, 'OKAY or FAIL' - -module.exports = GetDevicePathCommand diff --git a/src/adb/command/host-serial/getdevicepath.js b/src/adb/command/host-serial/getdevicepath.js new file mode 100644 index 00000000..dacf11d2 --- /dev/null +++ b/src/adb/command/host-serial/getdevicepath.js @@ -0,0 +1,38 @@ +var Command, GetDevicePathCommand, Protocol, + extend = function(child, parent) { for (var key in parent) { if (hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; }, + hasProp = {}.hasOwnProperty; + +Command = require('../../command'); + +Protocol = require('../../protocol'); + +GetDevicePathCommand = (function(superClass) { + extend(GetDevicePathCommand, superClass); + + function GetDevicePathCommand() { + return GetDevicePathCommand.__super__.constructor.apply(this, arguments); + } + + GetDevicePathCommand.prototype.execute = function(serial) { + this._send("host-serial:" + serial + ":get-devpath"); + return this.parser.readAscii(4).then((function(_this) { + return function(reply) { + switch (reply) { + case Protocol.OKAY: + return _this.parser.readValue().then(function(value) { + return value.toString(); + }); + case Protocol.FAIL: + return _this.parser.readError(); + default: + return _this.parser.unexpected(reply, 'OKAY or FAIL'); + } + }; + })(this)); + }; + + return GetDevicePathCommand; + +})(Command); + +module.exports = GetDevicePathCommand; diff --git a/src/adb/command/host-serial/getserialno.coffee b/src/adb/command/host-serial/getserialno.coffee deleted file mode 100644 index e6e88fc5..00000000 --- a/src/adb/command/host-serial/getserialno.coffee +++ /dev/null @@ -1,19 +0,0 @@ -Command = require '../../command' -Protocol = require '../../protocol' - -class GetSerialNoCommand extends Command - execute: (serial) -> - this._send "host-serial:#{serial}:get-serialno" - @parser.readAscii 4 - .then (reply) => - switch reply - when Protocol.OKAY - @parser.readValue() - .then (value) -> - value.toString() - when Protocol.FAIL - @parser.readError() - else - @parser.unexpected reply, 'OKAY or FAIL' - -module.exports = GetSerialNoCommand diff --git a/src/adb/command/host-serial/getserialno.js b/src/adb/command/host-serial/getserialno.js new file mode 100644 index 00000000..0de01683 --- /dev/null +++ b/src/adb/command/host-serial/getserialno.js @@ -0,0 +1,38 @@ +var Command, GetSerialNoCommand, Protocol, + extend = function(child, parent) { for (var key in parent) { if (hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; }, + hasProp = {}.hasOwnProperty; + +Command = require('../../command'); + +Protocol = require('../../protocol'); + +GetSerialNoCommand = (function(superClass) { + extend(GetSerialNoCommand, superClass); + + function GetSerialNoCommand() { + return GetSerialNoCommand.__super__.constructor.apply(this, arguments); + } + + GetSerialNoCommand.prototype.execute = function(serial) { + this._send("host-serial:" + serial + ":get-serialno"); + return this.parser.readAscii(4).then((function(_this) { + return function(reply) { + switch (reply) { + case Protocol.OKAY: + return _this.parser.readValue().then(function(value) { + return value.toString(); + }); + case Protocol.FAIL: + return _this.parser.readError(); + default: + return _this.parser.unexpected(reply, 'OKAY or FAIL'); + } + }; + })(this)); + }; + + return GetSerialNoCommand; + +})(Command); + +module.exports = GetSerialNoCommand; diff --git a/src/adb/command/host-serial/getstate.coffee b/src/adb/command/host-serial/getstate.coffee deleted file mode 100644 index b378fdba..00000000 --- a/src/adb/command/host-serial/getstate.coffee +++ /dev/null @@ -1,19 +0,0 @@ -Command = require '../../command' -Protocol = require '../../protocol' - -class GetStateCommand extends Command - execute: (serial) -> - this._send "host-serial:#{serial}:get-state" - @parser.readAscii 4 - .then (reply) => - switch reply - when Protocol.OKAY - @parser.readValue() - .then (value) -> - value.toString() - when Protocol.FAIL - @parser.readError() - else - @parser.unexpected reply, 'OKAY or FAIL' - -module.exports = GetStateCommand diff --git a/src/adb/command/host-serial/getstate.js b/src/adb/command/host-serial/getstate.js new file mode 100644 index 00000000..13381709 --- /dev/null +++ b/src/adb/command/host-serial/getstate.js @@ -0,0 +1,38 @@ +var Command, GetStateCommand, Protocol, + extend = function(child, parent) { for (var key in parent) { if (hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; }, + hasProp = {}.hasOwnProperty; + +Command = require('../../command'); + +Protocol = require('../../protocol'); + +GetStateCommand = (function(superClass) { + extend(GetStateCommand, superClass); + + function GetStateCommand() { + return GetStateCommand.__super__.constructor.apply(this, arguments); + } + + GetStateCommand.prototype.execute = function(serial) { + this._send("host-serial:" + serial + ":get-state"); + return this.parser.readAscii(4).then((function(_this) { + return function(reply) { + switch (reply) { + case Protocol.OKAY: + return _this.parser.readValue().then(function(value) { + return value.toString(); + }); + case Protocol.FAIL: + return _this.parser.readError(); + default: + return _this.parser.unexpected(reply, 'OKAY or FAIL'); + } + }; + })(this)); + }; + + return GetStateCommand; + +})(Command); + +module.exports = GetStateCommand; diff --git a/src/adb/command/host-serial/listforwards.coffee b/src/adb/command/host-serial/listforwards.coffee deleted file mode 100644 index 230f328d..00000000 --- a/src/adb/command/host-serial/listforwards.coffee +++ /dev/null @@ -1,27 +0,0 @@ -Command = require '../../command' -Protocol = require '../../protocol' - -class ListForwardsCommand extends Command - execute: (serial) -> - this._send "host-serial:#{serial}:list-forward" - @parser.readAscii 4 - .then (reply) => - switch reply - when Protocol.OKAY - @parser.readValue() - .then (value) => - this._parseForwards value - when Protocol.FAIL - @parser.readError() - else - @parser.unexpected reply, 'OKAY or FAIL' - - _parseForwards: (value) -> - forwards = [] - for forward in value.toString().split '\n' - if forward - [serial, local, remote] = forward.split /\s+/ - forwards.push serial: serial, local: local, remote: remote - return forwards - -module.exports = ListForwardsCommand diff --git a/src/adb/command/host-serial/listforwards.js b/src/adb/command/host-serial/listforwards.js new file mode 100644 index 00000000..43cfa0fe --- /dev/null +++ b/src/adb/command/host-serial/listforwards.js @@ -0,0 +1,56 @@ +var Command, ListForwardsCommand, Protocol, + extend = function(child, parent) { for (var key in parent) { if (hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; }, + hasProp = {}.hasOwnProperty; + +Command = require('../../command'); + +Protocol = require('../../protocol'); + +ListForwardsCommand = (function(superClass) { + extend(ListForwardsCommand, superClass); + + function ListForwardsCommand() { + return ListForwardsCommand.__super__.constructor.apply(this, arguments); + } + + ListForwardsCommand.prototype.execute = function(serial) { + this._send("host-serial:" + serial + ":list-forward"); + return this.parser.readAscii(4).then((function(_this) { + return function(reply) { + switch (reply) { + case Protocol.OKAY: + return _this.parser.readValue().then(function(value) { + return _this._parseForwards(value); + }); + case Protocol.FAIL: + return _this.parser.readError(); + default: + return _this.parser.unexpected(reply, 'OKAY or FAIL'); + } + }; + })(this)); + }; + + ListForwardsCommand.prototype._parseForwards = function(value) { + var forward, forwards, i, len, local, ref, ref1, remote, serial; + forwards = []; + ref = value.toString().split('\n'); + for (i = 0, len = ref.length; i < len; i++) { + forward = ref[i]; + if (forward) { + ref1 = forward.split(/\s+/), serial = ref1[0], local = ref1[1], remote = ref1[2]; + forwards.push({ + serial: serial, + local: local, + remote: remote + }); + } + } + return forwards; + }; + + return ListForwardsCommand; + +})(Command); + +module.exports = ListForwardsCommand; diff --git a/src/adb/command/host-serial/waitfordevice.coffee b/src/adb/command/host-serial/waitfordevice.coffee deleted file mode 100644 index 6041e8ce..00000000 --- a/src/adb/command/host-serial/waitfordevice.coffee +++ /dev/null @@ -1,25 +0,0 @@ -Command = require '../../command' -Protocol = require '../../protocol' - -class WaitForDeviceCommand extends Command - execute: (serial) -> - this._send "host-serial:#{serial}:wait-for-any" - @parser.readAscii 4 - .then (reply) => - switch reply - when Protocol.OKAY - @parser.readAscii 4 - .then (reply) => - switch reply - when Protocol.OKAY - serial - when Protocol.FAIL - @parser.readError() - else - @parser.unexpected reply, 'OKAY or FAIL' - when Protocol.FAIL - @parser.readError() - else - @parser.unexpected reply, 'OKAY or FAIL' - -module.exports = WaitForDeviceCommand diff --git a/src/adb/command/host-serial/waitfordevice.js b/src/adb/command/host-serial/waitfordevice.js new file mode 100644 index 00000000..05f16326 --- /dev/null +++ b/src/adb/command/host-serial/waitfordevice.js @@ -0,0 +1,45 @@ +var Command, Protocol, WaitForDeviceCommand, + extend = function(child, parent) { for (var key in parent) { if (hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; }, + hasProp = {}.hasOwnProperty; + +Command = require('../../command'); + +Protocol = require('../../protocol'); + +WaitForDeviceCommand = (function(superClass) { + extend(WaitForDeviceCommand, superClass); + + function WaitForDeviceCommand() { + return WaitForDeviceCommand.__super__.constructor.apply(this, arguments); + } + + WaitForDeviceCommand.prototype.execute = function(serial) { + this._send("host-serial:" + serial + ":wait-for-any"); + return this.parser.readAscii(4).then((function(_this) { + return function(reply) { + switch (reply) { + case Protocol.OKAY: + return _this.parser.readAscii(4).then(function(reply) { + switch (reply) { + case Protocol.OKAY: + return serial; + case Protocol.FAIL: + return _this.parser.readError(); + default: + return _this.parser.unexpected(reply, 'OKAY or FAIL'); + } + }); + case Protocol.FAIL: + return _this.parser.readError(); + default: + return _this.parser.unexpected(reply, 'OKAY or FAIL'); + } + }; + })(this)); + }; + + return WaitForDeviceCommand; + +})(Command); + +module.exports = WaitForDeviceCommand; diff --git a/src/adb/command/host-transport/clear.coffee b/src/adb/command/host-transport/clear.coffee deleted file mode 100644 index 12a8bee6..00000000 --- a/src/adb/command/host-transport/clear.coffee +++ /dev/null @@ -1,27 +0,0 @@ -Command = require '../../command' -Protocol = require '../../protocol' - -class ClearCommand extends Command - execute: (pkg) -> - this._send "shell:pm clear #{pkg}" - @parser.readAscii 4 - .then (reply) => - switch reply - when Protocol.OKAY - @parser.searchLine /^(Success|Failed)$/ - .finally => - @parser.end() - .then (result) -> - switch result[0] - when 'Success' - true - when 'Failed' - # Unfortunately, the command may stall at this point and we - # have to kill the connection. - throw new Error "Package '#{pkg}' could not be cleared" - when Protocol.FAIL - @parser.readError() - else - @parser.unexpected reply, 'OKAY or FAIL' - -module.exports = ClearCommand diff --git a/src/adb/command/host-transport/clear.js b/src/adb/command/host-transport/clear.js new file mode 100644 index 00000000..62a209d2 --- /dev/null +++ b/src/adb/command/host-transport/clear.js @@ -0,0 +1,45 @@ +var ClearCommand, Command, Protocol, + extend = function(child, parent) { for (var key in parent) { if (hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; }, + hasProp = {}.hasOwnProperty; + +Command = require('../../command'); + +Protocol = require('../../protocol'); + +ClearCommand = (function(superClass) { + extend(ClearCommand, superClass); + + function ClearCommand() { + return ClearCommand.__super__.constructor.apply(this, arguments); + } + + ClearCommand.prototype.execute = function(pkg) { + this._send("shell:pm clear " + pkg); + return this.parser.readAscii(4).then((function(_this) { + return function(reply) { + switch (reply) { + case Protocol.OKAY: + return _this.parser.searchLine(/^(Success|Failed)$/)["finally"](function() { + return _this.parser.end(); + }).then(function(result) { + switch (result[0]) { + case 'Success': + return true; + case 'Failed': + throw new Error("Package '" + pkg + "' could not be cleared"); + } + }); + case Protocol.FAIL: + return _this.parser.readError(); + default: + return _this.parser.unexpected(reply, 'OKAY or FAIL'); + } + }; + })(this)); + }; + + return ClearCommand; + +})(Command); + +module.exports = ClearCommand; diff --git a/src/adb/command/host-transport/framebuffer.coffee b/src/adb/command/host-transport/framebuffer.coffee deleted file mode 100644 index e691e1f8..00000000 --- a/src/adb/command/host-transport/framebuffer.coffee +++ /dev/null @@ -1,87 +0,0 @@ -Assert = require 'assert' -{spawn} = require 'child_process' -debug = require('debug')('adb:command:framebuffer') - -Command = require '../../command' -Protocol = require '../../protocol' -RgbTransform = require '../../framebuffer/rgbtransform' - -class FrameBufferCommand extends Command - execute: (format) -> - this._send 'framebuffer:' - @parser.readAscii 4 - .then (reply) => - switch reply - when Protocol.OKAY - @parser.readBytes 52 - .then (header) => - meta = this._parseHeader header - switch format - when 'raw' - stream = @parser.raw() - stream.meta = meta - stream - else - stream = this._convert meta, format - stream.meta = meta - stream - when Protocol.FAIL - @parser.readError() - else - @parser.unexpected reply, 'OKAY or FAIL' - - _convert: (meta, format, raw) -> - debug "Converting raw framebuffer stream into #{format.toUpperCase()}" - switch meta.format - when 'rgb', 'rgba' - # Known to be supported by GraphicsMagick - else - debug "Silently transforming '#{meta.format}' into 'rgb' for `gm`" - transform = new RgbTransform meta - meta.format = 'rgb' - raw = @parser.raw().pipe transform - proc = spawn 'gm', [ - 'convert' - '-size' - "#{meta.width}x#{meta.height}" - "#{meta.format}:-" - "#{format}:-" - ] - raw.pipe proc.stdin - return proc.stdout - - _parseHeader: (header) -> - meta = {} - offset = 0 - meta.version = header.readUInt32LE offset - if meta.version is 16 - throw new Error 'Old-style raw images are not supported' - offset += 4 - meta.bpp = header.readUInt32LE offset - offset += 4 - meta.size = header.readUInt32LE offset - offset += 4 - meta.width = header.readUInt32LE offset - offset += 4 - meta.height = header.readUInt32LE offset - offset += 4 - meta.red_offset = header.readUInt32LE offset - offset += 4 - meta.red_length = header.readUInt32LE offset - offset += 4 - meta.blue_offset = header.readUInt32LE offset - offset += 4 - meta.blue_length = header.readUInt32LE offset - offset += 4 - meta.green_offset = header.readUInt32LE offset - offset += 4 - meta.green_length = header.readUInt32LE offset - offset += 4 - meta.alpha_offset = header.readUInt32LE offset - offset += 4 - meta.alpha_length = header.readUInt32LE offset - meta.format = if meta.blue_offset is 0 then 'bgr' else 'rgb' - meta.format += 'a' if meta.bpp is 32 or meta.alpha_length - return meta - -module.exports = FrameBufferCommand diff --git a/src/adb/command/host-transport/framebuffer.js b/src/adb/command/host-transport/framebuffer.js new file mode 100644 index 00000000..a9668a45 --- /dev/null +++ b/src/adb/command/host-transport/framebuffer.js @@ -0,0 +1,114 @@ +var Assert, Command, FrameBufferCommand, Protocol, RgbTransform, debug, spawn, + extend = function(child, parent) { for (var key in parent) { if (hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; }, + hasProp = {}.hasOwnProperty; + +Assert = require('assert'); + +spawn = require('child_process').spawn; + +debug = require('debug')('adb:command:framebuffer'); + +Command = require('../../command'); + +Protocol = require('../../protocol'); + +RgbTransform = require('../../framebuffer/rgbtransform'); + +FrameBufferCommand = (function(superClass) { + extend(FrameBufferCommand, superClass); + + function FrameBufferCommand() { + return FrameBufferCommand.__super__.constructor.apply(this, arguments); + } + + FrameBufferCommand.prototype.execute = function(format) { + this._send('framebuffer:'); + return this.parser.readAscii(4).then((function(_this) { + return function(reply) { + switch (reply) { + case Protocol.OKAY: + return _this.parser.readBytes(52).then(function(header) { + var meta, stream; + meta = _this._parseHeader(header); + switch (format) { + case 'raw': + stream = _this.parser.raw(); + stream.meta = meta; + return stream; + default: + stream = _this._convert(meta, format); + stream.meta = meta; + return stream; + } + }); + case Protocol.FAIL: + return _this.parser.readError(); + default: + return _this.parser.unexpected(reply, 'OKAY or FAIL'); + } + }; + })(this)); + }; + + FrameBufferCommand.prototype._convert = function(meta, format, raw) { + var proc, transform; + debug("Converting raw framebuffer stream into " + (format.toUpperCase())); + switch (meta.format) { + case 'rgb': + case 'rgba': + break; + default: + debug("Silently transforming '" + meta.format + "' into 'rgb' for `gm`"); + transform = new RgbTransform(meta); + meta.format = 'rgb'; + raw = this.parser.raw().pipe(transform); + } + proc = spawn('gm', ['convert', '-size', meta.width + "x" + meta.height, meta.format + ":-", format + ":-"]); + raw.pipe(proc.stdin); + return proc.stdout; + }; + + FrameBufferCommand.prototype._parseHeader = function(header) { + var meta, offset; + meta = {}; + offset = 0; + meta.version = header.readUInt32LE(offset); + if (meta.version === 16) { + throw new Error('Old-style raw images are not supported'); + } + offset += 4; + meta.bpp = header.readUInt32LE(offset); + offset += 4; + meta.size = header.readUInt32LE(offset); + offset += 4; + meta.width = header.readUInt32LE(offset); + offset += 4; + meta.height = header.readUInt32LE(offset); + offset += 4; + meta.red_offset = header.readUInt32LE(offset); + offset += 4; + meta.red_length = header.readUInt32LE(offset); + offset += 4; + meta.blue_offset = header.readUInt32LE(offset); + offset += 4; + meta.blue_length = header.readUInt32LE(offset); + offset += 4; + meta.green_offset = header.readUInt32LE(offset); + offset += 4; + meta.green_length = header.readUInt32LE(offset); + offset += 4; + meta.alpha_offset = header.readUInt32LE(offset); + offset += 4; + meta.alpha_length = header.readUInt32LE(offset); + meta.format = meta.blue_offset === 0 ? 'bgr' : 'rgb'; + if (meta.bpp === 32 || meta.alpha_length) { + meta.format += 'a'; + } + return meta; + }; + + return FrameBufferCommand; + +})(Command); + +module.exports = FrameBufferCommand; diff --git a/src/adb/command/host-transport/getfeatures.coffee b/src/adb/command/host-transport/getfeatures.coffee deleted file mode 100644 index 8b7eb048..00000000 --- a/src/adb/command/host-transport/getfeatures.coffee +++ /dev/null @@ -1,27 +0,0 @@ -Command = require '../../command' -Protocol = require '../../protocol' - -class GetFeaturesCommand extends Command - RE_FEATURE = /^feature:(.*?)(?:=(.*?))?\r?$/gm - - execute: -> - this._send 'shell:pm list features 2>/dev/null' - @parser.readAscii 4 - .then (reply) => - switch reply - when Protocol.OKAY - @parser.readAll() - .then (data) => - this._parseFeatures data.toString() - when Protocol.FAIL - @parser.readError() - else - @parser.unexpected reply, 'OKAY or FAIL' - - _parseFeatures: (value) -> - features = {} - while match = RE_FEATURE.exec value - features[match[1]] = match[2] or true - return features - -module.exports = GetFeaturesCommand diff --git a/src/adb/command/host-transport/getfeatures.js b/src/adb/command/host-transport/getfeatures.js new file mode 100644 index 00000000..ca04c66d --- /dev/null +++ b/src/adb/command/host-transport/getfeatures.js @@ -0,0 +1,51 @@ +var Command, GetFeaturesCommand, Protocol, + extend = function(child, parent) { for (var key in parent) { if (hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; }, + hasProp = {}.hasOwnProperty; + +Command = require('../../command'); + +Protocol = require('../../protocol'); + +GetFeaturesCommand = (function(superClass) { + var RE_FEATURE; + + extend(GetFeaturesCommand, superClass); + + function GetFeaturesCommand() { + return GetFeaturesCommand.__super__.constructor.apply(this, arguments); + } + + RE_FEATURE = /^feature:(.*?)(?:=(.*?))?\r?$/gm; + + GetFeaturesCommand.prototype.execute = function() { + this._send('shell:pm list features 2>/dev/null'); + return this.parser.readAscii(4).then((function(_this) { + return function(reply) { + switch (reply) { + case Protocol.OKAY: + return _this.parser.readAll().then(function(data) { + return _this._parseFeatures(data.toString()); + }); + case Protocol.FAIL: + return _this.parser.readError(); + default: + return _this.parser.unexpected(reply, 'OKAY or FAIL'); + } + }; + })(this)); + }; + + GetFeaturesCommand.prototype._parseFeatures = function(value) { + var features, match; + features = {}; + while (match = RE_FEATURE.exec(value)) { + features[match[1]] = match[2] || true; + } + return features; + }; + + return GetFeaturesCommand; + +})(Command); + +module.exports = GetFeaturesCommand; diff --git a/src/adb/command/host-transport/getpackages.coffee b/src/adb/command/host-transport/getpackages.coffee deleted file mode 100644 index e0482ebd..00000000 --- a/src/adb/command/host-transport/getpackages.coffee +++ /dev/null @@ -1,27 +0,0 @@ -Command = require '../../command' -Protocol = require '../../protocol' - -class GetPackagesCommand extends Command - RE_PACKAGE = /^package:(.*?)\r?$/gm - - execute: -> - this._send 'shell:pm list packages 2>/dev/null' - @parser.readAscii 4 - .then (reply) => - switch reply - when Protocol.OKAY - @parser.readAll() - .then (data) => - this._parsePackages data.toString() - when Protocol.FAIL - @parser.readError() - else - @parser.unexpected reply, 'OKAY or FAIL' - - _parsePackages: (value) -> - features = [] - while match = RE_PACKAGE.exec value - features.push match[1] - return features - -module.exports = GetPackagesCommand diff --git a/src/adb/command/host-transport/getpackages.js b/src/adb/command/host-transport/getpackages.js new file mode 100644 index 00000000..56d9a63a --- /dev/null +++ b/src/adb/command/host-transport/getpackages.js @@ -0,0 +1,51 @@ +var Command, GetPackagesCommand, Protocol, + extend = function(child, parent) { for (var key in parent) { if (hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; }, + hasProp = {}.hasOwnProperty; + +Command = require('../../command'); + +Protocol = require('../../protocol'); + +GetPackagesCommand = (function(superClass) { + var RE_PACKAGE; + + extend(GetPackagesCommand, superClass); + + function GetPackagesCommand() { + return GetPackagesCommand.__super__.constructor.apply(this, arguments); + } + + RE_PACKAGE = /^package:(.*?)\r?$/gm; + + GetPackagesCommand.prototype.execute = function() { + this._send('shell:pm list packages 2>/dev/null'); + return this.parser.readAscii(4).then((function(_this) { + return function(reply) { + switch (reply) { + case Protocol.OKAY: + return _this.parser.readAll().then(function(data) { + return _this._parsePackages(data.toString()); + }); + case Protocol.FAIL: + return _this.parser.readError(); + default: + return _this.parser.unexpected(reply, 'OKAY or FAIL'); + } + }; + })(this)); + }; + + GetPackagesCommand.prototype._parsePackages = function(value) { + var features, match; + features = []; + while (match = RE_PACKAGE.exec(value)) { + features.push(match[1]); + } + return features; + }; + + return GetPackagesCommand; + +})(Command); + +module.exports = GetPackagesCommand; diff --git a/src/adb/command/host-transport/getproperties.coffee b/src/adb/command/host-transport/getproperties.coffee deleted file mode 100644 index d84e03e7..00000000 --- a/src/adb/command/host-transport/getproperties.coffee +++ /dev/null @@ -1,27 +0,0 @@ -Command = require '../../command' -Protocol = require '../../protocol' - -class GetPropertiesCommand extends Command - RE_KEYVAL = /^\[([\s\S]*?)\]: \[([\s\S]*?)\]\r?$/gm - - execute: -> - this._send 'shell:getprop' - @parser.readAscii 4 - .then (reply) => - switch reply - when Protocol.OKAY - @parser.readAll() - .then (data) => - this._parseProperties data.toString() - when Protocol.FAIL - @parser.readError() - else - @parser.unexpected reply, 'OKAY or FAIL' - - _parseProperties: (value) -> - properties = {} - while match = RE_KEYVAL.exec value - properties[match[1]] = match[2] - return properties - -module.exports = GetPropertiesCommand diff --git a/src/adb/command/host-transport/getproperties.js b/src/adb/command/host-transport/getproperties.js new file mode 100644 index 00000000..1fe9a863 --- /dev/null +++ b/src/adb/command/host-transport/getproperties.js @@ -0,0 +1,51 @@ +var Command, GetPropertiesCommand, Protocol, + extend = function(child, parent) { for (var key in parent) { if (hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; }, + hasProp = {}.hasOwnProperty; + +Command = require('../../command'); + +Protocol = require('../../protocol'); + +GetPropertiesCommand = (function(superClass) { + var RE_KEYVAL; + + extend(GetPropertiesCommand, superClass); + + function GetPropertiesCommand() { + return GetPropertiesCommand.__super__.constructor.apply(this, arguments); + } + + RE_KEYVAL = /^\[([\s\S]*?)\]: \[([\s\S]*?)\]\r?$/gm; + + GetPropertiesCommand.prototype.execute = function() { + this._send('shell:getprop'); + return this.parser.readAscii(4).then((function(_this) { + return function(reply) { + switch (reply) { + case Protocol.OKAY: + return _this.parser.readAll().then(function(data) { + return _this._parseProperties(data.toString()); + }); + case Protocol.FAIL: + return _this.parser.readError(); + default: + return _this.parser.unexpected(reply, 'OKAY or FAIL'); + } + }; + })(this)); + }; + + GetPropertiesCommand.prototype._parseProperties = function(value) { + var match, properties; + properties = {}; + while (match = RE_KEYVAL.exec(value)) { + properties[match[1]] = match[2]; + } + return properties; + }; + + return GetPropertiesCommand; + +})(Command); + +module.exports = GetPropertiesCommand; diff --git a/src/adb/command/host-transport/install.coffee b/src/adb/command/host-transport/install.coffee deleted file mode 100644 index 292790ed..00000000 --- a/src/adb/command/host-transport/install.coffee +++ /dev/null @@ -1,29 +0,0 @@ -Command = require '../../command' -Protocol = require '../../protocol' - -class InstallCommand extends Command - execute: (apk) -> - this._send "shell:pm install -r #{this._escapeCompat(apk)}" - @parser.readAscii 4 - .then (reply) => - switch reply - when Protocol.OKAY - @parser.searchLine /^(Success|Failure \[(.*?)\])$/ - .then (match) -> - if match[1] is 'Success' - true - else - code = match[2] - err = new Error "#{apk} could not be installed [#{code}]" - err.code = code - throw err - .finally => - # Consume all remaining content to "naturally" close the - # connection. - @parser.readAll() - when Protocol.FAIL - @parser.readError() - else - @parser.unexpected reply, 'OKAY or FAIL' - -module.exports = InstallCommand diff --git a/src/adb/command/host-transport/install.js b/src/adb/command/host-transport/install.js new file mode 100644 index 00000000..3f254f93 --- /dev/null +++ b/src/adb/command/host-transport/install.js @@ -0,0 +1,48 @@ +var Command, InstallCommand, Protocol, + extend = function(child, parent) { for (var key in parent) { if (hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; }, + hasProp = {}.hasOwnProperty; + +Command = require('../../command'); + +Protocol = require('../../protocol'); + +InstallCommand = (function(superClass) { + extend(InstallCommand, superClass); + + function InstallCommand() { + return InstallCommand.__super__.constructor.apply(this, arguments); + } + + InstallCommand.prototype.execute = function(apk) { + this._send("shell:pm install -r " + (this._escapeCompat(apk))); + return this.parser.readAscii(4).then((function(_this) { + return function(reply) { + switch (reply) { + case Protocol.OKAY: + return _this.parser.searchLine(/^(Success|Failure \[(.*?)\])$/).then(function(match) { + var code, err; + if (match[1] === 'Success') { + return true; + } else { + code = match[2]; + err = new Error(apk + " could not be installed [" + code + "]"); + err.code = code; + throw err; + } + })["finally"](function() { + return _this.parser.readAll(); + }); + case Protocol.FAIL: + return _this.parser.readError(); + default: + return _this.parser.unexpected(reply, 'OKAY or FAIL'); + } + }; + })(this)); + }; + + return InstallCommand; + +})(Command); + +module.exports = InstallCommand; diff --git a/src/adb/command/host-transport/isinstalled.coffee b/src/adb/command/host-transport/isinstalled.coffee deleted file mode 100644 index c1a432a9..00000000 --- a/src/adb/command/host-transport/isinstalled.coffee +++ /dev/null @@ -1,26 +0,0 @@ -Command = require '../../command' -Protocol = require '../../protocol' -Parser = require '../../parser' - -class IsInstalledCommand extends Command - execute: (pkg) -> - this._send "shell:pm path #{pkg} 2>/dev/null" - @parser.readAscii 4 - .then (reply) => - switch reply - when Protocol.OKAY - @parser.readAscii 8 - .then (reply) => - switch reply - when 'package:' - true - else - @parser.unexpected reply, "'package:'" - .catch Parser.PrematureEOFError, (err) -> - false - when Protocol.FAIL - @parser.readError() - else - @parser.unexpected reply, 'OKAY or FAIL' - -module.exports = IsInstalledCommand diff --git a/src/adb/command/host-transport/isinstalled.js b/src/adb/command/host-transport/isinstalled.js new file mode 100644 index 00000000..30cc62be --- /dev/null +++ b/src/adb/command/host-transport/isinstalled.js @@ -0,0 +1,47 @@ +var Command, IsInstalledCommand, Parser, Protocol, + extend = function(child, parent) { for (var key in parent) { if (hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; }, + hasProp = {}.hasOwnProperty; + +Command = require('../../command'); + +Protocol = require('../../protocol'); + +Parser = require('../../parser'); + +IsInstalledCommand = (function(superClass) { + extend(IsInstalledCommand, superClass); + + function IsInstalledCommand() { + return IsInstalledCommand.__super__.constructor.apply(this, arguments); + } + + IsInstalledCommand.prototype.execute = function(pkg) { + this._send("shell:pm path " + pkg + " 2>/dev/null"); + return this.parser.readAscii(4).then((function(_this) { + return function(reply) { + switch (reply) { + case Protocol.OKAY: + return _this.parser.readAscii(8).then(function(reply) { + switch (reply) { + case 'package:': + return true; + default: + return _this.parser.unexpected(reply, "'package:'"); + } + })["catch"](Parser.PrematureEOFError, function(err) { + return false; + }); + case Protocol.FAIL: + return _this.parser.readError(); + default: + return _this.parser.unexpected(reply, 'OKAY or FAIL'); + } + }; + })(this)); + }; + + return IsInstalledCommand; + +})(Command); + +module.exports = IsInstalledCommand; diff --git a/src/adb/command/host-transport/listreverses.coffee b/src/adb/command/host-transport/listreverses.coffee deleted file mode 100644 index ceca375e..00000000 --- a/src/adb/command/host-transport/listreverses.coffee +++ /dev/null @@ -1,27 +0,0 @@ -Command = require '../../command' -Protocol = require '../../protocol' - -class ListReversesCommand extends Command - execute: -> - this._send "reverse:list-forward" - @parser.readAscii 4 - .then (reply) => - switch reply - when Protocol.OKAY - @parser.readValue() - .then (value) => - this._parseReverses value - when Protocol.FAIL - @parser.readError() - else - @parser.unexpected reply, 'OKAY or FAIL' - - _parseReverses: (value) -> - reverses = [] - for reverse in value.toString().split '\n' - if reverse - [serial, remote, local] = reverse.split /\s+/ - reverses.push remote: remote, local: local - return reverses - -module.exports = ListReversesCommand diff --git a/src/adb/command/host-transport/listreverses.js b/src/adb/command/host-transport/listreverses.js new file mode 100644 index 00000000..d9b9261b --- /dev/null +++ b/src/adb/command/host-transport/listreverses.js @@ -0,0 +1,55 @@ +var Command, ListReversesCommand, Protocol, + extend = function(child, parent) { for (var key in parent) { if (hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; }, + hasProp = {}.hasOwnProperty; + +Command = require('../../command'); + +Protocol = require('../../protocol'); + +ListReversesCommand = (function(superClass) { + extend(ListReversesCommand, superClass); + + function ListReversesCommand() { + return ListReversesCommand.__super__.constructor.apply(this, arguments); + } + + ListReversesCommand.prototype.execute = function() { + this._send("reverse:list-forward"); + return this.parser.readAscii(4).then((function(_this) { + return function(reply) { + switch (reply) { + case Protocol.OKAY: + return _this.parser.readValue().then(function(value) { + return _this._parseReverses(value); + }); + case Protocol.FAIL: + return _this.parser.readError(); + default: + return _this.parser.unexpected(reply, 'OKAY or FAIL'); + } + }; + })(this)); + }; + + ListReversesCommand.prototype._parseReverses = function(value) { + var i, len, local, ref, ref1, remote, reverse, reverses, serial; + reverses = []; + ref = value.toString().split('\n'); + for (i = 0, len = ref.length; i < len; i++) { + reverse = ref[i]; + if (reverse) { + ref1 = reverse.split(/\s+/), serial = ref1[0], remote = ref1[1], local = ref1[2]; + reverses.push({ + remote: remote, + local: local + }); + } + } + return reverses; + }; + + return ListReversesCommand; + +})(Command); + +module.exports = ListReversesCommand; diff --git a/src/adb/command/host-transport/local.coffee b/src/adb/command/host-transport/local.coffee deleted file mode 100644 index e3c3bfbf..00000000 --- a/src/adb/command/host-transport/local.coffee +++ /dev/null @@ -1,17 +0,0 @@ -Command = require '../../command' -Protocol = require '../../protocol' - -class LocalCommand extends Command - execute: (path) -> - this._send if /:/.test(path) then path else "localfilesystem:#{path}" - @parser.readAscii 4 - .then (reply) => - switch reply - when Protocol.OKAY - @parser.raw() - when Protocol.FAIL - @parser.readError() - else - @parser.unexpected reply, 'OKAY or FAIL' - -module.exports = LocalCommand diff --git a/src/adb/command/host-transport/local.js b/src/adb/command/host-transport/local.js new file mode 100644 index 00000000..d24371ad --- /dev/null +++ b/src/adb/command/host-transport/local.js @@ -0,0 +1,36 @@ +var Command, LocalCommand, Protocol, + extend = function(child, parent) { for (var key in parent) { if (hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; }, + hasProp = {}.hasOwnProperty; + +Command = require('../../command'); + +Protocol = require('../../protocol'); + +LocalCommand = (function(superClass) { + extend(LocalCommand, superClass); + + function LocalCommand() { + return LocalCommand.__super__.constructor.apply(this, arguments); + } + + LocalCommand.prototype.execute = function(path) { + this._send(/:/.test(path) ? path : "localfilesystem:" + path); + return this.parser.readAscii(4).then((function(_this) { + return function(reply) { + switch (reply) { + case Protocol.OKAY: + return _this.parser.raw(); + case Protocol.FAIL: + return _this.parser.readError(); + default: + return _this.parser.unexpected(reply, 'OKAY or FAIL'); + } + }; + })(this)); + }; + + return LocalCommand; + +})(Command); + +module.exports = LocalCommand; diff --git a/src/adb/command/host-transport/log.coffee b/src/adb/command/host-transport/log.coffee deleted file mode 100644 index e1a7aff2..00000000 --- a/src/adb/command/host-transport/log.coffee +++ /dev/null @@ -1,17 +0,0 @@ -Command = require '../../command' -Protocol = require '../../protocol' - -class LogCommand extends Command - execute: (name) -> - this._send "log:#{name}" - @parser.readAscii 4 - .then (reply) => - switch reply - when Protocol.OKAY - @parser.raw() - when Protocol.FAIL - @parser.readError() - else - @parser.unexpected reply, 'OKAY or FAIL' - -module.exports = LogCommand diff --git a/src/adb/command/host-transport/log.js b/src/adb/command/host-transport/log.js new file mode 100644 index 00000000..20d2efdc --- /dev/null +++ b/src/adb/command/host-transport/log.js @@ -0,0 +1,36 @@ +var Command, LogCommand, Protocol, + extend = function(child, parent) { for (var key in parent) { if (hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; }, + hasProp = {}.hasOwnProperty; + +Command = require('../../command'); + +Protocol = require('../../protocol'); + +LogCommand = (function(superClass) { + extend(LogCommand, superClass); + + function LogCommand() { + return LogCommand.__super__.constructor.apply(this, arguments); + } + + LogCommand.prototype.execute = function(name) { + this._send("log:" + name); + return this.parser.readAscii(4).then((function(_this) { + return function(reply) { + switch (reply) { + case Protocol.OKAY: + return _this.parser.raw(); + case Protocol.FAIL: + return _this.parser.readError(); + default: + return _this.parser.unexpected(reply, 'OKAY or FAIL'); + } + }; + })(this)); + }; + + return LogCommand; + +})(Command); + +module.exports = LogCommand; diff --git a/src/adb/command/host-transport/logcat.coffee b/src/adb/command/host-transport/logcat.coffee deleted file mode 100644 index 3127dec9..00000000 --- a/src/adb/command/host-transport/logcat.coffee +++ /dev/null @@ -1,23 +0,0 @@ -Command = require '../../command' -Protocol = require '../../protocol' -LineTransform = require '../../linetransform' - -class LogcatCommand extends Command - execute: (options = {}) -> - # For some reason, LG G Flex requires a filter spec with the -B option. - # It doesn't actually use it, though. Regardless of the spec we always get - # all events on all devices. - cmd = 'logcat -B *:I 2>/dev/null' - cmd = "logcat -c 2>/dev/null && #{cmd}" if options.clear - this._send "shell:echo && #{cmd}" - @parser.readAscii 4 - .then (reply) => - switch reply - when Protocol.OKAY - @parser.raw().pipe new LineTransform autoDetect: true - when Protocol.FAIL - @parser.readError() - else - @parser.unexpected reply, 'OKAY or FAIL' - -module.exports = LogcatCommand diff --git a/src/adb/command/host-transport/logcat.js b/src/adb/command/host-transport/logcat.js new file mode 100644 index 00000000..db2e817f --- /dev/null +++ b/src/adb/command/host-transport/logcat.js @@ -0,0 +1,48 @@ +var Command, LineTransform, LogcatCommand, Protocol, + extend = function(child, parent) { for (var key in parent) { if (hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; }, + hasProp = {}.hasOwnProperty; + +Command = require('../../command'); + +Protocol = require('../../protocol'); + +LineTransform = require('../../linetransform'); + +LogcatCommand = (function(superClass) { + extend(LogcatCommand, superClass); + + function LogcatCommand() { + return LogcatCommand.__super__.constructor.apply(this, arguments); + } + + LogcatCommand.prototype.execute = function(options) { + var cmd; + if (options == null) { + options = {}; + } + cmd = 'logcat -B *:I 2>/dev/null'; + if (options.clear) { + cmd = "logcat -c 2>/dev/null && " + cmd; + } + this._send("shell:echo && " + cmd); + return this.parser.readAscii(4).then((function(_this) { + return function(reply) { + switch (reply) { + case Protocol.OKAY: + return _this.parser.raw().pipe(new LineTransform({ + autoDetect: true + })); + case Protocol.FAIL: + return _this.parser.readError(); + default: + return _this.parser.unexpected(reply, 'OKAY or FAIL'); + } + }; + })(this)); + }; + + return LogcatCommand; + +})(Command); + +module.exports = LogcatCommand; diff --git a/src/adb/command/host-transport/monkey.coffee b/src/adb/command/host-transport/monkey.coffee deleted file mode 100644 index ea1e9fd2..00000000 --- a/src/adb/command/host-transport/monkey.coffee +++ /dev/null @@ -1,42 +0,0 @@ -Promise = require 'bluebird' - -Command = require '../../command' -Protocol = require '../../protocol' - -class MonkeyCommand extends Command - execute: (port) -> - # Some devices have broken /sdcard (i.e. /mnt/sdcard), which monkey will - # attempt to use to write log files to. We can cheat and set the location - # with an environment variable, because most logs use - # Environment.getLegacyExternalStorageDirectory() like they should. There - # are some hardcoded logs, though. Anyway, this should enable most things. - # Check https://github.com/android/platform_frameworks_base/blob/master/ - # core/java/android/os/Environment.java for the variables. - this._send "shell:EXTERNAL_STORAGE=/data/local/tmp monkey --port #{port} -v" - - @parser.readAscii 4 - .then (reply) => - switch reply - when Protocol.OKAY - # The monkey command is a bit weird in that it doesn't look like - # it starts in daemon mode, but it actually does. So even though - # the command leaves the terminal "hanging", Ctrl-C (or just - # ending the connection) will not end the daemon. HOWEVER, on - # some devices, such as SO-02C by Sony, it is required to leave - # the command hanging around. In any case, if the command exits - # by itself, it means that something went wrong. - @parser.searchLine /^:Monkey:/ - # On some devices (such as F-08D by Fujitsu), the monkey - # command gives no output no matter how many verbose flags you - # give it. So we use a fallback timeout. - .timeout 1000 - .then => - @parser.raw() - .catch Promise.TimeoutError, (err) => - @parser.raw() - when Protocol.FAIL - @parser.readError() - else - @parser.unexpected reply, 'OKAY or FAIL' - -module.exports = MonkeyCommand diff --git a/src/adb/command/host-transport/monkey.js b/src/adb/command/host-transport/monkey.js new file mode 100644 index 00000000..63b9cce7 --- /dev/null +++ b/src/adb/command/host-transport/monkey.js @@ -0,0 +1,42 @@ +var Command, MonkeyCommand, Promise, Protocol, + extend = function(child, parent) { for (var key in parent) { if (hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; }, + hasProp = {}.hasOwnProperty; + +Promise = require('bluebird'); + +Command = require('../../command'); + +Protocol = require('../../protocol'); + +MonkeyCommand = (function(superClass) { + extend(MonkeyCommand, superClass); + + function MonkeyCommand() { + return MonkeyCommand.__super__.constructor.apply(this, arguments); + } + + MonkeyCommand.prototype.execute = function(port) { + this._send("shell:EXTERNAL_STORAGE=/data/local/tmp monkey --port " + port + " -v"); + return this.parser.readAscii(4).then((function(_this) { + return function(reply) { + switch (reply) { + case Protocol.OKAY: + return _this.parser.searchLine(/^:Monkey:/).timeout(1000).then(function() { + return _this.parser.raw(); + })["catch"](Promise.TimeoutError, function(err) { + return _this.parser.raw(); + }); + case Protocol.FAIL: + return _this.parser.readError(); + default: + return _this.parser.unexpected(reply, 'OKAY or FAIL'); + } + }; + })(this)); + }; + + return MonkeyCommand; + +})(Command); + +module.exports = MonkeyCommand; diff --git a/src/adb/command/host-transport/reboot.coffee b/src/adb/command/host-transport/reboot.coffee deleted file mode 100644 index a0c50bb5..00000000 --- a/src/adb/command/host-transport/reboot.coffee +++ /dev/null @@ -1,18 +0,0 @@ -Command = require '../../command' -Protocol = require '../../protocol' - -class RebootCommand extends Command - execute: -> - this._send 'reboot:' - @parser.readAscii 4 - .then (reply) => - switch reply - when Protocol.OKAY - @parser.readAll() - .return true - when Protocol.FAIL - @parser.readError() - else - @parser.unexpected reply, 'OKAY or FAIL' - -module.exports = RebootCommand diff --git a/src/adb/command/host-transport/reboot.js b/src/adb/command/host-transport/reboot.js new file mode 100644 index 00000000..6e6aee25 --- /dev/null +++ b/src/adb/command/host-transport/reboot.js @@ -0,0 +1,36 @@ +var Command, Protocol, RebootCommand, + extend = function(child, parent) { for (var key in parent) { if (hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; }, + hasProp = {}.hasOwnProperty; + +Command = require('../../command'); + +Protocol = require('../../protocol'); + +RebootCommand = (function(superClass) { + extend(RebootCommand, superClass); + + function RebootCommand() { + return RebootCommand.__super__.constructor.apply(this, arguments); + } + + RebootCommand.prototype.execute = function() { + this._send('reboot:'); + return this.parser.readAscii(4).then((function(_this) { + return function(reply) { + switch (reply) { + case Protocol.OKAY: + return _this.parser.readAll()["return"](true); + case Protocol.FAIL: + return _this.parser.readError(); + default: + return _this.parser.unexpected(reply, 'OKAY or FAIL'); + } + }; + })(this)); + }; + + return RebootCommand; + +})(Command); + +module.exports = RebootCommand; diff --git a/src/adb/command/host-transport/remount.coffee b/src/adb/command/host-transport/remount.coffee deleted file mode 100644 index 75f8c919..00000000 --- a/src/adb/command/host-transport/remount.coffee +++ /dev/null @@ -1,17 +0,0 @@ -Command = require '../../command' -Protocol = require '../../protocol' - -class RemountCommand extends Command - execute: -> - this._send 'remount:' - @parser.readAscii 4 - .then (reply) => - switch reply - when Protocol.OKAY - true - when Protocol.FAIL - @parser.readError() - else - @parser.unexpected reply, 'OKAY or FAIL' - -module.exports = RemountCommand diff --git a/src/adb/command/host-transport/remount.js b/src/adb/command/host-transport/remount.js new file mode 100644 index 00000000..643e9e36 --- /dev/null +++ b/src/adb/command/host-transport/remount.js @@ -0,0 +1,36 @@ +var Command, Protocol, RemountCommand, + extend = function(child, parent) { for (var key in parent) { if (hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; }, + hasProp = {}.hasOwnProperty; + +Command = require('../../command'); + +Protocol = require('../../protocol'); + +RemountCommand = (function(superClass) { + extend(RemountCommand, superClass); + + function RemountCommand() { + return RemountCommand.__super__.constructor.apply(this, arguments); + } + + RemountCommand.prototype.execute = function() { + this._send('remount:'); + return this.parser.readAscii(4).then((function(_this) { + return function(reply) { + switch (reply) { + case Protocol.OKAY: + return true; + case Protocol.FAIL: + return _this.parser.readError(); + default: + return _this.parser.unexpected(reply, 'OKAY or FAIL'); + } + }; + })(this)); + }; + + return RemountCommand; + +})(Command); + +module.exports = RemountCommand; diff --git a/src/adb/command/host-transport/reverse.coffee b/src/adb/command/host-transport/reverse.coffee deleted file mode 100644 index cad2e9be..00000000 --- a/src/adb/command/host-transport/reverse.coffee +++ /dev/null @@ -1,25 +0,0 @@ -Command = require '../../command' -Protocol = require '../../protocol' - -class ReverseCommand extends Command - execute: (remote, local) -> - this._send "reverse:forward:#{remote};#{local}" - @parser.readAscii 4 - .then (reply) => - switch reply - when Protocol.OKAY - @parser.readAscii 4 - .then (reply) => - switch reply - when Protocol.OKAY - true - when Protocol.FAIL - @parser.readError() - else - @parser.unexpected reply, 'OKAY or FAIL' - when Protocol.FAIL - @parser.readError() - else - @parser.unexpected reply, 'OKAY or FAIL' - -module.exports = ReverseCommand diff --git a/src/adb/command/host-transport/reverse.js b/src/adb/command/host-transport/reverse.js new file mode 100644 index 00000000..c36174da --- /dev/null +++ b/src/adb/command/host-transport/reverse.js @@ -0,0 +1,45 @@ +var Command, Protocol, ReverseCommand, + extend = function(child, parent) { for (var key in parent) { if (hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; }, + hasProp = {}.hasOwnProperty; + +Command = require('../../command'); + +Protocol = require('../../protocol'); + +ReverseCommand = (function(superClass) { + extend(ReverseCommand, superClass); + + function ReverseCommand() { + return ReverseCommand.__super__.constructor.apply(this, arguments); + } + + ReverseCommand.prototype.execute = function(remote, local) { + this._send("reverse:forward:" + remote + ";" + local); + return this.parser.readAscii(4).then((function(_this) { + return function(reply) { + switch (reply) { + case Protocol.OKAY: + return _this.parser.readAscii(4).then(function(reply) { + switch (reply) { + case Protocol.OKAY: + return true; + case Protocol.FAIL: + return _this.parser.readError(); + default: + return _this.parser.unexpected(reply, 'OKAY or FAIL'); + } + }); + case Protocol.FAIL: + return _this.parser.readError(); + default: + return _this.parser.unexpected(reply, 'OKAY or FAIL'); + } + }; + })(this)); + }; + + return ReverseCommand; + +})(Command); + +module.exports = ReverseCommand; diff --git a/src/adb/command/host-transport/root.coffee b/src/adb/command/host-transport/root.coffee deleted file mode 100644 index 5b54712a..00000000 --- a/src/adb/command/host-transport/root.coffee +++ /dev/null @@ -1,24 +0,0 @@ -Command = require '../../command' -Protocol = require '../../protocol' - -class RootCommand extends Command - RE_OK = /restarting adbd as root/ - - execute: -> - this._send 'root:' - @parser.readAscii 4 - .then (reply) => - switch reply - when Protocol.OKAY - @parser.readAll() - .then (value) -> - if RE_OK.test(value) - true - else - throw new Error value.toString().trim() - when Protocol.FAIL - @parser.readError() - else - @parser.unexpected reply, 'OKAY or FAIL' - -module.exports = RootCommand diff --git a/src/adb/command/host-transport/root.js b/src/adb/command/host-transport/root.js new file mode 100644 index 00000000..4ba7dcc0 --- /dev/null +++ b/src/adb/command/host-transport/root.js @@ -0,0 +1,46 @@ +var Command, Protocol, RootCommand, + extend = function(child, parent) { for (var key in parent) { if (hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; }, + hasProp = {}.hasOwnProperty; + +Command = require('../../command'); + +Protocol = require('../../protocol'); + +RootCommand = (function(superClass) { + var RE_OK; + + extend(RootCommand, superClass); + + function RootCommand() { + return RootCommand.__super__.constructor.apply(this, arguments); + } + + RE_OK = /restarting adbd as root/; + + RootCommand.prototype.execute = function() { + this._send('root:'); + return this.parser.readAscii(4).then((function(_this) { + return function(reply) { + switch (reply) { + case Protocol.OKAY: + return _this.parser.readAll().then(function(value) { + if (RE_OK.test(value)) { + return true; + } else { + throw new Error(value.toString().trim()); + } + }); + case Protocol.FAIL: + return _this.parser.readError(); + default: + return _this.parser.unexpected(reply, 'OKAY or FAIL'); + } + }; + })(this)); + }; + + return RootCommand; + +})(Command); + +module.exports = RootCommand; diff --git a/src/adb/command/host-transport/screencap.coffee b/src/adb/command/host-transport/screencap.coffee deleted file mode 100644 index a1eaf113..00000000 --- a/src/adb/command/host-transport/screencap.coffee +++ /dev/null @@ -1,28 +0,0 @@ -Promise = require 'bluebird' - -Command = require '../../command' -Protocol = require '../../protocol' -Parser = require '../../parser' -LineTransform = require '../../linetransform' - -class ScreencapCommand extends Command - execute: -> - this._send 'shell:echo && screencap -p 2>/dev/null' - @parser.readAscii 4 - .then (reply) => - switch reply - when Protocol.OKAY - transform = new LineTransform - @parser.readBytes 1 - .then (chunk) => - transform = new LineTransform autoDetect: true - transform.write chunk - @parser.raw().pipe transform - .catch Parser.PrematureEOFError, -> - throw new Error 'No support for the screencap command' - when Protocol.FAIL - @parser.readError() - else - @parser.unexpected reply, 'OKAY or FAIL' - -module.exports = ScreencapCommand diff --git a/src/adb/command/host-transport/screencap.js b/src/adb/command/host-transport/screencap.js new file mode 100644 index 00000000..a89b689a --- /dev/null +++ b/src/adb/command/host-transport/screencap.js @@ -0,0 +1,52 @@ +var Command, LineTransform, Parser, Promise, Protocol, ScreencapCommand, + extend = function(child, parent) { for (var key in parent) { if (hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; }, + hasProp = {}.hasOwnProperty; + +Promise = require('bluebird'); + +Command = require('../../command'); + +Protocol = require('../../protocol'); + +Parser = require('../../parser'); + +LineTransform = require('../../linetransform'); + +ScreencapCommand = (function(superClass) { + extend(ScreencapCommand, superClass); + + function ScreencapCommand() { + return ScreencapCommand.__super__.constructor.apply(this, arguments); + } + + ScreencapCommand.prototype.execute = function() { + this._send('shell:echo && screencap -p 2>/dev/null'); + return this.parser.readAscii(4).then((function(_this) { + return function(reply) { + var transform; + switch (reply) { + case Protocol.OKAY: + transform = new LineTransform; + return _this.parser.readBytes(1).then(function(chunk) { + transform = new LineTransform({ + autoDetect: true + }); + transform.write(chunk); + return _this.parser.raw().pipe(transform); + })["catch"](Parser.PrematureEOFError, function() { + throw new Error('No support for the screencap command'); + }); + case Protocol.FAIL: + return _this.parser.readError(); + default: + return _this.parser.unexpected(reply, 'OKAY or FAIL'); + } + }; + })(this)); + }; + + return ScreencapCommand; + +})(Command); + +module.exports = ScreencapCommand; diff --git a/src/adb/command/host-transport/shell.coffee b/src/adb/command/host-transport/shell.coffee deleted file mode 100644 index 36d29ada..00000000 --- a/src/adb/command/host-transport/shell.coffee +++ /dev/null @@ -1,19 +0,0 @@ -Command = require '../../command' -Protocol = require '../../protocol' - -class ShellCommand extends Command - execute: (command) -> - if Array.isArray command - command = command.map(this._escape).join ' ' - this._send "shell:#{command}" - @parser.readAscii 4 - .then (reply) => - switch reply - when Protocol.OKAY - @parser.raw() - when Protocol.FAIL - @parser.readError() - else - @parser.unexpected reply, 'OKAY or FAIL' - -module.exports = ShellCommand diff --git a/src/adb/command/host-transport/shell.js b/src/adb/command/host-transport/shell.js new file mode 100644 index 00000000..d2574cf7 --- /dev/null +++ b/src/adb/command/host-transport/shell.js @@ -0,0 +1,39 @@ +var Command, Protocol, ShellCommand, + extend = function(child, parent) { for (var key in parent) { if (hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; }, + hasProp = {}.hasOwnProperty; + +Command = require('../../command'); + +Protocol = require('../../protocol'); + +ShellCommand = (function(superClass) { + extend(ShellCommand, superClass); + + function ShellCommand() { + return ShellCommand.__super__.constructor.apply(this, arguments); + } + + ShellCommand.prototype.execute = function(command) { + if (Array.isArray(command)) { + command = command.map(this._escape).join(' '); + } + this._send("shell:" + command); + return this.parser.readAscii(4).then((function(_this) { + return function(reply) { + switch (reply) { + case Protocol.OKAY: + return _this.parser.raw(); + case Protocol.FAIL: + return _this.parser.readError(); + default: + return _this.parser.unexpected(reply, 'OKAY or FAIL'); + } + }; + })(this)); + }; + + return ShellCommand; + +})(Command); + +module.exports = ShellCommand; diff --git a/src/adb/command/host-transport/startactivity.coffee b/src/adb/command/host-transport/startactivity.coffee deleted file mode 100644 index c834b781..00000000 --- a/src/adb/command/host-transport/startactivity.coffee +++ /dev/null @@ -1,123 +0,0 @@ -Command = require '../../command' -Protocol = require '../../protocol' -Parser = require '../../parser' - -class StartActivityCommand extends Command - RE_ERROR = /^Error: (.*)$/ - EXTRA_TYPES = - string: 's' - null: 'sn' - bool: 'z' - int: 'i' - long: 'l' - float: 'l' - uri: 'u' - component: 'cn' - - execute: (options) -> - args = this._intentArgs options - if options.debug - args.push '-D' - if options.wait - args.push '-W' - if options.user or options.user is 0 - args.push '--user', this._escape options.user - this._run 'start', args - - _run: (command, args) -> - this._send "shell:am #{command} #{args.join ' '}" - @parser.readAscii 4 - .then (reply) => - switch reply - when Protocol.OKAY - @parser.searchLine RE_ERROR - .finally => - @parser.end() - .then (match) -> - throw new Error match[1] - .catch Parser.PrematureEOFError, (err) -> - true - when Protocol.FAIL - @parser.readError() - else - @parser.unexpected reply, 'OKAY or FAIL' - - _intentArgs: (options) -> - args = [] - if options.extras - args.push.apply args, this._formatExtras options.extras - if options.action - args.push '-a', this._escape options.action - if options.data - args.push '-d', this._escape options.data - if options.mimeType - args.push '-t', this._escape options.mimeType - if options.category - if Array.isArray options.category - options.category.forEach (category) => - args.push '-c', this._escape category - else - args.push '-c', this._escape options.category - if options.component - args.push '-n', this._escape options.component - if options.flags - args.push '-f', this._escape options.flags - return args - - _formatExtras: (extras) -> - return [] unless extras - if Array.isArray extras - extras.reduce (all, extra) => - all.concat this._formatLongExtra extra - , [] - else - Object.keys(extras).reduce (all, key) => - all.concat this._formatShortExtra key, extras[key] - , [] - - _formatShortExtra: (key, value) -> - sugared = - key: key - if value is null - sugared.type = 'null' - else if Array.isArray value - throw new Error "Refusing to format array value '#{key}' using short - syntax; empty array would cause unpredictable results due to unknown - type. Please use long syntax instead." - else - switch typeof value - when 'string' - sugared.type = 'string' - sugared.value = value - when 'boolean' - sugared.type = 'bool' - sugared.value = value - when 'number' - sugared.type = 'int' - sugared.value = value - when 'object' - sugared = value - sugared.key = key - return this._formatLongExtra sugared - - _formatLongExtra: (extra) -> - args = [] - extra.type = 'string' unless extra.type - type = EXTRA_TYPES[extra.type] - unless type - throw new Error "Unsupported type '#{extra.type}' for extra - '#{extra.key}'" - if extra.type is 'null' - args.push "--e#{type}" - args.push this._escape extra.key - else if Array.isArray extra.value - args.push "--e#{type}a" - args.push this._escape extra.key - args.push this._escape extra.value.join ',' - else - args.push "--e#{type}" - args.push this._escape extra.key - args.push this._escape extra.value - return args - -module.exports = StartActivityCommand diff --git a/src/adb/command/host-transport/startactivity.js b/src/adb/command/host-transport/startactivity.js new file mode 100644 index 00000000..ab514011 --- /dev/null +++ b/src/adb/command/host-transport/startactivity.js @@ -0,0 +1,184 @@ +var Command, Parser, Protocol, StartActivityCommand, + extend = function(child, parent) { for (var key in parent) { if (hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; }, + hasProp = {}.hasOwnProperty; + +Command = require('../../command'); + +Protocol = require('../../protocol'); + +Parser = require('../../parser'); + +StartActivityCommand = (function(superClass) { + var EXTRA_TYPES, RE_ERROR; + + extend(StartActivityCommand, superClass); + + function StartActivityCommand() { + return StartActivityCommand.__super__.constructor.apply(this, arguments); + } + + RE_ERROR = /^Error: (.*)$/; + + EXTRA_TYPES = { + string: 's', + "null": 'sn', + bool: 'z', + int: 'i', + long: 'l', + float: 'l', + uri: 'u', + component: 'cn' + }; + + StartActivityCommand.prototype.execute = function(options) { + var args; + args = this._intentArgs(options); + if (options.debug) { + args.push('-D'); + } + if (options.wait) { + args.push('-W'); + } + if (options.user || options.user === 0) { + args.push('--user', this._escape(options.user)); + } + return this._run('start', args); + }; + + StartActivityCommand.prototype._run = function(command, args) { + this._send("shell:am " + command + " " + (args.join(' '))); + return this.parser.readAscii(4).then((function(_this) { + return function(reply) { + switch (reply) { + case Protocol.OKAY: + return _this.parser.searchLine(RE_ERROR)["finally"](function() { + return _this.parser.end(); + }).then(function(match) { + throw new Error(match[1]); + })["catch"](Parser.PrematureEOFError, function(err) { + return true; + }); + case Protocol.FAIL: + return _this.parser.readError(); + default: + return _this.parser.unexpected(reply, 'OKAY or FAIL'); + } + }; + })(this)); + }; + + StartActivityCommand.prototype._intentArgs = function(options) { + var args; + args = []; + if (options.extras) { + args.push.apply(args, this._formatExtras(options.extras)); + } + if (options.action) { + args.push('-a', this._escape(options.action)); + } + if (options.data) { + args.push('-d', this._escape(options.data)); + } + if (options.mimeType) { + args.push('-t', this._escape(options.mimeType)); + } + if (options.category) { + if (Array.isArray(options.category)) { + options.category.forEach((function(_this) { + return function(category) { + return args.push('-c', _this._escape(category)); + }; + })(this)); + } else { + args.push('-c', this._escape(options.category)); + } + } + if (options.component) { + args.push('-n', this._escape(options.component)); + } + if (options.flags) { + args.push('-f', this._escape(options.flags)); + } + return args; + }; + + StartActivityCommand.prototype._formatExtras = function(extras) { + if (!extras) { + return []; + } + if (Array.isArray(extras)) { + return extras.reduce((function(_this) { + return function(all, extra) { + return all.concat(_this._formatLongExtra(extra)); + }; + })(this), []); + } else { + return Object.keys(extras).reduce((function(_this) { + return function(all, key) { + return all.concat(_this._formatShortExtra(key, extras[key])); + }; + })(this), []); + } + }; + + StartActivityCommand.prototype._formatShortExtra = function(key, value) { + var sugared; + sugared = { + key: key + }; + if (value === null) { + sugared.type = 'null'; + } else if (Array.isArray(value)) { + throw new Error("Refusing to format array value '" + key + "' using short syntax; empty array would cause unpredictable results due to unknown type. Please use long syntax instead."); + } else { + switch (typeof value) { + case 'string': + sugared.type = 'string'; + sugared.value = value; + break; + case 'boolean': + sugared.type = 'bool'; + sugared.value = value; + break; + case 'number': + sugared.type = 'int'; + sugared.value = value; + break; + case 'object': + sugared = value; + sugared.key = key; + } + } + return this._formatLongExtra(sugared); + }; + + StartActivityCommand.prototype._formatLongExtra = function(extra) { + var args, type; + args = []; + if (!extra.type) { + extra.type = 'string'; + } + type = EXTRA_TYPES[extra.type]; + if (!type) { + throw new Error("Unsupported type '" + extra.type + "' for extra '" + extra.key + "'"); + } + if (extra.type === 'null') { + args.push("--e" + type); + args.push(this._escape(extra.key)); + } else if (Array.isArray(extra.value)) { + args.push("--e" + type + "a"); + args.push(this._escape(extra.key)); + args.push(this._escape(extra.value.join(','))); + } else { + args.push("--e" + type); + args.push(this._escape(extra.key)); + args.push(this._escape(extra.value)); + } + return args; + }; + + return StartActivityCommand; + +})(Command); + +module.exports = StartActivityCommand; diff --git a/src/adb/command/host-transport/startservice.coffee b/src/adb/command/host-transport/startservice.coffee deleted file mode 100644 index f7cb2bf9..00000000 --- a/src/adb/command/host-transport/startservice.coffee +++ /dev/null @@ -1,14 +0,0 @@ -Command = require '../../command' -Protocol = require '../../protocol' -Parser = require '../../parser' - -StartActivityCommand = require './startactivity' - -class StartServiceCommand extends StartActivityCommand - execute: (options) -> - args = this._intentArgs options - if options.user or options.user is 0 - args.push '--user', this._escape options.user - this._run 'startservice', args - -module.exports = StartServiceCommand diff --git a/src/adb/command/host-transport/startservice.js b/src/adb/command/host-transport/startservice.js new file mode 100644 index 00000000..d9fa88af --- /dev/null +++ b/src/adb/command/host-transport/startservice.js @@ -0,0 +1,33 @@ +var Command, Parser, Protocol, StartActivityCommand, StartServiceCommand, + extend = function(child, parent) { for (var key in parent) { if (hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; }, + hasProp = {}.hasOwnProperty; + +Command = require('../../command'); + +Protocol = require('../../protocol'); + +Parser = require('../../parser'); + +StartActivityCommand = require('./startactivity'); + +StartServiceCommand = (function(superClass) { + extend(StartServiceCommand, superClass); + + function StartServiceCommand() { + return StartServiceCommand.__super__.constructor.apply(this, arguments); + } + + StartServiceCommand.prototype.execute = function(options) { + var args; + args = this._intentArgs(options); + if (options.user || options.user === 0) { + args.push('--user', this._escape(options.user)); + } + return this._run('startservice', args); + }; + + return StartServiceCommand; + +})(StartActivityCommand); + +module.exports = StartServiceCommand; diff --git a/src/adb/command/host-transport/sync.coffee b/src/adb/command/host-transport/sync.coffee deleted file mode 100644 index c349086e..00000000 --- a/src/adb/command/host-transport/sync.coffee +++ /dev/null @@ -1,18 +0,0 @@ -Command = require '../../command' -Protocol = require '../../protocol' -Sync = require '../../sync' - -class SyncCommand extends Command - execute: -> - this._send 'sync:' - @parser.readAscii 4 - .then (reply) => - switch reply - when Protocol.OKAY - new Sync @connection - when Protocol.FAIL - @parser.readError() - else - @parser.unexpected reply, 'OKAY or FAIL' - -module.exports = SyncCommand diff --git a/src/adb/command/host-transport/sync.js b/src/adb/command/host-transport/sync.js new file mode 100644 index 00000000..6bc6d543 --- /dev/null +++ b/src/adb/command/host-transport/sync.js @@ -0,0 +1,38 @@ +var Command, Protocol, Sync, SyncCommand, + extend = function(child, parent) { for (var key in parent) { if (hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; }, + hasProp = {}.hasOwnProperty; + +Command = require('../../command'); + +Protocol = require('../../protocol'); + +Sync = require('../../sync'); + +SyncCommand = (function(superClass) { + extend(SyncCommand, superClass); + + function SyncCommand() { + return SyncCommand.__super__.constructor.apply(this, arguments); + } + + SyncCommand.prototype.execute = function() { + this._send('sync:'); + return this.parser.readAscii(4).then((function(_this) { + return function(reply) { + switch (reply) { + case Protocol.OKAY: + return new Sync(_this.connection); + case Protocol.FAIL: + return _this.parser.readError(); + default: + return _this.parser.unexpected(reply, 'OKAY or FAIL'); + } + }; + })(this)); + }; + + return SyncCommand; + +})(Command); + +module.exports = SyncCommand; diff --git a/src/adb/command/host-transport/tcp.coffee b/src/adb/command/host-transport/tcp.coffee deleted file mode 100644 index 398d04a7..00000000 --- a/src/adb/command/host-transport/tcp.coffee +++ /dev/null @@ -1,17 +0,0 @@ -Command = require '../../command' -Protocol = require '../../protocol' - -class TcpCommand extends Command - execute: (port, host) -> - this._send "tcp:#{port}" + if host then ":#{host}" else '' - @parser.readAscii 4 - .then (reply) => - switch reply - when Protocol.OKAY - @parser.raw() - when Protocol.FAIL - @parser.readError() - else - @parser.unexpected reply, 'OKAY or FAIL' - -module.exports = TcpCommand diff --git a/src/adb/command/host-transport/tcp.js b/src/adb/command/host-transport/tcp.js new file mode 100644 index 00000000..41813f8d --- /dev/null +++ b/src/adb/command/host-transport/tcp.js @@ -0,0 +1,36 @@ +var Command, Protocol, TcpCommand, + extend = function(child, parent) { for (var key in parent) { if (hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; }, + hasProp = {}.hasOwnProperty; + +Command = require('../../command'); + +Protocol = require('../../protocol'); + +TcpCommand = (function(superClass) { + extend(TcpCommand, superClass); + + function TcpCommand() { + return TcpCommand.__super__.constructor.apply(this, arguments); + } + + TcpCommand.prototype.execute = function(port, host) { + this._send(("tcp:" + port) + (host ? ":" + host : '')); + return this.parser.readAscii(4).then((function(_this) { + return function(reply) { + switch (reply) { + case Protocol.OKAY: + return _this.parser.raw(); + case Protocol.FAIL: + return _this.parser.readError(); + default: + return _this.parser.unexpected(reply, 'OKAY or FAIL'); + } + }; + })(this)); + }; + + return TcpCommand; + +})(Command); + +module.exports = TcpCommand; diff --git a/src/adb/command/host-transport/tcpip.coffee b/src/adb/command/host-transport/tcpip.coffee deleted file mode 100644 index 1081fa20..00000000 --- a/src/adb/command/host-transport/tcpip.coffee +++ /dev/null @@ -1,24 +0,0 @@ -Command = require '../../command' -Protocol = require '../../protocol' - -class TcpIpCommand extends Command - RE_OK = /restarting in/ - - execute: (port) -> - this._send "tcpip:#{port}" - @parser.readAscii 4 - .then (reply) => - switch reply - when Protocol.OKAY - @parser.readAll() - .then (value) -> - if RE_OK.test(value) - port - else - throw new Error value.toString().trim() - when Protocol.FAIL - @parser.readError() - else - @parser.unexpected reply, 'OKAY or FAIL' - -module.exports = TcpIpCommand diff --git a/src/adb/command/host-transport/tcpip.js b/src/adb/command/host-transport/tcpip.js new file mode 100644 index 00000000..a3e23994 --- /dev/null +++ b/src/adb/command/host-transport/tcpip.js @@ -0,0 +1,46 @@ +var Command, Protocol, TcpIpCommand, + extend = function(child, parent) { for (var key in parent) { if (hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; }, + hasProp = {}.hasOwnProperty; + +Command = require('../../command'); + +Protocol = require('../../protocol'); + +TcpIpCommand = (function(superClass) { + var RE_OK; + + extend(TcpIpCommand, superClass); + + function TcpIpCommand() { + return TcpIpCommand.__super__.constructor.apply(this, arguments); + } + + RE_OK = /restarting in/; + + TcpIpCommand.prototype.execute = function(port) { + this._send("tcpip:" + port); + return this.parser.readAscii(4).then((function(_this) { + return function(reply) { + switch (reply) { + case Protocol.OKAY: + return _this.parser.readAll().then(function(value) { + if (RE_OK.test(value)) { + return port; + } else { + throw new Error(value.toString().trim()); + } + }); + case Protocol.FAIL: + return _this.parser.readError(); + default: + return _this.parser.unexpected(reply, 'OKAY or FAIL'); + } + }; + })(this)); + }; + + return TcpIpCommand; + +})(Command); + +module.exports = TcpIpCommand; diff --git a/src/adb/command/host-transport/trackjdwp.coffee b/src/adb/command/host-transport/trackjdwp.coffee deleted file mode 100644 index 715daf79..00000000 --- a/src/adb/command/host-transport/trackjdwp.coffee +++ /dev/null @@ -1,68 +0,0 @@ -{EventEmitter} = require 'events' - -Promise = require 'bluebird' - -Command = require '../../command' -Protocol = require '../../protocol' -Parser = require '../../parser' - -class TrackJdwpCommand extends Command - execute: -> - this._send 'track-jdwp' - @parser.readAscii 4 - .then (reply) => - switch reply - when Protocol.OKAY - new Tracker this - when Protocol.FAIL - @parser.readError() - else - @parser.unexpected reply, 'OKAY or FAIL' - - class Tracker extends EventEmitter - constructor: (@command) -> - @pids = [] - @pidMap = Object.create null - @reader = this.read() - .catch Parser.PrematureEOFError, (err) => - this.emit 'end' - .catch Promise.CancellationError, (err) => - @command.connection.end() - this.emit 'end' - .catch (err) => - @command.connection.end() - this.emit 'error', err - this.emit 'end' - - read: -> - @command.parser.readValue() - .cancellable() - .then (list) => - pids = list.toString().split '\n' - pids.push maybeEmpty if maybeEmpty = pids.pop() - this.update pids - - update: (newList) -> - changeSet = - removed: [] - added: [] - newMap = Object.create null - for pid in newList - unless @pidMap[pid] - changeSet.added.push pid - this.emit 'add', pid - newMap[pid] = pid - for pid in @pids - unless newMap[pid] - changeSet.removed.push pid - this.emit 'remove', pid - @pids = newList - @pidMap = newMap - this.emit 'changeSet', changeSet, newList - return this - - end: -> - @reader.cancel() - return this - -module.exports = TrackJdwpCommand diff --git a/src/adb/command/host-transport/trackjdwp.js b/src/adb/command/host-transport/trackjdwp.js new file mode 100644 index 00000000..225200fd --- /dev/null +++ b/src/adb/command/host-transport/trackjdwp.js @@ -0,0 +1,120 @@ +var Command, EventEmitter, Parser, Promise, Protocol, TrackJdwpCommand, + extend = function(child, parent) { for (var key in parent) { if (hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; }, + hasProp = {}.hasOwnProperty; + +EventEmitter = require('events').EventEmitter; + +Promise = require('bluebird'); + +Command = require('../../command'); + +Protocol = require('../../protocol'); + +Parser = require('../../parser'); + +TrackJdwpCommand = (function(superClass) { + var Tracker; + + extend(TrackJdwpCommand, superClass); + + function TrackJdwpCommand() { + return TrackJdwpCommand.__super__.constructor.apply(this, arguments); + } + + TrackJdwpCommand.prototype.execute = function() { + this._send('track-jdwp'); + return this.parser.readAscii(4).then((function(_this) { + return function(reply) { + switch (reply) { + case Protocol.OKAY: + return new Tracker(_this); + case Protocol.FAIL: + return _this.parser.readError(); + default: + return _this.parser.unexpected(reply, 'OKAY or FAIL'); + } + }; + })(this)); + }; + + Tracker = (function(superClass1) { + extend(Tracker, superClass1); + + function Tracker(command) { + this.command = command; + this.pids = []; + this.pidMap = Object.create(null); + this.reader = this.read()["catch"](Parser.PrematureEOFError, (function(_this) { + return function(err) { + return _this.emit('end'); + }; + })(this))["catch"](Promise.CancellationError, (function(_this) { + return function(err) { + _this.command.connection.end(); + return _this.emit('end'); + }; + })(this))["catch"]((function(_this) { + return function(err) { + _this.command.connection.end(); + _this.emit('error', err); + return _this.emit('end'); + }; + })(this)); + } + + Tracker.prototype.read = function() { + return this.command.parser.readValue().cancellable().then((function(_this) { + return function(list) { + var maybeEmpty, pids; + pids = list.toString().split('\n'); + if (maybeEmpty = pids.pop()) { + pids.push(maybeEmpty); + } + return _this.update(pids); + }; + })(this)); + }; + + Tracker.prototype.update = function(newList) { + var changeSet, i, j, len, len1, newMap, pid, ref; + changeSet = { + removed: [], + added: [] + }; + newMap = Object.create(null); + for (i = 0, len = newList.length; i < len; i++) { + pid = newList[i]; + if (!this.pidMap[pid]) { + changeSet.added.push(pid); + this.emit('add', pid); + newMap[pid] = pid; + } + } + ref = this.pids; + for (j = 0, len1 = ref.length; j < len1; j++) { + pid = ref[j]; + if (!newMap[pid]) { + changeSet.removed.push(pid); + this.emit('remove', pid); + } + } + this.pids = newList; + this.pidMap = newMap; + this.emit('changeSet', changeSet, newList); + return this; + }; + + Tracker.prototype.end = function() { + this.reader.cancel(); + return this; + }; + + return Tracker; + + })(EventEmitter); + + return TrackJdwpCommand; + +})(Command); + +module.exports = TrackJdwpCommand; diff --git a/src/adb/command/host-transport/uninstall.coffee b/src/adb/command/host-transport/uninstall.coffee deleted file mode 100644 index ce2d3ff6..00000000 --- a/src/adb/command/host-transport/uninstall.coffee +++ /dev/null @@ -1,28 +0,0 @@ -Command = require '../../command' -Protocol = require '../../protocol' - -class UninstallCommand extends Command - execute: (pkg) -> - this._send "shell:pm uninstall #{pkg}" - @parser.readAscii 4 - .then (reply) => - switch reply - when Protocol.OKAY - @parser.searchLine /^(Success|Failure.*|.*Unknown package:.*)$/ - .then (match) -> - if match[1] is 'Success' - true - else - # Either way, the package was uninstalled or doesn't exist, - # which is good enough for us. - true - .finally => - # Consume all remaining content to "naturally" close the - # connection. - @parser.readAll() - when Protocol.FAIL - @parser.readError() - else - @parser.unexpected reply, "OKAY or FAIL" - -module.exports = UninstallCommand diff --git a/src/adb/command/host-transport/uninstall.js b/src/adb/command/host-transport/uninstall.js new file mode 100644 index 00000000..b9f81b5c --- /dev/null +++ b/src/adb/command/host-transport/uninstall.js @@ -0,0 +1,44 @@ +var Command, Protocol, UninstallCommand, + extend = function(child, parent) { for (var key in parent) { if (hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; }, + hasProp = {}.hasOwnProperty; + +Command = require('../../command'); + +Protocol = require('../../protocol'); + +UninstallCommand = (function(superClass) { + extend(UninstallCommand, superClass); + + function UninstallCommand() { + return UninstallCommand.__super__.constructor.apply(this, arguments); + } + + UninstallCommand.prototype.execute = function(pkg) { + this._send("shell:pm uninstall " + pkg); + return this.parser.readAscii(4).then((function(_this) { + return function(reply) { + switch (reply) { + case Protocol.OKAY: + return _this.parser.searchLine(/^(Success|Failure.*|.*Unknown package:.*)$/).then(function(match) { + if (match[1] === 'Success') { + return true; + } else { + return true; + } + })["finally"](function() { + return _this.parser.readAll(); + }); + case Protocol.FAIL: + return _this.parser.readError(); + default: + return _this.parser.unexpected(reply, "OKAY or FAIL"); + } + }; + })(this)); + }; + + return UninstallCommand; + +})(Command); + +module.exports = UninstallCommand; diff --git a/src/adb/command/host-transport/usb.coffee b/src/adb/command/host-transport/usb.coffee deleted file mode 100644 index 6d09b4db..00000000 --- a/src/adb/command/host-transport/usb.coffee +++ /dev/null @@ -1,24 +0,0 @@ -Command = require '../../command' -Protocol = require '../../protocol' - -class UsbCommand extends Command - RE_OK = /restarting in/ - - execute: -> - this._send 'usb:' - @parser.readAscii 4 - .then (reply) => - switch reply - when Protocol.OKAY - @parser.readAll() - .then (value) -> - if RE_OK.test(value) - true - else - throw new Error value.toString().trim() - when Protocol.FAIL - @parser.readError() - else - @parser.unexpected reply, 'OKAY or FAIL' - -module.exports = UsbCommand diff --git a/src/adb/command/host-transport/usb.js b/src/adb/command/host-transport/usb.js new file mode 100644 index 00000000..6f3648ed --- /dev/null +++ b/src/adb/command/host-transport/usb.js @@ -0,0 +1,46 @@ +var Command, Protocol, UsbCommand, + extend = function(child, parent) { for (var key in parent) { if (hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; }, + hasProp = {}.hasOwnProperty; + +Command = require('../../command'); + +Protocol = require('../../protocol'); + +UsbCommand = (function(superClass) { + var RE_OK; + + extend(UsbCommand, superClass); + + function UsbCommand() { + return UsbCommand.__super__.constructor.apply(this, arguments); + } + + RE_OK = /restarting in/; + + UsbCommand.prototype.execute = function() { + this._send('usb:'); + return this.parser.readAscii(4).then((function(_this) { + return function(reply) { + switch (reply) { + case Protocol.OKAY: + return _this.parser.readAll().then(function(value) { + if (RE_OK.test(value)) { + return true; + } else { + throw new Error(value.toString().trim()); + } + }); + case Protocol.FAIL: + return _this.parser.readError(); + default: + return _this.parser.unexpected(reply, 'OKAY or FAIL'); + } + }; + })(this)); + }; + + return UsbCommand; + +})(Command); + +module.exports = UsbCommand; diff --git a/src/adb/command/host-transport/waitbootcomplete.coffee b/src/adb/command/host-transport/waitbootcomplete.coffee deleted file mode 100644 index 556aeaa9..00000000 --- a/src/adb/command/host-transport/waitbootcomplete.coffee +++ /dev/null @@ -1,24 +0,0 @@ -debug = require('debug')('adb:command:waitboot') - -Command = require '../../command' -Protocol = require '../../protocol' - -class WaitBootCompleteCommand extends Command - execute: -> - this._send \ - 'shell:while getprop sys.boot_completed 2>/dev/null; do sleep 1; done' - @parser.readAscii 4 - .then (reply) => - switch reply - when Protocol.OKAY - @parser.searchLine /^1$/ - .finally => - @parser.end() - .then -> - true - when Protocol.FAIL - @parser.readError() - else - @parser.unexpected reply, 'OKAY or FAIL' - -module.exports = WaitBootCompleteCommand diff --git a/src/adb/command/host-transport/waitbootcomplete.js b/src/adb/command/host-transport/waitbootcomplete.js new file mode 100644 index 00000000..f797e706 --- /dev/null +++ b/src/adb/command/host-transport/waitbootcomplete.js @@ -0,0 +1,42 @@ +var Command, Protocol, WaitBootCompleteCommand, debug, + extend = function(child, parent) { for (var key in parent) { if (hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; }, + hasProp = {}.hasOwnProperty; + +debug = require('debug')('adb:command:waitboot'); + +Command = require('../../command'); + +Protocol = require('../../protocol'); + +WaitBootCompleteCommand = (function(superClass) { + extend(WaitBootCompleteCommand, superClass); + + function WaitBootCompleteCommand() { + return WaitBootCompleteCommand.__super__.constructor.apply(this, arguments); + } + + WaitBootCompleteCommand.prototype.execute = function() { + this._send('shell:while getprop sys.boot_completed 2>/dev/null; do sleep 1; done'); + return this.parser.readAscii(4).then((function(_this) { + return function(reply) { + switch (reply) { + case Protocol.OKAY: + return _this.parser.searchLine(/^1$/)["finally"](function() { + return _this.parser.end(); + }).then(function() { + return true; + }); + case Protocol.FAIL: + return _this.parser.readError(); + default: + return _this.parser.unexpected(reply, 'OKAY or FAIL'); + } + }; + })(this)); + }; + + return WaitBootCompleteCommand; + +})(Command); + +module.exports = WaitBootCompleteCommand; diff --git a/src/adb/command/host/connect.coffee b/src/adb/command/host/connect.coffee deleted file mode 100644 index 966f5609..00000000 --- a/src/adb/command/host/connect.coffee +++ /dev/null @@ -1,28 +0,0 @@ -Command = require '../../command' -Protocol = require '../../protocol' - -class ConnectCommand extends Command - # Possible replies: - # "unable to connect to 192.168.2.2:5555" - # "connected to 192.168.2.2:5555" - # "already connected to 192.168.2.2:5555" - RE_OK = /connected to|already connected/ - - execute: (host, port) -> - this._send "host:connect:#{host}:#{port}" - @parser.readAscii 4 - .then (reply) => - switch reply - when Protocol.OKAY - @parser.readValue() - .then (value) -> - if RE_OK.test value - "#{host}:#{port}" - else - throw new Error value.toString() - when Protocol.FAIL - @parser.readError() - else - @parser.unexpected reply, 'OKAY or FAIL' - -module.exports = ConnectCommand diff --git a/src/adb/command/host/connect.js b/src/adb/command/host/connect.js new file mode 100644 index 00000000..4596e2de --- /dev/null +++ b/src/adb/command/host/connect.js @@ -0,0 +1,46 @@ +var Command, ConnectCommand, Protocol, + extend = function(child, parent) { for (var key in parent) { if (hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; }, + hasProp = {}.hasOwnProperty; + +Command = require('../../command'); + +Protocol = require('../../protocol'); + +ConnectCommand = (function(superClass) { + var RE_OK; + + extend(ConnectCommand, superClass); + + function ConnectCommand() { + return ConnectCommand.__super__.constructor.apply(this, arguments); + } + + RE_OK = /connected to|already connected/; + + ConnectCommand.prototype.execute = function(host, port) { + this._send("host:connect:" + host + ":" + port); + return this.parser.readAscii(4).then((function(_this) { + return function(reply) { + switch (reply) { + case Protocol.OKAY: + return _this.parser.readValue().then(function(value) { + if (RE_OK.test(value)) { + return host + ":" + port; + } else { + throw new Error(value.toString()); + } + }); + case Protocol.FAIL: + return _this.parser.readError(); + default: + return _this.parser.unexpected(reply, 'OKAY or FAIL'); + } + }; + })(this)); + }; + + return ConnectCommand; + +})(Command); + +module.exports = ConnectCommand; diff --git a/src/adb/command/host/devices.coffee b/src/adb/command/host/devices.coffee deleted file mode 100644 index b6071f50..00000000 --- a/src/adb/command/host/devices.coffee +++ /dev/null @@ -1,31 +0,0 @@ -Command = require '../../command' -Protocol = require '../../protocol' - -class HostDevicesCommand extends Command - execute: -> - this._send 'host:devices' - @parser.readAscii 4 - .then (reply) => - switch reply - when Protocol.OKAY - this._readDevices() - when Protocol.FAIL - @parser.readError() - else - @parser.unexpected reply, 'OKAY or FAIL' - - _readDevices: -> - @parser.readValue() - .then (value) => - this._parseDevices value - - _parseDevices: (value) -> - devices = [] - return devices unless value.length - for line in value.toString('ascii').split '\n' - if line - [id, type] = line.split '\t' - devices.push id: id, type: type - return devices - -module.exports = HostDevicesCommand diff --git a/src/adb/command/host/devices.js b/src/adb/command/host/devices.js new file mode 100644 index 00000000..58833feb --- /dev/null +++ b/src/adb/command/host/devices.js @@ -0,0 +1,64 @@ +var Command, HostDevicesCommand, Protocol, + extend = function(child, parent) { for (var key in parent) { if (hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; }, + hasProp = {}.hasOwnProperty; + +Command = require('../../command'); + +Protocol = require('../../protocol'); + +HostDevicesCommand = (function(superClass) { + extend(HostDevicesCommand, superClass); + + function HostDevicesCommand() { + return HostDevicesCommand.__super__.constructor.apply(this, arguments); + } + + HostDevicesCommand.prototype.execute = function() { + this._send('host:devices'); + return this.parser.readAscii(4).then((function(_this) { + return function(reply) { + switch (reply) { + case Protocol.OKAY: + return _this._readDevices(); + case Protocol.FAIL: + return _this.parser.readError(); + default: + return _this.parser.unexpected(reply, 'OKAY or FAIL'); + } + }; + })(this)); + }; + + HostDevicesCommand.prototype._readDevices = function() { + return this.parser.readValue().then((function(_this) { + return function(value) { + return _this._parseDevices(value); + }; + })(this)); + }; + + HostDevicesCommand.prototype._parseDevices = function(value) { + var devices, i, id, len, line, ref, ref1, type; + devices = []; + if (!value.length) { + return devices; + } + ref = value.toString('ascii').split('\n'); + for (i = 0, len = ref.length; i < len; i++) { + line = ref[i]; + if (line) { + ref1 = line.split('\t'), id = ref1[0], type = ref1[1]; + devices.push({ + id: id, + type: type + }); + } + } + return devices; + }; + + return HostDevicesCommand; + +})(Command); + +module.exports = HostDevicesCommand; diff --git a/src/adb/command/host/deviceswithpaths.coffee b/src/adb/command/host/deviceswithpaths.coffee deleted file mode 100644 index d1b9deef..00000000 --- a/src/adb/command/host/deviceswithpaths.coffee +++ /dev/null @@ -1,32 +0,0 @@ -Command = require '../../command' -Protocol = require '../../protocol' - -class HostDevicesWithPathsCommand extends Command - execute: -> - this._send 'host:devices-l' - @parser.readAscii 4 - .then (reply) => - switch reply - when Protocol.OKAY - this._readDevices() - when Protocol.FAIL - @parser.readError() - else - @parser.unexpected reply, 'OKAY or FAIL' - - _readDevices: -> - @parser.readValue() - .then (value) => - this._parseDevices value - - _parseDevices: (value) -> - devices = [] - return devices unless value.length - for line in value.toString('ascii').split '\n' - if line - # For some reason, the columns are separated by spaces instead of tabs - [id, type, path] = line.split /\s+/ - devices.push id: id, type: type, path: path - return devices - -module.exports = HostDevicesWithPathsCommand diff --git a/src/adb/command/host/deviceswithpaths.js b/src/adb/command/host/deviceswithpaths.js new file mode 100644 index 00000000..ef13ded5 --- /dev/null +++ b/src/adb/command/host/deviceswithpaths.js @@ -0,0 +1,65 @@ +var Command, HostDevicesWithPathsCommand, Protocol, + extend = function(child, parent) { for (var key in parent) { if (hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; }, + hasProp = {}.hasOwnProperty; + +Command = require('../../command'); + +Protocol = require('../../protocol'); + +HostDevicesWithPathsCommand = (function(superClass) { + extend(HostDevicesWithPathsCommand, superClass); + + function HostDevicesWithPathsCommand() { + return HostDevicesWithPathsCommand.__super__.constructor.apply(this, arguments); + } + + HostDevicesWithPathsCommand.prototype.execute = function() { + this._send('host:devices-l'); + return this.parser.readAscii(4).then((function(_this) { + return function(reply) { + switch (reply) { + case Protocol.OKAY: + return _this._readDevices(); + case Protocol.FAIL: + return _this.parser.readError(); + default: + return _this.parser.unexpected(reply, 'OKAY or FAIL'); + } + }; + })(this)); + }; + + HostDevicesWithPathsCommand.prototype._readDevices = function() { + return this.parser.readValue().then((function(_this) { + return function(value) { + return _this._parseDevices(value); + }; + })(this)); + }; + + HostDevicesWithPathsCommand.prototype._parseDevices = function(value) { + var devices, i, id, len, line, path, ref, ref1, type; + devices = []; + if (!value.length) { + return devices; + } + ref = value.toString('ascii').split('\n'); + for (i = 0, len = ref.length; i < len; i++) { + line = ref[i]; + if (line) { + ref1 = line.split(/\s+/), id = ref1[0], type = ref1[1], path = ref1[2]; + devices.push({ + id: id, + type: type, + path: path + }); + } + } + return devices; + }; + + return HostDevicesWithPathsCommand; + +})(Command); + +module.exports = HostDevicesWithPathsCommand; diff --git a/src/adb/command/host/disconnect.coffee b/src/adb/command/host/disconnect.coffee deleted file mode 100644 index ae6cd884..00000000 --- a/src/adb/command/host/disconnect.coffee +++ /dev/null @@ -1,27 +0,0 @@ -Command = require '../../command' -Protocol = require '../../protocol' - -class DisconnectCommand extends Command - # Possible replies: - # "No such device 192.168.2.2:5555" - # "" - RE_OK = /^$/ - - execute: (host, port) -> - this._send "host:disconnect:#{host}:#{port}" - @parser.readAscii 4 - .then (reply) => - switch reply - when Protocol.OKAY - @parser.readValue() - .then (value) -> - if RE_OK.test value - "#{host}:#{port}" - else - throw new Error value.toString() - when Protocol.FAIL - @parser.readError() - else - @parser.unexpected reply, 'OKAY or FAIL' - -module.exports = DisconnectCommand diff --git a/src/adb/command/host/disconnect.js b/src/adb/command/host/disconnect.js new file mode 100644 index 00000000..dc3ca082 --- /dev/null +++ b/src/adb/command/host/disconnect.js @@ -0,0 +1,46 @@ +var Command, DisconnectCommand, Protocol, + extend = function(child, parent) { for (var key in parent) { if (hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; }, + hasProp = {}.hasOwnProperty; + +Command = require('../../command'); + +Protocol = require('../../protocol'); + +DisconnectCommand = (function(superClass) { + var RE_OK; + + extend(DisconnectCommand, superClass); + + function DisconnectCommand() { + return DisconnectCommand.__super__.constructor.apply(this, arguments); + } + + RE_OK = /^$/; + + DisconnectCommand.prototype.execute = function(host, port) { + this._send("host:disconnect:" + host + ":" + port); + return this.parser.readAscii(4).then((function(_this) { + return function(reply) { + switch (reply) { + case Protocol.OKAY: + return _this.parser.readValue().then(function(value) { + if (RE_OK.test(value)) { + return host + ":" + port; + } else { + throw new Error(value.toString()); + } + }); + case Protocol.FAIL: + return _this.parser.readError(); + default: + return _this.parser.unexpected(reply, 'OKAY or FAIL'); + } + }; + })(this)); + }; + + return DisconnectCommand; + +})(Command); + +module.exports = DisconnectCommand; diff --git a/src/adb/command/host/kill.coffee b/src/adb/command/host/kill.coffee deleted file mode 100644 index 3f09a246..00000000 --- a/src/adb/command/host/kill.coffee +++ /dev/null @@ -1,17 +0,0 @@ -Command = require '../../command' -Protocol = require '../../protocol' - -class HostKillCommand extends Command - execute: -> - this._send 'host:kill' - @parser.readAscii 4 - .then (reply) => - switch reply - when Protocol.OKAY - true - when Protocol.FAIL - @parser.readError() - else - @parser.unexpected reply, 'OKAY or FAIL' - -module.exports = HostKillCommand diff --git a/src/adb/command/host/kill.js b/src/adb/command/host/kill.js new file mode 100644 index 00000000..95376d5a --- /dev/null +++ b/src/adb/command/host/kill.js @@ -0,0 +1,36 @@ +var Command, HostKillCommand, Protocol, + extend = function(child, parent) { for (var key in parent) { if (hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; }, + hasProp = {}.hasOwnProperty; + +Command = require('../../command'); + +Protocol = require('../../protocol'); + +HostKillCommand = (function(superClass) { + extend(HostKillCommand, superClass); + + function HostKillCommand() { + return HostKillCommand.__super__.constructor.apply(this, arguments); + } + + HostKillCommand.prototype.execute = function() { + this._send('host:kill'); + return this.parser.readAscii(4).then((function(_this) { + return function(reply) { + switch (reply) { + case Protocol.OKAY: + return true; + case Protocol.FAIL: + return _this.parser.readError(); + default: + return _this.parser.unexpected(reply, 'OKAY or FAIL'); + } + }; + })(this)); + }; + + return HostKillCommand; + +})(Command); + +module.exports = HostKillCommand; diff --git a/src/adb/command/host/trackdevices.coffee b/src/adb/command/host/trackdevices.coffee deleted file mode 100644 index 9a7b5fa6..00000000 --- a/src/adb/command/host/trackdevices.coffee +++ /dev/null @@ -1,19 +0,0 @@ -Command = require '../../command' -Protocol = require '../../protocol' -Tracker = require '../../tracker' -HostDevicesCommand = require './devices' - -class HostTrackDevicesCommand extends HostDevicesCommand - execute: -> - this._send 'host:track-devices' - @parser.readAscii 4 - .then (reply) => - switch reply - when Protocol.OKAY - new Tracker this - when Protocol.FAIL - @parser.readError() - else - @parser.unexpected reply, 'OKAY or FAIL' - -module.exports = HostTrackDevicesCommand diff --git a/src/adb/command/host/trackdevices.js b/src/adb/command/host/trackdevices.js new file mode 100644 index 00000000..accc34a3 --- /dev/null +++ b/src/adb/command/host/trackdevices.js @@ -0,0 +1,40 @@ +var Command, HostDevicesCommand, HostTrackDevicesCommand, Protocol, Tracker, + extend = function(child, parent) { for (var key in parent) { if (hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; }, + hasProp = {}.hasOwnProperty; + +Command = require('../../command'); + +Protocol = require('../../protocol'); + +Tracker = require('../../tracker'); + +HostDevicesCommand = require('./devices'); + +HostTrackDevicesCommand = (function(superClass) { + extend(HostTrackDevicesCommand, superClass); + + function HostTrackDevicesCommand() { + return HostTrackDevicesCommand.__super__.constructor.apply(this, arguments); + } + + HostTrackDevicesCommand.prototype.execute = function() { + this._send('host:track-devices'); + return this.parser.readAscii(4).then((function(_this) { + return function(reply) { + switch (reply) { + case Protocol.OKAY: + return new Tracker(_this); + case Protocol.FAIL: + return _this.parser.readError(); + default: + return _this.parser.unexpected(reply, 'OKAY or FAIL'); + } + }; + })(this)); + }; + + return HostTrackDevicesCommand; + +})(HostDevicesCommand); + +module.exports = HostTrackDevicesCommand; diff --git a/src/adb/command/host/transport.coffee b/src/adb/command/host/transport.coffee deleted file mode 100644 index d4bdf29c..00000000 --- a/src/adb/command/host/transport.coffee +++ /dev/null @@ -1,17 +0,0 @@ -Command = require '../../command' -Protocol = require '../../protocol' - -class HostTransportCommand extends Command - execute: (serial) -> - this._send "host:transport:#{serial}" - @parser.readAscii 4 - .then (reply) => - switch reply - when Protocol.OKAY - true - when Protocol.FAIL - @parser.readError() - else - @parser.unexpected reply, 'OKAY or FAIL' - -module.exports = HostTransportCommand diff --git a/src/adb/command/host/transport.js b/src/adb/command/host/transport.js new file mode 100644 index 00000000..daa23f97 --- /dev/null +++ b/src/adb/command/host/transport.js @@ -0,0 +1,36 @@ +var Command, HostTransportCommand, Protocol, + extend = function(child, parent) { for (var key in parent) { if (hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; }, + hasProp = {}.hasOwnProperty; + +Command = require('../../command'); + +Protocol = require('../../protocol'); + +HostTransportCommand = (function(superClass) { + extend(HostTransportCommand, superClass); + + function HostTransportCommand() { + return HostTransportCommand.__super__.constructor.apply(this, arguments); + } + + HostTransportCommand.prototype.execute = function(serial) { + this._send("host:transport:" + serial); + return this.parser.readAscii(4).then((function(_this) { + return function(reply) { + switch (reply) { + case Protocol.OKAY: + return true; + case Protocol.FAIL: + return _this.parser.readError(); + default: + return _this.parser.unexpected(reply, 'OKAY or FAIL'); + } + }; + })(this)); + }; + + return HostTransportCommand; + +})(Command); + +module.exports = HostTransportCommand; diff --git a/src/adb/command/host/version.coffee b/src/adb/command/host/version.coffee deleted file mode 100644 index 36af6733..00000000 --- a/src/adb/command/host/version.coffee +++ /dev/null @@ -1,22 +0,0 @@ -Command = require '../../command' -Protocol = require '../../protocol' - -class HostVersionCommand extends Command - execute: -> - this._send 'host:version' - @parser.readAscii 4 - .then (reply) => - switch reply - when Protocol.OKAY - @parser.readValue() - .then (value) => - this._parseVersion value - when Protocol.FAIL - @parser.readError() - else - this._parseVersion reply - - _parseVersion: (version) -> - parseInt version, 16 - -module.exports = HostVersionCommand diff --git a/src/adb/command/host/version.js b/src/adb/command/host/version.js new file mode 100644 index 00000000..5cbfb334 --- /dev/null +++ b/src/adb/command/host/version.js @@ -0,0 +1,42 @@ +var Command, HostVersionCommand, Protocol, + extend = function(child, parent) { for (var key in parent) { if (hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; }, + hasProp = {}.hasOwnProperty; + +Command = require('../../command'); + +Protocol = require('../../protocol'); + +HostVersionCommand = (function(superClass) { + extend(HostVersionCommand, superClass); + + function HostVersionCommand() { + return HostVersionCommand.__super__.constructor.apply(this, arguments); + } + + HostVersionCommand.prototype.execute = function() { + this._send('host:version'); + return this.parser.readAscii(4).then((function(_this) { + return function(reply) { + switch (reply) { + case Protocol.OKAY: + return _this.parser.readValue().then(function(value) { + return _this._parseVersion(value); + }); + case Protocol.FAIL: + return _this.parser.readError(); + default: + return _this._parseVersion(reply); + } + }; + })(this)); + }; + + HostVersionCommand.prototype._parseVersion = function(version) { + return parseInt(version, 16); + }; + + return HostVersionCommand; + +})(Command); + +module.exports = HostVersionCommand; diff --git a/src/adb/connection.coffee b/src/adb/connection.coffee deleted file mode 100644 index 03ba07b8..00000000 --- a/src/adb/connection.coffee +++ /dev/null @@ -1,63 +0,0 @@ -Net = require 'net' -debug = require('debug')('adb:connection') -{EventEmitter} = require 'events' -{execFile} = require 'child_process' - -Parser = require './parser' -dump = require './dump' - -class Connection extends EventEmitter - constructor: (@options) -> - @socket = null - @parser = null - @triedStarting = false - - connect: -> - @socket = Net.connect @options - @socket.setNoDelay true - @parser = new Parser @socket - @socket.on 'connect', => - this.emit 'connect' - @socket.on 'end', => - this.emit 'end' - @socket.on 'drain', => - this.emit 'drain' - @socket.on 'timeout', => - this.emit 'timeout' - @socket.on 'error', (err) => - this._handleError err - @socket.on 'close', (hadError) => - this.emit 'close', hadError - return this - - end: -> - @socket.end() - return this - - write: (data, callback) -> - @socket.write dump(data), callback - return this - - startServer: (callback) -> - debug "Starting ADB server via '#{@options.bin} start-server'" - return this._exec ['start-server'], {}, callback - - _exec: (args, options, callback) -> - debug "CLI: #{@options.bin} #{args.join ' '}" - execFile @options.bin, args, options, callback - return this - - _handleError: (err) -> - if err.code is 'ECONNREFUSED' and not @triedStarting - debug "Connection was refused, let's try starting the server once" - @triedStarting = true - this.startServer (err) => - return this._handleError err if err - this.connect() - else - debug "Connection had an error: #{err.message}" - this.emit 'error', err - this.end() - return - -module.exports = Connection diff --git a/src/adb/connection.js b/src/adb/connection.js new file mode 100644 index 00000000..6a756a01 --- /dev/null +++ b/src/adb/connection.js @@ -0,0 +1,108 @@ +var Connection, EventEmitter, Net, Parser, debug, dump, execFile, + extend = function(child, parent) { for (var key in parent) { if (hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; }, + hasProp = {}.hasOwnProperty; + +Net = require('net'); + +debug = require('debug')('adb:connection'); + +EventEmitter = require('events').EventEmitter; + +execFile = require('child_process').execFile; + +Parser = require('./parser'); + +dump = require('./dump'); + +Connection = (function(superClass) { + extend(Connection, superClass); + + function Connection(options1) { + this.options = options1; + this.socket = null; + this.parser = null; + this.triedStarting = false; + } + + Connection.prototype.connect = function() { + this.socket = Net.connect(this.options); + this.socket.setNoDelay(true); + this.parser = new Parser(this.socket); + this.socket.on('connect', (function(_this) { + return function() { + return _this.emit('connect'); + }; + })(this)); + this.socket.on('end', (function(_this) { + return function() { + return _this.emit('end'); + }; + })(this)); + this.socket.on('drain', (function(_this) { + return function() { + return _this.emit('drain'); + }; + })(this)); + this.socket.on('timeout', (function(_this) { + return function() { + return _this.emit('timeout'); + }; + })(this)); + this.socket.on('error', (function(_this) { + return function(err) { + return _this._handleError(err); + }; + })(this)); + this.socket.on('close', (function(_this) { + return function(hadError) { + return _this.emit('close', hadError); + }; + })(this)); + return this; + }; + + Connection.prototype.end = function() { + this.socket.end(); + return this; + }; + + Connection.prototype.write = function(data, callback) { + this.socket.write(dump(data), callback); + return this; + }; + + Connection.prototype.startServer = function(callback) { + debug("Starting ADB server via '" + this.options.bin + " start-server'"); + return this._exec(['start-server'], {}, callback); + }; + + Connection.prototype._exec = function(args, options, callback) { + debug("CLI: " + this.options.bin + " " + (args.join(' '))); + execFile(this.options.bin, args, options, callback); + return this; + }; + + Connection.prototype._handleError = function(err) { + if (err.code === 'ECONNREFUSED' && !this.triedStarting) { + debug("Connection was refused, let's try starting the server once"); + this.triedStarting = true; + this.startServer((function(_this) { + return function(err) { + if (err) { + return _this._handleError(err); + } + return _this.connect(); + }; + })(this)); + } else { + debug("Connection had an error: " + err.message); + this.emit('error', err); + this.end(); + } + }; + + return Connection; + +})(EventEmitter); + +module.exports = Connection; diff --git a/src/adb/dump.coffee b/src/adb/dump.coffee deleted file mode 100644 index 9152fbf8..00000000 --- a/src/adb/dump.coffee +++ /dev/null @@ -1,9 +0,0 @@ -fs = require 'fs' - -if process.env.ADBKIT_DUMP - out = fs.createWriteStream 'adbkit.dump' - module.exports = (chunk) -> - out.write chunk - chunk -else - module.exports = (chunk) -> chunk diff --git a/src/adb/dump.js b/src/adb/dump.js new file mode 100644 index 00000000..a82fc01f --- /dev/null +++ b/src/adb/dump.js @@ -0,0 +1,15 @@ +var fs, out; + +fs = require('fs'); + +if (process.env.ADBKIT_DUMP) { + out = fs.createWriteStream('adbkit.dump'); + module.exports = function(chunk) { + out.write(chunk); + return chunk; + }; +} else { + module.exports = function(chunk) { + return chunk; + }; +} diff --git a/src/adb/framebuffer/rgbtransform.coffee b/src/adb/framebuffer/rgbtransform.coffee deleted file mode 100644 index b01d6a48..00000000 --- a/src/adb/framebuffer/rgbtransform.coffee +++ /dev/null @@ -1,41 +0,0 @@ -Assert = require 'assert' -Stream = require 'stream' - -class RgbTransform extends Stream.Transform - constructor: (@meta, options) -> - @_buffer = new Buffer '' - Assert.ok (@meta.bpp is 24 or @meta.bpp is 32), - 'Only 24-bit and 32-bit raw images with 8-bits per color are supported' - @_r_pos = @meta.red_offset / 8 - @_g_pos = @meta.green_offset / 8 - @_b_pos = @meta.blue_offset / 8 - @_a_pos = @meta.alpha_offset / 8 - @_pixel_bytes = @meta.bpp / 8 - super options - - _transform: (chunk, encoding, done) -> - if @_buffer.length - @_buffer = Buffer.concat [@_buffer, chunk], @_buffer.length + chunk.length - else - @_buffer = chunk - sourceCursor = 0 - targetCursor = 0 - target = if @_pixel_bytes is 3 \ - then @_buffer - else new Buffer Math.max 4, chunk.length / @_pixel_bytes * 3 - while @_buffer.length - sourceCursor >= @_pixel_bytes - r = @_buffer[sourceCursor + @_r_pos] - g = @_buffer[sourceCursor + @_g_pos] - b = @_buffer[sourceCursor + @_b_pos] - target[targetCursor + 0] = r - target[targetCursor + 1] = g - target[targetCursor + 2] = b - sourceCursor += @_pixel_bytes - targetCursor += 3 - if targetCursor - this.push target.slice 0, targetCursor - @_buffer = @_buffer.slice sourceCursor - done() - return - -module.exports = RgbTransform diff --git a/src/adb/framebuffer/rgbtransform.js b/src/adb/framebuffer/rgbtransform.js new file mode 100644 index 00000000..86f99a11 --- /dev/null +++ b/src/adb/framebuffer/rgbtransform.js @@ -0,0 +1,55 @@ +var Assert, RgbTransform, Stream, + extend = function(child, parent) { for (var key in parent) { if (hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; }, + hasProp = {}.hasOwnProperty; + +Assert = require('assert'); + +Stream = require('stream'); + +RgbTransform = (function(superClass) { + extend(RgbTransform, superClass); + + function RgbTransform(meta, options) { + this.meta = meta; + this._buffer = new Buffer(''); + Assert.ok(this.meta.bpp === 24 || this.meta.bpp === 32, 'Only 24-bit and 32-bit raw images with 8-bits per color are supported'); + this._r_pos = this.meta.red_offset / 8; + this._g_pos = this.meta.green_offset / 8; + this._b_pos = this.meta.blue_offset / 8; + this._a_pos = this.meta.alpha_offset / 8; + this._pixel_bytes = this.meta.bpp / 8; + RgbTransform.__super__.constructor.call(this, options); + } + + RgbTransform.prototype._transform = function(chunk, encoding, done) { + var b, g, r, sourceCursor, target, targetCursor; + if (this._buffer.length) { + this._buffer = Buffer.concat([this._buffer, chunk], this._buffer.length + chunk.length); + } else { + this._buffer = chunk; + } + sourceCursor = 0; + targetCursor = 0; + target = this._pixel_bytes === 3 ? this._buffer : new Buffer(Math.max(4, chunk.length / this._pixel_bytes * 3)); + while (this._buffer.length - sourceCursor >= this._pixel_bytes) { + r = this._buffer[sourceCursor + this._r_pos]; + g = this._buffer[sourceCursor + this._g_pos]; + b = this._buffer[sourceCursor + this._b_pos]; + target[targetCursor + 0] = r; + target[targetCursor + 1] = g; + target[targetCursor + 2] = b; + sourceCursor += this._pixel_bytes; + targetCursor += 3; + } + if (targetCursor) { + this.push(target.slice(0, targetCursor)); + this._buffer = this._buffer.slice(sourceCursor); + } + done(); + }; + + return RgbTransform; + +})(Stream.Transform); + +module.exports = RgbTransform; diff --git a/src/adb/keycode.coffee b/src/adb/keycode.coffee deleted file mode 100644 index 3eec1d0f..00000000 --- a/src/adb/keycode.coffee +++ /dev/null @@ -1,227 +0,0 @@ -# Generated by `grunt keycode` on Tue, 26 Nov 2013 08:02:49 GMT -# KeyEvent.java Copyright (C) 2006 The Android Open Source Project - -module.exports = - KEYCODE_UNKNOWN: 0 - KEYCODE_SOFT_LEFT: 1 - KEYCODE_SOFT_RIGHT: 2 - KEYCODE_HOME: 3 - KEYCODE_BACK: 4 - KEYCODE_CALL: 5 - KEYCODE_ENDCALL: 6 - KEYCODE_0: 7 - KEYCODE_1: 8 - KEYCODE_2: 9 - KEYCODE_3: 10 - KEYCODE_4: 11 - KEYCODE_5: 12 - KEYCODE_6: 13 - KEYCODE_7: 14 - KEYCODE_8: 15 - KEYCODE_9: 16 - KEYCODE_STAR: 17 - KEYCODE_POUND: 18 - KEYCODE_DPAD_UP: 19 - KEYCODE_DPAD_DOWN: 20 - KEYCODE_DPAD_LEFT: 21 - KEYCODE_DPAD_RIGHT: 22 - KEYCODE_DPAD_CENTER: 23 - KEYCODE_VOLUME_UP: 24 - KEYCODE_VOLUME_DOWN: 25 - KEYCODE_POWER: 26 - KEYCODE_CAMERA: 27 - KEYCODE_CLEAR: 28 - KEYCODE_A: 29 - KEYCODE_B: 30 - KEYCODE_C: 31 - KEYCODE_D: 32 - KEYCODE_E: 33 - KEYCODE_F: 34 - KEYCODE_G: 35 - KEYCODE_H: 36 - KEYCODE_I: 37 - KEYCODE_J: 38 - KEYCODE_K: 39 - KEYCODE_L: 40 - KEYCODE_M: 41 - KEYCODE_N: 42 - KEYCODE_O: 43 - KEYCODE_P: 44 - KEYCODE_Q: 45 - KEYCODE_R: 46 - KEYCODE_S: 47 - KEYCODE_T: 48 - KEYCODE_U: 49 - KEYCODE_V: 50 - KEYCODE_W: 51 - KEYCODE_X: 52 - KEYCODE_Y: 53 - KEYCODE_Z: 54 - KEYCODE_COMMA: 55 - KEYCODE_PERIOD: 56 - KEYCODE_ALT_LEFT: 57 - KEYCODE_ALT_RIGHT: 58 - KEYCODE_SHIFT_LEFT: 59 - KEYCODE_SHIFT_RIGHT: 60 - KEYCODE_TAB: 61 - KEYCODE_SPACE: 62 - KEYCODE_SYM: 63 - KEYCODE_EXPLORER: 64 - KEYCODE_ENVELOPE: 65 - KEYCODE_ENTER: 66 - KEYCODE_DEL: 67 - KEYCODE_GRAVE: 68 - KEYCODE_MINUS: 69 - KEYCODE_EQUALS: 70 - KEYCODE_LEFT_BRACKET: 71 - KEYCODE_RIGHT_BRACKET: 72 - KEYCODE_BACKSLASH: 73 - KEYCODE_SEMICOLON: 74 - KEYCODE_APOSTROPHE: 75 - KEYCODE_SLASH: 76 - KEYCODE_AT: 77 - KEYCODE_NUM: 78 - KEYCODE_HEADSETHOOK: 79 - KEYCODE_FOCUS: 80 - KEYCODE_PLUS: 81 - KEYCODE_MENU: 82 - KEYCODE_NOTIFICATION: 83 - KEYCODE_SEARCH: 84 - KEYCODE_MEDIA_PLAY_PAUSE: 85 - KEYCODE_MEDIA_STOP: 86 - KEYCODE_MEDIA_NEXT: 87 - KEYCODE_MEDIA_PREVIOUS: 88 - KEYCODE_MEDIA_REWIND: 89 - KEYCODE_MEDIA_FAST_FORWARD: 90 - KEYCODE_MUTE: 91 - KEYCODE_PAGE_UP: 92 - KEYCODE_PAGE_DOWN: 93 - KEYCODE_PICTSYMBOLS: 94 - KEYCODE_SWITCH_CHARSET: 95 - KEYCODE_BUTTON_A: 96 - KEYCODE_BUTTON_B: 97 - KEYCODE_BUTTON_C: 98 - KEYCODE_BUTTON_X: 99 - KEYCODE_BUTTON_Y: 100 - KEYCODE_BUTTON_Z: 101 - KEYCODE_BUTTON_L1: 102 - KEYCODE_BUTTON_R1: 103 - KEYCODE_BUTTON_L2: 104 - KEYCODE_BUTTON_R2: 105 - KEYCODE_BUTTON_THUMBL: 106 - KEYCODE_BUTTON_THUMBR: 107 - KEYCODE_BUTTON_START: 108 - KEYCODE_BUTTON_SELECT: 109 - KEYCODE_BUTTON_MODE: 110 - KEYCODE_ESCAPE: 111 - KEYCODE_FORWARD_DEL: 112 - KEYCODE_CTRL_LEFT: 113 - KEYCODE_CTRL_RIGHT: 114 - KEYCODE_CAPS_LOCK: 115 - KEYCODE_SCROLL_LOCK: 116 - KEYCODE_META_LEFT: 117 - KEYCODE_META_RIGHT: 118 - KEYCODE_FUNCTION: 119 - KEYCODE_SYSRQ: 120 - KEYCODE_BREAK: 121 - KEYCODE_MOVE_HOME: 122 - KEYCODE_MOVE_END: 123 - KEYCODE_INSERT: 124 - KEYCODE_FORWARD: 125 - KEYCODE_MEDIA_PLAY: 126 - KEYCODE_MEDIA_PAUSE: 127 - KEYCODE_MEDIA_CLOSE: 128 - KEYCODE_MEDIA_EJECT: 129 - KEYCODE_MEDIA_RECORD: 130 - KEYCODE_F1: 131 - KEYCODE_F2: 132 - KEYCODE_F3: 133 - KEYCODE_F4: 134 - KEYCODE_F5: 135 - KEYCODE_F6: 136 - KEYCODE_F7: 137 - KEYCODE_F8: 138 - KEYCODE_F9: 139 - KEYCODE_F10: 140 - KEYCODE_F11: 141 - KEYCODE_F12: 142 - KEYCODE_NUM_LOCK: 143 - KEYCODE_NUMPAD_0: 144 - KEYCODE_NUMPAD_1: 145 - KEYCODE_NUMPAD_2: 146 - KEYCODE_NUMPAD_3: 147 - KEYCODE_NUMPAD_4: 148 - KEYCODE_NUMPAD_5: 149 - KEYCODE_NUMPAD_6: 150 - KEYCODE_NUMPAD_7: 151 - KEYCODE_NUMPAD_8: 152 - KEYCODE_NUMPAD_9: 153 - KEYCODE_NUMPAD_DIVIDE: 154 - KEYCODE_NUMPAD_MULTIPLY: 155 - KEYCODE_NUMPAD_SUBTRACT: 156 - KEYCODE_NUMPAD_ADD: 157 - KEYCODE_NUMPAD_DOT: 158 - KEYCODE_NUMPAD_COMMA: 159 - KEYCODE_NUMPAD_ENTER: 160 - KEYCODE_NUMPAD_EQUALS: 161 - KEYCODE_NUMPAD_LEFT_PAREN: 162 - KEYCODE_NUMPAD_RIGHT_PAREN: 163 - KEYCODE_VOLUME_MUTE: 164 - KEYCODE_INFO: 165 - KEYCODE_CHANNEL_UP: 166 - KEYCODE_CHANNEL_DOWN: 167 - KEYCODE_ZOOM_IN: 168 - KEYCODE_ZOOM_OUT: 169 - KEYCODE_TV: 170 - KEYCODE_WINDOW: 171 - KEYCODE_GUIDE: 172 - KEYCODE_DVR: 173 - KEYCODE_BOOKMARK: 174 - KEYCODE_CAPTIONS: 175 - KEYCODE_SETTINGS: 176 - KEYCODE_TV_POWER: 177 - KEYCODE_TV_INPUT: 178 - KEYCODE_STB_POWER: 179 - KEYCODE_STB_INPUT: 180 - KEYCODE_AVR_POWER: 181 - KEYCODE_AVR_INPUT: 182 - KEYCODE_PROG_RED: 183 - KEYCODE_PROG_GREEN: 184 - KEYCODE_PROG_YELLOW: 185 - KEYCODE_PROG_BLUE: 186 - KEYCODE_APP_SWITCH: 187 - KEYCODE_BUTTON_1: 188 - KEYCODE_BUTTON_2: 189 - KEYCODE_BUTTON_3: 190 - KEYCODE_BUTTON_4: 191 - KEYCODE_BUTTON_5: 192 - KEYCODE_BUTTON_6: 193 - KEYCODE_BUTTON_7: 194 - KEYCODE_BUTTON_8: 195 - KEYCODE_BUTTON_9: 196 - KEYCODE_BUTTON_10: 197 - KEYCODE_BUTTON_11: 198 - KEYCODE_BUTTON_12: 199 - KEYCODE_BUTTON_13: 200 - KEYCODE_BUTTON_14: 201 - KEYCODE_BUTTON_15: 202 - KEYCODE_BUTTON_16: 203 - KEYCODE_LANGUAGE_SWITCH: 204 - KEYCODE_MANNER_MODE: 205 - KEYCODE_3D_MODE: 206 - KEYCODE_CONTACTS: 207 - KEYCODE_CALENDAR: 208 - KEYCODE_MUSIC: 209 - KEYCODE_CALCULATOR: 210 - KEYCODE_ZENKAKU_HANKAKU: 211 - KEYCODE_EISU: 212 - KEYCODE_MUHENKAN: 213 - KEYCODE_HENKAN: 214 - KEYCODE_KATAKANA_HIRAGANA: 215 - KEYCODE_YEN: 216 - KEYCODE_RO: 217 - KEYCODE_KANA: 218 - KEYCODE_ASSIST: 219 - KEYCODE_BRIGHTNESS_DOWN: 220 - KEYCODE_BRIGHTNESS_UP: 221 - KEYCODE_MEDIA_AUDIO_TRACK: 222 diff --git a/src/adb/keycode.js b/src/adb/keycode.js new file mode 100644 index 00000000..07d75218 --- /dev/null +++ b/src/adb/keycode.js @@ -0,0 +1,225 @@ +module.exports = { + KEYCODE_UNKNOWN: 0, + KEYCODE_SOFT_LEFT: 1, + KEYCODE_SOFT_RIGHT: 2, + KEYCODE_HOME: 3, + KEYCODE_BACK: 4, + KEYCODE_CALL: 5, + KEYCODE_ENDCALL: 6, + KEYCODE_0: 7, + KEYCODE_1: 8, + KEYCODE_2: 9, + KEYCODE_3: 10, + KEYCODE_4: 11, + KEYCODE_5: 12, + KEYCODE_6: 13, + KEYCODE_7: 14, + KEYCODE_8: 15, + KEYCODE_9: 16, + KEYCODE_STAR: 17, + KEYCODE_POUND: 18, + KEYCODE_DPAD_UP: 19, + KEYCODE_DPAD_DOWN: 20, + KEYCODE_DPAD_LEFT: 21, + KEYCODE_DPAD_RIGHT: 22, + KEYCODE_DPAD_CENTER: 23, + KEYCODE_VOLUME_UP: 24, + KEYCODE_VOLUME_DOWN: 25, + KEYCODE_POWER: 26, + KEYCODE_CAMERA: 27, + KEYCODE_CLEAR: 28, + KEYCODE_A: 29, + KEYCODE_B: 30, + KEYCODE_C: 31, + KEYCODE_D: 32, + KEYCODE_E: 33, + KEYCODE_F: 34, + KEYCODE_G: 35, + KEYCODE_H: 36, + KEYCODE_I: 37, + KEYCODE_J: 38, + KEYCODE_K: 39, + KEYCODE_L: 40, + KEYCODE_M: 41, + KEYCODE_N: 42, + KEYCODE_O: 43, + KEYCODE_P: 44, + KEYCODE_Q: 45, + KEYCODE_R: 46, + KEYCODE_S: 47, + KEYCODE_T: 48, + KEYCODE_U: 49, + KEYCODE_V: 50, + KEYCODE_W: 51, + KEYCODE_X: 52, + KEYCODE_Y: 53, + KEYCODE_Z: 54, + KEYCODE_COMMA: 55, + KEYCODE_PERIOD: 56, + KEYCODE_ALT_LEFT: 57, + KEYCODE_ALT_RIGHT: 58, + KEYCODE_SHIFT_LEFT: 59, + KEYCODE_SHIFT_RIGHT: 60, + KEYCODE_TAB: 61, + KEYCODE_SPACE: 62, + KEYCODE_SYM: 63, + KEYCODE_EXPLORER: 64, + KEYCODE_ENVELOPE: 65, + KEYCODE_ENTER: 66, + KEYCODE_DEL: 67, + KEYCODE_GRAVE: 68, + KEYCODE_MINUS: 69, + KEYCODE_EQUALS: 70, + KEYCODE_LEFT_BRACKET: 71, + KEYCODE_RIGHT_BRACKET: 72, + KEYCODE_BACKSLASH: 73, + KEYCODE_SEMICOLON: 74, + KEYCODE_APOSTROPHE: 75, + KEYCODE_SLASH: 76, + KEYCODE_AT: 77, + KEYCODE_NUM: 78, + KEYCODE_HEADSETHOOK: 79, + KEYCODE_FOCUS: 80, + KEYCODE_PLUS: 81, + KEYCODE_MENU: 82, + KEYCODE_NOTIFICATION: 83, + KEYCODE_SEARCH: 84, + KEYCODE_MEDIA_PLAY_PAUSE: 85, + KEYCODE_MEDIA_STOP: 86, + KEYCODE_MEDIA_NEXT: 87, + KEYCODE_MEDIA_PREVIOUS: 88, + KEYCODE_MEDIA_REWIND: 89, + KEYCODE_MEDIA_FAST_FORWARD: 90, + KEYCODE_MUTE: 91, + KEYCODE_PAGE_UP: 92, + KEYCODE_PAGE_DOWN: 93, + KEYCODE_PICTSYMBOLS: 94, + KEYCODE_SWITCH_CHARSET: 95, + KEYCODE_BUTTON_A: 96, + KEYCODE_BUTTON_B: 97, + KEYCODE_BUTTON_C: 98, + KEYCODE_BUTTON_X: 99, + KEYCODE_BUTTON_Y: 100, + KEYCODE_BUTTON_Z: 101, + KEYCODE_BUTTON_L1: 102, + KEYCODE_BUTTON_R1: 103, + KEYCODE_BUTTON_L2: 104, + KEYCODE_BUTTON_R2: 105, + KEYCODE_BUTTON_THUMBL: 106, + KEYCODE_BUTTON_THUMBR: 107, + KEYCODE_BUTTON_START: 108, + KEYCODE_BUTTON_SELECT: 109, + KEYCODE_BUTTON_MODE: 110, + KEYCODE_ESCAPE: 111, + KEYCODE_FORWARD_DEL: 112, + KEYCODE_CTRL_LEFT: 113, + KEYCODE_CTRL_RIGHT: 114, + KEYCODE_CAPS_LOCK: 115, + KEYCODE_SCROLL_LOCK: 116, + KEYCODE_META_LEFT: 117, + KEYCODE_META_RIGHT: 118, + KEYCODE_FUNCTION: 119, + KEYCODE_SYSRQ: 120, + KEYCODE_BREAK: 121, + KEYCODE_MOVE_HOME: 122, + KEYCODE_MOVE_END: 123, + KEYCODE_INSERT: 124, + KEYCODE_FORWARD: 125, + KEYCODE_MEDIA_PLAY: 126, + KEYCODE_MEDIA_PAUSE: 127, + KEYCODE_MEDIA_CLOSE: 128, + KEYCODE_MEDIA_EJECT: 129, + KEYCODE_MEDIA_RECORD: 130, + KEYCODE_F1: 131, + KEYCODE_F2: 132, + KEYCODE_F3: 133, + KEYCODE_F4: 134, + KEYCODE_F5: 135, + KEYCODE_F6: 136, + KEYCODE_F7: 137, + KEYCODE_F8: 138, + KEYCODE_F9: 139, + KEYCODE_F10: 140, + KEYCODE_F11: 141, + KEYCODE_F12: 142, + KEYCODE_NUM_LOCK: 143, + KEYCODE_NUMPAD_0: 144, + KEYCODE_NUMPAD_1: 145, + KEYCODE_NUMPAD_2: 146, + KEYCODE_NUMPAD_3: 147, + KEYCODE_NUMPAD_4: 148, + KEYCODE_NUMPAD_5: 149, + KEYCODE_NUMPAD_6: 150, + KEYCODE_NUMPAD_7: 151, + KEYCODE_NUMPAD_8: 152, + KEYCODE_NUMPAD_9: 153, + KEYCODE_NUMPAD_DIVIDE: 154, + KEYCODE_NUMPAD_MULTIPLY: 155, + KEYCODE_NUMPAD_SUBTRACT: 156, + KEYCODE_NUMPAD_ADD: 157, + KEYCODE_NUMPAD_DOT: 158, + KEYCODE_NUMPAD_COMMA: 159, + KEYCODE_NUMPAD_ENTER: 160, + KEYCODE_NUMPAD_EQUALS: 161, + KEYCODE_NUMPAD_LEFT_PAREN: 162, + KEYCODE_NUMPAD_RIGHT_PAREN: 163, + KEYCODE_VOLUME_MUTE: 164, + KEYCODE_INFO: 165, + KEYCODE_CHANNEL_UP: 166, + KEYCODE_CHANNEL_DOWN: 167, + KEYCODE_ZOOM_IN: 168, + KEYCODE_ZOOM_OUT: 169, + KEYCODE_TV: 170, + KEYCODE_WINDOW: 171, + KEYCODE_GUIDE: 172, + KEYCODE_DVR: 173, + KEYCODE_BOOKMARK: 174, + KEYCODE_CAPTIONS: 175, + KEYCODE_SETTINGS: 176, + KEYCODE_TV_POWER: 177, + KEYCODE_TV_INPUT: 178, + KEYCODE_STB_POWER: 179, + KEYCODE_STB_INPUT: 180, + KEYCODE_AVR_POWER: 181, + KEYCODE_AVR_INPUT: 182, + KEYCODE_PROG_RED: 183, + KEYCODE_PROG_GREEN: 184, + KEYCODE_PROG_YELLOW: 185, + KEYCODE_PROG_BLUE: 186, + KEYCODE_APP_SWITCH: 187, + KEYCODE_BUTTON_1: 188, + KEYCODE_BUTTON_2: 189, + KEYCODE_BUTTON_3: 190, + KEYCODE_BUTTON_4: 191, + KEYCODE_BUTTON_5: 192, + KEYCODE_BUTTON_6: 193, + KEYCODE_BUTTON_7: 194, + KEYCODE_BUTTON_8: 195, + KEYCODE_BUTTON_9: 196, + KEYCODE_BUTTON_10: 197, + KEYCODE_BUTTON_11: 198, + KEYCODE_BUTTON_12: 199, + KEYCODE_BUTTON_13: 200, + KEYCODE_BUTTON_14: 201, + KEYCODE_BUTTON_15: 202, + KEYCODE_BUTTON_16: 203, + KEYCODE_LANGUAGE_SWITCH: 204, + KEYCODE_MANNER_MODE: 205, + KEYCODE_3D_MODE: 206, + KEYCODE_CONTACTS: 207, + KEYCODE_CALENDAR: 208, + KEYCODE_MUSIC: 209, + KEYCODE_CALCULATOR: 210, + KEYCODE_ZENKAKU_HANKAKU: 211, + KEYCODE_EISU: 212, + KEYCODE_MUHENKAN: 213, + KEYCODE_HENKAN: 214, + KEYCODE_KATAKANA_HIRAGANA: 215, + KEYCODE_YEN: 216, + KEYCODE_RO: 217, + KEYCODE_KANA: 218, + KEYCODE_ASSIST: 219, + KEYCODE_BRIGHTNESS_DOWN: 220, + KEYCODE_BRIGHTNESS_UP: 221, + KEYCODE_MEDIA_AUDIO_TRACK: 222 +}; diff --git a/src/adb/linetransform.coffee b/src/adb/linetransform.coffee deleted file mode 100644 index 9b95d35e..00000000 --- a/src/adb/linetransform.coffee +++ /dev/null @@ -1,76 +0,0 @@ -Stream = require 'stream' - -class LineTransform extends Stream.Transform - constructor: (options = {}) -> - @savedR = null - @autoDetect = options.autoDetect or false - @transformNeeded = true - @skipBytes = 0 - delete options.autoDetect - super options - - _nullTransform: (chunk, encoding, done) -> - this.push chunk - done() - return - - # Sadly, the ADB shell is not very smart. It automatically converts every - # 0x0a ('\n') it can find to 0x0d 0x0a ('\r\n'). This also applies to binary - # content. We could get rid of this behavior by setting `stty raw`, but - # unfortunately it's not available by default (you'd have to install busybox) - # or something similar. On the up side, it really does do this for all line - # feeds, so a simple transform works fine. - _transform: (chunk, encoding, done) -> - # If auto detection is enabled, check the first byte. The first two - # bytes must be either 0x0a .. or 0x0d 0x0a. This causes a need to skip - # either one or two bytes. The autodetection runs only once. - if @autoDetect - if chunk[0] is 0x0a - @transformNeeded = false - @skipBytes = 1 - else - @skipBytes = 2 - @autoDetect = false - - # It's technically possible that we may receive the first two bytes - # in two separate chunks. That's why the autodetect bytes are skipped - # here, separately. - if @skipBytes - skip = Math.min chunk.length, @skipBytes - chunk = chunk.slice skip - @skipBytes -= skip - - # It's possible that skipping bytes has created an empty chunk. - return done() unless chunk.length - - # At this point all bytes that needed to be skipped should have been - # skipped. If transform is not needed, shortcut to null transform. - return this._nullTransform(chunk, encoding, done) unless @transformNeeded - - # Ok looks like we're transforming. - lo = 0 - hi = 0 - if @savedR - this.push @savedR unless chunk[0] is 0x0a - @savedR = null - last = chunk.length - 1 - while hi <= last - if chunk[hi] is 0x0d - if hi is last - @savedR = chunk.slice last - break # Stop hi from incrementing, we want to skip the last byte. - else if chunk[hi + 1] is 0x0a - this.push chunk.slice lo, hi - lo = hi + 1 - hi += 1 - unless hi is lo - this.push chunk.slice lo, hi - done() - return - - # When the stream ends on an '\r', output it as-is (assume binary data). - _flush: (done) -> - this.push @savedR if @savedR - done() - -module.exports = LineTransform diff --git a/src/adb/linetransform.js b/src/adb/linetransform.js new file mode 100644 index 00000000..7bb5d6a6 --- /dev/null +++ b/src/adb/linetransform.js @@ -0,0 +1,87 @@ +var LineTransform, Stream, + extend = function(child, parent) { for (var key in parent) { if (hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; }, + hasProp = {}.hasOwnProperty; + +Stream = require('stream'); + +LineTransform = (function(superClass) { + extend(LineTransform, superClass); + + function LineTransform(options) { + if (options == null) { + options = {}; + } + this.savedR = null; + this.autoDetect = options.autoDetect || false; + this.transformNeeded = true; + this.skipBytes = 0; + delete options.autoDetect; + LineTransform.__super__.constructor.call(this, options); + } + + LineTransform.prototype._nullTransform = function(chunk, encoding, done) { + this.push(chunk); + done(); + }; + + LineTransform.prototype._transform = function(chunk, encoding, done) { + var hi, last, lo, skip; + if (this.autoDetect) { + if (chunk[0] === 0x0a) { + this.transformNeeded = false; + this.skipBytes = 1; + } else { + this.skipBytes = 2; + } + this.autoDetect = false; + } + if (this.skipBytes) { + skip = Math.min(chunk.length, this.skipBytes); + chunk = chunk.slice(skip); + this.skipBytes -= skip; + } + if (!chunk.length) { + return done(); + } + if (!this.transformNeeded) { + return this._nullTransform(chunk, encoding, done); + } + lo = 0; + hi = 0; + if (this.savedR) { + if (chunk[0] !== 0x0a) { + this.push(this.savedR); + } + this.savedR = null; + } + last = chunk.length - 1; + while (hi <= last) { + if (chunk[hi] === 0x0d) { + if (hi === last) { + this.savedR = chunk.slice(last); + break; + } else if (chunk[hi + 1] === 0x0a) { + this.push(chunk.slice(lo, hi)); + lo = hi + 1; + } + } + hi += 1; + } + if (hi !== lo) { + this.push(chunk.slice(lo, hi)); + } + done(); + }; + + LineTransform.prototype._flush = function(done) { + if (this.savedR) { + this.push(this.savedR); + } + return done(); + }; + + return LineTransform; + +})(Stream.Transform); + +module.exports = LineTransform; diff --git a/src/adb/parser.coffee b/src/adb/parser.coffee deleted file mode 100644 index 02991ad5..00000000 --- a/src/adb/parser.coffee +++ /dev/null @@ -1,201 +0,0 @@ -Promise = require 'bluebird' - -Protocol = require './protocol' - -class Parser - constructor: (@stream) -> - @ended = false - - end: -> - return Promise.resolve(true) if @ended - - resolver = Promise.defer() - - tryRead = => - while @stream.read() - continue - return - - @stream.on 'readable', tryRead - - @stream.on 'error', errorListener = (err) -> - resolver.reject err - - @stream.on 'end', endListener = => - @ended = true - resolver.resolve true - - @stream.read(0) - @stream.end() - - resolver.promise.cancellable().finally => - @stream.removeListener 'readable', tryRead - @stream.removeListener 'error', errorListener - @stream.removeListener 'end', endListener - - raw: -> - @stream - - readAll: -> - all = new Buffer 0 - resolver = Promise.defer() - - tryRead = => - while chunk = @stream.read() - all = Buffer.concat [all, chunk] - resolver.resolve all if @ended - - @stream.on 'readable', tryRead - - @stream.on 'error', errorListener = (err) -> - resolver.reject err - - @stream.on 'end', endListener = => - @ended = true - resolver.resolve all - - tryRead() - - resolver.promise.cancellable().finally => - @stream.removeListener 'readable', tryRead - @stream.removeListener 'error', errorListener - @stream.removeListener 'end', endListener - - readAscii: (howMany) -> - this.readBytes howMany - .then (chunk) -> - chunk.toString 'ascii' - - readBytes: (howMany) -> - resolver = Promise.defer() - - tryRead = => - if howMany - if chunk = @stream.read howMany - # If the stream ends while still having unread bytes, the read call - # will ignore the limit and just return what it's got. - howMany -= chunk.length - return resolver.resolve chunk if howMany is 0 - resolver.reject new Parser.PrematureEOFError howMany if @ended - else - resolver.resolve new Buffer 0 - - endListener = => - @ended = true - resolver.reject new Parser.PrematureEOFError howMany - - errorListener = (err) -> - resolver.reject err - - @stream.on 'readable', tryRead - @stream.on 'error', errorListener - @stream.on 'end', endListener - - tryRead() - - resolver.promise.cancellable().finally => - @stream.removeListener 'readable', tryRead - @stream.removeListener 'error', errorListener - @stream.removeListener 'end', endListener - - readByteFlow: (howMany, targetStream) -> - resolver = Promise.defer() - - tryRead = => - if howMany - # Try to get the exact amount we need first. If unsuccessful, take - # whatever is available, which will be less than the needed amount. - while chunk = @stream.read(howMany) or @stream.read() - howMany -= chunk.length - targetStream.write chunk - return resolver.resolve() if howMany is 0 - resolver.reject new Parser.PrematureEOFError howMany if @ended - else - resolver.resolve() - - endListener = => - @ended = true - resolver.reject new Parser.PrematureEOFError howMany - - errorListener = (err) -> - resolver.reject err - - @stream.on 'readable', tryRead - @stream.on 'error', errorListener - @stream.on 'end', endListener - - tryRead() - - resolver.promise.cancellable().finally => - @stream.removeListener 'readable', tryRead - @stream.removeListener 'error', errorListener - @stream.removeListener 'end', endListener - - readError: -> - this.readValue() - .then (value) -> - Promise.reject new Parser.FailError value.toString() - - readValue: -> - this.readAscii 4 - .then (value) => - length = Protocol.decodeLength value - this.readBytes length - - readUntil: (code) -> - skipped = new Buffer 0 - read = => - this.readBytes 1 - .then (chunk) -> - if chunk[0] is code - skipped - else - skipped = Buffer.concat [skipped, chunk] - read() - read() - - searchLine: (re) -> - this.readLine() - .then (line) => - if match = re.exec line - match - else - this.searchLine re - - readLine: -> - this.readUntil 0x0a # '\n' - .then (line) -> - if line[line.length - 1] is 0x0d # '\r' - line.slice 0, -1 - else - line - - unexpected: (data, expected) -> - Promise.reject new Parser.UnexpectedDataError data, expected - -class Parser.FailError extends Error - constructor: (message) -> - Error.call this - this.name = 'FailError' - this.message = "Failure: '#{message}'" - Error.captureStackTrace this, Parser.FailError - -class Parser.PrematureEOFError extends Error - constructor: (howManyMissing) -> - Error.call this - this.name = 'PrematureEOFError' - this.message = "Premature end of stream, needed #{howManyMissing} - more bytes" - this.missingBytes = howManyMissing - Error.captureStackTrace this, Parser.PrematureEOFError - -class Parser.UnexpectedDataError extends Error - constructor: (unexpected, expected) -> - Error.call this - this.name = 'UnexpectedDataError' - this.message = "Unexpected '#{unexpected}', was expecting #{expected}" - this.unexpected = unexpected - this.expected = expected - Error.captureStackTrace this, Parser.UnexpectedDataError - -module.exports = Parser diff --git a/src/adb/parser.js b/src/adb/parser.js new file mode 100644 index 00000000..1b66bda1 --- /dev/null +++ b/src/adb/parser.js @@ -0,0 +1,291 @@ +var Parser, Promise, Protocol, + extend = function(child, parent) { for (var key in parent) { if (hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; }, + hasProp = {}.hasOwnProperty; + +Promise = require('bluebird'); + +Protocol = require('./protocol'); + +Parser = (function() { + function Parser(stream) { + this.stream = stream; + this.ended = false; + } + + Parser.prototype.end = function() { + var endListener, errorListener, resolver, tryRead; + if (this.ended) { + return Promise.resolve(true); + } + resolver = Promise.defer(); + tryRead = (function(_this) { + return function() { + while (_this.stream.read()) { + continue; + } + }; + })(this); + this.stream.on('readable', tryRead); + this.stream.on('error', errorListener = function(err) { + return resolver.reject(err); + }); + this.stream.on('end', endListener = (function(_this) { + return function() { + _this.ended = true; + return resolver.resolve(true); + }; + })(this)); + this.stream.read(0); + this.stream.end(); + return resolver.promise.cancellable()["finally"]((function(_this) { + return function() { + _this.stream.removeListener('readable', tryRead); + _this.stream.removeListener('error', errorListener); + return _this.stream.removeListener('end', endListener); + }; + })(this)); + }; + + Parser.prototype.raw = function() { + return this.stream; + }; + + Parser.prototype.readAll = function() { + var all, endListener, errorListener, resolver, tryRead; + all = new Buffer(0); + resolver = Promise.defer(); + tryRead = (function(_this) { + return function() { + var chunk; + while (chunk = _this.stream.read()) { + all = Buffer.concat([all, chunk]); + } + if (_this.ended) { + return resolver.resolve(all); + } + }; + })(this); + this.stream.on('readable', tryRead); + this.stream.on('error', errorListener = function(err) { + return resolver.reject(err); + }); + this.stream.on('end', endListener = (function(_this) { + return function() { + _this.ended = true; + return resolver.resolve(all); + }; + })(this)); + tryRead(); + return resolver.promise.cancellable()["finally"]((function(_this) { + return function() { + _this.stream.removeListener('readable', tryRead); + _this.stream.removeListener('error', errorListener); + return _this.stream.removeListener('end', endListener); + }; + })(this)); + }; + + Parser.prototype.readAscii = function(howMany) { + return this.readBytes(howMany).then(function(chunk) { + return chunk.toString('ascii'); + }); + }; + + Parser.prototype.readBytes = function(howMany) { + var endListener, errorListener, resolver, tryRead; + resolver = Promise.defer(); + tryRead = (function(_this) { + return function() { + var chunk; + if (howMany) { + if (chunk = _this.stream.read(howMany)) { + howMany -= chunk.length; + if (howMany === 0) { + return resolver.resolve(chunk); + } + } + if (_this.ended) { + return resolver.reject(new Parser.PrematureEOFError(howMany)); + } + } else { + return resolver.resolve(new Buffer(0)); + } + }; + })(this); + endListener = (function(_this) { + return function() { + _this.ended = true; + return resolver.reject(new Parser.PrematureEOFError(howMany)); + }; + })(this); + errorListener = function(err) { + return resolver.reject(err); + }; + this.stream.on('readable', tryRead); + this.stream.on('error', errorListener); + this.stream.on('end', endListener); + tryRead(); + return resolver.promise.cancellable()["finally"]((function(_this) { + return function() { + _this.stream.removeListener('readable', tryRead); + _this.stream.removeListener('error', errorListener); + return _this.stream.removeListener('end', endListener); + }; + })(this)); + }; + + Parser.prototype.readByteFlow = function(howMany, targetStream) { + var endListener, errorListener, resolver, tryRead; + resolver = Promise.defer(); + tryRead = (function(_this) { + return function() { + var chunk; + if (howMany) { + while (chunk = _this.stream.read(howMany) || _this.stream.read()) { + howMany -= chunk.length; + targetStream.write(chunk); + if (howMany === 0) { + return resolver.resolve(); + } + } + if (_this.ended) { + return resolver.reject(new Parser.PrematureEOFError(howMany)); + } + } else { + return resolver.resolve(); + } + }; + })(this); + endListener = (function(_this) { + return function() { + _this.ended = true; + return resolver.reject(new Parser.PrematureEOFError(howMany)); + }; + })(this); + errorListener = function(err) { + return resolver.reject(err); + }; + this.stream.on('readable', tryRead); + this.stream.on('error', errorListener); + this.stream.on('end', endListener); + tryRead(); + return resolver.promise.cancellable()["finally"]((function(_this) { + return function() { + _this.stream.removeListener('readable', tryRead); + _this.stream.removeListener('error', errorListener); + return _this.stream.removeListener('end', endListener); + }; + })(this)); + }; + + Parser.prototype.readError = function() { + return this.readValue().then(function(value) { + return Promise.reject(new Parser.FailError(value.toString())); + }); + }; + + Parser.prototype.readValue = function() { + return this.readAscii(4).then((function(_this) { + return function(value) { + var length; + length = Protocol.decodeLength(value); + return _this.readBytes(length); + }; + })(this)); + }; + + Parser.prototype.readUntil = function(code) { + var read, skipped; + skipped = new Buffer(0); + read = (function(_this) { + return function() { + return _this.readBytes(1).then(function(chunk) { + if (chunk[0] === code) { + return skipped; + } else { + skipped = Buffer.concat([skipped, chunk]); + return read(); + } + }); + }; + })(this); + return read(); + }; + + Parser.prototype.searchLine = function(re) { + return this.readLine().then((function(_this) { + return function(line) { + var match; + if (match = re.exec(line)) { + return match; + } else { + return _this.searchLine(re); + } + }; + })(this)); + }; + + Parser.prototype.readLine = function() { + return this.readUntil(0x0a).then(function(line) { + if (line[line.length - 1] === 0x0d) { + return line.slice(0, -1); + } else { + return line; + } + }); + }; + + Parser.prototype.unexpected = function(data, expected) { + return Promise.reject(new Parser.UnexpectedDataError(data, expected)); + }; + + return Parser; + +})(); + +Parser.FailError = (function(superClass) { + extend(FailError, superClass); + + function FailError(message) { + Error.call(this); + this.name = 'FailError'; + this.message = "Failure: '" + message + "'"; + Error.captureStackTrace(this, Parser.FailError); + } + + return FailError; + +})(Error); + +Parser.PrematureEOFError = (function(superClass) { + extend(PrematureEOFError, superClass); + + function PrematureEOFError(howManyMissing) { + Error.call(this); + this.name = 'PrematureEOFError'; + this.message = "Premature end of stream, needed " + howManyMissing + " more bytes"; + this.missingBytes = howManyMissing; + Error.captureStackTrace(this, Parser.PrematureEOFError); + } + + return PrematureEOFError; + +})(Error); + +Parser.UnexpectedDataError = (function(superClass) { + extend(UnexpectedDataError, superClass); + + function UnexpectedDataError(unexpected, expected) { + Error.call(this); + this.name = 'UnexpectedDataError'; + this.message = "Unexpected '" + unexpected + "', was expecting " + expected; + this.unexpected = unexpected; + this.expected = expected; + Error.captureStackTrace(this, Parser.UnexpectedDataError); + } + + return UnexpectedDataError; + +})(Error); + +module.exports = Parser; diff --git a/src/adb/proc/stat.coffee b/src/adb/proc/stat.coffee deleted file mode 100644 index d5b9d127..00000000 --- a/src/adb/proc/stat.coffee +++ /dev/null @@ -1,99 +0,0 @@ -{EventEmitter} = require 'events' -split = require 'split' - -Parser = require '../parser' - -class ProcStat extends EventEmitter - RE_CPULINE = /^cpu[0-9]+ .*$/mg - RE_COLSEP = /\ +/g - - constructor: (@sync) -> - @interval = 1000 - @stats = this._emptyStats() - @_ignore = {} - @_timer = setInterval => - this.update() - , @interval - this.update() - - end: -> - clearInterval @_timer - @sync.end() - @sync = null - - update: -> - new Parser(@sync.pull '/proc/stat') - .readAll() - .then (out) => - this._parse out - .catch (err) => - this._error err - return - - _parse: (out) -> - stats = this._emptyStats() - while match = RE_CPULINE.exec out - line = match[0] - cols = line.split RE_COLSEP - type = cols.shift() - continue if @_ignore[type] is line - total = 0 - total += +val for val in cols - stats.cpus[type] = - line: line - user: +cols[0] or 0 - nice: +cols[1] or 0 - system: +cols[2] or 0 - idle: +cols[3] or 0 - iowait: +cols[4] or 0 - irq: +cols[5] or 0 - softirq: +cols[6] or 0 - steal: +cols[7] or 0 - guest: +cols[8] or 0 - guestnice: +cols[9] or 0 - total: total - this._set stats - - _set: (stats) -> - loads = {} - found = false - for id, cur of stats.cpus - old = @stats.cpus[id] - continue unless old - ticks = cur.total - old.total - if ticks > 0 - found = true - # Calculate percentages for everything. For ease of formatting, - # let's do `x / y * 100` as `100 / y * x`. - m = 100 / ticks - loads[id] = - user: Math.floor m * (cur.user - old.user) - nice: Math.floor m * (cur.nice - old.nice) - system: Math.floor m * (cur.system - old.system) - idle: Math.floor m * (cur.idle - old.idle) - iowait: Math.floor m * (cur.iowait - old.iowait) - irq: Math.floor m * (cur.irq - old.irq) - softirq: Math.floor m * (cur.softirq - old.softirq) - steal: Math.floor m * (cur.steal - old.steal) - guest: Math.floor m * (cur.guest - old.guest) - guestnice: Math.floor m * (cur.guestnice - old.guestnice) - total: 100 - else - # The CPU is either offline (nothing was done) or it mysteriously - # warped back in time (idle stat dropped significantly), causing the - # total tick count to be <0. The latter seems to only happen on - # Galaxy S4 so far. Either way we don't want those anomalies in our - # stats. We'll also ignore the line in the next cycle. This doesn't - # completely eliminate the anomalies, but it helps. - @_ignore[id] = cur.line - delete stats.cpus[id] - this.emit 'load', loads if found - @stats = stats - - _error: (err) -> - this.emit 'error', err - - _emptyStats: -> - cpus: {} - -module.exports = ProcStat diff --git a/src/adb/proc/stat.js b/src/adb/proc/stat.js new file mode 100644 index 00000000..9385964b --- /dev/null +++ b/src/adb/proc/stat.js @@ -0,0 +1,137 @@ +var EventEmitter, Parser, ProcStat, split, + extend = function(child, parent) { for (var key in parent) { if (hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; }, + hasProp = {}.hasOwnProperty; + +EventEmitter = require('events').EventEmitter; + +split = require('split'); + +Parser = require('../parser'); + +ProcStat = (function(superClass) { + var RE_COLSEP, RE_CPULINE; + + extend(ProcStat, superClass); + + RE_CPULINE = /^cpu[0-9]+ .*$/mg; + + RE_COLSEP = /\ +/g; + + function ProcStat(sync) { + this.sync = sync; + this.interval = 1000; + this.stats = this._emptyStats(); + this._ignore = {}; + this._timer = setInterval((function(_this) { + return function() { + return _this.update(); + }; + })(this), this.interval); + this.update(); + } + + ProcStat.prototype.end = function() { + clearInterval(this._timer); + this.sync.end(); + return this.sync = null; + }; + + ProcStat.prototype.update = function() { + return new Parser(this.sync.pull('/proc/stat')).readAll().then((function(_this) { + return function(out) { + return _this._parse(out); + }; + })(this))["catch"]((function(_this) { + return function(err) { + _this._error(err); + }; + })(this)); + }; + + ProcStat.prototype._parse = function(out) { + var cols, i, len, line, match, stats, total, type, val; + stats = this._emptyStats(); + while (match = RE_CPULINE.exec(out)) { + line = match[0]; + cols = line.split(RE_COLSEP); + type = cols.shift(); + if (this._ignore[type] === line) { + continue; + } + total = 0; + for (i = 0, len = cols.length; i < len; i++) { + val = cols[i]; + total += +val; + } + stats.cpus[type] = { + line: line, + user: +cols[0] || 0, + nice: +cols[1] || 0, + system: +cols[2] || 0, + idle: +cols[3] || 0, + iowait: +cols[4] || 0, + irq: +cols[5] || 0, + softirq: +cols[6] || 0, + steal: +cols[7] || 0, + guest: +cols[8] || 0, + guestnice: +cols[9] || 0, + total: total + }; + } + return this._set(stats); + }; + + ProcStat.prototype._set = function(stats) { + var cur, found, id, loads, m, old, ref, ticks; + loads = {}; + found = false; + ref = stats.cpus; + for (id in ref) { + cur = ref[id]; + old = this.stats.cpus[id]; + if (!old) { + continue; + } + ticks = cur.total - old.total; + if (ticks > 0) { + found = true; + m = 100 / ticks; + loads[id] = { + user: Math.floor(m * (cur.user - old.user)), + nice: Math.floor(m * (cur.nice - old.nice)), + system: Math.floor(m * (cur.system - old.system)), + idle: Math.floor(m * (cur.idle - old.idle)), + iowait: Math.floor(m * (cur.iowait - old.iowait)), + irq: Math.floor(m * (cur.irq - old.irq)), + softirq: Math.floor(m * (cur.softirq - old.softirq)), + steal: Math.floor(m * (cur.steal - old.steal)), + guest: Math.floor(m * (cur.guest - old.guest)), + guestnice: Math.floor(m * (cur.guestnice - old.guestnice)), + total: 100 + }; + } else { + this._ignore[id] = cur.line; + delete stats.cpus[id]; + } + } + if (found) { + this.emit('load', loads); + } + return this.stats = stats; + }; + + ProcStat.prototype._error = function(err) { + return this.emit('error', err); + }; + + ProcStat.prototype._emptyStats = function() { + return { + cpus: {} + }; + }; + + return ProcStat; + +})(EventEmitter); + +module.exports = ProcStat; diff --git a/src/adb/protocol.coffee b/src/adb/protocol.coffee deleted file mode 100644 index df21eccf..00000000 --- a/src/adb/protocol.coffee +++ /dev/null @@ -1,24 +0,0 @@ -class Protocol - @OKAY = 'OKAY' - @FAIL = 'FAIL' - @STAT = 'STAT' - @LIST = 'LIST' - @DENT = 'DENT' - @RECV = 'RECV' - @DATA = 'DATA' - @DONE = 'DONE' - @SEND = 'SEND' - @QUIT = 'QUIT' - - @decodeLength: (length) -> - parseInt length, 16 - - @encodeLength: (length) -> - ('0000' + length.toString 16).slice(-4).toUpperCase() - - @encodeData: (data) -> - unless Buffer.isBuffer data - data = new Buffer data - Buffer.concat [new Buffer(Protocol.encodeLength data.length), data] - -module.exports = Protocol diff --git a/src/adb/protocol.js b/src/adb/protocol.js new file mode 100644 index 00000000..d1377d06 --- /dev/null +++ b/src/adb/protocol.js @@ -0,0 +1,45 @@ +var Protocol; + +Protocol = (function() { + function Protocol() {} + + Protocol.OKAY = 'OKAY'; + + Protocol.FAIL = 'FAIL'; + + Protocol.STAT = 'STAT'; + + Protocol.LIST = 'LIST'; + + Protocol.DENT = 'DENT'; + + Protocol.RECV = 'RECV'; + + Protocol.DATA = 'DATA'; + + Protocol.DONE = 'DONE'; + + Protocol.SEND = 'SEND'; + + Protocol.QUIT = 'QUIT'; + + Protocol.decodeLength = function(length) { + return parseInt(length, 16); + }; + + Protocol.encodeLength = function(length) { + return ('0000' + length.toString(16)).slice(-4).toUpperCase(); + }; + + Protocol.encodeData = function(data) { + if (!Buffer.isBuffer(data)) { + data = new Buffer(data); + } + return Buffer.concat([new Buffer(Protocol.encodeLength(data.length)), data]); + }; + + return Protocol; + +})(); + +module.exports = Protocol; diff --git a/src/adb/sync.coffee b/src/adb/sync.coffee deleted file mode 100644 index 53ff3d66..00000000 --- a/src/adb/sync.coffee +++ /dev/null @@ -1,269 +0,0 @@ -Fs = require 'fs' -Path = require 'path' -Promise = require 'bluebird' -{EventEmitter} = require 'events' -debug = require('debug')('adb:sync') - -Parser = require './parser' -Protocol = require './protocol' -Stats = require './sync/stats' -Entry = require './sync/entry' -PushTransfer = require './sync/pushtransfer' -PullTransfer = require './sync/pulltransfer' - -class Sync extends EventEmitter - TEMP_PATH = '/data/local/tmp' - DEFAULT_CHMOD = 0o644 - DATA_MAX_LENGTH = 65536 - - @temp: (path) -> - "#{TEMP_PATH}/#{Path.basename path}" - - constructor: (@connection) -> - @parser = @connection.parser - - stat: (path, callback) -> - this._sendCommandWithArg Protocol.STAT, path - @parser.readAscii 4 - .then (reply) => - switch reply - when Protocol.STAT - @parser.readBytes 12 - .then (stat) => - mode = stat.readUInt32LE 0 - size = stat.readUInt32LE 4 - mtime = stat.readUInt32LE 8 - if mode is 0 - this._enoent path - else - new Stats mode, size, mtime - when Protocol.FAIL - this._readError() - else - @parser.unexpected reply, 'STAT or FAIL' - .nodeify callback - - readdir: (path, callback) -> - files = [] - - readNext = => - @parser.readAscii 4 - .then (reply) => - switch reply - when Protocol.DENT - @parser.readBytes 16 - .then (stat) => - mode = stat.readUInt32LE 0 - size = stat.readUInt32LE 4 - mtime = stat.readUInt32LE 8 - namelen = stat.readUInt32LE 12 - @parser.readBytes namelen - .then (name) -> - name = name.toString() - # Skip '.' and '..' to match Node's fs.readdir(). - unless name is '.' or name is '..' - files.push new Entry name, mode, size, mtime - readNext() - when Protocol.DONE - @parser.readBytes 16 - .then (zero) -> - files - when Protocol.FAIL - this._readError() - else - @parser.unexpected reply, 'DENT, DONE or FAIL' - - this._sendCommandWithArg Protocol.LIST, path - - readNext() - .nodeify callback - - push: (contents, path, mode) -> - if typeof contents is 'string' - this.pushFile contents, path, mode - else - this.pushStream contents, path, mode - - pushFile: (file, path, mode = DEFAULT_CHMOD) -> - mode or= DEFAULT_CHMOD - this.pushStream Fs.createReadStream(file), path, mode - - pushStream: (stream, path, mode = DEFAULT_CHMOD) -> - mode |= Stats.S_IFREG - this._sendCommandWithArg Protocol.SEND, "#{path},#{mode}" - this._writeData stream, Math.floor(Date.now() / 1000) - - pull: (path) -> - this._sendCommandWithArg Protocol.RECV, "#{path}" - this._readData() - - end: -> - @connection.end() - return this - - tempFile: (path) -> - Sync.temp path - - _writeData: (stream, timeStamp) -> - transfer = new PushTransfer - - writeData = => - resolver = Promise.defer() - writer = Promise.resolve() - .cancellable() - - stream.on 'end', endListener = => - writer.then => - this._sendCommandWithLength Protocol.DONE, timeStamp - resolver.resolve() - - waitForDrain = => - resolver = Promise.defer() - - @connection.on 'drain', drainListener = -> - resolver.resolve() - - resolver.promise.finally => - @connection.removeListener 'drain', drainListener - - track = -> - transfer.pop() - - writeNext = => - if chunk = stream.read(DATA_MAX_LENGTH) or stream.read() - this._sendCommandWithLength Protocol.DATA, chunk.length - transfer.push chunk.length - if @connection.write chunk, track - writeNext() - else - waitForDrain() - .then writeNext - else - Promise.resolve() - - stream.on 'readable', readableListener = -> - writer.then writeNext - - stream.on 'error', errorListener = (err) -> - resolver.reject err - - resolver.promise.finally -> - stream.removeListener 'end', endListener - stream.removeListener 'readable', readableListener - stream.removeListener 'error', errorListener - writer.cancel() - - readReply = => - @parser.readAscii 4 - .then (reply) => - switch reply - when Protocol.OKAY - @parser.readBytes 4 - .then (zero) -> - true - when Protocol.FAIL - this._readError() - else - @parser.unexpected reply, 'OKAY or FAIL' - - # While I can't think of a case that would break this double-Promise - # writer-reader arrangement right now, it's not immediately obvious - # that the code is correct and it may or may not have some failing - # edge cases. Refactor pending. - - writer = writeData() - .cancellable() - .catch Promise.CancellationError, (err) => - @connection.end() - .catch (err) -> - transfer.emit 'error', err - reader.cancel() - - reader = readReply() - .cancellable() - .catch Promise.CancellationError, (err) -> - true - .catch (err) -> - transfer.emit 'error', err - writer.cancel() - .finally -> - transfer.end() - - transfer.on 'cancel', -> - writer.cancel() - reader.cancel() - - return transfer - - _readData: -> - transfer = new PullTransfer - - readNext = => - @parser.readAscii 4 - .cancellable() - .then (reply) => - switch reply - when Protocol.DATA - @parser.readBytes 4 - .then (lengthData) => - length = lengthData.readUInt32LE 0 - @parser.readByteFlow(length, transfer) - .then readNext - when Protocol.DONE - @parser.readBytes 4 - .then (zero) -> - true - when Protocol.FAIL - this._readError() - else - @parser.unexpected reply, 'DATA, DONE or FAIL' - - reader = readNext() - .catch Promise.CancellationError, (err) => - @connection.end() - .catch (err) -> - transfer.emit 'error', err - .finally -> - transfer.removeListener 'cancel', cancelListener - transfer.end() - - transfer.on 'cancel', cancelListener = -> - reader.cancel() - - return transfer - - _readError: -> - @parser.readBytes 4 - .then (length) => - @parser.readBytes length.readUInt32LE(0) - .then (buf) -> - Promise.reject new Parser.FailError buf.toString() - .finally => - @parser.end() - - _sendCommandWithLength: (cmd, length) -> - debug cmd unless cmd is Protocol.DATA - payload = new Buffer cmd.length + 4 - payload.write cmd, 0, cmd.length - payload.writeUInt32LE length, cmd.length - @connection.write payload - - _sendCommandWithArg: (cmd, arg) -> - debug "#{cmd} #{arg}" - payload = new Buffer cmd.length + 4 + arg.length - pos = 0 - payload.write cmd, pos, cmd.length - pos += cmd.length - payload.writeUInt32LE arg.length, pos - pos += 4 - payload.write arg, pos - @connection.write payload - - _enoent: (path) -> - err = new Error "ENOENT, no such file or directory '#{path}'" - err.errno = 34 - err.code = 'ENOENT' - err.path = path - Promise.reject err - -module.exports = Sync diff --git a/src/adb/sync.js b/src/adb/sync.js new file mode 100644 index 00000000..42fb0ba4 --- /dev/null +++ b/src/adb/sync.js @@ -0,0 +1,336 @@ +var Entry, EventEmitter, Fs, Parser, Path, Promise, Protocol, PullTransfer, PushTransfer, Stats, Sync, debug, + extend = function(child, parent) { for (var key in parent) { if (hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; }, + hasProp = {}.hasOwnProperty; + +Fs = require('fs'); + +Path = require('path'); + +Promise = require('bluebird'); + +EventEmitter = require('events').EventEmitter; + +debug = require('debug')('adb:sync'); + +Parser = require('./parser'); + +Protocol = require('./protocol'); + +Stats = require('./sync/stats'); + +Entry = require('./sync/entry'); + +PushTransfer = require('./sync/pushtransfer'); + +PullTransfer = require('./sync/pulltransfer'); + +Sync = (function(superClass) { + var DATA_MAX_LENGTH, DEFAULT_CHMOD, TEMP_PATH; + + extend(Sync, superClass); + + TEMP_PATH = '/data/local/tmp'; + + DEFAULT_CHMOD = 0x1a4; + + DATA_MAX_LENGTH = 65536; + + Sync.temp = function(path) { + return TEMP_PATH + "/" + (Path.basename(path)); + }; + + function Sync(connection) { + this.connection = connection; + this.parser = this.connection.parser; + } + + Sync.prototype.stat = function(path, callback) { + this._sendCommandWithArg(Protocol.STAT, path); + return this.parser.readAscii(4).then((function(_this) { + return function(reply) { + switch (reply) { + case Protocol.STAT: + return _this.parser.readBytes(12).then(function(stat) { + var mode, mtime, size; + mode = stat.readUInt32LE(0); + size = stat.readUInt32LE(4); + mtime = stat.readUInt32LE(8); + if (mode === 0) { + return _this._enoent(path); + } else { + return new Stats(mode, size, mtime); + } + }); + case Protocol.FAIL: + return _this._readError(); + default: + return _this.parser.unexpected(reply, 'STAT or FAIL'); + } + }; + })(this)).nodeify(callback); + }; + + Sync.prototype.readdir = function(path, callback) { + var files, readNext; + files = []; + readNext = (function(_this) { + return function() { + return _this.parser.readAscii(4).then(function(reply) { + switch (reply) { + case Protocol.DENT: + return _this.parser.readBytes(16).then(function(stat) { + var mode, mtime, namelen, size; + mode = stat.readUInt32LE(0); + size = stat.readUInt32LE(4); + mtime = stat.readUInt32LE(8); + namelen = stat.readUInt32LE(12); + return _this.parser.readBytes(namelen).then(function(name) { + name = name.toString(); + if (!(name === '.' || name === '..')) { + files.push(new Entry(name, mode, size, mtime)); + } + return readNext(); + }); + }); + case Protocol.DONE: + return _this.parser.readBytes(16).then(function(zero) { + return files; + }); + case Protocol.FAIL: + return _this._readError(); + default: + return _this.parser.unexpected(reply, 'DENT, DONE or FAIL'); + } + }); + }; + })(this); + this._sendCommandWithArg(Protocol.LIST, path); + return readNext().nodeify(callback); + }; + + Sync.prototype.push = function(contents, path, mode) { + if (typeof contents === 'string') { + return this.pushFile(contents, path, mode); + } else { + return this.pushStream(contents, path, mode); + } + }; + + Sync.prototype.pushFile = function(file, path, mode) { + if (mode == null) { + mode = DEFAULT_CHMOD; + } + mode || (mode = DEFAULT_CHMOD); + return this.pushStream(Fs.createReadStream(file), path, mode); + }; + + Sync.prototype.pushStream = function(stream, path, mode) { + if (mode == null) { + mode = DEFAULT_CHMOD; + } + mode |= Stats.S_IFREG; + this._sendCommandWithArg(Protocol.SEND, path + "," + mode); + return this._writeData(stream, Math.floor(Date.now() / 1000)); + }; + + Sync.prototype.pull = function(path) { + this._sendCommandWithArg(Protocol.RECV, "" + path); + return this._readData(); + }; + + Sync.prototype.end = function() { + this.connection.end(); + return this; + }; + + Sync.prototype.tempFile = function(path) { + return Sync.temp(path); + }; + + Sync.prototype._writeData = function(stream, timeStamp) { + var readReply, reader, transfer, writeData, writer; + transfer = new PushTransfer; + writeData = (function(_this) { + return function() { + var endListener, errorListener, readableListener, resolver, track, waitForDrain, writeNext, writer; + resolver = Promise.defer(); + writer = Promise.resolve().cancellable(); + stream.on('end', endListener = function() { + return writer.then(function() { + _this._sendCommandWithLength(Protocol.DONE, timeStamp); + return resolver.resolve(); + }); + }); + waitForDrain = function() { + var drainListener; + resolver = Promise.defer(); + _this.connection.on('drain', drainListener = function() { + return resolver.resolve(); + }); + return resolver.promise["finally"](function() { + return _this.connection.removeListener('drain', drainListener); + }); + }; + track = function() { + return transfer.pop(); + }; + writeNext = function() { + var chunk; + if (chunk = stream.read(DATA_MAX_LENGTH) || stream.read()) { + _this._sendCommandWithLength(Protocol.DATA, chunk.length); + transfer.push(chunk.length); + if (_this.connection.write(chunk, track)) { + return writeNext(); + } else { + return waitForDrain().then(writeNext); + } + } else { + return Promise.resolve(); + } + }; + stream.on('readable', readableListener = function() { + return writer.then(writeNext); + }); + stream.on('error', errorListener = function(err) { + return resolver.reject(err); + }); + return resolver.promise["finally"](function() { + stream.removeListener('end', endListener); + stream.removeListener('readable', readableListener); + stream.removeListener('error', errorListener); + return writer.cancel(); + }); + }; + })(this); + readReply = (function(_this) { + return function() { + return _this.parser.readAscii(4).then(function(reply) { + switch (reply) { + case Protocol.OKAY: + return _this.parser.readBytes(4).then(function(zero) { + return true; + }); + case Protocol.FAIL: + return _this._readError(); + default: + return _this.parser.unexpected(reply, 'OKAY or FAIL'); + } + }); + }; + })(this); + writer = writeData().cancellable()["catch"](Promise.CancellationError, (function(_this) { + return function(err) { + return _this.connection.end(); + }; + })(this))["catch"](function(err) { + transfer.emit('error', err); + return reader.cancel(); + }); + reader = readReply().cancellable()["catch"](Promise.CancellationError, function(err) { + return true; + })["catch"](function(err) { + transfer.emit('error', err); + return writer.cancel(); + })["finally"](function() { + return transfer.end(); + }); + transfer.on('cancel', function() { + writer.cancel(); + return reader.cancel(); + }); + return transfer; + }; + + Sync.prototype._readData = function() { + var cancelListener, readNext, reader, transfer; + transfer = new PullTransfer; + readNext = (function(_this) { + return function() { + return _this.parser.readAscii(4).cancellable().then(function(reply) { + switch (reply) { + case Protocol.DATA: + return _this.parser.readBytes(4).then(function(lengthData) { + var length; + length = lengthData.readUInt32LE(0); + return _this.parser.readByteFlow(length, transfer).then(readNext); + }); + case Protocol.DONE: + return _this.parser.readBytes(4).then(function(zero) { + return true; + }); + case Protocol.FAIL: + return _this._readError(); + default: + return _this.parser.unexpected(reply, 'DATA, DONE or FAIL'); + } + }); + }; + })(this); + reader = readNext()["catch"](Promise.CancellationError, (function(_this) { + return function(err) { + return _this.connection.end(); + }; + })(this))["catch"](function(err) { + return transfer.emit('error', err); + })["finally"](function() { + transfer.removeListener('cancel', cancelListener); + return transfer.end(); + }); + transfer.on('cancel', cancelListener = function() { + return reader.cancel(); + }); + return transfer; + }; + + Sync.prototype._readError = function() { + return this.parser.readBytes(4).then((function(_this) { + return function(length) { + return _this.parser.readBytes(length.readUInt32LE(0)).then(function(buf) { + return Promise.reject(new Parser.FailError(buf.toString())); + }); + }; + })(this))["finally"]((function(_this) { + return function() { + return _this.parser.end(); + }; + })(this)); + }; + + Sync.prototype._sendCommandWithLength = function(cmd, length) { + var payload; + if (cmd !== Protocol.DATA) { + debug(cmd); + } + payload = new Buffer(cmd.length + 4); + payload.write(cmd, 0, cmd.length); + payload.writeUInt32LE(length, cmd.length); + return this.connection.write(payload); + }; + + Sync.prototype._sendCommandWithArg = function(cmd, arg) { + var payload, pos; + debug(cmd + " " + arg); + payload = new Buffer(cmd.length + 4 + arg.length); + pos = 0; + payload.write(cmd, pos, cmd.length); + pos += cmd.length; + payload.writeUInt32LE(arg.length, pos); + pos += 4; + payload.write(arg, pos); + return this.connection.write(payload); + }; + + Sync.prototype._enoent = function(path) { + var err; + err = new Error("ENOENT, no such file or directory '" + path + "'"); + err.errno = 34; + err.code = 'ENOENT'; + err.path = path; + return Promise.reject(err); + }; + + return Sync; + +})(EventEmitter); + +module.exports = Sync; diff --git a/src/adb/sync/entry.coffee b/src/adb/sync/entry.coffee deleted file mode 100644 index f9996989..00000000 --- a/src/adb/sync/entry.coffee +++ /dev/null @@ -1,10 +0,0 @@ -Stats = require './stats' - -class Entry extends Stats - constructor: (@name, mode, size, mtime) -> - super mode, size, mtime - - toString: -> - @name - -module.exports = Entry diff --git a/src/adb/sync/entry.js b/src/adb/sync/entry.js new file mode 100644 index 00000000..a17f3483 --- /dev/null +++ b/src/adb/sync/entry.js @@ -0,0 +1,23 @@ +var Entry, Stats, + extend = function(child, parent) { for (var key in parent) { if (hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; }, + hasProp = {}.hasOwnProperty; + +Stats = require('./stats'); + +Entry = (function(superClass) { + extend(Entry, superClass); + + function Entry(name, mode, size, mtime) { + this.name = name; + Entry.__super__.constructor.call(this, mode, size, mtime); + } + + Entry.prototype.toString = function() { + return this.name; + }; + + return Entry; + +})(Stats); + +module.exports = Entry; diff --git a/src/adb/sync/pulltransfer.coffee b/src/adb/sync/pulltransfer.coffee deleted file mode 100644 index 16c86cce..00000000 --- a/src/adb/sync/pulltransfer.coffee +++ /dev/null @@ -1,17 +0,0 @@ -Stream = require 'stream' - -class PullTransfer extends Stream.PassThrough - constructor: -> - @stats = - bytesTransferred: 0 - super() - - cancel: -> - this.emit 'cancel' - - write: (chunk, encoding, callback) -> - @stats.bytesTransferred += chunk.length - this.emit 'progress', @stats - super chunk, encoding, callback - -module.exports = PullTransfer diff --git a/src/adb/sync/pulltransfer.js b/src/adb/sync/pulltransfer.js new file mode 100644 index 00000000..38a04820 --- /dev/null +++ b/src/adb/sync/pulltransfer.js @@ -0,0 +1,31 @@ +var PullTransfer, Stream, + extend = function(child, parent) { for (var key in parent) { if (hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; }, + hasProp = {}.hasOwnProperty; + +Stream = require('stream'); + +PullTransfer = (function(superClass) { + extend(PullTransfer, superClass); + + function PullTransfer() { + this.stats = { + bytesTransferred: 0 + }; + PullTransfer.__super__.constructor.call(this); + } + + PullTransfer.prototype.cancel = function() { + return this.emit('cancel'); + }; + + PullTransfer.prototype.write = function(chunk, encoding, callback) { + this.stats.bytesTransferred += chunk.length; + this.emit('progress', this.stats); + return PullTransfer.__super__.write.call(this, chunk, encoding, callback); + }; + + return PullTransfer; + +})(Stream.PassThrough); + +module.exports = PullTransfer; diff --git a/src/adb/sync/pushtransfer.coffee b/src/adb/sync/pushtransfer.coffee deleted file mode 100644 index 04871a5d..00000000 --- a/src/adb/sync/pushtransfer.coffee +++ /dev/null @@ -1,23 +0,0 @@ -{EventEmitter} = require 'events' - -class PushTransfer extends EventEmitter - constructor: -> - @_stack = [] - @stats = - bytesTransferred: 0 - - cancel: -> - this.emit 'cancel' - - push: (byteCount) -> - @_stack.push byteCount - - pop: -> - byteCount = @_stack.pop() - @stats.bytesTransferred += byteCount - this.emit 'progress', @stats - - end: -> - this.emit 'end' - -module.exports = PushTransfer diff --git a/src/adb/sync/pushtransfer.js b/src/adb/sync/pushtransfer.js new file mode 100644 index 00000000..f3b60799 --- /dev/null +++ b/src/adb/sync/pushtransfer.js @@ -0,0 +1,40 @@ +var EventEmitter, PushTransfer, + extend = function(child, parent) { for (var key in parent) { if (hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; }, + hasProp = {}.hasOwnProperty; + +EventEmitter = require('events').EventEmitter; + +PushTransfer = (function(superClass) { + extend(PushTransfer, superClass); + + function PushTransfer() { + this._stack = []; + this.stats = { + bytesTransferred: 0 + }; + } + + PushTransfer.prototype.cancel = function() { + return this.emit('cancel'); + }; + + PushTransfer.prototype.push = function(byteCount) { + return this._stack.push(byteCount); + }; + + PushTransfer.prototype.pop = function() { + var byteCount; + byteCount = this._stack.pop(); + this.stats.bytesTransferred += byteCount; + return this.emit('progress', this.stats); + }; + + PushTransfer.prototype.end = function() { + return this.emit('end'); + }; + + return PushTransfer; + +})(EventEmitter); + +module.exports = PushTransfer; diff --git a/src/adb/sync/stats.coffee b/src/adb/sync/stats.coffee deleted file mode 100644 index 14284bba..00000000 --- a/src/adb/sync/stats.coffee +++ /dev/null @@ -1,26 +0,0 @@ -Fs = require 'fs' - -class Stats extends Fs.Stats - # The following constant were extracted from `man 2 stat` on Ubuntu 12.10. - @S_IFMT = 0o170000 # bit mask for the file type bit fields - @S_IFSOCK = 0o140000 # socket - @S_IFLNK = 0o120000 # symbolic link - @S_IFREG = 0o100000 # regular file - @S_IFBLK = 0o060000 # block device - @S_IFDIR = 0o040000 # directory - @S_IFCHR = 0o020000 # character device - @S_IFIFO = 0o010000 # FIFO - @S_ISUID = 0o004000 # set UID bit - @S_ISGID = 0o002000 # set-group-ID bit (see below) - @S_ISVTX = 0o001000 # sticky bit (see below) - @S_IRWXU = 0o0700 # mask for file owner permissions - @S_IRUSR = 0o0400 # owner has read permission - @S_IWUSR = 0o0200 # owner has write permission - @S_IXUSR = 0o0100 # owner has execute permission - @S_IRWXG = 0o0070 # mask for group permissions - @S_IRGRP = 0o0040 # group has read permission - - constructor: (@mode, @size, mtime) -> - @mtime = new Date mtime * 1000 - -module.exports = Stats diff --git a/src/adb/sync/stats.js b/src/adb/sync/stats.js new file mode 100644 index 00000000..5a82d0a4 --- /dev/null +++ b/src/adb/sync/stats.js @@ -0,0 +1,54 @@ +var Fs, Stats, + extend = function(child, parent) { for (var key in parent) { if (hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; }, + hasProp = {}.hasOwnProperty; + +Fs = require('fs'); + +Stats = (function(superClass) { + extend(Stats, superClass); + + Stats.S_IFMT = 0xf000; + + Stats.S_IFSOCK = 0xc000; + + Stats.S_IFLNK = 0xa000; + + Stats.S_IFREG = 0x8000; + + Stats.S_IFBLK = 0x6000; + + Stats.S_IFDIR = 0x4000; + + Stats.S_IFCHR = 0x2000; + + Stats.S_IFIFO = 0x1000; + + Stats.S_ISUID = 0x800; + + Stats.S_ISGID = 0x400; + + Stats.S_ISVTX = 0x200; + + Stats.S_IRWXU = 0x1c0; + + Stats.S_IRUSR = 0x100; + + Stats.S_IWUSR = 0x80; + + Stats.S_IXUSR = 0x40; + + Stats.S_IRWXG = 0x38; + + Stats.S_IRGRP = 0x20; + + function Stats(mode, size, mtime) { + this.mode = mode; + this.size = size; + this.mtime = new Date(mtime * 1000); + } + + return Stats; + +})(Fs.Stats); + +module.exports = Stats; diff --git a/src/adb/tcpusb/packet.coffee b/src/adb/tcpusb/packet.coffee deleted file mode 100644 index a99ebb6a..00000000 --- a/src/adb/tcpusb/packet.coffee +++ /dev/null @@ -1,73 +0,0 @@ -class Packet - @A_SYNC = 0x434e5953 - @A_CNXN = 0x4e584e43 - @A_OPEN = 0x4e45504f - @A_OKAY = 0x59414b4f - @A_CLSE = 0x45534c43 - @A_WRTE = 0x45545257 - @A_AUTH = 0x48545541 - - @checksum: (data) -> - sum = 0 - sum += char for char in data if data - return sum - - @magic: (command) -> - # We need the full uint32 range, which ">>> 0" thankfully allows us to use - (command ^ 0xffffffff) >>> 0 - - @assemble: (command, arg0, arg1, data) -> - if data - chunk = new Buffer 24 + data.length - chunk.writeUInt32LE command, 0 - chunk.writeUInt32LE arg0, 4 - chunk.writeUInt32LE arg1, 8 - chunk.writeUInt32LE data.length, 12 - chunk.writeUInt32LE Packet.checksum(data), 16 - chunk.writeUInt32LE Packet.magic(command), 20 - data.copy chunk, 24 - chunk - else - chunk = new Buffer 24 - chunk.writeUInt32LE command, 0 - chunk.writeUInt32LE arg0, 4 - chunk.writeUInt32LE arg1, 8 - chunk.writeUInt32LE 0, 12 - chunk.writeUInt32LE 0, 16 - chunk.writeUInt32LE Packet.magic(command), 20 - chunk - - @swap32: (n) -> - buffer = new Buffer(4) - buffer.writeUInt32LE(n, 0) - buffer.readUInt32BE(0) - - constructor: (@command, @arg0, @arg1, @length, @check, @magic, @data) -> - - verifyChecksum: -> - @check is Packet.checksum @data - - verifyMagic: -> - @magic is Packet.magic @command - - toString: -> - type = switch @command - when Packet.A_SYNC - "SYNC" - when Packet.A_CNXN - "CNXN" - when Packet.A_OPEN - "OPEN" - when Packet.A_OKAY - "OKAY" - when Packet.A_CLSE - "CLSE" - when Packet.A_WRTE - "WRTE" - when Packet.A_AUTH - "AUTH" - else - throw new Error "Unknown command {@command}" - "#{type} arg0=#{@arg0} arg1=#{@arg1} length=#{@length}" - -module.exports = Packet diff --git a/src/adb/tcpusb/packet.js b/src/adb/tcpusb/packet.js new file mode 100644 index 00000000..76523aa5 --- /dev/null +++ b/src/adb/tcpusb/packet.js @@ -0,0 +1,112 @@ +var Packet; + +Packet = (function() { + Packet.A_SYNC = 0x434e5953; + + Packet.A_CNXN = 0x4e584e43; + + Packet.A_OPEN = 0x4e45504f; + + Packet.A_OKAY = 0x59414b4f; + + Packet.A_CLSE = 0x45534c43; + + Packet.A_WRTE = 0x45545257; + + Packet.A_AUTH = 0x48545541; + + Packet.checksum = function(data) { + var char, i, len, sum; + sum = 0; + if (data) { + for (i = 0, len = data.length; i < len; i++) { + char = data[i]; + sum += char; + } + } + return sum; + }; + + Packet.magic = function(command) { + return (command ^ 0xffffffff) >>> 0; + }; + + Packet.assemble = function(command, arg0, arg1, data) { + var chunk; + if (data) { + chunk = new Buffer(24 + data.length); + chunk.writeUInt32LE(command, 0); + chunk.writeUInt32LE(arg0, 4); + chunk.writeUInt32LE(arg1, 8); + chunk.writeUInt32LE(data.length, 12); + chunk.writeUInt32LE(Packet.checksum(data), 16); + chunk.writeUInt32LE(Packet.magic(command), 20); + data.copy(chunk, 24); + return chunk; + } else { + chunk = new Buffer(24); + chunk.writeUInt32LE(command, 0); + chunk.writeUInt32LE(arg0, 4); + chunk.writeUInt32LE(arg1, 8); + chunk.writeUInt32LE(0, 12); + chunk.writeUInt32LE(0, 16); + chunk.writeUInt32LE(Packet.magic(command), 20); + return chunk; + } + }; + + Packet.swap32 = function(n) { + var buffer; + buffer = new Buffer(4); + buffer.writeUInt32LE(n, 0); + return buffer.readUInt32BE(0); + }; + + function Packet(command1, arg01, arg11, length, check, magic, data1) { + this.command = command1; + this.arg0 = arg01; + this.arg1 = arg11; + this.length = length; + this.check = check; + this.magic = magic; + this.data = data1; + } + + Packet.prototype.verifyChecksum = function() { + return this.check === Packet.checksum(this.data); + }; + + Packet.prototype.verifyMagic = function() { + return this.magic === Packet.magic(this.command); + }; + + Packet.prototype.toString = function() { + var type; + type = (function() { + switch (this.command) { + case Packet.A_SYNC: + return "SYNC"; + case Packet.A_CNXN: + return "CNXN"; + case Packet.A_OPEN: + return "OPEN"; + case Packet.A_OKAY: + return "OKAY"; + case Packet.A_CLSE: + return "CLSE"; + case Packet.A_WRTE: + return "WRTE"; + case Packet.A_AUTH: + return "AUTH"; + default: + throw new Error("Unknown command {@command}"); + } + }).call(this); + return type + " arg0=" + this.arg0 + " arg1=" + this.arg1 + " length=" + this.length; + }; + + return Packet; + +})(); + +module.exports = Packet; diff --git a/src/adb/tcpusb/packetreader.coffee b/src/adb/tcpusb/packetreader.coffee deleted file mode 100644 index bc85d820..00000000 --- a/src/adb/tcpusb/packetreader.coffee +++ /dev/null @@ -1,75 +0,0 @@ -{EventEmitter} = require 'events' - -Packet = require './packet' - -class PacketReader extends EventEmitter - constructor: (@stream) -> - super() - @inBody = false - @buffer = null - @packet = null - @stream.on 'readable', this._tryRead.bind(this) - @stream.on 'error', (err) => this.emit 'error', err - @stream.on 'end', => this.emit 'end' - setImmediate this._tryRead.bind(this) - - _tryRead: -> - while this._appendChunk() - while @buffer - if @inBody - break unless @buffer.length >= @packet.length - @packet.data = this._consume(@packet.length) - unless @packet.verifyChecksum() - this.emit 'error', new PacketReader.ChecksumError(@packet) - return - this.emit 'packet', @packet - @inBody = false - else - break unless @buffer.length >= 24 - header = this._consume(24) - @packet = new Packet( - header.readUInt32LE 0 - header.readUInt32LE 4 - header.readUInt32LE 8 - header.readUInt32LE 12 - header.readUInt32LE 16 - header.readUInt32LE 20 - new Buffer(0) - ) - unless @packet.verifyMagic() - this.emit 'error', new PacketReader.MagicError(@packet) - return - if @packet.length is 0 - this.emit 'packet', @packet - else - @inBody = true - - _appendChunk: -> - if chunk = @stream.read() - if @buffer - @buffer = Buffer.concat([@buffer, chunk], @buffer.length + chunk.length) - else - @buffer = chunk - else - null - - _consume: (length) -> - chunk = @buffer.slice(0, length) - @buffer = if length is @buffer.length then null else @buffer.slice(length) - chunk - -class PacketReader.ChecksumError extends Error - constructor: (@packet) -> - Error.call this - @name = 'ChecksumError' - @message = "Checksum mismatch" - Error.captureStackTrace this, PacketReader.ChecksumError - -class PacketReader.MagicError extends Error - constructor: (@packet) -> - Error.call this - @name = 'MagicError' - @message = "Magic value mismatch" - Error.captureStackTrace this, PacketReader.MagicError - -module.exports = PacketReader diff --git a/src/adb/tcpusb/packetreader.js b/src/adb/tcpusb/packetreader.js new file mode 100644 index 00000000..f0824bff --- /dev/null +++ b/src/adb/tcpusb/packetreader.js @@ -0,0 +1,121 @@ +var EventEmitter, Packet, PacketReader, + extend = function(child, parent) { for (var key in parent) { if (hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; }, + hasProp = {}.hasOwnProperty; + +EventEmitter = require('events').EventEmitter; + +Packet = require('./packet'); + +PacketReader = (function(superClass) { + extend(PacketReader, superClass); + + function PacketReader(stream) { + this.stream = stream; + PacketReader.__super__.constructor.call(this); + this.inBody = false; + this.buffer = null; + this.packet = null; + this.stream.on('readable', this._tryRead.bind(this)); + this.stream.on('error', (function(_this) { + return function(err) { + return _this.emit('error', err); + }; + })(this)); + this.stream.on('end', (function(_this) { + return function() { + return _this.emit('end'); + }; + })(this)); + setImmediate(this._tryRead.bind(this)); + } + + PacketReader.prototype._tryRead = function() { + var header; + while (this._appendChunk()) { + while (this.buffer) { + if (this.inBody) { + if (!(this.buffer.length >= this.packet.length)) { + break; + } + this.packet.data = this._consume(this.packet.length); + if (!this.packet.verifyChecksum()) { + this.emit('error', new PacketReader.ChecksumError(this.packet)); + return; + } + this.emit('packet', this.packet); + this.inBody = false; + } else { + if (!(this.buffer.length >= 24)) { + break; + } + header = this._consume(24); + this.packet = new Packet(header.readUInt32LE(0), header.readUInt32LE(4), header.readUInt32LE(8), header.readUInt32LE(12), header.readUInt32LE(16), header.readUInt32LE(20), new Buffer(0)); + if (!this.packet.verifyMagic()) { + this.emit('error', new PacketReader.MagicError(this.packet)); + return; + } + if (this.packet.length === 0) { + this.emit('packet', this.packet); + } else { + this.inBody = true; + } + } + } + } + }; + + PacketReader.prototype._appendChunk = function() { + var chunk; + if (chunk = this.stream.read()) { + if (this.buffer) { + return this.buffer = Buffer.concat([this.buffer, chunk], this.buffer.length + chunk.length); + } else { + return this.buffer = chunk; + } + } else { + return null; + } + }; + + PacketReader.prototype._consume = function(length) { + var chunk; + chunk = this.buffer.slice(0, length); + this.buffer = length === this.buffer.length ? null : this.buffer.slice(length); + return chunk; + }; + + return PacketReader; + +})(EventEmitter); + +PacketReader.ChecksumError = (function(superClass) { + extend(ChecksumError, superClass); + + function ChecksumError(packet) { + this.packet = packet; + Error.call(this); + this.name = 'ChecksumError'; + this.message = "Checksum mismatch"; + Error.captureStackTrace(this, PacketReader.ChecksumError); + } + + return ChecksumError; + +})(Error); + +PacketReader.MagicError = (function(superClass) { + extend(MagicError, superClass); + + function MagicError(packet) { + this.packet = packet; + Error.call(this); + this.name = 'MagicError'; + this.message = "Magic value mismatch"; + Error.captureStackTrace(this, PacketReader.MagicError); + } + + return MagicError; + +})(Error); + +module.exports = PacketReader; diff --git a/src/adb/tcpusb/rollingcounter.coffee b/src/adb/tcpusb/rollingcounter.coffee deleted file mode 100644 index 35b53a01..00000000 --- a/src/adb/tcpusb/rollingcounter.coffee +++ /dev/null @@ -1,9 +0,0 @@ -class RollingCounter - constructor: (@max, @min = 1) -> - @now = @min - - next: -> - @now = @min unless @now < @max - return ++@now - -module.exports = RollingCounter diff --git a/src/adb/tcpusb/rollingcounter.js b/src/adb/tcpusb/rollingcounter.js new file mode 100644 index 00000000..e3812c31 --- /dev/null +++ b/src/adb/tcpusb/rollingcounter.js @@ -0,0 +1,21 @@ +var RollingCounter; + +RollingCounter = (function() { + function RollingCounter(max, min) { + this.max = max; + this.min = min != null ? min : 1; + this.now = this.min; + } + + RollingCounter.prototype.next = function() { + if (!(this.now < this.max)) { + this.now = this.min; + } + return ++this.now; + }; + + return RollingCounter; + +})(); + +module.exports = RollingCounter; diff --git a/src/adb/tcpusb/server.coffee b/src/adb/tcpusb/server.coffee deleted file mode 100644 index 7e643110..00000000 --- a/src/adb/tcpusb/server.coffee +++ /dev/null @@ -1,39 +0,0 @@ -Net = require 'net' -{EventEmitter} = require 'events' - -Socket = require './socket' - -class Server extends EventEmitter - constructor: (@client, @serial, @options) -> - @connections = [] - @server = Net.createServer allowHalfOpen: true - @server.on 'error', (err) => - this.emit 'error', err - @server.on 'listening', => - this.emit 'listening' - @server.on 'close', => - this.emit 'close' - @server.on 'connection', (conn) => - socket = new Socket @client, @serial, conn, @options - @connections.push socket - socket.on 'error', (err) => - # 'conn' is guaranteed to get ended - this.emit 'error', err - socket.once 'end', => - # 'conn' is guaranteed to get ended - @connections = @connections.filter (val) -> val isnt socket - this.emit 'connection', socket - - listen: -> - @server.listen.apply @server, arguments - return this - - close: -> - @server.close() - return this - - end: -> - conn.end() for conn in @connections - return this - -module.exports = Server diff --git a/src/adb/tcpusb/server.js b/src/adb/tcpusb/server.js new file mode 100644 index 00000000..41b3379f --- /dev/null +++ b/src/adb/tcpusb/server.js @@ -0,0 +1,79 @@ +var EventEmitter, Net, Server, Socket, + extend = function(child, parent) { for (var key in parent) { if (hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; }, + hasProp = {}.hasOwnProperty; + +Net = require('net'); + +EventEmitter = require('events').EventEmitter; + +Socket = require('./socket'); + +Server = (function(superClass) { + extend(Server, superClass); + + function Server(client, serial, options) { + this.client = client; + this.serial = serial; + this.options = options; + this.connections = []; + this.server = Net.createServer({ + allowHalfOpen: true + }); + this.server.on('error', (function(_this) { + return function(err) { + return _this.emit('error', err); + }; + })(this)); + this.server.on('listening', (function(_this) { + return function() { + return _this.emit('listening'); + }; + })(this)); + this.server.on('close', (function(_this) { + return function() { + return _this.emit('close'); + }; + })(this)); + this.server.on('connection', (function(_this) { + return function(conn) { + var socket; + socket = new Socket(_this.client, _this.serial, conn, _this.options); + _this.connections.push(socket); + socket.on('error', function(err) { + return _this.emit('error', err); + }); + socket.once('end', function() { + return _this.connections = _this.connections.filter(function(val) { + return val !== socket; + }); + }); + return _this.emit('connection', socket); + }; + })(this)); + } + + Server.prototype.listen = function() { + this.server.listen.apply(this.server, arguments); + return this; + }; + + Server.prototype.close = function() { + this.server.close(); + return this; + }; + + Server.prototype.end = function() { + var conn, i, len, ref; + ref = this.connections; + for (i = 0, len = ref.length; i < len; i++) { + conn = ref[i]; + conn.end(); + } + return this; + }; + + return Server; + +})(EventEmitter); + +module.exports = Server; diff --git a/src/adb/tcpusb/service.coffee b/src/adb/tcpusb/service.coffee deleted file mode 100644 index 5c01fcce..00000000 --- a/src/adb/tcpusb/service.coffee +++ /dev/null @@ -1,125 +0,0 @@ -{EventEmitter} = require 'events' - -Promise = require 'bluebird' -debug = require('debug')('adb:tcpusb:service') - -Parser = require '../parser' -Protocol = require '../protocol' -Packet = require './packet' - -class Service extends EventEmitter - constructor: (@client, @serial, @localId, @remoteId, @socket) -> - super() - @opened = false - @ended = false - @transport = null - @needAck = false - - end: -> - @transport.end() if @transport - return this if @ended - debug 'O:A_CLSE' - localId = if @opened then @localId else 0 # Zero can only mean a failed open - try - # We may or may not have gotten here due to @socket ending, so write - # may fail. - @socket.write Packet.assemble(Packet.A_CLSE, localId, @remoteId, null) - catch err - # Let it go - @transport = null - @ended = true - this.emit 'end' - return this - - handle: (packet) -> - Promise.try => - switch packet.command - when Packet.A_OPEN - this._handleOpenPacket(packet) - when Packet.A_OKAY - this._handleOkayPacket(packet) - when Packet.A_WRTE - this._handleWritePacket(packet) - when Packet.A_CLSE - this._handleClosePacket(packet) - else - throw new Error "Unexpected packet #{packet.command}" - .catch (err) => - this.emit 'error', err - this.end() - - _handleOpenPacket: (packet) -> - debug 'I:A_OPEN', packet - @client.transport @serial - .then (@transport) => - throw new LateTransportError() if @ended - @transport.write Protocol.encodeData \ - packet.data.slice(0, -1) # Discard null byte at end - @transport.parser.readAscii 4 - .then (reply) => - switch reply - when Protocol.OKAY - debug 'O:A_OKAY' - @socket.write \ - Packet.assemble(Packet.A_OKAY, @localId, @remoteId, null) - @opened = true - when Protocol.FAIL - @transport.parser.readError() - else - @transport.parser.unexpected reply, 'OKAY or FAIL' - .then => - new Promise (resolve, reject) => - @transport.socket - .on 'readable', => this._tryPush() - .on 'end', resolve - .on 'error', reject - this._tryPush() - .finally => - this.end() - - _handleOkayPacket: (packet) -> - debug 'I:A_OKAY', packet - return if @ended - throw new Service.PrematurePacketError(packet) unless @transport - @needAck = false - this._tryPush() - - _handleWritePacket: (packet) -> - debug 'I:A_WRTE', packet - return if @ended - throw new Service.PrematurePacketError(packet) unless @transport - @transport.write packet.data if packet.data - debug 'O:A_OKAY' - @socket.write Packet.assemble(Packet.A_OKAY, @localId, @remoteId, null) - - _handleClosePacket: (packet) -> - debug 'I:A_CLSE', packet - return if @ended - throw new Service.PrematurePacketError(packet) unless @transport - this.end() - - _tryPush: -> - return if @needAck or @ended - if chunk = this._readChunk(@transport.socket) - debug 'O:A_WRTE' - @socket.write Packet.assemble(Packet.A_WRTE, @localId, @remoteId, chunk) - @needAck = true - - _readChunk: (stream) -> - stream.read(@socket.maxPayload) or stream.read() - -class Service.PrematurePacketError extends Error - constructor: (@packet) -> - Error.call this - @name = 'PrematurePacketError' - @message = "Premature packet" - Error.captureStackTrace this, Service.PrematurePacketError - -class Service.LateTransportError extends Error - constructor: -> - Error.call this - @name = 'LateTransportError' - @message = "Late transport" - Error.captureStackTrace this, Service.LateTransportError - -module.exports = Service diff --git a/src/adb/tcpusb/service.js b/src/adb/tcpusb/service.js new file mode 100644 index 00000000..f9f46292 --- /dev/null +++ b/src/adb/tcpusb/service.js @@ -0,0 +1,203 @@ +var EventEmitter, Packet, Parser, Promise, Protocol, Service, debug, + extend = function(child, parent) { for (var key in parent) { if (hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; }, + hasProp = {}.hasOwnProperty; + +EventEmitter = require('events').EventEmitter; + +Promise = require('bluebird'); + +debug = require('debug')('adb:tcpusb:service'); + +Parser = require('../parser'); + +Protocol = require('../protocol'); + +Packet = require('./packet'); + +Service = (function(superClass) { + extend(Service, superClass); + + function Service(client, serial, localId1, remoteId, socket) { + this.client = client; + this.serial = serial; + this.localId = localId1; + this.remoteId = remoteId; + this.socket = socket; + Service.__super__.constructor.call(this); + this.opened = false; + this.ended = false; + this.transport = null; + this.needAck = false; + } + + Service.prototype.end = function() { + var err, localId; + if (this.transport) { + this.transport.end(); + } + if (this.ended) { + return this; + } + debug('O:A_CLSE'); + localId = this.opened ? this.localId : 0; + try { + this.socket.write(Packet.assemble(Packet.A_CLSE, localId, this.remoteId, null)); + } catch (_error) { + err = _error; + } + this.transport = null; + this.ended = true; + this.emit('end'); + return this; + }; + + Service.prototype.handle = function(packet) { + return Promise["try"]((function(_this) { + return function() { + switch (packet.command) { + case Packet.A_OPEN: + return _this._handleOpenPacket(packet); + case Packet.A_OKAY: + return _this._handleOkayPacket(packet); + case Packet.A_WRTE: + return _this._handleWritePacket(packet); + case Packet.A_CLSE: + return _this._handleClosePacket(packet); + default: + throw new Error("Unexpected packet " + packet.command); + } + }; + })(this))["catch"]((function(_this) { + return function(err) { + _this.emit('error', err); + return _this.end(); + }; + })(this)); + }; + + Service.prototype._handleOpenPacket = function(packet) { + debug('I:A_OPEN', packet); + return this.client.transport(this.serial).then((function(_this) { + return function(transport) { + _this.transport = transport; + if (_this.ended) { + throw new LateTransportError(); + } + _this.transport.write(Protocol.encodeData(packet.data.slice(0, -1))); + return _this.transport.parser.readAscii(4).then(function(reply) { + switch (reply) { + case Protocol.OKAY: + debug('O:A_OKAY'); + _this.socket.write(Packet.assemble(Packet.A_OKAY, _this.localId, _this.remoteId, null)); + return _this.opened = true; + case Protocol.FAIL: + return _this.transport.parser.readError(); + default: + return _this.transport.parser.unexpected(reply, 'OKAY or FAIL'); + } + }); + }; + })(this)).then((function(_this) { + return function() { + return new Promise(function(resolve, reject) { + _this.transport.socket.on('readable', function() { + return _this._tryPush(); + }).on('end', resolve).on('error', reject); + return _this._tryPush(); + }); + }; + })(this))["finally"]((function(_this) { + return function() { + return _this.end(); + }; + })(this)); + }; + + Service.prototype._handleOkayPacket = function(packet) { + debug('I:A_OKAY', packet); + if (this.ended) { + return; + } + if (!this.transport) { + throw new Service.PrematurePacketError(packet); + } + this.needAck = false; + return this._tryPush(); + }; + + Service.prototype._handleWritePacket = function(packet) { + debug('I:A_WRTE', packet); + if (this.ended) { + return; + } + if (!this.transport) { + throw new Service.PrematurePacketError(packet); + } + if (packet.data) { + this.transport.write(packet.data); + } + debug('O:A_OKAY'); + return this.socket.write(Packet.assemble(Packet.A_OKAY, this.localId, this.remoteId, null)); + }; + + Service.prototype._handleClosePacket = function(packet) { + debug('I:A_CLSE', packet); + if (this.ended) { + return; + } + if (!this.transport) { + throw new Service.PrematurePacketError(packet); + } + return this.end(); + }; + + Service.prototype._tryPush = function() { + var chunk; + if (this.needAck || this.ended) { + return; + } + if (chunk = this._readChunk(this.transport.socket)) { + debug('O:A_WRTE'); + this.socket.write(Packet.assemble(Packet.A_WRTE, this.localId, this.remoteId, chunk)); + return this.needAck = true; + } + }; + + Service.prototype._readChunk = function(stream) { + return stream.read(this.socket.maxPayload) || stream.read(); + }; + + return Service; + +})(EventEmitter); + +Service.PrematurePacketError = (function(superClass) { + extend(PrematurePacketError, superClass); + + function PrematurePacketError(packet1) { + this.packet = packet1; + Error.call(this); + this.name = 'PrematurePacketError'; + this.message = "Premature packet"; + Error.captureStackTrace(this, Service.PrematurePacketError); + } + + return PrematurePacketError; + +})(Error); + +Service.LateTransportError = (function(superClass) { + extend(LateTransportError, superClass); + + function LateTransportError() { + Error.call(this); + this.name = 'LateTransportError'; + this.message = "Late transport"; + Error.captureStackTrace(this, Service.LateTransportError); + } + + return LateTransportError; + +})(Error); + +module.exports = Service; diff --git a/src/adb/tcpusb/servicemap.coffee b/src/adb/tcpusb/servicemap.coffee deleted file mode 100644 index f8750246..00000000 --- a/src/adb/tcpusb/servicemap.coffee +++ /dev/null @@ -1,31 +0,0 @@ -class ServiceMap - constructor: -> - @remotes = Object.create null - @count = 0 - - end: -> - for remoteId, remote of @remotes - remote.end() - @remotes = Object.create null - @count = 0 - return - - insert: (remoteId, socket) -> - if @remotes[remoteId] - throw new Error "Remote ID #{remoteId} is already being used" - else - @count += 1 - @remotes[remoteId] = socket - - get: (remoteId) -> - @remotes[remoteId] or null - - remove: (remoteId) -> - if remote = @remotes[remoteId] - delete @remotes[remoteId] - @count -= 1 - remote - else - null - -module.exports = ServiceMap diff --git a/src/adb/tcpusb/servicemap.js b/src/adb/tcpusb/servicemap.js new file mode 100644 index 00000000..5ed59b31 --- /dev/null +++ b/src/adb/tcpusb/servicemap.js @@ -0,0 +1,48 @@ +var ServiceMap; + +ServiceMap = (function() { + function ServiceMap() { + this.remotes = Object.create(null); + this.count = 0; + } + + ServiceMap.prototype.end = function() { + var ref, remote, remoteId; + ref = this.remotes; + for (remoteId in ref) { + remote = ref[remoteId]; + remote.end(); + } + this.remotes = Object.create(null); + this.count = 0; + }; + + ServiceMap.prototype.insert = function(remoteId, socket) { + if (this.remotes[remoteId]) { + throw new Error("Remote ID " + remoteId + " is already being used"); + } else { + this.count += 1; + return this.remotes[remoteId] = socket; + } + }; + + ServiceMap.prototype.get = function(remoteId) { + return this.remotes[remoteId] || null; + }; + + ServiceMap.prototype.remove = function(remoteId) { + var remote; + if (remote = this.remotes[remoteId]) { + delete this.remotes[remoteId]; + this.count -= 1; + return remote; + } else { + return null; + } + }; + + return ServiceMap; + +})(); + +module.exports = ServiceMap; diff --git a/src/adb/tcpusb/socket.coffee b/src/adb/tcpusb/socket.coffee deleted file mode 100644 index 9d5791ce..00000000 --- a/src/adb/tcpusb/socket.coffee +++ /dev/null @@ -1,204 +0,0 @@ -crypto = require 'crypto' -{EventEmitter} = require 'events' - -Promise = require 'bluebird' -Forge = require 'node-forge' -debug = require('debug')('adb:tcpusb:socket') - -Parser = require '../parser' -Protocol = require '../protocol' -Auth = require '../auth' -Packet = require './packet' -PacketReader = require './packetreader' -Service = require './service' -ServiceMap = require './servicemap' -RollingCounter = require './rollingcounter' - -class Socket extends EventEmitter - UINT32_MAX = 0xFFFFFFFF - UINT16_MAX = 0xFFFF - - AUTH_TOKEN = 1 - AUTH_SIGNATURE = 2 - AUTH_RSAPUBLICKEY = 3 - - TOKEN_LENGTH = 20 - - constructor: (@client, @serial, @socket, @options = {}) -> - @options.auth or= Promise.resolve true - @ended = false - @socket.setNoDelay true - @reader = new PacketReader @socket - .on 'packet', this._handle.bind(this) - .on 'error', (err) => - debug "PacketReader error: #{err.message}" - this.end() - .on 'end', this.end.bind(this) - @version = 1 - @maxPayload = 4096 - @authorized = false - @syncToken = new RollingCounter UINT32_MAX - @remoteId = new RollingCounter UINT32_MAX - @services = new ServiceMap - @remoteAddress = @socket.remoteAddress - @token = null - @signature = null - - end: -> - return this if @ended - # End services first so that they can send a final payload before FIN. - @services.end() - @socket.end() - @ended = true - this.emit 'end' - return this - - _error: (err) -> - this.emit 'error', err - this.end() - - _handle: (packet) -> - return if @ended - this.emit 'userActivity', packet - Promise.try => - switch packet.command - when Packet.A_SYNC - this._handleSyncPacket packet - when Packet.A_CNXN - this._handleConnectionPacket packet - when Packet.A_OPEN - this._handleOpenPacket packet - when Packet.A_OKAY, Packet.A_WRTE, Packet.A_CLSE - this._forwardServicePacket packet - when Packet.A_AUTH - this._handleAuthPacket packet - else - throw new Error "Unknown command #{packet.command}" - .catch Socket.AuthError, => - this.end() - .catch Socket.UnauthorizedError, => - this.end() - .catch (err) => - this._error err - - _handleSyncPacket: (packet) -> - # No need to do anything? - debug 'I:A_SYNC' - debug 'O:A_SYNC' - this.write Packet.assemble(Packet.A_SYNC, 1, @syncToken.next(), null) - - _handleConnectionPacket: (packet) -> - debug 'I:A_CNXN', packet - version = Packet.swap32(packet.arg0) - @maxPayload = Math.min UINT16_MAX, packet.arg1 - this._createToken() - .then (@token) => - debug "Created challenge '#{@token.toString('base64')}'" - debug 'O:A_AUTH' - this.write Packet.assemble(Packet.A_AUTH, AUTH_TOKEN, 0, @token) - - _handleAuthPacket: (packet) -> - debug 'I:A_AUTH', packet - switch packet.arg0 - when AUTH_SIGNATURE - # Store first signature, ignore the rest - debug "Received signature '#{packet.data.toString('base64')}'" - @signature = packet.data unless @signature - debug 'O:A_AUTH' - this.write Packet.assemble(Packet.A_AUTH, AUTH_TOKEN, 0, @token) - when AUTH_RSAPUBLICKEY - unless @signature - throw new Socket.AuthError "Public key sent before signature" - unless packet.data and packet.data.length >= 2 - throw new Socket.AuthError "Empty RSA public key" - debug "Received RSA public key '#{packet.data.toString('base64')}'" - Auth.parsePublicKey this._skipNull(packet.data) - .then (key) => - digest = @token.toString 'binary' - sig = @signature.toString 'binary' - unless key.verify digest, sig - debug "Signature mismatch" - throw new Socket.AuthError "Signature mismatch" - debug "Signature verified" - key - .then (key) => - @options.auth key - .catch (err) -> - debug "Connection rejected by user-defined auth handler" - throw new Socket.AuthError "Rejected by user-defined handler" - .then => - this._deviceId() - .then (id) => - @authorized = true - debug 'O:A_CNXN' - this.write Packet.assemble(Packet.A_CNXN, - Packet.swap32(@version), @maxPayload, id) - else - throw new Error "Unknown authentication method #{packet.arg0}" - - _handleOpenPacket: (packet) -> - throw new Socket.UnauthorizedError() unless @authorized - remoteId = packet.arg0 - localId = @remoteId.next() - unless packet.data and packet.data.length >= 2 - throw new Error "Empty service name" - name = this._skipNull(packet.data) - debug "Calling #{name}" - service = new Service @client, @serial, localId, remoteId, this - new Promise (resolve, reject) => - service.on 'error', reject - service.on 'end', resolve - @services.insert localId, service - debug "Handling #{@services.count} services simultaneously" - service.handle packet - .catch (err) -> - true - .finally => - @services.remove localId - debug "Handling #{@services.count} services simultaneously" - service.end() - - _forwardServicePacket: (packet) -> - throw new Socket.UnauthorizedError() unless @authorized - remoteId = packet.arg0 - localId = packet.arg1 - if service = @services.get localId - service.handle packet - else - debug "Received a packet to a service that may have been closed already" - - write: (chunk) -> - return if @ended - @socket.write chunk - - _createToken: -> - Promise.promisify(crypto.randomBytes)(TOKEN_LENGTH) - - _skipNull: (data) -> - data.slice 0, -1 # Discard null byte at end - - _deviceId: -> - debug "Loading device properties to form a standard device ID" - @client.getProperties @serial - .then (properties) -> - id = ("#{prop}=#{properties[prop]};" for prop in [ - 'ro.product.name' - 'ro.product.model' - 'ro.product.device' - ]).join('') - new Buffer "device::#{id}\0" - -class Socket.AuthError extends Error - constructor: (@message) -> - Error.call this - @name = 'AuthError' - Error.captureStackTrace this, Socket.AuthError - -class Socket.UnauthorizedError extends Error - constructor: -> - Error.call this - @name = 'UnauthorizedError' - @message = "Unauthorized access" - Error.captureStackTrace this, Socket.UnauthorizedError - -module.exports = Socket diff --git a/src/adb/tcpusb/socket.js b/src/adb/tcpusb/socket.js new file mode 100644 index 00000000..7c75000f --- /dev/null +++ b/src/adb/tcpusb/socket.js @@ -0,0 +1,314 @@ +var Auth, EventEmitter, Forge, Packet, PacketReader, Parser, Promise, Protocol, RollingCounter, Service, ServiceMap, Socket, crypto, debug, + extend = function(child, parent) { for (var key in parent) { if (hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; }, + hasProp = {}.hasOwnProperty; + +crypto = require('crypto'); + +EventEmitter = require('events').EventEmitter; + +Promise = require('bluebird'); + +Forge = require('node-forge'); + +debug = require('debug')('adb:tcpusb:socket'); + +Parser = require('../parser'); + +Protocol = require('../protocol'); + +Auth = require('../auth'); + +Packet = require('./packet'); + +PacketReader = require('./packetreader'); + +Service = require('./service'); + +ServiceMap = require('./servicemap'); + +RollingCounter = require('./rollingcounter'); + +Socket = (function(superClass) { + var AUTH_RSAPUBLICKEY, AUTH_SIGNATURE, AUTH_TOKEN, TOKEN_LENGTH, UINT16_MAX, UINT32_MAX; + + extend(Socket, superClass); + + UINT32_MAX = 0xFFFFFFFF; + + UINT16_MAX = 0xFFFF; + + AUTH_TOKEN = 1; + + AUTH_SIGNATURE = 2; + + AUTH_RSAPUBLICKEY = 3; + + TOKEN_LENGTH = 20; + + function Socket(client, serial, socket, options) { + var base; + this.client = client; + this.serial = serial; + this.socket = socket; + this.options = options != null ? options : {}; + (base = this.options).auth || (base.auth = Promise.resolve(true)); + this.ended = false; + this.socket.setNoDelay(true); + this.reader = new PacketReader(this.socket).on('packet', this._handle.bind(this)).on('error', (function(_this) { + return function(err) { + debug("PacketReader error: " + err.message); + return _this.end(); + }; + })(this)).on('end', this.end.bind(this)); + this.version = 1; + this.maxPayload = 4096; + this.authorized = false; + this.syncToken = new RollingCounter(UINT32_MAX); + this.remoteId = new RollingCounter(UINT32_MAX); + this.services = new ServiceMap; + this.remoteAddress = this.socket.remoteAddress; + this.token = null; + this.signature = null; + } + + Socket.prototype.end = function() { + if (this.ended) { + return this; + } + this.services.end(); + this.socket.end(); + this.ended = true; + this.emit('end'); + return this; + }; + + Socket.prototype._error = function(err) { + this.emit('error', err); + return this.end(); + }; + + Socket.prototype._handle = function(packet) { + if (this.ended) { + return; + } + this.emit('userActivity', packet); + return Promise["try"]((function(_this) { + return function() { + switch (packet.command) { + case Packet.A_SYNC: + return _this._handleSyncPacket(packet); + case Packet.A_CNXN: + return _this._handleConnectionPacket(packet); + case Packet.A_OPEN: + return _this._handleOpenPacket(packet); + case Packet.A_OKAY: + case Packet.A_WRTE: + case Packet.A_CLSE: + return _this._forwardServicePacket(packet); + case Packet.A_AUTH: + return _this._handleAuthPacket(packet); + default: + throw new Error("Unknown command " + packet.command); + } + }; + })(this))["catch"](Socket.AuthError, (function(_this) { + return function() { + return _this.end(); + }; + })(this))["catch"](Socket.UnauthorizedError, (function(_this) { + return function() { + return _this.end(); + }; + })(this))["catch"]((function(_this) { + return function(err) { + return _this._error(err); + }; + })(this)); + }; + + Socket.prototype._handleSyncPacket = function(packet) { + debug('I:A_SYNC'); + debug('O:A_SYNC'); + return this.write(Packet.assemble(Packet.A_SYNC, 1, this.syncToken.next(), null)); + }; + + Socket.prototype._handleConnectionPacket = function(packet) { + var version; + debug('I:A_CNXN', packet); + version = Packet.swap32(packet.arg0); + this.maxPayload = Math.min(UINT16_MAX, packet.arg1); + return this._createToken().then((function(_this) { + return function(token) { + _this.token = token; + debug("Created challenge '" + (_this.token.toString('base64')) + "'"); + debug('O:A_AUTH'); + return _this.write(Packet.assemble(Packet.A_AUTH, AUTH_TOKEN, 0, _this.token)); + }; + })(this)); + }; + + Socket.prototype._handleAuthPacket = function(packet) { + debug('I:A_AUTH', packet); + switch (packet.arg0) { + case AUTH_SIGNATURE: + debug("Received signature '" + (packet.data.toString('base64')) + "'"); + if (!this.signature) { + this.signature = packet.data; + } + debug('O:A_AUTH'); + return this.write(Packet.assemble(Packet.A_AUTH, AUTH_TOKEN, 0, this.token)); + case AUTH_RSAPUBLICKEY: + if (!this.signature) { + throw new Socket.AuthError("Public key sent before signature"); + } + if (!(packet.data && packet.data.length >= 2)) { + throw new Socket.AuthError("Empty RSA public key"); + } + debug("Received RSA public key '" + (packet.data.toString('base64')) + "'"); + return Auth.parsePublicKey(this._skipNull(packet.data)).then((function(_this) { + return function(key) { + var digest, sig; + digest = _this.token.toString('binary'); + sig = _this.signature.toString('binary'); + if (!key.verify(digest, sig)) { + debug("Signature mismatch"); + throw new Socket.AuthError("Signature mismatch"); + } + debug("Signature verified"); + return key; + }; + })(this)).then((function(_this) { + return function(key) { + return _this.options.auth(key)["catch"](function(err) { + debug("Connection rejected by user-defined auth handler"); + throw new Socket.AuthError("Rejected by user-defined handler"); + }); + }; + })(this)).then((function(_this) { + return function() { + return _this._deviceId(); + }; + })(this)).then((function(_this) { + return function(id) { + _this.authorized = true; + debug('O:A_CNXN'); + return _this.write(Packet.assemble(Packet.A_CNXN, Packet.swap32(_this.version), _this.maxPayload, id)); + }; + })(this)); + default: + throw new Error("Unknown authentication method " + packet.arg0); + } + }; + + Socket.prototype._handleOpenPacket = function(packet) { + var localId, name, remoteId, service; + if (!this.authorized) { + throw new Socket.UnauthorizedError(); + } + remoteId = packet.arg0; + localId = this.remoteId.next(); + if (!(packet.data && packet.data.length >= 2)) { + throw new Error("Empty service name"); + } + name = this._skipNull(packet.data); + debug("Calling " + name); + service = new Service(this.client, this.serial, localId, remoteId, this); + return new Promise((function(_this) { + return function(resolve, reject) { + service.on('error', reject); + service.on('end', resolve); + _this.services.insert(localId, service); + debug("Handling " + _this.services.count + " services simultaneously"); + return service.handle(packet); + }; + })(this))["catch"](function(err) { + return true; + })["finally"]((function(_this) { + return function() { + _this.services.remove(localId); + debug("Handling " + _this.services.count + " services simultaneously"); + return service.end(); + }; + })(this)); + }; + + Socket.prototype._forwardServicePacket = function(packet) { + var localId, remoteId, service; + if (!this.authorized) { + throw new Socket.UnauthorizedError(); + } + remoteId = packet.arg0; + localId = packet.arg1; + if (service = this.services.get(localId)) { + return service.handle(packet); + } else { + return debug("Received a packet to a service that may have been closed already"); + } + }; + + Socket.prototype.write = function(chunk) { + if (this.ended) { + return; + } + return this.socket.write(chunk); + }; + + Socket.prototype._createToken = function() { + return Promise.promisify(crypto.randomBytes)(TOKEN_LENGTH); + }; + + Socket.prototype._skipNull = function(data) { + return data.slice(0, -1); + }; + + Socket.prototype._deviceId = function() { + debug("Loading device properties to form a standard device ID"); + return this.client.getProperties(this.serial).then(function(properties) { + var id, prop; + id = ((function() { + var i, len, ref, results; + ref = ['ro.product.name', 'ro.product.model', 'ro.product.device']; + results = []; + for (i = 0, len = ref.length; i < len; i++) { + prop = ref[i]; + results.push(prop + "=" + properties[prop] + ";"); + } + return results; + })()).join(''); + return new Buffer("device::" + id + "\0"); + }); + }; + + return Socket; + +})(EventEmitter); + +Socket.AuthError = (function(superClass) { + extend(AuthError, superClass); + + function AuthError(message) { + this.message = message; + Error.call(this); + this.name = 'AuthError'; + Error.captureStackTrace(this, Socket.AuthError); + } + + return AuthError; + +})(Error); + +Socket.UnauthorizedError = (function(superClass) { + extend(UnauthorizedError, superClass); + + function UnauthorizedError() { + Error.call(this); + this.name = 'UnauthorizedError'; + this.message = "Unauthorized access"; + Error.captureStackTrace(this, Socket.UnauthorizedError); + } + + return UnauthorizedError; + +})(Error); + +module.exports = Socket; diff --git a/src/adb/tracker.coffee b/src/adb/tracker.coffee deleted file mode 100644 index 2fdadbce..00000000 --- a/src/adb/tracker.coffee +++ /dev/null @@ -1,59 +0,0 @@ -{EventEmitter} = require 'events' -Promise = require 'bluebird' - -Parser = require './parser' - -class Tracker extends EventEmitter - constructor: (@command) -> - @deviceList = [] - @deviceMap = {} - @reader = this.read() - .catch Promise.CancellationError, -> - true - .catch Parser.PrematureEOFError, -> - throw new Error 'Connection closed' - .catch (err) => - this.emit 'error', err - return - .finally => - @command.parser.end() - .then => - this.emit 'end' - - read: -> - @command._readDevices() - .cancellable() - .then (list) => - this.update list - this.read() - - update: (newList) -> - changeSet = - removed: [] - changed: [] - added: [] - newMap = {} - for device in newList - oldDevice = @deviceMap[device.id] - if oldDevice - unless oldDevice.type is device.type - changeSet.changed.push device - this.emit 'change', device, oldDevice - else - changeSet.added.push device - this.emit 'add', device - newMap[device.id] = device - for device in @deviceList - unless newMap[device.id] - changeSet.removed.push device - this.emit 'remove', device - this.emit 'changeSet', changeSet - @deviceList = newList - @deviceMap = newMap - return this - - end: -> - @reader.cancel() - return this - -module.exports = Tracker diff --git a/src/adb/tracker.js b/src/adb/tracker.js new file mode 100644 index 00000000..46ecf057 --- /dev/null +++ b/src/adb/tracker.js @@ -0,0 +1,89 @@ +var EventEmitter, Parser, Promise, Tracker, + extend = function(child, parent) { for (var key in parent) { if (hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; }, + hasProp = {}.hasOwnProperty; + +EventEmitter = require('events').EventEmitter; + +Promise = require('bluebird'); + +Parser = require('./parser'); + +Tracker = (function(superClass) { + extend(Tracker, superClass); + + function Tracker(command) { + this.command = command; + this.deviceList = []; + this.deviceMap = {}; + this.reader = this.read()["catch"](Promise.CancellationError, function() { + return true; + })["catch"](Parser.PrematureEOFError, function() { + throw new Error('Connection closed'); + })["catch"]((function(_this) { + return function(err) { + _this.emit('error', err); + }; + })(this))["finally"]((function(_this) { + return function() { + return _this.command.parser.end().then(function() { + return _this.emit('end'); + }); + }; + })(this)); + } + + Tracker.prototype.read = function() { + return this.command._readDevices().cancellable().then((function(_this) { + return function(list) { + _this.update(list); + return _this.read(); + }; + })(this)); + }; + + Tracker.prototype.update = function(newList) { + var changeSet, device, i, j, len, len1, newMap, oldDevice, ref; + changeSet = { + removed: [], + changed: [], + added: [] + }; + newMap = {}; + for (i = 0, len = newList.length; i < len; i++) { + device = newList[i]; + oldDevice = this.deviceMap[device.id]; + if (oldDevice) { + if (oldDevice.type !== device.type) { + changeSet.changed.push(device); + this.emit('change', device, oldDevice); + } + } else { + changeSet.added.push(device); + this.emit('add', device); + } + newMap[device.id] = device; + } + ref = this.deviceList; + for (j = 0, len1 = ref.length; j < len1; j++) { + device = ref[j]; + if (!newMap[device.id]) { + changeSet.removed.push(device); + this.emit('remove', device); + } + } + this.emit('changeSet', changeSet); + this.deviceList = newList; + this.deviceMap = newMap; + return this; + }; + + Tracker.prototype.end = function() { + this.reader.cancel(); + return this; + }; + + return Tracker; + +})(EventEmitter); + +module.exports = Tracker; diff --git a/src/adb/util.coffee b/src/adb/util.coffee deleted file mode 100644 index 9697f8be..00000000 --- a/src/adb/util.coffee +++ /dev/null @@ -1,10 +0,0 @@ -Parser = require './parser' -Auth = require './auth' - -module.exports.readAll = (stream, callback) -> - new Parser(stream).readAll stream - .nodeify callback - -module.exports.parsePublicKey = (keyString, callback) -> - Auth.parsePublicKey keyString - .nodeify callback diff --git a/src/adb/util.js b/src/adb/util.js new file mode 100644 index 00000000..e3bc00a4 --- /dev/null +++ b/src/adb/util.js @@ -0,0 +1,13 @@ +var Auth, Parser; + +Parser = require('./parser'); + +Auth = require('./auth'); + +module.exports.readAll = function(stream, callback) { + return new Parser(stream).readAll(stream).nodeify(callback); +}; + +module.exports.parsePublicKey = function(keyString, callback) { + return Auth.parsePublicKey(keyString).nodeify(callback); +}; diff --git a/src/cli.coffee b/src/cli.coffee deleted file mode 100644 index 9e6c3c1c..00000000 --- a/src/cli.coffee +++ /dev/null @@ -1,61 +0,0 @@ -fs = require 'fs' -program = require 'commander' -Promise = require 'bluebird' -forge = require 'node-forge' - -pkg = require '../package' -Adb = require './adb' -Auth = require './adb/auth' -PacketReader = require './adb/tcpusb/packetreader' - -Promise.longStackTraces() - -program - .version pkg.version - -program - .command 'pubkey-convert ' - .option '-f, --format ', 'format (pem or openssh)', String, 'pem' - .description 'Converts an ADB-generated public key into PEM format.' - .action (file, options) -> - Auth.parsePublicKey fs.readFileSync file - .then (key) -> - switch options.format.toLowerCase() - when 'pem' - console.log forge.pki.publicKeyToPem(key).trim() - when 'openssh' - console.log forge.ssh.publicKeyToOpenSSH(key, 'adbkey').trim() - else - console.error "Unsupported format '#{options.format}'" - process.exit 1 - -program - .command 'pubkey-fingerprint ' - .description 'Outputs the fingerprint of an ADB-generated public key.' - .action (file) -> - Auth.parsePublicKey fs.readFileSync file - .then (key) -> - console.log '%s %s', key.fingerprint, key.comment - -program - .command 'usb-device-to-tcp ' - .option '-p, --port ', 'port number', String, 6174 - .description 'Provides an USB device over TCP using a translating proxy.' - .action (serial, options) -> - adb = Adb.createClient() - server = adb.createTcpUsbBridge(serial, auth: -> Promise.resolve()) - .on 'listening', -> - console.info 'Connect with `adb connect localhost:%d`', options.port - .on 'error', (err) -> - console.error "An error occured: #{err.message}" - server.listen options.port - -program - .command 'parse-tcp-packets ' - .description 'Parses ADB TCP packets from the given file.' - .action (file, options) -> - reader = new PacketReader fs.createReadStream(file) - reader.on 'packet', (packet) -> - console.log packet.toString() - -program.parse process.argv diff --git a/src/cli.js b/src/cli.js new file mode 100644 index 00000000..bc2c5d99 --- /dev/null +++ b/src/cli.js @@ -0,0 +1,66 @@ +var Adb, Auth, PacketReader, Promise, forge, fs, pkg, program; + +fs = require('fs'); + +program = require('commander'); + +Promise = require('bluebird'); + +forge = require('node-forge'); + +pkg = require('../package'); + +Adb = require('./adb'); + +Auth = require('./adb/auth'); + +PacketReader = require('./adb/tcpusb/packetreader'); + +Promise.longStackTraces(); + +program.version(pkg.version); + +program.command('pubkey-convert ').option('-f, --format ', 'format (pem or openssh)', String, 'pem').description('Converts an ADB-generated public key into PEM format.').action(function(file, options) { + return Auth.parsePublicKey(fs.readFileSync(file)).then(function(key) { + switch (options.format.toLowerCase()) { + case 'pem': + return console.log(forge.pki.publicKeyToPem(key).trim()); + case 'openssh': + return console.log(forge.ssh.publicKeyToOpenSSH(key, 'adbkey').trim()); + default: + console.error("Unsupported format '" + options.format + "'"); + return process.exit(1); + } + }); +}); + +program.command('pubkey-fingerprint ').description('Outputs the fingerprint of an ADB-generated public key.').action(function(file) { + return Auth.parsePublicKey(fs.readFileSync(file)).then(function(key) { + return console.log('%s %s', key.fingerprint, key.comment); + }); +}); + +program.command('usb-device-to-tcp ').option('-p, --port ', 'port number', String, 6174).description('Provides an USB device over TCP using a translating proxy.').action(function(serial, options) { + var adb, server; + adb = Adb.createClient(); + server = adb.createTcpUsbBridge(serial, { + auth: function() { + return Promise.resolve(); + } + }).on('listening', function() { + return console.info('Connect with `adb connect localhost:%d`', options.port); + }).on('error', function(err) { + return console.error("An error occured: " + err.message); + }); + return server.listen(options.port); +}); + +program.command('parse-tcp-packets ').description('Parses ADB TCP packets from the given file.').action(function(file, options) { + var reader; + reader = new PacketReader(fs.createReadStream(file)); + return reader.on('packet', function(packet) { + return console.log(packet.toString()); + }); +}); + +program.parse(process.argv); diff --git a/tasks/keycode.coffee b/tasks/keycode.coffee deleted file mode 100644 index eb459fa1..00000000 --- a/tasks/keycode.coffee +++ /dev/null @@ -1,52 +0,0 @@ -Https = require 'https' - -module.exports = (grunt) -> - - grunt.registerMultiTask 'keycode', 'Updates KeyEvent mapping.', -> - - repo_path = '/android/platform_frameworks_base/master' - done = this.async() - options = this.options - original: - hostname: 'raw.github.com' - path: "#{repo_path}/core/java/android/view/KeyEvent.java" - method: 'GET' - regex: /public static final int (KEYCODE_[^\s]+)\s*=\s*([0-9]+);/g - - grunt.util.async.forEach this.files, (file, next) -> - req = Https.request options.original, (res) -> - unless res.statusCode is 200 - grunt.fail.warn \ - "Unable to retrieve KeyEvent.java (HTTP #{res.statusCode})" - return next() - - raw = new Buffer '' - - res.on 'data', (chunk) -> - raw = Buffer.concat [raw, chunk] - - res.on 'end', -> - code = raw.toString() - date = new Date().toUTCString() - coffee = [] - coffee.push "# Generated by `grunt keycode` on #{date}" - coffee.push \ - "# KeyEvent.java Copyright (C) 2006 The Android Open Source Project" - coffee.push '' - coffee.push 'module.exports =' - - while match = options.regex.exec code - coffee.push " #{match[1]}: #{match[2]}" - - coffee.push '' - - grunt.file.write file.dest, coffee.join '\n' - grunt.log.ok "File #{file.dest} created" - - next() - - req.on 'error', next - - req.end() - - , done diff --git a/tasks/keycode.js b/tasks/keycode.js new file mode 100644 index 00000000..dc49cf38 --- /dev/null +++ b/tasks/keycode.js @@ -0,0 +1,52 @@ +var Https; + +Https = require('https'); + +module.exports = function(grunt) { + return grunt.registerMultiTask('keycode', 'Updates KeyEvent mapping.', function() { + var done, options, repo_path; + repo_path = '/android/platform_frameworks_base/master'; + done = this.async(); + options = this.options({ + original: { + hostname: 'raw.github.com', + path: repo_path + "/core/java/android/view/KeyEvent.java", + method: 'GET' + }, + regex: /public static final int (KEYCODE_[^\s]+)\s*=\s*([0-9]+);/g + }); + return grunt.util.async.forEach(this.files, function(file, next) { + var req; + req = Https.request(options.original, function(res) { + var raw; + if (res.statusCode !== 200) { + grunt.fail.warn("Unable to retrieve KeyEvent.java (HTTP " + res.statusCode + ")"); + return next(); + } + raw = new Buffer(''); + res.on('data', function(chunk) { + return raw = Buffer.concat([raw, chunk]); + }); + return res.on('end', function() { + var code, coffee, date, match; + code = raw.toString(); + date = new Date().toUTCString(); + coffee = []; + coffee.push("# Generated by `grunt keycode` on " + date); + coffee.push("# KeyEvent.java Copyright (C) 2006 The Android Open Source Project"); + coffee.push(''); + coffee.push('module.exports ='); + while (match = options.regex.exec(code)) { + coffee.push(" " + match[1] + ": " + match[2]); + } + coffee.push(''); + grunt.file.write(file.dest, coffee.join('\n')); + grunt.log.ok("File " + file.dest + " created"); + return next(); + }); + }); + req.on('error', next); + return req.end(); + }, done); + }); +}; diff --git a/test/adb.coffee b/test/adb.coffee deleted file mode 100644 index 3bbc872d..00000000 --- a/test/adb.coffee +++ /dev/null @@ -1,24 +0,0 @@ -{expect} = require 'chai' - -Adb = require '../src/adb' -Client = require '../src/adb/client' -Keycode = require '../src/adb/keycode' -util = require '../src/adb/util' - -describe 'Adb', -> - - it "should expose Keycode", (done) -> - expect(Adb).to.have.property 'Keycode' - expect(Adb.Keycode).to.equal Keycode - done() - - it "should expose util", (done) -> - expect(Adb).to.have.property 'util' - expect(Adb.util).to.equal util - done() - - describe '@createClient(options)', -> - - it "should return a Client instance", (done) -> - expect(Adb.createClient()).to.be.an.instanceOf Client - done() diff --git a/test/adb.js b/test/adb.js new file mode 100644 index 00000000..04868c85 --- /dev/null +++ b/test/adb.js @@ -0,0 +1,30 @@ +var Adb, Client, Keycode, expect, util; + +expect = require('chai').expect; + +Adb = require('../src/adb'); + +Client = require('../src/adb/client'); + +Keycode = require('../src/adb/keycode'); + +util = require('../src/adb/util'); + +describe('Adb', function() { + it("should expose Keycode", function(done) { + expect(Adb).to.have.property('Keycode'); + expect(Adb.Keycode).to.equal(Keycode); + return done(); + }); + it("should expose util", function(done) { + expect(Adb).to.have.property('util'); + expect(Adb.util).to.equal(util); + return done(); + }); + return describe('@createClient(options)', function() { + return it("should return a Client instance", function(done) { + expect(Adb.createClient()).to.be.an.instanceOf(Client); + return done(); + }); + }); +}); diff --git a/test/adb/command/host-serial/waitfordevice.coffee b/test/adb/command/host-serial/waitfordevice.coffee deleted file mode 100644 index 1c8250c9..00000000 --- a/test/adb/command/host-serial/waitfordevice.coffee +++ /dev/null @@ -1,52 +0,0 @@ -Stream = require 'stream' -Sinon = require 'sinon' -Chai = require 'chai' -Chai.use require 'sinon-chai' -{expect} = Chai - -MockConnection = require '../../../mock/connection' -Protocol = require '../../../../src/adb/protocol' -WaitForDeviceCommand = \ - require '../../../../src/adb/command/host-serial/waitfordevice' - -describe 'WaitForDeviceCommand', -> - - it "should send 'host-serial::wait-for-any'", (done) -> - conn = new MockConnection - cmd = new WaitForDeviceCommand conn - conn.socket.on 'write', (chunk) -> - expect(chunk.toString()).to.equal \ - Protocol.encodeData('host-serial:abba:wait-for-any').toString() - setImmediate -> - conn.socket.causeRead Protocol.OKAY - conn.socket.causeRead Protocol.OKAY - conn.socket.causeEnd() - cmd.execute 'abba' - .then -> - done() - - it "should resolve with id when the device is connected", (done) -> - conn = new MockConnection - cmd = new WaitForDeviceCommand conn - setImmediate -> - conn.socket.causeRead Protocol.OKAY - conn.socket.causeRead Protocol.OKAY - conn.socket.causeEnd() - cmd.execute 'abba' - .then (id) -> - expect(id).to.equal 'abba' - done() - - it "should reject with error if unable to connect", (done) -> - conn = new MockConnection - cmd = new WaitForDeviceCommand conn - setImmediate -> - conn.socket.causeRead Protocol.OKAY - conn.socket.causeRead Protocol.FAIL - conn.socket.causeRead \ - Protocol.encodeData('not sure how this might happen') - conn.socket.causeEnd() - cmd.execute 'abba' - .catch (err) -> - expect(err.message).to.contain 'not sure how this might happen' - done() diff --git a/test/adb/command/host-serial/waitfordevice.js b/test/adb/command/host-serial/waitfordevice.js new file mode 100644 index 00000000..802e70b1 --- /dev/null +++ b/test/adb/command/host-serial/waitfordevice.js @@ -0,0 +1,65 @@ +var Chai, MockConnection, Protocol, Sinon, Stream, WaitForDeviceCommand, expect; + +Stream = require('stream'); + +Sinon = require('sinon'); + +Chai = require('chai'); + +Chai.use(require('sinon-chai')); + +expect = Chai.expect; + +MockConnection = require('../../../mock/connection'); + +Protocol = require('../../../../src/adb/protocol'); + +WaitForDeviceCommand = require('../../../../src/adb/command/host-serial/waitfordevice'); + +describe('WaitForDeviceCommand', function() { + it("should send 'host-serial::wait-for-any'", function(done) { + var cmd, conn; + conn = new MockConnection; + cmd = new WaitForDeviceCommand(conn); + conn.socket.on('write', function(chunk) { + return expect(chunk.toString()).to.equal(Protocol.encodeData('host-serial:abba:wait-for-any').toString()); + }); + setImmediate(function() { + conn.socket.causeRead(Protocol.OKAY); + conn.socket.causeRead(Protocol.OKAY); + return conn.socket.causeEnd(); + }); + return cmd.execute('abba').then(function() { + return done(); + }); + }); + it("should resolve with id when the device is connected", function(done) { + var cmd, conn; + conn = new MockConnection; + cmd = new WaitForDeviceCommand(conn); + setImmediate(function() { + conn.socket.causeRead(Protocol.OKAY); + conn.socket.causeRead(Protocol.OKAY); + return conn.socket.causeEnd(); + }); + return cmd.execute('abba').then(function(id) { + expect(id).to.equal('abba'); + return done(); + }); + }); + return it("should reject with error if unable to connect", function(done) { + var cmd, conn; + conn = new MockConnection; + cmd = new WaitForDeviceCommand(conn); + setImmediate(function() { + conn.socket.causeRead(Protocol.OKAY); + conn.socket.causeRead(Protocol.FAIL); + conn.socket.causeRead(Protocol.encodeData('not sure how this might happen')); + return conn.socket.causeEnd(); + }); + return cmd.execute('abba')["catch"](function(err) { + expect(err.message).to.contain('not sure how this might happen'); + return done(); + }); + }); +}); diff --git a/test/adb/command/host-transport/clear.coffee b/test/adb/command/host-transport/clear.coffee deleted file mode 100644 index c4fa88d8..00000000 --- a/test/adb/command/host-transport/clear.coffee +++ /dev/null @@ -1,71 +0,0 @@ -Stream = require 'stream' -Sinon = require 'sinon' -Chai = require 'chai' -Chai.use require 'sinon-chai' -{expect} = Chai - -MockConnection = require '../../../mock/connection' -Protocol = require '../../../../src/adb/protocol' -ClearCommand = require '../../../../src/adb/command/host-transport/clear' - -describe 'ClearCommand', -> - - it "should send 'pm clear '", (done) -> - conn = new MockConnection - cmd = new ClearCommand conn - conn.socket.on 'write', (chunk) -> - expect(chunk.toString()).to.equal \ - Protocol.encodeData('shell:pm clear foo.bar.c').toString() - conn.socket.causeRead Protocol.OKAY - conn.socket.causeRead 'Success\r\n' - conn.socket.causeEnd() - cmd.execute 'foo.bar.c' - .then -> - done() - - it "should succeed on 'Success'", (done) -> - conn = new MockConnection - cmd = new ClearCommand conn - conn.socket.on 'write', (chunk) -> - conn.socket.causeRead Protocol.OKAY - conn.socket.causeRead 'Success\r\n' - conn.socket.causeEnd() - cmd.execute 'foo.bar.c' - .then -> - done() - - it "should error on 'Failed'", (done) -> - conn = new MockConnection - cmd = new ClearCommand conn - conn.socket.on 'write', (chunk) -> - conn.socket.causeRead Protocol.OKAY - conn.socket.causeRead 'Failed\r\n' - conn.socket.causeEnd() - cmd.execute 'foo.bar.c' - .catch (err) -> - expect(err).to.be.an.instanceof Error - done() - - it "should error on 'Failed' even if connection not closed by - device", (done) -> - conn = new MockConnection - cmd = new ClearCommand conn - conn.socket.on 'write', (chunk) -> - conn.socket.causeRead Protocol.OKAY - conn.socket.causeRead 'Failed\r\n' - cmd.execute 'foo.bar.c' - .catch (err) -> - expect(err).to.be.an.instanceof Error - done() - - it "should ignore irrelevant lines", (done) -> - conn = new MockConnection - cmd = new ClearCommand conn - conn.socket.on 'write', (chunk) -> - conn.socket.causeRead Protocol.OKAY - conn.socket.causeRead 'Open: foo error\n\n' - conn.socket.causeRead 'Success\r\n' - conn.socket.causeEnd() - cmd.execute 'foo.bar.c' - .then -> - done() diff --git a/test/adb/command/host-transport/clear.js b/test/adb/command/host-transport/clear.js new file mode 100644 index 00000000..f0dacfb5 --- /dev/null +++ b/test/adb/command/host-transport/clear.js @@ -0,0 +1,88 @@ +var Chai, ClearCommand, MockConnection, Protocol, Sinon, Stream, expect; + +Stream = require('stream'); + +Sinon = require('sinon'); + +Chai = require('chai'); + +Chai.use(require('sinon-chai')); + +expect = Chai.expect; + +MockConnection = require('../../../mock/connection'); + +Protocol = require('../../../../src/adb/protocol'); + +ClearCommand = require('../../../../src/adb/command/host-transport/clear'); + +describe('ClearCommand', function() { + it("should send 'pm clear '", function(done) { + var cmd, conn; + conn = new MockConnection; + cmd = new ClearCommand(conn); + conn.socket.on('write', function(chunk) { + expect(chunk.toString()).to.equal(Protocol.encodeData('shell:pm clear foo.bar.c').toString()); + conn.socket.causeRead(Protocol.OKAY); + conn.socket.causeRead('Success\r\n'); + return conn.socket.causeEnd(); + }); + return cmd.execute('foo.bar.c').then(function() { + return done(); + }); + }); + it("should succeed on 'Success'", function(done) { + var cmd, conn; + conn = new MockConnection; + cmd = new ClearCommand(conn); + conn.socket.on('write', function(chunk) { + conn.socket.causeRead(Protocol.OKAY); + conn.socket.causeRead('Success\r\n'); + return conn.socket.causeEnd(); + }); + return cmd.execute('foo.bar.c').then(function() { + return done(); + }); + }); + it("should error on 'Failed'", function(done) { + var cmd, conn; + conn = new MockConnection; + cmd = new ClearCommand(conn); + conn.socket.on('write', function(chunk) { + conn.socket.causeRead(Protocol.OKAY); + conn.socket.causeRead('Failed\r\n'); + return conn.socket.causeEnd(); + }); + return cmd.execute('foo.bar.c')["catch"](function(err) { + expect(err).to.be.an["instanceof"](Error); + return done(); + }); + }); + it("should error on 'Failed' even if connection not closed by device", function(done) { + var cmd, conn; + conn = new MockConnection; + cmd = new ClearCommand(conn); + conn.socket.on('write', function(chunk) { + conn.socket.causeRead(Protocol.OKAY); + return conn.socket.causeRead('Failed\r\n'); + }); + return cmd.execute('foo.bar.c')["catch"](function(err) { + expect(err).to.be.an["instanceof"](Error); + return done(); + }); + }); + return it("should ignore irrelevant lines", function(done) { + var cmd, conn; + conn = new MockConnection; + cmd = new ClearCommand(conn); + conn.socket.on('write', function(chunk) { + conn.socket.causeRead(Protocol.OKAY); + conn.socket.causeRead('Open: foo error\n\n'); + conn.socket.causeRead('Success\r\n'); + return conn.socket.causeEnd(); + }); + return cmd.execute('foo.bar.c').then(function() { + return done(); + }); + }); +}); diff --git a/test/adb/command/host-transport/framebuffer.coffee b/test/adb/command/host-transport/framebuffer.coffee deleted file mode 100644 index ac10a1bb..00000000 --- a/test/adb/command/host-transport/framebuffer.coffee +++ /dev/null @@ -1,73 +0,0 @@ -Sinon = require 'sinon' -Chai = require 'chai' -Chai.use require 'sinon-chai' -{expect} = Chai - -MockConnection = require '../../../mock/connection' -Protocol = require '../../../../src/adb/protocol' -FrameBufferCommand = - require '../../../../src/adb/command/host-transport/framebuffer' - -describe 'FrameBufferCommand', -> - - it "should send 'framebuffer:'", (done) -> - conn = new MockConnection - cmd = new FrameBufferCommand conn - conn.socket.on 'write', (chunk) -> - expect(chunk.toString()).to.equal \ - Protocol.encodeData('framebuffer:').toString() - setImmediate -> - meta = new Buffer 52 - meta.fill 0 - conn.socket.causeRead Protocol.OKAY - conn.socket.causeRead meta - conn.socket.causeEnd() - cmd.execute 'raw' - .then -> - done() - - it "should parse meta header and return it as the 'meta' - property of the stream", (done) -> - conn = new MockConnection - cmd = new FrameBufferCommand conn - conn.socket.on 'write', (chunk) -> - expect(chunk.toString()).to.equal \ - Protocol.encodeData('framebuffer:').toString() - setImmediate -> - meta = new Buffer 52 - offset = 0 - meta.writeUInt32LE 1, offset - meta.writeUInt32LE 32, offset += 4 - meta.writeUInt32LE 819200, offset += 4 - meta.writeUInt32LE 640, offset += 4 - meta.writeUInt32LE 320, offset += 4 - meta.writeUInt32LE 0, offset += 4 - meta.writeUInt32LE 8, offset += 4 - meta.writeUInt32LE 16, offset += 4 - meta.writeUInt32LE 8, offset += 4 - meta.writeUInt32LE 8, offset += 4 - meta.writeUInt32LE 8, offset += 4 - meta.writeUInt32LE 24, offset += 4 - meta.writeUInt32LE 8, offset += 4 - conn.socket.causeRead Protocol.OKAY - conn.socket.causeRead meta - conn.socket.causeEnd() - cmd.execute 'raw' - .then (stream) -> - expect(stream).to.have.property 'meta' - expect(stream.meta).to.eql - version: 1 - bpp: 32 - size: 819200 - width: 640 - height: 320 - red_offset: 0 - red_length: 8 - blue_offset: 16 - blue_length: 8 - green_offset: 8 - green_length: 8 - alpha_offset: 24 - alpha_length: 8 - format: 'rgba' - done() diff --git a/test/adb/command/host-transport/framebuffer.js b/test/adb/command/host-transport/framebuffer.js new file mode 100644 index 00000000..5c5beb55 --- /dev/null +++ b/test/adb/command/host-transport/framebuffer.js @@ -0,0 +1,86 @@ +var Chai, FrameBufferCommand, MockConnection, Protocol, Sinon, expect; + +Sinon = require('sinon'); + +Chai = require('chai'); + +Chai.use(require('sinon-chai')); + +expect = Chai.expect; + +MockConnection = require('../../../mock/connection'); + +Protocol = require('../../../../src/adb/protocol'); + +FrameBufferCommand = require('../../../../src/adb/command/host-transport/framebuffer'); + +describe('FrameBufferCommand', function() { + it("should send 'framebuffer:'", function(done) { + var cmd, conn; + conn = new MockConnection; + cmd = new FrameBufferCommand(conn); + conn.socket.on('write', function(chunk) { + return expect(chunk.toString()).to.equal(Protocol.encodeData('framebuffer:').toString()); + }); + setImmediate(function() { + var meta; + meta = new Buffer(52); + meta.fill(0); + conn.socket.causeRead(Protocol.OKAY); + conn.socket.causeRead(meta); + return conn.socket.causeEnd(); + }); + return cmd.execute('raw').then(function() { + return done(); + }); + }); + return it("should parse meta header and return it as the 'meta' property of the stream", function(done) { + var cmd, conn; + conn = new MockConnection; + cmd = new FrameBufferCommand(conn); + conn.socket.on('write', function(chunk) { + return expect(chunk.toString()).to.equal(Protocol.encodeData('framebuffer:').toString()); + }); + setImmediate(function() { + var meta, offset; + meta = new Buffer(52); + offset = 0; + meta.writeUInt32LE(1, offset); + meta.writeUInt32LE(32, offset += 4); + meta.writeUInt32LE(819200, offset += 4); + meta.writeUInt32LE(640, offset += 4); + meta.writeUInt32LE(320, offset += 4); + meta.writeUInt32LE(0, offset += 4); + meta.writeUInt32LE(8, offset += 4); + meta.writeUInt32LE(16, offset += 4); + meta.writeUInt32LE(8, offset += 4); + meta.writeUInt32LE(8, offset += 4); + meta.writeUInt32LE(8, offset += 4); + meta.writeUInt32LE(24, offset += 4); + meta.writeUInt32LE(8, offset += 4); + conn.socket.causeRead(Protocol.OKAY); + conn.socket.causeRead(meta); + return conn.socket.causeEnd(); + }); + return cmd.execute('raw').then(function(stream) { + expect(stream).to.have.property('meta'); + expect(stream.meta).to.eql({ + version: 1, + bpp: 32, + size: 819200, + width: 640, + height: 320, + red_offset: 0, + red_length: 8, + blue_offset: 16, + blue_length: 8, + green_offset: 8, + green_length: 8, + alpha_offset: 24, + alpha_length: 8, + format: 'rgba' + }); + return done(); + }); + }); +}); diff --git a/test/adb/command/host-transport/getfeatures.coffee b/test/adb/command/host-transport/getfeatures.coffee deleted file mode 100644 index ef6a2538..00000000 --- a/test/adb/command/host-transport/getfeatures.coffee +++ /dev/null @@ -1,56 +0,0 @@ -Stream = require 'stream' -Sinon = require 'sinon' -Chai = require 'chai' -Chai.use require 'sinon-chai' -{expect} = Chai - -MockConnection = require '../../../mock/connection' -Protocol = require '../../../../src/adb/protocol' -GetFeaturesCommand = - require '../../../../src/adb/command/host-transport/getfeatures' - -describe 'GetFeaturesCommand', -> - - it "should send 'pm list features'", (done) -> - conn = new MockConnection - cmd = new GetFeaturesCommand conn - conn.socket.on 'write', (chunk) -> - expect(chunk.toString()).to.equal \ - Protocol.encodeData('shell:pm list features 2>/dev/null').toString() - setImmediate -> - conn.socket.causeRead Protocol.OKAY - conn.socket.causeEnd() - cmd.execute() - .then -> - done() - - it "should return an empty object for an empty feature list", (done) -> - conn = new MockConnection - cmd = new GetFeaturesCommand conn - setImmediate -> - conn.socket.causeRead Protocol.OKAY - conn.socket.causeEnd() - cmd.execute() - .then (features) -> - expect(Object.keys(features)).to.be.empty - done() - - it "should return a map of features", (done) -> - conn = new MockConnection - cmd = new GetFeaturesCommand conn - setImmediate -> - conn.socket.causeRead Protocol.OKAY - conn.socket.causeRead """ - feature:reqGlEsVersion=0x20000 - feature:foo\r - feature:bar - """ - conn.socket.causeEnd() - cmd.execute() - .then (features) -> - expect(Object.keys(features)).to.have.length 3 - expect(features).to.eql - reqGlEsVersion: '0x20000' - foo: true - bar: true - done() diff --git a/test/adb/command/host-transport/getfeatures.js b/test/adb/command/host-transport/getfeatures.js new file mode 100644 index 00000000..85b58b62 --- /dev/null +++ b/test/adb/command/host-transport/getfeatures.js @@ -0,0 +1,67 @@ +var Chai, GetFeaturesCommand, MockConnection, Protocol, Sinon, Stream, expect; + +Stream = require('stream'); + +Sinon = require('sinon'); + +Chai = require('chai'); + +Chai.use(require('sinon-chai')); + +expect = Chai.expect; + +MockConnection = require('../../../mock/connection'); + +Protocol = require('../../../../src/adb/protocol'); + +GetFeaturesCommand = require('../../../../src/adb/command/host-transport/getfeatures'); + +describe('GetFeaturesCommand', function() { + it("should send 'pm list features'", function(done) { + var cmd, conn; + conn = new MockConnection; + cmd = new GetFeaturesCommand(conn); + conn.socket.on('write', function(chunk) { + return expect(chunk.toString()).to.equal(Protocol.encodeData('shell:pm list features 2>/dev/null').toString()); + }); + setImmediate(function() { + conn.socket.causeRead(Protocol.OKAY); + return conn.socket.causeEnd(); + }); + return cmd.execute().then(function() { + return done(); + }); + }); + it("should return an empty object for an empty feature list", function(done) { + var cmd, conn; + conn = new MockConnection; + cmd = new GetFeaturesCommand(conn); + setImmediate(function() { + conn.socket.causeRead(Protocol.OKAY); + return conn.socket.causeEnd(); + }); + return cmd.execute().then(function(features) { + expect(Object.keys(features)).to.be.empty; + return done(); + }); + }); + return it("should return a map of features", function(done) { + var cmd, conn; + conn = new MockConnection; + cmd = new GetFeaturesCommand(conn); + setImmediate(function() { + conn.socket.causeRead(Protocol.OKAY); + conn.socket.causeRead("feature:reqGlEsVersion=0x20000\nfeature:foo\r\nfeature:bar"); + return conn.socket.causeEnd(); + }); + return cmd.execute().then(function(features) { + expect(Object.keys(features)).to.have.length(3); + expect(features).to.eql({ + reqGlEsVersion: '0x20000', + foo: true, + bar: true + }); + return done(); + }); + }); +}); diff --git a/test/adb/command/host-transport/getpackages.coffee b/test/adb/command/host-transport/getpackages.coffee deleted file mode 100644 index 0c1c9172..00000000 --- a/test/adb/command/host-transport/getpackages.coffee +++ /dev/null @@ -1,65 +0,0 @@ -Stream = require 'stream' -Sinon = require 'sinon' -Chai = require 'chai' -Chai.use require 'sinon-chai' -{expect} = Chai - -MockConnection = require '../../../mock/connection' -Protocol = require '../../../../src/adb/protocol' -GetPackagesCommand = - require '../../../../src/adb/command/host-transport/getpackages' - -describe 'GetPackagesCommand', -> - - it "should send 'pm list packages'", (done) -> - conn = new MockConnection - cmd = new GetPackagesCommand conn - conn.socket.on 'write', (chunk) -> - expect(chunk.toString()).to.equal \ - Protocol.encodeData('shell:pm list packages 2>/dev/null').toString() - setImmediate -> - conn.socket.causeRead Protocol.OKAY - conn.socket.causeEnd() - cmd.execute() - .then -> - done() - - it "should return an empty array for an empty package list", (done) -> - conn = new MockConnection - cmd = new GetPackagesCommand conn - setImmediate -> - conn.socket.causeRead Protocol.OKAY - conn.socket.causeEnd() - cmd.execute() - .then (packages) -> - expect(packages).to.be.empty - done() - - it "should return an array of packages", (done) -> - conn = new MockConnection - cmd = new GetPackagesCommand conn - setImmediate -> - conn.socket.causeRead Protocol.OKAY - conn.socket.causeRead """ - package:com.google.android.gm - package:com.google.android.inputmethod.japanese - package:com.google.android.tag\r - package:com.google.android.GoogleCamera - package:com.google.android.youtube - package:com.google.android.apps.magazines - package:com.google.earth - """ - conn.socket.causeEnd() - cmd.execute() - .then (packages) -> - expect(packages).to.have.length 7 - expect(packages).to.eql [ - 'com.google.android.gm' - 'com.google.android.inputmethod.japanese' - 'com.google.android.tag' - 'com.google.android.GoogleCamera', - 'com.google.android.youtube', - 'com.google.android.apps.magazines', - 'com.google.earth' - ] - done() diff --git a/test/adb/command/host-transport/getpackages.js b/test/adb/command/host-transport/getpackages.js new file mode 100644 index 00000000..8d4f2ee7 --- /dev/null +++ b/test/adb/command/host-transport/getpackages.js @@ -0,0 +1,63 @@ +var Chai, GetPackagesCommand, MockConnection, Protocol, Sinon, Stream, expect; + +Stream = require('stream'); + +Sinon = require('sinon'); + +Chai = require('chai'); + +Chai.use(require('sinon-chai')); + +expect = Chai.expect; + +MockConnection = require('../../../mock/connection'); + +Protocol = require('../../../../src/adb/protocol'); + +GetPackagesCommand = require('../../../../src/adb/command/host-transport/getpackages'); + +describe('GetPackagesCommand', function() { + it("should send 'pm list packages'", function(done) { + var cmd, conn; + conn = new MockConnection; + cmd = new GetPackagesCommand(conn); + conn.socket.on('write', function(chunk) { + return expect(chunk.toString()).to.equal(Protocol.encodeData('shell:pm list packages 2>/dev/null').toString()); + }); + setImmediate(function() { + conn.socket.causeRead(Protocol.OKAY); + return conn.socket.causeEnd(); + }); + return cmd.execute().then(function() { + return done(); + }); + }); + it("should return an empty array for an empty package list", function(done) { + var cmd, conn; + conn = new MockConnection; + cmd = new GetPackagesCommand(conn); + setImmediate(function() { + conn.socket.causeRead(Protocol.OKAY); + return conn.socket.causeEnd(); + }); + return cmd.execute().then(function(packages) { + expect(packages).to.be.empty; + return done(); + }); + }); + return it("should return an array of packages", function(done) { + var cmd, conn; + conn = new MockConnection; + cmd = new GetPackagesCommand(conn); + setImmediate(function() { + conn.socket.causeRead(Protocol.OKAY); + conn.socket.causeRead("package:com.google.android.gm\npackage:com.google.android.inputmethod.japanese\npackage:com.google.android.tag\r\npackage:com.google.android.GoogleCamera\npackage:com.google.android.youtube\npackage:com.google.android.apps.magazines\npackage:com.google.earth"); + return conn.socket.causeEnd(); + }); + return cmd.execute().then(function(packages) { + expect(packages).to.have.length(7); + expect(packages).to.eql(['com.google.android.gm', 'com.google.android.inputmethod.japanese', 'com.google.android.tag', 'com.google.android.GoogleCamera', 'com.google.android.youtube', 'com.google.android.apps.magazines', 'com.google.earth']); + return done(); + }); + }); +}); diff --git a/test/adb/command/host-transport/getproperties.coffee b/test/adb/command/host-transport/getproperties.coffee deleted file mode 100644 index fad60eba..00000000 --- a/test/adb/command/host-transport/getproperties.coffee +++ /dev/null @@ -1,58 +0,0 @@ -Stream = require 'stream' -Sinon = require 'sinon' -Chai = require 'chai' -Chai.use require 'sinon-chai' -{expect} = Chai - -MockConnection = require '../../../mock/connection' -Protocol = require '../../../../src/adb/protocol' -GetPropertiesCommand = - require '../../../../src/adb/command/host-transport/getproperties' - -describe 'GetPropertiesCommand', -> - - it "should send 'getprop'", (done) -> - conn = new MockConnection - cmd = new GetPropertiesCommand conn - conn.socket.on 'write', (chunk) -> - expect(chunk.toString()).to.equal \ - Protocol.encodeData('shell:getprop').toString() - setImmediate -> - conn.socket.causeRead Protocol.OKAY - conn.socket.causeEnd() - cmd.execute() - .then -> - done() - - it "should return an empty object for an empty property list", (done) -> - conn = new MockConnection - cmd = new GetPropertiesCommand conn - setImmediate -> - conn.socket.causeRead Protocol.OKAY - conn.socket.causeEnd() - cmd.execute() - .then (properties) -> - expect(Object.keys(properties)).to.be.empty - done() - - it "should return a map of properties", (done) -> - conn = new MockConnection - cmd = new GetPropertiesCommand conn - setImmediate -> - conn.socket.causeRead Protocol.OKAY - conn.socket.causeRead """ - [ro.product.locale.region]: [US] - [ro.product.manufacturer]: [samsung]\r - [ro.product.model]: [SC-04E] - [ro.product.name]: [SC-04E] - """ - conn.socket.causeEnd() - cmd.execute() - .then (properties) -> - expect(Object.keys(properties)).to.have.length 4 - expect(properties).to.eql - 'ro.product.locale.region': 'US' - 'ro.product.manufacturer': 'samsung' - 'ro.product.model': 'SC-04E' - 'ro.product.name': 'SC-04E' - done() diff --git a/test/adb/command/host-transport/getproperties.js b/test/adb/command/host-transport/getproperties.js new file mode 100644 index 00000000..61e2b7c6 --- /dev/null +++ b/test/adb/command/host-transport/getproperties.js @@ -0,0 +1,68 @@ +var Chai, GetPropertiesCommand, MockConnection, Protocol, Sinon, Stream, expect; + +Stream = require('stream'); + +Sinon = require('sinon'); + +Chai = require('chai'); + +Chai.use(require('sinon-chai')); + +expect = Chai.expect; + +MockConnection = require('../../../mock/connection'); + +Protocol = require('../../../../src/adb/protocol'); + +GetPropertiesCommand = require('../../../../src/adb/command/host-transport/getproperties'); + +describe('GetPropertiesCommand', function() { + it("should send 'getprop'", function(done) { + var cmd, conn; + conn = new MockConnection; + cmd = new GetPropertiesCommand(conn); + conn.socket.on('write', function(chunk) { + return expect(chunk.toString()).to.equal(Protocol.encodeData('shell:getprop').toString()); + }); + setImmediate(function() { + conn.socket.causeRead(Protocol.OKAY); + return conn.socket.causeEnd(); + }); + return cmd.execute().then(function() { + return done(); + }); + }); + it("should return an empty object for an empty property list", function(done) { + var cmd, conn; + conn = new MockConnection; + cmd = new GetPropertiesCommand(conn); + setImmediate(function() { + conn.socket.causeRead(Protocol.OKAY); + return conn.socket.causeEnd(); + }); + return cmd.execute().then(function(properties) { + expect(Object.keys(properties)).to.be.empty; + return done(); + }); + }); + return it("should return a map of properties", function(done) { + var cmd, conn; + conn = new MockConnection; + cmd = new GetPropertiesCommand(conn); + setImmediate(function() { + conn.socket.causeRead(Protocol.OKAY); + conn.socket.causeRead("[ro.product.locale.region]: [US]\n[ro.product.manufacturer]: [samsung]\r\n[ro.product.model]: [SC-04E]\n[ro.product.name]: [SC-04E]"); + return conn.socket.causeEnd(); + }); + return cmd.execute().then(function(properties) { + expect(Object.keys(properties)).to.have.length(4); + expect(properties).to.eql({ + 'ro.product.locale.region': 'US', + 'ro.product.manufacturer': 'samsung', + 'ro.product.model': 'SC-04E', + 'ro.product.name': 'SC-04E' + }); + return done(); + }); + }); +}); diff --git a/test/adb/command/host-transport/install.coffee b/test/adb/command/host-transport/install.coffee deleted file mode 100644 index b3ef4687..00000000 --- a/test/adb/command/host-transport/install.coffee +++ /dev/null @@ -1,72 +0,0 @@ -Stream = require 'stream' -Sinon = require 'sinon' -Chai = require 'chai' -Chai.use require 'sinon-chai' -{expect} = Chai - -MockConnection = require '../../../mock/connection' -Protocol = require '../../../../src/adb/protocol' -InstallCommand = - require '../../../../src/adb/command/host-transport/install' - -describe 'InstallCommand', -> - - it "should send 'pm install -r '", (done) -> - conn = new MockConnection - cmd = new InstallCommand conn - conn.socket.on 'write', (chunk) -> - expect(chunk.toString()).to.equal \ - Protocol.encodeData('shell:pm install -r "foo"').toString() - setImmediate -> - conn.socket.causeRead Protocol.OKAY - conn.socket.causeRead 'Success\r\n' - conn.socket.causeEnd() - cmd.execute 'foo' - .then -> - done() - - it "should succeed when command responds with 'Success'", (done) -> - conn = new MockConnection - cmd = new InstallCommand conn - setImmediate -> - conn.socket.causeRead Protocol.OKAY - conn.socket.causeRead 'Success\r\n' - conn.socket.causeEnd() - cmd.execute 'foo' - .then -> - done() - - it "should reject if command responds with 'Failure [REASON]'", (done) -> - conn = new MockConnection - cmd = new InstallCommand conn - setImmediate -> - conn.socket.causeRead Protocol.OKAY - conn.socket.causeRead 'Failure [BAR]\r\n' - conn.socket.causeEnd() - cmd.execute 'foo' - .catch (err) -> - done() - - it "should give detailed reason in rejection's code property", (done) -> - conn = new MockConnection - cmd = new InstallCommand conn - setImmediate -> - conn.socket.causeRead Protocol.OKAY - conn.socket.causeRead 'Failure [ALREADY_EXISTS]\r\n' - conn.socket.causeEnd() - cmd.execute 'foo' - .catch (err) -> - expect(err.code).to.equal 'ALREADY_EXISTS' - done() - - it "should ignore any other data", (done) -> - conn = new MockConnection - cmd = new InstallCommand conn - setImmediate -> - conn.socket.causeRead Protocol.OKAY - conn.socket.causeRead 'open: Permission failed\r\n' - conn.socket.causeRead 'Success\r\n' - conn.socket.causeEnd() - cmd.execute 'foo' - .then -> - done() diff --git a/test/adb/command/host-transport/install.js b/test/adb/command/host-transport/install.js new file mode 100644 index 00000000..68dd2192 --- /dev/null +++ b/test/adb/command/host-transport/install.js @@ -0,0 +1,90 @@ +var Chai, InstallCommand, MockConnection, Protocol, Sinon, Stream, expect; + +Stream = require('stream'); + +Sinon = require('sinon'); + +Chai = require('chai'); + +Chai.use(require('sinon-chai')); + +expect = Chai.expect; + +MockConnection = require('../../../mock/connection'); + +Protocol = require('../../../../src/adb/protocol'); + +InstallCommand = require('../../../../src/adb/command/host-transport/install'); + +describe('InstallCommand', function() { + it("should send 'pm install -r '", function(done) { + var cmd, conn; + conn = new MockConnection; + cmd = new InstallCommand(conn); + conn.socket.on('write', function(chunk) { + return expect(chunk.toString()).to.equal(Protocol.encodeData('shell:pm install -r "foo"').toString()); + }); + setImmediate(function() { + conn.socket.causeRead(Protocol.OKAY); + conn.socket.causeRead('Success\r\n'); + return conn.socket.causeEnd(); + }); + return cmd.execute('foo').then(function() { + return done(); + }); + }); + it("should succeed when command responds with 'Success'", function(done) { + var cmd, conn; + conn = new MockConnection; + cmd = new InstallCommand(conn); + setImmediate(function() { + conn.socket.causeRead(Protocol.OKAY); + conn.socket.causeRead('Success\r\n'); + return conn.socket.causeEnd(); + }); + return cmd.execute('foo').then(function() { + return done(); + }); + }); + it("should reject if command responds with 'Failure [REASON]'", function(done) { + var cmd, conn; + conn = new MockConnection; + cmd = new InstallCommand(conn); + setImmediate(function() { + conn.socket.causeRead(Protocol.OKAY); + conn.socket.causeRead('Failure [BAR]\r\n'); + return conn.socket.causeEnd(); + }); + return cmd.execute('foo')["catch"](function(err) { + return done(); + }); + }); + it("should give detailed reason in rejection's code property", function(done) { + var cmd, conn; + conn = new MockConnection; + cmd = new InstallCommand(conn); + setImmediate(function() { + conn.socket.causeRead(Protocol.OKAY); + conn.socket.causeRead('Failure [ALREADY_EXISTS]\r\n'); + return conn.socket.causeEnd(); + }); + return cmd.execute('foo')["catch"](function(err) { + expect(err.code).to.equal('ALREADY_EXISTS'); + return done(); + }); + }); + return it("should ignore any other data", function(done) { + var cmd, conn; + conn = new MockConnection; + cmd = new InstallCommand(conn); + setImmediate(function() { + conn.socket.causeRead(Protocol.OKAY); + conn.socket.causeRead('open: Permission failed\r\n'); + conn.socket.causeRead('Success\r\n'); + return conn.socket.causeEnd(); + }); + return cmd.execute('foo').then(function() { + return done(); + }); + }); +}); diff --git a/test/adb/command/host-transport/isinstalled.coffee b/test/adb/command/host-transport/isinstalled.coffee deleted file mode 100644 index 0d414209..00000000 --- a/test/adb/command/host-transport/isinstalled.coffee +++ /dev/null @@ -1,61 +0,0 @@ -Stream = require 'stream' -Sinon = require 'sinon' -Chai = require 'chai' -Chai.use require 'sinon-chai' -{expect} = Chai - -MockConnection = require '../../../mock/connection' -Protocol = require '../../../../src/adb/protocol' -IsInstalledCommand = - require '../../../../src/adb/command/host-transport/isinstalled' - -describe 'IsInstalledCommand', -> - - it "should send 'pm path '", (done) -> - conn = new MockConnection - cmd = new IsInstalledCommand conn - conn.socket.on 'write', (chunk) -> - expect(chunk.toString()).to.equal \ - Protocol.encodeData("shell:pm path foo 2>/dev/null").toString() - setImmediate -> - conn.socket.causeRead Protocol.OKAY - conn.socket.causeRead 'package:foo\r\n' - conn.socket.causeEnd() - cmd.execute 'foo' - .then -> - done() - - it "should resolve with true if package returned by command", (done) -> - conn = new MockConnection - cmd = new IsInstalledCommand conn - setImmediate -> - conn.socket.causeRead Protocol.OKAY - conn.socket.causeRead 'package:bar\r\n' - conn.socket.causeEnd() - cmd.execute 'foo' - .then (found) -> - expect(found).to.be.true - done() - - it "should resolve with false if no package returned", (done) -> - conn = new MockConnection - cmd = new IsInstalledCommand conn - setImmediate -> - conn.socket.causeRead Protocol.OKAY - conn.socket.causeEnd() - cmd.execute 'foo' - .then (found) -> - expect(found).to.be.false - done() - - it "should fail if any other data is received", (done) -> - conn = new MockConnection - cmd = new IsInstalledCommand conn - setImmediate -> - conn.socket.causeRead Protocol.OKAY - conn.socket.causeRead 'open: Permission failed\r\n' - conn.socket.causeEnd() - cmd.execute 'foo' - .catch (err) -> - expect(err).to.be.an.instanceof Error - done() diff --git a/test/adb/command/host-transport/isinstalled.js b/test/adb/command/host-transport/isinstalled.js new file mode 100644 index 00000000..9c2851cd --- /dev/null +++ b/test/adb/command/host-transport/isinstalled.js @@ -0,0 +1,77 @@ +var Chai, IsInstalledCommand, MockConnection, Protocol, Sinon, Stream, expect; + +Stream = require('stream'); + +Sinon = require('sinon'); + +Chai = require('chai'); + +Chai.use(require('sinon-chai')); + +expect = Chai.expect; + +MockConnection = require('../../../mock/connection'); + +Protocol = require('../../../../src/adb/protocol'); + +IsInstalledCommand = require('../../../../src/adb/command/host-transport/isinstalled'); + +describe('IsInstalledCommand', function() { + it("should send 'pm path '", function(done) { + var cmd, conn; + conn = new MockConnection; + cmd = new IsInstalledCommand(conn); + conn.socket.on('write', function(chunk) { + return expect(chunk.toString()).to.equal(Protocol.encodeData("shell:pm path foo 2>/dev/null").toString()); + }); + setImmediate(function() { + conn.socket.causeRead(Protocol.OKAY); + conn.socket.causeRead('package:foo\r\n'); + return conn.socket.causeEnd(); + }); + return cmd.execute('foo').then(function() { + return done(); + }); + }); + it("should resolve with true if package returned by command", function(done) { + var cmd, conn; + conn = new MockConnection; + cmd = new IsInstalledCommand(conn); + setImmediate(function() { + conn.socket.causeRead(Protocol.OKAY); + conn.socket.causeRead('package:bar\r\n'); + return conn.socket.causeEnd(); + }); + return cmd.execute('foo').then(function(found) { + expect(found).to.be["true"]; + return done(); + }); + }); + it("should resolve with false if no package returned", function(done) { + var cmd, conn; + conn = new MockConnection; + cmd = new IsInstalledCommand(conn); + setImmediate(function() { + conn.socket.causeRead(Protocol.OKAY); + return conn.socket.causeEnd(); + }); + return cmd.execute('foo').then(function(found) { + expect(found).to.be["false"]; + return done(); + }); + }); + return it("should fail if any other data is received", function(done) { + var cmd, conn; + conn = new MockConnection; + cmd = new IsInstalledCommand(conn); + setImmediate(function() { + conn.socket.causeRead(Protocol.OKAY); + conn.socket.causeRead('open: Permission failed\r\n'); + return conn.socket.causeEnd(); + }); + return cmd.execute('foo')["catch"](function(err) { + expect(err).to.be.an["instanceof"](Error); + return done(); + }); + }); +}); diff --git a/test/adb/command/host-transport/local.coffee b/test/adb/command/host-transport/local.coffee deleted file mode 100644 index afc8f79c..00000000 --- a/test/adb/command/host-transport/local.coffee +++ /dev/null @@ -1,48 +0,0 @@ -Stream = require 'stream' -Sinon = require 'sinon' -Chai = require 'chai' -Chai.use require 'sinon-chai' -{expect} = Chai - -MockConnection = require '../../../mock/connection' -Protocol = require '../../../../src/adb/protocol' -LocalCommand = require '../../../../src/adb/command/host-transport/local' - -describe 'LocalCommand', -> - - it "should send 'localfilesystem:'", (done) -> - conn = new MockConnection - cmd = new LocalCommand conn - conn.socket.on 'write', (chunk) -> - expect(chunk.toString()).to.equal \ - Protocol.encodeData('localfilesystem:/foo.sock').toString() - setImmediate -> - conn.socket.causeRead Protocol.OKAY - conn.socket.causeEnd() - cmd.execute '/foo.sock' - .then (stream) -> - done() - - it "should send ':' if prefixed with ':'", (done) -> - conn = new MockConnection - cmd = new LocalCommand conn - conn.socket.on 'write', (chunk) -> - expect(chunk.toString()).to.equal \ - Protocol.encodeData('localabstract:/foo.sock').toString() - setImmediate -> - conn.socket.causeRead Protocol.OKAY - conn.socket.causeEnd() - cmd.execute 'localabstract:/foo.sock' - .then (stream) -> - done() - - it "should resolve with the stream", (done) -> - conn = new MockConnection - cmd = new LocalCommand conn - setImmediate -> - conn.socket.causeRead Protocol.OKAY - cmd.execute '/foo.sock' - .then (stream) -> - stream.end() - expect(stream).to.be.an.instanceof Stream.Readable - done() diff --git a/test/adb/command/host-transport/local.js b/test/adb/command/host-transport/local.js new file mode 100644 index 00000000..bff6c1da --- /dev/null +++ b/test/adb/command/host-transport/local.js @@ -0,0 +1,63 @@ +var Chai, LocalCommand, MockConnection, Protocol, Sinon, Stream, expect; + +Stream = require('stream'); + +Sinon = require('sinon'); + +Chai = require('chai'); + +Chai.use(require('sinon-chai')); + +expect = Chai.expect; + +MockConnection = require('../../../mock/connection'); + +Protocol = require('../../../../src/adb/protocol'); + +LocalCommand = require('../../../../src/adb/command/host-transport/local'); + +describe('LocalCommand', function() { + it("should send 'localfilesystem:'", function(done) { + var cmd, conn; + conn = new MockConnection; + cmd = new LocalCommand(conn); + conn.socket.on('write', function(chunk) { + return expect(chunk.toString()).to.equal(Protocol.encodeData('localfilesystem:/foo.sock').toString()); + }); + setImmediate(function() { + conn.socket.causeRead(Protocol.OKAY); + return conn.socket.causeEnd(); + }); + return cmd.execute('/foo.sock').then(function(stream) { + return done(); + }); + }); + it("should send ':' if prefixed with ':'", function(done) { + var cmd, conn; + conn = new MockConnection; + cmd = new LocalCommand(conn); + conn.socket.on('write', function(chunk) { + return expect(chunk.toString()).to.equal(Protocol.encodeData('localabstract:/foo.sock').toString()); + }); + setImmediate(function() { + conn.socket.causeRead(Protocol.OKAY); + return conn.socket.causeEnd(); + }); + return cmd.execute('localabstract:/foo.sock').then(function(stream) { + return done(); + }); + }); + return it("should resolve with the stream", function(done) { + var cmd, conn; + conn = new MockConnection; + cmd = new LocalCommand(conn); + setImmediate(function() { + return conn.socket.causeRead(Protocol.OKAY); + }); + return cmd.execute('/foo.sock').then(function(stream) { + stream.end(); + expect(stream).to.be.an["instanceof"](Stream.Readable); + return done(); + }); + }); +}); diff --git a/test/adb/command/host-transport/log.coffee b/test/adb/command/host-transport/log.coffee deleted file mode 100644 index 07fb9351..00000000 --- a/test/adb/command/host-transport/log.coffee +++ /dev/null @@ -1,35 +0,0 @@ -Stream = require 'stream' -Sinon = require 'sinon' -Chai = require 'chai' -Chai.use require 'sinon-chai' -{expect} = Chai - -MockConnection = require '../../../mock/connection' -Protocol = require '../../../../src/adb/protocol' -LogCommand = require '../../../../src/adb/command/host-transport/log' - -describe 'LogCommand', -> - - it "should send 'log:'", (done) -> - conn = new MockConnection - cmd = new LogCommand conn - conn.socket.on 'write', (chunk) -> - expect(chunk.toString()).to.equal \ - Protocol.encodeData('log:main').toString() - setImmediate -> - conn.socket.causeRead Protocol.OKAY - conn.socket.causeEnd() - cmd.execute 'main' - .then (stream) -> - done() - - it "should resolve with the log stream", (done) -> - conn = new MockConnection - cmd = new LogCommand conn - setImmediate -> - conn.socket.causeRead Protocol.OKAY - cmd.execute 'main' - .then (stream) -> - stream.end() - expect(stream).to.be.an.instanceof Stream.Readable - done() diff --git a/test/adb/command/host-transport/log.js b/test/adb/command/host-transport/log.js new file mode 100644 index 00000000..199ca635 --- /dev/null +++ b/test/adb/command/host-transport/log.js @@ -0,0 +1,48 @@ +var Chai, LogCommand, MockConnection, Protocol, Sinon, Stream, expect; + +Stream = require('stream'); + +Sinon = require('sinon'); + +Chai = require('chai'); + +Chai.use(require('sinon-chai')); + +expect = Chai.expect; + +MockConnection = require('../../../mock/connection'); + +Protocol = require('../../../../src/adb/protocol'); + +LogCommand = require('../../../../src/adb/command/host-transport/log'); + +describe('LogCommand', function() { + it("should send 'log:'", function(done) { + var cmd, conn; + conn = new MockConnection; + cmd = new LogCommand(conn); + conn.socket.on('write', function(chunk) { + return expect(chunk.toString()).to.equal(Protocol.encodeData('log:main').toString()); + }); + setImmediate(function() { + conn.socket.causeRead(Protocol.OKAY); + return conn.socket.causeEnd(); + }); + return cmd.execute('main').then(function(stream) { + return done(); + }); + }); + return it("should resolve with the log stream", function(done) { + var cmd, conn; + conn = new MockConnection; + cmd = new LogCommand(conn); + setImmediate(function() { + return conn.socket.causeRead(Protocol.OKAY); + }); + return cmd.execute('main').then(function(stream) { + stream.end(); + expect(stream).to.be.an["instanceof"](Stream.Readable); + return done(); + }); + }); +}); diff --git a/test/adb/command/host-transport/logcat.coffee b/test/adb/command/host-transport/logcat.coffee deleted file mode 100644 index 1249c7d8..00000000 --- a/test/adb/command/host-transport/logcat.coffee +++ /dev/null @@ -1,81 +0,0 @@ -Stream = require 'stream' -Promise = require 'bluebird' -Sinon = require 'sinon' -Chai = require 'chai' -Chai.use require 'sinon-chai' -{expect} = Chai - -MockConnection = require '../../../mock/connection' -Protocol = require '../../../../src/adb/protocol' -Parser = require '../../../../src/adb/parser' -LogcatCommand = require '../../../../src/adb/command/host-transport/logcat' - -describe 'LogcatCommand', -> - - it "should send 'echo && logcat -B *:I'", (done) -> - conn = new MockConnection - cmd = new LogcatCommand conn - conn.socket.on 'write', (chunk) -> - expect(chunk.toString()).to.equal \ - Protocol.encodeData('shell:echo && - logcat -B *:I 2>/dev/null').toString() - setImmediate -> - conn.socket.causeRead Protocol.OKAY - conn.socket.causeEnd() - cmd.execute() - .then (stream) -> - done() - - it "should send 'echo && logcat -c && logcat -B *:I' if options.clear - is set", (done) -> - conn = new MockConnection - cmd = new LogcatCommand conn - conn.socket.on 'write', (chunk) -> - expect(chunk.toString()).to.equal \ - Protocol.encodeData('shell:echo && logcat -c 2>/dev/null && - logcat -B *:I 2>/dev/null').toString() - setImmediate -> - conn.socket.causeRead Protocol.OKAY - conn.socket.causeEnd() - cmd.execute clear: true - .then (stream) -> - done() - - it "should resolve with the logcat stream", (done) -> - conn = new MockConnection - cmd = new LogcatCommand conn - setImmediate -> - conn.socket.causeRead Protocol.OKAY - cmd.execute() - .then (stream) -> - stream.end() - expect(stream).to.be.an.instanceof Stream.Readable - done() - - it "should perform CRLF transformation by default", (done) -> - conn = new MockConnection - cmd = new LogcatCommand conn - setImmediate -> - conn.socket.causeRead Protocol.OKAY - conn.socket.causeRead '\r\nfoo\r\n' - conn.socket.causeEnd() - cmd.execute() - .then (stream) -> - new Parser(stream).readAll() - .then (out) -> - expect(out.toString()).to.equal 'foo\n' - done() - - it "should not perform CRLF transformation if not needed", (done) -> - conn = new MockConnection - cmd = new LogcatCommand conn - setImmediate -> - conn.socket.causeRead Protocol.OKAY - conn.socket.causeRead '\nfoo\r\n' - conn.socket.causeEnd() - cmd.execute() - .then (stream) -> - new Parser(stream).readAll() - .then (out) -> - expect(out.toString()).to.equal 'foo\r\n' - done() diff --git a/test/adb/command/host-transport/logcat.js b/test/adb/command/host-transport/logcat.js new file mode 100644 index 00000000..445e862c --- /dev/null +++ b/test/adb/command/host-transport/logcat.js @@ -0,0 +1,101 @@ +var Chai, LogcatCommand, MockConnection, Parser, Promise, Protocol, Sinon, Stream, expect; + +Stream = require('stream'); + +Promise = require('bluebird'); + +Sinon = require('sinon'); + +Chai = require('chai'); + +Chai.use(require('sinon-chai')); + +expect = Chai.expect; + +MockConnection = require('../../../mock/connection'); + +Protocol = require('../../../../src/adb/protocol'); + +Parser = require('../../../../src/adb/parser'); + +LogcatCommand = require('../../../../src/adb/command/host-transport/logcat'); + +describe('LogcatCommand', function() { + it("should send 'echo && logcat -B *:I'", function(done) { + var cmd, conn; + conn = new MockConnection; + cmd = new LogcatCommand(conn); + conn.socket.on('write', function(chunk) { + return expect(chunk.toString()).to.equal(Protocol.encodeData('shell:echo && logcat -B *:I 2>/dev/null').toString()); + }); + setImmediate(function() { + conn.socket.causeRead(Protocol.OKAY); + return conn.socket.causeEnd(); + }); + return cmd.execute().then(function(stream) { + return done(); + }); + }); + it("should send 'echo && logcat -c && logcat -B *:I' if options.clear is set", function(done) { + var cmd, conn; + conn = new MockConnection; + cmd = new LogcatCommand(conn); + conn.socket.on('write', function(chunk) { + return expect(chunk.toString()).to.equal(Protocol.encodeData('shell:echo && logcat -c 2>/dev/null && logcat -B *:I 2>/dev/null').toString()); + }); + setImmediate(function() { + conn.socket.causeRead(Protocol.OKAY); + return conn.socket.causeEnd(); + }); + return cmd.execute({ + clear: true + }).then(function(stream) { + return done(); + }); + }); + it("should resolve with the logcat stream", function(done) { + var cmd, conn; + conn = new MockConnection; + cmd = new LogcatCommand(conn); + setImmediate(function() { + return conn.socket.causeRead(Protocol.OKAY); + }); + return cmd.execute().then(function(stream) { + stream.end(); + expect(stream).to.be.an["instanceof"](Stream.Readable); + return done(); + }); + }); + it("should perform CRLF transformation by default", function(done) { + var cmd, conn; + conn = new MockConnection; + cmd = new LogcatCommand(conn); + setImmediate(function() { + conn.socket.causeRead(Protocol.OKAY); + conn.socket.causeRead('\r\nfoo\r\n'); + return conn.socket.causeEnd(); + }); + return cmd.execute().then(function(stream) { + return new Parser(stream).readAll(); + }).then(function(out) { + expect(out.toString()).to.equal('foo\n'); + return done(); + }); + }); + return it("should not perform CRLF transformation if not needed", function(done) { + var cmd, conn; + conn = new MockConnection; + cmd = new LogcatCommand(conn); + setImmediate(function() { + conn.socket.causeRead(Protocol.OKAY); + conn.socket.causeRead('\nfoo\r\n'); + return conn.socket.causeEnd(); + }); + return cmd.execute().then(function(stream) { + return new Parser(stream).readAll(); + }).then(function(out) { + expect(out.toString()).to.equal('foo\r\n'); + return done(); + }); + }); +}); diff --git a/test/adb/command/host-transport/monkey.coffee b/test/adb/command/host-transport/monkey.coffee deleted file mode 100644 index 82a81cf4..00000000 --- a/test/adb/command/host-transport/monkey.coffee +++ /dev/null @@ -1,50 +0,0 @@ -Stream = require 'stream' -Promise = require 'bluebird' -Sinon = require 'sinon' -Chai = require 'chai' -Chai.use require 'sinon-chai' -{expect} = Chai - -MockConnection = require '../../../mock/connection' -Protocol = require '../../../../src/adb/protocol' -MonkeyCommand = require '../../../../src/adb/command/host-transport/monkey' - -describe 'MonkeyCommand', -> - - it "should send 'monkey --port -v'", (done) -> - conn = new MockConnection - cmd = new MonkeyCommand conn - conn.socket.on 'write', (chunk) -> - expect(chunk.toString()).to.equal \ - Protocol.encodeData('shell:EXTERNAL_STORAGE=/data/local/tmp monkey - --port 1080 -v').toString() - setImmediate -> - conn.socket.causeRead Protocol.OKAY - conn.socket.causeRead ':Monkey: foo\n' - cmd.execute 1080 - .then (stream) -> - done() - - it "should resolve with the output stream", (done) -> - conn = new MockConnection - cmd = new MonkeyCommand conn - setImmediate -> - conn.socket.causeRead Protocol.OKAY - conn.socket.causeRead ':Monkey: foo\n' - cmd.execute 1080 - .then (stream) -> - stream.end() - expect(stream).to.be.an.instanceof Stream.Readable - done() - - it "should resolve after a timeout if result can't be judged from - output", (done) -> - conn = new MockConnection - cmd = new MonkeyCommand conn - setImmediate -> - conn.socket.causeRead Protocol.OKAY - cmd.execute 1080 - .then (stream) -> - stream.end() - expect(stream).to.be.an.instanceof Stream.Readable - done() diff --git a/test/adb/command/host-transport/monkey.js b/test/adb/command/host-transport/monkey.js new file mode 100644 index 00000000..70f4bf06 --- /dev/null +++ b/test/adb/command/host-transport/monkey.js @@ -0,0 +1,64 @@ +var Chai, MockConnection, MonkeyCommand, Promise, Protocol, Sinon, Stream, expect; + +Stream = require('stream'); + +Promise = require('bluebird'); + +Sinon = require('sinon'); + +Chai = require('chai'); + +Chai.use(require('sinon-chai')); + +expect = Chai.expect; + +MockConnection = require('../../../mock/connection'); + +Protocol = require('../../../../src/adb/protocol'); + +MonkeyCommand = require('../../../../src/adb/command/host-transport/monkey'); + +describe('MonkeyCommand', function() { + it("should send 'monkey --port -v'", function(done) { + var cmd, conn; + conn = new MockConnection; + cmd = new MonkeyCommand(conn); + conn.socket.on('write', function(chunk) { + return expect(chunk.toString()).to.equal(Protocol.encodeData('shell:EXTERNAL_STORAGE=/data/local/tmp monkey --port 1080 -v').toString()); + }); + setImmediate(function() { + conn.socket.causeRead(Protocol.OKAY); + return conn.socket.causeRead(':Monkey: foo\n'); + }); + return cmd.execute(1080).then(function(stream) { + return done(); + }); + }); + it("should resolve with the output stream", function(done) { + var cmd, conn; + conn = new MockConnection; + cmd = new MonkeyCommand(conn); + setImmediate(function() { + conn.socket.causeRead(Protocol.OKAY); + return conn.socket.causeRead(':Monkey: foo\n'); + }); + return cmd.execute(1080).then(function(stream) { + stream.end(); + expect(stream).to.be.an["instanceof"](Stream.Readable); + return done(); + }); + }); + return it("should resolve after a timeout if result can't be judged from output", function(done) { + var cmd, conn; + conn = new MockConnection; + cmd = new MonkeyCommand(conn); + setImmediate(function() { + return conn.socket.causeRead(Protocol.OKAY); + }); + return cmd.execute(1080).then(function(stream) { + stream.end(); + expect(stream).to.be.an["instanceof"](Stream.Readable); + return done(); + }); + }); +}); diff --git a/test/adb/command/host-transport/reboot.coffee b/test/adb/command/host-transport/reboot.coffee deleted file mode 100644 index d1116a76..00000000 --- a/test/adb/command/host-transport/reboot.coffee +++ /dev/null @@ -1,40 +0,0 @@ -Sinon = require 'sinon' -Chai = require 'chai' -Chai.use require 'sinon-chai' -{expect} = Chai - -MockConnection = require '../../../mock/connection' -Protocol = require '../../../../src/adb/protocol' -RebootCommand = require '../../../../src/adb/command/host-transport/reboot' - -describe 'RebootCommand', -> - - it "should send 'reboot:'", (done) -> - conn = new MockConnection - cmd = new RebootCommand conn - conn.socket.on 'write', (chunk) -> - expect(chunk.toString()).to.equal \ - Protocol.encodeData('reboot:').toString() - setImmediate -> - conn.socket.causeRead Protocol.OKAY - conn.socket.causeEnd() - cmd.execute() - .then -> - done() - - it "should send wait for the connection to end", (done) -> - conn = new MockConnection - cmd = new RebootCommand conn - ended = false - conn.socket.on 'write', (chunk) -> - expect(chunk.toString()).to.equal \ - Protocol.encodeData('reboot:').toString() - setImmediate -> - conn.socket.causeRead Protocol.OKAY - setImmediate -> - ended = true - conn.socket.causeEnd() - cmd.execute() - .then -> - expect(ended).to.be.true - done() diff --git a/test/adb/command/host-transport/reboot.js b/test/adb/command/host-transport/reboot.js new file mode 100644 index 00000000..320ca13b --- /dev/null +++ b/test/adb/command/host-transport/reboot.js @@ -0,0 +1,53 @@ +var Chai, MockConnection, Protocol, RebootCommand, Sinon, expect; + +Sinon = require('sinon'); + +Chai = require('chai'); + +Chai.use(require('sinon-chai')); + +expect = Chai.expect; + +MockConnection = require('../../../mock/connection'); + +Protocol = require('../../../../src/adb/protocol'); + +RebootCommand = require('../../../../src/adb/command/host-transport/reboot'); + +describe('RebootCommand', function() { + it("should send 'reboot:'", function(done) { + var cmd, conn; + conn = new MockConnection; + cmd = new RebootCommand(conn); + conn.socket.on('write', function(chunk) { + return expect(chunk.toString()).to.equal(Protocol.encodeData('reboot:').toString()); + }); + setImmediate(function() { + conn.socket.causeRead(Protocol.OKAY); + return conn.socket.causeEnd(); + }); + return cmd.execute().then(function() { + return done(); + }); + }); + return it("should send wait for the connection to end", function(done) { + var cmd, conn, ended; + conn = new MockConnection; + cmd = new RebootCommand(conn); + ended = false; + conn.socket.on('write', function(chunk) { + return expect(chunk.toString()).to.equal(Protocol.encodeData('reboot:').toString()); + }); + setImmediate(function() { + return conn.socket.causeRead(Protocol.OKAY); + }); + setImmediate(function() { + ended = true; + return conn.socket.causeEnd(); + }); + return cmd.execute().then(function() { + expect(ended).to.be["true"]; + return done(); + }); + }); +}); diff --git a/test/adb/command/host-transport/remount.coffee b/test/adb/command/host-transport/remount.coffee deleted file mode 100644 index 1eaa3257..00000000 --- a/test/adb/command/host-transport/remount.coffee +++ /dev/null @@ -1,22 +0,0 @@ -Sinon = require 'sinon' -Chai = require 'chai' -Chai.use require 'sinon-chai' -{expect} = Chai - -MockConnection = require '../../../mock/connection' -Protocol = require '../../../../src/adb/protocol' -RemountCommand = require '../../../../src/adb/command/host-transport/remount' - -describe 'RemountCommand', -> - - it "should send 'remount:'", (done) -> - conn = new MockConnection - cmd = new RemountCommand conn - conn.socket.on 'write', (chunk) -> - expect(chunk.toString()).to.equal \ - Protocol.encodeData('remount:').toString() - conn.socket.causeRead Protocol.OKAY - conn.socket.causeEnd() - cmd.execute() - .then -> - done() diff --git a/test/adb/command/host-transport/remount.js b/test/adb/command/host-transport/remount.js new file mode 100644 index 00000000..43a16506 --- /dev/null +++ b/test/adb/command/host-transport/remount.js @@ -0,0 +1,31 @@ +var Chai, MockConnection, Protocol, RemountCommand, Sinon, expect; + +Sinon = require('sinon'); + +Chai = require('chai'); + +Chai.use(require('sinon-chai')); + +expect = Chai.expect; + +MockConnection = require('../../../mock/connection'); + +Protocol = require('../../../../src/adb/protocol'); + +RemountCommand = require('../../../../src/adb/command/host-transport/remount'); + +describe('RemountCommand', function() { + return it("should send 'remount:'", function(done) { + var cmd, conn; + conn = new MockConnection; + cmd = new RemountCommand(conn); + conn.socket.on('write', function(chunk) { + expect(chunk.toString()).to.equal(Protocol.encodeData('remount:').toString()); + conn.socket.causeRead(Protocol.OKAY); + return conn.socket.causeEnd(); + }); + return cmd.execute().then(function() { + return done(); + }); + }); +}); diff --git a/test/adb/command/host-transport/root.coffee b/test/adb/command/host-transport/root.coffee deleted file mode 100644 index 38fc60c0..00000000 --- a/test/adb/command/host-transport/root.coffee +++ /dev/null @@ -1,39 +0,0 @@ -Stream = require 'stream' -Sinon = require 'sinon' -Chai = require 'chai' -Chai.use require 'sinon-chai' -{expect} = Chai - -MockConnection = require '../../../mock/connection' -Protocol = require '../../../../src/adb/protocol' -RootCommand = require '../../../../src/adb/command/host-transport/root' - -describe 'RootCommand', -> - - it "should send 'root:'", (done) -> - conn = new MockConnection - cmd = new RootCommand conn - conn.socket.on 'write', (chunk) -> - expect(chunk.toString()).to.equal \ - Protocol.encodeData('root:').toString() - setImmediate -> - conn.socket.causeRead Protocol.OKAY - conn.socket.causeRead "restarting adbd as root\n" - conn.socket.causeEnd() - cmd.execute() - .then (val) -> - expect(val).to.be.true - done() - - it "should reject on unexpected reply", (done) -> - conn = new MockConnection - cmd = new RootCommand conn - setImmediate -> - conn.socket.causeRead Protocol.OKAY - conn.socket.causeRead "adbd cannot run as root in production builds\n" - conn.socket.causeEnd() - cmd.execute() - .catch (err) -> - expect(err.message).to.eql \ - 'adbd cannot run as root in production builds' - done() diff --git a/test/adb/command/host-transport/root.js b/test/adb/command/host-transport/root.js new file mode 100644 index 00000000..5469c70a --- /dev/null +++ b/test/adb/command/host-transport/root.js @@ -0,0 +1,51 @@ +var Chai, MockConnection, Protocol, RootCommand, Sinon, Stream, expect; + +Stream = require('stream'); + +Sinon = require('sinon'); + +Chai = require('chai'); + +Chai.use(require('sinon-chai')); + +expect = Chai.expect; + +MockConnection = require('../../../mock/connection'); + +Protocol = require('../../../../src/adb/protocol'); + +RootCommand = require('../../../../src/adb/command/host-transport/root'); + +describe('RootCommand', function() { + it("should send 'root:'", function(done) { + var cmd, conn; + conn = new MockConnection; + cmd = new RootCommand(conn); + conn.socket.on('write', function(chunk) { + return expect(chunk.toString()).to.equal(Protocol.encodeData('root:').toString()); + }); + setImmediate(function() { + conn.socket.causeRead(Protocol.OKAY); + conn.socket.causeRead("restarting adbd as root\n"); + return conn.socket.causeEnd(); + }); + return cmd.execute().then(function(val) { + expect(val).to.be["true"]; + return done(); + }); + }); + return it("should reject on unexpected reply", function(done) { + var cmd, conn; + conn = new MockConnection; + cmd = new RootCommand(conn); + setImmediate(function() { + conn.socket.causeRead(Protocol.OKAY); + conn.socket.causeRead("adbd cannot run as root in production builds\n"); + return conn.socket.causeEnd(); + }); + return cmd.execute()["catch"](function(err) { + expect(err.message).to.eql('adbd cannot run as root in production builds'); + return done(); + }); + }); +}); diff --git a/test/adb/command/host-transport/screencap.coffee b/test/adb/command/host-transport/screencap.coffee deleted file mode 100644 index fd9836a5..00000000 --- a/test/adb/command/host-transport/screencap.coffee +++ /dev/null @@ -1,78 +0,0 @@ -Sinon = require 'sinon' -Chai = require 'chai' -Chai.use require 'sinon-chai' -{expect} = Chai - -MockConnection = require '../../../mock/connection' -Protocol = require '../../../../src/adb/protocol' -Parser = require '../../../../src/adb/parser' -ScreencapCommand = - require '../../../../src/adb/command/host-transport/screencap' - -describe 'ScreencapCommand', -> - - it "should send 'screencap -p'", (done) -> - conn = new MockConnection - cmd = new ScreencapCommand conn - conn.socket.on 'write', (chunk) -> - expect(chunk.toString()).to.equal \ - Protocol.encodeData('shell:echo && screencap -p 2>/dev/null').toString() - setImmediate -> - conn.socket.causeRead Protocol.OKAY - conn.socket.causeRead '\r\nlegit image' - conn.socket.causeEnd() - cmd.execute() - .then (stream) -> - done() - - it "should resolve with the PNG stream", (done) -> - conn = new MockConnection - cmd = new ScreencapCommand conn - setImmediate -> - conn.socket.causeRead Protocol.OKAY - conn.socket.causeRead '\r\nlegit image' - conn.socket.causeEnd() - cmd.execute() - .then (stream) -> - new Parser(stream).readAll() - .then (out) -> - expect(out.toString()).to.equal 'legit image' - done() - - it "should reject if command not supported", (done) -> - conn = new MockConnection - cmd = new ScreencapCommand conn - setImmediate -> - conn.socket.causeRead Protocol.OKAY - conn.socket.causeEnd() - cmd.execute() - .catch (err) -> - done() - - it "should perform CRLF transformation by default", (done) -> - conn = new MockConnection - cmd = new ScreencapCommand conn - setImmediate -> - conn.socket.causeRead Protocol.OKAY - conn.socket.causeRead '\r\nfoo\r\n' - conn.socket.causeEnd() - cmd.execute() - .then (stream) -> - new Parser(stream).readAll() - .then (out) -> - expect(out.toString()).to.equal 'foo\n' - done() - - it "should not perform CRLF transformation if not needed", (done) -> - conn = new MockConnection - cmd = new ScreencapCommand conn - setImmediate -> - conn.socket.causeRead Protocol.OKAY - conn.socket.causeRead '\nfoo\r\n' - conn.socket.causeEnd() - cmd.execute() - .then (stream) -> - new Parser(stream).readAll() - .then (out) -> - expect(out.toString()).to.equal 'foo\r\n' - done() diff --git a/test/adb/command/host-transport/screencap.js b/test/adb/command/host-transport/screencap.js new file mode 100644 index 00000000..8a875791 --- /dev/null +++ b/test/adb/command/host-transport/screencap.js @@ -0,0 +1,96 @@ +var Chai, MockConnection, Parser, Protocol, ScreencapCommand, Sinon, expect; + +Sinon = require('sinon'); + +Chai = require('chai'); + +Chai.use(require('sinon-chai')); + +expect = Chai.expect; + +MockConnection = require('../../../mock/connection'); + +Protocol = require('../../../../src/adb/protocol'); + +Parser = require('../../../../src/adb/parser'); + +ScreencapCommand = require('../../../../src/adb/command/host-transport/screencap'); + +describe('ScreencapCommand', function() { + it("should send 'screencap -p'", function(done) { + var cmd, conn; + conn = new MockConnection; + cmd = new ScreencapCommand(conn); + conn.socket.on('write', function(chunk) { + return expect(chunk.toString()).to.equal(Protocol.encodeData('shell:echo && screencap -p 2>/dev/null').toString()); + }); + setImmediate(function() { + conn.socket.causeRead(Protocol.OKAY); + conn.socket.causeRead('\r\nlegit image'); + return conn.socket.causeEnd(); + }); + return cmd.execute().then(function(stream) { + return done(); + }); + }); + it("should resolve with the PNG stream", function(done) { + var cmd, conn; + conn = new MockConnection; + cmd = new ScreencapCommand(conn); + setImmediate(function() { + conn.socket.causeRead(Protocol.OKAY); + conn.socket.causeRead('\r\nlegit image'); + return conn.socket.causeEnd(); + }); + return cmd.execute().then(function(stream) { + return new Parser(stream).readAll(); + }).then(function(out) { + expect(out.toString()).to.equal('legit image'); + return done(); + }); + }); + it("should reject if command not supported", function(done) { + var cmd, conn; + conn = new MockConnection; + cmd = new ScreencapCommand(conn); + setImmediate(function() { + conn.socket.causeRead(Protocol.OKAY); + return conn.socket.causeEnd(); + }); + return cmd.execute()["catch"](function(err) { + return done(); + }); + }); + it("should perform CRLF transformation by default", function(done) { + var cmd, conn; + conn = new MockConnection; + cmd = new ScreencapCommand(conn); + setImmediate(function() { + conn.socket.causeRead(Protocol.OKAY); + conn.socket.causeRead('\r\nfoo\r\n'); + return conn.socket.causeEnd(); + }); + return cmd.execute().then(function(stream) { + return new Parser(stream).readAll(); + }).then(function(out) { + expect(out.toString()).to.equal('foo\n'); + return done(); + }); + }); + return it("should not perform CRLF transformation if not needed", function(done) { + var cmd, conn; + conn = new MockConnection; + cmd = new ScreencapCommand(conn); + setImmediate(function() { + conn.socket.causeRead(Protocol.OKAY); + conn.socket.causeRead('\nfoo\r\n'); + return conn.socket.causeEnd(); + }); + return cmd.execute().then(function(stream) { + return new Parser(stream).readAll(); + }).then(function(out) { + expect(out.toString()).to.equal('foo\r\n'); + return done(); + }); + }); +}); diff --git a/test/adb/command/host-transport/shell.coffee b/test/adb/command/host-transport/shell.coffee deleted file mode 100644 index 1d3906de..00000000 --- a/test/adb/command/host-transport/shell.coffee +++ /dev/null @@ -1,67 +0,0 @@ -Stream = require 'stream' -Sinon = require 'sinon' -Chai = require 'chai' -Chai.use require 'sinon-chai' -{expect} = Chai - -MockConnection = require '../../../mock/connection' -Protocol = require '../../../../src/adb/protocol' -Parser = require '../../../../src/adb/parser' -ShellCommand = - require '../../../../src/adb/command/host-transport/shell' - -describe 'ShellCommand', -> - - it "should pass String commands as-is", (done) -> - conn = new MockConnection - cmd = new ShellCommand conn - conn.socket.on 'write', (chunk) -> - expect(chunk.toString()).to.equal \ - Protocol.encodeData('shell:foo \'bar').toString() - setImmediate -> - conn.socket.causeRead Protocol.OKAY - conn.socket.causeEnd() - cmd.execute 'foo \'bar' - .then (out) -> - done() - - it "should escape Array commands", (done) -> - conn = new MockConnection - cmd = new ShellCommand conn - conn.socket.on 'write', (chunk) -> - expect(chunk.toString()).to.equal \ - Protocol.encodeData("""shell:'foo' ''"'"'bar'"'"'' '"'""").toString() - setImmediate -> - conn.socket.causeRead Protocol.OKAY - conn.socket.causeEnd() - cmd.execute ['foo', '\'bar\'', '"'] - .then (out) -> - done() - - it "should not escape numbers in arguments", (done) -> - conn = new MockConnection - cmd = new ShellCommand conn - conn.socket.on 'write', (chunk) -> - expect(chunk.toString()).to.equal \ - Protocol.encodeData("""shell:'foo' 67""").toString() - setImmediate -> - conn.socket.causeRead Protocol.OKAY - conn.socket.causeEnd() - cmd.execute ['foo', 67] - .then (out) -> - done() - - it "should reject with FailError on ADB failure (not command - failure)", (done) -> - conn = new MockConnection - cmd = new ShellCommand conn - conn.socket.on 'write', (chunk) -> - expect(chunk.toString()).to.equal \ - Protocol.encodeData("""shell:'foo'""").toString() - setImmediate -> - conn.socket.causeRead Protocol.FAIL - conn.socket.causeRead Protocol.encodeData 'mystery' - conn.socket.causeEnd() - cmd.execute ['foo'] - .catch Parser.FailError, (err) -> - done() diff --git a/test/adb/command/host-transport/shell.js b/test/adb/command/host-transport/shell.js new file mode 100644 index 00000000..e791ec4f --- /dev/null +++ b/test/adb/command/host-transport/shell.js @@ -0,0 +1,83 @@ +var Chai, MockConnection, Parser, Protocol, ShellCommand, Sinon, Stream, expect; + +Stream = require('stream'); + +Sinon = require('sinon'); + +Chai = require('chai'); + +Chai.use(require('sinon-chai')); + +expect = Chai.expect; + +MockConnection = require('../../../mock/connection'); + +Protocol = require('../../../../src/adb/protocol'); + +Parser = require('../../../../src/adb/parser'); + +ShellCommand = require('../../../../src/adb/command/host-transport/shell'); + +describe('ShellCommand', function() { + it("should pass String commands as-is", function(done) { + var cmd, conn; + conn = new MockConnection; + cmd = new ShellCommand(conn); + conn.socket.on('write', function(chunk) { + return expect(chunk.toString()).to.equal(Protocol.encodeData('shell:foo \'bar').toString()); + }); + setImmediate(function() { + conn.socket.causeRead(Protocol.OKAY); + return conn.socket.causeEnd(); + }); + return cmd.execute('foo \'bar').then(function(out) { + return done(); + }); + }); + it("should escape Array commands", function(done) { + var cmd, conn; + conn = new MockConnection; + cmd = new ShellCommand(conn); + conn.socket.on('write', function(chunk) { + return expect(chunk.toString()).to.equal(Protocol.encodeData("shell:'foo' ''\"'\"'bar'\"'\"'' '\"'").toString()); + }); + setImmediate(function() { + conn.socket.causeRead(Protocol.OKAY); + return conn.socket.causeEnd(); + }); + return cmd.execute(['foo', '\'bar\'', '"']).then(function(out) { + return done(); + }); + }); + it("should not escape numbers in arguments", function(done) { + var cmd, conn; + conn = new MockConnection; + cmd = new ShellCommand(conn); + conn.socket.on('write', function(chunk) { + return expect(chunk.toString()).to.equal(Protocol.encodeData("shell:'foo' 67").toString()); + }); + setImmediate(function() { + conn.socket.causeRead(Protocol.OKAY); + return conn.socket.causeEnd(); + }); + return cmd.execute(['foo', 67]).then(function(out) { + return done(); + }); + }); + return it("should reject with FailError on ADB failure (not command failure)", function(done) { + var cmd, conn; + conn = new MockConnection; + cmd = new ShellCommand(conn); + conn.socket.on('write', function(chunk) { + return expect(chunk.toString()).to.equal(Protocol.encodeData("shell:'foo'").toString()); + }); + setImmediate(function() { + conn.socket.causeRead(Protocol.FAIL); + conn.socket.causeRead(Protocol.encodeData('mystery')); + return conn.socket.causeEnd(); + }); + return cmd.execute(['foo'])["catch"](Parser.FailError, function(err) { + return done(); + }); + }); +}); diff --git a/test/adb/command/host-transport/startactivity.coffee b/test/adb/command/host-transport/startactivity.coffee deleted file mode 100644 index 3e2dfe30..00000000 --- a/test/adb/command/host-transport/startactivity.coffee +++ /dev/null @@ -1,516 +0,0 @@ -Stream = require 'stream' -Sinon = require 'sinon' -Chai = require 'chai' -Chai.use require 'sinon-chai' -{expect} = Chai - -MockConnection = require '../../../mock/connection' -Protocol = require '../../../../src/adb/protocol' -StartActivityCommand = require \ - '../../../../src/adb/command/host-transport/startactivity' - -describe 'StartActivityCommand', -> - - it "should succeed when 'Success' returned", (done) -> - conn = new MockConnection - cmd = new StartActivityCommand conn - setImmediate -> - conn.socket.causeRead Protocol.OKAY - conn.socket.causeRead 'Success' - conn.socket.causeEnd() - options = - component: 'com.dummy.component/.Main' - cmd.execute options - .then -> - done() - - it "should fail when 'Error' returned", (done) -> - conn = new MockConnection - cmd = new StartActivityCommand conn - setImmediate -> - conn.socket.causeRead Protocol.OKAY - conn.socket.causeRead 'Error: foo\n' - conn.socket.causeEnd() - options = - component: 'com.dummy.component/.Main' - cmd.execute options - .catch (err) -> - expect(err).to.be.be.an.instanceOf Error - done() - - it "should send 'am start -n '", (done) -> - conn = new MockConnection - cmd = new StartActivityCommand conn - conn.socket.on 'write', (chunk) -> - expect(chunk.toString()).to.equal \ - Protocol.encodeData("shell:am start - -n 'com.dummy.component/.Main'").toString() - setImmediate -> - conn.socket.causeRead Protocol.OKAY - conn.socket.causeRead 'Success\n' - conn.socket.causeEnd() - options = - component: 'com.dummy.component/.Main' - cmd.execute options - .then -> - done() - - it "should send 'am start -W -D --user 0 -n '", (done) -> - conn = new MockConnection - cmd = new StartActivityCommand conn - conn.socket.on 'write', (chunk) -> - expect(chunk.toString()).to.equal \ - Protocol.encodeData("shell:am start - -n 'com.dummy.component/.Main' - -D - -W - --user 0").toString() - setImmediate -> - conn.socket.causeRead Protocol.OKAY - conn.socket.causeRead 'Success\n' - conn.socket.causeEnd() - options = - component: 'com.dummy.component/.Main' - user: 0 - wait: true - debug: true - cmd.execute options - .then -> - done() - - it "should send 'am start -a '", (done) -> - conn = new MockConnection - cmd = new StartActivityCommand conn - conn.socket.on 'write', (chunk) -> - expect(chunk.toString()).to.equal \ - Protocol.encodeData("shell:am start - -a 'foo.ACTION_BAR'").toString() - setImmediate -> - conn.socket.causeRead Protocol.OKAY - conn.socket.causeRead 'Success\n' - conn.socket.causeEnd() - options = - action: "foo.ACTION_BAR" - cmd.execute options - .then -> - done() - - it "should send 'am start -d '", (done) -> - conn = new MockConnection - cmd = new StartActivityCommand conn - conn.socket.on 'write', (chunk) -> - expect(chunk.toString()).to.equal \ - Protocol.encodeData("shell:am start - -d 'foo://bar'").toString() - setImmediate -> - conn.socket.causeRead Protocol.OKAY - conn.socket.causeRead 'Success\n' - conn.socket.causeEnd() - options = - data: "foo://bar" - cmd.execute options - .then -> - done() - - it "should send 'am start -t '", (done) -> - conn = new MockConnection - cmd = new StartActivityCommand conn - conn.socket.on 'write', (chunk) -> - expect(chunk.toString()).to.equal \ - Protocol.encodeData("shell:am start - -t 'text/plain'").toString() - setImmediate -> - conn.socket.causeRead Protocol.OKAY - conn.socket.causeRead 'Success\n' - conn.socket.causeEnd() - options = - mimeType: "text/plain" - cmd.execute options - .then -> - done() - - it "should send 'am start -c '", (done) -> - conn = new MockConnection - cmd = new StartActivityCommand conn - conn.socket.on 'write', (chunk) -> - expect(chunk.toString()).to.equal \ - Protocol.encodeData("shell:am start - -c 'android.intent.category.LAUNCHER'").toString() - setImmediate -> - conn.socket.causeRead Protocol.OKAY - conn.socket.causeRead 'Success\n' - conn.socket.causeEnd() - options = - category: "android.intent.category.LAUNCHER" - cmd.execute options - .then -> - done() - - it "should send 'am start -c -c '", (done) -> - conn = new MockConnection - cmd = new StartActivityCommand conn - conn.socket.on 'write', (chunk) -> - expect(chunk.toString()).to.equal \ - Protocol.encodeData("shell:am start - -c 'android.intent.category.LAUNCHER' - -c 'android.intent.category.DEFAULT'").toString() - setImmediate -> - conn.socket.causeRead Protocol.OKAY - conn.socket.causeRead 'Success\n' - conn.socket.causeEnd() - options = - category: [ - "android.intent.category.LAUNCHER" - "android.intent.category.DEFAULT" - ] - cmd.execute options - .then -> - done() - - it "should send 'am start -f '", (done) -> - conn = new MockConnection - cmd = new StartActivityCommand conn - conn.socket.on 'write', (chunk) -> - expect(chunk.toString()).to.equal \ - Protocol.encodeData("shell:am start - -f #{0x10210000}").toString() - setImmediate -> - conn.socket.causeRead Protocol.OKAY - conn.socket.causeRead 'Success\n' - conn.socket.causeEnd() - options = - flags: 0x10210000 - cmd.execute options - .then -> - done() - - it "should send 'am start -n --es '", (done) -> - conn = new MockConnection - cmd = new StartActivityCommand conn - conn.socket.on 'write', (chunk) -> - expect(chunk.toString()).to.equal \ - Protocol.encodeData("shell:am start - --es 'key1' 'value1' - --es 'key2' 'value2' - -n 'com.dummy.component/.Main'").toString() - setImmediate -> - conn.socket.causeRead Protocol.OKAY - conn.socket.causeRead 'Success\n' - conn.socket.causeEnd() - options = - component: "com.dummy.component/.Main" - extras: [ - key: 'key1' - value: 'value1' - , - key: 'key2' - value: 'value2' - ] - cmd.execute options - .then -> - done() - - it "should send 'am start -n --ei '", (done) -> - conn = new MockConnection - cmd = new StartActivityCommand conn - conn.socket.on 'write', (chunk) -> - expect(chunk.toString()).to.equal \ - Protocol.encodeData("shell:am start - --ei 'key1' 1 - --ei 'key2' 2 - -n 'com.dummy.component/.Main'").toString() - setImmediate -> - conn.socket.causeRead Protocol.OKAY - conn.socket.causeRead 'Success\n' - conn.socket.causeEnd() - options = - component: 'com.dummy.component/.Main' - extras: [ - key: 'key1' - value: 1 - type: 'int' - , - key: 'key2' - value: 2 - type: 'int' - ] - cmd.execute options - .then -> - done() - - it "should send 'am start -n --ez '", (done) -> - conn = new MockConnection - cmd = new StartActivityCommand conn - conn.socket.on 'write', (chunk) -> - expect(chunk.toString()).to.equal \ - Protocol.encodeData("shell:am start - --ez 'key1' 'true' - --ez 'key2' 'false' - -n 'com.dummy.component/.Main'").toString() - setImmediate -> - conn.socket.causeRead Protocol.OKAY - conn.socket.causeRead 'Success' - conn.socket.causeEnd() - options = - component: "com.dummy.component/.Main" - extras: [ - key: 'key1' - value: true - type: 'bool' - , - key: 'key2' - value: false - type: 'bool' - ] - cmd.execute options - .then -> - done() - - it "should send 'am start -n --el '", (done) -> - conn = new MockConnection - cmd = new StartActivityCommand conn - conn.socket.on 'write', (chunk) -> - expect(chunk.toString()).to.equal \ - Protocol.encodeData("shell:am start - --el 'key1' 1 - --el 'key2' '2' - -n 'com.dummy.component/.Main'").toString() - setImmediate -> - conn.socket.causeRead Protocol.OKAY - conn.socket.causeRead 'Success' - conn.socket.causeEnd() - options = - component: 'com.dummy.component/.Main' - extras: [ - key: 'key1' - value: 1 - type: 'long' - , - key: 'key2' - value: '2' - type: 'long' - ] - cmd.execute options - .then -> - done() - - it "should send 'am start -n --eu '", (done) -> - conn = new MockConnection - cmd = new StartActivityCommand conn - conn.socket.on 'write', (chunk) -> - expect(chunk.toString()).to.equal \ - Protocol.encodeData("shell:am start - --eu 'key1' 'http://example.org' - --eu 'key2' 'http://example.org' - -n 'com.dummy.component/.Main'").toString() - setImmediate -> - conn.socket.causeRead Protocol.OKAY - conn.socket.causeRead 'Success' - conn.socket.causeEnd() - options = - component: 'com.dummy.component/.Main' - extras: [ - key: 'key1' - value: 'http://example.org' - type: 'uri' - , - key: 'key2' - value: 'http://example.org' - type: 'uri' - ] - cmd.execute options - .then -> - done() - - it "should send 'am start -n --es '", (done) -> - conn = new MockConnection - cmd = new StartActivityCommand conn - conn.socket.on 'write', (chunk) -> - expect(chunk.toString()).to.equal \ - Protocol.encodeData("shell:am start - --es 'key1' 'a' - --es 'key2' 'b' - -n 'com.dummy.component/.Main'").toString() - setImmediate -> - conn.socket.causeRead Protocol.OKAY - conn.socket.causeRead 'Success' - conn.socket.causeEnd() - options = - component: 'com.dummy.component/.Main' - extras: [ - key: 'key1' - value: 'a' - type: 'string' - , - key: 'key2' - value: 'b' - type: 'string' - ] - cmd.execute options - .then -> - done() - - it "should send 'am start -n --eia '", (done) -> - conn = new MockConnection - cmd = new StartActivityCommand conn - conn.socket.on 'write', (chunk) -> - expect(chunk.toString()).to.equal \ - Protocol.encodeData("shell:am start - --eia 'key1' '2,3' - --ela 'key2' '20,30' - --ei 'key3' 5 - -n 'com.dummy.component/.Main'").toString() - setImmediate -> - conn.socket.causeRead Protocol.OKAY - conn.socket.causeRead 'Success' - conn.socket.causeEnd() - options = - component: 'com.dummy.component/.Main' - extras: [ - key: 'key1' - value: [ - 2 - 3 - ] - type: 'int' - , - key: 'key2' - value: [ - 20 - 30 - ] - type: 'long' - , - key: 'key3' - value: 5 - type: 'int' - ] - cmd.execute options - .then -> - done() - - it "should send 'am start -n --esn '", (done) -> - conn = new MockConnection - cmd = new StartActivityCommand conn - conn.socket.on 'write', (chunk) -> - expect(chunk.toString()).to.equal \ - Protocol.encodeData("shell:am start - --esn 'key1' - --esn 'key2' - -n 'com.dummy.component/.Main'").toString() - setImmediate -> - conn.socket.causeRead Protocol.OKAY - conn.socket.causeRead 'Success' - conn.socket.causeEnd() - options = - component: 'com.dummy.component/.Main' - extras: [ - key: 'key1' - type: 'null' - , - key: 'key2' - type: 'null' - ] - cmd.execute options - .then -> - done() - - it "should throw when calling with an unknown extra type", (done) -> - conn = new MockConnection - cmd = new StartActivityCommand conn - options = - component: 'com.dummy.component/.Main' - extras: [ - key: 'key1' - value: 'value1' - type: 'nonexisting' - ] - expect(-> cmd.execute(options, ->)).to.throw - done() - - it "should accept mixed types of extras", (done) -> - conn = new MockConnection - cmd = new StartActivityCommand conn - conn.socket.on 'write', (chunk) -> - expect(chunk.toString()).to.equal \ - Protocol.encodeData("shell:am start - --ez 'key1' 'true' - --es 'key2' 'somestr' - --es 'key3' 'defaultType' - --ei 'key4' 3 - --el 'key5' '4' - --eu 'key6' 'http://example.org' - --esn 'key7' - -n 'com.dummy.component/.Main'").toString() - setImmediate -> - conn.socket.causeRead Protocol.OKAY - conn.socket.causeRead 'Success' - conn.socket.causeEnd() - options = - component: 'com.dummy.component/.Main' - extras: [ - key: 'key1' - value: true - type: 'bool' - , - key: 'key2' - value: 'somestr' - type: 'string' - , - key: 'key3' - value: 'defaultType' - , - key: 'key4' - value: 3 - type: 'int' - , - key: 'key5' - value: '4' - type: 'long' - , - key: 'key6' - value: 'http://example.org' - type: 'uri' - , - key: 'key7' - type: 'null' - ] - cmd.execute options - .then -> - done() - - it "should map short extras to long extras", (done) -> - conn = new MockConnection - cmd = new StartActivityCommand conn - short = cmd._formatExtras - someString: 'bar' - someInt: 5 - someUrl: - type: 'uri' - value: 'http://example.org' - someArray: - type: 'int' - value: [1, 2] - someNull: null - long = cmd._formatExtras [ - key: 'someString' - value: 'bar' - type: 'string' - , - key: 'someInt' - value: 5 - type: 'int' - , - key: 'someUrl' - value: 'http://example.org' - type: 'uri' - , - key: 'someArray' - value: [1, 2] - type: 'int' - , - key: 'someNull' - type: 'null' - ] - expect(short).to.eql long - done() diff --git a/test/adb/command/host-transport/startactivity.js b/test/adb/command/host-transport/startactivity.js new file mode 100644 index 00000000..1cf4a66f --- /dev/null +++ b/test/adb/command/host-transport/startactivity.js @@ -0,0 +1,557 @@ +var Chai, MockConnection, Protocol, Sinon, StartActivityCommand, Stream, expect; + +Stream = require('stream'); + +Sinon = require('sinon'); + +Chai = require('chai'); + +Chai.use(require('sinon-chai')); + +expect = Chai.expect; + +MockConnection = require('../../../mock/connection'); + +Protocol = require('../../../../src/adb/protocol'); + +StartActivityCommand = require('../../../../src/adb/command/host-transport/startactivity'); + +describe('StartActivityCommand', function() { + it("should succeed when 'Success' returned", function(done) { + var cmd, conn, options; + conn = new MockConnection; + cmd = new StartActivityCommand(conn); + setImmediate(function() { + conn.socket.causeRead(Protocol.OKAY); + conn.socket.causeRead('Success'); + return conn.socket.causeEnd(); + }); + options = { + component: 'com.dummy.component/.Main' + }; + return cmd.execute(options).then(function() { + return done(); + }); + }); + it("should fail when 'Error' returned", function(done) { + var cmd, conn, options; + conn = new MockConnection; + cmd = new StartActivityCommand(conn); + setImmediate(function() { + conn.socket.causeRead(Protocol.OKAY); + conn.socket.causeRead('Error: foo\n'); + return conn.socket.causeEnd(); + }); + options = { + component: 'com.dummy.component/.Main' + }; + return cmd.execute(options)["catch"](function(err) { + expect(err).to.be.be.an.instanceOf(Error); + return done(); + }); + }); + it("should send 'am start -n '", function(done) { + var cmd, conn, options; + conn = new MockConnection; + cmd = new StartActivityCommand(conn); + conn.socket.on('write', function(chunk) { + return expect(chunk.toString()).to.equal(Protocol.encodeData("shell:am start -n 'com.dummy.component/.Main'").toString()); + }); + setImmediate(function() { + conn.socket.causeRead(Protocol.OKAY); + conn.socket.causeRead('Success\n'); + return conn.socket.causeEnd(); + }); + options = { + component: 'com.dummy.component/.Main' + }; + return cmd.execute(options).then(function() { + return done(); + }); + }); + it("should send 'am start -W -D --user 0 -n '", function(done) { + var cmd, conn, options; + conn = new MockConnection; + cmd = new StartActivityCommand(conn); + conn.socket.on('write', function(chunk) { + return expect(chunk.toString()).to.equal(Protocol.encodeData("shell:am start -n 'com.dummy.component/.Main' -D -W --user 0").toString()); + }); + setImmediate(function() { + conn.socket.causeRead(Protocol.OKAY); + conn.socket.causeRead('Success\n'); + return conn.socket.causeEnd(); + }); + options = { + component: 'com.dummy.component/.Main', + user: 0, + wait: true, + debug: true + }; + return cmd.execute(options).then(function() { + return done(); + }); + }); + it("should send 'am start -a '", function(done) { + var cmd, conn, options; + conn = new MockConnection; + cmd = new StartActivityCommand(conn); + conn.socket.on('write', function(chunk) { + return expect(chunk.toString()).to.equal(Protocol.encodeData("shell:am start -a 'foo.ACTION_BAR'").toString()); + }); + setImmediate(function() { + conn.socket.causeRead(Protocol.OKAY); + conn.socket.causeRead('Success\n'); + return conn.socket.causeEnd(); + }); + options = { + action: "foo.ACTION_BAR" + }; + return cmd.execute(options).then(function() { + return done(); + }); + }); + it("should send 'am start -d '", function(done) { + var cmd, conn, options; + conn = new MockConnection; + cmd = new StartActivityCommand(conn); + conn.socket.on('write', function(chunk) { + return expect(chunk.toString()).to.equal(Protocol.encodeData("shell:am start -d 'foo://bar'").toString()); + }); + setImmediate(function() { + conn.socket.causeRead(Protocol.OKAY); + conn.socket.causeRead('Success\n'); + return conn.socket.causeEnd(); + }); + options = { + data: "foo://bar" + }; + return cmd.execute(options).then(function() { + return done(); + }); + }); + it("should send 'am start -t '", function(done) { + var cmd, conn, options; + conn = new MockConnection; + cmd = new StartActivityCommand(conn); + conn.socket.on('write', function(chunk) { + return expect(chunk.toString()).to.equal(Protocol.encodeData("shell:am start -t 'text/plain'").toString()); + }); + setImmediate(function() { + conn.socket.causeRead(Protocol.OKAY); + conn.socket.causeRead('Success\n'); + return conn.socket.causeEnd(); + }); + options = { + mimeType: "text/plain" + }; + return cmd.execute(options).then(function() { + return done(); + }); + }); + it("should send 'am start -c '", function(done) { + var cmd, conn, options; + conn = new MockConnection; + cmd = new StartActivityCommand(conn); + conn.socket.on('write', function(chunk) { + return expect(chunk.toString()).to.equal(Protocol.encodeData("shell:am start -c 'android.intent.category.LAUNCHER'").toString()); + }); + setImmediate(function() { + conn.socket.causeRead(Protocol.OKAY); + conn.socket.causeRead('Success\n'); + return conn.socket.causeEnd(); + }); + options = { + category: "android.intent.category.LAUNCHER" + }; + return cmd.execute(options).then(function() { + return done(); + }); + }); + it("should send 'am start -c -c '", function(done) { + var cmd, conn, options; + conn = new MockConnection; + cmd = new StartActivityCommand(conn); + conn.socket.on('write', function(chunk) { + return expect(chunk.toString()).to.equal(Protocol.encodeData("shell:am start -c 'android.intent.category.LAUNCHER' -c 'android.intent.category.DEFAULT'").toString()); + }); + setImmediate(function() { + conn.socket.causeRead(Protocol.OKAY); + conn.socket.causeRead('Success\n'); + return conn.socket.causeEnd(); + }); + options = { + category: ["android.intent.category.LAUNCHER", "android.intent.category.DEFAULT"] + }; + return cmd.execute(options).then(function() { + return done(); + }); + }); + it("should send 'am start -f '", function(done) { + var cmd, conn, options; + conn = new MockConnection; + cmd = new StartActivityCommand(conn); + conn.socket.on('write', function(chunk) { + return expect(chunk.toString()).to.equal(Protocol.encodeData("shell:am start -f " + 0x10210000).toString()); + }); + setImmediate(function() { + conn.socket.causeRead(Protocol.OKAY); + conn.socket.causeRead('Success\n'); + return conn.socket.causeEnd(); + }); + options = { + flags: 0x10210000 + }; + return cmd.execute(options).then(function() { + return done(); + }); + }); + it("should send 'am start -n --es '", function(done) { + var cmd, conn, options; + conn = new MockConnection; + cmd = new StartActivityCommand(conn); + conn.socket.on('write', function(chunk) { + return expect(chunk.toString()).to.equal(Protocol.encodeData("shell:am start --es 'key1' 'value1' --es 'key2' 'value2' -n 'com.dummy.component/.Main'").toString()); + }); + setImmediate(function() { + conn.socket.causeRead(Protocol.OKAY); + conn.socket.causeRead('Success\n'); + return conn.socket.causeEnd(); + }); + options = { + component: "com.dummy.component/.Main", + extras: [ + { + key: 'key1', + value: 'value1' + }, { + key: 'key2', + value: 'value2' + } + ] + }; + return cmd.execute(options).then(function() { + return done(); + }); + }); + it("should send 'am start -n --ei '", function(done) { + var cmd, conn, options; + conn = new MockConnection; + cmd = new StartActivityCommand(conn); + conn.socket.on('write', function(chunk) { + return expect(chunk.toString()).to.equal(Protocol.encodeData("shell:am start --ei 'key1' 1 --ei 'key2' 2 -n 'com.dummy.component/.Main'").toString()); + }); + setImmediate(function() { + conn.socket.causeRead(Protocol.OKAY); + conn.socket.causeRead('Success\n'); + return conn.socket.causeEnd(); + }); + options = { + component: 'com.dummy.component/.Main', + extras: [ + { + key: 'key1', + value: 1, + type: 'int' + }, { + key: 'key2', + value: 2, + type: 'int' + } + ] + }; + return cmd.execute(options).then(function() { + return done(); + }); + }); + it("should send 'am start -n --ez '", function(done) { + var cmd, conn, options; + conn = new MockConnection; + cmd = new StartActivityCommand(conn); + conn.socket.on('write', function(chunk) { + return expect(chunk.toString()).to.equal(Protocol.encodeData("shell:am start --ez 'key1' 'true' --ez 'key2' 'false' -n 'com.dummy.component/.Main'").toString()); + }); + setImmediate(function() { + conn.socket.causeRead(Protocol.OKAY); + conn.socket.causeRead('Success'); + return conn.socket.causeEnd(); + }); + options = { + component: "com.dummy.component/.Main", + extras: [ + { + key: 'key1', + value: true, + type: 'bool' + }, { + key: 'key2', + value: false, + type: 'bool' + } + ] + }; + return cmd.execute(options).then(function() { + return done(); + }); + }); + it("should send 'am start -n --el '", function(done) { + var cmd, conn, options; + conn = new MockConnection; + cmd = new StartActivityCommand(conn); + conn.socket.on('write', function(chunk) { + return expect(chunk.toString()).to.equal(Protocol.encodeData("shell:am start --el 'key1' 1 --el 'key2' '2' -n 'com.dummy.component/.Main'").toString()); + }); + setImmediate(function() { + conn.socket.causeRead(Protocol.OKAY); + conn.socket.causeRead('Success'); + return conn.socket.causeEnd(); + }); + options = { + component: 'com.dummy.component/.Main', + extras: [ + { + key: 'key1', + value: 1, + type: 'long' + }, { + key: 'key2', + value: '2', + type: 'long' + } + ] + }; + return cmd.execute(options).then(function() { + return done(); + }); + }); + it("should send 'am start -n --eu '", function(done) { + var cmd, conn, options; + conn = new MockConnection; + cmd = new StartActivityCommand(conn); + conn.socket.on('write', function(chunk) { + return expect(chunk.toString()).to.equal(Protocol.encodeData("shell:am start --eu 'key1' 'http://example.org' --eu 'key2' 'http://example.org' -n 'com.dummy.component/.Main'").toString()); + }); + setImmediate(function() { + conn.socket.causeRead(Protocol.OKAY); + conn.socket.causeRead('Success'); + return conn.socket.causeEnd(); + }); + options = { + component: 'com.dummy.component/.Main', + extras: [ + { + key: 'key1', + value: 'http://example.org', + type: 'uri' + }, { + key: 'key2', + value: 'http://example.org', + type: 'uri' + } + ] + }; + return cmd.execute(options).then(function() { + return done(); + }); + }); + it("should send 'am start -n --es '", function(done) { + var cmd, conn, options; + conn = new MockConnection; + cmd = new StartActivityCommand(conn); + conn.socket.on('write', function(chunk) { + return expect(chunk.toString()).to.equal(Protocol.encodeData("shell:am start --es 'key1' 'a' --es 'key2' 'b' -n 'com.dummy.component/.Main'").toString()); + }); + setImmediate(function() { + conn.socket.causeRead(Protocol.OKAY); + conn.socket.causeRead('Success'); + return conn.socket.causeEnd(); + }); + options = { + component: 'com.dummy.component/.Main', + extras: [ + { + key: 'key1', + value: 'a', + type: 'string' + }, { + key: 'key2', + value: 'b', + type: 'string' + } + ] + }; + return cmd.execute(options).then(function() { + return done(); + }); + }); + it("should send 'am start -n --eia '", function(done) { + var cmd, conn, options; + conn = new MockConnection; + cmd = new StartActivityCommand(conn); + conn.socket.on('write', function(chunk) { + return expect(chunk.toString()).to.equal(Protocol.encodeData("shell:am start --eia 'key1' '2,3' --ela 'key2' '20,30' --ei 'key3' 5 -n 'com.dummy.component/.Main'").toString()); + }); + setImmediate(function() { + conn.socket.causeRead(Protocol.OKAY); + conn.socket.causeRead('Success'); + return conn.socket.causeEnd(); + }); + options = { + component: 'com.dummy.component/.Main', + extras: [ + { + key: 'key1', + value: [2, 3], + type: 'int' + }, { + key: 'key2', + value: [20, 30], + type: 'long' + }, { + key: 'key3', + value: 5, + type: 'int' + } + ] + }; + return cmd.execute(options).then(function() { + return done(); + }); + }); + it("should send 'am start -n --esn '", function(done) { + var cmd, conn, options; + conn = new MockConnection; + cmd = new StartActivityCommand(conn); + conn.socket.on('write', function(chunk) { + return expect(chunk.toString()).to.equal(Protocol.encodeData("shell:am start --esn 'key1' --esn 'key2' -n 'com.dummy.component/.Main'").toString()); + }); + setImmediate(function() { + conn.socket.causeRead(Protocol.OKAY); + conn.socket.causeRead('Success'); + return conn.socket.causeEnd(); + }); + options = { + component: 'com.dummy.component/.Main', + extras: [ + { + key: 'key1', + type: 'null' + }, { + key: 'key2', + type: 'null' + } + ] + }; + return cmd.execute(options).then(function() { + return done(); + }); + }); + it("should throw when calling with an unknown extra type", function(done) { + var cmd, conn, options; + conn = new MockConnection; + cmd = new StartActivityCommand(conn); + options = { + component: 'com.dummy.component/.Main', + extras: [ + { + key: 'key1', + value: 'value1', + type: 'nonexisting' + } + ] + }; + expect(function() { + return cmd.execute(options, function() {}); + }).to["throw"]; + return done(); + }); + it("should accept mixed types of extras", function(done) { + var cmd, conn, options; + conn = new MockConnection; + cmd = new StartActivityCommand(conn); + conn.socket.on('write', function(chunk) { + return expect(chunk.toString()).to.equal(Protocol.encodeData("shell:am start --ez 'key1' 'true' --es 'key2' 'somestr' --es 'key3' 'defaultType' --ei 'key4' 3 --el 'key5' '4' --eu 'key6' 'http://example.org' --esn 'key7' -n 'com.dummy.component/.Main'").toString()); + }); + setImmediate(function() { + conn.socket.causeRead(Protocol.OKAY); + conn.socket.causeRead('Success'); + return conn.socket.causeEnd(); + }); + options = { + component: 'com.dummy.component/.Main', + extras: [ + { + key: 'key1', + value: true, + type: 'bool' + }, { + key: 'key2', + value: 'somestr', + type: 'string' + }, { + key: 'key3', + value: 'defaultType' + }, { + key: 'key4', + value: 3, + type: 'int' + }, { + key: 'key5', + value: '4', + type: 'long' + }, { + key: 'key6', + value: 'http://example.org', + type: 'uri' + }, { + key: 'key7', + type: 'null' + } + ] + }; + return cmd.execute(options).then(function() { + return done(); + }); + }); + return it("should map short extras to long extras", function(done) { + var cmd, conn, long, short; + conn = new MockConnection; + cmd = new StartActivityCommand(conn); + short = cmd._formatExtras({ + someString: 'bar', + someInt: 5, + someUrl: { + type: 'uri', + value: 'http://example.org' + }, + someArray: { + type: 'int', + value: [1, 2] + }, + someNull: null + }); + long = cmd._formatExtras([ + { + key: 'someString', + value: 'bar', + type: 'string' + }, { + key: 'someInt', + value: 5, + type: 'int' + }, { + key: 'someUrl', + value: 'http://example.org', + type: 'uri' + }, { + key: 'someArray', + value: [1, 2], + type: 'int' + }, { + key: 'someNull', + type: 'null' + } + ]); + expect(short).to.eql(long); + return done(); + }); +}); diff --git a/test/adb/command/host-transport/startservice.coffee b/test/adb/command/host-transport/startservice.coffee deleted file mode 100644 index 386d577a..00000000 --- a/test/adb/command/host-transport/startservice.coffee +++ /dev/null @@ -1,75 +0,0 @@ -Stream = require 'stream' -Sinon = require 'sinon' -Chai = require 'chai' -Chai.use require 'sinon-chai' -{expect} = Chai - -MockConnection = require '../../../mock/connection' -Protocol = require '../../../../src/adb/protocol' -StartServiceCommand = require \ - '../../../../src/adb/command/host-transport/startservice' - -describe 'StartServiceCommand', -> - - it "should succeed when 'Success' returned", (done) -> - conn = new MockConnection - cmd = new StartServiceCommand conn - setImmediate -> - conn.socket.causeRead Protocol.OKAY - conn.socket.causeRead 'Success' - conn.socket.causeEnd() - options = - component: 'com.dummy.component/.Main' - cmd.execute options - .then -> - done() - - it "should fail when 'Error' returned", (done) -> - conn = new MockConnection - cmd = new StartServiceCommand conn - setImmediate -> - conn.socket.causeRead Protocol.OKAY - conn.socket.causeRead 'Error: foo\n' - conn.socket.causeEnd() - options = - component: 'com.dummy.component/.Main' - cmd.execute options - .catch (err) -> - expect(err).to.be.be.an.instanceOf Error - done() - - it "should send 'am startservice --user 0 -n '", (done) -> - conn = new MockConnection - cmd = new StartServiceCommand conn - conn.socket.on 'write', (chunk) -> - expect(chunk.toString()).to.equal \ - Protocol.encodeData("shell:am startservice - -n 'com.dummy.component/.Main' - --user 0").toString() - setImmediate -> - conn.socket.causeRead Protocol.OKAY - conn.socket.causeRead 'Success\n' - conn.socket.causeEnd() - options = - component: 'com.dummy.component/.Main' - user: 0 - cmd.execute options - .then -> - done() - - it "should not send user option if not set'", (done) -> - conn = new MockConnection - cmd = new StartServiceCommand conn - conn.socket.on 'write', (chunk) -> - expect(chunk.toString()).to.equal \ - Protocol.encodeData("shell:am startservice - -n 'com.dummy.component/.Main'").toString() - setImmediate -> - conn.socket.causeRead Protocol.OKAY - conn.socket.causeRead 'Success\n' - conn.socket.causeEnd() - options = - component: 'com.dummy.component/.Main' - cmd.execute options - .then -> - done() diff --git a/test/adb/command/host-transport/startservice.js b/test/adb/command/host-transport/startservice.js new file mode 100644 index 00000000..476ad421 --- /dev/null +++ b/test/adb/command/host-transport/startservice.js @@ -0,0 +1,92 @@ +var Chai, MockConnection, Protocol, Sinon, StartServiceCommand, Stream, expect; + +Stream = require('stream'); + +Sinon = require('sinon'); + +Chai = require('chai'); + +Chai.use(require('sinon-chai')); + +expect = Chai.expect; + +MockConnection = require('../../../mock/connection'); + +Protocol = require('../../../../src/adb/protocol'); + +StartServiceCommand = require('../../../../src/adb/command/host-transport/startservice'); + +describe('StartServiceCommand', function() { + it("should succeed when 'Success' returned", function(done) { + var cmd, conn, options; + conn = new MockConnection; + cmd = new StartServiceCommand(conn); + setImmediate(function() { + conn.socket.causeRead(Protocol.OKAY); + conn.socket.causeRead('Success'); + return conn.socket.causeEnd(); + }); + options = { + component: 'com.dummy.component/.Main' + }; + return cmd.execute(options).then(function() { + return done(); + }); + }); + it("should fail when 'Error' returned", function(done) { + var cmd, conn, options; + conn = new MockConnection; + cmd = new StartServiceCommand(conn); + setImmediate(function() { + conn.socket.causeRead(Protocol.OKAY); + conn.socket.causeRead('Error: foo\n'); + return conn.socket.causeEnd(); + }); + options = { + component: 'com.dummy.component/.Main' + }; + return cmd.execute(options)["catch"](function(err) { + expect(err).to.be.be.an.instanceOf(Error); + return done(); + }); + }); + it("should send 'am startservice --user 0 -n '", function(done) { + var cmd, conn, options; + conn = new MockConnection; + cmd = new StartServiceCommand(conn); + conn.socket.on('write', function(chunk) { + return expect(chunk.toString()).to.equal(Protocol.encodeData("shell:am startservice -n 'com.dummy.component/.Main' --user 0").toString()); + }); + setImmediate(function() { + conn.socket.causeRead(Protocol.OKAY); + conn.socket.causeRead('Success\n'); + return conn.socket.causeEnd(); + }); + options = { + component: 'com.dummy.component/.Main', + user: 0 + }; + return cmd.execute(options).then(function() { + return done(); + }); + }); + return it("should not send user option if not set'", function(done) { + var cmd, conn, options; + conn = new MockConnection; + cmd = new StartServiceCommand(conn); + conn.socket.on('write', function(chunk) { + return expect(chunk.toString()).to.equal(Protocol.encodeData("shell:am startservice -n 'com.dummy.component/.Main'").toString()); + }); + setImmediate(function() { + conn.socket.causeRead(Protocol.OKAY); + conn.socket.causeRead('Success\n'); + return conn.socket.causeEnd(); + }); + options = { + component: 'com.dummy.component/.Main' + }; + return cmd.execute(options).then(function() { + return done(); + }); + }); +}); diff --git a/test/adb/command/host-transport/sync.coffee b/test/adb/command/host-transport/sync.coffee deleted file mode 100644 index ba736820..00000000 --- a/test/adb/command/host-transport/sync.coffee +++ /dev/null @@ -1,22 +0,0 @@ -Sinon = require 'sinon' -Chai = require 'chai' -Chai.use require 'sinon-chai' -{expect} = Chai - -MockConnection = require '../../../mock/connection' -Protocol = require '../../../../src/adb/protocol' -SyncCommand = require '../../../../src/adb/command/host-transport/sync' - -describe 'SyncCommand', -> - - it "should send 'sync:'", (done) -> - conn = new MockConnection - cmd = new SyncCommand conn - conn.socket.on 'write', (chunk) -> - expect(chunk.toString()).to.equal \ - Protocol.encodeData('sync:').toString() - conn.socket.causeRead Protocol.OKAY - conn.socket.causeEnd() - cmd.execute() - .then -> - done() diff --git a/test/adb/command/host-transport/sync.js b/test/adb/command/host-transport/sync.js new file mode 100644 index 00000000..f15e314f --- /dev/null +++ b/test/adb/command/host-transport/sync.js @@ -0,0 +1,31 @@ +var Chai, MockConnection, Protocol, Sinon, SyncCommand, expect; + +Sinon = require('sinon'); + +Chai = require('chai'); + +Chai.use(require('sinon-chai')); + +expect = Chai.expect; + +MockConnection = require('../../../mock/connection'); + +Protocol = require('../../../../src/adb/protocol'); + +SyncCommand = require('../../../../src/adb/command/host-transport/sync'); + +describe('SyncCommand', function() { + return it("should send 'sync:'", function(done) { + var cmd, conn; + conn = new MockConnection; + cmd = new SyncCommand(conn); + conn.socket.on('write', function(chunk) { + expect(chunk.toString()).to.equal(Protocol.encodeData('sync:').toString()); + conn.socket.causeRead(Protocol.OKAY); + return conn.socket.causeEnd(); + }); + return cmd.execute().then(function() { + return done(); + }); + }); +}); diff --git a/test/adb/command/host-transport/tcp.coffee b/test/adb/command/host-transport/tcp.coffee deleted file mode 100644 index 600b0e32..00000000 --- a/test/adb/command/host-transport/tcp.coffee +++ /dev/null @@ -1,48 +0,0 @@ -Stream = require 'stream' -Sinon = require 'sinon' -Chai = require 'chai' -Chai.use require 'sinon-chai' -{expect} = Chai - -MockConnection = require '../../../mock/connection' -Protocol = require '../../../../src/adb/protocol' -TcpCommand = require '../../../../src/adb/command/host-transport/tcp' - -describe 'TcpCommand', -> - - it "should send 'tcp:' when no host given", (done) -> - conn = new MockConnection - cmd = new TcpCommand conn - conn.socket.on 'write', (chunk) -> - expect(chunk.toString()).to.equal \ - Protocol.encodeData('tcp:8080').toString() - setImmediate -> - conn.socket.causeRead Protocol.OKAY - conn.socket.causeEnd() - cmd.execute 8080 - .then (stream) -> - done() - - it "should send 'tcp::' when host given", (done) -> - conn = new MockConnection - cmd = new TcpCommand conn - conn.socket.on 'write', (chunk) -> - expect(chunk.toString()).to.equal \ - Protocol.encodeData('tcp:8080:127.0.0.1').toString() - setImmediate -> - conn.socket.causeRead Protocol.OKAY - conn.socket.causeEnd() - cmd.execute 8080, '127.0.0.1' - .then (stream) -> - done() - - it "should resolve with the tcp stream", (done) -> - conn = new MockConnection - cmd = new TcpCommand conn - setImmediate -> - conn.socket.causeRead Protocol.OKAY - cmd.execute 8080 - .then (stream) -> - stream.end() - expect(stream).to.be.an.instanceof Stream.Readable - done() diff --git a/test/adb/command/host-transport/tcp.js b/test/adb/command/host-transport/tcp.js new file mode 100644 index 00000000..5c82df1d --- /dev/null +++ b/test/adb/command/host-transport/tcp.js @@ -0,0 +1,63 @@ +var Chai, MockConnection, Protocol, Sinon, Stream, TcpCommand, expect; + +Stream = require('stream'); + +Sinon = require('sinon'); + +Chai = require('chai'); + +Chai.use(require('sinon-chai')); + +expect = Chai.expect; + +MockConnection = require('../../../mock/connection'); + +Protocol = require('../../../../src/adb/protocol'); + +TcpCommand = require('../../../../src/adb/command/host-transport/tcp'); + +describe('TcpCommand', function() { + it("should send 'tcp:' when no host given", function(done) { + var cmd, conn; + conn = new MockConnection; + cmd = new TcpCommand(conn); + conn.socket.on('write', function(chunk) { + return expect(chunk.toString()).to.equal(Protocol.encodeData('tcp:8080').toString()); + }); + setImmediate(function() { + conn.socket.causeRead(Protocol.OKAY); + return conn.socket.causeEnd(); + }); + return cmd.execute(8080).then(function(stream) { + return done(); + }); + }); + it("should send 'tcp::' when host given", function(done) { + var cmd, conn; + conn = new MockConnection; + cmd = new TcpCommand(conn); + conn.socket.on('write', function(chunk) { + return expect(chunk.toString()).to.equal(Protocol.encodeData('tcp:8080:127.0.0.1').toString()); + }); + setImmediate(function() { + conn.socket.causeRead(Protocol.OKAY); + return conn.socket.causeEnd(); + }); + return cmd.execute(8080, '127.0.0.1').then(function(stream) { + return done(); + }); + }); + return it("should resolve with the tcp stream", function(done) { + var cmd, conn; + conn = new MockConnection; + cmd = new TcpCommand(conn); + setImmediate(function() { + return conn.socket.causeRead(Protocol.OKAY); + }); + return cmd.execute(8080).then(function(stream) { + stream.end(); + expect(stream).to.be.an["instanceof"](Stream.Readable); + return done(); + }); + }); +}); diff --git a/test/adb/command/host-transport/tcpip.coffee b/test/adb/command/host-transport/tcpip.coffee deleted file mode 100644 index 601fc7fe..00000000 --- a/test/adb/command/host-transport/tcpip.coffee +++ /dev/null @@ -1,49 +0,0 @@ -Stream = require 'stream' -Sinon = require 'sinon' -Chai = require 'chai' -Chai.use require 'sinon-chai' -{expect} = Chai - -MockConnection = require '../../../mock/connection' -Protocol = require '../../../../src/adb/protocol' -TcpIpCommand = require '../../../../src/adb/command/host-transport/tcpip' - -describe 'TcpIpCommand', -> - - it "should send 'tcp:'", (done) -> - conn = new MockConnection - cmd = new TcpIpCommand conn - conn.socket.on 'write', (chunk) -> - expect(chunk.toString()).to.equal \ - Protocol.encodeData('tcpip:5555').toString() - setImmediate -> - conn.socket.causeRead Protocol.OKAY - conn.socket.causeRead "restarting in TCP mode port: 5555\n" - conn.socket.causeEnd() - cmd.execute 5555 - .then -> - done() - - it "should resolve with the port", (done) -> - conn = new MockConnection - cmd = new TcpIpCommand conn - setImmediate -> - conn.socket.causeRead Protocol.OKAY - conn.socket.causeRead "restarting in TCP mode port: 5555\n" - conn.socket.causeEnd() - cmd.execute 5555 - .then (port) -> - expect(port).to.equal 5555 - done() - - it "should reject on unexpected reply", (done) -> - conn = new MockConnection - cmd = new TcpIpCommand conn - setImmediate -> - conn.socket.causeRead Protocol.OKAY - conn.socket.causeRead "not sure what this could be\n" - conn.socket.causeEnd() - cmd.execute 5555 - .catch (err) -> - expect(err.message).to.eql 'not sure what this could be' - done() diff --git a/test/adb/command/host-transport/tcpip.js b/test/adb/command/host-transport/tcpip.js new file mode 100644 index 00000000..6e637763 --- /dev/null +++ b/test/adb/command/host-transport/tcpip.js @@ -0,0 +1,64 @@ +var Chai, MockConnection, Protocol, Sinon, Stream, TcpIpCommand, expect; + +Stream = require('stream'); + +Sinon = require('sinon'); + +Chai = require('chai'); + +Chai.use(require('sinon-chai')); + +expect = Chai.expect; + +MockConnection = require('../../../mock/connection'); + +Protocol = require('../../../../src/adb/protocol'); + +TcpIpCommand = require('../../../../src/adb/command/host-transport/tcpip'); + +describe('TcpIpCommand', function() { + it("should send 'tcp:'", function(done) { + var cmd, conn; + conn = new MockConnection; + cmd = new TcpIpCommand(conn); + conn.socket.on('write', function(chunk) { + return expect(chunk.toString()).to.equal(Protocol.encodeData('tcpip:5555').toString()); + }); + setImmediate(function() { + conn.socket.causeRead(Protocol.OKAY); + conn.socket.causeRead("restarting in TCP mode port: 5555\n"); + return conn.socket.causeEnd(); + }); + return cmd.execute(5555).then(function() { + return done(); + }); + }); + it("should resolve with the port", function(done) { + var cmd, conn; + conn = new MockConnection; + cmd = new TcpIpCommand(conn); + setImmediate(function() { + conn.socket.causeRead(Protocol.OKAY); + conn.socket.causeRead("restarting in TCP mode port: 5555\n"); + return conn.socket.causeEnd(); + }); + return cmd.execute(5555).then(function(port) { + expect(port).to.equal(5555); + return done(); + }); + }); + return it("should reject on unexpected reply", function(done) { + var cmd, conn; + conn = new MockConnection; + cmd = new TcpIpCommand(conn); + setImmediate(function() { + conn.socket.causeRead(Protocol.OKAY); + conn.socket.causeRead("not sure what this could be\n"); + return conn.socket.causeEnd(); + }); + return cmd.execute(5555)["catch"](function(err) { + expect(err.message).to.eql('not sure what this could be'); + return done(); + }); + }); +}); diff --git a/test/adb/command/host-transport/uninstall.coffee b/test/adb/command/host-transport/uninstall.coffee deleted file mode 100644 index 2a2c8035..00000000 --- a/test/adb/command/host-transport/uninstall.coffee +++ /dev/null @@ -1,125 +0,0 @@ -Stream = require 'stream' -Sinon = require 'sinon' -Chai = require 'chai' -Chai.use require 'sinon-chai' -{expect} = Chai - -MockConnection = require '../../../mock/connection' -Protocol = require '../../../../src/adb/protocol' -Parser = require '../../../../src/adb/parser' -UninstallCommand = - require '../../../../src/adb/command/host-transport/uninstall' - -describe 'UninstallCommand', -> - - it "should succeed when command responds with 'Success'", (done) -> - conn = new MockConnection - cmd = new UninstallCommand conn - conn.socket.on 'write', (chunk) -> - expect(chunk.toString()).to.equal \ - Protocol.encodeData('shell:pm uninstall foo').toString() - setImmediate -> - conn.socket.causeRead Protocol.OKAY - conn.socket.causeRead 'Success\r\n' - conn.socket.causeEnd() - cmd.execute 'foo' - .then -> - done() - - it "should succeed even if command responds with 'Failure'", (done) -> - conn = new MockConnection - cmd = new UninstallCommand conn - conn.socket.on 'write', (chunk) -> - expect(chunk.toString()).to.equal \ - Protocol.encodeData('shell:pm uninstall foo').toString() - setImmediate -> - conn.socket.causeRead Protocol.OKAY - conn.socket.causeRead 'Failure\r\n' - conn.socket.causeEnd() - cmd.execute 'foo' - .then -> - done() - - it "should succeed even if command responds with 'Failure' - with info in standard format", (done) -> - conn = new MockConnection - cmd = new UninstallCommand conn - conn.socket.on 'write', (chunk) -> - expect(chunk.toString()).to.equal \ - Protocol.encodeData('shell:pm uninstall foo').toString() - setImmediate -> - conn.socket.causeRead Protocol.OKAY - conn.socket.causeRead 'Failure [DELETE_FAILED_INTERNAL_ERROR]\r\n' - conn.socket.causeEnd() - cmd.execute 'foo' - .then -> - done() - - it "should succeed even if command responds with 'Failure' - with info info in weird format", (done) -> - conn = new MockConnection - cmd = new UninstallCommand conn - setImmediate -> - conn.socket.causeRead Protocol.OKAY - conn.socket.causeRead 'Failure - not installed for 0\r\n' - conn.socket.causeEnd() - cmd.execute 'foo' - .then -> - done() - - it "should succeed even if command responds with a buggy exception", (done) -> - conn = new MockConnection - cmd = new UninstallCommand conn - setImmediate -> - conn.socket.causeRead Protocol.OKAY - # coffeelint: disable=max_line_length - conn.socket.causeRead """ - - Exception occurred while dumping: - java.lang.IllegalArgumentException: Unknown package: foo - at com.android.server.pm.Settings.isOrphaned(Settings.java:4134) - at com.android.server.pm.PackageManagerService.isOrphaned(PackageManagerService.java:18066) - at com.android.server.pm.PackageManagerService.deletePackage(PackageManagerService.java:15483) - at com.android.server.pm.PackageInstallerService.uninstall(PackageInstallerService.java:888) - at com.android.server.pm.PackageManagerShellCommand.runUninstall(PackageManagerShellCommand.java:765) - at com.android.server.pm.PackageManagerShellCommand.onCommand(PackageManagerShellCommand.java:113) - at android.os.ShellCommand.exec(ShellCommand.java:94) - at com.android.server.pm.PackageManagerService.onShellCommand(PackageManagerService.java:18324) - at android.os.Binder.shellCommand(Binder.java:468) - at android.os.Binder.onTransact(Binder.java:367) - at android.content.pm.IPackageManager$Stub.onTransact(IPackageManager.java:2387) - at com.android.server.pm.PackageManagerService.onTransact(PackageManagerService.java:3019) - at android.os.Binder.execTransact(Binder.java:565) - """ - # coffeelint: enable=max_line_length - conn.socket.causeEnd() - cmd.execute 'foo' - .then -> - done() - - it "should reject with Parser.PrematureEOFError if stream ends - before match", (done) -> - conn = new MockConnection - cmd = new UninstallCommand conn - setImmediate -> - conn.socket.causeRead Protocol.OKAY - conn.socket.causeRead 'Hello. Is it me you are looking for?\r\n' - conn.socket.causeEnd() - cmd.execute 'foo' - .catch Parser.PrematureEOFError, (err) -> - done() - - it "should ignore any other data", (done) -> - conn = new MockConnection - cmd = new UninstallCommand conn - conn.socket.on 'write', (chunk) -> - expect(chunk.toString()).to.equal \ - Protocol.encodeData('shell:pm uninstall foo').toString() - setImmediate -> - conn.socket.causeRead Protocol.OKAY - conn.socket.causeRead 'open: Permission failed\r\n' - conn.socket.causeRead 'Failure\r\n' - conn.socket.causeEnd() - cmd.execute 'foo' - .then -> - done() diff --git a/test/adb/command/host-transport/uninstall.js b/test/adb/command/host-transport/uninstall.js new file mode 100644 index 00000000..5b341fe3 --- /dev/null +++ b/test/adb/command/host-transport/uninstall.js @@ -0,0 +1,126 @@ +var Chai, MockConnection, Parser, Protocol, Sinon, Stream, UninstallCommand, expect; + +Stream = require('stream'); + +Sinon = require('sinon'); + +Chai = require('chai'); + +Chai.use(require('sinon-chai')); + +expect = Chai.expect; + +MockConnection = require('../../../mock/connection'); + +Protocol = require('../../../../src/adb/protocol'); + +Parser = require('../../../../src/adb/parser'); + +UninstallCommand = require('../../../../src/adb/command/host-transport/uninstall'); + +describe('UninstallCommand', function() { + it("should succeed when command responds with 'Success'", function(done) { + var cmd, conn; + conn = new MockConnection; + cmd = new UninstallCommand(conn); + conn.socket.on('write', function(chunk) { + return expect(chunk.toString()).to.equal(Protocol.encodeData('shell:pm uninstall foo').toString()); + }); + setImmediate(function() { + conn.socket.causeRead(Protocol.OKAY); + conn.socket.causeRead('Success\r\n'); + return conn.socket.causeEnd(); + }); + return cmd.execute('foo').then(function() { + return done(); + }); + }); + it("should succeed even if command responds with 'Failure'", function(done) { + var cmd, conn; + conn = new MockConnection; + cmd = new UninstallCommand(conn); + conn.socket.on('write', function(chunk) { + return expect(chunk.toString()).to.equal(Protocol.encodeData('shell:pm uninstall foo').toString()); + }); + setImmediate(function() { + conn.socket.causeRead(Protocol.OKAY); + conn.socket.causeRead('Failure\r\n'); + return conn.socket.causeEnd(); + }); + return cmd.execute('foo').then(function() { + return done(); + }); + }); + it("should succeed even if command responds with 'Failure' with info in standard format", function(done) { + var cmd, conn; + conn = new MockConnection; + cmd = new UninstallCommand(conn); + conn.socket.on('write', function(chunk) { + return expect(chunk.toString()).to.equal(Protocol.encodeData('shell:pm uninstall foo').toString()); + }); + setImmediate(function() { + conn.socket.causeRead(Protocol.OKAY); + conn.socket.causeRead('Failure [DELETE_FAILED_INTERNAL_ERROR]\r\n'); + return conn.socket.causeEnd(); + }); + return cmd.execute('foo').then(function() { + return done(); + }); + }); + it("should succeed even if command responds with 'Failure' with info info in weird format", function(done) { + var cmd, conn; + conn = new MockConnection; + cmd = new UninstallCommand(conn); + setImmediate(function() { + conn.socket.causeRead(Protocol.OKAY); + conn.socket.causeRead('Failure - not installed for 0\r\n'); + return conn.socket.causeEnd(); + }); + return cmd.execute('foo').then(function() { + return done(); + }); + }); + it("should succeed even if command responds with a buggy exception", function(done) { + var cmd, conn; + conn = new MockConnection; + cmd = new UninstallCommand(conn); + setImmediate(function() { + conn.socket.causeRead(Protocol.OKAY); + conn.socket.causeRead("\nException occurred while dumping:\njava.lang.IllegalArgumentException: Unknown package: foo\n at com.android.server.pm.Settings.isOrphaned(Settings.java:4134)\n at com.android.server.pm.PackageManagerService.isOrphaned(PackageManagerService.java:18066)\n at com.android.server.pm.PackageManagerService.deletePackage(PackageManagerService.java:15483)\n at com.android.server.pm.PackageInstallerService.uninstall(PackageInstallerService.java:888)\n at com.android.server.pm.PackageManagerShellCommand.runUninstall(PackageManagerShellCommand.java:765)\n at com.android.server.pm.PackageManagerShellCommand.onCommand(PackageManagerShellCommand.java:113)\n at android.os.ShellCommand.exec(ShellCommand.java:94)\n at com.android.server.pm.PackageManagerService.onShellCommand(PackageManagerService.java:18324)\n at android.os.Binder.shellCommand(Binder.java:468)\n at android.os.Binder.onTransact(Binder.java:367)\n at android.content.pm.IPackageManager$Stub.onTransact(IPackageManager.java:2387)\n at com.android.server.pm.PackageManagerService.onTransact(PackageManagerService.java:3019)\n at android.os.Binder.execTransact(Binder.java:565)"); + return conn.socket.causeEnd(); + }); + return cmd.execute('foo').then(function() { + return done(); + }); + }); + it("should reject with Parser.PrematureEOFError if stream ends before match", function(done) { + var cmd, conn; + conn = new MockConnection; + cmd = new UninstallCommand(conn); + setImmediate(function() { + conn.socket.causeRead(Protocol.OKAY); + conn.socket.causeRead('Hello. Is it me you are looking for?\r\n'); + return conn.socket.causeEnd(); + }); + return cmd.execute('foo')["catch"](Parser.PrematureEOFError, function(err) { + return done(); + }); + }); + return it("should ignore any other data", function(done) { + var cmd, conn; + conn = new MockConnection; + cmd = new UninstallCommand(conn); + conn.socket.on('write', function(chunk) { + return expect(chunk.toString()).to.equal(Protocol.encodeData('shell:pm uninstall foo').toString()); + }); + setImmediate(function() { + conn.socket.causeRead(Protocol.OKAY); + conn.socket.causeRead('open: Permission failed\r\n'); + conn.socket.causeRead('Failure\r\n'); + return conn.socket.causeEnd(); + }); + return cmd.execute('foo').then(function() { + return done(); + }); + }); +}); diff --git a/test/adb/command/host-transport/usb.coffee b/test/adb/command/host-transport/usb.coffee deleted file mode 100644 index db6d2797..00000000 --- a/test/adb/command/host-transport/usb.coffee +++ /dev/null @@ -1,38 +0,0 @@ -Stream = require 'stream' -Sinon = require 'sinon' -Chai = require 'chai' -Chai.use require 'sinon-chai' -{expect} = Chai - -MockConnection = require '../../../mock/connection' -Protocol = require '../../../../src/adb/protocol' -UsbCommand = require '../../../../src/adb/command/host-transport/usb' - -describe 'UsbCommand', -> - - it "should send 'usb:'", (done) -> - conn = new MockConnection - cmd = new UsbCommand conn - conn.socket.on 'write', (chunk) -> - expect(chunk.toString()).to.equal \ - Protocol.encodeData('usb:').toString() - setImmediate -> - conn.socket.causeRead Protocol.OKAY - conn.socket.causeRead "restarting in USB mode\n" - conn.socket.causeEnd() - cmd.execute() - .then (val) -> - expect(val).to.be.true - done() - - it "should reject on unexpected reply", (done) -> - conn = new MockConnection - cmd = new UsbCommand conn - setImmediate -> - conn.socket.causeRead Protocol.OKAY - conn.socket.causeRead "invalid port\n" - conn.socket.causeEnd() - cmd.execute() - .catch (err) -> - expect(err.message).to.eql 'invalid port' - done() diff --git a/test/adb/command/host-transport/usb.js b/test/adb/command/host-transport/usb.js new file mode 100644 index 00000000..ff6902dc --- /dev/null +++ b/test/adb/command/host-transport/usb.js @@ -0,0 +1,51 @@ +var Chai, MockConnection, Protocol, Sinon, Stream, UsbCommand, expect; + +Stream = require('stream'); + +Sinon = require('sinon'); + +Chai = require('chai'); + +Chai.use(require('sinon-chai')); + +expect = Chai.expect; + +MockConnection = require('../../../mock/connection'); + +Protocol = require('../../../../src/adb/protocol'); + +UsbCommand = require('../../../../src/adb/command/host-transport/usb'); + +describe('UsbCommand', function() { + it("should send 'usb:'", function(done) { + var cmd, conn; + conn = new MockConnection; + cmd = new UsbCommand(conn); + conn.socket.on('write', function(chunk) { + return expect(chunk.toString()).to.equal(Protocol.encodeData('usb:').toString()); + }); + setImmediate(function() { + conn.socket.causeRead(Protocol.OKAY); + conn.socket.causeRead("restarting in USB mode\n"); + return conn.socket.causeEnd(); + }); + return cmd.execute().then(function(val) { + expect(val).to.be["true"]; + return done(); + }); + }); + return it("should reject on unexpected reply", function(done) { + var cmd, conn; + conn = new MockConnection; + cmd = new UsbCommand(conn); + setImmediate(function() { + conn.socket.causeRead(Protocol.OKAY); + conn.socket.causeRead("invalid port\n"); + return conn.socket.causeEnd(); + }); + return cmd.execute()["catch"](function(err) { + expect(err.message).to.eql('invalid port'); + return done(); + }); + }); +}); diff --git a/test/adb/command/host-transport/waitbootcomplete.coffee b/test/adb/command/host-transport/waitbootcomplete.coffee deleted file mode 100644 index 8dc3bca3..00000000 --- a/test/adb/command/host-transport/waitbootcomplete.coffee +++ /dev/null @@ -1,77 +0,0 @@ -Stream = require 'stream' -Sinon = require 'sinon' -Chai = require 'chai' -Chai.use require 'sinon-chai' -{expect} = Chai - -MockConnection = require '../../../mock/connection' -Protocol = require '../../../../src/adb/protocol' -Parser = require '../../../../src/adb/parser' -WaitBootCompleteCommand = - require '../../../../src/adb/command/host-transport/waitbootcomplete' - -describe 'WaitBootCompleteCommand', -> - - it "should send a while loop with boot check", (done) -> - conn = new MockConnection - cmd = new WaitBootCompleteCommand conn - want = - 'shell:while getprop sys.boot_completed 2>/dev/null; do sleep 1; done' - conn.socket.on 'write', (chunk) -> - expect(chunk.toString()).to.equal \ - Protocol.encodeData(want).toString() - setImmediate -> - conn.socket.causeRead Protocol.OKAY - conn.socket.causeRead '1\r\n' - conn.socket.causeEnd() - cmd.execute() - .then -> - done() - - it "should reject with Parser.PrematureEOFError if connection cuts - prematurely", (done) -> - conn = new MockConnection - cmd = new WaitBootCompleteCommand conn - setImmediate -> - conn.socket.causeRead Protocol.OKAY - conn.socket.causeEnd() - cmd.execute() - .then -> - done new Error 'Succeeded even though it should not' - .catch Parser.PrematureEOFError, (err) -> - done() - - it "should not return until boot is complete", (done) -> - conn = new MockConnection - cmd = new WaitBootCompleteCommand conn - sent = false - setImmediate -> - conn.socket.causeRead Protocol.OKAY - conn.socket.causeRead '\r\n' - conn.socket.causeRead '\r\n' - conn.socket.causeRead '\r\n' - conn.socket.causeRead '\r\n' - conn.socket.causeRead '\r\n' - conn.socket.causeRead '\r\n' - conn.socket.causeRead '\r\n' - conn.socket.causeRead '\r\n' - conn.socket.causeRead '\r\n' - conn.socket.causeRead '\r\n' - setTimeout -> - sent = true - conn.socket.causeRead '1\r\n' - , 50 - cmd.execute() - .then -> - expect(sent).to.be.true - done() - - it "should close connection when done", (done) -> - conn = new MockConnection - cmd = new WaitBootCompleteCommand conn - setImmediate -> - conn.socket.causeRead Protocol.OKAY - conn.socket.causeRead '1\r\n' - conn.socket.on 'end', -> - done() - cmd.execute() diff --git a/test/adb/command/host-transport/waitbootcomplete.js b/test/adb/command/host-transport/waitbootcomplete.js new file mode 100644 index 00000000..5fa08129 --- /dev/null +++ b/test/adb/command/host-transport/waitbootcomplete.js @@ -0,0 +1,93 @@ +var Chai, MockConnection, Parser, Protocol, Sinon, Stream, WaitBootCompleteCommand, expect; + +Stream = require('stream'); + +Sinon = require('sinon'); + +Chai = require('chai'); + +Chai.use(require('sinon-chai')); + +expect = Chai.expect; + +MockConnection = require('../../../mock/connection'); + +Protocol = require('../../../../src/adb/protocol'); + +Parser = require('../../../../src/adb/parser'); + +WaitBootCompleteCommand = require('../../../../src/adb/command/host-transport/waitbootcomplete'); + +describe('WaitBootCompleteCommand', function() { + it("should send a while loop with boot check", function(done) { + var cmd, conn, want; + conn = new MockConnection; + cmd = new WaitBootCompleteCommand(conn); + want = 'shell:while getprop sys.boot_completed 2>/dev/null; do sleep 1; done'; + conn.socket.on('write', function(chunk) { + return expect(chunk.toString()).to.equal(Protocol.encodeData(want).toString()); + }); + setImmediate(function() { + conn.socket.causeRead(Protocol.OKAY); + conn.socket.causeRead('1\r\n'); + return conn.socket.causeEnd(); + }); + return cmd.execute().then(function() { + return done(); + }); + }); + it("should reject with Parser.PrematureEOFError if connection cuts prematurely", function(done) { + var cmd, conn; + conn = new MockConnection; + cmd = new WaitBootCompleteCommand(conn); + setImmediate(function() { + conn.socket.causeRead(Protocol.OKAY); + return conn.socket.causeEnd(); + }); + return cmd.execute().then(function() { + return done(new Error('Succeeded even though it should not')); + })["catch"](Parser.PrematureEOFError, function(err) { + return done(); + }); + }); + it("should not return until boot is complete", function(done) { + var cmd, conn, sent; + conn = new MockConnection; + cmd = new WaitBootCompleteCommand(conn); + sent = false; + setImmediate(function() { + conn.socket.causeRead(Protocol.OKAY); + conn.socket.causeRead('\r\n'); + conn.socket.causeRead('\r\n'); + conn.socket.causeRead('\r\n'); + conn.socket.causeRead('\r\n'); + conn.socket.causeRead('\r\n'); + conn.socket.causeRead('\r\n'); + conn.socket.causeRead('\r\n'); + conn.socket.causeRead('\r\n'); + conn.socket.causeRead('\r\n'); + conn.socket.causeRead('\r\n'); + return setTimeout(function() { + sent = true; + return conn.socket.causeRead('1\r\n'); + }, 50); + }); + return cmd.execute().then(function() { + expect(sent).to.be["true"]; + return done(); + }); + }); + return it("should close connection when done", function(done) { + var cmd, conn; + conn = new MockConnection; + cmd = new WaitBootCompleteCommand(conn); + setImmediate(function() { + conn.socket.causeRead(Protocol.OKAY); + return conn.socket.causeRead('1\r\n'); + }); + conn.socket.on('end', function() { + return done(); + }); + return cmd.execute(); + }); +}); diff --git a/test/adb/command/host/connect.coffee b/test/adb/command/host/connect.coffee deleted file mode 100644 index 3b1cd39d..00000000 --- a/test/adb/command/host/connect.coffee +++ /dev/null @@ -1,63 +0,0 @@ -Stream = require 'stream' -Sinon = require 'sinon' -Chai = require 'chai' -Chai.use require 'sinon-chai' -{expect} = Chai - -MockConnection = require '../../../mock/connection' -Protocol = require '../../../../src/adb/protocol' -ConnectCommand = require '../../../../src/adb/command/host/connect' - -describe 'ConnectCommand', -> - - it "should send 'host:connect::'", (done) -> - conn = new MockConnection - cmd = new ConnectCommand conn - conn.socket.on 'write', (chunk) -> - expect(chunk.toString()).to.equal \ - Protocol.encodeData('host:connect:192.168.2.2:5555').toString() - setImmediate -> - conn.socket.causeRead Protocol.OKAY - conn.socket.causeRead Protocol.encodeData('connected to 192.168.2.2:5555') - conn.socket.causeEnd() - cmd.execute '192.168.2.2', 5555 - .then -> - done() - - it "should resolve with the new device id if connected", (done) -> - conn = new MockConnection - cmd = new ConnectCommand conn - setImmediate -> - conn.socket.causeRead Protocol.OKAY - conn.socket.causeRead Protocol.encodeData('connected to 192.168.2.2:5555') - conn.socket.causeEnd() - cmd.execute '192.168.2.2', 5555 - .then (val) -> - expect(val).to.be.equal '192.168.2.2:5555' - done() - - it "should resolve with the new device id if already connected", (done) -> - conn = new MockConnection - cmd = new ConnectCommand conn - setImmediate -> - conn.socket.causeRead Protocol.OKAY - conn.socket.causeRead \ - Protocol.encodeData('already connected to 192.168.2.2:5555') - conn.socket.causeEnd() - cmd.execute '192.168.2.2', 5555 - .then (val) -> - expect(val).to.be.equal '192.168.2.2:5555' - done() - - it "should reject with error if unable to connect", (done) -> - conn = new MockConnection - cmd = new ConnectCommand conn - setImmediate -> - conn.socket.causeRead Protocol.OKAY - conn.socket.causeRead \ - Protocol.encodeData('unable to connect to 192.168.2.2:5555') - conn.socket.causeEnd() - cmd.execute '192.168.2.2', 5555 - .catch (err) -> - expect(err.message).to.eql 'unable to connect to 192.168.2.2:5555' - done() diff --git a/test/adb/command/host/connect.js b/test/adb/command/host/connect.js new file mode 100644 index 00000000..a7fd12b2 --- /dev/null +++ b/test/adb/command/host/connect.js @@ -0,0 +1,78 @@ +var Chai, ConnectCommand, MockConnection, Protocol, Sinon, Stream, expect; + +Stream = require('stream'); + +Sinon = require('sinon'); + +Chai = require('chai'); + +Chai.use(require('sinon-chai')); + +expect = Chai.expect; + +MockConnection = require('../../../mock/connection'); + +Protocol = require('../../../../src/adb/protocol'); + +ConnectCommand = require('../../../../src/adb/command/host/connect'); + +describe('ConnectCommand', function() { + it("should send 'host:connect::'", function(done) { + var cmd, conn; + conn = new MockConnection; + cmd = new ConnectCommand(conn); + conn.socket.on('write', function(chunk) { + return expect(chunk.toString()).to.equal(Protocol.encodeData('host:connect:192.168.2.2:5555').toString()); + }); + setImmediate(function() { + conn.socket.causeRead(Protocol.OKAY); + conn.socket.causeRead(Protocol.encodeData('connected to 192.168.2.2:5555')); + return conn.socket.causeEnd(); + }); + return cmd.execute('192.168.2.2', 5555).then(function() { + return done(); + }); + }); + it("should resolve with the new device id if connected", function(done) { + var cmd, conn; + conn = new MockConnection; + cmd = new ConnectCommand(conn); + setImmediate(function() { + conn.socket.causeRead(Protocol.OKAY); + conn.socket.causeRead(Protocol.encodeData('connected to 192.168.2.2:5555')); + return conn.socket.causeEnd(); + }); + return cmd.execute('192.168.2.2', 5555).then(function(val) { + expect(val).to.be.equal('192.168.2.2:5555'); + return done(); + }); + }); + it("should resolve with the new device id if already connected", function(done) { + var cmd, conn; + conn = new MockConnection; + cmd = new ConnectCommand(conn); + setImmediate(function() { + conn.socket.causeRead(Protocol.OKAY); + conn.socket.causeRead(Protocol.encodeData('already connected to 192.168.2.2:5555')); + return conn.socket.causeEnd(); + }); + return cmd.execute('192.168.2.2', 5555).then(function(val) { + expect(val).to.be.equal('192.168.2.2:5555'); + return done(); + }); + }); + return it("should reject with error if unable to connect", function(done) { + var cmd, conn; + conn = new MockConnection; + cmd = new ConnectCommand(conn); + setImmediate(function() { + conn.socket.causeRead(Protocol.OKAY); + conn.socket.causeRead(Protocol.encodeData('unable to connect to 192.168.2.2:5555')); + return conn.socket.causeEnd(); + }); + return cmd.execute('192.168.2.2', 5555)["catch"](function(err) { + expect(err.message).to.eql('unable to connect to 192.168.2.2:5555'); + return done(); + }); + }); +}); diff --git a/test/adb/command/host/disconnect.coffee b/test/adb/command/host/disconnect.coffee deleted file mode 100644 index 68a4a3bc..00000000 --- a/test/adb/command/host/disconnect.coffee +++ /dev/null @@ -1,50 +0,0 @@ -Stream = require 'stream' -Sinon = require 'sinon' -Chai = require 'chai' -Chai.use require 'sinon-chai' -{expect} = Chai - -MockConnection = require '../../../mock/connection' -Protocol = require '../../../../src/adb/protocol' -DisconnectCommand = require '../../../../src/adb/command/host/disconnect' - -describe 'DisconnectCommand', -> - - it "should send 'host:disconnect::'", (done) -> - conn = new MockConnection - cmd = new DisconnectCommand conn - conn.socket.on 'write', (chunk) -> - expect(chunk.toString()).to.equal \ - Protocol.encodeData('host:disconnect:192.168.2.2:5555').toString() - setImmediate -> - conn.socket.causeRead Protocol.OKAY - conn.socket.causeRead Protocol.encodeData('') - conn.socket.causeEnd() - cmd.execute '192.168.2.2', 5555 - .then -> - done() - - it "should resolve with the new device id if disconnected", (done) -> - conn = new MockConnection - cmd = new DisconnectCommand conn - setImmediate -> - conn.socket.causeRead Protocol.OKAY - conn.socket.causeRead Protocol.encodeData('') - conn.socket.causeEnd() - cmd.execute '192.168.2.2', 5555 - .then (val) -> - expect(val).to.be.equal '192.168.2.2:5555' - done() - - it "should reject with error if unable to disconnect", (done) -> - conn = new MockConnection - cmd = new DisconnectCommand conn - setImmediate -> - conn.socket.causeRead Protocol.OKAY - conn.socket.causeRead \ - Protocol.encodeData('No such device 192.168.2.2:5555') - conn.socket.causeEnd() - cmd.execute '192.168.2.2', 5555 - .catch (err) -> - expect(err.message).to.eql 'No such device 192.168.2.2:5555' - done() diff --git a/test/adb/command/host/disconnect.js b/test/adb/command/host/disconnect.js new file mode 100644 index 00000000..f8319ba6 --- /dev/null +++ b/test/adb/command/host/disconnect.js @@ -0,0 +1,64 @@ +var Chai, DisconnectCommand, MockConnection, Protocol, Sinon, Stream, expect; + +Stream = require('stream'); + +Sinon = require('sinon'); + +Chai = require('chai'); + +Chai.use(require('sinon-chai')); + +expect = Chai.expect; + +MockConnection = require('../../../mock/connection'); + +Protocol = require('../../../../src/adb/protocol'); + +DisconnectCommand = require('../../../../src/adb/command/host/disconnect'); + +describe('DisconnectCommand', function() { + it("should send 'host:disconnect::'", function(done) { + var cmd, conn; + conn = new MockConnection; + cmd = new DisconnectCommand(conn); + conn.socket.on('write', function(chunk) { + return expect(chunk.toString()).to.equal(Protocol.encodeData('host:disconnect:192.168.2.2:5555').toString()); + }); + setImmediate(function() { + conn.socket.causeRead(Protocol.OKAY); + conn.socket.causeRead(Protocol.encodeData('')); + return conn.socket.causeEnd(); + }); + return cmd.execute('192.168.2.2', 5555).then(function() { + return done(); + }); + }); + it("should resolve with the new device id if disconnected", function(done) { + var cmd, conn; + conn = new MockConnection; + cmd = new DisconnectCommand(conn); + setImmediate(function() { + conn.socket.causeRead(Protocol.OKAY); + conn.socket.causeRead(Protocol.encodeData('')); + return conn.socket.causeEnd(); + }); + return cmd.execute('192.168.2.2', 5555).then(function(val) { + expect(val).to.be.equal('192.168.2.2:5555'); + return done(); + }); + }); + return it("should reject with error if unable to disconnect", function(done) { + var cmd, conn; + conn = new MockConnection; + cmd = new DisconnectCommand(conn); + setImmediate(function() { + conn.socket.causeRead(Protocol.OKAY); + conn.socket.causeRead(Protocol.encodeData('No such device 192.168.2.2:5555')); + return conn.socket.causeEnd(); + }); + return cmd.execute('192.168.2.2', 5555)["catch"](function(err) { + expect(err.message).to.eql('No such device 192.168.2.2:5555'); + return done(); + }); + }); +}); diff --git a/test/adb/command/host/version.coffee b/test/adb/command/host/version.coffee deleted file mode 100644 index 88a33f4f..00000000 --- a/test/adb/command/host/version.coffee +++ /dev/null @@ -1,47 +0,0 @@ -Sinon = require 'sinon' -Chai = require 'chai' -Chai.use require 'sinon-chai' -{expect} = Chai - -MockConnection = require '../../../mock/connection' -Protocol = require '../../../../src/adb/protocol' -HostVersionCommand = require '../../../../src/adb/command/host/version' - -describe 'HostVersionCommand', -> - - it "should send 'host:version'", (done) -> - conn = new MockConnection - cmd = new HostVersionCommand conn - conn.socket.on 'write', (chunk) -> - expect(chunk.toString()).to.equal \ - Protocol.encodeData('host:version').toString() - setImmediate -> - conn.socket.causeRead Protocol.OKAY - conn.socket.causeRead Protocol.encodeData '0000' - conn.socket.causeEnd() - cmd.execute() - .then (version) -> - done() - - it "should resolve with version", (done) -> - conn = new MockConnection - cmd = new HostVersionCommand conn - setImmediate -> - conn.socket.causeRead Protocol.OKAY - conn.socket.causeRead Protocol.encodeData (0x1234).toString 16 - conn.socket.causeEnd() - cmd.execute() - .then (version) -> - expect(version).to.equal 0x1234 - done() - - it "should handle old-style version", (done) -> - conn = new MockConnection - cmd = new HostVersionCommand conn - setImmediate -> - conn.socket.causeRead (0x1234).toString 16 - conn.socket.causeEnd() - cmd.execute() - .then (version) -> - expect(version).to.equal 0x1234 - done() diff --git a/test/adb/command/host/version.js b/test/adb/command/host/version.js new file mode 100644 index 00000000..bce552e3 --- /dev/null +++ b/test/adb/command/host/version.js @@ -0,0 +1,61 @@ +var Chai, HostVersionCommand, MockConnection, Protocol, Sinon, expect; + +Sinon = require('sinon'); + +Chai = require('chai'); + +Chai.use(require('sinon-chai')); + +expect = Chai.expect; + +MockConnection = require('../../../mock/connection'); + +Protocol = require('../../../../src/adb/protocol'); + +HostVersionCommand = require('../../../../src/adb/command/host/version'); + +describe('HostVersionCommand', function() { + it("should send 'host:version'", function(done) { + var cmd, conn; + conn = new MockConnection; + cmd = new HostVersionCommand(conn); + conn.socket.on('write', function(chunk) { + return expect(chunk.toString()).to.equal(Protocol.encodeData('host:version').toString()); + }); + setImmediate(function() { + conn.socket.causeRead(Protocol.OKAY); + conn.socket.causeRead(Protocol.encodeData('0000')); + return conn.socket.causeEnd(); + }); + return cmd.execute().then(function(version) { + return done(); + }); + }); + it("should resolve with version", function(done) { + var cmd, conn; + conn = new MockConnection; + cmd = new HostVersionCommand(conn); + setImmediate(function() { + conn.socket.causeRead(Protocol.OKAY); + conn.socket.causeRead(Protocol.encodeData(0x1234.toString(16))); + return conn.socket.causeEnd(); + }); + return cmd.execute().then(function(version) { + expect(version).to.equal(0x1234); + return done(); + }); + }); + return it("should handle old-style version", function(done) { + var cmd, conn; + conn = new MockConnection; + cmd = new HostVersionCommand(conn); + setImmediate(function() { + conn.socket.causeRead(0x1234.toString(16)); + return conn.socket.causeEnd(); + }); + return cmd.execute().then(function(version) { + expect(version).to.equal(0x1234); + return done(); + }); + }); +}); diff --git a/test/adb/framebuffer/rgbtransform.coffee b/test/adb/framebuffer/rgbtransform.coffee deleted file mode 100644 index a75473b5..00000000 --- a/test/adb/framebuffer/rgbtransform.coffee +++ /dev/null @@ -1,192 +0,0 @@ -Stream = require 'stream' -Sinon = require 'sinon' -Chai = require 'chai' -Chai.use require 'sinon-chai' -{expect} = Chai - -RgbTransform = require '../../../src/adb/framebuffer/rgbtransform' - -describe 'RgbTransform', -> - - it "should transform BGRA into RGB", (done) -> - meta = - bpp: 32 - red_offset: 16 - red_length: 8 - green_offset: 8 - green_length: 8 - blue_offset: 0 - blue_length: 8 - alpha_offset: 24 - alpha_length: 8 - pixel = new Buffer 4 - pixel.writeUInt8 50, 0 - pixel.writeUInt8 100, 1 - pixel.writeUInt8 150, 2 - pixel.writeUInt8 200, 3 - stream = new Stream.PassThrough - transform = new RgbTransform meta - stream.pipe transform - transform.on 'data', (chunk) -> - expect(chunk).to.have.length 3 - expect(chunk.readUInt8 0).to.equal 150 - expect(chunk.readUInt8 1).to.equal 100 - expect(chunk.readUInt8 2).to.equal 50 - done() - stream.write pixel - stream.end() - - it "should transform BGR into RGB", (done) -> - meta = - bpp: 32 - red_offset: 16 - red_length: 8 - green_offset: 8 - green_length: 8 - blue_offset: 0 - blue_length: 8 - alpha_offset: 0 - alpha_length: 0 - pixel = new Buffer 4 - pixel.writeUInt8 50, 0 - pixel.writeUInt8 100, 1 - pixel.writeUInt8 150, 2 - stream = new Stream.PassThrough - transform = new RgbTransform meta - stream.pipe transform - transform.on 'data', (chunk) -> - expect(chunk).to.have.length 3 - expect(chunk.readUInt8 0).to.equal 150 - expect(chunk.readUInt8 1).to.equal 100 - expect(chunk.readUInt8 2).to.equal 50 - done() - stream.write pixel - stream.end() - - it "should transform RGB into RGB", (done) -> - meta = - bpp: 24 - red_offset: 0 - red_length: 8 - green_offset: 8 - green_length: 8 - blue_offset: 16 - blue_length: 8 - alpha_offset: 0 - alpha_length: 0 - pixel = new Buffer 3 - pixel.writeUInt8 50, 0 - pixel.writeUInt8 100, 1 - pixel.writeUInt8 150, 2 - stream = new Stream.PassThrough - transform = new RgbTransform meta - stream.pipe transform - transform.on 'data', (chunk) -> - expect(chunk).to.have.length 3 - expect(chunk.readUInt8 0).to.equal 50 - expect(chunk.readUInt8 1).to.equal 100 - expect(chunk.readUInt8 2).to.equal 150 - done() - stream.write pixel - stream.end() - - it "should transform RGBA into RGB", (done) -> - meta = - bpp: 32 - red_offset: 0 - red_length: 8 - green_offset: 8 - green_length: 8 - blue_offset: 16 - blue_length: 8 - alpha_offset: 24 - alpha_length: 8 - pixel = new Buffer 4 - pixel.writeUInt8 50, 0 - pixel.writeUInt8 100, 1 - pixel.writeUInt8 150, 2 - pixel.writeUInt8 200, 3 - stream = new Stream.PassThrough - transform = new RgbTransform meta - stream.pipe transform - transform.on 'data', (chunk) -> - expect(chunk).to.have.length 3 - expect(chunk.readUInt8 0).to.equal 50 - expect(chunk.readUInt8 1).to.equal 100 - expect(chunk.readUInt8 2).to.equal 150 - done() - stream.write pixel - stream.end() - - it "should wait for a complete pixel before transforming", (done) -> - meta = - bpp: 32 - red_offset: 0 - red_length: 8 - green_offset: 8 - green_length: 8 - blue_offset: 16 - blue_length: 8 - alpha_offset: 24 - alpha_length: 8 - pixel = new Buffer 4 - pixel.writeUInt8 50, 0 - pixel.writeUInt8 100, 1 - pixel.writeUInt8 150, 2 - pixel.writeUInt8 200, 3 - stream = new Stream.PassThrough - transform = new RgbTransform meta - stream.pipe transform - transform.on 'data', (chunk) -> - expect(chunk).to.have.length 3 - expect(chunk.readUInt8 0).to.equal 50 - expect(chunk.readUInt8 1).to.equal 100 - expect(chunk.readUInt8 2).to.equal 150 - done() - stream.write pixel.slice 0, 2 - stream.write pixel.slice 2, 3 - stream.write pixel.slice 3, 4 - stream.end() - - it "should transform a stream of multiple pixels", (done) -> - meta = - bpp: 32 - red_offset: 16 - red_length: 8 - green_offset: 8 - green_length: 8 - blue_offset: 0 - blue_length: 8 - alpha_offset: 24 - alpha_length: 8 - pixel1 = new Buffer 4 - pixel1.writeUInt8 50, 0 - pixel1.writeUInt8 100, 1 - pixel1.writeUInt8 150, 2 - pixel1.writeUInt8 200, 3 - pixel2 = new Buffer 4 - pixel2.writeUInt8 51, 0 - pixel2.writeUInt8 101, 1 - pixel2.writeUInt8 151, 2 - pixel2.writeUInt8 201, 3 - stream = new Stream.PassThrough - transform = new RgbTransform meta - stream.pipe transform - all = new Buffer '' - transform.on 'data', (chunk) -> - all = Buffer.concat [all, chunk] - transform.on 'end', -> - expect(all).to.have.length 15 - expect(all.readUInt8 0).to.equal 150 - expect(all.readUInt8 1).to.equal 100 - expect(all.readUInt8 2).to.equal 50 - expect(all.readUInt8 3).to.equal 151 - expect(all.readUInt8 4).to.equal 101 - expect(all.readUInt8 5).to.equal 51 - done() - stream.write pixel1 - stream.write pixel2 - stream.write pixel1 - stream.write pixel2 - stream.write pixel1 - stream.end() diff --git a/test/adb/framebuffer/rgbtransform.js b/test/adb/framebuffer/rgbtransform.js new file mode 100644 index 00000000..b579df1f --- /dev/null +++ b/test/adb/framebuffer/rgbtransform.js @@ -0,0 +1,218 @@ +var Chai, RgbTransform, Sinon, Stream, expect; + +Stream = require('stream'); + +Sinon = require('sinon'); + +Chai = require('chai'); + +Chai.use(require('sinon-chai')); + +expect = Chai.expect; + +RgbTransform = require('../../../src/adb/framebuffer/rgbtransform'); + +describe('RgbTransform', function() { + it("should transform BGRA into RGB", function(done) { + var meta, pixel, stream, transform; + meta = { + bpp: 32, + red_offset: 16, + red_length: 8, + green_offset: 8, + green_length: 8, + blue_offset: 0, + blue_length: 8, + alpha_offset: 24, + alpha_length: 8 + }; + pixel = new Buffer(4); + pixel.writeUInt8(50, 0); + pixel.writeUInt8(100, 1); + pixel.writeUInt8(150, 2); + pixel.writeUInt8(200, 3); + stream = new Stream.PassThrough; + transform = new RgbTransform(meta); + stream.pipe(transform); + transform.on('data', function(chunk) { + expect(chunk).to.have.length(3); + expect(chunk.readUInt8(0)).to.equal(150); + expect(chunk.readUInt8(1)).to.equal(100); + expect(chunk.readUInt8(2)).to.equal(50); + return done(); + }); + stream.write(pixel); + return stream.end(); + }); + it("should transform BGR into RGB", function(done) { + var meta, pixel, stream, transform; + meta = { + bpp: 32, + red_offset: 16, + red_length: 8, + green_offset: 8, + green_length: 8, + blue_offset: 0, + blue_length: 8, + alpha_offset: 0, + alpha_length: 0 + }; + pixel = new Buffer(4); + pixel.writeUInt8(50, 0); + pixel.writeUInt8(100, 1); + pixel.writeUInt8(150, 2); + stream = new Stream.PassThrough; + transform = new RgbTransform(meta); + stream.pipe(transform); + transform.on('data', function(chunk) { + expect(chunk).to.have.length(3); + expect(chunk.readUInt8(0)).to.equal(150); + expect(chunk.readUInt8(1)).to.equal(100); + expect(chunk.readUInt8(2)).to.equal(50); + return done(); + }); + stream.write(pixel); + return stream.end(); + }); + it("should transform RGB into RGB", function(done) { + var meta, pixel, stream, transform; + meta = { + bpp: 24, + red_offset: 0, + red_length: 8, + green_offset: 8, + green_length: 8, + blue_offset: 16, + blue_length: 8, + alpha_offset: 0, + alpha_length: 0 + }; + pixel = new Buffer(3); + pixel.writeUInt8(50, 0); + pixel.writeUInt8(100, 1); + pixel.writeUInt8(150, 2); + stream = new Stream.PassThrough; + transform = new RgbTransform(meta); + stream.pipe(transform); + transform.on('data', function(chunk) { + expect(chunk).to.have.length(3); + expect(chunk.readUInt8(0)).to.equal(50); + expect(chunk.readUInt8(1)).to.equal(100); + expect(chunk.readUInt8(2)).to.equal(150); + return done(); + }); + stream.write(pixel); + return stream.end(); + }); + it("should transform RGBA into RGB", function(done) { + var meta, pixel, stream, transform; + meta = { + bpp: 32, + red_offset: 0, + red_length: 8, + green_offset: 8, + green_length: 8, + blue_offset: 16, + blue_length: 8, + alpha_offset: 24, + alpha_length: 8 + }; + pixel = new Buffer(4); + pixel.writeUInt8(50, 0); + pixel.writeUInt8(100, 1); + pixel.writeUInt8(150, 2); + pixel.writeUInt8(200, 3); + stream = new Stream.PassThrough; + transform = new RgbTransform(meta); + stream.pipe(transform); + transform.on('data', function(chunk) { + expect(chunk).to.have.length(3); + expect(chunk.readUInt8(0)).to.equal(50); + expect(chunk.readUInt8(1)).to.equal(100); + expect(chunk.readUInt8(2)).to.equal(150); + return done(); + }); + stream.write(pixel); + return stream.end(); + }); + it("should wait for a complete pixel before transforming", function(done) { + var meta, pixel, stream, transform; + meta = { + bpp: 32, + red_offset: 0, + red_length: 8, + green_offset: 8, + green_length: 8, + blue_offset: 16, + blue_length: 8, + alpha_offset: 24, + alpha_length: 8 + }; + pixel = new Buffer(4); + pixel.writeUInt8(50, 0); + pixel.writeUInt8(100, 1); + pixel.writeUInt8(150, 2); + pixel.writeUInt8(200, 3); + stream = new Stream.PassThrough; + transform = new RgbTransform(meta); + stream.pipe(transform); + transform.on('data', function(chunk) { + expect(chunk).to.have.length(3); + expect(chunk.readUInt8(0)).to.equal(50); + expect(chunk.readUInt8(1)).to.equal(100); + expect(chunk.readUInt8(2)).to.equal(150); + return done(); + }); + stream.write(pixel.slice(0, 2)); + stream.write(pixel.slice(2, 3)); + stream.write(pixel.slice(3, 4)); + return stream.end(); + }); + return it("should transform a stream of multiple pixels", function(done) { + var all, meta, pixel1, pixel2, stream, transform; + meta = { + bpp: 32, + red_offset: 16, + red_length: 8, + green_offset: 8, + green_length: 8, + blue_offset: 0, + blue_length: 8, + alpha_offset: 24, + alpha_length: 8 + }; + pixel1 = new Buffer(4); + pixel1.writeUInt8(50, 0); + pixel1.writeUInt8(100, 1); + pixel1.writeUInt8(150, 2); + pixel1.writeUInt8(200, 3); + pixel2 = new Buffer(4); + pixel2.writeUInt8(51, 0); + pixel2.writeUInt8(101, 1); + pixel2.writeUInt8(151, 2); + pixel2.writeUInt8(201, 3); + stream = new Stream.PassThrough; + transform = new RgbTransform(meta); + stream.pipe(transform); + all = new Buffer(''); + transform.on('data', function(chunk) { + return all = Buffer.concat([all, chunk]); + }); + transform.on('end', function() { + expect(all).to.have.length(15); + expect(all.readUInt8(0)).to.equal(150); + expect(all.readUInt8(1)).to.equal(100); + expect(all.readUInt8(2)).to.equal(50); + expect(all.readUInt8(3)).to.equal(151); + expect(all.readUInt8(4)).to.equal(101); + expect(all.readUInt8(5)).to.equal(51); + return done(); + }); + stream.write(pixel1); + stream.write(pixel2); + stream.write(pixel1); + stream.write(pixel2); + stream.write(pixel1); + return stream.end(); + }); +}); diff --git a/test/adb/linetransform.coffee b/test/adb/linetransform.coffee deleted file mode 100644 index 6a56e44d..00000000 --- a/test/adb/linetransform.coffee +++ /dev/null @@ -1,173 +0,0 @@ -Stream = require 'stream' -Sinon = require 'sinon' -Chai = require 'chai' -Chai.use require 'sinon-chai' -{expect} = Chai - -LineTransform = require '../../src/adb/linetransform' -MockDuplex = require '../mock/duplex' - -describe 'LineTransform', -> - - it "should implement stream.Transform", (done) -> - expect(new LineTransform).to.be.an.instanceOf Stream.Transform - done() - - describe 'with autoDetect', -> - it "should not modify data if first byte is 0x0a", (done) -> - duplex = new MockDuplex - transform = new LineTransform autoDetect: true - transform.on 'data', (data) -> - expect(data.toString()).to.equal 'bar\r\n' - done() - duplex.pipe transform - duplex.causeRead '\nbar\r\n' - duplex.causeEnd() - - it "should not include initial 0x0a", (done) -> - duplex = new MockDuplex - transform = new LineTransform autoDetect: true - buffer = new Buffer '' - transform.on 'data', (data) -> - buffer = Buffer.concat [buffer, data] - transform.on 'end', -> - expect(buffer.toString()).to.equal 'bar\r\n' - done() - duplex.pipe transform - duplex.causeRead '\nbar\r\n' - duplex.causeEnd() - - it "should not include initial 0x0d 0x0a", (done) -> - duplex = new MockDuplex - transform = new LineTransform autoDetect: true - buffer = new Buffer '' - transform.on 'data', (data) -> - buffer = Buffer.concat [buffer, data] - transform.on 'end', -> - expect(buffer.toString()).to.equal 'bar\n' - done() - duplex.pipe transform - duplex.causeRead '\r\nbar\r\n' - duplex.causeEnd() - - it "should not include initial 0x0d 0x0a even if in separate - chunks", (done) -> - duplex = new MockDuplex - transform = new LineTransform autoDetect: true - buffer = new Buffer '' - transform.on 'data', (data) -> - buffer = Buffer.concat [buffer, data] - transform.on 'end', -> - expect(buffer.toString()).to.equal 'bar\n' - done() - duplex.pipe transform - duplex.causeRead '\r' - duplex.causeRead '\nbar\r\n' - duplex.causeEnd() - - it "should transform as usual if first byte is not 0x0a", (done) -> - duplex = new MockDuplex - transform = new LineTransform autoDetect: true - buffer = new Buffer '' - transform.on 'data', (data) -> - buffer = Buffer.concat [buffer, data] - transform.on 'end', -> - expect(buffer.toString()).to.equal 'bar\nfoo' - done() - duplex.pipe transform - duplex.causeRead '\r\nbar\r\nfoo' - duplex.causeEnd() - - describe 'without autoDetect', -> - it "should transform as usual even if first byte is 0x0a", (done) -> - duplex = new MockDuplex - transform = new LineTransform - buffer = new Buffer '' - transform.on 'data', (data) -> - buffer = Buffer.concat [buffer, data] - transform.on 'end', -> - expect(buffer.toString()).to.equal '\n\nbar\nfoo' - done() - duplex.pipe transform - duplex.causeRead '\n\r\nbar\r\nfoo' - duplex.causeEnd() - - it "should not modify data that does not have 0x0d 0x0a in it", (done) -> - duplex = new MockDuplex - transform = new LineTransform - transform.on 'data', (data) -> - expect(data.toString()).to.equal 'foo' - done() - duplex.pipe transform - duplex.causeRead 'foo' - duplex.causeEnd() - - it "should not remove 0x0d if not followed by 0x0a", (done) -> - duplex = new MockDuplex - transform = new LineTransform - transform.on 'data', (data) -> - expect(data.length).to.equal 2 - expect(data[0]).to.equal 0x0d - expect(data[1]).to.equal 0x05 - done() - duplex.pipe transform - duplex.causeRead new Buffer [0x0d, 0x05] - duplex.causeEnd() - - it "should remove 0x0d if followed by 0x0a", (done) -> - duplex = new MockDuplex - transform = new LineTransform - transform.on 'data', (data) -> - expect(data.length).to.equal 2 - expect(data[0]).to.equal 0x0a - expect(data[1]).to.equal 0x97 - done() - duplex.pipe transform - duplex.causeRead new Buffer [0x0d, 0x0a, 0x97] - duplex.causeEnd() - - it "should push 0x0d without 0x0a if last in stream", (done) -> - duplex = new MockDuplex - transform = new LineTransform - transform.on 'data', (data) -> - expect(data.length).to.equal 1 - expect(data[0]).to.equal 0x0d - done() - duplex.pipe transform - duplex.causeRead new Buffer [0x0d] - duplex.causeEnd() - - it "should push saved 0x0d if next chunk does not start with 0x0a", (done) -> - duplex = new MockDuplex - transform = new LineTransform - buffer = new Buffer '' - transform.on 'data', (data) -> - buffer = Buffer.concat [buffer, data] - transform.on 'end', -> - expect(buffer).to.have.length 3 - expect(buffer[0]).to.equal 0x62 - expect(buffer[1]).to.equal 0x0d - expect(buffer[2]).to.equal 0x37 - done() - duplex.pipe transform - duplex.causeRead new Buffer [0x62, 0x0d] - duplex.causeRead new Buffer [0x37] - duplex.causeEnd() - duplex.end() - - it "should remove saved 0x0d if next chunk starts with 0x0a", (done) -> - duplex = new MockDuplex - transform = new LineTransform - buffer = new Buffer '' - transform.on 'data', (data) -> - buffer = Buffer.concat [buffer, data] - transform.on 'end', -> - expect(buffer).to.have.length 2 - expect(buffer[0]).to.equal 0x62 - expect(buffer[1]).to.equal 0x0a - done() - duplex.pipe transform - duplex.causeRead new Buffer [0x62, 0x0d] - duplex.causeRead new Buffer [0x0a] - duplex.causeEnd() - duplex.end() diff --git a/test/adb/linetransform.js b/test/adb/linetransform.js new file mode 100644 index 00000000..49f54fef --- /dev/null +++ b/test/adb/linetransform.js @@ -0,0 +1,223 @@ +var Chai, LineTransform, MockDuplex, Sinon, Stream, expect; + +Stream = require('stream'); + +Sinon = require('sinon'); + +Chai = require('chai'); + +Chai.use(require('sinon-chai')); + +expect = Chai.expect; + +LineTransform = require('../../src/adb/linetransform'); + +MockDuplex = require('../mock/duplex'); + +describe('LineTransform', function() { + it("should implement stream.Transform", function(done) { + expect(new LineTransform).to.be.an.instanceOf(Stream.Transform); + return done(); + }); + describe('with autoDetect', function() { + it("should not modify data if first byte is 0x0a", function(done) { + var duplex, transform; + duplex = new MockDuplex; + transform = new LineTransform({ + autoDetect: true + }); + transform.on('data', function(data) { + expect(data.toString()).to.equal('bar\r\n'); + return done(); + }); + duplex.pipe(transform); + duplex.causeRead('\nbar\r\n'); + return duplex.causeEnd(); + }); + it("should not include initial 0x0a", function(done) { + var buffer, duplex, transform; + duplex = new MockDuplex; + transform = new LineTransform({ + autoDetect: true + }); + buffer = new Buffer(''); + transform.on('data', function(data) { + return buffer = Buffer.concat([buffer, data]); + }); + transform.on('end', function() { + expect(buffer.toString()).to.equal('bar\r\n'); + return done(); + }); + duplex.pipe(transform); + duplex.causeRead('\nbar\r\n'); + return duplex.causeEnd(); + }); + it("should not include initial 0x0d 0x0a", function(done) { + var buffer, duplex, transform; + duplex = new MockDuplex; + transform = new LineTransform({ + autoDetect: true + }); + buffer = new Buffer(''); + transform.on('data', function(data) { + return buffer = Buffer.concat([buffer, data]); + }); + transform.on('end', function() { + expect(buffer.toString()).to.equal('bar\n'); + return done(); + }); + duplex.pipe(transform); + duplex.causeRead('\r\nbar\r\n'); + return duplex.causeEnd(); + }); + it("should not include initial 0x0d 0x0a even if in separate chunks", function(done) { + var buffer, duplex, transform; + duplex = new MockDuplex; + transform = new LineTransform({ + autoDetect: true + }); + buffer = new Buffer(''); + transform.on('data', function(data) { + return buffer = Buffer.concat([buffer, data]); + }); + transform.on('end', function() { + expect(buffer.toString()).to.equal('bar\n'); + return done(); + }); + duplex.pipe(transform); + duplex.causeRead('\r'); + duplex.causeRead('\nbar\r\n'); + return duplex.causeEnd(); + }); + return it("should transform as usual if first byte is not 0x0a", function(done) { + var buffer, duplex, transform; + duplex = new MockDuplex; + transform = new LineTransform({ + autoDetect: true + }); + buffer = new Buffer(''); + transform.on('data', function(data) { + return buffer = Buffer.concat([buffer, data]); + }); + transform.on('end', function() { + expect(buffer.toString()).to.equal('bar\nfoo'); + return done(); + }); + duplex.pipe(transform); + duplex.causeRead('\r\nbar\r\nfoo'); + return duplex.causeEnd(); + }); + }); + describe('without autoDetect', function() { + return it("should transform as usual even if first byte is 0x0a", function(done) { + var buffer, duplex, transform; + duplex = new MockDuplex; + transform = new LineTransform; + buffer = new Buffer(''); + transform.on('data', function(data) { + return buffer = Buffer.concat([buffer, data]); + }); + transform.on('end', function() { + expect(buffer.toString()).to.equal('\n\nbar\nfoo'); + return done(); + }); + duplex.pipe(transform); + duplex.causeRead('\n\r\nbar\r\nfoo'); + return duplex.causeEnd(); + }); + }); + it("should not modify data that does not have 0x0d 0x0a in it", function(done) { + var duplex, transform; + duplex = new MockDuplex; + transform = new LineTransform; + transform.on('data', function(data) { + expect(data.toString()).to.equal('foo'); + return done(); + }); + duplex.pipe(transform); + duplex.causeRead('foo'); + return duplex.causeEnd(); + }); + it("should not remove 0x0d if not followed by 0x0a", function(done) { + var duplex, transform; + duplex = new MockDuplex; + transform = new LineTransform; + transform.on('data', function(data) { + expect(data.length).to.equal(2); + expect(data[0]).to.equal(0x0d); + expect(data[1]).to.equal(0x05); + return done(); + }); + duplex.pipe(transform); + duplex.causeRead(new Buffer([0x0d, 0x05])); + return duplex.causeEnd(); + }); + it("should remove 0x0d if followed by 0x0a", function(done) { + var duplex, transform; + duplex = new MockDuplex; + transform = new LineTransform; + transform.on('data', function(data) { + expect(data.length).to.equal(2); + expect(data[0]).to.equal(0x0a); + expect(data[1]).to.equal(0x97); + return done(); + }); + duplex.pipe(transform); + duplex.causeRead(new Buffer([0x0d, 0x0a, 0x97])); + return duplex.causeEnd(); + }); + it("should push 0x0d without 0x0a if last in stream", function(done) { + var duplex, transform; + duplex = new MockDuplex; + transform = new LineTransform; + transform.on('data', function(data) { + expect(data.length).to.equal(1); + expect(data[0]).to.equal(0x0d); + return done(); + }); + duplex.pipe(transform); + duplex.causeRead(new Buffer([0x0d])); + return duplex.causeEnd(); + }); + it("should push saved 0x0d if next chunk does not start with 0x0a", function(done) { + var buffer, duplex, transform; + duplex = new MockDuplex; + transform = new LineTransform; + buffer = new Buffer(''); + transform.on('data', function(data) { + return buffer = Buffer.concat([buffer, data]); + }); + transform.on('end', function() { + expect(buffer).to.have.length(3); + expect(buffer[0]).to.equal(0x62); + expect(buffer[1]).to.equal(0x0d); + expect(buffer[2]).to.equal(0x37); + return done(); + }); + duplex.pipe(transform); + duplex.causeRead(new Buffer([0x62, 0x0d])); + duplex.causeRead(new Buffer([0x37])); + duplex.causeEnd(); + return duplex.end(); + }); + return it("should remove saved 0x0d if next chunk starts with 0x0a", function(done) { + var buffer, duplex, transform; + duplex = new MockDuplex; + transform = new LineTransform; + buffer = new Buffer(''); + transform.on('data', function(data) { + return buffer = Buffer.concat([buffer, data]); + }); + transform.on('end', function() { + expect(buffer).to.have.length(2); + expect(buffer[0]).to.equal(0x62); + expect(buffer[1]).to.equal(0x0a); + return done(); + }); + duplex.pipe(transform); + duplex.causeRead(new Buffer([0x62, 0x0d])); + duplex.causeRead(new Buffer([0x0a])); + duplex.causeEnd(); + return duplex.end(); + }); +}); diff --git a/test/adb/parser.coffee b/test/adb/parser.coffee deleted file mode 100644 index d7dfdeb8..00000000 --- a/test/adb/parser.coffee +++ /dev/null @@ -1,409 +0,0 @@ -Stream = require 'stream' -Promise = require 'bluebird' -Sinon = require 'sinon' -Chai = require 'chai' -Chai.use require 'sinon-chai' -{expect} = require 'chai' - -Parser = require '../../src/adb/parser' - -describe 'Parser', -> - - describe 'end()', -> - - it "should end the stream and consume all remaining data", (done) -> - stream = new Stream.PassThrough - parser = new Parser stream - stream.write 'F' - stream.write 'O' - stream.write 'O' - parser.end() - .then -> - done() - - describe 'readAll()', -> - - it "should return a cancellable Promise", (done) -> - stream = new Stream.PassThrough - parser = new Parser stream - promise = parser.readAll() - expect(promise).to.be.an.instanceOf Promise - expect(promise.isCancellable()).to.be.true - promise.catch Promise.CancellationError, (err) -> - done() - promise.cancel() - - it "should read all remaining content until the stream ends", (done) -> - stream = new Stream.PassThrough - parser = new Parser stream - parser.readAll() - .then (buf) -> - expect(buf.length).to.equal 3 - expect(buf.toString()).to.equal 'FOO' - done() - stream.write 'F' - stream.write 'O' - stream.write 'O' - stream.end() - - it "should resolve with an empty Buffer if the stream has already ended - and there's nothing more to read", (done) -> - stream = new Stream.PassThrough - parser = new Parser stream - parser.readAll() - .then (buf) -> - expect(buf.length).to.equal 0 - done() - stream.end() - - describe 'readBytes(howMany)', -> - - it "should return a cancellable Promise", (done) -> - stream = new Stream.PassThrough - parser = new Parser stream - promise = parser.readBytes 1 - expect(promise).to.be.an.instanceOf Promise - expect(promise.isCancellable()).to.be.true - promise.catch Promise.CancellationError, (err) -> - done() - promise.cancel() - - it "should read as many bytes as requested", (done) -> - stream = new Stream.PassThrough - parser = new Parser stream - parser.readBytes 4 - .then (buf) -> - expect(buf.length).to.equal 4 - expect(buf.toString()).to.equal 'OKAY' - parser.readBytes 2 - .then (buf) -> - expect(buf).to.have.length 2 - expect(buf.toString()).to.equal 'FA' - done() - stream.write 'OKAYFAIL' - - it "should wait for enough data to appear", (done) -> - stream = new Stream.PassThrough - parser = new Parser stream - parser.readBytes 5 - .then (buf) -> - expect(buf.toString()).to.equal 'BYTES' - done() - Promise.delay 50 - .then -> - stream.write 'BYTES' - - it "should keep data waiting even when nothing has been - requested", (done) -> - stream = new Stream.PassThrough - parser = new Parser stream - stream.write 'FOO' - Promise.delay 50 - .then -> - parser.readBytes 2 - .then (buf) -> - expect(buf.length).to.equal 2 - expect(buf.toString()).to.equal 'FO' - done() - - it "should reject with Parser.PrematureEOFError if stream ends - before enough bytes can be read", (done) -> - stream = new Stream.PassThrough - parser = new Parser stream - stream.write 'F' - parser.readBytes 10 - .catch Parser.PrematureEOFError, (err) -> - expect(err.missingBytes).to.equal 9 - done() - stream.end() - - describe 'readByteFlow(maxHowMany, targetStream)', -> - - it "should return a cancellable Promise", (done) -> - stream = new Stream.PassThrough - parser = new Parser stream - target = new Stream.PassThrough - promise = parser.readByteFlow 1, target - expect(promise).to.be.an.instanceOf Promise - expect(promise.isCancellable()).to.be.true - promise.catch Promise.CancellationError, (err) -> - done() - promise.cancel() - - it "should read as many bytes as requested", (done) -> - stream = new Stream.PassThrough - parser = new Parser stream - target = new Stream.PassThrough - parser.readByteFlow 4, target - .then -> - expect(target.read()).to.eql new Buffer('OKAY') - parser.readByteFlow 2, target - .then -> - expect(target.read()).to.eql new Buffer('FA') - done() - .catch done - stream.write 'OKAYFAIL' - - it "should progress with new/partial chunk until maxHowMany", (done) -> - stream = new Stream.PassThrough - parser = new Parser stream - target = new Stream.PassThrough - parser.readByteFlow 3, target - .then -> - expect(target.read()).to.eql new Buffer('PIE') - done() - .catch done - b1 = new Buffer 'P' - b2 = new Buffer 'I' - b3 = new Buffer 'ES' - b4 = new Buffer 'R' - stream.write b1 - stream.write b2 - stream.write b3 - stream.write b4 - - describe 'readAscii(howMany)', -> - - it "should return a cancellable Promise", (done) -> - stream = new Stream.PassThrough - parser = new Parser stream - promise = parser.readAscii 1 - expect(promise).to.be.an.instanceOf Promise - expect(promise.isCancellable()).to.be.true - promise.catch Promise.CancellationError, (err) -> - done() - promise.cancel() - - it "should read as many ascii characters as requested", (done) -> - stream = new Stream.PassThrough - parser = new Parser stream - parser.readAscii 4 - .then (str) -> - expect(str.length).to.equal 4 - expect(str).to.equal 'OKAY' - done() - stream.write 'OKAYFAIL' - - it "should reject with Parser.PrematureEOFError if stream ends - before enough bytes can be read", (done) -> - stream = new Stream.PassThrough - parser = new Parser stream - stream.write 'FOO' - parser.readAscii 7 - .catch Parser.PrematureEOFError, (err) -> - expect(err.missingBytes).to.equal 4 - done() - stream.end() - - describe 'readValue()', -> - - it "should return a cancellable Promise", (done) -> - stream = new Stream.PassThrough - parser = new Parser stream - promise = parser.readValue() - expect(promise).to.be.an.instanceOf Promise - expect(promise.isCancellable()).to.be.true - promise.catch Promise.CancellationError, (err) -> - done() - promise.cancel() - - it "should read a protocol value as a Buffer", (done) -> - stream = new Stream.PassThrough - parser = new Parser stream - parser.readValue() - .then (value) -> - expect(value).to.be.an.instanceOf Buffer - expect(value).to.have.length 4 - expect(value.toString()).to.equal '001f' - done() - stream.write '0004001f' - - it "should return an empty value", (done) -> - stream = new Stream.PassThrough - parser = new Parser stream - parser.readValue() - .then (value) -> - expect(value).to.be.an.instanceOf Buffer - expect(value).to.have.length 0 - done() - stream.write '0000' - - it "should reject with Parser.PrematureEOFError if stream ends - before the value can be read", (done) -> - stream = new Stream.PassThrough - parser = new Parser stream - parser.readValue() - .catch Parser.PrematureEOFError, (err) -> - done() - stream.write '00ffabc' - stream.end() - - describe 'readError()', -> - - it "should return a cancellable Promise", (done) -> - stream = new Stream.PassThrough - parser = new Parser stream - promise = parser.readError() - expect(promise).to.be.an.instanceOf Promise - expect(promise.isCancellable()).to.be.true - promise.catch Promise.CancellationError, (err) -> - done() - promise.cancel() - - it "should reject with Parser.FailError using the value", (done) -> - stream = new Stream.PassThrough - parser = new Parser stream - parser.readError() - .catch Parser.FailError, (err) -> - expect(err.message).to.equal "Failure: 'epic failure'" - done() - stream.write '000cepic failure' - - it "should reject with Parser.PrematureEOFError if stream ends - before the error can be read", (done) -> - stream = new Stream.PassThrough - parser = new Parser stream - parser.readError() - .catch Parser.PrematureEOFError, (err) -> - done() - stream.write '000cepic' - stream.end() - - describe 'searchLine(re)', -> - - it "should return a cancellable Promise", (done) -> - stream = new Stream.PassThrough - parser = new Parser stream - promise = parser.searchLine /foo/ - expect(promise).to.be.an.instanceOf Promise - expect(promise.isCancellable()).to.be.true - promise.catch Promise.CancellationError, (err) -> - done() - promise.cancel() - - it "should return the re.exec match of the matching line", (done) -> - stream = new Stream.PassThrough - parser = new Parser stream - parser.searchLine /za(p)/ - .then (line) -> - expect(line[0]).to.equal 'zap' - expect(line[1]).to.equal 'p' - expect(line.input).to.equal 'zip zap' - done() - stream.write 'foo bar\nzip zap\npip pop\n' - - it "should reject with Parser.PrematureEOFError if stream ends - before a line is found", (done) -> - stream = new Stream.PassThrough - parser = new Parser stream - parser.searchLine /nope/ - .catch Parser.PrematureEOFError, (err) -> - done() - stream.write 'foo bar' - stream.end() - - describe 'readLine()', -> - - it "should return a cancellable Promise", (done) -> - stream = new Stream.PassThrough - parser = new Parser stream - promise = parser.readLine() - expect(promise).to.be.an.instanceOf Promise - expect(promise.isCancellable()).to.be.true - promise.catch Promise.CancellationError, (err) -> - done() - promise.cancel() - - it "should skip a line terminated by \\n", (done) -> - stream = new Stream.PassThrough - parser = new Parser stream - parser.readLine() - .then -> - parser.readBytes 7 - .then (buf) -> - expect(buf.toString()).to.equal 'zip zap' - done() - stream.write 'foo bar\nzip zap\npip pop' - - it "should return skipped line", (done) -> - stream = new Stream.PassThrough - parser = new Parser stream - parser.readLine() - .then (buf) -> - expect(buf.toString()).to.equal 'foo bar' - done() - stream.write 'foo bar\nzip zap\npip pop' - - it "should strip trailing \\r", (done) -> - stream = new Stream.PassThrough - parser = new Parser stream - parser.readLine() - .then (buf) -> - expect(buf.toString()).to.equal 'foo bar' - done() - stream.write 'foo bar\r\n' - - it "should reject with Parser.PrematureEOFError if stream ends - before a line is found", (done) -> - stream = new Stream.PassThrough - parser = new Parser stream - parser.readLine() - .catch Parser.PrematureEOFError, (err) -> - done() - stream.write 'foo bar' - stream.end() - - describe 'readUntil(code)', -> - - it "should return a cancellable Promise", (done) -> - stream = new Stream.PassThrough - parser = new Parser stream - promise = parser.readUntil 0xa0 - expect(promise).to.be.an.instanceOf Promise - expect(promise.isCancellable()).to.be.true - promise.catch Promise.CancellationError, (err) -> - done() - promise.cancel() - - it "should return any characters before given value", (done) -> - stream = new Stream.PassThrough - parser = new Parser stream - parser.readUntil 'p'.charCodeAt 0 - .then (buf) -> - expect(buf.toString()).to.equal 'foo bar\nzi' - done() - stream.write 'foo bar\nzip zap\npip pop' - - it "should reject with Parser.PrematureEOFError if stream ends - before a line is found", (done) -> - stream = new Stream.PassThrough - parser = new Parser stream - parser.readUntil 'z'.charCodeAt 0 - .catch Parser.PrematureEOFError, (err) -> - done() - stream.write 'ho ho' - stream.end() - - describe 'raw()', -> - - it "should return the resumed raw stream", (done) -> - stream = new Stream.PassThrough - parser = new Parser stream - raw = parser.raw() - expect(raw).to.equal stream - raw.on 'data', -> - done() - raw.write 'foo' - - describe 'unexpected(data, expected)', -> - - it "should reject with Parser.UnexpectedDataError", (done) -> - stream = new Stream.PassThrough - parser = new Parser stream - parser.unexpected('foo', "'bar' or end of stream") - .catch Parser.UnexpectedDataError, (err) -> - expect(err.message).to.equal "Unexpected 'foo', was expecting 'bar' - or end of stream" - expect(err.unexpected).to.equal 'foo' - expect(err.expected).to.equal "'bar' or end of stream" - done() diff --git a/test/adb/parser.js b/test/adb/parser.js new file mode 100644 index 00000000..7526fa49 --- /dev/null +++ b/test/adb/parser.js @@ -0,0 +1,450 @@ +var Chai, Parser, Promise, Sinon, Stream, expect; + +Stream = require('stream'); + +Promise = require('bluebird'); + +Sinon = require('sinon'); + +Chai = require('chai'); + +Chai.use(require('sinon-chai')); + +expect = require('chai').expect; + +Parser = require('../../src/adb/parser'); + +describe('Parser', function() { + describe('end()', function() { + return it("should end the stream and consume all remaining data", function(done) { + var parser, stream; + stream = new Stream.PassThrough; + parser = new Parser(stream); + stream.write('F'); + stream.write('O'); + stream.write('O'); + return parser.end().then(function() { + return done(); + }); + }); + }); + describe('readAll()', function() { + it("should return a cancellable Promise", function(done) { + var parser, promise, stream; + stream = new Stream.PassThrough; + parser = new Parser(stream); + promise = parser.readAll(); + expect(promise).to.be.an.instanceOf(Promise); + expect(promise.isCancellable()).to.be["true"]; + promise["catch"](Promise.CancellationError, function(err) { + return done(); + }); + return promise.cancel(); + }); + it("should read all remaining content until the stream ends", function(done) { + var parser, stream; + stream = new Stream.PassThrough; + parser = new Parser(stream); + parser.readAll().then(function(buf) { + expect(buf.length).to.equal(3); + expect(buf.toString()).to.equal('FOO'); + return done(); + }); + stream.write('F'); + stream.write('O'); + stream.write('O'); + return stream.end(); + }); + return it("should resolve with an empty Buffer if the stream has already ended and there's nothing more to read", function(done) { + var parser, stream; + stream = new Stream.PassThrough; + parser = new Parser(stream); + parser.readAll().then(function(buf) { + expect(buf.length).to.equal(0); + return done(); + }); + return stream.end(); + }); + }); + describe('readBytes(howMany)', function() { + it("should return a cancellable Promise", function(done) { + var parser, promise, stream; + stream = new Stream.PassThrough; + parser = new Parser(stream); + promise = parser.readBytes(1); + expect(promise).to.be.an.instanceOf(Promise); + expect(promise.isCancellable()).to.be["true"]; + promise["catch"](Promise.CancellationError, function(err) { + return done(); + }); + return promise.cancel(); + }); + it("should read as many bytes as requested", function(done) { + var parser, stream; + stream = new Stream.PassThrough; + parser = new Parser(stream); + parser.readBytes(4).then(function(buf) { + expect(buf.length).to.equal(4); + expect(buf.toString()).to.equal('OKAY'); + return parser.readBytes(2).then(function(buf) { + expect(buf).to.have.length(2); + expect(buf.toString()).to.equal('FA'); + return done(); + }); + }); + return stream.write('OKAYFAIL'); + }); + it("should wait for enough data to appear", function(done) { + var parser, stream; + stream = new Stream.PassThrough; + parser = new Parser(stream); + parser.readBytes(5).then(function(buf) { + expect(buf.toString()).to.equal('BYTES'); + return done(); + }); + return Promise.delay(50).then(function() { + return stream.write('BYTES'); + }); + }); + it("should keep data waiting even when nothing has been requested", function(done) { + var parser, stream; + stream = new Stream.PassThrough; + parser = new Parser(stream); + stream.write('FOO'); + return Promise.delay(50).then(function() { + return parser.readBytes(2).then(function(buf) { + expect(buf.length).to.equal(2); + expect(buf.toString()).to.equal('FO'); + return done(); + }); + }); + }); + return it("should reject with Parser.PrematureEOFError if stream ends before enough bytes can be read", function(done) { + var parser, stream; + stream = new Stream.PassThrough; + parser = new Parser(stream); + stream.write('F'); + parser.readBytes(10)["catch"](Parser.PrematureEOFError, function(err) { + expect(err.missingBytes).to.equal(9); + return done(); + }); + return stream.end(); + }); + }); + describe('readByteFlow(maxHowMany, targetStream)', function() { + it("should return a cancellable Promise", function(done) { + var parser, promise, stream, target; + stream = new Stream.PassThrough; + parser = new Parser(stream); + target = new Stream.PassThrough; + promise = parser.readByteFlow(1, target); + expect(promise).to.be.an.instanceOf(Promise); + expect(promise.isCancellable()).to.be["true"]; + promise["catch"](Promise.CancellationError, function(err) { + return done(); + }); + return promise.cancel(); + }); + it("should read as many bytes as requested", function(done) { + var parser, stream, target; + stream = new Stream.PassThrough; + parser = new Parser(stream); + target = new Stream.PassThrough; + parser.readByteFlow(4, target).then(function() { + expect(target.read()).to.eql(new Buffer('OKAY')); + return parser.readByteFlow(2, target).then(function() { + expect(target.read()).to.eql(new Buffer('FA')); + return done(); + }); + })["catch"](done); + return stream.write('OKAYFAIL'); + }); + return it("should progress with new/partial chunk until maxHowMany", function(done) { + var b1, b2, b3, b4, parser, stream, target; + stream = new Stream.PassThrough; + parser = new Parser(stream); + target = new Stream.PassThrough; + parser.readByteFlow(3, target).then(function() { + expect(target.read()).to.eql(new Buffer('PIE')); + return done(); + })["catch"](done); + b1 = new Buffer('P'); + b2 = new Buffer('I'); + b3 = new Buffer('ES'); + b4 = new Buffer('R'); + stream.write(b1); + stream.write(b2); + stream.write(b3); + return stream.write(b4); + }); + }); + describe('readAscii(howMany)', function() { + it("should return a cancellable Promise", function(done) { + var parser, promise, stream; + stream = new Stream.PassThrough; + parser = new Parser(stream); + promise = parser.readAscii(1); + expect(promise).to.be.an.instanceOf(Promise); + expect(promise.isCancellable()).to.be["true"]; + promise["catch"](Promise.CancellationError, function(err) { + return done(); + }); + return promise.cancel(); + }); + it("should read as many ascii characters as requested", function(done) { + var parser, stream; + stream = new Stream.PassThrough; + parser = new Parser(stream); + parser.readAscii(4).then(function(str) { + expect(str.length).to.equal(4); + expect(str).to.equal('OKAY'); + return done(); + }); + return stream.write('OKAYFAIL'); + }); + return it("should reject with Parser.PrematureEOFError if stream ends before enough bytes can be read", function(done) { + var parser, stream; + stream = new Stream.PassThrough; + parser = new Parser(stream); + stream.write('FOO'); + parser.readAscii(7)["catch"](Parser.PrematureEOFError, function(err) { + expect(err.missingBytes).to.equal(4); + return done(); + }); + return stream.end(); + }); + }); + describe('readValue()', function() { + it("should return a cancellable Promise", function(done) { + var parser, promise, stream; + stream = new Stream.PassThrough; + parser = new Parser(stream); + promise = parser.readValue(); + expect(promise).to.be.an.instanceOf(Promise); + expect(promise.isCancellable()).to.be["true"]; + promise["catch"](Promise.CancellationError, function(err) { + return done(); + }); + return promise.cancel(); + }); + it("should read a protocol value as a Buffer", function(done) { + var parser, stream; + stream = new Stream.PassThrough; + parser = new Parser(stream); + parser.readValue().then(function(value) { + expect(value).to.be.an.instanceOf(Buffer); + expect(value).to.have.length(4); + expect(value.toString()).to.equal('001f'); + return done(); + }); + return stream.write('0004001f'); + }); + it("should return an empty value", function(done) { + var parser, stream; + stream = new Stream.PassThrough; + parser = new Parser(stream); + parser.readValue().then(function(value) { + expect(value).to.be.an.instanceOf(Buffer); + expect(value).to.have.length(0); + return done(); + }); + return stream.write('0000'); + }); + return it("should reject with Parser.PrematureEOFError if stream ends before the value can be read", function(done) { + var parser, stream; + stream = new Stream.PassThrough; + parser = new Parser(stream); + parser.readValue()["catch"](Parser.PrematureEOFError, function(err) { + return done(); + }); + stream.write('00ffabc'); + return stream.end(); + }); + }); + describe('readError()', function() { + it("should return a cancellable Promise", function(done) { + var parser, promise, stream; + stream = new Stream.PassThrough; + parser = new Parser(stream); + promise = parser.readError(); + expect(promise).to.be.an.instanceOf(Promise); + expect(promise.isCancellable()).to.be["true"]; + promise["catch"](Promise.CancellationError, function(err) { + return done(); + }); + return promise.cancel(); + }); + it("should reject with Parser.FailError using the value", function(done) { + var parser, stream; + stream = new Stream.PassThrough; + parser = new Parser(stream); + parser.readError()["catch"](Parser.FailError, function(err) { + expect(err.message).to.equal("Failure: 'epic failure'"); + return done(); + }); + return stream.write('000cepic failure'); + }); + return it("should reject with Parser.PrematureEOFError if stream ends before the error can be read", function(done) { + var parser, stream; + stream = new Stream.PassThrough; + parser = new Parser(stream); + parser.readError()["catch"](Parser.PrematureEOFError, function(err) { + return done(); + }); + stream.write('000cepic'); + return stream.end(); + }); + }); + describe('searchLine(re)', function() { + it("should return a cancellable Promise", function(done) { + var parser, promise, stream; + stream = new Stream.PassThrough; + parser = new Parser(stream); + promise = parser.searchLine(/foo/); + expect(promise).to.be.an.instanceOf(Promise); + expect(promise.isCancellable()).to.be["true"]; + promise["catch"](Promise.CancellationError, function(err) { + return done(); + }); + return promise.cancel(); + }); + it("should return the re.exec match of the matching line", function(done) { + var parser, stream; + stream = new Stream.PassThrough; + parser = new Parser(stream); + parser.searchLine(/za(p)/).then(function(line) { + expect(line[0]).to.equal('zap'); + expect(line[1]).to.equal('p'); + expect(line.input).to.equal('zip zap'); + return done(); + }); + return stream.write('foo bar\nzip zap\npip pop\n'); + }); + return it("should reject with Parser.PrematureEOFError if stream ends before a line is found", function(done) { + var parser, stream; + stream = new Stream.PassThrough; + parser = new Parser(stream); + parser.searchLine(/nope/)["catch"](Parser.PrematureEOFError, function(err) { + return done(); + }); + stream.write('foo bar'); + return stream.end(); + }); + }); + describe('readLine()', function() { + it("should return a cancellable Promise", function(done) { + var parser, promise, stream; + stream = new Stream.PassThrough; + parser = new Parser(stream); + promise = parser.readLine(); + expect(promise).to.be.an.instanceOf(Promise); + expect(promise.isCancellable()).to.be["true"]; + promise["catch"](Promise.CancellationError, function(err) { + return done(); + }); + return promise.cancel(); + }); + it("should skip a line terminated by \\n", function(done) { + var parser, stream; + stream = new Stream.PassThrough; + parser = new Parser(stream); + parser.readLine().then(function() { + return parser.readBytes(7).then(function(buf) { + expect(buf.toString()).to.equal('zip zap'); + return done(); + }); + }); + return stream.write('foo bar\nzip zap\npip pop'); + }); + it("should return skipped line", function(done) { + var parser, stream; + stream = new Stream.PassThrough; + parser = new Parser(stream); + parser.readLine().then(function(buf) { + expect(buf.toString()).to.equal('foo bar'); + return done(); + }); + return stream.write('foo bar\nzip zap\npip pop'); + }); + it("should strip trailing \\r", function(done) { + var parser, stream; + stream = new Stream.PassThrough; + parser = new Parser(stream); + parser.readLine().then(function(buf) { + expect(buf.toString()).to.equal('foo bar'); + return done(); + }); + return stream.write('foo bar\r\n'); + }); + return it("should reject with Parser.PrematureEOFError if stream ends before a line is found", function(done) { + var parser, stream; + stream = new Stream.PassThrough; + parser = new Parser(stream); + parser.readLine()["catch"](Parser.PrematureEOFError, function(err) { + return done(); + }); + stream.write('foo bar'); + return stream.end(); + }); + }); + describe('readUntil(code)', function() { + it("should return a cancellable Promise", function(done) { + var parser, promise, stream; + stream = new Stream.PassThrough; + parser = new Parser(stream); + promise = parser.readUntil(0xa0); + expect(promise).to.be.an.instanceOf(Promise); + expect(promise.isCancellable()).to.be["true"]; + promise["catch"](Promise.CancellationError, function(err) { + return done(); + }); + return promise.cancel(); + }); + it("should return any characters before given value", function(done) { + var parser, stream; + stream = new Stream.PassThrough; + parser = new Parser(stream); + parser.readUntil('p'.charCodeAt(0)).then(function(buf) { + expect(buf.toString()).to.equal('foo bar\nzi'); + return done(); + }); + return stream.write('foo bar\nzip zap\npip pop'); + }); + return it("should reject with Parser.PrematureEOFError if stream ends before a line is found", function(done) { + var parser, stream; + stream = new Stream.PassThrough; + parser = new Parser(stream); + parser.readUntil('z'.charCodeAt(0))["catch"](Parser.PrematureEOFError, function(err) { + return done(); + }); + stream.write('ho ho'); + return stream.end(); + }); + }); + describe('raw()', function() { + return it("should return the resumed raw stream", function(done) { + var parser, raw, stream; + stream = new Stream.PassThrough; + parser = new Parser(stream); + raw = parser.raw(); + expect(raw).to.equal(stream); + raw.on('data', function() { + return done(); + }); + return raw.write('foo'); + }); + }); + return describe('unexpected(data, expected)', function() { + return it("should reject with Parser.UnexpectedDataError", function(done) { + var parser, stream; + stream = new Stream.PassThrough; + parser = new Parser(stream); + return parser.unexpected('foo', "'bar' or end of stream")["catch"](Parser.UnexpectedDataError, function(err) { + expect(err.message).to.equal("Unexpected 'foo', was expecting 'bar' or end of stream"); + expect(err.unexpected).to.equal('foo'); + expect(err.expected).to.equal("'bar' or end of stream"); + return done(); + }); + }); + }); +}); diff --git a/test/adb/protocol.coffee b/test/adb/protocol.coffee deleted file mode 100644 index 98341f88..00000000 --- a/test/adb/protocol.coffee +++ /dev/null @@ -1,67 +0,0 @@ -{expect} = require 'chai' - -Protocol = require '../../src/adb/protocol' - -describe 'Protocol', -> - - it "should expose a 'FAIL' property", (done) -> - expect(Protocol).to.have.property 'FAIL' - expect(Protocol.FAIL).to.equal 'FAIL' - done() - - it "should expose an 'OKAY' property", (done) -> - expect(Protocol).to.have.property 'OKAY' - expect(Protocol.OKAY).to.equal 'OKAY' - done() - - describe '@decodeLength(length)', -> - - it "should return a Number", (done) -> - expect(Protocol.decodeLength '0x0046').to.be.a 'number' - done() - - it "should accept a hexadecimal string", (done) -> - expect(Protocol.decodeLength '0x5887').to.equal 0x5887 - done() - - describe '@encodeLength(length)', -> - - it "should return a String", (done) -> - expect(Protocol.encodeLength 27).to.be.a 'string' - done() - - it "should return a valid hexadecimal number", (done) -> - expect(parseInt Protocol.encodeLength(32), 16).to.equal 32 - expect(parseInt Protocol.encodeLength(9999), 16).to.equal 9999 - done() - - it "should return uppercase hexadecimal digits", (done) -> - expect(Protocol.encodeLength(0x0abc)).to.equal '0ABC' - done() - - it "should pad short values with zeroes for a 4-byte size", (done) -> - expect(Protocol.encodeLength 1).to.have.length 4 - expect(Protocol.encodeLength 2).to.have.length 4 - expect(Protocol.encodeLength 57).to.have.length 4 - done() - - it "should return 0000 for 0 length", (done) -> - expect(Protocol.encodeLength 0).to.equal '0000' - done() - - describe '@encodeData(data)', -> - - it "should return a Buffer", (done) -> - expect(Protocol.encodeData new Buffer '').to.be.an.instanceOf Buffer - done() - - it "should accept a string or a Buffer", (done) -> - expect(Protocol.encodeData '').to.be.an.instanceOf Buffer - expect(Protocol.encodeData new Buffer '').to.be.an.instanceOf Buffer - done() - - it "should prefix data with length", (done) -> - data = Protocol.encodeData new Buffer 0x270F - expect(data).to.have.length 0x270F + 4 - expect(data.toString 'ascii', 0, 4).to.equal '270F' - done() diff --git a/test/adb/protocol.js b/test/adb/protocol.js new file mode 100644 index 00000000..3bdbdd8b --- /dev/null +++ b/test/adb/protocol.js @@ -0,0 +1,71 @@ +var Protocol, expect; + +expect = require('chai').expect; + +Protocol = require('../../src/adb/protocol'); + +describe('Protocol', function() { + it("should expose a 'FAIL' property", function(done) { + expect(Protocol).to.have.property('FAIL'); + expect(Protocol.FAIL).to.equal('FAIL'); + return done(); + }); + it("should expose an 'OKAY' property", function(done) { + expect(Protocol).to.have.property('OKAY'); + expect(Protocol.OKAY).to.equal('OKAY'); + return done(); + }); + describe('@decodeLength(length)', function() { + it("should return a Number", function(done) { + expect(Protocol.decodeLength('0x0046')).to.be.a('number'); + return done(); + }); + return it("should accept a hexadecimal string", function(done) { + expect(Protocol.decodeLength('0x5887')).to.equal(0x5887); + return done(); + }); + }); + describe('@encodeLength(length)', function() { + it("should return a String", function(done) { + expect(Protocol.encodeLength(27)).to.be.a('string'); + return done(); + }); + it("should return a valid hexadecimal number", function(done) { + expect(parseInt(Protocol.encodeLength(32), 16)).to.equal(32); + expect(parseInt(Protocol.encodeLength(9999), 16)).to.equal(9999); + return done(); + }); + it("should return uppercase hexadecimal digits", function(done) { + expect(Protocol.encodeLength(0x0abc)).to.equal('0ABC'); + return done(); + }); + it("should pad short values with zeroes for a 4-byte size", function(done) { + expect(Protocol.encodeLength(1)).to.have.length(4); + expect(Protocol.encodeLength(2)).to.have.length(4); + expect(Protocol.encodeLength(57)).to.have.length(4); + return done(); + }); + return it("should return 0000 for 0 length", function(done) { + expect(Protocol.encodeLength(0)).to.equal('0000'); + return done(); + }); + }); + return describe('@encodeData(data)', function() { + it("should return a Buffer", function(done) { + expect(Protocol.encodeData(new Buffer(''))).to.be.an.instanceOf(Buffer); + return done(); + }); + it("should accept a string or a Buffer", function(done) { + expect(Protocol.encodeData('')).to.be.an.instanceOf(Buffer); + expect(Protocol.encodeData(new Buffer(''))).to.be.an.instanceOf(Buffer); + return done(); + }); + return it("should prefix data with length", function(done) { + var data; + data = Protocol.encodeData(new Buffer(0x270F)); + expect(data).to.have.length(0x270F + 4); + expect(data.toString('ascii', 0, 4)).to.equal('270F'); + return done(); + }); + }); +}); diff --git a/test/adb/sync.coffee b/test/adb/sync.coffee deleted file mode 100644 index 2b33147b..00000000 --- a/test/adb/sync.coffee +++ /dev/null @@ -1,247 +0,0 @@ -Fs = require 'fs' -Stream = require 'stream' -Promise = require 'bluebird' -Sinon = require 'sinon' -Chai = require 'chai' -Chai.use require 'sinon-chai' -{expect, assert} = Chai - -Adb = require '../../src/adb' -Sync = require '../../src/adb/sync' -Stats = require '../../src/adb/sync/stats' -Entry = require '../../src/adb/sync/entry' -PushTransfer = require '../../src/adb/sync/pushtransfer' -PullTransfer = require '../../src/adb/sync/pulltransfer' -MockConnection = require '../mock/connection' - -# This test suite is a bit special in that it requires a connected Android -# device (or many devices). All will be tested. -describe 'Sync', -> - # By default, skip tests that require a device. - dt = xit - dt = it if process.env.RUN_DEVICE_TESTS - - SURELY_EXISTING_FILE = '/system/build.prop' - SURELY_EXISTING_PATH = '/' - SURELY_NONEXISTING_PATH = '/non-existing-path' - SURELY_WRITABLE_FILE = '/data/local/tmp/_sync.test' - - client = null - deviceList = null - - forEachSyncDevice = (iterator, done) -> - assert deviceList.length > 0, - 'At least one connected Android device is required' - - promises = deviceList.map (device) -> - client.syncService device.id - .then (sync) -> - expect(sync).to.be.an.instanceof Sync - Promise.cast iterator sync - .finally -> - sync.end() - - Promise.all promises - .then -> - done() - .catch done - - before (done) -> - client = Adb.createClient() - client.listDevices() - .then (devices) -> - deviceList = devices - done() - - describe 'end()', -> - - it "should end the sync connection", -> - conn = new MockConnection - sync = new Sync conn - Sinon.stub conn, 'end' - sync.end() - expect(conn.end).to.have.been.called - - describe 'push(contents, path[, mode])', -> - - it "should call pushStream when contents is a Stream", -> - conn = new MockConnection - sync = new Sync conn - stream = new Stream.PassThrough - Sinon.stub sync, 'pushStream' - sync.push stream, 'foo' - expect(sync.pushStream).to.have.been.called - - it "should call pushFile when contents is a String", -> - conn = new MockConnection - sync = new Sync conn - stream = new Stream.PassThrough - Sinon.stub sync, 'pushFile' - sync.push __filename, 'foo' - expect(sync.pushFile).to.have.been.called - - it "should return a PushTransfer instance", -> - conn = new MockConnection - sync = new Sync conn - stream = new Stream.PassThrough - transfer = sync.push stream, 'foo' - expect(transfer).to.be.an.instanceof PushTransfer - transfer.cancel() - - describe 'pushStream(stream, path[, mode])', -> - - it "should return a PushTransfer instance", -> - conn = new MockConnection - sync = new Sync conn - stream = new Stream.PassThrough - transfer = sync.pushStream stream, 'foo' - expect(transfer).to.be.an.instanceof PushTransfer - transfer.cancel() - - dt "should be able to push >65536 byte chunks without error", (done) -> - forEachSyncDevice (sync) -> - new Promise (resolve, reject) -> - stream = new Stream.PassThrough - content = new Buffer 1000000 - transfer = sync.pushStream stream, SURELY_WRITABLE_FILE - transfer.on 'error', reject - transfer.on 'end', resolve - stream.write content - stream.end() - , done - - describe 'pull(path)', -> - - dt "should retrieve the same content pushStream() pushed", (done) -> - forEachSyncDevice (sync) -> - new Promise (resolve, reject) -> - stream = new Stream.PassThrough - content = 'ABCDEFGHI' + Date.now() - transfer = sync.pushStream stream, SURELY_WRITABLE_FILE - expect(transfer).to.be.an.instanceof PushTransfer - transfer.on 'error', reject - transfer.on 'end', -> - transfer = sync.pull SURELY_WRITABLE_FILE - expect(transfer).to.be.an.instanceof PullTransfer - transfer.on 'error', reject - transfer.on 'readable', -> - while chunk = transfer.read() - expect(chunk).to.not.be.null - expect(chunk.toString()).to.equal content - return resolve() - stream.write content - stream.end() - , done - - dt "should emit error for non-existing files", (done) -> - forEachSyncDevice (sync) -> - new Promise (resolve, reject) -> - transfer = sync.pull SURELY_NONEXISTING_PATH - transfer.on 'error', resolve - , done - - dt "should return a PullTransfer instance", (done) -> - forEachSyncDevice (sync) -> - rval = sync.pull SURELY_EXISTING_FILE - expect(rval).to.be.an.instanceof PullTransfer - rval.cancel() - , done - - describe 'Stream', -> - - dt "should emit 'end' when pull is done", (done) -> - forEachSyncDevice (sync) -> - new Promise (resolve, reject) -> - transfer = sync.pull SURELY_EXISTING_FILE - transfer.on 'error', reject - transfer.on 'end', resolve - transfer.resume() - , done - - describe 'stat(path)', -> - - dt "should return a Promise", (done) -> - forEachSyncDevice (sync) -> - rval = sync.stat SURELY_EXISTING_PATH - expect(rval).to.be.an.instanceof Promise - rval - , done - - dt "should call with an ENOENT error if the path does not exist", (done) -> - forEachSyncDevice (sync) -> - sync.stat SURELY_NONEXISTING_PATH - .then (stats) -> - throw new Error 'Should not reach success branch' - .catch (err) -> - expect(err).to.be.an.instanceof Error - expect(err.code).to.equal 'ENOENT' - expect(err.errno).to.equal 34 - expect(err.path).to.equal SURELY_NONEXISTING_PATH - , done - - dt "should call with an fs.Stats instance for an existing path", (done) -> - forEachSyncDevice (sync) -> - sync.stat SURELY_EXISTING_PATH - .then (stats) -> - expect(stats).to.be.an.instanceof Fs.Stats - , done - - describe 'Stats', -> - - it "should implement Fs.Stats", (done) -> - expect(new Stats 0, 0, 0).to.be.an.instanceof Fs.Stats - done() - - dt "should set the `.mode` property for isFile() etc", (done) -> - forEachSyncDevice (sync) -> - sync.stat SURELY_EXISTING_FILE - .then (stats) -> - expect(stats).to.be.an.instanceof Fs.Stats - expect(stats.mode).to.be.above 0 - expect(stats.isFile()).to.be.true - expect(stats.isDirectory()).to.be.false - , done - - dt "should set the `.size` property", (done) -> - forEachSyncDevice (sync) -> - sync.stat SURELY_EXISTING_FILE - .then (stats) -> - expect(stats).to.be.an.instanceof Fs.Stats - expect(stats.isFile()).to.be.true - expect(stats.size).to.be.above 0 - , done - - dt "should set the `.mtime` property", (done) -> - forEachSyncDevice (sync) -> - sync.stat SURELY_EXISTING_FILE - .then (stats) -> - expect(stats).to.be.an.instanceof Fs.Stats - expect(stats.mtime).to.be.an.instanceof Date - , done - - describe 'Entry', -> - - it "should implement Stats", (done) -> - expect(new Entry 'foo', 0, 0, 0).to.be.an.instanceof Stats - done() - - dt "should set the `.name` property", (done) -> - forEachSyncDevice (sync) -> - sync.readdir SURELY_EXISTING_PATH - .then (files) -> - expect(files).to.be.an 'Array' - files.forEach (file) -> - expect(file.name).to.not.be.null - expect(file).to.be.an.instanceof Entry - , done - - dt "should set the Stats properties", (done) -> - forEachSyncDevice (sync) -> - sync.readdir SURELY_EXISTING_PATH - .then (files) -> - expect(files).to.be.an 'Array' - files.forEach (file) -> - expect(file.mode).to.not.be.null - expect(file.size).to.not.be.null - expect(file.mtime).to.not.be.null - , done diff --git a/test/adb/sync.js b/test/adb/sync.js new file mode 100644 index 00000000..31a77aad --- /dev/null +++ b/test/adb/sync.js @@ -0,0 +1,279 @@ +var Adb, Chai, Entry, Fs, MockConnection, Promise, PullTransfer, PushTransfer, Sinon, Stats, Stream, Sync, assert, expect; + +Fs = require('fs'); + +Stream = require('stream'); + +Promise = require('bluebird'); + +Sinon = require('sinon'); + +Chai = require('chai'); + +Chai.use(require('sinon-chai')); + +expect = Chai.expect, assert = Chai.assert; + +Adb = require('../../src/adb'); + +Sync = require('../../src/adb/sync'); + +Stats = require('../../src/adb/sync/stats'); + +Entry = require('../../src/adb/sync/entry'); + +PushTransfer = require('../../src/adb/sync/pushtransfer'); + +PullTransfer = require('../../src/adb/sync/pulltransfer'); + +MockConnection = require('../mock/connection'); + +describe('Sync', function() { + var SURELY_EXISTING_FILE, SURELY_EXISTING_PATH, SURELY_NONEXISTING_PATH, SURELY_WRITABLE_FILE, client, deviceList, dt, forEachSyncDevice; + dt = xit; + if (process.env.RUN_DEVICE_TESTS) { + dt = it; + } + SURELY_EXISTING_FILE = '/system/build.prop'; + SURELY_EXISTING_PATH = '/'; + SURELY_NONEXISTING_PATH = '/non-existing-path'; + SURELY_WRITABLE_FILE = '/data/local/tmp/_sync.test'; + client = null; + deviceList = null; + forEachSyncDevice = function(iterator, done) { + var promises; + assert(deviceList.length > 0, 'At least one connected Android device is required'); + promises = deviceList.map(function(device) { + return client.syncService(device.id).then(function(sync) { + expect(sync).to.be.an["instanceof"](Sync); + return Promise.cast(iterator(sync))["finally"](function() { + return sync.end(); + }); + }); + }); + return Promise.all(promises).then(function() { + return done(); + })["catch"](done); + }; + before(function(done) { + client = Adb.createClient(); + return client.listDevices().then(function(devices) { + deviceList = devices; + return done(); + }); + }); + describe('end()', function() { + return it("should end the sync connection", function() { + var conn, sync; + conn = new MockConnection; + sync = new Sync(conn); + Sinon.stub(conn, 'end'); + sync.end(); + return expect(conn.end).to.have.been.called; + }); + }); + describe('push(contents, path[, mode])', function() { + it("should call pushStream when contents is a Stream", function() { + var conn, stream, sync; + conn = new MockConnection; + sync = new Sync(conn); + stream = new Stream.PassThrough; + Sinon.stub(sync, 'pushStream'); + sync.push(stream, 'foo'); + return expect(sync.pushStream).to.have.been.called; + }); + it("should call pushFile when contents is a String", function() { + var conn, stream, sync; + conn = new MockConnection; + sync = new Sync(conn); + stream = new Stream.PassThrough; + Sinon.stub(sync, 'pushFile'); + sync.push(__filename, 'foo'); + return expect(sync.pushFile).to.have.been.called; + }); + return it("should return a PushTransfer instance", function() { + var conn, stream, sync, transfer; + conn = new MockConnection; + sync = new Sync(conn); + stream = new Stream.PassThrough; + transfer = sync.push(stream, 'foo'); + expect(transfer).to.be.an["instanceof"](PushTransfer); + return transfer.cancel(); + }); + }); + describe('pushStream(stream, path[, mode])', function() { + it("should return a PushTransfer instance", function() { + var conn, stream, sync, transfer; + conn = new MockConnection; + sync = new Sync(conn); + stream = new Stream.PassThrough; + transfer = sync.pushStream(stream, 'foo'); + expect(transfer).to.be.an["instanceof"](PushTransfer); + return transfer.cancel(); + }); + return dt("should be able to push >65536 byte chunks without error", function(done) { + return forEachSyncDevice(function(sync) { + return new Promise(function(resolve, reject) { + var content, stream, transfer; + stream = new Stream.PassThrough; + content = new Buffer(1000000); + transfer = sync.pushStream(stream, SURELY_WRITABLE_FILE); + transfer.on('error', reject); + transfer.on('end', resolve); + stream.write(content); + return stream.end(); + }); + }, done); + }); + }); + describe('pull(path)', function() { + dt("should retrieve the same content pushStream() pushed", function(done) { + return forEachSyncDevice(function(sync) { + return new Promise(function(resolve, reject) { + var content, stream, transfer; + stream = new Stream.PassThrough; + content = 'ABCDEFGHI' + Date.now(); + transfer = sync.pushStream(stream, SURELY_WRITABLE_FILE); + expect(transfer).to.be.an["instanceof"](PushTransfer); + transfer.on('error', reject); + transfer.on('end', function() { + transfer = sync.pull(SURELY_WRITABLE_FILE); + expect(transfer).to.be.an["instanceof"](PullTransfer); + transfer.on('error', reject); + return transfer.on('readable', function() { + var chunk; + while (chunk = transfer.read()) { + expect(chunk).to.not.be["null"]; + expect(chunk.toString()).to.equal(content); + return resolve(); + } + }); + }); + stream.write(content); + return stream.end(); + }); + }, done); + }); + dt("should emit error for non-existing files", function(done) { + return forEachSyncDevice(function(sync) { + return new Promise(function(resolve, reject) { + var transfer; + transfer = sync.pull(SURELY_NONEXISTING_PATH); + return transfer.on('error', resolve); + }); + }, done); + }); + dt("should return a PullTransfer instance", function(done) { + return forEachSyncDevice(function(sync) { + var rval; + rval = sync.pull(SURELY_EXISTING_FILE); + expect(rval).to.be.an["instanceof"](PullTransfer); + return rval.cancel(); + }, done); + }); + return describe('Stream', function() { + return dt("should emit 'end' when pull is done", function(done) { + return forEachSyncDevice(function(sync) { + return new Promise(function(resolve, reject) { + var transfer; + transfer = sync.pull(SURELY_EXISTING_FILE); + transfer.on('error', reject); + transfer.on('end', resolve); + return transfer.resume(); + }); + }, done); + }); + }); + }); + return describe('stat(path)', function() { + dt("should return a Promise", function(done) { + return forEachSyncDevice(function(sync) { + var rval; + rval = sync.stat(SURELY_EXISTING_PATH); + expect(rval).to.be.an["instanceof"](Promise); + return rval; + }, done); + }); + dt("should call with an ENOENT error if the path does not exist", function(done) { + return forEachSyncDevice(function(sync) { + return sync.stat(SURELY_NONEXISTING_PATH).then(function(stats) { + throw new Error('Should not reach success branch'); + })["catch"](function(err) { + expect(err).to.be.an["instanceof"](Error); + expect(err.code).to.equal('ENOENT'); + expect(err.errno).to.equal(34); + return expect(err.path).to.equal(SURELY_NONEXISTING_PATH); + }); + }, done); + }); + dt("should call with an fs.Stats instance for an existing path", function(done) { + return forEachSyncDevice(function(sync) { + return sync.stat(SURELY_EXISTING_PATH).then(function(stats) { + return expect(stats).to.be.an["instanceof"](Fs.Stats); + }); + }, done); + }); + describe('Stats', function() { + it("should implement Fs.Stats", function(done) { + expect(new Stats(0, 0, 0)).to.be.an["instanceof"](Fs.Stats); + return done(); + }); + dt("should set the `.mode` property for isFile() etc", function(done) { + return forEachSyncDevice(function(sync) { + return sync.stat(SURELY_EXISTING_FILE).then(function(stats) { + expect(stats).to.be.an["instanceof"](Fs.Stats); + expect(stats.mode).to.be.above(0); + expect(stats.isFile()).to.be["true"]; + return expect(stats.isDirectory()).to.be["false"]; + }); + }, done); + }); + dt("should set the `.size` property", function(done) { + return forEachSyncDevice(function(sync) { + return sync.stat(SURELY_EXISTING_FILE).then(function(stats) { + expect(stats).to.be.an["instanceof"](Fs.Stats); + expect(stats.isFile()).to.be["true"]; + return expect(stats.size).to.be.above(0); + }); + }, done); + }); + return dt("should set the `.mtime` property", function(done) { + return forEachSyncDevice(function(sync) { + return sync.stat(SURELY_EXISTING_FILE).then(function(stats) { + expect(stats).to.be.an["instanceof"](Fs.Stats); + return expect(stats.mtime).to.be.an["instanceof"](Date); + }); + }, done); + }); + }); + return describe('Entry', function() { + it("should implement Stats", function(done) { + expect(new Entry('foo', 0, 0, 0)).to.be.an["instanceof"](Stats); + return done(); + }); + dt("should set the `.name` property", function(done) { + return forEachSyncDevice(function(sync) { + return sync.readdir(SURELY_EXISTING_PATH).then(function(files) { + expect(files).to.be.an('Array'); + return files.forEach(function(file) { + expect(file.name).to.not.be["null"]; + return expect(file).to.be.an["instanceof"](Entry); + }); + }); + }, done); + }); + return dt("should set the Stats properties", function(done) { + return forEachSyncDevice(function(sync) { + return sync.readdir(SURELY_EXISTING_PATH).then(function(files) { + expect(files).to.be.an('Array'); + return files.forEach(function(file) { + expect(file.mode).to.not.be["null"]; + expect(file.size).to.not.be["null"]; + return expect(file.mtime).to.not.be["null"]; + }); + }); + }, done); + }); + }); + }); +}); diff --git a/test/adb/tracker.coffee b/test/adb/tracker.coffee deleted file mode 100644 index 4a20ab00..00000000 --- a/test/adb/tracker.coffee +++ /dev/null @@ -1,160 +0,0 @@ -Stream = require 'stream' -Sinon = require 'sinon' -Chai = require 'chai' -Chai.use require 'sinon-chai' -{expect} = require 'chai' - -Parser = require '../../src/adb/parser' -Tracker = require '../../src/adb/tracker' -Protocol = require '../../src/adb/protocol' -HostTrackDevicesCommand = require '../../src/adb/command/host/trackdevices' - -describe 'Tracker', -> - - beforeEach -> - @writer = new Stream.PassThrough - @conn = - parser: new Parser @writer - end: -> - @cmd = new HostTrackDevicesCommand @conn - @tracker = new Tracker @cmd - - it "should emit 'add' when a device is added", (done) -> - spy = Sinon.spy() - @tracker.on 'add', spy - device1 = - id: 'a', - type: 'device' - device2 = - id: 'b', - type: 'device' - @tracker.update [device1, device2] - expect(spy).to.have.been.calledTwice - expect(spy).to.have.been.calledWith device1 - expect(spy).to.have.been.calledWith device2 - done() - - it "should emit 'remove' when a device is removed", (done) -> - spy = Sinon.spy() - @tracker.on 'remove', spy - device1 = - id: 'a', - type: 'device' - device2 = - id: 'b', - type: 'device' - @tracker.update [device1, device2] - @tracker.update [device1] - expect(spy).to.have.been.calledOnce - expect(spy).to.have.been.calledWith device2 - done() - - it "should emit 'change' when a device changes", (done) -> - spy = Sinon.spy() - @tracker.on 'change', spy - deviceOld = - id: 'a', - type: 'device' - deviceNew = - id: 'a', - type: 'offline' - @tracker.update [deviceOld] - @tracker.update [deviceNew] - expect(spy).to.have.been.calledOnce - expect(spy).to.have.been.calledWith deviceNew, deviceOld - done() - - it "should emit 'changeSet' with all changes", (done) -> - spy = Sinon.spy() - @tracker.on 'changeSet', spy - device1 = - id: 'a', - type: 'device' - device2 = - id: 'b', - type: 'device' - device3 = - id: 'c', - type: 'device' - device3New = - id: 'c', - type: 'offline' - device4 = - id: 'd', - type: 'offline' - @tracker.update [device1, device2, device3] - @tracker.update [device1, device3New, device4] - expect(spy).to.have.been.calledTwice - expect(spy).to.have.been.calledWith - added: [device1, device2, device3] - changed: [] - removed: [] - expect(spy).to.have.been.calledWith - added: [device4] - changed: [device3New] - removed: [device2] - done() - - it "should emit 'error' and 'end' when connection ends", (done) -> - @tracker.on 'error', => - @tracker.on 'end', -> - done() - @writer.end() - - it "should read devices from socket", (done) -> - spy = Sinon.spy() - @tracker.on 'changeSet', spy - device1 = - id: 'a', - type: 'device' - device2 = - id: 'b', - type: 'device' - device3 = - id: 'c', - type: 'device' - device3New = - id: 'c', - type: 'offline' - device4 = - id: 'd', - type: 'offline' - @writer.write Protocol.encodeData """ - a\tdevice - b\tdevice - c\tdevice - """ - @writer.write Protocol.encodeData """ - a\tdevice - c\toffline - d\toffline - """ - setTimeout -> - expect(spy).to.have.been.calledTwice - expect(spy).to.have.been.calledWith - added: [device1, device2, device3] - changed: [] - removed: [] - expect(spy).to.have.been.calledWith - added: [device4] - changed: [device3New] - removed: [device2] - done() - , 10 - - describe 'end()', -> - - it "should close the connection", (done) -> - Sinon.spy @conn.parser, 'end' - @tracker.on 'end', => - expect(@conn.parser.end).to.have.been.calledOnce - done() - @tracker.end() - - it "should not cause an error to be emit", (done) -> - spy = Sinon.spy() - @tracker.on 'error', spy - @tracker.on 'end', -> - expect(spy).to.not.have.been.called - done() - @tracker.end() diff --git a/test/adb/tracker.js b/test/adb/tracker.js new file mode 100644 index 00000000..bcd29264 --- /dev/null +++ b/test/adb/tracker.js @@ -0,0 +1,197 @@ +var Chai, HostTrackDevicesCommand, Parser, Protocol, Sinon, Stream, Tracker, expect; + +Stream = require('stream'); + +Sinon = require('sinon'); + +Chai = require('chai'); + +Chai.use(require('sinon-chai')); + +expect = require('chai').expect; + +Parser = require('../../src/adb/parser'); + +Tracker = require('../../src/adb/tracker'); + +Protocol = require('../../src/adb/protocol'); + +HostTrackDevicesCommand = require('../../src/adb/command/host/trackdevices'); + +describe('Tracker', function() { + beforeEach(function() { + this.writer = new Stream.PassThrough; + this.conn = { + parser: new Parser(this.writer), + end: function() {} + }; + this.cmd = new HostTrackDevicesCommand(this.conn); + return this.tracker = new Tracker(this.cmd); + }); + it("should emit 'add' when a device is added", function(done) { + var device1, device2, spy; + spy = Sinon.spy(); + this.tracker.on('add', spy); + device1 = { + id: 'a', + type: 'device' + }; + device2 = { + id: 'b', + type: 'device' + }; + this.tracker.update([device1, device2]); + expect(spy).to.have.been.calledTwice; + expect(spy).to.have.been.calledWith(device1); + expect(spy).to.have.been.calledWith(device2); + return done(); + }); + it("should emit 'remove' when a device is removed", function(done) { + var device1, device2, spy; + spy = Sinon.spy(); + this.tracker.on('remove', spy); + device1 = { + id: 'a', + type: 'device' + }; + device2 = { + id: 'b', + type: 'device' + }; + this.tracker.update([device1, device2]); + this.tracker.update([device1]); + expect(spy).to.have.been.calledOnce; + expect(spy).to.have.been.calledWith(device2); + return done(); + }); + it("should emit 'change' when a device changes", function(done) { + var deviceNew, deviceOld, spy; + spy = Sinon.spy(); + this.tracker.on('change', spy); + deviceOld = { + id: 'a', + type: 'device' + }; + deviceNew = { + id: 'a', + type: 'offline' + }; + this.tracker.update([deviceOld]); + this.tracker.update([deviceNew]); + expect(spy).to.have.been.calledOnce; + expect(spy).to.have.been.calledWith(deviceNew, deviceOld); + return done(); + }); + it("should emit 'changeSet' with all changes", function(done) { + var device1, device2, device3, device3New, device4, spy; + spy = Sinon.spy(); + this.tracker.on('changeSet', spy); + device1 = { + id: 'a', + type: 'device' + }; + device2 = { + id: 'b', + type: 'device' + }; + device3 = { + id: 'c', + type: 'device' + }; + device3New = { + id: 'c', + type: 'offline' + }; + device4 = { + id: 'd', + type: 'offline' + }; + this.tracker.update([device1, device2, device3]); + this.tracker.update([device1, device3New, device4]); + expect(spy).to.have.been.calledTwice; + expect(spy).to.have.been.calledWith({ + added: [device1, device2, device3], + changed: [], + removed: [] + }); + expect(spy).to.have.been.calledWith({ + added: [device4], + changed: [device3New], + removed: [device2] + }); + return done(); + }); + it("should emit 'error' and 'end' when connection ends", function(done) { + this.tracker.on('error', (function(_this) { + return function() { + return _this.tracker.on('end', function() { + return done(); + }); + }; + })(this)); + return this.writer.end(); + }); + it("should read devices from socket", function(done) { + var device1, device2, device3, device3New, device4, spy; + spy = Sinon.spy(); + this.tracker.on('changeSet', spy); + device1 = { + id: 'a', + type: 'device' + }; + device2 = { + id: 'b', + type: 'device' + }; + device3 = { + id: 'c', + type: 'device' + }; + device3New = { + id: 'c', + type: 'offline' + }; + device4 = { + id: 'd', + type: 'offline' + }; + this.writer.write(Protocol.encodeData("a\tdevice\nb\tdevice\nc\tdevice")); + this.writer.write(Protocol.encodeData("a\tdevice\nc\toffline\nd\toffline")); + return setTimeout(function() { + expect(spy).to.have.been.calledTwice; + expect(spy).to.have.been.calledWith({ + added: [device1, device2, device3], + changed: [], + removed: [] + }); + expect(spy).to.have.been.calledWith({ + added: [device4], + changed: [device3New], + removed: [device2] + }); + return done(); + }, 10); + }); + return describe('end()', function() { + it("should close the connection", function(done) { + Sinon.spy(this.conn.parser, 'end'); + this.tracker.on('end', (function(_this) { + return function() { + expect(_this.conn.parser.end).to.have.been.calledOnce; + return done(); + }; + })(this)); + return this.tracker.end(); + }); + return it("should not cause an error to be emit", function(done) { + var spy; + spy = Sinon.spy(); + this.tracker.on('error', spy); + this.tracker.on('end', function() { + expect(spy).to.not.have.been.called; + return done(); + }); + return this.tracker.end(); + }); + }); +}); diff --git a/test/adb/util.coffee b/test/adb/util.coffee deleted file mode 100644 index 3838b2f2..00000000 --- a/test/adb/util.coffee +++ /dev/null @@ -1,33 +0,0 @@ -Stream = require 'stream' -Promise = require 'bluebird' -Sinon = require 'sinon' -Chai = require 'chai' -Chai.use require 'sinon-chai' -{expect} = require 'chai' - -util = require '../../src/adb/util' - -describe 'util', -> - - describe 'readAll(stream)', -> - - it "should return a cancellable Promise", (done) -> - stream = new Stream.PassThrough - promise = util.readAll(stream) - expect(promise).to.be.an.instanceOf Promise - expect(promise.isCancellable()).to.be.true - promise.catch Promise.CancellationError, (err) -> - done() - promise.cancel() - - it "should read all remaining content until the stream ends", (done) -> - stream = new Stream.PassThrough - util.readAll(stream) - .then (buf) -> - expect(buf.length).to.equal 3 - expect(buf.toString()).to.equal 'FOO' - done() - stream.write 'F' - stream.write 'O' - stream.write 'O' - stream.end() diff --git a/test/adb/util.js b/test/adb/util.js new file mode 100644 index 00000000..8d662fb1 --- /dev/null +++ b/test/adb/util.js @@ -0,0 +1,44 @@ +var Chai, Promise, Sinon, Stream, expect, util; + +Stream = require('stream'); + +Promise = require('bluebird'); + +Sinon = require('sinon'); + +Chai = require('chai'); + +Chai.use(require('sinon-chai')); + +expect = require('chai').expect; + +util = require('../../src/adb/util'); + +describe('util', function() { + return describe('readAll(stream)', function() { + it("should return a cancellable Promise", function(done) { + var promise, stream; + stream = new Stream.PassThrough; + promise = util.readAll(stream); + expect(promise).to.be.an.instanceOf(Promise); + expect(promise.isCancellable()).to.be["true"]; + promise["catch"](Promise.CancellationError, function(err) { + return done(); + }); + return promise.cancel(); + }); + return it("should read all remaining content until the stream ends", function(done) { + var stream; + stream = new Stream.PassThrough; + util.readAll(stream).then(function(buf) { + expect(buf.length).to.equal(3); + expect(buf.toString()).to.equal('FOO'); + return done(); + }); + stream.write('F'); + stream.write('O'); + stream.write('O'); + return stream.end(); + }); + }); +}); diff --git a/test/mock/connection.coffee b/test/mock/connection.coffee deleted file mode 100644 index 31d38687..00000000 --- a/test/mock/connection.coffee +++ /dev/null @@ -1,17 +0,0 @@ -Parser = require '../../src/adb/parser' -MockDuplex = require './duplex' - -class MockConnection - constructor: -> - @socket = new MockDuplex - @parser = new Parser @socket - - end: -> - @socket.causeEnd() - return this - - write: -> - @socket.write.apply @socket, arguments - return this - -module.exports = MockConnection diff --git a/test/mock/connection.js b/test/mock/connection.js new file mode 100644 index 00000000..11872f88 --- /dev/null +++ b/test/mock/connection.js @@ -0,0 +1,27 @@ +var MockConnection, MockDuplex, Parser; + +Parser = require('../../src/adb/parser'); + +MockDuplex = require('./duplex'); + +MockConnection = (function() { + function MockConnection() { + this.socket = new MockDuplex; + this.parser = new Parser(this.socket); + } + + MockConnection.prototype.end = function() { + this.socket.causeEnd(); + return this; + }; + + MockConnection.prototype.write = function() { + this.socket.write.apply(this.socket, arguments); + return this; + }; + + return MockConnection; + +})(); + +module.exports = MockConnection; diff --git a/test/mock/duplex.coffee b/test/mock/duplex.coffee deleted file mode 100644 index 19d68ae8..00000000 --- a/test/mock/duplex.coffee +++ /dev/null @@ -1,25 +0,0 @@ -Stream = require 'stream' - -class MockDuplex extends Stream.Duplex - _read: (size) -> - - _write: (chunk, encoding, callback) -> - @emit 'write', chunk, encoding, callback - callback null - return - - causeRead: (chunk) -> - unless Buffer.isBuffer chunk - chunk = new Buffer chunk - this.push chunk - return - - causeEnd: -> - this.push null - return - - end: -> - this.causeEnd() # In order to better emulate socket streams - Stream.Duplex.prototype.end.apply this, arguments - -module.exports = MockDuplex diff --git a/test/mock/duplex.js b/test/mock/duplex.js new file mode 100644 index 00000000..9eef0037 --- /dev/null +++ b/test/mock/duplex.js @@ -0,0 +1,41 @@ +var MockDuplex, Stream, + extend = function(child, parent) { for (var key in parent) { if (hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; }, + hasProp = {}.hasOwnProperty; + +Stream = require('stream'); + +MockDuplex = (function(superClass) { + extend(MockDuplex, superClass); + + function MockDuplex() { + return MockDuplex.__super__.constructor.apply(this, arguments); + } + + MockDuplex.prototype._read = function(size) {}; + + MockDuplex.prototype._write = function(chunk, encoding, callback) { + this.emit('write', chunk, encoding, callback); + callback(null); + }; + + MockDuplex.prototype.causeRead = function(chunk) { + if (!Buffer.isBuffer(chunk)) { + chunk = new Buffer(chunk); + } + this.push(chunk); + }; + + MockDuplex.prototype.causeEnd = function() { + this.push(null); + }; + + MockDuplex.prototype.end = function() { + this.causeEnd(); + return Stream.Duplex.prototype.end.apply(this, arguments); + }; + + return MockDuplex; + +})(Stream.Duplex); + +module.exports = MockDuplex;