pymobiledevice3 5.0.4__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 (79) hide show
  1. misc/understanding_idevice_protocol_layers.md +10 -5
  2. pymobiledevice3/__main__.py +171 -46
  3. pymobiledevice3/_version.py +2 -2
  4. pymobiledevice3/bonjour.py +22 -21
  5. pymobiledevice3/cli/activation.py +24 -22
  6. pymobiledevice3/cli/afc.py +49 -41
  7. pymobiledevice3/cli/amfi.py +13 -18
  8. pymobiledevice3/cli/apps.py +71 -65
  9. pymobiledevice3/cli/backup.py +134 -93
  10. pymobiledevice3/cli/bonjour.py +31 -29
  11. pymobiledevice3/cli/cli_common.py +175 -232
  12. pymobiledevice3/cli/companion_proxy.py +12 -12
  13. pymobiledevice3/cli/crash.py +95 -52
  14. pymobiledevice3/cli/developer/__init__.py +62 -0
  15. pymobiledevice3/cli/developer/accessibility/__init__.py +65 -0
  16. pymobiledevice3/cli/developer/accessibility/settings.py +43 -0
  17. pymobiledevice3/cli/developer/arbitration.py +50 -0
  18. pymobiledevice3/cli/developer/condition.py +33 -0
  19. pymobiledevice3/cli/developer/core_device.py +294 -0
  20. pymobiledevice3/cli/developer/debugserver.py +244 -0
  21. pymobiledevice3/cli/developer/dvt/__init__.py +438 -0
  22. pymobiledevice3/cli/developer/dvt/core_profile_session.py +295 -0
  23. pymobiledevice3/cli/developer/dvt/simulate_location.py +56 -0
  24. pymobiledevice3/cli/developer/dvt/sysmon/__init__.py +69 -0
  25. pymobiledevice3/cli/developer/dvt/sysmon/process.py +188 -0
  26. pymobiledevice3/cli/developer/fetch_symbols.py +108 -0
  27. pymobiledevice3/cli/developer/simulate_location.py +51 -0
  28. pymobiledevice3/cli/diagnostics/__init__.py +75 -0
  29. pymobiledevice3/cli/diagnostics/battery.py +47 -0
  30. pymobiledevice3/cli/idam.py +42 -0
  31. pymobiledevice3/cli/lockdown.py +70 -75
  32. pymobiledevice3/cli/mounter.py +99 -57
  33. pymobiledevice3/cli/notification.py +38 -26
  34. pymobiledevice3/cli/pcap.py +36 -20
  35. pymobiledevice3/cli/power_assertion.py +15 -16
  36. pymobiledevice3/cli/processes.py +11 -17
  37. pymobiledevice3/cli/profile.py +120 -75
  38. pymobiledevice3/cli/provision.py +27 -26
  39. pymobiledevice3/cli/remote.py +109 -100
  40. pymobiledevice3/cli/restore.py +134 -129
  41. pymobiledevice3/cli/springboard.py +50 -50
  42. pymobiledevice3/cli/syslog.py +145 -65
  43. pymobiledevice3/cli/usbmux.py +66 -27
  44. pymobiledevice3/cli/version.py +2 -5
  45. pymobiledevice3/cli/webinspector.py +232 -156
  46. pymobiledevice3/exceptions.py +6 -2
  47. pymobiledevice3/lockdown.py +5 -1
  48. pymobiledevice3/lockdown_service_provider.py +5 -0
  49. pymobiledevice3/remote/remote_service_discovery.py +18 -10
  50. pymobiledevice3/restore/device.py +28 -4
  51. pymobiledevice3/restore/restore.py +2 -2
  52. pymobiledevice3/service_connection.py +15 -12
  53. pymobiledevice3/services/afc.py +731 -220
  54. pymobiledevice3/services/device_link.py +45 -31
  55. pymobiledevice3/services/idam.py +20 -0
  56. pymobiledevice3/services/lockdown_service.py +12 -9
  57. pymobiledevice3/services/mobile_config.py +1 -0
  58. pymobiledevice3/services/mobilebackup2.py +6 -3
  59. pymobiledevice3/services/os_trace.py +97 -55
  60. pymobiledevice3/services/remote_fetch_symbols.py +13 -8
  61. pymobiledevice3/services/screenshot.py +2 -2
  62. pymobiledevice3/services/web_protocol/alert.py +8 -8
  63. pymobiledevice3/services/web_protocol/automation_session.py +87 -79
  64. pymobiledevice3/services/web_protocol/cdp_screencast.py +2 -1
  65. pymobiledevice3/services/web_protocol/driver.py +71 -70
  66. pymobiledevice3/services/web_protocol/element.py +58 -56
  67. pymobiledevice3/services/web_protocol/selenium_api.py +47 -47
  68. pymobiledevice3/services/web_protocol/session_protocol.py +3 -2
  69. pymobiledevice3/services/web_protocol/switch_to.py +23 -19
  70. pymobiledevice3/services/webinspector.py +42 -67
  71. {pymobiledevice3-5.0.4.dist-info → pymobiledevice3-7.0.6.dist-info}/METADATA +5 -3
  72. {pymobiledevice3-5.0.4.dist-info → pymobiledevice3-7.0.6.dist-info}/RECORD +76 -61
  73. pymobiledevice3/cli/completions.py +0 -50
  74. pymobiledevice3/cli/developer.py +0 -1539
  75. pymobiledevice3/cli/diagnostics.py +0 -110
  76. {pymobiledevice3-5.0.4.dist-info → pymobiledevice3-7.0.6.dist-info}/WHEEL +0 -0
  77. {pymobiledevice3-5.0.4.dist-info → pymobiledevice3-7.0.6.dist-info}/entry_points.txt +0 -0
  78. {pymobiledevice3-5.0.4.dist-info → pymobiledevice3-7.0.6.dist-info}/licenses/LICENSE +0 -0
  79. {pymobiledevice3-5.0.4.dist-info → pymobiledevice3-7.0.6.dist-info}/top_level.txt +0 -0
@@ -3,9 +3,12 @@ import datetime
3
3
  import shutil
4
4
  import struct
5
5
  import warnings
6
+ from collections.abc import Iterable, Mapping, Sequence
6
7
  from pathlib import Path
8
+ from typing import Any, Callable, Optional, cast
7
9
 
8
10
  from pymobiledevice3.exceptions import NotEnoughDiskSpaceError, PyMobileDevice3Exception
11
+ from pymobiledevice3.service_connection import ServiceConnection
9
12
 
10
13
  SIZE_FORMAT = ">I"
11
14
  CODE_FORMAT = ">B"
@@ -26,12 +29,16 @@ ERRNO_TO_DEVICE_ERROR = {
26
29
  28: -15,
27
30
  }
28
31
 
32
+ DLMessage = Sequence[Any]
33
+ ProgressCallback = Callable[[Any], None]
34
+ DLHandler = Callable[[DLMessage], None]
35
+
29
36
 
30
37
  class DeviceLink:
31
- def __init__(self, service, root_path: Path):
32
- self.service = service
33
- self.root_path = root_path
34
- self._dl_handlers = {
38
+ def __init__(self, service: ServiceConnection, root_path: Path) -> None:
39
+ self.service: ServiceConnection = service
40
+ self.root_path: Path = root_path
41
+ self._dl_handlers: dict[str, DLHandler] = {
35
42
  "DLMessageCreateDirectory": self.create_directory,
36
43
  "DLMessageUploadFiles": self.upload_files,
37
44
  "DLMessageGetFreeDiskSpace": self.get_free_disk_space,
@@ -43,7 +50,12 @@ class DeviceLink:
43
50
  "DLMessagePurgeDiskSpace": self.purge_disk_space,
44
51
  }
45
52
 
46
- def dl_loop(self, progress_callback=lambda x: None):
53
+ def dl_loop(self, progress_callback: Optional[ProgressCallback] = None) -> Any:
54
+ def _noop(_: Any) -> None:
55
+ return None
56
+
57
+ callback: ProgressCallback = progress_callback if progress_callback is not None else _noop
58
+
47
59
  while True:
48
60
  message = self.receive_message()
49
61
  command = message[0]
@@ -55,9 +67,9 @@ class DeviceLink:
55
67
  "DLMessageRemoveFiles",
56
68
  "DLMessageRemoveItems",
57
69
  ):
58
- progress_callback(message[3])
70
+ callback(message[3])
59
71
  elif command == "DLMessageUploadFiles":
60
- progress_callback(message[2])
72
+ callback(message[2])
61
73
 
62
74
  if command == "DLMessageProcessMessage":
63
75
  if not message[1]["ErrorCode"]:
@@ -66,7 +78,7 @@ class DeviceLink:
66
78
  raise PyMobileDevice3Exception(f"Device link error: {message[1]}")
67
79
  self._dl_handlers[command](message)
68
80
 
69
- def version_exchange(self):
81
+ def version_exchange(self) -> None:
70
82
  dl_message_version_exchange = self.receive_message()
71
83
  version_major = dl_message_version_exchange[1]
72
84
  self.service.send_plist(["DLMessageVersionExchange", "DLVersionsOk", version_major])
@@ -74,12 +86,13 @@ class DeviceLink:
74
86
  if dl_message_device_ready[0] != "DLMessageDeviceReady":
75
87
  raise PyMobileDevice3Exception("Device link didn't return ready state")
76
88
 
77
- def send_process_message(self, message):
89
+ def send_process_message(self, message: Mapping[str, Any]) -> None:
78
90
  self.service.send_plist(["DLMessageProcessMessage", message])
79
91
 
80
- def download_files(self, message):
81
- status = {}
82
- for file in message[1]:
92
+ def download_files(self, message: DLMessage) -> None:
93
+ status: dict[str, dict[str, Any]] = {}
94
+ files = cast(Iterable[str], message[1])
95
+ for file in files:
83
96
  self.service.sendall(struct.pack(SIZE_FORMAT, len(file)))
84
97
  self.service.sendall(file.encode())
85
98
 
@@ -114,9 +127,9 @@ class DeviceLink:
114
127
  else:
115
128
  self.status_response(0)
116
129
 
117
- def contents_of_directory(self, message):
130
+ def contents_of_directory(self, message: DLMessage) -> None:
118
131
  data = {}
119
- path = self.root_path / message[1]
132
+ path = self.root_path / cast(str, message[1])
120
133
  for file in path.iterdir():
121
134
  ftype = "DLFileTypeUnknown"
122
135
  if file.is_dir():
@@ -132,7 +145,7 @@ class DeviceLink:
132
145
  }
133
146
  self.status_response(0, status_dict=data)
134
147
 
135
- def upload_files(self, message):
148
+ def upload_files(self, _message: DLMessage) -> None:
136
149
  while True:
137
150
  device_name = self._prefixed_recv()
138
151
  if not device_name:
@@ -158,20 +171,21 @@ class DeviceLink:
158
171
  assert code == CODE_SUCCESS
159
172
  self.status_response(0)
160
173
 
161
- def get_free_disk_space(self, message):
174
+ def get_free_disk_space(self, _message: DLMessage) -> None:
162
175
  freespace = shutil.disk_usage(self.root_path).free
163
176
  self.status_response(0, status_dict=freespace)
164
177
 
165
- def move_items(self, message):
166
- for src, dst in message[1].items():
178
+ def move_items(self, message: DLMessage) -> None:
179
+ items = cast(Mapping[str, str], message[1])
180
+ for src, dst in items.items():
167
181
  dest = self.root_path / dst
168
182
  dest.parent.mkdir(parents=True, exist_ok=True)
169
183
  shutil.move(self.root_path / src, dest)
170
184
  self.status_response(0)
171
185
 
172
- def copy_item(self, message):
173
- src = self.root_path / message[1]
174
- dest = self.root_path / message[2]
186
+ def copy_item(self, message: DLMessage) -> None:
187
+ src = self.root_path / cast(str, message[1])
188
+ dest = self.root_path / cast(str, message[2])
175
189
  dest.parent.mkdir(parents=True, exist_ok=True)
176
190
  if src.is_dir():
177
191
  shutil.copytree(src, dest)
@@ -179,11 +193,11 @@ class DeviceLink:
179
193
  shutil.copy(src, dest)
180
194
  self.status_response(0)
181
195
 
182
- def purge_disk_space(self, message) -> None:
196
+ def purge_disk_space(self, _message: DLMessage) -> None:
183
197
  raise NotEnoughDiskSpaceError()
184
198
 
185
- def remove_items(self, message):
186
- for path in message[1]:
199
+ def remove_items(self, message: DLMessage) -> None:
200
+ for path in cast(Iterable[str], message[1]):
187
201
  rm_path = self.root_path / path
188
202
  if rm_path.is_dir():
189
203
  shutil.rmtree(rm_path)
@@ -191,12 +205,12 @@ class DeviceLink:
191
205
  rm_path.unlink(missing_ok=True)
192
206
  self.status_response(0)
193
207
 
194
- def create_directory(self, message):
195
- path = message[1]
208
+ def create_directory(self, message: DLMessage) -> None:
209
+ path = cast(str, message[1])
196
210
  (self.root_path / path).mkdir(parents=True, exist_ok=True)
197
211
  self.status_response(0)
198
212
 
199
- def status_response(self, status_code, status_str="", status_dict=None):
213
+ def status_response(self, status_code: int, status_str: str = "", status_dict: Any = None) -> None:
200
214
  self.service.send_plist([
201
215
  "DLMessageStatusResponse",
202
216
  ctypes.c_uint64(status_code).value,
@@ -204,12 +218,12 @@ class DeviceLink:
204
218
  status_dict if status_dict is not None else {},
205
219
  ])
206
220
 
207
- def receive_message(self):
208
- return self.service.recv_plist()
221
+ def receive_message(self) -> DLMessage:
222
+ return cast(DLMessage, self.service.recv_plist())
209
223
 
210
- def disconnect(self):
224
+ def disconnect(self) -> None:
211
225
  self.service.send_plist(["DLMessageDisconnect", "___EmptyParameterString___"])
212
226
 
213
- def _prefixed_recv(self):
227
+ def _prefixed_recv(self) -> str:
214
228
  (size,) = struct.unpack(SIZE_FORMAT, self.service.recvall(struct.calcsize(SIZE_FORMAT)))
215
229
  return self.service.recvall(size).decode()
@@ -0,0 +1,20 @@
1
+ from pymobiledevice3.lockdown import LockdownClient
2
+ from pymobiledevice3.lockdown_service_provider import LockdownServiceProvider
3
+ from pymobiledevice3.services.lockdown_service import LockdownService
4
+
5
+
6
+ class IDAMService(LockdownService):
7
+ RSD_SERVICE_NAME = "com.apple.idamd.shim.remote"
8
+ SERVICE_NAME = "com.apple.idamd"
9
+
10
+ def __init__(self, lockdown: LockdownServiceProvider) -> None:
11
+ if isinstance(lockdown, LockdownClient):
12
+ super().__init__(lockdown, self.SERVICE_NAME)
13
+ else:
14
+ super().__init__(lockdown, self.RSD_SERVICE_NAME)
15
+
16
+ def configuration_inquiry(self) -> dict:
17
+ return self.service.send_recv_plist({"Configuration Inquiry": True})
18
+
19
+ def set_idam_configuration(self, value: bool) -> None:
20
+ self.service.send_recv_plist({"Set IDAM Configuration": value})
@@ -1,4 +1,7 @@
1
1
  import logging
2
+ from typing import Optional
3
+
4
+ from typing_extensions import Self
2
5
 
3
6
  from pymobiledevice3.lockdown_service_provider import LockdownServiceProvider
4
7
  from pymobiledevice3.service_connection import ServiceConnection
@@ -9,10 +12,10 @@ class LockdownService:
9
12
  self,
10
13
  lockdown: LockdownServiceProvider,
11
14
  service_name: str,
12
- is_developer_service=False,
13
- service: ServiceConnection = None,
15
+ is_developer_service: bool = False,
16
+ service: Optional[ServiceConnection] = None,
14
17
  include_escrow_bag: bool = False,
15
- ):
18
+ ) -> None:
16
19
  """
17
20
  :param lockdown: server provider
18
21
  :param service_name: wrapped service name - will attempt
@@ -26,15 +29,15 @@ class LockdownService:
26
29
  )
27
30
  service = start_service(service_name, include_escrow_bag=include_escrow_bag)
28
31
 
29
- self.service_name = service_name
30
- self.lockdown = lockdown
31
- self.service = service
32
- self.logger = logging.getLogger(self.__module__)
32
+ self.service_name: str = service_name
33
+ self.lockdown: LockdownServiceProvider = lockdown
34
+ self.service: ServiceConnection = service
35
+ self.logger: logging.Logger = logging.getLogger(self.__module__)
33
36
 
34
- def __enter__(self):
37
+ def __enter__(self) -> Self:
35
38
  return self
36
39
 
37
- async def __aenter__(self) -> "LockdownService":
40
+ async def __aenter__(self) -> Self:
38
41
  return self
39
42
 
40
43
  def __exit__(self, exc_type, exc_val, exc_tb):
@@ -279,6 +279,7 @@ class MobileConfigService(LockdownService):
279
279
  "OSShowcase",
280
280
  "SafetyAndHandling",
281
281
  "Tips",
282
+ "AgeBasedSafetySettings",
282
283
  ],
283
284
  "SupervisorHostCertificates": [public_key],
284
285
  })
@@ -5,6 +5,7 @@ import uuid
5
5
  from contextlib import contextmanager, suppress
6
6
  from datetime import datetime
7
7
  from pathlib import Path
8
+ from typing import Union
8
9
 
9
10
  from pymobiledevice3.exceptions import (
10
11
  AfcException,
@@ -16,7 +17,7 @@ from pymobiledevice3.exceptions import (
16
17
  )
17
18
  from pymobiledevice3.lockdown import LockdownClient
18
19
  from pymobiledevice3.lockdown_service_provider import LockdownServiceProvider
19
- from pymobiledevice3.services.afc import AFC_LOCK_EX, AFC_LOCK_UN, AfcService, afc_error_t
20
+ from pymobiledevice3.services.afc import AFC_LOCK_EX, AFC_LOCK_UN, AfcError, AfcService
20
21
  from pymobiledevice3.services.device_link import DeviceLink
21
22
  from pymobiledevice3.services.installation_proxy import InstallationProxyService
22
23
  from pymobiledevice3.services.lockdown_service import LockdownService
@@ -60,7 +61,9 @@ class Mobilebackup2Service(LockdownService):
60
61
  except LockdownError:
61
62
  return False
62
63
 
63
- def backup(self, full: bool = True, backup_directory: str = ".", progress_callback=lambda x: None) -> None:
64
+ def backup(
65
+ self, full: bool = True, backup_directory: Union[str, Path] = ".", progress_callback=lambda x: None
66
+ ) -> None:
64
67
  """
65
68
  Backup a device.
66
69
  :param full: Whether to do a full backup. If full is True, any previous backup attempts will be discarded.
@@ -386,7 +389,7 @@ class Mobilebackup2Service(LockdownService):
386
389
  try:
387
390
  afc.lock(lockfile, AFC_LOCK_EX)
388
391
  except AfcException as e:
389
- if e.status == afc_error_t.OP_WOULD_BLOCK:
392
+ if e.status == AfcError.OP_WOULD_BLOCK:
390
393
  time.sleep(0.2)
391
394
  else:
392
395
  afc.fclose(lockfile)
@@ -9,8 +9,6 @@ from enum import IntEnum
9
9
  from pathlib import Path
10
10
  from tarfile import TarFile
11
11
 
12
- from construct import Adapter, Byte, Bytes, Computed, Enum, Int16ul, Int32ul, Optional, RepeatUntil, Struct, this
13
-
14
12
  from pymobiledevice3.exceptions import PyMobileDevice3Exception
15
13
  from pymobiledevice3.lockdown import LockdownClient
16
14
  from pymobiledevice3.lockdown_service_provider import LockdownServiceProvider
@@ -45,51 +43,107 @@ class SyslogEntry:
45
43
  timestamp: datetime
46
44
  level: SyslogLogLevel
47
45
  image_name: str
46
+ image_offset: int
48
47
  filename: str
49
48
  message: str
50
49
  label: typing.Optional[SyslogLabel] = None
51
50
 
52
51
 
53
- class TimestampAdapter(Adapter):
54
- def _decode(self, obj, context, path):
55
- return datetime.fromtimestamp(obj.seconds + (obj.microseconds / 1000000))
56
-
57
- def _encode(self, obj, context, path):
58
- return list(map(int, obj.split(".")))
59
-
60
-
61
- timestamp_t = Struct("seconds" / Int32ul, Bytes(4), "microseconds" / Int32ul)
62
-
63
- syslog_t = Struct(
64
- Bytes(9),
65
- "pid" / Int32ul,
66
- Bytes(42),
67
- "timestamp" / TimestampAdapter(timestamp_t),
68
- Bytes(1),
69
- "level" / Enum(Byte, Notice=0, Info=0x01, Debug=0x02, Error=0x10, Fault=0x11),
70
- Bytes(38),
71
- "image_name_size" / Int16ul,
72
- "message_size" / Int16ul,
73
- Bytes(6),
74
- "_subsystem_size" / Int32ul,
75
- "_category_size" / Int32ul,
76
- Bytes(4),
77
- "_filename" / RepeatUntil(lambda x, lst, ctx: lst[-1] == 0, Byte),
78
- "filename" / Computed(lambda ctx: try_decode(bytearray(ctx._filename[:-1]))),
79
- "_image_name" / Bytes(this.image_name_size),
80
- "image_name" / Computed(lambda ctx: try_decode(ctx._image_name[:-1])),
81
- "_message" / Bytes(this.message_size),
82
- "message" / Computed(lambda ctx: try_decode(ctx._message[:-1])),
83
- "label"
84
- / Optional(
85
- Struct(
86
- "_subsystem" / Bytes(this._._subsystem_size),
87
- "subsystem" / Computed(lambda ctx: try_decode(ctx._subsystem[:-1])),
88
- "_category" / Bytes(this._._category_size),
89
- "category" / Computed(lambda ctx: try_decode(ctx._category[:-1])),
90
- )
91
- ),
92
- )
52
+ def parse_syslog_entry(data: bytes) -> SyslogEntry:
53
+ """
54
+ Parse a syslog entry from binary data.
55
+
56
+ :param data: Raw binary data
57
+ :return: SyslogEntry
58
+ """
59
+ offset = 0
60
+
61
+ # Skip first 9 bytes
62
+ offset += 9
63
+
64
+ # Parse pid (4 bytes, little-endian unsigned int)
65
+ pid = struct.unpack("<I", data[offset : offset + 4])[0]
66
+ offset += 4
67
+
68
+ # Skip 42 bytes
69
+ offset += 42
70
+
71
+ # Parse timestamp
72
+ seconds = struct.unpack("<I", data[offset : offset + 4])[0]
73
+ offset += 4
74
+ offset += 4 # Skip 4 bytes
75
+ microseconds = struct.unpack("<I", data[offset : offset + 4])[0]
76
+ offset += 4
77
+ timestamp = datetime.fromtimestamp(seconds + (microseconds / 1000000))
78
+
79
+ # Skip 1 byte
80
+ offset += 1
81
+
82
+ # Parse level (1 byte)
83
+ level = data[offset]
84
+ offset += 1
85
+
86
+ # Skip 38 bytes
87
+ offset += 38
88
+
89
+ # Parse image_name_size (2 bytes, little-endian unsigned short)
90
+ image_name_size = struct.unpack("<H", data[offset : offset + 2])[0]
91
+ offset += 2
92
+
93
+ # Parse message_size (2 bytes, little-endian unsigned short)
94
+ message_size = struct.unpack("<H", data[offset : offset + 2])[0]
95
+ offset += 2
96
+
97
+ # Skip 2 bytes
98
+ offset += 2
99
+
100
+ # Parse sender_image_offset (4 bytes, little-endian unsigned int)
101
+ sender_image_offset = struct.unpack("<I", data[offset : offset + 4])[0]
102
+ offset += 4
103
+
104
+ # Parse subsystem_size (4 bytes, little-endian unsigned int)
105
+ subsystem_size = struct.unpack("<I", data[offset : offset + 4])[0]
106
+ offset += 4
107
+
108
+ # Parse category_size (4 bytes, little-endian unsigned int)
109
+ category_size = struct.unpack("<I", data[offset : offset + 4])[0]
110
+ offset += 4
111
+
112
+ # Skip 4 bytes
113
+ offset += 4
114
+
115
+ # Parse filename (null-terminated)
116
+ filename_end = data.find(b"\x00", offset)
117
+ filename = try_decode(data[offset:filename_end])
118
+ offset = filename_end + 1
119
+
120
+ # Parse image_name
121
+ image_name = try_decode(data[offset : offset + image_name_size - 1])
122
+ offset += image_name_size
123
+
124
+ # Parse message
125
+ message = try_decode(data[offset : offset + message_size - 1])
126
+ offset += message_size
127
+
128
+ # Parse label (optional)
129
+ label = None
130
+ if subsystem_size > 0 and category_size > 0:
131
+ subsystem = try_decode(data[offset : offset + subsystem_size - 1])
132
+ offset += subsystem_size
133
+ category = try_decode(data[offset : offset + category_size - 1])
134
+ offset += category_size
135
+ label = SyslogLabel(subsystem=subsystem, category=category)
136
+
137
+ return SyslogEntry(
138
+ pid=pid,
139
+ timestamp=timestamp,
140
+ level=SyslogLogLevel(level),
141
+ image_name=image_name,
142
+ image_offset=sender_image_offset,
143
+ filename=filename,
144
+ message=message,
145
+ label=label,
146
+ )
93
147
 
94
148
 
95
149
  class OsTraceService(LockdownService):
@@ -185,16 +239,4 @@ class OsTraceService(LockdownService):
185
239
  assert self.service.recvall(1) == b"\x02"
186
240
  (length,) = struct.unpack("<I", self.service.recvall(4))
187
241
  line = self.service.recvall(length)
188
- entry = syslog_t.parse(line)
189
- label = None
190
- if entry.label is not None:
191
- label = SyslogLabel(subsystem=entry.label.subsystem, category=entry.label.category)
192
- yield SyslogEntry(
193
- pid=entry.pid,
194
- timestamp=entry.timestamp,
195
- level=SyslogLogLevel(int(entry.level)),
196
- image_name=entry.image_name,
197
- filename=entry.filename,
198
- message=entry.message,
199
- label=label,
200
- )
242
+ yield parse_syslog_entry(line)
@@ -38,11 +38,16 @@ class RemoteFetchSymbolsService(RemoteService):
38
38
 
39
39
  async def download(self, out: Path) -> None:
40
40
  files = await self.get_dsc_file_list()
41
- for i, file in enumerate(files):
42
- self.logger.info(f"Downloading {file}")
43
- out_file = out / file.file_path[1:] # trim the "/" prefix
44
- out_file.parent.mkdir(parents=True, exist_ok=True)
45
- with open(out_file, "wb") as f, tqdm(total=files[i].file_size, dynamic_ncols=True) as pb:
46
- async for chunk in self.service.iter_file_chunks(files[i].file_size, file_idx=i):
47
- f.write(chunk)
48
- pb.update(len(chunk))
41
+ with tqdm(total=len(files), dynamic_ncols=True, desc="Downloading DSC") as total_pb:
42
+ for i, file in enumerate(files):
43
+ total_pb.set_description("Downloading DSC")
44
+ out_file = out / file.file_path[1:] # trim the "/" prefix
45
+ out_file.parent.mkdir(parents=True, exist_ok=True)
46
+ with (
47
+ open(out_file, "wb") as f,
48
+ tqdm(total=files[i].file_size, dynamic_ncols=True, desc=file.file_path.rsplit("/", 1)[-1]) as pb,
49
+ ):
50
+ async for chunk in self.service.iter_file_chunks(files[i].file_size, file_idx=i):
51
+ f.write(chunk)
52
+ pb.update(len(chunk))
53
+ total_pb.update(1)
@@ -1,12 +1,12 @@
1
1
  from pymobiledevice3.exceptions import PyMobileDevice3Exception
2
- from pymobiledevice3.lockdown import LockdownClient
2
+ from pymobiledevice3.lockdown_service_provider import LockdownServiceProvider
3
3
  from pymobiledevice3.services.lockdown_service import LockdownService
4
4
 
5
5
 
6
6
  class ScreenshotService(LockdownService):
7
7
  SERVICE_NAME = "com.apple.mobile.screenshotr"
8
8
 
9
- def __init__(self, lockdown: LockdownClient):
9
+ def __init__(self, lockdown: LockdownServiceProvider) -> None:
10
10
  super().__init__(lockdown, self.SERVICE_NAME, is_developer_service=True)
11
11
 
12
12
  dl_message_version_exchange = self.service.recv_plist()
@@ -5,22 +5,22 @@ class Alert:
5
5
  """
6
6
  self.session = session
7
7
 
8
- def accept(self):
8
+ async def accept(self):
9
9
  """Accepts the alert available."""
10
- self.session.accept_current_javascript_dialog()
10
+ await self.session.accept_current_javascript_dialog()
11
11
 
12
- def dismiss(self):
12
+ async def dismiss(self):
13
13
  """Dismisses the alert available."""
14
- self.session.dismiss_current_javascript_dialog()
14
+ await self.session.dismiss_current_javascript_dialog()
15
15
 
16
- def send_keys(self, text: str):
16
+ async def send_keys(self, text: str):
17
17
  """
18
18
  Send Keys to the Alert.
19
19
  :param text: Text to send to prompts.
20
20
  """
21
- self.session.set_user_input_for_current_javascript_prompt(text)
21
+ await self.session.set_user_input_for_current_javascript_prompt(text)
22
22
 
23
23
  @property
24
- def text(self) -> str:
24
+ async def text(self) -> str:
25
25
  """Gets the text of the Alert."""
26
- return self.session.message_of_current_javascript_dialog()
26
+ return await self.session.message_of_current_javascript_dialog()