From ffca21f2743a7b367fa212799c6e2fea6190dd5d Mon Sep 17 00:00:00 2001 From: manuel Date: Tue, 3 Mar 2015 16:53:59 +0100 Subject: initial commit for kodi master --- xbmc/addons/Addon.cpp | 653 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 653 insertions(+) create mode 100644 xbmc/addons/Addon.cpp (limited to 'xbmc/addons/Addon.cpp') diff --git a/xbmc/addons/Addon.cpp b/xbmc/addons/Addon.cpp new file mode 100644 index 0000000..2aa849f --- /dev/null +++ b/xbmc/addons/Addon.cpp @@ -0,0 +1,653 @@ +/* + * 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 "Addon.h" +#include "AddonManager.h" +#include "settings/Settings.h" +#include "filesystem/Directory.h" +#include "filesystem/File.h" +#include "system.h" +#ifdef HAS_PYTHON +#include "interfaces/python/XBPython.h" +#endif +#if defined(TARGET_DARWIN) +#include "../osx/OSXGNUReplacements.h" +#endif +#ifdef TARGET_FREEBSD +#include "freebsd/FreeBSDGNUReplacements.h" +#endif +#include "utils/log.h" +#include "utils/StringUtils.h" +#include "utils/URIUtils.h" +#include "URL.h" +#include "Util.h" +#include +#include +#include + +using XFILE::CDirectory; +using XFILE::CFile; +using namespace std; + +namespace ADDON +{ + +/** + * helper functions + * + */ + +typedef struct +{ + const char* name; + TYPE type; + int pretty; + const char* icon; +} TypeMapping; + +static const TypeMapping types[] = + {{"unknown", ADDON_UNKNOWN, 0, "" }, + {"xbmc.metadata.scraper.albums", ADDON_SCRAPER_ALBUMS, 24016, "DefaultAddonAlbumInfo.png" }, + {"xbmc.metadata.scraper.artists", ADDON_SCRAPER_ARTISTS, 24017, "DefaultAddonArtistInfo.png" }, + {"xbmc.metadata.scraper.movies", ADDON_SCRAPER_MOVIES, 24007, "DefaultAddonMovieInfo.png" }, + {"xbmc.metadata.scraper.musicvideos", ADDON_SCRAPER_MUSICVIDEOS, 24015, "DefaultAddonMusicVideoInfo.png" }, + {"xbmc.metadata.scraper.tvshows", ADDON_SCRAPER_TVSHOWS, 24014, "DefaultAddonTvInfo.png" }, + {"xbmc.metadata.scraper.library", ADDON_SCRAPER_LIBRARY, 24083, "DefaultAddonInfoLibrary.png" }, + {"xbmc.ui.screensaver", ADDON_SCREENSAVER, 24008, "DefaultAddonScreensaver.png" }, + {"xbmc.player.musicviz", ADDON_VIZ, 24010, "DefaultAddonVisualization.png" }, + {"visualization-library", ADDON_VIZ_LIBRARY, 24084, "" }, + {"xbmc.python.pluginsource", ADDON_PLUGIN, 24005, "" }, + {"xbmc.python.script", ADDON_SCRIPT, 24009, "" }, + {"xbmc.python.weather", ADDON_SCRIPT_WEATHER, 24027, "DefaultAddonWeather.png" }, + {"xbmc.python.lyrics", ADDON_SCRIPT_LYRICS, 24013, "DefaultAddonLyrics.png" }, + {"xbmc.python.library", ADDON_SCRIPT_LIBRARY, 24081, "DefaultAddonHelper.png" }, + {"xbmc.python.module", ADDON_SCRIPT_MODULE, 24082, "DefaultAddonLibrary.png" }, + {"xbmc.subtitle.module", ADDON_SUBTITLE_MODULE, 24012, "DefaultAddonSubtitles.png" }, + {"kodi.context.item", ADDON_CONTEXT_ITEM, 24025, "DefaultAddonContextItem.png" }, + {"xbmc.gui.skin", ADDON_SKIN, 166, "DefaultAddonSkin.png" }, + {"xbmc.webinterface", ADDON_WEB_INTERFACE, 199, "DefaultAddonWebSkin.png" }, + {"xbmc.addon.repository", ADDON_REPOSITORY, 24011, "DefaultAddonRepository.png" }, + {"xbmc.pvrclient", ADDON_PVRDLL, 24019, "DefaultAddonPVRClient.png" }, + {"xbmc.addon.video", ADDON_VIDEO, 1037, "DefaultAddonVideo.png" }, + {"xbmc.addon.audio", ADDON_AUDIO, 1038, "DefaultAddonMusic.png" }, + {"xbmc.addon.image", ADDON_IMAGE, 1039, "DefaultAddonPicture.png" }, + {"xbmc.addon.executable", ADDON_EXECUTABLE, 1043, "DefaultAddonProgram.png" }, + {"xbmc.audioencoder", ADDON_AUDIOENCODER, 200, "DefaultAddonAudioEncoder.png" }, + {"xbmc.service", ADDON_SERVICE, 24018, "DefaultAddonService.png" }}; + +const std::string TranslateType(const ADDON::TYPE &type, bool pretty/*=false*/) +{ + for (unsigned int index=0; index < ARRAY_SIZE(types); ++index) + { + const TypeMapping &map = types[index]; + if (type == map.type) + { + if (pretty && map.pretty) + return g_localizeStrings.Get(map.pretty); + else + return map.name; + } + } + return ""; +} + +TYPE TranslateType(const std::string &string) +{ + for (unsigned int index=0; index < ARRAY_SIZE(types); ++index) + { + const TypeMapping &map = types[index]; + if (string == map.name) + return map.type; + } + + return ADDON_UNKNOWN; +} + +const std::string GetIcon(const ADDON::TYPE& type) +{ + for (unsigned int index=0; index < ARRAY_SIZE(types); ++index) + { + const TypeMapping &map = types[index]; + if (type == map.type) + return map.icon; + } + return ""; +} + +#define EMPTY_IF(x,y) \ + { \ + std::string fan=CAddonMgr::Get().GetExtValue(metadata->configuration, x); \ + if (fan == "true") \ + y.clear(); \ + } + +#define SS(x) (x) ? x : "" + +AddonProps::AddonProps(const cp_extension_t *ext) + : id(SS(ext->plugin->identifier)) + , version(SS(ext->plugin->version)) + , minversion(SS(ext->plugin->abi_bw_compatibility)) + , name(SS(ext->plugin->name)) + , path(SS(ext->plugin->plugin_path)) + , author(SS(ext->plugin->provider_name)) + , stars(0) +{ + if (ext->ext_point_id) + type = TranslateType(ext->ext_point_id); + + icon = "icon.png"; + fanart = URIUtils::AddFileToFolder(path, "fanart.jpg"); + changelog = URIUtils::AddFileToFolder(path, "changelog.txt"); + // Grab more detail from the props... + const cp_extension_t *metadata = CAddonMgr::Get().GetExtension(ext->plugin, "xbmc.addon.metadata"); //plugin, "kodi.addon.metadata"); + if (metadata) + { + summary = CAddonMgr::Get().GetTranslatedString(metadata->configuration, "summary"); + description = CAddonMgr::Get().GetTranslatedString(metadata->configuration, "description"); + disclaimer = CAddonMgr::Get().GetTranslatedString(metadata->configuration, "disclaimer"); + license = CAddonMgr::Get().GetExtValue(metadata->configuration, "license"); + std::string language; + language = CAddonMgr::Get().GetExtValue(metadata->configuration, "language"); + if (!language.empty()) + extrainfo.insert(make_pair("language",language)); + broken = CAddonMgr::Get().GetExtValue(metadata->configuration, "broken"); + EMPTY_IF("nofanart",fanart) + EMPTY_IF("noicon",icon) + EMPTY_IF("nochangelog",changelog) + } + BuildDependencies(ext->plugin); +} + +AddonProps::AddonProps(const cp_plugin_info_t *plugin) + : id(SS(plugin->identifier)) + , type(ADDON_UNKNOWN) + , version(SS(plugin->version)) + , minversion(SS(plugin->abi_bw_compatibility)) + , name(SS(plugin->name)) + , path(SS(plugin->plugin_path)) + , author(SS(plugin->provider_name)) + , stars(0) +{ + BuildDependencies(plugin); +} + +void AddonProps::Serialize(CVariant &variant) const +{ + variant["addonid"] = id; + variant["type"] = TranslateType(type); + variant["version"] = version.asString(); + variant["minversion"] = minversion.asString(); + variant["name"] = name; + variant["license"] = license; + variant["summary"] = summary; + variant["description"] = description; + variant["path"] = path; + variant["libname"] = libname; + variant["author"] = author; + variant["source"] = source; + + if (CURL::IsFullPath(icon)) + variant["icon"] = icon; + else + variant["icon"] = URIUtils::AddFileToFolder(path, icon); + + variant["thumbnail"] = variant["icon"]; + variant["disclaimer"] = disclaimer; + variant["changelog"] = changelog; + + if (CURL::IsFullPath(fanart)) + variant["fanart"] = fanart; + else + variant["fanart"] = URIUtils::AddFileToFolder(path, fanart); + + variant["dependencies"] = CVariant(CVariant::VariantTypeArray); + for (ADDONDEPS::const_iterator it = dependencies.begin(); it != dependencies.end(); ++it) + { + CVariant dep(CVariant::VariantTypeObject); + dep["addonid"] = it->first; + dep["version"] = it->second.first.asString(); + dep["optional"] = it->second.second; + variant["dependencies"].push_back(dep); + } + if (broken.empty()) + variant["broken"] = false; + else + variant["broken"] = broken; + variant["extrainfo"] = CVariant(CVariant::VariantTypeArray); + for (InfoMap::const_iterator it = extrainfo.begin(); it != extrainfo.end(); ++it) + { + CVariant info(CVariant::VariantTypeObject); + info["key"] = it->first; + info["value"] = it->second; + variant["extrainfo"].push_back(info); + } + variant["rating"] = stars; +} + +void AddonProps::BuildDependencies(const cp_plugin_info_t *plugin) +{ + if (!plugin) + return; + for (unsigned int i = 0; i < plugin->num_imports; ++i) + dependencies.insert(make_pair(std::string(plugin->imports[i].plugin_id), + make_pair(AddonVersion(SS(plugin->imports[i].version)), plugin->imports[i].optional != 0))); +} + +/** + * CAddon + * + */ + +CAddon::CAddon(const cp_extension_t *ext) + : m_props(ext) +{ + BuildLibName(ext); + Props().libname = m_strLibName; + BuildProfilePath(); + m_userSettingsPath = URIUtils::AddFileToFolder(Profile(), "settings.xml"); + m_enabled = true; + m_hasSettings = true; + m_hasStrings = false; + m_checkedStrings = false; + m_settingsLoaded = false; + m_userSettingsLoaded = false; +} + +CAddon::CAddon(const cp_plugin_info_t *plugin) + : m_props(plugin) +{ + m_enabled = true; + m_hasSettings = false; + m_hasStrings = false; + m_checkedStrings = true; + m_settingsLoaded = false; + m_userSettingsLoaded = false; +} + +CAddon::CAddon(const AddonProps &props) + : m_props(props) +{ + if (props.libname.empty()) BuildLibName(); + else m_strLibName = props.libname; + BuildProfilePath(); + m_userSettingsPath = URIUtils::AddFileToFolder(Profile(), "settings.xml"); + m_enabled = true; + m_hasSettings = true; + m_hasStrings = false; + m_checkedStrings = false; + m_settingsLoaded = false; + m_userSettingsLoaded = false; +} + +CAddon::CAddon(const CAddon &rhs) + : m_props(rhs.Props()), + m_settings(rhs.m_settings) +{ + m_addonXmlDoc = rhs.m_addonXmlDoc; + m_settingsLoaded = rhs.m_settingsLoaded; + m_userSettingsLoaded = rhs.m_userSettingsLoaded; + m_hasSettings = rhs.m_hasSettings; + BuildProfilePath(); + m_userSettingsPath = URIUtils::AddFileToFolder(Profile(), "settings.xml"); + m_strLibName = rhs.m_strLibName; + m_enabled = rhs.Enabled(); + m_hasStrings = false; + m_checkedStrings = false; +} + +AddonPtr CAddon::Clone() const +{ + return AddonPtr(new CAddon(*this)); +} + +bool CAddon::MeetsVersion(const AddonVersion &version) const +{ + return m_props.minversion <= version && version <= m_props.version; +} + +//TODO platform/path crap should be negotiated between the addon and +// the handler for it's type +void CAddon::BuildLibName(const cp_extension_t *extension) +{ + if (!extension) + { + m_strLibName = "default"; + std::string ext; + switch (m_props.type) + { + case ADDON_SCRAPER_ALBUMS: + case ADDON_SCRAPER_ARTISTS: + case ADDON_SCRAPER_MOVIES: + case ADDON_SCRAPER_MUSICVIDEOS: + case ADDON_SCRAPER_TVSHOWS: + case ADDON_SCRAPER_LIBRARY: + ext = ADDON_SCRAPER_EXT; + break; + case ADDON_SCREENSAVER: + ext = ADDON_SCREENSAVER_EXT; + break; + case ADDON_SKIN: + m_strLibName = "skin.xml"; + return; + case ADDON_VIZ: + ext = ADDON_VIS_EXT; + break; + case ADDON_PVRDLL: + ext = ADDON_PVRDLL_EXT; + break; + case ADDON_SCRIPT: + case ADDON_SCRIPT_LIBRARY: + case ADDON_SCRIPT_LYRICS: + case ADDON_SCRIPT_WEATHER: + case ADDON_SUBTITLE_MODULE: + case ADDON_PLUGIN: + case ADDON_SERVICE: + case ADDON_CONTEXT_ITEM: + ext = ADDON_PYTHON_EXT; + break; + default: + m_strLibName.clear(); + return; + } + // extensions are returned as *.ext + // so remove the asterisk + ext.erase(0,1); + m_strLibName.append(ext); + } + else + { + switch (m_props.type) + { + case ADDON_SCREENSAVER: + case ADDON_SCRIPT: + case ADDON_SCRIPT_LIBRARY: + case ADDON_SCRIPT_LYRICS: + case ADDON_SCRIPT_WEATHER: + case ADDON_SCRIPT_MODULE: + case ADDON_SUBTITLE_MODULE: + case ADDON_SCRAPER_ALBUMS: + case ADDON_SCRAPER_ARTISTS: + case ADDON_SCRAPER_MOVIES: + case ADDON_SCRAPER_MUSICVIDEOS: + case ADDON_SCRAPER_TVSHOWS: + case ADDON_SCRAPER_LIBRARY: + case ADDON_PVRDLL: + case ADDON_PLUGIN: + case ADDON_WEB_INTERFACE: + case ADDON_SERVICE: + case ADDON_REPOSITORY: + case ADDON_AUDIOENCODER: + case ADDON_CONTEXT_ITEM: + { + std::string temp = CAddonMgr::Get().GetExtValue(extension->configuration, "@library"); + m_strLibName = temp; + } + break; + default: + m_strLibName.clear(); + break; + } + } +} + +/** + * Language File Handling + */ +bool CAddon::LoadStrings() +{ + // Path where the language strings reside + std::string chosenPath = URIUtils::AddFileToFolder(m_props.path, "resources/language/"); + + m_hasStrings = m_strings.Load(chosenPath, CSettings::Get().GetString("locale.language")); + return m_checkedStrings = true; +} + +void CAddon::ClearStrings() +{ + // Unload temporary language strings + m_strings.Clear(); + m_hasStrings = false; +} + +std::string CAddon::GetString(uint32_t id) +{ + if (!m_hasStrings && ! m_checkedStrings && !LoadStrings()) + return ""; + + return m_strings.Get(id); +} + +/** + * Settings Handling + */ +bool CAddon::HasSettings() +{ + return LoadSettings(); +} + +bool CAddon::LoadSettings(bool bForce /* = false*/) +{ + if (m_settingsLoaded && !bForce) + return true; + if (!m_hasSettings) + return false; + std::string addonFileName = URIUtils::AddFileToFolder(m_props.path, "resources/settings.xml"); + + if (!m_addonXmlDoc.LoadFile(addonFileName)) + { + if (CFile::Exists(addonFileName)) + CLog::Log(LOGERROR, "Unable to load: %s, Line %d\n%s", addonFileName.c_str(), m_addonXmlDoc.ErrorRow(), m_addonXmlDoc.ErrorDesc()); + m_hasSettings = false; + return false; + } + + // Make sure that the addon XML has the settings element + TiXmlElement *setting = m_addonXmlDoc.RootElement(); + if (!setting || strcmpi(setting->Value(), "settings") != 0) + { + CLog::Log(LOGERROR, "Error loading Settings %s: cannot find root element 'settings'", addonFileName.c_str()); + return false; + } + SettingsFromXML(m_addonXmlDoc, true); + LoadUserSettings(); + m_settingsLoaded = true; + return true; +} + +bool CAddon::HasUserSettings() +{ + if (!LoadSettings()) + return false; + + return m_userSettingsLoaded; +} + +bool CAddon::ReloadSettings() +{ + return LoadSettings(true); +} + +bool CAddon::LoadUserSettings() +{ + m_userSettingsLoaded = false; + CXBMCTinyXML doc; + if (doc.LoadFile(m_userSettingsPath)) + m_userSettingsLoaded = SettingsFromXML(doc); + return m_userSettingsLoaded; +} + +void CAddon::SaveSettings(void) +{ + if (m_settings.empty()) + return; // no settings to save + + // break down the path into directories + std::string strAddon = URIUtils::GetDirectory(m_userSettingsPath); + URIUtils::RemoveSlashAtEnd(strAddon); + std::string strRoot = URIUtils::GetDirectory(strAddon); + URIUtils::RemoveSlashAtEnd(strRoot); + + // create the individual folders + if (!CDirectory::Exists(strRoot)) + CDirectory::Create(strRoot); + if (!CDirectory::Exists(strAddon)) + CDirectory::Create(strAddon); + + // create the XML file + CXBMCTinyXML doc; + SettingsToXML(doc); + doc.SaveFile(m_userSettingsPath); + m_userSettingsLoaded = true; + + CAddonMgr::Get().ReloadSettings(ID());//push the settings changes to the running addon instance +#ifdef HAS_PYTHON + g_pythonParser.OnSettingsChanged(ID()); +#endif +} + +std::string CAddon::GetSetting(const std::string& key) +{ + if (!LoadSettings()) + return ""; // no settings available + + map::const_iterator i = m_settings.find(key); + if (i != m_settings.end()) + return i->second; + return ""; +} + +void CAddon::UpdateSetting(const std::string& key, const std::string& value) +{ + LoadSettings(); + if (key.empty()) return; + m_settings[key] = value; +} + +bool CAddon::SettingsFromXML(const CXBMCTinyXML &doc, bool loadDefaults /*=false */) +{ + if (!doc.RootElement()) + return false; + + if (loadDefaults) + m_settings.clear(); + + const TiXmlElement* category = doc.RootElement()->FirstChildElement("category"); + if (!category) + category = doc.RootElement(); + + bool foundSetting = false; + while (category) + { + const TiXmlElement *setting = category->FirstChildElement("setting"); + while (setting) + { + const char *id = setting->Attribute("id"); + const char *value = setting->Attribute(loadDefaults ? "default" : "value"); + if (id && value) + { + m_settings[id] = value; + foundSetting = true; + } + setting = setting->NextSiblingElement("setting"); + } + category = category->NextSiblingElement("category"); + } + return foundSetting; +} + +void CAddon::SettingsToXML(CXBMCTinyXML &doc) const +{ + TiXmlElement node("settings"); + doc.InsertEndChild(node); + for (map::const_iterator i = m_settings.begin(); i != m_settings.end(); ++i) + { + TiXmlElement nodeSetting("setting"); + nodeSetting.SetAttribute("id", i->first.c_str()); + nodeSetting.SetAttribute("value", i->second.c_str()); + doc.RootElement()->InsertEndChild(nodeSetting); + } + doc.SaveFile(m_userSettingsPath); +} + +TiXmlElement* CAddon::GetSettingsXML() +{ + return m_addonXmlDoc.RootElement(); +} + +void CAddon::BuildProfilePath() +{ + m_profile = StringUtils::Format("special://profile/addon_data/%s/", ID().c_str()); +} + +const std::string CAddon::Icon() const +{ + if (CURL::IsFullPath(m_props.icon)) + return m_props.icon; + return URIUtils::AddFileToFolder(m_props.path, m_props.icon); +} + +const std::string CAddon::LibPath() const +{ + return URIUtils::AddFileToFolder(m_props.path, m_strLibName); +} + +AddonVersion CAddon::GetDependencyVersion(const std::string &dependencyID) const +{ + const ADDON::ADDONDEPS &deps = GetDeps(); + ADDONDEPS::const_iterator it = deps.find(dependencyID); + if (it != deps.end()) + return it->second.first; + return AddonVersion("0.0.0"); +} + +/** + * CAddonLibrary + * + */ + +CAddonLibrary::CAddonLibrary(const cp_extension_t *ext) + : CAddon(ext) + , m_addonType(SetAddonType()) +{ +} + +CAddonLibrary::CAddonLibrary(const AddonProps& props) + : CAddon(props) + , m_addonType(SetAddonType()) +{ +} + +AddonPtr CAddonLibrary::Clone() const +{ + return AddonPtr(new CAddonLibrary(*this)); +} + +TYPE CAddonLibrary::SetAddonType() +{ + if (Type() == ADDON_VIZ_LIBRARY) + return ADDON_VIZ; + else + return ADDON_UNKNOWN; +} + +} /* namespace ADDON */ + -- cgit v1.2.3