#ifndef __HistoryFileWriter_h_ #define __HistoryFileWriter_h_ #include #include #include "../../shared/XmlSupport.h" #include "../DataFormat.h" class HistoryFileWriter { private: int _recordCount; int64_t _minId; int64_t _maxId; time_t _minTime; time_t _maxTime; std::fstream _output; enum State { s_writingRecords, s_closed, s_error }; State _state; // These are only meaningful after _recordCount > 0. int64_t _lastId; time_t _lastTime; std::string _errorMessage; void reportError(std::string const &message, bool useErrNo = false); public: HistoryFileWriter(std::string const &fileName); void addRecord(Record::Ref const &record); void buildIndex(); bool inErrorState() const { return _state == s_error; } std::string const &getErrorMessage() const { return _errorMessage; } int getRecordCount() const { return _recordCount; } }; // This object is read-only and thread safe. Several threads can all safely // iterate over a file at the same time. class HistoryFileReader : NoCopy, NoAssign { private: char *_base; int64_t _memoryMapSize; // The position of the first key in the index. This is relative to _base, // not the beginning of the file. int64_t _firstKey; // The position of the first pointer in the index. The is relative to // _base, not the beginning of the file. int64_t _firstPointer; int64_t _bytesPerKey; int64_t _bytesPerPointer; int64_t _minId; time_t _minTime; int64_t _dataEnd; // _minId + _maxIdCount = the largest id that we can store in a key. This // probably is not an id that actually exists in the file. This value will // always be some number of 0's followed by some number of ones, in binary. int64_t _maxIdCount; // _minTime + _maxTimeCount = the largest time that we can store in a key. // This probably is not a time that actually exists in the file. This value // will always be some number of 0's followed by some number of ones, in // binary. time_t _maxTimeCount; int _indexCount; int _idBits; //int _idBytes; int _fileHandle; std::string _errorMessage; // A wrapper around pread. // // Returns false on failure. On failure errno will be 0, or something // relevant. (I.e. not an old message from a previous and unrelated system // call.) The destination will be in an undefined state after a failure. bool read(void *dest, int64_t size, int64_t offset) const; // Returns INVALID if the index is out of range. int64_t indexToFilePosition(int index) const; template < typename T > bool read(T &dest, int64_t offset) const { return read(&dest, sizeof(dest), offset); } // This will automatically call cleanUp(). void recordError(std::string const &message, bool useErrno = false); // Release resources. It is safe to call this more than once. This will // automatically be called in the destructor, but you can call it sooner. void cleanUp(); public: HistoryFileReader(std::string const &fileName); ~HistoryFileReader(); bool isGood() const { return _fileHandle >= 0; } std::string const &getErrorMessage() const { return _errorMessage; } // Note that lowerBound and upperBound are symmetric. If the item exists, // its positions will be >= lowerBound and <= upperBound. If we found the // exact position, lowerBound and upperBound will be identical. This is // different from the STL idea, that upperBound is always past the end. // The implementation of the binary search will look more like STL, but that // gets converted before we return a result. // // If the item we are looking for comes before anything in the index, we // set the lowerBound to INVALID, and the upperBound to the lowest thing // in the index. If the item comes after anything in the index, we set // upperBound to INVALID and we set lowerBound to the highest value in the // index. If the index is empty, both bounds are set to INVALID. void find(time_t time, int64_t id, int64_t &lowerBound, int64_t &upperBound) const; // If you are going forward, and you want to start at a particular time, // and you want to catch all alerts for that time, start from here. This // will return an upper bound. int64_t findFirst(time_t time) const; // If you are going backward, and you want to start at a particular time, // and you want to catch all alerts for that time, start from here. This // will return a lower bound. int64_t findLast(time_t time) const; int64_t findInitial(time_t time, bool forward) const { return forward?findFirst(time):findLast(time); } // Forward means forward in time. Either the next record will have a higher // number for its timestamp, or the next record will have the same timestamp // and a higher id. // // This will read a record from the current position. On success, the record // will be stored in record, and position will point to the next record after // this one. On failure, record will be set to NULL. position will be set // to INVALID if we read the last record, or of there is a failure. // // It is an error to try to read from the INVALID position. // // The resulting record will include pointers into this HistoryFileReader. // If you dispose of this HistoryFileReader, any records that you read will // be useless. If you try to access such a record you'll probably get a core // dump. void readForward(int64_t &position, Record::Ref &record) const; // This is the same as readForward, except that we move the position to // the previous record, instead of the next one. void readBackward(int64_t &position, Record::Ref &record) const; // Same as readForward() or readBackward() void readAndMove(int64_t &position, Record::Ref &record, bool forward) const; // This is like readAndMove() but it doesn't give you a record. This might // be more efficient than readAndMove(). void move(int64_t &position, bool forward) const; time_t getStartTime() const { return _minTime; } // This is used as a file position any time we read off either end of the // file. E.g. if someone tried to find() a value that's higher than any // in the file, lowerBound will be the highest legal position in the file, // and upperBound will be INVALID. static const int64_t INVALID = 0; void dump(XmlNode &parent) const; }; class HistoryFileIterator : NoCopy, NoAssign { private: const HistoryFileReader &_reader; const bool _forward; std::stack< Record::Ref > _buffered; bool _readFromBuffer; int64_t _nextLocation; enum CompareResult { RecordComesBefore, Equal, RecordComesAfter }; CompareResult compare(time_t time, Record::Ref const &record) const; public: // TODO add a version that takes an alert id, too. HistoryFileIterator(HistoryFileReader &reader, bool forward, time_t initialTime); // This will return NULL if you read past the end of the file. // Otherwise this will create a record which points into memory that is part // of the HistoryFileReader. If you dispose of the HistoryFileReader, this // record will include some dangling pointers and trying to use this record // will probably cause a core dump. Record::Ref getNext(); }; #endif