1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
//! Interface for manipulating the end of a file.

use std::fs::{File, OpenOptions};
use std::io::{BufRead, BufReader, Seek};

#[doc(inline)]
use crate::error::PathError;

/// Create a new file for appending from the supplied filename.
pub fn append_open(filename: &str) -> std::result::Result<File, PathError> {
    OpenOptions::new()
        .create(true)
        .append(true)
        .open(&filename)
        .map_err(|e| PathError::FileAccess(filename.to_owned(), e.to_string()))
}

/// Open the supplied file for reading and writing, return a [`File`].
///
/// ## Errors
///
/// - Return [`PathError::FileAccess`] if unable to open the file.
pub fn rw_open(filename: &str) -> std::result::Result<File, PathError> {
    OpenOptions::new()
        .read(true)
        .write(true)
        .open(filename)
        .map_err(|e| PathError::FileAccess(filename.to_owned(), e.to_string()))
}

/// Remove the most recent task from the stack file and return the task string.
pub fn pop_last_line(file: &mut File) -> Option<String> {
    let (line, pos) = find_last_line(file)?;

    file.set_len(pos).ok()?;
    Some(line)
}

/// Find the last line in the supplied file.
/// Return an optional tuple of the line and the offset to that line.
pub fn find_last_line(file: &mut File) -> Option<(String, u64)> {
    let mut last_pos = 0u64;
    let mut last_line = String::new();

    if file.metadata().ok()?.len() == 0u64 {
        return None;
    }

    let mut reader = BufReader::new(file);
    while let Some((line, pos)) = last_line_and_pos(&mut reader) {
        last_pos = pos;
        last_line = line.trim_end().to_owned();
    }

    Some((last_line, last_pos))
}

// Find the last line and the position of the beginning of that line in the stack file.
fn last_line_and_pos<R: BufRead + Seek>(reader: &mut R) -> Option<(String, u64)> {
    let pos = reader.stream_position().ok()?;
    let mut line = String::new();
    if 0 == reader.read_line(&mut line).ok()? {
        return None;
    }
    Some((line, pos))
}