summaryrefslogtreecommitdiffstats
path: root/xbmc/pvr/filesystem/PVRGUIDirectory.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'xbmc/pvr/filesystem/PVRGUIDirectory.cpp')
-rw-r--r--xbmc/pvr/filesystem/PVRGUIDirectory.cpp625
1 files changed, 625 insertions, 0 deletions
diff --git a/xbmc/pvr/filesystem/PVRGUIDirectory.cpp b/xbmc/pvr/filesystem/PVRGUIDirectory.cpp
new file mode 100644
index 0000000..1ef7810
--- /dev/null
+++ b/xbmc/pvr/filesystem/PVRGUIDirectory.cpp
@@ -0,0 +1,625 @@
+/*
+ * Copyright (C) 2012-2019 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "PVRGUIDirectory.h"
+
+#include "FileItem.h"
+#include "ServiceBroker.h"
+#include "guilib/LocalizeStrings.h"
+#include "guilib/WindowIDs.h"
+#include "input/WindowTranslator.h"
+#include "pvr/PVRManager.h"
+#include "pvr/addons/PVRClients.h"
+#include "pvr/channels/PVRChannel.h"
+#include "pvr/channels/PVRChannelGroupMember.h"
+#include "pvr/channels/PVRChannelGroups.h"
+#include "pvr/channels/PVRChannelGroupsContainer.h"
+#include "pvr/channels/PVRChannelsPath.h"
+#include "pvr/epg/EpgContainer.h"
+#include "pvr/epg/EpgSearchFilter.h"
+#include "pvr/epg/EpgSearchPath.h"
+#include "pvr/recordings/PVRRecording.h"
+#include "pvr/recordings/PVRRecordings.h"
+#include "pvr/recordings/PVRRecordingsPath.h"
+#include "pvr/timers/PVRTimerInfoTag.h"
+#include "pvr/timers/PVRTimers.h"
+#include "pvr/timers/PVRTimersPath.h"
+#include "settings/Settings.h"
+#include "settings/SettingsComponent.h"
+#include "utils/StringUtils.h"
+#include "utils/URIUtils.h"
+#include "utils/log.h"
+
+#include <memory>
+#include <set>
+#include <string>
+#include <vector>
+
+using namespace PVR;
+
+bool CPVRGUIDirectory::Exists() const
+{
+ if (!CServiceBroker::GetPVRManager().IsStarted())
+ return false;
+
+ return m_url.IsProtocol("pvr") && StringUtils::StartsWith(m_url.GetFileName(), "recordings");
+}
+
+bool CPVRGUIDirectory::SupportsWriteFileOperations() const
+{
+ if (!CServiceBroker::GetPVRManager().IsStarted())
+ return false;
+
+ const std::string filename = m_url.GetFileName();
+ return URIUtils::IsPVRRecording(filename);
+}
+
+namespace
+{
+
+bool GetRootDirectory(bool bRadio, CFileItemList& results)
+{
+ std::shared_ptr<CFileItem> item;
+
+ const std::shared_ptr<CPVRClients> clients = CServiceBroker::GetPVRManager().Clients();
+
+ // EPG
+ const bool bAnyClientSupportingEPG = clients->AnyClientSupportingEPG();
+ if (bAnyClientSupportingEPG)
+ {
+ item.reset(
+ new CFileItem(StringUtils::Format("pvr://guide/{}/", bRadio ? "radio" : "tv"), true));
+ item->SetLabel(g_localizeStrings.Get(19069)); // Guide
+ item->SetProperty("node.target", CWindowTranslator::TranslateWindow(bRadio ? WINDOW_RADIO_GUIDE
+ : WINDOW_TV_GUIDE));
+ item->SetArt("icon", "DefaultPVRGuide.png");
+ results.Add(item);
+ }
+
+ // Channels
+ item.reset(new CFileItem(
+ bRadio ? CPVRChannelsPath::PATH_RADIO_CHANNELS : CPVRChannelsPath::PATH_TV_CHANNELS, true));
+ item->SetLabel(g_localizeStrings.Get(19019)); // Channels
+ item->SetProperty("node.target", CWindowTranslator::TranslateWindow(bRadio ? WINDOW_RADIO_CHANNELS
+ : WINDOW_TV_CHANNELS));
+ item->SetArt("icon", "DefaultPVRChannels.png");
+ results.Add(item);
+
+ // Recordings
+ if (clients->AnyClientSupportingRecordings())
+ {
+ item.reset(new CFileItem(bRadio ? CPVRRecordingsPath::PATH_ACTIVE_RADIO_RECORDINGS
+ : CPVRRecordingsPath::PATH_ACTIVE_TV_RECORDINGS,
+ true));
+ item->SetLabel(g_localizeStrings.Get(19017)); // Recordings
+ item->SetProperty("node.target", CWindowTranslator::TranslateWindow(
+ bRadio ? WINDOW_RADIO_RECORDINGS : WINDOW_TV_RECORDINGS));
+ item->SetArt("icon", "DefaultPVRRecordings.png");
+ results.Add(item);
+ }
+
+ // Timers/Timer rules
+ // - always present, because Reminders are always available, no client support needed for this
+ item.reset(new CFileItem(
+ bRadio ? CPVRTimersPath::PATH_RADIO_TIMERS : CPVRTimersPath::PATH_TV_TIMERS, true));
+ item->SetLabel(g_localizeStrings.Get(19040)); // Timers
+ item->SetProperty("node.target", CWindowTranslator::TranslateWindow(bRadio ? WINDOW_RADIO_TIMERS
+ : WINDOW_TV_TIMERS));
+ item->SetArt("icon", "DefaultPVRTimers.png");
+ results.Add(item);
+
+ item.reset(new CFileItem(
+ bRadio ? CPVRTimersPath::PATH_RADIO_TIMER_RULES : CPVRTimersPath::PATH_TV_TIMER_RULES, true));
+ item->SetLabel(g_localizeStrings.Get(19138)); // Timer rules
+ item->SetProperty("node.target", CWindowTranslator::TranslateWindow(
+ bRadio ? WINDOW_RADIO_TIMER_RULES : WINDOW_TV_TIMER_RULES));
+ item->SetArt("icon", "DefaultPVRTimerRules.png");
+ results.Add(item);
+
+ // Search
+ if (bAnyClientSupportingEPG)
+ {
+ item.reset(new CFileItem(
+ bRadio ? CPVREpgSearchPath::PATH_RADIO_SEARCH : CPVREpgSearchPath::PATH_TV_SEARCH, true));
+ item->SetLabel(g_localizeStrings.Get(137)); // Search
+ item->SetProperty("node.target", CWindowTranslator::TranslateWindow(bRadio ? WINDOW_RADIO_SEARCH
+ : WINDOW_TV_SEARCH));
+ item->SetArt("icon", "DefaultPVRSearch.png");
+ results.Add(item);
+ }
+
+ return true;
+}
+
+} // unnamed namespace
+
+bool CPVRGUIDirectory::GetDirectory(CFileItemList& results) const
+{
+ std::string base = m_url.Get();
+ URIUtils::RemoveSlashAtEnd(base);
+
+ std::string fileName = m_url.GetFileName();
+ URIUtils::RemoveSlashAtEnd(fileName);
+
+ results.SetCacheToDisc(CFileItemList::CACHE_NEVER);
+
+ if (fileName.empty())
+ {
+ if (CServiceBroker::GetPVRManager().IsStarted())
+ {
+ std::shared_ptr<CFileItem> item;
+
+ item.reset(new CFileItem(base + "channels/", true));
+ item->SetLabel(g_localizeStrings.Get(19019)); // Channels
+ item->SetLabelPreformatted(true);
+ results.Add(item);
+
+ item.reset(new CFileItem(base + "recordings/active/", true));
+ item->SetLabel(g_localizeStrings.Get(19017)); // Recordings
+ item->SetLabelPreformatted(true);
+ results.Add(item);
+
+ item.reset(new CFileItem(base + "recordings/deleted/", true));
+ item->SetLabel(g_localizeStrings.Get(19184)); // Deleted recordings
+ item->SetLabelPreformatted(true);
+ results.Add(item);
+
+ // Sort by name only. Labels are preformatted.
+ results.AddSortMethod(SortByLabel, 551 /* Name */, LABEL_MASKS("%L", "", "%L", ""));
+ }
+ return true;
+ }
+ else if (StringUtils::StartsWith(fileName, "tv"))
+ {
+ if (CServiceBroker::GetPVRManager().IsStarted())
+ {
+ return GetRootDirectory(false, results);
+ }
+ return true;
+ }
+ else if (StringUtils::StartsWith(fileName, "radio"))
+ {
+ if (CServiceBroker::GetPVRManager().IsStarted())
+ {
+ return GetRootDirectory(true, results);
+ }
+ return true;
+ }
+ else if (StringUtils::StartsWith(fileName, "recordings"))
+ {
+ if (CServiceBroker::GetPVRManager().IsStarted())
+ {
+ return GetRecordingsDirectory(results);
+ }
+ return true;
+ }
+ else if (StringUtils::StartsWith(fileName, "channels"))
+ {
+ if (CServiceBroker::GetPVRManager().IsStarted())
+ {
+ return GetChannelsDirectory(results);
+ }
+ return true;
+ }
+ else if (StringUtils::StartsWith(fileName, "timers"))
+ {
+ if (CServiceBroker::GetPVRManager().IsStarted())
+ {
+ return GetTimersDirectory(results);
+ }
+ return true;
+ }
+
+ const CPVREpgSearchPath path(m_url.Get());
+ if (path.IsValid())
+ {
+ if (CServiceBroker::GetPVRManager().IsStarted())
+ {
+ if (path.IsSavedSearchesRoot())
+ return GetSavedSearchesDirectory(path.IsRadio(), results);
+ }
+ return true;
+ }
+
+ return false;
+}
+
+bool CPVRGUIDirectory::HasTVRecordings()
+{
+ return CServiceBroker::GetPVRManager().IsStarted() &&
+ CServiceBroker::GetPVRManager().Recordings()->GetNumTVRecordings() > 0;
+}
+
+bool CPVRGUIDirectory::HasDeletedTVRecordings()
+{
+ return CServiceBroker::GetPVRManager().IsStarted() &&
+ CServiceBroker::GetPVRManager().Recordings()->HasDeletedTVRecordings();
+}
+
+bool CPVRGUIDirectory::HasRadioRecordings()
+{
+ return CServiceBroker::GetPVRManager().IsStarted() &&
+ CServiceBroker::GetPVRManager().Recordings()->GetNumRadioRecordings() > 0;
+}
+
+bool CPVRGUIDirectory::HasDeletedRadioRecordings()
+{
+ return CServiceBroker::GetPVRManager().IsStarted() &&
+ CServiceBroker::GetPVRManager().Recordings()->HasDeletedRadioRecordings();
+}
+
+namespace
+{
+
+std::string TrimSlashes(const std::string& strOrig)
+{
+ std::string strReturn = strOrig;
+ while (strReturn[0] == '/')
+ strReturn.erase(0, 1);
+
+ URIUtils::RemoveSlashAtEnd(strReturn);
+ return strReturn;
+}
+
+bool IsDirectoryMember(const std::string& strDirectory,
+ const std::string& strEntryDirectory,
+ bool bGrouped)
+{
+ const std::string strUseDirectory = TrimSlashes(strDirectory);
+ const std::string strUseEntryDirectory = TrimSlashes(strEntryDirectory);
+
+ // Case-insensitive comparison since sub folders are created with case-insensitive matching (GetSubDirectories)
+ if (bGrouped)
+ return StringUtils::EqualsNoCase(strUseDirectory, strUseEntryDirectory);
+ else
+ return StringUtils::StartsWithNoCase(strUseEntryDirectory, strUseDirectory);
+}
+
+void GetSubDirectories(const CPVRRecordingsPath& recParentPath,
+ const std::vector<std::shared_ptr<CPVRRecording>>& recordings,
+ CFileItemList& results)
+{
+ // Only active recordings are fetched to provide sub directories.
+ // Not applicable for deleted view which is supposed to be flattened.
+ std::set<std::shared_ptr<CFileItem>> unwatchedFolders;
+ bool bRadio = recParentPath.IsRadio();
+
+ for (const auto& recording : recordings)
+ {
+ if (recording->IsDeleted())
+ continue;
+
+ if (recording->IsRadio() != bRadio)
+ continue;
+
+ const std::string strCurrent =
+ recParentPath.GetUnescapedSubDirectoryPath(recording->Directory());
+ if (strCurrent.empty())
+ continue;
+
+ CPVRRecordingsPath recChildPath(recParentPath);
+ recChildPath.AppendSegment(strCurrent);
+ const std::string strFilePath = recChildPath;
+
+ std::shared_ptr<CFileItem> item;
+ if (!results.Contains(strFilePath))
+ {
+ item.reset(new CFileItem(strCurrent, true));
+ item->SetPath(strFilePath);
+ item->SetLabel(strCurrent);
+ item->SetLabelPreformatted(true);
+ item->m_dateTime = recording->RecordingTimeAsLocalTime();
+ item->SetProperty("totalepisodes", 0);
+ item->SetProperty("watchedepisodes", 0);
+ item->SetProperty("unwatchedepisodes", 0);
+ item->SetProperty("sizeinbytes", UINT64_C(0));
+
+ // Assume all folders are watched, we'll change the overlay later
+ item->SetOverlayImage(CGUIListItem::ICON_OVERLAY_WATCHED, false);
+ results.Add(item);
+ }
+ else
+ {
+ item = results.Get(strFilePath);
+ if (item->m_dateTime < recording->RecordingTimeAsLocalTime())
+ item->m_dateTime = recording->RecordingTimeAsLocalTime();
+ }
+
+ item->IncrementProperty("totalepisodes", 1);
+ if (recording->GetPlayCount() == 0)
+ {
+ unwatchedFolders.insert(item);
+ item->IncrementProperty("unwatchedepisodes", 1);
+ }
+ else
+ {
+ item->IncrementProperty("watchedepisodes", 1);
+ }
+ item->SetLabel2(StringUtils::Format("{} / {}", item->GetProperty("watchedepisodes").asString(),
+ item->GetProperty("totalepisodes").asString()));
+
+ item->IncrementProperty("sizeinbytes", recording->GetSizeInBytes());
+ }
+
+ // Replace the incremental size of the recordings with a string equivalent
+ for (auto& item : results.GetList())
+ {
+ int64_t size = item->GetProperty("sizeinbytes").asInteger();
+ item->ClearProperty("sizeinbytes");
+ item->m_dwSize = size; // We'll also sort recording folders by size
+ if (size > 0)
+ item->SetProperty("recordingsize", StringUtils::SizeToString(size));
+ }
+
+ // Change the watched overlay to unwatched for folders containing unwatched entries
+ for (auto& item : unwatchedFolders)
+ item->SetOverlayImage(CGUIListItem::ICON_OVERLAY_UNWATCHED, false);
+}
+
+} // unnamed namespace
+
+bool CPVRGUIDirectory::GetRecordingsDirectory(CFileItemList& results) const
+{
+ results.SetContent("recordings");
+
+ bool bGrouped = false;
+ const std::vector<std::shared_ptr<CPVRRecording>> recordings =
+ CServiceBroker::GetPVRManager().Recordings()->GetAll();
+
+ if (m_url.HasOption("view"))
+ {
+ const std::string view = m_url.GetOption("view");
+ if (view == "grouped")
+ bGrouped = true;
+ else if (view == "flat")
+ bGrouped = false;
+ else
+ {
+ CLog::LogF(LOGERROR, "Unsupported value '{}' for url parameter 'view'", view);
+ return false;
+ }
+ }
+ else
+ {
+ bGrouped = CServiceBroker::GetSettingsComponent()->GetSettings()->GetBool(
+ CSettings::SETTING_PVRRECORD_GROUPRECORDINGS);
+ }
+
+ const CPVRRecordingsPath recPath(m_url.GetWithoutOptions());
+ if (recPath.IsValid())
+ {
+ // Get the directory structure if in non-flatten mode
+ // Deleted view is always flatten. So only for an active view
+ const std::string strDirectory = recPath.GetUnescapedDirectoryPath();
+ if (!recPath.IsDeleted() && bGrouped)
+ GetSubDirectories(recPath, recordings, results);
+
+ // get all files of the current directory or recursively all files starting at the current directory if in flatten mode
+ std::shared_ptr<CFileItem> item;
+ for (const auto& recording : recordings)
+ {
+ // Omit recordings not matching criteria
+ if (recording->IsDeleted() != recPath.IsDeleted() ||
+ recording->IsRadio() != recPath.IsRadio() ||
+ !IsDirectoryMember(strDirectory, recording->Directory(), bGrouped))
+ continue;
+
+ item = std::make_shared<CFileItem>(recording);
+ item->SetOverlayImage(CGUIListItem::ICON_OVERLAY_UNWATCHED, recording->GetPlayCount() > 0);
+ results.Add(item);
+ }
+ }
+
+ return recPath.IsValid();
+}
+
+bool CPVRGUIDirectory::GetSavedSearchesDirectory(bool bRadio, CFileItemList& results) const
+{
+ const std::vector<std::shared_ptr<CPVREpgSearchFilter>> searches =
+ CServiceBroker::GetPVRManager().EpgContainer().GetSavedSearches(bRadio);
+
+ for (const auto& search : searches)
+ {
+ results.Add(std::make_shared<CFileItem>(search));
+ }
+ return true;
+}
+
+bool CPVRGUIDirectory::GetChannelGroupsDirectory(bool bRadio,
+ bool bExcludeHidden,
+ CFileItemList& results)
+{
+ const CPVRChannelGroups* channelGroups =
+ CServiceBroker::GetPVRManager().ChannelGroups()->Get(bRadio);
+ if (channelGroups)
+ {
+ std::shared_ptr<CFileItem> item;
+ const std::vector<std::shared_ptr<CPVRChannelGroup>> groups =
+ channelGroups->GetMembers(bExcludeHidden);
+ for (const auto& group : groups)
+ {
+ item = std::make_shared<CFileItem>(group->GetPath(), true);
+ item->m_strTitle = group->GroupName();
+ item->SetLabel(group->GroupName());
+ results.Add(item);
+ }
+ return true;
+ }
+ return false;
+}
+
+bool CPVRGUIDirectory::GetChannelsDirectory(CFileItemList& results) const
+{
+ const CPVRChannelsPath path(m_url.GetWithoutOptions());
+ if (path.IsValid())
+ {
+ if (path.IsEmpty())
+ {
+ std::shared_ptr<CFileItem> item;
+
+ // all tv channels
+ item.reset(new CFileItem(CPVRChannelsPath::PATH_TV_CHANNELS, true));
+ item->SetLabel(g_localizeStrings.Get(19020)); // TV
+ item->SetLabelPreformatted(true);
+ results.Add(item);
+
+ // all radio channels
+ item.reset(new CFileItem(CPVRChannelsPath::PATH_RADIO_CHANNELS, true));
+ item->SetLabel(g_localizeStrings.Get(19021)); // Radio
+ item->SetLabelPreformatted(true);
+ results.Add(item);
+
+ return true;
+ }
+ else if (path.IsChannelsRoot())
+ {
+ return GetChannelGroupsDirectory(path.IsRadio(), true, results);
+ }
+ else if (path.IsChannelGroup())
+ {
+ const std::string& strGroupName = path.GetGroupName();
+ bool bShowHiddenChannels = path.IsHiddenChannelGroup();
+
+ std::shared_ptr<CPVRChannelGroup> group;
+ if (bShowHiddenChannels || strGroupName == "*") // all channels
+ {
+ group = CServiceBroker::GetPVRManager().ChannelGroups()->GetGroupAll(path.IsRadio());
+ }
+ else
+ {
+ group = CServiceBroker::GetPVRManager()
+ .ChannelGroups()
+ ->Get(path.IsRadio())
+ ->GetByName(strGroupName);
+ }
+
+ if (group)
+ {
+ const bool playedOnly =
+ (m_url.HasOption("view") && (m_url.GetOption("view") == "lastplayed"));
+
+ const std::vector<std::shared_ptr<CPVRChannelGroupMember>> groupMembers =
+ group->GetMembers();
+ for (const auto& groupMember : groupMembers)
+ {
+ if (bShowHiddenChannels != groupMember->Channel()->IsHidden())
+ continue;
+
+ if (playedOnly && !groupMember->Channel()->LastWatched())
+ continue;
+
+ results.Add(std::make_shared<CFileItem>(groupMember));
+ }
+ }
+ else
+ {
+ CLog::LogF(LOGERROR, "Unable to obtain members of channel group '{}'", strGroupName);
+ return false;
+ }
+
+ return true;
+ }
+ }
+ return false;
+}
+
+namespace
+{
+
+bool GetTimersRootDirectory(const CPVRTimersPath& path,
+ bool bHideDisabled,
+ const std::vector<std::shared_ptr<CPVRTimerInfoTag>>& timers,
+ CFileItemList& results)
+{
+ bool bRadio = path.IsRadio();
+ bool bRules = path.IsRules();
+
+ for (const auto& timer : timers)
+ {
+ if ((bRadio == timer->IsRadio() ||
+ (bRules && timer->ClientChannelUID() == PVR_TIMER_ANY_CHANNEL)) &&
+ (bRules == timer->IsTimerRule()) && (!bHideDisabled || !timer->IsDisabled()))
+ {
+ const auto item = std::make_shared<CFileItem>(timer);
+ const CPVRTimersPath timersPath(path.GetPath(), timer->ClientID(), timer->ClientIndex());
+ item->SetPath(timersPath.GetPath());
+ results.Add(item);
+ }
+ }
+ return true;
+}
+
+bool GetTimersSubDirectory(const CPVRTimersPath& path,
+ bool bHideDisabled,
+ const std::vector<std::shared_ptr<CPVRTimerInfoTag>>& timers,
+ CFileItemList& results)
+{
+ bool bRadio = path.IsRadio();
+ int iParentId = path.GetParentId();
+ int iClientId = path.GetClientId();
+
+ std::shared_ptr<CFileItem> item;
+
+ for (const auto& timer : timers)
+ {
+ if ((timer->IsRadio() == bRadio) && timer->HasParent() && (timer->ClientID() == iClientId) &&
+ (timer->ParentClientIndex() == iParentId) && (!bHideDisabled || !timer->IsDisabled()))
+ {
+ item.reset(new CFileItem(timer));
+ const CPVRTimersPath timersPath(path.GetPath(), timer->ClientID(), timer->ClientIndex());
+ item->SetPath(timersPath.GetPath());
+ results.Add(item);
+ }
+ }
+ return true;
+}
+
+} // unnamed namespace
+
+bool CPVRGUIDirectory::GetTimersDirectory(CFileItemList& results) const
+{
+ const CPVRTimersPath path(m_url.GetWithoutOptions());
+ if (path.IsValid() && (path.IsTimersRoot() || path.IsTimerRule()))
+ {
+ bool bHideDisabled = false;
+ if (m_url.HasOption("view"))
+ {
+ const std::string view = m_url.GetOption("view");
+ if (view == "hidedisabled")
+ {
+ bHideDisabled = true;
+ }
+ else
+ {
+ CLog::LogF(LOGERROR, "Unsupported value '{}' for url parameter 'view'", view);
+ return false;
+ }
+ }
+ else
+ {
+ bHideDisabled = CServiceBroker::GetSettingsComponent()->GetSettings()->GetBool(
+ CSettings::SETTING_PVRTIMERS_HIDEDISABLEDTIMERS);
+ }
+
+ const std::vector<std::shared_ptr<CPVRTimerInfoTag>> timers =
+ CServiceBroker::GetPVRManager().Timers()->GetAll();
+
+ if (path.IsTimersRoot())
+ {
+ /* Root folder containing either timer rules or timers. */
+ return GetTimersRootDirectory(path, bHideDisabled, timers, results);
+ }
+ else if (path.IsTimerRule())
+ {
+ /* Sub folder containing the timers scheduled by the given timer rule. */
+ return GetTimersSubDirectory(path, bHideDisabled, timers, results);
+ }
+ }
+
+ return false;
+}