#ifndef BU_MYRIAD_H #define BU_MYRIAD_H #include "bu/stream.h" #include "bu/exceptionbase.h" #include "bu/mutex.h" #include "bu/array.h" #include "bu/hash.h" #include "bu/bitstring.h" namespace Bu { class MyriadStream; subExceptionDeclBegin( MyriadException ) enum { emptyStream, invalidFormat, badVersion, invalidWordSize, noSuchStream, streamExists, invalidStreamId, protectedStream, invalidParameter, invalidBackingStream, badMode, streamOpen, }; subExceptionDeclEnd(); /** * Myriad Stream Multiplexer. This is a system that allows you to store * many streams within a single backing stream. This is great for databases, * caching, etc. It's fairly lightweight, and allows all streams to grow * dynamically using a block-allocation scheme. This is used extensively * by the caching system and MyriadFs as well as other systems within * libbu++. */ class Myriad { public: typedef int32_t StreamId; typedef Bu::Array StreamIdArray; typedef Bu::List StreamIdList; enum Mode : int32_t { None = 0x00, // Flags Read = 0x01, ///< Open file for reading Write = 0x02, ///< Open file for writing Create = 0x04, ///< Create file if it doesn't exist Truncate = 0x08, ///< Truncate file if it does exist Append = 0x10, ///< Start writing at end of file //NonBlock = 0x20, ///< Open file in non-blocking mode Exclusive = 0x40, ///< Create file, if it exists then fail // Helpful mixes ReadWrite = 0x03, ///< Open for reading and writing WriteNew = 0x0E ///< Create a file (or truncate) for writing. /// Same as Write|Create|Truncate }; public: /** * Open existing Myriad container, or initialize a new one if the * backing stream is empty. If other data is already in the provided * backing stream an error is thrown. * * Myriad format V0 * 0 - 3: Myriad_MAGIC_CODE (0ad3fa84) * 4 - 4: Version Id (1) * 5 - 5: Bits per integer (32) * 6 - 9: Block size in bytes. * 10 - 13: Number of streams. * 14 - ...: Stream Data * * Stream Data: * 0 - 3: Stream Id * 4 - 7: Size of stream in bytes * 8 - ...: List of blocks in stream (4 bytes per block */ Myriad( Bu::Stream &rBacking, int32_t iBlockSize=-1, int32_t iPreallocateBlocks=-1 ); virtual ~Myriad(); /** * Creates a new stream open in the specified eMode and, optionally, * preallocates the specificed amount of space. The stream is zero * bytes even if space is preallocated. The open stream is returned, * ready for use. Use this if you don't care what the id is of the * newly created stream. */ MyriadStream create( Mode eMode, int32_t iPreallocateBytes=-1 ); /** * Open an existing stream or create a new stream with the specified * id (iStream) with the specified eMode. This respects the normal file * modes, see Bu::Myriad::Mode for details. */ MyriadStream open( StreamId iStream, Mode eMode ); /** * Allocate a new stream but do not open it, just ensure it exists and * return the id of the newly allocated stream. */ StreamId allocate(); /** * Erase the stream specified by iStream. This only can work when the * stream is not open at the moment. */ void erase( StreamId iStream ); void setSize( StreamId iStream, int32_t iNewSize ); int32_t getSize( StreamId iStream ) const; bool exists( StreamId iStream ) const; Bu::String getLocation() const; int32_t getBlockSize() const; int32_t getTotalBlocks() const; int32_t getUsedBlocks() const; int32_t getFreeBlocks() const; int32_t getTotalStreams() const; int32_t getTotalUsedBytes() const; int32_t getTotalUnusedBytes( int32_t iAssumeBlockSize=-1 ) const; Bu::BitString buildBlockUseMap() const; StreamIdArray buildBlockMap() const; /** * Lists all stream ids that you are allowed to open. Technically there * is always a zero stream, but it is used by Myriad for stream/block * accounting. It works like a normal stream but you should not open * it. */ StreamIdList getStreamList() const; /** * Flush all caches to the backing stream, write all structural and * header changes. */ void sync(); private: bool loadMyriad(); void createMyriad( int32_t iBlockSize, int32_t iPreallocateBlocks ); void writeHeader(); int32_t __calcHeaderSize(); int32_t allocateBlock(); int32_t __allocateBlock(); void releaseBlock( int32_t iBlockId, bool bBlank=true ); void __releaseBlock( int32_t iBlockId, bool bBlank=true ); void blankBlock( int32_t iBlockId ); void openStream( StreamId id ); void closeStream( StreamId id ); /** * Block restricted read, it will not read past the end of the block * that iStart places it in. */ int32_t blockRead( int32_t iBlock, int32_t iStart, void *pTarget, int32_t iSize ); /** * Block restricted write, it will not write past the end of the block * that iStart places it in. If this returns a non-zero number it's an * indication that you need to allocate a new block. */ int32_t blockWrite( int32_t iBlock, int32_t iStart, const void *pTarget, int32_t iSize ); public: /** * Bridge/communication/tracking class for individual Myriad streams. * Not for general use, this is used by Myriad and MyriadStream to * control access. */ class Stream { friend Bu::Myriad; private: Stream( Myriad &rParent, StreamId iStream, int32_t iSize ); virtual ~Stream(); public: int32_t getSize() const; int32_t getBlockSize() const; StreamId getStreamId() const; int32_t getOpenCount() const; void setSize( int32_t iNewSize ); int32_t read( int32_t iStart, void *pTarget, int32_t iSize ); int32_t write( int32_t iStart, const void *pTarget, int32_t iSize ); Bu::String getLocation() const; Bu::Array getBlockList() const; /** * Doesn't actually open, just increments the open counter. * If the open counter is non-zero then at least one stream has * a lock on this stream. */ void open(); /** * Doesn't actually close, just decrements the open counter. *@returns true if there are still handles open, false if no * streams have a lock. */ bool close(); private: mutable Bu::Mutex mAccess; Myriad &rParent; StreamId iStream; int32_t iSize; Bu::Array aBlocks; int32_t iOpenCount; }; private: typedef Bu::Hash StreamHash; typedef Bu::List IndexList; mutable Bu::Mutex mAccess; mutable Bu::Mutex mBacking; Bu::Stream &rBacking; int32_t iBlockSize; int32_t iBlockCount; bool bIsNewStream; bool bStructureChanged; mutable Bu::Mutex mhStream; StreamHash hStream; IndexList lFreeBlocks; StreamId iLastUsedIndex; }; constexpr Myriad::Mode operator&( Myriad::Mode a, Myriad::Mode b ) { return static_cast( static_cast::type>(a) & static_cast::type>(b) ); } constexpr Myriad::Mode operator|( Myriad::Mode a, Myriad::Mode b ) { return static_cast( static_cast::type>(a) | static_cast::type>(b) ); } }; #endif