add sqlite3 feature WIP
parent
6b5ed25289
commit
3af6f338ce
|
@ -309,6 +309,16 @@ dependencies = [
|
|||
"synstructure 0.10.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[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"
|
||||
|
@ -458,6 +468,20 @@ dependencies = [
|
|||
"pkg-config 0.3.14 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[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"
|
||||
|
@ -474,6 +498,14 @@ dependencies = [
|
|||
"cfg-if 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[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"
|
||||
|
@ -974,6 +1006,20 @@ dependencies = [
|
|||
"winapi 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[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"
|
||||
|
@ -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"
|
||||
|
|
|
@ -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>> {
|
||||
|
|
|
@ -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 = []
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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…
Reference in New Issue