Get .lnk target in rust with lnk crate? - rust

I need to get lnk file target and then then push it to Vec as a string. How can I do it?
Here is code that I`ve tried:
use lnk::ShellLink;
let contents = fs::read_dir(path).unwrap();
for entry in contents {
let entry = entry.unwrap();
output.push(
ShellLink::open(entry.path().display().to_string())
.unwrap()
.relative_path()
.clone()
.unwrap()
.to_string(),
);
}

Related

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.

Why is .to_string() method needed when opening the path given to the program from its user's input?

The following code opens a file:
use std::fs;
use std::io;
fn main() {
println!("Give me the absolute path to the file you want to read.");
let mut to_read = String::new();
io::stdin().read_line(&mut to_read).expect("Failed to read line");
to_read = to_read.trim_end().to_string();
let contents = fs::read_to_string(to_read).expect("Something went wrong reading the file");
println!("{}", contents.trim() );
}
From what I've read about .to_string() it converts the given value to a String.
What confuses me is that in my code the given value, i.e. the value assigned to the variable to_read is already a String
at the moment of its assignment: let mut to_read = String::new();. I discovered through
tinkering, that the line to_read = to_read.trim_end().to_string(); is necessary for my code to work, otherwise Rust panics with the message:
'Something went wrong reading the file: Os { code: 2, kind: NotFound, message: "No such file or directory" }
Why is it so?
trim_end returns a &str: a slice, that is a reference over a portion of the initial string.
So if you do
to_read = to_read.trim_end()
then you're trying to assign a &str to a variable of type String.
The solution you took was to build a new String from the &str with to_string(). While this works, it's uselessly expensive as your don't need a String later in read_to_string.
A better solution would be to keep the &str, in a new variable, which can have the same name:
use std::fs;
use std::io;
fn main() {
println!("Give me the absolute path to the file you want to read.");
let mut to_read = String::new();
io::stdin().read_line(&mut to_read).expect("Failed to read line");
let to_read = to_read.trim_end(); // same name but new variable
let contents = fs::read_to_string(to_read).expect("Something went wrong reading the file");
println!("{}", contents.trim() );
}

Creates a temporary which is freed while still in use

I'm creating a small application that explores variable lifetimes and threads. I want to load in a file once, and then use its contents (in this case an audio file) in a separate channel. I am having issues with value lifetimes.
I'm almost certain the syntax is wrong for what I have so far (for creating a static variable), but I can't find any resources for File types and lifetimes. What I have thus far produces this error:
let file = &File::open("src/censor-beep-01.wav").unwrap();
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ creates a temporary which is freed while still in use
let x: &'static File = file;
------------- type annotation requires that borrow lasts for `'static`
The code I currently have is:
#![allow(dead_code)]
#![allow(unused_imports)]
#![allow(unused_must_use)]
#![allow(unused_variables)]
use std::io::{self, BufRead, BufReader, stdin, Read};
use std::sync::mpsc::{self, TryRecvError};
use std::thread;
use std::time::Duration;
use std::fs::File;
use std::rc::Rc;
use rodio::Source;
fn main() {
let file = &File::open("src/censor-beep-01.wav").unwrap();
let x: &'static File = file;
loop {
let (tx, rx) = mpsc::channel();
thread::spawn(move || loop {
let tmp = x;
let (stream, stream_handle) = rodio::OutputStream::try_default().unwrap();
let source = rodio::Decoder::new(BufReader::new(tmp)).unwrap();
stream_handle.play_raw(source.convert_samples());
match rx.try_recv() {
Ok(_) | Err(TryRecvError::Disconnected) => {
break;
}
Err(TryRecvError::Empty) => {
println!("z");
thread::sleep(Duration::from_millis(1000));
}
}
});
let mut line = String::new();
let stdin = io::stdin();
let _ = stdin.lock().read_line(&mut line);
let _ = tx.send(());
return;
}
}
You need to wrap the file with Arc and Mutex like Arc::new(Mutex::new(file)) and then clone the file before passing it to the thread.
Arc is used for reference counting, which is needed to share the target object (in your case it is a file) across the thread and Mutex is needed to access the target object synchronously.
sample code (I have simplified your code to make it more understandable):
let file = Arc::new(Mutex::new(File::open("src/censor-beep-01.wav").unwrap()));
loop {
let file = file.clone();
thread::spawn(move || loop {
let mut file_guard = match file.lock() {
Ok(guard) => guard,
Err(poison) => poison.into_inner()
};
let file = file_guard.deref();
// now you can pass above file object to BufReader like "BufReader::new(file)"
});
}
reason for creates a temporary which is freed while still in use error:
You have only stored the reference of the file without the actual file object. so, the object will be droped in that line itself.

String concatenation in rust

I am trying to get a &str and &str to concatenate in a for loop withe intention of using the new combined string after a number of parts have been added to it. A general layout of the for loop can be seen below but I am having a lot of trouble combining strings due to numerous errors.
for line in reader.lines() {
let split_line = line.unwrap().split(",");
let mut edited_line = "";
for word in split_line {
if !word.contains("substring") {
let test_string = [edited_line, word].join(",");
edited_line = &test_string;
}
}
let _ = writeln!(outfile, "{}", edited_line).expect("Unable to write to file");
}
First error:
error[E0716]: temporary value dropped while borrowed
Comes when running the above.
Second error:
error[E0308]: mismatched types expected &str, found struct std::string::String
happens when you remove the & from test_string when it is assigned to edited_line
Note: format! and concat! macros both also give error 2.
It seems to be if I get error 2 and convert the std::string:String and convert it to &str I get the error stating the variables don't live long enough.
How am I supposed to go about building a string of many parts?
Note that Rust has two string types, String and &str (actually, there are more, but that's irrelevant here).
String is an owned string and can grow and shrink dynamically.
&str is a borrowed string and is immutable.
Calling [edited_line, word].join(",") creates a new String, which is allocated on the heap. edited_line = &test_string then borrows the String and implicitly converts it to a &str.
The problem is that its memory is freed as soon as the owner (test_string) goes out of scope, but the borrow lives longer than test_string. This is fundamentally impossible in Rust, since it would otherwise be a use-after-free bug.
The correct and most efficient way to do this is to create an empty String outside of the loop and only append to it in the loop:
let mut edited_line = String::new();
for word in split_line {
if !word.contains("substring") {
edited_line.push(',');
edited_line.push_str(word);
}
}
Note that the resulting string will start with a comma, which might not be desired. To avoid it, you can write
let mut edited_line = String::new();
for word in split_line {
if !word.contains("substring") {
if !edited_line.is_empty() {
edited_line.push(',');
}
edited_line.push_str(word);
}
}
This could be done more elegantly with the itertools crate, which provides a join method for iterators:
use itertools::Itertools;
let edited_line: String = line
.unwrap()
.split(",")
.filter(|word| !word.contains("substring"))
.join(",");
let mut edited_line = ""; makes edited_line a &str with a static lifetime.
To actually make edited_line a string, either append .to_owned(), or use String::new():
let mut edited_line = String::new();
// Or
let mut edited_line = "".to_owned();
See What are the differences between Rust's `String` and `str`? if you are unfamiliar with the differences.
Most importantly for your case, you can't extend a &str, but you can extend a String.
Once you switched edited_line to a String, using the method of setting edited_line to [edited_line, word].join(","); works:
for line in reader.lines() {
let split_line = line.unwrap().split(",");
let mut edited_line = String::new();
for word in split_line {
if !word.contains("substring") {
let test_string = [edited_line.as_str(), word].join(","); // Added .as_str() to edited_line
edited_line = test_string; // Removed the & here
}
}
let _ = writeln!(outfile, "{}", edited_line).expect("Unable to write to file");
}
Playground
However, this is both not very efficient, nor ergonomic. Also it has the (probably unintended) result of prepending each line with a ,.
Here is an alternative that uses only one String instance:
for line in reader.lines() {
let split_line = line.unwrap().split(",");
let mut edited_line = String::new();
for word in split_line {
if !word.contains("substring") {
edited_line.push(',');
edited_line.push_str(word);
}
}
let _ = writeln!(outfile, "{}", edited_line).expect("Unable to write to file");
}
This still prepends the , character before each line however. You can probably fix that by checking if edited_line is not empty before pushing the ,.
Playground
The third option is to change the for loop into an iterator:
for line in reader.lines() {
let edited_line = line.split(",")
.filter(|word| !word.contains("substring"))
.collect::<Vec<&str>>() // Collecting allows us to use the join function.
.join(",");
let _ = writeln!(outfile, "{}", edited_line).expect("Unable to write to file");
}
Playground
This way we can use the join function as intended, neatly eliminating the initial , at the start of each line.
PS: If you have trouble knowing what types each variable is, I suggest using an IDE like Intellij-rust, which shows type hints for each variable as you write them.

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