Skip to content
Snippets Groups Projects
MAVLinkLogManager.cc 31.9 KiB
Newer Older
  • Learn to ignore specific revisions
  • Gus Grubba's avatar
    Gus Grubba committed
    /****************************************************************************
     *
     *   (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
    #include "MAVLinkLogManager.h"
    
    Gus Grubba's avatar
    Gus Grubba committed
    #include "QGCApplication.h"
    
    #include "SettingsManager.h"
    
    
    Gus Grubba's avatar
    Gus Grubba committed
    #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
    QGC_LOGGING_CATEGORY(MAVLinkLogManagerLog, "MAVLinkLogManagerLog")
    
    static const char* kMAVLinkLogGroup         = "MAVLinkLogGroup";
    static const char* kEmailAddressKey         = "Email";
    static const char* kDescriptionsKey         = "Description";
    
    static const char* kDefaultDescr            = "QGroundControl Session";
    
    static const char* kPx4URLKey               = "LogURL";
    
    static const char* kDefaultPx4URL           = "https://logs.px4.io/upload";
    
    static const char* kEnableAutoUploadKey     = "EnableAutoUpload";
    static const char* kEnableAutoStartKey      = "EnableAutoStart";
    static const char* kEnableDeletetKey        = "EnableDelete";
    
    Gus Grubba's avatar
    Gus Grubba committed
    static const char* kSidecarExtension        = ".uploaded";
    
    static const char* kVideoURLKey             = "VideoURL";
    static const char* kWindSpeedKey            = "WindSpeed";
    static const char* kRateKey                 = "RateKey";
    static const char* kPublicLogKey            = "PublicLog";
    static const char* kFeedback                = "feedback";
    static const char* kVideoURL                = "videoUrl";
    
    Gus Grubba's avatar
    Gus Grubba committed
    
    //-----------------------------------------------------------------------------
    
    Gus Grubba's avatar
    Gus Grubba committed
    MAVLinkLogFiles::MAVLinkLogFiles(MAVLinkLogManager* manager, const QString& filePath, bool newFile)
    
    Gus Grubba's avatar
    Gus Grubba committed
        : _manager(manager)
        , _size(0)
        , _selected(false)
        , _uploading(false)
        , _progress(0)
    
    Gus Grubba's avatar
    Gus Grubba committed
        , _writing(false)
    
    Gus Grubba's avatar
    Gus Grubba committed
        , _uploaded(false)
    
    Gus Grubba's avatar
    Gus Grubba committed
    {
        QFileInfo fi(filePath);
        _name = fi.baseName();
    
    Gus Grubba's avatar
    Gus Grubba committed
        if(!newFile) {
            _size = (quint32)fi.size();
            QString sideCar = filePath;
    
            sideCar.replace(manager->logExtension(), kSidecarExtension);
    
    Gus Grubba's avatar
    Gus Grubba committed
            QFileInfo sc(sideCar);
            _uploaded = sc.exists();
        }
    
    //-----------------------------------------------------------------------------
    void
    
    Gus Grubba's avatar
    Gus Grubba committed
    MAVLinkLogFiles::setSize(quint32 size)
    
    {
        _size = size;
        emit sizeChanged();
    }
    
    
    Gus Grubba's avatar
    Gus Grubba committed
    //-----------------------------------------------------------------------------
    void
    
    Gus Grubba's avatar
    Gus Grubba committed
    MAVLinkLogFiles::setSelected(bool selected)
    
    Gus Grubba's avatar
    Gus Grubba committed
    {
        _selected = selected;
        emit selectedChanged();
        emit _manager->selectedCountChanged();
    }
    
    //-----------------------------------------------------------------------------
    void
    
    Gus Grubba's avatar
    Gus Grubba committed
    MAVLinkLogFiles::setUploading(bool uploading)
    
    Gus Grubba's avatar
    Gus Grubba committed
    {
        _uploading = uploading;
        emit uploadingChanged();
    }
    
    //-----------------------------------------------------------------------------
    void
    
    Gus Grubba's avatar
    Gus Grubba committed
    MAVLinkLogFiles::setProgress(qreal progress)
    
    Gus Grubba's avatar
    Gus Grubba committed
    {
        _progress = progress;
        emit progressChanged();
    }
    
    
    //-----------------------------------------------------------------------------
    void
    
    Gus Grubba's avatar
    Gus Grubba committed
    MAVLinkLogFiles::setWriting(bool writing)
    
    {
        _writing = writing;
        emit writingChanged();
    }
    
    
    Gus Grubba's avatar
    Gus Grubba committed
    //-----------------------------------------------------------------------------
    void
    
    Gus Grubba's avatar
    Gus Grubba committed
    MAVLinkLogFiles::setUploaded(bool uploaded)
    
    Gus Grubba's avatar
    Gus Grubba committed
    {
        _uploaded = uploaded;
        emit uploadedChanged();
    }
    
    
    Gus Grubba's avatar
    Gus Grubba committed
    //-----------------------------------------------------------------------------
    //-----------------------------------------------------------------------------
    
    Gus Grubba's avatar
    Gus Grubba committed
    MAVLinkLogProcessor::MAVLinkLogProcessor()
    
    Gus Grubba's avatar
    Gus Grubba committed
        : _fd(NULL)
        , _written(0)
        , _sequence(-1)
        , _numDrops(0)
        , _gotHeader(false)
        , _error(false)
        , _record(NULL)
    
    Gus Grubba's avatar
    Gus Grubba committed
    {
    
    Gus Grubba's avatar
    Gus Grubba committed
    }
    
    //-----------------------------------------------------------------------------
    
    Gus Grubba's avatar
    Gus Grubba committed
    MAVLinkLogProcessor::~MAVLinkLogProcessor()
    
    Gus Grubba's avatar
    Gus Grubba committed
    {
        close();
    }
    
    //-----------------------------------------------------------------------------
    void
    
    Gus Grubba's avatar
    Gus Grubba committed
    MAVLinkLogProcessor::close()
    
    Gus Grubba's avatar
    Gus Grubba committed
    {
        if(_fd) {
            fclose(_fd);
            _fd = NULL;
    
    Gus Grubba's avatar
    Gus Grubba committed
        }
    }
    
    
    Gus Grubba's avatar
    Gus Grubba committed
    //-----------------------------------------------------------------------------
    bool
    
    Gus Grubba's avatar
    Gus Grubba committed
    MAVLinkLogProcessor::valid()
    
    Gus Grubba's avatar
    Gus Grubba committed
    {
        return (_fd != NULL) && (_record != NULL);
    }
    
    //-----------------------------------------------------------------------------
    bool
    
    Gus Grubba's avatar
    Gus Grubba committed
    MAVLinkLogProcessor::create(MAVLinkLogManager* manager, const QString path, uint8_t id)
    
    Gus Grubba's avatar
    Gus Grubba committed
    {
        _fileName.sprintf("%s/%03d-%s%s",
    
    DonLakeFlyer's avatar
    DonLakeFlyer committed
                          path.toLatin1().data(),
                          id,
    
                          QDateTime::currentDateTime().toString("yyyy-MM-dd-hh-mm-ss-zzz").toLocal8Bit().data(),
                          manager->logExtension().toLocal8Bit().data());
        _fd = fopen(_fileName.toLocal8Bit().data(), "wb");
    
    Gus Grubba's avatar
    Gus Grubba committed
        if(_fd) {
    
    Gus Grubba's avatar
    Gus Grubba committed
            _record = new MAVLinkLogFiles(manager, _fileName, true);
    
    Gus Grubba's avatar
    Gus Grubba committed
            _record->setWriting(true);
            _sequence = -1;
            return true;
        }
        return false;
    }
    
    //-----------------------------------------------------------------------------
    bool
    
    Gus Grubba's avatar
    Gus Grubba committed
    MAVLinkLogProcessor::_checkSequence(uint16_t seq, int& num_drops)
    
    Gus Grubba's avatar
    Gus Grubba committed
    {
        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
    MAVLinkLogProcessor::_writeData(void* data, int len)
    
    Gus Grubba's avatar
    Gus Grubba committed
    {
        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
                qCDebug(MAVLinkLogManagerLog) << "File IO error:" << len << "bytes into" << _fileName;
    
    Gus Grubba's avatar
    Gus Grubba committed
            }
        }
    }
    
    //-----------------------------------------------------------------------------
    QByteArray
    
    Gus Grubba's avatar
    Gus Grubba committed
    MAVLinkLogProcessor::_writeUlogMessage(QByteArray& data)
    
    Gus Grubba's avatar
    Gus Grubba committed
    {
        //-- 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
    MAVLinkLogProcessor::processStreamData(uint16_t sequence, uint8_t first_message, QByteArray data)
    
    Gus Grubba's avatar
    Gus Grubba committed
    {
        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.
    
                    qCWarning(MAVLinkLogManagerLog) << "Corrupt log header. Canceling log download.";
    
    Gus Grubba's avatar
    Gus Grubba committed
                    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();
    
    Patrick José Pereira's avatar
    Patrick José Pereira committed
                //-- If no useful information in this message. Drop it.
    
    Gus Grubba's avatar
    Gus Grubba committed
                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
    //-----------------------------------------------------------------------------
    
    Gus Grubba's avatar
    Gus Grubba committed
    //-----------------------------------------------------------------------------
    
    MAVLinkLogManager::MAVLinkLogManager(QGCApplication* app, QGCToolbox* toolbox)
        : QGCTool(app, toolbox)
    
        , _enableAutoStart(false)
    
    Gus Grubba's avatar
    Gus Grubba committed
        , _nam(NULL)
        , _currentLogfile(NULL)
    
        , _vehicle(NULL)
        , _logRunning(false)
    
    Gus Grubba's avatar
    Gus Grubba committed
        , _loggingDisabled(false)
    
    Gus Grubba's avatar
    Gus Grubba committed
        , _logProcessor(NULL)
    
        , _deleteAfterUpload(false)
    
        , _windSpeed(-1)
        , _publicLog(false)
    
        , _logginDenied(false)
    
    Gus Grubba's avatar
    Gus Grubba committed
    {
        //-- Get saved settings
        QSettings settings;
    
        settings.beginGroup(kMAVLinkLogGroup);
    
    Gus Grubba's avatar
    Gus Grubba committed
        setEmailAddress(settings.value(kEmailAddressKey, QString()).toString());
        setDescription(settings.value(kDescriptionsKey, QString(kDefaultDescr)).toString());
        setUploadURL(settings.value(kPx4URLKey, QString(kDefaultPx4URL)).toString());
    
        setVideoURL(settings.value(kVideoURLKey, QString()).toString());
    
        setEnableAutoUpload(settings.value(kEnableAutoUploadKey, true).toBool());
    
        setEnableAutoStart(settings.value(kEnableAutoStartKey, false).toBool());
    
        setDeleteAfterUpload(settings.value(kEnableDeletetKey, false).toBool());
    
        setWindSpeed(settings.value(kWindSpeedKey, -1).toInt());
        setRating(settings.value(kRateKey, "notset").toString());
        setPublicLog(settings.value(kPublicLogKey, true).toBool());
    
    Gus Grubba's avatar
    Gus Grubba committed
    }
    
    //-----------------------------------------------------------------------------
    
    Gus Grubba's avatar
    Gus Grubba committed
    MAVLinkLogManager::~MAVLinkLogManager()
    
    Gus Grubba's avatar
    Gus Grubba committed
    {
        _logFiles.clear();
    }
    
    //-----------------------------------------------------------------------------
    void
    
    Gus Grubba's avatar
    Gus Grubba committed
    MAVLinkLogManager::setToolbox(QGCToolbox* toolbox)
    
    Gus Grubba's avatar
    Gus Grubba committed
        QGCTool::setToolbox(toolbox);
        QQmlEngine::setObjectOwnership(this, QQmlEngine::CppOwnership);
    
    Gus Grubba's avatar
    Gus Grubba committed
        qmlRegisterUncreatableType<MAVLinkLogManager>("QGroundControl.MAVLinkLogManager", 1, 0, "MAVLinkLogManager", "Reference only");
    
        //-- Logging location
        _ulogExtension  = ".";
        _ulogExtension += qgcApp()->toolbox()->settingsManager()->appSettings()->logFileExtension;
        _logPath = qgcApp()->toolbox()->settingsManager()->appSettings()->logSavePath();
        if(!QDir(_logPath).exists()) {
            if(!QDir().mkpath(_logPath)) {
                qCWarning(MAVLinkLogManagerLog) << "Could not create MAVLink log download path:" << _logPath;
                _loggingDisabled = true;
            }
        }
    
    Gus Grubba's avatar
    Gus Grubba committed
        if(!_loggingDisabled) {
    
            //-- Load current list of logs
            QString filter = "*";
            filter += _ulogExtension;
            QDirIterator it(_logPath, QStringList() << filter, QDir::Files);
            while(it.hasNext()) {
                _insertNewLog(new MAVLinkLogFiles(this, it.next()));
            }
            qCDebug(MAVLinkLogManagerLog) << "MAVLink logs directory:" << _logPath;
    
    Gus Grubba's avatar
    Gus Grubba committed
            connect(toolbox->multiVehicleManager(), &MultiVehicleManager::activeVehicleChanged, this, &MAVLinkLogManager::_activeVehicleChanged);
    
    Gus Grubba's avatar
    Gus Grubba committed
    }
    
    //-----------------------------------------------------------------------------
    void
    
    Gus Grubba's avatar
    Gus Grubba committed
    MAVLinkLogManager::setEmailAddress(QString email)
    
    Gus Grubba's avatar
    Gus Grubba committed
    {
        _emailAddress = email;
        QSettings settings;
    
        settings.beginGroup(kMAVLinkLogGroup);
    
    Gus Grubba's avatar
    Gus Grubba committed
        settings.setValue(kEmailAddressKey, email);
        emit emailAddressChanged();
    }
    
    //-----------------------------------------------------------------------------
    void
    
    Gus Grubba's avatar
    Gus Grubba committed
    MAVLinkLogManager::setDescription(QString description)
    
    Gus Grubba's avatar
    Gus Grubba committed
    {
        _description = description;
        QSettings settings;
    
        settings.beginGroup(kMAVLinkLogGroup);
    
    Gus Grubba's avatar
    Gus Grubba committed
        settings.setValue(kDescriptionsKey, description);
        emit descriptionChanged();
    }
    
    //-----------------------------------------------------------------------------
    void
    
    Gus Grubba's avatar
    Gus Grubba committed
    MAVLinkLogManager::setUploadURL(QString url)
    
    Gus Grubba's avatar
    Gus Grubba committed
    {
        _uploadURL = url;
        if(_uploadURL.isEmpty()) {
            _uploadURL = kDefaultPx4URL;
        }
        QSettings settings;
    
        settings.beginGroup(kMAVLinkLogGroup);
    
    Gus Grubba's avatar
    Gus Grubba committed
        settings.setValue(kPx4URLKey, _uploadURL);
        emit uploadURLChanged();
    }
    
    
    //-----------------------------------------------------------------------------
    void
    MAVLinkLogManager::setFeedback(QString fb)
    {
        _feedback = fb;
        emit feedbackChanged();
    }
    
    //-----------------------------------------------------------------------------
    void
    MAVLinkLogManager::setVideoURL(QString url)
    {
        _videoURL = url;
        QSettings settings;
        settings.beginGroup(kMAVLinkLogGroup);
        settings.setValue(kVideoURLKey, url);
        emit videoURLChanged();
    }
    
    
    Gus Grubba's avatar
    Gus Grubba committed
    //-----------------------------------------------------------------------------
    void
    
    Gus Grubba's avatar
    Gus Grubba committed
    MAVLinkLogManager::setEnableAutoUpload(bool enable)
    
    {
        _enableAutoUpload = enable;
        QSettings settings;
    
        settings.beginGroup(kMAVLinkLogGroup);
    
        settings.setValue(kEnableAutoUploadKey, enable);
        emit enableAutoUploadChanged();
    }
    
    //-----------------------------------------------------------------------------
    void
    
    Gus Grubba's avatar
    Gus Grubba committed
    MAVLinkLogManager::setEnableAutoStart(bool enable)
    
    Gus Grubba's avatar
    Gus Grubba committed
        QSettings settings;
    
        settings.beginGroup(kMAVLinkLogGroup);
    
        settings.setValue(kEnableAutoStartKey, enable);
        emit enableAutoStartChanged();
    
    //-----------------------------------------------------------------------------
    void
    
    Gus Grubba's avatar
    Gus Grubba committed
    MAVLinkLogManager::setDeleteAfterUpload(bool enable)
    
    {
        _deleteAfterUpload = enable;
        QSettings settings;
    
        settings.beginGroup(kMAVLinkLogGroup);
    
        settings.setValue(kEnableDeletetKey, enable);
        emit deleteAfterUploadChanged();
    }
    
    
    //-----------------------------------------------------------------------------
    void
    MAVLinkLogManager::setWindSpeed(int speed)
    {
        _windSpeed = speed;
        QSettings settings;
        settings.beginGroup(kMAVLinkLogGroup);
        settings.setValue(kWindSpeedKey, speed);
        emit windSpeedChanged();
    }
    
    //-----------------------------------------------------------------------------
    void
    MAVLinkLogManager::setRating(QString rate)
    {
        _rating = rate;
        QSettings settings;
        settings.beginGroup(kMAVLinkLogGroup);
        settings.setValue(kRateKey, rate);
        emit ratingChanged();
    }
    
    //-----------------------------------------------------------------------------
    void
    MAVLinkLogManager::setPublicLog(bool pub)
    {
        _publicLog = pub;
        QSettings settings;
        settings.beginGroup(kMAVLinkLogGroup);
        settings.setValue(kPublicLogKey, pub);
        emit publicLogChanged();
    }
    
    
    Gus Grubba's avatar
    Gus Grubba committed
    //-----------------------------------------------------------------------------
    bool
    
    Gus Grubba's avatar
    Gus Grubba committed
    MAVLinkLogManager::uploading()
    
    Gus Grubba's avatar
    Gus Grubba committed
    {
        return _currentLogfile != NULL;
    }
    
    //-----------------------------------------------------------------------------
    void
    
    Gus Grubba's avatar
    Gus Grubba committed
    MAVLinkLogManager::uploadLog()
    
    Gus Grubba's avatar
    Gus Grubba committed
    {
        if(_currentLogfile) {
            _currentLogfile->setUploading(false);
        }
    
    Gus Grubba's avatar
    Gus Grubba committed
        for(int i = 0; i < _logFiles.count(); i++) {
    
    Gus Grubba's avatar
    Gus Grubba committed
            _currentLogfile = qobject_cast<MAVLinkLogFiles*>(_logFiles.get(i));
    
    DonLakeFlyer's avatar
    DonLakeFlyer committed
            if (_currentLogfile) {
                if(_currentLogfile->selected()) {
                    _currentLogfile->setSelected(false);
                    if(!_currentLogfile->uploaded() && !_emailAddress.isEmpty() && !_uploadURL.isEmpty()) {
                        _currentLogfile->setUploading(true);
                        _currentLogfile->setProgress(0.0);
                        QString filePath = _makeFilename(_currentLogfile->name());
                        _sendLog(filePath);
                        emit uploadingChanged();
                        return;
                    }
    
    DonLakeFlyer's avatar
    DonLakeFlyer committed
            } else {
                qWarning() << "Internal error";
    
    Gus Grubba's avatar
    Gus Grubba committed
            }
        }
        _currentLogfile = NULL;
    
        emit uploadingChanged();
    }
    
    //-----------------------------------------------------------------------------
    void
    
    Gus Grubba's avatar
    Gus Grubba committed
    MAVLinkLogManager::_insertNewLog(MAVLinkLogFiles* newLog)
    
    {
        //-- 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
                MAVLinkLogFiles* f = qobject_cast<MAVLinkLogFiles*>(_logFiles.get(i));
    
                if(newLog->name() < f->name()) {
                    _logFiles.insert(i, newLog);
                    return;
                }
            }
            _logFiles.append(newLog);
        }
    
    Gus Grubba's avatar
    Gus Grubba committed
    //-----------------------------------------------------------------------------
    int
    
    Gus Grubba's avatar
    Gus Grubba committed
    MAVLinkLogManager::_getFirstSelected()
    
    Gus Grubba's avatar
    Gus Grubba committed
    {
        for(int i = 0; i < _logFiles.count(); i++) {
    
    Gus Grubba's avatar
    Gus Grubba committed
            MAVLinkLogFiles* f = qobject_cast<MAVLinkLogFiles*>(_logFiles.get(i));
    
    DonLakeFlyer's avatar
    DonLakeFlyer committed
            if (f) {
                if(f->selected()) {
                    return i;
                }
            } else {
                qWarning() << "Internal error";
    
    Gus Grubba's avatar
    Gus Grubba committed
    //-----------------------------------------------------------------------------
    void
    
    Gus Grubba's avatar
    Gus Grubba committed
    MAVLinkLogManager::deleteLog()
    
    Gus Grubba's avatar
    Gus Grubba committed
        while (true) {
            int idx = _getFirstSelected();
            if(idx < 0) {
                break;
            }
    
    Gus Grubba's avatar
    Gus Grubba committed
            MAVLinkLogFiles* log = qobject_cast<MAVLinkLogFiles*>(_logFiles.get(idx));
    
            _deleteLog(log);
    
    //-----------------------------------------------------------------------------
    void
    
    Gus Grubba's avatar
    Gus Grubba committed
    MAVLinkLogManager::_deleteLog(MAVLinkLogFiles* log)
    
    Gus Grubba's avatar
    Gus Grubba committed
        QString filePath = _makeFilename(log->name());
    
        QFile gone(filePath);
        if(!gone.remove()) {
    
    Gus Grubba's avatar
    Gus Grubba committed
            qCWarning(MAVLinkLogManagerLog) << "Could not delete MAVLink log file:" << _logPath;
    
    Gus Grubba's avatar
    Gus Grubba committed
        //-- Remove sidecar file (if any)
    
        filePath.replace(_ulogExtension, kSidecarExtension);
    
    Gus Grubba's avatar
    Gus Grubba committed
        QFile sgone(filePath);
        if(sgone.exists()) {
            sgone.remove();
        }
        //-- Remove file from list and delete record
    
        _logFiles.removeOne(log);
        delete log;
        emit logFilesChanged();
    }
    
    
    Gus Grubba's avatar
    Gus Grubba committed
    //-----------------------------------------------------------------------------
    void
    
    Gus Grubba's avatar
    Gus Grubba committed
    MAVLinkLogManager::cancelUpload()
    
    Gus Grubba's avatar
    Gus Grubba committed
        for(int i = 0; i < _logFiles.count(); i++) {
    
    Gus Grubba's avatar
    Gus Grubba committed
            MAVLinkLogFiles* pLogFile = qobject_cast<MAVLinkLogFiles*>(_logFiles.get(i));
    
    DonLakeFlyer's avatar
    DonLakeFlyer committed
            if (pLogFile) {
                if(pLogFile->selected() && pLogFile != _currentLogfile) {
                    pLogFile->setSelected(false);
                }
            } else {
                qWarning() << "Internal error";
    
    Gus Grubba's avatar
    Gus Grubba committed
            }
        }
        if(_currentLogfile) {
            emit abortUpload();
        }
    }
    
    
    //-----------------------------------------------------------------------------
    void
    
    Gus Grubba's avatar
    Gus Grubba committed
    MAVLinkLogManager::startLogging()
    
        if(_vehicle && _vehicle->px4Firmware() && !_logginDenied) {
    
    Gus Grubba's avatar
    Gus Grubba committed
            if(_createNewLog()) {
                _vehicle->startMavlinkLog();
                _logRunning = true;
                emit logRunningChanged();
            }
    
        }
    }
    
    //-----------------------------------------------------------------------------
    void
    
    Gus Grubba's avatar
    Gus Grubba committed
    MAVLinkLogManager::stopLogging()
    
        if(_vehicle && _vehicle->px4Firmware()) {
    
            //-- Tell vehicle to stop sending logs
    
            _vehicle->stopMavlinkLog();
    
    Gus Grubba's avatar
    Gus Grubba committed
        if(_logProcessor) {
            _logProcessor->close();
            if(_logProcessor->record()) {
                _logProcessor->record()->setWriting(false);
    
                if(_enableAutoUpload) {
                    //-- Queue log for auto upload (set selected flag)
    
    Gus Grubba's avatar
    Gus Grubba committed
                    _logProcessor->record()->setSelected(true);
    
                    if(!uploading()) {
                        uploadLog();
    
    Gus Grubba's avatar
    Gus Grubba committed
            delete _logProcessor;
            _logProcessor = NULL;
    
            _logRunning = false;
            emit logRunningChanged();
    
    Gus Grubba's avatar
    Gus Grubba committed
    //-----------------------------------------------------------------------------
    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
    MAVLinkLogManager::_sendLog(const QString& logFile)
    
    Gus Grubba's avatar
    Gus Grubba committed
    {
        QString defaultDescription = _description;
        if(_description.isEmpty()) {
    
    Gus Grubba's avatar
    Gus Grubba committed
            qCWarning(MAVLinkLogManagerLog) << "Log description missing. Using defaults.";
    
    Gus Grubba's avatar
    Gus Grubba committed
            defaultDescription = kDefaultDescr;
        }
        if(_emailAddress.isEmpty()) {
    
            qCWarning(MAVLinkLogManagerLog) << "User email missing.";
    
    Gus Grubba's avatar
    Gus Grubba committed
            return false;
        }
        if(_uploadURL.isEmpty()) {
    
            qCWarning(MAVLinkLogManagerLog) << "Upload URL missing.";
    
    Gus Grubba's avatar
    Gus Grubba committed
            return false;
        }
        QFileInfo fi(logFile);
        if(!fi.exists()) {
    
            qCWarning(MAVLinkLogManagerLog) << "Log file missing:" << logFile;
    
    Gus Grubba's avatar
    Gus Grubba committed
            return false;
        }
    
    Gus Grubba's avatar
    Gus Grubba committed
        QFile* file = new QFile(logFile);
    
    Gus Grubba's avatar
    Gus Grubba committed
        if(!file || !file->open(QIODevice::ReadOnly)) {
    
    Gus Grubba's avatar
    Gus Grubba committed
            if(file) {
                delete file;
            }
    
            qCWarning(MAVLinkLogManagerLog) << "Could not open log file:" << logFile;
    
    Gus Grubba's avatar
    Gus Grubba committed
            return false;
        }
        if(!_nam) {
    
    Gus Grubba's avatar
    Gus Grubba committed
            _nam = new QNetworkAccessManager(this);
    
    Gus Grubba's avatar
    Gus Grubba committed
        }
        QNetworkProxy savedProxy = _nam->proxy();
        QNetworkProxy tempProxy;
        tempProxy.setType(QNetworkProxy::DefaultProxy);
        _nam->setProxy(tempProxy);
        //-- Build POST request
    
    Gus Grubba's avatar
    Gus Grubba committed
        QHttpMultiPart* multiPart = new QHttpMultiPart(QHttpMultiPart::FormDataType);
    
    Gus Grubba's avatar
    Gus Grubba committed
        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 typePart        = create_form_part("type", "flightreport");
        QHttpPart windPart        = create_form_part("windSpeed", QString::number(_windSpeed));
        QHttpPart ratingPart      = create_form_part("rating", _rating);
        QHttpPart publicPart      = create_form_part("public", _publicLog ? "true" : "false");
    
    Gus Grubba's avatar
    Gus Grubba committed
        //-- Assemble request and POST it
        multiPart->append(emailPart);
        multiPart->append(descriptionPart);
        multiPart->append(sourcePart);
        multiPart->append(versionPart);
    
        multiPart->append(typePart);
        multiPart->append(windPart);
        multiPart->append(ratingPart);
        multiPart->append(publicPart);
        //-- Optional
        QHttpPart feedbackPart;
        if(_feedback.isEmpty()) {
            feedbackPart = create_form_part(kFeedback, "None Given");
        } else {
            feedbackPart = create_form_part(kFeedback, _feedback);
        }
        multiPart->append(feedbackPart);
        QHttpPart videoPart;
        if(_videoURL.isEmpty()) {
            videoPart = create_form_part(kVideoURL, "None");
        } else {
            videoPart = create_form_part(kVideoURL, _videoURL);
        }
        multiPart->append(videoPart);
        //-- Actual Log File
        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);
    
    Gus Grubba's avatar
    Gus Grubba committed
        multiPart->append(logPart);
        file->setParent(multiPart);
        QNetworkRequest request(_uploadURL);
    
    #if QT_VERSION > 0x050600
    
    Gus Grubba's avatar
    Gus Grubba committed
        request.setAttribute(QNetworkRequest::FollowRedirectsAttribute, true);
    
    Gus Grubba's avatar
    Gus Grubba committed
        QNetworkReply* reply = _nam->post(request, multiPart);
    
    Gus Grubba's avatar
    Gus Grubba committed
        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
        multiPart->setParent(reply);
    
    Gus Grubba's avatar
    Gus Grubba committed
        qCDebug(MAVLinkLogManagerLog) << "Log" << fi.baseName() << "Uploading." << fi.size() << "bytes.";
    
    Gus Grubba's avatar
    Gus Grubba committed
        _nam->setProxy(savedProxy);
        return true;
    }
    
    //-----------------------------------------------------------------------------
    bool
    
    Gus Grubba's avatar
    Gus Grubba committed
    MAVLinkLogManager::_processUploadResponse(int http_code, QByteArray& data)
    
    Gus Grubba's avatar
    Gus Grubba committed
        qCDebug(MAVLinkLogManagerLog) << "Uploaded response:" << QString::fromUtf8(data);
    
    Gus Grubba's avatar
    Gus Grubba committed
        emit readyRead(data);
        return http_code == 200;
    }
    
    //-----------------------------------------------------------------------------
    void
    
    Gus Grubba's avatar
    Gus Grubba committed
    MAVLinkLogManager::_dataAvailable()
    
    Gus Grubba's avatar
    Gus Grubba committed
        QNetworkReply* reply = qobject_cast<QNetworkReply*>(sender());
    
    Gus Grubba's avatar
    Gus Grubba committed
        if(!reply) {
            return;
        }
        QByteArray data = reply->readAll();
    
    Gus Grubba's avatar
    Gus Grubba committed
        qCDebug(MAVLinkLogManagerLog) << "Uploaded response data:" << QString::fromUtf8(data);
    
    Gus Grubba's avatar
    Gus Grubba committed
    }
    
    //-----------------------------------------------------------------------------
    void
    
    Gus Grubba's avatar
    Gus Grubba committed
    MAVLinkLogManager::_uploadFinished()
    
    Gus Grubba's avatar
    Gus Grubba committed
        QNetworkReply* reply = qobject_cast<QNetworkReply*>(sender());
    
    Gus Grubba's avatar
    Gus Grubba committed
        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
            qCDebug(MAVLinkLogManagerLog) << "Log uploaded.";
    
    Gus Grubba's avatar
    Gus Grubba committed
            emit succeed();
    
            if(_deleteAfterUpload) {
                if(_currentLogfile) {
                    _deleteLog(_currentLogfile);
                    _currentLogfile = NULL;
                }
    
    Gus Grubba's avatar
    Gus Grubba committed
            } else {
                if(_currentLogfile) {
                    _currentLogfile->setUploaded(true);
                    //-- Write side-car file to flag it as uploaded
                    QString sideCar = _makeFilename(_currentLogfile->name());
    
                    sideCar.replace(_ulogExtension, kSidecarExtension);
    
    Gus Grubba's avatar
    Gus Grubba committed
                    FILE* f = fopen(sideCar.toLatin1().data(), "wb");
                    if(f) {
                        fclose(f);
                    }
                }
    
    Gus Grubba's avatar
    Gus Grubba committed
        } else {
    
    Gus Grubba's avatar
    Gus Grubba committed
            qCWarning(MAVLinkLogManagerLog) << QString("Log Upload Error: %1 status: %2").arg(reply->errorString(), reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toString());
    
    Gus Grubba's avatar
    Gus Grubba committed
            emit failed();
        }
        reply->deleteLater();
        //-- Next (if any)
        uploadLog();
    }
    
    //-----------------------------------------------------------------------------
    void
    
    Gus Grubba's avatar
    Gus Grubba committed
    MAVLinkLogManager::_uploadProgress(qint64 bytesSent, qint64 bytesTotal)
    
    Gus Grubba's avatar
    Gus Grubba committed
    {
        if(bytesTotal) {
            qreal progress = (qreal)bytesSent / (qreal)bytesTotal;
    
    Gus Grubba's avatar
    Gus Grubba committed
            if(_currentLogfile) {
    
    Gus Grubba's avatar
    Gus Grubba committed
                _currentLogfile->setProgress(progress);
    
    Gus Grubba's avatar
    Gus Grubba committed
        qCDebug(MAVLinkLogManagerLog) << bytesSent << "of" << bytesTotal;
    
    
    //-----------------------------------------------------------------------------
    void
    
    Gus Grubba's avatar
    Gus Grubba committed
    MAVLinkLogManager::_activeVehicleChanged(Vehicle* vehicle)
    
    {
        //-- 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
        //   For now, we only handle one log download at a time.
    
        // Disconnect the previous one (if any)
    
        if(_vehicle && _vehicle->px4Firmware()) {
    
            disconnect(_vehicle, &Vehicle::armedChanged,        this, &MAVLinkLogManager::_armedChanged);
            disconnect(_vehicle, &Vehicle::mavlinkLogData,      this, &MAVLinkLogManager::_mavlinkLogData);
            disconnect(_vehicle, &Vehicle::mavCommandResult,    this, &MAVLinkLogManager::_mavCommandResult);
    
            //-- Stop logging (if that's the case)
            stopLogging();
    
            emit canStartLogChanged();
        }
        // Connect new system
    
        if(vehicle && vehicle->px4Firmware()) {
    
            //-- Reset logging denied flag as well
            _logginDenied = false;
    
            connect(_vehicle, &Vehicle::armedChanged,       this, &MAVLinkLogManager::_armedChanged);
            connect(_vehicle, &Vehicle::mavlinkLogData,     this, &MAVLinkLogManager::_mavlinkLogData);
            connect(_vehicle, &Vehicle::mavCommandResult,   this, &MAVLinkLogManager::_mavCommandResult);
    
            emit canStartLogChanged();
        }
    }
    
    //-----------------------------------------------------------------------------
    void
    
    Gus Grubba's avatar
    Gus Grubba committed
    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
        if(_logProcessor && _logProcessor->valid()) {
            if(!_logProcessor->processStreamData(sequence, first_message, data)) {
    
                qCWarning(MAVLinkLogManagerLog) << "Error writing MAVLink log file:" << _logProcessor->fileName();
    
    Gus Grubba's avatar
    Gus Grubba committed
                delete _logProcessor;
                _logProcessor = NULL;
    
    Gus Grubba's avatar
    Gus Grubba committed
                _logRunning = false;
                _vehicle->stopMavlinkLog();
                emit logRunningChanged();
            }
        } else {
    
    Gus Grubba's avatar
    Gus Grubba committed
            qCWarning(MAVLinkLogManagerLog) << "MAVLink log data received when not expected.";
    
    //-----------------------------------------------------------------------------
    void
    
    MAVLinkLogManager::_mavCommandResult(int vehicleId, int component, int command, int result, bool noReponseFromVehicle)
    
        Q_UNUSED(vehicleId);
        Q_UNUSED(component);
        Q_UNUSED(noReponseFromVehicle)
    
    
        if(command == MAV_CMD_LOGGING_START || command == MAV_CMD_LOGGING_STOP) {
            //-- Did it fail?
    
            if(result != MAV_RESULT_ACCEPTED) {
    
                if(command == MAV_CMD_LOGGING_STOP) {
                    //-- Not that it could happen but...
    
    Gus Grubba's avatar
    Gus Grubba committed
                    qCWarning(MAVLinkLogManagerLog) << "Stop MAVLink log command failed.";
    
                } else {
                    //-- Could not start logging for some reason.
    
                    if(result == MAV_RESULT_DENIED) {
                        _logginDenied = true;
                        qCWarning(MAVLinkLogManagerLog) << "Start MAVLink log command denied.";
                    } else {
                        qCWarning(MAVLinkLogManagerLog) << "Start MAVLink log command failed.";
                    }
    
                    _discardLog();
                }
            }
        }
    }
    
    //-----------------------------------------------------------------------------
    void
    
    Gus Grubba's avatar
    Gus Grubba committed
    MAVLinkLogManager::_discardLog()
    
    {
        //-- Delete (empty) log file (and record)
    
    Gus Grubba's avatar
    Gus Grubba committed
        if(_logProcessor) {
            _logProcessor->close();
            if(_logProcessor->record()) {
                _deleteLog(_logProcessor->record());
    
    Gus Grubba's avatar
    Gus Grubba committed
            delete _logProcessor;
            _logProcessor = NULL;
    
        }
        _logRunning = false;
        emit logRunningChanged();
    }
    
    
    Gus Grubba's avatar
    Gus Grubba committed
    //-----------------------------------------------------------------------------
    bool
    
    Gus Grubba's avatar
    Gus Grubba committed
    MAVLinkLogManager::_createNewLog()
    
    Gus Grubba's avatar
    Gus Grubba committed
        if(_logProcessor) {
            delete _logProcessor;
            _logProcessor = NULL;
    
    Gus Grubba's avatar
    Gus Grubba committed
        _logProcessor = new MAVLinkLogProcessor;
    
    Gus Grubba's avatar
    Gus Grubba committed
        if(_logProcessor->create(this, _logPath, _vehicle->id())) {
            _insertNewLog(_logProcessor->record());
    
            emit logFilesChanged();
        } else {
    
            qCWarning(MAVLinkLogManagerLog) << "Could not create MAVLink log file:" << _logProcessor->fileName();
    
    Gus Grubba's avatar
    Gus Grubba committed
            delete _logProcessor;
            _logProcessor = NULL;
    
    Gus Grubba's avatar
    Gus Grubba committed
        return _logProcessor != NULL;
    
    }
    
    //-----------------------------------------------------------------------------
    void
    
    Gus Grubba's avatar
    Gus Grubba committed
    MAVLinkLogManager::_armedChanged(bool armed)
    
        if(_vehicle && _vehicle->px4Firmware()) {
    
            if(armed) {
                if(_enableAutoStart) {
    
    Gus Grubba's avatar
    Gus Grubba committed
                    startLogging();
    
                }
            } else {
                if(_logRunning && _enableAutoStart) {
    
    Gus Grubba's avatar
    Gus Grubba committed
                    stopLogging();
    
    Gus Grubba's avatar
    Gus Grubba committed
    
    //-----------------------------------------------------------------------------
    QString
    
    Gus Grubba's avatar
    Gus Grubba committed
    MAVLinkLogManager::_makeFilename(const QString& baseName)
    
    Gus Grubba's avatar
    Gus Grubba committed
    {
        QString filePath = _logPath;
        filePath += "/";
        filePath += baseName;
    
        filePath += _ulogExtension;
    
    Gus Grubba's avatar
    Gus Grubba committed
        return filePath;
    }