public IEnumerable <TapeRecord> ReadRecords(long afterVersion, int maxCount)
        {
            if (afterVersion < 0)
            {
                throw new ArgumentOutOfRangeException("afterVersion", "Must be zero or greater.");
            }

            if (maxCount <= 0)
            {
                throw new ArgumentOutOfRangeException("maxCount", "Must be more than zero.");
            }

            // afterVersion + maxCount > long.MaxValue, but transformed to avoid overflow
            if (afterVersion > long.MaxValue - maxCount)
            {
                throw new ArgumentOutOfRangeException("maxCount", "Version will exceed long.MaxValue.");
            }

            var blob = _container.GetBlockBlobReference(_blobName);

            var dataExists = blob.Exists();

            // we return empty result if writer didn't even start writing to the storage.
            if (!dataExists)
            {
                return(Enumerable.Empty <TapeRecord>());
            }

            var blockList = blob.DownloadBlockList();

            var range = GetReadRange(blockList, afterVersion, maxCount);

            if (range.Item4 == 0)
            {
                return(new TapeRecord[0]);
            }

            using (var s = blob.OpenRead())
            {
                s.ReadAheadSize = range.Item2;
                s.Seek(range.Item1, SeekOrigin.Begin);

                using (var reader = new BinaryReader(s))
                {
                    var bytes = reader.ReadBytes(range.Item2);

                    using (var ms = new MemoryStream(bytes))
                    {
                        TapeStreamSerializer.SkipRecords(range.Item3, ms);
                        var records = Enumerable.Range(0, range.Item4)
                                      .Select(i => TapeStreamSerializer.ReadRecord(ms))
                                      .ToArray();

                        return(records);
                    }
                }
            }
        }
        public void WriteRecordTest()
        {
            byte[]     expecteddata    = Encoding.UTF8.GetBytes("TEST");
            const long expectedversion = 3L;
            var        expectedrecord  = new TapeRecord(expectedversion, expecteddata);

            using (MemoryStream ms = new MemoryStream())
            {
                new TapeStreamSerializer().WriteRecord(ms, expecteddata, expectedversion);
                ms.Seek(0, SeekOrigin.Begin);

                var actual = new TapeStreamSerializer().ReadRecord(ms);

                Assert.AreEqual(expectedrecord.Version, actual.Version);
                Assert.AreEqual(expectedrecord.Data[0], actual.Data[0]);
            }
        }
        public bool TryAppend(byte[] buffer, TapeAppendCondition appendCondition = new TapeAppendCondition())
        {
            if (buffer == null)
            {
                throw new ArgumentNullException("buffer");
            }

            if (buffer.Length == 0)
            {
                throw new ArgumentException("Buffer must contain at least one byte.");
            }

            long          version;
            int           lastBlockSize;
            long          offset;
            long          firstVersion;
            long          count;
            List <string> blockNames;

            var blob = _container.GetBlockBlobReference(_blobName);

            if (blob.Exists())
            {
                var blockList = blob.DownloadBlockList().ToArray();
                blockNames = blockList.Select(bl => bl.Name).ToList();
                var lastBlock = blockList.LastOrDefault();

                if (default(ListBlockItem) == lastBlock)
                {
                    version       = 0;
                    lastBlockSize = int.MaxValue;
                    offset        = 0;
                    firstVersion  = 1;
                    count         = 0;
                }
                else
                {
                    var nameInfo = Naming.GetInfo(DecodeName(lastBlock.Name));
                    firstVersion = nameInfo.FirstVersion;
                    version      = nameInfo.FirstVersion - 1 + nameInfo.Count;
                    count        = nameInfo.Count;

                    if (lastBlock.Size > int.MaxValue)
                    {
                        throw new InvalidOperationException("last block size must be in 'int' range");
                    }

                    lastBlockSize = (int)lastBlock.Size;
                    offset        = blockList.Reverse().Skip(1).Sum(l => l.Size);
                }
            }
            else
            {
                version       = 0;
                lastBlockSize = int.MaxValue;
                offset        = 0;
                firstVersion  = 1;
                count         = 0;
                blockNames    = new List <string>();
            }

            if (!appendCondition.Satisfy(version))
            {
                return(false);
            }

            if (version > long.MaxValue - 1)
            {
                throw new IndexOutOfRangeException("Version is more than long.MaxValue.");
            }

            if (buffer.Length > FourMb)
            {
                throw new ArgumentException("buffer size must be less than or equal to 4 Mb", "buffer");
            }

            using (var outStream = new MemoryStream())
            {
                if (buffer.Length < MaxBlockSize && lastBlockSize <= MaxBlockSize - buffer.Length)
                {
                    // read old block
                    using (var s = blob.OpenRead())
                    {
                        s.Seek(offset, SeekOrigin.Begin);
                        s.CopyTo(outStream);
                        TapeStreamSerializer.WriteRecord(outStream, buffer, version + 1);

                        count++;
                        blockNames.RemoveAt(blockNames.Count - 1);
                    }
                }
                else
                {
                    TapeStreamSerializer.WriteRecord(outStream, buffer, version + 1);

                    firstVersion = version + 1;
                    count        = 1;
                }

                var blockId = EncodeName(Naming.GetName(firstVersion, count));

                string md5Hash;
                outStream.Seek(0, SeekOrigin.Begin);
                using (var md5 = MD5.Create()) {
                    md5Hash = Convert.ToBase64String(md5.ComputeHash(outStream));
                }

                outStream.Seek(0, SeekOrigin.Begin);
                blob.PutBlock(blockId, outStream, md5Hash);
                blockNames.Add(blockId);
                blob.PutBlockList(blockNames);

                return(true);
            }
        }