Cemu/src/gui/GameUpdateWindow.cpp

350 lines
9.6 KiB
C++

#include "gui/wxgui.h"
#include "gui/GameUpdateWindow.h"
#include "util/helpers/helpers.h"
#include <filesystem>
#include <sstream>
#include "util/helpers/SystemException.h"
#include "gui/CemuApp.h"
#include "Cafe/TitleList/GameInfo.h"
#include "gui/helpers/wxHelpers.h"
#include "wxHelper.h"
std::string _GetTitleIdTypeStr(TitleId titleId)
{
TitleIdParser tip(titleId);
switch (tip.GetType())
{
case TitleIdParser::TITLE_TYPE::AOC:
return _("DLC").ToStdString();
case TitleIdParser::TITLE_TYPE::BASE_TITLE:
return _("Base game").ToStdString();
case TitleIdParser::TITLE_TYPE::BASE_TITLE_DEMO:
return _("Demo").ToStdString();
case TitleIdParser::TITLE_TYPE::SYSTEM_TITLE:
case TitleIdParser::TITLE_TYPE::SYSTEM_OVERLAY_TITLE:
return _("System title").ToStdString();
case TitleIdParser::TITLE_TYPE::SYSTEM_DATA:
return _("System data title").ToStdString();
case TitleIdParser::TITLE_TYPE::BASE_TITLE_UPDATE:
return _("Update").ToStdString();
default:
break;
}
return "Unknown";
}
bool IsRunningInWine();
bool GameUpdateWindow::ParseUpdate(const fs::path& metaPath)
{
m_title_info = TitleInfo(metaPath);
if (!m_title_info.IsValid())
return false;
fs::path target_location = ActiveSettings::GetMlcPath(m_title_info.GetInstallPath());
std::error_code ec;
if (fs::exists(target_location, ec))
{
try
{
const TitleInfo tmp(target_location);
if (!tmp.IsValid())
{
// does not exist / is not valid. We allow to overwrite it
}
else
{
TitleIdParser tip(m_title_info.GetAppTitleId());
TitleIdParser tipOther(tmp.GetAppTitleId());
if (tip.GetType() != tipOther.GetType())
{
std::string typeStrToInstall = _GetTitleIdTypeStr(m_title_info.GetAppTitleId());
std::string typeStrCurrentlyInstalled = _GetTitleIdTypeStr(tmp.GetAppTitleId());
std::string wxMsg = wxHelper::MakeUTF8(_("It seems that there is already a title installed at the target location but it has a different type.\nCurrently installed: \'{}\' Installing: \'{}\'\n\nThis can happen for titles which were installed with very old Cemu versions.\nDo you still want to continue with the installation? It will replace the currently installed title."));
wxMessageDialog dialog(this, fmt::format(fmt::runtime(wxMsg), typeStrCurrentlyInstalled, typeStrToInstall), _("Warning"), wxCENTRE | wxYES_NO | wxICON_EXCLAMATION);
if (dialog.ShowModal() != wxID_YES)
return false;
}
else if (tmp.GetAppTitleVersion() == m_title_info.GetAppTitleVersion())
{
wxMessageDialog dialog(this, _("It seems that the selected title is already installed, do you want to reinstall it?"), _("Warning"), wxCENTRE | wxYES_NO);
if (dialog.ShowModal() != wxID_YES)
return false;
}
else if (tmp.GetAppTitleVersion() > m_title_info.GetAppTitleVersion())
{
wxMessageDialog dialog(this, _("It seems that a newer version is already installed, do you still want to install the older version?"), _("Warning"), wxCENTRE | wxYES_NO);
if (dialog.ShowModal() != wxID_YES)
return false;
}
}
// temp rename until done
m_backup_folder = target_location;
m_backup_folder.replace_extension(".backup");
std::error_code ec;
while (fs::exists(m_backup_folder, ec) || ec)
{
fs::remove_all(m_backup_folder, ec);
if (ec)
{
const auto error_msg = wxStringFormat2(_("Error when trying to move former title installation:\n{}"), GetSystemErrorMessage(ec));
wxMessageBox(error_msg, _("Error"), wxOK | wxCENTRE, this);
return false;
}
// wait so filesystem doesnt
std::this_thread::sleep_for(std::chrono::milliseconds(100));
}
fs::rename(target_location, m_backup_folder);
}
catch (const std::exception& ex)
{
forceLog_printf("GameUpdateWindow::ParseUpdate exist-error: %s at %s", ex.what(), target_location.generic_u8string().c_str());
}
}
m_target_path = target_location;
fs::path source(metaPath);
m_source_paths =
{
fs::path(source).append("content"),
fs::path(source).append("code"),
fs::path(source).append("meta")
};
m_required_size = 0;
for (auto& path : m_source_paths)
{
for (const fs::directory_entry& f : fs::recursive_directory_iterator(path))
{
if (is_regular_file(f.path()))
m_required_size += file_size(f.path());
}
}
// checking size is buggy on Wine (on Steam Deck this would return values too small to install bigger updates) - we therefore skip this step
if(!IsRunningInWine())
{
const fs::space_info targetSpace = fs::space(ActiveSettings::GetMlcPath());
if (targetSpace.free <= m_required_size)
{
auto string = wxStringFormat(_("Not enough space available.\nRequired: {0} MB\nAvailable: {1} MB"), L"%lld %lld", (m_required_size / 1024 / 1024), (targetSpace.free / 1024 / 1024));
throw std::runtime_error(string);
}
}
return true;
}
GameUpdateWindow::GameUpdateWindow(wxWindow& parent, const fs::path& filePath)
: wxDialog(&parent, wxID_ANY, wxEmptyString, wxDefaultPosition, wxDefaultSize, wxCAPTION | wxMINIMIZE_BOX | wxSYSTEM_MENU | wxTAB_TRAVERSAL | wxCLOSE_BOX),
m_thread_state(ThreadRunning)
{
try
{
#if BOOST_OS_WINDOWS
SetLastError(0);
#endif
if(!ParseUpdate(filePath))
throw AbortException();
}
catch (const std::runtime_error& ex)
{
throw SystemException(ex);
}
auto sizer = new wxBoxSizer(wxVERTICAL);
TitleIdParser tip(GetTitleId());
if (tip.GetType() == TitleIdParser::TITLE_TYPE::AOC)
SetTitle(_("Installing DLC..."));
else if (tip.GetType() == TitleIdParser::TITLE_TYPE::BASE_TITLE_UPDATE)
SetTitle(_("Installing update..."));
else if (tip.IsSystemTitle())
SetTitle(_("Installing system title..."));
else
SetTitle(_("Installing title..."));
m_processBar = new wxGauge(this, wxID_ANY, 100, wxDefaultPosition, wxSize(500, 20), wxGA_HORIZONTAL);
m_processBar->SetValue(0);
m_processBar->SetRange((sint32)(m_required_size / 1000));
sizer->Add(m_processBar, 0, wxALL | wxEXPAND, 5);
wxButton* m_cancelButton = new wxButton(this, wxID_ANY, _("Cancel"), wxDefaultPosition, wxDefaultSize, 0);
m_cancelButton->Bind(wxEVT_BUTTON, &GameUpdateWindow::OnCancelButton, this);
sizer->Add(m_cancelButton, 0, wxALIGN_RIGHT | wxALL, 5);
this->SetSizer(sizer);
this->Centre(wxBOTH);
wxWindowBase::Layout();
wxWindowBase::Fit();
m_timer = new wxTimer(this);
this->Bind(wxEVT_TIMER, &GameUpdateWindow::OnUpdate, this);
this->Bind(wxEVT_CLOSE_WINDOW, &GameUpdateWindow::OnClose, this);
m_timer->Start(250);
m_thread_state = ThreadRunning;
m_thread = std::thread(&GameUpdateWindow::ThreadWork, this);
}
void GameUpdateWindow::ThreadWork()
{
fs::directory_entry currentDirEntry;
try
{
// create base directories
for (auto& path : m_source_paths)
{
if (!path.has_stem())
continue;
fs::path targetDir = fs::path(m_target_path) / path.stem();
create_directories(targetDir);
}
for (auto& path : m_source_paths)
{
if (m_thread_state == ThreadCanceled)
break;
if (!path.has_parent_path())
continue;
const auto len = path.parent_path().string().size() + 1;
for (const fs::directory_entry& f : fs::recursive_directory_iterator {path})
{
if (m_thread_state == ThreadCanceled)
break;
currentDirEntry = f;
fs::path relative(f.path().string().substr(len));
fs::path target = fs::path(m_target_path) / relative;
if (is_directory(f))
{
create_directories(target);
continue;
}
copy(f, target, fs::copy_options::overwrite_existing);
if (is_regular_file(f.path()))
{
m_processed_size += file_size(f.path());
}
}
}
}
catch (const std::exception& ex)
{
std::stringstream error_msg;
error_msg << GetSystemErrorMessage(ex);
if(currentDirEntry != fs::directory_entry{})
error_msg << fmt::format("\n{}\n{}",_("Current file:").ToStdString(), _pathToUtf8(currentDirEntry.path()));
m_thread_exception = error_msg.str();
m_thread_state = ThreadCanceled;
}
if (m_thread_state == ThreadCanceled)
{
if(fs::exists(m_target_path))
fs::remove_all(m_target_path);
}
else
m_thread_state = ThreadFinished;
}
GameUpdateWindow::~GameUpdateWindow()
{
m_timer->Stop();
if (m_thread.joinable())
m_thread.join();
}
int GameUpdateWindow::ShowModal()
{
wxDialog::ShowModal();
return m_thread_state == ThreadCanceled ? wxID_CANCEL : wxID_OK;
}
void GameUpdateWindow::OnClose(wxCloseEvent& event)
{
if (m_thread_state == ThreadRunning)
{
wxMessageDialog dialog(this, _("Do you really want to cancel the installation process?\n\nCanceling the process will delete the applied files."), _("Info"), wxCENTRE | wxYES_NO);
if (dialog.ShowModal() != wxID_YES)
return;
m_thread_state = ThreadCanceled;
}
m_timer->Stop();
if (m_thread.joinable())
m_thread.join();
if(!m_backup_folder.empty())
{
if(m_thread_state == ThreadCanceled)
{
// restore backup
try
{
if(fs::exists(m_target_path))
fs::remove_all(m_target_path);
fs::rename(m_backup_folder, m_target_path);
}
catch (const std::exception& ex)
{
forceLogDebug_printf("can't restore update backup: %s",ex.what());
}
}
else
{
// delete backup
try
{
if(fs::exists(m_backup_folder))
fs::remove_all(m_backup_folder);
}
catch (const std::exception& ex)
{
forceLogDebug_printf("can't delete update backup: %s",ex.what());
}
}
m_backup_folder.clear();
}
event.Skip();
}
void GameUpdateWindow::OnUpdate(const wxTimerEvent& event)
{
if (m_thread_state != ThreadRunning)
{
Close();
return;
}
const auto processedSize = (sint32)(m_processed_size / 1000);
if (m_processBar->GetValue() != processedSize)
m_processBar->SetValue(processedSize);
}
void GameUpdateWindow::OnCancelButton(const wxCommandEvent& event)
{
Close();
}