6833: Support deleting/renaming demo folders, improve demo popups r=def- a=Robyt3

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.

## Checklist

- [X] Tested the change ingame
- [ ] Provided screenshots if it is a visual change
- [ ] Tested in combination with possibly related configuration options
- [ ] Written a unit test (especially base/) or added coverage to integration test
- [ ] Considered possible null pointers and out of bounds array indexing
- [ ] Changed no physics that affect existing maps
- [ ] Tested the change with [ASan+UBSan or valgrind's memcheck](https://github.com/ddnet/ddnet/#using-addresssanitizer--undefinedbehavioursanitizer-or-valgrinds-memcheck) (optional)


Co-authored-by: Robert Müller <robytemueller@gmail.com>
This commit is contained in:
bors[bot] 2023-07-11 17:38:32 +00:00 committed by GitHub
commit 3e5daf0a68
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
3 changed files with 110 additions and 67 deletions

View file

@ -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"));

View file

@ -322,9 +322,10 @@ protected:
char m_aCurrentDemoSelectionName[IO_MAX_PATH_LENGTH];
CLineInputBuffered<IO_MAX_PATH_LENGTH> m_DemoRenameInput;
CLineInputBuffered<IO_MAX_PATH_LENGTH> m_DemoSliceInput;
#if defined(CONF_VIDEORECORDER)
CLineInputBuffered<IO_MAX_PATH_LENGTH> 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);

View file

@ -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"));
}
}