2 * Copyright (C) 2005-2013 Team XBMC
5 * This Program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 2, or (at your option)
10 * This Program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
15 * You should have received a copy of the GNU General Public License
16 * along with XBMC; see the file COPYING. If not, see
17 * <http://www.gnu.org/licenses/>.
22 #include "AddonManager.h"
23 #include "settings/Settings.h"
24 #include "filesystem/Directory.h"
25 #include "filesystem/File.h"
28 #include "interfaces/python/XBPython.h"
30 #if defined(TARGET_DARWIN)
31 #include "../osx/OSXGNUReplacements.h"
34 #include "freebsd/FreeBSDGNUReplacements.h"
36 #include "utils/log.h"
37 #include "utils/StringUtils.h"
38 #include "utils/URIUtils.h"
44 using XFILE::CDirectory;
64 static const TypeMapping types[] =
65 {{"unknown", ADDON_UNKNOWN, 0, "" },
66 {"xbmc.metadata.scraper.albums", ADDON_SCRAPER_ALBUMS, 24016, "DefaultAddonAlbumInfo.png" },
67 {"xbmc.metadata.scraper.artists", ADDON_SCRAPER_ARTISTS, 24017, "DefaultAddonArtistInfo.png" },
68 {"xbmc.metadata.scraper.movies", ADDON_SCRAPER_MOVIES, 24007, "DefaultAddonMovieInfo.png" },
69 {"xbmc.metadata.scraper.musicvideos", ADDON_SCRAPER_MUSICVIDEOS, 24015, "DefaultAddonMusicVideoInfo.png" },
70 {"xbmc.metadata.scraper.tvshows", ADDON_SCRAPER_TVSHOWS, 24014, "DefaultAddonTvInfo.png" },
71 {"xbmc.metadata.scraper.library", ADDON_SCRAPER_LIBRARY, 0, "" },
72 {"xbmc.ui.screensaver", ADDON_SCREENSAVER, 24008, "DefaultAddonScreensaver.png" },
73 {"xbmc.player.musicviz", ADDON_VIZ, 24010, "DefaultAddonVisualization.png" },
74 {"visualization-library", ADDON_VIZ_LIBRARY, 0, "" },
75 {"xbmc.python.pluginsource", ADDON_PLUGIN, 24005, "" },
76 {"xbmc.python.script", ADDON_SCRIPT, 24009, "" },
77 {"xbmc.python.weather", ADDON_SCRIPT_WEATHER, 24027, "DefaultAddonWeather.png" },
78 {"xbmc.python.lyrics", ADDON_SCRIPT_LYRICS, 24013, "DefaultAddonLyrics.png" },
79 {"xbmc.python.library", ADDON_SCRIPT_LIBRARY, 24014, "" },
80 {"xbmc.python.module", ADDON_SCRIPT_MODULE, 0, "" },
81 {"xbmc.subtitle.module", ADDON_SUBTITLE_MODULE, 24012, "DefaultAddonSubtitles.png" },
82 {"xbmc.gui.skin", ADDON_SKIN, 166, "DefaultAddonSkin.png" },
83 {"xbmc.gui.webinterface", ADDON_WEB_INTERFACE, 199, "DefaultAddonWebSkin.png" },
84 {"xbmc.addon.repository", ADDON_REPOSITORY, 24011, "DefaultAddonRepository.png" },
85 {"xbmc.pvrclient", ADDON_PVRDLL, 24019, "DefaultAddonPVRClient.png" },
86 {"xbmc.addon.video", ADDON_VIDEO, 1037, "DefaultAddonVideo.png" },
87 {"xbmc.addon.audio", ADDON_AUDIO, 1038, "DefaultAddonMusic.png" },
88 {"xbmc.addon.image", ADDON_IMAGE, 1039, "DefaultAddonPicture.png" },
89 {"xbmc.addon.executable", ADDON_EXECUTABLE, 1043, "DefaultAddonProgram.png" },
90 {"xbmc.service", ADDON_SERVICE, 24018, "DefaultAddonService.png" }};
92 const CStdString TranslateType(const ADDON::TYPE &type, bool pretty/*=false*/)
94 for (unsigned int index=0; index < sizeof(types)/sizeof(types[0]); ++index)
96 const TypeMapping &map = types[index];
99 if (pretty && map.pretty)
100 return g_localizeStrings.Get(map.pretty);
108 TYPE TranslateType(const CStdString &string)
110 for (unsigned int index=0; index < sizeof(types)/sizeof(types[0]); ++index)
112 const TypeMapping &map = types[index];
113 if (string.Equals(map.name))
116 return ADDON_UNKNOWN;
119 const CStdString GetIcon(const ADDON::TYPE& type)
121 for (unsigned int index=0; index < sizeof(types)/sizeof(types[0]); ++index)
123 const TypeMapping &map = types[index];
124 if (type == map.type)
130 #define EMPTY_IF(x,y) \
132 CStdString fan=CAddonMgr::Get().GetExtValue(metadata->configuration, x); \
133 if (fan.Equals("true")) \
137 AddonProps::AddonProps(const cp_extension_t *ext)
138 : id(ext->plugin->identifier)
139 , version(ext->plugin->version)
140 , minversion(ext->plugin->abi_bw_compatibility)
141 , name(ext->plugin->name)
142 , path(ext->plugin->plugin_path)
143 , author(ext->plugin->provider_name)
146 if (ext->ext_point_id)
147 type = TranslateType(ext->ext_point_id);
150 fanart = URIUtils::AddFileToFolder(path, "fanart.jpg");
151 changelog = URIUtils::AddFileToFolder(path, "changelog.txt");
152 // Grab more detail from the props...
153 const cp_extension_t *metadata = CAddonMgr::Get().GetExtension(ext->plugin, "xbmc.addon.metadata");
156 summary = CAddonMgr::Get().GetTranslatedString(metadata->configuration, "summary");
157 description = CAddonMgr::Get().GetTranslatedString(metadata->configuration, "description");
158 disclaimer = CAddonMgr::Get().GetTranslatedString(metadata->configuration, "disclaimer");
159 license = CAddonMgr::Get().GetExtValue(metadata->configuration, "license");
161 language = CAddonMgr::Get().GetExtValue(metadata->configuration, "language");
162 if (!language.empty())
163 extrainfo.insert(make_pair("language",language));
164 broken = CAddonMgr::Get().GetExtValue(metadata->configuration, "broken");
165 EMPTY_IF("nofanart",fanart)
166 EMPTY_IF("noicon",icon)
167 EMPTY_IF("nochangelog",changelog)
169 BuildDependencies(ext->plugin);
172 AddonProps::AddonProps(const cp_plugin_info_t *plugin)
173 : id(plugin->identifier)
174 , version(plugin->version)
175 , minversion(plugin->abi_bw_compatibility)
177 , path(plugin->plugin_path)
178 , author(plugin->provider_name)
181 BuildDependencies(plugin);
184 void AddonProps::Serialize(CVariant &variant) const
186 variant["addonid"] = id;
187 variant["type"] = TranslateType(type);
188 variant["version"] = version.c_str();
189 variant["minversion"] = minversion.c_str();
190 variant["name"] = name;
191 variant["license"] = license;
192 variant["summary"] = summary;
193 variant["description"] = description;
194 variant["path"] = path;
195 variant["libname"] = libname;
196 variant["author"] = author;
197 variant["source"] = source;
199 if (CURL::IsFullPath(icon))
200 variant["icon"] = icon;
202 variant["icon"] = URIUtils::AddFileToFolder(path, icon);
204 variant["thumbnail"] = variant["icon"];
205 variant["disclaimer"] = disclaimer;
206 variant["changelog"] = changelog;
208 if (CURL::IsFullPath(fanart))
209 variant["fanart"] = fanart;
211 variant["fanart"] = URIUtils::AddFileToFolder(path, fanart);
213 variant["dependencies"] = CVariant(CVariant::VariantTypeArray);
214 for (ADDONDEPS::const_iterator it = dependencies.begin(); it != dependencies.end(); it++)
216 CVariant dep(CVariant::VariantTypeObject);
217 dep["addonid"] = it->first;
218 dep["version"] = it->second.first.c_str();
219 dep["optional"] = it->second.second;
220 variant["dependencies"].push_back(dep);
223 variant["broken"] = false;
225 variant["broken"] = broken;
226 variant["extrainfo"] = CVariant(CVariant::VariantTypeArray);
227 for (InfoMap::const_iterator it = extrainfo.begin(); it != extrainfo.end(); it++)
229 CVariant info(CVariant::VariantTypeObject);
230 info["key"] = it->first;
231 info["value"] = it->second;
232 variant["extrainfo"].push_back(info);
234 variant["rating"] = stars;
237 void AddonProps::BuildDependencies(const cp_plugin_info_t *plugin)
241 for (unsigned int i = 0; i < plugin->num_imports; ++i)
242 dependencies.insert(make_pair(CStdString(plugin->imports[i].plugin_id),
243 make_pair(AddonVersion(plugin->imports[i].version), plugin->imports[i].optional != 0)));
251 CAddon::CAddon(const cp_extension_t *ext)
255 Props().libname = m_strLibName;
257 m_userSettingsPath = URIUtils::AddFileToFolder(Profile(), "settings.xml");
259 m_hasSettings = true;
260 m_hasStrings = false;
261 m_checkedStrings = false;
262 m_settingsLoaded = false;
263 m_userSettingsLoaded = false;
266 CAddon::CAddon(const cp_plugin_info_t *plugin)
270 m_hasSettings = false;
271 m_hasStrings = false;
272 m_checkedStrings = true;
273 m_settingsLoaded = false;
274 m_userSettingsLoaded = false;
277 CAddon::CAddon(const AddonProps &props)
280 if (props.libname.empty()) BuildLibName();
281 else m_strLibName = props.libname;
283 m_userSettingsPath = URIUtils::AddFileToFolder(Profile(), "settings.xml");
285 m_hasSettings = true;
286 m_hasStrings = false;
287 m_checkedStrings = false;
288 m_settingsLoaded = false;
289 m_userSettingsLoaded = false;
292 CAddon::CAddon(const CAddon &rhs)
293 : m_props(rhs.Props())
295 m_settings = rhs.m_settings;
296 m_addonXmlDoc = rhs.m_addonXmlDoc;
297 m_settingsLoaded = rhs.m_settingsLoaded;
298 m_userSettingsLoaded = rhs.m_userSettingsLoaded;
299 m_hasSettings = rhs.m_hasSettings;
301 m_userSettingsPath = URIUtils::AddFileToFolder(Profile(), "settings.xml");
302 m_strLibName = rhs.m_strLibName;
303 m_enabled = rhs.Enabled();
304 m_hasStrings = false;
305 m_checkedStrings = false;
308 AddonPtr CAddon::Clone() const
310 return AddonPtr(new CAddon(*this));
313 bool CAddon::MeetsVersion(const AddonVersion &version) const
315 // if the addon is one of xbmc's extension point definitions (addonid starts with "xbmc.")
316 // and the minversion is "0.0.0" i.e. no <backwards-compatibility> tag has been specified
317 // we need to assume that the current version is not backwards-compatible and therefore check against the actual version
318 if (StringUtils::StartsWithNoCase(m_props.id, "xbmc.") &&
319 (strlen(m_props.minversion.c_str()) == 0 || StringUtils::EqualsNoCase(m_props.minversion.c_str(), "0.0.0")))
320 return m_props.version == version;
322 return m_props.minversion <= version && version <= m_props.version;
325 //TODO platform/path crap should be negotiated between the addon and
326 // the handler for it's type
327 void CAddon::BuildLibName(const cp_extension_t *extension)
331 m_strLibName = "default";
333 switch (m_props.type)
335 case ADDON_SCRAPER_ALBUMS:
336 case ADDON_SCRAPER_ARTISTS:
337 case ADDON_SCRAPER_MOVIES:
338 case ADDON_SCRAPER_MUSICVIDEOS:
339 case ADDON_SCRAPER_TVSHOWS:
340 case ADDON_SCRAPER_LIBRARY:
341 ext = ADDON_SCRAPER_EXT;
343 case ADDON_SCREENSAVER:
344 ext = ADDON_SCREENSAVER_EXT;
347 m_strLibName = "skin.xml";
353 ext = ADDON_PVRDLL_EXT;
356 case ADDON_SCRIPT_LIBRARY:
357 case ADDON_SCRIPT_LYRICS:
358 case ADDON_SCRIPT_WEATHER:
359 case ADDON_SUBTITLE_MODULE:
362 ext = ADDON_PYTHON_EXT;
365 m_strLibName.clear();
368 // extensions are returned as *.ext
369 // so remove the asterisk
371 m_strLibName.append(ext);
375 switch (m_props.type)
377 case ADDON_SCREENSAVER:
379 case ADDON_SCRIPT_LIBRARY:
380 case ADDON_SCRIPT_LYRICS:
381 case ADDON_SCRIPT_WEATHER:
382 case ADDON_SCRIPT_MODULE:
383 case ADDON_SUBTITLE_MODULE:
384 case ADDON_SCRAPER_ALBUMS:
385 case ADDON_SCRAPER_ARTISTS:
386 case ADDON_SCRAPER_MOVIES:
387 case ADDON_SCRAPER_MUSICVIDEOS:
388 case ADDON_SCRAPER_TVSHOWS:
389 case ADDON_SCRAPER_LIBRARY:
393 case ADDON_REPOSITORY:
395 CStdString temp = CAddonMgr::Get().GetExtValue(extension->configuration, "@library");
400 m_strLibName.clear();
407 * Language File Handling
409 bool CAddon::LoadStrings()
411 // Path where the language strings reside
412 CStdString chosenPath = URIUtils::AddFileToFolder(m_props.path, "resources/language/");
414 m_hasStrings = m_strings.Load(chosenPath, CSettings::Get().GetString("locale.language"));
415 return m_checkedStrings = true;
418 void CAddon::ClearStrings()
420 // Unload temporary language strings
422 m_hasStrings = false;
425 CStdString CAddon::GetString(uint32_t id)
427 if (!m_hasStrings && ! m_checkedStrings && !LoadStrings())
430 return m_strings.Get(id);
436 bool CAddon::HasSettings()
438 return LoadSettings();
441 bool CAddon::LoadSettings(bool bForce /* = false*/)
443 if (m_settingsLoaded && !bForce)
447 CStdString addonFileName = URIUtils::AddFileToFolder(m_props.path, "resources/settings.xml");
449 if (!m_addonXmlDoc.LoadFile(addonFileName))
451 if (CFile::Exists(addonFileName))
452 CLog::Log(LOGERROR, "Unable to load: %s, Line %d\n%s", addonFileName.c_str(), m_addonXmlDoc.ErrorRow(), m_addonXmlDoc.ErrorDesc());
453 m_hasSettings = false;
457 // Make sure that the addon XML has the settings element
458 TiXmlElement *setting = m_addonXmlDoc.RootElement();
459 if (!setting || strcmpi(setting->Value(), "settings") != 0)
461 CLog::Log(LOGERROR, "Error loading Settings %s: cannot find root element 'settings'", addonFileName.c_str());
464 SettingsFromXML(m_addonXmlDoc, true);
466 m_settingsLoaded = true;
470 bool CAddon::HasUserSettings()
475 return m_userSettingsLoaded;
478 bool CAddon::ReloadSettings()
480 return LoadSettings(true);
483 bool CAddon::LoadUserSettings()
485 m_userSettingsLoaded = false;
487 if (doc.LoadFile(m_userSettingsPath))
488 m_userSettingsLoaded = SettingsFromXML(doc);
489 return m_userSettingsLoaded;
492 void CAddon::SaveSettings(void)
494 if (m_settings.empty())
495 return; // no settings to save
497 // break down the path into directories
498 CStdString strAddon = URIUtils::GetDirectory(m_userSettingsPath);
499 URIUtils::RemoveSlashAtEnd(strAddon);
500 CStdString strRoot = URIUtils::GetDirectory(strAddon);
501 URIUtils::RemoveSlashAtEnd(strRoot);
503 // create the individual folders
504 if (!CDirectory::Exists(strRoot))
505 CDirectory::Create(strRoot);
506 if (!CDirectory::Exists(strAddon))
507 CDirectory::Create(strAddon);
509 // create the XML file
512 doc.SaveFile(m_userSettingsPath);
513 m_userSettingsLoaded = true;
515 CAddonMgr::Get().ReloadSettings(ID());//push the settings changes to the running addon instance
517 g_pythonParser.OnSettingsChanged(ID());
521 CStdString CAddon::GetSetting(const CStdString& key)
524 return ""; // no settings available
526 map<CStdString, CStdString>::const_iterator i = m_settings.find(key);
527 if (i != m_settings.end())
532 void CAddon::UpdateSetting(const CStdString& key, const CStdString& value)
535 if (key.empty()) return;
536 m_settings[key] = value;
539 bool CAddon::SettingsFromXML(const CXBMCTinyXML &doc, bool loadDefaults /*=false */)
541 if (!doc.RootElement())
547 const TiXmlElement* category = doc.RootElement()->FirstChildElement("category");
549 category = doc.RootElement();
551 bool foundSetting = false;
554 const TiXmlElement *setting = category->FirstChildElement("setting");
557 const char *id = setting->Attribute("id");
558 const char *value = setting->Attribute(loadDefaults ? "default" : "value");
561 m_settings[id] = value;
564 setting = setting->NextSiblingElement("setting");
566 category = category->NextSiblingElement("category");
571 void CAddon::SettingsToXML(CXBMCTinyXML &doc) const
573 TiXmlElement node("settings");
574 doc.InsertEndChild(node);
575 for (map<CStdString, CStdString>::const_iterator i = m_settings.begin(); i != m_settings.end(); ++i)
577 TiXmlElement nodeSetting("setting");
578 nodeSetting.SetAttribute("id", i->first.c_str());
579 nodeSetting.SetAttribute("value", i->second.c_str());
580 doc.RootElement()->InsertEndChild(nodeSetting);
582 doc.SaveFile(m_userSettingsPath);
585 TiXmlElement* CAddon::GetSettingsXML()
587 return m_addonXmlDoc.RootElement();
590 void CAddon::BuildProfilePath()
592 m_profile = StringUtils::Format("special://profile/addon_data/%s/", ID().c_str());
595 const CStdString CAddon::Icon() const
597 if (CURL::IsFullPath(m_props.icon))
599 return URIUtils::AddFileToFolder(m_props.path, m_props.icon);
602 const CStdString CAddon::LibPath() const
604 return URIUtils::AddFileToFolder(m_props.path, m_strLibName);
612 CAddonLibrary::CAddonLibrary(const cp_extension_t *ext)
614 , m_addonType(SetAddonType())
618 CAddonLibrary::CAddonLibrary(const AddonProps& props)
620 , m_addonType(SetAddonType())
624 AddonPtr CAddonLibrary::Clone() const
626 return AddonPtr(new CAddonLibrary(*this));
629 TYPE CAddonLibrary::SetAddonType()
631 if (Type() == ADDON_VIZ_LIBRARY)
634 return ADDON_UNKNOWN;
637 CStdString GetXbmcApiVersionDependency(ADDON::AddonPtr addon)
639 CStdString version("1.0");
640 if (addon.get() != NULL)
642 const ADDON::ADDONDEPS &deps = addon->GetDeps();
643 ADDON::ADDONDEPS::const_iterator it;
644 CStdString key("xbmc.python");
646 if (!(it == deps.end()))
648 const ADDON::AddonVersion * xbmcApiVersion = &(it->second.first);
649 version = xbmcApiVersion->c_str();
657 } /* namespace ADDON */