5024
|
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 }
|