summaryrefslogtreecommitdiffstats
path: root/xbmc/addons/Repository.cpp
diff options
context:
space:
mode:
authormanuel <manuel@mausz.at>2015-03-03 16:53:59 +0100
committermanuel <manuel@mausz.at>2015-03-03 16:53:59 +0100
commitffca21f2743a7b367fa212799c6e2fea6190dd5d (patch)
tree0608ea3a29cf644ec9ab204e2b4bb9bfaae1c381 /xbmc/addons/Repository.cpp
downloadkodi-pvr-build-ffca21f2743a7b367fa212799c6e2fea6190dd5d.tar.gz
kodi-pvr-build-ffca21f2743a7b367fa212799c6e2fea6190dd5d.tar.bz2
kodi-pvr-build-ffca21f2743a7b367fa212799c6e2fea6190dd5d.zip
initial commit for kodi master
Diffstat (limited to 'xbmc/addons/Repository.cpp')
-rw-r--r--xbmc/addons/Repository.cpp395
1 files changed, 395 insertions, 0 deletions
diff --git a/xbmc/addons/Repository.cpp b/xbmc/addons/Repository.cpp
new file mode 100644
index 0000000..e65a195
--- /dev/null
+++ b/xbmc/addons/Repository.cpp
@@ -0,0 +1,395 @@
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 "Repository.h"
22#include "addons/AddonDatabase.h"
23#include "addons/AddonInstaller.h"
24#include "addons/AddonManager.h"
25#include "dialogs/GUIDialogYesNo.h"
26#include "dialogs/GUIDialogKaiToast.h"
27#include "filesystem/File.h"
28#include "filesystem/PluginDirectory.h"
29#include "settings/Settings.h"
30#include "utils/log.h"
31#include "utils/JobManager.h"
32#include "utils/StringUtils.h"
33#include "utils/URIUtils.h"
34#include "utils/XBMCTinyXML.h"
35#include "FileItem.h"
36#include "TextureDatabase.h"
37#include "URL.h"
38
39using namespace std;
40using namespace XFILE;
41using namespace ADDON;
42
43AddonPtr CRepository::Clone() const
44{
45 return AddonPtr(new CRepository(*this));
46}
47
48CRepository::CRepository(const AddonProps& props) :
49 CAddon(props)
50{
51}
52
53CRepository::CRepository(const cp_extension_t *ext)
54 : CAddon(ext)
55{
56 // read in the other props that we need
57 if (ext)
58 {
59 AddonVersion version("0.0.0");
60 AddonPtr addonver;
61 if (CAddonMgr::Get().GetAddon("xbmc.addon", addonver))
62 version = addonver->Version();
63 for (size_t i = 0; i < ext->configuration->num_children; ++i)
64 {
65 if(ext->configuration->children[i].name &&
66 strcmp(ext->configuration->children[i].name, "dir") == 0)
67 {
68 AddonVersion min_version(CAddonMgr::Get().GetExtValue(&ext->configuration->children[i], "@minversion"));
69 if (min_version <= version)
70 {
71 DirInfo dir;
72 dir.version = min_version;
73 dir.checksum = CAddonMgr::Get().GetExtValue(&ext->configuration->children[i], "checksum");
74 dir.compressed = CAddonMgr::Get().GetExtValue(&ext->configuration->children[i], "info@compressed") == "true";
75 dir.info = CAddonMgr::Get().GetExtValue(&ext->configuration->children[i], "info");
76 dir.datadir = CAddonMgr::Get().GetExtValue(&ext->configuration->children[i], "datadir");
77 dir.zipped = CAddonMgr::Get().GetExtValue(&ext->configuration->children[i], "datadir@zip") == "true";
78 dir.hashes = CAddonMgr::Get().GetExtValue(&ext->configuration->children[i], "hashes") == "true";
79 m_dirs.push_back(dir);
80 }
81 }
82 }
83 // backward compatibility
84 if (!CAddonMgr::Get().GetExtValue(ext->configuration, "info").empty())
85 {
86 DirInfo info;
87 info.checksum = CAddonMgr::Get().GetExtValue(ext->configuration, "checksum");
88 info.compressed = CAddonMgr::Get().GetExtValue(ext->configuration, "info@compressed") == "true";
89 info.info = CAddonMgr::Get().GetExtValue(ext->configuration, "info");
90 info.datadir = CAddonMgr::Get().GetExtValue(ext->configuration, "datadir");
91 info.zipped = CAddonMgr::Get().GetExtValue(ext->configuration, "datadir@zip") == "true";
92 info.hashes = CAddonMgr::Get().GetExtValue(ext->configuration, "hashes") == "true";
93 m_dirs.push_back(info);
94 }
95 }
96}
97
98CRepository::CRepository(const CRepository &rhs)
99 : CAddon(rhs), m_dirs(rhs.m_dirs)
100{
101}
102
103CRepository::~CRepository()
104{
105}
106
107string CRepository::FetchChecksum(const string& url)
108{
109 CFile file;
110 try
111 {
112 if (file.Open(url))
113 {
114 // we intentionally avoid using file.GetLength() for
115 // Transfer-Encoding: chunked servers.
116 std::stringstream str;
117 char temp[1024];
118 int read;
119 while ((read=file.Read(temp, sizeof(temp))) > 0)
120 str.write(temp, read);
121 return str.str();
122 }
123 return "";
124 }
125 catch (...)
126 {
127 return "";
128 }
129}
130
131string CRepository::GetAddonHash(const AddonPtr& addon) const
132{
133 string checksum;
134 DirList::const_iterator it;
135 for (it = m_dirs.begin();it != m_dirs.end(); ++it)
136 if (URIUtils::IsInPath(addon->Path(), it->datadir))
137 break;
138 if (it != m_dirs.end() && it->hashes)
139 {
140 checksum = FetchChecksum(addon->Path()+".md5");
141 size_t pos = checksum.find_first_of(" \n");
142 if (pos != string::npos)
143 return checksum.substr(0, pos);
144 }
145 return checksum;
146}
147
148#define SET_IF_NOT_EMPTY(x,y) \
149 { \
150 if (!x.empty()) \
151 x = y; \
152 }
153
154bool CRepository::Parse(const DirInfo& dir, VECADDONS &result)
155{
156 string file = dir.info;
157 if (dir.compressed)
158 {
159 CURL url(dir.info);
160 string opts = url.GetProtocolOptions();
161 if (!opts.empty())
162 opts += "&";
163 url.SetProtocolOptions(opts+"Encoding=gzip");
164 file = url.Get();
165 }
166
167 CXBMCTinyXML doc;
168 if (doc.LoadFile(file) && doc.RootElement() &&
169 CAddonMgr::Get().AddonsFromRepoXML(doc.RootElement(), result))
170 {
171 for (IVECADDONS i = result.begin(); i != result.end(); ++i)
172 {
173 AddonPtr addon = *i;
174 if (dir.zipped)
175 {
176 string file = StringUtils::Format("%s/%s-%s.zip", addon->ID().c_str(), addon->ID().c_str(), addon->Version().asString().c_str());
177 addon->Props().path = URIUtils::AddFileToFolder(dir.datadir,file);
178 SET_IF_NOT_EMPTY(addon->Props().icon,URIUtils::AddFileToFolder(dir.datadir,addon->ID()+"/icon.png"))
179 file = StringUtils::Format("%s/changelog-%s.txt", addon->ID().c_str(), addon->Version().asString().c_str());
180 SET_IF_NOT_EMPTY(addon->Props().changelog,URIUtils::AddFileToFolder(dir.datadir,file))
181 SET_IF_NOT_EMPTY(addon->Props().fanart,URIUtils::AddFileToFolder(dir.datadir,addon->ID()+"/fanart.jpg"))
182 }
183 else
184 {
185 addon->Props().path = URIUtils::AddFileToFolder(dir.datadir,addon->ID()+"/");
186 SET_IF_NOT_EMPTY(addon->Props().icon,URIUtils::AddFileToFolder(dir.datadir,addon->ID()+"/icon.png"))
187 SET_IF_NOT_EMPTY(addon->Props().changelog,URIUtils::AddFileToFolder(dir.datadir,addon->ID()+"/changelog.txt"))
188 SET_IF_NOT_EMPTY(addon->Props().fanart,URIUtils::AddFileToFolder(dir.datadir,addon->ID()+"/fanart.jpg"))
189 }
190 }
191 return true;
192 }
193 return false;
194}
195
196void CRepository::OnPostInstall(bool restart, bool update, bool modal)
197{
198 VECADDONS addons;
199 AddonPtr repo(new CRepository(*this));
200 addons.push_back(repo);
201 CJobManager::GetInstance().AddJob(new CRepositoryUpdateJob(addons), &CAddonInstaller::Get());
202}
203
204void CRepository::OnPostUnInstall()
205{
206 CAddonDatabase database;
207 database.Open();
208 database.DeleteRepository(ID());
209}
210
211CRepositoryUpdateJob::CRepositoryUpdateJob(const VECADDONS &repos)
212 : m_repos(repos)
213{
214}
215
216void MergeAddons(map<string, AddonPtr> &addons, const VECADDONS &new_addons)
217{
218 for (VECADDONS::const_iterator it = new_addons.begin(); it != new_addons.end(); ++it)
219 {
220 map<string, AddonPtr>::iterator existing = addons.find((*it)->ID());
221 if (existing != addons.end())
222 { // already got it - replace if we have a newer version
223 if (existing->second->Version() < (*it)->Version())
224 existing->second = *it;
225 }
226 else
227 addons.insert(make_pair((*it)->ID(), *it));
228 }
229}
230
231bool CRepositoryUpdateJob::DoWork()
232{
233 map<string, AddonPtr> addons;
234 for (VECADDONS::const_iterator i = m_repos.begin(); i != m_repos.end(); ++i)
235 {
236 if (ShouldCancel(0, 0))
237 return false;
238 const RepositoryPtr repo = std::dynamic_pointer_cast<CRepository>(*i);
239 VECADDONS newAddons;
240 if (GrabAddons(repo, newAddons))
241 MergeAddons(addons, newAddons);
242 }
243 if (addons.empty())
244 return true; //Nothing to do
245
246 // check for updates
247 CAddonDatabase database;
248 database.Open();
249 database.BeginMultipleExecute();
250
251 CTextureDatabase textureDB;
252 textureDB.Open();
253 textureDB.BeginMultipleExecute();
254 VECADDONS notifications;
255 for (map<string, AddonPtr>::const_iterator i = addons.begin(); i != addons.end(); ++i)
256 {
257 // manager told us to feck off
258 if (ShouldCancel(0,0))
259 break;
260
261 AddonPtr newAddon = i->second;
262 bool deps_met = CAddonInstaller::Get().CheckDependencies(newAddon, &database);
263 if (!deps_met && newAddon->Props().broken.empty())
264 newAddon->Props().broken = "DEPSNOTMET";
265
266 // invalidate the art associated with this item
267 if (!newAddon->Props().fanart.empty())
268 textureDB.InvalidateCachedTexture(newAddon->Props().fanart);
269 if (!newAddon->Props().icon.empty())
270 textureDB.InvalidateCachedTexture(newAddon->Props().icon);
271
272 AddonPtr addon;
273 CAddonMgr::Get().GetAddon(newAddon->ID(),addon);
274 if (addon && newAddon->Version() > addon->Version() &&
275 !database.IsAddonBlacklisted(newAddon->ID(),newAddon->Version().asString()) &&
276 deps_met)
277 {
278 if (CSettings::Get().GetInt("general.addonupdates") == AUTO_UPDATES_ON)
279 {
280 string referer;
281 if (URIUtils::IsInternetStream(newAddon->Path()))
282 referer = StringUtils::Format("Referer=%s-%s.zip",addon->ID().c_str(),addon->Version().asString().c_str());
283
284 if (newAddon->CanInstall(referer))
285 CAddonInstaller::Get().Install(addon->ID(), true, referer);
286 }
287 else
288 notifications.push_back(addon);
289 }
290
291 // Check if we should mark the add-on as broken. We may have a newer version
292 // of this add-on in the database or installed - if so, we keep it unbroken.
293 bool haveNewer = (addon && addon->Version() > newAddon->Version()) ||
294 database.GetAddonVersion(newAddon->ID()) > newAddon->Version();
295 if (!haveNewer)
296 {
297 if (!newAddon->Props().broken.empty())
298 {
299 if (database.IsAddonBroken(newAddon->ID()).empty())
300 {
301 std::string line = g_localizeStrings.Get(24096);
302 if (newAddon->Props().broken == "DEPSNOTMET")
303 line = g_localizeStrings.Get(24104);
304 if (addon && CGUIDialogYesNo::ShowAndGetInput(newAddon->Name(),
305 line,
306 g_localizeStrings.Get(24097),
307 ""))
308 CAddonMgr::Get().DisableAddon(newAddon->ID());
309 }
310 }
311 database.BreakAddon(newAddon->ID(), newAddon->Props().broken);
312 }
313 }
314 database.CommitMultipleExecute();
315 textureDB.CommitMultipleExecute();
316 if (!notifications.empty() && CSettings::Get().GetBool("general.addonnotifications"))
317 {
318 if (notifications.size() == 1)
319 CGUIDialogKaiToast::QueueNotification(notifications[0]->Icon(),
320 g_localizeStrings.Get(24061),
321 notifications[0]->Name(),TOAST_DISPLAY_TIME,false,TOAST_DISPLAY_TIME);
322 else
323 CGUIDialogKaiToast::QueueNotification("",
324 g_localizeStrings.Get(24001),
325 g_localizeStrings.Get(24061),TOAST_DISPLAY_TIME,false,TOAST_DISPLAY_TIME);
326 }
327
328 return true;
329}
330
331bool CRepositoryUpdateJob::GrabAddons(const RepositoryPtr& repo, VECADDONS& addons)
332{
333 CAddonDatabase database;
334 database.Open();
335 string oldReposum;
336 if (!database.GetRepoChecksum(repo->ID(), oldReposum))
337 oldReposum = "";
338
339 string reposum;
340 for (CRepository::DirList::const_iterator it = repo->m_dirs.begin(); it != repo->m_dirs.end(); ++it)
341 {
342 if (ShouldCancel(0, 0))
343 return false;
344 if (!it->checksum.empty())
345 {
346 const string dirsum = CRepository::FetchChecksum(it->checksum);
347 if (dirsum.empty())
348 {
349 CLog::Log(LOGERROR, "Failed to fetch checksum for directory listing %s for repository %s. ", (*it).info.c_str(), repo->ID().c_str());
350 return false;
351 }
352 reposum += dirsum;
353 }
354 }
355
356 if (oldReposum != reposum || oldReposum.empty())
357 {
358 map<string, AddonPtr> uniqueAddons;
359 for (CRepository::DirList::const_iterator it = repo->m_dirs.begin(); it != repo->m_dirs.end(); ++it)
360 {
361 if (ShouldCancel(0, 0))
362 return false;
363 VECADDONS addons;
364 if (!CRepository::Parse(*it, addons))
365 { //TODO: Hash is invalid and should not be saved, but should we fail?
366 //We can still report a partial addon listing.
367 CLog::Log(LOGERROR, "Failed to read directory listing %s for repository %s. ", (*it).info.c_str(), repo->ID().c_str());
368 return false;
369 }
370 MergeAddons(uniqueAddons, addons);
371 }
372
373 bool add = true;
374 if (!repo->Props().libname.empty())
375 {
376 CFileItemList dummy;
377 string s = StringUtils::Format("plugin://%s/?action=update", repo->ID().c_str());
378 add = CDirectory::GetDirectory(s, dummy);
379 }
380 if (add)
381 {
382 for (map<string, AddonPtr>::const_iterator i = uniqueAddons.begin(); i != uniqueAddons.end(); ++i)
383 addons.push_back(i->second);
384 database.AddRepository(repo->ID(), addons, reposum, repo->Version());
385 }
386 }
387 else
388 {
389 CLog::Log(LOGDEBUG, "Checksum for repository %s not changed.", repo->ID().c_str());
390 database.GetRepository(repo->ID(), addons);
391 database.SetRepoTimestamp(repo->ID(), CDateTime::GetCurrentDateTime().GetAsDBDateTime(), repo->Version());
392 }
393 return true;
394}
395