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"
27 #include "interfaces/python/XBPython.h"
29 #if defined(TARGET_DARWIN)
30 #include "../osx/OSXGNUReplacements.h"
33 #include "freebsd/FreeBSDGNUReplacements.h"
35 #include "utils/log.h"
36 #include "utils/StringUtils.h"
37 #include "utils/URIUtils.h"
43 using XFILE::CDirectory;
63 static const TypeMapping types[] =
64 {{"unknown", ADDON_UNKNOWN, 0, "" },
65 {"xbmc.metadata.scraper.albums", ADDON_SCRAPER_ALBUMS, 24016, "DefaultAddonAlbumInfo.png" },
66 {"xbmc.metadata.scraper.artists", ADDON_SCRAPER_ARTISTS, 24017, "DefaultAddonArtistInfo.png" },
67 {"xbmc.metadata.scraper.movies", ADDON_SCRAPER_MOVIES, 24007, "DefaultAddonMovieInfo.png" },
68 {"xbmc.metadata.scraper.musicvideos", ADDON_SCRAPER_MUSICVIDEOS, 24015, "DefaultAddonMusicVideoInfo.png" },
69 {"xbmc.metadata.scraper.tvshows", ADDON_SCRAPER_TVSHOWS, 24014, "DefaultAddonTvInfo.png" },
70 {"xbmc.metadata.scraper.library", ADDON_SCRAPER_LIBRARY, 0, "" },
71 {"xbmc.ui.screensaver", ADDON_SCREENSAVER, 24008, "DefaultAddonScreensaver.png" },
72 {"xbmc.player.musicviz", ADDON_VIZ, 24010, "DefaultAddonVisualization.png" },
73 {"visualization-library", ADDON_VIZ_LIBRARY, 0, "" },
74 {"xbmc.python.pluginsource", ADDON_PLUGIN, 24005, "" },
75 {"xbmc.python.script", ADDON_SCRIPT, 24009, "" },
76 {"xbmc.python.weather", ADDON_SCRIPT_WEATHER, 24027, "DefaultAddonWeather.png" },
77 {"xbmc.python.subtitles", ADDON_SCRIPT_SUBTITLES, 24012, "DefaultAddonSubtitles.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.gui.skin", ADDON_SKIN, 166, "DefaultAddonSkin.png" },
82 {"xbmc.gui.webinterface", ADDON_WEB_INTERFACE, 199, "DefaultAddonWebSkin.png" },
83 {"xbmc.addon.repository", ADDON_REPOSITORY, 24011, "DefaultAddonRepository.png" },
84 {"xbmc.pvrclient", ADDON_PVRDLL, 24019, "DefaultAddonPVRClient.png" },
85 {"xbmc.addon.video", ADDON_VIDEO, 1037, "DefaultAddonVideo.png" },
86 {"xbmc.addon.audio", ADDON_AUDIO, 1038, "DefaultAddonMusic.png" },
87 {"xbmc.addon.image", ADDON_IMAGE, 1039, "DefaultAddonPicture.png" },
88 {"xbmc.addon.executable", ADDON_EXECUTABLE, 1043, "DefaultAddonProgram.png" },
89 {"xbmc.service", ADDON_SERVICE, 24018, "DefaultAddonService.png" }};
91 const CStdString TranslateType(const ADDON::TYPE &type, bool pretty/*=false*/)
93 for (unsigned int index=0; index < sizeof(types)/sizeof(types[0]); ++index)
95 const TypeMapping &map = types[index];
98 if (pretty && map.pretty)
99 return g_localizeStrings.Get(map.pretty);
107 TYPE TranslateType(const CStdString &string)
109 for (unsigned int index=0; index < sizeof(types)/sizeof(types[0]); ++index)
111 const TypeMapping &map = types[index];
112 if (string.Equals(map.name))
115 return ADDON_UNKNOWN;
118 const CStdString GetIcon(const ADDON::TYPE& type)
120 for (unsigned int index=0; index < sizeof(types)/sizeof(types[0]); ++index)
122 const TypeMapping &map = types[index];
123 if (type == map.type)
129 #define EMPTY_IF(x,y) \
131 CStdString fan=CAddonMgr::Get().GetExtValue(metadata->configuration, x); \
132 if (fan.Equals("true")) \
136 AddonProps::AddonProps(const cp_extension_t *ext)
137 : id(ext->plugin->identifier)
138 , version(ext->plugin->version)
139 , minversion(ext->plugin->abi_bw_compatibility)
140 , name(ext->plugin->name)
141 , path(ext->plugin->plugin_path)
142 , author(ext->plugin->provider_name)
145 if (ext->ext_point_id)
146 type = TranslateType(ext->ext_point_id);
149 fanart = URIUtils::AddFileToFolder(path, "fanart.jpg");
150 changelog = URIUtils::AddFileToFolder(path, "changelog.txt");
151 // Grab more detail from the props...
152 const cp_extension_t *metadata = CAddonMgr::Get().GetExtension(ext->plugin, "xbmc.addon.metadata");
155 summary = CAddonMgr::Get().GetTranslatedString(metadata->configuration, "summary");
156 description = CAddonMgr::Get().GetTranslatedString(metadata->configuration, "description");
157 disclaimer = CAddonMgr::Get().GetTranslatedString(metadata->configuration, "disclaimer");
158 license = CAddonMgr::Get().GetExtValue(metadata->configuration, "license");
160 language = CAddonMgr::Get().GetExtValue(metadata->configuration, "language");
161 if (!language.IsEmpty())
162 extrainfo.insert(make_pair("language",language));
163 broken = CAddonMgr::Get().GetExtValue(metadata->configuration, "broken");
164 EMPTY_IF("nofanart",fanart)
165 EMPTY_IF("noicon",icon)
166 EMPTY_IF("nochangelog",changelog)
168 BuildDependencies(ext->plugin);
171 AddonProps::AddonProps(const cp_plugin_info_t *plugin)
172 : id(plugin->identifier)
173 , version(plugin->version)
174 , minversion(plugin->abi_bw_compatibility)
176 , path(plugin->plugin_path)
177 , author(plugin->provider_name)
180 BuildDependencies(plugin);
183 void AddonProps::Serialize(CVariant &variant) const
185 variant["addonid"] = id;
186 variant["type"] = TranslateType(type);
187 variant["version"] = version.c_str();
188 variant["minversion"] = minversion.c_str();
189 variant["name"] = name;
190 variant["parent"] = parent;
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)
253 , m_parent(AddonPtr())
256 Props().libname = m_strLibName;
258 m_userSettingsPath = URIUtils::AddFileToFolder(Profile(), "settings.xml");
260 m_hasSettings = true;
261 m_hasStrings = false;
262 m_checkedStrings = false;
263 m_settingsLoaded = false;
264 m_userSettingsLoaded = false;
267 CAddon::CAddon(const cp_plugin_info_t *plugin)
269 , m_parent(AddonPtr())
272 m_hasSettings = false;
273 m_hasStrings = false;
274 m_checkedStrings = true;
275 m_settingsLoaded = false;
276 m_userSettingsLoaded = false;
279 CAddon::CAddon(const AddonProps &props)
281 , m_parent(AddonPtr())
283 if (props.libname.IsEmpty()) BuildLibName();
284 else m_strLibName = props.libname;
286 m_userSettingsPath = URIUtils::AddFileToFolder(Profile(), "settings.xml");
288 m_hasSettings = true;
289 m_hasStrings = false;
290 m_checkedStrings = false;
291 m_settingsLoaded = false;
292 m_userSettingsLoaded = false;
295 CAddon::CAddon(const CAddon &rhs, const AddonPtr &parent)
296 : m_props(rhs.Props())
299 m_settings = rhs.m_settings;
300 m_addonXmlDoc = rhs.m_addonXmlDoc;
301 m_settingsLoaded = rhs.m_settingsLoaded;
302 m_userSettingsLoaded = rhs.m_userSettingsLoaded;
303 m_hasSettings = rhs.m_hasSettings;
305 m_userSettingsPath = URIUtils::AddFileToFolder(Profile(), "settings.xml");
306 m_strLibName = rhs.m_strLibName;
307 m_enabled = rhs.Enabled();
308 m_hasStrings = false;
309 m_checkedStrings = false;
312 AddonPtr CAddon::Clone(const AddonPtr &self) const
314 return AddonPtr(new CAddon(*this, self));
317 bool CAddon::MeetsVersion(const AddonVersion &version) const
319 // if the addon is one of xbmc's extension point definitions (addonid starts with "xbmc.")
320 // and the minversion is "0.0.0" i.e. no <backwards-compatibility> tag has been specified
321 // we need to assume that the current version is not backwards-compatible and therefore check against the actual version
322 if (StringUtils::StartsWith(m_props.id, "xbmc.") &&
323 (strlen(m_props.minversion.c_str()) == 0 || StringUtils::EqualsNoCase(m_props.minversion.c_str(), "0.0.0")))
324 return m_props.version == version;
326 return m_props.minversion <= version && version <= m_props.version;
329 //TODO platform/path crap should be negotiated between the addon and
330 // the handler for it's type
331 void CAddon::BuildLibName(const cp_extension_t *extension)
335 m_strLibName = "default";
337 switch (m_props.type)
339 case ADDON_SCRAPER_ALBUMS:
340 case ADDON_SCRAPER_ARTISTS:
341 case ADDON_SCRAPER_MOVIES:
342 case ADDON_SCRAPER_MUSICVIDEOS:
343 case ADDON_SCRAPER_TVSHOWS:
344 case ADDON_SCRAPER_LIBRARY:
345 ext = ADDON_SCRAPER_EXT;
347 case ADDON_SCREENSAVER:
348 ext = ADDON_SCREENSAVER_EXT;
351 m_strLibName = "skin.xml";
357 ext = ADDON_PVRDLL_EXT;
360 case ADDON_SCRIPT_LIBRARY:
361 case ADDON_SCRIPT_LYRICS:
362 case ADDON_SCRIPT_WEATHER:
363 case ADDON_SCRIPT_SUBTITLES:
366 ext = ADDON_PYTHON_EXT;
369 m_strLibName.clear();
372 // extensions are returned as *.ext
373 // so remove the asterisk
375 m_strLibName.append(ext);
379 switch (m_props.type)
381 case ADDON_SCREENSAVER:
383 case ADDON_SCRIPT_LIBRARY:
384 case ADDON_SCRIPT_LYRICS:
385 case ADDON_SCRIPT_WEATHER:
386 case ADDON_SCRIPT_SUBTITLES:
387 case ADDON_SCRIPT_MODULE:
388 case ADDON_SCRAPER_ALBUMS:
389 case ADDON_SCRAPER_ARTISTS:
390 case ADDON_SCRAPER_MOVIES:
391 case ADDON_SCRAPER_MUSICVIDEOS:
392 case ADDON_SCRAPER_TVSHOWS:
393 case ADDON_SCRAPER_LIBRARY:
397 case ADDON_REPOSITORY:
399 CStdString temp = CAddonMgr::Get().GetExtValue(extension->configuration, "@library");
404 m_strLibName.clear();
411 * Language File Handling
413 bool CAddon::LoadStrings()
415 // Path where the language strings reside
416 CStdString chosenPath = URIUtils::AddFileToFolder(m_props.path, "resources/language/");
418 m_hasStrings = m_strings.Load(chosenPath, CSettings::Get().GetString("locale.language"));
419 return m_checkedStrings = true;
422 void CAddon::ClearStrings()
424 // Unload temporary language strings
426 m_hasStrings = false;
429 CStdString CAddon::GetString(uint32_t id)
431 if (!m_hasStrings && ! m_checkedStrings && !LoadStrings())
434 return m_strings.Get(id);
440 bool CAddon::HasSettings()
442 return LoadSettings();
445 bool CAddon::LoadSettings(bool bForce /* = false*/)
447 if (m_settingsLoaded && !bForce)
451 CStdString addonFileName = URIUtils::AddFileToFolder(m_props.path, "resources/settings.xml");
453 if (!m_addonXmlDoc.LoadFile(addonFileName))
455 if (CFile::Exists(addonFileName))
456 CLog::Log(LOGERROR, "Unable to load: %s, Line %d\n%s", addonFileName.c_str(), m_addonXmlDoc.ErrorRow(), m_addonXmlDoc.ErrorDesc());
457 m_hasSettings = false;
461 // Make sure that the addon XML has the settings element
462 TiXmlElement *setting = m_addonXmlDoc.RootElement();
463 if (!setting || strcmpi(setting->Value(), "settings") != 0)
465 CLog::Log(LOGERROR, "Error loading Settings %s: cannot find root element 'settings'", addonFileName.c_str());
468 SettingsFromXML(m_addonXmlDoc, true);
470 m_settingsLoaded = true;
474 bool CAddon::HasUserSettings()
479 return m_userSettingsLoaded;
482 bool CAddon::ReloadSettings()
484 return LoadSettings(true);
487 bool CAddon::LoadUserSettings()
489 m_userSettingsLoaded = false;
491 if (doc.LoadFile(m_userSettingsPath))
492 m_userSettingsLoaded = SettingsFromXML(doc);
493 return m_userSettingsLoaded;
496 void CAddon::SaveSettings(void)
498 if (!m_settings.size())
499 return; // no settings to save
501 // break down the path into directories
502 CStdString strRoot, strAddon;
503 URIUtils::GetDirectory(m_userSettingsPath, strAddon);
504 URIUtils::RemoveSlashAtEnd(strAddon);
505 URIUtils::GetDirectory(strAddon, strRoot);
506 URIUtils::RemoveSlashAtEnd(strRoot);
508 // create the individual folders
509 if (!CDirectory::Exists(strRoot))
510 CDirectory::Create(strRoot);
511 if (!CDirectory::Exists(strAddon))
512 CDirectory::Create(strAddon);
514 // create the XML file
517 doc.SaveFile(m_userSettingsPath);
518 m_userSettingsLoaded = true;
520 CAddonMgr::Get().ReloadSettings(ID());//push the settings changes to the running addon instance
522 g_pythonParser.OnSettingsChanged(ID());
526 CStdString CAddon::GetSetting(const CStdString& key)
529 return ""; // no settings available
531 map<CStdString, CStdString>::const_iterator i = m_settings.find(key);
532 if (i != m_settings.end())
537 void CAddon::UpdateSetting(const CStdString& key, const CStdString& value)
540 if (key.empty()) return;
541 m_settings[key] = value;
544 bool CAddon::SettingsFromXML(const CXBMCTinyXML &doc, bool loadDefaults /*=false */)
546 if (!doc.RootElement())
552 const TiXmlElement* category = doc.RootElement()->FirstChildElement("category");
554 category = doc.RootElement();
556 bool foundSetting = false;
559 const TiXmlElement *setting = category->FirstChildElement("setting");
562 const char *id = setting->Attribute("id");
563 const char *value = setting->Attribute(loadDefaults ? "default" : "value");
566 m_settings[id] = value;
569 setting = setting->NextSiblingElement("setting");
571 category = category->NextSiblingElement("category");
576 void CAddon::SettingsToXML(CXBMCTinyXML &doc) const
578 TiXmlElement node("settings");
579 doc.InsertEndChild(node);
580 for (map<CStdString, CStdString>::const_iterator i = m_settings.begin(); i != m_settings.end(); ++i)
582 TiXmlElement nodeSetting("setting");
583 nodeSetting.SetAttribute("id", i->first.c_str());
584 nodeSetting.SetAttribute("value", i->second.c_str());
585 doc.RootElement()->InsertEndChild(nodeSetting);
587 doc.SaveFile(m_userSettingsPath);
590 TiXmlElement* CAddon::GetSettingsXML()
592 return m_addonXmlDoc.RootElement();
595 void CAddon::BuildProfilePath()
597 m_profile.Format("special://profile/addon_data/%s/", ID().c_str());
600 const CStdString CAddon::Icon() const
602 if (CURL::IsFullPath(m_props.icon))
604 return URIUtils::AddFileToFolder(m_props.path, m_props.icon);
607 const CStdString CAddon::LibPath() const
609 return URIUtils::AddFileToFolder(m_props.path, m_strLibName);
617 CAddonLibrary::CAddonLibrary(const cp_extension_t *ext)
619 , m_addonType(SetAddonType())
623 CAddonLibrary::CAddonLibrary(const AddonProps& props)
625 , m_addonType(SetAddonType())
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 */