/*
* 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 "GUIDialogAddonInfo.h"
#include "dialogs/GUIDialogYesNo.h"
#include "dialogs/GUIDialogOK.h"
#include "addons/AddonManager.h"
#include "AddonDatabase.h"
#include "FileItem.h"
#include "filesystem/Directory.h"
#include "filesystem/SpecialProtocol.h"
#include "GUIDialogAddonSettings.h"
#include "dialogs/GUIDialogContextMenu.h"
#include "dialogs/GUIDialogTextViewer.h"
#include "GUIUserMessages.h"
#include "guilib/GUIWindowManager.h"
#include "input/Key.h"
#include "utils/JobManager.h"
#include "utils/FileOperationJob.h"
#include "utils/StringUtils.h"
#include "utils/URIUtils.h"
#include "utils/log.h"
#include "addons/AddonInstaller.h"
#include "pvr/PVRManager.h"
#include "Util.h"
#include "interfaces/Builtins.h"
#define CONTROL_BTN_INSTALL 6
#define CONTROL_BTN_ENABLE 7
#define CONTROL_BTN_UPDATE 8
#define CONTROL_BTN_SETTINGS 9
#define CONTROL_BTN_CHANGELOG 10
#define CONTROL_BTN_ROLLBACK 11
#define CONTROL_BTN_SELECT 12
using namespace std;
using namespace ADDON;
using namespace XFILE;
CGUIDialogAddonInfo::CGUIDialogAddonInfo(void)
: CGUIDialog(WINDOW_DIALOG_ADDON_INFO, "DialogAddonInfo.xml"), m_jobid(0)
{
m_item = CFileItemPtr(new CFileItem);
m_loadType = KEEP_IN_MEMORY;
}
CGUIDialogAddonInfo::~CGUIDialogAddonInfo(void)
{
}
bool CGUIDialogAddonInfo::OnMessage(CGUIMessage& message)
{
switch ( message.GetMessage() )
{
case GUI_MSG_WINDOW_DEINIT:
{
if (m_jobid)
CJobManager::GetInstance().CancelJob(m_jobid);
}
break;
case GUI_MSG_CLICKED:
{
int iControl = message.GetSenderId();
if (iControl == CONTROL_BTN_UPDATE)
{
OnUpdate();
return true;
}
if (iControl == CONTROL_BTN_INSTALL)
{
if (!m_localAddon)
{
OnInstall();
return true;
}
else
{
OnUninstall();
return true;
}
}
else if (iControl == CONTROL_BTN_SELECT)
{
OnLaunch();
return true;
}
else if (iControl == CONTROL_BTN_ENABLE)
{
OnEnable(!m_item->GetProperty("Addon.Enabled").asBoolean());
return true;
}
else if (iControl == CONTROL_BTN_SETTINGS)
{
OnSettings();
return true;
}
else if (iControl == CONTROL_BTN_CHANGELOG)
{
OnChangeLog();
return true;
}
else if (iControl == CONTROL_BTN_ROLLBACK)
{
OnRollback();
return true;
}
}
break;
default:
break;
}
return CGUIDialog::OnMessage(message);
}
bool CGUIDialogAddonInfo::OnAction(const CAction &action)
{
if (action.GetID() == ACTION_SHOW_INFO)
{
Close();
return true;
}
return CGUIDialog::OnAction(action);
}
void CGUIDialogAddonInfo::OnInitWindow()
{
UpdateControls();
CGUIDialog::OnInitWindow();
m_changelog = false;
}
void CGUIDialogAddonInfo::UpdateControls()
{
bool isInstalled = NULL != m_localAddon.get();
bool isEnabled = isInstalled && m_item->GetProperty("Addon.Enabled").asBoolean();
bool isUpdatable = isInstalled && m_item->GetProperty("Addon.UpdateAvail").asBoolean();
bool isExecutable = isInstalled && (m_localAddon->Type() == ADDON_PLUGIN || m_localAddon->Type() == ADDON_SCRIPT);
if (isInstalled)
GrabRollbackVersions();
bool canDisable = isInstalled && CAddonMgr::Get().CanAddonBeDisabled(m_localAddon->ID());
bool canInstall = !isInstalled && m_item->GetProperty("Addon.Broken").empty();
bool isRepo = (isInstalled && m_localAddon->Type() == ADDON_REPOSITORY) || (m_addon && m_addon->Type() == ADDON_REPOSITORY);
CONTROL_ENABLE_ON_CONDITION(CONTROL_BTN_INSTALL, canDisable || canInstall);
SET_CONTROL_LABEL(CONTROL_BTN_INSTALL, isInstalled ? 24037 : 24038);
CONTROL_ENABLE_ON_CONDITION(CONTROL_BTN_ENABLE, canDisable);
SET_CONTROL_LABEL(CONTROL_BTN_ENABLE, isEnabled ? 24021 : 24022);
CONTROL_ENABLE_ON_CONDITION(CONTROL_BTN_UPDATE, isUpdatable);
CONTROL_ENABLE_ON_CONDITION(CONTROL_BTN_SETTINGS, isInstalled && m_localAddon->HasSettings());
CONTROL_ENABLE_ON_CONDITION(CONTROL_BTN_SELECT, isExecutable);
CONTROL_ENABLE_ON_CONDITION(CONTROL_BTN_CHANGELOG, !isRepo);
CONTROL_ENABLE_ON_CONDITION(CONTROL_BTN_ROLLBACK, m_rollbackVersions.size() > 1);
}
void CGUIDialogAddonInfo::OnUpdate()
{
std::string referer = StringUtils::Format("Referer=%s-%s.zip",m_localAddon->ID().c_str(),m_localAddon->Version().asString().c_str());
CAddonInstaller::Get().Install(m_addon->ID(), true, referer); // force install
Close();
}
void CGUIDialogAddonInfo::OnInstall()
{
CAddonInstaller::Get().Install(m_addon->ID());
Close();
}
void CGUIDialogAddonInfo::OnLaunch()
{
if (!m_localAddon)
return;
CBuiltins::Execute("RunAddon(" + m_localAddon->ID() + ")");
Close();
}
bool CGUIDialogAddonInfo::PromptIfDependency(int heading, int line2)
{
if (!m_localAddon)
return false;
VECADDONS addons;
vector deps;
CAddonMgr::Get().GetAllAddons(addons);
for (VECADDONS::const_iterator it = addons.begin();
it != addons.end();++it)
{
ADDONDEPS::const_iterator i = (*it)->GetDeps().find(m_localAddon->ID());
if (i != (*it)->GetDeps().end() && !i->second.second) // non-optional dependency
deps.push_back((*it)->Name());
}
if (!deps.empty())
{
string line0 = StringUtils::Format(g_localizeStrings.Get(24046).c_str(), m_localAddon->Name().c_str());
string line1 = StringUtils::Join(deps, ", ");
CGUIDialogOK::ShowAndGetInput(heading, line0, line1, line2);
return true;
}
return false;
}
void CGUIDialogAddonInfo::OnUninstall()
{
if (!m_localAddon.get())
return;
if (!g_passwordManager.CheckMenuLock(WINDOW_ADDON_BROWSER))
return;
// ensure the addon is not a dependency of other installed addons
if (PromptIfDependency(24037, 24047))
return;
// prompt user to be sure
if (!CGUIDialogYesNo::ShowAndGetInput(24037, 750, 0, 0))
return;
// ensure the addon isn't disabled in our database
CAddonMgr::Get().DisableAddon(m_localAddon->ID(), false);
CJobManager::GetInstance().AddJob(new CAddonUnInstallJob(m_localAddon),
&CAddonInstaller::Get());
CAddonMgr::Get().RemoveAddon(m_localAddon->ID());
Close();
}
void CGUIDialogAddonInfo::OnEnable(bool enable)
{
if (!m_localAddon.get())
return;
if (!g_passwordManager.CheckMenuLock(WINDOW_ADDON_BROWSER))
return;
if (!enable && PromptIfDependency(24075, 24091))
return;
CAddonMgr::Get().DisableAddon(m_localAddon->ID(), !enable);
SetItem(m_item);
UpdateControls();
g_windowManager.SendMessage(GUI_MSG_NOTIFY_ALL, 0, 0, GUI_MSG_UPDATE);
}
void CGUIDialogAddonInfo::OnSettings()
{
CGUIDialogAddonSettings::ShowAndGetInput(m_localAddon);
}
void CGUIDialogAddonInfo::OnChangeLog()
{
CGUIDialogTextViewer* pDlgInfo = (CGUIDialogTextViewer*)g_windowManager.GetWindow(WINDOW_DIALOG_TEXT_VIEWER);
std::string name;
if (m_addon)
name = m_addon->Name();
else if (m_localAddon)
name = m_localAddon->Name();
pDlgInfo->SetHeading(g_localizeStrings.Get(24054)+" - "+name);
if (m_item->GetProperty("Addon.Changelog").empty())
{
pDlgInfo->SetText(g_localizeStrings.Get(13413));
CFileItemList items;
if (m_localAddon &&
!m_item->GetProperty("Addon.UpdateAvail").asBoolean())
{
items.Add(CFileItemPtr(new CFileItem(m_localAddon->ChangeLog(),false)));
}
else
items.Add(CFileItemPtr(new CFileItem(m_addon->ChangeLog(),false)));
items[0]->Select(true);
m_jobid = CJobManager::GetInstance().AddJob(
new CFileOperationJob(CFileOperationJob::ActionCopy,items,
"special://temp/"),this);
}
else
pDlgInfo->SetText(m_item->GetProperty("Addon.Changelog").asString());
m_changelog = true;
pDlgInfo->DoModal();
m_changelog = false;
}
void CGUIDialogAddonInfo::OnRollback()
{
if (!g_passwordManager.CheckMenuLock(WINDOW_ADDON_BROWSER))
return;
CGUIDialogContextMenu* dlg = (CGUIDialogContextMenu*)g_windowManager.GetWindow(WINDOW_DIALOG_CONTEXT_MENU);
CAddonDatabase database;
database.Open();
CContextButtons buttons;
for (unsigned int i=0;iVersion().asString())
label += " "+g_localizeStrings.Get(24094);
if (database.IsAddonBlacklisted(m_localAddon->ID(),label))
label += " "+g_localizeStrings.Get(24095);
buttons.Add(i,label);
}
int choice;
if ((choice=dlg->ShowAndGetChoice(buttons)) > -1)
{
// blacklist everything newer
for (unsigned int j=choice+1;jID(),m_rollbackVersions[j]);
std::string path = "special://home/addons/packages/";
path += m_localAddon->ID()+"-"+m_rollbackVersions[choice]+".zip";
// needed as cpluff won't downgrade
if (!m_localAddon->IsType(ADDON_SERVICE))
//we will handle this for service addons in CAddonInstallJob::OnPostInstall
CAddonMgr::Get().RemoveAddon(m_localAddon->ID());
CAddonInstaller::Get().InstallFromZip(path);
database.RemoveAddonFromBlacklist(m_localAddon->ID(),m_rollbackVersions[choice]);
Close();
}
}
bool CGUIDialogAddonInfo::ShowForItem(const CFileItemPtr& item)
{
CGUIDialogAddonInfo* dialog = (CGUIDialogAddonInfo*)g_windowManager.GetWindow(WINDOW_DIALOG_ADDON_INFO);
if (!dialog)
return false;
if (!dialog->SetItem(item))
return false;
dialog->DoModal();
return true;
}
bool CGUIDialogAddonInfo::SetItem(const CFileItemPtr& item)
{
*m_item = *item;
m_rollbackVersions.clear();
// grab the local addon, if it's available
m_localAddon.reset();
m_addon.reset();
if (CAddonMgr::Get().GetAddon(item->GetProperty("Addon.ID").asString(), m_localAddon)) // sets m_localAddon if installed regardless of enabled state
m_item->SetProperty("Addon.Enabled", "true");
else
m_item->SetProperty("Addon.Enabled", "false");
m_item->SetProperty("Addon.Installed", m_localAddon ? "true" : "false");
CAddonDatabase database;
database.Open();
database.GetAddon(item->GetProperty("Addon.ID").asString(),m_addon);
if (TranslateType(item->GetProperty("Addon.intType").asString()) == ADDON_REPOSITORY)
{
CAddonDatabase database;
database.Open();
VECADDONS addons;
if (m_addon)
database.GetRepository(m_addon->ID(), addons);
else if (m_localAddon) // sanity
database.GetRepository(m_localAddon->ID(), addons);
int tot=0;
for (int i = ADDON_UNKNOWN+1;iType() == (TYPE)i)
++num;
}
m_item->SetProperty("Repo." + TranslateType((TYPE)i), num);
tot += num;
}
m_item->SetProperty("Repo.Addons", tot);
}
return true;
}
void CGUIDialogAddonInfo::OnJobComplete(unsigned int jobID, bool success,
CJob* job)
{
if (!m_changelog)
return;
CGUIDialogTextViewer* pDlgInfo = (CGUIDialogTextViewer*)g_windowManager.GetWindow(WINDOW_DIALOG_TEXT_VIEWER);
m_jobid = 0;
if (!success)
{
pDlgInfo->SetText(g_localizeStrings.Get(195));
}
else
{
CFile file;
XFILE::auto_buffer buf;
if (file.LoadFile("special://temp/" +
URIUtils::GetFileName(((CFileOperationJob*)job)->GetItems()[0]->GetPath()), buf) > 0)
{
std::string str(buf.get(), buf.length());
m_item->SetProperty("Addon.Changelog", str);
pDlgInfo->SetText(str);
}
}
CGUIMessage msg(GUI_MSG_NOTIFY_ALL, WINDOW_DIALOG_TEXT_VIEWER, 0, GUI_MSG_UPDATE);
g_windowManager.SendThreadMessage(msg);
}
void CGUIDialogAddonInfo::GrabRollbackVersions()
{
CFileItemList items;
XFILE::CDirectory::GetDirectory("special://home/addons/packages/",items,".zip",DIR_FLAG_NO_FILE_DIRS);
items.Sort(SortByLabel, SortOrderAscending);
CAddonDatabase db;
db.Open();
for (int i=0;im_bIsFolder)
continue;
std::string ID, version;
AddonVersion::SplitFileName(ID,version,items[i]->GetLabel());
if (ID == m_localAddon->ID())
{
std::string hash, path(items[i]->GetPath());
if (db.GetPackageHash(m_localAddon->ID(), path, hash))
{
std::string md5 = CUtil::GetFileMD5(path);
if (md5 == hash)
m_rollbackVersions.push_back(version);
else /* The package has been corrupted */
{
CLog::Log(LOGWARNING, "%s: Removing corrupt addon package %s.", __FUNCTION__, path.c_str());
CFile::Delete(path);
db.RemovePackage(path);
}
}
}
}
}