diff --git a/src/ui/App.hpp b/src/ui/App.hpp index de86d0a..b86ce8c 100644 --- a/src/ui/App.hpp +++ b/src/ui/App.hpp @@ -83,6 +83,8 @@ private: void start_download(uint32_t handle, uint32_t storage_id, const std::string& filename, uint64_t size); void start_upload(const fs::path& src_path, uint64_t file_size); + // Recursively create a remote folder and enqueue files under parent_handle. + void upload_local_tree(const fs::path& dir, uint32_t parent_handle, uint32_t storage_id); // Recursively download an MTP association (folder) into dest (must hold device_mtx_). void download_remote_tree(uint32_t handle, uint32_t storage_id, const fs::path& dest); diff --git a/src/ui/App.mm b/src/ui/App.mm index 0882985..172d404 100644 --- a/src/ui/App.mm +++ b/src/ui/App.mm @@ -275,6 +275,49 @@ void App::start_upload(const fs::path& src, uint64_t sz) { src.filename().string(), sz); set_status("status.uploading", {src.filename().string()}); } + +void App::upload_local_tree(const fs::path& dir, uint32_t parent_handle, uint32_t storage_id) { + if (!engine_ || !ops_) return; + const std::string name = dir.filename().string(); + if (name.empty() || name == "." || name == "..") return; + + uint32_t dir_handle = 0; + try { + std::lock_guard lock(device_mtx_); + if (!ops_) return; + dir_handle = ops_->create_directory(storage_id, parent_handle, name); + } catch (const std::exception& e) { + set_status("status.listing_error", {e.what()}); + return; + } catch (...) { + set_status("status.listing_error", {"Failed to create folder: " + name}); + return; + } + if (dir_handle == 0) { + set_status("status.listing_error", {"Failed to create folder: " + name}); + return; + } + + std::error_code ec; + for (fs::directory_iterator it(dir, ec), end; !ec && it != end; it.increment(ec)) { + const auto& entry = *it; + const std::string child_name = entry.path().filename().string(); + if (child_name.empty() || child_name == "." || child_name == "..") continue; + // Skip macOS clutter that Switch MTP installers don't need. + if (child_name == ".DS_Store" || child_name.rfind("._", 0) == 0) continue; + + std::error_code st_ec; + if (entry.is_directory(st_ec)) { + upload_local_tree(entry.path(), dir_handle, storage_id); + } else if (entry.is_regular_file(st_ec)) { + auto sz = entry.file_size(st_ec); + if (st_ec) continue; + engine_->enqueue_upload(entry.path().string(), dir_handle, storage_id, + child_name, sz); + } + } +} + void App::set_status(const std::string& key, std::vector args, float dur) { status_key_ = key; status_args_ = std::move(args); @@ -381,13 +424,8 @@ void App::process_finder_drops() { drops.swap(finder_drops_); } for (auto& dropped : drops) { - fs::path p(dropped); - std::error_code ec; - if (fs::is_directory(p, ec)) continue; - if (!ec && connected_.load()) { - auto sz = fs::file_size(p, ec); - if (!ec) start_upload(p, sz); - } + if (connected_.load()) + do_upload(dropped); } } @@ -419,8 +457,17 @@ void App::set_active_storage(uint32_t id) { } void App::request_refresh_remote() { refresh_remote(); } void App::do_upload(const std::string& src) { + if (!connected_.load() || !engine_ || remote_nav_stack_.empty()) return; fs::path p(src); std::error_code ec; + if (fs::is_directory(p, ec)) { + const auto& top = remote_nav_stack_.back(); + set_status("status.uploading", {p.filename().string()}); + upload_local_tree(p, top.handle, top.storage_id); + remote_needs_refresh_ = true; + return; + } + if (ec) return; auto sz = fs::file_size(p, ec); if (!ec) start_upload(p, sz); } diff --git a/src/ui/WebUI.mm b/src/ui/WebUI.mm index b094613..5b9dedf 100644 --- a/src/ui/WebUI.mm +++ b/src/ui/WebUI.mm @@ -410,7 +410,7 @@ static NSString* OmniPreferredDownloadURL(NSDictionary* release) { dispatch_async(dispatch_get_main_queue(), ^{ NSOpenPanel* panel = [NSOpenPanel openPanel]; panel.canChooseFiles = YES; - panel.canChooseDirectories = NO; + panel.canChooseDirectories = YES; panel.allowsMultipleSelection = YES; [panel beginWithCompletionHandler:^(NSModalResponse r) { if (r == NSModalResponseOK) { @@ -872,9 +872,10 @@ static const CGFloat kTrafficLightInset = 72.0; NSString* path = url.path; if (!path) continue; BOOL isDir = NO; - if ([fm fileExistsAtPath:path isDirectory:&isDir] && !isDir) - _app->do_upload(path.UTF8String); - handled = YES; + if ([fm fileExistsAtPath:path isDirectory:&isDir]) { + self->_app->do_upload(path.UTF8String); + handled = YES; + } } return handled; }