|
|
@ -36,10 +36,51 @@ |
|
|
|
#include <QUrl> |
|
|
|
#include <QUrl> |
|
|
|
|
|
|
|
|
|
|
|
#define kTimeOutMilliseconds 500 |
|
|
|
#define kTimeOutMilliseconds 500 |
|
|
|
|
|
|
|
#define kGUIRateMilliseconds 17 |
|
|
|
|
|
|
|
#define kTableBins 128 |
|
|
|
|
|
|
|
#define kChunkSize (kTableBins * MAVLINK_MSG_LOG_DATA_FIELD_DATA_LEN) |
|
|
|
|
|
|
|
|
|
|
|
QGC_LOGGING_CATEGORY(LogDownloadLog, "LogDownloadLog") |
|
|
|
QGC_LOGGING_CATEGORY(LogDownloadLog, "LogDownloadLog") |
|
|
|
|
|
|
|
|
|
|
|
static QLocale kLocale; |
|
|
|
static QLocale kLocale; |
|
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
|
|
|
|
struct LogDownloadData { |
|
|
|
|
|
|
|
LogDownloadData(QGCLogEntry* entry); |
|
|
|
|
|
|
|
QBitArray chunk_table; |
|
|
|
|
|
|
|
uint32_t current_chunk; |
|
|
|
|
|
|
|
QFile file; |
|
|
|
|
|
|
|
QString filename; |
|
|
|
|
|
|
|
uint ID; |
|
|
|
|
|
|
|
QGCLogEntry* entry; |
|
|
|
|
|
|
|
uint written; |
|
|
|
|
|
|
|
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_) |
|
|
|
LogDownloadData::LogDownloadData(QGCLogEntry* entry_) |
|
|
@ -274,36 +315,62 @@ LogDownloadController::_logData(UASInterface* uas, uint32_t ofs, uint16_t id, ui |
|
|
|
qWarning() << "Received log data for wrong log"; |
|
|
|
qWarning() << "Received log data for wrong log"; |
|
|
|
return; |
|
|
|
return; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if ((ofs % MAVLINK_MSG_LOG_DATA_FIELD_DATA_LEN) != 0) { |
|
|
|
|
|
|
|
qWarning() << "Ignored misaligned incoming packet @" << ofs; |
|
|
|
|
|
|
|
return; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
bool result = false; |
|
|
|
bool result = false; |
|
|
|
//-- Find offset table entry
|
|
|
|
uint32_t timeout_time = kTimeOutMilliseconds; |
|
|
|
uint o_index = ofs / MAVLINK_MSG_LOG_DATA_FIELD_DATA_LEN; |
|
|
|
if(ofs <= _downloadData->entry->size()) { |
|
|
|
if(o_index <= (uint)_downloadData->offsets.count()) { |
|
|
|
const uint32_t chunk = ofs / kChunkSize; |
|
|
|
_downloadData->offsets[o_index] = count; |
|
|
|
if (chunk != _downloadData->current_chunk) { |
|
|
|
|
|
|
|
qWarning() << "Ignored packet for out of order chunk" << 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
|
|
|
|
//-- Write chunk to file
|
|
|
|
if(_downloadData->file.seek(ofs)) { |
|
|
|
|
|
|
|
if(_downloadData->file.write((const char*)data, count)) { |
|
|
|
if(_downloadData->file.write((const char*)data, count)) { |
|
|
|
_downloadData->written += count; |
|
|
|
_downloadData->written += count; |
|
|
|
|
|
|
|
if (_downloadData->elapsed.elapsed() >= kGUIRateMilliseconds) { |
|
|
|
//-- Update status
|
|
|
|
//-- Update status
|
|
|
|
QString comma_value = kLocale.toString(_downloadData->written); |
|
|
|
QString comma_value = kLocale.toString(_downloadData->written); |
|
|
|
_downloadData->entry->setStatus(comma_value); |
|
|
|
_downloadData->entry->setStatus(comma_value); |
|
|
|
|
|
|
|
_downloadData->elapsed.start(); |
|
|
|
|
|
|
|
} |
|
|
|
result = true; |
|
|
|
result = true; |
|
|
|
//-- reset retries
|
|
|
|
//-- reset retries
|
|
|
|
_retries = 0; |
|
|
|
_retries = 0; |
|
|
|
//-- Reset timer
|
|
|
|
//-- Reset timer
|
|
|
|
_timer.start(kTimeOutMilliseconds); |
|
|
|
_timer.start(timeout_time); |
|
|
|
//-- Do we have it all?
|
|
|
|
//-- Do we have it all?
|
|
|
|
if(_logComplete()) { |
|
|
|
if(_logComplete()) { |
|
|
|
_downloadData->entry->setStatus(QString("Downloaded")); |
|
|
|
_downloadData->entry->setStatus(QString("Downloaded")); |
|
|
|
//-- Check for more
|
|
|
|
//-- Check for more
|
|
|
|
_receivedAllData(); |
|
|
|
_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 { |
|
|
|
} else { |
|
|
|
qWarning() << "Error while writing log file chunk"; |
|
|
|
qWarning() << "Error while writing log file chunk"; |
|
|
|
} |
|
|
|
} |
|
|
|
} else { |
|
|
|
} else { |
|
|
|
qWarning() << "Error while seeking log file offset"; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
} else { |
|
|
|
|
|
|
|
qWarning() << "Received log offset greater than expected"; |
|
|
|
qWarning() << "Received log offset greater than expected"; |
|
|
|
} |
|
|
|
} |
|
|
|
if(!result) { |
|
|
|
if(!result) { |
|
|
@ -311,18 +378,19 @@ LogDownloadController::_logData(UASInterface* uas, uint32_t ofs, uint16_t id, ui |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
//----------------------------------------------------------------------------------------
|
|
|
|
//----------------------------------------------------------------------------------------
|
|
|
|
bool |
|
|
|
bool |
|
|
|
LogDownloadController::_logComplete() |
|
|
|
LogDownloadController::_chunkComplete() const |
|
|
|
{ |
|
|
|
{ |
|
|
|
//-- Iterate entries and look for a gap
|
|
|
|
return _downloadData->chunkEquals(true); |
|
|
|
int num_ofs = _downloadData->offsets.count(); |
|
|
|
|
|
|
|
for(int i = 0; i < num_ofs; i++) { |
|
|
|
|
|
|
|
if(_downloadData->offsets[i] == 0) { |
|
|
|
|
|
|
|
return false; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
} |
|
|
|
} |
|
|
|
return true; |
|
|
|
|
|
|
|
|
|
|
|
//----------------------------------------------------------------------------------------
|
|
|
|
|
|
|
|
bool |
|
|
|
|
|
|
|
LogDownloadController::_logComplete() const |
|
|
|
|
|
|
|
{ |
|
|
|
|
|
|
|
return _chunkComplete() && (_downloadData->current_chunk+1) == _downloadData->numChunks(); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
//----------------------------------------------------------------------------------------
|
|
|
|
//----------------------------------------------------------------------------------------
|
|
|
@ -333,7 +401,7 @@ LogDownloadController::_receivedAllData() |
|
|
|
//-- Anything queued up for download?
|
|
|
|
//-- Anything queued up for download?
|
|
|
|
if(_prepareLogDownload()) { |
|
|
|
if(_prepareLogDownload()) { |
|
|
|
//-- Request Log
|
|
|
|
//-- Request Log
|
|
|
|
_requestLogData(_downloadData->ID, 0, _downloadData->entry->size()); |
|
|
|
_requestLogData(_downloadData->ID, 0, _downloadData->chunk_table.size()*MAVLINK_MSG_LOG_DATA_FIELD_DATA_LEN); |
|
|
|
} else { |
|
|
|
} else { |
|
|
|
_resetSelection(); |
|
|
|
_resetSelection(); |
|
|
|
_setDownloading(false); |
|
|
|
_setDownloading(false); |
|
|
@ -344,25 +412,13 @@ LogDownloadController::_receivedAllData() |
|
|
|
void |
|
|
|
void |
|
|
|
LogDownloadController::_findMissingData() |
|
|
|
LogDownloadController::_findMissingData() |
|
|
|
{ |
|
|
|
{ |
|
|
|
int start = -1; |
|
|
|
if (_logComplete()) { |
|
|
|
int end = -1; |
|
|
|
_receivedAllData(); |
|
|
|
int num_ofs = _downloadData->offsets.count(); |
|
|
|
return; |
|
|
|
//-- Iterate offsets and look for a gap
|
|
|
|
} else if (_chunkComplete()) { |
|
|
|
for(int i = 0; i < num_ofs; i++) { |
|
|
|
_downloadData->advanceChunk(); |
|
|
|
if(_downloadData->offsets[i] == 0) { |
|
|
|
|
|
|
|
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) { |
|
|
|
if(_retries++ > 2) { |
|
|
|
_downloadData->entry->setStatus(QString("Timed Out")); |
|
|
|
_downloadData->entry->setStatus(QString("Timed Out")); |
|
|
|
//-- Give up
|
|
|
|
//-- Give up
|
|
|
@ -370,18 +426,24 @@ LogDownloadController::_findMissingData() |
|
|
|
_receivedAllData(); |
|
|
|
_receivedAllData(); |
|
|
|
return; |
|
|
|
return; |
|
|
|
} |
|
|
|
} |
|
|
|
//-- Is it a sequence or just one entry?
|
|
|
|
|
|
|
|
if(end < 0) { |
|
|
|
uint16_t start = 0, end = 0; |
|
|
|
end = start; |
|
|
|
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; |
|
|
|
} |
|
|
|
} |
|
|
|
//-- Request these log chunks again
|
|
|
|
|
|
|
|
_requestLogData( |
|
|
|
|
|
|
|
_downloadData->ID, |
|
|
|
|
|
|
|
(uint32_t)(start * MAVLINK_MSG_LOG_DATA_FIELD_DATA_LEN), |
|
|
|
|
|
|
|
(uint32_t)((end - start + 1) * MAVLINK_MSG_LOG_DATA_FIELD_DATA_LEN)); |
|
|
|
|
|
|
|
} else { |
|
|
|
|
|
|
|
_receivedAllData(); |
|
|
|
|
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
//----------------------------------------------------------------------------------------
|
|
|
|
//----------------------------------------------------------------------------------------
|
|
|
@ -534,11 +596,9 @@ LogDownloadController::_prepareLogDownload() |
|
|
|
if(!_downloadData->file.resize(entry->size())) { |
|
|
|
if(!_downloadData->file.resize(entry->size())) { |
|
|
|
qWarning() << "Failed to allocate space for log file:" << _downloadData->filename; |
|
|
|
qWarning() << "Failed to allocate space for log file:" << _downloadData->filename; |
|
|
|
} else { |
|
|
|
} else { |
|
|
|
//-- Prepare Offset Table
|
|
|
|
_downloadData->current_chunk = 0; |
|
|
|
uint o_count = (uint)ceil(entry->size() / (double)MAVLINK_MSG_LOG_DATA_FIELD_DATA_LEN); |
|
|
|
_downloadData->chunk_table = QBitArray(_downloadData->chunkBins(), false); |
|
|
|
for(uint i = 0; i < o_count; i++) { |
|
|
|
_downloadData->elapsed.start(); |
|
|
|
_downloadData->offsets.append(0); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
result = true; |
|
|
|
result = true; |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|