Mercurial > hg > orthanc
comparison OrthancServer/Plugins/Samples/DelayedDeletion/Plugin.cpp @ 5031:eec3e4a91663 delayed-deletion
DelayedDeletion plugin: first version
author | Alain Mazy <am@osimis.io> |
---|---|
date | Tue, 21 Jun 2022 17:29:36 +0200 |
parents | c2ebc47f4f18 |
children | ae3f29be5ca5 |
comparison
equal
deleted
inserted
replaced
5030:d6ed4c73c719 | 5031:eec3e4a91663 |
---|---|
1 #include "PendingDeletionsDatabase.h" | 1 #include "PendingDeletionsDatabase.h" |
2 #include "LargeDeleteJob.h" | |
3 | 2 |
4 #include "../../../../OrthancFramework/Sources/FileStorage/FilesystemStorage.h" | 3 #include "../../../../OrthancFramework/Sources/FileStorage/FilesystemStorage.h" |
5 #include "../../../../OrthancFramework/Sources/Logging.h" | 4 #include "../../../../OrthancFramework/Sources/Logging.h" |
6 #include "../../../../OrthancFramework/Sources/MultiThreading/SharedMessageQueue.h" | 5 #include "../../../../OrthancFramework/Sources/MultiThreading/SharedMessageQueue.h" |
7 #include "../../../../OrthancServer/Plugins/Engine/PluginsEnumerations.h" | 6 #include "../../../../OrthancServer/Plugins/Engine/PluginsEnumerations.h" |
7 #include "../../../../OrthancServer/Plugins/Samples/Common/OrthancPluginCppWrapper.h" | |
8 | 8 |
9 #include <boost/thread.hpp> | 9 #include <boost/thread.hpp> |
10 | |
11 | |
12 #define ASYNCHRONOUS_SQLITE 0 | |
13 | 10 |
14 | 11 |
15 class PendingDeletion : public Orthanc::IDynamicObject | 12 class PendingDeletion : public Orthanc::IDynamicObject |
16 { | 13 { |
17 private: | 14 private: |
36 return uuid_; | 33 return uuid_; |
37 } | 34 } |
38 }; | 35 }; |
39 | 36 |
40 | 37 |
41 | 38 static const char* DELAYED_DELETION = "DelayedDeletion"; |
42 | 39 static bool continue_ = false; |
43 | |
44 static bool continue_; | |
45 static Orthanc::SharedMessageQueue queue_; | 40 static Orthanc::SharedMessageQueue queue_; |
46 static std::unique_ptr<Orthanc::FilesystemStorage> storage_; | 41 static std::unique_ptr<Orthanc::FilesystemStorage> storage_; |
47 static std::unique_ptr<PendingDeletionsDatabase> db_; | 42 static std::unique_ptr<PendingDeletionsDatabase> db_; |
48 static std::unique_ptr<boost::thread> databaseThread_; | |
49 static std::unique_ptr<boost::thread> deletionThread_; | 43 static std::unique_ptr<boost::thread> deletionThread_; |
50 | 44 static const char* databaseServerIdentifier_ = NULL; |
45 static unsigned int throttleDelayMs_ = 0; | |
51 | 46 |
52 | 47 |
53 static OrthancPluginErrorCode StorageCreate(const char* uuid, | 48 static OrthancPluginErrorCode StorageCreate(const char* uuid, |
54 const void* content, | 49 const void* content, |
55 int64_t size, | 50 int64_t size, |
132 static OrthancPluginErrorCode StorageRemove(const char* uuid, | 127 static OrthancPluginErrorCode StorageRemove(const char* uuid, |
133 OrthancPluginContentType type) | 128 OrthancPluginContentType type) |
134 { | 129 { |
135 try | 130 try |
136 { | 131 { |
137 #if ASYNCHRONOUS_SQLITE == 1 | 132 LOG(INFO) << "DelayedDeletion - Scheduling delayed deletion of " << uuid; |
138 queue_.Enqueue(new PendingDeletion(Orthanc::Plugins::Convert(type), uuid)); | |
139 #else | |
140 db_->Enqueue(uuid, Orthanc::Plugins::Convert(type)); | 133 db_->Enqueue(uuid, Orthanc::Plugins::Convert(type)); |
141 #endif | |
142 | 134 |
143 return OrthancPluginErrorCode_Success; | 135 return OrthancPluginErrorCode_Success; |
144 } | 136 } |
145 catch (Orthanc::OrthancException& e) | 137 catch (Orthanc::OrthancException& e) |
146 { | 138 { |
150 { | 142 { |
151 return OrthancPluginErrorCode_StorageAreaPlugin; | 143 return OrthancPluginErrorCode_StorageAreaPlugin; |
152 } | 144 } |
153 } | 145 } |
154 | 146 |
155 | |
156 static void DatabaseWorker() | |
157 { | |
158 #if ASYNCHRONOUS_SQLITE == 1 | |
159 while (continue_) | |
160 { | |
161 for (;;) | |
162 { | |
163 std::auto_ptr<Orthanc::IDynamicObject> obj(queue_.Dequeue(100)); | |
164 if (obj.get() == NULL) | |
165 { | |
166 break; | |
167 } | |
168 else | |
169 { | |
170 const PendingDeletion& deletion = dynamic_cast<const PendingDeletion&>(*obj); | |
171 db_->Enqueue(deletion.GetUuid(), deletion.GetType()); | |
172 } | |
173 } | |
174 } | |
175 #endif | |
176 } | |
177 | |
178 | |
179 static void DeletionWorker() | 147 static void DeletionWorker() |
180 { | 148 { |
149 static const unsigned int GRANULARITY = 100; // In milliseconds | |
150 | |
181 while (continue_) | 151 while (continue_) |
182 { | 152 { |
183 std::string uuid; | 153 std::string uuid; |
184 Orthanc::FileContentType type = Orthanc::FileContentType_Dicom; // Dummy initialization | 154 Orthanc::FileContentType type = Orthanc::FileContentType_Dicom; // Dummy initialization |
185 | 155 |
186 bool hasDeleted = false; | 156 bool hasDeleted = false; |
187 | 157 |
188 while (db_->Dequeue(uuid, type)) | 158 while (continue_ && db_->Dequeue(uuid, type)) |
189 { | 159 { |
190 if (!hasDeleted) | 160 if (!hasDeleted) |
191 { | 161 { |
192 LOG(INFO) << "TEST DELETION - Starting to process the pending deletions"; | 162 LOG(INFO) << "DelayedDeletion - Starting to process the pending deletions"; |
193 } | 163 } |
194 | 164 |
195 hasDeleted = true; | 165 hasDeleted = true; |
196 | 166 |
197 try | 167 try |
198 { | 168 { |
199 LOG(INFO) << "TEST DELETION - Asynchronous removal of file: " << uuid; | 169 LOG(INFO) << "DelayedDeletion - Asynchronous removal of file: " << uuid; |
200 storage_->Remove(uuid, type); | 170 storage_->Remove(uuid, type); |
171 | |
172 if (throttleDelayMs_ > 0) | |
173 { | |
174 boost::this_thread::sleep(boost::posix_time::milliseconds(throttleDelayMs_)); | |
175 } | |
201 } | 176 } |
202 catch (Orthanc::OrthancException&) | 177 catch (Orthanc::OrthancException& ex) |
203 { | 178 { |
204 LOG(ERROR) << "Cannot remove file: " << uuid; | 179 LOG(ERROR) << "DelayedDeletion - Cannot remove file: " << uuid << " " << ex.What(); |
205 } | 180 } |
206 } | 181 } |
207 | 182 |
208 if (hasDeleted) | 183 if (hasDeleted) |
209 { | 184 { |
210 LOG(INFO) << "TEST DELETION - All the pending deletions have been completed"; | 185 LOG(INFO) << "DelayedDeletion - All the pending deletions have been completed"; |
211 } | 186 } |
212 | 187 |
213 boost::this_thread::sleep(boost::posix_time::milliseconds(100)); | 188 boost::this_thread::sleep(boost::posix_time::milliseconds(GRANULARITY)); |
214 } | 189 } |
215 } | 190 } |
216 | 191 |
217 | 192 |
218 OrthancPluginErrorCode OnChangeCallback(OrthancPluginChangeType changeType, | 193 OrthancPluginErrorCode OnChangeCallback(OrthancPluginChangeType changeType, |
220 const char* resourceId) | 195 const char* resourceId) |
221 { | 196 { |
222 switch (changeType) | 197 switch (changeType) |
223 { | 198 { |
224 case OrthancPluginChangeType_OrthancStarted: | 199 case OrthancPluginChangeType_OrthancStarted: |
225 assert(deletionThread_.get() == NULL && | 200 assert(deletionThread_.get() == NULL); |
226 databaseThread_.get() == NULL); | |
227 | 201 |
228 LOG(WARNING) << "TEST DELETION - Starting the threads"; | 202 LOG(WARNING) << "DelayedDeletion - Starting the deletion thread"; |
229 continue_ = true; | 203 continue_ = true; |
230 deletionThread_.reset(new boost::thread(DeletionWorker)); | 204 deletionThread_.reset(new boost::thread(DeletionWorker)); |
231 databaseThread_.reset(new boost::thread(DatabaseWorker)); | |
232 break; | 205 break; |
233 | 206 |
234 case OrthancPluginChangeType_OrthancStopped: | 207 case OrthancPluginChangeType_OrthancStopped: |
235 | 208 |
236 if (deletionThread_.get() != NULL) | 209 if (deletionThread_.get() != NULL) |
237 { | 210 { |
238 LOG(WARNING) << "TEST DELETION - Stopping the deletion thread"; | 211 LOG(WARNING) << "DelayedDeletion - Stopping the deletion thread"; |
239 continue_ = false; | 212 continue_ = false; |
240 if (deletionThread_->joinable()) | 213 if (deletionThread_->joinable()) |
241 { | 214 { |
242 deletionThread_->join(); | 215 deletionThread_->join(); |
243 } | 216 } |
244 } | 217 } |
245 | 218 |
246 if (databaseThread_.get() != NULL) | |
247 { | |
248 LOG(WARNING) << "TEST DELETION - Stopping the database thread"; | |
249 continue_ = false; | |
250 if (databaseThread_->joinable()) | |
251 { | |
252 databaseThread_->join(); | |
253 } | |
254 } | |
255 | |
256 break; | 219 break; |
257 | 220 |
258 default: | 221 default: |
259 break; | 222 break; |
260 } | 223 } |
262 return OrthancPluginErrorCode_Success; | 225 return OrthancPluginErrorCode_Success; |
263 } | 226 } |
264 | 227 |
265 | 228 |
266 | 229 |
267 void Statistics(OrthancPluginRestOutput* output, | 230 void GetPluginStatus(OrthancPluginRestOutput* output, |
268 const char* url, | 231 const char* url, |
269 const OrthancPluginHttpRequest* request) | 232 const OrthancPluginHttpRequest* request) |
270 { | 233 { |
271 Json::Value stats; | 234 |
272 OrthancPlugins::RestApiGet(stats, "/statistics", false); | 235 Json::Value status; |
273 | 236 status["FilesPendingDeletion"] = db_->GetSize(); |
274 stats["PendingDeletions"] = db_->GetSize(); | 237 status["DatabaseServerIdentifier"] = databaseServerIdentifier_; |
275 | 238 |
276 std::string s = stats.toStyledString(); | 239 std::string s = status.toStyledString(); |
277 OrthancPluginAnswerBuffer(OrthancPlugins::GetGlobalContext(), output, s.c_str(), | 240 OrthancPluginAnswerBuffer(OrthancPlugins::GetGlobalContext(), output, s.c_str(), |
278 s.size(), "application/json"); | 241 s.size(), "application/json"); |
279 } | 242 } |
280 | 243 |
281 | 244 |
282 | 245 |
283 extern "C" | 246 extern "C" |
284 { | 247 { |
285 ORTHANC_PLUGINS_API int32_t OrthancPluginInitialize(OrthancPluginContext* context) | 248 ORTHANC_PLUGINS_API int32_t OrthancPluginInitialize(OrthancPluginContext* context) |
286 { | 249 { |
250 OrthancPlugins::SetGlobalContext(context); | |
251 Orthanc::Logging::InitializePluginContext(context); | |
252 | |
253 | |
287 /* Check the version of the Orthanc core */ | 254 /* Check the version of the Orthanc core */ |
288 if (OrthancPluginCheckVersion(context) == 0) | 255 if (OrthancPluginCheckVersion(context) == 0) |
289 { | 256 { |
290 char info[1024]; | 257 char info[1024]; |
291 sprintf(info, "Your version of Orthanc (%s) must be above %d.%d.%d to run this plugin", | 258 sprintf(info, "Your version of Orthanc (%s) must be above %d.%d.%d to run this plugin", |
295 ORTHANC_PLUGINS_MINIMAL_REVISION_NUMBER); | 262 ORTHANC_PLUGINS_MINIMAL_REVISION_NUMBER); |
296 OrthancPluginLogError(context, info); | 263 OrthancPluginLogError(context, info); |
297 return -1; | 264 return -1; |
298 } | 265 } |
299 | 266 |
300 Orthanc::Logging::InitializePluginContext(context); | |
301 | |
302 OrthancPlugins::SetGlobalContext(context); | |
303 OrthancPluginSetDescription(context, "Plugin removing files from storage asynchronously."); | 267 OrthancPluginSetDescription(context, "Plugin removing files from storage asynchronously."); |
304 | 268 |
305 OrthancPlugins::OrthancConfiguration config; | 269 OrthancPlugins::OrthancConfiguration orthancConfig; |
306 | 270 |
307 if (config.GetBooleanValue("DelayedDeletionEnabled", false)) | 271 if (!orthancConfig.IsSection(DELAYED_DELETION)) |
308 { | 272 { |
309 // Json::Value system; | 273 LOG(WARNING) << "DelayedDeletion - plugin is loaded but not enabled (no \"DelayedDeletion\" section found in configuration)"; |
310 // OrthancPlugins::RestApiGet(system, "/system", false); | 274 return 0; |
311 // const std::string& databaseIdentifier = system["DatabaseIdentifier"].asString(); | 275 } |
312 std::string databaseServerIdentifier = config.GetDatabaseServerIdentifier(); | 276 |
313 | 277 OrthancPlugins::OrthancConfiguration delayedDeletionConfig; |
314 std::string pathStorage = config.GetStringValue("StorageDirectory", "OrthancStorage"); | 278 orthancConfig.GetSection(delayedDeletionConfig, DELAYED_DELETION); |
279 | |
280 if (delayedDeletionConfig.GetBooleanValue("Enable", true)) | |
281 { | |
282 databaseServerIdentifier_ = OrthancPluginGetDatabaseServerIdentifier(context); | |
283 throttleDelayMs_ = delayedDeletionConfig.GetUnsignedIntegerValue("ThrottleDelayMs", 0); // delay in ms | |
284 | |
285 | |
286 std::string pathStorage = orthancConfig.GetStringValue("StorageDirectory", "OrthancStorage"); | |
315 LOG(WARNING) << "DelayedDeletion - Path to the storage area: " << pathStorage; | 287 LOG(WARNING) << "DelayedDeletion - Path to the storage area: " << pathStorage; |
316 | 288 |
317 storage_.reset(new Orthanc::FilesystemStorage(pathStorage)); | 289 storage_.reset(new Orthanc::FilesystemStorage(pathStorage)); |
318 | 290 |
319 boost::filesystem::path p = boost::filesystem::path(pathStorage) / ("pending-deletions." + databaseServerIdentifier + ".db"); | 291 boost::filesystem::path defaultDbPath = boost::filesystem::path(pathStorage) / (std::string("pending-deletions.") + databaseServerIdentifier_ + ".db"); |
320 LOG(WARNING) << "DelayedDeletion - Path to the SQLite database: " << p.string(); | 292 std::string dbPath = delayedDeletionConfig.GetStringValue("Path", defaultDbPath.string()); |
293 | |
294 LOG(WARNING) << "DelayedDeletion - Path to the SQLite database: " << dbPath; | |
321 | 295 |
322 // This must run after the allocation of "storage_", to make sure | 296 // This must run after the allocation of "storage_", to make sure |
323 // that the folder actually exists | 297 // that the folder actually exists |
324 db_.reset(new PendingDeletionsDatabase(p.string())); | 298 db_.reset(new PendingDeletionsDatabase(dbPath)); |
325 | 299 |
326 OrthancPluginRegisterStorageArea2(context, StorageCreate, StorageReadWhole, StorageReadRange, StorageRemove); | 300 OrthancPluginRegisterStorageArea2(context, StorageCreate, StorageReadWhole, StorageReadRange, StorageRemove); |
327 | 301 |
328 OrthancPluginRegisterOnChangeCallback(context, OnChangeCallback); | 302 OrthancPluginRegisterOnChangeCallback(context, OnChangeCallback); |
303 | |
304 OrthancPlugins::RegisterRestCallback<GetPluginStatus>(std::string("/plugins/") + ORTHANC_PLUGIN_NAME + "/status", true); | |
329 } | 305 } |
330 else | 306 else |
331 { | 307 { |
332 LOG(WARNING) << "DelayedDeletion - plugin is loaded but not enabled (check your \"DelayedDeletionEnabled\" configuration)"; | 308 LOG(WARNING) << "DelayedDeletion - plugin is loaded but disabled (check your \"DelayedDeletion.Enable\" configuration)"; |
333 } | 309 } |
334 | |
335 OrthancPlugins::RegisterRestCallback<LargeDeleteJob::RestHandler>("/tools/large-delete", true); | |
336 OrthancPlugins::RegisterRestCallback<Statistics>("/statistics", true); | |
337 | 310 |
338 return 0; | 311 return 0; |
339 } | 312 } |
340 | 313 |
341 ORTHANC_PLUGINS_API void OrthancPluginFinalize() | 314 ORTHANC_PLUGINS_API void OrthancPluginFinalize() |