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.
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"
40 using namespace std::chrono_literals
;
45 class CRepositoryUpdateJob
: public CProgressJob
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
; }
54 const RepositoryPtr m_repo
;
57 bool CRepositoryUpdateJob::DoWork()
59 CLog::Log(LOGDEBUG
, "CRepositoryUpdateJob[{}] checking for updates.", m_repo
->ID());
60 CAddonDatabase database
;
63 std::string oldChecksum
;
64 if (database
.GetRepoChecksum(m_repo
->ID(), oldChecksum
) == -1)
67 const CAddonDatabase::RepoUpdateData updateData
{database
.GetRepoUpdateData(m_repo
->ID())};
68 if (updateData
.lastCheckedVersion
!= m_repo
->Version())
71 std::string newChecksum
;
72 std::vector
<AddonInfoPtr
> addons
;
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
)));
83 if (status
== CRepository::STATUS_ERROR
)
86 if (status
== CRepository::STATUS_NOT_MODIFIED
)
88 CLog::Log(LOGDEBUG
, "CRepositoryUpdateJob[{}] checksum not changed.", m_repo
->ID());
94 CTextureDatabase textureDB
;
96 textureDB
.BeginMultipleExecute();
98 for (const auto& addon
: addons
)
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
);
125 CRepositoryUpdater::CRepositoryUpdater(CAddonMgr
& addonMgr
) :
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
));
165 CLog::Log(LOGDEBUG
, "CRepositoryUpdater: done.");
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
);
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
)
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
)
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
);
218 static void SetProgressIndicator(CRepositoryUpdateJob
* job
)
220 auto dialog
= CServiceBroker::GetGUI()->GetWindowManager().GetWindow
<CGUIDialogExtendedProgressBar
>(WINDOW_DIALOG_EXT_PROGRESS
);
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
);
237 SetProgressIndicator(job
);
238 CServiceBroker::GetJobManager()->AddJob(job
, this, CJob::PRIORITY_LOW
);
242 if (showProgress
&& !(*job
)->HasProgressIndicator())
243 SetProgressIndicator(*job
);
247 void CRepositoryUpdater::Await()
252 void CRepositoryUpdater::OnTimeout()
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
);
264 CLog::Log(LOGDEBUG
,"CRepositoryUpdater: running scheduled update");
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
277 if (!m_addonMgr
.GetAddons(repos
, AddonType::REPOSITORY
) || repos
.empty())
282 std::vector
<CDateTime
> updateTimes
;
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
;
291 return *std::min_element(updateTimes
.begin(), updateTimes
.end());
294 CDateTime
CRepositoryUpdater::ClosestNextCheck() const
297 if (!m_addonMgr
.GetAddons(repos
, AddonType::REPOSITORY
) || repos
.empty())
302 std::vector
<CDateTime
> nextCheckTimes
;
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
;
311 return *std::min_element(nextCheckTimes
.begin(), nextCheckTimes
.end());
314 void CRepositoryUpdater::ScheduleUpdate(UpdateScheduleType scheduleType
)
316 std::unique_lock
<CCriticalSection
> lock(m_criticalSection
);
319 if (CAddonSystemSettings::GetInstance().GetAddonAutoUpdateMode() == AUTO_UPDATES_NEVER
)
322 if (!m_addonMgr
.HasAddons(AddonType::REPOSITORY
))
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
);
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");