1 /*
2  *  Copyright (C) 2005-2018 Team Kodi
3  *  This file is part of Kodi - https://kodi.tv
4  *
5  *  SPDX-License-Identifier: GPL-2.0-or-later
6  *  See LICENSES/README.md for more information.
7  */
8 
9 #include "GUIMediaWindow.h"
10 #include "Application.h"
11 #include "ServiceBroker.h"
12 #include "messaging/ApplicationMessenger.h"
13 #include "ContextMenuManager.h"
14 #include "FileItemListModification.h"
15 #include "GUIPassword.h"
16 #include "GUIUserMessages.h"
17 #include "PartyModeManager.h"
18 #include "PlayListPlayer.h"
19 #include "URL.h"
20 #include "Util.h"
21 #include "addons/AddonManager.h"
22 #include "addons/PluginSource.h"
23 #if defined(TARGET_ANDROID)
24 #include "platform/android/activity/XBMCApp.h"
25 #endif
26 #include "dialogs/GUIDialogBusy.h"
27 #include "dialogs/GUIDialogKaiToast.h"
28 #include "dialogs/GUIDialogMediaFilter.h"
29 #include "dialogs/GUIDialogProgress.h"
30 #include "dialogs/GUIDialogSmartPlaylistEditor.h"
31 #include "filesystem/File.h"
32 #include "filesystem/DirectoryFactory.h"
33 #include "filesystem/FileDirectoryFactory.h"
34 #include "filesystem/MultiPathDirectory.h"
35 #include "filesystem/PluginDirectory.h"
36 #include "filesystem/SmartPlaylistDirectory.h"
37 #include "guilib/GUIComponent.h"
38 #include "guilib/GUIEditControl.h"
39 #include "guilib/GUIKeyboardFactory.h"
40 #include "guilib/GUIWindowManager.h"
41 #include "guilib/LocalizeStrings.h"
42 #include "interfaces/generic/ScriptInvocationManager.h"
43 #include "input/Key.h"
44 #include "messaging/helpers/DialogOKHelper.h"
45 #include "network/Network.h"
46 #include "playlists/PlayList.h"
47 #include "profiles/ProfileManager.h"
48 #include "settings/AdvancedSettings.h"
49 #include "settings/Settings.h"
50 #include "settings/SettingsComponent.h"
51 #include "storage/MediaManager.h"
52 #include "threads/IRunnable.h"
53 #include "threads/SystemClock.h"
54 #include "utils/FileUtils.h"
55 #include "utils/LabelFormatter.h"
56 #include "utils/log.h"
57 #include "utils/SortUtils.h"
58 #include "utils/StringUtils.h"
59 #include "utils/URIUtils.h"
60 #include "utils/Variant.h"
61 #include "view/GUIViewState.h"
62 #include <inttypes.h>
63 
64 #define CONTROL_BTNVIEWASICONS       2
65 #define CONTROL_BTNSORTBY            3
66 #define CONTROL_BTNSORTASC           4
67 #define CONTROL_BTN_FILTER          19
68 
69 #define CONTROL_LABELFILES          12
70 
71 #define PROPERTY_PATH_DB            "path.db"
72 #define PROPERTY_SORT_ORDER         "sort.order"
73 #define PROPERTY_SORT_ASCENDING     "sort.ascending"
74 
75 #define PLUGIN_REFRESH_DELAY 200
76 
77 using namespace ADDON;
78 using namespace KODI::MESSAGING;
79 
80 namespace
81 {
82 class CGetDirectoryItems : public IRunnable
83 {
84 public:
CGetDirectoryItems(XFILE::CVirtualDirectory & dir,CURL & url,CFileItemList & items,bool useDir)85   CGetDirectoryItems(XFILE::CVirtualDirectory &dir, CURL &url, CFileItemList &items, bool useDir)
86   : m_dir(dir), m_url(url), m_items(items), m_useDir(useDir)
87   {
88   }
89 
Run()90   void Run() override
91   {
92     m_result = m_dir.GetDirectory(m_url, m_items, m_useDir, true);
93   }
94 
Cancel()95   void Cancel() override
96   {
97     m_dir.CancelDirectory();
98   }
99 
100   bool m_result = false;
101 
102 protected:
103   XFILE::CVirtualDirectory &m_dir;
104   CURL m_url;
105   CFileItemList &m_items;
106   bool m_useDir;
107 };
108 }
109 
CGUIMediaWindow(int id,const char * xmlFile)110 CGUIMediaWindow::CGUIMediaWindow(int id, const char *xmlFile)
111     : CGUIWindow(id, xmlFile)
112 {
113   m_loadType = KEEP_IN_MEMORY;
114   m_vecItems = new CFileItemList;
115   m_unfilteredItems = new CFileItemList;
116   m_vecItems->SetPath("?");
117   m_iLastControl = -1;
118   m_canFilterAdvanced = false;
119 
120   m_guiState.reset(CGUIViewState::GetViewState(GetID(), *m_vecItems));
121 }
122 
~CGUIMediaWindow()123 CGUIMediaWindow::~CGUIMediaWindow()
124 {
125   delete m_vecItems;
126   delete m_unfilteredItems;
127 }
128 
Load(TiXmlElement * pRootElement)129 bool CGUIMediaWindow::Load(TiXmlElement *pRootElement)
130 {
131   bool retVal = CGUIWindow::Load(pRootElement);
132 
133   if (!retVal)
134     return false;
135 
136   // configure our view control
137   m_viewControl.Reset();
138   m_viewControl.SetParentWindow(GetID());
139   TiXmlElement *element = pRootElement->FirstChildElement("views");
140   if (element && element->FirstChild())
141   { // format is <views>50,29,51,95</views>
142     const std::string &allViews = element->FirstChild()->ValueStr();
143     std::vector<std::string> views = StringUtils::Split(allViews, ",");
144     for (std::vector<std::string>::const_iterator i = views.begin(); i != views.end(); ++i)
145     {
146       int controlID = atol(i->c_str());
147       CGUIControl *control = GetControl(controlID);
148       if (control && control->IsContainer())
149         m_viewControl.AddView(control);
150     }
151   }
152   m_viewControl.SetViewControlID(CONTROL_BTNVIEWASICONS);
153 
154   return true;
155 }
156 
OnWindowLoaded()157 void CGUIMediaWindow::OnWindowLoaded()
158 {
159   SendMessage(GUI_MSG_SET_TYPE, CONTROL_BTN_FILTER, CGUIEditControl::INPUT_TYPE_FILTER);
160   CGUIWindow::OnWindowLoaded();
161   SetupShares();
162 }
163 
OnWindowUnload()164 void CGUIMediaWindow::OnWindowUnload()
165 {
166   CGUIWindow::OnWindowUnload();
167   m_viewControl.Reset();
168 }
169 
GetCurrentListItem(int offset)170 CFileItemPtr CGUIMediaWindow::GetCurrentListItem(int offset)
171 {
172   int item = m_viewControl.GetSelectedItem();
173   if (!m_vecItems->Size() || item < 0)
174     return CFileItemPtr();
175   item = (item + offset) % m_vecItems->Size();
176   if (item < 0) item += m_vecItems->Size();
177   return m_vecItems->Get(item);
178 }
179 
OnAction(const CAction & action)180 bool CGUIMediaWindow::OnAction(const CAction &action)
181 {
182   if (action.GetID() == ACTION_PARENT_DIR)
183   {
184     GoParentFolder();
185     return true;
186   }
187 
188   // the non-contextual menu can be called at any time
189   if (action.GetID() == ACTION_CONTEXT_MENU && !m_viewControl.HasControl(GetFocusedControlID()))
190   {
191     OnPopupMenu(-1);
192     return true;
193   }
194 
195   if (CGUIWindow::OnAction(action))
196     return true;
197 
198   if (action.GetID() == ACTION_FILTER)
199     return Filter();
200 
201   // live filtering
202   if (action.GetID() == ACTION_FILTER_CLEAR)
203   {
204     CGUIMessage message(GUI_MSG_NOTIFY_ALL, GetID(), 0, GUI_MSG_FILTER_ITEMS);
205     message.SetStringParam("");
206     OnMessage(message);
207     return true;
208   }
209 
210   if (action.GetID() == ACTION_BACKSPACE)
211   {
212     CGUIMessage message(GUI_MSG_NOTIFY_ALL, GetID(), 0, GUI_MSG_FILTER_ITEMS, 2); // 2 for delete
213     OnMessage(message);
214     return true;
215   }
216 
217   if (action.GetID() >= ACTION_FILTER_SMS2 && action.GetID() <= ACTION_FILTER_SMS9)
218   {
219     std::string filter = StringUtils::Format("%i", action.GetID() - ACTION_FILTER_SMS2 + 2);
220     CGUIMessage message(GUI_MSG_NOTIFY_ALL, GetID(), 0, GUI_MSG_FILTER_ITEMS, 1); // 1 for append
221     message.SetStringParam(filter);
222     OnMessage(message);
223     return true;
224   }
225 
226   return false;
227 }
228 
OnBack(int actionID)229 bool CGUIMediaWindow::OnBack(int actionID)
230 {
231   CancelUpdateItems();
232 
233   CURL filterUrl(m_strFilterPath);
234   if (actionID == ACTION_NAV_BACK &&
235       !m_vecItems->IsVirtualDirectoryRoot() &&
236       !URIUtils::PathEquals(m_vecItems->GetPath(), GetRootPath(), true) &&
237      (!URIUtils::PathEquals(m_vecItems->GetPath(), m_startDirectory, true) || (m_canFilterAdvanced && filterUrl.HasOption("filter"))))
238   {
239     if (GoParentFolder())
240       return true;
241   }
242   return CGUIWindow::OnBack(actionID);
243 }
244 
OnMessage(CGUIMessage & message)245 bool CGUIMediaWindow::OnMessage(CGUIMessage& message)
246 {
247   switch ( message.GetMessage() )
248   {
249   case GUI_MSG_WINDOW_DEINIT:
250     {
251       CancelUpdateItems();
252 
253       m_iLastControl = GetFocusedControlID();
254       CGUIWindow::OnMessage(message);
255 
256       // get rid of any active filtering
257       if (m_canFilterAdvanced)
258       {
259         m_canFilterAdvanced = false;
260         m_filter.Reset();
261       }
262       m_strFilterPath.clear();
263 
264       // Call ClearFileItems() after our window has finished doing any WindowClose
265       // animations
266       ClearFileItems();
267       return true;
268     }
269     break;
270 
271   case GUI_MSG_CLICKED:
272     {
273       int iControl = message.GetSenderId();
274       if (iControl == CONTROL_BTNVIEWASICONS)
275       {
276         // view as control could be a select button
277         int viewMode = 0;
278         const CGUIControl *control = GetControl(CONTROL_BTNVIEWASICONS);
279         if (control && control->GetControlType() != CGUIControl::GUICONTROL_BUTTON)
280         {
281           CGUIMessage msg(GUI_MSG_ITEM_SELECTED, GetID(), CONTROL_BTNVIEWASICONS);
282           OnMessage(msg);
283           viewMode = m_viewControl.GetViewModeNumber(msg.GetParam1());
284         }
285         else
286           viewMode = m_viewControl.GetNextViewMode();
287 
288         if (m_guiState)
289           m_guiState->SaveViewAsControl(viewMode);
290 
291         UpdateButtons();
292         return true;
293       }
294       else if (iControl == CONTROL_BTNSORTASC) // sort asc
295       {
296         if (m_guiState)
297           m_guiState->SetNextSortOrder();
298         UpdateFileList();
299         return true;
300       }
301       else if (iControl == CONTROL_BTNSORTBY) // sort by
302       {
303         if (m_guiState.get() && m_guiState->ChooseSortMethod())
304           UpdateFileList();
305         return true;
306       }
307       else if (iControl == CONTROL_BTN_FILTER)
308         return Filter(false);
309       else if (m_viewControl.HasControl(iControl))  // list/thumb control
310       {
311         int iItem = m_viewControl.GetSelectedItem();
312         int iAction = message.GetParam1();
313         if (iItem < 0) break;
314         if (iAction == ACTION_SELECT_ITEM || iAction == ACTION_MOUSE_LEFT_CLICK)
315         {
316           OnSelect(iItem);
317         }
318         else if (iAction == ACTION_CONTEXT_MENU || iAction == ACTION_MOUSE_RIGHT_CLICK)
319         {
320           OnPopupMenu(iItem);
321           return true;
322         }
323       }
324     }
325     break;
326 
327   case GUI_MSG_SETFOCUS:
328     {
329       if (m_viewControl.HasControl(message.GetControlId()) && m_viewControl.GetCurrentControl() != message.GetControlId())
330       {
331         m_viewControl.SetFocused();
332         return true;
333       }
334     }
335     break;
336 
337   case GUI_MSG_NOTIFY_ALL:
338     { // Message is received even if this window is inactive
339       if (message.GetParam1() == GUI_MSG_WINDOW_RESET)
340       {
341         m_vecItems->SetPath("?");
342         return true;
343       }
344       else if ( message.GetParam1() == GUI_MSG_REFRESH_THUMBS )
345       {
346         for (int i = 0; i < m_vecItems->Size(); i++)
347           m_vecItems->Get(i)->FreeMemory(true);
348         break;  // the window will take care of any info images
349       }
350       else if (message.GetParam1() == GUI_MSG_REMOVED_MEDIA)
351       {
352         if ((m_vecItems->IsVirtualDirectoryRoot() ||
353              m_vecItems->IsSourcesPath()) && IsActive())
354         {
355           int iItem = m_viewControl.GetSelectedItem();
356           Refresh();
357           m_viewControl.SetSelectedItem(iItem);
358         }
359         else if (m_vecItems->IsRemovable())
360         { // check that we have this removable share still
361           if (!m_rootDir.IsInSource(m_vecItems->GetPath()))
362           { // don't have this share any more
363             if (IsActive()) Update("");
364             else
365             {
366               m_history.ClearPathHistory();
367               m_vecItems->SetPath("");
368             }
369           }
370         }
371 
372         return true;
373       }
374       else if (message.GetParam1()==GUI_MSG_UPDATE_SOURCES)
375       { // State of the sources changed, so update our view
376         if ((m_vecItems->IsVirtualDirectoryRoot() ||
377              m_vecItems->IsSourcesPath()) && IsActive())
378         {
379           if (m_vecItemsUpdating)
380           {
381             CLog::Log(LOGWARNING, "CGUIMediaWindow::OnMessage - updating in progress");
382             return true;
383           }
384           CUpdateGuard ug(m_vecItemsUpdating);
385           int iItem = m_viewControl.GetSelectedItem();
386           Refresh(true);
387           m_viewControl.SetSelectedItem(iItem);
388         }
389         return true;
390       }
391       else if (message.GetParam1()==GUI_MSG_UPDATE && IsActive())
392       {
393         if (m_vecItemsUpdating)
394         {
395           CLog::Log(LOGWARNING, "CGUIMediaWindow::OnMessage - updating in progress");
396           return true;
397         }
398         CUpdateGuard ug(m_vecItemsUpdating);
399         if (message.GetNumStringParams())
400         {
401           if (message.GetParam2()) // param2 is used for resetting the history
402             SetHistoryForPath(message.GetStringParam());
403 
404           CFileItemList list(message.GetStringParam());
405           list.RemoveDiscCache(GetID());
406           Update(message.GetStringParam());
407         }
408         else
409           Refresh(true); // refresh the listing
410       }
411       else if (message.GetParam1()==GUI_MSG_UPDATE_ITEM && message.GetItem())
412       {
413         int flag = message.GetParam2();
414         CFileItemPtr newItem = std::static_pointer_cast<CFileItem>(message.GetItem());
415 
416         if (IsActive() || (flag & GUI_MSG_FLAG_FORCE_UPDATE))
417         {
418           m_vecItems->UpdateItem(newItem.get());
419 
420           if (flag & GUI_MSG_FLAG_UPDATE_LIST)
421           { // need the list updated as well
422             UpdateFileList();
423           }
424         }
425         else if (newItem)
426         { // need to remove the disc cache
427           CFileItemList items;
428           items.SetPath(URIUtils::GetDirectory(newItem->GetPath()));
429           if (newItem->HasProperty("cachefilename"))
430           {
431             // Use stored cache file name
432             std::string crcfile = newItem->GetProperty("cachefilename").asString();
433             items.RemoveDiscCacheCRC(crcfile);
434           }
435           else
436             // No stored cache file name, attempt using truncated item path as list path
437             items.RemoveDiscCache(GetID());
438         }
439       }
440       else if (message.GetParam1()==GUI_MSG_UPDATE_PATH)
441       {
442         if (IsActive())
443         {
444           if((message.GetStringParam() == m_vecItems->GetPath()) ||
445              (m_vecItems->IsMultiPath() && XFILE::CMultiPathDirectory::HasPath(m_vecItems->GetPath(), message.GetStringParam())))
446             Refresh();
447         }
448       }
449       else if (message.GetParam1() == GUI_MSG_FILTER_ITEMS && IsActive())
450       {
451         std::string filter = GetProperty("filter").asString();
452         // check if this is meant for advanced filtering
453         if (message.GetParam2() != 10)
454         {
455           if (message.GetParam2() == 1) // append
456             filter += message.GetStringParam();
457           else if (message.GetParam2() == 2)
458           { // delete
459             if (filter.size())
460               filter.erase(filter.size() - 1);
461           }
462           else
463             filter = message.GetStringParam();
464         }
465         OnFilterItems(filter);
466         UpdateButtons();
467         return true;
468       }
469       else
470         return CGUIWindow::OnMessage(message);
471 
472       return true;
473     }
474     break;
475   case GUI_MSG_PLAYBACK_STARTED:
476   case GUI_MSG_PLAYBACK_ENDED:
477   case GUI_MSG_PLAYBACK_STOPPED:
478   case GUI_MSG_PLAYLIST_CHANGED:
479   case GUI_MSG_PLAYLISTPLAYER_STOPPED:
480   case GUI_MSG_PLAYLISTPLAYER_STARTED:
481   case GUI_MSG_PLAYLISTPLAYER_CHANGED:
482     { // send a notify all to all controls on this window
483       CGUIMessage msg(GUI_MSG_NOTIFY_ALL, GetID(), 0, GUI_MSG_REFRESH_LIST);
484       OnMessage(msg);
485       break;
486     }
487   case GUI_MSG_CHANGE_VIEW_MODE:
488     {
489       int viewMode = 0;
490       if (message.GetParam1())  // we have an id
491         viewMode = m_viewControl.GetViewModeByID(message.GetParam1());
492       else if (message.GetParam2())
493         viewMode = m_viewControl.GetNextViewMode(message.GetParam2());
494 
495       if (m_guiState)
496         m_guiState->SaveViewAsControl(viewMode);
497       UpdateButtons();
498       return true;
499     }
500     break;
501   case GUI_MSG_CHANGE_SORT_METHOD:
502     {
503       if (m_guiState)
504       {
505         if (message.GetParam1())
506           m_guiState->SetCurrentSortMethod(message.GetParam1());
507         else if (message.GetParam2())
508           m_guiState->SetNextSortMethod(message.GetParam2());
509       }
510       UpdateFileList();
511       return true;
512     }
513     break;
514   case GUI_MSG_CHANGE_SORT_DIRECTION:
515     {
516       if (m_guiState)
517         m_guiState->SetNextSortOrder();
518       UpdateFileList();
519       return true;
520     }
521     break;
522   case GUI_MSG_WINDOW_INIT:
523     {
524       if (m_vecItems->GetPath() == "?")
525         m_vecItems->SetPath("");
526 
527       std::string dir = message.GetStringParam(0);
528       const std::string& ret = message.GetStringParam(1);
529       const std::string& swap = message.GetStringParam(message.GetNumStringParams() - 1);
530       const bool returning = StringUtils::EqualsNoCase(ret, "return");
531       const bool replacing = StringUtils::EqualsNoCase(swap, "replace");
532 
533       if (!dir.empty())
534       {
535         // ensure our directory is valid
536         dir = GetStartFolder(dir);
537         bool resetHistory = false;
538         if (!returning || !URIUtils::PathEquals(dir, m_startDirectory, true))
539         { // we're not returning to the same path, so set our directory to the requested path
540           m_vecItems->SetPath(dir);
541           resetHistory = true;
542         }
543         else if (m_vecItems->GetPath().empty() && URIUtils::PathEquals(dir, m_startDirectory, true))
544           m_vecItems->SetPath(dir);
545 
546         // check for network up
547         if (URIUtils::IsRemote(m_vecItems->GetPath()) && !WaitForNetwork())
548         {
549           m_vecItems->SetPath("");
550           resetHistory = true;
551         }
552         if (resetHistory)
553         {
554           m_vecItems->RemoveDiscCache(GetID());
555           // only compute the history for the provided path if "return" is not defined
556           // (otherwise the root level for the path will be added by default to the path history
557           // and we won't be able to move back to the path we came from)
558           if (!returning)
559             SetHistoryForPath(m_vecItems->GetPath());
560         }
561       }
562       if (message.GetParam1() != WINDOW_INVALID)
563       {
564         // if this is the first time to this window - make sure we set the root path
565         // if "return" is defined make sure we set the startDirectory to the directory we are
566         // moving to (so that we can move back to where we were onBack). If we are activating
567         // the same window but with a different path, do nothing - we are simply adding to the
568         // window history. Note that if the window is just being replaced, the start directory
569         // also needs to be set as the manager has just popped the previous window.
570         if (message.GetParam1() != message.GetParam2() || replacing)
571           m_startDirectory = returning ? dir : GetRootPath();
572       }
573       if (message.GetParam2() == PLUGIN_REFRESH_DELAY)
574       {
575         Refresh();
576         SetInitialVisibility();
577         RestoreControlStates();
578         SetInitialVisibility();
579         return true;
580       }
581     }
582     break;
583   }
584 
585   return CGUIWindow::OnMessage(message);
586 }
587 
588 /*!
589  * \brief Updates the states
590  *
591  * This updates the states (enable, disable, visible...) of the controls defined
592  * by this window.
593  *
594  * \note Override this function in a derived class to add new controls
595  */
UpdateButtons()596 void CGUIMediaWindow::UpdateButtons()
597 {
598   if (m_guiState)
599   {
600     // Update sorting controls
601     if (m_guiState->GetSortOrder() == SortOrderNone)
602     {
603       CONTROL_DISABLE(CONTROL_BTNSORTASC);
604     }
605     else
606     {
607       CONTROL_ENABLE(CONTROL_BTNSORTASC);
608       SET_CONTROL_SELECTED(GetID(), CONTROL_BTNSORTASC, m_guiState->GetSortOrder() != SortOrderAscending);
609     }
610 
611     // Update list/thumb control
612     m_viewControl.SetCurrentView(m_guiState->GetViewAsControl());
613 
614     // Update sort by button
615     if (!m_guiState->HasMultipleSortMethods())
616       CONTROL_DISABLE(CONTROL_BTNSORTBY);
617     else
618       CONTROL_ENABLE(CONTROL_BTNSORTBY);
619 
620     std::string sortLabel = StringUtils::Format(g_localizeStrings.Get(550).c_str(),
621                                                 g_localizeStrings.Get(m_guiState->GetSortMethodLabel()).c_str());
622     SET_CONTROL_LABEL(CONTROL_BTNSORTBY, sortLabel);
623   }
624 
625   std::string items = StringUtils::Format("%i %s", m_vecItems->GetObjectCount(), g_localizeStrings.Get(127).c_str());
626   SET_CONTROL_LABEL(CONTROL_LABELFILES, items);
627 
628   SET_CONTROL_LABEL2(CONTROL_BTN_FILTER, GetProperty("filter").asString());
629 }
630 
ClearFileItems()631 void CGUIMediaWindow::ClearFileItems()
632 {
633   m_viewControl.Clear();
634   m_vecItems->Clear();
635   m_unfilteredItems->Clear();
636 }
637 
638 /*!
639  * \brief Sort file items
640  *
641  * This sorts file items based on the sort method and sort order provided by
642  * guiViewState.
643  */
SortItems(CFileItemList & items)644 void CGUIMediaWindow::SortItems(CFileItemList &items)
645 {
646   std::unique_ptr<CGUIViewState> guiState(CGUIViewState::GetViewState(GetID(), items));
647 
648   if (guiState)
649   {
650     SortDescription sorting = guiState->GetSortMethod();
651     sorting.sortOrder = guiState->GetSortOrder();
652     // If the sort method is "sort by playlist" and we have a specific
653     // sort order available we can use the specified sort order to do the sorting
654     // We do this as the new SortBy methods are a superset of the SORT_METHOD methods, thus
655     // not all are available. This may be removed once SORT_METHOD_* have been replaced by
656     // SortBy.
657     if ((sorting.sortBy == SortByPlaylistOrder) && items.HasProperty(PROPERTY_SORT_ORDER))
658     {
659       SortBy sortBy = (SortBy)items.GetProperty(PROPERTY_SORT_ORDER).asInteger();
660       if (sortBy != SortByNone && sortBy != SortByPlaylistOrder && sortBy != SortByProgramCount)
661       {
662         sorting.sortBy = sortBy;
663         sorting.sortOrder = items.GetProperty(PROPERTY_SORT_ASCENDING).asBoolean() ? SortOrderAscending : SortOrderDescending;
664         sorting.sortAttributes = CServiceBroker::GetSettingsComponent()->GetSettings()->GetBool(CSettings::SETTING_FILELISTS_IGNORETHEWHENSORTING) ? SortAttributeIgnoreArticle : SortAttributeNone;
665 
666         // if the sort order is descending, we need to switch the original sort order, as we assume
667         // in CGUIViewState::AddPlaylistOrder that SortByPlaylistOrder is ascending.
668         if (guiState->GetSortOrder() == SortOrderDescending)
669           sorting.sortOrder = sorting.sortOrder == SortOrderDescending ? SortOrderAscending : SortOrderDescending;
670       }
671     }
672 
673     items.Sort(sorting);
674   }
675 }
676 
677 /*!
678  * \brief Formats item labels
679  *
680  * This is based on the formatting provided by guiViewState.
681  */
FormatItemLabels(CFileItemList & items,const LABEL_MASKS & labelMasks)682 void CGUIMediaWindow::FormatItemLabels(CFileItemList &items, const LABEL_MASKS &labelMasks)
683 {
684   CLabelFormatter fileFormatter(labelMasks.m_strLabelFile, labelMasks.m_strLabel2File);
685   CLabelFormatter folderFormatter(labelMasks.m_strLabelFolder, labelMasks.m_strLabel2Folder);
686   for (int i=0; i<items.Size(); ++i)
687   {
688     CFileItemPtr pItem=items[i];
689 
690     if (pItem->IsLabelPreformatted())
691       continue;
692 
693     if (pItem->m_bIsFolder)
694       folderFormatter.FormatLabels(pItem.get());
695     else
696       fileFormatter.FormatLabels(pItem.get());
697   }
698 
699   if (items.GetSortMethod() == SortByLabel)
700     items.ClearSortState();
701 }
702 
703 /*!
704  * \brief Format and sort file items
705  *
706  * Prepares and adds the fileitems to list/thumb panel
707  */
FormatAndSort(CFileItemList & items)708 void CGUIMediaWindow::FormatAndSort(CFileItemList &items)
709 {
710   std::unique_ptr<CGUIViewState> viewState(CGUIViewState::GetViewState(GetID(), items));
711 
712   if (viewState)
713   {
714     LABEL_MASKS labelMasks;
715     viewState->GetSortMethodLabelMasks(labelMasks);
716     FormatItemLabels(items, labelMasks);
717 
718     items.Sort(viewState->GetSortMethod().sortBy, viewState->GetSortOrder(), viewState->GetSortMethod().sortAttributes);
719   }
720 }
721 
722 /*!
723  * \brief Overwrite to fill fileitems from a source
724  *
725  * \param[in] strDirectory Path to read
726  * \param[out] items Fill with items specified in \e strDirectory
727  * \return false if given directory not present
728  */
GetDirectory(const std::string & strDirectory,CFileItemList & items)729 bool CGUIMediaWindow::GetDirectory(const std::string &strDirectory, CFileItemList &items)
730 {
731   CURL pathToUrl(strDirectory);
732 
733   std::string strParentPath = m_history.GetParentPath();
734 
735   CLog::Log(LOGDEBUG,"CGUIMediaWindow::GetDirectory (%s)",
736             CURL::GetRedacted(strDirectory).c_str());
737   CLog::Log(LOGDEBUG,"  ParentPath = [%s]", CURL::GetRedacted(strParentPath).c_str());
738 
739   if (pathToUrl.IsProtocol("plugin") && !pathToUrl.GetHostName().empty())
740     CServiceBroker::GetAddonMgr().UpdateLastUsed(pathToUrl.GetHostName());
741 
742   // see if we can load a previously cached folder
743   CFileItemList cachedItems(strDirectory);
744   if (!strDirectory.empty() && cachedItems.Load(GetID()))
745   {
746     items.Assign(cachedItems);
747   }
748   else
749   {
750     unsigned int time = XbmcThreads::SystemClockMillis();
751 
752     if (strDirectory.empty())
753       SetupShares();
754 
755     CFileItemList dirItems;
756     if (!GetDirectoryItems(pathToUrl, dirItems, UseFileDirectories()))
757       return false;
758 
759     // assign fetched directory items
760     items.Assign(dirItems);
761 
762     // took over a second, and not normally cached, so cache it
763     if ((XbmcThreads::SystemClockMillis() - time) > 1000  && items.CacheToDiscIfSlow())
764       items.Save(GetID());
765 
766     // if these items should replace the current listing, then pop it off the top
767     if (items.GetReplaceListing())
768       m_history.RemoveParentPath();
769   }
770 
771   // update the view state's reference to the current items
772   m_guiState.reset(CGUIViewState::GetViewState(GetID(), items));
773 
774   bool bHideParent = false;
775 
776   if (m_guiState && m_guiState->HideParentDirItems())
777     bHideParent = true;
778   if (items.GetPath() == GetRootPath())
779     bHideParent = true;
780 
781   if (!bHideParent)
782   {
783     CFileItemPtr pItem(new CFileItem(".."));
784     pItem->SetPath(strParentPath);
785     pItem->m_bIsFolder = true;
786     pItem->m_bIsShareOrDrive = false;
787     items.AddFront(pItem, 0);
788   }
789 
790   int iWindow = GetID();
791   std::vector<std::string> regexps;
792 
793   //! @todo Do we want to limit the directories we apply the video ones to?
794   if (iWindow == WINDOW_VIDEO_NAV)
795     regexps = CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_videoExcludeFromListingRegExps;
796   if (iWindow == WINDOW_MUSIC_NAV)
797     regexps = CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_audioExcludeFromListingRegExps;
798   if (iWindow == WINDOW_PICTURES)
799     regexps = CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_pictureExcludeFromListingRegExps;
800 
801   if (regexps.size())
802   {
803     for (int i=0; i < items.Size();)
804     {
805       if (CUtil::ExcludeFileOrFolder(items[i]->GetPath(), regexps))
806         items.Remove(i);
807       else
808         i++;
809     }
810   }
811 
812   // clear the filter
813   SetProperty("filter", "");
814   m_canFilterAdvanced = false;
815   m_filter.Reset();
816   return true;
817 }
818 
Update(const std::string & strDirectory,bool updateFilterPath)819 bool CGUIMediaWindow::Update(const std::string &strDirectory, bool updateFilterPath /* = true */)
820 {
821   //! @todo OnInitWindow calls Update() before window path has been set properly.
822   if (strDirectory == "?")
823     return false;
824 
825   // The path to load. Empty string is used in various places to denote root, so translate to the
826   // real root path first
827   const std::string path = strDirectory.empty() ? GetRootPath() : strDirectory;
828 
829   // stores the selected item in history
830   SaveSelectedItemInHistory();
831 
832   const std::string previousPath = m_vecItems->GetPath();
833 
834   // check if the path contains a filter and temporarily remove it
835   // so that the retrieved list of items is unfiltered
836   std::string pathNoFilter = path;
837   if (CanContainFilter(pathNoFilter) && CURL(pathNoFilter).HasOption("filter"))
838     pathNoFilter = RemoveParameterFromPath(pathNoFilter, "filter");
839 
840   if (!GetDirectory(pathNoFilter, *m_vecItems))
841   {
842     CLog::Log(LOGERROR,"CGUIMediaWindow::GetDirectory(%s) failed", CURL(path).GetRedacted().c_str());
843 
844     if (URIUtils::PathEquals(path, GetRootPath()))
845       return false; // Nothing to fallback to
846 
847     // Try to return to the previous directory, if not the same
848     // else fallback to root
849     if (URIUtils::PathEquals(path, previousPath) || !Update(m_history.RemoveParentPath()))
850       Update(""); // Fallback to root
851 
852     // Return false to be able to eg. show
853     // an error message.
854     return false;
855   }
856 
857   if (m_vecItems->GetLabel().empty())
858   {
859     // Removable sources
860     VECSOURCES removables;
861     CServiceBroker::GetMediaManager().GetRemovableDrives(removables);
862     for (const auto& s : removables)
863     {
864       if (URIUtils::CompareWithoutSlashAtEnd(s.strPath, m_vecItems->GetPath()))
865       {
866         m_vecItems->SetLabel(s.strName);
867         break;
868       }
869     }
870   }
871 
872   if (m_vecItems->GetLabel().empty())
873     m_vecItems->SetLabel(CUtil::GetTitleFromPath(m_vecItems->GetPath(), true));
874 
875   // check the given path for filter data
876   UpdateFilterPath(path, *m_vecItems, updateFilterPath);
877 
878   // if we're getting the root source listing
879   // make sure the path history is clean
880   if (URIUtils::PathEquals(path, GetRootPath()))
881     m_history.ClearPathHistory();
882 
883   int iWindow = GetID();
884   int showLabel = 0;
885   if (URIUtils::PathEquals(path, GetRootPath()))
886   {
887     if (iWindow == WINDOW_PICTURES)
888       showLabel = 997;
889     else if (iWindow == WINDOW_FILES)
890       showLabel = 1026;
891     else if (iWindow == WINDOW_GAMES)
892       showLabel = 35250; // "Add games..."
893   }
894   if (m_vecItems->IsPath("sources://video/"))
895     showLabel = 999;
896   else if (m_vecItems->IsPath("sources://music/"))
897     showLabel = 998;
898   else if (m_vecItems->IsPath("sources://pictures/"))
899     showLabel = 997;
900   else if (m_vecItems->IsPath("sources://files/"))
901     showLabel = 1026;
902   else if (m_vecItems->IsPath("sources://games/"))
903     showLabel = 35250; // "Add games..."
904    // Add 'Add source ' item
905   if (showLabel && (m_vecItems->Size() == 0 || !m_guiState->DisableAddSourceButtons()) &&
906       iWindow != WINDOW_MUSIC_PLAYLIST_EDITOR)
907   {
908     const std::string& strLabel = g_localizeStrings.Get(showLabel);
909     CFileItemPtr pItem(new CFileItem(strLabel));
910     pItem->SetPath("add");
911     pItem->SetArt("icon", "DefaultAddSource.png");
912     pItem->SetLabel(strLabel);
913     pItem->SetLabelPreformatted(true);
914     pItem->m_bIsFolder = true;
915     pItem->SetSpecialSort(CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_addSourceOnTop ?
916                                              SortSpecialOnTop : SortSpecialOnBottom);
917     m_vecItems->Add(pItem);
918   }
919   m_iLastControl = GetFocusedControlID();
920 
921   // Check whether to enabled advanced filtering based on the content type
922   m_canFilterAdvanced = CheckFilterAdvanced(*m_vecItems);
923   if (m_canFilterAdvanced)
924     m_filter.SetType(m_vecItems->GetContent());
925 
926   //  Ask the derived class if it wants to load additional info
927   //  for the fileitems like media info or additional
928   //  filtering on the items, setting thumbs.
929   OnPrepareFileItems(*m_vecItems);
930 
931   m_vecItems->FillInDefaultIcons();
932 
933   // remember the original (untouched) list of items (for filtering etc)
934   m_unfilteredItems->Assign(*m_vecItems);
935 
936   // Cache the list of items if possible
937   OnCacheFileItems(*m_vecItems);
938 
939   // Filter and group the items if necessary
940   OnFilterItems(GetProperty("filter").asString());
941   UpdateButtons();
942 
943   // Restore selected item from history
944   RestoreSelectedItemFromHistory();
945 
946   m_history.AddPath(m_vecItems->GetPath(), m_strFilterPath);
947 
948   //m_history.DumpPathHistory();
949 
950   return true;
951 }
952 
Refresh(bool clearCache)953 bool CGUIMediaWindow::Refresh(bool clearCache /* = false */)
954 {
955   std::string strCurrentDirectory = m_vecItems->GetPath();
956   if (strCurrentDirectory == "?")
957     return false;
958 
959   if (clearCache)
960     m_vecItems->RemoveDiscCache(GetID());
961 
962   bool ret = true;
963 
964   // get the original number of items
965   if (!Update(strCurrentDirectory, false))
966   {
967     ret = false;
968   }
969 
970   return ret;
971 }
972 
973 /*!
974  * \brief On prepare file items
975  *
976  * This function will be called by Update() before the labels of the fileitems
977  * are formatted.
978  *
979  * \note Override this function to set custom thumbs or load additional media
980  * info.
981  *
982  * It's used to load tag info for music.
983  */
OnPrepareFileItems(CFileItemList & items)984 void CGUIMediaWindow::OnPrepareFileItems(CFileItemList &items)
985 {
986   CFileItemListModification::GetInstance().Modify(items);
987 }
988 
989 /*!
990  * \brief On cache file items
991  *
992  * This function will be called by Update() before
993  * any additional formatting, filtering or sorting is applied.
994  *
995  * \note Override this function to define a custom caching behaviour.
996  */
OnCacheFileItems(CFileItemList & items)997 void CGUIMediaWindow::OnCacheFileItems(CFileItemList &items)
998 {
999   // Should these items be saved to the hdd
1000   if (items.CacheToDiscAlways() && !IsFiltered())
1001     items.Save(GetID());
1002 }
1003 
1004 /*!
1005  * \brief On click
1006  *
1007  * With this function you can react on a users click in the list/thumb panel.
1008  * It returns true, if the click is handled.
1009  * This function calls OnPlayMedia()
1010  */
OnClick(int iItem,const std::string & player)1011 bool CGUIMediaWindow::OnClick(int iItem, const std::string &player)
1012 {
1013   if (iItem < 0 || iItem >= m_vecItems->Size())
1014     return true;
1015 
1016   const std::shared_ptr<CProfileManager> profileManager = CServiceBroker::GetSettingsComponent()->GetProfileManager();
1017 
1018   CFileItemPtr pItem = m_vecItems->Get(iItem);
1019 
1020   if (pItem->IsParentFolder())
1021   {
1022     GoParentFolder();
1023     return true;
1024   }
1025 
1026   if (pItem->GetPath() == "add" || pItem->GetPath() == "sources://add/") // 'add source button' in empty root
1027   {
1028     if (profileManager->IsMasterProfile())
1029     {
1030       if (!g_passwordManager.IsMasterLockUnlocked(true))
1031         return false;
1032     }
1033     else if (!profileManager->GetCurrentProfile().canWriteSources() && !g_passwordManager.IsProfileLockUnlocked())
1034       return false;
1035 
1036     if (OnAddMediaSource())
1037       Refresh(true);
1038 
1039     return true;
1040   }
1041 
1042   if (!pItem->m_bIsFolder && pItem->IsFileFolder(EFILEFOLDER_MASK_ONCLICK))
1043   {
1044     XFILE::IFileDirectory *pFileDirectory = nullptr;
1045     pFileDirectory = XFILE::CFileDirectoryFactory::Create(pItem->GetURL(), pItem.get(), "");
1046     if(pFileDirectory)
1047       pItem->m_bIsFolder = true;
1048     else if(pItem->m_bIsFolder)
1049       pItem->m_bIsFolder = false;
1050     delete pFileDirectory;
1051   }
1052 
1053   if (pItem->IsScript())
1054   {
1055     // execute the script
1056     CURL url(pItem->GetPath());
1057     AddonPtr addon;
1058     if (CServiceBroker::GetAddonMgr().GetAddon(url.GetHostName(), addon, ADDON_SCRIPT,
1059                                                OnlyEnabled::YES))
1060     {
1061       if (!CScriptInvocationManager::GetInstance().Stop(addon->LibPath()))
1062       {
1063         CServiceBroker::GetAddonMgr().UpdateLastUsed(addon->ID());
1064         CScriptInvocationManager::GetInstance().ExecuteAsync(addon->LibPath(), addon);
1065       }
1066       return true;
1067     }
1068   }
1069 
1070   if (pItem->m_bIsFolder)
1071   {
1072     if ( pItem->m_bIsShareOrDrive )
1073     {
1074       const std::string& strLockType=m_guiState->GetLockType();
1075       if (profileManager->GetMasterProfile().getLockMode() != LOCK_MODE_EVERYONE)
1076         if (!strLockType.empty() && !g_passwordManager.IsItemUnlocked(pItem.get(), strLockType))
1077             return true;
1078 
1079       if (!HaveDiscOrConnection(pItem->GetPath(), pItem->m_iDriveType))
1080         return true;
1081     }
1082 
1083     // check for the partymode playlist items - they may not exist yet
1084     if ((pItem->GetPath() == profileManager->GetUserDataItem("PartyMode.xsp")) ||
1085         (pItem->GetPath() == profileManager->GetUserDataItem("PartyMode-Video.xsp")))
1086     {
1087       // party mode playlist item - if it doesn't exist, prompt for user to define it
1088       if (!XFILE::CFile::Exists(pItem->GetPath()))
1089       {
1090         m_vecItems->RemoveDiscCache(GetID());
1091         if (CGUIDialogSmartPlaylistEditor::EditPlaylist(pItem->GetPath()))
1092           Refresh();
1093         return true;
1094       }
1095     }
1096 
1097     // remove the directory cache if the folder is not normally cached
1098     CFileItemList items(pItem->GetPath());
1099     if (!items.AlwaysCache())
1100       items.RemoveDiscCache(GetID());
1101 
1102     // if we have a filtered list, we need to add the filtered
1103     // path to be able to come back to the filtered view
1104     std::string strCurrentDirectory = m_vecItems->GetPath();
1105     if (m_canFilterAdvanced && !m_filter.IsEmpty() &&
1106         !URIUtils::PathEquals(m_strFilterPath, strCurrentDirectory))
1107     {
1108       m_history.RemoveParentPath();
1109       m_history.AddPath(strCurrentDirectory, m_strFilterPath);
1110     }
1111 
1112     if (m_vecItemsUpdating)
1113     {
1114       CLog::Log(LOGWARNING, "CGUIMediaWindow::OnClick - updating in progress");
1115       return true;
1116     }
1117     CUpdateGuard ug(m_vecItemsUpdating);
1118 
1119     CFileItem directory(*pItem);
1120     if (!Update(directory.GetPath()))
1121       ShowShareErrorMessage(&directory);
1122 
1123     return true;
1124   }
1125   else if (pItem->IsPlugin() && !pItem->GetProperty("isplayable").asBoolean())
1126   {
1127     bool resume = pItem->m_lStartOffset == STARTOFFSET_RESUME;
1128     return XFILE::CPluginDirectory::RunScriptWithParams(pItem->GetPath(), resume);
1129   }
1130 #if defined(TARGET_ANDROID)
1131   else if (pItem->IsAndroidApp())
1132   {
1133     std::string appName = URIUtils::GetFileName(pItem->GetPath());
1134     CLog::Log(LOGDEBUG, "CGUIMediaWindow::OnClick Trying to run: %s",appName.c_str());
1135     return CXBMCApp::StartActivity(appName);
1136   }
1137 #endif
1138   else
1139   {
1140     SaveSelectedItemInHistory();
1141 
1142     if (pItem->GetPath() == "newplaylist://")
1143     {
1144       m_vecItems->RemoveDiscCache(GetID());
1145       CServiceBroker::GetGUI()->GetWindowManager().ActivateWindow(WINDOW_MUSIC_PLAYLIST_EDITOR,"newplaylist://");
1146       return true;
1147     }
1148     else if (StringUtils::StartsWithNoCase(pItem->GetPath(), "newsmartplaylist://"))
1149     {
1150       m_vecItems->RemoveDiscCache(GetID());
1151       if (CGUIDialogSmartPlaylistEditor::NewPlaylist(pItem->GetPath().substr(19)))
1152         Refresh();
1153       return true;
1154     }
1155 
1156     bool autoplay = m_guiState.get() && m_guiState->AutoPlayNextItem();
1157 
1158     if (m_vecItems->IsPlugin())
1159     {
1160       CURL url(m_vecItems->GetPath());
1161       AddonPtr addon;
1162       if (CServiceBroker::GetAddonMgr().GetAddon(url.GetHostName(), addon, ADDON_UNKNOWN,
1163                                                  OnlyEnabled::YES))
1164       {
1165         PluginPtr plugin = std::dynamic_pointer_cast<CPluginSource>(addon);
1166         if (plugin && plugin->Provides(CPluginSource::AUDIO))
1167         {
1168           CFileItemList items;
1169           std::unique_ptr<CGUIViewState> state(CGUIViewState::GetViewState(GetID(), items));
1170           autoplay = state.get() && state->AutoPlayNextItem();
1171         }
1172       }
1173     }
1174 
1175     if (autoplay && !g_partyModeManager.IsEnabled())
1176     {
1177       return OnPlayAndQueueMedia(pItem, player);
1178     }
1179     else
1180     {
1181       return OnPlayMedia(iItem, player);
1182     }
1183   }
1184 
1185   return false;
1186 }
1187 
OnSelect(int item)1188 bool CGUIMediaWindow::OnSelect(int item)
1189 {
1190   return OnClick(item);
1191 }
1192 
1193 /*!
1194  * \brief Check disc or connection present
1195  *
1196  * Checks if there is a disc in the dvd drive and whether the
1197  * network is connected or not.
1198  */
HaveDiscOrConnection(const std::string & strPath,int iDriveType)1199 bool CGUIMediaWindow::HaveDiscOrConnection(const std::string& strPath, int iDriveType)
1200 {
1201   if (iDriveType==CMediaSource::SOURCE_TYPE_DVD)
1202   {
1203     if (!CServiceBroker::GetMediaManager().IsDiscInDrive(strPath))
1204     {
1205       HELPERS::ShowOKDialogText(CVariant{218}, CVariant{219});
1206       return false;
1207     }
1208   }
1209   else if (iDriveType==CMediaSource::SOURCE_TYPE_REMOTE)
1210   {
1211     //! @todo Handle not connected to a remote share
1212     if (!CServiceBroker::GetNetwork().IsConnected())
1213     {
1214       HELPERS::ShowOKDialogText(CVariant{220}, CVariant{221});
1215       return false;
1216     }
1217   }
1218 
1219   return true;
1220 }
1221 
1222 /*!
1223  * \brief Shows a standard error message for a given pItem.
1224  */
ShowShareErrorMessage(CFileItem * pItem) const1225 void CGUIMediaWindow::ShowShareErrorMessage(CFileItem* pItem) const
1226 {
1227   if (!pItem->m_bIsShareOrDrive)
1228     return;
1229 
1230   int idMessageText = 0;
1231   CURL url(pItem->GetPath());
1232 
1233   if (url.IsProtocol("smb") && url.GetHostName().empty()) //  smb workgroup
1234     idMessageText = 15303; // Workgroup not found
1235   else if (pItem->m_iDriveType == CMediaSource::SOURCE_TYPE_REMOTE || URIUtils::IsRemote(pItem->GetPath()))
1236     idMessageText = 15301; // Could not connect to network server
1237   else
1238     idMessageText = 15300; // Path not found or invalid
1239 
1240   HELPERS::ShowOKDialogText(CVariant{220}, CVariant{idMessageText});
1241 }
1242 
1243 /*!
1244  * \brief Go one directory up on list items
1245  *
1246  * The function goes up one level in the directory tree
1247  */
GoParentFolder()1248 bool CGUIMediaWindow::GoParentFolder()
1249 {
1250   if (m_vecItems->IsVirtualDirectoryRoot())
1251     return false;
1252 
1253   if (URIUtils::PathEquals(m_vecItems->GetPath(), GetRootPath()))
1254     return false;
1255 
1256   //m_history.DumpPathHistory();
1257 
1258   const std::string currentPath = m_vecItems->GetPath();
1259   std::string parentPath = m_history.GetParentPath();
1260   // Check if a) the current folder is on the stack more than once, (parent is
1261   // often same as current), OR
1262   // b) the parent is an xml file (happens when ActivateWindow() called with
1263   // a node file) and so current path is the result of expanding the xml.
1264   // Keep going until there's nothing left or they dont match anymore.
1265   while (!parentPath.empty() &&
1266          (URIUtils::PathEquals(parentPath, currentPath, true) ||
1267           StringUtils::EndsWith(parentPath, ".xml/") || StringUtils::EndsWith(parentPath, ".xml")))
1268   {
1269     m_history.RemoveParentPath();
1270     parentPath = m_history.GetParentPath();
1271   }
1272 
1273   // remove the current filter but only if the parent
1274   // item doesn't have a filter as well
1275   CURL filterUrl(m_strFilterPath);
1276   if (filterUrl.HasOption("filter"))
1277   {
1278     CURL parentUrl(m_history.GetParentPath(true));
1279     if (!parentUrl.HasOption("filter"))
1280     {
1281       // we need to overwrite m_strFilterPath because
1282       // Refresh() will set updateFilterPath to false
1283       m_strFilterPath.clear();
1284       Refresh();
1285       return true;
1286     }
1287   }
1288 
1289   // pop directory path from the stack
1290   m_strFilterPath = m_history.GetParentPath(true);
1291   m_history.RemoveParentPath();
1292 
1293   if (!Update(parentPath, false))
1294     return false;
1295 
1296   // No items to show so go another level up
1297   if (!m_vecItems->GetPath().empty() && (m_filter.IsEmpty() ? m_vecItems->Size() : m_unfilteredItems->Size()) <= 0)
1298   {
1299     CGUIDialogKaiToast::QueueNotification(CGUIDialogKaiToast::Info, g_localizeStrings.Get(2080), g_localizeStrings.Get(2081));
1300     return GoParentFolder();
1301   }
1302   return true;
1303 }
1304 
SaveSelectedItemInHistory()1305 void CGUIMediaWindow::SaveSelectedItemInHistory()
1306 {
1307   int iItem = m_viewControl.GetSelectedItem();
1308   std::string strSelectedItem;
1309   if (iItem >= 0 && iItem < m_vecItems->Size())
1310   {
1311     CFileItemPtr pItem = m_vecItems->Get(iItem);
1312     GetDirectoryHistoryString(pItem.get(), strSelectedItem);
1313   }
1314 
1315   m_history.SetSelectedItem(strSelectedItem, m_vecItems->GetPath());
1316 }
1317 
RestoreSelectedItemFromHistory()1318 void CGUIMediaWindow::RestoreSelectedItemFromHistory()
1319 {
1320   std::string strSelectedItem = m_history.GetSelectedItem(m_vecItems->GetPath());
1321 
1322   if (!strSelectedItem.empty())
1323   {
1324     for (int i = 0; i < m_vecItems->Size(); ++i)
1325     {
1326       CFileItemPtr pItem = m_vecItems->Get(i);
1327       std::string strHistory;
1328       GetDirectoryHistoryString(pItem.get(), strHistory);
1329       // set selected item if equals with history
1330       if (strHistory == strSelectedItem)
1331       {
1332         m_viewControl.SetSelectedItem(i);
1333         return;
1334       }
1335     }
1336   }
1337 
1338   // if we haven't found the selected item, select the first item
1339   m_viewControl.SetSelectedItem(0);
1340 }
1341 
1342 /*!
1343  * \brief Get history string for given file item
1344  *
1345  * \note Override the function to change the default behavior on how
1346  * a selected item history should look like
1347  */
GetDirectoryHistoryString(const CFileItem * pItem,std::string & strHistoryString) const1348 void CGUIMediaWindow::GetDirectoryHistoryString(const CFileItem* pItem, std::string& strHistoryString) const
1349 {
1350   if (pItem->m_bIsShareOrDrive)
1351   {
1352     // We are in the virtual directory
1353 
1354     // History string of the DVD drive
1355     // must be handled separately
1356     if (pItem->m_iDriveType == CMediaSource::SOURCE_TYPE_DVD)
1357     {
1358       // Remove disc label from item label
1359       // and use as history string, m_strPath
1360       // can change for new discs
1361       std::string strLabel = pItem->GetLabel();
1362       size_t nPosOpen = strLabel.find('(');
1363       size_t nPosClose = strLabel.rfind(')');
1364       if (nPosOpen != std::string::npos &&
1365           nPosClose != std::string::npos &&
1366           nPosClose > nPosOpen)
1367       {
1368         strLabel.erase(nPosOpen + 1, (nPosClose) - (nPosOpen + 1));
1369         strHistoryString = strLabel;
1370       }
1371       else
1372         strHistoryString = strLabel;
1373     }
1374     else
1375     {
1376       // Other items in virtual directory
1377       std::string strPath = pItem->GetPath();
1378       URIUtils::RemoveSlashAtEnd(strPath);
1379 
1380       strHistoryString = pItem->GetLabel() + strPath;
1381     }
1382   }
1383   else if (pItem->m_lEndOffset>pItem->m_lStartOffset && pItem->m_lStartOffset != -1)
1384   {
1385     // Could be a cue item, all items of a cue share the same filename
1386     // so add the offsets to build the history string
1387     strHistoryString = StringUtils::Format("%" PRIi64 "%" PRIi64,
1388                                            pItem->m_lStartOffset,
1389                                            pItem->m_lEndOffset);
1390     strHistoryString += pItem->GetPath();
1391   }
1392   else
1393   {
1394     // Normal directory items
1395     strHistoryString = pItem->GetPath();
1396   }
1397 
1398   // remove any filter
1399   if (CanContainFilter(strHistoryString))
1400     strHistoryString = RemoveParameterFromPath(strHistoryString, "filter");
1401 
1402   URIUtils::RemoveSlashAtEnd(strHistoryString);
1403   StringUtils::ToLower(strHistoryString);
1404 }
1405 
1406 /*!
1407  * \brief Set history for path
1408  *
1409  * Call this function to create a directory history for the
1410  * path given by strDirectory.
1411  */
SetHistoryForPath(const std::string & strDirectory)1412 void CGUIMediaWindow::SetHistoryForPath(const std::string& strDirectory)
1413 {
1414   // Make sure our shares are configured
1415   SetupShares();
1416   if (!strDirectory.empty())
1417   {
1418     // Build the directory history for default path
1419     std::string strPath, strParentPath;
1420     strPath = strDirectory;
1421     URIUtils::RemoveSlashAtEnd(strPath);
1422 
1423     CFileItemList items;
1424     CURL url;
1425     GetDirectoryItems(url, items, UseFileDirectories());
1426 
1427     m_history.ClearPathHistory();
1428 
1429     bool originalPath = true;
1430     while (URIUtils::GetParentPath(strPath, strParentPath))
1431     {
1432       for (int i = 0; i < items.Size(); ++i)
1433       {
1434         CFileItemPtr pItem = items[i];
1435         std::string path(pItem->GetPath());
1436         URIUtils::RemoveSlashAtEnd(path);
1437         if (URIUtils::PathEquals(path, strPath))
1438         {
1439           std::string strHistory;
1440           GetDirectoryHistoryString(pItem.get(), strHistory);
1441           m_history.SetSelectedItem(strHistory, "");
1442           URIUtils::AddSlashAtEnd(strPath);
1443           m_history.AddPathFront(strPath);
1444           m_history.AddPathFront("");
1445 
1446           //m_history.DumpPathHistory();
1447           return ;
1448         }
1449       }
1450 
1451       if (URIUtils::IsVideoDb(strPath))
1452       {
1453         CURL url(strParentPath);
1454         url.SetOptions(""); // clear any URL options from recreated parent path
1455         strParentPath = url.Get();
1456       }
1457 
1458       // set the original path exactly as it was passed in
1459       if (URIUtils::PathEquals(strPath, strDirectory, true))
1460         strPath = strDirectory;
1461       else
1462         URIUtils::AddSlashAtEnd(strPath);
1463 
1464       m_history.AddPathFront(strPath, originalPath ? m_strFilterPath : "");
1465       m_history.SetSelectedItem(strPath, strParentPath);
1466       originalPath = false;
1467       strPath = strParentPath;
1468       URIUtils::RemoveSlashAtEnd(strPath);
1469     }
1470   }
1471   else
1472     m_history.ClearPathHistory();
1473 
1474   //m_history.DumpPathHistory();
1475 }
1476 
1477 /*!
1478  * \brief On media play
1479  *
1480  * \note Override if you want to change the default behavior, what is done
1481  * when the user clicks on a file.
1482  *
1483  * This function is called by OnClick()
1484  */
OnPlayMedia(int iItem,const std::string & player)1485 bool CGUIMediaWindow::OnPlayMedia(int iItem, const std::string &player)
1486 {
1487   // Reset Playlistplayer, playback started now does
1488   // not use the playlistplayer.
1489   CServiceBroker::GetPlaylistPlayer().Reset();
1490   CServiceBroker::GetPlaylistPlayer().SetCurrentPlaylist(PLAYLIST_NONE);
1491   CFileItemPtr pItem=m_vecItems->Get(iItem);
1492 
1493   CLog::Log(LOGDEBUG, "%s %s", __FUNCTION__, CURL::GetRedacted(pItem->GetPath()).c_str());
1494 
1495   bool bResult = false;
1496   if (pItem->IsInternetStream() || pItem->IsPlayList())
1497     bResult = g_application.PlayMedia(*pItem, player, m_guiState->GetPlaylist());
1498   else
1499     bResult = g_application.PlayFile(*pItem, player);
1500 
1501   if (pItem->m_lStartOffset == STARTOFFSET_RESUME)
1502     pItem->m_lStartOffset = 0;
1503 
1504   return bResult;
1505 }
1506 
1507 /*!
1508  * \brief On play and media queue
1509  *
1510  * \note Override if you want to change the default behavior of what is done
1511  * when the user clicks on a file in a "folder" with similar files.
1512  *
1513  * This function is called by OnClick()
1514  */
OnPlayAndQueueMedia(const CFileItemPtr & item,const std::string & player)1515 bool CGUIMediaWindow::OnPlayAndQueueMedia(const CFileItemPtr& item, const std::string& player)
1516 {
1517   //play and add current directory to temporary playlist
1518   int iPlaylist = m_guiState->GetPlaylist();
1519   if (iPlaylist != PLAYLIST_NONE)
1520   {
1521     CServiceBroker::GetPlaylistPlayer().ClearPlaylist(iPlaylist);
1522     CServiceBroker::GetPlaylistPlayer().Reset();
1523     int mediaToPlay = 0;
1524 
1525     // first try to find mainDVD file (VIDEO_TS.IFO).
1526     // If we find this we should not allow to queue VOB files
1527     std::string mainDVD;
1528     for (int i = 0; i < m_vecItems->Size(); i++)
1529     {
1530       std::string path = URIUtils::GetFileName(m_vecItems->Get(i)->GetDynPath());
1531       if (StringUtils::EqualsNoCase(path, "VIDEO_TS.IFO"))
1532       {
1533         mainDVD = path;
1534         break;
1535       }
1536     }
1537 
1538     // now queue...
1539     for ( int i = 0; i < m_vecItems->Size(); i++ )
1540     {
1541       CFileItemPtr nItem = m_vecItems->Get(i);
1542 
1543       if (nItem->m_bIsFolder)
1544         continue;
1545 
1546       if (!nItem->IsZIP() && !nItem->IsRAR() && (!nItem->IsDVDFile() || (URIUtils::GetFileName(nItem->GetDynPath()) == mainDVD)))
1547         CServiceBroker::GetPlaylistPlayer().Add(iPlaylist, nItem);
1548 
1549       if (item->IsSamePath(nItem.get()))
1550       { // item that was clicked
1551         mediaToPlay = CServiceBroker::GetPlaylistPlayer().GetPlaylist(iPlaylist).size() - 1;
1552       }
1553     }
1554 
1555     // Save current window and directory to know where the selected item was
1556     if (m_guiState)
1557       m_guiState->SetPlaylistDirectory(m_vecItems->GetPath());
1558 
1559     // figure out where we start playback
1560     if (CServiceBroker::GetPlaylistPlayer().IsShuffled(iPlaylist))
1561     {
1562       int iIndex = CServiceBroker::GetPlaylistPlayer().GetPlaylist(iPlaylist).FindOrder(mediaToPlay);
1563       CServiceBroker::GetPlaylistPlayer().GetPlaylist(iPlaylist).Swap(0, iIndex);
1564       mediaToPlay = 0;
1565     }
1566 
1567     // play
1568     CServiceBroker::GetPlaylistPlayer().SetCurrentPlaylist(iPlaylist);
1569     CServiceBroker::GetPlaylistPlayer().Play(mediaToPlay, player);
1570   }
1571   return true;
1572 }
1573 
1574 /*!
1575  * \brief Update file list
1576  *
1577  * Synchronize the fileitems with the playlistplayer
1578  * also recreates the playlist of the playlistplayer based
1579  * on the fileitems of the window
1580  */
UpdateFileList()1581 void CGUIMediaWindow::UpdateFileList()
1582 {
1583   int nItem = m_viewControl.GetSelectedItem();
1584   std::string strSelected;
1585   if (nItem >= 0)
1586     strSelected = m_vecItems->Get(nItem)->GetPath();
1587 
1588   FormatAndSort(*m_vecItems);
1589   UpdateButtons();
1590 
1591   m_viewControl.SetItems(*m_vecItems);
1592   m_viewControl.SetSelectedItem(strSelected);
1593 
1594   //  set the currently playing item as selected, if its in this directory
1595   if (m_guiState.get() && m_guiState->IsCurrentPlaylistDirectory(m_vecItems->GetPath()))
1596   {
1597     int iPlaylist=m_guiState->GetPlaylist();
1598     int nSong = CServiceBroker::GetPlaylistPlayer().GetCurrentSong();
1599     CFileItem playlistItem;
1600     if (nSong > -1 && iPlaylist > -1)
1601       playlistItem=*CServiceBroker::GetPlaylistPlayer().GetPlaylist(iPlaylist)[nSong];
1602 
1603     CServiceBroker::GetPlaylistPlayer().ClearPlaylist(iPlaylist);
1604     CServiceBroker::GetPlaylistPlayer().Reset();
1605 
1606     for (int i = 0; i < m_vecItems->Size(); i++)
1607     {
1608       CFileItemPtr pItem = m_vecItems->Get(i);
1609       if (pItem->m_bIsFolder)
1610         continue;
1611 
1612       if (!pItem->IsPlayList() && !pItem->IsZIP() && !pItem->IsRAR())
1613         CServiceBroker::GetPlaylistPlayer().Add(iPlaylist, pItem);
1614 
1615       if (pItem->GetPath() == playlistItem.GetPath() &&
1616           pItem->m_lStartOffset == playlistItem.m_lStartOffset)
1617         CServiceBroker::GetPlaylistPlayer().SetCurrentSong(CServiceBroker::GetPlaylistPlayer().GetPlaylist(iPlaylist).size() - 1);
1618     }
1619   }
1620 }
1621 
OnDeleteItem(int iItem)1622 void CGUIMediaWindow::OnDeleteItem(int iItem)
1623 {
1624   if ( iItem < 0 || iItem >= m_vecItems->Size()) return;
1625   CFileItemPtr item = m_vecItems->Get(iItem);
1626 
1627   if (item->IsPlayList())
1628     item->m_bIsFolder = false;
1629 
1630   const std::shared_ptr<CProfileManager> profileManager = CServiceBroker::GetSettingsComponent()->GetProfileManager();
1631 
1632   if (profileManager->GetCurrentProfile().getLockMode() != LOCK_MODE_EVERYONE && profileManager->GetCurrentProfile().filesLocked())
1633   {
1634     if (!g_passwordManager.IsMasterLockUnlocked(true))
1635       return;
1636   }
1637 
1638   CGUIComponent *gui = CServiceBroker::GetGUI();
1639   if (gui && gui->ConfirmDelete(item->GetPath()))
1640   {
1641     if (!CFileUtils::DeleteItem(item))
1642       return;
1643   }
1644   else
1645     return;
1646 
1647   Refresh(true);
1648   m_viewControl.SetSelectedItem(iItem);
1649 }
1650 
OnRenameItem(int iItem)1651 void CGUIMediaWindow::OnRenameItem(int iItem)
1652 {
1653   if (iItem < 0 || iItem >= m_vecItems->Size())
1654     return;
1655 
1656   const std::shared_ptr<CProfileManager> profileManager = CServiceBroker::GetSettingsComponent()->GetProfileManager();
1657 
1658   if (profileManager->GetCurrentProfile().getLockMode() != LOCK_MODE_EVERYONE && profileManager->GetCurrentProfile().filesLocked())
1659   {
1660     if (!g_passwordManager.IsMasterLockUnlocked(true))
1661       return;
1662   }
1663 
1664   if (!CFileUtils::RenameFile(m_vecItems->Get(iItem)->GetPath()))
1665     return;
1666 
1667   Refresh(true);
1668   m_viewControl.SetSelectedItem(iItem);
1669 }
1670 
OnInitWindow()1671 void CGUIMediaWindow::OnInitWindow()
1672 {
1673   // initial fetch is done unthreaded to ensure the items are setup prior to skin animations kicking off
1674   m_backgroundLoad = false;
1675 
1676   // the start directory may change during Refresh
1677   bool updateStartDirectory = URIUtils::PathEquals(m_vecItems->GetPath(), m_startDirectory, true);
1678 
1679   // we have python scripts hooked in everywhere :(
1680   // those scripts may open windows and we can't open a window
1681   // while opening this one.
1682   // for plugin sources delay call to Refresh
1683   if (!URIUtils::IsPlugin(m_vecItems->GetPath()))
1684   {
1685     Refresh();
1686   }
1687   else
1688   {
1689     CGUIMessage msg(GUI_MSG_WINDOW_INIT, 0, 0, WINDOW_INVALID, PLUGIN_REFRESH_DELAY);
1690     CServiceBroker::GetGUI()->GetWindowManager().SendThreadMessage(msg, GetID());
1691   }
1692 
1693   if (updateStartDirectory)
1694   {
1695     // reset the start directory to the path of the items
1696     m_startDirectory = m_vecItems->GetPath();
1697 
1698     // reset the history based on the path of the items
1699     SetHistoryForPath(m_startDirectory);
1700   }
1701 
1702   m_backgroundLoad = true;
1703 
1704   CGUIWindow::OnInitWindow();
1705 }
1706 
SaveControlStates()1707 void CGUIMediaWindow::SaveControlStates()
1708 {
1709   CGUIWindow::SaveControlStates();
1710   SaveSelectedItemInHistory();
1711 }
1712 
RestoreControlStates()1713 void CGUIMediaWindow::RestoreControlStates()
1714 {
1715   CGUIWindow::RestoreControlStates();
1716   RestoreSelectedItemFromHistory();
1717 }
1718 
GetFirstFocusableControl(int id)1719 CGUIControl *CGUIMediaWindow::GetFirstFocusableControl(int id)
1720 {
1721   if (m_viewControl.HasControl(id))
1722     id = m_viewControl.GetCurrentControl();
1723   return CGUIWindow::GetFirstFocusableControl(id);
1724 }
1725 
SetupShares()1726 void CGUIMediaWindow::SetupShares()
1727 {
1728   // Setup shares and filemasks for this window
1729   CFileItemList items;
1730   CGUIViewState* viewState=CGUIViewState::GetViewState(GetID(), items);
1731   if (viewState)
1732   {
1733     m_rootDir.SetMask(viewState->GetExtensions());
1734     m_rootDir.SetSources(viewState->GetSources());
1735     delete viewState;
1736   }
1737 }
1738 
OnPopupMenu(int itemIdx)1739 bool CGUIMediaWindow::OnPopupMenu(int itemIdx)
1740 {
1741   auto InRange = [](size_t i, std::pair<size_t, size_t> range){ return i >= range.first && i < range.second; };
1742 
1743   if (itemIdx < 0 || itemIdx >= m_vecItems->Size())
1744     return false;
1745 
1746   auto item = m_vecItems->Get(itemIdx);
1747   if (!item)
1748     return false;
1749 
1750   CContextButtons buttons;
1751 
1752   //Add items from plugin
1753   {
1754     int i = 0;
1755     while (item->HasProperty(StringUtils::Format("contextmenulabel(%i)", i)))
1756     {
1757       buttons.emplace_back(~buttons.size(), item->GetProperty(StringUtils::Format("contextmenulabel(%i)", i)).asString());
1758       ++i;
1759     }
1760   }
1761   auto pluginMenuRange = std::make_pair(static_cast<size_t>(0), buttons.size());
1762 
1763   //Add the global menu
1764   auto globalMenu = CServiceBroker::GetContextMenuManager().GetItems(*item, CContextMenuManager::MAIN);
1765   auto globalMenuRange = std::make_pair(buttons.size(), buttons.size() + globalMenu.size());
1766   for (const auto& menu : globalMenu)
1767     buttons.emplace_back(~buttons.size(), menu->GetLabel(*item));
1768 
1769   //Add legacy items from windows
1770   auto buttonsSize = buttons.size();
1771   GetContextButtons(itemIdx, buttons);
1772   auto windowMenuRange = std::make_pair(buttonsSize, buttons.size());
1773 
1774   //Add addon menus
1775   auto addonMenu = CServiceBroker::GetContextMenuManager().GetAddonItems(*item, CContextMenuManager::MAIN);
1776   auto addonMenuRange = std::make_pair(buttons.size(), buttons.size() + addonMenu.size());
1777   for (const auto& menu : addonMenu)
1778     buttons.emplace_back(~buttons.size(), menu->GetLabel(*item));
1779 
1780   if (buttons.empty())
1781     return true;
1782 
1783   int idx = CGUIDialogContextMenu::Show(buttons);
1784   if (idx < 0 || idx >= static_cast<int>(buttons.size()))
1785     return false;
1786 
1787   if (InRange(static_cast<size_t>(idx), pluginMenuRange))
1788   {
1789     bool saveVal = m_backgroundLoad;
1790     m_backgroundLoad = false;
1791     CApplicationMessenger::GetInstance().SendMsg(TMSG_EXECUTE_BUILT_IN, -1, -1, nullptr,
1792         item->GetProperty(StringUtils::Format("contextmenuaction(%i)", idx - pluginMenuRange.first)).asString());
1793     m_backgroundLoad = saveVal;
1794     return true;
1795   }
1796 
1797   if (InRange(idx, windowMenuRange))
1798     return OnContextButton(itemIdx, static_cast<CONTEXT_BUTTON>(buttons[idx].first));
1799 
1800   if (InRange(idx, globalMenuRange))
1801     return CONTEXTMENU::LoopFrom(*globalMenu[idx - globalMenuRange.first], item);
1802 
1803   return CONTEXTMENU::LoopFrom(*addonMenu[idx - addonMenuRange.first], item);
1804 }
1805 
GetContextButtons(int itemNumber,CContextButtons & buttons)1806 void CGUIMediaWindow::GetContextButtons(int itemNumber, CContextButtons &buttons)
1807 {
1808   CFileItemPtr item = (itemNumber >= 0 && itemNumber < m_vecItems->Size()) ? m_vecItems->Get(itemNumber) : CFileItemPtr();
1809 
1810   if (!item || item->IsParentFolder())
1811     return;
1812 
1813   if (item->IsFileFolder(EFILEFOLDER_MASK_ONBROWSE))
1814     buttons.Add(CONTEXT_BUTTON_BROWSE_INTO, 37015);
1815 
1816 }
1817 
OnContextButton(int itemNumber,CONTEXT_BUTTON button)1818 bool CGUIMediaWindow::OnContextButton(int itemNumber, CONTEXT_BUTTON button)
1819 {
1820   switch (button)
1821   {
1822   case CONTEXT_BUTTON_BROWSE_INTO:
1823     {
1824       CFileItemPtr item = m_vecItems->Get(itemNumber);
1825       Update(item->GetPath());
1826       return true;
1827     }
1828   default:
1829     break;
1830   }
1831   return false;
1832 }
1833 
GetViewState() const1834 const CGUIViewState *CGUIMediaWindow::GetViewState() const
1835 {
1836   return m_guiState.get();
1837 }
1838 
CurrentDirectory() const1839 const CFileItemList& CGUIMediaWindow::CurrentDirectory() const
1840 {
1841   return *m_vecItems;
1842 }
1843 
WaitForNetwork() const1844 bool CGUIMediaWindow::WaitForNetwork() const
1845 {
1846   if (CServiceBroker::GetNetwork().IsAvailable())
1847     return true;
1848 
1849   CGUIDialogProgress *progress = CServiceBroker::GetGUI()->GetWindowManager().GetWindow<CGUIDialogProgress>(WINDOW_DIALOG_PROGRESS);
1850   if (!progress)
1851     return true;
1852 
1853   CURL url(m_vecItems->GetPath());
1854   progress->SetHeading(CVariant{1040}); // Loading Directory
1855   progress->SetLine(1, CVariant{url.GetWithoutUserDetails()});
1856   progress->ShowProgressBar(false);
1857   progress->Open();
1858   while (!CServiceBroker::GetNetwork().IsAvailable())
1859   {
1860     progress->Progress();
1861     if (progress->IsCanceled())
1862     {
1863       progress->Close();
1864       return false;
1865     }
1866   }
1867   progress->Close();
1868   return true;
1869 }
1870 
UpdateFilterPath(const std::string & strDirectory,const CFileItemList & items,bool updateFilterPath)1871 void CGUIMediaWindow::UpdateFilterPath(const std::string &strDirectory, const CFileItemList &items, bool updateFilterPath)
1872 {
1873   bool canfilter = CanContainFilter(strDirectory);
1874 
1875   std::string filter;
1876   CURL url(strDirectory);
1877   if (canfilter && url.HasOption("filter"))
1878     filter = url.GetOption("filter");
1879 
1880   // only set the filter path if it hasn't been marked
1881   // as preset or if it's empty
1882   if (updateFilterPath || m_strFilterPath.empty())
1883   {
1884     if (items.HasProperty(PROPERTY_PATH_DB))
1885       m_strFilterPath = items.GetProperty(PROPERTY_PATH_DB).asString();
1886     else
1887       m_strFilterPath = items.GetPath();
1888   }
1889 
1890   // maybe the filter path can contain a filter
1891   if (!canfilter && CanContainFilter(m_strFilterPath))
1892     canfilter = true;
1893 
1894   // check if the filter path contains a filter
1895   CURL filterPathUrl(m_strFilterPath);
1896   if (canfilter && filter.empty())
1897   {
1898     if (filterPathUrl.HasOption("filter"))
1899       filter = filterPathUrl.GetOption("filter");
1900   }
1901 
1902   // check if there is a filter and re-apply it
1903   if (canfilter && !filter.empty())
1904   {
1905     if (!m_filter.LoadFromJson(filter))
1906     {
1907       CLog::Log(LOGWARNING, "CGUIMediaWindow::UpdateFilterPath(): unable to load existing filter (%s)", filter.c_str());
1908       m_filter.Reset();
1909       m_strFilterPath = m_vecItems->GetPath();
1910     }
1911     else
1912     {
1913       // add the filter to the filter path
1914       filterPathUrl.SetOption("filter", filter);
1915       m_strFilterPath = filterPathUrl.Get();
1916     }
1917   }
1918 }
1919 
OnFilterItems(const std::string & filter)1920 void CGUIMediaWindow::OnFilterItems(const std::string &filter)
1921 {
1922   m_viewControl.Clear();
1923 
1924   CFileItemList items;
1925   items.Copy(*m_vecItems, false); // use the original path - it'll likely be relied on for other things later.
1926   items.Append(*m_unfilteredItems);
1927   bool filtered = GetFilteredItems(filter, items);
1928 
1929   m_vecItems->ClearItems();
1930   // we need to clear the sort state and re-sort the items
1931   m_vecItems->ClearSortState();
1932   m_vecItems->Append(items);
1933 
1934   // if the filter has changed, get the new filter path
1935   if (filtered && m_canFilterAdvanced)
1936   {
1937     if (items.HasProperty(PROPERTY_PATH_DB))
1938       m_strFilterPath = items.GetProperty(PROPERTY_PATH_DB).asString();
1939     // only set m_strFilterPath if it hasn't been set before
1940     // otherwise we might overwrite it with a non-filter path
1941     // in case GetFilteredItems() returns true even though no
1942     // db-based filter (e.g. watched filter) has been applied
1943     else if (m_strFilterPath.empty())
1944       m_strFilterPath = items.GetPath();
1945   }
1946 
1947   GetGroupedItems(*m_vecItems);
1948   FormatAndSort(*m_vecItems);
1949 
1950   CFileItemPtr currentItem;
1951   std::string currentItemPath;
1952   int item = m_viewControl.GetSelectedItem();
1953   if (item >= 0 && item < m_vecItems->Size())
1954   {
1955     currentItem = m_vecItems->Get(item);
1956     currentItemPath = currentItem->GetPath();
1957   }
1958 
1959   // get the "filter" option
1960   std::string filterOption;
1961   CURL filterUrl(m_strFilterPath);
1962   if (filterUrl.HasOption("filter"))
1963     filterOption = filterUrl.GetOption("filter");
1964 
1965   // apply the "filter" option to any folder item so that
1966   // the filter can be passed down to the sub-directory
1967   for (int index = 0; index < m_vecItems->Size(); index++)
1968   {
1969     CFileItemPtr pItem = m_vecItems->Get(index);
1970     // if the item is a folder we need to copy the path of
1971     // the filtered item to be able to keep the applied filters
1972     if (pItem->m_bIsFolder)
1973     {
1974       CURL itemUrl(pItem->GetPath());
1975       if (!filterOption.empty())
1976         itemUrl.SetOption("filter", filterOption);
1977       else
1978         itemUrl.RemoveOption("filter");
1979       pItem->SetPath(itemUrl.Get());
1980     }
1981   }
1982 
1983   SetProperty("filter", filter);
1984   if (filtered && m_canFilterAdvanced)
1985   {
1986     // to be able to select the same item as before we need to adjust
1987     // the path of the item i.e. add or remove the "filter=" URL option
1988     // but that's only necessary for folder items
1989     if (currentItem.get() && currentItem->m_bIsFolder)
1990     {
1991       CURL curUrl(currentItemPath), newUrl(m_strFilterPath);
1992       if (newUrl.HasOption("filter"))
1993         curUrl.SetOption("filter", newUrl.GetOption("filter"));
1994       else if (curUrl.HasOption("filter"))
1995         curUrl.RemoveOption("filter");
1996 
1997       currentItemPath = curUrl.Get();
1998     }
1999   }
2000 
2001   // The idea here is to ensure we have something to focus if our file list
2002   // is empty.  As such, this check MUST be last and ignore the hide parent
2003   // fileitems settings.
2004   if (m_vecItems->IsEmpty())
2005   {
2006     CFileItemPtr pItem(new CFileItem(".."));
2007     pItem->SetPath(m_history.GetParentPath());
2008     pItem->m_bIsFolder = true;
2009     pItem->m_bIsShareOrDrive = false;
2010     m_vecItems->AddFront(pItem, 0);
2011   }
2012 
2013   // and update our view control + buttons
2014   m_viewControl.SetItems(*m_vecItems);
2015   m_viewControl.SetSelectedItem(currentItemPath);
2016 }
2017 
GetFilteredItems(const std::string & filter,CFileItemList & items)2018 bool CGUIMediaWindow::GetFilteredItems(const std::string &filter, CFileItemList &items)
2019 {
2020   bool result = false;
2021   if (m_canFilterAdvanced)
2022     result = GetAdvanceFilteredItems(items);
2023 
2024   std::string trimmedFilter(filter);
2025   StringUtils::TrimLeft(trimmedFilter);
2026   StringUtils::ToLower(trimmedFilter);
2027 
2028   if (trimmedFilter.empty())
2029     return result;
2030 
2031   CFileItemList filteredItems(items.GetPath()); // use the original path - it'll likely be relied on for other things later.
2032   bool numericMatch = StringUtils::IsNaturalNumber(trimmedFilter);
2033   for (int i = 0; i < items.Size(); i++)
2034   {
2035     CFileItemPtr item = items.Get(i);
2036     if (item->IsParentFolder())
2037     {
2038       filteredItems.Add(item);
2039       continue;
2040     }
2041     //! @todo Need to update this to get all labels, ideally out of the displayed info (ie from m_layout and m_focusedLayout)
2042     //! though that isn't practical.  Perhaps a better idea would be to just grab the info that we should filter on based on
2043     //! where we are in the library tree.
2044     //! Another idea is tying the filter string to the current level of the tree, so that going deeper disables the filter,
2045     //! but it's re-enabled on the way back out.
2046     std::string match;
2047     /*    if (item->GetFocusedLayout())
2048      match = item->GetFocusedLayout()->GetAllText();
2049      else if (item->GetLayout())
2050      match = item->GetLayout()->GetAllText();
2051      else*/
2052     match = item->GetLabel(); // Filter label only for now
2053 
2054     if (numericMatch)
2055       StringUtils::WordToDigits(match);
2056 
2057     size_t pos = StringUtils::FindWords(match.c_str(), trimmedFilter.c_str());
2058     if (pos != std::string::npos)
2059       filteredItems.Add(item);
2060   }
2061 
2062   items.ClearItems();
2063   items.Append(filteredItems);
2064 
2065   return items.GetObjectCount() > 0;
2066 }
2067 
GetAdvanceFilteredItems(CFileItemList & items)2068 bool CGUIMediaWindow::GetAdvanceFilteredItems(CFileItemList &items)
2069 {
2070   // don't run the advanced filter if the filter is empty
2071   // and there hasn't been a filter applied before which
2072   // would have to be removed
2073   CURL url(m_strFilterPath);
2074   if (m_filter.IsEmpty() && !url.HasOption("filter"))
2075     return false;
2076 
2077   CFileItemList resultItems;
2078   XFILE::CSmartPlaylistDirectory::GetDirectory(m_filter, resultItems, m_strFilterPath, true);
2079 
2080   // put together a lookup map for faster path comparison
2081   std::map<std::string, CFileItemPtr> lookup;
2082   for (int j = 0; j < resultItems.Size(); j++)
2083   {
2084     std::string itemPath = CURL(resultItems[j]->GetPath()).GetWithoutOptions();
2085     StringUtils::ToLower(itemPath);
2086 
2087     lookup[itemPath] = resultItems[j];
2088   }
2089 
2090   // loop through all the original items and find
2091   // those which are still part of the filter
2092   CFileItemList filteredItems;
2093   for (int i = 0; i < items.Size(); i++)
2094   {
2095     CFileItemPtr item = items.Get(i);
2096     if (item->IsParentFolder())
2097     {
2098       filteredItems.Add(item);
2099       continue;
2100     }
2101 
2102     // check if the item is part of the resultItems list
2103     // by comparing their paths (but ignoring any special
2104     // options because they differ from filter to filter)
2105     std::string path = CURL(item->GetPath()).GetWithoutOptions();
2106     StringUtils::ToLower(path);
2107 
2108     std::map<std::string, CFileItemPtr>::iterator itItem = lookup.find(path);
2109     if (itItem != lookup.end())
2110     {
2111       // add the item to the list of filtered items
2112       filteredItems.Add(item);
2113 
2114       // remove the item from the lists
2115       resultItems.Remove(itItem->second.get());
2116       lookup.erase(itItem);
2117     }
2118   }
2119 
2120   if (resultItems.Size() > 0)
2121     CLog::Log(LOGWARNING, "CGUIMediaWindow::GetAdvanceFilteredItems(): %d unknown items", resultItems.Size());
2122 
2123   items.ClearItems();
2124   items.Append(filteredItems);
2125   items.SetPath(resultItems.GetPath());
2126   if (resultItems.HasProperty(PROPERTY_PATH_DB))
2127     items.SetProperty(PROPERTY_PATH_DB, resultItems.GetProperty(PROPERTY_PATH_DB));
2128   return true;
2129 }
2130 
IsFiltered()2131 bool CGUIMediaWindow::IsFiltered()
2132 {
2133   return (!m_canFilterAdvanced && !GetProperty("filter").empty()) ||
2134          (m_canFilterAdvanced && !m_filter.IsEmpty());
2135 }
2136 
IsSameStartFolder(const std::string & dir)2137 bool CGUIMediaWindow::IsSameStartFolder(const std::string &dir)
2138 {
2139   const std::string startFolder = GetStartFolder(dir);
2140   return URIUtils::PathHasParent(m_vecItems->GetPath(), startFolder);
2141 }
2142 
Filter(bool advanced)2143 bool CGUIMediaWindow::Filter(bool advanced /* = true */)
2144 {
2145   // basic filtering
2146   if (!m_canFilterAdvanced || !advanced)
2147   {
2148     const CGUIControl *btnFilter = GetControl(CONTROL_BTN_FILTER);
2149     if (btnFilter && btnFilter->GetControlType() == CGUIControl::GUICONTROL_EDIT)
2150     { // filter updated
2151       CGUIMessage selected(GUI_MSG_ITEM_SELECTED, GetID(), CONTROL_BTN_FILTER);
2152       OnMessage(selected);
2153       OnFilterItems(selected.GetLabel());
2154       UpdateButtons();
2155       return true;
2156     }
2157     if (GetProperty("filter").empty())
2158     {
2159       std::string filter = GetProperty("filter").asString();
2160       CGUIKeyboardFactory::ShowAndGetFilter(filter, false);
2161       SetProperty("filter", filter);
2162     }
2163     else
2164     {
2165       OnFilterItems("");
2166       UpdateButtons();
2167     }
2168   }
2169   // advanced filtering
2170   else
2171     CGUIDialogMediaFilter::ShowAndEditMediaFilter(m_strFilterPath, m_filter);
2172 
2173   return true;
2174 }
2175 
GetStartFolder(const std::string & dir)2176 std::string CGUIMediaWindow::GetStartFolder(const std::string &dir)
2177 {
2178   if (StringUtils::EqualsNoCase(dir, "$root") ||
2179       StringUtils::EqualsNoCase(dir, "root"))
2180     return "";
2181 
2182   // Let plugins handle their own urls themselves
2183   if (StringUtils::StartsWith(dir, "plugin://"))
2184     return dir;
2185 
2186 //! @todo This ifdef block probably belongs somewhere else. Move it to a better place!
2187 #if defined(TARGET_ANDROID)
2188   // Hack for Android items (numbered id's) on the leanback screen
2189   std::string path;
2190   std::string fileName;
2191   URIUtils::Split(dir, path, fileName);
2192   URIUtils::RemoveExtension(fileName);
2193   if (StringUtils::IsInteger(fileName))
2194     return path;
2195 #endif
2196 
2197   return dir;
2198 }
2199 
RemoveParameterFromPath(const std::string & strDirectory,const std::string & strParameter)2200 std::string CGUIMediaWindow::RemoveParameterFromPath(const std::string &strDirectory, const std::string &strParameter)
2201 {
2202   CURL url(strDirectory);
2203   if (url.HasOption(strParameter))
2204   {
2205     url.RemoveOption(strParameter);
2206     return url.Get();
2207   }
2208 
2209   return strDirectory;
2210 }
2211 
ProcessRenderLoop(bool renderOnly)2212 bool CGUIMediaWindow::ProcessRenderLoop(bool renderOnly)
2213 {
2214   return CServiceBroker::GetGUI()->GetWindowManager().ProcessRenderLoop(renderOnly);
2215 }
2216 
GetDirectoryItems(CURL & url,CFileItemList & items,bool useDir)2217 bool CGUIMediaWindow::GetDirectoryItems(CURL &url, CFileItemList &items, bool useDir)
2218 {
2219   if (m_backgroundLoad)
2220   {
2221     bool ret = true;
2222     CGetDirectoryItems getItems(m_rootDir, url, items, useDir);
2223 
2224     if (!WaitGetDirectoryItems(getItems))
2225     {
2226       // cancelled
2227       ret = false;
2228     }
2229     else if (!getItems.m_result)
2230     {
2231       if (g_application.IsCurrentThread() && m_rootDir.GetDirImpl() && !m_rootDir.GetDirImpl()->ProcessRequirements())
2232       {
2233         ret = false;
2234       }
2235       else if (!WaitGetDirectoryItems(getItems) || !getItems.m_result)
2236       {
2237         ret = false;
2238       }
2239     }
2240 
2241     m_updateJobActive = false;
2242     m_rootDir.ReleaseDirImpl();
2243     return ret;
2244   }
2245   else
2246   {
2247     return m_rootDir.GetDirectory(url, items, useDir, false);
2248   }
2249 }
2250 
WaitGetDirectoryItems(CGetDirectoryItems & items)2251 bool CGUIMediaWindow::WaitGetDirectoryItems(CGetDirectoryItems &items)
2252 {
2253   bool ret = true;
2254   CGUIDialogBusy* dialog = CServiceBroker::GetGUI()->GetWindowManager().GetWindow<CGUIDialogBusy>(WINDOW_DIALOG_BUSY);
2255   if (dialog && !dialog->IsDialogRunning())
2256   {
2257     if (!CGUIDialogBusy::Wait(&items, 100, true))
2258     {
2259       // cancelled
2260       ret = false;
2261     }
2262   }
2263   else
2264   {
2265     m_updateJobActive = true;
2266     m_updateEvent.Reset();
2267     CJobManager::GetInstance().Submit([&]() {
2268       items.Run();
2269       m_updateEvent.Set();
2270     }, nullptr, CJob::PRIORITY_NORMAL);
2271 
2272     while (!m_updateEvent.WaitMSec(1))
2273     {
2274       if (!ProcessRenderLoop(false))
2275         break;
2276     }
2277 
2278     if (m_updateAborted || !items.m_result)
2279     {
2280       ret = false;
2281     }
2282   }
2283   return ret;
2284 }
2285 
CancelUpdateItems()2286 void CGUIMediaWindow::CancelUpdateItems()
2287 {
2288   if (m_updateJobActive)
2289   {
2290     m_rootDir.CancelDirectory();
2291     m_updateAborted = true;
2292     if (!m_updateEvent.WaitMSec(5000))
2293     {
2294       CLog::Log(LOGERROR, "CGUIMediaWindow::CancelUpdateItems - error cancel update");
2295     }
2296     m_updateJobActive = false;
2297   }
2298 }
2299