FileManager.cc 23.8 KB
Newer Older
1
/*=====================================================================
2

3
 QGroundControl Open Source Ground Control Station
4

5
 (c) 2009 - 2014 QGROUNDCONTROL PROJECT <http://www.qgroundcontrol.org>
6

7
 This file is part of the QGROUNDCONTROL project
8

9 10 11 12
 QGROUNDCONTROL is free software: you can redistribute it and/or modify
 it under the terms of the GNU General Public License as published by
 the Free Software Foundation, either version 3 of the License, or
 (at your option) any later version.
13

14 15 16 17
 QGROUNDCONTROL is distributed in the hope that it will be useful,
 but WITHOUT ANY WARRANTY; without even the implied warranty of
 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 GNU General Public License for more details.
18

19 20
 You should have received a copy of the GNU General Public License
 along with QGROUNDCONTROL. If not, see <http://www.gnu.org/licenses/>.
21

22 23
 ======================================================================*/

24
#include "FileManager.h"
25
#include "QGC.h"
Lorenz Meier's avatar
Lorenz Meier committed
26
#include "MAVLinkProtocol.h"
27
#include "MainWindow.h"
Lorenz Meier's avatar
Lorenz Meier committed
28 29 30

#include <QFile>
#include <QDir>
none's avatar
none committed
31
#include <string>
Lorenz Meier's avatar
Lorenz Meier committed
32

33
QGC_LOGGING_CATEGORY(FileManagerLog, "FileManagerLog")
34

Don Gagne's avatar
Don Gagne committed
35
FileManager::FileManager(QObject* parent, UASInterface* uas) :
36
    QObject(parent),
37
    _currentOperation(kCOIdle),
Lorenz Meier's avatar
Lorenz Meier committed
38
    _mav(uas),
39
    _lastOutgoingSeqNumber(0),
40
    _activeSession(0),
Don Gagne's avatar
Don Gagne committed
41
    _systemIdQGC(0)
Lorenz Meier's avatar
Lorenz Meier committed
42
{
43
    connect(&_ackTimer, &QTimer::timeout, this, &FileManager::_ackTimeout);
44 45
    
    _systemIdServer = _mav->getUASID();
46 47
    
    // Make sure we don't have bad structure packing
48
    Q_ASSERT(sizeof(RequestHeader) == 12);
Lorenz Meier's avatar
Lorenz Meier committed
49 50
}

Don Gagne's avatar
Don Gagne committed
51
/// Respond to the Ack associated with the Open command with the next read command.
52
void FileManager::_openAckResponse(Request* openAck)
53
{
Don Gagne's avatar
Don Gagne committed
54 55 56
    qCDebug(FileManagerLog) << QString("_openAckResponse: _currentOperation(%1) _readFileLength(%2)").arg(_currentOperation).arg(openAck->openFileLength);
    
	Q_ASSERT(_currentOperation == kCOOpenRead || _currentOperation == kCOOpenBurst);
57
	_currentOperation = _currentOperation == kCOOpenRead ? kCORead : kCOBurst;
58
    _activeSession = openAck->hdr.session;
59 60 61
    
    // File length comes back in data
    Q_ASSERT(openAck->hdr.size == sizeof(uint32_t));
Don Gagne's avatar
Don Gagne committed
62
    _downloadFileSize = openAck->openFileLength;
63 64
    
    // Start the sequence of read commands
65

Don Gagne's avatar
Don Gagne committed
66
    _downloadOffset = 0;            // Start reading at beginning of file
67
    _readFileAccumulator.clear();   // Start with an empty file
68

69 70
    Request request;
    request.hdr.session = _activeSession;
71 72 73
	Q_ASSERT(_currentOperation == kCORead || _currentOperation == kCOBurst);
	request.hdr.opcode = _currentOperation == kCORead ? kCmdReadFile : kCmdBurstReadFile;
    request.hdr.offset = _downloadOffset;
74
    request.hdr.size = sizeof(request.data);
75

76 77 78
    _sendRequest(&request);
}

Don Gagne's avatar
Don Gagne committed
79
/// Closes out a download session by writing the file and doing cleanup.
80
///     @param success true: successful download completion, false: error during download
81
void FileManager::_closeDownloadSession(bool success)
82
{
Don Gagne's avatar
Don Gagne committed
83 84 85 86
    qCDebug(FileManagerLog) << QString("_closeDownloadSession: success(%1)").arg(success);
    
    _currentOperation = kCOIdle;
    
87 88
    if (success) {
        QString downloadFilePath = _readFileDownloadDir.absoluteFilePath(_readFileDownloadFilename);
89

90 91 92 93 94
        QFile file(downloadFilePath);
        if (!file.open(QIODevice::WriteOnly | QIODevice::Truncate)) {
            _emitErrorMessage(tr("Unable to open local file for writing (%1)").arg(downloadFilePath));
            return;
        }
95

96 97 98 99 100 101 102
        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();
103

Don Gagne's avatar
Don Gagne committed
104
        emit commandComplete();
105
    }
Don Gagne's avatar
Don Gagne committed
106
    
Don Gagne's avatar
Don Gagne committed
107 108
    _readFileAccumulator.clear();
    
109
    // Close the open session
Don Gagne's avatar
Don Gagne committed
110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128
    _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();
129 130
}

131 132 133
/// 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)
134 135
{
    if (readAck->hdr.session != _activeSession) {
Don Gagne's avatar
Don Gagne committed
136
        _closeDownloadSession(false /* failure */);
137
        _emitErrorMessage(tr("Download: Incorrect session returned"));
138 139
        return;
    }
140

141
    if (readAck->hdr.offset != _downloadOffset) {
Don Gagne's avatar
Don Gagne committed
142
        _closeDownloadSession(false /* failure */);
143
        _emitErrorMessage(tr("Download: Offset returned (%1) differs from offset requested/expected (%2)").arg(readAck->hdr.offset).arg(_downloadOffset));
144 145
        return;
    }
146 147
    
    qCDebug(FileManagerLog) << QString("_downloadAckResponse: offset(%1) size(%2) burstComplete(%3)").arg(readAck->hdr.offset).arg(readAck->hdr.size).arg(readAck->hdr.burstComplete);
148

149
	_downloadOffset += readAck->hdr.size;
150
    _readFileAccumulator.append((const char*)readAck->data, readAck->hdr.size);
Don Gagne's avatar
Don Gagne committed
151 152 153 154
    
    if (_downloadFileSize != 0) {
        emit commandProgress(100 * ((float)_readFileAccumulator.length() / (float)_downloadFileSize));
    }
155

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

Don Gagne's avatar
Don Gagne committed
159 160 161 162 163
        Request request;
        request.hdr.session = _activeSession;
        request.hdr.opcode = readFile ? kCmdReadFile : kCmdBurstReadFile;
        request.hdr.offset = _downloadOffset;
        request.hdr.size = 0;
164

Don Gagne's avatar
Don Gagne committed
165 166 167 168
        _sendRequest(&request);
    } else if (!readFile) {
        // Streaming, so next ack should come automatically
        _setupAckTimeout();
169 170 171 172
    }
}

/// @brief Respond to the Ack associated with the List command.
173
void FileManager::_listAckResponse(Request* listAck)
174 175 176 177 178 179
{
    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;
    }
180

181 182 183
    uint8_t offset = 0;
    uint8_t cListEntries = 0;
    uint8_t cBytes = listAck->hdr.size;
184

185 186 187
    // parse filenames out of the buffer
    while (offset < cBytes) {
        const char * ptr = ((const char *)listAck->data) + offset;
188

189 190
        // get the length of the name
        uint8_t cBytesLeft = cBytes - offset;
Don Gagne's avatar
Don Gagne committed
191
        uint8_t nlen = static_cast<uint8_t>(strnlen(ptr, cBytesLeft));
192
        if ((*ptr == 'S' && nlen > 1) || (*ptr != 'S' && nlen < 2)) {
193 194 195 196 197 198 199 200
            _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;
        }
201

202
        // Returned names are prepended with D for directory, F for file, S for skip
203 204
        if (*ptr == 'F' || *ptr == 'D') {
            // put it in the view
205
            _emitListEntry(ptr);
206 207
        } else if (*ptr == 'S') {
            // do nothing
208 209
        } else {
            qDebug() << "unknown entry" << ptr;
210
        }
211

212 213
        // account for the name + NUL
        offset += nlen + 1;
214

215 216 217
        cListEntries++;
    }

Don Gagne's avatar
Don Gagne committed
218
    if (listAck->hdr.size == 0 || cListEntries == 0) {
219 220 221
        // Directory is empty, we're done
        Q_ASSERT(listAck->hdr.opcode == kRspAck);
        _currentOperation = kCOIdle;
Don Gagne's avatar
Don Gagne committed
222
        emit commandComplete();
223 224
    } else {
        // Possibly more entries to come, need to keep trying till we get EOF
225 226 227
        _currentOperation = kCOList;
        _listOffset += cListEntries;
        _sendListCommand();
228 229 230
    }
}

231
/// @brief Respond to the Ack associated with the create command.
232
void FileManager::_createAckResponse(Request* createAck)
233
{
Don Gagne's avatar
Don Gagne committed
234 235
    qCDebug(FileManagerLog) << "_createAckResponse";
    
236 237 238
    _currentOperation = kCOWrite;
    _activeSession = createAck->hdr.session;

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

Don Gagne's avatar
Don Gagne committed
241
    _writeOffset = 0;
242
    _writeSize = 0;
Don Gagne's avatar
Don Gagne committed
243
    
244 245 246 247
    _writeFileDatablock();
}

/// @brief Respond to the Ack associated with the write command.
248
void FileManager::_writeAckResponse(Request* writeAck)
249
{
250
    if(_writeOffset + _writeSize >= _writeFileSize){
Don Gagne's avatar
Don Gagne committed
251
        _closeUploadSession(true /* success */);
252 253
    }

254
    if (writeAck->hdr.session != _activeSession) {
Don Gagne's avatar
Don Gagne committed
255
        _closeUploadSession(false /* failure */);
256 257 258 259 260
        _emitErrorMessage(tr("Write: Incorrect session returned"));
        return;
    }

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

266
    if (writeAck->hdr.size != sizeof(uint32_t)) {
Don Gagne's avatar
Don Gagne committed
267
        _closeUploadSession(false /* failure */);
268
        _emitErrorMessage(tr("Write: Returned invalid size of write size data"));
269 270 271
        return;
    }

272

Don Gagne's avatar
Don Gagne committed
273 274
    if( writeAck->writeFileLength !=_writeSize) {
        _closeUploadSession(false /* failure */);
275
        _emitErrorMessage(tr("Write: Size returned (%1) differs from size requested (%2)").arg(writeAck->writeFileLength).arg(_writeSize));
276 277 278 279 280 281 282
        return;
    }

    _writeFileDatablock();
}

/// @brief Send next write file data block.
283
void FileManager::_writeFileDatablock(void)
284
{
Don Gagne's avatar
Don Gagne committed
285 286
    if (_writeOffset + _writeSize >= _writeFileSize){
        _closeUploadSession(true /* success */);
287
        return;
288 289
    }

290 291
    _writeOffset += _writeSize;

292 293 294 295 296
    Request request;
    request.hdr.session = _activeSession;
    request.hdr.opcode = kCmdWriteFile;
    request.hdr.offset = _writeOffset;

297
    if(_writeFileSize -_writeOffset > sizeof(request.data) )
298 299
        _writeSize = sizeof(request.data);
    else
300
        _writeSize = _writeFileSize - _writeOffset;
301 302 303

    request.hdr.size = _writeSize;

304
    memcpy(request.data, &_writeFileAccumulator.data()[_writeOffset], _writeSize);
305 306 307 308

    _sendRequest(&request);
}

309
void FileManager::receiveMessage(LinkInterface* link, mavlink_message_t message)
Lorenz Meier's avatar
Lorenz Meier committed
310
{
311
    Q_UNUSED(link);
312

313 314
    // 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) {
315 316
        return;
    }
317
	
318 319
    mavlink_file_transfer_protocol_t data;
    mavlink_msg_file_transfer_protocol_decode(&message, &data);
320
	
321 322
    // Make sure we are the target system
    if (data.target_system != _systemIdQGC) {
323
        qDebug() << "Received MAVLINK_MSG_ID_FILE_TRANSFER_PROTOCOL with incorrect target_system:" <<  data.target_system << "expected:" << _systemIdQGC;
324 325
        return;
    }
326 327 328
    
    Request* request = (Request*)&data.payload[0];
    
329 330
    _clearAckTimeout();
    
331 332
	qCDebug(FileManagerLog) << "receiveMessage" << request->hdr.opcode;
	
333
    uint16_t incomingSeqNumber = request->hdr.seqNumber;
334 335 336 337
    
    // Make sure we have a good sequence number
    uint16_t expectedSeqNumber = _lastOutgoingSeqNumber + 1;
    if (incomingSeqNumber != expectedSeqNumber) {
Don Gagne's avatar
Don Gagne committed
338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361
        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;
        }
        
362 363 364 365 366 367
        _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;
368

369
    if (request->hdr.opcode == kRspAck) {
370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388
        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:
389 390
                _createAckResponse(request);
                break;
391 392
                
            case kCmdWriteFile:
393 394
                _writeAckResponse(request);
                break;
395 396 397 398 399 400
                
			default:
				// Ack back from operation which does not require additional work
				_currentOperation = kCOIdle;
				break;
		}
401
    } else if (request->hdr.opcode == kRspNak) {
402
        uint8_t errorCode = request->data[0];
403

404 405 406
        // 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);
        
407
        _currentOperation = kCOIdle;
408

409 410
        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
411
            emit commandComplete();
412
            return;
413 414 415
        } 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 */);
416
            return;
417
        } else if (request->hdr.req_opcode == kCmdCreateFile) {
418 419
            _emitErrorMessage(tr("Nak received creating file, error: %1").arg(errorString(request->data[0])));
            return;
420 421
        } else {
            // Generic Nak handling
422 423 424
            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
425 426 427
            } else if (request->hdr.req_opcode == kCmdWriteFile) {
                // Nak error during upload loop, upload failed
                _closeUploadSession(false /* failure */);
428 429 430
            }
            _emitErrorMessage(tr("Nak received, error: %1").arg(errorString(request->data[0])));
        }
431 432 433
    } else {
        // Note that we don't change our operation state. If something goes wrong beyond this, the operation
        // will time out.
434
        _emitErrorMessage(tr("Unknown opcode returned from server: %1").arg(request->hdr.opcode));
Lorenz Meier's avatar
Lorenz Meier committed
435 436 437
    }
}

438
void FileManager::listDirectory(const QString& dirPath)
Lorenz Meier's avatar
Lorenz Meier committed
439
{
440 441 442
    if (_currentOperation != kCOIdle) {
        _emitErrorMessage(tr("Command not sent. Waiting for previous command to complete."));
        return;
none's avatar
none committed
443 444 445
    }

    // initialise the lister
446
    _listPath = dirPath;
447 448
    _listOffset = 0;
    _currentOperation = kCOList;
none's avatar
none committed
449 450

    // and send the initial request
451
    _sendListCommand();
none's avatar
none committed
452 453
}

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

460
void FileManager::_sendListCommand(void)
none's avatar
none committed
461
{
462
    Request request;
none's avatar
none committed
463

464
    request.hdr.session = 0;
465
    request.hdr.opcode = kCmdListDirectory;
466
    request.hdr.offset = _listOffset;
467
    request.hdr.size = 0;
468

469
    _fillRequestWithString(&request, _listPath);
470

471 472
    qCDebug(FileManagerLog) << "listDirectory: path:" << _listPath << "offset:" <<  _listOffset;
    
473
    _sendRequest(&request);
Lorenz Meier's avatar
Lorenz Meier committed
474 475
}

476
void FileManager::downloadPath(const QString& from, const QDir& downloadDir)
Lorenz Meier's avatar
Lorenz Meier committed
477
{
Don Gagne's avatar
Don Gagne committed
478 479 480 481 482
    if (_currentOperation != kCOIdle) {
        _emitErrorMessage(tr("Command not sent. Waiting for previous command to complete."));
        return;
    }
    
483 484 485
	qCDebug(FileManagerLog) << "downloadPath from:" << from << "to:" << downloadDir;
	_downloadWorker(from, downloadDir, true /* read file */);
}
486

487 488
void FileManager::streamPath(const QString& from, const QDir& downloadDir)
{
Don Gagne's avatar
Don Gagne committed
489 490 491 492 493
    if (_currentOperation != kCOIdle) {
        _emitErrorMessage(tr("Command not sent. Waiting for previous command to complete."));
        return;
    }
    
494 495 496
	qCDebug(FileManagerLog) << "streamPath from:" << from << "to:" << downloadDir;
	_downloadWorker(from, downloadDir, false /* stream file */);
}
Lorenz Meier's avatar
Lorenz Meier committed
497

498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516
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
517
	_currentOperation = readFile ? kCOOpenRead : kCOOpenBurst;
518 519 520 521 522 523 524 525
	
	Request request;
	request.hdr.session = 0;
	request.hdr.opcode = kCmdOpenFileRO;
	request.hdr.offset = 0;
	request.hdr.size = 0;
	_fillRequestWithString(&request, from);
	_sendRequest(&request);
526
}
none's avatar
none committed
527

528 529 530
/// @brief Uploads the specified file.
///     @param toPath File in UAS to upload to, fully qualified path
///     @param uploadFile Local file to upload from
531
void FileManager::uploadPath(const QString& toPath, const QFileInfo& uploadFile)
532
{
533 534 535 536 537
    if(_currentOperation != kCOIdle){
        _emitErrorMessage(tr("UAS File manager busy.  Try again later"));
        return;
    }

538 539 540 541
    if (toPath.isEmpty()) {
        return;
    }

Don Gagne's avatar
Don Gagne committed
542
    if (!uploadFile.isReadable()){
543
        _emitErrorMessage(tr("File (%1) is not readable for upload").arg(uploadFile.path()));
Don Gagne's avatar
Don Gagne committed
544
        return;
545 546 547
    }

    QFile file(uploadFile.absoluteFilePath());
548
    if (!file.open(QIODevice::ReadOnly)) {
549 550 551 552 553
            _emitErrorMessage(tr("Unable to open local file for upload (%1)").arg(uploadFile.absoluteFilePath()));
            return;
        }

    _writeFileAccumulator = file.readAll();
554
    _writeFileSize = _writeFileAccumulator.size();
555

556 557 558
    file.close();

    if (_writeFileAccumulator.size() == 0) {
559 560 561 562 563 564 565 566 567 568
        _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;
569
    request.hdr.size = 0;
570
    _fillRequestWithString(&request, toPath + "/" + uploadFile.fileName());
571 572 573
    _sendRequest(&request);
}

574
QString FileManager::errorString(uint8_t errorCode)
none's avatar
none committed
575 576
{
    switch(errorCode) {
577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592
        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:
            return QString("no sessions availble");
593 594 595 596
        case kErrFailFileExists:
            return QString("File already exists on target");
        case kErrFailFileProtected:
            return QString("File is write protected");
597 598
        default:
            return QString("unknown error code");
none's avatar
none committed
599 600
    }
}
601 602 603 604 605

/// @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
606
bool FileManager::_sendOpcodeOnlyCmd(uint8_t opcode, OperationState newOpState)
607 608 609 610 611
{
    if (_currentOperation != kCOIdle) {
        // Can't have multiple commands in play at the same time
        return false;
    }
612

613 614 615 616 617
    Request request;
    request.hdr.session = 0;
    request.hdr.opcode = opcode;
    request.hdr.offset = 0;
    request.hdr.size = 0;
618

619
    _currentOperation = newOpState;
620

621
    _sendRequest(&request);
622

623
    return true;
624 625 626
}

/// @brief Starts the ack timeout timer
627
void FileManager::_setupAckTimeout(void)
628
{
629 630
	qCDebug(FileManagerLog) << "_setupAckTimeout";

631
    Q_ASSERT(!_ackTimer.isActive());
632

633
    _ackTimer.setSingleShot(true);
Don Gagne's avatar
Don Gagne committed
634
    _ackTimer.start(ackTimerTimeoutMsecs);
635 636 637
}

/// @brief Clears the ack timeout timer
638
void FileManager::_clearAckTimeout(void)
639
{
640
	qCDebug(FileManagerLog) << "_clearAckTimeout";
641 642 643 644
    _ackTimer.stop();
}

/// @brief Called when ack timeout timer fires
645
void FileManager::_ackTimeout(void)
646
{
647 648
    qCDebug(FileManagerLog) << "_ackTimeout";
    
Don Gagne's avatar
Don Gagne committed
649 650 651
    // 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.
652 653 654

    switch (_currentOperation) {
        case kCORead:
655 656
        case kCOBurst:
            _closeDownloadSession(false /* failure */);
Don Gagne's avatar
Don Gagne committed
657 658 659 660 661 662 663 664
            _emitErrorMessage(tr("Timeout waiting for ack: Download failed"));
            break;
            
        case kCOOpenRead:
        case kCOOpenBurst:
            _currentOperation = kCOIdle;
            _emitErrorMessage(tr("Timeout waiting for ack: Download failed"));
            _sendResetCommand();
665
            break;
666
            
667
        case kCOCreate:
Don Gagne's avatar
Don Gagne committed
668 669 670
            _currentOperation = kCOIdle;
            _emitErrorMessage(tr("Timeout waiting for ack: Upload failed"));
            _sendResetCommand();
671 672
            break;
            
673
        case kCOWrite:
Don Gagne's avatar
Don Gagne committed
674 675
            _closeUploadSession(false /* failure */);
            _emitErrorMessage(tr("Timeout waiting for ack: Upload failed"));
676
            break;
677
			
678 679
        default:
            _currentOperation = kCOIdle;
Don Gagne's avatar
Don Gagne committed
680
            _emitErrorMessage(QString("Timeout waiting for ack: Command failed (%1)").arg(_currentOperation));
681 682 683 684
            break;
    }
}

Don Gagne's avatar
Don Gagne committed
685
void FileManager::_sendResetCommand(void)
686 687
{
    Request request;
Don Gagne's avatar
Don Gagne committed
688
    request.hdr.opcode = kCmdResetSessions;
689
    request.hdr.size = 0;
690
    _sendRequest(&request);
691 692
}

693
void FileManager::_emitErrorMessage(const QString& msg)
694
{
695
	qCDebug(FileManagerLog) << "Error:" << msg;
Don Gagne's avatar
Don Gagne committed
696
    emit commandError(msg);
697 698
}

699
void FileManager::_emitListEntry(const QString& entry)
700
{
701
    qCDebug(FileManagerLog) << "_emitListEntry" << entry;
702
    emit listEntry(entry);
703 704
}

705
/// @brief Sends the specified Request out to the UAS.
706
void FileManager::_sendRequest(Request* request)
707
{
708

709
    mavlink_message_t message;
710

711
    _setupAckTimeout();
712 713
    
    _lastOutgoingSeqNumber++;
714

715 716
    request->hdr.seqNumber = _lastOutgoingSeqNumber;
    
Don Gagne's avatar
Don Gagne committed
717 718
    qCDebug(FileManagerLog) << "_sendRequest opcode:" << request->hdr.opcode << "seqNumber:" << request->hdr.seqNumber;
    
719
    if (_systemIdQGC == 0) {
720
        _systemIdQGC = MAVLinkProtocol::instance()->getSystemId();
721 722 723 724 725 726 727 728 729 730 731
    }
    
    Q_ASSERT(_mav);
    mavlink_msg_file_transfer_protocol_pack(_systemIdQGC,       // QGC System ID
                                            0,                  // QGC Component ID
                                            &message,           // Mavlink Message to pack into
                                            0,                  // Target network
                                            _systemIdServer,    // Target system
                                            0,                  // Target component
                                            (uint8_t*)request); // Payload
    
732
    _mav->sendMessage(message);
Lorenz Meier's avatar
Lorenz Meier committed
733
}