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
19FTPController::FTPController(QObject *parent)
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
176void FTPController::cancelActiveOperation()
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
417void FTPController::cancelExtraction()
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()
void downloadComplete(const QString &filePath, const QString &error)
void extractionComplete(const QString &outputDir, const QString &error)
void currentPathChanged()
void errorStringChanged()
void progressChanged()
void extractingChanged()
void deleteComplete(const QString &remotePath, const QString &error)
void extractionProgressChanged()
void activeOperationChanged()
void lastDownloadFileChanged()
void lastUploadTargetChanged()
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:84
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:29
void listDirectoryComplete(const QStringList &dirList, const QString &errorMsg)
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)