Merge pull request #3602 from jmarshallnz/broken_vs_deps
[vuplus_xbmc] / xbmc / addons / GUIDialogAddonInfo.cpp
1 /*
2  *      Copyright (C) 2005-2013 Team XBMC
3  *      http://xbmc.org
4  *
5  *  This Program is free software; you can redistribute it and/or modify
6  *  it under the terms of the GNU General Public License as published by
7  *  the Free Software Foundation; either version 2, or (at your option)
8  *  any later version.
9  *
10  *  This Program is distributed in the hope that it will be useful,
11  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
12  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13  *  GNU General Public License for more details.
14  *
15  *  You should have received a copy of the GNU General Public License
16  *  along with XBMC; see the file COPYING.  If not, see
17  *  <http://www.gnu.org/licenses/>.
18  *
19  */
20
21 #include "GUIDialogAddonInfo.h"
22 #include "dialogs/GUIDialogYesNo.h"
23 #include "dialogs/GUIDialogOK.h"
24 #include "addons/AddonManager.h"
25 #include "AddonDatabase.h"
26 #include "FileItem.h"
27 #include "filesystem/Directory.h"
28 #include "filesystem/SpecialProtocol.h"
29 #include "GUIDialogAddonSettings.h"
30 #include "dialogs/GUIDialogContextMenu.h"
31 #include "dialogs/GUIDialogTextViewer.h"
32 #include "GUIUserMessages.h"
33 #include "guilib/GUIWindowManager.h"
34 #include "guilib/Key.h"
35 #include "utils/JobManager.h"
36 #include "utils/FileOperationJob.h"
37 #include "utils/StringUtils.h"
38 #include "utils/URIUtils.h"
39 #include "addons/AddonInstaller.h"
40 #include "pvr/PVRManager.h"
41
42 #define CONTROL_BTN_INSTALL          6
43 #define CONTROL_BTN_ENABLE           7
44 #define CONTROL_BTN_UPDATE           8
45 #define CONTROL_BTN_SETTINGS         9
46 #define CONTROL_BTN_CHANGELOG       10
47 #define CONTROL_BTN_ROLLBACK        11
48
49 using namespace std;
50 using namespace ADDON;
51 using namespace XFILE;
52
53 CGUIDialogAddonInfo::CGUIDialogAddonInfo(void)
54     : CGUIDialog(WINDOW_DIALOG_ADDON_INFO, "DialogAddonInfo.xml")
55 {
56   m_item = CFileItemPtr(new CFileItem);
57   m_loadType = KEEP_IN_MEMORY;
58 }
59
60 CGUIDialogAddonInfo::~CGUIDialogAddonInfo(void)
61 {
62 }
63
64 bool CGUIDialogAddonInfo::OnMessage(CGUIMessage& message)
65 {
66   switch ( message.GetMessage() )
67   {
68   case GUI_MSG_WINDOW_DEINIT:
69     {
70       if (m_jobid)
71         CJobManager::GetInstance().CancelJob(m_jobid);
72     }
73     break;
74
75   case GUI_MSG_CLICKED:
76     {
77       int iControl = message.GetSenderId();
78       if (iControl == CONTROL_BTN_UPDATE)
79       {
80         OnUpdate();
81         return true;
82       }
83       if (iControl == CONTROL_BTN_INSTALL)
84       {
85         if (!m_localAddon)
86         {
87           OnInstall();
88           return true;
89         }
90         else
91         {
92           OnUninstall();
93           return true;
94         }
95       }
96       else if (iControl == CONTROL_BTN_ENABLE)
97       {
98         OnEnable(!m_item->GetProperty("Addon.Enabled").asBoolean());
99         return true;
100       }
101       else if (iControl == CONTROL_BTN_SETTINGS)
102       {
103         OnSettings();
104         return true;
105       }
106       else if (iControl == CONTROL_BTN_CHANGELOG)
107       {
108         OnChangeLog();
109         return true;
110       }
111       else if (iControl == CONTROL_BTN_ROLLBACK)
112       {
113         OnRollback();
114         return true;
115       }
116     }
117     break;
118 default:
119     break;
120   }
121
122   return CGUIDialog::OnMessage(message);
123 }
124
125 bool CGUIDialogAddonInfo::OnAction(const CAction &action)
126 {
127   if (action.GetID() == ACTION_SHOW_INFO)
128   {
129     Close();
130     return true;
131   }
132   return CGUIDialog::OnAction(action);
133 }
134
135 void CGUIDialogAddonInfo::OnInitWindow()
136 {
137   UpdateControls();
138   CGUIDialog::OnInitWindow();
139   m_changelog = false;
140 }
141
142 void CGUIDialogAddonInfo::UpdateControls()
143 {
144   CStdString xbmcPath = CSpecialProtocol::TranslatePath("special://xbmc/addons");
145   bool isInstalled = NULL != m_localAddon.get();
146   bool isSystem = isInstalled && m_localAddon->Path().Left(xbmcPath.size()).Equals(xbmcPath);
147   bool isEnabled = isInstalled && m_item->GetProperty("Addon.Enabled").asBoolean();
148   bool isUpdatable = isInstalled && m_item->GetProperty("Addon.UpdateAvail").asBoolean();
149   if (isInstalled)
150     GrabRollbackVersions();
151
152   // TODO: System addons should be able to be disabled
153   bool isPVR = isInstalled && m_localAddon->Type() == ADDON_PVRDLL;
154   bool canDisable = isInstalled && (!isSystem || isPVR) && !m_localAddon->IsInUse();
155   bool canInstall = !isInstalled && m_item->GetProperty("Addon.Broken").empty();
156   bool isRepo = (isInstalled && m_localAddon->Type() == ADDON_REPOSITORY) || (m_addon && m_addon->Type() == ADDON_REPOSITORY);
157
158   CONTROL_ENABLE_ON_CONDITION(CONTROL_BTN_INSTALL, canDisable || canInstall);
159   SET_CONTROL_LABEL(CONTROL_BTN_INSTALL, isInstalled ? 24037 : 24038);
160
161   CONTROL_ENABLE_ON_CONDITION(CONTROL_BTN_ENABLE, canDisable);
162   SET_CONTROL_LABEL(CONTROL_BTN_ENABLE, isEnabled ? 24021 : 24022);
163
164   CONTROL_ENABLE_ON_CONDITION(CONTROL_BTN_UPDATE, isUpdatable);
165   CONTROL_ENABLE_ON_CONDITION(CONTROL_BTN_SETTINGS, isInstalled && m_localAddon->HasSettings());
166   CONTROL_ENABLE_ON_CONDITION(CONTROL_BTN_CHANGELOG, !isRepo);
167   CONTROL_ENABLE_ON_CONDITION(CONTROL_BTN_ROLLBACK, m_rollbackVersions.size() > 1);
168 }
169
170 void CGUIDialogAddonInfo::OnUpdate()
171 {
172   CStdString referer;
173   referer.Format("Referer=%s-%s.zip",m_localAddon->ID().c_str(),m_localAddon->Version().c_str());
174   CAddonInstaller::Get().Install(m_addon->ID(), true, referer); // force install
175   Close();
176 }
177
178 void CGUIDialogAddonInfo::OnInstall()
179 {
180   CAddonInstaller::Get().Install(m_addon->ID());
181   Close();
182 }
183
184 bool CGUIDialogAddonInfo::PromptIfDependency(int heading, int line2)
185 {
186   if (!m_localAddon)
187     return false;
188
189   VECADDONS addons;
190   vector<string> deps;
191   CAddonMgr::Get().GetAllAddons(addons);
192   for (VECADDONS::const_iterator it  = addons.begin();
193        it != addons.end();++it)
194   {
195     ADDONDEPS::const_iterator i = (*it)->GetDeps().find(m_localAddon->ID());
196     if (i != (*it)->GetDeps().end() && !i->second.second) // non-optional dependency
197       deps.push_back((*it)->Name());
198   }
199
200   if (!deps.empty())
201   {
202     string line0 = StringUtils::Format(g_localizeStrings.Get(24046), m_localAddon->Name().c_str());
203     string line1 = StringUtils::Join(deps, ", ");
204     CGUIDialogOK::ShowAndGetInput(heading, line0, line1, line2);
205     return true;
206   }
207   return false;
208 }
209
210 void CGUIDialogAddonInfo::OnUninstall()
211 {
212   if (!m_localAddon.get())
213     return;
214
215   // ensure the addon is not a dependency of other installed addons
216   if (PromptIfDependency(24037, 24047))
217     return;
218
219   // prompt user to be sure
220   if (CGUIDialogYesNo::ShowAndGetInput(24037, 750, 0, 0))
221     return;
222
223   // ensure the addon isn't disabled in our database
224   CAddonMgr::Get().DisableAddon(m_localAddon->ID(), false);
225
226   CJobManager::GetInstance().AddJob(new CAddonUnInstallJob(m_localAddon),
227                                     &CAddonInstaller::Get());
228   CAddonMgr::Get().RemoveAddon(m_localAddon->ID());
229   Close();
230 }
231
232 void CGUIDialogAddonInfo::OnEnable(bool enable)
233 {
234   if (!m_localAddon.get())
235     return;
236
237   if (!enable && PromptIfDependency(24075, 24091))
238     return;
239
240   CAddonMgr::Get().DisableAddon(m_localAddon->ID(), !enable);
241   SetItem(m_item);
242   UpdateControls();
243   g_windowManager.SendMessage(GUI_MSG_NOTIFY_ALL, 0, 0, GUI_MSG_UPDATE);
244 }
245
246 void CGUIDialogAddonInfo::OnSettings()
247 {
248   CGUIDialogAddonSettings::ShowAndGetInput(m_localAddon);
249 }
250
251 void CGUIDialogAddonInfo::OnChangeLog()
252 {
253   CGUIDialogTextViewer* pDlgInfo = (CGUIDialogTextViewer*)g_windowManager.GetWindow(WINDOW_DIALOG_TEXT_VIEWER);
254   CStdString name;
255   if (m_addon)
256     name = m_addon->Name();
257   else if (m_localAddon)
258     name = m_localAddon->Name();
259   pDlgInfo->SetHeading(g_localizeStrings.Get(24054)+" - "+name);
260   if (m_item->GetProperty("Addon.Changelog").empty())
261   {
262     pDlgInfo->SetText(g_localizeStrings.Get(13413));
263     CFileItemList items;
264     if (m_localAddon && 
265         !m_item->GetProperty("Addon.UpdateAvail").asBoolean())
266     {
267       items.Add(CFileItemPtr(new CFileItem(m_localAddon->ChangeLog(),false)));
268     }
269     else
270       items.Add(CFileItemPtr(new CFileItem(m_addon->ChangeLog(),false)));
271     items[0]->Select(true);
272     m_jobid = CJobManager::GetInstance().AddJob(
273       new CFileOperationJob(CFileOperationJob::ActionCopy,items,
274                             "special://temp/"),this);
275   }
276   else
277     pDlgInfo->SetText(m_item->GetProperty("Addon.Changelog").asString());
278
279   m_changelog = true;
280   pDlgInfo->DoModal();
281   m_changelog = false;
282 }
283
284 void CGUIDialogAddonInfo::OnRollback()
285 {
286   CGUIDialogContextMenu* dlg = (CGUIDialogContextMenu*)g_windowManager.GetWindow(WINDOW_DIALOG_CONTEXT_MENU);
287   CAddonDatabase database;
288   database.Open();
289
290   CContextButtons buttons;
291   for (unsigned int i=0;i<m_rollbackVersions.size();++i)
292   {
293     CStdString label(m_rollbackVersions[i]);
294     if (m_rollbackVersions[i].Equals(m_localAddon->Version().c_str()))
295      label += " "+g_localizeStrings.Get(24094);
296    if (database.IsAddonBlacklisted(m_localAddon->ID(),label))
297      label += " "+g_localizeStrings.Get(24095);
298
299     buttons.Add(i,label);
300   }
301   int choice;
302   if ((choice=dlg->ShowAndGetChoice(buttons)) > -1)
303   {
304     // blacklist everything newer
305     for (unsigned int j=choice+1;j<m_rollbackVersions.size();++j)
306       database.BlacklistAddon(m_localAddon->ID(),m_rollbackVersions[j]);
307     CStdString path = "special://home/addons/packages/";
308     path += m_localAddon->ID()+"-"+m_rollbackVersions[choice]+".zip";
309     // needed as cpluff won't downgrade
310     if (!m_localAddon->IsType(ADDON_SERVICE))
311       //we will handle this for service addons in CAddonInstallJob::OnPostInstall
312       CAddonMgr::Get().RemoveAddon(m_localAddon->ID());
313     CAddonInstaller::Get().InstallFromZip(path);
314     database.RemoveAddonFromBlacklist(m_localAddon->ID(),m_rollbackVersions[choice]);
315     Close();
316   }
317 }
318
319 bool CGUIDialogAddonInfo::ShowForItem(const CFileItemPtr& item)
320 {
321   CGUIDialogAddonInfo* dialog = (CGUIDialogAddonInfo*)g_windowManager.GetWindow(WINDOW_DIALOG_ADDON_INFO);
322   if (!dialog)
323     return false;
324   if (!dialog->SetItem(item))
325     return false;
326
327   dialog->DoModal(); 
328   return true;
329 }
330
331 bool CGUIDialogAddonInfo::SetItem(const CFileItemPtr& item)
332 {
333   *m_item = *item;
334   m_rollbackVersions.clear();
335
336   // grab the local addon, if it's available
337   m_localAddon.reset();
338   m_addon.reset();
339   if (CAddonMgr::Get().GetAddon(item->GetProperty("Addon.ID").asString(), m_localAddon)) // sets m_addon if installed regardless of enabled state
340     m_item->SetProperty("Addon.Enabled", "true");
341   else
342     m_item->SetProperty("Addon.Enabled", "false");
343   m_item->SetProperty("Addon.Installed", m_addon ? "true" : "false");
344
345   CAddonDatabase database;
346   database.Open();
347   database.GetAddon(item->GetProperty("Addon.ID").asString(),m_addon);
348
349   if (TranslateType(item->GetProperty("Addon.intType").asString()) == ADDON_REPOSITORY)
350   {
351     CAddonDatabase database;
352     database.Open();
353     VECADDONS addons;
354     if (m_addon)
355       database.GetRepository(m_addon->ID(), addons);
356     else if (m_localAddon) // sanity
357       database.GetRepository(m_localAddon->ID(), addons);
358     int tot=0;
359     for (int i = ADDON_UNKNOWN+1;i<ADDON_VIZ_LIBRARY;++i)
360     {
361       int num=0;
362       for (unsigned int j=0;j<addons.size();++j)
363       {
364         if (addons[j]->Type() == (TYPE)i)
365           ++num;
366       }
367       m_item->SetProperty(CStdString("Repo.") + TranslateType((TYPE)i), num);
368       tot += num;
369     }
370     m_item->SetProperty("Repo.Addons", tot);
371   }
372   return true;
373 }
374
375 void CGUIDialogAddonInfo::OnJobComplete(unsigned int jobID, bool success,
376                                         CJob* job)
377 {
378   if (!m_changelog)
379     return;
380
381   CGUIDialogTextViewer* pDlgInfo = (CGUIDialogTextViewer*)g_windowManager.GetWindow(WINDOW_DIALOG_TEXT_VIEWER);
382
383   m_jobid = 0;
384   if (!success)
385   {
386     pDlgInfo->SetText(g_localizeStrings.Get(195));
387   }
388   else
389   {
390     CFile file;
391     if (file.Open("special://temp/"+
392       URIUtils::GetFileName(((CFileOperationJob*)job)->GetItems()[0]->GetPath())))
393     {
394       char* temp = new char[(size_t)file.GetLength()+1];
395       file.Read(temp,file.GetLength());
396       temp[file.GetLength()] = '\0';
397       m_item->SetProperty("Addon.Changelog",temp);
398       pDlgInfo->SetText(temp);
399       delete[] temp;
400     }
401   }
402   CGUIMessage msg(GUI_MSG_NOTIFY_ALL, WINDOW_DIALOG_TEXT_VIEWER, 0, GUI_MSG_UPDATE);
403   g_windowManager.SendThreadMessage(msg);
404 }
405
406 void CGUIDialogAddonInfo::GrabRollbackVersions()
407 {
408   CFileItemList items;
409   XFILE::CDirectory::GetDirectory("special://home/addons/packages/",items,".zip",DIR_FLAG_NO_FILE_DIRS);
410   items.Sort(SortByLabel, SortOrderAscending);
411   for (int i=0;i<items.Size();++i)
412   {
413     if (items[i]->m_bIsFolder)
414       continue;
415     CStdString ID, version;
416     AddonVersion::SplitFileName(ID,version,items[i]->GetLabel());
417     if (ID.Equals(m_localAddon->ID()))
418       m_rollbackVersions.push_back(version);
419   }
420 }