#include "tools/cabana/streams/routes.h" #include #include #include #include #include #include #include #include class OneShotHttpRequest : public HttpRequest { public: OneShotHttpRequest(QObject *parent) : HttpRequest(parent, false) {} void send(const QString &url) { if (reply) { reply->disconnect(); reply->abort(); reply->deleteLater(); reply = nullptr; } sendRequest(url); } }; // The RouteListWidget class extends QListWidget to display a custom message when empty class RouteListWidget : public QListWidget { public: RouteListWidget(QWidget *parent = nullptr) : QListWidget(parent) {} void setEmptyText(const QString &text) { empty_text_ = text; viewport()->update(); } void paintEvent(QPaintEvent *event) override { QListWidget::paintEvent(event); if (count() == 0) { QPainter painter(viewport()); painter.drawText(viewport()->rect(), Qt::AlignCenter, empty_text_); } } QString empty_text_ = tr("No items"); }; RoutesDialog::RoutesDialog(QWidget *parent) : QDialog(parent), route_requester_(new OneShotHttpRequest(this)) { setWindowTitle(tr("Remote routes")); QFormLayout *layout = new QFormLayout(this); layout->addRow(tr("Device"), device_list_ = new QComboBox(this)); layout->addRow(period_selector_ = new QComboBox(this)); layout->addRow(route_list_ = new RouteListWidget(this)); auto button_box = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel); layout->addRow(button_box); device_list_->addItem(tr("Loading...")); // Populate period selector with predefined durations period_selector_->addItem(tr("Last week"), 7); period_selector_->addItem(tr("Last 2 weeks"), 14); period_selector_->addItem(tr("Last month"), 30); period_selector_->addItem(tr("Last 6 months"), 180); period_selector_->addItem(tr("Preserved"), -1); // Connect signals and slots QObject::connect(route_requester_, &HttpRequest::requestDone, this, &RoutesDialog::parseRouteList); connect(device_list_, QOverload::of(&QComboBox::currentIndexChanged), this, &RoutesDialog::fetchRoutes); connect(period_selector_, QOverload::of(&QComboBox::currentIndexChanged), this, &RoutesDialog::fetchRoutes); connect(route_list_, &QListWidget::itemDoubleClicked, this, &QDialog::accept); QObject::connect(button_box, &QDialogButtonBox::accepted, this, &QDialog::accept); QObject::connect(button_box, &QDialogButtonBox::rejected, this, &QDialog::reject); // Send request to fetch devices HttpRequest *http = new HttpRequest(this, false); QObject::connect(http, &HttpRequest::requestDone, this, &RoutesDialog::parseDeviceList); http->sendRequest(CommaApi::BASE_URL + "/v1/me/devices/"); } void RoutesDialog::parseDeviceList(const QString &json, bool success, QNetworkReply::NetworkError err) { if (success) { device_list_->clear(); auto devices = QJsonDocument::fromJson(json.toUtf8()).array(); for (const QJsonValue &device : devices) { QString dongle_id = device["dongle_id"].toString(); device_list_->addItem(dongle_id, dongle_id); } } else { bool unauthorized = (err == QNetworkReply::ContentAccessDenied || err == QNetworkReply::AuthenticationRequiredError); QMessageBox::warning(this, tr("Error"), unauthorized ? tr("Unauthorized, Authenticate with tools/lib/auth.py") : tr("Network error")); reject(); } sender()->deleteLater(); } void RoutesDialog::fetchRoutes() { if (device_list_->currentIndex() == -1 || device_list_->currentData().isNull()) return; route_list_->clear(); route_list_->setEmptyText(tr("Loading...")); // Construct URL with selected device and date range QString url = QString("%1/v1/devices/%2").arg(CommaApi::BASE_URL, device_list_->currentText()); int period = period_selector_->currentData().toInt(); if (period == -1) { url += "/routes/preserved"; } else { QDateTime now = QDateTime::currentDateTime(); url += QString("/routes_segments?start=%1&end=%2") .arg(now.addDays(-period).toMSecsSinceEpoch()) .arg(now.toMSecsSinceEpoch()); } route_requester_->send(url); } void RoutesDialog::parseRouteList(const QString &json, bool success, QNetworkReply::NetworkError err) { if (success) { for (const QJsonValue &route : QJsonDocument::fromJson(json.toUtf8()).array()) { QDateTime from, to; if (period_selector_->currentData().toInt() == -1) { from = QDateTime::fromString(route["start_time"].toString(), Qt::ISODateWithMs); to = QDateTime::fromString(route["end_time"].toString(), Qt::ISODateWithMs); } else { from = QDateTime::fromMSecsSinceEpoch(route["start_time_utc_millis"].toDouble()); to = QDateTime::fromMSecsSinceEpoch(route["end_time_utc_millis"].toDouble()); } auto item = new QListWidgetItem(QString("%1 %2min").arg(from.toString()).arg(from.secsTo(to) / 60)); item->setData(Qt::UserRole, route["fullname"].toString()); route_list_->addItem(item); } if (route_list_->count() > 0) route_list_->setCurrentRow(0); } else { QMessageBox::warning(this, tr("Error"), tr("Failed to fetch routes. Check your network connection.")); reject(); } route_list_->setEmptyText(tr("No items")); } QString RoutesDialog::route() { auto current_item = route_list_->currentItem(); return current_item ? current_item->data(Qt::UserRole).toString() : ""; }