testing md files
list.md edited
236 lines 7.3 kB view raw view rendered
1- [x] `at://` parsing and struct 2- [x] TID codecs 3- [x] XRPC client 4- [x] DID & handle resolution service with a cache 5- [ ] Structs with validation for the common lexicons 6 - [ ] Probably codegen for doing this with other lexicons 7- [ ] Extended XRPC client with support for validated inputs/outputs 8- [ ] Oauth stuff 9 10testing linkify: https://oppi.li oppi.li 11 12lets try `ul` items now: 13 14- foo 15- bar 16 17and ol: 18 191. foo 202. bar 213. baz 22 23```rust 24mod client; 25mod error; 26mod fs; 27mod resolver; 28 29use atrium_api::{client::AtpServiceClient, com, types}; 30use atrium_common::resolver::Resolver; 31use atrium_identity::identity_resolver::ResolvedIdentity; 32use atrium_repo::{Repository, blockstore::CarStore}; 33use atrium_xrpc_client::isahc::IsahcClient; 34use fuser::MountOption; 35use futures::{StreamExt, stream}; 36use indicatif::{MultiProgress, ProgressBar, ProgressStyle}; 37use std::{ 38 collections::HashMap, 39 io::{Cursor, Write}, 40 path::PathBuf, 41 sync::Arc, 42}; 43use xdg::BaseDirectories; 44 45fn main() { 46 let rt = tokio::runtime::Runtime::new().unwrap(); 47 let matches = clap::command!() 48 .arg( 49 clap::Arg::new("handles") 50 .index(1) 51 .required(true) 52 .num_args(1..) 53 .help("One or more handles to download and mount"), 54 ) 55 .arg( 56 clap::Arg::new("mountpoint") 57 .short('m') 58 .action(clap::ArgAction::Set) 59 .value_parser(clap::value_parser!(PathBuf)), 60 ) 61 .get_matches(); 62 let handles = matches 63 .get_many::<String>("handles") 64 .unwrap() 65 .cloned() 66 .collect::<Vec<_>>(); 67 let mountpoint = matches 68 .get_one::<PathBuf>("mountpoint") 69 .map(ToOwned::to_owned) 70 .unwrap_or(PathBuf::from("mnt")); 71 let _ = std::fs::create_dir_all(&mountpoint); 72 73 let resolver = Arc::new(resolver::id_resolver()); 74 let bars = Arc::new(MultiProgress::new()); 75 let repos = rt.block_on( 76 stream::iter(handles) 77 .then(|handle| { 78 let h = handle.clone(); 79 let r = Arc::clone(&resolver); 80 let b = Arc::clone(&bars); 81 async move { 82 let id = r.resolve(&h).await?; 83 let bytes = cached_download(&id, &b).await?; 84 let repo = build_repo(bytes).await?; 85 Ok::<_, error::Error>((id.did, repo)) 86 } 87 }) 88 .collect::<Vec<_>>(), 89 ); 90 let (success, errors): (Vec<_>, Vec<_>) = repos.into_iter().partition(|r| r.is_ok()); 91 for e in errors { 92 eprintln!("{:?}", e.as_ref().unwrap_err()); 93 } 94 let repos = success 95 .into_iter() 96 .map(|s| s.unwrap()) 97 .collect::<HashMap<_, _>>(); 98 99 // construct the fs 100 let mut fs = fs::PdsFs::new(); 101 for (did, repo) in repos { 102 rt.block_on(fs.add(did, repo)) 103 } 104 105 // mount 106 let options = vec![MountOption::RO, MountOption::FSName("pdsfs".to_string())]; 107 let join_handle = fuser::spawn_mount2(fs, &mountpoint, &options).unwrap(); 108 109 println!("mounted at {mountpoint:?}"); 110 print!("hit enter to unmount and exit..."); 111 std::io::stdout().flush().unwrap(); 112 113 // Wait for user input 114 let mut input = String::new(); 115 std::io::stdin().read_line(&mut input).unwrap(); 116 117 join_handle.join(); 118 std::fs::remove_dir(&mountpoint).unwrap(); 119 120 println!("unmounted {mountpoint:?}"); 121} 122 123async fn cached_download( 124 id: &ResolvedIdentity, 125 m: &MultiProgress, 126) -> Result<Vec<u8>, error::Error> { 127 let mut pb = ProgressBar::new_spinner(); 128 pb.set_style( 129 ProgressStyle::default_spinner() 130 .template("{spinner:.green} [{elapsed_precise}] {msg}") 131 .unwrap() 132 .tick_strings(&["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"]), 133 ); 134 pb.enable_steady_tick(std::time::Duration::from_millis(100)); 135 pb = m.add(pb); 136 137 let dirs = BaseDirectories::new(); 138 139 let dir = dirs 140 .get_cache_home() 141 .expect("$HOME is absent") 142 .join("pdsfs"); 143 tokio::fs::create_dir_all(&dir).await?; 144 145 let file = dir.join(&id.did); 146 let exists = std::fs::exists(&file)?; 147 148 let bytes = if !exists { 149 pb.set_message(format!("downloading CAR file for...{}", id.did)); 150 download_car_file(id, &pb).await? 151 } else { 152 pb.set_message(format!("using cached CAR file for...{}", id.did)); 153 tokio::fs::read(&file).await? 154 }; 155 156 // write to disk 157 if !exists { 158 tokio::fs::write(&file, &bytes).await?; 159 } 160 161 pb.finish(); 162 Ok(bytes) 163} 164 165async fn download_car_file( 166 id: &ResolvedIdentity, 167 pb: &ProgressBar, 168) -> Result<Vec<u8>, error::Error> { 169 // download the entire car file first before mounting it as a fusefs 170 let client = AtpServiceClient::new(IsahcClient::new(&id.pds)); 171 let did = types::string::Did::new(id.did.clone()).unwrap(); 172 173 let bytes = client 174 .service 175 .com 176 .atproto 177 .sync 178 .get_repo(com::atproto::sync::get_repo::Parameters::from( 179 com::atproto::sync::get_repo::ParametersData { did, since: None }, 180 )) 181 .await?; 182 183 pb.finish_with_message(format!("download complete for \t...\t{}", id.did)); 184 185 Ok(bytes) 186} 187 188async fn build_repo(bytes: Vec<u8>) -> Result<Repository<CarStore<Cursor<Vec<u8>>>>, error::Error> { 189 let store = CarStore::open(Cursor::new(bytes)).await?; 190 let root = store.roots().next().unwrap(); 191 let repo = Repository::open(store, root).await?; 192 Ok(repo) 193} 194``` 195 196``` 197foo bar 198``` 199 200<details> 201 <summary><strong>MacOS users will have to setup a Nix Builder first</strong></summary> 202 In order to build Tangled's dev VM on macOS, you will first need to set up a 203 Linux Nix builder. The recommended way to do so is to run a 204 [`darwin.linux-builder VM`](https://nixos.org/manual/nixpkgs/unstable/#sec-darwin-builder) and to register it in `nix.conf` 205 as a builder for Linux with the same architecture as your Mac (`linux-aarch64` 206 if you are using Apple Silicon). 207 208 > IMPORTANT: You must build `darwin.linux-builder` somewhere other than inside 209 > the tangled repo so that it doesn't conflict with the other VM. For example, 210 > you can do 211 > 212 > ```shell 213 > cd $(mktemp -d buildervm.XXXXX) && nix run nixpkgs#darwin.linux-builder 214 > ``` 215 > 216 > to store the builder VM in a temporary dir. 217 > 218 > You should read and follow [all the other intructions][darwin builder vm] to 219 > avoid subtle problems. 220 221 Alternatively, you can use any other method to set up a 222 Linux machine with `nix` installed that you can `sudo ssh` 223 into (in other words, root user on your Mac has to be able 224 to ssh into the Linux machine without entering a password) 225 and that has the same architecture as your Mac. See 226 [remote builder 227 instructions](https://nix.dev/manual/nix/2.28/advanced-topics/distributed-builds.html#requirements) 228 for how to register such a builder in `nix.conf`. 229 230 > WARNING: If you'd like to use 231 > [`nixos-lima`](https://github.com/nixos-lima/nixos-lima) or 232 > [Orbstack](https://orbstack.dev/), note that setting them up so that `sudo 233 > ssh` works can be tricky. It seems to be [possible with 234 > Orbstack](https://github.com/orgs/orbstack/discussions/1669). 235 236</details>