comparison OrthancServer/Plugins/Samples/DelayedDeletion/LargeDeleteJob.cpp @ 5024:c2ebc47f4f18 delayed-deletion

wip: adding DelayedDeletion plugin
author Alain Mazy <am@osimis.io>
date Mon, 20 Jun 2022 16:53:21 +0200
parents
children 99751c5a7cfe
comparison
equal deleted inserted replaced
5021:559b35d18ef7 5024:c2ebc47f4f18
1 #include "LargeDeleteJob.h"
2
3 #include "../../../../OrthancFramework/Sources/Logging.h"
4 #include "../../../../OrthancFramework/Sources/OrthancException.h"
5
6 #include <json/reader.h>
7
8 void LargeDeleteJob::UpdateDeleteProgress()
9 {
10 size_t total = 2 * resources_.size() + instances_.size() + series_.size();
11
12 float progress;
13 if (total == 0)
14 {
15 progress = 1;
16 }
17 else
18 {
19 progress = (static_cast<float>(posResources_ + posInstances_ + posSeries_ + posDelete_) /
20 static_cast<float>(total));
21 }
22
23 UpdateProgress(progress);
24 }
25
26
27 void LargeDeleteJob::ScheduleChildrenResources(std::vector<std::string>& target,
28 const std::string& uri)
29 {
30 Json::Value items;
31 if (OrthancPlugins::RestApiGet(items, uri, false))
32 {
33 if (items.type() != Json::arrayValue)
34 {
35 throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
36 }
37
38 for (Json::Value::ArrayIndex i = 0; i < items.size(); i++)
39 {
40 if (items[i].type() != Json::objectValue ||
41 !items[i].isMember("ID") ||
42 items[i]["ID"].type() != Json::stringValue)
43 {
44 throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
45 }
46 else
47 {
48 target.push_back(items[i]["ID"].asString());
49 }
50 }
51 }
52 }
53
54
55 void LargeDeleteJob::ScheduleResource(Orthanc::ResourceType level,
56 const std::string& id)
57 {
58 #if 0
59 // Instance-level granularity => very slow!
60 switch (level)
61 {
62 case Orthanc::ResourceType_Patient:
63 ScheduleChildrenResources(instances_, "/patients/" + id + "/instances");
64 break;
65
66 case Orthanc::ResourceType_Study:
67 ScheduleChildrenResources(instances_, "/studies/" + id + "/instances");
68 break;
69
70 case Orthanc::ResourceType_Series:
71 ScheduleChildrenResources(instances_, "/series/" + id + "/instances");
72 break;
73
74 case Orthanc::ResourceType_Instance:
75 instances_.push_back(id);
76 break;
77
78 default:
79 throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
80 }
81 #else
82 /**
83 * Series-level granularity => looks like a good compromise between
84 * having the Orthanc mutex locked during all the study, and very
85 * slow instance-level granularity.
86 **/
87 switch (level)
88 {
89 case Orthanc::ResourceType_Patient:
90 ScheduleChildrenResources(series_, "/patients/" + id + "/series");
91 break;
92
93 case Orthanc::ResourceType_Study:
94 ScheduleChildrenResources(series_, "/studies/" + id + "/series");
95 break;
96
97 case Orthanc::ResourceType_Series:
98 series_.push_back(id);
99 break;
100
101 case Orthanc::ResourceType_Instance:
102 instances_.push_back(id);
103 break;
104
105 default:
106 throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
107 }
108 #endif
109 }
110
111
112 void LargeDeleteJob::DeleteResource(Orthanc::ResourceType level,
113 const std::string& id)
114 {
115 std::string uri;
116 switch (level)
117 {
118 case Orthanc::ResourceType_Patient:
119 uri = "/patients/" + id;
120 break;
121
122 case Orthanc::ResourceType_Study:
123 uri = "/studies/" + id;
124 break;
125
126 case Orthanc::ResourceType_Series:
127 uri = "/series/" + id;
128 break;
129
130 case Orthanc::ResourceType_Instance:
131 uri = "/instances/" + id;
132 break;
133
134 default:
135 throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
136 }
137
138 OrthancPlugins::RestApiDelete(uri, false);
139 }
140
141
142 LargeDeleteJob::LargeDeleteJob(const std::vector<std::string>& resources,
143 const std::vector<Orthanc::ResourceType>& levels) :
144 OrthancJob("LargeDelete"),
145 resources_(resources),
146 levels_(levels),
147 posResources_(0),
148 posInstances_(0),
149 posDelete_(0)
150 {
151 if (resources.size() != levels.size())
152 {
153 throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
154 }
155 }
156
157
158 OrthancPluginJobStepStatus LargeDeleteJob::Step()
159 {
160 if (posResources_ == 0)
161 {
162 if (resources_.size() == 1)
163 {
164 // LOG(WARNING) << "LargeDeleteJob has started on resource: " << resources_[0];
165 }
166 else
167 {
168 // LOG(WARNING) << "LargeDeleteJob has started";
169 }
170 }
171
172 if (posResources_ < resources_.size())
173 {
174 // First step: Discovering all the instances of the resources
175
176 ScheduleResource(levels_[posResources_], resources_[posResources_]);
177
178 posResources_ += 1;
179 UpdateDeleteProgress();
180 return OrthancPluginJobStepStatus_Continue;
181 }
182 else if (posInstances_ < instances_.size())
183 {
184 // Second step: Deleting the instances one by one
185
186 DeleteResource(Orthanc::ResourceType_Instance, instances_[posInstances_]);
187
188 posInstances_ += 1;
189 UpdateDeleteProgress();
190 return OrthancPluginJobStepStatus_Continue;
191 }
192 else if (posSeries_ < series_.size())
193 {
194 // Third step: Deleting the series one by one
195
196 DeleteResource(Orthanc::ResourceType_Series, series_[posSeries_]);
197
198 posSeries_ += 1;
199 UpdateDeleteProgress();
200 return OrthancPluginJobStepStatus_Continue;
201 }
202 else if (posDelete_ < resources_.size())
203 {
204 // Fourth step: Make sure the resources where fully deleted
205 // (instances might have been received since the beginning of
206 // the job)
207
208 DeleteResource(levels_[posDelete_], resources_[posDelete_]);
209
210 posDelete_ += 1;
211 UpdateDeleteProgress();
212 return OrthancPluginJobStepStatus_Continue;
213 }
214 else
215 {
216 if (resources_.size() == 1)
217 {
218 // LOG(WARNING) << "LargeDeleteJob has completed on resource: " << resources_[0];
219 }
220 else
221 {
222 // LOG(WARNING) << "LargeDeleteJob has completed";
223 }
224
225 UpdateProgress(1);
226 return OrthancPluginJobStepStatus_Success;
227 }
228 }
229
230
231 void LargeDeleteJob::Reset()
232 {
233 posResources_ = 0;
234 posInstances_ = 0;
235 posDelete_ = 0;
236 instances_.clear();
237 }
238
239
240 void LargeDeleteJob::RestHandler(OrthancPluginRestOutput* output,
241 const char* url,
242 const OrthancPluginHttpRequest* request)
243 {
244 static const char* KEY_RESOURCES = "Resources";
245
246 if (request->method != OrthancPluginHttpMethod_Post)
247 {
248 OrthancPluginSendMethodNotAllowed(OrthancPlugins::GetGlobalContext(), output, "POST");
249 return;
250 }
251
252 Json::Value body;
253 Json::Reader reader;
254 if (!reader.parse(reinterpret_cast<const char*>(request->body),
255 reinterpret_cast<const char*>(request->body) + request->bodySize, body))
256 {
257 throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat, "JSON body is expected");
258 }
259
260 if (body.type() != Json::objectValue)
261 {
262 throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat,
263 "Expected a JSON object in the body");
264 }
265
266 if (!body.isMember(KEY_RESOURCES) ||
267 body[KEY_RESOURCES].type() != Json::arrayValue)
268 {
269 throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat,
270 "The JSON object must contain an array in \"" +
271 std::string(KEY_RESOURCES) + "\"");
272 }
273
274 std::vector<std::string> resources;
275 std::vector<Orthanc::ResourceType> levels;
276
277 resources.reserve(body.size());
278 levels.reserve(body.size());
279
280 const Json::Value& arr = body[KEY_RESOURCES];
281 for (Json::Value::ArrayIndex i = 0; i < arr.size(); i++)
282 {
283 if (arr[i].type() != Json::arrayValue ||
284 arr[i].size() != 2u ||
285 arr[i][0].type() != Json::stringValue ||
286 arr[i][1].type() != Json::stringValue)
287 {
288 throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat,
289 "Each entry must be an array containing 2 strings, "
290 "the resource level and its ID");
291 }
292 else
293 {
294 levels.push_back(Orthanc::StringToResourceType(arr[i][0].asCString()));
295 resources.push_back(arr[i][1].asString());
296 }
297 }
298
299 OrthancPlugins::OrthancJob::SubmitFromRestApiPost(
300 output, body, new LargeDeleteJob(resources, levels));
301 }