Skip to content

Commit 208c36d

Browse files
Address cloud upload review feedback for safety and robustness.
Use shared cancel flags so worker threads outlive session teardown, guard GUI uploads against overlapping project lists and stale tokens, tighten MCP dependency selection with a timeout, and add a test watchdog against hangs. Co-authored-by: Cursor <cursoragent@cursor.com>
1 parent dd2a131 commit 208c36d

6 files changed

Lines changed: 69 additions & 13 deletions

File tree

src/MCPServer.cpp

Lines changed: 21 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5277,9 +5277,17 @@ QJsonObject MCPServer::toolCloudUpload(const QJsonObject &args)
52775277

52785278
const bool runScan = !args.contains(QStringLiteral("scan")) || args.value(QStringLiteral("scan")).toBool(true);
52795279

5280+
const QString mainCanonical = QFileInfo(filePath).canonicalFilePath();
52805281
QStringList selectedPaths;
5281-
for (const DependencyEntry& entry : DependencyResolver::detect(filePath))
5282-
selectedPaths.append(entry.absolutePath);
5282+
selectedPaths.append(mainCanonical);
5283+
for (const DependencyEntry& entry : DependencyResolver::detect(filePath)) {
5284+
if (!entry.exists || !entry.checkedByDefault)
5285+
continue;
5286+
const QString absolute = QFileInfo(entry.absolutePath).absoluteFilePath();
5287+
if (absolute == mainCanonical)
5288+
continue;
5289+
selectedPaths.append(absolute);
5290+
}
52835291

52845292
CloudPackageUploadRequest request;
52855293
request.mainAssetPath = filePath;
@@ -5294,7 +5302,7 @@ QJsonObject MCPServer::toolCloudUpload(const QJsonObject &args)
52945302
QString error;
52955303
QString reportWarning;
52965304
bool uploadOk = false;
5297-
int uploadedFileCount = selectedPaths.size();
5305+
const int uploadedFileCount = selectedPaths.size();
52985306
connect(&session, &QtMeshCloudSession::uploadFinished, &loop,
52995307
[&](bool ok, const QString& err, const QString& url, const QString&) {
53005308
uploadOk = ok;
@@ -5310,6 +5318,16 @@ QJsonObject MCPServer::toolCloudUpload(const QJsonObject &args)
53105318
error = QStringLiteral("Upload canceled");
53115319
loop.quit();
53125320
});
5321+
QTimer timeout;
5322+
timeout.setSingleShot(true);
5323+
timeout.setInterval(10 * 60 * 1000);
5324+
connect(&timeout, &QTimer::timeout, &loop, [&]() {
5325+
session.cancel();
5326+
uploadOk = false;
5327+
error = QStringLiteral("Upload timed out");
5328+
loop.quit();
5329+
});
5330+
timeout.start();
53135331
session.uploadPackageFromAssets(request);
53145332
loop.exec();
53155333

src/QtMeshCloudSession.cpp

Lines changed: 14 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,8 @@ QtMeshCloudSession::QtMeshCloudSession(const QString& bearerToken, QObject* pare
8080
void QtMeshCloudSession::cancel()
8181
{
8282
m_canceled.store(true);
83+
if (m_uploadCancelFlag)
84+
m_uploadCancelFlag->store(true);
8385
}
8486

8587
void QtMeshCloudSession::listProjects()
@@ -107,18 +109,21 @@ void QtMeshCloudSession::uploadPackage(const PackageMetadata& metadata,
107109
const QString& projectSlug,
108110
bool createNewProject)
109111
{
112+
m_uploadCancelFlag = std::make_shared<std::atomic_bool>(false);
110113
m_canceled.store(false);
111114
startUploadWorker(metadata, ownerSlug, projectSlug, createNewProject);
112115
}
113116

114117
void QtMeshCloudSession::uploadPackageFromAssets(const CloudPackageUploadRequest& request)
115118
{
119+
m_uploadCancelFlag = std::make_shared<std::atomic_bool>(false);
116120
m_canceled.store(false);
117121
const QString token = m_bearerToken;
118122
const CloudPackageUploadRequest req = request;
119123
QPointer<QtMeshCloudSession> self(this);
124+
const std::shared_ptr<std::atomic_bool> canceled = m_uploadCancelFlag;
120125

121-
QThread* worker = QThread::create([self, token, req, canceled = &m_canceled]() {
126+
QThread* worker = QThread::create([self, token, req, canceled]() {
122127
if (canceled->load()) {
123128
invokeUploadCanceled(self);
124129
return;
@@ -247,7 +252,7 @@ void QtMeshCloudSession::uploadPackageFromAssets(const CloudPackageUploadRequest
247252
invokeUploadProgress(self, i + 1, total + 1, descriptors.at(i).uploadName);
248253

249254
const auto result = QtMeshCloudClient::uploadFileContent(
250-
token, uploadUrls.uploads.at(i), descriptors.at(i).path, canceled);
255+
token, uploadUrls.uploads.at(i), descriptors.at(i).path, canceled.get());
251256
if (result.canceled) {
252257
invokeUploadCanceled(self);
253258
return;
@@ -288,13 +293,13 @@ void QtMeshCloudSession::uploadPackageFromAssets(const CloudPackageUploadRequest
288293
reportWarning = QStringLiteral("File uploaded, but analysis report upload failed.");
289294
if (!reportResult.errorString.isEmpty())
290295
reportWarning += QStringLiteral("\n\n") + reportResult.errorString;
291-
SentryReporter::addBreadcrumb(QStringLiteral("cloud.upload"),
296+
SentryReporter::addBreadcrumb(QStringLiteral("file.export"),
292297
reportWarning,
293298
QStringLiteral("warning"));
294299
}
295300
}
296301

297-
SentryReporter::addBreadcrumb(QStringLiteral("cloud.upload"),
302+
SentryReporter::addBreadcrumb(QStringLiteral("file.export"),
298303
QStringLiteral("QtMesh Cloud package upload completed"));
299304
invokeUploadFinished(self, true, reportWarning, project.projectUrl, completed.scanStatus);
300305
});
@@ -310,9 +315,10 @@ void QtMeshCloudSession::startUploadWorker(const PackageMetadata& package,
310315
{
311316
const QString token = m_bearerToken;
312317
QPointer<QtMeshCloudSession> self(this);
318+
const std::shared_ptr<std::atomic_bool> canceled = m_uploadCancelFlag;
313319

314320
QThread* worker = QThread::create([self, token, package, ownerSlug, projectSlug, createNewProject,
315-
canceled = &m_canceled]() {
321+
canceled]() {
316322
if (canceled->load()) {
317323
invokeUploadCanceled(self);
318324
return;
@@ -380,7 +386,7 @@ void QtMeshCloudSession::startUploadWorker(const PackageMetadata& package,
380386
invokeUploadProgress(self, i + 1, total + 1, descriptors.at(i).uploadName);
381387

382388
const auto result = QtMeshCloudClient::uploadFileContent(
383-
token, uploadUrls.uploads.at(i), descriptors.at(i).path, canceled);
389+
token, uploadUrls.uploads.at(i), descriptors.at(i).path, canceled.get());
384390
if (result.canceled) {
385391
invokeUploadCanceled(self);
386392
return;
@@ -421,13 +427,13 @@ void QtMeshCloudSession::startUploadWorker(const PackageMetadata& package,
421427
reportWarning = QStringLiteral("File uploaded, but analysis report upload failed.");
422428
if (!reportResult.errorString.isEmpty())
423429
reportWarning += QStringLiteral("\n\n") + reportResult.errorString;
424-
SentryReporter::addBreadcrumb(QStringLiteral("cloud.upload"),
430+
SentryReporter::addBreadcrumb(QStringLiteral("file.export"),
425431
reportWarning,
426432
QStringLiteral("warning"));
427433
}
428434
}
429435

430-
SentryReporter::addBreadcrumb(QStringLiteral("cloud.upload"),
436+
SentryReporter::addBreadcrumb(QStringLiteral("file.export"),
431437
QStringLiteral("QtMesh Cloud package upload completed"));
432438
invokeUploadFinished(self, true, reportWarning, project.projectUrl, completed.scanStatus);
433439
});

src/QtMeshCloudSession.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66

77
#include <QObject>
88
#include <atomic>
9+
#include <memory>
910

1011
struct CloudPackageUploadRequest {
1112
QString mainAssetPath;
@@ -60,6 +61,7 @@ class QtMeshCloudSession : public QObject {
6061

6162
QString m_bearerToken;
6263
std::atomic_bool m_canceled{false};
64+
std::shared_ptr<std::atomic_bool> m_uploadCancelFlag;
6365
};
6466

6567
#endif // QTMESH_CLOUD_SESSION_H

src/QtMeshCloudSession_test.cpp

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
#include <QTcpServer>
1616
#include <QTcpSocket>
1717
#include <QTemporaryDir>
18+
#include <QTimer>
1819
#include <memory>
1920

2021
namespace {
@@ -210,9 +211,14 @@ TEST_F(QtMeshCloudSessionUploadReportTest, ReportFailureDoesNotFailBinaryUpload)
210211
});
211212

212213
session.uploadPackage(manifest);
214+
QTimer watchdog;
215+
watchdog.setSingleShot(true);
216+
watchdog.setInterval(30000);
217+
QObject::connect(&watchdog, &QTimer::timeout, &loop, &QEventLoop::quit);
218+
watchdog.start();
213219
loop.exec();
214220

215-
EXPECT_TRUE(uploadOk);
221+
ASSERT_TRUE(uploadOk) << "uploadFinished never fired (timeout or failure)";
216222
EXPECT_TRUE(m_mock->completeCalled());
217223
EXPECT_TRUE(m_mock->reportCalled());
218224
EXPECT_TRUE(uploadError.contains(QStringLiteral("analysis report upload failed"),

src/mainwindow.cpp

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2747,17 +2747,25 @@ void MainWindow::uploadFilesToQtMeshCloud()
27472747
return;
27482748
}
27492749

2750+
if (m_cloudUploadListingInFlight) {
2751+
statusBar()->showMessage(tr("Cloud upload already in progress…"), 3000);
2752+
return;
2753+
}
2754+
m_cloudUploadListingInFlight = true;
2755+
27502756
statusBar()->showMessage(tr("Loading cloud projects…"), 0);
27512757
m_cloudUploadProgress->start(tr("Loading cloud projects…"), 1);
27522758

27532759
QtMeshCloudSession* session = cloudSessionForToken(token);
2760+
disconnect(session, &QtMeshCloudSession::projectsListed, this, nullptr);
27542761
QPointer<MainWindow> self(this);
27552762
connect(session, &QtMeshCloudSession::projectsListed, this,
27562763
[self, token, mainAssetPath](const QList<QtMeshCloudClient::ProjectSummary>& projects,
27572764
const QString& listError) {
27582765
if (!self)
27592766
return;
27602767

2768+
self->m_cloudUploadListingInFlight = false;
27612769
self->m_cloudUploadProgress->hideProgress();
27622770
self->statusBar()->clearMessage();
27632771

@@ -2775,6 +2783,21 @@ void MainWindow::uploadFilesToQtMeshCloud()
27752783
return;
27762784
}
27772785

2786+
if (!CloudCredentialStore::hasSession()) {
2787+
QMessageBox::warning(self, self->tr("QtMesh Cloud Upload"),
2788+
self->tr("Your QtMesh Cloud session expired. Sign in again."));
2789+
self->updateCloudAuthActions();
2790+
return;
2791+
}
2792+
const QString currentToken = CloudCredentialStore::loadSession().token;
2793+
if (currentToken.isEmpty() || currentToken != token) {
2794+
QMessageBox::warning(self, self->tr("QtMesh Cloud Upload"),
2795+
self->tr("Your QtMesh Cloud session changed while loading "
2796+
"projects. Sign in again and retry."));
2797+
self->updateCloudAuthActions();
2798+
return;
2799+
}
2800+
27782801
CloudUploadDialog dialog(self);
27792802
dialog.setAccountLabel(storedCloudDisplayName());
27802803
dialog.setProjects(projects);
@@ -2800,7 +2823,7 @@ void MainWindow::uploadFilesToQtMeshCloud()
28002823
request.createNewProject = false;
28012824
request.runLocalScan = dialog.runLocalScanBeforeUpload();
28022825

2803-
self->startCloudPackageUpload(self->cloudSessionForToken(token), request);
2826+
self->startCloudPackageUpload(self->cloudSessionForToken(currentToken), request);
28042827
},
28052828
Qt::SingleShotConnection);
28062829

src/mainwindow.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -206,6 +206,7 @@ public slots:
206206
CloudUploadProgress* m_cloudUploadProgress = nullptr;
207207
QtMeshCloudSession* m_cloudSession = nullptr;
208208
QAction* m_cloudUploadMenuAction = nullptr;
209+
bool m_cloudUploadListingInFlight = false;
209210

210211
void updateCloudUploadActionState();
211212
void startCloudPackageUpload(QtMeshCloudSession* session,

0 commit comments

Comments
 (0)