1 /*
2  *  Copyright (C) 2011-2018 Team Kodi
3  *  This file is part of Kodi - https://kodi.tv
4  *
5  *  SPDX-License-Identifier: GPL-2.0-or-later
6  *  See LICENSES/README.md for more information.
7  */
8 
9 #include "AddonsOperations.h"
10 
11 #include "JSONUtils.h"
12 #include "ServiceBroker.h"
13 #include "TextureCache.h"
14 #include "addons/AddonDatabase.h"
15 #include "addons/AddonManager.h"
16 #include "addons/PluginSource.h"
17 #include "filesystem/File.h"
18 #include "messaging/ApplicationMessenger.h"
19 #include "utils/StringUtils.h"
20 #include "utils/Variant.h"
21 
22 using namespace JSONRPC;
23 using namespace ADDON;
24 using namespace XFILE;
25 using namespace KODI::MESSAGING;
26 
GetAddons(const std::string & method,ITransportLayer * transport,IClient * client,const CVariant & parameterObject,CVariant & result)27 JSONRPC_STATUS CAddonsOperations::GetAddons(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant &parameterObject, CVariant &result)
28 {
29   std::vector<TYPE> addonTypes;
30   TYPE addonType = CAddonInfo::TranslateType(parameterObject["type"].asString());
31   CPluginSource::Content content = CPluginSource::Translate(parameterObject["content"].asString());
32   CVariant enabled = parameterObject["enabled"];
33   CVariant installed = parameterObject["installed"];
34 
35   // ignore the "content" parameter if the type is specified but not a plugin or script
36   if (addonType != ADDON_UNKNOWN && addonType != ADDON_PLUGIN && addonType != ADDON_SCRIPT)
37     content = CPluginSource::UNKNOWN;
38 
39   if (addonType >= ADDON_VIDEO && addonType <= ADDON_EXECUTABLE)
40   {
41     addonTypes.push_back(ADDON_PLUGIN);
42     addonTypes.push_back(ADDON_SCRIPT);
43 
44     switch (addonType)
45     {
46     case ADDON_VIDEO:
47       content = CPluginSource::VIDEO;
48       break;
49     case ADDON_AUDIO:
50       content = CPluginSource::AUDIO;
51       break;
52     case ADDON_IMAGE:
53       content = CPluginSource::IMAGE;
54       break;
55     case ADDON_GAME:
56       content = CPluginSource::GAME;
57       break;
58     case ADDON_EXECUTABLE:
59       content = CPluginSource::EXECUTABLE;
60       break;
61 
62     default:
63       break;
64     }
65   }
66   else
67     addonTypes.push_back(addonType);
68 
69   VECADDONS addons;
70   for (const auto& typeIt : addonTypes)
71   {
72     VECADDONS typeAddons;
73     if (typeIt == ADDON_UNKNOWN)
74     {
75       if (!enabled.isBoolean()) //All
76       {
77         if (!installed.isBoolean() || installed.asBoolean())
78           CServiceBroker::GetAddonMgr().GetInstalledAddons(typeAddons);
79         if (!installed.isBoolean() || (installed.isBoolean() && !installed.asBoolean()))
80           CServiceBroker::GetAddonMgr().GetInstallableAddons(typeAddons);
81       }
82       else if (enabled.asBoolean() && (!installed.isBoolean() || installed.asBoolean())) //Enabled
83         CServiceBroker::GetAddonMgr().GetAddons(typeAddons);
84       else if (!installed.isBoolean() || installed.asBoolean())
85         CServiceBroker::GetAddonMgr().GetDisabledAddons(typeAddons);
86     }
87     else
88     {
89       if (!enabled.isBoolean()) //All
90       {
91         if (!installed.isBoolean() || installed.asBoolean())
92           CServiceBroker::GetAddonMgr().GetInstalledAddons(typeAddons, typeIt);
93         if (!installed.isBoolean() || (installed.isBoolean() && !installed.asBoolean()))
94           CServiceBroker::GetAddonMgr().GetInstallableAddons(typeAddons, typeIt);
95       }
96       else if (enabled.asBoolean() && (!installed.isBoolean() || installed.asBoolean())) //Enabled
97         CServiceBroker::GetAddonMgr().GetAddons(typeAddons, typeIt);
98       else if (!installed.isBoolean() || installed.asBoolean())
99         CServiceBroker::GetAddonMgr().GetDisabledAddons(typeAddons, typeIt);
100     }
101 
102     addons.insert(addons.end(), typeAddons.begin(), typeAddons.end());
103   }
104 
105   // remove library addons
106   for (int index = 0; index < (int)addons.size(); index++)
107   {
108     PluginPtr plugin;
109     if (content != CPluginSource::UNKNOWN)
110       plugin = std::dynamic_pointer_cast<CPluginSource>(addons.at(index));
111 
112     if ((addons.at(index)->Type() <= ADDON_UNKNOWN || addons.at(index)->Type() >= ADDON_MAX) ||
113        ((content != CPluginSource::UNKNOWN && plugin == NULL) || (plugin != NULL && !plugin->Provides(content))))
114     {
115       addons.erase(addons.begin() + index);
116       index--;
117     }
118   }
119 
120   int start, end;
121   HandleLimits(parameterObject, result, addons.size(), start, end);
122 
123   CAddonDatabase addondb;
124   for (int index = start; index < end; index++)
125     FillDetails(addons.at(index), parameterObject["properties"], result["addons"], addondb, true);
126 
127   return OK;
128 }
129 
GetAddonDetails(const std::string & method,ITransportLayer * transport,IClient * client,const CVariant & parameterObject,CVariant & result)130 JSONRPC_STATUS CAddonsOperations::GetAddonDetails(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant &parameterObject, CVariant &result)
131 {
132   std::string id = parameterObject["addonid"].asString();
133   AddonPtr addon;
134   if (!CServiceBroker::GetAddonMgr().GetAddon(id, addon, ADDON::ADDON_UNKNOWN, OnlyEnabled::NO) ||
135       addon.get() == NULL || addon->Type() <= ADDON_UNKNOWN || addon->Type() >= ADDON_MAX)
136     return InvalidParams;
137 
138   CAddonDatabase addondb;
139   FillDetails(addon, parameterObject["properties"], result["addon"], addondb);
140 
141   return OK;
142 }
143 
SetAddonEnabled(const std::string & method,ITransportLayer * transport,IClient * client,const CVariant & parameterObject,CVariant & result)144 JSONRPC_STATUS CAddonsOperations::SetAddonEnabled(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant &parameterObject, CVariant &result)
145 {
146   std::string id = parameterObject["addonid"].asString();
147   AddonPtr addon;
148   if (!CServiceBroker::GetAddonMgr().GetAddon(id, addon, ADDON::ADDON_UNKNOWN, OnlyEnabled::NO) ||
149       addon == nullptr || addon->Type() <= ADDON_UNKNOWN || addon->Type() >= ADDON_MAX)
150     return InvalidParams;
151 
152   bool disabled = false;
153   if (parameterObject["enabled"].isBoolean())
154   {
155     disabled = !parameterObject["enabled"].asBoolean();
156   }
157   // we need to toggle the current disabled state of the addon
158   else if (parameterObject["enabled"].isString())
159   {
160     disabled = !CServiceBroker::GetAddonMgr().IsAddonDisabled(id);
161   }
162   else
163   {
164     return InvalidParams;
165   }
166 
167   bool success = disabled
168                      ? CServiceBroker::GetAddonMgr().DisableAddon(id, AddonDisabledReason::USER)
169                      : CServiceBroker::GetAddonMgr().EnableAddon(id);
170 
171   return success ? ACK : InvalidParams;
172 }
173 
ExecuteAddon(const std::string & method,ITransportLayer * transport,IClient * client,const CVariant & parameterObject,CVariant & result)174 JSONRPC_STATUS CAddonsOperations::ExecuteAddon(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant &parameterObject, CVariant &result)
175 {
176   std::string id = parameterObject["addonid"].asString();
177   AddonPtr addon;
178   if (!CServiceBroker::GetAddonMgr().GetAddon(id, addon, ADDON_UNKNOWN, OnlyEnabled::YES) ||
179       addon.get() == NULL || addon->Type() < ADDON_VIZ || addon->Type() >= ADDON_MAX)
180     return InvalidParams;
181 
182   std::string argv;
183   CVariant params = parameterObject["params"];
184   if (params.isObject())
185   {
186     for (CVariant::const_iterator_map it = params.begin_map(); it != params.end_map(); it++)
187     {
188       if (it != params.begin_map())
189         argv += ",";
190       argv += it->first + "=" + it->second.asString();
191     }
192   }
193   else if (params.isArray())
194   {
195     for (CVariant::const_iterator_array it = params.begin_array(); it != params.end_array(); it++)
196     {
197       if (it != params.begin_array())
198         argv += ",";
199       argv += StringUtils::Paramify(it->asString());
200     }
201   }
202   else if (params.isString())
203   {
204     if (!params.empty())
205       argv = StringUtils::Paramify(params.asString());
206   }
207 
208   std::string cmd;
209   if (params.empty())
210     cmd = StringUtils::Format("RunAddon(%s)", id.c_str());
211   else
212     cmd = StringUtils::Format("RunAddon(%s, %s)", id.c_str(), argv.c_str());
213 
214   if (params["wait"].asBoolean())
215     CApplicationMessenger::GetInstance().SendMsg(TMSG_EXECUTE_BUILT_IN, -1, -1, nullptr, cmd);
216   else
217     CApplicationMessenger::GetInstance().PostMsg(TMSG_EXECUTE_BUILT_IN, -1, -1, nullptr, cmd);
218 
219   return ACK;
220 }
221 
Serialize(const AddonPtr & addon)222 static CVariant Serialize(const AddonPtr& addon)
223 {
224   CVariant variant;
225   variant["addonid"] = addon->ID();
226   variant["type"] = CAddonInfo::TranslateType(addon->Type(), false);
227   variant["name"] = addon->Name();
228   variant["version"] = addon->Version().asString();
229   variant["summary"] = addon->Summary();
230   variant["description"] = addon->Description();
231   variant["path"] = addon->Path();
232   variant["author"] = addon->Author();
233   variant["thumbnail"] = addon->Icon();
234   variant["disclaimer"] = addon->Disclaimer();
235   variant["fanart"] = addon->FanArt();
236 
237   variant["dependencies"] = CVariant(CVariant::VariantTypeArray);
238   for (const auto& dep : addon->GetDependencies())
239   {
240     CVariant info(CVariant::VariantTypeObject);
241     info["addonid"] = dep.id;
242     info["minversion"] = dep.versionMin.asString();
243     info["version"] = dep.version.asString();
244     info["optional"] = dep.optional;
245     variant["dependencies"].push_back(std::move(info));
246   }
247   if (addon->LifecycleState() == AddonLifecycleState::BROKEN)
248     variant["broken"] = addon->LifecycleStateDescription();
249   else
250     variant["broken"] = false;
251   if (addon->LifecycleState() == AddonLifecycleState::DEPRECATED)
252     variant["deprecated"] = addon->LifecycleStateDescription();
253   else
254     variant["deprecated"] = false;
255   variant["extrainfo"] = CVariant(CVariant::VariantTypeArray);
256   for (const auto& kv : addon->ExtraInfo())
257   {
258     CVariant info(CVariant::VariantTypeObject);
259     info["key"] = kv.first;
260     info["value"] = kv.second;
261     variant["extrainfo"].push_back(std::move(info));
262   }
263   variant["rating"] = -1;
264   return variant;
265 }
266 
FillDetails(const AddonPtr & addon,const CVariant & fields,CVariant & result,CAddonDatabase & addondb,bool append)267 void CAddonsOperations::FillDetails(const AddonPtr& addon,
268                                     const CVariant& fields,
269                                     CVariant& result,
270                                     CAddonDatabase& addondb,
271                                     bool append /* = false */)
272 {
273   if (addon.get() == NULL)
274     return;
275 
276   CVariant addonInfo = Serialize(addon);
277 
278   CVariant object;
279   object["addonid"] = addonInfo["addonid"];
280   object["type"] = addonInfo["type"];
281 
282   for (unsigned int index = 0; index < fields.size(); index++)
283   {
284     std::string field = fields[index].asString();
285 
286     // we need to manually retrieve the enabled / installed state of every addon
287     // from the addon database because it can't be read from addon.xml
288     if (field == "enabled")
289     {
290       object[field] = !CServiceBroker::GetAddonMgr().IsAddonDisabled(addon->ID());
291     }
292     else if (field == "installed")
293     {
294       object[field] = CServiceBroker::GetAddonMgr().IsAddonInstalled(addon->ID());
295     }
296     else if (field == "fanart" || field == "thumbnail")
297     {
298       std::string url = addonInfo[field].asString();
299       // We need to check the existence of fanart and thumbnails as the addon simply
300       // holds where the art will be, not whether it exists.
301       bool needsRecaching;
302       std::string image = CTextureCache::GetInstance().CheckCachedImage(url, needsRecaching);
303       if (!image.empty() || CFile::Exists(url))
304         object[field] = CTextureUtils::GetWrappedImageURL(url);
305       else
306         object[field] = "";
307     }
308     else if (addonInfo.isMember(field))
309       object[field] = addonInfo[field];
310   }
311 
312   if (append)
313     result.append(object);
314   else
315     result = object;
316 }
317