backup_suite/core/
copy_engine.rs

1use std::fs::File;
2use std::io::{BufReader, BufWriter, Read, Write};
3use std::path::Path;
4
5use crate::error::Result;
6
7/// ファイルコピー最適化エンジン
8///
9/// ファイルサイズに応じて最適なコピー手法を選択します。
10///
11/// # 機能
12///
13/// - 小ファイル: 標準の`fs::copy`を使用(高速)
14/// - 大ファイル: バッファリングコピー(メモリ効率)
15/// - 並列処理対応: 複数ファイルの同時コピー
16///
17/// # 使用例
18///
19/// ```no_run
20/// use backup_suite::core::copy_engine::CopyEngine;
21/// use std::path::Path;
22///
23/// let engine = CopyEngine::new();
24/// let bytes = engine.copy_file(
25///     Path::new("/source/file.txt"),
26///     Path::new("/dest/file.txt")
27/// ).unwrap();
28/// println!("{}バイトをコピーしました", bytes);
29/// ```
30#[derive(Debug, Clone)]
31pub struct CopyEngine {
32    /// バッファサイズ(バイト)
33    buffer_size: usize,
34    /// 並列処理を開始するファイルサイズの閾値(バイト)
35    parallel_threshold: u64,
36}
37
38impl CopyEngine {
39    /// デフォルト設定でCopyEngineを作成
40    ///
41    /// - バッファサイズ: 64KB
42    /// - 並列処理閾値: 10MB
43    ///
44    /// # 戻り値
45    ///
46    /// CopyEngineインスタンス
47    ///
48    /// # 使用例
49    ///
50    /// ```no_run
51    /// use backup_suite::core::copy_engine::CopyEngine;
52    ///
53    /// let engine = CopyEngine::new();
54    /// ```
55    #[must_use]
56    pub fn new() -> Self {
57        Self {
58            buffer_size: 64 * 1024,               // 64KB
59            parallel_threshold: 10 * 1024 * 1024, // 10MB
60        }
61    }
62
63    /// カスタム設定でCopyEngineを作成
64    ///
65    /// # 引数
66    ///
67    /// * `buffer_size` - バッファサイズ(バイト)
68    /// * `parallel_threshold` - 並列処理閾値(バイト)
69    ///
70    /// # 戻り値
71    ///
72    /// カスタム設定のCopyEngineインスタンス
73    ///
74    /// # 使用例
75    ///
76    /// ```no_run
77    /// use backup_suite::core::copy_engine::CopyEngine;
78    ///
79    /// let engine = CopyEngine::with_config(128 * 1024, 20 * 1024 * 1024);
80    /// ```
81    #[must_use]
82    pub fn with_config(buffer_size: usize, parallel_threshold: u64) -> Self {
83        Self {
84            buffer_size,
85            parallel_threshold,
86        }
87    }
88
89    /// ファイルをコピー
90    ///
91    /// ファイルサイズに応じて最適なコピー手法を自動選択します。
92    ///
93    /// # 引数
94    ///
95    /// * `source` - コピー元ファイルパス
96    /// * `dest` - コピー先ファイルパス
97    ///
98    /// # 戻り値
99    ///
100    /// コピーしたバイト数
101    ///
102    /// # Errors
103    ///
104    /// 以下の場合にエラーを返します:
105    /// * ファイルメタデータの取得に失敗した場合
106    /// * ファイルのオープンに失敗した場合(読み取り・書き込み)
107    /// * ファイルの読み込みに失敗した場合
108    /// * ファイルの書き込みに失敗した場合
109    /// * バッファのフラッシュに失敗した場合
110    /// * ファイルパーミッションの設定に失敗した場合(Unix系OSのみ)
111    ///
112    /// # 使用例
113    ///
114    /// ```no_run
115    /// use backup_suite::core::copy_engine::CopyEngine;
116    /// use std::path::Path;
117    ///
118    /// let engine = CopyEngine::new();
119    /// let bytes = engine.copy_file(
120    ///     Path::new("/source/file.txt"),
121    ///     Path::new("/dest/file.txt")
122    /// ).unwrap();
123    /// ```
124    pub fn copy_file(&self, source: &Path, dest: &Path) -> Result<u64> {
125        // ファイルサイズを取得
126        let metadata = std::fs::metadata(source)?;
127        let size = metadata.len();
128
129        // 小さいファイルは標準のコピーを使用(最速)
130        if size < self.parallel_threshold {
131            return std::fs::copy(source, dest).map_err(Into::into);
132        }
133
134        // 大きいファイルはバッファリングコピー(メモリ効率的)
135        self.buffered_copy(source, dest)
136    }
137
138    /// バッファリングコピーを実行
139    ///
140    /// 大きいファイルをメモリ効率的にコピーします。
141    ///
142    /// # 引数
143    ///
144    /// * `source` - コピー元ファイルパス
145    /// * `dest` - コピー先ファイルパス
146    ///
147    /// # 戻り値
148    ///
149    /// コピーしたバイト数
150    #[allow(clippy::indexing_slicing)] // read() guarantees bytes_read <= buffer.len()
151    fn buffered_copy(&self, source: &Path, dest: &Path) -> Result<u64> {
152        let mut reader = BufReader::with_capacity(self.buffer_size, File::open(source)?);
153        let mut writer = BufWriter::with_capacity(self.buffer_size, File::create(dest)?);
154
155        let mut buffer = vec![0u8; self.buffer_size];
156        let mut total_bytes = 0u64;
157
158        loop {
159            let bytes_read = reader.read(&mut buffer)?;
160            if bytes_read == 0 {
161                break;
162            }
163
164            writer.write_all(&buffer[..bytes_read])?;
165            total_bytes += bytes_read as u64;
166        }
167
168        writer.flush()?;
169
170        // メタデータのコピー(パーミッション等)
171        #[cfg(unix)]
172        {
173            let perms = std::fs::metadata(source)?.permissions();
174            std::fs::set_permissions(dest, perms)?;
175        }
176
177        Ok(total_bytes)
178    }
179
180    /// バッファサイズを取得
181    ///
182    /// # 戻り値
183    ///
184    /// 現在のバッファサイズ(バイト)
185    #[must_use]
186    pub fn buffer_size(&self) -> usize {
187        self.buffer_size
188    }
189
190    /// 並列処理閾値を取得
191    ///
192    /// # 戻り値
193    ///
194    /// 並列処理を開始するファイルサイズ(バイト)
195    #[must_use]
196    pub fn parallel_threshold(&self) -> u64 {
197        self.parallel_threshold
198    }
199}
200
201impl Default for CopyEngine {
202    fn default() -> Self {
203        Self::new()
204    }
205}
206
207#[cfg(test)]
208mod tests {
209    use super::*;
210    use std::io::Write;
211    use tempfile::TempDir;
212
213    #[test]
214    fn test_new_engine() {
215        let engine = CopyEngine::new();
216        assert_eq!(engine.buffer_size(), 64 * 1024);
217        assert_eq!(engine.parallel_threshold(), 10 * 1024 * 1024);
218    }
219
220    #[test]
221    fn test_with_config() {
222        let engine = CopyEngine::with_config(128 * 1024, 20 * 1024 * 1024);
223        assert_eq!(engine.buffer_size(), 128 * 1024);
224        assert_eq!(engine.parallel_threshold(), 20 * 1024 * 1024);
225    }
226
227    #[test]
228    fn test_copy_small_file() {
229        let temp_dir = TempDir::new().unwrap();
230        let source = temp_dir.path().join("source.txt");
231        let dest = temp_dir.path().join("dest.txt");
232
233        // 小さいファイルを作成
234        let mut file = File::create(&source).unwrap();
235        file.write_all(b"test content").unwrap();
236
237        let engine = CopyEngine::new();
238        let bytes = engine.copy_file(&source, &dest).unwrap();
239
240        assert_eq!(bytes, 12);
241        assert!(dest.exists());
242
243        let content = std::fs::read_to_string(&dest).unwrap();
244        assert_eq!(content, "test content");
245    }
246
247    #[test]
248    fn test_copy_large_file() {
249        let temp_dir = TempDir::new().unwrap();
250        let source = temp_dir.path().join("large_source.bin");
251        let dest = temp_dir.path().join("large_dest.bin");
252
253        // 大きいファイルを作成(15MB)
254        let large_content = vec![0u8; 15 * 1024 * 1024];
255        std::fs::write(&source, &large_content).unwrap();
256
257        let engine = CopyEngine::new();
258        let bytes = engine.copy_file(&source, &dest).unwrap();
259
260        assert_eq!(bytes, 15 * 1024 * 1024);
261        assert!(dest.exists());
262
263        let metadata = std::fs::metadata(&dest).unwrap();
264        assert_eq!(metadata.len(), 15 * 1024 * 1024);
265    }
266
267    #[test]
268    fn test_buffered_copy() {
269        let temp_dir = TempDir::new().unwrap();
270        let source = temp_dir.path().join("buffered_source.txt");
271        let dest = temp_dir.path().join("buffered_dest.txt");
272
273        // テストファイル作成
274        let content = "buffered copy test content";
275        std::fs::write(&source, content).unwrap();
276
277        let engine = CopyEngine::new();
278        let bytes = engine.buffered_copy(&source, &dest).unwrap();
279
280        assert_eq!(bytes, content.len() as u64);
281        assert!(dest.exists());
282
283        let copied_content = std::fs::read_to_string(&dest).unwrap();
284        assert_eq!(copied_content, content);
285    }
286
287    #[test]
288    fn test_default_engine() {
289        let engine = CopyEngine::default();
290        assert_eq!(engine.buffer_size(), 64 * 1024);
291    }
292
293    #[cfg(unix)]
294    #[test]
295    fn test_preserve_permissions() {
296        use std::os::unix::fs::PermissionsExt;
297
298        let temp_dir = TempDir::new().unwrap();
299        let source = temp_dir.path().join("perm_source.txt");
300        let dest = temp_dir.path().join("perm_dest.txt");
301
302        // ファイル作成とパーミッション設定
303        let mut file = File::create(&source).unwrap();
304        file.write_all(b"permission test").unwrap();
305        drop(file);
306
307        let mut perms = std::fs::metadata(&source).unwrap().permissions();
308        perms.set_mode(0o644);
309        std::fs::set_permissions(&source, perms).unwrap();
310
311        // コピー実行
312        let engine = CopyEngine::new();
313        engine.copy_file(&source, &dest).unwrap();
314
315        // パーミッション確認
316        let dest_perms = std::fs::metadata(&dest).unwrap().permissions();
317        assert_eq!(dest_perms.mode() & 0o777, 0o644);
318    }
319}