#include <assert.h>
#include <map>
#include <string>
#include <iostream>
#include <fstream>
#include <sstream>
#include <string.h>
#include <stdlib.h>
#include "expat.h"
#include "ofxCore.h"
#include "ofxImageEffect.h"
#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;
using namespace OFX::Host;
void PluginBinary::loadPluginInfo(PluginCache *cache) {
if (isInvalid()) {
return;
}
_fileModificationTime = _binary.getTime();
_fileSize = _binary.getSize();
_binaryChanged = false;
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++;
}
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;
#if defined(__APPLE__) && (defined(__x86_64) || defined(__x86_64__))
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;
PluginBinary *pb = 0;
#if defined(__x86_64) || defined(__x86_64__)
pb = new PluginBinary(binpath, bundlename, this);
# if defined(__APPLE__)
if (pb->isInvalid()) {
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
}
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 {
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()) {
_dirty = true;
i = _binaries.erase(i);
delete pb;
} else {
bool binChanged = pb->hasBinaryChanged();
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++;
}
}
}
static void elementBeginHandler(void *userData, const XML_Char *name, const XML_Char **atts) {
PluginCache::getPluginCache()->elementBeginCallback(userData, name, atts);
}
static void elementCharHandler(void *userData, const XML_Char *data, int len) {
PluginCache::getPluginCache()->elementCharCallback(userData, data, len);
}
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 *, 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;
}
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)) {
}
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)) {
}
std::string api = attmap["api"];
std::string rawIdentifier = attmap["name"];
std::string identifier = rawIdentifier;
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 *, 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 {
}
}
void PluginCache::elementEndCallback(void *, const XML_Char *name) {
if (_ignoreCache) {
return;
}
std::string ename = name;
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;
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;
}