feat(lib): answer FolderContentsRequest with the folder's listing
"Download folder" in SoulseekQt and Nicotine+ asks the sharer for that one folder (peer code 36) instead of the whole share, and a sharer that never answers leaves the folder download hanging. The requested folder now comes back as a FolderContentsResponse (code 37), which carries a directory listing in the same form a browse does, so the two share one codec. The browse and folder replies also share one reply helper.
This commit is contained in:
@@ -0,0 +1,68 @@
|
||||
//! `FolderContentsRequest` (peer code 36) and its reply (code 37): one folder
|
||||
//! of a share in the listing form code 5 uses, which is what "download
|
||||
//! folder" in other clients asks for.
|
||||
|
||||
use crate::message::peer::SharedDirectory;
|
||||
use crate::message::peer::shared_file_list::{
|
||||
decompress_body, read_directories, write_directories,
|
||||
};
|
||||
use crate::message::{Message, MessageHandler};
|
||||
use crate::peer::PeerMessage;
|
||||
use crate::utils::zlib::compress_stored;
|
||||
use std::sync::mpsc::Sender;
|
||||
|
||||
pub struct FolderContentsRequest;
|
||||
impl MessageHandler<PeerMessage> for FolderContentsRequest {
|
||||
fn get_code(&self) -> u32 {
|
||||
36
|
||||
}
|
||||
fn handle(&self, message: &mut Message, sender: Sender<PeerMessage>) {
|
||||
let token = message.read_int32();
|
||||
let folder = message.read_string();
|
||||
let _ =
|
||||
sender.send(PeerMessage::FolderContentsRequested { token, folder });
|
||||
}
|
||||
}
|
||||
|
||||
/// Build a `FolderContentsResponse` (peer code 37).
|
||||
#[must_use]
|
||||
pub fn build_folder_contents(
|
||||
token: u32,
|
||||
folder: &str,
|
||||
dirs: &[SharedDirectory],
|
||||
) -> Message {
|
||||
let mut payload = Message::new();
|
||||
payload.write_int32(token).write_string(folder);
|
||||
write_directories(&mut payload, dirs);
|
||||
Message::new()
|
||||
.write_int32(37)
|
||||
.write_raw_bytes(compress_stored(&payload.get_data()))
|
||||
.clone()
|
||||
}
|
||||
|
||||
/// Parse a `FolderContentsResponse` positioned at its compressed blob, or
|
||||
/// `None` when it does not inflate.
|
||||
#[must_use]
|
||||
pub fn parse_folder_contents(
|
||||
message: &mut Message,
|
||||
) -> Option<(u32, String, Vec<SharedDirectory>)> {
|
||||
let mut body = decompress_body(message)?;
|
||||
let token = body.read_int32();
|
||||
let folder = body.read_string();
|
||||
Some((token, folder, read_directories(&mut body)))
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_folder_listing_round_trips() {
|
||||
let dirs = vec![SharedDirectory {
|
||||
name: "music\\album".to_string(),
|
||||
files: vec![("one.flac".to_string(), 40), ("two.flac".to_string(), 50)],
|
||||
}];
|
||||
let built = build_folder_contents(7, "music\\album", &dirs);
|
||||
let mut message = Message::new_with_data(built.get_data());
|
||||
message.set_pointer(4);
|
||||
assert_eq!(
|
||||
parse_folder_contents(&mut message),
|
||||
Some((7, "music\\album".to_string(), dirs))
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user