33 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
44
55use std:: {
6- collections:: HashSet ,
6+ collections:: { HashMap , HashSet } ,
77 sync:: { Arc , Weak } ,
88} ;
99
1010use camino:: Utf8PathBuf ;
1111use parking_lot:: Mutex ;
12+ use serde:: Deserialize ;
13+ use viaduct:: Request ;
1214
1315use crate :: {
14- config:: BaseUrl , storage:: Storage , RemoteSettingsClient , RemoteSettingsConfig2 ,
15- RemoteSettingsContext , RemoteSettingsServer , Result ,
16+ client :: RemoteState , config:: BaseUrl , error :: Error , storage:: Storage , RemoteSettingsClient ,
17+ RemoteSettingsConfig2 , RemoteSettingsContext , RemoteSettingsServer , Result ,
1618} ;
1719
1820/// Internal Remote settings service API
@@ -25,6 +27,7 @@ struct RemoteSettingsServiceInner {
2527 base_url : BaseUrl ,
2628 bucket_name : String ,
2729 app_context : Option < RemoteSettingsContext > ,
30+ remote_state : RemoteState ,
2831 /// Weakrefs for all clients that we've created. Note: this stores the
2932 /// top-level/public `RemoteSettingsClient` structs rather than `client::RemoteSettingsClient`.
3033 /// The reason for this is that we return Arcs to the public struct to the foreign code, so we
@@ -51,6 +54,7 @@ impl RemoteSettingsService {
5154 base_url,
5255 bucket_name,
5356 app_context : config. app_context ,
57+ remote_state : RemoteState :: default ( ) ,
5458 clients : vec ! [ ] ,
5559 } ) ,
5660 }
@@ -81,13 +85,30 @@ impl RemoteSettingsService {
8185 // Make sure we only sync each collection once, even if there are multiple clients
8286 let mut synced_collections = HashSet :: new ( ) ;
8387
84- // TODO: poll the server using `/buckets/monitor/collections/changes/changeset` to fetch
85- // the current timestamp for all collections. That way we can avoid fetching collections
86- // we know haven't changed and also pass the `?_expected{ts}` param to the server.
88+ let mut inner = self . inner . lock ( ) ;
89+ let changes = inner. fetch_changes ( ) ?;
90+ let change_map: HashMap < _ , _ > = changes
91+ . changes
92+ . iter ( )
93+ . map ( |c| ( ( c. collection . as_str ( ) , & c. bucket ) , c. last_modified ) )
94+ . collect ( ) ;
95+ let bucket_name = inner. bucket_name . clone ( ) ;
8796
88- for client in self . inner . lock ( ) . active_clients ( ) {
89- if synced_collections. insert ( client. collection_name ( ) ) {
90- client. internal . sync ( ) ?;
97+ for client in inner. active_clients ( ) {
98+ let client = & client. internal ;
99+ let collection_name = client. collection_name ( ) ;
100+ if let Some ( client_last_modified) = client. get_last_modified_timestamp ( ) ? {
101+ if let Some ( server_last_modified) = change_map. get ( & ( collection_name, & bucket_name) )
102+ {
103+ if client_last_modified != * server_last_modified {
104+ log:: trace!( "skipping up-to-date collection: {collection_name}" ) ;
105+ continue ;
106+ }
107+ }
108+ }
109+ if synced_collections. insert ( collection_name. to_string ( ) ) {
110+ log:: trace!( "syncing collection: {collection_name}" ) ;
111+ client. sync ( ) ?;
91112 }
92113 }
93114 Ok ( synced_collections. into_iter ( ) . collect ( ) )
@@ -134,4 +155,52 @@ impl RemoteSettingsServiceInner {
134155 } ) ;
135156 active_clients
136157 }
158+
159+ fn fetch_changes ( & mut self ) -> Result < Changes > {
160+ let mut url = self . base_url . clone ( ) ;
161+ url. path_segments_mut ( )
162+ . push ( "buckets" )
163+ . push ( "monitor" )
164+ . push ( "collections" )
165+ . push ( "changes" )
166+ . push ( "changeset" ) ;
167+ // For now, always use `0` for the expected value. This means we'll get updates based on
168+ // the default TTL of 1 hour.
169+ //
170+ // Eventually, we should add support for push notifications and use the timestamp from the
171+ // notification.
172+ url. query_pairs_mut ( ) . append_pair ( "_expected" , "0" ) ;
173+ let url = url. into_inner ( ) ;
174+ log:: trace!( "make_request: {url}" ) ;
175+ self . remote_state . ensure_no_backoff ( ) ?;
176+
177+ let req = Request :: get ( url) ;
178+ let resp = req. send ( ) ?;
179+
180+ self . remote_state . handle_backoff_hint ( & resp) ?;
181+
182+ if resp. is_success ( ) {
183+ Ok ( resp. json ( ) ?)
184+ } else {
185+ Err ( Error :: ResponseError ( format ! (
186+ "status code: {}" ,
187+ resp. status
188+ ) ) )
189+ }
190+ }
191+ }
192+
193+ /// Data from the changes endpoint
194+ ///
195+ /// https://remote-settings.readthedocs.io/en/latest/client-specifications.html#endpoints
196+ #[ derive( Debug , Deserialize ) ]
197+ struct Changes {
198+ changes : Vec < ChangesCollection > ,
199+ }
200+
201+ #[ derive( Debug , Deserialize ) ]
202+ struct ChangesCollection {
203+ collection : String ,
204+ bucket : String ,
205+ last_modified : u64 ,
137206}
0 commit comments