Mercurial > hg > orthanc
comparison OrthancServer/Plugins/Samples/Housekeeper/Plugin.cpp @ 4988:8fba26292a9f
Housekeeper plugin: finalizing + integration tests ok
author | Alain Mazy <am@osimis.io> |
---|---|
date | Sat, 30 Apr 2022 19:39:40 +0200 |
parents | 40fd2a485a84 |
children | 3cdda1cec537 |
comparison
equal
deleted
inserted
replaced
4986:a25e74fad379 | 4988:8fba26292a9f |
---|---|
23 #include "../../../../OrthancFramework/Sources/Compatibility.h" | 23 #include "../../../../OrthancFramework/Sources/Compatibility.h" |
24 #include "../Common/OrthancPluginCppWrapper.h" | 24 #include "../Common/OrthancPluginCppWrapper.h" |
25 | 25 |
26 #include <boost/thread.hpp> | 26 #include <boost/thread.hpp> |
27 #include <boost/algorithm/string.hpp> | 27 #include <boost/algorithm/string.hpp> |
28 #include <boost/date_time/posix_time/posix_time.hpp> | |
28 #include <json/value.h> | 29 #include <json/value.h> |
29 #include <json/writer.h> | 30 #include <json/writer.h> |
30 #include <string.h> | 31 #include <string.h> |
31 #include <iostream> | 32 #include <iostream> |
32 #include <algorithm> | 33 #include <algorithm> |
40 static std::unique_ptr<boost::thread> workerThread_; | 41 static std::unique_ptr<boost::thread> workerThread_; |
41 static bool workerThreadShouldStop_ = false; | 42 static bool workerThreadShouldStop_ = false; |
42 static bool triggerOnStorageCompressionChange_ = true; | 43 static bool triggerOnStorageCompressionChange_ = true; |
43 static bool triggerOnMainDicomTagsChange_ = true; | 44 static bool triggerOnMainDicomTagsChange_ = true; |
44 static bool triggerOnUnnecessaryDicomAsJsonFiles_ = true; | 45 static bool triggerOnUnnecessaryDicomAsJsonFiles_ = true; |
46 static bool triggerOnIngestTranscodingChange_ = true; | |
45 | 47 |
46 | 48 |
47 struct RunningPeriod | 49 struct RunningPeriod |
48 { | 50 { |
49 int fromHour_; | 51 int fromHour_; |
110 | 112 |
111 return false; | 113 return false; |
112 } | 114 } |
113 }; | 115 }; |
114 | 116 |
117 | |
115 struct RunningPeriods | 118 struct RunningPeriods |
116 { | 119 { |
117 std::list<RunningPeriod> runningPeriods_; | 120 std::list<RunningPeriod> runningPeriods_; |
118 | 121 |
119 void load(const Json::Value& scheduleConfiguration) | 122 void load(const Json::Value& scheduleConfiguration) |
150 return false; | 153 return false; |
151 } | 154 } |
152 }; | 155 }; |
153 | 156 |
154 RunningPeriods runningPeriods_; | 157 RunningPeriods runningPeriods_; |
158 | |
155 | 159 |
156 struct DbConfiguration | 160 struct DbConfiguration |
157 { | 161 { |
158 std::string orthancVersion; | 162 std::string orthancVersion; |
159 std::string patientsMainDicomTagsSignature; | 163 std::string patientsMainDicomTagsSignature; |
160 std::string studiesMainDicomTagsSignature; | 164 std::string studiesMainDicomTagsSignature; |
161 std::string seriesMainDicomTagsSignature; | 165 std::string seriesMainDicomTagsSignature; |
162 std::string instancesMainDicomTagsSignature; | 166 std::string instancesMainDicomTagsSignature; |
167 std::string ingestTranscoding; | |
163 bool storageCompressionEnabled; | 168 bool storageCompressionEnabled; |
164 | 169 |
165 DbConfiguration() | 170 DbConfiguration() |
166 : storageCompressionEnabled(false) | 171 : storageCompressionEnabled(false) |
167 { | 172 { |
177 orthancVersion.clear(); | 182 orthancVersion.clear(); |
178 patientsMainDicomTagsSignature.clear(); | 183 patientsMainDicomTagsSignature.clear(); |
179 studiesMainDicomTagsSignature.clear(); | 184 studiesMainDicomTagsSignature.clear(); |
180 seriesMainDicomTagsSignature.clear(); | 185 seriesMainDicomTagsSignature.clear(); |
181 instancesMainDicomTagsSignature.clear(); | 186 instancesMainDicomTagsSignature.clear(); |
187 ingestTranscoding.clear(); | |
182 } | 188 } |
183 | 189 |
184 void ToJson(Json::Value& target) | 190 void ToJson(Json::Value& target) |
185 { | 191 { |
186 if (!IsDefined()) | 192 if (!IsDefined()) |
200 signatures["Instance"] = instancesMainDicomTagsSignature; | 206 signatures["Instance"] = instancesMainDicomTagsSignature; |
201 | 207 |
202 target["MainDicomTagsSignature"] = signatures; | 208 target["MainDicomTagsSignature"] = signatures; |
203 target["OrthancVersion"] = orthancVersion; | 209 target["OrthancVersion"] = orthancVersion; |
204 target["StorageCompressionEnabled"] = storageCompressionEnabled; | 210 target["StorageCompressionEnabled"] = storageCompressionEnabled; |
211 target["IngestTranscoding"] = ingestTranscoding; | |
205 } | 212 } |
206 } | 213 } |
207 | 214 |
208 void FromJson(Json::Value& source) | 215 void FromJson(Json::Value& source) |
209 { | 216 { |
216 studiesMainDicomTagsSignature = signatures["Study"].asString(); | 223 studiesMainDicomTagsSignature = signatures["Study"].asString(); |
217 seriesMainDicomTagsSignature = signatures["Series"].asString(); | 224 seriesMainDicomTagsSignature = signatures["Series"].asString(); |
218 instancesMainDicomTagsSignature = signatures["Instance"].asString(); | 225 instancesMainDicomTagsSignature = signatures["Instance"].asString(); |
219 | 226 |
220 storageCompressionEnabled = source["StorageCompressionEnabled"].asBool(); | 227 storageCompressionEnabled = source["StorageCompressionEnabled"].asBool(); |
228 ingestTranscoding = source["IngestTranscoding"].asString(); | |
221 } | 229 } |
222 } | 230 } |
223 }; | 231 }; |
232 | |
224 | 233 |
225 struct PluginStatus | 234 struct PluginStatus |
226 { | 235 { |
227 int statusVersion; | 236 int statusVersion; |
228 int64_t lastProcessedChange; | 237 int64_t lastProcessedChange; |
229 int64_t lastChangeToProcess; | 238 int64_t lastChangeToProcess; |
239 boost::posix_time::ptime lastTimeStarted; | |
230 | 240 |
231 DbConfiguration currentlyProcessingConfiguration; // last configuration being processed (has not reached last change yet) | 241 DbConfiguration currentlyProcessingConfiguration; // last configuration being processed (has not reached last change yet) |
232 DbConfiguration lastProcessedConfiguration; // last configuration that has been fully processed (till last change) | 242 DbConfiguration lastProcessedConfiguration; // last configuration that has been fully processed (till last change) |
233 | 243 |
234 PluginStatus() | 244 PluginStatus() |
235 : statusVersion(1), | 245 : statusVersion(1), |
236 lastProcessedChange(-1), | 246 lastProcessedChange(-1), |
237 lastChangeToProcess(-1) | 247 lastChangeToProcess(-1), |
248 lastTimeStarted(boost::date_time::special_values::not_a_date_time) | |
238 { | 249 { |
239 } | 250 } |
240 | 251 |
241 void ToJson(Json::Value& target) | 252 void ToJson(Json::Value& target) |
242 { | 253 { |
243 target = Json::objectValue; | 254 target = Json::objectValue; |
244 | 255 |
245 target["Version"] = statusVersion; | 256 target["Version"] = statusVersion; |
246 target["LastProcessedChange"] = Json::Value::Int64(lastProcessedChange); | 257 target["LastProcessedChange"] = Json::Value::Int64(lastProcessedChange); |
247 target["LastChangeToProcess"] = Json::Value::Int64(lastChangeToProcess); | 258 target["LastChangeToProcess"] = Json::Value::Int64(lastChangeToProcess); |
259 | |
260 if (lastTimeStarted == boost::posix_time::special_values::not_a_date_time) | |
261 { | |
262 target["LastTimeStarted"] = Json::Value::null; | |
263 } | |
264 else | |
265 { | |
266 target["LastTimeStarted"] = boost::posix_time::to_iso_string(lastTimeStarted); | |
267 } | |
248 | 268 |
249 currentlyProcessingConfiguration.ToJson(target["CurrentlyProcessingConfiguration"]); | 269 currentlyProcessingConfiguration.ToJson(target["CurrentlyProcessingConfiguration"]); |
250 lastProcessedConfiguration.ToJson(target["LastProcessedConfiguration"]); | 270 lastProcessedConfiguration.ToJson(target["LastProcessedConfiguration"]); |
251 } | 271 } |
252 | 272 |
253 void FromJson(Json::Value& source) | 273 void FromJson(Json::Value& source) |
254 { | 274 { |
255 statusVersion = source["Version"].asInt(); | 275 statusVersion = source["Version"].asInt(); |
256 lastProcessedChange = source["LastProcessedChange"].asInt64(); | 276 lastProcessedChange = source["LastProcessedChange"].asInt64(); |
257 lastChangeToProcess = source["LastChangeToProcess"].asInt64(); | 277 lastChangeToProcess = source["LastChangeToProcess"].asInt64(); |
278 if (source["LastTimeStarted"].isNull()) | |
279 { | |
280 lastTimeStarted = boost::posix_time::special_values::not_a_date_time; | |
281 } | |
282 else | |
283 { | |
284 lastTimeStarted = boost::posix_time::from_iso_string(source["LastTimeStarted"].asString()); | |
285 } | |
258 | 286 |
259 Json::Value& current = source["CurrentlyProcessingConfiguration"]; | 287 Json::Value& current = source["CurrentlyProcessingConfiguration"]; |
260 Json::Value& last = source["LastProcessedConfiguration"]; | 288 Json::Value& last = source["LastProcessedConfiguration"]; |
261 | 289 |
262 currentlyProcessingConfiguration.FromJson(current); | 290 currentlyProcessingConfiguration.FromJson(current); |
263 lastProcessedConfiguration.FromJson(last); | 291 lastProcessedConfiguration.FromJson(last); |
264 } | 292 } |
265 }; | 293 }; |
266 | 294 |
267 | 295 static PluginStatus pluginStatus_; |
268 static void ReadStatusFromDb(PluginStatus& pluginStatus) | 296 static boost::recursive_mutex pluginStatusMutex_; |
269 { | 297 |
298 static void ReadStatusFromDb() | |
299 { | |
300 boost::recursive_mutex::scoped_lock lock(pluginStatusMutex_); | |
301 | |
270 OrthancPlugins::OrthancString globalPropertyContent; | 302 OrthancPlugins::OrthancString globalPropertyContent; |
271 | 303 |
272 globalPropertyContent.Assign(OrthancPluginGetGlobalProperty(OrthancPlugins::GetGlobalContext(), | 304 globalPropertyContent.Assign(OrthancPluginGetGlobalProperty(OrthancPlugins::GetGlobalContext(), |
273 globalPropertyId_, | 305 globalPropertyId_, |
274 "")); | 306 "")); |
275 | 307 |
276 if (!globalPropertyContent.IsNullOrEmpty()) | 308 if (!globalPropertyContent.IsNullOrEmpty()) |
277 { | 309 { |
278 Json::Value jsonStatus; | 310 Json::Value jsonStatus; |
279 globalPropertyContent.ToJson(jsonStatus); | 311 globalPropertyContent.ToJson(jsonStatus); |
280 pluginStatus.FromJson(jsonStatus); | 312 pluginStatus_.FromJson(jsonStatus); |
281 } | 313 } |
282 else | 314 else |
283 { | 315 { |
284 // default config | 316 // default config |
285 pluginStatus.statusVersion = 1; | 317 pluginStatus_.statusVersion = 1; |
286 pluginStatus.lastProcessedChange = -1; | 318 pluginStatus_.lastProcessedChange = -1; |
287 pluginStatus.lastChangeToProcess = -1; | 319 pluginStatus_.lastChangeToProcess = -1; |
320 pluginStatus_.lastTimeStarted = boost::date_time::special_values::not_a_date_time; | |
288 | 321 |
289 pluginStatus.currentlyProcessingConfiguration.orthancVersion = "1.9.0"; // when we don't know, we assume some files were stored with Orthanc 1.9.0 (last version saving the dicom-as-json files) | 322 pluginStatus_.lastProcessedConfiguration.orthancVersion = "1.9.0"; // when we don't know, we assume some files were stored with Orthanc 1.9.0 (last version saving the dicom-as-json files) |
290 | 323 |
291 // default main dicom tags signature are the one from Orthanc 1.4.2 (last time the list was changed): | 324 // default main dicom tags signature are the one from Orthanc 1.4.2 (last time the list was changed): |
292 pluginStatus.currentlyProcessingConfiguration.patientsMainDicomTagsSignature = "0010,0010;0010,0020;0010,0030;0010,0040;0010,1000"; | 325 pluginStatus_.lastProcessedConfiguration.patientsMainDicomTagsSignature = "0010,0010;0010,0020;0010,0030;0010,0040;0010,1000"; |
293 pluginStatus.currentlyProcessingConfiguration.studiesMainDicomTagsSignature = "0008,0020;0008,0030;0008,0050;0008,0080;0008,0090;0008,1030;0020,000d;0020,0010;0032,1032;0032,1060"; | 326 pluginStatus_.lastProcessedConfiguration.studiesMainDicomTagsSignature = "0008,0020;0008,0030;0008,0050;0008,0080;0008,0090;0008,1030;0020,000d;0020,0010;0032,1032;0032,1060"; |
294 pluginStatus.currentlyProcessingConfiguration.seriesMainDicomTagsSignature = "0008,0021;0008,0031;0008,0060;0008,0070;0008,1010;0008,103e;0008,1070;0018,0010;0018,0015;0018,0024;0018,1030;0018,1090;0018,1400;0020,000e;0020,0011;0020,0037;0020,0105;0020,1002;0040,0254;0054,0081;0054,0101;0054,1000"; | 327 pluginStatus_.lastProcessedConfiguration.seriesMainDicomTagsSignature = "0008,0021;0008,0031;0008,0060;0008,0070;0008,1010;0008,103e;0008,1070;0018,0010;0018,0015;0018,0024;0018,1030;0018,1090;0018,1400;0020,000e;0020,0011;0020,0037;0020,0105;0020,1002;0040,0254;0054,0081;0054,0101;0054,1000"; |
295 pluginStatus.currentlyProcessingConfiguration.instancesMainDicomTagsSignature = "0008,0012;0008,0013;0008,0018;0020,0012;0020,0013;0020,0032;0020,0037;0020,0100;0020,4000;0028,0008;0054,1330"; | 328 pluginStatus_.lastProcessedConfiguration.instancesMainDicomTagsSignature = "0008,0012;0008,0013;0008,0018;0020,0012;0020,0013;0020,0032;0020,0037;0020,0100;0020,4000;0028,0008;0054,1330"; |
296 } | 329 } |
297 } | 330 } |
298 | 331 |
299 static void SaveStatusInDb(PluginStatus& pluginStatus) | 332 static void SaveStatusInDb() |
300 { | 333 { |
334 boost::recursive_mutex::scoped_lock lock(pluginStatusMutex_); | |
335 | |
301 Json::Value jsonStatus; | 336 Json::Value jsonStatus; |
302 pluginStatus.ToJson(jsonStatus); | 337 pluginStatus_.ToJson(jsonStatus); |
303 | 338 |
304 Json::StreamWriterBuilder builder; | 339 Json::StreamWriterBuilder builder; |
305 builder.settings_["indentation"] = " "; | 340 builder.settings_["indentation"] = " "; |
306 std::string serializedStatus = Json::writeString(builder, jsonStatus); | 341 std::string serializedStatus = Json::writeString(builder, jsonStatus); |
307 | 342 |
319 configuration.patientsMainDicomTagsSignature = systemInfo["MainDicomTags"]["Patient"].asString(); | 354 configuration.patientsMainDicomTagsSignature = systemInfo["MainDicomTags"]["Patient"].asString(); |
320 configuration.studiesMainDicomTagsSignature = systemInfo["MainDicomTags"]["Study"].asString(); | 355 configuration.studiesMainDicomTagsSignature = systemInfo["MainDicomTags"]["Study"].asString(); |
321 configuration.seriesMainDicomTagsSignature = systemInfo["MainDicomTags"]["Series"].asString(); | 356 configuration.seriesMainDicomTagsSignature = systemInfo["MainDicomTags"]["Series"].asString(); |
322 configuration.instancesMainDicomTagsSignature = systemInfo["MainDicomTags"]["Instance"].asString(); | 357 configuration.instancesMainDicomTagsSignature = systemInfo["MainDicomTags"]["Instance"].asString(); |
323 configuration.storageCompressionEnabled = systemInfo["StorageCompression"].asBool(); | 358 configuration.storageCompressionEnabled = systemInfo["StorageCompression"].asBool(); |
359 configuration.ingestTranscoding = systemInfo["IngestTranscoding"].asString(); | |
324 | 360 |
325 configuration.orthancVersion = OrthancPlugins::GetGlobalContext()->orthancVersion; | 361 configuration.orthancVersion = OrthancPlugins::GetGlobalContext()->orthancVersion; |
326 } | 362 } |
327 | 363 |
328 static bool NeedsProcessing(const DbConfiguration& current, const DbConfiguration& last) | 364 static void CheckNeedsProcessing(bool& needsReconstruct, bool& needsReingest, const DbConfiguration& current, const DbConfiguration& last) |
329 { | 365 { |
366 needsReconstruct = false; | |
367 needsReingest = false; | |
368 | |
330 if (!last.IsDefined()) | 369 if (!last.IsDefined()) |
331 { | 370 { |
332 return true; | 371 return; |
333 } | 372 } |
334 | 373 |
335 const char* lastVersion = last.orthancVersion.c_str(); | 374 const char* lastVersion = last.orthancVersion.c_str(); |
336 bool needsProcessing = false; | |
337 | 375 |
338 if (!OrthancPlugins::CheckMinimalVersion(lastVersion, 1, 9, 1)) | 376 if (!OrthancPlugins::CheckMinimalVersion(lastVersion, 1, 9, 1)) |
339 { | 377 { |
340 if (triggerOnUnnecessaryDicomAsJsonFiles_) | 378 if (triggerOnUnnecessaryDicomAsJsonFiles_) |
341 { | 379 { |
342 OrthancPlugins::LogWarning("Housekeeper: your storage might still contain some dicom-as-json files -> will perform housekeeping"); | 380 OrthancPlugins::LogWarning("Housekeeper: your storage might still contain some dicom-as-json files -> will perform housekeeping"); |
343 needsProcessing = true; | 381 needsReconstruct = true; // the default reconstruct removes the dicom-as-json |
344 } | 382 } |
345 else | 383 else |
346 { | 384 { |
347 OrthancPlugins::LogWarning("Housekeeper: your storage might still contain some dicom-as-json files but the trigger has been disabled"); | 385 OrthancPlugins::LogWarning("Housekeeper: your storage might still contain some dicom-as-json files but the trigger has been disabled"); |
348 } | 386 } |
351 if (last.patientsMainDicomTagsSignature != current.patientsMainDicomTagsSignature) | 389 if (last.patientsMainDicomTagsSignature != current.patientsMainDicomTagsSignature) |
352 { | 390 { |
353 if (triggerOnMainDicomTagsChange_) | 391 if (triggerOnMainDicomTagsChange_) |
354 { | 392 { |
355 OrthancPlugins::LogWarning("Housekeeper: Patient main dicom tags have changed, -> will perform housekeeping"); | 393 OrthancPlugins::LogWarning("Housekeeper: Patient main dicom tags have changed, -> will perform housekeeping"); |
356 needsProcessing = true; | 394 needsReconstruct = true; |
357 } | 395 } |
358 else | 396 else |
359 { | 397 { |
360 OrthancPlugins::LogWarning("Housekeeper: Patient main dicom tags have changed but the trigger is disabled"); | 398 OrthancPlugins::LogWarning("Housekeeper: Patient main dicom tags have changed but the trigger is disabled"); |
361 } | 399 } |
364 if (last.studiesMainDicomTagsSignature != current.studiesMainDicomTagsSignature) | 402 if (last.studiesMainDicomTagsSignature != current.studiesMainDicomTagsSignature) |
365 { | 403 { |
366 if (triggerOnMainDicomTagsChange_) | 404 if (triggerOnMainDicomTagsChange_) |
367 { | 405 { |
368 OrthancPlugins::LogWarning("Housekeeper: Study main dicom tags have changed, -> will perform housekeeping"); | 406 OrthancPlugins::LogWarning("Housekeeper: Study main dicom tags have changed, -> will perform housekeeping"); |
369 needsProcessing = true; | 407 needsReconstruct = true; |
370 } | 408 } |
371 else | 409 else |
372 { | 410 { |
373 OrthancPlugins::LogWarning("Housekeeper: Study main dicom tags have changed but the trigger is disabled"); | 411 OrthancPlugins::LogWarning("Housekeeper: Study main dicom tags have changed but the trigger is disabled"); |
374 } | 412 } |
377 if (last.seriesMainDicomTagsSignature != current.seriesMainDicomTagsSignature) | 415 if (last.seriesMainDicomTagsSignature != current.seriesMainDicomTagsSignature) |
378 { | 416 { |
379 if (triggerOnMainDicomTagsChange_) | 417 if (triggerOnMainDicomTagsChange_) |
380 { | 418 { |
381 OrthancPlugins::LogWarning("Housekeeper: Series main dicom tags have changed, -> will perform housekeeping"); | 419 OrthancPlugins::LogWarning("Housekeeper: Series main dicom tags have changed, -> will perform housekeeping"); |
382 needsProcessing = true; | 420 needsReconstruct = true; |
383 } | 421 } |
384 else | 422 else |
385 { | 423 { |
386 OrthancPlugins::LogWarning("Housekeeper: Series main dicom tags have changed but the trigger is disabled"); | 424 OrthancPlugins::LogWarning("Housekeeper: Series main dicom tags have changed but the trigger is disabled"); |
387 } | 425 } |
390 if (last.instancesMainDicomTagsSignature != current.instancesMainDicomTagsSignature) | 428 if (last.instancesMainDicomTagsSignature != current.instancesMainDicomTagsSignature) |
391 { | 429 { |
392 if (triggerOnMainDicomTagsChange_) | 430 if (triggerOnMainDicomTagsChange_) |
393 { | 431 { |
394 OrthancPlugins::LogWarning("Housekeeper: Instance main dicom tags have changed, -> will perform housekeeping"); | 432 OrthancPlugins::LogWarning("Housekeeper: Instance main dicom tags have changed, -> will perform housekeeping"); |
395 needsProcessing = true; | 433 needsReconstruct = true; |
396 } | 434 } |
397 else | 435 else |
398 { | 436 { |
399 OrthancPlugins::LogWarning("Housekeeper: Instance main dicom tags have changed but the trigger is disabled"); | 437 OrthancPlugins::LogWarning("Housekeeper: Instance main dicom tags have changed but the trigger is disabled"); |
400 } | 438 } |
411 else | 449 else |
412 { | 450 { |
413 OrthancPlugins::LogWarning("Housekeeper: storage compression is now disabled -> will perform housekeeping"); | 451 OrthancPlugins::LogWarning("Housekeeper: storage compression is now disabled -> will perform housekeeping"); |
414 } | 452 } |
415 | 453 |
416 needsProcessing = true; | 454 needsReingest = true; |
417 } | 455 } |
418 else | 456 else |
419 { | 457 { |
420 OrthancPlugins::LogWarning("Housekeeper: storage compression has changed but the trigger is disabled"); | 458 OrthancPlugins::LogWarning("Housekeeper: storage compression has changed but the trigger is disabled"); |
421 } | 459 } |
422 } | 460 } |
423 | 461 |
424 return needsProcessing; | 462 if (current.ingestTranscoding != last.ingestTranscoding) |
463 { | |
464 if (triggerOnIngestTranscodingChange_) | |
465 { | |
466 OrthancPlugins::LogWarning("Housekeeper: ingest transcoding has changed -> will perform housekeeping"); | |
467 | |
468 needsReingest = true; | |
469 } | |
470 else | |
471 { | |
472 OrthancPlugins::LogWarning("Housekeeper: ingest transcoding has changed but the trigger is disabled"); | |
473 } | |
474 } | |
475 | |
425 } | 476 } |
426 | 477 |
427 static bool ProcessChanges(PluginStatus& pluginStatus, const DbConfiguration& currentDbConfiguration) | 478 static bool ProcessChanges(bool needsReconstruct, bool needsReingest, const DbConfiguration& currentDbConfiguration) |
428 { | 479 { |
429 Json::Value changes; | 480 Json::Value changes; |
430 | 481 |
431 pluginStatus.currentlyProcessingConfiguration = currentDbConfiguration; | 482 { |
432 | 483 boost::recursive_mutex::scoped_lock lock(pluginStatusMutex_); |
433 OrthancPlugins::RestApiGet(changes, "/changes?since=" + boost::lexical_cast<std::string>(pluginStatus.lastProcessedChange) + "&limit=100", false); | 484 |
485 pluginStatus_.currentlyProcessingConfiguration = currentDbConfiguration; | |
486 | |
487 OrthancPlugins::RestApiGet(changes, "/changes?since=" + boost::lexical_cast<std::string>(pluginStatus_.lastProcessedChange) + "&limit=100", false); | |
488 } | |
434 | 489 |
435 for (Json::ArrayIndex i = 0; i < changes["Changes"].size(); i++) | 490 for (Json::ArrayIndex i = 0; i < changes["Changes"].size(); i++) |
436 { | 491 { |
437 const Json::Value& change = changes["Changes"][i]; | 492 const Json::Value& change = changes["Changes"][i]; |
438 int64_t seq = change["Seq"].asInt64(); | 493 int64_t seq = change["Seq"].asInt64(); |
439 | 494 |
440 if (change["ChangeType"] == "NewStudy") // some StableStudy might be missing if orthanc was shutdown during a StableAge -> consider only the NewStudy events that can not be missed | 495 if (change["ChangeType"] == "NewStudy") // some StableStudy might be missing if orthanc was shutdown during a StableAge -> consider only the NewStudy events that can not be missed |
441 { | 496 { |
442 Json::Value result; | 497 Json::Value result; |
443 OrthancPlugins::RestApiPost(result, "/studies/" + change["ID"].asString() + "/reconstruct", std::string(""), false); | 498 Json::Value request; |
499 if (needsReingest) | |
500 { | |
501 request["ReconstructFiles"] = true; | |
502 } | |
503 OrthancPlugins::RestApiPost(result, "/studies/" + change["ID"].asString() + "/reconstruct", request, false); | |
504 } | |
505 | |
506 { | |
507 boost::recursive_mutex::scoped_lock lock(pluginStatusMutex_); | |
508 | |
509 pluginStatus_.lastProcessedChange = seq; | |
510 | |
511 if (seq >= pluginStatus_.lastChangeToProcess) // we are done ! | |
512 { | |
513 return true; | |
514 } | |
515 } | |
516 | |
517 if (change["ChangeType"] == "NewStudy") | |
518 { | |
444 boost::this_thread::sleep(boost::posix_time::milliseconds(throttleDelay_ * 1000)); | 519 boost::this_thread::sleep(boost::posix_time::milliseconds(throttleDelay_ * 1000)); |
445 } | 520 } |
446 | |
447 if (seq >= pluginStatus.lastChangeToProcess) // we are done ! | |
448 { | |
449 return true; | |
450 } | |
451 | |
452 pluginStatus.lastProcessedChange = seq; | |
453 } | 521 } |
454 | 522 |
455 return false; | 523 return false; |
456 } | 524 } |
457 | 525 |
458 | 526 |
459 static void WorkerThread() | 527 static void WorkerThread() |
460 { | 528 { |
461 PluginStatus pluginStatus; | |
462 DbConfiguration currentDbConfiguration; | 529 DbConfiguration currentDbConfiguration; |
463 | 530 |
464 OrthancPluginLogWarning(OrthancPlugins::GetGlobalContext(), "Starting Housekeeper worker thread"); | 531 OrthancPluginLogWarning(OrthancPlugins::GetGlobalContext(), "Starting Housekeeper worker thread"); |
465 | 532 |
466 ReadStatusFromDb(pluginStatus); | 533 ReadStatusFromDb(); |
534 | |
467 GetCurrentDbConfiguration(currentDbConfiguration); | 535 GetCurrentDbConfiguration(currentDbConfiguration); |
468 | 536 |
469 if (!NeedsProcessing(currentDbConfiguration, pluginStatus.lastProcessedConfiguration)) | 537 bool needsReconstruct = false; |
538 bool needsReingest = false; | |
539 | |
540 { | |
541 boost::recursive_mutex::scoped_lock lock(pluginStatusMutex_); | |
542 CheckNeedsProcessing(needsReconstruct, needsReingest, currentDbConfiguration, pluginStatus_.lastProcessedConfiguration); | |
543 } | |
544 | |
545 bool needsProcessing = needsReconstruct || needsReingest; | |
546 | |
547 if (!needsProcessing) | |
470 { | 548 { |
471 OrthancPlugins::LogWarning("Housekeeper: everything has been processed already !"); | 549 OrthancPlugins::LogWarning("Housekeeper: everything has been processed already !"); |
472 return; | 550 return; |
473 } | 551 } |
474 | 552 |
475 if (force_ || NeedsProcessing(currentDbConfiguration, pluginStatus.currentlyProcessingConfiguration)) | 553 if (force_ || needsProcessing) |
476 { | 554 { |
477 if (force_) | 555 if (force_) |
478 { | 556 { |
479 OrthancPlugins::LogWarning("Housekeeper: forcing execution -> will perform housekeeping"); | 557 OrthancPlugins::LogWarning("Housekeeper: forcing execution -> will perform housekeeping"); |
480 } | 558 } |
484 } | 562 } |
485 | 563 |
486 Json::Value changes; | 564 Json::Value changes; |
487 OrthancPlugins::RestApiGet(changes, "/changes?last", false); | 565 OrthancPlugins::RestApiGet(changes, "/changes?last", false); |
488 | 566 |
489 pluginStatus.lastProcessedChange = 0; | 567 { |
490 pluginStatus.lastChangeToProcess = changes["Last"].asInt64(); // the last change is the last change at the time we start. We assume that every new ingested file will be constructed correctly | 568 boost::recursive_mutex::scoped_lock lock(pluginStatusMutex_); |
569 | |
570 pluginStatus_.lastProcessedChange = 0; | |
571 pluginStatus_.lastChangeToProcess = changes["Last"].asInt64(); // the last change is the last change at the time we start. We assume that every new ingested file will be constructed correctly | |
572 pluginStatus_.lastTimeStarted = boost::posix_time::microsec_clock::universal_time(); | |
573 } | |
491 } | 574 } |
492 else | 575 else |
493 { | 576 { |
494 OrthancPlugins::LogWarning("Housekeeper: the DB configuration has not changed since last run, will continue processing changes"); | 577 OrthancPlugins::LogWarning("Housekeeper: the DB configuration has not changed since last run, will continue processing changes"); |
495 } | 578 } |
496 | 579 |
497 bool completed = pluginStatus.lastChangeToProcess == 0; // if the DB is empty at start, no need to process anyting | 580 bool completed = false; |
581 { | |
582 boost::recursive_mutex::scoped_lock lock(pluginStatusMutex_); | |
583 completed = pluginStatus_.lastChangeToProcess == 0; // if the DB is empty at start, no need to process anyting | |
584 } | |
585 | |
498 bool loggedNotRightPeriodChangeMessage = false; | 586 bool loggedNotRightPeriodChangeMessage = false; |
499 | 587 |
500 while (!workerThreadShouldStop_ && !completed) | 588 while (!workerThreadShouldStop_ && !completed) |
501 { | 589 { |
502 if (runningPeriods_.isInPeriod()) | 590 if (runningPeriods_.isInPeriod()) |
503 { | 591 { |
504 completed = ProcessChanges(pluginStatus, currentDbConfiguration); | 592 completed = ProcessChanges(needsReconstruct, needsReingest, currentDbConfiguration); |
505 SaveStatusInDb(pluginStatus); | 593 SaveStatusInDb(); |
506 | 594 |
507 if (!completed) | 595 if (!completed) |
508 { | 596 { |
597 boost::recursive_mutex::scoped_lock lock(pluginStatusMutex_); | |
598 | |
509 OrthancPlugins::LogInfo("Housekeeper: processed changes " + | 599 OrthancPlugins::LogInfo("Housekeeper: processed changes " + |
510 boost::lexical_cast<std::string>(pluginStatus.lastProcessedChange) + | 600 boost::lexical_cast<std::string>(pluginStatus_.lastProcessedChange) + |
511 " / " + boost::lexical_cast<std::string>(pluginStatus.lastChangeToProcess)); | 601 " / " + boost::lexical_cast<std::string>(pluginStatus_.lastChangeToProcess)); |
512 | 602 |
513 boost::this_thread::sleep(boost::posix_time::milliseconds(throttleDelay_ * 100)); // wait 1/10 of the delay between changes | 603 boost::this_thread::sleep(boost::posix_time::milliseconds(throttleDelay_ * 100)); // wait 1/10 of the delay between changes |
514 } | 604 } |
515 | 605 |
516 loggedNotRightPeriodChangeMessage = false; | 606 loggedNotRightPeriodChangeMessage = false; |
525 } | 615 } |
526 } | 616 } |
527 | 617 |
528 if (completed) | 618 if (completed) |
529 { | 619 { |
530 pluginStatus.lastProcessedConfiguration = currentDbConfiguration; | 620 boost::recursive_mutex::scoped_lock lock(pluginStatusMutex_); |
531 pluginStatus.currentlyProcessingConfiguration.Clear(); | 621 |
532 | 622 pluginStatus_.lastProcessedConfiguration = currentDbConfiguration; |
533 pluginStatus.lastProcessedChange = -1; | 623 pluginStatus_.currentlyProcessingConfiguration.Clear(); |
534 pluginStatus.lastChangeToProcess = -1; | 624 |
625 pluginStatus_.lastProcessedChange = -1; | |
626 pluginStatus_.lastChangeToProcess = -1; | |
535 | 627 |
536 SaveStatusInDb(pluginStatus); | 628 SaveStatusInDb(); |
537 | 629 |
538 OrthancPluginLogWarning(OrthancPlugins::GetGlobalContext(), "Housekeeper: finished processing all changes"); | 630 OrthancPluginLogWarning(OrthancPlugins::GetGlobalContext(), "Housekeeper: finished processing all changes"); |
539 } | 631 } |
540 } | 632 } |
541 | 633 |
542 extern "C" | 634 extern "C" |
543 { | 635 { |
636 OrthancPluginErrorCode GetPluginStatus(OrthancPluginRestOutput* output, | |
637 const char* url, | |
638 const OrthancPluginHttpRequest* request) | |
639 { | |
640 if (request->method != OrthancPluginHttpMethod_Get) | |
641 { | |
642 OrthancPlugins::AnswerMethodNotAllowed(output, "GET"); | |
643 } | |
644 else | |
645 { | |
646 boost::recursive_mutex::scoped_lock lock(pluginStatusMutex_); | |
647 | |
648 Json::Value status; | |
649 pluginStatus_.ToJson(status); | |
650 | |
651 OrthancPlugins::AnswerJson(status, output); | |
652 } | |
653 | |
654 return OrthancPluginErrorCode_Success; | |
655 } | |
656 | |
657 | |
544 OrthancPluginErrorCode OnChangeCallback(OrthancPluginChangeType changeType, | 658 OrthancPluginErrorCode OnChangeCallback(OrthancPluginChangeType changeType, |
545 OrthancPluginResourceType resourceType, | 659 OrthancPluginResourceType resourceType, |
546 const char* resourceId) | 660 const char* resourceId) |
547 { | 661 { |
548 switch (changeType) | 662 switch (changeType) |
579 } | 693 } |
580 | 694 |
581 OrthancPlugins::LogWarning("Housekeeper plugin is initializing"); | 695 OrthancPlugins::LogWarning("Housekeeper plugin is initializing"); |
582 OrthancPluginSetDescription(c, "Optimizes your DB and storage."); | 696 OrthancPluginSetDescription(c, "Optimizes your DB and storage."); |
583 | 697 |
584 OrthancPlugins::OrthancConfiguration configuration; | 698 OrthancPlugins::OrthancConfiguration orthancConfiguration; |
585 | 699 |
586 OrthancPlugins::OrthancConfiguration housekeeper; | 700 OrthancPlugins::OrthancConfiguration housekeeper; |
587 configuration.GetSection(housekeeper, "Housekeeper"); | 701 orthancConfiguration.GetSection(housekeeper, "Housekeeper"); |
588 | 702 |
589 bool enabled = housekeeper.GetBooleanValue("Enable", false); | 703 bool enabled = housekeeper.GetBooleanValue("Enable", false); |
590 if (enabled) | 704 if (enabled) |
591 { | 705 { |
592 /* | 706 /* |
642 throttleDelay_ = housekeeper.GetUnsignedIntegerValue("ThrottleDelay", 5); | 756 throttleDelay_ = housekeeper.GetUnsignedIntegerValue("ThrottleDelay", 5); |
643 | 757 |
644 if (housekeeper.GetJson().isMember("Triggers")) | 758 if (housekeeper.GetJson().isMember("Triggers")) |
645 { | 759 { |
646 triggerOnStorageCompressionChange_ = housekeeper.GetBooleanValue("StorageCompressionChange", true); | 760 triggerOnStorageCompressionChange_ = housekeeper.GetBooleanValue("StorageCompressionChange", true); |
761 | |
647 triggerOnMainDicomTagsChange_ = housekeeper.GetBooleanValue("MainDicomTagsChange", true); | 762 triggerOnMainDicomTagsChange_ = housekeeper.GetBooleanValue("MainDicomTagsChange", true); |
648 triggerOnUnnecessaryDicomAsJsonFiles_ = housekeeper.GetBooleanValue("UnnecessaryDicomAsJsonFiles", true); | 763 triggerOnUnnecessaryDicomAsJsonFiles_ = housekeeper.GetBooleanValue("UnnecessaryDicomAsJsonFiles", true); |
764 triggerOnIngestTranscodingChange_ = housekeeper.GetBooleanValue("IngestTranscodingChange", true); | |
649 } | 765 } |
650 | 766 |
651 if (housekeeper.GetJson().isMember("Schedule")) | 767 if (housekeeper.GetJson().isMember("Schedule")) |
652 { | 768 { |
653 runningPeriods_.load(housekeeper.GetJson()["Schedule"]); | 769 runningPeriods_.load(housekeeper.GetJson()["Schedule"]); |
654 } | 770 } |
655 | 771 |
656 OrthancPluginRegisterOnChangeCallback(c, OnChangeCallback); | 772 OrthancPluginRegisterOnChangeCallback(c, OnChangeCallback); |
773 OrthancPluginRegisterRestCallback(c, "/housekeeper/status", GetPluginStatus); | |
657 } | 774 } |
658 else | 775 else |
659 { | 776 { |
660 OrthancPlugins::LogWarning("Housekeeper plugin is disabled by the configuration file"); | 777 OrthancPlugins::LogWarning("Housekeeper plugin is disabled by the configuration file"); |
661 } | 778 } |