1use base64::{engine::general_purpose::URL_SAFE_NO_PAD, Engine};
2use futures::{channel::mpsc::channel, try_join};
3use notify::RecursiveMode;
4use serde::Deserialize;
5use std::path::PathBuf;
6use std::sync::Arc;
7use tokio::runtime::Runtime;
8use tokio::sync::Mutex;
9
10use log::debug;
11
12use crate::chunker::{Chunker, InMemoryCache};
13use crate::file_watcher::async_watcher;
14use crate::indexer::check_index_once;
15use crate::syncer::{check_download_once, check_upload_once};
16
17const CHANNEL_SIZE: usize = 100;
18const INMEMORY_CACHE_MAX_REC: usize = 100000;
19const INMEMORY_CACHE_MAX_MEM: u64 = 100_000_000_000;
20
21pub mod chunker;
22pub mod connection;
23pub mod context;
24pub mod errors;
25pub mod file_watcher;
26pub mod indexer;
27pub mod models;
28pub mod registry;
29pub mod remote;
30pub mod schema;
31pub mod syncer;
32
33pub use context::{SyncContext, SyncStatusListener};
35pub use models::SyncStatus;
36
37pub fn extract_uid_from_jwt(token: &str) -> i32 {
40 #[derive(Deserialize)]
41 struct Claims {
42 uid: i32,
43 }
44
45 let parts: Vec<&str> = token.split('.').collect();
46 let payload = URL_SAFE_NO_PAD
47 .decode(parts[1])
48 .expect("Failed to decode JWT payload");
49 let claims: Claims = serde_json::from_slice(&payload).expect("Failed to parse JWT claims");
50
51 claims.uid
52}
53
54#[cfg(feature = "ffi")]
55uniffi::setup_scaffolding!();
56
57#[cfg_attr(feature = "ffi", uniffi::export)]
60pub fn run(
61 context: Arc<SyncContext>,
62 storage_dir: &str,
63 db_file_path: &str,
64 api_endpoint: &str,
65 remote_token: &str,
66 namespace_id: i32,
67 download_only: bool,
68) -> Result<(), errors::SyncError> {
69 Runtime::new()?.block_on(run_async(
70 context,
71 storage_dir,
72 db_file_path,
73 api_endpoint,
74 remote_token,
75 namespace_id,
76 download_only,
77 ))?;
78
79 Ok(())
80}
81
82#[cfg_attr(feature = "ffi", uniffi::export)]
87pub fn wait_remote_update(api_endpoint: &str, remote_token: &str) -> Result<(), errors::SyncError> {
88 Runtime::new()?.block_on(remote::Remote::new(api_endpoint, remote_token).poll())?;
89
90 Ok(())
91}
92
93#[cfg_attr(feature = "ffi", uniffi::export)]
97pub fn run_download_once(
98 storage_dir: &str,
99 db_file_path: &str,
100 api_endpoint: &str,
101 remote_token: &str,
102 namespace_id: i32,
103) -> Result<(), errors::SyncError> {
104 use std::env;
105
106 env::set_var("CARGO_LOG", "trace");
107
108 let storage_dir = &PathBuf::from(storage_dir);
109 let chunk_cache = InMemoryCache::new(INMEMORY_CACHE_MAX_REC, INMEMORY_CACHE_MAX_MEM);
110 let chunker = Arc::new(Mutex::new(Chunker::new(chunk_cache, storage_dir.clone())));
111 let remote = &remote::Remote::new(api_endpoint, remote_token);
112
113 let pool = connection::get_connection_pool(db_file_path)?;
114 debug!("Started connection pool for {:?}", db_file_path);
115
116 Runtime::new()?.block_on(check_download_once(
117 &pool,
118 Arc::clone(&chunker),
119 remote,
120 storage_dir,
121 namespace_id,
122 ))?;
123
124 Ok(())
125}
126
127#[cfg_attr(feature = "ffi", uniffi::export)]
131pub fn run_upload_once(
132 storage_dir: &str,
133 db_file_path: &str,
134 api_endpoint: &str,
135 remote_token: &str,
136 namespace_id: i32,
137) -> Result<(), errors::SyncError> {
138 let storage_dir = &PathBuf::from(storage_dir);
139 let chunk_cache = InMemoryCache::new(INMEMORY_CACHE_MAX_REC, INMEMORY_CACHE_MAX_MEM);
140 let chunker = Arc::new(Mutex::new(Chunker::new(chunk_cache, storage_dir.clone())));
141 let remote = &remote::Remote::new(api_endpoint, remote_token);
142
143 let pool = connection::get_connection_pool(db_file_path)?;
144 debug!("Started connection pool for {:?}", db_file_path);
145
146 check_index_once(&pool, storage_dir, namespace_id)?;
147
148 let runtime = Runtime::new()?;
149
150 if !runtime.block_on(check_upload_once(
153 &pool,
154 Arc::clone(&chunker),
155 remote,
156 namespace_id,
157 ))? {
158 runtime.block_on(check_upload_once(
159 &pool,
160 Arc::clone(&chunker),
161 remote,
162 namespace_id,
163 ))?;
164 }
165
166 Ok(())
167}
168
169#[allow(clippy::too_many_arguments)]
171pub async fn run_async(
172 context: Arc<SyncContext>,
173 storage_dir: &str,
174 db_file_path: &str,
175 api_endpoint: &str,
176 remote_token: &str,
177 namespace_id: i32,
178 download_only: bool,
179) -> Result<(), errors::SyncError> {
180 let token = context.token();
181 let listener = context.listener();
182
183 let (mut debouncer, local_file_update_rx) = async_watcher()?;
185 let (local_registry_updated_tx, local_registry_updated_rx) = channel(CHANNEL_SIZE);
186
187 let storage_dir = &PathBuf::from(storage_dir);
188 let chunk_cache = InMemoryCache::new(INMEMORY_CACHE_MAX_REC, INMEMORY_CACHE_MAX_MEM);
189 let chunker = Chunker::new(chunk_cache, storage_dir.clone());
190 let remote = &remote::Remote::new(api_endpoint, remote_token);
191
192 let pool = connection::get_connection_pool(db_file_path)?;
193 debug!("Started connection pool for {:?}", db_file_path);
194
195 if !download_only {
196 debouncer
197 .watcher()
198 .watch(storage_dir, RecursiveMode::Recursive)?;
199 debug!("Started watcher on {:?}", storage_dir);
200 }
201
202 if let Some(ref cb) = listener {
204 cb.on_status_changed(SyncStatus::Syncing);
205 }
206
207 let indexer = indexer::run(
208 token.clone(),
209 listener.clone(),
210 &pool,
211 storage_dir,
212 namespace_id,
213 local_file_update_rx,
214 local_registry_updated_tx,
215 );
216 debug!("Started indexer on {:?}", storage_dir);
217
218 let syncer = syncer::run(
219 token.clone(),
220 listener.clone(),
221 &pool,
222 storage_dir,
223 namespace_id,
224 chunker,
225 remote,
226 local_registry_updated_rx,
227 download_only,
228 );
229 debug!("Started syncer");
230
231 let result = try_join!(indexer, syncer);
232
233 if let Some(ref cb) = listener {
235 match result {
236 Ok(_) => cb.on_complete(true, None),
237 Err(ref e) => cb.on_complete(false, Some(format!("{:?}", e))),
238 }
239 }
240
241 result?;
242 Ok(())
243}