728x90
반응형
728x170
▶ 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
반응형
그리드형(광고전용)
'C# > Common' 카테고리의 다른 글
[C#/COMMON] 누겟 설치 : AudioSwitcher.AudioApi (0) | 2022.01.08 |
---|---|
[C#/COMMON] 누겟 설치 : NAudio (0) | 2022.01.08 |
[C#/COMMON] Configuration 클래스 : AppData 폴더에서 구성 파일 사용하기 (0) | 2022.01.08 |
[C#/COMMON] Enumerable 클래스 : Repeat 정적 메소드를 사용해 값을 초기화한 배열 구하기 (0) | 2022.01.08 |
[C#/COMMON] MarshalAsAttribute 클래스 : 고정 크기 배열 사용하기 (0) | 2022.01.08 |
[C#/COMMON] Stream 클래스 : 스트림 복사하기 (0) | 2022.01.01 |
[C#/COMMON/.NET5] 누겟 설치 : System.Management (0) | 2021.12.26 |
[C#/COMMON/.NET5] ManagementObjectSearcher 클래스 : 오디오 장치 열거하기 (0) | 2021.12.26 |
[C#/COMMON/.NET5] 누겟 설치 : FftSharp (0) | 2021.12.17 |
[C#/COMMON] WAV 파일 생성하기 (0) | 2021.12.08 |
댓글을 달아 주세요