2018-08-07 12:01:15 +00:00
/*
2020-02-04 13:52:12 +00:00
* meli
2018-08-07 12:01:15 +00:00
*
* 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/>.
* /
2020-02-04 13:52:12 +00:00
/*! UI types used throughout meli.
*
* The ` segment_tree ` module performs maximum range queries . This is used in getting the maximum
* element of a column within a specific range in e - mail lists . That way a very large value that
* is not the in the currently displayed page does not cause the column to be rendered bigger
* than it has to .
*
* ` UIMode ` describes the application ' s .. . mode . Same as in the modal editor ` vi ` .
*
* ` UIEvent ` is the type passed around ` Component ` s when something happens .
* /
2019-03-03 12:24:15 +00:00
extern crate serde ;
2018-08-08 08:50:51 +00:00
#[ macro_use ]
2018-08-06 13:53:23 +00:00
mod helpers ;
pub use self ::helpers ::* ;
2020-07-25 10:08:36 +00:00
use super ::command ::Action ;
2020-07-04 12:59:09 +00:00
use super ::jobs ::JobId ;
2019-03-12 18:35:23 +00:00
use super ::terminal ::* ;
Add smtp client support for sending mail in UI
`mailer_command` was removed, and a new setting `send_mail` was added.
Its possible values are a string, consisting of a shell command to
execute, or settings to configure an smtp server connection. The
configuration I used for testing this is:
[composing]
send_mail = { hostname = "smtp.mail.tld", port = 587, auth = { type = "auto", username = "yoshi", password = { type = "command_eval", value = "gpg2 --no-tty -q -d ~/.passwords/msmtp/yoshi.gpg" } }, security = { type = "STARTTLS" } }
For local smtp server:
[composing]
send_mail = { hostname = "localhost", port = 25, auth = { type = "none" }, security = { type = "none" } }
2020-07-15 11:38:43 +00:00
use crate ::components ::{ Component , ComponentId } ;
2018-08-06 13:53:23 +00:00
2020-05-10 19:05:04 +00:00
use melib ::backends ::{ AccountHash , MailboxHash } ;
2020-06-23 14:23:42 +00:00
use melib ::{ EnvelopeHash , RefreshEvent , ThreadHash } ;
2019-11-19 20:47:34 +00:00
use nix ::unistd ::Pid ;
2018-08-06 13:53:23 +00:00
use std ::fmt ;
2018-08-07 12:01:15 +00:00
use std ::thread ;
2018-09-03 22:49:29 +00:00
use uuid ::Uuid ;
2018-08-06 13:53:23 +00:00
2018-08-26 16:29:12 +00:00
#[ derive(Debug) ]
pub enum StatusEvent {
DisplayMessage ( String ) ,
BufClear ,
BufSet ( String ) ,
2019-09-15 06:41:52 +00:00
UpdateStatus ( String ) ,
2020-06-30 08:40:26 +00:00
NewJob ( JobId ) ,
JobFinished ( JobId ) ,
2020-07-24 19:06:19 +00:00
JobCanceled ( JobId ) ,
2018-08-26 16:29:12 +00:00
}
2018-08-06 13:53:23 +00:00
/// `ThreadEvent` encapsulates all of the possible values we need to transfer between our threads
/// to the main process.
#[ derive(Debug) ]
pub enum ThreadEvent {
2019-12-11 23:01:11 +00:00
NewThread ( thread ::ThreadId , String ) ,
2018-08-06 13:53:23 +00:00
/// User input.
2020-05-29 12:35:29 +00:00
Input ( ( Key , Vec < u8 > ) ) ,
2019-11-05 06:32:27 +00:00
/// User input and input as raw bytes.
2020-02-26 08:54:10 +00:00
/// A watched Mailbox has been refreshed.
2018-09-12 12:10:19 +00:00
RefreshMailbox ( Box < RefreshEvent > ) ,
2019-04-10 20:37:20 +00:00
UIEvent ( UIEvent ) ,
2019-09-11 14:57:55 +00:00
/// A thread has updated some of its information
Pulse ,
2018-08-06 13:53:23 +00:00
//Decode { _ }, // For gpg2 signature check
2020-06-26 15:31:37 +00:00
JobFinished ( JobId ) ,
2018-08-06 13:53:23 +00:00
}
impl From < RefreshEvent > for ThreadEvent {
fn from ( event : RefreshEvent ) -> Self {
2018-09-12 12:10:19 +00:00
ThreadEvent ::RefreshMailbox ( Box ::new ( event ) )
2018-08-06 13:53:23 +00:00
}
}
#[ derive(Debug) ]
pub enum ForkType {
2019-11-05 06:32:27 +00:00
/// Already finished fork, we only want to restore input/output
Finished ,
/// Embed pty
2019-11-19 20:47:34 +00:00
Embed ( Pid ) ,
2018-08-06 13:53:23 +00:00
Generic ( std ::process ::Child ) ,
NewDraft ( File , std ::process ::Child ) ,
}
2019-09-15 20:35:30 +00:00
#[ derive(Debug) ]
pub enum NotificationType {
INFO ,
ERROR ,
NewMail ,
}
2018-08-06 13:53:23 +00:00
#[ derive(Debug) ]
2019-04-10 20:37:20 +00:00
pub enum UIEvent {
2018-08-06 13:53:23 +00:00
Input ( Key ) ,
2020-07-25 10:08:36 +00:00
CmdInput ( Key ) ,
2019-02-25 09:11:56 +00:00
InsertInput ( Key ) ,
2019-11-05 06:32:27 +00:00
EmbedInput ( ( Key , Vec < u8 > ) ) ,
2018-08-06 13:53:23 +00:00
//Quit?
Resize ,
/// Force redraw.
Fork ( ForkType ) ,
ChangeMailbox ( usize ) ,
ChangeMode ( UIMode ) ,
Command ( String ) ,
2019-09-15 20:35:30 +00:00
Notification ( Option < String > , String , Option < NotificationType > ) ,
2018-08-06 13:53:23 +00:00
Action ( Action ) ,
2018-08-26 16:29:12 +00:00
StatusEvent ( StatusEvent ) ,
2020-02-26 08:54:10 +00:00
MailboxUpdate ( ( usize , MailboxHash ) ) , // (account_idx, mailbox_idx)
MailboxDelete ( ( usize , MailboxHash ) ) ,
MailboxCreate ( ( usize , MailboxHash ) ) ,
2020-02-08 21:42:31 +00:00
AccountStatusChange ( usize ) ,
2019-04-10 19:01:02 +00:00
ComponentKill ( Uuid ) ,
2020-05-10 19:05:04 +00:00
WorkerProgress ( AccountHash , MailboxHash ) ,
2020-02-26 08:54:10 +00:00
StartupCheck ( MailboxHash ) ,
2018-09-15 17:09:41 +00:00
RefreshEvent ( Box < RefreshEvent > ) ,
2019-04-01 20:53:06 +00:00
EnvelopeUpdate ( EnvelopeHash ) ,
2019-06-05 21:27:40 +00:00
EnvelopeRename ( EnvelopeHash , EnvelopeHash ) , // old_hash, new_hash
2020-06-23 14:23:42 +00:00
EnvelopeRemove ( EnvelopeHash , ThreadHash ) ,
2020-02-19 14:57:37 +00:00
Contacts ( ContactEvent ) ,
Compose ( ComposeEvent ) ,
Add smtp client support for sending mail in UI
`mailer_command` was removed, and a new setting `send_mail` was added.
Its possible values are a string, consisting of a shell command to
execute, or settings to configure an smtp server connection. The
configuration I used for testing this is:
[composing]
send_mail = { hostname = "smtp.mail.tld", port = 587, auth = { type = "auto", username = "yoshi", password = { type = "command_eval", value = "gpg2 --no-tty -q -d ~/.passwords/msmtp/yoshi.gpg" } }, security = { type = "STARTTLS" } }
For local smtp server:
[composing]
send_mail = { hostname = "localhost", port = 25, auth = { type = "none" }, security = { type = "none" } }
2020-07-15 11:38:43 +00:00
FinishedUIDialog ( ComponentId , UIMessage ) ,
GlobalUIDialog ( Box < dyn Component > ) ,
2020-01-08 15:07:14 +00:00
Timer ( u8 ) ,
2018-09-15 17:09:41 +00:00
}
impl From < RefreshEvent > for UIEvent {
fn from ( event : RefreshEvent ) -> Self {
2019-04-10 20:37:20 +00:00
UIEvent ::RefreshEvent ( Box ::new ( event ) )
2018-09-15 17:09:41 +00:00
}
2018-08-06 13:53:23 +00:00
}
#[ derive(Debug, PartialEq, Copy, Clone) ]
pub enum UIMode {
Normal ,
2019-02-25 09:11:56 +00:00
Insert ,
2019-11-05 06:32:27 +00:00
/// Forward input to an embed pseudoterminal.
Embed ,
2020-07-25 10:08:36 +00:00
Command ,
2018-08-06 13:53:23 +00:00
Fork ,
}
impl fmt ::Display for UIMode {
fn fmt ( & self , f : & mut fmt ::Formatter ) -> fmt ::Result {
write! (
f ,
" {} " ,
match * self {
UIMode ::Normal = > " NORMAL " ,
2019-02-25 09:11:56 +00:00
UIMode ::Insert = > " INSERT " ,
2020-07-25 10:08:36 +00:00
UIMode ::Command = > " COMMAND " ,
2018-08-06 13:53:23 +00:00
UIMode ::Fork = > " FORK " ,
2019-11-05 06:32:27 +00:00
UIMode ::Embed = > " EMBED " ,
2018-08-06 13:53:23 +00:00
}
)
}
}
/// An event notification that is passed to Entities for handling.
pub struct Notification {
_title : String ,
_content : String ,
_timestamp : std ::time ::Instant ,
}
2019-12-12 09:11:32 +00:00
pub mod segment_tree {
/*! Simple segment tree implementation for maximum in range queries. This is useful if given an
* array of numbers you want to get the maximum value inside an interval quickly .
* /
2020-01-08 15:04:44 +00:00
use smallvec ::SmallVec ;
2019-12-12 09:11:32 +00:00
use std ::convert ::TryFrom ;
use std ::iter ::FromIterator ;
#[ derive(Default, Debug, Clone) ]
pub struct SegmentTree {
2020-06-07 11:25:33 +00:00
pub array : SmallVec < [ u8 ; 1024 ] > ,
2020-01-08 15:04:44 +00:00
tree : SmallVec < [ u8 ; 1024 ] > ,
2019-12-12 09:11:32 +00:00
}
2020-01-08 15:04:44 +00:00
impl From < SmallVec < [ u8 ; 1024 ] > > for SegmentTree {
fn from ( val : SmallVec < [ u8 ; 1024 ] > ) -> SegmentTree {
2019-12-12 09:11:32 +00:00
SegmentTree ::new ( val )
}
}
impl SegmentTree {
2020-01-08 15:04:44 +00:00
pub fn new ( val : SmallVec < [ u8 ; 1024 ] > ) -> SegmentTree {
2019-12-12 09:11:32 +00:00
if val . is_empty ( ) {
return SegmentTree {
array : val . clone ( ) ,
tree : val ,
} ;
}
let height = ( f64 ::from ( u32 ::try_from ( val . len ( ) ) . unwrap_or ( 0 ) ) )
. log2 ( )
. ceil ( ) as u32 ;
2020-01-15 10:31:49 +00:00
let max_size = 2 * ( 2_ usize . pow ( height ) ) ;
2019-12-12 09:11:32 +00:00
2020-01-08 15:04:44 +00:00
let mut segment_tree : SmallVec < [ u8 ; 1024 ] > =
SmallVec ::from_iter ( core ::iter ::repeat ( 0 ) . take ( max_size ) ) ;
2019-12-12 09:11:32 +00:00
for i in 0 .. val . len ( ) {
segment_tree [ val . len ( ) + i ] = val [ i ] ;
}
for i in ( 1 .. val . len ( ) ) . rev ( ) {
segment_tree [ i ] = std ::cmp ::max ( segment_tree [ 2 * i ] , segment_tree [ 2 * i + 1 ] ) ;
}
SegmentTree {
array : val ,
tree : segment_tree ,
}
}
/// (left, right) is inclusive
pub fn get_max ( & self , mut left : usize , mut right : usize ) -> u8 {
let len = self . array . len ( ) ;
debug_assert! ( left < = right ) ;
if right > = len {
right = len . saturating_sub ( 1 ) ;
}
left + = len ;
right + = len + 1 ;
let mut max = 0 ;
while left < right {
if ( left & 1 ) > 0 {
max = std ::cmp ::max ( max , self . tree [ left ] ) ;
left + = 1 ;
}
if ( right & 1 ) > 0 {
right - = 1 ;
max = std ::cmp ::max ( max , self . tree [ right ] ) ;
}
left / = 2 ;
right / = 2 ;
}
max
}
2020-05-18 17:58:55 +00:00
pub fn update ( & mut self , pos : usize , value : u8 ) {
let mut ctr = pos + self . array . len ( ) ;
// Update leaf node value
self . tree [ ctr ] = value ;
while ctr > 1 {
// move up one level
ctr > > = 1 ;
self . tree [ ctr ] = std ::cmp ::max ( self . tree [ 2 * ctr ] , self . tree [ 2 * ctr + 1 ] ) ;
}
}
2019-12-12 09:11:32 +00:00
}
#[ test ]
fn test_segment_tree ( ) {
2020-01-08 15:04:44 +00:00
let array : SmallVec < [ u8 ; 1024 ] > = [ 9 , 1 , 17 , 2 , 3 , 23 , 4 , 5 , 6 , 37 ]
2020-02-26 10:25:57 +00:00
. iter ( )
2019-12-12 09:11:32 +00:00
. cloned ( )
2020-01-08 15:04:44 +00:00
. collect ::< SmallVec < [ u8 ; 1024 ] > > ( ) ;
2020-05-18 17:58:55 +00:00
let mut segment_tree = SegmentTree ::from ( array . clone ( ) ) ;
2019-12-12 09:11:32 +00:00
assert_eq! ( segment_tree . get_max ( 0 , 5 ) , 23 ) ;
assert_eq! ( segment_tree . get_max ( 6 , 9 ) , 37 ) ;
2020-05-18 17:58:55 +00:00
segment_tree . update ( 2_ usize , 24_ u8 ) ;
assert_eq! ( segment_tree . get_max ( 0 , 5 ) , 24 ) ;
2019-12-12 09:11:32 +00:00
}
}
2020-01-15 10:36:31 +00:00
#[ derive(Debug) ]
pub struct RateLimit {
last_tick : std ::time ::Instant ,
pub timer : crate ::timer ::PosixTimer ,
rate : std ::time ::Duration ,
reqs : u64 ,
millis : std ::time ::Duration ,
pub active : bool ,
}
2020-01-21 22:04:14 +00:00
//FIXME: tests.
2020-01-15 10:36:31 +00:00
impl RateLimit {
pub fn new ( reqs : u64 , millis : u64 ) -> Self {
RateLimit {
last_tick : std ::time ::Instant ::now ( ) ,
timer : crate ::timer ::PosixTimer ::new_with_signal (
std ::time ::Duration ::from_secs ( 0 ) ,
2020-05-18 17:58:20 +00:00
std ::time ::Duration ::from_millis ( millis ) ,
2020-01-15 10:36:31 +00:00
nix ::sys ::signal ::Signal ::SIGALRM ,
)
. unwrap ( ) ,
rate : std ::time ::Duration ::from_millis ( millis / reqs ) ,
reqs ,
millis : std ::time ::Duration ::from_millis ( millis ) ,
active : false ,
}
}
pub fn reset ( & mut self ) {
self . last_tick = std ::time ::Instant ::now ( ) ;
self . active = false ;
}
pub fn tick ( & mut self ) -> bool {
let now = std ::time ::Instant ::now ( ) ;
2020-05-18 17:58:20 +00:00
if self . last_tick + self . rate > now {
self . active = false ;
} else {
2020-01-15 10:36:31 +00:00
self . timer . rearm ( ) ;
2020-05-18 17:58:20 +00:00
self . last_tick = now ;
2020-01-15 10:36:31 +00:00
self . active = true ;
}
2020-05-18 17:58:20 +00:00
self . active
2020-01-15 10:36:31 +00:00
}
#[ inline(always) ]
pub fn id ( & self ) -> u8 {
self . timer . si_value
}
}
2020-05-18 17:58:20 +00:00
#[ test ]
fn test_rate_limit ( ) {
use std ::sync ::{ Arc , Condvar , Mutex } ;
/* RateLimit sends a SIGALRM with its timer value in siginfo_t. */
let pair = Arc ::new ( ( Mutex ::new ( None ) , Condvar ::new ( ) ) ) ;
let pair2 = pair . clone ( ) ;
/* self-pipe trick:
* since we can only use signal - safe functions in the signal handler , make a pipe and
* write one byte to it from the handler . Counting the number of bytes in the pipe can tell
* us how many times the handler was called * /
let ( alarm_pipe_r , alarm_pipe_w ) = nix ::unistd ::pipe ( ) . unwrap ( ) ;
nix ::fcntl ::fcntl (
alarm_pipe_r ,
nix ::fcntl ::FcntlArg ::F_SETFL ( nix ::fcntl ::OFlag ::O_NONBLOCK ) ,
)
. expect ( " Could not set pipe to NONBLOCK? " ) ;
let alarm_handler = move | info : & nix ::libc ::siginfo_t | {
let value = unsafe { info . si_value ( ) . sival_ptr as u8 } ;
let ( lock , cvar ) = & * pair2 ;
let mut started = lock . lock ( ) . unwrap ( ) ;
/* set mutex to timer value */
* started = Some ( value ) ;
/* notify condvar in order to wake up the test thread */
cvar . notify_all ( ) ;
nix ::unistd ::write ( alarm_pipe_w , & [ value ] ) . expect ( " Could not write inside alarm handler? " ) ;
} ;
unsafe {
signal_hook_registry ::register_sigaction ( signal_hook ::SIGALRM , alarm_handler ) . unwrap ( ) ;
}
/* Accept at most one request per 3 milliseconds */
let mut rt = RateLimit ::new ( 1 , 3 ) ;
std ::thread ::sleep ( std ::time ::Duration ::from_millis ( 2000 ) ) ;
let ( lock , cvar ) = & * pair ;
2020-05-29 19:21:12 +00:00
let started = lock . lock ( ) . unwrap ( ) ;
2020-05-18 17:58:20 +00:00
let result = cvar
. wait_timeout ( started , std ::time ::Duration ::from_millis ( 100 ) )
. unwrap ( ) ;
/* assert that the handler was called with rt's timer id */
assert_eq! ( * result . 0 , Some ( rt . id ( ) ) ) ;
drop ( result ) ;
drop ( pair ) ;
let mut buf = [ 0 ; 1 ] ;
nix ::unistd ::read ( alarm_pipe_r , buf . as_mut ( ) ) . expect ( " Could not read from self-pipe? " ) ;
/* assert that only one request per 3 milliseconds is accepted */
for _ in 0 .. 5 {
assert! ( rt . tick ( ) ) ;
std ::thread ::sleep ( std ::time ::Duration ::from_millis ( 1 ) ) ;
assert! ( ! rt . tick ( ) ) ;
std ::thread ::sleep ( std ::time ::Duration ::from_millis ( 1 ) ) ;
assert! ( ! rt . tick ( ) ) ;
std ::thread ::sleep ( std ::time ::Duration ::from_millis ( 1 ) ) ;
/* How many times was the signal handler called? We've slept for at least 3
* milliseconds , so it should have been called once * /
let mut ctr = 0 ;
while nix ::unistd ::read ( alarm_pipe_r , buf . as_mut ( ) )
. map ( | s | s > 0 )
. unwrap_or ( false )
{
ctr + = 1 ;
}
assert_eq! ( ctr , 1 ) ;
}
/* next, test at most 100 requests per second */
let mut rt = RateLimit ::new ( 100 , 1000 ) ;
for _ in 0 .. 5 {
let mut ctr = 0 ;
for _ in 0 .. 500 {
if rt . tick ( ) {
ctr + = 1 ;
}
std ::thread ::sleep ( std ::time ::Duration ::from_millis ( 2 ) ) ;
}
/* around 100 requests should succeed. might be 99 if in first loop, since
* RateLimit ::new ( ) has a delay * /
assert! ( ctr > 97 & & ctr < 103 ) ;
/* alarm should expire in 1 second */
std ::thread ::sleep ( std ::time ::Duration ::from_millis ( 1000 ) ) ;
/* How many times was the signal handler called? */
ctr = 0 ;
while nix ::unistd ::read ( alarm_pipe_r , buf . as_mut ( ) )
. map ( | s | s > 0 )
. unwrap_or ( false )
{
ctr + = 1 ;
}
assert_eq! ( ctr , 1 ) ;
}
/* next, test at most 500 requests per second */
let mut rt = RateLimit ::new ( 500 , 1000 ) ;
for _ in 0 .. 5 {
let mut ctr = 0 ;
for _ in 0 .. 500 {
if rt . tick ( ) {
ctr + = 1 ;
}
std ::thread ::sleep ( std ::time ::Duration ::from_millis ( 2 ) ) ;
}
/* all requests should succeed. */
assert! ( ctr < 503 & & ctr > 497 ) ;
/* alarm should expire in 1 second */
std ::thread ::sleep ( std ::time ::Duration ::from_millis ( 1000 ) ) ;
/* How many times was the signal handler called? */
ctr = 0 ;
while nix ::unistd ::read ( alarm_pipe_r , buf . as_mut ( ) )
. map ( | s | s > 0 )
. unwrap_or ( false )
{
ctr + = 1 ;
}
assert_eq! ( ctr , 1 ) ;
}
}
2020-02-19 14:57:37 +00:00
#[ derive(Debug) ]
pub enum ContactEvent {
CreateContacts ( Vec < melib ::Card > ) ,
}
#[ derive(Debug) ]
pub enum ComposeEvent {
SetReceipients ( Vec < melib ::Address > ) ,
}
pub type UIMessage = Box < dyn 'static + std ::any ::Any + Send + Sync > ;