Add #include guards for HAS_PYTHON
[vuplus_xbmc] / xbmc / addons / Addon.cpp
1 /*
2  *      Copyright (C) 2005-2013 Team XBMC
3  *      http://www.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 #ifdef HAS_PYTHON
27 #include "interfaces/python/XBPython.h"
28 #endif
29 #if defined(TARGET_DARWIN)
30 #include "../osx/OSXGNUReplacements.h"
31 #endif
32 #ifdef TARGET_FREEBSD
33 #include "freebsd/FreeBSDGNUReplacements.h"
34 #endif
35 #include "utils/log.h"
36 #include "utils/StringUtils.h"
37 #include "utils/URIUtils.h"
38 #include "URL.h"
39 #include <vector>
40 #include <string.h>
41 #include <ostream>
42
43 using XFILE::CDirectory;
44 using XFILE::CFile;
45 using namespace std;
46
47 namespace ADDON
48 {
49
50 /**
51  * helper functions 
52  *
53  */
54
55 typedef struct
56 {
57   const char* name;
58   TYPE        type;
59   int         pretty;
60   const char* icon;
61 } TypeMapping;
62
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" }};
90
91 const CStdString TranslateType(const ADDON::TYPE &type, bool pretty/*=false*/)
92 {
93   for (unsigned int index=0; index < sizeof(types)/sizeof(types[0]); ++index)
94   {
95     const TypeMapping &map = types[index];
96     if (type == map.type)
97     {
98       if (pretty && map.pretty)
99         return g_localizeStrings.Get(map.pretty);
100       else
101         return map.name;
102     }
103   }
104   return "";
105 }
106
107 TYPE TranslateType(const CStdString &string)
108 {
109   for (unsigned int index=0; index < sizeof(types)/sizeof(types[0]); ++index)
110   {
111     const TypeMapping &map = types[index];
112     if (string.Equals(map.name))
113       return map.type;
114   }
115   return ADDON_UNKNOWN;
116 }
117
118 const CStdString GetIcon(const ADDON::TYPE& type)
119 {
120   for (unsigned int index=0; index < sizeof(types)/sizeof(types[0]); ++index)
121   {
122     const TypeMapping &map = types[index];
123     if (type == map.type)
124       return map.icon;
125   }
126   return "";
127 }
128
129 #define EMPTY_IF(x,y) \
130   { \
131     CStdString fan=CAddonMgr::Get().GetExtValue(metadata->configuration, x); \
132     if (fan.Equals("true")) \
133       y.Empty(); \
134   }
135
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)
143   , stars(0)
144 {
145   if (ext->ext_point_id)
146     type = TranslateType(ext->ext_point_id);
147
148   icon = "icon.png";
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");
153   if (metadata)
154   {
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");
159     CStdString language;
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)
167   }
168   BuildDependencies(ext->plugin);
169 }
170
171 AddonProps::AddonProps(const cp_plugin_info_t *plugin)
172   : id(plugin->identifier)
173   , version(plugin->version)
174   , minversion(plugin->abi_bw_compatibility)
175   , name(plugin->name)
176   , path(plugin->plugin_path)
177   , author(plugin->provider_name)
178   , stars(0)
179 {
180   BuildDependencies(plugin);
181 }
182
183 void AddonProps::Serialize(CVariant &variant) const
184 {
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;
198
199   if (CURL::IsFullPath(icon))
200     variant["icon"] = icon;
201   else
202     variant["icon"] = URIUtils::AddFileToFolder(path, icon);
203
204   variant["thumbnail"] = variant["icon"];
205   variant["disclaimer"] = disclaimer;
206   variant["changelog"] = changelog;
207
208   if (CURL::IsFullPath(fanart))
209     variant["fanart"] = fanart;
210   else
211     variant["fanart"] = URIUtils::AddFileToFolder(path, fanart);
212
213   variant["dependencies"] = CVariant(CVariant::VariantTypeArray);
214   for (ADDONDEPS::const_iterator it = dependencies.begin(); it != dependencies.end(); it++)
215   {
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);
221   }
222   if (broken.empty())
223     variant["broken"] = false;
224   else
225     variant["broken"] = broken;
226   variant["extrainfo"] = CVariant(CVariant::VariantTypeArray);
227   for (InfoMap::const_iterator it = extrainfo.begin(); it != extrainfo.end(); it++)
228   {
229     CVariant info(CVariant::VariantTypeObject);
230     info["key"] = it->first;
231     info["value"] = it->second;
232     variant["extrainfo"].push_back(info);
233   }
234   variant["rating"] = stars;
235 }
236
237 void AddonProps::BuildDependencies(const cp_plugin_info_t *plugin)
238 {
239   if (!plugin)
240     return;
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)));
244 }
245
246 /**
247  * CAddon
248  *
249  */
250
251 CAddon::CAddon(const cp_extension_t *ext)
252   : m_props(ext)
253   , m_parent(AddonPtr())
254 {
255   BuildLibName(ext);
256   Props().libname = m_strLibName;
257   BuildProfilePath();
258   m_userSettingsPath = URIUtils::AddFileToFolder(Profile(), "settings.xml");
259   m_enabled = true;
260   m_hasSettings = true;
261   m_hasStrings = false;
262   m_checkedStrings = false;
263   m_settingsLoaded = false;
264   m_userSettingsLoaded = false;
265 }
266
267 CAddon::CAddon(const cp_plugin_info_t *plugin)
268   : m_props(plugin)
269   , m_parent(AddonPtr())
270 {
271   m_enabled = true;
272   m_hasSettings = false;
273   m_hasStrings = false;
274   m_checkedStrings = true;
275   m_settingsLoaded = false;
276   m_userSettingsLoaded = false;
277 }
278
279 CAddon::CAddon(const AddonProps &props)
280   : m_props(props)
281   , m_parent(AddonPtr())
282 {
283   if (props.libname.IsEmpty()) BuildLibName();
284   else m_strLibName = props.libname;
285   BuildProfilePath();
286   m_userSettingsPath = URIUtils::AddFileToFolder(Profile(), "settings.xml");
287   m_enabled = true;
288   m_hasSettings = true;
289   m_hasStrings = false;
290   m_checkedStrings = false;
291   m_settingsLoaded = false;
292   m_userSettingsLoaded = false;
293 }
294
295 CAddon::CAddon(const CAddon &rhs, const AddonPtr &parent)
296   : m_props(rhs.Props())
297   , m_parent(parent)
298 {
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;
304   BuildProfilePath();
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;
310 }
311
312 AddonPtr CAddon::Clone(const AddonPtr &self) const
313 {
314   return AddonPtr(new CAddon(*this, self));
315 }
316
317 bool CAddon::MeetsVersion(const AddonVersion &version) const
318 {
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;
325
326   return m_props.minversion <= version && version <= m_props.version;
327 }
328
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)
332 {
333   if (!extension)
334   {
335     m_strLibName = "default";
336     CStdString ext;
337     switch (m_props.type)
338     {
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;
346       break;
347     case ADDON_SCREENSAVER:
348       ext = ADDON_SCREENSAVER_EXT;
349       break;
350     case ADDON_SKIN:
351       m_strLibName = "skin.xml";
352       return;
353     case ADDON_VIZ:
354       ext = ADDON_VIS_EXT;
355       break;
356     case ADDON_PVRDLL:
357       ext = ADDON_PVRDLL_EXT;
358       break;
359     case ADDON_SCRIPT:
360     case ADDON_SCRIPT_LIBRARY:
361     case ADDON_SCRIPT_LYRICS:
362     case ADDON_SCRIPT_WEATHER:
363     case ADDON_SCRIPT_SUBTITLES:
364     case ADDON_PLUGIN:
365     case ADDON_SERVICE:
366       ext = ADDON_PYTHON_EXT;
367       break;
368     default:
369       m_strLibName.clear();
370       return;
371     }
372     // extensions are returned as *.ext
373     // so remove the asterisk
374     ext.erase(0,1);
375     m_strLibName.append(ext);
376   }
377   else
378   {
379     switch (m_props.type)
380     {
381       case ADDON_SCREENSAVER:
382       case ADDON_SCRIPT:
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:
394       case ADDON_PVRDLL:
395       case ADDON_PLUGIN:
396       case ADDON_SERVICE:
397       case ADDON_REPOSITORY:
398         {
399           CStdString temp = CAddonMgr::Get().GetExtValue(extension->configuration, "@library");
400           m_strLibName = temp;
401         }
402         break;
403       default:
404         m_strLibName.clear();
405         break;
406     }
407   }
408 }
409
410 /**
411  * Language File Handling
412  */
413 bool CAddon::LoadStrings()
414 {
415   // Path where the language strings reside
416   CStdString chosenPath = URIUtils::AddFileToFolder(m_props.path, "resources/language/");
417
418   m_hasStrings = m_strings.Load(chosenPath, CSettings::Get().GetString("locale.language"));
419   return m_checkedStrings = true;
420 }
421
422 void CAddon::ClearStrings()
423 {
424   // Unload temporary language strings
425   m_strings.Clear();
426   m_hasStrings = false;
427 }
428
429 CStdString CAddon::GetString(uint32_t id)
430 {
431   if (!m_hasStrings && ! m_checkedStrings && !LoadStrings())
432      return "";
433
434   return m_strings.Get(id);
435 }
436
437 /**
438  * Settings Handling
439  */
440 bool CAddon::HasSettings()
441 {
442   return LoadSettings();
443 }
444
445 bool CAddon::LoadSettings(bool bForce /* = false*/)
446 {
447   if (m_settingsLoaded && !bForce)
448     return true;
449   if (!m_hasSettings)
450     return false;
451   CStdString addonFileName = URIUtils::AddFileToFolder(m_props.path, "resources/settings.xml");
452
453   if (!m_addonXmlDoc.LoadFile(addonFileName))
454   {
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;
458     return false;
459   }
460
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)
464   {
465     CLog::Log(LOGERROR, "Error loading Settings %s: cannot find root element 'settings'", addonFileName.c_str());
466     return false;
467   }
468   SettingsFromXML(m_addonXmlDoc, true);
469   LoadUserSettings();
470   m_settingsLoaded = true;
471   return true;
472 }
473
474 bool CAddon::HasUserSettings()
475 {
476   if (!LoadSettings())
477     return false;
478
479   return m_userSettingsLoaded;
480 }
481
482 bool CAddon::ReloadSettings()
483 {
484   return LoadSettings(true);
485 }
486
487 bool CAddon::LoadUserSettings()
488 {
489   m_userSettingsLoaded = false;
490   CXBMCTinyXML doc;
491   if (doc.LoadFile(m_userSettingsPath))
492     m_userSettingsLoaded = SettingsFromXML(doc);
493   return m_userSettingsLoaded;
494 }
495
496 void CAddon::SaveSettings(void)
497 {
498   if (!m_settings.size())
499     return; // no settings to save
500
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);
507
508   // create the individual folders
509   if (!CDirectory::Exists(strRoot))
510     CDirectory::Create(strRoot);
511   if (!CDirectory::Exists(strAddon))
512     CDirectory::Create(strAddon);
513
514   // create the XML file
515   CXBMCTinyXML doc;
516   SettingsToXML(doc);
517   doc.SaveFile(m_userSettingsPath);
518   m_userSettingsLoaded = true;
519   
520   CAddonMgr::Get().ReloadSettings(ID());//push the settings changes to the running addon instance
521 #ifdef HAS_PYTHON
522   g_pythonParser.OnSettingsChanged(ID());
523 #endif
524 }
525
526 CStdString CAddon::GetSetting(const CStdString& key)
527 {
528   if (!LoadSettings())
529     return ""; // no settings available
530
531   map<CStdString, CStdString>::const_iterator i = m_settings.find(key);
532   if (i != m_settings.end())
533     return i->second;
534   return "";
535 }
536
537 void CAddon::UpdateSetting(const CStdString& key, const CStdString& value)
538 {
539   LoadSettings();
540   if (key.empty()) return;
541   m_settings[key] = value;
542 }
543
544 bool CAddon::SettingsFromXML(const CXBMCTinyXML &doc, bool loadDefaults /*=false */)
545 {
546   if (!doc.RootElement())
547     return false;
548
549   if (loadDefaults)
550     m_settings.clear();
551
552   const TiXmlElement* category = doc.RootElement()->FirstChildElement("category");
553   if (!category)
554     category = doc.RootElement();
555
556   bool foundSetting = false;
557   while (category)
558   {
559     const TiXmlElement *setting = category->FirstChildElement("setting");
560     while (setting)
561     {
562       const char *id = setting->Attribute("id");
563       const char *value = setting->Attribute(loadDefaults ? "default" : "value");
564       if (id && value)
565       {
566         m_settings[id] = value;
567         foundSetting = true;
568       }
569       setting = setting->NextSiblingElement("setting");
570     }
571     category = category->NextSiblingElement("category");
572   }
573   return foundSetting;
574 }
575
576 void CAddon::SettingsToXML(CXBMCTinyXML &doc) const
577 {
578   TiXmlElement node("settings");
579   doc.InsertEndChild(node);
580   for (map<CStdString, CStdString>::const_iterator i = m_settings.begin(); i != m_settings.end(); ++i)
581   {
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);
586   }
587   doc.SaveFile(m_userSettingsPath);
588 }
589
590 TiXmlElement* CAddon::GetSettingsXML()
591 {
592   return m_addonXmlDoc.RootElement();
593 }
594
595 void CAddon::BuildProfilePath()
596 {
597   m_profile.Format("special://profile/addon_data/%s/", ID().c_str());
598 }
599
600 const CStdString CAddon::Icon() const
601 {
602   if (CURL::IsFullPath(m_props.icon))
603     return m_props.icon;
604   return URIUtils::AddFileToFolder(m_props.path, m_props.icon);
605 }
606
607 const CStdString CAddon::LibPath() const
608 {
609   return URIUtils::AddFileToFolder(m_props.path, m_strLibName);
610 }
611
612 /**
613  * CAddonLibrary
614  *
615  */
616
617 CAddonLibrary::CAddonLibrary(const cp_extension_t *ext)
618   : CAddon(ext)
619   , m_addonType(SetAddonType())
620 {
621 }
622
623 CAddonLibrary::CAddonLibrary(const AddonProps& props)
624   : CAddon(props)
625   , m_addonType(SetAddonType())
626 {
627 }
628
629 TYPE CAddonLibrary::SetAddonType()
630 {
631   if (Type() == ADDON_VIZ_LIBRARY)
632     return ADDON_VIZ;
633   else
634     return ADDON_UNKNOWN;
635 }
636
637 CStdString GetXbmcApiVersionDependency(ADDON::AddonPtr addon)
638 {
639   CStdString version("1.0");
640   if (addon.get() != NULL)
641   {
642     const ADDON::ADDONDEPS &deps = addon->GetDeps();
643     ADDON::ADDONDEPS::const_iterator it;
644     CStdString key("xbmc.python");
645     it = deps.find(key);
646     if (!(it == deps.end()))
647     {
648       const ADDON::AddonVersion * xbmcApiVersion = &(it->second.first);
649       version = xbmcApiVersion->c_str();
650     }
651   }
652
653   return version;
654 }
655
656
657 } /* namespace ADDON */
658