summaryrefslogtreecommitdiffstats
path: root/xbmc/addons/Addon.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'xbmc/addons/Addon.cpp')
-rw-r--r--xbmc/addons/Addon.cpp653
1 files changed, 653 insertions, 0 deletions
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 @@
1/*
2 * Copyright (C) 2005-2013 Team XBMC
3 * http://xbmc.org
4 *
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)
8 * any later version.
9 *
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.
14 *
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/>.
18 *
19 */
20
21#include "Addon.h"
22#include "AddonManager.h"
23#include "settings/Settings.h"
24#include "filesystem/Directory.h"
25#include "filesystem/File.h"
26#include "system.h"
27#ifdef HAS_PYTHON
28#include "interfaces/python/XBPython.h"
29#endif
30#if defined(TARGET_DARWIN)
31#include "../osx/OSXGNUReplacements.h"
32#endif
33#ifdef TARGET_FREEBSD
34#include "freebsd/FreeBSDGNUReplacements.h"
35#endif
36#include "utils/log.h"
37#include "utils/StringUtils.h"
38#include "utils/URIUtils.h"
39#include "URL.h"
40#include "Util.h"
41#include <vector>
42#include <string.h>
43#include <ostream>
44
45using XFILE::CDirectory;
46using XFILE::CFile;
47using namespace std;
48
49namespace ADDON
50{
51
52/**
53 * helper functions
54 *
55 */
56
57typedef struct
58{
59 const char* name;
60 TYPE type;
61 int pretty;
62 const char* icon;
63} TypeMapping;
64
65static const TypeMapping types[] =
66 {{"unknown", ADDON_UNKNOWN, 0, "" },
67 {"xbmc.metadata.scraper.albums", ADDON_SCRAPER_ALBUMS, 24016, "DefaultAddonAlbumInfo.png" },
68 {"xbmc.metadata.scraper.artists", ADDON_SCRAPER_ARTISTS, 24017, "DefaultAddonArtistInfo.png" },
69 {"xbmc.metadata.scraper.movies", ADDON_SCRAPER_MOVIES, 24007, "DefaultAddonMovieInfo.png" },
70 {"xbmc.metadata.scraper.musicvideos", ADDON_SCRAPER_MUSICVIDEOS, 24015, "DefaultAddonMusicVideoInfo.png" },
71 {"xbmc.metadata.scraper.tvshows", ADDON_SCRAPER_TVSHOWS, 24014, "DefaultAddonTvInfo.png" },
72 {"xbmc.metadata.scraper.library", ADDON_SCRAPER_LIBRARY, 24083, "DefaultAddonInfoLibrary.png" },
73 {"xbmc.ui.screensaver", ADDON_SCREENSAVER, 24008, "DefaultAddonScreensaver.png" },
74 {"xbmc.player.musicviz", ADDON_VIZ, 24010, "DefaultAddonVisualization.png" },
75 {"visualization-library", ADDON_VIZ_LIBRARY, 24084, "" },
76 {"xbmc.python.pluginsource", ADDON_PLUGIN, 24005, "" },
77 {"xbmc.python.script", ADDON_SCRIPT, 24009, "" },
78 {"xbmc.python.weather", ADDON_SCRIPT_WEATHER, 24027, "DefaultAddonWeather.png" },
79 {"xbmc.python.lyrics", ADDON_SCRIPT_LYRICS, 24013, "DefaultAddonLyrics.png" },
80 {"xbmc.python.library", ADDON_SCRIPT_LIBRARY, 24081, "DefaultAddonHelper.png" },
81 {"xbmc.python.module", ADDON_SCRIPT_MODULE, 24082, "DefaultAddonLibrary.png" },
82 {"xbmc.subtitle.module", ADDON_SUBTITLE_MODULE, 24012, "DefaultAddonSubtitles.png" },
83 {"kodi.context.item", ADDON_CONTEXT_ITEM, 24025, "DefaultAddonContextItem.png" },
84 {"xbmc.gui.skin", ADDON_SKIN, 166, "DefaultAddonSkin.png" },
85 {"xbmc.webinterface", ADDON_WEB_INTERFACE, 199, "DefaultAddonWebSkin.png" },
86 {"xbmc.addon.repository", ADDON_REPOSITORY, 24011, "DefaultAddonRepository.png" },
87 {"xbmc.pvrclient", ADDON_PVRDLL, 24019, "DefaultAddonPVRClient.png" },
88 {"xbmc.addon.video", ADDON_VIDEO, 1037, "DefaultAddonVideo.png" },
89 {"xbmc.addon.audio", ADDON_AUDIO, 1038, "DefaultAddonMusic.png" },
90 {"xbmc.addon.image", ADDON_IMAGE, 1039, "DefaultAddonPicture.png" },
91 {"xbmc.addon.executable", ADDON_EXECUTABLE, 1043, "DefaultAddonProgram.png" },
92 {"xbmc.audioencoder", ADDON_AUDIOENCODER, 200, "DefaultAddonAudioEncoder.png" },
93 {"xbmc.service", ADDON_SERVICE, 24018, "DefaultAddonService.png" }};
94
95const std::string TranslateType(const ADDON::TYPE &type, bool pretty/*=false*/)
96{
97 for (unsigned int index=0; index < ARRAY_SIZE(types); ++index)
98 {
99 const TypeMapping &map = types[index];
100 if (type == map.type)
101 {
102 if (pretty && map.pretty)
103 return g_localizeStrings.Get(map.pretty);
104 else
105 return map.name;
106 }
107 }
108 return "";
109}
110
111TYPE TranslateType(const std::string &string)
112{
113 for (unsigned int index=0; index < ARRAY_SIZE(types); ++index)
114 {
115 const TypeMapping &map = types[index];
116 if (string == map.name)
117 return map.type;
118 }
119
120 return ADDON_UNKNOWN;
121}
122
123const std::string GetIcon(const ADDON::TYPE& type)
124{
125 for (unsigned int index=0; index < ARRAY_SIZE(types); ++index)
126 {
127 const TypeMapping &map = types[index];
128 if (type == map.type)
129 return map.icon;
130 }
131 return "";
132}
133
134#define EMPTY_IF(x,y) \
135 { \
136 std::string fan=CAddonMgr::Get().GetExtValue(metadata->configuration, x); \
137 if (fan == "true") \
138 y.clear(); \
139 }
140
141#define SS(x) (x) ? x : ""
142
143AddonProps::AddonProps(const cp_extension_t *ext)
144 : id(SS(ext->plugin->identifier))
145 , version(SS(ext->plugin->version))
146 , minversion(SS(ext->plugin->abi_bw_compatibility))
147 , name(SS(ext->plugin->name))
148 , path(SS(ext->plugin->plugin_path))
149 , author(SS(ext->plugin->provider_name))
150 , stars(0)
151{
152 if (ext->ext_point_id)
153 type = TranslateType(ext->ext_point_id);
154
155 icon = "icon.png";
156 fanart = URIUtils::AddFileToFolder(path, "fanart.jpg");
157 changelog = URIUtils::AddFileToFolder(path, "changelog.txt");
158 // Grab more detail from the props...
159 const cp_extension_t *metadata = CAddonMgr::Get().GetExtension(ext->plugin, "xbmc.addon.metadata"); //<! backword compatibilty
160 if (!metadata)
161 metadata = CAddonMgr::Get().GetExtension(ext->plugin, "kodi.addon.metadata");
162 if (metadata)
163 {
164 summary = CAddonMgr::Get().GetTranslatedString(metadata->configuration, "summary");
165 description = CAddonMgr::Get().GetTranslatedString(metadata->configuration, "description");
166 disclaimer = CAddonMgr::Get().GetTranslatedString(metadata->configuration, "disclaimer");
167 license = CAddonMgr::Get().GetExtValue(metadata->configuration, "license");
168 std::string language;
169 language = CAddonMgr::Get().GetExtValue(metadata->configuration, "language");
170 if (!language.empty())
171 extrainfo.insert(make_pair("language",language));
172 broken = CAddonMgr::Get().GetExtValue(metadata->configuration, "broken");
173 EMPTY_IF("nofanart",fanart)
174 EMPTY_IF("noicon",icon)
175 EMPTY_IF("nochangelog",changelog)
176 }
177 BuildDependencies(ext->plugin);
178}
179
180AddonProps::AddonProps(const cp_plugin_info_t *plugin)
181 : id(SS(plugin->identifier))
182 , type(ADDON_UNKNOWN)
183 , version(SS(plugin->version))
184 , minversion(SS(plugin->abi_bw_compatibility))
185 , name(SS(plugin->name))
186 , path(SS(plugin->plugin_path))
187 , author(SS(plugin->provider_name))
188 , stars(0)
189{
190 BuildDependencies(plugin);
191}
192
193void AddonProps::Serialize(CVariant &variant) const
194{
195 variant["addonid"] = id;
196 variant["type"] = TranslateType(type);
197 variant["version"] = version.asString();
198 variant["minversion"] = minversion.asString();
199 variant["name"] = name;
200 variant["license"] = license;
201 variant["summary"] = summary;
202 variant["description"] = description;
203 variant["path"] = path;
204 variant["libname"] = libname;
205 variant["author"] = author;
206 variant["source"] = source;
207
208 if (CURL::IsFullPath(icon))
209 variant["icon"] = icon;
210 else
211 variant["icon"] = URIUtils::AddFileToFolder(path, icon);
212
213 variant["thumbnail"] = variant["icon"];
214 variant["disclaimer"] = disclaimer;
215 variant["changelog"] = changelog;
216
217 if (CURL::IsFullPath(fanart))
218 variant["fanart"] = fanart;
219 else
220 variant["fanart"] = URIUtils::AddFileToFolder(path, fanart);
221
222 variant["dependencies"] = CVariant(CVariant::VariantTypeArray);
223 for (ADDONDEPS::const_iterator it = dependencies.begin(); it != dependencies.end(); ++it)
224 {
225 CVariant dep(CVariant::VariantTypeObject);
226 dep["addonid"] = it->first;
227 dep["version"] = it->second.first.asString();
228 dep["optional"] = it->second.second;
229 variant["dependencies"].push_back(dep);
230 }
231 if (broken.empty())
232 variant["broken"] = false;
233 else
234 variant["broken"] = broken;
235 variant["extrainfo"] = CVariant(CVariant::VariantTypeArray);
236 for (InfoMap::const_iterator it = extrainfo.begin(); it != extrainfo.end(); ++it)
237 {
238 CVariant info(CVariant::VariantTypeObject);
239 info["key"] = it->first;
240 info["value"] = it->second;
241 variant["extrainfo"].push_back(info);
242 }
243 variant["rating"] = stars;
244}
245
246void AddonProps::BuildDependencies(const cp_plugin_info_t *plugin)
247{
248 if (!plugin)
249 return;
250 for (unsigned int i = 0; i < plugin->num_imports; ++i)
251 dependencies.insert(make_pair(std::string(plugin->imports[i].plugin_id),
252 make_pair(AddonVersion(SS(plugin->imports[i].version)), plugin->imports[i].optional != 0)));
253}
254
255/**
256 * CAddon
257 *
258 */
259
260CAddon::CAddon(const cp_extension_t *ext)
261 : m_props(ext)
262{
263 BuildLibName(ext);
264 Props().libname = m_strLibName;
265 BuildProfilePath();
266 m_userSettingsPath = URIUtils::AddFileToFolder(Profile(), "settings.xml");
267 m_enabled = true;
268 m_hasSettings = true;
269 m_hasStrings = false;
270 m_checkedStrings = false;
271 m_settingsLoaded = false;
272 m_userSettingsLoaded = false;
273}
274
275CAddon::CAddon(const cp_plugin_info_t *plugin)
276 : m_props(plugin)
277{
278 m_enabled = true;
279 m_hasSettings = false;
280 m_hasStrings = false;
281 m_checkedStrings = true;
282 m_settingsLoaded = false;
283 m_userSettingsLoaded = false;
284}
285
286CAddon::CAddon(const AddonProps &props)
287 : m_props(props)
288{
289 if (props.libname.empty()) BuildLibName();
290 else m_strLibName = props.libname;
291 BuildProfilePath();
292 m_userSettingsPath = URIUtils::AddFileToFolder(Profile(), "settings.xml");
293 m_enabled = true;
294 m_hasSettings = true;
295 m_hasStrings = false;
296 m_checkedStrings = false;
297 m_settingsLoaded = false;
298 m_userSettingsLoaded = false;
299}
300
301CAddon::CAddon(const CAddon &rhs)
302 : m_props(rhs.Props()),
303 m_settings(rhs.m_settings)
304{
305 m_addonXmlDoc = rhs.m_addonXmlDoc;
306 m_settingsLoaded = rhs.m_settingsLoaded;
307 m_userSettingsLoaded = rhs.m_userSettingsLoaded;
308 m_hasSettings = rhs.m_hasSettings;
309 BuildProfilePath();
310 m_userSettingsPath = URIUtils::AddFileToFolder(Profile(), "settings.xml");
311 m_strLibName = rhs.m_strLibName;
312 m_enabled = rhs.Enabled();
313 m_hasStrings = false;
314 m_checkedStrings = false;
315}
316
317AddonPtr CAddon::Clone() const
318{
319 return AddonPtr(new CAddon(*this));
320}
321
322bool CAddon::MeetsVersion(const AddonVersion &version) const
323{
324 return m_props.minversion <= version && version <= m_props.version;
325}
326
327//TODO platform/path crap should be negotiated between the addon and
328// the handler for it's type
329void CAddon::BuildLibName(const cp_extension_t *extension)
330{
331 if (!extension)
332 {
333 m_strLibName = "default";
334 std::string ext;
335 switch (m_props.type)
336 {
337 case ADDON_SCRAPER_ALBUMS:
338 case ADDON_SCRAPER_ARTISTS:
339 case ADDON_SCRAPER_MOVIES:
340 case ADDON_SCRAPER_MUSICVIDEOS:
341 case ADDON_SCRAPER_TVSHOWS:
342 case ADDON_SCRAPER_LIBRARY:
343 ext = ADDON_SCRAPER_EXT;
344 break;
345 case ADDON_SCREENSAVER:
346 ext = ADDON_SCREENSAVER_EXT;
347 break;
348 case ADDON_SKIN:
349 m_strLibName = "skin.xml";
350 return;
351 case ADDON_VIZ:
352 ext = ADDON_VIS_EXT;
353 break;
354 case ADDON_PVRDLL:
355 ext = ADDON_PVRDLL_EXT;
356 break;
357 case ADDON_SCRIPT:
358 case ADDON_SCRIPT_LIBRARY:
359 case ADDON_SCRIPT_LYRICS:
360 case ADDON_SCRIPT_WEATHER:
361 case ADDON_SUBTITLE_MODULE:
362 case ADDON_PLUGIN:
363 case ADDON_SERVICE:
364 case ADDON_CONTEXT_ITEM:
365 ext = ADDON_PYTHON_EXT;
366 break;
367 default:
368 m_strLibName.clear();
369 return;
370 }
371 // extensions are returned as *.ext
372 // so remove the asterisk
373 ext.erase(0,1);
374 m_strLibName.append(ext);
375 }
376 else
377 {
378 switch (m_props.type)
379 {
380 case ADDON_SCREENSAVER:
381 case ADDON_SCRIPT:
382 case ADDON_SCRIPT_LIBRARY:
383 case ADDON_SCRIPT_LYRICS:
384 case ADDON_SCRIPT_WEATHER:
385 case ADDON_SCRIPT_MODULE:
386 case ADDON_SUBTITLE_MODULE:
387 case ADDON_SCRAPER_ALBUMS:
388 case ADDON_SCRAPER_ARTISTS:
389 case ADDON_SCRAPER_MOVIES:
390 case ADDON_SCRAPER_MUSICVIDEOS:
391 case ADDON_SCRAPER_TVSHOWS:
392 case ADDON_SCRAPER_LIBRARY:
393 case ADDON_PVRDLL:
394 case ADDON_PLUGIN:
395 case ADDON_WEB_INTERFACE:
396 case ADDON_SERVICE:
397 case ADDON_REPOSITORY:
398 case ADDON_AUDIOENCODER:
399 case ADDON_CONTEXT_ITEM:
400 {
401 std::string temp = CAddonMgr::Get().GetExtValue(extension->configuration, "@library");
402 m_strLibName = temp;
403 }
404 break;
405 default:
406 m_strLibName.clear();
407 break;
408 }
409 }
410}
411
412/**
413 * Language File Handling
414 */
415bool CAddon::LoadStrings()
416{
417 // Path where the language strings reside
418 std::string chosenPath = URIUtils::AddFileToFolder(m_props.path, "resources/language/");
419
420 m_hasStrings = m_strings.Load(chosenPath, CSettings::Get().GetString("locale.language"));
421 return m_checkedStrings = true;
422}
423
424void CAddon::ClearStrings()
425{
426 // Unload temporary language strings
427 m_strings.Clear();
428 m_hasStrings = false;
429}
430
431std::string CAddon::GetString(uint32_t id)
432{
433 if (!m_hasStrings && ! m_checkedStrings && !LoadStrings())
434 return "";
435
436 return m_strings.Get(id);
437}
438
439/**
440 * Settings Handling
441 */
442bool CAddon::HasSettings()
443{
444 return LoadSettings();
445}
446
447bool CAddon::LoadSettings(bool bForce /* = false*/)
448{
449 if (m_settingsLoaded && !bForce)
450 return true;
451 if (!m_hasSettings)
452 return false;
453 std::string addonFileName = URIUtils::AddFileToFolder(m_props.path, "resources/settings.xml");
454
455 if (!m_addonXmlDoc.LoadFile(addonFileName))
456 {
457 if (CFile::Exists(addonFileName))
458 CLog::Log(LOGERROR, "Unable to load: %s, Line %d\n%s", addonFileName.c_str(), m_addonXmlDoc.ErrorRow(), m_addonXmlDoc.ErrorDesc());
459 m_hasSettings = false;
460 return false;
461 }
462
463 // Make sure that the addon XML has the settings element
464 TiXmlElement *setting = m_addonXmlDoc.RootElement();
465 if (!setting || strcmpi(setting->Value(), "settings") != 0)
466 {
467 CLog::Log(LOGERROR, "Error loading Settings %s: cannot find root element 'settings'", addonFileName.c_str());
468 return false;
469 }
470 SettingsFromXML(m_addonXmlDoc, true);
471 LoadUserSettings();
472 m_settingsLoaded = true;
473 return true;
474}
475
476bool CAddon::HasUserSettings()
477{
478 if (!LoadSettings())
479 return false;
480
481 return m_userSettingsLoaded;
482}
483
484bool CAddon::ReloadSettings()
485{
486 return LoadSettings(true);
487}
488
489bool CAddon::LoadUserSettings()
490{
491 m_userSettingsLoaded = false;
492 CXBMCTinyXML doc;
493 if (doc.LoadFile(m_userSettingsPath))
494 m_userSettingsLoaded = SettingsFromXML(doc);
495 return m_userSettingsLoaded;
496}
497
498void CAddon::SaveSettings(void)
499{
500 if (m_settings.empty())
501 return; // no settings to save
502
503 // break down the path into directories
504 std::string strAddon = URIUtils::GetDirectory(m_userSettingsPath);
505 URIUtils::RemoveSlashAtEnd(strAddon);
506 std::string strRoot = URIUtils::GetDirectory(strAddon);
507 URIUtils::RemoveSlashAtEnd(strRoot);
508
509 // create the individual folders
510 if (!CDirectory::Exists(strRoot))
511 CDirectory::Create(strRoot);
512 if (!CDirectory::Exists(strAddon))
513 CDirectory::Create(strAddon);
514
515 // create the XML file
516 CXBMCTinyXML doc;
517 SettingsToXML(doc);
518 doc.SaveFile(m_userSettingsPath);
519 m_userSettingsLoaded = true;
520
521 CAddonMgr::Get().ReloadSettings(ID());//push the settings changes to the running addon instance
522#ifdef HAS_PYTHON
523 g_pythonParser.OnSettingsChanged(ID());
524#endif
525}
526
527std::string CAddon::GetSetting(const std::string& key)
528{
529 if (!LoadSettings())
530 return ""; // no settings available
531
532 map<std::string, std::string>::const_iterator i = m_settings.find(key);
533 if (i != m_settings.end())
534 return i->second;
535 return "";
536}
537
538void CAddon::UpdateSetting(const std::string& key, const std::string& value)
539{
540 LoadSettings();
541 if (key.empty()) return;
542 m_settings[key] = value;
543}
544
545bool CAddon::SettingsFromXML(const CXBMCTinyXML &doc, bool loadDefaults /*=false */)
546{
547 if (!doc.RootElement())
548 return false;
549
550 if (loadDefaults)
551 m_settings.clear();
552
553 const TiXmlElement* category = doc.RootElement()->FirstChildElement("category");
554 if (!category)
555 category = doc.RootElement();
556
557 bool foundSetting = false;
558 while (category)
559 {
560 const TiXmlElement *setting = category->FirstChildElement("setting");
561 while (setting)
562 {
563 const char *id = setting->Attribute("id");
564 const char *value = setting->Attribute(loadDefaults ? "default" : "value");
565 if (id && value)
566 {
567 m_settings[id] = value;
568 foundSetting = true;
569 }
570 setting = setting->NextSiblingElement("setting");
571 }
572 category = category->NextSiblingElement("category");
573 }
574 return foundSetting;
575}
576
577void CAddon::SettingsToXML(CXBMCTinyXML &doc) const
578{
579 TiXmlElement node("settings");
580 doc.InsertEndChild(node);
581 for (map<std::string, std::string>::const_iterator i = m_settings.begin(); i != m_settings.end(); ++i)
582 {
583 TiXmlElement nodeSetting("setting");
584 nodeSetting.SetAttribute("id", i->first.c_str());
585 nodeSetting.SetAttribute("value", i->second.c_str());
586 doc.RootElement()->InsertEndChild(nodeSetting);
587 }
588 doc.SaveFile(m_userSettingsPath);
589}
590
591TiXmlElement* CAddon::GetSettingsXML()
592{
593 return m_addonXmlDoc.RootElement();
594}
595
596void CAddon::BuildProfilePath()
597{
598 m_profile = StringUtils::Format("special://profile/addon_data/%s/", ID().c_str());
599}
600
601const std::string CAddon::Icon() const
602{
603 if (CURL::IsFullPath(m_props.icon))
604 return m_props.icon;
605 return URIUtils::AddFileToFolder(m_props.path, m_props.icon);
606}
607
608const std::string CAddon::LibPath() const
609{
610 return URIUtils::AddFileToFolder(m_props.path, m_strLibName);
611}
612
613AddonVersion CAddon::GetDependencyVersion(const std::string &dependencyID) const
614{
615 const ADDON::ADDONDEPS &deps = GetDeps();
616 ADDONDEPS::const_iterator it = deps.find(dependencyID);
617 if (it != deps.end())
618 return it->second.first;
619 return AddonVersion("0.0.0");
620}
621
622/**
623 * CAddonLibrary
624 *
625 */
626
627CAddonLibrary::CAddonLibrary(const cp_extension_t *ext)
628 : CAddon(ext)
629 , m_addonType(SetAddonType())
630{
631}
632
633CAddonLibrary::CAddonLibrary(const AddonProps& props)
634 : CAddon(props)
635 , m_addonType(SetAddonType())
636{
637}
638
639AddonPtr CAddonLibrary::Clone() const
640{
641 return AddonPtr(new CAddonLibrary(*this));
642}
643
644TYPE CAddonLibrary::SetAddonType()
645{
646 if (Type() == ADDON_VIZ_LIBRARY)
647 return ADDON_VIZ;
648 else
649 return ADDON_UNKNOWN;
650}
651
652} /* namespace ADDON */
653