2 * Copyright (C) 2005-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 "GUIWindowAddonBrowser.h"
11 #include "ContextMenuManager.h"
13 #include "FileItemList.h"
14 #include "GUIDialogAddonInfo.h"
15 #include "GUIUserMessages.h"
17 #include "ServiceBroker.h"
19 #include "addons/AddonInstaller.h"
20 #include "addons/AddonManager.h"
21 #include "addons/AddonSystemSettings.h"
22 #include "addons/IAddon.h"
23 #include "addons/RepositoryUpdater.h"
24 #include "addons/addoninfo/AddonType.h"
25 #include "dialogs/GUIDialogBusy.h"
26 #include "dialogs/GUIDialogFileBrowser.h"
27 #include "dialogs/GUIDialogSelect.h"
28 #include "dialogs/GUIDialogYesNo.h"
29 #include "filesystem/AddonsDirectory.h"
30 #include "guilib/GUIComponent.h"
31 #include "guilib/GUIWindowManager.h"
32 #include "guilib/LocalizeStrings.h"
33 #include "input/actions/ActionIDs.h"
34 #include "messaging/helpers/DialogHelper.h"
35 #include "platform/Platform.h"
36 #include "settings/MediaSourceSettings.h"
37 #include "settings/Settings.h"
38 #include "settings/SettingsComponent.h"
39 #include "storage/MediaManager.h"
40 #include "threads/IRunnable.h"
41 #include "utils/StringUtils.h"
42 #include "utils/Variant.h"
46 #define CONTROL_SETTINGS 5
47 #define CONTROL_FOREIGNFILTER 7
48 #define CONTROL_BROKENFILTER 8
49 #define CONTROL_CHECK_FOR_UPDATES 9
51 using namespace ADDON
;
52 using namespace XFILE
;
54 CGUIWindowAddonBrowser::CGUIWindowAddonBrowser(void)
55 : CGUIMediaWindow(WINDOW_ADDON_BROWSER
, "AddonBrowser.xml")
59 CGUIWindowAddonBrowser::~CGUIWindowAddonBrowser() = default;
61 bool CGUIWindowAddonBrowser::OnMessage(CGUIMessage
& message
)
63 switch (message
.GetMessage())
65 case GUI_MSG_WINDOW_DEINIT
:
67 CServiceBroker::GetRepositoryUpdater().Events().Unsubscribe(this);
68 CServiceBroker::GetAddonMgr().Events().Unsubscribe(this);
70 if (m_thumbLoader
.IsLoading())
71 m_thumbLoader
.StopThread();
74 case GUI_MSG_WINDOW_INIT
:
76 CServiceBroker::GetRepositoryUpdater().Events().Subscribe(this,
77 &CGUIWindowAddonBrowser::OnEvent
);
78 CServiceBroker::GetAddonMgr().Events().Subscribe(this, &CGUIWindowAddonBrowser::OnEvent
);
85 int iControl
= message
.GetSenderId();
86 if (iControl
== CONTROL_FOREIGNFILTER
)
88 const std::shared_ptr
<CSettings
> settings
=
89 CServiceBroker::GetSettingsComponent()->GetSettings();
90 settings
->ToggleBool(CSettings::SETTING_GENERAL_ADDONFOREIGNFILTER
);
95 else if (iControl
== CONTROL_BROKENFILTER
)
97 const std::shared_ptr
<CSettings
> settings
=
98 CServiceBroker::GetSettingsComponent()->GetSettings();
99 settings
->ToggleBool(CSettings::SETTING_GENERAL_ADDONBROKENFILTER
);
104 else if (iControl
== CONTROL_CHECK_FOR_UPDATES
)
106 CServiceBroker::GetRepositoryUpdater().CheckForUpdates(true);
109 else if (iControl
== CONTROL_SETTINGS
)
111 CServiceBroker::GetGUI()->GetWindowManager().ActivateWindow(WINDOW_SETTINGS_SYSTEM
,
115 else if (m_viewControl
.HasControl(iControl
)) // list/thumb control
118 int iItem
= m_viewControl
.GetSelectedItem();
119 int iAction
= message
.GetParam1();
121 // iItem is checked for validity inside these routines
122 if (iAction
== ACTION_SHOW_INFO
)
124 if (!m_vecItems
->Get(iItem
)->GetProperty("Addon.ID").empty())
125 return CGUIDialogAddonInfo::ShowForItem((*m_vecItems
)[iItem
]);
131 case GUI_MSG_NOTIFY_ALL
:
133 if (message
.GetParam1() == GUI_MSG_UPDATE_ITEM
&& IsActive() &&
134 message
.GetNumStringParams() == 1)
135 { // update this item
136 for (int i
= 0; i
< m_vecItems
->Size(); ++i
)
138 CFileItemPtr item
= m_vecItems
->Get(i
);
139 if (item
->GetProperty("Addon.ID") == message
.GetStringParam())
142 FormatAndSort(*m_vecItems
);
147 else if (message
.GetParam1() == GUI_MSG_UPDATE
&& IsActive())
154 return CGUIMediaWindow::OnMessage(message
);
157 void CGUIWindowAddonBrowser::SetProperties()
159 auto lastUpdated
= CServiceBroker::GetRepositoryUpdater().LastUpdated();
160 SetProperty("Updated", lastUpdated
.IsValid() ? lastUpdated
.GetAsLocalizedDateTime()
161 : g_localizeStrings
.Get(21337));
164 class UpdateAddons
: public IRunnable
168 for (const auto& addon
: CServiceBroker::GetAddonMgr().GetAvailableUpdates())
169 CAddonInstaller::GetInstance().InstallOrUpdate(addon
->ID(), BackgroundJob::CHOICE_YES
,
170 ModalJob::CHOICE_NO
);
174 class UpdateAllowedAddons
: public IRunnable
178 for (const auto& addon
: CServiceBroker::GetAddonMgr().GetAvailableUpdates())
179 if (CServiceBroker::GetAddonMgr().IsAutoUpdateable(addon
->ID()))
180 CAddonInstaller::GetInstance().InstallOrUpdate(addon
->ID(), BackgroundJob::CHOICE_YES
,
181 ModalJob::CHOICE_NO
);
185 void CGUIWindowAddonBrowser::OnEvent(const ADDON::CRepositoryUpdater::RepositoryUpdated
& event
)
187 CGUIMessage
msg(GUI_MSG_NOTIFY_ALL
, 0, 0, GUI_MSG_UPDATE
);
188 CServiceBroker::GetGUI()->GetWindowManager().SendThreadMessage(msg
);
191 void CGUIWindowAddonBrowser::OnEvent(const ADDON::AddonEvent
& event
)
193 CGUIMessage
msg(GUI_MSG_NOTIFY_ALL
, 0, 0, GUI_MSG_UPDATE
);
194 CServiceBroker::GetGUI()->GetWindowManager().SendThreadMessage(msg
);
197 void CGUIWindowAddonBrowser::InstallFromZip()
199 using namespace KODI::MESSAGING::HELPERS
;
201 if (!CServiceBroker::GetSettingsComponent()->GetSettings()->GetBool(
202 CSettings::SETTING_ADDONS_ALLOW_UNKNOWN_SOURCES
))
204 if (ShowYesNoDialogText(13106, 36617, 186, 10004) == DialogResponse::CHOICE_YES
)
205 CServiceBroker::GetGUI()->GetWindowManager().ActivateWindow(
206 WINDOW_SETTINGS_SYSTEM
, CSettings::SETTING_ADDONS_ALLOW_UNKNOWN_SOURCES
);
210 // pop up filebrowser to grab an installed folder
211 std::vector
<CMediaSource
> shares
= *CMediaSourceSettings::GetInstance().GetSources("files");
212 CServiceBroker::GetMediaManager().GetLocalDrives(shares
);
213 CServiceBroker::GetMediaManager().GetNetworkLocations(shares
);
215 if (CGUIDialogFileBrowser::ShowAndGetFile(shares
, "*.zip", g_localizeStrings
.Get(24041), path
))
217 CAddonInstaller::GetInstance().InstallFromZip(path
);
222 bool CGUIWindowAddonBrowser::OnClick(int iItem
, const std::string
& player
)
224 CFileItemPtr item
= m_vecItems
->Get(iItem
);
225 if (item
->GetPath() == "addons://install/")
230 if (item
->GetPath() == "addons://update_all/")
232 UpdateAddons updater
;
233 CGUIDialogBusy::Wait(&updater
, 100, true);
236 if (item
->GetPath() == "addons://update_allowed/")
238 UpdateAllowedAddons updater
;
239 CGUIDialogBusy::Wait(&updater
, 100, true);
242 if (!item
->m_bIsFolder
)
244 // cancel a downloading job
245 if (item
->HasProperty("Addon.Downloading"))
247 if (CGUIDialogYesNo::ShowAndGetInput(CVariant
{24000}, item
->GetProperty("Addon.Name"),
248 CVariant
{24066}, CVariant
{""}))
250 if (CAddonInstaller::GetInstance().Cancel(item
->GetProperty("Addon.ID").asString()))
256 CGUIDialogAddonInfo::ShowForItem(item
);
259 if (item
->IsPath("addons://search/"))
261 Update(item
->GetPath());
265 return CGUIMediaWindow::OnClick(iItem
, player
);
268 void CGUIWindowAddonBrowser::UpdateButtons()
270 const std::shared_ptr
<CSettings
> settings
= CServiceBroker::GetSettingsComponent()->GetSettings();
271 SET_CONTROL_SELECTED(GetID(), CONTROL_FOREIGNFILTER
,
272 settings
->GetBool(CSettings::SETTING_GENERAL_ADDONFOREIGNFILTER
));
273 SET_CONTROL_SELECTED(GetID(), CONTROL_BROKENFILTER
,
274 settings
->GetBool(CSettings::SETTING_GENERAL_ADDONBROKENFILTER
));
275 CONTROL_ENABLE(CONTROL_CHECK_FOR_UPDATES
);
276 CONTROL_ENABLE(CONTROL_SETTINGS
);
278 bool allowFilter
= CAddonsDirectory::IsRepoDirectory(CURL(m_vecItems
->GetPath()));
279 CONTROL_ENABLE_ON_CONDITION(CONTROL_FOREIGNFILTER
, allowFilter
);
280 CONTROL_ENABLE_ON_CONDITION(CONTROL_BROKENFILTER
, allowFilter
);
282 CGUIMediaWindow::UpdateButtons();
285 static bool IsForeign(const std::string
& languages
)
287 if (languages
.empty())
290 for (const auto& lang
: StringUtils::Split(languages
, " "))
292 if (lang
== "en" || lang
== g_langInfo
.GetLocale().GetLanguageCode() ||
293 lang
== g_langInfo
.GetLocale().ToShortString())
296 // for backwards compatibility
297 if (lang
== "no" && g_langInfo
.GetLocale().ToShortString() == "nb_NO")
303 bool CGUIWindowAddonBrowser::GetDirectory(const std::string
& strDirectory
, CFileItemList
& items
)
305 bool result
= CGUIMediaWindow::GetDirectory(strDirectory
, items
);
307 if (result
&& CAddonsDirectory::IsRepoDirectory(CURL(strDirectory
)))
309 const std::shared_ptr
<CSettings
> settings
=
310 CServiceBroker::GetSettingsComponent()->GetSettings();
311 if (settings
->GetBool(CSettings::SETTING_GENERAL_ADDONFOREIGNFILTER
))
314 while (i
< items
.Size())
316 auto prop
= items
[i
]->GetProperty("Addon.Language");
317 if (!prop
.isNull() && IsForeign(prop
.asString()))
323 if (settings
->GetBool(CSettings::SETTING_GENERAL_ADDONBROKENFILTER
))
325 for (int i
= items
.Size() - 1; i
>= 0; i
--)
327 if (items
[i
]->GetAddonInfo() &&
328 items
[i
]->GetAddonInfo()->LifecycleState() == AddonLifecycleState::BROKEN
)
330 //check if it's installed
332 if (!CServiceBroker::GetAddonMgr().GetAddon(items
[i
]->GetProperty("Addon.ID").asString(),
333 addon
, OnlyEnabled::CHOICE_YES
))
340 for (int i
= 0; i
< items
.Size(); ++i
)
341 UpdateStatus(items
[i
]);
346 void CGUIWindowAddonBrowser::UpdateStatus(const CFileItemPtr
& item
)
348 if (!item
|| item
->m_bIsFolder
)
351 unsigned int percent
;
352 bool downloadFinshed
;
353 if (CAddonInstaller::GetInstance().GetProgress(item
->GetProperty("Addon.ID").asString(), percent
,
356 std::string progress
= StringUtils::Format(
357 !downloadFinshed
? g_localizeStrings
.Get(24042) : g_localizeStrings
.Get(24044), percent
);
358 item
->SetProperty("Addon.Status", progress
);
359 item
->SetProperty("Addon.Downloading", true);
362 item
->ClearProperty("Addon.Downloading");
365 bool CGUIWindowAddonBrowser::Update(const std::string
& strDirectory
,
366 bool updateFilterPath
/* = true */)
368 if (m_thumbLoader
.IsLoading())
369 m_thumbLoader
.StopThread();
371 if (!CGUIMediaWindow::Update(strDirectory
, updateFilterPath
))
374 m_thumbLoader
.Load(*m_vecItems
);
379 int CGUIWindowAddonBrowser::SelectAddonID(AddonType type
,
380 std::string
& addonID
,
381 bool showNone
/* = false */,
382 bool showDetails
/* = true */,
383 bool showInstalled
/* = true */,
384 bool showInstallable
/*= false */,
385 bool showMore
/* = true */)
387 std::vector
<AddonType
> types
;
388 types
.push_back(type
);
389 return SelectAddonID(types
, addonID
, showNone
, showDetails
, showInstalled
, showInstallable
,
393 int CGUIWindowAddonBrowser::SelectAddonID(AddonType type
,
394 std::vector
<std::string
>& addonIDs
,
395 bool showNone
/* = false */,
396 bool showDetails
/* = true */,
397 bool multipleSelection
/* = true */,
398 bool showInstalled
/* = true */,
399 bool showInstallable
/* = false */,
400 bool showMore
/* = true */)
402 std::vector
<AddonType
> types
;
403 types
.push_back(type
);
404 return SelectAddonID(types
, addonIDs
, showNone
, showDetails
, multipleSelection
, showInstalled
,
405 showInstallable
, showMore
);
408 int CGUIWindowAddonBrowser::SelectAddonID(const std::vector
<AddonType
>& types
,
409 std::string
& addonID
,
410 bool showNone
/* = false */,
411 bool showDetails
/* = true */,
412 bool showInstalled
/* = true */,
413 bool showInstallable
/* = false */,
414 bool showMore
/* = true */)
416 std::vector
<std::string
> addonIDs
;
417 if (!addonID
.empty())
418 addonIDs
.push_back(addonID
);
419 int retval
= SelectAddonID(types
, addonIDs
, showNone
, showDetails
, false, showInstalled
,
420 showInstallable
, showMore
);
421 if (!addonIDs
.empty())
422 addonID
= addonIDs
.at(0);
428 int CGUIWindowAddonBrowser::SelectAddonID(const std::vector
<AddonType
>& types
,
429 std::vector
<std::string
>& addonIDs
,
430 bool showNone
/* = false */,
431 bool showDetails
/* = true */,
432 bool multipleSelection
/* = true */,
433 bool showInstalled
/* = true */,
434 bool showInstallable
/* = false */,
435 bool showMore
/* = true */)
437 // if we shouldn't show neither installed nor installable addons the list will be empty
438 if (!showInstalled
&& !showInstallable
)
441 // can't show the "Get More" button if we already show installable addons
445 CGUIDialogSelect
* dialog
=
446 CServiceBroker::GetGUI()->GetWindowManager().GetWindow
<CGUIDialogSelect
>(
447 WINDOW_DIALOG_SELECT
);
451 // get rid of any invalid addon types
452 std::vector
<AddonType
> validTypes(types
.size());
453 std::copy_if(types
.begin(), types
.end(), validTypes
.begin(),
454 [](AddonType type
) { return type
!= AddonType::UNKNOWN
; });
456 if (validTypes
.empty())
459 // get all addons to show
463 for (std::vector
<AddonType
>::const_iterator type
= validTypes
.begin(); type
!= validTypes
.end();
466 VECADDONS typeAddons
;
467 if (*type
== AddonType::AUDIO
)
468 CAddonsDirectory::GetScriptsAndPlugins("audio", typeAddons
);
469 else if (*type
== AddonType::EXECUTABLE
)
470 CAddonsDirectory::GetScriptsAndPlugins("executable", typeAddons
);
471 else if (*type
== AddonType::IMAGE
)
472 CAddonsDirectory::GetScriptsAndPlugins("image", typeAddons
);
473 else if (*type
== AddonType::VIDEO
)
474 CAddonsDirectory::GetScriptsAndPlugins("video", typeAddons
);
475 else if (*type
== AddonType::GAME
)
476 CAddonsDirectory::GetScriptsAndPlugins("game", typeAddons
);
478 CServiceBroker::GetAddonMgr().GetAddons(typeAddons
, *type
);
480 addons
.insert(addons
.end(), typeAddons
.begin(), typeAddons
.end());
484 if (showInstallable
|| showMore
)
486 VECADDONS installableAddons
;
487 if (CServiceBroker::GetAddonMgr().GetInstallableAddons(installableAddons
))
489 for (auto addon
= installableAddons
.begin(); addon
!= installableAddons
.end();)
491 AddonPtr pAddon
= *addon
;
493 // check if the addon matches one of the provided addon types
494 bool matchesType
= false;
495 for (std::vector
<AddonType
>::const_iterator type
= validTypes
.begin();
496 type
!= validTypes
.end(); ++type
)
498 if (pAddon
->HasType(*type
))
511 addon
= installableAddons
.erase(addon
);
515 addons
.insert(addons
.end(), installableAddons
.begin(), installableAddons
.end());
517 showMore
= !installableAddons
.empty();
521 if (addons
.empty() && !showNone
)
524 // turn the addons into items
525 std::map
<std::string
, AddonPtr
> addonMap
;
527 for (const auto& addon
: addons
)
529 const CFileItemPtr
item(CAddonsDirectory::FileItemFromAddon(addon
, addon
->ID()));
531 // Game controllers don't have specific summaries
532 if (addon
->Type() != AddonType::GAME_CONTROLLER
)
533 item
->SetLabel2(addon
->Summary());
535 if (!items
.Contains(item
->GetPath()))
538 addonMap
.insert(std::make_pair(item
->GetPath(), addon
));
542 if (items
.IsEmpty() && !showNone
)
546 for (std::vector
<AddonType
>::const_iterator type
= validTypes
.begin(); type
!= validTypes
.end();
549 if (!heading
.empty())
551 heading
+= CAddonInfo::TranslateType(*type
, true);
554 dialog
->SetHeading(CVariant
{std::move(heading
)});
556 dialog
->SetUseDetails(showDetails
);
558 if (multipleSelection
)
562 dialog
->EnableButton(true, 186);
565 dialog
->EnableButton(true, 21452);
569 CFileItemPtr
item(new CFileItem("", false));
570 item
->SetLabel(g_localizeStrings
.Get(231));
571 item
->SetLabel2(g_localizeStrings
.Get(24040));
572 item
->SetArt("icon", "DefaultAddonNone.png");
573 item
->SetSpecialSort(SortSpecialOnTop
);
576 items
.Sort(SortByLabel
, SortOrderAscending
);
578 if (!addonIDs
.empty())
580 for (std::vector
<std::string
>::const_iterator it
= addonIDs
.begin(); it
!= addonIDs
.end(); ++it
)
582 CFileItemPtr item
= items
.Get(*it
);
587 dialog
->SetItems(items
);
588 dialog
->SetMultiSelection(multipleSelection
);
591 // if the "Get More" button has been pressed and we haven't shown the
592 // installable addons so far show a list of installable addons
593 if (showMore
&& dialog
->IsButtonPressed())
594 return SelectAddonID(types
, addonIDs
, showNone
, showDetails
, multipleSelection
, false, true,
597 if (!dialog
->IsConfirmed())
601 for (int i
: dialog
->GetSelectedItems())
603 const CFileItemPtr
& item
= items
.Get(i
);
605 // check if one of the selected addons needs to be installed
608 std::map
<std::string
, AddonPtr
>::const_iterator itAddon
= addonMap
.find(item
->GetPath());
609 if (itAddon
!= addonMap
.end())
611 const AddonPtr
& addon
= itAddon
->second
;
613 // if the addon isn't installed we need to install it
614 if (!CServiceBroker::GetAddonMgr().IsAddonInstalled(addon
->ID()))
616 AddonPtr installedAddon
;
617 if (!CAddonInstaller::GetInstance().InstallModal(addon
->ID(), installedAddon
,
618 InstallModalPrompt::CHOICE_NO
))
622 // if the addon is disabled we need to enable it
623 if (CServiceBroker::GetAddonMgr().IsAddonDisabled(addon
->ID()))
624 CServiceBroker::GetAddonMgr().EnableAddon(addon
->ID());
628 addonIDs
.push_back(item
->GetPath());
633 std::string
CGUIWindowAddonBrowser::GetStartFolder(const std::string
& dir
)
635 if (StringUtils::StartsWith(dir
, "addons://"))
637 if (StringUtils::StartsWith(dir
, "addons://default_binary_addons_source/"))
639 const bool all
= CServiceBroker::GetPlatform().SupportsUserInstalledBinaryAddons();
640 std::string startDir
= dir
;
641 StringUtils::Replace(startDir
, "/default_binary_addons_source/", all
? "/all/" : "/user/");
648 return CGUIMediaWindow::GetStartFolder(dir
);