TuttleOFX  1
TuttleOFX/libraries/tuttle/src/tuttle/host/ofx/OfxhPluginCache.cpp
Go to the documentation of this file.
00001 /*
00002  * Software License :
00003  *
00004  * Copyright (c) 2007-2009, The Open Effects Association Ltd. All rights reserved.
00005  *
00006  * Redistribution and use in source and binary forms, with or without
00007  * modification, are permitted provided that the following conditions are met:
00008  *
00009  * Redistributions of source code must retain the above copyright notice,
00010  * this list of conditions and the following disclaimer.
00011  * Redistributions in binary form must reproduce the above copyright notice,
00012  * this list of conditions and the following disclaimer in the documentation
00013  * and/or other materials provided with the distribution.
00014  * Neither the name The Open Effects Association Ltd, nor the names of its
00015  * contributors may be used to endorse or promote products derived from this
00016  * software without specific prior written permission.
00017  *
00018  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
00019  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
00020  * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
00021  * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
00022  * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
00023  * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
00024  * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
00025  * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
00026  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
00027  * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
00028  */
00029 
00030 // ofx host
00031 #include "property/OfxhSet.hpp"
00032 #include "OfxhBinary.hpp"
00033 #include "OfxhMemory.hpp"
00034 #include "OfxhPluginAPICache.hpp"
00035 #include "OfxhPluginCache.hpp"
00036 #include "OfxhHost.hpp"
00037 #include "OfxhUtilities.hpp"
00038 
00039 // ofx
00040 #include <ofxCore.h>
00041 #include <ofxImageEffect.h>
00042 
00043 #include <map>
00044 #include <string>
00045 #include <iostream>
00046 #include <fstream>
00047 #include <sstream>
00048 #include <cstring>
00049 #include <cstdlib>
00050 
00051 
00052 #if defined ( __linux__ )
00053 
00054  #define DIRLIST_SEP_CHARS ":;"
00055  #define DIRSEP "/"
00056 #include <dirent.h>
00057 
00058  #define ARCHSTR getArchStr()
00059 
00060 #elif defined ( __APPLE__ )
00061 
00062  #define DIRLIST_SEP_CHARS ";:"
00063  #define ARCHSTR "MacOS"
00064  #define DIRSEP "/"
00065 #include <dirent.h>
00066 
00067 #elif defined ( WINDOWS )
00068  #define DIRLIST_SEP_CHARS ";"
00069 #ifdef _WIN64
00070   #define ARCHSTR "win64"
00071 #else
00072   #define ARCHSTR "win32"
00073 #endif
00074  #define DIRSEP "\\"
00075 
00076 // CINTERFACE needs to be declared if compiling with VC++
00077 #include <shlobj.h>
00078 #include <tchar.h>
00079 #ifndef _MSC_VER
00080   #define SHGFP_TYPE_CURRENT 0
00081 #endif
00082 
00083 #endif
00084 
00085 namespace tuttle {
00086 namespace host {
00087 namespace ofx {
00088 
00089 struct PluginCacheSupportedApi
00090 {
00091         APICache::OfxhPluginAPICacheI* _handler;
00092 
00093         PluginCacheSupportedApi( APICache::OfxhPluginAPICacheI* handler )
00094                 : _handler( handler ) {}
00095 
00096         bool matches( std::string api, int version ) const
00097         {
00098                 if( api == _handler->_apiName && version >= _handler->_apiVersionMin && version <= _handler->_apiVersionMax )
00099                 {
00100                         return true;
00101                 }
00102                 return false;
00103         }
00104 };
00105 
00106 
00107 #if defined ( __linux__ )
00108 
00109 static const char* getArchStr()
00110 {
00111         if( sizeof( void* ) == 4 )
00112         {
00113                 return "Linux-x86";
00114         }
00115         else
00116         {
00117                 return "Linux-x86-64";
00118         }
00119 }
00120 
00121 #endif
00122 #if defined ( WINDOWS )
00123 
00124 const TCHAR* getStdOFXPluginPath( const std::string& hostId = "Plugins" )
00125 {
00126         static TCHAR buffer[MAX_PATH];
00127         static int gotIt = 0;
00128 
00129         if( !gotIt )
00130         {
00131                 gotIt = 1;
00132                 SHGetFolderPath( NULL, CSIDL_PROGRAM_FILES_COMMON, NULL, SHGFP_TYPE_CURRENT, buffer );
00133                 strncat( buffer, "\\OFX\\Plugins", MAX_PATH );
00134         }
00135         return buffer;
00136 }
00137 
00138 #endif
00139 
00140 std::string OFXGetEnv( const char* e )
00141 {
00142         #if !defined( __GNUC__ ) && defined( WINDOWS )
00143         std::size_t requiredSize;
00144         getenv_s( &requiredSize, 0, 0, e );
00145         std::vector<char> buffer( requiredSize );
00146         if( requiredSize > 0 )
00147         {
00148                 getenv_s( &requiredSize, &buffer.front(), requiredSize, e );
00149                 return &buffer.front();
00150         }
00151         return "";
00152         #else
00153         const char* env_value = getenv( e );
00154         if( env_value == NULL )
00155                 return "";
00156         return env_value;
00157         #endif
00158 }
00159 
00160 OfxhPluginCache::OfxhPluginCache()
00161         : _ignoreCache( false )
00162         , _cacheVersion( "" )
00163         , _dirty( false )
00164         , _enablePluginSeek( true )
00165 {
00166         std::string s = OFXGetEnv( "OFX_PLUGIN_PATH" );
00167 
00168         while( s.length() )
00169         {
00170                 int spos = int(s.find_first_of( DIRLIST_SEP_CHARS ) );
00171 
00172                 std::string path;
00173 
00174                 if( spos != -1 )
00175                 {
00176                         path = s.substr( 0, spos );
00177                         s    = s.substr( spos + 1 );
00178                 }
00179                 else
00180                 {
00181                         path = s;
00182                         s    = "";
00183                 }
00184 
00185                 _pluginPath.push_back( path );
00186         }
00187         
00188         #if defined( WINDOWS )
00189         _pluginPath.push_back( getStdOFXPluginPath() );
00190         _pluginPath.push_back( "C:\\Program Files\\Common Files\\OFX\\Plugins" );
00191         #endif
00192         #if defined( __linux__ )
00193         _pluginPath.push_back( "/usr/OFX/Plugins" );
00194         #endif
00195         #if defined( __APPLE__ )
00196         _pluginPath.push_back( "/Library/OFX/Plugins" );
00197         #endif
00198 }
00199 
00200 OfxhPluginCache::~OfxhPluginCache()
00201 {}
00202 
00203 void OfxhPluginCache::setPluginHostPath( const std::string& hostId )
00204 {
00205         #if defined( WINDOWS )
00206         _pluginPath.push_back( getStdOFXPluginPath( hostId ) );
00207         _pluginPath.push_back( "C:\\Program Files\\Common Files\\OFX\\" + hostId );
00208         #endif
00209         #if defined( __linux__ )
00210         _pluginPath.push_back( "/usr/OFX/" + hostId );
00211         #endif
00212         #if defined( __APPLE__ )
00213         _pluginPath.push_back( "/Library/OFX/" + hostId );
00214         #endif
00215 }
00216 
00217 void OfxhPluginCache::scanDirectory( std::set<std::string>& foundBinFiles, const std::string& dir, bool recurse )
00218 {
00219         TUTTLE_LOG_TRACE( "Search plugins" << (recurse?" recursively":"") << " in " << quotes(dir) << "." );
00220 
00221         #if defined ( WINDOWS )
00222         WIN32_FIND_DATA findData;
00223         HANDLE findHandle;
00224         #else
00225         DIR* d = opendir( dir.c_str() );
00226         if( !d )
00227         {
00228                 return;
00229         }
00230         #endif
00231 
00232         _pluginDirs.push_back( dir );
00233 
00234         #if defined ( UNIX )
00235         while( dirent * de = readdir( d ) )
00236         #elif defined ( WINDOWS )
00237         findHandle = FindFirstFile( ( dir + "\\*" ).c_str(), &findData );
00238 
00239         if( findHandle == INVALID_HANDLE_VALUE )
00240         {
00241                 return;
00242         }
00243 
00244         while( 1 )
00245         #endif
00246         {
00247                 #if defined ( UNIX )
00248                 std::string name = de->d_name;
00249                 bool isdir = true;
00250                 #else
00251                 std::string name = findData.cFileName;
00252                 bool isdir = ( findData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY ) != 0;
00253                 #endif
00254                 if( name.find( ".ofx.bundle" ) != std::string::npos )
00255                 {
00256                         const std::string barename = name.substr( 0, name.length() - strlen( ".bundle" ) );
00257                         const std::string bundlepath = dir + DIRSEP + name;
00258                         const std::string binpath = bundlepath + DIRSEP "Contents" DIRSEP + ARCHSTR + DIRSEP + barename;
00259 
00260                         foundBinFiles.insert( binpath );
00261 
00262                         if( _knownBinFiles.find( binpath ) == _knownBinFiles.end() )
00263                         {
00264                                 TUTTLE_LOG_TRACE( "Binary does not exist in the cache: " << quotes(binpath) );
00265                                 try
00266                                 {
00267                                         // Creating the binary may throw, if there are some missing
00268                                         // dependencies (like wrong LD_LIBRARY_PATH).
00269                                         // If it throws, it will not be declared in the plugin cache.
00270                                         OfxhPluginBinary* pb = new OfxhPluginBinary( binpath, bundlepath, this );
00271                                         _binaries.push_back( pb );
00272                                         
00273                                         // The binary file has been succesfully loaded,
00274                                         // so we need to add it into the cache.
00275                                         setDirty();  // the cache has to be rewrite
00276                                         _knownBinFiles.insert( binpath );
00277                                         
00278                                         TUTTLE_LOG_TRACE( quotes(barename) << " contains " << pb->getNPlugins() <<  " plugins." );
00279                                         
00280                                         // Now, if there is an error that's because the plugin
00281                                         // is not supported by the host.
00282                                         for( int j = 0; j < pb->getNPlugins(); ++j )
00283                                         {
00284                                                 OfxhPlugin& plug = pb->getPlugin( j );
00285                                                 APICache::OfxhPluginAPICacheI& api = plug.getApiHandler();
00286                                                 try
00287                                                 {
00288                                                         api.loadFromPlugin( plug );
00289                                                 }       
00290                                                 catch(... )
00291                                                 {
00292                                                         TUTTLE_LOG_INFO( "Can't load plugin "
00293                                                                 << quotes(plug.getIdentifier()) << " " << plug.getVersionMajor() << "." << plug.getVersionMinor()
00294                                                                 << " from file " << quotes(binpath) );
00295                                                         TUTTLE_LOG_TRACE( boost::current_exception_diagnostic_information() );
00296                                                 }
00297                                         }
00298                                 }
00299                                 catch(... )
00300                                 {
00301                                         TUTTLE_LOG_INFO( "Can't load plugin file " << quotes(binpath) );
00302                                         TUTTLE_LOG_TRACE( boost::current_exception_diagnostic_information() );
00303 #ifdef __WINDOWS__
00304                                         TUTTLE_LOG_TRACE( "PATH: " << std::getenv("PATH") << std::endl );
00305 #else
00306                                         TUTTLE_LOG_TRACE( "LD_LIBRARY_PATH: " << std::getenv("LD_LIBRARY_PATH") << std::endl );
00307 #endif
00308                                 }
00309                         }
00310                         else
00311                         {
00312                                 TUTTLE_LOG_TRACE( "Found cached binary " << quotes(binpath) );
00313                         }
00314                 }
00315                 else
00316                 {
00317                         if( isdir && ( recurse && name[0] != '@' && name != "." && name != ".." ) )
00318                         {
00319                                 scanDirectory( foundBinFiles, dir + DIRSEP + name, recurse );
00320                         }
00321                 }
00322                 #if defined( WINDOWS )
00323                 int rval = FindNextFile( findHandle, &findData );
00324 
00325                 if( rval == 0 )
00326                 {
00327                         break;
00328                 }
00329                 #endif
00330         }
00331 
00332         #if defined( UNIX )
00333         closedir( d );
00334         #else
00335         FindClose( findHandle );
00336         #endif
00337 }
00338 
00339 void OfxhPluginCache::addPlugin( OfxhPlugin* plugin )
00340 {
00341         // Check if the same plugin has already been loaded
00342         if( _loadedMap.find( plugin->getIdentity() ) == _loadedMap.end() )
00343         {
00344                 _loadedMap[plugin->getIdentity()] = true;
00345         }
00346         else
00347         {
00348                 TUTTLE_LOG_INFO( "Plugin: " << plugin->getRawIdentifier() << " loaded twice! (" << plugin->getBinary().getFilePath() << ")" );
00349         }
00350         _plugins.push_back( plugin );
00351 
00352         if( _pluginsByID.find( plugin->getIdentifier() ) != _pluginsByID.end() )
00353         {
00354                 OfxhPlugin& otherPlugin = *_pluginsByID[plugin->getIdentifier()];
00355                 if( plugin->trumps( otherPlugin ) )
00356                 {
00357                         _pluginsByID[plugin->getIdentifier()] = plugin;
00358                 }
00359         }
00360         else
00361         {
00362                 _pluginsByID[plugin->getIdentifier()] = plugin;
00363         }
00364 }
00365 
00366 std::string OfxhPluginCache::seekPluginFile( const std::string& baseName ) const
00367 {
00368         // Exit early if disabled
00369         if( !_enablePluginSeek )
00370                 return "";
00371 
00372         for( std::list<std::string>::const_iterator paths = _pluginDirs.begin();
00373              paths != _pluginDirs.end();
00374              ++paths )
00375         {
00376                 std::string candidate = *paths + DIRSEP + baseName;
00377                 FILE* f               = fopen( candidate.c_str(), "r" );
00378                 if( f )
00379                 {
00380                         fclose( f );
00381                         return candidate;
00382                 }
00383         }
00384         return "";
00385 }
00386 
00387 void OfxhPluginCache::scanPluginFiles()
00388 {
00389         std::set<std::string> foundBinFiles;
00390 
00391         for( std::list<std::string>::iterator paths = _pluginPath.begin();
00392              paths != _pluginPath.end();
00393              ++paths )
00394         {
00395                 scanDirectory( foundBinFiles, *paths, _nonrecursePath.find( *paths ) == _nonrecursePath.end() );
00396         }
00397 
00398         OfxhPluginBinaryList::iterator i = _binaries.begin();
00399         while( i != _binaries.end() )
00400         {
00401                 if( foundBinFiles.find( i->getFilePath() ) == foundBinFiles.end() )
00402                 {
00403                         // the binary was in the cache, but was not on the path
00404                         setDirty();
00405                         i = _binaries.erase( i );
00406                 }
00407                 else
00408                 {
00409                         const bool binChanged = i->hasBinaryChanged();
00410 
00411                         try
00412                         {
00413                                 // the binary was in the cache, but the binary has changed and thus we need to reload
00414                                 if( binChanged )
00415                                 {
00416                                         i->loadPluginInfo( this );
00417                                         setDirty();
00418                                 }
00419 
00420                                 for( int j = 0; j < i->getNPlugins(); ++j )
00421                                 {
00422                                         OfxhPlugin& plug                   = i->getPlugin( j );
00423                                         try
00424                                         {
00425                                                 APICache::OfxhPluginAPICacheI& api = plug.getApiHandler();
00426 
00427                                                 if( binChanged )
00428                                                 {
00429                                                         api.loadFromPlugin( plug ); // may throw
00430                                                 }
00431 
00432                                                 std::string reason;
00433 
00434                                                 if( api.pluginSupported( plug, reason ) )
00435                                                 {
00436                                                         addPlugin( &plug );
00437                                                         api.confirmPlugin( plug );
00438                                                 }
00439                                                 else
00440                                                 {
00441                                                         TUTTLE_LOG_INFO(
00442                                                                 "Ignoring plugin " << quotes(plug.getIdentifier()) <<
00443                                                                 ": unsupported, " << reason << "." );
00444                                                 }
00445                                         }
00446                                         catch(...)
00447                                         {
00448                                                 TUTTLE_LOG_INFO(
00449                                                         "Ignoring plugin " << quotes(plug.getIdentifier()) <<
00450                                                         ": loading error." );
00451                                                 TUTTLE_LOG_TRACE(boost::current_exception_diagnostic_information());
00452                                         }
00453                                 }
00454                         }
00455                         catch(...)
00456                         {
00457                                 TUTTLE_LOG_INFO(
00458                                         "Ignoring ofx bundle " << quotes(i->getBundlePath()) <<
00459                                         ": loading error." );
00460                                 TUTTLE_LOG_TRACE(boost::current_exception_diagnostic_information());
00461                         }
00462 
00463                         ++i;
00464                 }
00465         }
00466 }
00467 
00468 void OfxhPluginCache::clearPluginFiles()
00469 {
00470         setDirty();
00471         
00472         _binaries.clear();
00473         _plugins.clear();
00474         _pluginsByID.clear();
00475         _loadedMap.clear();
00476         _knownBinFiles.clear();
00477 }
00478 
00479 void OfxhPluginCache::registerAPICache( APICache::OfxhPluginAPICacheI& apiCache )
00480 {
00481         _apiHandlers.push_back( PluginCacheSupportedApi( &apiCache ) );
00482 }
00483 
00484 APICache::OfxhPluginAPICacheI* OfxhPluginCache::findApiHandler( const std::string& api, int version )
00485 {
00486         std::list<PluginCacheSupportedApi>::iterator i = _apiHandlers.begin();
00487         while( i != _apiHandlers.end() )
00488         {
00489                 if( i->matches( api, version ) )
00490                 {
00491                         return i->_handler;
00492                 }
00493                 ++i;
00494         }
00495         return 0;
00496 }
00497 
00498 /**
00499  * get the plugin by id.  vermaj and vermin can be specified.  if they are not it will
00500  * pick the highest found version.
00501  */
00502 OfxhPlugin* OfxhPluginCache::getPluginById( const std::string& id, int vermaj, int vermin )
00503 {
00504         if( vermaj == -1 &&  vermin == -1 )
00505                 return _pluginsByID[id];
00506 
00507         // return the highest version one, which fits the pattern provided
00508         OfxhPlugin* sofar = 0;
00509 
00510         for( std::list<OfxhPlugin*>::iterator i = _plugins.begin(); i != _plugins.end(); ++i )
00511         {
00512                 OfxhPlugin* p = *i;
00513 
00514                 if( p->getIdentifier() != id )
00515                 {
00516                         continue;
00517                 }
00518 
00519                 if( vermaj != -1 && p->getVersionMajor() != vermaj )
00520                 {
00521                         continue;
00522                 }
00523 
00524                 if( vermin != -1 && p->getVersionMinor() != vermin )
00525                 {
00526                         continue;
00527                 }
00528 
00529                 if( !sofar || p->trumps( *sofar ) )
00530                 {
00531                         sofar = p;
00532                 }
00533         }
00534         return sofar;
00535 }
00536 
00537 std::ostream& operator<<( std::ostream& os, const OfxhPluginCache& v )
00538 {
00539         os << "OfxhPluginCache {" << std::endl;
00540 
00541         if( v._pluginsByID.empty() )
00542                 os << "No Plug-ins Found." << std::endl;
00543 
00544         os << "________________________________________________________________________________" << std::endl;
00545         for( std::map<std::string, OfxhPlugin*>::const_iterator it = v._pluginsByID.begin(); it != v._pluginsByID.end(); ++it )
00546         {
00547                 os << "Plug-in:" << it->first << std::endl;
00548                 os << "  " << "Filepath: " << it->second->getBinary().getFilePath();
00549                 os << "(" << it->second->getIndex() << ")" << std::endl;
00550 
00551                 //              os << "Contexts:" << std::endl;
00552                 //              const std::set<std::string>& contexts = it->second->getContexts();
00553                 //              for( std::set<std::string>::const_iterator it2 = contexts.begin(); it2 != contexts.end(); ++it2 )
00554                 //                      os << "  * " << *it2 << std::endl;
00555                 //              const OfxhImageEffectNodeDescriptor& d = it->second->getDescriptor();
00556                 //              os << "Inputs:" << std::endl;
00557                 //              const std::map<std::string, attribute::OfxhClipImageDescriptor*>& inputs = d.getClips();
00558                 //              for( std::map<std::string, attribute::OfxhClipImageDescriptor*>::const_iterator it2 = inputs.begin(); it2 != inputs.end(); ++it2 )
00559                 //                      os << "    * " << it2->first << std::endl;
00560                 os << "________________________________________________________________________________" << std::endl;
00561         }
00562         os << "}" << std::endl;
00563         return os;
00564 }
00565 
00566 }
00567 }
00568 }
00569 
00570