diff --git a/src/qgcunittest/MockMavlinkFileServer.cc b/src/qgcunittest/MockMavlinkFileServer.cc index b17aa898938fd306f6c3b7d436e24baf21cc0ea8..8264edb4aecc10d4ef3a9c54a057ac6400cf078a 100644 --- a/src/qgcunittest/MockMavlinkFileServer.cc +++ b/src/qgcunittest/MockMavlinkFileServer.cc @@ -23,11 +23,160 @@ #include "MockMavlinkFileServer.h" -void MockMavlinkFileServer::sendMessage(mavlink_message_t message) +const char* MockMavlinkFileServer::smallFilename = "small"; +const char* MockMavlinkFileServer::largeFilename = "large"; + +// FIXME: -2 to avoid eof on full packet +const uint8_t MockMavlinkFileServer::smallFileLength = sizeof(((QGCUASFileManager::Request*)0)->data) - 2; +const uint8_t MockMavlinkFileServer::largeFileLength = sizeof(((QGCUASFileManager::Request*)0)->data) + 1; + +const uint8_t MockMavlinkFileServer::_sessionId = 1; + + +/// @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) { QGCUASFileManager::Request ackResponse; QString path; + // We only support root path + path = (char *)&request->data[0]; + if (!path.isEmpty() && path != "/") { + _sendNak(QGCUASFileManager::kErrNotDir); + return; + } + + // Offset requested is past the end of the list + if (request->hdr.offset > (uint32_t)_fileList.size()) { + _sendNak(QGCUASFileManager::kErrEOF); + return; + } + + ackResponse.hdr.magic = 'f'; + ackResponse.hdr.opcode = QGCUASFileManager::kRspAck; + ackResponse.hdr.session = 0; + ackResponse.hdr.size = 0; + + if (request->hdr.offset == 0) { + // Requesting first batch of file names + Q_ASSERT(_fileList.size()); + char *bufPtr = (char *)&ackResponse.data[0]; + for (int i=0; i<_fileList.size(); i++) { + const char *filename = _fileList[i].toStdString().c_str(); + size_t cchFilename = strlen(filename); + strcpy(bufPtr, filename); + ackResponse.hdr.size += cchFilename + 1; + bufPtr += cchFilename + 1; + } + + // Final double termination + *bufPtr = 0; + ackResponse.hdr.size++; + + } else { + // All filenames fit in first ack, send final null terminated ack + ackResponse.data[0] = 0; + ackResponse.hdr.size = 1; + } + + _emitResponse(&ackResponse); +} + +/// @brief Handles Open command requests. Two filenames are supported: +/// '/small' - file fits in a single packet +/// '/large' - file requires multiple packets to be sent +/// In all cases file contents are one byte data length, followed by a single +/// byte repeating increasing sequence (0x00, 0x01, .. 0xFF) for specified +/// number of bytes. +void MockMavlinkFileServer::_openCommand(QGCUASFileManager::Request* request) +{ + QGCUASFileManager::Request response; + QString path; + + // Make sure one byte of length is enough to overflow into two packets. + Q_ASSERT((sizeof(request->data) & 0xFF) == sizeof(request->data)); + + path = (char *)&request->data[0]; + if (path == smallFilename) { + _readFileLength = smallFileLength; + qDebug() << "Reading file length" << smallFileLength; + } else if (path == largeFilename) { + _readFileLength = largeFileLength; + qDebug() << "Reading file length" << largeFileLength; + } else { + _sendNak(QGCUASFileManager::kErrNotFile); + return; + } + + response.hdr.magic = 'f'; + response.hdr.opcode = QGCUASFileManager::kRspAck; + response.hdr.session = _sessionId; + response.hdr.size = 0; + + _emitResponse(&response); +} + +/// @brief Handles Read command requests. +void MockMavlinkFileServer::_readCommand(QGCUASFileManager::Request* request) +{ + qDebug() << "Read command:" << request->hdr.offset; + + QGCUASFileManager::Request response; + + if (request->hdr.session != _sessionId) { + _sendNak(QGCUASFileManager::kErrNoSession); + return; + } + + if (request->hdr.size > sizeof(response.data)) { + _sendNak(QGCUASFileManager::kErrPerm); + return; + } + + uint8_t size = 0; + uint32_t offset = request->hdr.offset; + + // Offset check is > instead of >= to take into accoutn extra length byte at beginning of file + if (offset > _readFileLength) { + _sendNak(QGCUASFileManager::kErrIO); + return; + } + + // Write length byte if needed + if (offset == 0) { + response.data[0] = _readFileLength; + offset++; + size++; + } + + // Write file bytes. Data is a repeating sequence of 0x00, 0x01, .. 0xFF. + for (; size < sizeof(response.data) && offset <= _readFileLength; offset++, size++) { + response.data[size] = (offset - 1) & 0xFF; + } + + qDebug() << "_readCommand bytes written" << size; + + // If we didn't write any bytes it was a bad request + if (size == 0) { + _sendNak(QGCUASFileManager::kErrEOF); + return; + } + + response.hdr.magic = 'f'; + response.hdr.opcode = QGCUASFileManager::kRspAck; + response.hdr.session = _sessionId; + response.hdr.size = size; + response.hdr.offset = request->hdr.offset; + + _emitResponse(&response); +} + +/// @brief Handles messages sent to the FTP server. +void MockMavlinkFileServer::sendMessage(mavlink_message_t message) +{ + QGCUASFileManager::Request ackResponse; + Q_ASSERT(message.msgid == MAVLINK_MSG_ID_ENCAPSULATED_DATA); mavlink_encapsulated_data_t requestEncapsulatedData; @@ -59,57 +208,21 @@ void MockMavlinkFileServer::sendMessage(mavlink_message_t message) break; case QGCUASFileManager::kCmdList: - // We only support root path - path = (char *)&request->data[0]; - if (!path.isEmpty() && path != "/") { - _sendNak(QGCUASFileManager::kErrNotDir); - break; - } - - if (request->hdr.offset > (uint32_t)_fileList.size()) { - _sendNak(QGCUASFileManager::kErrEOF); - break; - } - - ackResponse.hdr.magic = 'f'; - ackResponse.hdr.opcode = QGCUASFileManager::kRspAck; - ackResponse.hdr.session = 0; - ackResponse.hdr.size = 0; - - if (request->hdr.offset == 0) { - // Requesting first batch of file names - Q_ASSERT(_fileList.size()); - char *bufPtr = (char *)&ackResponse.data[0]; - for (int i=0; i<_fileList.size(); i++) { - const char *filename = _fileList[i].toStdString().c_str(); - size_t cchFilename = strlen(filename); - strcpy(bufPtr, filename); - ackResponse.hdr.size += cchFilename + 1; - bufPtr += cchFilename + 1; - } - - // Final double termination - *bufPtr = 0; - ackResponse.hdr.size++; - - } else { - // All filenames fit in first ack, send final null terminated ack - ackResponse.data[0] = 0; - ackResponse.hdr.size = 1; - } + _listCommand(request); + break; - _emitResponse(&ackResponse); + case QGCUASFileManager::kCmdOpen: + _openCommand(request); + break; + case QGCUASFileManager::kCmdRead: + _readCommand(request); break; - + // Remainder of commands are NYI case QGCUASFileManager::kCmdTerminate: // releases sessionID, closes file - case QGCUASFileManager::kCmdOpen: - // opens for reading, returns - case QGCUASFileManager::kCmdRead: - // reads bytes from in case QGCUASFileManager::kCmdCreate: // creates for writing, returns case QGCUASFileManager::kCmdWrite: @@ -123,6 +236,7 @@ void MockMavlinkFileServer::sendMessage(mavlink_message_t message) } } +/// @brief Sends a Nak with the specified error code. void MockMavlinkFileServer::_sendNak(QGCUASFileManager::ErrorCode error) { QGCUASFileManager::Request nakResponse; @@ -136,6 +250,7 @@ void MockMavlinkFileServer::_sendNak(QGCUASFileManager::ErrorCode error) _emitResponse(&nakResponse); } +/// @brief Emits a Request through the messageReceived signal. void MockMavlinkFileServer::_emitResponse(QGCUASFileManager::Request* request) { mavlink_message_t mavlinkMessage; diff --git a/src/qgcunittest/MockMavlinkFileServer.h b/src/qgcunittest/MockMavlinkFileServer.h index 0bccd3d4ab0915c27611210b6a686e3063904a68..6abe12fc8d0206db01b1e4ead1906d21b761c335 100644 --- a/src/qgcunittest/MockMavlinkFileServer.h +++ b/src/qgcunittest/MockMavlinkFileServer.h @@ -27,6 +27,11 @@ #include "MockMavlinkInterface.h" #include "QGCUASFileManager.h" +/// @file +/// @brief Mock implementation of Mavlink FTP server. Used as mavlink plugin to MockUAS. +/// Only root directory access is supported. +/// +/// @author Don Gagne #include @@ -37,16 +42,29 @@ class MockMavlinkFileServer : public MockMavlinkInterface public: MockMavlinkFileServer(void) { }; + /// @brief Sets the list of files returned by the List command. Prepend names with F or D + /// to indicate (F)ile or (D)irectory. void setFileList(QStringList& fileList) { _fileList = fileList; } // From MockMavlinkInterface virtual void sendMessage(mavlink_message_t message); + static const char* smallFilename; + static const char* largeFilename; + static const uint8_t smallFileLength; + static const uint8_t largeFileLength; + private: void _sendNak(QGCUASFileManager::ErrorCode error); void _emitResponse(QGCUASFileManager::Request* request); + void _listCommand(QGCUASFileManager::Request* request); + void _openCommand(QGCUASFileManager::Request* request); + void _readCommand(QGCUASFileManager::Request* request); + + QStringList _fileList; ///< List of files returned by List command - QStringList _fileList; + static const uint8_t _sessionId; + uint8_t _readFileLength; ///< Length of active file being read }; #endif diff --git a/src/qgcunittest/QGCUASFileManagerTest.cc b/src/qgcunittest/QGCUASFileManagerTest.cc index fe325de78f24eb24077c47a4bf3f320a5317034d..c18b1162c5898b2c549b0da03331683a893d6e32 100644 --- a/src/qgcunittest/QGCUASFileManagerTest.cc +++ b/src/qgcunittest/QGCUASFileManagerTest.cc @@ -24,7 +24,9 @@ #include "QGCUASFileManagerTest.h" /// @file -/// @brief QGCUASFileManager unit test +/// @brief QGCUASFileManager 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 @@ -154,3 +156,63 @@ void QGCUASFileManagerUnitTest::_listTest(void) QCOMPARE(_multiSpy->checkNoSignalByMask(errorMessageSignalMask), true); // We should not get an error signals QVERIFY(_fileListReceived == fileListExpected); } + +void QGCUASFileManagerUnitTest::_validateFileContents(const QString& filePath, uint8_t length) +{ + QFile file(filePath); + QVERIFY(file.open(QIODevice::ReadOnly)); + QByteArray bytes = file.readAll(); + file.close(); + + QCOMPARE(bytes.length(), length + 1); // +1 for length byte + + // Validate length byte + QCOMPARE((uint8_t)bytes[0], length); + + // Validate file contents + for (uint8_t i=1; icheckNoSignals() == true); + + // Send a bogus path + // We should get a single resetStatusMessages signal + // We should get a single errorMessage signal + _fileManager->downloadPath("bogus", QDir::temp()); + QCOMPARE(_multiSpy->checkOnlySignalByMask(errorMessageSignalMask | resetStatusMessagesSignalMask), true); + _multiSpy->clearAllSignals(); + + // Clean previous downloads + + QString smallFilePath; + smallFilePath = QDir::temp().absoluteFilePath(MockMavlinkFileServer::smallFilename); + if (QFile::exists(smallFilePath)) { + Q_ASSERT(QFile::remove(smallFilePath)); + } + + QString largeFilePath; + largeFilePath = QDir::temp().absoluteFilePath(MockMavlinkFileServer::largeFilename); + if (QFile::exists(largeFilePath)) { + Q_ASSERT(QFile::remove(largeFilePath)); + } + + // Send a valid file to download + // We should get a single resetStatusMessages signal + // We should get a single statusMessage signal, which indicated download completion + + _fileManager->downloadPath(MockMavlinkFileServer::smallFilename, QDir::temp()); + QCOMPARE(_multiSpy->checkOnlySignalByMask(statusMessageSignalMask | resetStatusMessagesSignalMask), true); + _multiSpy->clearAllSignals(); + _validateFileContents(smallFilePath, MockMavlinkFileServer::smallFileLength); + + _fileManager->downloadPath(MockMavlinkFileServer::largeFilename, QDir::temp()); + QCOMPARE(_multiSpy->checkOnlySignalByMask(statusMessageSignalMask | resetStatusMessagesSignalMask), true); + _multiSpy->clearAllSignals(); + _validateFileContents(largeFilePath, MockMavlinkFileServer::largeFileLength); +} diff --git a/src/qgcunittest/QGCUASFileManagerTest.h b/src/qgcunittest/QGCUASFileManagerTest.h index 2a76fbb5d4bc4cfcd71e9645bb4e6671763ccbc5..a2c6ac32e00fe165d983bde6a89f652b4d1f9d19 100644 --- a/src/qgcunittest/QGCUASFileManagerTest.h +++ b/src/qgcunittest/QGCUASFileManagerTest.h @@ -56,11 +56,14 @@ private slots: void _noAckTest(void); void _resetTest(void); void _listTest(void); + void _openTest(void); // Connected to QGCUASFileManager statusMessage signal void statusMessage(const QString&); private: + void _validateFileContents(const QString& filePath, uint8_t length); + enum { statusMessageSignalIndex = 0, errorMessageSignalIndex, diff --git a/src/uas/QGCUASFileManager.cc b/src/uas/QGCUASFileManager.cc index 41d27d4ba146ad3e7d92eb35641f8ba5e950f372..1efe9b2ffc15967612204309929995a18890d236 100644 --- a/src/uas/QGCUASFileManager.cc +++ b/src/uas/QGCUASFileManager.cc @@ -69,7 +69,8 @@ QGCUASFileManager::QGCUASFileManager(QObject* parent, UASInterface* uas) : QObject(parent), _currentOperation(kCOIdle), _mav(uas), - _encdata_seq(0) + _encdata_seq(0), + _activeSession(0) { bool connected = connect(&_ackTimer, SIGNAL(timeout()), this, SLOT(_ackTimeout())); Q_ASSERT(connected); @@ -103,6 +104,81 @@ void QGCUASFileManager::nothingMessage() // FIXME: Connect ui correctly } +/// @brief Respond to the Ack associated with the Open command. +void QGCUASFileManager::_openResponse(Request* openAck) +{ + _currentOperation = kCORead; + _activeSession = openAck->hdr.session; + _readOffset = 0; + _readFileAccumulator.clear(); + + Request request; + request.hdr.magic = 'f'; + request.hdr.session = _activeSession; + request.hdr.opcode = kCmdRead; + request.hdr.offset = _readOffset; + request.hdr.size = sizeof(request.data); + + _sendRequest(&request); +} + +/// @brief Respond to the Ack associated with the Read command. +void QGCUASFileManager::_readResponse(Request* readAck) +{ + if (readAck->hdr.session != _activeSession) { + _currentOperation = kCOIdle; + _readFileAccumulator.clear(); + _emitErrorMessage(tr("Read: Incorrect session returned")); + return; + } + + if (readAck->hdr.offset != _readOffset) { + _currentOperation = kCOIdle; + _readFileAccumulator.clear(); + _emitErrorMessage(tr("Read: Offset returned (%1) differs from offset requested (%2)").arg(readAck->hdr.offset).arg(_readOffset)); + return; + } + + qDebug() << "Accumulator size" << readAck->hdr.size; + _readFileAccumulator.append((const char*)readAck->data, readAck->hdr.size); + + if (readAck->hdr.size == sizeof(readAck->data)) { + // Still more data to read + _currentOperation = kCORead; + + _readOffset += sizeof(readAck->data); + + Request request; + request.hdr.magic = 'f'; + request.hdr.session = _activeSession; + request.hdr.opcode = kCmdRead; + request.hdr.offset = _readOffset; + request.hdr.size = sizeof(request.data); + + _sendRequest(&request); + } else { + _currentOperation = kCOIdle; + + QString downloadFilePath = _readFileDownloadDir.absoluteFilePath(_readFileDownloadFilename); + + QFile file(downloadFilePath); + if (!file.open(QIODevice::WriteOnly | QIODevice::Truncate)) { + _emitErrorMessage(tr("Unable to open local file for writing (%1)").arg(downloadFilePath)); + return; + } + + 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(); + + _emitStatusMessage(tr("Download complete '%1'").arg(downloadFilePath)); + } +} + void QGCUASFileManager::receiveMessage(LinkInterface* link, mavlink_message_t message) { Q_UNUSED(link); @@ -116,8 +192,6 @@ void QGCUASFileManager::receiveMessage(LinkInterface* link, mavlink_message_t me mavlink_msg_encapsulated_data_decode(&message, &data); Request* request = (Request*)&data.data[0]; - qDebug() << "FTP: opcode:" << request->hdr.opcode; - // FIXME: Check CRC if (request->hdr.opcode == kRspAck) { @@ -132,14 +206,21 @@ void QGCUASFileManager::receiveMessage(LinkInterface* link, mavlink_message_t me case kCOAck: // We are expecting an ack back - _clearAckTimeout(); _currentOperation = kCOIdle; break; case kCOList: listDecode(&request->data[0], request->hdr.size); break; + + case kCOOpen: + _openResponse(request); + break; + case kCORead: + _readResponse(request); + break; + default: _emitErrorMessage("Ack received in unexpected state"); break; @@ -196,7 +277,7 @@ void QGCUASFileManager::listDecode(const uint8_t *data, unsigned len) } // put it in the view - emit statusMessage(s); + _emitStatusMessage(s); // account for the name + NUL offset += nlen + 1; @@ -217,6 +298,12 @@ void QGCUASFileManager::listDecode(const uint8_t *data, unsigned len) } } +void QGCUASFileManager::_fillRequestWithString(Request* request, const QString& str) +{ + strncpy((char *)&request->data[0], str.toStdString().c_str(), sizeof(request->data)); + request->hdr.size = strnlen((const char *)&request->data[0], sizeof(request->data)); +} + void QGCUASFileManager::sendList() { Request request; @@ -226,32 +313,44 @@ void QGCUASFileManager::sendList() request.hdr.opcode = kCmdList; request.hdr.offset = _listOffset; - strncpy((char *)&request.data[0], _listPath.toStdString().c_str(), sizeof(request.data)); - request.hdr.size = strnlen((const char *)&request.data[0], sizeof(request.data)); + _fillRequestWithString(&request, _listPath); _sendRequest(&request); } -void QGCUASFileManager::downloadPath(const QString &from, const QString &to) +/// @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) { - Q_UNUSED(from); + if (from.isEmpty()) { + return; + } - // Send path, e.g. /fs/microsd and download content - // recursively into a local directory - - char buf[255]; - unsigned len = 10; - - QByteArray data(buf, len); - QString filename = "log001.bin"; // XXX get this from onboard - - // Qt takes care of slash conversions in paths - QFile file(to + QDir::separator() + filename); - file.open(QIODevice::WriteOnly); - file.write(data); - file.close(); + _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); + + emit resetStatusMessages(); + + _currentOperation = kCOOpen; - emit statusMessage(QString("Downloaded: %1 to directory %2").arg(filename).arg(to)); + Request request; + request.hdr.magic = 'f'; + request.hdr.session = 0; + request.hdr.opcode = kCmdOpen; + request.hdr.offset = 0; + _fillRequestWithString(&request, from); + _sendRequest(&request); } QString QGCUASFileManager::errorString(uint8_t errorCode) @@ -307,7 +406,6 @@ bool QGCUASFileManager::_sendOpcodeOnlyCmd(uint8_t opcode, OperationState newOpS request.hdr.size = 0; _currentOperation = newOpState; - _setupAckTimeout(); _sendRequest(&request); @@ -317,8 +415,6 @@ bool QGCUASFileManager::_sendOpcodeOnlyCmd(uint8_t opcode, OperationState newOpS /// @brief Starts the ack timeout timer void QGCUASFileManager::_setupAckTimeout(void) { - qDebug() << "Setting Ack"; - Q_ASSERT(!_ackTimer.isActive()); _ackTimer.setSingleShot(true); @@ -328,8 +424,6 @@ void QGCUASFileManager::_setupAckTimeout(void) /// @brief Clears the ack timeout timer void QGCUASFileManager::_clearAckTimeout(void) { - qDebug() << "Clearing Ack"; - Q_ASSERT(_ackTimer.isActive()); _ackTimer.stop(); @@ -344,10 +438,16 @@ void QGCUASFileManager::_ackTimeout(void) void QGCUASFileManager::_emitErrorMessage(const QString& msg) { - qDebug() << "QGCUASFileManager:" << msg; + qDebug() << "QGCUASFileManager: Error" << msg; emit errorMessage(msg); } +void QGCUASFileManager::_emitStatusMessage(const QString& msg) +{ + qDebug() << "QGCUASFileManager: Status" << msg; + emit statusMessage(msg); +} + /// @brief Sends the specified Request out to the UAS. void QGCUASFileManager::_sendRequest(Request* request) { diff --git a/src/uas/QGCUASFileManager.h b/src/uas/QGCUASFileManager.h index 5c41c0bd157b6eee4877ab51b46b2c6b197f8939..408c74c35ba31a82e039931bd1281b0e12d9c29b 100644 --- a/src/uas/QGCUASFileManager.h +++ b/src/uas/QGCUASFileManager.h @@ -25,6 +25,7 @@ #define QGCUASFILEMANAGER_H #include +#include #include "UASInterface.h" @@ -48,7 +49,7 @@ public slots: void receiveMessage(LinkInterface* link, mavlink_message_t message); void nothingMessage(); void listRecursively(const QString &from); - void downloadPath(const QString& from, const QString& to); + void downloadPath(const QString& from, const QDir& downloadDir); protected: struct RequestHeader @@ -109,7 +110,9 @@ protected: { kCOIdle, // not doing anything kCOAck, // waiting for an Ack - kCOList, // waiting for a List response + kCOList, // waiting for List response + kCOOpen, // waiting for Open response + kCORead, // waiting for Read response }; @@ -121,7 +124,11 @@ protected: void _setupAckTimeout(void); void _clearAckTimeout(void); void _emitErrorMessage(const QString& msg); + void _emitStatusMessage(const QString& msg); void _sendRequest(Request* request); + void _fillRequestWithString(Request* request, const QString& str); + void _openResponse(Request* openAck); + void _readResponse(Request* readAck); void sendList(); void listDecode(const uint8_t *data, unsigned len); @@ -136,8 +143,14 @@ protected: UASInterface* _mav; quint16 _encdata_seq; - unsigned _listOffset; // offset for the current List operation - QString _listPath; // path for the current List operation + unsigned _listOffset; ///> offset for the current List operation + QString _listPath; ///> path for the current List operation + + uint8_t _activeSession; ///> currently active session, 0 for none + uint32_t _readOffset; ///> current read offset + QByteArray _readFileAccumulator; ///> Holds file being downloaded + QDir _readFileDownloadDir; ///> Directory to download file to + QString _readFileDownloadFilename; ///> Filename (no path) for download file // We give MockMavlinkFileServer friend access so that it can use the data structures and opcodes // to build a mock mavlink file server for testing. diff --git a/src/ui/QGCUASFileView.cc b/src/ui/QGCUASFileView.cc index 09ccc3b04ee3792619a898c8c0e656bfae428e1c..f9323489490d348c18926d54f450d347a64a6be6 100644 --- a/src/ui/QGCUASFileView.cc +++ b/src/ui/QGCUASFileView.cc @@ -33,10 +33,10 @@ void QGCUASFileView::listFiles() void QGCUASFileView::downloadFiles() { - QString dir = QFileDialog::getExistingDirectory(this, tr("Open Directory"), + QString dir = QFileDialog::getExistingDirectory(this, tr("Download Directory"), QDir::homePath(), QFileDialog::ShowDirsOnly | QFileDialog::DontResolveSymlinks); // And now download to this location - _manager->downloadPath(ui->pathLineEdit->text(), dir); + _manager->downloadPath(ui->pathLineEdit->text(), QDir(dir)); }