Files
oak-editor/third_party/openfx/HostSupport/src/ofxhPluginCache.cpp
T

695 lines
18 KiB
C++

#include <assert.h>
#include <map>
#include <string>
#include <iostream>
#include <fstream>
#include <sstream>
#include <string.h>
#include <stdlib.h>
#include "expat.h"
// ofx
#include "ofxCore.h"
#include "ofxImageEffect.h"
// ofx host
#include "ofxhBinary.h"
#include "ofxhPropertySuite.h"
#include "ofxhMemory.h"
#include "ofxhPluginAPICache.h"
#include "ofxhPluginCache.h"
#include "ofxhHost.h"
#include "ofxhXml.h"
#if defined (__linux__) || defined (__FreeBSD__)
#define DIRLIST_SEP_CHARS ":;"
#define DIRSEP "/"
#include <dirent.h>
static const char *getArchStr()
{
if(sizeof(void *) == 4) {
#if defined(__linux__)
return "Linux-x86";
#else
return "FreeBSD-x86";
#endif
}
else {
#if defined(__linux__)
return "Linux-x86-64";
#else
return "FreeBSD-x86-64";
#endif
}
}
#define ARCHSTR getArchStr()
#elif defined (__APPLE__)
#define DIRLIST_SEP_CHARS ";:"
#if defined(__x86_64) || defined(__x86_64__)
#define ARCHSTR "MacOS-x86-64"
#else
#define ARCHSTR "MacOS"
#endif
#define DIRSEP "/"
#include <dirent.h>
#elif defined (WINDOWS)
#define DIRLIST_SEP_CHARS ";"
#ifdef _WIN64
#define ARCHSTR "win64"
#else
#define ARCHSTR "win32"
#endif
#define DIRSEP "\\"
#include "shlobj.h"
#include "tchar.h"
#endif
OFX::Host::PluginCache* OFX::Host::PluginCache::gPluginCachePtr = 0;
// Define this to enable ofx plugin cache debug messages.
//#define CACHE_DEBUG
using namespace OFX::Host;
/// try to open the plugin bundle object and query it for plugins
void PluginBinary::loadPluginInfo(PluginCache *cache) {
if (isInvalid()) {
return;
}
_fileModificationTime = _binary.getTime();
_fileSize = _binary.getSize();
_binaryChanged = false;
// Take a reference to load the binary only once per session. It will
// eventually be unloaded in the destructor (see below).
// This avoid lots of useless calls to dlopen()/dlclose().
if (!_binary.isLoaded()) {
_binary.ref();
}
int (*getNo)(void) = (int(*)()) _binary.findSymbol("OfxGetNumberOfPlugins");
OfxPlugin* (*getPlug)(int) = (OfxPlugin*(*)(int)) _binary.findSymbol("OfxGetPlugin");
if (getNo == 0 || getPlug == 0) {
_binary.setInvalid(true);
} else {
int pluginCount = (*getNo)();
_plugins.reserve(pluginCount);
for (int i=0;i<pluginCount;i++) {
OfxPlugin *plug = (*getPlug)(i);
APICache::PluginAPICacheI *api = cache->findApiHandler(plug->pluginApi, plug->apiVersion);
assert(api);
_plugins.push_back(api->newPlugin(this, i, plug));
}
}
}
PluginBinary::~PluginBinary() {
std::vector<Plugin*>::iterator i = _plugins.begin();
while (i != _plugins.end()) {
delete *i;
i++;
}
// release the last reference to the binary, which should unload it
// if this reference was taken by loadPluginInfo().
if (_binary.isLoaded()) {
_binary.unref();
}
assert(!_binary.isLoaded());
}
PluginHandle::PluginHandle(Plugin *p, OFX::Host::Host *host)
{
_b = p->getBinary();
_b->_binary.ref();
_op = 0;
OfxPlugin* (*getPlug)(int) = (OfxPlugin*(*)(int)) _b->_binary.findSymbol("OfxGetPlugin");
if (getPlug) {
_op = getPlug(p->getIndex());
if (_op) {
_op->setHost(host->getHandle());
}
}
}
PluginHandle::~PluginHandle() {
_b->_binary.unref();
}
#if defined (WINDOWS)
const TCHAR *getStdOFXPluginPath(const std::string &hostId = "Plugins")
{
static TCHAR buffer[MAX_PATH];
static int gotIt = 0;
if(!gotIt) {
gotIt = 1;
SHGetFolderPath(NULL, CSIDL_PROGRAM_FILES_COMMON, NULL, SHGFP_TYPE_CURRENT, buffer);
strcat_s(buffer, MAX_PATH, __T("\\OFX\\Plugins"));
}
return buffer;
}
#endif
static
std::string OFXGetEnv(const char* e)
{
#if defined(WINDOWS) && !defined(__MINGW32__)
size_t requiredSize;
getenv_s(&requiredSize, 0, 0, e);
std::vector<char> buffer(requiredSize);
if(requiredSize >0)
{
getenv_s(&requiredSize, &buffer.front(), requiredSize, e);
return &buffer.front();
}
return "";
#else
if(getenv(e))
return getenv(e);
#endif
return "";
}
PluginCache* PluginCache::getPluginCache()
{
if(!gPluginCachePtr)
gPluginCachePtr = new PluginCache();
return gPluginCachePtr;
}
void PluginCache::clearPluginCache()
{
delete gPluginCachePtr;
gPluginCachePtr = 0;
}
PluginCache::~PluginCache()
{
for(std::list<PluginBinary *>::iterator it=_binaries.begin(); it != _binaries.end(); ++it) {
delete (*it);
}
_binaries.clear();
}
PluginCache::PluginCache() : _hostSpec(0), _xmlCurrentBinary(0), _xmlCurrentPlugin(0) {
_cacheVersion = "";
_ignoreCache = false;
_dirty = false;
_enablePluginSeek = true;
std::string s = OFXGetEnv("OFX_PLUGIN_PATH");
while (s.length()) {
int spos = int(s.find_first_of(DIRLIST_SEP_CHARS));
std::string path;
if (spos != -1) {
path = s.substr(0, spos);
s = s.substr(spos+1);
}
else {
path = s;
s = "";
}
_pluginPath.push_back(path);
}
#if defined(WINDOWS)
_pluginPath.push_back(getStdOFXPluginPath());
_pluginPath.push_back("C:\\Program Files\\Common Files\\OFX\\Plugins");
#endif
#if defined(__linux__) || defined(__FreeBSD__)
_pluginPath.push_back("/usr/OFX/Plugins");
#endif
#if defined(__APPLE__)
_pluginPath.push_back("/Library/OFX/Plugins");
#endif
}
void PluginCache::setPluginHostPath(const std::string &hostId) {
#if defined(WINDOWS)
_pluginPath.push_back(getStdOFXPluginPath(hostId));
_pluginPath.push_back("C:\\Program Files\\Common Files\\OFX\\" + hostId);
#endif
#if defined(__linux__) || defined(__FreeBSD__)
_pluginPath.push_back("/usr/OFX/" + hostId);
#endif
#if defined(__APPLE__)
_pluginPath.push_back("/Library/OFX/" + hostId);
#endif
}
void PluginCache::scanDirectory(std::set<std::string> &foundBinFiles, const std::string &dir, bool recurse)
{
#ifdef CACHE_DEBUG
printf("looking in %s for plugins\n", dir.c_str());
#endif
#if defined (WINDOWS)
WIN32_FIND_DATA findData;
HANDLE findHandle;
#else
DIR *d = opendir(dir.c_str());
if (!d) {
return;
}
#endif
_pluginDirs.push_back(dir.c_str());
#if defined (UNIX)
while (dirent *de = readdir(d))
#elif defined (WINDOWS)
findHandle = FindFirstFile((dir + "\\*").c_str(), &findData);
if (findHandle == INVALID_HANDLE_VALUE)
{
return;
}
while (1)
#endif
{
#if defined (UNIX)
std::string name = de->d_name;
bool isdir = true;
#else
std::string name = findData.cFileName;
bool isdir = (findData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) != 0;
#endif
if (name.find(".ofx.bundle") != std::string::npos) {
std::string barename = name.substr(0, name.length() - strlen(".bundle"));
std::string bundlename = dir + DIRSEP + name;
std::string binpath = dir + DIRSEP + name + DIRSEP "Contents" DIRSEP + ARCHSTR + DIRSEP + barename;
// don't insert binpath yet, do it later because of Mac OS X Universal stuff
//foundBinFiles.insert(binpath);
#if defined(__APPLE__) && (defined(__x86_64) || defined(__x86_64__))
/* From the OpenFX specification:
MacOS-x86-64 - for Apple Macintosh OS X, specifically on
intel x86 CPUs running AMD's 64 bit extensions. 64 bit host
applications should check this first, and if it doesn't
exist or is empty, fall back to "MacOS" looking for a
universal binary.
*/
std::string binpath_universal = dir + DIRSEP + name + DIRSEP "Contents" DIRSEP + "MacOS" + DIRSEP + barename;
if (_knownBinFiles.find(binpath_universal) != _knownBinFiles.end()) {
binpath = binpath_universal;
}
#endif
if (_knownBinFiles.find(binpath) == _knownBinFiles.end()) {
#ifdef CACHE_DEBUG
printf("found non-cached binary %s\n", binpath.c_str());
#endif
_dirty = true;
// the binary was not in the cache
PluginBinary *pb = 0;
#if defined(__x86_64) || defined(__x86_64__)
pb = new PluginBinary(binpath, bundlename, this);
# if defined(__APPLE__)
if (pb->isInvalid()) {
// fallback to "MacOS"
delete pb;
binpath = binpath_universal;
pb = new PluginBinary(binpath, bundlename, this);
}
# endif
#else
pb = new PluginBinary(binpath, bundlename, this);
#endif
_binaries.push_back(pb);
_knownBinFiles.insert(binpath);
foundBinFiles.insert(binpath);
for (int j=0;j<pb->getNPlugins();j++) {
Plugin *plug = &pb->getPlugin(j);
const APICache::PluginAPICacheI &api = plug->getApiHandler();
api.loadFromPlugin(plug);
}
} else {
#ifdef CACHE_DEBUG
printf("found cached binary %s\n", binpath.c_str());
#endif
}
// insert final path (universal or not) in the list of found files
foundBinFiles.insert(binpath);
} else {
if (isdir && (recurse && name[0] != '@' && name != "." && name != "..")) {
scanDirectory(foundBinFiles, dir + DIRSEP + name, recurse);
}
}
#if defined(WINDOWS)
int rval = FindNextFile(findHandle, &findData);
if (rval == 0) {
break;
}
#endif
}
#if defined(UNIX)
closedir(d);
#else
FindClose(findHandle);
#endif
}
std::string PluginCache::seekPluginFile(const std::string &baseName) const {
// Exit early if disabled
if (!_enablePluginSeek)
return "";
for (std::list<std::string>::const_iterator paths= _pluginDirs.begin();
paths != _pluginDirs.end();
paths++) {
std::string candidate = *paths + DIRSEP + baseName;
FILE *f = fopen(candidate.c_str(), "r");
if (f) {
fclose(f);
return candidate;
}
}
return "";
}
void PluginCache::scanPluginFiles()
{
std::set<std::string> foundBinFiles;
for (std::list<std::string>::iterator paths= _pluginPath.begin();
paths != _pluginPath.end();
paths++) {
scanDirectory(foundBinFiles, *paths, _nonrecursePath.find(*paths) == _nonrecursePath.end());
}
std::list<PluginBinary *>::iterator i=_binaries.begin();
while (i!=_binaries.end()) {
PluginBinary *pb = *i;
if (foundBinFiles.find(pb->getFilePath()) == foundBinFiles.end()) {
// the binary was in the cache, but was not on the path
_dirty = true;
i = _binaries.erase(i);
delete pb;
} else {
bool binChanged = pb->hasBinaryChanged();
// the binary was in the cache, but the binary has changed and thus we need to reload
if (binChanged) {
pb->loadPluginInfo(this);
_dirty = true;
}
for (int j=0;j<pb->getNPlugins();j++) {
Plugin *plug = &pb->getPlugin(j);
APICache::PluginAPICacheI &api = plug->getApiHandler();
if (binChanged) {
api.loadFromPlugin(plug);
}
std::string reason;
if (api.pluginSupported(plug, reason)) {
_plugins.push_back(plug);
api.confirmPlugin(plug);
} else {
std::cerr << "ignoring plugin " << plug->getIdentifier() <<
" as unsupported (" << reason << ")" << std::endl;
}
}
i++;
}
}
}
/// callback for XML parser
static void elementBeginHandler(void *userData, const XML_Char *name, const XML_Char **atts) {
PluginCache::getPluginCache()->elementBeginCallback(userData, name, atts);
}
/// callback for XML parser
static void elementCharHandler(void *userData, const XML_Char *data, int len) {
PluginCache::getPluginCache()->elementCharCallback(userData, data, len);
}
/// callback for XML parser
static void elementEndHandler(void *userData, const XML_Char *name) {
PluginCache::getPluginCache()->elementEndCallback(userData, name);
}
static bool mapHasAll(const std::map<std::string, std::string> &attmap, const char **atts) {
while (*atts) {
if (attmap.find(*atts) == attmap.end()) {
return false;
}
atts++;
}
return true;
}
void PluginCache::elementBeginCallback(void */*userData*/, const XML_Char *name, const XML_Char **atts) {
if (_ignoreCache) {
return;
}
std::string ename = name;
std::map<std::string, std::string> attmap;
while (*atts) {
attmap[atts[0]] = atts[1];
atts += 2;
}
/// XXX: validate in general
if (ename == "cache") {
std::string cacheversion = attmap["version"];
if (cacheversion != _cacheVersion) {
#ifdef CACHE_DEBUG
printf("mismatched version, ignoring cache (got '%s', wanted '%s')\n",
cacheversion.c_str(),
_cacheVersion.c_str());
#endif
_ignoreCache = true;
}
}
if (ename == "binary") {
const char *binAtts[] = {"path", "bundle_path", "mtime", "size", NULL};
if (!mapHasAll(attmap, binAtts)) {
// no path: bad XML
}
std::string fname = attmap["path"];
std::string bname = attmap["bundle_path"];
time_t mtime = OFX::Host::Property::stringToInt(attmap["mtime"]);
size_t size = OFX::Host::Property::stringToInt(attmap["size"]);
_xmlCurrentBinary = new PluginBinary(fname, bname, mtime, size);
_binaries.push_back(_xmlCurrentBinary);
_knownBinFiles.insert(fname);
return;
}
if (ename == "plugin" && _xmlCurrentBinary && !_xmlCurrentBinary->hasBinaryChanged()) {
const char *plugAtts[] = {"api", "name", "index", "api_version", "major_version", "minor_version", NULL};
if (!mapHasAll(attmap, plugAtts)) {
// no path: bad XML
}
std::string api = attmap["api"];
std::string rawIdentifier = attmap["name"];
std::string identifier = rawIdentifier;
// Who says the pluginIdentifier is case-insensitive? OFX 1.3 spec doesn't mention this.
// http://openfx.sourceforge.net/Documentation/1.3/ofxProgrammingReference.html#id472588
//for (size_t i=0;i<identifier.size();i++) {
// identifier[i] = tolower(identifier[i]);
//}
int idx = OFX::Host::Property::stringToInt(attmap["index"]);
int api_version = OFX::Host::Property::stringToInt(attmap["api_version"]);
int major_version = OFX::Host::Property::stringToInt(attmap["major_version"]);
int minor_version = OFX::Host::Property::stringToInt(attmap["minor_version"]);
APICache::PluginAPICacheI *apiCache = findApiHandler(api, api_version);
if (apiCache) {
Plugin *pe = apiCache->newPlugin(_xmlCurrentBinary, idx, api, api_version, identifier, rawIdentifier, major_version, minor_version);
_xmlCurrentBinary->addPlugin(pe);
_xmlCurrentPlugin = pe;
apiCache->beginXmlParsing(pe);
}
return;
}
if (_xmlCurrentPlugin) {
APICache::PluginAPICacheI &api = _xmlCurrentPlugin->getApiHandler();
api.xmlElementBegin(name, attmap);
}
}
void PluginCache::elementCharCallback(void */*userData*/, const XML_Char *data, int size)
{
if (_ignoreCache) {
return;
}
std::string s(data, size);
if (_xmlCurrentPlugin) {
APICache::PluginAPICacheI &api = _xmlCurrentPlugin->getApiHandler();
api.xmlCharacterHandler(s);
} else {
/// XXX: we only want whitespace
}
}
void PluginCache::elementEndCallback(void */*userData*/, const XML_Char *name) {
if (_ignoreCache) {
return;
}
std::string ename = name;
/// XXX: validation?
if (ename == "plugin") {
if (_xmlCurrentPlugin) {
APICache::PluginAPICacheI &api = _xmlCurrentPlugin->getApiHandler();
api.endXmlParsing();
}
_xmlCurrentPlugin = 0;
return;
}
if (ename == "bundle") {
_xmlCurrentBinary = 0;
return;
}
if (_xmlCurrentPlugin) {
APICache::PluginAPICacheI &api = _xmlCurrentPlugin->getApiHandler();
api.xmlElementEnd(name);
}
}
void PluginCache::readCache(std::istream &ifs) {
XML_Parser xP = XML_ParserCreate(NULL);
XML_SetElementHandler(xP, elementBeginHandler, elementEndHandler);
XML_SetCharacterDataHandler(xP, elementCharHandler);
while (ifs.good()) {
char buf[1001] = {0};
ifs.read(buf, 1000);
if (buf[0] == 0) {
XML_Parse(xP, "", 0, XML_TRUE);
break;
}
int p = XML_Parse(xP, buf, int(strlen(buf)), XML_FALSE);
if (p == XML_STATUS_ERROR) {
std::cout << "xml error : " << XML_GetErrorCode(xP) << std::endl;
/// XXX: do something here
break;
}
}
XML_ParserFree(xP);
}
void PluginCache::writePluginCache(std::ostream &os) const {
#ifdef CACHE_DEBUG
printf("writing pluginCache with version = %s\n", _cacheVersion.c_str());
#endif
os << "<cache version=\"" << _cacheVersion << "\">\n";
for (std::list<PluginBinary *>::const_iterator i=_binaries.begin();i!=_binaries.end();i++) {
PluginBinary *b = *i;
os << "<bundle>\n";
os << " <binary "
<< XML::attribute("bundle_path", b->getBundlePath())
<< XML::attribute("path", b->getFilePath())
<< XML::attribute("mtime", int(b->getFileModificationTime()))
<< XML::attribute("size", int(b->getFileSize())) << "/>\n";
for (int j=0;j<b->getNPlugins();j++) {
Plugin *p = &b->getPlugin(j);
os << " <plugin "
<< XML::attribute("name", p->getRawIdentifier())
<< XML::attribute("index", p->getIndex())
<< XML::attribute("api", p->getPluginApi())
<< XML::attribute("api_version", p->getApiVersion())
<< XML::attribute("major_version", p->getVersionMajor())
<< XML::attribute("minor_version", p->getVersionMinor())
<< ">\n";
const APICache::PluginAPICacheI &api = p->getApiHandler();
os << " <apiproperties>\n";
api.saveXML(p, os);
os << " </apiproperties>\n";
os << " </plugin>\n";
}
os << "</bundle>\n";
}
os << "</cache>\n";
}
APICache::PluginAPICacheI *PluginCache::findApiHandler(const std::string &api, int version) {
std::list<PluginCacheSupportedApi>::iterator i = _apiHandlers.begin();
while (i != _apiHandlers.end()) {
if (i->matches(api, version)) {
return i->handler;
}
i++;
}
return 0;
}