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.
9 #include "FavouritesService.h"
12 #include "GUIPassword.h"
13 #include "ServiceBroker.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"
29 bool IsMediasourceOfFavItemUnlocked(const std::shared_ptr
<CFileItem
>& item
)
33 CLog::Log(LOGERROR
, "{}: No item passed (nullptr).", __func__
);
37 if (!item
->IsFavourite())
39 CLog::Log(LOGERROR
, "{}: Wrong item passed (not a favourite).", __func__
);
43 const auto settingsComponent
= CServiceBroker::GetSettingsComponent();
44 if (!settingsComponent
)
46 CLog::Log(LOGERROR
, "{}: returned nullptr.", __func__
);
50 const auto profileManager
= settingsComponent
->GetProfileManager();
53 CLog::Log(LOGERROR
, "{}: returned nullptr.", __func__
);
57 const CFavouritesURL
url(item
->GetPath());
60 CLog::Log(LOGERROR
, "{}: Invalid exec string (syntax error).", __func__
);
64 const CFavouritesURL::Action action
= url
.GetAction();
66 if (action
!= CFavouritesURL::Action::PLAY_MEDIA
&&
67 action
!= CFavouritesURL::Action::SHOW_PICTURE
)
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());
81 else if (itemToCheck
.IsAudio())
83 if (!profileManager
->GetCurrentProfile().musicLocked())
84 return g_passwordManager
.IsMediaFileUnlocked("music", itemToCheck
.GetPath());
89 else if (action
== CFavouritesURL::Action::SHOW_PICTURE
&& itemToCheck
.IsPicture())
91 if (!profileManager
->GetCurrentProfile().picturesLocked())
92 return g_passwordManager
.IsMediaFileUnlocked("pictures", itemToCheck
.GetPath());
100 bool LoadFromFile(const std::string
& strPath
, CFileItemList
& items
)
103 if (!doc
.LoadFile(strPath
))
105 CLog::Log(LOGERROR
, "Unable to load {} (line {})", strPath
, doc
.ErrorLineNum());
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");
115 auto* favourite
= root
->FirstChildElement("favourite");
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
);
133 item
->SetArt("thumb", thumb
);
137 favourite
= favourite
->NextSiblingElement("favourite");
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();
155 m_favourites
.SetContent("favourites");
157 std::string favourites
= "special://xbmc/system/favourites.xml";
158 if (CFileUtils::Exists(favourites
))
159 LoadFromFile(favourites
, m_favourites
);
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
);
167 CLog::Log(LOGDEBUG
, "CFavourites::Load - no userdata favourites found, skipping");
170 bool CFavouritesService::Persist()
173 auto* element
= doc
.NewElement("favourites");
174 auto* rootNode
= doc
.InsertEndChild(element
);
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();
200 m_favourites
.Copy(items
);
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
)};
221 const auto it
= m_targets
.find(match
->GetPath());
222 if (it
!= m_targets
.end())
225 m_favourites
.Remove(match
.get());
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
);
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())
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())
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
))
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())
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
});
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
);
326 if (g_passwordManager
.IsMasterLockUnlocked(false)) // don't prompt
328 items
.Copy(m_favourites
, true); // copy items
332 for (const auto& fav
: m_favourites
)
334 if (IsMediasourceOfFavItemUnlocked(fav
))
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
{});