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.
- misc/understanding_idevice_protocol_layers.md +10 -5
- pymobiledevice3/__main__.py +171 -46
- pymobiledevice3/_version.py +2 -2
- pymobiledevice3/bonjour.py +22 -21
- pymobiledevice3/cli/activation.py +24 -22
- pymobiledevice3/cli/afc.py +49 -41
- pymobiledevice3/cli/amfi.py +13 -18
- pymobiledevice3/cli/apps.py +71 -65
- pymobiledevice3/cli/backup.py +134 -93
- pymobiledevice3/cli/bonjour.py +31 -29
- pymobiledevice3/cli/cli_common.py +175 -232
- pymobiledevice3/cli/companion_proxy.py +12 -12
- pymobiledevice3/cli/crash.py +95 -52
- pymobiledevice3/cli/developer/__init__.py +62 -0
- pymobiledevice3/cli/developer/accessibility/__init__.py +65 -0
- pymobiledevice3/cli/developer/accessibility/settings.py +43 -0
- pymobiledevice3/cli/developer/arbitration.py +50 -0
- pymobiledevice3/cli/developer/condition.py +33 -0
- pymobiledevice3/cli/developer/core_device.py +294 -0
- pymobiledevice3/cli/developer/debugserver.py +244 -0
- pymobiledevice3/cli/developer/dvt/__init__.py +438 -0
- pymobiledevice3/cli/developer/dvt/core_profile_session.py +295 -0
- pymobiledevice3/cli/developer/dvt/simulate_location.py +56 -0
- pymobiledevice3/cli/developer/dvt/sysmon/__init__.py +69 -0
- pymobiledevice3/cli/developer/dvt/sysmon/process.py +188 -0
- pymobiledevice3/cli/developer/fetch_symbols.py +108 -0
- pymobiledevice3/cli/developer/simulate_location.py +51 -0
- pymobiledevice3/cli/diagnostics/__init__.py +75 -0
- pymobiledevice3/cli/diagnostics/battery.py +47 -0
- pymobiledevice3/cli/idam.py +42 -0
- pymobiledevice3/cli/lockdown.py +70 -75
- pymobiledevice3/cli/mounter.py +99 -57
- pymobiledevice3/cli/notification.py +38 -26
- pymobiledevice3/cli/pcap.py +36 -20
- pymobiledevice3/cli/power_assertion.py +15 -16
- pymobiledevice3/cli/processes.py +11 -17
- pymobiledevice3/cli/profile.py +120 -75
- pymobiledevice3/cli/provision.py +27 -26
- pymobiledevice3/cli/remote.py +109 -100
- pymobiledevice3/cli/restore.py +134 -129
- pymobiledevice3/cli/springboard.py +50 -50
- pymobiledevice3/cli/syslog.py +145 -65
- pymobiledevice3/cli/usbmux.py +66 -27
- pymobiledevice3/cli/version.py +2 -5
- pymobiledevice3/cli/webinspector.py +232 -156
- pymobiledevice3/exceptions.py +6 -2
- pymobiledevice3/lockdown.py +5 -1
- pymobiledevice3/lockdown_service_provider.py +5 -0
- pymobiledevice3/remote/remote_service_discovery.py +18 -10
- pymobiledevice3/restore/device.py +28 -4
- pymobiledevice3/restore/restore.py +2 -2
- pymobiledevice3/service_connection.py +15 -12
- pymobiledevice3/services/afc.py +731 -220
- pymobiledevice3/services/device_link.py +45 -31
- pymobiledevice3/services/idam.py +20 -0
- pymobiledevice3/services/lockdown_service.py +12 -9
- pymobiledevice3/services/mobile_config.py +1 -0
- pymobiledevice3/services/mobilebackup2.py +6 -3
- pymobiledevice3/services/os_trace.py +97 -55
- pymobiledevice3/services/remote_fetch_symbols.py +13 -8
- pymobiledevice3/services/screenshot.py +2 -2
- pymobiledevice3/services/web_protocol/alert.py +8 -8
- pymobiledevice3/services/web_protocol/automation_session.py +87 -79
- pymobiledevice3/services/web_protocol/cdp_screencast.py +2 -1
- pymobiledevice3/services/web_protocol/driver.py +71 -70
- pymobiledevice3/services/web_protocol/element.py +58 -56
- pymobiledevice3/services/web_protocol/selenium_api.py +47 -47
- pymobiledevice3/services/web_protocol/session_protocol.py +3 -2
- pymobiledevice3/services/web_protocol/switch_to.py +23 -19
- pymobiledevice3/services/webinspector.py +42 -67
- {pymobiledevice3-5.0.4.dist-info → pymobiledevice3-7.0.6.dist-info}/METADATA +5 -3
- {pymobiledevice3-5.0.4.dist-info → pymobiledevice3-7.0.6.dist-info}/RECORD +76 -61
- pymobiledevice3/cli/completions.py +0 -50
- pymobiledevice3/cli/developer.py +0 -1539
- pymobiledevice3/cli/diagnostics.py +0 -110
- {pymobiledevice3-5.0.4.dist-info → pymobiledevice3-7.0.6.dist-info}/WHEEL +0 -0
- {pymobiledevice3-5.0.4.dist-info → pymobiledevice3-7.0.6.dist-info}/entry_points.txt +0 -0
- {pymobiledevice3-5.0.4.dist-info → pymobiledevice3-7.0.6.dist-info}/licenses/LICENSE +0 -0
- {pymobiledevice3-5.0.4.dist-info → pymobiledevice3-7.0.6.dist-info}/top_level.txt +0 -0
pymobiledevice3/cli/amfi.py
CHANGED
|
@@ -1,38 +1,33 @@
|
|
|
1
1
|
import logging
|
|
2
2
|
|
|
3
|
-
import
|
|
3
|
+
from typer_injector import InjectingTyper
|
|
4
4
|
|
|
5
|
-
from pymobiledevice3.cli.cli_common import
|
|
6
|
-
from pymobiledevice3.lockdown import LockdownClient
|
|
5
|
+
from pymobiledevice3.cli.cli_common import ServiceProviderDep, print_json
|
|
7
6
|
from pymobiledevice3.services.amfi import AmfiService
|
|
8
7
|
|
|
9
8
|
logger = logging.getLogger(__name__)
|
|
10
9
|
|
|
11
10
|
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
11
|
+
cli = InjectingTyper(
|
|
12
|
+
name="amfi",
|
|
13
|
+
help="Enable developer-mode or query its state",
|
|
14
|
+
no_args_is_help=True,
|
|
15
|
+
)
|
|
15
16
|
|
|
16
17
|
|
|
17
|
-
@cli.
|
|
18
|
-
def
|
|
19
|
-
"""Enable developer-mode or query its state"""
|
|
20
|
-
pass
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
@amfi.command(cls=Command)
|
|
24
|
-
def reveal_developer_mode(service_provider: LockdownClient):
|
|
18
|
+
@cli.command()
|
|
19
|
+
def reveal_developer_mode(service_provider: ServiceProviderDep) -> None:
|
|
25
20
|
"""reveal developer mode option in device's UI"""
|
|
26
21
|
AmfiService(service_provider).reveal_developer_mode_option_in_ui()
|
|
27
22
|
|
|
28
23
|
|
|
29
|
-
@
|
|
30
|
-
def enable_developer_mode(service_provider:
|
|
24
|
+
@cli.command()
|
|
25
|
+
def enable_developer_mode(service_provider: ServiceProviderDep) -> None:
|
|
31
26
|
"""enable developer mode"""
|
|
32
27
|
AmfiService(service_provider).enable_developer_mode()
|
|
33
28
|
|
|
34
29
|
|
|
35
|
-
@
|
|
36
|
-
def developer_mode_status(service_provider:
|
|
30
|
+
@cli.command()
|
|
31
|
+
def developer_mode_status(service_provider: ServiceProviderDep) -> None:
|
|
37
32
|
"""query developer mode status"""
|
|
38
33
|
print_json(service_provider.developer_mode_status)
|
pymobiledevice3/cli/apps.py
CHANGED
|
@@ -1,35 +1,37 @@
|
|
|
1
|
-
import
|
|
1
|
+
from pathlib import Path
|
|
2
|
+
from typing import Annotated, Literal
|
|
2
3
|
|
|
3
|
-
|
|
4
|
-
from
|
|
5
|
-
|
|
4
|
+
import typer
|
|
5
|
+
from typer_injector import InjectingTyper
|
|
6
|
+
|
|
7
|
+
from pymobiledevice3.cli.cli_common import ServiceProviderDep, print_json
|
|
6
8
|
from pymobiledevice3.services.house_arrest import HouseArrestService
|
|
7
9
|
from pymobiledevice3.services.installation_proxy import InstallationProxyService
|
|
8
10
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
@cli.group()
|
|
16
|
-
def apps() -> None:
|
|
17
|
-
"""Manage installed applications"""
|
|
18
|
-
pass
|
|
11
|
+
cli = InjectingTyper(
|
|
12
|
+
name="apps",
|
|
13
|
+
help="List, query, install, uninstall, and inspect apps on the device.",
|
|
14
|
+
no_args_is_help=True,
|
|
15
|
+
)
|
|
19
16
|
|
|
20
17
|
|
|
21
|
-
@
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
)
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
18
|
+
@cli.command("list")
|
|
19
|
+
def apps_list(
|
|
20
|
+
service_provider: ServiceProviderDep,
|
|
21
|
+
app_type: Annotated[
|
|
22
|
+
Literal["System", "User", "Hidden", "Any"],
|
|
23
|
+
typer.Option(
|
|
24
|
+
"--type",
|
|
25
|
+
"-t",
|
|
26
|
+
help="Filter by application type (System/User/Hidden/Any).",
|
|
27
|
+
),
|
|
28
|
+
] = "Any",
|
|
29
|
+
calculate_sizes: Annotated[
|
|
30
|
+
bool,
|
|
31
|
+
typer.Option(help="Include app size information (slower)."),
|
|
32
|
+
] = False,
|
|
33
|
+
) -> None:
|
|
34
|
+
"""List installed apps."""
|
|
33
35
|
print_json(
|
|
34
36
|
InstallationProxyService(lockdown=service_provider).get_apps(
|
|
35
37
|
application_type=app_type, calculate_sizes=calculate_sizes
|
|
@@ -37,11 +39,16 @@ def apps_list(service_provider: LockdownServiceProvider, app_type: str, calculat
|
|
|
37
39
|
)
|
|
38
40
|
|
|
39
41
|
|
|
40
|
-
@
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
42
|
+
@cli.command("query")
|
|
43
|
+
def apps_query(
|
|
44
|
+
service_provider: ServiceProviderDep,
|
|
45
|
+
bundle_identifiers: list[str],
|
|
46
|
+
calculate_sizes: Annotated[
|
|
47
|
+
bool,
|
|
48
|
+
typer.Option(help="Include app size information (slower)."),
|
|
49
|
+
] = False,
|
|
50
|
+
) -> None:
|
|
51
|
+
"""Return metadata for specific bundle identifiers."""
|
|
45
52
|
print_json(
|
|
46
53
|
InstallationProxyService(lockdown=service_provider).get_apps(
|
|
47
54
|
calculate_sizes=calculate_sizes, bundle_identifiers=bundle_identifiers
|
|
@@ -49,50 +56,49 @@ def apps_query(service_provider: LockdownServiceProvider, bundle_identifiers: li
|
|
|
49
56
|
)
|
|
50
57
|
|
|
51
58
|
|
|
52
|
-
@
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
"""uninstall app by given bundle_id"""
|
|
59
|
+
@cli.command("uninstall")
|
|
60
|
+
def uninstall(service_provider: ServiceProviderDep, bundle_id: str) -> None:
|
|
61
|
+
"""Uninstall an app by bundle identifier."""
|
|
56
62
|
InstallationProxyService(lockdown=service_provider).uninstall(bundle_id)
|
|
57
63
|
|
|
58
64
|
|
|
59
|
-
@
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
65
|
+
@cli.command("install")
|
|
66
|
+
def install(
|
|
67
|
+
service_provider: ServiceProviderDep,
|
|
68
|
+
package: Annotated[
|
|
69
|
+
Path,
|
|
70
|
+
typer.Argument(exists=True),
|
|
71
|
+
],
|
|
72
|
+
developer: Annotated[
|
|
73
|
+
bool,
|
|
74
|
+
typer.Option(help="Install developer package"),
|
|
75
|
+
] = False,
|
|
76
|
+
) -> None:
|
|
77
|
+
"""Install a local .ipa/.app/.ipcc package."""
|
|
64
78
|
InstallationProxyService(lockdown=service_provider).install_from_local(package, developer=developer)
|
|
65
79
|
|
|
66
80
|
|
|
67
|
-
@
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
"""
|
|
81
|
+
@cli.command("afc")
|
|
82
|
+
def afc(
|
|
83
|
+
service_provider: ServiceProviderDep, bundle_id: str, documents: Annotated[bool, typer.Option()] = False
|
|
84
|
+
) -> None:
|
|
85
|
+
"""Open an AFC shell into the app container; pass --documents for Documents-only."""
|
|
72
86
|
HouseArrestService(lockdown=service_provider, bundle_id=bundle_id, documents_only=documents).shell()
|
|
73
87
|
|
|
74
88
|
|
|
75
|
-
@
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
def pull(service_provider: LockdownClient, bundle_id: str, remote_file: str, local_file: str):
|
|
80
|
-
"""pull remote file from specified bundle_id"""
|
|
81
|
-
HouseArrestService(lockdown=service_provider, bundle_id=bundle_id).pull(remote_file, local_file)
|
|
89
|
+
@cli.command("pull")
|
|
90
|
+
def pull(service_provider: ServiceProviderDep, bundle_id: str, remote_file: Path, local_file: Path) -> None:
|
|
91
|
+
"""Pull a file from an app container to a local path."""
|
|
92
|
+
HouseArrestService(lockdown=service_provider, bundle_id=bundle_id).pull(str(remote_file), str(local_file))
|
|
82
93
|
|
|
83
94
|
|
|
84
|
-
@
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
def push(service_provider: LockdownClient, bundle_id: str, local_file: str, remote_file: str):
|
|
89
|
-
"""push local file into specified bundle_id"""
|
|
90
|
-
HouseArrestService(lockdown=service_provider, bundle_id=bundle_id).push(local_file, remote_file)
|
|
95
|
+
@cli.command("push")
|
|
96
|
+
def push(service_provider: ServiceProviderDep, bundle_id: str, local_file: Path, remote_file: Path) -> None:
|
|
97
|
+
"""Push a local file into an app container."""
|
|
98
|
+
HouseArrestService(lockdown=service_provider, bundle_id=bundle_id).push(str(local_file), str(remote_file))
|
|
91
99
|
|
|
92
100
|
|
|
93
|
-
@
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
"""remove remote file from specified bundle_id"""
|
|
98
|
-
HouseArrestService(lockdown=service_provider, bundle_id=bundle_id).rm(remote_file)
|
|
101
|
+
@cli.command("rm")
|
|
102
|
+
def rm(service_provider: ServiceProviderDep, bundle_id: str, remote_file: Path) -> None:
|
|
103
|
+
"""Delete a file from an app container."""
|
|
104
|
+
HouseArrestService(lockdown=service_provider, bundle_id=bundle_id).rm(str(remote_file))
|
pymobiledevice3/cli/backup.py
CHANGED
|
@@ -1,42 +1,65 @@
|
|
|
1
1
|
import logging
|
|
2
|
+
from pathlib import Path
|
|
3
|
+
from typing import Annotated, Literal
|
|
2
4
|
|
|
3
|
-
import
|
|
5
|
+
import typer
|
|
4
6
|
from tqdm import tqdm
|
|
7
|
+
from typer_injector import InjectingTyper
|
|
5
8
|
|
|
6
|
-
from pymobiledevice3.cli.cli_common import
|
|
7
|
-
from pymobiledevice3.lockdown import LockdownClient
|
|
8
|
-
from pymobiledevice3.lockdown_service_provider import LockdownServiceProvider
|
|
9
|
+
from pymobiledevice3.cli.cli_common import ServiceProviderDep
|
|
9
10
|
from pymobiledevice3.services.mobilebackup2 import Mobilebackup2Service
|
|
10
11
|
|
|
11
|
-
source_option = click.option("--source", default="", help="The UDID of the source device.")
|
|
12
|
-
password_option = click.option("-p", "--password", default="", help="Backup password.")
|
|
13
|
-
backup_directory_arg = click.argument("backup-directory", type=click.Path(exists=True, file_okay=False))
|
|
14
|
-
backup_directory_option = click.option(
|
|
15
|
-
"-b", "--backup-directory", type=click.Path(exists=True, file_okay=False), default="."
|
|
16
|
-
)
|
|
17
|
-
|
|
18
12
|
logger = logging.getLogger(__name__)
|
|
19
13
|
|
|
20
14
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
@cli.group()
|
|
27
|
-
def backup2() -> None:
|
|
28
|
-
"""Backup/Restore options"""
|
|
29
|
-
pass
|
|
15
|
+
cli = InjectingTyper(
|
|
16
|
+
name="backup2",
|
|
17
|
+
help="Create, inspect, and restore MobileBackup2 backups.",
|
|
18
|
+
no_args_is_help=True,
|
|
19
|
+
)
|
|
30
20
|
|
|
31
21
|
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
22
|
+
SourceOption = Annotated[
|
|
23
|
+
str,
|
|
24
|
+
typer.Option(help="The UDID of the source device."),
|
|
25
|
+
]
|
|
26
|
+
PasswordOption = Annotated[
|
|
27
|
+
str,
|
|
28
|
+
typer.Option(
|
|
29
|
+
"--password",
|
|
30
|
+
"-p",
|
|
31
|
+
help="Backup password.",
|
|
32
|
+
),
|
|
33
|
+
]
|
|
34
|
+
BackupDirectoryArg = Annotated[
|
|
35
|
+
Path,
|
|
36
|
+
typer.Argument(
|
|
37
|
+
exists=True,
|
|
38
|
+
file_okay=False,
|
|
39
|
+
),
|
|
40
|
+
]
|
|
41
|
+
BackupDirectoryOption = Annotated[
|
|
42
|
+
Path,
|
|
43
|
+
typer.Option(
|
|
44
|
+
"--backup-directory",
|
|
45
|
+
"-b",
|
|
46
|
+
exists=True,
|
|
47
|
+
file_okay=False,
|
|
48
|
+
),
|
|
49
|
+
]
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
@cli.command()
|
|
53
|
+
def backup(
|
|
54
|
+
service_provider: ServiceProviderDep,
|
|
55
|
+
backup_directory: BackupDirectoryArg,
|
|
56
|
+
full: Annotated[
|
|
57
|
+
bool,
|
|
58
|
+
typer.Option(
|
|
59
|
+
help="Whether to do a full backup. If full is True, any previous backup attempts will be discarded.",
|
|
60
|
+
),
|
|
61
|
+
],
|
|
62
|
+
) -> None:
|
|
40
63
|
"""
|
|
41
64
|
Backup device.
|
|
42
65
|
|
|
@@ -45,34 +68,43 @@ def backup(service_provider: LockdownServiceProvider, backup_directory: str, ful
|
|
|
45
68
|
backup_client = Mobilebackup2Service(service_provider)
|
|
46
69
|
with tqdm(total=100, dynamic_ncols=True) as pbar:
|
|
47
70
|
|
|
48
|
-
def update_bar(percentage):
|
|
71
|
+
def update_bar(percentage) -> None:
|
|
49
72
|
pbar.n = percentage
|
|
50
73
|
pbar.refresh()
|
|
51
74
|
|
|
52
|
-
backup_client.backup(full=full, backup_directory=backup_directory, progress_callback=update_bar)
|
|
75
|
+
backup_client.backup(full=full, backup_directory=str(backup_directory), progress_callback=update_bar)
|
|
53
76
|
|
|
54
77
|
|
|
55
|
-
@
|
|
56
|
-
@backup_directory_arg
|
|
57
|
-
@click.option("--system/--no-system", default=False, help="Restore system files.")
|
|
58
|
-
@click.option("--reboot/--no-reboot", default=True, help="Reboot the device when done.")
|
|
59
|
-
@click.option("--copy/--no-copy", default=False, help="Create a copy of backup folder before restoring.")
|
|
60
|
-
@click.option("--settings/--no-settings", default=True, help="Restore device settings.")
|
|
61
|
-
@click.option("--remove/--no-remove", default=False, help="Remove items which aren't being restored.")
|
|
62
|
-
@click.option("--skip-apps", is_flag=True, help="Do not trigger re-installation of apps after restore")
|
|
63
|
-
@password_option
|
|
64
|
-
@source_option
|
|
78
|
+
@cli.command()
|
|
65
79
|
def restore(
|
|
66
|
-
service_provider:
|
|
67
|
-
backup_directory:
|
|
68
|
-
system:
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
80
|
+
service_provider: ServiceProviderDep,
|
|
81
|
+
backup_directory: BackupDirectoryArg,
|
|
82
|
+
system: Annotated[
|
|
83
|
+
bool,
|
|
84
|
+
typer.Option(help="Restore system files."),
|
|
85
|
+
] = False,
|
|
86
|
+
reboot: Annotated[
|
|
87
|
+
bool,
|
|
88
|
+
typer.Option(help="Reboot the device when done."),
|
|
89
|
+
] = True,
|
|
90
|
+
copy: Annotated[
|
|
91
|
+
bool,
|
|
92
|
+
typer.Option(help="Create a copy of backup folder before restoring."),
|
|
93
|
+
] = False,
|
|
94
|
+
settings: Annotated[
|
|
95
|
+
bool,
|
|
96
|
+
typer.Option(help="Restore device settings."),
|
|
97
|
+
] = True,
|
|
98
|
+
remove: Annotated[
|
|
99
|
+
bool,
|
|
100
|
+
typer.Option(help="Remove items which aren't being restored."),
|
|
101
|
+
] = False,
|
|
102
|
+
skip_apps: Annotated[
|
|
103
|
+
bool,
|
|
104
|
+
typer.Option(help="Do not trigger re-installation of apps after restore."),
|
|
105
|
+
] = False,
|
|
106
|
+
password: PasswordOption = "",
|
|
107
|
+
source: SourceOption = "",
|
|
76
108
|
) -> None:
|
|
77
109
|
"""
|
|
78
110
|
Restore a backup to a device.
|
|
@@ -82,12 +114,12 @@ def restore(
|
|
|
82
114
|
backup_client = Mobilebackup2Service(service_provider)
|
|
83
115
|
with tqdm(total=100, dynamic_ncols=True) as pbar:
|
|
84
116
|
|
|
85
|
-
def update_bar(percentage):
|
|
117
|
+
def update_bar(percentage) -> None:
|
|
86
118
|
pbar.n = percentage
|
|
87
119
|
pbar.refresh()
|
|
88
120
|
|
|
89
121
|
backup_client.restore(
|
|
90
|
-
backup_directory=backup_directory,
|
|
122
|
+
backup_directory=str(backup_directory),
|
|
91
123
|
progress_callback=update_bar,
|
|
92
124
|
system=system,
|
|
93
125
|
reboot=reboot,
|
|
@@ -100,47 +132,49 @@ def restore(
|
|
|
100
132
|
)
|
|
101
133
|
|
|
102
134
|
|
|
103
|
-
@
|
|
104
|
-
|
|
105
|
-
@source_option
|
|
106
|
-
def info(service_provider: LockdownClient, backup_directory, source):
|
|
135
|
+
@cli.command()
|
|
136
|
+
def info(service_provider: ServiceProviderDep, backup_directory: BackupDirectoryArg, source: SourceOption = "") -> None:
|
|
107
137
|
"""
|
|
108
138
|
Print information about a backup.
|
|
109
139
|
"""
|
|
110
140
|
backup_client = Mobilebackup2Service(service_provider)
|
|
111
|
-
print(backup_client.info(backup_directory=backup_directory, source=source))
|
|
141
|
+
print(backup_client.info(backup_directory=str(backup_directory), source=source))
|
|
112
142
|
|
|
113
143
|
|
|
114
|
-
@
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
144
|
+
@cli.command("list")
|
|
145
|
+
def list_(
|
|
146
|
+
service_provider: ServiceProviderDep, backup_directory: BackupDirectoryArg, source: SourceOption = ""
|
|
147
|
+
) -> None:
|
|
118
148
|
"""
|
|
119
149
|
List all file in the backup in a CSV format.
|
|
120
150
|
"""
|
|
121
151
|
backup_client = Mobilebackup2Service(service_provider)
|
|
122
|
-
print(backup_client.list(backup_directory=backup_directory, source=source))
|
|
152
|
+
print(backup_client.list(backup_directory=str(backup_directory), source=source))
|
|
123
153
|
|
|
124
154
|
|
|
125
|
-
@
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
155
|
+
@cli.command()
|
|
156
|
+
def unback(
|
|
157
|
+
service_provider: ServiceProviderDep,
|
|
158
|
+
backup_directory: BackupDirectoryArg,
|
|
159
|
+
password: PasswordOption = "",
|
|
160
|
+
source: SourceOption = "",
|
|
161
|
+
) -> None:
|
|
130
162
|
"""
|
|
131
163
|
Convert all files in the backup to the correct directory hierarchy.
|
|
132
164
|
"""
|
|
133
165
|
backup_client = Mobilebackup2Service(service_provider)
|
|
134
|
-
backup_client.unback(backup_directory=backup_directory, password=password, source=source)
|
|
166
|
+
backup_client.unback(backup_directory=str(backup_directory), password=password, source=source)
|
|
135
167
|
|
|
136
168
|
|
|
137
|
-
@
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
169
|
+
@cli.command()
|
|
170
|
+
def extract(
|
|
171
|
+
service_provider: ServiceProviderDep,
|
|
172
|
+
domain_name: str,
|
|
173
|
+
relative_path: str,
|
|
174
|
+
backup_directory: BackupDirectoryArg,
|
|
175
|
+
password: PasswordOption = "",
|
|
176
|
+
source: SourceOption = "",
|
|
177
|
+
) -> None:
|
|
144
178
|
"""
|
|
145
179
|
Extract a file from the backup.
|
|
146
180
|
|
|
@@ -149,15 +183,21 @@ def extract(service_provider: LockdownClient, domain_name, relative_path, backup
|
|
|
149
183
|
"""
|
|
150
184
|
backup_client = Mobilebackup2Service(service_provider)
|
|
151
185
|
backup_client.extract(
|
|
152
|
-
domain_name, relative_path, backup_directory=backup_directory, password=password, source=source
|
|
186
|
+
domain_name, relative_path, backup_directory=str(backup_directory), password=password, source=source
|
|
153
187
|
)
|
|
154
188
|
|
|
155
189
|
|
|
156
|
-
@
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
190
|
+
@cli.command()
|
|
191
|
+
def encryption(
|
|
192
|
+
service_provider: ServiceProviderDep,
|
|
193
|
+
*,
|
|
194
|
+
backup_directory: BackupDirectoryOption = Path("."),
|
|
195
|
+
mode: Annotated[
|
|
196
|
+
Literal["on", "off"],
|
|
197
|
+
typer.Argument(case_sensitive=False),
|
|
198
|
+
],
|
|
199
|
+
password: str,
|
|
200
|
+
) -> None:
|
|
161
201
|
"""
|
|
162
202
|
Set backup encryption on / off.
|
|
163
203
|
|
|
@@ -170,16 +210,18 @@ def encryption(service_provider: LockdownClient, backup_directory, mode, passwor
|
|
|
170
210
|
logger.error("Encryption already " + ("on!" if should_encrypt else "off!"))
|
|
171
211
|
return
|
|
172
212
|
if should_encrypt:
|
|
173
|
-
backup_client.change_password(backup_directory, new=password)
|
|
213
|
+
backup_client.change_password(str(backup_directory), new=password)
|
|
174
214
|
else:
|
|
175
|
-
backup_client.change_password(backup_directory, old=password)
|
|
215
|
+
backup_client.change_password(str(backup_directory), old=password)
|
|
176
216
|
|
|
177
217
|
|
|
178
|
-
@
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
218
|
+
@cli.command()
|
|
219
|
+
def change_password(
|
|
220
|
+
service_provider: ServiceProviderDep,
|
|
221
|
+
old_password: str,
|
|
222
|
+
new_password: str,
|
|
223
|
+
backup_directory: BackupDirectoryOption = Path("."),
|
|
224
|
+
) -> None:
|
|
183
225
|
"""
|
|
184
226
|
Change the backup password.
|
|
185
227
|
"""
|
|
@@ -187,14 +229,13 @@ def change_password(service_provider: LockdownClient, old_password, new_password
|
|
|
187
229
|
if not backup_client.will_encrypt:
|
|
188
230
|
logger.error("Encryption is not turned on!")
|
|
189
231
|
return
|
|
190
|
-
backup_client.change_password(backup_directory, old=old_password, new=new_password)
|
|
232
|
+
backup_client.change_password(str(backup_directory), old=old_password, new=new_password)
|
|
191
233
|
|
|
192
234
|
|
|
193
|
-
@
|
|
194
|
-
|
|
195
|
-
def erase_device(service_provider: LockdownClient, backup_directory):
|
|
235
|
+
@cli.command()
|
|
236
|
+
def erase_device(service_provider: ServiceProviderDep, backup_directory: BackupDirectoryArg) -> None:
|
|
196
237
|
"""
|
|
197
238
|
Erase all data on the device.
|
|
198
239
|
"""
|
|
199
240
|
backup_client = Mobilebackup2Service(service_provider)
|
|
200
|
-
backup_client.erase_device(backup_directory)
|
|
241
|
+
backup_client.erase_device(str(backup_directory))
|
pymobiledevice3/cli/bonjour.py
CHANGED
|
@@ -1,26 +1,23 @@
|
|
|
1
1
|
import asyncio
|
|
2
|
-
from
|
|
2
|
+
from pathlib import Path
|
|
3
|
+
from typing import Annotated, Optional
|
|
3
4
|
|
|
4
|
-
import
|
|
5
|
+
import typer
|
|
6
|
+
from typer_injector import InjectingTyper
|
|
5
7
|
|
|
6
8
|
from pymobiledevice3.bonjour import DEFAULT_BONJOUR_TIMEOUT, browse_remotepairing, browse_remotepairing_manual_pairing
|
|
7
|
-
from pymobiledevice3.cli.cli_common import
|
|
9
|
+
from pymobiledevice3.cli.cli_common import print_json
|
|
8
10
|
from pymobiledevice3.cli.remote import browse_rsd
|
|
9
11
|
from pymobiledevice3.lockdown import get_mobdev2_lockdowns
|
|
10
12
|
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
@cli.group("bonjour")
|
|
18
|
-
def bonjour_cli() -> None:
|
|
19
|
-
"""Browse devices over bonjour"""
|
|
20
|
-
pass
|
|
13
|
+
cli = InjectingTyper(
|
|
14
|
+
name="bonjour",
|
|
15
|
+
help="Browse devices over bonjour",
|
|
16
|
+
no_args_is_help=True,
|
|
17
|
+
)
|
|
21
18
|
|
|
22
19
|
|
|
23
|
-
async def cli_mobdev2_task(timeout: float, pair_records: Optional[
|
|
20
|
+
async def cli_mobdev2_task(timeout: float, pair_records: Optional[Path]) -> None:
|
|
24
21
|
output = []
|
|
25
22
|
async for ip, lockdown in get_mobdev2_lockdowns(timeout=timeout, pair_records=pair_records):
|
|
26
23
|
short_info = lockdown.short_info
|
|
@@ -29,14 +26,19 @@ async def cli_mobdev2_task(timeout: float, pair_records: Optional[str]) -> None:
|
|
|
29
26
|
print_json(output)
|
|
30
27
|
|
|
31
28
|
|
|
32
|
-
@
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
29
|
+
@cli.command("mobdev2")
|
|
30
|
+
def cli_mobdev2(
|
|
31
|
+
timeout: Annotated[float, typer.Option()] = DEFAULT_BONJOUR_TIMEOUT,
|
|
32
|
+
pair_records: Annotated[
|
|
33
|
+
Optional[Path],
|
|
34
|
+
typer.Option(
|
|
35
|
+
exists=True,
|
|
36
|
+
dir_okay=True,
|
|
37
|
+
file_okay=True,
|
|
38
|
+
help="pair records to attempt validation with",
|
|
39
|
+
),
|
|
40
|
+
] = None,
|
|
41
|
+
) -> None:
|
|
40
42
|
"""browse for mobdev2 devices over bonjour"""
|
|
41
43
|
asyncio.run(cli_mobdev2_task(timeout, pair_records))
|
|
42
44
|
|
|
@@ -49,9 +51,8 @@ async def cli_remotepairing_task(timeout: float) -> None:
|
|
|
49
51
|
print_json(output)
|
|
50
52
|
|
|
51
53
|
|
|
52
|
-
@
|
|
53
|
-
|
|
54
|
-
def cli_remotepairing(timeout: float) -> None:
|
|
54
|
+
@cli.command("remotepairing")
|
|
55
|
+
def cli_remotepairing(timeout: Annotated[float, typer.Option()] = DEFAULT_BONJOUR_TIMEOUT) -> None:
|
|
55
56
|
"""browse for remotepairing devices over bonjour (without attempting pair verification)"""
|
|
56
57
|
asyncio.run(cli_remotepairing_task(timeout=timeout))
|
|
57
58
|
|
|
@@ -68,9 +69,10 @@ async def cli_remotepairing_manual_pairing_task(timeout: float) -> None:
|
|
|
68
69
|
print_json(output)
|
|
69
70
|
|
|
70
71
|
|
|
71
|
-
@
|
|
72
|
-
|
|
73
|
-
|
|
72
|
+
@cli.command("remotepairing-manual-pairing")
|
|
73
|
+
def cli_remotepairing_manual_pairing(
|
|
74
|
+
timeout: Annotated[float, typer.Option()] = DEFAULT_BONJOUR_TIMEOUT,
|
|
75
|
+
) -> None:
|
|
74
76
|
"""browse for remotepairing-manual-pairing devices over bonjour"""
|
|
75
77
|
asyncio.run(cli_remotepairing_manual_pairing_task(timeout=timeout))
|
|
76
78
|
|
|
@@ -79,7 +81,7 @@ async def cli_browse_rsd() -> None:
|
|
|
79
81
|
print_json(await browse_rsd())
|
|
80
82
|
|
|
81
83
|
|
|
82
|
-
@
|
|
84
|
+
@cli.command("rsd")
|
|
83
85
|
def cli_rsd() -> None:
|
|
84
86
|
"""browse RemoteXPC devices using bonjour"""
|
|
85
87
|
asyncio.run(cli_browse_rsd(), debug=True)
|