diff --git a/adb/adb_commands.py b/adb/adb_commands.py index 0996552..54188f7 100644 --- a/adb/adb_commands.py +++ b/adb/adb_commands.py @@ -109,13 +109,13 @@ def Devices(cls): def GetState(self): return self._device_state - def Install(self, apk_path, destination_dir=None, timeout_ms=None): + def Install(self, apk_path, destination_dir='', timeout_ms=None): """Install an apk to the device. Doesn't support verifier file, instead allows destination directory to be overridden. - Arguments: + Args: apk_path: Local path to apk to install. destination_dir: Optional destination directory. Use /system/app/ for persistent applications. @@ -135,10 +135,10 @@ def Install(self, apk_path, destination_dir=None, timeout_ms=None): def Push(self, source_file, device_filename, mtime='0', timeout_ms=None): """Push a file or directory to the device. - Arguments: + Args: source_file: Either a filename, a directory or file-like object to push to the device. - device_filename: The filename on the device to write to. + device_filename: Destination on the device to write to. mtime: Optional, modification time to set on the file. timeout_ms: Expected timeout for any part of the push. """ @@ -158,11 +158,11 @@ def Push(self, source_file, device_filename, mtime='0', timeout_ms=None): mtime=int(mtime)) connection.Close() - def Pull(self, device_filename, dest_file=None, timeout_ms=None): + def Pull(self, device_filename, dest_file='', timeout_ms=None): """Pull a file from the device. - Arguments: - device_filename: The filename on the device to pull. + Args: + device_filename: Filename on the device to pull. dest_file: If set, a filename or writable file-like object. timeout_ms: Expected timeout for any part of the pull. @@ -192,7 +192,11 @@ def Stat(self, device_filename): return mode, size, mtime def List(self, device_path): - """Return a directory listing of the given path.""" + """Return a directory listing of the given path. + + Args: + device_path: Directory to list. + """ connection = self.protocol_handler.Open(self.handle, destination='sync:') listing = self.filesync_handler.List(connection, device_path) connection.Close() @@ -201,7 +205,8 @@ def List(self, device_path): def Reboot(self, destination=''): """Reboot the device. - Specify 'bootloader' for fastboot. + Args: + destination: Specify 'bootloader' for fastboot. """ self.protocol_handler.Open(self.handle, 'reboot:%s' % destination) @@ -227,7 +232,7 @@ def StreamingShell(self, command, timeout_ms=None): """Run command on the device, yielding each line of output. Args: - command: the command to run on the target. + command: Command to run on the target. timeout_ms: Maximum time to allow the command to run. Yields: @@ -238,7 +243,11 @@ def StreamingShell(self, command, timeout_ms=None): timeout_ms=timeout_ms) def Logcat(self, options, timeout_ms=None): - """Run 'shell logcat' and stream the output to stdout.""" + """Run 'shell logcat' and stream the output to stdout. + + Args: + options: Arguments to pass to 'logcat'. + """ return self.protocol_handler.StreamingCommand( self.handle, service='shell', command='logcat %s' % options, timeout_ms=timeout_ms) diff --git a/adb/adb_debug.py b/adb/adb_debug.py index 839ce72..dc34321 100755 --- a/adb/adb_debug.py +++ b/adb/adb_debug.py @@ -12,15 +12,16 @@ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. -"""ADB debugging binary. -Call it similar to how you call android's adb. Takes either --serial or ---port_path to connect to a device. -""" +"""Daemon-less ADB client in python.""" + +import argparse +import logging import os +import pipes +import stat import sys - -import gflags +import time import adb_commands import common_cli @@ -36,34 +37,147 @@ rsa_signer = None -gflags.ADOPT_module_key_flags(common_cli) +def Devices(args): + """Lists the available devices. + + Mimics 'adb devices' output: + List of devices attached + 015DB7591102001A device 1,2 + """ + for d in adb_commands.AdbCommands.Devices(): + if args.output_port_path: + print('%s\tdevice\t%s' % ( + d.serial_number, ','.join(str(p) for p in d.port_path))) + else: + print('%s\tdevice' % d.serial_number) + return 0 + + +def List(self, device_path): + """Prints a directory listing. + + Args: + device_path: Directory to list. + """ + files = adb_commands.AdbCommands.List(self, device_path) + files.sort(key=lambda x: x.filename) + maxname = max(len(f.filename) for f in files) + maxsize = max(len(str(f.size)) for f in files) + for f in files: + mode = ( + ('d' if stat.S_ISDIR(f.mode) else '-') + + ('r' if f.mode & stat.S_IRUSR else '-') + + ('w' if f.mode & stat.S_IWUSR else '-') + + ('x' if f.mode & stat.S_IXUSR else '-') + + ('r' if f.mode & stat.S_IRGRP else '-') + + ('w' if f.mode & stat.S_IWGRP else '-') + + ('x' if f.mode & stat.S_IXGRP else '-') + + ('r' if f.mode & stat.S_IROTH else '-') + + ('w' if f.mode & stat.S_IWOTH else '-') + + ('x' if f.mode & stat.S_IXOTH else '-')) + t = time.gmtime(f.mtime) + yield '%s %*d %04d-%02d-%02d %02d:%02d:%02d %-*s\n' % ( + mode, maxsize, f.size, + t.tm_year, t.tm_mon, t.tm_mday, t.tm_hour, t.tm_min, t.tm_sec, + maxname, f.filename) + + +def Logcat(self, *options): + return adb_commands.AdbCommands.StreamingShell( + self, 'logcat ' + ' '.join(pipes.quote(o) for o in options)) + + +Logcat.__doc__ = adb_commands.AdbCommands.Logcat.__doc__ + + +def Shell(self, *command): + """Runs a command on the device and prints the stdout. + + Args: + command: Command to run on the target. + """ + return adb_commands.AdbCommands.StreamingShell( + self, ' '.join(pipes.quote(o) for o in command)) + + +def main(): + common = common_cli.GetCommonArguments() + common.add_argument( + '--rsa_key_path', action='append', default=[], + metavar='~/.android/adbkey', + help='RSA key(s) to use, use multiple times to load mulitple keys') + common.add_argument( + '--auth_timeout_s', default=60., metavar='60', type=int, + help='Seconds to wait for the dialog to be accepted when using ' + 'authenticated ADB.') + device = common_cli.GetDeviceArguments() + parents = [common, device] + + parser = argparse.ArgumentParser( + description=sys.modules[__name__].__doc__, parents=[common]) + subparsers = parser.add_subparsers(title='Commands', dest='command_name') -gflags.DEFINE_multistring('rsa_key_path', '~/.android/adbkey', - 'RSA key(s) to use') -gflags.DEFINE_integer('auth_timeout_s', 60, - 'Seconds to wait for the dialog to be accepted when using ' - 'authenticated ADB.') -FLAGS = gflags.FLAGS + subparser = subparsers.add_parser( + name='help', help='Prints the commands available') + subparser = subparsers.add_parser( + name='devices', help='Lists the available devices', parents=[common]) + subparser.add_argument( + '--output_port_path', action='store_true', + help='Outputs the port_path alongside the serial') + subparser = common_cli.MakeSubparser( + subparsers, parents, adb_commands.AdbCommands.Install) + subparser = common_cli.MakeSubparser(subparsers, parents, List) + subparser = common_cli.MakeSubparser(subparsers, parents, Logcat) + subparser = common_cli.MakeSubparser( + subparsers, parents, adb_commands.AdbCommands.Push, + {'source_file': 'Filename or directory to push to the device.'}) + subparser = common_cli.MakeSubparser( + subparsers, parents, adb_commands.AdbCommands.Pull, + { + 'dest_file': 'Filename to write to on the host, if not specified, ' + 'prints the content to stdout.', + }) + subparser = common_cli.MakeSubparser( + subparsers, parents, adb_commands.AdbCommands.Reboot) + subparser = common_cli.MakeSubparser( + subparsers, parents, adb_commands.AdbCommands.RebootBootloader) + subparser = common_cli.MakeSubparser( + subparsers, parents, adb_commands.AdbCommands.Remount) + subparser = common_cli.MakeSubparser( + subparsers, parents, adb_commands.AdbCommands.Root) + subparser = common_cli.MakeSubparser(subparsers, parents, Shell) -def GetRSAKwargs(): - if FLAGS.rsa_key_path: - if rsa_signer is None: - print >> sys.stderr, 'Please install either M2Crypto or python-rsa' - sys.exit(1) - return { - 'rsa_keys': [rsa_signer(os.path.expanduser(path)) - for path in FLAGS.rsa_key_path], - 'auth_timeout_ms': int(FLAGS.auth_timeout_s * 1000.0), - } - return {} + if len(sys.argv) == 1: + parser.print_help() + return 2 + args = parser.parse_args() + if args.verbose: + logging.basicConfig(level=logging.DEBUG) + if not args.rsa_key_path: + default = os.path.expanduser('~/.android/adbkey') + if os.path.isfile(default): + args.rsa_key_path = [default] + if args.rsa_key_path and not rsa_signer: + parser.error('Please install either M2Crypto or python-rsa') + # Hacks so that the generated doc is nicer. + if args.command_name == 'devices': + return Devices(args) + if args.command_name == 'help': + parser.print_help() + return 0 + if args.command_name == 'logcat': + args.positional = args.options + elif args.command_name == 'shell': + args.positional = args.command -def main(argv): - common_cli.StartCli( - argv, adb_commands.AdbCommands.ConnectDevice, - list_callback=adb_commands.AdbCommands.Devices, **GetRSAKwargs()) + return common_cli.StartCli( + args, + adb_commands.AdbCommands.ConnectDevice, + auth_timeout_ms=args.auth_timeout_s * 1000, + rsa_keys=[rsa_signer(path) for path in args.rsa_key_path]) if __name__ == '__main__': - main(FLAGS(sys.argv)) + sys.exit(main()) diff --git a/adb/common_cli.py b/adb/common_cli.py index 435e99f..ebbec8d 100644 --- a/adb/common_cli.py +++ b/adb/common_cli.py @@ -19,144 +19,141 @@ outputting the results. """ +import argparse import cStringIO import inspect +import logging import re import sys +import textwrap import types -import gflags - import usb_exceptions -gflags.DEFINE_integer('timeout_ms', 10000, 'Timeout in milliseconds.') -gflags.DEFINE_list('port_path', [], 'USB port path integers (eg 1,2 or 2,1,1)') -gflags.DEFINE_string('serial', None, 'Device serial to look for (host:port or USB serial)', short_name='s') - -gflags.DEFINE_bool('output_port_path', False, - 'Affects the devices command only, outputs the port_path ' - 'alongside the serial if true.') - -FLAGS = gflags.FLAGS - -_BLACKLIST = { - 'Connect', - 'Close', - 'ConnectDevice', - 'DeviceIsAvailable', -} - - -def Uncamelcase(name): - parts = re.split(r'([A-Z][a-z]+)', name)[1:-1:2] - return ('-'.join(parts)).lower() - - -def Camelcase(name): - return name.replace('-', ' ').title().replace(' ', '') - -def Usage(adb_dev): - methods = inspect.getmembers(adb_dev, inspect.ismethod) - print 'Methods:' - for name, method in methods: - if name.startswith('_'): - continue - if not method.__doc__: - continue - if name in _BLACKLIST: - continue - - argspec = inspect.getargspec(method) - args = argspec.args[1:] or '' - # Surround default'd arguments with [] - defaults = argspec.defaults or [] - if args: - args = (args[:-len(defaults)] + - ['[%s]' % arg for arg in args[-len(defaults):]]) - - args = ' ' + ' '.join(args) - - print ' %s%s:' % (Uncamelcase(name), args) - print ' %s' % method.__doc__ - - -def StartCli(argv, device_callback, kwarg_callback=None, list_callback=None, - **device_kwargs): - """Starts a common CLI interface for this usb path and protocol.""" - argv = argv[1:] - - if len(argv) == 1 and argv[0] == 'devices' and list_callback is not None: - # To mimic 'adb devices' output like: - # ------------------------------ - # List of devices attached - # 015DB7591102001A device - # Or with --output_port_path: - # 015DB7591102001A device 1,2 - # ------------------------------ - for device in list_callback(): - if FLAGS.output_port_path: - print '%s\tdevice\t%s' % ( - device.serial_number, - ','.join(str(port) for port in device.port_path)) +class _PortPathAction(argparse.Action): + def __call__(self, parser, namespace, values, option_string=None): + setattr( + namespace, self.dest, + [int(i) for i in values.replace('/', ',').split(',')]) + + +class PositionalArg(argparse.Action): + def __call__(self, parser, namespace, values, option_string=None): + namespace.positional.append(values) + + +def GetDeviceArguments(): + group = argparse.ArgumentParser('Device', add_help=False) + group.add_argument( + '--timeout_ms', default=10000, type=int, metavar='10000', + help='Timeout in milliseconds.') + group.add_argument( + '--port_path', action=_PortPathAction, + help='USB port path integers (eg 1,2 or 2,1,1)') + group.add_argument( + '-s', '--serial', + help='Device serial to look for (host:port or USB serial)') + return group + + +def GetCommonArguments(): + group = argparse.ArgumentParser('Common', add_help=False) + group.add_argument('--verbose', action='store_true', help='Enable logging') + return group + + +def _DocToArgs(doc): + """Converts a docstring documenting arguments into a dict.""" + offset = None + in_arg = False + out = {} + for l in doc.splitlines(): + if l.strip() == 'Args:': + in_arg = True + elif in_arg: + if not l.strip(): + break + if offset is None: + offset = len(l) - len(l.lstrip()) + l = l[offset:] + if l[0] == ' ': + out[m.group(1)] += ' ' + l.lstrip() else: - print '%s\tdevice' % device.serial_number - return + m = re.match(r'^([a-z_]+): (.+)$', l.strip()) + out[m.group(1)] = m.group(2) + return out + + +def MakeSubparser(subparsers, parents, method, arguments=None): + """Returns an argparse subparser to create a 'subcommand' to adb.""" + name = ('-'.join(re.split(r'([A-Z][a-z]+)', method.__name__)[1:-1:2])).lower() + help = method.__doc__.splitlines()[0] + subparser = subparsers.add_parser( + name=name, description=help, help=help.rstrip('.'), parents=parents) + subparser.set_defaults(method=method, positional=[]) + argspec = inspect.getargspec(method) + offset = len(argspec.args) - len(argspec.defaults or []) - 1 + positional = [] + for i in xrange(1, len(argspec.args)): + if i > offset and argspec.defaults[i-offset-1] is None: + break + positional.append(argspec.args[i]) + args_help = _DocToArgs(method.__doc__) + defaults = [None] * offset + list(argspec.defaults or []) + if positional or argspec.varargs: + for name, default in zip(positional, defaults): + if not isinstance(default, (None.__class__, str)): + continue + subparser.add_argument( + name, help=(arguments or {}).get(name, args_help.get(name)), + default=default, nargs='?' if default is not None else None, + action=PositionalArg) + if argspec.varargs: + subparser.add_argument( + argspec.varargs, nargs=argparse.REMAINDER, + help=(arguments or {}).get(argspec.varargs, args_help.get(argspec.varargs))) + return subparser + + +def _RunMethod(dev, args, extra): + """Runs a method registered via MethodToSubparser.""" + logging.info('%s(%s)', args.method.__name__, ', '.join(args.positional)) + result = args.method(dev, *args.positional, **extra) + if result is not None: + if isinstance(result, cStringIO.OutputType): + sys.stdout.write(result.getvalue()) + elif isinstance(result, (list, types.GeneratorType)): + for r in result: + r = str(r) + sys.stdout.write(r) + if not r.endswith('\n'): + sys.stdout.write('\n') + else: + result = str(result) + sys.stdout.write(result) + if not result.endswith('\n'): + sys.stdout.write('\n') + return 0 - port_path = [int(part) for part in FLAGS.port_path] - serial = FLAGS.serial - device_kwargs.setdefault('default_timeout_ms', FLAGS.timeout_ms) +def StartCli(args, device_factory, extra=None, **device_kwargs): + """Starts a common CLI interface for this usb path and protocol.""" try: - dev = device_callback( - port_path=port_path, serial=serial, **device_kwargs) + dev = device_factory( + port_path=args.port_path, serial=args.serial, + default_timeout_ms=args.timeout_ms, **device_kwargs) except usb_exceptions.DeviceNotFoundError as e: print >> sys.stderr, 'No device found: %s' % e - return + return 1 except usb_exceptions.CommonUsbError as e: print >> sys.stderr, 'Could not connect to device: %s' % e - raise - - if not argv: - Usage(dev) - return - - kwargs = {} - - # CamelCase method names, eg reboot-bootloader -> RebootBootloader - method_name = Camelcase(argv[0]) - method = getattr(dev, method_name) - argspec = inspect.getargspec(method) - num_args = len(argspec.args) - 1 # self is the first one. - # Handle putting the remaining command line args into the last normal arg. - argv.pop(0) - - # Flags -> Keyword args - if kwarg_callback: - kwarg_callback(kwargs, argspec) - + return 1 try: - if num_args == 1: - # Only one argument, so join them all with spaces - result = method(' '.join(argv), **kwargs) - else: - result = method(*argv, **kwargs) - - if result is not None: - if isinstance(result, cStringIO.OutputType): - sys.stdout.write(result.getvalue()) - elif isinstance(result, (list, types.GeneratorType)): - for r in result: - r = str(r) - sys.stdout.write(r) - if not r.endswith('\n'): - sys.stdout.write('\n') - else: - sys.stdout.write(result) - sys.stdout.write('\n') + return _RunMethod(dev, args, extra or {}) except Exception as e: # pylint: disable=broad-except + raise sys.stdout.write(str(e)) - return + return 1 finally: dev.Close() - diff --git a/adb/fastboot.py b/adb/fastboot.py index 684d3ee..0cdf040 100644 --- a/adb/fastboot.py +++ b/adb/fastboot.py @@ -13,6 +13,7 @@ # limitations under the License. """A libusb1-based fastboot implementation.""" +import argparse import binascii import collections import cStringIO @@ -20,16 +21,9 @@ import os import struct -import gflags - import common import usb_exceptions -FLAGS = gflags.FLAGS -gflags.DEFINE_integer('fastboot_write_chunk_size_kb', 4, - 'The size of packets to write to usb, this is set to 4 ' - "for legacy reasons. We've had success with 1MB " - 'DRASTICALLY decreasing flashing times.') _LOG = logging.getLogger('fastboot') @@ -70,13 +64,15 @@ class FastbootProtocol(object): """Encapsulates the fastboot protocol.""" FINAL_HEADERS = {'OKAY', 'DATA'} - def __init__(self, usb): + def __init__(self, usb, chunk_kb=1024): """Constructs a FastbootProtocol instance. - Arguments: + Args: usb: UsbHandle instance. + chunk_kb: Packet size. For older devices, 4 may be required. """ self.usb = usb + self.chunk_kb = chunk_kb @property def usb_handle(self): @@ -111,7 +107,7 @@ def HandleDataSending(self, source_file, source_len, progress_callback=None, timeout_ms=None): """Handles the protocol for sending data to the device. - Arguments: + Args: source_file: File-object to read from for the device. source_len: Amount of data, in bytes, to send to the device. info_cb: Optional callback for text sent from the bootloader. @@ -143,7 +139,7 @@ def HandleDataSending(self, source_file, source_len, def _AcceptResponses(self, expected_header, info_cb, timeout_ms=None): """Accepts responses until the expected header or a FAIL. - Arguments: + Args: expected_header: OKAY or DATA info_cb: Optional callback for text sent from the bootloader. timeout_ms: Timeout in milliseconds to wait for each response. @@ -195,7 +191,7 @@ def _Write(self, data, length, progress_callback=None): progress = self._HandleProgress(length, progress_callback) progress.next() while length: - tmp = data.read(FLAGS.fastboot_write_chunk_size_kb * 1024) + tmp = data.read(self.chunk_kb * 1024) length -= len(tmp) self.usb.BulkWrite(tmp) @@ -205,16 +201,15 @@ def _Write(self, data, length, progress_callback=None): class FastbootCommands(object): """Encapsulates the fastboot commands.""" - protocol_handler = FastbootProtocol - def __init__(self, usb): + def __init__(self, usb, chunk_kb=1024): """Constructs a FastbootCommands instance. - Arguments: + Args: usb: UsbHandle instance. """ self._usb = usb - self._protocol = self.protocol_handler(usb) + self._protocol = FastbootProtocol(usb, chunk_kb) @property def usb_handle(self): @@ -225,12 +220,12 @@ def Close(self): @classmethod def ConnectDevice( - cls, port_path=None, serial=None, default_timeout_ms=None): + cls, port_path=None, serial=None, default_timeout_ms=None, chunk_kb=1024): """Convenience function to get an adb device from usb path or serial.""" usb = common.UsbHandle.FindAndOpen( DeviceIsAvailable, port_path=port_path, serial=serial, timeout_ms=default_timeout_ms) - return cls(usb) + return cls(usb, chunk_kb=chunk_kb) @classmethod def Devices(cls): @@ -300,7 +295,7 @@ def Flash(self, partition, timeout_ms=0, info_cb=DEFAULT_MESSAGE_CALLBACK): """Flashes the last downloaded file to the given partition. Args: - partition: Partition to flash. + partition: Partition to overwrite with the new image. timeout_ms: Optional timeout in milliseconds to wait for it to finish. info_cb: See Download. Usually no messages. @@ -311,15 +306,20 @@ def Flash(self, partition, timeout_ms=0, info_cb=DEFAULT_MESSAGE_CALLBACK): timeout_ms=timeout_ms) def Erase(self, partition, timeout_ms=None): - """Erases the given partition.""" + """Erases the given partition. + + Args: + partition: Partition to clear. + """ self._SimpleCommand('erase', arg=partition, timeout_ms=timeout_ms) def Getvar(self, var, info_cb=DEFAULT_MESSAGE_CALLBACK): """Returns the given variable's definition. Args: - var: A variable the bootloader tracks, such as version. + var: A variable the bootloader tracks. Use 'all' to get them all. info_cb: See Download. Usually no messages. + Returns: Value of var according to the current bootloader. """ @@ -329,9 +329,10 @@ def Oem(self, command, timeout_ms=None, info_cb=DEFAULT_MESSAGE_CALLBACK): """Executes an OEM command on the device. Args: - command: The command to execute, such as 'poweroff' or 'bootconfig read'. + command: Command to execute, such as 'poweroff' or 'bootconfig read'. timeout_ms: Optional timeout in milliseconds to wait for a response. info_cb: See Download. Messages vary based on command. + Returns: The final response from the device. """ @@ -342,17 +343,19 @@ def Continue(self): """Continues execution past fastboot into the system.""" return self._SimpleCommand('continue') - def Reboot(self, target_mode=None, timeout_ms=None): + def Reboot(self, target_mode='', timeout_ms=None): """Reboots the device. Args: - target_mode: Normal reboot when unspecified (or None). Can specify - other target modes, such as 'recovery' or 'bootloader'. + target_mode: Normal reboot when unspecified. Can specify other target + modes such as 'recovery' or 'bootloader'. timeout_ms: Optional timeout in milliseconds to wait for a response. + Returns: Usually the empty string. Depends on the bootloader and the target_mode. """ - return self._SimpleCommand('reboot', arg=target_mode, timeout_ms=timeout_ms) + return self._SimpleCommand( + 'reboot', arg=target_mode or None, timeout_ms=timeout_ms) def RebootBootloader(self, timeout_ms=None): """Reboots into the bootloader, usually equiv to Reboot('bootloader').""" diff --git a/adb/fastboot_debug.py b/adb/fastboot_debug.py index a9c056f..82fc24f 100755 --- a/adb/fastboot_debug.py +++ b/adb/fastboot_debug.py @@ -12,34 +12,94 @@ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. -"""Fastboot debugging binary. + +"""Fastboot in python. Call it similar to how you call android's fastboot. Call it similar to how you call android's fastboot, but this only accepts usb paths and no serials. """ + +import argparse +import inspect +import logging import sys -import gflags import progressbar import common_cli import fastboot -gflags.ADOPT_module_key_flags(common_cli) -FLAGS = gflags.FLAGS +def Devices(args): + """Lists the available devices. + + List of devices attached + 015DB7591102001A device + """ + for device in fastboot.FastbootCommands.Devices(): + print('%s\tdevice' % device.serial_number) + return 0 + + +def _InfoCb(message): + # Use an unbuffered version of stdout. + if not message.message: + return + sys.stdout.write('%s: %s\n' % (message.header, message.message)) + sys.stdout.flush() + +def main(): + common = common_cli.GetCommonArguments() + device = common_cli.GetDeviceArguments() + device.add_argument( + '--chunk', type=int, default=1024, metavar='1024', + help='Size of packets to write in Kb. For older devices, it may be ' + 'required to use 4.') + parents = [common, device] -def KwargHandler(kwargs, argspec): + parser = argparse.ArgumentParser( + description=sys.modules[__name__].__doc__, parents=[common]) + subparsers = parser.add_subparsers(title='Commands', dest='command_name') + subparser = subparsers.add_parser( + name='help', help='Prints the commands available') + subparser = subparsers.add_parser( + name='devices', help='Lists the available devices', parents=[common]) + subparser = common_cli.MakeSubparser( + subparsers, parents, fastboot.FastbootCommands.Continue) + + subparser = common_cli.MakeSubparser( + subparsers, parents, fastboot.FastbootCommands.Download, + {'source_file': 'Filename on the host to push'}) + subparser = common_cli.MakeSubparser( + subparsers, parents, fastboot.FastbootCommands.Erase) + subparser = common_cli.MakeSubparser( + subparsers, parents, fastboot.FastbootCommands.Flash) + subparser = common_cli.MakeSubparser( + subparsers, parents, fastboot.FastbootCommands.Getvar) + subparser = common_cli.MakeSubparser( + subparsers, parents, fastboot.FastbootCommands.Oem) + subparser = common_cli.MakeSubparser( + subparsers, parents, fastboot.FastbootCommands.Reboot) + + if len(sys.argv) == 1: + parser.print_help() + return 2 + + args = parser.parse_args() + if args.verbose: + logging.basicConfig(level=logging.DEBUG) + if args.command_name == 'devices': + return Devices(args) + if args.command_name == 'help': + parser.print_help() + return 0 + + kwargs = {} + argspec = inspect.getargspec(args.method) if 'info_cb' in argspec.args: - # Use an unbuffered version of stdout. - def InfoCb(message): - if not message.message: - return - sys.stdout.write('%s: %s\n' % (message.header, message.message)) - sys.stdout.flush() - kwargs['info_cb'] = InfoCb + kwargs['info_cb'] = _InfoCb if 'progress_callback' in argspec.args: bar = progressbar.ProgessBar( widgets=[progressbar.Bar(), progressbar.Percentage()]) @@ -50,13 +110,10 @@ def SetProgress(current, total): bar.finish() kwargs['progress_callback'] = SetProgress - -def main(argv): - common_cli.StartCli( - argv, fastboot.FastbootCommands.ConnectDevice, - list_callback=fastboot.FastbootCommands.Devices, - kwarg_callback=KwargHandler) + return common_cli.StartCli( + args, fastboot.FastbootCommands.ConnectDevice, chunk_kb=args.chunk, + extra=kwargs) if __name__ == '__main__': - main(FLAGS(sys.argv)) + sys.exit(main()) diff --git a/setup.py b/setup.py index 8545664..37d00c0 100644 --- a/setup.py +++ b/setup.py @@ -43,7 +43,7 @@ keywords = ['android', 'adb', 'fastboot'], - install_requires = ['python-gflags>=2.0', 'libusb1>=1.0.16', 'M2Crypto>=0.21.1'], + install_requires = ['libusb1>=1.0.16', 'M2Crypto>=0.21.1'], extra_requires = { 'fastboot': 'progressbar>=2.3'