webusb: add support for exporting. usb: block requests with no timeout, using pbox to cancel if the user presses B.

This commit is contained in:
ITotalJustice
2025-09-02 04:24:45 +01:00
parent 7bdec8457f
commit fd67da0527
14 changed files with 482 additions and 33 deletions

View File

@@ -11,6 +11,7 @@ namespace sphaira::ui {
struct ProgressBox;
using ProgressBoxCallback = std::function<Result(ProgressBox*)>;
using ProgressBoxDoneCallback = std::function<void(Result rc)>;
// using CancelCallback = std::function<void()>;
struct ProgressBox final : Widget {
ProgressBox(
@@ -39,6 +40,9 @@ struct ProgressBox final : Widget {
auto ShouldExit() -> bool;
auto ShouldExitResult() -> Result;
void AddCancelEvent(UEvent* event);
void RemoveCancelEvent(const UEvent* event);
// helper functions
auto CopyFile(fs::Fs* fs_src, fs::Fs* fs_dst, const fs::FsPath& src, const fs::FsPath& dst, bool single_threaded = false) -> Result;
auto CopyFile(fs::Fs* fs, const fs::FsPath& src, const fs::FsPath& dst, bool single_threaded = false) -> Result;
@@ -82,6 +86,7 @@ private:
Thread m_thread{};
ThreadData m_thread_data{};
ProgressBoxDoneCallback m_done{};
std::vector<UEvent*> m_cancel_events{};
// shared data start.
std::string m_action{};

View File

@@ -24,6 +24,14 @@ struct Usb {
// Result OpenFile(u32 index, s64& file_size);
Result CloseFile();
auto GetOpenResult() const {
return m_open_result;
}
auto GetCancelEvent() {
return m_usb->GetCancelEvent();
}
private:
Result SendAndVerify(const void* data, u32 size, u64 timeout, api::ResultPacket* out = nullptr);
Result SendAndVerify(const void* data, u32 size, api::ResultPacket* out = nullptr);

View File

@@ -25,6 +25,14 @@ struct Usb {
Result OpenFile(u32 index, s64& file_size);
Result CloseFile();
auto GetOpenResult() const {
return m_open_result;
}
auto GetCancelEvent() {
return m_usb->GetCancelEvent();
}
private:
Result SendAndVerify(const void* data, u32 size, u64 timeout, api::ResultPacket* out = nullptr);
Result SendAndVerify(const void* data, u32 size, api::ResultPacket* out = nullptr);

View File

@@ -29,6 +29,14 @@ struct Usb {
Result file_transfer_loop();
auto GetOpenResult() const {
return m_open_result;
}
auto GetCancelEvent() {
return m_usb->GetCancelEvent();
}
private:
Result SendResult(u32 result, u32 arg3 = 0, u32 arg4 = 0);

View File

@@ -43,6 +43,14 @@ struct Usb final : Base {
return m_usb->CloseFile();
}
auto GetOpenResult() const {
return m_usb->GetOpenResult();
}
auto GetCancelEvent() {
return m_usb->GetCancelEvent();
}
private:
std::unique_ptr<usb::install::Usb> m_usb{};
};

View File

@@ -165,6 +165,14 @@ struct WriteUsbSource final : WriteSource {
R_SUCCEED();
}
auto GetOpenResult() const {
return m_usb->GetOpenResult();
}
auto GetCancelEvent() {
return m_usb->GetCancelEvent();
}
private:
std::unique_ptr<usb::dump::Usb> m_usb{};
bool m_was_mtp_enabled{};
@@ -178,8 +186,8 @@ constexpr DumpLocationEntry DUMP_LOCATIONS[]{
};
struct UsbTest final : usb::upload::Usb, yati::source::Stream {
UsbTest(ui::ProgressBox* pbox, BaseSource* source, std::span<const fs::FsPath> paths)
: Usb{UINT64_MAX}
UsbTest(ui::ProgressBox* pbox, BaseSource* source, std::span<const fs::FsPath> paths, u64 timeout)
: Usb{timeout}
, m_pbox{pbox}
, m_source{source}
, m_paths{paths} {
@@ -248,6 +256,10 @@ struct UsbTest final : usb::upload::Usb, yati::source::Stream {
return m_pull_offset;
}
auto GetOpenResult() const {
return Usb::GetOpenResult();
}
private:
ui::ProgressBox* m_pbox{};
BaseSource* m_source{};
@@ -261,7 +273,14 @@ private:
};
Result DumpToUsb(ui::ProgressBox* pbox, BaseSource* source, std::span<const fs::FsPath> paths, const CustomTransfer& custom_transfer) {
auto write_source = std::make_unique<WriteUsbSource>(3e+9);
// create write source and verify that it opened.
constexpr u64 timeout = UINT64_MAX;
auto write_source = std::make_unique<WriteUsbSource>(timeout);
R_TRY(write_source->GetOpenResult());
// add cancel event.
pbox->AddCancelEvent(write_source->GetCancelEvent());
ON_SCOPE_EXIT(pbox->RemoveCancelEvent(write_source->GetCancelEvent()));
for (const auto& path : paths) {
const auto file_size = source->GetSize(path);
@@ -273,7 +292,7 @@ Result DumpToUsb(ui::ProgressBox* pbox, BaseSource* source, std::span<const fs::
while (true) {
R_TRY(pbox->ShouldExitResult());
const auto rc = write_source->WaitForConnection(path, 3e+9);
const auto rc = write_source->WaitForConnection(path, timeout);
if (R_SUCCEEDED(rc)) {
break;
}
@@ -398,8 +417,14 @@ Result DumpToUsbS2S(ui::ProgressBox* pbox, BaseSource* source, std::span<const f
file_list.emplace_back(path);
}
auto usb = std::make_unique<UsbTest>(pbox, source, paths);
constexpr u64 timeout = 3e+9;
// create usb test instance and verify that it opened.
constexpr u64 timeout = UINT64_MAX;
auto usb = std::make_unique<UsbTest>(pbox, source, paths, timeout);
R_TRY(usb->GetOpenResult());
// add cancel event.
pbox->AddCancelEvent(usb->GetCancelEvent());
ON_SCOPE_EXIT(pbox->RemoveCancelEvent(usb->GetCancelEvent()));
while (!pbox->ShouldExit()) {
if (R_SUCCEEDED(usb->IsUsbConnected(timeout))) {

View File

@@ -97,6 +97,9 @@ void Menu::Update(Controller* controller, TouchInfo* touch) {
m_state = State::Progress;
log_write("got connection\n");
App::Push<ui::ProgressBox>(0, "Installing "_i18n, "", [this](auto pbox) -> Result {
pbox->AddCancelEvent(m_usb_source->GetCancelEvent());
ON_SCOPE_EXIT(pbox->RemoveCancelEvent(m_usb_source->GetCancelEvent()));
log_write("inside progress box\n");
for (u32 i = 0; i < std::size(m_names); i++) {
const auto& file_name = m_names[i];

View File

@@ -232,79 +232,72 @@ auto ProgressBox::Draw(NVGcontext* vg, Theme* theme) -> void {
}
auto ProgressBox::SetActionName(const std::string& action) -> ProgressBox& {
mutexLock(&m_mutex);
SCOPED_MUTEX(&m_mutex);
m_action = action;
mutexUnlock(&m_mutex);
Yield();
return *this;
}
auto ProgressBox::SetTitle(const std::string& title) -> ProgressBox& {
mutexLock(&m_mutex);
SCOPED_MUTEX(&m_mutex);
m_title = title;
mutexUnlock(&m_mutex);
Yield();
return *this;
}
auto ProgressBox::NewTransfer(const std::string& transfer) -> ProgressBox& {
mutexLock(&m_mutex);
SCOPED_MUTEX(&m_mutex);
m_transfer = transfer;
m_size = 0;
m_offset = 0;
m_last_offset = 0;
m_timestamp.Update();
mutexUnlock(&m_mutex);
Yield();
return *this;
}
auto ProgressBox::ResetTranfser() -> ProgressBox& {
mutexLock(&m_mutex);
SCOPED_MUTEX(&m_mutex);
m_size = 0;
m_offset = 0;
m_last_offset = 0;
m_timestamp.Update();
mutexUnlock(&m_mutex);
Yield();
return *this;
}
auto ProgressBox::UpdateTransfer(s64 offset, s64 size) -> ProgressBox& {
mutexLock(&m_mutex);
SCOPED_MUTEX(&m_mutex);
m_size = size;
m_offset = offset;
mutexUnlock(&m_mutex);
Yield();
return *this;
}
auto ProgressBox::SetImage(int image) -> ProgressBox& {
mutexLock(&m_mutex);
SCOPED_MUTEX(&m_mutex);
m_image_pending = image;
m_is_image_pending = true;
mutexUnlock(&m_mutex);
return *this;
}
auto ProgressBox::SetImageData(std::vector<u8>& data) -> ProgressBox& {
mutexLock(&m_mutex);
SCOPED_MUTEX(&m_mutex);
std::swap(m_image_data, data);
mutexUnlock(&m_mutex);
return *this;
}
auto ProgressBox::SetImageDataConst(std::span<const u8> data) -> ProgressBox& {
mutexLock(&m_mutex);
SCOPED_MUTEX(&m_mutex);
m_image_data.resize(data.size());
std::memcpy(m_image_data.data(), data.data(), m_image_data.size());
mutexUnlock(&m_mutex);
return *this;
}
void ProgressBox::RequestExit() {
SCOPED_MUTEX(&m_mutex);
m_stop_source.request_stop();
ueventSignal(GetCancelEvent());
// cancel any registered events.
for (auto& e : m_cancel_events) {
ueventSignal(e);
}
}
auto ProgressBox::ShouldExit() -> bool {
@@ -318,6 +311,26 @@ auto ProgressBox::ShouldExitResult() -> Result {
R_SUCCEED();
}
void ProgressBox::AddCancelEvent(UEvent* event) {
if (!event) {
return;
}
SCOPED_MUTEX(&m_mutex);
if (std::ranges::find(m_cancel_events, event) == m_cancel_events.end()) {
m_cancel_events.emplace_back(event);
}
}
void ProgressBox::RemoveCancelEvent(const UEvent* event) {
if (!event) {
return;
}
SCOPED_MUTEX(&m_mutex);
m_cancel_events.erase(std::remove(m_cancel_events.begin(), m_cancel_events.end(), event), m_cancel_events.end());
}
auto ProgressBox::CopyFile(fs::Fs* fs_src, fs::Fs* fs_dst, const fs::FsPath& src_path, const fs::FsPath& dst_path, bool single_threaded) -> Result {
const auto is_file_based_emummc = App::IsFileBaseEmummc();
const auto is_both_native = fs_src->IsNative() && fs_dst->IsNative();

View File

@@ -36,7 +36,7 @@ Base::Base(u64 transfer_timeout) {
App::SetAutoSleepDisabled(true);
m_transfer_timeout = transfer_timeout;
ueventCreate(GetCancelEvent(), true);
ueventCreate(GetCancelEvent(), false);
m_aligned = std::make_unique<u8*>(new(std::align_val_t{TRANSFER_ALIGN}) u8[TRANSFER_MAX]);
}

View File

@@ -20,7 +20,7 @@ Usb::Usb(u64 transfer_timeout) {
Usb::~Usb() {
if (m_was_connected && R_SUCCEEDED(m_usb->IsUsbConnected(0))) {
const auto send_header = SendPacket::Build(CMD_QUIT);
SendAndVerify(&send_header, sizeof(send_header));
SendAndVerify(&send_header, sizeof(send_header), 1e+9);
}
}

View File

@@ -20,7 +20,7 @@ Usb::Usb(u64 transfer_timeout) {
Usb::~Usb() {
if (m_was_connected && R_SUCCEEDED(m_usb->IsUsbConnected(0))) {
const auto send_header = SendPacket::Build(CMD_QUIT);
SendAndVerify(&send_header, sizeof(send_header));
SendAndVerify(&send_header, sizeof(send_header), 1e+9);
}
}