File indexing completed on 2025-03-09 04:58:14
0001 // SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL 0002 // SPDX-FileCopyrightText: 2022-2023 Harald Sitter <sitter@kde.org> 0003 0004 #include "sentrypostman.h" 0005 0006 #include <chrono> 0007 #include <thread> 0008 0009 #include <QDirIterator> 0010 #include <QEventLoopLocker> 0011 #include <QJsonDocument> 0012 0013 #include "debug.h" 0014 #include "sentrypaths.h" 0015 0016 using namespace Qt::StringLiterals; 0017 using namespace std::chrono_literals; 0018 0019 struct Transfer { 0020 QString path; 0021 QString sentPath; 0022 SentryReply *reply; 0023 std::shared_ptr<QEventLoopLocker> lock = std::make_shared<QEventLoopLocker>(); 0024 }; 0025 0026 void SentryPostman::run() 0027 { 0028 QEventLoopLocker lock; 0029 0030 const auto cacheDir = SentryPaths::payloadsDir(); 0031 if (cacheDir.isEmpty()) { 0032 qCWarning(SENTRY_DEBUG) << "Failed to resolve payloadsDir"; 0033 return; 0034 } 0035 0036 qCDebug(SENTRY_DEBUG) << "looking at " << cacheDir; 0037 QDirIterator it(cacheDir, QDir::Files); 0038 if (!it.hasNext()) { 0039 qCDebug(SENTRY_DEBUG) << "nothing there"; 0040 return; // nothing to process 0041 } 0042 0043 while (it.hasNext()) { 0044 const auto path = it.next(); 0045 const auto filename = it.fileName(); 0046 const auto mtime = it.fileInfo().lastModified(); 0047 post(path, filename, mtime); 0048 } 0049 } 0050 void SentryPostman::post(const QString &path, const QString &filename, const QDateTime &mtime) 0051 { 0052 const auto currentTime = QDateTime::currentDateTime(); 0053 0054 qCDebug(SENTRY_DEBUG) << "processing" << path; 0055 static constexpr auto daysInMonth = 30; 0056 if (currentTime - mtime >= daysInMonth * 24h) { 0057 // Incredibly old report. Probably not worth to submit it anymore. This can happen if the file is getting 0058 // rejected by the server over and over mostly. 0059 QFile::remove(path); 0060 return; 0061 } 0062 0063 static constexpr auto flushTime = 8s; // arbitrary timeout in which we expect a flush to finish 0064 if (currentTime - mtime < flushTime) { 0065 std::this_thread::sleep_for(flushTime); // make sure the file is fully flushed 0066 } 0067 0068 QFile file(path); 0069 if (!file.open(QFile::ReadOnly)) { 0070 qCWarning(SENTRY_DEBUG) << "Failed to open" << path; 0071 return; 0072 } 0073 0074 const auto doc = QJsonDocument::fromJson(file.readLine()); 0075 file.reset(); 0076 const auto dsn = doc["dsn"_L1].toString(); 0077 if (dsn.isEmpty()) { 0078 qCWarning(SENTRY_DEBUG) << "Missing DSN. Discarding" << path; 0079 QFile::remove(path); // invalid, discard it 0080 return; 0081 } 0082 0083 QNetworkRequest request(QUrl(dsn + "/envelope/"_L1)); 0084 request.setHeader(QNetworkRequest::ContentTypeHeader, "application/x-sentry-envelope"_L1); 0085 request.setHeader(QNetworkRequest::UserAgentHeader, "DrKonqi"_L1); 0086 // Auth is handled through the payload itself, it should carry a DSN. 0087 qCDebug(SENTRY_DEBUG) << "requesting" << request.url(); 0088 0089 auto reply = m_connection.post(request, file.readAll()); 0090 Transfer transfer{.path = path, .sentPath = SentryPaths::sentPayloadPath(filename), .reply = reply}; 0091 QObject::connect(reply, &SentryReply::finished, this, [transfer] { 0092 transfer.reply->deleteLater(); 0093 qCDebug(SENTRY_DEBUG) << transfer.reply->error(); 0094 if (transfer.reply->error() != QNetworkReply::NoError) { 0095 qCWarning(SENTRY_DEBUG) << transfer.reply->error() << transfer.reply->errorString(); 0096 return; 0097 } 0098 qCDebug(SENTRY_DEBUG) << "renaming" << transfer.path << "to" << transfer.sentPath; 0099 if (QFile::exists(transfer.sentPath)) { 0100 QFile::remove(transfer.sentPath); 0101 } 0102 QFile::rename(transfer.path, transfer.sentPath); 0103 }); 0104 } 0105 0106 #include "moc_sentrypostman.cpp"