Commit 2e3088b8 authored by Don Gagne's avatar Don Gagne

Merge pull request #1532 from DonLakeFlyer/BurstDownload

New burst download for file transfer
parents 1909dfba 3b60ab27
......@@ -267,7 +267,7 @@ HEADERS += \
src/QmlControls/ParameterEditorController.h \
src/QmlControls/ScreenTools.h \
src/uas/QGCMAVLinkUASFactory.h \
src/uas/QGCUASFileManager.h \
src/uas/FileManager.h \
src/uas/UAS.h \
src/uas/UASInterface.h \
src/uas/UASManager.h \
......@@ -399,7 +399,7 @@ SOURCES += \
src/QmlControls/ParameterEditorController.cc \
src/QmlControls/ScreenTools.cc \
src/uas/QGCMAVLinkUASFactory.cc \
src/uas/QGCUASFileManager.cc \
src/uas/FileManager.cc \
src/uas/UAS.cc \
src/uas/UASManager.cc \
src/uas/UASMessageHandler.cc \
......
......@@ -21,18 +21,18 @@
======================================================================*/
#include "QGCUASFileManagerTest.h"
#include "FileManagerTest.h"
/// @file
/// @brief QGCUASFileManager unit test. Note: All code here assumes all work between
/// @brief FileManager unit test. Note: All code here assumes all work between
/// the unit test, mack mavlink file server and file manager is happening on
/// the same thread.
///
/// @author Don Gagne <don@thegagnes.com>
UT_REGISTER_TEST(QGCUASFileManagerUnitTest)
UT_REGISTER_TEST(FileManagerTest)
QGCUASFileManagerUnitTest::QGCUASFileManagerUnitTest(void) :
FileManagerTest::FileManagerTest(void) :
_mockFileServer(_systemIdQGC, _systemIdServer),
_fileManager(NULL),
_multiSpy(NULL)
......@@ -40,7 +40,7 @@ QGCUASFileManagerUnitTest::QGCUASFileManagerUnitTest(void) :
}
// Called once before all test cases are run
void QGCUASFileManagerUnitTest::initTestCase(void)
void FileManagerTest::initTestCase(void)
{
_mockUAS = new MockUAS();
Q_CHECK_PTR(_mockUAS);
......@@ -49,28 +49,28 @@ void QGCUASFileManagerUnitTest::initTestCase(void)
_mockUAS->setMockMavlinkPlugin(&_mockFileServer);
}
void QGCUASFileManagerUnitTest::cleanupTestCase(void)
void FileManagerTest::cleanupTestCase(void)
{
delete _mockUAS;
}
// Called before every test case
void QGCUASFileManagerUnitTest::init(void)
void FileManagerTest::init(void)
{
UnitTest::init();
Q_ASSERT(_multiSpy == NULL);
_fileManager = new QGCUASFileManager(NULL, _mockUAS, _systemIdQGC);
_fileManager = new FileManager(NULL, _mockUAS, _systemIdQGC);
Q_CHECK_PTR(_fileManager);
// Reset any internal state back to normal
_mockFileServer.setErrorMode(MockMavlinkFileServer::errModeNone);
_fileListReceived.clear();
connect(&_mockFileServer, &MockMavlinkFileServer::messageReceived, _fileManager, &QGCUASFileManager::receiveMessage);
connect(&_mockFileServer, &MockMavlinkFileServer::messageReceived, _fileManager, &FileManager::receiveMessage);
connect(_fileManager, &QGCUASFileManager::listEntry, this, &QGCUASFileManagerUnitTest::listEntry);
connect(_fileManager, &FileManager::listEntry, this, &FileManagerTest::listEntry);
_rgSignals[listEntrySignalIndex] = SIGNAL(listEntry(const QString&));
_rgSignals[listCompleteSignalIndex] = SIGNAL(listComplete(void));
......@@ -86,7 +86,7 @@ void QGCUASFileManagerUnitTest::init(void)
}
// Called after every test case
void QGCUASFileManagerUnitTest::cleanup(void)
void FileManagerTest::cleanup(void)
{
Q_ASSERT(_multiSpy);
Q_ASSERT(_fileManager);
......@@ -100,15 +100,16 @@ void QGCUASFileManagerUnitTest::cleanup(void)
UnitTest::cleanup();
}
/// @brief Connected to QGCUASFileManager listEntry signal in order to catch list entries
void QGCUASFileManagerUnitTest::listEntry(const QString& entry)
/// @brief Connected to FileManager listEntry signal in order to catch list entries
void FileManagerTest::listEntry(const QString& entry)
{
// Keep a list of all names received so we can test it for correctness
_fileListReceived += entry;
}
void QGCUASFileManagerUnitTest::_ackTest(void)
#if 0
void FileManagerTest::_ackTest(void)
{
Q_ASSERT(_fileManager);
Q_ASSERT(_multiSpy);
......@@ -134,7 +135,7 @@ void QGCUASFileManagerUnitTest::_ackTest(void)
_multiSpy->clearAllSignals();
}
void QGCUASFileManagerUnitTest::_noAckTest(void)
void FileManagerTest::_noAckTest(void)
{
Q_ASSERT(_fileManager);
Q_ASSERT(_multiSpy);
......@@ -146,7 +147,7 @@ void QGCUASFileManagerUnitTest::_noAckTest(void)
QCOMPARE(_multiSpy->checkOnlySignalByMask(errorMessageSignalMask), true);
}
void QGCUASFileManagerUnitTest::_resetTest(void)
void FileManagerTest::_resetTest(void)
{
Q_ASSERT(_fileManager);
Q_ASSERT(_multiSpy);
......@@ -158,13 +159,13 @@ void QGCUASFileManagerUnitTest::_resetTest(void)
QVERIFY(_multiSpy->checkNoSignals());
}
void QGCUASFileManagerUnitTest::_listTest(void)
void FileManagerTest::_listTest(void)
{
Q_ASSERT(_fileManager);
Q_ASSERT(_multiSpy);
Q_ASSERT(_multiSpy->checkNoSignals() == true);
// QGCUASFileManager::listDirectory signalling as follows:
// FileManager::listDirectory signalling as follows:
// Emits a listEntry signal for each list entry
// Emits an errorMessage signal if:
// It gets a Nak back
......@@ -225,32 +226,13 @@ void QGCUASFileManagerUnitTest::_listTest(void)
QVERIFY(_fileListReceived == fileList);
}
void QGCUASFileManagerUnitTest::_validateFileContents(const QString& filePath, uint8_t length)
{
QFile file(filePath);
// Make sure file size is correct
QCOMPARE(file.size(), (qint64)length);
// Read data
QVERIFY(file.open(QIODevice::ReadOnly));
QByteArray bytes = file.readAll();
file.close();
// Validate file contents:
// Repeating 0x00, 0x01 .. 0xFF until file is full
for (uint8_t i=0; i<bytes.length(); i++) {
QCOMPARE((uint8_t)bytes[i], (uint8_t)(i & 0xFF));
}
}
void QGCUASFileManagerUnitTest::_downloadTest(void)
void FileManagerTest::_readDownloadTest(void)
{
Q_ASSERT(_fileManager);
Q_ASSERT(_multiSpy);
Q_ASSERT(_multiSpy->checkNoSignals() == true);
// QGCUASFileManager::downloadPath works as follows:
// FileManager::downloadPath works as follows:
// Sends an Open Command to the server
// Expects an Ack Response back from the server with the correct sequence numner
// Emits an errorMessage signal if it gets a Nak back
......@@ -297,7 +279,7 @@ void QGCUASFileManagerUnitTest::_downloadTest(void)
// Run through the various failure modes for this test case
for (size_t j=0; j<MockMavlinkFileServer::cFailureModes; j++) {
MockMavlinkFileServer::ErrorMode_t errMode = MockMavlinkFileServer::rgFailureModes[j];
qDebug() << "Testing failure mode:" << errMode;
_mockFileServer.setErrorMode(errMode);
......@@ -341,6 +323,7 @@ void QGCUASFileManagerUnitTest::_downloadTest(void)
}
// Run what should be a successful file download test case. No servers errors are being simulated.
_mockFileServer.setErrorMode(MockMavlinkFileServer::errModeNone);
_fileManager->downloadPath(testCase->filename, QDir::temp());
// This should be a succesful download
......@@ -360,3 +343,141 @@ void QGCUASFileManagerUnitTest::_downloadTest(void)
_validateFileContents(filePath, MockMavlinkFileServer::rgFileTestCases[i].length);
}
}
#endif
void FileManagerTest::_streamDownloadTest(void)
{
Q_ASSERT(_fileManager);
Q_ASSERT(_multiSpy);
Q_ASSERT(_multiSpy->checkNoSignals() == true);
// FileManager::streamPath works as follows:
// Sends an Open Command to the server
// Expects an Ack Response back from the server with the correct sequence numner
// Emits an errorMessage signal if it gets a Nak back
// Emits an downloadFileLength signal with the file length if it gets back a good Ack
// Sends a single Stream command to the server
// Expects continuous Ack responses back with file contents
// Emits a downloadFileProgress for each ack it gets back
// Sends Terminate command to server when download is complete to close Open command
// Mock file server will signal terminateCommandReceived when it gets a Terminate command
// Sends downloadFileComplete signal to indicate the download is complete
// Emits an errorMessage signal if sequence number is incorrrect on any response
// Emits an errorMessage signal if CRC is incorrect on any responses
// Expected signals if the Open command fails for any reason
quint16 signalMaskOpenFailure = errorMessageSignalMask;
// Expected signals if the Read command fails for any reason
quint16 signalMaskReadFailure = downloadFileLengthSignalMask | errorMessageSignalMask;
// Expected signals if the downloadPath command succeeds
quint16 signalMaskDownloadSuccess = downloadFileLengthSignalMask | downloadFileCompleteSignalMask;
// Send a bogus path
// We should get a single resetStatusMessages signal
// We should get a single errorMessage signal
_fileManager->streamPath("bogus", QDir::temp());
QCOMPARE(_multiSpy->checkOnlySignalByMask(signalMaskOpenFailure), true);
_multiSpy->clearAllSignals();
// Clean previous downloads
for (size_t i=0; i<MockMavlinkFileServer::cFileTestCases; i++) {
QString filePath = QDir::temp().absoluteFilePath(MockMavlinkFileServer::rgFileTestCases[i].filename);
if (QFile::exists(filePath)) {
Q_ASSERT(QFile::remove(filePath));
}
}
// We setup a spy on the Terminate command signal of the mock file server so that we can determine that a
// Terminate command was correctly sent after the Open/Read commands complete.
QSignalSpy terminateSpy(&_mockFileServer, SIGNAL(terminateCommandReceived()));
// Run through the set of file test cases
for (size_t i=0; i<MockMavlinkFileServer::cFileTestCases; i++) {
const MockMavlinkFileServer::FileTestCase* testCase = &MockMavlinkFileServer::rgFileTestCases[i];
// Run through the various failure modes for this test case
for (size_t j=0; j<MockMavlinkFileServer::cFailureModes; j++) {
MockMavlinkFileServer::ErrorMode_t errMode = MockMavlinkFileServer::rgFailureModes[j];
qDebug() << "Testing failure mode:" << errMode;
_mockFileServer.setErrorMode(errMode);
_fileManager->streamPath(testCase->filename, QDir::temp());
QTest::qWait(_ackTimerTimeoutMsecs); // Let the file manager timeout
if (errMode == MockMavlinkFileServer::errModeNoSecondResponse || errMode == MockMavlinkFileServer::errModeNakSecondResponse) {
// For simulated server errors on subsequent Acks, the first Ack will go through. We must handle things differently depending
// on whether the downloaded file requires multiple packets to complete the download.
if (testCase->packetCount != 1) {
// The downloaded file requires multiple Acks to complete. Second Ack should have failed.
QCOMPARE(_multiSpy->checkOnlySignalByMask(signalMaskReadFailure), true);
// Open command succeeded, so we should get a Terminate for the open
QCOMPARE(terminateSpy.count(), 1);
} else {
// The downloaded file fits within a single Ack response, hence there is no second Read issued.
// This should result in a successful download.
QCOMPARE(_multiSpy->checkOnlySignalByMask(signalMaskDownloadSuccess), true);
// We should get a single Terminate command to close the Open session
QCOMPARE(terminateSpy.count(), 1);
// Validate file contents
QString filePath = QDir::temp().absoluteFilePath(testCase->filename);
_validateFileContents(filePath, testCase->length);
}
} else {
// For all the other simulated server errors the Open command should have failed. Since the Open failed
// there is no session to terminate, hence no Terminate in this case.
QCOMPARE(_multiSpy->checkOnlySignalByMask(signalMaskOpenFailure), true);
QCOMPARE(terminateSpy.count(), 0);
}
// Cleanup for next iteration
_multiSpy->clearAllSignals();
terminateSpy.clear();
_mockFileServer.setErrorMode(MockMavlinkFileServer::errModeNone);
}
// Run what should be a successful file download test case. No servers errors are being simulated.
_mockFileServer.setErrorMode(MockMavlinkFileServer::errModeNone);
_fileManager->streamPath(testCase->filename, QDir::temp());
// This should be a succesful download
QCOMPARE(_multiSpy->checkOnlySignalByMask(signalMaskDownloadSuccess), true);
// Make sure the file length coming back through the openFileLength signal is correct
QVERIFY(_multiSpy->getSpyByIndex(downloadFileLengthSignalIndex)->takeFirst().at(0).toInt() == testCase->length);
_multiSpy->clearAllSignals();
// We should get a single Terminate command to close the session
QCOMPARE(terminateSpy.count(), 1);
terminateSpy.clear();
// Validate file contents
QString filePath = QDir::temp().absoluteFilePath(MockMavlinkFileServer::rgFileTestCases[i].filename);
_validateFileContents(filePath, MockMavlinkFileServer::rgFileTestCases[i].length);
}
}
void FileManagerTest::_validateFileContents(const QString& filePath, uint8_t length)
{
QFile file(filePath);
// Make sure file size is correct
QCOMPARE(file.size(), (qint64)length);
// Read data
QVERIFY(file.open(QIODevice::ReadOnly));
QByteArray bytes = file.readAll();
file.close();
// Validate file contents:
// Repeating 0x00, 0x01 .. 0xFF until file is full
for (uint8_t i=0; i<bytes.length(); i++) {
QCOMPARE((uint8_t)bytes[i], (uint8_t)(i & 0xFF));
}
}
......@@ -21,8 +21,8 @@
======================================================================*/
#ifndef QGCUASFILEMANAGERTEST_H
#define QGCUASFILEMANAGERTEST_H
#ifndef FileManagerTEST_H
#define FileManagerTEST_H
#include <QObject>
#include <QtTest/QtTest>
......@@ -30,20 +30,20 @@
#include "UnitTest.h"
#include "MockUAS.h"
#include "MockMavlinkFileServer.h"
#include "QGCUASFileManager.h"
#include "FileManager.h"
#include "MultiSignalSpy.h"
/// @file
/// @brief QGCUASFileManager unit test
/// @brief FileManager unit test
///
/// @author Don Gagne <don@thegagnes.com>
class QGCUASFileManagerUnitTest : public UnitTest
class FileManagerTest : public UnitTest
{
Q_OBJECT
public:
QGCUASFileManagerUnitTest(void);
FileManagerTest(void);
private slots:
// Test case initialization
......@@ -53,13 +53,16 @@ private slots:
void cleanup(void);
// Test cases
#if 0
void _ackTest(void);
void _noAckTest(void);
void _resetTest(void);
void _listTest(void);
void _downloadTest(void);
// Connected to QGCUASFileManager listEntry signal
void _readDownloadTest(void);
#endif
void _streamDownloadTest(void);
// Connected to FileManager listEntry signal
void listEntry(const QString& entry);
private:
......@@ -88,7 +91,7 @@ private:
MockUAS* _mockUAS;
MockMavlinkFileServer _mockFileServer;
QGCUASFileManager* _fileManager;
FileManager* _fileManager;
MultiSignalSpy* _multiSpy;
static const size_t _cSignals = maxSignalIndex;
......@@ -96,7 +99,7 @@ private:
/// @brief This is the amount of time to wait to allow the FileManager enough time to timeout waiting for an Ack.
/// As such it must be larger than the Ack Timeout used by the FileManager.
static const int _ackTimerTimeoutMsecs = QGCUASFileManager::ackTimerTimeoutMsecs * 2;
static const int _ackTimerTimeoutMsecs = FileManager::ackTimerTimeoutMsecs * 2;
QStringList _fileListReceived;
};
......
......@@ -34,11 +34,11 @@ const size_t MockMavlinkFileServer::cFailureModes = sizeof(MockMavlinkFileServer
const MockMavlinkFileServer::FileTestCase MockMavlinkFileServer::rgFileTestCases[MockMavlinkFileServer::cFileTestCases] = {
// File fits one Read Ack packet, partially filling data
{ "partial.qgc", sizeof(((QGCUASFileManager::Request*)0)->data) - 1, false },
{ "partial.qgc", sizeof(((FileManager::Request*)0)->data) - 1, 1, false},
// File fits one Read Ack packet, exactly filling all data
{ "exact.qgc", sizeof(((QGCUASFileManager::Request*)0)->data), true },
{ "exact.qgc", sizeof(((FileManager::Request*)0)->data), 1, true },
// File is larger than a single Read Ack packets, requires multiple Reads
{ "multi.qgc", sizeof(((QGCUASFileManager::Request*)0)->data) + 1, true },
{ "multi.qgc", sizeof(((FileManager::Request*)0)->data) + 1, 2, false },
};
// We only support a single fixed session
......@@ -54,28 +54,28 @@ MockMavlinkFileServer::MockMavlinkFileServer(uint8_t systemIdQGC, uint8_t system
/// @brief Handles List command requests. Only supports root folder paths.
/// File list returned is set using the setFileList method.
void MockMavlinkFileServer::_listCommand(QGCUASFileManager::Request* request, uint16_t seqNumber)
void MockMavlinkFileServer::_listCommand(FileManager::Request* request, uint16_t seqNumber)
{
// FIXME: Does not support directories that span multiple packets
QGCUASFileManager::Request ackResponse;
FileManager::Request ackResponse;
QString path;
uint16_t outgoingSeqNumber = _nextSeqNumber(seqNumber);
// We only support root path
path = (char *)&request->data[0];
if (!path.isEmpty() && path != "/") {
_sendNak(QGCUASFileManager::kErrFail, outgoingSeqNumber);
_sendNak(FileManager::kErrFail, outgoingSeqNumber, FileManager::kCmdListDirectory);
return;
}
// Offset requested is past the end of the list
if (request->hdr.offset > (uint32_t)_fileList.size()) {
_sendNak(QGCUASFileManager::kErrEOF, outgoingSeqNumber);
_sendNak(FileManager::kErrEOF, outgoingSeqNumber, FileManager::kCmdListDirectory);
return;
}
ackResponse.hdr.opcode = QGCUASFileManager::kRspAck;
ackResponse.hdr.opcode = FileManager::kRspAck;
ackResponse.hdr.session = 0;
ackResponse.hdr.offset = request->hdr.offset;
ackResponse.hdr.size = 0;
......@@ -95,21 +95,21 @@ void MockMavlinkFileServer::_listCommand(QGCUASFileManager::Request* request, ui
_emitResponse(&ackResponse, outgoingSeqNumber);
} else if (_errMode == errModeNakSecondResponse) {
// Nak error all subsequent requests
_sendNak(QGCUASFileManager::kErrFail, outgoingSeqNumber);
_sendNak(FileManager::kErrFail, outgoingSeqNumber, FileManager::kCmdListDirectory);
return;
} else if (_errMode == errModeNoSecondResponse) {
// No response for all subsequent requests
return;
} else {
// FIXME: Does not support directories that span multiple packets
_sendNak(QGCUASFileManager::kErrEOF, outgoingSeqNumber);
_sendNak(FileManager::kErrEOF, outgoingSeqNumber, FileManager::kCmdListDirectory);
}
}
/// @brief Handles Open command requests.
void MockMavlinkFileServer::_openCommand(QGCUASFileManager::Request* request, uint16_t seqNumber)
void MockMavlinkFileServer::_openCommand(FileManager::Request* request, uint16_t seqNumber)
{
QGCUASFileManager::Request response;
FileManager::Request response;
QString path;
uint16_t outgoingSeqNumber = _nextSeqNumber(seqNumber);
......@@ -129,11 +129,12 @@ void MockMavlinkFileServer::_openCommand(QGCUASFileManager::Request* request, ui
}
}
if (!found) {
_sendNak(QGCUASFileManager::kErrFail, outgoingSeqNumber);
_sendNak(FileManager::kErrFail, outgoingSeqNumber, FileManager::kCmdOpenFileRO);
return;
}
response.hdr.opcode = QGCUASFileManager::kRspAck;
response.hdr.opcode = FileManager::kRspAck;
response.hdr.req_opcode = FileManager::kCmdOpenFileRO;
response.hdr.session = _sessionId;
// Data contains file length
......@@ -143,14 +144,13 @@ void MockMavlinkFileServer::_openCommand(QGCUASFileManager::Request* request, ui
_emitResponse(&response, outgoingSeqNumber);
}
/// @brief Handles Read command requests.
void MockMavlinkFileServer::_readCommand(QGCUASFileManager::Request* request, uint16_t seqNumber)
void MockMavlinkFileServer::_readCommand(FileManager::Request* request, uint16_t seqNumber)
{
QGCUASFileManager::Request response;
uint16_t outgoingSeqNumber = _nextSeqNumber(seqNumber);
FileManager::Request response;
uint16_t outgoingSeqNumber = _nextSeqNumber(seqNumber);
if (request->hdr.session != _sessionId) {
_sendNak(QGCUASFileManager::kErrFail, outgoingSeqNumber);
_sendNak(FileManager::kErrFail, outgoingSeqNumber, FileManager::kCmdReadFile);
return;
}
......@@ -161,7 +161,7 @@ void MockMavlinkFileServer::_readCommand(QGCUASFileManager::Request* request, ui
// If we get here it means the client is requesting additional data past the first request
if (_errMode == errModeNakSecondResponse) {
// Nak error all subsequent requests
_sendNak(QGCUASFileManager::kErrFail, outgoingSeqNumber);
_sendNak(FileManager::kErrFail, outgoingSeqNumber, FileManager::kCmdReadFile);
return;
} else if (_errMode == errModeNoSecondResponse) {
// No rsponse for all subsequent requests
......@@ -170,7 +170,7 @@ void MockMavlinkFileServer::_readCommand(QGCUASFileManager::Request* request, ui
}
if (readOffset >= _readFileLength) {
_sendNak(QGCUASFileManager::kErrEOF, outgoingSeqNumber);
_sendNak(FileManager::kErrEOF, outgoingSeqNumber, FileManager::kCmdReadFile);
return;
}
......@@ -185,23 +185,76 @@ void MockMavlinkFileServer::_readCommand(QGCUASFileManager::Request* request, ui
response.hdr.session = _sessionId;
response.hdr.size = cDataBytes;
response.hdr.offset = request->hdr.offset;
response.hdr.opcode = QGCUASFileManager::kRspAck;
response.hdr.opcode = FileManager::kRspAck;
response.hdr.req_opcode = FileManager::kCmdReadFile;
_emitResponse(&response, outgoingSeqNumber);
}
void MockMavlinkFileServer::_streamCommand(FileManager::Request* request, uint16_t seqNumber)
{
uint16_t outgoingSeqNumber = _nextSeqNumber(seqNumber);
FileManager::Request response;
if (request->hdr.session != _sessionId) {
_sendNak(FileManager::kErrFail, outgoingSeqNumber, FileManager::kCmdBurstReadFile);
return;
}
uint32_t readOffset = 0; // offset into file for reading
uint32_t ackOffset = 0; // offset for ack
uint8_t cDataAck; // number of bytes in ack
while (readOffset < _readFileLength) {
cDataAck = 0;
if (readOffset != 0) {
// If we get here it means the client is requesting additional data past the first request
if (_errMode == errModeNakSecondResponse) {
// Nak error all subsequent requests
_sendNak(FileManager::kErrFail, outgoingSeqNumber, FileManager::kCmdBurstReadFile);
return;
} else if (_errMode == errModeNoSecondResponse) {
// No response for all subsequent requests
return;
}
}
// Write file bytes. Data is a repeating sequence of 0x00, 0x01, .. 0xFF.
for (; cDataAck < sizeof(response.data) && readOffset < _readFileLength; readOffset++, cDataAck++) {
response.data[cDataAck] = readOffset & 0xFF;
}
// We should always have written something, otherwise there is something wrong with the code above
Q_ASSERT(cDataAck);
response.hdr.session = _sessionId;
response.hdr.size = cDataAck;
response.hdr.offset = ackOffset;
response.hdr.opcode = FileManager::kRspAck;
response.hdr.req_opcode = FileManager::kCmdBurstReadFile;
_emitResponse(&response, outgoingSeqNumber);
outgoingSeqNumber = _nextSeqNumber(outgoingSeqNumber);
ackOffset += cDataAck;
}
_sendNak(FileManager::kErrEOF, outgoingSeqNumber, FileManager::kCmdBurstReadFile);
}
/// @brief Handles Terminate commands
void MockMavlinkFileServer::_terminateCommand(QGCUASFileManager::Request* request, uint16_t seqNumber)
void MockMavlinkFileServer::_terminateCommand(FileManager::Request* request, uint16_t seqNumber)
{
uint16_t outgoingSeqNumber = _nextSeqNumber(seqNumber);
if (request->hdr.session != _sessionId) {
_sendNak(QGCUASFileManager::kErrInvalidSession, outgoingSeqNumber);
_sendNak(FileManager::kErrInvalidSession, outgoingSeqNumber, FileManager::kCmdTerminateSession);
return;
}
_sendAck(outgoingSeqNumber);
_sendAck(outgoingSeqNumber, FileManager::kCmdTerminateSession);
// Let our test harness know that we got a terminate command. This is used to validate the a Terminate is correctly
// sent after an Open.
emit terminateCommandReceived();
......@@ -210,13 +263,13 @@ void MockMavlinkFileServer::_terminateCommand(QGCUASFileManager::Request* reques
/// @brief Handles messages sent to the FTP server.
void MockMavlinkFileServer::sendMessage(mavlink_message_t message)
{
QGCUASFileManager::Request ackResponse;
FileManager::Request ackResponse;
Q_ASSERT(message.msgid == MAVLINK_MSG_ID_FILE_TRANSFER_PROTOCOL);
mavlink_file_transfer_protocol_t requestFileTransferProtocol;
mavlink_msg_file_transfer_protocol_decode(&message, &requestFileTransferProtocol);
QGCUASFileManager::Request* request = (QGCUASFileManager::Request*)&requestFileTransferProtocol.payload[0];
FileManager::Request* request = (FileManager::Request*)&requestFileTransferProtocol.payload[0];
Q_ASSERT(requestFileTransferProtocol.target_system == _systemIdServer);
......@@ -228,56 +281,61 @@ void MockMavlinkFileServer::sendMessage(mavlink_message_t message)
return;
} else if (_errMode == errModeNakResponse) {
// Nak all requests, the actual error send back doesn't really matter as long as it's an error
_sendNak(QGCUASFileManager::kErrFail, outgoingSeqNumber);
_sendNak(FileManager::kErrFail, outgoingSeqNumber, (FileManager::Opcode)request->hdr.opcode);
return;
}
switch (request->hdr.opcode) {
case QGCUASFileManager::kCmdTestNoAck:
case FileManager::kCmdTestNoAck:
// ignored, ack not sent back, for testing only
break;
case QGCUASFileManager::kCmdResetSessions:
case FileManager::kCmdResetSessions:
// terminates all sessions
// Fall through to send back Ack
case QGCUASFileManager::kCmdNone:
case FileManager::kCmdNone:
// ignored, always acked
ackResponse.hdr.opcode = QGCUASFileManager::kRspAck;
ackResponse.hdr.opcode = FileManager::kRspAck;
ackResponse.hdr.session = 0;
ackResponse.hdr.size = 0;
_emitResponse(&ackResponse, outgoingSeqNumber);
break;
case QGCUASFileManager::kCmdListDirectory:
case FileManager::kCmdListDirectory:
_listCommand(request, incomingSeqNumber);
break;
case QGCUASFileManager::kCmdOpenFile:
case FileManager::kCmdOpenFileRO:
_openCommand(request, incomingSeqNumber);
break;
case QGCUASFileManager::kCmdReadFile:
case FileManager::kCmdReadFile:
_readCommand(request, incomingSeqNumber);
break;
case QGCUASFileManager::kCmdTerminateSession:
case FileManager::kCmdBurstReadFile:
_streamCommand(request, incomingSeqNumber);
break;
case FileManager::kCmdTerminateSession:
_terminateCommand(request, incomingSeqNumber);
break;
default:
// nack for all NYI opcodes
_sendNak(QGCUASFileManager::kErrUnknownCommand, outgoingSeqNumber);
_sendNak(FileManager::kErrUnknownCommand, outgoingSeqNumber, (FileManager::Opcode)request->hdr.opcode);
break;
}
}
/// @brief Sends an Ack
void MockMavlinkFileServer::_sendAck(uint16_t seqNumber)
void MockMavlinkFileServer::_sendAck(uint16_t seqNumber, FileManager::Opcode reqOpcode)
{
QGCUASFileManager::Request ackResponse;
FileManager::Request ackResponse;
ackResponse.hdr.opcode = QGCUASFileManager::kRspAck;
ackResponse.hdr.opcode = FileManager::kRspAck;
ackResponse.hdr.req_opcode = reqOpcode;
ackResponse.hdr.session = 0;
ackResponse.hdr.size = 0;
......@@ -285,11 +343,12 @@ void MockMavlinkFileServer::_sendAck(uint16_t seqNumber)
}
/// @brief Sends a Nak with the specified error code.
void MockMavlinkFileServer::_sendNak(QGCUASFileManager::ErrorCode error, uint16_t seqNumber)
void MockMavlinkFileServer::_sendNak(FileManager::ErrorCode error, uint16_t seqNumber, FileManager::Opcode reqOpcode)
{
QGCUASFileManager::Request nakResponse;
FileManager::Request nakResponse;
nakResponse.hdr.opcode = QGCUASFileManager::kRspNak;
nakResponse.hdr.opcode = FileManager::kRspNak;
nakResponse.hdr.req_opcode = reqOpcode;
nakResponse.hdr.session = 0;
nakResponse.hdr.size = 1;
nakResponse.data[0] = error;
......@@ -298,7 +357,7 @@ void MockMavlinkFileServer::_sendNak(QGCUASFileManager::ErrorCode error, uint16_
}
/// @brief Emits a Request through the messageReceived signal.
void MockMavlinkFileServer::_emitResponse(QGCUASFileManager::Request* request, uint16_t seqNumber)
void MockMavlinkFileServer::_emitResponse(FileManager::Request* request, uint16_t seqNumber)
{
mavlink_message_t mavlinkMessage;
......
......@@ -25,7 +25,7 @@
#define MOCKMAVLINKFILESERVER_H
#include "MockMavlinkInterface.h"
#include "QGCUASFileManager.h"
#include "FileManager.h"
/// @file
/// @brief Mock implementation of Mavlink FTP server. Used as mavlink plugin to MockUAS.
......@@ -77,7 +77,8 @@ public:
struct FileTestCase {
const char* filename; ///< Filename to download
uint8_t length; ///< Length of file in bytes
bool fMultiPacketResponse; ///< true: multiple acks required to download, false: single ack contains entire download
int packetCount; ///< Number of packets required for data
bool exactFit; ///< true: last packet is exact fit, false: last packet is partially filled
};
/// @brief The numbers of test cases in the rgFileTestCases array.
......@@ -91,13 +92,14 @@ signals:
void terminateCommandReceived(void);
private:
void _sendAck(uint16_t seqNumber);
void _sendNak(QGCUASFileManager::ErrorCode error, uint16_t seqNumber);
void _emitResponse(QGCUASFileManager::Request* request, uint16_t seqNumber);
void _listCommand(QGCUASFileManager::Request* request, uint16_t seqNumber);
void _openCommand(QGCUASFileManager::Request* request, uint16_t seqNumber);
void _readCommand(QGCUASFileManager::Request* request, uint16_t seqNumber);
void _terminateCommand(QGCUASFileManager::Request* request, uint16_t seqNumber);
void _sendAck(uint16_t seqNumber, FileManager::Opcode reqOpcode);
void _sendNak(FileManager::ErrorCode error, uint16_t seqNumber, FileManager::Opcode reqOpcode);
void _emitResponse(FileManager::Request* request, uint16_t seqNumber);
void _listCommand(FileManager::Request* request, uint16_t seqNumber);
void _openCommand(FileManager::Request* request, uint16_t seqNumber);
void _readCommand(FileManager::Request* request, uint16_t seqNumber);
void _streamCommand(FileManager::Request* request, uint16_t seqNumber);
void _terminateCommand(FileManager::Request* request, uint16_t seqNumber);
uint16_t _nextSeqNumber(uint16_t seqNumber);
QStringList _fileList; ///< List of files returned by List command
......
......@@ -21,7 +21,7 @@
======================================================================*/
#include "QGCUASFileManager.h"
#include "FileManager.h"
#include "QGC.h"
#include "MAVLinkProtocol.h"
#include "MainWindow.h"
......@@ -30,8 +30,9 @@
#include <QDir>
#include <string>
QGC_LOGGING_CATEGORY(FileManagerLog, "FileManagerLog")
QGCUASFileManager::QGCUASFileManager(QObject* parent, UASInterface* uas, uint8_t unitTestSystemIdQGC) :
FileManager::FileManager(QObject* parent, UASInterface* uas, uint8_t unitTestSystemIdQGC) :
QObject(parent),
_currentOperation(kCOIdle),
_mav(uas),
......@@ -39,7 +40,7 @@ QGCUASFileManager::QGCUASFileManager(QObject* parent, UASInterface* uas, uint8_t
_activeSession(0),
_systemIdQGC(unitTestSystemIdQGC)
{
connect(&_ackTimer, &QTimer::timeout, this, &QGCUASFileManager::_ackTimeout);
connect(&_ackTimer, &QTimer::timeout, this, &FileManager::_ackTimeout);
_systemIdServer = _mav->getUASID();
......@@ -48,9 +49,10 @@ QGCUASFileManager::QGCUASFileManager(QObject* parent, UASInterface* uas, uint8_t
}
/// @brief Respond to the Ack associated with the Open command with the next Read command.
void QGCUASFileManager::_openAckResponse(Request* openAck)
void FileManager::_openAckResponse(Request* openAck)
{
_currentOperation = kCORead;
Q_ASSERT(_currentOperation == kCOOpenRead || _currentOperation == kCOOpenStream);
_currentOperation = _currentOperation == kCOOpenRead ? kCORead : kCOBurst;
_activeSession = openAck->hdr.session;
// File length comes back in data
......@@ -59,13 +61,14 @@ void QGCUASFileManager::_openAckResponse(Request* openAck)
// Start the sequence of read commands
_readOffset = 0; // Start reading at beginning of file
_downloadOffset = 0; // Start reading at beginning of file
_readFileAccumulator.clear(); // Start with an empty file
Request request;
request.hdr.session = _activeSession;
request.hdr.opcode = kCmdReadFile;
request.hdr.offset = _readOffset;
Q_ASSERT(_currentOperation == kCORead || _currentOperation == kCOBurst);
request.hdr.opcode = _currentOperation == kCORead ? kCmdReadFile : kCmdBurstReadFile;
request.hdr.offset = _downloadOffset;
request.hdr.size = sizeof(request.data);
_sendRequest(&request);
......@@ -73,7 +76,7 @@ void QGCUASFileManager::_openAckResponse(Request* openAck)
/// @brief Closes out a read session by writing the file and doing cleanup.
/// @param success true: successful download completion, false: error during download
void QGCUASFileManager::_closeReadSession(bool success)
void FileManager::_closeDownloadSession(bool success)
{
if (success) {
QString downloadFilePath = _readFileDownloadDir.absoluteFilePath(_readFileDownloadFilename);
......@@ -99,49 +102,54 @@ void QGCUASFileManager::_closeReadSession(bool success)
_sendTerminateCommand();
}
/// @brief Respond to the Ack associated with the Read command.
void QGCUASFileManager::_readAckResponse(Request* readAck)
/// 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)
{
if (readAck->hdr.session != _activeSession) {
_currentOperation = kCOIdle;
_readFileAccumulator.clear();
_emitErrorMessage(tr("Read: Incorrect session returned"));
_emitErrorMessage(tr("Download: Incorrect session returned"));
return;
}
if (readAck->hdr.offset != _readOffset) {
if (readAck->hdr.offset != _downloadOffset) {
_currentOperation = kCOIdle;
_readFileAccumulator.clear();
_emitErrorMessage(tr("Read: Offset returned (%1) differs from offset requested (%2)").arg(readAck->hdr.offset).arg(_readOffset));
_emitErrorMessage(tr("Download: Offset returned (%1) differs from offset requested/expected (%2)").arg(readAck->hdr.offset).arg(_downloadOffset));
return;
}
qCDebug(FileManagerLog) << QString("_downloadAckResponse: offset(%1) size(%2) burstComplete(%3)").arg(readAck->hdr.offset).arg(readAck->hdr.size).arg(readAck->hdr.burstComplete);
_downloadOffset += readAck->hdr.size;
_readFileAccumulator.append((const char*)readAck->data, readAck->hdr.size);
emit downloadFileProgress(_readFileAccumulator.length());
if (readAck->hdr.size == sizeof(readAck->data)) {
// Possibly still more data to read, send next read request
_currentOperation = kCORead;
_readOffset += readAck->hdr.size;
Request request;
request.hdr.session = _activeSession;
request.hdr.opcode = kCmdReadFile;
request.hdr.offset = _readOffset;
request.hdr.size = 0;
_sendRequest(&request);
} else {
if (readFile || readAck->hdr.burstComplete) {
// Possibly still more data to read, send next read request
Request request;
request.hdr.session = _activeSession;
request.hdr.opcode = readFile ? kCmdReadFile : kCmdBurstReadFile;
request.hdr.offset = _downloadOffset;
request.hdr.size = 0;
_sendRequest(&request);
} else {
// Streaming, so next ack should come automatically
_setupAckTimeout();
}
} else if (readFile) {
// We only receieved a partial buffer back. These means we are at EOF
_currentOperation = kCOIdle;
_closeReadSession(true /* success */);
_closeDownloadSession(true /* success */);
}
}
/// @brief Respond to the Ack associated with the List command.
void QGCUASFileManager::_listAckResponse(Request* listAck)
void FileManager::_listAckResponse(Request* listAck)
{
if (listAck->hdr.offset != _listOffset) {
_currentOperation = kCOIdle;
......@@ -199,9 +207,8 @@ void QGCUASFileManager::_listAckResponse(Request* listAck)
}
}
/// @brief Respond to the Ack associated with the create command.
void QGCUASFileManager::_createAckResponse(Request* createAck)
void FileManager::_createAckResponse(Request* createAck)
{
_currentOperation = kCOWrite;
_activeSession = createAck->hdr.session;
......@@ -215,7 +222,7 @@ void QGCUASFileManager::_createAckResponse(Request* createAck)
}
/// @brief Respond to the Ack associated with the write command.
void QGCUASFileManager::_writeAckResponse(Request* writeAck)
void FileManager::_writeAckResponse(Request* writeAck)
{
if(_writeOffset + _writeSize >= _writeFileSize){
_writeFileAccumulator.clear();
......@@ -256,9 +263,8 @@ void QGCUASFileManager::_writeAckResponse(Request* writeAck)
_writeFileDatablock();
}
/// @brief Send next write file data block.
void QGCUASFileManager::_writeFileDatablock(void)
void FileManager::_writeFileDatablock(void)
{
/// @brief Maximum data size in RequestHeader::data
// static const uint8_t kMaxDataLength = MAVLINK_MSG_FILE_TRANSFER_PROTOCOL_FIELD_PAYLOAD_LEN - sizeof(RequestHeader);
......@@ -288,8 +294,7 @@ void QGCUASFileManager::_writeFileDatablock(void)
_sendRequest(&request);
}
void QGCUASFileManager::receiveMessage(LinkInterface* link, mavlink_message_t message)
void FileManager::receiveMessage(LinkInterface* link, mavlink_message_t message)
{
Q_UNUSED(link);
......@@ -297,13 +302,13 @@ void QGCUASFileManager::receiveMessage(LinkInterface* link, mavlink_message_t me
if (message.msgid != MAVLINK_MSG_ID_FILE_TRANSFER_PROTOCOL) {
return;
}
mavlink_file_transfer_protocol_t data;
mavlink_msg_file_transfer_protocol_decode(&message, &data);
// Make sure we are the target system
if (data.target_system != _systemIdQGC) {
qDebug() << "Received MAVLINK_MSG_ID_FILE_TRANSFER_PROTOCOL with possibly incorrect system id" << _systemIdQGC;
qDebug() << "Received MAVLINK_MSG_ID_FILE_TRANSFER_PROTOCOL with incorrect target_system:" << data.target_system << "expected:" << _systemIdQGC;
return;
}
......@@ -311,6 +316,8 @@ void QGCUASFileManager::receiveMessage(LinkInterface* link, mavlink_message_t me
_clearAckTimeout();
qCDebug(FileManagerLog) << "receiveMessage" << request->hdr.opcode;
uint16_t incomingSeqNumber = request->hdr.seqNumber;
// Make sure we have a good sequence number
......@@ -325,45 +332,38 @@ void QGCUASFileManager::receiveMessage(LinkInterface* link, mavlink_message_t me
_lastOutgoingSeqNumber = incomingSeqNumber;
if (request->hdr.opcode == kRspAck) {
switch (_currentOperation) {
case kCOIdle:
// we should not be seeing anything here.. shut the other guy up
_sendCmdReset();
break;
case kCOAck:
// We are expecting an ack back
_currentOperation = kCOIdle;
break;
case kCOList:
_listAckResponse(request);
break;
case kCOOpen:
_openAckResponse(request);
break;
case kCORead:
_readAckResponse(request);
break;
case kCOCreate:
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:
_createAckResponse(request);
break;
case kCOWrite:
case kCmdWriteFile:
_writeAckResponse(request);
break;
default:
_emitErrorMessage(tr("Ack received in unexpected state"));
break;
}
default:
// Ack back from operation which does not require additional work
_currentOperation = kCOIdle;
break;
}
} else if (request->hdr.opcode == kRspNak) {
OperationState previousOperation = _currentOperation;
uint8_t errorCode = request->data[0];
// Nak's normally have 1 byte of data for error code, except for kErrFailErrno which has additional byte for errno
......@@ -371,24 +371,24 @@ void QGCUASFileManager::receiveMessage(LinkInterface* link, mavlink_message_t me
_currentOperation = kCOIdle;
if (previousOperation == kCOList && errorCode == kErrEOF) {
// This is not an error, just the end of the read loop
if (request->hdr.req_opcode == kCmdListDirectory && errorCode == kErrEOF) {
// This is not an error, just the end of the list loop
emit listComplete();
return;
} else if (previousOperation == kCORead && errorCode == kErrEOF) {
// This is not an error, just the end of the read loop
_closeReadSession(true /* success */);
} 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 */);
return;
} else if (previousOperation == kCOCreate) {
} else if (request->hdr.req_opcode == kCmdCreateFile) {
// End a failed create file operation
_sendTerminateCommand();
_emitErrorMessage(tr("Nak received creating file, error: %1").arg(errorString(request->data[0])));
return;
} else {
// Generic Nak handling
if (previousOperation == kCORead) {
// Nak error during read loop, download failed
_closeReadSession(false /* failure */);
if (request->hdr.req_opcode == kCmdReadFile || request->hdr.req_opcode == kCmdBurstReadFile) {
// Nak error during download loop, download failed
_closeDownloadSession(false /* failure */);
}
_emitErrorMessage(tr("Nak received, error: %1").arg(errorString(request->data[0])));
}
......@@ -399,7 +399,7 @@ void QGCUASFileManager::receiveMessage(LinkInterface* link, mavlink_message_t me
}
}
void QGCUASFileManager::listDirectory(const QString& dirPath)
void FileManager::listDirectory(const QString& dirPath)
{
if (_currentOperation != kCOIdle) {
_emitErrorMessage(tr("Command not sent. Waiting for previous command to complete."));
......@@ -415,13 +415,13 @@ void QGCUASFileManager::listDirectory(const QString& dirPath)
_sendListCommand();
}
void QGCUASFileManager::_fillRequestWithString(Request* request, const QString& str)
void FileManager::_fillRequestWithString(Request* request, const QString& str)
{
strncpy((char *)&request->data[0], str.toStdString().c_str(), sizeof(request->data));
request->hdr.size = static_cast<uint8_t>(strnlen((const char *)&request->data[0], sizeof(request->data)));
}
void QGCUASFileManager::_sendListCommand(void)
void FileManager::_sendListCommand(void)
{
Request request;
......@@ -432,46 +432,57 @@ void QGCUASFileManager::_sendListCommand(void)
_fillRequestWithString(&request, _listPath);
qCDebug(FileManagerLog) << "listDirectory: path:" << _listPath << "offset:" << _listOffset;
_sendRequest(&request);
}
/// @brief Downloads the specified file.
/// @param from File to download from UAS, fully qualified path
/// @param downloadDir Local directory to download file to
void QGCUASFileManager::downloadPath(const QString& from, const QDir& downloadDir)
void FileManager::downloadPath(const QString& from, const QDir& downloadDir)
{
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);
qCDebug(FileManagerLog) << "downloadPath from:" << from << "to:" << downloadDir;
_downloadWorker(from, downloadDir, true /* read file */);
}
_currentOperation = kCOOpen;
void FileManager::streamPath(const QString& from, const QDir& downloadDir)
{
qCDebug(FileManagerLog) << "streamPath from:" << from << "to:" << downloadDir;
_downloadWorker(from, downloadDir, false /* stream file */);
}
Request request;
request.hdr.session = 0;
request.hdr.opcode = kCmdOpenFile;
request.hdr.offset = 0;
request.hdr.size = 0;
_fillRequestWithString(&request, from);
_sendRequest(&request);
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);
_currentOperation = readFile ? kCOOpenRead : kCOOpenStream;
Request request;
request.hdr.session = 0;
request.hdr.opcode = kCmdOpenFileRO;
request.hdr.offset = 0;
request.hdr.size = 0;
_fillRequestWithString(&request, from);
_sendRequest(&request);
}
/// @brief Uploads the specified file.
/// @param toPath File in UAS to upload to, fully qualified path
/// @param uploadFile Local file to upload from
void QGCUASFileManager::uploadPath(const QString& toPath, const QFileInfo& uploadFile)
void FileManager::uploadPath(const QString& toPath, const QFileInfo& uploadFile)
{
if(_currentOperation != kCOIdle){
_emitErrorMessage(tr("UAS File manager busy. Try again later"));
......@@ -513,9 +524,7 @@ void QGCUASFileManager::uploadPath(const QString& toPath, const QFileInfo& uploa
_sendRequest(&request);
}
QString QGCUASFileManager::errorString(uint8_t errorCode)
QString FileManager::errorString(uint8_t errorCode)
{
switch(errorCode) {
case kErrNone:
......@@ -547,7 +556,7 @@ QString QGCUASFileManager::errorString(uint8_t errorCode)
/// @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
bool QGCUASFileManager::_sendOpcodeOnlyCmd(uint8_t opcode, OperationState newOpState)
bool FileManager::_sendOpcodeOnlyCmd(uint8_t opcode, OperationState newOpState)
{
if (_currentOperation != kCOIdle) {
// Can't have multiple commands in play at the same time
......@@ -568,8 +577,10 @@ bool QGCUASFileManager::_sendOpcodeOnlyCmd(uint8_t opcode, OperationState newOpS
}
/// @brief Starts the ack timeout timer
void QGCUASFileManager::_setupAckTimeout(void)
void FileManager::_setupAckTimeout(void)
{
qCDebug(FileManagerLog) << "_setupAckTimeout";
Q_ASSERT(!_ackTimer.isActive());
_ackTimer.setSingleShot(true);
......@@ -577,43 +588,50 @@ void QGCUASFileManager::_setupAckTimeout(void)
}
/// @brief Clears the ack timeout timer
void QGCUASFileManager::_clearAckTimeout(void)
void FileManager::_clearAckTimeout(void)
{
qCDebug(FileManagerLog) << "_clearAckTimeout";
_ackTimer.stop();
}
/// @brief Called when ack timeout timer fires
void QGCUASFileManager::_ackTimeout(void)
void FileManager::_ackTimeout(void)
{
qCDebug(FileManagerLog) << "_ackTimeout";
// 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.
switch (_currentOperation) {
case kCORead:
case kCOBurst:
_closeDownloadSession(false /* failure */);
_currentOperation = kCOAck;
_emitErrorMessage(tr("Timeout waiting for ack: Sending Terminate command"));
_sendTerminateCommand();
break;
case kCOCreate:
_currentOperation = kCOAck;
_writeFileAccumulator.clear();
_emitErrorMessage(tr("Timeout waiting for ack: Sending Create command"));
_emitErrorMessage(tr("Timeout waiting for ack: Sending Terminate command"));
_sendTerminateCommand();
break;
case kCOWrite:
_currentOperation = kCOAck;
_writeFileAccumulator.clear();
_emitErrorMessage(tr("Timeout waiting for ack: Sending Write command"));
_sendTerminateCommand();
_emitErrorMessage(tr("Timeout waiting for ack: Sending Terminate command"));
break;
default:
_currentOperation = kCOIdle;
_emitErrorMessage(tr("Timeout waiting for ack"));
_emitErrorMessage(QString("Timeout waiting for ack: operation (%1)").arg(_currentOperation));
break;
}
}
void QGCUASFileManager::_sendTerminateCommand(void)
void FileManager::_sendTerminateCommand(void)
{
Request request;
request.hdr.session = _activeSession;
......@@ -622,21 +640,23 @@ void QGCUASFileManager::_sendTerminateCommand(void)
_sendRequest(&request);
}
void QGCUASFileManager::_emitErrorMessage(const QString& msg)
void FileManager::_emitErrorMessage(const QString& msg)
{
qDebug() << "QGCUASFileManager: Error" << msg;
qCDebug(FileManagerLog) << "Error:" << msg;
emit errorMessage(msg);
}
void QGCUASFileManager::_emitListEntry(const QString& entry)
void FileManager::_emitListEntry(const QString& entry)
{
qDebug() << "QGCUASFileManager: list entry" << entry;
qCDebug(FileManagerLog) << "_emitListEntry" << entry;
emit listEntry(entry);
}
/// @brief Sends the specified Request out to the UAS.
void QGCUASFileManager::_sendRequest(Request* request)
void FileManager::_sendRequest(Request* request)
{
qCDebug(FileManagerLog) << "_sendRequest opcode:" << request->hdr.opcode;
mavlink_message_t message;
_setupAckTimeout();
......
......@@ -21,29 +21,49 @@
======================================================================*/
#ifndef QGCUASFILEMANAGER_H
#define QGCUASFILEMANAGER_H
#ifndef FILEMANAGER_H
#define FILEMANAGER_H
#include <QObject>
#include <QDir>
#include "UASInterface.h"
#include "QGCLoggingCategory.h"
class QGCUASFileManager : public QObject
Q_DECLARE_LOGGING_CATEGORY(FileManagerLog)
class FileManager : public QObject
{
Q_OBJECT
public:
QGCUASFileManager(QObject* parent, UASInterface* uas, uint8_t unitTestSystemIdQGC = 0);
FileManager(QObject* parent, UASInterface* uas, uint8_t unitTestSystemIdQGC = 0);
/// These methods are only used for testing purposes.
bool _sendCmdTestAck(void) { return _sendOpcodeOnlyCmd(kCmdNone, kCOAck); };
bool _sendCmdTestNoAck(void) { return _sendOpcodeOnlyCmd(kCmdTestNoAck, kCOAck); };
bool _sendCmdReset(void) { return _sendOpcodeOnlyCmd(kCmdResetSessions, kCOAck); };
/// @brief Timeout in msecs to wait for an Ack time come back. This is public so we can write unit tests which wait long enough
/// Timeout in msecs to wait for an Ack time come back. This is public so we can write unit tests which wait long enough
/// for the FileManager to timeout.
static const int ackTimerTimeoutMsecs = 1000;
static const int ackTimerTimeoutMsecs = 10000;
/// Downloads the specified file.
/// @param from File to download from UAS, fully qualified path
/// @param downloadDir Local directory to download file to
void downloadPath(const QString& from, const QDir& downloadDir);
/// Stream downloads the specified file.
/// @param from File to download from UAS, fully qualified path
/// @param downloadDir Local directory to download file to
void streamPath(const QString& from, const QDir& downloadDir);
/// Lists the specified directory. Emits listEntry signal for each entry, followed by listComplete signal.
/// @param dirPath Fully qualified path to list
void listDirectory(const QString& dirPath);
/// Upload the specified file to the specified location
void uploadPath(const QString& toPath, const QFileInfo& uploadFile);
signals:
/// @brief Signalled whenever an error occurs during the listDirectory or downloadPath methods.
void errorMessage(const QString& msg);
......@@ -80,23 +100,23 @@ signals:
public slots:
void receiveMessage(LinkInterface* link, mavlink_message_t message);
void listDirectory(const QString& dirPath);
void downloadPath(const QString& from, const QDir& downloadDir);
void uploadPath(const QString& toPath, const QFileInfo& uploadFile);
private slots:
void _ackTimeout(void);
protected:
private:
/// @brief This is the fixed length portion of the protocol data. Trying to pack structures across differing compilers is
/// questionable, so we pad the structure ourselves to 32 bit alignment which should get us what we want.
struct RequestHeader
{
uint16_t seqNumber; ///< sequence number for message
uint8_t session; ///< Session id for read and write commands
uint8_t opcode; ///< Command opcode
uint8_t size; ///< Size of data
uint8_t req_opcode; ///< Request opcode returned in kRspAck, kRspNak message
uint8_t padding[2]; ///< 32 bit aligment padding
uint32_t offset; ///< Offsets for List and Read commands
uint16_t seqNumber; ///< sequence number for message
uint8_t session; ///< Session id for read and write commands
uint8_t opcode; ///< Command opcode
uint8_t size; ///< Size of data
uint8_t req_opcode; ///< Request opcode returned in kRspAck, kRspNak message
uint8_t burstComplete; ///< Only used if req_opcode=kCmdBurstReadFile - 1: set of burst packets complete, 0: More burst packets coming.
uint8_t padding; ///< 32 bit aligment padding
uint32_t offset; ///< Offsets for List and Read commands
};
struct Request
......@@ -108,7 +128,7 @@ protected:
// The entire Request must fit into the payload member of the mavlink_file_transfer_protocol_t structure. We use as many leftover bytes
// after we use up space for the RequestHeader for the data portion of the Request.
uint8_t data[sizeof(((mavlink_file_transfer_protocol_t*)0)->payload) - sizeof(RequestHeader)];
// File length returned by Open command
uint32_t openFileLength;
......@@ -119,17 +139,22 @@ protected:
enum Opcode
{
kCmdNone, ///< ignored, always acked
kCmdNone, ///< ignored, always acked
kCmdTerminateSession, ///< Terminates open Read session
kCmdResetSessions, ///< Terminates all open Read sessions
kCmdListDirectory, ///< List files in <path> from <offset>
kCmdOpenFile, ///< Opens file at <path> for reading, returns <session>
kCmdReadFile, ///< Reads <size> bytes from <offset> in <session>
kCmdCreateFile, ///< Creates file at <path> for writing, returns <session>
kCmdWriteFile, ///< Appends <size> bytes to file in <session>
kCmdRemoveFile, ///< Remove file at <path>
kCmdResetSessions, ///< Terminates all open Read sessions
kCmdListDirectory, ///< List files in <path> from <offset>
kCmdOpenFileRO, ///< Opens file at <path> for reading, returns <session>
kCmdReadFile, ///< Reads <size> bytes from <offset> in <session>
kCmdCreateFile, ///< Creates file at <path> for writing, returns <session>
kCmdWriteFile, ///< Writes <size> bytes to <offset> in <session>
kCmdRemoveFile, ///< Remove file at <path>
kCmdCreateDirectory, ///< Creates directory at <path>
kCmdRemoveDirectory, ///< Removes Directory at <path>, must be empty
kCmdOpenFileWO, ///< Opens file at <path> for writing, returns <session>
kCmdTruncateFile, ///< Truncate file at <path> to <offset> length
kCmdRename, ///< Rename <path1> to <path2>
kCmdCalcFileCRC32, ///< Calculate CRC32 for file at <path>
kCmdBurstReadFile, ///< Burst download session file
kRspAck = 128, ///< Ack response
kRspNak, ///< Nak response
......@@ -155,20 +180,17 @@ protected:
enum OperationState
{
kCOIdle, // not doing anything
kCOAck, // waiting for an Ack
kCOList, // waiting for List response
kCOOpen, // waiting for Open response
kCORead, // waiting for Read response
kCOCreate, // waiting for Create response
kCOWrite, // waiting for Write response
kCOIdle, // not doing anything
kCOAck, // waiting for an Ack
kCOList, // waiting for List response
kCOOpenRead, // waiting for Open response followed by Read download
kCOOpenStream, // waiting for Open response, followed by Stream download
kCORead, // waiting for Read response
kCOBurst, // waiting for Burst response
kCOWrite, // waiting for Write response
kCOCreate, // waiting for Create response
};
protected slots:
void _ackTimeout(void);
protected:
bool _sendOpcodeOnlyCmd(uint8_t opcode, OperationState newOpState);
void _setupAckTimeout(void);
void _clearAckTimeout(void);
......@@ -177,14 +199,15 @@ protected:
void _sendRequest(Request* request);
void _fillRequestWithString(Request* request, const QString& str);
void _openAckResponse(Request* openAck);
void _readAckResponse(Request* readAck);
void _downloadAckResponse(Request* readAck, bool readFile);
void _listAckResponse(Request* listAck);
void _createAckResponse(Request* createAck);
void _writeAckResponse(Request* writeAck);
void _writeFileDatablock(void);
void _sendListCommand(void);
void _sendTerminateCommand(void);
void _closeReadSession(bool success);
void _closeDownloadSession(bool success);
void _downloadWorker(const QString& from, const QDir& downloadDir, bool readFile);
static QString errorString(uint8_t errorCode);
......@@ -203,6 +226,7 @@ protected:
uint32_t _writeOffset; ///< current write offset
uint32_t _writeSize; ///< current write data size
uint32_t _writeFileSize; ///< current write file size
uint32_t _downloadOffset; ///< current download offset
QByteArray _readFileAccumulator; ///< Holds file being downloaded
QByteArray _writeFileAccumulator; ///< Holds file being uploaded
QDir _readFileDownloadDir; ///< Directory to download file to
......@@ -216,4 +240,4 @@ protected:
friend class MockMavlinkFileServer;
};
#endif // QGCUASFILEMANAGER_H
#endif // FileManager_H
......@@ -40,7 +40,7 @@ This file is part of the QGROUNDCONTROL project
#include "QGCFlightGearLink.h"
#include "QGCJSBSimLink.h"
#include "QGCXPlaneLink.h"
#include "QGCUASFileManager.h"
#include "FileManager.h"
Q_DECLARE_LOGGING_CATEGORY(UASLog)
......@@ -332,7 +332,7 @@ public:
bool isFixedWing();
friend class UASWaypointManager;
friend class QGCUASFileManager;
friend class FileManager;
protected: //COMMENTS FOR TEST UNIT
/// LINK ID AND STATUS
......@@ -435,7 +435,7 @@ protected: //COMMENTS FOR TEST UNIT
double groundSpeed; ///< Groundspeed
double bearingToWaypoint; ///< Bearing to next waypoint
UASWaypointManager waypointManager;
QGCUASFileManager fileManager;
FileManager fileManager;
/// ATTITUDE
bool attitudeKnown; ///< True if attitude was received, false else
......@@ -487,7 +487,7 @@ public:
return &waypointManager;
}
virtual QGCUASFileManager* getFileManager() {
virtual FileManager* getFileManager() {
return &fileManager;
}
......
......@@ -42,7 +42,7 @@ This file is part of the QGROUNDCONTROL project
#include "ProtocolInterface.h"
#include "UASWaypointManager.h"
class QGCUASFileManager;
class FileManager;
enum BatteryType
{
......@@ -132,7 +132,7 @@ public:
/** @brief Get reference to the waypoint manager **/
virtual UASWaypointManager* getWaypointManager(void) = 0;
virtual QGCUASFileManager* getFileManager() = 0;
virtual FileManager* getFileManager() = 0;
/** @brief Send a message over this link (to this or to all UAS on this link) */
virtual void sendMessage(LinkInterface* link, mavlink_message_t message) = 0;
......
......@@ -22,14 +22,14 @@
======================================================================*/
#include "QGCUASFileView.h"
#include "uas/QGCUASFileManager.h"
#include "uas/FileManager.h"
#include "QGCFileDialog.h"
#include <QFileDialog>
#include <QDir>
#include <QDebug>
QGCUASFileView::QGCUASFileView(QWidget *parent, QGCUASFileManager *manager) :
QGCUASFileView::QGCUASFileView(QWidget *parent, FileManager *manager) :
QWidget(parent),
_manager(manager),
_listInProgress(false),
......@@ -83,13 +83,12 @@ void QGCUASFileView::_downloadFile(void)
path.prepend("/" + name);
item = item->parent();
} while (item);
qDebug() << "Download: " << path;
_ui.downloadButton->setEnabled(false);
_downloadInProgress = true;
_connectDownloadSignals();
_manager->downloadPath(path, QDir(downloadToHere));
_manager->streamPath(path, QDir(downloadToHere));
}
}
......@@ -163,7 +162,7 @@ void QGCUASFileView::_downloadProgress(unsigned int bytesReceived)
}
}
/// @brief Called when the download associated with the QGCUASFileManager::downloadPath command completes.
/// @brief Called when the download associated with the FileManager::downloadPath command completes.
void QGCUASFileView::_downloadComplete(void)
{
Q_ASSERT(_downloadInProgress);
......@@ -305,11 +304,10 @@ void QGCUASFileView::_currentItemChanged(QTreeWidgetItem* current, QTreeWidgetIt
void QGCUASFileView::_requestDirectoryList(const QString& dir)
{
qDebug() << "List:" << dir;
_manager->listDirectory(dir);
}
/// @brief Connects to the signals associated with the QGCUASFileManager::downloadPath method. We only leave these signals connected
/// @brief Connects to the signals associated with the FileManager::downloadPath method. We only leave these signals connected
/// while a download because there may be multiple UAS, which in turn means multiple QGCUASFileView instances. We only want the signals
/// connected to the active FileView which is doing the current download.
void QGCUASFileView::_connectDownloadSignals(void)
......@@ -335,7 +333,7 @@ void QGCUASFileView::_disconnectDownloadSignals(void)
disconnect(_manager, SIGNAL(errorMessage(const QString&)), this, SLOT(_downloadErrorMessage(const QString&)));
}
/// @brief Connects to the signals associated with the QGCUASFileManager::listDirectory method. We only leave these signals connected
/// @brief Connects to the signals associated with the FileManager::listDirectory method. We only leave these signals connected
/// while a download because there may be multiple UAS, which in turn means multiple QGCUASFileView instances. We only want the signals
/// connected to the active FileView which is doing the current download.
void QGCUASFileView::_connectListSignals(void)
......
......@@ -27,7 +27,7 @@
#include <QWidget>
#include <QTreeWidgetItem>
#include "uas/QGCUASFileManager.h"
#include "uas/FileManager.h"
#include "ui_QGCUASFileView.h"
class QGCUASFileView : public QWidget
......@@ -35,10 +35,10 @@ class QGCUASFileView : public QWidget
Q_OBJECT
public:
explicit QGCUASFileView(QWidget *parent, QGCUASFileManager *manager);
explicit QGCUASFileView(QWidget *parent, FileManager *manager);
protected:
QGCUASFileManager* _manager;
FileManager* _manager;
private slots:
void _refreshTree(void);
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment