MAVLinkLogManager.cc 27.8 KB
Newer Older
Gus Grubba's avatar
Gus Grubba committed
1 2 3 4 5 6 7 8 9
/****************************************************************************
 *
 *   (c) 2009-2016 QGROUNDCONTROL PROJECT <http://www.qgroundcontrol.org>
 *
 * QGroundControl is licensed according to the terms in the file
 * COPYING.md in the root of the source code directory.
 *
 ****************************************************************************/

Gus Grubba's avatar
Gus Grubba committed
10
#include "MAVLinkLogManager.h"
Gus Grubba's avatar
Gus Grubba committed
11 12 13 14 15 16 17 18 19 20 21
#include "QGCApplication.h"
#include <QQmlContext>
#include <QQmlProperty>
#include <QQmlEngine>
#include <QtQml>
#include <QSettings>
#include <QHttpPart>
#include <QNetworkReply>
#include <QFile>
#include <QFileInfo>

Gus Grubba's avatar
Gus Grubba committed
22
QGC_LOGGING_CATEGORY(MAVLinkLogManagerLog, "MAVLinkLogManagerLog")
Gus Grubba's avatar
Gus Grubba committed
23

24 25
static const char* kEmailAddressKey         = "MAVLinkLogEmail";
static const char* kDescriptionsKey         = "MAVLinkLogDescription";
26
static const char* kDefaultDescr            = "QGroundControl Session";
27
static const char* kPx4URLKey               = "MAVLinkLogURL";
28 29 30
static const char* kDefaultPx4URL           = "http://logs.px4.io/upload";
static const char* kEnableAutoUploadKey     = "EnableAutoUploadKey";
static const char* kEnableAutoStartKey      = "EnableAutoStartKey";
31
static const char* kEnableDeletetKey        = "EnableDeleteKey";
Gus Grubba's avatar
Gus Grubba committed
32 33
static const char* kUlogExtension           = ".ulg";
static const char* kSidecarExtension        = ".uploaded";
Gus Grubba's avatar
Gus Grubba committed
34 35

//-----------------------------------------------------------------------------
Gus Grubba's avatar
Gus Grubba committed
36
MAVLinkLogFiles::MAVLinkLogFiles(MAVLinkLogManager* manager, const QString& filePath, bool newFile)
Gus Grubba's avatar
Gus Grubba committed
37 38 39 40 41
    : _manager(manager)
    , _size(0)
    , _selected(false)
    , _uploading(false)
    , _progress(0)
Gus Grubba's avatar
Gus Grubba committed
42
    , _writing(false)
Gus Grubba's avatar
Gus Grubba committed
43
    , _uploaded(false)
Gus Grubba's avatar
Gus Grubba committed
44 45 46
{
    QFileInfo fi(filePath);
    _name = fi.baseName();
Gus Grubba's avatar
Gus Grubba committed
47 48 49 50 51 52 53
    if(!newFile) {
        _size = (quint32)fi.size();
        QString sideCar = filePath;
        sideCar.replace(kUlogExtension, kSidecarExtension);
        QFileInfo sc(sideCar);
        _uploaded = sc.exists();
    }
Gus Grubba's avatar
Gus Grubba committed
54 55
}

56 57
//-----------------------------------------------------------------------------
void
Gus Grubba's avatar
Gus Grubba committed
58
MAVLinkLogFiles::setSize(quint32 size)
59 60 61 62 63
{
    _size = size;
    emit sizeChanged();
}

Gus Grubba's avatar
Gus Grubba committed
64 65
//-----------------------------------------------------------------------------
void
Gus Grubba's avatar
Gus Grubba committed
66
MAVLinkLogFiles::setSelected(bool selected)
Gus Grubba's avatar
Gus Grubba committed
67 68 69 70 71 72 73 74
{
    _selected = selected;
    emit selectedChanged();
    emit _manager->selectedCountChanged();
}

//-----------------------------------------------------------------------------
void
Gus Grubba's avatar
Gus Grubba committed
75
MAVLinkLogFiles::setUploading(bool uploading)
Gus Grubba's avatar
Gus Grubba committed
76 77 78 79 80 81 82
{
    _uploading = uploading;
    emit uploadingChanged();
}

//-----------------------------------------------------------------------------
void
Gus Grubba's avatar
Gus Grubba committed
83
MAVLinkLogFiles::setProgress(qreal progress)
Gus Grubba's avatar
Gus Grubba committed
84 85 86 87 88
{
    _progress = progress;
    emit progressChanged();
}

89 90
//-----------------------------------------------------------------------------
void
Gus Grubba's avatar
Gus Grubba committed
91
MAVLinkLogFiles::setWriting(bool writing)
92 93 94 95 96
{
    _writing = writing;
    emit writingChanged();
}

Gus Grubba's avatar
Gus Grubba committed
97 98
//-----------------------------------------------------------------------------
void
Gus Grubba's avatar
Gus Grubba committed
99
MAVLinkLogFiles::setUploaded(bool uploaded)
Gus Grubba's avatar
Gus Grubba committed
100 101 102 103 104
{
    _uploaded = uploaded;
    emit uploadedChanged();
}

Gus Grubba's avatar
Gus Grubba committed
105 106
//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
Gus Grubba's avatar
Gus Grubba committed
107
MAVLinkLogProcessor::MAVLinkLogProcessor()
Gus Grubba's avatar
Gus Grubba committed
108 109 110 111 112 113 114
    : _fd(NULL)
    , _written(0)
    , _sequence(-1)
    , _numDrops(0)
    , _gotHeader(false)
    , _error(false)
    , _record(NULL)
Gus Grubba's avatar
Gus Grubba committed
115
{
Gus Grubba's avatar
Gus Grubba committed
116 117 118
}

//-----------------------------------------------------------------------------
Gus Grubba's avatar
Gus Grubba committed
119
MAVLinkLogProcessor::~MAVLinkLogProcessor()
Gus Grubba's avatar
Gus Grubba committed
120 121 122 123 124 125
{
    close();
}

//-----------------------------------------------------------------------------
void
Gus Grubba's avatar
Gus Grubba committed
126
MAVLinkLogProcessor::close()
Gus Grubba's avatar
Gus Grubba committed
127 128 129 130
{
    if(_fd) {
        fclose(_fd);
        _fd = NULL;
Gus Grubba's avatar
Gus Grubba committed
131 132 133
    }
}

Gus Grubba's avatar
Gus Grubba committed
134 135
//-----------------------------------------------------------------------------
bool
Gus Grubba's avatar
Gus Grubba committed
136
MAVLinkLogProcessor::valid()
Gus Grubba's avatar
Gus Grubba committed
137 138 139 140 141 142
{
    return (_fd != NULL) && (_record != NULL);
}

//-----------------------------------------------------------------------------
bool
Gus Grubba's avatar
Gus Grubba committed
143
MAVLinkLogProcessor::create(MAVLinkLogManager* manager, const QString path, uint8_t id)
Gus Grubba's avatar
Gus Grubba committed
144 145 146 147 148 149 150 151
{
    _fileName.sprintf("%s/%03d-%s%s",
        path.toLatin1().data(),
        id,
        QDateTime::currentDateTime().toString("yyyy-MM-dd-hh-mm-ss-zzz").toLatin1().data(),
        kUlogExtension);
    _fd = fopen(_fileName.toLatin1().data(), "wb");
    if(_fd) {
Gus Grubba's avatar
Gus Grubba committed
152
        _record = new MAVLinkLogFiles(manager, _fileName, true);
Gus Grubba's avatar
Gus Grubba committed
153 154 155 156 157 158 159 160 161
        _record->setWriting(true);
        _sequence = -1;
        return true;
    }
    return false;
}

//-----------------------------------------------------------------------------
bool
Gus Grubba's avatar
Gus Grubba committed
162
MAVLinkLogProcessor::_checkSequence(uint16_t seq, int& num_drops)
Gus Grubba's avatar
Gus Grubba committed
163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195
{
    num_drops = 0;
    //-- Check if a sequence is newer than the one previously received and if
    //   there were dropped messages between the last one and this.
    if(_sequence == -1) {
        _sequence = seq;
        return true;
    }
    if((uint16_t)_sequence == seq) {
        return false;
    }
    if(seq > (uint16_t)_sequence) {
        // Account for wrap-arounds, sequence is 2 bytes
        if((seq - _sequence) > (1 << 15)) { // Assume reordered
            return false;
        }
        num_drops = seq - _sequence - 1;
        _numDrops += num_drops;
        _sequence = seq;
        return true;
    } else {
        if((_sequence - seq) > (1 << 15)) {
            num_drops = (1 << 16) - _sequence - 1 + seq;
            _numDrops += num_drops;
            _sequence = seq;
            return true;
        }
        return false;
    }
}

//-----------------------------------------------------------------------------
void
Gus Grubba's avatar
Gus Grubba committed
196
MAVLinkLogProcessor::_writeData(void* data, int len)
Gus Grubba's avatar
Gus Grubba committed
197 198 199 200 201 202 203 204 205
{
    if(!_error) {
        _error = fwrite(data, 1, len, _fd) != (size_t)len;
        if(!_error) {
            _written += len;
            if(_record) {
                _record->setSize(_written);
            }
        } else {
Gus Grubba's avatar
Gus Grubba committed
206
            qCDebug(MAVLinkLogManagerLog) << "File IO error:" << len << "bytes into" << _fileName;
Gus Grubba's avatar
Gus Grubba committed
207 208 209 210 211 212
        }
    }
}

//-----------------------------------------------------------------------------
QByteArray
Gus Grubba's avatar
Gus Grubba committed
213
MAVLinkLogProcessor::_writeUlogMessage(QByteArray& data)
Gus Grubba's avatar
Gus Grubba committed
214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229
{
    //-- Write ulog data w/o integrity checking, assuming data starts with a
    //   valid ulog message. returns the remaining data at the end.
    while(data.length() > 2) {
        uint8_t* ptr = (uint8_t*)data.data();
        int message_length = ptr[0] + (ptr[1] * 256) + 3; // 3 = ULog msg header
        if(message_length > data.length())
            break;
        _writeData(data.data(), message_length);
        data.remove(0, message_length);
    }
    return data;
}

//-----------------------------------------------------------------------------
bool
Gus Grubba's avatar
Gus Grubba committed
230
MAVLinkLogProcessor::processStreamData(uint16_t sequence, uint8_t first_message, QByteArray data)
Gus Grubba's avatar
Gus Grubba committed
231 232 233 234 235 236 237 238
{
    int num_drops = 0;
    _error = false;
    while(_checkSequence(sequence, num_drops)) {
        //-- The first 16 bytes need special treatment (this sounds awfully brittle)
        if(!_gotHeader) {
            if(data.size() < 16) {
                //-- Shouldn't happen but if it does, we might as well close shop.
Gus Grubba's avatar
Gus Grubba committed
239
                qCCritical(MAVLinkLogManagerLog) << "Corrupt log header. Canceling log download.";
Gus Grubba's avatar
Gus Grubba committed
240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288
                return false;
            }
            //-- Write header
            _writeData(data.data(), 16);
            data.remove(0, 16);
            _gotHeader = true;
            // What about data start offset now that we removed 16 bytes off the start?
        }
        if(_gotHeader && num_drops > 0) {
            if(num_drops > 25) num_drops = 25;
            //-- Hocus Pocus
            //   Write a dropout message. We don't really know the actual duration,
            //   so just use the number of drops * 10 ms
            uint8_t bogus[] = {2, 0, 79, 0, 0};
            bogus[3] = num_drops * 10;
            _writeData(bogus, sizeof(bogus));
        }
        if(num_drops > 0) {
            _writeUlogMessage(_ulogMessage);
            _ulogMessage.clear();
            //-- If no usefull information in this message. Drop it.
            if(first_message == 255) {
                break;
            }
            if(first_message > 0) {
                data.remove(0, first_message);
                first_message = 0;
            }
        }
        if(first_message == 255 && _ulogMessage.length() > 0) {
            _ulogMessage.append(data);
            break;
        }
        if(_ulogMessage.length()) {
            _writeData(_ulogMessage.data(), _ulogMessage.length());
            if(first_message) {
                _writeData(data.left(first_message).data(), first_message);
            }
            _ulogMessage.clear();
        }
        if(first_message) {
            data.remove(0, first_message);
        }
        _ulogMessage = _writeUlogMessage(data);
        break;
    }
    return !_error;
}

Gus Grubba's avatar
Gus Grubba committed
289
//-----------------------------------------------------------------------------
Gus Grubba's avatar
Gus Grubba committed
290
//-----------------------------------------------------------------------------
Gus Grubba's avatar
Gus Grubba committed
291
MAVLinkLogManager::MAVLinkLogManager(QGCApplication* app)
Gus Grubba's avatar
Gus Grubba committed
292
    : QGCTool(app)
293 294
    , _enableAutoUpload(true)
    , _enableAutoStart(true)
Gus Grubba's avatar
Gus Grubba committed
295 296
    , _nam(NULL)
    , _currentLogfile(NULL)
297 298
    , _vehicle(NULL)
    , _logRunning(false)
Gus Grubba's avatar
Gus Grubba committed
299
    , _loggingDisabled(false)
Gus Grubba's avatar
Gus Grubba committed
300
    , _logProcessor(NULL)
301
    , _deleteAfterUpload(false)
Gus Grubba's avatar
Gus Grubba committed
302 303 304 305 306 307
{
    //-- Get saved settings
    QSettings settings;
    setEmailAddress(settings.value(kEmailAddressKey, QString()).toString());
    setDescription(settings.value(kDescriptionsKey, QString(kDefaultDescr)).toString());
    setUploadURL(settings.value(kPx4URLKey, QString(kDefaultPx4URL)).toString());
308 309
    setEnableAutoUpload(settings.value(kEnableAutoUploadKey, true).toBool());
    setEnableAutoStart(settings.value(kEnableAutoStartKey, true).toBool());
310
    setDeleteAfterUpload(settings.value(kEnableDeletetKey, false).toBool());
Gus Grubba's avatar
Gus Grubba committed
311 312
    //-- Logging location
    _logPath = QStandardPaths::writableLocation(QStandardPaths::AppDataLocation);
Gus Grubba's avatar
Gus Grubba committed
313
    _logPath += "/MAVLinkLogs";
Gus Grubba's avatar
Gus Grubba committed
314
    if(!QDir(_logPath).exists()) {
Gus Grubba's avatar
Gus Grubba committed
315
        if(!QDir().mkpath(_logPath)) {
Gus Grubba's avatar
Gus Grubba committed
316
            qCCritical(MAVLinkLogManagerLog) << "Could not create MAVLink log download path:" << _logPath;
Gus Grubba's avatar
Gus Grubba committed
317
            _loggingDisabled = true;
Gus Grubba's avatar
Gus Grubba committed
318 319
        }
    }
Gus Grubba's avatar
Gus Grubba committed
320 321
    if(!_loggingDisabled) {
        //-- Load current list of logs
Gus Grubba's avatar
Gus Grubba committed
322 323 324
        QString filter = "*";
        filter += kUlogExtension;
        QDirIterator it(_logPath, QStringList() << filter, QDir::Files);
Gus Grubba's avatar
Gus Grubba committed
325
        while(it.hasNext()) {
Gus Grubba's avatar
Gus Grubba committed
326
            _insertNewLog(new MAVLinkLogFiles(this, it.next()));
Gus Grubba's avatar
Gus Grubba committed
327
        }
Gus Grubba's avatar
Gus Grubba committed
328
        qCDebug(MAVLinkLogManagerLog) << "MAVLink logs directory:" << _logPath;
Gus Grubba's avatar
Gus Grubba committed
329 330 331 332
    }
}

//-----------------------------------------------------------------------------
Gus Grubba's avatar
Gus Grubba committed
333
MAVLinkLogManager::~MAVLinkLogManager()
Gus Grubba's avatar
Gus Grubba committed
334 335 336 337 338 339
{
    _logFiles.clear();
}

//-----------------------------------------------------------------------------
void
Gus Grubba's avatar
Gus Grubba committed
340
MAVLinkLogManager::setToolbox(QGCToolbox* toolbox)
Gus Grubba's avatar
Gus Grubba committed
341
{
Gus Grubba's avatar
Gus Grubba committed
342 343
    QGCTool::setToolbox(toolbox);
    QQmlEngine::setObjectOwnership(this, QQmlEngine::CppOwnership);
Gus Grubba's avatar
Gus Grubba committed
344
    qmlRegisterUncreatableType<MAVLinkLogManager>("QGroundControl.MAVLinkLogManager", 1, 0, "MAVLinkLogManager", "Reference only");
Gus Grubba's avatar
Gus Grubba committed
345
    if(!_loggingDisabled) {
Gus Grubba's avatar
Gus Grubba committed
346
        connect(toolbox->multiVehicleManager(), &MultiVehicleManager::activeVehicleChanged, this, &MAVLinkLogManager::_activeVehicleChanged);
Gus Grubba's avatar
Gus Grubba committed
347
    }
Gus Grubba's avatar
Gus Grubba committed
348 349 350 351
}

//-----------------------------------------------------------------------------
void
Gus Grubba's avatar
Gus Grubba committed
352
MAVLinkLogManager::setEmailAddress(QString email)
Gus Grubba's avatar
Gus Grubba committed
353 354 355 356 357 358 359 360 361
{
    _emailAddress = email;
    QSettings settings;
    settings.setValue(kEmailAddressKey, email);
    emit emailAddressChanged();
}

//-----------------------------------------------------------------------------
void
Gus Grubba's avatar
Gus Grubba committed
362
MAVLinkLogManager::setDescription(QString description)
Gus Grubba's avatar
Gus Grubba committed
363 364 365 366 367 368 369 370 371
{
    _description = description;
    QSettings settings;
    settings.setValue(kDescriptionsKey, description);
    emit descriptionChanged();
}

//-----------------------------------------------------------------------------
void
Gus Grubba's avatar
Gus Grubba committed
372
MAVLinkLogManager::setUploadURL(QString url)
Gus Grubba's avatar
Gus Grubba committed
373 374 375 376 377 378 379 380 381 382 383 384
{
    _uploadURL = url;
    if(_uploadURL.isEmpty()) {
        _uploadURL = kDefaultPx4URL;
    }
    QSettings settings;
    settings.setValue(kPx4URLKey, _uploadURL);
    emit uploadURLChanged();
}

//-----------------------------------------------------------------------------
void
Gus Grubba's avatar
Gus Grubba committed
385
MAVLinkLogManager::setEnableAutoUpload(bool enable)
386 387 388 389 390 391 392 393 394
{
    _enableAutoUpload = enable;
    QSettings settings;
    settings.setValue(kEnableAutoUploadKey, enable);
    emit enableAutoUploadChanged();
}

//-----------------------------------------------------------------------------
void
Gus Grubba's avatar
Gus Grubba committed
395
MAVLinkLogManager::setEnableAutoStart(bool enable)
Gus Grubba's avatar
Gus Grubba committed
396
{
397
    _enableAutoStart = enable;
Gus Grubba's avatar
Gus Grubba committed
398
    QSettings settings;
399 400
    settings.setValue(kEnableAutoStartKey, enable);
    emit enableAutoStartChanged();
Gus Grubba's avatar
Gus Grubba committed
401 402
}

403 404
//-----------------------------------------------------------------------------
void
Gus Grubba's avatar
Gus Grubba committed
405
MAVLinkLogManager::setDeleteAfterUpload(bool enable)
406 407 408 409 410 411 412
{
    _deleteAfterUpload = enable;
    QSettings settings;
    settings.setValue(kEnableDeletetKey, enable);
    emit deleteAfterUploadChanged();
}

Gus Grubba's avatar
Gus Grubba committed
413 414
//-----------------------------------------------------------------------------
bool
Gus Grubba's avatar
Gus Grubba committed
415
MAVLinkLogManager::uploading()
Gus Grubba's avatar
Gus Grubba committed
416 417 418 419 420 421
{
    return _currentLogfile != NULL;
}

//-----------------------------------------------------------------------------
void
Gus Grubba's avatar
Gus Grubba committed
422
MAVLinkLogManager::uploadLog()
Gus Grubba's avatar
Gus Grubba committed
423 424 425 426
{
    if(_currentLogfile) {
        _currentLogfile->setUploading(false);
    }
Gus Grubba's avatar
Gus Grubba committed
427
    for(int i = 0; i < _logFiles.count(); i++) {
Gus Grubba's avatar
Gus Grubba committed
428
        _currentLogfile = qobject_cast<MAVLinkLogFiles*>(_logFiles.get(i));
Gus Grubba's avatar
Gus Grubba committed
429 430 431
        Q_ASSERT(_currentLogfile);
        if(_currentLogfile->selected()) {
            _currentLogfile->setSelected(false);
Gus Grubba's avatar
Gus Grubba committed
432 433 434 435 436 437 438 439
            if(!_currentLogfile->uploaded() && !_emailAddress.isEmpty() && !_uploadURL.isEmpty()) {
                _currentLogfile->setUploading(true);
                _currentLogfile->setProgress(0.0);
                QString filePath = _makeFilename(_currentLogfile->name());
                _sendLog(filePath);
                emit uploadingChanged();
                return;
            }
Gus Grubba's avatar
Gus Grubba committed
440 441 442
        }
    }
    _currentLogfile = NULL;
443 444 445 446 447
    emit uploadingChanged();
}

//-----------------------------------------------------------------------------
void
Gus Grubba's avatar
Gus Grubba committed
448
MAVLinkLogManager::_insertNewLog(MAVLinkLogFiles* newLog)
449 450 451 452 453 454 455
{
    //-- Simpler than trying to sort this thing
    int count = _logFiles.count();
    if(!count) {
        _logFiles.append(newLog);
    } else {
        for(int i = 0; i < count; i++) {
Gus Grubba's avatar
Gus Grubba committed
456
            MAVLinkLogFiles* f = qobject_cast<MAVLinkLogFiles*>(_logFiles.get(i));
457 458 459 460 461 462 463
            if(newLog->name() < f->name()) {
                _logFiles.insert(i, newLog);
                return;
            }
        }
        _logFiles.append(newLog);
    }
Gus Grubba's avatar
Gus Grubba committed
464 465
}

Gus Grubba's avatar
Gus Grubba committed
466 467
//-----------------------------------------------------------------------------
int
Gus Grubba's avatar
Gus Grubba committed
468
MAVLinkLogManager::_getFirstSelected()
Gus Grubba's avatar
Gus Grubba committed
469 470
{
    for(int i = 0; i < _logFiles.count(); i++) {
Gus Grubba's avatar
Gus Grubba committed
471
        MAVLinkLogFiles* f = qobject_cast<MAVLinkLogFiles*>(_logFiles.get(i));
Gus Grubba's avatar
Gus Grubba committed
472 473 474 475 476 477 478 479
        Q_ASSERT(f);
        if(f->selected()) {
            return i;
        }
    }
    return -1;
}

Gus Grubba's avatar
Gus Grubba committed
480 481
//-----------------------------------------------------------------------------
void
Gus Grubba's avatar
Gus Grubba committed
482
MAVLinkLogManager::deleteLog()
Gus Grubba's avatar
Gus Grubba committed
483
{
Gus Grubba's avatar
Gus Grubba committed
484 485 486 487 488
    while (true) {
        int idx = _getFirstSelected();
        if(idx < 0) {
            break;
        }
Gus Grubba's avatar
Gus Grubba committed
489
        MAVLinkLogFiles* log = qobject_cast<MAVLinkLogFiles*>(_logFiles.get(idx));
490
        _deleteLog(log);
Gus Grubba's avatar
Gus Grubba committed
491
    }
Gus Grubba's avatar
Gus Grubba committed
492 493
}

494 495
//-----------------------------------------------------------------------------
void
Gus Grubba's avatar
Gus Grubba committed
496
MAVLinkLogManager::_deleteLog(MAVLinkLogFiles* log)
497
{
Gus Grubba's avatar
Gus Grubba committed
498
    QString filePath = _makeFilename(log->name());
499 500
    QFile gone(filePath);
    if(!gone.remove()) {
Gus Grubba's avatar
Gus Grubba committed
501
        qCWarning(MAVLinkLogManagerLog) << "Could not delete MAVLink log file:" << _logPath;
502
    }
Gus Grubba's avatar
Gus Grubba committed
503 504 505 506 507 508 509
    //-- Remove sidecar file (if any)
    filePath.replace(kUlogExtension, kSidecarExtension);
    QFile sgone(filePath);
    if(sgone.exists()) {
        sgone.remove();
    }
    //-- Remove file from list and delete record
510 511 512 513 514
    _logFiles.removeOne(log);
    delete log;
    emit logFilesChanged();
}

Gus Grubba's avatar
Gus Grubba committed
515 516
//-----------------------------------------------------------------------------
void
Gus Grubba's avatar
Gus Grubba committed
517
MAVLinkLogManager::cancelUpload()
Gus Grubba's avatar
Gus Grubba committed
518
{
Gus Grubba's avatar
Gus Grubba committed
519
    for(int i = 0; i < _logFiles.count(); i++) {
Gus Grubba's avatar
Gus Grubba committed
520
        MAVLinkLogFiles* pLogFile = qobject_cast<MAVLinkLogFiles*>(_logFiles.get(i));
Gus Grubba's avatar
Gus Grubba committed
521 522 523 524 525 526 527 528 529 530
        Q_ASSERT(pLogFile);
        if(pLogFile->selected() && pLogFile != _currentLogfile) {
            pLogFile->setSelected(false);
        }
    }
    if(_currentLogfile) {
        emit abortUpload();
    }
}

531 532
//-----------------------------------------------------------------------------
void
Gus Grubba's avatar
Gus Grubba committed
533
MAVLinkLogManager::startLogging()
534 535
{
    if(_vehicle) {
Gus Grubba's avatar
Gus Grubba committed
536 537 538 539 540
        if(_createNewLog()) {
            _vehicle->startMavlinkLog();
            _logRunning = true;
            emit logRunningChanged();
        }
541 542 543 544 545
    }
}

//-----------------------------------------------------------------------------
void
Gus Grubba's avatar
Gus Grubba committed
546
MAVLinkLogManager::stopLogging()
547 548
{
    if(_vehicle) {
549
        //-- Tell vehicle to stop sending logs
550
        _vehicle->stopMavlinkLog();
551
    }
Gus Grubba's avatar
Gus Grubba committed
552 553 554 555
    if(_logProcessor) {
        _logProcessor->close();
        if(_logProcessor->record()) {
            _logProcessor->record()->setWriting(false);
556 557
            if(_enableAutoUpload) {
                //-- Queue log for auto upload (set selected flag)
Gus Grubba's avatar
Gus Grubba committed
558
                _logProcessor->record()->setSelected(true);
559 560
                if(!uploading()) {
                    uploadLog();
561
                }
Gus Grubba's avatar
Gus Grubba committed
562 563
            }
        }
Gus Grubba's avatar
Gus Grubba committed
564 565
        delete _logProcessor;
        _logProcessor = NULL;
566 567
        _logRunning = false;
        emit logRunningChanged();
568 569 570
    }
}

Gus Grubba's avatar
Gus Grubba committed
571 572 573 574 575 576 577 578 579 580 581 582
//-----------------------------------------------------------------------------
QHttpPart
create_form_part(const QString& name, const QString& value)
{
    QHttpPart formPart;
    formPart.setHeader(QNetworkRequest::ContentDispositionHeader, QString("form-data; name=\"%1\"").arg(name));
    formPart.setBody(value.toUtf8());
    return formPart;
}

//-----------------------------------------------------------------------------
bool
Gus Grubba's avatar
Gus Grubba committed
583
MAVLinkLogManager::_sendLog(const QString& logFile)
Gus Grubba's avatar
Gus Grubba committed
584 585 586
{
    QString defaultDescription = _description;
    if(_description.isEmpty()) {
Gus Grubba's avatar
Gus Grubba committed
587
        qCWarning(MAVLinkLogManagerLog) << "Log description missing. Using defaults.";
Gus Grubba's avatar
Gus Grubba committed
588 589 590
        defaultDescription = kDefaultDescr;
    }
    if(_emailAddress.isEmpty()) {
Gus Grubba's avatar
Gus Grubba committed
591
        qCCritical(MAVLinkLogManagerLog) << "User email missing.";
Gus Grubba's avatar
Gus Grubba committed
592 593 594
        return false;
    }
    if(_uploadURL.isEmpty()) {
Gus Grubba's avatar
Gus Grubba committed
595
        qCCritical(MAVLinkLogManagerLog) << "Upload URL missing.";
Gus Grubba's avatar
Gus Grubba committed
596 597 598 599
        return false;
    }
    QFileInfo fi(logFile);
    if(!fi.exists()) {
Gus Grubba's avatar
Gus Grubba committed
600
        qCCritical(MAVLinkLogManagerLog) << "Log file missing:" << logFile;
Gus Grubba's avatar
Gus Grubba committed
601 602
        return false;
    }
Gus Grubba's avatar
Gus Grubba committed
603
    QFile* file = new QFile(logFile);
Gus Grubba's avatar
Gus Grubba committed
604
    if(!file || !file->open(QIODevice::ReadOnly)) {
Gus Grubba's avatar
Gus Grubba committed
605 606 607
        if(file) {
            delete file;
        }
Gus Grubba's avatar
Gus Grubba committed
608
        qCCritical(MAVLinkLogManagerLog) << "Could not open log file:" << logFile;
Gus Grubba's avatar
Gus Grubba committed
609 610 611
        return false;
    }
    if(!_nam) {
Gus Grubba's avatar
Gus Grubba committed
612
        _nam = new QNetworkAccessManager(this);
Gus Grubba's avatar
Gus Grubba committed
613 614 615 616 617 618
    }
    QNetworkProxy savedProxy = _nam->proxy();
    QNetworkProxy tempProxy;
    tempProxy.setType(QNetworkProxy::DefaultProxy);
    _nam->setProxy(tempProxy);
    //-- Build POST request
Gus Grubba's avatar
Gus Grubba committed
619
    QHttpMultiPart* multiPart = new QHttpMultiPart(QHttpMultiPart::FormDataType);
Gus Grubba's avatar
Gus Grubba committed
620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635
    QHttpPart emailPart = create_form_part("email", _emailAddress);
    QHttpPart descriptionPart = create_form_part("description", _description);
    QHttpPart sourcePart = create_form_part("source", "QGroundControl");
    QHttpPart versionPart = create_form_part("version", _app->applicationVersion());
    QHttpPart logPart;
    logPart.setHeader(QNetworkRequest::ContentTypeHeader, "application/octet-stream");
    logPart.setHeader(QNetworkRequest::ContentDispositionHeader, QString("form-data; name=\"filearg\"; filename=\"%1\"").arg(fi.fileName()));
    logPart.setBodyDevice(file);
    //-- Assemble request and POST it
    multiPart->append(emailPart);
    multiPart->append(descriptionPart);
    multiPart->append(sourcePart);
    multiPart->append(versionPart);
    multiPart->append(logPart);
    file->setParent(multiPart);
    QNetworkRequest request(_uploadURL);
636
#if QT_VERSION > 0x050600
Gus Grubba's avatar
Gus Grubba committed
637
    request.setAttribute(QNetworkRequest::FollowRedirectsAttribute, true);
638
#endif
Gus Grubba's avatar
Gus Grubba committed
639
    QNetworkReply* reply = _nam->post(request, multiPart);
Gus Grubba's avatar
Gus Grubba committed
640 641 642 643
    connect(reply, &QNetworkReply::finished,  this, &MAVLinkLogManager::_uploadFinished);
    connect(this, &MAVLinkLogManager::abortUpload, reply, &QNetworkReply::abort);
    //connect(reply, &QNetworkReply::readyRead, this, &MAVLinkLogManager::_dataAvailable);
    connect(reply, &QNetworkReply::uploadProgress, this, &MAVLinkLogManager::_uploadProgress);
Gus Grubba's avatar
Gus Grubba committed
644
    multiPart->setParent(reply);
Gus Grubba's avatar
Gus Grubba committed
645
    qCDebug(MAVLinkLogManagerLog) << "Log" << fi.baseName() << "Uploading." << fi.size() << "bytes.";
Gus Grubba's avatar
Gus Grubba committed
646 647 648 649 650 651
    _nam->setProxy(savedProxy);
    return true;
}

//-----------------------------------------------------------------------------
bool
Gus Grubba's avatar
Gus Grubba committed
652
MAVLinkLogManager::_processUploadResponse(int http_code, QByteArray& data)
Gus Grubba's avatar
Gus Grubba committed
653
{
Gus Grubba's avatar
Gus Grubba committed
654
    qCDebug(MAVLinkLogManagerLog) << "Uploaded response:" << QString::fromUtf8(data);
Gus Grubba's avatar
Gus Grubba committed
655 656 657 658 659 660
    emit readyRead(data);
    return http_code == 200;
}

//-----------------------------------------------------------------------------
void
Gus Grubba's avatar
Gus Grubba committed
661
MAVLinkLogManager::_dataAvailable()
Gus Grubba's avatar
Gus Grubba committed
662
{
Gus Grubba's avatar
Gus Grubba committed
663
    QNetworkReply* reply = qobject_cast<QNetworkReply*>(sender());
Gus Grubba's avatar
Gus Grubba committed
664 665 666 667
    if(!reply) {
        return;
    }
    QByteArray data = reply->readAll();
Gus Grubba's avatar
Gus Grubba committed
668
    qCDebug(MAVLinkLogManagerLog) << "Uploaded response data:" << QString::fromUtf8(data);
Gus Grubba's avatar
Gus Grubba committed
669 670 671 672
}

//-----------------------------------------------------------------------------
void
Gus Grubba's avatar
Gus Grubba committed
673
MAVLinkLogManager::_uploadFinished()
Gus Grubba's avatar
Gus Grubba committed
674
{
Gus Grubba's avatar
Gus Grubba committed
675
    QNetworkReply* reply = qobject_cast<QNetworkReply*>(sender());
Gus Grubba's avatar
Gus Grubba committed
676 677 678 679 680 681
    if(!reply) {
        return;
    }
    const int http_code = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
    QByteArray data = reply->readAll();
    if(_processUploadResponse(http_code, data)) {
Gus Grubba's avatar
Gus Grubba committed
682
        qCDebug(MAVLinkLogManagerLog) << "Log uploaded.";
Gus Grubba's avatar
Gus Grubba committed
683
        emit succeed();
684 685 686 687 688
        if(_deleteAfterUpload) {
            if(_currentLogfile) {
                _deleteLog(_currentLogfile);
                _currentLogfile = NULL;
            }
Gus Grubba's avatar
Gus Grubba committed
689 690 691 692 693 694 695 696 697 698 699
        } else {
            if(_currentLogfile) {
                _currentLogfile->setUploaded(true);
                //-- Write side-car file to flag it as uploaded
                QString sideCar = _makeFilename(_currentLogfile->name());
                sideCar.replace(kUlogExtension, kSidecarExtension);
                FILE* f = fopen(sideCar.toLatin1().data(), "wb");
                if(f) {
                    fclose(f);
                }
            }
700
        }
Gus Grubba's avatar
Gus Grubba committed
701
    } else {
Gus Grubba's avatar
Gus Grubba committed
702
        qCWarning(MAVLinkLogManagerLog) << QString("Log Upload Error: %1 status: %2").arg(reply->errorString(), reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toString());
Gus Grubba's avatar
Gus Grubba committed
703 704 705 706 707 708 709 710 711
        emit failed();
    }
    reply->deleteLater();
    //-- Next (if any)
    uploadLog();
}

//-----------------------------------------------------------------------------
void
Gus Grubba's avatar
Gus Grubba committed
712
MAVLinkLogManager::_uploadProgress(qint64 bytesSent, qint64 bytesTotal)
Gus Grubba's avatar
Gus Grubba committed
713 714 715
{
    if(bytesTotal) {
        qreal progress = (qreal)bytesSent / (qreal)bytesTotal;
Gus Grubba's avatar
Gus Grubba committed
716
        if(_currentLogfile) {
Gus Grubba's avatar
Gus Grubba committed
717
            _currentLogfile->setProgress(progress);
Gus Grubba's avatar
Gus Grubba committed
718
        }
Gus Grubba's avatar
Gus Grubba committed
719
    }
Gus Grubba's avatar
Gus Grubba committed
720
    qCDebug(MAVLinkLogManagerLog) << bytesSent << "of" << bytesTotal;
Gus Grubba's avatar
Gus Grubba committed
721
}
722 723 724

//-----------------------------------------------------------------------------
void
Gus Grubba's avatar
Gus Grubba committed
725
MAVLinkLogManager::_activeVehicleChanged(Vehicle* vehicle)
726 727 728 729 730
{
    //-- TODO: This is not quite right. This is being used to detect when a vehicle
    //   connects/disconnects. In reality, if QGC is connected to multiple vehicles,
    //   this is called each time the user switches from one vehicle to another. So
    //   far, I'm working on the assumption that multiple vehicles is a rare exception.
Gus Grubba's avatar
Gus Grubba committed
731
    //   For now, we only handle one log download at a time.
732
    // Disconnect the previous one (if any)
Gus Grubba's avatar
Gus Grubba committed
733
    if(_vehicle) {
734 735 736
        disconnect(_vehicle, &Vehicle::armedChanged,        this, &MAVLinkLogManager::_armedChanged);
        disconnect(_vehicle, &Vehicle::mavlinkLogData,      this, &MAVLinkLogManager::_mavlinkLogData);
        disconnect(_vehicle, &Vehicle::mavCommandResult,    this, &MAVLinkLogManager::_mavCommandResult);
737
        _vehicle = NULL;
738 739
        //-- Stop logging (if that's the case)
        stopLogging();
740 741 742
        emit canStartLogChanged();
    }
    // Connect new system
Gus Grubba's avatar
Gus Grubba committed
743
    if(vehicle) {
744
        _vehicle = vehicle;
745 746 747
        connect(_vehicle, &Vehicle::armedChanged,       this, &MAVLinkLogManager::_armedChanged);
        connect(_vehicle, &Vehicle::mavlinkLogData,     this, &MAVLinkLogManager::_mavlinkLogData);
        connect(_vehicle, &Vehicle::mavCommandResult,   this, &MAVLinkLogManager::_mavCommandResult);
748 749 750 751 752 753
        emit canStartLogChanged();
    }
}

//-----------------------------------------------------------------------------
void
Gus Grubba's avatar
Gus Grubba committed
754
MAVLinkLogManager::_mavlinkLogData(Vehicle* /*vehicle*/, uint8_t /*target_system*/, uint8_t /*target_component*/, uint16_t sequence, uint8_t first_message, QByteArray data, bool /*acked*/)
Gus Grubba's avatar
Gus Grubba committed
755
{
Gus Grubba's avatar
Gus Grubba committed
756 757
    if(_logProcessor && _logProcessor->valid()) {
        if(!_logProcessor->processStreamData(sequence, first_message, data)) {
Gus Grubba's avatar
Gus Grubba committed
758
            qCCritical(MAVLinkLogManagerLog) << "Error writing MAVLink log file:" << _logProcessor->fileName();
Gus Grubba's avatar
Gus Grubba committed
759 760
            delete _logProcessor;
            _logProcessor = NULL;
Gus Grubba's avatar
Gus Grubba committed
761 762 763 764 765
            _logRunning = false;
            _vehicle->stopMavlinkLog();
            emit logRunningChanged();
        }
    } else {
Gus Grubba's avatar
Gus Grubba committed
766
        qCWarning(MAVLinkLogManagerLog) << "MAVLink log data received when not expected.";
Gus Grubba's avatar
Gus Grubba committed
767 768 769
    }
}

770 771
//-----------------------------------------------------------------------------
void
772
MAVLinkLogManager::_mavCommandResult(int vehicleId, int component, MAV_CMD command, MAV_RESULT result, bool noReponseFromVehicle)
773
{
774 775 776 777
    Q_UNUSED(vehicleId);
    Q_UNUSED(component);
    Q_UNUSED(noReponseFromVehicle)

778 779
    if(command == MAV_CMD_LOGGING_START || command == MAV_CMD_LOGGING_STOP) {
        //-- Did it fail?
780
        if(result != MAV_RESULT_ACCEPTED) {
781 782
            if(command == MAV_CMD_LOGGING_STOP) {
                //-- Not that it could happen but...
Gus Grubba's avatar
Gus Grubba committed
783
                qCWarning(MAVLinkLogManagerLog) << "Stop MAVLink log command failed.";
784 785
            } else {
                //-- Could not start logging for some reason.
Gus Grubba's avatar
Gus Grubba committed
786
                qCWarning(MAVLinkLogManagerLog) << "Start MAVLink log command failed.";
787 788 789 790 791 792 793 794
                _discardLog();
            }
        }
    }
}

//-----------------------------------------------------------------------------
void
Gus Grubba's avatar
Gus Grubba committed
795
MAVLinkLogManager::_discardLog()
796 797
{
    //-- Delete (empty) log file (and record)
Gus Grubba's avatar
Gus Grubba committed
798 799 800 801
    if(_logProcessor) {
        _logProcessor->close();
        if(_logProcessor->record()) {
            _deleteLog(_logProcessor->record());
802
        }
Gus Grubba's avatar
Gus Grubba committed
803 804
        delete _logProcessor;
        _logProcessor = NULL;
805 806 807 808 809
    }
    _logRunning = false;
    emit logRunningChanged();
}

Gus Grubba's avatar
Gus Grubba committed
810 811
//-----------------------------------------------------------------------------
bool
Gus Grubba's avatar
Gus Grubba committed
812
MAVLinkLogManager::_createNewLog()
813
{
Gus Grubba's avatar
Gus Grubba committed
814 815 816
    if(_logProcessor) {
        delete _logProcessor;
        _logProcessor = NULL;
Gus Grubba's avatar
Gus Grubba committed
817
    }
Gus Grubba's avatar
Gus Grubba committed
818
    _logProcessor = new MAVLinkLogProcessor;
Gus Grubba's avatar
Gus Grubba committed
819 820
    if(_logProcessor->create(this, _logPath, _vehicle->id())) {
        _insertNewLog(_logProcessor->record());
821 822
        emit logFilesChanged();
    } else {
Gus Grubba's avatar
Gus Grubba committed
823
        qCCritical(MAVLinkLogManagerLog) << "Could not create MAVLink log file:" << _logProcessor->fileName();
Gus Grubba's avatar
Gus Grubba committed
824 825
        delete _logProcessor;
        _logProcessor = NULL;
Gus Grubba's avatar
Gus Grubba committed
826
    }
Gus Grubba's avatar
Gus Grubba committed
827
    return _logProcessor != NULL;
828 829 830 831
}

//-----------------------------------------------------------------------------
void
Gus Grubba's avatar
Gus Grubba committed
832
MAVLinkLogManager::_armedChanged(bool armed)
833 834 835 836
{
    if(_vehicle) {
        if(armed) {
            if(_enableAutoStart) {
Gus Grubba's avatar
Gus Grubba committed
837
                startLogging();
838 839 840
            }
        } else {
            if(_logRunning && _enableAutoStart) {
Gus Grubba's avatar
Gus Grubba committed
841
                stopLogging();
842 843 844 845
            }
        }
    }
}
Gus Grubba's avatar
Gus Grubba committed
846 847 848

//-----------------------------------------------------------------------------
QString
Gus Grubba's avatar
Gus Grubba committed
849
MAVLinkLogManager::_makeFilename(const QString& baseName)
Gus Grubba's avatar
Gus Grubba committed
850 851 852 853 854 855 856
{
    QString filePath = _logPath;
    filePath += "/";
    filePath += baseName;
    filePath += kUlogExtension;
    return filePath;
}