├── .gitignore ├── .travis.yml ├── CONTRIBUTING.md ├── Cargo.toml ├── LICENSE ├── README.md ├── src ├── dir.rs ├── error.rs ├── file.rs └── lib.rs └── tests ├── dir.rs ├── file.rs ├── lib.rs └── temp ├── dir └── empty ├── file └── empty └── lib └── empty /.gitignore: -------------------------------------------------------------------------------- 1 | # Generated by Cargo 2 | # will have compiled files and executables 3 | /target/ 4 | 5 | # Remove Cargo.lock from gitignore if creating an executable, leave it for libraries 6 | # More information here http://doc.crates.io/guide.html#cargotoml-vs-cargolock 7 | Cargo.lock 8 | 9 | /tests/temp/file/*/* 10 | /tests/temp/dir/*/* 11 | /tests/temp/lib/*/* 12 | 13 | .vscode/ 14 | 15 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: rust 2 | rust: 3 | - stable 4 | script: 5 | - cargo build --verbose 6 | - cargo test --verbose 7 | - cargo doc 8 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contribute to fs_extra 2 | ## About me 3 | Hello everyone, my name is Denis. I'm a maintainer at the current time of this project. I begin to write when I study rust. Because the current code base has some inconvenience. When I work full time on rust in other projects I found it. But don't have free time to fix it. Many people make a request to get full access to fs_extra and I'm ready to do it. But I should make sure that the project moves into good hands. Because I planing find 3 hours per week for this project. I'm not sure I have time for programming. But this time I plan to spend on a merge PR, publish a new version, and answer issues. Maybe sometime I found time for codding. But my life very changed much after 24 February when war is begin... When my stupid government begins it. I lost my business and lеft my country because I don't support what Russians dot it right now. And this little step, what I can do. I've tried to go to the street and say I am against it, but people who follow Russia they know how it ends. Right now have a more important task in my life to survive. 4 | 5 | ## Report for bugs 6 | We use GitHub issues for our public bugs. If you would like to report a problem, take a look around and see if someone already opened an issue about it. If you are certain this is a new unreported bug, you can submit a bug report. 7 | If you have questions about using fs_extra you can ask us in [Discussions](https://github.com/webdesus/fs_extra/discussions) 8 | 9 | ## Pull request 10 | So you have decided to contribute code back to upstream by opening a pull request. You've invested a good chunk of time, and we appreciate it. We will do our best to work with you and get the PR looked at. 11 | Your PR will be awesome if he follows: 12 | - Dependencies are optional. If you add a feature that requires installing a new library this feature should use Rust compile option for switch on. Default off. 13 | Structs and methods for new features which will be using external lib should contain in `src/features/` folder. 14 | - Contains test cases if you add new features. 15 | ## RFC 16 | We store RFC documents using md format in `src/rfc` folder. Using PR mechanism you can offer new ideas for our project 17 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "fs_extra" 3 | version = "1.3.0" 4 | edition = "2018" 5 | description = "Expanding std::fs and std::io. Recursively copy folders with information about process and much more." 6 | license = "MIT" 7 | authors = ["Denis Kurilenko "] 8 | keywords = ["filesystem", "recursion", "copy", "dir", "file"] 9 | homepage = "https://github.com/webdesus/fs_extra" 10 | documentation = "https://docs.rs/fs_extra" 11 | repository = "https://github.com/webdesus/fs_extra" 12 | include = [ 13 | "**/*.rs", 14 | "Cargo.toml", 15 | "LICENSE", 16 | "README.md", 17 | "CHANGELOG.md", 18 | ] 19 | 20 | [dependencies] 21 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Denis Kurilenko 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # fs_extra 2 | 3 | A Rust library that provides additional functionality not present in [`std::fs`](https://doc.rust-lang.org/std/fs/index.html). 4 | 5 | [![Build Status](https://travis-ci.org/webdesus/fs_extra.svg)](https://travis-ci.org/webdesus/fs_extra) 6 | [![Crates.io Status](https://img.shields.io/crates/v/fs_extra.svg)](https://crates.io/crates/fs_extra) 7 | [![Docs](https://docs.rs/fs_extra/badge.svg)](https://docs.rs/fs_extra) 8 | 9 | [Documentation](https://docs.rs/fs_extra) 10 | 11 | [Migrations to 1.x.x version](https://github.com/webdesus/fs_extra/wiki/Migrations-to-1.x.x-version) 12 | 13 | 14 | ## Key features: 15 | 16 | * Copy files (optionally with information about the progress). 17 | 18 | * Copy directories recursively (optionally with information about the progress). 19 | 20 | * Move files (optionally with information about the progress). 21 | 22 | * Move directories recursively (optionally with information about the progress). 23 | 24 | * A single method for create and write `String` content in file. 25 | 26 | * A single method for open and read `String` content from file. 27 | 28 | * Get folder size 29 | 30 | * Get collection of directory entries 31 | 32 | ## Functions: 33 | 34 | | Function | Description | 35 | | ------------- | ------------- | 36 | | [fs_extra::copy_items](https://docs.rs/fs_extra/*/fs_extra/fn.copy_items.html) | Recursively copies files and directories from one location to another | 37 | | [fs_extra::copy_items_with_progress](https://docs.rs/fs_extra/*/fs_extra/fn.copy_items_with_progress.html) | Recursively copies files and directories from one location to another with information about progress | 38 | | [fs_extra::move_items](https://docs.rs/fs_extra/*/fs_extra/fn.move_items.html) | Recursively moves files and directories from one location to another | 39 | | [fs_extra::move_items_with_progress](https://docs.rs/fs_extra/*/fs_extra/fn.move_items_with_progress.html) | Recursively moves files and directories from one location to another with information about progress | 40 | | [fs_extra::remove_items](https://docs.rs/fs_extra/*/fs_extra/fn.remove_items.html) | Removes files or directories | 41 | | [fs_extra::file::copy](https://docs.rs/fs_extra/*/fs_extra/file/fn.copy.html) | Copies the contents of one file to another | 42 | | [fs_extra::file::copy_with_progress](https://docs.rs/fs_extra/*/fs_extra/file/fn.copy_with_progress.html) | Copies the contents of one file to another with information about progress | 43 | | [fs_extra::file::move_file](https://docs.rs/fs_extra/*/fs_extra/file/fn.move_file.html) | Moves a file from one location to another | 44 | | [fs_extra::file::move_file_with_progress](https://docs.rs/fs_extra/*/fs_extra/file/fn.move_file_with_progress.html) | Moves a file from one location to another with information about progress | 45 | | [fs_extra::file::remove](https://docs.rs/fs_extra/*/fs_extra/file/fn.remove.html) | Removes a file | 46 | | [fs_extra::file::read_to_string](https://docs.rs/fs_extra/*/fs_extra/file/fn.read_to_string.html) | Reads file content into a `String` | 47 | | [fs_extra::file::write_all](https://docs.rs/fs_extra/*/fs_extra/file/fn.write_all.html) | Writes `String` content to a file | 48 | | [fs_extra::dir::create](https://docs.rs/fs_extra/*/fs_extra/dir/fn.create.html) | Creates a new, empty directory at the given path | 49 | | [fs_extra::dir::create_all](https://docs.rs/fs_extra/*/fs_extra/dir/fn.create_all.html) | Recursively creates a directory and all of its parent components if they are missing | 50 | | [fs_extra::dir::copy](https://docs.rs/fs_extra/*/fs_extra/dir/fn.copy.html) | Recursively copies the directory contents from one location to another | 51 | | [fs_extra::dir::copy_with_progress](https://docs.rs/fs_extra/*/fs_extra/dir/fn.copy_with_progress.html) | Recursively copies the directory contents from one location to another with information about progress | 52 | | [fs_extra::dir::move_dir](https://docs.rs/fs_extra/*/fs_extra/dir/fn.move_dir.html) | Moves directory contents from one location to another | 53 | | [fs_extra::dir::move_dir_with_progress](https://docs.rs/fs_extra/*/fs_extra/dir/fn.move_dir_with_progress.html) | Moves directory contents from one location to another with information about progress | 54 | | [fs_extra::dir::remove](https://docs.rs/fs_extra/*/fs_extra/dir/fn.remove.html) | Removes directory | 55 | | [fs_extra::dir::get_size](https://docs.rs/fs_extra/*/fs_extra/dir/fn.get_size.html) | Returns the size of the file or directory | 56 | | [fs_extra::dir::get_dir_content](https://docs.rs/fs_extra/*/fs_extra/dir/fn.get_dir_content.html) | Gets details such as the size and child items of a directory | 57 | | [fs_extra::dir::get_dir_content2](https://docs.rs/fs_extra/*/fs_extra/dir/fn.get_dir_content2.html) | Gets details such as the size and child items of a directory using specified settings | 58 | | [fs_extra::dir::get_details_entry](https://docs.rs/fs_extra/*/fs_extra/dir/fn.get_details_entry.html) | Gets attributes of a directory entry | 59 | | [fs_extra::dir::ls](https://docs.rs/fs_extra/*/fs_extra/dir/fn.ls.html) | Gets attributes of directory entries in a directory | 60 | 61 | ## Usage 62 | 63 | Add this to your `Cargo.toml`: 64 | ```toml 65 | [dependencies] 66 | fs_extra = "1.3.0" 67 | ``` 68 | ## Examples 69 | 70 | The following example shows how to copy a directory recursively and display progress. First a source directory `./temp/dir` containing file `test1.txt` and a subdirectory `sub` is createad with `sub` itself having a file `test2.txt`. `./temp/dir` and all contents are then copied out to `./out/dir`. 71 | 72 | ```rust 73 | use std::path::Path; 74 | use std::{thread, time}; 75 | use std::sync::mpsc::{self, TryRecvError}; 76 | 77 | extern crate fs_extra; 78 | use fs_extra::dir::*; 79 | use fs_extra::error::*; 80 | 81 | fn example_copy() -> Result<()> { 82 | 83 | let path_from = Path::new("./temp"); 84 | let path_to = path_from.join("out"); 85 | let test_folder = path_from.join("test_folder"); 86 | let dir = test_folder.join("dir"); 87 | let sub = dir.join("sub"); 88 | let file1 = dir.join("file1.txt"); 89 | let file2 = sub.join("file2.txt"); 90 | 91 | create_all(&sub, true)?; 92 | create_all(&path_to, true)?; 93 | fs_extra::file::write_all(&file1, "content1")?; 94 | fs_extra::file::write_all(&file2, "content2")?; 95 | 96 | assert!(dir.exists()); 97 | assert!(sub.exists()); 98 | assert!(file1.exists()); 99 | assert!(file2.exists()); 100 | 101 | 102 | let mut options = CopyOptions::new(); 103 | options.buffer_size = 1; 104 | let (tx, rx) = mpsc::channel(); 105 | thread::spawn(move || { 106 | let handler = |process_info: TransitProcess| { 107 | tx.send(process_info).unwrap(); 108 | thread::sleep(time::Duration::from_millis(500)); 109 | fs_extra::dir::TransitProcessResult::ContinueOrAbort 110 | }; 111 | copy_with_progress(&test_folder, &path_to, &options, handler).unwrap(); 112 | }); 113 | 114 | loop { 115 | match rx.try_recv() { 116 | Ok(process_info) => { 117 | println!("{} of {} bytes", 118 | process_info.copied_bytes, 119 | process_info.total_bytes); 120 | } 121 | Err(TryRecvError::Disconnected) => { 122 | println!("finished"); 123 | break; 124 | } 125 | Err(TryRecvError::Empty) => {} 126 | } 127 | } 128 | Ok(()) 129 | 130 | } 131 | fn main() { 132 | example_copy(); 133 | } 134 | ``` 135 | -------------------------------------------------------------------------------- /src/dir.rs: -------------------------------------------------------------------------------- 1 | use crate::error::*; 2 | use std::collections::{HashMap, HashSet}; 3 | use std::fs::{create_dir, create_dir_all, read_dir, remove_dir_all, Metadata}; 4 | use std::path::{Path, PathBuf}; 5 | use std::time::SystemTime; 6 | 7 | /// Options and flags which can be used to configure how a file will be copied or moved. 8 | #[derive(Clone)] 9 | pub struct CopyOptions { 10 | /// Overwrite existing files if true (default: false). 11 | pub overwrite: bool, 12 | /// Skip existing files if true (default: false). 13 | pub skip_exist: bool, 14 | /// Buffer size that specifies the amount of bytes to be moved or copied before the progress handler is called. This only affects functions with progress handlers. (default: 64000) 15 | pub buffer_size: usize, 16 | /// Recursively copy a directory with a new name or place it inside the destination (default: false, same behaviors as cp -r on Unix) 17 | pub copy_inside: bool, 18 | /// Copy only contents without a creating a new folder in the destination folder (default: false). 19 | pub content_only: bool, 20 | /// Sets levels reading. Set 0 for read all directory folder (default: 0). 21 | /// 22 | /// Warning: Work only for copy operations! 23 | pub depth: u64, 24 | } 25 | 26 | impl CopyOptions { 27 | /// Initialize struct CopyOptions with default value. 28 | /// 29 | /// ```rust,ignore 30 | /// overwrite: false 31 | /// 32 | /// skip_exist: false 33 | /// 34 | /// buffer_size: 64000 // 64kb 35 | /// 36 | /// copy_inside: false 37 | /// ``` 38 | pub fn new() -> CopyOptions { 39 | CopyOptions { 40 | overwrite: false, 41 | skip_exist: false, 42 | buffer_size: 64000, // 64kb 43 | copy_inside: false, 44 | content_only: false, 45 | depth: 0, 46 | } 47 | } 48 | 49 | /// Overwrite existing files if true. 50 | pub fn overwrite(mut self, overwrite: bool) -> Self { 51 | self.overwrite = overwrite; 52 | self 53 | } 54 | 55 | /// Skip existing files if true. 56 | pub fn skip_exist(mut self, skip_exist: bool) -> Self { 57 | self.skip_exist = skip_exist; 58 | self 59 | } 60 | 61 | /// Buffer size that specifies the amount of bytes to be moved or copied before the progress handler is called. This only affects functions with progress handlers. 62 | pub fn buffer_size(mut self, buffer_size: usize) -> Self { 63 | self.buffer_size = buffer_size; 64 | self 65 | } 66 | 67 | /// Recursively copy a directory with a new name or place it inside the destination (default: false, same behaviors as cp -r on Unix) 68 | pub fn copy_inside(mut self, copy_inside: bool) -> Self { 69 | self.copy_inside = copy_inside; 70 | self 71 | } 72 | 73 | /// Copy only contents without a creating a new folder in the destination folder. 74 | pub fn content_only(mut self, content_only: bool) -> Self { 75 | self.content_only = content_only; 76 | self 77 | } 78 | 79 | /// Sets levels reading. Set 0 for read all directory folder 80 | pub fn depth(mut self, depth: u64) -> Self { 81 | self.depth = depth; 82 | self 83 | } 84 | } 85 | 86 | impl Default for CopyOptions { 87 | fn default() -> Self { 88 | CopyOptions::new() 89 | } 90 | } 91 | 92 | // Options and flags which can be used to configure how to read a directory. 93 | #[derive(Clone, Default)] 94 | pub struct DirOptions { 95 | /// Sets levels reading. Set value 0 for read all directory folder. By default 0. 96 | pub depth: u64, 97 | } 98 | 99 | impl DirOptions { 100 | /// Initialize struct DirOptions with default value. 101 | pub fn new() -> DirOptions { 102 | Default::default() 103 | } 104 | } 105 | 106 | /// A structure which include information about directory 107 | pub struct DirContent { 108 | /// Directory size in bytes. 109 | pub dir_size: u64, 110 | /// List all files directory and sub directories. 111 | pub files: Vec, 112 | /// List all folders and sub folders directory. 113 | pub directories: Vec, 114 | } 115 | 116 | /// A structure which include information about the current status of the copy or move directory. 117 | pub struct TransitProcess { 118 | /// Copied bytes on this time for folder 119 | pub copied_bytes: u64, 120 | /// All the bytes which should to copy or move (dir size). 121 | pub total_bytes: u64, 122 | /// Copied bytes on this time for file. 123 | pub file_bytes_copied: u64, 124 | /// Size current copied file. 125 | pub file_total_bytes: u64, 126 | /// Name current copied file. 127 | pub file_name: String, 128 | /// Transit state 129 | pub state: TransitState, 130 | } 131 | 132 | /// 133 | #[derive(Hash, Eq, PartialEq, Clone)] 134 | pub enum TransitState { 135 | /// Standard state. 136 | Normal, 137 | /// Pause state when destination path exists. 138 | Exists, 139 | /// Pause state when current process does not have the permission to access from or to 140 | /// path. 141 | NoAccess, 142 | } 143 | 144 | /// Available returns codes for user decide 145 | pub enum TransitProcessResult { 146 | /// Rewrite exist file or directory. 147 | Overwrite, 148 | /// Rewrite for all exist files or directories. 149 | OverwriteAll, 150 | /// Skip current problem file or directory. 151 | Skip, 152 | /// Skip for all problems file or directory. 153 | SkipAll, 154 | /// Retry current operation. 155 | Retry, 156 | /// Abort current operation. 157 | Abort, 158 | /// Continue execute process if process not have error and abort if process content error. 159 | ContinueOrAbort, 160 | } 161 | 162 | impl Clone for TransitProcess { 163 | fn clone(&self) -> TransitProcess { 164 | TransitProcess { 165 | copied_bytes: self.copied_bytes, 166 | total_bytes: self.total_bytes, 167 | file_bytes_copied: self.file_bytes_copied, 168 | file_total_bytes: self.file_total_bytes, 169 | file_name: self.file_name.clone(), 170 | state: self.state.clone(), 171 | } 172 | } 173 | } 174 | 175 | /// Available attributes for get information about directory entry. 176 | #[derive(Hash, Eq, PartialEq, Clone)] 177 | pub enum DirEntryAttr { 178 | /// Folder name or file name without extension. 179 | Name, 180 | /// File extension. 181 | Ext, 182 | /// Folder name or file name with extension. 183 | FullName, 184 | /// Path to file or directory. 185 | Path, 186 | /// Dos path to file or directory. 187 | DosPath, 188 | /// File size in bytes. 189 | FileSize, 190 | /// Size file or directory in bytes. 191 | /// 192 | /// `Attention!`: This operation very expensive and sometimes required additional rights. 193 | Size, 194 | /// Return whether entry is directory or not. 195 | IsDir, 196 | /// Return whether entry is file or not. 197 | IsFile, 198 | /// Last modification time for directory entry. 199 | Modified, 200 | /// Last access time for directory entry. 201 | Accessed, 202 | /// Created time for directory entry. 203 | /// 204 | /// `Attention!`: Not supported UNIX platform. 205 | Created, 206 | /// Return or not return base information target folder. 207 | BaseInfo, 208 | } 209 | 210 | /// Available types for directory entry. 211 | pub enum DirEntryValue { 212 | /// String type 213 | String(String), 214 | /// Boolean type 215 | Boolean(bool), 216 | /// SystemTime type 217 | SystemTime(SystemTime), 218 | /// u64 type 219 | U64(u64), 220 | } 221 | 222 | /// Result returned by the `ls` function. 223 | pub struct LsResult { 224 | /// Base folder target path 225 | pub base: HashMap, 226 | /// Collection directory entry with information. 227 | pub items: Vec>, 228 | } 229 | 230 | /// Returned information about directory entry with information which you choose in config. 231 | /// 232 | /// This function takes to arguments: 233 | /// 234 | /// * `path` - Path to directory. 235 | /// 236 | /// * `config` - Set attributes which you want see inside return data. 237 | /// 238 | /// # Errors 239 | /// 240 | /// This function will return an error in the following situations, but is not limited to just 241 | /// these cases: 242 | /// 243 | /// * This `path` does not exist. 244 | /// * Invalid `path`. 245 | /// * The current process does not have the permission to access `path`. 246 | /// 247 | /// #Examples 248 | /// 249 | /// ```rust,ignore 250 | /// extern crate fs_extra; 251 | /// use fs_extra::dir::{get_details_entry, DirEntryAttr}; 252 | /// use std::collections::{HashMap, HashSet}; 253 | /// 254 | /// let mut config = HashSet::new(); 255 | /// config.insert(DirEntryAttr::Name); 256 | /// config.insert(DirEntryAttr::Size); 257 | /// 258 | /// let entry_info = get_details_entry("test", &config); 259 | /// assert_eq!(2, entry_info.len()); 260 | /// ``` 261 | pub fn get_details_entry

( 262 | path: P, 263 | config: &HashSet, 264 | ) -> Result> 265 | where 266 | P: AsRef, 267 | { 268 | let path = path.as_ref(); 269 | let metadata = path.metadata()?; 270 | get_details_entry_with_meta(path, config, metadata) 271 | } 272 | 273 | fn get_details_entry_with_meta

( 274 | path: P, 275 | config: &HashSet, 276 | metadata: Metadata, 277 | ) -> Result> 278 | where 279 | P: AsRef, 280 | { 281 | let path = path.as_ref(); 282 | let mut item = HashMap::new(); 283 | if config.contains(&DirEntryAttr::Name) { 284 | if metadata.is_dir() { 285 | if let Some(file_name) = path.file_name() { 286 | item.insert( 287 | DirEntryAttr::Name, 288 | DirEntryValue::String(file_name.to_os_string().into_string()?), 289 | ); 290 | } else { 291 | item.insert(DirEntryAttr::Name, DirEntryValue::String(String::new())); 292 | } 293 | } else if let Some(file_stem) = path.file_stem() { 294 | item.insert( 295 | DirEntryAttr::Name, 296 | DirEntryValue::String(file_stem.to_os_string().into_string()?), 297 | ); 298 | } else { 299 | item.insert(DirEntryAttr::Name, DirEntryValue::String(String::new())); 300 | } 301 | } 302 | if config.contains(&DirEntryAttr::Ext) { 303 | if let Some(value) = path.extension() { 304 | item.insert( 305 | DirEntryAttr::Ext, 306 | DirEntryValue::String(value.to_os_string().into_string()?), 307 | ); 308 | } else { 309 | item.insert(DirEntryAttr::Ext, DirEntryValue::String(String::from(""))); 310 | } 311 | } 312 | if config.contains(&DirEntryAttr::FullName) { 313 | if let Some(file_name) = path.file_name() { 314 | item.insert( 315 | DirEntryAttr::FullName, 316 | DirEntryValue::String(file_name.to_os_string().into_string()?), 317 | ); 318 | } else { 319 | item.insert(DirEntryAttr::FullName, DirEntryValue::String(String::new())); 320 | } 321 | } 322 | if config.contains(&DirEntryAttr::Path) { 323 | let mut result_path: PathBuf; 324 | match path.canonicalize() { 325 | Ok(new_path) => { 326 | result_path = new_path; 327 | } 328 | Err(_) => { 329 | if let Some(parent_path) = path.parent() { 330 | if let Some(name) = path.file_name() { 331 | result_path = parent_path.canonicalize()?; 332 | result_path.push(name); 333 | } else { 334 | err!("Error get part name path", ErrorKind::Other); 335 | } 336 | } else { 337 | err!("Error get parent path", ErrorKind::Other); 338 | } 339 | } 340 | } 341 | let mut path = result_path.as_os_str().to_os_string().into_string()?; 342 | if path.find("\\\\?\\") == Some(0) { 343 | path = path[4..].to_string(); 344 | } 345 | item.insert(DirEntryAttr::Path, DirEntryValue::String(path)); 346 | } 347 | if config.contains(&DirEntryAttr::DosPath) { 348 | let mut result_path: PathBuf; 349 | match path.canonicalize() { 350 | Ok(new_path) => { 351 | result_path = new_path; 352 | } 353 | Err(_) => { 354 | if let Some(parent_path) = path.parent() { 355 | if let Some(name) = path.file_name() { 356 | result_path = parent_path.canonicalize()?; 357 | result_path.push(name); 358 | } else { 359 | err!("Error get part name path", ErrorKind::Other); 360 | } 361 | } else { 362 | err!("Error get parent path", ErrorKind::Other); 363 | } 364 | } 365 | } 366 | let path = result_path.as_os_str().to_os_string().into_string()?; 367 | item.insert(DirEntryAttr::DosPath, DirEntryValue::String(path)); 368 | } 369 | if config.contains(&DirEntryAttr::Size) { 370 | item.insert(DirEntryAttr::Size, DirEntryValue::U64(get_size(&path)?)); 371 | } 372 | if config.contains(&DirEntryAttr::FileSize) { 373 | item.insert(DirEntryAttr::FileSize, DirEntryValue::U64(metadata.len())); 374 | } 375 | if config.contains(&DirEntryAttr::IsDir) { 376 | item.insert( 377 | DirEntryAttr::IsDir, 378 | DirEntryValue::Boolean(metadata.is_dir()), 379 | ); 380 | } 381 | if config.contains(&DirEntryAttr::IsFile) { 382 | item.insert( 383 | DirEntryAttr::IsFile, 384 | DirEntryValue::Boolean(metadata.is_file()), 385 | ); 386 | } 387 | if config.contains(&DirEntryAttr::Modified) { 388 | item.insert( 389 | DirEntryAttr::Modified, 390 | DirEntryValue::SystemTime(metadata.modified()?), 391 | ); 392 | } 393 | if config.contains(&DirEntryAttr::Accessed) { 394 | item.insert( 395 | DirEntryAttr::Accessed, 396 | DirEntryValue::SystemTime(metadata.accessed()?), 397 | ); 398 | } 399 | if config.contains(&DirEntryAttr::Created) { 400 | item.insert( 401 | DirEntryAttr::Created, 402 | DirEntryValue::SystemTime(metadata.created()?), 403 | ); 404 | } 405 | Ok(item) 406 | } 407 | 408 | /// Returns a collection of directory entries with attributes specifying the information that should be returned. 409 | /// 410 | /// This function takes to arguments: 411 | /// 412 | /// * `path` - Path to directory. 413 | /// 414 | /// * `config` - Set attributes which you want see in return data. 415 | /// 416 | /// # Errors 417 | /// 418 | /// This function will return an error in the following situations, but is not limited to just 419 | /// these cases: 420 | /// 421 | /// * This `path` directory does not exist. 422 | /// * Invalid `path`. 423 | /// * The current process does not have the permission to access `path`. 424 | /// 425 | /// #Examples 426 | /// 427 | /// ```rust,ignore 428 | /// extern crate fs_extra; 429 | /// use fs_extra::dir::{ls, DirEntryAttr, LsResult}; 430 | /// use std::collections::HashSet; 431 | /// 432 | /// let mut config = HashSet::new(); 433 | /// config.insert(DirEntryAttr::Name); 434 | /// config.insert(DirEntryAttr::Size); 435 | /// config.insert(DirEntryAttr::BaseInfo); 436 | /// 437 | /// let result = ls("test", &config); 438 | /// assert_eq!(2, ls_result.items.len()); 439 | /// assert_eq!(2, ls_result.base.len()); 440 | /// ``` 441 | pub fn ls

(path: P, config: &HashSet) -> Result 442 | where 443 | P: AsRef, 444 | { 445 | let mut items = Vec::new(); 446 | let path = path.as_ref(); 447 | if !path.is_dir() { 448 | err!("Path does not directory", ErrorKind::InvalidFolder); 449 | } 450 | for entry in read_dir(&path)? { 451 | let entry = entry?; 452 | let path = entry.path(); 453 | let metadata = entry.metadata()?; 454 | let item = get_details_entry_with_meta(path, &config, metadata)?; 455 | items.push(item); 456 | } 457 | let mut base = HashMap::new(); 458 | if config.contains(&DirEntryAttr::BaseInfo) { 459 | base = get_details_entry(&path, &config)?; 460 | } 461 | Ok(LsResult { items, base }) 462 | } 463 | 464 | /// Creates a new, empty directory at the provided path. 465 | /// 466 | /// This function takes to arguments: 467 | /// 468 | /// * `path` - Path to new directory. 469 | /// 470 | /// * `erase` - If set true and folder exist, then folder will be erased. 471 | /// 472 | /// #Errors 473 | /// 474 | /// This function will return an error in the following situations, 475 | /// but is not limited to just these cases: 476 | /// 477 | /// * User lacks permissions to create directory at `path`. 478 | /// 479 | /// * `path` already exists if `erase` set false. 480 | /// 481 | /// #Examples 482 | /// 483 | /// ```rust,ignore 484 | /// extern crate fs_extra; 485 | /// use fs_extra::dir::create; 486 | /// 487 | /// create("dir", false); // create directory 488 | /// ``` 489 | pub fn create

(path: P, erase: bool) -> Result<()> 490 | where 491 | P: AsRef, 492 | { 493 | if erase && path.as_ref().exists() { 494 | remove(&path)?; 495 | } 496 | Ok(create_dir(&path)?) 497 | } 498 | 499 | /// Recursively create a directory and all of its parent components if they are missing. 500 | /// 501 | /// This function takes to arguments: 502 | /// 503 | /// * `path` - Path to new directory. 504 | /// 505 | /// * `erase` - If set true and folder exist, then folder will be erased. 506 | /// 507 | ///#Errors 508 | /// 509 | /// This function will return an error in the following situations, 510 | /// but is not limited to just these cases: 511 | /// 512 | /// * User lacks permissions to create directory at `path`. 513 | /// 514 | /// * `path` already exists if `erase` set false. 515 | /// 516 | /// #Examples 517 | /// 518 | /// ```rust,ignore 519 | /// extern crate fs_extra; 520 | /// use fs_extra::dir::create_all; 521 | /// 522 | /// create_all("/some/dir", false); // create directory some and dir 523 | pub fn create_all

(path: P, erase: bool) -> Result<()> 524 | where 525 | P: AsRef, 526 | { 527 | if erase && path.as_ref().exists() { 528 | remove(&path)?; 529 | } 530 | Ok(create_dir_all(&path)?) 531 | } 532 | 533 | /// Copies the directory contents from one place to another using recursive method. 534 | /// This function will also copy the permission bits of the original files to 535 | /// destination files (not for directories). 536 | /// 537 | /// # Errors 538 | /// 539 | /// This function will return an error in the following situations, but is not limited to just 540 | /// these cases: 541 | /// 542 | /// * This `from` path is not a directory. 543 | /// * This `from` directory does not exist. 544 | /// * Invalid folder name for `from` or `to`. 545 | /// * The current process does not have the permission to access `from` or write `to`. 546 | /// 547 | /// # Example 548 | /// ```rust,ignore 549 | /// extern crate fs_extra; 550 | /// use fs_extra::dir::copy; 551 | /// 552 | /// let options = CopyOptions::new(); //Initialize default values for CopyOptions 553 | /// // options.mirror_copy = true; // To mirror copy the whole structure of the source directory 554 | /// 555 | /// 556 | /// // copy source/dir1 to target/dir1 557 | /// copy("source/dir1", "target/dir1", &options)?; 558 | /// 559 | /// ``` 560 | pub fn copy(from: P, to: Q, options: &CopyOptions) -> Result 561 | where 562 | P: AsRef, 563 | Q: AsRef, 564 | { 565 | let from = from.as_ref(); 566 | 567 | if !from.exists() { 568 | if let Some(msg) = from.to_str() { 569 | let msg = format!("Path \"{}\" does not exist or you don't have access!", msg); 570 | err!(&msg, ErrorKind::NotFound); 571 | } 572 | err!( 573 | "Path does not exist Or you don't have access!", 574 | ErrorKind::NotFound 575 | ); 576 | } 577 | if !from.is_dir() { 578 | if let Some(msg) = from.to_str() { 579 | let msg = format!("Path \"{}\" is not a directory!", msg); 580 | err!(&msg, ErrorKind::InvalidFolder); 581 | } 582 | err!("Path is not a directory!", ErrorKind::InvalidFolder); 583 | } 584 | let dir_name; 585 | if let Some(val) = from.components().last() { 586 | dir_name = val.as_os_str(); 587 | } else { 588 | err!("Invalid folder from", ErrorKind::InvalidFolder); 589 | } 590 | let mut to: PathBuf = to.as_ref().to_path_buf(); 591 | if (to.exists() || !options.copy_inside) && !options.content_only { 592 | to.push(dir_name); 593 | } 594 | 595 | let mut read_options = DirOptions::new(); 596 | if options.depth > 0 { 597 | read_options.depth = options.depth; 598 | } 599 | 600 | let dir_content = get_dir_content2(from, &read_options)?; 601 | for directory in dir_content.directories { 602 | let tmp_to = Path::new(&directory).strip_prefix(from)?; 603 | let dir = to.join(&tmp_to); 604 | if !dir.exists() { 605 | if options.copy_inside { 606 | create_all(dir, false)?; 607 | } else { 608 | create(dir, false)?; 609 | } 610 | } 611 | } 612 | let mut result: u64 = 0; 613 | for file in dir_content.files { 614 | let to = to.to_path_buf(); 615 | let tp = Path::new(&file).strip_prefix(from)?; 616 | let path = to.join(&tp); 617 | 618 | let file_options = super::file::CopyOptions { 619 | overwrite: options.overwrite, 620 | skip_exist: options.skip_exist, 621 | buffer_size: options.buffer_size, 622 | }; 623 | let mut result_copy: Result; 624 | let mut work = true; 625 | 626 | while work { 627 | result_copy = super::file::copy(&file, &path, &file_options); 628 | match result_copy { 629 | Ok(val) => { 630 | result += val; 631 | work = false; 632 | } 633 | Err(err) => { 634 | let err_msg = err.to_string(); 635 | err!(err_msg.as_str(), err.kind) 636 | } 637 | } 638 | } 639 | } 640 | Ok(result) 641 | } 642 | 643 | /// Return DirContent which contains information about directory: 644 | /// 645 | /// * Size of the directory in bytes. 646 | /// * List of source paths of files in the directory (files inside subdirectories included too). 647 | /// * List of source paths of all directories and subdirectories. 648 | /// 649 | /// # Errors 650 | /// 651 | /// This function will return an error in the following situations, but is not limited to just 652 | /// these cases: 653 | /// 654 | /// * This `path` directory does not exist. 655 | /// * Invalid `path`. 656 | /// * The current process does not have the permission to access `path`. 657 | /// 658 | /// # Examples 659 | /// ```rust,ignore 660 | /// extern crate fs_extra; 661 | /// use fs_extra::dir::get_dir_content; 662 | /// 663 | /// let dir_content = get_dir_content("dir")?; 664 | /// for directory in dir_content.directories { 665 | /// println!("{}", directory); // print directory path 666 | /// } 667 | /// ``` 668 | /// 669 | pub fn get_dir_content

(path: P) -> Result 670 | where 671 | P: AsRef, 672 | { 673 | let options = DirOptions::new(); 674 | get_dir_content2(path, &options) 675 | } 676 | 677 | /// Return DirContent which contains information about directory: 678 | /// 679 | /// * Size directory. 680 | /// * List all files source directory(files subdirectories included too). 681 | /// * List all directory and subdirectories source path. 682 | /// 683 | /// # Errors 684 | /// 685 | /// This function will return an error in the following situations, but is not limited to just 686 | /// these cases: 687 | /// 688 | /// * This `path` directory does not exist. 689 | /// * Invalid `path`. 690 | /// * The current process does not have the permission to access `path`. 691 | /// 692 | /// # Examples 693 | /// ```rust,ignore 694 | /// extern crate fs_extra; 695 | /// use fs_extra::dir::{DirOptions, get_dir_content2}; 696 | /// 697 | /// let mut options = DirOptions::new(); 698 | /// options.depth = 3; // Get 3 levels of folder. 699 | /// let dir_content = get_dir_content2("dir", &options)?; 700 | /// for directory in dir_content.directories { 701 | /// println!("{}", directory); // print directory path 702 | /// } 703 | /// ``` 704 | /// 705 | pub fn get_dir_content2

(path: P, options: &DirOptions) -> Result 706 | where 707 | P: AsRef, 708 | { 709 | let mut depth = 0; 710 | if options.depth != 0 { 711 | depth = options.depth + 1; 712 | } 713 | _get_dir_content(path, depth) 714 | } 715 | 716 | fn _get_dir_content

(path: P, mut depth: u64) -> Result 717 | where 718 | P: AsRef, 719 | { 720 | let mut directories = Vec::new(); 721 | let mut files = Vec::new(); 722 | let mut dir_size; 723 | let item = path.as_ref().to_str(); 724 | if item.is_none() { 725 | err!("Invalid path", ErrorKind::InvalidPath); 726 | } 727 | let item = item.unwrap().to_string(); 728 | 729 | if path.as_ref().is_dir() { 730 | dir_size = path.as_ref().metadata()?.len(); 731 | directories.push(item); 732 | if depth == 0 || depth > 1 { 733 | if depth > 1 { 734 | depth -= 1; 735 | } 736 | for entry in read_dir(&path)? { 737 | let _path = entry?.path(); 738 | 739 | match _get_dir_content(_path, depth) { 740 | Ok(items) => { 741 | let mut _files = items.files; 742 | let mut _directories = items.directories; 743 | dir_size += items.dir_size; 744 | files.append(&mut _files); 745 | directories.append(&mut _directories); 746 | } 747 | Err(err) => return Err(err), 748 | } 749 | } 750 | } 751 | } else { 752 | dir_size = path.as_ref().metadata()?.len(); 753 | files.push(item); 754 | } 755 | Ok(DirContent { 756 | dir_size, 757 | files, 758 | directories, 759 | }) 760 | } 761 | 762 | /// Returns the size of the file or directory in bytes.(!important: folders size not count) 763 | /// 764 | /// If used on a directory, this function will recursively iterate over every file and every 765 | /// directory inside the directory. This can be very time consuming if used on large directories. 766 | /// 767 | /// Does not follow symlinks. 768 | /// 769 | /// # Errors 770 | /// 771 | /// This function will return an error in the following situations, but is not limited to just 772 | /// these cases: 773 | /// 774 | /// * This `path` directory does not exist. 775 | /// * Invalid `path`. 776 | /// * The current process does not have the permission to access `path`. 777 | /// 778 | /// # Examples 779 | /// ```rust,ignore 780 | /// extern crate fs_extra; 781 | /// use fs_extra::dir::get_size; 782 | /// 783 | /// let folder_size = get_size("dir")?; 784 | /// println!("{}", folder_size); // print directory size in bytes 785 | /// ``` 786 | pub fn get_size

(path: P) -> Result 787 | where 788 | P: AsRef, 789 | { 790 | // Using `fs::symlink_metadata` since we don't want to follow symlinks, 791 | // as we're calculating the exact size of the requested path itself. 792 | let path_metadata = path.as_ref().symlink_metadata()?; 793 | 794 | let mut size_in_bytes = 0; 795 | 796 | if path_metadata.is_dir() { 797 | for entry in read_dir(&path)? { 798 | let entry = entry?; 799 | // `DirEntry::metadata` does not follow symlinks (unlike `fs::metadata`), so in the 800 | // case of symlinks, this is the size of the symlink itself, not its target. 801 | let entry_metadata = entry.metadata()?; 802 | 803 | if entry_metadata.is_dir() { 804 | // The size of the directory entry itself will be counted inside the `get_size()` call, 805 | // so we intentionally don't also add `entry_metadata.len()` to the total here. 806 | size_in_bytes += get_size(entry.path())?; 807 | } else { 808 | size_in_bytes += entry_metadata.len(); 809 | } 810 | } 811 | } else { 812 | size_in_bytes = path_metadata.len(); 813 | } 814 | 815 | Ok(size_in_bytes) 816 | } 817 | 818 | /// Copies the directory contents from one place to another using recursive method, 819 | /// with information about progress. This function will also copy the 820 | /// permission bits of the original files to destination files (not for directories). 821 | /// 822 | /// # Errors 823 | /// 824 | /// This function will return an error in the following situations, but is not limited to just 825 | /// these cases: 826 | /// 827 | /// * This `from` path is not a directory. 828 | /// * This `from` directory does not exist. 829 | /// * Invalid folder name for `from` or `to`. 830 | /// * The current process does not have the permission to access `from` or write `to`. 831 | /// 832 | /// # Example 833 | /// ```rust,ignore 834 | /// extern crate fs_extra; 835 | /// use fs_extra::dir::copy; 836 | /// 837 | /// let options = CopyOptions::new(); //Initialize default values for CopyOptions 838 | /// let handle = |process_info: TransitProcess| { 839 | /// println!("{}", process_info.total_bytes); 840 | /// fs_extra::dir::TransitProcessResult::ContinueOrAbort 841 | /// } 842 | /// // copy source/dir1 to target/dir1 843 | /// copy_with_progress("source/dir1", "target/dir1", &options, handle)?; 844 | /// 845 | /// ``` 846 | pub fn copy_with_progress( 847 | from: P, 848 | to: Q, 849 | options: &CopyOptions, 850 | mut progress_handler: F, 851 | ) -> Result 852 | where 853 | P: AsRef, 854 | Q: AsRef, 855 | F: FnMut(TransitProcess) -> TransitProcessResult, 856 | { 857 | let from = from.as_ref(); 858 | 859 | if !from.exists() { 860 | if let Some(msg) = from.to_str() { 861 | let msg = format!("Path \"{}\" does not exist or you don't have access!", msg); 862 | err!(&msg, ErrorKind::NotFound); 863 | } 864 | err!( 865 | "Path does not exist or you don't have access!", 866 | ErrorKind::NotFound 867 | ); 868 | } 869 | 870 | let mut to: PathBuf = to.as_ref().to_path_buf(); 871 | if !from.is_dir() { 872 | if let Some(msg) = from.to_str() { 873 | let msg = format!("Path \"{}\" is not a directory!", msg); 874 | err!(&msg, ErrorKind::InvalidFolder); 875 | } 876 | err!("Path is not a directory!", ErrorKind::InvalidFolder); 877 | } 878 | 879 | let dir_name; 880 | if let Some(val) = from.components().last() { 881 | dir_name = val.as_os_str(); 882 | } else { 883 | err!("Invalid folder from", ErrorKind::InvalidFolder); 884 | } 885 | if (to.exists() || !options.copy_inside) && !options.content_only { 886 | to.push(dir_name); 887 | } 888 | 889 | let mut read_options = DirOptions::new(); 890 | if options.depth > 0 { 891 | read_options.depth = options.depth; 892 | } 893 | 894 | let dir_content = get_dir_content2(from, &read_options)?; 895 | for directory in dir_content.directories { 896 | let tmp_to = Path::new(&directory).strip_prefix(from)?; 897 | let dir = to.join(&tmp_to); 898 | if !dir.exists() { 899 | if options.copy_inside { 900 | create_all(dir, false)?; 901 | } else { 902 | create(dir, false)?; 903 | } 904 | } 905 | } 906 | 907 | let mut result: u64 = 0; 908 | let mut info_process = TransitProcess { 909 | copied_bytes: 0, 910 | total_bytes: dir_content.dir_size, 911 | file_bytes_copied: 0, 912 | file_total_bytes: 0, 913 | file_name: String::new(), 914 | state: TransitState::Normal, 915 | }; 916 | 917 | let mut options = options.clone(); 918 | for file in dir_content.files { 919 | let mut to = to.to_path_buf(); 920 | let tp = Path::new(&file).strip_prefix(from)?; 921 | let path = to.join(&tp); 922 | 923 | let file_name = path.file_name(); 924 | if file_name.is_none() { 925 | err!("No file name"); 926 | } 927 | let file_name = file_name.unwrap(); 928 | to.push(file_name); 929 | 930 | let mut file_options = super::file::CopyOptions { 931 | overwrite: options.overwrite, 932 | skip_exist: options.skip_exist, 933 | buffer_size: options.buffer_size, 934 | }; 935 | 936 | if let Some(file_name) = file_name.to_str() { 937 | info_process.file_name = file_name.to_string(); 938 | } else { 939 | err!("Invalid file name", ErrorKind::InvalidFileName); 940 | } 941 | 942 | info_process.file_bytes_copied = 0; 943 | info_process.file_total_bytes = Path::new(&file).metadata()?.len(); 944 | 945 | let mut result_copy: Result; 946 | let mut work = true; 947 | let copied_bytes = result; 948 | while work { 949 | { 950 | let _progress_handler = |info: super::file::TransitProcess| { 951 | info_process.copied_bytes = copied_bytes + info.copied_bytes; 952 | info_process.file_bytes_copied = info.copied_bytes; 953 | progress_handler(info_process.clone()); 954 | }; 955 | 956 | result_copy = 957 | super::file::copy_with_progress(&file, &path, &file_options, _progress_handler); 958 | } 959 | match result_copy { 960 | Ok(val) => { 961 | result += val; 962 | work = false; 963 | } 964 | Err(err) => match err.kind { 965 | ErrorKind::AlreadyExists => { 966 | let mut info_process = info_process.clone(); 967 | info_process.state = TransitState::Exists; 968 | let user_decide = progress_handler(info_process); 969 | match user_decide { 970 | TransitProcessResult::Overwrite => { 971 | file_options.overwrite = true; 972 | } 973 | TransitProcessResult::OverwriteAll => { 974 | file_options.overwrite = true; 975 | options.overwrite = true; 976 | } 977 | TransitProcessResult::Skip => { 978 | file_options.skip_exist = true; 979 | } 980 | TransitProcessResult::SkipAll => { 981 | file_options.skip_exist = true; 982 | options.skip_exist = true; 983 | } 984 | TransitProcessResult::Retry => {} 985 | TransitProcessResult::ContinueOrAbort => { 986 | let err_msg = err.to_string(); 987 | err!(err_msg.as_str(), err.kind) 988 | } 989 | TransitProcessResult::Abort => { 990 | let err_msg = err.to_string(); 991 | err!(err_msg.as_str(), err.kind) 992 | } 993 | } 994 | } 995 | ErrorKind::PermissionDenied => { 996 | let mut info_process = info_process.clone(); 997 | info_process.state = TransitState::Exists; 998 | let user_decide = progress_handler(info_process); 999 | match user_decide { 1000 | TransitProcessResult::Overwrite => { 1001 | err!("Overwrite denied for this situation!", ErrorKind::Other); 1002 | } 1003 | TransitProcessResult::OverwriteAll => { 1004 | err!("Overwrite denied for this situation!", ErrorKind::Other); 1005 | } 1006 | TransitProcessResult::Skip => { 1007 | file_options.skip_exist = true; 1008 | } 1009 | TransitProcessResult::SkipAll => { 1010 | file_options.skip_exist = true; 1011 | options.skip_exist = true; 1012 | } 1013 | TransitProcessResult::Retry => {} 1014 | TransitProcessResult::ContinueOrAbort => { 1015 | let err_msg = err.to_string(); 1016 | err!(err_msg.as_str(), err.kind) 1017 | } 1018 | TransitProcessResult::Abort => { 1019 | let err_msg = err.to_string(); 1020 | err!(err_msg.as_str(), err.kind) 1021 | } 1022 | } 1023 | } 1024 | _ => { 1025 | let err_msg = err.to_string(); 1026 | err!(err_msg.as_str(), err.kind) 1027 | } 1028 | }, 1029 | } 1030 | } 1031 | } 1032 | 1033 | Ok(result) 1034 | } 1035 | 1036 | /// Moves the directory contents from one place to another. 1037 | /// This function will also copy the permission bits of the original files to 1038 | /// destination files (not for directories). 1039 | /// 1040 | /// # Errors 1041 | /// 1042 | /// This function will return an error in the following situations, but is not limited to just 1043 | /// these cases: 1044 | /// 1045 | /// * This `from` path is not a directory. 1046 | /// * This `from` directory does not exist. 1047 | /// * Invalid folder name for `from` or `to`. 1048 | /// * The current process does not have the permission to access `from` or write `to`. 1049 | /// 1050 | /// # Example 1051 | /// ```rust,ignore 1052 | /// extern crate fs_extra; 1053 | /// use fs_extra::dir::move_dir; 1054 | /// 1055 | /// let options = CopyOptions::new(); //Initialize default values for CopyOptions 1056 | /// 1057 | /// // move source/dir1 to target/dir1 1058 | /// move_dir("source/dir1", "target/dir1", &options)?; 1059 | /// 1060 | /// ``` 1061 | pub fn move_dir(from: P, to: Q, options: &CopyOptions) -> Result 1062 | where 1063 | P: AsRef, 1064 | Q: AsRef, 1065 | { 1066 | let mut is_remove = true; 1067 | if options.skip_exist && to.as_ref().exists() && !options.overwrite { 1068 | is_remove = false; 1069 | } 1070 | let from = from.as_ref(); 1071 | 1072 | if !from.exists() { 1073 | if let Some(msg) = from.to_str() { 1074 | let msg = format!("Path \"{}\" does not exist", msg); 1075 | err!(&msg, ErrorKind::NotFound); 1076 | } 1077 | err!( 1078 | "Path does not exist or you don't have access!", 1079 | ErrorKind::NotFound 1080 | ); 1081 | } 1082 | 1083 | let mut to: PathBuf = to.as_ref().to_path_buf(); 1084 | if !from.is_dir() { 1085 | if let Some(msg) = from.to_str() { 1086 | let msg = format!( 1087 | "Path \"{}\" is not a directory or you don't have access!", 1088 | msg 1089 | ); 1090 | err!(&msg, ErrorKind::InvalidFolder); 1091 | } 1092 | err!( 1093 | "Path is not a directory or you don't have access!", 1094 | ErrorKind::InvalidFolder 1095 | ); 1096 | } 1097 | let dir_name; 1098 | if let Some(val) = from.components().last() { 1099 | dir_name = val.as_os_str(); 1100 | } else { 1101 | err!("Invalid folder from", ErrorKind::InvalidFolder); 1102 | } 1103 | 1104 | if (to.exists() || !options.copy_inside) && !options.content_only { 1105 | to.push(dir_name); 1106 | } 1107 | let dir_content = get_dir_content(from)?; 1108 | for directory in dir_content.directories { 1109 | let tmp_to = Path::new(&directory).strip_prefix(from)?; 1110 | let dir = to.join(&tmp_to); 1111 | if !dir.exists() { 1112 | if options.copy_inside { 1113 | create_all(dir, false)?; 1114 | } else { 1115 | create(dir, false)?; 1116 | } 1117 | } 1118 | } 1119 | let mut result: u64 = 0; 1120 | for file in dir_content.files { 1121 | let to = to.to_path_buf(); 1122 | let tp = Path::new(&file).strip_prefix(from)?; 1123 | let path = to.join(&tp); 1124 | 1125 | let file_options = super::file::CopyOptions { 1126 | overwrite: options.overwrite, 1127 | skip_exist: options.skip_exist, 1128 | buffer_size: options.buffer_size, 1129 | }; 1130 | 1131 | let mut result_copy: Result; 1132 | let mut work = true; 1133 | while work { 1134 | { 1135 | result_copy = super::file::move_file(&file, &path, &file_options); 1136 | match result_copy { 1137 | Ok(val) => { 1138 | result += val; 1139 | work = false; 1140 | } 1141 | Err(err) => { 1142 | let err_msg = err.to_string(); 1143 | err!(err_msg.as_str(), err.kind) 1144 | } 1145 | } 1146 | } 1147 | } 1148 | } 1149 | if is_remove { 1150 | remove(from)?; 1151 | } 1152 | 1153 | Ok(result) 1154 | } 1155 | 1156 | /// Moves the directory contents from one place to another with information about progress. 1157 | /// This function will also copy the permission bits of the original files to 1158 | /// destination files (not for directories). 1159 | /// 1160 | /// # Errors 1161 | /// 1162 | /// This function will return an error in the following situations, but is not limited to just 1163 | /// these cases: 1164 | /// 1165 | /// * This `from` path is not a directory. 1166 | /// * This `from` directory does not exist. 1167 | /// * Invalid folder name for `from` or `to`. 1168 | /// * The current process does not have the permission to access `from` or write `to`. 1169 | /// 1170 | /// # Example 1171 | /// ```rust,ignore 1172 | /// extern crate fs_extra; 1173 | /// use fs_extra::dir::move_dir_with_progress; 1174 | /// 1175 | /// let options = CopyOptions::new(); //Initialize default values for CopyOptions 1176 | /// let handle = |process_info: TransitProcess| { 1177 | /// println!("{}", process_info.total_bytes); 1178 | /// fs_extra::dir::TransitProcessResult::ContinueOrAbort 1179 | /// } 1180 | /// 1181 | /// // move source/dir1 to target/dir1 1182 | /// move_dir_with_progress("source/dir1", "target/dir1", &options, handle)?; 1183 | /// 1184 | /// ``` 1185 | pub fn move_dir_with_progress( 1186 | from: P, 1187 | to: Q, 1188 | options: &CopyOptions, 1189 | mut progress_handler: F, 1190 | ) -> Result 1191 | where 1192 | P: AsRef, 1193 | Q: AsRef, 1194 | F: FnMut(TransitProcess) -> TransitProcessResult, 1195 | { 1196 | let mut is_remove = true; 1197 | if options.skip_exist && to.as_ref().exists() && !options.overwrite { 1198 | is_remove = false; 1199 | } 1200 | let from = from.as_ref(); 1201 | 1202 | if !from.exists() { 1203 | if let Some(msg) = from.to_str() { 1204 | let msg = format!("Path \"{}\" does not exist or you don't have access!", msg); 1205 | err!(&msg, ErrorKind::NotFound); 1206 | } 1207 | err!( 1208 | "Path does not exist or you don't have access!", 1209 | ErrorKind::NotFound 1210 | ); 1211 | } 1212 | 1213 | let mut to: PathBuf = to.as_ref().to_path_buf(); 1214 | if !from.is_dir() { 1215 | if let Some(msg) = from.to_str() { 1216 | let msg = format!("Path \"{}\" is not a directory!", msg); 1217 | err!(&msg, ErrorKind::InvalidFolder); 1218 | } 1219 | err!("Path is not a directory!", ErrorKind::InvalidFolder); 1220 | } 1221 | let dir_name; 1222 | if let Some(val) = from.components().last() { 1223 | dir_name = val.as_os_str(); 1224 | } else { 1225 | err!("Invalid folder from", ErrorKind::InvalidFolder); 1226 | } 1227 | if !(options.content_only || options.copy_inside && !to.exists()) { 1228 | to.push(dir_name); 1229 | } 1230 | 1231 | let dir_content = get_dir_content(from)?; 1232 | for directory in dir_content.directories { 1233 | let tmp_to = Path::new(&directory).strip_prefix(from)?; 1234 | let dir = to.join(&tmp_to); 1235 | if !dir.exists() { 1236 | if options.copy_inside { 1237 | create_all(dir, false)?; 1238 | } else { 1239 | create(dir, false)?; 1240 | } 1241 | } 1242 | } 1243 | 1244 | let mut result: u64 = 0; 1245 | let mut info_process = TransitProcess { 1246 | copied_bytes: 0, 1247 | total_bytes: dir_content.dir_size, 1248 | file_bytes_copied: 0, 1249 | file_total_bytes: 0, 1250 | file_name: String::new(), 1251 | state: TransitState::Normal, 1252 | }; 1253 | 1254 | let mut options = options.clone(); 1255 | for file in dir_content.files { 1256 | let mut to = to.to_path_buf(); 1257 | let tp = Path::new(&file).strip_prefix(from)?; 1258 | let path = to.join(&tp); 1259 | 1260 | let file_name = path.file_name(); 1261 | if file_name.is_none() { 1262 | err!("No file name"); 1263 | } 1264 | let file_name = file_name.unwrap(); 1265 | to.push(file_name); 1266 | 1267 | let mut file_options = super::file::CopyOptions { 1268 | overwrite: options.overwrite, 1269 | skip_exist: options.skip_exist, 1270 | buffer_size: options.buffer_size, 1271 | }; 1272 | 1273 | if let Some(file_name) = file_name.to_str() { 1274 | info_process.file_name = file_name.to_string(); 1275 | } else { 1276 | err!("Invalid file name", ErrorKind::InvalidFileName); 1277 | } 1278 | 1279 | info_process.file_bytes_copied = 0; 1280 | info_process.file_total_bytes = Path::new(&file).metadata()?.len(); 1281 | 1282 | let mut result_copy: Result; 1283 | let mut work = true; 1284 | let copied_bytes = result; 1285 | while work { 1286 | { 1287 | let _progress_handler = |info: super::file::TransitProcess| { 1288 | info_process.copied_bytes = copied_bytes + info.copied_bytes; 1289 | info_process.file_bytes_copied = info.copied_bytes; 1290 | progress_handler(info_process.clone()); 1291 | }; 1292 | 1293 | result_copy = super::file::move_file_with_progress( 1294 | &file, 1295 | &path, 1296 | &file_options, 1297 | _progress_handler, 1298 | ); 1299 | } 1300 | match result_copy { 1301 | Ok(val) => { 1302 | result += val; 1303 | work = false; 1304 | } 1305 | Err(err) => match err.kind { 1306 | ErrorKind::AlreadyExists => { 1307 | let mut info_process = info_process.clone(); 1308 | info_process.state = TransitState::Exists; 1309 | let user_decide = progress_handler(info_process); 1310 | match user_decide { 1311 | TransitProcessResult::Overwrite => { 1312 | file_options.overwrite = true; 1313 | } 1314 | TransitProcessResult::OverwriteAll => { 1315 | file_options.overwrite = true; 1316 | options.overwrite = true; 1317 | } 1318 | TransitProcessResult::Skip => { 1319 | is_remove = false; 1320 | file_options.skip_exist = true; 1321 | } 1322 | TransitProcessResult::SkipAll => { 1323 | is_remove = false; 1324 | file_options.skip_exist = true; 1325 | options.skip_exist = true; 1326 | } 1327 | TransitProcessResult::Retry => {} 1328 | TransitProcessResult::ContinueOrAbort => { 1329 | let err_msg = err.to_string(); 1330 | err!(err_msg.as_str(), err.kind) 1331 | } 1332 | TransitProcessResult::Abort => { 1333 | let err_msg = err.to_string(); 1334 | err!(err_msg.as_str(), err.kind) 1335 | } 1336 | } 1337 | } 1338 | ErrorKind::PermissionDenied => { 1339 | let mut info_process = info_process.clone(); 1340 | info_process.state = TransitState::Exists; 1341 | let user_decide = progress_handler(info_process); 1342 | match user_decide { 1343 | TransitProcessResult::Overwrite => { 1344 | err!("Overwrite denied for this situation!", ErrorKind::Other); 1345 | } 1346 | TransitProcessResult::OverwriteAll => { 1347 | err!("Overwrite denied for this situation!", ErrorKind::Other); 1348 | } 1349 | TransitProcessResult::Skip => { 1350 | is_remove = false; 1351 | file_options.skip_exist = true; 1352 | } 1353 | TransitProcessResult::SkipAll => { 1354 | file_options.skip_exist = true; 1355 | options.skip_exist = true; 1356 | } 1357 | TransitProcessResult::Retry => {} 1358 | TransitProcessResult::ContinueOrAbort => { 1359 | let err_msg = err.to_string(); 1360 | err!(err_msg.as_str(), err.kind) 1361 | } 1362 | TransitProcessResult::Abort => { 1363 | let err_msg = err.to_string(); 1364 | err!(err_msg.as_str(), err.kind) 1365 | } 1366 | } 1367 | } 1368 | _ => { 1369 | let err_msg = err.to_string(); 1370 | err!(err_msg.as_str(), err.kind) 1371 | } 1372 | }, 1373 | } 1374 | } 1375 | } 1376 | if is_remove { 1377 | remove(from)?; 1378 | } 1379 | 1380 | Ok(result) 1381 | } 1382 | 1383 | /// Removes directory. 1384 | /// 1385 | /// # Example 1386 | /// ```rust,ignore 1387 | /// extern crate fs_extra; 1388 | /// use fs_extra::dir::remove; 1389 | /// 1390 | /// remove("source/dir1"); // remove dir1 1391 | /// ``` 1392 | pub fn remove>(path: P) -> Result<()> { 1393 | if path.as_ref().exists() { 1394 | Ok(remove_dir_all(path)?) 1395 | } else { 1396 | Ok(()) 1397 | } 1398 | } 1399 | -------------------------------------------------------------------------------- /src/error.rs: -------------------------------------------------------------------------------- 1 | use std::error::Error as StdError; 2 | use std::ffi::OsString; 3 | use std::fmt; 4 | use std::io::Error as IoError; 5 | use std::io::ErrorKind as IoErrorKind; 6 | use std::path::StripPrefixError; 7 | 8 | /// A list specifying general categories of fs_extra error. 9 | #[derive(Debug)] 10 | pub enum ErrorKind { 11 | /// An entity was not found. 12 | NotFound, 13 | /// The operation lacked the necessary privileges to complete. 14 | PermissionDenied, 15 | /// An entity already exists. 16 | AlreadyExists, 17 | /// This operation was interrupted. 18 | Interrupted, 19 | /// Path does not a directory. 20 | InvalidFolder, 21 | /// Path does not a file. 22 | InvalidFile, 23 | /// Invalid file name. 24 | InvalidFileName, 25 | /// Invalid path. 26 | InvalidPath, 27 | /// Any I/O error. 28 | Io(IoError), 29 | /// Any StripPrefix error. 30 | StripPrefix(StripPrefixError), 31 | /// Any OsString error. 32 | OsString(OsString), 33 | /// Any fs_extra error not part of this list. 34 | Other, 35 | } 36 | 37 | impl ErrorKind { 38 | fn as_str(&self) -> &str { 39 | match *self { 40 | ErrorKind::NotFound => "entity not found", 41 | ErrorKind::PermissionDenied => "permission denied", 42 | ErrorKind::AlreadyExists => "entity already exists", 43 | ErrorKind::Interrupted => "operation interrupted", 44 | ErrorKind::Other => "other os error", 45 | ErrorKind::InvalidFolder => "invalid folder error", 46 | ErrorKind::InvalidFile => "invalid file error", 47 | ErrorKind::InvalidFileName => "invalid file name error", 48 | ErrorKind::InvalidPath => "invalid path error", 49 | ErrorKind::Io(_) => "Io error", 50 | ErrorKind::StripPrefix(_) => "Strip prefix error", 51 | ErrorKind::OsString(_) => "OsString error", 52 | } 53 | } 54 | } 55 | 56 | /// A specialized Result type for fs_extra operations. 57 | /// 58 | /// This typedef is generally used to avoid writing out fs_extra::Error directly 59 | /// and is otherwise a direct mapping to Result. 60 | /// 61 | ///#Examples 62 | /// 63 | /// ```rust,ignore 64 | /// extern crate fs_extra; 65 | /// use fs_extra::dir::create; 66 | /// 67 | ///fn get_string() -> io::Result<()> { 68 | /// 69 | /// create("test_dir")?; 70 | /// 71 | /// Ok(()) 72 | /// } 73 | /// ``` 74 | pub type Result = ::std::result::Result; 75 | 76 | /// The error type for fs_extra operations with files and folder. 77 | /// 78 | /// Errors mostly originate from the underlying OS, but custom instances of 79 | /// `Error` can be created with crafted error messages and a particular value of 80 | /// [`ErrorKind`]. 81 | /// 82 | /// [`ErrorKind`]: enum.ErrorKind.html 83 | #[derive(Debug)] 84 | pub struct Error { 85 | /// Type error 86 | pub kind: ErrorKind, 87 | message: String, 88 | } 89 | 90 | impl Error { 91 | /// Create a new fs_extra error from a kind of error error as well as an arbitrary error payload. 92 | /// 93 | ///#Examples 94 | /// ```rust,ignore 95 | /// 96 | /// extern crate fs_extra; 97 | /// use fs_extra::error::{Error, ErrorKind}; 98 | /// 99 | /// errors can be created from strings 100 | /// let custom_error = Error::new(ErrorKind::Other, "Other Error!"); 101 | /// // errors can also be created from other errors 102 | /// let custom_error2 = Error::new(ErrorKind::Interrupted, custom_error); 103 | /// 104 | /// ``` 105 | pub fn new(kind: ErrorKind, message: &str) -> Error { 106 | Error { 107 | kind, 108 | message: message.to_string(), 109 | } 110 | } 111 | } 112 | 113 | impl fmt::Display for Error { 114 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 115 | write!(f, "{}", self.message) 116 | } 117 | } 118 | 119 | impl StdError for Error { 120 | fn description(&self) -> &str { 121 | self.kind.as_str() 122 | } 123 | } 124 | impl From for Error { 125 | fn from(err: StripPrefixError) -> Error { 126 | Error::new( 127 | ErrorKind::StripPrefix(err), 128 | "StripPrefixError. Look inside for more details", 129 | ) 130 | } 131 | } 132 | 133 | impl From for Error { 134 | fn from(err: OsString) -> Error { 135 | Error::new( 136 | ErrorKind::OsString(err), 137 | "OsString. Look inside for more details", 138 | ) 139 | } 140 | } 141 | 142 | impl From for Error { 143 | fn from(err: IoError) -> Error { 144 | let err_kind: ErrorKind; 145 | match err.kind() { 146 | IoErrorKind::NotFound => err_kind = ErrorKind::NotFound, 147 | IoErrorKind::PermissionDenied => err_kind = ErrorKind::PermissionDenied, 148 | IoErrorKind::AlreadyExists => err_kind = ErrorKind::AlreadyExists, 149 | IoErrorKind::Interrupted => err_kind = ErrorKind::Interrupted, 150 | IoErrorKind::Other => err_kind = ErrorKind::Other, 151 | _ => { 152 | err_kind = ErrorKind::Io(err); 153 | return Error::new(err_kind, "Io error. Look inside err_kind for more details."); 154 | } 155 | } 156 | Error::new(err_kind, &err.to_string()) 157 | } 158 | } 159 | -------------------------------------------------------------------------------- /src/file.rs: -------------------------------------------------------------------------------- 1 | use crate::error::{Error, ErrorKind, Result}; 2 | use std; 3 | use std::fs::{remove_file, File}; 4 | use std::io::{Read, Write}; 5 | use std::path::Path; 6 | 7 | // Options and flags which can be used to configure how a file will be copied or moved. 8 | pub struct CopyOptions { 9 | /// Sets the option true for overwrite existing files. 10 | pub overwrite: bool, 11 | /// Sets the option true for skip existing files. 12 | pub skip_exist: bool, 13 | /// Sets buffer size for copy/move work only with receipt information about process work. 14 | pub buffer_size: usize, 15 | } 16 | 17 | impl CopyOptions { 18 | /// Initialize struct CopyOptions with default value. 19 | /// 20 | /// ```rust,ignore 21 | /// 22 | /// overwrite: false 23 | /// 24 | /// skip_exist: false 25 | /// 26 | /// buffer_size: 64000 //64kb 27 | /// ``` 28 | pub fn new() -> CopyOptions { 29 | CopyOptions { 30 | overwrite: false, 31 | skip_exist: false, 32 | buffer_size: 64000, //64kb 33 | } 34 | } 35 | 36 | /// Sets the option true for overwrite existing files. 37 | pub fn overwrite(mut self, overwrite: bool) -> Self { 38 | self.overwrite = overwrite; 39 | self 40 | } 41 | 42 | /// Sets the option true for skip existing files. 43 | pub fn skip_exist(mut self, skip_exist: bool) -> Self { 44 | self.skip_exist = skip_exist; 45 | self 46 | } 47 | 48 | /// Sets buffer size for copy/move work only with receipt information about process work. 49 | pub fn buffer_size(mut self, buffer_size: usize) -> Self { 50 | self.buffer_size = buffer_size; 51 | self 52 | } 53 | } 54 | 55 | impl Default for CopyOptions { 56 | fn default() -> Self { 57 | CopyOptions::new() 58 | } 59 | } 60 | 61 | /// A structure which stores information about the current status of a file that's copied or moved. . 62 | pub struct TransitProcess { 63 | /// Copied bytes on this time. 64 | pub copied_bytes: u64, 65 | /// All the bytes which should to copy or move. 66 | pub total_bytes: u64, 67 | } 68 | 69 | /// Copies the contents of one file to another. This function will also copy the permission 70 | /// bits of the original file to the destination file. 71 | /// 72 | /// # Errors 73 | /// 74 | /// This function will return an error in the following situations, but is not limited to just 75 | /// these cases: 76 | /// 77 | /// * This `from` path is not a file. 78 | /// * This `from` file does not exist. 79 | /// * The current process does not have the permission to access `from` or write `to`. 80 | /// 81 | /// # Example 82 | /// 83 | /// ```rust,ignore 84 | /// extern crate fs_extra; 85 | /// use fs_extra::file::copy; 86 | /// 87 | /// let options = CopyOptions::new(); //Initialize default values for CopyOptions 88 | /// copy("dir1/foo.txt", "dir2/bar.txt", &options)?; // Copy dir1/foo.txt to dir2/bar.txt 89 | /// 90 | /// ``` 91 | pub fn copy(from: P, to: Q, options: &CopyOptions) -> Result 92 | where 93 | P: AsRef, 94 | Q: AsRef, 95 | { 96 | let from = from.as_ref(); 97 | if !from.exists() { 98 | if let Some(msg) = from.to_str() { 99 | let msg = format!("Path \"{}\" does not exist or you don't have access!", msg); 100 | err!(&msg, ErrorKind::NotFound); 101 | } 102 | err!( 103 | "Path does not exist or you don't have access!", 104 | ErrorKind::NotFound 105 | ); 106 | } 107 | 108 | if !from.is_file() { 109 | if let Some(msg) = from.to_str() { 110 | let msg = format!("Path \"{}\" is not a file!", msg); 111 | err!(&msg, ErrorKind::InvalidFile); 112 | } 113 | err!("Path is not a file!", ErrorKind::InvalidFile); 114 | } 115 | 116 | if !options.overwrite && to.as_ref().exists() { 117 | if options.skip_exist { 118 | return Ok(0); 119 | } 120 | 121 | if let Some(msg) = to.as_ref().to_str() { 122 | let msg = format!("Path \"{}\" exists", msg); 123 | err!(&msg, ErrorKind::AlreadyExists); 124 | } 125 | } 126 | 127 | Ok(std::fs::copy(from, to)?) 128 | } 129 | 130 | /// Copies the contents of one file to another file with information about progress. 131 | /// This function will also copy the permission bits of the original file to the 132 | /// destination file. 133 | /// 134 | /// # Errors 135 | /// 136 | /// This function will return an error in the following situations, but is not limited to just 137 | /// these cases: 138 | /// 139 | /// * This `from` path is not a file. 140 | /// * This `from` file does not exist. 141 | /// * The current process does not have the permission to access `from` or write `to`. 142 | /// 143 | /// # Example 144 | /// ```rust,ignore 145 | /// extern crate fs_extra; 146 | /// use fs_extra::file::copy_with_progress; 147 | /// 148 | /// let options = CopyOptions::new(); //Initialize default values for CopyOptions 149 | /// let handle = |process_info: TransitProcess| println!("{}", process_info.total_bytes); 150 | /// 151 | /// // Copy dir1/foo.txt to dir2/foo.txt 152 | /// copy_with_progress("dir1/foo.txt", "dir2/foo.txt", &options, handle)?; 153 | /// 154 | /// ``` 155 | pub fn copy_with_progress( 156 | from: P, 157 | to: Q, 158 | options: &CopyOptions, 159 | mut progress_handler: F, 160 | ) -> Result 161 | where 162 | P: AsRef, 163 | Q: AsRef, 164 | F: FnMut(TransitProcess), 165 | { 166 | let from = from.as_ref(); 167 | if !from.exists() { 168 | if let Some(msg) = from.to_str() { 169 | let msg = format!("Path \"{}\" does not exist or you don't have access!", msg); 170 | err!(&msg, ErrorKind::NotFound); 171 | } 172 | err!( 173 | "Path does not exist or you don't have access!", 174 | ErrorKind::NotFound 175 | ); 176 | } 177 | 178 | if !from.is_file() { 179 | if let Some(msg) = from.to_str() { 180 | let msg = format!("Path \"{}\" is not a file!", msg); 181 | err!(&msg, ErrorKind::InvalidFile); 182 | } 183 | err!("Path is not a file!", ErrorKind::InvalidFile); 184 | } 185 | 186 | if !options.overwrite && to.as_ref().exists() { 187 | if options.skip_exist { 188 | return Ok(0); 189 | } 190 | 191 | if let Some(msg) = to.as_ref().to_str() { 192 | let msg = format!("Path \"{}\" exists", msg); 193 | err!(&msg, ErrorKind::AlreadyExists); 194 | } 195 | } 196 | let mut file_from = File::open(from)?; 197 | let mut buf = vec![0; options.buffer_size]; 198 | let file_size = file_from.metadata()?.len(); 199 | let mut copied_bytes: u64 = 0; 200 | 201 | let mut file_to = File::create(to)?; 202 | while !buf.is_empty() { 203 | match file_from.read(&mut buf) { 204 | Ok(0) => break, 205 | Ok(n) => { 206 | let written_bytes = file_to.write(&buf[..n])?; 207 | if written_bytes != n { 208 | err!("Couldn't write the whole buffer to file", ErrorKind::Other); 209 | } 210 | copied_bytes += n as u64; 211 | let data = TransitProcess { 212 | copied_bytes, 213 | total_bytes: file_size, 214 | }; 215 | progress_handler(data); 216 | } 217 | Err(ref e) if e.kind() == ::std::io::ErrorKind::Interrupted => {} 218 | Err(e) => return Err(::std::convert::From::from(e)), 219 | } 220 | } 221 | Ok(file_size) 222 | } 223 | 224 | /// Moves a file from one place to another. This function will also copy the permission 225 | /// bits of the original file to the destination file. 226 | /// 227 | /// # Errors 228 | /// 229 | /// This function will return an error in the following situations, but is not limited to just 230 | /// these cases: 231 | /// 232 | /// * This `from` path is not a file. 233 | /// * This `from` file does not exist. 234 | /// * The current process does not have the permission to access `from` or write `to`. 235 | /// 236 | /// # Example 237 | /// ```rust,ignore 238 | /// extern crate fs_extra; 239 | /// use fs_extra::file::move_file; 240 | /// 241 | /// let options = CopyOptions::new(); //Initialize default values for CopyOptions 242 | /// move_file("dir1/foo.txt", "dir2/foo.txt", &options)?; // Move dir1/foo.txt to dir2/foo.txt 243 | /// 244 | /// ``` 245 | pub fn move_file(from: P, to: Q, options: &CopyOptions) -> Result 246 | where 247 | P: AsRef, 248 | Q: AsRef, 249 | { 250 | let mut is_remove = true; 251 | if options.skip_exist && to.as_ref().exists() && !options.overwrite { 252 | is_remove = false; 253 | } 254 | let result = copy(&from, to, options)?; 255 | if is_remove { 256 | remove(from)?; 257 | } 258 | 259 | Ok(result) 260 | } 261 | 262 | /// Moves a file from one place to another with information about progress. 263 | /// This function will also copy the permission bits of the original file to the 264 | /// destination file. 265 | /// 266 | /// # Errors 267 | /// 268 | /// This function will return an error in the following situations, but is not limited to just 269 | /// these cases: 270 | /// 271 | /// * This `from` path is not a file. 272 | /// * This `from` file does not exist. 273 | /// * The current process does not have the permission to access `from` or write `to`. 274 | /// 275 | /// # Example 276 | /// ```rust,ignore 277 | /// extern crate fs_extra; 278 | /// use fs_extra::file::move_file; 279 | /// 280 | /// let options = CopyOptions::new(); //Initialize default values for CopyOptions 281 | /// let handle = |process_info: TransitProcess| println!("{}", process_info.total_bytes); 282 | /// 283 | /// // Move dir1/foo.txt to dir2/foo.txt 284 | /// move_file("dir1/foo.txt", "dir2/foo.txt", &options, handle)?; 285 | /// 286 | /// ``` 287 | pub fn move_file_with_progress( 288 | from: P, 289 | to: Q, 290 | options: &CopyOptions, 291 | progress_handler: F, 292 | ) -> Result 293 | where 294 | P: AsRef, 295 | Q: AsRef, 296 | F: FnMut(TransitProcess), 297 | { 298 | let mut is_remove = true; 299 | if options.skip_exist && to.as_ref().exists() && !options.overwrite { 300 | is_remove = false; 301 | } 302 | let result = copy_with_progress(&from, to, options, progress_handler)?; 303 | if is_remove { 304 | remove(from)?; 305 | } 306 | 307 | Ok(result) 308 | } 309 | 310 | /// Removes a file from the filesystem. 311 | /// 312 | /// # Errors 313 | /// 314 | /// This function will return an error in the following situations, but is not limited to just 315 | /// these cases: 316 | /// 317 | /// * The current process does not have the permission to access `path`. 318 | /// 319 | /// # Example 320 | /// ```rust,ignore 321 | /// extern crate fs_extra; 322 | /// use fs_extra::file::remove; 323 | /// 324 | /// remove("foo.txt" )?; // Remove foo.txt 325 | /// 326 | /// ``` 327 | pub fn remove

(path: P) -> Result<()> 328 | where 329 | P: AsRef, 330 | { 331 | if path.as_ref().exists() { 332 | Ok(remove_file(path)?) 333 | } else { 334 | Ok(()) 335 | } 336 | } 337 | 338 | /// Read file contents, placing them into `String`. 339 | /// 340 | /// # Errors 341 | /// 342 | /// This function will return an error in the following situations, but is not limited to just 343 | /// these cases: 344 | /// 345 | /// * This `path` is not a file. 346 | /// * This `path` file does not exist. 347 | /// * The current process does not have the permission to access `path`. 348 | /// 349 | /// # Example 350 | /// ```rust,ignore 351 | /// extern crate fs_extra; 352 | /// use fs_extra::file::read_to_string; 353 | /// 354 | /// let file_content = read_to_string("foo.txt" )?; // Get file content from foo.txt 355 | /// println!("{}", file_content); 356 | /// 357 | /// ``` 358 | pub fn read_to_string

(path: P) -> Result 359 | where 360 | P: AsRef, 361 | { 362 | let path = path.as_ref(); 363 | if path.exists() && !path.is_file() { 364 | if let Some(msg) = path.to_str() { 365 | let msg = format!("Path \"{}\" is not a file!", msg); 366 | err!(&msg, ErrorKind::InvalidFile); 367 | } 368 | err!("Path is not a file!", ErrorKind::InvalidFile); 369 | } 370 | 371 | let mut file = File::open(path)?; 372 | let mut result = String::new(); 373 | file.read_to_string(&mut result)?; 374 | 375 | Ok(result) 376 | } 377 | 378 | /// Write `String` content into file. 379 | /// 380 | /// # Errors 381 | /// 382 | /// This function will return an error in the following situations, but is not limited to just 383 | /// these cases: 384 | /// 385 | /// * This `path` is not a file. 386 | /// * This `path` file does not exist. 387 | /// * The current process does not have the permission to access `path`. 388 | /// 389 | /// # Example 390 | /// ```rust,ignore 391 | /// extern crate fs_extra; 392 | /// use fs_extra::file::read_to_string; 393 | /// 394 | /// write_all("foo.txt", "contents" )?; // Create file foo.txt and send content inside 395 | /// 396 | /// ``` 397 | pub fn write_all

(path: P, content: &str) -> Result<()> 398 | where 399 | P: AsRef, 400 | { 401 | let path = path.as_ref(); 402 | if path.exists() && !path.is_file() { 403 | if let Some(msg) = path.to_str() { 404 | let msg = format!("Path \"{}\" is not a file!", msg); 405 | err!(&msg, ErrorKind::InvalidFile); 406 | } 407 | err!("Path is not a file!", ErrorKind::InvalidFile); 408 | } 409 | 410 | let mut f = File::create(path)?; 411 | 412 | Ok(f.write_all(content.as_bytes())?) 413 | } 414 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | macro_rules! err { 2 | ($text:expr, $kind:expr) => { 3 | return Err(Error::new($kind, $text)) 4 | }; 5 | 6 | ($text:expr) => { 7 | err!($text, ErrorKind::Other) 8 | }; 9 | } 10 | 11 | /// The error type for fs_extra operations on files and directories. 12 | pub mod error; 13 | /// This module includes additional methods for working with files. 14 | /// 15 | /// One of the distinguishing features is receipt information 16 | /// about process work with files. 17 | /// 18 | /// # Example 19 | /// ```rust,ignore 20 | /// use std::path::Path; 21 | /// use std::{thread, time}; 22 | /// use std::sync::mpsc::{self, TryRecvError}; 23 | /// 24 | /// extern crate fs_extra; 25 | /// use fs_extra::file::*; 26 | /// use fs_extra::error::*; 27 | /// 28 | /// fn example_copy() -> Result<()> { 29 | /// let path_from = Path::new("./temp"); 30 | /// let path_to = path_from.join("out"); 31 | /// let test_file = (path_from.join("test_file.txt"), path_to.join("test_file.txt")); 32 | /// 33 | /// 34 | /// fs_extra::dir::create_all(&path_from, true)?; 35 | /// fs_extra::dir::create_all(&path_to, true)?; 36 | /// 37 | /// write_all(&test_file.0, "test_data")?; 38 | /// assert!(test_file.0.exists()); 39 | /// assert!(!test_file.1.exists()); 40 | /// 41 | /// 42 | /// let options = CopyOptions { 43 | /// buffer_size: 1, 44 | /// ..Default::default() 45 | /// } 46 | /// let (tx, rx) = mpsc::channel(); 47 | /// thread::spawn(move || { 48 | /// let handler = |process_info: TransitProcess| { 49 | /// tx.send(process_info).unwrap(); 50 | /// thread::sleep(time::Duration::from_millis(500)); 51 | /// }; 52 | /// copy_with_progress(&test_file.0, &test_file.1, &options, handler).unwrap(); 53 | /// assert!(test_file.0.exists()); 54 | /// assert!(test_file.1.exists()); 55 | /// 56 | /// }); 57 | /// loop { 58 | /// match rx.try_recv() { 59 | /// Ok(process_info) => { 60 | /// println!("{} of {} bytes", 61 | /// process_info.copied_bytes, 62 | /// process_info.total_bytes); 63 | /// } 64 | /// Err(TryRecvError::Disconnected) => { 65 | /// println!("finished"); 66 | /// break; 67 | /// } 68 | /// Err(TryRecvError::Empty) => {} 69 | /// } 70 | /// } 71 | /// Ok(()) 72 | /// 73 | /// } 74 | /// 75 | /// 76 | /// fn main() { 77 | /// example_copy(); 78 | /// } 79 | /// 80 | /// ``` 81 | pub mod file; 82 | 83 | /// This module includes additional methods for working with directories. 84 | /// 85 | /// One of the additional features is information 86 | /// about process and recursion operations. 87 | /// 88 | /// # Example 89 | /// ```rust,ignore 90 | /// use std::path::Path; 91 | /// use std::{thread, time}; 92 | /// use std::sync::mpsc::{self, TryRecvError}; 93 | /// 94 | /// extern crate fs_extra; 95 | /// use fs_extra::dir::*; 96 | /// use fs_extra::error::*; 97 | /// 98 | /// fn example_copy() -> Result<()> { 99 | /// 100 | /// let path_from = Path::new("./temp"); 101 | /// let path_to = path_from.join("out"); 102 | /// let test_folder = path_from.join("test_folder"); 103 | /// let dir = test_folder.join("dir"); 104 | /// let sub = dir.join("sub"); 105 | /// let file1 = dir.join("file1.txt"); 106 | /// let file2 = sub.join("file2.txt"); 107 | /// 108 | /// create_all(&sub, true)?; 109 | /// create_all(&path_to, true)?; 110 | /// fs_extra::file::write_all(&file1, "content1")?; 111 | /// fs_extra::file::write_all(&file2, "content2")?; 112 | /// 113 | /// assert!(dir.exists()); 114 | /// assert!(sub.exists()); 115 | /// assert!(file1.exists()); 116 | /// assert!(file2.exists()); 117 | /// 118 | /// 119 | /// let options = CopyOptions { 120 | /// buffer_size: 1, 121 | /// ..Default::default(), 122 | /// }; 123 | /// let (tx, rx) = mpsc::channel(); 124 | /// thread::spawn(move || { 125 | /// let handler = |process_info: TransitProcess| { 126 | /// tx.send(process_info).unwrap(); 127 | /// thread::sleep(time::Duration::from_millis(500)); 128 | /// }; 129 | /// copy_with_progress(&test_folder, &path_to, &options, handler).unwrap(); 130 | /// }); 131 | /// 132 | /// loop { 133 | /// match rx.try_recv() { 134 | /// Ok(process_info) => { 135 | /// println!("{} of {} bytes", 136 | /// process_info.copied_bytes, 137 | /// process_info.total_bytes); 138 | /// } 139 | /// Err(TryRecvError::Disconnected) => { 140 | /// println!("finished"); 141 | /// break; 142 | /// } 143 | /// Err(TryRecvError::Empty) => {} 144 | /// } 145 | /// } 146 | /// Ok(()) 147 | /// 148 | /// } 149 | /// fn main() { 150 | /// example_copy(); 151 | /// } 152 | /// ``` 153 | /// 154 | pub mod dir; 155 | 156 | use crate::error::*; 157 | use std::path::Path; 158 | 159 | /// Copies a list of directories and files to another place recursively. This function will 160 | /// also copy the permission bits of the original files to destination files (not for 161 | /// directories). 162 | /// 163 | /// # Errors 164 | /// 165 | /// This function will return an error in the following situations, but is not limited to just 166 | /// these case: 167 | /// 168 | /// * List `from` contains file or directory does not exist. 169 | /// 170 | /// * List `from` contains file or directory with invalid name. 171 | /// 172 | /// * The current process does not have the permission to access to file from `lists from` or 173 | /// `to`. 174 | /// 175 | /// # Example 176 | /// 177 | /// ```rust,ignore 178 | /// extern crate fs_extra; 179 | /// use fs_extra::dir::copy; 180 | /// 181 | /// let options = dir::CopyOptions::new(); //Initialize default values for CopyOptions 182 | /// 183 | /// // copy dir1 and file1.txt to target/dir1 and target/file1.txt 184 | /// let mut from_paths = Vec::new(); 185 | /// from_paths.push("source/dir1"); 186 | /// from_paths.push("source/file.txt"); 187 | /// copy_items(&from_paths, "target", &options)?; 188 | /// ``` 189 | /// 190 | pub fn copy_items(from: &[P], to: Q, options: &dir::CopyOptions) -> Result 191 | where 192 | P: AsRef, 193 | Q: AsRef, 194 | { 195 | let mut result: u64 = 0; 196 | if options.content_only { 197 | err!( 198 | "Options 'content_only' not acccess for copy_items function", 199 | ErrorKind::Other 200 | ); 201 | } 202 | for item in from { 203 | let item = item.as_ref(); 204 | if item.is_dir() { 205 | result += dir::copy(item, &to, options)?; 206 | } else if let Some(file_name) = item.file_name() { 207 | if let Some(file_name) = file_name.to_str() { 208 | let file_options = file::CopyOptions { 209 | overwrite: options.overwrite, 210 | skip_exist: options.skip_exist, 211 | ..Default::default() 212 | }; 213 | result += file::copy(item, to.as_ref().join(file_name), &file_options)?; 214 | } 215 | } else { 216 | err!("Invalid file name", ErrorKind::InvalidFileName); 217 | } 218 | } 219 | 220 | Ok(result) 221 | } 222 | 223 | /// A structure which includes information about the current status of copying or moving a directory. 224 | pub struct TransitProcess { 225 | /// Already copied bytes 226 | pub copied_bytes: u64, 227 | /// All the bytes which should be copied or moved (dir size). 228 | pub total_bytes: u64, 229 | /// Copied bytes on this time for file. 230 | pub file_bytes_copied: u64, 231 | /// Size of currently copied file. 232 | pub file_total_bytes: u64, 233 | /// Name of currently copied file. 234 | pub file_name: String, 235 | /// Name of currently copied folder. 236 | pub dir_name: String, 237 | /// Transit state 238 | pub state: dir::TransitState, 239 | } 240 | 241 | impl Clone for TransitProcess { 242 | fn clone(&self) -> TransitProcess { 243 | TransitProcess { 244 | copied_bytes: self.copied_bytes, 245 | total_bytes: self.total_bytes, 246 | file_bytes_copied: self.file_bytes_copied, 247 | file_total_bytes: self.file_total_bytes, 248 | file_name: self.file_name.clone(), 249 | dir_name: self.dir_name.clone(), 250 | state: self.state.clone(), 251 | } 252 | } 253 | } 254 | 255 | /// Copies a list of directories and files to another place recursively, with 256 | /// information about progress. This function will also copy the permission bits of the 257 | /// original files to destination files (not for directories). 258 | /// 259 | /// # Errors 260 | /// 261 | /// This function will return an error in the following situations, but is not limited to just 262 | /// these case: 263 | /// 264 | /// * List `from` contains file or directory does not exist. 265 | /// 266 | /// * List `from` contains file or directory with invalid name. 267 | /// 268 | /// * The current process does not have the permission to access to file from `lists from` or 269 | /// `to`. 270 | /// 271 | /// # Example 272 | /// ```rust,ignore 273 | /// 274 | /// extern crate fs_extra; 275 | /// use fs_extra::dir::copy; 276 | /// 277 | /// let options = dir::CopyOptions::new(); //Initialize default values for CopyOptions 278 | /// let handle = |process_info: TransitProcess| { 279 | /// println!("{}", process_info.total_bytes); 280 | /// fs_extra::dir::TransitProcessResult::ContinueOrAbort 281 | /// } 282 | /// // copy dir1 and file1.txt to target/dir1 and target/file1.txt 283 | /// let mut from_paths = Vec::new(); 284 | /// from_paths.push("source/dir1"); 285 | /// from_paths.push("source/file.txt"); 286 | /// copy_items_with_progress(&from_paths, "target", &options, handle)?; 287 | /// ``` 288 | /// 289 | pub fn copy_items_with_progress( 290 | from: &[P], 291 | to: Q, 292 | options: &dir::CopyOptions, 293 | mut progress_handler: F, 294 | ) -> Result 295 | where 296 | P: AsRef, 297 | Q: AsRef, 298 | F: FnMut(TransitProcess) -> dir::TransitProcessResult, 299 | { 300 | if options.content_only { 301 | err!( 302 | "Options 'content_only' not access for copy_items_with_progress function", 303 | ErrorKind::Other 304 | ); 305 | } 306 | let mut total_size = 0; 307 | let mut list_paths = Vec::new(); 308 | for item in from { 309 | let item = item.as_ref(); 310 | total_size += dir::get_size(item)?; 311 | list_paths.push(item); 312 | } 313 | 314 | let mut result: u64 = 0; 315 | let mut info_process = TransitProcess { 316 | copied_bytes: 0, 317 | total_bytes: total_size, 318 | file_bytes_copied: 0, 319 | file_total_bytes: 0, 320 | file_name: String::new(), 321 | dir_name: String::new(), 322 | state: dir::TransitState::Normal, 323 | }; 324 | 325 | let mut options = options.clone(); 326 | for item in list_paths { 327 | if item.is_dir() { 328 | if let Some(dir_name) = item.components().last() { 329 | if let Ok(dir_name) = dir_name.as_os_str().to_os_string().into_string() { 330 | info_process.dir_name = dir_name; 331 | } else { 332 | err!("Invalid folder from", ErrorKind::InvalidFolder); 333 | } 334 | } else { 335 | err!("Invalid folder from", ErrorKind::InvalidFolder); 336 | } 337 | 338 | let copied_bytes = result; 339 | let dir_options = options.clone(); 340 | let handler = |info: dir::TransitProcess| { 341 | info_process.copied_bytes = copied_bytes + info.copied_bytes; 342 | info_process.state = info.state; 343 | let result = progress_handler(info_process.clone()); 344 | match result { 345 | dir::TransitProcessResult::OverwriteAll => options.overwrite = true, 346 | dir::TransitProcessResult::SkipAll => options.skip_exist = true, 347 | _ => {} 348 | } 349 | result 350 | }; 351 | result += dir::copy_with_progress(item, &to, &dir_options, handler)?; 352 | } else { 353 | let mut file_options = file::CopyOptions { 354 | overwrite: options.overwrite, 355 | skip_exist: options.skip_exist, 356 | buffer_size: options.buffer_size, 357 | }; 358 | 359 | if let Some(file_name) = item.file_name() { 360 | if let Some(file_name) = file_name.to_str() { 361 | info_process.file_name = file_name.to_string(); 362 | } else { 363 | err!("Invalid file name", ErrorKind::InvalidFileName); 364 | } 365 | } else { 366 | err!("Invalid file name", ErrorKind::InvalidFileName); 367 | } 368 | 369 | info_process.file_bytes_copied = 0; 370 | info_process.file_total_bytes = item.metadata()?.len(); 371 | 372 | let copied_bytes = result; 373 | let file_name = to.as_ref().join(info_process.file_name.clone()); 374 | let mut work = true; 375 | 376 | let mut result_copy: Result; 377 | while work { 378 | { 379 | let handler = |info: file::TransitProcess| { 380 | info_process.copied_bytes = copied_bytes + info.copied_bytes; 381 | info_process.file_bytes_copied = info.copied_bytes; 382 | progress_handler(info_process.clone()); 383 | }; 384 | result_copy = 385 | file::copy_with_progress(item, &file_name, &file_options, handler); 386 | } 387 | match result_copy { 388 | Ok(val) => { 389 | result += val; 390 | work = false; 391 | } 392 | Err(err) => match err.kind { 393 | ErrorKind::AlreadyExists => { 394 | let mut info_process = info_process.clone(); 395 | info_process.state = dir::TransitState::Exists; 396 | let user_decide = progress_handler(info_process); 397 | match user_decide { 398 | dir::TransitProcessResult::Overwrite => { 399 | file_options.overwrite = true; 400 | } 401 | dir::TransitProcessResult::OverwriteAll => { 402 | file_options.overwrite = true; 403 | options.overwrite = true; 404 | } 405 | dir::TransitProcessResult::Skip => { 406 | file_options.skip_exist = true; 407 | } 408 | dir::TransitProcessResult::SkipAll => { 409 | file_options.skip_exist = true; 410 | options.skip_exist = true; 411 | } 412 | dir::TransitProcessResult::Retry => {} 413 | dir::TransitProcessResult::ContinueOrAbort => { 414 | let err_msg = err.to_string(); 415 | err!(err_msg.as_str(), err.kind) 416 | } 417 | dir::TransitProcessResult::Abort => { 418 | let err_msg = err.to_string(); 419 | err!(err_msg.as_str(), err.kind) 420 | } 421 | } 422 | } 423 | ErrorKind::PermissionDenied => { 424 | let mut info_process = info_process.clone(); 425 | info_process.state = dir::TransitState::Exists; 426 | let user_decide = progress_handler(info_process); 427 | match user_decide { 428 | dir::TransitProcessResult::Overwrite => { 429 | err!("Overwrite denied for this situation!", ErrorKind::Other); 430 | } 431 | dir::TransitProcessResult::OverwriteAll => { 432 | err!("Overwrite denied for this situation!", ErrorKind::Other); 433 | } 434 | dir::TransitProcessResult::Skip => { 435 | file_options.skip_exist = true; 436 | } 437 | dir::TransitProcessResult::SkipAll => { 438 | file_options.skip_exist = true; 439 | options.skip_exist = true; 440 | } 441 | dir::TransitProcessResult::Retry => {} 442 | dir::TransitProcessResult::ContinueOrAbort => { 443 | let err_msg = err.to_string(); 444 | err!(err_msg.as_str(), err.kind) 445 | } 446 | dir::TransitProcessResult::Abort => { 447 | let err_msg = err.to_string(); 448 | err!(err_msg.as_str(), err.kind) 449 | } 450 | } 451 | } 452 | _ => { 453 | let err_msg = err.to_string(); 454 | err!(err_msg.as_str(), err.kind) 455 | } 456 | }, 457 | } 458 | } 459 | } 460 | } 461 | 462 | Ok(result) 463 | } 464 | 465 | /// Moves a list of directories and files to another place recursively. This function will 466 | /// also copy the permission bits of the original files to destination files (not for 467 | /// directories). 468 | /// 469 | /// # Errors 470 | /// 471 | /// This function will return an error in the following situations, but is not limited to just 472 | /// these case: 473 | /// 474 | /// * List `from` contains file or directory does not exist. 475 | /// 476 | /// * List `from` contains file or directory with invalid name. 477 | /// 478 | /// * The current process does not have the permission to access to file from `lists from` or 479 | /// `to`. 480 | /// 481 | /// # Example 482 | /// 483 | /// ```rust,ignore 484 | /// extern crate fs_extra; 485 | /// use fs_extra::dir::copy; 486 | /// 487 | /// let options = dir::CopyOptions::new(); //Initialize default values for CopyOptions 488 | /// 489 | /// // move dir1 and file1.txt to target/dir1 and target/file1.txt 490 | /// let mut from_paths = Vec::new(); 491 | /// from_paths.push("source/dir1"); 492 | /// from_paths.push("source/file.txt"); 493 | /// move_items(&from_paths, "target", &options)?; 494 | /// ``` 495 | /// 496 | pub fn move_items(from_items: &[P], to: Q, options: &dir::CopyOptions) -> Result 497 | where 498 | P: AsRef, 499 | Q: AsRef, 500 | { 501 | if options.content_only { 502 | err!( 503 | "Options 'content_only' not access for move_items function", 504 | ErrorKind::Other 505 | ); 506 | } 507 | let mut total_size = 0; 508 | let mut list_paths = Vec::new(); 509 | for item in from_items { 510 | let item = item.as_ref(); 511 | total_size += dir::get_size(item)?; 512 | list_paths.push(item); 513 | } 514 | 515 | let mut result = 0; 516 | let mut info_process = TransitProcess { 517 | copied_bytes: 0, 518 | total_bytes: total_size, 519 | file_bytes_copied: 0, 520 | file_total_bytes: 0, 521 | file_name: String::new(), 522 | dir_name: String::new(), 523 | state: dir::TransitState::Normal, 524 | }; 525 | 526 | for item in list_paths { 527 | if item.is_dir() { 528 | if let Some(dir_name) = item.components().last() { 529 | if let Ok(dir_name) = dir_name.as_os_str().to_os_string().into_string() { 530 | info_process.dir_name = dir_name; 531 | } else { 532 | err!("Invalid folder from", ErrorKind::InvalidFolder); 533 | } 534 | } else { 535 | err!("Invalid folder from", ErrorKind::InvalidFolder); 536 | } 537 | 538 | result += dir::move_dir(item, &to, options)?; 539 | } else { 540 | let file_options = file::CopyOptions { 541 | overwrite: options.overwrite, 542 | skip_exist: options.skip_exist, 543 | buffer_size: options.buffer_size, 544 | }; 545 | 546 | if let Some(file_name) = item.file_name() { 547 | if let Some(file_name) = file_name.to_str() { 548 | info_process.file_name = file_name.to_string(); 549 | } else { 550 | err!("Invalid file name", ErrorKind::InvalidFileName); 551 | } 552 | } else { 553 | err!("Invalid file name", ErrorKind::InvalidFileName); 554 | } 555 | 556 | info_process.file_bytes_copied = 0; 557 | info_process.file_total_bytes = item.metadata()?.len(); 558 | 559 | let file_name = to.as_ref().join(info_process.file_name.clone()); 560 | result += file::move_file(item, &file_name, &file_options)?; 561 | } 562 | } 563 | 564 | Ok(result) 565 | } 566 | 567 | /// Moves a list of directories and files to another place recursively, with 568 | /// information about progress. This function will also copy the permission bits of the 569 | /// original files to destination files (not for directories). 570 | /// 571 | /// # Errors 572 | /// 573 | /// This function will return an error in the following situations, but is not limited to just 574 | /// these case: 575 | /// 576 | /// * List `from` contains file or directory does not exist. 577 | /// 578 | /// * List `from` contains file or directory with invalid name. 579 | /// 580 | /// * The current process does not have the permission to access to file from `lists from` or 581 | /// `to`. 582 | /// 583 | /// # Example 584 | /// 585 | /// ```rust,ignore 586 | /// extern crate fs_extra; 587 | /// use fs_extra::dir::copy; 588 | /// 589 | /// let options = dir::CopyOptions::new(); //Initialize default values for CopyOptions 590 | /// let handle = |process_info: TransitProcess| { 591 | /// println!("{}", process_info.total_bytes); 592 | /// fs_extra::dir::TransitProcessResult::ContinueOrAbort 593 | /// } 594 | /// // move dir1 and file1.txt to target/dir1 and target/file1.txt 595 | /// let mut from_paths = Vec::new(); 596 | /// from_paths.push("source/dir1"); 597 | /// from_paths.push("source/file.txt"); 598 | /// move_items_with_progress(&from_paths, "target", &options, handle)?; 599 | /// ``` 600 | /// 601 | pub fn move_items_with_progress( 602 | from_items: &[P], 603 | to: Q, 604 | options: &dir::CopyOptions, 605 | mut progress_handler: F, 606 | ) -> Result 607 | where 608 | P: AsRef, 609 | Q: AsRef, 610 | F: FnMut(TransitProcess) -> dir::TransitProcessResult, 611 | { 612 | if options.content_only { 613 | err!( 614 | "Options 'content_only' not access for move_items_with_progress function", 615 | ErrorKind::Other 616 | ); 617 | } 618 | let mut total_size = 0; 619 | let mut list_paths = Vec::new(); 620 | for item in from_items { 621 | let item = item.as_ref(); 622 | total_size += dir::get_size(item)?; 623 | list_paths.push(item); 624 | } 625 | 626 | let mut result = 0; 627 | let mut info_process = TransitProcess { 628 | copied_bytes: 0, 629 | total_bytes: total_size, 630 | file_bytes_copied: 0, 631 | file_total_bytes: 0, 632 | file_name: String::new(), 633 | dir_name: String::new(), 634 | state: dir::TransitState::Normal, 635 | }; 636 | let mut options = options.clone(); 637 | 638 | for item in list_paths { 639 | if item.is_dir() { 640 | if let Some(dir_name) = item.components().last() { 641 | if let Ok(dir_name) = dir_name.as_os_str().to_os_string().into_string() { 642 | info_process.dir_name = dir_name; 643 | } else { 644 | err!("Invalid folder from", ErrorKind::InvalidFolder); 645 | } 646 | } else { 647 | err!("Invalid folder from", ErrorKind::InvalidFolder); 648 | } 649 | 650 | let copied_bytes = result; 651 | let dir_options = options.clone(); 652 | let handler = |info: dir::TransitProcess| { 653 | info_process.copied_bytes = copied_bytes + info.copied_bytes; 654 | info_process.state = info.state; 655 | let result = progress_handler(info_process.clone()); 656 | match result { 657 | dir::TransitProcessResult::OverwriteAll => options.overwrite = true, 658 | dir::TransitProcessResult::SkipAll => options.skip_exist = true, 659 | _ => {} 660 | } 661 | result 662 | }; 663 | result += dir::move_dir_with_progress(item, &to, &dir_options, handler)?; 664 | } else { 665 | let mut file_options = file::CopyOptions { 666 | overwrite: options.overwrite, 667 | skip_exist: options.skip_exist, 668 | buffer_size: options.buffer_size, 669 | }; 670 | 671 | if let Some(file_name) = item.file_name() { 672 | if let Some(file_name) = file_name.to_str() { 673 | info_process.file_name = file_name.to_string(); 674 | } else { 675 | err!("Invalid file name", ErrorKind::InvalidFileName); 676 | } 677 | } else { 678 | err!("Invalid file name", ErrorKind::InvalidFileName); 679 | } 680 | 681 | info_process.file_bytes_copied = 0; 682 | info_process.file_total_bytes = item.metadata()?.len(); 683 | 684 | let copied_bytes = result; 685 | let file_name = to.as_ref().join(info_process.file_name.clone()); 686 | let mut work = true; 687 | 688 | let mut result_copy: Result; 689 | while work { 690 | { 691 | let handler = |info: file::TransitProcess| { 692 | info_process.copied_bytes = copied_bytes + info.copied_bytes; 693 | info_process.file_bytes_copied = info.copied_bytes; 694 | progress_handler(info_process.clone()); 695 | }; 696 | result_copy = 697 | file::move_file_with_progress(item, &file_name, &file_options, handler); 698 | } 699 | match result_copy { 700 | Ok(val) => { 701 | result += val; 702 | work = false; 703 | } 704 | Err(err) => match err.kind { 705 | ErrorKind::AlreadyExists => { 706 | let mut info_process = info_process.clone(); 707 | info_process.state = dir::TransitState::Exists; 708 | let user_decide = progress_handler(info_process); 709 | match user_decide { 710 | dir::TransitProcessResult::Overwrite => { 711 | file_options.overwrite = true; 712 | } 713 | dir::TransitProcessResult::OverwriteAll => { 714 | file_options.overwrite = true; 715 | options.overwrite = true; 716 | } 717 | dir::TransitProcessResult::Skip => { 718 | file_options.skip_exist = true; 719 | } 720 | dir::TransitProcessResult::SkipAll => { 721 | file_options.skip_exist = true; 722 | options.skip_exist = true; 723 | } 724 | dir::TransitProcessResult::Retry => {} 725 | dir::TransitProcessResult::ContinueOrAbort => { 726 | let err_msg = err.to_string(); 727 | err!(err_msg.as_str(), err.kind) 728 | } 729 | dir::TransitProcessResult::Abort => { 730 | let err_msg = err.to_string(); 731 | err!(err_msg.as_str(), err.kind) 732 | } 733 | } 734 | } 735 | ErrorKind::PermissionDenied => { 736 | let mut info_process = info_process.clone(); 737 | info_process.state = dir::TransitState::Exists; 738 | let user_decide = progress_handler(info_process); 739 | match user_decide { 740 | dir::TransitProcessResult::Overwrite => { 741 | err!("Overwrite denied for this situation!", ErrorKind::Other); 742 | } 743 | dir::TransitProcessResult::OverwriteAll => { 744 | err!("Overwrite denied for this situation!", ErrorKind::Other); 745 | } 746 | dir::TransitProcessResult::Skip => { 747 | file_options.skip_exist = true; 748 | } 749 | dir::TransitProcessResult::SkipAll => { 750 | file_options.skip_exist = true; 751 | options.skip_exist = true; 752 | } 753 | dir::TransitProcessResult::Retry => {} 754 | dir::TransitProcessResult::ContinueOrAbort => { 755 | let err_msg = err.to_string(); 756 | err!(err_msg.as_str(), err.kind) 757 | } 758 | dir::TransitProcessResult::Abort => { 759 | let err_msg = err.to_string(); 760 | err!(err_msg.as_str(), err.kind) 761 | } 762 | } 763 | } 764 | _ => { 765 | let err_msg = err.to_string(); 766 | err!(err_msg.as_str(), err.kind) 767 | } 768 | }, 769 | } 770 | } 771 | } 772 | } 773 | Ok(result) 774 | } 775 | 776 | /// Removes a list of files or directories. 777 | /// 778 | /// # Example 779 | /// 780 | /// ```rust,ignore 781 | /// let mut from_paths = Vec::new(); 782 | /// from_paths.push("source/dir1"); 783 | /// from_paths.push("source/file.txt"); 784 | /// 785 | /// remove_items(&from_paths).unwrap(); 786 | /// ``` 787 | /// 788 | pub fn remove_items

(from_items: &[P]) -> Result<()> 789 | where 790 | P: AsRef, 791 | { 792 | for item in from_items { 793 | let item = item.as_ref(); 794 | if item.is_dir() { 795 | dir::remove(item)?; 796 | } else { 797 | file::remove(item)? 798 | } 799 | } 800 | 801 | Ok(()) 802 | } 803 | -------------------------------------------------------------------------------- /tests/file.rs: -------------------------------------------------------------------------------- 1 | // use std::io::{ErrorKind, Result}; 2 | use std::path::{Path, PathBuf}; 3 | use std::sync::mpsc; 4 | use std::thread; 5 | 6 | extern crate fs_extra; 7 | use fs_extra::error::*; 8 | use fs_extra::file::*; 9 | 10 | const TEST_FOLDER: &'static str = "./tests/temp/file"; 11 | 12 | fn files_eq(file1: P, file2: Q) -> Result 13 | where 14 | P: AsRef, 15 | Q: AsRef, 16 | { 17 | let content1 = read_to_string(file1)?; 18 | let content2 = read_to_string(file2)?; 19 | Ok(content1 == content2) 20 | } 21 | 22 | #[test] 23 | fn it_read_and_write_work() { 24 | let mut test_file = PathBuf::from(TEST_FOLDER); 25 | test_file.push("it_read_and_write_work"); 26 | test_file.push("test.txt"); 27 | fs_extra::dir::create_all(test_file.parent().unwrap(), true).unwrap(); 28 | let content1 = "test_1"; 29 | let content2 = "test_2"; 30 | write_all(&test_file, &content1).unwrap(); 31 | assert!(test_file.exists()); 32 | let read1 = read_to_string(&test_file).unwrap(); 33 | assert_eq!(content1, read1); 34 | write_all(&test_file, &content2).unwrap(); 35 | let read2 = read_to_string(&test_file).unwrap(); 36 | assert_eq!(content2, read2); 37 | } 38 | 39 | #[test] 40 | fn it_read_not_exist_file() { 41 | let mut test_file = PathBuf::from(TEST_FOLDER); 42 | test_file.push("it_read_not_exist_file"); 43 | test_file.push("test.txt"); 44 | fs_extra::dir::create_all(test_file.parent().unwrap(), true).unwrap(); 45 | assert!(!test_file.exists()); 46 | match read_to_string(&test_file) { 47 | Ok(_) => panic!("should be error"), 48 | Err(err) => match err.kind { 49 | ErrorKind::NotFound => {} 50 | _ => panic!("wrong error"), 51 | }, 52 | } 53 | } 54 | 55 | #[test] 56 | fn it_read_not_file() { 57 | let mut test_file = PathBuf::from(TEST_FOLDER); 58 | test_file.push("it_read_not_file"); 59 | fs_extra::dir::create_all(&test_file, true).unwrap(); 60 | match read_to_string(&test_file) { 61 | Ok(_) => panic!("should be error"), 62 | Err(err) => match err.kind { 63 | ErrorKind::InvalidFile => {} 64 | _ => panic!("wrong error"), 65 | }, 66 | } 67 | } 68 | 69 | #[test] 70 | fn it_write_not_file() { 71 | let mut test_file = PathBuf::from(TEST_FOLDER); 72 | test_file.push("it_write_not_file"); 73 | test_file.push("test.txt"); 74 | fs_extra::dir::create_all(test_file.parent().unwrap(), true).unwrap(); 75 | assert!(!test_file.exists()); 76 | test_file.pop(); 77 | match write_all(test_file, "content") { 78 | Ok(_) => panic!("should be error"), 79 | Err(err) => match err.kind { 80 | ErrorKind::InvalidFile => {} 81 | _ => panic!("wrong error"), 82 | }, 83 | } 84 | } 85 | 86 | #[test] 87 | fn it_remove_file() { 88 | let mut test_file = PathBuf::from(TEST_FOLDER); 89 | test_file.push("it_remove_file"); 90 | test_file.push("test.txt"); 91 | fs_extra::dir::create_all(test_file.parent().unwrap(), true).unwrap(); 92 | write_all(&test_file, "test").unwrap(); 93 | assert!(test_file.exists()); 94 | remove(&test_file).unwrap(); 95 | assert!(!test_file.exists()); 96 | } 97 | 98 | #[test] 99 | fn it_copy_work() { 100 | let mut test_file = PathBuf::from(TEST_FOLDER); 101 | test_file.push("it_copy_work"); 102 | let mut test_file_out = test_file.clone(); 103 | test_file.push("test.txt"); 104 | test_file_out.push("out"); 105 | test_file_out.push("test.txt"); 106 | fs_extra::dir::create_all(&test_file.parent().unwrap(), true).unwrap(); 107 | fs_extra::dir::create_all(&test_file_out.parent().unwrap(), true).unwrap(); 108 | 109 | write_all(&test_file, "test_data").unwrap(); 110 | assert!(test_file.exists()); 111 | assert!(!test_file_out.exists()); 112 | let options = CopyOptions::new(); 113 | copy(&test_file, &test_file_out, &options).unwrap(); 114 | assert!(test_file.exists()); 115 | assert!(test_file_out.exists()); 116 | assert_eq!(test_file.file_name(), test_file_out.file_name()); 117 | assert!(files_eq(test_file, test_file_out).unwrap()); 118 | } 119 | 120 | #[test] 121 | fn it_copy_not_file() { 122 | let mut test_file = PathBuf::from(TEST_FOLDER); 123 | test_file.push("it_copy_work"); 124 | let mut test_file_out = test_file.clone(); 125 | test_file.push("test.txt"); 126 | test_file_out.push("out"); 127 | test_file_out.push("test.txt"); 128 | fs_extra::dir::create_all(&test_file.parent().unwrap(), true).unwrap(); 129 | fs_extra::dir::create_all(&test_file_out.parent().unwrap(), true).unwrap(); 130 | 131 | write_all(&test_file, "test_data").unwrap(); 132 | assert!(test_file.exists()); 133 | assert!(!test_file_out.exists()); 134 | test_file.pop(); 135 | let options = CopyOptions::new(); 136 | 137 | match copy(&test_file, &test_file_out, &options) { 138 | Err(err) => match err.kind { 139 | ErrorKind::InvalidFile => { 140 | let wrong_path = format!("Path \"{}\" is not a file!", test_file.to_str().unwrap()); 141 | assert_eq!(wrong_path, err.to_string()); 142 | } 143 | _ => { 144 | panic!("wrong error"); 145 | } 146 | }, 147 | Ok(_) => { 148 | panic!("should be error"); 149 | } 150 | } 151 | } 152 | 153 | #[test] 154 | fn it_copy_source_not_exist() { 155 | let mut test_file = PathBuf::from(TEST_FOLDER); 156 | test_file.push("it_copy_source_not_exist"); 157 | let mut test_file_out = test_file.clone(); 158 | test_file.push("test1.txt"); 159 | test_file_out.push("out"); 160 | test_file_out.push("test.txt"); 161 | fs_extra::dir::create_all(&test_file.parent().unwrap(), true).unwrap(); 162 | fs_extra::dir::create_all(&test_file_out.parent().unwrap(), true).unwrap(); 163 | 164 | assert!(!test_file.exists()); 165 | let options = CopyOptions::new(); 166 | match copy(&test_file, test_file_out, &options) { 167 | Ok(_) => panic!("should be error"), 168 | Err(err) => match err.kind { 169 | ErrorKind::NotFound => { 170 | let wrong_path = format!( 171 | "Path \"{}\" does not exist or you don't have \ 172 | access!", 173 | test_file.to_str().unwrap() 174 | ); 175 | assert_eq!(wrong_path, err.to_string()); 176 | () 177 | } 178 | _ => panic!("wrong error"), 179 | }, 180 | } 181 | } 182 | 183 | #[test] 184 | fn it_copy_exist_overwrite() { 185 | let mut test_file = PathBuf::from(TEST_FOLDER); 186 | test_file.push("it_copy_exist_overwrite"); 187 | let mut test_file_out = test_file.clone(); 188 | test_file.push("test.txt"); 189 | test_file_out.push("out"); 190 | test_file_out.push("test.txt"); 191 | fs_extra::dir::create_all(&test_file.parent().unwrap(), true).unwrap(); 192 | fs_extra::dir::create_all(&test_file_out.parent().unwrap(), true).unwrap(); 193 | 194 | write_all(&test_file, "test_data").unwrap(); 195 | let mut options = CopyOptions::new(); 196 | copy(&test_file, &test_file_out, &options).unwrap(); 197 | assert!(test_file.exists()); 198 | assert!(files_eq(&test_file, &test_file_out).unwrap()); 199 | options.overwrite = true; 200 | write_all(&test_file, "test_data2").unwrap(); 201 | match copy(&test_file, &test_file_out, &options) { 202 | Ok(_) => { 203 | assert!(test_file.exists()); 204 | assert_eq!(read_to_string(test_file_out).unwrap(), "test_data2"); 205 | () 206 | } 207 | Err(err) => panic!(err.to_string()), 208 | } 209 | } 210 | 211 | #[test] 212 | fn it_copy_exist_not_overwrite() { 213 | let mut test_file = PathBuf::from(TEST_FOLDER); 214 | test_file.push("it_copy_exist_not_overwrite"); 215 | let mut test_file_out = test_file.clone(); 216 | test_file.push("test.txt"); 217 | test_file_out.push("out"); 218 | test_file_out.push("test.txt"); 219 | fs_extra::dir::create_all(&test_file.parent().unwrap(), true).unwrap(); 220 | fs_extra::dir::create_all(&test_file_out.parent().unwrap(), true).unwrap(); 221 | 222 | write_all(&test_file, "test_data").unwrap(); 223 | let mut options = CopyOptions::new(); 224 | copy(&test_file, &test_file_out, &options).unwrap(); 225 | assert!(test_file.exists()); 226 | options.overwrite = false; 227 | write_all(&test_file, "test_data2").unwrap(); 228 | match copy(&test_file, &test_file_out, &options) { 229 | Ok(_) => panic!("should be error"), 230 | Err(err) => { 231 | let file_path = format!("Path \"{}\" exists", test_file_out.to_str().unwrap()); 232 | assert_eq!(file_path, err.to_string()); 233 | assert!(!files_eq(test_file, test_file_out).unwrap()); 234 | () 235 | } 236 | } 237 | } 238 | 239 | #[test] 240 | fn it_copy_exist_skip_exist() { 241 | let mut test_file = PathBuf::from(TEST_FOLDER); 242 | test_file.push("it_copy_exist_skip_exist"); 243 | let mut test_file_out = test_file.clone(); 244 | test_file.push("test.txt"); 245 | test_file_out.push("out"); 246 | test_file_out.push("test.txt"); 247 | fs_extra::dir::create_all(&test_file.parent().unwrap(), true).unwrap(); 248 | fs_extra::dir::create_all(&test_file_out.parent().unwrap(), true).unwrap(); 249 | 250 | write_all(&test_file, "test_data").unwrap(); 251 | let mut options = CopyOptions::new(); 252 | copy(&test_file, &test_file_out, &options).unwrap(); 253 | assert!(test_file.exists()); 254 | options.skip_exist = true; 255 | write_all(&test_file, "test_data2").unwrap(); 256 | match copy(&test_file, &test_file_out, &options) { 257 | Ok(_) => { 258 | assert!(!files_eq(test_file, test_file_out).unwrap()); 259 | () 260 | } 261 | Err(_) => panic!("should be error"), 262 | } 263 | } 264 | 265 | #[test] 266 | fn it_copy_exist_overwrite_and_skip_exist() { 267 | let mut test_file = PathBuf::from(TEST_FOLDER); 268 | test_file.push("it_copy_exist_overwrite_and_skip_exist"); 269 | let mut test_file_out = test_file.clone(); 270 | test_file.push("test.txt"); 271 | test_file_out.push("out"); 272 | test_file_out.push("test.txt"); 273 | fs_extra::dir::create_all(&test_file.parent().unwrap(), true).unwrap(); 274 | fs_extra::dir::create_all(&test_file_out.parent().unwrap(), true).unwrap(); 275 | 276 | write_all(&test_file, "test_data").unwrap(); 277 | let mut options = CopyOptions::new(); 278 | copy(&test_file, &test_file_out, &options).unwrap(); 279 | assert!(test_file.exists()); 280 | assert!(files_eq(&test_file, &test_file_out).unwrap()); 281 | options.overwrite = true; 282 | options.skip_exist = true; 283 | write_all(&test_file, "test_data2").unwrap(); 284 | match copy(&test_file, &test_file_out, &options) { 285 | Ok(_) => { 286 | assert!(test_file.exists()); 287 | assert_eq!(read_to_string(test_file_out).unwrap(), "test_data2"); 288 | () 289 | } 290 | Err(err) => panic!(err.to_string()), 291 | } 292 | } 293 | 294 | #[test] 295 | fn it_copy_with_progress_work() { 296 | let mut test_file = PathBuf::from(TEST_FOLDER); 297 | test_file.push("it_copy_with_progress_work"); 298 | let mut test_file_out = test_file.clone(); 299 | test_file.push("test.txt"); 300 | test_file_out.push("out"); 301 | test_file_out.push("test.txt"); 302 | fs_extra::dir::create_all(&test_file.parent().unwrap(), true).unwrap(); 303 | fs_extra::dir::create_all(&test_file_out.parent().unwrap(), true).unwrap(); 304 | 305 | write_all(&test_file, "test_data").unwrap(); 306 | assert!(test_file.exists()); 307 | assert!(!test_file_out.exists()); 308 | let mut options = CopyOptions::new(); 309 | options.buffer_size = 1; 310 | let (tx, rx) = mpsc::channel(); 311 | thread::spawn(move || { 312 | let func_test = |process_info: TransitProcess| { 313 | tx.send(process_info).unwrap(); 314 | }; 315 | copy_with_progress(&test_file, &test_file_out, &options, func_test).unwrap(); 316 | assert!(test_file.exists()); 317 | assert!(test_file_out.exists()); 318 | assert_eq!(test_file.file_name(), test_file_out.file_name()); 319 | assert!(files_eq(test_file, test_file_out).unwrap()); 320 | }); 321 | for i in 1..10 { 322 | let process_info: TransitProcess = rx.recv().unwrap(); 323 | assert_eq!(i, process_info.copied_bytes); 324 | assert_eq!(9, process_info.total_bytes); 325 | } 326 | } 327 | 328 | #[test] 329 | fn it_copy_progress_not_file() { 330 | let mut test_file = PathBuf::from(TEST_FOLDER); 331 | test_file.push("it_copy_progress_not_file"); 332 | let mut test_file_out = test_file.clone(); 333 | test_file.push("test.txt"); 334 | test_file_out.push("out"); 335 | test_file_out.push("test.txt"); 336 | fs_extra::dir::create_all(&test_file.parent().unwrap(), true).unwrap(); 337 | fs_extra::dir::create_all(&test_file_out.parent().unwrap(), true).unwrap(); 338 | 339 | write_all(&test_file, "test_data").unwrap(); 340 | assert!(test_file.exists()); 341 | assert!(!test_file_out.exists()); 342 | test_file.pop(); 343 | let options = CopyOptions::new(); 344 | let func_test = |process_info: TransitProcess| println!("{}", process_info.total_bytes); 345 | 346 | match copy_with_progress(&test_file, &test_file_out, &options, func_test) { 347 | Err(err) => match err.kind { 348 | ErrorKind::InvalidFile => { 349 | let wrong_path = format!("Path \"{}\" is not a file!", test_file.to_str().unwrap()); 350 | assert_eq!(wrong_path, err.to_string()); 351 | } 352 | _ => { 353 | panic!("wrong error"); 354 | } 355 | }, 356 | Ok(_) => { 357 | panic!("should be error"); 358 | } 359 | } 360 | } 361 | 362 | #[test] 363 | fn it_copy_with_progress_work_dif_buf_size() { 364 | let mut test_file = PathBuf::from(TEST_FOLDER); 365 | test_file.push("it_copy_with_progress_work_dif_buf_size"); 366 | let mut test_file_out = test_file.clone(); 367 | test_file.push("test.txt"); 368 | test_file_out.push("out"); 369 | test_file_out.push("test.txt"); 370 | fs_extra::dir::create_all(&test_file.parent().unwrap(), true).unwrap(); 371 | fs_extra::dir::create_all(&test_file_out.parent().unwrap(), true).unwrap(); 372 | 373 | write_all(&test_file, "test_data_").unwrap(); 374 | assert!(test_file.exists()); 375 | assert!(!test_file_out.exists()); 376 | let mut options = CopyOptions::new(); 377 | options.buffer_size = 1; 378 | let (tx, rx) = mpsc::channel(); 379 | thread::spawn(move || { 380 | let func_test = |process_info: TransitProcess| { 381 | tx.send(process_info).unwrap(); 382 | }; 383 | copy_with_progress(&test_file, &test_file_out, &options, func_test).unwrap(); 384 | assert!(test_file.exists()); 385 | assert!(test_file_out.exists()); 386 | assert_eq!(test_file.file_name(), test_file_out.file_name()); 387 | assert!(files_eq(&test_file, &test_file_out).unwrap()); 388 | 389 | let mut options = CopyOptions::new(); 390 | options.buffer_size = 2; 391 | options.overwrite = true; 392 | let (tx, rx) = mpsc::channel(); 393 | thread::spawn(move || { 394 | let func_test = |process_info: TransitProcess| { 395 | tx.send(process_info).unwrap(); 396 | }; 397 | copy_with_progress(&test_file, &test_file_out, &options, func_test).unwrap(); 398 | }); 399 | for i in 1..6 { 400 | let process_info: TransitProcess = rx.recv().unwrap(); 401 | assert_eq!(i * 2, process_info.copied_bytes); 402 | assert_eq!(10, process_info.total_bytes); 403 | } 404 | }); 405 | for i in 1..11 { 406 | let process_info: TransitProcess = rx.recv().unwrap(); 407 | assert_eq!(i, process_info.copied_bytes); 408 | assert_eq!(10, process_info.total_bytes); 409 | } 410 | } 411 | 412 | #[test] 413 | fn it_copy_with_progress_source_not_exist() { 414 | let mut test_file = PathBuf::from(TEST_FOLDER); 415 | test_file.push("it_copy_with_progress_source_not_exist"); 416 | let mut test_file_out = test_file.clone(); 417 | test_file.push("test1.txt"); 418 | test_file_out.push("out"); 419 | test_file_out.push("test.txt"); 420 | fs_extra::dir::create_all(&test_file.parent().unwrap(), true).unwrap(); 421 | fs_extra::dir::create_all(&test_file_out.parent().unwrap(), true).unwrap(); 422 | 423 | assert!(!test_file.exists()); 424 | let options = CopyOptions::new(); 425 | let func_test = |process_info: TransitProcess| { 426 | println!("{}", process_info.total_bytes); 427 | }; 428 | match copy_with_progress(&test_file, &test_file_out, &options, func_test) { 429 | Ok(_) => panic!("should be error"), 430 | Err(err) => match err.kind { 431 | ErrorKind::NotFound => { 432 | let wrong_path = format!( 433 | "Path \"{}\" does not exist or you don't have \ 434 | access!", 435 | test_file.to_str().unwrap() 436 | ); 437 | 438 | assert_eq!(wrong_path, err.to_string()); 439 | () 440 | } 441 | _ => panic!("wrong error"), 442 | }, 443 | } 444 | } 445 | 446 | #[test] 447 | fn it_copy_with_progress_exist_overwrite() { 448 | let mut test_file = PathBuf::from(TEST_FOLDER); 449 | test_file.push("it_copy_with_progress_exist_overwrite"); 450 | let mut test_file_out = test_file.clone(); 451 | test_file.push("test.txt"); 452 | test_file_out.push("out"); 453 | test_file_out.push("test.txt"); 454 | fs_extra::dir::create_all(&test_file.parent().unwrap(), true).unwrap(); 455 | fs_extra::dir::create_all(&test_file_out.parent().unwrap(), true).unwrap(); 456 | 457 | write_all(&test_file, "test_data").unwrap(); 458 | let mut options = CopyOptions::new(); 459 | copy(&test_file, &test_file_out, &options).unwrap(); 460 | assert!(test_file.exists()); 461 | assert!(files_eq(&test_file, &test_file_out).unwrap()); 462 | options.overwrite = true; 463 | write_all(&test_file, "test_data2").unwrap(); 464 | let func_test = |process_info: TransitProcess| { 465 | println!("{}", process_info.total_bytes); 466 | }; 467 | match copy_with_progress(&test_file, &test_file_out, &options, func_test) { 468 | Ok(_) => { 469 | assert!(test_file.exists()); 470 | assert_eq!(read_to_string(test_file_out).unwrap(), "test_data2"); 471 | () 472 | } 473 | Err(err) => panic!(err.to_string()), 474 | } 475 | } 476 | 477 | #[test] 478 | fn it_copy_with_progress_exist_not_overwrite() { 479 | let mut test_file = PathBuf::from(TEST_FOLDER); 480 | test_file.push("it_copy_with_progress_exist_not_overwrite"); 481 | let mut test_file_out = test_file.clone(); 482 | test_file.push("test.txt"); 483 | test_file_out.push("out"); 484 | test_file_out.push("test.txt"); 485 | fs_extra::dir::create_all(&test_file.parent().unwrap(), true).unwrap(); 486 | fs_extra::dir::create_all(&test_file_out.parent().unwrap(), true).unwrap(); 487 | 488 | write_all(&test_file, "test_data").unwrap(); 489 | let mut options = CopyOptions::new(); 490 | copy(&test_file, &test_file_out, &options).unwrap(); 491 | assert!(test_file.exists()); 492 | options.overwrite = false; 493 | write_all(&test_file, "test_data2").unwrap(); 494 | let func_test = |process_info: TransitProcess| { 495 | println!("{}", process_info.total_bytes); 496 | }; 497 | match copy_with_progress(&test_file, &test_file_out, &options, func_test) { 498 | Ok(_) => panic!("should be error"), 499 | Err(err) => { 500 | let file_path = format!("Path \"{}\" exists", test_file_out.to_str().unwrap()); 501 | 502 | assert_eq!(file_path, err.to_string()); 503 | assert!(!files_eq(test_file, test_file_out).unwrap()); 504 | () 505 | } 506 | } 507 | } 508 | 509 | #[test] 510 | fn it_copy_with_progress_exist_skip_exist() { 511 | let mut test_file = PathBuf::from(TEST_FOLDER); 512 | test_file.push("it_copy_with_progress_exist_skip_exist"); 513 | let mut test_file_out = test_file.clone(); 514 | test_file.push("test.txt"); 515 | test_file_out.push("out"); 516 | test_file_out.push("test.txt"); 517 | fs_extra::dir::create_all(&test_file.parent().unwrap(), true).unwrap(); 518 | fs_extra::dir::create_all(&test_file_out.parent().unwrap(), true).unwrap(); 519 | 520 | write_all(&test_file, "test_data").unwrap(); 521 | let mut options = CopyOptions::new(); 522 | copy(&test_file, &test_file_out, &options).unwrap(); 523 | assert!(test_file.exists()); 524 | options.skip_exist = true; 525 | write_all(&test_file, "test_data2").unwrap(); 526 | let func_test = |process_info: TransitProcess| { 527 | println!("{}", process_info.total_bytes); 528 | }; 529 | match copy_with_progress(&test_file, &test_file_out, &options, func_test) { 530 | Ok(_) => { 531 | assert!(!files_eq(test_file, test_file_out).unwrap()); 532 | () 533 | } 534 | Err(_) => panic!("should be error"), 535 | } 536 | } 537 | 538 | #[test] 539 | fn it_copy_with_progress_exist_overwrite_and_skip_exist() { 540 | let mut test_file = PathBuf::from(TEST_FOLDER); 541 | test_file.push("it_copy_with_progress_exist_overwrite_and_skip_exist"); 542 | let mut test_file_out = test_file.clone(); 543 | test_file.push("test.txt"); 544 | test_file_out.push("out"); 545 | test_file_out.push("test.txt"); 546 | fs_extra::dir::create_all(&test_file.parent().unwrap(), true).unwrap(); 547 | fs_extra::dir::create_all(&test_file_out.parent().unwrap(), true).unwrap(); 548 | 549 | write_all(&test_file, "test_data").unwrap(); 550 | let mut options = CopyOptions::new(); 551 | copy(&test_file, &test_file_out, &options).unwrap(); 552 | assert!(test_file.exists()); 553 | assert!(files_eq(&test_file, &test_file_out).unwrap()); 554 | options.overwrite = true; 555 | options.skip_exist = true; 556 | write_all(&test_file, "test_data2").unwrap(); 557 | let func_test = |process_info: TransitProcess| { 558 | println!("{}", process_info.total_bytes); 559 | }; 560 | match copy_with_progress(&test_file, &test_file_out, &options, func_test) { 561 | Ok(_) => { 562 | assert!(test_file.exists()); 563 | assert_eq!(read_to_string(test_file_out).unwrap(), "test_data2"); 564 | () 565 | } 566 | Err(err) => panic!(err.to_string()), 567 | } 568 | } 569 | 570 | #[test] 571 | fn it_move_work() { 572 | let mut test_file = PathBuf::from(TEST_FOLDER); 573 | test_file.push("it_move_work"); 574 | let mut test_file_out = test_file.clone(); 575 | test_file.push("test.txt"); 576 | test_file_out.push("out"); 577 | test_file_out.push("test.txt"); 578 | fs_extra::dir::create_all(&test_file.parent().unwrap(), true).unwrap(); 579 | fs_extra::dir::create_all(&test_file_out.parent().unwrap(), true).unwrap(); 580 | 581 | write_all(&test_file, "test_data").unwrap(); 582 | assert!(test_file.exists()); 583 | assert!(!test_file_out.exists()); 584 | let options = CopyOptions::new(); 585 | let old_name = test_file.file_name(); 586 | let old_content = read_to_string(&test_file).unwrap(); 587 | move_file(&test_file, &test_file_out, &options).unwrap(); 588 | assert!(!test_file.exists()); 589 | assert!(test_file_out.exists()); 590 | assert_eq!(old_name, test_file_out.file_name()); 591 | let new_content = read_to_string(&test_file_out).unwrap(); 592 | assert_eq!(old_content, new_content); 593 | } 594 | 595 | #[test] 596 | fn it_move_not_file() { 597 | let mut test_file = PathBuf::from(TEST_FOLDER); 598 | test_file.push("it_move_work"); 599 | let mut test_file_out = test_file.clone(); 600 | test_file.push("test.txt"); 601 | test_file_out.push("out"); 602 | test_file_out.push("test.txt"); 603 | fs_extra::dir::create_all(&test_file.parent().unwrap(), true).unwrap(); 604 | fs_extra::dir::create_all(&test_file_out.parent().unwrap(), true).unwrap(); 605 | 606 | write_all(&test_file, "test_data").unwrap(); 607 | assert!(test_file.exists()); 608 | assert!(!test_file_out.exists()); 609 | test_file.pop(); 610 | let options = CopyOptions::new(); 611 | 612 | match move_file(&test_file, &test_file_out, &options) { 613 | Err(err) => match err.kind { 614 | ErrorKind::InvalidFile => { 615 | let wrong_path = format!("Path \"{}\" is not a file!", test_file.to_str().unwrap()); 616 | assert_eq!(wrong_path, err.to_string()); 617 | } 618 | _ => { 619 | panic!("wrong error"); 620 | } 621 | }, 622 | Ok(_) => { 623 | panic!("should be error"); 624 | } 625 | } 626 | } 627 | 628 | #[test] 629 | fn it_move_source_not_exist() { 630 | let mut test_file = PathBuf::from(TEST_FOLDER); 631 | test_file.push("it_move_source_not_exist"); 632 | let mut test_file_out = test_file.clone(); 633 | test_file.push("test1.txt"); 634 | test_file_out.push("out"); 635 | test_file_out.push("test.txt"); 636 | fs_extra::dir::create_all(&test_file.parent().unwrap(), true).unwrap(); 637 | fs_extra::dir::create_all(&test_file_out.parent().unwrap(), true).unwrap(); 638 | 639 | assert!(!test_file.exists()); 640 | let options = CopyOptions::new(); 641 | match move_file(&test_file, &test_file_out, &options) { 642 | Ok(_) => panic!("should be error"), 643 | Err(err) => match err.kind { 644 | ErrorKind::NotFound => { 645 | let wrong_path = format!( 646 | "Path \"{}\" does not exist or you don't have \ 647 | access!", 648 | test_file.to_str().unwrap() 649 | ); 650 | 651 | assert_eq!(wrong_path, err.to_string()); 652 | () 653 | } 654 | _ => panic!("wrong error"), 655 | }, 656 | } 657 | } 658 | 659 | #[test] 660 | fn it_move_exist_overwrite() { 661 | let mut test_file = PathBuf::from(TEST_FOLDER); 662 | test_file.push("it_move_exist_overwrite"); 663 | let mut test_file_out = test_file.clone(); 664 | test_file.push("test.txt"); 665 | test_file_out.push("out"); 666 | test_file_out.push("test.txt"); 667 | fs_extra::dir::create_all(&test_file.parent().unwrap(), true).unwrap(); 668 | fs_extra::dir::create_all(&test_file_out.parent().unwrap(), true).unwrap(); 669 | 670 | write_all(&test_file, "test_data").unwrap(); 671 | let mut options = CopyOptions::new(); 672 | copy(&test_file, &test_file_out, &options).unwrap(); 673 | assert!(test_file.exists()); 674 | assert!(files_eq(&test_file, &test_file_out).unwrap()); 675 | options.overwrite = true; 676 | write_all(&test_file, "test_data2").unwrap(); 677 | match move_file(&test_file, &test_file_out, &options) { 678 | Ok(_) => { 679 | assert!(!test_file.exists()); 680 | assert_eq!(read_to_string(test_file_out).unwrap(), "test_data2"); 681 | () 682 | } 683 | Err(err) => panic!(err.to_string()), 684 | } 685 | } 686 | 687 | #[test] 688 | fn it_move_exist_not_overwrite() { 689 | let mut test_file = PathBuf::from(TEST_FOLDER); 690 | test_file.push("it_move_exist_not_overwrite"); 691 | let mut test_file_out = test_file.clone(); 692 | test_file.push("test.txt"); 693 | test_file_out.push("out"); 694 | test_file_out.push("test.txt"); 695 | fs_extra::dir::create_all(&test_file.parent().unwrap(), true).unwrap(); 696 | fs_extra::dir::create_all(&test_file_out.parent().unwrap(), true).unwrap(); 697 | 698 | write_all(&test_file, "test_data").unwrap(); 699 | let mut options = CopyOptions::new(); 700 | copy(&test_file, &test_file_out, &options).unwrap(); 701 | assert!(test_file.exists()); 702 | options.overwrite = false; 703 | write_all(&test_file, "test_data2").unwrap(); 704 | match move_file(&test_file, &test_file_out, &options) { 705 | Ok(_) => panic!("should be error"), 706 | Err(err) => { 707 | let file_path = format!("Path \"{}\" exists", test_file_out.to_str().unwrap()); 708 | 709 | assert_eq!(file_path, err.to_string()); 710 | assert!(!files_eq(test_file, test_file_out).unwrap()); 711 | () 712 | } 713 | } 714 | } 715 | 716 | #[test] 717 | fn it_move_exist_skip_exist() { 718 | let mut test_file = PathBuf::from(TEST_FOLDER); 719 | test_file.push("it_move_exist_skip_exist"); 720 | let mut test_file_out = test_file.clone(); 721 | test_file.push("test.txt"); 722 | test_file_out.push("out"); 723 | test_file_out.push("test.txt"); 724 | fs_extra::dir::create_all(&test_file.parent().unwrap(), true).unwrap(); 725 | fs_extra::dir::create_all(&test_file_out.parent().unwrap(), true).unwrap(); 726 | 727 | write_all(&test_file, "test_data").unwrap(); 728 | let mut options = CopyOptions::new(); 729 | copy(&test_file, &test_file_out, &options).unwrap(); 730 | assert!(test_file.exists()); 731 | options.skip_exist = true; 732 | write_all(&test_file, "test_data2").unwrap(); 733 | match move_file(&test_file, &test_file_out, &options) { 734 | Ok(_) => { 735 | assert!(!files_eq(test_file, test_file_out).unwrap()); 736 | () 737 | } 738 | Err(_) => panic!("should be error"), 739 | } 740 | } 741 | 742 | #[test] 743 | fn it_move_exist_overwrite_and_skip_exist() { 744 | let mut test_file = PathBuf::from(TEST_FOLDER); 745 | test_file.push("it_move_exist_overwrite_and_skip_exist"); 746 | let mut test_file_out = test_file.clone(); 747 | test_file.push("test.txt"); 748 | test_file_out.push("out"); 749 | test_file_out.push("test.txt"); 750 | fs_extra::dir::create_all(&test_file.parent().unwrap(), true).unwrap(); 751 | fs_extra::dir::create_all(&test_file_out.parent().unwrap(), true).unwrap(); 752 | 753 | write_all(&test_file, "test_data").unwrap(); 754 | let mut options = CopyOptions::new(); 755 | copy(&test_file, &test_file_out, &options).unwrap(); 756 | assert!(test_file.exists()); 757 | assert!(files_eq(&test_file, &test_file_out).unwrap()); 758 | options.overwrite = true; 759 | options.skip_exist = true; 760 | write_all(&test_file, "test_data2").unwrap(); 761 | match move_file(&test_file, &test_file_out, &options) { 762 | Ok(_) => { 763 | assert!(!test_file.exists()); 764 | assert_eq!(read_to_string(test_file_out).unwrap(), "test_data2"); 765 | () 766 | } 767 | Err(err) => panic!(err.to_string()), 768 | } 769 | } 770 | 771 | #[test] 772 | fn it_move_with_progress_work() { 773 | let mut test_file = PathBuf::from(TEST_FOLDER); 774 | test_file.push("it_move_with_progress_work"); 775 | let mut test_file_out = test_file.clone(); 776 | test_file.push("test.txt"); 777 | test_file_out.push("out"); 778 | test_file_out.push("test.txt"); 779 | fs_extra::dir::create_all(&test_file.parent().unwrap(), true).unwrap(); 780 | fs_extra::dir::create_all(&test_file_out.parent().unwrap(), true).unwrap(); 781 | 782 | write_all(&test_file, "test_data").unwrap(); 783 | assert!(test_file.exists()); 784 | assert!(!test_file_out.exists()); 785 | let mut options = CopyOptions::new(); 786 | options.buffer_size = 1; 787 | let (tx, rx) = mpsc::channel(); 788 | thread::spawn(move || { 789 | let old_name = test_file.file_name(); 790 | let old_content = read_to_string(&test_file).unwrap(); 791 | let func_test = |process_info: TransitProcess| { 792 | tx.send(process_info).unwrap(); 793 | }; 794 | move_file_with_progress(&test_file, &test_file_out, &options, func_test).unwrap(); 795 | assert!(!test_file.exists()); 796 | assert!(test_file_out.exists()); 797 | assert_eq!(old_name, test_file_out.file_name()); 798 | let new_content = read_to_string(&test_file_out).unwrap(); 799 | assert_eq!(old_content, new_content); 800 | }); 801 | for i in 1..10 { 802 | let process_info: TransitProcess = rx.recv().unwrap(); 803 | assert_eq!(i, process_info.copied_bytes); 804 | assert_eq!(9, process_info.total_bytes); 805 | } 806 | } 807 | 808 | #[test] 809 | fn it_move_progress_not_file() { 810 | let mut test_file = PathBuf::from(TEST_FOLDER); 811 | test_file.push("it_move_progress_not_file"); 812 | let mut test_file_out = test_file.clone(); 813 | test_file.push("test.txt"); 814 | test_file_out.push("out"); 815 | test_file_out.push("test.txt"); 816 | fs_extra::dir::create_all(&test_file.parent().unwrap(), true).unwrap(); 817 | fs_extra::dir::create_all(&test_file_out.parent().unwrap(), true).unwrap(); 818 | 819 | write_all(&test_file, "test_data").unwrap(); 820 | assert!(test_file.exists()); 821 | assert!(!test_file_out.exists()); 822 | test_file.pop(); 823 | let options = CopyOptions::new(); 824 | let func_test = |process_info: TransitProcess| println!("{}", process_info.total_bytes); 825 | 826 | match move_file_with_progress(&test_file, &test_file_out, &options, func_test) { 827 | Err(err) => match err.kind { 828 | ErrorKind::InvalidFile => { 829 | let wrong_path = format!("Path \"{}\" is not a file!", test_file.to_str().unwrap()); 830 | assert_eq!(wrong_path, err.to_string()); 831 | } 832 | _ => { 833 | panic!("wrong error"); 834 | } 835 | }, 836 | Ok(_) => { 837 | panic!("should be error"); 838 | } 839 | } 840 | } 841 | 842 | #[test] 843 | fn it_move_with_progress_work_dif_buf_size() { 844 | let mut test_file = PathBuf::from(TEST_FOLDER); 845 | test_file.push("it_move_with_progress_work_dif_buf_size"); 846 | let mut test_file_out = test_file.clone(); 847 | test_file.push("test.txt"); 848 | test_file_out.push("out"); 849 | test_file_out.push("test.txt"); 850 | fs_extra::dir::create_all(&test_file.parent().unwrap(), true).unwrap(); 851 | fs_extra::dir::create_all(&test_file_out.parent().unwrap(), true).unwrap(); 852 | 853 | write_all(&test_file, "test_data_").unwrap(); 854 | assert!(test_file.exists()); 855 | assert!(!test_file_out.exists()); 856 | let mut options = CopyOptions::new(); 857 | options.buffer_size = 2; 858 | let (tx, rx) = mpsc::channel(); 859 | thread::spawn(move || { 860 | let old_name = test_file.file_name(); 861 | let old_content = read_to_string(&test_file).unwrap(); 862 | let func_test = |process_info: TransitProcess| { 863 | tx.send(process_info).unwrap(); 864 | }; 865 | move_file_with_progress(&test_file, &test_file_out, &options, func_test).unwrap(); 866 | assert!(!test_file.exists()); 867 | assert!(test_file_out.exists()); 868 | assert_eq!(old_name, test_file_out.file_name()); 869 | let new_content = read_to_string(&test_file_out).unwrap(); 870 | assert_eq!(old_content, new_content); 871 | }); 872 | for i in 1..6 { 873 | let process_info: TransitProcess = rx.recv().unwrap(); 874 | assert_eq!(i * 2, process_info.copied_bytes); 875 | assert_eq!(10, process_info.total_bytes); 876 | } 877 | } 878 | 879 | #[test] 880 | fn it_move_with_progress_source_not_exist() { 881 | let mut test_file = PathBuf::from(TEST_FOLDER); 882 | test_file.push("it_move_with_progress_source_not_exist"); 883 | let mut test_file_out = test_file.clone(); 884 | test_file.push("test1.txt"); 885 | test_file_out.push("out"); 886 | test_file_out.push("test.txt"); 887 | fs_extra::dir::create_all(&test_file.parent().unwrap(), true).unwrap(); 888 | fs_extra::dir::create_all(&test_file_out.parent().unwrap(), true).unwrap(); 889 | 890 | assert!(!test_file.exists()); 891 | let options = CopyOptions::new(); 892 | let func_test = |process_info: TransitProcess| { 893 | println!("{}", process_info.total_bytes); 894 | }; 895 | match move_file_with_progress(&test_file, &test_file_out, &options, func_test) { 896 | Ok(_) => panic!("should be error"), 897 | Err(err) => match err.kind { 898 | ErrorKind::NotFound => { 899 | let wrong_path = format!( 900 | "Path \"{}\" does not exist or you don't have \ 901 | access!", 902 | test_file.to_str().unwrap() 903 | ); 904 | 905 | assert_eq!(wrong_path, err.to_string()); 906 | () 907 | } 908 | _ => panic!("wrong error"), 909 | }, 910 | } 911 | } 912 | 913 | #[test] 914 | fn it_move_with_progress_exist_overwrite() { 915 | let mut test_file = PathBuf::from(TEST_FOLDER); 916 | test_file.push("it_move_with_progress_exist_overwrite"); 917 | let mut test_file_out = test_file.clone(); 918 | test_file.push("test.txt"); 919 | test_file_out.push("out"); 920 | test_file_out.push("test.txt"); 921 | fs_extra::dir::create_all(&test_file.parent().unwrap(), true).unwrap(); 922 | fs_extra::dir::create_all(&test_file_out.parent().unwrap(), true).unwrap(); 923 | 924 | write_all(&test_file, "test_data").unwrap(); 925 | let mut options = CopyOptions::new(); 926 | copy(&test_file, &test_file_out, &options).unwrap(); 927 | assert!(test_file.exists()); 928 | assert!(files_eq(&test_file, &test_file_out).unwrap()); 929 | options.overwrite = true; 930 | write_all(&test_file, "test_data2").unwrap(); 931 | let func_test = |process_info: TransitProcess| { 932 | println!("{}", process_info.total_bytes); 933 | }; 934 | match move_file_with_progress(&test_file, &test_file_out, &options, func_test) { 935 | Ok(_) => { 936 | assert!(!test_file.exists()); 937 | assert_eq!(read_to_string(test_file_out).unwrap(), "test_data2"); 938 | () 939 | } 940 | Err(err) => panic!(err.to_string()), 941 | } 942 | } 943 | 944 | #[test] 945 | fn it_move_with_progress_exist_not_overwrite() { 946 | let mut test_file = PathBuf::from(TEST_FOLDER); 947 | test_file.push("it_move_with_progress_exist_not_overwrite"); 948 | let mut test_file_out = test_file.clone(); 949 | test_file.push("test.txt"); 950 | test_file_out.push("out"); 951 | test_file_out.push("test.txt"); 952 | fs_extra::dir::create_all(&test_file.parent().unwrap(), true).unwrap(); 953 | fs_extra::dir::create_all(&test_file_out.parent().unwrap(), true).unwrap(); 954 | 955 | write_all(&test_file, "test_data").unwrap(); 956 | let mut options = CopyOptions::new(); 957 | copy(&test_file, &test_file_out, &options).unwrap(); 958 | assert!(test_file.exists()); 959 | options.overwrite = false; 960 | write_all(&test_file, "test_data2").unwrap(); 961 | let func_test = |process_info: TransitProcess| { 962 | println!("{}", process_info.total_bytes); 963 | }; 964 | match move_file_with_progress(&test_file, &test_file_out, &options, func_test) { 965 | Ok(_) => panic!("should be error"), 966 | Err(err) => { 967 | let file_path = format!("Path \"{}\" exists", test_file_out.to_str().unwrap()); 968 | 969 | assert_eq!(file_path, err.to_string()); 970 | assert!(!files_eq(test_file, test_file_out).unwrap()); 971 | () 972 | } 973 | } 974 | } 975 | 976 | #[test] 977 | fn it_move_with_progress_exist_skip_exist() { 978 | let mut test_file = PathBuf::from(TEST_FOLDER); 979 | test_file.push("it_move_with_progress_exist_skip_exist"); 980 | let mut test_file_out = test_file.clone(); 981 | test_file.push("test.txt"); 982 | test_file_out.push("out"); 983 | test_file_out.push("test.txt"); 984 | fs_extra::dir::create_all(&test_file.parent().unwrap(), true).unwrap(); 985 | fs_extra::dir::create_all(&test_file_out.parent().unwrap(), true).unwrap(); 986 | 987 | write_all(&test_file, "test_data").unwrap(); 988 | let mut options = CopyOptions::new(); 989 | copy(&test_file, &test_file_out, &options).unwrap(); 990 | assert!(test_file.exists()); 991 | options.skip_exist = true; 992 | write_all(&test_file, "test_data2").unwrap(); 993 | let func_test = |process_info: TransitProcess| { 994 | println!("{}", process_info.total_bytes); 995 | }; 996 | match move_file_with_progress(&test_file, &test_file_out, &options, func_test) { 997 | Ok(_) => { 998 | assert!(!files_eq(test_file, test_file_out).unwrap()); 999 | () 1000 | } 1001 | Err(_) => panic!("should be error"), 1002 | } 1003 | } 1004 | 1005 | #[test] 1006 | fn it_move_with_progress_exist_overwrite_and_skip_exist() { 1007 | let mut test_file = PathBuf::from(TEST_FOLDER); 1008 | test_file.push("it_move_with_progress_exist_overwrite_and_skip_exist"); 1009 | let mut test_file_out = test_file.clone(); 1010 | test_file.push("test.txt"); 1011 | test_file_out.push("out"); 1012 | test_file_out.push("test.txt"); 1013 | fs_extra::dir::create_all(&test_file.parent().unwrap(), true).unwrap(); 1014 | fs_extra::dir::create_all(&test_file_out.parent().unwrap(), true).unwrap(); 1015 | 1016 | write_all(&test_file, "test_data").unwrap(); 1017 | let mut options = CopyOptions::new(); 1018 | copy(&test_file, &test_file_out, &options).unwrap(); 1019 | assert!(test_file.exists()); 1020 | assert!(files_eq(&test_file, &test_file_out).unwrap()); 1021 | options.overwrite = true; 1022 | options.skip_exist = true; 1023 | write_all(&test_file, "test_data2").unwrap(); 1024 | let func_test = |process_info: TransitProcess| { 1025 | println!("{}", process_info.total_bytes); 1026 | }; 1027 | match move_file_with_progress(&test_file, &test_file_out, &options, func_test) { 1028 | Ok(_) => { 1029 | assert!(!test_file.exists()); 1030 | assert!(test_file_out.exists()); 1031 | assert_eq!(read_to_string(test_file_out).unwrap(), "test_data2"); 1032 | () 1033 | } 1034 | Err(err) => panic!(err.to_string()), 1035 | } 1036 | } 1037 | -------------------------------------------------------------------------------- /tests/temp/dir/empty: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/webdesus/fs_extra/1754296075e7cc4a25feaa876a3f4b9daccc0b98/tests/temp/dir/empty -------------------------------------------------------------------------------- /tests/temp/file/empty: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/webdesus/fs_extra/1754296075e7cc4a25feaa876a3f4b9daccc0b98/tests/temp/file/empty -------------------------------------------------------------------------------- /tests/temp/lib/empty: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/webdesus/fs_extra/1754296075e7cc4a25feaa876a3f4b9daccc0b98/tests/temp/lib/empty --------------------------------------------------------------------------------