feat: create stream and add members to stream
This commit is contained in:
@@ -38,6 +38,10 @@ qt_add_executable(llink
|
|||||||
src/particlelistmodel.cpp
|
src/particlelistmodel.cpp
|
||||||
src/textinputdialog.h
|
src/textinputdialog.h
|
||||||
src/textinputdialog.cpp
|
src/textinputdialog.cpp
|
||||||
|
src/createstreamdialog.h
|
||||||
|
src/createstreamdialog.cpp
|
||||||
|
src/memberpickerdialog.h
|
||||||
|
src/memberpickerdialog.cpp
|
||||||
)
|
)
|
||||||
|
|
||||||
qt_add_resources(llink
|
qt_add_resources(llink
|
||||||
|
|||||||
+66
-1
@@ -5,10 +5,13 @@
|
|||||||
#include "MainWindow.h"
|
#include "MainWindow.h"
|
||||||
#include "authdialog.h"
|
#include "authdialog.h"
|
||||||
#include "authmanager.h"
|
#include "authmanager.h"
|
||||||
|
#include "createstreamdialog.h"
|
||||||
|
#include "memberpickerdialog.h"
|
||||||
#include "settingsdialog.h"
|
#include "settingsdialog.h"
|
||||||
#include "textinputdialog.h"
|
#include "textinputdialog.h"
|
||||||
#include "ui_mainwindow.h"
|
#include "ui_mainwindow.h"
|
||||||
#include <QApplication>
|
#include <QApplication>
|
||||||
|
#include <QJsonDocument>
|
||||||
#include <QHeaderView>
|
#include <QHeaderView>
|
||||||
#include <QItemSelectionModel>
|
#include <QItemSelectionModel>
|
||||||
#include <QLabel>
|
#include <QLabel>
|
||||||
@@ -23,7 +26,7 @@
|
|||||||
MainWindow::MainWindow(QWidget *parent)
|
MainWindow::MainWindow(QWidget *parent)
|
||||||
: QMainWindow(parent), m_trayIcon(nullptr), m_authManager(new AuthManager(this)), m_settingsDialog(nullptr),
|
: QMainWindow(parent), m_trayIcon(nullptr), m_authManager(new AuthManager(this)), m_settingsDialog(nullptr),
|
||||||
m_authDialog(nullptr), m_store(nullptr), m_networkStreamModel(nullptr), m_particleListModel(nullptr),
|
m_authDialog(nullptr), m_store(nullptr), m_networkStreamModel(nullptr), m_particleListModel(nullptr),
|
||||||
m_selectedParticleRow(-1), m_isDragging(false), m_textInputDialog(nullptr)
|
m_selectedParticleRow(-1), m_isDragging(false), m_textInputDialog(nullptr), m_createStreamDialog(nullptr)
|
||||||
{
|
{
|
||||||
m_authDialog = new AuthDialog(this);
|
m_authDialog = new AuthDialog(this);
|
||||||
m_textInputDialog = new TextInputDialog(this);
|
m_textInputDialog = new TextInputDialog(this);
|
||||||
@@ -110,6 +113,57 @@ MainWindow::MainWindow(QWidget *parent)
|
|||||||
// DocumentSend toolbar button opens text input dialog
|
// DocumentSend toolbar button opens text input dialog
|
||||||
connect(ui->toolButton_3, &QToolButton::clicked, this, &MainWindow::showTextInputDialog);
|
connect(ui->toolButton_3, &QToolButton::clicked, this, &MainWindow::showTextInputDialog);
|
||||||
|
|
||||||
|
// "New stream" button
|
||||||
|
m_createStreamDialog = new CreateStreamDialog(this);
|
||||||
|
connect(ui->pushButton, &QPushButton::clicked, this, [this]() {
|
||||||
|
if (m_selectedNetworkName.isEmpty())
|
||||||
|
return;
|
||||||
|
const Network *network = m_store->networkById(m_selectedNetworkName);
|
||||||
|
if (network)
|
||||||
|
m_createStreamDialog->setNetworkMembers(network->members);
|
||||||
|
m_createStreamDialog->show();
|
||||||
|
m_createStreamDialog->raise();
|
||||||
|
m_createStreamDialog->activateWindow();
|
||||||
|
});
|
||||||
|
connect(m_createStreamDialog, &CreateStreamDialog::streamRequested, this,
|
||||||
|
[this](const QString &name, const QString &visibility, const QStringList &members) {
|
||||||
|
m_store->createStream(m_selectedNetworkName, name, "", visibility, members);
|
||||||
|
});
|
||||||
|
|
||||||
|
// "Add members" toolbar button (ListAdd icon in right sidebar)
|
||||||
|
connect(ui->toolButton_7, &QToolButton::clicked, this, [this]() {
|
||||||
|
if (m_selectedStreamId.isEmpty() || m_selectedNetworkName.isEmpty())
|
||||||
|
return;
|
||||||
|
const Network *network = m_store->networkById(m_selectedNetworkName);
|
||||||
|
const Stream *stream = m_store->streamById(m_selectedStreamId);
|
||||||
|
if (!network || !stream)
|
||||||
|
return;
|
||||||
|
|
||||||
|
MemberPickerDialog picker("Add Members", this);
|
||||||
|
picker.setMembers(network->members, stream->memberEmails);
|
||||||
|
|
||||||
|
if (picker.exec() == QDialog::Accepted)
|
||||||
|
{
|
||||||
|
QStringList emails = picker.selectedEmails();
|
||||||
|
if (!emails.isEmpty())
|
||||||
|
{
|
||||||
|
auto *op = m_store->addStreamMembers(m_selectedStreamId, emails);
|
||||||
|
connect(op, &Operation::success, this, [this](const QJsonDocument &) {
|
||||||
|
auto *fetchOp = m_store->fetchStream(m_selectedStreamId);
|
||||||
|
connect(fetchOp, &Operation::success, this, [this](const QJsonDocument &) {
|
||||||
|
ui->membersList->clear();
|
||||||
|
const Stream *s = m_store->streamById(m_selectedStreamId);
|
||||||
|
if (s)
|
||||||
|
{
|
||||||
|
for (const QString &email : s->memberEmails)
|
||||||
|
ui->membersList->addItem(email.split("@")[0]);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
m_authManager->tryRestoreSession();
|
m_authManager->tryRestoreSession();
|
||||||
|
|
||||||
setupTrayIcon();
|
setupTrayIcon();
|
||||||
@@ -286,6 +340,12 @@ void MainWindow::onParticleSelectionChanged(const QItemSelection &selected, cons
|
|||||||
{
|
{
|
||||||
ui->label->setText("");
|
ui->label->setText("");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Mark seen on selection
|
||||||
|
if (!particle->seen)
|
||||||
|
{
|
||||||
|
m_store->markParticleSeen(particle->id);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
updateStatusBar();
|
updateStatusBar();
|
||||||
@@ -407,6 +467,11 @@ bool MainWindow::eventFilter(QObject *obj, QEvent *event)
|
|||||||
showTextInputDialog();
|
showTextInputDialog();
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
if (keyEvent->key() == Qt::Key_A && keyEvent->modifiers() == Qt::NoModifier && !m_selectedParticleId.isEmpty())
|
||||||
|
{
|
||||||
|
m_store->ackParticle(m_selectedParticleId);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return QMainWindow::eventFilter(obj, event);
|
return QMainWindow::eventFilter(obj, event);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,6 +18,8 @@ class QLabel;
|
|||||||
QT_END_NAMESPACE
|
QT_END_NAMESPACE
|
||||||
|
|
||||||
class AuthDialog;
|
class AuthDialog;
|
||||||
|
class CreateStreamDialog;
|
||||||
|
class MemberPickerDialog;
|
||||||
class SettingsDialog;
|
class SettingsDialog;
|
||||||
class TextInputDialog;
|
class TextInputDialog;
|
||||||
|
|
||||||
@@ -62,6 +64,7 @@ private:
|
|||||||
|
|
||||||
SettingsDialog *m_settingsDialog;
|
SettingsDialog *m_settingsDialog;
|
||||||
TextInputDialog *m_textInputDialog;
|
TextInputDialog *m_textInputDialog;
|
||||||
|
CreateStreamDialog *m_createStreamDialog;
|
||||||
|
|
||||||
// Data and models
|
// Data and models
|
||||||
Store *m_store;
|
Store *m_store;
|
||||||
|
|||||||
@@ -0,0 +1,80 @@
|
|||||||
|
#include "createstreamdialog.h"
|
||||||
|
#include <QComboBox>
|
||||||
|
#include <QDialogButtonBox>
|
||||||
|
#include <QLabel>
|
||||||
|
#include <QLineEdit>
|
||||||
|
#include <QListWidget>
|
||||||
|
#include <QVBoxLayout>
|
||||||
|
|
||||||
|
CreateStreamDialog::CreateStreamDialog(QWidget *parent)
|
||||||
|
: QDialog(parent)
|
||||||
|
{
|
||||||
|
setWindowTitle("New Stream");
|
||||||
|
setMinimumWidth(320);
|
||||||
|
|
||||||
|
auto *layout = new QVBoxLayout(this);
|
||||||
|
|
||||||
|
layout->addWidget(new QLabel("Stream name:"));
|
||||||
|
m_nameEdit = new QLineEdit(this);
|
||||||
|
m_nameEdit->setPlaceholderText("e.g. design-review");
|
||||||
|
layout->addWidget(m_nameEdit);
|
||||||
|
|
||||||
|
layout->addWidget(new QLabel("Visibility:"));
|
||||||
|
m_visibilityCombo = new QComboBox(this);
|
||||||
|
m_visibilityCombo->addItem("Everyone in network", "network_all");
|
||||||
|
m_visibilityCombo->addItem("Specific members", "custom");
|
||||||
|
layout->addWidget(m_visibilityCombo);
|
||||||
|
|
||||||
|
m_membersLabel = new QLabel("Select members:");
|
||||||
|
m_membersList = new QListWidget(this);
|
||||||
|
m_membersLabel->setVisible(false);
|
||||||
|
m_membersList->setVisible(false);
|
||||||
|
layout->addWidget(m_membersLabel);
|
||||||
|
layout->addWidget(m_membersList);
|
||||||
|
|
||||||
|
connect(m_visibilityCombo, &QComboBox::currentIndexChanged, this, [this](int) {
|
||||||
|
bool specific = (m_visibilityCombo->currentData().toString() == "custom");
|
||||||
|
m_membersLabel->setVisible(specific);
|
||||||
|
m_membersList->setVisible(specific);
|
||||||
|
});
|
||||||
|
|
||||||
|
auto *buttons = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel, this);
|
||||||
|
layout->addWidget(buttons);
|
||||||
|
|
||||||
|
connect(buttons, &QDialogButtonBox::accepted, this, [this]() {
|
||||||
|
QString name = m_nameEdit->text().trimmed();
|
||||||
|
if (name.isEmpty())
|
||||||
|
return;
|
||||||
|
|
||||||
|
QString visibility = m_visibilityCombo->currentData().toString();
|
||||||
|
QStringList members;
|
||||||
|
if (visibility == "custom")
|
||||||
|
{
|
||||||
|
for (int i = 0; i < m_membersList->count(); ++i)
|
||||||
|
{
|
||||||
|
auto *item = m_membersList->item(i);
|
||||||
|
if (item->checkState() == Qt::Checked)
|
||||||
|
members.append(item->data(Qt::UserRole).toString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
emit streamRequested(name, visibility, members);
|
||||||
|
m_nameEdit->clear();
|
||||||
|
m_visibilityCombo->setCurrentIndex(0);
|
||||||
|
accept();
|
||||||
|
});
|
||||||
|
|
||||||
|
connect(buttons, &QDialogButtonBox::rejected, this, &QDialog::reject);
|
||||||
|
}
|
||||||
|
|
||||||
|
void CreateStreamDialog::setNetworkMembers(const std::vector<Human> &members)
|
||||||
|
{
|
||||||
|
m_membersList->clear();
|
||||||
|
for (const auto &human : members)
|
||||||
|
{
|
||||||
|
auto *item = new QListWidgetItem(human.email, m_membersList);
|
||||||
|
item->setFlags(item->flags() | Qt::ItemIsUserCheckable);
|
||||||
|
item->setCheckState(Qt::Unchecked);
|
||||||
|
item->setData(Qt::UserRole, human.email);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,34 @@
|
|||||||
|
#ifndef CREATESTREAMDIALOG_H
|
||||||
|
#define CREATESTREAMDIALOG_H
|
||||||
|
|
||||||
|
#include "models.h"
|
||||||
|
#include <QDialog>
|
||||||
|
|
||||||
|
QT_BEGIN_NAMESPACE
|
||||||
|
class QLineEdit;
|
||||||
|
class QComboBox;
|
||||||
|
class QLabel;
|
||||||
|
class QListWidget;
|
||||||
|
QT_END_NAMESPACE
|
||||||
|
|
||||||
|
class CreateStreamDialog : public QDialog
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
|
||||||
|
public:
|
||||||
|
explicit CreateStreamDialog(QWidget *parent = nullptr);
|
||||||
|
|
||||||
|
/// Call before showing to populate the member checklist.
|
||||||
|
void setNetworkMembers(const std::vector<Human> &members);
|
||||||
|
|
||||||
|
signals:
|
||||||
|
void streamRequested(const QString &name, const QString &visibility, const QStringList &memberEmails);
|
||||||
|
|
||||||
|
private:
|
||||||
|
QLineEdit *m_nameEdit;
|
||||||
|
QComboBox *m_visibilityCombo;
|
||||||
|
QLabel *m_membersLabel;
|
||||||
|
QListWidget *m_membersList;
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // CREATESTREAMDIALOG_H
|
||||||
@@ -0,0 +1,55 @@
|
|||||||
|
#include "memberpickerdialog.h"
|
||||||
|
#include <QDialogButtonBox>
|
||||||
|
#include <QLabel>
|
||||||
|
#include <QListWidget>
|
||||||
|
#include <QSet>
|
||||||
|
#include <QVBoxLayout>
|
||||||
|
|
||||||
|
MemberPickerDialog::MemberPickerDialog(const QString &title, QWidget *parent)
|
||||||
|
: QDialog(parent)
|
||||||
|
{
|
||||||
|
setWindowTitle(title);
|
||||||
|
setMinimumWidth(300);
|
||||||
|
|
||||||
|
auto *layout = new QVBoxLayout(this);
|
||||||
|
|
||||||
|
m_list = new QListWidget(this);
|
||||||
|
layout->addWidget(m_list);
|
||||||
|
|
||||||
|
auto *buttons = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel, this);
|
||||||
|
layout->addWidget(buttons);
|
||||||
|
|
||||||
|
connect(buttons, &QDialogButtonBox::accepted, this, &QDialog::accept);
|
||||||
|
connect(buttons, &QDialogButtonBox::rejected, this, &QDialog::reject);
|
||||||
|
}
|
||||||
|
|
||||||
|
void MemberPickerDialog::setMembers(const std::vector<Human> &members, const std::vector<QString> &exclude)
|
||||||
|
{
|
||||||
|
m_list->clear();
|
||||||
|
|
||||||
|
QSet<QString> excludeSet;
|
||||||
|
for (const auto &e : exclude)
|
||||||
|
excludeSet.insert(e);
|
||||||
|
|
||||||
|
for (const auto &human : members)
|
||||||
|
{
|
||||||
|
if (excludeSet.contains(human.email))
|
||||||
|
continue;
|
||||||
|
auto *item = new QListWidgetItem(human.email, m_list);
|
||||||
|
item->setFlags(item->flags() | Qt::ItemIsUserCheckable);
|
||||||
|
item->setCheckState(Qt::Unchecked);
|
||||||
|
item->setData(Qt::UserRole, human.email);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
QStringList MemberPickerDialog::selectedEmails() const
|
||||||
|
{
|
||||||
|
QStringList result;
|
||||||
|
for (int i = 0; i < m_list->count(); ++i)
|
||||||
|
{
|
||||||
|
auto *item = m_list->item(i);
|
||||||
|
if (item->checkState() == Qt::Checked)
|
||||||
|
result.append(item->data(Qt::UserRole).toString());
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
#ifndef MEMBERPICKERDIALOG_H
|
||||||
|
#define MEMBERPICKERDIALOG_H
|
||||||
|
|
||||||
|
#include "models.h"
|
||||||
|
#include <QDialog>
|
||||||
|
|
||||||
|
QT_BEGIN_NAMESPACE
|
||||||
|
class QListWidget;
|
||||||
|
QT_END_NAMESPACE
|
||||||
|
|
||||||
|
/// A simple dialog that shows a checklist of humans and returns the selected emails.
|
||||||
|
class MemberPickerDialog : public QDialog
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
|
||||||
|
public:
|
||||||
|
explicit MemberPickerDialog(const QString &title, QWidget *parent = nullptr);
|
||||||
|
|
||||||
|
/// Populate the checklist. Members whose emails appear in `exclude` are hidden.
|
||||||
|
void setMembers(const std::vector<Human> &members, const std::vector<QString> &exclude = {});
|
||||||
|
|
||||||
|
/// Returns the emails of all checked items.
|
||||||
|
QStringList selectedEmails() const;
|
||||||
|
|
||||||
|
private:
|
||||||
|
QListWidget *m_list;
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // MEMBERPICKERDIALOG_H
|
||||||
@@ -12,6 +12,26 @@ NetworkStreamModel::NetworkStreamModel(Store *store, QObject *parent) : QAbstrac
|
|||||||
beginResetModel();
|
beginResetModel();
|
||||||
endResetModel();
|
endResetModel();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Granular update: repaint a single stream row when its properties change (e.g. unseen count)
|
||||||
|
connect(m_store, &Store::streamUpdated, this, [this](const QString &streamId) {
|
||||||
|
const auto &networks = m_store->networks();
|
||||||
|
for (int ni = 0; ni < networks.size(); ++ni)
|
||||||
|
{
|
||||||
|
const auto &streams = networks.at(ni).streams;
|
||||||
|
for (int si = 0; si < static_cast<int>(streams.size()); ++si)
|
||||||
|
{
|
||||||
|
if (streams[si].id == streamId)
|
||||||
|
{
|
||||||
|
// Build indices for both columns of this stream row
|
||||||
|
QModelIndex topLeft = index(si, 0, index(ni, 0));
|
||||||
|
QModelIndex bottomRight = index(si, 1, index(ni, 0));
|
||||||
|
emit dataChanged(topLeft, bottomRight, {Qt::DisplayRole, Qt::ForegroundRole});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
QModelIndex NetworkStreamModel::index(int row, int column, const QModelIndex &parent) const
|
QModelIndex NetworkStreamModel::index(int row, int column, const QModelIndex &parent) const
|
||||||
|
|||||||
@@ -15,6 +15,22 @@ ParticleListModel::ParticleListModel(Store *store, QObject *parent)
|
|||||||
endResetModel();
|
endResetModel();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Granular update: repaint a single row when a particle property changes
|
||||||
|
connect(m_store, &Store::particleUpdated, this, [this](const QString &streamId, const QString &particleId) {
|
||||||
|
if (streamId != m_currentStreamId)
|
||||||
|
return;
|
||||||
|
const QList<Particle> &particles = m_store->particlesForStream(m_currentStreamId);
|
||||||
|
for (int i = 0; i < particles.size(); ++i)
|
||||||
|
{
|
||||||
|
if (particles.at(i).id == particleId)
|
||||||
|
{
|
||||||
|
QModelIndex idx = index(i, 0);
|
||||||
|
emit dataChanged(idx, idx, {Qt::DisplayRole, Qt::ForegroundRole});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
int ParticleListModel::rowCount(const QModelIndex &parent) const
|
int ParticleListModel::rowCount(const QModelIndex &parent) const
|
||||||
|
|||||||
+70
-150
@@ -10,35 +10,14 @@
|
|||||||
// Operation
|
// Operation
|
||||||
// ────────────────────────────────────────────────────────────────
|
// ────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
Operation::Operation(QObject *parent)
|
Operation::Operation(QNetworkReply *reply, QObject *parent)
|
||||||
: QObject(parent)
|
: QObject(parent)
|
||||||
{
|
{
|
||||||
}
|
connect(this, &Operation::finished, this, &QObject::deleteLater);
|
||||||
|
|
||||||
// ────────────────────────────────────────────────────────────────
|
connect(reply, &QNetworkReply::finished, this, [this, reply]() {
|
||||||
// Store — construction
|
reply->deleteLater();
|
||||||
// ────────────────────────────────────────────────────────────────
|
|
||||||
|
|
||||||
Store::Store(QObject *parent)
|
|
||||||
: QObject(parent)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
// ────────────────────────────────────────────────────────────────
|
|
||||||
// Operation helpers
|
|
||||||
// ────────────────────────────────────────────────────────────────
|
|
||||||
|
|
||||||
Operation *Store::makeOperation()
|
|
||||||
{
|
|
||||||
auto *op = new Operation(this);
|
|
||||||
connect(op, &Operation::finished, op, &QObject::deleteLater);
|
|
||||||
return op;
|
|
||||||
}
|
|
||||||
|
|
||||||
void Store::wireReply(QNetworkReply *reply, Operation *op,
|
|
||||||
std::function<void(const QJsonDocument &)> successHandler)
|
|
||||||
{
|
|
||||||
connect(reply, &QNetworkReply::finished, this, [reply, op, successHandler = std::move(successHandler)]() {
|
|
||||||
if (reply->error() != QNetworkReply::NoError)
|
if (reply->error() != QNetworkReply::NoError)
|
||||||
{
|
{
|
||||||
QByteArray body = reply->readAll();
|
QByteArray body = reply->readAll();
|
||||||
@@ -48,23 +27,26 @@ void Store::wireReply(QNetworkReply *reply, Operation *op,
|
|||||||
{
|
{
|
||||||
message = reply->errorString();
|
message = reply->errorString();
|
||||||
}
|
}
|
||||||
emit op->failed(message);
|
qWarning() << message;
|
||||||
emit op->finished();
|
emit failed(message);
|
||||||
|
emit finished();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
QByteArray body = reply->readAll();
|
QByteArray body = reply->readAll();
|
||||||
QJsonDocument doc = body.isEmpty() ? QJsonDocument() : QJsonDocument::fromJson(body);
|
QJsonDocument doc = body.isEmpty() ? QJsonDocument() : QJsonDocument::fromJson(body);
|
||||||
successHandler(doc);
|
emit success(doc);
|
||||||
|
emit finished();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
void Store::wireReply(QNetworkReply *reply, Operation *op)
|
// ────────────────────────────────────────────────────────────────
|
||||||
|
// Store — construction
|
||||||
|
// ────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
Store::Store(QObject *parent)
|
||||||
|
: QObject(parent)
|
||||||
{
|
{
|
||||||
wireReply(reply, op, [op](const QJsonDocument &) {
|
|
||||||
emit op->success();
|
|
||||||
emit op->finished();
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ────────────────────────────────────────────────────────────────
|
// ────────────────────────────────────────────────────────────────
|
||||||
@@ -381,23 +363,18 @@ const Particle *Store::particleById(const QString &id) const
|
|||||||
|
|
||||||
Operation *Store::loadStartupData()
|
Operation *Store::loadStartupData()
|
||||||
{
|
{
|
||||||
Operation *op = makeOperation();
|
|
||||||
QNetworkReply *reply = NetworkManager::instance().get("/startup");
|
QNetworkReply *reply = NetworkManager::instance().get("/startup");
|
||||||
|
auto *op = new Operation(reply, this);
|
||||||
wireReply(reply, op, [this, op](const QJsonDocument &doc) {
|
connect(op, &Operation::success, this, [this](const QJsonDocument &doc) {
|
||||||
if (!doc.isObject())
|
if (!doc.isObject())
|
||||||
{
|
{
|
||||||
emit op->failed("Invalid startup data");
|
qWarning() << "Invalid startup data";
|
||||||
emit op->finished();
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
populateFromStartupData(doc.object());
|
populateFromStartupData(doc.object());
|
||||||
emit startupDataLoaded();
|
emit startupDataLoaded();
|
||||||
emit networksChanged();
|
emit networksChanged();
|
||||||
emit op->success();
|
|
||||||
emit op->finished();
|
|
||||||
});
|
});
|
||||||
|
|
||||||
return op;
|
return op;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -407,51 +384,41 @@ Operation *Store::loadStartupData()
|
|||||||
|
|
||||||
Operation *Store::createNetwork(const QString &name)
|
Operation *Store::createNetwork(const QString &name)
|
||||||
{
|
{
|
||||||
Operation *op = makeOperation();
|
|
||||||
|
|
||||||
QJsonObject body;
|
QJsonObject body;
|
||||||
body["name"] = name;
|
body["name"] = name;
|
||||||
|
|
||||||
QNetworkReply *reply = NetworkManager::instance().post("/networks", QJsonDocument(body));
|
QNetworkReply *reply = NetworkManager::instance().post("/networks", QJsonDocument(body));
|
||||||
|
auto *op = new Operation(reply, this);
|
||||||
wireReply(reply, op, [this, op](const QJsonDocument &doc) {
|
connect(op, &Operation::success, this, [this](const QJsonDocument &doc) {
|
||||||
if (doc.isObject())
|
if (doc.isObject())
|
||||||
{
|
{
|
||||||
upsertNetwork(parseNetwork(doc.object()));
|
upsertNetwork(parseNetwork(doc.object()));
|
||||||
emit networksChanged();
|
emit networksChanged();
|
||||||
}
|
}
|
||||||
emit op->success();
|
|
||||||
emit op->finished();
|
|
||||||
});
|
});
|
||||||
|
|
||||||
return op;
|
return op;
|
||||||
}
|
}
|
||||||
|
|
||||||
Operation *Store::fetchNetworks()
|
Operation *Store::fetchNetworks()
|
||||||
{
|
{
|
||||||
Operation *op = makeOperation();
|
|
||||||
QNetworkReply *reply = NetworkManager::instance().get("/networks");
|
QNetworkReply *reply = NetworkManager::instance().get("/networks");
|
||||||
|
auto *op = new Operation(reply, this);
|
||||||
wireReply(reply, op, [this, op](const QJsonDocument &doc) {
|
connect(op, &Operation::success, this, [this](const QJsonDocument &doc) {
|
||||||
const QJsonArray arr = doc.array();
|
const QJsonArray arr = doc.array();
|
||||||
for (const QJsonValue &v : arr)
|
for (const QJsonValue &v : arr)
|
||||||
{
|
{
|
||||||
upsertNetwork(parseNetwork(v.toObject()));
|
upsertNetwork(parseNetwork(v.toObject()));
|
||||||
}
|
}
|
||||||
emit networksChanged();
|
emit networksChanged();
|
||||||
emit op->success();
|
|
||||||
emit op->finished();
|
|
||||||
});
|
});
|
||||||
|
|
||||||
return op;
|
return op;
|
||||||
}
|
}
|
||||||
|
|
||||||
Operation *Store::fetchNetwork(const QString &networkId)
|
Operation *Store::fetchNetwork(const QString &networkId)
|
||||||
{
|
{
|
||||||
Operation *op = makeOperation();
|
|
||||||
QNetworkReply *reply = NetworkManager::instance().get(QString("/networks/%1").arg(networkId));
|
QNetworkReply *reply = NetworkManager::instance().get(QString("/networks/%1").arg(networkId));
|
||||||
|
auto *op = new Operation(reply, this);
|
||||||
wireReply(reply, op, [this, op, networkId](const QJsonDocument &doc) {
|
connect(op, &Operation::success, this, [this, networkId](const QJsonDocument &doc) {
|
||||||
QJsonObject obj = doc.object();
|
QJsonObject obj = doc.object();
|
||||||
Network network = parseNetwork(obj);
|
Network network = parseNetwork(obj);
|
||||||
|
|
||||||
@@ -464,17 +431,12 @@ Operation *Store::fetchNetwork(const QString &networkId)
|
|||||||
}
|
}
|
||||||
upsertNetwork(network);
|
upsertNetwork(network);
|
||||||
emit networksChanged();
|
emit networksChanged();
|
||||||
emit op->success();
|
|
||||||
emit op->finished();
|
|
||||||
});
|
});
|
||||||
|
|
||||||
return op;
|
return op;
|
||||||
}
|
}
|
||||||
|
|
||||||
Operation *Store::addNetworkMembers(const QString &networkId, const QStringList &emails)
|
Operation *Store::addNetworkMembers(const QString &networkId, const QStringList &emails)
|
||||||
{
|
{
|
||||||
Operation *op = makeOperation();
|
|
||||||
|
|
||||||
QJsonObject body;
|
QJsonObject body;
|
||||||
QJsonArray arr;
|
QJsonArray arr;
|
||||||
for (const QString &e : emails)
|
for (const QString &e : emails)
|
||||||
@@ -485,30 +447,24 @@ Operation *Store::addNetworkMembers(const QString &networkId, const QStringList
|
|||||||
|
|
||||||
QNetworkReply *reply = NetworkManager::instance().post(
|
QNetworkReply *reply = NetworkManager::instance().post(
|
||||||
QString("/networks/%1/members").arg(networkId), QJsonDocument(body));
|
QString("/networks/%1/members").arg(networkId), QJsonDocument(body));
|
||||||
wireReply(reply, op);
|
return new Operation(reply, this);
|
||||||
return op;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Operation *Store::removeNetworkMember(const QString &networkId, const QString &email)
|
Operation *Store::removeNetworkMember(const QString &networkId, const QString &email)
|
||||||
{
|
{
|
||||||
Operation *op = makeOperation();
|
|
||||||
QNetworkReply *reply = NetworkManager::instance().del(
|
QNetworkReply *reply = NetworkManager::instance().del(
|
||||||
QString("/networks/%1/members/%2").arg(networkId, email));
|
QString("/networks/%1/members/%2").arg(networkId, email));
|
||||||
wireReply(reply, op);
|
return new Operation(reply, this);
|
||||||
return op;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Operation *Store::setStreamCapacity(const QString &networkId, int capacity)
|
Operation *Store::setStreamCapacity(const QString &networkId, int capacity)
|
||||||
{
|
{
|
||||||
Operation *op = makeOperation();
|
|
||||||
|
|
||||||
QJsonObject body;
|
QJsonObject body;
|
||||||
body["capacity"] = capacity;
|
body["capacity"] = capacity;
|
||||||
|
|
||||||
QNetworkReply *reply = NetworkManager::instance().put(
|
QNetworkReply *reply = NetworkManager::instance().put(
|
||||||
QString("/networks/%1/capacity").arg(networkId), QJsonDocument(body));
|
QString("/networks/%1/capacity").arg(networkId), QJsonDocument(body));
|
||||||
wireReply(reply, op);
|
return new Operation(reply, this);
|
||||||
return op;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ────────────────────────────────────────────────────────────────
|
// ────────────────────────────────────────────────────────────────
|
||||||
@@ -519,8 +475,6 @@ Operation *Store::createStream(const QString &networkId, const QString &name,
|
|||||||
const QString &description, const QString &visibility,
|
const QString &description, const QString &visibility,
|
||||||
const QStringList &members)
|
const QStringList &members)
|
||||||
{
|
{
|
||||||
Operation *op = makeOperation();
|
|
||||||
|
|
||||||
QJsonObject body;
|
QJsonObject body;
|
||||||
body["name"] = name;
|
body["name"] = name;
|
||||||
body["description"] = description;
|
body["description"] = description;
|
||||||
@@ -534,27 +488,23 @@ Operation *Store::createStream(const QString &networkId, const QString &name,
|
|||||||
|
|
||||||
QNetworkReply *reply = NetworkManager::instance().post(
|
QNetworkReply *reply = NetworkManager::instance().post(
|
||||||
QString("/networks/%1/streams").arg(networkId), QJsonDocument(body));
|
QString("/networks/%1/streams").arg(networkId), QJsonDocument(body));
|
||||||
|
auto *op = new Operation(reply, this);
|
||||||
wireReply(reply, op, [this, op, networkId](const QJsonDocument &doc) {
|
connect(op, &Operation::success, this, [this, networkId](const QJsonDocument &doc) {
|
||||||
if (doc.isObject())
|
if (doc.isObject())
|
||||||
{
|
{
|
||||||
Stream stream = parseStream(doc.object());
|
Stream stream = parseStream(doc.object());
|
||||||
upsertStream(networkId, stream);
|
upsertStream(networkId, stream);
|
||||||
emit streamsChanged(networkId);
|
emit streamsChanged(networkId);
|
||||||
}
|
}
|
||||||
emit op->success();
|
|
||||||
emit op->finished();
|
|
||||||
});
|
});
|
||||||
|
|
||||||
return op;
|
return op;
|
||||||
}
|
}
|
||||||
|
|
||||||
Operation *Store::fetchStream(const QString &streamId)
|
Operation *Store::fetchStream(const QString &streamId)
|
||||||
{
|
{
|
||||||
Operation *op = makeOperation();
|
|
||||||
QNetworkReply *reply = NetworkManager::instance().get(QString("/streams/%1").arg(streamId));
|
QNetworkReply *reply = NetworkManager::instance().get(QString("/streams/%1").arg(streamId));
|
||||||
|
auto *op = new Operation(reply, this);
|
||||||
wireReply(reply, op, [this, op, streamId](const QJsonDocument &doc) {
|
connect(op, &Operation::success, this, [this, streamId](const QJsonDocument &doc) {
|
||||||
QJsonObject obj = doc.object();
|
QJsonObject obj = doc.object();
|
||||||
Stream stream = parseStream(obj);
|
Stream stream = parseStream(obj);
|
||||||
|
|
||||||
@@ -574,18 +524,12 @@ Operation *Store::fetchStream(const QString &streamId)
|
|||||||
}
|
}
|
||||||
m_particles[streamId] = particleList;
|
m_particles[streamId] = particleList;
|
||||||
emit particlesChanged(streamId);
|
emit particlesChanged(streamId);
|
||||||
|
|
||||||
emit op->success();
|
|
||||||
emit op->finished();
|
|
||||||
});
|
});
|
||||||
|
|
||||||
return op;
|
return op;
|
||||||
}
|
}
|
||||||
|
|
||||||
Operation *Store::updateStream(const QString &streamId, const QString &name, const QString &description)
|
Operation *Store::updateStream(const QString &streamId, const QString &name, const QString &description)
|
||||||
{
|
{
|
||||||
Operation *op = makeOperation();
|
|
||||||
|
|
||||||
QJsonObject body;
|
QJsonObject body;
|
||||||
if (!name.isEmpty())
|
if (!name.isEmpty())
|
||||||
{
|
{
|
||||||
@@ -598,8 +542,8 @@ Operation *Store::updateStream(const QString &streamId, const QString &name, con
|
|||||||
|
|
||||||
QNetworkReply *reply = NetworkManager::instance().patch(
|
QNetworkReply *reply = NetworkManager::instance().patch(
|
||||||
QString("/streams/%1").arg(streamId), QJsonDocument(body));
|
QString("/streams/%1").arg(streamId), QJsonDocument(body));
|
||||||
|
auto *op = new Operation(reply, this);
|
||||||
wireReply(reply, op, [this, op, streamId](const QJsonDocument &doc) {
|
connect(op, &Operation::success, this, [this, streamId](const QJsonDocument &doc) {
|
||||||
if (doc.isObject())
|
if (doc.isObject())
|
||||||
{
|
{
|
||||||
Stream stream = parseStream(doc.object());
|
Stream stream = parseStream(doc.object());
|
||||||
@@ -610,20 +554,16 @@ Operation *Store::updateStream(const QString &streamId, const QString &name, con
|
|||||||
emit streamsChanged(networkId);
|
emit streamsChanged(networkId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
emit op->success();
|
|
||||||
emit op->finished();
|
|
||||||
});
|
});
|
||||||
|
|
||||||
return op;
|
return op;
|
||||||
}
|
}
|
||||||
|
|
||||||
Operation *Store::openStream(const QString &streamId)
|
Operation *Store::openStream(const QString &streamId)
|
||||||
{
|
{
|
||||||
Operation *op = makeOperation();
|
|
||||||
QNetworkReply *reply = NetworkManager::instance().post(
|
QNetworkReply *reply = NetworkManager::instance().post(
|
||||||
QString("/streams/%1/open").arg(streamId), QJsonDocument());
|
QString("/streams/%1/open").arg(streamId), QJsonDocument());
|
||||||
|
auto *op = new Operation(reply, this);
|
||||||
wireReply(reply, op, [this, op, streamId](const QJsonDocument &) {
|
connect(op, &Operation::success, this, [this, streamId](const QJsonDocument &) {
|
||||||
QString networkId = m_streamToNetwork.value(streamId);
|
QString networkId = m_streamToNetwork.value(streamId);
|
||||||
if (m_networkIndex.contains(networkId))
|
if (m_networkIndex.contains(networkId))
|
||||||
{
|
{
|
||||||
@@ -638,20 +578,16 @@ Operation *Store::openStream(const QString &streamId)
|
|||||||
}
|
}
|
||||||
emit streamsChanged(networkId);
|
emit streamsChanged(networkId);
|
||||||
}
|
}
|
||||||
emit op->success();
|
|
||||||
emit op->finished();
|
|
||||||
});
|
});
|
||||||
|
|
||||||
return op;
|
return op;
|
||||||
}
|
}
|
||||||
|
|
||||||
Operation *Store::closeStream(const QString &streamId)
|
Operation *Store::closeStream(const QString &streamId)
|
||||||
{
|
{
|
||||||
Operation *op = makeOperation();
|
|
||||||
QNetworkReply *reply = NetworkManager::instance().post(
|
QNetworkReply *reply = NetworkManager::instance().post(
|
||||||
QString("/streams/%1/close").arg(streamId), QJsonDocument());
|
QString("/streams/%1/close").arg(streamId), QJsonDocument());
|
||||||
|
auto *op = new Operation(reply, this);
|
||||||
wireReply(reply, op, [this, op, streamId](const QJsonDocument &) {
|
connect(op, &Operation::success, this, [this, streamId](const QJsonDocument &) {
|
||||||
QString networkId = m_streamToNetwork.value(streamId);
|
QString networkId = m_streamToNetwork.value(streamId);
|
||||||
if (m_networkIndex.contains(networkId))
|
if (m_networkIndex.contains(networkId))
|
||||||
{
|
{
|
||||||
@@ -666,17 +602,12 @@ Operation *Store::closeStream(const QString &streamId)
|
|||||||
}
|
}
|
||||||
emit streamsChanged(networkId);
|
emit streamsChanged(networkId);
|
||||||
}
|
}
|
||||||
emit op->success();
|
|
||||||
emit op->finished();
|
|
||||||
});
|
});
|
||||||
|
|
||||||
return op;
|
return op;
|
||||||
}
|
}
|
||||||
|
|
||||||
Operation *Store::addStreamMembers(const QString &streamId, const QStringList &emails)
|
Operation *Store::addStreamMembers(const QString &streamId, const QStringList &emails)
|
||||||
{
|
{
|
||||||
Operation *op = makeOperation();
|
|
||||||
|
|
||||||
QJsonObject body;
|
QJsonObject body;
|
||||||
QJsonArray arr;
|
QJsonArray arr;
|
||||||
for (const QString &e : emails)
|
for (const QString &e : emails)
|
||||||
@@ -687,14 +618,11 @@ Operation *Store::addStreamMembers(const QString &streamId, const QStringList &e
|
|||||||
|
|
||||||
QNetworkReply *reply = NetworkManager::instance().post(
|
QNetworkReply *reply = NetworkManager::instance().post(
|
||||||
QString("/streams/%1/members").arg(streamId), QJsonDocument(body));
|
QString("/streams/%1/members").arg(streamId), QJsonDocument(body));
|
||||||
wireReply(reply, op);
|
return new Operation(reply, this);
|
||||||
return op;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Operation *Store::removeStreamMembers(const QString &streamId, const QStringList &emails)
|
Operation *Store::removeStreamMembers(const QString &streamId, const QStringList &emails)
|
||||||
{
|
{
|
||||||
Operation *op = makeOperation();
|
|
||||||
|
|
||||||
QJsonObject body;
|
QJsonObject body;
|
||||||
QJsonArray arr;
|
QJsonArray arr;
|
||||||
for (const QString &e : emails)
|
for (const QString &e : emails)
|
||||||
@@ -703,8 +631,7 @@ Operation *Store::removeStreamMembers(const QString &streamId, const QStringList
|
|||||||
|
|
||||||
QNetworkReply *reply = NetworkManager::instance().del(
|
QNetworkReply *reply = NetworkManager::instance().del(
|
||||||
QString("/streams/%1/members").arg(streamId), QJsonDocument(body));
|
QString("/streams/%1/members").arg(streamId), QJsonDocument(body));
|
||||||
wireReply(reply, op);
|
return new Operation(reply, this);
|
||||||
return op;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ────────────────────────────────────────────────────────────────
|
// ────────────────────────────────────────────────────────────────
|
||||||
@@ -714,8 +641,6 @@ Operation *Store::removeStreamMembers(const QString &streamId, const QStringList
|
|||||||
Operation *Store::createParticle(const QString &streamId, const QString &type,
|
Operation *Store::createParticle(const QString &streamId, const QString &type,
|
||||||
const QMap<QString, QVariant> &data)
|
const QMap<QString, QVariant> &data)
|
||||||
{
|
{
|
||||||
Operation *op = makeOperation();
|
|
||||||
|
|
||||||
QJsonObject body;
|
QJsonObject body;
|
||||||
body["type"] = type;
|
body["type"] = type;
|
||||||
QJsonObject dataObj;
|
QJsonObject dataObj;
|
||||||
@@ -725,27 +650,23 @@ Operation *Store::createParticle(const QString &streamId, const QString &type,
|
|||||||
|
|
||||||
QNetworkReply *reply = NetworkManager::instance().post(
|
QNetworkReply *reply = NetworkManager::instance().post(
|
||||||
QString("/streams/%1/particles").arg(streamId), QJsonDocument(body));
|
QString("/streams/%1/particles").arg(streamId), QJsonDocument(body));
|
||||||
|
auto *op = new Operation(reply, this);
|
||||||
wireReply(reply, op, [this, op, streamId](const QJsonDocument &doc) {
|
connect(op, &Operation::success, this, [this, streamId](const QJsonDocument &doc) {
|
||||||
if (doc.isObject())
|
if (doc.isObject())
|
||||||
{
|
{
|
||||||
Particle particle = parseParticle(doc.object());
|
Particle particle = parseParticle(doc.object());
|
||||||
upsertParticle(streamId, particle);
|
upsertParticle(streamId, particle);
|
||||||
emit particlesChanged(streamId);
|
emit particlesChanged(streamId);
|
||||||
}
|
}
|
||||||
emit op->success();
|
|
||||||
emit op->finished();
|
|
||||||
});
|
});
|
||||||
|
|
||||||
return op;
|
return op;
|
||||||
}
|
}
|
||||||
|
|
||||||
Operation *Store::fetchParticle(const QString &particleId)
|
Operation *Store::fetchParticle(const QString &particleId)
|
||||||
{
|
{
|
||||||
Operation *op = makeOperation();
|
|
||||||
QNetworkReply *reply = NetworkManager::instance().get(QString("/particles/%1").arg(particleId));
|
QNetworkReply *reply = NetworkManager::instance().get(QString("/particles/%1").arg(particleId));
|
||||||
|
auto *op = new Operation(reply, this);
|
||||||
wireReply(reply, op, [this, op, particleId](const QJsonDocument &doc) {
|
connect(op, &Operation::success, this, [this, particleId](const QJsonDocument &doc) {
|
||||||
if (doc.isObject())
|
if (doc.isObject())
|
||||||
{
|
{
|
||||||
Particle particle = parseParticle(doc.object());
|
Particle particle = parseParticle(doc.object());
|
||||||
@@ -756,17 +677,12 @@ Operation *Store::fetchParticle(const QString &particleId)
|
|||||||
emit particlesChanged(streamId);
|
emit particlesChanged(streamId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
emit op->success();
|
|
||||||
emit op->finished();
|
|
||||||
});
|
});
|
||||||
|
|
||||||
return op;
|
return op;
|
||||||
}
|
}
|
||||||
|
|
||||||
Operation *Store::updateParticle(const QString &particleId, const QMap<QString, QVariant> &data)
|
Operation *Store::updateParticle(const QString &particleId, const QMap<QString, QVariant> &data)
|
||||||
{
|
{
|
||||||
Operation *op = makeOperation();
|
|
||||||
|
|
||||||
QJsonObject body;
|
QJsonObject body;
|
||||||
QJsonObject dataObj;
|
QJsonObject dataObj;
|
||||||
for (auto it = data.constBegin(); it != data.constEnd(); ++it)
|
for (auto it = data.constBegin(); it != data.constEnd(); ++it)
|
||||||
@@ -775,8 +691,8 @@ Operation *Store::updateParticle(const QString &particleId, const QMap<QString,
|
|||||||
|
|
||||||
QNetworkReply *reply = NetworkManager::instance().patch(
|
QNetworkReply *reply = NetworkManager::instance().patch(
|
||||||
QString("/particles/%1").arg(particleId), QJsonDocument(body));
|
QString("/particles/%1").arg(particleId), QJsonDocument(body));
|
||||||
|
auto *op = new Operation(reply, this);
|
||||||
wireReply(reply, op, [this, op, particleId](const QJsonDocument &doc) {
|
connect(op, &Operation::success, this, [this, particleId](const QJsonDocument &doc) {
|
||||||
if (doc.isObject())
|
if (doc.isObject())
|
||||||
{
|
{
|
||||||
Particle particle = parseParticle(doc.object());
|
Particle particle = parseParticle(doc.object());
|
||||||
@@ -787,27 +703,20 @@ Operation *Store::updateParticle(const QString &particleId, const QMap<QString,
|
|||||||
emit particlesChanged(streamId);
|
emit particlesChanged(streamId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
emit op->success();
|
|
||||||
emit op->finished();
|
|
||||||
});
|
});
|
||||||
|
|
||||||
return op;
|
return op;
|
||||||
}
|
}
|
||||||
|
|
||||||
Operation *Store::deleteParticle(const QString &particleId)
|
Operation *Store::deleteParticle(const QString &particleId)
|
||||||
{
|
{
|
||||||
Operation *op = makeOperation();
|
|
||||||
QNetworkReply *reply = NetworkManager::instance().del(QString("/particles/%1").arg(particleId));
|
QNetworkReply *reply = NetworkManager::instance().del(QString("/particles/%1").arg(particleId));
|
||||||
|
auto *op = new Operation(reply, this);
|
||||||
wireReply(reply, op, [this, op, particleId](const QJsonDocument &) {
|
connect(op, &Operation::success, this, [this, particleId](const QJsonDocument &) {
|
||||||
QString streamId = findStreamForParticle(particleId);
|
QString streamId = findStreamForParticle(particleId);
|
||||||
removeParticleFromState(particleId);
|
removeParticleFromState(particleId);
|
||||||
if (!streamId.isEmpty())
|
if (!streamId.isEmpty())
|
||||||
emit particlesChanged(streamId);
|
emit particlesChanged(streamId);
|
||||||
emit op->success();
|
|
||||||
emit op->finished();
|
|
||||||
});
|
});
|
||||||
|
|
||||||
return op;
|
return op;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -818,20 +727,36 @@ Operation *Store::markParticleSeen(const QString &particleId)
|
|||||||
{
|
{
|
||||||
for (int i = 0; i < it.value().size(); ++i)
|
for (int i = 0; i < it.value().size(); ++i)
|
||||||
{
|
{
|
||||||
if (it.value()[i].id == particleId)
|
if (it.value()[i].id == particleId && !it.value()[i].seen)
|
||||||
{
|
{
|
||||||
it.value()[i].seen = true;
|
it.value()[i].seen = true;
|
||||||
emit particlesChanged(it.key());
|
QString streamId = it.key();
|
||||||
|
|
||||||
|
// Decrement the parent stream's unseen count
|
||||||
|
QString networkId = m_streamToNetwork.value(streamId);
|
||||||
|
if (m_networkIndex.contains(networkId))
|
||||||
|
{
|
||||||
|
auto &streams = m_networks[m_networkIndex[networkId]].streams;
|
||||||
|
for (auto &s : streams)
|
||||||
|
{
|
||||||
|
if (s.id == streamId && s.unseenCount > 0)
|
||||||
|
{
|
||||||
|
s.unseenCount--;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
emit particleUpdated(streamId, particleId);
|
||||||
|
emit streamUpdated(streamId);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Operation *op = makeOperation();
|
|
||||||
QNetworkReply *reply = NetworkManager::instance().post(
|
QNetworkReply *reply = NetworkManager::instance().post(
|
||||||
QString("/particles/%1/seen").arg(particleId), QJsonDocument());
|
QString("/particles/%1/seen").arg(particleId), QJsonDocument());
|
||||||
wireReply(reply, op);
|
return new Operation(reply, this);
|
||||||
return op;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Operation *Store::markParticlesSeen(const QStringList &particleIds)
|
Operation *Store::markParticlesSeen(const QStringList &particleIds)
|
||||||
@@ -856,8 +781,6 @@ Operation *Store::markParticlesSeen(const QStringList &particleIds)
|
|||||||
for (const QString &streamId : affectedStreams)
|
for (const QString &streamId : affectedStreams)
|
||||||
emit particlesChanged(streamId);
|
emit particlesChanged(streamId);
|
||||||
|
|
||||||
Operation *op = makeOperation();
|
|
||||||
|
|
||||||
QJsonObject body;
|
QJsonObject body;
|
||||||
QJsonArray ids;
|
QJsonArray ids;
|
||||||
for (const QString &id : particleIds)
|
for (const QString &id : particleIds)
|
||||||
@@ -865,15 +788,12 @@ Operation *Store::markParticlesSeen(const QStringList &particleIds)
|
|||||||
body["particle_ids"] = ids;
|
body["particle_ids"] = ids;
|
||||||
|
|
||||||
QNetworkReply *reply = NetworkManager::instance().post("/particles/seen", QJsonDocument(body));
|
QNetworkReply *reply = NetworkManager::instance().post("/particles/seen", QJsonDocument(body));
|
||||||
wireReply(reply, op);
|
return new Operation(reply, this);
|
||||||
return op;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Operation *Store::ackParticle(const QString &particleId)
|
Operation *Store::ackParticle(const QString &particleId)
|
||||||
{
|
{
|
||||||
Operation *op = makeOperation();
|
|
||||||
QNetworkReply *reply = NetworkManager::instance().post(
|
QNetworkReply *reply = NetworkManager::instance().post(
|
||||||
QString("/particles/%1/ack").arg(particleId), QJsonDocument());
|
QString("/particles/%1/ack").arg(particleId), QJsonDocument());
|
||||||
wireReply(reply, op);
|
return new Operation(reply, this);
|
||||||
return op;
|
|
||||||
}
|
}
|
||||||
|
|||||||
+8
-17
@@ -6,21 +6,22 @@
|
|||||||
#include <QMap>
|
#include <QMap>
|
||||||
|
|
||||||
QT_BEGIN_NAMESPACE
|
QT_BEGIN_NAMESPACE
|
||||||
|
class QJsonDocument;
|
||||||
class QNetworkReply;
|
class QNetworkReply;
|
||||||
QT_END_NAMESPACE
|
QT_END_NAMESPACE
|
||||||
|
|
||||||
/// Represents an in-flight API operation.
|
/// Represents an in-flight API operation.
|
||||||
/// Callers connect to success/failed for one-off feedback.
|
/// Wraps a QNetworkReply: owns cleanup, parses JSON, and emits signals.
|
||||||
/// Automatically deletes itself after finished() fires.
|
/// Automatically deletes itself after finished() fires.
|
||||||
class Operation : public QObject
|
class Operation : public QObject
|
||||||
{
|
{
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
|
|
||||||
public:
|
public:
|
||||||
explicit Operation(QObject *parent = nullptr);
|
explicit Operation(QNetworkReply *reply, QObject *parent = nullptr);
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
void success();
|
void success(const QJsonDocument &response);
|
||||||
void failed(const QString &message);
|
void failed(const QString &message);
|
||||||
void finished();
|
void finished();
|
||||||
};
|
};
|
||||||
@@ -91,6 +92,10 @@ signals:
|
|||||||
void streamsChanged(const QString &networkId);
|
void streamsChanged(const QString &networkId);
|
||||||
void particlesChanged(const QString &streamId);
|
void particlesChanged(const QString &streamId);
|
||||||
|
|
||||||
|
// Granular signals for in-place property updates (no structural change)
|
||||||
|
void particleUpdated(const QString &streamId, const QString &particleId);
|
||||||
|
void streamUpdated(const QString &streamId);
|
||||||
|
|
||||||
void startupDataLoaded();
|
void startupDataLoaded();
|
||||||
void errorOccurred(const QString &message);
|
void errorOccurred(const QString &message);
|
||||||
|
|
||||||
@@ -120,20 +125,6 @@ private:
|
|||||||
/// Returns the streamId that contains the given particle, or empty QString.
|
/// Returns the streamId that contains the given particle, or empty QString.
|
||||||
QString findStreamForParticle(const QString &particleId) const;
|
QString findStreamForParticle(const QString &particleId) const;
|
||||||
|
|
||||||
// ── Operation helpers ──
|
|
||||||
|
|
||||||
/// Creates an Operation parented to this Store, wired to self-destruct after finished().
|
|
||||||
Operation *makeOperation();
|
|
||||||
|
|
||||||
/// Connects reply->finished to a handler. On error, emits op->failed + op->finished.
|
|
||||||
/// On success, calls successHandler with the parsed QJsonDocument.
|
|
||||||
/// successHandler is responsible for emitting op->success() and op->finished().
|
|
||||||
void wireReply(QNetworkReply *reply, Operation *op,
|
|
||||||
std::function<void(const QJsonDocument &)> successHandler);
|
|
||||||
|
|
||||||
/// Simplified wireReply for operations that don't need to parse a response body
|
|
||||||
/// (e.g. 204 No Content). Emits op->success() and op->finished() automatically.
|
|
||||||
void wireReply(QNetworkReply *reply, Operation *op);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // STORE_H
|
#endif // STORE_H
|
||||||
|
|||||||
Reference in New Issue
Block a user