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