[ci skip] OptionsTab: add option to unmount UMS devices

Uses a SelectListItem element with displayValue set to false to avoid displaying a value string. Furthermore, the default click event callback is replaced to check if any UMS devices are mounted before attempting to display the dropdown menu.

Other changes include:

* UmsTask: define UmsDeviceVectorEntry.
* UmsTask: redefine UmsDeviceVector as a UmsDeviceVectorEntry vector.
* UmsTask: migrate UMS device string generation logic from DumpOptionsFrame::UpdateStorages() to UmsTask::PopulateUmsDeviceVector().

* DumpOptionsFrame: simplify UpdateStorages() to reflect the changes made to UmsTask.
This commit is contained in:
Pablo Curiel 2024-04-18 21:58:47 +02:00
parent 7d7f2d58a8
commit 17e0edb61c
9 changed files with 119 additions and 26 deletions

View File

@ -91,7 +91,8 @@ namespace nxdt::views
u64 total_sz = 0, free_sz = 0;
char total_sz_str[64] = {0}, free_sz_str[64] = {0};
const UsbHsFsDevice *cur_ums_device = (i >= ConfigOutputStorage_Count ? &(ums_devices.at(i - ConfigOutputStorage_Count)) : nullptr);
const nxdt::tasks::UmsDeviceVectorEntry *ums_device_entry = (i >= ConfigOutputStorage_Count ? &(ums_devices.at(i - ConfigOutputStorage_Count)) : nullptr);
const UsbHsFsDevice *cur_ums_device = (ums_device_entry ? ums_device_entry->first : nullptr);
sprintf(total_sz_str, "%s/", cur_ums_device ? cur_ums_device->name : DEVOPTAB_SDMC_DEVICE);
utilsGetFileSystemStatsByPath(total_sz_str, &total_sz, &free_sz);
@ -100,9 +101,7 @@ namespace nxdt::views
if (cur_ums_device)
{
std::string ums_extra_info = (cur_ums_device->product_name[0] ? (std::string(cur_ums_device->product_name) + ", ") : "");
ums_extra_info += fmt::format("LUN {}, FS #{}, {}", cur_ums_device->lun, cur_ums_device->fs_idx, LIBUSBHSFS_FS_TYPE_STR(cur_ums_device->fs_type));
storages.push_back(brls::i18n::getStr("dump_options/output_storage/value_02", static_cast<int>(strlen(cur_ums_device->name + 3) - 1), cur_ums_device->name + 3, free_sz_str, total_sz_str, ums_extra_info));
storages.push_back(brls::i18n::getStr("dump_options/output_storage/value_02", ums_device_entry->second, free_sz_str, total_sz_str));
} else {
storages.push_back(brls::i18n::getStr("dump_options/output_storage/value_00", free_sz_str, total_sz_str));
}

View File

@ -73,9 +73,13 @@ namespace nxdt::views
private:
RootView *root_view = nullptr;
brls::SelectListItem *unmount_ums_device = nullptr;
nxdt::tasks::UmsDeviceVector ums_devices{};
nxdt::tasks::UmsEvent::Subscription ums_task_sub;
bool display_notification = true;
brls::menu_timer_t notification_timer = 0.0f;
brls::menu_timer_ctx_entry_t notification_timer_ctx = {0};
brls::menu_timer_ctx_entry_t notification_timer_ctx{};
void DisplayNotification(const std::string& str);
public:

View File

@ -52,8 +52,9 @@ namespace nxdt::tasks
/* Used to hold pointers to application metadata entries. */
typedef std::vector<TitleApplicationMetadata*> TitleApplicationMetadataVector;
/* Used to hold UMS devices. */
typedef std::vector<UsbHsFsDevice> UmsDeviceVector;
/* Used to hold information from UMS devices. */
typedef std::pair<const UsbHsFsDevice*, std::string> UmsDeviceVectorEntry;
typedef std::vector<UmsDeviceVectorEntry> UmsDeviceVector;
/* Custom event types. */
typedef brls::Event<const StatusInfoData&> StatusInfoEvent;
@ -133,7 +134,11 @@ namespace nxdt::tasks
{
private:
UmsEvent ums_event;
UmsDeviceVector ums_devices{};
UsbHsFsDevice *ums_devices = nullptr;
u32 ums_devices_count = 0;
UmsDeviceVector ums_devices_vector{};
void PopulateUmsDeviceVector(void);

@ -1 +1 @@
Subproject commit ed6f2ef76a136b0f77ce599e8401dbdf6bd6d11a
Subproject commit e5cbe0d97c32d1102a6ea253ed68a3a8ecfdc3a9

View File

@ -8,8 +8,8 @@
"label": "Output storage",
"description": "Storage where the dumped data will be written to. Changing it will automatically update the output filename to better suit the output filesystem limitations.\nUsing a connected USB host requires a libusb-based driver, as well as the Python host script. For more information, please visit \"{0}\".",
"value_00": "SD card ({0} free / {1} total)",
"value_01": "USB host",
"value_02": "USB Mass Storage {1:.{0}} ({2} free / {3} total) ({4})"
"value_01": "USB host (PC)",
"value_02": "{0} ({1} free / {2} total)"
},
"prepend_key_area": {

View File

@ -13,6 +13,11 @@
"value_01": "ID and version only"
},
"unmount_ums_device": {
"label": "Unmount USB Mass Storage device",
"description": "Safely unmount any USB Mass Storage devices that are currently connected and mounted by {0}.\n\nIf a UMS device has more than one mounted volume, selecting a single one will unmount all volumes from that device.\n\nUMS devices are always safely unmounted at exit."
},
"update_nswdb_xml": {
"label": "Update NSWDB XML",
"description": "Retrieves the latest NSWDB XML, which can be optionally used to validate checksums from gamecard dumps. Requires Internet connectivity."
@ -35,6 +40,9 @@
},
"notifications": {
"no_ums_devices": "No USB Mass Storage devices available.",
"ums_device_unmount_success": "USB Mass Storage device successfully unmounted!",
"ums_device_unmount_failure": "Failed to unmount USB Mass Storage device!",
"no_internet_connection": "Internet connection unavailable. Unable to update.",
"update_failed": "Update failed! Check the logfile for more info.",
"nswdb_xml_updated": "NSWDB XML successfully updated!",

View File

@ -277,7 +277,7 @@ namespace nxdt::views
brls::ListItem *dump_initial_data = new brls::ListItem("gamecard_tab/list/dump_initial_data/label"_i18n, "gamecard_tab/list/dump_initial_data/description"_i18n);
this->list->addView(dump_initial_data);
brls::ListItem *dump_certificate = new brls::ListItem("gamecard_tab/list/dump_certificate/label"_i18n, brls::i18n::getStr("gamecard_tab/list/dump_certificate/description", GAMECARD_CERTIFICATE_OFFSET / GAMECARD_PAGE_SIZE));
brls::ListItem *dump_certificate = new brls::ListItem("gamecard_tab/list/dump_certificate/label"_i18n, i18n::getStr("gamecard_tab/list/dump_certificate/description", GAMECARD_CERTIFICATE_OFFSET / GAMECARD_PAGE_SIZE));
this->list->addView(dump_certificate);
brls::ListItem *dump_card_id_set = new brls::ListItem("gamecard_tab/list/dump_card_id_set/label"_i18n, "gamecard_tab/list/dump_card_id_set/description"_i18n);
@ -286,7 +286,7 @@ namespace nxdt::views
brls::ListItem *dump_card_uid = new brls::ListItem("gamecard_tab/list/dump_card_uid/label"_i18n, "gamecard_tab/list/dump_card_uid/description"_i18n);
this->list->addView(dump_card_uid);
brls::ListItem *dump_header = new brls::ListItem("gamecard_tab/list/dump_header/label"_i18n, brls::i18n::getStr("gamecard_tab/list/dump_header/description", 0));
brls::ListItem *dump_header = new brls::ListItem("gamecard_tab/list/dump_header/label"_i18n, i18n::getStr("gamecard_tab/list/dump_header/description", 0));
this->list->addView(dump_header);
brls::ListItem *dump_plaintext_cardinfo = new brls::ListItem("gamecard_tab/list/dump_plaintext_cardinfo/label"_i18n, "gamecard_tab/list/dump_plaintext_cardinfo/description"_i18n);

View File

@ -317,6 +317,62 @@ namespace nxdt::views
this->addView(naming_convention);
/* Unmount UMS devices. */
/* We will replace its default click event with a new one that will: */
/* 1. Check if any UMS devices are available before displaying the dropdown and display a notification if there are none. */
/* 2. Generate the string vector required by the dropdown. */
/* 3. Initialize the dropdown and pass a custom callback that will take care of unmounting the selected device. */
this->unmount_ums_device = new brls::SelectListItem("options_tab/unmount_ums_device/label"_i18n, { "dummy" }, 0,
i18n::getStr("options_tab/unmount_ums_device/description"_i18n, APP_TITLE), false);
this->unmount_ums_device->getClickEvent()->unsubscribeAll();
this->unmount_ums_device->getClickEvent()->subscribe([this](brls::View* view) {
if (this->ums_devices.empty())
{
/* Display a notification if we haven't mounted any UMS devices at all. */
this->DisplayNotification("options_tab/notifications/no_ums_devices"_i18n);
return;
}
/* Generate values vector for the dropdown. */
std::vector<std::string> values{};
for(nxdt::tasks::UmsDeviceVectorEntry ums_device_entry : this->ums_devices) values.push_back(ums_device_entry.second);
/* Display dropdown. */
brls::Dropdown::open(this->unmount_ums_device->getLabel(), values, [this](int idx) {
/* Make sure the current value isn't out of bounds. */
if (idx < 0 || idx >= static_cast<int>(this->ums_devices.size())) return;
/* Unmount UMS device. */
if (umsUnmountDevice(this->ums_devices.at(idx).first))
{
this->DisplayNotification("options_tab/notifications/ums_device_unmount_success"_i18n);
} else {
this->DisplayNotification("options_tab/notifications/ums_device_unmount_failure"_i18n);
}
});
});
/* Update UMS devices vector. */
this->ums_devices = this->root_view->GetUmsDevices();
/* Subscribe to the UMS device event. */
this->ums_task_sub = this->root_view->RegisterUmsTaskListener([this](const nxdt::tasks::UmsDeviceVector& ums_devices) {
/* Update UMS devices vector. */
this->ums_devices = this->root_view->GetUmsDevices();
/* Generate values vector for the dropdown. */
std::vector<std::string> values{};
for(nxdt::tasks::UmsDeviceVectorEntry ums_device_entry : this->ums_devices) values.push_back(ums_device_entry.second);
/* Update SelectListItem values. */
/* If the dropdown menu is already being displayed, it'll be reloaded or popped from the view stack, depending on whether the provided vector is empty or not. */
this->unmount_ums_device->updateValues(values);
});
this->addView(unmount_ums_device);
/* Update NSWDB XML. */
brls::ListItem *update_nswdb_xml = new brls::ListItem("options_tab/update_nswdb_xml/label"_i18n, "options_tab/update_nswdb_xml/description"_i18n);
@ -367,6 +423,10 @@ namespace nxdt::views
OptionsTab::~OptionsTab(void)
{
this->root_view->UnregisterUmsTaskListener(this->ums_task_sub);
this->ums_devices.clear();
brls::menu_timer_kill(&(this->notification_timer));
}

View File

@ -212,7 +212,10 @@ namespace nxdt::tasks
UmsTask::~UmsTask(void)
{
/* Clear UMS device vector. */
this->ums_devices.clear();
this->ums_devices_vector.clear();
/* Free UMS devices buffer. */
if (this->ums_devices) free(this->ums_devices);
LOG_MSG_DEBUG("UMS task stopped.");
}
@ -230,35 +233,49 @@ namespace nxdt::tasks
this->PopulateUmsDeviceVector();
/* Fire task event. */
this->ums_event.fire(this->ums_devices);
this->ums_event.fire(this->ums_devices_vector);
}
}
const UmsDeviceVector& UmsTask::GetUmsDevices(void)
{
return this->ums_devices;
return this->ums_devices_vector;
}
void UmsTask::PopulateUmsDeviceVector(void)
{
UsbHsFsDevice *ums_devices = nullptr;
u32 ums_device_count = 0;
/* Clear UMS device vector. */
this->ums_devices.clear();
this->ums_devices_vector.clear();
/* Free UMS devices buffer. */
if (this->ums_devices) free(this->ums_devices);
/* Reset UMS devices counter. */
this->ums_devices_count = 0;
/* Get UMS devices. */
ums_devices = umsGetDevices(&ums_device_count);
if (ums_devices)
this->ums_devices = umsGetDevices(&(this->ums_devices_count));
if (this->ums_devices)
{
/* Fill UMS device vector. */
for(u32 i = 0; i < ums_device_count; i++) this->ums_devices.push_back(ums_devices[i]);
for(u32 i = 0; i < this->ums_devices_count; i++)
{
const UsbHsFsDevice *cur_ums_device = &(this->ums_devices[i]);
int name_len = static_cast<int>(strlen(cur_ums_device->name) - 1);
std::string ums_info{};
/* Free UMS devices array. */
free(ums_devices);
if (cur_ums_device->product_name[0])
{
ums_info = fmt::format("{1:.{0}} ({2}, LUN #{3}, FS#{4}, {5})", name_len, cur_ums_device->name, cur_ums_device->product_name, cur_ums_device->lun, cur_ums_device->fs_idx, LIBUSBHSFS_FS_TYPE_STR(cur_ums_device->fs_type));
} else {
ums_info = fmt::format("{1:.{0}} (LUN #{2}, FS#{3}, {4})", name_len, cur_ums_device->name, cur_ums_device->lun, cur_ums_device->fs_idx, LIBUSBHSFS_FS_TYPE_STR(cur_ums_device->fs_type));
}
this->ums_devices_vector.push_back(std::make_pair(cur_ums_device, ums_info));
}
}
LOG_MSG_DEBUG("Retrieved info for %u UMS %s.", ums_device_count, ums_device_count == 1 ? "device" : "devices");
LOG_MSG_DEBUG("Retrieved info for %u UMS %s.", this->ums_devices_count, this->ums_devices_count == 1 ? "device" : "devices");
}
/* USB host device connection task. */