1 /*
2  *  Copyright (C) 2005-2018 Team Kodi
3  *  This file is part of Kodi - https://kodi.tv
4  *
5  *  SPDX-License-Identifier: GPL-2.0-or-later
6  *  See LICENSES/README.md for more information.
7  */
8 
9 #include "GUIDialogProfileSettings.h"
10 
11 #include "FileItem.h"
12 #include "GUIPassword.h"
13 #include "ServiceBroker.h"
14 #include "Util.h"
15 #include "dialogs/GUIDialogFileBrowser.h"
16 #include "dialogs/GUIDialogYesNo.h"
17 #include "filesystem/Directory.h"
18 #include "filesystem/File.h"
19 #include "guilib/GUIComponent.h"
20 #include "guilib/GUIKeyboardFactory.h"
21 #include "guilib/GUIWindowManager.h"
22 #include "guilib/LocalizeStrings.h"
23 #include "profiles/ProfileManager.h"
24 #include "profiles/dialogs/GUIDialogLockSettings.h"
25 #include "settings/SettingsComponent.h"
26 #include "settings/lib/Setting.h"
27 #include "settings/windows/GUIControlSettings.h"
28 #include "storage/MediaManager.h"
29 #include "utils/StringUtils.h"
30 #include "utils/URIUtils.h"
31 #include "utils/Variant.h"
32 #include "utils/log.h"
33 
34 #include <cassert>
35 #include <utility>
36 
37 #define SETTING_PROFILE_NAME          "profile.name"
38 #define SETTING_PROFILE_IMAGE         "profile.image"
39 #define SETTING_PROFILE_DIRECTORY     "profile.directory"
40 #define SETTING_PROFILE_LOCKS         "profile.locks"
41 #define SETTING_PROFILE_MEDIA         "profile.media"
42 #define SETTING_PROFILE_MEDIA_SOURCES "profile.mediasources"
43 
CGUIDialogProfileSettings()44 CGUIDialogProfileSettings::CGUIDialogProfileSettings()
45     : CGUIDialogSettingsManualBase(WINDOW_DIALOG_PROFILE_SETTINGS, "DialogSettings.xml")
46 { }
47 
48 CGUIDialogProfileSettings::~CGUIDialogProfileSettings() = default;
49 
ShowForProfile(unsigned int iProfile,bool firstLogin)50 bool CGUIDialogProfileSettings::ShowForProfile(unsigned int iProfile, bool firstLogin)
51 {
52   const std::shared_ptr<CProfileManager> profileManager = CServiceBroker::GetSettingsComponent()->GetProfileManager();
53 
54   if (firstLogin && iProfile > profileManager->GetNumberOfProfiles())
55     return false;
56 
57   CGUIDialogProfileSettings *dialog = CServiceBroker::GetGUI()->GetWindowManager().GetWindow<CGUIDialogProfileSettings>(WINDOW_DIALOG_PROFILE_SETTINGS);
58   if (dialog == NULL)
59     return false;
60 
61   dialog->m_needsSaving = false;
62   dialog->m_isDefault = iProfile == 0;
63   dialog->m_showDetails = !firstLogin;
64 
65   const CProfile *profile = profileManager->GetProfile(iProfile);
66   if (profile == NULL)
67   {
68     dialog->m_name.clear();
69     dialog->m_dbMode = 2;
70     dialog->m_sourcesMode = 2;
71     dialog->m_locks = CProfile::CLock();
72 
73     bool bLock = profileManager->GetMasterProfile().getLockMode() != LOCK_MODE_EVERYONE && !g_passwordManager.bMasterUser;
74     dialog->m_locks.addonManager = bLock;
75     dialog->m_locks.settings = (bLock) ? LOCK_LEVEL::ALL : LOCK_LEVEL::NONE;
76     dialog->m_locks.files = bLock;
77 
78     dialog->m_directory.clear();
79     dialog->m_thumb.clear();
80 
81     // prompt for a name
82     std::string profileName;
83     if (!CGUIKeyboardFactory::ShowAndGetInput(profileName, CVariant{g_localizeStrings.Get(20093)}, false) || profileName.empty())
84       return false;
85     dialog->m_name = profileName;
86 
87     // create a default path
88     std::string defaultDir = URIUtils::AddFileToFolder("profiles", CUtil::MakeLegalFileName(dialog->m_name));
89     URIUtils::AddSlashAtEnd(defaultDir);
90     XFILE::CDirectory::Create(URIUtils::AddFileToFolder("special://masterprofile/", defaultDir));
91 
92     // prompt for the user to change it if they want
93     std::string userDir = defaultDir;
94     if (GetProfilePath(userDir, false)) // can't be the master user
95     {
96       if (!URIUtils::PathHasParent(userDir, defaultDir)) // user chose a different folder
97         XFILE::CDirectory::Remove(URIUtils::AddFileToFolder("special://masterprofile/", defaultDir));
98     }
99     dialog->m_directory = userDir;
100     dialog->m_needsSaving = true;
101   }
102   else
103   {
104     dialog->m_name = profile->getName();
105     dialog->m_thumb = profile->getThumb();
106     dialog->m_directory = profile->getDirectory();
107     dialog->m_dbMode = profile->canWriteDatabases() ? 0 : 1;
108     if (profile->hasDatabases())
109       dialog->m_dbMode += 2;
110     dialog->m_sourcesMode = profile->canWriteSources() ? 0 : 1;
111     if (profile->hasSources())
112       dialog->m_sourcesMode += 2;
113 
114     dialog->m_locks = profile->GetLocks();
115   }
116 
117   dialog->Open();
118   if (dialog->m_needsSaving)
119   {
120     if (iProfile >= profileManager->GetNumberOfProfiles())
121     {
122       if (dialog->m_name.empty() || dialog->m_directory.empty())
123         return false;
124 
125       /*std::string strLabel;
126       strLabel.Format(g_localizeStrings.Get(20047),dialog->m_strName);
127       if (!CGUIDialogYesNo::ShowAndGetInput(20058, strLabel, dialog->m_strDirectory, ""))
128       {
129         CDirectory::Remove(URIUtils::AddFileToFolder(profileManager.GetUserDataFolder(), dialog->m_strDirectory));
130         return false;
131       }*/
132 
133       // check for old profile settings
134       CProfile profile(dialog->m_directory, dialog->m_name, profileManager->GetNextProfileId());
135       profileManager->AddProfile(profile);
136       bool exists = XFILE::CFile::Exists(URIUtils::AddFileToFolder("special://masterprofile/", dialog->m_directory, "guisettings.xml"));
137 
138       if (exists && !CGUIDialogYesNo::ShowAndGetInput(CVariant{20058}, CVariant{20104}))
139         exists = false;
140 
141       if (!exists)
142       {
143         // copy masterprofile guisettings to new profile guisettings
144         // If the user selects 'start fresh', do nothing as a fresh
145         // guisettings.xml will be created on first profile use.
146         if (CGUIDialogYesNo::ShowAndGetInput(CVariant{20058}, CVariant{20048}, CVariant{""}, CVariant{""}, CVariant{20044}, CVariant{20064}))
147         {
148           XFILE::CFile::Copy(URIUtils::AddFileToFolder("special://masterprofile/", "guisettings.xml"),
149                               URIUtils::AddFileToFolder("special://masterprofile/", dialog->m_directory, "guisettings.xml"));
150         }
151       }
152 
153       exists = XFILE::CFile::Exists(URIUtils::AddFileToFolder("special://masterprofile/", dialog->m_directory, "sources.xml"));
154       if (exists && !CGUIDialogYesNo::ShowAndGetInput(CVariant{20058}, CVariant{20106}))
155         exists = false;
156 
157       if (!exists)
158       {
159         if ((dialog->m_sourcesMode & 2) == 2)
160           // prompt user to copy masterprofile's sources.xml file
161           // If 'start fresh' (no) is selected, do nothing.
162           if (CGUIDialogYesNo::ShowAndGetInput(CVariant{20058}, CVariant{20071}, CVariant{""}, CVariant{""}, CVariant{20044}, CVariant{20064}))
163           {
164             XFILE::CFile::Copy(URIUtils::AddFileToFolder("special://masterprofile/", "sources.xml"),
165                                 URIUtils::AddFileToFolder("special://masterprofile/", dialog->m_directory, "sources.xml"));
166           }
167       }
168     }
169 
170     /*if (!dialog->m_bIsNewUser)
171       if (!CGUIDialogYesNo::ShowAndGetInput(20067, 20103))
172         return false;*/
173 
174     CProfile *profile = profileManager->GetProfile(iProfile);
175     assert(profile);
176     profile->setName(dialog->m_name);
177     profile->setDirectory(dialog->m_directory);
178     profile->setThumb(dialog->m_thumb);
179     profile->setWriteDatabases(!((dialog->m_dbMode & 1) == 1));
180     profile->setWriteSources(!((dialog->m_sourcesMode & 1) == 1));
181     profile->setDatabases((dialog->m_dbMode & 2) == 2);
182     profile->setSources((dialog->m_sourcesMode & 2) == 2);
183     profile->SetLocks(dialog->m_locks);
184     profileManager->Save();
185 
186     return true;
187   }
188 
189   return dialog->m_needsSaving;
190 }
191 
OnWindowLoaded()192 void CGUIDialogProfileSettings::OnWindowLoaded()
193 {
194   CGUIDialogSettingsManualBase::OnWindowLoaded();
195 }
196 
OnSettingChanged(const std::shared_ptr<const CSetting> & setting)197 void CGUIDialogProfileSettings::OnSettingChanged(const std::shared_ptr<const CSetting>& setting)
198 {
199   if (setting == NULL)
200     return;
201 
202   CGUIDialogSettingsManualBase::OnSettingChanged(setting);
203 
204   const std::string &settingId = setting->GetId();
205   if (settingId == SETTING_PROFILE_NAME)
206   {
207     m_name = std::static_pointer_cast<const CSettingString>(setting)->GetValue();
208   }
209   else if (settingId == SETTING_PROFILE_MEDIA)
210     m_dbMode = std::static_pointer_cast<const CSettingInt>(setting)->GetValue();
211   else if (settingId == SETTING_PROFILE_MEDIA_SOURCES)
212     m_sourcesMode = std::static_pointer_cast<const CSettingInt>(setting)->GetValue();
213 
214   m_needsSaving = true;
215 }
216 
OnSettingAction(const std::shared_ptr<const CSetting> & setting)217 void CGUIDialogProfileSettings::OnSettingAction(const std::shared_ptr<const CSetting>& setting)
218 {
219   if (setting == NULL)
220     return;
221 
222   CGUIDialogSettingsManualBase::OnSettingAction(setting);
223 
224   const std::string &settingId = setting->GetId();
225   if (settingId == SETTING_PROFILE_IMAGE)
226   {
227     VECSOURCES shares;
228     CServiceBroker::GetMediaManager().GetLocalDrives(shares);
229 
230     CFileItemList items;
231     if (!m_thumb.empty())
232     {
233       CFileItemPtr item(new CFileItem("thumb://Current", false));
234       item->SetArt("thumb", m_thumb);
235       item->SetLabel(g_localizeStrings.Get(20016));
236       items.Add(item);
237     }
238 
239     CFileItemPtr item(new CFileItem("thumb://None", false));
240     item->SetArt("thumb", "DefaultUser.png");
241     item->SetLabel(g_localizeStrings.Get(20018));
242     items.Add(item);
243 
244     std::string thumb;
245     if (CGUIDialogFileBrowser::ShowAndGetImage(items, shares, g_localizeStrings.Get(1030), thumb) &&
246         !StringUtils::EqualsNoCase(thumb, "thumb://Current"))
247     {
248       m_needsSaving = true;
249       m_thumb = StringUtils::EqualsNoCase(thumb, "thumb://None") ? "" : thumb;
250 
251       UpdateProfileImage();
252     }
253   }
254   else if (settingId == SETTING_PROFILE_DIRECTORY)
255   {
256     if (!GetProfilePath(m_directory, m_isDefault))
257       return;
258 
259     m_needsSaving = true;
260     updateProfileDirectory();
261   }
262   else if (settingId == SETTING_PROFILE_LOCKS)
263   {
264     if (m_showDetails)
265     {
266       const std::shared_ptr<CProfileManager> profileManager = CServiceBroker::GetSettingsComponent()->GetProfileManager();
267 
268       if (profileManager->GetMasterProfile().getLockMode() == LOCK_MODE_EVERYONE && !m_isDefault)
269       {
270         if (CGUIDialogYesNo::ShowAndGetInput(CVariant{20066}, CVariant{20118}))
271           g_passwordManager.SetMasterLockMode(false);
272         if (profileManager->GetMasterProfile().getLockMode() == LOCK_MODE_EVERYONE)
273           return;
274       }
275       if (CGUIDialogLockSettings::ShowAndGetLock(m_locks, m_isDefault ? 12360 : 20068,
276               profileManager->GetMasterProfile().getLockMode() == LOCK_MODE_EVERYONE || m_isDefault))
277         m_needsSaving = true;
278     }
279     else
280     {
281       if (CGUIDialogLockSettings::ShowAndGetLock(m_locks, m_isDefault ? 12360 : 20068, false, false))
282         m_needsSaving = true;
283     }
284   }
285 }
286 
OnCancel()287 void CGUIDialogProfileSettings::OnCancel()
288 {
289   m_needsSaving = false;
290 
291   CGUIDialogSettingsManualBase::OnCancel();
292 }
293 
SetupView()294 void CGUIDialogProfileSettings::SetupView()
295 {
296   CGUIDialogSettingsManualBase::SetupView();
297 
298   // set the heading
299   SetHeading(!m_showDetails ? 20255 : 20067);
300 
301   SET_CONTROL_HIDDEN(CONTROL_SETTINGS_CUSTOM_BUTTON);
302   SET_CONTROL_LABEL(CONTROL_SETTINGS_OKAY_BUTTON, 186);
303   SET_CONTROL_LABEL(CONTROL_SETTINGS_CANCEL_BUTTON, 222);
304 
305   // set the profile image and directory
306   UpdateProfileImage();
307   updateProfileDirectory();
308 }
309 
InitializeSettings()310 void CGUIDialogProfileSettings::InitializeSettings()
311 {
312   CGUIDialogSettingsManualBase::InitializeSettings();
313 
314   const std::shared_ptr<CSettingCategory> category = AddCategory("profilesettings", -1);
315   if (category == NULL)
316   {
317     CLog::Log(LOGERROR, "CGUIDialogProfileSettings: unable to setup settings");
318     return;
319   }
320 
321   const std::shared_ptr<CSettingGroup> group = AddGroup(category);
322   if (group == NULL)
323   {
324     CLog::Log(LOGERROR, "CGUIDialogProfileSettings: unable to setup settings");
325     return;
326   }
327 
328   const std::shared_ptr<CProfileManager> profileManager = CServiceBroker::GetSettingsComponent()->GetProfileManager();
329 
330   AddEdit(group, SETTING_PROFILE_NAME, 20093, SettingLevel::Basic, m_name);
331   AddButton(group, SETTING_PROFILE_IMAGE, 20065, SettingLevel::Basic);
332 
333   if (!m_isDefault && m_showDetails)
334     AddButton(group, SETTING_PROFILE_DIRECTORY, 20070, SettingLevel::Basic);
335 
336   if (m_showDetails ||
337      (m_locks.mode == LOCK_MODE_EVERYONE && profileManager->GetMasterProfile().getLockMode() != LOCK_MODE_EVERYONE))
338     AddButton(group, SETTING_PROFILE_LOCKS, 20066, SettingLevel::Basic);
339 
340   if (!m_isDefault && m_showDetails)
341   {
342     const std::shared_ptr<CSettingGroup> groupMedia = AddGroup(category);
343     if (groupMedia == NULL)
344     {
345       CLog::Log(LOGERROR, "CGUIDialogProfileSettings: unable to setup settings");
346       return;
347     }
348 
349     TranslatableIntegerSettingOptions entries;
350     entries.push_back(TranslatableIntegerSettingOption(20062, 0));
351     entries.push_back(TranslatableIntegerSettingOption(20063, 1));
352     entries.push_back(TranslatableIntegerSettingOption(20061, 2));
353     if (profileManager->GetMasterProfile().getLockMode() != LOCK_MODE_EVERYONE)
354       entries.push_back(TranslatableIntegerSettingOption(20107, 3));
355 
356     AddSpinner(groupMedia, SETTING_PROFILE_MEDIA, 20060, SettingLevel::Basic, m_dbMode, entries);
357     AddSpinner(groupMedia, SETTING_PROFILE_MEDIA_SOURCES, 20094, SettingLevel::Basic, m_sourcesMode, entries);
358   }
359 }
360 
GetProfilePath(std::string & directory,bool isDefault)361 bool CGUIDialogProfileSettings::GetProfilePath(std::string &directory, bool isDefault)
362 {
363   VECSOURCES shares;
364   CMediaSource share;
365   share.strName = g_localizeStrings.Get(13200);
366   share.strPath = "special://masterprofile/profiles/";
367   shares.push_back(share);
368 
369   std::string strDirectory;
370   if (directory.empty())
371     strDirectory = share.strPath;
372   else
373     strDirectory = URIUtils::AddFileToFolder("special://masterprofile/", directory);
374 
375   if (!CGUIDialogFileBrowser::ShowAndGetDirectory(shares, g_localizeStrings.Get(657), strDirectory, true))
376     return false;
377 
378   directory = strDirectory;
379   if (!isDefault)
380     directory.erase(0, 24);
381 
382   return true;
383 }
384 
UpdateProfileImage()385 void CGUIDialogProfileSettings::UpdateProfileImage()
386 {
387   BaseSettingControlPtr settingControl = GetSettingControl(SETTING_PROFILE_IMAGE);
388   if (settingControl != NULL && settingControl->GetControl() != NULL)
389     SET_CONTROL_LABEL2(settingControl->GetID(), URIUtils::GetFileName(m_thumb));
390 }
391 
updateProfileDirectory()392 void CGUIDialogProfileSettings::updateProfileDirectory()
393 {
394   BaseSettingControlPtr settingControl = GetSettingControl(SETTING_PROFILE_DIRECTORY);
395   if (settingControl != NULL && settingControl->GetControl() != NULL)
396     SET_CONTROL_LABEL2(settingControl->GetID(), m_directory);
397 }
398