diff --git a/src/rpc.rs b/src/rpc.rs index ccca52b..c64d2b4 100644 --- a/src/rpc.rs +++ b/src/rpc.rs @@ -338,6 +338,92 @@ impl RpcServerImpl { Ok(()) } + + /// Build database file path for given server/db_id + fn db_file_path(&self, server: &Server, db_id: u64) -> std::path::PathBuf { + std::path::PathBuf::from(&server.option.dir).join(format!("{}.db", db_id)) + } + + /// Recursively compute size on disk for the database path + fn compute_size_on_disk(&self, path: &std::path::Path) -> Option { + fn dir_size(p: &std::path::Path) -> u64 { + if p.is_file() { + std::fs::metadata(p).map(|m| m.len()).unwrap_or(0) + } else if p.is_dir() { + let mut total = 0u64; + if let Ok(read) = std::fs::read_dir(p) { + for entry in read.flatten() { + total += dir_size(&entry.path()); + } + } + total + } else { + 0 + } + } + Some(dir_size(path)) + } + + /// Extract created and last access times (secs) from a path, with fallbacks + fn get_file_times_secs(path: &std::path::Path) -> (u64, Option) { + let now = std::time::SystemTime::now(); + let created = std::fs::metadata(path) + .and_then(|m| m.created().or_else(|_| m.modified())) + .unwrap_or(now) + .duration_since(std::time::UNIX_EPOCH) + .unwrap_or_default() + .as_secs(); + + let last_access = std::fs::metadata(path) + .and_then(|m| m.accessed()) + .ok() + .and_then(|t| t.duration_since(std::time::UNIX_EPOCH).ok().map(|d| d.as_secs())); + + (created, last_access) + } + + /// Compose a DatabaseInfo by probing storage, metadata and filesystem + async fn build_database_info(&self, db_id: u64, server: &Server) -> DatabaseInfo { + // Probe storage to determine encryption state + let storage = server.current_storage().ok(); + let encrypted = storage.as_ref().map(|s| s.is_encrypted()).unwrap_or(server.option.encrypt); + + // Load meta to get access key count + let meta = Self::load_meta_static(&self.base_dir, db_id).await.unwrap_or(DatabaseMeta { + public: true, + keys: HashMap::new(), + }); + let key_count = Some(meta.keys.len() as u64); + + // Compute size on disk and timestamps + let db_path = self.db_file_path(server, db_id); + let size_on_disk = self.compute_size_on_disk(&db_path); + + let meta_path = std::path::PathBuf::from(&self.base_dir).join(format!("{}_meta.json", db_id)); + let (created_at, last_access) = if meta_path.exists() { + Self::get_file_times_secs(&meta_path) + } else { + Self::get_file_times_secs(&db_path) + }; + + let backend = match server.option.backend { + crate::options::BackendType::Redb => BackendType::Redb, + crate::options::BackendType::Sled => BackendType::Sled, + }; + + DatabaseInfo { + id: db_id, + name: None, + backend, + encrypted, + redis_version: Some("7.0".to_string()), + storage_path: Some(server.option.dir.clone()), + size_on_disk, + key_count, + created_at, + last_access, + } + } } #[jsonrpsee::core::async_trait] @@ -426,28 +512,9 @@ impl RpcServer for RpcServerImpl { let mut result = Vec::new(); for db_id in db_ids { - // Try to get or create server for this database if let Ok(server) = self.get_or_create_server(db_id).await { - let backend = match server.option.backend { - crate::options::BackendType::Redb => BackendType::Redb, - crate::options::BackendType::Sled => BackendType::Sled, - }; - - let info = DatabaseInfo { - id: db_id, - name: None, // TODO: Store name in server metadata - backend, - encrypted: server.option.encrypt, - redis_version: Some("7.0".to_string()), // Default Redis compatibility - storage_path: Some(server.option.dir.clone()), - size_on_disk: None, // TODO: Calculate actual size - key_count: None, // TODO: Get key count from storage - created_at: std::time::SystemTime::now() - .duration_since(std::time::UNIX_EPOCH) - .unwrap() - .as_secs(), - last_access: None, - }; + // Build accurate info from storage/meta/fs + let info = self.build_database_info(db_id, &server).await; result.push(info); } } @@ -457,27 +524,9 @@ impl RpcServer for RpcServerImpl { async fn get_database_info(&self, db_id: u64) -> RpcResult { let server = self.get_or_create_server(db_id).await?; - - let backend = match server.option.backend { - crate::options::BackendType::Redb => BackendType::Redb, - crate::options::BackendType::Sled => BackendType::Sled, - }; - - Ok(DatabaseInfo { - id: db_id, - name: None, - backend, - encrypted: server.option.encrypt, - redis_version: Some("7.0".to_string()), - storage_path: Some(server.option.dir.clone()), - size_on_disk: None, - key_count: None, - created_at: std::time::SystemTime::now() - .duration_since(std::time::UNIX_EPOCH) - .unwrap() - .as_secs(), - last_access: None, - }) + // Build accurate info from storage/meta/fs + let info = self.build_database_info(db_id, &server).await; + Ok(info) } async fn delete_database(&self, db_id: u64) -> RpcResult {