#pragma once /* * Copyright (C) 2005-2013 Team XBMC * http://xbmc.org * * 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, or (at your option) * 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 XBMC; see the file COPYING. If not, see * . * */ #include #include #include "Addon.h" #include "DllAddon.h" #include "AddonManager.h" #include "AddonStatusHandler.h" #include "AddonCallbacks.h" #include "utils/URIUtils.h" #include "filesystem/File.h" #include "filesystem/SpecialProtocol.h" #include "filesystem/Directory.h" #include "utils/log.h" #include "interfaces/IAnnouncer.h" #include "interfaces/AnnouncementManager.h" #include "utils/XMLUtils.h" #include "Util.h" namespace ADDON { template class CAddonDll : public CAddon, public ANNOUNCEMENT::IAnnouncer { public: CAddonDll(const AddonProps &props); CAddonDll(const cp_extension_t *ext); CAddonDll(const CAddonDll &rhs); virtual ~CAddonDll(); virtual AddonPtr Clone() const; virtual ADDON_STATUS GetStatus(); // addon settings virtual void SaveSettings(); virtual std::string GetSetting(const std::string& key); ADDON_STATUS Create(); virtual void Stop(); virtual bool CheckAPIVersion(void) { return true; } void Destroy(); bool DllLoaded(void) const; void Announce(ANNOUNCEMENT::AnnouncementFlag flag, const char *sender, const char *message, const CVariant &data); protected: void HandleException(std::exception &e, const char* context); bool Initialized() { return m_initialized; } virtual void BuildLibName(const cp_extension_t *ext = NULL) {} virtual bool LoadSettings(); TheStruct* m_pStruct; TheProps* m_pInfo; CAddonCallbacks* m_pHelpers; bool m_bIsChild; private: TheDll* m_pDll; bool m_initialized; bool LoadDll(); bool m_needsavedsettings; virtual ADDON_STATUS TransferSettings(); TiXmlElement MakeSetting(DllSetting& setting) const; static void AddOnStatusCallback(void *userData, const ADDON_STATUS status, const char* msg); static bool AddOnGetSetting(void *userData, const char *settingName, void *settingValue); static void AddOnOpenSettings(const char *url, bool bReload); static void AddOnOpenOwnSettings(void *userData, bool bReload); static const char* AddOnGetAddonDirectory(void *userData); static const char* AddOnGetUserDirectory(void *userData); }; template CAddonDll::CAddonDll(const cp_extension_t *ext) : CAddon(ext), m_bIsChild(false) { // if library attribute isn't present, look for a system-dependent one if (ext && m_strLibName.empty()) { #if defined(TARGET_ANDROID) m_strLibName = CAddonMgr::Get().GetExtValue(ext->configuration, "@library_android"); #elif defined(TARGET_LINUX) || defined(TARGET_FREEBSD) m_strLibName = CAddonMgr::Get().GetExtValue(ext->configuration, "@library_linux"); #elif defined(TARGET_WINDOWS) && defined(HAS_DX) m_strLibName = CAddonMgr::Get().GetExtValue(ext->configuration, "@library_windx"); #elif defined(TARGET_DARWIN) m_strLibName = CAddonMgr::Get().GetExtValue(ext->configuration, "@library_osx"); #endif } m_pStruct = NULL; m_initialized = false; m_pDll = NULL; m_pInfo = NULL; m_pHelpers = NULL; m_needsavedsettings = false; } template CAddonDll::CAddonDll(const AddonProps &props) : CAddon(props), m_bIsChild(false) { m_pStruct = NULL; m_initialized = false; m_pDll = NULL; m_pInfo = NULL; m_pHelpers = NULL; m_needsavedsettings = false; } template CAddonDll::CAddonDll(const CAddonDll &rhs) : CAddon(rhs), m_bIsChild(true) { m_pStruct = rhs.m_pStruct; m_initialized = rhs.m_initialized; m_pDll = rhs.m_pDll; m_pInfo = rhs.m_pInfo; m_pHelpers = rhs.m_pHelpers; m_needsavedsettings = rhs.m_needsavedsettings; } template CAddonDll::~CAddonDll() { if (m_initialized) Destroy(); } template AddonPtr CAddonDll::Clone() const { return AddonPtr(new CAddonDll(*this)); } template bool CAddonDll::LoadDll() { std::string strFileName; if (!m_bIsChild) { strFileName = LibPath(); } else { //FIXME hack to load same Dll twice std::string extension = URIUtils::GetExtension(m_strLibName); strFileName = "special://temp/" + m_strLibName; URIUtils::RemoveExtension(strFileName); strFileName += "-" + ID() + extension; if (!XFILE::CFile::Exists(strFileName)) XFILE::CFile::Copy(LibPath(), strFileName); CLog::Log(LOGNOTICE, "ADDON: Loaded virtual child addon %s", strFileName.c_str()); } /* Check if lib being loaded exists, else check in XBMC binary location */ #if defined(TARGET_ANDROID) // Android libs MUST live in this path, else multi-arch will break. // The usual soname requirements apply. no subdirs, and filename is ^lib.*\.so$ if (!XFILE::CFile::Exists(strFileName)) { std::string tempbin = getenv("XBMC_ANDROID_LIBS"); strFileName = tempbin + "/" + m_strLibName; } #endif if (!XFILE::CFile::Exists(strFileName)) { std::string temp = CSpecialProtocol::TranslatePath("special://xbmc/"); std::string tempbin = CSpecialProtocol::TranslatePath("special://xbmcbin/"); strFileName.erase(0, temp.size()); strFileName = tempbin + strFileName; if (!XFILE::CFile::Exists(strFileName)) { CLog::Log(LOGERROR, "ADDON: Could not locate %s", m_strLibName.c_str()); return false; } } /* Load the Dll */ m_pDll = new TheDll; m_pDll->SetFile(strFileName); m_pDll->EnableDelayedUnload(false); if (!m_pDll->Load()) { delete m_pDll; m_pDll = NULL; new CAddonStatusHandler(ID(), ADDON_STATUS_UNKNOWN, "Can't load Dll", false); return false; } m_pStruct = (TheStruct*)malloc(sizeof(TheStruct)); if (m_pStruct) { ZeroMemory(m_pStruct, sizeof(TheStruct)); m_pDll->GetAddon(m_pStruct); return true; } return false; } template ADDON_STATUS CAddonDll::Create() { ADDON_STATUS status(ADDON_STATUS_UNKNOWN); CLog::Log(LOGDEBUG, "ADDON: Dll Initializing - %s", Name().c_str()); m_initialized = false; if (!LoadDll() || !CheckAPIVersion()) return ADDON_STATUS_PERMANENT_FAILURE; /* Allocate the helper function class to allow crosstalk over helper libraries */ m_pHelpers = new CAddonCallbacks(this); /* Call Create to make connections, initializing data or whatever is needed to become the AddOn running */ try { status = m_pDll->Create(m_pHelpers->GetCallbacks(), m_pInfo); if (status == ADDON_STATUS_OK) { m_initialized = true; ANNOUNCEMENT::CAnnouncementManager::Get().AddAnnouncer(this); } else if ((status == ADDON_STATUS_NEED_SETTINGS) || (status == ADDON_STATUS_NEED_SAVEDSETTINGS)) { m_needsavedsettings = (status == ADDON_STATUS_NEED_SAVEDSETTINGS); if ((status = TransferSettings()) == ADDON_STATUS_OK) m_initialized = true; else new CAddonStatusHandler(ID(), status, "", false); } else { // Addon failed initialization CLog::Log(LOGERROR, "ADDON: Dll %s - Client returned bad status (%i) from Create and is not usable", Name().c_str(), status); new CAddonStatusHandler(ID(), status, "", false); } } catch (std::exception &e) { HandleException(e, "m_pDll->Create"); } if (!m_initialized) SAFE_DELETE(m_pHelpers); return status; } template void CAddonDll::Stop() { /* Inform dll to stop all activities */ try { if (m_needsavedsettings) // If the addon supports it we save some settings to settings.xml before stop { char str_id[64] = ""; char str_value[1024]; CAddon::LoadUserSettings(); for (unsigned int i=0; (strcmp(str_id,"###End") != 0); i++) { strcpy(str_id, "###GetSavedSettings"); sprintf (str_value, "%i", i); ADDON_STATUS status = m_pDll->SetSetting((const char*)&str_id, (void*)&str_value); if (status == ADDON_STATUS_UNKNOWN) break; if (strcmp(str_id,"###End") != 0) UpdateSetting(str_id, str_value); } CAddon::SaveSettings(); } if (m_pDll) { m_pDll->Stop(); CLog::Log(LOGINFO, "ADDON: Dll Stopped - %s", Name().c_str()); } } catch (std::exception &e) { HandleException(e, "m_pDll->Stop"); } } template void CAddonDll::Destroy() { ANNOUNCEMENT::CAnnouncementManager::Get().RemoveAnnouncer(this); /* Unload library file */ try { if (m_pDll) { m_pDll->Destroy(); m_pDll->Unload(); } } catch (std::exception &e) { HandleException(e, "m_pDll->Unload"); } delete m_pHelpers; m_pHelpers = NULL; free(m_pStruct); m_pStruct = NULL; if (m_pDll) { delete m_pDll; m_pDll = NULL; CLog::Log(LOGINFO, "ADDON: Dll Destroyed - %s", Name().c_str()); } m_initialized = false; } template bool CAddonDll::DllLoaded(void) const { return m_pDll != NULL; } template ADDON_STATUS CAddonDll::GetStatus() { try { return m_pDll->GetStatus(); } catch (std::exception &e) { HandleException(e, "m_pDll->GetStatus()"); } return ADDON_STATUS_UNKNOWN; } template bool CAddonDll::LoadSettings() { if (m_settingsLoaded) return true; if (!LoadDll()) return false; ADDON_StructSetting** sSet; std::vector vSet; unsigned entries = 0; try { entries = m_pDll->GetSettings(&sSet); DllUtils::StructToVec(entries, &sSet, &vSet); m_pDll->FreeSettings(); } catch (std::exception &e) { HandleException(e, "m_pDll->GetSettings()"); return false; } if (vSet.size()) { // regenerate XML doc m_addonXmlDoc.Clear(); TiXmlElement node("settings"); m_addonXmlDoc.InsertEndChild(node); for (unsigned i=0; i < entries; i++) { DllSetting& setting = vSet[i]; m_addonXmlDoc.RootElement()->InsertEndChild(MakeSetting(setting)); } CAddon::SettingsFromXML(m_addonXmlDoc, true); } else return CAddon::LoadSettings(); m_settingsLoaded = true; CAddon::LoadUserSettings(); return true; } template TiXmlElement CAddonDll::MakeSetting(DllSetting& setting) const { TiXmlElement node("setting"); switch (setting.type) { case DllSetting::CHECK: { node.SetAttribute("id", setting.id); node.SetAttribute("type", "bool"); node.SetAttribute("label", setting.label); break; } case DllSetting::SPIN: { node.SetAttribute("id", setting.id); node.SetAttribute("type", "enum"); node.SetAttribute("label", setting.label); std::string values; for (unsigned int i = 0; i < setting.entry.size(); i++) { values.append(setting.entry[i]); values.append("|"); } node.SetAttribute("values", values.c_str()); break; } default: break; } return node; } template void CAddonDll::SaveSettings() { // must save first, as TransferSettings() reloads saved settings! CAddon::SaveSettings(); if (m_initialized) TransferSettings(); } template std::string CAddonDll::GetSetting(const std::string& key) { return CAddon::GetSetting(key); } template ADDON_STATUS CAddonDll::TransferSettings() { bool restart = false; ADDON_STATUS reportStatus = ADDON_STATUS_OK; CLog::Log(LOGDEBUG, "Calling TransferSettings for: %s", Name().c_str()); LoadSettings(); const TiXmlElement *category = m_addonXmlDoc.RootElement() ? m_addonXmlDoc.RootElement()->FirstChildElement("category") : NULL; if (!category) category = m_addonXmlDoc.RootElement(); // no categories while (category) { const TiXmlElement *setting = category->FirstChildElement("setting"); while (setting) { ADDON_STATUS status = ADDON_STATUS_OK; const char *id = setting->Attribute("id"); const std::string type = XMLUtils::GetAttribute(setting, "type"); const char *option = setting->Attribute("option"); if (id && !type.empty()) { if (type == "sep" || type == "lsep") { /* Don't propagate separators */ } else if (type == "text" || type == "ipaddress" || type == "video" || type == "audio" || type == "image" || type == "folder" || type == "executable" || type == "file" || type == "action" || type == "date" || type == "time" || type == "select" || type == "addon" || type == "labelenum" || type == "fileenum" ) { status = m_pDll->SetSetting(id, (const char*) GetSetting(id).c_str()); } else if (type == "enum" || type =="integer" || type == "labelenum" || type == "rangeofnum") { int tmp = atoi(GetSetting(id).c_str()); status = m_pDll->SetSetting(id, (int*) &tmp); } else if (type == "bool") { bool tmp = (GetSetting(id) == "true") ? true : false; status = m_pDll->SetSetting(id, (bool*) &tmp); } else if (type == "rangeofnum" || type == "slider" || type == "number") { float tmpf = (float)atof(GetSetting(id).c_str()); int tmpi; if (option && strcmpi(option,"int") == 0) { tmpi = (int)floor(tmpf); status = m_pDll->SetSetting(id, (int*) &tmpi); } else { status = m_pDll->SetSetting(id, (float*) &tmpf); } } else { /* Log unknowns as an error, but go ahead and transfer the string */ CLog::Log(LOGERROR, "Unknown setting type '%s' for %s", type.c_str(), Name().c_str()); status = m_pDll->SetSetting(id, (const char*) GetSetting(id).c_str()); } if (status == ADDON_STATUS_NEED_RESTART) restart = true; else if (status != ADDON_STATUS_OK) reportStatus = status; } setting = setting->NextSiblingElement("setting"); } category = category->NextSiblingElement("category"); } if (restart || reportStatus != ADDON_STATUS_OK) { new CAddonStatusHandler(ID(), restart ? ADDON_STATUS_NEED_RESTART : reportStatus, "", true); } return ADDON_STATUS_OK; } template void CAddonDll::Announce(ANNOUNCEMENT::AnnouncementFlag flag, const char *sender, const char *message, const CVariant &data) { try { m_pDll->Announce(ANNOUNCEMENT::AnnouncementFlagToString(flag), sender, message, &data); } catch (std::exception &e) { HandleException(e, "m_pDll->Announce()"); } } template void CAddonDll::HandleException(std::exception &e, const char* context) { m_initialized = false; m_pDll->Unload(); CLog::Log(LOGERROR, "ADDON: Dll %s, throws an exception '%s' during %s. Contact developer '%s' with bug reports", Name().c_str(), e.what(), context, Author().c_str()); } }; /* namespace ADDON */