6#include <QtCore/QFileInfo>
21 this, &QGCArchiveWatcher::_onDirectoryChanged);
26 if (_extractionJob !=
nullptr && _extractionJob->
isRunning()) {
27 _cancelPending =
true;
43 if (_autoDecompress != enable) {
44 _autoDecompress = enable;
51 if (_outputDirectory != directory) {
52 _outputDirectory = directory;
59 _removeAfterExtraction = remove;
78 if (directoryPath.isEmpty()) {
79 qCWarning(QGCArchiveWatcherLog) <<
"watchDirectory: empty path";
83 const QString canonicalPath = QFileInfo(directoryPath).absoluteFilePath();
85 if (!QFileInfo(canonicalPath).isDir()) {
86 qCWarning(QGCArchiveWatcherLog) <<
"watchDirectory: not a directory:" << directoryPath;
91 if (!_knownFiles.contains(canonicalPath)) {
92 QDir dir(canonicalPath);
93 const QStringList entries = dir.entryList(QDir::Files);
94 QSet<QString> fileSet;
95 for (
const QString &entry : entries) {
96 fileSet.insert(dir.absoluteFilePath(entry));
98 _knownFiles[canonicalPath] = fileSet;
99 qCDebug(QGCArchiveWatcherLog) <<
"Initialized" << fileSet.size() <<
"known files in" << canonicalPath;
103 qCDebug(QGCArchiveWatcherLog) <<
"Watching directory for archives:" << canonicalPath;
112 const QString canonicalPath = QFileInfo(directoryPath).absoluteFilePath();
113 _knownFiles.remove(canonicalPath);
124 _fileWatcher->
clear();
126 _pendingExtractions.clear();
127 _currentArchive.clear();
128 _setExtracting(
false);
131 if (_extractionJob) {
133 _cancelPending =
true;
145 QStringList archives;
147 QDir dir(directoryPath);
152 const QStringList entries = dir.entryList(QDir::Files);
153 for (
const QString &entry : entries) {
154 const QString fullPath = dir.absoluteFilePath(entry);
155 if (_isWatchedFormat(fullPath)) {
156 archives.append(fullPath);
165 if (_extractionJob !=
nullptr && _extractionJob->
isRunning()) {
166 qCDebug(QGCArchiveWatcherLog) <<
"Cancelling extraction";
167 _cancelPending =
true;
170 _pendingExtractions.clear();
171 _currentArchive.clear();
172 _setExtracting(
false);
180void QGCArchiveWatcher::_onDirectoryChanged(
const QString &path)
182 qCDebug(QGCArchiveWatcherLog) <<
"Directory changed:" << path;
190 const QStringList currentEntries = dir.entryList(QDir::Files);
191 QSet<QString> currentFiles;
192 for (
const QString &entry : currentEntries) {
193 currentFiles.insert(dir.absoluteFilePath(entry));
197 QSet<QString> &knownFiles = _knownFiles[path];
198 const QSet<QString> newFiles = currentFiles - knownFiles;
201 knownFiles = currentFiles;
204 for (
const QString &filePath : newFiles) {
205 _processNewFile(filePath);
209void QGCArchiveWatcher::_onExtractionProgress(qreal progress)
214void QGCArchiveWatcher::_onExtractionFinished(
bool success)
216 if (_cancelPending) {
217 _cancelPending =
false;
218 _currentArchive.clear();
219 _setExtracting(
false);
224 qCDebug(QGCArchiveWatcherLog) <<
"Extraction finished:" << _currentArchive
225 <<
"success:" << success;
227 QString outputPath = _extractionJob->
outputPath();
231 if (success && _removeAfterExtraction) {
232 if (QFile::remove(_currentArchive)) {
233 qCDebug(QGCArchiveWatcherLog) <<
"Removed source archive:" << _currentArchive;
235 qCWarning(QGCArchiveWatcherLog) <<
"Failed to remove source archive:" << _currentArchive;
241 _currentArchive.clear();
242 _setExtracting(
false);
246 if (!_pendingExtractions.isEmpty()) {
247 const QString next = _pendingExtractions.takeFirst();
248 _startExtraction(next);
256bool QGCArchiveWatcher::_isWatchedFormat(
const QString &filePath)
const
260 switch (_filterMode) {
273void QGCArchiveWatcher::_processNewFile(
const QString &filePath)
275 if (!_isWatchedFormat(filePath)) {
281 qCDebug(QGCArchiveWatcherLog) <<
"Detected archive:" << filePath
286 if (_autoDecompress) {
289 if (!_pendingExtractions.contains(filePath)) {
290 _pendingExtractions.append(filePath);
291 qCDebug(QGCArchiveWatcherLog) <<
"Queued for extraction:" << filePath;
294 _startExtraction(filePath);
299void QGCArchiveWatcher::_startExtraction(
const QString &archivePath)
301 if (!QFileInfo::exists(archivePath)) {
302 qCWarning(QGCArchiveWatcherLog) <<
"Archive no longer exists:" << archivePath;
307 QString outputPath = _outputDirectory;
308 if (outputPath.isEmpty()) {
309 outputPath = QFileInfo(archivePath).absolutePath();
317 qCDebug(QGCArchiveWatcherLog) <<
"Starting extraction:" << archivePath
318 <<
"to" << outputPath
319 << (isArchive ?
"(archive)" :
"(compressed file)");
321 _currentArchive = archivePath;
322 _cancelPending =
false;
323 _setExtracting(
true);
327 if (_extractionJob ==
nullptr) {
330 this, &QGCArchiveWatcher::_onExtractionProgress);
332 this, &QGCArchiveWatcher::_onExtractionFinished);
340 const QString decompressedPath = outputPath +
"/" + strippedName;
345void QGCArchiveWatcher::_setExtracting(
bool extracting)
347 if (_extracting != extracting) {
348 _extracting = extracting;
353void QGCArchiveWatcher::_setProgress(qreal progress)
355 if (!qFuzzyCompare(_progress,
progress)) {
Watches directories for archive files with optional auto-decompression.
QObject wrapper for async compression operations using QtConcurrent/QPromise.
#define QGC_LOGGING_CATEGORY(name, categoryStr)
Watches directories for new archive files and optionally auto-decompresses them.
int debounceDelay() const
void setRemoveAfterExtraction(bool remove)
QStringList watchedDirectories() const
QStringList scanDirectory(const QString &directoryPath) const
qreal progress() const
Get current extraction progress.
void setOutputDirectory(const QString &directory)
void cancelExtraction()
Cancel current extraction.
void autoDecompressChanged(bool autoDecompress)
Property change signals.
void setFilterMode(FilterMode mode)
void setDebounceDelay(int milliseconds)
bool watchDirectory(const QString &directoryPath)
bool unwatchDirectory(const QString &directoryPath)
void progressChanged(qreal progress)
void setAutoDecompress(bool enable)
FilterMode
Whether auto-decompression is enabled.
@ Both
Watch for both archives and compressed files.
@ Archives
Watch for archive files (.zip, .tar, .tar.gz, .7z)
@ Compressed
Watch for single-file compressed (.gz, .xz, .zst, .bz2, .lz4)
void extractionComplete(const QString &archivePath, const QString &outputPath, bool success, const QString &errorString)
void outputDirectoryChanged(const QString &directory)
void clear()
Stop watching all directories.
~QGCArchiveWatcher() override
void extractingChanged(bool extracting)
void archiveDetected(const QString &archivePath, QGCCompression::Format format)
QObject wrapper for compression operations with progress signals.
void progressChanged(qreal progress)
Emitted when progress changes (0.0 to 1.0)
void cancel()
Cancel current operation.
void finished(bool success)
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)
Callback-based file/directory watcher with debouncing support.
bool unwatchDirectory(const QString &directoryPath)
void clear()
Stop watching all files and directories.
void setDebounceDelay(int milliseconds)
void directoryChanged(const QString &path)
int debounceDelay() const
QStringList watchedDirectories() const
bool watchDirectory(const QString &directoryPath, ChangeCallback callback)
bool isCompressionFormat(Format format)
Check if format is a compression format (single stream)
QString formatName(Format format)
Get human-readable name for a format.
bool isArchiveFormat(Format format)
Check if format is an archive (contains multiple files)
QString strippedPath(const QString &filePath)
Format
Archive and compression format types (for decompression)
Format detectFormat(const QString &filePath, bool useContentFallback)