1 /*
2  *  Copyright (C) 2005-2020 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 "PlayListXML.h"
10 
11 #include "Util.h"
12 #include "filesystem/File.h"
13 #include "media/MediaLockState.h"
14 #include "utils/StringUtils.h"
15 #include "utils/URIUtils.h"
16 #include "utils/Variant.h"
17 #include "utils/XMLUtils.h"
18 #include "utils/log.h"
19 
20 using namespace PLAYLIST;
21 using namespace XFILE;
22 
23 /*
24  Playlist example (must be stored with .pxml extension):
25 
26 <?xml version="1.0" encoding="UTF-8" standalone="yes"?>
27 <streams>
28   <!-- Stream definition. To have multiple streams, just add another <stream>...</stream> set.  !-->
29   <stream>
30     <!-- Stream URL !-->
31     <url>mms://stream02.rambler.ru/eurosport</url>
32     <!-- Stream name - used for display !-->
33     <name>Евроспорт</name>
34     <!-- Stream category - currently only LIVETV is supported !-->
35     <category>LIVETV</category>
36     <!-- Stream language code !-->
37     <lang>RU</lang>
38     <!-- Stream channel number - will be used to select stream by channel number !-->
39     <channel>1</channel>
40     <!-- Stream is password-protected !-->
41     <lockpassword>123</lockpassword>
42   </stream>
43 
44   <stream>
45     <url>mms://video.rfn.ru/vesti_24</url>
46     <name>Вести 24</name>
47     <category>LIVETV</category>
48     <lang>RU</lang>
49     <channel>2</channel>
50   </stream>
51 
52 </streams>
53 */
54 
55 
56 CPlayListXML::CPlayListXML(void) = default;
57 
58 CPlayListXML::~CPlayListXML(void) = default;
59 
60 
GetString(const TiXmlElement * pRootElement,const char * tagName)61 static inline std::string GetString( const TiXmlElement* pRootElement, const char *tagName )
62 {
63   std::string strValue;
64   if ( XMLUtils::GetString(pRootElement, tagName, strValue) )
65     return strValue;
66 
67   return "";
68 }
69 
Load(const std::string & strFileName)70 bool CPlayListXML::Load( const std::string& strFileName )
71 {
72   CXBMCTinyXML xmlDoc;
73 
74   m_strPlayListName = URIUtils::GetFileName(strFileName);
75   URIUtils::GetParentPath(strFileName, m_strBasePath);
76 
77   Clear();
78 
79   // Try to load the file as XML. If it does not load, return an error.
80   if ( !xmlDoc.LoadFile( strFileName ) )
81   {
82     CLog::Log(LOGERROR, "Playlist %s has invalid format/malformed xml", strFileName.c_str());
83     return false;
84   }
85 
86   TiXmlElement *pRootElement = xmlDoc.RootElement();
87 
88   // If the stream does not contain "streams", still ok. Not an error.
89   if (!pRootElement || StringUtils::CompareNoCase(pRootElement->Value(), "streams"))
90   {
91     CLog::Log(LOGERROR, "Playlist %s has no <streams> root", strFileName.c_str());
92     return false;
93   }
94 
95   TiXmlElement* pSet = pRootElement->FirstChildElement("stream");
96 
97   while ( pSet )
98   {
99     // Get parameters
100     std::string url = GetString( pSet, "url" );
101     std::string name = GetString( pSet, "name" );
102     std::string category = GetString( pSet, "category" );
103     std::string lang = GetString( pSet, "lang" );
104     std::string channel = GetString( pSet, "channel" );
105     std::string lockpass = GetString( pSet, "lockpassword" );
106 
107     // If url is empty, it doesn't make any sense
108     if ( !url.empty() )
109     {
110        // If the name is empty, use url
111        if ( name.empty() )
112          name = url;
113 
114        // Append language to the name, and also set as metadata
115        if ( !lang.empty() )
116          name += " [" + lang + "]";
117 
118        std::string info = name;
119        CFileItemPtr newItem( new CFileItem(info) );
120        newItem->SetPath(url);
121 
122        // Set language as metadata
123        if ( !lang.empty() )
124          newItem->SetProperty("language", lang.c_str() );
125 
126        // Set category as metadata
127        if ( !category.empty() )
128          newItem->SetProperty("category", category.c_str() );
129 
130        // Set channel as extra info and as metadata
131        if ( !channel.empty() )
132        {
133          newItem->SetProperty("remotechannel", channel.c_str() );
134          newItem->SetExtraInfo( "Channel: " + channel );
135        }
136 
137        if ( !lockpass.empty() )
138        {
139          newItem->m_strLockCode = lockpass;
140          newItem->m_iHasLock = LOCK_STATE_LOCKED;
141          newItem->m_iLockMode = LOCK_MODE_NUMERIC;
142        }
143 
144        Add(newItem);
145     }
146     else
147        CLog::Log(LOGERROR, "Playlist entry %s in file %s has missing <url> tag", name.c_str(), strFileName.c_str());
148 
149     pSet = pSet->NextSiblingElement("stream");
150   }
151 
152   return true;
153 }
154 
155 
Save(const std::string & strFileName) const156 void CPlayListXML::Save(const std::string& strFileName) const
157 {
158   if (!m_vecItems.size()) return ;
159   std::string strPlaylist = CUtil::MakeLegalPath(strFileName);
160   CFile file;
161   if (!file.OpenForWrite(strPlaylist, true))
162   {
163     CLog::Log(LOGERROR, "Could not save WPL playlist: [%s]", strPlaylist.c_str());
164     return ;
165   }
166   std::string write;
167   write += StringUtils::Format("<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?>\n");
168   write += StringUtils::Format("<streams>\n");
169   for (int i = 0; i < (int)m_vecItems.size(); ++i)
170   {
171     CFileItemPtr item = m_vecItems[i];
172     write += StringUtils::Format("  <stream>\n" );
173     write += StringUtils::Format("    <url>%s</url>", item->GetPath().c_str() );
174     write += StringUtils::Format("    <name>%s</name>", item->GetLabel().c_str() );
175 
176     if ( !item->GetProperty("language").empty() )
177       write += StringUtils::Format("    <lang>%s</lang>", item->GetProperty("language").c_str() );
178 
179     if ( !item->GetProperty("category").empty() )
180       write += StringUtils::Format("    <category>%s</category>", item->GetProperty("category").c_str() );
181 
182     if ( !item->GetProperty("remotechannel").empty() )
183       write += StringUtils::Format("    <channel>%s</channel>", item->GetProperty("remotechannel").c_str() );
184 
185     if (item->m_iHasLock > LOCK_STATE_NO_LOCK)
186       write += StringUtils::Format("    <lockpassword>%s<lockpassword>", item->m_strLockCode.c_str() );
187 
188     write += StringUtils::Format("  </stream>\n\n" );
189   }
190 
191   write += StringUtils::Format("</streams>\n");
192   file.Write(write.c_str(), write.size());
193   file.Close();
194 }
195