remotivelabs-cli 0.0.25__py3-none-any.whl → 0.0.27__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.
- cli/broker/brokers.py +2 -2
- cli/broker/export.py +5 -5
- cli/broker/files.py +5 -5
- cli/broker/lib/broker.py +82 -51
- cli/broker/license_flows.py +11 -9
- cli/broker/licenses.py +2 -2
- cli/broker/playback.py +14 -34
- cli/broker/record.py +3 -3
- cli/broker/scripting.py +4 -4
- cli/broker/signals.py +20 -12
- cli/cloud/__init__.py +0 -1
- cli/cloud/auth.py +40 -35
- cli/cloud/auth_tokens.py +73 -37
- cli/cloud/brokers.py +24 -33
- cli/cloud/cloud_cli.py +7 -18
- cli/cloud/configs.py +28 -11
- cli/cloud/filestorage.py +63 -51
- cli/cloud/organisations.py +30 -0
- cli/cloud/projects.py +11 -8
- cli/cloud/recordings.py +148 -117
- cli/cloud/recordings_playback.py +52 -39
- cli/cloud/rest_helper.py +247 -196
- cli/cloud/resumable_upload.py +9 -8
- cli/cloud/sample_recordings.py +5 -5
- cli/cloud/service_account_tokens.py +18 -16
- cli/cloud/service_accounts.py +9 -9
- cli/connect/__init__.py +0 -1
- cli/connect/connect.py +7 -6
- cli/connect/protopie/protopie.py +32 -16
- cli/errors.py +6 -5
- cli/remotive.py +13 -9
- cli/requirements.txt +4 -1
- cli/settings.py +9 -9
- cli/tools/__init__.py +0 -1
- cli/tools/can/__init__.py +0 -1
- cli/tools/can/can.py +8 -8
- cli/tools/tools.py +2 -2
- {remotivelabs_cli-0.0.25.dist-info → remotivelabs_cli-0.0.27.dist-info}/METADATA +5 -3
- remotivelabs_cli-0.0.27.dist-info/RECORD +45 -0
- remotivelabs_cli-0.0.25.dist-info/RECORD +0 -44
- {remotivelabs_cli-0.0.25.dist-info → remotivelabs_cli-0.0.27.dist-info}/LICENSE +0 -0
- {remotivelabs_cli-0.0.25.dist-info → remotivelabs_cli-0.0.27.dist-info}/WHEEL +0 -0
- {remotivelabs_cli-0.0.25.dist-info → remotivelabs_cli-0.0.27.dist-info}/entry_points.txt +0 -0
cli/cloud/recordings.py
CHANGED
@@ -9,32 +9,34 @@ import tempfile
|
|
9
9
|
import time
|
10
10
|
import urllib.parse
|
11
11
|
from pathlib import Path
|
12
|
-
from typing import Dict, List, Union
|
12
|
+
from typing import Any, Dict, List, Tuple, Union
|
13
|
+
from urllib.parse import quote
|
13
14
|
|
14
15
|
import grpc
|
15
16
|
import requests
|
16
17
|
import typer
|
17
|
-
from rich.progress import Progress, SpinnerColumn, TextColumn, track
|
18
|
+
from rich.progress import Progress, SpinnerColumn, TaskID, TextColumn, track
|
18
19
|
|
19
20
|
from cli.errors import ErrorPrinter
|
20
21
|
|
21
22
|
from ..broker.lib.broker import Broker
|
22
|
-
from . import rest_helper as rest
|
23
23
|
from .recordings_playback import app as playback_app
|
24
|
+
from .rest_helper import RestHelper as Rest
|
25
|
+
from .rest_helper import err_console
|
24
26
|
|
25
27
|
app = typer.Typer()
|
26
28
|
app.add_typer(playback_app, name="playback")
|
27
29
|
|
28
30
|
|
29
|
-
def uid(p):
|
31
|
+
def uid(p: Any) -> Any:
|
30
32
|
print(p)
|
31
33
|
return p["uid"]
|
32
34
|
|
33
35
|
|
34
36
|
# to be used in options
|
35
37
|
# autocompletion=project_names)
|
36
|
-
def project_names():
|
37
|
-
r = requests.get(f"{
|
38
|
+
def project_names() -> Any:
|
39
|
+
r = requests.get(f"{Rest.get_base_url()}/api/bu/{Rest.get_org()}/project", headers=Rest.get_headers(), timeout=60)
|
38
40
|
# sys.stderr.write(r.text)
|
39
41
|
if r.status_code == 200:
|
40
42
|
projects = r.json()
|
@@ -48,7 +50,7 @@ def project_names():
|
|
48
50
|
def list_recordings(
|
49
51
|
is_processing: bool = typer.Option(default=False, help="Use this option to see only those that are beeing processed or are invalid"),
|
50
52
|
project: str = typer.Option(..., help="Project ID", envvar="REMOTIVE_CLOUD_PROJECT"),
|
51
|
-
):
|
53
|
+
) -> None:
|
52
54
|
"""
|
53
55
|
List all recording sessions in a project. You can choose to see all valid recordings (default) or use
|
54
56
|
--is-processing and you will get those that are currently beeing processed or that failed to be validated.
|
@@ -56,35 +58,35 @@ def list_recordings(
|
|
56
58
|
"""
|
57
59
|
|
58
60
|
if is_processing:
|
59
|
-
|
61
|
+
Rest.handle_get(f"/api/project/{project}/files/recording/processing")
|
60
62
|
else:
|
61
|
-
|
63
|
+
Rest.handle_get(f"/api/project/{project}/files/recording")
|
62
64
|
|
63
65
|
|
64
66
|
@app.command(help="Shows details about a specific recording in project")
|
65
67
|
def describe(
|
66
68
|
recording_session: str = typer.Argument(..., help="Recording session id", envvar="REMOTIVE_CLOUD_RECORDING_SESSION"),
|
67
69
|
project: str = typer.Option(..., help="Project ID", envvar="REMOTIVE_CLOUD_PROJECT"),
|
68
|
-
):
|
69
|
-
|
70
|
+
) -> None:
|
71
|
+
Rest.handle_get(f"/api/project/{project}/files/recording/{recording_session}")
|
70
72
|
|
71
73
|
|
72
74
|
# @app.command(help="Shows details about a specific recording in project")
|
73
75
|
# def copy(recording_session: str = typer.Argument(..., help="Recording session id"),
|
74
76
|
# target_project: str = typer.Option(..., help="Which project to copy the recording to"),
|
75
77
|
# project: str = typer.Option(..., help="Project ID", envvar='REMOTIVE_CLOUD_PROJECT')):
|
76
|
-
#
|
78
|
+
# Rest.handle_post(url=f"/api/project/{project}/files/recording/{recording_session}/copy",
|
77
79
|
# body=json.dumps({'projectUid': target_project}))
|
78
80
|
|
79
81
|
|
80
|
-
def do_start(name: str, project: str, api_key: str, return_response: bool = False):
|
82
|
+
def do_start(name: str, project: str, api_key: str, return_response: bool = False) -> requests.Response | None:
|
81
83
|
if api_key == "":
|
82
84
|
body = {"size": "S"}
|
83
85
|
else:
|
84
86
|
body = {"size": "S", "apiKey": api_key}
|
85
87
|
|
86
88
|
name = name if name is not None else "personal"
|
87
|
-
return
|
89
|
+
return Rest.handle_post(
|
88
90
|
f"/api/project/{project}/brokers/{name}",
|
89
91
|
body=json.dumps(body),
|
90
92
|
return_response=return_response,
|
@@ -93,19 +95,20 @@ def do_start(name: str, project: str, api_key: str, return_response: bool = Fals
|
|
93
95
|
|
94
96
|
|
95
97
|
@app.command(help="Prepares all recording files and transformations to be available for playback")
|
96
|
-
def mount(
|
98
|
+
def mount( # noqa: C901
|
97
99
|
recording_session: str = typer.Argument(..., help="Recording session id", envvar="REMOTIVE_CLOUD_RECORDING_SESSION"),
|
98
100
|
broker: str = typer.Option(None, help="Broker to use"),
|
99
101
|
ensure_broker_started: bool = typer.Option(default=False, help="Ensure broker exists, start otherwise"),
|
100
102
|
transformation_name: str = typer.Option("default", help="Specify a custom signal transformation to use"),
|
101
103
|
project: str = typer.Option(..., help="Project ID", envvar="REMOTIVE_CLOUD_PROJECT"),
|
102
|
-
):
|
103
|
-
|
104
|
+
) -> None:
|
105
|
+
# pylint: disable=R0912
|
106
|
+
Rest.ensure_auth_token()
|
104
107
|
|
105
|
-
|
108
|
+
Rest.handle_get(f"/api/project/{project}/files/recording/{recording_session}", return_response=True)
|
106
109
|
|
107
110
|
if broker is None:
|
108
|
-
r =
|
111
|
+
r = Rest.handle_get(url=f"/api/project/{project}/brokers/personal", return_response=True, allow_status_codes=[404])
|
109
112
|
|
110
113
|
if r.status_code == 200:
|
111
114
|
broker = r.json()["shortName"]
|
@@ -114,32 +117,35 @@ def mount(
|
|
114
117
|
r = do_start(None, project, "", return_response=True)
|
115
118
|
if r.status_code != 200:
|
116
119
|
print(r.text)
|
117
|
-
exit(0)
|
120
|
+
sys.exit(0)
|
118
121
|
broker = r.json()["shortName"]
|
119
122
|
else:
|
120
123
|
sys.stderr.write(f"Got http status code {r.status_code}")
|
121
|
-
typer.Exit(0)
|
124
|
+
raise typer.Exit(0)
|
122
125
|
else:
|
123
|
-
r =
|
126
|
+
r = Rest.handle_get(url=f"/api/project/{project}/brokers/{broker}", return_response=True, allow_status_codes=[404])
|
124
127
|
|
128
|
+
if r is None:
|
129
|
+
sys.exit(1)
|
125
130
|
if r.status_code == 404:
|
126
131
|
if ensure_broker_started:
|
127
132
|
r = do_start(broker, project, "", return_response=True)
|
128
|
-
|
133
|
+
if r is None:
|
134
|
+
sys.exit(1)
|
129
135
|
if r.status_code != 200:
|
130
136
|
print(r.text)
|
131
|
-
exit(1)
|
137
|
+
sys.exit(1)
|
132
138
|
else:
|
133
139
|
ErrorPrinter.print_generic_error(f"Broker {broker} not running")
|
134
|
-
exit(1)
|
140
|
+
sys.exit(1)
|
135
141
|
elif r.status_code != 200:
|
136
142
|
sys.stderr.write(f"Got http status code {r.status_code}")
|
137
|
-
typer.Exit(1)
|
143
|
+
raise typer.Exit(1)
|
138
144
|
broker_config_query = ""
|
139
145
|
if transformation_name != "default":
|
140
146
|
broker_config_query = f"?brokerConfigName={transformation_name}"
|
141
147
|
|
142
|
-
|
148
|
+
Rest.handle_get(
|
143
149
|
f"/api/project/{project}/files/recording/{recording_session}/upload{broker_config_query}",
|
144
150
|
params={"brokerName": broker},
|
145
151
|
return_response=True,
|
@@ -155,29 +161,33 @@ def download_recording_file(
|
|
155
161
|
..., help="Recording session id that this file belongs to", envvar="REMOTIVE_CLOUD_RECORDING_SESSION"
|
156
162
|
),
|
157
163
|
project: str = typer.Option(..., help="Project ID", envvar="REMOTIVE_CLOUD_PROJECT"),
|
158
|
-
):
|
159
|
-
|
164
|
+
) -> None:
|
165
|
+
Rest.ensure_auth_token()
|
166
|
+
recording_file_name_qouted = quote(recording_file_name, safe="")
|
160
167
|
get_signed_url_resp = requests.get(
|
161
|
-
f"{
|
162
|
-
headers=
|
168
|
+
f"{Rest.get_base_url()}/api/project/{project}/files/recording/{recording_session}/recording-file/{recording_file_name_qouted}",
|
169
|
+
headers=Rest.get_headers(),
|
163
170
|
allow_redirects=True,
|
171
|
+
timeout=60,
|
164
172
|
)
|
165
173
|
if get_signed_url_resp.status_code == 200:
|
166
174
|
# Next download the actual file
|
167
|
-
|
175
|
+
Rest.download_file(recording_file_name, get_signed_url_resp.json()["downloadUrl"])
|
168
176
|
print(f"Downloaded {recording_file_name}")
|
177
|
+
else:
|
178
|
+
print(get_signed_url_resp)
|
169
179
|
|
170
180
|
|
171
181
|
@app.command(name="delete")
|
172
182
|
def delete(
|
173
183
|
recording_session: str = typer.Argument(..., help="Recording session id", envvar="REMOTIVE_CLOUD_RECORDING_SESSION"),
|
174
184
|
project: str = typer.Option(..., help="Project ID", envvar="REMOTIVE_CLOUD_PROJECT"),
|
175
|
-
):
|
185
|
+
) -> None:
|
176
186
|
"""
|
177
187
|
Deletes the specified recording session including all media files and configurations.
|
178
188
|
|
179
189
|
"""
|
180
|
-
|
190
|
+
Rest.handle_delete(f"/api/project/{project}/files/recording/{recording_session}")
|
181
191
|
|
182
192
|
|
183
193
|
@app.command(name="delete-recording-file")
|
@@ -187,12 +197,12 @@ def delete_recording_file(
|
|
187
197
|
..., help="Recording session id that this file belongs to", envvar="REMOTIVE_CLOUD_RECORDING_SESSION"
|
188
198
|
),
|
189
199
|
project: str = typer.Option(..., help="Project ID", envvar="REMOTIVE_CLOUD_PROJECT"),
|
190
|
-
):
|
200
|
+
) -> None:
|
191
201
|
"""
|
192
202
|
Deletes the specified recording file
|
193
203
|
|
194
204
|
"""
|
195
|
-
|
205
|
+
Rest.handle_delete(f"/api/project/{project}/files/recording/{recording_session}/recording-file/{recording_file_name}")
|
196
206
|
|
197
207
|
|
198
208
|
@app.command()
|
@@ -209,19 +219,22 @@ def upload( # noqa: C901
|
|
209
219
|
),
|
210
220
|
project: str = typer.Option(..., help="Project ID", envvar="REMOTIVE_CLOUD_PROJECT"),
|
211
221
|
recording_session: str = typer.Option(default=None, help="Optional existing recording to upload file to"),
|
212
|
-
):
|
222
|
+
) -> None:
|
223
|
+
# pylint: disable=R0912,R0914,R0915
|
213
224
|
"""
|
214
225
|
Uploads a recording to RemotiveCloud.
|
215
226
|
Except for recordings from RemotiveBroker you can also upload Can ASC (.asc), Can BLF(.blf) and Can LOG (.log, .txt)
|
216
227
|
"""
|
217
228
|
|
218
229
|
filename = os.path.basename(path.name)
|
219
|
-
|
230
|
+
Rest.ensure_auth_token()
|
220
231
|
|
221
232
|
if recording_session is None:
|
222
|
-
r =
|
233
|
+
r = Rest.handle_post(f"/api/project/{project}/files/recording/{filename}", return_response=True)
|
223
234
|
else:
|
224
|
-
r =
|
235
|
+
r = Rest.handle_post(f"/api/project/{project}/files/recording/{recording_session}/recording-file/{filename}", return_response=True)
|
236
|
+
if r is None:
|
237
|
+
return
|
225
238
|
|
226
239
|
upload_url = r.text
|
227
240
|
url_path = urllib.parse.urlparse(upload_url).path
|
@@ -234,14 +247,18 @@ def upload( # noqa: C901
|
|
234
247
|
"Something went wrong, please try again. Please contact RemotiveLabs support if this problem remains"
|
235
248
|
)
|
236
249
|
ErrorPrinter.print_hint("Please make sure to use the latest version of RemotiveCLI")
|
237
|
-
exit(1)
|
250
|
+
sys.exit(1)
|
238
251
|
|
239
|
-
upload_response =
|
252
|
+
upload_response = Rest.upload_file_with_signed_url(
|
240
253
|
path=path, url=upload_url, upload_headers={"Content-Type": "application/x-www-form-urlencoded"}, return_response=True
|
241
254
|
)
|
242
255
|
|
256
|
+
if upload_response is None:
|
257
|
+
return
|
258
|
+
|
243
259
|
# Exact same as in cloud console
|
244
260
|
def get_processing_message(step: str) -> str:
|
261
|
+
# pylint: disable=R0911
|
245
262
|
if step == "REQUESTED":
|
246
263
|
return "Preparing file..."
|
247
264
|
if step == "VALIDATING":
|
@@ -264,10 +281,12 @@ def upload( # noqa: C901
|
|
264
281
|
t = p.add_task("Processing...", total=1)
|
265
282
|
while True:
|
266
283
|
time.sleep(1)
|
267
|
-
r =
|
284
|
+
r = Rest.handle_get(
|
268
285
|
f"/api/project/{project}/files/recording/processing", return_response=True, use_progress_indicator=False
|
269
286
|
)
|
270
|
-
|
287
|
+
if r is None:
|
288
|
+
return
|
289
|
+
status_list: List[Dict[str, Any]] = r.json()
|
271
290
|
res = list(filter(lambda s: s["uploadId"] == upload_id, status_list))
|
272
291
|
if len(res) == 1:
|
273
292
|
tracking_state = res[0]
|
@@ -284,28 +303,41 @@ def upload( # noqa: C901
|
|
284
303
|
break
|
285
304
|
if error_message is not None:
|
286
305
|
ErrorPrinter.print_generic_error(error_message)
|
287
|
-
exit(1)
|
306
|
+
sys.exit(1)
|
288
307
|
|
289
308
|
else:
|
290
|
-
|
309
|
+
err_console.print(f":boom: [bold red]Got status code[/bold red]: {upload_response.status_code} {upload_response.text}")
|
291
310
|
|
292
311
|
|
312
|
+
# TODO - Change to use Path for directory # pylint: disable=W0511
|
293
313
|
@app.command()
|
294
|
-
def upload_broker_configuration(
|
295
|
-
directory:
|
314
|
+
def upload_broker_configuration( # noqa: C901
|
315
|
+
directory: Path = typer.Argument(
|
316
|
+
...,
|
317
|
+
exists=True,
|
318
|
+
file_okay=False,
|
319
|
+
dir_okay=True,
|
320
|
+
writable=False,
|
321
|
+
readable=True,
|
322
|
+
resolve_path=True,
|
323
|
+
help="Directory to upload",
|
324
|
+
),
|
296
325
|
recording_session: str = typer.Option(..., help="Recording session id", envvar="REMOTIVE_CLOUD_RECORDING_SESSION"),
|
297
326
|
project: str = typer.Option(..., help="Project ID", envvar="REMOTIVE_CLOUD_PROJECT"),
|
298
327
|
overwrite: bool = typer.Option(False, help="Overwrite existing configuration if it exists"),
|
299
|
-
):
|
328
|
+
) -> None:
|
329
|
+
"""
|
330
|
+
Uploads a broker configuration directory
|
331
|
+
"""
|
332
|
+
# pylint: disable=R0914,R0915
|
333
|
+
|
300
334
|
# Must end with /
|
301
|
-
if not directory.endswith("/"):
|
302
|
-
directory = f"{directory}/"
|
303
335
|
|
304
336
|
#
|
305
337
|
# List files in specified directory. Look for interfaces.json and use that directory where this is located
|
306
338
|
# as configuration home directory
|
307
339
|
#
|
308
|
-
files = list(filter(lambda item: "interfaces.json" in item, glob.iglob(directory + "
|
340
|
+
files = list(filter(lambda item: "interfaces.json" in item, glob.iglob(str(directory) + "/**/**", recursive=True)))
|
309
341
|
if len(files) == 0:
|
310
342
|
sys.stderr.write("No interfaces.json found in directory, this file is required")
|
311
343
|
raise typer.Exit(1)
|
@@ -319,14 +351,16 @@ def upload_broker_configuration(
|
|
319
351
|
# name already exists
|
320
352
|
#
|
321
353
|
# task = progress.add_task(description=f"Preparing upload of {broker_config_dir_name}", total=1)
|
322
|
-
details_resp =
|
354
|
+
details_resp = Rest.handle_get(f"/api/project/{project}/files/recording/{recording_session}", return_response=True)
|
355
|
+
if details_resp is None:
|
356
|
+
return
|
323
357
|
details = details_resp.json()
|
324
358
|
existing_configs = details["brokerConfigurations"]
|
325
359
|
if len(existing_configs) > 0:
|
326
360
|
data = list(filter(lambda x: x["name"] == broker_config_dir_name, existing_configs))
|
327
361
|
if len(data) > 0:
|
328
362
|
if overwrite:
|
329
|
-
|
363
|
+
Rest.handle_delete(
|
330
364
|
f"/api/project/{project}/files/recording/{recording_session}/configuration/{broker_config_dir_name}", quiet=True
|
331
365
|
)
|
332
366
|
else:
|
@@ -340,7 +374,7 @@ def upload_broker_configuration(
|
|
340
374
|
file_infos = list(
|
341
375
|
map(
|
342
376
|
lambda item: {"local_path": item, "remote_path": f"/{broker_config_dir_name}{item.rsplit(broker_config_dir_name, 1)[-1]}"},
|
343
|
-
glob.iglob(directory + "
|
377
|
+
glob.iglob(str(directory) + "/**/*.*", recursive=True),
|
344
378
|
)
|
345
379
|
)
|
346
380
|
|
@@ -349,11 +383,13 @@ def upload_broker_configuration(
|
|
349
383
|
#
|
350
384
|
json_request_upload_urls_req = {"name": "not_used", "paths": list(map(lambda x: x["remote_path"], file_infos))}
|
351
385
|
|
352
|
-
response =
|
386
|
+
response = Rest.handle_put(
|
353
387
|
url=f"/api/project/{project}/files/recording/{recording_session}/configuration",
|
354
388
|
return_response=True,
|
355
389
|
body=json.dumps(json_request_upload_urls_req),
|
356
390
|
)
|
391
|
+
if response is None:
|
392
|
+
return
|
357
393
|
if response.status_code != 200:
|
358
394
|
print("Failed to prepare configuration upload")
|
359
395
|
print(f"{response.text} - {response.status_code}")
|
@@ -371,48 +407,52 @@ def upload_broker_configuration(
|
|
371
407
|
path = file["local_path"]
|
372
408
|
url = upload_urls[key]
|
373
409
|
headers = {"Content-Type": "application/x-www-form-urlencoded"}
|
374
|
-
r = requests.put(url, open(path, "rb"), headers=headers)
|
410
|
+
r = requests.put(url, open(path, "rb"), headers=headers, timeout=60) # pylint: disable=R1732
|
375
411
|
if r.status_code != 200:
|
376
412
|
print("Failed to upload broker configuration")
|
377
413
|
print(r.status_code)
|
378
414
|
print(r.text)
|
379
|
-
typer.Exit(1)
|
415
|
+
raise typer.Exit(1)
|
380
416
|
|
381
417
|
print(f"Successfully uploaded broker configuration {broker_config_dir_name}")
|
382
418
|
|
383
419
|
|
384
420
|
@app.command(help="Downloads the specified broker configuration directory as zip file")
|
385
|
-
def
|
421
|
+
def download_broker_configuration(
|
386
422
|
broker_config_name: str = typer.Argument(..., help="Broker config name"),
|
387
423
|
recording_session: str = typer.Option(..., help="Recording session id", envvar="REMOTIVE_CLOUD_RECORDING_SESSION"),
|
388
424
|
project: str = typer.Option(..., help="Project ID", envvar="REMOTIVE_CLOUD_PROJECT"),
|
389
|
-
):
|
390
|
-
|
391
|
-
r =
|
425
|
+
) -> None:
|
426
|
+
Rest.ensure_auth_token()
|
427
|
+
r = Rest.handle_get(
|
392
428
|
url=f"/api/project/{project}/files/recording/{recording_session}/configuration/{broker_config_name}", return_response=True
|
393
429
|
)
|
430
|
+
if r is None:
|
431
|
+
return
|
394
432
|
filename = get_filename_from_cd(r.headers.get("content-disposition"))
|
395
|
-
|
396
|
-
|
433
|
+
if filename is not None:
|
434
|
+
with open(filename, "wb") as f:
|
435
|
+
f.write(r.content)
|
436
|
+
print(f"Downloaded file {filename}")
|
397
437
|
|
398
438
|
|
399
|
-
@app.command(help="
|
400
|
-
def
|
439
|
+
@app.command(help="Delete the specified broker configuration")
|
440
|
+
def delete_broker_configuration(
|
401
441
|
broker_config_name: str = typer.Argument(..., help="Broker config name"),
|
402
442
|
recording_session: str = typer.Option(..., help="Recording session id", envvar="REMOTIVE_CLOUD_RECORDING_SESSION"),
|
403
443
|
project: str = typer.Option(..., help="Project ID", envvar="REMOTIVE_CLOUD_PROJECT"),
|
404
|
-
):
|
405
|
-
|
444
|
+
) -> None:
|
445
|
+
Rest.handle_delete(url=f"/api/project/{project}/files/recording/{recording_session}/configuration/{broker_config_name}")
|
406
446
|
|
407
447
|
|
408
448
|
@app.command(help="Copy recording to another project")
|
409
449
|
def copy(
|
410
450
|
recording_session: str = typer.Argument(..., help="Recording session id"),
|
411
|
-
|
412
|
-
|
413
|
-
):
|
414
|
-
|
415
|
-
url=f"/api/project/{
|
451
|
+
from_project: str = typer.Option(..., help="Source project"),
|
452
|
+
to_project: str = typer.Option(..., help="Destination project"),
|
453
|
+
) -> None:
|
454
|
+
Rest.handle_post(
|
455
|
+
url=f"/api/project/{from_project}/files/recording/{recording_session}/copy", body=json.dumps({"projectUid": to_project})
|
416
456
|
)
|
417
457
|
|
418
458
|
|
@@ -421,7 +461,7 @@ def play(
|
|
421
461
|
recording_session: str = typer.Argument(..., help="Recording session id", envvar="REMOTIVE_CLOUD_RECORDING_SESSION"),
|
422
462
|
broker: str = typer.Option(None, help="Broker to use"),
|
423
463
|
project: str = typer.Option(..., help="Project ID", envvar="REMOTIVE_CLOUD_PROJECT"),
|
424
|
-
):
|
464
|
+
) -> None:
|
425
465
|
"""
|
426
466
|
Plays a recording (Deprecated - Use recordings playback play)"
|
427
467
|
"""
|
@@ -433,32 +473,20 @@ def pause(
|
|
433
473
|
recording_session: str = typer.Argument(..., help="Recording session id", envvar="REMOTIVE_CLOUD_RECORDING_SESSION"),
|
434
474
|
broker: str = typer.Option(None, help="Broker to use"),
|
435
475
|
project: str = typer.Option(..., help="Project ID", envvar="REMOTIVE_CLOUD_PROJECT"),
|
436
|
-
):
|
476
|
+
) -> None:
|
437
477
|
"""
|
438
478
|
Pause recording (Deprecated - Use recordings playback pause")
|
439
479
|
"""
|
440
480
|
_do_change_playback_mode("pause", recording_session, broker, project)
|
441
481
|
|
442
482
|
|
443
|
-
@app.command(name="playback-status", deprecated=True)
|
444
|
-
def playback_status(
|
445
|
-
recording_session: str = typer.Argument(..., help="Recording session id", envvar="REMOTIVE_CLOUD_RECORDING_SESSION"),
|
446
|
-
broker: str = typer.Option(None, help="Broker to use"),
|
447
|
-
project: str = typer.Option(..., help="Project ID", envvar="REMOTIVE_CLOUD_PROJECT"),
|
448
|
-
):
|
449
|
-
"""
|
450
|
-
Playback progress (Deprecated - Use recordings playback progress)
|
451
|
-
"""
|
452
|
-
_do_change_playback_mode("status", recording_session, broker, project)
|
453
|
-
|
454
|
-
|
455
483
|
@app.command(deprecated=True)
|
456
484
|
def seek(
|
457
485
|
recording_session: str = typer.Argument(..., help="Recording session id", envvar="REMOTIVE_CLOUD_RECORDING_SESSION"),
|
458
486
|
seconds: int = typer.Option(..., min=0, help="Target offset in seconds"),
|
459
487
|
broker: str = typer.Option(None, help="Broker to use"),
|
460
488
|
project: str = typer.Option(..., help="Project ID", envvar="REMOTIVE_CLOUD_PROJECT"),
|
461
|
-
):
|
489
|
+
) -> None:
|
462
490
|
"""
|
463
491
|
Seek into recording (Deprecated - Use recordings playback seek)
|
464
492
|
"""
|
@@ -470,30 +498,36 @@ def stop(
|
|
470
498
|
recording_session: str = typer.Argument(..., help="Recording session id", envvar="REMOTIVE_CLOUD_RECORDING_SESSION"),
|
471
499
|
broker: str = typer.Option(None, help="Broker to use"),
|
472
500
|
project: str = typer.Option(..., help="Project ID", envvar="REMOTIVE_CLOUD_PROJECT"),
|
473
|
-
):
|
501
|
+
) -> None:
|
474
502
|
"""
|
475
503
|
Stop recording (Deprecated - Use recordings playback stop)
|
476
504
|
"""
|
477
505
|
_do_change_playback_mode("stop", recording_session, broker, project)
|
478
506
|
|
479
507
|
|
480
|
-
|
481
|
-
|
508
|
+
# pylint: disable-next=C0301
|
509
|
+
def _do_change_playback_mode(mode: str, recording_session: str, broker_name: str, project: str, seconds: int | None = None) -> None: # noqa: C901
|
510
|
+
# pylint: disable=R0912,R0914,R0915
|
511
|
+
response = Rest.handle_get(f"/api/project/{project}/files/recording/{recording_session}", return_response=True)
|
512
|
+
if response is None:
|
513
|
+
return
|
482
514
|
r = json.loads(response.text)
|
483
|
-
recordings:
|
515
|
+
recordings: List[Any] = r["recordings"]
|
484
516
|
files = list(map(lambda rec: {"recording": rec["fileName"], "namespace": rec["metadata"]["namespace"]}, recordings))
|
485
517
|
|
486
|
-
if
|
487
|
-
response =
|
518
|
+
if broker_name is not None:
|
519
|
+
response = Rest.handle_get(f"/api/project/{project}/brokers/{broker_name}", return_response=True, allow_status_codes=[404])
|
488
520
|
else:
|
489
|
-
response =
|
521
|
+
response = Rest.handle_get(f"/api/project/{project}/brokers/personal", return_response=True, allow_status_codes=[404])
|
522
|
+
if response is None:
|
523
|
+
return
|
490
524
|
if response.status_code == 404:
|
491
525
|
broker_arg = ""
|
492
|
-
if
|
493
|
-
broker_arg = f" --broker {
|
526
|
+
if broker_name is not None:
|
527
|
+
broker_arg = f" --broker {broker_name} --ensure-broker-started"
|
494
528
|
ErrorPrinter.print_generic_error("You need to mount the recording before you play")
|
495
529
|
ErrorPrinter.print_hint(f"remotive cloud recordings mount {recording_session}{broker_arg} --project {project}")
|
496
|
-
exit(1)
|
530
|
+
sys.exit(1)
|
497
531
|
|
498
532
|
broker_info = json.loads(response.text)
|
499
533
|
broker = Broker(broker_info["url"], None)
|
@@ -503,7 +537,7 @@ def _do_change_playback_mode(mode: str, recording_session: str, broker: str, pro
|
|
503
537
|
# error messages
|
504
538
|
tmp = os.path.join(tempfile.gettempdir(), os.urandom(24).hex())
|
505
539
|
broker.download(".cloud.context", tmp, True)
|
506
|
-
with open(tmp, "r") as f:
|
540
|
+
with open(tmp, "r", encoding="utf8") as f:
|
507
541
|
json_context = json.loads(f.read())
|
508
542
|
if json_context["recordingSessionId"] != recording_session:
|
509
543
|
ErrorPrinter.print_generic_error(
|
@@ -511,33 +545,30 @@ def _do_change_playback_mode(mode: str, recording_session: str, broker: str, pro
|
|
511
545
|
f"which not the same as you are trying to {mode}, use cmd below to mount this recording"
|
512
546
|
)
|
513
547
|
ErrorPrinter.print_hint(f"remotive cloud recordings mount {recording_session} --project {project}")
|
514
|
-
exit(1)
|
548
|
+
sys.exit(1)
|
515
549
|
except grpc.RpcError as rpc_error:
|
516
|
-
if rpc_error.code() == grpc.StatusCode.NOT_FOUND:
|
517
|
-
|
518
|
-
|
519
|
-
else:
|
520
|
-
|
521
|
-
exit(1)
|
550
|
+
# if rpc_error.code() == grpc.StatusCode.NOT_FOUND:
|
551
|
+
# ErrorPrinter.print_generic_error(f"You must use mount to prepare a recording before you can use {mode}")
|
552
|
+
# ErrorPrinter.print_hint(f"remotive cloud recordings mount {recording_session} --project {project}")
|
553
|
+
# else:
|
554
|
+
ErrorPrinter.print_grpc_error(rpc_error)
|
555
|
+
sys.exit(1)
|
522
556
|
if mode == "pause":
|
523
557
|
broker.pause_play(files, True)
|
524
558
|
elif mode == "play":
|
525
559
|
r = broker.play(files, True)
|
526
560
|
elif mode == "seek":
|
527
|
-
|
561
|
+
if seconds is not None:
|
562
|
+
broker.seek(files, int(seconds * 1000000), True)
|
563
|
+
else:
|
564
|
+
broker.seek(files, 0, True)
|
528
565
|
elif mode == "stop":
|
529
566
|
broker.seek(files, 0, True)
|
530
|
-
elif mode == "status":
|
531
|
-
p = Progress(SpinnerColumn(), TextColumn("[progress.description]{task.description}"), transient=True)
|
532
|
-
t = p.add_task("label", total=1)
|
533
|
-
|
534
|
-
with p:
|
535
|
-
broker.listen_on_playback(lambda c: p.update(t, description=str(c)))
|
536
567
|
else:
|
537
|
-
raise
|
568
|
+
raise ValueError(f"Illegal command {mode}")
|
538
569
|
|
539
570
|
|
540
|
-
def get_filename_from_cd(cd):
|
571
|
+
def get_filename_from_cd(cd: Union[str, None]) -> Union[str, None]:
|
541
572
|
"""
|
542
573
|
Get filename from content-disposition
|
543
574
|
"""
|
@@ -546,10 +577,10 @@ def get_filename_from_cd(cd):
|
|
546
577
|
fname = re.findall("filename=(.+)", cd)
|
547
578
|
if len(fname) == 0:
|
548
579
|
return None
|
549
|
-
return fname[0]
|
580
|
+
return str(fname[0])
|
550
581
|
|
551
582
|
|
552
|
-
def use_progress(label: str):
|
583
|
+
def use_progress(label: str) -> Tuple[Progress, TaskID]:
|
553
584
|
p = Progress(SpinnerColumn(), TextColumn("[progress.description]{task.description}"), transient=True)
|
554
585
|
t = p.add_task(label, total=1)
|
555
586
|
return (p, t)
|