1
- use anyhow:: Context as _;
2
1
use reqwest:: Client ;
3
2
use rust_team_data:: v1:: { People , Teams , ZulipMapping , BASE_URL } ;
4
3
use serde:: de:: DeserializeOwned ;
4
+ use std:: sync:: Arc ;
5
+ use std:: time:: { Duration , Instant } ;
6
+ use tokio:: sync:: RwLock ;
5
7
6
8
#[ derive( Clone ) ]
7
9
pub struct TeamApiClient {
8
10
base_url : String ,
9
11
client : Client ,
12
+ teams : CachedTeamItem < Teams > ,
13
+ people : CachedTeamItem < People > ,
14
+ zulip_mapping : CachedTeamItem < ZulipMapping > ,
10
15
}
11
16
12
17
impl TeamApiClient {
@@ -19,6 +24,9 @@ impl TeamApiClient {
19
24
Self {
20
25
base_url,
21
26
client : Client :: new ( ) ,
27
+ teams : CachedTeamItem :: new ( "/teams.json" ) ,
28
+ people : CachedTeamItem :: new ( "/people.json" ) ,
29
+ zulip_mapping : CachedTeamItem :: new ( "/zulip-map.json" ) ,
22
30
}
23
31
}
24
32
@@ -84,24 +92,68 @@ impl TeamApiClient {
84
92
}
85
93
86
94
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
90
96
}
91
97
92
98
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
96
100
}
97
101
98
102
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
102
104
}
103
105
}
104
106
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
+
105
157
async fn download < T : DeserializeOwned > (
106
158
client : & Client ,
107
159
base_url : & str ,
0 commit comments