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

心不动则不痛 提交于 2019-11-30 15:10:58
antoyo

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;
use std::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;
use std::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();
}

When non-lexical lifetimes (NLL) are implemented, this will be able to be simplified by removing the extra braces.

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!