diff options
Diffstat (limited to 'src/app/impl_self.rs')
-rw-r--r-- | src/app/impl_self.rs | 247 |
1 files changed, 247 insertions, 0 deletions
diff --git a/src/app/impl_self.rs b/src/app/impl_self.rs new file mode 100644 index 0000000..efed4e0 --- /dev/null +++ b/src/app/impl_self.rs | |||
@@ -0,0 +1,247 @@ | |||
1 | use std::default::Default; | ||
2 | use std::f64; | ||
3 | use std::fs::{File, OpenOptions}; | ||
4 | use std::io::prelude::*; | ||
5 | use std::path::PathBuf; | ||
6 | use std::sync::mpsc::channel; | ||
7 | use std::time::Duration; | ||
8 | |||
9 | use chrono::Local; | ||
10 | use cursive::direction::Absolute; | ||
11 | use cursive::Vec2; | ||
12 | use notify::{watcher, RecursiveMode, Watcher}; | ||
13 | |||
14 | use crate::habit::{Bit, Count, HabitWrapper, TrackEvent, ViewMode}; | ||
15 | use crate::utils; | ||
16 | use crate::Command; | ||
17 | use crate::CONFIGURATION; | ||
18 | |||
19 | use crate::app::{App, StatusLine}; | ||
20 | |||
21 | impl App { | ||
22 | pub fn new() -> Self { | ||
23 | let (tx, rx) = channel(); | ||
24 | let mut watcher = watcher(tx, Duration::from_secs(1)).unwrap(); | ||
25 | watcher | ||
26 | .watch(utils::auto_habit_file(), RecursiveMode::Recursive) | ||
27 | .unwrap_or_else(|e| { | ||
28 | panic!("Unable to start file watcher: {}", e); | ||
29 | }); | ||
30 | return App { | ||
31 | habits: vec![], | ||
32 | focus: 0, | ||
33 | _file_watcher: watcher, | ||
34 | file_event_recv: rx, | ||
35 | view_month_offset: 0, | ||
36 | }; | ||
37 | } | ||
38 | |||
39 | pub fn add_habit(&mut self, h: Box<dyn HabitWrapper>) { | ||
40 | self.habits.push(h); | ||
41 | } | ||
42 | |||
43 | pub fn delete_by_name(&mut self, name: &str) { | ||
44 | self.habits.retain(|h| h.name() != name); | ||
45 | } | ||
46 | |||
47 | pub fn get_mode(&self) -> ViewMode { | ||
48 | if self.habits.is_empty() { | ||
49 | return ViewMode::Day; | ||
50 | } | ||
51 | return self.habits[self.focus].view_mode(); | ||
52 | } | ||
53 | |||
54 | pub fn set_mode(&mut self, mode: ViewMode) { | ||
55 | if !self.habits.is_empty() { | ||
56 | self.habits[self.focus].set_view_mode(mode); | ||
57 | } | ||
58 | } | ||
59 | |||
60 | pub fn set_view_month_offset(&mut self, offset: u32) { | ||
61 | self.view_month_offset = offset; | ||
62 | for v in self.habits.iter_mut() { | ||
63 | v.set_view_month_offset(offset); | ||
64 | } | ||
65 | } | ||
66 | |||
67 | pub fn sift_backward(&mut self) { | ||
68 | self.view_month_offset += 1; | ||
69 | for v in self.habits.iter_mut() { | ||
70 | v.set_view_month_offset(self.view_month_offset); | ||
71 | } | ||
72 | } | ||
73 | |||
74 | pub fn sift_forward(&mut self) { | ||
75 | if self.view_month_offset > 0 { | ||
76 | self.view_month_offset -= 1; | ||
77 | for v in self.habits.iter_mut() { | ||
78 | v.set_view_month_offset(self.view_month_offset); | ||
79 | } | ||
80 | } | ||
81 | } | ||
82 | |||
83 | pub fn set_focus(&mut self, d: Absolute) { | ||
84 | let grid_width = CONFIGURATION.grid_width; | ||
85 | match d { | ||
86 | Absolute::Right => { | ||
87 | if self.focus != self.habits.len() - 1 { | ||
88 | self.focus += 1; | ||
89 | } | ||
90 | } | ||
91 | Absolute::Left => { | ||
92 | if self.focus != 0 { | ||
93 | self.focus -= 1; | ||
94 | } | ||
95 | } | ||
96 | Absolute::Down => { | ||
97 | if self.focus + grid_width < self.habits.len() - 1 { | ||
98 | self.focus += grid_width; | ||
99 | } else { | ||
100 | self.focus = self.habits.len() - 1; | ||
101 | } | ||
102 | } | ||
103 | Absolute::Up => { | ||
104 | if self.focus as isize - grid_width as isize >= 0 { | ||
105 | self.focus -= grid_width; | ||
106 | } else { | ||
107 | self.focus = 0; | ||
108 | } | ||
109 | } | ||
110 | Absolute::None => {} | ||
111 | } | ||
112 | } | ||
113 | |||
114 | pub fn status(&self) -> StatusLine { | ||
115 | let today = chrono::Local::now().naive_utc().date(); | ||
116 | let remaining = self.habits.iter().map(|h| h.remaining(today)).sum::<u32>(); | ||
117 | let total = self.habits.iter().map(|h| h.goal()).sum::<u32>(); | ||
118 | let completed = total - remaining; | ||
119 | |||
120 | let timestamp = if self.view_month_offset == 0 { | ||
121 | format!("{}", Local::now().date().format("%d/%b/%y"),) | ||
122 | } else { | ||
123 | let months = self.view_month_offset; | ||
124 | format!("{}", format!("{} months ago", months),) | ||
125 | }; | ||
126 | |||
127 | StatusLine { | ||
128 | 0: format!( | ||
129 | "Today: {} completed, {} remaining --{}--", | ||
130 | completed, | ||
131 | remaining, | ||
132 | self.get_mode() | ||
133 | ), | ||
134 | 1: timestamp, | ||
135 | } | ||
136 | } | ||
137 | |||
138 | pub fn max_size(&self) -> Vec2 { | ||
139 | let grid_width = CONFIGURATION.grid_width; | ||
140 | let width = { | ||
141 | if self.habits.len() > 0 { | ||
142 | grid_width * CONFIGURATION.view_width | ||
143 | } else { | ||
144 | 0 | ||
145 | } | ||
146 | }; | ||
147 | let height = { | ||
148 | if self.habits.len() > 0 { | ||
149 | (CONFIGURATION.view_height as f64 | ||
150 | * (self.habits.len() as f64 / grid_width as f64).ceil()) | ||
151 | as usize | ||
152 | } else { | ||
153 | 0 | ||
154 | } | ||
155 | }; | ||
156 | Vec2::new(width, height + 2) | ||
157 | } | ||
158 | |||
159 | pub fn load_state() -> Self { | ||
160 | let (regular_f, auto_f) = (utils::habit_file(), utils::auto_habit_file()); | ||
161 | let read_from_file = |file: PathBuf| -> Vec<Box<dyn HabitWrapper>> { | ||
162 | if let Ok(ref mut f) = File::open(file) { | ||
163 | let mut j = String::new(); | ||
164 | f.read_to_string(&mut j); | ||
165 | return serde_json::from_str(&j).unwrap(); | ||
166 | } else { | ||
167 | return Vec::new(); | ||
168 | } | ||
169 | }; | ||
170 | |||
171 | let mut regular = read_from_file(regular_f); | ||
172 | let auto = read_from_file(auto_f); | ||
173 | regular.extend(auto); | ||
174 | return App { | ||
175 | habits: regular, | ||
176 | ..Default::default() | ||
177 | }; | ||
178 | } | ||
179 | |||
180 | // this function does IO | ||
181 | // TODO: convert this into non-blocking async function | ||
182 | pub fn save_state(&self) { | ||
183 | let (regular, auto): (Vec<_>, Vec<_>) = self.habits.iter().partition(|&x| !x.is_auto()); | ||
184 | let (regular_f, auto_f) = (utils::habit_file(), utils::auto_habit_file()); | ||
185 | |||
186 | let write_to_file = |data: Vec<&Box<dyn HabitWrapper>>, file: PathBuf| { | ||
187 | let j = serde_json::to_string_pretty(&data).unwrap(); | ||
188 | match OpenOptions::new() | ||
189 | .write(true) | ||
190 | .create(true) | ||
191 | .truncate(true) | ||
192 | .open(file) | ||
193 | { | ||
194 | Ok(ref mut f) => f.write_all(j.as_bytes()).unwrap(), | ||
195 | Err(_) => panic!("Unable to write!"), | ||
196 | }; | ||
197 | }; | ||
198 | |||
199 | write_to_file(regular, regular_f); | ||
200 | write_to_file(auto, auto_f); | ||
201 | } | ||
202 | |||
203 | pub fn parse_command(&mut self, c: Command) { | ||
204 | match c { | ||
205 | Command::Add(name, goal, auto) => { | ||
206 | let kind = if goal == Some(1) { "bit" } else { "count" }; | ||
207 | if kind == "count" { | ||
208 | self.add_habit(Box::new(Count::new( | ||
209 | name, | ||
210 | goal.unwrap_or(0), | ||
211 | auto.unwrap_or(false), | ||
212 | ))); | ||
213 | } else if kind == "bit" { | ||
214 | self.add_habit(Box::new(Bit::new(name, auto.unwrap_or(false)))); | ||
215 | } | ||
216 | } | ||
217 | Command::Delete(name) => { | ||
218 | self.delete_by_name(&name); | ||
219 | self.focus = 0; | ||
220 | } | ||
221 | Command::TrackUp(name) => { | ||
222 | let target_habit = self | ||
223 | .habits | ||
224 | .iter_mut() | ||
225 | .find(|x| x.name() == name && x.is_auto()); | ||
226 | if let Some(h) = target_habit { | ||
227 | h.modify(Local::now().naive_utc().date(), TrackEvent::Increment); | ||
228 | } | ||
229 | } | ||
230 | Command::TrackDown(name) => { | ||
231 | let target_habit = self | ||
232 | .habits | ||
233 | .iter_mut() | ||
234 | .find(|x| x.name() == name && x.is_auto()); | ||
235 | if let Some(h) = target_habit { | ||
236 | h.modify(Local::now().naive_utc().date(), TrackEvent::Decrement); | ||
237 | } | ||
238 | } | ||
239 | Command::Quit => self.save_state(), | ||
240 | Command::MonthNext => self.sift_forward(), | ||
241 | Command::MonthPrev => self.sift_backward(), | ||
242 | _ => { | ||
243 | eprintln!("UNKNOWN COMMAND!"); | ||
244 | } | ||
245 | } | ||
246 | } | ||
247 | } | ||