Skip to content

Commit 0ae59bb

Browse files
committed
Tighten remote stream selection handling
1 parent c2e7868 commit 0ae59bb

5 files changed

Lines changed: 59 additions & 39 deletions

File tree

README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,8 @@ Currently supported commands include:
9393
* `update`
9494
* `filename ...`
9595

96+
Commands respond with `OK`, `WARNING ...`, or `ERROR ...`.
97+
9698
`filename` is followed by a series of space-delimited options enclosed in curly braces. e.g. {root:C:\root_data_dir}
9799
* `root` - Sets the root data directory.
98100
* `template` - sets the File Name / Template. Will unselect BIDS option. May contain wildcards.

src/mainwindow.cpp

Lines changed: 41 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ MainWindow::MainWindow(QWidget *parent, const char *config_file)
4040
connect(ui->refreshButton, &QPushButton::clicked, this, &MainWindow::refreshStreams);
4141
connect(ui->selectAllButton, &QPushButton::clicked, this, &MainWindow::selectAllStreams);
4242
connect(ui->selectNoneButton, &QPushButton::clicked, this, &MainWindow::selectNoStreams);
43-
connect(ui->startButton, &QPushButton::clicked, this, &MainWindow::startRecording);
43+
connect(ui->startButton, &QPushButton::clicked, this, [this]() { startRecording(); });
4444
connect(ui->stopButton, &QPushButton::clicked, this, &MainWindow::stopRecording);
4545
connect(ui->actionAbout, &QAction::triggered, this, [this]() {
4646
QString infostr = QStringLiteral("LSL library version: ") +
@@ -312,12 +312,11 @@ void MainWindow::rebuildStreamList() {
312312
item->setData(Qt::UserRole, i);
313313
item->setCheckState(k.checked ? Qt::Checked : Qt::Unchecked);
314314
item->setForeground(good_brush);
315-
item->setToolTip(QString("Name: %1\nType: %2\nSource ID: %3\nHostname: %4\nUID: %5")
315+
item->setToolTip(QString("Name: %1\nType: %2\nSource ID: %3\nHostname: %4")
316316
.arg(QString::fromStdString(k.name),
317317
QString::fromStdString(k.type),
318318
QString::fromStdString(k.id),
319-
QString::fromStdString(k.host),
320-
QString::fromStdString(k.uid)));
319+
QString::fromStdString(k.host)));
321320
ui->streamList->addItem(item);
322321
}
323322
}
@@ -384,9 +383,8 @@ std::vector<lsl::stream_info> MainWindow::refreshStreams() {
384383
return requestedAndAvailableStreams;
385384
}
386385

387-
void MainWindow::startRecording() {
386+
MainWindow::StartResult MainWindow::startRecording() {
388387
if (!currentRecording) {
389-
390388
// automatically refresh streams
391389
const std::vector<lsl::stream_info> requestedAndAvailableStreams = refreshStreams();
392390

@@ -399,29 +397,29 @@ void MainWindow::startRecording() {
399397
QMessageBox::Yes | QMessageBox::No, this);
400398
msgBox.setInformativeText("Do you want to start recording anyway?");
401399
msgBox.setDefaultButton(QMessageBox::No);
402-
if (msgBox.exec() != QMessageBox::Yes) return;
400+
if (msgBox.exec() != QMessageBox::Yes) return StartResult::Failed;
403401
}
404402

405403
if (requestedAndAvailableStreams.size() == 0) {
406404
QMessageBox msgBox(QMessageBox::Warning, "No available streams selected",
407405
"You have selected no streams", QMessageBox::Yes | QMessageBox::No, this);
408406
msgBox.setInformativeText("Do you want to start recording anyway?");
409407
msgBox.setDefaultButton(QMessageBox::No);
410-
if (msgBox.exec() != QMessageBox::Yes) return;
408+
if (msgBox.exec() != QMessageBox::Yes) return StartResult::Failed;
411409
}
412410
}
413411

414412
// don't hide critical errors.
415413
QString recFilename = replaceFilename(QDir::cleanPath(ui->lineEdit_template->text()));
416414
if (recFilename.isEmpty()) {
417415
QMessageBox::critical(this, "Filename empty", "Can not record without a file name");
418-
return;
416+
return StartResult::Failed;
419417
}
420418
if (ui->rootEdit->text().trimmed().isEmpty()) {
421419
QMessageBox::critical(this, "Study Root empty",
422420
"Can not record without a Study Root folder. "
423421
"Please set a Study Root before recording.");
424-
return;
422+
return StartResult::Failed;
425423
}
426424
recFilename.prepend(QDir::cleanPath(ui->rootEdit->text()) + '/');
427425

@@ -430,7 +428,7 @@ void MainWindow::startRecording() {
430428
if (recFileInfo.isDir()) {
431429
QMessageBox::warning(
432430
this, "Error", "Recording path already exists and is a directory");
433-
return;
431+
return StartResult::Failed;
434432
}
435433
QString rename_to = recFileInfo.absolutePath() + '/' + recFileInfo.baseName() +
436434
"_old%1." + recFileInfo.suffix();
@@ -441,7 +439,7 @@ void MainWindow::startRecording() {
441439
if (!QFile::rename(recFileInfo.absoluteFilePath(), newname)) {
442440
QMessageBox::warning(this, "Permissions issue",
443441
"Cannot rename the file " + recFilename + " to " + newname);
444-
return;
442+
return StartResult::Failed;
445443
}
446444
qInfo() << "Moved existing file to " << newname;
447445
recFileInfo.refresh();
@@ -452,7 +450,7 @@ void MainWindow::startRecording() {
452450
QMessageBox::warning(this, "Permissions issue",
453451
"Can not create the directory " + recFileInfo.dir().path() +
454452
". Please check your permissions.");
455-
return;
453+
return StartResult::Failed;
456454
}
457455

458456
std::vector<std::string> watchfor;
@@ -483,11 +481,13 @@ void MainWindow::startRecording() {
483481
ui->stopButton->setEnabled(true);
484482
ui->startButton->setEnabled(false);
485483
startTime = (int)lsl::local_clock();
484+
return StartResult::Started;
486485

487486
} else if (!hideWarnings) {
488487
QMessageBox::information(
489488
this, "Already recording", "The recording is already running", QMessageBox::Ok);
490489
}
490+
return StartResult::AlreadyRecording;
491491
}
492492

493493
void MainWindow::stopRecording() {
@@ -527,15 +527,16 @@ bool MainWindow::hasSelectedStreams() const {
527527
return false;
528528
}
529529

530-
void MainWindow::selectStreams(const QString &query) {
530+
MainWindow::SelectResult MainWindow::selectStreams(const QString &query) {
531531
updateKnownStreamSelectionFromUi();
532532
std::vector<lsl::stream_info> matchedStreams;
533533
try {
534534
matchedStreams = lsl::resolve_stream(query.toStdString(), 0, 1.0);
535535
} catch (std::exception &e) {
536536
qWarning() << "Invalid stream selection query" << query << ":" << e.what();
537-
return;
537+
return SelectResult::InvalidQuery;
538538
}
539+
if (matchedStreams.empty()) return SelectResult::NoMatches;
539540

540541
for (const auto &stream : matchedStreams) {
541542
bool known = false;
@@ -551,6 +552,7 @@ void MainWindow::selectStreams(const QString &query) {
551552
missingStreams.remove(info_to_listName(stream));
552553
}
553554
rebuildStreamList();
555+
return SelectResult::Selected;
554556
}
555557

556558
void MainWindow::buildBidsTemplate() {
@@ -693,7 +695,7 @@ void MainWindow::enableRcs(bool bEnable) {
693695
connect(rcs.get(), &RemoteControlSocket::filename, this, &MainWindow::rcsUpdateFilename);
694696
connect(rcs.get(), &RemoteControlSocket::select_all, this, &MainWindow::selectAllStreams);
695697
connect(rcs.get(), &RemoteControlSocket::select_none, this, &MainWindow::selectNoStreams);
696-
connect(rcs.get(), &RemoteControlSocket::select_stream, this, &MainWindow::selectStreams);
698+
connect(rcs.get(), &RemoteControlSocket::select_stream, this, &MainWindow::rcsSelectStreams);
697699
}
698700
bool oldState = ui->rcsCheckBox->blockSignals(true);
699701
ui->rcsCheckBox->setChecked(bEnable);
@@ -711,16 +713,37 @@ void MainWindow::rcsStartRecording(QTcpSocket *sock) {
711713
// Remote start should record the current stream selection. Do not call
712714
// selectAllStreams() here; doing so would override TCP `select <query>` commands.
713715
// hideWarnings suppresses non-critical confirmation dialogs for remote control.
716+
if (currentRecording) {
717+
if (sock) sock->write("WARNING already recording");
718+
return;
719+
}
714720
if (!hasSelectedStreams()) {
715721
qWarning() << "Remote start rejected: no streams selected";
716722
if (sock) sock->write("ERROR no streams selected");
717723
return;
718724
}
719725
const bool oldHideWarnings = hideWarnings;
720726
hideWarnings = true;
721-
startRecording();
727+
const StartResult result = startRecording();
722728
hideWarnings = oldHideWarnings;
723-
if (sock) sock->write("OK");
729+
if (!sock) return;
730+
if (result == StartResult::Started)
731+
sock->write("OK");
732+
else if (result == StartResult::AlreadyRecording)
733+
sock->write("WARNING already recording");
734+
else
735+
sock->write("ERROR failed to start recording");
736+
}
737+
738+
void MainWindow::rcsSelectStreams(const QString &query, QTcpSocket *sock) {
739+
const SelectResult result = selectStreams(query);
740+
if (!sock) return;
741+
if (result == SelectResult::Selected)
742+
sock->write("OK");
743+
else if (result == SelectResult::NoMatches)
744+
sock->write("WARNING no streams matched");
745+
else
746+
sock->write("ERROR invalid select query");
724747
}
725748

726749
void MainWindow::rcsStopRecording() {

src/mainwindow.h

Lines changed: 10 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -24,40 +24,27 @@ class StreamItem {
2424
public:
2525
StreamItem(const lsl::stream_info &info, bool required)
2626
: name(info.name()), type(info.type()), id(info.source_id()), host(info.hostname()),
27-
uid(info.uid()), sessionId(info.session_id()), channelCount(info.channel_count()),
28-
nominalSrate(info.nominal_srate()), channelFormat(info.channel_format()),
29-
createdAt(info.created_at()), checked(required) {}
27+
sessionId(info.session_id()), checked(required) {}
3028

3129
QString listName() const { return QString::fromStdString(name + " (" + host + ")"); }
3230
bool matches(const lsl::stream_info &info) const {
33-
if (!id.empty() || !info.source_id().empty())
34-
return name == info.name() && type == info.type() && id == info.source_id();
31+
if (!id.empty() && !info.source_id().empty())
32+
return id == info.source_id() && name == info.name() && type == info.type();
3533
return name == info.name() && type == info.type() && host == info.hostname() &&
36-
sessionId == info.session_id() && channelCount == info.channel_count() &&
37-
nominalSrate == info.nominal_srate() && channelFormat == info.channel_format();
34+
sessionId == info.session_id();
3835
}
3936
void updateInfo(const lsl::stream_info &info) {
4037
name = info.name();
4138
type = info.type();
4239
id = info.source_id();
4340
host = info.hostname();
44-
uid = info.uid();
4541
sessionId = info.session_id();
46-
channelCount = info.channel_count();
47-
nominalSrate = info.nominal_srate();
48-
channelFormat = info.channel_format();
49-
createdAt = info.created_at();
5042
}
5143
std::string name;
5244
std::string type;
5345
std::string id;
5446
std::string host;
55-
std::string uid;
5647
std::string sessionId;
57-
int32_t channelCount;
58-
double nominalSrate;
59-
lsl::channel_format_t channelFormat;
60-
double createdAt;
6148
bool checked;
6249
};
6350

@@ -74,26 +61,30 @@ private slots:
7461
void closeEvent(QCloseEvent *ev) override;
7562
void blockSelected(const QString &block);
7663
std::vector<lsl::stream_info> refreshStreams(void);
77-
void startRecording(void);
7864
void stopRecording(void);
7965
void selectAllStreams();
8066
void selectNoStreams();
81-
void selectStreams(const QString &query);
8267
void buildFilename();
8368
void buildBidsTemplate();
8469
void printReplacedFilename();
8570
void enableRcs(bool bEnable);
8671
void rcsCheckBoxChanged(bool checked);
8772
void rcsUpdateFilename(QString s);
8873
void rcsStartRecording(QTcpSocket *sock);
74+
void rcsSelectStreams(const QString &query, QTcpSocket *sock);
8975
void rcsStopRecording();
9076
void rcsportValueChangedInt(int value);
9177

9278
private:
79+
enum class StartResult { Started, AlreadyRecording, Failed };
80+
enum class SelectResult { Selected, NoMatches, InvalidQuery };
81+
9382
QString replaceFilename(QString fullfile) const;
9483
// function for loading / saving the config file
9584
QString find_config_file(const char *filename);
9685
QString counterPlaceholder() const;
86+
StartResult startRecording();
87+
SelectResult selectStreams(const QString &query);
9788
bool hasSelectedStreams() const;
9889
void updateKnownStreamSelectionFromUi();
9990
void rebuildStreamList();

src/tcpinterface.cpp

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,11 @@ void RemoteControlSocket::handleLine(QString s, QTcpSocket *sock) {
3131
} else if (s == "select none") {
3232
emit select_none();
3333
} else if (s.startsWith("select ")) {
34-
emit select_stream(s.mid(QStringLiteral("select ").size()));
34+
emit select_stream(s.mid(QStringLiteral("select ").size()), sock);
35+
return;
36+
} else {
37+
sock->write("ERROR unknown command");
38+
return;
3539
}
3640
sock->write("OK");
3741
// TODO: select /deselect streams

src/tcpinterface.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ class RemoteControlSocket : public QObject {
2121
void filename(QString s);
2222
void select_all();
2323
void select_none();
24-
void select_stream(QString query);
24+
void select_stream(QString query, QTcpSocket *sock);
2525

2626
public slots:
2727
void addClient();

0 commit comments

Comments
 (0)