omaha_client/
app_set.rs

1// Copyright 2021 The Fuchsia Authors
2//
3// Licensed under a BSD-style license <LICENSE-BSD>, Apache License, Version 2.0
4// <LICENSE-APACHE or https://www.apache.org/licenses/LICENSE-2.0>, or the MIT
5// license <LICENSE-MIT or https://opensource.org/licenses/MIT>, at your option.
6// This file may not be copied, modified, or distributed except according to
7// those terms.
8
9use {
10    crate::{common::App, state_machine::update_check::AppResponse, storage::Storage},
11    futures::{future::LocalBoxFuture, prelude::*},
12};
13
14/// The trait for the platform-specific AppSet to implement.
15pub trait AppSet {
16    fn get_apps(&self) -> Vec<App>;
17    fn iter_mut_apps(&mut self) -> Box<dyn Iterator<Item = &mut App> + '_>;
18    fn get_system_app_id(&self) -> &str;
19}
20
21pub trait AppSetExt: AppSet {
22    /// Return whether all the apps in the app set are valid.
23    fn all_valid(&self) -> bool {
24        self.get_apps().iter().all(|app| app.valid())
25    }
26
27    /// Update the cohort and user counting for each app from Omaha app response.
28    fn update_from_omaha(&mut self, app_responses: &[AppResponse]) {
29        for app in self.iter_mut_apps() {
30            for app_response in app_responses {
31                if app.id == app_response.app_id {
32                    app.cohort.update_from_omaha(app_response.cohort.clone());
33                    app.user_counting = app_response.user_counting.clone();
34                    break;
35                }
36            }
37        }
38    }
39
40    /// Load data from |storage|, only overwrite existing fields if data exists.
41    #[must_use]
42    fn load<'a>(&'a mut self, storage: &'a impl Storage) -> LocalBoxFuture<'a, ()> {
43        async move {
44            for app in self.iter_mut_apps() {
45                app.load(storage).await;
46            }
47        }
48        .boxed_local()
49    }
50
51    /// Persist cohort and user counting to |storage|, will try to set all of them to storage even
52    /// if previous set fails.
53    /// It will NOT call commit() on |storage|, caller is responsible to call commit().
54    #[must_use]
55    fn persist<'a>(&'a self, storage: &'a mut impl Storage) -> LocalBoxFuture<'a, ()> {
56        async move {
57            for app in self.get_apps() {
58                app.persist(storage).await;
59            }
60        }
61        .boxed_local()
62    }
63}
64
65impl<T> AppSetExt for T where T: AppSet {}
66
67/// An AppSet implementation based on Vec, the first app will be treated as the system app.
68pub struct VecAppSet {
69    pub apps: Vec<App>,
70}
71
72impl VecAppSet {
73    /// Panics if the passed apps is empty.
74    pub fn new(apps: Vec<App>) -> Self {
75        assert!(!apps.is_empty());
76        Self { apps }
77    }
78}
79
80impl AppSet for VecAppSet {
81    fn get_apps(&self) -> Vec<App> {
82        self.apps.clone()
83    }
84    fn iter_mut_apps(&mut self) -> Box<dyn Iterator<Item = &mut App> + '_> {
85        Box::new(self.apps.iter_mut())
86    }
87    fn get_system_app_id(&self) -> &str {
88        &self.apps[0].id
89    }
90}
91
92#[cfg(test)]
93mod tests {
94    use super::*;
95    use crate::{common::UserCounting, protocol::Cohort, state_machine::update_check::Action};
96
97    #[test]
98    fn test_appsetext_update_from_omaha() {
99        let mut app_set = VecAppSet::new(vec![
100            App::builder().id("some_id").version([0, 1]).build(),
101            App::builder().id("not_updated_id").version([2]).build(),
102        ]);
103        let cohort = Cohort {
104            name: Some("some-channel".to_string()),
105            ..Cohort::default()
106        };
107        let user_counting = UserCounting::ClientRegulatedByDate(Some(42));
108        let app_responses = vec![
109            AppResponse {
110                app_id: "some_id".to_string(),
111                cohort: cohort.clone(),
112                user_counting: user_counting.clone(),
113                result: Action::Updated,
114            },
115            AppResponse {
116                app_id: "some_other_id".to_string(),
117                cohort: cohort.clone(),
118                user_counting: user_counting.clone(),
119                result: Action::NoUpdate,
120            },
121        ];
122
123        app_set.update_from_omaha(&app_responses);
124        let apps = app_set.get_apps();
125        assert_eq!(cohort, apps[0].cohort);
126        assert_eq!(user_counting, apps[0].user_counting);
127
128        assert_eq!(
129            apps[1],
130            App::builder().id("not_updated_id").version([2]).build()
131        );
132    }
133
134    #[test]
135    fn test_appsetext_valid() {
136        let app_set = VecAppSet::new(vec![App::builder().id("some_id").version([0, 1]).build()]);
137        assert!(app_set.all_valid());
138        let app_set = VecAppSet::new(vec![
139            App::builder().id("some_id").version([0, 1]).build(),
140            App::builder().id("some_id_2").version([1]).build(),
141        ]);
142        assert!(app_set.all_valid());
143    }
144
145    #[test]
146    fn test_appsetext_not_valid() {
147        let app_set = VecAppSet::new(vec![
148            App::builder().id("some_id").version([0, 1]).build(),
149            App::builder().id("").version([0, 1]).build(),
150        ]);
151        assert!(!app_set.all_valid());
152        let app_set = VecAppSet::new(vec![
153            App::builder().id("some_id").version([0]).build(),
154            App::builder().id("some_id_2").version([0, 1]).build(),
155        ]);
156        assert!(!app_set.all_valid());
157        let app_set = VecAppSet::new(vec![
158            App::builder().id("some_id").version([0]).build(),
159            App::builder().id("").version([0, 1]).build(),
160        ]);
161        assert!(!app_set.all_valid());
162    }
163
164    #[test]
165    fn test_get_apps() {
166        let apps = vec![App::builder().id("some_id").version([0, 1]).build()];
167        let app_set = VecAppSet::new(apps.clone());
168        assert_eq!(app_set.get_apps(), apps);
169    }
170
171    #[test]
172    fn test_iter_mut_apps() {
173        let apps = vec![
174            App::builder().id("id1").version([1]).build(),
175            App::builder().id("id2").version([2]).build(),
176        ];
177        let mut app_set = VecAppSet::new(apps);
178        for app in app_set.iter_mut_apps() {
179            app.id += "_mutated";
180        }
181        assert_eq!(
182            app_set.get_apps(),
183            vec![
184                App::builder().id("id1_mutated").version([1]).build(),
185                App::builder().id("id2_mutated").version([2]).build()
186            ]
187        );
188    }
189
190    #[test]
191    fn test_get_system_app_id() {
192        let apps = vec![
193            App::builder().id("id1").version([1]).build(),
194            App::builder().id("id2").version([2]).build(),
195        ];
196        let app_set = VecAppSet::new(apps);
197        assert_eq!(app_set.get_system_app_id(), "id1");
198    }
199}