diff --git a/Overview/Filing.md b/Overview/Filing.md
index fa524ac..6d290f3 100644
--- a/Overview/Filing.md
+++ b/Overview/Filing.md
@@ -14,5 +14,13 @@
## Serialize/Deserialize
## InnFile
+* [`fon9/InnFile.hpp`](../fon9/InnFile.hpp)
+* 著重功能:
+ * 房間的建立、取得。
+ * 讀取房間內容。
+ * 寫入房間內容。
+* 進階功能:由 InnDbf 提供。
-## 資料同步機制
+## InnDbf
+### 資料表的連結
+### 資料同步機制
diff --git a/build/cmake/unit_test_all.sh b/build/cmake/unit_test_all.sh
index 2e1072c..ea6dd1f 100644
--- a/build/cmake/unit_test_all.sh
+++ b/build/cmake/unit_test_all.sh
@@ -45,6 +45,7 @@ $OUTPUT_DIR/MemBlock_UT
# unit tests: file
$OUTPUT_DIR/File_UT
$OUTPUT_DIR/TimedFileName_UT
+$OUTPUT_DIR/InnFile_UT
rm -rf ./logs
rm -rf /tmp/*log
diff --git a/build/vs2015/_UnitTests/InnFile_UT.vcxproj b/build/vs2015/_UnitTests/InnFile_UT.vcxproj
new file mode 100644
index 0000000..994ed14
--- /dev/null
+++ b/build/vs2015/_UnitTests/InnFile_UT.vcxproj
@@ -0,0 +1,85 @@
+
+
+
+
+ Debug
+ x64
+
+
+ Release
+ x64
+
+
+
+ {F46375FE-1536-4DB4-8D69-2B137A639A09}
+ InnFile_UT
+ 8.1
+
+
+
+ Application
+ true
+ v140
+ NotSet
+
+
+ Application
+ false
+ v140
+ true
+ NotSet
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ $(SolutionDir)..\..\..\output\$(SolutionName)\$(PlatformArchitecture)\$(Configuration)\
+ $(OutDir)$(ProjectName)\
+
+
+ $(SolutionDir)..\..\..\output\$(SolutionName)\$(PlatformArchitecture)\$(Configuration)\
+ $(OutDir)$(ProjectName)\
+
+
+
+ EnableAllWarnings
+ Disabled
+ true
+ ..\..\..\..\fon9
+
+
+
+
+ EnableAllWarnings
+ MaxSpeed
+ true
+ true
+ true
+ ..\..\..\..\fon9
+
+
+ true
+ true
+
+
+
+
+
+
+
+ {6b9031a7-02e1-4171-8ca5-9f780be21806}
+
+
+
+
+
+
\ No newline at end of file
diff --git a/build/vs2015/_UnitTests/InnFile_UT.vcxproj.filters b/build/vs2015/_UnitTests/InnFile_UT.vcxproj.filters
new file mode 100644
index 0000000..b4edf4a
--- /dev/null
+++ b/build/vs2015/_UnitTests/InnFile_UT.vcxproj.filters
@@ -0,0 +1,22 @@
+
+
+
+
+ {4FC737F1-C7A5-4376-A066-2A32D752A2FF}
+ cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx
+
+
+ {93995380-89BD-4b04-88EB-625FBE52EBFB}
+ h;hh;hpp;hxx;hm;inl;inc;xsd
+
+
+ {67DA6AB6-F800-4c08-8B7A-83BB121AAD01}
+ rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms
+
+
+
+
+ Source Files
+
+
+
\ No newline at end of file
diff --git a/build/vs2015/fon9.sln b/build/vs2015/fon9.sln
index 7f355d9..25d10a7 100644
--- a/build/vs2015/fon9.sln
+++ b/build/vs2015/fon9.sln
@@ -35,7 +35,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Tools_Utility", "Tools_Util
EndProject
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "MemBlock_UT", "_UnitTests\MemBlock_UT.vcxproj", "{5D83E5F6-E5A8-4AEC-84AF-F34DD582B586}"
EndProject
-Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "File", "File", "{CB1CFD79-6CAD-4A0F-8CE1-A59F434B5A84}"
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "File_Inn", "File_Inn", "{CB1CFD79-6CAD-4A0F-8CE1-A59F434B5A84}"
EndProject
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "File_UT", "_UnitTests\File_UT.vcxproj", "{E7F7AAA0-BAB0-45CC-A51C-888667CC8FF2}"
EndProject
@@ -69,6 +69,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Container_Algorithm", "Cont
EndProject
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "Tree_UT", "_UnitTests\Tree_UT.vcxproj", "{C2283F31-2244-4DEB-8C13-CA1FF484FDDB}"
EndProject
+Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "InnFile_UT", "_UnitTests\InnFile_UT.vcxproj", "{F46375FE-1536-4DB4-8D69-2B137A639A09}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|x64 = Debug|x64
@@ -171,6 +173,10 @@ Global
{C2283F31-2244-4DEB-8C13-CA1FF484FDDB}.Debug|x64.Build.0 = Debug|x64
{C2283F31-2244-4DEB-8C13-CA1FF484FDDB}.Release|x64.ActiveCfg = Release|x64
{C2283F31-2244-4DEB-8C13-CA1FF484FDDB}.Release|x64.Build.0 = Release|x64
+ {F46375FE-1536-4DB4-8D69-2B137A639A09}.Debug|x64.ActiveCfg = Debug|x64
+ {F46375FE-1536-4DB4-8D69-2B137A639A09}.Debug|x64.Build.0 = Debug|x64
+ {F46375FE-1536-4DB4-8D69-2B137A639A09}.Release|x64.ActiveCfg = Release|x64
+ {F46375FE-1536-4DB4-8D69-2B137A639A09}.Release|x64.Build.0 = Release|x64
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@@ -206,5 +212,6 @@ Global
{3F6A3F48-F258-40BC-A170-DF0CD150A212} = {993AE6E8-08E5-4943-BC92-592A81211FF0}
{84EDB5C7-36C8-4EC9-9A66-9B83356A908B} = {6B161511-B07B-4921-8404-EE71775CE5DC}
{C2283F31-2244-4DEB-8C13-CA1FF484FDDB} = {51700D09-381E-46C8-8511-801626F39F1F}
+ {F46375FE-1536-4DB4-8D69-2B137A639A09} = {CB1CFD79-6CAD-4A0F-8CE1-A59F434B5A84}
EndGlobalSection
EndGlobal
diff --git a/build/vs2015/libfon9/libfon9.vcxproj b/build/vs2015/libfon9/libfon9.vcxproj
index 43f0617..3ecd1b7 100644
--- a/build/vs2015/libfon9/libfon9.vcxproj
+++ b/build/vs2015/libfon9/libfon9.vcxproj
@@ -100,6 +100,7 @@
+
@@ -227,6 +228,7 @@
+
diff --git a/build/vs2015/libfon9/libfon9.vcxproj.filters b/build/vs2015/libfon9/libfon9.vcxproj.filters
index f21708a..7d61459 100644
--- a/build/vs2015/libfon9/libfon9.vcxproj.filters
+++ b/build/vs2015/libfon9/libfon9.vcxproj.filters
@@ -102,6 +102,12 @@
{96dc55c5-7be5-443c-92a2-1b2de4a7a509}
+
+ {2f1ce5cc-8750-497b-b8bf-c40d8a72df75}
+
+
+ {09f01b7f-354b-4472-b568-469b30a64644}
+
@@ -482,6 +488,9 @@
Header Files\_base\_Container / Algorithm
+
+ Header Files\_base\_Inn
+
@@ -718,5 +727,8 @@
Source Files\seed\_base
+
+ Source Files\_base\_Inn
+
\ No newline at end of file
diff --git a/fon9/CMakeLists.txt b/fon9/CMakeLists.txt
index 250ccee..a677202 100644
--- a/fon9/CMakeLists.txt
+++ b/fon9/CMakeLists.txt
@@ -29,6 +29,7 @@ set(fon9src
FileAppender.cpp
LogFile.cpp
FdrNotify.cpp
+ InnFile.cpp
buffer/MemBlock.cpp
buffer/BufferNode.cpp
@@ -145,6 +146,9 @@ target_link_libraries(TimedFileName_UT fon9_s)
add_executable(LogFile_UT LogFile_UT.cpp)
target_link_libraries(LogFile_UT fon9_s)
+add_executable(InnFile_UT InnFile_UT.cpp)
+target_link_libraries(InnFile_UT fon9_s)
+
# unit tests: io
add_executable(Socket_UT io/Socket_UT.cpp)
target_link_libraries(Socket_UT fon9_s)
diff --git a/fon9/Exception.hpp b/fon9/Exception.hpp
index 3d612f2..b06a17a 100644
--- a/fon9/Exception.hpp
+++ b/fon9/Exception.hpp
@@ -12,7 +12,7 @@ namespace fon9 {
/// 透過一層間接呼叫, 可讓 compiler 更容易最佳化: 會丟出異常的 function.
template
[[noreturn]] void Raise(ArgsT&&... args) {
- throw E{std::forward(args)...};
+ throw E(std::forward(args)...);
}
/// \ingroup Misc
/// 丟出異常.
@@ -21,7 +21,7 @@ template
/// - 這在 constexpr function 裡面很常用到。
template
[[noreturn]] ReturnT Raise(ArgsT&&... args) {
- throw E{std::forward(args)...};
+ throw E(std::forward(args)...);
}
fon9_MSC_WARN_DISABLE(4623); // 4623: 'fon9::BufferOverflow': default constructor was implicitly defined as deleted
diff --git a/fon9/InnFile.cpp b/fon9/InnFile.cpp
new file mode 100644
index 0000000..9c0b8ef
--- /dev/null
+++ b/fon9/InnFile.cpp
@@ -0,0 +1,359 @@
+/// \file fon9/InnFile.cpp
+/// \author fonwinz@gmail.com
+#include "fon9/InnFile.hpp"
+#include "fon9/Endian.hpp"
+#include "fon9/buffer/FwdBufferList.hpp"
+
+namespace fon9 {
+
+static const char kInnHeaderStr[20] = "fon9.inn.00001\n";
+enum : InnFile::SizeT {
+ kBlockSizeAlign = 16,
+
+ kOffset_BlockCount = 0,
+ kOffset_RoomType = kOffset_BlockCount + sizeof(InnFile::SizeT),
+ kOffset_DataSize = kOffset_RoomType + sizeof(InnRoomType),
+ kOffset_DataBegin = kOffset_DataSize + sizeof(InnFile::SizeT),
+};
+static_assert(kOffset_DataBegin == InnFile::kRoomHeaderSize, "RoomHeaderSize or kOffset_DataBegin ERROR!");
+
+//--------------------------------------------------------------------------//
+
+InnFile::InnFile() {
+}
+InnFile::~InnFile() {
+}
+
+InnFile::OpenResult InnFile::Open(OpenArgs& args) {
+ if (this->Storage_.IsOpened())
+ return InnFile::OpenResult{std::errc::already_connected};
+ File fd;
+ OpenResult res = fd.Open(args.FileName_, args.OpenMode_);
+ if (!res)
+ return res;
+ byte header[kInnHeaderSize];
+ res = fd.Read(0, header, sizeof(header));
+ if (!res)
+ return res;
+ if (fon9_UNLIKELY(res.GetResult() == 0)) {
+ // new file, write header.
+ memcpy(header, kInnHeaderStr, sizeof(kInnHeaderStr));
+ byte* pdat = header + sizeof(kInnHeaderStr);
+ PutBigEndian(pdat, this->HeaderSize_ = kInnHeaderSize);
+ pdat += sizeof(SizeT);
+
+ PutBigEndian(pdat, this->BlockSize_ = args.BlockSize_);
+ pdat += sizeof(SizeT);
+
+ memset(pdat, 0, static_cast(header + sizeof(header) - pdat));
+
+ res = fd.Write(0, header, this->HeaderSize_);
+ if (!res) {
+ fd.SetFileSize(0);
+ return res;
+ }
+ this->Storage_ = std::move(fd);
+ this->FileSize_ = this->HeaderSize_;
+ return OpenResult{0};
+ }
+ if (res.GetResult() != sizeof(header))
+ return InnFile::OpenResult{std::errc::bad_message};
+ if (memcmp(header, kInnHeaderStr, sizeof(kInnHeaderStr)) != 0)
+ return InnFile::OpenResult{std::errc::bad_message};
+ const byte* pdat = header + sizeof(kInnHeaderStr);
+ this->HeaderSize_ = GetBigEndian(pdat);
+ pdat += sizeof(SizeT);
+ this->BlockSize_ = GetBigEndian(pdat);
+
+ static_assert(kInnHeaderSize % kBlockSizeAlign == 0, "Bad 'kInnHeaderSize'");
+ if (this->BlockSize_ < kBlockSizeAlign
+ || (this->BlockSize_ % kBlockSizeAlign != 0)
+ || (this->HeaderSize_ < kInnHeaderSize)
+ || (this->HeaderSize_ % kBlockSizeAlign != 0))
+ return InnFile::OpenResult{std::errc::bad_message};
+ res = fd.GetFileSize();
+ if (!res)
+ return res;
+ if ((res.GetResult() - this->HeaderSize_) % this->BlockSize_ != 0)
+ return InnFile::OpenResult{std::errc::bad_message};
+ args.BlockSize_ = this->BlockSize_;
+ this->Storage_ = std::move(fd);
+ this->FileSize_ = res.GetResult();
+ return OpenResult{(res.GetResult() - this->HeaderSize_) / this->BlockSize_};
+}
+
+//--------------------------------------------------------------------------//
+
+static inline void CheckIoSize(const File::Result& res, File::SizeType sz, const char* exResError, const char* exSizeError) {
+ if (fon9_UNLIKELY(!res))
+ Raise(res.GetError(), exResError);
+ if (fon9_UNLIKELY(res.GetResult() != sz))
+ Raise(std::errc::bad_message, exSizeError);
+}
+
+void InnFile::CheckRoomPos(RoomPosT pos, const char* exNotOpen, const char* exBadPos) const {
+ if (fon9_UNLIKELY(!this->Storage_.IsOpened()))
+ Raise(std::errc::bad_file_descriptor, exNotOpen);
+ if (fon9_UNLIKELY(!this->IsGoodRoomPos(pos)))
+ Raise(exBadPos);
+}
+
+//--------------------------------------------------------------------------//
+
+void InnFile::RoomHeaderToRoomKeyInfo(const byte* roomHeader, RoomKey::Info& roomKeyInfo) {
+ roomKeyInfo.RoomSize_ = this->CalcRoomSize(GetBigEndian(roomHeader + kOffset_BlockCount));
+ roomKeyInfo.RoomType_ = GetBigEndian(roomHeader + kOffset_RoomType);
+ roomKeyInfo.DataSize_ = GetBigEndian(roomHeader + kOffset_DataSize);
+}
+
+InnFile::RoomKey InnFile::MakeRoomKey(RoomPosT roomPos, void* exRoomHeader, SizeT exRoomHeaderSize) {
+ if (roomPos == 0)
+ roomPos = this->HeaderSize_;
+ this->CheckRoomPos(roomPos, "InnFile.MakeRoomKey: not opened.", "InnFile.MakeRoomKey: bad roomPos.");
+ if (roomPos >= this->FileSize_) { // 已到尾端(EOF).
+ RoomKey::Info info;
+ ZeroStruct(info);
+ return RoomKey{info};
+ }
+ byte roomHeader[kRoomHeaderSize + 128];
+ SizeT sz = kRoomHeaderSize + exRoomHeaderSize;
+ if (sz > sizeof(roomHeader))
+ sz = kRoomHeaderSize;
+ auto res = this->Storage_.Read(roomPos, roomHeader, sz);
+ CheckIoSize(res, sz,
+ "InnFile.MakeRoomKey: read room header error.",
+ "InnFile.MakeRoomKey: read room header error size.");
+
+ RoomKey::Info info;
+ info.RoomPos_ = roomPos;
+ this->RoomHeaderToRoomKeyInfo(roomHeader, info);
+ if (info.DataSize_ < exRoomHeaderSize)
+ Raise(std::errc::bad_message, "InnFile.MakeRoomKey: DataSize < exRoomHeaderSize.");
+ if (info.DataSize_ > info.RoomSize_ || info.RoomSize_ <= 0
+ || info.RoomPos_ + info.RoomSize_ + kRoomHeaderSize > this->FileSize_)
+ Raise(std::errc::bad_message, "InnFile.MakeRoomKey: bad room header.");
+
+ if (sz == kRoomHeaderSize) {
+ if (exRoomHeaderSize) {
+ res = this->Storage_.Read(roomPos + sz, exRoomHeader, exRoomHeaderSize);
+ CheckIoSize(res, exRoomHeaderSize,
+ "InnFile.MakeRoomKey: read room ExHeader error.",
+ "InnFile.MakeRoomKey: read room ExHeader error size.");
+ }
+ }
+ else if (exRoomHeaderSize)
+ memcpy(exRoomHeader, roomHeader + kRoomHeaderSize, exRoomHeaderSize);
+ return RoomKey{info};
+}
+
+InnFile::RoomKey InnFile::MakeNextRoomKey(const RoomKey& roomKey, void* exRoomHeader, SizeT exRoomHeaderSize) {
+ if (!roomKey)
+ Raise("InnFile.MakeNextRoomKey: invalid current RoomKey.");
+ return MakeRoomKey(roomKey.Info_.RoomPos_ + roomKey.Info_.RoomSize_ + kRoomHeaderSize,
+ exRoomHeader, exRoomHeaderSize);
+}
+
+InnFile::RoomKey InnFile::MakeNewRoom(InnRoomType roomType, SizeT size) {
+ if (fon9_UNLIKELY(!this->Storage_.IsOpened()))
+ Raise(std::errc::bad_file_descriptor, "InnFile.MakeNewRoom: not opened.");
+
+ RoomKey::Info info;
+ info.RoomPos_ = this->FileSize_;
+ SizeT blockCount = static_cast(static_cast(size) + kRoomHeaderSize + this->BlockSize_ - 1) / this->BlockSize_;
+ info.RoomType_ = roomType;
+ info.DataSize_ = 0;
+ info.RoomSize_ = this->CalcRoomSize(blockCount);
+
+ byte roomHeader[kRoomHeaderSize];
+ PutBigEndian(roomHeader + kOffset_BlockCount, blockCount);
+ PutBigEndian(roomHeader + kOffset_RoomType, info.RoomType_);
+ PutBigEndian(roomHeader + kOffset_DataSize, info.DataSize_);
+
+ auto res = this->Storage_.SetFileSize(this->FileSize_ = info.RoomPos_ + blockCount * this->BlockSize_);
+ const char* exWhat;
+ if (!res) {
+ exWhat = "InnFile.MakeNewRoom: build room error.";
+__RETURN_RESTORE_FILE:
+ this->Storage_.SetFileSize(this->FileSize_ = info.RoomPos_);
+ Raise(res.GetError(), exWhat);
+ }
+
+ res = this->Storage_.Write(info.RoomPos_, roomHeader, kRoomHeaderSize);
+ if (!res) {
+ exWhat = "InnFile.MakeNewRoom: write room header error.";
+ goto __RETURN_RESTORE_FILE;
+ }
+ if (res.GetResult() != kRoomHeaderSize) {
+ res = std::errc::bad_message;
+ exWhat = "InnFile.MakeNewRoom: write room header error size.";
+ goto __RETURN_RESTORE_FILE;
+ }
+ return RoomKey{info};
+}
+
+InnFile::RoomKey InnFile::ClearRoom(RoomPosT roomPos, InnRoomType roomType, SizeT size) {
+ this->CheckRoomPos(roomPos, "InnFile.ClearRoom: not opened.", "InnFile.ClearRoom: bad roomPos.");
+ SizeT blockCount;
+ auto res = this->Storage_.Read(roomPos, &blockCount, sizeof(blockCount));
+ blockCount = GetBigEndian(&blockCount);
+ CheckIoSize(res, sizeof(blockCount),
+ "InnFile.ClearRoom: read room header error.",
+ "InnFile.ClearRoom: read room header error size.");
+
+ RoomKey::Info info;
+ info.RoomSize_ = this->CalcRoomSize(blockCount);
+ if (info.RoomSize_ < size)
+ Raise("InnFile.ClearRoom: room size error.");
+
+ static_assert(kOffset_RoomType == sizeof(blockCount) && kOffset_DataSize == kOffset_RoomType + 1,
+ "room header format error.");
+ byte clearRoomHeader[kRoomHeaderSize - sizeof(blockCount)];
+ clearRoomHeader[0] = info.RoomType_ = roomType;
+ PutBigEndian(clearRoomHeader + sizeof(roomType), info.DataSize_ = 0);
+ res = this->Storage_.Write((info.RoomPos_ = roomPos) + kOffset_RoomType, clearRoomHeader, sizeof(clearRoomHeader));
+ CheckIoSize(res, sizeof(clearRoomHeader),
+ "InnFile.ClearRoom: update room header error.",
+ "InnFile.ClearRoom: update room header error size.");
+
+ return RoomKey{info};
+}
+
+void InnFile::Reduce(RoomKey& roomKey, SizeT newDataSize, const void* exRoomHeader, SizeT exRoomHeaderSize) {
+ this->CheckRoomPos(roomKey.Info_.RoomPos_, "InnFile.Reduce: not opened.", "InnFile.Reduce: bad roomPos.");
+ if (exRoomHeaderSize > newDataSize)
+ Raise(std::errc::invalid_argument, "InnFile.Reduce: exRoomHeaderSize > newDataSize");
+ if (roomKey.GetDataSize() < newDataSize) // Reduce() 資料量可以縮減(或不變), 不能增加.
+ Raise(std::errc::invalid_argument, "InnFile.Reduce: oldDataSize < newDataSize");
+
+ if (roomKey.GetDataSize() != newDataSize) {
+ char roomHeader[sizeof(newDataSize) + 128];
+ PutBigEndian(roomHeader, roomKey.Info_.DataSize_ = newDataSize);
+ SizeT wrsz = sizeof(newDataSize);
+ if (0 < exRoomHeaderSize && exRoomHeaderSize <= sizeof(roomHeader) - sizeof(newDataSize)) {
+ memcpy(roomHeader + sizeof(newDataSize), exRoomHeader, exRoomHeaderSize);
+ wrsz += exRoomHeaderSize;
+ exRoomHeaderSize = 0;
+ }
+ CheckIoSize(this->Storage_.Write(roomKey.Info_.RoomPos_ + kOffset_DataSize, roomHeader, wrsz), wrsz,
+ "InnFile.Reduce: update room DataSize error.",
+ "InnFile.Reduce: update room DataSize error size.");
+ }
+ if (exRoomHeaderSize > 0) {
+ CheckIoSize(this->Storage_.Write(roomKey.Info_.RoomPos_ + kRoomHeaderSize, exRoomHeader, exRoomHeaderSize),
+ exRoomHeaderSize,
+ "InnFile.Reduce: update room ExHeader error.",
+ "InnFile.Reduce: update room ExHeader error size.");
+ }
+}
+
+//--------------------------------------------------------------------------//
+
+InnFile::RoomPosT InnFile::CheckReadArgs(const RoomKey& roomKey, SizeT offset, SizeT& size) {
+ this->CheckRoomPos(roomKey.Info_.RoomPos_, "InnFile.Read: not opened.", "InnFile.Read: bad roomPos.");
+ if (offset >= roomKey.GetDataSize() || size <= 0)
+ return 0;
+ if (offset + size > roomKey.GetDataSize())
+ size = roomKey.GetDataSize() - offset;
+ return roomKey.Info_.RoomPos_ + kRoomHeaderSize + offset;
+}
+
+static InnFile::SizeT ReadToNode(File& fd, FwdBufferNode* back, InnFile::RoomPosT pos, InnFile::SizeT size) {
+ CheckIoSize(fd.Read(pos, back->GetDataEnd(), size), size,
+ "InnFile.Read: read error.",
+ "InnFile.Read: read error size.");
+ back->SetDataEnd(back->GetDataEnd() + size);
+ return size;
+}
+
+InnFile::SizeT InnFile::Read(const RoomKey& roomKey, SizeT offset, SizeT size, BufferList& buf) {
+ RoomPosT pos = this->CheckReadArgs(roomKey, offset, size);
+ if (pos == 0)
+ return 0;
+ File::Result res;
+ if (FwdBufferNode* back = FwdBufferNode::CastFrom(buf.back())) {
+ if (back->GetRemainSize() >= size)
+ return ReadToNode(this->Storage_, back, pos, size);
+ }
+ FwdBufferNode* back = FwdBufferNode::Alloc(size);
+ BufferList tempbuf; // for auto free back.
+ tempbuf.push_back(back);
+ ReadToNode(this->Storage_, back, pos, size);
+ buf.push_back(tempbuf.ReleaseList());
+ return size;
+}
+InnFile::SizeT InnFile::Read(const RoomKey& roomKey, SizeT offset, SizeT size, void* buf) {
+ if (RoomPosT pos = this->CheckReadArgs(roomKey, offset, size)) {
+ CheckIoSize(this->Storage_.Read(pos, buf, size), size,
+ "InnFile.Read: read error.",
+ "InnFile.Read: read error size.");
+ return size;
+ }
+ return 0;
+}
+InnFile::SizeT InnFile::ReadAll(const RoomKey& roomKey, void* buf, SizeT bufsz) {
+ if (bufsz >= roomKey.GetDataSize())
+ return this->Read(roomKey, 0, roomKey.GetDataSize(), buf);
+ Raise(std::errc::invalid_argument, "InnFile.ReadAll: buffer too small.");
+}
+
+//--------------------------------------------------------------------------//
+
+void InnFile::UpdateDataSize(RoomKey& roomKey, InnFile::SizeT newsz, const char* exResError, const char* exSizeError) {
+ PutBigEndian(&newsz, roomKey.Info_.DataSize_ = newsz);
+ CheckIoSize(this->Storage_.Write(roomKey.Info_.RoomPos_ + kOffset_DataSize, &newsz, sizeof(newsz)),
+ sizeof(newsz), exResError, exSizeError);
+}
+
+void WriteRoom(File& fd, File::PosType pos, size_t wrsz, DcQueue& buf, const char* exResError, const char* exSizeError) {
+ for (;;) {
+ auto blk = buf.PeekCurrBlock();
+ if (blk.second > wrsz)
+ blk.second = wrsz;
+ CheckIoSize(fd.Write(pos, blk.first, blk.second), blk.second, exResError, exSizeError);
+ buf.PopConsumed(blk.second);
+ if ((wrsz -= blk.second) <= 0)
+ break;
+ pos += blk.second;
+ }
+}
+
+InnFile::SizeT InnFile::Rewrite(RoomKey& roomKey, DcQueue& buf) {
+ RoomPosT pos = roomKey.Info_.RoomPos_;
+ this->CheckRoomPos(pos, "InnFile.Rewrite: not opened.", "InnFile.Rewrite: bad roomPos.");
+ size_t bufsz = buf.CalcSize();
+ if (bufsz > roomKey.GetRoomSize())
+ Raise("InnFile.Rewrite: request size > RoomSize.");
+ WriteRoom(this->Storage_, pos + kRoomHeaderSize, bufsz, buf,
+ "InnFile.Rewrite: write error.",
+ "InnFile.Rewrite: write error size.");
+ if (roomKey.Info_.DataSize_ != static_cast(bufsz))
+ this->UpdateDataSize(roomKey, static_cast(bufsz),
+ "InnFile.Rewrite: update DataSize error.",
+ "InnFile.Rewrite: update DataSize error size.");
+ return roomKey.Info_.DataSize_;
+}
+
+InnFile::SizeT InnFile::Write(RoomKey& roomKey, const SizeT offset, const SizeT size, DcQueue& buf) {
+ RoomPosT pos = roomKey.Info_.RoomPos_;
+ this->CheckRoomPos(pos, "InnFile.Write: not opened.", "InnFile.Write: bad roomPos.");
+ if (offset > roomKey.GetDataSize())
+ Raise("InnFile.Write: bad offset.");
+ if (offset + size > roomKey.GetRoomSize())
+ Raise("InnFile.Write: bad request size.");
+
+ if (size <= 0)
+ return 0;
+ if (size > buf.CalcSize())
+ Raise(std::errc::invalid_argument, "InnFile.Write: request size > buffer size.");
+ WriteRoom(this->Storage_, pos + kRoomHeaderSize + offset, size, buf,
+ "InnFile.Write: write error.",
+ "InnFile.Write: write error size.");
+ SizeT newsz = offset + size;
+ if (roomKey.Info_.DataSize_ < newsz)
+ this->UpdateDataSize(roomKey, newsz,
+ "InnFile.Write: update DataSize error.",
+ "InnFile.Write: update DataSize error size.");
+ return size;
+}
+
+} // namespaces
diff --git a/fon9/InnFile.hpp b/fon9/InnFile.hpp
new file mode 100644
index 0000000..e4ee0f3
--- /dev/null
+++ b/fon9/InnFile.hpp
@@ -0,0 +1,269 @@
+/// \file fon9/InnFile.hpp
+/// \author fonwinz@gmail.com
+#ifndef __fon9_InnFile_hpp__
+#define __fon9_InnFile_hpp__
+#include "fon9/File.hpp"
+#include "fon9/buffer/BufferList.hpp"
+#include "fon9/buffer/DcQueue.hpp"
+#include "fon9/Exception.hpp"
+
+namespace fon9 {
+
+/// \ingroup Misc
+/// InnFile 不解釋 InnRoomType 的值, 由使用者自行解釋.
+using InnRoomType = byte;
+
+fon9_WARN_DISABLE_PADDING;
+fon9_MSC_WARN_DISABLE_NO_PUSH(4623 /* default constructor was implicitly defined as deleted */);
+
+fon9_DEFINE_EXCEPTION(InnRoomPosError, std::runtime_error);
+fon9_DEFINE_EXCEPTION(InnRoomSizeError, std::runtime_error);
+class InnFileError : public std::runtime_error {
+ using base = std::runtime_error;
+public:
+ ErrC ErrCode_;
+ InnFileError(ErrC e, const char* what) : base{what}, ErrCode_{e} {
+ }
+};
+
+/// \ingroup Misc
+/// 檔案空間, 採用類似 memory alloc 的管理方式, 分配空間 & 釋放空間.
+/// - InnFile 不理會分配出去的空間如何使用, InnFile 僅提供最基本的功能.
+/// - 所有操作都 **不是** thread safe.
+/// - 所有操作都 **立即** 操作檔案.
+/// - 除了 Open() 使用 OpenResult 傳回結果, 其餘操作若有錯誤, 則拋出異常.
+/// - 檔案格式:
+/// - 所有的數字格式使用 big endian
+/// - File Header: 64 bytes.
+/// - char[20] "fon9.inn.0001\n" // const char kInnHeaderStr[20]; 尾端補 '\0'
+/// - SizeT HeaderSize_; // 64: 包含 kInnHeaderStr.
+/// - SizeT BlockSize_; // 每個資料區塊的大小, 必定是 kBlockSizeAlign 的倍數.
+/// - byte[] 用 '\0' 補足 HeaderSize_(64) bytes.
+/// - Room Header: 9 bytes.
+/// - SizeT block count.
+/// - InnRoomType RoomType
+/// - SizeT data size in this room.
+/// - 為什麼不壓縮 SizeT, RoomPosT?
+/// - 放在 Room 前端, 必須是固定大小, 否則當使用 Append 方式添加資料時:
+/// 可能會需要移動已存入的資料, 變得很難處理。
+/// - 不壓縮, 可以在取得 SizeT, RoomPosT 時, 檢查其內容是否合理:
+/// 可以當成簡易的資料核對功能。
+/// - 實際浪費的空間有限: 即使有 1M Rooms, 最多可能的浪費也僅有 6..11M。
+/// - block count(3) + data size(3) = 6;
+/// - 如果每個 Room 額外包含 NextRoomPos: sizeof(RoomPosT) - 假設實際使用 3 bytes = 8 - 3 = 5;
+/// 加上 block count(3) + data size(3) = 6; 所以為 11。
+class fon9_API InnFile {
+ fon9_NON_COPYABLE(InnFile);
+
+public:
+ InnFile();
+ ~InnFile();
+
+ //--------------------------------------------------------------------------//
+
+ const std::string& GetOpenName() const {
+ return this->Storage_.GetOpenName();
+ }
+
+ void Sync() {
+ this->Storage_.Sync();
+ }
+
+ //--------------------------------------------------------------------------//
+
+ using SizeT = uint32_t;
+ using RoomPosT = File::PosType;
+
+ static constexpr FileMode kDefaultFileMode = FileMode::Read | FileMode::Write | FileMode::DenyWrite | FileMode::CreatePath;
+ static constexpr SizeT kInnHeaderSize = 64;
+ static constexpr SizeT kRoomHeaderSize = static_cast(sizeof(InnRoomType/*RoomType*/)
+ + sizeof(SizeT/*BlockCount*/)
+ + sizeof(SizeT/*DataSize*/));
+
+ //--------------------------------------------------------------------------//
+
+ struct OpenArgs {
+ std::string FileName_;
+ SizeT BlockSize_;
+ FileMode OpenMode_;
+
+ OpenArgs(std::string fileName, SizeT blockSize = 64, FileMode openMode = kDefaultFileMode)
+ : FileName_{std::move(fileName)}
+ , BlockSize_{blockSize}
+ , OpenMode_{openMode} {
+ }
+ };
+
+ using OpenResult = File::Result;
+ /// \retval success 0:無資料 or 新檔案, >0:此檔共使用了多少個 Block.
+ // 若檔案已存在, 則 args.BlockSize_ 儲存 inn file 的設定.
+ /// \retval errc::already_connected 重複開啟, 這次開啟操作失敗, 不影響先前的開啟狀態.
+ /// \retval errc::bad_message 檔案格式有誤.
+ OpenResult Open(OpenArgs& args);
+
+ void Close() {
+ this->Storage_.Close();
+ }
+
+ //--------------------------------------------------------------------------//
+
+ /// - 如果您擁有一把 RoomKey, 則可以從 Room 取出 or 存入.
+ /// - 直接拋棄 RoomKey (例: 擁有 roomkey 的物件死亡時),
+ /// 則仍會保留您儲存的內容, 當載入時會還原您的資料.
+ class RoomKey {
+ fon9_NON_COPYABLE(RoomKey);
+ struct Info {
+ RoomPosT RoomPos_;
+ SizeT RoomSize_;
+ SizeT DataSize_;
+ InnRoomType RoomType_;
+ };
+ Info Info_;
+ friend class InnFile;
+
+ RoomKey(const Info& info) : Info_(info) {
+ }
+
+ public:
+ RoomKey(RoomKey&& rhs) : Info_(rhs.Info_) {
+ rhs.Info_.RoomPos_ = 0;
+ }
+ RoomKey& operator=(RoomKey&& rhs) {
+ this->Info_ = rhs.Info_;
+ rhs.Info_.RoomPos_ = 0;
+ return *this;
+ }
+
+ RoomPosT GetRoomPos() const {
+ return this->Info_.RoomPos_;
+ }
+ InnRoomType GetRoomType() const {
+ return this->Info_.RoomType_;
+ }
+ SizeT GetDataSize() const {
+ return this->Info_.DataSize_;
+ }
+ // 不包含 kRoomHeaderSize(room header) 的實際可用空間.
+ SizeT GetRoomSize() const {
+ return this->Info_.RoomSize_;
+ }
+ /// 判斷是否為無效的 RoomKey.
+ bool operator!() const {
+ return this->Info_.RoomPos_ == 0;
+ }
+ explicit operator bool() const {
+ return this->Info_.RoomPos_ > 0;
+ }
+ };
+
+ /// 使用 roomPos 建立 RoomKey, 若 roomPos 不符合規則, 則會拋出 InnRoomPosError 異常!
+ /// 若 roomPos==0 則表示 first room.
+ /// 如果 roomPos 已到 inn 的最後, 則 (!retval) == true;
+ RoomKey MakeRoomKey(RoomPosT roomPos, void* exRoomHeader, SizeT exRoomHeaderSize);
+
+ /// 取得 roomKey 的下一個房間, 此時的 roomKey 必須是有效的.
+ /// 如果 roomKey 無效, 則會拋出 InnRoomPosError 異常!
+ /// \code
+ /// InnFile::RoomKey rkey = inn.MakeRoomKey(0, nullptr, 0);
+ /// while(rkey) {
+ /// inn.Read(rkey, ...);
+ /// rkey = inn.MakeNextRoomKey(rkey, nullptr, 0);
+ /// }
+ /// \endcode
+ RoomKey MakeNextRoomKey(const RoomKey& roomKey, void* exRoomHeader, SizeT exRoomHeaderSize);
+
+ /// 在檔案尾端新增一個 room.
+ RoomKey MakeNewRoom(InnRoomType roomType, SizeT size);
+
+ //--------------------------------------------------------------------------//
+
+ /// 重新分配一個 room (e.g. free room => 分配使用).
+ /// - 若 room 的空間 < size, 則會拋出 InnRoomSizeError 異常!
+ /// - size=需要的大小, room 的實際可用大小必定 >= size.
+ /// - size 在此僅作為檢查 RoomSize 使用.
+ RoomKey ReallocRoom(RoomPosT roomPos, InnRoomType roomType, SizeT size) {
+ return this->ClearRoom(roomPos, roomType, size);
+ }
+
+ /// 歸還一個 room (e.g. 不再使用 => free room), 返回時 roomKey 會變成無效.
+ void FreeRoom(RoomKey& roomKey, InnRoomType roomType) {
+ this->ClearRoom(roomKey.Info_.RoomPos_, roomType, 0);
+ ZeroStruct(roomKey.Info_);
+ }
+
+ //--------------------------------------------------------------------------//
+
+ /// 將 room 儲存的內容, 從 offset 開始取出 size bytes, 放入 buf 尾端.
+ /// - offset=0 表示從標準 RoomHead 之後的第一個 byte 開始讀取.
+ /// - 若 offset 超過 room DataSize 則, 不會取出任何資料, 且傳回 0.
+ /// - 若 offset + size > room DataSize 則會自動縮減取出的資料量.
+ /// - 實際取出的資料量, 必定 <= size.
+ SizeT Read(const RoomKey& roomKey, SizeT offset, SizeT size, BufferList& buf);
+ /// buf 大小最少需要 `size` bytes.
+ /// 實際取出的資料量, 必定 <= size.
+ SizeT Read(const RoomKey& roomKey, SizeT offset, SizeT size, void* buf);
+
+ /// 將 room 儲存的全部內容取出, 放入 buf 尾端.
+ /// 傳回取出的資料量, 必定等於 roomKey.GetDataSize();
+ SizeT ReadAll(const RoomKey& roomKey, BufferList& buf) {
+ return this->Read(roomKey, 0, roomKey.GetDataSize(), buf);
+ }
+ SizeT ReadAll(const RoomKey& roomKey, void* buf, SizeT bufsz);
+
+ /// 將 「buf全部」 覆寫入 room, 成功後返回寫入 room 的資料量 = roomKey.GetDataSize() = buf.CalcSize();
+ /// 若 room 的空間不足, 則 room 內容不變, 直接拋出 InnRoomSizeError 異常.
+ SizeT Rewrite(RoomKey& roomKey, DcQueue& buf);
+ SizeT Rewrite(RoomKey& roomKey, const void* pbufmem, SizeT bufsz) {
+ DcQueueFixedMem buf{pbufmem, bufsz};
+ return this->Rewrite(roomKey, buf);
+ }
+
+ /// 將 buf 寫入 room, 寫入的資料量由 size 指定(必須 <= buf.CalcSize());
+ /// - 拋出 InnRoomSizeError 異常:
+ /// - offset > roomKey.GetRoomSize()
+ /// - offset + size > roomKey.GetRoomSize()
+ /// - 拋出 InnFileError(std::errc::invalid_argument) 異常:
+ /// - size > buf.CalcSize()
+ /// - 若成功完成:
+ /// - 傳回值必定 = size
+ /// - 若 offset + size > roomKey.GetDataSize()
+ /// - 則新的 DataSize = offset + size
+ /// - 否則 DataSize 不變.
+ SizeT Write(RoomKey& roomKey, SizeT offset, SizeT size, DcQueue& buf);
+ SizeT Write(RoomKey& roomKey, SizeT offset, const void* pbufmem, SizeT bufsz) {
+ DcQueueFixedMem buf{pbufmem, bufsz};
+ return this->Write(roomKey, offset, bufsz, buf);
+ }
+
+ /// 縮減資料量(或不變, 僅重寫 exRoomHeader), 但不能增加.
+ /// - 重新調整 room DataSize, 並寫入額外的 exRoomHeader 資訊.
+ /// - newDataSize 不可大於現在的 roomKey.GetDataSize();
+ /// - 通常用在 pRoom 需要 Append 或 [覆寫尾端最後一筆] 時, 發現尾端空間不足,
+ /// 此時會先分配一個新的 nRoom 寫入,
+ /// 然後呼叫此處更新 pRoom, 此時 exRoomHeader 包含 nRoom 的位置.
+ void Reduce(RoomKey& roomKey, SizeT newDataSize, const void* exRoomHeader, SizeT exRoomHeaderSize);
+
+ //--------------------------------------------------------------------------//
+
+private:
+ SizeT CalcRoomSize(SizeT blockCount) const {
+ return blockCount ? static_cast(this->BlockSize_ * blockCount - kRoomHeaderSize) : 0;
+ }
+ bool IsGoodRoomPos(RoomPosT pos) const {
+ return (pos >= this->HeaderSize_) && (pos % this->BlockSize_) == 0;
+ }
+ void RoomHeaderToRoomKeyInfo(const byte* roomHeader, RoomKey::Info& roomKeyInfo);
+ void CheckRoomPos(RoomPosT pos, const char* exNotOpen, const char* exBadPos) const;
+ void UpdateDataSize(RoomKey& roomKey, SizeT newsz, const char* exResError, const char* exSizeError);
+ RoomPosT CheckReadArgs(const RoomKey& roomKey, SizeT offset, SizeT& size);
+ RoomKey ClearRoom(RoomPosT roomPos, InnRoomType roomType, SizeT size);
+
+ File Storage_;
+ SizeT BlockSize_{0};
+ SizeT HeaderSize_;
+ File::SizeType FileSize_;
+};
+fon9_WARN_POP;
+
+} // namespaces
+#endif//__fon9_InnFile_hpp__
diff --git a/fon9/InnFile_UT.cpp b/fon9/InnFile_UT.cpp
new file mode 100644
index 0000000..9593078
--- /dev/null
+++ b/fon9/InnFile_UT.cpp
@@ -0,0 +1,279 @@
+// \file fon9/InnFile_UT.cpp
+// \author fonwinz@gmail.com
+#define _CRT_SECURE_NO_WARNINGS
+#include "fon9/TestTools.hpp"
+#include "fon9/InnFile.hpp"
+#include "fon9/buffer/RevBuffer.hpp"
+#include "fon9/buffer/FwdBufferList.hpp"
+#include "fon9/Endian.hpp"
+#include "fon9/RevPrint.hpp"
+
+//--------------------------------------------------------------------------//
+
+static const char kInnFileName[] = "./test.inn";
+constexpr fon9::InnFile::SizeT kBlockSize = 32;
+
+//--------------------------------------------------------------------------//
+
+void TestInnOpen(fon9::InnFile& inn, fon9::InnFile::OpenArgs& args, fon9::InnFile::OpenResult resExpect) {
+ inn.Close();
+ auto res = inn.Open(args);
+ if (res == resExpect)
+ return;
+ fon9::RevBufferFixedSize<1024> rmsg;
+ rmsg.RewindEOS();
+ fon9::RevPrint(rmsg, "|res=", res, "|expect=", resExpect);
+ std::cout << "[ERROR] InnFile.Open|fname=" << args.FileName_ << rmsg.GetCurrent() << std::endl;
+ abort();
+}
+size_t TestMakeNewRoom(fon9::InnFile& inn, const fon9::InnFile::OpenArgs& args, fon9::InnFile::SizeT exsz) {
+ size_t blkcount = (exsz + inn.kRoomHeaderSize + args.BlockSize_ - 1) / args.BlockSize_;
+ auto rkey = inn.MakeNewRoom(0, exsz);
+ if (rkey.GetRoomSize() != blkcount * args.BlockSize_ - inn.kRoomHeaderSize) {
+ std::cout << "[ERROR] InnFile.Open|fname=" << args.FileName_
+ << "|roomSize=" << rkey.GetRoomSize()
+ << "|expect=" << blkcount * args.BlockSize_ - inn.kRoomHeaderSize
+ << std::endl;
+ abort();
+ }
+ if (rkey.GetRoomSize() < exsz) {
+ std::cout << "[ERROR] InnFile.Open|fname=" << args.FileName_
+ << "|roomSize=" << rkey.GetRoomSize()
+ << "|expect=" << exsz
+ << std::endl;
+ abort();
+ }
+ return blkcount;
+}
+void CheckRoomKey(const fon9::InnFile::RoomKey& rkey, fon9::InnFile::SizeT exsz) {
+ if (rkey.GetRoomType() == 0 && rkey.GetDataSize() == 0 && rkey.GetRoomSize() >= exsz)
+ return;
+ std::cout << "[ERROR] RoomKey|roomType=" << static_cast(rkey.GetRoomType())
+ << "|dataSize=" << rkey.GetDataSize()
+ << "|roomSize=" << rkey.GetRoomSize()
+ << "|expectRoomSize=" << exsz
+ << std::endl;
+ abort();
+}
+void TestErrorMakeRoomKey(fon9::InnFile& inn, fon9::InnFile::RoomPosT pos) {
+ try {
+ auto rkey = inn.MakeRoomKey(pos, nullptr, 0);
+ std::cout << "[ERROR] MakeRoomKey|pos=" << pos
+ << "|roomType=" << static_cast(rkey.GetRoomType())
+ << "|dataSize=" << rkey.GetDataSize()
+ << "|roomSize=" << rkey.GetRoomSize()
+ << std::endl;
+ abort();
+ }
+ catch (std::exception& ex) {
+ std::cout << "[OK ] MakeRoomKey|pos=" << pos << "|ex=" << ex.what() << std::endl;
+ }
+}
+void TestInnOpen() {
+ remove(kInnFileName);
+ fon9::InnFile::OpenArgs args{kInnFileName, kBlockSize};
+ fon9::InnFile inn;
+ TestInnOpen(inn, args, fon9::InnFile::OpenResult{0});
+ // 測試: 當 inn 為空, 建立的 roomKey 應為 !roomKey.
+ if (inn.MakeRoomKey(0, nullptr, 0)) {
+ std::cout << "[ERROR] InnFile.Open|fname=" << args.FileName_
+ << "|err=MakeRoomKey() from empty inn error."
+ << std::endl;
+ abort();
+ }
+
+ args.BlockSize_ = 64;
+ TestInnOpen(inn, args, fon9::InnFile::OpenResult{0});
+ // 測試: 重新開啟 inn 則 BlockSize 應為 inn 的 Header 的內容.
+ if (args.BlockSize_ != kBlockSize) {
+ std::cout << "[ERROR] InnFile.Open|fname=" << args.FileName_
+ << "|blockSize=" << args.BlockSize_
+ << "|expect=" << kBlockSize
+ << std::endl;
+ abort();
+ }
+
+ // 測試: 分配一個 room, 則重新開啟 inn 時, 則 inn 有該 room 的空間.
+ size_t blkcount = TestMakeNewRoom(inn, args, 100);
+ TestInnOpen(inn, args, fon9::InnFile::OpenResult{blkcount});
+ blkcount += TestMakeNewRoom(inn, args, 64 - inn.kRoomHeaderSize);
+
+ // 測試: 建立了 2 個 rooms 的 inn 並檢查 inn 空間是否正確:
+ TestInnOpen(inn, args, fon9::InnFile::OpenResult{blkcount});
+
+ // 測試: 依序取出 room key
+ auto rkey = inn.MakeRoomKey(0, nullptr, 0);
+ CheckRoomKey(rkey, 100);
+ rkey = inn.MakeNextRoomKey(rkey, nullptr, 0);
+ CheckRoomKey(rkey, 64 - inn.kRoomHeaderSize);
+ rkey = inn.MakeNextRoomKey(rkey, nullptr, 0);
+ if (rkey) {
+ std::cout << "[ERROR] Last.RoomKey|roomType=" << static_cast(rkey.GetRoomType())
+ << "|dataSize=" << rkey.GetDataSize()
+ << "|roomSize=" << rkey.GetRoomSize()
+ << std::endl;
+ abort();
+ }
+
+ // 測試: 用不正確的 room pos 建立 roomkey
+ TestErrorMakeRoomKey(inn, 1);
+ TestErrorMakeRoomKey(inn, fon9::InnFile::kInnHeaderSize + 1);
+ TestErrorMakeRoomKey(inn, fon9::InnFile::kInnHeaderSize + args.BlockSize_);
+
+ inn.Close();
+ remove(args.FileName_.c_str());
+ std::cout << "[OK ] InnFile.Open|fname=" << args.FileName_
+ << "|blockSize=" << args.BlockSize_
+ << "|res=" << blkcount << std::endl;
+}
+
+//--------------------------------------------------------------------------//
+
+char membuf[1024];
+
+// 檢查全部的 room:
+// room# = 0 .. sizeof(membuf)-1;
+// room.DataSize = room#;
+// room.內容 = 填滿 static_cast(room#);
+// room.Type = static_cast(room#);
+void CheckRooms(fon9::InnFile& inn, bool useBufferList) {
+ std::cout << "CheckRooms...";
+ auto rkey = inn.MakeRoomKey(0, nullptr, 0);
+ for (size_t L = 0; L < sizeof(membuf); ++L) {
+ if (rkey.GetDataSize() != L) {
+ std::cout << "\r" "[ERROR] InnFile.Read|err=Invalid data size|"
+ "room#=" << L << "|dataSize=" << rkey.GetDataSize() << std::endl;
+ abort();
+ }
+ if (rkey.GetRoomType() != static_cast(L)) {
+ std::cout << "\r" "[ERROR] InnFile.Read|err=Invalid room type|"
+ "room#=" << L << "|roomType=" << static_cast(rkey.GetRoomType()) << std::endl;
+ abort();
+ }
+ fon9::InnFile::SizeT res;
+ if (!useBufferList)
+ res = inn.ReadAll(rkey, membuf, static_cast(L));
+ else {
+ fon9::BufferList buf;
+ buf.push_back(fon9::FwdBufferNode::Alloc(1));
+ res = inn.ReadAll(rkey, buf);
+ memcpy(membuf, fon9::BufferTo(buf).c_str(), res);
+ }
+ if (res != L) {
+ std::cout << "\r" "[ERROR] InnFile.Read|res=" << res << "|request=" << L << std::endl;
+ abort();
+ }
+ auto pne = std::find_if(membuf, membuf + L, [L](fon9::byte b) { return b != static_cast(L); });
+ if (pne != membuf + L) {
+ std::cout << "[ERROR] InnFile.Read|room#=" << L << "|ctx err at=" << (pne - membuf) << std::endl;
+ abort();
+ }
+ rkey = inn.MakeNextRoomKey(rkey, nullptr, 0);
+ }
+}
+
+void TestInnFunc() {
+ remove(kInnFileName);
+ fon9::InnFile::OpenArgs args{kInnFileName, kBlockSize};
+ fon9::InnFile inn;
+ TestInnOpen(inn, args, fon9::InnFile::OpenResult{0});
+
+ std::cout << "[TEST ] InnFile.Rewrite...";
+ for (size_t L = 0; L < sizeof(membuf); ++L) {
+ memset(membuf, static_cast(L), L);
+ auto rkey = inn.MakeNewRoom(static_cast(L), static_cast(L));
+ auto res = inn.Rewrite(rkey, membuf, static_cast(L));
+ if (res != L) {
+ std::cout << "\r" "[ERROR] InnFile.Rewrite|res=" << res << "|request=" << L << std::endl;
+ abort();
+ }
+ }
+ CheckRooms(inn, false);
+ std::cout << "\r" "[OK ] InnFile.Rewrite & CheckRooms success." << std::endl;
+
+ // 測試 FreeRoom()
+ fon9::InnRoomType kRoomTypeFreed = 0xff;
+ std::cout << "[TEST ] InnFile.ClearRoom";
+ auto rkey = inn.MakeRoomKey(0, nullptr, 0);
+ for (size_t L = 0; L < sizeof(membuf); ++L) {
+ auto nkey = inn.MakeNextRoomKey(rkey, nullptr, 0);
+ inn.FreeRoom(rkey, kRoomTypeFreed);
+ rkey = std::move(nkey);
+ }
+ // 檢查 FreeRoom() 的結果.
+ rkey = inn.MakeRoomKey(0, nullptr, 0);
+ for (size_t L = 0; L < sizeof(membuf); ++L) {
+ if (rkey.GetDataSize() != 0 || rkey.GetRoomType() != kRoomTypeFreed) {
+ std::cout << "\r" "[ERROR] InnFile.ClearRoom"
+ "|room#=" << L <<
+ "|roomType=" << static_cast(rkey.GetRoomType()) <<
+ "|dataSize=" << rkey.GetDataSize() << std::endl;
+ abort();
+ }
+ rkey = inn.MakeNextRoomKey(rkey, nullptr, 0);
+ }
+ std::cout << "\r" "[OK ]" << std::endl;
+
+ // 測試使用 Write() 寫入資料.
+ std::cout << "[TEST ] InnFile.Write...";
+ rkey = inn.MakeRoomKey(0, nullptr, 0);
+ for (size_t L = 0; L < sizeof(membuf); ++L) {
+ rkey = inn.ReallocRoom(rkey.GetRoomPos(), static_cast(L), static_cast(L));
+ memset(membuf, static_cast(L), L);
+ auto res = inn.Write(rkey, 0, membuf, static_cast(L));
+ if (res != L) {
+ std::cout << "\r" "[ERROR] InnFile.Write|res=" << res << "|request=" << L << std::endl;
+ abort();
+ }
+ rkey = inn.MakeNextRoomKey(rkey, nullptr, 0);
+ }
+ CheckRooms(inn, true);
+ std::cout << "\r" "[OK ] InnFile.Write & CheckRooms success." << std::endl;
+
+ // 測試 Reduce().
+ std::cout << "[TEST ] InnFile.Reduce...";
+ rkey = inn.MakeRoomKey(0, nullptr, 0);
+ for (size_t L = 0; L < sizeof(membuf); ++L) {
+ if (rkey.GetDataSize() < sizeof(L))
+ inn.Rewrite(rkey, &L, sizeof(L));
+ else
+ inn.Reduce(rkey, sizeof(L), &L, sizeof(L));
+ if (rkey.GetDataSize() != sizeof(L)) {
+ std::cout << "\r" "[ERROR] InnFile.Reduce"
+ "|room#=" << L << "|afterDataSize=" << rkey.GetDataSize() << std::endl;
+ abort();
+ }
+ rkey = inn.MakeNextRoomKey(rkey, nullptr, 0);
+ }
+ // 檢查 Reduce() 的結果.
+ size_t exRoomHeader;
+ rkey = inn.MakeRoomKey(0, &exRoomHeader, sizeof(exRoomHeader));
+ for (size_t L = 0; L < sizeof(membuf); ++L) {
+ if (rkey.GetDataSize() != sizeof(L)
+ || rkey.GetRoomType() != static_cast(L)
+ || exRoomHeader != L) {
+ std::cout << "\r" "[ERROR] InnFile.Reduce"
+ "|room#=" << L <<
+ "|roomType=" << static_cast(rkey.GetRoomType()) <<
+ "|dataSize=" << rkey.GetDataSize() <<
+ "|exRoomHeader=" << exRoomHeader <<
+ std::endl;
+ abort();
+ }
+ rkey = inn.MakeNextRoomKey(rkey, &exRoomHeader, sizeof(exRoomHeader));
+ }
+ std::cout << "\r" "[OK ] InnFile.Reduce & CheckRooms success." << std::endl;
+}
+
+//--------------------------------------------------------------------------//
+
+int main() {
+#if defined(_MSC_VER) && defined(_DEBUG)
+ _CrtSetDbgFlag(_CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF);
+ //_CrtSetBreakAlloc(176);
+#endif
+ fon9::AutoPrintTestInfo utinfo("InnFile");
+ TestInnOpen();
+ TestInnFunc();
+ remove(kInnFileName);
+}