Merge pull request #26148 from ksooo/fix-secondstotimestring-warning
[xbmc.git] / xbmc / addons / RepositoryUpdater.cpp
blobc8c1dcbcc32181219852240d429762f892829cf4
1 /*
2 * Copyright (C) 2015-2018 Team Kodi
3 * This file is part of Kodi - https://kodi.tv
5 * SPDX-License-Identifier: GPL-2.0-or-later
6 * See LICENSES/README.md for more information.
7 */
9 #include "RepositoryUpdater.h"
11 #include "ServiceBroker.h"
12 #include "TextureDatabase.h"
13 #include "addons/AddonDatabase.h"
14 #include "addons/AddonEvents.h"
15 #include "addons/AddonInstaller.h"
16 #include "addons/AddonManager.h"
17 #include "addons/AddonSystemSettings.h"
18 #include "addons/Repository.h"
19 #include "addons/addoninfo/AddonInfo.h"
20 #include "addons/addoninfo/AddonType.h"
21 #include "dialogs/GUIDialogExtendedProgressBar.h"
22 #include "dialogs/GUIDialogKaiToast.h"
23 #include "events/AddonManagementEvent.h"
24 #include "events/EventLog.h"
25 #include "guilib/GUIComponent.h"
26 #include "guilib/GUIWindowManager.h"
27 #include "guilib/LocalizeStrings.h"
28 #include "settings/Settings.h"
29 #include "settings/SettingsComponent.h"
30 #include "settings/lib/Setting.h"
31 #include "utils/JobManager.h"
32 #include "utils/ProgressJob.h"
33 #include "utils/log.h"
35 #include <algorithm>
36 #include <iterator>
37 #include <mutex>
38 #include <vector>
40 using namespace std::chrono_literals;
42 namespace ADDON
45 class CRepositoryUpdateJob : public CProgressJob
47 public:
48 explicit CRepositoryUpdateJob(const RepositoryPtr& repo) : m_repo(repo) {}
49 ~CRepositoryUpdateJob() override = default;
50 bool DoWork() override;
51 const RepositoryPtr& GetAddon() const { return m_repo; }
53 private:
54 const RepositoryPtr m_repo;
57 bool CRepositoryUpdateJob::DoWork()
59 CLog::Log(LOGDEBUG, "CRepositoryUpdateJob[{}] checking for updates.", m_repo->ID());
60 CAddonDatabase database;
61 database.Open();
63 std::string oldChecksum;
64 if (database.GetRepoChecksum(m_repo->ID(), oldChecksum) == -1)
65 oldChecksum = "";
67 const CAddonDatabase::RepoUpdateData updateData{database.GetRepoUpdateData(m_repo->ID())};
68 if (updateData.lastCheckedVersion != m_repo->Version())
69 oldChecksum = "";
71 std::string newChecksum;
72 std::vector<AddonInfoPtr> addons;
73 int recheckAfter;
74 auto status = m_repo->FetchIfChanged(oldChecksum, newChecksum, addons, recheckAfter);
76 database.SetRepoUpdateData(
77 m_repo->ID(), CAddonDatabase::RepoUpdateData(
78 CDateTime::GetCurrentDateTime(), m_repo->Version(),
79 CDateTime::GetCurrentDateTime() + CDateTimeSpan(0, 0, 0, recheckAfter)));
81 MarkFinished();
83 if (status == CRepository::STATUS_ERROR)
84 return false;
86 if (status == CRepository::STATUS_NOT_MODIFIED)
88 CLog::Log(LOGDEBUG, "CRepositoryUpdateJob[{}] checksum not changed.", m_repo->ID());
89 return true;
92 //Invalidate art.
94 CTextureDatabase textureDB;
95 textureDB.Open();
96 textureDB.BeginMultipleExecute();
98 for (const auto& addon : addons)
100 AddonPtr oldAddon;
101 if (CServiceBroker::GetAddonMgr().FindInstallableById(addon->ID(), oldAddon) && oldAddon &&
102 addon->Version() > oldAddon->Version())
104 if (!oldAddon->Icon().empty() || !oldAddon->Art().empty() ||
105 !oldAddon->Screenshots().empty())
106 CLog::Log(LOGDEBUG, "CRepository: invalidating cached art for '{}'", addon->ID());
108 if (!oldAddon->Icon().empty())
109 textureDB.InvalidateCachedTexture(oldAddon->Icon());
111 for (const auto& path : oldAddon->Screenshots())
112 textureDB.InvalidateCachedTexture(path);
114 for (const auto& art : oldAddon->Art())
115 textureDB.InvalidateCachedTexture(art.second);
118 textureDB.CommitMultipleExecute();
121 database.UpdateRepositoryContent(m_repo->ID(), m_repo->Version(), newChecksum, addons);
122 return true;
125 CRepositoryUpdater::CRepositoryUpdater(CAddonMgr& addonMgr) :
126 m_timer(this),
127 m_doneEvent(true),
128 m_addonMgr(addonMgr)
130 // Register settings
131 std::set<std::string> settingSet;
132 settingSet.insert(CSettings::SETTING_ADDONS_AUTOUPDATES);
133 CServiceBroker::GetSettingsComponent()->GetSettings()->RegisterCallback(this, settingSet);
136 void CRepositoryUpdater::Start()
138 m_addonMgr.Events().Subscribe(this, &CRepositoryUpdater::OnEvent);
139 ScheduleUpdate(UpdateScheduleType::First);
142 CRepositoryUpdater::~CRepositoryUpdater()
144 // Unregister settings
145 CServiceBroker::GetSettingsComponent()->GetSettings()->UnregisterCallback(this);
147 m_addonMgr.Events().Unsubscribe(this);
150 void CRepositoryUpdater::OnEvent(const ADDON::AddonEvent& event)
152 if (typeid(event) == typeid(ADDON::AddonEvents::Enabled))
154 if (m_addonMgr.HasType(event.addonId, AddonType::REPOSITORY))
155 ScheduleUpdate(UpdateScheduleType::First);
159 void CRepositoryUpdater::OnJobComplete(unsigned int jobID, bool success, CJob* job)
161 std::unique_lock<CCriticalSection> lock(m_criticalSection);
162 m_jobs.erase(std::find(m_jobs.begin(), m_jobs.end(), job));
163 if (m_jobs.empty())
165 CLog::Log(LOGDEBUG, "CRepositoryUpdater: done.");
166 m_doneEvent.Set();
168 VECADDONS updates = m_addonMgr.GetAvailableUpdates();
170 if (CAddonSystemSettings::GetInstance().GetAddonAutoUpdateMode() == AUTO_UPDATES_NOTIFY)
172 if (!updates.empty())
174 if (updates.size() == 1)
175 CGUIDialogKaiToast::QueueNotification(
176 updates[0]->Icon(), updates[0]->Name(), g_localizeStrings.Get(24068),
177 TOAST_DISPLAY_TIME, false, TOAST_DISPLAY_TIME);
178 else
179 CGUIDialogKaiToast::QueueNotification(
180 "", g_localizeStrings.Get(24001), g_localizeStrings.Get(24061),
181 TOAST_DISPLAY_TIME, false, TOAST_DISPLAY_TIME);
183 auto eventLog = CServiceBroker::GetEventLog();
184 for (const auto &addon : updates)
186 if (eventLog)
187 eventLog->Add(EventPtr(new CAddonManagementEvent(addon, 24068)));
192 if (CAddonSystemSettings::GetInstance().GetAddonAutoUpdateMode() == AUTO_UPDATES_ON)
194 m_addonMgr.CheckAndInstallAddonUpdates(false);
197 ScheduleUpdate(UpdateScheduleType::Regular);
199 m_events.Publish(RepositoryUpdated{});
203 bool CRepositoryUpdater::CheckForUpdates(bool showProgress)
205 VECADDONS addons;
206 if (m_addonMgr.GetAddons(addons, AddonType::REPOSITORY) && !addons.empty())
208 std::unique_lock<CCriticalSection> lock(m_criticalSection);
209 for (const auto& addon : addons)
210 CheckForUpdates(std::static_pointer_cast<ADDON::CRepository>(addon), showProgress);
212 return true;
215 return false;
218 static void SetProgressIndicator(CRepositoryUpdateJob* job)
220 auto dialog = CServiceBroker::GetGUI()->GetWindowManager().GetWindow<CGUIDialogExtendedProgressBar>(WINDOW_DIALOG_EXT_PROGRESS);
221 if (dialog)
222 job->SetProgressIndicators(dialog->GetHandle(g_localizeStrings.Get(24092)), nullptr);
225 void CRepositoryUpdater::CheckForUpdates(const ADDON::RepositoryPtr& repo, bool showProgress)
227 std::unique_lock<CCriticalSection> lock(m_criticalSection);
228 auto job = std::find_if(m_jobs.begin(), m_jobs.end(),
229 [&](CRepositoryUpdateJob* job){ return job->GetAddon()->ID() == repo->ID(); });
231 if (job == m_jobs.end())
233 auto* job = new CRepositoryUpdateJob(repo);
234 m_jobs.push_back(job);
235 m_doneEvent.Reset();
236 if (showProgress)
237 SetProgressIndicator(job);
238 CServiceBroker::GetJobManager()->AddJob(job, this, CJob::PRIORITY_LOW);
240 else
242 if (showProgress && !(*job)->HasProgressIndicator())
243 SetProgressIndicator(*job);
247 void CRepositoryUpdater::Await()
249 m_doneEvent.Wait();
252 void CRepositoryUpdater::OnTimeout()
254 //workaround
255 if (CServiceBroker::GetGUI()->GetWindowManager().GetActiveWindow() == WINDOW_FULLSCREEN_VIDEO ||
256 CServiceBroker::GetGUI()->GetWindowManager().GetActiveWindow() == WINDOW_FULLSCREEN_GAME ||
257 CServiceBroker::GetGUI()->GetWindowManager().GetActiveWindow() == WINDOW_SLIDESHOW)
259 CLog::Log(LOGDEBUG,"CRepositoryUpdater: busy playing. postponing scheduled update");
260 m_timer.RestartAsync(2min);
261 return;
264 CLog::Log(LOGDEBUG,"CRepositoryUpdater: running scheduled update");
265 CheckForUpdates();
268 void CRepositoryUpdater::OnSettingChanged(const std::shared_ptr<const CSetting>& setting)
270 if (setting->GetId() == CSettings::SETTING_ADDONS_AUTOUPDATES)
271 ScheduleUpdate(UpdateScheduleType::First);
274 CDateTime CRepositoryUpdater::LastUpdated() const
276 VECADDONS repos;
277 if (!m_addonMgr.GetAddons(repos, AddonType::REPOSITORY) || repos.empty())
278 return CDateTime();
280 CAddonDatabase db;
281 db.Open();
282 std::vector<CDateTime> updateTimes;
283 std::transform(
284 repos.begin(), repos.end(), std::back_inserter(updateTimes), [&](const AddonPtr& repo) {
285 const auto updateData = db.GetRepoUpdateData(repo->ID());
286 if (updateData.lastCheckedAt.IsValid() && updateData.lastCheckedVersion == repo->Version())
287 return updateData.lastCheckedAt;
288 return CDateTime();
291 return *std::min_element(updateTimes.begin(), updateTimes.end());
294 CDateTime CRepositoryUpdater::ClosestNextCheck() const
296 VECADDONS repos;
297 if (!m_addonMgr.GetAddons(repos, AddonType::REPOSITORY) || repos.empty())
298 return CDateTime();
300 CAddonDatabase db;
301 db.Open();
302 std::vector<CDateTime> nextCheckTimes;
303 std::transform(
304 repos.begin(), repos.end(), std::back_inserter(nextCheckTimes), [&](const AddonPtr& repo) {
305 const auto updateData = db.GetRepoUpdateData(repo->ID());
306 if (updateData.nextCheckAt.IsValid() && updateData.lastCheckedVersion == repo->Version())
307 return updateData.nextCheckAt;
308 return CDateTime();
311 return *std::min_element(nextCheckTimes.begin(), nextCheckTimes.end());
314 void CRepositoryUpdater::ScheduleUpdate(UpdateScheduleType scheduleType)
316 std::unique_lock<CCriticalSection> lock(m_criticalSection);
317 m_timer.Stop(true);
319 if (CAddonSystemSettings::GetInstance().GetAddonAutoUpdateMode() == AUTO_UPDATES_NEVER)
320 return;
322 if (!m_addonMgr.HasAddons(AddonType::REPOSITORY))
323 return;
325 int delta{1};
326 const auto nextCheck = ClosestNextCheck();
327 if (nextCheck.IsValid())
329 // Repos were already checked once and we know when to check next
330 delta = std::max(1, (nextCheck - CDateTime::GetCurrentDateTime()).GetSecondsTotal() * 1000);
331 CLog::Log(LOGDEBUG, "CRepositoryUpdater: closest next update check at {} (in {} s)",
332 nextCheck.GetAsLocalizedDateTime(), delta / 1000);
335 if (scheduleType == UpdateScheduleType::Regular)
337 // Enforce minimum hold-off time of 1 hour between regular updates - this is especially
338 // important to handle all sorts of failure cases (e.g., failure to update the add-on database)
339 // that would otherwise lead to an immediate new update attempt and continuous hammering of the servers.
340 delta = std::max(1 * 60 * 60 * 1'000, delta);
342 else
344 // delta must be positive and not zero (m_timer.Start() ignores 0 wait time)
345 delta = std::max(1, delta);
348 CLog::Log(LOGDEBUG, "CRepositoryUpdater: checking in {} ms", delta);
350 if (!m_timer.Start(std::chrono::milliseconds(delta)))
351 CLog::Log(LOGERROR,"CRepositoryUpdater: failed to start timer");