[feat] add dual-pane file transfers and directory browsing, refs #135

This commit is contained in:
dijunkun
2026-11-05 15:27:53 +08:00
parent 6f7c5a29ab
commit 751cf04434
26 changed files with 2624 additions and 87 deletions
@@ -74,6 +74,15 @@ class Session {
std::lock_guard<std::mutex> lock(mutex_);
directory_ = std::move(directory);
}
// A browser destination belongs to one wire ID, never to the whole peer.
// Keep reservations until consumed: other queued files may take hours.
bool ReserveDirectory(uint32_t id, const std::filesystem::path& directory) {
std::lock_guard<std::mutex> lock(mutex_);
if (closed_ || !id || incoming_.count(id) || cancelled_incoming_.count(id) ||
destinations_.size() >= 8192)
return false;
return destinations_.emplace(id, directory).second;
}
bool Start(const std::filesystem::path& path, const std::string& name,
Clock::time_point now = Clock::now(), uint32_t id = 0) {
std::lock_guard<std::mutex> lock(mutex_);
@@ -240,6 +249,7 @@ class Session {
(it != incoming_.end() && h.total_size != it->second.total))
return ack;
cancelled_incoming_[h.file_id] = now;
destinations_.erase(h.file_id);
if (it != incoming_.end()) {
NotifyReceive(h.file_id, it->second, ReceiveStatus::Cancelled);
Notify(it->second.name, -1, false, "");
@@ -253,6 +263,7 @@ class Session {
if (cancelled_incoming_.count(h.file_id))
return {kFileAckMagic, h.file_id, 0, h.total_size, 2};
auto reject = [&]() {
destinations_.erase(h.file_id);
if (it != incoming_.end()) {
NotifyReceive(h.file_id, it->second, ReceiveStatus::Failed);
Notify(it->second.name, -1, false, ""); Remove(it->second); incoming_.erase(it);
@@ -275,6 +286,10 @@ class Session {
Incoming file;
file.name = SafeName(chunk.file_name); file.total = h.total_size;
file.directory = directory_.empty() ? std::filesystem::path(".") : directory_;
if (const auto destination = destinations_.find(h.file_id); destination != destinations_.end()) {
file.directory = destination->second;
destinations_.erase(destination);
}
std::error_code ec;
std::filesystem::create_directories(file.directory, ec);
if (ec || (directory_.empty() && options_.receive_subdirectory)) return reject();
@@ -435,6 +450,7 @@ class Session {
for (auto& entry : incoming_) Remove(entry.second);
incoming_.clear();
cancelled_incoming_.clear();
destinations_.clear();
}
const std::chrono::seconds timeout_{30};
mutable std::mutex mutex_;
@@ -447,6 +463,7 @@ class Session {
std::filesystem::path directory_;
std::optional<Outgoing> outgoing_;
std::map<uint32_t, Incoming> incoming_;
std::map<uint32_t, std::filesystem::path> destinations_;
std::map<uint32_t, Clock::time_point> cancelled_incoming_;
};
+2
View File
@@ -145,6 +145,8 @@ struct HostInfo {
bool supports_deskhub_resolution;
bool supports_deskhub_fps15;
bool supports_deskhub_bandwidth;
// Optional; legacy peers keep the original single-file transfer UI.
bool supports_file_browser;
};
struct ServiceStatus {
+5 -1
View File
@@ -32,6 +32,7 @@ void ResetHostInfo(HostInfo& info) {
info.supports_deskhub_resolution = false;
info.supports_deskhub_fps15 = false;
info.supports_deskhub_bandwidth = false;
info.supports_file_browser = false;
}
void CopyHostName(HostInfo& info, const std::string& host_name) {
@@ -251,7 +252,8 @@ std::string RemoteAction::ToJson(const RemoteAction& action) {
{"supports_deskhub_preview", action.i.supports_deskhub_preview},
{"supports_deskhub_resolution", action.i.supports_deskhub_resolution},
{"supports_deskhub_fps15", action.i.supports_deskhub_fps15},
{"supports_deskhub_bandwidth", action.i.supports_deskhub_bandwidth}};
{"supports_deskhub_bandwidth", action.i.supports_deskhub_bandwidth},
{"supports_file_browser", action.i.supports_file_browser}};
break;
}
case ControlType::invalid:
@@ -476,6 +478,8 @@ bool RemoteAction::FromJson(const std::string& json_string,
host_info_object.value("supports_deskhub_fps15", false);
output.i.supports_deskhub_bandwidth =
host_info_object.value("supports_deskhub_bandwidth", false);
output.i.supports_file_browser =
host_info_object.value("supports_file_browser", false);
const std::string host_name =
host_info_object.at("host_name").get<std::string>();
CopyHostName(output.i, host_name);