diff --git a/melib/Cargo.toml b/melib/Cargo.toml index 63afcd1d..ba2d27b9 100644 --- a/melib/Cargo.toml +++ b/melib/Cargo.toml @@ -7,7 +7,7 @@ workspace = ".." [dependencies] bitflags = "1.0" chan = "0.1.21" -chrono = "0.4" +chrono = { version = "0.4", features = ["serde"] } crossbeam = "^0.3.0" data-encoding = "2.1.1" encoding = "0.2.33" @@ -21,3 +21,4 @@ xdg = "2.1.0" serde = "1.0.71" serde_derive = "1.0.71" bincode = "1.0.1" +uuid = { version = "0.6", features = ["serde", "v4"] } diff --git a/melib/src/addressbook/mod.rs b/melib/src/addressbook/mod.rs new file mode 100644 index 00000000..71695c43 --- /dev/null +++ b/melib/src/addressbook/mod.rs @@ -0,0 +1,160 @@ +/* + * meli - addressbook module + * + * Copyright 2019 Manos Pitsidianakis + * + * This file is part of meli. + * + * meli is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * meli is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with meli. If not, see . + */ +use chrono::{DateTime, Local}; +use uuid::Uuid; +use fnv::FnvHashMap; + + +#[derive(Debug, Clone, PartialEq, Deserialize, Serialize)] +pub struct AddressBook { + display_name: String, + created: DateTime, + last_edited: DateTime, + cards: FnvHashMap +} + +#[derive(Debug, Clone, PartialEq, Deserialize, Serialize)] +pub struct Card { + uuid: Uuid, + title: String, + firstname: String, + lastname: String, + additionalname: String, + name_prefix: String, + name_suffix: String, + //address + + birthday: Option>, + email: String, + url: String, + key: String, + + last_edited: DateTime, + extra_properties: FnvHashMap +} + +impl AddressBook { + pub fn new(display_name: String) -> AddressBook { + AddressBook { + display_name, + created: Local::now(), + last_edited: Local::now(), + cards: FnvHashMap::default(), + } + } + pub fn add_card(&mut self, card: Card) { + self.cards.insert(card.uuid, card); + } + pub fn remove_card(&mut self, card_uuid: Uuid) { + self.cards.remove(&card_uuid); + } + pub fn card_exists(&self, card_uuid: Uuid) -> bool { + self.cards.contains_key(&card_uuid) + } +} + + +impl Card { + pub fn new() -> Card { + Card { + uuid: Uuid::new_v4(), + title: String::new(), + firstname: String::new(), + lastname: String::new(), + additionalname: String::new(), + name_prefix: String::new(), + name_suffix: String::new(), + //address + + birthday: None, + email: String::new(), + url: String::new(), + key: String::new(), + + last_edited: Local::now(), + extra_properties: FnvHashMap::default(), + } + } + + pub fn title(&self) -> &str { + self.title.as_str() + } + pub fn firstname(&self) -> &str { + self.firstname.as_str() + } + pub fn lastname(&self) -> &str { + self.lastname.as_str() + } + pub fn additionalname(&self) -> &str { + self.additionalname.as_str() + } + pub fn name_prefix(&self) -> &str { + self.name_prefix.as_str() + } + pub fn name_suffix(&self) -> &str { + self.name_suffix.as_str() + } + pub fn email(&self) -> &str { + self.email.as_str() + } + pub fn url(&self) -> &str { + self.url.as_str() + } + pub fn key(&self) -> &str { + self.key.as_str() + } + + pub fn set_title(&mut self, new: &str) { + self.title = new.to_string();() + } + pub fn set_firstname(&mut self, new: &str) { + self.firstname = new.to_string(); + } + pub fn set_lastname(&mut self, new: &str) { + self.lastname = new.to_string(); + } + pub fn set_additionalname(&mut self, new: &str) { + self.additionalname = new.to_string(); + } + pub fn set_name_prefix(&mut self, new: &str) { + self.name_prefix = new.to_string(); + } + pub fn set_name_suffix(&mut self, new: &str) { + self.name_suffix = new.to_string(); + } + pub fn set_email(&mut self, new: &str) { + self.email = new.to_string(); + } + pub fn set_url(&mut self, new: &str) { + self.url = new.to_string(); + } + pub fn set_key(&mut self, new: &str) { + self.key = new.to_string(); + } + + pub fn set_extra_property(&mut self, key: &str, value: String) { + self.extra_properties.insert(key.to_string(), value); + } + pub fn extra_property(&self, key: &str) -> Option<&str> { + self.extra_properties.get(key).map(|v| v.as_str()) + } + +} diff --git a/melib/src/lib.rs b/melib/src/lib.rs index 1953a8a1..7ba22d0c 100644 --- a/melib/src/lib.rs +++ b/melib/src/lib.rs @@ -22,6 +22,7 @@ pub mod async; pub mod conf; pub mod error; pub mod mailbox; +pub mod addressbook; #[macro_use] extern crate serde_derive; @@ -37,6 +38,8 @@ extern crate chan; #[macro_use] extern crate bitflags; +extern crate uuid; +extern crate fnv; pub use conf::*; pub use mailbox::*; @@ -44,3 +47,5 @@ pub use mailbox::*; pub use error::{MeliError, Result}; pub use mailbox::backends::{Backends, RefreshEvent, RefreshEventConsumer}; pub use mailbox::email::{Envelope, Flag}; + +pub use addressbook::*; diff --git a/melib/src/mailbox/email/mod.rs b/melib/src/mailbox/email/mod.rs index b0de6811..290b23e6 100644 --- a/melib/src/mailbox/email/mod.rs +++ b/melib/src/mailbox/email/mod.rs @@ -67,6 +67,22 @@ pub enum Address { Group(GroupAddress), } +impl Address { + pub fn get_display_name(&self) -> String { + match self { + Address::Mailbox(m) => m.display_name.display(&m.raw), + Address::Group(g) => g.display_name.display(&g.raw), + } + } + + pub fn get_email(&self) -> String { + match self { + Address::Mailbox(m) => m.address_spec.display(&m.raw), + Address::Group(_) => String::new(), + } + } +} + impl Eq for Address {} impl PartialEq for Address { fn eq(&self, other: &Address) -> bool { diff --git a/src/bin.rs b/src/bin.rs index 5b314f94..49585720 100644 --- a/src/bin.rs +++ b/src/bin.rs @@ -66,7 +66,7 @@ fn main() { let menu = Entity::from(Box::new(AccountMenu::new(&state.context.accounts))); let listing = listing::Listing::default(); let b = Entity::from(Box::new(listing)); - let tabs = Box::new(Tabbed::new(vec![Box::new(VSplit::new(menu, b, 90, true))])); + let tabs = Box::new(Tabbed::new(vec![Box::new(VSplit::new(menu, b, 90, true)), Box::new(AccountsPanel::new(&state.context))])); let window = Entity::from(tabs); let status_bar = Entity::from(Box::new(StatusBar::new(window))); diff --git a/ui/Cargo.toml b/ui/Cargo.toml index 38f4d016..ff66bd2d 100644 --- a/ui/Cargo.toml +++ b/ui/Cargo.toml @@ -19,4 +19,5 @@ nom = "3.2.0" notify = "4.0.1" notify-rust = "^3" termion = "1.5.1" +bincode = "1.0.1" uuid = { version = "0.6", features = ["serde", "v4"] } diff --git a/ui/src/components/contacts.rs b/ui/src/components/contacts.rs new file mode 100644 index 00000000..6168fb34 --- /dev/null +++ b/ui/src/components/contacts.rs @@ -0,0 +1,76 @@ +/* + * meli - contacts module + * + * Copyright 2019 Manos Pitsidianakis + * + * This file is part of meli. + * + * meli is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * meli is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with meli. If not, see . + */ + +use super::*; + +use melib::{AddressBook, Card}; + +#[derive(Debug)] +pub struct ContactManager { + content: CellBuffer, + dirty: bool, + initialized: bool, +} + +impl Default for ContactManager { + fn default() -> Self { + ContactManager { + content: CellBuffer::default(), + dirty: true, + initialized: false, + } + } +} + +impl fmt::Display for ContactManager { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "contacts") + } +} + +impl ContactManager { +} + +impl Component for ContactManager { + fn draw(&mut self, grid: &mut CellBuffer, area: Area, context: &mut Context) { + if !self.initialized { + clear_area(grid, area); + self.initialized = true; + } + context.dirty_areas.push_back(area); + } + + fn process_event(&mut self, event: &UIEvent, context: &mut Context) -> bool { + false + } + + fn is_dirty(&self) -> bool { + self.dirty + } + + fn set_dirty(&mut self) { + self.dirty = true; + self.initialized = false; + } + + fn kill(&mut self, uuid: Uuid) { + } +} diff --git a/ui/src/components/mail/accounts/contacts.rs b/ui/src/components/mail/accounts/contacts.rs new file mode 100644 index 00000000..e914a095 --- /dev/null +++ b/ui/src/components/mail/accounts/contacts.rs @@ -0,0 +1,105 @@ +/* + * meli - ui crate. + * + * Copyright 2019 Manos Pitsidianakis + * + * This file is part of meli. + * + * meli is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * meli is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with meli. If not, see . + */ + +use super::*; + +#[derive(Debug)] +pub struct ContactsPanel { + content: CellBuffer, + dirty: bool, +} + +impl fmt::Display for ContactsPanel { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "contacts") + } +} + + +impl Component for ContactsPanel { + fn draw(&mut self, grid: &mut CellBuffer, area: Area, context: &mut Context) { + if self.dirty { + self.dirty = false; + } + clear_area(grid, area); + + let (width, height) = self.content.size(); + copy_area(grid, &self.content, area, ((0, 0), (width - 1, height - 1))); + context.dirty_areas.push_back(area); + } + fn process_event(&mut self, event: &UIEvent, context: &mut Context) -> bool { + false + } + fn is_dirty(&self) -> bool { + self.dirty + } + fn set_dirty(&mut self) { + self.dirty = true; + } +} + +impl ContactsPanel { + pub fn new(context: &Context) -> ContactsPanel { + let mut content = CellBuffer::new(120, 25 + context.accounts.len() * 20, Cell::default()); + write_string_to_grid( + "Contacts", + &mut content, + Color::Default, + Color::Default, + ((2, 3), (120 - 1, 3)), + true, + ); + + for (i, a) in context.accounts.iter().enumerate() { + create_box(&mut content, ((2,5+i*10 ), (120-1, 15+i*10))); + let (x, y) = write_string_to_grid( + a.name(), + &mut content, + Color::Default, + Color::Default, + ((3, 5 + i*10), (120 - 2, 5 + i*10)), + true, + ); + write_string_to_grid( + " ▒██▒ ", + &mut content, + Color::Byte(32), + Color::Default, + ((x, y), (120 - 2, 5 + i*10)), + true, + ); + write_string_to_grid( + &a.runtime_settings.account().identity, + &mut content, + Color::Default, + Color::Default, + ((4, y + 2), (120 - 2, y + 2)), + true, + ); + + } + + ContactsPanel { + content, + dirty: true, + } + } +} diff --git a/ui/src/components/mail/accounts/mod.rs b/ui/src/components/mail/accounts/mod.rs new file mode 100644 index 00000000..3e3e3da3 --- /dev/null +++ b/ui/src/components/mail/accounts/mod.rs @@ -0,0 +1,159 @@ +/* + * meli - accounts module. + * + * Copyright 2019 Manos Pitsidianakis + * + * This file is part of meli. + * + * meli is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * meli is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with meli. If not, see . + */ + +mod contacts; + +pub use contacts::*; + +use super::*; +use std::fmt; + +#[derive(Debug)] +pub struct AccountsPanel { + cursor: usize, + content: CellBuffer, + dirty: bool, +} + +impl fmt::Display for AccountsPanel { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "accounts") + } +} + + +impl Component for AccountsPanel { + fn draw(&mut self, grid: &mut CellBuffer, area: Area, context: &mut Context) { + if self.dirty { + write_string_to_grid( + "Accounts", + &mut self.content, + Color::Default, + Color::Default, + ((2, 3), (120 - 1, 3)), + true, + ); + + for (i, a) in context.accounts.iter().enumerate() { + create_box(&mut self.content, ((2,5+i*10 ), (120-1, 15+i*10))); + let (x, y) = write_string_to_grid( + a.name(), + &mut self.content, + Color::Default, + Color::Default, + ((3, 5 + i*10), (120 - 2, 5 + i*10)), + true, + ); + write_string_to_grid( + " ▒██▒ ", + &mut self.content, + Color::Byte(32), + Color::Default, + ((x, y), (120 - 2, 5 + i*10)), + true, + ); + write_string_to_grid( + &a.runtime_settings.account().identity, + &mut self.content, + Color::Default, + Color::Default, + ((4, y + 2), (120 - 2, y + 2)), + true, + ); + if i == self.cursor { + for h in 1..8 { + self.content[(2, h+y+1)].set_ch('*'); + } + } + write_string_to_grid( + "- Settings", + &mut self.content, + Color::Default, + Color::Default, + ((5, y + 3), (120 - 2, y + 3)), + true, + ); + write_string_to_grid( + "- Contacts", + &mut self.content, + Color::Default, + Color::Default, + ((5, y + 4), (120 - 2, y + 4)), + true, + ); + write_string_to_grid( + "- Mailing Lists", + &mut self.content, + Color::Default, + Color::Default, + ((5, y + 5), (120 - 2, y + 5)), + true, + ); + + + + } + self.dirty = false; + } + clear_area(grid, area); + + let (width, height) = self.content.size(); + copy_area(grid, &self.content, area, ((0, 0), (width - 1, height - 1))); + context.dirty_areas.push_back(area); + } + fn process_event(&mut self, event: &UIEvent, context: &mut Context) -> bool { + match event.event_type { + UIEventType::Input(Key::Up) => { + self.cursor = self.cursor.saturating_sub(1); + self.dirty = true; + return true; + }, + UIEventType::Input(Key::Down) => { + if self.cursor + 1 < context.accounts.len() { + self.cursor += 1; + self.dirty = true; + } + return true; + }, + _ => {}, + } + + false + } + fn is_dirty(&self) -> bool { + self.dirty + } + fn set_dirty(&mut self) { + self.dirty = true; + } +} + +impl AccountsPanel { + pub fn new(context: &Context) -> AccountsPanel { + let mut content = CellBuffer::new(120, 25 + context.accounts.len() * 20, Cell::default()); + + AccountsPanel { + cursor: 0, + content, + dirty: true, + } + } +} diff --git a/ui/src/components/mail/mod.rs b/ui/src/components/mail/mod.rs index f3dbc19a..e2d32bef 100644 --- a/ui/src/components/mail/mod.rs +++ b/ui/src/components/mail/mod.rs @@ -31,6 +31,9 @@ pub use view::*; mod compose; pub use self::compose::*; +mod accounts; +pub use self::accounts::*; + #[derive(Debug)] struct AccountMenuEntry { name: String, diff --git a/ui/src/components/mail/view/mod.rs b/ui/src/components/mail/view/mod.rs index b2de65d6..96e5da36 100644 --- a/ui/src/components/mail/view/mod.rs +++ b/ui/src/components/mail/view/mod.rs @@ -22,6 +22,7 @@ use super::*; use linkify::{Link, LinkFinder}; use std::process::{Command, Stdio}; +use std::any::Any; mod html; pub use self::html::*; @@ -40,6 +41,7 @@ enum ViewMode { Attachment(usize), Raw, Subview, + ContactSelector(Selector), } impl Default for ViewMode { @@ -170,7 +172,8 @@ impl MailView { let mut ret = "Viewing attachment. Press `r` to return \n".to_string(); ret.push_str(&attachments[aidx].text()); ret - } + }, + ViewMode::ContactSelector(_) => { unimplemented!()}, } } pub fn plain_text_to_buf(s: &str, highlight_urls: bool) -> CellBuffer { @@ -339,11 +342,15 @@ impl Component for MailView { }; self.dirty = false; } + match self.mode { ViewMode::Subview => { if let Some(s) = self.subview.as_mut() { s.draw(grid, (set_y(upper_left, y + 1), bottom_right), context); } + }, + ViewMode::ContactSelector(ref mut s) => { + s.draw(grid, (set_y(upper_left, y + 1), bottom_right), context); } _ => { if let Some(p) = self.pager.as_mut() { @@ -361,7 +368,12 @@ impl Component for MailView { return true; } } - } + }, + ViewMode::ContactSelector(ref mut s) => { + if s.process_event(event, context) { + return true; + } + }, _ => { if let Some(p) = self.pager.as_mut() { if p.process_event(event, context) { @@ -372,6 +384,36 @@ impl Component for MailView { } match event.event_type { + UIEventType::Input(Key::Char('c')) => { + /* + let mut new_card: Card = Card::new(); + new_card.set_email(&envelope.from()[0].get_email()); + new_card.set_firstname(&envelope.from()[0].get_display_name()); + + eprintln!("{:?}", new_card); + + */ + match self.mode { + ViewMode::ContactSelector(_) => { + if let ViewMode::ContactSelector(s) = std::mem::replace(&mut self.mode, ViewMode::Normal) { + //eprintln!("{:?}", s.collect()); + } + return true; + }, + _ => {}, + } + + let accounts = &context.accounts; + let mailbox = &accounts[self.coordinates.0][self.coordinates.1] + .as_ref() + .unwrap(); + let envelope: &Envelope = &mailbox.collection[&self.coordinates.2]; + let mut entries = Vec::new(); + entries.push((envelope.from()[0].get_email().into_bytes(), format!("{}", envelope.from()[0]))); + entries.push((String::from("foo@bar.de").into_bytes(), String::from("Johann de Vir "))); + self.mode = ViewMode::ContactSelector(Selector::new(entries, true)); + //context.accounts.context(self.coordinates.0).address_book.add_card(new_card); + }, UIEventType::Input(Key::Esc) | UIEventType::Input(Key::Alt('')) => { self.cmd_buf.clear(); context.replies.push_back(UIEvent { @@ -556,7 +598,7 @@ impl Component for MailView { true } fn is_dirty(&self) -> bool { - self.dirty + self.dirty || true || self.pager.as_ref().map(|p| p.is_dirty()).unwrap_or(false) || self.subview.as_ref().map(|p| p.is_dirty()).unwrap_or(false) } diff --git a/ui/src/components/mod.rs b/ui/src/components/mod.rs index a3ffa21c..1c9cd952 100644 --- a/ui/src/components/mod.rs +++ b/ui/src/components/mod.rs @@ -38,6 +38,9 @@ pub use self::indexer::*; pub mod utilities; pub use self::utilities::*; +pub mod contacts; +pub use contacts::*; + use std::fmt; use std::fmt::{Debug, Display}; use std::ops::{Deref, DerefMut}; @@ -67,6 +70,11 @@ const LIGHT_DOWN_AND_HORIZONTAL: char = '┬'; const LIGHT_UP_AND_HORIZONTAL: char = '┴'; +const DOUBLE_DOWN_AND_RIGHT: char = '╔'; +const DOUBLE_DOWN_AND_LEFT: char = '╗'; +const DOUBLE_UP_AND_LEFT: char = '╝'; +const DOUBLE_UP_AND_RIGHT: char = '╚'; + /// `Entity` is a container for Components. #[derive(Debug)] pub struct Entity { @@ -405,3 +413,22 @@ pub(crate) fn set_and_join_box(grid: &mut CellBuffer, idx: Pos, ch: char) { grid[idx].set_ch(bin_to_ch(bin_set)); } + +pub fn create_box(grid: &mut CellBuffer, area: Area) { + let upper_left = upper_left!(area); + let bottom_right = bottom_right!(area); + + for x in get_x(upper_left)..get_x(bottom_right) { + grid[(x, get_y(upper_left))].set_ch(HORZ_BOUNDARY); + grid[(x, get_y(bottom_right))].set_ch(HORZ_BOUNDARY); + } + + for y in get_y(upper_left)..get_y(bottom_right) { + grid[(get_x(upper_left), y)].set_ch(VERT_BOUNDARY); + grid[(get_x(bottom_right), y)].set_ch(VERT_BOUNDARY); + } + set_and_join_box(grid, upper_left, HORZ_BOUNDARY); + set_and_join_box(grid, set_x(upper_left, get_x(bottom_right)), HORZ_BOUNDARY); + set_and_join_box(grid, set_y(upper_left, get_y(bottom_right)), VERT_BOUNDARY); + set_and_join_box(grid, bottom_right, VERT_BOUNDARY); +} diff --git a/ui/src/components/utilities.rs b/ui/src/components/utilities.rs index 93fa4e93..4139fae0 100644 --- a/ui/src/components/utilities.rs +++ b/ui/src/components/utilities.rs @@ -836,3 +836,114 @@ impl Component for Tabbed { self.children[self.cursor_pos].set_dirty(); } } + + +type EntryIdentifier = Vec; +/// Shows selection to user +#[derive(Debug, PartialEq)] +pub struct Selector { + single_only: bool, /// allow only one selection + entries: Vec<(EntryIdentifier, bool)>, + selected_entry_count: u32, + content: CellBuffer, + + cursor: usize, + + dirty: bool, +} + +impl fmt::Display for Selector { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + Display::fmt("Selector", f) + } +} + +impl Component for Selector { + fn draw(&mut self, grid: &mut CellBuffer, area: Area, context: &mut Context) { + let (width, height) = self.content.size(); + copy_area_with_break( + grid, + &self.content, + area, + ((0, 0), (width, height)), + ); + context.dirty_areas.push_back(area); + } + fn process_event(&mut self, event: &UIEvent, context: &mut Context) -> bool { + let (width, height) = self.content.size(); + match event.event_type { + UIEventType::Input(Key::Char(' ')) => { + self.entries[self.cursor].1 = ! self.entries[self.cursor].1; + if self.entries[self.cursor].1 { + write_string_to_grid( + "x", + &mut self.content, + Color::Default, + Color::Default, + ((1, self.cursor), (width, self.cursor)), + false, + ); + } else { + write_string_to_grid( + " ", + &mut self.content, + Color::Default, + Color::Default, + ((1, self.cursor), (width, self.cursor)), + false, + ); + } + return true; + }, + UIEventType::Input(Key::Up) if self.cursor > 0 => { + self.cursor -= 1; + return true; + }, + UIEventType::Input(Key::Down) if self.cursor < height - 1=> { + self.cursor += 1; + return true; + }, + _ => {} + } + + false + } + fn is_dirty(&self) -> bool { + self.dirty + } + fn set_dirty(&mut self) { + self.dirty = true; + } +} + +impl Selector { + pub fn new(mut entries: Vec<(EntryIdentifier, String)>, single_only: bool) -> Selector { + let width = entries.iter().max_by_key(|e| e.1.len()).map(|v| v.1.len()).unwrap_or(0) + 4; + let height = entries.len(); + let mut content = CellBuffer::new(width, height, Cell::with_char(' ')); + let identifiers = entries.iter_mut().map(|(id, _)| (std::mem::replace(&mut *id, Vec::new()), false)).collect(); + for (i, e) in entries.into_iter().enumerate() { + write_string_to_grid( + &format!("[ ] {}", e.1), + &mut content, + Color::Default, + Color::Default, + ((0, i), (width - 1, i)), + false, + ); + } + + Selector { + single_only, + entries: identifiers, + selected_entry_count: 0, + content, + cursor: 0, + dirty: true, + } + } + + pub fn collect(self) -> Vec { + self.entries.into_iter().filter(|v| v.1).map(|(id, _)| id).collect() + } +} diff --git a/ui/src/conf/accounts.rs b/ui/src/conf/accounts.rs index a0598f92..271f966f 100644 --- a/ui/src/conf/accounts.rs +++ b/ui/src/conf/accounts.rs @@ -30,6 +30,10 @@ use mailbox::backends::{ }; use mailbox::*; use melib::error::Result; +use melib::AddressBook; + +use std::fs; +use std::io; use std::mem; use std::ops::{Index, IndexMut}; use std::result; @@ -48,17 +52,38 @@ macro_rules! mailbox { pub struct Account { name: String, folders: Vec>>, - - pub workers: Vec, - sent_folder: Option, - pub settings: AccountConf, - pub runtime_settings: AccountConf, - pub backend: Box, + pub(crate) address_book: AddressBook, + + pub(crate) workers: Vec, + + + pub(crate) settings: AccountConf, + pub(crate) runtime_settings: AccountConf, + pub(crate) backend: Box, notify_fn: Arc, } +impl Drop for Account { + fn drop(&mut self) { + let data_dir = + xdg::BaseDirectories::with_profile("meli", &self.name) + .unwrap(); + if let Ok(data) = data_dir.place_data_file("addressbook") { + /* place result in cache directory */ + let f = match fs::File::create(data) { + Ok(f) => f, + Err(e) => { + panic!("{}", e); + } + }; + let writer = io::BufWriter::new(f); + bincode::serialize_into(writer, &self.address_book).unwrap(); + }; + } +} + impl Account { pub fn new(name: String, settings: AccountConf, map: &Backends, notify_fn: NotifyFn) -> Self { let mut backend = map.get(settings.account().format())(settings.account()); @@ -73,12 +98,31 @@ impl Account { folders.push(None); workers.push(Account::new_worker(f, &mut backend, notify_fn.clone())); } - eprintln!("sent_folder for {} is {:?}", name, sent_folder); + let data_dir = + xdg::BaseDirectories::with_profile("meli", &name) + .unwrap(); + let address_book = if let Ok(data) = data_dir.place_data_file("addressbook") { + if data.exists() { + let reader = io::BufReader::new(fs::File::open(data).unwrap()); + let result: result::Result = bincode::deserialize_from(reader); + if let Ok(mut data_t) = result { + data_t + } else { + AddressBook::new(name.clone()) + } + } else { + AddressBook::new(name.clone()) + } + } else { + AddressBook::new(name.clone()) + }; + Account { name, folders, - workers, + address_book, sent_folder, + workers, settings: settings.clone(), runtime_settings: settings, backend, diff --git a/ui/src/conf/mod.rs b/ui/src/conf/mod.rs index c08dd456..781e9cc3 100644 --- a/ui/src/conf/mod.rs +++ b/ui/src/conf/mod.rs @@ -22,6 +22,8 @@ extern crate config; extern crate serde; extern crate xdg; +extern crate bincode; + pub mod pager; pub mod accounts;