#include #include #include #include #include #include #include #include #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 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 #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;ifindApiHandler(plug->pluginApi, plug->apiVersion); assert(api); _plugins.push_back(api->newPlugin(this, i, plug)); } } } PluginBinary::~PluginBinary() { std::vector::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 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::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 &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;jgetNPlugins();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::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 foundBinFiles; for (std::list::iterator paths= _pluginPath.begin(); paths != _pluginPath.end(); paths++) { scanDirectory(foundBinFiles, *paths, _nonrecursePath.find(*paths) == _nonrecursePath.end()); } std::list::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;jgetNPlugins();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 &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 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;inewPlugin(_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 << "\n"; for (std::list::const_iterator i=_binaries.begin();i!=_binaries.end();i++) { PluginBinary *b = *i; os << "\n"; os << " getBundlePath()) << XML::attribute("path", b->getFilePath()) << XML::attribute("mtime", int(b->getFileModificationTime())) << XML::attribute("size", int(b->getFileSize())) << "/>\n"; for (int j=0;jgetNPlugins();j++) { Plugin *p = &b->getPlugin(j); os << " 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 << " \n"; api.saveXML(p, os); os << " \n"; os << " \n"; } os << "\n"; } os << "\n"; } APICache::PluginAPICacheI *PluginCache::findApiHandler(const std::string &api, int version) { std::list::iterator i = _apiHandlers.begin(); while (i != _apiHandlers.end()) { if (i->matches(api, version)) { return i->handler; } i++; } return 0; }