11#![ allow( dead_code) ]
22
3- use std:: collections:: HashMap ;
43use std:: io:: { self } ;
54use std:: net:: IpAddr ;
65use std:: path:: { Path , PathBuf } ;
76use std:: sync:: Arc ;
87use std:: sync:: atomic:: { AtomicBool , Ordering } ;
98
109use crate :: app:: SqlitePool ;
11- use crossbeam_utils :: sync :: ShardedLock ;
10+ use arc_swap :: ArcSwapOption ;
1211use eyre:: { Context , OptionExt , Result } ;
1312use futures_lite:: StreamExt ;
1413use md5:: { Digest , Md5 } ;
@@ -19,6 +18,7 @@ const BASE_URL: &str = "https://updates.maxmind.com";
1918const METADATA_ENDPOINT : & str = "/geoip/updates/metadata?edition_id=" ;
2019const DOWNLOAD_ENDPOINT : & str = "/geoip/databases/" ;
2120
21+ #[ derive( Default ) ]
2222pub struct LookupResult {
2323 pub city : Option < String > ,
2424 pub country_code : Option < String > ,
@@ -27,30 +27,26 @@ pub struct LookupResult {
2727#[ derive( Clone ) ]
2828pub struct LiwanGeoIP {
2929 pool : SqlitePool ,
30- reader : Arc < ShardedLock < Option < maxminddb:: Reader < Vec < u8 > > > > > ,
30+ reader : Arc < ArcSwapOption < maxminddb:: Reader < Vec < u8 > > > > ,
3131
3232 downloading : Arc < AtomicBool > ,
33- config : crate :: config:: Config ,
3433 geoip : crate :: config:: GeoIpConfig ,
3534 path : PathBuf ,
3635}
3736
3837impl LiwanGeoIP {
39- pub fn try_new ( config : crate :: config:: Config , pool : SqlitePool ) -> Result < Option < Self > > {
40- let Some ( geoip) = & config. geoip else {
41- tracing:: trace!( "GeoIP support disabled, skipping..." ) ;
42- return Ok ( None ) ;
43- } ;
44-
38+ pub fn try_new ( config : crate :: config:: Config , pool : SqlitePool ) -> Result < Self > {
39+ let geoip = config. geoip ;
4540 if geoip. maxmind_account_id . is_none ( ) && geoip. maxmind_license_key . is_none ( ) && geoip. maxmind_db_path . is_none ( )
4641 {
4742 tracing:: trace!( "GeoIP support disabled, skipping..." ) ;
48- return Ok ( None ) ;
43+ return Ok ( Self :: noop ( pool ) ) ;
4944 }
5045
51- let edition = geoip. maxmind_edition . as_deref ( ) . unwrap_or ( "GeoLite2-City" ) ;
46+ let edition = & geoip. maxmind_edition ;
5247 let default_path = PathBuf :: from ( config. data_dir . clone ( ) ) . join ( format ! ( "./geoip/{edition}.mmdb" ) ) ;
5348 let path = geoip. maxmind_db_path . as_ref ( ) . map_or ( default_path, PathBuf :: from) ;
49+
5450 if let Some ( parent) = path. parent ( ) {
5551 std:: fs:: create_dir_all ( parent) ?;
5652 }
@@ -60,28 +56,38 @@ impl LiwanGeoIP {
6056 }
6157
6258 tracing:: info!( database = geoip. maxmind_db_path, "GeoIP support enabled, loading database" ) ;
63- let reader = if path. exists ( ) {
64- Some ( maxminddb:: Reader :: open_readfile ( path. clone ( ) ) . expect ( "Failed to open GeoIP database file" ) )
65- } else {
66- None
67- } ;
6859
69- Ok ( Some ( Self {
70- geoip : geoip. clone ( ) ,
71- config,
60+ let reader = path. exists ( ) . then ( || {
61+ maxminddb:: Reader :: open_readfile ( path. clone ( ) ) . expect ( "Failed to open GeoIP database file" ) . into ( )
62+ } ) ;
63+
64+ Ok ( Self { geoip, pool, reader : ArcSwapOption :: new ( reader) . into ( ) , path, downloading : Default :: default ( ) } )
65+ }
66+
67+ fn is_enabled ( & self ) -> bool {
68+ self . reader . load ( ) . is_some ( ) || self . downloading . load ( Ordering :: Acquire )
69+ }
70+
71+ fn noop ( pool : SqlitePool ) -> Self {
72+ Self {
73+ geoip : Default :: default ( ) ,
7274 pool,
73- reader : Arc :: new ( ShardedLock :: new ( reader ) ) ,
74- path ,
75- downloading : Arc :: new ( AtomicBool :: new ( false ) ) ,
76- } ) )
75+ reader : ArcSwapOption :: new ( None ) . into ( ) ,
76+ downloading : Default :: default ( ) ,
77+ path : PathBuf :: new ( ) ,
78+ }
7779 }
7880
7981 // Lookup the IP address in the GeoIP database
8082 pub fn lookup ( & self , ip : & IpAddr ) -> Result < LookupResult > {
81- let reader = self . reader . read ( ) . map_err ( |_| eyre:: eyre!( "Failed to acquire GeoIP reader lock" ) ) ?;
82- let reader = reader. as_ref ( ) . ok_or_eyre ( "GeoIP database not found" ) ?;
83- let lookup: maxminddb:: geoip2:: City =
84- reader. lookup ( * ip) ?. ok_or_else ( || eyre:: eyre!( "No data found for IP address" ) ) ?;
83+ let Some ( reader) = & * self . reader . load ( ) else {
84+ return Ok ( Default :: default ( ) ) ;
85+ } ;
86+
87+ let lookup = reader
88+ . lookup :: < maxminddb:: geoip2:: City > ( * ip) ?
89+ . ok_or_else ( || eyre:: eyre!( "No data found for IP address" ) ) ?;
90+
8591 let city = lookup. city . and_then ( |city| city. names . and_then ( |names| names. get ( "en" ) . map ( |s| ( * s) . to_string ( ) ) ) ) ;
8692 let country_code = lookup. country . and_then ( |country| country. iso_code . map ( ToString :: to_string) ) ;
8793 Ok ( LookupResult { city, country_code } )
@@ -93,16 +99,17 @@ impl LiwanGeoIP {
9399 return Ok ( ( ) ) ;
94100 }
95101
96- let maxmind_edition = self . geoip . maxmind_edition . clone ( ) . ok_or_eyre ( "MaxMind edition not found" ) ?;
97- let maxmind_account_id = self . geoip . maxmind_account_id . clone ( ) . ok_or_eyre ( "MaxMind account ID not found" ) ?;
98- let maxmind_license_key = self . geoip . maxmind_license_key . clone ( ) . ok_or_eyre ( "MaxMind license key not found" ) ?;
102+ let maxmind_edition = & self . geoip . maxmind_edition ;
103+ let maxmind_account_id = self . geoip . maxmind_account_id . as_deref ( ) . ok_or_eyre ( "MaxMind account ID not found" ) ?;
104+ let maxmind_license_key =
105+ self . geoip . maxmind_license_key . as_deref ( ) . ok_or_eyre ( "MaxMind license key not found" ) ?;
99106
100107 let db_exists = self . path . exists ( ) ;
101108 let db_md5 = if db_exists { file_md5 ( & self . path ) ? } else { String :: new ( ) } ;
102109
103- let mut update = false ;
110+ let mut update = !db_exists ;
104111 if db_exists {
105- match get_latest_md5 ( & maxmind_edition, & maxmind_account_id, & maxmind_license_key) . await {
112+ match get_latest_md5 ( maxmind_edition, maxmind_account_id, maxmind_license_key) . await {
106113 Ok ( latest_md5) => {
107114 if latest_md5 != db_md5 {
108115 tracing:: info!( "GeoIP database outdated, downloading..." ) ;
@@ -115,11 +122,10 @@ impl LiwanGeoIP {
115122 } ;
116123 } else {
117124 tracing:: info!( "GeoIP database doesn't exist, attempting to download..." ) ;
118- update = true ;
119125 }
120126
121127 if update {
122- let file = match download_maxmind_db ( & maxmind_edition, & maxmind_account_id, & maxmind_license_key) . await {
128+ let file = match download_maxmind_db ( maxmind_edition, maxmind_account_id, maxmind_license_key) . await {
123129 Ok ( file) => file,
124130 Err ( e) => {
125131 tracing:: warn!( error = ?e, "Failed to download GeoIP database, skipping update" ) ;
@@ -129,18 +135,15 @@ impl LiwanGeoIP {
129135 } ;
130136
131137 // close the current reader to free up the file
132- {
133- let mut reader = self . reader . write ( ) . unwrap ( ) ;
134- reader. take ( ) ;
135- }
138+ self . reader . swap ( None ) ;
136139
137140 // move the downloaded file to the correct path
138141 std:: fs:: copy ( & file, & self . path ) ?;
139142 std:: fs:: remove_file ( file) ?;
140143
141144 // open the new reader
142145 let reader = maxminddb:: Reader :: open_readfile ( self . path . clone ( ) ) ?;
143- * self . reader . write ( ) . unwrap ( ) = Some ( reader) ;
146+ self . reader . store ( Some ( reader. into ( ) ) ) ;
144147
145148 let path = std:: fs:: canonicalize ( & self . path ) ?;
146149 tracing:: info!( path = ?path, "GeoIP database updated successfully" ) ;
@@ -151,8 +154,10 @@ impl LiwanGeoIP {
151154 }
152155}
153156
154- pub fn keep_updated ( geoip : Option < LiwanGeoIP > ) {
155- let Some ( geoip) = geoip else { return } ;
157+ pub fn keep_updated ( geoip : LiwanGeoIP ) {
158+ if !geoip. is_enabled ( ) {
159+ return ;
160+ }
156161
157162 tokio:: task:: spawn ( async move {
158163 if let Err ( e) = geoip. check_for_updates ( ) . await {
@@ -182,7 +187,7 @@ async fn get_latest_md5(edition: &str, account_id: &str, license_key: &str) -> R
182187 . basic_auth ( account_id, Some ( license_key) )
183188 . send ( )
184189 . await ?
185- . json :: < HashMap < String , Vec < HashMap < String , String > > > > ( )
190+ . json :: < ahash :: HashMap < String , Vec < ahash :: HashMap < String , String > > > > ( )
186191 . await ?;
187192
188193 Ok ( response
0 commit comments