2 * Copyright (C) 2013-2020 Team Kodi
3 * This file is part of Kodi - https://kodi.tv
5 * SPDX-License-Identifier: GPL-2.0-or-later
6 * See LICENSES/README.md for more information.
9 #include "MediaSourceSettings.h"
11 #include "ServiceBroker.h"
14 #include "media/MediaLockState.h"
15 #include "network/WakeOnAccess.h"
16 #include "profiles/ProfileManager.h"
17 #include "settings/SettingsComponent.h"
18 #include "utils/FileUtils.h"
19 #include "utils/StringUtils.h"
20 #include "utils/URIUtils.h"
21 #include "utils/XBMCTinyXML2.h"
22 #include "utils/XMLUtils.h"
23 #include "utils/log.h"
28 #define SOURCES_FILE "sources.xml"
29 #define XML_SOURCES "sources"
30 #define XML_SOURCE "source"
32 CMediaSourceSettings::CMediaSourceSettings()
37 CMediaSourceSettings::~CMediaSourceSettings() = default;
39 CMediaSourceSettings
& CMediaSourceSettings::GetInstance()
41 static CMediaSourceSettings sMediaSourceSettings
;
42 return sMediaSourceSettings
;
45 std::string
CMediaSourceSettings::GetSourcesFile()
47 const std::shared_ptr
<CProfileManager
> profileManager
=
48 CServiceBroker::GetSettingsComponent()->GetProfileManager();
51 if (profileManager
->GetCurrentProfile().hasSources())
52 file
= profileManager
->GetProfileUserDataFolder();
54 file
= profileManager
->GetUserDataFolder();
56 return URIUtils::AddFileToFolder(file
, SOURCES_FILE
);
59 void CMediaSourceSettings::OnSettingsLoaded()
64 void CMediaSourceSettings::OnSettingsUnloaded()
69 bool CMediaSourceSettings::Load()
71 return Load(GetSourcesFile());
74 bool CMediaSourceSettings::Load(const std::string
& file
)
78 if (!CFileUtils::Exists(file
))
81 CLog::Log(LOGINFO
, "CMediaSourceSettings: loading media sources from {}", file
);
85 if (!xmlDoc
.LoadFile(file
))
87 CLog::Log(LOGERROR
, "CMediaSourceSettings: error loading {}: Line {}, {}", file
,
88 xmlDoc
.ErrorLineNum(), xmlDoc
.ErrorStr());
92 auto* rootElement
= xmlDoc
.RootElement();
93 if (!rootElement
|| !StringUtils::EqualsNoCase(rootElement
->Value(), XML_SOURCES
))
94 CLog::Log(LOGERROR
, "CMediaSourceSettings: sources.xml file does not contain <sources>");
98 GetSources(rootElement
, "video", m_videoSources
, dummy
);
99 GetSources(rootElement
, "programs", m_programSources
, m_defaultProgramSource
);
100 GetSources(rootElement
, "pictures", m_pictureSources
, m_defaultPictureSource
);
101 GetSources(rootElement
, "files", m_fileSources
, m_defaultFileSource
);
102 GetSources(rootElement
, "music", m_musicSources
, m_defaultMusicSource
);
103 GetSources(rootElement
, "games", m_gameSources
, dummy
);
108 bool CMediaSourceSettings::Save()
110 return Save(GetSourcesFile());
113 bool CMediaSourceSettings::Save(const std::string
& file
) const
116 auto* element
= doc
.NewElement(XML_SOURCES
);
117 auto* rootNode
= doc
.InsertFirstChild(element
);
122 // ok, now run through and save each sources section
123 SetSources(rootNode
, "programs", m_programSources
, m_defaultProgramSource
);
124 SetSources(rootNode
, "video", m_videoSources
, "");
125 SetSources(rootNode
, "music", m_musicSources
, m_defaultMusicSource
);
126 SetSources(rootNode
, "pictures", m_pictureSources
, m_defaultPictureSource
);
127 SetSources(rootNode
, "files", m_fileSources
, m_defaultFileSource
);
128 SetSources(rootNode
, "games", m_gameSources
, "");
130 CWakeOnAccess::GetInstance().QueueMACDiscoveryForAllRemotes();
132 return doc
.SaveFile(file
);
135 void CMediaSourceSettings::Clear()
137 m_programSources
.clear();
138 m_pictureSources
.clear();
139 m_fileSources
.clear();
140 m_musicSources
.clear();
141 m_videoSources
.clear();
142 m_gameSources
.clear();
145 VECSOURCES
* CMediaSourceSettings::GetSources(const std::string
& type
)
147 if (type
== "programs" || type
== "myprograms")
148 return &m_programSources
;
149 else if (type
== "files")
150 return &m_fileSources
;
151 else if (type
== "music")
152 return &m_musicSources
;
153 else if (type
== "video" || type
== "videos")
154 return &m_videoSources
;
155 else if (type
== "pictures")
156 return &m_pictureSources
;
157 else if (type
== "games")
158 return &m_gameSources
;
163 const std::string
& CMediaSourceSettings::GetDefaultSource(const std::string
& type
) const
165 if (type
== "programs" || type
== "myprograms")
166 return m_defaultProgramSource
;
167 else if (type
== "files")
168 return m_defaultFileSource
;
169 else if (type
== "music")
170 return m_defaultMusicSource
;
171 else if (type
== "pictures")
172 return m_defaultPictureSource
;
174 return StringUtils::Empty
;
177 void CMediaSourceSettings::SetDefaultSource(const std::string
& type
, const std::string
& source
)
179 if (type
== "programs" || type
== "myprograms")
180 m_defaultProgramSource
= source
;
181 else if (type
== "files")
182 m_defaultFileSource
= source
;
183 else if (type
== "music")
184 m_defaultMusicSource
= source
;
185 else if (type
== "pictures")
186 m_defaultPictureSource
= source
;
189 // NOTE: This function does NOT save the sources.xml file - you need to call SaveSources() separately.
190 bool CMediaSourceSettings::UpdateSource(const std::string
& strType
,
191 const std::string
& strOldName
,
192 const std::string
& strUpdateChild
,
193 const std::string
& strUpdateValue
)
195 VECSOURCES
* pShares
= GetSources(strType
);
199 for (IVECSOURCES it
= pShares
->begin(); it
!= pShares
->end(); ++it
)
201 if (it
->strName
== strOldName
)
203 if (strUpdateChild
== "name")
204 it
->strName
= strUpdateValue
;
205 else if (strUpdateChild
== "lockmode")
206 it
->m_iLockMode
= (LockType
)std::strtol(strUpdateValue
.c_str(), NULL
, 10);
207 else if (strUpdateChild
== "lockcode")
208 it
->m_strLockCode
= strUpdateValue
;
209 else if (strUpdateChild
== "badpwdcount")
210 it
->m_iBadPwdCount
= (int)std::strtol(strUpdateValue
.c_str(), NULL
, 10);
211 else if (strUpdateChild
== "thumbnail")
212 it
->m_strThumbnailImage
= strUpdateValue
;
213 else if (strUpdateChild
== "path")
215 it
->vecPaths
.clear();
216 it
->strPath
= strUpdateValue
;
217 it
->vecPaths
.push_back(strUpdateValue
);
229 bool CMediaSourceSettings::DeleteSource(const std::string
& strType
,
230 const std::string
& strName
,
231 const std::string
& strPath
,
232 bool virtualSource
/* = false */)
234 VECSOURCES
* pShares
= GetSources(strType
);
240 for (IVECSOURCES it
= pShares
->begin(); it
!= pShares
->end(); ++it
)
242 if (it
->strName
== strName
&& it
->strPath
== strPath
)
244 CLog::Log(LOGDEBUG
, "CMediaSourceSettings: found share, removing!");
257 bool CMediaSourceSettings::AddShare(const std::string
& type
, const CMediaSource
& share
)
259 VECSOURCES
* pShares
= GetSources(type
);
263 // translate dir and add to our current shares
264 std::string strPath1
= share
.strPath
;
265 if (strPath1
.empty())
267 CLog::Log(LOGERROR
, "CMediaSourceSettings: unable to add empty path");
270 StringUtils::ToUpper(strPath1
);
272 CMediaSource shareToAdd
= share
;
273 if (strPath1
.at(0) == '$')
275 shareToAdd
.strPath
= CUtil::TranslateSpecialSource(strPath1
);
276 if (!share
.strPath
.empty())
277 CLog::Log(LOGDEBUG
, "CMediaSourceSettings: translated ({}) to path ({})", strPath1
,
281 CLog::Log(LOGDEBUG
, "CMediaSourceSettings: skipping invalid special directory token ({})",
286 pShares
->push_back(shareToAdd
);
294 bool CMediaSourceSettings::UpdateShare(const std::string
& type
,
295 const std::string
& oldName
,
296 const CMediaSource
& share
)
298 VECSOURCES
* pShares
= GetSources(type
);
302 // update our current share list
303 CMediaSource
* pShare
= NULL
;
304 for (IVECSOURCES it
= pShares
->begin(); it
!= pShares
->end(); ++it
)
306 if (it
->strName
== oldName
)
308 it
->strName
= share
.strName
;
309 it
->strPath
= share
.strPath
;
310 it
->vecPaths
= share
.vecPaths
;
319 // Update our XML file as well
323 bool CMediaSourceSettings::GetSource(const std::string
& category
,
324 const tinyxml2::XMLNode
* source
,
327 const auto* nodeName
= source
->FirstChildElement("name");
329 if (nodeName
&& nodeName
->FirstChild())
330 name
= nodeName
->FirstChild()->Value();
335 // get multiple paths
336 std::vector
<std::string
> vecPaths
;
337 const auto* pathName
= source
->FirstChildElement("path");
340 if (pathName
->FirstChild())
342 std::string path
= pathName
->FirstChild()->Value();
344 // make sure there are no virtualpaths or stack paths defined in sources.xml
345 if (!URIUtils::IsStack(path
))
347 // translate special tags
348 if (!path
.empty() && path
.at(0) == '$')
349 path
= CUtil::TranslateSpecialSource(path
);
351 // need to check path validity again as CUtil::TranslateSpecialSource() may have failed
354 URIUtils::AddSlashAtEnd(path
);
355 vecPaths
.push_back(path
);
359 CLog::Log(LOGERROR
, "CMediaSourceSettings: invalid path type ({}) in source", path
);
362 pathName
= pathName
->NextSiblingElement("path");
365 if (vecPaths
.empty())
368 const auto* lockModeElement
= source
->FirstChildElement("lockmode");
369 const auto* lockCodeElement
= source
->FirstChildElement("lockcode");
370 const auto* badPwdCountElement
= source
->FirstChildElement("badpwdcount");
371 const auto* thumbnailNodeElement
= source
->FirstChildElement("thumbnail");
373 std::vector
<std::string
> verifiedPaths
;
374 // disallowed for files, or there's only a single path in the vector
375 if (StringUtils::EqualsNoCase(category
, "files") || vecPaths
.size() == 1)
377 verifiedPaths
.push_back(vecPaths
[0]);
379 else // multiple paths?
381 // validate the paths
382 for (auto path
= vecPaths
.begin(); path
!= vecPaths
.end(); ++path
)
385 bool isInvalid
= false;
388 if (StringUtils::EqualsNoCase(category
, "programs") ||
389 StringUtils::EqualsNoCase(category
, "myprograms"))
391 // only allow HD and plugins
392 if (url
.IsLocal() || url
.IsProtocol("plugin"))
393 verifiedPaths
.push_back(*path
);
397 // for others allow everything (if the user does something silly, we can't stop them)
399 verifiedPaths
.push_back(*path
);
403 CLog::Log(LOGERROR
, "CMediaSourceSettings: invalid path type ({}) for multipath source",
407 // no valid paths? skip to next source
408 if (verifiedPaths
.empty())
411 "CMediaSourceSettings: missing or invalid <name> and/or <path> in source");
416 share
.FromNameAndPaths(category
, name
, verifiedPaths
);
418 share
.m_iBadPwdCount
= 0;
422 static_cast<LockType
>(std::strtol(lockModeElement
->FirstChild()->Value(), nullptr, 10));
423 share
.m_iHasLock
= LOCK_STATE_LOCKED
;
426 if (lockCodeElement
&& lockCodeElement
->FirstChild())
427 share
.m_strLockCode
= lockCodeElement
->FirstChild()->Value();
429 if (badPwdCountElement
&& badPwdCountElement
->FirstChild())
431 share
.m_iBadPwdCount
=
432 static_cast<int>(std::strtol(badPwdCountElement
->FirstChild()->Value(), nullptr, 10));
435 if (thumbnailNodeElement
&& thumbnailNodeElement
->FirstChild())
436 share
.m_strThumbnailImage
= thumbnailNodeElement
->FirstChild()->Value();
438 XMLUtils::GetBoolean(source
, "allowsharing", share
.m_allowSharing
);
443 void CMediaSourceSettings::GetSources(const tinyxml2::XMLNode
* rootElement
,
444 const std::string
& tagName
,
446 std::string
& defaultString
)
452 const auto* childElement
= rootElement
->FirstChildElement(tagName
.c_str());
455 CLog::Log(LOGDEBUG
, "CMediaSourceSettings: <{}> tag is missing or sources.xml is malformed",
460 auto child
= childElement
->FirstChild();
463 std::string value
= child
->Value();
464 if (value
== XML_SOURCE
||
465 value
== "bookmark") // "bookmark" left in for backwards compatibility
468 if (GetSource(tagName
, child
, share
))
469 items
.push_back(share
);
472 "CMediaSourceSettings: Missing or invalid <name> and/or <path> in source");
474 else if (value
== "default")
476 const auto* valueNode
= child
->FirstChild();
479 const char* text
= child
->FirstChild()->Value();
480 if (strcmp(text
, "\0") != 0)
481 defaultString
= text
;
482 CLog::Log(LOGDEBUG
, "CMediaSourceSettings: Setting <default> source to : {}",
487 child
= child
->NextSibling();
491 bool CMediaSourceSettings::SetSources(tinyxml2::XMLNode
* root
,
493 const VECSOURCES
& shares
,
494 const std::string
& defaultPath
) const
496 auto* doc
= root
->GetDocument();
497 auto* newElement
= doc
->NewElement(section
);
498 auto* sectionNode
= root
->InsertEndChild(newElement
);
503 XMLUtils::SetPath(sectionNode
, "default", defaultPath
);
504 for (CIVECSOURCES it
= shares
.begin(); it
!= shares
.end(); ++it
)
506 const CMediaSource
& share
= *it
;
510 auto* sourceElement
= doc
->NewElement(XML_SOURCE
);
512 XMLUtils::SetString(sourceElement
, "name", share
.strName
);
514 for (unsigned int i
= 0; i
< share
.vecPaths
.size(); i
++)
515 XMLUtils::SetPath(sourceElement
, "path", share
.vecPaths
[i
]);
517 if (share
.m_iHasLock
)
519 XMLUtils::SetInt(sourceElement
, "lockmode", share
.m_iLockMode
);
520 XMLUtils::SetString(sourceElement
, "lockcode", share
.m_strLockCode
);
521 XMLUtils::SetInt(sourceElement
, "badpwdcount", share
.m_iBadPwdCount
);
524 if (!share
.m_strThumbnailImage
.empty())
525 XMLUtils::SetPath(sourceElement
, "thumbnail", share
.m_strThumbnailImage
);
527 XMLUtils::SetBoolean(sourceElement
, "allowsharing", share
.m_allowSharing
);
529 sectionNode
->InsertEndChild(sourceElement
);