pymobiledevice3 4.14.6__py3-none-any.whl → 7.0.6__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (164) hide show
  1. misc/plist_sniffer.py +15 -15
  2. misc/remotexpc_sniffer.py +29 -28
  3. misc/understanding_idevice_protocol_layers.md +15 -10
  4. pymobiledevice3/__main__.py +317 -127
  5. pymobiledevice3/_version.py +22 -4
  6. pymobiledevice3/bonjour.py +358 -113
  7. pymobiledevice3/ca.py +253 -16
  8. pymobiledevice3/cli/activation.py +31 -23
  9. pymobiledevice3/cli/afc.py +49 -40
  10. pymobiledevice3/cli/amfi.py +16 -21
  11. pymobiledevice3/cli/apps.py +87 -42
  12. pymobiledevice3/cli/backup.py +160 -90
  13. pymobiledevice3/cli/bonjour.py +44 -40
  14. pymobiledevice3/cli/cli_common.py +204 -198
  15. pymobiledevice3/cli/companion_proxy.py +14 -14
  16. pymobiledevice3/cli/crash.py +105 -56
  17. pymobiledevice3/cli/developer/__init__.py +62 -0
  18. pymobiledevice3/cli/developer/accessibility/__init__.py +65 -0
  19. pymobiledevice3/cli/developer/accessibility/settings.py +43 -0
  20. pymobiledevice3/cli/developer/arbitration.py +50 -0
  21. pymobiledevice3/cli/developer/condition.py +33 -0
  22. pymobiledevice3/cli/developer/core_device.py +294 -0
  23. pymobiledevice3/cli/developer/debugserver.py +244 -0
  24. pymobiledevice3/cli/developer/dvt/__init__.py +438 -0
  25. pymobiledevice3/cli/developer/dvt/core_profile_session.py +295 -0
  26. pymobiledevice3/cli/developer/dvt/simulate_location.py +56 -0
  27. pymobiledevice3/cli/developer/dvt/sysmon/__init__.py +69 -0
  28. pymobiledevice3/cli/developer/dvt/sysmon/process.py +188 -0
  29. pymobiledevice3/cli/developer/fetch_symbols.py +108 -0
  30. pymobiledevice3/cli/developer/simulate_location.py +51 -0
  31. pymobiledevice3/cli/diagnostics/__init__.py +75 -0
  32. pymobiledevice3/cli/diagnostics/battery.py +47 -0
  33. pymobiledevice3/cli/idam.py +42 -0
  34. pymobiledevice3/cli/lockdown.py +108 -103
  35. pymobiledevice3/cli/mounter.py +158 -99
  36. pymobiledevice3/cli/notification.py +38 -26
  37. pymobiledevice3/cli/pcap.py +45 -24
  38. pymobiledevice3/cli/power_assertion.py +18 -17
  39. pymobiledevice3/cli/processes.py +17 -23
  40. pymobiledevice3/cli/profile.py +165 -109
  41. pymobiledevice3/cli/provision.py +35 -34
  42. pymobiledevice3/cli/remote.py +217 -129
  43. pymobiledevice3/cli/restore.py +159 -143
  44. pymobiledevice3/cli/springboard.py +63 -53
  45. pymobiledevice3/cli/syslog.py +193 -86
  46. pymobiledevice3/cli/usbmux.py +73 -33
  47. pymobiledevice3/cli/version.py +5 -7
  48. pymobiledevice3/cli/webinspector.py +376 -214
  49. pymobiledevice3/common.py +3 -1
  50. pymobiledevice3/exceptions.py +182 -58
  51. pymobiledevice3/irecv.py +52 -53
  52. pymobiledevice3/irecv_devices.py +1489 -464
  53. pymobiledevice3/lockdown.py +473 -275
  54. pymobiledevice3/lockdown_service_provider.py +15 -8
  55. pymobiledevice3/osu/os_utils.py +27 -9
  56. pymobiledevice3/osu/posix_util.py +34 -15
  57. pymobiledevice3/osu/win_util.py +14 -8
  58. pymobiledevice3/pair_records.py +102 -21
  59. pymobiledevice3/remote/common.py +8 -4
  60. pymobiledevice3/remote/core_device/app_service.py +94 -67
  61. pymobiledevice3/remote/core_device/core_device_service.py +17 -14
  62. pymobiledevice3/remote/core_device/device_info.py +5 -5
  63. pymobiledevice3/remote/core_device/diagnostics_service.py +19 -4
  64. pymobiledevice3/remote/core_device/file_service.py +53 -23
  65. pymobiledevice3/remote/remote_service_discovery.py +79 -45
  66. pymobiledevice3/remote/remotexpc.py +73 -44
  67. pymobiledevice3/remote/tunnel_service.py +442 -317
  68. pymobiledevice3/remote/utils.py +14 -13
  69. pymobiledevice3/remote/xpc_message.py +145 -125
  70. pymobiledevice3/resources/dsc_uuid_map.py +19 -19
  71. pymobiledevice3/resources/firmware_notifications.py +20 -16
  72. pymobiledevice3/resources/notifications.txt +144 -0
  73. pymobiledevice3/restore/asr.py +27 -27
  74. pymobiledevice3/restore/base_restore.py +110 -21
  75. pymobiledevice3/restore/consts.py +87 -66
  76. pymobiledevice3/restore/device.py +59 -12
  77. pymobiledevice3/restore/fdr.py +46 -48
  78. pymobiledevice3/restore/ftab.py +19 -19
  79. pymobiledevice3/restore/img4.py +163 -0
  80. pymobiledevice3/restore/mbn.py +587 -0
  81. pymobiledevice3/restore/recovery.py +151 -151
  82. pymobiledevice3/restore/restore.py +562 -544
  83. pymobiledevice3/restore/restore_options.py +131 -110
  84. pymobiledevice3/restore/restored_client.py +51 -31
  85. pymobiledevice3/restore/tss.py +385 -267
  86. pymobiledevice3/service_connection.py +252 -59
  87. pymobiledevice3/services/accessibilityaudit.py +202 -120
  88. pymobiledevice3/services/afc.py +962 -365
  89. pymobiledevice3/services/amfi.py +24 -30
  90. pymobiledevice3/services/companion.py +23 -19
  91. pymobiledevice3/services/crash_reports.py +71 -47
  92. pymobiledevice3/services/debugserver_applist.py +3 -3
  93. pymobiledevice3/services/device_arbitration.py +8 -8
  94. pymobiledevice3/services/device_link.py +101 -79
  95. pymobiledevice3/services/diagnostics.py +973 -967
  96. pymobiledevice3/services/dtfetchsymbols.py +8 -8
  97. pymobiledevice3/services/dvt/dvt_secure_socket_proxy.py +4 -4
  98. pymobiledevice3/services/dvt/dvt_testmanaged_proxy.py +4 -4
  99. pymobiledevice3/services/dvt/instruments/activity_trace_tap.py +85 -74
  100. pymobiledevice3/services/dvt/instruments/application_listing.py +2 -3
  101. pymobiledevice3/services/dvt/instruments/condition_inducer.py +7 -6
  102. pymobiledevice3/services/dvt/instruments/core_profile_session_tap.py +466 -384
  103. pymobiledevice3/services/dvt/instruments/device_info.py +20 -11
  104. pymobiledevice3/services/dvt/instruments/energy_monitor.py +1 -1
  105. pymobiledevice3/services/dvt/instruments/graphics.py +1 -1
  106. pymobiledevice3/services/dvt/instruments/location_simulation.py +1 -1
  107. pymobiledevice3/services/dvt/instruments/location_simulation_base.py +10 -10
  108. pymobiledevice3/services/dvt/instruments/network_monitor.py +17 -17
  109. pymobiledevice3/services/dvt/instruments/notifications.py +1 -1
  110. pymobiledevice3/services/dvt/instruments/process_control.py +35 -10
  111. pymobiledevice3/services/dvt/instruments/screenshot.py +2 -2
  112. pymobiledevice3/services/dvt/instruments/sysmontap.py +15 -15
  113. pymobiledevice3/services/dvt/testmanaged/xcuitest.py +42 -52
  114. pymobiledevice3/services/file_relay.py +10 -10
  115. pymobiledevice3/services/heartbeat.py +9 -8
  116. pymobiledevice3/services/house_arrest.py +16 -15
  117. pymobiledevice3/services/idam.py +20 -0
  118. pymobiledevice3/services/installation_proxy.py +173 -81
  119. pymobiledevice3/services/lockdown_service.py +20 -10
  120. pymobiledevice3/services/misagent.py +22 -19
  121. pymobiledevice3/services/mobile_activation.py +147 -64
  122. pymobiledevice3/services/mobile_config.py +331 -294
  123. pymobiledevice3/services/mobile_image_mounter.py +141 -113
  124. pymobiledevice3/services/mobilebackup2.py +203 -145
  125. pymobiledevice3/services/notification_proxy.py +11 -11
  126. pymobiledevice3/services/os_trace.py +134 -74
  127. pymobiledevice3/services/pcapd.py +314 -302
  128. pymobiledevice3/services/power_assertion.py +10 -9
  129. pymobiledevice3/services/preboard.py +4 -4
  130. pymobiledevice3/services/remote_fetch_symbols.py +21 -14
  131. pymobiledevice3/services/remote_server.py +176 -146
  132. pymobiledevice3/services/restore_service.py +16 -16
  133. pymobiledevice3/services/screenshot.py +15 -12
  134. pymobiledevice3/services/simulate_location.py +7 -7
  135. pymobiledevice3/services/springboard.py +15 -15
  136. pymobiledevice3/services/syslog.py +5 -5
  137. pymobiledevice3/services/web_protocol/alert.py +11 -11
  138. pymobiledevice3/services/web_protocol/automation_session.py +251 -239
  139. pymobiledevice3/services/web_protocol/cdp_screencast.py +46 -37
  140. pymobiledevice3/services/web_protocol/cdp_server.py +19 -19
  141. pymobiledevice3/services/web_protocol/cdp_target.py +411 -373
  142. pymobiledevice3/services/web_protocol/driver.py +114 -111
  143. pymobiledevice3/services/web_protocol/element.py +124 -111
  144. pymobiledevice3/services/web_protocol/inspector_session.py +106 -102
  145. pymobiledevice3/services/web_protocol/selenium_api.py +49 -49
  146. pymobiledevice3/services/web_protocol/session_protocol.py +18 -12
  147. pymobiledevice3/services/web_protocol/switch_to.py +30 -27
  148. pymobiledevice3/services/webinspector.py +189 -155
  149. pymobiledevice3/tcp_forwarder.py +87 -69
  150. pymobiledevice3/tunneld/__init__.py +0 -0
  151. pymobiledevice3/tunneld/api.py +63 -0
  152. pymobiledevice3/tunneld/server.py +603 -0
  153. pymobiledevice3/usbmux.py +198 -147
  154. pymobiledevice3/utils.py +14 -11
  155. {pymobiledevice3-4.14.6.dist-info → pymobiledevice3-7.0.6.dist-info}/METADATA +55 -28
  156. pymobiledevice3-7.0.6.dist-info/RECORD +188 -0
  157. {pymobiledevice3-4.14.6.dist-info → pymobiledevice3-7.0.6.dist-info}/WHEEL +1 -1
  158. pymobiledevice3/cli/developer.py +0 -1215
  159. pymobiledevice3/cli/diagnostics.py +0 -99
  160. pymobiledevice3/tunneld.py +0 -524
  161. pymobiledevice3-4.14.6.dist-info/RECORD +0 -168
  162. {pymobiledevice3-4.14.6.dist-info → pymobiledevice3-7.0.6.dist-info}/entry_points.txt +0 -0
  163. {pymobiledevice3-4.14.6.dist-info → pymobiledevice3-7.0.6.dist-info/licenses}/LICENSE +0 -0
  164. {pymobiledevice3-4.14.6.dist-info → pymobiledevice3-7.0.6.dist-info}/top_level.txt +0 -0
@@ -1,59 +1,104 @@
1
- import click
1
+ from pathlib import Path
2
+ from typing import Annotated, Literal
2
3
 
3
- from pymobiledevice3.cli.cli_common import Command, print_json
4
- from pymobiledevice3.lockdown import LockdownClient
5
- from pymobiledevice3.lockdown_service_provider import LockdownServiceProvider
4
+ import typer
5
+ from typer_injector import InjectingTyper
6
+
7
+ from pymobiledevice3.cli.cli_common import ServiceProviderDep, print_json
6
8
  from pymobiledevice3.services.house_arrest import HouseArrestService
7
9
  from pymobiledevice3.services.installation_proxy import InstallationProxyService
8
10
 
11
+ cli = InjectingTyper(
12
+ name="apps",
13
+ help="List, query, install, uninstall, and inspect apps on the device.",
14
+ no_args_is_help=True,
15
+ )
16
+
9
17
 
10
- @click.group()
11
- def cli() -> None:
12
- pass
18
+ @cli.command("list")
19
+ def apps_list(
20
+ service_provider: ServiceProviderDep,
21
+ app_type: Annotated[
22
+ Literal["System", "User", "Hidden", "Any"],
23
+ typer.Option(
24
+ "--type",
25
+ "-t",
26
+ help="Filter by application type (System/User/Hidden/Any).",
27
+ ),
28
+ ] = "Any",
29
+ calculate_sizes: Annotated[
30
+ bool,
31
+ typer.Option(help="Include app size information (slower)."),
32
+ ] = False,
33
+ ) -> None:
34
+ """List installed apps."""
35
+ print_json(
36
+ InstallationProxyService(lockdown=service_provider).get_apps(
37
+ application_type=app_type, calculate_sizes=calculate_sizes
38
+ )
39
+ )
13
40
 
14
41
 
15
- @cli.group()
16
- def apps() -> None:
17
- """ Manage installed applications """
18
- pass
42
+ @cli.command("query")
43
+ def apps_query(
44
+ service_provider: ServiceProviderDep,
45
+ bundle_identifiers: list[str],
46
+ calculate_sizes: Annotated[
47
+ bool,
48
+ typer.Option(help="Include app size information (slower)."),
49
+ ] = False,
50
+ ) -> None:
51
+ """Return metadata for specific bundle identifiers."""
52
+ print_json(
53
+ InstallationProxyService(lockdown=service_provider).get_apps(
54
+ calculate_sizes=calculate_sizes, bundle_identifiers=bundle_identifiers
55
+ )
56
+ )
19
57
 
20
58
 
21
- @apps.command('list', cls=Command)
22
- @click.option('app_type', '-t', '--type', type=click.Choice(['System', 'User', 'Hidden', 'Any']), default='Any',
23
- help='include only applications of given type')
24
- @click.option('--calculate-sizes/--no-calculate-size', default=False)
25
- def apps_list(service_provider: LockdownServiceProvider, app_type: str, calculate_sizes: bool) -> None:
26
- """ list installed apps """
27
- print_json(InstallationProxyService(lockdown=service_provider).get_apps(application_type=app_type,
28
- calculate_sizes=calculate_sizes))
59
+ @cli.command("uninstall")
60
+ def uninstall(service_provider: ServiceProviderDep, bundle_id: str) -> None:
61
+ """Uninstall an app by bundle identifier."""
62
+ InstallationProxyService(lockdown=service_provider).uninstall(bundle_id)
29
63
 
30
64
 
31
- @apps.command('query', cls=Command)
32
- @click.argument('bundle_identifiers', nargs=-1)
33
- @click.option('--calculate-sizes/--no-calculate-size', default=False)
34
- def apps_query(service_provider: LockdownServiceProvider, bundle_identifiers: list[str], calculate_sizes: bool) -> None:
35
- """ query installed apps """
36
- print_json(InstallationProxyService(lockdown=service_provider)
37
- .get_apps(calculate_sizes=calculate_sizes, bundle_identifiers=bundle_identifiers))
65
+ @cli.command("install")
66
+ def install(
67
+ service_provider: ServiceProviderDep,
68
+ package: Annotated[
69
+ Path,
70
+ typer.Argument(exists=True),
71
+ ],
72
+ developer: Annotated[
73
+ bool,
74
+ typer.Option(help="Install developer package"),
75
+ ] = False,
76
+ ) -> None:
77
+ """Install a local .ipa/.app/.ipcc package."""
78
+ InstallationProxyService(lockdown=service_provider).install_from_local(package, developer=developer)
38
79
 
39
80
 
40
- @apps.command('uninstall', cls=Command)
41
- @click.argument('bundle_id')
42
- def uninstall(service_provider: LockdownClient, bundle_id):
43
- """ uninstall app by given bundle_id """
44
- InstallationProxyService(lockdown=service_provider).uninstall(bundle_id)
81
+ @cli.command("afc")
82
+ def afc(
83
+ service_provider: ServiceProviderDep, bundle_id: str, documents: Annotated[bool, typer.Option()] = False
84
+ ) -> None:
85
+ """Open an AFC shell into the app container; pass --documents for Documents-only."""
86
+ HouseArrestService(lockdown=service_provider, bundle_id=bundle_id, documents_only=documents).shell()
45
87
 
46
88
 
47
- @apps.command('install', cls=Command)
48
- @click.argument('package', type=click.Path(exists=True))
49
- def install(service_provider: LockdownServiceProvider, package: str) -> None:
50
- """ install given .ipa/.app/.ipcc """
51
- InstallationProxyService(lockdown=service_provider).install_from_local(package)
89
+ @cli.command("pull")
90
+ def pull(service_provider: ServiceProviderDep, bundle_id: str, remote_file: Path, local_file: Path) -> None:
91
+ """Pull a file from an app container to a local path."""
92
+ HouseArrestService(lockdown=service_provider, bundle_id=bundle_id).pull(str(remote_file), str(local_file))
52
93
 
53
94
 
54
- @apps.command('afc', cls=Command)
55
- @click.option('--documents', is_flag=True)
56
- @click.argument('bundle_id')
57
- def afc(service_provider: LockdownClient, bundle_id: str, documents: bool):
58
- """ open an AFC shell for given bundle_id, assuming its profile is installed """
59
- HouseArrestService(lockdown=service_provider, bundle_id=bundle_id, documents_only=documents).shell()
95
+ @cli.command("push")
96
+ def push(service_provider: ServiceProviderDep, bundle_id: str, local_file: Path, remote_file: Path) -> None:
97
+ """Push a local file into an app container."""
98
+ HouseArrestService(lockdown=service_provider, bundle_id=bundle_id).push(str(local_file), str(remote_file))
99
+
100
+
101
+ @cli.command("rm")
102
+ def rm(service_provider: ServiceProviderDep, bundle_id: str, remote_file: Path) -> None:
103
+ """Delete a file from an app container."""
104
+ HouseArrestService(lockdown=service_provider, bundle_id=bundle_id).rm(str(remote_file))
@@ -1,37 +1,65 @@
1
1
  import logging
2
+ from pathlib import Path
3
+ from typing import Annotated, Literal
2
4
 
3
- import click
5
+ import typer
4
6
  from tqdm import tqdm
7
+ from typer_injector import InjectingTyper
5
8
 
6
- from pymobiledevice3.cli.cli_common import Command
7
- from pymobiledevice3.lockdown import LockdownClient
9
+ from pymobiledevice3.cli.cli_common import ServiceProviderDep
8
10
  from pymobiledevice3.services.mobilebackup2 import Mobilebackup2Service
9
11
 
10
- source_option = click.option('--source', default='', help='The UDID of the source device.')
11
- password_option = click.option('-p', '--password', default='', help='Backup password.')
12
- backup_directory_arg = click.argument('backup-directory', type=click.Path(exists=True, file_okay=False))
13
- backup_directory_option = click.option('-b', '--backup-directory', type=click.Path(exists=True, file_okay=False),
14
- default='.')
15
-
16
12
  logger = logging.getLogger(__name__)
17
13
 
18
14
 
19
- @click.group()
20
- def cli() -> None:
21
- pass
22
-
23
-
24
- @cli.group()
25
- def backup2() -> None:
26
- """ Backup/Restore options """
27
- pass
28
-
29
-
30
- @backup2.command(cls=Command)
31
- @click.argument('backup-directory', type=click.Path(file_okay=False))
32
- @click.option('--full', is_flag=True, help=('Whether to do a full backup.'
33
- ' If full is True, any previous backup attempts will be discarded.'))
34
- def backup(service_provider: LockdownClient, backup_directory, full):
15
+ cli = InjectingTyper(
16
+ name="backup2",
17
+ help="Create, inspect, and restore MobileBackup2 backups.",
18
+ no_args_is_help=True,
19
+ )
20
+
21
+
22
+ SourceOption = Annotated[
23
+ str,
24
+ typer.Option(help="The UDID of the source device."),
25
+ ]
26
+ PasswordOption = Annotated[
27
+ str,
28
+ typer.Option(
29
+ "--password",
30
+ "-p",
31
+ help="Backup password.",
32
+ ),
33
+ ]
34
+ BackupDirectoryArg = Annotated[
35
+ Path,
36
+ typer.Argument(
37
+ exists=True,
38
+ file_okay=False,
39
+ ),
40
+ ]
41
+ BackupDirectoryOption = Annotated[
42
+ Path,
43
+ typer.Option(
44
+ "--backup-directory",
45
+ "-b",
46
+ exists=True,
47
+ file_okay=False,
48
+ ),
49
+ ]
50
+
51
+
52
+ @cli.command()
53
+ def backup(
54
+ service_provider: ServiceProviderDep,
55
+ backup_directory: BackupDirectoryArg,
56
+ full: Annotated[
57
+ bool,
58
+ typer.Option(
59
+ help="Whether to do a full backup. If full is True, any previous backup attempts will be discarded.",
60
+ ),
61
+ ],
62
+ ) -> None:
35
63
  """
36
64
  Backup device.
37
65
 
@@ -39,23 +67,45 @@ def backup(service_provider: LockdownClient, backup_directory, full):
39
67
  """
40
68
  backup_client = Mobilebackup2Service(service_provider)
41
69
  with tqdm(total=100, dynamic_ncols=True) as pbar:
42
- def update_bar(percentage):
70
+
71
+ def update_bar(percentage) -> None:
43
72
  pbar.n = percentage
44
73
  pbar.refresh()
45
74
 
46
- backup_client.backup(full=full, backup_directory=backup_directory, progress_callback=update_bar)
47
-
48
-
49
- @backup2.command(cls=Command)
50
- @backup_directory_arg
51
- @click.option('--system/--no-system', default=False, help='Restore system files.')
52
- @click.option('--reboot/--no-reboot', default=True, help='Reboot the device when done.')
53
- @click.option('--copy/--no-copy', default=True, help='Create a copy of backup folder before restoring.')
54
- @click.option('--settings/--no-settings', default=True, help='Restore device settings.')
55
- @click.option('--remove/--no-remove', default=False, help='Remove items which aren\'t being restored.')
56
- @password_option
57
- @source_option
58
- def restore(service_provider: LockdownClient, backup_directory, system, reboot, copy, settings, remove, password, source):
75
+ backup_client.backup(full=full, backup_directory=str(backup_directory), progress_callback=update_bar)
76
+
77
+
78
+ @cli.command()
79
+ def restore(
80
+ service_provider: ServiceProviderDep,
81
+ backup_directory: BackupDirectoryArg,
82
+ system: Annotated[
83
+ bool,
84
+ typer.Option(help="Restore system files."),
85
+ ] = False,
86
+ reboot: Annotated[
87
+ bool,
88
+ typer.Option(help="Reboot the device when done."),
89
+ ] = True,
90
+ copy: Annotated[
91
+ bool,
92
+ typer.Option(help="Create a copy of backup folder before restoring."),
93
+ ] = False,
94
+ settings: Annotated[
95
+ bool,
96
+ typer.Option(help="Restore device settings."),
97
+ ] = True,
98
+ remove: Annotated[
99
+ bool,
100
+ typer.Option(help="Remove items which aren't being restored."),
101
+ ] = False,
102
+ skip_apps: Annotated[
103
+ bool,
104
+ typer.Option(help="Do not trigger re-installation of apps after restore."),
105
+ ] = False,
106
+ password: PasswordOption = "",
107
+ source: SourceOption = "",
108
+ ) -> None:
59
109
  """
60
110
  Restore a backup to a device.
61
111
 
@@ -63,56 +113,68 @@ def restore(service_provider: LockdownClient, backup_directory, system, reboot,
63
113
  """
64
114
  backup_client = Mobilebackup2Service(service_provider)
65
115
  with tqdm(total=100, dynamic_ncols=True) as pbar:
66
- def update_bar(percentage):
116
+
117
+ def update_bar(percentage) -> None:
67
118
  pbar.n = percentage
68
119
  pbar.refresh()
69
120
 
70
- backup_client.restore(backup_directory=backup_directory, progress_callback=update_bar, system=system,
71
- reboot=reboot, copy=copy, settings=settings, remove=remove, password=password,
72
- source=source)
121
+ backup_client.restore(
122
+ backup_directory=str(backup_directory),
123
+ progress_callback=update_bar,
124
+ system=system,
125
+ reboot=reboot,
126
+ copy=copy,
127
+ settings=settings,
128
+ remove=remove,
129
+ password=password,
130
+ source=source,
131
+ skip_apps=skip_apps,
132
+ )
73
133
 
74
134
 
75
- @backup2.command(cls=Command)
76
- @backup_directory_arg
77
- @source_option
78
- def info(service_provider: LockdownClient, backup_directory, source):
135
+ @cli.command()
136
+ def info(service_provider: ServiceProviderDep, backup_directory: BackupDirectoryArg, source: SourceOption = "") -> None:
79
137
  """
80
138
  Print information about a backup.
81
139
  """
82
140
  backup_client = Mobilebackup2Service(service_provider)
83
- print(backup_client.info(backup_directory=backup_directory, source=source))
141
+ print(backup_client.info(backup_directory=str(backup_directory), source=source))
84
142
 
85
143
 
86
- @backup2.command('list', cls=Command)
87
- @backup_directory_arg
88
- @source_option
89
- def list_(service_provider: LockdownClient, backup_directory, source):
144
+ @cli.command("list")
145
+ def list_(
146
+ service_provider: ServiceProviderDep, backup_directory: BackupDirectoryArg, source: SourceOption = ""
147
+ ) -> None:
90
148
  """
91
149
  List all file in the backup in a CSV format.
92
150
  """
93
151
  backup_client = Mobilebackup2Service(service_provider)
94
- print(backup_client.list(backup_directory=backup_directory, source=source))
152
+ print(backup_client.list(backup_directory=str(backup_directory), source=source))
95
153
 
96
154
 
97
- @backup2.command(cls=Command)
98
- @backup_directory_arg
99
- @password_option
100
- @source_option
101
- def unback(service_provider: LockdownClient, backup_directory, password, source):
155
+ @cli.command()
156
+ def unback(
157
+ service_provider: ServiceProviderDep,
158
+ backup_directory: BackupDirectoryArg,
159
+ password: PasswordOption = "",
160
+ source: SourceOption = "",
161
+ ) -> None:
102
162
  """
103
163
  Convert all files in the backup to the correct directory hierarchy.
104
164
  """
105
165
  backup_client = Mobilebackup2Service(service_provider)
106
- backup_client.unback(backup_directory=backup_directory, password=password, source=source)
166
+ backup_client.unback(backup_directory=str(backup_directory), password=password, source=source)
107
167
 
108
168
 
109
- @backup2.command(cls=Command)
110
- @click.argument('domain-name')
111
- @click.argument('relative-path')
112
- @backup_directory_arg
113
- @password_option
114
- @source_option
115
- def extract(service_provider: LockdownClient, domain_name, relative_path, backup_directory, password, source):
169
+ @cli.command()
170
+ def extract(
171
+ service_provider: ServiceProviderDep,
172
+ domain_name: str,
173
+ relative_path: str,
174
+ backup_directory: BackupDirectoryArg,
175
+ password: PasswordOption = "",
176
+ source: SourceOption = "",
177
+ ) -> None:
116
178
  """
117
179
  Extract a file from the backup.
118
180
 
@@ -120,15 +182,22 @@ def extract(service_provider: LockdownClient, domain_name, relative_path, backup
120
182
  will be extracted to the BACKUP_DIRECTORY.
121
183
  """
122
184
  backup_client = Mobilebackup2Service(service_provider)
123
- backup_client.extract(domain_name, relative_path, backup_directory=backup_directory, password=password,
124
- source=source)
125
-
126
-
127
- @backup2.command(cls=Command)
128
- @click.argument('mode', type=click.Choice(['on', 'off'], case_sensitive=False))
129
- @click.argument('password')
130
- @backup_directory_option
131
- def encryption(service_provider: LockdownClient, backup_directory, mode, password):
185
+ backup_client.extract(
186
+ domain_name, relative_path, backup_directory=str(backup_directory), password=password, source=source
187
+ )
188
+
189
+
190
+ @cli.command()
191
+ def encryption(
192
+ service_provider: ServiceProviderDep,
193
+ *,
194
+ backup_directory: BackupDirectoryOption = Path("."),
195
+ mode: Annotated[
196
+ Literal["on", "off"],
197
+ typer.Argument(case_sensitive=False),
198
+ ],
199
+ password: str,
200
+ ) -> None:
132
201
  """
133
202
  Set backup encryption on / off.
134
203
 
@@ -136,36 +205,37 @@ def encryption(service_provider: LockdownClient, backup_directory, mode, passwor
136
205
  When off, PASSWORD is the current backup password.
137
206
  """
138
207
  backup_client = Mobilebackup2Service(service_provider)
139
- should_encrypt = mode.lower() == 'on'
208
+ should_encrypt = mode.lower() == "on"
140
209
  if should_encrypt == backup_client.will_encrypt:
141
- logger.error('Encryption already ' + ('on!' if should_encrypt else 'off!'))
210
+ logger.error("Encryption already " + ("on!" if should_encrypt else "off!"))
142
211
  return
143
212
  if should_encrypt:
144
- backup_client.change_password(backup_directory, new=password)
213
+ backup_client.change_password(str(backup_directory), new=password)
145
214
  else:
146
- backup_client.change_password(backup_directory, old=password)
215
+ backup_client.change_password(str(backup_directory), old=password)
147
216
 
148
217
 
149
- @backup2.command(cls=Command)
150
- @click.argument('old-password')
151
- @click.argument('new-password')
152
- @backup_directory_option
153
- def change_password(service_provider: LockdownClient, old_password, new_password, backup_directory):
218
+ @cli.command()
219
+ def change_password(
220
+ service_provider: ServiceProviderDep,
221
+ old_password: str,
222
+ new_password: str,
223
+ backup_directory: BackupDirectoryOption = Path("."),
224
+ ) -> None:
154
225
  """
155
226
  Change the backup password.
156
227
  """
157
228
  backup_client = Mobilebackup2Service(service_provider)
158
229
  if not backup_client.will_encrypt:
159
- logger.error('Encryption is not turned on!')
230
+ logger.error("Encryption is not turned on!")
160
231
  return
161
- backup_client.change_password(backup_directory, old=old_password, new=new_password)
232
+ backup_client.change_password(str(backup_directory), old=old_password, new=new_password)
162
233
 
163
234
 
164
- @backup2.command(cls=Command)
165
- @backup_directory_arg
166
- def erase_device(service_provider: LockdownClient, backup_directory):
235
+ @cli.command()
236
+ def erase_device(service_provider: ServiceProviderDep, backup_directory: BackupDirectoryArg) -> None:
167
237
  """
168
238
  Erase all data on the device.
169
239
  """
170
240
  backup_client = Mobilebackup2Service(service_provider)
171
- backup_client.erase_device(backup_directory)
241
+ backup_client.erase_device(str(backup_directory))
@@ -1,75 +1,79 @@
1
1
  import asyncio
2
- import plistlib
3
2
  from pathlib import Path
3
+ from typing import Annotated, Optional
4
4
 
5
- import click
5
+ import typer
6
+ from typer_injector import InjectingTyper
6
7
 
7
8
  from pymobiledevice3.bonjour import DEFAULT_BONJOUR_TIMEOUT, browse_remotepairing, browse_remotepairing_manual_pairing
8
- from pymobiledevice3.cli.cli_common import BaseCommand, print_json
9
+ from pymobiledevice3.cli.cli_common import print_json
9
10
  from pymobiledevice3.cli.remote import browse_rsd
10
11
  from pymobiledevice3.lockdown import get_mobdev2_lockdowns
11
12
 
13
+ cli = InjectingTyper(
14
+ name="bonjour",
15
+ help="Browse devices over bonjour",
16
+ no_args_is_help=True,
17
+ )
12
18
 
13
- @click.group()
14
- def cli() -> None:
15
- pass
16
19
 
17
-
18
- @cli.group('bonjour')
19
- def bonjour_cli() -> None:
20
- """ Browse devices over bonjour """
21
- pass
22
-
23
-
24
- async def cli_mobdev2_task(timeout: float, pair_records: str) -> None:
25
- records = []
26
- if pair_records is not None:
27
- for record in Path(pair_records).glob('*.plist'):
28
- records.append(plistlib.loads(record.read_bytes()))
20
+ async def cli_mobdev2_task(timeout: float, pair_records: Optional[Path]) -> None:
29
21
  output = []
30
- async for ip, lockdown in get_mobdev2_lockdowns(timeout=timeout):
22
+ async for ip, lockdown in get_mobdev2_lockdowns(timeout=timeout, pair_records=pair_records):
31
23
  short_info = lockdown.short_info
32
- short_info['ip'] = ip
24
+ short_info["ip"] = ip
33
25
  output.append(short_info)
34
26
  print_json(output)
35
27
 
36
28
 
37
- @bonjour_cli.command('mobdev2', cls=BaseCommand)
38
- @click.option('--timeout', default=DEFAULT_BONJOUR_TIMEOUT, type=click.INT)
39
- @click.option('--pair-records', type=click.Path(dir_okay=True, file_okay=False, exists=True),
40
- help='pair records to attempt validation with')
41
- def cli_mobdev2(timeout: float, pair_records: str) -> None:
42
- """ browse for mobdev2 devices over bonjour """
29
+ @cli.command("mobdev2")
30
+ def cli_mobdev2(
31
+ timeout: Annotated[float, typer.Option()] = DEFAULT_BONJOUR_TIMEOUT,
32
+ pair_records: Annotated[
33
+ Optional[Path],
34
+ typer.Option(
35
+ exists=True,
36
+ dir_okay=True,
37
+ file_okay=True,
38
+ help="pair records to attempt validation with",
39
+ ),
40
+ ] = None,
41
+ ) -> None:
42
+ """browse for mobdev2 devices over bonjour"""
43
43
  asyncio.run(cli_mobdev2_task(timeout, pair_records))
44
44
 
45
45
 
46
46
  async def cli_remotepairing_task(timeout: float) -> None:
47
47
  output = []
48
48
  for answer in await browse_remotepairing(timeout=timeout):
49
- for ip in answer.ips:
50
- output.append({'hostname': ip, 'port': answer.port})
49
+ for address in answer.addresses:
50
+ output.append({"hostname": address.full_ip, "port": answer.port})
51
51
  print_json(output)
52
52
 
53
53
 
54
- @bonjour_cli.command('remotepairing', cls=BaseCommand)
55
- @click.option('--timeout', default=DEFAULT_BONJOUR_TIMEOUT, type=click.FLOAT)
56
- def cli_remotepairing(timeout: float) -> None:
57
- """ browse for remotepairing devices over bonjour (without attempting pair verification) """
54
+ @cli.command("remotepairing")
55
+ def cli_remotepairing(timeout: Annotated[float, typer.Option()] = DEFAULT_BONJOUR_TIMEOUT) -> None:
56
+ """browse for remotepairing devices over bonjour (without attempting pair verification)"""
58
57
  asyncio.run(cli_remotepairing_task(timeout=timeout))
59
58
 
60
59
 
61
60
  async def cli_remotepairing_manual_pairing_task(timeout: float) -> None:
62
61
  output = []
63
62
  for answer in await browse_remotepairing_manual_pairing(timeout=timeout):
64
- for ip in answer.ips:
65
- output.append({'hostname': ip, 'port': answer.port, 'name': answer.properties[b'name'].decode()})
63
+ for address in answer.addresses:
64
+ output.append({
65
+ "hostname": address.full_ip,
66
+ "port": answer.port,
67
+ "name": answer.properties[b"name"].decode(),
68
+ })
66
69
  print_json(output)
67
70
 
68
71
 
69
- @bonjour_cli.command('remotepairing-manual-pairing', cls=BaseCommand)
70
- @click.option('--timeout', default=DEFAULT_BONJOUR_TIMEOUT, type=click.FLOAT)
71
- def cli_remotepairing_manual_pairing(timeout: float) -> None:
72
- """ browse for remotepairing-manual-pairing devices over bonjour """
72
+ @cli.command("remotepairing-manual-pairing")
73
+ def cli_remotepairing_manual_pairing(
74
+ timeout: Annotated[float, typer.Option()] = DEFAULT_BONJOUR_TIMEOUT,
75
+ ) -> None:
76
+ """browse for remotepairing-manual-pairing devices over bonjour"""
73
77
  asyncio.run(cli_remotepairing_manual_pairing_task(timeout=timeout))
74
78
 
75
79
 
@@ -77,7 +81,7 @@ async def cli_browse_rsd() -> None:
77
81
  print_json(await browse_rsd())
78
82
 
79
83
 
80
- @bonjour_cli.command('rsd', cls=BaseCommand)
84
+ @cli.command("rsd")
81
85
  def cli_rsd() -> None:
82
- """ browse RemoteXPC devices using bonjour """
86
+ """browse RemoteXPC devices using bonjour"""
83
87
  asyncio.run(cli_browse_rsd(), debug=True)