From 44bcbef0c1c6dbcab76974ccc7df19d107c0cd7e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20M=C3=BCller?= Date: Sun, 9 Jul 2023 12:45:26 +0200 Subject: [PATCH] Support deleting/renaming demo folders, improve demo popups Support deleting and renaming folders in the demo browser. Only empty folders can be deleted. Ensure only files and folders in the save directory can be deleted and renamed. Also check if a folder with a demo rename/render filename already exists. Fix broken `m_DemolistSelectedIsDir` checks by using `m_vDemos[m_DemolistSelectedIndex].m_IsDir` instead. Append `.mp4` file extension only internally instead of appending it to the render filename lineinput, as this causes the file extension to appear when rendering doesn't start due an error message. Use more efficient `FileExists` instead of `FindFile` to check if rendered demo video file already exists. Change popup preconditions to assertions. --- src/game/client/components/menus.cpp | 93 +++++++++++++---------- src/game/client/components/menus.h | 4 +- src/game/client/components/menus_demo.cpp | 80 +++++++++++++------ 3 files changed, 110 insertions(+), 67 deletions(-) diff --git a/src/game/client/components/menus.cpp b/src/game/client/components/menus.cpp index 412850ac2..e7566b339 100644 --- a/src/game/client/components/menus.cpp +++ b/src/game/client/components/menus.cpp @@ -1214,7 +1214,8 @@ int CMenus::Render() } else if(m_Popup == POPUP_RENAME_DEMO) { - pTitle = Localize("Rename demo"); + dbg_assert(m_DemolistSelectedIndex >= 0, "m_DemolistSelectedIndex invalid for POPUP_RENAME_DEMO"); + pTitle = m_vDemos[m_DemolistSelectedIndex].m_IsDir ? Localize("Rename folder") : Localize("Rename demo"); } #if defined(CONF_VIDEORECORDER) else if(m_Popup == POPUP_RENDER_DEMO) @@ -1517,31 +1518,32 @@ int CMenus::Render() { m_Popup = POPUP_NONE; // rename demo - if(m_DemolistSelectedIndex >= 0 && !m_DemolistSelectedIsDir) - { - char aBufOld[IO_MAX_PATH_LENGTH]; - str_format(aBufOld, sizeof(aBufOld), "%s/%s", m_aCurrentDemoFolder, m_vDemos[m_DemolistSelectedIndex].m_aFilename); - char aBufNew[IO_MAX_PATH_LENGTH]; - str_format(aBufNew, sizeof(aBufNew), "%s/%s", m_aCurrentDemoFolder, m_DemoRenameInput.GetString()); - if(!str_endswith(aBufNew, ".demo")) - str_append(aBufNew, ".demo"); + char aBufOld[IO_MAX_PATH_LENGTH]; + str_format(aBufOld, sizeof(aBufOld), "%s/%s", m_aCurrentDemoFolder, m_vDemos[m_DemolistSelectedIndex].m_aFilename); + char aBufNew[IO_MAX_PATH_LENGTH]; + str_format(aBufNew, sizeof(aBufNew), "%s/%s", m_aCurrentDemoFolder, m_DemoRenameInput.GetString()); + if(!m_vDemos[m_DemolistSelectedIndex].m_IsDir && !str_endswith(aBufNew, ".demo")) + str_append(aBufNew, ".demo"); - if(Storage()->FileExists(aBufNew, m_vDemos[m_DemolistSelectedIndex].m_StorageType)) - { - PopupMessage(Localize("Error"), Localize("A demo with this name already exists"), Localize("Ok"), POPUP_RENAME_DEMO); - } - else if(Storage()->RenameFile(aBufOld, aBufNew, m_vDemos[m_DemolistSelectedIndex].m_StorageType)) - { - str_copy(m_aCurrentDemoSelectionName, m_DemoRenameInput.GetString()); - if(str_endswith(m_aCurrentDemoSelectionName, ".demo")) - m_aCurrentDemoSelectionName[str_length(m_aCurrentDemoSelectionName) - str_length(".demo")] = '\0'; - DemolistPopulate(); - DemolistOnUpdate(false); - } - else - { - PopupMessage(Localize("Error"), Localize("Unable to rename the demo"), Localize("Ok"), POPUP_RENAME_DEMO); - } + if(Storage()->FileExists(aBufNew, m_vDemos[m_DemolistSelectedIndex].m_StorageType)) + { + PopupMessage(Localize("Error"), Localize("A demo with this name already exists"), Localize("Ok"), POPUP_RENAME_DEMO); + } + else if(Storage()->FolderExists(aBufNew, m_vDemos[m_DemolistSelectedIndex].m_StorageType)) + { + PopupMessage(Localize("Error"), Localize("A folder with this name already exists"), Localize("Ok"), POPUP_RENAME_DEMO); + } + else if(Storage()->RenameFile(aBufOld, aBufNew, m_vDemos[m_DemolistSelectedIndex].m_StorageType)) + { + str_copy(m_aCurrentDemoSelectionName, m_DemoRenameInput.GetString()); + if(!m_vDemos[m_DemolistSelectedIndex].m_IsDir) + fs_split_file_extension(m_DemoRenameInput.GetString(), m_aCurrentDemoSelectionName, sizeof(m_aCurrentDemoSelectionName)); + DemolistPopulate(); + DemolistOnUpdate(false); + } + else + { + PopupMessage(Localize("Error"), m_vDemos[m_DemolistSelectedIndex].m_IsDir ? Localize("Unable to rename the folder") : Localize("Unable to rename the demo"), Localize("Ok"), POPUP_RENAME_DEMO); } } @@ -1558,6 +1560,8 @@ int CMenus::Render() #if defined(CONF_VIDEORECORDER) else if(m_Popup == POPUP_RENDER_DEMO) { + dbg_assert(m_DemolistSelectedIndex >= 0 && !m_vDemos[m_DemolistSelectedIndex].m_IsDir, "m_DemolistSelectedIndex invalid for POPUP_RENDER_DEMO"); + CUIRect Label, TextBox, Ok, Abort, Button; Box.HSplitBottom(20.f, &Box, &Part); @@ -1581,23 +1585,24 @@ int CMenus::Render() if(DoButton_Menu(&s_ButtonOk, Localize("Ok"), 0, &Ok) || UI()->ConsumeHotkey(CUI::HOTKEY_ENTER)) { m_Popup = POPUP_NONE; - // name video - if(m_DemolistSelectedIndex >= 0 && !m_DemolistSelectedIsDir) + // render video + char aVideoPath[IO_MAX_PATH_LENGTH]; + str_format(aVideoPath, sizeof(aVideoPath), "videos/%s", m_DemoRenderInput.GetString()); + if(!str_endswith(aVideoPath, ".mp4")) + str_append(aVideoPath, ".mp4"); + if(Storage()->FolderExists(aVideoPath, IStorage::TYPE_SAVE)) { - if(!str_endswith(m_DemoRenderInput.GetString(), ".mp4")) - m_DemoRenderInput.Append(".mp4"); - char aWholePath[IO_MAX_PATH_LENGTH]; - // store new video filename to origin buffer - if(Storage()->FindFile(m_DemoRenderInput.GetString(), "videos", IStorage::TYPE_ALL, aWholePath, sizeof(aWholePath))) - { - char aMessage[128 + IO_MAX_PATH_LENGTH]; - str_format(aMessage, sizeof(aMessage), Localize("File '%s' already exists, do you want to overwrite it?"), m_DemoRenderInput.GetString()); - PopupConfirm(Localize("Replace video"), aMessage, Localize("Yes"), Localize("No"), &CMenus::PopupConfirmDemoReplaceVideo, POPUP_NONE, &CMenus::DefaultButtonCallback, POPUP_RENDER_DEMO); - } - else - { - PopupConfirmDemoReplaceVideo(); - } + PopupMessage(Localize("Error"), Localize("A folder with this name already exists"), Localize("Ok"), POPUP_RENDER_DEMO); + } + else if(Storage()->FileExists(aVideoPath, IStorage::TYPE_SAVE)) + { + char aMessage[128 + IO_MAX_PATH_LENGTH]; + str_format(aMessage, sizeof(aMessage), Localize("File '%s' already exists, do you want to overwrite it?"), m_DemoRenderInput.GetString()); + PopupConfirm(Localize("Replace video"), aMessage, Localize("Yes"), Localize("No"), &CMenus::PopupConfirmDemoReplaceVideo, POPUP_NONE, &CMenus::DefaultButtonCallback, POPUP_RENDER_DEMO); + } + else + { + PopupConfirmDemoReplaceVideo(); } } Box.HSplitBottom(30.f, &Box, 0); @@ -1789,7 +1794,11 @@ void CMenus::PopupConfirmDemoReplaceVideo() { char aBuf[IO_MAX_PATH_LENGTH]; str_format(aBuf, sizeof(aBuf), "%s/%s", m_aCurrentDemoFolder, m_vDemos[m_DemolistSelectedIndex].m_aFilename); - const char *pError = Client()->DemoPlayer_Render(aBuf, m_vDemos[m_DemolistSelectedIndex].m_StorageType, m_DemoRenderInput.GetString(), m_Speed); + char aVideoName[IO_MAX_PATH_LENGTH]; + str_copy(aVideoName, m_DemoRenderInput.GetString()); + if(!str_endswith(aVideoName, ".mp4")) + str_append(aVideoName, ".mp4"); + const char *pError = Client()->DemoPlayer_Render(aBuf, m_vDemos[m_DemolistSelectedIndex].m_StorageType, aVideoName, m_Speed); m_Speed = 4; if(pError) PopupMessage(Localize("Error"), str_comp(pError, "error loading demo") ? pError : Localize("Error loading demo"), Localize("Ok")); diff --git a/src/game/client/components/menus.h b/src/game/client/components/menus.h index 5170de023..b4af676c2 100644 --- a/src/game/client/components/menus.h +++ b/src/game/client/components/menus.h @@ -322,9 +322,10 @@ protected: char m_aCurrentDemoSelectionName[IO_MAX_PATH_LENGTH]; CLineInputBuffered m_DemoRenameInput; CLineInputBuffered m_DemoSliceInput; +#if defined(CONF_VIDEORECORDER) CLineInputBuffered m_DemoRenderInput; +#endif int m_DemolistSelectedIndex; - bool m_DemolistSelectedIsDir; bool m_DemolistSelectedReveal = false; int m_DemolistStorageType; bool m_DemolistMultipleStorages = false; @@ -428,6 +429,7 @@ protected: void RenderDemoPlayerSliceSavePopup(CUIRect MainView); void RenderDemoList(CUIRect MainView); void PopupConfirmDeleteDemo(); + void PopupConfirmDeleteFolder(); // found in menus_start.cpp void RenderStartMenu(CUIRect MainView); diff --git a/src/game/client/components/menus_demo.cpp b/src/game/client/components/menus_demo.cpp index 701bcfe7f..ce9a5f9fd 100644 --- a/src/game/client/components/menus_demo.cpp +++ b/src/game/client/components/menus_demo.cpp @@ -951,7 +951,7 @@ void CMenus::RenderDemoList(CUIRect MainView) MainView.VMargin(5.0f, &MainView); MainView.HSplitBottom(5.0f, &MainView, 0); MainView.Draw(ColorRGBA(0, 0, 0, 0.15f), IGraphics::CORNER_B, 4.0f); - if(!m_DemolistSelectedIsDir && m_DemolistSelectedIndex >= 0 && m_vDemos[m_DemolistSelectedIndex].m_Valid) + if(m_DemolistSelectedIndex >= 0 && !m_vDemos[m_DemolistSelectedIndex].m_IsDir && m_vDemos[m_DemolistSelectedIndex].m_Valid) { CUIRect Left, Right, Labels; MainView.VMargin(20.0f, &MainView); @@ -1290,36 +1290,51 @@ void CMenus::RenderDemoList(CUIRect MainView) } GameClient()->m_Tooltips.DoToolTip(&s_DirectoryButtonID, &DirectoryButton, Localize("Open the directory that contains the demo files")); - if(m_DemolistSelectedIndex >= 0 && !m_vDemos[m_DemolistSelectedIndex].m_IsDir) + if(m_DemolistSelectedIndex >= 0 && m_aCurrentDemoFolder[0] != '\0') { - static CButtonContainer s_DeleteButton; - if(DoButton_Menu(&s_DeleteButton, Localize("Delete"), 0, &DeleteRect) || UI()->ConsumeHotkey(CUI::HOTKEY_DELETE) || (Input()->KeyPress(KEY_D) && m_pClient->m_GameConsole.IsClosed())) + if(str_comp(m_vDemos[m_DemolistSelectedIndex].m_aFilename, "..") != 0 && m_vDemos[m_DemolistSelectedIndex].m_StorageType == IStorage::TYPE_SAVE) { - char aBuf[128 + IO_MAX_PATH_LENGTH]; - str_format(aBuf, sizeof(aBuf), Localize("Are you sure that you want to delete the demo '%s'?"), m_vDemos[m_DemolistSelectedIndex].m_aFilename); - PopupConfirm(Localize("Delete demo"), aBuf, Localize("Yes"), Localize("No"), &CMenus::PopupConfirmDeleteDemo); - return; - } + static CButtonContainer s_DeleteButton; + if(DoButton_Menu(&s_DeleteButton, Localize("Delete"), 0, &DeleteRect) || UI()->ConsumeHotkey(CUI::HOTKEY_DELETE) || (Input()->KeyPress(KEY_D) && m_pClient->m_GameConsole.IsClosed())) + { + char aBuf[128 + IO_MAX_PATH_LENGTH]; + str_format(aBuf, sizeof(aBuf), m_vDemos[m_DemolistSelectedIndex].m_IsDir ? Localize("Are you sure that you want to delete the folder '%s'?") : Localize("Are you sure that you want to delete the demo '%s'?"), m_vDemos[m_DemolistSelectedIndex].m_aFilename); + PopupConfirm(m_vDemos[m_DemolistSelectedIndex].m_IsDir ? Localize("Delete folder") : Localize("Delete demo"), aBuf, Localize("Yes"), Localize("No"), m_vDemos[m_DemolistSelectedIndex].m_IsDir ? &CMenus::PopupConfirmDeleteFolder : &CMenus::PopupConfirmDeleteDemo); + return; + } - static CButtonContainer s_RenameButton; - if(DoButton_Menu(&s_RenameButton, Localize("Rename"), 0, &RenameRect)) - { - m_Popup = POPUP_RENAME_DEMO; - m_DemoRenameInput.Set(m_vDemos[m_DemolistSelectedIndex].m_aFilename); - UI()->SetActiveItem(&m_DemoRenameInput); - return; + static CButtonContainer s_RenameButton; + if(DoButton_Menu(&s_RenameButton, Localize("Rename"), 0, &RenameRect)) + { + m_Popup = POPUP_RENAME_DEMO; + if(m_vDemos[m_DemolistSelectedIndex].m_IsDir) + { + m_DemoRenameInput.Set(m_vDemos[m_DemolistSelectedIndex].m_aFilename); + } + else + { + char aNameWithoutExt[IO_MAX_PATH_LENGTH]; + fs_split_file_extension(m_vDemos[m_DemolistSelectedIndex].m_aFilename, aNameWithoutExt, sizeof(aNameWithoutExt)); + m_DemoRenameInput.Set(aNameWithoutExt); + } + UI()->SetActiveItem(&m_DemoRenameInput); + return; + } } #if defined(CONF_VIDEORECORDER) - static CButtonContainer s_RenderButton; - if(DoButton_Menu(&s_RenderButton, Localize("Render"), 0, &RenderRect) || (Input()->KeyPress(KEY_R) && m_pClient->m_GameConsole.IsClosed())) + if(!m_vDemos[m_DemolistSelectedIndex].m_IsDir) { - m_Popup = POPUP_RENDER_DEMO; - char aNameWithoutExt[IO_MAX_PATH_LENGTH]; - fs_split_file_extension(m_vDemos[m_DemolistSelectedIndex].m_aFilename, aNameWithoutExt, sizeof(aNameWithoutExt)); - m_DemoRenderInput.Set(aNameWithoutExt); - UI()->SetActiveItem(&m_DemoRenderInput); - return; + static CButtonContainer s_RenderButton; + if(DoButton_Menu(&s_RenderButton, Localize("Render"), 0, &RenderRect) || (Input()->KeyPress(KEY_R) && m_pClient->m_GameConsole.IsClosed())) + { + m_Popup = POPUP_RENDER_DEMO; + char aNameWithoutExt[IO_MAX_PATH_LENGTH]; + fs_split_file_extension(m_vDemos[m_DemolistSelectedIndex].m_aFilename, aNameWithoutExt, sizeof(aNameWithoutExt)); + m_DemoRenderInput.Set(aNameWithoutExt); + UI()->SetActiveItem(&m_DemoRenderInput); + return; + } } #endif } @@ -1343,3 +1358,20 @@ void CMenus::PopupConfirmDeleteDemo() PopupMessage(Localize("Error"), aError, Localize("Ok")); } } + +void CMenus::PopupConfirmDeleteFolder() +{ + char aBuf[IO_MAX_PATH_LENGTH]; + str_format(aBuf, sizeof(aBuf), "%s/%s", m_aCurrentDemoFolder, m_vDemos[m_DemolistSelectedIndex].m_aFilename); + if(Storage()->RemoveFolder(aBuf, m_vDemos[m_DemolistSelectedIndex].m_StorageType)) + { + DemolistPopulate(); + DemolistOnUpdate(false); + } + else + { + char aError[128 + IO_MAX_PATH_LENGTH]; + str_format(aError, sizeof(aError), Localize("Unable to delete the folder '%s'. Make sure it's empty first."), m_vDemos[m_DemolistSelectedIndex].m_aFilename); + PopupMessage(Localize("Error"), aError, Localize("Ok")); + } +}