2 * Copyright (C) 2005-2013 Team XBMC
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)
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.
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/>.
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 "pvr/PVRManager.h"
30 #include "settings/Settings.h"
31 #include "utils/log.h"
32 #include "utils/StringUtils.h"
33 #include "utils/URIUtils.h"
34 #include "utils/XBMCTinyXML.h"
36 #include "TextureDatabase.h"
40 using namespace XFILE;
41 using namespace ADDON;
43 AddonPtr CRepository::Clone() const
45 return AddonPtr(new CRepository(*this));
48 CRepository::CRepository(const AddonProps& props) :
53 CRepository::CRepository(const cp_extension_t *ext)
56 // read in the other props that we need
59 AddonVersion version("0.0.0");
61 if (CAddonMgr::Get().GetAddon("xbmc.addon", addonver))
62 version = addonver->Version();
63 for (size_t i = 0; i < ext->configuration->num_children; ++i)
65 if(ext->configuration->children[i].name &&
66 strcmp(ext->configuration->children[i].name, "dir") == 0)
68 AddonVersion min_version(CAddonMgr::Get().GetExtValue(&ext->configuration->children[i], "@minversion"));
69 if (min_version <= version)
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").Equals("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").Equals("true");
78 dir.hashes = CAddonMgr::Get().GetExtValue(&ext->configuration->children[i], "hashes").Equals("true");
79 m_dirs.push_back(dir);
83 // backward compatibility
84 if (!CAddonMgr::Get().GetExtValue(ext->configuration, "info").empty())
87 info.checksum = CAddonMgr::Get().GetExtValue(ext->configuration, "checksum");
88 info.compressed = CAddonMgr::Get().GetExtValue(ext->configuration, "info@compressed").Equals("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").Equals("true");
92 info.hashes = CAddonMgr::Get().GetExtValue(ext->configuration, "hashes").Equals("true");
93 m_dirs.push_back(info);
98 CRepository::CRepository(const CRepository &rhs)
104 CRepository::~CRepository()
108 string CRepository::Checksum() const
110 /* This code is duplicated in CRepositoryUpdateJob::GrabAddons().
111 * If you make changes here, they may be applicable there, too.
114 for (DirList::const_iterator it = m_dirs.begin(); it != m_dirs.end(); ++it)
116 if (!it->checksum.empty())
117 result += FetchChecksum(it->checksum);
122 string CRepository::FetchChecksum(const string& url)
129 // we intentionally avoid using file.GetLength() for
130 // Transfer-Encoding: chunked servers.
131 std::stringstream str;
134 while ((read=file.Read(temp, sizeof(temp))) > 0)
135 str.write(temp, read);
146 string CRepository::GetAddonHash(const AddonPtr& addon) const
149 DirList::const_iterator it;
150 for (it = m_dirs.begin();it != m_dirs.end(); ++it)
151 if (URIUtils::IsInPath(addon->Path(), it->datadir))
153 if (it != m_dirs.end() && it->hashes)
155 checksum = FetchChecksum(addon->Path()+".md5");
156 size_t pos = checksum.find_first_of(" \n");
157 if (pos != string::npos)
158 return checksum.substr(0, pos);
163 #define SET_IF_NOT_EMPTY(x,y) \
169 VECADDONS CRepository::Parse(const DirInfo& dir)
174 string file = dir.info;
178 string opts = url.GetProtocolOptions();
181 url.SetProtocolOptions(opts+"Encoding=gzip");
185 if (doc.LoadFile(file) && doc.RootElement())
187 CAddonMgr::Get().AddonsFromRepoXML(doc.RootElement(), result);
188 for (IVECADDONS i = result.begin(); i != result.end(); ++i)
193 string file = StringUtils::Format("%s/%s-%s.zip", addon->ID().c_str(), addon->ID().c_str(), addon->Version().c_str());
194 addon->Props().path = URIUtils::AddFileToFolder(dir.datadir,file);
195 SET_IF_NOT_EMPTY(addon->Props().icon,URIUtils::AddFileToFolder(dir.datadir,addon->ID()+"/icon.png"))
196 file = StringUtils::Format("%s/changelog-%s.txt", addon->ID().c_str(), addon->Version().c_str());
197 SET_IF_NOT_EMPTY(addon->Props().changelog,URIUtils::AddFileToFolder(dir.datadir,file))
198 SET_IF_NOT_EMPTY(addon->Props().fanart,URIUtils::AddFileToFolder(dir.datadir,addon->ID()+"/fanart.jpg"))
202 addon->Props().path = URIUtils::AddFileToFolder(dir.datadir,addon->ID()+"/");
203 SET_IF_NOT_EMPTY(addon->Props().icon,URIUtils::AddFileToFolder(dir.datadir,addon->ID()+"/icon.png"))
204 SET_IF_NOT_EMPTY(addon->Props().changelog,URIUtils::AddFileToFolder(dir.datadir,addon->ID()+"/changelog.txt"))
205 SET_IF_NOT_EMPTY(addon->Props().fanart,URIUtils::AddFileToFolder(dir.datadir,addon->ID()+"/fanart.jpg"))
213 CRepositoryUpdateJob::CRepositoryUpdateJob(const VECADDONS &repos)
218 void MergeAddons(map<string, AddonPtr> &addons, const VECADDONS &new_addons)
220 for (VECADDONS::const_iterator it = new_addons.begin(); it != new_addons.end(); ++it)
222 map<string, AddonPtr>::iterator existing = addons.find((*it)->ID());
223 if (existing != addons.end())
224 { // already got it - replace if we have a newer version
225 if (existing->second->Version() < (*it)->Version())
226 existing->second = *it;
229 addons.insert(make_pair((*it)->ID(), *it));
233 bool CRepositoryUpdateJob::DoWork()
235 map<string, AddonPtr> addons;
236 for (VECADDONS::const_iterator i = m_repos.begin(); i != m_repos.end(); ++i)
238 if (ShouldCancel(0, 0))
240 RepositoryPtr repo = boost::dynamic_pointer_cast<CRepository>(*i);
241 VECADDONS newAddons = GrabAddons(repo);
242 MergeAddons(addons, newAddons);
248 CAddonDatabase database;
250 database.BeginMultipleExecute();
252 CTextureDatabase textureDB;
254 textureDB.BeginMultipleExecute();
255 VECADDONS notifications;
256 for (map<string, AddonPtr>::const_iterator i = addons.begin(); i != addons.end(); ++i)
258 // manager told us to feck off
259 if (ShouldCancel(0,0))
262 AddonPtr newAddon = i->second;
263 bool deps_met = CAddonInstaller::Get().CheckDependencies(newAddon, &database);
264 if (!deps_met && newAddon->Props().broken.empty())
265 newAddon->Props().broken = "DEPSNOTMET";
267 // invalidate the art associated with this item
268 if (!newAddon->Props().fanart.empty())
269 textureDB.InvalidateCachedTexture(newAddon->Props().fanart);
270 if (!newAddon->Props().icon.empty())
271 textureDB.InvalidateCachedTexture(newAddon->Props().icon);
274 CAddonMgr::Get().GetAddon(newAddon->ID(),addon);
275 if (addon && newAddon->Version() > addon->Version() &&
276 !database.IsAddonBlacklisted(newAddon->ID(),newAddon->Version().c_str()) &&
279 if (CSettings::Get().GetBool("general.addonautoupdate") || addon->Type() >= ADDON_VIZ_LIBRARY)
282 if (URIUtils::IsInternetStream(newAddon->Path()))
283 referer = StringUtils::Format("Referer=%s-%s.zip",addon->ID().c_str(),addon->Version().c_str());
285 if (newAddon->Type() == ADDON_PVRDLL &&
286 !PVR::CPVRManager::Get().InstallAddonAllowed(newAddon->ID()))
287 PVR::CPVRManager::Get().MarkAsOutdated(addon->ID(), referer);
289 CAddonInstaller::Get().Install(addon->ID(), true, referer);
292 notifications.push_back(addon);
295 // Check if we should mark the add-on as broken. We may have a newer version
296 // of this add-on in the database or installed - if so, we keep it unbroken.
297 bool haveNewer = (addon && addon->Version() > newAddon->Version()) ||
298 database.GetAddonVersion(newAddon->ID()) > newAddon->Version();
301 if (!newAddon->Props().broken.empty())
303 if (database.IsAddonBroken(newAddon->ID()).empty())
305 std::string line = g_localizeStrings.Get(24096);
306 if (newAddon->Props().broken == "DEPSNOTMET")
307 line = g_localizeStrings.Get(24104);
308 if (addon && CGUIDialogYesNo::ShowAndGetInput(newAddon->Name(),
310 g_localizeStrings.Get(24097),
312 CAddonMgr::Get().DisableAddon(newAddon->ID());
315 database.BreakAddon(newAddon->ID(), newAddon->Props().broken);
318 database.CommitMultipleExecute();
319 textureDB.CommitMultipleExecute();
320 if (!notifications.empty() && CSettings::Get().GetBool("general.addonnotifications"))
322 if (notifications.size() == 1)
323 CGUIDialogKaiToast::QueueNotification(notifications[0]->Icon(),
324 g_localizeStrings.Get(24061),
325 notifications[0]->Name(),TOAST_DISPLAY_TIME,false,TOAST_DISPLAY_TIME);
327 CGUIDialogKaiToast::QueueNotification("",
328 g_localizeStrings.Get(24001),
329 g_localizeStrings.Get(24061),TOAST_DISPLAY_TIME,false,TOAST_DISPLAY_TIME);
335 VECADDONS CRepositoryUpdateJob::GrabAddons(RepositoryPtr& repo)
337 CAddonDatabase database;
341 database.GetRepoChecksum(repo->ID(),checksum);
344 /* This for loop is duplicated in CRepository::Checksum().
345 * If you make changes here, they may be applicable there, too.
347 for (CRepository::DirList::const_iterator it = repo->m_dirs.begin(); it != repo->m_dirs.end(); ++it)
349 if (ShouldCancel(0, 0))
351 if (!it->checksum.empty())
352 reposum += CRepository::FetchChecksum(it->checksum);
355 if (checksum != reposum || checksum.empty())
357 map<string, AddonPtr> uniqueAddons;
358 for (CRepository::DirList::const_iterator it = repo->m_dirs.begin(); it != repo->m_dirs.end(); ++it)
360 if (ShouldCancel(0, 0))
362 VECADDONS addons2 = CRepository::Parse(*it);
363 MergeAddons(uniqueAddons, addons2);
366 if (uniqueAddons.empty())
368 CLog::Log(LOGERROR,"Repository %s returned no add-ons, listing may have failed",repo->Name().c_str());
369 reposum = checksum; // don't update the checksum
374 if (!repo->Props().libname.empty())
377 string s = StringUtils::Format("plugin://%s/?action=update", repo->ID().c_str());
378 add = CDirectory::GetDirectory(s, dummy);
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);
389 database.GetRepository(repo->ID(),addons);
390 database.SetRepoTimestamp(repo->ID(),CDateTime::GetCurrentDateTime().GetAsDBDateTime());