[release] version bump to 13.0 beta1
[vuplus_xbmc] / xbmc / addons / Addon.cpp
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 <vector>
41 #include <string.h>
42 #include <ostream>
43
44 using XFILE::CDirectory;
45 using XFILE::CFile;
46 using namespace std;
47
48 namespace ADDON
49 {
50
51 /**
52  * helper functions 
53  *
54  */
55
56 typedef struct
57 {
58   const char* name;
59   TYPE        type;
60   int         pretty;
61   const char* icon;
62 } TypeMapping;
63
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" }};
91
92 const CStdString TranslateType(const ADDON::TYPE &type, bool pretty/*=false*/)
93 {
94   for (unsigned int index=0; index < sizeof(types)/sizeof(types[0]); ++index)
95   {
96     const TypeMapping &map = types[index];
97     if (type == map.type)
98     {
99       if (pretty && map.pretty)
100         return g_localizeStrings.Get(map.pretty);
101       else
102         return map.name;
103     }
104   }
105   return "";
106 }
107
108 TYPE TranslateType(const CStdString &string)
109 {
110   for (unsigned int index=0; index < sizeof(types)/sizeof(types[0]); ++index)
111   {
112     const TypeMapping &map = types[index];
113     if (string.Equals(map.name))
114       return map.type;
115   }
116   return ADDON_UNKNOWN;
117 }
118
119 const CStdString GetIcon(const ADDON::TYPE& type)
120 {
121   for (unsigned int index=0; index < sizeof(types)/sizeof(types[0]); ++index)
122   {
123     const TypeMapping &map = types[index];
124     if (type == map.type)
125       return map.icon;
126   }
127   return "";
128 }
129
130 #define EMPTY_IF(x,y) \
131   { \
132     CStdString fan=CAddonMgr::Get().GetExtValue(metadata->configuration, x); \
133     if (fan.Equals("true")) \
134       y.clear(); \
135   }
136
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)
144   , stars(0)
145 {
146   if (ext->ext_point_id)
147     type = TranslateType(ext->ext_point_id);
148
149   icon = "icon.png";
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");
154   if (metadata)
155   {
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");
160     CStdString language;
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)
168   }
169   BuildDependencies(ext->plugin);
170 }
171
172 AddonProps::AddonProps(const cp_plugin_info_t *plugin)
173   : id(plugin->identifier)
174   , version(plugin->version)
175   , minversion(plugin->abi_bw_compatibility)
176   , name(plugin->name)
177   , path(plugin->plugin_path)
178   , author(plugin->provider_name)
179   , stars(0)
180 {
181   BuildDependencies(plugin);
182 }
183
184 void AddonProps::Serialize(CVariant &variant) const
185 {
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;
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 {
254   BuildLibName(ext);
255   Props().libname = m_strLibName;
256   BuildProfilePath();
257   m_userSettingsPath = URIUtils::AddFileToFolder(Profile(), "settings.xml");
258   m_enabled = true;
259   m_hasSettings = true;
260   m_hasStrings = false;
261   m_checkedStrings = false;
262   m_settingsLoaded = false;
263   m_userSettingsLoaded = false;
264 }
265
266 CAddon::CAddon(const cp_plugin_info_t *plugin)
267   : m_props(plugin)
268 {
269   m_enabled = true;
270   m_hasSettings = false;
271   m_hasStrings = false;
272   m_checkedStrings = true;
273   m_settingsLoaded = false;
274   m_userSettingsLoaded = false;
275 }
276
277 CAddon::CAddon(const AddonProps &props)
278   : m_props(props)
279 {
280   if (props.libname.empty()) BuildLibName();
281   else m_strLibName = props.libname;
282   BuildProfilePath();
283   m_userSettingsPath = URIUtils::AddFileToFolder(Profile(), "settings.xml");
284   m_enabled = true;
285   m_hasSettings = true;
286   m_hasStrings = false;
287   m_checkedStrings = false;
288   m_settingsLoaded = false;
289   m_userSettingsLoaded = false;
290 }
291
292 CAddon::CAddon(const CAddon &rhs)
293   : m_props(rhs.Props())
294 {
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;
300   BuildProfilePath();
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;
306 }
307
308 AddonPtr CAddon::Clone() const
309 {
310   return AddonPtr(new CAddon(*this));
311 }
312
313 bool CAddon::MeetsVersion(const AddonVersion &version) const
314 {
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;
321
322   return m_props.minversion <= version && version <= m_props.version;
323 }
324
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)
328 {
329   if (!extension)
330   {
331     m_strLibName = "default";
332     CStdString ext;
333     switch (m_props.type)
334     {
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;
342       break;
343     case ADDON_SCREENSAVER:
344       ext = ADDON_SCREENSAVER_EXT;
345       break;
346     case ADDON_SKIN:
347       m_strLibName = "skin.xml";
348       return;
349     case ADDON_VIZ:
350       ext = ADDON_VIS_EXT;
351       break;
352     case ADDON_PVRDLL:
353       ext = ADDON_PVRDLL_EXT;
354       break;
355     case ADDON_SCRIPT:
356     case ADDON_SCRIPT_LIBRARY:
357     case ADDON_SCRIPT_LYRICS:
358     case ADDON_SCRIPT_WEATHER:
359     case ADDON_SUBTITLE_MODULE:        
360     case ADDON_PLUGIN:
361     case ADDON_SERVICE:
362       ext = ADDON_PYTHON_EXT;
363       break;
364     default:
365       m_strLibName.clear();
366       return;
367     }
368     // extensions are returned as *.ext
369     // so remove the asterisk
370     ext.erase(0,1);
371     m_strLibName.append(ext);
372   }
373   else
374   {
375     switch (m_props.type)
376     {
377       case ADDON_SCREENSAVER:
378       case ADDON_SCRIPT:
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:
390       case ADDON_PVRDLL:
391       case ADDON_PLUGIN:
392       case ADDON_SERVICE:
393       case ADDON_REPOSITORY:
394         {
395           CStdString temp = CAddonMgr::Get().GetExtValue(extension->configuration, "@library");
396           m_strLibName = temp;
397         }
398         break;
399       default:
400         m_strLibName.clear();
401         break;
402     }
403   }
404 }
405
406 /**
407  * Language File Handling
408  */
409 bool CAddon::LoadStrings()
410 {
411   // Path where the language strings reside
412   CStdString chosenPath = URIUtils::AddFileToFolder(m_props.path, "resources/language/");
413
414   m_hasStrings = m_strings.Load(chosenPath, CSettings::Get().GetString("locale.language"));
415   return m_checkedStrings = true;
416 }
417
418 void CAddon::ClearStrings()
419 {
420   // Unload temporary language strings
421   m_strings.Clear();
422   m_hasStrings = false;
423 }
424
425 CStdString CAddon::GetString(uint32_t id)
426 {
427   if (!m_hasStrings && ! m_checkedStrings && !LoadStrings())
428      return "";
429
430   return m_strings.Get(id);
431 }
432
433 /**
434  * Settings Handling
435  */
436 bool CAddon::HasSettings()
437 {
438   return LoadSettings();
439 }
440
441 bool CAddon::LoadSettings(bool bForce /* = false*/)
442 {
443   if (m_settingsLoaded && !bForce)
444     return true;
445   if (!m_hasSettings)
446     return false;
447   CStdString addonFileName = URIUtils::AddFileToFolder(m_props.path, "resources/settings.xml");
448
449   if (!m_addonXmlDoc.LoadFile(addonFileName))
450   {
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;
454     return false;
455   }
456
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)
460   {
461     CLog::Log(LOGERROR, "Error loading Settings %s: cannot find root element 'settings'", addonFileName.c_str());
462     return false;
463   }
464   SettingsFromXML(m_addonXmlDoc, true);
465   LoadUserSettings();
466   m_settingsLoaded = true;
467   return true;
468 }
469
470 bool CAddon::HasUserSettings()
471 {
472   if (!LoadSettings())
473     return false;
474
475   return m_userSettingsLoaded;
476 }
477
478 bool CAddon::ReloadSettings()
479 {
480   return LoadSettings(true);
481 }
482
483 bool CAddon::LoadUserSettings()
484 {
485   m_userSettingsLoaded = false;
486   CXBMCTinyXML doc;
487   if (doc.LoadFile(m_userSettingsPath))
488     m_userSettingsLoaded = SettingsFromXML(doc);
489   return m_userSettingsLoaded;
490 }
491
492 void CAddon::SaveSettings(void)
493 {
494   if (m_settings.empty())
495     return; // no settings to save
496
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);
502
503   // create the individual folders
504   if (!CDirectory::Exists(strRoot))
505     CDirectory::Create(strRoot);
506   if (!CDirectory::Exists(strAddon))
507     CDirectory::Create(strAddon);
508
509   // create the XML file
510   CXBMCTinyXML doc;
511   SettingsToXML(doc);
512   doc.SaveFile(m_userSettingsPath);
513   m_userSettingsLoaded = true;
514   
515   CAddonMgr::Get().ReloadSettings(ID());//push the settings changes to the running addon instance
516 #ifdef HAS_PYTHON
517   g_pythonParser.OnSettingsChanged(ID());
518 #endif
519 }
520
521 CStdString CAddon::GetSetting(const CStdString& key)
522 {
523   if (!LoadSettings())
524     return ""; // no settings available
525
526   map<CStdString, CStdString>::const_iterator i = m_settings.find(key);
527   if (i != m_settings.end())
528     return i->second;
529   return "";
530 }
531
532 void CAddon::UpdateSetting(const CStdString& key, const CStdString& value)
533 {
534   LoadSettings();
535   if (key.empty()) return;
536   m_settings[key] = value;
537 }
538
539 bool CAddon::SettingsFromXML(const CXBMCTinyXML &doc, bool loadDefaults /*=false */)
540 {
541   if (!doc.RootElement())
542     return false;
543
544   if (loadDefaults)
545     m_settings.clear();
546
547   const TiXmlElement* category = doc.RootElement()->FirstChildElement("category");
548   if (!category)
549     category = doc.RootElement();
550
551   bool foundSetting = false;
552   while (category)
553   {
554     const TiXmlElement *setting = category->FirstChildElement("setting");
555     while (setting)
556     {
557       const char *id = setting->Attribute("id");
558       const char *value = setting->Attribute(loadDefaults ? "default" : "value");
559       if (id && value)
560       {
561         m_settings[id] = value;
562         foundSetting = true;
563       }
564       setting = setting->NextSiblingElement("setting");
565     }
566     category = category->NextSiblingElement("category");
567   }
568   return foundSetting;
569 }
570
571 void CAddon::SettingsToXML(CXBMCTinyXML &doc) const
572 {
573   TiXmlElement node("settings");
574   doc.InsertEndChild(node);
575   for (map<CStdString, CStdString>::const_iterator i = m_settings.begin(); i != m_settings.end(); ++i)
576   {
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);
581   }
582   doc.SaveFile(m_userSettingsPath);
583 }
584
585 TiXmlElement* CAddon::GetSettingsXML()
586 {
587   return m_addonXmlDoc.RootElement();
588 }
589
590 void CAddon::BuildProfilePath()
591 {
592   m_profile = StringUtils::Format("special://profile/addon_data/%s/", ID().c_str());
593 }
594
595 const CStdString CAddon::Icon() const
596 {
597   if (CURL::IsFullPath(m_props.icon))
598     return m_props.icon;
599   return URIUtils::AddFileToFolder(m_props.path, m_props.icon);
600 }
601
602 const CStdString CAddon::LibPath() const
603 {
604   return URIUtils::AddFileToFolder(m_props.path, m_strLibName);
605 }
606
607 /**
608  * CAddonLibrary
609  *
610  */
611
612 CAddonLibrary::CAddonLibrary(const cp_extension_t *ext)
613   : CAddon(ext)
614   , m_addonType(SetAddonType())
615 {
616 }
617
618 CAddonLibrary::CAddonLibrary(const AddonProps& props)
619   : CAddon(props)
620   , m_addonType(SetAddonType())
621 {
622 }
623
624 AddonPtr CAddonLibrary::Clone() const
625 {
626   return AddonPtr(new CAddonLibrary(*this));
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