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