I have to zip one folder that has more folders and more files in it, this is the code I have, and its weird, because when I open the .zip file with Winrar or File Explorer, it seems to be a perfectly working .zip file, but when I open the file with 7zip, it appears to be corrupted.
This is the code:
fn walk_dir(path: &str, zip: &mut ZipWriter<File>, options: FileOptions, path_cache: &str) -> io::Result<()> {
let entries = fs::read_dir(path)?;
for entry in entries {
let entry = entry?;
let entry_path = entry.path();
let metadata = fs::metadata(&entry_path)?;
let entry_actual_path = Path::new(path_cache).join(entry.file_name().to_str().unwrap());
let entry_path_str = entry_path.as_path().to_str().unwrap();
let entry_actual_path_str = entry_actual_path.as_path().to_str().unwrap();
if metadata.is_dir() {
zip.add_directory(entry_actual_path_str, options)?;
walk_dir(entry_path_str, zip, options, entry_actual_path_str)?;
} else {
zip.start_file(entry_actual_path_str, options)?;
let mut file = File::open(&entry_path)?;
let mut buffer = BUFFER.lock().unwrap();
buffer.clear();
file.read_to_end(&mut buffer)?;
zip.write_all(&buffer)?;
}
}
Ok(())
}
I found out that instead of / or \ in the paths, there are this characters: .
Here are some screenshots:
I'm just trying to create a zip file that works well, but it just wont. I also tried with different compression methods, but none of those works.
I just wanted to read file contents from a user picked file and save them to a var.
Unfortunately I am getting Ownership Errors, but I dont know how to pass a mutable Reference to the function I'm calling in the following code:
use tauri::api::dialog::FileDialogBuilder;
#[tauri::command]
fn import_file() -> String {
let mut file_contents = String::new();
let mut file_name = String::new();
FileDialogBuilder::new()
.set_title("Select a file to import")
.pick_file(|file_path| {
// do something with the optional file path here
// the file path is `None` if the user closed the dialog
if let Some(file_path) = file_path {
println!("{}", file_path.to_string_lossy().into_owned());
let contents = std::fs::read_to_string(file_path).unwrap();
file_contents = contents;
}
});
println!("{}", file_contents);
return String::from("yoo");
}
I tried wrapping the expression in a block and assigned it to the var like
file_contents = { FileDialogBuilder::new()...
but got errors again.
I tried to cargo-expand the main.rs but got unreadable binary code. Seems to come from tauri.
I want to read files from a config folder at the directory where the executable is located. I do that using the following functions:
use std::env;
// add part of path to te path gotten from fn get_exe_path();
fn get_file_path(path_to_file: &str) -> PathBuf {
let final_path = match get_exe_path() {
Ok(mut path) => {
path.push(path_to_file);
path
}
Err(err) => panic!("Path does not exists"),
};
final_path
}
// Get path to current executable
fn get_exe_path() -> Result<PathBuf, io::Error> {
//std::env::current_exe()
env::current_exe()
}
In my case, get_exe_path() will return C:\Users\User\Documents\Rust\Hangman\target\debug\Hangman.exe.
With get_file_path("Config\test.txt"), I want to append Config\test.txt To the above path. Then I get the following path to the file: C:\Users\User\Documents\Rust\Hangman\target\debug\Hangman.exe\Config\test.txt
The problem is that std::env::current_exe() will get the file name of the executable also and I do not need that. I only need the directory where it is located.
Question
The following the following function call should return C:\Users\User\Documents\Rust\Hangman\target\debug\Config\test.txt:
let path = get_file_path("Config\\test.txt");
How can I get the path from the current directory without the executable name like above example? Are there any other ways to do this than using std::env::current_exe()
PathBuf::pop is the mirror of PathBuf::push:
Truncates self to self.parent.
Returns false and does nothing if self.file_name is None. Otherwise,
returns true.
In your case:
use std::env;
use std::io;
use std::path::PathBuf;
fn inner_main() -> io::Result<PathBuf> {
let mut dir = env::current_exe()?;
dir.pop();
dir.push("Config");
dir.push("test.txt");
Ok(dir)
}
fn main() {
let path = inner_main().expect("Couldn't");
println!("{}", path.display());
}
There's also the possibility of using Path::parent:
Returns the Path without its final component, if there is one.
Returns None if the path terminates in a root or prefix.
In your case:
fn inner_main() -> io::Result<PathBuf> {
let exe = env::current_exe()?;
let dir = exe.parent().expect("Executable must be in some directory");
let mut dir = dir.join("Config");
dir.push("test.txt");
Ok(dir)
}
See also:
How to get the name of current program without the directory part?
I want to read all the files in the current directory.
Here's my progress:
use std::fs;
fn main() {
let files = fs::read_dir(".").unwrap();
files
.filter_map(Result::ok)
.filter(|d| if let Some(e) = d.path().extension() { e == "txt" } else {false})
.for_each(|f| println!("{:?}", f));
}
Here I got a little lost, how can I read all file contents? Should I add them to a growing Vec in the for_each block? if so then how?
If you want a single vec with all files bytes in one you can use
let target_ext = OsString::from("txt");
let files = fs::read_dir(".").unwrap();
let file_bytes : Vec<u8> = files
.filter_map(Result::ok)
.map(|d| d.path())
.filter(|path| path.extension() == Some(&target_ext))
.flat_map(|path| fs::read(path).expect("Failed to read"))
.collect();
if you want a vec that contains each file's content separately, change flat_map to a map and it will return a Vec<Vec<u8>>
let file_bytes : Vec<Vec<u8>> = files
.filter_map(Result::ok)
.map(|d| d.path())
.filter(|path| path.extension() == Some(&target_ext))
.map(|path| fs::read(path).expect("Failed to read"))
.collect();
I would like to copy an entire directory to a location in a user's $HOME. Individually copying files to that directory is straightforward:
let contents = include_str!("resources/profiles/default.json");
let fpath = dpath.join(&fname);
fs::write(fpath, contents).expect(&format!("failed to create profile: {}", n));
I haven't found a way to adapt this to multiple files:
for n in ["default"] {
let fname = format!("{}{}", n, ".json");
let x = format!("resources/profiles/{}", fname).as_str();
let contents = include_str!(x);
let fpath = dpath.join(&fname);
fs::write(fpath, contents).expect(&format!("failed to create profile: {}", n));
}
...the compiler complains that x must be a string literal.
As far as I know, there are two options:
Write a custom macro.
Replicate the first code for each file I want to copy.
What is the best way of doing this?
I would create a build script that iterates through a directory, building up an array of tuples containing the name and another macro call to include the raw data:
use std::{
env,
error::Error,
fs::{self, File},
io::Write,
path::Path,
};
const SOURCE_DIR: &str = "some/path/to/include";
fn main() -> Result<(), Box<dyn Error>> {
let out_dir = env::var("OUT_DIR")?;
let dest_path = Path::new(&out_dir).join("all_the_files.rs");
let mut all_the_files = File::create(&dest_path)?;
writeln!(&mut all_the_files, r##"["##,)?;
for f in fs::read_dir(SOURCE_DIR)? {
let f = f?;
if !f.file_type()?.is_file() {
continue;
}
writeln!(
&mut all_the_files,
r##"("{name}", include_bytes!(r#"{name}"#)),"##,
name = f.path().display(),
)?;
}
writeln!(&mut all_the_files, r##"]"##,)?;
Ok(())
}
This has some weaknesses, namely that it requires the path to be expressible as a &str. Since you were already using include_string!, I don't think that's an extra requirement. This also means that the generated string has to be a valid Rust string. We use raw strings inside the generated file, but this can still fail if a filename were to contain the string "#. A better solution would probably use str::escape_default.
Since we are including files, I used include_bytes! instead of include_str!, but if you really needed to you can switch back. The raw bytes skips performing UTF-8 validation at compile time, so it's a small win.
Using it involves importing the generated value:
const ALL_THE_FILES: &[(&str, &[u8])] = &include!(concat!(env!("OUT_DIR"), "/all_the_files.rs"));
fn main() {
for (name, data) in ALL_THE_FILES {
println!("File {} is {} bytes", name, data.len());
}
}
See also:
How can I locate resources for testing with Cargo?
You can use include_dir macro.
use include_dir::{include_dir, Dir};
use std::path::Path;
const PROJECT_DIR: Dir = include_dir!(".");
// of course, you can retrieve a file by its full path
let lib_rs = PROJECT_DIR.get_file("src/lib.rs").unwrap();
// you can also inspect the file's contents
let body = lib_rs.contents_utf8().unwrap();
assert!(body.contains("SOME_INTERESTING_STRING"));
Using a macro:
macro_rules! incl_profiles {
( $( $x:expr ),* ) => {
{
let mut profs = Vec::new();
$(
profs.push(($x, include_str!(concat!("resources/profiles/", $x, ".json"))));
)*
profs
}
};
}
...
let prof_tups: Vec<(&str, &str)> = incl_profiles!("default", "python");
for (prof_name, prof_str) in prof_tups {
let fname = format!("{}{}", prof_name, ".json");
let fpath = dpath.join(&fname);
fs::write(fpath, prof_str).expect(&format!("failed to create profile: {}", prof_name));
}
Note: This is not dynamic. The files ("default" and "python") are specified in the call to the macro.
Updated: Use Vec instead of HashMap.