Skip to content

Commit a7002f1

Browse files
committed
Add live social/sharing example binaries
Args-based (no hardcoded credentials) examples used to exercise the client against a real Peergos server: - follow_send_live: send a follow request - complete_friend_share_live: accept/process a pending follow request, then read-share a file with the friend - create_writeshare_live: create a file and share it writable - mkdir_writeshare_live: create a directory and share it writable - read_dir_live: list a directory and sanity-check any PDFs
1 parent 43c4e15 commit a7002f1

5 files changed

Lines changed: 253 additions & 0 deletions

File tree

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
//! Complete a friendship (process any pending follow request from `target`) and
2+
//! read-share a file with them, on a live server.
3+
//! cargo run -p peergos-fs --example complete_friend_share_live -- <base> <username> <password> <target> <file-name>
4+
5+
use peergos_core::mutable::HttpMutablePointers;
6+
use peergos_core::{ContentAddressedStorage, HttpStorage, ReqwestPoster};
7+
use std::sync::Arc;
8+
9+
#[tokio::main]
10+
async fn main() -> Result<(), Box<dyn std::error::Error>> {
11+
let mut args = std::env::args().skip(1);
12+
let base = args.next().unwrap_or_else(|| "https://test.peergos.net/".to_string());
13+
let username = args.next().expect("usage: <base> <username> <password> <target> <file-name>");
14+
let password = args.next().expect("usage: <base> <username> <password> <target> <file-name>");
15+
let target = args.next().expect("usage: <base> <username> <password> <target> <file-name>");
16+
let file_name = args.next().expect("usage: <base> <username> <password> <target> <file-name>");
17+
18+
let poster = Arc::new(ReqwestPoster::new(&base, false)?);
19+
let store: Arc<dyn ContentAddressedStorage> = Arc::new(HttpStorage::new(poster.clone(), true));
20+
let mutable = HttpMutablePointers::new(poster.clone());
21+
22+
let user = peergos_fs::login(&username, &password, poster.as_ref(), store.clone(), &mutable, None).await?;
23+
println!("logged in as {username}");
24+
25+
// --- Complete the friendship: handle any follow request from `target` -----
26+
let pending = peergos_fs::get_pending_outgoing(&user, store.clone(), &mutable).await?;
27+
let we_asked_them = pending.iter().any(|u| u == &target);
28+
let requests = peergos_fs::get_follow_requests(&user, poster.as_ref()).await?;
29+
let from_target = requests.into_iter().find(|r| r.sender() == Some(target.as_str()));
30+
31+
match from_target {
32+
Some(req) => {
33+
if we_asked_them {
34+
// They accepted our request: process their reply so we follow them.
35+
peergos_fs::process_follow_reply(&user, &req, poster.as_ref(), store.clone(), &mutable).await?;
36+
println!("processed {target}'s reply to our follow request — friendship complete");
37+
} else {
38+
// A fresh request from them: accept and reciprocate.
39+
peergos_fs::accept_follow_request(&user, &req, true, poster.as_ref(), store.clone(), &mutable).await?;
40+
println!("accepted {target}'s follow request (reciprocated) — friendship complete");
41+
}
42+
}
43+
None => {
44+
println!(
45+
"no pending follow request from {target} yet (we_asked_them={we_asked_them}); \
46+
they still need to accept before the friendship is mutual. Sharing anyway."
47+
);
48+
}
49+
}
50+
51+
// Re-login to pick up the updated social state, then confirm friends.
52+
let user = peergos_fs::login(&username, &password, poster.as_ref(), store.clone(), &mutable, None).await?;
53+
let friends = peergos_fs::get_friends(&user, store.clone(), &mutable).await?;
54+
println!("friends: {:?}", friends.iter().map(|e| e.owner_name.clone()).collect::<Vec<_>>());
55+
56+
// --- Share the file read-only with `target` -------------------------------
57+
let home = user.home().ok_or("no home directory")?;
58+
let entry = peergos_fs::list_directory(home, store.clone(), &mutable)
59+
.await?
60+
.into_iter()
61+
.find(|e| e.name == file_name)
62+
.ok_or_else(|| format!("no file named {file_name:?} in home"))?;
63+
peergos_fs::share_read_access(&user, &file_name, &entry.cap, &target, store.clone(), &mutable).await?;
64+
println!("read-shared {file_name:?} with {target}");
65+
66+
// Show who the file is now shared with.
67+
let shared = peergos_fs::get_shared_with(&user, &file_name, peergos_fs::Access::Read, store.clone(), &mutable).await?;
68+
println!("{file_name:?} is now read-shared with: {shared:?}");
69+
Ok(())
70+
}
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
//! Create a file in home and share it WRITABLE with a target user, on a live server.
2+
//! cargo run -p peergos-fs --example create_writeshare_live -- <base> <username> <password> <target>
3+
4+
use peergos_core::mutable::HttpMutablePointers;
5+
use peergos_core::{ContentAddressedStorage, HttpStorage, ReqwestPoster};
6+
use std::sync::Arc;
7+
8+
#[tokio::main]
9+
async fn main() -> Result<(), Box<dyn std::error::Error>> {
10+
let mut args = std::env::args().skip(1);
11+
let base = args.next().unwrap_or_else(|| "https://test.peergos.net/".to_string());
12+
let username = args.next().expect("usage: <base> <username> <password> <target>");
13+
let password = args.next().expect("usage: <base> <username> <password> <target>");
14+
let target = args.next().expect("usage: <base> <username> <password> <target>");
15+
16+
let poster = Arc::new(ReqwestPoster::new(&base, false)?);
17+
let store: Arc<dyn ContentAddressedStorage> = Arc::new(HttpStorage::new(poster.clone(), true));
18+
let mutable = HttpMutablePointers::new(poster.clone());
19+
20+
let name = "test.md";
21+
let content = b"# Test\n\nThis is a collaboratively editable test file created by the Rust Peergos client.\n";
22+
23+
// Create the file in home.
24+
let user = peergos_fs::login(&username, &password, poster.as_ref(), store.clone(), &mutable, None).await?;
25+
let home = user.home().ok_or("no home directory")?.clone();
26+
let signer = peergos_fs::recover_signer(&home, store.clone(), &mutable).await?;
27+
peergos_fs::upload_file(&home, name, content, None, Some(signer), user.mirror_bat_id().as_ref(), store.clone(), &mutable).await?;
28+
println!("created {name:?} ({} bytes) in /{username}", content.len());
29+
30+
// Re-login for fresh state, then write-share it with the target.
31+
let user = peergos_fs::login(&username, &password, poster.as_ref(), store.clone(), &mutable, None).await?;
32+
let home = user.home().ok_or("no home directory")?.clone();
33+
peergos_fs::share_write_access(&user, "", &home, name, &target, store.clone(), &mutable).await?;
34+
println!("write-shared {name:?} with {target}");
35+
36+
// Confirm.
37+
let user = peergos_fs::login(&username, &password, poster.as_ref(), store.clone(), &mutable, None).await?;
38+
let shared = peergos_fs::get_shared_with(&user, name, peergos_fs::Access::Write, store.clone(), &mutable).await?;
39+
println!("{name:?} is now write-shared with: {shared:?}");
40+
Ok(())
41+
}
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
//! Send a follow request to a target user on a live server.
2+
//! cargo run -p peergos-fs --example follow_send_live -- <base> <username> <password> <target>
3+
4+
use peergos_core::mutable::HttpMutablePointers;
5+
use peergos_core::{ContentAddressedStorage, HttpStorage, ReqwestPoster};
6+
use std::sync::Arc;
7+
8+
#[tokio::main]
9+
async fn main() -> Result<(), Box<dyn std::error::Error>> {
10+
let mut args = std::env::args().skip(1);
11+
let base = args.next().unwrap_or_else(|| "https://test.peergos.net/".to_string());
12+
let username = args.next().expect("usage: follow_send_live <base> <username> <password> <target>");
13+
let password = args.next().expect("usage: follow_send_live <base> <username> <password> <target>");
14+
let target = args.next().expect("usage: follow_send_live <base> <username> <password> <target>");
15+
16+
let poster = Arc::new(ReqwestPoster::new(&base, false)?);
17+
let store: Arc<dyn ContentAddressedStorage> = Arc::new(HttpStorage::new(poster.clone(), true));
18+
let mutable = HttpMutablePointers::new(poster.clone());
19+
20+
let user = peergos_fs::login(&username, &password, poster.as_ref(), store.clone(), &mutable, None).await?;
21+
println!("logged in as {username}; sending a follow request to {target} ...");
22+
let sent = peergos_fs::send_follow_request(&user, &target, true, poster.as_ref(), store.clone(), &mutable).await?;
23+
println!("follow request sent = {sent}");
24+
if !sent {
25+
return Err("server rejected the follow request (already pending, or already following?)".into());
26+
}
27+
Ok(())
28+
}
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
//! Create a directory in home and share it WRITABLE with a target user, live.
2+
//! cargo run -p peergos-fs --example mkdir_writeshare_live -- <base> <username> <password> <target> <dir-name>
3+
4+
use peergos_core::mutable::HttpMutablePointers;
5+
use peergos_core::{ContentAddressedStorage, HttpStorage, ReqwestPoster};
6+
use std::sync::Arc;
7+
8+
#[tokio::main]
9+
async fn main() -> Result<(), Box<dyn std::error::Error>> {
10+
let mut args = std::env::args().skip(1);
11+
let base = args.next().unwrap_or_else(|| "https://test.peergos.net/".to_string());
12+
let username = args.next().expect("usage: <base> <username> <password> <target> <dir-name>");
13+
let password = args.next().expect("usage: <base> <username> <password> <target> <dir-name>");
14+
let target = args.next().expect("usage: <base> <username> <password> <target> <dir-name>");
15+
let dir = args.next().unwrap_or_else(|| "write".to_string());
16+
17+
let poster = Arc::new(ReqwestPoster::new(&base, false)?);
18+
let store: Arc<dyn ContentAddressedStorage> = Arc::new(HttpStorage::new(poster.clone(), true));
19+
let mutable = HttpMutablePointers::new(poster.clone());
20+
21+
// Create the directory in home.
22+
let user = peergos_fs::login(&username, &password, poster.as_ref(), store.clone(), &mutable, None).await?;
23+
let home = user.home().ok_or("no home directory")?.clone();
24+
let signer = peergos_fs::recover_signer(&home, store.clone(), &mutable).await?;
25+
peergos_fs::create_directory(&home, &dir, Some(signer), user.mirror_bat_id().as_ref(), store.clone(), &mutable).await?;
26+
println!("created directory {dir:?} in /{username}");
27+
28+
// Re-login for fresh state, then write-share it with the target.
29+
let user = peergos_fs::login(&username, &password, poster.as_ref(), store.clone(), &mutable, None).await?;
30+
let home = user.home().ok_or("no home directory")?.clone();
31+
peergos_fs::share_write_access(&user, "", &home, &dir, &target, store.clone(), &mutable).await?;
32+
println!("write-shared {dir:?} with {target}");
33+
34+
let user = peergos_fs::login(&username, &password, poster.as_ref(), store.clone(), &mutable, None).await?;
35+
let shared = peergos_fs::get_shared_with(&user, &dir, peergos_fs::Access::Write, store.clone(), &mutable).await?;
36+
println!("{dir:?} is now write-shared with: {shared:?}");
37+
Ok(())
38+
}
Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
//! List a directory on a live server and sanity-check any PDF files in it.
2+
//! cargo run -p peergos-fs --example read_dir_live -- <base> <username> <password> <dir-path>
3+
4+
use peergos_core::mutable::{HttpMutablePointers, MutablePointers};
5+
use peergos_core::{ContentAddressedStorage, HttpPoster, HttpStorage, ReqwestPoster};
6+
use peergos_fs::UserContext;
7+
use std::sync::Arc;
8+
9+
#[tokio::main]
10+
async fn main() -> Result<(), Box<dyn std::error::Error>> {
11+
let mut args = std::env::args().skip(1);
12+
let base = args.next().unwrap_or_else(|| "https://test.peergos.net/".to_string());
13+
let username = args.next().expect("usage: <base> <username> <password> <dir-path>");
14+
let password = args.next().expect("usage: <base> <username> <password> <dir-path>");
15+
let dir_path = args.next().expect("usage: <base> <username> <password> <dir-path>");
16+
17+
let poster: Arc<dyn HttpPoster> = Arc::new(ReqwestPoster::new(&base, false)?);
18+
let store: Arc<dyn ContentAddressedStorage> = Arc::new(HttpStorage::new(Arc::new(ReqwestPoster::new(&base, false)?), true));
19+
let mutable: Arc<dyn MutablePointers> = Arc::new(HttpMutablePointers::new(Arc::new(ReqwestPoster::new(&base, false)?)));
20+
21+
let ctx = UserContext::sign_in(&username, &password, None, poster, store, mutable).await?;
22+
println!("signed in as {username}");
23+
24+
let dir = ctx.get_by_path(&dir_path).await?.ok_or_else(|| format!("no directory at {dir_path}"))?;
25+
let children = dir.children().await?;
26+
println!("{dir_path} has {} entries:", children.len());
27+
for c in &children {
28+
let kind = if c.is_directory() { "dir " } else { "file" };
29+
println!(" [{kind}] {} ({} bytes)", c.name(), c.size());
30+
}
31+
32+
for c in &children {
33+
if c.is_directory() {
34+
continue;
35+
}
36+
let data = c.read().await?;
37+
if data.starts_with(b"%PDF-") {
38+
let version = std::str::from_utf8(&data[5..8]).unwrap_or("?");
39+
let has_eof = data.windows(5).rev().take(1024).any(|w| w == b"%%EOF")
40+
|| data.windows(5).any(|w| w == b"%%EOF");
41+
// Rough page count: occurrences of "/Type /Page" not followed by 's'.
42+
let pages = count_pages(&data);
43+
println!(
44+
"\nPDF {:?}: {} bytes, version {version}, valid trailer={has_eof}, ~{pages} page(s)",
45+
c.name(),
46+
data.len(),
47+
);
48+
} else if c.name().to_lowercase().ends_with(".pdf") {
49+
println!("\n{:?} is named .pdf but does not start with %PDF- (first bytes: {:02x?})", c.name(), &data[..data.len().min(8)]);
50+
}
51+
}
52+
Ok(())
53+
}
54+
55+
/// Rough page count: `/Type /Page` (optionally with whitespace) but not `/Pages`.
56+
fn count_pages(data: &[u8]) -> usize {
57+
let needle = b"/Type";
58+
let mut count = 0;
59+
let mut i = 0;
60+
while let Some(pos) = find(&data[i..], needle) {
61+
let start = i + pos + needle.len();
62+
// skip whitespace and a possible '/'
63+
let rest = &data[start..(start + 8).min(data.len())];
64+
let s: String = rest.iter().map(|&b| b as char).collect();
65+
let s = s.trim_start().trim_start_matches('/');
66+
if s.starts_with("Page") && !s.starts_with("Pages") {
67+
count += 1;
68+
}
69+
i = start;
70+
}
71+
count
72+
}
73+
74+
fn find(haystack: &[u8], needle: &[u8]) -> Option<usize> {
75+
haystack.windows(needle.len()).position(|w| w == needle)
76+
}

0 commit comments

Comments
 (0)