/* 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 . */ #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 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_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; } } }