1- use anyhow:: Context as _;
21use reqwest:: Client ;
32use rust_team_data:: v1:: { People , Teams , ZulipMapping , BASE_URL } ;
43use serde:: de:: DeserializeOwned ;
4+ use std:: sync:: Arc ;
5+ use std:: time:: { Duration , Instant } ;
6+ use tokio:: sync:: RwLock ;
57
68#[ derive( Clone ) ]
79pub struct TeamApiClient {
810 base_url : String ,
911 client : Client ,
12+ teams : CachedTeamItem < Teams > ,
13+ people : CachedTeamItem < People > ,
14+ zulip_mapping : CachedTeamItem < ZulipMapping > ,
1015}
1116
1217impl TeamApiClient {
@@ -19,6 +24,9 @@ impl TeamApiClient {
1924 Self {
2025 base_url,
2126 client : Client :: new ( ) ,
27+ teams : CachedTeamItem :: new ( "/teams.json" ) ,
28+ people : CachedTeamItem :: new ( "/people.json" ) ,
29+ zulip_mapping : CachedTeamItem :: new ( "/zulip-map.json" ) ,
2230 }
2331 }
2432
@@ -84,24 +92,68 @@ impl TeamApiClient {
8492 }
8593
8694 pub async fn zulip_map ( & self ) -> anyhow:: Result < ZulipMapping > {
87- download ( & self . client , & self . base_url , "/zulip-map.json" )
88- . await
89- . context ( "team-api: zulip-map.json" )
95+ self . zulip_mapping . get ( & self . client , & self . base_url ) . await
9096 }
9197
9298 pub async fn teams ( & self ) -> anyhow:: Result < Teams > {
93- download ( & self . client , & self . base_url , "/teams.json" )
94- . await
95- . context ( "team-api: teams.json" )
99+ self . teams . get ( & self . client , & self . base_url ) . await
96100 }
97101
98102 pub async fn people ( & self ) -> anyhow:: Result < People > {
99- download ( & self . client , & self . base_url , "/people.json" )
100- . await
101- . context ( "team-api: people.json" )
103+ self . people . get ( & self . client , & self . base_url ) . await
102104 }
103105}
104106
107+ /// How long should downloaded team data items be cached in memory.
108+ const CACHE_DURATION : Duration = Duration :: from_secs ( 2 * 60 ) ;
109+
110+ #[ derive( Clone ) ]
111+ struct CachedTeamItem < T > {
112+ value : Arc < RwLock < CachedValue < T > > > ,
113+ url_path : String ,
114+ }
115+
116+ impl < T : DeserializeOwned + Clone > CachedTeamItem < T > {
117+ fn new ( url_path : & str ) -> Self {
118+ Self {
119+ value : Arc :: new ( RwLock :: new ( CachedValue :: Empty ) ) ,
120+ url_path : url_path. to_string ( ) ,
121+ }
122+ }
123+
124+ async fn get ( & self , client : & Client , base_url : & str ) -> anyhow:: Result < T > {
125+ let now = Instant :: now ( ) ;
126+ {
127+ let value = self . value . read ( ) . await ;
128+ if let CachedValue :: Present {
129+ value,
130+ last_download,
131+ } = & * value
132+ {
133+ if * last_download + CACHE_DURATION > now {
134+ return Ok ( value. clone ( ) ) ;
135+ }
136+ }
137+ }
138+ match download :: < T > ( client, base_url, & self . url_path ) . await {
139+ Ok ( v) => {
140+ let mut value = self . value . write ( ) . await ;
141+ * value = CachedValue :: Present {
142+ value : v. clone ( ) ,
143+ last_download : Instant :: now ( ) ,
144+ } ;
145+ Ok ( v)
146+ }
147+ Err ( e) => Err ( e) ,
148+ }
149+ }
150+ }
151+
152+ enum CachedValue < T > {
153+ Empty ,
154+ Present { value : T , last_download : Instant } ,
155+ }
156+
105157async fn download < T : DeserializeOwned > (
106158 client : & Client ,
107159 base_url : & str ,
0 commit comments