How to wait until a file is created in Rust? - file

The file may or may not be created before my program starts, so I need to ensure that this file exists before proceeding. What is the most idiomatic way to do that?

Taking into account suggestions from comments I've written the following code:
fn wait_until_file_created(file_path: &PathBuf) -> Result<(), Box<Error>> {
let (tx, rx) = mpsc::channel();
let mut watcher = notify::raw_watcher(tx)?;
// Watcher can't be registered for file that don't exists.
// I use its parent directory instead, because I'm sure that it always exists
let file_dir = file_path.parent().unwrap();
watcher.watch(&file_dir, RecursiveMode::NonRecursive)?;
if !file_path.exists() {
loop {
match rx.recv_timeout(Duration::from_secs(2))? {
RawEvent { path: Some(p), op: Ok(op::CREATE), .. } =>
if p == file_path {
break
},
_ => continue,
}
}
}
watcher.unwatch(file_dir)?;
Ok(())
}

Related

Render thousands/millions times with React and Tauri (rust)

I am learning Rust. I am creating a desktop app which read thousand/million rows of data in csv file and then transfer them one by one using tauri event.
Result: Rust has no problem read through the file (under 5 seconds). On the frontend side, my React app seems unable to keep up with events. On the screen, the altitude value is updated intermittently.
How to handle this situation is React? or What did I do wrong?
React side:
// App.js
import { listen } from '#tauri-apps/api/event';
import { useEffect, useCallback, useState } from 'react';
import { invoke } from '#tauri-apps/api/tauri'
const App = () => {
const [altitude, setAltitude] = useState("0");
useEffect(() => {
listen('rust-event', myCallback)
}, [])
const myCallback = useCallback((e) => {
console.log(e);
setAltitude(e.payload);
},[])
const handleClick = async () => {
invoke('my_custom_command').catch(error => console.log(error));
};
return (
<div>
<button onClick={handleClick}>Click Me To Start Fetching!</button>
<span>{altitude}</span>
</div>
);
}
export default App;
Tauri side:
// main.rs
use arrow::csv;
use arrow::datatypes::{DataType, Field, Schema};
use std::fs::File;
use std::sync::Arc;
use arrow::array::{StringArray, ArrayRef};
#[tauri::command]
async fn my_custom_command(window: tauri::Window) {
let schema = Schema::new(vec![
Field::new("altitude", DataType::Utf8, false)
]);
// Open file
let file = File::open("src/data.csv").unwrap();
// Get csv Reader using schema
let mut csv = csv::Reader::new(file, Arc::new(schema), true, None, 1, None, None);
// Loop through each row
while let Some(m) = csv.next() {
let n = m.unwrap();
// Get reference of array of a column
let col: &ArrayRef = n.column(0);
// Cast the reference of array to array of string
let col = col.as_any().downcast_ref::<StringArray>().unwrap();
// Get value from the array using index
let v = col.value(0);
println!("{}", col.value(0));
// Send each value through an event
window
.emit("rust-event", v)
.expect("failed to emit");
}
}
fn main() {
tauri::Builder::default()
.invoke_handler(tauri::generate_handler![my_custom_command])
.run(tauri::generate_context!())
.expect("failed to run app");
}
I personally do not suggest what you are doing as you can get a stack overflow.
The best would be to emit them in batch, you can fill a local buffer, when there are X elements (or the end is reached), emit the event, and clear the buffer.
Example
#[tauri::command]
async fn my_custom_command(window: tauri::Window) {
// Your code
// [...]
// send 20 elements in the Vec (array)
let should_trigger_at = 20;
// local buffer
let local_buffer: Vec<String> = Vec::new();
// Loop through each row
while let Some(m) = csv.next() {
let n = m.unwrap();
// Get reference of array of a column
let col: &ArrayRef = n.column(0);
// Cast the reference of array to array of string
let col = col.as_any().downcast_ref::<StringArray>().unwrap();
// Get value from the array using index
let v = col.value(0);
println!("{}", col.value(0));
// add the value in the buffer
local_buffer.push(col.value(0));
if local_buffer.len() == should_trigger_at {
// Send each value through an event
window
.emit("rust-event", local_buffer)
.expect("failed to emit");
// reset local buffer
local_buffer = Vec::new();
}
}
// if buffer not empty, lets emit the values
if local_buffer.len() > 0 {
window
.emit("rust-event", local_buffer)
.expect("failed to emit");
}
// [...]
// Your code
}
Please note; doing this will send an Array of String to the Webview instead of a String.
Well, I guess Rust is too fast :) React is unable to handle the speed.
I slow down the event emit with settimeout rust lib and I am happy with it for now.
// Before emit an event, delay it 100 microsecond;
set_timeout(Duration::from_micros(100)).await;
window
.emit("rust-event", v)
.expect("failed to emit");

Validate array command

As I put in the question I have these commands that work correctly but are not validated, which means that if I enter a name it simply makes the record not validating if this name already exists inside the array.
It sounds simple but I've tried to validate using array.find and then compare with the value before inserting but it does not work: for example in the command track I'm trying to validate if the name you are trying to insert already exists. In the command untrack if the name does not exist send a message, because that is another problem: if the name does not exist the command deletes the last inserted record.
If someone has the knowledge on how to make such validations, I would appreciate a little help.
const args = message.content.slice(prefix.length).trim().split(/ +/g);
const command = args.shift().toLowerCase();
if (command === "track") {
let [playerName, playerType] = args;
list.push({
name: playerName,
type: playerType
}) var logger = fs.createWriteStream('./realmtrack-config.json')
logger.write(JSON.stringify(config)) message.reply(`Player being tracked!`);
}
if (command === "untrack") {
let [playerName, playerType] = args;
let entry_to_delete = list.find((e: any) => e.name === playerName);
list.splice(list.indexOf(entry_to_delete), 1);
var logger = fs.createWriteStream('./realmtrack-config.json');
logger.write(JSON.stringify(config));
message.reply(`${playerName} stopped being tracked!`);
}
You have an array of objects
let list = [
{ name: "Joe", type: "Cool" },
{ name: "Sally", type: "Cool"}
]
You could create the below function to check if "Sally" is the name of one of the objects.
function(obj) {
return obj.name === "Sally"
}
We can make this shorter with ES6 arrow functions
obj => obj.name === "Sally"
Next, we can scour list for an argument which will return true when passed to that function.
list.some(obj => obj.name === "Sally")
.some will return false only if all of the elements in an array will return false when passed to the callback function. Otherwise, it returns true.
We can add the below line near the start of your message handler, then.
const isBeingTracked = list.some(obj => obj.name === args[0])
For the track command, you could add if(!isBeingTracked) { /* ... */ } (the ! means "not"). For the untrack command, you could add if(isBeingTracked) { /* ... */ }. /* ... */ is what you would replace for actual code.

When i run my script nothing happens

So i recently found this google drive script and tried to use it.
The script should normally get file names and url on my google drive folder and copy them in a spreadsheet.
When i click Run, I get no error and nothing happens in my drive.
Is there variables i should change to make it work ?
PS : I'm very new to coding and can't seem to find what is wrong with this code
Thanks in advance for your help !
Here is the code :
function myFunction() {
function listFilesInFolder(foldername) {
// If we have not been provided a foldername, assume we will interact with user.
var interactive = (typeof foldername === 'undefined');
// Get name of folder to list
if (interactive) {
foldername = Browser.inputBox("List files in folder", "Enter folder name", Browser.Buttons.OK_CANCEL);
}
if (foldername === '') return; // No name provided, exit quietly
var folders = DriveApp.getFoldersByName(foldername);
if (!folders.hasNext()) {
if (interactive) Browser.msgBox("Folder not found.");
return;
}
var folder = folders.next();
var contents = folder.getFiles();
var file, data, sheet = SpreadsheetApp.getActiveSheet();
sheet.clear();
sheet.appendRow(["Name", "Date", "Size", "URL", /*"Download",*/ "Description", "Type"]);
// Loop over files in folder, using file iterator
while (contents.hasNext()) {
file = contents.next();
if (file.getMimeType() == MimeType.GOOGLE_SHEETS) { // "SPREADSHEET"
// Skip displaying spreadsheets - I don't know why...
continue;
}
data = [
file.getName(),
file.getDateCreated(),
file.getSize(),
file.getUrl(),
//"https://docs.google.com/uc?export=download&confirm=no_antivirus&id=" + file.getId(),
file.getDescription(),
niceFileType( file.getMimeType() )
];
sheet.appendRow(data);
}
}
}
Two issues. This needs to be ran in a script attached to a spreadsheet and you have a nested function.
Your code:
function myFunction() {
function listFilesInFolder(foldername) {
...
}
}
remove the outer function decleration and its matching closing bracket. The code will run.
It should look like:
function listFilesInFolder(foldername) {
...
}
Try this modified code in spreadsheet instead:
function myfunction(){
//Declaring the function listFolders to temp
var temp = function listFolders(foldername) {
// If we have not been provided a foldername, assume we will interact with user.
var interactive = (typeof foldername === 'undefined');
// Get name of folder to list
if (interactive) {
foldername = Browser.inputBox("List files in folder", "Enter folder name", Browser.Buttons.OK_CANCEL);
}
if (foldername === '') return; // No name provided, exit quietly
var folders = DriveApp.getFoldersByName(foldername);
if (!folders.hasNext()) {
if (interactive) Browser.msgBox("Folder not found.");
return;
}
var folder = folders.next();
var contents = folder.getFiles();
var file, data, sheet = SpreadsheetApp.getActiveSheet();
sheet.clear();
sheet.appendRow(["Name", "Date", "Size", "URL", /*"Download",*/ "Description", "Type"]);
// Loop over files in folder, using file iterator
while (contents.hasNext()) {
file = contents.next();
if (file.getMimeType() == MimeType.GOOGLE_SHEETS) { // "SPREADSHEET"
// Skip displaying spreadsheets - I don't know why...
continue;
}
data = [
file.getName(),
file.getDateCreated(),
file.getSize(),
file.getUrl(),
//"https://docs.google.com/uc?export=download&confirm=no_antivirus&id=" + file.getId(),
file.getDescription(),
file.getMimeType()
];
sheet.appendRow(data);
}
}
// calls the function listFolders below
temp()
}

How to use a file with a BufReader and still be able to write to it?

I want to open a file and read its contents as a BufReader using lines(). I also want to be able to seek to the end of the file and write some new lines.
Using let mut file lets me write to the file, but once I've given the file to the BufReader I can no longer write to it, as the main function no longer owns file:
fn main() {
let filename = "tt.txt";
// open a tt.txt file in the local directory
let file = OpenOptions::new()
.read(true)
.write(true)
.create(true)
.open(filename)
.unwrap();
// now read the whole file to get the latest state
let date_re = Regex::new(r"^(\d{4})-(\d{2})-(\d{2})").unwrap();
let time_activity_re = Regex::new(r"^(\d{2}):(\d{2})\s*(.*)").unwrap();
let reader = BufReader::new(file);
let mut latest_date: Option<Date<Local>> = None;
let mut latest_datetime: Option<DateTime<Local>> = None;
let mut latest_activity: Option<String> = None;
for wrapped_line in reader.lines() {
let line = wrapped_line.unwrap();
println!("line: {}", line);
if date_re.is_match(&line) {
let captures = date_re.captures(&line).unwrap();
let year = captures.at(1).unwrap().parse::<i32>().unwrap();
let month = captures.at(2).unwrap().parse::<u32>().unwrap();
let day = captures.at(3).unwrap().parse::<u32>().unwrap();
latest_date = Some(Local.ymd(year, month, day));
latest_datetime = None;
latest_activity = None;
}
if time_activity_re.is_match(&line) && latest_date != None {
let captures = time_activity_re.captures(&line).unwrap();
let hour = captures.at(1).unwrap().parse::<u32>().unwrap();
let minute = captures.at(2).unwrap().parse::<u32>().unwrap();
let activity = captures.at(3).unwrap();
latest_datetime = Some(latest_date.unwrap().and_hms(hour, minute, 0));
latest_activity = if activity.len() > 0 {
// TODO: if latest_activity already constains a string, clear it and reuse it
// as per: https://stackoverflow.com/questions/33781625/how-to-allocate-a-string-before-you-know-how-big-it-needs-to-be
Some(activity.to_string())
} else {
None
};
println!("time activity: {} |{}|", latest_datetime.unwrap(), activity);
}
}
// FIXME: I have to open a second file descriptor to the same file, in order to be able to write to it
let mut out = OpenOptions::new()
.read(true)
.write(true)
.create(true)
.open(filename)
.unwrap();
out.seek(End(0));
let now = Local::now();
if latest_date == None || latest_date.unwrap().year() != now.year()
|| latest_date.unwrap().month() != now.month()
|| latest_date.unwrap().day() != now.day()
{
if (latest_date != None) {
// not an empy file, as far as tt is concerned
out.write_all(b"\n\n");
}
out.write_all(format!("{}\n", now.format("%Y-%m-%d")).as_bytes());
out.write_all(b"\n");
}
let activity = env::args().skip(1).join(" ");
if (activity.len() > 0) {
out.write_all(format!("{} {}\n", now.format("%H:%M"), activity).as_bytes());
} else {
// if there was no latest activity *and* there is no activity, then there's no point in writing a second blank line with just a time
if latest_activity == None {
return;
}
out.write_all(format!("{}\n", now.format("%H:%M")).as_bytes());
}
// FIXME: we're just relying on the program exit to close the two file descriptors (which point at the same file).
}
How can I use a single file descriptor to read existing lines and append new lines?
(Code from https://github.com/chrisdew/tt/blob/e899f252014391f2e01c3cc9e281cab1ab88936f/src/main.rs)
To avoid moving a value, you can use a reference and a new scope.
Here is how you could do this:
fn main() {
let filename = "tt.txt";
// open a tt.txt file in the local directory
let mut file = OpenOptions::new()
.read(true)
.write(true)
.create(true)
.open(filename)
.unwrap();
// now read the whole file to get the latest state
let date_re = Regex::new(r"^(\d{4})-(\d{2})-(\d{2})").unwrap();
let time_activity_re = Regex::new(r"^(\d{2}):(\d{2})\s*(.*)").unwrap();
{
// BufReader now borrows the value instead of taking ownership.
let reader = BufReader::new(&mut file);
let mut latest_date: Option<Date<Local>> = None;
let mut latest_datetime: Option<DateTime<Local>> = None;
let mut latest_activity: Option<String> = None;
for wrapped_line in reader.lines() {
let line = wrapped_line.unwrap();
println!("line: {}", line);
if date_re.is_match(&line) {
let captures = date_re.captures(&line).unwrap();
let year = captures.at(1).unwrap().parse::<i32>().unwrap();
let month = captures.at(2).unwrap().parse::<u32>().unwrap();
let day = captures.at(3).unwrap().parse::<u32>().unwrap();
latest_date = Some(Local.ymd(year, month, day));
latest_datetime = None;
latest_activity = None;
}
if time_activity_re.is_match(&line) && latest_date != None {
let captures = time_activity_re.captures(&line).unwrap();
let hour = captures.at(1).unwrap().parse::<u32>().unwrap();
let minute = captures.at(2).unwrap().parse::<u32>().unwrap();
let activity = captures.at(3).unwrap();
latest_datetime = Some(latest_date.unwrap().and_hms(hour, minute, 0));
latest_activity = if activity.len() > 0 {
// TODO: if latest_activity already constains a string, clear it and reuse it
// as per: https://stackoverflow.com/questions/33781625/how-to-allocate-a-string-before-you-know-how-big-it-needs-to-be
Some(activity.to_string())
} else {
None
};
println!("time activity: {} |{}|", latest_datetime.unwrap(), activity);
}
}
}
// End of the scope, so now file is not borrowed anymore.
file.seek(End(0));
let now = Local::now();
if latest_date == None || latest_date.unwrap().year() != now.year()
|| latest_date.unwrap().month() != now.month()
|| latest_date.unwrap().day() != now.day()
{
if (latest_date != None) {
// not an empy file, as far as tt is concerned
file.write_all(b"\n\n");
}
file.write_all(format!("{}\n", now.format("%Y-%m-%d")).as_bytes());
file.write_all(b"\n");
}
let activity = env::args().skip(1).join(" ");
if (activity.len() > 0) {
file.write_all(format!("{} {}\n", now.format("%H:%M"), activity).as_bytes());
} else {
// if there was no latest activity *and* there is no activity, then there's no point in writing a second blank line with just a time
if latest_activity == None {
return;
}
file.write_all(format!("{}\n", now.format("%H:%M")).as_bytes());
}
// FIXME: we're just relying on the program exit to close the two file descriptors (which point at the same file).
}
You can use BufReader::into_inner to "recover" the file after it's been passed to the BufReader. This can be used in conjunction with Read::by_ref to avoid giving away ownership of the BufReader<File> in the first place:
use std::{
fs::File,
io::{BufRead, BufReader, Read, Write},
};
fn example(file: File) {
let mut reader = BufReader::new(file);
for _ in reader.by_ref().lines() {}
let mut out = reader.into_inner();
out.write_all(b"new stuff").unwrap();
}
Here is antoyo's solution with similar reduced code:
use std::{
fs::File,
io::{BufRead, BufReader, Write},
};
fn example(mut file: File) {
let reader = BufReader::new(&file);
for _ in reader.lines() {}
file.write_all(b"new stuff").unwrap();
}

Moving / cloning folder swift

I'm trying to copy and rename a folder in Swift. I went about this by saving all of the original folder's contents to a new address. However, it seems to be creating .exe files instead of folders. Any ideas how I can fix the below code?
func moveAssets () {
let fileManager = NSFileManager.defaultManager()
let enumerator:NSDirectoryEnumerator = fileManager.enumeratorAtPath(folderName)!
while let element = enumerator.nextObject() as? String {
if (element != "main.js") {
var dataPath = documentsFolder.stringByAppendingPathComponent(timeStamp)
var tPath = folderName.stringByAppendingPathComponent(element)
fileManager.copyItemAtPath(tPath, toPath: dataPath, error: nil)
}
}
}
Found a solution here:
var error: NSError?
if filemgr.moveItemAtPath(filepath1, toPath: filepath2, error: &error) {
println("Move successful")
} else {
println("Moved failed with error: \(error!.localizedDescription)")
}
I will have to manually delete the .js file though

Resources