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); +}