Rust · 5931 bytes Raw Blame History
1 use anyhow::{Context, Result};
2 use reqwest::Client;
3 use serde::{Deserialize, Serialize};
4 use std::path::Path;
5 use std::time::Duration;
6 use tokio::fs;
7
8 use crate::config::Config;
9
10 #[derive(Debug, Clone)]
11 pub struct ZephyrClient {
12 client: Client,
13 coordinator_url: String,
14 node_url: String,
15 }
16
17 #[derive(Debug, Serialize, Deserialize)]
18 pub struct NodeInfo {
19 pub id: String,
20 pub name: String,
21 pub status: String,
22 pub peers_connected: usize,
23 pub storage_used_gb: f64,
24 pub storage_available_gb: f64,
25 pub uptime_seconds: u64,
26 }
27
28 #[derive(Debug, Serialize, Deserialize)]
29 pub struct FileInfo {
30 pub name: String,
31 pub size: u64,
32 pub hash: String,
33 pub uploaded_at: chrono::DateTime<chrono::Utc>,
34 pub chunks: usize,
35 pub replicas: usize,
36 }
37
38 #[derive(Debug, Serialize, Deserialize)]
39 pub struct UploadRequest {
40 pub file_path: String,
41 pub file_name: String,
42 pub file_size: u64,
43 }
44
45 #[derive(Debug, Serialize, Deserialize)]
46 pub struct UploadResponse {
47 pub success: bool,
48 pub file_hash: String,
49 pub chunks_uploaded: usize,
50 }
51
52 #[derive(Debug, Serialize, Deserialize)]
53 pub struct DownloadRequest {
54 pub file_hash: String,
55 pub output_path: String,
56 }
57
58 impl ZephyrClient {
59 pub fn new(config: &Config) -> Self {
60 let client = Client::builder()
61 .timeout(Duration::from_secs(config.coordinator.timeout))
62 .build()
63 .expect("Failed to create HTTP client");
64
65 Self {
66 client,
67 coordinator_url: config.coordinator.endpoint.clone(),
68 node_url: format!("http://127.0.0.1:{}", config.node.listen_port),
69 }
70 }
71
72 pub async fn get_node_status(&self) -> Result<NodeInfo> {
73 let response = self
74 .client
75 .get(&format!("{}/api/status", self.node_url))
76 .send()
77 .await
78 .context("Failed to connect to node")?;
79
80 if !response.status().is_success() {
81 anyhow::bail!("Node returned error: {}", response.status());
82 }
83
84 let node_info: NodeInfo = response
85 .json()
86 .await
87 .context("Failed to parse node status response")?;
88
89 Ok(node_info)
90 }
91
92 pub async fn list_files(&self) -> Result<Vec<FileInfo>> {
93 let response = self
94 .client
95 .get(&format!("{}/api/files", self.node_url))
96 .send()
97 .await
98 .context("Failed to connect to node")?;
99
100 if !response.status().is_success() {
101 anyhow::bail!("Node returned error: {}", response.status());
102 }
103
104 let files: Vec<FileInfo> = response
105 .json()
106 .await
107 .context("Failed to parse files list response")?;
108
109 Ok(files)
110 }
111
112 pub async fn upload_file(&self, file_path: &Path) -> Result<UploadResponse> {
113 let metadata = fs::metadata(file_path).await
114 .with_context(|| format!("Failed to read file metadata: {:?}", file_path))?;
115
116 let file_name = file_path
117 .file_name()
118 .and_then(|n| n.to_str())
119 .context("Invalid file name")?;
120
121 let request = UploadRequest {
122 file_path: file_path.to_string_lossy().to_string(),
123 file_name: file_name.to_string(),
124 file_size: metadata.len(),
125 };
126
127 // Read file content
128 let file_content = fs::read(file_path).await
129 .with_context(|| format!("Failed to read file: {:?}", file_path))?;
130
131 let response = self
132 .client
133 .post(&format!("{}/api/upload", self.node_url))
134 .json(&request)
135 .body(file_content)
136 .send()
137 .await
138 .context("Failed to upload file to node")?;
139
140 if !response.status().is_success() {
141 anyhow::bail!("Upload failed: {}", response.status());
142 }
143
144 let upload_response: UploadResponse = response
145 .json()
146 .await
147 .context("Failed to parse upload response")?;
148
149 Ok(upload_response)
150 }
151
152 pub async fn download_file(&self, file_hash: &str, output_path: &Path) -> Result<()> {
153 let request = DownloadRequest {
154 file_hash: file_hash.to_string(),
155 output_path: output_path.to_string_lossy().to_string(),
156 };
157
158 let response = self
159 .client
160 .post(&format!("{}/api/download", self.node_url))
161 .json(&request)
162 .send()
163 .await
164 .context("Failed to download file from node")?;
165
166 if !response.status().is_success() {
167 anyhow::bail!("Download failed: {}", response.status());
168 }
169
170 let file_content = response
171 .bytes()
172 .await
173 .context("Failed to read download response")?;
174
175 fs::write(output_path, file_content).await
176 .with_context(|| format!("Failed to write file: {:?}", output_path))?;
177
178 Ok(())
179 }
180
181 pub async fn join_network(&self, bootstrap_peer: &str) -> Result<()> {
182 let response = self
183 .client
184 .post(&format!("{}/api/network/join", self.node_url))
185 .json(&serde_json::json!({ "bootstrap_peer": bootstrap_peer }))
186 .send()
187 .await
188 .context("Failed to join network")?;
189
190 if !response.status().is_success() {
191 anyhow::bail!("Failed to join network: {}", response.status());
192 }
193
194 Ok(())
195 }
196
197 pub async fn initialize_node(&self) -> Result<()> {
198 let response = self
199 .client
200 .post(&format!("{}/api/node/init", self.node_url))
201 .send()
202 .await
203 .context("Failed to initialize node")?;
204
205 if !response.status().is_success() {
206 anyhow::bail!("Failed to initialize node: {}", response.status());
207 }
208
209 Ok(())
210 }
211 }