QGroundControl
Ground Control Station for MAVLink Drones
Loading...
Searching...
No Matches
FTPController.cc
Go to the documentation of this file.
1#include "FTPController.h"
2
3#include "QGCArchiveModel.h"
4#include "QGCCompression.h"
5#include "QGCCompressionJob.h"
6#include "FTPManager.h"
8#include "QGCFileHelper.h"
9#include "Vehicle.h"
10
11#include <QtCore/QDir>
12#include <QtCore/QFileInfo>
13
14#include <cmath>
15#include <limits>
16
17QGC_LOGGING_CATEGORY(FTPControllerLog, "Vehicle.FTPController")
18
20 : QObject(parent)
21 , _vehicle(MultiVehicleManager::instance()->activeVehicle())
22 , _ftpManager(_vehicle->ftpManager())
23 , _archiveModel(new QGCArchiveModel(this))
24{
25 connect(_ftpManager, &FTPManager::downloadComplete, this, &FTPController::_handleDownloadComplete);
27 connect(_ftpManager, &FTPManager::uploadComplete, this, &FTPController::_handleUploadComplete);
28 connect(_ftpManager, &FTPManager::uploadComplete, this, &FTPController::uploadComplete);
29 connect(_ftpManager, &FTPManager::listDirectoryComplete, this, &FTPController::_handleDirectoryComplete);
30 connect(_ftpManager, &FTPManager::commandProgress, this, &FTPController::_handleCommandProgress);
31 connect(_ftpManager, &FTPManager::deleteComplete, this, &FTPController::_handleDeleteComplete);
32 connect(_ftpManager, &FTPManager::deleteComplete, this, &FTPController::deleteComplete);
33}
34
35bool FTPController::listDirectory(const QString &uri, int componentId)
36{
37 if (_operation != Operation::None) {
38 _setErrorString(tr("Another FTP operation is in progress"));
39 return false;
40 }
41
42 const QString previousPath = _currentPath;
43
44 _resetDirectoryState();
45 _currentPath = uri;
46 emit currentPathChanged();
47 _setErrorString(QString());
48
49 _setBusy(true);
50 _setOperation(Operation::List);
51 _progress = 0.0F;
52 emit progressChanged();
53
54 const uint8_t compId = _componentIdForRequest(componentId);
55 if (!_ftpManager->listDirectory(compId, uri)) {
56 qCWarning(FTPControllerLog) << "Failed to start list operation for" << uri;
57 _setErrorString(tr("Failed to list %1").arg(uri));
58 _currentPath = previousPath;
59 emit currentPathChanged();
60 _setBusy(false);
61 _clearOperation();
62 return false;
63 }
64
65 return true;
66}
67
68bool FTPController::downloadFile(const QString &uri, const QString &localDir, const QString &fileName, int componentId)
69{
70 if (_operation != Operation::None) {
71 const QString error = tr("Another FTP operation is in progress");
72 _setErrorString(error);
73 emit downloadComplete(QString(), error);
74 return false;
75 }
76
78 const QString error = tr("Could not create directory %1").arg(localDir);
79 _setErrorString(error);
80 emit downloadComplete(QString(), error);
81 return false;
82 }
83
84 const QString absoluteLocalDir = QDir(localDir).absolutePath();
85
86 _lastDownloadFile.clear();
88 _setErrorString(QString());
89
90 _setBusy(true);
91 _setOperation(Operation::Download);
92 _progress = 0.0F;
93 emit progressChanged();
94
95 const uint8_t compId = _componentIdForRequest(componentId);
96 if (!_ftpManager->download(compId, uri, absoluteLocalDir, fileName)) {
97 qCWarning(FTPControllerLog) << "Failed to start download" << uri << absoluteLocalDir;
98 const QString error = tr("Failed to download %1").arg(uri);
99 _setErrorString(error);
100 _setBusy(false);
101 _clearOperation();
102 emit downloadComplete(QString(), error);
103 return false;
104 }
105
106 return true;
107}
108
109bool FTPController::uploadFile(const QString &localFile, const QString &uri, int componentId)
110{
111 if (_operation != Operation::None) {
112 const QString error = tr("Another FTP operation is in progress");
113 _setErrorString(error);
114 emit uploadComplete(QString(), error);
115 return false;
116 }
117
118 QFileInfo sourceInfo(localFile);
119 if (!sourceInfo.exists() || !sourceInfo.isFile()) {
120 const QString error = tr("File %1 does not exist").arg(localFile);
121 _setErrorString(error);
122 emit uploadComplete(QString(), error);
123 return false;
124 }
125
126 _lastUploadTarget.clear();
128 _setErrorString(QString());
129
130 _setBusy(true);
131 _setOperation(Operation::Upload);
132 _progress = 0.0F;
133 emit progressChanged();
134
135 const uint8_t compId = _componentIdForRequest(componentId);
136 if (!_ftpManager->upload(compId, uri, sourceInfo.absoluteFilePath())) {
137 qCWarning(FTPControllerLog) << "Failed to start upload" << sourceInfo.absoluteFilePath() << uri;
138 const QString error = tr("Failed to upload %1").arg(sourceInfo.fileName());
139 _setErrorString(error);
140 _setBusy(false);
141 _clearOperation();
142 emit uploadComplete(QString(), error);
143 return false;
144 }
145
146 return true;
147}
148
149bool FTPController::deleteFile(const QString &uri, int componentId)
150{
151 if (_operation != Operation::None) {
152 const QString error = tr("Another FTP operation is in progress");
153 _setErrorString(error);
154 emit deleteComplete(QString(), error);
155 return false;
156 }
157
158 _setErrorString(QString());
159 _setBusy(true);
160 _setOperation(Operation::Delete);
161
162 const uint8_t compId = _componentIdForRequest(componentId);
163 if (!_ftpManager->deleteFile(compId, uri)) {
164 qCWarning(FTPControllerLog) << "Failed to start delete" << uri;
165 const QString error = tr("Failed to delete %1").arg(uri);
166 _setErrorString(error);
167 _setBusy(false);
168 _clearOperation();
169 emit deleteComplete(QString(), error);
170 return false;
171 }
172
173 return true;
174}
175
177{
178 switch (_operation) {
179 case Operation::Download:
180 _ftpManager->cancelDownload();
181 break;
182 case Operation::Upload:
183 _ftpManager->cancelUpload();
184 break;
185 case Operation::List:
186 _ftpManager->cancelListDirectory();
187 break;
188 case Operation::Delete:
189 _ftpManager->cancelDelete();
190 break;
191 case Operation::None:
192 break;
193 }
194}
195
196void FTPController::_handleDownloadComplete(const QString &filePath, const QString &error)
197{
198 if (_operation != Operation::Download) {
199 return;
200 }
201
202 _setBusy(false);
203
204 if (error.isEmpty()) {
205 if (!filePath.isEmpty()) {
206 _lastDownloadFile = filePath;
207 _lastDownloadIsArchive = QGCCompression::isArchiveFile(filePath);
209 }
210 _setErrorString(QString());
211 } else {
212 _setErrorString(error);
213 _lastDownloadFile.clear();
214 _lastDownloadIsArchive = false;
216 }
217
218 _clearOperation();
219}
220
221void FTPController::_handleUploadComplete(const QString &remotePath, const QString &error)
222{
223 if (_operation != Operation::Upload) {
224 return;
225 }
226
227 _setBusy(false);
228
229 if (error.isEmpty()) {
230 _lastUploadTarget = remotePath;
232 _setErrorString(QString());
233 } else {
234 _setErrorString(error);
235 _lastUploadTarget.clear();
237 }
238
239 _clearOperation();
240}
241
242void FTPController::_handleDirectoryComplete(const QStringList &entries, const QString &error)
243{
244 if (_operation != Operation::List) {
245 return;
246 }
247
248 _setBusy(false);
249
250 if (error.isEmpty()) {
251 if (_directoryEntries != entries) {
252 _directoryEntries = entries;
254 }
255 _setErrorString(QString());
256 } else {
257 _setErrorString(error);
258 _resetDirectoryState();
259 }
260
261 _clearOperation();
262}
263
264void FTPController::_handleDeleteComplete(const QString &remotePath, const QString &error)
265{
266 Q_UNUSED(remotePath)
267
268 if (_operation != Operation::Delete) {
269 return;
270 }
271
272 _setBusy(false);
273
274 if (error.isEmpty()) {
275 _setErrorString(QString());
276 } else {
277 _setErrorString(error);
278 }
279
280 _clearOperation();
281}
282
283void FTPController::_handleCommandProgress(float value)
284{
285 if (_operation == Operation::Download || _operation == Operation::Upload) {
286 if (std::fabs(_progress - value) > std::numeric_limits<float>::epsilon()) {
287 _progress = value;
288 emit progressChanged();
289 }
290 }
291}
292
293void FTPController::_setBusy(bool busy)
294{
295 if (_busy == busy) {
296 return;
297 }
298
299 _busy = busy;
300 emit busyChanged();
301}
302
303void FTPController::_setOperation(Operation operation)
304{
305 if (_operation == operation) {
306 return;
307 }
308
309 _operation = operation;
311}
312
313void FTPController::_clearOperation()
314{
315 _setOperation(Operation::None);
316 if (std::fabs(_progress) > std::numeric_limits<float>::epsilon()) {
317 _progress = 0.0F;
318 emit progressChanged();
319 }
320}
321
322void FTPController::_setErrorString(const QString &error)
323{
324 if (_errorString == error) {
325 return;
326 }
327
328 _errorString = error;
329 emit errorStringChanged();
330}
331
332void FTPController::_resetDirectoryState()
333{
334 if (!_directoryEntries.isEmpty()) {
335 _directoryEntries.clear();
337 }
338}
339
340uint8_t FTPController::_componentIdForRequest(int componentId) const
341{
342 if (componentId <= 0 || componentId > std::numeric_limits<uint8_t>::max()) {
343 return MAV_COMP_ID_AUTOPILOT1;
344 }
345
346 return static_cast<uint8_t>(componentId);
347}
348
349bool FTPController::browseArchive(const QString &archivePath)
350{
351 if (archivePath.isEmpty()) {
352 _setErrorString(tr("No archive path specified"));
353 return false;
354 }
355
356 if (!QFile::exists(archivePath)) {
357 _setErrorString(tr("Archive file not found: %1").arg(archivePath));
358 return false;
359 }
360
361 if (!QGCCompression::isArchiveFile(archivePath)) {
362 _setErrorString(tr("Not a supported archive format: %1").arg(archivePath));
363 return false;
364 }
365
366 _archiveModel->setArchivePath(archivePath);
367 _setErrorString(QString());
368 return true;
369}
370
371bool FTPController::extractArchive(const QString &archivePath, const QString &outputDir)
372{
373 if (_extracting) {
374 _setErrorString(tr("Extraction already in progress"));
375 return false;
376 }
377
378 if (archivePath.isEmpty()) {
379 _setErrorString(tr("No archive path specified"));
380 return false;
381 }
382
383 if (!QFile::exists(archivePath)) {
384 _setErrorString(tr("Archive file not found: %1").arg(archivePath));
385 return false;
386 }
387
388 QString targetDir = outputDir;
389 if (targetDir.isEmpty()) {
390 targetDir = QFileInfo(archivePath).absolutePath();
391 }
392
393 if (!QGCFileHelper::ensureDirectoryExists(targetDir)) {
394 _setErrorString(tr("Could not create output directory: %1").arg(targetDir));
395 return false;
396 }
397
398 _extractionOutputDir = targetDir;
399
400 if (_extractionJob == nullptr) {
401 _extractionJob = new QGCCompressionJob(this);
402 connect(_extractionJob, &QGCCompressionJob::progressChanged,
403 this, &FTPController::_handleExtractionProgress);
404 connect(_extractionJob, &QGCCompressionJob::finished,
405 this, &FTPController::_handleExtractionFinished);
406 }
407
408 _extracting = true;
409 _extractionProgress = 0.0F;
410 emit extractingChanged();
412
413 _extractionJob->extractArchive(archivePath, targetDir);
414 return true;
415}
416
418{
419 if (_extractionJob != nullptr && _extracting) {
420 _extractionJob->cancel();
421 }
422}
423
424void FTPController::_handleExtractionProgress(qreal progress)
425{
426 const auto newProgress = static_cast<float>(progress);
427 if (std::fabs(_extractionProgress - newProgress) > std::numeric_limits<float>::epsilon()) {
428 _extractionProgress = newProgress;
430 }
431}
432
433void FTPController::_handleExtractionFinished(bool success)
434{
435 _extracting = false;
436 emit extractingChanged();
437
438 if (success) {
439 _setErrorString(QString());
440 emit extractionComplete(_extractionOutputDir, QString());
441 } else {
442 const QString error = _extractionJob != nullptr ? _extractionJob->errorString() : tr("Extraction failed");
443 _setErrorString(error);
444 emit extractionComplete(QString(), error);
445 }
446
447 _extractionProgress = 0.0F;
449}
QObject wrapper for async compression operations using QtConcurrent/QPromise.
Error error
#define QGC_LOGGING_CATEGORY(name, categoryStr)
QML-facing controller for MAVLink FTP operations.
void uploadComplete(const QString &remotePath, const QString &error)
void directoryEntriesChanged()
float progress() const
void downloadComplete(const QString &filePath, const QString &error)
void extractionComplete(const QString &outputDir, const QString &error)
Q_INVOKABLE void cancelActiveOperation()
Q_INVOKABLE bool downloadFile(const QString &uri, const QString &localDir, const QString &fileName=QString(), int componentId=MAV_COMP_ID_AUTOPILOT1)
void currentPathChanged()
void errorStringChanged()
bool busy() const
Q_INVOKABLE bool extractArchive(const QString &archivePath, const QString &outputDir=QString())
Q_INVOKABLE bool uploadFile(const QString &localFile, const QString &uri, int componentId=MAV_COMP_ID_AUTOPILOT1)
Q_INVOKABLE void cancelExtraction()
Cancel an in-progress extraction.
void progressChanged()
void extractingChanged()
void deleteComplete(const QString &remotePath, const QString &error)
void extractionProgressChanged()
Q_INVOKABLE bool browseArchive(const QString &archivePath)
void activeOperationChanged()
void lastDownloadFileChanged()
void lastUploadTargetChanged()
Q_INVOKABLE bool deleteFile(const QString &uri, int componentId=MAV_COMP_ID_AUTOPILOT1)
Q_INVOKABLE bool listDirectory(const QString &uri, int componentId=MAV_COMP_ID_AUTOPILOT1)
void busyChanged()
void cancelDownload()
bool listDirectory(uint8_t fromCompId, const QString &fromURI)
bool deleteFile(uint8_t fromCompId, const QString &fromURI)
bool upload(uint8_t toCompId, const QString &toURI, const QString &fromFile)
Definition FTPManager.cc:85
void uploadComplete(const QString &file, const QString &errorMsg)
void cancelUpload()
void commandProgress(float value)
void downloadComplete(const QString &file, const QString &errorMsg)
void cancelListDirectory()
void deleteComplete(const QString &file, const QString &errorMsg)
void cancelDelete()
bool download(uint8_t fromCompId, const QString &fromURI, const QString &toDir, const QString &fileName="", bool checksize=true)
Definition FTPManager.cc:30
void listDirectoryComplete(const QStringList &dirList, const QString &errorMsg)
List model for archive contents, suitable for QML ListView binding.
void setArchivePath(const QString &path)
QObject wrapper for compression operations with progress signals.
QString errorString() const
void progressChanged(qreal progress)
Emitted when progress changes (0.0 to 1.0)
void cancel()
Cancel current operation.
void finished(bool success)
void extractArchive(const QString &archivePath, const QString &outputDirectoryPath, qint64 maxBytes=0)
bool isArchiveFile(const QString &filePath)
Check if file path indicates an archive file (.zip, .tar, .tar.gz, etc.)
bool ensureDirectoryExists(const QString &path)