//============================================================================= // // File : kvi_modulemanager.cpp // Creation date : Sat Aug 12 2000 20:32:11 by Szymon Stefanek // // This file is part of the KVirc irc client distribution // Copyright (C) 2000-2005 Szymon Stefanek (pragma at kvirc dot net) // // This program is FREE software. You can redistribute it and/or // modify it under the terms of the GNU General Public License // as published by the Free Software Foundation; either version 2 // of the License, or (at your opinion) any later version. // // This program is distributed in the HOPE that it will be USEFUL, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. // See the GNU General Public License for more details. // // You should have received a copy of the GNU General Public License // along with this program. If not, write to the Free Software Foundation, // Inc. ,51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. // //============================================================================= #define __KVIRC__ #define _KVI_MODULEMANAGER_CPP_ #include "kvi_modulemanager.h" #include "kvi_fileutils.h" #include "kvi_app.h" #include "kvi_options.h" #include "kvi_frame.h" #include "kvi_console.h" #include "kvi_locale.h" #include "kvi_out.h" #include "kvi_library.h" #include KviModuleManager * g_pModuleManager = 0; KviModuleManager::KviModuleManager() { m_pModuleDict = new KviPointerHashTable(17,false); m_pModuleDict->setAutoDelete(false); m_pCleanupTimer = new TQTimer(this); connect(m_pCleanupTimer,TQT_SIGNAL(timeout()),this,TQT_SLOT(cleanupUnusedModules())); } KviModuleManager::~KviModuleManager() { unloadAllModules(); delete m_pModuleDict; delete m_pCleanupTimer; } void KviModuleManager::loadModulesByCaps(const char * caps,const char * dir) { KviStr szCapsPath(KviStr::Format,"%s%ccaps%c%s%c",dir,KVI_PATH_SEPARATOR_CHAR, KVI_PATH_SEPARATOR_CHAR,caps,KVI_PATH_SEPARATOR_CHAR); TQDir d(TQString(szCapsPath.ptr())); // FIXME: maybe check timestamps ? (old modules) TQStringList sl = d.entryList(TQDir::Files | TQDir::Readable | TQDir::NoSymLinks); for(TQStringList::Iterator it = sl.begin();it != sl.end();++it) { KviStr modname = *it; modname.cutToLast(KVI_PATH_SEPARATOR_CHAR); getModule((*it).ascii()); } } void KviModuleManager::loadModulesByCaps(const char * caps) { KviStr szDir; g_pApp->getLocalKvircDirectory(szDir,KviApp::Plugins); loadModulesByCaps(caps,szDir.ptr()); g_pApp->getGlobalKvircDirectory(szDir,KviApp::Plugins); loadModulesByCaps(caps,szDir.ptr()); } void KviModuleManager::completeModuleNames(const TQString &path,const TQString &word,KviPointerList * matches) { TQDir d(path); #ifdef COMPILE_ON_WINDOWS d.setNameFilter("kvi*.dll"); #else d.setNameFilter("libkvi*.so"); #endif // FIXME: maybe check timestamps ? (old modules) TQStringList sl = d.entryList(TQDir::Files | TQDir::Readable | TQDir::NoSymLinks); for(TQStringList::Iterator it = sl.begin();it != sl.end();++it) { TQString * modname = new TQString(*it); KviTQString::cutToLast(*modname,KVI_PATH_SEPARATOR_CHAR); KviTQString::cutToFirst(*modname,"kvi"); if(KviTQString::equalCIN(word,*modname,word.length())) { KviTQString::cutFromLast(*modname,".so"); if(!modname->isEmpty()) matches->append(modname); else delete modname; } else delete modname; } } void KviModuleManager::completeModuleNames(const TQString &word,KviPointerList * matches) { TQString szDir; // FIXME: Should check for duplicate names here! g_pApp->getLocalKvircDirectory(szDir,KviApp::Plugins); completeModuleNames(szDir,word,matches); g_pApp->getGlobalKvircDirectory(szDir,KviApp::Plugins); completeModuleNames(szDir,word,matches); } KviModule * KviModuleManager::findModule(const char * modName) { KviModule * m = m_pModuleDict->find(modName); if(m)m->updateAccessTime(); return m; } KviModule * KviModuleManager::getModule(const char * modName) { KviModule * m = m_pModuleDict->find(modName); if(!m) { if(!loadModule(modName)) return 0; m = m_pModuleDict->find(modName); } if(m)m->updateAccessTime(); return m; } /* static bool default_module_cmd_load(KviModule *,KviCommand *) { return true; } static bool default_module_cmd_unload(KviModule *m,KviCommand *) { g_pModuleManager->unloadModule(m->name()); return true; } */ bool KviModuleManager::loadModule(const char * modName) { if(findModule(modName)) { //tqDebug("MODULE %s ALREADY IN CORE MEMORY",modName); return true; } TQString tmp; TQString szName; #ifdef COMPILE_ON_WINDOWS KviTQString::appendFormatted(szName,"kvi%s.dll",modName); #else KviTQString::appendFormatted(szName,"libkvi%s.so",modName); #endif szName=szName.lower(); g_pApp->getLocalKvircDirectory(tmp,KviApp::Plugins,szName); if(!KviFileUtils::fileExists(tmp)) { g_pApp->getGlobalKvircDirectory(tmp,KviApp::Plugins,szName); } if(!KviFileUtils::fileExists(tmp)) return false; kvi_library_t handle = kvi_library_open(tmp.local8Bit().data()); if(!handle) { m_szLastError = kvi_library_error(); //tqDebug("ERROR IN LOADING MODULE %s (%s): %s",modName,szName.ptr(),kvi_library_error()); return false; } KviModuleInfo * info = (KviModuleInfo *)kvi_library_symbol(handle,KVIRC_MODULE_STRUCTURE_SYMBOL); if(!info) { m_szLastError = __tr2qs("No " KVIRC_MODULE_STRUCTURE_SYMBOL " symbol exported: not a kvirc module ?"); kvi_library_close(handle); return false; } if(!info->szKVIrcVersion) { m_szLastError = __tr2qs("This module has no version informations: refusing to load it"); kvi_library_close(handle); return false; } if(!KVI_OPTION_BOOL(KviOption_boolIgnoreModuleVersions)) { if(!kvi_strEqualCS(info->szKVIrcVersion,KVI_VERSION)) { m_szLastError = __tr2qs("This module was compiled for a different KVIrc version and can't be loaded"); m_szLastError += " ("; m_szLastError += info->szKVIrcVersion; m_szLastError += ")"; kvi_library_close(handle); return false; } } KviModule * module = new KviModule(handle,info,modName,szName.utf8().data()); // the module is probably up.. the only thing can fail is the init_routine now // load the message catalogue if any KviStr szDir; // it's more probable to have the translations in the global directory // try it as first... (yes, catalogue overriding is impossible this way.. but , anybody cares ?) g_pApp->getGlobalKvircDirectory(szDir,KviApp::Locale); if(!KviLocale::loadCatalogue(modName,szDir.ptr())) { // try the local directory then g_pApp->getLocalKvircDirectory(szDir,KviApp::Locale); KviLocale::loadCatalogue(modName,szDir.ptr()); } if(info->init_routine) { if(!((info->init_routine)(module))) { m_szLastError = __tr2qs("Failed to execute the init routine"); //tqDebug("ERROR IN LOADING MODULE %s (%s): failed to execute the init routine",modName,szName.ptr()); kvi_library_close(handle); delete module; // kill the message catalogue too then KviLocale::unloadCatalogue(modName); return false; } } m_pModuleDict->insert(modName,module); /* registerDefaultCommands(module); */ module->registerDefaultCommands(); if(KVI_OPTION_BOOL(KviOption_boolCleanupUnusedModules)) { if(!m_pCleanupTimer->isActive()) { if(KVI_OPTION_UINT(KviOption_uintModuleCleanupTimerInterval) < 30) KVI_OPTION_UINT(KviOption_uintModuleCleanupTimerInterval) = 30; m_pCleanupTimer->start(KVI_OPTION_UINT(KviOption_uintModuleCleanupTimerInterval) * 1000); } } // be verbose if needed....just make sure that we're not shutting down... if(_OUTPUT_VERBOSE && !g_pApp->closingDown()) { if(g_pFrame)g_pFrame->firstConsole()->output(KVI_OUT_VERBOSE, __tr2qs("Loaded module '%s' (%s)"),modName,szName.utf8().data()); } return true; } /* void KviModuleManager::registerDefaultCommands(KviModule * module) { // Register the default commands module->registerCommand("load",default_module_cmd_load); module->registerCommand("unload",default_module_cmd_unload); } */ bool KviModuleManager::unloadModule(const char * modName) { return unloadModule(findModule(modName)); } bool KviModuleManager::unloadModule(KviModule * module) { if(!module)return false; moduleAboutToUnload(module); if(module->moduleInfo()->cleanup_routine) { (module->moduleInfo()->cleanup_routine)(module); } KviStr szModName = module->name(); kvi_library_close(module->handle()); //tqDebug("Closing module %s, dlclose returns %d",szModName.ptr(),dlclose(module->handle())); m_pModuleDict->remove(szModName.ptr()); delete module; // unload the message catalogues, if any KviLocale::unloadCatalogue(szModName.ptr()); if(m_pModuleDict->isEmpty()) { if(m_pCleanupTimer->isActive())m_pCleanupTimer->stop(); } if(_OUTPUT_VERBOSE && !g_pApp->closingDown()) { if(g_pFrame)g_pFrame->firstConsole()->output(KVI_OUT_VERBOSE, __tr2qs("Unloaded module '%s'"),szModName.ptr()); } return true; } bool KviModuleManager::hasLockedModules() { KviPointerHashTableIterator it(*m_pModuleDict); while(KviModule * m = it.current()) { if(m->isLocked())return true; ++it; } return false; } void KviModuleManager::cleanupUnusedModules() { KviPointerHashTableIterator it(*m_pModuleDict); KviPointerList lModulesToUnload; lModulesToUnload.setAutoDelete(false); while(it.current()) { if(it.current()->secondsSinceLastAccess() > KVI_OPTION_UINT(KviOption_uintModuleCleanupTimeout)) { if(it.current()->moduleInfo()->can_unload) { if((it.current()->moduleInfo()->can_unload)(it.current())) lModulesToUnload.append(it.current()); else { // the module don't want to be unloaded // keep it memory for a while it.current()->updateAccessTime(); } } else { if(!(it.current()->isLocked())) lModulesToUnload.append(it.current()); } } ++it; } for(KviModule * pModule = lModulesToUnload.first();pModule;pModule = lModulesToUnload.next()) unloadModule(pModule); } void KviModuleManager::unloadAllModules() { KviPointerHashTableIterator it(*m_pModuleDict); KviPointerList lModulesToUnload; lModulesToUnload.setAutoDelete(false); while(KviModule * pModule = it.current()) { lModulesToUnload.append(pModule); ++it; } for(KviModule * pModule = lModulesToUnload.first();pModule;pModule = lModulesToUnload.next()) unloadModule(pModule); }