public Result <string> TryAcquireLease(string containerName, string blobName)
        {
            var path = Path.Combine(_root, containerName, blobName);

            try
            {
                var file = new FileInfo(path);
                if (!file.Exists)
                {
                    return(null);
                }

                using (var stream = file.Open(FileMode.Open, FileAccess.ReadWrite, FileShare.None))
                    using (var epStream = new MetadataPrefixStream(stream))
                    {
                        var flags = epStream.ReadFlags();
                        if ((flags & 0x1) == 0x1)
                        {
                            // already locked, conflict
                            return(Result <string> .CreateError("Conflict"));
                        }

                        epStream.WriteFlags((byte)(flags | 0x1));
                        return(Result.CreateSuccess(epStream.ReadETag()));
                    }
            }
            catch (FileNotFoundException)
            {
                return(Result <string> .CreateError("NotFound"));
            }
            catch (DirectoryNotFoundException)
            {
                return(Result <string> .CreateError("NotFound"));
            }
        }
        public string GetBlobEtag(string containerName, string blobName)
        {
            var path = Path.Combine(_root, containerName, blobName);

            try
            {
                var file = new FileInfo(path);
                if (!file.Exists)
                {
                    return(null);
                }

                using (var stream = file.OpenRead())
                    using (var epStream = new MetadataPrefixStream(stream))
                    {
                        return(epStream.ReadETag());
                    }
            }
            catch (FileNotFoundException)
            {
                return(null);
            }
            catch (DirectoryNotFoundException)
            {
                return(null);
            }
        }
        public Maybe <object> GetBlob(string containerName, string blobName, Type type, out string etag, IDataSerializer serializer = null)
        {
            var path = Path.Combine(_root, containerName, blobName);

            try
            {
                var file = new FileInfo(path);
                if (!file.Exists)
                {
                    etag = null;
                    return(Maybe <object> .Empty);
                }

                using (var stream = file.OpenRead())
                    using (var epStream = new MetadataPrefixStream(stream))
                    {
                        etag = epStream.ReadETag();
                        var deserialized = (serializer ?? _defaultSerializer).TryDeserialize(epStream, type);
                        return(deserialized.IsSuccess ? new Maybe <object>(deserialized.Value) : Maybe <object> .Empty);
                    }
            }
            catch (FileNotFoundException)
            {
                etag = null;
                return(Maybe <object> .Empty);
            }
            catch (DirectoryNotFoundException)
            {
                etag = null;
                return(Maybe <object> .Empty);
            }
        }
        /// <returns>New ETag</returns>
        private string WriteToStream(MetadataPrefixStream stream, object item, Type type, IDataSerializer serializer = null)
        {
            byte[] result;
            using (var resultStream = new MemoryStream())
            {
                (serializer ?? _defaultSerializer).Serialize(item, resultStream, type);
                result = resultStream.ToArray();
            }

            stream.Seek(0, SeekOrigin.Begin);
            stream.Write(result, 0, result.Length);
            stream.SetLength(result.Length);
            return(stream.WriteNewETag());
        }
        private string WriteToStream(MetadataPrefixStream stream, Stream item)
        {
            byte[] result;
            using (var resultStream = new MemoryStream())
            {
                item.Position = 0;
                item.CopyTo(resultStream);
                result = resultStream.ToArray();
            }

            stream.Seek(0, SeekOrigin.Begin);
            stream.Write(result, 0, result.Length);
            stream.SetLength(result.Length);
            stream.Seek(0, SeekOrigin.Begin);
            return(stream.WriteNewETag());
        }
        public Maybe <Stream> GetBlobStream(string containerName, string blobName, out string etag)
        {
            var path = Path.Combine(_root, containerName, blobName);

            try
            {
                var file = new FileInfo(path);
                if (!file.Exists)
                {
                    etag = null;
                    return(Maybe <Stream> .Empty);
                }

                using (var stream = file.OpenRead())
                    using (var epStream = new MetadataPrefixStream(stream))
                    {
                        etag = epStream.ReadETag();
                        int length = (int)epStream.Length;
                        epStream.Seek(0, SeekOrigin.Begin);
                        var    returnStream = new MemoryStream(length);
                        byte[] buffer       = new byte[8192];
                        int    bytesRead    = 1;
                        while (length > 0 && bytesRead > 0)
                        {
                            bytesRead = epStream.Read(buffer, 0, Math.Min(length, buffer.Length));
                            returnStream.Write(buffer, 0, bytesRead);
                            length -= bytesRead;
                        }
                        returnStream.Position = 0;
                        return(new Maybe <Stream>(returnStream));
                    }
            }
            catch (FileNotFoundException)
            {
                etag = null;
                return(Maybe <Stream> .Empty);
            }
            catch (DirectoryNotFoundException)
            {
                etag = null;
                return(Maybe <Stream> .Empty);
            }
        }
        public Result <string> TryRenewLease(string containerName, string blobName, string leaseId)
        {
            var path = Path.Combine(_root, containerName, blobName);

            try
            {
                var file = new FileInfo(path);
                if (!file.Exists)
                {
                    return(null);
                }

                using (var stream = file.Open(FileMode.Open, FileAccess.Read, FileShare.None))
                    using (var epStream = new MetadataPrefixStream(stream))
                    {
                        var flags = epStream.ReadFlags();
                        if ((flags & 0x1) == 0x0)
                        {
                            // not locked
                            return(Result <string> .CreateError("NotFound"));
                        }

                        if (leaseId != epStream.ReadETag())
                        {
                            // locked by another leaseId, conflict
                            return(Result <string> .CreateError("Conflict"));
                        }

                        return(Result.CreateSuccess("OK"));
                    }
            }
            catch (FileNotFoundException)
            {
                return(Result <string> .CreateError("NotFound"));
            }
            catch (DirectoryNotFoundException)
            {
                return(Result <string> .CreateError("NotFound"));
            }
        }
        public Maybe <XElement> GetBlobXml(string containerName, string blobName, out string etag, IDataSerializer serializer = null)
        {
            var formatter = (serializer ?? _defaultSerializer) as IIntermediateDataSerializer;

            if (formatter == null)
            {
                etag = null;
                return(Maybe <XElement> .Empty);
            }

            var path = Path.Combine(_root, containerName, blobName);

            try
            {
                var file = new FileInfo(path);
                if (!file.Exists)
                {
                    etag = null;
                    return(Maybe <XElement> .Empty);
                }

                using (var stream = file.OpenRead())
                    using (var epStream = new MetadataPrefixStream(stream))
                    {
                        etag = epStream.ReadETag();
                        var unpacked = formatter.TryUnpackXml(epStream);
                        return(unpacked.IsSuccess ? new Maybe <XElement>(unpacked.Value) : Maybe <XElement> .Empty);
                    }
            }
            catch (FileNotFoundException)
            {
                etag = null;
                return(Maybe <XElement> .Empty);
            }
            catch (DirectoryNotFoundException)
            {
                etag = null;
                return(Maybe <XElement> .Empty);
            }
        }
        public Maybe <T> UpsertBlobOrSkip <T>(
            string containerName, string blobName, Func <Maybe <T> > insert, Func <T, Maybe <T> > update, IDataSerializer serializer = null)
        {
            var path   = Path.Combine(_root, containerName, blobName);
            var folder = Path.GetDirectoryName(path);

            if (folder != null && !Directory.Exists(folder))
            {
                Directory.CreateDirectory(folder);
            }

            var optimisticPolicy = _policies.OptimisticConcurrency();
            int retryCount       = 0;

            while (true)
            {
                try
                {
                    using (var file = File.Open(path, FileMode.OpenOrCreate, FileAccess.ReadWrite, FileShare.None))
                        using (var epStream = new MetadataPrefixStream(file))
                        {
                            var input = Maybe <T> .Empty;
                            if (epStream.Length != 0)
                            {
                                var deserialized = (serializer ?? _defaultSerializer).TryDeserializeAs <T>(epStream);
                                if (deserialized.IsSuccess)
                                {
                                    input = deserialized.Value;
                                }
                            }

                            var output = input.HasValue ? update(input.Value) : insert();
                            if (output.HasValue)
                            {
                                WriteToStream(epStream, output.Value, typeof(T), serializer);
                                return(output.Value);
                            }

                            if (!input.HasValue)
                            {
                                epStream.Close();
                                file.Close();
                                File.Delete(path);
                            }

                            return(Maybe <T> .Empty);
                        }
                }
                catch (IOException exception)
                {
                    TimeSpan retryInterval;
                    if (!optimisticPolicy.ShouldRetry(retryCount++, 0, exception, out retryInterval, null))
                    {
                        throw;
                    }

                    // Retry
                    Thread.Sleep(retryInterval);
                }
            }
        }
        bool PutBlob(string containerName, string blobName, object item, Type type, bool overwrite, string expectedEtag, out string etag, IDataSerializer serializer = null)
        {
            var path   = Path.Combine(_root, containerName, blobName);
            var folder = Path.GetDirectoryName(path);

            if (folder != null && !Directory.Exists(folder))
            {
                Directory.CreateDirectory(folder);
            }

            var file = new FileInfo(path);

            if (overwrite)
            {
                // retry in case it is currently locked by another operation
                var optimisticPolicy = _policies.OptimisticConcurrency();
                int retryCount       = 0;
                while (true)
                {
                    try
                    {
                        using (var stream = file.Open(FileMode.OpenOrCreate, FileAccess.ReadWrite, FileShare.None))
                            using (var epStream = new MetadataPrefixStream(stream))
                            {
                                if (!string.IsNullOrEmpty(expectedEtag) && epStream.ReadETag() != expectedEtag)
                                {
                                    etag = null;
                                    return(false);
                                }

                                etag = WriteToStream(epStream, item, type, serializer);
                            }

                        return(true);
                    }
                    catch (IOException exception)
                    {
                        TimeSpan retryInterval;
                        if (!optimisticPolicy.ShouldRetry(retryCount++, 0, exception, out retryInterval, null))
                        {
                            throw;
                        }

                        // Retry
                        Thread.Sleep(retryInterval);
                    }
                }
            }

            // no need to retry in the non-overwrite case, since being locked implies the file already exist anyway
            try
            {
                using (var stream = file.Open(FileMode.CreateNew, FileAccess.Write, FileShare.None))
                    using (var epStream = new MetadataPrefixStream(stream))
                    {
                        etag = WriteToStream(epStream, item, type, serializer);
                    }

                return(true);
            }
            catch (IOException)
            {
                etag = null;
                return(false);
            }
        }