#region Copyright 2011-2014 by Roger Knapp, Licensed under the Apache License, Version 2.0 /* Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #endregion using System; using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.Runtime.InteropServices; using System.Threading; using CSharpTest.Net.Interfaces; using CSharpTest.Net.Synchronization; namespace CSharpTest.Net.IO { /// /// Provides a means of storing multitudes of small files inside one big one. I doubt this is a /// good name for it, but it works. Anyway, the file is broken into fixed size blocks and each /// block can be chained to another to allow the sub-allocation to grow larger than the block size. /// This is the primary storage vehicle for the BPlusTree implementation. /// public class FragmentedFile : IDisposable { private const FileOptions NoBuffering = (FileOptions)0x20000000; /// Common operational values for 'normal' files public const FileOptions OptionsDefault = FileOptions.RandomAccess; /// Common operational values for using OS cache write-through (SLOW) public const FileOptions OptionsWriteThrough = FileOptions.RandomAccess | FileOptions.WriteThrough; /// Uses FILE_FLAG_NO_BUFFERING see http://msdn.microsoft.com/en-us/library/cc644950(v=vs.85).aspx (SLOWEST) public const FileOptions OptionsNoBuffering = OptionsWriteThrough | NoBuffering; private readonly StreamCache _streamCache; private readonly bool _useAlignedIo; private readonly long _maskVersion, _maskOffset; private readonly int _blockSize; private readonly int _reallocSize; private readonly object _syncFreeBlock; private readonly FileBlock _header; private bool _disposed; private long _nextFree; /// /// Opens an existing fragmented file store, to create a new one call the CreateNew() static /// /// The file name that will store the data /// The block size that was specified when CreateNew() was called public FragmentedFile(string filename, int blockSize) : this(filename, blockSize, 100, Environment.ProcessorCount, OptionsWriteThrough) { } /// /// Opens an existing fragmented file store, to create a new one call the CreateNew() static /// /// The file name that will store the data /// The block size that was specified when CreateNew() was called /// The number of blocks to grow the file by when needed, or zero for on-demand /// The number of threads that can simultaneously access the file /// The file options to use when opening the file public FragmentedFile(string filename, int blockSize, int growthRate, int cacheLimit, FileOptions options) : this(new FileStreamFactory(filename, FileMode.Open, FileAccess.ReadWrite, FileShare.ReadWrite, 8, options), blockSize, growthRate, cacheLimit, options) { } /// /// Opens an existing fragmented file store, to create a new one call the CreateNew() static /// /// The file name that will store the data /// The block size on disk to be used for allocations /// The number of blocks to grow the file by when needed, or zero for on-demand /// The number of threads that can simultaneously access the file /// The file access requested /// The file share permissions /// The file options to use when opening the file public FragmentedFile(string filename, int blockSize, int growthRate, int cacheLimit, FileAccess access, FileShare share, FileOptions options) : this(new FileStreamFactory(filename, FileMode.Open, access, share, 8, options), blockSize, growthRate, cacheLimit, options) { } /// /// Opens an existing fragmented file store, to create a new one call the CreateNew() static /// /// An IFactory that produces streams for a storage /// The block size to be used for allocations /// The number of blocks to grow the file by when needed, or zero for on-demand /// The number of threads that can simultaneously access the file public FragmentedFile(IFactory streamFactory, int blockSize, int growthRate, int cacheLimit) : this(streamFactory, blockSize, growthRate, cacheLimit, FileOptions.None) { } /// Internal use to specify aligned IO when using NoBuffering file option protected FragmentedFile(IFactory streamFactory, int blockSize, int growthRate, int cacheLimit, FileOptions options) { _useAlignedIo = (options & NoBuffering) == NoBuffering; _streamCache = new StreamCache(streamFactory, cacheLimit); _header = new FileBlock(blockSize, _useAlignedIo); _syncFreeBlock = new object(); try { long fallocated; bool canWrite; using (Stream s = _streamCache.Open(FileAccess.ReadWrite)) { canWrite = s.CanWrite; if (!s.CanRead) throw new InvalidOperationException("The stream does not support Read access."); _header.Read(s, blockSize); if ((_header.Flags & ~BlockFlags.HeaderFilter) != BlockFlags.HeaderFlags) throw new InvalidDataException(); _nextFree = _header.NextBlockId; SetBlockSize(_header.Length, out _blockSize, out _maskVersion, out _maskOffset); if (blockSize != _blockSize) throw new ArgumentOutOfRangeException("blockSize"); fallocated = LastAllocated(s); _reallocSize = growthRate * _blockSize; if (canWrite) { s.Position = 0; _header.NextBlockId = long.MinValue; _header.Write(s, FileBlock.HeaderSize); } } if (canWrite) { if ((_header.Flags & BlockFlags.ResizingFile) == BlockFlags.ResizingFile && _nextFree > 0) ResizeFile(_nextFree, Math.Max(fallocated, _nextFree + _reallocSize)); if (_nextFree == long.MinValue) _nextFree = RecoverFreeBlocks(); } } catch { _streamCache.Dispose(); throw; } } /// /// Closes the storage, a must-do to save a costly recomputation of free block on open /// public void Dispose() { if (!_disposed) { _disposed = true; try { using (Stream s = _streamCache.Open(FileAccess.ReadWrite)) { if (s.CanWrite) { _header.Read(s, FileBlock.HeaderSize); if (_header.Flags != BlockFlags.HeaderFlags) throw new InvalidDataException(); s.Position = 0; _header.NextBlockId = _nextFree; _header.Write(s, FileBlock.HeaderSize); } } _header.Dispose(); _streamCache.Dispose(); } catch (ObjectDisposedException) { } } } /// /// Creates a new file (or truncates an existing one) that stores multiple smaller files /// public static FragmentedFile CreateNew(string filename, int blockSize) { return CreateNew(filename, blockSize, 100, Environment.ProcessorCount, OptionsWriteThrough); } /// /// Creates a new file (or truncates an existing one) that stores multiple smaller files /// public static FragmentedFile CreateNew(string filename, int blockSize, int growthRate, int cacheLimit, FileOptions options) { WriteEmtpy(new FileStreamFactory(filename, FileMode.Create, FileAccess.Write, FileShare.None), blockSize); return new FragmentedFile(filename, blockSize, growthRate, cacheLimit, options); } /// /// Creates a new file (or truncates an existing one) that stores multiple smaller files /// public static FragmentedFile CreateNew(IFactory streamFactory, int blockSize, int growthRate, int cacheLimit) { WriteEmtpy(streamFactory, blockSize); return new FragmentedFile(streamFactory, blockSize, growthRate, cacheLimit); } /// /// Creates a new file (or truncates an existing one) that stores multiple smaller files /// private static void WriteEmtpy(IFactory streamFactory, int blockSize) { long mask; SetBlockSize(blockSize, out blockSize, out mask, out mask); using (FileBlock block = new FileBlock(Check.InRange(blockSize, 512, /*65,536*/0x10000), false)) { block.Length = blockSize; block.Flags = BlockFlags.HeaderFlags; using (Stream f = streamFactory.Create()) { f.Position = 0; block.Write(f, blockSize); f.SetLength(f.Position); } } } /// Destroys all contents of the file and resets to an initial state public void Clear() { using (Stream f = _streamCache.Open(FileAccess.ReadWrite)) { f.Position = 0; f.SetLength(0); _header.NextBlockId = 0; _header.Write(f, _blockSize); } _nextFree = 0; } /// Creates a new allocation block within the file /// A unique integer id for the block to be used with Open/Delete public long Create() { using (FileBlock block = new FileBlock(_blockSize, _useAlignedIo)) { long identity = AllocBlock(block, BlockFlags.ExternalBlock); return identity; } } /// Creates a new allocation block within the file /// A unique integer id for the block to be used with Open/Delete /// The stream to write to the newly created block public Stream Create(out long identity) { using (FileBlock block = new FileBlock(_blockSize, _useAlignedIo)) { identity = AllocBlock(block, BlockFlags.ExternalBlock); return new BlockStreamWriter(this, block); } } /// /// Opens the file with the identity previously obtained by Create() using the /// access provided; however, Read+Write is not supported, use either Read or /// Write but not both. /// public Stream Open(long identity, FileAccess access) { if (identity < FirstIdentity) throw new ArgumentOutOfRangeException("identity"); if (access == FileAccess.Read) return new BlockStreamReader(this, identity); if (access == FileAccess.Write) return new BlockStreamWriter(this, identity); throw new ArgumentOutOfRangeException("access"); } /// /// Deletes the contents written to the identity provided and returns the /// identity to the available pool. /// public void Delete(long identity) { if (identity < FirstIdentity) throw new ArgumentOutOfRangeException("identity"); if(!FreeBlock(identity, BlockFlags.ExternalBlock)) throw new ArgumentOutOfRangeException("identity"); } private Stream OpenBlock(FileAccess access, long identity) { Stream stream = _streamCache.Open(access); try { if ((identity & _maskOffset) > LastAllocated(stream)) throw new ArgumentOutOfRangeException("identity"); long offset = (identity & _maskOffset); stream.Position = offset; Check.IsEqual(offset, stream.Position); return stream; } catch { if (stream != null) stream.Dispose(); throw; } } /// Returns the 'first' block identity that can be allocated public long FirstIdentity { get { return _blockSize | 1; } } private long LastAllocated(Stream s) { return ((s.Length - 1) / _blockSize) * _blockSize; } private static void SetBlockSize(int value, out int blockSize, out long maskVersion, out long maskOffset) { maskVersion = 0; int ix = 0; while (true) { long bit = 1 << ix; if (value == bit) break; maskVersion |= bit; if (++ix >= 32) throw new ArgumentOutOfRangeException("blockSize", "The block size must be a power of 2."); } maskOffset = ~maskVersion; blockSize = value; } private long RecoverFreeBlocks() { Trace.TraceWarning("The file store was not closed properly, recovering free blocks."); using (Stream s = _streamCache.Open(FileAccess.ReadWrite)) using (FileBlock block = new FileBlock(_blockSize, _useAlignedIo)) { long lastBlock = LastAllocated(s); long last = 0; for (long blk = lastBlock; blk > 0; blk -= _blockSize) { s.Position = blk & _maskOffset; block.Read(s, FileBlock.HeaderSize); Check.Assert((block.BlockId & _maskOffset) == blk); if ((block.Flags & BlockFlags.BlockDeleted) == BlockFlags.BlockDeleted) { block.NextBlockId = last; last = block.BlockId; s.Position = blk & _maskOffset; block.Write(s, FileBlock.HeaderSize); } } return last; } } /// Used for enumeration of the storage blocks in the file. /// Allows enumeration of all stream, or of just the externally allocated streams /// Determines if the checksum should be verified while reading the block bytes /// A method that returns true to ignore the exception and continue processing /// Enumeration of the identity and data stream of each block in the file public IEnumerable> ForeachBlock(bool allocatedOnly, bool verifyReads, Converter ignoreException) { using (Stream s = _streamCache.Open(FileAccess.ReadWrite)) using (FileBlock block = new FileBlock(_blockSize, _useAlignedIo)) { long lastBlock = LastAllocated(s); for (long blk = lastBlock; blk > 0; blk -= _blockSize) { s.Position = blk & _maskOffset; block.Read(s, FileBlock.HeaderSize); byte[] bytes; try { if ((block.BlockId & _maskOffset) != blk) throw new InvalidDataException(); if (allocatedOnly && (block.Flags & BlockFlags.ExternalBlock) != BlockFlags.ExternalBlock) continue; using (Stream reader = new BlockStreamReader(this, block.BlockId, BlockFlags.None, verifyReads)) bytes = IOStream.ReadAllBytes(reader); } catch (Exception error) { if (ignoreException != null && ignoreException(error)) continue; throw; } using (Stream ms = new MemoryStream(bytes, false)) yield return new KeyValuePair(block.BlockId, ms); } } } private bool FreeBlock(long blockId, BlockFlags expected) { using (FileBlock first = new FileBlock(_blockSize, _useAlignedIo)) { ReadBlock(blockId, first, FileBlock.HeaderSize, expected | BlockFlags.BlockDeleted); if ((first.Flags & expected) != expected) return false; using (FileBlock last = first.Clone()) { while (last.NextBlockId != 0) { last.Flags = BlockFlags.BlockDeleted; WriteBlock(last.BlockId, last, FileBlock.HeaderSize); ReadBlock(last.NextBlockId, last, FileBlock.HeaderSize, BlockFlags.InternalBlock); } using (new SafeLock(_syncFreeBlock)) { last.Flags = BlockFlags.BlockDeleted; last.NextBlockId = _nextFree; WriteBlock(last.BlockId, last, FileBlock.HeaderSize); _nextFree = first.BlockId; } return true; } } } private void ResizeFile(long startBlock, long endBlock) { if ((startBlock & _maskVersion) != 0 || (endBlock & _maskVersion) != 0) throw new InvalidDataException(); using (FileBlock block = new FileBlock(_blockSize, _useAlignedIo)) using (Stream io = _streamCache.Open(FileAccess.Write)) { _header.Flags |= BlockFlags.ResizingFile; _header.NextBlockId = startBlock; io.Position = 0; _header.Write(io, FileBlock.HeaderSize); try { block.Clear(); block.Flags = BlockFlags.BlockDeleted; _nextFree = 0; for (long ix = endBlock; ix >= startBlock; ix -= _blockSize) { block.BlockId = ix; block.NextBlockId = _nextFree; _nextFree = block.BlockId; io.Position = ix & _maskOffset; block.Write(io, FileBlock.HeaderSize); } } finally { _header.Flags &= ~BlockFlags.ResizingFile; _header.NextBlockId = long.MinValue; io.Position = 0; _header.Write(io, FileBlock.HeaderSize); } } } private long AllocBlock(FileBlock block, BlockFlags type) { using (new SafeLock(_syncFreeBlock)) { long blockId = _nextFree; if (blockId == 0 && _reallocSize > 0) { long fsize; using (Stream s = _streamCache.Open(FileAccess.Read)) fsize = LastAllocated(s); ResizeFile(fsize + _blockSize, fsize + _reallocSize); blockId = _nextFree; } if (blockId <= 0) throw new IOException(); using (Stream io = OpenBlock(FileAccess.Read, blockId)) block.Read(io, FileBlock.HeaderSize); if ((block.BlockId & _maskOffset) != (blockId & _maskOffset) || (block.Flags & BlockFlags.BlockDeleted) == 0) throw new InvalidDataException(); _nextFree = block.NextBlockId; block.BlockId = blockId; block.IncrementId(_maskVersion); block.NextBlockId = 0; block.Flags = type == BlockFlags.ExternalBlock ? (type | BlockFlags.Temporary) : type; block.Length = 0; WriteBlock(block.BlockId, block, FileBlock.HeaderSize); return block.BlockId; } } private void ReadBlock(long ordinal, FileBlock block, int length, BlockFlags type) { ReadBlock(ordinal, block, length, type, true); } private void ReadBlock(long ordinal, FileBlock block, int length, BlockFlags type, bool exactId) { using (Stream io = OpenBlock(FileAccess.Read, ordinal)) block.Read(io, length); if (exactId && block.BlockId != ordinal) throw new InvalidDataException(); if ((block.Flags & type) == 0 && type != 0) throw new InvalidDataException(); if (block.Length < 0 || block.Length > (_blockSize - FileBlock.HeaderSize)) throw new InvalidDataException(); } private void WriteBlock(long ordinal, FileBlock block, int length) { if (block.BlockId != ordinal) throw new InvalidDataException(); if (block.Length < 0 || block.Length > (_blockSize - FileBlock.HeaderSize)) throw new InvalidDataException(); try { } finally { using (Stream io = OpenBlock(FileAccess.Write, ordinal)) block.Write(io, length); } } #region BlockStreamReader class BlockStreamReader : Stream { readonly FragmentedFile _file; readonly FileBlock _block; readonly int _expectedSum; readonly bool _validated; int _blockPos; bool _disposed; Crc32 _checksum; public BlockStreamReader(FragmentedFile file, long ordinal) : this(file, ordinal, BlockFlags.ExternalBlock, true) { } public BlockStreamReader(FragmentedFile file, long ordinal, BlockFlags typeExpected, bool validated) { _file = file; _blockPos = 0; _validated = validated; _block = new FileBlock(file._blockSize, file._useAlignedIo); _file.ReadBlock(ordinal, _block, file._blockSize, typeExpected, _validated); if (_validated) { _expectedSum = _block.CheckSum; _checksum = new Crc32(); _checksum.Add(_block.BlockData, _block.DataOffset, _block.Length); if (_block.NextBlockId == 0 && _checksum != _expectedSum) throw new InvalidDataException(); } } protected override void Dispose(bool disposing) { _disposed = true; _block.Dispose(); base.Dispose(disposing); } public override bool CanRead { get { return !_disposed; } } public override bool CanSeek { get { return false; } } public override bool CanWrite { get { return false; } } public override void Flush() { } public override long Position { get { throw new NotSupportedException(); } set { throw new NotSupportedException(); } } public override long Length { get { throw new NotSupportedException(); } } public override void SetLength(long value) { throw new NotSupportedException(); } public override long Seek(long offset, SeekOrigin origin) { throw new NotSupportedException(); } private bool PrepareRead() { int remains = _block.Length - _blockPos; if (remains <= 0 && _block.NextBlockId != 0) { _file.ReadBlock(_block.NextBlockId, _block, _file._blockSize, _validated ? BlockFlags.InternalBlock : 0, _validated); remains = _block.Length; _blockPos = 0; if (_validated) { _checksum.Add(_block.BlockData, _block.DataOffset, _block.Length); if (_block.NextBlockId == 0 && _checksum != _expectedSum) throw new InvalidDataException(); } } return (remains > 0); } public override int Read(byte[] buffer, int offset, int count) { if (_disposed) throw new ObjectDisposedException(GetType().FullName); if (!PrepareRead()) return 0; int remains = _block.Length - _blockPos; int amt = Math.Min(remains, count); Array.Copy(_block.BlockData, _blockPos + _block.DataOffset, buffer, offset, amt); _blockPos += amt; return amt; } public override int ReadByte() { if (!PrepareRead()) return -1; byte response = _block.BlockData[_blockPos + _block.DataOffset]; _blockPos += 1; return response; } public override void Write(byte[] buffer, int offset, int count) { throw new NotSupportedException(); } } #endregion #region BlockStreamWriter class BlockStreamWriter : Stream, ITransactable { readonly FragmentedFile _file; readonly FileBlock _first, _restore; FileBlock _current, _temp; Crc32 _checksum; readonly bool _isNew; bool _saved, _reverted; private BlockStreamWriter(FragmentedFile file) { _file = file; _checksum = new Crc32(); _saved = _reverted = false; } public BlockStreamWriter(FragmentedFile file, FileBlock block) : this(file) { _current = _first = block; _current.Flags &= ~BlockFlags.Temporary; _restore = null; _temp = null; _isNew = true; } public BlockStreamWriter(FragmentedFile file, long ordinal) : this(file) { _current = _first = new FileBlock(file._blockSize, file._useAlignedIo); _file.ReadBlock(ordinal, _current, file._blockSize, BlockFlags.ExternalBlock); if ((_current.Flags & BlockFlags.ExternalBlock) != BlockFlags.ExternalBlock) throw new InvalidOperationException(); if ((_current.Flags & BlockFlags.Temporary) == BlockFlags.Temporary) { _isNew = true; _restore = null; _current.Flags &= ~BlockFlags.Temporary; } else { _isNew = false; _restore = _current.Clone(); _current.NextBlockId = 0; } _current.Length = 0; } private int FileBlockDataSize { get { return _file._blockSize - FileBlock.HeaderSize; } } public override void Close() { Commit(); base.Close(); } protected override void Dispose(bool disposing) { if (!_reverted) { if (_restore != null && _restore.NextBlockId != 0) { _file.FreeBlock(_restore.NextBlockId, BlockFlags.InternalBlock); _restore.NextBlockId = 0; } _current.Dispose(); _first.Dispose(); if (_restore != null) _restore.Dispose(); if(_temp != null) _temp.Dispose(); } base.Dispose(disposing); } public void Commit() { if (_saved || _reverted) return; _saved = true; try { if(!ReferenceEquals(_current, _first)) _file.WriteBlock(_current.BlockId, _current, _current.Length + FileBlock.HeaderSize); _first.CheckSum = _checksum.Value; _file.WriteBlock(_first.BlockId, _first, _first.Length + FileBlock.HeaderSize); } catch { try { Rollback(); } catch (Exception e) { GC.KeepAlive(e); } throw; } } public void Rollback() { if (_reverted) return; _reverted = true; try { if (_restore != null) _file.WriteBlock(_restore.BlockId, _restore, _restore.Length + FileBlock.HeaderSize); if (_isNew) _file.FreeBlock(_first.BlockId, BlockFlags.ExternalBlock); if (_first.NextBlockId > 0) _file.FreeBlock(_first.NextBlockId, BlockFlags.InternalBlock); } finally { _first.BlockId = -1; } } public override bool CanRead { get { return false; } } public override bool CanSeek { get { return false; } } public override bool CanWrite { get { return !_saved && !_reverted; } } public override void Flush() { } public override long Position { get { throw new NotSupportedException(); } set { throw new NotSupportedException(); } } public override long Length { get { throw new NotSupportedException(); } } public override void SetLength(long value) { throw new NotSupportedException(); } public override long Seek(long offset, SeekOrigin origin) { throw new NotSupportedException(); } public override int Read(byte[] buffer, int offset, int count) { throw new NotSupportedException(); } private void PrepareWrite() { if (_saved || _reverted) throw new ObjectDisposedException(GetType().FullName); if (_current.Length == FileBlockDataSize)//full { FileBlock next = _temp ?? new FileBlock(_file._blockSize, _file._useAlignedIo); _current.NextBlockId = _file.AllocBlock(next, BlockFlags.InternalBlock); if (!ReferenceEquals(_current, _first)) { _file.WriteBlock(_current.BlockId, _current, _current.Length + FileBlock.HeaderSize); _temp = _current; } _current = next; } } public override void Write(byte[] buffer, int offset, int count) { _checksum.Add(buffer, offset, count); while (count > 0) { PrepareWrite(); int amt = Math.Min(count, FileBlockDataSize - _current.Length); Array.Copy(buffer, offset, _current.BlockData, _current.Length + _current.DataOffset, amt); _current.Length += amt; offset += amt; count -= amt; } } public override void WriteByte(byte value) { _checksum.Add(value); PrepareWrite(); int position = _current.Length + _current.DataOffset; _current.BlockData[position] = value; _current.Length += 1; } } #endregion #region FileBlock class FileBlock : IDisposable { public const int HeaderSize = 32; private readonly bool _alignedIo; private GCHandle _pin; private readonly int _baseOffset; private readonly int _minimumIo; public readonly byte[] BlockData; public FileBlock(int blockSize, bool alignIo) { _alignedIo = alignIo; _baseOffset = 0; _minimumIo = HeaderSize; BlockData = new byte[alignIo ? blockSize * 2 : blockSize]; if (_alignedIo) { //FILE_FLAG_NO_BUFFERING required alignment see http://msdn.microsoft.com/en-us/library/cc644950(v=vs.85).aspx _pin = GCHandle.Alloc(BlockData, GCHandleType.Pinned); _baseOffset = (int)(_pin.AddrOfPinnedObject().ToInt64() & (blockSize - 1)); _minimumIo = blockSize; } } public int DataOffset { get { return _baseOffset + HeaderSize; } } private int BlockSize { get { return _alignedIo ? (BlockData.Length / 2) : BlockData.Length; } } public void Dispose() { if (_alignedIo && _pin.IsAllocated) _pin.Free(); } public void Clear() { long id = BlockId; Array.Clear(BlockData, 0, BlockData.Length); BlockId = id; } private long ReadLong(int offset) { int start = _baseOffset + offset; return ( ((long)BlockData[start + 0] << 56) | ((long)BlockData[start + 1] << 48) | ((long)BlockData[start + 2] << 40) | ((long)BlockData[start + 3] << 32) | ((long)BlockData[start + 4] << 24) | ((long)BlockData[start + 5] << 16) | ((long)BlockData[start + 6] << 8) | ((long)BlockData[start + 7] << 0) ); } private void WriteLong(int offset, long value) { int start = _baseOffset + offset; BlockData[start + 0] = (byte)(value >> 56); BlockData[start + 1] = (byte)(value >> 48); BlockData[start + 2] = (byte)(value >> 40); BlockData[start + 3] = (byte)(value >> 32); BlockData[start + 4] = (byte)(value >> 24); BlockData[start + 5] = (byte)(value >> 16); BlockData[start + 6] = (byte)(value >> 8); BlockData[start + 7] = (byte)(value >> 0); } private int ReadInt(int offset) { int start = _baseOffset + offset; return ( (BlockData[start + 0] << 24) | (BlockData[start + 1] << 16) | (BlockData[start + 2] << 8) | (BlockData[start + 3] << 0) ); } private void WriteInt(int offset, int value) { int start = _baseOffset + offset; BlockData[start + 0] = (byte)(value >> 24); BlockData[start + 1] = (byte)(value >> 16); BlockData[start + 2] = (byte)(value >> 8); BlockData[start + 3] = (byte)(value >> 0); } public long BlockId { get { return ReadLong(0); } set { WriteLong(0, value); } } public long NextBlockId { get { return ReadLong(8); } set { WriteLong(8, value); } } public int Length { get { return ReadInt(16); } set { WriteInt(16, value); } } public int CheckSum { get { return ReadInt(20); } set { WriteInt(20, value); } } //private int Reserved { get { return ReadInt(24); } set { WriteInt(24, value); } } public BlockFlags Flags { get { return (BlockFlags)ReadInt(28); } set { WriteInt(28, (int)value); } } public void IncrementId(long versionMask) { BlockId = (BlockId & ~versionMask) | ((BlockId + 1) & versionMask); } public void Read(Stream stream, int length) { int bytesRead = stream.Read(BlockData, _baseOffset, Math.Max(_minimumIo, length)); if (bytesRead < HeaderSize) throw new InvalidDataException(); } public void Write(Stream stream, int length) { stream.Write(BlockData, _baseOffset, Math.Max(_minimumIo, length)); if(!_alignedIo) stream.Flush(); } public FileBlock Clone() { FileBlock copy = new FileBlock(BlockSize, _alignedIo); Array.Copy(BlockData, _baseOffset, copy.BlockData, copy._baseOffset, BlockSize); return copy; } } #endregion [Flags] enum BlockFlags : uint { None = 0, HeaderFilter = 0x000F0000, // Allowable flags in header HeaderFlags = 0x01000000, // A valid header, for breaking compatibility between versions. ResizingFile = 0x00020000, // Currently resizing the file BlockDeleted = 0x00000001, // A block that has been destroyed InternalBlock = 0x00000002, // A block that chains with a public block index ExternalBlock = 0x00000004, // A block that was allocated publicly Temporary = 0x00000008, // Indicates a block that was allocated but not written to } } }