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 "music/MusicFileItemClassify.h"
18 #include "profiles/ProfileManager.h"
19 #include "settings/SettingsComponent.h"
20 #include "utils/ContentUtils.h"
21 #include "utils/FileUtils.h"
22 #include "utils/URIUtils.h"
23 #include "utils/XBMCTinyXML2.h"
24 #include "utils/log.h"
25 #include "video/VideoFileItemClassify.h"
33 bool IsMediasourceOfFavItemUnlocked(const std::shared_ptr
<CFileItem
>& item
)
37 CLog::Log(LOGERROR
, "{}: No item passed (nullptr).", __func__
);
41 if (!item
->IsFavourite())
43 CLog::Log(LOGERROR
, "{}: Wrong item passed (not a favourite).", __func__
);
47 const auto settingsComponent
= CServiceBroker::GetSettingsComponent();
48 if (!settingsComponent
)
50 CLog::Log(LOGERROR
, "{}: returned nullptr.", __func__
);
54 const auto profileManager
= settingsComponent
->GetProfileManager();
57 CLog::Log(LOGERROR
, "{}: returned nullptr.", __func__
);
61 const CFavouritesURL
url(item
->GetPath());
64 CLog::Log(LOGERROR
, "{}: Invalid exec string (syntax error).", __func__
);
68 const CFavouritesURL::Action action
= url
.GetAction();
70 if (action
!= CFavouritesURL::Action::PLAY_MEDIA
&&
71 action
!= CFavouritesURL::Action::SHOW_PICTURE
)
74 const CFileItem
itemToCheck(url
.GetTarget(), url
.IsDir());
76 if (action
== CFavouritesURL::Action::PLAY_MEDIA
)
78 if (VIDEO::IsVideo(itemToCheck
))
80 if (!profileManager
->GetCurrentProfile().videoLocked())
81 return g_passwordManager
.IsMediaFileUnlocked("video", itemToCheck
.GetPath());
85 else if (MUSIC::IsAudio(itemToCheck
))
87 if (!profileManager
->GetCurrentProfile().musicLocked())
88 return g_passwordManager
.IsMediaFileUnlocked("music", itemToCheck
.GetPath());
93 else if (action
== CFavouritesURL::Action::SHOW_PICTURE
&& itemToCheck
.IsPicture())
95 if (!profileManager
->GetCurrentProfile().picturesLocked())
96 return g_passwordManager
.IsMediaFileUnlocked("pictures", itemToCheck
.GetPath());
104 bool LoadFromFile(const std::string
& strPath
, CFileItemList
& items
)
107 if (!doc
.LoadFile(strPath
))
109 CLog::Log(LOGERROR
, "Unable to load {} (line {})", strPath
, doc
.ErrorLineNum());
112 auto* root
= doc
.RootElement();
113 if (!root
|| strcmp(root
->Value(), "favourites"))
115 CLog::Log(LOGERROR
, "Favourites.xml doesn't contain the <favourites> root element");
119 auto* favourite
= root
->FirstChildElement("favourite");
123 // <favourite name="Cool Video" thumb="foo.jpg">PlayMedia(c:\videos\cool_video.avi)</favourite>
124 // <favourite name="My Album" thumb="bar.tbn">ActivateWindow(MyMusic,c:\music\my album)</favourite>
125 // <favourite name="Apple Movie Trailers" thumb="path_to_thumb.png">RunScript(special://xbmc/scripts/apple movie trailers/default.py)</favourite>
126 const char *name
= favourite
->Attribute("name");
127 const char *thumb
= favourite
->Attribute("thumb");
128 if (name
&& favourite
->FirstChild())
130 const std::string
favURL(
131 CFavouritesURL(CExecString(favourite
->FirstChild()->Value())).GetURL());
132 if (!items
.Contains(favURL
))
134 const CFileItemPtr
item(std::make_shared
<CFileItem
>(name
));
135 item
->SetPath(favURL
);
137 item
->SetArt("thumb", thumb
);
141 favourite
= favourite
->NextSiblingElement("favourite");
145 } // unnamed namespace
147 CFavouritesService::CFavouritesService(std::string userDataFolder
) : m_favourites("favourites://")
149 ReInit(std::move(userDataFolder
));
152 void CFavouritesService::ReInit(std::string userDataFolder
)
154 std::unique_lock
<CCriticalSection
> lock(m_criticalSection
);
156 m_userDataFolder
= std::move(userDataFolder
);
157 m_favourites
.Clear();
159 m_favourites
.SetContent("favourites");
161 std::string favourites
= "special://xbmc/system/favourites.xml";
162 if (CFileUtils::Exists(favourites
))
163 LoadFromFile(favourites
, m_favourites
);
165 CLog::Log(LOGDEBUG
, "CFavourites::Load - no system favourites found, skipping");
167 favourites
= URIUtils::AddFileToFolder(m_userDataFolder
, "favourites.xml");
168 if (CFileUtils::Exists(favourites
))
169 LoadFromFile(favourites
, m_favourites
);
171 CLog::Log(LOGDEBUG
, "CFavourites::Load - no userdata favourites found, skipping");
174 void CFavouritesService::CleanupTargetsCache(const CFileItem
& item
)
176 // Cleanup cache. Resume info etc. of cached target items might need refresh.
177 std::unique_lock
<CCriticalSection
> lock(m_criticalSection
);
179 const std::string dynPath
{item
.GetDynPath()};
180 std::erase_if(m_targets
,
181 [&dynPath
](const auto& entry
)
183 auto const& [key
, value
] = entry
;
184 return (value
->GetDynPath() == dynPath
);
188 void CFavouritesService::OnPlaybackStopped(const CFileItem
& item
)
190 CleanupTargetsCache(item
);
193 void CFavouritesService::OnPlaybackEnded(const CFileItem
& item
)
195 CleanupTargetsCache(item
);
198 bool CFavouritesService::Persist()
201 auto* element
= doc
.NewElement("favourites");
202 auto* rootNode
= doc
.InsertEndChild(element
);
206 for (const auto& item
: m_favourites
)
208 auto* favNode
= doc
.NewElement("favourite");
209 favNode
->SetAttribute("name", item
->GetLabel().c_str());
210 if (item
->HasArt("thumb"))
211 favNode
->SetAttribute("thumb", item
->GetArt("thumb").c_str());
213 auto* execute
= doc
.NewText(CFavouritesURL(item
->GetPath()).GetExecString().c_str());
214 favNode
->InsertEndChild(execute
);
215 rootNode
->InsertEndChild(favNode
);
218 auto path
= URIUtils::AddFileToFolder(m_userDataFolder
, "favourites.xml");
219 return doc
.SaveFile(path
);
222 bool CFavouritesService::Save(const CFileItemList
& items
)
225 std::unique_lock
<CCriticalSection
> lock(m_criticalSection
);
226 m_favourites
.Clear();
228 m_favourites
.Copy(items
);
235 void CFavouritesService::OnUpdated()
237 m_events
.Publish(FavouritesUpdated
{});
240 bool CFavouritesService::AddOrRemove(const CFileItem
& item
, int contextWindow
)
243 std::unique_lock
<CCriticalSection
> lock(m_criticalSection
);
245 const std::shared_ptr
<CFileItem
> match
{GetFavourite(item
, contextWindow
)};
249 const auto it
= m_targets
.find(match
->GetPath());
250 if (it
!= m_targets
.end())
253 m_favourites
.Remove(match
.get());
257 // create our new favourite item
258 const auto favourite
{std::make_shared
<CFileItem
>(item
.GetLabel())};
259 if (item
.GetLabel().empty())
260 favourite
->SetLabel(CUtil::GetTitleFromPath(item
.GetPath(), item
.m_bIsFolder
));
261 favourite
->SetArt("thumb", ContentUtils::GetPreferredArtImage(item
));
262 const std::string favUrl
{CFavouritesURL(item
, contextWindow
).GetURL()};
263 favourite
->SetPath(favUrl
);
264 m_favourites
.Add(favourite
);
272 std::shared_ptr
<CFileItem
> CFavouritesService::GetFavourite(const CFileItem
& item
,
273 int contextWindow
) const
275 std::unique_lock
<CCriticalSection
> lock(m_criticalSection
);
277 const CFavouritesURL favURL
{item
, contextWindow
};
278 const bool isVideoDb
{URIUtils::IsVideoDb(favURL
.GetTarget())};
279 const bool isMusicDb
{URIUtils::IsMusicDb(favURL
.GetTarget())};
281 for (const auto& favItem
: m_favourites
)
283 const CFavouritesURL favItemURL
{*favItem
, contextWindow
};
285 // Compare the whole target URLs
286 if (favItemURL
.GetTarget() == item
.GetPath())
289 // Compare the target URLs ignoring optional parameters
290 if (favItemURL
.GetAction() == favURL
.GetAction() &&
291 (favItemURL
.GetAction() != CFavouritesURL::Action::ACTIVATE_WINDOW
||
292 favItemURL
.GetWindowID() == favURL
.GetWindowID()))
294 if (favItemURL
.GetTarget() == favURL
.GetTarget())
297 // Check videodb and musicdb paths. Might be different strings pointing to same resource!
298 // Example: "musicdb://recentlyaddedalbums/4711/" and "musicdb://recentlyplayedalbums/4711/",
299 // both pointing to same album with db id 4711.
300 if ((isVideoDb
&& URIUtils::IsVideoDb(favItemURL
.GetTarget())) ||
301 (isMusicDb
&& URIUtils::IsMusicDb(favItemURL
.GetTarget())))
303 const std::shared_ptr
<CFileItem
> targetItem
{ResolveFavourite(*favItem
)};
304 if (targetItem
&& targetItem
->IsSamePath(&item
))
312 bool CFavouritesService::IsFavourited(const CFileItem
& item
, int contextWindow
) const
314 return (GetFavourite(item
, contextWindow
) != nullptr);
317 std::shared_ptr
<CFileItem
> CFavouritesService::ResolveFavourite(const CFileItem
& item
) const
319 if (item
.IsFavourite())
321 std::unique_lock
<CCriticalSection
> lock(m_criticalSection
);
323 const auto it
= m_targets
.find(item
.GetPath());
324 if (it
!= m_targets
.end())
327 const CFavouritesURL favURL
{item
.GetPath()};
328 if (favURL
.IsValid())
330 auto targetItem
{std::make_shared
<CFileItem
>(favURL
.GetTarget(), favURL
.IsDir())};
331 targetItem
->LoadDetails();
332 if (favURL
.GetWindowID() != -1)
334 const std::string window
{CWindowTranslator::TranslateWindow(favURL
.GetWindowID())};
335 targetItem
->SetProperty("targetwindow", CVariant
{window
});
337 m_targets
.insert({item
.GetPath(), targetItem
});
344 int CFavouritesService::Size() const
346 std::unique_lock
<CCriticalSection
> lock(m_criticalSection
);
347 return m_favourites
.Size();
350 void CFavouritesService::GetAll(CFileItemList
& items
) const
352 std::unique_lock
<CCriticalSection
> lock(m_criticalSection
);
354 if (g_passwordManager
.IsMasterLockUnlocked(false)) // don't prompt
356 items
.Copy(m_favourites
, true); // copy items
360 for (const auto& fav
: m_favourites
)
362 if (IsMediasourceOfFavItemUnlocked(fav
))
368 for (const auto& item
: items
)
370 const CFavouritesURL
favURL(item
->GetPath());
371 item
->SetProperty("favourite.action", favURL
.GetActionLabel());
372 item
->SetProperty("favourite.provider", favURL
.GetProviderLabel());
373 item
->SetProperty("favourite.index", index
++);
377 void CFavouritesService::RefreshFavourites()
379 m_events
.Publish(FavouritesUpdated
{});