@@ -1056,6 +1056,93 @@ QtMeshCloudClient::CompleteUploadResult QtMeshCloudClient::completeUpload(
10561056 return out;
10571057}
10581058
1059+ QtMeshCloudClient::UploadResult QtMeshCloudClient::uploadFileReport (
1060+ const QString& bearerToken,
1061+ const QString& ownerSlug,
1062+ const QString& projectSlug,
1063+ const QString& fileId,
1064+ const QJsonObject& report,
1065+ int timeoutMs)
1066+ {
1067+ UploadResult out;
1068+ if (bearerToken.isEmpty ()) {
1069+ out.errorString = QStringLiteral (" missing bearer token" );
1070+ return out;
1071+ }
1072+ if (ownerSlug.isEmpty () || projectSlug.isEmpty () || fileId.isEmpty ()) {
1073+ out.errorString = QStringLiteral (" owner slug, project slug, and fileId are required" );
1074+ return out;
1075+ }
1076+ if (report.isEmpty ()) {
1077+ out.errorString = QStringLiteral (" report JSON is empty" );
1078+ return out;
1079+ }
1080+
1081+ const QByteArray payload = QJsonDocument (report).toJson (QJsonDocument::Compact);
1082+ static constexpr int kMaxReportBytes = 5 * 1024 * 1024 ;
1083+ if (payload.size () > kMaxReportBytes ) {
1084+ out.errorString = QStringLiteral (" report exceeds 5 MB limit" );
1085+ return out;
1086+ }
1087+
1088+ const QString path = ownerProjectPath (ownerSlug, projectSlug,
1089+ QStringLiteral (" files/%1/report" )
1090+ .arg (QString::fromUtf8 (QUrl::toPercentEncoding (fileId))));
1091+ const QUrl url (apiBaseUrl () + path);
1092+ if (!url.isValid ()) {
1093+ out.errorString = QStringLiteral (" invalid API base URL" );
1094+ return out;
1095+ }
1096+
1097+ QNetworkAccessManager nam;
1098+ QNetworkRequest req = authorizedJsonRequest (url, bearerToken, timeoutMs);
1099+
1100+ SentryReporter::addBreadcrumb (QStringLiteral (" cloud.upload" ),
1101+ QStringLiteral (" QtMesh Cloud uploadFileReport: start fileId=%1 bytes=%2" )
1102+ .arg (fileId, QString::number (payload.size ())));
1103+
1104+ QNetworkReply* reply = nam.put (req, payload);
1105+ QEventLoop loop;
1106+ QObject::connect (reply, &QNetworkReply::finished, &loop, &QEventLoop::quit);
1107+ loop.exec ();
1108+
1109+ out.httpStatus = reply->attribute (QNetworkRequest::HttpStatusCodeAttribute).toInt ();
1110+ const QByteArray responseBody = reply->readAll ();
1111+ const auto nerr = reply->error ();
1112+ const QString transportErr = reply->errorString ();
1113+ reply->deleteLater ();
1114+
1115+ if (nerr != QNetworkReply::NoError || out.httpStatus < 200 || out.httpStatus >= 300 ) {
1116+ out.responseBodySnippet = trimSnippet (responseBody);
1117+ out.errorString = nerr != QNetworkReply::NoError ? transportErr : QStringLiteral (" HTTP %1" ).arg (out.httpStatus );
1118+ if (!out.responseBodySnippet .isEmpty ())
1119+ out.errorString += QStringLiteral (" — " ) + out.responseBodySnippet ;
1120+ SentryReporter::addBreadcrumb (QStringLiteral (" cloud.upload" ),
1121+ QStringLiteral (" QtMesh Cloud uploadFileReport: failure HTTP %1" ).arg (out.httpStatus ),
1122+ QStringLiteral (" warning" ));
1123+ return out;
1124+ }
1125+
1126+ QJsonObject root;
1127+ if (!parseJsonObjectBody (responseBody, root, out.errorString ))
1128+ return out;
1129+ if (root.contains (QStringLiteral (" ok" )) && !root.value (QStringLiteral (" ok" )).toBool ()) {
1130+ out.errorString = jsonErrorCode (root);
1131+ if (out.errorString .isEmpty ())
1132+ out.errorString = QStringLiteral (" report upload rejected" );
1133+ out.responseBodySnippet = trimSnippet (responseBody);
1134+ SentryReporter::addBreadcrumb (QStringLiteral (" cloud.upload" ),
1135+ QStringLiteral (" QtMesh Cloud uploadFileReport: rejected" ),
1136+ QStringLiteral (" warning" ));
1137+ return out;
1138+ }
1139+
1140+ out.ok = true ;
1141+ SentryReporter::addBreadcrumb (QStringLiteral (" cloud.upload" ),
1142+ QStringLiteral (" QtMesh Cloud uploadFileReport: ok fileId=%1" ).arg (fileId));
1143+ return out;
1144+ }
1145+
10591146QtMeshCloudClient::ManifestResult QtMeshCloudClient::fetchProjectManifest (const QString& bearerToken,
10601147 const QString& ownerSlug,
10611148 const QString& projectSlug,
0 commit comments