From 1ce5cf89948029621a72207da614797bf6f2a1a6 Mon Sep 17 00:00:00 2001 From: doron zarhi Date: Thu, 3 Jun 2021 16:59:13 +0300 Subject: [PATCH] cli: make almost all outputs json colored --- pymobiledevice3/cli/apps.py | 13 +++-- pymobiledevice3/cli/cli_common.py | 10 ++-- pymobiledevice3/cli/developer.py | 12 ++--- pymobiledevice3/cli/diagnostics.py | 7 +-- pymobiledevice3/cli/list_devices.py | 4 +- pymobiledevice3/cli/mounter.py | 51 ++++++++++++++----- pymobiledevice3/cli/profile.py | 10 ++-- pymobiledevice3/cli/ps.py | 9 ++-- pymobiledevice3/exceptions.py | 10 ++++ .../services/mobile_image_mounter.py | 11 ++-- 10 files changed, 87 insertions(+), 50 deletions(-) diff --git a/pymobiledevice3/cli/apps.py b/pymobiledevice3/cli/apps.py index abbcdfd0f..6b3db3f2d 100644 --- a/pymobiledevice3/cli/apps.py +++ b/pymobiledevice3/cli/apps.py @@ -1,8 +1,6 @@ -from pprint import pprint - import click -from pymobiledevice3.cli.cli_common import Command +from pymobiledevice3.cli.cli_common import Command, print_json from pymobiledevice3.services.house_arrest import HouseArrestService from pymobiledevice3.services.installation_proxy import InstallationProxyService @@ -20,30 +18,31 @@ def apps(): @apps.command('list', cls=Command) +@click.option('--nocolor', is_flag=True) @click.option('-u', '--user', is_flag=True, help='include user apps') @click.option('-s', '--system', is_flag=True, help='include system apps') -def apps_list(lockdown, user, system): +def apps_list(lockdown, nocolor, user, system): """ list installed apps """ app_types = [] if user: app_types.append('User') if system: app_types.append('System') - pprint(InstallationProxyService(lockdown=lockdown).get_apps(app_types)) + print_json(InstallationProxyService(lockdown=lockdown).get_apps(app_types), colored=not nocolor) @apps.command('uninstall', cls=Command) @click.argument('bundle_id') def uninstall(lockdown, bundle_id): """ uninstall app by given bundle_id """ - pprint(InstallationProxyService(lockdown=lockdown).uninstall(bundle_id)) + InstallationProxyService(lockdown=lockdown).uninstall(bundle_id) @apps.command('install', cls=Command) @click.argument('ipa_path', type=click.Path(exists=True)) def install(lockdown, ipa_path): """ install given .ipa """ - pprint(InstallationProxyService(lockdown=lockdown).install_from_local(ipa_path)) + InstallationProxyService(lockdown=lockdown).install_from_local(ipa_path) @apps.command('afc', cls=Command) diff --git a/pymobiledevice3/cli/cli_common.py b/pymobiledevice3/cli/cli_common.py index abdb3bf6f..a2273ac99 100644 --- a/pymobiledevice3/cli/cli_common.py +++ b/pymobiledevice3/cli/cli_common.py @@ -1,6 +1,5 @@ import json import os -from pprint import pprint import click from pygments import highlight, lexers, formatters @@ -8,13 +7,14 @@ from pymobiledevice3.lockdown import LockdownClient -def print_object(buf, colored=True, default=None): +def print_json(buf, colored=True, default=None): + formatted_json = json.dumps(buf, sort_keys=True, indent=4, default=default) if colored: - formatted_json = json.dumps(buf, sort_keys=True, indent=4, default=default) - colorful_json = highlight(formatted_json, lexers.JsonLexer(), formatters.TerminalFormatter()) + colorful_json = highlight(formatted_json, lexers.JsonLexer(), + formatters.TerminalTrueColorFormatter(style='stata-dark')) print(colorful_json) else: - pprint(buf) + print(formatted_json) class Command(click.Command): diff --git a/pymobiledevice3/cli/developer.py b/pymobiledevice3/cli/developer.py index b70030551..57a42dfc0 100644 --- a/pymobiledevice3/cli/developer.py +++ b/pymobiledevice3/cli/developer.py @@ -11,7 +11,7 @@ import click from termcolor import colored -from pymobiledevice3.cli.cli_common import print_object, Command +from pymobiledevice3.cli.cli_common import print_json, Command from pymobiledevice3.exceptions import DvtDirListError from pymobiledevice3.lockdown import LockdownClient from pymobiledevice3.services.dvt.dvt_secure_socket_proxy import DvtSecureSocketProxyService @@ -64,7 +64,7 @@ def proclist(lockdown, nocolor): if 'startDate' in process: process['startDate'] = str(process['startDate']) - print_object(processes, colored=not nocolor) + print_json(processes, colored=not nocolor) @developer.command('applist', cls=Command) @@ -73,7 +73,7 @@ def applist(lockdown, nocolor): """ show application list """ with DvtSecureSocketProxyService(lockdown=lockdown) as dvt: apps = ApplicationListing(dvt).applist() - print_object(apps, colored=not nocolor) + print_json(apps, colored=not nocolor) @developer.command('kill', cls=Command) @@ -156,7 +156,7 @@ def device_information(lockdown, nocolor): """ Print system information. """ with DvtSecureSocketProxyService(lockdown=lockdown) as dvt: device_info = DeviceInfo(dvt) - print_object({ + print_json({ 'system': device_info.system_information(), 'hardware': device_info.hardware_information(), 'network': device_info.network_information(), @@ -371,7 +371,7 @@ def stackshot(lockdown, out, nocolor): if out is not None: json.dump(data, out, indent=4) else: - print_object(data, colored=not nocolor) + print_json(data, colored=not nocolor) def parse_live_print(tap, pid, show_tid, parsed): @@ -427,7 +427,7 @@ def trace_codes(lockdown, nocolor): """ Print system information. """ with DvtSecureSocketProxyService(lockdown=lockdown) as dvt: device_info = DeviceInfo(dvt) - print_object({hex(k): v for k, v in device_info.trace_codes().items()}, colored=not nocolor) + print_json({hex(k): v for k, v in device_info.trace_codes().items()}, colored=not nocolor) @developer.command('oslog', cls=Command) diff --git a/pymobiledevice3/cli/diagnostics.py b/pymobiledevice3/cli/diagnostics.py index d51c67225..0e49d7cd2 100644 --- a/pymobiledevice3/cli/diagnostics.py +++ b/pymobiledevice3/cli/diagnostics.py @@ -2,7 +2,7 @@ import click -from pymobiledevice3.cli.cli_common import Command +from pymobiledevice3.cli.cli_common import Command, print_json from pymobiledevice3.services.diagnostics import DiagnosticsService @@ -37,9 +37,10 @@ def diagnostics_sleep(lockdown): @diagnostics.command('info', cls=Command) -def diagnostics_info(lockdown): +@click.option('--nocolor', is_flag=True) +def diagnostics_info(lockdown, nocolor): """ get diagnostics info """ - pprint(DiagnosticsService(lockdown=lockdown).info()) + print_json(DiagnosticsService(lockdown=lockdown).info(), colored=not nocolor) @diagnostics.command('ioregistry', cls=Command) diff --git a/pymobiledevice3/cli/list_devices.py b/pymobiledevice3/cli/list_devices.py index 724bec772..0e5420693 100644 --- a/pymobiledevice3/cli/list_devices.py +++ b/pymobiledevice3/cli/list_devices.py @@ -1,7 +1,7 @@ import click from pymobiledevice3 import usbmux -from pymobiledevice3.cli.cli_common import print_object +from pymobiledevice3.cli.cli_common import print_json from pymobiledevice3.lockdown import LockdownClient @@ -23,4 +23,4 @@ def list_devices(nocolor): lockdown = LockdownClient(udid) connected_devices.append(lockdown.all_values) - print_object(connected_devices, colored=not nocolor, default=lambda x: '') + print_json(connected_devices, colored=not nocolor, default=lambda x: '') diff --git a/pymobiledevice3/cli/mounter.py b/pymobiledevice3/cli/mounter.py index 5ede03bce..a78c15425 100644 --- a/pymobiledevice3/cli/mounter.py +++ b/pymobiledevice3/cli/mounter.py @@ -1,12 +1,12 @@ -from pprint import pprint import logging import re import click -from pymobiledevice3.cli.cli_common import Command +from pymobiledevice3.cli.cli_common import Command, print_json +from pymobiledevice3.exceptions import DeviceVersionFormatError, PyMobileDevice3Exception, NotMountedError, \ + UnsupportedCommandError from pymobiledevice3.services.mobile_image_mounter import MobileImageMounterService -from pymobiledevice3.exceptions import DeviceVersionFormatError @click.group() @@ -22,16 +22,31 @@ def mounter(): @mounter.command('list', cls=Command) -def mounter_list(lockdown): - """ lookup mounter image type """ - pprint(MobileImageMounterService(lockdown=lockdown).list_images()) +@click.option('--nocolor', is_flag=True) +def mounter_list(lockdown, nocolor): + """ list all mounted images """ + output = [] + + images = MobileImageMounterService(lockdown=lockdown).list_images()['EntryList'] + for image in images: + image['ImageSignature'] = image['ImageSignature'].hex() + output.append(image) + + print_json(output, colored=not nocolor) @mounter.command('lookup', cls=Command) +@click.option('--nocolor', is_flag=True) @click.argument('image_type') -def mounter_lookup(lockdown, image_type): +def mounter_lookup(lockdown, nocolor, image_type): """ lookup mounter image type """ - pprint(MobileImageMounterService(lockdown=lockdown).lookup_image(image_type)) + output = [] + + signatures = MobileImageMounterService(lockdown=lockdown).lookup_image(image_type)['ImageSignature'] + for signature in signatures: + output.append(signature.hex()) + + print_json(output, colored=not nocolor) @mounter.command('umount', cls=Command) @@ -40,12 +55,18 @@ def mounter_umount(lockdown): image_type = 'Developer' mount_path = '/Developer' image_mounter = MobileImageMounterService(lockdown=lockdown) - image_mounter.umount(image_type, mount_path, b'') + try: + image_mounter.umount(image_type, mount_path, b'') + logging.info('DeveloperDiskImage umounted successfully') + except NotMountedError: + logging.error('DeveloperDiskImage isn\'t currently mounted') + except UnsupportedCommandError: + logging.error('Your iOS version doesn\'t support this command') def sanitize_version(version): try: - return re.match('\d*\.\d*', version)[0] + return re.match(r'\d*\.\d*', version)[0] except TypeError as e: raise DeviceVersionFormatError from e @@ -80,5 +101,11 @@ def mounter_mount(lockdown, image, signature, xcode, version): image_mounter = MobileImageMounterService(lockdown=lockdown) image_mounter.upload_image(image_type, image, signature) - image_mounter.mount(image_type, signature) - logging.info('DeveloperDiskImage mounted successfully') + try: + image_mounter.mount(image_type, signature) + logging.info('DeveloperDiskImage mounted successfully') + except PyMobileDevice3Exception as e: + if 'is already mounted' in str(e): + logging.error('DeveloperDiskImage is already mounted') + else: + raise e diff --git a/pymobiledevice3/cli/profile.py b/pymobiledevice3/cli/profile.py index 412b4e62d..d02cf519e 100644 --- a/pymobiledevice3/cli/profile.py +++ b/pymobiledevice3/cli/profile.py @@ -1,8 +1,6 @@ -from pprint import pprint - import click -from pymobiledevice3.cli.cli_common import Command +from pymobiledevice3.cli.cli_common import Command, print_json from pymobiledevice3.services.mobile_config import MobileConfigService @@ -21,18 +19,18 @@ def profile_group(): @profile_group.command('list', cls=Command) def profile_list(lockdown): """ list installed profiles """ - pprint(MobileConfigService(lockdown=lockdown).get_profile_list()) + print_json(MobileConfigService(lockdown=lockdown).get_profile_list()) @profile_group.command('install', cls=Command) @click.argument('profile', type=click.File('rb')) def profile_install(lockdown, profile): """ install given profile file """ - pprint(MobileConfigService(lockdown=lockdown).install_profile(profile.read())) + print_json(MobileConfigService(lockdown=lockdown).install_profile(profile.read())) @profile_group.command('remove', cls=Command) @click.argument('name') def profile_remove(lockdown, name): """ remove profile by name """ - pprint(MobileConfigService(lockdown=lockdown).remove_profile(name)) + print_json(MobileConfigService(lockdown=lockdown).remove_profile(name)) diff --git a/pymobiledevice3/cli/ps.py b/pymobiledevice3/cli/ps.py index 6dbd5d415..996defa22 100644 --- a/pymobiledevice3/cli/ps.py +++ b/pymobiledevice3/cli/ps.py @@ -1,8 +1,6 @@ -from pprint import pprint - import click -from pymobiledevice3.cli.cli_common import Command +from pymobiledevice3.cli.cli_common import Command, print_json from pymobiledevice3.services.os_trace import OsTraceService @@ -13,6 +11,7 @@ def cli(): @cli.command(cls=Command) -def ps(lockdown): +@click.option('--nocolor', is_flag=True) +def ps(lockdown, nocolor): """ show process list """ - pprint(OsTraceService(lockdown=lockdown).get_pid_list()) + print_json(OsTraceService(lockdown=lockdown).get_pid_list(), colored=not nocolor) diff --git a/pymobiledevice3/exceptions.py b/pymobiledevice3/exceptions.py index cbeb451a0..6f589294c 100644 --- a/pymobiledevice3/exceptions.py +++ b/pymobiledevice3/exceptions.py @@ -78,3 +78,13 @@ class DvtException(PyMobileDevice3Exception): class DvtDirListError(DvtException): """ Raise when directory listing fails. """ pass + + +class NotMountedError(PyMobileDevice3Exception): + """ Given image for umount wasn't mounted in the first place """ + pass + + +class UnsupportedCommandError(PyMobileDevice3Exception): + """ Given command isn't supported for this iOS version """ + pass diff --git a/pymobiledevice3/services/mobile_image_mounter.py b/pymobiledevice3/services/mobile_image_mounter.py index 817b6da49..86f3a23e4 100755 --- a/pymobiledevice3/services/mobile_image_mounter.py +++ b/pymobiledevice3/services/mobile_image_mounter.py @@ -1,6 +1,6 @@ import logging -from pymobiledevice3.exceptions import PyMobileDevice3Exception +from pymobiledevice3.exceptions import PyMobileDevice3Exception, NotMountedError, UnsupportedCommandError from pymobiledevice3.lockdown import LockdownClient @@ -36,9 +36,12 @@ def umount(self, image_type, mount_path, signature): 'MountPath': mount_path, 'ImageSignature': signature}) response = self.service.recv_plist() - - if response.get('Error'): - raise PyMobileDevice3Exception('unsupported command') + error = response.get('Error') + if error: + if error == 'UnknownCommand': + raise UnsupportedCommandError() + else: + raise NotMountedError() def mount(self, image_type, signature): """ Upload image into device. """