Skip to main content

lb_fs/
lib.rs

1use crate::cache::FileEntry;
2use crate::fs_impl::Drive;
3use crate::mount::{mount, umount};
4use cli_rs::cli_error::{CliError, CliResult};
5use lb_rs::model::core_config::Config;
6use lb_rs::model::errors::Unexpected;
7use lb_rs::service::events::{Actor, Event};
8use lb_rs::{Lb, Uuid};
9use nfs3_server::tcp::{NFSTcp, NFSTcpListener};
10use std::io;
11use std::io::IsTerminal;
12use std::process::exit;
13use std::sync::Arc;
14use std::time::Duration;
15use tokio::time;
16use tracing::{debug, error, info};
17
18pub mod cache;
19pub(crate) mod file_handle;
20pub mod fs_impl;
21pub mod logger;
22pub mod mount;
23pub mod utils;
24
25impl Drive {
26    pub async fn init() -> Self {
27        let lb = Lb::init(Config {
28            writeable_path: Config::writeable_path("drive"),
29            background_work: false,
30            logs: false,
31            stdout_logs: false,
32            colored_logs: false,
33        })
34        .await
35        .unwrap();
36
37        logger::init();
38
39        let root = lb.root().await.map(|file| file.id).unwrap_or(Uuid::nil());
40
41        let data = Arc::default();
42
43        let fs = Self { lb, root, data };
44        fs.clone().monitor_lb().await;
45
46        fs
47    }
48
49    pub async fn import() -> CliResult<()> {
50        let drive = Self::init().await;
51
52        if io::stdin().is_terminal() {
53            return Err(CliError::from("to import an existing lockbook account, pipe your account string into this command, e.g.:\npbpaste | lb-fs import".to_string()));
54        }
55
56        let mut account_string = String::new();
57        io::stdin()
58            .read_line(&mut account_string)
59            .expect("failed to read from stdin");
60        account_string.retain(|c| !c.is_whitespace());
61
62        println!("importing account...");
63        drive
64            .lb
65            .import_account(&account_string, None)
66            .await
67            .unwrap();
68
69        drive.lb.sync().await.unwrap();
70
71        Ok(())
72    }
73
74    pub async fn mount() -> CliResult<()> {
75        let drive = Self::init().await;
76        drive.lb.sync().await.unwrap();
77        drive.fill_cache().await;
78        info!("registering sig handler");
79
80        // capture ctrl_c and try to cleanup
81        tokio::spawn(async move {
82            tokio::signal::ctrl_c().await.unwrap();
83            let mut unmount_success = umount().await;
84            while !unmount_success {
85                error!("unmount failed, please close any apps using lb-fs! Retrying in 1s.");
86                time::sleep(Duration::from_secs(1)).await;
87                unmount_success = umount().await;
88            }
89            info!("cleaned up, goodbye!");
90            exit(0);
91        });
92
93        // sync periodically in the background
94        let syncer = drive.clone();
95        tokio::spawn(async move {
96            loop {
97                info!("will sync in 30 seconds");
98                tokio::time::sleep(Duration::from_secs(30)).await;
99                info!("syncing");
100                syncer.lb.sync().await.map_unexpected().log_and_ignore();
101            }
102        });
103
104        // monitor changes to lb
105        let event_handler = drive.clone();
106        tokio::spawn(async move {
107            let mut events = event_handler.lb.subscribe();
108            loop {
109                let event = events.recv().await.unwrap();
110
111                // todo: this is the last thing that needs to be fleshed out for lb-fs to be fully
112                // embedded into desktop clients:
113                // there needs to be a more nuanced concept of Actor, so that we can respond to
114                // changes other clients made without reacting to changes that we made ourselves
115                //
116                // perhaps it would be nice to additionally integrate the status of lb-fs in status
117                // generally. Maybe it would be best for workspace to orchestrate it? Maybe have a
118                // tab dedicated to the status of the virtual file system mount
119                //
120                // maybe that works nicely with tab persistence too. can gate to beta_users pretty
121                // easily. Maybe can have a special filename that lets people test an early version
122                match event {
123                    Event::MetadataChanged(Actor::Sync) => event_handler.fill_cache().await,
124                    Event::DocumentWritten(dirty_id, Actor::Sync) => {
125                        let file = event_handler.lb.get_file_by_id(dirty_id).await.unwrap();
126                        let size = if file.is_document() {
127                            event_handler
128                                .lb
129                                .read_document(dirty_id, false)
130                                .await
131                                .unwrap()
132                                .len()
133                        } else {
134                            0
135                        };
136
137                        let mut entry = FileEntry::from_file(file, size as u64);
138
139                        let now = FileEntry::now();
140
141                        entry.fattr.mtime = now;
142                        entry.fattr.ctime = now;
143
144                        event_handler
145                            .data
146                            .lock()
147                            .await
148                            .insert(entry.file.id.into(), entry);
149                    }
150                    _ => {}
151                }
152            }
153        });
154
155        // todo have a better port selection strategy
156        info!("creating server");
157        let listener = NFSTcpListener::bind("127.0.0.1:11111", drive)
158            .await
159            .unwrap();
160
161        info!("mounting");
162        mount();
163
164        info!("ready");
165        listener.handle_forever().await.unwrap();
166        Ok(())
167    }
168
169    async fn monitor_lb(self) {
170        tokio::spawn(async move {
171            let mut sub = self.lb.subscribe();
172            loop {
173                if let Event::Sync(sync_increment) = sub.recv().await.unwrap() {
174                    debug!("syncing: {sync_increment:?}")
175                }
176            }
177        });
178    }
179}