comparison OrthancServer/Plugins/Samples/DelayedDeletion/Plugin.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 eec3e4a91663
comparison
equal deleted inserted replaced
5021:559b35d18ef7 5024:c2ebc47f4f18
1 #include "PendingDeletionsDatabase.h"
2 #include "LargeDeleteJob.h"
3
4 #include "../../../../OrthancFramework/Sources/FileStorage/FilesystemStorage.h"
5 #include "../../../../OrthancFramework/Sources/Logging.h"
6 #include "../../../../OrthancFramework/Sources/MultiThreading/SharedMessageQueue.h"
7 #include "../../../../OrthancServer/Plugins/Engine/PluginsEnumerations.h"
8
9 #include <boost/thread.hpp>
10
11
12 #define ASYNCHRONOUS_SQLITE 0
13
14
15 class PendingDeletion : public Orthanc::IDynamicObject
16 {
17 private:
18 Orthanc::FileContentType type_;
19 std::string uuid_;
20
21 public:
22 PendingDeletion(Orthanc::FileContentType type,
23 const std::string& uuid) :
24 type_(type),
25 uuid_(uuid)
26 {
27 }
28
29 Orthanc::FileContentType GetType() const
30 {
31 return type_;
32 }
33
34 const std::string& GetUuid() const
35 {
36 return uuid_;
37 }
38 };
39
40
41
42
43
44 static bool continue_;
45 static Orthanc::SharedMessageQueue queue_;
46 static std::unique_ptr<Orthanc::FilesystemStorage> storage_;
47 static std::unique_ptr<PendingDeletionsDatabase> db_;
48 static std::unique_ptr<boost::thread> databaseThread_;
49 static std::unique_ptr<boost::thread> deletionThread_;
50
51
52
53 static OrthancPluginErrorCode StorageCreate(const char* uuid,
54 const void* content,
55 int64_t size,
56 OrthancPluginContentType type)
57 {
58 try
59 {
60 storage_->Create(uuid, content, size, Orthanc::Plugins::Convert(type));
61 return OrthancPluginErrorCode_Success;
62 }
63 catch (Orthanc::OrthancException& e)
64 {
65 return static_cast<OrthancPluginErrorCode>(e.GetErrorCode());
66 }
67 catch (...)
68 {
69 return OrthancPluginErrorCode_StorageAreaPlugin;
70 }
71 }
72
73
74 static OrthancPluginErrorCode StorageReadWhole(OrthancPluginMemoryBuffer64* target, // Memory buffer where to store the content of the file. It must be allocated by the plugin using OrthancPluginCreateMemoryBuffer64(). The core of Orthanc will free it.
75 const char* uuid,
76 OrthancPluginContentType type)
77 {
78 try
79 {
80 std::unique_ptr<Orthanc::IMemoryBuffer> buffer(storage_->Read(uuid, Orthanc::Plugins::Convert(type)));
81
82 // copy from a buffer allocated on plugin's heap into a buffer allocated on core's heap
83 if (OrthancPluginCreateMemoryBuffer64(OrthancPlugins::GetGlobalContext(), target, buffer->GetSize()) != OrthancPluginErrorCode_Success)
84 {
85 OrthancPlugins::LogError("Delayed deletion plugin: error while reading object " + std::string(uuid) + ", cannot allocate memory of size " + boost::lexical_cast<std::string>(buffer->GetSize()) + " bytes");
86 return OrthancPluginErrorCode_StorageAreaPlugin;
87 }
88
89 memcpy(target->data, buffer->GetData(), buffer->GetSize());
90
91 return OrthancPluginErrorCode_Success;
92 }
93 catch (Orthanc::OrthancException& e)
94 {
95 return static_cast<OrthancPluginErrorCode>(e.GetErrorCode());
96 }
97 catch (...)
98 {
99 return OrthancPluginErrorCode_StorageAreaPlugin;
100 }
101 }
102
103
104 static OrthancPluginErrorCode StorageReadRange(OrthancPluginMemoryBuffer64* target, // Memory buffer where to store the content of the range. The memory buffer is allocated and freed by Orthanc. The length of the range of interest corresponds to the size of this buffer.
105 const char* uuid,
106 OrthancPluginContentType type,
107 uint64_t rangeStart)
108 {
109 try
110 {
111 std::unique_ptr<Orthanc::IMemoryBuffer> buffer(storage_->ReadRange(uuid, Orthanc::Plugins::Convert(type), rangeStart, rangeStart + target->size));
112
113 assert(buffer->GetSize() == target->size);
114
115 memcpy(target->data, buffer->GetData(), buffer->GetSize());
116
117 return OrthancPluginErrorCode_Success;
118 }
119 catch (Orthanc::OrthancException& e)
120 {
121 return static_cast<OrthancPluginErrorCode>(e.GetErrorCode());
122 }
123 catch (...)
124 {
125 return OrthancPluginErrorCode_StorageAreaPlugin;
126 }
127
128 return OrthancPluginErrorCode_Success;
129 }
130
131
132 static OrthancPluginErrorCode StorageRemove(const char* uuid,
133 OrthancPluginContentType type)
134 {
135 try
136 {
137 #if ASYNCHRONOUS_SQLITE == 1
138 queue_.Enqueue(new PendingDeletion(Orthanc::Plugins::Convert(type), uuid));
139 #else
140 db_->Enqueue(uuid, Orthanc::Plugins::Convert(type));
141 #endif
142
143 return OrthancPluginErrorCode_Success;
144 }
145 catch (Orthanc::OrthancException& e)
146 {
147 return static_cast<OrthancPluginErrorCode>(e.GetErrorCode());
148 }
149 catch (...)
150 {
151 return OrthancPluginErrorCode_StorageAreaPlugin;
152 }
153 }
154
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()
180 {
181 while (continue_)
182 {
183 std::string uuid;
184 Orthanc::FileContentType type = Orthanc::FileContentType_Dicom; // Dummy initialization
185
186 bool hasDeleted = false;
187
188 while (db_->Dequeue(uuid, type))
189 {
190 if (!hasDeleted)
191 {
192 LOG(INFO) << "TEST DELETION - Starting to process the pending deletions";
193 }
194
195 hasDeleted = true;
196
197 try
198 {
199 LOG(INFO) << "TEST DELETION - Asynchronous removal of file: " << uuid;
200 storage_->Remove(uuid, type);
201 }
202 catch (Orthanc::OrthancException&)
203 {
204 LOG(ERROR) << "Cannot remove file: " << uuid;
205 }
206 }
207
208 if (hasDeleted)
209 {
210 LOG(INFO) << "TEST DELETION - All the pending deletions have been completed";
211 }
212
213 boost::this_thread::sleep(boost::posix_time::milliseconds(100));
214 }
215 }
216
217
218 OrthancPluginErrorCode OnChangeCallback(OrthancPluginChangeType changeType,
219 OrthancPluginResourceType resourceType,
220 const char* resourceId)
221 {
222 switch (changeType)
223 {
224 case OrthancPluginChangeType_OrthancStarted:
225 assert(deletionThread_.get() == NULL &&
226 databaseThread_.get() == NULL);
227
228 LOG(WARNING) << "TEST DELETION - Starting the threads";
229 continue_ = true;
230 deletionThread_.reset(new boost::thread(DeletionWorker));
231 databaseThread_.reset(new boost::thread(DatabaseWorker));
232 break;
233
234 case OrthancPluginChangeType_OrthancStopped:
235
236 if (deletionThread_.get() != NULL)
237 {
238 LOG(WARNING) << "TEST DELETION - Stopping the deletion thread";
239 continue_ = false;
240 if (deletionThread_->joinable())
241 {
242 deletionThread_->join();
243 }
244 }
245
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;
257
258 default:
259 break;
260 }
261
262 return OrthancPluginErrorCode_Success;
263 }
264
265
266
267 void Statistics(OrthancPluginRestOutput* output,
268 const char* url,
269 const OrthancPluginHttpRequest* request)
270 {
271 Json::Value stats;
272 OrthancPlugins::RestApiGet(stats, "/statistics", false);
273
274 stats["PendingDeletions"] = db_->GetSize();
275
276 std::string s = stats.toStyledString();
277 OrthancPluginAnswerBuffer(OrthancPlugins::GetGlobalContext(), output, s.c_str(),
278 s.size(), "application/json");
279 }
280
281
282
283 extern "C"
284 {
285 ORTHANC_PLUGINS_API int32_t OrthancPluginInitialize(OrthancPluginContext* context)
286 {
287 /* Check the version of the Orthanc core */
288 if (OrthancPluginCheckVersion(context) == 0)
289 {
290 char info[1024];
291 sprintf(info, "Your version of Orthanc (%s) must be above %d.%d.%d to run this plugin",
292 context->orthancVersion,
293 ORTHANC_PLUGINS_MINIMAL_MAJOR_NUMBER,
294 ORTHANC_PLUGINS_MINIMAL_MINOR_NUMBER,
295 ORTHANC_PLUGINS_MINIMAL_REVISION_NUMBER);
296 OrthancPluginLogError(context, info);
297 return -1;
298 }
299
300 Orthanc::Logging::InitializePluginContext(context);
301
302 OrthancPlugins::SetGlobalContext(context);
303 OrthancPluginSetDescription(context, "Plugin removing files from storage asynchronously.");
304
305 OrthancPlugins::OrthancConfiguration config;
306
307 if (config.GetBooleanValue("DelayedDeletionEnabled", false))
308 {
309 // Json::Value system;
310 // OrthancPlugins::RestApiGet(system, "/system", false);
311 // const std::string& databaseIdentifier = system["DatabaseIdentifier"].asString();
312 std::string databaseServerIdentifier = config.GetDatabaseServerIdentifier();
313
314 std::string pathStorage = config.GetStringValue("StorageDirectory", "OrthancStorage");
315 LOG(WARNING) << "DelayedDeletion - Path to the storage area: " << pathStorage;
316
317 storage_.reset(new Orthanc::FilesystemStorage(pathStorage));
318
319 boost::filesystem::path p = boost::filesystem::path(pathStorage) / ("pending-deletions." + databaseServerIdentifier + ".db");
320 LOG(WARNING) << "DelayedDeletion - Path to the SQLite database: " << p.string();
321
322 // This must run after the allocation of "storage_", to make sure
323 // that the folder actually exists
324 db_.reset(new PendingDeletionsDatabase(p.string()));
325
326 OrthancPluginRegisterStorageArea2(context, StorageCreate, StorageReadWhole, StorageReadRange, StorageRemove);
327
328 OrthancPluginRegisterOnChangeCallback(context, OnChangeCallback);
329 }
330 else
331 {
332 LOG(WARNING) << "DelayedDeletion - plugin is loaded but not enabled (check your \"DelayedDeletionEnabled\" configuration)";
333 }
334
335 OrthancPlugins::RegisterRestCallback<LargeDeleteJob::RestHandler>("/tools/large-delete", true);
336 OrthancPlugins::RegisterRestCallback<Statistics>("/statistics", true);
337
338 return 0;
339 }
340
341 ORTHANC_PLUGINS_API void OrthancPluginFinalize()
342 {
343 db_.reset();
344 storage_.reset();
345 }
346
347 ORTHANC_PLUGINS_API const char* OrthancPluginGetName()
348 {
349 return ORTHANC_PLUGIN_NAME;
350 }
351
352 ORTHANC_PLUGINS_API const char* OrthancPluginGetVersion()
353 {
354 return ORTHANC_PLUGIN_VERSION;
355 }
356 }