2 * Copyright (C) 2013 Team XBMC
5 * This Program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 2, or (at your option)
10 * This Program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
15 * You should have received a copy of the GNU General Public License
16 * along with XBMC; see the file COPYING. If not, see
17 * <http://www.gnu.org/licenses/>.
22 #include "ProfilesManager.h"
23 #include "Application.h"
24 #include "DatabaseManager.h"
26 #include "GUIInfoManager.h"
28 #include "PasswordManager.h"
30 #include "dialogs/GUIDialogYesNo.h"
31 #include "filesystem/Directory.h"
32 #include "filesystem/DirectoryCache.h"
33 #include "filesystem/File.h"
34 #include "filesystem/SpecialProtocol.h"
35 #include "guilib/GUIWindowManager.h"
36 #include "guilib/LocalizeStrings.h"
37 #include "input/ButtonTranslator.h"
38 #include "input/MouseStat.h"
39 #include "settings/Settings.h"
40 #if !defined(TARGET_WINDOWS) && defined(HAS_DVD_DRIVE)
41 #include "storage/DetectDVDType.h"
43 #include "threads/SingleLock.h"
44 #include "utils/CharsetConverter.h"
45 #include "utils/FileUtils.h"
46 #include "utils/log.h"
47 #include "utils/StringUtils.h"
48 #include "utils/URIUtils.h"
49 #include "utils/XMLUtils.h"
52 // eventually the profile should dictate where special://masterprofile/ is
53 // but for now it makes sense to leave all the profile settings in a user
54 // writeable location like special://masterprofile/
55 #define PROFILES_FILE "special://masterprofile/profiles.xml"
57 #define XML_PROFILES "profiles"
58 #define XML_AUTO_LOGIN "autologin"
59 #define XML_LAST_LOADED "lastloaded"
60 #define XML_LOGIN_SCREEN "useloginscreen"
61 #define XML_NEXTID "nextIdProfile"
62 #define XML_PROFILE "profile"
65 using namespace XFILE;
67 static CProfile EmptyProfile;
69 CProfilesManager::CProfilesManager()
70 : m_usingLoginScreen(false), m_autoLoginProfile(-1), m_lastUsedProfile(0),
71 m_currentProfile(0), m_nextProfileId(0)
74 CProfilesManager::~CProfilesManager()
77 CProfilesManager& CProfilesManager::Get()
79 static CProfilesManager sProfilesManager;
80 return sProfilesManager;
83 void CProfilesManager::OnSettingsLoaded()
86 string strDir = CSettings::Get().GetString("system.playlistspath");
87 if (strDir == "set default" || strDir.empty())
89 strDir = "special://profile/playlists/";
90 CSettings::Get().SetString("system.playlistspath", strDir.c_str());
93 CDirectory::Create(strDir);
94 CDirectory::Create(URIUtils::AddFileToFolder(strDir,"music"));
95 CDirectory::Create(URIUtils::AddFileToFolder(strDir,"video"));
96 CDirectory::Create(URIUtils::AddFileToFolder(strDir,"mixed"));
99 bool CProfilesManager::OnSettingsSaved()
105 void CProfilesManager::OnSettingsCleared()
110 bool CProfilesManager::Load()
112 return Load(PROFILES_FILE);
115 bool CProfilesManager::Load(const std::string &file)
117 CSingleLock lock(m_critical);
120 // clear out our profiles
123 if (CFile::Exists(file))
125 CXBMCTinyXML profilesDoc;
126 if (profilesDoc.LoadFile(file))
128 const TiXmlElement *rootElement = profilesDoc.RootElement();
129 if (rootElement && StringUtils::EqualsNoCase(rootElement->Value(), XML_PROFILES))
131 XMLUtils::GetUInt(rootElement, XML_LAST_LOADED, m_lastUsedProfile);
132 XMLUtils::GetBoolean(rootElement, XML_LOGIN_SCREEN, m_usingLoginScreen);
133 XMLUtils::GetInt(rootElement, XML_AUTO_LOGIN, m_autoLoginProfile);
134 XMLUtils::GetInt(rootElement, XML_NEXTID, m_nextProfileId);
136 CStdString defaultDir("special://home/userdata");
137 if (!CDirectory::Exists(defaultDir))
138 defaultDir = "special://xbmc/userdata";
140 const TiXmlElement* pProfile = rootElement->FirstChildElement(XML_PROFILE);
143 CProfile profile(defaultDir);
144 profile.Load(pProfile, GetNextProfileId());
147 pProfile = pProfile->NextSiblingElement(XML_PROFILE);
152 CLog::Log(LOGERROR, "CProfilesManager: error loading %s, no <profiles> node", file.c_str());
158 CLog::Log(LOGERROR, "CProfilesManager: error loading %s, Line %d\n%s", file.c_str(), profilesDoc.ErrorRow(), profilesDoc.ErrorDesc());
163 if (m_profiles.empty())
164 { // add the master user
165 CProfile profile("special://masterprofile/", "Master user", 0);
169 // check the validity of the previous profile index
170 if (m_lastUsedProfile >= m_profiles.size())
171 m_lastUsedProfile = 0;
173 SetCurrentProfileId(m_lastUsedProfile);
175 // check the validity of the auto login profile index
176 if (m_autoLoginProfile < -1 || m_autoLoginProfile >= (int)m_profiles.size())
177 m_autoLoginProfile = -1;
178 else if (m_autoLoginProfile >= 0)
179 SetCurrentProfileId(m_autoLoginProfile);
181 // the login screen runs as the master profile, so if we're using this, we need to ensure
182 // we switch to the master profile
183 if (m_usingLoginScreen)
184 SetCurrentProfileId(0);
189 bool CProfilesManager::Save()
191 return Save(PROFILES_FILE);
194 bool CProfilesManager::Save(const std::string &file) const
196 CSingleLock lock(m_critical);
199 TiXmlElement xmlRootElement(XML_PROFILES);
200 TiXmlNode *pRoot = xmlDoc.InsertEndChild(xmlRootElement);
204 XMLUtils::SetInt(pRoot, XML_LAST_LOADED, m_currentProfile);
205 XMLUtils::SetBoolean(pRoot, XML_LOGIN_SCREEN, m_usingLoginScreen);
206 XMLUtils::SetInt(pRoot, XML_AUTO_LOGIN, m_autoLoginProfile);
207 XMLUtils::SetInt(pRoot, XML_NEXTID, m_nextProfileId);
209 for (vector<CProfile>::const_iterator profile = m_profiles.begin(); profile != m_profiles.end(); profile++)
210 profile->Save(pRoot);
213 return xmlDoc.SaveFile(file);
216 void CProfilesManager::Clear()
218 CSingleLock lock(m_critical);
219 m_usingLoginScreen = false;
220 m_lastUsedProfile = 0;
222 SetCurrentProfileId(0);
226 bool CProfilesManager::LoadProfile(size_t index)
228 CSingleLock lock(m_critical);
229 // check if the index is valid or not
230 if (index >= m_profiles.size())
233 // check if the profile is already active
234 if (m_currentProfile == index)
237 // unload any old settings
238 CSettings::Get().Unload();
240 SetCurrentProfileId(index);
242 // load the new settings
243 if (!CSettings::Get().Load())
245 CLog::Log(LOGFATAL, "CProfilesManager: unable to load settings for profile \"%s\"", m_profiles.at(index).getName().c_str());
248 CSettings::Get().SetLoaded();
250 CreateProfileFolders();
252 // Load the langinfo to have user charset <-> utf-8 conversion
253 string strLanguage = CSettings::Get().GetString("locale.language");
254 strLanguage[0] = toupper(strLanguage[0]);
256 string strLangInfoPath = StringUtils::Format("special://xbmc/language/%s/langinfo.xml", strLanguage.c_str());
257 CLog::Log(LOGINFO, "CProfilesManager: load language info file: %s", strLangInfoPath.c_str());
258 g_langInfo.Load(strLangInfoPath);
260 CButtonTranslator::GetInstance().Load(true);
261 g_localizeStrings.Load("special://xbmc/language/", strLanguage);
263 CDatabaseManager::Get().Initialize();
265 g_Mouse.SetEnabled(CSettings::Get().GetBool("input.enablemouse"));
267 g_infoManager.ResetCache();
268 g_infoManager.ResetLibraryBools();
270 // always reload the skin - we need it for the new language strings
271 g_application.ReloadSkin();
273 if (m_currentProfile != 0)
276 if (doc.LoadFile(URIUtils::AddFileToFolder(GetUserDataFolder(), "guisettings.xml")))
278 CSettings::Get().LoadSetting(doc.RootElement(), "masterlock.maxretries");
279 CSettings::Get().LoadSetting(doc.RootElement(), "masterlock.startuplock");
283 CPasswordManager::GetInstance().Clear();
285 // to set labels - shares are reloaded
286 #if !defined(TARGET_WINDOWS) && defined(HAS_DVD_DRIVE)
287 MEDIA_DETECT::CDetectDVDMedia::UpdateState();
291 CGUIMessage msg(GUI_MSG_NOTIFY_ALL, 0, 0, GUI_MSG_WINDOW_RESET);
292 g_windowManager.SendMessage(msg);
294 CUtil::DeleteDirectoryCache();
295 g_directoryCache.Clear();
300 bool CProfilesManager::DeleteProfile(size_t index)
302 CSingleLock lock(m_critical);
303 const CProfile *profile = GetProfile(index);
307 CGUIDialogYesNo* dlgYesNo = (CGUIDialogYesNo*)g_windowManager.GetWindow(WINDOW_DIALOG_YES_NO);
308 if (dlgYesNo == NULL)
312 string str = g_localizeStrings.Get(13201);
313 message = StringUtils::Format(str.c_str(), profile->getName().c_str());
314 dlgYesNo->SetHeading(13200);
315 dlgYesNo->SetLine(0, message);
316 dlgYesNo->SetLine(1, "");
317 dlgYesNo->SetLine(2, "");
320 if (!dlgYesNo->IsConfirmed())
323 // fall back to master profile if necessary
324 if ((int)index == m_autoLoginProfile)
325 m_autoLoginProfile = 0;
328 string strDirectory = profile->getDirectory();
329 m_profiles.erase(m_profiles.begin() + index);
331 // fall back to master profile if necessary
332 if (index == m_currentProfile)
335 CSettings::Get().Save();
338 CFileItemPtr item = CFileItemPtr(new CFileItem(URIUtils::AddFileToFolder(GetUserDataFolder(), strDirectory)));
339 item->SetPath(URIUtils::AddFileToFolder(GetUserDataFolder(), strDirectory + "/"));
340 item->m_bIsFolder = true;
342 CFileUtils::DeleteItem(item);
347 void CProfilesManager::CreateProfileFolders()
349 CDirectory::Create(GetDatabaseFolder());
350 CDirectory::Create(GetCDDBFolder());
351 CDirectory::Create(GetLibraryFolder());
353 // create Thumbnails/*
354 CDirectory::Create(GetThumbnailsFolder());
355 CDirectory::Create(GetVideoThumbFolder());
356 CDirectory::Create(GetBookmarksThumbFolder());
357 for (size_t hex = 0; hex < 16; hex++)
358 CDirectory::Create(URIUtils::AddFileToFolder(GetThumbnailsFolder(), StringUtils::Format("%x", hex)));
360 CDirectory::Create("special://profile/addon_data");
361 CDirectory::Create("special://profile/keymaps");
364 const CProfile& CProfilesManager::GetMasterProfile() const
366 CSingleLock lock(m_critical);
367 if (!m_profiles.empty())
368 return m_profiles[0];
370 CLog::Log(LOGERROR, "%s: master profile doesn't exist", __FUNCTION__);
374 const CProfile& CProfilesManager::GetCurrentProfile() const
376 CSingleLock lock(m_critical);
377 if (m_currentProfile < m_profiles.size())
378 return m_profiles[m_currentProfile];
380 CLog::Log(LOGERROR, "CProfilesManager: current profile index (%u) is outside of the valid range (%" PRIdS ")", m_currentProfile, m_profiles.size());
384 const CProfile* CProfilesManager::GetProfile(size_t index) const
386 CSingleLock lock(m_critical);
387 if (index < m_profiles.size())
388 return &m_profiles[index];
393 CProfile* CProfilesManager::GetProfile(size_t index)
395 CSingleLock lock(m_critical);
396 if (index < m_profiles.size())
397 return &m_profiles[index];
402 int CProfilesManager::GetProfileIndex(const std::string &name) const
404 CSingleLock lock(m_critical);
405 for (size_t i = 0; i < m_profiles.size(); i++)
407 if (StringUtils::EqualsNoCase(m_profiles[i].getName(), name))
414 void CProfilesManager::AddProfile(const CProfile &profile)
416 CSingleLock lock(m_critical);
417 // data integrity check - covers off migration from old profiles.xml,
418 // incrementing of the m_nextIdProfile,and bad data coming in
419 m_nextProfileId = max(m_nextProfileId, profile.getId() + 1);
421 m_profiles.push_back(profile);
424 void CProfilesManager::UpdateCurrentProfileDate()
426 CSingleLock lock(m_critical);
427 if (m_currentProfile < m_profiles.size())
428 m_profiles[m_currentProfile].setDate();
431 void CProfilesManager::LoadMasterProfileForLogin()
433 CSingleLock lock(m_critical);
434 // save the previous user
435 m_lastUsedProfile = m_currentProfile;
436 if (m_currentProfile != 0)
440 bool CProfilesManager::GetProfileName(const size_t profileId, std::string& name) const
442 CSingleLock lock(m_critical);
443 const CProfile *profile = GetProfile(profileId);
447 name = profile->getName();
451 std::string CProfilesManager::GetUserDataFolder() const
453 return GetMasterProfile().getDirectory();
456 std::string CProfilesManager::GetProfileUserDataFolder() const
458 if (m_currentProfile == 0)
459 return GetUserDataFolder();
461 return URIUtils::AddFileToFolder(GetUserDataFolder(), GetCurrentProfile().getDirectory());
464 std::string CProfilesManager::GetDatabaseFolder() const
466 if (GetCurrentProfile().hasDatabases())
467 return URIUtils::AddFileToFolder(GetProfileUserDataFolder(), "Database");
469 return URIUtils::AddFileToFolder(GetUserDataFolder(), "Database");
472 std::string CProfilesManager::GetCDDBFolder() const
474 return URIUtils::AddFileToFolder(GetDatabaseFolder(), "CDDB");
477 std::string CProfilesManager::GetThumbnailsFolder() const
479 if (GetCurrentProfile().hasDatabases())
480 return URIUtils::AddFileToFolder(GetProfileUserDataFolder(), "Thumbnails");
482 return URIUtils::AddFileToFolder(GetUserDataFolder(), "Thumbnails");
485 std::string CProfilesManager::GetVideoThumbFolder() const
487 return URIUtils::AddFileToFolder(GetThumbnailsFolder(), "Video");
490 std::string CProfilesManager::GetBookmarksThumbFolder() const
492 return URIUtils::AddFileToFolder(GetVideoThumbFolder(), "Bookmarks");
495 std::string CProfilesManager::GetLibraryFolder() const
497 if (GetCurrentProfile().hasDatabases())
498 return URIUtils::AddFileToFolder(GetProfileUserDataFolder(), "library");
500 return URIUtils::AddFileToFolder(GetUserDataFolder(), "library");
503 std::string CProfilesManager::GetSettingsFile() const
506 if (m_currentProfile == 0)
507 return "special://masterprofile/guisettings.xml";
509 return "special://profile/guisettings.xml";
512 std::string CProfilesManager::GetUserDataItem(const std::string& strFile) const
515 path = "special://profile/" + strFile;
517 // check if item exists in the profile (either for folder or
518 // for a file (depending on slashAtEnd of strFile) otherwise
519 // return path to masterprofile
520 if ((URIUtils::HasSlashAtEnd(path) && !CDirectory::Exists(path)) || !CFile::Exists(path))
521 path = "special://masterprofile/" + strFile;
526 void CProfilesManager::SetCurrentProfileId(size_t profileId)
528 CSingleLock lock(m_critical);
529 m_currentProfile = profileId;
530 CSpecialProtocol::SetProfilePath(GetProfileUserDataFolder());