@@ -5,6 +5,7 @@ use std::net::IpAddr;
55use std:: path:: { Path , PathBuf } ;
66use std:: sync:: Arc ;
77use std:: sync:: atomic:: { AtomicBool , Ordering } ;
8+ use std:: time:: Duration ;
89
910use crate :: app:: SqlitePool ;
1011use anyhow:: { Context , Result , anyhow} ;
@@ -63,10 +64,6 @@ impl LiwanGeoIP {
6364 Ok ( Self { geoip, pool, reader : ArcSwapOption :: new ( reader) , path, downloading : Default :: default ( ) } )
6465 }
6566
66- fn is_enabled ( & self ) -> bool {
67- self . reader . load ( ) . is_some ( ) || self . downloading . load ( Ordering :: Acquire )
68- }
69-
7067 fn noop ( pool : SqlitePool ) -> Self {
7168 Self {
7269 geoip : Default :: default ( ) ,
@@ -149,30 +146,66 @@ impl LiwanGeoIP {
149146 self . downloading . store ( false , Ordering :: Release ) ;
150147 Ok ( ( ) )
151148 }
152- }
153149
154- pub fn keep_updated ( geoip : Arc < LiwanGeoIP > ) {
155- if !geoip. is_enabled ( ) {
156- return ;
150+ pub fn reload ( & self ) -> Result < ( ) > {
151+ if self . downloading . load ( Ordering :: Acquire ) {
152+ return Ok ( ( ) ) ;
153+ }
154+
155+ let reader = maxminddb:: Reader :: open_readfile ( self . path . clone ( ) ) ?;
156+ self . reader . store ( Some ( reader. into ( ) ) ) ;
157+ Ok ( ( ) )
157158 }
159+ }
158160
159- tokio:: task:: spawn ( async move {
160- if let Err ( e) = geoip. check_for_updates ( ) . await {
161- tracing:: error!( error = ?e, "Failed to check for GeoIP database updates" ) ;
162- }
161+ pub async fn keep_updated ( geoip : Arc < LiwanGeoIP > ) {
162+ let path: PathBuf = geoip. path . clone ( ) ;
163+ let mut last_meta = get_file_meta ( & path) ;
164+
165+ let mut file_interval = tokio:: time:: interval ( Duration :: from_secs ( 60 ) ) ;
166+ let mut daily_interval = tokio:: time:: interval ( Duration :: from_secs ( 24 * 60 * 60 ) ) ;
167+ let enable_updates = geoip. geoip . maxmind_account_id . is_some ( ) && geoip. geoip . maxmind_license_key . is_some ( ) ;
163168
164- let mut interval = tokio:: time:: interval ( std:: time:: Duration :: from_hours ( 24 ) ) ;
165- loop {
166- interval. tick ( ) . await ;
167- let geoip = geoip. clone ( ) ;
168- // Run the task once a day
169- tokio:: task:: spawn ( async move {
170- if let Err ( e) = geoip. check_for_updates ( ) . await {
169+ loop {
170+ tokio:: select! {
171+ _ = file_interval. tick( ) => {
172+ // just a simple polling based file watcher so we don't need to add a bunch of dependencies
173+ let meta = get_file_meta( & path) ;
174+ if meta != last_meta {
175+ if let Err ( e) = geoip. reload( ) {
176+ tracing:: error!( error = ?e, "Failed to reload GeoIP database" ) ;
177+ } else {
178+ tracing:: info!( "GeoIP database reloaded after file change" ) ;
179+ last_meta = meta;
180+ }
181+ }
182+ }
183+ _ = daily_interval. tick( ) => {
184+ if enable_updates && let Err ( e) = geoip. check_for_updates( ) . await {
171185 tracing:: error!( error = ?e, "Failed to check for GeoIP database updates" ) ;
172186 }
173- } ) ;
187+ }
174188 }
175- } ) ;
189+ }
190+ }
191+
192+ fn get_file_meta ( path : & PathBuf ) -> Option < ( u64 , u64 , u64 , i64 ) > {
193+ let md = std:: fs:: metadata ( path) . ok ( ) ?;
194+
195+ #[ cfg( unix) ]
196+ {
197+ use std:: os:: unix:: fs:: MetadataExt ;
198+ return Some ( ( md. dev ( ) , md. ino ( ) , md. size ( ) , md. mtime ( ) ) ) ;
199+ }
200+
201+ #[ cfg( windows) ]
202+ {
203+ use std:: os:: windows:: fs:: MetadataExt ;
204+ return Some ( ( md. file_index ( ) , md. volume_serial_number ( ) , md. file_size ( ) , md. last_write_time ( ) as i64 ) ) ;
205+ } ;
206+
207+ #[ cfg( not( any( unix, windows) ) ) ]
208+ Some ( ( 0 , 0 , md. len ( ) , md. modified ( ) . ok ( ) ?. elapsed ( ) . ok ( ) ?. as_secs ( ) as i64 ) )
176209}
177210
178211async fn get_latest_md5 ( edition : & str , account_id : & str , license_key : & str ) -> Result < String > {
0 commit comments