806 lines
26 KiB
C++
806 lines
26 KiB
C++
/****************************************************************************
|
|
*
|
|
* (c) 2009-2020 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.
|
|
*
|
|
****************************************************************************/
|
|
|
|
|
|
#include "LogDownloadController.h"
|
|
#include "MultiVehicleManager.h"
|
|
#include "QGCMAVLink.h"
|
|
#include "UAS.h"
|
|
#include "QGCApplication.h"
|
|
#include "QGCToolbox.h"
|
|
#include "QGCMapEngine.h"
|
|
#include "ParameterManager.h"
|
|
#include "Vehicle.h"
|
|
#include "SettingsManager.h"
|
|
|
|
#include <QDebug>
|
|
#include <QSettings>
|
|
#include <QUrl>
|
|
#include <QBitArray>
|
|
#include <QtCore/qmath.h>
|
|
|
|
#define kTimeOutMilliseconds 500
|
|
#define kGUIRateMilliseconds 17
|
|
#define kTableBins 512
|
|
#define kChunkSize (kTableBins * MAVLINK_MSG_LOG_DATA_FIELD_DATA_LEN)
|
|
|
|
QGC_LOGGING_CATEGORY(LogDownloadLog, "LogDownloadLog")
|
|
|
|
//-----------------------------------------------------------------------------
|
|
struct LogDownloadData {
|
|
LogDownloadData(QGCLogEntry* entry);
|
|
QBitArray chunk_table;
|
|
uint32_t current_chunk;
|
|
QFile file;
|
|
QString filename;
|
|
uint ID;
|
|
QGCLogEntry* entry;
|
|
uint written;
|
|
size_t rate_bytes;
|
|
qreal rate_avg;
|
|
QElapsedTimer elapsed;
|
|
|
|
void advanceChunk()
|
|
{
|
|
current_chunk++;
|
|
chunk_table = QBitArray(chunkBins(), false);
|
|
}
|
|
|
|
// The number of MAVLINK_MSG_LOG_DATA_FIELD_DATA_LEN bins in the current chunk
|
|
uint32_t chunkBins() const
|
|
{
|
|
return qMin(qCeil((entry->size() - current_chunk*kChunkSize)/static_cast<qreal>(MAVLINK_MSG_LOG_DATA_FIELD_DATA_LEN)),
|
|
kTableBins);
|
|
}
|
|
|
|
// The number of kChunkSize chunks in the file
|
|
uint32_t numChunks() const
|
|
{
|
|
return qCeil(entry->size() / static_cast<qreal>(kChunkSize));
|
|
}
|
|
|
|
// True if all bins in the chunk have been set to val
|
|
bool chunkEquals(const bool val) const
|
|
{
|
|
return chunk_table == QBitArray(chunk_table.size(), val);
|
|
}
|
|
|
|
};
|
|
|
|
//----------------------------------------------------------------------------------------
|
|
LogDownloadData::LogDownloadData(QGCLogEntry* entry_)
|
|
: ID(entry_->id())
|
|
, entry(entry_)
|
|
, written(0)
|
|
, rate_bytes(0)
|
|
, rate_avg(0)
|
|
{
|
|
|
|
}
|
|
|
|
//----------------------------------------------------------------------------------------
|
|
QGCLogEntry::QGCLogEntry(uint logId, const QDateTime& dateTime, uint logSize, bool received)
|
|
: _logID(logId)
|
|
, _logSize(logSize)
|
|
, _logTimeUTC(dateTime)
|
|
, _received(received)
|
|
, _selected(false)
|
|
{
|
|
_status = tr("Pending");
|
|
}
|
|
|
|
//----------------------------------------------------------------------------------------
|
|
QString
|
|
QGCLogEntry::sizeStr() const
|
|
{
|
|
return QGCMapEngine::bigSizeToString(_logSize);
|
|
}
|
|
|
|
//----------------------------------------------------------------------------------------
|
|
LogDownloadController::LogDownloadController(void)
|
|
: _uas(nullptr)
|
|
, _downloadData(nullptr)
|
|
, _vehicle(nullptr)
|
|
, _requestingLogEntries(false)
|
|
, _downloadingLogs(false)
|
|
, _retries(0)
|
|
, _apmOneBased(0)
|
|
{
|
|
MultiVehicleManager *manager = qgcApp()->toolbox()->multiVehicleManager();
|
|
connect(manager, &MultiVehicleManager::activeVehicleChanged, this, &LogDownloadController::_setActiveVehicle);
|
|
connect(&_timer, &QTimer::timeout, this, &LogDownloadController::_processDownload);
|
|
_setActiveVehicle(manager->activeVehicle());
|
|
}
|
|
|
|
//----------------------------------------------------------------------------------------
|
|
void
|
|
LogDownloadController::_processDownload()
|
|
{
|
|
if(_requestingLogEntries) {
|
|
_findMissingEntries();
|
|
} else if(_downloadingLogs) {
|
|
_findMissingData();
|
|
}
|
|
}
|
|
|
|
//----------------------------------------------------------------------------------------
|
|
void
|
|
LogDownloadController::_setActiveVehicle(Vehicle* vehicle)
|
|
{
|
|
if(_uas) {
|
|
_logEntriesModel.clear();
|
|
disconnect(_uas, &UASInterface::logEntry, this, &LogDownloadController::_logEntry);
|
|
disconnect(_uas, &UASInterface::logData, this, &LogDownloadController::_logData);
|
|
_uas = nullptr;
|
|
}
|
|
_vehicle = vehicle;
|
|
if(_vehicle) {
|
|
_uas = vehicle->uas();
|
|
connect(_uas, &UASInterface::logEntry, this, &LogDownloadController::_logEntry);
|
|
connect(_uas, &UASInterface::logData, this, &LogDownloadController::_logData);
|
|
}
|
|
}
|
|
|
|
//----------------------------------------------------------------------------------------
|
|
void
|
|
LogDownloadController::_logEntry(UASInterface* uas, uint32_t time_utc, uint32_t size, uint16_t id, uint16_t num_logs, uint16_t /*last_log_num*/)
|
|
{
|
|
//-- Do we care?
|
|
if(!_uas || uas != _uas || !_requestingLogEntries) {
|
|
return;
|
|
}
|
|
//-- If this is the first, pre-fill it
|
|
if(!_logEntriesModel.count() && num_logs > 0) {
|
|
//-- Is this APM? They send a first entry with bogus ID and only the
|
|
// count is valid. From now on, all entries are 1-based.
|
|
if(_vehicle->firmwareType() == MAV_AUTOPILOT_ARDUPILOTMEGA) {
|
|
_apmOneBased = 1;
|
|
}
|
|
for(int i = 0; i < num_logs; i++) {
|
|
QGCLogEntry *entry = new QGCLogEntry(i);
|
|
_logEntriesModel.append(entry);
|
|
}
|
|
}
|
|
//-- Update this log record
|
|
if(num_logs > 0) {
|
|
//-- Skip if empty (APM first packet)
|
|
if(size || _vehicle->firmwareType() != MAV_AUTOPILOT_ARDUPILOTMEGA) {
|
|
id -= _apmOneBased;
|
|
if(id < _logEntriesModel.count()) {
|
|
QGCLogEntry* entry = _logEntriesModel[id];
|
|
entry->setSize(size);
|
|
entry->setTime(QDateTime::fromSecsSinceEpoch(time_utc));
|
|
entry->setReceived(true);
|
|
entry->setStatus(tr("Available"));
|
|
} else {
|
|
qWarning() << "Received log entry for out-of-bound index:" << id;
|
|
}
|
|
}
|
|
} else {
|
|
//-- No logs to list
|
|
_receivedAllEntries();
|
|
}
|
|
//-- Reset retry count
|
|
_retries = 0;
|
|
//-- Do we have it all?
|
|
if(_entriesComplete()) {
|
|
_receivedAllEntries();
|
|
} else {
|
|
//-- Reset timer
|
|
_timer.start(kTimeOutMilliseconds);
|
|
}
|
|
}
|
|
|
|
//----------------------------------------------------------------------------------------
|
|
bool
|
|
LogDownloadController::_entriesComplete()
|
|
{
|
|
//-- Iterate entries and look for a gap
|
|
int num_logs = _logEntriesModel.count();
|
|
for(int i = 0; i < num_logs; i++) {
|
|
QGCLogEntry* entry = _logEntriesModel[i];
|
|
if(entry) {
|
|
if(!entry->received()) {
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
//----------------------------------------------------------------------------------------
|
|
void
|
|
LogDownloadController::_resetSelection(bool canceled)
|
|
{
|
|
int num_logs = _logEntriesModel.count();
|
|
for(int i = 0; i < num_logs; i++) {
|
|
QGCLogEntry* entry = _logEntriesModel[i];
|
|
if(entry) {
|
|
if(entry->selected()) {
|
|
if(canceled) {
|
|
entry->setStatus(tr("Canceled"));
|
|
}
|
|
entry->setSelected(false);
|
|
}
|
|
}
|
|
}
|
|
emit selectionChanged();
|
|
}
|
|
|
|
//----------------------------------------------------------------------------------------
|
|
void
|
|
LogDownloadController::_receivedAllEntries()
|
|
{
|
|
_timer.stop();
|
|
_setListing(false);
|
|
}
|
|
|
|
//----------------------------------------------------------------------------------------
|
|
void
|
|
LogDownloadController::_findMissingEntries()
|
|
{
|
|
int start = -1;
|
|
int end = -1;
|
|
int num_logs = _logEntriesModel.count();
|
|
//-- Iterate entries and look for a gap
|
|
for(int i = 0; i < num_logs; i++) {
|
|
QGCLogEntry* entry = _logEntriesModel[i];
|
|
if(entry) {
|
|
if(!entry->received()) {
|
|
if(start < 0)
|
|
start = i;
|
|
else
|
|
end = i;
|
|
} else {
|
|
if(start >= 0) {
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
//-- Is there something missing?
|
|
if(start >= 0) {
|
|
//-- Have we tried too many times?
|
|
if(_retries++ > 2) {
|
|
for(int i = 0; i < num_logs; i++) {
|
|
QGCLogEntry* entry = _logEntriesModel[i];
|
|
if(entry && !entry->received()) {
|
|
entry->setStatus(tr("Error"));
|
|
}
|
|
}
|
|
//-- Give up
|
|
_receivedAllEntries();
|
|
qWarning() << "Too many errors retreiving log list. Giving up.";
|
|
return;
|
|
}
|
|
//-- Is it a sequence or just one entry?
|
|
if(end < 0) {
|
|
end = start;
|
|
}
|
|
//-- APM "Fix"
|
|
start += _apmOneBased;
|
|
end += _apmOneBased;
|
|
//-- Request these entries again
|
|
_requestLogList((uint32_t)start, (uint32_t) end);
|
|
} else {
|
|
_receivedAllEntries();
|
|
}
|
|
}
|
|
|
|
void LogDownloadController::_updateDataRate(void)
|
|
{
|
|
if (_downloadData->elapsed.elapsed() >= kGUIRateMilliseconds) {
|
|
//-- Update download rate
|
|
qreal rrate = _downloadData->rate_bytes / (_downloadData->elapsed.elapsed() / 1000.0);
|
|
_downloadData->rate_avg = (_downloadData->rate_avg * 0.95) + (rrate * 0.05);
|
|
_downloadData->rate_bytes = 0;
|
|
|
|
//-- Update status
|
|
const QString status = QString("%1 (%2/s)").arg(QGCMapEngine::bigSizeToString(_downloadData->written),
|
|
QGCMapEngine::bigSizeToString(_downloadData->rate_avg));
|
|
|
|
_downloadData->entry->setStatus(status);
|
|
_downloadData->elapsed.start();
|
|
}
|
|
}
|
|
|
|
|
|
//----------------------------------------------------------------------------------------
|
|
void
|
|
LogDownloadController::_logData(UASInterface* uas, uint32_t ofs, uint16_t id, uint8_t count, const uint8_t* data)
|
|
{
|
|
if(!_uas || uas != _uas || !_downloadData) {
|
|
return;
|
|
}
|
|
//-- APM "Fix"
|
|
id -= _apmOneBased;
|
|
if(_downloadData->ID != id) {
|
|
qWarning() << "Received log data for wrong log";
|
|
return;
|
|
}
|
|
|
|
if ((ofs % MAVLINK_MSG_LOG_DATA_FIELD_DATA_LEN) != 0) {
|
|
qWarning() << "Ignored misaligned incoming packet @" << ofs;
|
|
return;
|
|
}
|
|
|
|
bool result = false;
|
|
uint32_t timeout_time = kTimeOutMilliseconds;
|
|
if(ofs <= _downloadData->entry->size()) {
|
|
const uint32_t chunk = ofs / kChunkSize;
|
|
if (chunk != _downloadData->current_chunk) {
|
|
qWarning() << "Ignored packet for out of order chunk actual:expected" << chunk << _downloadData->current_chunk;
|
|
return;
|
|
}
|
|
const uint16_t bin = (ofs - chunk*kChunkSize) / MAVLINK_MSG_LOG_DATA_FIELD_DATA_LEN;
|
|
if (bin >= _downloadData->chunk_table.size()) {
|
|
qWarning() << "Out of range bin received";
|
|
} else
|
|
_downloadData->chunk_table.setBit(bin);
|
|
if (_downloadData->file.pos() != ofs) {
|
|
// Seek to correct position
|
|
if (!_downloadData->file.seek(ofs)) {
|
|
qWarning() << "Error while seeking log file offset";
|
|
return;
|
|
}
|
|
}
|
|
|
|
//-- Write chunk to file
|
|
if(_downloadData->file.write((const char*)data, count)) {
|
|
_downloadData->written += count;
|
|
_downloadData->rate_bytes += count;
|
|
_updateDataRate();
|
|
result = true;
|
|
//-- reset retries
|
|
_retries = 0;
|
|
//-- Reset timer
|
|
_timer.start(timeout_time);
|
|
//-- Do we have it all?
|
|
if(_logComplete()) {
|
|
_downloadData->entry->setStatus(tr("Downloaded"));
|
|
//-- Check for more
|
|
_receivedAllData();
|
|
} else if (_chunkComplete()) {
|
|
_downloadData->advanceChunk();
|
|
_requestLogData(_downloadData->ID,
|
|
_downloadData->current_chunk*kChunkSize,
|
|
_downloadData->chunk_table.size()*MAVLINK_MSG_LOG_DATA_FIELD_DATA_LEN);
|
|
} else if (bin < _downloadData->chunk_table.size() - 1 && _downloadData->chunk_table.at(bin+1)) {
|
|
// Likely to be grabbing fragments and got to the end of a gap
|
|
_findMissingData();
|
|
}
|
|
} else {
|
|
qWarning() << "Error while writing log file chunk";
|
|
}
|
|
} else {
|
|
qWarning() << "Received log offset greater than expected";
|
|
}
|
|
if(!result) {
|
|
_downloadData->entry->setStatus(tr("Error"));
|
|
}
|
|
}
|
|
|
|
|
|
//----------------------------------------------------------------------------------------
|
|
bool
|
|
LogDownloadController::_chunkComplete() const
|
|
{
|
|
return _downloadData->chunkEquals(true);
|
|
}
|
|
|
|
//----------------------------------------------------------------------------------------
|
|
bool
|
|
LogDownloadController::_logComplete() const
|
|
{
|
|
return _chunkComplete() && (_downloadData->current_chunk+1) == _downloadData->numChunks();
|
|
}
|
|
|
|
//----------------------------------------------------------------------------------------
|
|
void
|
|
LogDownloadController::_receivedAllData()
|
|
{
|
|
_timer.stop();
|
|
//-- Anything queued up for download?
|
|
if(_prepareLogDownload()) {
|
|
//-- Request Log
|
|
_requestLogData(_downloadData->ID, 0, _downloadData->chunk_table.size()*MAVLINK_MSG_LOG_DATA_FIELD_DATA_LEN);
|
|
_timer.start(kTimeOutMilliseconds);
|
|
} else {
|
|
_resetSelection();
|
|
_setDownloading(false);
|
|
}
|
|
}
|
|
|
|
//----------------------------------------------------------------------------------------
|
|
void
|
|
LogDownloadController::_findMissingData()
|
|
{
|
|
if (_logComplete()) {
|
|
_receivedAllData();
|
|
return;
|
|
} else if (_chunkComplete()) {
|
|
_downloadData->advanceChunk();
|
|
}
|
|
|
|
_retries++;
|
|
#if 0
|
|
// Trying the change to infinite log download. This way if retries hit 100% failure the data rate will
|
|
// slowly fall to 0 and the user can Cancel. This should work better on really crappy links.
|
|
if(_retries > 5) {
|
|
_downloadData->entry->setStatus(tr("Timed Out"));
|
|
//-- Give up
|
|
qWarning() << "Too many errors retreiving log data. Giving up.";
|
|
_receivedAllData();
|
|
return;
|
|
}
|
|
#endif
|
|
|
|
_updateDataRate();
|
|
|
|
uint16_t start = 0, end = 0;
|
|
const int size = _downloadData->chunk_table.size();
|
|
for (; start < size; start++) {
|
|
if (!_downloadData->chunk_table.testBit(start)) {
|
|
break;
|
|
}
|
|
}
|
|
|
|
for (end = start; end < size; end++) {
|
|
if (_downloadData->chunk_table.testBit(end)) {
|
|
break;
|
|
}
|
|
}
|
|
|
|
const uint32_t pos = _downloadData->current_chunk*kChunkSize + start*MAVLINK_MSG_LOG_DATA_FIELD_DATA_LEN,
|
|
len = (end - start)*MAVLINK_MSG_LOG_DATA_FIELD_DATA_LEN;
|
|
_requestLogData(_downloadData->ID, pos, len, _retries);
|
|
}
|
|
|
|
//----------------------------------------------------------------------------------------
|
|
void
|
|
LogDownloadController::_requestLogData(uint16_t id, uint32_t offset, uint32_t count, int retryCount)
|
|
{
|
|
if (_vehicle) {
|
|
WeakLinkInterfacePtr weakLink = _vehicle->vehicleLinkManager()->primaryLink();
|
|
if (!weakLink.expired()) {
|
|
SharedLinkInterfacePtr sharedLink = weakLink.lock();
|
|
|
|
//-- APM "Fix"
|
|
id += _apmOneBased;
|
|
qCDebug(LogDownloadLog) << "Request log data (id:" << id << "offset:" << offset << "size:" << count << "retryCount" << retryCount << ")";
|
|
mavlink_message_t msg;
|
|
mavlink_msg_log_request_data_pack_chan(
|
|
qgcApp()->toolbox()->mavlinkProtocol()->getSystemId(),
|
|
qgcApp()->toolbox()->mavlinkProtocol()->getComponentId(),
|
|
sharedLink->mavlinkChannel(),
|
|
&msg,
|
|
_vehicle->id(), _vehicle->defaultComponentId(),
|
|
id, offset, count);
|
|
_vehicle->sendMessageOnLinkThreadSafe(sharedLink.get(), msg);
|
|
}
|
|
}
|
|
}
|
|
|
|
//----------------------------------------------------------------------------------------
|
|
void
|
|
LogDownloadController::refresh(void)
|
|
{
|
|
_logEntriesModel.clear();
|
|
//-- Get first 50 entries
|
|
_requestLogList(0, 49);
|
|
}
|
|
|
|
//----------------------------------------------------------------------------------------
|
|
void
|
|
LogDownloadController::_requestLogList(uint32_t start, uint32_t end)
|
|
{
|
|
if(_vehicle && _uas) {
|
|
qCDebug(LogDownloadLog) << "Request log entry list (" << start << "through" << end << ")";
|
|
_setListing(true);
|
|
WeakLinkInterfacePtr weakLink = _vehicle->vehicleLinkManager()->primaryLink();
|
|
if (!weakLink.expired()) {
|
|
SharedLinkInterfacePtr sharedLink = weakLink.lock();
|
|
|
|
mavlink_message_t msg;
|
|
mavlink_msg_log_request_list_pack_chan(
|
|
qgcApp()->toolbox()->mavlinkProtocol()->getSystemId(),
|
|
qgcApp()->toolbox()->mavlinkProtocol()->getComponentId(),
|
|
sharedLink->mavlinkChannel(),
|
|
&msg,
|
|
_vehicle->id(),
|
|
_vehicle->defaultComponentId(),
|
|
start,
|
|
end);
|
|
_vehicle->sendMessageOnLinkThreadSafe(sharedLink.get(), msg);
|
|
}
|
|
//-- Wait 5 seconds before bitching about not getting anything
|
|
_timer.start(5000);
|
|
}
|
|
}
|
|
|
|
//----------------------------------------------------------------------------------------
|
|
void
|
|
LogDownloadController::download(QString path)
|
|
{
|
|
QString dir = path;
|
|
if (dir.isEmpty()) {
|
|
dir = qgcApp()->toolbox()->settingsManager()->appSettings()->logSavePath();
|
|
}
|
|
downloadToDirectory(dir);
|
|
}
|
|
|
|
void LogDownloadController::downloadToDirectory(const QString& dir)
|
|
{
|
|
//-- Stop listing just in case
|
|
_receivedAllEntries();
|
|
//-- Reset downloads, again just in case
|
|
delete _downloadData;
|
|
_downloadData = nullptr;
|
|
|
|
_downloadPath = dir;
|
|
if(!_downloadPath.isEmpty()) {
|
|
if(!_downloadPath.endsWith(QDir::separator()))
|
|
_downloadPath += QDir::separator();
|
|
//-- Iterate selected entries and shown them as waiting
|
|
int num_logs = _logEntriesModel.count();
|
|
for(int i = 0; i < num_logs; i++) {
|
|
QGCLogEntry* entry = _logEntriesModel[i];
|
|
if(entry) {
|
|
if(entry->selected()) {
|
|
entry->setStatus(tr("Waiting"));
|
|
}
|
|
}
|
|
}
|
|
//-- Start download process
|
|
_setDownloading(true);
|
|
_receivedAllData();
|
|
}
|
|
}
|
|
|
|
|
|
//----------------------------------------------------------------------------------------
|
|
QGCLogEntry*
|
|
LogDownloadController::_getNextSelected()
|
|
{
|
|
//-- Iterate entries and look for a selected file
|
|
int num_logs = _logEntriesModel.count();
|
|
for(int i = 0; i < num_logs; i++) {
|
|
QGCLogEntry* entry = _logEntriesModel[i];
|
|
if(entry) {
|
|
if(entry->selected()) {
|
|
return entry;
|
|
}
|
|
}
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
//----------------------------------------------------------------------------------------
|
|
bool
|
|
LogDownloadController::_prepareLogDownload()
|
|
{
|
|
delete _downloadData;
|
|
_downloadData = nullptr;
|
|
|
|
QGCLogEntry* entry = _getNextSelected();
|
|
if(!entry) {
|
|
return false;
|
|
}
|
|
//-- Deselect file
|
|
entry->setSelected(false);
|
|
emit selectionChanged();
|
|
bool result = false;
|
|
QString ftime;
|
|
if(entry->time().date().year() < 2010) {
|
|
ftime = tr("UnknownDate");
|
|
} else {
|
|
ftime = entry->time().toString(QStringLiteral("yyyy-M-d-hh-mm-ss"));
|
|
}
|
|
_downloadData = new LogDownloadData(entry);
|
|
_downloadData->filename = QString("log_") + QString::number(entry->id()) + "_" + ftime;
|
|
if (_vehicle->firmwareType() == MAV_AUTOPILOT_PX4) {
|
|
QString loggerParam = QStringLiteral("SYS_LOGGER");
|
|
if (_vehicle->parameterManager()->parameterExists(FactSystem::defaultComponentId, loggerParam) &&
|
|
_vehicle->parameterManager()->getParameter(FactSystem::defaultComponentId, loggerParam)->rawValue().toInt() == 0) {
|
|
_downloadData->filename += ".px4log";
|
|
} else {
|
|
_downloadData->filename += ".ulg";
|
|
}
|
|
} else {
|
|
_downloadData->filename += ".bin";
|
|
}
|
|
_downloadData->file.setFileName(_downloadPath + _downloadData->filename);
|
|
//-- Append a number to the end if the filename already exists
|
|
if (_downloadData->file.exists()){
|
|
uint num_dups = 0;
|
|
QStringList filename_spl = _downloadData->filename.split('.');
|
|
do {
|
|
num_dups +=1;
|
|
_downloadData->file.setFileName(filename_spl[0] + '_' + QString::number(num_dups) + '.' + filename_spl[1]);
|
|
} while( _downloadData->file.exists());
|
|
}
|
|
//-- Create file
|
|
if (!_downloadData->file.open(QIODevice::WriteOnly)) {
|
|
qWarning() << "Failed to create log file:" << _downloadData->filename;
|
|
} else {
|
|
//-- Preallocate file
|
|
if(!_downloadData->file.resize(entry->size())) {
|
|
qWarning() << "Failed to allocate space for log file:" << _downloadData->filename;
|
|
} else {
|
|
_downloadData->current_chunk = 0;
|
|
_downloadData->chunk_table = QBitArray(_downloadData->chunkBins(), false);
|
|
_downloadData->elapsed.start();
|
|
result = true;
|
|
}
|
|
}
|
|
if(!result) {
|
|
if (_downloadData->file.exists()) {
|
|
_downloadData->file.remove();
|
|
}
|
|
_downloadData->entry->setStatus(tr("Error"));
|
|
delete _downloadData;
|
|
_downloadData = nullptr;
|
|
}
|
|
return result;
|
|
}
|
|
|
|
//----------------------------------------------------------------------------------------
|
|
void
|
|
LogDownloadController::_setDownloading(bool active)
|
|
{
|
|
if (_downloadingLogs != active) {
|
|
_downloadingLogs = active;
|
|
_vehicle->vehicleLinkManager()->setCommunicationLostEnabled(!active);
|
|
emit downloadingLogsChanged();
|
|
}
|
|
}
|
|
|
|
//----------------------------------------------------------------------------------------
|
|
void
|
|
LogDownloadController::_setListing(bool active)
|
|
{
|
|
if (_requestingLogEntries != active) {
|
|
_requestingLogEntries = active;
|
|
_vehicle->vehicleLinkManager()->setCommunicationLostEnabled(!active);
|
|
emit requestingListChanged();
|
|
}
|
|
}
|
|
|
|
//----------------------------------------------------------------------------------------
|
|
void
|
|
LogDownloadController::eraseAll(void)
|
|
{
|
|
if(_vehicle && _uas) {
|
|
WeakLinkInterfacePtr weakLink = _vehicle->vehicleLinkManager()->primaryLink();
|
|
if (!weakLink.expired()) {
|
|
SharedLinkInterfacePtr sharedLink = weakLink.lock();
|
|
|
|
mavlink_message_t msg;
|
|
mavlink_msg_log_erase_pack_chan(
|
|
qgcApp()->toolbox()->mavlinkProtocol()->getSystemId(),
|
|
qgcApp()->toolbox()->mavlinkProtocol()->getComponentId(),
|
|
sharedLink->mavlinkChannel(),
|
|
&msg,
|
|
qgcApp()->toolbox()->multiVehicleManager()->activeVehicle()->id(), qgcApp()->toolbox()->multiVehicleManager()->activeVehicle()->defaultComponentId());
|
|
_vehicle->sendMessageOnLinkThreadSafe(sharedLink.get(), msg);
|
|
}
|
|
refresh();
|
|
}
|
|
}
|
|
|
|
//----------------------------------------------------------------------------------------
|
|
void
|
|
LogDownloadController::cancel(void)
|
|
{
|
|
if(_uas){
|
|
_receivedAllEntries();
|
|
}
|
|
if(_downloadData) {
|
|
_downloadData->entry->setStatus(tr("Canceled"));
|
|
if (_downloadData->file.exists()) {
|
|
_downloadData->file.remove();
|
|
}
|
|
delete _downloadData;
|
|
_downloadData = 0;
|
|
}
|
|
_resetSelection(true);
|
|
_setDownloading(false);
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
QGCLogModel::QGCLogModel(QObject* parent)
|
|
: QAbstractListModel(parent)
|
|
{
|
|
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
QGCLogEntry*
|
|
QGCLogModel::get(int index)
|
|
{
|
|
qDebug() << "QGCLogModel::get" << index;
|
|
if (index < 0 || index >= _logEntries.count()) {
|
|
qDebug() << "QGCLogModel::get failed";
|
|
return nullptr;
|
|
}
|
|
return _logEntries[index];
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
int
|
|
QGCLogModel::count() const
|
|
{
|
|
return _logEntries.count();
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
void
|
|
QGCLogModel::append(QGCLogEntry* object)
|
|
{
|
|
beginInsertRows(QModelIndex(), rowCount(), rowCount());
|
|
QQmlEngine::setObjectOwnership(object, QQmlEngine::CppOwnership);
|
|
_logEntries.append(object);
|
|
endInsertRows();
|
|
emit countChanged();
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
void
|
|
QGCLogModel::clear(void)
|
|
{
|
|
if(!_logEntries.isEmpty()) {
|
|
beginRemoveRows(QModelIndex(), 0, _logEntries.count());
|
|
while (_logEntries.count()) {
|
|
QGCLogEntry* entry = _logEntries.last();
|
|
if(entry) entry->deleteLater();
|
|
_logEntries.removeLast();
|
|
}
|
|
endRemoveRows();
|
|
emit countChanged();
|
|
}
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
QGCLogEntry*
|
|
QGCLogModel::operator[](int index)
|
|
{
|
|
return get(index);
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
int
|
|
QGCLogModel::rowCount(const QModelIndex& /*parent*/) const
|
|
{
|
|
return _logEntries.count();
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
QVariant
|
|
QGCLogModel::data(const QModelIndex & index, int role) const {
|
|
if (index.row() < 0 || index.row() >= _logEntries.count())
|
|
return QVariant();
|
|
if (role == ObjectRole)
|
|
return QVariant::fromValue(_logEntries[index.row()]);
|
|
return QVariant();
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
QHash<int, QByteArray>
|
|
QGCLogModel::roleNames() const {
|
|
QHash<int, QByteArray> roles;
|
|
roles[ObjectRole] = "logEntry";
|
|
return roles;
|
|
}
|
|
|
|
int QGCLogModel::columnCount(const QModelIndex &parent) const
|
|
{
|
|
Q_UNUSED(parent);
|
|
return 4;
|
|
}
|