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
24QGCCompressionJob::~QGCCompressionJob()
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) {
56 return QGCCompression::decompressFile(inputPath, outputPath,
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) {
99 return QGCCompression::decompressFile(inputPath, outputPath,
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)
void runningChanged(bool running)
Emitted when running state changes.
void progressChanged(qreal progress)
Emitted when progress changes (0.0 to 1.0)
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.
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)
void extractArchive(const QString &archivePath, const QString &outputDirectoryPath, qint64 maxBytes=0)
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)