-33
Cargo.lock
-33
Cargo.lock
···
112
112
"serde_json",
113
113
"tokio",
114
114
"url",
115
-
"vfs",
116
115
]
117
116
118
117
[[package]]
···
744
743
dependencies = [
745
744
"rand_core 0.6.4",
746
745
"subtle",
747
-
]
748
-
749
-
[[package]]
750
-
name = "filetime"
751
-
version = "0.2.26"
752
-
source = "registry+https://github.com/rust-lang/crates.io-index"
753
-
checksum = "bc0505cd1b6fa6580283f6bdf70a73fcf4aba1184038c90902b92b3dd0df63ed"
754
-
dependencies = [
755
-
"cfg-if",
756
-
"libc",
757
-
"libredox",
758
-
"windows-sys 0.60.2",
759
746
]
760
747
761
748
[[package]]
···
1728
1715
version = "0.2.15"
1729
1716
source = "registry+https://github.com/rust-lang/crates.io-index"
1730
1717
checksum = "f9fbbcab51052fe104eb5e5d351cf728d30a5be1fe14d9be8a3b097481fb97de"
1731
-
1732
-
[[package]]
1733
-
name = "libredox"
1734
-
version = "0.1.10"
1735
-
source = "registry+https://github.com/rust-lang/crates.io-index"
1736
-
checksum = "416f7e718bdb06000964960ffa43b4335ad4012ae8b99060261aa4a8088d5ccb"
1737
-
dependencies = [
1738
-
"bitflags",
1739
-
"libc",
1740
-
"redox_syscall",
1741
-
]
1742
1718
1743
1719
[[package]]
1744
1720
name = "litemap"
···
3556
3532
version = "0.9.5"
3557
3533
source = "registry+https://github.com/rust-lang/crates.io-index"
3558
3534
checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a"
3559
-
3560
-
[[package]]
3561
-
name = "vfs"
3562
-
version = "0.12.2"
3563
-
source = "registry+https://github.com/rust-lang/crates.io-index"
3564
-
checksum = "9e723b9e1c02a3cf9f9d0de6a4ddb8cdc1df859078902fe0ae0589d615711ae6"
3565
-
dependencies = [
3566
-
"filetime",
3567
-
]
3568
3535
3569
3536
[[package]]
3570
3537
name = "want"
-1
Cargo.toml
-1
Cargo.toml
+24
-19
src/fuse.rs
+24
-19
src/fuse.rs
···
1
-
use super::*;
2
1
use easy_fuser::{prelude::*, templates::DefaultFuseHandler};
2
+
use tokio::runtime::Handle;
3
3
4
4
use std::{
5
5
ffi::{OsStr, OsString},
6
-
io::{Read, Seek, SeekFrom},
6
+
io::{Cursor, Read, Seek, SeekFrom},
7
7
path::PathBuf,
8
+
sync::Arc,
8
9
time::UNIX_EPOCH,
9
10
};
10
11
12
+
use crate::{AtpFS, FileType};
13
+
11
14
pub struct AtpFuse {
12
15
pub fs: Arc<AtpFS>,
13
16
pub inner: DefaultFuseHandler,
17
+
pub runtime: Handle,
14
18
}
15
19
16
20
impl AtpFuse {
···
45
49
46
50
fn vfs_metadata_attr(&self, vfs_path: &str) -> FuseResult<FileAttribute> {
47
51
let meta = self
48
-
.fs
49
-
.metadata(vfs_path)
52
+
.runtime
53
+
.block_on(self.fs.metadata(vfs_path))
50
54
.map_err(|_| ErrorKind::FileNotFound.to_error("Not found"))?;
51
55
52
56
let (kind, perm, nlink) = match meta.file_type {
53
-
VfsFileType::Directory => (FileKind::Directory, 0o755, 2),
54
-
VfsFileType::File => (FileKind::RegularFile, 0o644, 1),
57
+
FileType::Directory => (FileKind::Directory, 0o755, 2),
58
+
FileType::File => (FileKind::RegularFile, 0o644, 1),
55
59
};
56
60
57
61
Ok(FileAttribute {
···
115
119
let vfs_path = self.path_to_str(&file_id);
116
120
117
121
let stream = self
118
-
.fs
119
-
.read_dir(&vfs_path)
122
+
.runtime
123
+
.block_on(self.fs.read_dir(&vfs_path))
120
124
.map_err(|_| ErrorKind::InputOutputError.to_error("Read dir failed"))?;
121
125
122
126
let mut entries = vec![
···
125
129
];
126
130
127
131
for name in stream {
128
-
let kind = name
129
-
.ends_with(".json")
130
-
.then_some(FileKind::RegularFile)
131
-
.unwrap_or(FileKind::Directory);
132
+
let kind = if name.ends_with(".json") {
133
+
FileKind::RegularFile
134
+
} else {
135
+
FileKind::Directory
136
+
};
132
137
entries.push((OsString::from(name), kind));
133
138
}
134
139
···
145
150
_flags: FUSEOpenFlags,
146
151
_lock_owner: Option<u64>,
147
152
) -> FuseResult<Vec<u8>> {
148
-
let vfs_path = self.path_to_str(&file_id);
149
-
let mut reader = self
150
-
.fs
151
-
.open_file(&vfs_path)
152
-
.map_err(|_| ErrorKind::FileNotFound.to_error("File not found"))?;
153
-
154
153
// Only support absolute start seeks for now.
155
154
let pos = match seek {
156
155
SeekFrom::Start(p) => p,
···
163
162
return Ok(Vec::new());
164
163
}
165
164
165
+
let vfs_path = self.path_to_str(&file_id);
166
+
let data = self
167
+
.runtime
168
+
.block_on(self.fs.open_file(&vfs_path))
169
+
.map_err(|_| ErrorKind::FileNotFound.to_error("File not found"))?;
170
+
let mut reader = Cursor::new(data.as_slice());
171
+
166
172
// Seek to the requested position.
167
173
reader
168
174
.seek(SeekFrom::Start(pos))
169
175
.map_err(|_| ErrorKind::InputOutputError.to_error("Seek failed"))?;
170
176
171
177
// Read up to `size` bytes into the buffer.
172
-
// We use take to limit the read, then read_to_end or just read into buffer.
173
178
let mut buf = vec![0u8; size as usize];
174
179
let n = reader
175
180
.read(&mut buf)
+122
-166
src/lib.rs
+122
-166
src/lib.rs
···
1
1
use anyhow::{anyhow, Result};
2
-
#[cfg(target_arch = "wasm32")]
3
-
use futures::executor::block_on;
4
2
use jacquard::{
5
3
api::com_atproto::repo::{describe_repo::DescribeRepo, list_records::ListRecords},
6
4
client::{credential_session::CredentialSession, Agent, BasicClient, MemorySessionStore},
7
5
identity::{resolver::IdentityResolver, slingshot_resolver_default},
6
+
prelude::*,
8
7
types::{did::Did, nsid::Nsid, string::Handle},
9
-
xrpc::XrpcClient,
10
8
};
11
9
use scc::{HashMap, HashSet};
12
10
use url::Url;
13
-
use vfs::{error::VfsErrorKind, FileSystem, SeekAndRead, VfsFileType, VfsMetadata, VfsResult};
14
11
15
-
use std::{collections::HashMap as StdHashMap, fmt::Debug, sync::Arc};
12
+
use std::{
13
+
collections::HashMap as StdHashMap,
14
+
fmt::Debug,
15
+
io::{self, ErrorKind},
16
+
sync::Arc,
17
+
};
16
18
17
19
pub mod cli;
18
20
#[cfg(target_os = "linux")]
···
41
43
.ok_or_else(|| anyhow!("no pds endpoint in did doc"))
42
44
}
43
45
46
+
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
47
+
pub enum FileType {
48
+
File,
49
+
Directory,
50
+
}
51
+
52
+
#[derive(Debug, Clone)]
53
+
pub struct Metadata {
54
+
pub file_type: FileType,
55
+
pub len: u64,
56
+
}
57
+
44
58
#[derive(Debug)]
45
59
struct CachedPage {
46
-
files: StdHashMap<String, Vec<u8>>,
60
+
files: StdHashMap<String, Arc<Vec<u8>>>,
47
61
next_cursor: Option<String>,
48
62
}
49
63
···
52
66
client: BasicClient,
53
67
cache: HashMap<String, Arc<CachedPage>>,
54
68
root_cache: HashSet<String>,
55
-
#[cfg(not(target_arch = "wasm32"))]
56
-
handle: tokio::runtime::Handle,
57
69
}
58
70
59
71
impl Debug for AtpFS {
60
72
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
61
-
f.debug_struct("AtProtoFS").field("did", &self.did).finish()
73
+
f.debug_struct("AtpFS").field("did", &self.did).finish()
62
74
}
63
75
}
64
76
65
77
impl AtpFS {
66
-
pub fn new(did: Did<'static>, pds: Url) -> Self {
67
-
#[cfg(not(target_arch = "wasm32"))]
68
-
let handle = tokio::runtime::Handle::current();
69
-
78
+
pub async fn new(did: Did<'static>, pds: Url) -> Self {
70
79
let store = MemorySessionStore::default();
71
80
let session =
72
81
CredentialSession::new(Arc::new(store), Arc::new(slingshot_resolver_default()));
73
82
74
-
#[cfg(not(target_arch = "wasm32"))]
75
-
tokio::task::block_in_place(|| handle.block_on(session.set_endpoint(pds)));
76
-
77
-
#[cfg(target_arch = "wasm32")]
78
-
block_on(session.set_endpoint(pds));
83
+
session.set_endpoint(pds).await;
79
84
80
85
Self {
81
86
did,
82
87
client: Agent::new(session),
83
88
cache: HashMap::default(),
84
89
root_cache: HashSet::default(),
85
-
#[cfg(not(target_arch = "wasm32"))]
86
-
handle,
87
90
}
88
91
}
89
92
90
-
#[cfg(not(target_arch = "wasm32"))]
91
-
fn block_on<F: std::future::Future>(&self, future: F) -> F::Output {
92
-
tokio::task::block_in_place(move || self.handle.block_on(future))
93
-
}
94
-
95
-
#[cfg(target_arch = "wasm32")]
96
-
fn block_on<F: std::future::Future>(&self, future: F) -> F::Output {
97
-
block_on(future)
98
-
}
99
-
100
93
fn segments<'a, 's>(&'s self, path: &'a str) -> Vec<&'a str> {
101
94
path.trim_matches('/')
102
95
.split('/')
···
104
97
.collect()
105
98
}
106
99
107
-
fn vfs_dir_metadata() -> VfsMetadata {
108
-
VfsMetadata {
109
-
file_type: VfsFileType::Directory,
100
+
fn dir_metadata() -> Metadata {
101
+
Metadata {
102
+
file_type: FileType::Directory,
110
103
len: 0,
111
-
created: None,
112
-
modified: None,
113
-
accessed: None,
114
104
}
115
105
}
116
106
117
-
fn vfs_file_metadata(len: u64) -> VfsMetadata {
118
-
VfsMetadata {
119
-
file_type: VfsFileType::File,
107
+
fn file_metadata(len: u64) -> Metadata {
108
+
Metadata {
109
+
file_type: FileType::File,
120
110
len,
121
-
created: None,
122
-
modified: None,
123
-
accessed: None,
124
111
}
125
112
}
126
113
127
-
async fn ensure_root_loaded(&self) -> VfsResult<String> {
114
+
async fn ensure_root_loaded(&self) -> io::Result<()> {
128
115
if self.root_cache.is_empty() {
129
116
let request = DescribeRepo::new().repo(self.did.clone()).build();
130
117
···
132
119
.client
133
120
.send(request)
134
121
.await
135
-
.map_err(|e| VfsErrorKind::Other(e.to_string()))?;
122
+
.map_err(|e| io::Error::new(ErrorKind::Other, e.to_string()))?;
136
123
137
124
let output = response
138
125
.into_output()
139
-
.map_err(|e| VfsErrorKind::Other(e.to_string()))?;
126
+
.map_err(|e| io::Error::new(ErrorKind::Other, e.to_string()))?;
140
127
141
128
for col in output.collections {
142
129
let _ = self.root_cache.insert_async(col.to_string()).await;
143
130
}
144
131
}
145
-
return Ok("".to_string());
132
+
return Ok(());
146
133
}
147
134
148
-
async fn ensure_loaded(&self, path: &str) -> VfsResult<String> {
135
+
async fn ensure_loaded(&self, path: &str) -> io::Result<String> {
149
136
let segs = self.segments(path);
150
137
151
138
if segs.is_empty() {
152
-
return self.ensure_root_loaded().await;
139
+
self.ensure_root_loaded().await?;
140
+
return Ok("".to_string());
153
141
}
154
142
155
143
let collection = segs[0];
···
158
146
}
159
147
160
148
if !self.root_cache.contains(collection) {
161
-
return Err(VfsErrorKind::FileNotFound.into());
149
+
return Err(ErrorKind::NotFound.into());
162
150
}
163
151
164
152
let mut current_key = collection.to_string();
···
174
162
parent_cursor = Some(cursor);
175
163
current_key = format!("{}/next", current_key);
176
164
} else {
177
-
return Err(VfsErrorKind::FileNotFound.into());
165
+
return Err(ErrorKind::NotFound.into());
178
166
}
179
167
} else if segment.ends_with(".json") {
180
168
break;
181
169
} else {
182
-
return Err(VfsErrorKind::FileNotFound.into());
170
+
return Err(ErrorKind::NotFound.into());
183
171
}
184
172
}
185
173
···
188
176
Ok(current_key)
189
177
}
190
178
191
-
async fn fetch_page_if_missing(&self, key: &str, cursor: Option<String>) -> VfsResult<()> {
179
+
async fn fetch_page_if_missing(&self, key: &str, cursor: Option<String>) -> io::Result<()> {
192
180
if self.cache.contains(key) {
193
181
return Ok(());
194
182
}
···
206
194
.client
207
195
.send(request.build())
208
196
.await
209
-
.map_err(|e| VfsErrorKind::Other(e.to_string()))?;
197
+
.map_err(|e| io::Error::new(ErrorKind::Other, e.to_string()))?;
210
198
211
199
let output = response
212
200
.into_output()
213
-
.map_err(|e| VfsErrorKind::Other(e.to_string()))?;
201
+
.map_err(|e| io::Error::new(ErrorKind::Other, e.to_string()))?;
214
202
215
203
let mut files = StdHashMap::new();
216
204
for rec in output.records {
217
205
if let Some(rkey) = rec.uri.rkey() {
218
206
let filename = format!("{}.json", rkey.0);
219
207
let content = serde_json::to_vec_pretty(&rec.value).unwrap_or_default();
220
-
files.insert(filename, content);
208
+
files.insert(filename, Arc::new(content));
221
209
}
222
210
}
223
211
···
234
222
235
223
Ok(())
236
224
}
237
-
}
238
225
239
-
impl FileSystem for AtpFS {
240
-
fn read_dir(&self, path: &str) -> VfsResult<Box<dyn Iterator<Item = String> + Send>> {
241
-
self.block_on(async {
242
-
let segs = self.segments(path);
243
-
244
-
if segs.is_empty() {
245
-
self.ensure_root_loaded().await?;
246
-
let mut keys = Vec::new();
247
-
self.root_cache.scan(|k| keys.push(k.clone()));
248
-
return Ok(Box::new(keys.into_iter()) as Box<dyn Iterator<Item = String> + Send>);
249
-
}
226
+
pub async fn read_dir(&self, path: &str) -> io::Result<Vec<String>> {
227
+
let segs = self.segments(path);
250
228
251
-
let cache_key = self.ensure_loaded(path).await?;
229
+
if segs.is_empty() {
230
+
self.ensure_root_loaded().await?;
231
+
let mut keys = Vec::new();
232
+
self.root_cache.scan(|k| keys.push(k.clone()));
233
+
return Ok(keys);
234
+
}
252
235
253
-
if path.ends_with(".json") {
254
-
return Err(VfsErrorKind::Other("not a directory".into()).into());
255
-
}
236
+
let cache_key = self.ensure_loaded(path).await?;
256
237
257
-
let page = self
258
-
.cache
259
-
.read(&cache_key, |_, v| v.clone())
260
-
.ok_or(VfsErrorKind::FileNotFound)?;
238
+
if path.ends_with(".json") {
239
+
return Err(io::Error::new(ErrorKind::Other, "not a directory"));
240
+
}
261
241
262
-
let mut entries: Vec<String> = page.files.keys().cloned().collect();
263
-
if page.next_cursor.is_some() {
264
-
entries.push("next".to_string());
265
-
}
242
+
let page = self
243
+
.cache
244
+
.read(&cache_key, |_, v| v.clone())
245
+
.ok_or(ErrorKind::NotFound)?;
266
246
267
-
Ok(Box::new(entries.into_iter()) as Box<dyn Iterator<Item = String> + Send>)
268
-
})
269
-
}
247
+
let mut entries: Vec<String> = page.files.keys().cloned().collect();
248
+
if page.next_cursor.is_some() {
249
+
entries.push("next".to_string());
250
+
}
270
251
271
-
fn create_dir(&self, _path: &str) -> VfsResult<()> {
272
-
Err(VfsErrorKind::NotSupported.into())
252
+
Ok(entries)
273
253
}
274
254
275
-
fn open_file(&self, path: &str) -> VfsResult<Box<dyn SeekAndRead + Send>> {
276
-
self.block_on(async {
277
-
let parent_path = std::path::Path::new(path)
278
-
.parent()
279
-
.unwrap_or(std::path::Path::new(""))
280
-
.to_str()
281
-
.unwrap();
282
-
let cache_key = self.ensure_loaded(parent_path).await?;
283
-
let filename = path.split('/').last().ok_or(VfsErrorKind::FileNotFound)?;
284
-
285
-
let content = self
286
-
.cache
287
-
.read(&cache_key, |_, page| page.files.get(filename).cloned())
288
-
.flatten();
255
+
pub async fn open_file(&self, path: &str) -> io::Result<Arc<Vec<u8>>> {
256
+
let parent_path = std::path::Path::new(path)
257
+
.parent()
258
+
.unwrap_or(std::path::Path::new(""))
259
+
.to_str()
260
+
.unwrap();
261
+
let cache_key = self.ensure_loaded(parent_path).await?;
262
+
let filename = path.split('/').last().ok_or(ErrorKind::NotFound)?;
289
263
290
-
if let Some(data) = content {
291
-
return Ok(Box::new(std::io::Cursor::new(data)) as Box<dyn SeekAndRead + Send>);
292
-
}
264
+
let content = self
265
+
.cache
266
+
.read(&cache_key, |_, page| page.files.get(filename).cloned())
267
+
.flatten();
293
268
294
-
Err(VfsErrorKind::FileNotFound.into())
295
-
})
269
+
content.ok_or(ErrorKind::NotFound.into())
296
270
}
297
271
298
-
fn metadata(&self, path: &str) -> VfsResult<VfsMetadata> {
299
-
self.block_on(async {
300
-
let segs = self.segments(path);
301
-
if segs.is_empty() {
302
-
return Ok(AtpFS::vfs_dir_metadata());
303
-
}
304
-
305
-
if segs.len() == 1 {
306
-
self.ensure_root_loaded().await?;
307
-
if self.root_cache.contains(segs[0]) {
308
-
return Ok(AtpFS::vfs_dir_metadata());
309
-
} else {
310
-
return Err(VfsErrorKind::FileNotFound.into());
311
-
}
312
-
}
272
+
pub async fn metadata(&self, path: &str) -> io::Result<Metadata> {
273
+
let segs = self.segments(path);
274
+
if segs.is_empty() {
275
+
return Ok(Self::dir_metadata());
276
+
}
313
277
314
-
if let Some(last) = segs.last() {
315
-
if *last == "next" {
316
-
let parent = &path[0..path.len() - 5];
317
-
let cache_key = self.ensure_loaded(parent).await?;
318
-
let has_next = self
319
-
.cache
320
-
.read(&cache_key, |_, v| v.next_cursor.is_some())
321
-
.unwrap_or(false);
322
-
if has_next {
323
-
return Ok(AtpFS::vfs_dir_metadata());
324
-
}
325
-
return Err(VfsErrorKind::FileNotFound.into());
326
-
}
278
+
if segs.len() == 1 {
279
+
self.ensure_root_loaded().await?;
280
+
if self.root_cache.contains(segs[0]) {
281
+
return Ok(Self::dir_metadata());
282
+
} else {
283
+
return Err(ErrorKind::NotFound.into());
327
284
}
328
-
329
-
if path.ends_with(".json") {
330
-
let parent_path = std::path::Path::new(path)
331
-
.parent()
332
-
.unwrap()
333
-
.to_str()
334
-
.unwrap();
335
-
let cache_key = self.ensure_loaded(parent_path).await?;
336
-
let filename = segs.last().unwrap();
285
+
}
337
286
338
-
let len = self
287
+
if let Some(last) = segs.last() {
288
+
if *last == "next" {
289
+
let parent = &path[0..path.len() - 5];
290
+
let cache_key = self.ensure_loaded(parent).await?;
291
+
let has_next = self
339
292
.cache
340
-
.read(&cache_key, |_, page| {
341
-
page.files.get(*filename).map(|f| f.len())
342
-
})
343
-
.flatten();
344
-
345
-
if let Some(l) = len {
346
-
return Ok(AtpFS::vfs_file_metadata(l as u64));
293
+
.read(&cache_key, |_, v| v.next_cursor.is_some())
294
+
.unwrap_or(false);
295
+
if has_next {
296
+
return Ok(Self::dir_metadata());
347
297
}
348
-
return Err(VfsErrorKind::FileNotFound.into());
298
+
return Err(ErrorKind::NotFound.into());
349
299
}
350
-
351
-
Err(VfsErrorKind::FileNotFound.into())
352
-
})
353
-
}
300
+
}
354
301
355
-
fn exists(&self, path: &str) -> VfsResult<bool> {
356
-
Ok(self.metadata(path).is_ok())
357
-
}
302
+
if path.ends_with(".json") {
303
+
let parent_path = std::path::Path::new(path)
304
+
.parent()
305
+
.unwrap()
306
+
.to_str()
307
+
.unwrap();
308
+
let cache_key = self.ensure_loaded(parent_path).await?;
309
+
let filename = segs.last().unwrap();
358
310
359
-
fn create_file(&self, _: &str) -> VfsResult<Box<dyn vfs::SeekAndWrite + Send>> {
360
-
Err(VfsErrorKind::NotSupported.into())
361
-
}
311
+
let len = self
312
+
.cache
313
+
.read(&cache_key, |_, page| {
314
+
page.files.get(*filename).map(|f| f.len())
315
+
})
316
+
.flatten();
362
317
363
-
fn append_file(&self, _: &str) -> VfsResult<Box<dyn vfs::SeekAndWrite + Send>> {
364
-
Err(VfsErrorKind::NotSupported.into())
365
-
}
318
+
if let Some(l) = len {
319
+
return Ok(Self::file_metadata(l as u64));
320
+
}
321
+
return Err(ErrorKind::NotFound.into());
322
+
}
366
323
367
-
fn remove_file(&self, _path: &str) -> VfsResult<()> {
368
-
Err(VfsErrorKind::NotSupported.into())
324
+
Err(ErrorKind::NotFound.into())
369
325
}
370
326
371
-
fn remove_dir(&self, _path: &str) -> VfsResult<()> {
372
-
Err(VfsErrorKind::NotSupported.into())
327
+
pub async fn exists(&self, path: &str) -> io::Result<bool> {
328
+
Ok(self.metadata(path).await.is_ok())
373
329
}
374
330
}
+7
-5
src/main.rs
+7
-5
src/main.rs
···
3
3
cli::{opts, SubCommand},
4
4
resolve_did, resolve_pds, AtpFS,
5
5
};
6
-
use vfs::FileSystem;
7
6
8
7
use std::sync::Arc;
8
+
use tokio::runtime::Handle;
9
9
10
10
async fn run_app(args: Vec<String>) -> Result<()> {
11
11
let opts = opts().run_inner(args.as_slice());
···
23
23
let pds = resolve_pds(&did).await?;
24
24
println!("resolved PDS: {}", pds);
25
25
26
-
let fs = Arc::new(AtpFS::new(did, pds));
26
+
let fs = Arc::new(AtpFS::new(did, pds).await);
27
27
28
28
match opts.cmd {
29
29
SubCommand::Ls { path } => {
30
-
println!("Listing: {}", path);
31
-
let iterator = fs.read_dir(&path)?;
32
-
for item in iterator {
30
+
let files = fs.read_dir(&path).await?;
31
+
for item in files {
33
32
println!("{}", item);
34
33
}
35
34
}
···
40
39
41
40
let options = vec![MountOption::RO, MountOption::FSName("atproto".to_string())];
42
41
42
+
let handle = Handle::current();
43
+
43
44
let fuse_handler = AtpFuse {
44
45
fs,
45
46
inner: DefaultFuseHandler::new(),
47
+
runtime: handle,
46
48
};
47
49
48
50
println!("mounting at {:?}...", mount_point);