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.
 *
 ****************************************************************************/
9

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();
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;
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
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
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);
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);
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;
        }
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();
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;
    }
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));
    }
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;
    }
170

171 172 173
    uint8_t offset = 0;
    uint8_t cListEntries = 0;
    uint8_t cBytes = listAck->hdr.size;
174

175 176 177
    // parse filenames out of the buffer
    while (offset < cBytes) {
        const char * ptr = ((const char *)listAck->data) + offset;
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
        }
201

202 203
        // account for the name + NUL
        offset += nlen + 1;
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 */);
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;
313 314
        return;
    }
315 316 317
    
    Request* request = (Request*)&data.payload[0];
    
318 319
    _clearAckTimeout();
    
320 321
	qCDebug(FileManagerLog) << "receiveMessage" << request->hdr.opcode;
	
322
    uint16_t incomingSeqNumber = request->hdr.seqNumber;
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;
        }
        
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;
357

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;
		}
390
    } else if (request->hdr.opcode == kRspNak) {
391
        uint8_t errorCode = request->data[0];
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);
        
396
        _currentOperation = kCOIdle;
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])));
        }
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
{
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;
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
{
457
    Request request;
none's avatar
none committed
458

459
    request.hdr.session = 0;
460
    request.hdr.opcode = kCmdListDirectory;
461
    request.hdr.offset = _listOffset;
462
    request.hdr.size = 0;
463

464
    _fillRequestWithString(&request, _listPath);
465

466 467
    qCDebug(FileManagerLog) << "listDirectory: path:" << _listPath << "offset:" <<  _listOffset;
    
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 */);
}
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
    }
}
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)
620 621 622 623 624
{
    if (_currentOperation != kCOIdle) {
        // Can't have multiple commands in play at the same time
        return false;
    }
625

626 627 628 629 630
    Request request;
    request.hdr.session = 0;
    request.hdr.opcode = opcode;
    request.hdr.offset = 0;
    request.hdr.size = 0;
631

632
    _currentOperation = newOpState;
633

634
    _sendRequest(&request);
635

636
    return true;
637 638 639
}

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

644
    Q_ASSERT(!_ackTimer.isActive());
645

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

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

/// @brief Called when ack timeout timer fires
658
void FileManager::_ackTimeout(void)
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;
702
    request.hdr.size = 0;
703
    _sendRequest(&request);
704 705
}

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

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

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

722
    mavlink_message_t message;
723

724
    _setupAckTimeout();
725 726
    
    _lastOutgoingSeqNumber++;
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
}