328 lines
9.5 KiB
C++
328 lines
9.5 KiB
C++
/*
|
|
PIP - Platform Independent Primitives
|
|
Class for write binary data to logfile, and read or playback this data
|
|
Copyright (C) 2014 Andrey Bychkov work.a.b@yandex.ru
|
|
|
|
This program is free software: you can redistribute it and/or modify
|
|
it under the terms of the GNU General Public License as published by
|
|
the Free Software Foundation, either version 3 of the License, or
|
|
(at your option) any later version.
|
|
|
|
This program is distributed in the hope that it will be useful,
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
GNU General Public License for more details.
|
|
|
|
You should have received a copy of the GNU General Public License
|
|
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
#include "pibinarylog.h"
|
|
|
|
|
|
/*! \class PIBinaryLog
|
|
* \brief Class for read and write binary data to logfile, and playback this data in realtime
|
|
*
|
|
* \section PIBinaryLog_sec0 Synopsis
|
|
* Binary Log is a file with simle header, where you can read and write some binary data.
|
|
* Any written data include special header with ID, size and timestamp.
|
|
* This header provides separation different messages from the one file by choosing different IDs.
|
|
* With \a filterID or special functions, like \a readBinLog() you can choose IDs what you want to read.
|
|
* With function \a writeBinLog() or \a setDefaultID() you can choose ID that mark you data.
|
|
* By default ID = 1, and \a filterID is empty, that mean you read any ID without filtering.
|
|
* ThreadedRead provide you playback data, with delay that you write data.
|
|
* You can choose realtime playbak or variable speed play back by set \a PlayMode.
|
|
*
|
|
* \section PIBinaryLog_sec1 Basic usage
|
|
* This class provide all functions of \a PIIODevice, such \a open(), \a close(),
|
|
* \a read() ,\a write(), and threaded read/write.
|
|
* function \a setLogDir() need to set directory for BinLog files
|
|
* function \a createNewFile() need to create new binlog file
|
|
* function \a restart() need start from the begining of binlog file
|
|
*
|
|
*/
|
|
|
|
REGISTER_DEVICE(PIBinaryLog);
|
|
|
|
|
|
PIBinaryLog::PIBinaryLog() {
|
|
binlog_sig[0] = 'B';
|
|
binlog_sig[1] = 'I';
|
|
binlog_sig[2] = 'N';
|
|
binlog_sig[3] = 'L';
|
|
binlog_sig[4] = 'O';
|
|
binlog_sig[5] = 'G';
|
|
setThreadedReadBufferSize(65536);
|
|
is_started = false;
|
|
setPlaySpeed(1.f);
|
|
setDefaultID(1);
|
|
setPlayMode(PlayVariableSpeed);
|
|
setLogDir(PIString());
|
|
setFilePrefix(PIString());
|
|
setRapidStart(false);
|
|
}
|
|
|
|
|
|
bool PIBinaryLog::openDevice() {
|
|
lastrecord.timestamp = PISystemTime();
|
|
lastrecord.id = 0;
|
|
is_started = false;
|
|
is_thread_ok = true;
|
|
if (mode_ == ReadWrite) {
|
|
piCoutObj << "Error: ReadWrite mode not supported, use WriteOnly or ReadOnly";
|
|
return false;
|
|
}
|
|
if (!file.open(path(), mode_))
|
|
return false;
|
|
setName(path());
|
|
if (mode_ == WriteOnly) {
|
|
file.resize(0);
|
|
if (!writeFileHeader()) {
|
|
piCoutObj << "Error: Can't write binlog file header";
|
|
return false;
|
|
}
|
|
is_started = true;
|
|
}
|
|
if (mode_ == ReadOnly) {
|
|
if (file.isEmpty()) {
|
|
piCoutObj << "Error: File is null";
|
|
fileError();
|
|
return false;
|
|
}
|
|
if (!checkFileHeader()) {
|
|
fileError();
|
|
return false;
|
|
}
|
|
if (isEmpty()) piCoutObj << "Error: Empty BinLog file";
|
|
// startlogtime = PISystemTime::current();
|
|
play_time = 0;
|
|
// nextrecord = readsRecord();
|
|
if (!rapidStart()) is_started = true;
|
|
}
|
|
startlogtime = PISystemTime::current();
|
|
return true;
|
|
}
|
|
|
|
|
|
bool PIBinaryLog::closeDevice() {
|
|
if (canWrite() && isEmpty()) {
|
|
file.remove();
|
|
return true;
|
|
}
|
|
return file.close();
|
|
}
|
|
|
|
|
|
bool PIBinaryLog::threadedRead(uchar *readed, int size) {
|
|
is_thread_ok = false;
|
|
PISystemTime pt;
|
|
double delay;
|
|
switch (playMode()) {
|
|
case PlayRealTime:
|
|
pt = PISystemTime::current() - startlogtime;
|
|
// if (real_speedX > 0)
|
|
// for (int i=0; i<real_speedX-1; i++) pt += pt;
|
|
// piCout << pt << lastrecord.timestamp << lastrecord.timestamp - pt;
|
|
if (is_started) {
|
|
if (lastrecord.timestamp > pt)
|
|
(lastrecord.timestamp - pt).sleep();
|
|
} else {
|
|
startlogtime = PISystemTime::current() - lastrecord.timestamp;
|
|
is_started = true;
|
|
}
|
|
// int delay = piRoundd(lastread_timestamp.toMilliseconds() - (PISystemTime::current() - startlogtime).toMilliseconds());
|
|
break;
|
|
case PlayVariableSpeed:
|
|
delay = lastrecord.timestamp.toMilliseconds() - play_time;
|
|
delay /= playSpeed();
|
|
if (is_started) {
|
|
if (delay > 0)
|
|
PISystemTime::fromMilliseconds(delay).sleep();
|
|
} else is_started = true;
|
|
play_time = lastrecord.timestamp.toMilliseconds();
|
|
break;
|
|
default:
|
|
return false;
|
|
}
|
|
bool res = PIIODevice::threadedRead(readed, size);
|
|
is_thread_ok = true;
|
|
return res;
|
|
}
|
|
|
|
|
|
PIString PIBinaryLog::createNewFile() {
|
|
if (!file.close()) return PIString();
|
|
if (open(logDir() + "/" + filePrefix() + PIDateTime::current().toString("yyyy_MM_dd__hh_mm_ss.binlog"), PIIODevice::WriteOnly))
|
|
return file.path();
|
|
piCoutObj << "Can't create new file, maybe LogDir is invalid.";
|
|
return PIString();
|
|
}
|
|
|
|
|
|
int PIBinaryLog::writeBinLog(int id, const void *data, int size) {
|
|
if (size <= 0 || !canWrite()) return -1;
|
|
PIByteArray logdata;
|
|
logdata << id << size << (PISystemTime::current() - startlogtime) << PIByteArray::RawData(data, size);
|
|
int res = file.write(logdata.data(), logdata.size());
|
|
file.flush();
|
|
if (res > 0) return size;
|
|
else return res;
|
|
}
|
|
|
|
|
|
PIByteArray PIBinaryLog::readBinLog(int id) {
|
|
if (!canRead()) return PIByteArray();
|
|
BinLogRecord br = readRecord();
|
|
if (br.id == -1) {
|
|
piCoutObj << "End of BinLog file";
|
|
fileEnd();
|
|
return PIByteArray();
|
|
}
|
|
if (id == 0 && br.id > 0) return br.data;
|
|
while (br.id != id && !isEnd()) br = readRecord();
|
|
if (br.id == -1) {
|
|
piCoutObj << "End of BinLog file";
|
|
fileEnd();
|
|
return PIByteArray();
|
|
}
|
|
if (br.id == id) return br.data;
|
|
piCoutObj << "Can't find record with id =" << id;
|
|
return PIByteArray();
|
|
}
|
|
|
|
|
|
int PIBinaryLog::readBinLog(int id, void *read_to, int max_size) {
|
|
if (max_size <= 0 || read_to == 0) return -1;
|
|
PIByteArray ba = readBinLog(id);
|
|
if (ba.isEmpty()) return -1;
|
|
int sz = piMini(max_size, ba.size());
|
|
memcpy(read_to, ba.data(), sz);
|
|
return sz;
|
|
}
|
|
|
|
|
|
|
|
int PIBinaryLog::read(void *read_to, int max_size) {
|
|
// piCoutObj << "read";
|
|
if (lastrecord.id == -1 || isEnd()) return 0;
|
|
if(!is_thread_ok && lastrecord.id > 0) return lastrecord.data.size();
|
|
if (!canRead()) return -1;
|
|
if (max_size <= 0 || read_to == 0) return -1;
|
|
BinLogRecord br;
|
|
br.id = 0;
|
|
if (filterID.isEmpty()) br = readRecord();
|
|
else {
|
|
while (!filterID.contains(br.id) && !isEnd()) br = readRecord();
|
|
}
|
|
if (br.id == -1) {
|
|
fileEnd();
|
|
piCoutObj << "End of BinLog file";
|
|
//stopThreadedRead();
|
|
return 0;
|
|
}
|
|
if (br.id <= 0) {
|
|
piCoutObj << "Read record error";
|
|
return -1;
|
|
}
|
|
int sz = piMini(max_size, br.data.size());
|
|
memcpy(read_to, br.data.data(), sz);
|
|
return sz;
|
|
}
|
|
|
|
|
|
void PIBinaryLog::restart() {
|
|
bool th = isRunning();
|
|
if (th) stopThreadedRead();
|
|
if (!canRead()) return;
|
|
lastrecord.timestamp = PISystemTime();
|
|
lastrecord.id = 0;
|
|
is_thread_ok = true;
|
|
if (rapidStart()) is_started = false;
|
|
else is_started = true;
|
|
play_time = 0;
|
|
file.seekToBegin();
|
|
checkFileHeader();
|
|
startlogtime = PISystemTime::current();
|
|
if (th) startThreadedRead();
|
|
}
|
|
|
|
|
|
bool PIBinaryLog::writeFileHeader() {
|
|
if (file.write(&binlog_sig, PIBINARYLOG_SIGNATURE_SIZE) <= 0) return false;
|
|
uchar version = PIBINARYLOG_VERSION;
|
|
if (file.write(&version, 1) <= 0) return false;
|
|
file.flush();
|
|
return true;
|
|
}
|
|
|
|
|
|
bool PIBinaryLog::checkFileHeader() {
|
|
uchar read_sig[PIBINARYLOG_SIGNATURE_SIZE];
|
|
for (int i=0; i<PIBINARYLOG_SIGNATURE_SIZE; i++) read_sig[i] = 0;
|
|
if (file.read(read_sig, PIBINARYLOG_SIGNATURE_SIZE) < 0) return false;
|
|
bool correct = true;
|
|
for (int i=0; i<PIBINARYLOG_SIGNATURE_SIZE; i++)
|
|
if (read_sig[i] != binlog_sig[i]) correct = false;
|
|
if (!correct) {
|
|
piCoutObj << "BinLogFile signature is corrupted or invalid file";
|
|
return false;
|
|
}
|
|
uchar read_version = 0;
|
|
if (file.read(&read_version, 1) < 0) return false;
|
|
if (read_version == PIBINARYLOG_VERSION) return true;
|
|
if (read_version == 0)
|
|
piCoutObj << "BinLogFile has invalid version";
|
|
if (read_version < PIBINARYLOG_VERSION)
|
|
piCoutObj << "BinLogFile has too old verion";
|
|
if (read_version > PIBINARYLOG_VERSION)
|
|
piCoutObj << "BinLogFile has too newest version";
|
|
return false;
|
|
}
|
|
|
|
|
|
PIBinaryLog::BinLogRecord PIBinaryLog::readRecord() {
|
|
// piCoutObj << "readRecord";
|
|
PIByteArray ba;
|
|
BinLogRecord br;
|
|
lastrecord.id = 0;
|
|
lastrecord.data.clear();
|
|
lastrecord.timestamp = PISystemTime();
|
|
ba.resize(sizeof(BinLogRecord) - sizeof(PIByteArray));
|
|
if(file.read(ba.data(), ba.size_s()) > 0) {
|
|
ba >> br.id >> br.size >> br.timestamp;
|
|
// piCoutObj << "read" << br.id << br.size << br.timestamp;
|
|
} else {
|
|
br.id = -1;
|
|
return br;
|
|
}
|
|
if (br.id > 0 && br.size > 0) {
|
|
ba.resize(br.size);
|
|
if(file.read(ba.data(), ba.size_s()) > 0) br.data = ba;
|
|
else br.id = 0;
|
|
} else br.id = 0;
|
|
lastrecord = br;
|
|
if (br.id == 0) fileError();
|
|
return br;
|
|
}
|
|
|
|
|
|
PIString PIBinaryLog::constructFullPath() const {
|
|
PIString ret(fullPathPrefix() + "://");
|
|
ret << logDir() << ":" << filePrefix() << ":" << defaultID();
|
|
return ret;
|
|
}
|
|
|
|
|
|
void PIBinaryLog::configureFromFullPath(const PIString & full_path) {
|
|
PIStringList pl = full_path.split(":");
|
|
for (int i = 0; i < pl.size_s(); ++i) {
|
|
PIString p(pl[i]);
|
|
switch (i) {
|
|
case 0: setLogDir(p); break;
|
|
case 1: setFilePrefix(p); break;
|
|
case 2: setDefaultID(p.toInt()); break;
|
|
}
|
|
}
|
|
}
|
|
|