TuttleOFX  1
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 // Define this to enable ofx plugin cache debug messages.
00052 //#define CACHE_DEBUG
00053 
00054 #if defined ( __linux__ )
00055 
00056  #define DIRLIST_SEP_CHARS ":;"
00057  #define DIRSEP "/"
00058 #include <dirent.h>
00059 
00060  #define ARCHSTR getArchStr()
00061 
00062 #elif defined ( __APPLE__ )
00063 
00064  #define DIRLIST_SEP_CHARS ";:"
00065  #define ARCHSTR "MacOS"
00066  #define DIRSEP "/"
00067 #include <dirent.h>
00068 
00069 #elif defined ( WINDOWS )
00070  #define DIRLIST_SEP_CHARS ";"
00071 #ifdef _WIN64
00072   #define ARCHSTR "win64"
00073 #else
00074   #define ARCHSTR "win32"
00075 #endif
00076  #define DIRSEP "\\"
00077 
00078 // CINTERFACE needs to be declared if compiling with VC++
00079 #include <shlobj.h>
00080 #include <tchar.h>
00081 #ifndef _MSC_VER
00082   #define SHGFP_TYPE_CURRENT 0
00083 #endif
00084 
00085 #endif
00086 
00087 namespace tuttle {
00088 namespace host {
00089 namespace ofx {
00090 
00091 #if defined ( __linux__ )
00092 
00093 static const char* getArchStr()
00094 {
00095         if( sizeof( void* ) == 4 )
00096         {
00097                 return "Linux-x86";
00098         }
00099         else
00100         {
00101                 return "Linux-x86-64";
00102         }
00103 }
00104 
00105 #endif
00106 #if defined ( WINDOWS )
00107 
00108 const TCHAR* getStdOFXPluginPath( const std::string& hostId = "Plugins" )
00109 {
00110         static TCHAR buffer[MAX_PATH];
00111         static int gotIt = 0;
00112 
00113         if( !gotIt )
00114         {
00115                 gotIt = 1;
00116                 SHGetFolderPath( NULL, CSIDL_PROGRAM_FILES_COMMON, NULL, SHGFP_TYPE_CURRENT, buffer );
00117                 strncat( buffer, "\\OFX\\Plugins", MAX_PATH );
00118         }
00119         return buffer;
00120 }
00121 
00122 #endif
00123 
00124 std::string OFXGetEnv( const char* e )
00125 {
00126         #if !defined( __GNUC__ ) && defined( WINDOWS )
00127         std::size_t requiredSize;
00128         getenv_s( &requiredSize, 0, 0, e );
00129         std::vector<char> buffer( requiredSize );
00130         if( requiredSize > 0 )
00131         {
00132                 getenv_s( &requiredSize, &buffer.front(), requiredSize, e );
00133                 return &buffer.front();
00134         }
00135         return "";
00136         #else
00137         const char* env_value = getenv( e );
00138         if( env_value == NULL )
00139                 return "";
00140         return env_value;
00141         #endif
00142 }
00143 
00144 OfxhPluginCache::OfxhPluginCache()
00145         : _ignoreCache( false )
00146         , _cacheVersion( "" )
00147         , _dirty( false )
00148         , _enablePluginSeek( true )
00149 {
00150         std::string s = OFXGetEnv( "OFX_PLUGIN_PATH" );
00151 
00152         while( s.length() )
00153         {
00154                 int spos = int(s.find_first_of( DIRLIST_SEP_CHARS ) );
00155 
00156                 std::string path;
00157 
00158                 if( spos != -1 )
00159                 {
00160                         path = s.substr( 0, spos );
00161                         s    = s.substr( spos + 1 );
00162                 }
00163                 else
00164                 {
00165                         path = s;
00166                         s    = "";
00167                 }
00168 
00169                 _pluginPath.push_back( path );
00170         }
00171         
00172         #if defined( WINDOWS )
00173         _pluginPath.push_back( getStdOFXPluginPath() );
00174         _pluginPath.push_back( "C:\\Program Files\\Common Files\\OFX\\Plugins" );
00175         #endif
00176         #if defined( __linux__ )
00177         _pluginPath.push_back( "/usr/OFX/Plugins" );
00178         #endif
00179         #if defined( __APPLE__ )
00180         _pluginPath.push_back( "/Library/OFX/Plugins" );
00181         #endif
00182 }
00183 
00184 OfxhPluginCache::~OfxhPluginCache()
00185 {}
00186 
00187 void OfxhPluginCache::setPluginHostPath( const std::string& hostId )
00188 {
00189         #if defined( WINDOWS )
00190         _pluginPath.push_back( getStdOFXPluginPath( hostId ) );
00191         _pluginPath.push_back( "C:\\Program Files\\Common Files\\OFX\\" + hostId );
00192         #endif
00193         #if defined( __linux__ )
00194         _pluginPath.push_back( "/usr/OFX/" + hostId );
00195         #endif
00196         #if defined( __APPLE__ )
00197         _pluginPath.push_back( "/Library/OFX/" + hostId );
00198         #endif
00199 }
00200 
00201 void OfxhPluginCache::scanDirectory( std::set<std::string>& foundBinFiles, const std::string& dir, bool recurse )
00202 {
00203         #ifdef CACHE_DEBUG
00204         TUTTLE_TLOG( TUTTLE_INFO, "looking in " << dir << " for plugins" );
00205         #endif
00206 
00207         #if defined ( WINDOWS )
00208         WIN32_FIND_DATA findData;
00209         HANDLE findHandle;
00210         #else
00211         DIR* d = opendir( dir.c_str() );
00212         if( !d )
00213         {
00214                 return;
00215         }
00216         #endif
00217 
00218         _pluginDirs.push_back( dir.c_str() );
00219 
00220         #if defined ( UNIX )
00221         while( dirent * de = readdir( d ) )
00222         #elif defined ( WINDOWS )
00223         findHandle = FindFirstFile( ( dir + "\\*" ).c_str(), &findData );
00224 
00225         if( findHandle == INVALID_HANDLE_VALUE )
00226         {
00227                 return;
00228         }
00229 
00230         while( 1 )
00231         #endif
00232         {
00233                 #if defined ( UNIX )
00234                 std::string name = de->d_name;
00235                 bool isdir       = true;
00236                 #else
00237                 std::string name = findData.cFileName;
00238                 bool isdir       = ( findData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY ) != 0;
00239                 #endif
00240                 if( name.find( ".ofx.bundle" ) != std::string::npos )
00241                 {
00242                         std::string barename   = name.substr( 0, name.length() - strlen( ".bundle" ) );
00243                         std::string bundlepath = dir + DIRSEP + name;
00244                         std::string binpath    = bundlepath + DIRSEP "Contents" DIRSEP + ARCHSTR + DIRSEP + barename;
00245 
00246                         foundBinFiles.insert( binpath );
00247 
00248                         if( _knownBinFiles.find( binpath ) == _knownBinFiles.end() )
00249                         {
00250                                 #ifdef CACHE_DEBUG
00251                                 TUTTLE_TLOG( TUTTLE_INFO, "found non-cached binary " << binpath );
00252                                 #endif
00253                                 setDirty();
00254                                 try
00255                                 {
00256                                         // the binary was not in the cache
00257                                         OfxhPluginBinary* pb = new OfxhPluginBinary( binpath, bundlepath, this );
00258                                         //TUTTLE_LOG_WARNING( binpath );
00259                                         _binaries.push_back( pb );
00260                                         _knownBinFiles.insert( binpath );
00261                                         //TUTTLE_LOG_WARNING( binpath << " (" << pb->getNPlugins() <<  ")" );
00262                                         for( int j = 0; j < pb->getNPlugins(); ++j )
00263                                         {
00264                                                 OfxhPlugin& plug                   = pb->getPlugin( j );
00265                                                 APICache::OfxhPluginAPICacheI& api = plug.getApiHandler();
00266                                                 api.loadFromPlugin( plug );
00267                                         }
00268                                 }
00269                                 catch(... )
00270                                 {
00271                                         TUTTLE_LOG_WARNING( "Can't load " << binpath );
00272                                         TUTTLE_LOG_WARNING( boost::current_exception_diagnostic_information() );
00273                                         TUTTLE_LOG_WARNING( "LD_LIBRARY_PATH: " << std::getenv("LD_LIBRARY_PATH") );
00274                                 }
00275                         }
00276                         else
00277                         {
00278                                 #ifdef CACHE_DEBUG
00279                                 TUTTLE_TLOG( TUTTLE_INFO, "found cached binary " << binpath );
00280                                 #endif
00281                         }
00282                 }
00283                 else
00284                 {
00285                         if( isdir && ( recurse && name[0] != '@' && name != "." && name != ".." ) )
00286                         {
00287                                 scanDirectory( foundBinFiles, dir + DIRSEP + name, recurse );
00288                         }
00289                 }
00290                 #if defined( WINDOWS )
00291                 int rval = FindNextFile( findHandle, &findData );
00292 
00293                 if( rval == 0 )
00294                 {
00295                         break;
00296                 }
00297                 #endif
00298         }
00299 
00300         #if defined( UNIX )
00301         closedir( d );
00302         #else
00303         FindClose( findHandle );
00304         #endif
00305 }
00306 
00307 std::string OfxhPluginCache::seekPluginFile( const std::string& baseName ) const
00308 {
00309         // Exit early if disabled
00310         if( !_enablePluginSeek )
00311                 return "";
00312 
00313         for( std::list<std::string>::const_iterator paths = _pluginDirs.begin();
00314              paths != _pluginDirs.end();
00315              ++paths )
00316         {
00317                 std::string candidate = *paths + DIRSEP + baseName;
00318                 FILE* f               = fopen( candidate.c_str(), "r" );
00319                 if( f )
00320                 {
00321                         fclose( f );
00322                         return candidate;
00323                 }
00324         }
00325         return "";
00326 }
00327 
00328 void OfxhPluginCache::scanPluginFiles()
00329 {
00330         std::set<std::string> foundBinFiles;
00331 
00332         for( std::list<std::string>::iterator paths = _pluginPath.begin();
00333              paths != _pluginPath.end();
00334              ++paths )
00335         {
00336                 scanDirectory( foundBinFiles, *paths, _nonrecursePath.find( *paths ) == _nonrecursePath.end() );
00337         }
00338 
00339         OfxhPluginBinaryList::iterator i = _binaries.begin();
00340         while( i != _binaries.end() )
00341         {
00342                 if( foundBinFiles.find( i->getFilePath() ) == foundBinFiles.end() )
00343                 {
00344                         // the binary was in the cache, but was not on the path
00345                         setDirty();
00346                         i = _binaries.erase( i );
00347                 }
00348                 else
00349                 {
00350                         const bool binChanged = i->hasBinaryChanged();
00351 
00352                         // the binary was in the cache, but the binary has changed and thus we need to reload
00353                         if( binChanged )
00354                         {
00355                                 i->loadPluginInfo( this );
00356                                 setDirty();
00357                         }
00358 
00359                         for( int j = 0; j < i->getNPlugins(); ++j )
00360                         {
00361                                 OfxhPlugin& plug                   = i->getPlugin( j );
00362                                 try
00363                                 {
00364                                         APICache::OfxhPluginAPICacheI& api = plug.getApiHandler();
00365 
00366                                         if( binChanged )
00367                                         {
00368                                                 api.loadFromPlugin( plug ); // may throw
00369                                         }
00370 
00371                                         std::string reason;
00372 
00373                                         if( api.pluginSupported( plug, reason ) )
00374                                         {
00375                                                 addPlugin( &plug );
00376                                                 api.confirmPlugin( plug );
00377                                         }
00378                                         else
00379                                         {
00380                                                 TUTTLE_LOG_ERROR(
00381                                                         "Ignoring plugin " << quotes(plug.getIdentifier()) <<
00382                                                         ": unsupported, " << reason << "." );
00383                                         }
00384                                 }
00385                                 catch(...)
00386                                 {
00387                                         TUTTLE_LOG_ERROR(
00388                                                 "Ignoring plugin " << quotes(plug.getIdentifier()) <<
00389                                                 ": loading error." );
00390                                         
00391                                 }
00392                         }
00393 
00394                         ++i;
00395                 }
00396         }
00397 }
00398 
00399 APICache::OfxhPluginAPICacheI* OfxhPluginCache::findApiHandler( const std::string& api, int version )
00400 {
00401         std::list<PluginCacheSupportedApi>::iterator i = _apiHandlers.begin();
00402         while( i != _apiHandlers.end() )
00403         {
00404                 if( i->matches( api, version ) )
00405                 {
00406                         return i->_handler;
00407                 }
00408                 ++i;
00409         }
00410         return 0;
00411 }
00412 
00413 /**
00414  * get the plugin by id.  vermaj and vermin can be specified.  if they are not it will
00415  * pick the highest found version.
00416  */
00417 OfxhPlugin* OfxhPluginCache::getPluginById( const std::string& id, int vermaj, int vermin )
00418 {
00419         if( vermaj == -1 &&  vermin == -1 )
00420                 return _pluginsByID[id];
00421 
00422         // return the highest version one, which fits the pattern provided
00423         OfxhPlugin* sofar = 0;
00424 
00425         for( std::list<OfxhPlugin*>::iterator i = _plugins.begin(); i != _plugins.end(); ++i )
00426         {
00427                 OfxhPlugin* p = *i;
00428 
00429                 if( p->getIdentifier() != id )
00430                 {
00431                         continue;
00432                 }
00433 
00434                 if( vermaj != -1 && p->getVersionMajor() != vermaj )
00435                 {
00436                         continue;
00437                 }
00438 
00439                 if( vermin != -1 && p->getVersionMinor() != vermin )
00440                 {
00441                         continue;
00442                 }
00443 
00444                 if( !sofar || p->trumps( *sofar ) )
00445                 {
00446                         sofar = p;
00447                 }
00448         }
00449         return sofar;
00450 }
00451 
00452 std::ostream& operator<<( std::ostream& os, const OfxhPluginCache& v )
00453 {
00454         os << "OfxhPluginCache {" << std::endl;
00455 
00456         if( v._pluginsByID.empty() )
00457                 os << "No Plug-ins Found." << std::endl;
00458 
00459         os << "________________________________________________________________________________" << std::endl;
00460         for( std::map<std::string, OfxhPlugin*>::const_iterator it = v._pluginsByID.begin(); it != v._pluginsByID.end(); ++it )
00461         {
00462                 os << "Plug-in:" << it->first << std::endl;
00463                 os << "  " << "Filepath: " << it->second->getBinary().getFilePath();
00464                 os << "(" << it->second->getIndex() << ")" << std::endl;
00465 
00466                 //              os << "Contexts:" << std::endl;
00467                 //              const std::set<std::string>& contexts = it->second->getContexts();
00468                 //              for( std::set<std::string>::const_iterator it2 = contexts.begin(); it2 != contexts.end(); ++it2 )
00469                 //                      os << "  * " << *it2 << std::endl;
00470                 //              const OfxhImageEffectNodeDescriptor& d = it->second->getDescriptor();
00471                 //              os << "Inputs:" << std::endl;
00472                 //              const std::map<std::string, attribute::OfxhClipImageDescriptor*>& inputs = d.getClips();
00473                 //              for( std::map<std::string, attribute::OfxhClipImageDescriptor*>::const_iterator it2 = inputs.begin(); it2 != inputs.end(); ++it2 )
00474                 //                      os << "    * " << it2->first << std::endl;
00475                 os << "________________________________________________________________________________" << std::endl;
00476         }
00477         os << "}" << std::endl;
00478         return os;
00479 }
00480 
00481 }
00482 }
00483 }
00484 
00485