/*! \file pibinarylog.h * \brief Binary log */ /* PIP - Platform Independent Primitives Class for write binary data to logfile, and read or playback this data 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 Lesser 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 Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this program. If not, see . */ #ifndef PIBINARYLOG_H #define PIBINARYLOG_H #include "pifile.h" class PIP_EXPORT PIBinaryLog: public PIIODevice { PIIODEVICE(PIBinaryLog) public: explicit PIBinaryLog(); ~PIBinaryLog() {closeDevice();} //! \brief Play modes for \a PIBinaryLog enum PlayMode { PlayRealTime /*! Play in system realtime, default mode */ , PlayVariableSpeed /*! Play in software realtime with speed, set by \a setSpeed */ , PlayStaticDelay /*! Play with custom static delay, ignoring timestamp */ }; //! \brief Different split modes for writing \a PIBinaryLog, which can separate files by size, by time or by records count enum SplitMode { SplitNone /*! Without separate, default mode */ , SplitTime /*! Separate files by record time */ , SplitSize /*! Separate files by size */ , SplitCount /*! Separate files by records count */ }; //! \brief Struct contains information about all records with same ID struct PIP_EXPORT BinLogRecordInfo { BinLogRecordInfo() { id = count = 0; minimum_size = maximum_size = 0; } int id; int count; int minimum_size; int maximum_size; PISystemTime start_time; PISystemTime end_time; }; //! \brief Struct contains full information about Binary Log file and about all Records using map of \a BinLogRecordInfo struct PIP_EXPORT BinLogInfo { PIString path; int records_count; llong log_size; PISystemTime start_time; PISystemTime end_time; PIMap records; PIByteArray user_header; }; //! \brief Struct contains position, ID and timestamp of record in file struct PIP_EXPORT BinLogIndex { int id; llong pos; PISystemTime timestamp; }; //! Current \a PlayMode PlayMode playMode() const {return play_mode;} //! Current \a SplitMode SplitMode splitMode() const {return split_mode;} //! Current directory where billogs wiil be saved PIString logDir() const {return property("logDir").toString();} //! Returns current file prefix PIString filePrefix() const {return property("filePrefix").toString();} //! Default ID, used in \a write function int defaultID() const {return default_id;} //! Returns current play speed double playSpeed() const {return play_speed > 0 ? 1. / play_speed : 0.;} //! Returns current play delay PISystemTime playDelay() const {return play_delay;} //! Returns current binlog file split time PISystemTime splitTime() const {return split_time;} //! Returns current binlog file split size llong splitFileSize() const {return split_size;} //! Returns current binlog file split records count int splitRecordCount() const {return split_count;} //! Returns if rapid start enabled bool rapidStart() const {return rapid_start;} //! Create binlog file with Filename = path void createNewFile(const PIString &path); //! Set \a PlayMode void setPlayMode(PlayMode mode) {setProperty("playMode", (int)mode);} //! Set \a SplitMode void setSplitMode(SplitMode mode) {setProperty("splitMode", (int)mode);} //! Set path to directory where binlogs will be saved void setLogDir(const PIString & path) {setProperty("logDir", path);} //! Set file prefix, used to void setFilePrefix(const PIString & prefix) {setProperty("filePrefix", prefix);} //! Set defaultID, used in \a write function void setDefaultID(int id) {setProperty("defaultID", id);} //! If enabled BinLog \a ThreadedRead starts without delay for first record, i.e. first record will be readed immediately void setRapidStart(bool enabled) {setProperty("rapidStart", enabled);} //! Set play speed to "speed", default value is 1.0x //! Also this function set \a playMode to \a PlayVariableSpeed void setPlaySpeed(double speed) {setPlayMode(PlayVariableSpeed); setProperty("playSpeed", speed);} //! Setting static delay between records, default value is 1 sec //! Also this function set \a playMode to \a PlayStaticDelay void setPlayDelay(const PISystemTime & delay) {setPlayMode(PlayStaticDelay); setProperty("playDelay", delay);} //! Set \a playMode to \a PlayRealTime void setPlayRealTime() {setPlayMode(PlayRealTime);} //! Set binlog file split time //! Also this function set \a splitMode to \a SplitTime void setSplitTime(const PISystemTime & time) {setSplitMode(SplitTime); setProperty("splitTime", time);} //! Set binlog file split size //! Also this function set \a splitMode to \a SplitSize void setSplitFileSize(llong size) {setSplitMode(SplitSize); setProperty("splitFileSize", size);} //! Set binlog file split records count //! Also this function set \a splitMode to \a SplitCount void setSplitRecordCount(int count) {setSplitMode(SplitCount); setProperty("splitRecordCount", count);} //! Set pause while playing via \a threadedRead or writing via write void setPause(bool pause); //! Write one record to BinLog file, with ID = id, id must be greather than 0 int writeBinLog(int id, PIByteArray data) {return writeBinLog(id, data.data(), data.size_s());} //! Write one record to BinLog file, with ID = id, id must be greather than 0 int writeBinLog(int id, const void * data, int size); //! Write one RAW record to BinLog file, with ID = id, Timestamp = time int writeBinLog_raw(int id, const PISystemTime &time, const PIByteArray &data) {return writeBinLog_raw(id, time, data.data(), data.size_s());} int writeBinLog_raw(int id, const PISystemTime &time, const void * data, int size); //! Returns count of writed records int writeCount() const {return write_count;} //! Read one record from BinLog file, with ID = id, if id = 0 than any id will be readed PIByteArray readBinLog(int id = 0, PISystemTime * time = 0); //! Read one record from BinLog file, with ID = id, if id = 0 than any id will be readed int readBinLog(int id, void * read_to, int max_size, PISystemTime * time = 0); //! Returns binary log file size llong logSize() const {return log_size;} //! Return position in current binlog file llong logPos() const {return file.pos();} //! Return true, if position at the end of BinLog file bool isEnd() const {if (isClosed()) return true; return file.isEnd();} //! Returns if BinLog file is empty bool isEmpty() const; //! Returns BinLog pause status bool isPause() const {return is_pause;} //! Returns id of last readed record int lastReadedID() const {return lastrecord.id;} //! Returns timestamp of last readed record PISystemTime lastReadedTimestamp() const {return lastrecord.timestamp;} //!Set custom file header, you can get it back when read this binlog void setHeader(const PIByteArray & header); //!Get custom file header PIByteArray getHeader(); #ifdef DOXYGEN //! Read one message from binlog file, with ID contains in "filterID" or any ID, if "filterID" is empty int read(void *read_to, int max_size); //! Write one record to BinLog file, with ID = "defaultID" int write(const void * data, int size); #endif //! Array of ID, that BinLog can read from binlog file, when use \a read function, or in \a ThreadedRead PIVector filterID; //! Go to begin of BinLog file void restart(); //! Get binlog info \a BinLogInfo BinLogInfo logInfo() const {if (is_indexed) return binfo; return getLogInfo(path());} //! Get binlog index \a BinLogIndex, need \a createIndex before getting index const PIVector & logIndex() const {return index;} //! Create index of current binlog file bool createIndex(); //! Return if current binlog file is indexed bool isIndexed() {return is_indexed;} //! Go to record #index void seekTo(int rindex); //! Go to nearest record bool seek(const PISystemTime & time); //! Set position in file to reading/playing bool seek(llong filepos); //! Get current record index (position record in file) int pos() const {if (is_indexed) return current_index; return -1;} //! \handlers //! \{ //! \fn PIString createNewFile() //! \brief Create new binlog file in \a logDir, if successful returns filename, else returns empty string. //! Filename is like \a filePrefix + "yyyy_MM_dd__hh_mm_ss.binlog" //! \} //! \events //! \{ //! \fn void fileEnd() //! \brief Raise on file end while reading //! \fn void fileError() //! \brief Raise on file creation error //! \fn void newFile(const PIString & filename) //! \brief Raise on new file created //! \} EVENT_HANDLER(PIString, createNewFile); EVENT(fileEnd) EVENT(fileError) EVENT1(newFile, const PIString &, filename) EVENT1(posChanged, int, pos) //! Get binlog info and statistic static BinLogInfo getLogInfo(const PIString & path); static bool cutBinLog(const BinLogInfo & src, const PIString & dst, int from, int to); protected: PIString fullPathPrefix() const {return PIStringAscii("binlog");} PIString constructFullPathDevice() const; void configureFromFullPathDevice(const PIString & full_path); PIPropertyStorage constructVariantDevice() const; void configureFromVariantDevice(const PIPropertyStorage & d); int readDevice(void *read_to, int max_size); int writeDevice(const void * data, int size) {return writeBinLog(default_id, data, size);} bool openDevice(); bool closeDevice(); void propertyChanged(const PIString &); bool threadedRead(uchar *readed, int size); DeviceInfoFlags deviceInfoFlags() const {return PIIODevice::Reliable;} private: struct PIP_EXPORT BinLogRecord { int id; int size; PISystemTime timestamp; PIByteArray data; }; bool writeFileHeader(); bool checkFileHeader(); BinLogRecord readRecord(); static void parseLog(PIFile *f, BinLogInfo *info, PIVector * index); void moveIndex(int i); PIString getLogfilePath() const; PIVector index; PIMap index_pos; BinLogInfo binfo; PlayMode play_mode; SplitMode split_mode; PIFile file; BinLogRecord lastrecord; PISystemTime startlogtime, play_delay, split_time, pause_time; mutable PIMutex logmutex, pausemutex; double play_time, play_speed; llong split_size, log_size; int write_count, split_count, default_id, current_index; bool is_started, is_thread_ok, is_indexed, rapid_start, is_pause; PIByteArray user_header; }; //! \relatesalso PICout \relatesalso PIBinaryLog::BinLogInfo \brief Output operator to PICout inline PICout operator <<(PICout s, const PIBinaryLog::BinLogInfo & bi) { s.space(); s.setControl(0, true); s << "[PIBinaryLog] " << bi.path << "\n"; if (bi.log_size < 0) { s << "invalid file path"; s.restoreControl(); return s; } if (bi.log_size == 0) { s << "Invalid empty file"; s.restoreControl(); return s; } if (bi.records_count < 0 && bi.records_count > -4) { s << "Invalid file or corrupted signature"; s.restoreControl(); return s; } if (bi.records_count < -3) { s << "Invalid binlog version"; s.restoreControl(); return s; } s << "read records " << bi.records_count << " in " << bi.records.size() << " types, log size " << bi.log_size; s << "\nlog start " << bi.start_time << " , log end " << bi.end_time; PIVector keys = bi.records.keys(); piForeachC(int i, keys) { const PIBinaryLog::BinLogRecordInfo &bri(bi.records[i]); s << "\n record id " << bri.id << " , count " << bri.count; s << "\n record start " << bri.start_time << " , end " << bri.end_time; s << "\n record size " << bri.minimum_size << " - " << bri.maximum_size; } s.restoreControl(); return s; } #endif // PIBINARYLOG_H