Merge pull request #4314 from MartijnKaijser/beta1
[vuplus_xbmc] / xbmc / addons / GUIWindowAddonBrowser.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 "GUIWindowAddonBrowser.h"
22 #include "addons/AddonManager.h"
23 #include "addons/Repository.h"
24 #include "GUIDialogAddonInfo.h"
25 #include "GUIDialogAddonSettings.h"
26 #include "dialogs/GUIDialogBusy.h"
27 #include "dialogs/GUIDialogYesNo.h"
28 #include "dialogs/GUIDialogSelect.h"
29 #include "dialogs/GUIDialogFileBrowser.h"
30 #include "GUIUserMessages.h"
31 #include "guilib/GUIWindowManager.h"
32 #include "utils/URIUtils.h"
33 #include "URL.h"
34 #include "FileItem.h"
35 #include "filesystem/File.h"
36 #include "filesystem/Directory.h"
37 #include "filesystem/AddonsDirectory.h"
38 #include "addons/AddonInstaller.h"
39 #include "utils/JobManager.h"
40 #include "utils/log.h"
41 #include "threads/SingleLock.h"
42 #include "settings/Settings.h"
43 #include "settings/MediaSourceSettings.h"
44 #include "utils/StringUtils.h"
45 #include "AddonDatabase.h"
46 #include "settings/AdvancedSettings.h"
47 #include "storage/MediaManager.h"
48 #include "LangInfo.h"
49 #include "guilib/Key.h"
50
51 #define CONTROL_AUTOUPDATE    5
52 #define CONTROL_SHUTUP        6
53 #define CONTROL_FOREIGNFILTER 7
54
55 using namespace ADDON;
56 using namespace XFILE;
57 using namespace std;
58
59 CGUIWindowAddonBrowser::CGUIWindowAddonBrowser(void)
60 : CGUIMediaWindow(WINDOW_ADDON_BROWSER, "AddonBrowser.xml")
61 {
62 }
63
64 CGUIWindowAddonBrowser::~CGUIWindowAddonBrowser()
65 {
66 }
67
68 bool CGUIWindowAddonBrowser::OnMessage(CGUIMessage& message)
69 {
70   switch ( message.GetMessage() )
71   {
72     case GUI_MSG_WINDOW_DEINIT:
73     {
74       if (m_thumbLoader.IsLoading())
75         m_thumbLoader.StopThread();
76     }
77     break;
78   case GUI_MSG_WINDOW_INIT:
79     {
80       m_rootDir.AllowNonLocalSources(false);
81
82       // is this the first time the window is opened?
83       if (m_vecItems->GetPath() == "?" && message.GetStringParam().empty())
84         m_vecItems->SetPath("");
85     }
86     break;
87   case GUI_MSG_CLICKED:
88     {
89       int iControl = message.GetSenderId();
90       if (iControl == CONTROL_AUTOUPDATE)
91       {
92         CSettings::Get().ToggleBool("general.addonautoupdate");
93         return true;
94       }
95       else if (iControl == CONTROL_SHUTUP)
96       {
97         CSettings::Get().ToggleBool("general.addonnotifications");
98         CSettings::Get().Save();
99         return true;
100       }
101       else if (iControl == CONTROL_FOREIGNFILTER)
102       {
103         CSettings::Get().ToggleBool("general.addonforeignfilter");
104         CSettings::Get().Save();
105         Refresh();
106         return true;
107       }
108       else if (m_viewControl.HasControl(iControl))  // list/thumb control
109       {
110         // get selected item
111         int iItem = m_viewControl.GetSelectedItem();
112         int iAction = message.GetParam1();
113
114         // iItem is checked for validity inside these routines
115         if (iAction == ACTION_SHOW_INFO)
116         {
117           if (!m_vecItems->Get(iItem)->GetProperty("Addon.ID").empty())
118             return CGUIDialogAddonInfo::ShowForItem((*m_vecItems)[iItem]);
119           return false;
120         }
121       }
122     }
123     break;
124   case GUI_MSG_NOTIFY_ALL:
125     {
126       if (message.GetParam1() == GUI_MSG_UPDATE_ITEM && IsActive() && message.GetNumStringParams() == 1)
127       { // update this item
128         for (int i = 0; i < m_vecItems->Size(); ++i)
129         {
130           CFileItemPtr item = m_vecItems->Get(i);
131           if (item->GetProperty("Addon.ID") == message.GetStringParam())
132           {
133             SetItemLabel2(item);
134             return true;
135           }
136         }
137       }
138     }
139     break;
140    default:
141      break;
142   }
143   return CGUIMediaWindow::OnMessage(message);
144 }
145
146 void CGUIWindowAddonBrowser::GetContextButtons(int itemNumber,
147                                                CContextButtons& buttons)
148 {
149   CFileItemPtr pItem = m_vecItems->Get(itemNumber);
150   if (pItem->GetPath().Equals("addons://enabled/"))
151     buttons.Add(CONTEXT_BUTTON_SCAN,24034);
152   
153   AddonPtr addon;
154   if (!CAddonMgr::Get().GetAddon(pItem->GetProperty("Addon.ID").asString(), addon, ADDON_UNKNOWN, false)) // allow disabled addons
155     return;
156
157   if (addon->Type() == ADDON_REPOSITORY && pItem->m_bIsFolder)
158   {
159     buttons.Add(CONTEXT_BUTTON_SCAN,24034);
160     buttons.Add(CONTEXT_BUTTON_REFRESH,24035);
161   }
162
163   buttons.Add(CONTEXT_BUTTON_INFO,24003);
164
165   if (addon->HasSettings())
166     buttons.Add(CONTEXT_BUTTON_SETTINGS,24020);
167 }
168
169 bool CGUIWindowAddonBrowser::OnContextButton(int itemNumber,
170                                              CONTEXT_BUTTON button)
171 {
172   CFileItemPtr pItem = m_vecItems->Get(itemNumber);
173   if (pItem->GetPath().Equals("addons://enabled/"))
174   {
175     if (button == CONTEXT_BUTTON_SCAN)
176     {
177       CAddonMgr::Get().FindAddons();
178       return true;
179     }
180   }
181   AddonPtr addon;
182   if (!CAddonMgr::Get().GetAddon(pItem->GetProperty("Addon.ID").asString(), addon, ADDON_UNKNOWN, false)) // allow disabled addons
183     return false;
184
185   if (button == CONTEXT_BUTTON_SETTINGS)
186     return CGUIDialogAddonSettings::ShowAndGetInput(addon);
187
188   if (button == CONTEXT_BUTTON_REFRESH)
189   {
190     CAddonDatabase database;
191     database.Open();
192     database.DeleteRepository(addon->ID());
193     button = CONTEXT_BUTTON_SCAN;
194   }
195
196   if (button == CONTEXT_BUTTON_SCAN)
197   {
198     RepositoryPtr repo = boost::dynamic_pointer_cast<CRepository>(addon);
199     CAddonInstaller::Get().UpdateRepos(true);
200     return true;
201   }
202
203   if (button == CONTEXT_BUTTON_INFO)
204   {
205     CGUIDialogAddonInfo::ShowForItem(pItem);
206     return true;
207   }
208
209   return CGUIMediaWindow::OnContextButton(itemNumber, button);
210 }
211
212 class UpdateAddons : public IRunnable
213 {
214   virtual void Run()
215   {
216     VECADDONS addons;
217     CAddonMgr::Get().GetAllOutdatedAddons(addons, true); // get local
218     for (VECADDONS::iterator i = addons.begin(); i != addons.end(); ++i)
219     {
220       CStdString referer = StringUtils::Format("Referer=%s-%s.zip",(*i)->ID().c_str(),(*i)->Version().c_str());
221       CAddonInstaller::Get().Install((*i)->ID(), true, referer); // force install
222     }
223   }
224 };
225
226 bool CGUIWindowAddonBrowser::OnClick(int iItem)
227 {
228   CFileItemPtr item = m_vecItems->Get(iItem);
229   if (item->GetPath() == "addons://install/")
230   {
231     // pop up filebrowser to grab an installed folder
232     VECSOURCES shares = *CMediaSourceSettings::Get().GetSources("files");
233     g_mediaManager.GetLocalDrives(shares);
234     g_mediaManager.GetNetworkLocations(shares);
235     CStdString path;
236     if (CGUIDialogFileBrowser::ShowAndGetFile(shares, "*.zip", g_localizeStrings.Get(24041), path))
237       CAddonInstaller::Get().InstallFromZip(path);
238     return true;
239   }
240   if (item->GetPath() == "addons://update_all/")
241   {
242     // fire off a threaded update of all addons
243     UpdateAddons updater;
244     if (CGUIDialogBusy::Wait(&updater))
245       return Update("addons://downloading/");
246     return true;
247   }
248   if (!item->m_bIsFolder)
249   {
250     // cancel a downloading job
251     if (item->HasProperty("Addon.Downloading"))
252     {
253       if (CGUIDialogYesNo::ShowAndGetInput(g_localizeStrings.Get(24000),
254                                            item->GetProperty("Addon.Name").asString(),
255                                            g_localizeStrings.Get(24066),""))
256       {
257         if (CAddonInstaller::Get().Cancel(item->GetProperty("Addon.ID").asString()))
258           Refresh();
259       }
260       return true;
261     }
262
263     CGUIDialogAddonInfo::ShowForItem(item);
264     return true;
265   }
266   if (item->GetPath().Equals("addons://search/"))
267     return Update(item->GetPath());
268
269   return CGUIMediaWindow::OnClick(iItem);
270 }
271
272 void CGUIWindowAddonBrowser::UpdateButtons()
273 {
274   SET_CONTROL_SELECTED(GetID(),CONTROL_AUTOUPDATE, CSettings::Get().GetBool("general.addonautoupdate"));
275   SET_CONTROL_SELECTED(GetID(),CONTROL_SHUTUP, CSettings::Get().GetBool("general.addonnotifications"));
276   SET_CONTROL_SELECTED(GetID(),CONTROL_FOREIGNFILTER, CSettings::Get().GetBool("general.addonforeignfilter"));
277   CGUIMediaWindow::UpdateButtons();
278 }
279
280 static bool FilterVar(bool valid, const CVariant& variant,
281                                   const std::string& check)
282 {
283   if (!valid)
284     return false;
285
286   if (variant.isNull() || variant.asString().empty())
287     return false;
288
289   std::string regions = variant.asString();
290   return regions.find(check) == std::string::npos;
291 }
292
293 bool CGUIWindowAddonBrowser::GetDirectory(const CStdString& strDirectory,
294                                           CFileItemList& items)
295 {
296   bool result;
297   if (strDirectory.Equals("addons://downloading/"))
298   {
299     VECADDONS addons;
300     CAddonInstaller::Get().GetInstallList(addons);
301
302     CURL url(strDirectory);
303     CAddonsDirectory::GenerateListing(url,addons,items);
304     result = true;
305     items.SetProperty("reponame",g_localizeStrings.Get(24067));
306     items.SetPath(strDirectory);
307
308     if (m_guiState.get() && !m_guiState->HideParentDirItems())
309     {
310       CFileItemPtr pItem(new CFileItem(".."));
311       pItem->SetPath(m_history.GetParentPath());
312       pItem->m_bIsFolder = true;
313       pItem->m_bIsShareOrDrive = false;
314       items.AddFront(pItem, 0);
315     }
316
317   }
318   else
319   {
320     result = CGUIMediaWindow::GetDirectory(strDirectory,items);
321     if (CSettings::Get().GetBool("general.addonforeignfilter"))
322     {
323       int i=0;
324       while (i < items.Size())
325       {
326         if (!FilterVar(CSettings::Get().GetBool("general.addonforeignfilter"),
327                       items[i]->GetProperty("Addon.Language"), "en") ||
328             !FilterVar(CSettings::Get().GetBool("general.addonforeignfilter"),
329                       items[i]->GetProperty("Addon.Language"),
330                       g_langInfo.GetLanguageLocale()))
331         {
332           i++;
333         }
334         else
335           items.Remove(i);
336       }
337     }
338   }
339
340   if (strDirectory.empty() && CAddonInstaller::Get().IsDownloading())
341   {
342     CFileItemPtr item(new CFileItem("addons://downloading/",true));
343     item->SetLabel(g_localizeStrings.Get(24067));
344     item->SetLabelPreformated(true);
345     item->SetIconImage("DefaultNetwork.png");
346     items.Add(item);
347   }
348
349   items.SetContent("addons");
350
351   for (int i=0;i<items.Size();++i)
352     SetItemLabel2(items[i]);
353
354   return result;
355 }
356
357 void CGUIWindowAddonBrowser::SetItemLabel2(CFileItemPtr item)
358 {
359   if (!item || item->m_bIsFolder) return;
360   unsigned int percent;
361   if (CAddonInstaller::Get().GetProgress(item->GetProperty("Addon.ID").asString(), percent))
362   {
363     CStdString progress = StringUtils::Format(g_localizeStrings.Get(24042).c_str(), percent);
364     item->SetProperty("Addon.Status", progress);
365     item->SetProperty("Addon.Downloading", true);
366   }
367   else
368     item->ClearProperty("Addon.Downloading");
369   item->SetLabel2(item->GetProperty("Addon.Status").asString());
370   // to avoid the view state overriding label 2
371   item->SetLabelPreformated(true);
372 }
373
374 bool CGUIWindowAddonBrowser::Update(const CStdString &strDirectory, bool updateFilterPath /* = true */)
375 {
376   if (m_thumbLoader.IsLoading())
377     m_thumbLoader.StopThread();
378
379   if (!CGUIMediaWindow::Update(strDirectory, updateFilterPath))
380     return false;
381
382   m_thumbLoader.Load(*m_vecItems);
383
384   return true;
385 }
386
387 int CGUIWindowAddonBrowser::SelectAddonID(TYPE type, CStdString &addonID, bool showNone /*= false*/)
388 {
389   vector<ADDON::TYPE> types;
390   types.push_back(type);
391   return SelectAddonID(types, addonID, showNone);
392 }
393
394 int CGUIWindowAddonBrowser::SelectAddonID(ADDON::TYPE type, CStdStringArray &addonIDs, bool showNone /*= false*/, bool multipleSelection /*= true*/)
395 {
396   vector<ADDON::TYPE> types;
397   types.push_back(type);
398   return SelectAddonID(types, addonIDs, showNone, multipleSelection);
399 }
400
401 int CGUIWindowAddonBrowser::SelectAddonID(const vector<ADDON::TYPE> &types, CStdString &addonID, bool showNone /*= false*/)
402 {
403   CStdStringArray addonIDs;
404   if (!addonID.empty())
405     addonIDs.push_back(addonID);
406   int retval = SelectAddonID(types, addonIDs, showNone, false);
407   if (addonIDs.size() > 0)
408     addonID = addonIDs.at(0);
409   else
410     addonID = "";
411   return retval;
412 }
413
414 int CGUIWindowAddonBrowser::SelectAddonID(const vector<ADDON::TYPE> &types, CStdStringArray &addonIDs, bool showNone /*= false*/, bool multipleSelection /*= true*/)
415 {
416   CGUIDialogSelect *dialog = (CGUIDialogSelect*)g_windowManager.GetWindow(WINDOW_DIALOG_SELECT);
417   if (!dialog)
418     return 0;
419
420   CFileItemList items;
421   CStdString heading;
422   int iTypes = 0;
423   for (vector<ADDON::TYPE>::const_iterator it = types.begin(); it != types.end(); ++it)
424   {
425     if (*it == ADDON_UNKNOWN)
426       continue;
427     ADDON::VECADDONS addons;
428     iTypes++;
429     if (*it == ADDON_AUDIO)
430       CAddonsDirectory::GetScriptsAndPlugins("audio",addons);
431     else if (*it == ADDON_EXECUTABLE)
432       CAddonsDirectory::GetScriptsAndPlugins("executable",addons);
433     else if (*it == ADDON_IMAGE)
434       CAddonsDirectory::GetScriptsAndPlugins("image",addons);
435     else if (*it == ADDON_VIDEO)
436       CAddonsDirectory::GetScriptsAndPlugins("video",addons);
437     else
438       CAddonMgr::Get().GetAddons(*it, addons);
439     for (ADDON::IVECADDONS it2 = addons.begin() ; it2 != addons.end() ; ++it2)
440     {
441       CFileItemPtr item(CAddonsDirectory::FileItemFromAddon(*it2, ""));
442       if (!items.Contains(item->GetPath()))
443         items.Add(item);
444     }
445
446     if (!heading.empty())
447       heading += ", ";
448     heading += TranslateType(*it, true);
449   }
450
451   if (iTypes == 0)
452     return 0;
453
454   dialog->SetHeading(heading);
455   dialog->Reset();
456   dialog->SetUseDetails(true);
457   if (multipleSelection)
458     showNone = false;
459   if (multipleSelection || iTypes > 1)
460     dialog->EnableButton(true, 186);
461   else
462     dialog->EnableButton(true, 21452);
463   if (showNone)
464   {
465     CFileItemPtr item(new CFileItem("", false));
466     item->SetLabel(g_localizeStrings.Get(231));
467     item->SetLabel2(g_localizeStrings.Get(24040));
468     item->SetIconImage("DefaultAddonNone.png");
469     item->SetSpecialSort(SortSpecialOnTop);
470     items.Add(item);
471   }
472   items.Sort(SortByLabel, SortOrderAscending);
473
474   if (addonIDs.size() > 0)
475   {
476     for (CStdStringArray::const_iterator it = addonIDs.begin(); it != addonIDs.end() ; it++)
477     {
478       CFileItemPtr item = items.Get(*it);
479       if (item)
480         item->Select(true);
481     }
482   }
483   dialog->SetItems(&items);
484   dialog->SetMultiSelection(multipleSelection);
485   dialog->DoModal();
486   if (!multipleSelection && iTypes == 1 && dialog->IsButtonPressed())
487   { // switch to the addons browser.
488     vector<CStdString> params;
489     params.push_back("addons://all/"+TranslateType(types[0],false)+"/");
490     params.push_back("return");
491     g_windowManager.ActivateWindow(WINDOW_ADDON_BROWSER, params);
492     return 2;
493   }
494   if (!dialog->IsConfirmed())
495     return 0;
496   addonIDs.clear();
497   const CFileItemList& list = dialog->GetSelectedItems();
498   for (int i = 0 ; i < list.Size() ; i++)
499     addonIDs.push_back(list.Get(i)->GetPath());
500   return 1;
501 }
502
503 CStdString CGUIWindowAddonBrowser::GetStartFolder(const CStdString &dir)
504 {
505   if (StringUtils::StartsWithNoCase(dir, "addons://"))
506     return dir;
507   return CGUIMediaWindow::GetStartFolder(dir);
508 }