[Test] Added tests for CUtil::SplitParams
[xbmc.git] / xbmc / favourites / FavouritesService.cpp
blob97f55073c3d313cb67ecc1ccfa77d4f29efb52d9
1 /*
2 * Copyright (C) 2005-2018 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.
7 */
9 #include "FavouritesService.h"
11 #include "FileItem.h"
12 #include "GUIPassword.h"
13 #include "ServiceBroker.h"
14 #include "Util.h"
15 #include "favourites/FavouritesURL.h"
16 #include "input/WindowTranslator.h"
17 #include "profiles/ProfileManager.h"
18 #include "settings/SettingsComponent.h"
19 #include "utils/ContentUtils.h"
20 #include "utils/FileUtils.h"
21 #include "utils/URIUtils.h"
22 #include "utils/XBMCTinyXML2.h"
23 #include "utils/log.h"
25 #include <mutex>
27 namespace
29 bool IsMediasourceOfFavItemUnlocked(const std::shared_ptr<CFileItem>& item)
31 if (!item)
33 CLog::Log(LOGERROR, "{}: No item passed (nullptr).", __func__);
34 return true;
37 if (!item->IsFavourite())
39 CLog::Log(LOGERROR, "{}: Wrong item passed (not a favourite).", __func__);
40 return true;
43 const auto settingsComponent = CServiceBroker::GetSettingsComponent();
44 if (!settingsComponent)
46 CLog::Log(LOGERROR, "{}: returned nullptr.", __func__);
47 return true;
50 const auto profileManager = settingsComponent->GetProfileManager();
51 if (!profileManager)
53 CLog::Log(LOGERROR, "{}: returned nullptr.", __func__);
54 return true;
57 const CFavouritesURL url(item->GetPath());
58 if (!url.IsValid())
60 CLog::Log(LOGERROR, "{}: Invalid exec string (syntax error).", __func__);
61 return true;
64 const CFavouritesURL::Action action = url.GetAction();
66 if (action != CFavouritesURL::Action::PLAY_MEDIA &&
67 action != CFavouritesURL::Action::SHOW_PICTURE)
68 return true;
70 const CFileItem itemToCheck(url.GetTarget(), url.IsDir());
72 if (action == CFavouritesURL::Action::PLAY_MEDIA)
74 if (itemToCheck.IsVideo())
76 if (!profileManager->GetCurrentProfile().videoLocked())
77 return g_passwordManager.IsMediaFileUnlocked("video", itemToCheck.GetPath());
79 return false;
81 else if (itemToCheck.IsAudio())
83 if (!profileManager->GetCurrentProfile().musicLocked())
84 return g_passwordManager.IsMediaFileUnlocked("music", itemToCheck.GetPath());
86 return false;
89 else if (action == CFavouritesURL::Action::SHOW_PICTURE && itemToCheck.IsPicture())
91 if (!profileManager->GetCurrentProfile().picturesLocked())
92 return g_passwordManager.IsMediaFileUnlocked("pictures", itemToCheck.GetPath());
94 return false;
97 return true;
100 bool LoadFromFile(const std::string& strPath, CFileItemList& items)
102 CXBMCTinyXML2 doc;
103 if (!doc.LoadFile(strPath))
105 CLog::Log(LOGERROR, "Unable to load {} (line {})", strPath, doc.ErrorLineNum());
106 return false;
108 auto* root = doc.RootElement();
109 if (!root || strcmp(root->Value(), "favourites"))
111 CLog::Log(LOGERROR, "Favourites.xml doesn't contain the <favourites> root element");
112 return false;
115 auto* favourite = root->FirstChildElement("favourite");
116 while (favourite)
118 // format:
119 // <favourite name="Cool Video" thumb="foo.jpg">PlayMedia(c:\videos\cool_video.avi)</favourite>
120 // <favourite name="My Album" thumb="bar.tbn">ActivateWindow(MyMusic,c:\music\my album)</favourite>
121 // <favourite name="Apple Movie Trailers" thumb="path_to_thumb.png">RunScript(special://xbmc/scripts/apple movie trailers/default.py)</favourite>
122 const char *name = favourite->Attribute("name");
123 const char *thumb = favourite->Attribute("thumb");
124 if (name && favourite->FirstChild())
126 const std::string favURL(
127 CFavouritesURL(CExecString(favourite->FirstChild()->Value())).GetURL());
128 if (!items.Contains(favURL))
130 const CFileItemPtr item(std::make_shared<CFileItem>(name));
131 item->SetPath(favURL);
132 if (thumb)
133 item->SetArt("thumb", thumb);
134 items.Add(item);
137 favourite = favourite->NextSiblingElement("favourite");
139 return true;
141 } // unnamed namespace
143 CFavouritesService::CFavouritesService(std::string userDataFolder) : m_favourites("favourites://")
145 ReInit(std::move(userDataFolder));
148 void CFavouritesService::ReInit(std::string userDataFolder)
150 std::unique_lock<CCriticalSection> lock(m_criticalSection);
152 m_userDataFolder = std::move(userDataFolder);
153 m_favourites.Clear();
154 m_targets.clear();
155 m_favourites.SetContent("favourites");
157 std::string favourites = "special://xbmc/system/favourites.xml";
158 if (CFileUtils::Exists(favourites))
159 LoadFromFile(favourites, m_favourites);
160 else
161 CLog::Log(LOGDEBUG, "CFavourites::Load - no system favourites found, skipping");
163 favourites = URIUtils::AddFileToFolder(m_userDataFolder, "favourites.xml");
164 if (CFileUtils::Exists(favourites))
165 LoadFromFile(favourites, m_favourites);
166 else
167 CLog::Log(LOGDEBUG, "CFavourites::Load - no userdata favourites found, skipping");
170 bool CFavouritesService::Persist()
172 CXBMCTinyXML2 doc;
173 auto* element = doc.NewElement("favourites");
174 auto* rootNode = doc.InsertEndChild(element);
175 if (!rootNode)
176 return false;
178 for (const auto& item : m_favourites)
180 auto* favNode = doc.NewElement("favourite");
181 favNode->SetAttribute("name", item->GetLabel().c_str());
182 if (item->HasArt("thumb"))
183 favNode->SetAttribute("thumb", item->GetArt("thumb").c_str());
185 auto* execute = doc.NewText(CFavouritesURL(item->GetPath()).GetExecString().c_str());
186 favNode->InsertEndChild(execute);
187 rootNode->InsertEndChild(favNode);
190 auto path = URIUtils::AddFileToFolder(m_userDataFolder, "favourites.xml");
191 return doc.SaveFile(path);
194 bool CFavouritesService::Save(const CFileItemList& items)
197 std::unique_lock<CCriticalSection> lock(m_criticalSection);
198 m_favourites.Clear();
199 m_targets.clear();
200 m_favourites.Copy(items);
201 Persist();
203 OnUpdated();
204 return true;
207 void CFavouritesService::OnUpdated()
209 m_events.Publish(FavouritesUpdated{});
212 bool CFavouritesService::AddOrRemove(const CFileItem& item, int contextWindow)
215 std::unique_lock<CCriticalSection> lock(m_criticalSection);
217 const std::shared_ptr<CFileItem> match{GetFavourite(item, contextWindow)};
218 if (match)
220 // remove the item
221 const auto it = m_targets.find(match->GetPath());
222 if (it != m_targets.end())
223 m_targets.erase(it);
225 m_favourites.Remove(match.get());
227 else
229 // create our new favourite item
230 const auto favourite{std::make_shared<CFileItem>(item.GetLabel())};
231 if (item.GetLabel().empty())
232 favourite->SetLabel(CUtil::GetTitleFromPath(item.GetPath(), item.m_bIsFolder));
233 favourite->SetArt("thumb", ContentUtils::GetPreferredArtImage(item));
234 const std::string favUrl{CFavouritesURL(item, contextWindow).GetURL()};
235 favourite->SetPath(favUrl);
236 m_favourites.Add(favourite);
238 Persist();
240 OnUpdated();
241 return true;
244 std::shared_ptr<CFileItem> CFavouritesService::GetFavourite(const CFileItem& item,
245 int contextWindow) const
247 std::unique_lock<CCriticalSection> lock(m_criticalSection);
249 const CFavouritesURL favURL{item, contextWindow};
250 const bool isVideoDb{URIUtils::IsVideoDb(favURL.GetTarget())};
251 const bool isMusicDb{URIUtils::IsMusicDb(favURL.GetTarget())};
253 for (const auto& favItem : m_favourites)
255 const CFavouritesURL favItemURL{*favItem, contextWindow};
257 // Compare the whole target URLs
258 if (favItemURL.GetTarget() == item.GetPath())
259 return favItem;
261 // Compare the target URLs ignoring optional parameters
262 if (favItemURL.GetAction() == favURL.GetAction() &&
263 (favItemURL.GetAction() != CFavouritesURL::Action::ACTIVATE_WINDOW ||
264 favItemURL.GetWindowID() == favURL.GetWindowID()))
266 if (favItemURL.GetTarget() == favURL.GetTarget())
267 return favItem;
269 // Check videodb and musicdb paths. Might be different strings pointing to same resource!
270 // Example: "musicdb://recentlyaddedalbums/4711/" and "musicdb://recentlyplayedalbums/4711/",
271 // both pointing to same album with db id 4711.
272 if ((isVideoDb && URIUtils::IsVideoDb(favItemURL.GetTarget())) ||
273 (isMusicDb && URIUtils::IsMusicDb(favItemURL.GetTarget())))
275 const std::shared_ptr<CFileItem> targetItem{ResolveFavourite(*favItem)};
276 if (targetItem && targetItem->IsSamePath(&item))
277 return favItem;
281 return {};
284 bool CFavouritesService::IsFavourited(const CFileItem& item, int contextWindow) const
286 return (GetFavourite(item, contextWindow) != nullptr);
289 std::shared_ptr<CFileItem> CFavouritesService::ResolveFavourite(const CFileItem& item) const
291 if (item.IsFavourite())
293 std::unique_lock<CCriticalSection> lock(m_criticalSection);
295 const auto it = m_targets.find(item.GetPath());
296 if (it != m_targets.end())
297 return (*it).second;
299 const CFavouritesURL favURL{item.GetPath()};
300 if (favURL.IsValid())
302 auto targetItem{std::make_shared<CFileItem>(favURL.GetTarget(), favURL.IsDir())};
303 targetItem->LoadDetails();
304 if (favURL.GetWindowID() != -1)
306 const std::string window{CWindowTranslator::TranslateWindow(favURL.GetWindowID())};
307 targetItem->SetProperty("targetwindow", CVariant{window});
309 m_targets.insert({item.GetPath(), targetItem});
310 return targetItem;
313 return {};
316 int CFavouritesService::Size() const
318 std::unique_lock<CCriticalSection> lock(m_criticalSection);
319 return m_favourites.Size();
322 void CFavouritesService::GetAll(CFileItemList& items) const
324 std::unique_lock<CCriticalSection> lock(m_criticalSection);
325 items.Clear();
326 if (g_passwordManager.IsMasterLockUnlocked(false)) // don't prompt
328 items.Copy(m_favourites, true); // copy items
330 else
332 for (const auto& fav : m_favourites)
334 if (IsMediasourceOfFavItemUnlocked(fav))
335 items.Add(fav);
339 int index = 0;
340 for (const auto& item : items)
342 const CFavouritesURL favURL(item->GetPath());
343 item->SetProperty("favourite.action", favURL.GetActionLabel());
344 item->SetProperty("favourite.provider", favURL.GetProviderLabel());
345 item->SetProperty("favourite.index", index++);
349 void CFavouritesService::RefreshFavourites()
351 m_events.Publish(FavouritesUpdated{});