첨부 실행 코드는 나눔고딕코딩 폰트를 사용합니다.

728x90
반응형
728x170

TestSolution.zip
0.72MB

▶ StorageStreamChangeType.cs

namespace TestLibrary
{
    /// <summary>
    /// 저장소 스트림 변경 타입
    /// </summary>
    internal enum StorageStreamChangeType
    {
        /// <summary>
        /// 세그먼트/메타 데이터
        /// </summary>
        SegmentsAndMetadata,

        /// <summary>
        /// 폐쇄
        /// </summary>
        Closing
    }
}

 

728x90

 

▶ TransactionStateChangeType.cs

namespace TestLibrary
{
    /// <summary>
    /// 트랜잭션 상태 변경 타입
    /// </summary>
    public enum TransactionStateChangeType
    {
        /// <summary>
        /// 시작
        /// </summary>
        Start,

        /// <summary>
        /// 커밋
        /// </summary>
        Commit,

        /// <summary>
        /// 롤백
        /// </summary>
        Rollback
    }
}

 

반응형

 

▶ ReadWriteEventArgs.cs

using System;

namespace TestLibrary
{
    /// <summary>
    /// 읽기/쓰기 이벤트 인자
    /// </summary>
    public class ReadWriteEventArgs : EventArgs
    {
        //////////////////////////////////////////////////////////////////////////////////////////////////// Property
        ////////////////////////////////////////////////////////////////////////////////////////// Public

        #region 버퍼 - Buffer

        /// <summary>
        /// 버퍼
        /// </summary>
        public byte[] Buffer { get; private set; }

        #endregion
        #region 위치 - Location

        /// <summary>
        /// 위치
        /// </summary>
        public long Location { get; private set; }

        #endregion
        #region 길이 - Length

        /// <summary>
        /// 길이
        /// </summary>
        public int Length { get; private set; }

        #endregion

        //////////////////////////////////////////////////////////////////////////////////////////////////// Constructor
        ////////////////////////////////////////////////////////////////////////////////////////// Public

        #region 생성자 - ReadWriteEventArgs(buffer, location, length)

        /// <summary>
        /// 생성자
        /// </summary>
        /// <param name="buffer">버퍼</param>
        /// <param name="location">위치</param>
        /// <param name="length">길이</param>
        public ReadWriteEventArgs(byte[] buffer, long location, int length)
        {
            Buffer   = buffer;
            Location = location;
            Length   = length;
        }

        #endregion
    }
}

 

300x250

 

▶ StorageStreamChangedEventArgs.cs

using System;

namespace TestLibrary
{
    /// <summary>
    /// 저장소 스트림 변경시 이벤트 인자
    /// </summary>
    internal class StorageStreamChangedEventArgs : EventArgs
    {
        //////////////////////////////////////////////////////////////////////////////////////////////////// Property
        ////////////////////////////////////////////////////////////////////////////////////////// Public

        #region 스트림 - Stream

        /// <summary>
        /// 스트림
        /// </summary>
        public StorageStream Stream { get; private set; }

        #endregion
        #region 변경 타입 - ChangeType

        /// <summary>
        /// 변경 타입
        /// </summary>
        public StorageStreamChangeType ChangeType { get; private set; }

        #endregion

        //////////////////////////////////////////////////////////////////////////////////////////////////// Constructor
        ////////////////////////////////////////////////////////////////////////////////////////// Public

        #region 생성자 - StorageStreamChangedEventArgs(stream, changeType)

        /// <summary>
        /// 생성자
        /// </summary>
        /// <param name="stream">스트림</param>
        /// <param name="changeType">변경 타입</param>
        public StorageStreamChangedEventArgs(StorageStream stream, StorageStreamChangeType changeType)
        {
            Stream     = stream;
            ChangeType = changeType;
        }

        #endregion
    }
}

 

▶ TransactionStateChangedEventArgs.cs

using System;

namespace TestLibrary
{
    /// <summary>
    /// 트랜잭션 상태 변경시 이벤트 인자
    /// </summary>
    public class TransactionStateChangedEventArgs : EventArgs
    {
        //////////////////////////////////////////////////////////////////////////////////////////////////// Property
        ////////////////////////////////////////////////////////////////////////////////////////// Public

        #region 트랜잭션 상태 변경 타입 - TransactionStateChangeType

        /// <summary>
        /// 트랜잭션 상태 변경 타입
        /// </summary>
        public TransactionStateChangeType TransactionStateChangeType { get; private set; }

        #endregion

        //////////////////////////////////////////////////////////////////////////////////////////////////// Constructor
        ////////////////////////////////////////////////////////////////////////////////////////// Public

        #region 생성자 - TransactionStateChangedEventArgs(transactionStateChangeType)

        /// <summary>
        /// 생성자
        /// </summary>
        /// <param name="transactionStateChangeType">트랜잭션 상태 변경 타입</param>
        public TransactionStateChangedEventArgs(TransactionStateChangeType transactionStateChangeType)
        {
            TransactionStateChangeType = transactionStateChangeType;
        }

        #endregion
    }
}

 

▶ InvalidSegmentException.cs

namespace TestLibrary
{
    /// <summary>
    /// 무효한 세그먼트 예외
    /// </summary>
    public class InvalidSegmentException : StorageException
    {
        //////////////////////////////////////////////////////////////////////////////////////////////////// Constructor
        ////////////////////////////////////////////////////////////////////////////////////////// Public

        #region 생성자 - InvalidSegmentException()

        /// <summary>
        /// 생성자
        /// </summary>
        public InvalidSegmentException() : base("Error loading segment")
        {
        }

        #endregion
    }
}

 

▶ InvalidStreamIDException.cs

namespace TestLibrary
{
    /// <summary>
    /// 무효한 스트림 ID 예외
    /// </summary>
    public class InvalidStreamIDException : StorageException
    {
        //////////////////////////////////////////////////////////////////////////////////////////////////// Constructor
        ////////////////////////////////////////////////////////////////////////////////////////// Public

        #region 생성자 - InvalidStreamIDException()

        /// <summary>
        /// 생성자
        /// </summary>
        public InvalidStreamIDException() : base("Invalid stream id. It must be larger or equal to zero.")
        {
        }

        #endregion
    }
}

 

▶ StorageClosedException.cs

namespace TestLibrary
{
    /// <summary>
    /// 저장소 폐쇄시 예외
    /// </summary>
    public class StorageClosedException : StorageException
    {
        //////////////////////////////////////////////////////////////////////////////////////////////////// Constructor
        ////////////////////////////////////////////////////////////////////////////////////////// Public

        #region 생성자 - StorageClosedException()

        /// <summary>
        /// 생성자
        /// </summary>
        public StorageClosedException() : base("Storage is closed")
        {
        }

        #endregion
    }
}

 

▶ StorageCorruptException.cs

namespace TestLibrary
{
    /// <summary>
    /// 저장소 오염 예외
    /// </summary>
    public class StorageCorruptException : StorageException
    {
        //////////////////////////////////////////////////////////////////////////////////////////////////// Constructor
        ////////////////////////////////////////////////////////////////////////////////////////// Public

        #region 생성자 - StorageCorruptException(message)

        /// <summary>
        /// 생성자
        /// </summary>
        /// <param name="message">메시지</param>
        public StorageCorruptException(string message) : base($"Storage is corrupt ({message})")
        {
        }

        #endregion
    }
}

 

▶ StorageException.cs

using System;

namespace TestLibrary
{
    /// <summary>
    /// 저장소 예외
    /// </summary>
    public class StorageException : Exception
    {
        //////////////////////////////////////////////////////////////////////////////////////////////////// Constructor
        ////////////////////////////////////////////////////////////////////////////////////////// Public

        #region 생성자 - StorageException(message)

        /// <summary>
        /// 생성자
        /// </summary>
        /// <param name="message">메시지</param>
        public StorageException(string message) : base(message)
        {
        }

        #endregion
    }
}

 

▶ StreamClosedException.cs

namespace TestLibrary
{
    /// <summary>
    /// 스트림 폐쇄시 예외
    /// </summary>
    public class StreamClosedException : StorageException
    {
        //////////////////////////////////////////////////////////////////////////////////////////////////// Constructor
        ////////////////////////////////////////////////////////////////////////////////////////// Public

        #region 생성자 - StreamClosedException()

        /// <summary>
        /// 생성자
        /// </summary>
        public StreamClosedException() : base("Stream is closed")
        {
        }

        #endregion
    }
}

 

▶ StreamExistsException.cs

namespace TestLibrary
{
    /// <summary>
    /// 스트림 존재시 예외
    /// </summary>
    public class StreamExistsException : StorageException
    {
        //////////////////////////////////////////////////////////////////////////////////////////////////// Constructor
        ////////////////////////////////////////////////////////////////////////////////////////// Public

        #region 생성자 - StreamExistsException()

        /// <summary>
        /// 생성자
        /// </summary>
        public StreamExistsException() : base("Stream already exists")
        {
        }

        #endregion
    }
}

 

▶ StreamNotFoundException.cs

namespace TestLibrary
{
    /// <summary>
    /// 스트림 찾기 실패시 예외
    /// </summary>
    public class StreamNotFoundException : StorageException
    {
        //////////////////////////////////////////////////////////////////////////////////////////////////// Constructor
        ////////////////////////////////////////////////////////////////////////////////////////// Public

        #region 생성자 - StreamNotFoundException()

        /// <summary>
        /// 생성자
        /// </summary>
        public StreamNotFoundException() : base("Stream could not be found")
        {
        }

        #endregion
    }
}

 

▶ UnableToOpenStorageException.cs

namespace TestLibrary
{
    /// <summary>
    /// 저장소 오픈 불가시 예외
    /// </summary>
    public class UnableToOpenStorageException : StorageException
    {
        //////////////////////////////////////////////////////////////////////////////////////////////////// Constructor
        ////////////////////////////////////////////////////////////////////////////////////////// Public

        #region 생성자 - UnableToOpenStorageException(reason)

        /// <summary>
        /// 생성자
        /// </summary>
        /// <param name="reason">사유</param>
        public UnableToOpenStorageException(string reason) : base($"Unable to open storage : {reason}")
        {
        }

        #endregion
    }
}

 

▶ UnknownErrorException.cs

namespace TestLibrary
{
    /// <summary>
    /// 알 수 없는 에러시 예외
    /// </summary>
    public class UnknownErrorException : StorageException
    {
        //////////////////////////////////////////////////////////////////////////////////////////////////// Constructor
        ////////////////////////////////////////////////////////////////////////////////////////// Public

        #region 생성자 - UnknownErrorException(message)

        /// <summary>
        /// 생성자
        /// </summary>
        /// <param name="message">메시지</param>
        public UnknownErrorException(string message) : base(message)
        {
        }

        #endregion
    }
}

 

▶ WritingOutsideOfTransactionException.cs

namespace TestLibrary
{
    /// <summary>
    /// 트랜잭션 외부 작성시 예외
    /// </summary>
    public class WritingOutsideOfTransactionException : StorageException
    {
        //////////////////////////////////////////////////////////////////////////////////////////////////// Constructor
        ////////////////////////////////////////////////////////////////////////////////////////// Public

        #region 생성자 - WritingOutsideOfTransactionException()

        /// <summary>
        /// 생성자
        /// </summary>
        public WritingOutsideOfTransactionException() : base("Cannot perform write operation outside the transaction")
        {
        }

        #endregion
    }
}

 

▶ SplitData.cs

namespace TestLibrary
{
    /// <summary>
    /// 분리 데이터
    /// </summary>
    internal struct SplitData
    {
        //////////////////////////////////////////////////////////////////////////////////////////////////// Field
        ////////////////////////////////////////////////////////////////////////////////////////// Public

        #region Field

        /// <summary>
        /// 분리 세그먼트 크기
        /// </summary>
        public long SplittedSegmentSize;

        /// <summary>
        /// 전체 세그먼트 가져오기 여부
        /// </summary>
        public bool TakeWholeSegment;

        #endregion
    }
}

 

▶ TransactionLogStreamMetadata.cs

using System;
using System.IO;

namespace TestLibrary
{
    /// <summary>
    /// 트랜잭션 로그 스트림 메타 데이터
    /// </summary>
    internal class TransactionLogStreamMetadata
    {
        //////////////////////////////////////////////////////////////////////////////////////////////////// Property
        ////////////////////////////////////////////////////////////////////////////////////////// Static
        //////////////////////////////////////////////////////////////////////////////// Public

        #region 구조체 크기 - StructureSize

        /// <summary>
        /// 구조체 크기
        /// </summary>
        public static int StructureSize
        {
            get
            {
                return 16 + 2 * sizeof(long) + sizeof(bool);
            }
        }

        #endregion

        ////////////////////////////////////////////////////////////////////////////////////////// Instance
        //////////////////////////////////////////////////////////////////////////////// Public

        #region 트랜잭션 ID - TransactionID

        /// <summary>
        /// 트랜잭션 ID
        /// </summary>
        public Guid TransactionID { get; set; }

        #endregion
        #region 스트림 길이 - StreamLength

        /// <summary>
        /// 스트림 길이
        /// </summary>
        public long StreamLength { get; set; }

        #endregion
        #region 세그먼트 수 - SegmentCount

        /// <summary>
        /// 세그먼트 수
        /// </summary>
        public long SegmentCount { get; set; }

        #endregion
        #region 트랜잭션 완료 여부 - TransactionCompleted

        /// <summary>
        /// 트랜잭션 완료 여부
        /// </summary>
        public bool TransactionCompleted { get; set; }

        #endregion

        //////////////////////////////////////////////////////////////////////////////////////////////////// Method
        ////////////////////////////////////////////////////////////////////////////////////////// Static
        //////////////////////////////////////////////////////////////////////////////// Public

        #region 로드하기 - Load(stream)

        /// <summary>
        /// 로드하기
        /// </summary>
        /// <param name="stream">스트림</param>
        /// <returns>트랜잭션 로그 스트림 메타 데이터</returns>
        public static TransactionLogStreamMetadata Load(Stream stream)
        {
            BinaryReader reader = new BinaryReader(stream);

            TransactionLogStreamMetadata metadata = new TransactionLogStreamMetadata
            {
                TransactionID        = new Guid(reader.ReadBytes(16)),
                StreamLength         = reader.ReadInt64(),
                SegmentCount         = reader.ReadInt64(),
                TransactionCompleted = reader.ReadBoolean()
            };

            return metadata;
        }

        #endregion

        ////////////////////////////////////////////////////////////////////////////////////////// Instance
        //////////////////////////////////////////////////////////////////////////////// Public

        #region 저장하기 - Save(stream)

        /// <summary>
        /// 저장하기
        /// </summary>
        /// <param name="stream">스트림</param>
        public void Save(Stream stream)
        {
            stream.Position = 0;

            BinaryWriter writer = new BinaryWriter(stream);

            writer.Write(TransactionID.ToByteArray());

            writer.Write(StreamLength);

            writer.Write(SegmentCount);

            writer.Write(TransactionCompleted);
        }

        #endregion
    }
}

 

▶ TransactionSegment.cs

namespace TestLibrary
{
    /// <summary>
    /// 트랜잭션 세그먼트
    /// </summary>
    public class TransactionSegment
    {
        //////////////////////////////////////////////////////////////////////////////////////////////////// Field
        ////////////////////////////////////////////////////////////////////////////////////////// Public

        #region Field

        /// <summary>
        /// 위치
        /// </summary>
        public long Location;

        /// <summary>
        /// 크기
        /// </summary>
        public long Size;

        #endregion

        //////////////////////////////////////////////////////////////////////////////////////////////////// Constructor
        ////////////////////////////////////////////////////////////////////////////////////////// Public

        #region 생성자 - TransactionSegment(location, size)

        /// <summary>
        /// 생성자
        /// </summary>
        /// <param name="location">위치</param>
        /// <param name="size">크기</param>
        public TransactionSegment(long location, long size)
        {
            Location = location;
            Size     = size;
        }

        #endregion
    }
}

 

▶ TransactionSegmentMetadata.cs

using System;
using System.IO;

namespace TestLibrary
{
    /// <summary>
    /// 트랜잭션 세그먼트 메타 데이터
    /// </summary>
    internal class TransactionSegmentMetadata
    {
        //////////////////////////////////////////////////////////////////////////////////////////////////// Field
        ////////////////////////////////////////////////////////////////////////////////////////// Private

        #region Field

        /// <summary>
        /// 트랜잭션
        /// </summary>
        private Guid transactionID;

        #endregion

        //////////////////////////////////////////////////////////////////////////////////////////////////// Property
        ////////////////////////////////////////////////////////////////////////////////////////// Public

        #region 트랜잭션 ID - TransactionID

        /// <summary>
        /// 트랜잭션 ID
        /// </summary>
        public Guid TransactionID
        {
            get
            {
                return this.transactionID;
            }
            set
            {
                this.transactionID = value;
            }
        }

        #endregion
        #region 위치 - Position

        /// <summary>
        /// 위치
        /// </summary>
        public long Position { get; set; }

        #endregion
        #region 크기 - Size

        /// <summary>
        /// 크기
        /// </summary>
        public int Size { get; set; }

        #endregion

        //////////////////////////////////////////////////////////////////////////////////////////////////// Method
        ////////////////////////////////////////////////////////////////////////////////////////// Static
        //////////////////////////////////////////////////////////////////////////////// Public

        #region 로드하기 - Load(stream)

        /// <summary>
        /// 로드하기
        /// </summary>
        /// <param name="stream">스트림</param>
        /// <returns>트랜잭션 세그먼트 메타 데이터</returns>
        public static TransactionSegmentMetadata Load(Stream stream)
        {
            BinaryReader reader = new BinaryReader(stream);

            TransactionSegmentMetadata metadata = new TransactionSegmentMetadata
            {
                TransactionID = new Guid(reader.ReadBytes(16)),
                Position      = reader.ReadInt64(),
                Size          = reader.ReadInt32()
            };

            return metadata;
        }

        #endregion

        ////////////////////////////////////////////////////////////////////////////////////////// Instance
        //////////////////////////////////////////////////////////////////////////////// Public

        #region 저장하기 - Save(stream)

        /// <summary>
        /// 저장하기
        /// </summary>
        /// <param name="stream">스트림</param>
        public void Save(Stream stream)
        {
            BinaryWriter writer = new BinaryWriter(stream);

            writer.Write(TransactionID.ToByteArray());

            writer.Write(Position);

            writer.Write(Size);
        }

        #endregion
    }
}

 

▶ TransactionStream.cs

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;

namespace TestLibrary
{
    /// <summary>
    /// 트랜잭션 스트림
    /// </summary>
    public class TransactionStream : Stream
    {
        //////////////////////////////////////////////////////////////////////////////////////////////////// Field
        ////////////////////////////////////////////////////////////////////////////////////////// Private

        #region Field

        /// <summary>
        /// 블럭 크기
        /// </summary>
        private long blockSize;

        /// <summary>
        /// 세그먼트 리스트
        /// </summary>
        private LinkedList<TransactionSegment> segmentList = new LinkedList<TransactionSegment>();

        /// <summary>
        /// 로그 스트림
        /// </summary>
        private Stream logStream;

        /// <summary>
        /// 로그 스트림 헤더
        /// </summary>
        private TransactionLogStreamMetadata logStreamHeader = null;

        /// <summary>
        /// 롤백 필요 여부
        /// </summary>
        private bool rollbackNeeded = false;

        /// <summary>
        /// 마스터 스트림
        /// </summary>
        private Stream masterStream;

        #endregion

        //////////////////////////////////////////////////////////////////////////////////////////////////// Property
        ////////////////////////////////////////////////////////////////////////////////////////// Public

        #region 마스터 스트림 - MasterStream

        /// <summary>
        /// 마스터 스트림
        /// </summary>
        public Stream MasterStream
        {
            get
            {
                return this.masterStream;
            }
        }

        #endregion
        #region 현재 트랜잭션 ID - CurrentTransactionID

        /// <summary>
        /// 현재 트랜잭션 ID
        /// </summary>
        public Guid? CurrentTransactionID
        {
            get
            {
                return this.logStreamHeader != null ? this.logStreamHeader.TransactionID : (Guid?)null;
            }
        }

        #endregion
        #region 읽기 가능 여부 - CanRead

        /// <summary>
        /// 읽기 가능 여부
        /// </summary>
        public override bool CanRead
        {
            get
            {
                return this.masterStream.CanRead;
            }
        }

        #endregion
        #region 탐색 가능 여부 - CanSeek

        /// <summary>
        /// 탐색 가능 여부
        /// </summary>
        public override bool CanSeek
        {
            get
            {
                return this.masterStream.CanSeek;
            }
        }

        #endregion
        #region 쓰기 가능 여부 - CanWrite

        /// <summary>
        /// 쓰기 가능 여부
        /// </summary>
        public override bool CanWrite
        {
            get
            {
                return this.masterStream.CanWrite;
            }
        }

        #endregion
        #region 길이 - Length

        /// <summary>
        /// 길이
        /// </summary>
        public override long Length
        {
            get
            {
                return this.masterStream.Length;
            }
        }

        #endregion
        #region 위치 - Position

        /// <summary>
        /// 위치
        /// </summary>
        public override long Position
        {
            get
            {
                return this.masterStream.Position;
            }
            set
            {
                this.masterStream.Position = value;
            }
        }

        #endregion

        //////////////////////////////////////////////////////////////////////////////////////////////////// Constructor
        ////////////////////////////////////////////////////////////////////////////////////////// Public

        #region 생성자 - TransactionStream(masterStream, logStream, blockSize)

        /// <summary>
        /// 생성자
        /// </summary>
        /// <param name="masterStream">마스터 스트림</param>
        /// <param name="logStream">로그 스트림</param>
        /// <param name="blockSize">블럭 크기</param>
        public TransactionStream(Stream masterStream, Stream logStream, long blockSize)
        {
            this.masterStream = masterStream;
            this.logStream    = logStream;
            this.blockSize    = blockSize;

            if(logStream != null && logStream.Length > TransactionLogStreamMetadata.StructureSize)
            {
                this.logStreamHeader = TransactionLogStreamMetadata.Load(logStream);

                this.rollbackNeeded = !this.logStreamHeader.TransactionCompleted;

                if(this.logStreamHeader.SegmentCount == 0 || this.logStreamHeader.TransactionCompleted)
                {
                    this.logStreamHeader = null;
                }
            }
        }

        #endregion

        //////////////////////////////////////////////////////////////////////////////////////////////////// Method
        ////////////////////////////////////////////////////////////////////////////////////////// Public

        #region 트랜잭션 시작하기 - BeginTransaction(initialSegmentEnumerable)

        /// <summary>
        /// 트랜잭션 시작하기
        /// </summary>
        /// <param name="initialSegmentEnumerable">초기 세그먼트 열거 가능형</param>
        /// <returns>트랜잭션 ID</returns>
        public Guid BeginTransaction(IEnumerable<TransactionSegment> initialSegmentEnumerable = null)
        {
            CheckState();

            if(this.logStream == null)
            {
                throw new InvalidOperationException("Log stream not specified");
            }

            if(this.logStreamHeader != null)
            {
                throw new InvalidOperationException("Unable to start new transaction while existing transaction is in progress");
            }

            this.segmentList.Clear();

            if(initialSegmentEnumerable != null)
            {
                IOrderedEnumerable<TransactionSegment> segmentOrderedEnumerable = initialSegmentEnumerable.OrderBy(x => x.Location);

                foreach(TransactionSegment segment in segmentOrderedEnumerable)
                {
                    this.segmentList.AddLast(segment);
                }
            }

            this.segmentList.AddLast(new TransactionSegment(this.masterStream.Length, long.MaxValue - this.masterStream.Length));

            CheckSegmentLinkedList();

            this.logStream.SetLength(0);

            this.logStream.Flush();

            this.logStreamHeader = new TransactionLogStreamMetadata
            {
                TransactionID        = Guid.NewGuid(),
                StreamLength         = this.masterStream.Length,
                SegmentCount         = 0,
                TransactionCompleted = false
            };

            this.logStreamHeader.Save(this.logStream);

            this.logStream.Flush();

            return this.logStreamHeader.TransactionID;
        }

        #endregion
        #region 트랜잭션 종료하기 - EndTransaction()

        /// <summary>
        /// 트랜잭션 종료하기
        /// </summary>
        public void EndTransaction()
        {
            CheckState();

            if(this.logStreamHeader != null)
            {
                this.masterStream.Flush();

                this.logStreamHeader.TransactionCompleted = true;

                this.logStreamHeader.Save(this.logStream);

                this.logStream.Flush();

                this.logStreamHeader = null;

                CheckSegmentLinkedList();
            }
        }

        #endregion
        #region 트랜잭션 롤백하기 - RollbackTransaction()

        /// <summary>
        /// 트랜잭션 롤백하기
        /// </summary>
        public void RollbackTransaction()
        {
            if(this.logStreamHeader != null)
            {
                if(this.logStreamHeader.TransactionCompleted)
                {
                    throw new InvalidOperationException("Can't rollback completed transaction");
                }

                this.logStream.Position = TransactionLogStreamMetadata.StructureSize;

                for(int i = 0; i < this.logStreamHeader.SegmentCount; i++)
                {
                    TransactionSegmentMetadata metadata = TransactionSegmentMetadata.Load(this.logStream);

                    if(!metadata.TransactionID.Equals(this.logStreamHeader.TransactionID))
                    {
                        throw new InvalidOperationException("Wrong segment found in transaction log");
                    }

                    this.masterStream.Position = metadata.Position;

                    CopyData(this.logStream, metadata.Size, this.masterStream);
                }

                if(this.logStreamHeader.StreamLength < this.masterStream.Length)
                {
                    this.masterStream.SetLength(this.logStreamHeader.StreamLength);
                }

                this.masterStream.Flush();

                this.logStream.SetLength(0);

                this.logStream.Flush();

                this.logStreamHeader = null;

                this.rollbackNeeded = false;
            }
        }

        #endregion

        #region 버퍼 비우기 - Flush()

        /// <summary>
        /// 버퍼 비우기
        /// </summary>
        public override void Flush()
        {
            CheckState();

            this.masterStream.Flush();
        }

        #endregion
        #region 탐색하기 - Seek(offset, origin)

        /// <summary>
        /// 탐색하기
        /// </summary>
        /// <param name="offset">오프셋</param>
        /// <param name="origin">탐색 원점</param>
        /// <returns>위치</returns>
        public override long Seek(long offset, SeekOrigin origin)
        {
            return this.masterStream.Seek(offset, origin);
        }

        #endregion
        #region 길이 설정하기 - SetLength(value)

        /// <summary>
        /// 길이 설정하기
        /// </summary>
        /// <param name="value">값</param>
        public override void SetLength(long value)
        {
            CheckState();

            if(this.logStreamHeader != null && value < this.masterStream.Length)
            {
                BackupSegment(value, Length - value);
            }

            this.masterStream.SetLength(value);
        }

        #endregion
        #region 읽기 - Read(buffer, offset, count)

        /// <summary>
        /// 읽기
        /// </summary>
        /// <param name="buffer">버퍼</param>
        /// <param name="offset">오프셋</param>
        /// <param name="count">카운트</param>
        /// <returns>읽은 바이트 수</returns>
        public override int Read(byte[] buffer, int offset, int count)
        {
            CheckState();

            return this.masterStream.Read(buffer, offset, count);
        }

        #endregion
        #region 쓰기 - Write(buffer, offset, count)

        /// <summary>
        /// 쓰기
        /// </summary>
        /// <param name="buffer">버퍼</param>
        /// <param name="offset">오프셋</param>
        /// <param name="count">카운트</param>
        public override void Write(byte[] buffer, int offset, int count)
        {
            CheckState();

            long position = this.masterStream.Position;

            if(this.logStreamHeader != null)
            {
                BackupSegment(this.masterStream.Position, count);
            }

            this.masterStream.Position = position;

            this.masterStream.Write(buffer, offset, count);
        }

        #endregion

        ////////////////////////////////////////////////////////////////////////////////////////// Private

        #region 상태 체크하기 - CheckState()

        /// <summary>
        /// 상태 체크하기
        /// </summary>
        private void CheckState()
        {
            if(this.rollbackNeeded)
            {
                throw new InvalidOperationException("Previous transaction did not complete. Rollback needed.");
            }
        }

        #endregion
        #region 현재 세그먼트 구하기 - GetCurrentSegment(position, startNode)

        /// <summary>
        /// 현재 세그먼트 구하기
        /// </summary>
        /// <param name="position">위치</param>
        /// <param name="startNode">시작 노드</param>
        /// <returns>현재 노드</returns>
        private LinkedListNode<TransactionSegment> GetCurrentSegment(long position, LinkedListNode<TransactionSegment> startNode)
        {
            LinkedListNode<TransactionSegment> node = startNode != null ? startNode : segmentList.First;

            while(node != null)
            {
                if(node.Value.Location + node.Value.Size > position)
                {
                    return node;
                }
                else
                {
                    node = node.Next;
                }
            }

            return null;
        }

        #endregion
        #region 세그먼트 연결 리스트 체크하기 - CheckSegmentLinkedList()

        /// <summary>
        /// 세그먼트 연결 리스트 체크하기
        /// </summary>
        private void CheckSegmentLinkedList()
        {
            LinkedListNode<TransactionSegment> node = this.segmentList.First;

            while(node != null)
            {
                LinkedListNode<TransactionSegment> nextNode = node.Next;

                if(nextNode != null && (nextNode.Value.Location <= node.Value.Location + node.Value.Size))
                {
                    node.Value.Size = Math.Max
                    (
                        node.Value.Location + node.Value.Size,
                        nextNode.Value.Location + nextNode.Value.Size
                    ) - node.Value.Location;

                    this.segmentList.Remove(nextNode);
                }
                else
                {
                    node = nextNode;
                }
            }
        }

        #endregion
        #region 반올림 블럭 수 구하기 - GetBlockCountRoundUp(byteSize)

        /// <summary>
        /// 반올림 블럭 수 구하기
        /// </summary>
        /// <param name="byteSize">바이트 크기</param>
        /// <returns>반올림 블럭 수</returns>
        private long GetBlockCountRoundUp(long byteSize)
        {
            long result = (byteSize / this.blockSize) * this.blockSize;

            return byteSize % this.blockSize > 0 ? result + this.blockSize : result;
        }

        #endregion
        #region 반내림 블럭 수 구하기 - GetBlockCountRoundDown(byteSize)

        /// <summary>
        /// 반내림 블럭 수 구하기
        /// </summary>
        /// <param name="byteSize">바이트 크기</param>
        /// <returns>반내림 블럭 수</returns>
        private long GetBlockCountRoundDown(long byteSize)
        {
            return (byteSize / this.blockSize) * this.blockSize;
        }

        #endregion
        #region 세그먼트 백업하기 - BackupSegment(location, size)

        /// <summary>
        /// 세그먼트 백업하기
        /// </summary>
        /// <param name="location">위치</param>
        /// <param name="size">크기</param>
        private void BackupSegment(long location, long size)
        {
            long endLocation = GetBlockCountRoundUp(location + size);

            location = GetBlockCountRoundDown(location);
            size     = endLocation - location;

            LinkedListNode<TransactionSegment> node = GetCurrentSegment(location, null);

            while(size > 0)
            {
                bool backedUpPosition = node != null ?
                    ((location >= node.Value.Location) && (location < node.Value.Location + node.Value.Size)) : false;

                long amount;

                if(backedUpPosition)
                {
                    amount = node != null ? Math.Min(node.Value.Location + node.Value.Size - location, size) : size;
                }
                else
                {
                    amount = node != null ? Math.Min(node.Value.Location - location, size) : size;

                    BackupData(location, (int)amount);

                    TransactionSegment newSegment = new TransactionSegment(location, amount);

                    if(node != null)
                    {
                        this.segmentList.AddBefore(node, newSegment);
                    }
                    else
                    {
                        this.segmentList.AddLast(newSegment);

                        node = this.segmentList.First;
                    }
                }

                size -= amount;

                location += amount;

                node = GetCurrentSegment(location, node);
            }
        }

        #endregion
        #region 데이터 백업하기 - BackupData(location, size)

        /// <summary>
        /// 데이터 백업하기
        /// </summary>
        /// <param name="location">위치</param>
        /// <param name="size">크기</param>
        private void BackupData(long location, int size)
        {
            this.logStream.Seek(0, SeekOrigin.End);

            TransactionSegmentMetadata metadata = new TransactionSegmentMetadata
            {
                TransactionID = this.logStreamHeader.TransactionID,
                Position      = location,
                Size          = size
            };

            long metadataPosition = this.logStream.Position;

            metadata.Save(this.logStream);

            this.masterStream.Position = location;

            int byteCountCopied = CopyData(this.masterStream, size, this.logStream);

            if(byteCountCopied != size)
            {
                this.logStream.Position = metadataPosition;

                metadata.Size = byteCountCopied;

                metadata.Save(this.logStream);
            }

            this.logStream.Flush();

            this.logStreamHeader.SegmentCount++;

            this.logStreamHeader.Save(this.logStream);

            this.logStream.Flush();
        }

        #endregion
        #region 데이터 복사하기 - CopyData(sourceStream, copySize, targetStream)

        /// <summary>
        /// 데이터 복사하기
        /// </summary>
        /// <param name="sourceStream">소스 스트림</param>
        /// <param name="copySize">복사 크기</param>
        /// <param name="targetStream">타겟 스트림</param>
        /// <returns>복사 바이트 수</returns>
        private int CopyData(Stream sourceStream, int copySize, Stream targetStream)
        {
            byte[] buffer = new byte[32763];

            int byteCountCopied = 0;

            while(copySize > 0)
            {
                int byteCountRead = sourceStream.Read(buffer, 0, Math.Min(buffer.Length, copySize));

                if(byteCountRead == 0)
                {
                    break;
                }

                targetStream.Write(buffer, 0, byteCountRead);

                copySize -= byteCountRead;

                byteCountCopied += byteCountRead;
            }

            return byteCountCopied;
        }

        #endregion
    }
}

 

▶ MasterStream.cs

using System;
using System.IO;

namespace TestLibrary
{
    /// <summary>
    /// 마스터 스트림
    /// </summary>
    internal class MasterStream : Stream
    {
        //////////////////////////////////////////////////////////////////////////////////////////////////// Event
        ////////////////////////////////////////////////////////////////////////////////////////// Public

        #region 데이터 쓰기 - DataWrite

        /// <summary>
        /// 데이터 쓰기
        /// </summary>
        public event EventHandler<ReadWriteEventArgs> DataWrite;

        #endregion
        #region 데이터 읽기 - DataRead

        /// <summary>
        /// 데이터 읽기
        /// </summary>
        public event EventHandler<ReadWriteEventArgs> DataRead;

        #endregion

        //////////////////////////////////////////////////////////////////////////////////////////////////// Field
        ////////////////////////////////////////////////////////////////////////////////////////// Private

        #region Field

        /// <summary>
        /// 스트림
        /// </summary>
        private Stream stream;

        /// <summary>
        /// 쓰기 버퍼 스트림
        /// </summary>
        private WriteBufferedStream bufferStream;

        /// <summary>
        /// 트랜잭션 여부
        /// </summary>
        private bool inTransaction = false;

        /// <summary>
        /// 트랜잭션 이용 가능 여부
        /// </summary>
        private bool transactionsEnabled = true;

        /// <summary>
        /// 작성 바이트 수
        /// </summary>
        private long byteCountWritten = 0;

        /// <summary>
        /// 읽기 바이트 수
        /// </summary>
        private long byteCountRead = 0;

        #endregion

        //////////////////////////////////////////////////////////////////////////////////////////////////// Property
        ////////////////////////////////////////////////////////////////////////////////////////// Public

        #region 작성 바이트 수 - ByteCountWritten

        /// <summary>
        /// 작성 바이트 수
        /// </summary>
        public long ByteCountWritten
        {
            get
            {
                return this.byteCountWritten;
            }
        }

        #endregion
        #region 읽기 바이트 수 - ByteCountRead

        /// <summary>
        /// 읽기 바이트 수
        /// </summary>
        public long ByteCountRead
        {
            get
            {
                return this.byteCountRead;
            }
        }

        #endregion
        #region 위치 - Position

        /// <summary>
        /// 위치
        /// </summary>
        public override long Position
        {
            get
            {
                return this.stream.Position;
            }
            set
            {
                this.stream.Position = value;
            }
        }

        #endregion
        #region 읽기 가능 여부 - CanRead

        /// <summary>
        /// 읽기 가능 여부
        /// </summary>
        public override bool CanRead
        {
            get
            {
                return this.stream.CanRead;
            }
        }

        #endregion
        #region 탐색 가능 여부 - CanSeek

        /// <summary>
        /// 탐색 가능 여부
        /// </summary>
        public override bool CanSeek
        {
            get
            {
                return this.stream.CanSeek;
            }
        }

        #endregion
        #region 쓰기 가능 여부 - CanWrite

        /// <summary>
        /// 쓰기 가능 여부
        /// </summary>
        public override bool CanWrite
        {
            get
            {
                return this.stream.CanWrite;
            }
        }

        #endregion
        #region 길이 - Length

        /// <summary>
        /// 길이
        /// </summary>
        public override long Length
        {
            get
            {
                return this.stream.Length;
            }
        }

        #endregion

        ////////////////////////////////////////////////////////////////////////////////////////// Internal

        #region 트랜잭션 이용 가능 여부 - TransactionsEnabled

        /// <summary>
        /// 트랜잭션 이용 가능 여부
        /// </summary>
        internal bool TransactionsEnabled
        {
            get
            {
                return this.transactionsEnabled;
            }
            set
            {
                this.transactionsEnabled = value;

                WriteBufferedStream bufferedStream = this.stream as WriteBufferedStream;

                if(bufferedStream != null)
                {
                    bufferedStream.BufferingEnabled = value;
                }
            }
        }

        #endregion

        //////////////////////////////////////////////////////////////////////////////////////////////////// Constructor
        ////////////////////////////////////////////////////////////////////////////////////////// Public

        #region 생성자 - MasterStream(stream, bufferData)

        /// <summary>
        /// 생성자
        /// </summary>
        /// <param name="stream">스트림</param>
        /// <param name="bufferData">데이터 버퍼링 여부</param>
        public MasterStream(Stream stream, bool bufferData)
        {
            if(stream == null)
            {
                throw new ArgumentNullException("stream");
            }

            if(bufferData)
            {
                this.bufferStream = new WriteBufferedStream(stream, 512);

                this.stream = this.bufferStream;
            }
            else
            {
                this.stream = stream;
            }
        }

        #endregion

        //////////////////////////////////////////////////////////////////////////////////////////////////// Method
        ////////////////////////////////////////////////////////////////////////////////////////// Public

        #region 닫기 - Close()

        /// <summary>
        /// 닫기
        /// </summary>
        public override void Close()
        {
            this.stream.Close();
        }

        #endregion
        #region 버퍼 비우기 - Flush()

        /// <summary>
        /// 버퍼 비우기
        /// </summary>
        public override void Flush()
        {
            if(this.bufferStream != null)
            {
                this.bufferStream.FlushBufferedData();
            }
            else
            {
                this.stream.Flush();
            }
        }

        #endregion
        #region 탐색하기 - Seek(offset, origin)

        /// <summary>
        /// 탐색하기
        /// </summary>
        /// <param name="offset">오프셋</param>
        /// <param name="origin">탐색 원점</param>
        /// <returns>위치</returns>
        public override long Seek(long offset, SeekOrigin origin)
        {
            return this.stream.Seek(offset, origin);
        }

        #endregion
        #region 길이 설정하기 - SetLength(value)

        /// <summary>
        /// 길이 설정하기
        /// </summary>
        /// <param name="value">값</param>
        public override void SetLength(long value)
        {
            this.stream.SetLength(value);
        }

        #endregion
        #region 읽기 - Read(buffer, offset, count)

        /// <summary>
        /// 읽기
        /// </summary>
        /// <param name="buffer">버퍼</param>
        /// <param name="offset">오프셋</param>
        /// <param name="count">카운트</param>
        /// <returns>읽은 바이트 수</returns>
        public override int Read(byte[] buffer, int offset, int count)
        {
            long position = this.stream.Position;

            int byteCountRead = this.stream.Read(buffer, offset, count);

            this.byteCountRead += byteCountRead;

            if(DataRead != null)
            {
                DataRead(this, new ReadWriteEventArgs(buffer, position, byteCountRead));
            }

            return byteCountRead;
        }

        #endregion
        #region 쓰기 - Write(buffer, offset, count)

        /// <summary>
        /// 쓰기
        /// </summary>
        /// <param name="buffer">버퍼</param>
        /// <param name="offset">오프셋</param>
        /// <param name="count">카운트</param>
        public override void Write(byte[] buffer, int offset, int count)
        {
            if(!this.inTransaction && this.transactionsEnabled)
            {
                throw new WritingOutsideOfTransactionException();
            }

            long position = this.stream.Position;

            this.byteCountWritten += count;

            this.stream.Write(buffer, offset, count);

            if(DataWrite != null)
            {
                DataWrite(this, new ReadWriteEventArgs(buffer, position, count));
            }
        }

        #endregion
        #region 트랜잭션 시작하기 - StartTransaction()

        /// <summary>
        /// 트랜잭션 시작하기
        /// </summary>
        public void StartTransaction()
        {
            if(this.inTransaction)
            {
                throw new InvalidOperationException("Stream is already in transaction");
            }

            this.inTransaction = true;
        }

        #endregion
        #region 트랜잭션 커밋하기 - CommitTransaction()

        /// <summary>
        /// 트랜잭션 커밋하기
        /// </summary>
        public void CommitTransaction()
        {
            if(!this.inTransaction)
            {
                throw new InvalidOperationException("Stream is not in transaction");
            }

            this.inTransaction = false;
        }

        #endregion
        #region 트랜잭션 롤백하기 - RollbackTransaction()

        /// <summary>
        /// 트랜잭션 롤백하기
        /// </summary>
        public void RollbackTransaction()
        {
            if(!this.inTransaction)
            {
                throw new InvalidOperationException("Stream is not in transaction");
            }

            WriteBufferedStream bufferedStream = this.stream as WriteBufferedStream;

            if(bufferedStream != null)
            {
                bufferedStream.DiscardBufferedData();
            }

            this.inTransaction = false;
        }

        #endregion
    }
}

 

▶ Segment.cs

using System;
using System.IO;

namespace TestLibrary
{
    /// <summary>
    /// 세그먼트
    /// </summary>
    internal class Segment
    {
        //////////////////////////////////////////////////////////////////////////////////////////////////// Field
        ////////////////////////////////////////////////////////////////////////////////////////// Private

        #region Field

        /// <summary>
        /// 수정 여부
        /// </summary>
        private bool isModified = true;

        /// <summary>
        /// 위치
        /// </summary>
        private long location;

        /// <summary>
        /// 크기
        /// </summary>
        private long size;

        /// <summary>
        /// 다음 위치
        /// </summary>
        private long? nextLocation;

        #endregion

        //////////////////////////////////////////////////////////////////////////////////////////////////// Property
        ////////////////////////////////////////////////////////////////////////////////////////// Static
        //////////////////////////////////////////////////////////////////////////////// Public

        #region 구조체 크기 - StructureSize

        /// <summary>
        /// 구조체 크기
        /// </summary>
        public static long StructureSize
        {
            get
            {
                return 20;
            }
        }

        #endregion

        ////////////////////////////////////////////////////////////////////////////////////////// Instance
        //////////////////////////////////////////////////////////////////////////////// Public

        #region 위치 - Location

        /// <summary>
        /// 위치
        /// </summary>
        public long Location
        {
            get
            {
                return this.location;
            }
            private set
            {
                if(this.location != value)
                {
                    this.location = value;

                    this.isModified = true;
                }
            }
        }

        #endregion
        #region 크기 - Size

        /// <summary>
        /// 크기
        /// </summary>
        public long Size
        {
            get
            {
                return this.size;
            }
            set
            {
                if(this.size != value)
                {
                    this.size = value;

                    this.isModified = true;
                }
            }
        }

        #endregion
        #region 다음 위치 - NextLocation

        /// <summary>
        /// 다음 위치
        /// </summary>
        public long? NextLocation
        {
            get
            {
                return this.nextLocation;
            }
            set
            {
                if(this.nextLocation != value)
                {
                    this.nextLocation = value;

                    this.isModified = true;
                }
            }
        }

        #endregion
        #region 데이터 영역 크기 - DataAreaSize

        /// <summary>
        /// 데이터 영역 크기
        /// </summary>
        public long DataAreaSize
        {
            get
            {
                return Size - StructureSize;
            }
        }

        #endregion
        #region 데이터 영역 시작 - DataAreaStart

        /// <summary>
        /// 데이터 영역 시작
        /// </summary>
        public long DataAreaStart
        {
            get
            {
                return Location + StructureSize;
            }
        }

        #endregion
        #region 데이터 영역 종료 - DataAreaEnd

        /// <summary>
        /// 데이터 영역 종료
        /// </summary>
        public long DataAreaEnd
        {
            get
            {
                return Location + Size;
            }
        }

        #endregion

        //////////////////////////////////////////////////////////////////////////////////////////////////// Constructor
        ////////////////////////////////////////////////////////////////////////////////////////// Private

        #region 생성자 - Segment()

        /// <summary>
        /// 생성자
        /// </summary>
        private Segment()
        {
        }

        #endregion

        //////////////////////////////////////////////////////////////////////////////////////////////////// Method
        ////////////////////////////////////////////////////////////////////////////////////////// Static
        //////////////////////////////////////////////////////////////////////////////// Public

        #region 로드하기 - Load(stream, location)

        /// <summary>
        /// 로드하기
        /// </summary>
        /// <param name="stream">스트림</param>
        /// <param name="location">위치</param>
        /// <returns>세그먼트</returns>
        public static Segment Load(Stream stream, long location)
        {
            stream.Seek(location, SeekOrigin.Begin);

            stream.Read(UtilityHelper.Buffer, 0, (int)Segment.StructureSize);

            UtilityHelper.BufferReader.BaseStream.Position = 0;

            long size = UtilityHelper.BufferReader.ReadInt64();

            long temporaryNextSegmentLocation = UtilityHelper.BufferReader.ReadInt64();

            int hash = UtilityHelper.BufferReader.ReadInt32();

            int hashCalculated = UtilityHelper.CalculateHash(size, temporaryNextSegmentLocation);

            if(hash != hashCalculated || size == 0)
            {
                throw new InvalidSegmentException();
            }

            return Segment.Create
            (
                location,
                size,
                temporaryNextSegmentLocation != 0 ? temporaryNextSegmentLocation : (long?)null
            );
        }

        #endregion
        
        ////////////////////////////////////////////////////////////////////////////////////////// Instance
        //////////////////////////////////////////////////////////////////////////////// Public

        #region 분리하기 - Split(sizeToRemove, splitAtEnd)

        /// <summary>
        /// 분리하기
        /// </summary>
        /// <param name="sizeToRemove">제거할 크기</param>
        /// <param name="splitAtEnd">세그먼트 끝에서 분리 여부</param>
        /// <returns>세그먼트</returns>
        public Segment Split(long sizeToRemove, bool splitAtEnd)
        {
            Segment segment;

            if(sizeToRemove > Size)
            {
                throw new InvalidOperationException("Unable to split because size to split is larger than the segment itself");
            }

            long newSize = this.Size - sizeToRemove;

            if(splitAtEnd)
            {
                segment = Segment.Create(Location + Size - sizeToRemove, sizeToRemove, null);    
            }
            else
            {
                segment = Segment.Create(Location, sizeToRemove, null);

                Location += sizeToRemove;
            }

            Size = newSize;

            return segment;
        }

        #endregion
        #region 병합하기 - Merge(segment)

        /// <summary>
        /// 병합하기
        /// </summary>
        /// <param name="segment">세그먼트</param>
        /// <returns>병합 세그먼트</returns>
        public Segment Merge(Segment segment)
        {
            Segment newSegment;

            if(Location + Size == segment.Location)
            {
                newSegment = Segment.Create(Location, Size + segment.Size, null);
            }
            else if(segment.Location + segment.Size == Location)
            {
                newSegment = Segment.Create(segment.Location, Size + segment.Size, null);
            }
            else
            {
                throw new InvalidOperationException("Unable to merge because segments doesn't touch each other");
            }

            return newSegment;
        }

        #endregion
        #region 생성하기 - Create(location, size, nextSegmentLocation)

        /// <summary>
        /// 생성하기
        /// </summary>
        /// <param name="location">위치</param>
        /// <param name="size">크기</param>
        /// <param name="nextSegmentLocation">다음 세그먼트 위치</param>
        /// <returns>생성 세그먼트</returns>
        public static Segment Create(long location, long size, long? nextSegmentLocation)
        {
            if(size <= 0)
            {
                throw new StorageException("Segment size can't be zero or less");
            }

            return new Segment
            {
                Location     = location,
                Size         = size,
                NextLocation = nextSegmentLocation
            };
        }

        #endregion
        #region 저장하기 - Save(stream)

        /// <summary>
        /// 저장하기
        /// </summary>
        /// <param name="stream">스트림</param>
        public void Save(Stream stream)
        {
            if(this.isModified)
            {
                UtilityHelper.BufferWriter.BaseStream.Position = 0;

                UtilityHelper.BufferWriter.Write(Size);

                long nextSegmentValue = NextLocation.HasValue ? NextLocation.Value : (long)0;

                UtilityHelper.BufferWriter.Write(nextSegmentValue);

                int hash = UtilityHelper.CalculateHash(Size, nextSegmentValue);

                UtilityHelper.BufferWriter.Write(hash);

                stream.Position = Location;

                stream.Write(UtilityHelper.Buffer, 0, (int)Segment.StructureSize);

                this.isModified = false;
            }
        }

        #endregion
    }
}

 

▶ SegmentExtent.cs

namespace TestLibrary
{
    /// <summary>
    /// 세그먼트 범위
    /// </summary>
    public class SegmentExtent
    {
        //////////////////////////////////////////////////////////////////////////////////////////////////// Field
        ////////////////////////////////////////////////////////////////////////////////////////// Public

        #region Field

        /// <summary>
        /// 위치
        /// </summary>
        public readonly long Location;

        /// <summary>
        /// 크기
        /// </summary>
        public readonly long Size;

        #endregion

        //////////////////////////////////////////////////////////////////////////////////////////////////// Constructor
        ////////////////////////////////////////////////////////////////////////////////////////// Public

        #region 생성자 - SegmentExtent(location, size)

        /// <summary>
        /// 생성자
        /// </summary>
        /// <param name="location">위치</param>
        /// <param name="size">크기</param>
        internal SegmentExtent(long location, long size)
        {
            Location = location;
            Size     = size;
        }

        #endregion
    }
}

 

▶ Storage.cs

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;

namespace TestLibrary
{
    /// <summary>
    /// 저장소
    /// </summary>
    public class Storage
    {
        //////////////////////////////////////////////////////////////////////////////////////////////////// 
        ////////////////////////////////////////////////////////////////////////////////////////// Private

        #region 트랜잭션 상태 변경 전 - TransactionStateChanging

        /// <summary>
        /// 트랜잭션 상태 변경 전
        /// </summary>
        public event EventHandler<TransactionStateChangedEventArgs> TransactionStateChanging;

        #endregion
        #region 트랜잭션 상태 변경 후 - TransactionStateChanged

        /// <summary>
        /// 트랜잭션 상태 변경 후
        /// </summary>
        public event EventHandler<TransactionStateChangedEventArgs> TransactionStateChanged;

        #endregion

        //////////////////////////////////////////////////////////////////////////////////////////////////// Field
        ////////////////////////////////////////////////////////////////////////////////////////// Private

        #region Field

        /// <summary>
        /// 마스터 리더
        /// </summary>
        private BinaryReader masterReader;

        /// <summary>
        /// 마스터 작성자
        /// </summary>
        private BinaryWriter masterWriter;

        /// <summary>
        /// 저장소 메타 데이터 스트림
        /// </summary>
        private StorageStream storageMetadataStream;

        /// <summary>
        /// 스트림 테이블 스트림 메타 데이터
        /// </summary>
        private StorageStreamMetadata streamTableStreamMetadata;

        /// <summary>
        /// 스트림 테이블 스트림
        /// </summary>
        private StorageStream streamTableStream;

        /// <summary>
        /// 트랜잭션 스트림
        /// </summary>
        private TransactionStream transactionStream;

        /// <summary>
        /// 트랜잭션 레벨
        /// </summary>
        private int transactionLevel = 0;

        /// <summary>
        /// 트랜잭션 중 변경시 스트림 리스트
        /// </summary>
        private List<StorageStream> streamListChangedDuringTransaction = new List<StorageStream>();

        /// <summary>
        /// 트랜잭션 중 생성시 스트림 ID 리스트
        /// </summary>
        private List<Guid> streamIDListCreatedDuringTransaction = new List<Guid>();

        /// <summary>
        /// 스트림 테이블
        /// </summary>
        private StreamTable streamTable;

        /// <summary>
        /// 오픈 스트림 딕셔너리
        /// </summary>
        private Dictionary<Guid, WeakReference<StorageStream>> openedStreamDictionary = new Dictionary<Guid, WeakReference<StorageStream>>();

        /// <summary>
        /// 폐쇄 여부
        /// </summary>
        private bool isClosed = false;

        /// <summary>
        /// 블럭 크기
        /// </summary>
        private const uint BLOCK_SIZE = 512;

        #endregion

        //////////////////////////////////////////////////////////////////////////////////////////////////// Property
        ////////////////////////////////////////////////////////////////////////////////////////// Public

        #region 저장소 메타 데이터 - StorageMetadata

        /// <summary>
        /// 저장소 메타 데이터
        /// </summary>
        public StorageMetadata StorageMetadata { get; private set; }

        #endregion
        #region 폐쇄 여부 - IsClosed 

        /// <summary>
        /// 폐쇄 여부
        /// </summary>
        public bool IsClosed
        {
            get
            {
                return this.isClosed;
            }
        }

        #endregion
        #region 트랜잭션 여부 - InTransaction

        /// <summary>
        /// 트랜잭션 여부
        /// </summary>
        public bool InTransaction
        {
            get
            {
                return this.transactionLevel > 0;
            }
        }

        #endregion
        #region 저장소 통계 - Statistics

        /// <summary>
        /// 저장소 통계
        /// </summary>
        public StorageStatistics Statistics { get; private set; }

        #endregion

        ////////////////////////////////////////////////////////////////////////////////////////// Internal

        #region 여유 공간 스트림 - FreeSpaceStream

        /// <summary>
        /// 여유 공간 스트림
        /// </summary>
        internal StorageStream FreeSpaceStream { get; private set; }

        #endregion
        #region 마스터 스트림 - MasterStream

        /// <summary>
        /// 마스터 스트림
        /// </summary>
        internal MasterStream MasterStream { get; private set; }

        #endregion
        #region 스트림 테이블 - StreamTable

        /// <summary>
        /// 스트림 테이블
        /// </summary>
        internal StreamTable StreamTable
        {
            get
            {
                return this.streamTable;
            }
        }

        #endregion
        #region 오픈 스트림 딕셔너리 카운트 - OpenedStreamDictionaryCount

        /// <summary>
        /// 오픈 스트림 딕셔너리 카운트
        /// </summary>
        internal int OpenedStreamDictionaryCount
        {
            get
            {
                return this.openedStreamDictionary.Count;
            }
        }

        #endregion
        #region 블럭 크기 - BlockSize

        /// <summary>
        /// 블럭 크기
        /// </summary>
        internal uint BlockSize
        {
            get
            {
                return BLOCK_SIZE;
            }
        }
        #endregion

        //////////////////////////////////////////////////////////////////////////////////////////////////// Constructor
        ////////////////////////////////////////////////////////////////////////////////////////// Public

        #region 생성자 - Storage(stream, transactionLogStream)

        /// <summary>
        /// 생성자
        /// </summary>
        /// <param name="stream">스트림</param>
        /// <param name="transactionLogStream">트랜잭션 로그 스트림</param>
        public Storage(Stream stream, Stream transactionLogStream)
        {
            Statistics = new StorageStatistics(this);

            if(stream.Length == 0)
            {
                CreateStorage(stream);
            }

            this.transactionStream = transactionLogStream != null ? new TransactionStream(stream, transactionLogStream, BLOCK_SIZE) : null;

            MasterStream = new MasterStream(transactionStream != null ? transactionStream : stream, false);

            this.masterReader = new BinaryReader(stream);
            this.masterWriter = new BinaryWriter(stream);

            OpenStorage(MasterStream);
        }

        #endregion
        #region 생성자 - Storage(filePath, transactionLogFilePath)

        /// <summary>
        /// 생성자
        /// </summary>
        /// <param name="filePath">파일 경로</param>
        /// <param name="transactionLogFilePath">트랜잭션 로그 파일 경로</param>
        public Storage(string filePath, string transactionLogFilePath) : this
        (
            File.Open(filePath, FileMode.OpenOrCreate),
            transactionLogFilePath != null ? File.Open(transactionLogFilePath, FileMode.OpenOrCreate) : null
        )
        {
        }

        #endregion

        //////////////////////////////////////////////////////////////////////////////////////////////////// Method
        ////////////////////////////////////////////////////////////////////////////////////////// Public

        #region 스트림 생성하기 - CreateStream(streamID, tag)

        /// <summary>
        /// 스트림 생성하기
        /// </summary>
        /// <param name="streamID">스트림 ID</param>
        /// <param name="tag">태그</param>
        /// <returns>저장소 스트림</returns>
        public StorageStream CreateStream(Guid streamID, int tag = 0)
        {
            CheckClosed();

            if(SystemStreamID.IsSystemStreamID(streamID))
            {
                throw new InvalidStreamIDException();
            }

            if(ContainsStream(streamID))
            {
                throw new StreamExistsException();
            }

            StartTransaction();

            try
            {
                this.streamTable.Add(streamID, tag);

                CommitTransaction();

                this.streamIDListCreatedDuringTransaction.Add(streamID);
            }
            catch
            {
                RollbackTransaction();

                throw;
            }

            return OpenStream(streamID);
        }

        #endregion
        #region 스트림 열기 - OpenStream(streamID)

        /// <summary>
        /// 스트림 열기
        /// </summary>
        /// <param name="streamID">스트림 ID</param>
        /// <returns>저장소 스트림</returns>
        public StorageStream OpenStream(Guid streamID)
        {
            CheckClosed();

            if(SystemStreamID.IsSystemStreamID(streamID))
            {
                throw new InvalidStreamIDException();
            }

            StartTransaction();

            try
            {
                StorageStream temporaryStream = null;

                WeakReference<StorageStream> storageStreamWeakReference;

                if(this.openedStreamDictionary.TryGetValue(streamID, out storageStreamWeakReference))
                {
                    if(!storageStreamWeakReference.TryGetTarget(out temporaryStream))
                    {
                        temporaryStream = null;

                        this.openedStreamDictionary.Remove(streamID);
                    }
                }

                if(temporaryStream == null)
                {
                    StorageStreamMetadata metadata = this.streamTable.GetStorageStreamMetadata(streamID);

                    if(metadata == null)
                    {
                        throw new StreamNotFoundException();
                    }

                    temporaryStream = new StorageStream(metadata, this);

                    this.openedStreamDictionary.Add(streamID, new WeakReference<StorageStream>(temporaryStream));
                }

                temporaryStream.Position = 0;

                CommitTransaction();

                return temporaryStream;
            }
            catch
            {
                RollbackTransaction();

                throw;
            }
        }

        #endregion
        #region 스트림 삭제하기 - DeleteStream(streamID)

        /// <summary>
        /// 스트림 삭제하기
        /// </summary>
        /// <param name="streamID">스트림 ID</param>
        public void DeleteStream(Guid streamID)
        {
            CheckClosed();

            if(SystemStreamID.IsSystemStreamID(streamID))
            {
                throw new InvalidStreamIDException();
            }

            StartTransaction();

            try
            {
                StorageStream temporaryStream = OpenStream(streamID);

                temporaryStream.SetLength(0);

                temporaryStream.Close();

                this.openedStreamDictionary.Remove(streamID);

                this.streamTable.Remove(streamID);

                temporaryStream = this.streamListChangedDuringTransaction.SingleOrDefault(x => x.StreamID == streamID);

                if(temporaryStream != null)
                {
                    this.streamListChangedDuringTransaction.Remove(temporaryStream);
                }

                if(this.streamIDListCreatedDuringTransaction.Contains(streamID))
                {
                    this.streamIDListCreatedDuringTransaction.Remove(streamID);
                }

                CommitTransaction();
            }
            catch
            {
                RollbackTransaction();

                throw;
            }
        }

        #endregion
        #region 스트림 포함 여부 구하기 - ContainsStream(streamID)

        /// <summary>
        /// 스트림 포함 여부 구하기
        /// </summary>
        /// <param name="streamID">스트림 ID</param>
        /// <returns>스트림 포함 여부</returns>
        public bool ContainsStream(Guid streamID)
        {
            CheckClosed();

            if(SystemStreamID.IsSystemStreamID(streamID))
            {
                throw new InvalidStreamIDException();
            }

            return streamTable.Contains(streamID);
        }

        #endregion
        #region 스트림 세그먼트 범위 리스트 구하기 - GetStreamSegmentExtentList(streamID)

        /// <summary>
        /// 스트림 세그먼트 범위 리스트 구하기
        /// </summary>
        /// <param name="streamID">스트림 ID</param>
        /// <returns>스트림 세그먼트 범위 리스트</returns>
        public List<SegmentExtent> GetStreamSegmentExtentList(Guid streamID)
        {
            CheckClosed();

            if(SystemStreamID.IsSystemStreamID(streamID))
            {
                throw new InvalidStreamIDException();
            }

            StorageStream stream = OpenStream(streamID);

            return stream.GetStreamSegmentExtentList();
        }

        #endregion
        #region 여유 공간 세그먼트 범위 리스트 구하기 - GetFreeSpaceSegmentExtentList()

        /// <summary>
        /// 여유 공간 세그먼트 범위 리스트 구하기
        /// </summary>
        /// <returns>여유 공간 세그먼트 범위 리스트</returns>
        public List<SegmentExtent> GetFreeSpaceSegmentExtentList()
        {
            CheckClosed();

            if(FreeSpaceStream != null)
            {
                return FreeSpaceStream.SegmentEnumerable.Select(x => new SegmentExtent(x.Location, x.Size)).ToList();
            }
            else
            {
                return new List<SegmentExtent>();
            }
        }

        #endregion
        #region 닫기 - Close()

        /// <summary>
        /// 닫기
        /// </summary>
        public void Close()
        {
            if(this.transactionLevel > 0)
            {
                RollbackTransactionInternal();

                throw new StorageException("Unable to close storage while transaction is pending");
            }

            if(!this.isClosed)
            {
                lock(this.openedStreamDictionary)
                {
                    RollbackTransaction();

                    MasterStream.Flush();

                    MasterStream.Close();

                    this.openedStreamDictionary.Clear();

                    this.streamListChangedDuringTransaction.Clear();

                    this.isClosed = true;
                }
            }
        }

        #endregion
        #region 스트림 ID 열거 가능형 구하기 - GetStreamIDEnumerable(tag)

        /// <summary>
        /// 스트림 ID 열거 가능형 구하기
        /// </summary>
        /// <param name="tag">태그</param>
        /// <returns>스트림 ID 열거 가능형</returns>
        public IEnumerable<Guid> GetStreamIDEnumerable(int? tag)
        {
            return streamTable.GetStorageStreamMetadataEnumerable()
                .Where(x => !SystemStreamID.IsSystemStreamID(x.StreamID))
                .Where(x => !tag.HasValue || x.Tag == tag.Value)
                .Select(x => x.StreamID)
                .ToList();
        }

        #endregion
        #region 스트림 ID 열거 가능형 구하기 - GetStreamIDEnumerable()

        /// <summary>
        /// 스트림 ID 열거 가능형 구하기
        /// </summary>
        /// <returns>스트림 ID 열거 가능형</returns>
        public IEnumerable<Guid> GetStreamIDEnumerable()
        {
            return GetStreamIDEnumerable(null);
        }

        #endregion
        #region 저장소 다듬기 - TrimStorage()

        /// <summary>
        /// 저장소 다듬기
        /// </summary>
        public void TrimStorage()
        {
            Segment lastSegment = FreeSpaceStream.SegmentEnumerable.SingleOrDefault(x => !x.NextLocation.HasValue);

            if(lastSegment != null)
            {
                MasterStream.SetLength(lastSegment.DataAreaStart);
            }
        }

        #endregion
        #region 트랜잭션 시작하기 - StartTransaction()

        /// <summary>
        /// 트랜잭션 시작하기
        /// </summary>
        public void StartTransaction()
        {
            try
            {
                CheckClosed();

                this.transactionLevel++;

                if(this.transactionLevel == 1)
                {
                    if(this.streamListChangedDuringTransaction.Count > 0)
                    {
                        throw new StorageException("At the begining of transaction there should be no changed streams");
                    }

                    FireTransactionChangingEvent(TransactionStateChangeType.Start);

                    MasterStream.StartTransaction();

                    if(this.transactionStream != null)
                    {
                        IEnumerable<TransactionSegment> enumerable = FreeSpaceStream != null ?
                            FreeSpaceStream.SegmentEnumerable.Select(x => new TransactionSegment(x.DataAreaStart, x.DataAreaSize)) :
                            null;

                        transactionStream.BeginTransaction(enumerable);
                    }
    
                    FireTransactionChangedEvent(TransactionStateChangeType.Start);
                }
            }
            catch
            {
                RollbackTransactionInternal();

                throw;
            }
        }

        #endregion
        #region 트랜잭션 커밋하기 - CommitTransaction()

        /// <summary>
        /// 트랜잭션 커밋하기
        /// </summary>
        public void CommitTransaction()
        {
            try
            {
                CheckClosed();

                if(this.transactionLevel == 1)
                {
                    FireTransactionChangingEvent(TransactionStateChangeType.Commit);

                    SaveChanges();

                    if(this.transactionStream != null)
                    {
                        this.transactionStream.EndTransaction();
                    }

                    this.streamIDListCreatedDuringTransaction.Clear();

                    MasterStream.Flush();

                    MasterStream.CommitTransaction();

                    Statistics.TransactionCountCommited++;
                }

                if(this.transactionLevel > 0)
                {
                    this.transactionLevel--;

                    if(this.transactionLevel == 0)
                    {
                        FireTransactionChangedEvent(TransactionStateChangeType.Commit);
                    }
                }
            }
            catch
            {
                RollbackTransactionInternal();

                throw;
            }
        }

        #endregion
        #region 트랜잭션 롤백하기 - RollbackTransaction()

        /// <summary>
        /// 트랜잭션 롤백하기
        /// </summary>
        public void RollbackTransaction()
        {
            CheckClosed();

            if(this.transactionStream != null)
            {
                FireTransactionChangingEvent(TransactionStateChangeType.Rollback);

                RollbackTransactionInternal();

                this.transactionLevel = 0;

                Statistics.TransactionCountRolledBack++;

                FireTransactionChangedEvent(TransactionStateChangeType.Rollback);
            }
            else
            {
                CommitTransaction();
            }
        }

        #endregion
        #region 저장소 자르기 - TruncateStorage()

        /// <summary>
        /// 저장소 자르기
        /// </summary>
        public void TruncateStorage()
        {
            throw new NotImplementedException();
        }

        #endregion

        ////////////////////////////////////////////////////////////////////////////////////////// Internal

        #region 스트림 변경시 처리하기 - StreamChanged(changeType, stream)

        /// <summary>
        /// 스트림 변경시 처리하기
        /// </summary>
        /// <param name="changeType">저장소 스트림 변경 타입</param>
        /// <param name="stream">저장소 스트림</param>
        internal void StreamChanged(StorageStreamChangeType changeType, StorageStream stream)
        {
            if(!SystemStreamID.IsSystemStreamID(stream.StreamID) || stream.StreamID == SystemStreamID.EmptySpace)
            {
                switch(changeType)
                {
                    case StorageStreamChangeType.SegmentsAndMetadata :

                        if(!this.streamListChangedDuringTransaction.Contains(stream))
                        {
                            this.streamListChangedDuringTransaction.Add(stream);
                        }

                        break;

                    case StorageStreamChangeType.Closing :

                        if(this.streamListChangedDuringTransaction.Contains(stream))
                        {
                            this.streamListChangedDuringTransaction.Remove(stream);
                        }

                        this.openedStreamDictionary.Remove(stream.StreamID);

                        break;
                }
            }
        }

        #endregion

        ////////////////////////////////////////////////////////////////////////////////////////// Private

        #region 트랜잭션 롤백하기 (내부용) - RollbackTransactionInternal()

        /// <summary>
        /// 트랜잭션 롤백하기 (내부용)
        /// </summary>
        private void RollbackTransactionInternal()
        {
            if(this.transactionLevel > 0)
            {
                lock(this.openedStreamDictionary)
                {
                    foreach(Guid streamID in this.streamIDListCreatedDuringTransaction)
                    {
                        WeakReference<StorageStream> streamWeakReference;

                        if(this.openedStreamDictionary.TryGetValue(streamID, out streamWeakReference))
                        {
                            StorageStream temporaryStream;

                            if(streamWeakReference.TryGetTarget(out temporaryStream))
                            {
                                if(temporaryStream != null)
                                {
                                    temporaryStream.CloseInternal();
                                }
                            }

                            this.openedStreamDictionary.Remove(streamID);
                        }
                    }

                    this.streamIDListCreatedDuringTransaction.Clear();

                    this.transactionStream.RollbackTransaction();

                    MasterStream.RollbackTransaction();

                    this.streamListChangedDuringTransaction.Clear();

                    this.streamTableStream.ReloadSegmentsOnRollback(this.streamTableStreamMetadata);

                    this.streamTable.RollbackTransaction();

                    foreach(WeakReference<StorageStream> item in this.openedStreamDictionary.Values.ToList())
                    {
                        StorageStream temporaryStream;

                        if(item.TryGetTarget(out temporaryStream))
                        {
                            if(this.streamTable.Contains(temporaryStream.StreamID))
                            {
                                StorageStreamMetadata metadata = this.streamTable.GetStorageStreamMetadata(temporaryStream.StreamID);

                                temporaryStream.ReloadSegmentsOnRollback(metadata);
                            }
                            else
                            {
                                temporaryStream.CloseInternal();
                            }
                        }
                    }

                    StorageStreamMetadata freeSpaceStreamMetadata = this.streamTable.GetStorageStreamMetadata(SystemStreamID.EmptySpace);

                    FreeSpaceStream.ReloadSegmentsOnRollback(freeSpaceStreamMetadata);
                }
            }
        }

        #endregion
        #region 저장소 생성하기 - CreateStorage(stream)

        /// <summary>
        /// 저장소 생성하기
        /// </summary>
        /// <param name="stream">저장소</param>
        private void CreateStorage(Stream stream)
        {
            MasterStream = new MasterStream(stream, false);

            Segment metadataStreamSegment = Segment.Create(0, BLOCK_SIZE, null);

            metadataStreamSegment.Save(stream);

            StorageStream metadataStream = new StorageStream
            (
                new StorageStreamMetadata(null)
                {
                    FirstSegmentPosition = 0,
                    InitializedLength    = BLOCK_SIZE - Segment.StructureSize,
                    Length               = BLOCK_SIZE - Segment.StructureSize,
                    StreamID             = SystemStreamID.StorageMetadata,
                    StreamTableIndex     = -1
                },
                this
            );

            StorageMetadata storageMetadata = new StorageMetadata("[Version 1.0]");

            storageMetadata.Save(metadataStream);

            metadataStream.Close();

            long streamTableSegmentSize = 1000 / ((int)BLOCK_SIZE / StorageStreamMetadata.StructureSize) * BLOCK_SIZE;

            Segment streamTableSegment = Segment.Create(BLOCK_SIZE, streamTableSegmentSize, null);

            stream.Position = metadataStreamSegment.DataAreaEnd;

            streamTableSegment.Save(stream);

            StorageStream streamTableStream = new StorageStream
            (
                new StorageStreamMetadata(null)
                {
                    FirstSegmentPosition = BLOCK_SIZE,
                    InitializedLength    = streamTableSegmentSize - Segment.StructureSize,
                    Length               = streamTableSegmentSize - Segment.StructureSize,
                    StreamID             = SystemStreamID.StreamTable,
                    StreamTableIndex     = -1
                },
                this
            );

            BinaryWriter writer = new BinaryWriter(streamTableStream);

            Segment emptyStreamSegment = Segment.Create
            (
                streamTableSegment.DataAreaEnd,
                long.MaxValue - streamTableSegment.DataAreaEnd,
                null
            );

            stream.Position = streamTableSegment.DataAreaEnd;

            emptyStreamSegment.Save(stream);

            StorageStreamMetadata emptySpaceStreamMetadata = new StorageStreamMetadata(streamTableStream)
            {
                FirstSegmentPosition = emptyStreamSegment.Location,
                InitializedLength    = emptyStreamSegment.DataAreaSize,
                Length               = emptyStreamSegment.DataAreaSize,
                StreamID             = SystemStreamID.EmptySpace,
                StreamTableIndex     = 0
            };

            emptySpaceStreamMetadata.Save();

            MasterStream = null;
        }

        #endregion
        #region 저장소 열기 - OpenStorage(masterStream)

        /// <summary>
        /// 저장소 열기
        /// </summary>
        /// <param name="masterStream">마스터 스트림</param>
        private void OpenStorage(Stream masterStream)
        {
            StartTransaction();

            try
            {
                this.storageMetadataStream = new StorageStream
                (
                    new StorageStreamMetadata(null)
                    {
                        FirstSegmentPosition = 0,
                        InitializedLength    = 512 - Segment.StructureSize,
                        Length               = 512 - Segment.StructureSize,
                        StreamID             = SystemStreamID.StorageMetadata,
                        StreamTableIndex     = -1
                    },
                    this
                );

                StorageMetadata = StorageMetadata.Load(this.storageMetadataStream);

                this.streamTableStreamMetadata = new StorageStreamMetadata(this.storageMetadataStream)
                {
                    FirstSegmentPosition = BLOCK_SIZE,
                    StreamID             = SystemStreamID.StreamTable,
                    StreamTableIndex     = -1
                };

                this.streamTableStream = new StorageStream(this.streamTableStreamMetadata, this);

                this.streamTable = new StreamTable(this.streamTableStream);

                StorageStreamMetadata freeSpaceStreamMetadata = this.streamTable.GetStorageStreamMetadata(SystemStreamID.EmptySpace);

                FreeSpaceStream = new StorageStream(freeSpaceStreamMetadata, this);

                CommitTransaction();
            }
            catch
            {
                RollbackTransaction();

                throw;
            }
        }

        #endregion
        #region 변경 사항 저장하기 - SaveChanges()

        /// <summary>
        /// 변경 사항 저장하기
        /// </summary>
        private void SaveChanges()
        {
            foreach(StorageStream stream in this.streamListChangedDuringTransaction)
            {
                stream.Save();
            }

            if(this.streamTable != null)
            {
                this.streamTable.SaveChanges();
            }

            this.streamListChangedDuringTransaction.Clear();
        }

        #endregion
        #region 폐쇄 여부 체크하기 - CheckClosed()

        /// <summary>
        /// 폐쇄 여부 체크하기
        /// </summary>
        private void CheckClosed()
        {
            if(this.isClosed)
            {
                throw new StorageClosedException();
            }
        }

        #endregion
        #region 트랜잭션 변경 전 이벤트 발생시키기 - FireTransactionChangingEvent(transactionStateChangeType)

        /// <summary>
        /// 트랜잭션 변경 전 이벤트 발생시키기
        /// </summary>
        /// <param name="transactionStateChangeType">트랜잭션 상태 변경 타입</param>
        private void FireTransactionChangingEvent(TransactionStateChangeType transactionStateChangeType)
        {
            if(TransactionStateChanging != null)
            {
                TransactionStateChanging(this, new TransactionStateChangedEventArgs(transactionStateChangeType));
            }
        }

        #endregion
        #region 트랜잭션 변경 후 이벤트 발생시키기 - FireTransactionChangedEvent(transactionStateChangeType)

        /// <summary>
        /// 트랜잭션 변경 후 이벤트 발생시키기
        /// </summary>
        /// <param name="transactionStateChangeType">트랜잭션 상태 변경 타입</param>
        private void FireTransactionChangedEvent(TransactionStateChangeType transactionStateChangeType)
        {
            if(TransactionStateChanged != null)
            {
                TransactionStateChanged(this, new TransactionStateChangedEventArgs(transactionStateChangeType));
            }
        }

        #endregion
    }
}

 

▶ StorageMetadata.cs

using System.IO;

namespace TestLibrary
{
    /// <summary>
    /// 저장소 메타 데이터
    /// </summary>
    public class StorageMetadata
    {
        //////////////////////////////////////////////////////////////////////////////////////////////////// Property
        ////////////////////////////////////////////////////////////////////////////////////////// Public

        #region 버전 - Version

        /// <summary>
        /// 버전
        /// </summary>
        public string Version { get; private set; }

        #endregion

        //////////////////////////////////////////////////////////////////////////////////////////////////// Constructor
        ////////////////////////////////////////////////////////////////////////////////////////// Internal

        #region 생성자 - StorageMetadata(version)

        /// <summary>
        /// 생성자
        /// </summary>
        /// <param name="version">버전</param>
        internal StorageMetadata(string version)
        {
            Version = version;
        }

        #endregion

        //////////////////////////////////////////////////////////////////////////////////////////////////// Method
        ////////////////////////////////////////////////////////////////////////////////////////// Static
        //////////////////////////////////////////////////////////////////////////////// Internal

        #region 로드하기 - Load(stream)

        /// <summary>
        /// 로드하기
        /// </summary>
        /// <param name="stream">스트림</param>
        /// <returns>저장소 메타 데이터</returns>
        internal static StorageMetadata Load(Stream stream)
        {
            stream.Position = 0;

            BinaryReader reader = new BinaryReader(stream);

            string version     = reader.ReadString();
            int hash           = reader.ReadInt32();
            int calculatedHash = UtilityHelper.CalculateHash(version);

            if(hash != calculatedHash)
            {
                throw new StorageCorruptException("Metadata has check failed");
            }

            StorageMetadata metadata = new StorageMetadata(version);

            return metadata;
        }

        #endregion

        ////////////////////////////////////////////////////////////////////////////////////////// Instance
        //////////////////////////////////////////////////////////////////////////////// Internal

        #region 저장하기 - Save(stream)

        /// <summary>
        /// 저장하기
        /// </summary>
        /// <param name="stream">스트림</param>
        internal void Save(Stream stream)
        {
            stream.Position = 0;

            BinaryWriter writer = new BinaryWriter(stream);

            writer.Write(Version);

            int hash = UtilityHelper.CalculateHash(Version);

            writer.Write(hash);
        }

        #endregion
    }
}

 

▶ StorageStatistics.cs

namespace TestLibrary
{
    /// <summary>
    /// 저장소 통계
    /// </summary>
    public sealed class StorageStatistics
    {
        //////////////////////////////////////////////////////////////////////////////////////////////////// Field
        ////////////////////////////////////////////////////////////////////////////////////////// Private

        #region Field

        /// <summary>
        /// 저장소
        /// </summary>
        private Storage storage;

        #endregion

        //////////////////////////////////////////////////////////////////////////////////////////////////// Property
        ////////////////////////////////////////////////////////////////////////////////////////// Public

        #region 저장소 크기 - StorageSize

        /// <summary>
        /// 저장소 크기
        /// </summary>
        public long StorageSize
        {
            get
            {
                return this.storage.MasterStream.Length;
            }
        }

        #endregion
        #region 읽기 바이트 수 - ByteCountRead

        /// <summary>
        /// 읽기 바이트 수
        /// </summary>
        public long ByteCountRead
        {
            get
            {
                return this.storage.MasterStream.ByteCountRead;
            }
        }

        #endregion
        #region 쓰기 바이트 수 - ByteCountWritten

        /// <summary>
        /// 쓰기 바이트 수
        /// </summary>
        public long ByteCountWritten
        {
            get
            {
                return this.storage.MasterStream.ByteCountWritten;
            }
        }

        #endregion
        #region 전체 스트림 수 - TotalStreamCount

        /// <summary>
        /// 전체 스트림 수
        /// </summary>
        public int TotalStreamCount
        {
            get
            {
                return this.storage.StreamTable.Count;
            }
        }

        #endregion
        #region 오픈 스트림 딕셔너리 수 - OpenedStreamDictionaryCount

        /// <summary>
        /// 오픈 스트림 딕셔너리 수
        /// </summary>
        public int OpenedStreamDictionaryCount
        {
            get
            {
                return this.storage.OpenedStreamDictionaryCount;
            }
        }

        #endregion
        #region 커밋 트랜잭션 수 - TransactionCountCommited

        /// <summary>
        /// 커밋 트랜잭션 수
        /// </summary>
        public long TransactionCountCommited { get; internal set; }

        #endregion
        #region 롤백 트랜잭션 수 - TransactionCountRolledBack

        /// <summary>
        /// 롤백 트랜잭션 수
        /// </summary>
        public long TransactionCountRolledBack { get; internal set; }

        #endregion

        //////////////////////////////////////////////////////////////////////////////////////////////////// Constructor
        ////////////////////////////////////////////////////////////////////////////////////////// Public

        #region 생성자 - StorageStatistics(storage)

        /// <summary>
        /// 생성자
        /// </summary>
        /// <param name="storage">저장소</param>
        public StorageStatistics(Storage storage)
        {
            this.storage = storage;
        }

        #endregion
    }
}

 

▶ StorageStream.cs

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;

namespace TestLibrary
{
    /// <summary>
    /// 저장소 스트림
    /// </summary>
    public class StorageStream : Stream
    {
        //////////////////////////////////////////////////////////////////////////////////////////////////// Field
        ////////////////////////////////////////////////////////////////////////////////////////// Private

        #region Field

        /// <summary>
        /// 세그먼트 리스트
        /// </summary>
        private LinkedList<Segment> segmentList = new LinkedList<Segment>();

        /// <summary>
        /// 저장소 스트림 메타 데이터
        /// </summary>
        private StorageStreamMetadata metadata;

        /// <summary>
        /// 저장소
        /// </summary>
        private Storage storage;

        /// <summary>
        /// 폐쇄 여부
        /// </summary>
        private bool isClosed = false;

        /// <summary>
        /// 변경 통지 여부
        /// </summary>
        private bool changeNotified = false;

        /// <summary>
        /// 위치
        /// </summary>
        private long position = 0;

        #endregion

        //////////////////////////////////////////////////////////////////////////////////////////////////// Property
        ////////////////////////////////////////////////////////////////////////////////////////// Public

        #region 위치 - Position

        /// <summary>
        /// 위치
        /// </summary>
        public override long Position
        {
            get
            {
                CheckClosed();

                return this.position;
            }
            set
            {
                CheckClosed();

                this.position = value;
            }
        }

        #endregion
        #region 스트림 ID - StreamID

        /// <summary>
        /// 스트림 ID
        /// </summary>
        public Guid StreamID
        {
            get
            {
                return this.metadata.StreamID;
            }
        }

        #endregion
        #region 태그 - Tag

        /// <summary>
        /// 태그
        /// </summary>
        public int Tag
        {
            get
            {
                return this.metadata.Tag;
            }
        }

        #endregion
        #region 읽기 가능 여부 - CanRead

        /// <summary>
        /// 읽기 가능 여부
        /// </summary>
        public override bool CanRead
        {
            get
            {
                return true;
            }
        }

        #endregion
        #region 탐색 가능 여부 - CanSeek

        /// <summary>
        /// 탐색 가능 여부
        /// </summary>
        public override bool CanSeek
        {
            get
            {
                return true;
            }
        }

        #endregion
        #region 쓰기 가능 여부 - CanWrite

        /// <summary>
        /// 쓰기 가능 여부
        /// </summary>
        public override bool CanWrite
        {
            get
            {
                return true;
            }
        }

        #endregion
        #region 길이 - Length

        /// <summary>
        /// 길이
        /// </summary>
        public override long Length
        {
            get
            {
                CheckClosed();

                return this.metadata.Length;
            }
        }

        #endregion

        ////////////////////////////////////////////////////////////////////////////////////////// Internal

        #region 세그먼트 열거 가능형 - SegmentEnumerable

        /// <summary>
        /// 세그먼트 열거 가능형
        /// </summary>
        internal IEnumerable<Segment> SegmentEnumerable
        {
            get
            {
                return this.segmentList;
            }
        }

        #endregion

        //////////////////////////////////////////////////////////////////////////////////////////////////// Constructor
        ////////////////////////////////////////////////////////////////////////////////////////// Internal

        #region 생성자 - StorageStream(metadata, storage)

        /// <summary>
        /// 생성자
        /// </summary>
        /// <param name="metadata">저장소 스트림 메타 데이터</param>
        /// <param name="storage">저장소</param>
        internal StorageStream(StorageStreamMetadata metadata, Storage storage)
        {
            if(storage == null)
            {
                throw new ArgumentNullException("storage");
            }

            this.storage = storage;

            LoadStream(metadata);
        }

        #endregion

        //////////////////////////////////////////////////////////////////////////////////////////////////// Method
        ////////////////////////////////////////////////////////////////////////////////////////// Public

        #region 버퍼 비우기 - Flush()

        /// <summary>
        /// 버퍼 비우기
        /// </summary>
        public override void Flush()
        {
            CheckClosed();
        }

        #endregion
        #region 읽기 - Read(buffer, offset, count)

        /// <summary>
        /// 읽기
        /// </summary>
        /// <param name="buffer">버퍼</param>
        /// <param name="offset">오프셋</param>
        /// <param name="count">카운트</param>
        /// <returns>읽은 바이트 수</returns>
        public override int Read(byte[] buffer, int offset, int count)
        {
            CheckClosed();

            return ReadWriteData(buffer, offset, count, false);
        }

        #endregion
        #region 쓰기 - Write(buffer, offset, count)

        /// <summary>
        /// 쓰기
        /// </summary>
        /// <param name="buffer">버퍼</param>
        /// <param name="offset">오프셋</param>
        /// <param name="count">카운트</param>
        public override void Write(byte[] buffer, int offset, int count)
        {
            CheckClosed();

            this.storage.StartTransaction();

            try
            {
                long newLength = this.position + count;

                if(newLength > this.metadata.Length)
                {
                    SetLength(newLength);
                }

                if(this.position > this.metadata.InitializedLength)
                {
                    long savedPosition = this.position;

                    int byteCountToWrite = (int)(this.position - this.metadata.InitializedLength);

                    this.position = this.metadata.InitializedLength;

                    while(byteCountToWrite > 0)
                    {
                        int byteCountWritten = Math.Min(UtilityHelper.EmptyBuffer.Length, byteCountToWrite);

                        Write(UtilityHelper.EmptyBuffer, 0, byteCountWritten);

                        byteCountToWrite -= byteCountWritten;
                    }
                }

                ReadWriteData(buffer, offset, count, true);

                this.storage.CommitTransaction();
            }
            catch
            {
                this.storage.RollbackTransaction();

                throw;
            }
        }

        #endregion
        #region 탐색하기 - Seek(offset, origin)

        /// <summary>
        /// 탐색하기
        /// </summary>
        /// <param name="offset">오프셋</param>
        /// <param name="origin">탐색 원점</param>
        /// <returns>위치</returns>
        public override long Seek(long offset, SeekOrigin origin)
        {
            CheckClosed();

            switch(origin)
            {
                case SeekOrigin.Begin :
                
                    this.position = offset;
                    
                    break;

                case SeekOrigin.Current :
                
                    this.position += offset;

                    break;

                case SeekOrigin.End :

                    this.position = this.metadata.Length - offset;

                    break;
            }

            return this.position;
        }

        #endregion
        #region 길이 설정하기 - SetLength(value)

        /// <summary>
        /// 길이 설정하기
        /// </summary>
        /// <param name="value">값</param>
        public override void SetLength(long value)
        {
            CheckClosed();

            if(value == this.metadata.Length)
            {
                return;
            }

            this.storage.StartTransaction();

            try
            {
                if(value > this.metadata.Length)
                {
                    List<Segment> list = this.storage.FreeSpaceStream.DeallocateSpace(value - this.metadata.Length);

                    AddSegmentList(list);
                }
                else if(value < this.metadata.Length)
                {
                    if(value == 0)
                    {
                        this.storage.FreeSpaceStream.AddSegmentList(this.segmentList.ToList());

                        this.segmentList.Clear();

                        this.metadata.Length = 0;

                        RebuildChain();
                    }
                    else
                    {
                        List<Segment> list = DeallocateSpace(this.metadata.Length - value);

                        this.storage.FreeSpaceStream.AddSegmentList(list);
                    }
                }

                if(StreamID == SystemStreamID.StreamTable)
                {
                    this.metadata.Length = this.segmentList.Sum(x => x.DataAreaSize);
                }
                else
                {
                    this.metadata.Length = value;
                }

                this.storage.CommitTransaction();
            }
            catch
            {
                this.storage.RollbackTransaction();

                throw;
            }
        }

        #endregion
        #region 닫기 - Close()

        /// <summary>
        /// 닫기
        /// </summary>
        public override void Close()
        {
            this.storage.StartTransaction();

            try
            {
                if(!this.isClosed)
                {
                    Save();

                    CloseInternal();
                }

                this.storage.CommitTransaction();
            }
            catch
            {
                this.storage.RollbackTransaction();

                throw;
            }
        }

        #endregion

        ////////////////////////////////////////////////////////////////////////////////////////// Internal

        #region 저장하기 - Save()

        /// <summary>
        /// 저장하기
        /// </summary>
        internal void Save()
        {
            CheckClosed();

            foreach(Segment segment in this.segmentList)
            {
                segment.Save(this.storage.MasterStream);
            }

            this.metadata.Save();

            this.changeNotified = false;
        }

        #endregion
        #region 스트림 세그먼트 범위 리스트 구하기 - GetStreamSegmentExtentList()

        /// <summary>
        /// 스트림 세그먼트 범위 리스트 구하기
        /// </summary>
        /// <returns>스트림 세그먼트 범위 리스트</returns>
        internal List<SegmentExtent> GetStreamSegmentExtentList()
        {
            return this.segmentList.Select(x => new SegmentExtent(x.Location, x.Size)).ToList();
        }

        #endregion
        #region 닫기 (내부용) - CloseInternal()

        /// <summary>
        /// 닫기 (내부용)
        /// </summary>
        internal void CloseInternal()
        {
            FireStreamChangedEvent(StorageStreamChangeType.Closing);

            this.segmentList.Clear();

            this.isClosed = true;
            
        }

        #endregion
        #region 롤백시 세그먼트 리로드하기 - ReloadSegmentsOnRollback(metadata)

        /// <summary>
        /// 롤백시 세그먼트 리로드하기
        /// </summary>
        /// <param name="metadata">저장소 스트림 메타 데이터</param>
        internal void ReloadSegmentsOnRollback(StorageStreamMetadata metadata)
        {
            CheckClosed();

            this.changeNotified = false;

            LoadStream(metadata);
        }

        #endregion

        ////////////////////////////////////////////////////////////////////////////////////////// Private

        #region 폐쇄 여부 체크하기 - CheckClosed()

        /// <summary>
        /// 폐쇄 여부 체크하기
        /// </summary>
        private void CheckClosed()
        {
            if(this.isClosed)
            {
                throw new StreamClosedException();
            }
        }

        #endregion
        #region 스트림 로드하기 - LoadStream(metadata)

        /// <summary>
        /// 스트림 로드하기
        /// </summary>
        /// <param name="metadata">저장소 스트림 메타 데이터</param>
        private void LoadStream(StorageStreamMetadata metadata)
        {
            this.metadata = metadata;

            long? segmentPosition = metadata.FirstSegmentPosition;

            this.segmentList.Clear();

            while(segmentPosition.HasValue)
            {
                Segment segment = Segment.Load(this.storage.MasterStream, segmentPosition.Value);

                this.segmentList.AddLast(segment);

                segmentPosition = segment.NextLocation;
            }

            if(metadata.StreamID == SystemStreamID.StreamTable)
            {
                metadata.Length = this.segmentList.Sum(x => x.DataAreaSize);

                metadata.InitializedLength = metadata.Length;
            }
        }

        #endregion
        #region 스트림 변경시 이벤트 발생시키기 - FireStreamChangedEvent(changeType)

        /// <summary>
        /// 스트림 변경시 이벤트 발생시키기
        /// </summary>
        /// <param name="changeType">저장소 스트림 변경 타입</param>
        private void FireStreamChangedEvent(StorageStreamChangeType changeType)
        {
            switch(changeType)
            {
                case StorageStreamChangeType.SegmentsAndMetadata :

                    if(!this.changeNotified)
                    {
                        this.changeNotified = true;

                        this.storage.StreamChanged(changeType, this);
                    }

                    break;

                case StorageStreamChangeType.Closing :

                    this.storage.StreamChanged(changeType, this);

                    break;
            }
        }

        #endregion
        #region 데이터 읽기/쓰기 - ReadWriteData(buffer, offset, count, doWrite)

        /// <summary>
        /// 데이터 읽기/쓰기
        /// </summary>
        /// <param name="buffer">버퍼</param>
        /// <param name="offset">오프셋</param>
        /// <param name="count">카운트</param>
        /// <param name="doWrite">쓰기 여부</param>
        /// <returns>바이트 수</returns>
        private int ReadWriteData(byte[] buffer, int offset, int count, bool doWrite)
        {
            count = Math.Min(buffer.Length, count);

            if(!doWrite)
            {
                count = (int)Math.Min(count, this.metadata.Length - this.position);
            }

            int  realCount         = doWrite ? count : (int)Math.Min(count, this.metadata.InitializedLength - this.position);
            int  fillCount         = count - realCount;
            long positionInSegment = this.position;
            bool canReadOrWrite    = false;
            int  bufferOffset      = 0;

            LinkedListNode<Segment> node = this.segmentList.First;

            while(realCount > 0)
            {
                if(canReadOrWrite)
                {
                    this.storage.MasterStream.Position = node.Value.DataAreaStart + positionInSegment;

                    int byteCountToReadOrWrite = Math.Min
                    (
                        realCount,
                        (int)(node.Value.DataAreaEnd - (node.Value.DataAreaStart + positionInSegment))
                    );

                    if(doWrite)
                    {
                        this.storage.MasterStream.Write(buffer, bufferOffset, byteCountToReadOrWrite);
                    }
                    else
                    {
                        this.storage.MasterStream.Read(buffer, bufferOffset, byteCountToReadOrWrite);
                    }

                    realCount -= byteCountToReadOrWrite;

                    bufferOffset += byteCountToReadOrWrite;

                    node = node.Next;

                    positionInSegment = 0;
                }
                else
                {
                    if(positionInSegment < node.Value.DataAreaSize)
                    {
                        canReadOrWrite = true;
                    }
                    else
                    {
                        positionInSegment -= node.Value.DataAreaSize;

                        node = node.Next;
                    }
                }
            }

            if(!doWrite)
            {
                while(fillCount > 0)
                {
                    int byteCountToCopy = Math.Min(fillCount, UtilityHelper.EmptyBuffer.Length);

                    Array.Copy(UtilityHelper.EmptyBuffer, 0, buffer, bufferOffset, byteCountToCopy);

                    fillCount -= byteCountToCopy;
                }
            }

            position += count;

            if(doWrite && position > this.metadata.InitializedLength)
            {
                this.metadata.InitializedLength = position;

                FireStreamChangedEvent(StorageStreamChangeType.SegmentsAndMetadata);
            }

            return count;
        }

        #endregion
        #region 분리 세그먼트 크기 계산하기 - CalculateSplittedSegmentSize(segmentToSplit, amountToRemove, splitAtEnd, blockSize)

        /// <summary>
        /// 분리 세그먼트 크기 계산하기
        /// </summary>
        /// <param name="segmentToSplit">분리될 세그먼트</param>
        /// <param name="amountToRemove">제거할 양</param>
        /// <param name="splitAtEnd">세기먼트 끝에서 분리 여부</param>
        /// <param name="blockSize">블럭 크기</param>
        /// <returns>분리 데이터</returns>
        private SplitData CalculateSplittedSegmentSize(Segment segmentToSplit, long amountToRemove, bool splitAtEnd, long blockSize)
        {
            long newSegmentSize = splitAtEnd ? amountToRemove - Segment.StructureSize : amountToRemove + Segment.StructureSize;

            bool isRounded = newSegmentSize % blockSize == 0;

            newSegmentSize = (newSegmentSize / blockSize) * blockSize;

            if(!splitAtEnd && !isRounded)
            {
                newSegmentSize += blockSize;
            }

            long leftoverSegmentSize = segmentToSplit.Size - newSegmentSize;

            if(leftoverSegmentSize < blockSize)
            {
                return new SplitData
                {
                    SplittedSegmentSize = segmentToSplit.Size,
                    TakeWholeSegment    = true
                };
            }
            else
            {
                return new SplitData
                {
                    SplittedSegmentSize = newSegmentSize,
                    TakeWholeSegment    = false
                };
            }
        }

        #endregion
        #region 체인 재구축하기 - RebuildChain()

        /// <summary>
        /// 체인 재구축하기
        /// </summary>
        private void RebuildChain()
        {
            LinkedListNode<Segment> node = this.segmentList.First;

            this.metadata.FirstSegmentPosition = node != null ? node.Value.Location : (long?)null;

            while(node != null)
            {
                if(node.Next != null && node.Value.DataAreaEnd == node.Next.Value.Location)
                {
                    node.Value.Size += node.Next.Value.Size;

                    this.segmentList.Remove(node.Next);
                }
                else
                {
                    node.Value.NextLocation = node.Next != null ? node.Next.Value.Location : (long?)null;

                    node = node.Next;
                }
            }

            FireStreamChangedEvent(StorageStreamChangeType.SegmentsAndMetadata);
        }

        #endregion
        #region 공간 할당 취소하기 - DeallocateSpace(amount)

        /// <summary>
        /// 공간 할당 취소하기
        /// </summary>
        /// <param name="amount">양</param>
        /// <returns>세그먼트 리스트</returns>
        private List<Segment> DeallocateSpace(long amount)
        {
            List<Segment> list = new List<Segment>();
            
            if(amount > Length)
            {
                amount = Length;
            }

            if(this.metadata.StreamID == SystemStreamID.EmptySpace)
            {
                LinkedListNode<Segment> node     = this.segmentList.First;
                LinkedListNode<Segment> nextNode = node.Next;
                
                while(amount > 0)
                {
                    SplitData splitData = CalculateSplittedSegmentSize(node.Value, amount, false, this.storage.BlockSize);
                    
                    if(splitData.TakeWholeSegment)
                    {
                        list.Add(node.Value);

                        amount -= node.Value.DataAreaSize;

                        this.segmentList.Remove(node);

                        node = nextNode;

                        nextNode = node != null ? node.Next : null;
                    }
                    else
                    {
                        Segment splitSegment = node.Value.Split(splitData.SplittedSegmentSize, false);

                        amount -= splitSegment.DataAreaSize;

                        list.Add(splitSegment);
                    }
                }
            }
            else
            {
                LinkedListNode<Segment> node         = this.segmentList.Last;
                LinkedListNode<Segment> previousNode = node.Previous;

                while(amount > 0)
                {
                    SplitData splitData = CalculateSplittedSegmentSize(node.Value, amount, true, this.storage.BlockSize);

                    if(splitData.SplittedSegmentSize == 0)
                    {
                        break;
                    }

                    if(splitData.TakeWholeSegment)
                    {
                        list.Add(node.Value);

                        amount -= node.Value.DataAreaSize;

                        this.segmentList.Remove(node);

                        node = previousNode;

                        previousNode = node != null ? node.Previous : null;
                    }
                    else
                    {
                        Segment splitSegment = node.Value.Split(splitData.SplittedSegmentSize, true);

                        amount -= splitSegment.DataAreaSize;

                        list.Add(splitSegment);
                    }
                }
            }

            RebuildChain();

            return list;
        }

        #endregion
        #region 세그먼트 리스트 추가하기 - AddSegmentList(sourceList)

        /// <summary>
        /// 세그먼트 리스트 추가하기
        /// </summary>
        private void AddSegmentList(List<Segment> sourceList)
        {
            if(this.metadata.StreamID == SystemStreamID.EmptySpace)
            {
                sourceList = sourceList.OrderBy(x => x.Location).ToList();

                LinkedListNode<Segment> node = this.segmentList.First;

                int listIndex = 0;

                while(listIndex < sourceList.Count)
                {
                    if(node != null)
                    {
                        if(sourceList[listIndex].Location < node.Value.Location)
                        {
                            this.segmentList.AddBefore(node, sourceList[listIndex]);

                            listIndex++;
                        }
                        else
                        {
                            node = node.Next;
                        }
                    }
                    else
                    {
                        this.segmentList.AddLast(sourceList[listIndex]);

                        listIndex++;
                    }
                }
            }
            else
            {
                sourceList.ForEach(x => this.segmentList.AddLast(x));
            }

            RebuildChain();
        }

        #endregion
    }
}

 

▶ StorageStreamMetadata.cs

using System;
using System.IO;

namespace TestLibrary
{
    /// <summary>
    /// 저장소 스트림 메타 데이터
    /// </summary>
    internal class StorageStreamMetadata
    {
        //////////////////////////////////////////////////////////////////////////////////////////////////// Field
        ////////////////////////////////////////////////////////////////////////////////////////// Private

        #region Field

        /// <summary>
        /// 소유자 스트림
        /// </summary>
        private Stream ownerStream;

        #endregion

        //////////////////////////////////////////////////////////////////////////////////////////////////// Property
        ////////////////////////////////////////////////////////////////////////////////////////// Static
        //////////////////////////////////////////////////////////////////////////////// Public

        #region 구조체 크기 - StructureSize

        /// <summary>
        /// 구조체 크기
        /// </summary>
        public static int StructureSize
        {
            get
            {
                return 48;
            }
        }

        #endregion

        ////////////////////////////////////////////////////////////////////////////////////////// Instance
        //////////////////////////////////////////////////////////////////////////////// Public

        #region 초기화된 길이 - InitializedLength

        /// <summary>
        /// 초기화된 길이
        /// </summary>
        public long InitializedLength { get; set; }

        #endregion
        #region 스트림 길이 - Length

        /// <summary>
        /// 스트림 길이
        /// </summary>
        public long Length { get; set; }

        #endregion
        #region 스트림 ID - StreamID

        /// <summary>
        /// 스트림 ID
        /// </summary>
        public Guid StreamID { get; set; }

        #endregion
        #region 첫번째 세그먼트 위치 - FirstSegmentPosition

        /// <summary>
        /// 첫번째 세그먼트 위치
        /// </summary>
        public long? FirstSegmentPosition { get; set; }

        #endregion
        #region 태그 - Tag

        /// <summary>
        /// 태그
        /// </summary>
        public int Tag { get; set; }

        #endregion
        #region 스트림 테이블 인덱스 - StreamTableIndex

        /// <summary>
        /// 스트림 테이블 인덱스
        /// </summary>
        public int StreamTableIndex { get; set; }

        #endregion

        //////////////////////////////////////////////////////////////////////////////////////////////////// Constructor
        ////////////////////////////////////////////////////////////////////////////////////////// Internal

        #region 생성자 - StorageStreamMetadata(ownerStream)

        /// <summary>
        /// 생성자
        /// </summary>
        /// <param name="ownerStream">소유자 스트림</param>
        internal StorageStreamMetadata(Stream ownerStream)
        {
            this.ownerStream = ownerStream;
        }

        #endregion

        //////////////////////////////////////////////////////////////////////////////////////////////////// Method
        ////////////////////////////////////////////////////////////////////////////////////////// Static
        //////////////////////////////////////////////////////////////////////////////// Public

        #region 로드하기 - Load(streamTableStream, streamTableIndex)

        /// <summary>
        /// 로드하기
        /// </summary>
        /// <param name="streamTableStream">스트림 테이블 스트림</param>
        /// <param name="streamTableIndex">스트림 테이블 인덱스</param>
        /// <returns>저장소 스트림 메타 데이터</returns>
        public static StorageStreamMetadata Load(Stream streamTableStream, int streamTableIndex)
        {
            streamTableStream.Position = streamTableIndex * StorageStreamMetadata.StructureSize;

            streamTableStream.Read(UtilityHelper.Buffer, 0, StorageStreamMetadata.StructureSize);

            UtilityHelper.BufferReader.BaseStream.Position = 0;

            StorageStreamMetadata metadata = new StorageStreamMetadata(streamTableStream);

            metadata.StreamTableIndex  = streamTableIndex;
            metadata.StreamID          = new Guid(UtilityHelper.BufferReader.ReadBytes(16));
            metadata.Length            = UtilityHelper.BufferReader.ReadInt64();
            metadata.InitializedLength = UtilityHelper.BufferReader.ReadInt64();

            long firstSegmentPosition = UtilityHelper.BufferReader.ReadInt64();

            metadata.FirstSegmentPosition = firstSegmentPosition != 0 ? firstSegmentPosition : (long?)null;
            metadata.Tag                  = UtilityHelper.BufferReader.ReadInt32();

            int hash = UtilityHelper.BufferReader.ReadInt32();

            int calculatedHash = UtilityHelper.CalculateHash
            (
                metadata.StreamID,
                metadata.Length,
                metadata.InitializedLength,
                firstSegmentPosition
            );

            if(hash != calculatedHash)
            {
                throw new StorageException("Error loading stream metadata");
            }

            return metadata;
        }

        #endregion

        ////////////////////////////////////////////////////////////////////////////////////////// Instance
        //////////////////////////////////////////////////////////////////////////////// Public

        #region 저장하기 - Save()

        /// <summary>
        /// 저장하기
        /// </summary>
        public void Save()
        {
            if(this.ownerStream != null && StreamTableIndex != -1)
            {
                UtilityHelper.BufferWriter.Seek(0, SeekOrigin.Begin);

                UtilityHelper.BufferWriter.Write(StreamID.ToByteArray());
                UtilityHelper.BufferWriter.Write(Length);
                UtilityHelper.BufferWriter.Write(InitializedLength);

                long firstSegmentPosition = FirstSegmentPosition.HasValue ? FirstSegmentPosition.Value : (long)0;

                UtilityHelper.BufferWriter.Write(firstSegmentPosition);

                UtilityHelper.BufferWriter.Write(Tag);

                int hash = UtilityHelper.CalculateHash(StreamID, Length, InitializedLength, firstSegmentPosition);

                UtilityHelper.BufferWriter.Write(hash);

                this.ownerStream.Position = StreamTableIndex * StorageStreamMetadata.StructureSize;

                this.ownerStream.Write(UtilityHelper.Buffer, 0, StorageStreamMetadata.StructureSize);
            }
        }

        #endregion
    }
}

 

▶ StreamTable.cs

using System;
using System.Collections.Generic;
using System.IO;

namespace TestLibrary
{
    /// <summary>
    /// 스트림 테이블
    /// </summary>
    internal class StreamTable
    {
        //////////////////////////////////////////////////////////////////////////////////////////////////// Field
        ////////////////////////////////////////////////////////////////////////////////////////// Private

        #region Field

        /// <summary>
        /// 항목 딕셔너리
        /// </summary>
        private Dictionary<Guid, int> itemDictionary = new Dictionary<Guid, int>();

        /// <summary>
        /// 스트림 테이블 맵
        /// </summary>
        private StreamTableMap map = new StreamTableMap();

        /// <summary>
        /// 저장소 스트림
        /// </summary>
        private StorageStream stream;

        /// <summary>
        /// 이진 리더기
        /// </summary>
        private BinaryReader reader;

        /// <summary>
        /// 이진 작성자
        /// </summary>
        private BinaryWriter writer;

        /// <summary>
        /// 트랜잭션 중 추가된 엔트리 딕셔너리
        /// </summary>
        private Dictionary<Guid, StorageStreamMetadata> entryDictionaryAddedInTransaction = new Dictionary<Guid, StorageStreamMetadata>();

        #endregion

        //////////////////////////////////////////////////////////////////////////////////////////////////// Property
        ////////////////////////////////////////////////////////////////////////////////////////// Public

        #region 카운트 - Count

        /// <summary>
        /// 카운트
        /// </summary>
        public int Count
        {
            get
            {
                return this.itemDictionary.Count;
            }
        }

        #endregion

        //////////////////////////////////////////////////////////////////////////////////////////////////// Constructor
        ////////////////////////////////////////////////////////////////////////////////////////// Public

        #region 생성자 - StreamTable(stream)

        /// <summary>
        /// 생성자
        /// </summary>
        /// <param name="stream">저장소 스트림</param>
        public StreamTable(StorageStream stream)
        {
            this.stream = stream;

            this.reader = new BinaryReader(stream);
            this.writer = new BinaryWriter(stream);

            LoadStreamTable();
        }

        #endregion

        //////////////////////////////////////////////////////////////////////////////////////////////////// Method
        ////////////////////////////////////////////////////////////////////////////////////////// Public

        #region 저장소 스트림 메타 데이터 구하기 - GetStorageStreamMetadata(streamID)

        /// <summary>
        /// 저장소 스트림 메타 데이터 구하기
        /// </summary>
        /// <param name="streamID">스트림 ID</param>
        /// <returns>저장소 스트림 메타 데이터</returns>
        public StorageStreamMetadata GetStorageStreamMetadata(Guid streamID)
        {
            StorageStreamMetadata result = null;

            int index;

            if(!this.entryDictionaryAddedInTransaction.TryGetValue(streamID, out result))
            {
                if(this.itemDictionary.TryGetValue(streamID, out index))
                {
                    result = StorageStreamMetadata.Load(this.stream, index);
                }
            }

            return result;
        }

        #endregion
        #region 저장소 스트림 메타 데이터 열거 가능형 구하기 - GetStorageStreamMetadataEnumerable()

        /// <summary>
        /// 저장소 스트림 메타 데이터 열거 가능형 구하기
        /// </summary>
        /// <returns>저장소 스트림 메타 데이터 열거 가능형</returns>
        public IEnumerable<StorageStreamMetadata> GetStorageStreamMetadataEnumerable()
        {
            StorageStreamMetadata metadata;

            foreach(StorageStreamMetadata entry in this.entryDictionaryAddedInTransaction.Values)
            {
                yield return entry;
            }

            for(int i = 0; i < this.stream.Length / StorageStreamMetadata.StructureSize; i++)
            {
                metadata = StorageStreamMetadata.Load(this.stream, i);

                if(metadata.StreamID != Guid.Empty)
                {
                    yield return metadata;
                }
            }
        }

        #endregion
        #region 추가하기 - Add(streamID, tag)

        /// <summary>
        /// 추가하기
        /// </summary>
        /// <param name="streamID">스트림 ID</param>
        /// <param name="tag">태그</param>
        /// <returns>저장소 스트림 메타 데이터</returns>
        public StorageStreamMetadata Add(Guid streamID, int tag = 0)
        {
            int index = this.map.FindFirstEmptyEntry();

            long streamPosition = index * StorageStreamMetadata.StructureSize;

            if((streamPosition + StorageStreamMetadata.StructureSize) > this.stream.Length)
            {
                int count = (int)this.stream.Length / StorageStreamMetadata.StructureSize;

                count += Math.Min(Math.Max((int)(count * 1.5), 512), 50000);

                long oldLength = this.stream.Length;

                this.stream.SetLength(count * StorageStreamMetadata.StructureSize);

                long byteCountToWrite = this.stream.Length - oldLength;

                this.stream.Position = oldLength;

                while(byteCountToWrite > 0)
                {
                    int amount = (int)Math.Min(byteCountToWrite, UtilityHelper.EmptyBuffer.Length);

                    this.stream.Write(UtilityHelper.EmptyBuffer, 0, amount);

                    byteCountToWrite -= amount;
                }

                this.stream.Save();
            }

            StorageStreamMetadata metadata = new StorageStreamMetadata(this.stream)
            {
                FirstSegmentPosition = null,
                StreamID             = streamID,
                Tag                  = tag,
                StreamTableIndex     = index
            };

            this.entryDictionaryAddedInTransaction.Add(streamID, metadata);

            this.map.Set(index, true);

            this.itemDictionary.Add(streamID, index);

            return metadata;
        }

        #endregion
        #region 제거하기 - Remove(streamID)

        /// <summary>
        /// 제거하기
        /// </summary>
        /// <param name="streamID">스트림 ID</param>
        public void Remove(Guid streamID)
        {
            int index = this.itemDictionary[streamID];

            if(this.entryDictionaryAddedInTransaction.ContainsKey(streamID))
            {
                this.entryDictionaryAddedInTransaction.Remove(streamID);
            }
            else
            {
                this.stream.Position = index * StorageStreamMetadata.StructureSize;

                byte[] buffer = new byte[48];

                this.writer.Write(buffer, 0, buffer.Length);
            }

            this.map.Set(index, false);

            this.itemDictionary.Remove(streamID);
        }

        #endregion
        #region 포함 여부 구하기 - Contains(streamID)

        /// <summary>
        /// 포함 여부 구하기
        /// </summary>
        /// <param name="streamID">스트림 ID</param>
        /// <returns>포함 여부</returns>
        public bool Contains(Guid streamID)
        {
            return this.itemDictionary.ContainsKey(streamID);
        }

        #endregion
        #region 트랜잭션 롤백하기 - RollbackTransaction()

        /// <summary>
        /// 트랜잭션 롤백하기
        /// </summary>
        public void RollbackTransaction()
        {
            this.entryDictionaryAddedInTransaction.Clear();

            LoadStreamTable();
        }

        #endregion
        #region 변경 사항 저장하기 - SaveChanges()

        /// <summary>
        /// 변경 사항 저장하기
        /// </summary>
        public void SaveChanges()
        {
            foreach(StorageStreamMetadata entry in this.entryDictionaryAddedInTransaction.Values)
            {
                entry.Save();
            }

            this.entryDictionaryAddedInTransaction.Clear();
        }

        #endregion

        ////////////////////////////////////////////////////////////////////////////////////////// Private

        #region 스트림 테이블 로드하기 - LoadStreamTable()

        /// <summary>
        /// 스트림 테이블 로드하기
        /// </summary>
        private void LoadStreamTable()
        {
            StorageStreamMetadata metadata;

            this.stream.Position = 0;

            int index = 0;

            this.map.Clear();
            
            this.itemDictionary.Clear();

            int count = (int)this.stream.Length / StorageStreamMetadata.StructureSize;

            for(int i = 0; i < count; i++)
            {
                metadata = StorageStreamMetadata.Load(this.stream, i);

                if(metadata.StreamID != Guid.Empty)
                {
                    this.map.Set(index, true);

                    this.itemDictionary.Add(metadata.StreamID, index);
                }

                index++;
            }
        }

        #endregion
    }
}

 

▶ StreamTableMap.cs

using System.Collections.Generic;

namespace TestLibrary
{
    /// <summary>
    /// 스트림 테이블 맵
    /// </summary>
    public class StreamTableMap
    {
        //////////////////////////////////////////////////////////////////////////////////////////////////// Field
        ////////////////////////////////////////////////////////////////////////////////////////// Private

        #region Field

        /// <summary>
        /// 엔트리 리스트
        /// </summary>
        private List<int> entryList = new List<int>();

        /// <summary>
        /// 여류 엔트리 리스트
        /// </summary>
        private LinkedList<int> freeEntryList = new LinkedList<int>();

        /// <summary>
        /// 카운트
        /// </summary>
        private int count = 0;

        #endregion

        //////////////////////////////////////////////////////////////////////////////////////////////////// Method
        ////////////////////////////////////////////////////////////////////////////////////////// Public

        #region 구하기 - Get(index)

        /// <summary>
        /// 구하기
        /// </summary>
        /// <param name="index">인덱스</param>
        /// <returns>값</returns>
        public bool Get(int index)
        {
            if(index > this.count - 1)
            {
                return false;
            }

            int entryIndex  = index / 32;
            int entryOffset = index % 32;

            int value = this.entryList[entryIndex];
            int mask  = 1 << entryOffset;

            bool result = (value & mask) == mask;

            return result;
        }

        #endregion
        #region 설정하기 - Set(index, newValue)

        /// <summary>
        /// 설정하기
        /// </summary>
        /// <param name="index">인덱스</param>
        /// <param name="newValue">신규 값</param>
        public void Set(int index, bool newValue)
        {
            if(index > this.count - 1)
            {
                while(index > this.count - 1)
                {
                    int amount = 12500;

                    this.count += amount;

                    this.entryList.Capacity += amount;

                    for(int i = 0; i < amount; i++)
                    {
                        this.entryList.Add(0);
                    }
                }
            }

            int entryIndex  = index / 32;
            int entryOffset = index % 32;

            int value = this.entryList[entryIndex];
            int mask  = 1 << entryOffset;

            if(newValue)
            {
                value = value | mask;
            }
            else
            {
                int maskWithAllBitsSet = -1;

                mask = maskWithAllBitsSet ^ mask;

                value = value & mask;
            }

            this.entryList[entryIndex] = value;
        }

        #endregion
        #region 지우기 - Clear()

        /// <summary>
        /// 지우기
        /// </summary>
        public void Clear()
        {
            this.entryList.Clear();

            this.freeEntryList.Clear();

            this.count = 0;
        }

        #endregion
        #region 첫번째 빈 엔트리 찾기 - FindFirstEmptyEntry()

        /// <summary>
        /// 첫번째 빈 엔트리 찾기
        /// </summary>
        /// <returns>인덱스</returns>
        public int FindFirstEmptyEntry()
        {
            LinkedListNode<int> node = this.freeEntryList.First;

            while(node != null)
            {
                LinkedListNode<int> nextNode = node.Next;

                if(this.entryList[node.Value] != -1)
                {
                    break;
                }
                else
                {
                    this.freeEntryList.Remove(node);
                }

                node = nextNode;
            }

            if(node == null)
            {
                FillFreeEntryList();

                node = this.freeEntryList.First;
            }

            if(node != null)
            {
                int index = node.Value;

                for(int bitIndex = 0; bitIndex < 32; bitIndex++)
                {
                    int mask = 1 << bitIndex;

                    int maskWithAllBitsSet = -1;

                    mask = maskWithAllBitsSet ^ mask;

                    if((this.entryList[index] & mask) == this.entryList[index])
                    {
                        return index * 32 + bitIndex;
                    }
                }
            }

            return this.count;
        }

        #endregion

        ////////////////////////////////////////////////////////////////////////////////////////// Private

        #region 여유 엔트리 리스트 채우기 - FillFreeEntryList()

        /// <summary>
        /// 여유 엔트리 리스트 채우기
        /// </summary>
        private void FillFreeEntryList()
        {
            for(int i = 0; i < this.entryList.Count; i++)
            {
                if(this.freeEntryList.Count >= 20)
                {
                    break;
                }

                if(this.entryList[i] != -1)
                {
                    this.freeEntryList.AddLast(i);
                }
            }
        }

        #endregion
    }
}

 

▶ SystemStreamID.cs

using System;

namespace TestLibrary
{
    /// <summary>
    /// 시스템 스트림 ID
    /// </summary>
    internal static class SystemStreamID
    {
        //////////////////////////////////////////////////////////////////////////////////////////////////// Field
        ////////////////////////////////////////////////////////////////////////////////////////// Static
        //////////////////////////////////////////////////////////////////////////////// Public

        #region Field

        /// <summary>
        /// 저장소 메타 데이터
        /// </summary>
        public static Guid StorageMetadata = new Guid(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1);

        /// <summary>
        /// 빈 공간
        /// </summary>
        public static Guid EmptySpace = new Guid(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2);

        /// <summary>
        /// 스트림 테이블
        /// </summary>
        public static Guid StreamTable = new Guid(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3);

        #endregion

        //////////////////////////////////////////////////////////////////////////////////////////////////// Method
        ////////////////////////////////////////////////////////////////////////////////////////// Static
        //////////////////////////////////////////////////////////////////////////////// Public

        #region 시스템 스트림 ID 여부 구하기 - IsSystemStreamID(streamID)

        /// <summary>
        /// 시스템 스트림 ID 여부 구하기
        /// </summary>
        /// <param name="streamID">스트림 ID</param>
        /// <returns>시스템 스트림 ID 여부</returns>
        public static bool IsSystemStreamID(Guid streamID)
        {
            return streamID == StorageMetadata || streamID == EmptySpace || streamID == StreamTable;
        }

        #endregion
    }
}

 

▶ UtilityHelper.cs

using System.IO;

namespace TestLibrary
{
    /// <summary>
    /// 유틸리티 헬퍼
    /// </summary>
    internal static class UtilityHelper
    {
        //////////////////////////////////////////////////////////////////////////////////////////////////// Field
        ////////////////////////////////////////////////////////////////////////////////////////// Static
        //////////////////////////////////////////////////////////////////////////////// Public

        #region Field

        /// <summary>
        /// 빈 버퍼
        /// </summary>
        public readonly static byte[] EmptyBuffer = new byte[32768];

        /// <summary>
        /// 버퍼
        /// </summary>
        public static byte[] Buffer;

        #endregion

        //////////////////////////////////////////////////////////////////////////////// Private

        #region Field

        /// <summary>
        /// 버퍼 스트림
        /// </summary>
        private static MemoryStream _bufferStream;

        #endregion

        //////////////////////////////////////////////////////////////////////////////////////////////////// Property
        ////////////////////////////////////////////////////////////////////////////////////////// Static
        //////////////////////////////////////////////////////////////////////////////// Public

        #region 버퍼 리더 - BufferReader

        /// <summary>
        /// 버퍼 리더
        /// </summary>
        public static BinaryReader BufferReader { get; private set; }

        #endregion
        #region 버퍼 작성자 - BufferWriter

        /// <summary>
        /// 버퍼 작성자
        /// </summary>
        public static BinaryWriter BufferWriter { get; private set; }

        #endregion

        //////////////////////////////////////////////////////////////////////////////////////////////////// Constructor
        ////////////////////////////////////////////////////////////////////////////////////////// Static

        #region 생성자 - UtilityHelper()

        /// <summary>
        /// 생성자
        /// </summary>
        static UtilityHelper()
        {
            Buffer = new byte[128];

            _bufferStream = new MemoryStream(Buffer);

            BufferReader = new BinaryReader(_bufferStream);
            BufferWriter = new BinaryWriter(_bufferStream);
        }

        #endregion

        //////////////////////////////////////////////////////////////////////////////////////////////////// Method
        ////////////////////////////////////////////////////////////////////////////////////////// Static
        //////////////////////////////////////////////////////////////////////////////// Public

        #region 해시 계산하기 - CalculateHash(valueArray)

        /// <summary>
        /// 해시 계산하기
        /// </summary>
        /// <param name="valueArray"></param>
        /// <returns>해시</returns>
        public static int CalculateHash(params object[] valueArray)
        {
            int hash = 0;

            foreach(object value in valueArray)
            {
                hash ^= value.GetHashCode();
            }

            return hash;
        }

        #endregion
    }
}

 

▶ WriteBufferedStream.cs

using System;
using System.Collections.Generic;
using System.IO;

namespace TestLibrary
{
    /// <summary>
    /// 쓰기 버퍼링 스트림
    /// </summary>
    public class WriteBufferedStream : Stream
    {
        //////////////////////////////////////////////////////////////////////////////////////////////////// Class
        ////////////////////////////////////////////////////////////////////////////////////////// Private

        #region 블럭 - Block

        /// <summary>
        /// 블럭
        /// </summary>
        private class Block
        {
            //////////////////////////////////////////////////////////////////////////////////////////////////// Field
            ////////////////////////////////////////////////////////////////////////////////////////// Public

            #region Field 

            /// <summary>
            /// 데이터
            /// </summary>
            public byte[] Data;

            /// <summary>
            /// 위치
            /// </summary>
            public long Position;

            #endregion

            //////////////////////////////////////////////////////////////////////////////////////////////////// Property
            ////////////////////////////////////////////////////////////////////////////////////////// Public

            #region 종료 위치 - EndPosition

            /// <summary>
            /// 종료 위치
            /// </summary>
            public long EndPosition
            {
                get
                {
                    return Position + Data.Length;
                }
            }

            #endregion
            #region 수정 여부 - IsModified

            /// <summary>
            /// 수정 여부
            /// </summary>
            public bool IsModified { get; set; }

            #endregion

            //////////////////////////////////////////////////////////////////////////////////////////////////// Constructor
            ////////////////////////////////////////////////////////////////////////////////////////// Public

            #region 생성자 - Block()

            /// <summary>
            /// 생성자
            /// </summary>
            public Block()
            {
            }

            #endregion
        }

        #endregion

        //////////////////////////////////////////////////////////////////////////////////////////////////// Field
        ////////////////////////////////////////////////////////////////////////////////////////// Private

        #region Field

        /// <summary>
        /// 스트림
        /// </summary>
        private Stream stream;

        /// <summary>
        /// 블럭 크기
        /// </summary>
        private int blockSize;

        /// <summary>
        /// 블럭 리스트
        /// </summary>
        private LinkedList<Block> blockList = new LinkedList<Block>();

        /// <summary>
        /// 길이
        /// </summary>
        public long length = 0;

        /// <summary>
        /// 위치
        /// </summary>
        private long position = 0;

        /// <summary>
        /// 버퍼링 이용 가능 여부
        /// </summary>
        private bool bufferingEnabled = true;

        #endregion

        //////////////////////////////////////////////////////////////////////////////////////////////////// Property
        ////////////////////////////////////////////////////////////////////////////////////////// Public

        #region 읽기 가능 여부 - CanRead

        /// <summary>
        /// 읽기 가능 여부
        /// </summary>
        public override bool CanRead
        {
            get
            {
                return this.stream.CanRead;
            }
        }

        #endregion
        #region 탐색 가능 여부 - CanSeek

        /// <summary>
        /// 탐색 가능 여부
        /// </summary>
        public override bool CanSeek
        {
            get
            {
                return this.stream.CanSeek;
            }
        }

        #endregion
        #region 쓰기 가능 여부 - CanWrite

        /// <summary>
        /// 쓰기 가능 여부
        /// </summary>
        public override bool CanWrite
        {
            get
            {
                return this.stream.CanWrite;
            }
        }

        #endregion
        #region 길이 - Length

        /// <summary>
        /// 길이
        /// </summary>
        public override long Length
        {
            get
            {
                return this.length;
            }
        }

        #endregion
        #region 위치 - Position

        /// <summary>
        /// 위치
        /// </summary>
        public override long Position
        {
            get
            {
                return this.position;
            }
            set
            {
                this.position = value;
            }
        }

        #endregion
        #region 버퍼링 이용 가능 여부 - BufferingEnabled

        /// <summary>
        /// 버퍼링 이용 가능 여부
        /// </summary>
        public bool BufferingEnabled
        {
            get
            {
                return this.bufferingEnabled;
            }
            set
            {
                if(value == false && this.blockList.Count > 0)
                {
                    throw new InvalidOperationException("Can't disable buffering when data is already buffered");
                }

                this.bufferingEnabled = value;
            }
        }

        #endregion

        //////////////////////////////////////////////////////////////////////////////////////////////////// Constructor
        ////////////////////////////////////////////////////////////////////////////////////////// Public

        #region 생성자 - WriteBufferedStream(stream, blockSize)

        /// <summary>
        /// 생성자
        /// </summary>
        /// <param name="stream">스트림</param>
        /// <param name="blockSize">블럭 크기</param>
        public WriteBufferedStream(Stream stream, int blockSize)
        {
            if(blockSize < 512)
            {
                throw new ArgumentException("Block size cannot be less than 512 bytes");
            }

            this.stream    = stream;
            this.blockSize = blockSize;
            this.length    = stream.Length;
        }

        #endregion

        //////////////////////////////////////////////////////////////////////////////////////////////////// Method
        ////////////////////////////////////////////////////////////////////////////////////////// Public

        #region 버퍼 비우기 - Flush()

        /// <summary>
        /// 버퍼 비우기
        /// </summary>
        public override void Flush()
        {
        }

        #endregion
        #region 읽기 - Read(buffer, offset, count)

        /// <summary>
        /// 읽기
        /// </summary>
        /// <param name="buffer">버퍼</param>
        /// <param name="offset">오프셋</param>
        /// <param name="count">카운트</param>
        /// <returns>읽은 바이트 수</returns>
        public override int Read(byte[] buffer, int offset, int count)
        {
            if(this.bufferingEnabled)
            {
                LinkedListNode<Block> node = GetCurrentOrNextNode(this.position);

                count = Math.Min(count, (int)(this.length - this.position));

                while(count > 0)
                {
                    int amount;

                    if(node != null)
                    {
                        if(this.position < node.Value.Position)
                        {
                            amount = Math.Min(count, (int)(node.Value.Position - this.position));

                            this.stream.Position = this.position;

                            this.stream.Read(buffer, offset, amount);
                        }
                        else
                        {
                            amount = Math.Min(count, (int)(node.Value.EndPosition - this.position));

                            Array.Copy(node.Value.Data, this.position - node.Value.Position, buffer, offset, amount);

                            node = node.Next;
                        }
                    }
                    else
                    {
                        amount = count;

                        this.stream.Position = this.position;

                        this.stream.Read(buffer, offset, amount);
                    }

                    offset += amount;

                    count -= amount;

                    this.position += amount;
                }

                return count;
            }
            else
            {
                this.stream.Position = this.position;

                int amount = this.stream.Read(buffer, offset, count);

                this.position = this.stream.Position;

                return amount;
            }
        }

        #endregion
        #region 쓰기 - Write(buffer, offset, count)

        /// <summary>
        /// 쓰기
        /// </summary>
        /// <param name="buffer">버퍼</param>
        /// <param name="offset">오프셋</param>
        /// <param name="count">카운트</param>
        public override void Write(byte[] buffer, int offset, int count)
        {
            if(this.bufferingEnabled)
            {
                LinkedListNode<Block> node;

                if(this.position > this.length)
                {
                    node = GetCurrentOrNextNode(this.length);

                    Block newBlock = null;

                    if(node != null)
                    {
                        int blockCount = CalculateBlockCount(node.Value.EndPosition, this.position);

                        if(blockCount > 0)
                        {
                            newBlock = CreateBlock(node.Value.EndPosition, blockCount);
                        }
                    }
                    else
                    {
                        int blockCount = CalculateBlockCount(this.length, this.position);

                        if(blockCount > 0)
                        {
                            newBlock = CreateBlock(this.length, blockCount);
                        }
                    }

                    if(newBlock != null)
                    {
                        this.blockList.AddLast(newBlock);
                    }

                    this.length = this.position;
                }

                this.length = Math.Max(this.position + count, this.length);

                node = GetCurrentOrNextNode(this.position);

                while(count > 0)
                {
                    int amount;

                    if(node == null || (this.position < node.Value.Position))
                    {
                        LinkedListNode<Block> newNode = new LinkedListNode<Block>(CreateBlock(this.position, 1));

                        if(node != null)
                        {
                            this.blockList.AddBefore(node, newNode);
                        }
                        else
                        {
                            this.blockList.AddLast(newNode);
                        }

                        node = newNode;
                    }

                    amount = Math.Min(count, (int)(node.Value.EndPosition - this.position));

                    Array.Copy(buffer, offset, node.Value.Data, this.position - node.Value.Position, amount);

                    node = node.Next;

                    offset += amount;

                    count -= amount;

                    this.position += amount;
                }
            }
            else
            {
                this.stream.Position = this.position;

                this.stream.Write(buffer, offset, count);

                this.length = Math.Max(this.position + count, this.length);

                this.position = this.stream.Position;
            }
        }

        #endregion
        #region 탐색하기 - Seek(offset, origin)

        /// <summary>
        /// 탐색하기
        /// </summary>
        /// <param name="offset">오프셋</param>
        /// <param name="origin">탐색 원점</param>
        /// <returns>위치</returns>
        public override long Seek(long offset, SeekOrigin origin)
        {
            switch(origin)
            {
                case SeekOrigin.Begin :

                    this.position = offset;

                    break;

                case SeekOrigin.Current :

                    this.position += offset;

                    break;

                case SeekOrigin.End :

                    this.position = this.length - offset;

                    break;
            }

            return this.position;
        }

        #endregion
        #region 길이 설정하기 - SetLength(value)

        /// <summary>
        /// 길이 설정하기
        /// </summary>
        /// <param name="value">값</param>
        public override void SetLength(long value)
        {
            throw new NotImplementedException();
        }

        #endregion
        #region 버퍼 데이터 비우기 - FlushBufferedData()

        /// <summary>
        /// 버퍼 데이터 비우기
        /// </summary>
        public void FlushBufferedData()
        {
            foreach(Block block in this.blockList)
            {
                this.stream.Position = block.Position;

                this.stream.Write(block.Data, 0, block.Data.Length);
            }

            this.blockList.Clear();
        }

        #endregion
        #region 버퍼 데이터 버리기 - DiscardBufferedData()

        /// <summary>
        /// 버퍼 데이터 버리기
        /// </summary>
        public void DiscardBufferedData()
        {
            this.blockList.Clear();
        }

        #endregion

        ////////////////////////////////////////////////////////////////////////////////////////// Private

        #region 현재 또는 다음 노드 구하기 - GetCurrentOrNextNode(position)

        /// <summary>
        /// 현재 또는 다음 노드 구하기
        /// </summary>
        /// <param name="position">위치</param>
        /// <returns>현재 또는 다음 노드</returns>
        private LinkedListNode<Block> GetCurrentOrNextNode(long position)
        {
            LinkedListNode<Block> node = this.blockList.First;

            while(node != null)
            {
                if(node.Value.EndPosition > position)
                {
                    break;
                }

                node = node.Next;
            }

            return node;
        }

        #endregion
        #region 블럭 생성하기 - CreateBlock(position, groupSize)

        /// <summary>
        /// 블럭 생성하기
        /// </summary>
        /// <param name="position">위치</param>
        /// <param name="groupSize">그룹 크기</param>
        /// <returns>블럭</returns>
        private Block CreateBlock(long position, int groupSize)
        {
            long blockPosition = (position / this.blockSize) * this.blockSize;

            Block block = new Block
            {
                Data     = new byte[this.blockSize * groupSize],
                Position = blockPosition
            };

            this.stream.Position = block.Position;

            this.stream.Read(block.Data, 0, block.Data.Length);

            return block;
        }

        #endregion
        #region 블럭 수 계산하기 - CalculateBlockCount(position1, position2)

        /// <summary>
        /// 블럭 수 계산하기
        /// </summary>
        /// <param name="position1">위치 1</param>
        /// <param name="position2">위치 2</param>
        /// <returns>블럭 수</returns>
        private int CalculateBlockCount(long position1, long position2)
        {
            position1 = position1 / (long)this.blockSize;

            long remainder = position2 % (long)this.blockSize;

            position2 = position2 / (long)this.blockSize;

            if(remainder != 0)
            {
                position2++;
            }

            return (int)(position2 - position1);
        }

        #endregion
    }
}

 

[TestProject 프로젝트]

▶ Program.cs

using System;
using System.IO;
using System.Drawing;
using System.Drawing.Imaging;
using System.Reflection;
using System.Windows.Forms;

using TestLibrary;

namespace TestProject
{
    /// <summary>
    /// 프로그램
    /// </summary>
    class Program
    {
        //////////////////////////////////////////////////////////////////////////////////////////////////// Method
        ////////////////////////////////////////////////////////////////////////////////////////// Static
        //////////////////////////////////////////////////////////////////////////////// Private

        #region 프로그램 시작하기 - Main()

        /// <summary>
        /// 프로그램 시작하기
        /// </summary>
        private static void Main()
        {
            string executingDirectoryPath = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location);

            string fileStorageFilePath    = Path.Combine(executingDirectoryPath, "ImageFile.storage"   );
            string transactionLogFilePath = Path.Combine(executingDirectoryPath, "ImageFile.storagelog");

            Storage storage = new Storage(fileStorageFilePath, transactionLogFilePath);

            Console.WriteLine("파일 저장소를 생성했습니다.");
            Console.WriteLine($"    파일 저장소 파일 경로   : {fileStorageFilePath   }");
            Console.WriteLine($"    트랜잭션 로그 파일 경로 : {transactionLogFilePath}");

            Image image1 = Image.FromFile("IMAGE\\source.jpg");

            Guid imageID = new Guid("8DC9E405-9A7F-4BF6-B4B9-CFA1B01F7D51");
 
            Console.WriteLine("이미지 객체를 생성했습니다.");
            Console.WriteLine($"    이미지 ID : {imageID}");

            Console.WriteLine("파일 저장소 트랜잭션을 시작합니다.");

            storage.StartTransaction();

            try
            {
                using(Stream stream1 = storage.CreateStream(imageID))
                {
                    image1.Save(stream1, ImageFormat.Jpeg);
                }

                storage.CommitTransaction();

                Console.WriteLine("파일 저장소 트랜잭션을 커밋합니다.");
            }
            catch(Exception exception)
            {
                Console.WriteLine("이미지 객체 저장시 에러가 발생했습니다.");
                Console.WriteLine("--------------------------------------------------");
                Console.WriteLine(exception.ToString());
                Console.WriteLine("--------------------------------------------------");

                storage.RollbackTransaction();

                Console.WriteLine("파일 저장소 트랜잭션을 롤백합니다.");
            }

            Console.WriteLine("파일 저장소에서 이미지 객체를 구합니다.");

            using(Stream stream2 = storage.OpenStream(imageID))
            {
                Image image2 = Image.FromStream(stream2);

                Form form = new Form();

                form.Size                  = new Size(800, 600);
                form.Text                  = "이미지";
                form.BackgroundImage       = image2;
                form.BackgroundImageLayout = ImageLayout.Stretch;

                form.ShowDialog();
            }

            Console.WriteLine("파일 저장소에서 이미지 객체를 삭제합니다.");

            storage.DeleteStream(imageID);

            Console.WriteLine("파일 저장소 파일을 정리합니다.");

            storage.TrimStorage();
        }

        #endregion
    }
}
728x90
반응형
그리드형(광고전용)
Posted by icodebroker

댓글을 달아 주세요