QGroundControl
Ground Control Station for MAVLink Drones
Loading...
Searching...
No Matches
QGCCompressionJob.cc
Go to the documentation of this file.
1#include "QGCCompressionJob.h"
2#include "QGCCompression.h"
4
5#include <QtConcurrent/QtConcurrent>
6#include <QtCore/QPromise>
7
8QGC_LOGGING_CATEGORY(QGCCompressionJobLog, "Utilities.QGCCompressionJob")
9
10// ============================================================================
11// Construction / Destruction
12// ============================================================================
13
15 : QObject(parent)
16 , _watcher(new QFutureWatcher<bool>(this))
17{
18 connect(_watcher, &QFutureWatcher<bool>::progressValueChanged,
19 this, &QGCCompressionJob::_onProgressValueChanged);
20 connect(_watcher, &QFutureWatcher<bool>::finished,
21 this, &QGCCompressionJob::_onFutureFinished);
22}
23
25{
26 cancel();
27 if (_future.isRunning()) {
28 _future.waitForFinished();
29 }
30}
31
32// ============================================================================
33// Static Async Methods
34// ============================================================================
35
36QFuture<bool> QGCCompressionJob::extractArchiveAsync(const QString &archivePath,
37 const QString &outputDirectoryPath,
38 qint64 maxBytes)
39{
40 const auto cancelRequested = std::make_shared<std::atomic_bool>(false);
41 auto work = [archivePath, outputDirectoryPath, maxBytes](QGCCompression::ProgressCallback progress) {
42 return QGCCompression::extractArchive(archivePath, outputDirectoryPath,
44 progress, maxBytes);
45 };
46
47 return _runWithProgress(work, cancelRequested);
48}
49
50QFuture<bool> QGCCompressionJob::decompressFileAsync(const QString &inputPath,
51 const QString &outputPath,
52 qint64 maxBytes)
53{
54 const auto cancelRequested = std::make_shared<std::atomic_bool>(false);
55 auto work = [inputPath, outputPath, maxBytes](QGCCompression::ProgressCallback progress) {
58 progress, maxBytes);
59 };
60
61 return _runWithProgress(work, cancelRequested);
62}
63
64// ============================================================================
65// Public Slots
66// ============================================================================
67
68void QGCCompressionJob::extractArchive(const QString &archivePath,
69 const QString &outputDirectoryPath,
70 qint64 maxBytes)
71{
72 auto work = [archivePath, outputDirectoryPath, maxBytes](QGCCompression::ProgressCallback progress) {
73 return QGCCompression::extractArchive(archivePath, outputDirectoryPath,
75 progress, maxBytes);
76 };
77
78 _startOperation(Operation::ExtractArchive, archivePath, outputDirectoryPath, work);
79}
80
81void QGCCompressionJob::extractArchiveAtomic(const QString &archivePath,
82 const QString &outputDirectoryPath,
83 qint64 maxBytes)
84{
85 auto work = [archivePath, outputDirectoryPath, maxBytes](QGCCompression::ProgressCallback progress) {
86 return QGCCompression::extractArchiveAtomic(archivePath, outputDirectoryPath,
88 progress, maxBytes);
89 };
90
91 _startOperation(Operation::ExtractArchiveAtomic, archivePath, outputDirectoryPath, work);
92}
93
94void QGCCompressionJob::decompressFile(const QString &inputPath,
95 const QString &outputPath,
96 qint64 maxBytes)
97{
98 auto work = [inputPath, outputPath, maxBytes](QGCCompression::ProgressCallback progress) {
101 progress, maxBytes);
102 };
103
104 _startOperation(Operation::DecompressFile, inputPath, outputPath, work);
105}
106
107void QGCCompressionJob::extractFile(const QString &archivePath,
108 const QString &fileName,
109 const QString &outputPath)
110{
111 auto work = [archivePath, fileName, outputPath](QGCCompression::ProgressCallback) {
112 return QGCCompression::extractFile(archivePath, fileName, outputPath);
113 };
114
115 _startOperation(Operation::ExtractFile, archivePath, outputPath, work);
116}
117
118void QGCCompressionJob::extractFiles(const QString &archivePath,
119 const QStringList &fileNames,
120 const QString &outputDirectoryPath)
121{
122 auto work = [archivePath, fileNames, outputDirectoryPath](QGCCompression::ProgressCallback) {
123 return QGCCompression::extractFiles(archivePath, fileNames, outputDirectoryPath);
124 };
125
126 _startOperation(Operation::ExtractFiles, archivePath, outputDirectoryPath, work);
127}
128
130{
131 if (!_running || !_future.isRunning()) {
132 return;
133 }
134
135 qCDebug(QGCCompressionJobLog) << "Cancelling operation:" << static_cast<int>(_operation);
136
137 if (_cancelRequested) {
138 _cancelRequested->store(true, std::memory_order_release);
139 }
140
141 _future.cancel();
142}
143
144// ============================================================================
145// Private Slots
146// ============================================================================
147
148void QGCCompressionJob::_onProgressValueChanged(int progressValue)
149{
150 _setProgress(static_cast<qreal>(progressValue) / 100.0);
151}
152
153void QGCCompressionJob::_onFutureFinished()
154{
155 bool success = false;
156 QString error;
157 const bool wasCancelled = _future.isCanceled()
158 || (_cancelRequested && _cancelRequested->load(std::memory_order_acquire));
159
160 if (wasCancelled) {
161 error = QStringLiteral("Operation cancelled");
162 qCDebug(QGCCompressionJobLog) << "Operation cancelled:" << static_cast<int>(_operation);
163 } else {
164 try {
165 success = _future.result();
166 if (!success) {
168 }
169 } catch (const std::exception &e) {
170 error = QString::fromUtf8(e.what());
171 }
172 }
173
174 qCDebug(QGCCompressionJobLog) << "Operation finished:" << static_cast<int>(_operation)
175 << "success:" << success
176 << "error:" << error;
177
178 if (!success && !error.isEmpty()) {
179 _setErrorString(error);
180 } else if (success) {
181 _setErrorString(QString());
182 }
183
184 _setProgress(success ? 1.0 : _progress);
185 _setRunning(false);
186 _operation = Operation::None;
187 _cancelRequested.reset();
188
189 emit finished(success);
190}
191
192// ============================================================================
193// Private Methods
194// ============================================================================
195
196void QGCCompressionJob::_startOperation(Operation op, const QString &source,
197 const QString &output,
198 WorkFunction work)
199{
200 if (_running) {
201 qCWarning(QGCCompressionJobLog) << "Operation already in progress";
202 return;
203 }
204
205 qCDebug(QGCCompressionJobLog) << "Starting operation:" << static_cast<int>(op)
206 << "source:" << source << "output:" << output;
207
208 _operation = op;
209
210 // Update paths
211 if (_sourcePath != source) {
212 _sourcePath = source;
213 emit sourcePathChanged(_sourcePath);
214 }
215 if (_outputPath != output) {
216 _outputPath = output;
217 emit outputPathChanged(_outputPath);
218 }
219
220 _setProgress(0.0);
221 _setErrorString(QString());
222 _setRunning(true);
223 _cancelRequested = std::make_shared<std::atomic_bool>(false);
224
225 // Create and start the future
226 _future = _runWithProgress(std::move(work), _cancelRequested);
227 _watcher->setFuture(_future);
228}
229
230QFuture<bool> QGCCompressionJob::_runWithProgress(WorkFunction work,
231 const std::shared_ptr<std::atomic_bool> &cancelRequested)
232{
233 // Use QPromise to create a QFuture with progress reporting
234 return QtConcurrent::run([work = std::move(work), cancelRequested]() -> bool {
235 QPromise<bool> promise;
236 QFuture<bool> future = promise.future();
237
238 promise.start();
239 promise.setProgressRange(0, 100);
240
241 // Progress callback that updates QPromise and checks for cancellation
242 auto progressCallback = [&promise, cancelRequested](qint64 bytesProcessed, qint64 totalBytes) -> bool {
243 if (promise.isCanceled()
244 || (cancelRequested && cancelRequested->load(std::memory_order_acquire))) {
245 return false; // Signal cancellation to the work function
246 }
247
248 int progressValue = 0;
249 if (totalBytes > 0) {
250 progressValue = static_cast<int>((bytesProcessed * 100) / totalBytes);
251 } else if (bytesProcessed > 0) {
252 // Unknown total - asymptotic progress
253 const double normalized = static_cast<double>(bytesProcessed) / 1048576.0;
254 progressValue = static_cast<int>(50.0 * (1.0 - (1.0 / (1.0 + normalized))));
255 }
256
257 promise.setProgressValue(progressValue);
258 return true; // Continue
259 };
260
261 bool success = false;
262 try {
263 if (cancelRequested && cancelRequested->load(std::memory_order_acquire)) {
264 success = false;
265 } else {
266 success = work(progressCallback);
267 }
268 } catch (const std::exception &e) {
269 qCWarning(QGCCompressionJobLog) << "Exception during compression operation:" << e.what();
270 success = false;
271 }
272
273 promise.setProgressValue(100);
274 promise.addResult(success);
275 promise.finish();
276
277 return success;
278 });
279}
280
281void QGCCompressionJob::_setProgress(qreal progress)
282{
283 if (!qFuzzyCompare(_progress, progress)) {
284 _progress = progress;
285 emit progressChanged(_progress);
286 }
287}
288
289void QGCCompressionJob::_setRunning(bool running)
290{
291 if (_running != running) {
292 _running = running;
293 emit runningChanged(_running);
294 }
295}
296
297void QGCCompressionJob::_setErrorString(const QString &error)
298{
299 if (_errorString != error) {
300 _errorString = error;
301 emit errorStringChanged(_errorString);
302 }
303}
QObject wrapper for async compression operations using QtConcurrent/QPromise.
Error error
#define QGC_LOGGING_CATEGORY(name, categoryStr)
QObject wrapper for compression operations with progress signals.
void runningChanged(bool running)
Emitted when running state changes.
void progressChanged(qreal progress)
Emitted when progress changes (0.0 to 1.0)
qreal progress() const
void errorStringChanged(const QString &errorString)
Emitted when error string changes.
void extractArchiveAtomic(const QString &archivePath, const QString &outputDirectoryPath, qint64 maxBytes=0)
void extractFiles(const QString &archivePath, const QStringList &fileNames, const QString &outputDirectoryPath)
void extractFile(const QString &archivePath, const QString &fileName, const QString &outputPath)
void cancel()
Cancel current operation.
static QFuture< bool > extractArchiveAsync(const QString &archivePath, const QString &outputDirectoryPath, qint64 maxBytes=0)
void sourcePathChanged(const QString &sourcePath)
Emitted when source path changes.
void finished(bool success)
void outputPathChanged(const QString &outputPath)
Emitted when output path changes.
void decompressFile(const QString &inputPath, const QString &outputPath=QString(), qint64 maxBytes=0)
QString outputPath() const
void extractArchive(const QString &archivePath, const QString &outputDirectoryPath, qint64 maxBytes=0)
static QFuture< bool > decompressFileAsync(const QString &inputPath, const QString &outputPath=QString(), qint64 maxBytes=0)
QFuture< bool > future() const
~QGCCompressionJob() override
bool extractArchiveAtomic(const QString &archivePath, const QString &outputDirectoryPath, Format format, ProgressCallback progress, qint64 maxDecompressedBytes)
bool extractArchive(const QString &archivePath, const QString &outputDirectoryPath, Format format, ProgressCallback progress, qint64 maxDecompressedBytes)
bool decompressFile(const QString &inputPath, const QString &outputPath, Format format, ProgressCallback progress, qint64 maxDecompressedBytes)
QString lastErrorString()
Get a human-readable error message from the last operation (thread-local)
@ Auto
Auto-detect from file extension or magic bytes.
bool extractFile(const QString &archivePath, const QString &fileName, const QString &outputPath, Format format)
std::function< bool(qint64 bytesProcessed, qint64 totalBytes)> ProgressCallback
bool extractFiles(const QString &archivePath, const QStringList &fileNames, const QString &outputDirectoryPath, Format format)