#include "dialogs/FileInfoDialog.hpp" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "rpc/RpcClient.hpp" #include "util/Theme.hpp" namespace velox::gui { namespace { QString filenameFromUrl(const QString &url) { const QString path = QUrl(url).path(); const QString name = path.section('/', -1); return name.isEmpty() ? QStringLiteral("download") : name; } struct BufferChoice { const char *label; qint64 bytes; }; constexpr BufferChoice kBufferChoices[] = { {"256 KiB", 256 * 1024}, {"512 KiB", 512 * 1024}, {"1 MiB", 1024 * 1024}, {"2 MiB", 2 * 1024 * 1024}, {"4 MiB", 4 * 1024 * 1024}, {"8 MiB", 8 * 1024 * 1024}, {"16 MiB", 16 * 1024 * 1024}, }; } // namespace FileInfoDialog::FileInfoDialog(rpc::RpcClient *client, QString url, QJsonArray categories, QJsonArray queues, QWidget *parent) : QDialog(parent), client_(client), url_(std::move(url)), filenameEdit_(new QLineEdit(this)), saveDirEdit_(new QLineEdit(this)), categoryCombo_(new QComboBox(this)), sizeLabel_(new QLabel(tr("Probing…"), this)), resumeLabel_(new QLabel(tr("Probing…"), this)), descriptionEdit_(new QLineEdit(this)), connectionsSpin_(new QSpinBox(this)), bufferCombo_(new QComboBox(this)), addToQueueButton_(new QToolButton(this)), errorLabel_(new QLabel(this)) { setWindowTitle(tr("Download File Info")); filenameEdit_->setText(filenameFromUrl(url_)); connect(filenameEdit_, &QLineEdit::textEdited, this, [this] { filenameEditedByUser_ = true; }); connect(saveDirEdit_, &QLineEdit::textEdited, this, [this] { saveDirEditedByUser_ = true; }); populateCategories(categories); populateQueueMenu(queues); connectionsSpin_->setRange(1, 32); connectionsSpin_->setValue(8); for (const auto &choice : kBufferChoices) { bufferCombo_->addItem(QString::fromLatin1(choice.label), QVariant::fromValue(choice.bytes)); } bufferCombo_->setCurrentIndex(4); // 4 MiB, matching the spec's mockup default auto *browse = new QPushButton(tr("Browse…"), this); connect(browse, &QPushButton::clicked, this, &FileInfoDialog::browseSaveDir); auto *saveDirRow = new QWidget(this); auto *saveDirLayout = new QHBoxLayout(saveDirRow); saveDirLayout->setContentsMargins(0, 0, 0, 0); saveDirLayout->addWidget(saveDirEdit_, 1); saveDirLayout->addWidget(browse); auto *sizeResumeRow = new QWidget(this); auto *sizeResumeLayout = new QHBoxLayout(sizeResumeRow); sizeResumeLayout->setContentsMargins(0, 0, 0, 0); sizeResumeLayout->addWidget(sizeLabel_); sizeResumeLayout->addSpacing(16); sizeResumeLayout->addWidget(resumeLabel_); sizeResumeLayout->addStretch(); auto *remember = new QCheckBox(tr("Remember for this file type"), this); remember->setEnabled(false); // not wired to settings yet — Options dialog isn't built auto *form = new QFormLayout; form->addRow(tr("File Name:"), filenameEdit_); form->addRow(tr("Save As:"), saveDirRow); form->addRow(tr("Category:"), categoryCombo_); form->addRow(QString(), sizeResumeRow); form->addRow(tr("Description:"), descriptionEdit_); form->addRow(tr("Connections:"), connectionsSpin_); form->addRow(tr("Buffer:"), bufferCombo_); form->addRow(QString(), remember); errorLabel_->setStyleSheet(theme::errorLabelStyle()); errorLabel_->setWordWrap(true); errorLabel_->hide(); addToQueueButton_->setText(tr("Add to Queue")); addToQueueButton_->setPopupMode(QToolButton::MenuButtonPopup); auto *downloadNow = new QPushButton(tr("Download Now"), this); downloadNow->setDefault(true); auto *downloadLater = new QPushButton(tr("Download Later"), this); auto *cancel = new QPushButton(tr("Cancel"), this); connect(downloadNow, &QPushButton::clicked, this, [this] { submit(QStringLiteral("now"), {}); }); connect(downloadLater, &QPushButton::clicked, this, [this] { submit(QStringLiteral("later"), {}); }); connect(cancel, &QPushButton::clicked, this, &QDialog::reject); auto *buttonRow = new QHBoxLayout; buttonRow->addStretch(); buttonRow->addWidget(downloadNow); buttonRow->addWidget(downloadLater); buttonRow->addWidget(addToQueueButton_); buttonRow->addWidget(cancel); auto *outer = new QVBoxLayout(this); outer->addLayout(form); outer->addWidget(errorLabel_); outer->addLayout(buttonRow); resize(560, sizeHint().height()); QPointer self(this); client_->call(QStringLiteral("download.probe"), QJsonObject{{"url", url_}}, [self](const rpc::RpcReply &reply) { if (!self) { return; // dialog closed before the probe answered } self->onProbeFinished(reply.ok(), reply.result.toObject()); }); } void FileInfoDialog::populateCategories(const QJsonArray &categories) { categoryCombo_->addItem(tr("(Automatic)"), QString()); for (const QJsonValue &v : categories) { const QJsonObject c = v.toObject(); categoryCombo_->addItem(c.value("name").toString(), c.value("categoryId").toString()); } } void FileInfoDialog::populateQueueMenu(const QJsonArray &queues) { if (queues.isEmpty()) { addToQueueButton_->setEnabled(false); return; } auto *menu = new QMenu(addToQueueButton_); for (const QJsonValue &v : queues) { const QJsonObject q = v.toObject(); const QString queueId = q.value("queueId").toString(); menu->addAction(q.value("name").toString(), this, [this, queueId] { submit(QStringLiteral("queue"), queueId); }); } addToQueueButton_->setMenu(menu); connect(addToQueueButton_, &QToolButton::clicked, addToQueueButton_, &QToolButton::showMenu); } QString FileInfoDialog::selectedCategoryId() const { return categoryCombo_->currentData().toString(); } void FileInfoDialog::onProbeFinished(bool ok, const QJsonObject &result) { if (!ok) { sizeLabel_->setText(tr("Size: unknown")); resumeLabel_->setText(tr("Resume capability: unknown")); errorLabel_->setText( tr("Could not probe this URL; you can still try to download it as entered.")); errorLabel_->show(); return; } if (!filenameEditedByUser_) { const QString probed = result.value("filename").toString(); if (!probed.isEmpty()) { filenameEdit_->setText(probed); } } if (!saveDirEditedByUser_) { const QString suggested = result.value("suggestedSaveDir").toString(); if (!suggested.isEmpty()) { saveDirEdit_->setText(suggested); } } const QJsonValue size = result.value("sizeBytes"); sizeLabel_->setText(tr("Size: %1") .arg(size.isDouble() ? QLocale().formattedDataSize( static_cast(size.toDouble()), 2) : tr("unknown"))); resumeLabel_->setText( tr("Resume capability: %1").arg(result.value("resumable").toBool() ? tr("Yes") : tr("No"))); const QString suggestedCategory = result.value("suggestedCategoryId").toString(); if (!suggestedCategory.isEmpty()) { const int idx = categoryCombo_->findData(suggestedCategory); if (idx >= 0) { categoryCombo_->setCurrentIndex(idx); } } } void FileInfoDialog::browseSaveDir() { const QString dir = QFileDialog::getExistingDirectory(this, tr("Save As"), saveDirEdit_->text()); if (!dir.isEmpty()) { saveDirEdit_->setText(dir); saveDirEditedByUser_ = true; } } void FileInfoDialog::submit(const QString &startMode, const QString &queueId) { const QJsonObject spec = buildSpec( url_, filenameEdit_->text().trimmed(), saveDirEdit_->text().trimmed(), selectedCategoryId(), descriptionEdit_->text().trimmed(), connectionsSpin_->value(), bufferCombo_->currentData().toLongLong(), startMode, queueId); QPointer self(this); client_->call(QStringLiteral("download.add"), spec, [self](const rpc::RpcReply &reply) { if (!self) { return; } if (reply.ok()) { self->accept(); return; } self->errorLabel_->setText(tr("Could not add the download: %1").arg(reply.error.message)); self->errorLabel_->show(); }); } QJsonObject FileInfoDialog::buildSpec(const QString &url, const QString &filename, const QString &saveDir, const QString &categoryId, const QString &description, int segments, qint64 bufferBytes, const QString &startMode, const QString &queueId) { QJsonObject spec{{"url", url}, {"startMode", startMode}}; if (!filename.isEmpty()) { spec["filename"] = filename; } if (!saveDir.isEmpty()) { spec["saveDir"] = saveDir; } if (!categoryId.isEmpty()) { spec["categoryId"] = categoryId; } if (!description.isEmpty()) { spec["description"] = description; } if (segments > 0) { spec["segments"] = segments; } if (bufferBytes > 0) { spec["bufferBytes"] = bufferBytes; } if (startMode == QLatin1String("queue") && !queueId.isEmpty()) { spec["queueId"] = queueId; } return spec; } } // namespace velox::gui