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