Browse Source

add sqlite3 feature WIP

tags/pre-alpha-0.4.0
Manos Pitsidianakis 2 months ago
parent
commit
3af6f338ce
No known key found for this signature in database

+ 53
- 0
Cargo.lock View File

@@ -310,6 +310,16 @@ dependencies = [
]

[[package]]
name = "fallible-iterator"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"

[[package]]
name = "fallible-streaming-iterator"
version = "0.1.9"
source = "registry+https://github.com/rust-lang/crates.io-index"

[[package]]
name = "filetime"
version = "0.2.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -459,6 +469,20 @@ dependencies = [
]

[[package]]
name = "libsqlite3-sys"
version = "0.16.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"pkg-config 0.3.14 (registry+https://github.com/rust-lang/crates.io-index)",
"vcpkg 0.2.7 (registry+https://github.com/rust-lang/crates.io-index)",
]

[[package]]
name = "linked-hash-map"
version = "0.5.2"
source = "registry+https://github.com/rust-lang/crates.io-index"

[[package]]
name = "linkify"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -475,6 +499,14 @@ dependencies = [
]

[[package]]
name = "lru-cache"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"linked-hash-map 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)",
]

[[package]]
name = "mac-notification-sys"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -975,6 +1007,20 @@ dependencies = [
]

[[package]]
name = "rusqlite"
version = "0.20.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"bitflags 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
"fallible-iterator 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
"fallible-streaming-iterator 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)",
"libsqlite3-sys 0.16.0 (registry+https://github.com/rust-lang/crates.io-index)",
"lru-cache 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)",
"memchr 2.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
"time 0.1.42 (registry+https://github.com/rust-lang/crates.io-index)",
]

[[package]]
name = "rustc-demangle"
version = "0.1.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1197,6 +1243,7 @@ dependencies = [
"nom 3.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
"notify 4.0.12 (registry+https://github.com/rust-lang/crates.io-index)",
"notify-rust 3.6.0 (registry+https://github.com/rust-lang/crates.io-index)",
"rusqlite 0.20.0 (registry+https://github.com/rust-lang/crates.io-index)",
"serde 1.0.99 (registry+https://github.com/rust-lang/crates.io-index)",
"serde_derive 1.0.92 (registry+https://github.com/rust-lang/crates.io-index)",
"serde_json 1.0.39 (registry+https://github.com/rust-lang/crates.io-index)",
@@ -1336,6 +1383,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
"checksum encoding_index_tests 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)" = "a246d82be1c9d791c5dfde9a2bd045fc3cbba3fa2b11ad558f27d01712f00569"
"checksum failure 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "795bd83d3abeb9220f257e597aa0080a508b27533824adf336529648f6abf7e2"
"checksum failure_derive 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "ea1063915fd7ef4309e222a5a07cf9c319fb9c7836b1f89b85458672dbb127e1"
"checksum fallible-iterator 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "4443176a9f2c162692bd3d352d745ef9413eec5782a80d8fd6f8a1ac692a07f7"
"checksum fallible-streaming-iterator 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)" = "7360491ce676a36bf9bb3c56c1aa791658183a54d2744120f27285738d90465a"
"checksum filetime 0.2.5 (registry+https://github.com/rust-lang/crates.io-index)" = "2f8c63033fcba1f51ef744505b3cad42510432b904c062afa67ad7ece008429d"
"checksum fnv 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)" = "2fad85553e09a6f881f739c29f0b00b0f01357c743266d478b68951ce23285f3"
"checksum foreign-types 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)" = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1"
@@ -1356,8 +1405,11 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
"checksum lazycell 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "b294d6fa9ee409a054354afc4352b0b9ef7ca222c69b8812cbea9e7d2bf3783f"
"checksum libc 0.2.64 (registry+https://github.com/rust-lang/crates.io-index)" = "74dfca3d9957906e8d1e6a0b641dc9a59848e793f1da2165889fd4f62d10d79c"
"checksum libdbus-sys 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "18cb88963258d00f4962205dbb5933d82780d9962c8c8a064b651d2ad7189210"
"checksum libsqlite3-sys 0.16.0 (registry+https://github.com/rust-lang/crates.io-index)" = "5e5b95e89c330291768dc840238db7f9e204fd208511ab6319b56193a7f2ae25"
"checksum linked-hash-map 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)" = "ae91b68aebc4ddb91978b11a1b02ddd8602a05ec19002801c5666000e05e0f83"
"checksum linkify 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "9ce9439c6f4a1092dc1861272bef01034891da39f13aa1cdcf40ca3e4081de5f"
"checksum log 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)" = "c84ec4b527950aa83a329754b01dbe3f58361d1c5efacd1f6d68c494d08a17c6"
"checksum lru-cache 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "31e24f1ad8321ca0e8a1e0ac13f23cb668e6f5466c2c57319f6a5cf1cc8e3b1c"
"checksum mac-notification-sys 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "3dfb6b71a9a89cd38b395d994214297447e8e63b1ba5708a9a2b0b1048ceda76"
"checksum malloc_buf 0.0.6 (registry+https://github.com/rust-lang/crates.io-index)" = "62bb907fe88d54d8d9ce32a3cceab4218ed2f6b7d35617cafe9adf84e43919cb"
"checksum memchr 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "148fab2e51b4f1cfc66da2a7c32981d1d3c083a803978268bb11fe4b86925e7a"
@@ -1408,6 +1460,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
"checksum redox_termios 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "7e891cfe48e9100a70a3b6eb652fef28920c117d366339687bd5576160db0f76"
"checksum redox_users 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "3fe5204c3a17e97dde73f285d49be585df59ed84b50a872baf416e73b62c3828"
"checksum remove_dir_all 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)" = "4a83fa3702a688b9359eccba92d153ac33fd2e8462f9e0e3fdf155239ea7792e"
"checksum rusqlite 0.20.0 (registry+https://github.com/rust-lang/crates.io-index)" = "2a194373ef527035645a1bc21b10dc2125f73497e6e155771233eb187aedd051"
"checksum rustc-demangle 0.1.15 (registry+https://github.com/rust-lang/crates.io-index)" = "a7f4dccf6f4891ebcc0c39f9b6eb1a83b9bf5d747cb439ec6fba4f3b977038af"
"checksum rustc_version 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "138e3e0acb6c9fb258b19b67cb8abd63c00679d2851805ea151465464fe9030a"
"checksum ryu 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)" = "b96a9549dc8d48f2c283938303c4b5a77aa29bfbc5b54b084fb1630408899a8f"

+ 4
- 0
melib/src/email.rs View File

@@ -376,6 +376,10 @@ impl Envelope {
let _strings: Vec<String> = self.to.iter().map(|a| format!("{}", a)).collect();
_strings.join(", ")
}
pub fn field_references_to_string(&self) -> String {
let _strings: Vec<String> = self.references().iter().map(|a| a.to_string()).collect();
_strings.join(", ")
}

/// Requests bytes from backend and thus can fail
pub fn bytes(&self, mut operation: Box<dyn BackendOp>) -> Result<Vec<u8>> {

+ 3
- 1
ui/Cargo.toml View File

@@ -26,9 +26,11 @@ unicode-segmentation = "1.2.1" # >:c
text_processing = { path = "../text_processing", version = "*" }
libc = {version = "0.2.59", features = ["extra_traits",]}
nix = "0.15.0"
rusqlite = {version = "0.20.0", optional =true }

[features]
default = []
default = ["sqlite3"]

sqlite3 = ["rusqlite"]
# Print tracing logs as meli runs
debug-tracing = []

+ 41
- 67
ui/src/components/mail/listing/compact.rs View File

@@ -343,6 +343,7 @@ impl ListingTrait for CompactListing {
}
context.dirty_areas.push_back(area);
}

fn filter(&mut self, filter_term: &str, context: &Context) {
self.filtered_order.clear();
self.filtered_selection.clear();
@@ -351,84 +352,57 @@ impl ListingTrait for CompactListing {
}
self.filter_term.clear();

let mut error: Option<(EnvelopeHash, MeliError)> = None;
for (i, h) in self.order.keys().enumerate() {
let account = &context.accounts[self.cursor_pos.0];
let folder_hash = account[self.cursor_pos.1].unwrap().folder.hash();
let threads = &account.collection.threads[&folder_hash];
let env_hash = {
let thread_node = &threads.thread_nodes()[h];
if let Some(i) = thread_node.message() {
i
} else {
let mut iter_ptr = thread_node.children()[0];
while threads.thread_nodes()[&iter_ptr].message().is_none() {
iter_ptr = threads.thread_nodes()[&iter_ptr].children()[0];
let account = &context.accounts[self.cursor_pos.0];
let folder_hash = account[self.cursor_pos.1].unwrap().folder.hash();
match crate::search::filter(filter_term, context, self.cursor_pos.0, folder_hash) {
Ok(results) => {
let threads = &account.collection.threads[&folder_hash];
for env_hash in results {
if !account.collection.envelopes.contains_key(&env_hash) {
continue;
}
let env_hash_thread_hash = account.get_env(&env_hash).thread();
if !threads.thread_nodes.contains_key(&env_hash_thread_hash) {
continue;
}
let thread_group = melib::find_thread_group(
&threads.thread_nodes,
threads.thread_nodes[&env_hash_thread_hash].thread_group(),
);
if let Some(row) = self.order.get(&thread_group) {
self.filtered_selection.push(thread_group);
self.filtered_order.insert(thread_group, *row);
}
threads.thread_nodes()[&iter_ptr].message().unwrap()
}
};
let envelope = &account.collection[&env_hash];
if envelope.subject().contains(&filter_term) {
self.filtered_selection.push(*h);
self.filtered_order.insert(*h, i);
self.selection.insert(*h, false);
continue;
}
if envelope.field_from_to_string().contains(&filter_term) {
self.filtered_selection.push(*h);
self.filtered_order.insert(*h, i);
self.selection.insert(*h, false);
continue;
}
let op = account.operation(env_hash);
let body = match envelope.body(op) {
Ok(b) => b,
Err(e) => {
error = Some((env_hash, e));
break;
}
};
let decoded = decode_rec(&body, None);
let body_text = String::from_utf8_lossy(&decoded);
if body_text.contains(&filter_term) {
self.filtered_selection.push(*h);
self.filtered_order.insert(*h, i);
self.selection.insert(*h, false);
Err(e) => {
self.length = 0;
let message = format!("Error: {}", e);
log(
format!("Failed to search for term {}: {}", filter_term, e,),
ERROR,
);
self.data_columns.columns[0] =
CellBuffer::new_with_context(message.len(), 1, Cell::with_char(' '), context);
write_string_to_grid(
&message,
&mut self.data_columns.columns[0],
Color::Default,
Color::Default,
Attr::Default,
((0, 0), (message.len() - 1, 0)),
false,
);
}
}

if let Some((env_hash, error)) = error {
self.length = 0;
let message = format!("Error: {}", error.to_string());
log(
format!(
"Failed to open envelope {}: {}",
context.accounts[self.new_cursor_pos.0]
.get_env(&env_hash)
.message_id_display(),
error.to_string()
),
ERROR,
);
self.data_columns.columns[0] =
CellBuffer::new_with_context(message.len(), 1, Cell::with_char(' '), context);
write_string_to_grid(
&message,
&mut self.data_columns.columns[0],
Color::Default,
Color::Default,
Attr::Default,
((0, 0), (message.len() - 1, 0)),
false,
);
} else if !self.filtered_selection.is_empty() {
if !self.filtered_selection.is_empty() {
self.filter_term = filter_term.to_string();
self.cursor_pos.2 = std::cmp::min(self.filtered_selection.len() - 1, self.cursor_pos.2);
self.length = self.filtered_selection.len();
} else {
self.length = 0;
let message = format!("No results for `{}`.", filter_term);
let message = format!("No results for `{}`. Press ESC to go back.", filter_term);
self.data_columns.columns[0] =
CellBuffer::new_with_context(message.len(), 1, Cell::with_char(' '), context);
write_string_to_grid(

+ 53
- 206
ui/src/components/mail/listing/conversations.rs View File

@@ -403,67 +403,51 @@ impl ListingTrait for ConversationsListing {
}
self.filter_term.clear();

let mut error = None;
for (i, h) in self.order.keys().enumerate() {
let account = &context.accounts[self.cursor_pos.0];
let folder_hash = account[self.cursor_pos.1].unwrap().folder.hash();
let threads = &account.collection.threads[&folder_hash];
let env_hash = {
let thread_node = &threads.thread_nodes()[h];
if let Some(i) = thread_node.message() {
i
} else {
let mut iter_ptr = thread_node.children()[0];
while threads.thread_nodes()[&iter_ptr].message().is_none() {
iter_ptr = threads.thread_nodes()[&iter_ptr].children()[0];
let account = &context.accounts[self.cursor_pos.0];
let folder_hash = account[self.cursor_pos.1].unwrap().folder.hash();
match crate::search::filter(filter_term, context, self.cursor_pos.0, folder_hash) {
Ok(results) => {
let threads = &account.collection.threads[&folder_hash];
for env_hash in results {
if !account.collection.envelopes.contains_key(&env_hash) {
continue;
}
let env_hash_thread_hash = account.get_env(&env_hash).thread();
if !threads.thread_nodes.contains_key(&env_hash_thread_hash) {
continue;
}
let thread_group = melib::find_thread_group(
&threads.thread_nodes,
threads.thread_nodes[&env_hash_thread_hash].thread_group(),
);
if let Some(row) = self.order.get(&thread_group) {
self.filtered_selection.push(thread_group);
self.filtered_order.insert(thread_group, *row);
}
threads.thread_nodes()[&iter_ptr].message().unwrap()
}
};
let envelope = &account.collection[&env_hash];
if envelope.subject().contains(&filter_term) {
self.filtered_selection.push(*h);
self.filtered_order.insert(*h, i);
self.selection.insert(*h, false);
continue;
}
if envelope.field_from_to_string().contains(&filter_term) {
self.filtered_selection.push(*h);
self.filtered_order.insert(*h, i);
self.selection.insert(*h, false);
continue;
}
let op = account.operation(env_hash);
let body = match envelope.body(op) {
Ok(b) => b,
Err(e) => {
error = Some(e);
break;
}
};
let decoded = decode_rec(&body, None);
let body_text = String::from_utf8_lossy(&decoded);
if body_text.contains(&filter_term) {
self.filtered_selection.push(*h);
self.filtered_order.insert(*h, i);
self.selection.insert(*h, false);
Err(e) => {
self.length = 0;
let message = format!("No results for `{}`.", filter_term);
log(
format!("Failed to search for term {}: {}", filter_term, e,),
ERROR,
);
self.content =
CellBuffer::new_with_context(message.len(), 1, Cell::with_char(' '), context);
write_string_to_grid(
&message,
&mut self.content,
Color::Default,
Color::Default,
Attr::Default,
((0, 0), (message.len() - 1, 0)),
false,
);
}
}
if let Some(error) = error {
self.length = 0;
let message = format!("Error: {}", error.to_string());
self.content =
CellBuffer::new_with_context(message.len(), 1, Cell::with_char(' '), context);
write_string_to_grid(
&message,
&mut self.content,
Color::Default,
Color::Default,
Attr::Default,
((0, 0), (message.len() - 1, 0)),
false,
);
} else if !self.filtered_selection.is_empty() {

if !self.filtered_selection.is_empty() {
self.filter_term = filter_term.to_string();
self.cursor_pos.2 = std::cmp::min(self.filtered_selection.len() - 1, self.cursor_pos.2);
self.length = self.filtered_selection.len();
@@ -608,6 +592,10 @@ impl ConversationsListing {
self.view = ThreadView::new(self.new_cursor_pos, None, context);
}

self.redraw_list(context);
}

fn redraw_list(&mut self, context: &mut Context) {
let account = &context.accounts[self.cursor_pos.0];
let mailbox = &account[self.cursor_pos.1].unwrap();

@@ -619,7 +607,14 @@ impl ConversationsListing {
let mut max_entry_columns = 0;

threads.sort_by(self.sort, self.subsort, &account.collection);
for (idx, root_idx) in threads.root_iter().enumerate() {

let mut threads_iter = if self.filtered_selection.is_empty() {
Box::new(threads.root_iter()) as Box<dyn Iterator<Item = ThreadHash>>
} else {
Box::new(self.filtered_selection.iter().map(|h| *h))
as Box<dyn Iterator<Item = ThreadHash>>
};
for (idx, root_idx) in threads_iter.enumerate() {
self.length += 1;
let thread_node = &threads.thread_nodes()[&root_idx];
let i = if let Some(i) = thread_node.message() {
@@ -805,153 +800,6 @@ impl ConversationsListing {
}
}

fn draw_filtered_selection(&mut self, _context: &mut Context) {
/*
if self.filtered_selection.is_empty() {
return;
}
let account = &context.accounts[self.cursor_pos.0];
let mailbox = if account[self.cursor_pos.1].is_available() {
account[self.cursor_pos.1].unwrap()
} else {
return;
};

let threads = &account.collection.threads[&mailbox.folder.hash()];
self.length = 0;
let mut rows = Vec::with_capacity(1024);
let mut min_width = (0, 0, 0, 0, 0);

for (idx, envelope_hash) in self.filtered_selection.iter().enumerate() {
self.length += 1;
let envelope: &Envelope = &context.accounts[self.cursor_pos.0].get_env(&envelope_hash);
let t_idx = envelope.thread();
let strings = ConversationsListing::make_entry_string(
envelope,
threads[&envelope.thread()].len(),
idx,
threads.is_snoozed(t_idx),
);
min_width.0 = cmp::max(min_width.0, strings.0.grapheme_width()); /* index */
min_width.1 = cmp::max(min_width.1, strings.1.grapheme_width()); /* date */
min_width.2 = cmp::max(min_width.2, strings.2.grapheme_width()); /* from */
min_width.3 = cmp::max(min_width.3, strings.3.grapheme_width()); /* flags */
min_width.4 = cmp::max(min_width.4, strings.4.grapheme_width()); /* subject */
rows.push(strings);
}

/* index column */
self.data_columns.columns[0] =
CellBuffer::new(min_width.0, rows.len(), Cell::with_char(' '));
/* date column */
self.data_columns.columns[1] =
CellBuffer::new(min_width.1, rows.len(), Cell::with_char(' '));
/* from column */
self.data_columns.columns[2] =
CellBuffer::new(min_width.2, rows.len(), Cell::with_char(' '));
/* flags column */
self.data_columns.columns[3] =
CellBuffer::new(min_width.3, rows.len(), Cell::with_char(' '));
/* subject column */
self.data_columns.columns[4] =
CellBuffer::new(min_width.4, rows.len(), Cell::with_char(' '));

for ((idx, envelope_hash), strings) in self.filtered_selection.iter().enumerate().zip(rows)
{
let envelope: &Envelope = &context.accounts[self.cursor_pos.0].get_env(&envelope_hash);
let fg_color = if !envelope.is_seen() {
Color::Byte(0)
} else {
Color::Default
};
let bg_color = if self.selection[&envelope_hash] {
Color::Byte(210)
} else if !envelope.is_seen() {
Color::Byte(251)
} else {
Color::Default
};
let (x, _) = write_string_to_grid(
&strings.0,
&mut self.data_columns.columns[0],
fg_color,
bg_color,
Attr::Default,
((0, idx), (min_width.0, idx)),
false,
);
for x in x..min_width.0 {
self.data_columns.columns[0][(x, idx)].set_bg(bg_color);
}
let (x, _) = write_string_to_grid(
&strings.1,
&mut self.data_columns.columns[1],
fg_color,
bg_color,
Attr::Default,
((0, idx), (min_width.1, idx)),
false,
);
for x in x..min_width.1 {
self.data_columns.columns[1][(x, idx)].set_bg(bg_color);
}
let (x, _) = write_string_to_grid(
&strings.2,
&mut self.data_columns.columns[2],
fg_color,
bg_color,
Attr::Default,
((0, idx), (min_width.2, idx)),
false,
);
for x in x..min_width.2 {
self.data_columns.columns[2][(x, idx)].set_bg(bg_color);
}
let (x, _) = write_string_to_grid(
&strings.3,
&mut self.data_columns.columns[3],
fg_color,
bg_color,
Attr::Default,
((0, idx), (min_width.3, idx)),
false,
);
for x in x..min_width.3 {
self.data_columns.columns[3][(x, idx)].set_bg(bg_color);
}
let (x, _) = write_string_to_grid(
&strings.4,
&mut self.data_columns.columns[4],
fg_color,
bg_color,
Attr::Default,
((0, idx), (min_width.4, idx)),
false,
);
for x in x..min_width.4 {
self.data_columns.columns[4][(x, idx)].set_bg(bg_color);
}
match (
threads.is_snoozed(envelope.thread()),
&context.accounts[self.cursor_pos.0]
.get_env(&envelope_hash)
.has_attachments(),
) {
(true, true) => {
self.data_columns.columns[3][(0, idx)].set_fg(Color::Byte(103));
self.data_columns.columns[3][(2, idx)].set_fg(Color::Red);
}
(true, false) => {
self.data_columns.columns[3][(0, idx)].set_fg(Color::Red);
}
(false, true) => {
self.data_columns.columns[3][(0, idx)].set_fg(Color::Byte(103));
}
(false, false) => {}
}
}
*/
}
fn get_thread_under_cursor(&self, cursor: usize, context: &Context) -> ThreadHash {
let account = &context.accounts[self.cursor_pos.0];
let folder_hash = account[self.cursor_pos.1].unwrap().folder.hash();
@@ -1057,7 +905,7 @@ impl Component for ConversationsListing {
};

if !self.filtered_selection.is_empty() {
self.draw_filtered_selection(context);
self.redraw_list(context);
let (x, y) = write_string_to_grid(
&format!("Filter (Press ESC to exit): {}", self.filter_term),
grid,
@@ -1074,7 +922,6 @@ impl Component for ConversationsListing {

self.draw_list(grid, (set_y(upper_left, y + 1), bottom_right), context);
self.dirty = false;
return;
}
if !self.row_updates.is_empty() {
/* certain rows need to be updated (eg an unseen message was just set seen)

+ 24
- 0
ui/src/conf/accounts.rs View File

@@ -98,6 +98,29 @@ impl MailboxEntry {
false
}
}

pub fn as_result(&self) -> Result<&Mailbox> {
match self {
MailboxEntry::Available(ref m) => Ok(m),
MailboxEntry::Parsing(ref m, _, _) => Ok(m),
MailboxEntry::Failed(ref e) => Err(MeliError::new(format!(
"Mailbox is not available: {}",
e.to_string()
))),
}
}

pub fn as_mut_result(&mut self) -> Result<&mut Mailbox> {
match self {
MailboxEntry::Available(ref mut m) => Ok(m),
MailboxEntry::Parsing(ref mut m, _, _) => Ok(m),
MailboxEntry::Failed(ref e) => Err(MeliError::new(format!(
"Mailbox is not available: {}",
e.to_string()
))),
}
}

pub fn unwrap_mut(&mut self) -> &mut Mailbox {
match self {
MailboxEntry::Available(ref mut m) => m,
@@ -105,6 +128,7 @@ impl MailboxEntry {
e => panic!(format!("mailbox is not available! {:#}", e)),
}
}

pub fn unwrap(&self) -> &Mailbox {
match self {
MailboxEntry::Available(ref m) => m,

+ 5
- 0
ui/src/lib.rs View File

@@ -69,6 +69,11 @@ pub use crate::conf::*;
pub mod workers;
pub use crate::workers::*;

#[cfg(feature = "sqlite3")]
pub mod sqlite3;

pub mod search;

pub use crate::username::*;
pub mod username {
use libc;

+ 61
- 0
ui/src/search.rs View File

@@ -0,0 +1,61 @@
/*
* meli - ui crate.
*
* Copyright 2017-2018 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 <http://www.gnu.org/licenses/>.
*/

use crate::state::Context;
use melib::{backends::FolderHash, email::EnvelopeHash, error::Result, StackVec};

pub fn filter(
filter_term: &str,
context: &Context,
account_idx: usize,
folder_hash: FolderHash,
) -> Result<StackVec<EnvelopeHash>> {
#[cfg(feature = "sqlite3")]
{
crate::sqlite3::search(filter_term, context, account_idx, folder_hash)
}

#[cfg(not(feature = "sqlite3"))]
{
let mut ret = StackVec::new();

let account = &context.accounts[account_idx];
for env_hash in account.folders[folder_hash].as_result()?.envelopes {
let envelope = &account.collection[&env_hash];
if envelope.subject().contains(&filter_term) {
ret.push(env_hash);
continue;
}
if envelope.field_from_to_string().contains(&filter_term) {
ret.push(env_hash);
continue;
}
let op = account.operation(env_hash);
let body = envelope.body(op)?;
let decoded = decode_rec(&body, None);
let body_text = String::from_utf8_lossy(&decoded);
if body_text.contains(&filter_term) {
ret.push(env_hash);
}
}
ret
}
}

+ 194
- 0
ui/src/sqlite3.rs View File

@@ -0,0 +1,194 @@
/*
* meli - sqlite3.rs
*
* 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 <http://www.gnu.org/licenses/>.
*/

use crate::state::Context;
use melib::{backends::FolderHash, email::EnvelopeHash, MeliError, Result, StackVec};
use rusqlite::{params, Connection};
use std::convert::TryInto;

pub fn open_db(context: &crate::state::Context) -> Result<Connection> {
let data_dir =
xdg::BaseDirectories::with_prefix("meli").map_err(|e| MeliError::new(e.to_string()))?;
let conn = Connection::open(
data_dir
.place_data_file("meli.db")
.map_err(|e| MeliError::new(e.to_string()))?,
)
.map_err(|e| MeliError::new(e.to_string()))?;
//let conn = Connection::open_in_memory().map_err(|e| MeliError::new(e.to_string()))?;

/*
*
pub struct Envelope {
date: String,
from: Vec<Address>,
to: Vec<Address>,
cc: Vec<Address>,
bcc: Vec<Address>,
subject: Option<Vec<u8>>,
message_id: MessageID,
in_reply_to: Option<MessageID>,
references: Option<References>,
other_headers: FnvHashMap<String, String>,

timestamp: UnixTimestamp,
thread: ThreadHash,

hash: EnvelopeHash,

flags: Flag,
has_attachments: bool,
}
*/

conn.execute_batch(
"CREATE TABLE IF NOT EXISTS envelopes (
id INTEGER PRIMARY KEY,
hash BLOB NOT NULL,
date TEXT NOT NULL,
_from TEXT NOT NULL,
_to TEXT NOT NULL,
cc TEXT NOT NULL,
bcc TEXT NOT NULL,
subject TEXT NOT NULL,
message_id TEXT NOT NULL,
in_reply_to TEXT NOT NULL,
_references TEXT NOT NULL,
flags INTEGER NOT NULL,
has_attachments BOOLEAN NOT NULL,
body_text TEXT NOT NULL,
timestamp BLOB NOT NULL
);


CREATE INDEX IF NOT EXISTS envelope_timestamp_index ON envelopes (timestamp);
CREATE INDEX IF NOT EXISTS envelope__from_index ON envelopes (_from);
CREATE INDEX IF NOT EXISTS envelope__to_index ON envelopes (_to);
CREATE INDEX IF NOT EXISTS envelope_cc_index ON envelopes (cc);
CREATE INDEX IF NOT EXISTS envelope_bcc_index ON envelopes (bcc);
CREATE INDEX IF NOT EXISTS envelope_message_id_index ON envelopes (message_id);

CREATE VIRTUAL TABLE IF NOT EXISTS fts USING fts5(subject, body_text, content=envelopes, content_rowid=id);

-- Triggers to keep the FTS index up to date.
CREATE TRIGGER IF NOT EXISTS envelopes_ai AFTER INSERT ON envelopes BEGIN
INSERT INTO fts(rowid, subject, body_text) VALUES (new.id, new.subject, new.body_text);
END;

CREATE TRIGGER IF NOT EXISTS envelopes_ad AFTER DELETE ON envelopes BEGIN
INSERT INTO fts(fts, rowid, subject, body_text) VALUES('delete', old.id, old.subject, old.body_text);
END;

CREATE TRIGGER IF NOT EXISTS envelopes_au AFTER UPDATE ON envelopes BEGIN
INSERT INTO fts(fts, rowid, subject, body_text) VALUES('delete', old.id, old.subject, old.body_text);
INSERT INTO fts(rowid, subject, body_text) VALUES (new.id, new.subject, new.body_text);
END; ",
)
.map_err(|e| MeliError::new(e.to_string()))?;

Ok(conn)
}

pub fn insert(context: &crate::state::Context) -> Result<()> {
let data_dir =
xdg::BaseDirectories::with_prefix("meli").map_err(|e| MeliError::new(e.to_string()))?;
let conn = Connection::open(
data_dir
.place_data_file("meli.db")
.map_err(|e| MeliError::new(e.to_string()))?,
)
.map_err(|e| MeliError::new(e.to_string()))?;
for acc in context.accounts.iter() {
debug!("inserting {} envelopes", acc.collection.envelopes.len());
for e in acc.collection.envelopes.values() {
conn.execute(
"INSERT OR REPLACE INTO envelopes (hash, date, _from, _to, cc, bcc, subject, message_id, in_reply_to, _references, flags, has_attachments, body_text, timestamp)
VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?9, ?10, ?11, ?12, ?13, ?14)",
params![e.hash().to_be_bytes().to_vec(), e.date_as_str(), e.field_from_to_string(), e.field_to_to_string(), e.field_cc_to_string(), e.field_bcc_to_string(), e.subject().into_owned().trim_end_matches('\u{0}'), e.message_id_display().to_string(), e.in_reply_to_display().map(|f| f.to_string()).unwrap_or(String::new()), e.field_references_to_string(), i64::from(e.flags().bits()), if e.has_attachments() { 1 } else { 0 }, String::from("sdfsa"), e.hash().to_be_bytes().to_vec()],
)
.map_err(|e| MeliError::new(e.to_string()))?;
}
}

Ok(())
}

pub fn search(
term: &str,
_context: &Context,
_account_idx: usize,
_folder_hash: FolderHash,
) -> Result<StackVec<EnvelopeHash>> {
let data_dir =
xdg::BaseDirectories::with_prefix("meli").map_err(|e| MeliError::new(e.to_string()))?;
let conn = Connection::open(
data_dir
.place_data_file("meli.db")
.map_err(|e| MeliError::new(e.to_string()))?,
)
.map_err(|e| MeliError::new(e.to_string()))?;
let mut stmt= conn.prepare(
"SELECT hash FROM envelopes INNER JOIN fts ON fts.rowid = envelopes.id WHERE fts MATCH ?;")
.map_err(|e| MeliError::new(e.to_string()))?;

let results = stmt
.query_map(&[term], |row| Ok(row.get(0)?))
.map_err(|e| MeliError::new(e.to_string()))?
.map(|r: std::result::Result<Vec<u8>, rusqlite::Error>| {
Ok(u64::from_be_bytes(
r.map_err(|e| MeliError::new(e.to_string()))?
.as_slice()
.try_into()
.map_err(|e: std::array::TryFromSliceError| MeliError::new(e.to_string()))?,
))
})
.collect::<Result<StackVec<EnvelopeHash>>>();
results
}

pub fn from(term: &str) -> Result<StackVec<EnvelopeHash>> {
let data_dir =
xdg::BaseDirectories::with_prefix("meli").map_err(|e| MeliError::new(e.to_string()))?;
let conn = Connection::open_with_flags(
data_dir
.place_data_file("meli.db")
.map_err(|e| MeliError::new(e.to_string()))?,
rusqlite::OpenFlags::SQLITE_OPEN_READ_ONLY,
)
.map_err(|e| MeliError::new(e.to_string()))?;
let mut stmt = conn
.prepare("SELECT hash FROM envelopes WHERE _from LIKE ?;")
.map_err(|e| MeliError::new(e.to_string()))?;

let results = stmt
.query_map(&[term.trim()], |row| Ok(row.get(0)?))
.map_err(|e| MeliError::new(e.to_string()))?
.map(|r: std::result::Result<Vec<u8>, rusqlite::Error>| {
Ok(u64::from_be_bytes(
r.map_err(|e| MeliError::new(e.to_string()))?
.as_slice()
.try_into()
.map_err(|e: std::array::TryFromSliceError| MeliError::new(e.to_string()))?,
))
})
.collect::<Result<StackVec<EnvelopeHash>>>();
results
}

+ 8
- 0
ui/src/state.rs View File

@@ -529,6 +529,14 @@ impl State {

/// Convert user commands to actions/method calls.
fn parse_command(&mut self, cmd: &str) {
if cmd == "insert" {
crate::sqlite3::insert(&self.context).unwrap();
return;
} else if cmd.starts_with("_from") {
debug!(crate::sqlite3::from(&cmd["_from".len()..]));
return;
}

let result = parse_command(&cmd.as_bytes()).to_full_result();

if let Ok(v) = result {

Loading…
Cancel
Save