remotivelabs-cli 0.5.0a1__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.
- remotivelabs/cli/__init__.py +0 -0
- remotivelabs/cli/api/cloud/tokens.py +62 -0
- remotivelabs/cli/broker/__init__.py +33 -0
- remotivelabs/cli/broker/defaults.py +1 -0
- remotivelabs/cli/broker/discovery.py +43 -0
- remotivelabs/cli/broker/export.py +92 -0
- remotivelabs/cli/broker/files.py +119 -0
- remotivelabs/cli/broker/lib/__about__.py +4 -0
- remotivelabs/cli/broker/lib/broker.py +625 -0
- remotivelabs/cli/broker/lib/client.py +224 -0
- remotivelabs/cli/broker/lib/helper.py +277 -0
- remotivelabs/cli/broker/lib/signalcreator.py +196 -0
- remotivelabs/cli/broker/license_flows.py +167 -0
- remotivelabs/cli/broker/licenses.py +98 -0
- remotivelabs/cli/broker/playback.py +117 -0
- remotivelabs/cli/broker/record.py +41 -0
- remotivelabs/cli/broker/recording_session/__init__.py +3 -0
- remotivelabs/cli/broker/recording_session/client.py +67 -0
- remotivelabs/cli/broker/recording_session/cmd.py +254 -0
- remotivelabs/cli/broker/recording_session/time.py +49 -0
- remotivelabs/cli/broker/scripting.py +129 -0
- remotivelabs/cli/broker/signals.py +220 -0
- remotivelabs/cli/broker/version.py +31 -0
- remotivelabs/cli/cloud/__init__.py +17 -0
- remotivelabs/cli/cloud/auth/__init__.py +3 -0
- remotivelabs/cli/cloud/auth/cmd.py +128 -0
- remotivelabs/cli/cloud/auth/login.py +283 -0
- remotivelabs/cli/cloud/auth_tokens.py +149 -0
- remotivelabs/cli/cloud/brokers.py +109 -0
- remotivelabs/cli/cloud/configs.py +109 -0
- remotivelabs/cli/cloud/licenses/__init__.py +0 -0
- remotivelabs/cli/cloud/licenses/cmd.py +14 -0
- remotivelabs/cli/cloud/organisations.py +112 -0
- remotivelabs/cli/cloud/projects.py +44 -0
- remotivelabs/cli/cloud/recordings.py +580 -0
- remotivelabs/cli/cloud/recordings_playback.py +274 -0
- remotivelabs/cli/cloud/resumable_upload.py +87 -0
- remotivelabs/cli/cloud/sample_recordings.py +25 -0
- remotivelabs/cli/cloud/service_account_tokens.py +62 -0
- remotivelabs/cli/cloud/service_accounts.py +72 -0
- remotivelabs/cli/cloud/storage/__init__.py +5 -0
- remotivelabs/cli/cloud/storage/cmd.py +76 -0
- remotivelabs/cli/cloud/storage/copy.py +86 -0
- remotivelabs/cli/cloud/storage/uri_or_path.py +45 -0
- remotivelabs/cli/cloud/uri.py +113 -0
- remotivelabs/cli/connect/__init__.py +0 -0
- remotivelabs/cli/connect/connect.py +118 -0
- remotivelabs/cli/connect/protopie/protopie.py +185 -0
- remotivelabs/cli/py.typed +0 -0
- remotivelabs/cli/remotive.py +123 -0
- remotivelabs/cli/settings/__init__.py +20 -0
- remotivelabs/cli/settings/config_file.py +113 -0
- remotivelabs/cli/settings/core.py +333 -0
- remotivelabs/cli/settings/migration/__init__.py +0 -0
- remotivelabs/cli/settings/migration/migrate_all_token_files.py +80 -0
- remotivelabs/cli/settings/migration/migrate_config_file.py +64 -0
- remotivelabs/cli/settings/migration/migrate_legacy_dirs.py +50 -0
- remotivelabs/cli/settings/migration/migrate_token_file.py +52 -0
- remotivelabs/cli/settings/migration/migration_tools.py +38 -0
- remotivelabs/cli/settings/state_file.py +67 -0
- remotivelabs/cli/settings/token_file.py +128 -0
- remotivelabs/cli/tools/__init__.py +0 -0
- remotivelabs/cli/tools/can/__init__.py +0 -0
- remotivelabs/cli/tools/can/can.py +78 -0
- remotivelabs/cli/tools/tools.py +9 -0
- remotivelabs/cli/topology/__init__.py +28 -0
- remotivelabs/cli/topology/all.py +322 -0
- remotivelabs/cli/topology/cli/__init__.py +3 -0
- remotivelabs/cli/topology/cli/run_in_docker.py +58 -0
- remotivelabs/cli/topology/cli/topology_cli.py +16 -0
- remotivelabs/cli/topology/cmd.py +130 -0
- remotivelabs/cli/topology/start_trial.py +134 -0
- remotivelabs/cli/typer/__init__.py +0 -0
- remotivelabs/cli/typer/typer_utils.py +27 -0
- remotivelabs/cli/utils/__init__.py +0 -0
- remotivelabs/cli/utils/console.py +99 -0
- remotivelabs/cli/utils/rest_helper.py +369 -0
- remotivelabs/cli/utils/time.py +11 -0
- remotivelabs/cli/utils/versions.py +120 -0
- remotivelabs_cli-0.5.0a1.dist-info/METADATA +51 -0
- remotivelabs_cli-0.5.0a1.dist-info/RECORD +84 -0
- remotivelabs_cli-0.5.0a1.dist-info/WHEEL +4 -0
- remotivelabs_cli-0.5.0a1.dist-info/entry_points.txt +3 -0
- remotivelabs_cli-0.5.0a1.dist-info/licenses/LICENSE +17 -0
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
import time
|
|
5
|
+
from typing import Union
|
|
6
|
+
|
|
7
|
+
import grpc
|
|
8
|
+
import requests
|
|
9
|
+
import typer
|
|
10
|
+
from rich.progress import Progress, SpinnerColumn, TextColumn
|
|
11
|
+
|
|
12
|
+
from remotivelabs.cli.broker.lib.broker import Broker, LicenseInfo
|
|
13
|
+
from remotivelabs.cli.utils.console import print_unformatted_to_stderr
|
|
14
|
+
from remotivelabs.cli.utils.rest_helper import RestHelper as Rest
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class LicenseFlow:
|
|
18
|
+
def describe_with_url(self, url: str) -> LicenseInfo:
|
|
19
|
+
b = LicenseFlow.__try_connect_to_broker(
|
|
20
|
+
url=url,
|
|
21
|
+
progress_label=f"Fetching license from broker ({url})...",
|
|
22
|
+
on_error_progress_label=f"Fetching license from broker ({url})... make sure broker is running.",
|
|
23
|
+
)
|
|
24
|
+
return b.get_license()
|
|
25
|
+
|
|
26
|
+
def describe_with_hotspot(self, url: Union[str, None] = "http://192.168.4.1:50051") -> LicenseInfo:
|
|
27
|
+
if url is None:
|
|
28
|
+
url = "http://192.168.4.1:50051"
|
|
29
|
+
|
|
30
|
+
b = LicenseFlow.__try_connect_to_broker(
|
|
31
|
+
url=url, progress_label=f"Fetching license from broker using hotspot ({url})... Make sure to switch to remotivelabs-xxx Wi-Fi"
|
|
32
|
+
)
|
|
33
|
+
return b.get_license()
|
|
34
|
+
|
|
35
|
+
def request_with_url_with_internet(self, url: str) -> None:
|
|
36
|
+
print_unformatted_to_stderr("This requires internet connection from your computer during the entire licensing process")
|
|
37
|
+
|
|
38
|
+
email = LicenseFlow.__try_authenticate_and_get_email_from_cloud()
|
|
39
|
+
|
|
40
|
+
broker_to_license = LicenseFlow.__try_connect_to_broker(
|
|
41
|
+
url=url,
|
|
42
|
+
progress_label=f"Fetching existing broker license...({url})...",
|
|
43
|
+
)
|
|
44
|
+
|
|
45
|
+
existing_license = broker_to_license.get_license()
|
|
46
|
+
if existing_license.valid:
|
|
47
|
+
apply = typer.confirm(
|
|
48
|
+
f"This broker already has a valid license, and is licensed to {existing_license.email}. Do you still wish to proceed?"
|
|
49
|
+
)
|
|
50
|
+
if not apply:
|
|
51
|
+
return
|
|
52
|
+
|
|
53
|
+
print_unformatted_to_stderr(
|
|
54
|
+
":point_right: [bold yellow]This will request a new license or use an existing license if "
|
|
55
|
+
"this hardware has already been licensed[/bold yellow]"
|
|
56
|
+
)
|
|
57
|
+
apply_license = typer.confirm("Do you want to continue and agree to terms and conditions at https://remotivelabs.com/license?")
|
|
58
|
+
if not apply_license:
|
|
59
|
+
return
|
|
60
|
+
|
|
61
|
+
existing_license = broker_to_license.get_license()
|
|
62
|
+
license_data_b64 = LicenseFlow.__try_request_license(email, existing_license, "Requesting license...")
|
|
63
|
+
|
|
64
|
+
with use_progress("Applying license to broker..."):
|
|
65
|
+
new_license = broker_to_license.apply_license(license_data_b64)
|
|
66
|
+
if new_license.valid:
|
|
67
|
+
print_unformatted_to_stderr(f":thumbsup: Successfully applied license, it remains valid until {new_license.expires}")
|
|
68
|
+
|
|
69
|
+
def request_with_hotspot(self, url: Union[str, None] = "http://192.168.4.1:50051") -> None:
|
|
70
|
+
"""
|
|
71
|
+
This flow expects changes between networks and tries to guide the user accordingly
|
|
72
|
+
:param url: If None it will use the default hotspot IP
|
|
73
|
+
"""
|
|
74
|
+
if url is None:
|
|
75
|
+
url = "http://192.168.4.1:50051"
|
|
76
|
+
|
|
77
|
+
print_unformatted_to_stderr(
|
|
78
|
+
"Licensing a broker over its wifi hotspot will require you to switch between internet connectivity "
|
|
79
|
+
"and remotivelabs-xxx wifi hotspot"
|
|
80
|
+
)
|
|
81
|
+
|
|
82
|
+
email = LicenseFlow.__try_authenticate_and_get_email_from_cloud()
|
|
83
|
+
|
|
84
|
+
broker_to_license = LicenseFlow.__try_connect_to_broker(
|
|
85
|
+
url=url,
|
|
86
|
+
progress_label=f"Fetching license from broker using hotspot ({url})... Make sure to switch to remotivelabs-xxx wifi",
|
|
87
|
+
)
|
|
88
|
+
existing_license = broker_to_license.get_license()
|
|
89
|
+
if existing_license.valid:
|
|
90
|
+
apply = typer.confirm(
|
|
91
|
+
f"This broker already has a valid license and is licensed to {existing_license.email}. Do you still wish to proceed?"
|
|
92
|
+
)
|
|
93
|
+
if not apply:
|
|
94
|
+
return
|
|
95
|
+
|
|
96
|
+
print_unformatted_to_stderr(
|
|
97
|
+
":point_right: [bold yellow]This will request a new license or use an existing license if "
|
|
98
|
+
"this hardware has already been licensed[/bold yellow]"
|
|
99
|
+
)
|
|
100
|
+
apply_license = typer.confirm("Do you want to continue and agree to terms and conditions at https://remotivelabs.com/license?")
|
|
101
|
+
if not apply_license:
|
|
102
|
+
return
|
|
103
|
+
|
|
104
|
+
license_data_b64 = LicenseFlow.__try_request_license(email, existing_license)
|
|
105
|
+
|
|
106
|
+
broker_to_license = LicenseFlow.__try_connect_to_broker(
|
|
107
|
+
url=url,
|
|
108
|
+
progress_label="Applying license to broker... Make sure to switch back to remotivelabs-xxx wifi hotspot",
|
|
109
|
+
)
|
|
110
|
+
new_license = broker_to_license.apply_license(license_data_b64)
|
|
111
|
+
print_unformatted_to_stderr(f":thumbsup: Successfully applied license, expires {new_license.expires}")
|
|
112
|
+
|
|
113
|
+
@staticmethod
|
|
114
|
+
def __try_connect_to_broker(url: str, progress_label: str, on_error_progress_label: Union[str, None] = None) -> Broker:
|
|
115
|
+
with use_progress(progress_label) as p:
|
|
116
|
+
while True:
|
|
117
|
+
try:
|
|
118
|
+
broker_to_license = Broker(url=url)
|
|
119
|
+
break
|
|
120
|
+
except grpc.RpcError:
|
|
121
|
+
if on_error_progress_label is not None:
|
|
122
|
+
p.update(
|
|
123
|
+
p.task_ids[0],
|
|
124
|
+
description=on_error_progress_label,
|
|
125
|
+
)
|
|
126
|
+
time.sleep(1)
|
|
127
|
+
if on_error_progress_label is None:
|
|
128
|
+
print_unformatted_to_stderr(f":white_check_mark: {progress_label}")
|
|
129
|
+
else:
|
|
130
|
+
print_unformatted_to_stderr(f":white_check_mark: {on_error_progress_label}")
|
|
131
|
+
return broker_to_license
|
|
132
|
+
|
|
133
|
+
@staticmethod
|
|
134
|
+
def __try_authenticate_and_get_email_from_cloud() -> str:
|
|
135
|
+
with use_progress("Fetching user info from cloud... Make sure you are connected to internet"):
|
|
136
|
+
while True:
|
|
137
|
+
try:
|
|
138
|
+
r = Rest.handle_get("/api/whoami", return_response=True, use_progress_indicator=False, timeout=5)
|
|
139
|
+
break
|
|
140
|
+
except (requests.exceptions.ConnectionError, requests.exceptions.Timeout):
|
|
141
|
+
time.sleep(1)
|
|
142
|
+
print_unformatted_to_stderr(":white_check_mark: Fetching user info from cloud... Make sure you are connected to internet")
|
|
143
|
+
if r is None:
|
|
144
|
+
return ""
|
|
145
|
+
return str(r.json()["email"])
|
|
146
|
+
|
|
147
|
+
@staticmethod
|
|
148
|
+
def __try_request_license(
|
|
149
|
+
email: str,
|
|
150
|
+
existing_license: LicenseInfo,
|
|
151
|
+
progress_label: str = "Requesting license... make sure to switch back to network with internet access",
|
|
152
|
+
) -> bytes:
|
|
153
|
+
with use_progress(progress_label):
|
|
154
|
+
while True:
|
|
155
|
+
try:
|
|
156
|
+
license_data_b64 = Rest.request_license(email, json.loads(existing_license.machine_id)).encode()
|
|
157
|
+
break
|
|
158
|
+
except (requests.exceptions.ConnectionError, requests.exceptions.Timeout):
|
|
159
|
+
time.sleep(1)
|
|
160
|
+
print_unformatted_to_stderr(f":white_check_mark: {progress_label}")
|
|
161
|
+
return bytes(license_data_b64)
|
|
162
|
+
|
|
163
|
+
|
|
164
|
+
def use_progress(label: str) -> Progress:
|
|
165
|
+
p = Progress(SpinnerColumn(), TextColumn("[progress.description]{task.description}"), transient=True, expand=False)
|
|
166
|
+
p.add_task(label, total=1)
|
|
167
|
+
return p
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import dataclasses
|
|
2
|
+
import json
|
|
3
|
+
from enum import Enum
|
|
4
|
+
|
|
5
|
+
import typer
|
|
6
|
+
from rich import print_json
|
|
7
|
+
|
|
8
|
+
from remotivelabs.cli.broker.license_flows import LicenseFlow
|
|
9
|
+
from remotivelabs.cli.typer import typer_utils
|
|
10
|
+
|
|
11
|
+
app = typer_utils.create_typer()
|
|
12
|
+
|
|
13
|
+
help_text = """
|
|
14
|
+
More info on our docs page
|
|
15
|
+
https://docs.remotivelabs.com/docs/remotive-broker/getting-started
|
|
16
|
+
|
|
17
|
+
--connect describes how the connection is made to the broker and helps us properly connect to broker
|
|
18
|
+
|
|
19
|
+
url = Connects to the broker with the specified url, this is
|
|
20
|
+
hotspot = If you are using RemotiveBox (our reference hardware) you can connect to the
|
|
21
|
+
broker over its wifi hotspot. Getting the license from a RemotiveBox over its wifi hotspot
|
|
22
|
+
requires you switch wi-fi network to the RemotiveBox hotspot called 'remotivelabs-xxx' where
|
|
23
|
+
'xxx' is a random generated id.
|
|
24
|
+
|
|
25
|
+
--url is the broker url, this is mandatory when connect type is "url"
|
|
26
|
+
"""
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
class Connection(str, Enum):
|
|
30
|
+
hotspot = "hotspot"
|
|
31
|
+
url = "url"
|
|
32
|
+
# TODO - Support discover using mdns
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
@app.command()
|
|
36
|
+
def describe(
|
|
37
|
+
connect: Connection = typer.Option("url", case_sensitive=False, help="How to connect to broker"),
|
|
38
|
+
url: str = typer.Option("http://localhost:50051", is_eager=False, help="Broker URL", envvar="REMOTIVE_BROKER_URL"),
|
|
39
|
+
) -> None:
|
|
40
|
+
"""
|
|
41
|
+
Show licence information
|
|
42
|
+
|
|
43
|
+
More info on our docs page
|
|
44
|
+
https://docs.remotivelabs.com/docs/remotive-broker/getting-started
|
|
45
|
+
|
|
46
|
+
--connect describes how the connection is made to the broker and helps us properly connect to broker
|
|
47
|
+
|
|
48
|
+
url = Connects to the broker with the specified url, this is
|
|
49
|
+
hotspot = If you are using RemotiveBox (our reference hardware) you can connect to the
|
|
50
|
+
broker over its wifi hotspot. Getting the license from a RemotiveBox over its wifi hotspot
|
|
51
|
+
requires you switch wi-fi network to the RemotiveBox hotspot called 'remotivelabs-xxx' where
|
|
52
|
+
'xxx' is a random generated id.
|
|
53
|
+
|
|
54
|
+
--url is the broker url, this is mandatory when connect type is "url"
|
|
55
|
+
"""
|
|
56
|
+
license_flow = LicenseFlow()
|
|
57
|
+
if connect == Connection.url:
|
|
58
|
+
existing_license = license_flow.describe_with_url(url)
|
|
59
|
+
print_json(json.dumps(dataclasses.asdict(existing_license)))
|
|
60
|
+
if connect == Connection.hotspot:
|
|
61
|
+
existing_license = license_flow.describe_with_hotspot(url if url != "http://localhost:50051" else None)
|
|
62
|
+
print_json(json.dumps(dataclasses.asdict(existing_license)))
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
@app.command()
|
|
66
|
+
def request(
|
|
67
|
+
connect: Connection = typer.Option("url", case_sensitive=False, help="How to connect to broker"),
|
|
68
|
+
url: str = typer.Option(
|
|
69
|
+
"http://localhost:50051",
|
|
70
|
+
is_eager=False,
|
|
71
|
+
help="Broker url, this is mandatory when connect type is 'url'",
|
|
72
|
+
envvar="REMOTIVE_BROKER_URL",
|
|
73
|
+
),
|
|
74
|
+
) -> None:
|
|
75
|
+
"""
|
|
76
|
+
Requests and applies a new or existing License to a broker, Note that internet access is required on your
|
|
77
|
+
computer
|
|
78
|
+
|
|
79
|
+
More info on our docs page
|
|
80
|
+
https://docs.remotivelabs.com/docs/remotive-broker/getting-started
|
|
81
|
+
|
|
82
|
+
--connect describes how the connection is made to the broker and helps us properly connect to broker
|
|
83
|
+
|
|
84
|
+
url = Use url to connect to broker (use --url)
|
|
85
|
+
hotspot = If you are using RemotiveBox (our reference hardware) you can connect to the
|
|
86
|
+
broker over its wifi hotspot. Licensing a broker on a RemotiveBox over its wifi hotspot
|
|
87
|
+
requires you switch wi-fi network to the RemotiveBox hotspot called 'remotivelabs-xxx' where
|
|
88
|
+
'xxx' is a random generated id.
|
|
89
|
+
https://docs.remotivelabs.com/docs/remotive-broker/getting-started/remotive-box
|
|
90
|
+
|
|
91
|
+
--url is the broker url, this is mandatory when connect type is "url"
|
|
92
|
+
"""
|
|
93
|
+
|
|
94
|
+
license_flow = LicenseFlow()
|
|
95
|
+
if connect == Connection.url:
|
|
96
|
+
license_flow.request_with_url_with_internet(url)
|
|
97
|
+
if connect == Connection.hotspot:
|
|
98
|
+
license_flow.request_with_hotspot(url if url != "http://localhost:50051" else None)
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import Dict, List
|
|
4
|
+
|
|
5
|
+
import grpc
|
|
6
|
+
import typer
|
|
7
|
+
|
|
8
|
+
from remotivelabs.cli.broker.lib.broker import Broker
|
|
9
|
+
from remotivelabs.cli.typer import typer_utils
|
|
10
|
+
from remotivelabs.cli.utils.console import print_generic_error, print_generic_message, print_grpc_error, print_success
|
|
11
|
+
|
|
12
|
+
app = typer_utils.create_typer(help=help)
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def recording_and_namespace(recording: str) -> Dict[str, str]:
|
|
16
|
+
splitted = recording.split("::")
|
|
17
|
+
if len(splitted) != 2:
|
|
18
|
+
print_generic_error("Invalid --recording option, expected file_name::namespace")
|
|
19
|
+
raise typer.Exit(1)
|
|
20
|
+
return {"recording": splitted[0], "namespace": splitted[1]}
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
@app.command()
|
|
24
|
+
def play(
|
|
25
|
+
recording: List[str] = typer.Option(..., help="Which recording and which namespace to play"),
|
|
26
|
+
url: str = typer.Option(..., help="Broker URL", envvar="REMOTIVE_BROKER_URL"),
|
|
27
|
+
api_key: str = typer.Option("offline", help="Cloud Broker API-KEY or access token", envvar="REMOTIVE_BROKER_API_KEY"),
|
|
28
|
+
) -> None:
|
|
29
|
+
"""
|
|
30
|
+
Play recording files on broker.
|
|
31
|
+
|
|
32
|
+
Separate recording file and namespace with ::
|
|
33
|
+
|
|
34
|
+
remotive broker playback play --recording myrecording_can0::can0 --recording myrecording_can1::can1
|
|
35
|
+
"""
|
|
36
|
+
|
|
37
|
+
rec = list(map(recording_and_namespace, recording))
|
|
38
|
+
try:
|
|
39
|
+
broker = Broker(url, api_key)
|
|
40
|
+
status = broker.play(rec)
|
|
41
|
+
# TODO: use log instead of print for debug information?
|
|
42
|
+
print_generic_message(str(status))
|
|
43
|
+
except grpc.RpcError as err:
|
|
44
|
+
print_grpc_error(err)
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
@app.command()
|
|
48
|
+
def stop(
|
|
49
|
+
recording: List[str] = typer.Option(..., help="Which recording and which namespace to stop"),
|
|
50
|
+
url: str = typer.Option(..., help="Broker URL", envvar="REMOTIVE_BROKER_URL"),
|
|
51
|
+
api_key: str = typer.Option("offline", help="Cloud Broker API-KEY or access token", envvar="REMOTIVE_BROKER_API_KEY"),
|
|
52
|
+
) -> None:
|
|
53
|
+
"""
|
|
54
|
+
Stop recordings that are beeing played on brokers are done with the same syntax as when you start them.
|
|
55
|
+
|
|
56
|
+
Separate recording file and namespace with ::
|
|
57
|
+
|
|
58
|
+
remotive broker playback stop --recording myrecording_can0::can0 --recording myrecording_can1::can1
|
|
59
|
+
"""
|
|
60
|
+
|
|
61
|
+
rec = list(map(recording_and_namespace, recording))
|
|
62
|
+
|
|
63
|
+
try:
|
|
64
|
+
broker = Broker(url, api_key)
|
|
65
|
+
broker.stop_play(rec)
|
|
66
|
+
print_success("Recording stopped")
|
|
67
|
+
except grpc.RpcError as err:
|
|
68
|
+
print_grpc_error(err)
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
@app.command()
|
|
72
|
+
def pause(
|
|
73
|
+
recording: List[str] = typer.Option(..., help="Which recording and which namespace to stop"),
|
|
74
|
+
url: str = typer.Option(..., help="Broker URL", envvar="REMOTIVE_BROKER_URL"),
|
|
75
|
+
api_key: str = typer.Option("offline", help="Cloud Broker API-KEY or access token", envvar="REMOTIVE_BROKER_API_KEY"),
|
|
76
|
+
) -> None:
|
|
77
|
+
"""
|
|
78
|
+
Pause recordings that are beeing played on brokers are done with the same syntax as when you start them.
|
|
79
|
+
|
|
80
|
+
Separate recording file and namespace with ::
|
|
81
|
+
|
|
82
|
+
remotive broker playback pause --recording myrecording_can0::can0 --recording myrecording_can1::can1
|
|
83
|
+
"""
|
|
84
|
+
|
|
85
|
+
rec = list(map(recording_and_namespace, recording))
|
|
86
|
+
try:
|
|
87
|
+
broker = Broker(url, api_key)
|
|
88
|
+
broker.pause_play(rec)
|
|
89
|
+
print_success("Recording paused")
|
|
90
|
+
except grpc.RpcError as err:
|
|
91
|
+
print_grpc_error(err)
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
@app.command()
|
|
95
|
+
def seek(
|
|
96
|
+
recording: List[str] = typer.Option(..., help="Which recording and which namespace to stop"),
|
|
97
|
+
seconds: float = typer.Option(..., help="Target offset in seconds"),
|
|
98
|
+
url: str = typer.Option(..., help="Broker URL", envvar="REMOTIVE_BROKER_URL"),
|
|
99
|
+
api_key: str = typer.Option("offline", help="Cloud Broker API-KEY or access token", envvar="REMOTIVE_BROKER_API_KEY"),
|
|
100
|
+
) -> None:
|
|
101
|
+
"""
|
|
102
|
+
Seeks to a position in seconds into the recording
|
|
103
|
+
|
|
104
|
+
Separate recording file and namespace with ::
|
|
105
|
+
|
|
106
|
+
remotive broker playback seek --recording myrecording_can0::can0 --recording myrecording_can1::can1 --seconds 23
|
|
107
|
+
"""
|
|
108
|
+
|
|
109
|
+
broker = Broker(url, api_key)
|
|
110
|
+
|
|
111
|
+
rec = list(map(recording_and_namespace, recording))
|
|
112
|
+
|
|
113
|
+
try:
|
|
114
|
+
broker = Broker(url, api_key)
|
|
115
|
+
broker.seek(rec, int(seconds * 1000000))
|
|
116
|
+
except grpc.RpcError as err:
|
|
117
|
+
print_grpc_error(err)
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import List
|
|
4
|
+
|
|
5
|
+
import grpc
|
|
6
|
+
import typer
|
|
7
|
+
|
|
8
|
+
from remotivelabs.cli.broker.lib.broker import Broker
|
|
9
|
+
from remotivelabs.cli.typer import typer_utils
|
|
10
|
+
from remotivelabs.cli.utils.console import print_grpc_error, print_success
|
|
11
|
+
|
|
12
|
+
app = typer_utils.create_typer(help=help)
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
@app.command()
|
|
16
|
+
def start(
|
|
17
|
+
filename: str = typer.Argument(..., help="Path to local file to upload"),
|
|
18
|
+
namespace: List[str] = typer.Option(..., help="Namespace to record"),
|
|
19
|
+
url: str = typer.Option(..., help="Broker URL", envvar="REMOTIVE_BROKER_URL"),
|
|
20
|
+
api_key: str = typer.Option("offline", help="Cloud Broker API-KEY or access token", envvar="REMOTIVE_BROKER_API_KEY"),
|
|
21
|
+
) -> None:
|
|
22
|
+
try:
|
|
23
|
+
broker = Broker(url, api_key)
|
|
24
|
+
broker.record_multiple(namespace, filename)
|
|
25
|
+
except grpc.RpcError as rpc_error:
|
|
26
|
+
print_grpc_error(rpc_error)
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
@app.command()
|
|
30
|
+
def stop(
|
|
31
|
+
filename: str = typer.Argument(..., help="Path to local file to upload"),
|
|
32
|
+
namespace: List[str] = typer.Option(..., help="Namespace to record"),
|
|
33
|
+
url: str = typer.Option(..., help="Broker URL", envvar="REMOTIVE_BROKER_URL"),
|
|
34
|
+
api_key: str = typer.Option("offline", help="Cloud Broker API-KEY or access token", envvar="REMOTIVE_BROKER_API_KEY"),
|
|
35
|
+
) -> None:
|
|
36
|
+
try:
|
|
37
|
+
broker = Broker(url, api_key)
|
|
38
|
+
broker.stop_multiple(namespace, filename)
|
|
39
|
+
print_success("Recording stopped")
|
|
40
|
+
except grpc.RpcError as rpc_error:
|
|
41
|
+
print_grpc_error(rpc_error)
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import AsyncGenerator, Iterable, Optional, Set
|
|
4
|
+
|
|
5
|
+
from remotivelabs.broker.recording_session import File, RecordingSessionClient
|
|
6
|
+
from remotivelabs.broker.recording_session.file import FileType
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class RecursiveFilesListingClient:
|
|
10
|
+
"""
|
|
11
|
+
Client for recursively listing files using a broker API.
|
|
12
|
+
|
|
13
|
+
This class provides functionality to recursively list files and directories using
|
|
14
|
+
a RecordingSessionClient. It retrieves files from specified paths and can process them
|
|
15
|
+
with customizable options such as filtering by file types or including directories
|
|
16
|
+
in the results.
|
|
17
|
+
|
|
18
|
+
Attributes:
|
|
19
|
+
broker_url (str): The URL of the broker API.
|
|
20
|
+
api_key (Optional[str]): The API key used for authentication, if required by the
|
|
21
|
+
broker API.
|
|
22
|
+
"""
|
|
23
|
+
|
|
24
|
+
def __init__(self, client: RecordingSessionClient):
|
|
25
|
+
self._broker_client = client
|
|
26
|
+
|
|
27
|
+
async def _list_files(self, path: str = "/") -> list[File]:
|
|
28
|
+
return await self._broker_client.list_recording_files(path=path)
|
|
29
|
+
|
|
30
|
+
async def list_all_files(
|
|
31
|
+
self,
|
|
32
|
+
path: str = "/",
|
|
33
|
+
file_types: Optional[Iterable[FileType]] = None,
|
|
34
|
+
) -> list[File]:
|
|
35
|
+
files: list[File] = []
|
|
36
|
+
async for f in self._iter_files_recursive(root=path, return_types=file_types):
|
|
37
|
+
files.append(f)
|
|
38
|
+
return files
|
|
39
|
+
|
|
40
|
+
async def _iter_files_recursive(
|
|
41
|
+
self,
|
|
42
|
+
root: str = "/",
|
|
43
|
+
*,
|
|
44
|
+
return_types: Optional[Iterable[FileType]] = None,
|
|
45
|
+
include_dirs: bool = False,
|
|
46
|
+
) -> AsyncGenerator[File, None]:
|
|
47
|
+
seen: Set[str] = set()
|
|
48
|
+
|
|
49
|
+
async def _walk(path: str) -> AsyncGenerator[File, None]:
|
|
50
|
+
if path in seen:
|
|
51
|
+
return
|
|
52
|
+
seen.add(path)
|
|
53
|
+
|
|
54
|
+
resp = await self._list_files(path)
|
|
55
|
+
for f in resp or []:
|
|
56
|
+
if f.type == FileType.FILE_TYPE_FOLDER:
|
|
57
|
+
# Optionally yield the directory itself
|
|
58
|
+
if include_dirs and (return_types is None or f.type in return_types):
|
|
59
|
+
yield f
|
|
60
|
+
# Recurse into the folder
|
|
61
|
+
async for file in _walk(f.path):
|
|
62
|
+
yield file
|
|
63
|
+
elif return_types is None or f.type in return_types:
|
|
64
|
+
yield f
|
|
65
|
+
|
|
66
|
+
async for file in _walk(root):
|
|
67
|
+
yield file
|