# HG changeset patch # User Sebastien Jodogne # Date 1762970838 -3600 # Node ID ded8a2be0d464f00e0af748308ad57983693c9e9 # Parent b4b7b6d39ee37dc4878702f90e2712bf3e0d2f2c# Parent 55db426a8667ac4dbe4cd404fab338532ec83877 integration mainline->pixel-anon diff -r b4b7b6d39ee3 -r ded8a2be0d46 NEWS --- a/NEWS Tue Nov 04 15:58:06 2025 +0100 +++ b/NEWS Wed Nov 12 19:07:18 2025 +0100 @@ -5,7 +5,7 @@ ------- * New configuration "HttpBindAddresses" to list the IP addresses on which the - HTTP server listens. By default, this list is empty and the HTTP server listens + HTTP server listens. By default, this list is empty and the HTTP server listens on all network interfaces. REST API @@ -13,59 +13,67 @@ * Fix: C-Get SCU jobs or HTTP responses were always successful even if the C-Get operation actually failed. -* C-Store, C-Move and C-Get jobs and HTTP responses now include a new "DimseErrorStatus" +* C-Store, C-Move and C-Get jobs and HTTP responses now include a new "DimseErrorStatus" field (ONLY if the operation fails). - +* C-Move and C-Get jobs and HTTP responses now include a new "Details" field with the "DimseErrorStatus" + of each operation and the list of "RetrievedInstancesIds". Note that, if you have multiple + resources to retrieve and one of them fails, you'll only get those details if you have set + "Permissive" to true to allow other operations to continue after the failure. Maintenance ----------- -* Under Windows, the console output is now configured to interpret strings as UTF-8. - This notably allow cyrillic strings to display correctly in the console. -* Improved streaming of HTTP responses under OS pressure. In some conditions, when the OS - was not able to send a full buffer over the network, Orthanc was not retrying - to send the remaining part. This is now fixed. +* Under Microsoft Windows, the console output is now configured to interpret strings as UTF-8. + This notably allow Cyrillic strings to display correctly in the console. +* Improved streaming of HTTP responses under OS pressure. In some conditions, when the OS + was not able to send a full buffer over the network, Orthanc was not retrying + to send the remaining part. (https://discourse.orthanc-server.org/t/incomplete-zip-downloads-from-get-studies-id-media/6046) * Reworked all the paths handling, improving general support of non ASCII-only paths on Windows, specifically for the Storage directories and for the configuration files. - However, on Windows, some features might still not support non ASCII-only paths: + However, on Microsoft Windows, some features might still not support non ASCII-only paths: - Reading a DCMTK dictionary ("ExternalDictionaries" configuration) - Using a "TemporaryDirectory" to save zip file or to export DICOMDIR - The "SslCertificate" and other related configurations * Fix: DicomGetScu jobs are now saved in DB. * Fix: When the configuration option "MaximumStorageCacheSize" was set to 0, the default value (128) - was actually applied. From now on, a value of 0 really means that the storage cache is disabled. -* Fix: Orthanc was unable to convert the tags into dicom+json format if the instance contained an - empty element in a sequence. This was preventing access to /dicom-web/../metadata routes and prevented + was actually applied. From now on, a value of 0 really means that the storage cache is disabled. +* Fix: Orthanc was unable to convert the tags into dicom+json format if the instance contained an + empty element in a sequence. This was preventing access to /dicom-web/../metadata routes and visualization in e.g. the Stone Web viewer and OHIF. -* Fix issue 252: Disallow colons in HTTP basic usernames +* Fix issue #252: Disallow colons in HTTP basic usernames * Fix: Decoding of LUT with less than 256 entries: https://discourse.orthanc-server.org/t/cannot-preview-dicom-file/6275 * Fix: Possible deadlocks when using ZipLoaderThreads > 1 when the HttpClient disconnects while downloading the response. +* Fix issue #227: In SQL, string literals must use 'single quotes' + https://stackoverflow.com/a/25141338 * Upgraded dependencies for static builds: - civetweb 1.16, including patch for CVE-2025-55763 - + - SQLite 3.50.4 Plugins ------- * Worklists plugin: - - The Worklists plugin now provides a Rest API to: + - The Worklists plugin now provides a REST API to: - create worklists through POST at /worklists/create - - list the worklists throuhg GET at /worklists + - list the worklists through GET at /worklists - view a single worklist through GET at /worklists/{uuid} - modify a worklist through PUT at /worklists/{uuid} - delete a worklist through DELETE at /worklists/{uuid} - All details are available in the Orthanc book: https://orthanc.uclouvain.be/book/plugins/worklists-plugin.html - - New configurations: - - "SaveInOrthancDatabase" to store the worklists in the Orthanc DB (provided that you are using SQLite or PostgreSQL) - - "DeleteWorklistsOnStableStudy" to delete the worklist once its related study has been received and is stable - - "SetStudyInstanceUidIfMissing" to add a StudyInstanceUID if the Rest API request does not include one. - - "DeleteWorklistsDelay" to delete a worklist N hours after it has been created (only available if using the "SaveInOrthancDatabase" mode). - - "HousekeepingInterval" to define the delay between 2 executions of the Worklist Housekeeping thread that deletes - the worklists when required. - - Note: the previous "Database" configuration has now been renamed in "Directory" to better differentiate the "File" or "DB modes. + All details are available in the Orthanc book: + https://orthanc.uclouvain.be/book/plugins/worklists-plugin.html + - New configuration options: + - "SaveInOrthancDatabase" to store the worklists in the Orthanc DB (provided that you are using SQLite or PostgreSQL). + - "DeleteWorklistsOnStableStudy" to delete the worklist once its related study has been received and is stable. + - "SetStudyInstanceUidIfMissing" to add a StudyInstanceUID if the REST API request does not include one. + - "DeleteWorklistsDelay" to delete a worklist N hours after it has been created + (only available if using the "SaveInOrthancDatabase" mode). + - "HousekeepingInterval" to define the delay between 2 executions of the Worklist Housekeeping thread + that deletes the worklists when required. + - Note: the previous "Database" configuration has now been renamed in "Directory" to better differentiate + the "File" or "DB modes. Version 1.12.9 (2025-08-11) @@ -392,7 +400,7 @@ * Upgraded dependencies for static builds: - boost 1.86.0 - curl 8.9.0 - - SQLite 3.46 + - SQLite 3.46.1 Version 1.12.4 (2024-06-05) @@ -669,7 +677,7 @@ * Enforce the existence of the patient/study/instance while creating its archive * Security: New configuration option "RestApiWriteToFileSystemEnabled" to allow "/instances/../export" (the latter is now disabled by default) -* Fix issue 214: VOILUTSequence is not returned in Wado-RS +* Fix issue #214: VOILUTSequence is not returned in Wado-RS * Fix /tools/reset crashing when ExtraMainDicomTags were defined * Fix Housekeeper plugin infinite loop if Orthanc is empty. * Fix a crash in /tools/reconstruct triggered by the Housekeeper plugin diff -r b4b7b6d39ee3 -r ded8a2be0d46 OrthancFramework/Resources/CMake/SQLiteConfiguration.cmake --- a/OrthancFramework/Resources/CMake/SQLiteConfiguration.cmake Tue Nov 04 15:58:06 2025 +0100 +++ b/OrthancFramework/Resources/CMake/SQLiteConfiguration.cmake Wed Nov 12 19:07:18 2025 +0100 @@ -28,9 +28,9 @@ if (SQLITE_STATIC) - SET(SQLITE_SOURCES_DIR ${CMAKE_BINARY_DIR}/sqlite-amalgamation-3460100) - SET(SQLITE_MD5 "1fb0f7ebbee45752098cf453b6dffff3") - SET(SQLITE_URL "https://orthanc.uclouvain.be/downloads/third-party-downloads/sqlite-amalgamation-3460100.zip") + SET(SQLITE_SOURCES_DIR ${CMAKE_BINARY_DIR}/sqlite-amalgamation-3500400) + SET(SQLITE_MD5 "440abd85c5ee3297dd388ade51fec0cc") + SET(SQLITE_URL "https://orthanc.uclouvain.be/downloads/third-party-downloads/sqlite-amalgamation-3500400.zip") set(ORTHANC_SQLITE_VERSION 3046001) diff -r b4b7b6d39ee3 -r ded8a2be0d46 OrthancFramework/Sources/DicomFormat/DicomIntegerPixelAccessor.h --- a/OrthancFramework/Sources/DicomFormat/DicomIntegerPixelAccessor.h Tue Nov 04 15:58:06 2025 +0100 +++ b/OrthancFramework/Sources/DicomFormat/DicomIntegerPixelAccessor.h Wed Nov 12 19:07:18 2025 +0100 @@ -51,7 +51,7 @@ const void* pixelData, size_t size); - const DicomImageInformation GetInformation() const + const DicomImageInformation& GetInformation() const { return information_; } diff -r b4b7b6d39ee3 -r ded8a2be0d46 OrthancFramework/Sources/DicomFormat/DicomStreamReader.cpp --- a/OrthancFramework/Sources/DicomFormat/DicomStreamReader.cpp Tue Nov 04 15:58:06 2025 +0100 +++ b/OrthancFramework/Sources/DicomFormat/DicomStreamReader.cpp Wed Nov 12 19:07:18 2025 +0100 @@ -156,7 +156,7 @@ } - void DicomStreamReader::HandlePreamble(IVisitor& visitor, + void DicomStreamReader::HandlePreamble(const IVisitor& visitor, const std::string& block) { assert(block.size() == 144u); diff -r b4b7b6d39ee3 -r ded8a2be0d46 OrthancFramework/Sources/DicomFormat/DicomStreamReader.h --- a/OrthancFramework/Sources/DicomFormat/DicomStreamReader.h Tue Nov 04 15:58:06 2025 +0100 +++ b/OrthancFramework/Sources/DicomFormat/DicomStreamReader.h Wed Nov 12 19:07:18 2025 +0100 @@ -89,7 +89,7 @@ bool IsLittleEndian() const; - void HandlePreamble(IVisitor& visitor, + void HandlePreamble(const IVisitor& visitor, const std::string& block); void HandleMetaHeader(IVisitor& visitor, diff -r b4b7b6d39ee3 -r ded8a2be0d46 OrthancFramework/Sources/DicomNetworking/DicomAssociation.cpp --- a/OrthancFramework/Sources/DicomNetworking/DicomAssociation.cpp Tue Nov 04 15:58:06 2025 +0100 +++ b/OrthancFramework/Sources/DicomNetworking/DicomAssociation.cpp Wed Nov 12 19:07:18 2025 +0100 @@ -388,8 +388,8 @@ LST_HEAD **l = ¶ms_->DULparams.acceptedPresentationContext; if (*l != NULL) { - DUL_PRESENTATIONCONTEXT* pc = (DUL_PRESENTATIONCONTEXT*) LST_Head(l); - LST_Position(l, (LST_NODE*)pc); + DUL_PRESENTATIONCONTEXT* pc = reinterpret_cast(LST_Head(l)); + LST_Position(l, reinterpret_cast(pc)); while (pc) { if (pc->result == ASC_P_ACCEPTANCE && strlen(pc->abstractSyntax) > 0) @@ -409,7 +409,7 @@ } } - pc = (DUL_PRESENTATIONCONTEXT*) LST_Next(l); + pc = reinterpret_cast(LST_Next(l)); } } diff -r b4b7b6d39ee3 -r ded8a2be0d46 OrthancFramework/Sources/DicomNetworking/DicomControlUserConnection.cpp --- a/OrthancFramework/Sources/DicomNetworking/DicomControlUserConnection.cpp Tue Nov 04 15:58:06 2025 +0100 +++ b/OrthancFramework/Sources/DicomNetworking/DicomControlUserConnection.cpp Wed Nov 12 19:07:18 2025 +0100 @@ -426,7 +426,8 @@ void DicomControlUserConnection::MoveInternal(const std::string& targetAet, ResourceType level, - const DicomMap& fields) + const DicomMap& fields, + uint16_t messageId) { assert(association_.get() != NULL); association_->Open(parameters_); @@ -448,7 +449,15 @@ T_DIMSE_C_MoveRQ request; memset(&request, 0, sizeof(request)); - request.MessageID = association_->GetDcmtkAssociation().nextMsgID++; + if (messageId == 0) + { + request.MessageID = association_->GetDcmtkAssociation().nextMsgID++; + } + else + { + request.MessageID = messageId; + } + strncpy(request.AffectedSOPClassUID, sopClass, DIC_UI_LEN); request.Priority = DIMSE_PRIORITY_MEDIUM; request.DataSetType = DIMSE_DATASET_PRESENT; @@ -569,7 +578,7 @@ } T_DIMSE_Message msgGetRequest; - memset((char*)&msgGetRequest, 0, sizeof(msgGetRequest)); + memset(reinterpret_cast(&msgGetRequest), 0, sizeof(msgGetRequest)); msgGetRequest.CommandField = DIMSE_C_GET_RQ; T_DIMSE_C_GetRQ* request = &(msgGetRequest.msg.CGetRQ); @@ -608,7 +617,7 @@ { T_DIMSE_Message rsp; // Make sure everything is zeroed (especially options) - memset((char*)&rsp, 0, sizeof(rsp)); + memset(reinterpret_cast(&rsp), 0, sizeof(rsp)); // DcmDataset* statusDetail = NULL; T_ASC_PresentationContextID cmdPresId = 0; @@ -702,7 +711,7 @@ // send the Store response T_DIMSE_Message storeResponse; - memset((char*)&storeResponse, 0, sizeof(storeResponse)); + memset(reinterpret_cast(&storeResponse), 0, sizeof(storeResponse)); storeResponse.CommandField = DIMSE_C_STORE_RSP; T_DIMSE_C_StoreRSP& storeRsp = storeResponse.msg.CStoreRSP; @@ -907,7 +916,8 @@ void DicomControlUserConnection::Move(const std::string& targetAet, ResourceType level, - const DicomMap& moveQuery) + const DicomMap& moveQuery, + uint16_t messageId) { DicomMap move; switch (level) @@ -935,12 +945,13 @@ throw OrthancException(ErrorCode_InternalError); } - MoveInternal(targetAet, level, move); + MoveInternal(targetAet, level, move, messageId); } void DicomControlUserConnection::Move(const std::string& targetAet, - const DicomMap& moveQuery) + const DicomMap& moveQuery, + uint16_t messageId) { if (!moveQuery.HasTag(DICOM_TAG_QUERY_RETRIEVE_LEVEL)) { @@ -950,49 +961,7 @@ const std::string tmp = moveQuery.GetValue(DICOM_TAG_QUERY_RETRIEVE_LEVEL).GetContent(); ResourceType level = StringToResourceType(tmp.c_str()); - Move(targetAet, level, moveQuery); - } - - - void DicomControlUserConnection::MovePatient(const std::string& targetAet, - const std::string& patientId) - { - DicomMap query; - query.SetValue(DICOM_TAG_PATIENT_ID, patientId, false); - MoveInternal(targetAet, ResourceType_Patient, query); - } - - - void DicomControlUserConnection::MoveStudy(const std::string& targetAet, - const std::string& studyUid) - { - DicomMap query; - query.SetValue(DICOM_TAG_STUDY_INSTANCE_UID, studyUid, false); - MoveInternal(targetAet, ResourceType_Study, query); - } - - - void DicomControlUserConnection::MoveSeries(const std::string& targetAet, - const std::string& studyUid, - const std::string& seriesUid) - { - DicomMap query; - query.SetValue(DICOM_TAG_STUDY_INSTANCE_UID, studyUid, false); - query.SetValue(DICOM_TAG_SERIES_INSTANCE_UID, seriesUid, false); - MoveInternal(targetAet, ResourceType_Series, query); - } - - - void DicomControlUserConnection::MoveInstance(const std::string& targetAet, - const std::string& studyUid, - const std::string& seriesUid, - const std::string& instanceUid) - { - DicomMap query; - query.SetValue(DICOM_TAG_STUDY_INSTANCE_UID, studyUid, false); - query.SetValue(DICOM_TAG_SERIES_INSTANCE_UID, seriesUid, false); - query.SetValue(DICOM_TAG_SOP_INSTANCE_UID, instanceUid, false); - MoveInternal(targetAet, ResourceType_Instance, query); + Move(targetAet, level, moveQuery, messageId); } diff -r b4b7b6d39ee3 -r ded8a2be0d46 OrthancFramework/Sources/DicomNetworking/DicomControlUserConnection.h --- a/OrthancFramework/Sources/DicomNetworking/DicomControlUserConnection.h Tue Nov 04 15:58:06 2025 +0100 +++ b/OrthancFramework/Sources/DicomNetworking/DicomControlUserConnection.h Wed Nov 12 19:07:18 2025 +0100 @@ -90,7 +90,8 @@ void MoveInternal(const std::string& targetAet, ResourceType level, - const DicomMap& fields); + const DicomMap& fields, + uint16_t messageId); public: explicit DicomControlUserConnection(const DicomAssociationParameters& params, ScuOperationFlags scuOperation); @@ -126,26 +127,13 @@ void Move(const std::string& targetAet, ResourceType level, - const DicomMap& moveQuery); + const DicomMap& moveQuery, + uint16_t messageId = 0); void Move(const std::string& targetAet, - const DicomMap& moveQuery); + const DicomMap& moveQuery, + uint16_t messageId = 0); - void MovePatient(const std::string& targetAet, - const std::string& patientId); - - void MoveStudy(const std::string& targetAet, - const std::string& studyUid); - - void MoveSeries(const std::string& targetAet, - const std::string& studyUid, - const std::string& seriesUid); - - void MoveInstance(const std::string& targetAet, - const std::string& studyUid, - const std::string& seriesUid, - const std::string& instanceUid); - void FindWorklist(DicomFindAnswers& result, ParsedDicomFile& query); }; diff -r b4b7b6d39ee3 -r ded8a2be0d46 OrthancFramework/Sources/DicomNetworking/IStoreRequestHandler.h --- a/OrthancFramework/Sources/DicomNetworking/IStoreRequestHandler.h Tue Nov 04 15:58:06 2025 +0100 +++ b/OrthancFramework/Sources/DicomNetworking/IStoreRequestHandler.h Wed Nov 12 19:07:18 2025 +0100 @@ -44,6 +44,8 @@ virtual uint16_t Handle(DcmDataset& dicom, const std::string& remoteIp, const std::string& remoteAet, - const std::string& calledAet) = 0; + const std::string& calledAet, + uint16_t originatorMessageId, + const std::string& originatorAet) = 0; }; } diff -r b4b7b6d39ee3 -r ded8a2be0d46 OrthancFramework/Sources/DicomNetworking/Internals/CommandDispatcher.h --- a/OrthancFramework/Sources/DicomNetworking/Internals/CommandDispatcher.h Tue Nov 04 15:58:06 2025 +0100 +++ b/OrthancFramework/Sources/DicomNetworking/Internals/CommandDispatcher.h Wed Nov 12 19:07:18 2025 +0100 @@ -62,9 +62,9 @@ unsigned int maximumPduLength, IApplicationEntityFilter* filter); - virtual ~CommandDispatcher(); + virtual ~CommandDispatcher() ORTHANC_OVERRIDE; - virtual bool Step(); + virtual bool Step() ORTHANC_OVERRIDE; }; CommandDispatcher* AcceptAssociation(const DicomServer& server, diff -r b4b7b6d39ee3 -r ded8a2be0d46 OrthancFramework/Sources/DicomNetworking/Internals/FindScp.cpp --- a/OrthancFramework/Sources/DicomNetworking/Internals/FindScp.cpp Tue Nov 04 15:58:06 2025 +0100 +++ b/OrthancFramework/Sources/DicomNetworking/Internals/FindScp.cpp Wed Nov 12 19:07:18 2025 +0100 @@ -112,13 +112,13 @@ // in case the sequence attribute contains exactly one item with an empty // ReferencedSOPClassUID and an empty ReferencedSOPInstanceUID, remove the item if( dataset->findAndGetElement( sequenceTagKey, sequenceAttribute ).good() && - ( (DcmSequenceOfItems*)sequenceAttribute )->card() == 1 && - ( (DcmSequenceOfItems*)sequenceAttribute )->getItem(0)->findAndGetElement( DCM_ReferencedSOPClassUID, referencedSOPClassUIDAttribute ).good() && + ( reinterpret_cast(sequenceAttribute) )->card() == 1 && + ( reinterpret_cast(sequenceAttribute) )->getItem(0)->findAndGetElement( DCM_ReferencedSOPClassUID, referencedSOPClassUIDAttribute ).good() && referencedSOPClassUIDAttribute->getLength() == 0 && - ( (DcmSequenceOfItems*)sequenceAttribute )->getItem(0)->findAndGetElement( DCM_ReferencedSOPInstanceUID, referencedSOPInstanceUIDAttribute, OFFalse ).good() && + ( reinterpret_cast(sequenceAttribute) )->getItem(0)->findAndGetElement( DCM_ReferencedSOPInstanceUID, referencedSOPInstanceUIDAttribute, OFFalse ).good() && referencedSOPInstanceUIDAttribute->getLength() == 0 ) { - DcmItem *item = ((DcmSequenceOfItems*)sequenceAttribute)->remove( ((DcmSequenceOfItems*)sequenceAttribute)->getItem(0) ); + DcmItem *item = (reinterpret_cast(sequenceAttribute))->remove( (reinterpret_cast(sequenceAttribute))->getItem(0) ); delete item; } } diff -r b4b7b6d39ee3 -r ded8a2be0d46 OrthancFramework/Sources/DicomNetworking/Internals/StoreScp.cpp --- a/OrthancFramework/Sources/DicomNetworking/Internals/StoreScp.cpp Tue Nov 04 15:58:06 2025 +0100 +++ b/OrthancFramework/Sources/DicomNetworking/Internals/StoreScp.cpp Wed Nov 12 19:07:18 2025 +0100 @@ -184,7 +184,7 @@ { try { - rsp->DimseStatus = cbdata->handler->Handle(**imageDataSet, *cbdata->remoteIp, cbdata->remoteAET, cbdata->calledAET); + rsp->DimseStatus = cbdata->handler->Handle(**imageDataSet, *cbdata->remoteIp, cbdata->remoteAET, cbdata->calledAET, req->MoveOriginatorID, req->MoveOriginatorApplicationEntityTitle); } catch (OrthancException& e) { diff -r b4b7b6d39ee3 -r ded8a2be0d46 OrthancFramework/Sources/DicomParsing/FromDcmtkBridge.cpp --- a/OrthancFramework/Sources/DicomParsing/FromDcmtkBridge.cpp Tue Nov 04 15:58:06 2025 +0100 +++ b/OrthancFramework/Sources/DicomParsing/FromDcmtkBridge.cpp Wed Nov 12 19:07:18 2025 +0100 @@ -2019,13 +2019,13 @@ } else { - ok = element.putUint16Array((const Uint16*) decoded->c_str(), decoded->size() / sizeof(Uint16)).good(); + ok = element.putUint16Array(reinterpret_cast(decoded->c_str()), decoded->size() / sizeof(Uint16)).good(); } break; default: - ok = element.putUint8Array((const Uint8*) decoded->c_str(), decoded->size()).good(); + ok = element.putUint8Array(reinterpret_cast(decoded->c_str()), decoded->size()).good(); break; } diff -r b4b7b6d39ee3 -r ded8a2be0d46 OrthancFramework/Sources/FileStorage/MemoryStorageArea.h --- a/OrthancFramework/Sources/FileStorage/MemoryStorageArea.h Tue Nov 04 15:58:06 2025 +0100 +++ b/OrthancFramework/Sources/FileStorage/MemoryStorageArea.h Wed Nov 12 19:07:18 2025 +0100 @@ -42,7 +42,7 @@ Content content_; public: - virtual ~MemoryStorageArea(); + virtual ~MemoryStorageArea() ORTHANC_OVERRIDE; virtual void Create(const std::string& uuid, const void* content, diff -r b4b7b6d39ee3 -r ded8a2be0d46 OrthancFramework/Sources/HttpServer/HttpOutput.cpp --- a/OrthancFramework/Sources/HttpServer/HttpOutput.cpp Tue Nov 04 15:58:06 2025 +0100 +++ b/OrthancFramework/Sources/HttpServer/HttpOutput.cpp Wed Nov 12 19:07:18 2025 +0100 @@ -976,7 +976,7 @@ stateMachine_.AddHeader("Content-Disposition", "filename=\"" + std::string(filename) + "\""); } - stateMachine_.StartStream(contentType.c_str()); + stateMachine_.StartStream(contentType); while (stream.ReadNextChunk()) { @@ -988,7 +988,7 @@ void HttpOutput::StartStream(const std::string& contentType) { - stateMachine_.StartStream(contentType.c_str()); + stateMachine_.StartStream(contentType); } void HttpOutput::SendStreamItem(const void* data, diff -r b4b7b6d39ee3 -r ded8a2be0d46 OrthancFramework/Sources/Images/ImageProcessing.cpp --- a/OrthancFramework/Sources/Images/ImageProcessing.cpp Tue Nov 04 15:58:06 2025 +0100 +++ b/OrthancFramework/Sources/Images/ImageProcessing.cpp Wed Nov 12 19:07:18 2025 +0100 @@ -2161,7 +2161,7 @@ PolygonEdge edge(v1, v2, GetPolygonNextY(points, i)); globalEdgeTable[v1.GetY()].push_back(edge); } - else if (v1.GetY() > v2.GetY()) + else { // Down-going edge PolygonEdge edge(v2, v1, yPrev); diff -r b4b7b6d39ee3 -r ded8a2be0d46 OrthancFramework/Sources/Images/PamReader.h --- a/OrthancFramework/Sources/Images/PamReader.h Tue Nov 04 15:58:06 2025 +0100 +++ b/OrthancFramework/Sources/Images/PamReader.h Wed Nov 12 19:07:18 2025 +0100 @@ -73,7 +73,7 @@ */ explicit PamReader(bool enforceAligned); - virtual ~PamReader(); + virtual ~PamReader() ORTHANC_OVERRIDE; #if ORTHANC_SANDBOXED == 0 void ReadFromFile(const boost::filesystem::path& filename); diff -r b4b7b6d39ee3 -r ded8a2be0d46 OrthancFramework/Sources/Images/PngReader.cpp --- a/OrthancFramework/Sources/Images/PngReader.cpp Tue Nov 04 15:58:06 2025 +0100 +++ b/OrthancFramework/Sources/Images/PngReader.cpp Wed Nov 12 19:07:18 2025 +0100 @@ -102,9 +102,9 @@ } endInfo_ = png_create_info_struct(png_); - if (!info_) + if (!endInfo_) { - png_destroy_read_struct(&png_, &info_, NULL); + png_destroy_read_struct(&png_, &endInfo_, NULL); throw OrthancException(ErrorCode_NotEnoughMemory); } } diff -r b4b7b6d39ee3 -r ded8a2be0d46 OrthancFramework/Sources/JobsEngine/JobsRegistry.cpp --- a/OrthancFramework/Sources/JobsEngine/JobsRegistry.cpp Tue Nov 04 15:58:06 2025 +0100 +++ b/OrthancFramework/Sources/JobsEngine/JobsRegistry.cpp Wed Nov 12 19:07:18 2025 +0100 @@ -375,8 +375,8 @@ }; - bool JobsRegistry::PriorityComparator::operator() (JobHandler* const& a, - JobHandler* const& b) const + bool JobsRegistry::PriorityComparator::operator() (const JobHandler* const& a, + const JobHandler* const& b) const { return a->GetPriority() < b->GetPriority(); } @@ -404,7 +404,7 @@ return false; } - bool JobsRegistry::IsCompletedJob(JobHandler& job) const + bool JobsRegistry::IsCompletedJob(const JobHandler& job) const { for (CompletedJobs::const_iterator it = completedJobs_.begin(); it != completedJobs_.end(); ++it) @@ -918,12 +918,7 @@ { // Success, try and retrieve the status of the job JobsIndex::const_iterator it = jobsIndex_.find(id); - if (it == jobsIndex_.end()) - { - // Should not happen - state = JobState_Failure; - } - else + if (it != jobsIndex_.end()) { const JobStatus& status = it->second->GetLastStatus(); successContent = status.GetPublicContent(); @@ -1597,7 +1592,7 @@ for (JobsIndex::const_iterator it = jobsIndex_.begin(); it != jobsIndex_.end(); ++it) { - JobHandler& job = *it->second; + const JobHandler& job = *it->second; switch (job.GetState()) { diff -r b4b7b6d39ee3 -r ded8a2be0d46 OrthancFramework/Sources/JobsEngine/JobsRegistry.h --- a/OrthancFramework/Sources/JobsEngine/JobsRegistry.h Tue Nov 04 15:58:06 2025 +0100 +++ b/OrthancFramework/Sources/JobsEngine/JobsRegistry.h Wed Nov 12 19:07:18 2025 +0100 @@ -73,8 +73,8 @@ struct PriorityComparator { - bool operator() (JobHandler* const& a, - JobHandler* const& b) const; + bool operator() (const JobHandler* const& a, + const JobHandler* const& b) const; }; typedef std::map JobsIndex; @@ -100,7 +100,7 @@ #ifndef NDEBUG bool IsPendingJob(const JobHandler& job) const; - bool IsCompletedJob(JobHandler& job) const; + bool IsCompletedJob(const JobHandler& job) const; bool IsRetryJob(JobHandler& job) const; #endif diff -r b4b7b6d39ee3 -r ded8a2be0d46 OrthancFramework/Sources/JobsEngine/Operations/SequenceOfOperationsJob.cpp --- a/OrthancFramework/Sources/JobsEngine/Operations/SequenceOfOperationsJob.cpp Tue Nov 04 15:58:06 2025 +0100 +++ b/OrthancFramework/Sources/JobsEngine/Operations/SequenceOfOperationsJob.cpp Wed Nov 12 19:07:18 2025 +0100 @@ -460,7 +460,7 @@ done_(false) { std::string jobType; - GetJobType(jobType); + SequenceOfOperationsJob::GetJobType(jobType); if (SerializationToolbox::ReadString(serialized, TYPE) != jobType || !serialized.isMember(OPERATIONS) || diff -r b4b7b6d39ee3 -r ded8a2be0d46 OrthancFramework/Sources/JobsEngine/Operations/SequenceOfOperationsJob.h --- a/OrthancFramework/Sources/JobsEngine/Operations/SequenceOfOperationsJob.h Tue Nov 04 15:58:06 2025 +0100 +++ b/OrthancFramework/Sources/JobsEngine/Operations/SequenceOfOperationsJob.h Wed Nov 12 19:07:18 2025 +0100 @@ -70,7 +70,7 @@ SequenceOfOperationsJob(IJobUnserializer& unserializer, const Json::Value& serialized); - virtual ~SequenceOfOperationsJob(); + virtual ~SequenceOfOperationsJob() ORTHANC_OVERRIDE; void SetDescription(const std::string& description); diff -r b4b7b6d39ee3 -r ded8a2be0d46 OrthancFramework/Sources/JobsEngine/SetOfCommandsJob.h --- a/OrthancFramework/Sources/JobsEngine/SetOfCommandsJob.h Tue Nov 04 15:58:06 2025 +0100 +++ b/OrthancFramework/Sources/JobsEngine/SetOfCommandsJob.h Wed Nov 12 19:07:18 2025 +0100 @@ -71,7 +71,7 @@ SetOfCommandsJob(ICommandUnserializer* unserializer /* takes ownership */, const Json::Value& source); - virtual ~SetOfCommandsJob(); + virtual ~SetOfCommandsJob() ORTHANC_OVERRIDE; size_t GetPosition() const; @@ -79,7 +79,7 @@ const std::string& GetDescription() const; - void Reserve(size_t size); + virtual void Reserve(size_t size); size_t GetCommandsCount() const; diff -r b4b7b6d39ee3 -r ded8a2be0d46 OrthancFramework/Sources/JobsEngine/SetOfInstancesJob.cpp --- a/OrthancFramework/Sources/JobsEngine/SetOfInstancesJob.cpp Tue Nov 04 15:58:06 2025 +0100 +++ b/OrthancFramework/Sources/JobsEngine/SetOfInstancesJob.cpp Wed Nov 12 19:07:18 2025 +0100 @@ -106,7 +106,7 @@ { } - virtual ICommand* Unserialize(const Json::Value& source) const + virtual ICommand* Unserialize(const Json::Value& source) const ORTHANC_OVERRIDE { if (source.type() == Json::nullValue) { diff -r b4b7b6d39ee3 -r ded8a2be0d46 OrthancFramework/Sources/MallocMemoryBuffer.h --- a/OrthancFramework/Sources/MallocMemoryBuffer.h Tue Nov 04 15:58:06 2025 +0100 +++ b/OrthancFramework/Sources/MallocMemoryBuffer.h Wed Nov 12 19:07:18 2025 +0100 @@ -45,7 +45,7 @@ public: MallocMemoryBuffer(); - virtual ~MallocMemoryBuffer() + virtual ~MallocMemoryBuffer() ORTHANC_OVERRIDE { Clear(); } diff -r b4b7b6d39ee3 -r ded8a2be0d46 OrthancFramework/Sources/MultiThreading/IRunnableBySteps.h --- a/OrthancFramework/Sources/MultiThreading/IRunnableBySteps.h Tue Nov 04 15:58:06 2025 +0100 +++ b/OrthancFramework/Sources/MultiThreading/IRunnableBySteps.h Wed Nov 12 19:07:18 2025 +0100 @@ -25,13 +25,14 @@ #pragma once #include "../IDynamicObject.h" +#include "../Compatibility.h" namespace Orthanc { class IRunnableBySteps : public IDynamicObject { public: - virtual ~IRunnableBySteps() + virtual ~IRunnableBySteps() ORTHANC_OVERRIDE { } diff -r b4b7b6d39ee3 -r ded8a2be0d46 OrthancFramework/Sources/RestApi/RestApiHierarchy.cpp --- a/OrthancFramework/Sources/RestApi/RestApiHierarchy.cpp Tue Nov 04 15:58:06 2025 +0100 +++ b/OrthancFramework/Sources/RestApi/RestApiHierarchy.cpp Wed Nov 12 19:07:18 2025 +0100 @@ -169,9 +169,9 @@ } - void RestApiHierarchy::DeleteChildren(Children& children) + void RestApiHierarchy::DeleteChildren(const Children& children) { - for (Children::iterator it = children.begin(); + for (Children::const_iterator it = children.begin(); it != children.end(); ++it) { delete it->second; diff -r b4b7b6d39ee3 -r ded8a2be0d46 OrthancFramework/Sources/RestApi/RestApiHierarchy.h --- a/OrthancFramework/Sources/RestApi/RestApiHierarchy.h Tue Nov 04 15:58:06 2025 +0100 +++ b/OrthancFramework/Sources/RestApi/RestApiHierarchy.h Wed Nov 12 19:07:18 2025 +0100 @@ -97,7 +97,7 @@ static RestApiHierarchy& AddChild(Children& children, const std::string& name); - static void DeleteChildren(Children& children); + static void DeleteChildren(const Children& children); template void RegisterInternal(const RestApiPath& path, diff -r b4b7b6d39ee3 -r ded8a2be0d46 OrthancFramework/Sources/SystemToolbox.cpp --- a/OrthancFramework/Sources/SystemToolbox.cpp Tue Nov 04 15:58:06 2025 +0100 +++ b/OrthancFramework/Sources/SystemToolbox.cpp Wed Nov 12 19:07:18 2025 +0100 @@ -1029,27 +1029,25 @@ boost::filesystem::path SystemToolbox::InterpretRelativePath(const boost::filesystem::path& baseDirectory, - const std::string& relativePath) + const boost::filesystem::path& relativePath) { - boost::filesystem::path relative = SystemToolbox::PathFromUtf8(relativePath); - /** The following lines should be equivalent to this one: - return (base / relative); + return (baseDirectory / relativePath); However, for some unknown reason, some versions of Boost do not make the proper path resolution when "baseDirectory" is an absolute path. So, a hack is used below. **/ - if (relative.is_absolute()) + if (relativePath.is_absolute()) { - return relative; + return relativePath; } else { - return baseDirectory / relative; + return baseDirectory / relativePath; } } diff -r b4b7b6d39ee3 -r ded8a2be0d46 OrthancFramework/Sources/SystemToolbox.h --- a/OrthancFramework/Sources/SystemToolbox.h Tue Nov 04 15:58:06 2025 +0100 +++ b/OrthancFramework/Sources/SystemToolbox.h Wed Nov 12 19:07:18 2025 +0100 @@ -136,7 +136,7 @@ static void GetEnvironmentVariables(std::map& env); static boost::filesystem::path InterpretRelativePath(const boost::filesystem::path& baseDirectory, - const std::string& relativePath); + const boost::filesystem::path& relativePath); static void ReadFileRange(std::string& content, const boost::filesystem::path& path, diff -r b4b7b6d39ee3 -r ded8a2be0d46 OrthancFramework/Sources/Toolbox.cpp --- a/OrthancFramework/Sources/Toolbox.cpp Tue Nov 04 15:58:06 2025 +0100 +++ b/OrthancFramework/Sources/Toolbox.cpp Wed Nov 12 19:07:18 2025 +0100 @@ -2935,17 +2935,10 @@ else if (ORTHANC_SCANF(version, "%4d", &a) == 1 && a >= 0) { - if (a >= 0) - { - major = static_cast(a); - minor = 0; - revision = 0; - return true; - } - else - { - return false; - } + major = static_cast(a); + minor = 0; + revision = 0; + return true; } else { diff -r b4b7b6d39ee3 -r ded8a2be0d46 OrthancFramework/UnitTestsSources/FileStorageTests.cpp --- a/OrthancFramework/UnitTestsSources/FileStorageTests.cpp Tue Nov 04 15:58:06 2025 +0100 +++ b/OrthancFramework/UnitTestsSources/FileStorageTests.cpp Wed Nov 12 19:07:18 2025 +0100 @@ -59,7 +59,7 @@ std::string data = Toolbox::GenerateUuid(); std::string uid = Toolbox::GenerateUuid(); - s.Create(uid.c_str(), &data[0], data.size(), FileContentType_Unknown); + s.Create(uid, &data[0], data.size(), FileContentType_Unknown); std::string d; { std::unique_ptr buffer(s.ReadWhole(uid, FileContentType_Unknown)); @@ -83,7 +83,7 @@ std::vector data; StringToVector(data, Toolbox::GenerateUuid()); std::string uid = Toolbox::GenerateUuid(); - s.Create(uid.c_str(), &data[0], data.size(), FileContentType_Unknown); + s.Create(uid, &data[0], data.size(), FileContentType_Unknown); std::string d; { std::unique_ptr buffer(s.ReadWhole(uid, FileContentType_Unknown)); @@ -167,7 +167,7 @@ { std::string t = Toolbox::GenerateUuid(); std::string uid = Toolbox::GenerateUuid(); - s.Create(uid.c_str(), &t[0], t.size(), FileContentType_Unknown); + s.Create(uid, &t[0], t.size(), FileContentType_Unknown); u.push_back(uid); } diff -r b4b7b6d39ee3 -r ded8a2be0d46 OrthancFramework/UnitTestsSources/FrameworkTests.cpp --- a/OrthancFramework/UnitTestsSources/FrameworkTests.cpp Tue Nov 04 15:58:06 2025 +0100 +++ b/OrthancFramework/UnitTestsSources/FrameworkTests.cpp Wed Nov 12 19:07:18 2025 +0100 @@ -499,7 +499,7 @@ // This is a Latin-1 test string const unsigned char data[10] = { 0xe0, 0xe9, 0xea, 0xe7, 0x26, 0xc6, 0x61, 0x62, 0x63, 0x00 }; - std::string s((char*) &data[0], 10); + std::string s(reinterpret_cast(&data[0]), 10); ASSERT_EQ("&abc", Toolbox::ConvertToAscii(s)); // Open in Emacs, then save with UTF-8 encoding, then "hexdump -C" @@ -528,7 +528,7 @@ // This is a Latin-1 test string: "crane" with a circumflex accent const unsigned char latin1[] = { 0x63, 0x72, 0xe2, 0x6e, 0x65 }; - std::string s((char*) &latin1[0], sizeof(latin1) / sizeof(char)); + std::string s(reinterpret_cast(&latin1[0]), sizeof(latin1) / sizeof(char)); ASSERT_EQ(s, Toolbox::ConvertFromUtf8(Toolbox::ConvertToUtf8(s, Encoding_Latin1, false, false), Encoding_Latin1)); ASSERT_EQ("cre", Toolbox::ConvertToUtf8(s, Encoding_Utf8, false, false)); @@ -539,7 +539,7 @@ size_t size, size_t expectedLength) { - std::string s((char*) &data[0], size); + std::string s(reinterpret_cast(&data[0]), size); uint32_t unicode; size_t length; Toolbox::Utf8ToUnicodeCharacter(unicode, length, s, 0); diff -r b4b7b6d39ee3 -r ded8a2be0d46 OrthancFramework/UnitTestsSources/FromDcmtkTests.cpp --- a/OrthancFramework/UnitTestsSources/FromDcmtkTests.cpp Tue Nov 04 15:58:06 2025 +0100 +++ b/OrthancFramework/UnitTestsSources/FromDcmtkTests.cpp Wed Nov 12 19:07:18 2025 +0100 @@ -3630,9 +3630,9 @@ std::string source; Orthanc::SystemToolbox::ReadFile(source, SystemToolbox::PathFromUtf8(path)); - std::string c, k; try { + std::string c, k; scu.Transcode(c, k, transcoder, source.c_str(), source.size(), DicomTransferSyntax_LittleEndianExplicit, false, "", 0); } @@ -3678,8 +3678,6 @@ std::set s; s.insert(a); - std::string t; - IDicomTranscoder::DicomImage source, target; source.AcquireParsed(dynamic_cast(toto->clone())); @@ -3698,7 +3696,6 @@ a == DicomTransferSyntax_JPEGProcess2_4 || a == DicomTransferSyntax_JPEGLSLossy); - printf("SIZE: %d\n", static_cast(t.size())); if (sourceUid == IDicomTranscoder::GetSopInstanceUid(target.GetParsed())) { ASSERT_FALSE(lossy); diff -r b4b7b6d39ee3 -r ded8a2be0d46 OrthancFramework/UnitTestsSources/ImageTests.cpp --- a/OrthancFramework/UnitTestsSources/ImageTests.cpp Tue Nov 04 15:58:06 2025 +0100 +++ b/OrthancFramework/UnitTestsSources/ImageTests.cpp Wed Nov 12 19:07:18 2025 +0100 @@ -252,7 +252,7 @@ v = 0; for (unsigned int y = 0; y < height; y++) { - const uint16_t *p = reinterpret_cast((const uint8_t*) r.GetConstBuffer() + y * r.GetPitch()); + const uint16_t *p = reinterpret_cast(reinterpret_cast(r.GetConstBuffer()) + y * r.GetPitch()); ASSERT_EQ(p, r.GetConstRow(y)); for (unsigned int x = 0; x < width; x++, p++, v++) { @@ -276,7 +276,7 @@ v = 0; for (unsigned int y = 0; y < height; y++) { - const uint16_t *p = reinterpret_cast((const uint8_t*) r2.GetConstBuffer() + y * r2.GetPitch()); + const uint16_t *p = reinterpret_cast(reinterpret_cast(r2.GetConstBuffer()) + y * r2.GetPitch()); ASSERT_EQ(p, r2.GetConstRow(y)); for (unsigned int x = 0; x < width; x++, p++, v++) { @@ -500,7 +500,7 @@ for (unsigned int y = 0; y < height; y++) { const uint16_t *p = reinterpret_cast - ((const uint8_t*) r.GetConstBuffer() + y * r.GetPitch()); + (reinterpret_cast(r.GetConstBuffer()) + y * r.GetPitch()); ASSERT_EQ(p, r.GetConstRow(y)); for (unsigned int x = 0; x < width; x++, p++, v++) { @@ -522,7 +522,7 @@ for (unsigned int y = 0; y < height; y++) { const uint16_t* p = reinterpret_cast - ((const uint8_t*)r.GetConstBuffer() + y * r.GetPitch()); + (reinterpret_cast(r.GetConstBuffer()) + y * r.GetPitch()); ASSERT_EQ(p, r.GetConstRow(y)); for (unsigned int x = 0; x < width; x++, p++, v++) { @@ -547,7 +547,7 @@ for (unsigned int y = 0; y < height; y++) { const uint16_t *p = reinterpret_cast - ((const uint8_t*) r2.GetConstBuffer() + y * r2.GetPitch()); + (reinterpret_cast(r2.GetConstBuffer()) + y * r2.GetPitch()); ASSERT_EQ(p, r2.GetConstRow(y)); for (unsigned int x = 0; x < width; x++, p++, v++) { @@ -574,7 +574,7 @@ for (unsigned int y = 0; y < height; y++) { const uint16_t* p = reinterpret_cast - ((const uint8_t*)r2.GetConstBuffer() + y * r2.GetPitch()); + (reinterpret_cast(r2.GetConstBuffer()) + y * r2.GetPitch()); ASSERT_EQ(p, r2.GetConstRow(y)); for (unsigned int x = 0; x < width; x++, p++, v++) { diff -r b4b7b6d39ee3 -r ded8a2be0d46 OrthancFramework/UnitTestsSources/JobsTests.cpp --- a/OrthancFramework/UnitTestsSources/JobsTests.cpp Tue Nov 04 15:58:06 2025 +0100 +++ b/OrthancFramework/UnitTestsSources/JobsTests.cpp Wed Nov 12 19:07:18 2025 +0100 @@ -282,10 +282,10 @@ TEST(MultiThreading, SharedMessageQueueClean) { - std::set s; - try { + std::set s; + SharedMessageQueue q; q.Enqueue(new DynamicInteger(10, s)); q.Enqueue(new DynamicInteger(20, s)); @@ -733,25 +733,26 @@ } -TEST(JobsEngine, DISABLED_SequenceOfOperationsJob) +TEST(JobsEngine, SequenceOfOperationsJob) { JobsEngine engine(10); engine.SetThreadSleep(10); engine.SetWorkersCount(3); engine.Start(); - std::string id; - SequenceOfOperationsJob* job = NULL; - { - std::unique_ptr a(new SequenceOfOperationsJob); - job = a.get(); - engine.GetRegistry().Submit(id, a.release(), 0); + std::string id; + std::unique_ptr seq(new SequenceOfOperationsJob); + engine.GetRegistry().Submit(id, seq.release(), 0); } boost::this_thread::sleep(boost::posix_time::milliseconds(500)); { + SequenceOfOperationsJob* job = NULL; + std::unique_ptr seq(new SequenceOfOperationsJob); + job = seq.get(); + SequenceOfOperationsJob::Lock lock(*job); size_t i = lock.AddOperation(new LogJobOperation); size_t j = lock.AddOperation(new LogJobOperation); @@ -1137,9 +1138,9 @@ TEST(JobsSerialization, Registry) { Json::Value s; - std::string i1, i2; { + std::string i1, i2; JobsRegistry registry(10); registry.Submit(i1, new DummyJob(), 10); registry.Submit(i2, new SequenceOfOperationsJob(), 30); diff -r b4b7b6d39ee3 -r ded8a2be0d46 OrthancFramework/UnitTestsSources/RestApiTests.cpp --- a/OrthancFramework/UnitTestsSources/RestApiTests.cpp Tue Nov 04 15:58:06 2025 +0100 +++ b/OrthancFramework/UnitTestsSources/RestApiTests.cpp Wed Nov 12 19:07:18 2025 +0100 @@ -402,7 +402,7 @@ void Reset() { HttpContentNegociation::Dictionary parameters; - Handle("nope", "nope", parameters); + AcceptHandler::Handle("nope", "nope", parameters); } const std::string& GetType() const diff -r b4b7b6d39ee3 -r ded8a2be0d46 OrthancServer/OrthancExplorer/explorer.js --- a/OrthancServer/OrthancExplorer/explorer.js Tue Nov 04 15:58:06 2025 +0100 +++ b/OrthancServer/OrthancExplorer/explorer.js Wed Nov 12 19:07:18 2025 +0100 @@ -173,8 +173,8 @@ var authorizationTokens = GetAuthorizationTokensFromUrl(); -/* Copy the authoziation toekn from the url search parameters into HTTP headers in every request to the Rest API. -Thanks to this behaviour, you may specify a ?token=xxx in your url and this will be passed +/* Copy the authoziation token from the url search parameters into HTTP headers in every request to the REST API. +Thanks to this behaviour, you may specify a ?token=xxx in your url and this will be passed as the "token" header in every request to the API allowing you to use the authorization plugin */ $.ajaxSetup( { diff -r b4b7b6d39ee3 -r ded8a2be0d46 OrthancServer/Plugins/Engine/OrthancPluginDatabase.cpp --- a/OrthancServer/Plugins/Engine/OrthancPluginDatabase.cpp Tue Nov 04 15:58:06 2025 +0100 +++ b/OrthancServer/Plugins/Engine/OrthancPluginDatabase.cpp Wed Nov 12 19:07:18 2025 +0100 @@ -238,7 +238,7 @@ ResetAnswers(); } - virtual ~Transaction() + virtual ~Transaction() ORTHANC_OVERRIDE { assert(that_.activeTransaction_ != NULL); that_.activeTransaction_ = NULL; diff -r b4b7b6d39ee3 -r ded8a2be0d46 OrthancServer/Plugins/Engine/OrthancPluginDatabaseV3.h --- a/OrthancServer/Plugins/Engine/OrthancPluginDatabaseV3.h Tue Nov 04 15:58:06 2025 +0100 +++ b/OrthancServer/Plugins/Engine/OrthancPluginDatabaseV3.h Wed Nov 12 19:07:18 2025 +0100 @@ -54,7 +54,7 @@ void* database, const std::string& serverIdentifier); - virtual ~OrthancPluginDatabaseV3(); + virtual ~OrthancPluginDatabaseV3() ORTHANC_OVERRIDE; virtual void Open() ORTHANC_OVERRIDE; diff -r b4b7b6d39ee3 -r ded8a2be0d46 OrthancServer/Plugins/Engine/OrthancPluginDatabaseV4.cpp --- a/OrthancServer/Plugins/Engine/OrthancPluginDatabaseV4.cpp Tue Nov 04 15:58:06 2025 +0100 +++ b/OrthancServer/Plugins/Engine/OrthancPluginDatabaseV4.cpp Wed Nov 12 19:07:18 2025 +0100 @@ -527,7 +527,7 @@ } - virtual ~Transaction() + virtual ~Transaction() ORTHANC_OVERRIDE { try { diff -r b4b7b6d39ee3 -r ded8a2be0d46 OrthancServer/Plugins/Engine/OrthancPluginDatabaseV4.h --- a/OrthancServer/Plugins/Engine/OrthancPluginDatabaseV4.h Tue Nov 04 15:58:06 2025 +0100 +++ b/OrthancServer/Plugins/Engine/OrthancPluginDatabaseV4.h Wed Nov 12 19:07:18 2025 +0100 @@ -53,7 +53,7 @@ const _OrthancPluginRegisterDatabaseBackendV4& database, const std::string& serverIdentifier); - virtual ~OrthancPluginDatabaseV4(); + virtual ~OrthancPluginDatabaseV4() ORTHANC_OVERRIDE; const _OrthancPluginRegisterDatabaseBackendV4& GetDefinition() const { diff -r b4b7b6d39ee3 -r ded8a2be0d46 OrthancServer/Plugins/Engine/OrthancPlugins.cpp --- a/OrthancServer/Plugins/Engine/OrthancPlugins.cpp Tue Nov 04 15:58:06 2025 +0100 +++ b/OrthancServer/Plugins/Engine/OrthancPlugins.cpp Wed Nov 12 19:07:18 2025 +0100 @@ -1562,7 +1562,7 @@ } } - virtual ~Handler() + virtual ~Handler() ORTHANC_OVERRIDE { assert(handler_ != NULL); parameters_.destructor(handler_); @@ -5888,7 +5888,7 @@ case _OrthancPluginService_KeysValuesIteratorGetKey: { const _OrthancPluginKeysValuesIteratorGetKey& p = *reinterpret_cast(parameters); - StatelessDatabaseOperations::KeysValuesIterator& iterator = *reinterpret_cast(p.iterator); + const StatelessDatabaseOperations::KeysValuesIterator& iterator = *reinterpret_cast(p.iterator); *p.target = iterator.GetKey().c_str(); return true; } @@ -5896,7 +5896,7 @@ case _OrthancPluginService_KeysValuesIteratorGetValue: { const _OrthancPluginKeysValuesIteratorGetValue& p = *reinterpret_cast(parameters); - StatelessDatabaseOperations::KeysValuesIterator& iterator = *reinterpret_cast(p.iterator); + const StatelessDatabaseOperations::KeysValuesIterator& iterator = *reinterpret_cast(p.iterator); CopyToMemoryBuffer(p.target, iterator.GetValue()); return true; } @@ -6687,7 +6687,7 @@ assert(reader_ != NULL); } - virtual ~HttpServerChunkedReader() + virtual ~HttpServerChunkedReader() ORTHANC_OVERRIDE { assert(reader_ != NULL); parameters_.finalize(reader_); diff -r b4b7b6d39ee3 -r ded8a2be0d46 OrthancServer/Plugins/Engine/OrthancPlugins.h --- a/OrthancServer/Plugins/Engine/OrthancPlugins.h Tue Nov 04 15:58:06 2025 +0100 +++ b/OrthancServer/Plugins/Engine/OrthancPlugins.h Wed Nov 12 19:07:18 2025 +0100 @@ -281,7 +281,7 @@ public: explicit OrthancPlugins(const std::string& databaseServerIdentifier); - virtual ~OrthancPlugins(); + virtual ~OrthancPlugins() ORTHANC_OVERRIDE; void SetServerContext(ServerContext& context); diff -r b4b7b6d39ee3 -r ded8a2be0d46 OrthancServer/Plugins/Engine/PluginMemoryBuffer32.h --- a/OrthancServer/Plugins/Engine/PluginMemoryBuffer32.h Tue Nov 04 15:58:06 2025 +0100 +++ b/OrthancServer/Plugins/Engine/PluginMemoryBuffer32.h Wed Nov 12 19:07:18 2025 +0100 @@ -44,7 +44,7 @@ public: PluginMemoryBuffer32(); - virtual ~PluginMemoryBuffer32() + virtual ~PluginMemoryBuffer32() ORTHANC_OVERRIDE { Clear(); } diff -r b4b7b6d39ee3 -r ded8a2be0d46 OrthancServer/Plugins/Engine/PluginMemoryBuffer64.h --- a/OrthancServer/Plugins/Engine/PluginMemoryBuffer64.h Tue Nov 04 15:58:06 2025 +0100 +++ b/OrthancServer/Plugins/Engine/PluginMemoryBuffer64.h Wed Nov 12 19:07:18 2025 +0100 @@ -44,7 +44,7 @@ public: PluginMemoryBuffer64(); - virtual ~PluginMemoryBuffer64() + virtual ~PluginMemoryBuffer64() ORTHANC_OVERRIDE { Clear(); } diff -r b4b7b6d39ee3 -r ded8a2be0d46 OrthancServer/Plugins/Engine/PluginsJob.h --- a/OrthancServer/Plugins/Engine/PluginsJob.h Tue Nov 04 15:58:06 2025 +0100 +++ b/OrthancServer/Plugins/Engine/PluginsJob.h Wed Nov 12 19:07:18 2025 +0100 @@ -47,7 +47,7 @@ explicit PluginsJob(const _OrthancPluginCreateJob2& parameters); - virtual ~PluginsJob(); + virtual ~PluginsJob() ORTHANC_OVERRIDE; virtual void Start() ORTHANC_OVERRIDE { diff -r b4b7b6d39ee3 -r ded8a2be0d46 OrthancServer/Plugins/Samples/Basic/Plugin.c --- a/OrthancServer/Plugins/Samples/Basic/Plugin.c Tue Nov 04 15:58:06 2025 +0100 +++ b/OrthancServer/Plugins/Samples/Basic/Plugin.c Wed Nov 12 19:07:18 2025 +0100 @@ -239,7 +239,7 @@ else { const char* studyId = request->groups[0]; - int32_t statusHasChanged = 0; + uint8_t statusHasChanged = 0; if (strcmp(request->groups[1], "stabilize") == 0) { diff -r b4b7b6d39ee3 -r ded8a2be0d46 OrthancServer/Plugins/Samples/Housekeeper/Plugin.cpp --- a/OrthancServer/Plugins/Samples/Housekeeper/Plugin.cpp Tue Nov 04 15:58:06 2025 +0100 +++ b/OrthancServer/Plugins/Samples/Housekeeper/Plugin.cpp Wed Nov 12 19:07:18 2025 +0100 @@ -107,7 +107,7 @@ bool isInPeriod() const { time_t now = time(NULL); - tm* nowLocalTime = localtime(&now); + const tm* nowLocalTime = localtime(&now); if (nowLocalTime->tm_wday != weekday_) { diff -r b4b7b6d39ee3 -r ded8a2be0d46 OrthancServer/Plugins/Samples/ModalityWorklists/Plugin.cpp --- a/OrthancServer/Plugins/Samples/ModalityWorklists/Plugin.cpp Tue Nov 04 15:58:06 2025 +0100 +++ b/OrthancServer/Plugins/Samples/ModalityWorklists/Plugin.cpp Wed Nov 12 19:07:18 2025 +0100 @@ -76,12 +76,11 @@ Worklist(const std::string& id, const Json::Value& jsonWl) : - id_(id) + id_(id), + createdAt_(jsonWl["CreatedAt"].asString()) { - createdAt_ = jsonWl["CreatedAt"].asString(); std::string b64DicomContent = jsonWl["Dicom"].asString(); Orthanc::Toolbox::DecodeBase64(dicomContent_, b64DicomContent); - } void Serialize(std::string& target) const diff -r b4b7b6d39ee3 -r ded8a2be0d46 OrthancServer/Plugins/Samples/MultitenantDicom/StoreRequestHandler.cpp --- a/OrthancServer/Plugins/Samples/MultitenantDicom/StoreRequestHandler.cpp Tue Nov 04 15:58:06 2025 +0100 +++ b/OrthancServer/Plugins/Samples/MultitenantDicom/StoreRequestHandler.cpp Wed Nov 12 19:07:18 2025 +0100 @@ -36,7 +36,9 @@ uint16_t StoreRequestHandler::Handle(DcmDataset& dicom, const std::string& remoteIp, const std::string& remoteAet, - const std::string& calledAet) + const std::string& calledAet, + uint16_t originatorMessageId, + const std::string& originatorAet) { std::string buffer; std::string errorMessage; diff -r b4b7b6d39ee3 -r ded8a2be0d46 OrthancServer/Plugins/Samples/MultitenantDicom/StoreRequestHandler.h --- a/OrthancServer/Plugins/Samples/MultitenantDicom/StoreRequestHandler.h Tue Nov 04 15:58:06 2025 +0100 +++ b/OrthancServer/Plugins/Samples/MultitenantDicom/StoreRequestHandler.h Wed Nov 12 19:07:18 2025 +0100 @@ -44,5 +44,7 @@ virtual uint16_t Handle(DcmDataset& dicom, const std::string& remoteIp, const std::string& remoteAet, - const std::string& calledAet) ORTHANC_OVERRIDE; + const std::string& calledAet, + uint16_t originatorMessageId, + const std::string& originatorAet) ORTHANC_OVERRIDE; }; diff -r b4b7b6d39ee3 -r ded8a2be0d46 OrthancServer/Resources/RunCppCheck.sh --- a/OrthancServer/Resources/RunCppCheck.sh Tue Nov 04 15:58:06 2025 +0100 +++ b/OrthancServer/Resources/RunCppCheck.sh Wed Nov 12 19:07:18 2025 +0100 @@ -24,11 +24,8 @@ syntaxError:../../OrthancFramework/Sources/SQLite/FunctionContext.h:53 syntaxError:../../OrthancFramework/UnitTestsSources/DicomMapTests.cpp:74 syntaxError:../../OrthancFramework/UnitTestsSources/ZipTests.cpp:133 -syntaxError:../../OrthancServer/UnitTestsSources/UnitTestsMain.cpp:322 -uninitMemberVar:../../OrthancServer/Sources/ServerJobs/StorageCommitmentScpJob.cpp:417 -unreadVariable:../../OrthancFramework/Sources/FileStorage/StorageAccessor.cpp -unreadVariable:../../OrthancServer/Sources/OrthancRestApi/OrthancRestModalities.cpp:1173 -unusedFunction +syntaxError:../../OrthancServer/UnitTestsSources/UnitTestsMain.cpp:325 +uninitMemberVar:../../OrthancServer/Sources/ServerJobs/StorageCommitmentScpJob.cpp:419 useInitializationList:../../OrthancFramework/Sources/Images/PngReader.cpp:91 useInitializationList:../../OrthancFramework/Sources/Images/PngWriter.cpp:99 useInitializationList:../../OrthancServer/Sources/ServerJobs/DicomModalityStoreJob.cpp:275 @@ -39,9 +36,46 @@ assertWithSideEffect:../../OrthancServer/Sources/Database/StatelessDatabaseOperations.cpp:3066 assertWithSideEffect:../../OrthancServer/Sources/ServerJobs/ResourceModificationJob.cpp:286 assertWithSideEffect:../../OrthancFramework/Sources/DicomNetworking/Internals/CommandDispatcher.cpp:454 +variableScope:../../OrthancServer/Sources/OrthancRestApi/OrthancRestApi.cpp:228 +variableScope:../../OrthancServer/Sources/ServerJobs/OrthancPeerStoreJob.cpp:94 +uselessOverride:../../OrthancFramework/Sources/MultiThreading/IRunnableBySteps.h:35 +uselessOverride:../../OrthancFramework/Sources/JobsEngine/SetOfInstancesJob.h:76 +cstyleCast:../../OrthancServer/Plugins/Engine/PluginsManager.cpp:85 +cstyleCast:../../OrthancServer/Plugins/Engine/PluginsManager.cpp:108 +cstyleCast:../../OrthancServer/Plugins/Engine/PluginsManager.cpp:124 +cstyleCast:../../OrthancServer/Plugins/Engine/PluginsManager.cpp:140 +constParameterPointer:../../OrthancServer/Plugins/Include/orthanc/OrthancCPlugin.h +constParameterPointer:../../OrthancFramework/Sources/Logging.cpp:447 +constParameterPointer:../../OrthancFramework/Sources/Logging.cpp:451 +constParameterPointer:../../OrthancFramework/Sources/Toolbox.cpp:3053 +knownConditionTrueFalse:../../OrthancFramework/Sources/DicomNetworking/Internals/CommandDispatcher.cpp:114 +knownConditionTrueFalse:../../OrthancFramework/Sources/DicomParsing/Internals/DicomImageDecoder.cpp:425 +knownConditionTrueFalse:../../OrthancFramework/Sources/JobsEngine/Operations/SequenceOfOperationsJob.cpp:345 +throwInNoexceptFunction:../../OrthancFramework/Sources/Cache/MemoryStringCache.cpp:121 +throwInNoexceptFunction:../../OrthancFramework/Sources/Cache/MemoryCache.cpp:91 +throwInNoexceptFunction:../../OrthancFramework/Sources/Cache/MemoryObjectCache.cpp:99 +throwInNoexceptFunction:../../OrthancFramework/Sources/MallocMemoryBuffer.h:50 +throwInNoexceptFunction:../../OrthancFramework/Sources/MetricsRegistry.cpp:620 +throwInNoexceptFunction:../../OrthancFramework/Sources/MetricsRegistry.cpp:632 +throwInNoexceptFunction:../../OrthancFramework/Sources/MetricsRegistry.cpp:676 +throwInNoexceptFunction:../../OrthancFramework/Sources/JobsEngine/JobsRegistry.cpp:1296 +throwInNoexceptFunction:../../OrthancServer/Sources/LuaScripting.cpp:830 +throwInNoexceptFunction:../../OrthancServer/Sources/StorageCommitmentReports.cpp:187 +throwInNoexceptFunction:../../OrthancServer/Plugins/Samples/Common/OrthancPluginCppWrapper.h:218 +throwInNoexceptFunction:../../OrthancServer/Plugins/Samples/Common/OrthancPluginCppWrapper.h:376 +throwInNoexceptFunction:../../OrthancServer/Plugins/Samples/Common/OrthancPluginCppWrapper.h:496 +rethrowNoCurrentException:../../OrthancFramework/UnitTestsSources/FromDcmtkTests.cpp +rethrowNoCurrentException:../../OrthancFramework/UnitTestsSources/RestApiTests.cpp EOF -${CPPCHECK} --enable=all --quiet --std=c++11 \ +# TODO: re-enable nullPointerOutOfMemory + +${CPPCHECK} -j 8 --enable=all --quiet --std=c++11 \ + --suppress=missingIncludeSystem \ + --suppress=missingInclude \ + --suppress=useStlAlgorithm \ + --suppress=nullPointerOutOfMemory \ + --check-level=exhaustive \ --suppressions-list=/tmp/cppcheck-suppressions.txt \ -DBOOST_HAS_DATE_TIME=1 \ -DBOOST_HAS_FILESYSTEM_V3=1 \ diff -r b4b7b6d39ee3 -r ded8a2be0d46 OrthancServer/Sources/Database/Compatibility/GenericFind.cpp --- a/OrthancServer/Sources/Database/Compatibility/GenericFind.cpp Tue Nov 04 15:58:06 2025 +0100 +++ b/OrthancServer/Sources/Database/Compatibility/GenericFind.cpp Wed Nov 12 19:07:18 2025 +0100 @@ -187,7 +187,6 @@ request.GetOrthancIdentifiers(), ResourceType_Series, request.GetLevel()); } else if (request.GetMetadataConstraintsCount() == 0 && - request.GetOrdering().empty() && !request.GetOrthancIdentifiers().HasPatientId() && !request.GetOrthancIdentifiers().HasStudyId() && !request.GetOrthancIdentifiers().HasSeriesId() && diff -r b4b7b6d39ee3 -r ded8a2be0d46 OrthancServer/Sources/Database/PrepareDatabase.sql --- a/OrthancServer/Sources/Database/PrepareDatabase.sql Tue Nov 04 15:58:06 2025 +0100 +++ b/OrthancServer/Sources/Database/PrepareDatabase.sql Wed Nov 12 19:07:18 2025 +0100 @@ -160,4 +160,4 @@ -- Set the version of the database schema -- The "1" corresponds to the "GlobalProperty_DatabaseSchemaVersion" enumeration -INSERT INTO GlobalProperties VALUES (1, "6"); +INSERT INTO GlobalProperties VALUES (1, 6); diff -r b4b7b6d39ee3 -r ded8a2be0d46 OrthancServer/Sources/LuaScripting.cpp --- a/OrthancServer/Sources/LuaScripting.cpp Tue Nov 04 15:58:06 2025 +0100 +++ b/OrthancServer/Sources/LuaScripting.cpp Wed Nov 12 19:07:18 2025 +0100 @@ -1034,7 +1034,7 @@ return true; } - bool LuaScripting::FilterIncomingCStoreInstance(uint16_t& dimseStatus, + bool LuaScripting::FilterIncomingCStoreInstance(uint16_t& /*dimseStatus*/, const DicomInstanceToStore& instance, const Json::Value& simplified) { diff -r b4b7b6d39ee3 -r ded8a2be0d46 OrthancServer/Sources/OrthancFindRequestHandler.cpp --- a/OrthancServer/Sources/OrthancFindRequestHandler.cpp Tue Nov 04 15:58:06 2025 +0100 +++ b/OrthancServer/Sources/OrthancFindRequestHandler.cpp Wed Nov 12 19:07:18 2025 +0100 @@ -82,8 +82,7 @@ } - bool OrthancFindRequestHandler::FilterQueryTag(std::string& value /* can be modified */, - ResourceType level, + bool OrthancFindRequestHandler::FilterQueryTag(ResourceType level, const DicomTag& tag, ModalityManufacturer manufacturer) { @@ -417,7 +416,7 @@ continue; } - if (FilterQueryTag(value, level, tag, manufacturer)) + if (FilterQueryTag(level, tag, manufacturer)) { ValueRepresentation vr = FromDcmtkBridge::LookupValueRepresentation(tag); diff -r b4b7b6d39ee3 -r ded8a2be0d46 OrthancServer/Sources/OrthancFindRequestHandler.h --- a/OrthancServer/Sources/OrthancFindRequestHandler.h Tue Nov 04 15:58:06 2025 +0100 +++ b/OrthancServer/Sources/OrthancFindRequestHandler.h Wed Nov 12 19:07:18 2025 +0100 @@ -40,8 +40,7 @@ bool HasReachedLimit(const DicomFindAnswers& answers, ResourceType level) const; - bool FilterQueryTag(std::string& value /* can be modified */, - ResourceType level, + bool FilterQueryTag(ResourceType level, const DicomTag& tag, ModalityManufacturer manufacturer); diff -r b4b7b6d39ee3 -r ded8a2be0d46 OrthancServer/Sources/OrthancGetRequestHandler.cpp --- a/OrthancServer/Sources/OrthancGetRequestHandler.cpp Tue Nov 04 15:58:06 2025 +0100 +++ b/OrthancServer/Sources/OrthancGetRequestHandler.cpp Wed Nov 12 19:07:18 2025 +0100 @@ -133,13 +133,14 @@ LST_HEAD **l = &assoc->params->DULparams.acceptedPresentationContext; if (*l != NULL) { - DUL_PRESENTATIONCONTEXT* pc = (DUL_PRESENTATIONCONTEXT*) LST_Head(l); - LST_Position(l, (LST_NODE*)pc); + DUL_PRESENTATIONCONTEXT* pc = reinterpret_cast(LST_Head(l)); + LST_Position(l, reinterpret_cast(pc)); while (pc) { - DicomTransferSyntax transferSyntax; if (pc->result == ASC_P_ACCEPTANCE) { + DicomTransferSyntax transferSyntax; + if (LookupTransferSyntax(transferSyntax, pc->acceptedTransferSyntax)) { /*CLOG(TRACE, DICOM) << "C-GET SCP accepted: SOP class " << pc->abstractSyntax @@ -156,7 +157,7 @@ } } - pc = (DUL_PRESENTATIONCONTEXT*) LST_Next(l); + pc = reinterpret_cast(LST_Next(l)); } } diff -r b4b7b6d39ee3 -r ded8a2be0d46 OrthancServer/Sources/OrthancMoveRequestHandler.cpp --- a/OrthancServer/Sources/OrthancMoveRequestHandler.cpp Tue Nov 04 15:58:06 2025 +0100 +++ b/OrthancServer/Sources/OrthancMoveRequestHandler.cpp Wed Nov 12 19:07:18 2025 +0100 @@ -85,12 +85,12 @@ } } - virtual unsigned int GetSubOperationCount() const + virtual unsigned int GetSubOperationCount() const ORTHANC_OVERRIDE { return instances_.size(); } - virtual Status DoNext() + virtual Status DoNext() ORTHANC_OVERRIDE { if (position_ >= instances_.size()) { @@ -169,12 +169,12 @@ } } - virtual unsigned int GetSubOperationCount() const + virtual unsigned int GetSubOperationCount() const ORTHANC_OVERRIDE { return countInstances_; } - virtual Status DoNext() + virtual Status DoNext() ORTHANC_OVERRIDE { if (position_ >= countInstances_) { diff -r b4b7b6d39ee3 -r ded8a2be0d46 OrthancServer/Sources/OrthancRestApi/OrthancRestAnonymizeModify.cpp --- a/OrthancServer/Sources/OrthancRestApi/OrthancRestAnonymizeModify.cpp Tue Nov 04 15:58:06 2025 +0100 +++ b/OrthancServer/Sources/OrthancRestApi/OrthancRestAnonymizeModify.cpp Wed Nov 12 19:07:18 2025 +0100 @@ -601,7 +601,7 @@ static void CreateDicomV1(ParsedDicomFile& dicom, - RestApiPostCall& call, + const RestApiPostCall& call, const Json::Value& request) { // curl http://localhost:8042/tools/create-dicom -X POST -d '{"PatientName":"Hello^World"}' diff -r b4b7b6d39ee3 -r ded8a2be0d46 OrthancServer/Sources/OrthancRestApi/OrthancRestApi.cpp --- a/OrthancServer/Sources/OrthancRestApi/OrthancRestApi.cpp Tue Nov 04 15:58:06 2025 +0100 +++ b/OrthancServer/Sources/OrthancRestApi/OrthancRestApi.cpp Wed Nov 12 19:07:18 2025 +0100 @@ -72,7 +72,7 @@ void OrthancRestApi::AnswerStoredInstance(RestApiPostCall& call, - DicomInstanceToStore& instance, + const DicomInstanceToStore& instance, StoreStatus status, const std::string& instanceId) const { @@ -186,10 +186,10 @@ std::unique_ptr toStore(DicomInstanceToStore::CreateFromBuffer(content)); toStore->SetOrigin(DicomInstanceOrigin::FromRest(call)); - std::string publicId; - try { + std::string publicId; + ServerContext::StoreResult result = context.Store(publicId, *toStore, StoreInstanceMode_Default); Json::Value info; diff -r b4b7b6d39ee3 -r ded8a2be0d46 OrthancServer/Sources/OrthancRestApi/OrthancRestApi.h --- a/OrthancServer/Sources/OrthancRestApi/OrthancRestApi.h Tue Nov 04 15:58:06 2025 +0100 +++ b/OrthancServer/Sources/OrthancRestApi/OrthancRestApi.h Wed Nov 12 19:07:18 2025 +0100 @@ -103,7 +103,7 @@ // WARNING: "instanceId" can be different from // "instance.GetHasher().HashInstance()" if transcoding is enabled void AnswerStoredInstance(RestApiPostCall& call, - DicomInstanceToStore& instance, + const DicomInstanceToStore& instance, StoreStatus status, const std::string& instanceId) const; diff -r b4b7b6d39ee3 -r ded8a2be0d46 OrthancServer/Sources/OrthancWebDav.cpp --- a/OrthancServer/Sources/OrthancWebDav.cpp Tue Nov 04 15:58:06 2025 +0100 +++ b/OrthancServer/Sources/OrthancWebDav.cpp Wed Nov 12 19:07:18 2025 +0100 @@ -597,7 +597,7 @@ virtual INode* CreateSubfolder(const std::string& path) = 0; public: - virtual ~InternalNode() + virtual ~InternalNode() ORTHANC_OVERRIDE { for (Children::iterator it = children_.begin(); it != children_.end(); ++it) { @@ -1221,10 +1221,10 @@ std::unique_ptr instance(DicomInstanceToStore::CreateFromBuffer(uncompressedFile)); instance->SetOrigin(DicomInstanceOrigin::FromWebDav()); - std::string publicId; - try { + std::string publicId; + context_.Store(publicId, *instance, StoreInstanceMode_Default); } catch (OrthancException& e) @@ -1256,7 +1256,7 @@ success = true; } } - catch (OrthancException& e) + catch (const OrthancException&) { } } diff -r b4b7b6d39ee3 -r ded8a2be0d46 OrthancServer/Sources/OrthancWebDav.h --- a/OrthancServer/Sources/OrthancWebDav.h Tue Nov 04 15:58:06 2025 +0100 +++ b/OrthancServer/Sources/OrthancWebDav.h Wed Nov 12 19:07:18 2025 +0100 @@ -99,9 +99,9 @@ bool allowDicomDelete, bool allowUpload); - virtual ~OrthancWebDav() + virtual ~OrthancWebDav() ORTHANC_OVERRIDE { - Stop(); + OrthancWebDav::Stop(); } virtual bool IsExistingFolder(const UriComponents& path) ORTHANC_OVERRIDE; diff -r b4b7b6d39ee3 -r ded8a2be0d46 OrthancServer/Sources/Search/HierarchicalMatcher.cpp --- a/OrthancServer/Sources/Search/HierarchicalMatcher.cpp Tue Nov 04 15:58:06 2025 +0100 +++ b/OrthancServer/Sources/Search/HierarchicalMatcher.cpp Wed Nov 12 19:07:18 2025 +0100 @@ -272,7 +272,7 @@ currentPrivateCreator = ""; } - std::unique_ptr cloned(FromDcmtkBridge::CreateElementForTag(*it, currentPrivateCreator.c_str())); + std::unique_ptr cloned(FromDcmtkBridge::CreateElementForTag(*it, currentPrivateCreator)); cloned->copyFrom(*element); target->insert(cloned.release()); } diff -r b4b7b6d39ee3 -r ded8a2be0d46 OrthancServer/Sources/ServerContext.cpp --- a/OrthancServer/Sources/ServerContext.cpp Tue Nov 04 15:58:06 2025 +0100 +++ b/OrthancServer/Sources/ServerContext.cpp Wed Nov 12 19:07:18 2025 +0100 @@ -1886,7 +1886,7 @@ return decoded.release(); } } - catch (OrthancException& e) + catch (const OrthancException&) { // ignore, we'll try other alternatives } } @@ -1899,7 +1899,7 @@ { decoded.reset(GetPlugins().Decode(buffer, size, frameIndex)); } - catch (OrthancException& e) + catch (const OrthancException&) { } diff -r b4b7b6d39ee3 -r ded8a2be0d46 OrthancServer/Sources/ServerContext.h --- a/OrthancServer/Sources/ServerContext.h Tue Nov 04 15:58:06 2025 +0100 +++ b/OrthancServer/Sources/ServerContext.h Wed Nov 12 19:07:18 2025 +0100 @@ -319,7 +319,7 @@ bool readOnly, unsigned int maxConcurrentDcmtkTranscoder); - ~ServerContext(); + ~ServerContext() ORTHANC_OVERRIDE; void SetupJobsEngine(bool unitTesting, bool loadJobsFromDatabase); diff -r b4b7b6d39ee3 -r ded8a2be0d46 OrthancServer/Sources/ServerIndex.cpp --- a/OrthancServer/Sources/ServerIndex.cpp Tue Nov 04 15:58:06 2025 +0100 +++ b/OrthancServer/Sources/ServerIndex.cpp Wed Nov 12 19:07:18 2025 +0100 @@ -236,7 +236,7 @@ { } - virtual ITransactionContext* Create() + virtual ITransactionContext* Create() ORTHANC_OVERRIDE { // There can be concurrent calls to this method, which is not an // issue because we simply create an object diff -r b4b7b6d39ee3 -r ded8a2be0d46 OrthancServer/Sources/ServerJobs/ArchiveJob.cpp --- a/OrthancServer/Sources/ServerJobs/ArchiveJob.cpp Tue Nov 04 15:58:06 2025 +0100 +++ b/OrthancServer/Sources/ServerJobs/ArchiveJob.cpp Wed Nov 12 19:07:18 2025 +0100 @@ -207,9 +207,9 @@ } } - virtual ~ThreadedInstanceLoader() + virtual ~ThreadedInstanceLoader() ORTHANC_OVERRIDE { - Clear(); + ThreadedInstanceLoader::Clear(); } virtual void Clear() ORTHANC_OVERRIDE @@ -781,7 +781,6 @@ } void Apply(HierarchicalZipWriter& writer, - ServerContext& context, InstanceLoader& instanceLoader, DicomDirWriter* dicomDir, const std::string& dicomDirFolder, @@ -814,16 +813,11 @@ writer.OpenFile(filename_.c_str()); - std::unique_ptr parsed; - writer.Write(content); if (dicomDir != NULL) { - if (parsed.get() == NULL) - { - parsed.reset(new ParsedDicomFile(content)); - } + std::unique_ptr parsed(new ParsedDicomFile(content)); dicomDir->Add(dicomDirFolder, filename_, *parsed); } @@ -844,7 +838,6 @@ void ApplyInternal(HierarchicalZipWriter& writer, - ServerContext& context, InstanceLoader& instanceLoader, size_t index, DicomDirWriter* dicomDir, @@ -857,7 +850,7 @@ throw OrthancException(ErrorCode_ParameterOutOfRange); } - commands_[index]->Apply(writer, context, instanceLoader, dicomDir, dicomDirFolder, transcode, transferSyntax); + commands_[index]->Apply(writer, instanceLoader, dicomDir, dicomDirFolder, transcode, transferSyntax); } public: @@ -895,7 +888,6 @@ // "media" flavor (with DICOMDIR) void Apply(HierarchicalZipWriter& writer, - ServerContext& context, InstanceLoader& instanceLoader, size_t index, DicomDirWriter& dicomDir, @@ -903,18 +895,17 @@ bool transcode, DicomTransferSyntax transferSyntax) const { - ApplyInternal(writer, context, instanceLoader, index, &dicomDir, dicomDirFolder, transcode, transferSyntax); + ApplyInternal(writer, instanceLoader, index, &dicomDir, dicomDirFolder, transcode, transferSyntax); } // "archive" flavor (without DICOMDIR) void Apply(HierarchicalZipWriter& writer, - ServerContext& context, InstanceLoader& instanceLoader, size_t index, bool transcode, DicomTransferSyntax transferSyntax) const { - ApplyInternal(writer, context, instanceLoader, index, NULL, "", transcode, transferSyntax); + ApplyInternal(writer, instanceLoader, index, NULL, "", transcode, transferSyntax); } void AddOpenDirectory(const std::string& filename) @@ -1244,13 +1235,13 @@ if (isMedia_) { assert(dicomDir_.get() != NULL); - commands_.Apply(*zip_, context_, instanceLoader_, index, *dicomDir_, + commands_.Apply(*zip_, instanceLoader_, index, *dicomDir_, MEDIA_IMAGES_FOLDER, transcode, transferSyntax); } else { assert(dicomDir_.get() == NULL); - commands_.Apply(*zip_, context_, instanceLoader_, index, transcode, transferSyntax); + commands_.Apply(*zip_, instanceLoader_, index, transcode, transferSyntax); } } } diff -r b4b7b6d39ee3 -r ded8a2be0d46 OrthancServer/Sources/ServerJobs/ArchiveJob.h --- a/OrthancServer/Sources/ServerJobs/ArchiveJob.h Tue Nov 04 15:58:06 2025 +0100 +++ b/OrthancServer/Sources/ServerJobs/ArchiveJob.h Wed Nov 12 19:07:18 2025 +0100 @@ -83,7 +83,7 @@ bool enableExtendedSopClass, ResourceType jobLevel); - virtual ~ArchiveJob(); + virtual ~ArchiveJob() ORTHANC_OVERRIDE; void AcquireSynchronousTarget(ZipWriter::IOutputStream* synchronousTarget); diff -r b4b7b6d39ee3 -r ded8a2be0d46 OrthancServer/Sources/ServerJobs/DicomGetScuJob.cpp --- a/OrthancServer/Sources/ServerJobs/DicomGetScuJob.cpp Tue Nov 04 15:58:06 2025 +0100 +++ b/OrthancServer/Sources/ServerJobs/DicomGetScuJob.cpp Wed Nov 12 19:07:18 2025 +0100 @@ -46,7 +46,7 @@ const std::string& calledAet) { // this code is equivalent to OrthancStoreRequestHandler - ServerContext* context = reinterpret_cast(callbackContext); + DicomRetrieveScuBaseJob* job = reinterpret_cast(callbackContext); std::unique_ptr toStore(DicomInstanceToStore::CreateFromDcmDataset(dataset)); @@ -56,7 +56,14 @@ (remoteIp.c_str(), remoteAet.c_str(), calledAet.c_str())); std::string id; - ServerContext::StoreResult result = context->Store(id, *toStore, StoreInstanceMode_Default); + ServerContext::StoreResult result = job->GetContext().Store(id, *toStore, StoreInstanceMode_Default); + + // keep track of the received instances in the job + if (result.GetStatus() == StoreStatus_Success || result.GetStatus() == StoreStatus_AlreadyStored) + { + job->AddReceivedInstance(id); + } + return result.GetCStoreStatusCode(); } @@ -68,11 +75,12 @@ if (connection_.get() == NULL) { std::set sopClassesToPropose; - std::set acceptedSopClasses; std::list proposedTransferSyntaxes; if (sopClassesFromResourcesToRetrieve_.size() > 0) { + std::set acceptedSopClasses; + context_.GetAcceptedSopClasses(acceptedSopClasses, 0); // keep the sop classes from the resources to retrieve only if they are accepted by Orthanc @@ -99,7 +107,7 @@ } connection_->SetProgressListener(this); - connection_->Get(findAnswer, InstanceReceivedHandler, &context_); + connection_->Get(findAnswer, InstanceReceivedHandler, this); } void DicomGetScuJob::AddFindAnswer(const DicomMap& answer) diff -r b4b7b6d39ee3 -r ded8a2be0d46 OrthancServer/Sources/ServerJobs/DicomMoveScuJob.cpp --- a/OrthancServer/Sources/ServerJobs/DicomMoveScuJob.cpp Tue Nov 04 15:58:06 2025 +0100 +++ b/OrthancServer/Sources/ServerJobs/DicomMoveScuJob.cpp Wed Nov 12 19:07:18 2025 +0100 @@ -47,7 +47,9 @@ } connection_->SetProgressListener(this); - connection_->Move(targetAet_, findAnswer); + + // we use a unique message ID in order to know to which Move request a stored instance relates to. + connection_->Move(targetAet_, findAnswer, GetMessageId(GetParameters().GetLocalApplicationEntityTitle())); } diff -r b4b7b6d39ee3 -r ded8a2be0d46 OrthancServer/Sources/ServerJobs/DicomRetrieveScuBaseJob.cpp --- a/OrthancServer/Sources/ServerJobs/DicomRetrieveScuBaseJob.cpp Tue Nov 04 15:58:06 2025 +0100 +++ b/OrthancServer/Sources/ServerJobs/DicomRetrieveScuBaseJob.cpp Wed Nov 12 19:07:18 2025 +0100 @@ -29,6 +29,7 @@ #include #include #include "../../../OrthancFramework/Sources/Logging.h" +#include static const char* const LOCAL_AET = "LocalAet"; static const char* const QUERY = "Query"; @@ -36,8 +37,106 @@ static const char* const REMOTE = "Remote"; static const char* const TIMEOUT = "Timeout"; +static std::map messagesRegistry; +static uint16_t messageRegistryCurrentId = 1000; +static boost::mutex messageRegistryMutex; + namespace Orthanc { + std::string GetKey(const std::string& aet, uint16_t messageId) + { + return aet + "-" + boost::lexical_cast(messageId); + } + + uint16_t DicomRetrieveScuBaseJob::GetMessageId(const std::string& localAet) + { + assert(currentCommand_ != NULL); + boost::mutex::scoped_lock lock(messageRegistryMutex); + + // Each resource retrieval (command) has its own messageId. + // We start at 1000 to clearly differentiate them from other messages. We can actually use ANY value between 0 & 65535. + messageRegistryCurrentId = std::max(1000, (messageRegistryCurrentId + 1) % 0xFFFF); + messagesRegistry[GetKey(localAet, messageRegistryCurrentId)] = currentCommand_; + + return messageRegistryCurrentId; + } + + void DicomRetrieveScuBaseJob::AddReceivedInstanceFromCStore(uint16_t originatorMessageId, + const std::string& originatorAet, + const std::string& instanceId) + { + boost::mutex::scoped_lock lock(messageRegistryMutex); + + std::string key = GetKey(originatorAet, originatorMessageId); + + if (messagesRegistry.find(key) != messagesRegistry.end()) + { + dynamic_cast(messagesRegistry[key])->AddReceivedInstance(instanceId); + } + } + + DicomRetrieveScuBaseJob::Command::~Command() + { + // remove the command from the messageRegistry + boost::mutex::scoped_lock lock(messageRegistryMutex); + + for (std::map::const_iterator it = messagesRegistry.begin(); + it != messagesRegistry.end(); ++it) + { + if (it->second == this) + { + messagesRegistry.erase(it->first); + return; + } + } + } + + bool DicomRetrieveScuBaseJob::Command::Execute(const std::string &jobId) + { + try + { + that_.currentCommand_ = this; + that_.Retrieve(*findAnswer_); // here + } + catch (OrthancException& e) + { + dimseErrorStatus_ = e.GetDimseErrorStatus(); + throw e; + } + return true; + } + + void DicomRetrieveScuBaseJob::Command::Serialize(Json::Value &target) const + { + findAnswer_->Serialize(target["Query"]); + target["DimseErrorStatus"] = dimseErrorStatus_; + SerializationToolbox::WriteListOfStrings(target, receivedInstancesIds_, "ReceivedInstancesIds"); + } + + SetOfCommandsJob::ICommand* DicomRetrieveScuBaseJob::Unserializer::Unserialize(const Json::Value &source) const + { + DicomMap findAnswer; + + if (!source.isMember("Query")) // old format before 1.12.10, just in case we need to read old jobs after an upgrade + { + findAnswer.Unserialize(source); + return new DicomRetrieveScuBaseJob::Command(that_, findAnswer); + } + + findAnswer.Unserialize(source["Query"]); + + std::unique_ptr command(new DicomRetrieveScuBaseJob::Command(that_, findAnswer)); + command->SetDimseErrorStatus(static_cast(source["DimseErrorStatus"].asUInt())); + + std::list receivedInstancesIds; + SerializationToolbox::ReadListOfStrings(receivedInstancesIds, source, "ReceivedInstancesIds"); + for (std::list::const_iterator it = receivedInstancesIds.begin(); it != receivedInstancesIds.end(); ++it) + { + command->AddReceivedInstance(*it); + } + + return command.release(); + } static void AddToQuery(DicomFindAnswers& query, const DicomMap& item) @@ -152,6 +251,20 @@ value[QUERY] = Json::objectValue; query_.ToJson(value[QUERY], queryFormat_); + + value["Details"] = Json::arrayValue; + + for (size_t i = 0; i < GetCommandsCount(); ++i) + { + const DicomRetrieveScuBaseJob::Command& command = dynamic_cast(GetCommand(i)); + + Json::Value v; + v["DimseErrorStatus"] = command.GetDimseErrorStatus(); + query_.ToJson(v["Query"], i, DicomToJsonFormat_Short); + SerializationToolbox::WriteListOfStrings(v, command.GetReceivedInstancesIds(), "ReceivedInstancesIds"); + + value["Details"].append(v); + } } @@ -162,7 +275,8 @@ nbRemainingSubOperations_(0), nbCompletedSubOperations_(0), nbFailedSubOperations_(0), - nbWarningSubOperations_(0) + nbWarningSubOperations_(0), + currentCommand_(NULL) { } @@ -172,12 +286,13 @@ SetOfCommandsJob(new Unserializer(*this), serialized), context_(context), parameters_(DicomAssociationParameters::UnserializeJob(serialized)), - query_(true), + query_(false /* this is not for worklists */), queryFormat_(DicomToJsonFormat_Short), nbRemainingSubOperations_(0), nbCompletedSubOperations_(0), nbFailedSubOperations_(0), - nbWarningSubOperations_(0) + nbWarningSubOperations_(0), + currentCommand_(NULL) { if (serialized.isMember(QUERY)) { @@ -245,4 +360,12 @@ return float(nbCompletedSubOperations_ + nbFailedSubOperations_ + nbWarningSubOperations_) / float(totalOperations); } + + void DicomRetrieveScuBaseJob::AddReceivedInstance(const std::string& instanceId) + { + if (currentCommand_ != NULL) + { + currentCommand_->AddReceivedInstance(instanceId); + } + } } diff -r b4b7b6d39ee3 -r ded8a2be0d46 OrthancServer/Sources/ServerJobs/DicomRetrieveScuBaseJob.h --- a/OrthancServer/Sources/ServerJobs/DicomRetrieveScuBaseJob.h Tue Nov 04 15:58:06 2025 +0100 +++ b/OrthancServer/Sources/ServerJobs/DicomRetrieveScuBaseJob.h Wed Nov 12 19:07:18 2025 +0100 @@ -36,29 +36,50 @@ class DicomRetrieveScuBaseJob : public SetOfCommandsJob, public DicomControlUserConnection::IProgressListener { protected: + uint16_t GetMessageId(const std::string& localAet); + class Command : public SetOfCommandsJob::ICommand { private: DicomRetrieveScuBaseJob &that_; std::unique_ptr findAnswer_; + uint16_t dimseErrorStatus_; + std::list receivedInstancesIds_; + public: Command(DicomRetrieveScuBaseJob &that, const DicomMap &findAnswer) : that_(that), - findAnswer_(findAnswer.Clone()) + findAnswer_(findAnswer.Clone()), + dimseErrorStatus_(0) { } - virtual bool Execute(const std::string &jobId) ORTHANC_OVERRIDE + virtual ~Command() ORTHANC_OVERRIDE; + + virtual bool Execute(const std::string &jobId) ORTHANC_OVERRIDE; + + virtual void Serialize(Json::Value &target) const ORTHANC_OVERRIDE; + + uint16_t GetDimseErrorStatus() const { - that_.Retrieve(*findAnswer_); - return true; + return dimseErrorStatus_; } - virtual void Serialize(Json::Value &target) const ORTHANC_OVERRIDE + void SetDimseErrorStatus(uint16_t dimseErrorStatus) + { + dimseErrorStatus_ = dimseErrorStatus; + } + + void AddReceivedInstance(const std::string& instanceId) { - findAnswer_->Serialize(target); + receivedInstancesIds_.push_back(instanceId); + } + + const std::list& GetReceivedInstancesIds() const + { + return receivedInstancesIds_; } }; @@ -73,12 +94,7 @@ { } - virtual ICommand *Unserialize(const Json::Value &source) const ORTHANC_OVERRIDE - { - DicomMap findAnswer; - findAnswer.Unserialize(source); - return new Command(that_, findAnswer); - } + virtual ICommand *Unserialize(const Json::Value &source) const ORTHANC_OVERRIDE; }; ServerContext &context_; @@ -94,6 +110,8 @@ uint16_t nbFailedSubOperations_; uint16_t nbWarningSubOperations_; + Command* currentCommand_; + virtual void Retrieve(const DicomMap &findAnswer) = 0; explicit DicomRetrieveScuBaseJob(ServerContext &context); @@ -145,5 +163,15 @@ return true; } + ServerContext& GetContext() + { + return context_; + } + + void AddReceivedInstance(const std::string& instanceId); + + static void AddReceivedInstanceFromCStore(uint16_t originatorMessageId, + const std::string& originatorAet, + const std::string& instanceId); }; } diff -r b4b7b6d39ee3 -r ded8a2be0d46 OrthancServer/Sources/ServerJobs/ResourceModificationJob.cpp --- a/OrthancServer/Sources/ServerJobs/ResourceModificationJob.cpp Tue Nov 04 15:58:06 2025 +0100 +++ b/OrthancServer/Sources/ServerJobs/ResourceModificationJob.cpp Wed Nov 12 19:07:18 2025 +0100 @@ -185,7 +185,7 @@ for (std::set::const_iterator it = instancesToReconstruct_.begin(); it != instancesToReconstruct_.end(); ++it) { ServerContext::DicomCacheLocker locker(GetContext(), *it); - ParsedDicomFile& modifiedDicom = locker.GetDicom(); + const ParsedDicomFile& modifiedDicom = locker.GetDicom(); GetContext().GetIndex().ReconstructInstance(modifiedDicom, false, ResourceType_Instance /* dummy */); } @@ -219,7 +219,7 @@ try { ServerContext::DicomCacheLocker locker(GetContext(), instance); - ParsedDicomFile& original = locker.GetDicom(); + const ParsedDicomFile& original = locker.GetDicom(); originalHasher.reset(new DicomInstanceHasher(original.GetHasher())); modified.reset(original.Clone(true)); @@ -689,7 +689,7 @@ } // and we must make sure that we overwite them with the modified resources - if (IsKeepSource() && !GetContext().IsOverwriteInstances()) + if (!GetContext().IsOverwriteInstances()) { throw OrthancException(ErrorCode_BadRequest, "When keeping StudyInstanceUID, SeriesInstanceUID and SOPInstanceUID tag, you must have the 'OverwriteInstances' Orthanc configuration set to true in order to replace the modified resources"); diff -r b4b7b6d39ee3 -r ded8a2be0d46 OrthancServer/Sources/ServerJobs/StorageCommitmentScpJob.cpp --- a/OrthancServer/Sources/ServerJobs/StorageCommitmentScpJob.cpp Tue Nov 04 15:58:06 2025 +0100 +++ b/OrthancServer/Sources/ServerJobs/StorageCommitmentScpJob.cpp Wed Nov 12 19:07:18 2025 +0100 @@ -375,6 +375,8 @@ } else { + SetOfCommandsJob::Reserve(size); + sopClassUids_.reserve(size); sopInstanceUids_.reserve(size); } diff -r b4b7b6d39ee3 -r ded8a2be0d46 OrthancServer/Sources/ServerJobs/StorageCommitmentScpJob.h --- a/OrthancServer/Sources/ServerJobs/StorageCommitmentScpJob.h Tue Nov 04 15:58:06 2025 +0100 +++ b/OrthancServer/Sources/ServerJobs/StorageCommitmentScpJob.h Wed Nov 12 19:07:18 2025 +0100 @@ -78,7 +78,7 @@ StorageCommitmentScpJob(ServerContext& context, const Json::Value& serialized); - void Reserve(size_t size); + virtual void Reserve(size_t size) ORTHANC_OVERRIDE; void AddInstance(const std::string& sopClassUid, const std::string& sopInstanceUid); diff -r b4b7b6d39ee3 -r ded8a2be0d46 OrthancServer/Sources/ServerJobs/ThreadedSetOfInstancesJob.h --- a/OrthancServer/Sources/ServerJobs/ThreadedSetOfInstancesJob.h Tue Nov 04 15:58:06 2025 +0100 +++ b/OrthancServer/Sources/ServerJobs/ThreadedSetOfInstancesJob.h Wed Nov 12 19:07:18 2025 +0100 @@ -84,7 +84,7 @@ bool hasTrailingStep, bool defaultKeepSource); - virtual ~ThreadedSetOfInstancesJob(); + virtual ~ThreadedSetOfInstancesJob() ORTHANC_OVERRIDE; protected: virtual bool HandleInstance(const std::string& instance) = 0; diff -r b4b7b6d39ee3 -r ded8a2be0d46 OrthancServer/Sources/SimpleInstanceOrdering.cpp --- a/OrthancServer/Sources/SimpleInstanceOrdering.cpp Tue Nov 04 15:58:06 2025 +0100 +++ b/OrthancServer/Sources/SimpleInstanceOrdering.cpp Wed Nov 12 19:07:18 2025 +0100 @@ -96,7 +96,6 @@ for (size_t i = 0; i < response.GetSize(); ++i) { const FindResponse::Resource& resource = response.GetResourceByIndex(i); - std::string instanceId = resource.GetIdentifier(); std::string strIndexInSeries; uint32_t indexInSeries = 0; @@ -110,6 +109,8 @@ if (resource.LookupAttachment(fileInfo, revisionNotUsed, FileContentType_Dicom)) { + std::string instanceId = resource.GetIdentifier(); + allIndexInSeries.insert(indexInSeries); instances_.push_back(new SimpleInstanceOrdering::Instance(instanceId, indexInSeries, fileInfo)); } diff -r b4b7b6d39ee3 -r ded8a2be0d46 OrthancServer/Sources/SliceOrdering.cpp --- a/OrthancServer/Sources/SliceOrdering.cpp Tue Nov 04 15:58:06 2025 +0100 +++ b/OrthancServer/Sources/SliceOrdering.cpp Wed Nov 12 19:07:18 2025 +0100 @@ -192,12 +192,12 @@ } hasNormal_ = ComputeNormal(normal_, instance); - - std::string s; hasIndexInSeries_ = false; try { + std::string s; + if (index.LookupMetadata(s, instanceId, ResourceType_Instance, MetadataType_Instance_IndexInSeries)) { indexInSeries_ = boost::lexical_cast(Toolbox::StripSpaces(s)); diff -r b4b7b6d39ee3 -r ded8a2be0d46 OrthancServer/Sources/main.cpp --- a/OrthancServer/Sources/main.cpp Tue Nov 04 15:58:06 2025 +0100 +++ b/OrthancServer/Sources/main.cpp Wed Nov 12 19:07:18 2025 +0100 @@ -47,6 +47,7 @@ #include "ServerContext.h" #include "ServerEnumerations.h" #include "ServerJobs/StorageCommitmentScpJob.h" +#include "ServerJobs/DicomRetrieveScuBaseJob.h" #include "ServerToolbox.h" #include "StorageCommitmentReports.h" @@ -86,7 +87,9 @@ virtual uint16_t Handle(DcmDataset& dicom, const std::string& remoteIp, const std::string& remoteAet, - const std::string& calledAet) ORTHANC_OVERRIDE + const std::string& calledAet, + uint16_t originatorMessageId, + const std::string& originatorAet) ORTHANC_OVERRIDE { std::unique_ptr toStore(DicomInstanceToStore::CreateFromDcmDataset(dicom)); @@ -97,6 +100,13 @@ std::string id; ServerContext::StoreResult result = context_.Store(id, *toStore, StoreInstanceMode_Default); + + if (result.GetStatus() == StoreStatus_Success || result.GetStatus() == StoreStatus_AlreadyStored) + { + // In case this C-Store was triggered from a C-Move job, keep track of the instances that have been received + DicomRetrieveScuBaseJob::AddReceivedInstanceFromCStore(originatorMessageId, originatorAet, id); + } + return result.GetCStoreStatusCode(); } diff -r b4b7b6d39ee3 -r ded8a2be0d46 OrthancServer/UnitTestsSources/ServerJobsTests.cpp --- a/OrthancServer/UnitTestsSources/ServerJobsTests.cpp Tue Nov 04 15:58:06 2025 +0100 +++ b/OrthancServer/UnitTestsSources/ServerJobsTests.cpp Wed Nov 12 19:07:18 2025 +0100 @@ -1274,10 +1274,12 @@ TEST_F(OrthancJobsSerialization, DicomMoveScuJob) { Json::Value command = Json::objectValue; - command["0008,0005"]["Type"] = "String"; - command["0008,0005"]["Content"] = "ISO_IR 100"; - command["0010,0020"]["Type"] = "String"; - command["0010,0020"]["Content"] = "1234"; + command["Query"]["0008,0005"]["Type"] = "String"; + command["Query"]["0008,0005"]["Content"] = "ISO_IR 100"; + command["Query"]["0010,0020"]["Type"] = "String"; + command["Query"]["0010,0020"]["Content"] = "1234"; + command["DimseErrorStatus"] = 0; + command["ReceivedInstancesIds"] = Json::arrayValue; Json::Value query = Json::objectValue; query["0010,0020"] = "456"; diff -r b4b7b6d39ee3 -r ded8a2be0d46 TODO --- a/TODO Tue Nov 04 15:58:06 2025 +0100 +++ b/TODO Wed Nov 12 19:07:18 2025 +0100 @@ -64,7 +64,8 @@ https://discourse.orthanc-server.org/t/instances-id-content-api-results-are-different-in-docker-swarm-replicas-of-orthanc/4582 * Allow saving PrivateTags in ExtraMainDicomTags. Note: they can actually be stored but they then appear as "Unknown Tag & Data" in the responses. - If we try to add the PrivateCreator in the ExtraMainDicomTags, then, the DICOMWeb plugin fails to initialize because the private tags are not known. + If we try to add the PrivateCreator in the ExtraMainDicomTags, then, the DICOMweb plugin fails + to initialize because the private tags are not known. * Support hashed passwords in RegisteredUsers. E.g: "RegisteredUsers": { "admin": { @@ -216,6 +217,16 @@ * Log outgoing C-Find queries * Support other Transfer Syntaxes in the Worklist plugin: https://discourse.orthanc-server.org/t/could-you-please-create-an-option-to-set-the-transfer-syntax-in-the-worklist-plugin-currently-little-endian-explicit-is-fixed/4871 +* For C-Find (including C-Find for worklists) Orthanc always uses the "DefaultEncoding" in the response even if it needs to + return e.g. an Utf-8 PatientName that can not be represented in the the "DefaultEncoding" e.g. "Latin1". + According to the standard (PS 3.4 C.4.1.1.3.2): + Conditionally, the Attribute Specific Character Set (0008,0005). This Attribute shall be included if expanded or replacement + character sets may be used in any of the Attributes in the Response Identifier. It shall not be included otherwise. + The C-FIND SCP is not required to return responses in the Specific Character Set requested by the SCU if that character set + is not supported by the SCP. The SCP may return responses with a different Specific Character Set. + Therefore, Orthanc should: + - Preferrably use the same SpecificCharacterSet as the one used in the C-Find request. + - If Orthanc is unable to represent a string with this encoding, it shall switch to UTF-8 in the response * Deidentify Trace level logs: https://discourse.orthanc-server.org/t/dicom-log-deidentification-at-trace-log-level/5563 We should implement something like GetDeidentifiedContent(const DcmDataSet& dataset) that would reproduce DcmDataSet::print but hide individual elements that contain PHI. @@ -256,7 +267,7 @@ * (3) Check out rapidjson: https://github.com/miloyip/nativejson-benchmark * Long-shot & not sure it is even feasible at all: try to reduce memory usage by implementing streaming - when receiving DICOM instances from the Rest API or from DICOM and store files directly to disk as they + when receiving DICOM instances from the REST API or from DICOM and store files directly to disk as they are received. Note that this would likely require rewriting large parts of Orthanc. Note sure that would be compatible with Transcoding. Use case: receiving 10 1GB instances in parallel can consume up to 20 GB