Qt: fix some package install cancellation issues

- Abort installation if any thread has errors
- Only clean the directories of packages that actually had errors
- Additionally clean the directories of packages that were cancelled before they could finish
- Clear boot path in case of error or cancelation
- Propagate result to caller
- Skip success message if the installation was canceled
This commit is contained in:
Megamouse 2023-01-10 21:26:17 +01:00
parent 65f10ff840
commit fc85ed8730
3 changed files with 170 additions and 93 deletions

View file

@ -619,7 +619,7 @@ package_error package_reader::check_target_app_version() const
if (!target_app_ver.empty()) if (!target_app_ver.empty())
{ {
// We are unable to compare anything with the target app version // We are unable to compare anything with the target app version
pkg_log.error("A target app version is required (%s), but no PARAM.SFO was found for %s", target_app_ver, title_id); pkg_log.error("A target app version is required (%s), but no PARAM.SFO was found for %s. (path='%s', error=%s)", target_app_ver, title_id, sfo_path, fs::g_tls_error);
return package_error::app_version; return package_error::app_version;
} }
@ -840,20 +840,28 @@ bool package_reader::fill_data(std::map<std::string, install_entry*>& all_instal
fs::file DecryptEDAT(const fs::file& input, const std::string& input_file_name, int mode, u8 *custom_klic, bool verbose = false); fs::file DecryptEDAT(const fs::file& input, const std::string& input_file_name, int mode, u8 *custom_klic, bool verbose = false);
usz package_reader::extract_worker(thread_key thread_data_key) void package_reader::extract_worker(thread_key thread_data_key)
{ {
usz num_failures = 0; while (m_num_failures == 0 && !m_aborted)
while (true)
{ {
const usz maybe_index = m_entry_indexer++; // Make sure m_entry_indexer does not exceed m_install_entries
const usz index = m_entry_indexer.fetch_op([this](usz& v)
{
if (v < m_install_entries.size())
{
v++;
return true;
}
if (maybe_index >= m_install_entries.size()) return false;
}).first;
if (index >= m_install_entries.size())
{ {
break; break;
} }
const install_entry& entry = ::at32(m_install_entries, maybe_index); const install_entry& entry = ::at32(m_install_entries, index);
if (!entry.is_dominating()) if (!entry.is_dominating())
{ {
@ -934,7 +942,7 @@ usz package_reader::extract_worker(thread_key thread_data_key)
out = DecryptEDAT(out, name, 1, reinterpret_cast<u8*>(&m_header.klicensee), true); out = DecryptEDAT(out, name, 1, reinterpret_cast<u8*>(&m_header.klicensee), true);
if (!out || !fs::write_file(path, fs::rewrite, static_cast<fs::container_stream<std::vector<u8>>*>(out.release().get())->obj)) if (!out || !fs::write_file(path, fs::rewrite, static_cast<fs::container_stream<std::vector<u8>>*>(out.release().get())->obj))
{ {
num_failures++; m_num_failures++;
pkg_log.error("Failed to create file %s", path); pkg_log.error("Failed to create file %s", path);
break; break;
} }
@ -959,12 +967,12 @@ usz package_reader::extract_worker(thread_key thread_data_key)
} }
else else
{ {
num_failures++; m_num_failures++;
} }
} }
else else
{ {
num_failures++; m_num_failures++;
pkg_log.error("Failed to create file %s", path); pkg_log.error("Failed to create file %s", path);
} }
@ -972,21 +980,19 @@ usz package_reader::extract_worker(thread_key thread_data_key)
} }
default: default:
{ {
num_failures++; m_num_failures++;
pkg_log.error("Unknown PKG entry type (0x%x) %s", entry.type, name); pkg_log.error("Unknown PKG entry type (0x%x) %s", entry.type, name);
break; break;
} }
} }
} }
return num_failures;
} }
bool package_reader::extract_data(std::deque<package_reader>& readers, std::deque<std::string>& bootable_paths) bool package_reader::extract_data(std::deque<package_reader>& readers, std::deque<std::string>& bootable_paths)
{ {
std::map<std::string, install_entry*> all_install_entries; std::map<std::string, install_entry*> all_install_entries;
for (auto& reader : readers) for (package_reader& reader : readers)
{ {
if (!reader.fill_data(all_install_entries)) if (!reader.fill_data(all_install_entries))
{ {
@ -999,31 +1005,58 @@ bool package_reader::extract_data(std::deque<package_reader>& readers, std::dequ
for (package_reader& reader : readers) for (package_reader& reader : readers)
{ {
reader.m_bufs.resize(std::min<usz>(utils::get_thread_count(), reader.m_install_entries.size())); reader.m_bufs.resize(std::min<usz>(utils::get_thread_count(), reader.m_install_entries.size()));
reader.m_num_failures = 0;
reader.m_result = result::not_started;
atomic_t<usz> thread_indexer = 0; atomic_t<usz> thread_indexer = 0;
named_thread_group workers("PKG Installer "sv, std::max<usz>(reader.m_bufs.size(), 1) - 1, [&]() named_thread_group workers("PKG Installer "sv, std::max<usz>(reader.m_bufs.size(), 1) - 1, [&]()
{ {
num_failures += reader.extract_worker(thread_key{thread_indexer++}); reader.extract_worker(thread_key{thread_indexer++});
}); });
num_failures += reader.extract_worker(thread_key{thread_indexer++}); reader.extract_worker(thread_key{thread_indexer++});
workers.join(); workers.join();
num_failures += reader.m_num_failures;
reader.m_bufs.clear(); reader.m_bufs.clear();
reader.m_bufs.shrink_to_fit(); reader.m_bufs.shrink_to_fit();
if (num_failures) // We don't count this package as aborted if all entries were processed.
if (reader.m_num_failures || (reader.m_aborted && reader.m_entry_indexer < reader.m_install_entries.size()))
{ {
if (reader.m_was_null) // Clear boot path. We don't want to propagate potentially broken paths to the caller.
reader.m_bootable_file_path.clear();
bool cleaned = reader.m_was_null;
if (reader.m_was_null && fs::is_dir(reader.m_install_path))
{ {
fs::remove_all(reader.m_install_path, true); pkg_log.notice("Removing partial installation ('%s')", reader.m_install_path);
if (!fs::remove_all(reader.m_install_path, true))
{
pkg_log.notice("Failed to remove partial installation ('%s') (error=%s)", reader.m_install_path, fs::g_tls_error);
cleaned = false;
}
} }
pkg_log.success("Package failed to install ('%s')", reader.m_install_path); if (reader.m_num_failures)
{
pkg_log.error("Package failed to install ('%s')", reader.m_install_path);
reader.m_result = cleaned ? result::error_cleaned : result::error;
}
else
{
pkg_log.warning("Package installation aborted ('%s')", reader.m_install_path);
reader.m_result = cleaned ? result::aborted_cleaned : result::aborted;
}
break; break;
} }
reader.m_result = result::success;
if (reader.get_progress(1) != 1) if (reader.get_progress(1) != 1)
{ {
pkg_log.warning("Missing %d bytes from PKG total files size.", reader.m_header.data_size - reader.m_written_bytes); pkg_log.warning("Missing %d bytes from PKG total files size.", reader.m_header.data_size - reader.m_written_bytes);
@ -1147,14 +1180,5 @@ int package_reader::get_progress(int maximum) const
void package_reader::abort_extract() void package_reader::abort_extract()
{ {
m_entry_indexer.fetch_op([this](usz& v) m_aborted = true;
{
if (v < m_install_entries.size())
{
v = m_install_entries.size();
return true;
}
return false;
});
} }

View file

@ -332,10 +332,21 @@ public:
package_reader(const std::string& path); package_reader(const std::string& path);
~package_reader(); ~package_reader();
enum result
{
not_started,
success,
aborted,
aborted_cleaned,
error,
error_cleaned
};
bool is_valid() const { return m_is_valid; } bool is_valid() const { return m_is_valid; }
package_error check_target_app_version() const; package_error check_target_app_version() const;
static bool extract_data(std::deque<package_reader>& readers, std::deque<std::string>& bootable_paths); static bool extract_data(std::deque<package_reader>& readers, std::deque<std::string>& bootable_paths);
psf::registry get_psf() const { return m_psf; } psf::registry get_psf() const { return m_psf; }
result get_result() const { return m_result; };
int get_progress(int maximum = 100) const; int get_progress(int maximum = 100) const;
@ -351,10 +362,12 @@ private:
bool fill_data(std::map<std::string, install_entry*>& all_install_entries); bool fill_data(std::map<std::string, install_entry*>& all_install_entries);
std::span<const char> archive_read_block(u64 offset, void* data_ptr, u64 num_bytes); std::span<const char> archive_read_block(u64 offset, void* data_ptr, u64 num_bytes);
std::span<const char> decrypt(u64 offset, u64 size, const uchar* key, thread_key thread_data_key = {0}); std::span<const char> decrypt(u64 offset, u64 size, const uchar* key, thread_key thread_data_key = {0});
usz extract_worker(thread_key thread_data_key); void extract_worker(thread_key thread_data_key);
std::deque<install_entry> m_install_entries; std::deque<install_entry> m_install_entries;
std::string m_install_path; std::string m_install_path;
atomic_t<bool> m_aborted = false;
atomic_t<usz> m_num_failures = 0;
atomic_t<usz> m_entry_indexer = 0; atomic_t<usz> m_entry_indexer = 0;
atomic_t<usz> m_written_bytes = 0; atomic_t<usz> m_written_bytes = 0;
bool m_was_null = false; bool m_was_null = false;
@ -362,6 +375,7 @@ private:
static constexpr usz BUF_SIZE = 8192 * 1024; // 8 MB static constexpr usz BUF_SIZE = 8192 * 1024; // 8 MB
bool m_is_valid = false; bool m_is_valid = false;
result m_result = result::not_started;
std::string m_path{}; std::string m_path{};
std::string m_install_dir{}; std::string m_install_dir{};

View file

@ -900,6 +900,13 @@ void main_window::HandlePackageInstallation(QStringList file_paths)
Emu.GracefulShutdown(false); Emu.GracefulShutdown(false);
std::vector<std::string> path_vec;
for (const compat::package_info& pkg : packages)
{
path_vec.push_back(pkg.path.toStdString());
}
gui_log.notice("About to install packages:\n%s", fmt::merge(path_vec, "\n"));
progress_dialog pdlg(tr("RPCS3 Package Installer"), tr("Installing package, please wait..."), tr("Cancel"), 0, 1000, false, this); progress_dialog pdlg(tr("RPCS3 Package Installer"), tr("Installing package, please wait..."), tr("Cancel"), 0, 1000, false, this);
pdlg.setAutoClose(false); pdlg.setAutoClose(false);
pdlg.show(); pdlg.show();
@ -934,7 +941,6 @@ void main_window::HandlePackageInstallation(QStringList file_paths)
}; };
bool cancelled = false; bool cancelled = false;
std::map<std::string, QString> bootable_paths_installed; // -> title id
std::deque<package_reader> readers; std::deque<package_reader> readers;
@ -971,7 +977,7 @@ void main_window::HandlePackageInstallation(QStringList file_paths)
{ {
cancelled = true; cancelled = true;
for (auto& reader : readers) for (package_reader& reader : readers)
{ {
reader.abort_extract(); reader.abort_extract();
} }
@ -1002,15 +1008,45 @@ void main_window::HandlePackageInstallation(QStringList file_paths)
pdlg.SetValue(pdlg.maximum()); pdlg.SetValue(pdlg.maximum());
std::this_thread::sleep_for(100ms); std::this_thread::sleep_for(100ms);
for (usz i = 0; i < packages.size(); i++)
{ {
m_game_list_frame->Refresh(true); const compat::package_info& package = ::at32(packages, i);
const package_reader& reader = ::at32(readers, i);
for (const auto& package : packages) switch (reader.get_result())
{
case package_reader::result::success:
{ {
gui_log.success("Successfully installed %s (title_id=%s, title=%s, version=%s).", sstr(package.path), sstr(package.title_id), sstr(package.title), sstr(package.version)); gui_log.success("Successfully installed %s (title_id=%s, title=%s, version=%s).", sstr(package.path), sstr(package.title_id), sstr(package.title), sstr(package.version));
break;
} }
case package_reader::result::not_started:
case package_reader::result::aborted_cleaned:
{
gui_log.notice("Aborted installation of %s (title_id=%s, title=%s, version=%s).", sstr(package.path), sstr(package.title_id), sstr(package.title), sstr(package.version));
break;
}
case package_reader::result::error_cleaned:
{
gui_log.error("Failed to install %s (title_id=%s, title=%s, version=%s).", sstr(package.path), sstr(package.title_id), sstr(package.title), sstr(package.version));
break;
}
case package_reader::result::aborted:
case package_reader::result::error:
{
gui_log.error("Partially installed %s (title_id=%s, title=%s, version=%s).", sstr(package.path), sstr(package.title_id), sstr(package.title), sstr(package.version));
break;
}
}
}
gui_log.success("Package(s) successfully installed!"); m_game_list_frame->Refresh(true);
pdlg.hide();
if (!cancelled)
{
std::map<std::string, QString> bootable_paths_installed; // -> title id
for (usz index = 0; index < bootable_paths.size(); index++) for (usz index = 0; index < bootable_paths.size(); index++)
{ {
@ -1022,78 +1058,73 @@ void main_window::HandlePackageInstallation(QStringList file_paths)
bootable_paths_installed[bootable_paths[index]] = packages[index].title_id; bootable_paths_installed[bootable_paths[index]] = packages[index].title_id;
} }
if (bootable_paths_installed.empty())
{ {
pdlg.hide(); m_gui_settings->ShowInfoBox(tr("Success!"), tr("Successfully installed software from package(s)!"), gui::ib_pkg_success, this);
return;
}
bool create_desktop_shortcuts = false; auto dlg = new QDialog(this);
bool create_app_shortcut = false; dlg->setWindowTitle(tr("Success!"));
if (bootable_paths_installed.empty()) QVBoxLayout* vlayout = new QVBoxLayout(dlg);
{
m_gui_settings->ShowInfoBox(tr("Success!"), tr("Successfully installed software from package(s)!"), gui::ib_pkg_success, this);
}
else
{
auto dlg = new QDialog(this);
dlg->setWindowTitle(tr("Success!"));
QVBoxLayout* vlayout = new QVBoxLayout(dlg); QCheckBox* desk_check = new QCheckBox(tr("Add desktop shortcut(s)"));
QCheckBox* desk_check = new QCheckBox(tr("Add desktop shortcut(s)"));
#ifdef _WIN32 #ifdef _WIN32
QCheckBox* quick_check = new QCheckBox(tr("Add Start menu shortcut(s)")); QCheckBox* quick_check = new QCheckBox(tr("Add Start menu shortcut(s)"));
#elif defined(__APPLE__) #elif defined(__APPLE__)
QCheckBox* quick_check = new QCheckBox(tr("Add dock shortcut(s)")); QCheckBox* quick_check = new QCheckBox(tr("Add dock shortcut(s)"));
#else #else
QCheckBox* quick_check = new QCheckBox(tr("Add launcher shortcut(s)")); QCheckBox* quick_check = new QCheckBox(tr("Add launcher shortcut(s)"));
#endif #endif
QLabel* label = new QLabel(tr("Successfully installed software from package(s)!\nWould you like to install shortcuts to the installed software? (%1 new software detected)\n\n").arg(bootable_paths_installed.size()), dlg); QLabel* label = new QLabel(tr("Successfully installed software from package(s)!\nWould you like to install shortcuts to the installed software? (%1 new software detected)\n\n").arg(bootable_paths_installed.size()), dlg);
vlayout->addWidget(label); vlayout->addWidget(label);
vlayout->addStretch(10); vlayout->addStretch(10);
vlayout->addWidget(desk_check); vlayout->addWidget(desk_check);
vlayout->addStretch(3); vlayout->addStretch(3);
vlayout->addWidget(quick_check); vlayout->addWidget(quick_check);
vlayout->addStretch(3); vlayout->addStretch(3);
QDialogButtonBox* btn_box = new QDialogButtonBox(QDialogButtonBox::Ok); QDialogButtonBox* btn_box = new QDialogButtonBox(QDialogButtonBox::Ok);
vlayout->addWidget(btn_box); vlayout->addWidget(btn_box);
dlg->setLayout(vlayout); dlg->setLayout(vlayout);
connect(btn_box, &QDialogButtonBox::accepted, this, [&]() bool create_desktop_shortcuts = false;
{ bool create_app_shortcut = false;
create_desktop_shortcuts = desk_check->isChecked();
create_app_shortcut = quick_check->isChecked();
dlg->accept();
});
dlg->setAttribute(Qt::WA_DeleteOnClose); connect(btn_box, &QDialogButtonBox::accepted, this, [&]()
dlg->exec(); {
} create_desktop_shortcuts = desk_check->isChecked();
create_app_shortcut = quick_check->isChecked();
dlg->accept();
});
std::set<gui::utils::shortcut_location> locations; dlg->setAttribute(Qt::WA_DeleteOnClose);
dlg->exec();
std::set<gui::utils::shortcut_location> locations;
#ifdef _WIN32 #ifdef _WIN32
locations.insert(gui::utils::shortcut_location::rpcs3_shortcuts); locations.insert(gui::utils::shortcut_location::rpcs3_shortcuts);
#endif #endif
if (create_desktop_shortcuts) if (create_desktop_shortcuts)
{ {
locations.insert(gui::utils::shortcut_location::desktop); locations.insert(gui::utils::shortcut_location::desktop);
} }
if (create_app_shortcut) if (create_app_shortcut)
{ {
locations.insert(gui::utils::shortcut_location::applications); locations.insert(gui::utils::shortcut_location::applications);
} }
for (const auto& [boot_path, title_id] : bootable_paths_installed) for (const auto& [boot_path, title_id] : bootable_paths_installed)
{
for (const game_info& gameinfo : m_game_list_frame->GetGameInfo())
{ {
for (const game_info& gameinfo : m_game_list_frame->GetGameInfo()) if (gameinfo && gameinfo->info.bootable && gameinfo->info.serial == sstr(title_id) && boot_path.starts_with(gameinfo->info.path))
{ {
if (gameinfo && gameinfo->info.bootable && gameinfo->info.serial == sstr(title_id) && boot_path.starts_with(gameinfo->info.path)) m_game_list_frame->CreateShortcuts(gameinfo, locations);
{ break;
m_game_list_frame->CreateShortcuts(gameinfo, locations);
break;
}
} }
} }
} }
@ -1108,12 +1139,20 @@ void main_window::HandlePackageInstallation(QStringList file_paths)
{ {
const compat::package_info* package = nullptr; const compat::package_info* package = nullptr;
for (usz i = 0; i < readers.size(); i++) for (usz i = 0; i < readers.size() && !package; i++)
{ {
// Figure out what package failed the installation // Figure out what package failed the installation
if (readers[i].get_progress(1) != 1) switch (readers[i].get_result())
{ {
case package_reader::result::success:
case package_reader::result::not_started:
case package_reader::result::aborted:
case package_reader::result::aborted_cleaned:
break;
case package_reader::result::error:
case package_reader::result::error_cleaned:
package = &packages[i]; package = &packages[i];
break;
} }
} }