feat: browse your own shares as a peer receives them
The window could show any peer's share tree but never this account's, so "am I actually sharing that?" had no answer short of asking a friend to browse you. The tree is already there; what was missing was feeding it from our own index. B opens it — the same collapsible tree, filter, and navigation the peer browse offers, built from `Shares::directories()`, which is the listing GetShareFileList answers with. So what it shows is what the network sees, not what the config meant to share. r re-scans the disk rather than re-asking a peer, o steps across to the folders that feed it, and nothing here queues a download from ourselves. Attached to a daemon the index lives on the daemon's host, so it comes back over a new `shares.files` method rather than being scanned locally; `shares files [--filter]` prints the same listing headless, through the `emit_listing` that `browse` now shares with it — the two are only comparable while they are built the same way. Two allocation fixes came out of the review, both on paths this reaches: the daemon hands its listing to the DTO instead of deep-copying one it already owns, and `Shares::directories()` allocates its map key once per folder rather than once per file, which the peer-facing serve path has been paying for all along.
This commit is contained in:
@@ -2299,6 +2299,87 @@ fn shares_status_reports_what_the_network_will_see() {
|
||||
assert!(record["folders"].as_u64().is_some_and(|n| n >= 1));
|
||||
}
|
||||
|
||||
/// The index, file by file, in the records `browse` gives for a peer — the
|
||||
/// point being that the two are comparable: what the network sees of us
|
||||
/// reads exactly like what it sees of anyone else.
|
||||
#[test]
|
||||
fn shares_files_lists_the_index_as_a_peer_would_receive_it() {
|
||||
let server = server_or_skip!();
|
||||
let share = Scratch::new("share");
|
||||
std::fs::create_dir_all(share.path().join("album")).expect("subfolder");
|
||||
std::fs::write(share.path().join("top.bin"), probe_bytes())
|
||||
.expect("share file");
|
||||
std::fs::write(share.path().join("album").join("deep.bin"), probe_bytes())
|
||||
.expect("share file");
|
||||
|
||||
let listed = cli(
|
||||
&server,
|
||||
"cli_e2e_share_files",
|
||||
&[
|
||||
"--shared-dir",
|
||||
&share.display(),
|
||||
"--json",
|
||||
"shares",
|
||||
"files",
|
||||
],
|
||||
);
|
||||
assert_eq!(code(&listed), EXIT_OK, "stderr: {}", stderr(&listed));
|
||||
|
||||
let paths: Vec<String> = records(&listed)
|
||||
.iter()
|
||||
.map(|line| {
|
||||
serde_json::from_str::<serde_json::Value>(line).expect("JSON")
|
||||
["path"]
|
||||
.as_str()
|
||||
.expect("a path")
|
||||
.to_string()
|
||||
})
|
||||
.collect();
|
||||
assert_eq!(paths.len(), 2, "both files, got {paths:?}");
|
||||
assert!(
|
||||
paths.iter().any(|p| p.ends_with("album\\deep.bin")),
|
||||
"the nested file carries the backslash path peers download by: \
|
||||
{paths:?}"
|
||||
);
|
||||
|
||||
let first = json_record(&listed);
|
||||
assert_eq!(first["user"], "cli_e2e_share_files");
|
||||
assert!(first["size"].as_u64().is_some_and(|size| size > 0));
|
||||
|
||||
// The filter narrows to matching paths, and finding nothing is exit 4
|
||||
// rather than an empty success.
|
||||
let narrowed = cli(
|
||||
&server,
|
||||
"cli_e2e_share_files",
|
||||
&[
|
||||
"--shared-dir",
|
||||
&share.display(),
|
||||
"--json",
|
||||
"shares",
|
||||
"files",
|
||||
"--filter",
|
||||
"DEEP",
|
||||
],
|
||||
);
|
||||
assert_eq!(code(&narrowed), EXIT_OK, "stderr: {}", stderr(&narrowed));
|
||||
assert_eq!(records(&narrowed).len(), 1, "case-insensitively");
|
||||
|
||||
let missing = cli(
|
||||
&server,
|
||||
"cli_e2e_share_files",
|
||||
&[
|
||||
"--shared-dir",
|
||||
&share.display(),
|
||||
"--json",
|
||||
"shares",
|
||||
"files",
|
||||
"--filter",
|
||||
"nothing-like-this",
|
||||
],
|
||||
);
|
||||
assert_eq!(code(&missing), EXIT_NO_RESULTS);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn shares_reindex_picks_up_a_file_added_after_startup() {
|
||||
let server = server_or_skip!();
|
||||
|
||||
@@ -126,6 +126,41 @@ fn finished(tui: &MainTui, _: &str) -> bool {
|
||||
tui.state().searches[0].status == SearchStatus::Completed
|
||||
}
|
||||
|
||||
/// The window's answer to "am I actually sharing that?", against a real
|
||||
/// scan of a real folder rather than a stand-in listing.
|
||||
#[test]
|
||||
fn shift_b_shows_the_scanned_index_the_way_a_peer_would_get_it() {
|
||||
let server = server_or_skip!();
|
||||
|
||||
let share = tempfile::tempdir().expect("share dir");
|
||||
std::fs::create_dir_all(share.path().join("album")).expect("subfolder");
|
||||
std::fs::write(
|
||||
share.path().join("album").join("tui_probe_own.bin"),
|
||||
[3u8; 32],
|
||||
)
|
||||
.expect("share file");
|
||||
|
||||
let state = tempfile::tempdir().expect("state dir");
|
||||
let downloads = tempfile::tempdir().expect("download dir");
|
||||
let me = Arc::new(server.client(
|
||||
"tui_e2e_own_shares",
|
||||
vec![share.path().display().to_string()],
|
||||
));
|
||||
let mut window = Window::open(me, state.path(), downloads.path());
|
||||
|
||||
window.press(KeyCode::Char('B'));
|
||||
window.press(KeyCode::Char('L'));
|
||||
let screen = window.frame();
|
||||
assert!(screen.contains("My shares"), "{screen}");
|
||||
assert!(
|
||||
screen.contains("album") && screen.contains("tui_probe_own.bin"),
|
||||
"the scanned tree, folder by folder:\n{screen}"
|
||||
);
|
||||
|
||||
window.press(KeyCode::Esc);
|
||||
assert!(!window.tui.state().show_browse, "Esc hides the popup");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn shift_s_runs_a_restored_query_again_and_the_answers_land_on_screen() {
|
||||
let server = server_or_skip!();
|
||||
|
||||
Reference in New Issue
Block a user