/*
* Copyright (C) 2005-2013 Team XBMC
* http://xbmc.org
*
* This Program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2, or (at your option)
* any later version.
*
* This Program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with XBMC; see the file COPYING. If not, see
* .
*
*/
#include "GUIWindowAddonBrowser.h"
#include "addons/AddonManager.h"
#include "addons/Repository.h"
#include "GUIDialogAddonInfo.h"
#include "GUIDialogAddonSettings.h"
#include "dialogs/GUIDialogBusy.h"
#include "dialogs/GUIDialogOK.h"
#include "dialogs/GUIDialogYesNo.h"
#include "dialogs/GUIDialogSelect.h"
#include "dialogs/GUIDialogFileBrowser.h"
#include "GUIUserMessages.h"
#include "guilib/GUIWindowManager.h"
#include "utils/URIUtils.h"
#include "URL.h"
#include "FileItem.h"
#include "filesystem/File.h"
#include "filesystem/Directory.h"
#include "filesystem/AddonsDirectory.h"
#include "addons/AddonInstaller.h"
#include "utils/JobManager.h"
#include "utils/log.h"
#include "threads/SingleLock.h"
#include "settings/Settings.h"
#include "settings/MediaSourceSettings.h"
#include "utils/StringUtils.h"
#include "AddonDatabase.h"
#include "settings/AdvancedSettings.h"
#include "storage/MediaManager.h"
#include "LangInfo.h"
#include "input/Key.h"
#include "ContextMenuManager.h"
#define CONTROL_AUTOUPDATE 5
#define CONTROL_SHUTUP 6
#define CONTROL_FOREIGNFILTER 7
#define CONTROL_BROKENFILTER 8
using namespace ADDON;
using namespace XFILE;
using namespace std;
CGUIWindowAddonBrowser::CGUIWindowAddonBrowser(void)
: CGUIMediaWindow(WINDOW_ADDON_BROWSER, "AddonBrowser.xml")
{
}
CGUIWindowAddonBrowser::~CGUIWindowAddonBrowser()
{
}
bool CGUIWindowAddonBrowser::OnMessage(CGUIMessage& message)
{
switch ( message.GetMessage() )
{
case GUI_MSG_WINDOW_DEINIT:
{
if (m_thumbLoader.IsLoading())
m_thumbLoader.StopThread();
}
break;
case GUI_MSG_WINDOW_INIT:
{
m_rootDir.AllowNonLocalSources(false);
// is this the first time the window is opened?
if (m_vecItems->GetPath() == "?" && message.GetStringParam().empty())
m_vecItems->SetPath("");
}
break;
case GUI_MSG_CLICKED:
{
int iControl = message.GetSenderId();
if (iControl == CONTROL_AUTOUPDATE)
{
const CGUIControl *control = GetControl(CONTROL_AUTOUPDATE);
if (control && control->GetControlType() == CGUIControl::GUICONTROL_BUTTON)
CSettings::Get().SetInt("general.addonupdates", (CSettings::Get().GetInt("general.addonupdates")+1) % AUTO_UPDATES_MAX);
else
CSettings::Get().SetInt("general.addonupdates", (CSettings::Get().GetInt("general.addonupdates") == 0) ? 1 : 0);
UpdateButtons();
return true;
}
else if (iControl == CONTROL_SHUTUP)
{
CSettings::Get().ToggleBool("general.addonnotifications");
CSettings::Get().Save();
return true;
}
else if (iControl == CONTROL_FOREIGNFILTER)
{
CSettings::Get().ToggleBool("general.addonforeignfilter");
CSettings::Get().Save();
Refresh();
return true;
}
else if (iControl == CONTROL_BROKENFILTER)
{
CSettings::Get().ToggleBool("general.addonbrokenfilter");
CSettings::Get().Save();
Refresh();
return true;
}
else if (m_viewControl.HasControl(iControl)) // list/thumb control
{
// get selected item
int iItem = m_viewControl.GetSelectedItem();
int iAction = message.GetParam1();
// iItem is checked for validity inside these routines
if (iAction == ACTION_SHOW_INFO)
{
if (!m_vecItems->Get(iItem)->GetProperty("Addon.ID").empty())
return CGUIDialogAddonInfo::ShowForItem((*m_vecItems)[iItem]);
return false;
}
}
}
break;
case GUI_MSG_NOTIFY_ALL:
{
if (message.GetParam1() == GUI_MSG_UPDATE_ITEM && IsActive() && message.GetNumStringParams() == 1)
{ // update this item
for (int i = 0; i < m_vecItems->Size(); ++i)
{
CFileItemPtr item = m_vecItems->Get(i);
if (item->GetProperty("Addon.ID") == message.GetStringParam())
{
SetItemLabel2(item);
return true;
}
}
}
}
break;
default:
break;
}
return CGUIMediaWindow::OnMessage(message);
}
void CGUIWindowAddonBrowser::GetContextButtons(int itemNumber, CContextButtons& buttons)
{
if (itemNumber < 0 || itemNumber >= m_vecItems->Size())
return;
CFileItemPtr pItem = m_vecItems->Get(itemNumber);
if (!pItem->IsPath("addons://enabled/"))
buttons.Add(CONTEXT_BUTTON_SCAN,24034);
AddonPtr addon;
if (!CAddonMgr::Get().GetAddon(pItem->GetProperty("Addon.ID").asString(), addon, ADDON_UNKNOWN, false)) // allow disabled addons
return;
if (addon->Type() == ADDON_REPOSITORY && pItem->m_bIsFolder)
{
buttons.Add(CONTEXT_BUTTON_SCAN,24034);
buttons.Add(CONTEXT_BUTTON_REFRESH,24035);
}
buttons.Add(CONTEXT_BUTTON_INFO,24003);
if (addon->HasSettings())
buttons.Add(CONTEXT_BUTTON_SETTINGS,24020);
CContextMenuManager::Get().AddVisibleItems(pItem, buttons);
}
bool CGUIWindowAddonBrowser::OnContextButton(int itemNumber,
CONTEXT_BUTTON button)
{
CFileItemPtr pItem = m_vecItems->Get(itemNumber);
if (pItem->IsPath("addons://enabled/"))
{
if (button == CONTEXT_BUTTON_SCAN)
{
CAddonMgr::Get().FindAddons();
return true;
}
}
AddonPtr addon;
if (CAddonMgr::Get().GetAddon(pItem->GetProperty("Addon.ID").asString(), addon, ADDON_UNKNOWN, false))
{
if (button == CONTEXT_BUTTON_SETTINGS)
return CGUIDialogAddonSettings::ShowAndGetInput(addon);
if (button == CONTEXT_BUTTON_REFRESH)
{
CAddonDatabase database;
database.Open();
database.DeleteRepository(addon->ID());
button = CONTEXT_BUTTON_SCAN;
}
if (button == CONTEXT_BUTTON_SCAN)
{
CAddonInstaller::Get().UpdateRepos(true);
return true;
}
if (button == CONTEXT_BUTTON_INFO)
{
CGUIDialogAddonInfo::ShowForItem(pItem);
return true;
}
}
return CGUIMediaWindow::OnContextButton(itemNumber, button);
}
class UpdateAddons : public IRunnable
{
virtual void Run()
{
VECADDONS addons;
CAddonMgr::Get().GetAllOutdatedAddons(addons, true); // get local
for (VECADDONS::iterator i = addons.begin(); i != addons.end(); ++i)
{
std::string referer = StringUtils::Format("Referer=%s-%s.zip",(*i)->ID().c_str(),(*i)->Version().asString().c_str());
CAddonInstaller::Get().Install((*i)->ID(), true, referer); // force install
}
}
};
class UpdateRepos : public IRunnable
{
virtual void Run()
{
CAddonInstaller::Get().UpdateRepos(true, true);
}
};
bool CGUIWindowAddonBrowser::OnClick(int iItem)
{
CFileItemPtr item = m_vecItems->Get(iItem);
if (item->GetPath() == "addons://install/")
{
// pop up filebrowser to grab an installed folder
VECSOURCES shares = *CMediaSourceSettings::Get().GetSources("files");
g_mediaManager.GetLocalDrives(shares);
g_mediaManager.GetNetworkLocations(shares);
std::string path;
if (CGUIDialogFileBrowser::ShowAndGetFile(shares, "*.zip", g_localizeStrings.Get(24041), path))
CAddonInstaller::Get().InstallFromZip(path);
return true;
}
else if (item->GetPath() == "addons://check/")
{
// perform the check for updates
UpdateRepos updater;
if (CGUIDialogBusy::Wait(&updater))
Refresh();
return true;
}
if (item->GetPath() == "addons://update_all/")
{
// fire off a threaded update of all addons
UpdateAddons updater;
if (CGUIDialogBusy::Wait(&updater))
return Update("addons://downloading/");
return true;
}
if (!item->m_bIsFolder)
{
// cancel a downloading job
if (item->HasProperty("Addon.Downloading"))
{
if (CGUIDialogYesNo::ShowAndGetInput(g_localizeStrings.Get(24000),
item->GetProperty("Addon.Name").asString(),
g_localizeStrings.Get(24066),""))
{
if (CAddonInstaller::Get().Cancel(item->GetProperty("Addon.ID").asString()))
Refresh();
}
return true;
}
CGUIDialogAddonInfo::ShowForItem(item);
return true;
}
if (item->IsPath("addons://search/"))
return Update(item->GetPath());
return CGUIMediaWindow::OnClick(iItem);
}
void CGUIWindowAddonBrowser::UpdateButtons()
{
const CGUIControl *control = GetControl(CONTROL_AUTOUPDATE);
if (control && control->GetControlType() == CGUIControl::GUICONTROL_BUTTON)
{ // set label
CSettingInt *setting = (CSettingInt *)CSettings::Get().GetSetting("general.addonupdates");
if (setting)
{
const StaticIntegerSettingOptions& options = setting->GetOptions();
for (StaticIntegerSettingOptions::const_iterator it = options.begin(); it != options.end(); ++it)
{
if (it->second == setting->GetValue())
{
SET_CONTROL_LABEL(CONTROL_AUTOUPDATE, it->first);
break;
}
}
}
}
else
{ // old skin with toggle button - set on if auto updates are on
SET_CONTROL_SELECTED(GetID(),CONTROL_AUTOUPDATE, CSettings::Get().GetInt("general.addonupdates") == AUTO_UPDATES_ON);
}
SET_CONTROL_SELECTED(GetID(),CONTROL_SHUTUP, CSettings::Get().GetBool("general.addonnotifications"));
SET_CONTROL_SELECTED(GetID(),CONTROL_FOREIGNFILTER, CSettings::Get().GetBool("general.addonforeignfilter"));
SET_CONTROL_SELECTED(GetID(),CONTROL_BROKENFILTER, CSettings::Get().GetBool("general.addonbrokenfilter"));
CGUIMediaWindow::UpdateButtons();
}
static bool FilterVar(bool valid, const CVariant& variant,
const std::string& check)
{
if (!valid)
return false;
if (variant.isNull() || variant.asString().empty())
return false;
std::string regions = variant.asString();
return regions.find(check) == std::string::npos;
}
bool CGUIWindowAddonBrowser::GetDirectory(const std::string& strDirectory,
CFileItemList& items)
{
bool result;
if (URIUtils::PathEquals(strDirectory, "addons://downloading/"))
{
VECADDONS addons;
CAddonInstaller::Get().GetInstallList(addons);
CURL url(strDirectory);
CAddonsDirectory::GenerateListing(url,addons,items);
result = true;
items.SetProperty("reponame",g_localizeStrings.Get(24067));
items.SetPath(strDirectory);
if (m_guiState.get() && !m_guiState->HideParentDirItems())
{
CFileItemPtr pItem(new CFileItem(".."));
pItem->SetPath(m_history.GetParentPath());
pItem->m_bIsFolder = true;
pItem->m_bIsShareOrDrive = false;
items.AddFront(pItem, 0);
}
}
else
{
result = CGUIMediaWindow::GetDirectory(strDirectory,items);
if (CSettings::Get().GetBool("general.addonforeignfilter"))
{
int i=0;
while (i < items.Size())
{
if (!FilterVar(true, items[i]->GetProperty("Addon.Language"), "en") ||
!FilterVar(true, items[i]->GetProperty("Addon.Language"), g_langInfo.GetLanguageLocale()))
{
i++;
}
else
items.Remove(i);
}
}
if (CSettings::Get().GetBool("general.addonbrokenfilter"))
{
for (int i = items.Size() - 1; i >= 0; i--)
{
if (!items[i]->GetProperty("Addon.Broken").empty())
{ //check if it's installed
AddonPtr addon;
if (!CAddonMgr::Get().GetAddon(items[i]->GetProperty("Addon.ID").asString(), addon))
items.Remove(i);
}
}
}
}
if (strDirectory.empty() && CAddonInstaller::Get().IsDownloading())
{
CFileItemPtr item(new CFileItem("addons://downloading/",true));
item->SetLabel(g_localizeStrings.Get(24067));
item->SetLabelPreformated(true);
item->SetIconImage("DefaultNetwork.png");
items.Add(item);
}
items.SetContent("addons");
for (int i=0;im_bIsFolder) return;
unsigned int percent;
if (CAddonInstaller::Get().GetProgress(item->GetProperty("Addon.ID").asString(), percent))
{
std::string progress = StringUtils::Format(g_localizeStrings.Get(24042).c_str(), percent);
item->SetProperty("Addon.Status", progress);
item->SetProperty("Addon.Downloading", true);
}
else
item->ClearProperty("Addon.Downloading");
item->SetLabel2(item->GetProperty("Addon.Status").asString());
// to avoid the view state overriding label 2
item->SetLabelPreformated(true);
}
bool CGUIWindowAddonBrowser::Update(const std::string &strDirectory, bool updateFilterPath /* = true */)
{
if (m_thumbLoader.IsLoading())
m_thumbLoader.StopThread();
if (!CGUIMediaWindow::Update(strDirectory, updateFilterPath))
return false;
m_thumbLoader.Load(*m_vecItems);
return true;
}
int CGUIWindowAddonBrowser::SelectAddonID(TYPE type, std::string &addonID, bool showNone /* = false */, bool showDetails /* = true */, bool showInstalled /* = true */, bool showInstallable /*= false */, bool showMore /* = true */)
{
vector types;
types.push_back(type);
return SelectAddonID(types, addonID, showNone, showDetails, showInstalled, showInstallable, showMore);
}
int CGUIWindowAddonBrowser::SelectAddonID(ADDON::TYPE type, vector &addonIDs, bool showNone /* = false */, bool showDetails /* = true */, bool multipleSelection /* = true */, bool showInstalled /* = true */, bool showInstallable /* = false */, bool showMore /* = true */)
{
vector types;
types.push_back(type);
return SelectAddonID(types, addonIDs, showNone, showDetails, multipleSelection, showInstalled, showInstallable, showMore);
}
int CGUIWindowAddonBrowser::SelectAddonID(const vector &types, std::string &addonID, bool showNone /* = false */, bool showDetails /* = true */, bool showInstalled /* = true */, bool showInstallable /* = false */, bool showMore /* = true */)
{
vector addonIDs;
if (!addonID.empty())
addonIDs.push_back(addonID);
int retval = SelectAddonID(types, addonIDs, showNone, showDetails, false, showInstalled, showInstallable, showMore);
if (addonIDs.size() > 0)
addonID = addonIDs.at(0);
else
addonID = "";
return retval;
}
int CGUIWindowAddonBrowser::SelectAddonID(const vector &types, vector &addonIDs, bool showNone /* = false */, bool showDetails /* = true */, bool multipleSelection /* = true */, bool showInstalled /* = true */, bool showInstallable /* = false */, bool showMore /* = true */)
{
// if we shouldn't show neither installed nor installable addons the list will be empty
if (!showInstalled && !showInstallable)
return 0;
// can't show the "Get More" button if we already show installable addons
if (showInstallable)
showMore = false;
CGUIDialogSelect *dialog = (CGUIDialogSelect*)g_windowManager.GetWindow(WINDOW_DIALOG_SELECT);
if (!dialog)
return 0;
// get rid of any invalid addon types
vector validTypes(types.size());
std::copy_if(types.begin(), types.end(), validTypes.begin(), [](ADDON::TYPE type) { return type != ADDON_UNKNOWN; });
if (validTypes.empty())
return 0;
// get all addons to show
VECADDONS addons;
if (showInstalled)
{
for (vector::const_iterator type = validTypes.begin(); type != validTypes.end(); ++type)
{
VECADDONS typeAddons;
if (*type == ADDON_AUDIO)
CAddonsDirectory::GetScriptsAndPlugins("audio", typeAddons);
else if (*type == ADDON_EXECUTABLE)
CAddonsDirectory::GetScriptsAndPlugins("executable", typeAddons);
else if (*type == ADDON_IMAGE)
CAddonsDirectory::GetScriptsAndPlugins("image", typeAddons);
else if (*type == ADDON_VIDEO)
CAddonsDirectory::GetScriptsAndPlugins("video", typeAddons);
else
CAddonMgr::Get().GetAddons(*type, typeAddons);
addons.insert(addons.end(), typeAddons.begin(), typeAddons.end());
}
}
if (showInstallable || showMore)
{
VECADDONS installableAddons;
CAddonDatabase database;
if (database.Open() && database.GetAddons(installableAddons))
{
for (ADDON::IVECADDONS addon = installableAddons.begin(); addon != installableAddons.end();)
{
AddonPtr pAddon = *addon;
// check if the addon matches one of the provided addon types
bool matchesType = false;
for (vector::const_iterator type = validTypes.begin(); type != validTypes.end(); ++type)
{
if (pAddon->IsType(*type))
{
matchesType = true;
break;
}
}
// only show addons that match one of the provided addon types and that aren't disabled
if (matchesType && !CAddonMgr::Get().IsAddonDisabled(pAddon->ID()))
{
// check if the addon is installed
bool isInstalled = CAddonMgr::Get().IsAddonInstalled(pAddon->ID());
// check if the addon is installed or can be installed
if ((showInstallable || showMore) && !isInstalled && CAddonMgr::Get().CanAddonBeInstalled(pAddon))
{
++addon;
continue;
}
}
addon = installableAddons.erase(addon);
}
if (showInstallable)
addons.insert(addons.end(), installableAddons.begin(), installableAddons.end());
else if (showMore)
showMore = !installableAddons.empty();
}
}
if (addons.empty() && !showNone)
return 0;
// turn the addons into items
std::map addonMap;
CFileItemList items;
for (ADDON::IVECADDONS addon = addons.begin(); addon != addons.end(); ++addon)
{
CFileItemPtr item(CAddonsDirectory::FileItemFromAddon(*addon, ""));
if (!items.Contains(item->GetPath()))
{
items.Add(item);
addonMap.insert(std::make_pair(item->GetPath(), *addon));
}
}
if (items.IsEmpty() && !showNone)
return 0;
std::string heading;
for (vector::const_iterator type = validTypes.begin(); type != validTypes.end(); ++type)
{
if (!heading.empty())
heading += ", ";
heading += TranslateType(*type, true);
}
dialog->SetHeading(heading);
dialog->Reset();
dialog->SetUseDetails(showDetails);
if (multipleSelection)
{
showNone = false;
showMore = false;
dialog->EnableButton(true, 186);
}
else if (showMore)
dialog->EnableButton(true, 21452);
if (showNone)
{
CFileItemPtr item(new CFileItem("", false));
item->SetLabel(g_localizeStrings.Get(231));
item->SetLabel2(g_localizeStrings.Get(24040));
item->SetIconImage("DefaultAddonNone.png");
item->SetSpecialSort(SortSpecialOnTop);
items.Add(item);
}
items.Sort(SortByLabel, SortOrderAscending);
if (addonIDs.size() > 0)
{
for (vector::const_iterator it = addonIDs.begin(); it != addonIDs.end() ; ++it)
{
CFileItemPtr item = items.Get(*it);
if (item)
item->Select(true);
}
}
dialog->SetItems(&items);
dialog->SetMultiSelection(multipleSelection);
dialog->DoModal();
// if the "Get More" button has been pressed and we haven't shown the
// installable addons so far show a list of installable addons
if (showMore&& dialog->IsButtonPressed())
return SelectAddonID(types, addonIDs, showNone, showDetails, multipleSelection, false, true, false);
if (!dialog->IsConfirmed())
return 0;
addonIDs.clear();
const CFileItemList& list = dialog->GetSelectedItems();
for (int i = 0 ; i < list.Size() ; i++)
{
const CFileItemPtr& item = list.Get(i);
// check if one of the selected addons needs to be installed
if (showInstallable)
{
std::map::const_iterator itAddon = addonMap.find(item->GetPath());
if (itAddon != addonMap.end())
{
const AddonPtr& addon = itAddon->second;
// if the addon isn't installed we need to install it
if (!CAddonMgr::Get().IsAddonInstalled(addon->ID()))
{
AddonPtr installedAddon;
if (!CAddonInstaller::Get().InstallModal(addon->ID(), installedAddon, false))
continue;
}
// if the addon is disabled we need to enable it
if (CAddonMgr::Get().IsAddonDisabled(addon->ID()))
CAddonMgr::Get().DisableAddon(addon->ID(), false);
}
}
addonIDs.push_back(item->GetPath());
}
return 1;
}
std::string CGUIWindowAddonBrowser::GetStartFolder(const std::string &dir)
{
if (URIUtils::PathStarts(dir, "addons://"))
return dir;
return CGUIMediaWindow::GetStartFolder(dir);
}