464 lines
16 KiB
C++
464 lines
16 KiB
C++
#include <Agent/Agent.h>
|
|
#include <UI/Widgets/DownloadsWidget.h>
|
|
#include <UI/Widgets/AdaptixWidget.h>
|
|
#include <UI/Widgets/DockWidgetRegister.h>
|
|
#include <UI/Dialogs/DialogDownloader.h>
|
|
#include <Client/Requestor.h>
|
|
#include <Client/AuthProfile.h>
|
|
#include <Client/AxScript/AxScriptManager.h>
|
|
#include <Utils/NonBlockingDialogs.h>
|
|
#include <Utils/CustomElements.h>
|
|
|
|
static QString extractFileName(const QString& filePath)
|
|
{
|
|
QStringList pathParts = filePath.split("\\", Qt::SkipEmptyParts);
|
|
QString fileName = pathParts.isEmpty() ? filePath : pathParts.last();
|
|
pathParts = fileName.split("/", Qt::SkipEmptyParts);
|
|
return pathParts.isEmpty() ? fileName : pathParts.last();
|
|
}
|
|
|
|
REGISTER_DOCK_WIDGET(DownloadsWidget, "Downloads", true)
|
|
|
|
DownloadsWidget::DownloadsWidget(AdaptixWidget* w) : DockTab("Downloads", w->GetProfile()->GetProject(), ":/icons/downloads"), adaptixWidget(w)
|
|
{
|
|
this->createUI();
|
|
|
|
connect(tableView, &QTableView::customContextMenuRequested, this, &DownloadsWidget::handleDownloadsMenu);
|
|
connect(tableView->selectionModel(), &QItemSelectionModel::selectionChanged, this, [this](const QItemSelection &selected, const QItemSelection &deselected){
|
|
Q_UNUSED(selected)
|
|
Q_UNUSED(deselected)
|
|
if (!inputFilter->hasFocus())
|
|
tableView->setFocus();
|
|
});
|
|
connect(hideButton, &ClickableLabel::clicked, this, &DownloadsWidget::toggleSearchPanel);
|
|
connect(inputFilter, &QLineEdit::textChanged, this, &DownloadsWidget::onFilterUpdate);
|
|
connect(inputFilter, &QLineEdit::returnPressed, this, [this]() { proxyModel->setTextFilter(inputFilter->text()); });
|
|
connect(stateComboBox, &QComboBox::currentTextChanged, this, &DownloadsWidget::onStateFilterUpdate);
|
|
|
|
shortcutSearch = new QShortcut(QKeySequence("Ctrl+F"), this);
|
|
shortcutSearch->setContext(Qt::WidgetWithChildrenShortcut);
|
|
connect(shortcutSearch, &QShortcut::activated, this, &DownloadsWidget::toggleSearchPanel);
|
|
|
|
auto shortcutEsc = new QShortcut(QKeySequence(Qt::Key_Escape), inputFilter);
|
|
shortcutEsc->setContext(Qt::WidgetShortcut);
|
|
connect(shortcutEsc, &QShortcut::activated, this, [this]() { searchWidget->setVisible(false); });
|
|
|
|
this->dockWidget->setWidget(this);
|
|
}
|
|
|
|
void DownloadsWidget::createUI()
|
|
{
|
|
auto horizontalSpacer = new QSpacerItem(40, 20, QSizePolicy::Expanding, QSizePolicy::Minimum);
|
|
|
|
searchWidget = new QWidget(this);
|
|
searchWidget->setVisible(false);
|
|
|
|
inputFilter = new QLineEdit(searchWidget);
|
|
inputFilter->setPlaceholderText("filter: (exe | dll) & ^(temp)");
|
|
inputFilter->setMaximumWidth(300);
|
|
|
|
autoSearchCheck = new QCheckBox("auto", searchWidget);
|
|
autoSearchCheck->setChecked(true);
|
|
autoSearchCheck->setToolTip("Auto search on text change. If unchecked, press Enter to search.");
|
|
|
|
stateComboBox = new QComboBox(searchWidget);
|
|
stateComboBox->setMinimumWidth(100);
|
|
stateComboBox->addItems(QStringList() << "Any state" << "Running" << "Stopped" << "Finished");
|
|
|
|
hideButton = new ClickableLabel(" x ");
|
|
hideButton->setCursor(Qt::PointingHandCursor);
|
|
|
|
searchLayout = new QHBoxLayout(searchWidget);
|
|
searchLayout->setContentsMargins(0, 5, 0, 0);
|
|
searchLayout->setSpacing(4);
|
|
searchLayout->addWidget(inputFilter);
|
|
searchLayout->addWidget(autoSearchCheck);
|
|
searchLayout->addSpacing(8);
|
|
searchLayout->addWidget(stateComboBox);
|
|
searchLayout->addSpacing(8);
|
|
searchLayout->addWidget(hideButton);
|
|
searchLayout->addSpacerItem(horizontalSpacer);
|
|
|
|
downloadsModel = new DownloadsTableModel(this);
|
|
proxyModel = new DownloadsFilterProxyModel(this);
|
|
proxyModel->setSourceModel(downloadsModel);
|
|
proxyModel->setFilterCaseSensitivity(Qt::CaseInsensitive);
|
|
|
|
tableView = new QTableView(this);
|
|
tableView->setModel(proxyModel);
|
|
tableView->setHorizontalHeader(new BoldHeaderView(Qt::Horizontal, tableView));
|
|
tableView->setContextMenuPolicy(Qt::CustomContextMenu);
|
|
tableView->setAutoFillBackground(false);
|
|
tableView->setShowGrid(false);
|
|
tableView->setSortingEnabled(true);
|
|
tableView->setWordWrap(true);
|
|
tableView->setCornerButtonEnabled(false);
|
|
tableView->setSelectionBehavior(QAbstractItemView::SelectRows);
|
|
tableView->setFocusPolicy(Qt::NoFocus);
|
|
tableView->setAlternatingRowColors(true);
|
|
tableView->horizontalHeader()->setSectionResizeMode(QHeaderView::ResizeToContents);
|
|
tableView->horizontalHeader()->setCascadingSectionResizes(true);
|
|
tableView->horizontalHeader()->setHighlightSections(false);
|
|
tableView->verticalHeader()->setVisible(false);
|
|
|
|
tableView->setItemDelegate(new PaddingDelegate(tableView));
|
|
|
|
tableView->sortByColumn(DC_Date, Qt::AscendingOrder);
|
|
|
|
tableView->horizontalHeader()->setSectionResizeMode(DC_File, QHeaderView::Stretch);
|
|
tableView->setItemDelegateForColumn(DC_Progress, new ProgressBarDelegate(this));
|
|
tableView->hideColumn(DC_FileId);
|
|
|
|
mainGridLayout = new QGridLayout(this);
|
|
mainGridLayout->setContentsMargins(0, 0, 0, 0);
|
|
mainGridLayout->addWidget(searchWidget, 0, 0, 1, 1);
|
|
mainGridLayout->addWidget(tableView, 1, 0, 1, 1);
|
|
}
|
|
|
|
DownloadsWidget::~DownloadsWidget() = default;
|
|
|
|
void DownloadsWidget::SetUpdatesEnabled(bool enabled)
|
|
{
|
|
if (!enabled) {
|
|
bufferingEnabled = true;
|
|
} else {
|
|
bufferingEnabled = false;
|
|
flushPendingDownloads();
|
|
}
|
|
|
|
if (proxyModel)
|
|
proxyModel->setDynamicSortFilter(enabled);
|
|
if (tableView)
|
|
tableView->setSortingEnabled(enabled);
|
|
|
|
tableView->setUpdatesEnabled(enabled);
|
|
}
|
|
|
|
void DownloadsWidget::flushPendingDownloads()
|
|
{
|
|
if (pendingDownloads.isEmpty())
|
|
return;
|
|
|
|
QList<DownloadData> filtered;
|
|
{
|
|
QWriteLocker locker(&adaptixWidget->DownloadsLock);
|
|
int count = 0;
|
|
for (const auto& download : pendingDownloads) {
|
|
if (adaptixWidget->Downloads.contains(download.FileId))
|
|
continue;
|
|
|
|
adaptixWidget->Downloads[download.FileId] = download;
|
|
filtered.append(download);
|
|
}
|
|
}
|
|
|
|
if (!filtered.isEmpty())
|
|
downloadsModel->addBatch(filtered);
|
|
|
|
pendingDownloads.clear();
|
|
}
|
|
|
|
void DownloadsWidget::Clear() const
|
|
{
|
|
{
|
|
QWriteLocker locker(&adaptixWidget->DownloadsLock);
|
|
adaptixWidget->Downloads.clear();
|
|
}
|
|
downloadsModel->clear();
|
|
}
|
|
|
|
void DownloadsWidget::AddDownloadItem(const DownloadData &newDownload)
|
|
{
|
|
if (bufferingEnabled) {
|
|
pendingDownloads.append(newDownload);
|
|
return;
|
|
}
|
|
|
|
QWriteLocker locker(&adaptixWidget->DownloadsLock);
|
|
if (adaptixWidget->Downloads.contains(newDownload.FileId))
|
|
return;
|
|
|
|
adaptixWidget->Downloads[newDownload.FileId] = newDownload;
|
|
locker.unlock();
|
|
downloadsModel->add(newDownload);
|
|
}
|
|
|
|
void DownloadsWidget::EditDownloadItem(const QString &fileId, qint64 recvSize, int state)
|
|
{
|
|
{
|
|
QWriteLocker locker(&adaptixWidget->DownloadsLock);
|
|
if (!adaptixWidget->Downloads.contains(fileId))
|
|
return;
|
|
|
|
adaptixWidget->Downloads[fileId].RecvSize = recvSize;
|
|
adaptixWidget->Downloads[fileId].State = state;
|
|
|
|
if (state == DOWNLOAD_STATE_FINISHED)
|
|
adaptixWidget->Downloads[fileId].RecvSize = adaptixWidget->Downloads[fileId].TotalSize;
|
|
|
|
if (state == DOWNLOAD_STATE_CANCELED)
|
|
adaptixWidget->Downloads.remove(fileId);
|
|
}
|
|
|
|
if (state == DOWNLOAD_STATE_CANCELED) {
|
|
QStringList fileIds;
|
|
fileIds.append(fileId);
|
|
downloadsModel->remove(fileIds);
|
|
} else {
|
|
downloadsModel->update(fileId, recvSize, state);
|
|
}
|
|
}
|
|
|
|
void DownloadsWidget::RemoveDownloadItem(const QStringList &filesId)
|
|
{
|
|
QStringList filtered;
|
|
{
|
|
QWriteLocker locker(&adaptixWidget->DownloadsLock);
|
|
for (auto fileId : filesId) {
|
|
if (adaptixWidget->Downloads.contains(fileId)) {
|
|
adaptixWidget->Downloads.remove(fileId);
|
|
filtered.append(fileId);
|
|
}
|
|
}
|
|
}
|
|
downloadsModel->remove(filtered);
|
|
}
|
|
|
|
QString DownloadsWidget::getSelectedFileId() const
|
|
{
|
|
QModelIndexList selected = tableView->selectionModel()->selectedRows();
|
|
if (selected.isEmpty())
|
|
return {};
|
|
|
|
QModelIndex sourceIndex = proxyModel->mapToSource(selected.first());
|
|
return downloadsModel->getFileIdAt(sourceIndex.row());
|
|
}
|
|
|
|
const DownloadData* DownloadsWidget::getSelectedDownload() const
|
|
{
|
|
QString fileId = getSelectedFileId();
|
|
if (fileId.isEmpty())
|
|
return nullptr;
|
|
return downloadsModel->getById(fileId);
|
|
}
|
|
|
|
/// SLOTS
|
|
|
|
void DownloadsWidget::toggleSearchPanel() const
|
|
{
|
|
if (this->searchWidget->isVisible()) {
|
|
this->searchWidget->setVisible(false);
|
|
proxyModel->setSearchVisible(false);
|
|
}
|
|
else {
|
|
this->searchWidget->setVisible(true);
|
|
proxyModel->setSearchVisible(true);
|
|
inputFilter->setFocus();
|
|
}
|
|
}
|
|
|
|
void DownloadsWidget::onFilterUpdate() const
|
|
{
|
|
if (autoSearchCheck->isChecked()) {
|
|
proxyModel->setTextFilter(inputFilter->text());
|
|
}
|
|
inputFilter->setFocus();
|
|
tableView->horizontalHeader()->resizeSections(QHeaderView::ResizeToContents);
|
|
tableView->horizontalHeader()->setSectionResizeMode(DC_File, QHeaderView::Stretch);
|
|
}
|
|
|
|
void DownloadsWidget::onStateFilterUpdate() const
|
|
{
|
|
int idx = stateComboBox->currentIndex();
|
|
if (idx == 0)
|
|
proxyModel->setStateFilter(-1);
|
|
else
|
|
proxyModel->setStateFilter(idx);
|
|
tableView->horizontalHeader()->resizeSections(QHeaderView::ResizeToContents);
|
|
tableView->horizontalHeader()->setSectionResizeMode(DC_File, QHeaderView::Stretch);
|
|
}
|
|
|
|
void DownloadsWidget::handleDownloadsMenu(const QPoint &pos)
|
|
{
|
|
QModelIndex index = tableView->indexAt(pos);
|
|
if (!index.isValid())
|
|
return;
|
|
|
|
const DownloadData* download = getSelectedDownload();
|
|
if (!download)
|
|
return;
|
|
|
|
QVector<DataMenuDownload> files;
|
|
DataMenuDownload data = {};
|
|
data.agentId = download->AgentId;
|
|
data.fileId = download->FileId;
|
|
data.path = download->Filename;
|
|
|
|
auto ctxMenu = QMenu();
|
|
|
|
if (download->State == DOWNLOAD_STATE_FINISHED) {
|
|
ctxMenu.addAction("Sync file to client", this, &DownloadsWidget::actionSync);
|
|
|
|
auto syncMenu = new QMenu("Sync as ...", &ctxMenu);
|
|
syncMenu->addAction("Curl command", this, &DownloadsWidget::actionSyncCurl);
|
|
syncMenu->addAction("Wget command", this, &DownloadsWidget::actionSyncWget);
|
|
ctxMenu.addMenu(syncMenu);
|
|
ctxMenu.addSeparator();
|
|
|
|
data.state = "finished";
|
|
files.append(data);
|
|
int menuCount = adaptixWidget->ScriptManager->AddMenuDownload(&ctxMenu, "DownloadFinished", files, false);
|
|
if (menuCount > 0)
|
|
ctxMenu.addSeparator();
|
|
|
|
ctxMenu.addAction("Delete file", this, &DownloadsWidget::actionDelete);
|
|
}
|
|
else {
|
|
if (download->State == DOWNLOAD_STATE_RUNNING) {
|
|
data.state = "running";
|
|
} else {
|
|
data.state = "stopped";
|
|
}
|
|
files.append(data);
|
|
adaptixWidget->ScriptManager->AddMenuDownload(&ctxMenu, "DownloadRunning", files, true);
|
|
}
|
|
|
|
ctxMenu.exec(tableView->viewport()->mapToGlobal(pos));
|
|
}
|
|
|
|
void DownloadsWidget::actionSync()
|
|
{
|
|
const DownloadData* download = getSelectedDownload();
|
|
if (!download || download->State != DOWNLOAD_STATE_FINISHED)
|
|
return;
|
|
|
|
QString fileId = download->FileId;
|
|
QString filePath = download->Filename;
|
|
|
|
QString message = QString();
|
|
bool ok = false;
|
|
bool result = HttpReqGetOTP("download", fileId, *adaptixWidget->GetProfile(), &message, &ok);
|
|
if (!result) {
|
|
MessageError("Response timeout");
|
|
return;
|
|
}
|
|
if (!ok) {
|
|
MessageError(message);
|
|
return;
|
|
}
|
|
QString otp = message;
|
|
QString fileName = extractFileName(filePath);
|
|
|
|
QString baseDir = fileName;
|
|
if (adaptixWidget && adaptixWidget->GetProfile())
|
|
baseDir = QDir(adaptixWidget->GetProfile()->GetProjectDir()).filePath(fileName);
|
|
|
|
NonBlockingDialogs::getSaveFileName(this, "Save File", baseDir, "All Files (*.*)",
|
|
[this, otp](const QString& savedPath) {
|
|
if (savedPath.isEmpty())
|
|
return;
|
|
|
|
QString sUrl = adaptixWidget->GetProfile()->GetURL() + "/otp/download/sync";
|
|
|
|
DialogDownloader dialog(sUrl, otp, savedPath);
|
|
dialog.exec();
|
|
});
|
|
}
|
|
|
|
void DownloadsWidget::actionSyncCurl()
|
|
{
|
|
const DownloadData* download = getSelectedDownload();
|
|
if (!download || download->State != DOWNLOAD_STATE_FINISHED)
|
|
return;
|
|
|
|
QString fileId = download->FileId;
|
|
QString filePath = download->Filename;
|
|
|
|
QString message = QString();
|
|
bool ok = false;
|
|
bool result = HttpReqGetOTP("download", fileId, *adaptixWidget->GetProfile(), &message, &ok);
|
|
if (!result) {
|
|
MessageError("Response timeout");
|
|
return;
|
|
}
|
|
if (!ok) {
|
|
MessageError(message);
|
|
return;
|
|
}
|
|
QString otp = message;
|
|
QString fileName = extractFileName(filePath);
|
|
QString sUrl = adaptixWidget->GetProfile()->GetURL() + "/otp/download/sync";
|
|
|
|
QString command = QString("curl -k '%1?otp=%2' -o %3").arg(sUrl).arg(otp).arg(fileName);
|
|
|
|
QInputDialog inputDialog;
|
|
inputDialog.setWindowTitle("Sync file as curl");
|
|
inputDialog.setLabelText("Curl command:");
|
|
inputDialog.setTextEchoMode(QLineEdit::Normal);
|
|
inputDialog.setTextValue(command);
|
|
inputDialog.setFixedSize(700, 60);
|
|
inputDialog.move(QGuiApplication::primaryScreen()->geometry().center() - inputDialog.geometry().center());
|
|
inputDialog.exec();
|
|
}
|
|
|
|
void DownloadsWidget::actionSyncWget()
|
|
{
|
|
const DownloadData* download = getSelectedDownload();
|
|
if (!download || download->State != DOWNLOAD_STATE_FINISHED)
|
|
return;
|
|
|
|
QString fileId = download->FileId;
|
|
QString filePath = download->Filename;
|
|
|
|
QString message = QString();
|
|
bool ok = false;
|
|
bool result = HttpReqGetOTP("download", fileId, *adaptixWidget->GetProfile(), &message, &ok);
|
|
if (!result) {
|
|
MessageError("Response timeout");
|
|
return;
|
|
}
|
|
if (!ok) {
|
|
MessageError(message);
|
|
return;
|
|
}
|
|
QString otp = message;
|
|
QString fileName = extractFileName(filePath);
|
|
QString sUrl = adaptixWidget->GetProfile()->GetURL() + "/otp/download/sync";
|
|
|
|
QString command = QString("wget --no-check-certificate '%1?otp=%2' -O %3").arg(sUrl).arg(otp).arg(fileName);
|
|
|
|
QInputDialog inputDialog;
|
|
inputDialog.setWindowTitle("Sync file as wget");
|
|
inputDialog.setLabelText("Wget command:");
|
|
inputDialog.setTextEchoMode(QLineEdit::Normal);
|
|
inputDialog.setTextValue(command);
|
|
inputDialog.setFixedSize(700, 60);
|
|
inputDialog.move(QGuiApplication::primaryScreen()->geometry().center() - inputDialog.geometry().center());
|
|
inputDialog.exec();
|
|
}
|
|
|
|
void DownloadsWidget::actionDelete()
|
|
{
|
|
QStringList files;
|
|
QModelIndexList selectedRows = tableView->selectionModel()->selectedRows();
|
|
for (const QModelIndex &proxyIndex : selectedRows) {
|
|
QModelIndex sourceIndex = proxyModel->mapToSource(proxyIndex);
|
|
if (!sourceIndex.isValid()) continue;
|
|
|
|
QString fileId = downloadsModel->data(downloadsModel->index(sourceIndex.row(), DC_FileId), Qt::DisplayRole).toString();
|
|
files.append(fileId);
|
|
}
|
|
|
|
for (auto fileId : files) {
|
|
if ( downloadsModel->getById(fileId)->State != DOWNLOAD_STATE_FINISHED)
|
|
files.removeAll(fileId);
|
|
}
|
|
if (files.isEmpty())
|
|
return;
|
|
|
|
HttpReqDownloadDelete(files, *(adaptixWidget->GetProfile()), [](bool success, const QString& message, const QJsonObject&) {
|
|
if (!success)
|
|
MessageError(message.isEmpty() ? "Response timeout" : message);
|
|
});
|
|
}
|