How to read specific file from zip file - rust

I'm totally stuck reading a file from a variable path structure of a zip file without decompressing it.
My file is located here:
/info/[random-string]/info.json
Where [random-string] is the only file in the info folder.
So its like read the 'info' folder read the first folder read the 'info.json'.
Any ideas how to do that with one of these libraries (zip or rc_zip)?
let file_path = file.to_str().unwrap();
let file = File::open(file_path).unwrap();
let reader = BufReader::new(file);
let mut archive = zip::ZipArchive::new(reader).unwrap();
let info_folder = archive.by_name("info").unwrap();
// how to list files of info_folder

Here you are:
use std::error::Error;
use std::ffi::OsStr;
use std::fs::File;
use std::path::Path;
use zip::ZipArchive; // zip 0.5.13
fn main() -> Result<(), Box<dyn Error>> {
let archive = File::open("./info.zip")?;
let mut archive = ZipArchive::new(archive)?;
// iterate over all files, because you don't know the exact name
for idx in 0..archive.len() {
let entry = archive.by_index(idx)?;
let name = entry.enclosed_name();
if let Some(name) = name {
// process only entries which are named info.json
if name.file_name() == Some(OsStr::new("info.json")) {
// the ancestors() iterator lets you walk up the path segments
let mut ancestors = name.ancestors();
// skip self - the first entry is always the full path
ancestors.next();
// skip the random string
ancestors.next();
let expect_info = ancestors.next();
// the reminder must be only 'info/' otherwise this is the wrong entry
if expect_info == Some(Path::new("info/")) {
// do something with the file
println!("Found!!!");
break;
}
}
}
}
Ok(())
}

Related

Rust zip file corruption

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.

How to diagnose ownership errors and fix them?

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.

How to get `__dirname` like nodejs using rust [duplicate]

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?

Reading all file contents in current directory to a vector

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();

How do I use include_str! for multiple files or an entire directory?

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.

Resources