mirror of
https://github.com/RPCS3/rpcs3.git
synced 2025-02-20 15:40:23 +00:00
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:
parent
65f10ff840
commit
fc85ed8730
@ -619,7 +619,7 @@ package_error package_reader::check_target_app_version() const
|
||||
if (!target_app_ver.empty())
|
||||
{
|
||||
// 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;
|
||||
}
|
||||
|
||||
@ -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);
|
||||
|
||||
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 (true)
|
||||
while (m_num_failures == 0 && !m_aborted)
|
||||
{
|
||||
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;
|
||||
}
|
||||
|
||||
const install_entry& entry = ::at32(m_install_entries, maybe_index);
|
||||
const install_entry& entry = ::at32(m_install_entries, index);
|
||||
|
||||
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);
|
||||
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);
|
||||
break;
|
||||
}
|
||||
@ -959,12 +967,12 @@ usz package_reader::extract_worker(thread_key thread_data_key)
|
||||
}
|
||||
else
|
||||
{
|
||||
num_failures++;
|
||||
m_num_failures++;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
num_failures++;
|
||||
m_num_failures++;
|
||||
pkg_log.error("Failed to create file %s", path);
|
||||
}
|
||||
|
||||
@ -972,21 +980,19 @@ usz package_reader::extract_worker(thread_key thread_data_key)
|
||||
}
|
||||
default:
|
||||
{
|
||||
num_failures++;
|
||||
m_num_failures++;
|
||||
pkg_log.error("Unknown PKG entry type (0x%x) %s", entry.type, name);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return num_failures;
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
for (auto& reader : readers)
|
||||
for (package_reader& reader : readers)
|
||||
{
|
||||
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)
|
||||
{
|
||||
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;
|
||||
|
||||
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();
|
||||
|
||||
num_failures += reader.m_num_failures;
|
||||
|
||||
reader.m_bufs.clear();
|
||||
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;
|
||||
}
|
||||
|
||||
reader.m_result = result::success;
|
||||
|
||||
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);
|
||||
@ -1147,14 +1180,5 @@ int package_reader::get_progress(int maximum) const
|
||||
|
||||
void package_reader::abort_extract()
|
||||
{
|
||||
m_entry_indexer.fetch_op([this](usz& v)
|
||||
{
|
||||
if (v < m_install_entries.size())
|
||||
{
|
||||
v = m_install_entries.size();
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
});
|
||||
m_aborted = true;
|
||||
}
|
||||
|
@ -332,10 +332,21 @@ public:
|
||||
package_reader(const std::string& path);
|
||||
~package_reader();
|
||||
|
||||
enum result
|
||||
{
|
||||
not_started,
|
||||
success,
|
||||
aborted,
|
||||
aborted_cleaned,
|
||||
error,
|
||||
error_cleaned
|
||||
};
|
||||
|
||||
bool is_valid() const { return m_is_valid; }
|
||||
package_error check_target_app_version() const;
|
||||
static bool extract_data(std::deque<package_reader>& readers, std::deque<std::string>& bootable_paths);
|
||||
psf::registry get_psf() const { return m_psf; }
|
||||
result get_result() const { return m_result; };
|
||||
|
||||
int get_progress(int maximum = 100) const;
|
||||
|
||||
@ -351,10 +362,12 @@ private:
|
||||
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> 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::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_written_bytes = 0;
|
||||
bool m_was_null = false;
|
||||
@ -362,6 +375,7 @@ private:
|
||||
static constexpr usz BUF_SIZE = 8192 * 1024; // 8 MB
|
||||
|
||||
bool m_is_valid = false;
|
||||
result m_result = result::not_started;
|
||||
|
||||
std::string m_path{};
|
||||
std::string m_install_dir{};
|
||||
|
@ -900,6 +900,13 @@ void main_window::HandlePackageInstallation(QStringList file_paths)
|
||||
|
||||
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);
|
||||
pdlg.setAutoClose(false);
|
||||
pdlg.show();
|
||||
@ -934,7 +941,6 @@ void main_window::HandlePackageInstallation(QStringList file_paths)
|
||||
};
|
||||
|
||||
bool cancelled = false;
|
||||
std::map<std::string, QString> bootable_paths_installed; // -> title id
|
||||
|
||||
std::deque<package_reader> readers;
|
||||
|
||||
@ -971,7 +977,7 @@ void main_window::HandlePackageInstallation(QStringList file_paths)
|
||||
{
|
||||
cancelled = true;
|
||||
|
||||
for (auto& reader : readers)
|
||||
for (package_reader& reader : readers)
|
||||
{
|
||||
reader.abort_extract();
|
||||
}
|
||||
@ -1002,15 +1008,45 @@ void main_window::HandlePackageInstallation(QStringList file_paths)
|
||||
pdlg.SetValue(pdlg.maximum());
|
||||
std::this_thread::sleep_for(100ms);
|
||||
|
||||
for (usz i = 0; i < packages.size(); i++)
|
||||
{
|
||||
m_game_list_frame->Refresh(true);
|
||||
|
||||
for (const auto& package : packages)
|
||||
const compat::package_info& package = ::at32(packages, i);
|
||||
const package_reader& reader = ::at32(readers, i);
|
||||
|
||||
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));
|
||||
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++)
|
||||
{
|
||||
@ -1022,78 +1058,73 @@ void main_window::HandlePackageInstallation(QStringList file_paths)
|
||||
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;
|
||||
bool create_app_shortcut = false;
|
||||
auto dlg = new QDialog(this);
|
||||
dlg->setWindowTitle(tr("Success!"));
|
||||
|
||||
if (bootable_paths_installed.empty())
|
||||
{
|
||||
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);
|
||||
|
||||
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
|
||||
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__)
|
||||
QCheckBox* quick_check = new QCheckBox(tr("Add dock shortcut(s)"));
|
||||
QCheckBox* quick_check = new QCheckBox(tr("Add dock shortcut(s)"));
|
||||
#else
|
||||
QCheckBox* quick_check = new QCheckBox(tr("Add launcher shortcut(s)"));
|
||||
QCheckBox* quick_check = new QCheckBox(tr("Add launcher shortcut(s)"));
|
||||
#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->addStretch(10);
|
||||
vlayout->addWidget(desk_check);
|
||||
vlayout->addStretch(3);
|
||||
vlayout->addWidget(quick_check);
|
||||
vlayout->addStretch(3);
|
||||
vlayout->addWidget(label);
|
||||
vlayout->addStretch(10);
|
||||
vlayout->addWidget(desk_check);
|
||||
vlayout->addStretch(3);
|
||||
vlayout->addWidget(quick_check);
|
||||
vlayout->addStretch(3);
|
||||
|
||||
QDialogButtonBox* btn_box = new QDialogButtonBox(QDialogButtonBox::Ok);
|
||||
QDialogButtonBox* btn_box = new QDialogButtonBox(QDialogButtonBox::Ok);
|
||||
|
||||
vlayout->addWidget(btn_box);
|
||||
dlg->setLayout(vlayout);
|
||||
vlayout->addWidget(btn_box);
|
||||
dlg->setLayout(vlayout);
|
||||
|
||||
connect(btn_box, &QDialogButtonBox::accepted, this, [&]()
|
||||
{
|
||||
create_desktop_shortcuts = desk_check->isChecked();
|
||||
create_app_shortcut = quick_check->isChecked();
|
||||
dlg->accept();
|
||||
});
|
||||
bool create_desktop_shortcuts = false;
|
||||
bool create_app_shortcut = false;
|
||||
|
||||
dlg->setAttribute(Qt::WA_DeleteOnClose);
|
||||
dlg->exec();
|
||||
}
|
||||
connect(btn_box, &QDialogButtonBox::accepted, this, [&]()
|
||||
{
|
||||
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
|
||||
locations.insert(gui::utils::shortcut_location::rpcs3_shortcuts);
|
||||
locations.insert(gui::utils::shortcut_location::rpcs3_shortcuts);
|
||||
#endif
|
||||
if (create_desktop_shortcuts)
|
||||
{
|
||||
locations.insert(gui::utils::shortcut_location::desktop);
|
||||
}
|
||||
if (create_app_shortcut)
|
||||
{
|
||||
locations.insert(gui::utils::shortcut_location::applications);
|
||||
}
|
||||
if (create_desktop_shortcuts)
|
||||
{
|
||||
locations.insert(gui::utils::shortcut_location::desktop);
|
||||
}
|
||||
if (create_app_shortcut)
|
||||
{
|
||||
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;
|
||||
|
||||
for (usz i = 0; i < readers.size(); i++)
|
||||
for (usz i = 0; i < readers.size() && !package; i++)
|
||||
{
|
||||
// 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];
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user