Merge pull request #3819 from arnova/subtitles_for_stacks
[vuplus_xbmc] / xbmc / profiles / ProfilesManager.cpp
1 /*
2  *      Copyright (C) 2013 Team XBMC
3  *      http://xbmc.org
4  *
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)
8  *  any later version.
9  *
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.
14  *
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/>.
18  *
19  */
20
21 #include "system.h"
22 #include "ProfilesManager.h"
23 #include "Application.h"
24 #include "DatabaseManager.h"
25 #include "FileItem.h"
26 #include "GUIInfoManager.h"
27 #include "LangInfo.h"
28 #include "PasswordManager.h"
29 #include "Util.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"
42 #endif
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"
50
51 // TODO
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"
56
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"
63
64 using namespace std;
65 using namespace XFILE;
66
67 static CProfile EmptyProfile;
68
69 CProfilesManager::CProfilesManager()
70   : m_usingLoginScreen(false), m_autoLoginProfile(-1), m_lastUsedProfile(0),
71     m_currentProfile(0), m_nextProfileId(0)
72 { }
73
74 CProfilesManager::~CProfilesManager()
75 { }
76
77 CProfilesManager& CProfilesManager::Get()
78 {
79   static CProfilesManager sProfilesManager;
80   return sProfilesManager;
81 }
82
83 void CProfilesManager::OnSettingsLoaded()
84 {
85   // check them all
86   string strDir = CSettings::Get().GetString("system.playlistspath");
87   if (strDir == "set default" || strDir.empty())
88   {
89     strDir = "special://profile/playlists/";
90     CSettings::Get().SetString("system.playlistspath", strDir.c_str());
91   }
92
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"));
97 }
98
99 bool CProfilesManager::OnSettingsSaved()
100 {
101   // save mastercode
102   return Save();
103 }
104
105 void CProfilesManager::OnSettingsCleared()
106 {
107   Clear();
108 }
109
110 bool CProfilesManager::Load()
111 {
112   return Load(PROFILES_FILE);
113 }
114
115 bool CProfilesManager::Load(const std::string &file)
116 {
117   CSingleLock lock(m_critical);
118   bool ret = true;
119
120   // clear out our profiles
121   m_profiles.clear();
122
123   if (CFile::Exists(file))
124   {
125     CXBMCTinyXML profilesDoc;
126     if (profilesDoc.LoadFile(file))
127     {
128       const TiXmlElement *rootElement = profilesDoc.RootElement();
129       if (rootElement && StringUtils::EqualsNoCase(rootElement->Value(), XML_PROFILES))
130       {
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);
135         
136         CStdString defaultDir("special://home/userdata");
137         if (!CDirectory::Exists(defaultDir))
138           defaultDir = "special://xbmc/userdata";
139         
140         const TiXmlElement* pProfile = rootElement->FirstChildElement(XML_PROFILE);
141         while (pProfile)
142         {
143           CProfile profile(defaultDir);
144           profile.Load(pProfile, GetNextProfileId());
145           AddProfile(profile);
146
147           pProfile = pProfile->NextSiblingElement(XML_PROFILE);
148         }
149       }
150       else
151       {
152         CLog::Log(LOGERROR, "CProfilesManager: error loading %s, no <profiles> node", file.c_str());
153         ret = false;
154       }
155     }
156     else
157     {
158       CLog::Log(LOGERROR, "CProfilesManager: error loading %s, Line %d\n%s", file.c_str(), profilesDoc.ErrorRow(), profilesDoc.ErrorDesc());
159       ret = false;
160     }
161   }
162
163   if (m_profiles.empty())
164   { // add the master user
165     CProfile profile("special://masterprofile/", "Master user", 0);
166     AddProfile(profile);
167   }
168
169   // check the validity of the previous profile index
170   if (m_lastUsedProfile >= m_profiles.size())
171     m_lastUsedProfile = 0;
172
173   SetCurrentProfileId(m_lastUsedProfile);
174
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);
180
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);
185
186   return ret;
187 }
188
189 bool CProfilesManager::Save()
190 {
191   return Save(PROFILES_FILE);
192 }
193
194 bool CProfilesManager::Save(const std::string &file) const
195 {
196   CSingleLock lock(m_critical);
197
198   CXBMCTinyXML xmlDoc;
199   TiXmlElement xmlRootElement(XML_PROFILES);
200   TiXmlNode *pRoot = xmlDoc.InsertEndChild(xmlRootElement);
201   if (pRoot == NULL)
202     return false;
203
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);      
208
209   for (vector<CProfile>::const_iterator profile = m_profiles.begin(); profile != m_profiles.end(); profile++)
210     profile->Save(pRoot);
211
212   // save the file
213   return xmlDoc.SaveFile(file);
214 }
215
216 void CProfilesManager::Clear()
217 {
218   CSingleLock lock(m_critical);
219   m_usingLoginScreen = false;
220   m_lastUsedProfile = 0;
221   m_nextProfileId = 0;
222   SetCurrentProfileId(0);
223   m_profiles.clear();
224 }
225
226 bool CProfilesManager::LoadProfile(size_t index)
227 {
228   CSingleLock lock(m_critical);
229   // check if the index is valid or not
230   if (index >= m_profiles.size())
231     return false;
232
233   // check if the profile is already active
234   if (m_currentProfile == index)
235     return true;
236
237   // unload any old settings
238   CSettings::Get().Unload();
239
240   SetCurrentProfileId(index);
241
242   // load the new settings
243   if (!CSettings::Get().Load())
244   {
245     CLog::Log(LOGFATAL, "CProfilesManager: unable to load settings for profile \"%s\"", m_profiles.at(index).getName().c_str());
246     return false;
247   }
248   CSettings::Get().SetLoaded();
249
250   CreateProfileFolders();
251
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]);
255
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);
259
260   CButtonTranslator::GetInstance().Load(true);
261   g_localizeStrings.Load("special://xbmc/language/", strLanguage);
262
263   CDatabaseManager::Get().Initialize();
264
265   g_Mouse.SetEnabled(CSettings::Get().GetBool("input.enablemouse"));
266
267   g_infoManager.ResetCache();
268   g_infoManager.ResetLibraryBools();
269
270   // always reload the skin - we need it for the new language strings
271   g_application.ReloadSkin();
272
273   if (m_currentProfile != 0)
274   {
275     CXBMCTinyXML doc;
276     if (doc.LoadFile(URIUtils::AddFileToFolder(GetUserDataFolder(), "guisettings.xml")))
277     {
278       CSettings::Get().LoadSetting(doc.RootElement(), "masterlock.maxretries");
279       CSettings::Get().LoadSetting(doc.RootElement(), "masterlock.startuplock");
280     }
281   }
282
283   CPasswordManager::GetInstance().Clear();
284
285   // to set labels - shares are reloaded
286 #if !defined(TARGET_WINDOWS) && defined(HAS_DVD_DRIVE)
287   MEDIA_DETECT::CDetectDVDMedia::UpdateState();
288 #endif
289
290   // init windows
291   CGUIMessage msg(GUI_MSG_NOTIFY_ALL, 0, 0, GUI_MSG_WINDOW_RESET);
292   g_windowManager.SendMessage(msg);
293
294   CUtil::DeleteDirectoryCache();
295   g_directoryCache.Clear();
296
297   return true;
298 }
299
300 bool CProfilesManager::DeleteProfile(size_t index)
301 {
302   CSingleLock lock(m_critical);
303   const CProfile *profile = GetProfile(index);
304   if (profile == NULL)
305     return false;
306
307   CGUIDialogYesNo* dlgYesNo = (CGUIDialogYesNo*)g_windowManager.GetWindow(WINDOW_DIALOG_YES_NO);
308   if (dlgYesNo == NULL)
309     return false;
310
311   string message;
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, "");
318   dlgYesNo->DoModal();
319
320   if (!dlgYesNo->IsConfirmed())
321     return false;
322
323   // fall back to master profile if necessary
324   if ((int)index == m_autoLoginProfile)
325     m_autoLoginProfile = 0;
326
327   // delete profile
328   string strDirectory = profile->getDirectory();
329   m_profiles.erase(m_profiles.begin() + index);
330
331   // fall back to master profile if necessary
332   if (index == m_currentProfile)
333   {
334     LoadProfile(0);
335     CSettings::Get().Save();
336   }
337
338   CFileItemPtr item = CFileItemPtr(new CFileItem(URIUtils::AddFileToFolder(GetUserDataFolder(), strDirectory)));
339   item->SetPath(URIUtils::AddFileToFolder(GetUserDataFolder(), strDirectory + "/"));
340   item->m_bIsFolder = true;
341   item->Select(true);
342   CFileUtils::DeleteItem(item);
343
344   return Save();
345 }
346
347 void CProfilesManager::CreateProfileFolders()
348 {
349   CDirectory::Create(GetDatabaseFolder());
350   CDirectory::Create(GetCDDBFolder());
351   CDirectory::Create(GetLibraryFolder());
352
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)));
359
360   CDirectory::Create("special://profile/addon_data");
361   CDirectory::Create("special://profile/keymaps");
362 }
363
364 const CProfile& CProfilesManager::GetMasterProfile() const
365 {
366   CSingleLock lock(m_critical);
367   if (!m_profiles.empty())
368     return m_profiles[0];
369
370   CLog::Log(LOGERROR, "%s: master profile doesn't exist", __FUNCTION__);
371   return EmptyProfile;
372 }
373
374 const CProfile& CProfilesManager::GetCurrentProfile() const
375 {
376   CSingleLock lock(m_critical);
377   if (m_currentProfile < m_profiles.size())
378     return m_profiles[m_currentProfile];
379
380   CLog::Log(LOGERROR, "CProfilesManager: current profile index (%u) is outside of the valid range (%" PRIdS ")", m_currentProfile, m_profiles.size());
381   return EmptyProfile;
382 }
383
384 const CProfile* CProfilesManager::GetProfile(size_t index) const
385 {
386   CSingleLock lock(m_critical);
387   if (index < m_profiles.size())
388     return &m_profiles[index];
389
390   return NULL;
391 }
392
393 CProfile* CProfilesManager::GetProfile(size_t index)
394 {
395   CSingleLock lock(m_critical);
396   if (index < m_profiles.size())
397     return &m_profiles[index];
398
399   return NULL;
400 }
401
402 int CProfilesManager::GetProfileIndex(const std::string &name) const
403 {
404   CSingleLock lock(m_critical);
405   for (size_t i = 0; i < m_profiles.size(); i++)
406   {
407     if (StringUtils::EqualsNoCase(m_profiles[i].getName(), name))
408       return i;
409   }
410
411   return -1;
412 }
413
414 void CProfilesManager::AddProfile(const CProfile &profile)
415 {
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);
420
421   m_profiles.push_back(profile);
422 }
423
424 void CProfilesManager::UpdateCurrentProfileDate()
425 {
426   CSingleLock lock(m_critical);
427   if (m_currentProfile < m_profiles.size())
428     m_profiles[m_currentProfile].setDate();
429 }
430
431 void CProfilesManager::LoadMasterProfileForLogin()
432 {
433   CSingleLock lock(m_critical);
434   // save the previous user
435   m_lastUsedProfile = m_currentProfile;
436   if (m_currentProfile != 0)
437     LoadProfile(0);
438 }
439
440 bool CProfilesManager::GetProfileName(const size_t profileId, std::string& name) const
441 {
442   CSingleLock lock(m_critical);
443   const CProfile *profile = GetProfile(profileId);
444   if (!profile)
445     return false;
446
447   name = profile->getName();
448   return true;
449 }
450
451 std::string CProfilesManager::GetUserDataFolder() const
452 {
453   return GetMasterProfile().getDirectory();
454 }
455
456 std::string CProfilesManager::GetProfileUserDataFolder() const
457 {
458   if (m_currentProfile == 0)
459     return GetUserDataFolder();
460
461   return URIUtils::AddFileToFolder(GetUserDataFolder(), GetCurrentProfile().getDirectory());
462 }
463
464 std::string CProfilesManager::GetDatabaseFolder() const
465 {
466   if (GetCurrentProfile().hasDatabases())
467     return URIUtils::AddFileToFolder(GetProfileUserDataFolder(), "Database");
468
469   return URIUtils::AddFileToFolder(GetUserDataFolder(), "Database");
470 }
471
472 std::string CProfilesManager::GetCDDBFolder() const
473 {
474   return URIUtils::AddFileToFolder(GetDatabaseFolder(), "CDDB");
475 }
476
477 std::string CProfilesManager::GetThumbnailsFolder() const
478 {
479   if (GetCurrentProfile().hasDatabases())
480     return URIUtils::AddFileToFolder(GetProfileUserDataFolder(), "Thumbnails");
481
482   return URIUtils::AddFileToFolder(GetUserDataFolder(), "Thumbnails");
483 }
484
485 std::string CProfilesManager::GetVideoThumbFolder() const
486 {
487   return URIUtils::AddFileToFolder(GetThumbnailsFolder(), "Video");
488 }
489
490 std::string CProfilesManager::GetBookmarksThumbFolder() const
491 {
492   return URIUtils::AddFileToFolder(GetVideoThumbFolder(), "Bookmarks");
493 }
494
495 std::string CProfilesManager::GetLibraryFolder() const
496 {
497   if (GetCurrentProfile().hasDatabases())
498     return URIUtils::AddFileToFolder(GetProfileUserDataFolder(), "library");
499
500   return URIUtils::AddFileToFolder(GetUserDataFolder(), "library");
501 }
502
503 std::string CProfilesManager::GetSettingsFile() const
504 {
505   CStdString settings;
506   if (m_currentProfile == 0)
507     return "special://masterprofile/guisettings.xml";
508
509   return "special://profile/guisettings.xml";
510 }
511
512 std::string CProfilesManager::GetUserDataItem(const std::string& strFile) const
513 {
514   std::string path;
515   path = "special://profile/" + strFile;
516
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;
522
523   return path;
524 }
525
526 void CProfilesManager::SetCurrentProfileId(size_t profileId)
527 {
528   CSingleLock lock(m_critical);
529   m_currentProfile = profileId;
530   CSpecialProtocol::SetProfilePath(GetProfileUserDataFolder());
531 }