comparison OrthancServer/Sources/Database/StatelessDatabaseOperations.cpp @ 5296:5053a10da5a2

Fix orphan files remaining in storage when working with MaximumStorageSize
author Alain Mazy <am@osimis.io>
date Mon, 22 May 2023 15:00:01 +0200
parents f22c8fac764b
children c78138dc3889
comparison
equal deleted inserted replaced
5294:e0e2aee4453e 5296:5053a10da5a2
3096 return storeStatus_; 3096 return storeStatus_;
3097 } 3097 }
3098 3098
3099 virtual void Apply(ReadWriteTransaction& transaction) ORTHANC_OVERRIDE 3099 virtual void Apply(ReadWriteTransaction& transaction) ORTHANC_OVERRIDE
3100 { 3100 {
3101 try 3101 IDatabaseWrapper::CreateInstanceResult status;
3102 { 3102 int64_t instanceId;
3103 IDatabaseWrapper::CreateInstanceResult status; 3103
3104 int64_t instanceId; 3104 bool isNewInstance = transaction.CreateInstance(status, instanceId, hashPatient_,
3105 3105 hashStudy_, hashSeries_, hashInstance_);
3106 bool isNewInstance = transaction.CreateInstance(status, instanceId, hashPatient_, 3106
3107 hashStudy_, hashSeries_, hashInstance_); 3107 if (isReconstruct_ && isNewInstance)
3108 3108 {
3109 if (isReconstruct_ && isNewInstance) 3109 // In case of reconstruct, we just want to modify the attachments and some metadata like the TransferSyntex
3110 { 3110 // The DicomTags and many metadata have already been updated before we get here in ReconstructInstance
3111 // In case of reconstruct, we just want to modify the attachments and some metadata like the TransferSyntex 3111 throw OrthancException(ErrorCode_InternalError, "New instance while reconstructing; this should not happen.");
3112 // The DicomTags and many metadata have already been updated before we get here in ReconstructInstance 3112 }
3113 throw OrthancException(ErrorCode_InternalError, "New instance while reconstructing; this should not happen."); 3113
3114 } 3114 // Check whether this instance is already stored
3115 3115 if (!isNewInstance && !isReconstruct_)
3116 // Check whether this instance is already stored 3116 {
3117 if (!isNewInstance && !isReconstruct_) 3117 // The instance already exists
3118 { 3118 if (overwrite_)
3119 // The instance already exists 3119 {
3120 if (overwrite_) 3120 // Overwrite the old instance
3121 LOG(INFO) << "Overwriting instance: " << hashInstance_;
3122 transaction.DeleteResource(instanceId);
3123
3124 // Re-create the instance, now that the old one is removed
3125 if (!transaction.CreateInstance(status, instanceId, hashPatient_,
3126 hashStudy_, hashSeries_, hashInstance_))
3121 { 3127 {
3122 // Overwrite the old instance 3128 throw OrthancException(ErrorCode_InternalError, "No new instance while overwriting; this should not happen.");
3123 LOG(INFO) << "Overwriting instance: " << hashInstance_;
3124 transaction.DeleteResource(instanceId);
3125
3126 // Re-create the instance, now that the old one is removed
3127 if (!transaction.CreateInstance(status, instanceId, hashPatient_,
3128 hashStudy_, hashSeries_, hashInstance_))
3129 {
3130 throw OrthancException(ErrorCode_InternalError, "No new instance while overwriting; this should not happen.");
3131 }
3132 } 3129 }
3133 else 3130 }
3131 else
3132 {
3133 // Do nothing if the instance already exists and overwriting is disabled
3134 transaction.GetAllMetadata(instanceMetadata_, instanceId);
3135 storeStatus_ = StoreStatus_AlreadyStored;
3136 return;
3137 }
3138 }
3139
3140
3141 if (!isReconstruct_) // don't signal new resources if this is a reconstruction
3142 {
3143 // Warn about the creation of new resources. The order must be
3144 // from instance to patient.
3145
3146 // NB: In theory, could be sped up by grouping the underlying
3147 // calls to "transaction.LogChange()". However, this would only have an
3148 // impact when new patient/study/series get created, which
3149 // occurs far less often that creating new instances. The
3150 // positive impact looks marginal in practice.
3151 transaction.LogChange(instanceId, ChangeType_NewInstance, ResourceType_Instance, hashInstance_);
3152
3153 if (status.isNewSeries_)
3154 {
3155 transaction.LogChange(status.seriesId_, ChangeType_NewSeries, ResourceType_Series, hashSeries_);
3156 }
3157
3158 if (status.isNewStudy_)
3159 {
3160 transaction.LogChange(status.studyId_, ChangeType_NewStudy, ResourceType_Study, hashStudy_);
3161 }
3162
3163 if (status.isNewPatient_)
3164 {
3165 transaction.LogChange(status.patientId_, ChangeType_NewPatient, ResourceType_Patient, hashPatient_);
3166 }
3167 }
3168
3169 // Ensure there is enough room in the storage for the new instance
3170 uint64_t instanceSize = 0;
3171 for (Attachments::const_iterator it = attachments_.begin();
3172 it != attachments_.end(); ++it)
3173 {
3174 instanceSize += it->GetCompressedSize();
3175 }
3176
3177 if (!isReconstruct_) // reconstruction should not affect recycling
3178 {
3179 if (maximumStorageMode_ == MaxStorageMode_Reject)
3180 {
3181 if (transaction.HasReachedMaxStorageSize(maximumStorageSize_, instanceSize))
3134 { 3182 {
3135 // Do nothing if the instance already exists and overwriting is disabled 3183 storeStatus_ = StoreStatus_StorageFull;
3136 transaction.GetAllMetadata(instanceMetadata_, instanceId); 3184 throw OrthancException(ErrorCode_FullStorage, HttpStatus_507_InsufficientStorage, "Maximum storage size reached"); // throw to cancel the transaction
3137 storeStatus_ = StoreStatus_AlreadyStored;
3138 return;
3139 } 3185 }
3140 } 3186 if (transaction.HasReachedMaxPatientCount(maximumPatientCount_, hashPatient_))
3141 3187 {
3142 3188 storeStatus_ = StoreStatus_StorageFull;
3143 if (!isReconstruct_) // don't signal new resources if this is a reconstruction 3189 throw OrthancException(ErrorCode_FullStorage, HttpStatus_507_InsufficientStorage, "Maximum patient count reached"); // throw to cancel the transaction
3144 { 3190 }
3145 // Warn about the creation of new resources. The order must be 3191 }
3146 // from instance to patient. 3192 else
3147 3193 {
3148 // NB: In theory, could be sped up by grouping the underlying 3194 transaction.Recycle(maximumStorageSize_, maximumPatientCount_,
3149 // calls to "transaction.LogChange()". However, this would only have an 3195 instanceSize, hashPatient_ /* don't consider the current patient for recycling */);
3150 // impact when new patient/study/series get created, which 3196 }
3151 // occurs far less often that creating new instances. The 3197 }
3152 // positive impact looks marginal in practice. 3198
3153 transaction.LogChange(instanceId, ChangeType_NewInstance, ResourceType_Instance, hashInstance_); 3199 // Attach the files to the newly created instance
3200 for (Attachments::const_iterator it = attachments_.begin();
3201 it != attachments_.end(); ++it)
3202 {
3203 if (isReconstruct_)
3204 {
3205 // we are replacing attachments during a reconstruction
3206 transaction.DeleteAttachment(instanceId, it->GetContentType());
3207 }
3208
3209 transaction.AddAttachment(instanceId, *it, 0 /* this is the first revision */);
3210 }
3211
3212 if (!isReconstruct_)
3213 {
3214 ResourcesContent content(true /* new resource, metadata can be set */);
3215
3216 // Attach the user-specified metadata (in case of reconstruction, metadata_ contains all past metadata, including the system ones we want to keep)
3217 for (MetadataMap::const_iterator
3218 it = metadata_.begin(); it != metadata_.end(); ++it)
3219 {
3220 switch (it->first.first)
3221 {
3222 case ResourceType_Patient:
3223 content.AddMetadata(status.patientId_, it->first.second, it->second);
3224 break;
3225
3226 case ResourceType_Study:
3227 content.AddMetadata(status.studyId_, it->first.second, it->second);
3228 break;
3229
3230 case ResourceType_Series:
3231 content.AddMetadata(status.seriesId_, it->first.second, it->second);
3232 break;
3233
3234 case ResourceType_Instance:
3235 SetInstanceMetadata(content, instanceMetadata_, instanceId,
3236 it->first.second, it->second);
3237 break;
3238
3239 default:
3240 throw OrthancException(ErrorCode_ParameterOutOfRange);
3241 }
3242 }
3243
3244 if (!isReconstruct_)
3245 {
3246 // Populate the tags of the newly-created resources
3247 content.AddResource(instanceId, ResourceType_Instance, dicomSummary_);
3248 SetInstanceMetadata(content, instanceMetadata_, instanceId, MetadataType_MainDicomTagsSignature, DicomMap::GetMainDicomTagsSignature(ResourceType_Instance)); // New in Orthanc 1.11.0
3249 SetMainDicomSequenceMetadata(content, instanceId, dicomSummary_, ResourceType_Instance); // new in Orthanc 1.11.1
3154 3250
3155 if (status.isNewSeries_) 3251 if (status.isNewSeries_)
3156 { 3252 {
3157 transaction.LogChange(status.seriesId_, ChangeType_NewSeries, ResourceType_Series, hashSeries_); 3253 content.AddResource(status.seriesId_, ResourceType_Series, dicomSummary_);
3254 content.AddMetadata(status.seriesId_, MetadataType_MainDicomTagsSignature, DicomMap::GetMainDicomTagsSignature(ResourceType_Series)); // New in Orthanc 1.11.0
3255 SetMainDicomSequenceMetadata(content, status.seriesId_, dicomSummary_, ResourceType_Series); // new in Orthanc 1.11.1
3158 } 3256 }
3159 3257
3160 if (status.isNewStudy_) 3258 if (status.isNewStudy_)
3161 { 3259 {
3162 transaction.LogChange(status.studyId_, ChangeType_NewStudy, ResourceType_Study, hashStudy_); 3260 content.AddResource(status.studyId_, ResourceType_Study, dicomSummary_);
3261 content.AddMetadata(status.studyId_, MetadataType_MainDicomTagsSignature, DicomMap::GetMainDicomTagsSignature(ResourceType_Study)); // New in Orthanc 1.11.0
3262 SetMainDicomSequenceMetadata(content, status.studyId_, dicomSummary_, ResourceType_Study); // new in Orthanc 1.11.1
3163 } 3263 }
3164 3264
3165 if (status.isNewPatient_) 3265 if (status.isNewPatient_)
3166 { 3266 {
3167 transaction.LogChange(status.patientId_, ChangeType_NewPatient, ResourceType_Patient, hashPatient_); 3267 content.AddResource(status.patientId_, ResourceType_Patient, dicomSummary_);
3268 content.AddMetadata(status.patientId_, MetadataType_MainDicomTagsSignature, DicomMap::GetMainDicomTagsSignature(ResourceType_Patient)); // New in Orthanc 1.11.0
3269 SetMainDicomSequenceMetadata(content, status.patientId_, dicomSummary_, ResourceType_Patient); // new in Orthanc 1.11.1
3168 } 3270 }
3169 } 3271
3272 // Attach the auto-computed metadata for the patient/study/series levels
3273 std::string now = SystemToolbox::GetNowIsoString(true /* use UTC time (not local time) */);
3274 content.AddMetadata(status.seriesId_, MetadataType_LastUpdate, now);
3275 content.AddMetadata(status.studyId_, MetadataType_LastUpdate, now);
3276 content.AddMetadata(status.patientId_, MetadataType_LastUpdate, now);
3277
3278 if (status.isNewSeries_)
3279 {
3280 if (hasExpectedInstances_)
3281 {
3282 content.AddMetadata(status.seriesId_, MetadataType_Series_ExpectedNumberOfInstances,
3283 boost::lexical_cast<std::string>(expectedInstances_));
3284 }
3285
3286 // New in Orthanc 1.9.0
3287 content.AddMetadata(status.seriesId_, MetadataType_RemoteAet,
3288 origin_.GetRemoteAetC());
3289 }
3290 // Attach the auto-computed metadata for the instance level,
3291 // reflecting these additions into the input metadata map
3292 SetInstanceMetadata(content, instanceMetadata_, instanceId,
3293 MetadataType_Instance_ReceptionDate, now);
3294 SetInstanceMetadata(content, instanceMetadata_, instanceId, MetadataType_RemoteAet,
3295 origin_.GetRemoteAetC());
3296 SetInstanceMetadata(content, instanceMetadata_, instanceId, MetadataType_Instance_Origin,
3297 EnumerationToString(origin_.GetRequestOrigin()));
3298
3299 std::string s;
3300
3301 if (origin_.LookupRemoteIp(s))
3302 {
3303 // New in Orthanc 1.4.0
3304 SetInstanceMetadata(content, instanceMetadata_, instanceId,
3305 MetadataType_Instance_RemoteIp, s);
3306 }
3307
3308 if (origin_.LookupCalledAet(s))
3309 {
3310 // New in Orthanc 1.4.0
3311 SetInstanceMetadata(content, instanceMetadata_, instanceId,
3312 MetadataType_Instance_CalledAet, s);
3313 }
3314
3315 if (origin_.LookupHttpUsername(s))
3316 {
3317 // New in Orthanc 1.4.0
3318 SetInstanceMetadata(content, instanceMetadata_, instanceId,
3319 MetadataType_Instance_HttpUsername, s);
3320 }
3321 }
3322
3323 // Following metadatas are also updated if reconstructing the instance.
3324 // They might be missing since they have been introduced along Orthanc versions.
3325
3326 if (hasTransferSyntax_)
3327 {
3328 // New in Orthanc 1.2.0
3329 SetInstanceMetadata(content, instanceMetadata_, instanceId,
3330 MetadataType_Instance_TransferSyntax,
3331 GetTransferSyntaxUid(transferSyntax_));
3332 }
3333
3334 if (hasPixelDataOffset_)
3335 {
3336 // New in Orthanc 1.9.1
3337 SetInstanceMetadata(content, instanceMetadata_, instanceId,
3338 MetadataType_Instance_PixelDataOffset,
3339 boost::lexical_cast<std::string>(pixelDataOffset_));
3340 }
3170 3341
3171 // Ensure there is enough room in the storage for the new instance 3342 const DicomValue* value;
3172 uint64_t instanceSize = 0; 3343 if ((value = dicomSummary_.TestAndGetValue(DICOM_TAG_SOP_CLASS_UID)) != NULL &&
3173 for (Attachments::const_iterator it = attachments_.begin(); 3344 !value->IsNull() &&
3174 it != attachments_.end(); ++it) 3345 !value->IsBinary())
3175 { 3346 {
3176 instanceSize += it->GetCompressedSize(); 3347 SetInstanceMetadata(content, instanceMetadata_, instanceId,
3177 } 3348 MetadataType_Instance_SopClassUid, value->GetContent());
3178 3349 }
3179 if (!isReconstruct_) // reconstruction should not affect recycling 3350
3180 { 3351
3181 if (maximumStorageMode_ == MaxStorageMode_Reject) 3352 if ((value = dicomSummary_.TestAndGetValue(DICOM_TAG_INSTANCE_NUMBER)) != NULL ||
3182 { 3353 (value = dicomSummary_.TestAndGetValue(DICOM_TAG_IMAGE_INDEX)) != NULL)
3183 if (transaction.HasReachedMaxStorageSize(maximumStorageSize_, instanceSize)) 3354 {
3184 { 3355 if (!value->IsNull() &&
3185 storeStatus_ = StoreStatus_StorageFull;
3186 throw OrthancException(ErrorCode_FullStorage, HttpStatus_507_InsufficientStorage, "Maximum storage size reached"); // throw to cancel the transaction
3187 }
3188 if (transaction.HasReachedMaxPatientCount(maximumPatientCount_, hashPatient_))
3189 {
3190 storeStatus_ = StoreStatus_StorageFull;
3191 throw OrthancException(ErrorCode_FullStorage, HttpStatus_507_InsufficientStorage, "Maximum patient count reached"); // throw to cancel the transaction
3192 }
3193 }
3194 else
3195 {
3196 transaction.Recycle(maximumStorageSize_, maximumPatientCount_,
3197 instanceSize, hashPatient_ /* don't consider the current patient for recycling */);
3198 }
3199 }
3200
3201 // Attach the files to the newly created instance
3202 for (Attachments::const_iterator it = attachments_.begin();
3203 it != attachments_.end(); ++it)
3204 {
3205 if (isReconstruct_)
3206 {
3207 // we are replacing attachments during a reconstruction
3208 transaction.DeleteAttachment(instanceId, it->GetContentType());
3209 }
3210
3211 transaction.AddAttachment(instanceId, *it, 0 /* this is the first revision */);
3212 }
3213
3214 if (!isReconstruct_)
3215 {
3216 ResourcesContent content(true /* new resource, metadata can be set */);
3217
3218 // Attach the user-specified metadata (in case of reconstruction, metadata_ contains all past metadata, including the system ones we want to keep)
3219 for (MetadataMap::const_iterator
3220 it = metadata_.begin(); it != metadata_.end(); ++it)
3221 {
3222 switch (it->first.first)
3223 {
3224 case ResourceType_Patient:
3225 content.AddMetadata(status.patientId_, it->first.second, it->second);
3226 break;
3227
3228 case ResourceType_Study:
3229 content.AddMetadata(status.studyId_, it->first.second, it->second);
3230 break;
3231
3232 case ResourceType_Series:
3233 content.AddMetadata(status.seriesId_, it->first.second, it->second);
3234 break;
3235
3236 case ResourceType_Instance:
3237 SetInstanceMetadata(content, instanceMetadata_, instanceId,
3238 it->first.second, it->second);
3239 break;
3240
3241 default:
3242 throw OrthancException(ErrorCode_ParameterOutOfRange);
3243 }
3244 }
3245
3246 if (!isReconstruct_)
3247 {
3248 // Populate the tags of the newly-created resources
3249 content.AddResource(instanceId, ResourceType_Instance, dicomSummary_);
3250 SetInstanceMetadata(content, instanceMetadata_, instanceId, MetadataType_MainDicomTagsSignature, DicomMap::GetMainDicomTagsSignature(ResourceType_Instance)); // New in Orthanc 1.11.0
3251 SetMainDicomSequenceMetadata(content, instanceId, dicomSummary_, ResourceType_Instance); // new in Orthanc 1.11.1
3252
3253 if (status.isNewSeries_)
3254 {
3255 content.AddResource(status.seriesId_, ResourceType_Series, dicomSummary_);
3256 content.AddMetadata(status.seriesId_, MetadataType_MainDicomTagsSignature, DicomMap::GetMainDicomTagsSignature(ResourceType_Series)); // New in Orthanc 1.11.0
3257 SetMainDicomSequenceMetadata(content, status.seriesId_, dicomSummary_, ResourceType_Series); // new in Orthanc 1.11.1
3258 }
3259
3260 if (status.isNewStudy_)
3261 {
3262 content.AddResource(status.studyId_, ResourceType_Study, dicomSummary_);
3263 content.AddMetadata(status.studyId_, MetadataType_MainDicomTagsSignature, DicomMap::GetMainDicomTagsSignature(ResourceType_Study)); // New in Orthanc 1.11.0
3264 SetMainDicomSequenceMetadata(content, status.studyId_, dicomSummary_, ResourceType_Study); // new in Orthanc 1.11.1
3265 }
3266
3267 if (status.isNewPatient_)
3268 {
3269 content.AddResource(status.patientId_, ResourceType_Patient, dicomSummary_);
3270 content.AddMetadata(status.patientId_, MetadataType_MainDicomTagsSignature, DicomMap::GetMainDicomTagsSignature(ResourceType_Patient)); // New in Orthanc 1.11.0
3271 SetMainDicomSequenceMetadata(content, status.patientId_, dicomSummary_, ResourceType_Patient); // new in Orthanc 1.11.1
3272 }
3273
3274 // Attach the auto-computed metadata for the patient/study/series levels
3275 std::string now = SystemToolbox::GetNowIsoString(true /* use UTC time (not local time) */);
3276 content.AddMetadata(status.seriesId_, MetadataType_LastUpdate, now);
3277 content.AddMetadata(status.studyId_, MetadataType_LastUpdate, now);
3278 content.AddMetadata(status.patientId_, MetadataType_LastUpdate, now);
3279
3280 if (status.isNewSeries_)
3281 {
3282 if (hasExpectedInstances_)
3283 {
3284 content.AddMetadata(status.seriesId_, MetadataType_Series_ExpectedNumberOfInstances,
3285 boost::lexical_cast<std::string>(expectedInstances_));
3286 }
3287
3288 // New in Orthanc 1.9.0
3289 content.AddMetadata(status.seriesId_, MetadataType_RemoteAet,
3290 origin_.GetRemoteAetC());
3291 }
3292 // Attach the auto-computed metadata for the instance level,
3293 // reflecting these additions into the input metadata map
3294 SetInstanceMetadata(content, instanceMetadata_, instanceId,
3295 MetadataType_Instance_ReceptionDate, now);
3296 SetInstanceMetadata(content, instanceMetadata_, instanceId, MetadataType_RemoteAet,
3297 origin_.GetRemoteAetC());
3298 SetInstanceMetadata(content, instanceMetadata_, instanceId, MetadataType_Instance_Origin,
3299 EnumerationToString(origin_.GetRequestOrigin()));
3300
3301 std::string s;
3302
3303 if (origin_.LookupRemoteIp(s))
3304 {
3305 // New in Orthanc 1.4.0
3306 SetInstanceMetadata(content, instanceMetadata_, instanceId,
3307 MetadataType_Instance_RemoteIp, s);
3308 }
3309
3310 if (origin_.LookupCalledAet(s))
3311 {
3312 // New in Orthanc 1.4.0
3313 SetInstanceMetadata(content, instanceMetadata_, instanceId,
3314 MetadataType_Instance_CalledAet, s);
3315 }
3316
3317 if (origin_.LookupHttpUsername(s))
3318 {
3319 // New in Orthanc 1.4.0
3320 SetInstanceMetadata(content, instanceMetadata_, instanceId,
3321 MetadataType_Instance_HttpUsername, s);
3322 }
3323 }
3324
3325 // Following metadatas are also updated if reconstructing the instance.
3326 // They might be missing since they have been introduced along Orthanc versions.
3327
3328 if (hasTransferSyntax_)
3329 {
3330 // New in Orthanc 1.2.0
3331 SetInstanceMetadata(content, instanceMetadata_, instanceId,
3332 MetadataType_Instance_TransferSyntax,
3333 GetTransferSyntaxUid(transferSyntax_));
3334 }
3335
3336 if (hasPixelDataOffset_)
3337 {
3338 // New in Orthanc 1.9.1
3339 SetInstanceMetadata(content, instanceMetadata_, instanceId,
3340 MetadataType_Instance_PixelDataOffset,
3341 boost::lexical_cast<std::string>(pixelDataOffset_));
3342 }
3343
3344 const DicomValue* value;
3345 if ((value = dicomSummary_.TestAndGetValue(DICOM_TAG_SOP_CLASS_UID)) != NULL &&
3346 !value->IsNull() &&
3347 !value->IsBinary()) 3356 !value->IsBinary())
3348 { 3357 {
3349 SetInstanceMetadata(content, instanceMetadata_, instanceId, 3358 SetInstanceMetadata(content, instanceMetadata_, instanceId,
3350 MetadataType_Instance_SopClassUid, value->GetContent()); 3359 MetadataType_Instance_IndexInSeries, Toolbox::StripSpaces(value->GetContent()));
3351 } 3360 }
3352 3361 }
3353 3362
3354 if ((value = dicomSummary_.TestAndGetValue(DICOM_TAG_INSTANCE_NUMBER)) != NULL || 3363
3355 (value = dicomSummary_.TestAndGetValue(DICOM_TAG_IMAGE_INDEX)) != NULL) 3364 transaction.SetResourcesContent(content);
3356 { 3365 }
3357 if (!value->IsNull() && 3366
3358 !value->IsBinary()) 3367
3359 { 3368 // Check whether the series of this new instance is now completed
3360 SetInstanceMetadata(content, instanceMetadata_, instanceId, 3369 int64_t expectedNumberOfInstances;
3361 MetadataType_Instance_IndexInSeries, Toolbox::StripSpaces(value->GetContent())); 3370 if (ComputeExpectedNumberOfInstances(expectedNumberOfInstances, dicomSummary_))
3362 } 3371 {
3363 } 3372 SeriesStatus seriesStatus = transaction.GetSeriesStatus(status.seriesId_, expectedNumberOfInstances);
3364 3373 if (seriesStatus == SeriesStatus_Complete)
3374 {
3375 transaction.LogChange(status.seriesId_, ChangeType_CompletedSeries, ResourceType_Series, hashSeries_);
3376 }
3377 }
3365 3378
3366 transaction.SetResourcesContent(content); 3379 transaction.LogChange(status.seriesId_, ChangeType_NewChildInstance, ResourceType_Series, hashSeries_);
3367 } 3380 transaction.LogChange(status.studyId_, ChangeType_NewChildInstance, ResourceType_Study, hashStudy_);
3368 3381 transaction.LogChange(status.patientId_, ChangeType_NewChildInstance, ResourceType_Patient, hashPatient_);
3369 3382
3370 // Check whether the series of this new instance is now completed 3383 // Mark the parent resources of this instance as unstable
3371 int64_t expectedNumberOfInstances; 3384 transaction.GetTransactionContext().MarkAsUnstable(status.seriesId_, ResourceType_Series, hashSeries_);
3372 if (ComputeExpectedNumberOfInstances(expectedNumberOfInstances, dicomSummary_)) 3385 transaction.GetTransactionContext().MarkAsUnstable(status.studyId_, ResourceType_Study, hashStudy_);
3373 { 3386 transaction.GetTransactionContext().MarkAsUnstable(status.patientId_, ResourceType_Patient, hashPatient_);
3374 SeriesStatus seriesStatus = transaction.GetSeriesStatus(status.seriesId_, expectedNumberOfInstances); 3387 transaction.GetTransactionContext().SignalAttachmentsAdded(instanceSize);
3375 if (seriesStatus == SeriesStatus_Complete) 3388
3376 { 3389 storeStatus_ = StoreStatus_Success;
3377 transaction.LogChange(status.seriesId_, ChangeType_CompletedSeries, ResourceType_Series, hashSeries_);
3378 }
3379 }
3380
3381 transaction.LogChange(status.seriesId_, ChangeType_NewChildInstance, ResourceType_Series, hashSeries_);
3382 transaction.LogChange(status.studyId_, ChangeType_NewChildInstance, ResourceType_Study, hashStudy_);
3383 transaction.LogChange(status.patientId_, ChangeType_NewChildInstance, ResourceType_Patient, hashPatient_);
3384
3385 // Mark the parent resources of this instance as unstable
3386 transaction.GetTransactionContext().MarkAsUnstable(status.seriesId_, ResourceType_Series, hashSeries_);
3387 transaction.GetTransactionContext().MarkAsUnstable(status.studyId_, ResourceType_Study, hashStudy_);
3388 transaction.GetTransactionContext().MarkAsUnstable(status.patientId_, ResourceType_Patient, hashPatient_);
3389 transaction.GetTransactionContext().SignalAttachmentsAdded(instanceSize);
3390
3391 storeStatus_ = StoreStatus_Success;
3392 }
3393 catch (OrthancException& e)
3394 {
3395 if (e.GetErrorCode() == ErrorCode_DatabaseCannotSerialize)
3396 {
3397 throw; // the transaction has failed -> do not commit the current transaction (and retry)
3398 }
3399 else
3400 {
3401 LOG(ERROR) << "EXCEPTION [" << e.What() << " - " << e.GetDetails() << "]";
3402
3403 if (e.GetErrorCode() == ErrorCode_FullStorage)
3404 {
3405 throw; // do not commit the current transaction
3406 }
3407
3408 // this is an expected failure, exit normaly and commit the current transaction
3409 storeStatus_ = StoreStatus_Failure;
3410 }
3411 }
3412 } 3390 }
3413 }; 3391 };
3414 3392
3415 3393
3416 Operations operations(instanceMetadata, dicomSummary, attachments, metadata, origin, 3394 Operations operations(instanceMetadata, dicomSummary, attachments, metadata, origin,
3417 overwrite, hasTransferSyntax, transferSyntax, hasPixelDataOffset, 3395 overwrite, hasTransferSyntax, transferSyntax, hasPixelDataOffset,
3418 pixelDataOffset, maximumStorageMode, maximumStorageSize, maximumPatients, isReconstruct); 3396 pixelDataOffset, maximumStorageMode, maximumStorageSize, maximumPatients, isReconstruct);
3419 Apply(operations); 3397
3420 return operations.GetStoreStatus(); 3398 try
3399 {
3400 Apply(operations);
3401 return operations.GetStoreStatus();
3402 }
3403 catch (OrthancException& e)
3404 {
3405 if (e.GetErrorCode() == ErrorCode_FullStorage)
3406 {
3407 return StoreStatus_StorageFull;
3408 }
3409 else
3410 {
3411 // the transaction has failed -> do not commit the current transaction (and retry)
3412 throw;
3413 }
3414 }
3421 } 3415 }
3422 3416
3423 3417
3424 StoreStatus StatelessDatabaseOperations::AddAttachment(int64_t& newRevision, 3418 StoreStatus StatelessDatabaseOperations::AddAttachment(int64_t& newRevision,
3425 const FileInfo& attachment, 3419 const FileInfo& attachment,
3473 { 3467 {
3474 ResourceType resourceType; 3468 ResourceType resourceType;
3475 int64_t resourceId; 3469 int64_t resourceId;
3476 if (!transaction.LookupResource(resourceId, resourceType, publicId_)) 3470 if (!transaction.LookupResource(resourceId, resourceType, publicId_))
3477 { 3471 {
3478 status_ = StoreStatus_Failure; // Inexistent resource 3472 throw OrthancException(ErrorCode_InexistentItem, HttpStatus_404_NotFound);
3479 } 3473 }
3480 else 3474 else
3481 { 3475 {
3482 // Possibly remove previous attachment 3476 // Possibly remove previous attachment
3483 { 3477 {