Files
pip/libs/main/io_devices/pibinarylog.h
peri4 1de4304e30 PIBinaryLog createIndexOnFly, loadIndex, saveIndex features
PIFile readAll and writeAll static methods
2023-05-11 21:44:31 +03:00

478 lines
15 KiB
C++

/*! \file pibinarylog.h
* \ingroup IO
* \~\brief
* \~english Binary log
* \~russian Бинарный лог
*/
/*
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 <http://www.gnu.org/licenses/>.
*/
#ifndef PIBINARYLOG_H
#define PIBINARYLOG_H
#include "pichunkstream.h"
#include "pifile.h"
//! \ingroup IO
//! \~\brief
//! \~english Binary log
//! \~russian Бинарный лог
class PIP_EXPORT PIBinaryLog: public PIIODevice {
PIIODEVICE(PIBinaryLog, "binlog");
public:
explicit PIBinaryLog();
virtual ~PIBinaryLog();
//! \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 */
};
#pragma pack(push, 8)
//! \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 position, ID and timestamp of record in file
struct PIP_EXPORT BinLogIndex {
int id;
int data_size;
llong pos;
PISystemTime timestamp;
};
#pragma pack(pop)
//! \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 = 0;
llong log_size = 0L;
PISystemTime start_time;
PISystemTime end_time;
PIMap<int, BinLogRecordInfo> records;
PIByteArray user_header;
};
//! 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; }
//! Returns if index creates while writing
bool createIndexOnFly() const { return create_index_on_fly; }
//! 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 index creation while writing
void setCreateIndexOnFly(bool yes);
//! 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);
//! Set function wich returns new binlog file path when using split mode.
//! Overrides internal file path generator (logdir() + prefix() + current_time()).
//! To restore internal file path generator set this function to "nullptr".
void setFuncGetNewFilePath(std::function<PIString()> f) { f_new_path = f; }
//! 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, int * readed_id = 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, int * readed_id = 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; }
//! Returns timestamp of log start
PISystemTime logStartTimestamp() const { return startlogtime; }
//! 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<int> filterID;
//! Go to begin of BinLog file
void restart();
//! Get binlog info \a BinLogInfo
BinLogInfo logInfo() const {
if (is_indexed) return index.info;
return getLogInfo(path());
}
//! Get binlog index \a BinLogIndex, need \a createIndex before getting index
const PIVector<BinLogIndex> & logIndex() const { return index.index; }
//! Create index of current binlog file
bool createIndex();
//! Return if current binlog file is indexed
bool isIndexed() { return is_indexed; }
//! Find nearest record of time \"time\". Returns -1 if not indexed or time less than first record
int posForTime(const PISystemTime & time);
//! 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;
PIByteArray saveIndex() const;
bool loadIndex(PIByteArray saved);
//! \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);
//! Create new binlog from part of "src" with allowed IDs and "from" to "to" file position
static bool cutBinLog(const BinLogInfo & src, const PIString & dst, int from, int to);
//! Create new binlog from serial splitted binlogs "src"
static bool joinBinLogsSerial(const PIStringList & src,
const PIString & dst,
std::function<bool(const PIString &, PISystemTime)> progress = nullptr);
protected:
PIString constructFullPathDevice() const override;
void configureFromFullPathDevice(const PIString & full_path) override;
PIPropertyStorage constructVariantDevice() const override;
void configureFromVariantDevice(const PIPropertyStorage & d) override;
ssize_t readDevice(void * read_to, ssize_t max_size) override;
ssize_t writeDevice(const void * data, ssize_t size) override;
bool openDevice() override;
bool closeDevice() override;
void propertyChanged(const char * s) override;
bool threadedRead(const uchar * readed, ssize_t size) override;
DeviceInfoFlags deviceInfoFlags() const override { return PIIODevice::Reliable; }
private:
struct PIP_EXPORT Record {
int id;
int size;
PISystemTime timestamp;
PIByteArray data;
};
struct PIP_EXPORT CompleteIndex {
void clear();
void makeIndexPos();
BinLogInfo info;
PIVector<BinLogIndex> index;
PIMap<llong, int> index_pos;
};
BINARY_STREAM_FRIEND(CompleteIndex)
bool writeFileHeader();
bool checkFileHeader();
Record readRecord();
int writeRecord(int id, PISystemTime time, const void * data, int size);
static void parseLog(PIFile * f, BinLogInfo * info, PIVector<BinLogIndex> * index);
void moveIndex(int i);
static PIString getLogfilePath(const PIString & log_dir, const PIString & prefix);
CompleteIndex index;
PlayMode play_mode;
SplitMode split_mode;
PIFile file;
Record 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, create_index_on_fly;
PIByteArray user_header;
std::function<PIString()> f_new_path;
};
BINARY_STREAM_WRITE(PIBinaryLog::BinLogInfo) {
PIChunkStream cs;
cs.add(1, v.path)
.add(2, v.records_count)
.add(3, v.log_size)
.add(4, v.start_time)
.add(5, v.end_time)
.add(6, v.records)
.add(7, v.user_header);
s << cs.data();
return s;
}
BINARY_STREAM_READ(PIBinaryLog::BinLogInfo) {
PIByteArray csba;
s >> csba;
PIChunkStream cs(csba);
while (!cs.atEnd()) {
switch (cs.read()) {
case 1: cs.get(v.path); break;
case 2: cs.get(v.records_count); break;
case 3: cs.get(v.log_size); break;
case 4: cs.get(v.start_time); break;
case 5: cs.get(v.end_time); break;
case 6: cs.get(v.records); break;
case 7: cs.get(v.user_header); break;
}
}
return s;
}
BINARY_STREAM_WRITE(PIBinaryLog::CompleteIndex) {
s << v.info << v.index;
return s;
}
BINARY_STREAM_READ(PIBinaryLog::CompleteIndex) {
s >> v.info >> v.index;
return s;
}
//! \relatesalso PICout \brief Output operator PIBinaryLog::BinLogInfo to PICout
inline PICout operator<<(PICout s, const PIBinaryLog::BinLogInfo & bi) {
s.space();
s.saveAndSetControls(0);
s << "[PIBinaryLog] " << bi.path << "\n";
if (bi.log_size < 0) {
s << "invalid file path";
s.restoreControls();
return s;
}
if (bi.log_size == 0) {
s << "Invalid empty file";
s.restoreControls();
return s;
}
if (bi.records_count < 0 && bi.records_count > -4) {
s << "Invalid file or corrupted signature";
s.restoreControls();
return s;
}
if (bi.records_count < -3) {
s << "Invalid binlog version";
s.restoreControls();
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<int> keys = bi.records.keys();
for (int i: keys) {
PIBinaryLog::BinLogRecordInfo bri = bi.records.at(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.restoreControls();
return s;
}
#endif // PIBINARYLOG_H