@@ -9,11 +9,13 @@ use email_parser::mime::ContentType;
99use email_parser:: mime:: Entity ;
1010use mailin_embedded:: response:: OK ;
1111use mailin_embedded:: { Handler , Response , Server , SslConfig } ;
12- use once_cell:: sync:: OnceCell ;
12+ use once_cell:: sync:: { Lazy , OnceCell } ;
1313use serde:: Serialize ;
14- use std:: sync:: atomic:: { AtomicBool , Ordering } ;
14+ use std:: net:: { TcpListener , TcpStream } ;
15+ use std:: sync:: atomic:: { AtomicBool , AtomicU64 , Ordering } ;
16+ use std:: sync:: Mutex ;
1517use std:: thread;
16- use std:: net :: TcpListener ;
18+ use std:: time :: Duration ;
1719use tauri:: menu:: MenuBuilder ;
1820use tauri:: Emitter ;
1921use tauri:: Manager ;
@@ -53,52 +55,12 @@ impl Handler for MyHandler {
5355
5456#[ tauri:: command]
5557async fn start_server ( address : Option < String > ) -> Result < String , String > {
56- if SERVER_RUNNING . swap ( true , Ordering :: SeqCst ) {
57- return Ok ( "SMTP server is already running." . into ( ) ) ;
58- }
59-
60- let address = address. unwrap_or ( "127.0.0.1:1025" . into ( ) ) ;
61- let listener = match TcpListener :: bind ( & address) {
62- Ok ( listener) => listener,
63- Err ( err) => {
64- SERVER_RUNNING . store ( false , Ordering :: SeqCst ) ;
65- return Err ( format ! ( "Failed to bind SMTP server on {address}: {err}" ) ) ;
66- }
67- } ;
68-
69- let address_for_thread = address. clone ( ) ;
70- thread:: spawn ( move || {
71- let mut server = Server :: new ( MyHandler :: new ( ) ) ;
72-
73- if let Err ( err) = server
74- . with_name ( "blade mail" )
75- . with_tcp_listener ( listener)
76- . with_ssl ( SslConfig :: None )
77- {
78- eprintln ! ( "Failed to configure SMTP server on {address_for_thread}: {err}" ) ;
79- SERVER_RUNNING . store ( false , Ordering :: SeqCst ) ;
80- return ;
81- }
82-
83- println ! ( "SMTP server is starting on {address_for_thread}..." ) ;
84-
85- if let Err ( err) = server. serve ( ) {
86- eprintln ! ( "SMTP server stopped with error on {address_for_thread}: {err}" ) ;
87- }
88-
89- SERVER_RUNNING . store ( false , Ordering :: SeqCst ) ;
90- } ) ;
91-
92- Ok ( format ! ( "SMTP server started on {address}." ) )
58+ start_server_inner ( address. unwrap_or ( "127.0.0.1:1025" . into ( ) ) )
9359}
9460
9561#[ tauri:: command]
9662fn stop_server ( ) -> String {
97- if SERVER_RUNNING . load ( Ordering :: SeqCst ) {
98- "SMTP server stop is not implemented yet. The SMTP server is still running." . into ( )
99- } else {
100- "SMTP server is not running." . into ( )
101- }
63+ stop_server_inner ( ) . unwrap_or_else ( |err| err)
10264}
10365
10466// MailBox = (Name: String, EmailAddress: String)
@@ -172,6 +134,132 @@ fn collect_addresses(addresses: Option<&Vec<Address>>) -> Option<Vec<String>> {
172134 ( !addresses. is_empty ( ) ) . then_some ( addresses)
173135}
174136
137+ #[ derive( Default ) ]
138+ struct ServerControl {
139+ generation : u64 ,
140+ address : String ,
141+ listener : Option < TcpListener > ,
142+ stop_requested : bool ,
143+ }
144+
145+ static MAIN_WINDOW : OnceCell < WebviewWindow > = OnceCell :: new ( ) ;
146+ static SERVER_RUNNING : AtomicBool = AtomicBool :: new ( false ) ;
147+ static SERVER_GENERATION : AtomicU64 = AtomicU64 :: new ( 0 ) ;
148+ static SERVER_CONTROL : Lazy < Mutex < ServerControl > > = Lazy :: new ( || Mutex :: new ( ServerControl :: default ( ) ) ) ;
149+
150+ fn start_server_inner ( requested_address : String ) -> Result < String , String > {
151+ let listener = TcpListener :: bind ( & requested_address)
152+ . map_err ( |err| format ! ( "Failed to bind SMTP server on {requested_address}: {err}" ) ) ?;
153+
154+ let address = listener
155+ . local_addr ( )
156+ . map ( |addr| addr. to_string ( ) )
157+ . map_err ( |err| format ! ( "Failed to resolve SMTP server address: {err}" ) ) ?;
158+ let control_listener = listener
159+ . try_clone ( )
160+ . map_err ( |err| format ! ( "Failed to clone SMTP listener for {address}: {err}" ) ) ?;
161+
162+ let generation = {
163+ let mut control = SERVER_CONTROL . lock ( ) . unwrap ( ) ;
164+ if control. listener . is_some ( ) {
165+ return if control. stop_requested {
166+ Err ( "SMTP server is stopping. Please try again in a moment." . into ( ) )
167+ } else {
168+ Ok ( format ! ( "SMTP server is already running on {}." , control. address) )
169+ } ;
170+ }
171+
172+ let generation = SERVER_GENERATION . fetch_add ( 1 , Ordering :: SeqCst ) + 1 ;
173+ control. generation = generation;
174+ control. address = address. clone ( ) ;
175+ control. listener = Some ( control_listener) ;
176+ control. stop_requested = false ;
177+ generation
178+ } ;
179+
180+ SERVER_RUNNING . store ( true , Ordering :: SeqCst ) ;
181+
182+ let address_for_thread = address. clone ( ) ;
183+ thread:: spawn ( move || run_server ( listener, address_for_thread, generation) ) ;
184+
185+ Ok ( format ! ( "SMTP server started on {address}." ) )
186+ }
187+
188+ fn run_server ( listener : TcpListener , address : String , generation : u64 ) {
189+ let mut server = Server :: new ( MyHandler :: new ( ) ) ;
190+
191+ if let Err ( err) = server
192+ . with_name ( "blade mail" )
193+ . with_tcp_listener ( listener)
194+ . with_ssl ( SslConfig :: None )
195+ {
196+ eprintln ! ( "Failed to configure SMTP server on {address}: {err}" ) ;
197+ clear_server_control ( generation) ;
198+ return ;
199+ }
200+
201+ println ! ( "SMTP server is starting on {address}..." ) ;
202+
203+ match server. serve ( ) {
204+ Ok ( _) => println ! ( "SMTP server stopped on {address}." ) ,
205+ Err ( err) => {
206+ if is_stop_requested ( generation) {
207+ println ! ( "SMTP server stopped on {address}." ) ;
208+ } else {
209+ eprintln ! ( "SMTP server stopped with error on {address}: {err}" ) ;
210+ }
211+ }
212+ }
213+
214+ clear_server_control ( generation) ;
215+ }
216+
217+ fn stop_server_inner ( ) -> Result < String , String > {
218+ let address = {
219+ let mut control = SERVER_CONTROL . lock ( ) . unwrap ( ) ;
220+ if control. listener . is_none ( ) {
221+ return Ok ( "SMTP server is not running." . into ( ) ) ;
222+ }
223+
224+ control. stop_requested = true ;
225+ let listener = control
226+ . listener
227+ . as_ref ( )
228+ . ok_or_else ( || "SMTP server is not running." . to_string ( ) ) ?;
229+ listener
230+ . set_nonblocking ( true )
231+ . map_err ( |err| format ! ( "Failed to prepare SMTP shutdown on {}: {err}" , control. address) ) ?;
232+ control. address . clone ( )
233+ } ;
234+
235+ let _ = TcpStream :: connect ( & address) ;
236+
237+ for _ in 0 ..50 {
238+ if !SERVER_RUNNING . load ( Ordering :: SeqCst ) {
239+ return Ok ( format ! ( "SMTP server stopped on {address}." ) ) ;
240+ }
241+
242+ thread:: sleep ( Duration :: from_millis ( 20 ) ) ;
243+ }
244+
245+ Err ( format ! ( "Timed out stopping SMTP server on {address}." ) )
246+ }
247+
248+ fn is_stop_requested ( generation : u64 ) -> bool {
249+ let control = SERVER_CONTROL . lock ( ) . unwrap ( ) ;
250+ control. generation == generation && control. stop_requested
251+ }
252+
253+ fn clear_server_control ( generation : u64 ) {
254+ let mut control = SERVER_CONTROL . lock ( ) . unwrap ( ) ;
255+ if control. generation == generation {
256+ control. listener = None ;
257+ control. address . clear ( ) ;
258+ control. stop_requested = false ;
259+ SERVER_RUNNING . store ( false , Ordering :: SeqCst ) ;
260+ }
261+ }
262+
175263fn parse ( raw : String ) -> EmailPayload {
176264 let mut payload = EmailPayload :: empty ( raw) ;
177265
@@ -305,9 +393,6 @@ fn parse(raw: String) -> EmailPayload {
305393 payload
306394}
307395
308- static MAIN_WINDOW : OnceCell < WebviewWindow > = OnceCell :: new ( ) ;
309- static SERVER_RUNNING : AtomicBool = AtomicBool :: new ( false ) ;
310-
311396fn main ( ) {
312397 tauri:: Builder :: default ( )
313398 . plugin ( tauri_plugin_fs:: init ( ) )
@@ -341,7 +426,10 @@ fn main() {
341426
342427#[ cfg( test) ]
343428mod tests {
344- use super :: parse;
429+ use super :: { parse, start_server_inner, stop_server_inner, SERVER_CONTROL , SERVER_RUNNING } ;
430+ use std:: net:: TcpListener ;
431+ use std:: thread;
432+ use std:: time:: Duration ;
345433
346434 #[ test]
347435 fn parses_plain_text_email_without_panicking ( ) {
@@ -376,4 +464,29 @@ mod tests {
376464 assert ! ( payload. subject. is_empty( ) ) ;
377465 assert ! ( payload. from. is_empty( ) ) ;
378466 }
467+
468+ #[ test]
469+ fn start_and_stop_server_releases_the_port ( ) {
470+ let started = start_server_inner ( "127.0.0.1:0" . to_string ( ) ) . unwrap ( ) ;
471+ assert ! ( started. contains( "SMTP server started on 127.0.0.1:" ) ) ;
472+
473+ let address = {
474+ let control = SERVER_CONTROL . lock ( ) . unwrap ( ) ;
475+ control. address . clone ( )
476+ } ;
477+
478+ for _ in 0 ..50 {
479+ if SERVER_RUNNING . load ( std:: sync:: atomic:: Ordering :: SeqCst ) {
480+ break ;
481+ }
482+
483+ thread:: sleep ( Duration :: from_millis ( 10 ) ) ;
484+ }
485+
486+ let stopped = stop_server_inner ( ) . unwrap ( ) ;
487+ assert_eq ! ( stopped, format!( "SMTP server stopped on {address}." ) ) ;
488+ assert ! ( !SERVER_RUNNING . load( std:: sync:: atomic:: Ordering :: SeqCst ) ) ;
489+
490+ TcpListener :: bind ( & address) . expect ( "port should be released after stopping the server" ) ;
491+ }
379492}
0 commit comments