1use json::parse;
3use std::fs::File;
4use std::io::{Read, Write, stdout, stdin};
5use list::TodoList;
6use legacy::*;
7use todo::Todo;
8use error::*;
9
10#[derive(Debug, Clone, Serialize, Deserialize)]
18pub struct Tdo {
19 pub lists: Vec<TodoList>,
21 access_token: Option<String>,
23 version: String,
25}
26
27impl Tdo {
28 pub fn new() -> Tdo {
38 Tdo {
39 lists: vec![TodoList::default()],
40 access_token: None,
41 version: env!("CARGO_PKG_VERSION").to_string(),
42 }
43 }
44
45 pub fn load(path: &str) -> TdoResult<Tdo> {
57 match File::open(path) {
58 Ok(file) => {
59 match super::serde_json::from_reader(&file) {
60 Ok(tdo) => Ok(tdo),
61 Err(_) => update_json(path),
62 }
63 }
64 Err(_) => Err(ErrorKind::StorageError(storage_error::ErrorKind::FileNotFound).into()),
65 }
66
67 }
68
69 pub fn save(&self, path: &str) -> TdoResult<()> {
83 match File::create(path) {
87 Ok(mut f) => {
88 let _ = super::serde_json::to_writer_pretty(&mut f, self);
89 Ok(())
90 }
91 Err(_) => Err(ErrorKind::StorageError(storage_error::ErrorKind::SaveFailure).into()),
92 }
93 }
94
95 pub fn set_gh_token(&mut self, token: Option<&str>) {
97 let gh_token = match token {
98 Some(x) => x.to_string(),
99 None => {
100 print!("Please generate an access token ({})\nand enter a valid accesstoken: ",
101 "https://github.com/settings/tokens/new?scopes=repo&description=tdolist");
102 stdout().flush().ok().expect("Could not flush stdout!");
103 let mut answer = String::new();
104 stdin().read_line(&mut answer).unwrap();
105 answer.trim().to_string()
106 }
107 };
108 self.access_token = Some(gh_token);
109 }
110
111 pub fn get_gh_token(&self) -> Option<String> {
113 self.access_token.to_owned()
114 }
115
116 pub fn add_list(&mut self, list: TodoList) -> TdoResult<()> {
118 match self.get_list_index(&list.name) {
119 Ok(_) => Err(ErrorKind::TodoError(todo_error::ErrorKind::NameAlreadyExists).into()),
120 Err(_) => {
121 self.lists.push(list);
122 Ok(())
123 }
124 }
125 }
126
127 pub fn remove_list(&mut self, list_name: &str) -> TdoResult<()> {
129 if list_name == "default" {
130 Err(ErrorKind::TodoError(todo_error::ErrorKind::CanNotRemoveDefault).into())
131 } else {
132 match self.get_list_index(list_name) {
133 Ok(index) => {
134 self.lists.remove(index);
135 Ok(())
136 }
137 Err(_) => Err(ErrorKind::TodoError(todo_error::ErrorKind::NoSuchList).into()),
138 }
139 }
140 }
141
142 pub fn add_todo(&mut self, list_name: Option<&str>, todo: Todo) -> TdoResult<()> {
147 match self.get_list_index(&list_name.unwrap_or("default")) {
148 Ok(index) => {
149 self.lists[index].add(todo);
150 Ok(())
151 }
152 Err(x) => Err(x),
153 }
154 }
155
156 pub fn find_id(&self, id: u32) -> TdoResult<usize> {
161 for list in 0..self.lists.len() {
162 if self.lists[list].contains_id(id).is_ok() {
163 return Ok(list);
164 }
165 }
166 Err(ErrorKind::TodoError(todo_error::ErrorKind::NotInList).into())
167 }
168 pub fn done_id(&mut self, id: u32) -> TdoResult<()> {
172 let list = match self.find_id(id) {
173 Ok(list_id) => list_id,
174 Err(e) => return Err(e),
175 };
176 self.lists[list].done_id(id)
177 }
178
179 pub fn remove_id(&mut self, id: u32) -> TdoResult<()> {
183 let list = match self.find_id(id) {
184 Ok(list_id) => list_id,
185 Err(e) => return Err(e),
186 };
187 match self.lists[list].remove_id(id) {
188 Err(e) => Err(e),
189 _ => Ok(()),
190 }
191 }
192
193 pub fn clean_lists(&mut self) {
195 for list in 0..self.lists.len() {
196 self.lists[list].clean();
197 }
198 }
199
200 pub fn clean_list(&mut self, list: &str) -> TdoResult<()> {
202 let index = match self.get_list_index(list) {
203 Ok(index) => index,
204 Err(e) => return Err(e),
205 };
206 self.lists[index].clean();
207 Ok(())
208 }
209
210 fn get_list_index(&self, name: &str) -> TdoResult<usize> {
211 match self.lists
212 .iter()
213 .position(|x| x.name.to_lowercase() == name.to_string().to_lowercase()) {
214 Some(index) => Ok(index),
215 None => Err(ErrorKind::TodoError(todo_error::ErrorKind::NoSuchList).into()),
216 }
217 }
218
219 pub fn get_highest_id(&self) -> u32 {
221 self.lists
222 .iter()
223 .fold(0, |acc, &ref x| {
224 x.list
225 .iter()
226 .fold(acc,
227 |inner_acc, &ref y| if inner_acc < y.id { y.id } else { inner_acc })
228 })
229 }
230
231 pub fn move_todo(&mut self, id: u32, target_list: &str) -> TdoResult<()> {
233 let src_index = self.find_id(id)?;
234 let target = self.get_list_index(target_list)?;
235
236 let list_index = self.lists[src_index].contains_id(id)?;
238 if let Some(_) = self.lists[src_index].list[list_index].github {
239 return Err(ErrorKind::GithubError(github_error::ErrorKind::NotAllowedToMove).into())
240 }
241 let todo = self.lists[src_index].pop_id(id)?;
242 self.lists[target].insert_todo(todo);
243 Ok(())
244 }
245}
246
247fn update_json(path: &str) -> TdoResult<Tdo> {
248 match Tdo01::load(path) {
249 Ok(tdo) => Ok(tdo.into()),
250 Err(_) => {
251 println!("I have to do this here");
252 let mut file = File::open(path).unwrap();
253 let mut data = String::new();
254 file.read_to_string(&mut data).unwrap();
255 let mut json = match parse(&data) {
256 Ok(content) => content,
257 Err(_) => {
258 return Err(ErrorKind::StorageError(storage_error::ErrorKind::FileCorrupted)
259 .into())
260 }
261 };
262
263 let mut lists: Vec<TodoList> = vec![];
264
265 for outer in json.entries_mut() {
266 let mut list = TodoList::new(outer.0);
267 for inner in outer.1.entries_mut() {
268 let tdo_id = match inner.0.parse::<u32>() {
269 Ok(id) => id,
270 Err(_) => return Err(ErrorKind::StorageError(storage_error::ErrorKind::UnableToConvert).into()),
271 };
272 let done = match inner.1.pop().as_bool() {
273 Some(x) => x,
274 None => return Err(ErrorKind::StorageError(storage_error::ErrorKind::UnableToConvert).into()),
275 };
276 let tdo_name = match inner.1.pop().as_str() {
277 Some(x) => String::from(x),
278 None => return Err(ErrorKind::StorageError(storage_error::ErrorKind::UnableToConvert).into()),
279 };
280 let mut todo = Todo::new(tdo_id, &tdo_name, None);
281 if done {
282 todo.set_done();
283 }
284 list.add(todo);
285 }
286 lists.push(list);
287 }
288 let tdo = Tdo {
289 lists: lists,
290 access_token: None,
291 version: env!("CARGO_PKG_VERSION").to_string(),
292 };
293 Ok(tdo)
294 }
295 }
296}