pymobiledevice3 5.0.1__py3-none-any.whl → 5.0.2__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.

Potentially problematic release.


This version of pymobiledevice3 might be problematic. Click here for more details.

Files changed (143) hide show
  1. misc/plist_sniffer.py +15 -15
  2. misc/remotexpc_sniffer.py +29 -28
  3. pymobiledevice3/__main__.py +128 -102
  4. pymobiledevice3/_version.py +2 -2
  5. pymobiledevice3/bonjour.py +26 -49
  6. pymobiledevice3/ca.py +32 -24
  7. pymobiledevice3/cli/activation.py +7 -7
  8. pymobiledevice3/cli/afc.py +19 -19
  9. pymobiledevice3/cli/amfi.py +4 -4
  10. pymobiledevice3/cli/apps.py +51 -39
  11. pymobiledevice3/cli/backup.py +58 -32
  12. pymobiledevice3/cli/bonjour.py +25 -18
  13. pymobiledevice3/cli/cli_common.py +112 -81
  14. pymobiledevice3/cli/companion_proxy.py +4 -4
  15. pymobiledevice3/cli/completions.py +10 -10
  16. pymobiledevice3/cli/crash.py +37 -31
  17. pymobiledevice3/cli/developer.py +602 -520
  18. pymobiledevice3/cli/diagnostics.py +38 -33
  19. pymobiledevice3/cli/lockdown.py +79 -74
  20. pymobiledevice3/cli/mounter.py +85 -68
  21. pymobiledevice3/cli/notification.py +10 -10
  22. pymobiledevice3/cli/pcap.py +19 -14
  23. pymobiledevice3/cli/power_assertion.py +12 -10
  24. pymobiledevice3/cli/processes.py +10 -10
  25. pymobiledevice3/cli/profile.py +88 -77
  26. pymobiledevice3/cli/provision.py +17 -17
  27. pymobiledevice3/cli/remote.py +186 -110
  28. pymobiledevice3/cli/restore.py +43 -40
  29. pymobiledevice3/cli/springboard.py +30 -28
  30. pymobiledevice3/cli/syslog.py +85 -58
  31. pymobiledevice3/cli/usbmux.py +21 -20
  32. pymobiledevice3/cli/version.py +3 -2
  33. pymobiledevice3/cli/webinspector.py +157 -79
  34. pymobiledevice3/common.py +1 -1
  35. pymobiledevice3/exceptions.py +154 -60
  36. pymobiledevice3/irecv.py +49 -53
  37. pymobiledevice3/irecv_devices.py +1489 -492
  38. pymobiledevice3/lockdown.py +394 -241
  39. pymobiledevice3/lockdown_service_provider.py +5 -7
  40. pymobiledevice3/osu/os_utils.py +18 -9
  41. pymobiledevice3/osu/posix_util.py +28 -15
  42. pymobiledevice3/osu/win_util.py +14 -8
  43. pymobiledevice3/pair_records.py +19 -19
  44. pymobiledevice3/remote/common.py +4 -4
  45. pymobiledevice3/remote/core_device/app_service.py +94 -67
  46. pymobiledevice3/remote/core_device/core_device_service.py +17 -14
  47. pymobiledevice3/remote/core_device/device_info.py +5 -5
  48. pymobiledevice3/remote/core_device/diagnostics_service.py +10 -8
  49. pymobiledevice3/remote/core_device/file_service.py +47 -33
  50. pymobiledevice3/remote/remote_service_discovery.py +53 -35
  51. pymobiledevice3/remote/remotexpc.py +62 -41
  52. pymobiledevice3/remote/tunnel_service.py +371 -293
  53. pymobiledevice3/remote/utils.py +12 -11
  54. pymobiledevice3/remote/xpc_message.py +145 -125
  55. pymobiledevice3/resources/dsc_uuid_map.py +19 -19
  56. pymobiledevice3/resources/firmware_notifications.py +16 -16
  57. pymobiledevice3/restore/asr.py +27 -27
  58. pymobiledevice3/restore/base_restore.py +90 -47
  59. pymobiledevice3/restore/consts.py +87 -66
  60. pymobiledevice3/restore/device.py +11 -11
  61. pymobiledevice3/restore/fdr.py +46 -46
  62. pymobiledevice3/restore/ftab.py +19 -19
  63. pymobiledevice3/restore/img4.py +130 -133
  64. pymobiledevice3/restore/mbn.py +35 -54
  65. pymobiledevice3/restore/recovery.py +125 -135
  66. pymobiledevice3/restore/restore.py +524 -523
  67. pymobiledevice3/restore/restore_options.py +122 -115
  68. pymobiledevice3/restore/restored_client.py +25 -22
  69. pymobiledevice3/restore/tss.py +378 -270
  70. pymobiledevice3/service_connection.py +50 -46
  71. pymobiledevice3/services/accessibilityaudit.py +136 -126
  72. pymobiledevice3/services/afc.py +350 -291
  73. pymobiledevice3/services/amfi.py +21 -18
  74. pymobiledevice3/services/companion.py +23 -19
  75. pymobiledevice3/services/crash_reports.py +60 -46
  76. pymobiledevice3/services/debugserver_applist.py +3 -3
  77. pymobiledevice3/services/device_arbitration.py +8 -8
  78. pymobiledevice3/services/device_link.py +55 -47
  79. pymobiledevice3/services/diagnostics.py +971 -968
  80. pymobiledevice3/services/dtfetchsymbols.py +8 -8
  81. pymobiledevice3/services/dvt/dvt_secure_socket_proxy.py +4 -4
  82. pymobiledevice3/services/dvt/dvt_testmanaged_proxy.py +4 -4
  83. pymobiledevice3/services/dvt/instruments/activity_trace_tap.py +85 -74
  84. pymobiledevice3/services/dvt/instruments/application_listing.py +2 -3
  85. pymobiledevice3/services/dvt/instruments/condition_inducer.py +7 -6
  86. pymobiledevice3/services/dvt/instruments/core_profile_session_tap.py +442 -421
  87. pymobiledevice3/services/dvt/instruments/device_info.py +11 -11
  88. pymobiledevice3/services/dvt/instruments/energy_monitor.py +1 -1
  89. pymobiledevice3/services/dvt/instruments/graphics.py +1 -1
  90. pymobiledevice3/services/dvt/instruments/location_simulation.py +1 -1
  91. pymobiledevice3/services/dvt/instruments/location_simulation_base.py +10 -10
  92. pymobiledevice3/services/dvt/instruments/network_monitor.py +17 -17
  93. pymobiledevice3/services/dvt/instruments/notifications.py +1 -1
  94. pymobiledevice3/services/dvt/instruments/process_control.py +25 -10
  95. pymobiledevice3/services/dvt/instruments/screenshot.py +2 -2
  96. pymobiledevice3/services/dvt/instruments/sysmontap.py +15 -15
  97. pymobiledevice3/services/dvt/testmanaged/xcuitest.py +40 -50
  98. pymobiledevice3/services/file_relay.py +10 -10
  99. pymobiledevice3/services/heartbeat.py +8 -7
  100. pymobiledevice3/services/house_arrest.py +12 -15
  101. pymobiledevice3/services/installation_proxy.py +119 -100
  102. pymobiledevice3/services/lockdown_service.py +12 -5
  103. pymobiledevice3/services/misagent.py +22 -19
  104. pymobiledevice3/services/mobile_activation.py +84 -72
  105. pymobiledevice3/services/mobile_config.py +330 -301
  106. pymobiledevice3/services/mobile_image_mounter.py +137 -116
  107. pymobiledevice3/services/mobilebackup2.py +188 -150
  108. pymobiledevice3/services/notification_proxy.py +11 -11
  109. pymobiledevice3/services/os_trace.py +69 -51
  110. pymobiledevice3/services/pcapd.py +306 -306
  111. pymobiledevice3/services/power_assertion.py +10 -9
  112. pymobiledevice3/services/preboard.py +4 -4
  113. pymobiledevice3/services/remote_fetch_symbols.py +16 -14
  114. pymobiledevice3/services/remote_server.py +176 -146
  115. pymobiledevice3/services/restore_service.py +16 -16
  116. pymobiledevice3/services/screenshot.py +13 -10
  117. pymobiledevice3/services/simulate_location.py +7 -7
  118. pymobiledevice3/services/springboard.py +15 -15
  119. pymobiledevice3/services/syslog.py +5 -5
  120. pymobiledevice3/services/web_protocol/alert.py +3 -3
  121. pymobiledevice3/services/web_protocol/automation_session.py +180 -176
  122. pymobiledevice3/services/web_protocol/cdp_screencast.py +44 -36
  123. pymobiledevice3/services/web_protocol/cdp_server.py +19 -19
  124. pymobiledevice3/services/web_protocol/cdp_target.py +411 -373
  125. pymobiledevice3/services/web_protocol/driver.py +47 -45
  126. pymobiledevice3/services/web_protocol/element.py +74 -63
  127. pymobiledevice3/services/web_protocol/inspector_session.py +106 -102
  128. pymobiledevice3/services/web_protocol/selenium_api.py +2 -2
  129. pymobiledevice3/services/web_protocol/session_protocol.py +15 -10
  130. pymobiledevice3/services/web_protocol/switch_to.py +11 -12
  131. pymobiledevice3/services/webinspector.py +127 -116
  132. pymobiledevice3/tcp_forwarder.py +35 -22
  133. pymobiledevice3/tunneld/api.py +20 -15
  134. pymobiledevice3/tunneld/server.py +212 -133
  135. pymobiledevice3/usbmux.py +183 -138
  136. pymobiledevice3/utils.py +14 -11
  137. {pymobiledevice3-5.0.1.dist-info → pymobiledevice3-5.0.2.dist-info}/METADATA +1 -1
  138. pymobiledevice3-5.0.2.dist-info/RECORD +173 -0
  139. pymobiledevice3-5.0.1.dist-info/RECORD +0 -173
  140. {pymobiledevice3-5.0.1.dist-info → pymobiledevice3-5.0.2.dist-info}/WHEEL +0 -0
  141. {pymobiledevice3-5.0.1.dist-info → pymobiledevice3-5.0.2.dist-info}/entry_points.txt +0 -0
  142. {pymobiledevice3-5.0.1.dist-info → pymobiledevice3-5.0.2.dist-info}/licenses/LICENSE +0 -0
  143. {pymobiledevice3-5.0.1.dist-info → pymobiledevice3-5.0.2.dist-info}/top_level.txt +0 -0
misc/plist_sniffer.py CHANGED
@@ -7,8 +7,8 @@ import click
7
7
  from scapy.packet import Packet, Raw
8
8
  from scapy.sendrecv import sniff
9
9
 
10
- BPLIST_MAGIC = b'bplist'
11
- PLIST_MAGIC = b'<plist'
10
+ BPLIST_MAGIC = b"bplist"
11
+ PLIST_MAGIC = b"<plist"
12
12
 
13
13
 
14
14
  class PcapSniffer:
@@ -20,13 +20,13 @@ class PcapSniffer:
20
20
 
21
21
  if BPLIST_MAGIC in packet:
22
22
  try:
23
- plist = plistlib.loads(packet[packet.find(BPLIST_MAGIC):])
23
+ plist = plistlib.loads(packet[packet.find(BPLIST_MAGIC) :])
24
24
  self.report(plist)
25
25
  except plistlib.InvalidFileException:
26
26
  pass
27
27
  if PLIST_MAGIC in packet:
28
28
  try:
29
- plist = plistlib.loads(packet[packet.find(PLIST_MAGIC):])
29
+ plist = plistlib.loads(packet[packet.find(PLIST_MAGIC) :])
30
30
  self.report(plist)
31
31
  except xml.parsers.expat.ExpatError:
32
32
  pass
@@ -35,37 +35,37 @@ class PcapSniffer:
35
35
  try:
36
36
  print(plist)
37
37
  if self.file is not None:
38
- self.file.write('---\n')
38
+ self.file.write("---\n")
39
39
  self.file.write(pprint.pformat(plist))
40
- self.file.write('\n---\n')
40
+ self.file.write("\n---\n")
41
41
  except ValueError:
42
- print('failed to print plist')
42
+ print("failed to print plist")
43
43
 
44
44
 
45
45
  @click.group()
46
46
  def cli():
47
- """ Parse RemoteXPC traffic """
47
+ """Parse RemoteXPC traffic"""
48
48
  pass
49
49
 
50
50
 
51
51
  @cli.command()
52
- @click.argument('pcap', type=click.Path(exists=True, file_okay=True, dir_okay=False))
53
- @click.option('-o', '--out', type=click.File('wt'))
52
+ @click.argument("pcap", type=click.Path(exists=True, file_okay=True, dir_okay=False))
53
+ @click.option("-o", "--out", type=click.File("wt"))
54
54
  def offline(pcap: str, out: IO):
55
- """ Parse plists traffic from a .pcap file """
55
+ """Parse plists traffic from a .pcap file"""
56
56
  sniffer = PcapSniffer(out)
57
57
  for p in sniff(offline=pcap):
58
58
  sniffer.process_packet(p)
59
59
 
60
60
 
61
61
  @cli.command()
62
- @click.argument('iface')
63
- @click.option('-o', '--out', type=click.File('wt'))
62
+ @click.argument("iface")
63
+ @click.option("-o", "--out", type=click.File("wt"))
64
64
  def live(iface: str, out: IO):
65
- """ Parse plists live from a given network interface """
65
+ """Parse plists live from a given network interface"""
66
66
  sniffer = PcapSniffer(out)
67
67
  sniff(iface=iface, prn=sniffer.process_packet)
68
68
 
69
69
 
70
- if __name__ == '__main__':
70
+ if __name__ == "__main__":
71
71
  cli()
misc/remotexpc_sniffer.py CHANGED
@@ -22,7 +22,7 @@ FRAME_HEADER_SIZE = 9
22
22
 
23
23
 
24
24
  def create_stream_key(src: str, sport: int, dst: str, dport: int) -> str:
25
- return f'{src}/{sport}//{dst}/{dport}'
25
+ return f"{src}/{sport}//{dst}/{dport}"
26
26
 
27
27
 
28
28
  class TCPStream:
@@ -37,7 +37,7 @@ class TCPStream:
37
37
  self.segments = {} # data segments to add later
38
38
 
39
39
  def __repr__(self) -> str:
40
- return f'Stream<{self.key}>'
40
+ return f"Stream<{self.key}>"
41
41
 
42
42
  def __len__(self) -> int:
43
43
  return len(self.data)
@@ -58,12 +58,12 @@ class TCPStream:
58
58
  return False
59
59
  else:
60
60
  # if this data is in order (has a place to be inserted)
61
- self.data[seq_offset:seq_offset + data_len] = data
61
+ self.data[seq_offset : seq_offset + data_len] = data
62
62
  # check if there are any waiting data segments to add
63
63
  for seq_offset in sorted(self.segments.keys()):
64
64
  if seq_offset <= len(self.data): # if we can add this segment to the stream
65
65
  segment_payload = self.segments[seq_offset]
66
- self.data[seq_offset:seq_offset + len(segment_payload)] = segment_payload
66
+ self.data[seq_offset : seq_offset + len(segment_payload)] = segment_payload
67
67
  self.segments.pop(seq_offset)
68
68
  else:
69
69
  break # short circuit because list is sorted
@@ -72,12 +72,12 @@ class TCPStream:
72
72
 
73
73
  class H2Stream(TCPStream):
74
74
  def pop_frames(self) -> list[Frame]:
75
- """ Pop all available H2Frames """
75
+ """Pop all available H2Frames"""
76
76
 
77
77
  # If self.data starts with the http/2 magic bytes, pop them off
78
78
  if self.data.startswith(HTTP2_MAGIC):
79
- logger.debug('HTTP/2 magic bytes')
80
- self.data = self.data[len(HTTP2_MAGIC):]
79
+ logger.debug("HTTP/2 magic bytes")
80
+ self.data = self.data[len(HTTP2_MAGIC) :]
81
81
  self.seq += len(HTTP2_MAGIC)
82
82
 
83
83
  frames = []
@@ -120,13 +120,14 @@ class RemoteXPCSniffer:
120
120
  tcp_pkt = pkt[TCP]
121
121
  stream_key = create_stream_key(net_pkt.src, tcp_pkt.sport, net_pkt.dst, tcp_pkt.dport)
122
122
  stream = self._h2_streams.setdefault(
123
- stream_key, H2Stream(net_pkt.src, tcp_pkt.sport, net_pkt.dst, tcp_pkt.dport))
123
+ stream_key, H2Stream(net_pkt.src, tcp_pkt.sport, net_pkt.dst, tcp_pkt.dport)
124
+ )
124
125
  stream_finished_assembling = stream.add(tcp_pkt)
125
126
  if stream_finished_assembling: # if we just added something in order
126
127
  self._process_stream(stream)
127
128
 
128
129
  def _handle_data_frame(self, stream: H2Stream, frame: DataFrame) -> None:
129
- previous_frame_data = self._previous_frame_data.get(stream.key, b'')
130
+ previous_frame_data = self._previous_frame_data.get(stream.key, b"")
130
131
  try:
131
132
  payload = XpcWrapper.parse(previous_frame_data + frame.data).message.payload
132
133
  if payload is None:
@@ -134,10 +135,11 @@ class RemoteXPCSniffer:
134
135
  xpc_message = decode_xpc_object(payload.obj)
135
136
  except ConstError: # if we don't know what this payload is
136
137
  logger.debug(
137
- f'New Data frame {stream.src}->{stream.dst} on HTTP/2 stream {frame.stream_id} TCP port {stream.dport}')
138
+ f"New Data frame {stream.src}->{stream.dst} on HTTP/2 stream {frame.stream_id} TCP port {stream.dport}"
139
+ )
138
140
  hexdump(frame.data[:64])
139
141
  if len(frame.data) > 64:
140
- logger.debug(f'... {len(frame.data)} bytes')
142
+ logger.debug(f"... {len(frame.data)} bytes")
141
143
  return
142
144
  except StreamError:
143
145
  self._previous_frame_data[stream.key] = previous_frame_data + frame.data
@@ -149,27 +151,26 @@ class RemoteXPCSniffer:
149
151
  if xpc_message is None:
150
152
  return
151
153
 
152
- logger.info(f'As Python Object (#{frame.stream_id}): {pformat(xpc_message)}')
154
+ logger.info(f"As Python Object (#{frame.stream_id}): {pformat(xpc_message)}")
153
155
 
154
156
  # print `pairingData` if exists, since it contains an inner struct
155
- if 'value' not in xpc_message:
157
+ if "value" not in xpc_message:
156
158
  return
157
- message = xpc_message['value']['message']
158
- if 'plain' not in message:
159
+ message = xpc_message["value"]["message"]
160
+ if "plain" not in message:
159
161
  return
160
- plain = message['plain']['_0']
161
- if 'event' not in plain:
162
+ plain = message["plain"]["_0"]
163
+ if "event" not in plain:
162
164
  return
163
- pairing_data = plain['event']['_0']['pairingData']['_0']['data']
165
+ pairing_data = plain["event"]["_0"]["pairingData"]["_0"]["data"]
164
166
  logger.info(PairingDataComponentTLVBuf.parse(pairing_data))
165
167
 
166
168
  def _handle_single_frame(self, stream: H2Stream, frame: Frame) -> None:
167
- logger.debug(f'New HTTP/2 frame: {stream.key} ({frame})')
169
+ logger.debug(f"New HTTP/2 frame: {stream.key} ({frame})")
168
170
  if isinstance(frame, HeadersFrame):
169
- logger.debug(
170
- f'{stream.src} opening stream {frame.stream_id} for communication on port {stream.dport}')
171
+ logger.debug(f"{stream.src} opening stream {frame.stream_id} for communication on port {stream.dport}")
171
172
  elif isinstance(frame, GoAwayFrame):
172
- logger.debug(f'{stream.src} closing stream {frame.stream_id} on port {stream.sport}')
173
+ logger.debug(f"{stream.src} closing stream {frame.stream_id} on port {stream.sport}")
173
174
  elif isinstance(frame, DataFrame):
174
175
  self._handle_data_frame(stream, frame)
175
176
 
@@ -180,27 +181,27 @@ class RemoteXPCSniffer:
180
181
 
181
182
  @click.group()
182
183
  def cli():
183
- """ Parse RemoteXPC traffic """
184
+ """Parse RemoteXPC traffic"""
184
185
  pass
185
186
 
186
187
 
187
188
  @cli.command()
188
- @click.argument('file', type=click.Path(exists=True, file_okay=True, dir_okay=False))
189
+ @click.argument("file", type=click.Path(exists=True, file_okay=True, dir_okay=False))
189
190
  def offline(file: str):
190
- """ Parse RemoteXPC traffic from a .pcap file """
191
+ """Parse RemoteXPC traffic from a .pcap file"""
191
192
  sniffer = RemoteXPCSniffer()
192
193
  for p in sniff(offline=file):
193
194
  sniffer.process_packet(p)
194
195
 
195
196
 
196
197
  @cli.command()
197
- @click.argument('iface')
198
+ @click.argument("iface")
198
199
  def live(iface: str):
199
- """ Parse RemoteXPC live from a given network interface """
200
+ """Parse RemoteXPC live from a given network interface"""
200
201
  sniffer = RemoteXPCSniffer()
201
202
  sniff(iface=iface, prn=sniffer.process_packet)
202
203
 
203
204
 
204
- if __name__ == '__main__':
205
+ if __name__ == "__main__":
205
206
  coloredlogs.install(level=logging.DEBUG)
206
207
  cli()
@@ -13,33 +13,53 @@ import click
13
13
  import coloredlogs
14
14
 
15
15
  from pymobiledevice3.cli.cli_common import TUNNEL_ENV_VAR, isatty
16
- from pymobiledevice3.exceptions import AccessDeniedError, CloudConfigurationAlreadyPresentError, \
17
- ConnectionFailedError, ConnectionFailedToUsbmuxdError, DeprecationError, DeveloperModeError, \
18
- DeveloperModeIsNotEnabledError, DeviceHasPasscodeSetError, DeviceNotFoundError, FeatureNotSupportedError, \
19
- InternalError, InvalidServiceError, MessageNotSupportedError, MissingValueError, NoDeviceConnectedError, \
20
- NotEnoughDiskSpaceError, NotPairedError, OSNotSupportedError, PairingDialogResponsePendingError, \
21
- PasswordRequiredError, QuicProtocolNotSupportedError, RSDRequiredError, SetProhibitedError, \
22
- TunneldConnectionError, UserDeniedPairingError
16
+ from pymobiledevice3.exceptions import (
17
+ AccessDeniedError,
18
+ CloudConfigurationAlreadyPresentError,
19
+ ConnectionFailedError,
20
+ ConnectionFailedToUsbmuxdError,
21
+ DeprecationError,
22
+ DeveloperModeError,
23
+ DeveloperModeIsNotEnabledError,
24
+ DeviceHasPasscodeSetError,
25
+ DeviceNotFoundError,
26
+ FeatureNotSupportedError,
27
+ InternalError,
28
+ InvalidServiceError,
29
+ MessageNotSupportedError,
30
+ MissingValueError,
31
+ NoDeviceConnectedError,
32
+ NotEnoughDiskSpaceError,
33
+ NotPairedError,
34
+ OSNotSupportedError,
35
+ PairingDialogResponsePendingError,
36
+ PasswordRequiredError,
37
+ QuicProtocolNotSupportedError,
38
+ RSDRequiredError,
39
+ SetProhibitedError,
40
+ TunneldConnectionError,
41
+ UserDeniedPairingError,
42
+ )
23
43
  from pymobiledevice3.lockdown import retry_create_using_usbmux
24
44
  from pymobiledevice3.osu.os_utils import get_os_utils
25
45
 
26
46
  coloredlogs.install(level=logging.INFO)
27
47
 
28
- logging.getLogger('quic').disabled = True
29
- logging.getLogger('asyncio').disabled = True
30
- logging.getLogger('parso.cache').disabled = True
31
- logging.getLogger('parso.cache.pickle').disabled = True
32
- logging.getLogger('parso.python.diff').disabled = True
33
- logging.getLogger('humanfriendly.prompts').disabled = True
34
- logging.getLogger('blib2to3.pgen2.driver').disabled = True
35
- logging.getLogger('urllib3.connectionpool').disabled = True
48
+ logging.getLogger("quic").disabled = True
49
+ logging.getLogger("asyncio").disabled = True
50
+ logging.getLogger("parso.cache").disabled = True
51
+ logging.getLogger("parso.cache.pickle").disabled = True
52
+ logging.getLogger("parso.python.diff").disabled = True
53
+ logging.getLogger("humanfriendly.prompts").disabled = True
54
+ logging.getLogger("blib2to3.pgen2.driver").disabled = True
55
+ logging.getLogger("urllib3.connectionpool").disabled = True
36
56
 
37
57
  logger = logging.getLogger(__name__)
38
58
 
39
59
  # For issue https://github.com/doronz88/pymobiledevice3/issues/1217, details: https://bugs.python.org/issue37373
40
- if sys.platform == 'win32':
60
+ if sys.platform == "win32":
41
61
  with warnings.catch_warnings():
42
- warnings.simplefilter('ignore', category=DeprecationWarning)
62
+ warnings.simplefilter("ignore", category=DeprecationWarning)
43
63
  asyncio.set_event_loop_policy(asyncio.WindowsSelectorEventLoopPolicy())
44
64
 
45
65
  INVALID_SERVICE_MESSAGE = """Failed to start service. Possible reasons are:
@@ -61,36 +81,36 @@ INVALID_SERVICE_MESSAGE = """Failed to start service. Possible reasons are:
61
81
  https://github.com/doronz88/pymobiledevice3/issues/new?assignees=&labels=&projects=&template=bug_report.md&title=
62
82
  """
63
83
 
64
- CONTEXT_SETTINGS = dict(help_option_names=['-h', '--help'], max_content_width=400)
84
+ CONTEXT_SETTINGS = {"help_option_names": ["-h", "--help"], "max_content_width": 400}
65
85
 
66
86
  # Mapping of index options to import file names
67
87
  CLI_GROUPS = {
68
- 'activation': 'activation',
69
- 'afc': 'afc',
70
- 'amfi': 'amfi',
71
- 'apps': 'apps',
72
- 'backup2': 'backup',
73
- 'bonjour': 'bonjour',
74
- 'companion': 'companion_proxy',
75
- 'crash': 'crash',
76
- 'developer': 'developer',
77
- 'diagnostics': 'diagnostics',
78
- 'lockdown': 'lockdown',
79
- 'mounter': 'mounter',
80
- 'notification': 'notification',
81
- 'pcap': 'pcap',
82
- 'power-assertion': 'power_assertion',
83
- 'processes': 'processes',
84
- 'profile': 'profile',
85
- 'provision': 'provision',
86
- 'remote': 'remote',
87
- 'restore': 'restore',
88
- 'springboard': 'springboard',
89
- 'syslog': 'syslog',
90
- 'usbmux': 'usbmux',
91
- 'webinspector': 'webinspector',
92
- 'version': 'version',
93
- 'install-completions': 'completions',
88
+ "activation": "activation",
89
+ "afc": "afc",
90
+ "amfi": "amfi",
91
+ "apps": "apps",
92
+ "backup2": "backup",
93
+ "bonjour": "bonjour",
94
+ "companion": "companion_proxy",
95
+ "crash": "crash",
96
+ "developer": "developer",
97
+ "diagnostics": "diagnostics",
98
+ "lockdown": "lockdown",
99
+ "mounter": "mounter",
100
+ "notification": "notification",
101
+ "pcap": "pcap",
102
+ "power-assertion": "power_assertion",
103
+ "processes": "processes",
104
+ "profile": "profile",
105
+ "provision": "provision",
106
+ "remote": "remote",
107
+ "restore": "restore",
108
+ "springboard": "springboard",
109
+ "syslog": "syslog",
110
+ "usbmux": "usbmux",
111
+ "webinspector": "webinspector",
112
+ "version": "version",
113
+ "install-completions": "completions",
94
114
  }
95
115
 
96
116
  # Set if used the `--reconnect` option
@@ -102,26 +122,26 @@ class Pmd3Cli(click.Group):
102
122
  return CLI_GROUPS.keys()
103
123
 
104
124
  def get_command(self, ctx: click.Context, name: str) -> click.Command:
105
- if name not in CLI_GROUPS.keys():
125
+ if name not in CLI_GROUPS:
106
126
  self.handle_invalid_command(ctx, name)
107
127
  return self.import_and_get_command(ctx, name)
108
128
 
109
129
  def handle_invalid_command(self, ctx: click.Context, name: str) -> None:
110
130
  suggested_commands = self.search_commands(name)
111
131
  suggestion = self.format_suggestions(suggested_commands)
112
- ctx.fail(f'No such command {name!r}{suggestion}')
132
+ ctx.fail(f"No such command {name!r}{suggestion}")
113
133
 
114
134
  @staticmethod
115
135
  def format_suggestions(suggestions: list[str]) -> str:
116
136
  if not suggestions:
117
- return ''
118
- cmds = textwrap.indent('\n'.join(suggestions), ' ' * 4)
119
- return f'\nDid you mean this?\n{cmds}'
137
+ return ""
138
+ cmds = textwrap.indent("\n".join(suggestions), " " * 4)
139
+ return f"\nDid you mean this?\n{cmds}"
120
140
 
121
141
  @staticmethod
122
142
  def import_and_get_command(ctx: click.Context, name: str) -> click.Command:
123
- module_name = f'pymobiledevice3.cli.{CLI_GROUPS[name]}'
124
- mod = __import__(module_name, None, None, ['cli'])
143
+ module_name = f"pymobiledevice3.cli.{CLI_GROUPS[name]}"
144
+ mod = __import__(module_name, None, None, ["cli"])
125
145
  command = mod.cli.get_command(ctx, name)
126
146
  if not command:
127
147
  command_name = mod.cli.list_commands(ctx)[0]
@@ -130,20 +150,20 @@ class Pmd3Cli(click.Group):
130
150
 
131
151
  @staticmethod
132
152
  def highlight_keyword(text: str, keyword: str) -> str:
133
- return re.sub(f'({keyword})', click.style('\\1', bold=True), text, flags=re.IGNORECASE)
153
+ return re.sub(f"({keyword})", click.style("\\1", bold=True), text, flags=re.IGNORECASE)
134
154
 
135
155
  @staticmethod
136
156
  def collect_commands(command: click.Command) -> Union[str, list[str]]:
137
157
  commands = []
138
158
  if isinstance(command, click.Group):
139
- for k, v in command.commands.items():
159
+ for _k, v in command.commands.items():
140
160
  cmd = Pmd3Cli.collect_commands(v)
141
161
  if isinstance(cmd, list):
142
- commands.extend([f'{command.name} {c}' for c in cmd])
162
+ commands.extend([f"{command.name} {c}" for c in cmd])
143
163
  else:
144
- commands.append(f'{command.name} {cmd}')
164
+ commands.append(f"{command.name} {cmd}")
145
165
  return commands
146
- return f'{command.name}'
166
+ return f"{command.name}"
147
167
 
148
168
  @staticmethod
149
169
  def search_commands(pattern: str) -> list[str]:
@@ -158,9 +178,9 @@ class Pmd3Cli(click.Group):
158
178
  @staticmethod
159
179
  def load_all_commands() -> list[str]:
160
180
  all_commands = []
161
- for key in CLI_GROUPS.keys():
162
- module_name = f'pymobiledevice3.cli.{CLI_GROUPS[key]}'
163
- mod = __import__(module_name, None, None, ['cli'])
181
+ for key in CLI_GROUPS:
182
+ module_name = f"pymobiledevice3.cli.{CLI_GROUPS[key]}"
183
+ mod = __import__(module_name, None, None, ["cli"])
164
184
  cmd = Pmd3Cli.collect_commands(mod.cli.commands[key])
165
185
  if isinstance(cmd, list):
166
186
  all_commands.extend(cmd)
@@ -170,7 +190,7 @@ class Pmd3Cli(click.Group):
170
190
 
171
191
 
172
192
  @click.command(cls=Pmd3Cli, context_settings=CONTEXT_SETTINGS)
173
- @click.option('--reconnect', is_flag=True, default=False, help='Reconnect to device when disconnected.')
193
+ @click.option("--reconnect", is_flag=True, default=False, help="Reconnect to device when disconnected.")
174
194
  def cli(reconnect: bool) -> None:
175
195
  """
176
196
  \b
@@ -190,81 +210,87 @@ def invoke_cli_with_error_handling() -> bool:
190
210
  try:
191
211
  cli()
192
212
  except NoDeviceConnectedError:
193
- logger.error('Device is not connected')
213
+ logger.exception("Device is not connected")
194
214
  return True
195
215
  except ConnectionAbortedError:
196
- logger.error('Device was disconnected')
216
+ logger.exception("Device was disconnected")
197
217
  return True
198
218
  except NotPairedError:
199
- logger.error('Device is not paired')
219
+ logger.exception("Device is not paired")
200
220
  except UserDeniedPairingError:
201
- logger.error('User refused to trust this computer')
221
+ logger.exception("User refused to trust this computer")
202
222
  except PairingDialogResponsePendingError:
203
- logger.error('Waiting for user dialog approval')
223
+ logger.exception("Waiting for user dialog approval")
204
224
  except SetProhibitedError:
205
- logger.error('lockdownd denied the access')
225
+ logger.exception("lockdownd denied the access")
206
226
  except MissingValueError:
207
- logger.error('No such value')
227
+ logger.exception("No such value")
208
228
  except DeviceHasPasscodeSetError:
209
- logger.error('Cannot enable developer-mode when passcode is set')
210
- except DeveloperModeError as e:
211
- logger.error(f'Failed to enable developer-mode. Error: {e}')
229
+ logger.exception("Cannot enable developer-mode when passcode is set")
230
+ except DeveloperModeError:
231
+ logger.exception("Failed to enable developer-mode.")
212
232
  except ConnectionFailedToUsbmuxdError:
213
- logger.error('Failed to connect to usbmuxd socket. Make sure it\'s running.')
233
+ logger.exception("Failed to connect to usbmuxd socket. Make sure it's running.")
214
234
  except ConnectionFailedError:
215
- logger.error('Failed to connect to service port.')
235
+ logger.exception("Failed to connect to service port.")
216
236
  return True
217
237
  except MessageNotSupportedError:
218
- logger.error('Message not supported for this iOS version')
238
+ logger.exception("Message not supported for this iOS version")
219
239
  traceback.print_exc()
220
240
  except InternalError:
221
- logger.error('Internal Error')
241
+ logger.exception("Internal Error")
222
242
  except DeveloperModeIsNotEnabledError:
223
- logger.error('Developer Mode is disabled. You can try to enable it using: '
224
- 'python3 -m pymobiledevice3 amfi enable-developer-mode')
243
+ logger.exception(
244
+ "Developer Mode is disabled. You can try to enable it using: "
245
+ "python3 -m pymobiledevice3 amfi enable-developer-mode"
246
+ )
225
247
  except (InvalidServiceError, RSDRequiredError) as e:
226
248
  should_retry_over_tunneld = False
227
249
  if isinstance(e, RSDRequiredError):
228
- logger.warning('Trying again over tunneld since RSD is required for this command')
250
+ logger.warning("Trying again over tunneld since RSD is required for this command")
229
251
  should_retry_over_tunneld = True
230
- elif (e.identifier is not None) and ('developer' in sys.argv) and ('--tunnel' not in sys.argv):
231
- logger.warning('Got an InvalidServiceError. Trying again over tunneld since it is a developer command')
252
+ elif (e.identifier is not None) and ("developer" in sys.argv) and ("--tunnel" not in sys.argv):
253
+ logger.warning("Got an InvalidServiceError. Trying again over tunneld since it is a developer command")
232
254
  should_retry_over_tunneld = True
233
255
  if should_retry_over_tunneld:
234
256
  # use a single space because click will ignore envvars of empty strings
235
- os.environ[TUNNEL_ENV_VAR] = e.identifier or ' '
257
+ os.environ[TUNNEL_ENV_VAR] = e.identifier or " "
236
258
  return main()
237
- logger.error(INVALID_SERVICE_MESSAGE)
259
+ logger.exception(INVALID_SERVICE_MESSAGE)
238
260
  except PasswordRequiredError:
239
- logger.error('Device is password protected. Please unlock and retry')
261
+ logger.exception("Device is password protected. Please unlock and retry")
240
262
  except AccessDeniedError:
241
- logger.error(get_os_utils().access_denied_error)
263
+ logger.exception(get_os_utils().access_denied_error)
242
264
  except BrokenPipeError:
243
265
  traceback.print_exc()
244
266
  except TunneldConnectionError:
245
- logger.error(
246
- 'Unable to connect to Tunneld. You can start one using:\n'
247
- 'sudo python3 -m pymobiledevice3 remote tunneld')
267
+ logger.exception(
268
+ "Unable to connect to Tunneld. You can start one using:\nsudo python3 -m pymobiledevice3 remote tunneld"
269
+ )
248
270
  except DeviceNotFoundError as e:
249
- logger.error(f'Device not found: {e.udid}')
271
+ logger.exception(f"Device not found: {e.udid}")
250
272
  except NotEnoughDiskSpaceError:
251
- logger.error('Not enough disk space')
273
+ logger.exception("Not enough disk space")
252
274
  except DeprecationError:
253
- logger.error('failed to query MobileGestalt, MobileGestalt deprecated (iOS >= 17.4).')
275
+ logger.exception("failed to query MobileGestalt, MobileGestalt deprecated (iOS >= 17.4).")
254
276
  except OSNotSupportedError as e:
255
- logger.error(
256
- f'Unsupported OS - {e.os_name}. To add support, consider contributing at '
257
- f'https://github.com/doronz88/pymobiledevice3.')
277
+ logger.exception(
278
+ f"Unsupported OS - {e.os_name}. To add support, consider contributing at "
279
+ f"https://github.com/doronz88/pymobiledevice3."
280
+ )
258
281
  except CloudConfigurationAlreadyPresentError:
259
- logger.error('A cloud configuration is already present on device. You must first erase the device in order '
260
- 'to install new one:\n'
261
- '> pymobiledevice3 profile erase-device')
282
+ logger.exception(
283
+ "A cloud configuration is already present on device. You must first erase the device in order "
284
+ "to install new one:\n"
285
+ "> pymobiledevice3 profile erase-device"
286
+ )
262
287
  except FeatureNotSupportedError as e:
263
- logger.error(
264
- f'Missing implementation of `{e.feature}` on `{e.os_name}`. To add support, consider contributing at '
265
- f'https://github.com/doronz88/pymobiledevice3.')
266
- except QuicProtocolNotSupportedError as e:
267
- logger.error(str(e))
288
+ logger.exception(
289
+ f"Missing implementation of `{e.feature}` on `{e.os_name}`. To add support, consider contributing at "
290
+ f"https://github.com/doronz88/pymobiledevice3."
291
+ )
292
+ except QuicProtocolNotSupportedError:
293
+ logger.exception("Encountered a QUIC protocol error.")
268
294
 
269
295
  return False
270
296
 
@@ -281,9 +307,9 @@ def main() -> None:
281
307
  lockdown = retry_create_using_usbmux(None)
282
308
  lockdown.close()
283
309
  except KeyboardInterrupt:
284
- print('Aborted.')
310
+ print("Aborted.")
285
311
  break
286
312
 
287
313
 
288
- if __name__ == '__main__':
314
+ if __name__ == "__main__":
289
315
  main()
@@ -28,7 +28,7 @@ version_tuple: VERSION_TUPLE
28
28
  commit_id: COMMIT_ID
29
29
  __commit_id__: COMMIT_ID
30
30
 
31
- __version__ = version = '5.0.1'
32
- __version_tuple__ = version_tuple = (5, 0, 1)
31
+ __version__ = version = '5.0.2'
32
+ __version_tuple__ = version_tuple = (5, 0, 2)
33
33
 
34
34
  __commit_id__ = commit_id = None