Skip to main content

rskit_storage/
transfer.rs

1//! Streaming I/O helpers for copying and transferring files.
2
3use rskit_errors::AppResult;
4
5use crate::{
6    FileSink, FileSource, ProgressCallback, UploadOptions, store::FileStore, store::StoredFile,
7};
8
9/// Copy a file from source to sink, optionally reporting progress.
10pub async fn copy_file(
11    source: &FileSource,
12    sink: &FileSink,
13    _on_progress: Option<ProgressCallback>,
14) -> AppResult<FileSource> {
15    let data = source.read_all().await?;
16    let mut writer = sink.writer().await?;
17    writer.write_all(&data).await?;
18    writer.finalize().await
19}
20
21/// Transfer a file between two stores.
22pub async fn transfer(
23    from_store: &dyn FileStore,
24    from_key: &str,
25    to_store: &dyn FileStore,
26    to_key: &str,
27) -> AppResult<StoredFile> {
28    let source = from_store.download(from_key).await?;
29    let meta = from_store.head(from_key).await?;
30    to_store
31        .upload(
32            &source,
33            to_key,
34            UploadOptions::new().with_content_type(&meta.content_type),
35        )
36        .await
37}
38
39#[cfg(test)]
40mod tests {
41    use bytes::Bytes;
42
43    use super::*;
44    use crate::{FileStore, LocalStore, LocalStoreConfig};
45
46    #[tokio::test]
47    async fn copy_file_moves_source_bytes_to_each_sink_type() {
48        let source = FileSource::from_bytes(Bytes::from_static(b"copy-data"));
49
50        let memory = copy_file(&source, &FileSink::Memory, None).await.unwrap();
51        assert_eq!(
52            memory.read_all().await.unwrap(),
53            Bytes::from_static(b"copy-data")
54        );
55
56        let temp = copy_file(&source, &FileSink::Temp, None).await.unwrap();
57        assert_eq!(
58            temp.read_all().await.unwrap(),
59            Bytes::from_static(b"copy-data")
60        );
61
62        let dir = tempfile::tempdir().unwrap();
63        let output = dir.path().join("copied.bin");
64        let path = copy_file(&source, &FileSink::Path(output.clone()), None)
65            .await
66            .unwrap();
67        assert!(matches!(path, FileSource::Path(_)));
68        assert_eq!(tokio::fs::read(output).await.unwrap(), b"copy-data");
69    }
70
71    #[tokio::test]
72    async fn transfer_downloads_from_source_store_and_uploads_to_destination() {
73        let from_dir = tempfile::tempdir().unwrap();
74        let to_dir = tempfile::tempdir().unwrap();
75        let from_store = LocalStore::new(LocalStoreConfig {
76            root_dir: from_dir.path().to_path_buf(),
77            auto_create: false,
78        })
79        .unwrap();
80        let to_store = LocalStore::new(LocalStoreConfig {
81            root_dir: to_dir.path().to_path_buf(),
82            auto_create: false,
83        })
84        .unwrap();
85
86        from_store
87            .upload(
88                &FileSource::from_bytes(Bytes::from_static(b"transfer-data")),
89                "source.bin",
90                UploadOptions::new().with_content_type("application/custom"),
91            )
92            .await
93            .unwrap();
94
95        let stored = transfer(&from_store, "source.bin", &to_store, "nested/target.bin")
96            .await
97            .unwrap();
98
99        assert_eq!(stored.key, "nested/target.bin");
100        assert_eq!(stored.content_type, "application/octet-stream");
101        assert_eq!(
102            to_store
103                .download("nested/target.bin")
104                .await
105                .unwrap()
106                .read_all()
107                .await
108                .unwrap(),
109            Bytes::from_static(b"transfer-data")
110        );
111    }
112}