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 }