Skip to content

Commit ea815f1

Browse files
NC-7659: Use libzip callback interface
Instead of copiiing the full input stream into memory. This decreases the memory consumption of lib3MF on import by the size of the compressed zip-file. The speed of the import is higher for files with large attachments (e.g. large textures, binary blobs) and not slower for "regular" XML content of 3MFs.
1 parent 6027be7 commit ea815f1

File tree

8 files changed

+100
-18
lines changed

8 files changed

+100
-18
lines changed

CMakeLists.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ project (lib3MF)
99
# Define Version
1010
set(LIB3MF_VERSION_MAJOR 1) # increase on every backward-compatibility breaking change of the API
1111
set(LIB3MF_VERSION_MINOR 1) # increase on every backward compatible change of the API
12-
set(LIB3MF_VERSION_MICRO 4) # increase on on every change that does not alter the API
12+
set(LIB3MF_VERSION_MICRO 5) # increase on on every change that does not alter the API
1313

1414
set(CMAKE_INSTALL_BINDIR bin CACHE PATH "directory for installing binary files")
1515
set(CMAKE_INSTALL_LIBDIR lib CACHE PATH "directory for installing library files")

Include/Common/NMR_ErrorConst.h

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -275,6 +275,12 @@ NMR_ErrorConst.h defines all error code constants.
275275
// ZIP Entry too large for non zip64 zip-file
276276
#define NMR_ERROR_ZIPENTRYNON64_TOOLARGE 0x104A
277277

278+
// An individual custom attachment is too large
279+
#define NMR_ERROR_ATTACHMENTTOOLARGE 0x104B
280+
281+
// Error in zip-callback
282+
#define NMR_ERROR_ZIPCALLBACK 0x104C
283+
278284
/*-------------------------------------------------------------------
279285
Core framework error codes (0x2XXX)
280286
-------------------------------------------------------------------*/

Source/Common/NMR_Exception.cpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,8 @@ namespace NMR {
124124
case NMR_ERROR_IMPORTSTREAMISEMPTY: return "An attachment to be read does coes not have any content.";
125125
case NMR_ERROR_UUIDGENERATIONFAILED: return "Generation of a UUID failed.";
126126
case NMR_ERROR_ZIPENTRYNON64_TOOLARGE: return "A ZIP Entry is too large for non zip64 zip-file";
127+
case NMR_ERROR_ATTACHMENTTOOLARGE: return "An individual custom attachment is too large.";
128+
case NMR_ERROR_ZIPCALLBACK: return "Error in libzip callback.";
127129

128130

129131
// Unhandled exception

Source/Common/OPC/NMR_OpcPackageReader.cpp

Lines changed: 64 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,60 @@ NMR_OpcPackageReader.cpp defines an OPC Package reader in a portable way.
4242
#include <iostream>
4343

4444
namespace NMR {
45+
46+
// custom callbck function for reading from a CImportStream on the fly
47+
zip_int64_t custom_zip_source_callback(void *userData, void *data, zip_uint64_t len, zip_source_cmd_t cmd) {
48+
if (userData == nullptr)
49+
throw CNMRException(NMR_ERROR_INVALIDPARAM);
50+
51+
CImportStream* pImportStream = (CImportStream*)(userData);
52+
53+
switch (cmd) {
54+
case ZIP_SOURCE_SUPPORTS:
55+
zip_int64_t bitmap;
56+
bitmap = zip_source_make_command_bitmap(ZIP_SOURCE_OPEN, ZIP_SOURCE_READ, ZIP_SOURCE_CLOSE,
57+
ZIP_SOURCE_STAT, ZIP_SOURCE_ERROR, ZIP_SOURCE_SEEK, ZIP_SOURCE_TELL, ZIP_SOURCE_SUPPORTS, -1);
58+
return bitmap;
59+
60+
case ZIP_SOURCE_SEEK:
61+
zip_source_args_seek argsSeek;
62+
argsSeek = * ((zip_source_args_seek *)data);
63+
if (argsSeek.whence == SEEK_SET)
64+
pImportStream->seekPosition(argsSeek.offset, true);
65+
else if (argsSeek.whence == SEEK_CUR)
66+
pImportStream->seekForward(argsSeek.offset, true);
67+
else if (argsSeek.whence == SEEK_END) {
68+
pImportStream->seekFromEnd(argsSeek.offset, true);
69+
}
70+
else
71+
throw CNMRException(0);
72+
return 0;
73+
74+
case ZIP_SOURCE_OPEN:
75+
return 0;
76+
77+
case ZIP_SOURCE_READ:
78+
return pImportStream->readBuffer((nfByte*)data, len, true);
79+
80+
case ZIP_SOURCE_CLOSE:
81+
return 0;
82+
83+
case ZIP_SOURCE_TELL:
84+
return pImportStream->getPosition();
4585

86+
case ZIP_SOURCE_STAT:
87+
zip_stat_t* zipStat;
88+
zipStat = (zip_stat_t*)data;
89+
zip_stat_init(zipStat);
90+
zipStat->size = pImportStream->retrieveSize();
91+
zipStat->valid |= ZIP_STAT_SIZE;
92+
return sizeof(zip_stat_t);
93+
94+
default:
95+
throw CNMRException(NMR_ERROR_ZIPCALLBACK);
96+
}
97+
return -1;
98+
}
4699

47100
COpcPackageReader::COpcPackageReader(_In_ PImportStream pImportStream, _In_ PModelReaderWarnings pWarnings)
48101
{
@@ -65,13 +118,19 @@ namespace NMR {
65118
if (nStreamSize == 0)
66119
throw CNMRException(NMR_ERROR_COULDNOTGETSTREAMPOSITION);
67120

68-
// read ZIP into memory
69-
m_Buffer.resize ((size_t) nStreamSize);
70-
pImportStream->readBuffer(&m_Buffer[0], nStreamSize, true);
71-
72121
// create ZIP objects
73122
zip_error_init(&m_ZIPError);
74-
m_ZIPsource = zip_source_buffer_create(&m_Buffer[0], (size_t) nStreamSize, 0, &m_ZIPError);
123+
124+
if (true) {
125+
// read ZIP from callback: faster and requires less memory
126+
m_ZIPsource = zip_source_function_create(custom_zip_source_callback, pImportStream.get(), &m_ZIPError);
127+
}
128+
else {
129+
// read ZIP into memory
130+
m_Buffer.resize((size_t)nStreamSize);
131+
pImportStream->readBuffer(&m_Buffer[0], nStreamSize, true);
132+
m_ZIPsource = zip_source_buffer_create(&m_Buffer[0], (size_t)nStreamSize, 0, &m_ZIPError);
133+
}
75134
if (m_ZIPsource == nullptr)
76135
throw CNMRException(NMR_ERROR_COULDNOTREADZIPFILE);
77136

Source/Common/Platform/NMR_ImportStream_Memory.cpp

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,12 @@ namespace NMR {
5959

6060
// Retrieve Capacity and allocate buffer.
6161
nfUint64 cbCapacity = cbBytesToCopy;
62-
m_Buffer.resize((size_t) cbCapacity);
62+
try {
63+
m_Buffer.resize((size_t)cbCapacity);
64+
}
65+
catch (std::bad_alloc) {
66+
throw CNMRException(NMR_ERROR_INVALIDBUFFERSIZE);
67+
}
6368

6469
m_cbSize = 0;
6570
m_nPosition = 0;
@@ -151,6 +156,11 @@ namespace NMR {
151156

152157
nfBool CImportStream_Memory::seekFromEnd(_In_ nfUint64 cbBytes, _In_ nfBool bHasToSucceed)
153158
{
159+
// all seekFromEnd functions follow the calling conventions of fseek.
160+
// fseek expects a negative number to seek from the end (SEEK_END).
161+
// The program logic in this function requires a positive offset from the end.
162+
cbBytes = 1+~cbBytes;
163+
154164
if (cbBytes > m_cbSize) {
155165
if (bHasToSucceed)
156166
throw CNMRException(NMR_ERROR_COULDNOTSEEKSTREAM);

Source/Model/Reader/NMR_ModelReader_3MF_Native.cpp

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -151,13 +151,21 @@ namespace NMR {
151151
sURI = sTargetPartURIDir + sURI;
152152
POpcPackagePart pPart = m_pPackageReader->createPart(sURI);
153153
PImportStream pAttachmentStream = pPart->getImportStream();
154-
PImportStream pMemoryStream = pAttachmentStream->copyToMemory();
155-
156-
if (pMemoryStream->retrieveSize() == 0)
157-
m_pWarnings->addException(CNMRException(NMR_ERROR_IMPORTSTREAMISEMPTY), mrwMissingMandatoryValue);
154+
try {
155+
PImportStream pMemoryStream = pAttachmentStream->copyToMemory();
158156

159-
// Add Attachment Stream to Model
160-
m_pModel->addAttachment(sURI, sRelationShipType, pMemoryStream);
157+
if (pMemoryStream->retrieveSize() == 0)
158+
m_pWarnings->addException(CNMRException(NMR_ERROR_IMPORTSTREAMISEMPTY), mrwMissingMandatoryValue);
159+
160+
// Add Attachment Stream to Model
161+
m_pModel->addAttachment(sURI, sRelationShipType, pMemoryStream);
162+
}
163+
catch (CNMRException &e) {
164+
if (e.getErrorCode() == NMR_ERROR_INVALIDBUFFERSIZE)
165+
m_pWarnings->addException(CNMRException(NMR_ERROR_ATTACHMENTTOOLARGE), mrwMissingMandatoryValue);
166+
else
167+
throw;
168+
}
161169
}
162170
}
163171
}

UnitTests/Source/UnitTest_AllTests.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ int main(int argc, char **argv)
3838
// testing::GTEST_FLAG(filter) = "*LoadFromMemoryBuffer*";
3939
// testing::GTEST_FLAG(filter) = "*Production_LoadFileTest*ReadFiles*";
4040
// testing::GTEST_FLAG(filter) = "*ProgressCallback*";
41-
// testing::GTEST_FLAG(filter) = "*WriteBeamLattice_Negative*";
41+
// testing::GTEST_FLAG(filter) = "*Attachments*ReadAttachment*";
4242
testing::InitGoogleTest(&argc, argv);
4343
return RUN_ALL_TESTS();
4444
}

UnitTests/Source/UnitTest_Attachments.cpp

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,6 @@ namespace NMR {
8787
<< L"Could not read attachment from file";
8888
}
8989

90-
9190
ASSERT_EQ(NMR::lib3mf_model_addcustomcontenttypeutf8(pModel.get(), "xml", "application/xml"), S_OK)
9291
<< L"Could not add custom contenttype attachment from buffer";
9392

@@ -151,14 +150,12 @@ namespace NMR {
151150
if (hResult != S_OK) {
152151
DWORD errorCode;
153152
LPCSTR errString;
154-
hResult = lib3mf_getlasterror(p3MFReader.get(), &errorCode, &errString);
153+
lib3mf_getlasterror(p3MFReader.get(), &errorCode, &errString);
155154
std::string errorMessage = std::string(errString);
156155
}
157156
ASSERT_EQ(hResult, S_OK) << L"Could not read 3MF file.";
158157
}
159-
160158
Read3MFAttachments(pModel);
161-
162159
}
163160
};
164161

0 commit comments

Comments
 (0)