1use crate::error::Error;
4use std::cmp::Ordering;
5use std::path::{Path, PathBuf};
6
7pub const MAX_SEQ_NUM: usize = 999_999_999;
9
10pub const NUM_DIGITS: usize = 9;
12
13#[derive(Debug, Eq)]
14pub struct LogPath {
15 path: PathBuf,
16 seq_num: usize,
17}
18
19impl LogPath {
23 pub fn new(dir: &Path, seq_num: usize) -> LogPath {
26 assert!(seq_num > 0 && seq_num <= MAX_SEQ_NUM);
27 let mut path = dir.to_path_buf();
28 path.push(format!("{:09}.devlog", seq_num));
29 LogPath { path, seq_num }
30 }
31
32 pub fn from_path(path: PathBuf) -> Option<LogPath> {
35 let stem = path.file_stem().and_then(|s| s.to_str()).unwrap_or("");
36 let ext = path.extension().and_then(|s| s.to_str()).unwrap_or("");
37 let seq_num: Option<usize> = stem.parse().ok();
38 match (stem, ext, seq_num) {
39 (s, "devlog", Some(seq_num)) if s.len() == NUM_DIGITS => {
40 Some(LogPath { path, seq_num })
41 }
42 _ => None,
43 }
44 }
45
46 pub fn next(&self) -> Result<LogPath, Error> {
50 let seq_num = self.seq_num + 1;
51 if seq_num > MAX_SEQ_NUM {
52 Err(Error::LogFileLimitExceeded)
53 } else {
54 let mut path = match self.path.parent() {
55 Some(p) => p.to_path_buf(),
56 None => PathBuf::new(),
57 };
58 path.push(format!("{:09}.devlog", seq_num));
59 Ok(LogPath { path, seq_num })
60 }
61 }
62
63 pub fn seq_num(&self) -> usize {
65 self.seq_num
66 }
67
68 pub fn path(&self) -> &Path {
70 &self.path
71 }
72}
73
74impl PartialOrd for LogPath {
76 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
77 Some(self.cmp(other))
78 }
79}
80
81impl Ord for LogPath {
83 fn cmp(&self, other: &Self) -> Ordering {
84 self.seq_num.cmp(&other.seq_num)
85 }
86}
87
88impl PartialEq for LogPath {
90 fn eq(&self, other: &Self) -> bool {
91 self.seq_num == other.seq_num
92 }
93}
94
95#[cfg(test)]
96mod tests {
97 use super::*;
98
99 fn dir() -> PathBuf {
100 From::from(String::from("/foo/bar"))
101 }
102
103 fn rootdir() -> PathBuf {
104 From::from(String::from("/"))
105 }
106
107 #[test]
108 fn test_new() {
109 let d = dir();
110 let p = LogPath::new(&d, 123);
111 assert_eq!(p.seq_num(), 123);
112 assert_eq!(p.path(), d.join("000000123.devlog"));
113 }
114
115 #[test]
116 fn test_from_path() {
117 let path = dir().join("000000123.devlog");
118 let p = LogPath::from_path(path).unwrap();
119 assert_eq!(p.seq_num(), 123);
120 assert_eq!(p.path(), dir().join("000000123.devlog"));
121 }
122
123 #[test]
124 fn test_from_path_max_seq_num() {
125 let fname = format!("{}.devlog", MAX_SEQ_NUM);
126 let path = dir().join(&fname);
127 let p = LogPath::from_path(path).unwrap();
128 assert_eq!(p.seq_num(), MAX_SEQ_NUM);
129 assert_eq!(p.path(), dir().join(&fname));
130 }
131
132 #[test]
133 fn test_from_path_not_a_number() {
134 let path = dir().join("abc123.devlog");
135 assert!(LogPath::from_path(path).is_none());
136 }
137
138 #[test]
139 fn test_from_path_too_few_digits() {
140 let path = dir().join("12345678.devlog");
141 assert!(LogPath::from_path(path).is_none());
142 }
143
144 #[test]
145 fn test_from_path_too_many_digits() {
146 let path = dir().join("1234567890.devlog");
147 assert!(LogPath::from_path(path).is_none());
148 }
149
150 #[test]
151 fn test_from_path_seq_num_too_large() {
152 let fname = format!("{}.devlog", MAX_SEQ_NUM + 1);
153 let path = dir().join(&fname);
154 assert!(LogPath::from_path(path).is_none());
155 }
156
157 #[test]
158 fn test_from_path_wrong_ext() {
159 let path = dir().join("000000001.csv");
160 assert!(LogPath::from_path(path).is_none());
161 }
162
163 #[test]
164 fn test_next_in_subdir() {
165 let d = dir();
166 let p = LogPath::new(&d, 123).next().unwrap();
167 assert_eq!(p.seq_num(), 124);
168 assert_eq!(p.path(), dir().join("000000124.devlog"));
169 }
170
171 #[test]
172 fn test_next_in_rootdir() {
173 let d = rootdir();
174 let p = LogPath::new(&d, 123).next().unwrap();
175 assert_eq!(p.seq_num(), 124);
176 assert_eq!(p.path(), d.join("000000124.devlog"));
177 }
178
179 #[test]
180 fn test_next_file_limit_exceeded() {
181 let d = dir();
182 let p = LogPath::new(&d, MAX_SEQ_NUM).next();
183 match p {
184 Err(Error::LogFileLimitExceeded) => {}
185 _ => assert!(false),
186 }
187 }
188
189 #[test]
190 fn test_ordering() {
191 let d = dir();
192 let p1 = LogPath::new(&d, 1);
193 let p2 = LogPath::new(&d, 2);
194 let p3 = LogPath::new(&d, 2);
195 assert!(p1 < p2);
196 assert!(p2 > p1);
197 assert!(p2 == p3);
198 }
199}