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 "GUIDialogMediaSource.h"
12 #include "FileItemList.h"
13 #include "GUIDialogFileBrowser.h"
14 #include "GUIDialogYesNo.h"
15 #include "PasswordManager.h"
16 #include "ServiceBroker.h"
19 #include "filesystem/Directory.h"
20 #include "filesystem/PVRDirectory.h"
21 #include "guilib/GUIComponent.h"
22 #include "guilib/GUIKeyboardFactory.h"
23 #include "guilib/GUIWindowManager.h"
24 #include "guilib/LocalizeStrings.h"
25 #include "input/actions/ActionIDs.h"
26 #include "music/windows/GUIWindowMusicBase.h"
27 #include "pvr/recordings/PVRRecordingsPath.h"
28 #include "settings/MediaSourceSettings.h"
29 #include "settings/Settings.h"
30 #include "settings/SettingsComponent.h"
31 #include "utils/StringUtils.h"
32 #include "utils/URIUtils.h"
33 #include "utils/Variant.h"
34 #include "video/windows/GUIWindowVideoBase.h"
36 #if defined(TARGET_ANDROID)
37 #include "utils/FileUtils.h"
39 #include "platform/android/activity/XBMCApp.h"
42 #ifdef TARGET_WINDOWS_STORE
43 #include "platform/win10/filesystem/WinLibraryDirectory.h"
46 using namespace XFILE
;
48 #define CONTROL_HEADING 2
49 #define CONTROL_PATH 10
50 #define CONTROL_PATH_BROWSE 11
51 #define CONTROL_NAME 12
52 #define CONTROL_PATH_ADD 13
53 #define CONTROL_PATH_REMOVE 14
55 #define CONTROL_CANCEL 19
56 #define CONTROL_CONTENT 20
58 CGUIDialogMediaSource::CGUIDialogMediaSource(void)
59 : CGUIDialog(WINDOW_DIALOG_MEDIA_SOURCE
, "DialogMediaSource.xml")
61 m_paths
= new CFileItemList
;
62 m_loadType
= KEEP_IN_MEMORY
;
65 CGUIDialogMediaSource::~CGUIDialogMediaSource()
70 bool CGUIDialogMediaSource::OnBack(int actionID
)
73 return CGUIDialog::OnBack(actionID
);
76 bool CGUIDialogMediaSource::OnMessage(CGUIMessage
& message
)
78 switch (message
.GetMessage())
82 int iControl
= message
.GetSenderId();
83 int iAction
= message
.GetParam1();
84 if (iControl
== CONTROL_PATH
&& (iAction
== ACTION_SELECT_ITEM
|| iAction
== ACTION_MOUSE_LEFT_CLICK
))
85 OnPath(GetSelectedItem());
86 else if (iControl
== CONTROL_PATH_BROWSE
)
87 OnPathBrowse(GetSelectedItem());
88 else if (iControl
== CONTROL_PATH_ADD
)
90 else if (iControl
== CONTROL_PATH_REMOVE
)
91 OnPathRemove(GetSelectedItem());
92 else if (iControl
== CONTROL_NAME
)
94 OnEditChanged(iControl
, m_name
);
97 else if (iControl
== CONTROL_OK
)
99 else if (iControl
== CONTROL_CANCEL
)
106 case GUI_MSG_WINDOW_INIT
:
111 case GUI_MSG_SETFOCUS
:
112 if (message
.GetControlId() == CONTROL_PATH_BROWSE
||
113 message
.GetControlId() == CONTROL_PATH_ADD
||
114 message
.GetControlId() == CONTROL_PATH_REMOVE
)
116 HighlightItem(GetSelectedItem());
122 return CGUIDialog::OnMessage(message
);
125 // \brief Show CGUIDialogMediaSource dialog and prompt for a new media source.
126 // \return True if the media source is added, false otherwise.
127 bool CGUIDialogMediaSource::ShowAndAddMediaSource(const std::string
&type
)
129 CGUIDialogMediaSource
*dialog
= CServiceBroker::GetGUI()->GetWindowManager().GetWindow
<CGUIDialogMediaSource
>(WINDOW_DIALOG_MEDIA_SOURCE
);
130 if (!dialog
) return false;
131 dialog
->Initialize();
132 dialog
->SetShare(CMediaSource());
133 dialog
->SetTypeOfMedia(type
);
135 bool confirmed(dialog
->IsConfirmed());
138 // Add this media source
139 // Get unique source name
140 std::string strName
= dialog
->GetUniqueMediaSourceName();
143 share
.FromNameAndPaths(type
, strName
, dialog
->GetPaths());
144 if (dialog
->m_paths
->Size() > 0)
145 share
.m_strThumbnailImage
= dialog
->m_paths
->Get(0)->GetArt("thumb");
146 CMediaSourceSettings::GetInstance().AddShare(type
, share
);
147 OnMediaSourceChanged(type
, "", share
);
149 dialog
->m_paths
->Clear();
153 bool CGUIDialogMediaSource::ShowAndEditMediaSource(const std::string
&type
, const std::string
&share
)
155 VECSOURCES
* pShares
= CMediaSourceSettings::GetInstance().GetSources(type
);
158 for (unsigned int i
= 0;i
<pShares
->size();++i
)
160 if (StringUtils::EqualsNoCase((*pShares
)[i
].strName
, share
))
161 return ShowAndEditMediaSource(type
, (*pShares
)[i
]);
167 bool CGUIDialogMediaSource::ShowAndEditMediaSource(const std::string
&type
, const CMediaSource
&share
)
169 std::string strOldName
= share
.strName
;
170 CGUIDialogMediaSource
*dialog
= CServiceBroker::GetGUI()->GetWindowManager().GetWindow
<CGUIDialogMediaSource
>(WINDOW_DIALOG_MEDIA_SOURCE
);
171 if (!dialog
) return false;
172 dialog
->Initialize();
173 dialog
->SetShare(share
);
174 dialog
->SetTypeOfMedia(type
, true);
176 bool confirmed(dialog
->IsConfirmed());
179 // Update media source
180 // Get unique new source name when changed
181 std::string
strName(dialog
->m_name
);
182 if (!StringUtils::EqualsNoCase(dialog
->m_name
, strOldName
))
183 strName
= dialog
->GetUniqueMediaSourceName();
185 CMediaSource newShare
;
186 newShare
.FromNameAndPaths(type
, strName
, dialog
->GetPaths());
187 CMediaSourceSettings::GetInstance().UpdateShare(type
, strOldName
, newShare
);
189 OnMediaSourceChanged(type
, strOldName
, newShare
);
191 dialog
->m_paths
->Clear();
195 std::string
CGUIDialogMediaSource::GetUniqueMediaSourceName()
197 // Get unique source name for this media type
198 unsigned int i
, j
= 2;
199 bool bConfirmed
= false;
200 VECSOURCES
* pShares
= CMediaSourceSettings::GetInstance().GetSources(m_type
);
201 std::string strName
= m_name
;
204 for (i
= 0; i
<pShares
->size(); ++i
)
206 if (StringUtils::EqualsNoCase((*pShares
)[i
].strName
, strName
))
209 if (i
< pShares
->size())
210 // found a match - try next
211 strName
= StringUtils::Format("{} ({})", m_name
, j
++);
218 void CGUIDialogMediaSource::OnMediaSourceChanged(const std::string
& type
, const std::string
& oldName
, const CMediaSource
& share
)
220 // Processing once media source added/edited - library scraping and scanning
221 if (!StringUtils::StartsWithNoCase(share
.strPath
, "rss://") &&
222 !StringUtils::StartsWithNoCase(share
.strPath
, "rsss://") &&
223 !StringUtils::StartsWithNoCase(share
.strPath
, "upnp://"))
225 if (type
== "video" && !URIUtils::IsLiveTV(share
.strPath
))
226 // Assign content to a path, refresh scraper information optionally start a scan
227 CGUIWindowVideoBase::OnAssignContent(share
.strPath
);
228 else if (type
== "music")
229 CGUIWindowMusicBase::OnAssignContent(oldName
, share
);
233 void CGUIDialogMediaSource::OnPathBrowse(int item
)
235 if (item
< 0 || item
>= m_paths
->Size()) return;
236 // Browse is called. Open the filebrowser dialog.
237 // Ignore current path is best at this stage??
238 std::string path
= m_paths
->Get(item
)->GetPath();
239 bool allowNetworkShares(m_type
!= "programs");
240 VECSOURCES extraShares
;
242 if (m_name
!= CUtil::GetTitleFromPath(path
))
243 m_bNameChanged
= true;
246 if (m_type
== "music")
249 #if defined(TARGET_ANDROID)
250 // add the default android music directory
252 if (CXBMCApp::GetExternalStorage(path
, "music") && !path
.empty() && CDirectory::Exists(path
))
254 share1
.strPath
= path
;
255 share1
.strName
= g_localizeStrings
.Get(20240);
256 share1
.m_ignore
= true;
257 extraShares
.push_back(share1
);
261 #if defined(TARGET_WINDOWS_STORE)
262 // add the default UWP music directory
264 if (XFILE::CWinLibraryDirectory::GetStoragePath(m_type
, path
) && !path
.empty() && CDirectory::Exists(path
))
266 share1
.strPath
= path
;
267 share1
.strName
= g_localizeStrings
.Get(20245);
268 share1
.m_ignore
= true;
269 extraShares
.push_back(share1
);
273 // add the music playlist location
274 share1
.strPath
= "special://musicplaylists/";
275 share1
.strName
= g_localizeStrings
.Get(20011);
276 share1
.m_ignore
= true;
277 extraShares
.push_back(share1
);
279 // add the recordings dir as needed
280 if (CPVRDirectory::HasRadioRecordings())
282 share1
.strPath
= PVR::CPVRRecordingsPath::PATH_ACTIVE_RADIO_RECORDINGS
;
283 share1
.strName
= g_localizeStrings
.Get(19017); // Recordings
284 extraShares
.push_back(share1
);
286 if (CPVRDirectory::HasDeletedRadioRecordings())
288 share1
.strPath
= PVR::CPVRRecordingsPath::PATH_DELETED_RADIO_RECORDINGS
;
289 share1
.strName
= g_localizeStrings
.Get(19184); // Deleted recordings
290 extraShares
.push_back(share1
);
293 if (CServiceBroker::GetSettingsComponent()->GetSettings()->GetString(CSettings::SETTING_AUDIOCDS_RECORDINGPATH
) != "")
295 share1
.strPath
= "special://recordings/";
296 share1
.strName
= g_localizeStrings
.Get(21883);
297 extraShares
.push_back(share1
);
300 else if (m_type
== "video")
303 #if defined(TARGET_ANDROID)
304 // add the default android video directory
306 if (CXBMCApp::GetExternalStorage(path
, "videos") && !path
.empty() && CFileUtils::Exists(path
))
308 share1
.strPath
= path
;
309 share1
.strName
= g_localizeStrings
.Get(20241);
310 share1
.m_ignore
= true;
311 extraShares
.push_back(share1
);
314 #if defined(TARGET_WINDOWS_STORE)
315 // add the default UWP music directory
317 if (XFILE::CWinLibraryDirectory::GetStoragePath(m_type
, path
) && !path
.empty() && CDirectory::Exists(path
))
319 share1
.strPath
= path
;
320 share1
.strName
= g_localizeStrings
.Get(20246);
321 share1
.m_ignore
= true;
322 extraShares
.push_back(share1
);
326 // add the video playlist location
327 share1
.m_ignore
= true;
328 share1
.strPath
= "special://videoplaylists/";
329 share1
.strName
= g_localizeStrings
.Get(20012);
330 extraShares
.push_back(share1
);
332 // add the recordings dir as needed
333 if (CPVRDirectory::HasTVRecordings())
335 share1
.strPath
= PVR::CPVRRecordingsPath::PATH_ACTIVE_TV_RECORDINGS
;
336 share1
.strName
= g_localizeStrings
.Get(19017); // Recordings
337 extraShares
.push_back(share1
);
339 if (CPVRDirectory::HasDeletedTVRecordings())
341 share1
.strPath
= PVR::CPVRRecordingsPath::PATH_DELETED_TV_RECORDINGS
;
342 share1
.strName
= g_localizeStrings
.Get(19184); // Deleted recordings
343 extraShares
.push_back(share1
);
346 else if (m_type
== "pictures")
349 #if defined(TARGET_ANDROID)
350 // add the default android music directory
352 if (CXBMCApp::GetExternalStorage(path
, "pictures") && !path
.empty() && CFileUtils::Exists(path
))
354 share1
.strPath
= path
;
355 share1
.strName
= g_localizeStrings
.Get(20242);
356 share1
.m_ignore
= true;
357 extraShares
.push_back(share1
);
361 if (CXBMCApp::GetExternalStorage(path
, "photos") && !path
.empty() && CFileUtils::Exists(path
))
363 share1
.strPath
= path
;
364 share1
.strName
= g_localizeStrings
.Get(20243);
365 share1
.m_ignore
= true;
366 extraShares
.push_back(share1
);
369 #if defined(TARGET_WINDOWS_STORE)
370 // add the default UWP music directory
372 if (XFILE::CWinLibraryDirectory::GetStoragePath(m_type
, path
) && !path
.empty() && CDirectory::Exists(path
))
374 share1
.strPath
= path
;
375 share1
.strName
= g_localizeStrings
.Get(20247);
376 share1
.m_ignore
= true;
377 extraShares
.push_back(share1
);
380 if (XFILE::CWinLibraryDirectory::GetStoragePath("photos", path
) && !path
.empty() && CDirectory::Exists(path
))
382 share1
.strPath
= path
;
383 share1
.strName
= g_localizeStrings
.Get(20248);
384 share1
.m_ignore
= true;
385 extraShares
.push_back(share1
);
389 share1
.m_ignore
= true;
390 if (CServiceBroker::GetSettingsComponent()->GetSettings()->GetString(CSettings::SETTING_DEBUG_SCREENSHOTPATH
) != "")
392 share1
.strPath
= "special://screenshots/";
393 share1
.strName
= g_localizeStrings
.Get(20008);
394 extraShares
.push_back(share1
);
397 else if (m_type
== "games")
401 else if (m_type
== "programs")
405 if (CGUIDialogFileBrowser::ShowAndGetSource(path
, allowNetworkShares
, extraShares
.size() == 0 ? NULL
: &extraShares
))
407 if (item
< m_paths
->Size()) // if the skin does funky things, m_paths may have been cleared
408 m_paths
->Get(item
)->SetPath(path
);
409 if (!m_bNameChanged
|| m_name
.empty())
412 m_name
= url
.GetWithoutUserDetails();
413 URIUtils::RemoveSlashAtEnd(m_name
);
414 m_name
= CUtil::GetTitleFromPath(m_name
);
420 void CGUIDialogMediaSource::OnPath(int item
)
422 if (item
< 0 || item
>= m_paths
->Size()) return;
424 std::string
path(m_paths
->Get(item
)->GetPath());
425 if (m_name
!= CUtil::GetTitleFromPath(path
))
426 m_bNameChanged
= true;
428 CGUIKeyboardFactory::ShowAndGetInput(path
, CVariant
{ g_localizeStrings
.Get(1021) }, false);
429 m_paths
->Get(item
)->SetPath(path
);
431 if (!m_bNameChanged
|| m_name
.empty())
433 CURL
url(m_paths
->Get(item
)->GetPath());
434 m_name
= url
.GetWithoutUserDetails();
435 URIUtils::RemoveSlashAtEnd(m_name
);
436 m_name
= CUtil::GetTitleFromPath(m_name
);
441 void CGUIDialogMediaSource::OnOK()
443 // Verify the paths by doing a GetDirectory.
446 // Create temp media source to encode path urls as multipath
447 // Name of actual source may need to be made unique when saved in sources
449 share
.FromNameAndPaths(m_type
, m_name
, GetPaths());
451 if (StringUtils::StartsWithNoCase(share
.strPath
, "plugin://") ||
452 CDirectory::GetDirectory(share
.strPath
, items
, "", DIR_FLAG_NO_FILE_DIRS
| DIR_FLAG_ALLOW_PROMPT
) ||
453 CGUIDialogYesNo::ShowAndGetInput(CVariant
{ 1001 }, CVariant
{ 1025 }))
460 void CGUIDialogMediaSource::OnCancel()
466 void CGUIDialogMediaSource::UpdateButtons()
468 if (!m_paths
->Size()) // sanity
471 CONTROL_ENABLE_ON_CONDITION(CONTROL_OK
, !m_paths
->Get(0)->GetPath().empty() && !m_name
.empty());
472 CONTROL_ENABLE_ON_CONDITION(CONTROL_PATH_ADD
, !m_paths
->Get(0)->GetPath().empty());
473 CONTROL_ENABLE_ON_CONDITION(CONTROL_PATH_REMOVE
, m_paths
->Size() > 1);
475 SET_CONTROL_LABEL2(CONTROL_NAME
, m_name
);
476 SendMessage(GUI_MSG_SET_TYPE
, CONTROL_NAME
, 0, 1022);
478 int currentItem
= GetSelectedItem();
479 SendMessage(GUI_MSG_LABEL_RESET
, CONTROL_PATH
);
480 for (int i
= 0; i
< m_paths
->Size(); i
++)
482 CFileItemPtr item
= m_paths
->Get(i
);
484 CURL
url(item
->GetPath());
485 path
= url
.GetWithoutUserDetails();
486 if (path
.empty()) path
= "<" + g_localizeStrings
.Get(231) + ">"; // <None>
487 item
->SetLabel(path
);
489 CGUIMessage
msg(GUI_MSG_LABEL_BIND
, GetID(), CONTROL_PATH
, 0, 0, m_paths
);
491 SendMessage(GUI_MSG_ITEM_SELECT
, CONTROL_PATH
, currentItem
);
493 SET_CONTROL_HIDDEN(CONTROL_CONTENT
);
496 void CGUIDialogMediaSource::SetShare(const CMediaSource
&share
)
499 for (unsigned int i
= 0; i
< share
.vecPaths
.size(); i
++)
501 CFileItemPtr
item(new CFileItem(share
.vecPaths
[i
], true));
504 if (0 == share
.vecPaths
.size())
506 CFileItemPtr
item(new CFileItem("", true));
509 m_name
= share
.strName
;
513 void CGUIDialogMediaSource::SetTypeOfMedia(const std::string
&type
, bool editNotAdd
)
520 heading
= g_localizeStrings
.Get(10053);
521 else if (type
== "music")
522 heading
= g_localizeStrings
.Get(10054);
523 else if (type
== "pictures")
524 heading
= g_localizeStrings
.Get(10055);
525 else if (type
== "games")
526 heading
= g_localizeStrings
.Get(35252); // "Edit game source"
527 else if (type
== "programs")
528 heading
= g_localizeStrings
.Get(10056);
530 heading
= g_localizeStrings
.Get(10057);
535 heading
= g_localizeStrings
.Get(10048);
536 else if (type
== "music")
537 heading
= g_localizeStrings
.Get(10049);
538 else if (type
== "pictures")
539 heading
= g_localizeStrings
.Get(13006);
540 else if (type
== "games")
541 heading
= g_localizeStrings
.Get(35251); // "Add game source"
542 else if (type
== "programs")
543 heading
= g_localizeStrings
.Get(10051);
545 heading
= g_localizeStrings
.Get(10052);
547 SET_CONTROL_LABEL(CONTROL_HEADING
, heading
);
550 int CGUIDialogMediaSource::GetSelectedItem()
552 CGUIMessage
message(GUI_MSG_ITEM_SELECTED
, GetID(), CONTROL_PATH
);
554 int value
= message
.GetParam1();
555 if (value
< 0 || value
>= m_paths
->Size()) return 0;
559 void CGUIDialogMediaSource::HighlightItem(int item
)
561 for (int i
= 0; i
< m_paths
->Size(); i
++)
562 m_paths
->Get(i
)->Select(false);
563 if (item
>= 0 && item
< m_paths
->Size())
564 m_paths
->Get(item
)->Select(true);
565 CGUIMessage
msg(GUI_MSG_ITEM_SELECT
, GetID(), CONTROL_PATH
, item
);
569 void CGUIDialogMediaSource::OnPathRemove(int item
)
571 m_paths
->Remove(item
);
573 if (item
>= m_paths
->Size())
574 HighlightItem(m_paths
->Size() - 1);
577 if (m_paths
->Size() <= 1)
579 SET_CONTROL_FOCUS(CONTROL_PATH_ADD
, 0);
583 void CGUIDialogMediaSource::OnPathAdd()
585 // add a new item and select it as well
586 CFileItemPtr
item(new CFileItem("", true));
589 HighlightItem(m_paths
->Size() - 1);
592 std::vector
<std::string
> CGUIDialogMediaSource::GetPaths() const
594 std::vector
<std::string
> paths
;
595 for (int i
= 0; i
< m_paths
->Size(); i
++)
597 if (!m_paths
->Get(i
)->GetPath().empty())
598 { // strip off the user and password for supported paths (anything that the password manager can auth)
599 // and add the user/pass to the password manager - note, we haven't confirmed that it works
600 // at this point, but if it doesn't, the user will get prompted anyway in implementation.
601 CURL
url(m_paths
->Get(i
)->GetPath());
602 if (CPasswordManager::GetInstance().IsURLSupported(url
) && !url
.GetUserName().empty())
604 CPasswordManager::GetInstance().SaveAuthenticatedURL(url
);
609 paths
.push_back(url
.Get());
615 void CGUIDialogMediaSource::OnDeinitWindow(int nextWindowID
)
617 CGUIDialog::OnDeinitWindow(nextWindowID
);
619 // clear paths container
620 CGUIMessage
msg(GUI_MSG_LABEL_RESET
, GetID(), CONTROL_PATH
, 0);