11use std:: borrow:: Borrow ;
22use std:: collections:: HashMap ;
33use std:: fs:: { self , File } ;
4+ use std:: mem;
45use std:: path:: Path ;
56use std:: str:: FromStr ;
7+ use std:: sync:: Mutex ;
68
79use chrono:: NaiveDateTime ;
810use futures_util:: StreamExt ;
@@ -19,6 +21,10 @@ use crate::ts::package_reference::PackageReference;
1921use crate :: ts:: version:: Version ;
2022use crate :: util:: file;
2123
24+ // Memory cache for the package index. We set this when we initially load the index lookup table
25+ // so we don't need to query the disk, however we must also be able to update it when necessary.
26+ static INDEX_CACHE : OnceCell < Mutex < PackageIndex > > = OnceCell :: new ( ) ;
27+
2228#[ derive( Serialize , Deserialize ) ]
2329struct IndexHeader {
2430 update_time : NaiveDateTime ,
@@ -33,6 +39,7 @@ struct IndexHeader {
3339/// 3. The index. This contains a series of newline-delimited json strings, unparsed and unserialized.
3440#[ derive( Debug ) ]
3541pub struct PackageIndex {
42+ update_time : NaiveDateTime ,
3643 lookup : Vec < LookupTableEntry > ,
3744
3845 strict_lookup : HashMap < String , usize > ,
@@ -146,16 +153,15 @@ impl PackageIndex {
146153 }
147154
148155 /// Open and serialize the on-disk index, retrieving a fresh copy if it doesn't already exist.
149- pub async fn open ( tcli_home : & Path ) -> Result < & PackageIndex , Error > {
156+ pub async fn open ( tcli_home : & Path ) -> Result < & Mutex < PackageIndex > , Error > {
150157 // Sync the index before we open it if it's in an invalid state.
151158 if !is_index_valid ( tcli_home) {
152159 PackageIndex :: sync ( tcli_home) . await ?;
153160 }
154161
155162 // Maintain a cached version of the index so subsequent calls don't trigger a complete reload.
156- static CACHE : OnceCell < PackageIndex > = OnceCell :: new ( ) ;
157- if let Some ( index) = CACHE . get ( ) {
158- return Ok ( index) ;
163+ if let Some ( index) = INDEX_CACHE . get ( ) {
164+ return Ok ( & index) ;
159165 }
160166
161167 let index_dir = tcli_home. join ( "index" ) ;
@@ -180,16 +186,23 @@ impl PackageIndex {
180186 }
181187
182188 let index_file = File :: open ( index_dir. join ( "index.json" ) ) ?;
189+ let header: IndexHeader = {
190+ let header = fs:: read_to_string ( index_dir. join ( "header.json" ) ) ?;
191+ serde_json:: from_str ( & header)
192+ } ?;
183193
184194 let index = PackageIndex {
195+ update_time : header. update_time ,
185196 lookup : entries,
186197 loose_lookup : loose,
187198 strict_lookup : strict,
188199 index_file,
189200 } ;
190- CACHE . set ( index) . unwrap ( ) ;
191201
192- Ok ( CACHE . get ( ) . unwrap ( ) )
202+ // Set or otherwise update the memory cache.
203+ hydrate_cache ( index) ;
204+
205+ Ok ( & INDEX_CACHE . get ( ) . unwrap ( ) )
193206 }
194207
195208 /// Get a package which matches the given package reference.
@@ -233,7 +246,7 @@ impl PackageIndex {
233246}
234247
235248/// Determine if the index is in a valid state or not.
236- pub fn is_index_valid ( tcli_home : & Path ) -> bool {
249+ fn is_index_valid ( tcli_home : & Path ) -> bool {
237250 let index_dir = tcli_home. join ( "index" ) ;
238251
239252 let lookup = index_dir. join ( "lookup.json" ) ;
@@ -242,3 +255,36 @@ pub fn is_index_valid(tcli_home: &Path) -> bool {
242255
243256 index_dir. exists ( ) && lookup. exists ( ) && index. exists ( ) && header. exists ( )
244257}
258+
259+ /// Determine if the in-memory cache is older than the index on disk.
260+ fn is_cache_expired ( tcli_home : & Path ) -> bool {
261+ let index_dir = tcli_home. join ( "index" ) ;
262+ let Ok ( header) = fs:: read_to_string ( index_dir. join ( "header.json" ) ) else {
263+ warn ! ( "Failed to read from index header, invalidating the cache." ) ;
264+ return true ;
265+ } ;
266+
267+ let Ok ( header) = serde_json:: from_str :: < IndexHeader > ( & header) else {
268+ return true ;
269+ } ;
270+
271+ if let Some ( index) = INDEX_CACHE . get ( ) {
272+ let index = index. lock ( ) . unwrap ( ) ;
273+ index. update_time != header. update_time
274+ } else {
275+ true
276+ }
277+ }
278+
279+ /// Init the cache or hydrate it with updated data.
280+ fn hydrate_cache ( index : PackageIndex ) {
281+ // If the cache hasn't set, do so and stop.
282+ if INDEX_CACHE . get ( ) . is_none ( ) {
283+ INDEX_CACHE . get_or_init ( || Mutex :: new ( index) ) ;
284+ return ;
285+ } ;
286+
287+ // Otherwise update the cache.
288+ let cache = INDEX_CACHE . get ( ) . unwrap ( ) ;
289+ let _ = mem:: replace ( & mut * cache. lock ( ) . unwrap ( ) , index) ;
290+ }
0 commit comments