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,32 +1,33 @@
1
1
  #!/usr/bin/env python3
2
2
  import logging
3
3
 
4
- import construct
5
-
6
- from pymobiledevice3.exceptions import AmfiError, BadDevError, ConnectionFailedError, DeveloperModeError, \
7
- DeviceHasPasscodeSetError, DeviceNotFoundError, NoDeviceConnectedError, PyMobileDevice3Exception
8
- from pymobiledevice3.lockdown import LockdownClient, create_using_usbmux
4
+ from pymobiledevice3.exceptions import (
5
+ AmfiError,
6
+ DeveloperModeError,
7
+ DeviceHasPasscodeSetError,
8
+ PyMobileDevice3Exception,
9
+ )
10
+ from pymobiledevice3.lockdown import LockdownClient, retry_create_using_usbmux
9
11
  from pymobiledevice3.services.heartbeat import HeartbeatService
10
12
 
11
13
 
12
14
  class AmfiService:
13
-
14
15
  DEVELOPER_MODE_REVEAL = 0
15
16
  DEVELOPER_MODE_ENABLE = 1
16
17
  DEVELOPER_MODE_ACCEPT = 2
17
18
 
18
- SERVICE_NAME = 'com.apple.amfi.lockdown'
19
+ SERVICE_NAME = "com.apple.amfi.lockdown"
19
20
 
20
21
  def __init__(self, lockdown: LockdownClient):
21
22
  self._lockdown = lockdown
22
23
  self._logger = logging.getLogger(self.__module__)
23
24
 
24
25
  def reveal_developer_mode_option_in_ui(self):
25
- """ create an empty file at AMFIShowOverridePath """
26
+ """create an empty file at AMFIShowOverridePath"""
26
27
  service = self._lockdown.start_lockdown_service(self.SERVICE_NAME)
27
- resp = service.send_recv_plist({'action': self.DEVELOPER_MODE_REVEAL})
28
- if not resp.get('success'):
29
- raise PyMobileDevice3Exception(f'create_AMFIShowOverridePath() failed with: {resp}')
28
+ resp = service.send_recv_plist({"action": self.DEVELOPER_MODE_REVEAL})
29
+ if not resp.get("success"):
30
+ raise PyMobileDevice3Exception(f"create_AMFIShowOverridePath() failed with: {resp}")
30
31
 
31
32
  def enable_developer_mode(self, enable_post_restart=True):
32
33
  """
@@ -35,38 +36,31 @@ class AmfiService:
35
36
  with "yes"
36
37
  """
37
38
  service = self._lockdown.start_lockdown_service(self.SERVICE_NAME)
38
- resp = service.send_recv_plist({'action': self.DEVELOPER_MODE_ENABLE})
39
- error = resp.get('Error')
39
+ resp = service.send_recv_plist({"action": self.DEVELOPER_MODE_ENABLE})
40
+ error = resp.get("Error")
40
41
 
41
42
  if error is not None:
42
- if error == 'Device has a passcode set':
43
+ if error == "Device has a passcode set":
43
44
  raise DeviceHasPasscodeSetError()
44
45
  raise AmfiError(error)
45
46
 
46
- if not resp.get('success'):
47
- raise DeveloperModeError(f'enable_developer_mode(): {resp}')
47
+ if not resp.get("success"):
48
+ raise DeveloperModeError(f"enable_developer_mode(): {resp}")
48
49
 
49
50
  if not enable_post_restart:
50
51
  return
51
52
 
52
53
  try:
53
54
  HeartbeatService(self._lockdown).start()
54
- except ConnectionAbortedError:
55
- self._logger.debug('device disconnected, awaiting reconnect')
56
-
57
- while True:
58
- try:
59
- self._lockdown = create_using_usbmux(self._lockdown.udid)
60
- break
61
- except (NoDeviceConnectedError, ConnectionFailedError, BadDevError, OSError, construct.core.StreamError,
62
- DeviceNotFoundError):
63
- pass
55
+ except (ConnectionAbortedError, BrokenPipeError):
56
+ self._logger.debug("device disconnected, awaiting reconnect")
64
57
 
58
+ self._lockdown = retry_create_using_usbmux(None, serial=self._lockdown.udid)
65
59
  self.enable_developer_mode_post_restart()
66
60
 
67
61
  def enable_developer_mode_post_restart(self):
68
- """ answer the prompt that appears after the restart with "yes" """
62
+ """answer the prompt that appears after the restart with "yes" """
69
63
  service = self._lockdown.start_lockdown_service(self.SERVICE_NAME)
70
- resp = service.send_recv_plist({'action': self.DEVELOPER_MODE_ACCEPT})
71
- if not resp.get('success'):
72
- raise DeveloperModeError(f'enable_developer_mode_post_restart() failed: {resp}')
64
+ resp = service.send_recv_plist({"action": self.DEVELOPER_MODE_ACCEPT})
65
+ if not resp.get("success"):
66
+ raise DeveloperModeError(f"enable_developer_mode_post_restart() failed: {resp}")
@@ -8,8 +8,8 @@ from pymobiledevice3.services.lockdown_service import LockdownService
8
8
 
9
9
 
10
10
  class CompanionProxyService(LockdownService):
11
- SERVICE_NAME = 'com.apple.companion_proxy'
12
- RSD_SERVICE_NAME = 'com.apple.companion_proxy.shim.remote'
11
+ SERVICE_NAME = "com.apple.companion_proxy"
12
+ RSD_SERVICE_NAME = "com.apple.companion_proxy.shim.remote"
13
13
 
14
14
  def __init__(self, lockdown: LockdownServiceProvider):
15
15
  if isinstance(lockdown, LockdownClient):
@@ -19,48 +19,52 @@ class CompanionProxyService(LockdownService):
19
19
 
20
20
  def list(self):
21
21
  service = self.lockdown.start_lockdown_service(self.service_name)
22
- return service.send_recv_plist({'Command': 'GetDeviceRegistry'}).get('PairedDevicesArray', [])
22
+ return service.send_recv_plist({"Command": "GetDeviceRegistry"}).get("PairedDevicesArray", [])
23
23
 
24
24
  def listen_for_devices(self):
25
25
  service = self.lockdown.start_lockdown_service(self.service_name)
26
- service.send_plist({'Command': 'StartListeningForDevices'})
26
+ service.send_plist({"Command": "StartListeningForDevices"})
27
27
  while True:
28
28
  yield service.recv_plist()
29
29
 
30
30
  def get_value(self, udid: str, key: str):
31
31
  service = self.lockdown.start_lockdown_service(self.service_name)
32
- response = service.send_recv_plist({'Command': 'GetValueFromRegistry',
33
- 'GetValueGizmoUDIDKey': udid,
34
- 'GetValueKeyKey': key})
32
+ response = service.send_recv_plist({
33
+ "Command": "GetValueFromRegistry",
34
+ "GetValueGizmoUDIDKey": udid,
35
+ "GetValueKeyKey": key,
36
+ })
35
37
 
36
- value = response.get('RetrievedValueDictionary')
38
+ value = response.get("RetrievedValueDictionary")
37
39
  if value is not None:
38
40
  return value
39
41
 
40
- error = response.get('Error')
42
+ error = response.get("Error")
41
43
  raise PyMobileDevice3Exception(error)
42
44
 
43
- def start_forwarding_service_port(self, remote_port: int, service_name: Optional[str] = None,
44
- options: Optional[dict] = None):
45
+ def start_forwarding_service_port(
46
+ self, remote_port: int, service_name: Optional[str] = None, options: Optional[dict] = None
47
+ ):
45
48
  service = self.lockdown.start_lockdown_service(self.service_name)
46
49
 
47
- request = {'Command': 'StartForwardingServicePort',
48
- 'GizmoRemotePortNumber': remote_port,
49
- 'IsServiceLowPriority': False,
50
- 'PreferWifi': False}
50
+ request = {
51
+ "Command": "StartForwardingServicePort",
52
+ "GizmoRemotePortNumber": remote_port,
53
+ "IsServiceLowPriority": False,
54
+ "PreferWifi": False,
55
+ }
51
56
 
52
57
  if service_name is not None:
53
- request['ForwardedServiceName'] = service_name
58
+ request["ForwardedServiceName"] = service_name
54
59
 
55
60
  if options is not None:
56
61
  request.update(options)
57
62
 
58
- return service.send_recv_plist(request).get('CompanionProxyServicePort')
63
+ return service.send_recv_plist(request).get("CompanionProxyServicePort")
59
64
 
60
65
  def stop_forwarding_service_port(self, remote_port: int):
61
66
  service = self.lockdown.start_lockdown_service(self.service_name)
62
67
 
63
- request = {'Command': 'StopForwardingServicePort',
64
- 'GizmoRemotePortNumber': remote_port}
68
+ request = {"Command": "StopForwardingServicePort", "GizmoRemotePortNumber": remote_port}
65
69
 
66
70
  return service.send_recv_plist(request)
@@ -3,21 +3,27 @@ import posixpath
3
3
  import re
4
4
  import time
5
5
  from collections.abc import Generator
6
- from typing import Callable, Optional
6
+ from json import JSONDecodeError
7
+ from typing import Callable, ClassVar, Optional
7
8
 
8
9
  from pycrashreport.crash_report import get_crash_report_from_buf
9
10
  from xonsh.built_ins import XSH
10
11
  from xonsh.cli_utils import Annotated, Arg
11
12
 
12
- from pymobiledevice3.exceptions import AfcException, NotificationTimeoutError, SysdiagnoseTimeoutError
13
+ from pymobiledevice3.exceptions import (
14
+ AfcException,
15
+ AfcFileNotFoundError,
16
+ NotificationTimeoutError,
17
+ SysdiagnoseTimeoutError,
18
+ )
13
19
  from pymobiledevice3.lockdown import LockdownClient
14
20
  from pymobiledevice3.lockdown_service_provider import LockdownServiceProvider
15
21
  from pymobiledevice3.services.afc import AfcService, AfcShell, path_completer
16
22
  from pymobiledevice3.services.notification_proxy import NotificationProxyService
17
23
  from pymobiledevice3.services.os_trace import OsTraceService
18
24
 
19
- SYSDIAGNOSE_PROCESS_NAMES = ('sysdiagnose', 'sysdiagnosed')
20
- SYSDIAGNOSE_DIR = 'DiagnosticLogs/sysdiagnose'
25
+ SYSDIAGNOSE_PROCESS_NAMES = ("sysdiagnose", "sysdiagnosed")
26
+ SYSDIAGNOSE_DIR = "DiagnosticLogs/sysdiagnose"
21
27
  SYSDIAGNOSE_IN_PROGRESS_MAX_TTL_SECS = 600
22
28
 
23
29
  # on iOS17, we need to wait for a moment before trying to fetch the sysdiagnose archive
@@ -25,14 +31,14 @@ IOS17_SYSDIAGNOSE_DELAY = 3
25
31
 
26
32
 
27
33
  class CrashReportsManager:
28
- COPY_MOBILE_NAME = 'com.apple.crashreportcopymobile'
29
- RSD_COPY_MOBILE_NAME = 'com.apple.crashreportcopymobile.shim.remote'
34
+ COPY_MOBILE_NAME = "com.apple.crashreportcopymobile"
35
+ RSD_COPY_MOBILE_NAME = "com.apple.crashreportcopymobile.shim.remote"
30
36
 
31
- CRASH_MOVER_NAME = 'com.apple.crashreportmover'
32
- RSD_CRASH_MOVER_NAME = 'com.apple.crashreportmover.shim.remote'
37
+ CRASH_MOVER_NAME = "com.apple.crashreportmover"
38
+ RSD_CRASH_MOVER_NAME = "com.apple.crashreportmover.shim.remote"
33
39
 
34
- APPSTORED_PATH = '/com.apple.appstored'
35
- IN_PROGRESS_SYSDIAGNOSE_EXTENSIONS = ['.tmp', '.tar.gz']
40
+ APPSTORED_PATH = "/com.apple.appstored"
41
+ IN_PROGRESS_SYSDIAGNOSE_EXTENSIONS: ClassVar = [".tmp", ".tar.gz"]
36
42
 
37
43
  def __init__(self, lockdown: LockdownServiceProvider):
38
44
  self.logger = logging.getLogger(__name__)
@@ -61,16 +67,16 @@ class CrashReportsManager:
61
67
  Clear all crash reports.
62
68
  """
63
69
  undeleted_items = []
64
- for filename in self.ls('/'):
70
+ for filename in self.ls("/"):
65
71
  undeleted_items.extend(self.afc.rm(filename, force=True))
66
72
 
67
73
  for item in undeleted_items:
68
74
  # special case of file that sometimes created automatically right after delete,
69
75
  # and then we can't delete the folder because it's not empty
70
76
  if item != self.APPSTORED_PATH:
71
- raise AfcException(f'failed to clear crash reports directory, undeleted items: {undeleted_items}', None)
77
+ raise AfcException(f"failed to clear crash reports directory, undeleted items: {undeleted_items}", None)
72
78
 
73
- def ls(self, path: str = '/', depth: int = 1) -> list[str]:
79
+ def ls(self, path: str = "/", depth: int = 1) -> list[str]:
74
80
  """
75
81
  List file and folder in the crash report's directory.
76
82
  :param path: Path to list, relative to the crash report's directory.
@@ -79,30 +85,32 @@ class CrashReportsManager:
79
85
  """
80
86
  return list(self.afc.dirlist(path, depth))[1:] # skip the root path '/'
81
87
 
82
- def pull(self, out: str, entry: str = '/', erase: bool = False, match: Optional[str] = None) -> None:
88
+ def pull(
89
+ self, out: str, entry: str = "/", erase: bool = False, match: Optional[str] = None, progress_bar: bool = True
90
+ ) -> None:
83
91
  """
84
92
  Pull crash reports from the device.
85
93
  :param out: Directory to pull crash reports to.
86
94
  :param entry: File or Folder to pull.
87
95
  :param erase: Whether to erase the original file from the CrashReports directory.
88
96
  :param match: Regex to match against file and directory names to pull.
97
+ :param progress_bar: Whether to show a progress bar when pulling large files.
89
98
  """
90
99
 
91
100
  def log(src: str, dst: str) -> None:
92
- self.logger.info(f'{src} --> {dst}')
93
- if erase:
94
- if not self.afc.isdir(src):
95
- self.afc.rm_single(src, force=True)
101
+ self.logger.info(f"{src} --> {dst}")
102
+ if erase and not self.afc.isdir(src):
103
+ self.afc.rm_single(src, force=True)
96
104
 
97
105
  match = None if match is None else re.compile(match)
98
- self.afc.pull(entry, out, match, callback=log)
106
+ self.afc.pull(entry, out, match, callback=log, progress_bar=progress_bar, ignore_errors=True)
99
107
 
100
108
  def flush(self) -> None:
101
- """ Trigger com.apple.crashreportmover to flush all products into CrashReports directory """
102
- ack = b'ping\x00'
109
+ """Trigger com.apple.crashreportmover to flush all products into CrashReports directory"""
110
+ ack = b"ping\x00"
103
111
  assert ack == self.lockdown.start_lockdown_service(self.crash_mover_service_name).recvall(len(ack))
104
112
 
105
- def watch(self, name: str = None, raw: bool = False) -> Generator[str, None, None]:
113
+ def watch(self, name: Optional[str] = None, raw: bool = False) -> Generator[str, None, None]:
106
114
  """
107
115
  Monitor creation of new crash reports for a given process name.
108
116
 
@@ -110,20 +118,28 @@ class CrashReportsManager:
110
118
  representation for the crash.
111
119
  """
112
120
  for syslog_entry in OsTraceService(lockdown=self.lockdown).syslog():
113
- if (posixpath.basename(syslog_entry.filename) != 'osanalyticshelper') or \
114
- (posixpath.basename(syslog_entry.image_name) != 'OSAnalytics') or \
115
- not syslog_entry.message.startswith('Saved type '):
121
+ if (
122
+ (posixpath.basename(syslog_entry.filename) != "osanalyticshelper")
123
+ or (posixpath.basename(syslog_entry.image_name) != "OSAnalytics")
124
+ or not syslog_entry.message.startswith("Saved type ")
125
+ ):
116
126
  # skip non-ips creation syslog lines
117
127
  continue
118
128
 
119
129
  filename = posixpath.basename(syslog_entry.message.split()[-1])
120
- self.logger.debug(f'crash report: {filename}')
130
+ self.logger.debug(f"crash report: {filename}")
121
131
 
122
- if posixpath.splitext(filename)[-1] not in ('.ips', '.panic'):
132
+ if posixpath.splitext(filename)[-1] not in (".ips", ".panic"):
123
133
  continue
124
134
 
125
- crash_report_raw = self.afc.get_file_contents(filename).decode()
126
- crash_report = get_crash_report_from_buf(crash_report_raw, filename=filename)
135
+ while True:
136
+ try:
137
+ crash_report_raw = self.afc.get_file_contents(filename).decode()
138
+ crash_report = get_crash_report_from_buf(crash_report_raw, filename=filename)
139
+ break
140
+ except (AfcFileNotFoundError, JSONDecodeError):
141
+ # Sometimes we have to wait for the file to be readable
142
+ pass
127
143
 
128
144
  if name is None or crash_report.name == name:
129
145
  if raw:
@@ -131,8 +147,14 @@ class CrashReportsManager:
131
147
  else:
132
148
  yield crash_report
133
149
 
134
- def get_new_sysdiagnose(self, out: str, erase: bool = True, *, timeout: Optional[float] = None,
135
- callback: Optional[Callable[[float], None]] = None) -> None:
150
+ def get_new_sysdiagnose(
151
+ self,
152
+ out: str,
153
+ erase: bool = True,
154
+ *,
155
+ timeout: Optional[float] = None,
156
+ callback: Optional[Callable[[float], None]] = None,
157
+ ) -> None:
136
158
  """
137
159
  Monitor the creation of a newly created sysdiagnose archive and pull it
138
160
  :param out: filename
@@ -150,7 +172,7 @@ class CrashReportsManager:
150
172
  if callback is not None:
151
173
  callback(time.monotonic() - start_time)
152
174
 
153
- self.logger.info('sysdiagnose tarball creation has been started')
175
+ self.logger.info("sysdiagnose tarball creation has been started")
154
176
  self._wait_for_sysdiagnose_to_finish(timeout)
155
177
 
156
178
  if callback is not None:
@@ -163,17 +185,17 @@ class CrashReportsManager:
163
185
 
164
186
  def _wait_for_sysdiagnose_to_finish(self, end_time: Optional[float] = None) -> None:
165
187
  with NotificationProxyService(self.lockdown, timeout=end_time) as service:
166
- stop_notification = 'com.apple.sysdiagnose.sysdiagnoseStopped'
188
+ stop_notification = "com.apple.sysdiagnose.sysdiagnoseStopped"
167
189
  service.notify_register_dispatch(stop_notification)
168
190
  try:
169
191
  for event in service.receive_notification():
170
- if event['Name'] != stop_notification:
192
+ if event["Name"] != stop_notification:
171
193
  continue
172
- self.logger.debug(f'Received {event}')
194
+ self.logger.debug(f"Received {event}")
173
195
  time.sleep(IOS17_SYSDIAGNOSE_DELAY)
174
196
  break
175
197
  except NotificationTimeoutError as e:
176
- raise SysdiagnoseTimeoutError('Timeout waiting for sysdiagnose completion') from e
198
+ raise SysdiagnoseTimeoutError("Timeout waiting for sysdiagnose completion") from e
177
199
 
178
200
  def _get_new_sysdiagnose_filename(self, end_time: Optional[float] = None) -> str:
179
201
  sysdiagnose_filename = None
@@ -183,17 +205,19 @@ class CrashReportsManager:
183
205
  try:
184
206
  for filename in self.afc.listdir(SYSDIAGNOSE_DIR):
185
207
  # search for an IN_PROGRESS archive
186
- if filename not in excluded_temp_files and 'IN_PROGRESS_' in filename:
208
+ if filename not in excluded_temp_files and "IN_PROGRESS_" in filename:
187
209
  for ext in self.IN_PROGRESS_SYSDIAGNOSE_EXTENSIONS:
188
210
  if filename.endswith(ext):
189
- delta = self.lockdown.date - \
190
- self.afc.stat(posixpath.join(SYSDIAGNOSE_DIR, filename))['st_mtime']
211
+ delta = (
212
+ self.lockdown.date
213
+ - self.afc.stat(posixpath.join(SYSDIAGNOSE_DIR, filename))["st_mtime"]
214
+ )
191
215
  # Ignores IN_PROGRESS sysdiagnose files older than the defined time to live
192
216
  if delta.total_seconds() < SYSDIAGNOSE_IN_PROGRESS_MAX_TTL_SECS:
193
- self.logger.debug(f'Detected in progress sysdiagnose {filename}')
217
+ self.logger.debug(f"Detected in progress sysdiagnose {filename}")
194
218
  sysdiagnose_filename = filename.rsplit(ext)[0]
195
- sysdiagnose_filename = sysdiagnose_filename.replace('IN_PROGRESS_', '')
196
- sysdiagnose_filename = f'{sysdiagnose_filename}.tar.gz'
219
+ sysdiagnose_filename = sysdiagnose_filename.replace("IN_PROGRESS_", "")
220
+ sysdiagnose_filename = f"{sysdiagnose_filename}.tar.gz"
197
221
  return posixpath.join(SYSDIAGNOSE_DIR, sysdiagnose_filename)
198
222
  else:
199
223
  self.logger.warning(f"Old sysdiagnose temp file ignored {filename}")
@@ -202,7 +226,7 @@ class CrashReportsManager:
202
226
  pass
203
227
 
204
228
  if self._check_timeout(end_time):
205
- raise SysdiagnoseTimeoutError('Timeout finding in-progress sysdiagnose filename')
229
+ raise SysdiagnoseTimeoutError("Timeout finding in-progress sysdiagnose filename")
206
230
 
207
231
  def _check_timeout(self, end_time: Optional[float] = None) -> bool:
208
232
  return end_time is not None and time.monotonic() > end_time
@@ -212,16 +236,16 @@ class CrashReportsShell(AfcShell):
212
236
  @classmethod
213
237
  def create(cls, service_provider: LockdownServiceProvider, **kwargs):
214
238
  manager = CrashReportsManager(service_provider)
215
- XSH.ctx['_manager'] = manager
239
+ XSH.ctx["_manager"] = manager
216
240
  super(CrashReportsShell, CrashReportsShell).create(service_provider, service=manager.afc)
217
241
 
218
242
  def _setup_shell_commands(self):
219
243
  super()._setup_shell_commands()
220
- self._register_arg_parse_alias('parse', self._do_parse)
221
- self._register_arg_parse_alias('clear', self._do_clear)
244
+ self._register_arg_parse_alias("parse", self._do_parse)
245
+ self._register_arg_parse_alias("clear", self._do_clear)
222
246
 
223
247
  def _do_parse(self, filename: Annotated[str, Arg(completer=path_completer)]) -> None:
224
248
  print(get_crash_report_from_buf(self.afc.get_file_contents(filename).decode(), filename=filename))
225
249
 
226
250
  def _do_clear(self) -> None:
227
- XSH.ctx['_manager'].clear()
251
+ XSH.ctx["_manager"].clear()
@@ -7,14 +7,14 @@ CHUNK_SIZE = 200
7
7
 
8
8
 
9
9
  class DebugServerAppList(LockdownService):
10
- SERVICE_NAME = 'com.apple.debugserver.DVTSecureSocketProxy.applist'
10
+ SERVICE_NAME = "com.apple.debugserver.DVTSecureSocketProxy.applist"
11
11
 
12
12
  def __init__(self, lockdown: LockdownClient):
13
13
  super().__init__(lockdown, self.SERVICE_NAME)
14
14
 
15
15
  def get(self) -> dict:
16
- buf = b''
17
- while b'</plist>' not in buf:
16
+ buf = b""
17
+ while b"</plist>" not in buf:
18
18
  buf += self.service.recv(CHUNK_SIZE)
19
19
 
20
20
  return plistlib.loads(buf)
@@ -4,25 +4,25 @@ from pymobiledevice3.services.lockdown_service import LockdownService
4
4
 
5
5
 
6
6
  class DtDeviceArbitration(LockdownService):
7
- SERVICE_NAME = 'com.apple.dt.devicearbitration'
7
+ SERVICE_NAME = "com.apple.dt.devicearbitration"
8
8
 
9
9
  def __init__(self, lockdown: LockdownClient):
10
10
  super().__init__(lockdown, self.SERVICE_NAME, is_developer_service=True)
11
11
 
12
12
  @property
13
13
  def version(self) -> dict:
14
- return self.service.send_recv_plist({'command': 'version'})
14
+ return self.service.send_recv_plist({"command": "version"})
15
15
 
16
16
  def check_in(self, hostname: str, force: bool = False):
17
- request = {'command': 'check-in', 'hostname': hostname}
17
+ request = {"command": "check-in", "hostname": hostname}
18
18
  if force:
19
- request['command'] = 'force-check-in'
19
+ request["command"] = "force-check-in"
20
20
  response = self.service.send_recv_plist(request)
21
- if response.get('result') != 'success':
21
+ if response.get("result") != "success":
22
22
  raise DeviceAlreadyInUseError(response)
23
23
 
24
24
  def check_out(self):
25
- request = {'command': 'check-out'}
25
+ request = {"command": "check-out"}
26
26
  response = self.service.send_recv_plist(request)
27
- if response.get('result') != 'success':
28
- raise ArbitrationError(f'failed with: {response}')
27
+ if response.get("result") != "success":
28
+ raise ArbitrationError(f"failed with: {response}")