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()