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