FileManager.cc 24.3 KB
Newer Older
1
2
3
4
5
6
7
8
/****************************************************************************
 *
 *   (c) 2009-2016 QGROUNDCONTROL PROJECT <http://www.qgroundcontrol.org>
 *
 * QGroundControl is licensed according to the terms in the file
 * COPYING.md in the root of the source code directory.
 *
 ****************************************************************************/
Julian Oes's avatar
Julian Oes committed
9

Don Gagne's avatar
Don Gagne committed
10

11
#include "FileManager.h"
12
#include "QGC.h"
Lorenz Meier's avatar
Lorenz Meier committed
13
#include "MAVLinkProtocol.h"
14
#include "MainWindow.h"
15
#include "Vehicle.h"
16
#include "QGCApplication.h"
Lorenz Meier's avatar
Lorenz Meier committed
17
18
19

#include <QFile>
#include <QDir>
none's avatar
none committed
20
#include <string>
Lorenz Meier's avatar
Lorenz Meier committed
21

22
QGC_LOGGING_CATEGORY(FileManagerLog, "FileManagerLog")
23

24
25
26
27
28
29
30
31
FileManager::FileManager(QObject* parent, Vehicle* vehicle)
    : QObject(parent)
    , _currentOperation(kCOIdle)
    , _vehicle(vehicle)
    , _dedicatedLink(NULL)
    , _lastOutgoingSeqNumber(0)
    , _activeSession(0)
    , _systemIdQGC(0)
Lorenz Meier's avatar
Lorenz Meier committed
32
{
33
    connect(&_ackTimer, &QTimer::timeout, this, &FileManager::_ackTimeout);
34
    
35
    _systemIdServer = _vehicle->id();
Don Gagne's avatar
Don Gagne committed
36
37
    
    // Make sure we don't have bad structure packing
38
    Q_ASSERT(sizeof(RequestHeader) == 12);
Lorenz Meier's avatar
Lorenz Meier committed
39
40
}

Don Gagne's avatar
Don Gagne committed
41
/// Respond to the Ack associated with the Open command with the next read command.
42
void FileManager::_openAckResponse(Request* openAck)
43
{
Don Gagne's avatar
Don Gagne committed
44
45
46
    qCDebug(FileManagerLog) << QString("_openAckResponse: _currentOperation(%1) _readFileLength(%2)").arg(_currentOperation).arg(openAck->openFileLength);
    
	Q_ASSERT(_currentOperation == kCOOpenRead || _currentOperation == kCOOpenBurst);
47
	_currentOperation = _currentOperation == kCOOpenRead ? kCORead : kCOBurst;
48
    _activeSession = openAck->hdr.session;
Don Gagne's avatar
Don Gagne committed
49
50
51
    
    // File length comes back in data
    Q_ASSERT(openAck->hdr.size == sizeof(uint32_t));
Don Gagne's avatar
Don Gagne committed
52
    _downloadFileSize = openAck->openFileLength;
53
54
    
    // Start the sequence of read commands
Julian Oes's avatar
Julian Oes committed
55

Don Gagne's avatar
Don Gagne committed
56
    _downloadOffset = 0;            // Start reading at beginning of file
57
    _readFileAccumulator.clear();   // Start with an empty file
Julian Oes's avatar
Julian Oes committed
58

59
60
    Request request;
    request.hdr.session = _activeSession;
61
62
63
	Q_ASSERT(_currentOperation == kCORead || _currentOperation == kCOBurst);
	request.hdr.opcode = _currentOperation == kCORead ? kCmdReadFile : kCmdBurstReadFile;
    request.hdr.offset = _downloadOffset;
64
    request.hdr.size = sizeof(request.data);
Julian Oes's avatar
Julian Oes committed
65

66
67
68
    _sendRequest(&request);
}

Don Gagne's avatar
Don Gagne committed
69
/// Closes out a download session by writing the file and doing cleanup.
70
///     @param success true: successful download completion, false: error during download
71
void FileManager::_closeDownloadSession(bool success)
72
{
Don Gagne's avatar
Don Gagne committed
73
74
75
76
    qCDebug(FileManagerLog) << QString("_closeDownloadSession: success(%1)").arg(success);
    
    _currentOperation = kCOIdle;
    
77
78
    if (success) {
        QString downloadFilePath = _readFileDownloadDir.absoluteFilePath(_readFileDownloadFilename);
Julian Oes's avatar
Julian Oes committed
79

80
81
82
83
84
        QFile file(downloadFilePath);
        if (!file.open(QIODevice::WriteOnly | QIODevice::Truncate)) {
            _emitErrorMessage(tr("Unable to open local file for writing (%1)").arg(downloadFilePath));
            return;
        }
Julian Oes's avatar
Julian Oes committed
85

86
87
88
89
90
91
92
        qint64 bytesWritten = file.write((const char *)_readFileAccumulator, _readFileAccumulator.length());
        if (bytesWritten != _readFileAccumulator.length()) {
            file.close();
            _emitErrorMessage(tr("Unable to write data to local file (%1)").arg(downloadFilePath));
            return;
        }
        file.close();
Julian Oes's avatar
Julian Oes committed
93

Don Gagne's avatar
Don Gagne committed
94
        emit commandComplete();
95
    }
Don Gagne's avatar
Don Gagne committed
96
    
Don Gagne's avatar
Don Gagne committed
97
98
    _readFileAccumulator.clear();
    
99
    // Close the open session
Don Gagne's avatar
Don Gagne committed
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
    _sendResetCommand();
}

/// Closes out an upload session doing cleanup.
///     @param success true: successful upload completion, false: error during download
void FileManager::_closeUploadSession(bool success)
{
    qCDebug(FileManagerLog) << QString("_closeUploadSession: success(%1)").arg(success);
    
    _currentOperation = kCOIdle;
    _writeFileAccumulator.clear();
    _writeFileSize = 0;
    
    if (success) {
        emit commandComplete();
    }
    
    // Close the open session
    _sendResetCommand();
119
120
}

121
122
123
/// Respond to the Ack associated with the Read or Stream commands.
///		@param readFile: true: read file, false: stream file
void FileManager::_downloadAckResponse(Request* readAck, bool readFile)
124
125
{
    if (readAck->hdr.session != _activeSession) {
Don Gagne's avatar
Don Gagne committed
126
        _closeDownloadSession(false /* failure */);
127
        _emitErrorMessage(tr("Download: Incorrect session returned"));
128
129
        return;
    }
Julian Oes's avatar
Julian Oes committed
130

131
    if (readAck->hdr.offset != _downloadOffset) {
Don Gagne's avatar
Don Gagne committed
132
        _closeDownloadSession(false /* failure */);
133
        _emitErrorMessage(tr("Download: Offset returned (%1) differs from offset requested/expected (%2)").arg(readAck->hdr.offset).arg(_downloadOffset));
134
135
        return;
    }
136
137
    
    qCDebug(FileManagerLog) << QString("_downloadAckResponse: offset(%1) size(%2) burstComplete(%3)").arg(readAck->hdr.offset).arg(readAck->hdr.size).arg(readAck->hdr.burstComplete);
138

139
	_downloadOffset += readAck->hdr.size;
140
    _readFileAccumulator.append((const char*)readAck->data, readAck->hdr.size);
Don Gagne's avatar
Don Gagne committed
141
142
143
144
    
    if (_downloadFileSize != 0) {
        emit commandProgress(100 * ((float)_readFileAccumulator.length() / (float)_downloadFileSize));
    }
Julian Oes's avatar
Julian Oes committed
145

Don Gagne's avatar
Don Gagne committed
146
147
    if (readFile || readAck->hdr.burstComplete) {
        // Possibly still more data to read, send next read request
148

Don Gagne's avatar
Don Gagne committed
149
150
151
152
153
        Request request;
        request.hdr.session = _activeSession;
        request.hdr.opcode = readFile ? kCmdReadFile : kCmdBurstReadFile;
        request.hdr.offset = _downloadOffset;
        request.hdr.size = 0;
154

Don Gagne's avatar
Don Gagne committed
155
156
157
158
        _sendRequest(&request);
    } else if (!readFile) {
        // Streaming, so next ack should come automatically
        _setupAckTimeout();
159
160
161
162
    }
}

/// @brief Respond to the Ack associated with the List command.
163
void FileManager::_listAckResponse(Request* listAck)
164
165
166
167
168
169
{
    if (listAck->hdr.offset != _listOffset) {
        _currentOperation = kCOIdle;
        _emitErrorMessage(tr("List: Offset returned (%1) differs from offset requested (%2)").arg(listAck->hdr.offset).arg(_listOffset));
        return;
    }
Julian Oes's avatar
Julian Oes committed
170

171
172
173
    uint8_t offset = 0;
    uint8_t cListEntries = 0;
    uint8_t cBytes = listAck->hdr.size;
Julian Oes's avatar
Julian Oes committed
174

175
176
177
    // parse filenames out of the buffer
    while (offset < cBytes) {
        const char * ptr = ((const char *)listAck->data) + offset;
Julian Oes's avatar
Julian Oes committed
178

179
180
        // get the length of the name
        uint8_t cBytesLeft = cBytes - offset;
Don Gagne's avatar
Don Gagne committed
181
        uint8_t nlen = static_cast<uint8_t>(strnlen(ptr, cBytesLeft));
182
        if ((*ptr == 'S' && nlen > 1) || (*ptr != 'S' && nlen < 2)) {
183
184
185
186
187
188
189
190
            _currentOperation = kCOIdle;
            _emitErrorMessage(tr("Incorrectly formed list entry: '%1'").arg(ptr));
            return;
        } else if (nlen == cBytesLeft) {
            _currentOperation = kCOIdle;
            _emitErrorMessage(tr("Missing NULL termination in list entry"));
            return;
        }
191

192
        // Returned names are prepended with D for directory, F for file, S for skip
193
194
        if (*ptr == 'F' || *ptr == 'D') {
            // put it in the view
195
            _emitListEntry(ptr);
196
197
        } else if (*ptr == 'S') {
            // do nothing
198
199
        } else {
            qDebug() << "unknown entry" << ptr;
200
        }
Julian Oes's avatar
Julian Oes committed
201

202
203
        // account for the name + NUL
        offset += nlen + 1;
Julian Oes's avatar
Julian Oes committed
204

205
206
207
        cListEntries++;
    }

Don Gagne's avatar
Don Gagne committed
208
    if (listAck->hdr.size == 0 || cListEntries == 0) {
209
210
211
        // Directory is empty, we're done
        Q_ASSERT(listAck->hdr.opcode == kRspAck);
        _currentOperation = kCOIdle;
Don Gagne's avatar
Don Gagne committed
212
        emit commandComplete();
213
214
    } else {
        // Possibly more entries to come, need to keep trying till we get EOF
215
216
217
        _currentOperation = kCOList;
        _listOffset += cListEntries;
        _sendListCommand();
218
219
220
    }
}

221
/// @brief Respond to the Ack associated with the create command.
222
void FileManager::_createAckResponse(Request* createAck)
223
{
Don Gagne's avatar
Don Gagne committed
224
225
    qCDebug(FileManagerLog) << "_createAckResponse";
    
226
227
228
    _currentOperation = kCOWrite;
    _activeSession = createAck->hdr.session;

Don Gagne's avatar
Don Gagne committed
229
    // Start the sequence of write commands from the beginning of the file
230

Don Gagne's avatar
Don Gagne committed
231
    _writeOffset = 0;
232
    _writeSize = 0;
Don Gagne's avatar
Don Gagne committed
233
    
234
235
236
237
    _writeFileDatablock();
}

/// @brief Respond to the Ack associated with the write command.
238
void FileManager::_writeAckResponse(Request* writeAck)
239
{
240
    if(_writeOffset + _writeSize >= _writeFileSize){
Don Gagne's avatar
Don Gagne committed
241
        _closeUploadSession(true /* success */);
Beat Küng's avatar
Beat Küng committed
242
        return;
243
244
    }

245
    if (writeAck->hdr.session != _activeSession) {
Don Gagne's avatar
Don Gagne committed
246
        _closeUploadSession(false /* failure */);
247
248
249
250
251
        _emitErrorMessage(tr("Write: Incorrect session returned"));
        return;
    }

    if (writeAck->hdr.offset != _writeOffset) {
Don Gagne's avatar
Don Gagne committed
252
        _closeUploadSession(false /* failure */);
253
254
255
256
        _emitErrorMessage(tr("Write: Offset returned (%1) differs from offset requested (%2)").arg(writeAck->hdr.offset).arg(_writeOffset));
        return;
    }

257
    if (writeAck->hdr.size != sizeof(uint32_t)) {
Don Gagne's avatar
Don Gagne committed
258
        _closeUploadSession(false /* failure */);
259
        _emitErrorMessage(tr("Write: Returned invalid size of write size data"));
260
261
262
        return;
    }

263

Don Gagne's avatar
Don Gagne committed
264
265
    if( writeAck->writeFileLength !=_writeSize) {
        _closeUploadSession(false /* failure */);
266
        _emitErrorMessage(tr("Write: Size returned (%1) differs from size requested (%2)").arg(writeAck->writeFileLength).arg(_writeSize));
267
268
269
270
271
272
273
        return;
    }

    _writeFileDatablock();
}

/// @brief Send next write file data block.
274
void FileManager::_writeFileDatablock(void)
275
{
Don Gagne's avatar
Don Gagne committed
276
277
    if (_writeOffset + _writeSize >= _writeFileSize){
        _closeUploadSession(true /* success */);
278
        return;
279
280
    }

281
282
    _writeOffset += _writeSize;

283
284
285
286
287
    Request request;
    request.hdr.session = _activeSession;
    request.hdr.opcode = kCmdWriteFile;
    request.hdr.offset = _writeOffset;

288
    if(_writeFileSize -_writeOffset > sizeof(request.data) )
289
290
        _writeSize = sizeof(request.data);
    else
291
        _writeSize = _writeFileSize - _writeOffset;
292
293
294

    request.hdr.size = _writeSize;

295
    memcpy(request.data, &_writeFileAccumulator.data()[_writeOffset], _writeSize);
296
297
298
299

    _sendRequest(&request);
}

300
void FileManager::receiveMessage(mavlink_message_t message)
Lorenz Meier's avatar
Lorenz Meier committed
301
{
302
303
    // receiveMessage is signalled will all mavlink messages so we need to filter everything else out but ours.
    if (message.msgid != MAVLINK_MSG_ID_FILE_TRANSFER_PROTOCOL) {
304
305
        return;
    }
306
	
307
308
    mavlink_file_transfer_protocol_t data;
    mavlink_msg_file_transfer_protocol_decode(&message, &data);
309
	
310
311
    // Make sure we are the target system
    if (data.target_system != _systemIdQGC) {
312
        qDebug() << "Received MAVLINK_MSG_ID_FILE_TRANSFER_PROTOCOL with incorrect target_system:" <<  data.target_system << "expected:" << _systemIdQGC;
Don Gagne's avatar
Don Gagne committed
313
314
        return;
    }
315
316
317
    
    Request* request = (Request*)&data.payload[0];
    
Don Gagne's avatar
Don Gagne committed
318
319
    _clearAckTimeout();
    
320
321
	qCDebug(FileManagerLog) << "receiveMessage" << request->hdr.opcode;
	
322
    uint16_t incomingSeqNumber = request->hdr.seqNumber;
Don Gagne's avatar
Don Gagne committed
323
324
325
326
    
    // Make sure we have a good sequence number
    uint16_t expectedSeqNumber = _lastOutgoingSeqNumber + 1;
    if (incomingSeqNumber != expectedSeqNumber) {
Don Gagne's avatar
Don Gagne committed
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
        switch (_currentOperation) {
            case kCOBurst:
            case kCORead:
                _closeDownloadSession(false /* failure */);
                break;
            
            case kCOWrite:
                _closeUploadSession(false /* failure */);
                break;
                
            case kCOOpenRead:
            case kCOOpenBurst:
            case kCOCreate:
                // We could have an open session hanging around
                _currentOperation = kCOIdle;
                _sendResetCommand();
                break;
                
            default:
                // Don't need to do anything special
                _currentOperation = kCOIdle;
                break;
        }
        
Don Gagne's avatar
Don Gagne committed
351
352
353
354
355
356
        _emitErrorMessage(tr("Bad sequence number on received message: expected(%1) received(%2)").arg(expectedSeqNumber).arg(incomingSeqNumber));
        return;
    }
    
    // Move past the incoming sequence number for next request
    _lastOutgoingSeqNumber = incomingSeqNumber;
Julian Oes's avatar
Julian Oes committed
357

Don Gagne's avatar
Don Gagne committed
358
    if (request->hdr.opcode == kRspAck) {
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
        switch (request->hdr.req_opcode) {
			case kCmdListDirectory:
				_listAckResponse(request);
				break;
				
			case kCmdOpenFileRO:
            case kCmdOpenFileWO:
				_openAckResponse(request);
				break;
				
			case kCmdReadFile:
				_downloadAckResponse(request, true /* read file */);
				break;
				
			case kCmdBurstReadFile:
				_downloadAckResponse(request, false /* stream file */);
				break;
				
            case kCmdCreateFile:
378
379
                _createAckResponse(request);
                break;
380
381
                
            case kCmdWriteFile:
382
383
                _writeAckResponse(request);
                break;
384
385
386
387
388
389
                
			default:
				// Ack back from operation which does not require additional work
				_currentOperation = kCOIdle;
				break;
		}
Don Gagne's avatar
Don Gagne committed
390
    } else if (request->hdr.opcode == kRspNak) {
391
        uint8_t errorCode = request->data[0];
Julian Oes's avatar
Julian Oes committed
392

393
394
395
        // Nak's normally have 1 byte of data for error code, except for kErrFailErrno which has additional byte for errno
        Q_ASSERT((errorCode == kErrFailErrno && request->hdr.size == 2) || request->hdr.size == 1);
        
Don Gagne's avatar
Don Gagne committed
396
        _currentOperation = kCOIdle;
Julian Oes's avatar
Julian Oes committed
397

398
399
        if (request->hdr.req_opcode == kCmdListDirectory && errorCode == kErrEOF) {
            // This is not an error, just the end of the list loop
Don Gagne's avatar
Don Gagne committed
400
            emit commandComplete();
401
            return;
402
403
404
        } else if ((request->hdr.req_opcode == kCmdReadFile || request->hdr.req_opcode == kCmdBurstReadFile) && errorCode == kErrEOF) {
            // This is not an error, just the end of the download loop
            _closeDownloadSession(true /* success */);
405
            return;
406
        } else if (request->hdr.req_opcode == kCmdCreateFile) {
407
408
            _emitErrorMessage(tr("Nak received creating file, error: %1").arg(errorString(request->data[0])));
            return;
409
410
        } else {
            // Generic Nak handling
411
412
413
            if (request->hdr.req_opcode == kCmdReadFile || request->hdr.req_opcode == kCmdBurstReadFile) {
                // Nak error during download loop, download failed
                _closeDownloadSession(false /* failure */);
Don Gagne's avatar
Don Gagne committed
414
415
416
            } else if (request->hdr.req_opcode == kCmdWriteFile) {
                // Nak error during upload loop, upload failed
                _closeUploadSession(false /* failure */);
417
418
419
            }
            _emitErrorMessage(tr("Nak received, error: %1").arg(errorString(request->data[0])));
        }
Don Gagne's avatar
Don Gagne committed
420
421
422
    } else {
        // Note that we don't change our operation state. If something goes wrong beyond this, the operation
        // will time out.
423
        _emitErrorMessage(tr("Unknown opcode returned from server: %1").arg(request->hdr.opcode));
Lorenz Meier's avatar
Lorenz Meier committed
424
425
426
    }
}

427
void FileManager::listDirectory(const QString& dirPath)
Lorenz Meier's avatar
Lorenz Meier committed
428
{
Don Gagne's avatar
Don Gagne committed
429
430
431
    if (_currentOperation != kCOIdle) {
        _emitErrorMessage(tr("Command not sent. Waiting for previous command to complete."));
        return;
none's avatar
none committed
432
433
    }

434
435
436
437
438
439
    _dedicatedLink = _vehicle->priorityLink();
    if (!_dedicatedLink) {
        _emitErrorMessage(tr("Command not sent. No Vehicle links."));
        return;
    }

none's avatar
none committed
440
    // initialise the lister
441
    _listPath = dirPath;
Don Gagne's avatar
Don Gagne committed
442
443
    _listOffset = 0;
    _currentOperation = kCOList;
none's avatar
none committed
444
445

    // and send the initial request
446
    _sendListCommand();
none's avatar
none committed
447
448
}

449
void FileManager::_fillRequestWithString(Request* request, const QString& str)
450
451
{
    strncpy((char *)&request->data[0], str.toStdString().c_str(), sizeof(request->data));
Don Gagne's avatar
Don Gagne committed
452
    request->hdr.size = static_cast<uint8_t>(strnlen((const char *)&request->data[0], sizeof(request->data)));
453
454
}

455
void FileManager::_sendListCommand(void)
none's avatar
none committed
456
{
Don Gagne's avatar
Don Gagne committed
457
    Request request;
none's avatar
none committed
458

Don Gagne's avatar
Don Gagne committed
459
    request.hdr.session = 0;
460
    request.hdr.opcode = kCmdListDirectory;
Don Gagne's avatar
Don Gagne committed
461
    request.hdr.offset = _listOffset;
Don Gagne's avatar
Don Gagne committed
462
    request.hdr.size = 0;
Julian Oes's avatar
Julian Oes committed
463

464
    _fillRequestWithString(&request, _listPath);
Julian Oes's avatar
Julian Oes committed
465

466
467
    qCDebug(FileManagerLog) << "listDirectory: path:" << _listPath << "offset:" <<  _listOffset;
    
Don Gagne's avatar
Don Gagne committed
468
    _sendRequest(&request);
Lorenz Meier's avatar
Lorenz Meier committed
469
470
}

471
void FileManager::downloadPath(const QString& from, const QDir& downloadDir)
Lorenz Meier's avatar
Lorenz Meier committed
472
{
Don Gagne's avatar
Don Gagne committed
473
474
475
476
    if (_currentOperation != kCOIdle) {
        _emitErrorMessage(tr("Command not sent. Waiting for previous command to complete."));
        return;
    }
477
478
479
480
481
482

    _dedicatedLink = _vehicle->priorityLink();
    if (!_dedicatedLink) {
        _emitErrorMessage(tr("Command not sent. No Vehicle links."));
        return;
    }
Don Gagne's avatar
Don Gagne committed
483
    
484
485
486
	qCDebug(FileManagerLog) << "downloadPath from:" << from << "to:" << downloadDir;
	_downloadWorker(from, downloadDir, true /* read file */);
}
Julian Oes's avatar
Julian Oes committed
487

488
489
void FileManager::streamPath(const QString& from, const QDir& downloadDir)
{
Don Gagne's avatar
Don Gagne committed
490
491
492
493
    if (_currentOperation != kCOIdle) {
        _emitErrorMessage(tr("Command not sent. Waiting for previous command to complete."));
        return;
    }
494
495
496
497
498
499

    _dedicatedLink = _vehicle->priorityLink();
    if (!_dedicatedLink) {
        _emitErrorMessage(tr("Command not sent. No Vehicle links."));
        return;
    }
Don Gagne's avatar
Don Gagne committed
500
    
501
502
503
	qCDebug(FileManagerLog) << "streamPath from:" << from << "to:" << downloadDir;
	_downloadWorker(from, downloadDir, false /* stream file */);
}
Lorenz Meier's avatar
Lorenz Meier committed
504

505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
void FileManager::_downloadWorker(const QString& from, const QDir& downloadDir, bool readFile)
{
	if (from.isEmpty()) {
		return;
	}
	
	_readFileDownloadDir.setPath(downloadDir.absolutePath());
	
	// We need to strip off the file name from the fully qualified path. We can't use the usual QDir
	// routines because this path does not exist locally.
	int i;
	for (i=from.size()-1; i>=0; i--) {
		if (from[i] == '/') {
			break;
		}
	}
	i++; // move past slash
	_readFileDownloadFilename = from.right(from.size() - i);
	
Don Gagne's avatar
Don Gagne committed
524
	_currentOperation = readFile ? kCOOpenRead : kCOOpenBurst;
525
526
527
528
529
530
531
532
	
	Request request;
	request.hdr.session = 0;
	request.hdr.opcode = kCmdOpenFileRO;
	request.hdr.offset = 0;
	request.hdr.size = 0;
	_fillRequestWithString(&request, from);
	_sendRequest(&request);
533
}
none's avatar
none committed
534

535
536
537
/// @brief Uploads the specified file.
///     @param toPath File in UAS to upload to, fully qualified path
///     @param uploadFile Local file to upload from
538
void FileManager::uploadPath(const QString& toPath, const QFileInfo& uploadFile)
539
{
540
541
542
543
544
    if(_currentOperation != kCOIdle){
        _emitErrorMessage(tr("UAS File manager busy.  Try again later"));
        return;
    }

545
546
547
548
549
550
    _dedicatedLink = _vehicle->priorityLink();
    if (!_dedicatedLink) {
        _emitErrorMessage(tr("Command not sent. No Vehicle links."));
        return;
    }

551
552
553
554
    if (toPath.isEmpty()) {
        return;
    }

Don Gagne's avatar
Don Gagne committed
555
    if (!uploadFile.isReadable()){
556
        _emitErrorMessage(tr("File (%1) is not readable for upload").arg(uploadFile.path()));
Don Gagne's avatar
Don Gagne committed
557
        return;
558
559
560
    }

    QFile file(uploadFile.absoluteFilePath());
561
    if (!file.open(QIODevice::ReadOnly)) {
562
563
564
565
566
            _emitErrorMessage(tr("Unable to open local file for upload (%1)").arg(uploadFile.absoluteFilePath()));
            return;
        }

    _writeFileAccumulator = file.readAll();
567
    _writeFileSize = _writeFileAccumulator.size();
568

569
570
571
    file.close();

    if (_writeFileAccumulator.size() == 0) {
572
573
574
575
576
577
578
579
580
581
        _emitErrorMessage(tr("Unable to read data from local file (%1)").arg(uploadFile.absoluteFilePath()));
        return;
    }

    _currentOperation = kCOCreate;

    Request request;
    request.hdr.session = 0;
    request.hdr.opcode = kCmdCreateFile;
    request.hdr.offset = 0;
582
    request.hdr.size = 0;
583
    _fillRequestWithString(&request, toPath + "/" + uploadFile.fileName());
584
585
586
    _sendRequest(&request);
}

587
QString FileManager::errorString(uint8_t errorCode)
none's avatar
none committed
588
589
{
    switch(errorCode) {
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
        case kErrNone:
            return QString("no error");
        case kErrFail:
            return QString("unknown error");
        case kErrEOF:
            return QString("read beyond end of file");
        case kErrUnknownCommand:
            return QString("unknown command");
        case kErrFailErrno:
            return QString("command failed");
        case kErrInvalidDataSize:
            return QString("invalid data size");
        case kErrInvalidSession:
            return QString("invalid session");
        case kErrNoSessionsAvailable:
nopeppermint's avatar
nopeppermint committed
605
            return QString("no sessions available");
606
607
608
609
        case kErrFailFileExists:
            return QString("File already exists on target");
        case kErrFailFileProtected:
            return QString("File is write protected");
610
611
        default:
            return QString("unknown error code");
none's avatar
none committed
612
613
    }
}
Don Gagne's avatar
Don Gagne committed
614
615
616
617
618

/// @brief Sends a command which only requires an opcode and no additional data
///     @param opcode Opcode to send
///     @param newOpState State to put state machine into
/// @return TRUE: command sent, FALSE: command not sent, waiting for previous command to finish
619
bool FileManager::_sendOpcodeOnlyCmd(uint8_t opcode, OperationState newOpState)
Don Gagne's avatar
Don Gagne committed
620
621
622
623
624
{
    if (_currentOperation != kCOIdle) {
        // Can't have multiple commands in play at the same time
        return false;
    }
Julian Oes's avatar
Julian Oes committed
625

Don Gagne's avatar
Don Gagne committed
626
627
628
629
630
    Request request;
    request.hdr.session = 0;
    request.hdr.opcode = opcode;
    request.hdr.offset = 0;
    request.hdr.size = 0;
Julian Oes's avatar
Julian Oes committed
631

Don Gagne's avatar
Don Gagne committed
632
    _currentOperation = newOpState;
Julian Oes's avatar
Julian Oes committed
633

Don Gagne's avatar
Don Gagne committed
634
    _sendRequest(&request);
Julian Oes's avatar
Julian Oes committed
635

636
    return true;
Don Gagne's avatar
Don Gagne committed
637
638
639
}

/// @brief Starts the ack timeout timer
640
void FileManager::_setupAckTimeout(void)
Don Gagne's avatar
Don Gagne committed
641
{
642
643
	qCDebug(FileManagerLog) << "_setupAckTimeout";

Don Gagne's avatar
Don Gagne committed
644
    Q_ASSERT(!_ackTimer.isActive());
Julian Oes's avatar
Julian Oes committed
645

Don Gagne's avatar
Don Gagne committed
646
    _ackTimer.setSingleShot(true);
Don Gagne's avatar
Don Gagne committed
647
    _ackTimer.start(ackTimerTimeoutMsecs);
Don Gagne's avatar
Don Gagne committed
648
649
650
}

/// @brief Clears the ack timeout timer
651
void FileManager::_clearAckTimeout(void)
Don Gagne's avatar
Don Gagne committed
652
{
653
	qCDebug(FileManagerLog) << "_clearAckTimeout";
Don Gagne's avatar
Don Gagne committed
654
655
656
657
    _ackTimer.stop();
}

/// @brief Called when ack timeout timer fires
658
void FileManager::_ackTimeout(void)
Don Gagne's avatar
Don Gagne committed
659
{
660
661
    qCDebug(FileManagerLog) << "_ackTimeout";
    
Don Gagne's avatar
Don Gagne committed
662
663
664
    // Make sure to set _currentOperation state before emitting error message. Code may respond
    // to error message signal by sending another command, which will fail if state is not back
    // to idle. FileView UI works this way with the List command.
665
666
667

    switch (_currentOperation) {
        case kCORead:
668
669
        case kCOBurst:
            _closeDownloadSession(false /* failure */);
Don Gagne's avatar
Don Gagne committed
670
671
672
673
674
675
676
677
            _emitErrorMessage(tr("Timeout waiting for ack: Download failed"));
            break;
            
        case kCOOpenRead:
        case kCOOpenBurst:
            _currentOperation = kCOIdle;
            _emitErrorMessage(tr("Timeout waiting for ack: Download failed"));
            _sendResetCommand();
678
            break;
679
            
680
        case kCOCreate:
Don Gagne's avatar
Don Gagne committed
681
682
683
            _currentOperation = kCOIdle;
            _emitErrorMessage(tr("Timeout waiting for ack: Upload failed"));
            _sendResetCommand();
684
685
            break;
            
686
        case kCOWrite:
Don Gagne's avatar
Don Gagne committed
687
688
            _closeUploadSession(false /* failure */);
            _emitErrorMessage(tr("Timeout waiting for ack: Upload failed"));
689
            break;
690
			
691
692
        default:
            _currentOperation = kCOIdle;
Don Gagne's avatar
Don Gagne committed
693
            _emitErrorMessage(QString("Timeout waiting for ack: Command failed (%1)").arg(_currentOperation));
694
695
696
697
            break;
    }
}

Don Gagne's avatar
Don Gagne committed
698
void FileManager::_sendResetCommand(void)
699
700
{
    Request request;
Don Gagne's avatar
Don Gagne committed
701
    request.hdr.opcode = kCmdResetSessions;
Don Gagne's avatar
Don Gagne committed
702
    request.hdr.size = 0;
703
    _sendRequest(&request);
Don Gagne's avatar
Don Gagne committed
704
705
}

706
void FileManager::_emitErrorMessage(const QString& msg)
Don Gagne's avatar
Don Gagne committed
707
{
708
	qCDebug(FileManagerLog) << "Error:" << msg;
Don Gagne's avatar
Don Gagne committed
709
    emit commandError(msg);
Don Gagne's avatar
Don Gagne committed
710
711
}

712
void FileManager::_emitListEntry(const QString& entry)
713
{
714
    qCDebug(FileManagerLog) << "_emitListEntry" << entry;
715
    emit listEntry(entry);
716
717
}

Don Gagne's avatar
Don Gagne committed
718
/// @brief Sends the specified Request out to the UAS.
719
void FileManager::_sendRequest(Request* request)
Don Gagne's avatar
Don Gagne committed
720
{
721

Don Gagne's avatar
Don Gagne committed
722
    mavlink_message_t message;
Julian Oes's avatar
Julian Oes committed
723

Don Gagne's avatar
Don Gagne committed
724
    _setupAckTimeout();
Don Gagne's avatar
Don Gagne committed
725
726
    
    _lastOutgoingSeqNumber++;
Don Gagne's avatar
Don Gagne committed
727

728
729
    request->hdr.seqNumber = _lastOutgoingSeqNumber;
    
Don Gagne's avatar
Don Gagne committed
730
731
    qCDebug(FileManagerLog) << "_sendRequest opcode:" << request->hdr.opcode << "seqNumber:" << request->hdr.seqNumber;
    
732
    if (_systemIdQGC == 0) {
733
        _systemIdQGC = qgcApp()->toolbox()->mavlinkProtocol()->getSystemId();
734
    }
Don Gagne's avatar
Don Gagne committed
735
736
737
738
739
740
741
742

    // Unit testing code can end up here without _dedicateLink set since it tests inidividual commands.
    LinkInterface* link;
    if (_dedicatedLink) {
        link = _dedicatedLink;
    } else {
        link = _vehicle->priorityLink();
    }
743
    
744
745
    mavlink_msg_file_transfer_protocol_pack_chan(_systemIdQGC,       // QGC System ID
                                                 0,                  // QGC Component ID
Don Gagne's avatar
Don Gagne committed
746
                                                 link->mavlinkChannel(),
747
748
749
750
751
                                                 &message,           // Mavlink Message to pack into
                                                 0,                  // Target network
                                                 _systemIdServer,    // Target system
                                                 0,                  // Target component
                                                 (uint8_t*)request); // Payload
752
    
Don Gagne's avatar
Don Gagne committed
753
    _vehicle->sendMessageOnLink(link, message);
Lorenz Meier's avatar
Lorenz Meier committed
754
}