/* * Copyright (C) 2005-2018 Team Kodi * This file is part of Kodi - https://kodi.tv * * SPDX-License-Identifier: GPL-2.0-or-later * See LICENSES/README.md for more information. */ #include "GUIWindowFileManager.h" #include "Autorun.h" #include "GUIPassword.h" #include "GUIUserMessages.h" #include "PlayListPlayer.h" #include "ServiceBroker.h" #include "URL.h" #include "Util.h" #include "application/Application.h" #include "application/ApplicationComponents.h" #include "application/ApplicationPlayer.h" #include "cores/playercorefactory/PlayerCoreFactory.h" #include "dialogs/GUIDialogBusy.h" #include "dialogs/GUIDialogContextMenu.h" #include "dialogs/GUIDialogMediaSource.h" #include "dialogs/GUIDialogProgress.h" #include "dialogs/GUIDialogTextViewer.h" #include "dialogs/GUIDialogYesNo.h" #include "favourites/FavouritesService.h" #include "filesystem/Directory.h" #include "filesystem/FileDirectoryFactory.h" #include "filesystem/ZipManager.h" #include "guilib/GUIComponent.h" #include "guilib/GUIKeyboardFactory.h" #include "guilib/GUIWindowManager.h" #include "guilib/LocalizeStrings.h" #include "input/InputManager.h" #include "interfaces/generic/ScriptInvocationManager.h" #include "messaging/ApplicationMessenger.h" #include "messaging/helpers/DialogOKHelper.h" #include "network/Network.h" #include "pictures/GUIWindowSlideShow.h" #include "platform/Filesystem.h" #include "playlists/PlayList.h" #include "playlists/PlayListFactory.h" #include "settings/MediaSourceSettings.h" #include "settings/Settings.h" #include "settings/SettingsComponent.h" #include "storage/MediaManager.h" #include "threads/IRunnable.h" #include "utils/FileOperationJob.h" #include "utils/FileUtils.h" #include "utils/JobManager.h" #include "utils/StringUtils.h" #include "utils/URIUtils.h" #include "utils/Variant.h" #include "utils/log.h" using namespace XFILE; using namespace KODI::MESSAGING; #define CONTROL_BTNSELECTALL 1 #define CONTROL_BTNFAVOURITES 2 #define CONTROL_BTNPLAYWITH 3 #define CONTROL_BTNRENAME 4 #define CONTROL_BTNDELETE 5 #define CONTROL_BTNCOPY 6 #define CONTROL_BTNMOVE 7 #define CONTROL_BTNNEWFOLDER 8 #define CONTROL_BTNCALCSIZE 9 #define CONTROL_BTNSWITCHMEDIA 11 #define CONTROL_BTNCANCELJOB 12 #define CONTROL_BTNVIEW 13 #define CONTROL_NUMFILES_LEFT 12 #define CONTROL_NUMFILES_RIGHT 13 #define CONTROL_LEFT_LIST 20 #define CONTROL_RIGHT_LIST 21 #define CONTROL_CURRENTDIRLABEL_LEFT 101 #define CONTROL_CURRENTDIRLABEL_RIGHT 102 namespace { class CGetDirectoryItems : public IRunnable { public: CGetDirectoryItems(XFILE::CVirtualDirectory &dir, CURL &url, CFileItemList &items) : m_result(false), m_dir(dir), m_url(url), m_items(items) { } void Run() override { m_result = m_dir.GetDirectory(m_url, m_items, false, false); } void Cancel() override { m_dir.CancelDirectory(); } bool m_result; protected: XFILE::CVirtualDirectory &m_dir; CURL m_url; CFileItemList &m_items; }; } CGUIWindowFileManager::CGUIWindowFileManager(void) : CGUIWindow(WINDOW_FILES, "FileManager.xml"), CJobQueue(false,2) { m_Directory[0] = new CFileItem; m_Directory[1] = new CFileItem; m_vecItems[0] = new CFileItemList; m_vecItems[1] = new CFileItemList; m_Directory[0]->SetPath("?"); m_Directory[1]->SetPath("?"); m_Directory[0]->m_bIsFolder = true; m_Directory[1]->m_bIsFolder = true; bCheckShareConnectivity = true; m_loadType = KEEP_IN_MEMORY; } CGUIWindowFileManager::~CGUIWindowFileManager(void) { delete m_Directory[0]; delete m_Directory[1]; delete m_vecItems[0]; delete m_vecItems[1]; } bool CGUIWindowFileManager::OnAction(const CAction &action) { int list = GetFocusedList(); if (list >= 0 && list <= 1) { int item; // the non-contextual menu can be called at any time if (action.GetID() == ACTION_CONTEXT_MENU && m_vecItems[list]->Size() == 0) { OnPopupMenu(list,-1, false); return true; } if (action.GetID() == ACTION_DELETE_ITEM) { if (CanDelete(list)) { bool bDeselect = SelectItem(list, item); OnDelete(list); if (bDeselect) m_vecItems[list]->Get(item)->Select(false); } return true; } if (action.GetID() == ACTION_COPY_ITEM) { if (CanCopy(list)) { bool bDeselect = SelectItem(list, item); OnCopy(list); if (bDeselect) m_vecItems[list]->Get(item)->Select(false); } return true; } if (action.GetID() == ACTION_MOVE_ITEM) { if (CanMove(list)) { bool bDeselect = SelectItem(list, item); OnMove(list); if (bDeselect) m_vecItems[list]->Get(item)->Select(false); } return true; } if (action.GetID() == ACTION_RENAME_ITEM) { if (CanRename(list)) { bool bDeselect = SelectItem(list, item); OnRename(list); if (bDeselect) m_vecItems[list]->Get(item)->Select(false); } return true; } if (action.GetID() == ACTION_PARENT_DIR) { GoParentFolder(list); return true; } if (action.GetID() == ACTION_PLAYER_PLAY) { #ifdef HAS_DVD_DRIVE if (m_vecItems[list]->Get(GetSelectedItem(list))->IsDVD()) return MEDIA_DETECT::CAutorun::PlayDiscAskResume(m_vecItems[list]->Get(GetSelectedItem(list))->GetPath()); #endif } } return CGUIWindow::OnAction(action); } bool CGUIWindowFileManager::OnBack(int actionID) { int list = GetFocusedList(); if (list >= 0 && list <= 1 && actionID == ACTION_NAV_BACK && !m_vecItems[list]->IsVirtualDirectoryRoot()) { GoParentFolder(list); return true; } return CGUIWindow::OnBack(actionID); } bool CGUIWindowFileManager::OnMessage(CGUIMessage& message) { switch ( message.GetMessage() ) { case GUI_MSG_NOTIFY_ALL: { // Message is received even if window is inactive if (message.GetParam1() == GUI_MSG_WINDOW_RESET) { m_Directory[0]->SetPath("?"); m_Directory[1]->SetPath("?"); m_Directory[0]->m_bIsFolder = true; m_Directory[1]->m_bIsFolder = true; return true; } // handle removable media if (message.GetParam1() == GUI_MSG_REMOVED_MEDIA) { for (int i = 0; i < 2; i++) { if (m_Directory[i]->IsVirtualDirectoryRoot() && IsActive()) { int iItem = GetSelectedItem(i); Update(i, m_Directory[i]->GetPath()); CONTROL_SELECT_ITEM(CONTROL_LEFT_LIST + i, iItem); } else if (m_Directory[i]->IsRemovable() && !m_rootDir.IsInSource(m_Directory[i]->GetPath())) { // if (IsActive()) Update(i, ""); else m_Directory[i]->SetPath(""); } } return true; } else if (message.GetParam1()==GUI_MSG_UPDATE_SOURCES) { // State of the sources changed, so update our view for (int i = 0; i < 2; i++) { if (m_Directory[i]->IsVirtualDirectoryRoot() && IsActive()) { int iItem = GetSelectedItem(i); Update(i, m_Directory[i]->GetPath()); CONTROL_SELECT_ITEM(CONTROL_LEFT_LIST + i, iItem); } } return true; } else if (message.GetParam1()==GUI_MSG_UPDATE && IsActive()) { Refresh(); return true; } } break; case GUI_MSG_PLAYBACK_STARTED: case GUI_MSG_PLAYBACK_ENDED: case GUI_MSG_PLAYBACK_STOPPED: case GUI_MSG_PLAYLIST_CHANGED: case GUI_MSG_PLAYLISTPLAYER_STOPPED: case GUI_MSG_PLAYLISTPLAYER_STARTED: case GUI_MSG_PLAYLISTPLAYER_CHANGED: { // send a notify all to all controls on this window CGUIMessage msg(GUI_MSG_NOTIFY_ALL, GetID(), 0, GUI_MSG_REFRESH_LIST); OnMessage(msg); break; } case GUI_MSG_WINDOW_DEINIT: { CGUIWindow::OnMessage(message); ClearFileItems(0); ClearFileItems(1); return true; } break; case GUI_MSG_WINDOW_INIT: { SetInitialPath(message.GetStringParam()); message.SetStringParam(""); return CGUIWindow::OnMessage(message); } break; case GUI_MSG_CLICKED: { int iControl = message.GetSenderId(); if (iControl == CONTROL_LEFT_LIST || iControl == CONTROL_RIGHT_LIST) // list/thumb control { // get selected item int list = iControl - CONTROL_LEFT_LIST; int iItem = GetSelectedItem(list); int iAction = message.GetParam1(); // iItem is checked for validity inside these routines if (iAction == ACTION_HIGHLIGHT_ITEM || iAction == ACTION_MOUSE_LEFT_CLICK) { OnMark(list, iItem); if (!CServiceBroker::GetInputManager().IsMouseActive()) { //move to next item CGUIMessage msg(GUI_MSG_ITEM_SELECT, GetID(), iControl, iItem + 1); CServiceBroker::GetGUI()->GetWindowManager().SendMessage(msg); } } else if (iAction == ACTION_SELECT_ITEM || iAction == ACTION_MOUSE_DOUBLE_CLICK) { OnClick(list, iItem); } else if (iAction == ACTION_CONTEXT_MENU || iAction == ACTION_MOUSE_RIGHT_CLICK) { OnPopupMenu(list, iItem); } } } break; // prevent touch/gesture unfocussing .. case GUI_MSG_GESTURE_NOTIFY: case GUI_MSG_UNFOCUS_ALL: return true; } return CGUIWindow::OnMessage(message); } void CGUIWindowFileManager::OnSort(int iList) { using namespace KODI::PLATFORM::FILESYSTEM; // always sort the list by label in ascending order for (int i = 0; i < m_vecItems[iList]->Size(); i++) { CFileItemPtr pItem = m_vecItems[iList]->Get(i); if (pItem->m_bIsFolder && (!pItem->m_dwSize || pItem->IsPath("add"))) pItem->SetLabel2(""); else pItem->SetFileSizeLabel(); // Set free space on disc if (pItem->m_bIsShareOrDrive) { if (pItem->IsHD()) { std::error_code ec; auto freeSpace = space(pItem->GetPath(), ec); if (ec.value() == 0) { pItem->m_dwSize = freeSpace.free; pItem->SetFileSizeLabel(); } } else if (pItem->IsDVD() && CServiceBroker::GetMediaManager().IsDiscInDrive()) { std::error_code ec; auto freeSpace = space(pItem->GetPath(), ec); if (ec.value() == 0) { pItem->m_dwSize = freeSpace.capacity; pItem->SetFileSizeLabel(); } } } // if (pItem->m_bIsShareOrDrive) } m_vecItems[iList]->Sort(SortByLabel, SortOrderAscending); } void CGUIWindowFileManager::ClearFileItems(int iList) { CGUIMessage msg(GUI_MSG_LABEL_RESET, GetID(), iList + CONTROL_LEFT_LIST); CServiceBroker::GetGUI()->GetWindowManager().SendMessage(msg); m_vecItems[iList]->Clear(); // will clean up everything } void CGUIWindowFileManager::UpdateButtons() { // update our current directory labels std::string strDir = CURL(m_Directory[0]->GetPath()).GetWithoutUserDetails(); if (strDir.empty()) { SET_CONTROL_LABEL(CONTROL_CURRENTDIRLABEL_LEFT,g_localizeStrings.Get(20108)); } else { SET_CONTROL_LABEL(CONTROL_CURRENTDIRLABEL_LEFT, strDir); } strDir = CURL(m_Directory[1]->GetPath()).GetWithoutUserDetails(); if (strDir.empty()) { SET_CONTROL_LABEL(CONTROL_CURRENTDIRLABEL_RIGHT,g_localizeStrings.Get(20108)); } else { SET_CONTROL_LABEL(CONTROL_CURRENTDIRLABEL_RIGHT, strDir); } // update the number of items in each list UpdateItemCounts(); } void CGUIWindowFileManager::UpdateItemCounts() { for (int i = 0; i < 2; i++) { unsigned int selectedCount = 0; unsigned int totalCount = 0; int64_t selectedSize = 0; for (int j = 0; j < m_vecItems[i]->Size(); j++) { CFileItemPtr item = m_vecItems[i]->Get(j); if (item->IsParentFolder()) continue; if (item->IsSelected()) { selectedCount++; selectedSize += item->m_dwSize; } totalCount++; } std::string items; if (selectedCount > 0) items = StringUtils::Format("{}/{} {} ({})", selectedCount, totalCount, g_localizeStrings.Get(127), StringUtils::SizeToString(selectedSize)); else items = StringUtils::Format("{} {}", totalCount, g_localizeStrings.Get(127)); SET_CONTROL_LABEL(CONTROL_NUMFILES_LEFT + i, items); } } bool CGUIWindowFileManager::Update(int iList, const std::string &strDirectory) { if (m_updating) { CLog::Log(LOGWARNING, "CGUIWindowFileManager::Update - updating in progress"); return true; } CUpdateGuard ug(m_updating); // get selected item int iItem = GetSelectedItem(iList); std::string strSelectedItem = ""; if (iItem >= 0 && iItem < m_vecItems[iList]->Size()) { CFileItemPtr pItem = m_vecItems[iList]->Get(iItem); if (!pItem->IsParentFolder()) { GetDirectoryHistoryString(pItem.get(), strSelectedItem); m_history[iList].SetSelectedItem(strSelectedItem, m_Directory[iList]->GetPath()); } } std::string strOldDirectory=m_Directory[iList]->GetPath(); m_Directory[iList]->SetPath(strDirectory); CFileItemList items; if (!GetDirectory(iList, m_Directory[iList]->GetPath(), items)) { if (strDirectory != strOldDirectory && GetDirectory(iList, strOldDirectory, items)) m_Directory[iList]->SetPath(strOldDirectory); // Fallback to old (previous) path) else Update(iList, ""); // Fallback to root return false; } m_history[iList].SetSelectedItem(strSelectedItem, strOldDirectory); ClearFileItems(iList); m_vecItems[iList]->Append(items); m_vecItems[iList]->SetPath(items.GetPath()); std::string strParentPath; URIUtils::GetParentPath(strDirectory, strParentPath); if (strDirectory.empty() && (m_vecItems[iList]->Size() == 0 || CServiceBroker::GetSettingsComponent()->GetSettings()->GetBool(CSettings::SETTING_FILELISTS_SHOWADDSOURCEBUTTONS))) { // add 'add source button' const std::string& strLabel = g_localizeStrings.Get(1026); CFileItemPtr pItem(new CFileItem(strLabel)); pItem->SetPath("add"); pItem->SetArt("icon", "DefaultAddSource.png"); pItem->SetLabel(strLabel); pItem->SetLabelPreformatted(true); pItem->m_bIsFolder = true; pItem->SetSpecialSort(SortSpecialOnBottom); m_vecItems[iList]->Add(pItem); } else if (items.IsEmpty() || CServiceBroker::GetSettingsComponent()->GetSettings()->GetBool(CSettings::SETTING_FILELISTS_SHOWPARENTDIRITEMS)) { CFileItemPtr pItem(new CFileItem("..")); pItem->SetPath(m_rootDir.IsSource(strDirectory) ? "" : strParentPath); pItem->m_bIsFolder = true; pItem->m_bIsShareOrDrive = false; m_vecItems[iList]->AddFront(pItem, 0); } m_strParentPath[iList] = (m_rootDir.IsSource(strDirectory) ? "" : strParentPath); if (strDirectory.empty()) { CFileItemPtr pItem(new CFileItem("special://profile/", true)); pItem->SetLabel(g_localizeStrings.Get(20070)); pItem->SetArt("thumb", "DefaultFolder.png"); pItem->SetLabelPreformatted(true); m_vecItems[iList]->Add(pItem); #ifdef TARGET_DARWIN_EMBEDDED CFileItemPtr iItem(new CFileItem("special://envhome/Documents/Inbox", true)); iItem->SetLabel("Inbox"); iItem->SetArt("thumb", "DefaultFolder.png"); iItem->SetLabelPreformatted(true); m_vecItems[iList]->Add(iItem); #endif } // if we have a .tbn file, use itself as the thumb for (int i = 0; i < m_vecItems[iList]->Size(); i++) { CFileItemPtr pItem = m_vecItems[iList]->Get(i); if (pItem->IsHD() && URIUtils::HasExtension(pItem->GetPath(), ".tbn")) { pItem->SetArt("thumb", pItem->GetPath()); } } m_vecItems[iList]->FillInDefaultIcons(); OnSort(iList); UpdateButtons(); int item = 0; strSelectedItem = m_history[iList].GetSelectedItem(m_Directory[iList]->GetPath()); for (int i = 0; i < m_vecItems[iList]->Size(); ++i) { CFileItemPtr pItem = m_vecItems[iList]->Get(i); std::string strHistory; GetDirectoryHistoryString(pItem.get(), strHistory); if (strHistory == strSelectedItem) { item = i; break; } } UpdateControl(iList, item); return true; } void CGUIWindowFileManager::OnClick(int iList, int iItem) { if ( iList < 0 || iList >= 2) return ; if ( iItem < 0 || iItem >= m_vecItems[iList]->Size() ) return ; CFileItemPtr pItem = m_vecItems[iList]->Get(iItem); if (pItem->GetPath() == "add" && pItem->GetLabel() == g_localizeStrings.Get(1026)) // 'add source button' in empty root { if (CGUIDialogMediaSource::ShowAndAddMediaSource("files")) { m_rootDir.SetSources(*CMediaSourceSettings::GetInstance().GetSources("files")); Update(0,m_Directory[0]->GetPath()); Update(1,m_Directory[1]->GetPath()); } return; } if (!pItem->m_bIsFolder && pItem->IsFileFolder(EFILEFOLDER_MASK_ALL)) { XFILE::IFileDirectory *pFileDirectory = NULL; pFileDirectory = XFILE::CFileDirectoryFactory::Create(pItem->GetURL(), pItem.get(), ""); if(pFileDirectory) pItem->m_bIsFolder = true; else if(pItem->m_bIsFolder) pItem->m_bIsFolder = false; delete pFileDirectory; } if (pItem->m_bIsFolder) { // save path + drive type because of the possible refresh std::string strPath = pItem->GetPath(); int iDriveType = pItem->m_iDriveType; if ( pItem->m_bIsShareOrDrive ) { if ( !g_passwordManager.IsItemUnlocked( pItem.get(), "files" ) ) { Refresh(); return ; } if ( !HaveDiscOrConnection( strPath, iDriveType ) ) return ; } if (!Update(iList, strPath)) ShowShareErrorMessage(pItem.get()); } else if (pItem->IsZIP() || pItem->IsCBZ()) // mount zip archive { CURL pathToUrl = URIUtils::CreateArchivePath("zip", pItem->GetURL(), ""); Update(iList, pathToUrl.Get()); } else if (pItem->IsRAR() || pItem->IsCBR()) { CURL pathToUrl = URIUtils::CreateArchivePath("rar", pItem->GetURL(), ""); Update(iList, pathToUrl.Get()); } else { OnStart(pItem.get(), ""); return ; } // UpdateButtons(); } //! @todo 2.0: Can this be removed, or should we run without the "special" file directories while // in filemanager view. void CGUIWindowFileManager::OnStart(CFileItem *pItem, const std::string &player) { // start playlists from file manager if (pItem->IsPlayList()) { const std::string& strPlayList = pItem->GetPath(); std::unique_ptr pPlayList(PLAYLIST::CPlayListFactory::Create(strPlayList)); if (nullptr != pPlayList) { if (!pPlayList->Load(strPlayList)) { HELPERS::ShowOKDialogText(CVariant{6}, CVariant{477}); return; } } g_application.ProcessAndStartPlaylist(strPlayList, *pPlayList, PLAYLIST::TYPE_MUSIC); return; } if (pItem->IsAudio() || pItem->IsVideo()) { CServiceBroker::GetPlaylistPlayer().Play(std::make_shared(*pItem), player); return; } if (pItem->IsGame()) { g_application.PlayFile(*pItem, player); return ; } #ifdef HAS_PYTHON if (pItem->IsPythonScript()) { CScriptInvocationManager::GetInstance().ExecuteAsync(pItem->GetPath()); return ; } #endif if (pItem->IsPicture()) { CGUIWindowSlideShow *pSlideShow = CServiceBroker::GetGUI()->GetWindowManager().GetWindow(WINDOW_SLIDESHOW); if (!pSlideShow) return ; const auto& components = CServiceBroker::GetAppComponents(); const auto appPlayer = components.GetComponent(); if (appPlayer->IsPlayingVideo()) g_application.StopPlaying(); pSlideShow->Reset(); pSlideShow->Add(pItem); pSlideShow->Select(pItem->GetPath()); CServiceBroker::GetGUI()->GetWindowManager().ActivateWindow(WINDOW_SLIDESHOW); return; } if (pItem->IsType(".txt") || pItem->IsType(".xml")) CGUIDialogTextViewer::ShowForFile(pItem->GetPath(), true); } bool CGUIWindowFileManager::HaveDiscOrConnection( std::string& strPath, int iDriveType ) { if ( iDriveType == CMediaSource::SOURCE_TYPE_DVD ) { if (!CServiceBroker::GetMediaManager().IsDiscInDrive(strPath)) { HELPERS::ShowOKDialogText(CVariant{218}, CVariant{219}); int iList = GetFocusedList(); int iItem = GetSelectedItem(iList); Update(iList, ""); CONTROL_SELECT_ITEM(iList + CONTROL_LEFT_LIST, iItem); return false; } } else if ( iDriveType == CMediaSource::SOURCE_TYPE_REMOTE ) { //! @todo Handle not connected to a remote share if (!CServiceBroker::GetNetwork().IsConnected()) { HELPERS::ShowOKDialogText(CVariant{220}, CVariant{221}); return false; } } else return true; return true; } void CGUIWindowFileManager::UpdateControl(int iList, int item) { CGUIMessage msg(GUI_MSG_LABEL_BIND, GetID(), iList + CONTROL_LEFT_LIST, item, 0, m_vecItems[iList]); CServiceBroker::GetGUI()->GetWindowManager().SendMessage(msg); } void CGUIWindowFileManager::OnMark(int iList, int iItem) { CFileItemPtr pItem = m_vecItems[iList]->Get(iItem); if (!pItem->m_bIsShareOrDrive) { if (!pItem->IsParentFolder()) { // MARK file pItem->Select(!pItem->IsSelected()); } } UpdateItemCounts(); // UpdateButtons(); } void CGUIWindowFileManager::OnCopy(int iList) { if (!CGUIDialogYesNo::ShowAndGetInput(CVariant{120}, CVariant{123})) return; AddJob(new CFileOperationJob(CFileOperationJob::ActionCopy, *m_vecItems[iList], m_Directory[1 - iList]->GetPath(), true, 16201, 16202)); } void CGUIWindowFileManager::OnMove(int iList) { if (!CGUIDialogYesNo::ShowAndGetInput(CVariant{121}, CVariant{124})) return; AddJob(new CFileOperationJob(CFileOperationJob::ActionMove, *m_vecItems[iList], m_Directory[1 - iList]->GetPath(), true, 16203, 16204)); } void CGUIWindowFileManager::OnDelete(int iList) { if (!CGUIDialogYesNo::ShowAndGetInput(CVariant{122}, CVariant{125})) return; AddJob(new CFileOperationJob(CFileOperationJob::ActionDelete, *m_vecItems[iList], m_Directory[iList]->GetPath(), true, 16205, 16206)); } void CGUIWindowFileManager::OnRename(int iList) { std::string strFile; for (int i = 0; i < m_vecItems[iList]->Size();++i) { CFileItemPtr pItem = m_vecItems[iList]->Get(i); if (pItem->IsSelected()) { strFile = pItem->GetPath(); break; } } CFileUtils::RenameFile(strFile); Refresh(iList); } void CGUIWindowFileManager::OnSelectAll(int iList) { for (int i = 0; i < m_vecItems[iList]->Size();++i) { CFileItemPtr pItem = m_vecItems[iList]->Get(i); if (!pItem->IsParentFolder()) { pItem->Select(true); } } } void CGUIWindowFileManager::OnNewFolder(int iList) { std::string strNewFolder = ""; if (CGUIKeyboardFactory::ShowAndGetInput(strNewFolder, CVariant{g_localizeStrings.Get(16014)}, false)) { std::string strNewPath = m_Directory[iList]->GetPath(); URIUtils::AddSlashAtEnd(strNewPath); strNewPath += strNewFolder; CDirectory::Create(strNewPath); Refresh(iList); // select the new folder for (int i=0; iSize(); ++i) { CFileItemPtr pItem=m_vecItems[iList]->Get(i); std::string strPath=pItem->GetPath(); URIUtils::RemoveSlashAtEnd(strPath); if (strPath==strNewPath) { CONTROL_SELECT_ITEM(iList + CONTROL_LEFT_LIST, i); break; } } } } void CGUIWindowFileManager::Refresh(int iList) { int nSel = GetSelectedItem(iList); // update the list views Update(iList, m_Directory[iList]->GetPath()); while (nSel > m_vecItems[iList]->Size()) nSel--; CONTROL_SELECT_ITEM(iList + CONTROL_LEFT_LIST, nSel); } void CGUIWindowFileManager::Refresh() { int iList = GetFocusedList(); int nSel = GetSelectedItem(iList); // update the list views Update(0, m_Directory[0]->GetPath()); Update(1, m_Directory[1]->GetPath()); while (nSel > m_vecItems[iList]->Size()) nSel--; CONTROL_SELECT_ITEM(iList + CONTROL_LEFT_LIST, nSel); } int CGUIWindowFileManager::GetSelectedItem(int iControl) { if (iControl < 0 || iControl > 1 || m_vecItems[iControl]->IsEmpty()) return -1; CGUIMessage msg(GUI_MSG_ITEM_SELECTED, GetID(), iControl + CONTROL_LEFT_LIST); if (OnMessage(msg)) return msg.GetParam1(); return -1; } void CGUIWindowFileManager::GoParentFolder(int iList) { CURL url(m_Directory[iList]->GetPath()); if (url.IsProtocol("rar") || url.IsProtocol("zip")) { // check for step-below, if, unmount rar if (url.GetFileName().empty()) if (url.IsProtocol("zip")) g_ZipManager.release(m_Directory[iList]->GetPath()); // release resources } std::string strPath(m_strParentPath[iList]), strOldPath(m_Directory[iList]->GetPath()); Update(iList, strPath); } /// \brief Build a directory history string /// \param pItem Item to build the history string from /// \param strHistoryString History string build as return value void CGUIWindowFileManager::GetDirectoryHistoryString(const CFileItem* pItem, std::string& strHistoryString) { if (pItem->m_bIsShareOrDrive) { // We are in the virtual directory // History string of the DVD drive // must be handled separately if (pItem->m_iDriveType == CMediaSource::SOURCE_TYPE_DVD) { // Remove disc label from item label // and use as history string, m_strPath // can change for new discs std::string strLabel = pItem->GetLabel(); size_t nPosOpen = strLabel.find('('); size_t nPosClose = strLabel.rfind(')'); if (nPosOpen != std::string::npos && nPosClose != std::string::npos && nPosClose > nPosOpen) { strLabel.erase(nPosOpen + 1, (nPosClose) - (nPosOpen + 1)); strHistoryString = strLabel; } else strHistoryString = strLabel; } else { // Other items in virtual directory strHistoryString = pItem->GetLabel() + pItem->GetPath(); URIUtils::RemoveSlashAtEnd(strHistoryString); } } else { // Normal directory items strHistoryString = pItem->GetPath(); URIUtils::RemoveSlashAtEnd(strHistoryString); } } bool CGUIWindowFileManager::GetDirectory(int iList, const std::string &strDirectory, CFileItemList &items) { CURL pathToUrl(strDirectory); CGetDirectoryItems getItems(m_rootDir, pathToUrl, items); if (!CGUIDialogBusy::Wait(&getItems, 100, true)) { return false; } return getItems.m_result; } bool CGUIWindowFileManager::CanRename(int iList) { //! @todo Renaming of shares (requires writing to sources.xml) //! this might be able to be done via the webserver code stuff... if (m_Directory[iList]->IsVirtualDirectoryRoot()) return false; if (m_Directory[iList]->IsReadOnly()) return false; return true; } bool CGUIWindowFileManager::CanCopy(int iList) { // can't copy if the destination is not writeable, or if the source is a share! //! @todo Perhaps if the source is removeable media (DVD/CD etc.) we could //! put ripping/backup in here. if (!CUtil::SupportsReadFileOperations(m_Directory[iList]->GetPath())) return false; if (m_Directory[iList]->IsVirtualDirectoryRoot()) return false; if (m_Directory[1 - iList]->IsVirtualDirectoryRoot()) return false; if (m_Directory[iList]->IsVirtualDirectoryRoot()) return false; if (m_Directory[1 -iList]->IsReadOnly()) return false; return true; } bool CGUIWindowFileManager::CanMove(int iList) { // can't move if the destination is not writeable, or if the source is a share or not writeable! if (m_Directory[0]->IsVirtualDirectoryRoot() || m_Directory[0]->IsReadOnly()) return false; if (m_Directory[1]->IsVirtualDirectoryRoot() || m_Directory[1]->IsReadOnly()) return false; return true; } bool CGUIWindowFileManager::CanDelete(int iList) { if (m_Directory[iList]->IsVirtualDirectoryRoot()) return false; if (m_Directory[iList]->IsReadOnly()) return false; return true; } bool CGUIWindowFileManager::CanNewFolder(int iList) { if (m_Directory[iList]->IsVirtualDirectoryRoot()) return false; if (m_Directory[iList]->IsReadOnly()) return false; return true; } int CGUIWindowFileManager::NumSelected(int iList) { int iSelectedItems = 0; for (int iItem = 0; iItem < m_vecItems[iList]->Size(); ++iItem) { if (m_vecItems[iList]->Get(iItem)->IsSelected()) iSelectedItems++; } return iSelectedItems; } int CGUIWindowFileManager::GetFocusedList() const { return GetFocusedControlID() - CONTROL_LEFT_LIST; } void CGUIWindowFileManager::OnPopupMenu(int list, int item, bool bContextDriven /* = true */) { if (list < 0 || list >= 2) return ; bool bDeselect = SelectItem(list, item); // calculate the position for our menu float posX = 200; float posY = 100; const CGUIControl *pList = GetControl(CONTROL_LEFT_LIST + list); if (pList) { posX = pList->GetXPosition() + pList->GetWidth() / 2; posY = pList->GetYPosition() + pList->GetHeight() / 2; } CFileItemPtr pItem = m_vecItems[list]->Get(item); if (!pItem.get()) return; if (m_Directory[list]->IsVirtualDirectoryRoot()) { if (item < 0) { //! @todo We should add the option here for shares to be added if there aren't any return ; } // and do the popup menu if (CGUIDialogContextMenu::SourcesMenu("files", pItem, posX, posY)) { m_rootDir.SetSources(*CMediaSourceSettings::GetInstance().GetSources("files")); if (m_Directory[1 - list]->IsVirtualDirectoryRoot()) Refresh(); else Refresh(list); return ; } pItem->Select(false); return ; } const CPlayerCoreFactory &playerCoreFactory = CServiceBroker::GetPlayerCoreFactory(); // popup the context menu bool showEntry = false; if (item >= m_vecItems[list]->Size()) item = -1; if (item >= 0) showEntry=(!pItem->IsParentFolder() || (pItem->IsParentFolder() && m_vecItems[list]->GetSelectedCount()>0)); // determine available players std::vectorplayers; playerCoreFactory.GetPlayers(*pItem, players); // add the needed buttons CContextButtons choices; if (item >= 0) { //The ".." item is not selectable. Take that into account when figuring out if all items are selected int notSelectable = CServiceBroker::GetSettingsComponent()->GetSettings()->GetBool(CSettings::SETTING_FILELISTS_SHOWPARENTDIRITEMS) ? 1 : 0; if (NumSelected(list) < m_vecItems[list]->Size() - notSelectable) choices.Add(CONTROL_BTNSELECTALL, 188); // SelectAll if (!pItem->IsParentFolder()) choices.Add(CONTROL_BTNFAVOURITES, CServiceBroker::GetFavouritesService().IsFavourited(*pItem.get(), GetID()) ? 14077 : 14076); // Add/Remove Favourite if (players.size() > 1) choices.Add(CONTROL_BTNPLAYWITH, 15213); if (CanRename(list) && !pItem->IsParentFolder()) choices.Add(CONTROL_BTNRENAME, 118); if (CanDelete(list) && showEntry) choices.Add(CONTROL_BTNDELETE, 117); if (CanCopy(list) && showEntry) choices.Add(CONTROL_BTNCOPY, 115); if (CanMove(list) && showEntry) choices.Add(CONTROL_BTNMOVE, 116); } if (CanNewFolder(list)) choices.Add(CONTROL_BTNNEWFOLDER, 20309); if (item >= 0 && pItem->m_bIsFolder && !pItem->IsParentFolder()) choices.Add(CONTROL_BTNCALCSIZE, 13393); choices.Add(CONTROL_BTNSWITCHMEDIA, 523); if (CServiceBroker::GetJobManager()->IsProcessing("filemanager")) choices.Add(CONTROL_BTNCANCELJOB, 167); if (!pItem->m_bIsFolder) choices.Add(CONTROL_BTNVIEW, 39104); int btnid = CGUIDialogContextMenu::ShowAndGetChoice(choices); if (btnid == CONTROL_BTNSELECTALL) { OnSelectAll(list); bDeselect=false; } if (btnid == CONTROL_BTNFAVOURITES) { CServiceBroker::GetFavouritesService().AddOrRemove(*pItem.get(), GetID()); return; } if (btnid == CONTROL_BTNPLAYWITH) { std::vectorplayers; playerCoreFactory.GetPlayers(*pItem, players); std::string player = playerCoreFactory.SelectPlayerDialog(players); if (!player.empty()) OnStart(pItem.get(), player); } if (btnid == CONTROL_BTNRENAME) OnRename(list); if (btnid == CONTROL_BTNDELETE) OnDelete(list); if (btnid == CONTROL_BTNCOPY) OnCopy(list); if (btnid == CONTROL_BTNMOVE) OnMove(list); if (btnid == CONTROL_BTNNEWFOLDER) OnNewFolder(list); if (btnid == CONTROL_BTNCALCSIZE) { // setup the progress dialog, and show it CGUIDialogProgress *progress = CServiceBroker::GetGUI()->GetWindowManager().GetWindow(WINDOW_DIALOG_PROGRESS); if (progress) { progress->SetHeading(CVariant{13394}); for (int i=0; i < 3; i++) progress->SetLine(i, CVariant{""}); progress->Open(); } // Calculate folder size for each selected item for (int i=0; iSize(); ++i) { CFileItemPtr pItem2=m_vecItems[list]->Get(i); if (pItem2->m_bIsFolder && pItem2->IsSelected()) { int64_t folderSize = CalculateFolderSize(pItem2->GetPath(), progress); if (folderSize >= 0) { pItem2->m_dwSize = folderSize; if (folderSize == 0) pItem2->SetLabel2(StringUtils::SizeToString(folderSize)); else pItem2->SetFileSizeLabel(); } } } if (progress) progress->Close(); } if (btnid == CONTROL_BTNSWITCHMEDIA) { CGUIDialogContextMenu::SwitchMedia("files", m_vecItems[list]->GetPath()); return; } if (btnid == CONTROL_BTNCANCELJOB) CancelJobs(); if (btnid == CONTROL_BTNVIEW) CGUIDialogTextViewer::ShowForFile(pItem->GetPath(), true); if (bDeselect && item >= 0 && item < m_vecItems[list]->Size()) { // deselect item as we didn't do anything pItem->Select(false); } } // Highlights the item in the list under the cursor // returns true if we should deselect the item, false otherwise bool CGUIWindowFileManager::SelectItem(int list, int &item) { // get the currently selected item in the list item = GetSelectedItem(list); // select the item if we need to if (item > -1 && !NumSelected(list) && !m_vecItems[list]->Get(item)->IsParentFolder()) { m_vecItems[list]->Get(item)->Select(true); return true; } return false; } // recursively calculates the selected folder size int64_t CGUIWindowFileManager::CalculateFolderSize(const std::string &strDirectory, CGUIDialogProgress *pProgress) { const CURL pathToUrl(strDirectory); if (pProgress) { // update our progress control pProgress->Progress(); pProgress->SetLine(1, strDirectory); if (pProgress->IsCanceled()) return -1; } // start by calculating the size of the files in this folder... int64_t totalSize = 0; CFileItemList items; CVirtualDirectory rootDir; rootDir.SetSources(*CMediaSourceSettings::GetInstance().GetSources("files")); rootDir.GetDirectory(pathToUrl, items, false, false); for (int i=0; i < items.Size(); i++) { if (items[i]->m_bIsFolder && !items[i]->IsParentFolder()) // folder { int64_t folderSize = CalculateFolderSize(items[i]->GetPath(), pProgress); if (folderSize < 0) return -1; totalSize += folderSize; } else // file totalSize += items[i]->m_dwSize; } return totalSize; } void CGUIWindowFileManager::OnJobComplete(unsigned int jobID, bool success, CJob *job) { if(!success) { CFileOperationJob* fileJob = static_cast(job); HELPERS::ShowOKDialogLines(CVariant{fileJob->GetHeading()}, CVariant{fileJob->GetLine()}, CVariant{16200}, CVariant{0}); } if (IsActive()) { CGUIMessage msg(GUI_MSG_NOTIFY_ALL, GetID(), 0, GUI_MSG_UPDATE); CServiceBroker::GetAppMessenger()->SendGUIMessage(msg, GetID(), false); } CJobQueue::OnJobComplete(jobID, success, job); } void CGUIWindowFileManager::ShowShareErrorMessage(CFileItem* pItem) { int idMessageText = 0; CURL url(pItem->GetPath()); if (url.IsProtocol("smb") && url.GetHostName().empty()) // smb workgroup idMessageText = 15303; // Workgroup not found else if (pItem->m_iDriveType == CMediaSource::SOURCE_TYPE_REMOTE || URIUtils::IsRemote(pItem->GetPath())) idMessageText = 15301; // Could not connect to network server else idMessageText = 15300; // Path not found or invalid HELPERS::ShowOKDialogText(CVariant{220}, CVariant{idMessageText}); } void CGUIWindowFileManager::OnInitWindow() { bool bResult0 = Update(0, m_Directory[0]->GetPath()); bool bResult1 = Update(1, m_Directory[1]->GetPath()); CGUIWindow::OnInitWindow(); if (!bCheckShareConnectivity) { bCheckShareConnectivity = true; //reset CFileItem pItem(strCheckSharePath, true); ShowShareErrorMessage(&pItem); //show the error message after window is loaded! Update(0,""); // reset view to root } else if (!bResult0) { ShowShareErrorMessage(m_Directory[0]); //show the error message after window is loaded! Update(0, ""); // reset view to root } if (!bResult1) { ShowShareErrorMessage(m_Directory[1]); //show the error message after window is loaded! Update(1, ""); // reset view to root } } void CGUIWindowFileManager::SetInitialPath(const std::string &path) { // check for a passed destination path std::string strDestination = path; m_rootDir.SetSources(*CMediaSourceSettings::GetInstance().GetSources("files")); if (!strDestination.empty()) { CLog::Log(LOGINFO, "Attempting to quickpath to: {}", strDestination); } // otherwise, is this the first time accessing this window? else if (m_Directory[0]->GetPath() == "?") { m_Directory[0]->SetPath(strDestination = CMediaSourceSettings::GetInstance().GetDefaultSource("files")); CLog::Log(LOGINFO, "Attempting to default to: {}", strDestination); } // try to open the destination path if (!strDestination.empty()) { // open root if (StringUtils::EqualsNoCase(strDestination, "$ROOT")) { m_Directory[0]->SetPath(""); CLog::Log(LOGINFO, " Success! Opening root listing."); } else { // default parameters if the jump fails m_Directory[0]->SetPath(""); bool bIsSourceName = false; VECSOURCES shares; m_rootDir.GetSources(shares); int iIndex = CUtil::GetMatchingSource(strDestination, shares, bIsSourceName); if (iIndex > -1 #if defined(TARGET_DARWIN_EMBEDDED) || URIUtils::PathHasParent(strDestination, "special://envhome/Documents/Inbox/") #endif || URIUtils::PathHasParent(strDestination, "special://profile/")) { // set current directory to matching share std::string path; if (bIsSourceName && iIndex < (int)shares.size()) path = shares[iIndex].strPath; else path = strDestination; URIUtils::RemoveSlashAtEnd(path); m_Directory[0]->SetPath(path); CLog::Log(LOGINFO, " Success! Opened destination path: {}", strDestination); // outside call: check the share for connectivity bCheckShareConnectivity = Update(0, m_Directory[0]->GetPath()); if(!bCheckShareConnectivity) strCheckSharePath = m_Directory[0]->GetPath(); } else { CLog::Log(LOGERROR, " Failed! Destination parameter ({}) does not match a valid share!", strDestination); } } } if (m_Directory[1]->GetPath() == "?") m_Directory[1]->SetPath(""); } const CFileItem& CGUIWindowFileManager::CurrentDirectory(int indx) const { return *m_Directory[indx]; }