/*
* 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 */