FileManager.cc 23.8 KB
Newer Older
Don Gagne's avatar
Don Gagne committed
1
/*=====================================================================
Julian Oes's avatar
Julian Oes committed
2

Don Gagne's avatar
Don Gagne committed
3
 QGroundControl Open Source Ground Control Station
Julian Oes's avatar
Julian Oes committed
4

Don Gagne's avatar
Don Gagne committed
5
 (c) 2009 - 2014 QGROUNDCONTROL PROJECT <http://www.qgroundcontrol.org>
Julian Oes's avatar
Julian Oes committed
6

Don Gagne's avatar
Don Gagne committed
7
 This file is part of the QGROUNDCONTROL project
Julian Oes's avatar
Julian Oes committed
8

Don Gagne's avatar
Don Gagne committed
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.
Julian Oes's avatar
Julian Oes committed
13

Don Gagne's avatar
Don Gagne committed
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.
Julian Oes's avatar
Julian Oes committed
18

Don Gagne's avatar
Don Gagne committed
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/>.
Julian Oes's avatar
Julian Oes committed
21

Don Gagne's avatar
Don Gagne committed
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),
Don Gagne's avatar
Don Gagne committed
37
    _currentOperation(kCOIdle),
Lorenz Meier's avatar
Lorenz Meier committed
38
    _mav(uas),
Don Gagne's avatar
Don Gagne committed
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();
Don Gagne's avatar
Don Gagne committed
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;
Don Gagne's avatar
Don Gagne committed
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
Julian Oes's avatar
Julian Oes committed
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
Julian Oes's avatar
Julian Oes committed
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);
Julian Oes's avatar
Julian Oes committed
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);
Julian Oes's avatar
Julian Oes committed
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;
        }
Julian Oes's avatar
Julian Oes committed
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();
Julian Oes's avatar
Julian Oes committed
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;
    }
Julian Oes's avatar
Julian Oes committed
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));
    }
Julian Oes's avatar
Julian Oes committed
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;
    }
Julian Oes's avatar
Julian Oes committed
180

181
182
183
    uint8_t offset = 0;
    uint8_t cListEntries = 0;
    uint8_t cBytes = listAck->hdr.size;
Julian Oes's avatar
Julian Oes committed
184

185
186
187
    // parse filenames out of the buffer
    while (offset < cBytes) {
        const char * ptr = ((const char *)listAck->data) + offset;
Julian Oes's avatar
Julian Oes committed
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
        }
Julian Oes's avatar
Julian Oes committed
211

212
213
        // account for the name + NUL
        offset += nlen + 1;
Julian Oes's avatar
Julian Oes committed
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
{
Lorenz Meier's avatar
Lorenz Meier committed
311
    Q_UNUSED(link);
Julian Oes's avatar
Julian Oes committed
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;
Don Gagne's avatar
Don Gagne committed
324
325
        return;
    }
326
327
328
    
    Request* request = (Request*)&data.payload[0];
    
Don Gagne's avatar
Don Gagne committed
329
330
    _clearAckTimeout();
    
331
332
	qCDebug(FileManagerLog) << "receiveMessage" << request->hdr.opcode;
	
333
    uint16_t incomingSeqNumber = request->hdr.seqNumber;
Don Gagne's avatar
Don Gagne committed
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;
        }
        
Don Gagne's avatar
Don Gagne committed
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;
Julian Oes's avatar
Julian Oes committed
368

Don Gagne's avatar
Don Gagne committed
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;
		}
Don Gagne's avatar
Don Gagne committed
401
    } else if (request->hdr.opcode == kRspNak) {
402
        uint8_t errorCode = request->data[0];
Julian Oes's avatar
Julian Oes committed
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);
        
Don Gagne's avatar
Don Gagne committed
407
        _currentOperation = kCOIdle;
Julian Oes's avatar
Julian Oes committed
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])));
        }
Don Gagne's avatar
Don Gagne committed
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
{
Don Gagne's avatar
Don Gagne committed
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;
Don Gagne's avatar
Don Gagne committed
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
{
Don Gagne's avatar
Don Gagne committed
462
    Request request;
none's avatar
none committed
463

Don Gagne's avatar
Don Gagne committed
464
    request.hdr.session = 0;
465
    request.hdr.opcode = kCmdListDirectory;
Don Gagne's avatar
Don Gagne committed
466
    request.hdr.offset = _listOffset;
Don Gagne's avatar
Don Gagne committed
467
    request.hdr.size = 0;
Julian Oes's avatar
Julian Oes committed
468

469
    _fillRequestWithString(&request, _listPath);
Julian Oes's avatar
Julian Oes committed
470

471
472
    qCDebug(FileManagerLog) << "listDirectory: path:" << _listPath << "offset:" <<  _listOffset;
    
Don Gagne's avatar
Don Gagne committed
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 */);
}
Julian Oes's avatar
Julian Oes committed
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
    }
}
Don Gagne's avatar
Don Gagne committed
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)
Don Gagne's avatar
Don Gagne committed
607
608
609
610
611
{
    if (_currentOperation != kCOIdle) {
        // Can't have multiple commands in play at the same time
        return false;
    }
Julian Oes's avatar
Julian Oes committed
612

Don Gagne's avatar
Don Gagne committed
613
614
615
616
617
    Request request;
    request.hdr.session = 0;
    request.hdr.opcode = opcode;
    request.hdr.offset = 0;
    request.hdr.size = 0;
Julian Oes's avatar
Julian Oes committed
618

Don Gagne's avatar
Don Gagne committed
619
    _currentOperation = newOpState;
Julian Oes's avatar
Julian Oes committed
620

Don Gagne's avatar
Don Gagne committed
621
    _sendRequest(&request);
Julian Oes's avatar
Julian Oes committed
622

623
    return true;
Don Gagne's avatar
Don Gagne committed
624
625
626
}

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

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

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

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

/// @brief Called when ack timeout timer fires
645
void FileManager::_ackTimeout(void)
Don Gagne's avatar
Don Gagne committed
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;
Don Gagne's avatar
Don Gagne committed
689
    request.hdr.size = 0;
690
    _sendRequest(&request);
Don Gagne's avatar
Don Gagne committed
691
692
}

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

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

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

Don Gagne's avatar
Don Gagne committed
709
    mavlink_message_t message;
Julian Oes's avatar
Julian Oes committed
710

Don Gagne's avatar
Don Gagne committed
711
    _setupAckTimeout();
Don Gagne's avatar
Don Gagne committed
712
713
    
    _lastOutgoingSeqNumber++;
Don Gagne's avatar
Don Gagne committed
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
    
Don Gagne's avatar
Don Gagne committed
732
    _mav->sendMessage(message);
Lorenz Meier's avatar
Lorenz Meier committed
733
}