1use crate::io::{Directory, LocalDirectory, RemoteDirectory};
6use crate::path::{
7 add_source_filename_to_path_if_absent, LocalOrRemoteComponentStoragePath,
8 REMOTE_COMPONENT_STORAGE_PATH_HELP,
9};
10use anyhow::{anyhow, bail, Result};
11use std::path::PathBuf;
12
13use flex_client::ProxyHasDomain;
14use flex_fuchsia_io as fio;
15use flex_fuchsia_sys2::StorageAdminProxy;
16
17pub async fn copy(
25 storage_admin: StorageAdminProxy,
26 source_path: String,
27 destination_path: String,
28) -> Result<()> {
29 let (dir_proxy, server) = storage_admin.domain().create_proxy::<fio::DirectoryMarker>();
30 let server = server.into_channel();
31 let storage_dir = RemoteDirectory::from_proxy(dir_proxy);
32
33 match (
34 LocalOrRemoteComponentStoragePath::parse(&source_path),
35 LocalOrRemoteComponentStoragePath::parse(&destination_path),
36 ) {
37 (
38 LocalOrRemoteComponentStoragePath::Remote(source),
39 LocalOrRemoteComponentStoragePath::Local(destination_path),
40 ) => {
41 storage_admin
43 .open_component_storage_by_id(&source.instance_id, server.into())
44 .await?
45 .map_err(|e| anyhow!("Could not open component storage: {:?}", e))?;
46
47 let destination_dir = LocalDirectory::new();
48 do_copy(&storage_dir, &source.relative_path, &destination_dir, &destination_path).await
49 }
50 (
51 LocalOrRemoteComponentStoragePath::Local(source_path),
52 LocalOrRemoteComponentStoragePath::Remote(destination),
53 ) => {
54 storage_admin
56 .open_component_storage_by_id(&destination.instance_id, server.into())
57 .await?
58 .map_err(|e| anyhow!("Could not open component storage: {:?}", e))?;
59
60 let source_dir = LocalDirectory::new();
61 do_copy(&source_dir, &source_path, &storage_dir, &destination.relative_path).await
62 }
63 _ => {
64 bail!(
65 "One path must be remote and the other must be host. {}",
66 REMOTE_COMPONENT_STORAGE_PATH_HELP
67 )
68 }
69 }
70}
71
72async fn do_copy<S: Directory, D: Directory>(
73 source_dir: &S,
74 source_path: &PathBuf,
75 destination_dir: &D,
76 destination_path: &PathBuf,
77) -> Result<()> {
78 let destination_path_path =
79 add_source_filename_to_path_if_absent(destination_dir, source_path, destination_path)
80 .await?;
81
82 let data = source_dir.read_file_bytes(source_path).await?;
83 destination_dir.write_file(destination_path_path, &data).await
84}
85
86#[cfg(test)]
90mod test {
91 use super::*;
92 use crate::storage::test::{
93 node_to_file, setup_fake_storage_admin, setup_fake_storage_admin_with_tmp,
94 };
95 use flex_fuchsia_io as fio;
96 use futures::TryStreamExt;
97 use std::collections::HashMap;
98 use std::fs::{read, write};
99 use tempfile::tempdir;
100
101 const EXPECTED_DATA: [u8; 4] = [0x0, 0x1, 0x2, 0x3];
102
103 fn setup_fake_directory(mut root_dir: fio::DirectoryRequestStream) {
106 fuchsia_async::Task::local(async move {
107 let request = root_dir.try_next().await;
110 if let Ok(Some(fio::DirectoryRequest::Open { path, flags, object, .. })) = request {
111 if path == "from_local" {
112 assert!(flags.intersects(fio::Flags::FLAG_MAYBE_CREATE));
113 setup_fake_file_from_local(node_to_file(object.into()));
114 } else if path == "from_device" {
115 setup_fake_file_from_device(node_to_file(object.into()));
116 } else {
117 panic!("incorrect path: {}", path);
118 }
119 } else {
120 panic!("did not get open request: {:?}", request)
121 }
122 })
123 .detach();
124 }
125
126 fn setup_fake_file_from_local(mut file: fio::FileRequestStream) {
127 fuchsia_async::Task::local(async move {
128 let request = file.try_next().await;
131 if let Ok(Some(fio::FileRequest::Resize { length, responder })) = request {
132 assert_eq!(length, 0);
133 responder.send(Ok(())).unwrap();
134 } else {
135 panic!("did not get resize request: {:?}", request)
136 }
137
138 let request = file.try_next().await;
140 if let Ok(Some(fio::FileRequest::Write { data, responder })) = request {
141 assert_eq!(data, EXPECTED_DATA);
142 responder.send(Ok(data.len() as u64)).unwrap();
143 } else {
144 panic!("did not get write request: {:?}", request)
145 }
146
147 let request = file.try_next().await;
149 if let Ok(Some(fio::FileRequest::Close { responder })) = request {
150 responder.send(Ok(())).unwrap();
151 } else {
152 panic!("did not get close request: {:?}", request)
153 }
154 })
155 .detach();
156 }
157
158 fn setup_fake_file_from_device(mut file: fio::FileRequestStream) {
159 fuchsia_async::Task::local(async move {
160 let request = file.try_next().await;
163 if let Ok(Some(fio::FileRequest::Read { responder, .. })) = request {
164 responder.send(Ok(&EXPECTED_DATA)).unwrap();
165 } else {
166 panic!("did not get read request: {:?}", request)
167 }
168
169 let request = file.try_next().await;
171 if let Ok(Some(fio::FileRequest::Read { responder, .. })) = request {
172 responder.send(Ok(&[])).unwrap();
173 } else {
174 panic!("did not get read request: {:?}", request)
175 }
176
177 let request = file.try_next().await;
179 if let Ok(Some(fio::FileRequest::Close { responder })) = request {
180 responder.send(Ok(())).unwrap();
181 } else {
182 panic!("did not get close request: {:?}", request)
183 }
184 })
185 .detach();
186 }
187
188 #[fuchsia::test]
189 async fn test_copy_local_to_device() -> Result<()> {
190 let dir = tempdir().unwrap();
191 let storage_admin = setup_fake_storage_admin_with_tmp("123456", HashMap::new());
192 let from_local_filepath = dir.path().join("from_local");
193 write(&from_local_filepath, &EXPECTED_DATA).unwrap();
194 copy(
195 storage_admin,
196 from_local_filepath.display().to_string(),
197 "123456::from_local".to_string(),
198 )
199 .await
200 }
201
202 #[fuchsia::test]
203 async fn test_copy_local_to_device_different_file_names() -> Result<()> {
204 let dir = tempdir().unwrap();
205 let storage_admin = setup_fake_storage_admin_with_tmp("123456", HashMap::new());
206 let from_local_filepath = dir.path().join("from_local");
207 write(&from_local_filepath, &EXPECTED_DATA).unwrap();
208 copy(
209 storage_admin,
210 from_local_filepath.display().to_string(),
211 "123456::from_local_test".to_string(),
212 )
213 .await
214 }
215
216 #[fuchsia::test]
217 async fn test_copy_local_to_device_infer_path() -> Result<()> {
218 let dir = tempdir().unwrap();
219 let storage_admin = setup_fake_storage_admin_with_tmp("123456", HashMap::new());
220 let from_local_filepath = dir.path().join("from_local");
221 write(&from_local_filepath, &EXPECTED_DATA).unwrap();
222 copy(storage_admin, from_local_filepath.display().to_string(), "123456::".to_string()).await
223 }
224
225 #[fuchsia::test]
226 async fn test_copy_local_to_device_infer_slash_path() -> Result<()> {
227 let dir = tempdir().unwrap();
228 let storage_admin = setup_fake_storage_admin_with_tmp("123456", HashMap::new());
229 let from_local_filepath = dir.path().join("from_local");
230 write(&from_local_filepath, &EXPECTED_DATA).unwrap();
231 copy(storage_admin, from_local_filepath.display().to_string(), "123456::/".to_string())
232 .await
233 }
234
235 #[fuchsia::test]
236 async fn test_copy_local_to_device_overwrite_file() -> Result<()> {
237 let dir = tempdir().unwrap();
238 let mut seed_files = HashMap::new();
239 seed_files.insert("from_local", "Lorem Ipsum");
240 let storage_admin = setup_fake_storage_admin_with_tmp("123456", seed_files);
241 let from_local_filepath = dir.path().join("from_local");
242 write(&from_local_filepath, &EXPECTED_DATA).unwrap();
243 copy(
244 storage_admin,
245 from_local_filepath.display().to_string(),
246 "123456::from_local".to_string(),
247 )
248 .await
249 }
250
251 #[fuchsia::test]
252 async fn test_copy_local_to_device_populated_directory() -> Result<()> {
253 let dir = tempdir().unwrap();
254 let mut seed_files = HashMap::new();
255
256 seed_files.insert("foo.txt", "Lorem Ipsum");
257
258 let storage_admin = setup_fake_storage_admin_with_tmp("123456", seed_files);
259 let from_local_filepath = dir.path().join("from_local");
260 write(&from_local_filepath, &EXPECTED_DATA).unwrap();
261 copy(
262 storage_admin,
263 from_local_filepath.display().to_string(),
264 "123456::from_local".to_string(),
265 )
266 .await
267 }
268
269 #[fuchsia::test]
270 async fn test_copy_device_to_local_infer_path() -> Result<()> {
271 let dir = tempdir().unwrap();
272 let storage_admin = setup_fake_storage_admin("123456", setup_fake_directory);
273 let dest_filepath = dir.path();
274
275 copy(storage_admin, "123456::from_device".to_string(), dest_filepath.display().to_string())
276 .await?;
277
278 let final_path = dest_filepath.join("from_device");
279 let actual_data = read(final_path).unwrap();
280 assert_eq!(actual_data, EXPECTED_DATA);
281 Ok(())
282 }
283
284 #[fuchsia::test]
285 async fn test_copy_device_to_local_infer_slash_path() -> Result<()> {
286 let dir = tempdir().unwrap();
287 let storage_admin = setup_fake_storage_admin("123456", setup_fake_directory);
288 let dest_filepath = dir.path();
289
290 copy(
291 storage_admin,
292 "123456::from_device".to_string(),
293 dest_filepath.display().to_string() + "/",
294 )
295 .await?;
296
297 let final_path = dest_filepath.join("from_device");
298 let actual_data = read(final_path).unwrap();
299 assert_eq!(actual_data, EXPECTED_DATA);
300 Ok(())
301 }
302
303 #[fuchsia::test]
304 async fn test_copy_device_to_local() -> Result<()> {
305 let dir = tempdir().unwrap();
306 let storage_admin = setup_fake_storage_admin("123456", setup_fake_directory);
307 let dest_filepath = dir.path().join("from_device");
308 copy(storage_admin, "123456::from_device".to_string(), dest_filepath.display().to_string())
309 .await?;
310 let actual_data = read(dest_filepath).unwrap();
311 assert_eq!(actual_data, EXPECTED_DATA);
312 Ok(())
313 }
314}