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