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 "GUIWindowVideoBase.h"
10 
11 #include "Application.h"
12 #include "Autorun.h"
13 #include "GUIPassword.h"
14 #include "GUIUserMessages.h"
15 #include "PartyModeManager.h"
16 #include "PlayListPlayer.h"
17 #include "ServiceBroker.h"
18 #include "TextureDatabase.h"
19 #include "URL.h"
20 #include "Util.h"
21 #include "addons/gui/GUIDialogAddonInfo.h"
22 #include "cores/playercorefactory/PlayerCoreFactory.h"
23 #include "dialogs/GUIDialogProgress.h"
24 #include "dialogs/GUIDialogSelect.h"
25 #include "dialogs/GUIDialogSmartPlaylistEditor.h"
26 #include "dialogs/GUIDialogYesNo.h"
27 #include "filesystem/Directory.h"
28 #include "filesystem/StackDirectory.h"
29 #include "filesystem/VideoDatabaseDirectory.h"
30 #include "guilib/GUIComponent.h"
31 #include "guilib/GUIKeyboardFactory.h"
32 #include "guilib/GUIWindowManager.h"
33 #include "guilib/LocalizeStrings.h"
34 #include "input/Key.h"
35 #include "messaging/helpers/DialogOKHelper.h"
36 #include "music/dialogs/GUIDialogMusicInfo.h"
37 #include "playlists/PlayList.h"
38 #include "playlists/PlayListFactory.h"
39 #include "profiles/ProfileManager.h"
40 #include "settings/AdvancedSettings.h"
41 #include "settings/MediaSettings.h"
42 #include "settings/Settings.h"
43 #include "settings/SettingsComponent.h"
44 #include "settings/SettingUtils.h"
45 #include "settings/dialogs/GUIDialogContentSettings.h"
46 #include "storage/MediaManager.h"
47 #include "utils/FileExtensionProvider.h"
48 #include "utils/FileUtils.h"
49 #include "utils/GroupUtils.h"
50 #include "utils/StringUtils.h"
51 #include "utils/URIUtils.h"
52 #include "utils/Variant.h"
53 #include "utils/log.h"
54 #include "video/VideoInfoDownloader.h"
55 #include "video/VideoInfoScanner.h"
56 #include "video/VideoLibraryQueue.h"
57 #include "video/dialogs/GUIDialogVideoInfo.h"
58 #include "view/GUIViewState.h"
59 
60 using namespace XFILE;
61 using namespace PLAYLIST;
62 using namespace VIDEODATABASEDIRECTORY;
63 using namespace VIDEO;
64 using namespace ADDON;
65 using namespace PVR;
66 using namespace KODI::MESSAGING;
67 
68 #define CONTROL_BTNVIEWASICONS     2
69 #define CONTROL_BTNSORTBY          3
70 #define CONTROL_BTNSORTASC         4
71 #define CONTROL_LABELFILES        12
72 
73 #define CONTROL_PLAY_DVD           6
74 
75 #define PROPERTY_GROUP_BY           "group.by"
76 #define PROPERTY_GROUP_MIXED        "group.mixed"
77 
78 static constexpr int SETTING_AUTOPLAYNEXT_MUSICVIDEOS = 0;
79 static constexpr int SETTING_AUTOPLAYNEXT_EPISODES = 2;
80 static constexpr int SETTING_AUTOPLAYNEXT_MOVIES = 3;
81 static constexpr int SETTING_AUTOPLAYNEXT_UNCATEGORIZED = 4;
82 
CGUIWindowVideoBase(int id,const std::string & xmlFile)83 CGUIWindowVideoBase::CGUIWindowVideoBase(int id, const std::string &xmlFile)
84     : CGUIMediaWindow(id, xmlFile.c_str())
85 {
86   m_thumbLoader.SetObserver(this);
87   m_stackingAvailable = true;
88   m_dlgProgress = NULL;
89 }
90 
91 CGUIWindowVideoBase::~CGUIWindowVideoBase() = default;
92 
OnAction(const CAction & action)93 bool CGUIWindowVideoBase::OnAction(const CAction &action)
94 {
95   if (action.GetID() == ACTION_SCAN_ITEM)
96     return OnContextButton(m_viewControl.GetSelectedItem(),CONTEXT_BUTTON_SCAN);
97   else if (action.GetID() == ACTION_SHOW_PLAYLIST)
98   {
99     if (CServiceBroker::GetPlaylistPlayer().GetCurrentPlaylist() == PLAYLIST_VIDEO ||
100         CServiceBroker::GetPlaylistPlayer().GetPlaylist(PLAYLIST_VIDEO).size() > 0)
101     {
102       CServiceBroker::GetGUI()->GetWindowManager().ActivateWindow(WINDOW_VIDEO_PLAYLIST);
103       return true;
104     }
105   }
106 
107   return CGUIMediaWindow::OnAction(action);
108 }
109 
OnMessage(CGUIMessage & message)110 bool CGUIWindowVideoBase::OnMessage(CGUIMessage& message)
111 {
112   switch ( message.GetMessage() )
113   {
114   case GUI_MSG_WINDOW_DEINIT:
115     if (m_thumbLoader.IsLoading())
116       m_thumbLoader.StopThread();
117     m_database.Close();
118     break;
119 
120   case GUI_MSG_WINDOW_INIT:
121     {
122       m_database.Open();
123       m_dlgProgress = CServiceBroker::GetGUI()->GetWindowManager().GetWindow<CGUIDialogProgress>(WINDOW_DIALOG_PROGRESS);
124       return CGUIMediaWindow::OnMessage(message);
125     }
126     break;
127 
128   case GUI_MSG_CLICKED:
129     {
130       int iControl = message.GetSenderId();
131 #if defined(HAS_DVD_DRIVE)
132       if (iControl == CONTROL_PLAY_DVD)
133       {
134         // play movie...
135         MEDIA_DETECT::CAutorun::PlayDiscAskResume(
136             CServiceBroker::GetMediaManager().TranslateDevicePath(""));
137       }
138       else
139 #endif
140       if (m_viewControl.HasControl(iControl))  // list/thumb control
141       {
142         // get selected item
143         int iItem = m_viewControl.GetSelectedItem();
144         int iAction = message.GetParam1();
145 
146         // iItem is checked for validity inside these routines
147         if (iAction == ACTION_QUEUE_ITEM || iAction == ACTION_MOUSE_MIDDLE_CLICK)
148         {
149           OnQueueItem(iItem);
150           return true;
151         }
152         else if (iAction == ACTION_QUEUE_ITEM_NEXT)
153         {
154           OnQueueItem(iItem, true);
155           return true;
156         }
157         else if (iAction == ACTION_SHOW_INFO)
158         {
159           return OnItemInfo(iItem);
160         }
161         else if (iAction == ACTION_PLAYER_PLAY)
162         {
163           // if playback is paused or playback speed != 1, return
164           if (g_application.GetAppPlayer().IsPlayingVideo())
165           {
166             if (g_application.GetAppPlayer().IsPausedPlayback())
167               return false;
168             if (g_application.GetAppPlayer().GetPlaySpeed() != 1)
169               return false;
170           }
171 
172           // not playing video, or playback speed == 1
173           return OnResumeItem(iItem);
174         }
175         else if (iAction == ACTION_DELETE_ITEM)
176         {
177           const std::shared_ptr<CProfileManager> profileManager = CServiceBroker::GetSettingsComponent()->GetProfileManager();
178 
179           // is delete allowed?
180           if (profileManager->GetCurrentProfile().canWriteDatabases())
181           {
182             // must be at the title window
183             if (GetID() == WINDOW_VIDEO_NAV)
184               OnDeleteItem(iItem);
185 
186             // or be at the video playlists location
187             else if (m_vecItems->IsPath("special://videoplaylists/"))
188               OnDeleteItem(iItem);
189             else
190               return false;
191 
192             return true;
193           }
194         }
195       }
196     }
197     break;
198   case GUI_MSG_SEARCH:
199     OnSearch();
200     break;
201   }
202   return CGUIMediaWindow::OnMessage(message);
203 }
204 
OnItemInfo(const CFileItem & fileItem,ADDON::ScraperPtr & scraper)205 void CGUIWindowVideoBase::OnItemInfo(const CFileItem& fileItem, ADDON::ScraperPtr& scraper)
206 {
207   if (fileItem.IsParentFolder() || fileItem.m_bIsShareOrDrive || fileItem.IsPath("add") ||
208      (fileItem.IsPlayList() && !URIUtils::HasExtension(fileItem.GetDynPath(), ".strm")))
209     return;
210 
211   CFileItem item(fileItem);
212   bool fromDB = false;
213   if ((item.IsVideoDb() && item.HasVideoInfoTag()) ||
214       (item.HasVideoInfoTag() && item.GetVideoInfoTag()->m_iDbId != -1))
215   {
216     if (item.GetVideoInfoTag()->m_type == MediaTypeSeason)
217     { // clear out the art - we're really grabbing the info on the show here
218       item.ClearArt();
219       item.GetVideoInfoTag()->m_iDbId = item.GetVideoInfoTag()->m_iIdShow;
220     }
221     item.SetPath(item.GetVideoInfoTag()->GetPath());
222     fromDB = true;
223   }
224   else
225   {
226     if (item.m_bIsFolder && scraper && scraper->Content() != CONTENT_TVSHOWS)
227     {
228       CFileItemList items;
229       CDirectory::GetDirectory(item.GetPath(), items, CServiceBroker::GetFileExtensionProvider().GetVideoExtensions(),
230                                DIR_FLAG_DEFAULTS);
231 
232       // Check for cases 1_dir/1_dir/.../file (e.g. by packages where have a extra folder)
233       while (items.Size() == 1 && items[0]->m_bIsFolder)
234       {
235         const std::string path = items[0]->GetPath();
236         items.Clear();
237         CDirectory::GetDirectory(path, items,
238                                  CServiceBroker::GetFileExtensionProvider().GetVideoExtensions(),
239                                  DIR_FLAG_DEFAULTS);
240       }
241 
242       items.Stack();
243 
244       // check for media files
245       bool bFoundFile(false);
246       for (int i = 0; i < items.Size(); ++i)
247       {
248         CFileItemPtr item2 = items[i];
249 
250         if (item2->IsVideo() && !item2->IsPlayList() &&
251             !CUtil::ExcludeFileOrFolder(item2->GetPath(), CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_moviesExcludeFromScanRegExps))
252         {
253           item.SetPath(item2->GetPath());
254           item.m_bIsFolder = false;
255           bFoundFile = true;
256           break;
257         }
258       }
259 
260       // no video file in this folder
261       if (!bFoundFile)
262       {
263         HELPERS::ShowOKDialogText(CVariant{13346}, CVariant{20349});
264         return;
265       }
266     }
267   }
268 
269   // we need to also request any thumbs be applied to the folder item
270   if (fileItem.m_bIsFolder)
271     item.SetProperty("set_folder_thumb", fileItem.GetPath());
272 
273   bool modified = ShowIMDB(CFileItemPtr(new CFileItem(item)), scraper, fromDB);
274   if (modified &&
275      (CServiceBroker::GetGUI()->GetWindowManager().GetActiveWindow() == WINDOW_VIDEO_NAV)) // since we can be called from the music library we need this check
276   {
277     int itemNumber = m_viewControl.GetSelectedItem();
278     Refresh();
279     m_viewControl.SetSelectedItem(itemNumber);
280   }
281 }
282 
283 // ShowIMDB is called as follows:
284 // 1.  To lookup info on a file.
285 // 2.  To lookup info on a folder (which may or may not contain a file)
286 // 3.  To lookup info just for fun (no file or folder related)
287 
288 // We just need the item object for this.
289 // A "blank" item object is sent for 3.
290 // If a folder is sent, currently it sets strFolder and bFolder
291 // this is only used for setting the folder thumb, however.
292 
293 // Steps should be:
294 
295 // 1.  Check database to see if we have this information already
296 // 2.  Else, check for a nfoFile to get the URL
297 // 3.  Run a loop to check for refresh
298 // 4.  If no URL is present do a search to get the URL
299 // 4.  Once we have the URL, download the details
300 // 5.  Once we have the details, add to the database if necessary (case 1,2)
301 //     and show the information.
302 // 6.  Check for a refresh, and if so, go to 3.
303 
ShowIMDB(CFileItemPtr item,const ScraperPtr & info2,bool fromDB)304 bool CGUIWindowVideoBase::ShowIMDB(CFileItemPtr item, const ScraperPtr &info2, bool fromDB)
305 {
306   /*
307   CLog::Log(LOGDEBUG,"CGUIWindowVideoBase::ShowIMDB");
308   CLog::Log(LOGDEBUG,"  strMovie  = [%s]", strMovie.c_str());
309   CLog::Log(LOGDEBUG,"  strFile   = [%s]", strFile.c_str());
310   CLog::Log(LOGDEBUG,"  strFolder = [%s]", strFolder.c_str());
311   CLog::Log(LOGDEBUG,"  bFolder   = [%s]", ((int)bFolder ? "true" : "false"));
312   */
313 
314   CGUIDialogProgress* pDlgProgress = CServiceBroker::GetGUI()->GetWindowManager().GetWindow<CGUIDialogProgress>(WINDOW_DIALOG_PROGRESS);
315   CGUIDialogSelect* pDlgSelect = CServiceBroker::GetGUI()->GetWindowManager().GetWindow<CGUIDialogSelect>(WINDOW_DIALOG_SELECT);
316   CGUIDialogVideoInfo* pDlgInfo = CServiceBroker::GetGUI()->GetWindowManager().GetWindow<CGUIDialogVideoInfo>(WINDOW_DIALOG_VIDEO_INFO);
317 
318   const ScraperPtr& info(info2); // use this as nfo might change it..
319 
320   if (!pDlgProgress) return false;
321   if (!pDlgSelect) return false;
322   if (!pDlgInfo) return false;
323 
324   // 1.  Check for already downloaded information, and if we have it, display our dialog
325   //     Return if no Refresh is needed.
326   bool bHasInfo=false;
327 
328   CVideoInfoTag movieDetails;
329   if (info)
330   {
331     m_database.Open(); // since we can be called from the music library
332 
333     int dbId = item->HasVideoInfoTag() ? item->GetVideoInfoTag()->m_iDbId : -1;
334     if (info->Content() == CONTENT_MOVIES)
335     {
336       bHasInfo = m_database.GetMovieInfo(item->GetPath(), movieDetails, dbId);
337     }
338     if (info->Content() == CONTENT_TVSHOWS)
339     {
340       if (item->m_bIsFolder)
341       {
342         bHasInfo = m_database.GetTvShowInfo(item->GetPath(), movieDetails, dbId);
343       }
344       else
345       {
346         bHasInfo = m_database.GetEpisodeInfo(item->GetPath(), movieDetails, dbId);
347         if (!bHasInfo)
348         {
349           // !! WORKAROUND !!
350           // As we cannot add an episode to a non-existing tvshow entry, we have to check the parent directory
351           // to see if it`s already in our video database. If it's not yet part of the database we will exit here.
352           // (Ticket #4764)
353           //
354           // NOTE: This will fail for episodes on multipath shares, as the parent path isn't what is stored in the
355           //       database.  Possible solutions are to store the paths in the db separately and rely on the show
356           //       stacking stuff, or to modify GetTvShowId to do support multipath:// shares
357           std::string strParentDirectory;
358           URIUtils::GetParentPath(item->GetPath(), strParentDirectory);
359           if (m_database.GetTvShowId(strParentDirectory) < 0)
360           {
361             CLog::Log(LOGERROR,"%s: could not add episode [%s]. tvshow does not exist yet..", __FUNCTION__, item->GetPath().c_str());
362             return false;
363           }
364         }
365       }
366     }
367     if (info->Content() == CONTENT_MUSICVIDEOS)
368     {
369       bHasInfo = m_database.GetMusicVideoInfo(item->GetPath(), movieDetails);
370     }
371     m_database.Close();
372   }
373   else if(item->HasVideoInfoTag())
374   {
375     bHasInfo = true;
376     movieDetails = *item->GetVideoInfoTag();
377   }
378 
379   bool needsRefresh = false;
380   if (bHasInfo)
381   {
382     if (!info || info->Content() == CONTENT_NONE) // disable refresh button
383       item->SetProperty("xxuniqueid", "xx" + movieDetails.GetUniqueID());
384     *item->GetVideoInfoTag() = movieDetails;
385     pDlgInfo->SetMovie(item.get());
386     pDlgInfo->Open();
387     if (pDlgInfo->HasUpdatedUserrating())
388       return true;
389     needsRefresh = pDlgInfo->NeedRefresh();
390     if (!needsRefresh)
391       return pDlgInfo->HasUpdatedThumb();
392     // check if the item in the video info dialog has changed and if so, get the new item
393     else if (pDlgInfo->GetCurrentListItem() != NULL)
394     {
395       item = pDlgInfo->GetCurrentListItem();
396 
397       if (item->IsVideoDb() && item->HasVideoInfoTag())
398         item->SetPath(item->GetVideoInfoTag()->GetPath());
399     }
400   }
401 
402   const std::shared_ptr<CProfileManager> profileManager = CServiceBroker::GetSettingsComponent()->GetProfileManager();
403 
404   // quietly return if Internet lookups are disabled
405   if (!profileManager->GetCurrentProfile().canWriteDatabases() && !g_passwordManager.bMasterUser)
406     return false;
407 
408   if (!info)
409     return false;
410 
411   if (g_application.IsVideoScanning())
412   {
413     HELPERS::ShowOKDialogText(CVariant{13346}, CVariant{14057});
414     return false;
415   }
416 
417   bool listNeedsUpdating = false;
418   // 3. Run a loop so that if we Refresh we re-run this block
419   do
420   {
421     if (!CVideoLibraryQueue::GetInstance().RefreshItemModal(item, needsRefresh, pDlgInfo->RefreshAll()))
422       return listNeedsUpdating;
423 
424     // remove directory caches and reload images
425     CUtil::DeleteVideoDatabaseDirectoryCache();
426     CGUIMessage reload(GUI_MSG_NOTIFY_ALL, 0, 0, GUI_MSG_REFRESH_THUMBS);
427     OnMessage(reload);
428 
429     pDlgInfo->SetMovie(item.get());
430     pDlgInfo->Open();
431     item->SetArt("thumb", pDlgInfo->GetThumbnail());
432     needsRefresh = pDlgInfo->NeedRefresh();
433     listNeedsUpdating = true;
434   } while (needsRefresh);
435 
436   return listNeedsUpdating;
437 }
438 
OnQueueItem(int iItem,bool first)439 void CGUIWindowVideoBase::OnQueueItem(int iItem, bool first)
440 {
441   // Determine the proper list to queue this element
442   int playlist = CServiceBroker::GetPlaylistPlayer().GetCurrentPlaylist();
443   if (playlist == PLAYLIST_NONE)
444     playlist = g_application.GetAppPlayer().GetPreferredPlaylist();
445   if (playlist == PLAYLIST_NONE)
446     playlist = PLAYLIST_VIDEO;
447 
448   // don't re-queue items from playlist window
449   if ( iItem < 0 || iItem >= m_vecItems->Size() || GetID() == WINDOW_VIDEO_PLAYLIST ) return ;
450 
451   // we take a copy so that we can alter the queue state
452   CFileItemPtr item(new CFileItem(*m_vecItems->Get(iItem)));
453   if (item->IsRAR() || item->IsZIP())
454     return;
455 
456   //  Allow queuing of unqueueable items
457   //  when we try to queue them directly
458   if (!item->CanQueue())
459     item->SetCanQueue(true);
460 
461   CFileItemList queuedItems;
462   AddItemToPlayList(item, queuedItems);
463   // if party mode, add items but DONT start playing
464   if (g_partyModeManager.IsEnabled(PARTYMODECONTEXT_VIDEO))
465   {
466     g_partyModeManager.AddUserSongs(queuedItems, false);
467     return;
468   }
469 
470   if (first && g_application.GetAppPlayer().IsPlaying())
471     CServiceBroker::GetPlaylistPlayer().Insert(playlist, queuedItems, CServiceBroker::GetPlaylistPlayer().GetCurrentSong()+1);
472   else
473     CServiceBroker::GetPlaylistPlayer().Add(playlist, queuedItems);
474   CServiceBroker::GetPlaylistPlayer().SetCurrentPlaylist(playlist);
475   // video does not auto play on queue like music
476   m_viewControl.SetSelectedItem(iItem + 1);
477 }
478 
AddItemToPlayList(const CFileItemPtr & pItem,CFileItemList & queuedItems)479 void CGUIWindowVideoBase::AddItemToPlayList(const CFileItemPtr &pItem, CFileItemList &queuedItems)
480 {
481   if (!pItem->CanQueue() || pItem->IsRAR() || pItem->IsZIP() || pItem->IsParentFolder()) // no zip/rar enqueues thank you!
482     return;
483 
484   if (pItem->m_bIsFolder)
485   {
486     if (pItem->IsParentFolder())
487       return;
488 
489     // check if it's a folder with dvd or bluray files, then just add the relevant file
490     std::string mediapath(pItem->GetOpticalMediaPath());
491     if (!mediapath.empty())
492     {
493       CFileItemPtr item(new CFileItem(mediapath, false));
494       queuedItems.Add(item);
495       return;
496     }
497 
498     // Check if we add a locked share
499     if ( pItem->m_bIsShareOrDrive )
500     {
501       CFileItem item = *pItem;
502       if ( !g_passwordManager.IsItemUnlocked( &item, "video" ) )
503         return;
504     }
505 
506     // recursive
507     CFileItemList items;
508     GetDirectory(pItem->GetPath(), items);
509     FormatAndSort(items);
510 
511     int watchedMode = CMediaSettings::GetInstance().GetWatchedMode(items.GetContent());
512     bool unwatchedOnly = watchedMode == WatchedModeUnwatched;
513     bool watchedOnly = watchedMode == WatchedModeWatched;
514     for (int i = 0; i < items.Size(); ++i)
515     {
516       if (items[i]->m_bIsFolder)
517       {
518         std::string strPath = items[i]->GetPath();
519         URIUtils::RemoveSlashAtEnd(strPath);
520         if (StringUtils::EndsWithNoCase(strPath, "sample")) // skip sample folders
521         {
522           continue;
523         }
524       }
525       else if (items[i]->HasVideoInfoTag() &&
526        ((unwatchedOnly && items[i]->GetVideoInfoTag()->GetPlayCount() > 0) ||
527         (watchedOnly && items[i]->GetVideoInfoTag()->GetPlayCount() <= 0)))
528         continue;
529 
530       AddItemToPlayList(items[i], queuedItems);
531     }
532   }
533   else
534   {
535     // just an item
536     if (pItem->IsPlayList())
537     {
538       std::unique_ptr<CPlayList> pPlayList (CPlayListFactory::Create(*pItem));
539       if (pPlayList)
540       {
541         // load it
542         if (!pPlayList->Load(pItem->GetPath()))
543         {
544           HELPERS::ShowOKDialogText(CVariant{6}, CVariant{477});
545           return; //hmmm unable to load playlist?
546         }
547 
548         CPlayList playlist = *pPlayList;
549         for (int i = 0; i < playlist.size(); ++i)
550         {
551           AddItemToPlayList(playlist[i], queuedItems);
552         }
553         return;
554       }
555     }
556     else if(pItem->IsInternetStream())
557     { // just queue the internet stream, it will be expanded on play
558       queuedItems.Add(pItem);
559     }
560     else if (pItem->IsPlugin() && pItem->GetProperty("isplayable").asBoolean())
561     { // a playable python files
562       queuedItems.Add(pItem);
563     }
564     else if (pItem->IsVideoDb())
565     { // this case is needed unless we allow IsVideo() to return true for videodb items,
566       // but then we have issues with playlists of videodb items
567       CFileItemPtr item(new CFileItem(*pItem->GetVideoInfoTag()));
568       queuedItems.Add(item);
569     }
570     else if (!pItem->IsNFO() && pItem->IsVideo())
571     {
572       queuedItems.Add(pItem);
573     }
574   }
575 }
576 
GetResumeItemOffset(const CFileItem * item,int64_t & startoffset,int & partNumber)577 void CGUIWindowVideoBase::GetResumeItemOffset(const CFileItem *item, int64_t& startoffset, int& partNumber)
578 {
579   // do not resume Live TV and 'deleted' items (e.g. trashed pvr recordings)
580   if (item->IsLiveTV() || item->IsDeleted())
581     return;
582 
583   startoffset = 0;
584   partNumber = 0;
585 
586   if (!item->IsNFO() && !item->IsPlayList())
587   {
588     if (item->GetCurrentResumeTimeAndPartNumber(startoffset, partNumber))
589     {
590       startoffset = CUtil::ConvertSecsToMilliSecs(startoffset);
591     }
592     else
593     {
594       CBookmark bookmark;
595       std::string strPath = item->GetPath();
596       if ((item->IsVideoDb() || item->IsDVD()) && item->HasVideoInfoTag())
597         strPath = item->GetVideoInfoTag()->m_strFileNameAndPath;
598 
599       CVideoDatabase db;
600       if (!db.Open())
601       {
602         CLog::Log(LOGERROR, "%s - Cannot open VideoDatabase", __FUNCTION__);
603         return;
604       }
605       if (db.GetResumeBookMark(strPath, bookmark))
606       {
607         startoffset = CUtil::ConvertSecsToMilliSecs(bookmark.timeInSeconds);
608         partNumber = bookmark.partNumber;
609       }
610       db.Close();
611     }
612   }
613 }
614 
HasResumeItemOffset(const CFileItem * item)615 bool CGUIWindowVideoBase::HasResumeItemOffset(const CFileItem *item)
616 {
617   int64_t startoffset = 0;
618   int partNumber = 0;
619   GetResumeItemOffset(item, startoffset, partNumber);
620   return startoffset > 0;
621 }
622 
OnClick(int iItem,const std::string & player)623 bool CGUIWindowVideoBase::OnClick(int iItem, const std::string &player)
624 {
625   return CGUIMediaWindow::OnClick(iItem, player);
626 }
627 
OnSelect(int iItem)628 bool CGUIWindowVideoBase::OnSelect(int iItem)
629 {
630   if (iItem < 0 || iItem >= m_vecItems->Size())
631     return false;
632 
633   CFileItemPtr item = m_vecItems->Get(iItem);
634 
635   std::string path = item->GetPath();
636   if (!item->m_bIsFolder && path != "add" &&
637       !StringUtils::StartsWith(path, "newsmartplaylist://") &&
638       !StringUtils::StartsWith(path, "newplaylist://") &&
639       !StringUtils::StartsWith(path, "newtag://") &&
640       !StringUtils::StartsWith(path, "script://"))
641     return OnFileAction(iItem, CServiceBroker::GetSettingsComponent()->GetSettings()->GetInt(CSettings::SETTING_MYVIDEOS_SELECTACTION), "");
642 
643   return CGUIMediaWindow::OnSelect(iItem);
644 }
645 
OnFileAction(int iItem,int action,const std::string & player)646 bool CGUIWindowVideoBase::OnFileAction(int iItem, int action, const std::string& player)
647 {
648   CFileItemPtr item = m_vecItems->Get(iItem);
649 
650   // Reset the current start offset. The actual resume
651   // option is set in the switch, based on the action passed.
652   item->m_lStartOffset = 0;
653 
654   switch (action)
655   {
656   case SELECT_ACTION_CHOOSE:
657     {
658       CContextButtons choices;
659 
660       if (item->IsVideoDb())
661       {
662         std::string itemPath(item->GetPath());
663         itemPath = item->GetVideoInfoTag()->m_strFileNameAndPath;
664         if (URIUtils::IsStack(itemPath) && CFileItem(CStackDirectory::GetFirstStackedFile(itemPath),false).IsDiscImage())
665           choices.Add(SELECT_ACTION_PLAYPART, 20324); // Play Part
666       }
667 
668       std::string resumeString = GetResumeString(*item);
669       if (!resumeString.empty())
670       {
671         choices.Add(SELECT_ACTION_RESUME, resumeString);
672         choices.Add(SELECT_ACTION_PLAY, 12021);   // Play from beginning
673       }
674       else
675         choices.Add(SELECT_ACTION_PLAY, 208);   // Play
676 
677       choices.Add(SELECT_ACTION_INFO, 22081); // Info
678       choices.Add(SELECT_ACTION_MORE, 22082); // More
679       int value = CGUIDialogContextMenu::ShowAndGetChoice(choices);
680       if (value < 0)
681         return true;
682 
683       return OnFileAction(iItem, value, player);
684     }
685     break;
686   case SELECT_ACTION_PLAY_OR_RESUME:
687     return OnResumeItem(iItem, player);
688   case SELECT_ACTION_INFO:
689     if (OnItemInfo(iItem))
690       return true;
691     break;
692   case SELECT_ACTION_MORE:
693     OnPopupMenu(iItem);
694     return true;
695   case SELECT_ACTION_RESUME:
696     item->m_lStartOffset = STARTOFFSET_RESUME;
697     break;
698   case SELECT_ACTION_PLAYPART:
699     if (!OnPlayStackPart(iItem))
700       return false;
701     break;
702   case SELECT_ACTION_QUEUE:
703     OnQueueItem(iItem);
704     return true;
705   case SELECT_ACTION_PLAY:
706   default:
707     break;
708   }
709   return OnClick(iItem, player);
710 }
711 
OnItemInfo(int iItem)712 bool CGUIWindowVideoBase::OnItemInfo(int iItem)
713 {
714   if (iItem < 0 || iItem >= m_vecItems->Size())
715     return false;
716 
717   CFileItemPtr item = m_vecItems->Get(iItem);
718 
719   if (item->IsPath("add") || item->IsParentFolder() ||
720      (item->IsPlayList() && !URIUtils::HasExtension(item->GetDynPath(), ".strm")))
721     return false;
722 
723   if (!m_vecItems->IsPlugin() && (item->IsPlugin() || item->IsScript()))
724     return CGUIDialogAddonInfo::ShowForItem(item);
725 
726   if (item->m_bIsFolder &&
727       item->IsVideoDb() &&
728       StringUtils::StartsWith(item->GetPath(), "videodb://movies/sets/"))
729     return ShowIMDB(item, nullptr, true);
730 
731   ADDON::ScraperPtr scraper;
732 
733   // Match visibility test of CMusicInfo::IsVisible
734   if (item->IsVideoDb() && item->HasVideoInfoTag() &&
735       (item->HasProperty("artist_musicid") || item->HasProperty("album_musicid")))
736   {
737     CGUIDialogMusicInfo::ShowFor(item.get());
738     return true;
739   }
740   if (!m_vecItems->IsPlugin() && !m_vecItems->IsRSS() && !m_vecItems->IsLiveTV())
741   {
742     std::string strDir;
743     if (item->IsVideoDb()       &&
744         item->HasVideoInfoTag() &&
745         !item->GetVideoInfoTag()->m_strPath.empty())
746     {
747       strDir = item->GetVideoInfoTag()->m_strPath;
748     }
749     else
750       strDir = URIUtils::GetDirectory(item->GetPath());
751 
752     SScanSettings settings;
753     bool foundDirectly = false;
754     scraper = m_database.GetScraperForPath(strDir, settings, foundDirectly);
755 
756     if (!scraper &&
757         !(m_database.HasMovieInfo(item->GetPath()) ||
758           m_database.HasTvShowInfo(strDir)           ||
759           m_database.HasEpisodeInfo(item->GetPath())))
760     {
761       return false;
762     }
763 
764     if (scraper && scraper->Content() == CONTENT_TVSHOWS && foundDirectly && !settings.parent_name_root) // dont lookup on root tvshow folder
765       return true;
766   }
767 
768   OnItemInfo(*item, scraper);
769 
770   // Return whether or not we have information to display.
771   // Note: This will cause the default select action to start
772   // playback in case it's set to "Show information".
773   return item->HasVideoInfoTag();
774 }
775 
OnRestartItem(int iItem,const std::string & player)776 void CGUIWindowVideoBase::OnRestartItem(int iItem, const std::string &player)
777 {
778   CGUIMediaWindow::OnClick(iItem, player);
779 }
780 
GetResumeString(const CFileItem & item)781 std::string CGUIWindowVideoBase::GetResumeString(const CFileItem &item)
782 {
783   std::string resumeString;
784   int64_t startOffset = 0;
785   int startPart = 0;
786   GetResumeItemOffset(&item, startOffset, startPart);
787   if (startOffset > 0)
788   {
789     resumeString = StringUtils::Format(g_localizeStrings.Get(12022).c_str(),
790         StringUtils::SecondsToTimeString(static_cast<long>(CUtil::ConvertMilliSecsToSecsInt(startOffset)), TIME_FORMAT_HH_MM_SS).c_str());
791     if (startPart > 0)
792     {
793       std::string partString = StringUtils::Format(g_localizeStrings.Get(23051).c_str(), startPart);
794       resumeString += " (" + partString + ")";
795     }
796   }
797   return resumeString;
798 }
799 
ShowResumeMenu(CFileItem & item)800 bool CGUIWindowVideoBase::ShowResumeMenu(CFileItem &item)
801 {
802   if (!item.m_bIsFolder && !item.IsPVR())
803   {
804     std::string resumeString = GetResumeString(item);
805     if (!resumeString.empty())
806     { // prompt user whether they wish to resume
807       CContextButtons choices;
808       choices.Add(1, resumeString);
809       choices.Add(2, 12021); // Play from beginning
810       int retVal = CGUIDialogContextMenu::ShowAndGetChoice(choices);
811       if (retVal < 0)
812         return false; // don't do anything
813       if (retVal == 1)
814         item.m_lStartOffset = STARTOFFSET_RESUME;
815     }
816   }
817   return true;
818 }
819 
OnResumeItem(int iItem,const std::string & player)820 bool CGUIWindowVideoBase::OnResumeItem(int iItem, const std::string &player)
821 {
822   if (iItem < 0 || iItem >= m_vecItems->Size()) return true;
823   CFileItemPtr item = m_vecItems->Get(iItem);
824 
825   if (item->m_bIsFolder)
826   {
827     // resuming directories isn't supported yet. play.
828     PlayItem(iItem, player);
829     return true;
830   }
831 
832   std::string resumeString = GetResumeString(*item);
833 
834   if (!resumeString.empty())
835   {
836     CContextButtons choices;
837     choices.Add(SELECT_ACTION_RESUME, resumeString);
838     choices.Add(SELECT_ACTION_PLAY, 12021);   // Play from beginning
839     int value = CGUIDialogContextMenu::ShowAndGetChoice(choices);
840     if (value < 0)
841       return true;
842     return OnFileAction(iItem, value, player);
843   }
844 
845   return OnFileAction(iItem, SELECT_ACTION_PLAY, player);
846 }
847 
GetContextButtons(int itemNumber,CContextButtons & buttons)848 void CGUIWindowVideoBase::GetContextButtons(int itemNumber, CContextButtons &buttons)
849 {
850   CFileItemPtr item;
851   if (itemNumber >= 0 && itemNumber < m_vecItems->Size())
852     item = m_vecItems->Get(itemNumber);
853 
854   // contextual buttons
855   if (item)
856   {
857     if (!item->IsParentFolder())
858     {
859       std::string path(item->GetPath());
860       if (item->IsVideoDb() && item->HasVideoInfoTag())
861         path = item->GetVideoInfoTag()->m_strFileNameAndPath;
862 
863       if (!item->IsPath("add") && !item->IsPlugin() &&
864           !item->IsScript() && !item->IsAddonsPath() && !item->IsLiveTV())
865       {
866         if (URIUtils::IsStack(path))
867         {
868           std::vector<uint64_t> times;
869           if (m_database.GetStackTimes(path,times) || CFileItem(CStackDirectory::GetFirstStackedFile(path),false).IsDiscImage())
870             buttons.Add(CONTEXT_BUTTON_PLAY_PART, 20324);
871         }
872 
873         // allow a folder to be ad-hoc queued and played by the default player
874         if (item->m_bIsFolder || (item->IsPlayList() &&
875            !CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_playlistAsFolders))
876         {
877           buttons.Add(CONTEXT_BUTTON_PLAY_ITEM, 208);
878         }
879 
880         if (!m_vecItems->GetPath().empty() && !StringUtils::StartsWithNoCase(item->GetPath(), "newsmartplaylist://") && !StringUtils::StartsWithNoCase(item->GetPath(), "newtag://")
881             && !m_vecItems->IsSourcesPath())
882         {
883           buttons.Add(CONTEXT_BUTTON_QUEUE_ITEM, 13347);      // Add to Playlist
884           buttons.Add(CONTEXT_BUTTON_PLAY_NEXT, 10008);       // Play next
885         }
886       }
887 
888       if (!item->m_bIsFolder && !(item->IsPlayList() && !CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_playlistAsFolders))
889       {
890         const CPlayerCoreFactory &playerCoreFactory = CServiceBroker::GetPlayerCoreFactory();
891 
892         // get players
893         std::vector<std::string> players;
894         if (item->IsVideoDb())
895         {
896           CFileItem item2(item->GetVideoInfoTag()->m_strFileNameAndPath, false);
897           playerCoreFactory.GetPlayers(item2, players);
898         }
899         else
900           playerCoreFactory.GetPlayers(*item, players);
901 
902         if (players.size() > 1)
903           buttons.Add(CONTEXT_BUTTON_PLAY_WITH, 15213);
904       }
905       if (item->IsSmartPlayList())
906       {
907         buttons.Add(CONTEXT_BUTTON_PLAY_PARTYMODE, 15216); // Play in Partymode
908       }
909 
910       // if the item isn't a folder or script, is not explicitly marked as not playable,
911       // is a member of a list rather than a single item and we're not on the last element of the list,
912       // then add either 'play from here' or 'play only this' depending on default behaviour
913       if (!(item->m_bIsFolder || item->IsScript()) &&
914           (!item->HasProperty("IsPlayable") || item->GetProperty("IsPlayable").asBoolean()) &&
915           m_vecItems->Size() > 1 && itemNumber < m_vecItems->Size() - 1)
916       {
917         int settingValue = SETTING_AUTOPLAYNEXT_UNCATEGORIZED;
918 
919         if (item->IsVideoDb() && item->HasVideoInfoTag())
920         {
921           const std::string mediaType = item->GetVideoInfoTag()->m_type;
922 
923           if (mediaType == MediaTypeMusicVideo)
924             settingValue = SETTING_AUTOPLAYNEXT_MUSICVIDEOS;
925           else if (mediaType == MediaTypeEpisode)
926             settingValue = SETTING_AUTOPLAYNEXT_EPISODES;
927           else if (mediaType == MediaTypeMovie)
928             settingValue = SETTING_AUTOPLAYNEXT_MOVIES;
929         }
930 
931         const auto setting = std::dynamic_pointer_cast<CSettingList>(
932                    CServiceBroker::GetSettingsComponent()->GetSettings()->GetSetting(
933                    CSettings::SETTING_VIDEOPLAYER_AUTOPLAYNEXTITEM));
934 
935         if (setting && CSettingUtils::FindIntInList(setting, settingValue))
936           buttons.Add(CONTEXT_BUTTON_PLAY_ONLY_THIS, 13434);
937         else
938           buttons.Add(CONTEXT_BUTTON_PLAY_AND_QUEUE, 13412);
939       }
940       if (item->IsSmartPlayList() || m_vecItems->IsSmartPlayList())
941         buttons.Add(CONTEXT_BUTTON_EDIT_SMART_PLAYLIST, 586);
942     }
943   }
944   CGUIMediaWindow::GetContextButtons(itemNumber, buttons);
945 }
946 
OnPlayStackPart(int iItem)947 bool CGUIWindowVideoBase::OnPlayStackPart(int iItem)
948 {
949   if (iItem < 0 || iItem >= m_vecItems->Size())
950     return false;
951 
952   CFileItemPtr stack = m_vecItems->Get(iItem);
953   std::string path(stack->GetPath());
954   if (stack->IsVideoDb())
955     path = stack->GetVideoInfoTag()->m_strFileNameAndPath;
956 
957   if (!URIUtils::IsStack(path))
958     return false;
959 
960   CFileItemList parts;
961   CDirectory::GetDirectory(path, parts, "", DIR_FLAG_DEFAULTS);
962 
963   for (int i = 0; i < parts.Size(); i++)
964     parts[i]->SetLabel(StringUtils::Format(g_localizeStrings.Get(23051).c_str(), i+1));
965 
966   CGUIDialogSelect* pDialog = CServiceBroker::GetGUI()->GetWindowManager().GetWindow<CGUIDialogSelect>(WINDOW_DIALOG_SELECT);
967 
968   pDialog->Reset();
969   pDialog->SetHeading(CVariant{20324});
970   pDialog->SetItems(parts);
971   pDialog->Open();
972 
973   if (!pDialog->IsConfirmed())
974     return false;
975 
976   int selectedFile = pDialog->GetSelectedItem();
977   if (selectedFile >= 0)
978   {
979     // ISO stack
980     if (CFileItem(CStackDirectory::GetFirstStackedFile(path),false).IsDiscImage())
981     {
982       std::string resumeString = CGUIWindowVideoBase::GetResumeString(*(parts[selectedFile].get()));
983       stack->m_lStartOffset = 0;
984       if (!resumeString.empty())
985       {
986         CContextButtons choices;
987         choices.Add(SELECT_ACTION_RESUME, resumeString);
988         choices.Add(SELECT_ACTION_PLAY, 12021);   // Play from beginning
989         int value = CGUIDialogContextMenu::ShowAndGetChoice(choices);
990         if (value == SELECT_ACTION_RESUME)
991           GetResumeItemOffset(parts[selectedFile].get(), stack->m_lStartOffset, stack->m_lStartPartNumber);
992         else if (value != SELECT_ACTION_PLAY)
993           return false; // if not selected PLAY, then we changed our mind so return
994       }
995       stack->m_lStartPartNumber = selectedFile + 1;
996     }
997     // regular stack
998     else
999     {
1000       if (selectedFile > 0)
1001       {
1002         std::vector<uint64_t> times;
1003         if (m_database.GetStackTimes(path,times))
1004           stack->m_lStartOffset = times[selectedFile - 1];
1005       }
1006       else
1007         stack->m_lStartOffset = 0;
1008     }
1009 
1010 
1011   }
1012 
1013   return true;
1014 }
1015 
OnContextButton(int itemNumber,CONTEXT_BUTTON button)1016 bool CGUIWindowVideoBase::OnContextButton(int itemNumber, CONTEXT_BUTTON button)
1017 {
1018   CFileItemPtr item;
1019   if (itemNumber >= 0 && itemNumber < m_vecItems->Size())
1020     item = m_vecItems->Get(itemNumber);
1021   switch (button)
1022   {
1023   case CONTEXT_BUTTON_SET_CONTENT:
1024     {
1025       OnAssignContent(item->HasVideoInfoTag() && !item->GetVideoInfoTag()->m_strPath.empty() ? item->GetVideoInfoTag()->m_strPath : item->GetPath());
1026       return true;
1027     }
1028   case CONTEXT_BUTTON_PLAY_PART:
1029     {
1030       if (OnPlayStackPart(itemNumber))
1031       {
1032         // call CGUIMediaWindow::OnClick() as otherwise autoresume will kick in
1033         CGUIMediaWindow::OnClick(itemNumber);
1034         return true;
1035       }
1036       else
1037         return false;
1038     }
1039   case CONTEXT_BUTTON_QUEUE_ITEM:
1040     OnQueueItem(itemNumber);
1041     return true;
1042 
1043   case CONTEXT_BUTTON_PLAY_NEXT:
1044     OnQueueItem(itemNumber, true);
1045     return true;
1046 
1047   case CONTEXT_BUTTON_PLAY_ITEM:
1048     PlayItem(itemNumber);
1049     return true;
1050 
1051   case CONTEXT_BUTTON_PLAY_WITH:
1052     {
1053       const CPlayerCoreFactory &playerCoreFactory = CServiceBroker::GetPlayerCoreFactory();
1054 
1055       std::vector<std::string> players;
1056       if (item->IsVideoDb())
1057       {
1058         CFileItem item2(*item->GetVideoInfoTag());
1059         playerCoreFactory.GetPlayers(item2, players);
1060       }
1061       else
1062         playerCoreFactory.GetPlayers(*item, players);
1063 
1064       std:: string player = playerCoreFactory.SelectPlayerDialog(players);
1065       if (!player.empty())
1066       {
1067         // any other select actions but play or resume, resume, play or playpart
1068         // don't make any sense here since the user already decided that he'd
1069         // like to play the item (just with a specific player)
1070         VideoSelectAction selectAction = (VideoSelectAction)CServiceBroker::GetSettingsComponent()->GetSettings()->GetInt(CSettings::SETTING_MYVIDEOS_SELECTACTION);
1071         if (selectAction != SELECT_ACTION_PLAY_OR_RESUME &&
1072             selectAction != SELECT_ACTION_RESUME &&
1073             selectAction != SELECT_ACTION_PLAY &&
1074             selectAction != SELECT_ACTION_PLAYPART)
1075           selectAction = SELECT_ACTION_PLAY_OR_RESUME;
1076         return OnFileAction(itemNumber, selectAction, player);
1077       }
1078       return true;
1079     }
1080 
1081   case CONTEXT_BUTTON_PLAY_PARTYMODE:
1082     g_partyModeManager.Enable(PARTYMODECONTEXT_VIDEO, m_vecItems->Get(itemNumber)->GetPath());
1083     return true;
1084 
1085   case CONTEXT_BUTTON_SCAN:
1086     {
1087       if( !item)
1088         return false;
1089       ADDON::ScraperPtr info;
1090       SScanSettings settings;
1091       GetScraperForItem(item.get(), info, settings);
1092       std::string strPath = item->GetPath();
1093       if (item->IsVideoDb() && (!item->m_bIsFolder || item->GetVideoInfoTag()->m_strPath.empty()))
1094         return false;
1095 
1096       if (item->IsVideoDb())
1097         strPath = item->GetVideoInfoTag()->m_strPath;
1098 
1099       if (!info || info->Content() == CONTENT_NONE)
1100         return false;
1101 
1102       if (item->m_bIsFolder)
1103       {
1104         OnScan(strPath, true);
1105       }
1106       else
1107         OnItemInfo(*item, info);
1108 
1109       return true;
1110     }
1111   case CONTEXT_BUTTON_DELETE:
1112     OnDeleteItem(itemNumber);
1113     return true;
1114   case CONTEXT_BUTTON_EDIT_SMART_PLAYLIST:
1115     {
1116       std::string playlist = m_vecItems->Get(itemNumber)->IsSmartPlayList() ? m_vecItems->Get(itemNumber)->GetPath() : m_vecItems->GetPath(); // save path as activatewindow will destroy our items
1117       if (CGUIDialogSmartPlaylistEditor::EditPlaylist(playlist, "video"))
1118         Refresh(true); // need to update
1119       return true;
1120     }
1121   case CONTEXT_BUTTON_RENAME:
1122     OnRenameItem(itemNumber);
1123     return true;
1124   case CONTEXT_BUTTON_PLAY_AND_QUEUE:
1125     return OnPlayAndQueueMedia(item);
1126   case CONTEXT_BUTTON_PLAY_ONLY_THIS:
1127     return OnPlayMedia(itemNumber);
1128   default:
1129     break;
1130   }
1131   return CGUIMediaWindow::OnContextButton(itemNumber, button);
1132 }
1133 
OnPlayMedia(int iItem,const std::string & player)1134 bool CGUIWindowVideoBase::OnPlayMedia(int iItem, const std::string &player)
1135 {
1136   if ( iItem < 0 || iItem >= m_vecItems->Size() )
1137     return false;
1138 
1139   CFileItemPtr pItem = m_vecItems->Get(iItem);
1140 
1141   // party mode
1142   if (g_partyModeManager.IsEnabled(PARTYMODECONTEXT_VIDEO))
1143   {
1144     CPlayList playlistTemp;
1145     playlistTemp.Add(pItem);
1146     g_partyModeManager.AddUserSongs(playlistTemp, true);
1147     return true;
1148   }
1149 
1150   // Reset Playlistplayer, playback started now does
1151   // not use the playlistplayer.
1152   CServiceBroker::GetPlaylistPlayer().Reset();
1153   CServiceBroker::GetPlaylistPlayer().SetCurrentPlaylist(PLAYLIST_NONE);
1154 
1155   CFileItem item(*pItem);
1156   if (pItem->IsVideoDb())
1157   {
1158     item.SetPath(pItem->GetVideoInfoTag()->m_strFileNameAndPath);
1159     item.SetProperty("original_listitem_url", pItem->GetPath());
1160   }
1161   CLog::Log(LOGDEBUG, "%s %s", __FUNCTION__, CURL::GetRedacted(item.GetPath()).c_str());
1162 
1163   PlayMovie(&item, player);
1164 
1165   return true;
1166 }
1167 
OnPlayAndQueueMedia(const CFileItemPtr & item,const std::string & player)1168 bool CGUIWindowVideoBase::OnPlayAndQueueMedia(const CFileItemPtr& item, const std::string& player)
1169 {
1170   // Get the current playlist and make sure it is not shuffled
1171   int iPlaylist = m_guiState->GetPlaylist();
1172   if (iPlaylist != PLAYLIST_NONE && CServiceBroker::GetPlaylistPlayer().IsShuffled(iPlaylist))
1173      CServiceBroker::GetPlaylistPlayer().SetShuffle(iPlaylist, false);
1174 
1175   CFileItemPtr movieItem(new CFileItem(*item));
1176 
1177   // Call the base method to actually queue the items
1178   // and start playing the given item
1179   return CGUIMediaWindow::OnPlayAndQueueMedia(movieItem, player);
1180 }
1181 
PlayMovie(const CFileItem * item,const std::string & player)1182 void CGUIWindowVideoBase::PlayMovie(const CFileItem *item, const std::string &player)
1183 {
1184   if(m_thumbLoader.IsLoading())
1185     m_thumbLoader.StopAsync();
1186 
1187   CServiceBroker::GetPlaylistPlayer().Play(std::make_shared<CFileItem>(*item), player);
1188 
1189   if(!g_application.GetAppPlayer().IsPlayingVideo())
1190     m_thumbLoader.Load(*m_vecItems);
1191 }
1192 
OnDeleteItem(int iItem)1193 void CGUIWindowVideoBase::OnDeleteItem(int iItem)
1194 {
1195   if ( iItem < 0 || iItem >= m_vecItems->Size())
1196     return;
1197 
1198   OnDeleteItem(m_vecItems->Get(iItem));
1199 
1200   Refresh(true);
1201   m_viewControl.SetSelectedItem(iItem);
1202 }
1203 
OnDeleteItem(const CFileItemPtr & item)1204 void CGUIWindowVideoBase::OnDeleteItem(const CFileItemPtr& item)
1205 {
1206   // HACK: stacked files need to be treated as folders in order to be deleted
1207   if (item->IsStack())
1208     item->m_bIsFolder = true;
1209 
1210   const std::shared_ptr<CProfileManager> profileManager = CServiceBroker::GetSettingsComponent()->GetProfileManager();
1211 
1212   if (profileManager->GetCurrentProfile().getLockMode() != LOCK_MODE_EVERYONE &&
1213       profileManager->GetCurrentProfile().filesLocked())
1214   {
1215     if (!g_passwordManager.IsMasterLockUnlocked(true))
1216       return;
1217   }
1218 
1219   if ((CServiceBroker::GetSettingsComponent()->GetSettings()->GetBool(CSettings::SETTING_FILELISTS_ALLOWFILEDELETION) ||
1220        m_vecItems->IsPath("special://videoplaylists/")) &&
1221       CUtil::SupportsWriteFileOperations(item->GetPath()))
1222   {
1223     CGUIComponent *gui = CServiceBroker::GetGUI();
1224     if (gui && gui->ConfirmDelete(item->GetPath()))
1225       CFileUtils::DeleteItem(item);
1226   }
1227 }
1228 
LoadPlayList(const std::string & strPlayList,int iPlayList)1229 void CGUIWindowVideoBase::LoadPlayList(const std::string& strPlayList, int iPlayList /* = PLAYLIST_VIDEO */)
1230 {
1231   // if partymode is active, we disable it
1232   if (g_partyModeManager.IsEnabled())
1233     g_partyModeManager.Disable();
1234 
1235   // load a playlist like .m3u, .pls
1236   // first get correct factory to load playlist
1237   std::unique_ptr<CPlayList> pPlayList (CPlayListFactory::Create(strPlayList));
1238   if (pPlayList)
1239   {
1240     // load it
1241     if (!pPlayList->Load(strPlayList))
1242     {
1243       HELPERS::ShowOKDialogText(CVariant{6}, CVariant{477});
1244       return; //hmmm unable to load playlist?
1245     }
1246   }
1247 
1248   if (g_application.ProcessAndStartPlaylist(strPlayList, *pPlayList, iPlayList))
1249   {
1250     if (m_guiState)
1251       m_guiState->SetPlaylistDirectory("playlistvideo://");
1252   }
1253 }
1254 
PlayItem(int iItem,const std::string & player)1255 void CGUIWindowVideoBase::PlayItem(int iItem, const std::string &player)
1256 {
1257   // restrictions should be placed in the appropriate window code
1258   // only call the base code if the item passes since this clears
1259   // the currently playing temp playlist
1260 
1261   const CFileItemPtr pItem = m_vecItems->Get(iItem);
1262   // if its a folder, build a temp playlist
1263   if (pItem->m_bIsFolder && !pItem->IsPlugin())
1264   {
1265     // take a copy so we can alter the queue state
1266     CFileItemPtr item(new CFileItem(*m_vecItems->Get(iItem)));
1267 
1268     //  Allow queuing of unqueueable items
1269     //  when we try to queue them directly
1270     if (!item->CanQueue())
1271       item->SetCanQueue(true);
1272 
1273     // skip ".."
1274     if (item->IsParentFolder())
1275       return;
1276 
1277     // recursively add items to list
1278     CFileItemList queuedItems;
1279     AddItemToPlayList(item, queuedItems);
1280 
1281     CServiceBroker::GetPlaylistPlayer().ClearPlaylist(PLAYLIST_VIDEO);
1282     CServiceBroker::GetPlaylistPlayer().Reset();
1283     CServiceBroker::GetPlaylistPlayer().Add(PLAYLIST_VIDEO, queuedItems);
1284     CServiceBroker::GetPlaylistPlayer().SetCurrentPlaylist(PLAYLIST_VIDEO);
1285     CServiceBroker::GetPlaylistPlayer().Play();
1286   }
1287   else if (pItem->IsPlayList())
1288   {
1289     // load the playlist the old way
1290     LoadPlayList(pItem->GetPath(), PLAYLIST_VIDEO);
1291   }
1292   else
1293   {
1294     // single item, play it
1295     OnClick(iItem, player);
1296   }
1297 }
1298 
Update(const std::string & strDirectory,bool updateFilterPath)1299 bool CGUIWindowVideoBase::Update(const std::string &strDirectory, bool updateFilterPath /* = true */)
1300 {
1301   if (m_thumbLoader.IsLoading())
1302     m_thumbLoader.StopThread();
1303 
1304   if (!CGUIMediaWindow::Update(strDirectory, updateFilterPath))
1305     return false;
1306 
1307   // might already be running from GetGroupedItems
1308   if (!m_thumbLoader.IsLoading())
1309     m_thumbLoader.Load(*m_vecItems);
1310 
1311   return true;
1312 }
1313 
GetDirectory(const std::string & strDirectory,CFileItemList & items)1314 bool CGUIWindowVideoBase::GetDirectory(const std::string &strDirectory, CFileItemList &items)
1315 {
1316   bool bResult = CGUIMediaWindow::GetDirectory(strDirectory, items);
1317 
1318   // add in the "New Playlist" item if we're in the playlists folder
1319   if ((items.GetPath() == "special://videoplaylists/") && !items.Contains("newplaylist://"))
1320   {
1321     const std::shared_ptr<CProfileManager> profileManager = CServiceBroker::GetSettingsComponent()->GetProfileManager();
1322 
1323     CFileItemPtr newPlaylist(new CFileItem(profileManager->GetUserDataItem("PartyMode-Video.xsp"),false));
1324     newPlaylist->SetLabel(g_localizeStrings.Get(16035));
1325     newPlaylist->SetLabelPreformatted(true);
1326     newPlaylist->SetArt("icon", "DefaultPartyMode.png");
1327     newPlaylist->m_bIsFolder = true;
1328     items.Add(newPlaylist);
1329 
1330 /*    newPlaylist.reset(new CFileItem("newplaylist://", false));
1331     newPlaylist->SetLabel(g_localizeStrings.Get(525));
1332     newPlaylist->SetLabelPreformatted(true);
1333     items.Add(newPlaylist);
1334 */
1335     newPlaylist.reset(new CFileItem("newsmartplaylist://video", false));
1336     newPlaylist->SetLabel(g_localizeStrings.Get(21437));  // "new smart playlist..."
1337     newPlaylist->SetArt("icon", "DefaultAddSource.png");
1338     newPlaylist->SetLabelPreformatted(true);
1339     items.Add(newPlaylist);
1340   }
1341 
1342   m_stackingAvailable = StackingAvailable(items);
1343   // we may also be in a tvshow files listing
1344   // (ideally this should be removed, and our stack regexps tidied up if necessary
1345   // No "normal" episodes should stack, and multi-parts should be supported)
1346   ADDON::ScraperPtr info = m_database.GetScraperForPath(strDirectory);
1347   if (info && info->Content() == CONTENT_TVSHOWS)
1348     m_stackingAvailable = false;
1349 
1350   if (m_stackingAvailable && !items.IsStack() && CServiceBroker::GetSettingsComponent()->GetSettings()->GetBool(CSettings::SETTING_MYVIDEOS_STACKVIDEOS))
1351     items.Stack();
1352 
1353   return bResult;
1354 }
1355 
StackingAvailable(const CFileItemList & items)1356 bool CGUIWindowVideoBase::StackingAvailable(const CFileItemList &items)
1357 {
1358   CURL url(items.GetPath());
1359   return !(items.IsPlugin() || items.IsAddonsPath()  ||
1360            items.IsRSS() || items.IsInternetStream() ||
1361            items.IsVideoDb() || url.IsProtocol("playlistvideo"));
1362 }
1363 
GetGroupedItems(CFileItemList & items)1364 void CGUIWindowVideoBase::GetGroupedItems(CFileItemList &items)
1365 {
1366   CGUIMediaWindow::GetGroupedItems(items);
1367 
1368   std::string group;
1369   bool mixed = false;
1370   if (items.HasProperty(PROPERTY_GROUP_BY))
1371     group = items.GetProperty(PROPERTY_GROUP_BY).asString();
1372   if (items.HasProperty(PROPERTY_GROUP_MIXED))
1373     mixed = items.GetProperty(PROPERTY_GROUP_MIXED).asBoolean();
1374 
1375   // group == "none" completely suppresses any grouping
1376   if (!StringUtils::EqualsNoCase(group, "none"))
1377   {
1378     CQueryParams params;
1379     CVideoDatabaseDirectory dir;
1380     dir.GetQueryParams(items.GetPath(), params);
1381     VIDEODATABASEDIRECTORY::NODE_TYPE nodeType = CVideoDatabaseDirectory::GetDirectoryChildType(m_strFilterPath);
1382     const std::shared_ptr<CSettings> settings = CServiceBroker::GetSettingsComponent()->GetSettings();
1383     if (items.GetContent() == "movies" && params.GetSetId() <= 0 &&
1384         nodeType == NODE_TYPE_TITLE_MOVIES &&
1385        (settings->GetBool(CSettings::SETTING_VIDEOLIBRARY_GROUPMOVIESETS) || (StringUtils::EqualsNoCase(group, "sets") && mixed)))
1386     {
1387       CFileItemList groupedItems;
1388       GroupAttribute groupAttributes = settings->GetBool(CSettings::SETTING_VIDEOLIBRARY_GROUPSINGLEITEMSETS) ? GroupAttributeNone : GroupAttributeIgnoreSingleItems;
1389       if (GroupUtils::GroupAndMix(GroupBySet, m_strFilterPath, items, groupedItems, groupAttributes))
1390       {
1391         items.ClearItems();
1392         items.Append(groupedItems);
1393       }
1394     }
1395   }
1396 
1397   // reload thumbs after filtering and grouping
1398   if (m_thumbLoader.IsLoading())
1399     m_thumbLoader.StopThread();
1400 
1401   m_thumbLoader.Load(items);
1402 }
1403 
CheckFilterAdvanced(CFileItemList & items) const1404 bool CGUIWindowVideoBase::CheckFilterAdvanced(CFileItemList &items) const
1405 {
1406   const std::string& content = items.GetContent();
1407   if ((items.IsVideoDb() || CanContainFilter(m_strFilterPath)) &&
1408       (StringUtils::EqualsNoCase(content, "movies")   ||
1409        StringUtils::EqualsNoCase(content, "tvshows")  ||
1410        StringUtils::EqualsNoCase(content, "episodes") ||
1411        StringUtils::EqualsNoCase(content, "musicvideos")))
1412     return true;
1413 
1414   return false;
1415 }
1416 
CanContainFilter(const std::string & strDirectory) const1417 bool CGUIWindowVideoBase::CanContainFilter(const std::string &strDirectory) const
1418 {
1419   return URIUtils::IsProtocol(strDirectory, "videodb://");
1420 }
1421 
1422 /// \brief Search the current directory for a string got from the virtual keyboard
OnSearch()1423 void CGUIWindowVideoBase::OnSearch()
1424 {
1425   std::string strSearch;
1426   if (!CGUIKeyboardFactory::ShowAndGetInput(strSearch, CVariant{g_localizeStrings.Get(16017)}, false))
1427     return ;
1428 
1429   StringUtils::ToLower(strSearch);
1430   if (m_dlgProgress)
1431   {
1432     m_dlgProgress->SetHeading(CVariant{194});
1433     m_dlgProgress->SetLine(0, CVariant{strSearch});
1434     m_dlgProgress->SetLine(1, CVariant{""});
1435     m_dlgProgress->SetLine(2, CVariant{""});
1436     m_dlgProgress->Open();
1437     m_dlgProgress->Progress();
1438   }
1439   CFileItemList items;
1440   DoSearch(strSearch, items);
1441 
1442   if (m_dlgProgress)
1443     m_dlgProgress->Close();
1444 
1445   if (items.Size())
1446   {
1447     CGUIDialogSelect* pDlgSelect = CServiceBroker::GetGUI()->GetWindowManager().GetWindow<CGUIDialogSelect>(WINDOW_DIALOG_SELECT);
1448     pDlgSelect->Reset();
1449     pDlgSelect->SetHeading(CVariant{283});
1450 
1451     for (int i = 0; i < items.Size(); i++)
1452     {
1453       CFileItemPtr pItem = items[i];
1454       pDlgSelect->Add(pItem->GetLabel());
1455     }
1456 
1457     pDlgSelect->Open();
1458 
1459     int iItem = pDlgSelect->GetSelectedItem();
1460     if (iItem < 0)
1461       return;
1462 
1463     OnSearchItemFound(items[iItem].get());
1464   }
1465   else
1466   {
1467     HELPERS::ShowOKDialogText(CVariant{194}, CVariant{284});
1468   }
1469 }
1470 
1471 /// \brief React on the selected search item
1472 /// \param pItem Search result item
OnSearchItemFound(const CFileItem * pSelItem)1473 void CGUIWindowVideoBase::OnSearchItemFound(const CFileItem* pSelItem)
1474 {
1475   if (pSelItem->m_bIsFolder)
1476   {
1477     std::string strPath = pSelItem->GetPath();
1478     std::string strParentPath;
1479     URIUtils::GetParentPath(strPath, strParentPath);
1480 
1481     Update(strParentPath);
1482 
1483     if (pSelItem->IsVideoDb() && CServiceBroker::GetSettingsComponent()->GetSettings()->GetBool(CSettings::SETTING_MYVIDEOS_FLATTEN))
1484       SetHistoryForPath("");
1485     else
1486       SetHistoryForPath(strParentPath);
1487 
1488     strPath = pSelItem->GetPath();
1489     CURL url(strPath);
1490     if (pSelItem->IsSmb() && !URIUtils::HasSlashAtEnd(strPath))
1491       strPath += "/";
1492 
1493     for (int i = 0; i < m_vecItems->Size(); i++)
1494     {
1495       CFileItemPtr pItem = m_vecItems->Get(i);
1496       if (pItem->GetPath() == strPath)
1497       {
1498         m_viewControl.SetSelectedItem(i);
1499         break;
1500       }
1501     }
1502   }
1503   else
1504   {
1505     std::string strPath = URIUtils::GetDirectory(pSelItem->GetPath());
1506 
1507     Update(strPath);
1508 
1509     if (pSelItem->IsVideoDb() && CServiceBroker::GetSettingsComponent()->GetSettings()->GetBool(CSettings::SETTING_MYVIDEOS_FLATTEN))
1510       SetHistoryForPath("");
1511     else
1512       SetHistoryForPath(strPath);
1513 
1514     for (int i = 0; i < m_vecItems->Size(); i++)
1515     {
1516       CFileItemPtr pItem = m_vecItems->Get(i);
1517       CURL url(pItem->GetPath());
1518       if (pSelItem->IsVideoDb())
1519         url.SetOptions("");
1520       if (url.Get() == pSelItem->GetPath())
1521       {
1522         m_viewControl.SetSelectedItem(i);
1523         break;
1524       }
1525     }
1526   }
1527   m_viewControl.SetFocused();
1528 }
1529 
GetScraperForItem(CFileItem * item,ADDON::ScraperPtr & info,SScanSettings & settings)1530 int CGUIWindowVideoBase::GetScraperForItem(CFileItem *item, ADDON::ScraperPtr &info, SScanSettings& settings)
1531 {
1532   if (!item)
1533     return 0;
1534 
1535   if (m_vecItems->IsPlugin() || m_vecItems->IsRSS())
1536   {
1537     info.reset();
1538     return 0;
1539   }
1540   else if(m_vecItems->IsLiveTV())
1541   {
1542     info.reset();
1543     return 0;
1544   }
1545 
1546   bool foundDirectly = false;
1547   info = m_database.GetScraperForPath(item->HasVideoInfoTag() && !item->GetVideoInfoTag()->m_strPath.empty() ? item->GetVideoInfoTag()->m_strPath : item->GetPath(), settings, foundDirectly);
1548   return foundDirectly ? 1 : 0;
1549 }
1550 
OnScan(const std::string & strPath,bool scanAll)1551 void CGUIWindowVideoBase::OnScan(const std::string& strPath, bool scanAll)
1552 {
1553     g_application.StartVideoScan(strPath, true, scanAll);
1554 }
1555 
GetStartFolder(const std::string & dir)1556 std::string CGUIWindowVideoBase::GetStartFolder(const std::string &dir)
1557 {
1558   std::string lower(dir); StringUtils::ToLower(lower);
1559   if (lower == "$playlists" || lower == "playlists")
1560     return "special://videoplaylists/";
1561   else if (lower == "plugins" || lower == "addons")
1562     return "addons://sources/video/";
1563   return CGUIMediaWindow::GetStartFolder(dir);
1564 }
1565 
AppendAndClearSearchItems(CFileItemList & searchItems,const std::string & prependLabel,CFileItemList & results)1566 void CGUIWindowVideoBase::AppendAndClearSearchItems(CFileItemList &searchItems, const std::string &prependLabel, CFileItemList &results)
1567 {
1568   if (!searchItems.Size())
1569     return;
1570 
1571   searchItems.Sort(SortByLabel, SortOrderAscending, CServiceBroker::GetSettingsComponent()->GetSettings()->GetBool(CSettings::SETTING_FILELISTS_IGNORETHEWHENSORTING) ? SortAttributeIgnoreArticle : SortAttributeNone);
1572   for (int i = 0; i < searchItems.Size(); i++)
1573     searchItems[i]->SetLabel(prependLabel + searchItems[i]->GetLabel());
1574   results.Append(searchItems);
1575 
1576   searchItems.Clear();
1577 }
1578 
OnUnAssignContent(const std::string & path,int header,int text)1579 bool CGUIWindowVideoBase::OnUnAssignContent(const std::string &path, int header, int text)
1580 {
1581   bool bCanceled;
1582   CVideoDatabase db;
1583   db.Open();
1584   if (CGUIDialogYesNo::ShowAndGetInput(CVariant{header}, CVariant{text}, bCanceled, CVariant{ "" }, CVariant{ "" }, CGUIDialogYesNo::NO_TIMEOUT))
1585   {
1586     CGUIDialogProgress *progress = CServiceBroker::GetGUI()->GetWindowManager().GetWindow<CGUIDialogProgress>(WINDOW_DIALOG_PROGRESS);
1587     db.RemoveContentForPath(path, progress);
1588     db.Close();
1589     CUtil::DeleteVideoDatabaseDirectoryCache();
1590     return true;
1591   }
1592   else
1593   {
1594     if (!bCanceled)
1595     {
1596       ADDON::ScraperPtr info;
1597       SScanSettings settings;
1598       settings.exclude = true;
1599       db.SetScraperForPath(path,info,settings);
1600     }
1601   }
1602   db.Close();
1603 
1604   return false;
1605 }
1606 
OnAssignContent(const std::string & path)1607 void CGUIWindowVideoBase::OnAssignContent(const std::string &path)
1608 {
1609   bool bScan=false;
1610   CVideoDatabase db;
1611   db.Open();
1612 
1613   SScanSettings settings;
1614   ADDON::ScraperPtr info = db.GetScraperForPath(path, settings);
1615 
1616   ADDON::ScraperPtr info2(info);
1617 
1618   if (CGUIDialogContentSettings::Show(info, settings))
1619   {
1620     if(settings.exclude || (!info && info2))
1621     {
1622       OnUnAssignContent(path, 20375, 20340);
1623     }
1624     else if (info != info2)
1625     {
1626       if (OnUnAssignContent(path, 20442, 20443))
1627         bScan = true;
1628     }
1629     db.SetScraperForPath(path, info, settings);
1630   }
1631 
1632   if (bScan)
1633   {
1634     g_application.StartVideoScan(path, true, true);
1635   }
1636 }
1637