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
@@ -5,11 +5,19 @@ 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
- from pymobiledevice3.exceptions import AfcException, AfcFileNotFoundError, ConnectionTerminatedError, LockdownError, \
10
- PyMobileDevice3Exception
10
+ from pymobiledevice3.exceptions import (
11
+ AfcException,
12
+ AfcFileNotFoundError,
13
+ ConnectionTerminatedError,
14
+ LockdownError,
15
+ MissingValueError,
16
+ PyMobileDevice3Exception,
17
+ )
11
18
  from pymobiledevice3.lockdown import LockdownClient
12
- from pymobiledevice3.services.afc import AFC_LOCK_EX, AFC_LOCK_UN, AfcService, afc_error_t
19
+ from pymobiledevice3.lockdown_service_provider import LockdownServiceProvider
20
+ from pymobiledevice3.services.afc import AFC_LOCK_EX, AFC_LOCK_UN, AfcError, AfcService
13
21
  from pymobiledevice3.services.device_link import DeviceLink
14
22
  from pymobiledevice3.services.installation_proxy import InstallationProxyService
15
23
  from pymobiledevice3.services.lockdown_service import LockdownService
@@ -18,34 +26,44 @@ from pymobiledevice3.services.springboard import SpringBoardServicesService
18
26
 
19
27
  SUPPORTED_VERSIONS = [2.0, 2.1]
20
28
  ITUNES_FILES = [
21
- 'ApertureAlbumPrefs', 'IC-Info.sidb', 'IC-Info.sidv', 'PhotosFolderAlbums', 'PhotosFolderName',
22
- 'PhotosFolderPrefs', 'VoiceMemos.plist', 'iPhotoAlbumPrefs', 'iTunesApplicationIDs', 'iTunesPrefs',
23
- 'iTunesPrefs.plist'
29
+ "ApertureAlbumPrefs",
30
+ "IC-Info.sidb",
31
+ "IC-Info.sidv",
32
+ "PhotosFolderAlbums",
33
+ "PhotosFolderName",
34
+ "PhotosFolderPrefs",
35
+ "VoiceMemos.plist",
36
+ "iPhotoAlbumPrefs",
37
+ "iTunesApplicationIDs",
38
+ "iTunesPrefs",
39
+ "iTunesPrefs.plist",
24
40
  ]
25
- NP_SYNC_WILL_START = 'com.apple.itunes-mobdev.syncWillStart'
26
- NP_SYNC_DID_START = 'com.apple.itunes-mobdev.syncDidStart'
27
- NP_SYNC_LOCK_REQUEST = 'com.apple.itunes-mobdev.syncLockRequest'
28
- NP_SYNC_DID_FINISH = 'com.apple.itunes-mobdev.syncDidFinish'
41
+ NP_SYNC_WILL_START = "com.apple.itunes-mobdev.syncWillStart"
42
+ NP_SYNC_DID_START = "com.apple.itunes-mobdev.syncDidStart"
43
+ NP_SYNC_LOCK_REQUEST = "com.apple.itunes-mobdev.syncLockRequest"
44
+ NP_SYNC_DID_FINISH = "com.apple.itunes-mobdev.syncDidFinish"
29
45
 
30
46
 
31
47
  class Mobilebackup2Service(LockdownService):
32
- SERVICE_NAME = 'com.apple.mobilebackup2'
33
- RSD_SERVICE_NAME = 'com.apple.mobilebackup2.shim.remote'
48
+ SERVICE_NAME = "com.apple.mobilebackup2"
49
+ RSD_SERVICE_NAME = "com.apple.mobilebackup2.shim.remote"
34
50
 
35
- def __init__(self, lockdown: LockdownClient):
51
+ def __init__(self, lockdown: LockdownServiceProvider) -> None:
36
52
  if isinstance(lockdown, LockdownClient):
37
53
  super().__init__(lockdown, self.SERVICE_NAME, include_escrow_bag=True)
38
54
  else:
39
55
  super().__init__(lockdown, self.RSD_SERVICE_NAME, include_escrow_bag=True)
40
56
 
41
57
  @property
42
- def will_encrypt(self):
58
+ def will_encrypt(self) -> bool:
43
59
  try:
44
- return self.lockdown.get_value('com.apple.mobile.backup', 'WillEncrypt')
60
+ return self.lockdown.get_value("com.apple.mobile.backup", "WillEncrypt")
45
61
  except LockdownError:
46
62
  return False
47
63
 
48
- def backup(self, full: bool = True, backup_directory='.', 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:
49
67
  """
50
68
  Backup a device.
51
69
  :param full: Whether to do a full backup. If full is True, any previous backup attempts will be discarded.
@@ -57,42 +75,58 @@ class Mobilebackup2Service(LockdownService):
57
75
  device_directory = backup_directory / self.lockdown.udid
58
76
  device_directory.mkdir(exist_ok=True, mode=0o755, parents=True)
59
77
 
60
- with self.device_link(backup_directory) as dl, \
61
- NotificationProxyService(self.lockdown) as notification_proxy, \
62
- AfcService(self.lockdown) as afc:
63
- with self._backup_lock(afc, notification_proxy):
64
- # Initialize Info.plist
65
- info_plist = self.init_mobile_backup_factory_info(afc)
66
- with open(device_directory / 'Info.plist', 'wb') as fd:
67
- plistlib.dump(info_plist, fd)
78
+ with (
79
+ self.device_link(backup_directory) as dl,
80
+ NotificationProxyService(self.lockdown) as notification_proxy,
81
+ AfcService(self.lockdown) as afc,
82
+ self._backup_lock(afc, notification_proxy),
83
+ ):
84
+ # Initialize Info.plist
85
+ info_plist = self.init_mobile_backup_factory_info(afc)
86
+ with open(device_directory / "Info.plist", "wb") as fd:
87
+ plistlib.dump(info_plist, fd)
68
88
 
69
- # Initialize Status.plist file if doesn't exist.
70
- status_path = device_directory / 'Status.plist'
71
- current_date = datetime.now()
72
- current_date = current_date.replace(tzinfo=None)
73
- if full or not status_path.exists():
74
- with open(device_directory / 'Status.plist', 'wb') as fd:
75
- plistlib.dump({
76
- 'BackupState': 'new',
77
- 'Date': current_date,
78
- 'IsFullBackup': full,
79
- 'Version': '3.3',
80
- 'SnapshotState': 'finished',
81
- 'UUID': str(uuid.uuid4()).upper(),
82
- }, fd, fmt=plistlib.FMT_BINARY)
89
+ # Initialize Status.plist file if doesn't exist.
90
+ status_path = device_directory / "Status.plist"
91
+ current_date = datetime.now()
92
+ current_date = current_date.replace(tzinfo=None)
93
+ if full or not status_path.exists():
94
+ with open(device_directory / "Status.plist", "wb") as fd:
95
+ plistlib.dump(
96
+ {
97
+ "BackupState": "new",
98
+ "Date": current_date,
99
+ "IsFullBackup": full,
100
+ "Version": "3.3",
101
+ "SnapshotState": "finished",
102
+ "UUID": str(uuid.uuid4()).upper(),
103
+ },
104
+ fd,
105
+ fmt=plistlib.FMT_BINARY,
106
+ )
83
107
 
84
- # Create Manifest.plist if doesn't exist.
85
- manifest_path = device_directory / 'Manifest.plist'
86
- if full:
87
- manifest_path.unlink(missing_ok=True)
88
- (device_directory / 'Manifest.plist').touch()
108
+ # Create Manifest.plist if doesn't exist.
109
+ manifest_path = device_directory / "Manifest.plist"
110
+ if full:
111
+ manifest_path.unlink(missing_ok=True)
112
+ (device_directory / "Manifest.plist").touch()
89
113
 
90
- dl.send_process_message({'MessageName': 'Backup', 'TargetIdentifier': self.lockdown.udid})
91
- dl.dl_loop(progress_callback)
114
+ dl.send_process_message({"MessageName": "Backup", "TargetIdentifier": self.lockdown.udid})
115
+ dl.dl_loop(progress_callback)
92
116
 
93
- def restore(self, backup_directory='.', system: bool = False, reboot: bool = True, copy: bool = True,
94
- settings: bool = True, remove: bool = False, password: str = '', source: str = '',
95
- progress_callback=lambda x: None):
117
+ def restore(
118
+ self,
119
+ backup_directory=".",
120
+ system: bool = False,
121
+ reboot: bool = True,
122
+ copy: bool = True,
123
+ settings: bool = True,
124
+ remove: bool = False,
125
+ password: str = "",
126
+ source: str = "",
127
+ progress_callback=lambda x: None,
128
+ skip_apps: bool = False,
129
+ ):
96
130
  """
97
131
  Restore a previous backup to the device.
98
132
  :param backup_directory: Path of the backup directory.
@@ -104,42 +138,55 @@ class Mobilebackup2Service(LockdownService):
104
138
  :param password: Password of the backup if it is encrypted.
105
139
  :param source: Identifier of device to restore its backup.
106
140
  :param progress_callback: Function to be called as the backup progresses.
141
+ :param skip_apps: Do not trigger re-installation of apps after restore.
107
142
  The function shall receive the current percentage of the progress as a parameter.
108
143
  """
109
144
  backup_directory = Path(backup_directory)
110
145
  source = source if source else self.lockdown.udid
111
146
  self._assert_backup_exists(backup_directory, source)
112
147
 
113
- with self.device_link(backup_directory) as dl, \
114
- NotificationProxyService(self.lockdown) as notification_proxy, \
115
- AfcService(self.lockdown) as afc:
116
- with self._backup_lock(afc, notification_proxy):
117
- manifest_plist_path = backup_directory / source / 'Manifest.plist'
118
- with open(manifest_plist_path, 'rb') as fd:
119
- manifest = plistlib.load(fd)
120
- is_encrypted = manifest.get('IsEncrypted', False)
121
- options = {
122
- 'RestoreShouldReboot': reboot,
123
- 'RestoreDontCopyBackup': not copy,
124
- 'RestorePreserveSettings': settings,
125
- 'RestoreSystemFiles': system,
126
- 'RemoveItemsNotRestored': remove,
127
- }
128
- if is_encrypted:
129
- if password:
130
- options['Password'] = password
131
- else:
132
- self.logger.error('Backup is encrypted, please supply password.')
133
- return
134
- dl.send_process_message({
135
- 'MessageName': 'Restore',
136
- 'TargetIdentifier': self.lockdown.udid,
137
- 'SourceIdentifier': source,
138
- 'Options': options,
139
- })
140
- dl.dl_loop(progress_callback)
148
+ with (
149
+ self.device_link(backup_directory) as dl,
150
+ NotificationProxyService(self.lockdown) as notification_proxy,
151
+ AfcService(self.lockdown) as afc,
152
+ self._backup_lock(afc, notification_proxy),
153
+ ):
154
+ manifest_plist_path = backup_directory / source / "Manifest.plist"
155
+ with open(manifest_plist_path, "rb") as fd:
156
+ manifest = plistlib.load(fd)
157
+ is_encrypted = manifest.get("IsEncrypted", False)
158
+ options = {
159
+ "RestoreShouldReboot": reboot,
160
+ "RestoreDontCopyBackup": not copy,
161
+ "RestorePreserveSettings": settings,
162
+ "RestoreSystemFiles": system,
163
+ "RemoveItemsNotRestored": remove,
164
+ }
165
+ if is_encrypted:
166
+ if password:
167
+ options["Password"] = password
168
+ else:
169
+ self.logger.error("Backup is encrypted, please supply password.")
170
+ return
171
+ dl.send_process_message({
172
+ "MessageName": "Restore",
173
+ "TargetIdentifier": self.lockdown.udid,
174
+ "SourceIdentifier": source,
175
+ "Options": options,
176
+ })
141
177
 
142
- def info(self, backup_directory='.', source: str = '') -> str:
178
+ if not skip_apps:
179
+ # Write /iTunesRestore/RestoreApplications.plist so that the device will start
180
+ # restoring applications once the rest of the restore process is finished
181
+ info_plist_path = backup_directory / source / "Info.plist"
182
+ applications = plistlib.loads(info_plist_path.read_bytes()).get("Applications")
183
+ if applications is not None:
184
+ afc.makedirs("/iTunesRestore")
185
+ afc.set_file_contents("/iTunesRestore/RestoreApplications.plist", plistlib.dumps(applications))
186
+
187
+ dl.dl_loop(progress_callback)
188
+
189
+ def info(self, backup_directory=".", source: str = "") -> str:
143
190
  """
144
191
  Get information about a backup.
145
192
  :param backup_directory: Path of the backup directory.
@@ -149,14 +196,14 @@ class Mobilebackup2Service(LockdownService):
149
196
  backup_dir = Path(backup_directory)
150
197
  self._assert_backup_exists(backup_dir, source if source else self.lockdown.udid)
151
198
  with self.device_link(backup_dir) as dl:
152
- message = {'MessageName': 'Info', 'TargetIdentifier': self.lockdown.udid}
199
+ message = {"MessageName": "Info", "TargetIdentifier": self.lockdown.udid}
153
200
  if source:
154
- message['SourceIdentifier'] = source
201
+ message["SourceIdentifier"] = source
155
202
  dl.send_process_message(message)
156
203
  result = dl.dl_loop()
157
204
  return result
158
205
 
159
- def list(self, backup_directory='.', source: str = '') -> str:
206
+ def list(self, backup_directory=".", source: str = "") -> str:
160
207
  """
161
208
  List the files in the last backup.
162
209
  :param backup_directory: Path of the backup directory.
@@ -168,12 +215,14 @@ class Mobilebackup2Service(LockdownService):
168
215
  self._assert_backup_exists(backup_dir, source)
169
216
  with self.device_link(backup_dir) as dl:
170
217
  dl.send_process_message({
171
- 'MessageName': 'List', 'TargetIdentifier': self.lockdown.udid, 'SourceIdentifier': source,
218
+ "MessageName": "List",
219
+ "TargetIdentifier": self.lockdown.udid,
220
+ "SourceIdentifier": source,
172
221
  })
173
222
  result = dl.dl_loop()
174
223
  return result
175
224
 
176
- def unback(self, backup_directory='.', password: str = '', source: str = '') -> None:
225
+ def unback(self, backup_directory=".", password: str = "", source: str = "") -> None:
177
226
  """
178
227
  Unpack a complete backup to its device hierarchy.
179
228
  :param backup_directory: Path of the backup directory.
@@ -183,16 +232,17 @@ class Mobilebackup2Service(LockdownService):
183
232
  backup_dir = Path(backup_directory)
184
233
  self._assert_backup_exists(backup_dir, source if source else self.lockdown.udid)
185
234
  with self.device_link(backup_dir) as dl:
186
- message = {'MessageName': 'Unback', 'TargetIdentifier': self.lockdown.udid}
235
+ message = {"MessageName": "Unback", "TargetIdentifier": self.lockdown.udid}
187
236
  if source:
188
- message['SourceIdentifier'] = source
237
+ message["SourceIdentifier"] = source
189
238
  if password:
190
- message['Password'] = password
239
+ message["Password"] = password
191
240
  dl.send_process_message(message)
192
241
  dl.dl_loop()
193
242
 
194
- def extract(self, domain_name: str, relative_path: str, backup_directory='.', password: str = '',
195
- source: str = '') -> None:
243
+ def extract(
244
+ self, domain_name: str, relative_path: str, backup_directory=".", password: str = "", source: str = ""
245
+ ) -> None:
196
246
  """
197
247
  Extract a file from a previous backup.
198
248
  :param domain_name: File's domain name, e.g., SystemPreferencesDomain or HomeDomain.
@@ -205,17 +255,19 @@ class Mobilebackup2Service(LockdownService):
205
255
  self._assert_backup_exists(backup_dir, source if source else self.lockdown.udid)
206
256
  with self.device_link(backup_dir) as dl:
207
257
  message = {
208
- 'MessageName': 'Extract', 'TargetIdentifier': self.lockdown.udid, 'DomainName': domain_name,
209
- 'RelativePath': relative_path
258
+ "MessageName": "Extract",
259
+ "TargetIdentifier": self.lockdown.udid,
260
+ "DomainName": domain_name,
261
+ "RelativePath": relative_path,
210
262
  }
211
263
  if source:
212
- message['SourceIdentifier'] = source
264
+ message["SourceIdentifier"] = source
213
265
  if password:
214
- message['Password'] = password
266
+ message["Password"] = password
215
267
  dl.send_process_message(message)
216
268
  dl.dl_loop()
217
269
 
218
- def change_password(self, backup_directory='.', old: str = '', new: str = '') -> None:
270
+ def change_password(self, backup_directory=".", old: str = "", new: str = "") -> None:
219
271
  """
220
272
  Change backup password.
221
273
  :param backup_directory: Backups directory.
@@ -223,22 +275,21 @@ class Mobilebackup2Service(LockdownService):
223
275
  :param new: New password. Omit when disabling backup encryption.
224
276
  """
225
277
  with self.device_link(Path(backup_directory)) as dl:
226
- message = {'MessageName': 'ChangePassword', 'TargetIdentifier': self.lockdown.udid}
278
+ message = {"MessageName": "ChangePassword", "TargetIdentifier": self.lockdown.udid}
227
279
  if old:
228
- message['OldPassword'] = old
280
+ message["OldPassword"] = old
229
281
  if new:
230
- message['NewPassword'] = new
282
+ message["NewPassword"] = new
231
283
  dl.send_process_message(message)
232
284
  dl.dl_loop()
233
285
 
234
- def erase_device(self, backup_directory='.') -> None:
286
+ def erase_device(self, backup_directory=".") -> None:
235
287
  """
236
288
  Erase the device.
237
289
  """
238
- with suppress(ConnectionTerminatedError):
239
- with self.device_link(Path(backup_directory)) as dl:
240
- dl.send_process_message({'MessageName': 'EraseDevice', 'TargetIdentifier': self.lockdown.udid})
241
- dl.dl_loop()
290
+ with suppress(ConnectionTerminatedError), self.device_link(Path(backup_directory)) as dl:
291
+ dl.send_process_message({"MessageName": "EraseDevice", "TargetIdentifier": self.lockdown.udid})
292
+ dl.dl_loop()
242
293
 
243
294
  def version_exchange(self, dl: DeviceLink, local_versions=None) -> None:
244
295
  """
@@ -249,89 +300,96 @@ class Mobilebackup2Service(LockdownService):
249
300
  if local_versions is None:
250
301
  local_versions = SUPPORTED_VERSIONS
251
302
  dl.send_process_message({
252
- 'MessageName': 'Hello',
253
- 'SupportedProtocolVersions': local_versions,
303
+ "MessageName": "Hello",
304
+ "SupportedProtocolVersions": local_versions,
254
305
  })
255
306
  reply = dl.receive_message()
256
- assert reply[0] == 'DLMessageProcessMessage' and reply[1]['ErrorCode'] == 0
257
- assert reply[1]['ProtocolVersion'] in local_versions
307
+ assert reply[0] == "DLMessageProcessMessage" and reply[1]["ErrorCode"] == 0
308
+ assert reply[1]["ProtocolVersion"] in local_versions
258
309
 
259
310
  def init_mobile_backup_factory_info(self, afc: AfcService):
260
311
  with InstallationProxyService(self.lockdown) as ip, SpringBoardServicesService(self.lockdown) as sbs:
261
312
  root_node = self.lockdown.get_value()
262
- itunes_settings = self.lockdown.get_value(domain='com.apple.iTunes')
263
- min_itunes_version = self.lockdown.get_value('com.apple.mobile.iTunes', 'MinITunesVersion')
313
+ itunes_settings = self.lockdown.get_value(domain="com.apple.iTunes")
314
+ try:
315
+ min_itunes_version = self.lockdown.get_value("com.apple.mobile.iTunes", "MinITunesVersion")
316
+ except MissingValueError:
317
+ # iPadOS may not contain this value. See:
318
+ # https://github.com/doronz88/pymobiledevice3/issues/1332
319
+ min_itunes_version = "10.0.1"
264
320
  app_dict = {}
265
321
  installed_apps = []
266
- apps = ip.browse(options={'ApplicationType': 'User'},
267
- attributes=['CFBundleIdentifier', 'ApplicationSINF', 'iTunesMetadata'])
322
+ apps = ip.browse(
323
+ options={"ApplicationType": "User"},
324
+ attributes=["CFBundleIdentifier", "ApplicationSINF", "iTunesMetadata"],
325
+ )
268
326
  for app in apps:
269
- bundle_id = app['CFBundleIdentifier']
327
+ bundle_id = app["CFBundleIdentifier"]
270
328
  if bundle_id:
271
329
  installed_apps.append(bundle_id)
272
- if app.get('iTunesMetadata', False) and app.get('ApplicationSINF', False):
330
+ if app.get("iTunesMetadata", False) and app.get("ApplicationSINF", False):
273
331
  app_dict[bundle_id] = {
274
- 'ApplicationSINF': app['ApplicationSINF'],
275
- 'iTunesMetadata': app['iTunesMetadata'],
276
- 'PlaceholderIcon': sbs.get_icon_pngdata(bundle_id),
332
+ "ApplicationSINF": app["ApplicationSINF"],
333
+ "iTunesMetadata": app["iTunesMetadata"],
334
+ "PlaceholderIcon": sbs.get_icon_pngdata(bundle_id),
277
335
  }
278
336
 
279
337
  files = {}
280
338
  for file in ITUNES_FILES:
281
339
  try:
282
- data_buf = afc.get_file_contents('/iTunes_Control/iTunes/' + file)
340
+ data_buf = afc.get_file_contents("/iTunes_Control/iTunes/" + file)
283
341
  except AfcFileNotFoundError:
284
342
  pass
285
343
  else:
286
344
  files[file] = data_buf
287
345
 
288
346
  ret = {
289
- 'iTunes Version': min_itunes_version if min_itunes_version else '10.0.1',
290
- 'iTunes Files': files,
291
- 'Unique Identifier': self.lockdown.udid.upper(),
292
- 'Target Type': 'Device',
293
- 'Target Identifier': root_node['UniqueDeviceID'],
294
- 'Serial Number': root_node['SerialNumber'],
295
- 'Product Version': root_node['ProductVersion'],
296
- 'Product Type': root_node['ProductType'],
297
- 'Installed Applications': installed_apps,
298
- 'GUID': uuid.uuid4().bytes,
299
- 'Display Name': root_node['DeviceName'],
300
- 'Device Name': root_node['DeviceName'],
301
- 'Build Version': root_node['BuildVersion'],
302
- 'Applications': app_dict,
347
+ "iTunes Version": min_itunes_version if min_itunes_version else "10.0.1",
348
+ "iTunes Files": files,
349
+ "Unique Identifier": self.lockdown.udid.upper(),
350
+ "Target Type": "Device",
351
+ "Target Identifier": root_node["UniqueDeviceID"],
352
+ "Serial Number": root_node["SerialNumber"],
353
+ "Product Version": root_node["ProductVersion"],
354
+ "Product Type": root_node["ProductType"],
355
+ "Installed Applications": installed_apps,
356
+ "GUID": uuid.uuid4().bytes,
357
+ "Display Name": root_node["DeviceName"],
358
+ "Device Name": root_node["DeviceName"],
359
+ "Build Version": root_node["BuildVersion"],
360
+ "Applications": app_dict,
303
361
  }
304
362
 
305
- if 'IntegratedCircuitCardIdentity' in root_node:
306
- ret['ICCID'] = root_node['IntegratedCircuitCardIdentity']
307
- if 'InternationalMobileEquipmentIdentity' in root_node:
308
- ret['IMEI'] = root_node['InternationalMobileEquipmentIdentity']
309
- if 'MobileEquipmentIdentifier' in root_node:
310
- ret['MEID'] = root_node['MobileEquipmentIdentifier']
311
- if 'PhoneNumber' in root_node:
312
- ret['Phone Number'] = root_node['PhoneNumber']
363
+ if "IntegratedCircuitCardIdentity" in root_node:
364
+ ret["ICCID"] = root_node["IntegratedCircuitCardIdentity"]
365
+ if "InternationalMobileEquipmentIdentity" in root_node:
366
+ ret["IMEI"] = root_node["InternationalMobileEquipmentIdentity"]
367
+ if "MobileEquipmentIdentifier" in root_node:
368
+ ret["MEID"] = root_node["MobileEquipmentIdentifier"]
369
+ if "PhoneNumber" in root_node:
370
+ ret["Phone Number"] = root_node["PhoneNumber"]
313
371
 
314
372
  try:
315
- data_buf = afc.get_file_contents('/Books/iBooksData2.plist')
373
+ data_buf = afc.get_file_contents("/Books/iBooksData2.plist")
316
374
  except AfcFileNotFoundError:
317
375
  pass
318
376
  else:
319
- ret['iBooks Data 2'] = data_buf
377
+ ret["iBooks Data 2"] = data_buf
320
378
  if itunes_settings:
321
- ret['iTunes Settings'] = itunes_settings
379
+ ret["iTunes Settings"] = itunes_settings
322
380
  return ret
323
381
 
324
382
  @contextmanager
325
383
  def _backup_lock(self, afc, notification_proxy):
326
384
  notification_proxy.notify_post(NP_SYNC_WILL_START)
327
- lockfile = afc.fopen('/com.apple.itunes.lock_sync', 'r+')
385
+ lockfile = afc.fopen("/com.apple.itunes.lock_sync", "r+")
328
386
  if lockfile:
329
387
  notification_proxy.notify_post(NP_SYNC_LOCK_REQUEST)
330
388
  for _ in range(50):
331
389
  try:
332
390
  afc.lock(lockfile, AFC_LOCK_EX)
333
391
  except AfcException as e:
334
- if e.status == afc_error_t.OP_WOULD_BLOCK:
392
+ if e.status == AfcError.OP_WOULD_BLOCK:
335
393
  time.sleep(0.2)
336
394
  else:
337
395
  afc.fclose(lockfile)
@@ -341,7 +399,7 @@ class Mobilebackup2Service(LockdownService):
341
399
  break
342
400
  else: # No break, lock failed.
343
401
  afc.fclose(lockfile)
344
- raise PyMobileDevice3Exception('Failed to lock itunes sync file')
402
+ raise PyMobileDevice3Exception("Failed to lock itunes sync file")
345
403
  try:
346
404
  yield
347
405
  finally:
@@ -352,9 +410,9 @@ class Mobilebackup2Service(LockdownService):
352
410
  @staticmethod
353
411
  def _assert_backup_exists(backup_directory: Path, identifier: str):
354
412
  device_directory = backup_directory / identifier
355
- assert (device_directory / 'Info.plist').exists()
356
- assert (device_directory / 'Manifest.plist').exists()
357
- assert (device_directory / 'Status.plist').exists()
413
+ assert (device_directory / "Info.plist").exists()
414
+ assert (device_directory / "Manifest.plist").exists()
415
+ assert (device_directory / "Status.plist").exists()
358
416
 
359
417
  @contextmanager
360
418
  def device_link(self, backup_directory):
@@ -1,6 +1,6 @@
1
1
  import socket
2
2
  from collections.abc import Generator
3
- from typing import Union
3
+ from typing import Optional, Union
4
4
 
5
5
  from pymobiledevice3.exceptions import NotificationTimeoutError
6
6
  from pymobiledevice3.lockdown_service_provider import LockdownServiceProvider
@@ -9,13 +9,13 @@ from pymobiledevice3.services.lockdown_service import LockdownService
9
9
 
10
10
 
11
11
  class NotificationProxyService(LockdownService):
12
- SERVICE_NAME = 'com.apple.mobile.notification_proxy'
13
- RSD_SERVICE_NAME = 'com.apple.mobile.notification_proxy.shim.remote'
12
+ SERVICE_NAME = "com.apple.mobile.notification_proxy"
13
+ RSD_SERVICE_NAME = "com.apple.mobile.notification_proxy.shim.remote"
14
14
 
15
- INSECURE_SERVICE_NAME = 'com.apple.mobile.insecure_notification_proxy'
16
- RSD_INSECURE_SERVICE_NAME = 'com.apple.mobile.insecure_notification_proxy.shim.remote'
15
+ INSECURE_SERVICE_NAME = "com.apple.mobile.insecure_notification_proxy"
16
+ RSD_INSECURE_SERVICE_NAME = "com.apple.mobile.insecure_notification_proxy.shim.remote"
17
17
 
18
- def __init__(self, lockdown: LockdownServiceProvider, insecure=False, timeout: Union[float, int] = None):
18
+ def __init__(self, lockdown: LockdownServiceProvider, insecure=False, timeout: Optional[Union[float, int]] = None):
19
19
  if isinstance(lockdown, RemoteServiceDiscoveryService):
20
20
  secure_service_name = self.RSD_SERVICE_NAME
21
21
  insecure_service_name = self.RSD_INSECURE_SERVICE_NAME
@@ -32,13 +32,13 @@ class NotificationProxyService(LockdownService):
32
32
  self.service.socket.settimeout(timeout)
33
33
 
34
34
  def notify_post(self, name: str) -> None:
35
- """ Send notification to the device's notification_proxy. """
36
- self.service.send_plist({'Command': 'PostNotification', 'Name': name})
35
+ """Send notification to the device's notification_proxy."""
36
+ self.service.send_plist({"Command": "PostNotification", "Name": name})
37
37
 
38
38
  def notify_register_dispatch(self, name: str) -> None:
39
- """ Tells the device to send a notification on the specified event. """
40
- self.logger.info(f'Observing {name}')
41
- self.service.send_plist({'Command': 'ObserveNotification', 'Name': name})
39
+ """Tells the device to send a notification on the specified event."""
40
+ self.logger.info(f"Observing {name}")
41
+ self.service.send_plist({"Command": "ObserveNotification", "Name": name})
42
42
 
43
43
  def receive_notification(self) -> Generator[dict, None, None]:
44
44
  while True: