PluginCache.cpp
Go to the documentation of this file.
1#include "PluginCache.h"
2
3#include <QCryptographicHash>
4#include <QDateTime>
5#include <QDirIterator>
6#include <QFile>
7#include <QResource>
8
12
14
15namespace armarx
16{
17
21 s(armarx::ArmarXDataPath::GetDefaultUserConfigPath().c_str() + settingsApplicationName,
22 QSettings::NativeFormat)
23 {
25 QDir dir(cachePath);
26 if (!dir.exists())
27 {
28 dir.mkpath(".");
29 }
30
31
32 auto map = s.value("widgets").toMap();
33 for (QString key : map.keys())
34 {
35 QStringList w = map[key].toString().split(":");
36 QStringList keyparts = key.split(":");
37 if (w.size() == 3)
38 {
39 auto& elem = pluginData[keyparts[0]];
40 elem.pluginPath = keyparts[0];
41 elem.lastModified = QDateTime::fromMSecsSinceEpoch(w[0].toLongLong());
42 elem.hash = w[1].toLatin1();
43 ARMARX_INFO_S << "Widget: " << w[2];
44 elem.widgets[w[2]] = ArmarXWidgetInfoPtr();
45 }
46 }
47 }
48
50 {
51 shutdown = true;
52 if (preloadFuture.valid())
53 {
54 preloadFuture.wait();
55 }
56 }
57
58 bool
59 PluginCache::cachePlugin(const QString& pluginPath)
60 {
61 if (pluginPath.isEmpty())
62 {
63 return false;
64 }
65 bool found = false;
66 QFileInfo info(pluginPath);
67 {
68 std::unique_lock lock(cacheMutex);
69 for (auto& elem : pluginData)
70 {
71 // ARMARX_INFO_S << info.lastModified().toString() << elem.pluginPath;
72 if (elem.pluginPath == pluginPath)
73 {
74 if (info.lastModified() == elem.lastModified)
75 {
76 ARMARX_DEBUG_S << "Same timestamp";
77 found = true;
78 }
79 else if (getHash(pluginPath) == elem.hash)
80 {
81 ARMARX_DEBUG_S << "Same hash - timestamps "
82 << info.lastModified().toString() << " <> "
83 << elem.lastModified.toString();
85 found = true;
86 }
87 else
88 {
90 << "Plugin filestamp and hash is different - loading plugin again.";
91 }
92 break;
93 }
94 }
95 }
96 if (!found)
97 {
98 ARMARX_INFO_S << "Adding plugin " << pluginPath;
99 writeToCache(pluginPath);
100 return false;
101 }
102 ARMARX_INFO_S << "Found plugin " << pluginPath << " in cache";
103 return true;
104 }
105
106 bool
107 PluginCache::cacheWidget(QString widgetName, ArmarXWidgetInfoPtr widgetCreator)
108 {
109 std::unique_lock lock(cacheMutex);
110 pluginData[""].widgets[widgetName] = widgetCreator;
111 return true;
112 }
113
115 PluginCache::getWidgetCreator(const QString& widgetName)
116 {
117 std::unique_lock lock(cacheMutex);
118 for (const PluginData& data : pluginData)
119 {
120 WidgetCreatorMap::const_iterator it = data.widgets.find(widgetName);
121 if (it != data.widgets.end())
122 {
123 if (it->second)
124 {
125 return it->second;
126 }
127 }
128 }
129 PluginData data = loadFromCache(widgetName);
130 pluginData[data.pluginPath] = data;
131 WidgetCreatorMap::const_iterator it = data.widgets.find(widgetName);
132 if (it != data.widgets.end())
133 {
134 return it->second;
135 }
136 // widget does not exist (anymore) -> remove from cache file
137 removeWidgetFromCache(data.pluginPath, widgetName);
138 return ArmarXWidgetInfoPtr();
139 }
140
141 QStringList
143 {
144 std::unique_lock lock(cacheMutex);
145 QStringList result;
146 for (const PluginData& data : pluginData)
147 {
148 for (const auto& elem : data.widgets)
149 {
150 result << elem.first;
151 }
152 }
153 return result;
154 }
155
158 {
159 std::unique_lock lock(cacheMutex);
160 WidgetCreatorMap result;
161 for (const PluginData& data : pluginData)
162 {
163 result.insert(data.widgets.begin(), data.widgets.end());
164 }
165 return result;
166 }
167
168 void
170 {
171 QDirIterator it(":", QDirIterator::Subdirectories);
172 while (it.hasNext())
173 {
174 QString path = it.next().remove(0, 1);
175 auto newPath = cachePath + "/resources/" + path;
176 QDir dir(newPath);
177 if (!dir.exists())
178 {
179 dir.mkpath(".");
180 }
181 if (!path.isEmpty() && !QFile::exists(newPath))
182 {
183 QFile::copy(it.next(), newPath);
184 }
185 }
186 }
187
188 QString
190 {
191 return QString(armarx::ArmarXDataPath::GetCachePath().c_str()) + "/icons/";
192 }
193
194 QString
195 PluginCache::GetIconPath(const QString& widgetName)
196 {
197 return GetIconCachePath() + widgetName + ".png";
198 }
199
200 QString
201 PluginCache::GetCategoryIconPath(const QString& widgetName)
202 {
203 return GetIconCachePath() + widgetName + "-category.png";
204 }
205
206 bool
207 PluginCache::ContainsAny(const QString& str, const QStringList& items)
208 {
209 for (const QString& item : items)
210 {
211 if (str.contains(item, Qt::CaseInsensitive))
212 {
213 return true;
214 }
215 }
216 return false;
217 }
218
219 void
220 PluginCache::preloadAsync(QStringList widgetNames, int delayMS)
221 {
222 widgetNames.removeDuplicates();
223 auto preload = [this](QStringList widgetNames, int delayMS)
224 {
225 QStringList blacklist = {"libPointCloudViewerGuiPlugin"};
226 usleep(delayMS * 1000);
227 for (QString widgetName : widgetNames)
228 {
229 {
230 std::unique_lock lock(cacheMutex);
231 QString pluginPath;
232 for (PluginData& data : pluginData)
233 {
234 if (ContainsAny(data.pluginPath, blacklist))
235 {
237 << deactivateSpam(10, data.pluginPath.toStdString())
238 << "skipping blacklisted plugin for preload: " << data.pluginPath;
239 continue;
240 }
241 for (auto& elem : data.widgets)
242 {
243 if (shutdown)
244 {
245 return;
246 }
247 if (widgetName == elem.first)
248 {
249 pluginPath = data.pluginPath;
250 if (!data.pluginLoader && !pluginPath.isEmpty())
251 {
252 auto pluginLoader = QSharedPointer<QPluginLoader>(
253 new QPluginLoader(pluginPath));
254 ARMARX_VERBOSE_S << "Loading plugin " << pluginPath;
255 pluginLoader->load();
256 // data.pluginLoader->load();
257 break;
258 }
259 }
260 }
261 }
262 }
263 usleep(1000); // give others chance to get mutex
264 }
265 };
266 preloadFuture = std::async(std::launch::async, preload, widgetNames, delayMS);
267 }
268
269 void
271 {
272 std::unique_lock lock(cacheMutex);
273 ARMARX_INFO << "Clearing plugin cache";
274 s.clear();
275 }
276
277 QByteArray
278 PluginCache::getHash(const QString& pluginPath)
279 {
280 QFile file(pluginPath);
281 QByteArray hashData;
282 if (file.open(QIODevice::ReadOnly))
283 {
284 QByteArray fileData = file.readAll();
285 hashData = QCryptographicHash::hash(fileData, QCryptographicHash::Md5);
286 }
287 return hashData.toHex();
288 }
289
290 void
291 PluginCache::removeWidgetFromCache(QString pluginPath, QString widgetName)
292 {
293 std::unique_lock lock(cacheMutex);
294 QMap<QString, QVariant> widgetMap = s.value("widgets").toMap();
295 widgetMap.remove(pluginPath + ":" + widgetName);
296 s.setValue("widgets", widgetMap);
297
298 QMap<QString, PluginData> newPluginDataMap;
299 for (auto& key : pluginData.keys())
300 {
301 if (pluginData[key].pluginPath != pluginPath &&
302 pluginData[key].widgets.count(widgetName) == 0)
303 {
304 newPluginDataMap[key] = pluginData[key];
305 }
306 }
307 pluginData.swap(newPluginDataMap);
308 }
309
310 void
312 {
313 std::unique_lock lock(cacheMutex);
314 ARMARX_INFO << "Removing plugin " << pluginPath << " from cache";
315 QMap<QString, QVariant> widgetMap = s.value("widgets").toMap();
316 QMap<QString, QVariant> newWidgetMap;
317 for (auto key : widgetMap.keys())
318 {
319 if (!key.contains(pluginPath))
320 {
321 newWidgetMap[key] = widgetMap[key];
322 }
323 }
324 QMap<QString, PluginData> newPluginDataMap;
325 for (auto& key : pluginData.keys())
326 {
327 if (pluginData[key].pluginPath != pluginPath)
328 {
329 newPluginDataMap[key] = pluginData[key];
330 }
331 }
332 pluginData.swap(newPluginDataMap);
333 s.setValue("widgets", widgetMap);
334 }
335
336 void
338 {
339 QDateTime time = QFileInfo(pluginPath).lastModified();
340 std::unique_lock lock(cacheMutex);
341 QMap<QString, QVariant> widgetMap = s.value("widgets").toMap();
342 ARMARX_DEBUG_S << "Updating last modified timestamp of plugin " << pluginPath << " to "
343 << time.toString();
344 auto& pluginDataElement = pluginData[pluginPath];
345 for (auto& widget : pluginDataElement.widgets)
346 {
347 widgetMap[pluginPath + ":" + widget.first] = QString::number(time.toMSecsSinceEpoch()) +
348 ":" + pluginDataElement.hash + ":" +
349 widget.first;
350 }
351
352 assert(QString::number(time.toMSecsSinceEpoch()).toLongLong() == time.toMSecsSinceEpoch());
353
354 pluginDataElement.lastModified = time;
355 s.setValue("widgets", widgetMap);
356 }
357
358 void
359 PluginCache::writeToCache(const QString& pluginPath)
360 {
361 auto start = IceUtil::Time::now();
362 QByteArray hashData = getHash(pluginPath);
363 ARMARX_DEBUG_S << "Hashing took " << (IceUtil::Time::now() - start).toMicroSecondsDouble();
364 QSharedPointer<QPluginLoader> loader(new QPluginLoader(pluginPath));
365 auto widgets = loadPlugin(loader);
367 ARMARX_VERBOSE_S << "Writing plugin " << pluginPath << " to cache";
368 std::unique_lock lock(cacheMutex);
369 QMap<QString, QVariant> widgetMap = s.value("widgets").toMap();
370 QDateTime time = QFileInfo(pluginPath).lastModified();
371 for (auto& elem : widgets)
372 {
373 widgetMap[pluginPath + ":" + elem.first] =
374 QString::number(time.toMSecsSinceEpoch()) + ":" + hashData + ":" + elem.first;
375 }
376
377 s.setValue("widgets", widgetMap);
378
379
380 pluginData[pluginPath] = {loader, pluginPath, hashData, time, widgets};
381 }
382
384 PluginCache::loadFromCache(const QString& widgetName)
385 {
386 ARMARX_INFO_S << "Loading widget " << widgetName << " from cache ";
387
388 QString pluginPath;
389 QSharedPointer<QPluginLoader> loader;
390 WidgetCreatorMap widgets;
391 QDateTime time;
392 QByteArray hash;
393 QMap<QString, QVariant> map;
394 {
395 std::unique_lock lock(cacheMutex);
396 map = s.value("widgets").toMap();
397 }
398 for (auto key : map.keys())
399 {
400 QStringList w = map[key].toString().split(":");
401 QStringList keyparts = key.split(":");
402 if (w.size() == 3)
403 {
404 if (w[2] == widgetName)
405 {
406 pluginPath = keyparts[0];
407 if (QFile::exists(pluginPath))
408 {
409 hash = w[1].toLatin1();
410 loader = QSharedPointer<QPluginLoader>(new QPluginLoader(pluginPath));
411 widgets = loadPlugin(loader);
412 time = QDateTime::fromMSecsSinceEpoch(w[0].toLongLong());
413 break;
414 }
415 else
416 {
417 ARMARX_INFO_S << pluginPath << " does not exist";
418 }
419 }
420 }
421 }
422 if (!loader)
423 {
424 ARMARX_WARNING_S << "Could not find " << widgetName << " in cache";
425 }
426
427 return PluginData{loader, pluginPath, hash, time, widgets};
428 }
429
431 PluginCache::loadPlugin(QSharedPointer<QPluginLoader> loader)
432 {
433 WidgetCreatorMap result;
434 ARMARX_VERBOSE_S << "Loading plugin " << loader->fileName();
435 QObject* plugin = loader->instance(); // calls load
436
437 if (plugin)
438 {
439 ArmarXGuiInterface* newPlugin = qobject_cast<ArmarXGuiInterface*>(plugin);
440 if (newPlugin)
441 {
442 //ARMARX_INFO << "cast successful" << armarx::flush;
443 WidgetCreatorMap newWidgets = newPlugin->getProvidedWidgets();
444 for (auto elem : newWidgets)
445 {
446 ArmarXWidgetInfoPtr info = elem.second;
447 QIcon icon = info->getIcon();
448 if (!icon.isNull())
449 {
450 icon.pixmap(256, 64).save(GetIconPath(elem.first));
451 }
452 icon = info->getCategoryIcon();
453 if (!icon.isNull())
454 {
455 icon.pixmap(256, 64).save(GetCategoryIconPath(elem.first));
456 }
457 }
458 result.insert(newWidgets.begin(), newWidgets.end());
459 if (manager)
460 {
461 manager->registerKnownObjectFactoriesWithIce();
462 }
463 else
464 {
465 ARMARX_WARNING << "ARMARX Manager is NULL";
466 }
467 }
468 else
469 {
470 ARMARX_WARNING << "plugin instance is NULL";
471 }
472 }
473 else
474 {
475 ARMARX_WARNING_S << "Instantiating plugin failed: " << loader->fileName()
476 << "\nError string:\n"
477 << loader->errorString().toStdString();
478 }
479 return result;
480 }
481
482} // namespace armarx
std::shared_ptr< ArmarXWidgetInfo > ArmarXWidgetInfoPtr
std::map< QString, ArmarXWidgetInfoPtr > WidgetCreatorMap
SpamFilterDataPtr deactivateSpam(SpamFilterDataPtr const &spamFilter, float deactivationDurationSec, const std::string &identifier, bool deactivate)
Definition Logging.cpp:75
std::string str(const T &t)
The main gui interface.
virtual WidgetCreatorMap getProvidedWidgets()=0
The ArmarXDataPath class provides static methods to handle ArmarX data directories.
static std::string GetCachePath()
The base Cache directory of ArmarX.
void writeToCache(const QString &pluginPath)
QMap< QString, PluginData > pluginData
Definition PluginCache.h:66
const QString settingsApplicationName
Definition PluginCache.h:47
const QString cachePath
Definition PluginCache.h:65
PluginData loadFromCache(const QString &pluginPath)
ArmarXManagerPtr manager
Definition PluginCache.h:67
WidgetCreatorMap getAvailableWidgets() const
std::future< void > preloadFuture
Definition PluginCache.h:70
void updateLastModifiedTimestamp(const QString &pluginPath)
static QString GetIconCachePath()
void preloadAsync(QStringList widgetNames, int delayMS=1000)
static bool ContainsAny(const QString &str, const QStringList &items)
PluginCache(ArmarXManagerPtr manager)
std::recursive_mutex cacheMutex
Definition PluginCache.h:69
QByteArray getHash(const QString &pluginPath)
bool cacheWidget(QString widgetName, ArmarXWidgetInfoPtr widgetCreator)
QStringList getAvailableWidgetNames() const
void removePluginFromCache(QString pluginPath)
void removeWidgetFromCache(QString pluginPath, QString widgetName)
bool cachePlugin(const QString &pluginPath)
static QString GetIconPath(const QString &widgetName)
ArmarXWidgetInfoPtr getWidgetCreator(const QString &widgetName)
static QString GetCategoryIconPath(const QString &widgetName)
WidgetCreatorMap loadPlugin(QSharedPointer< QPluginLoader > loader)
#define ARMARX_CHECK_EXPRESSION(expression)
This macro evaluates the expression and if it turns out to be false it will throw an ExpressionExcept...
#define ARMARX_DEBUG_S
The logging level for output that is only interesting while debugging.
Definition Logging.h:205
#define ARMARX_INFO
The normal logging level.
Definition Logging.h:181
#define ARMARX_INFO_S
Definition Logging.h:202
#define ARMARX_VERBOSE_S
Definition Logging.h:207
#define ARMARX_WARNING
The logging level for unexpected behaviour, but not a serious problem.
Definition Logging.h:193
#define ARMARX_VERBOSE
The logging level for verbose information.
Definition Logging.h:187
#define ARMARX_WARNING_S
The logging level for unexpected behaviour, but not a serious problem.
Definition Logging.h:213
This file offers overloads of toIce() and fromIce() functions for STL container types.
IceUtil::Handle< ArmarXManager > ArmarXManagerPtr