settings/storage_migrations/
v1653667208_light_migration.rs1use crate::migration::{FileGenerator, Migration, MigrationError};
6use anyhow::{Context, anyhow};
7use fidl::endpoints::create_proxy;
8use fidl::persist;
9use fidl_fuchsia_io::FileProxy;
10use fidl_fuchsia_settings::LightGroup as LightGroupFidl;
11use fidl_fuchsia_settings_storage::LightGroups;
12use fidl_fuchsia_stash::{StoreProxy, Value};
13
14use settings_light::types::LightInfo;
15use settings_storage::fidl_storage::FidlStorageConvertible;
16
17const LIGHT_KEY: &str = "settings_light_info";
18
19pub(crate) struct V1653667208LightMigration(pub(crate) StoreProxy);
21
22#[async_trait::async_trait(?Send)]
23impl Migration for V1653667208LightMigration {
24 fn id(&self) -> u64 {
25 1653667208
26 }
27
28 async fn migrate(&self, file_generator: FileGenerator) -> Result<(), MigrationError> {
29 let (stash_proxy, server_end) = create_proxy();
30 self.0.create_accessor(true, server_end).expect("failed to create accessor for stash");
31 let value = stash_proxy.get_value(LIGHT_KEY).await.context("failed to call get_value")?;
32 let str_json = match value {
33 None => return Err(MigrationError::NoData),
34 Some(value) => {
35 if let Value::Stringval(str_json) = *value {
36 str_json
37 } else {
38 return Err(MigrationError::Unrecoverable(anyhow!("data in incorrect format")));
39 }
40 }
41 };
42 let light_data = serde_json::from_str::<LightInfo>(&str_json)
43 .context("failed to deserialize light data")?;
44
45 let light_groups = LightGroups {
46 groups: light_data
47 .light_groups
48 .into_values()
49 .map(LightGroupFidl::from)
50 .collect::<Vec<_>>(),
51 };
52 let file: FileProxy = file_generator
53 .new_file(<LightInfo as FidlStorageConvertible>::KEY)
54 .await
55 .map_err(|file_error| match file_error {
56 fuchsia_fs::node::OpenError::OpenError(status)
57 if status == zx::Status::NO_SPACE =>
58 {
59 MigrationError::DiskFull
60 }
61 _ => Err::<(), _>(file_error).context("Creating new file").unwrap_err().into(),
62 })?;
63 let encoded = persist(&light_groups).context("failed to serialize new fidl format")?;
64 let _ = file.write(&encoded).await.context("file to call write")?.map_err(|e| {
65 let status = zx::Status::from_raw(e);
66 if status == zx::Status::NO_SPACE {
67 MigrationError::DiskFull
68 } else {
69 MigrationError::Unrecoverable(anyhow!("failed to write migration: {:?}", e))
70 }
71 })?;
72 Ok(())
73 }
74}
75
76#[cfg(test)]
77mod tests {
78 use super::*;
79 use crate::storage_migrations::tests::open_tempdir;
80 use assert_matches::assert_matches;
81 use fidl_fuchsia_settings::{LightGroup, LightState, LightType, LightValue};
82 use fidl_fuchsia_stash::{StoreAccessorRequest, StoreMarker, StoreRequest};
83 use fidl_fuchsia_ui_types::ColorRgb;
84 use fuchsia_async as fasync;
85 use futures::StreamExt;
86
87 #[fuchsia::test]
90 async fn v1653667208_light_migration_error_getting_value() {
91 let (store_proxy, server_end) = create_proxy::<StoreMarker>();
92 let mut request_stream = server_end.into_stream();
93 let task = fasync::Task::local(async move {
94 let mut tasks = vec![];
95 while let Some(Ok(request)) = request_stream.next().await {
96 if let StoreRequest::CreateAccessor { accessor_request, .. } = request {
97 let mut request_stream = accessor_request.into_stream();
98 tasks.push(fasync::Task::local(async move {
99 while let Some(Ok(request)) = request_stream.next().await {
100 if let StoreAccessorRequest::GetValue { responder, .. } = request {
101 drop(responder);
103 } else {
104 panic!("unexpected request: {request:?}");
105 }
106 }
107 }))
108 }
109 }
110 for task in tasks {
111 task.await
112 }
113 });
114
115 let migration = V1653667208LightMigration(store_proxy);
116 let fs = tempfile::tempdir().expect("failed to create tempdir");
117 let directory = open_tempdir(&fs);
118 let file_generator = FileGenerator::new(0, migration.id(), Clone::clone(&directory));
119 let result = migration.migrate(file_generator).await;
120 assert_matches!(result, Err(MigrationError::Unrecoverable(_)));
121 let err = result.unwrap_err();
122 assert!(format!("{err:?}").contains("failed to call get_value"));
123
124 drop(migration);
125
126 task.await;
127 }
128
129 #[fuchsia::test]
131 async fn v1653667208_light_migration_no_data() {
132 let (store_proxy, server_end) = create_proxy::<StoreMarker>();
133 let mut request_stream = server_end.into_stream();
134 let task = fasync::Task::local(async move {
135 let mut tasks = vec![];
136 while let Some(Ok(request)) = request_stream.next().await {
137 if let StoreRequest::CreateAccessor { accessor_request, .. } = request {
138 let mut request_stream = accessor_request.into_stream();
139 tasks.push(fasync::Task::local(async move {
140 while let Some(Ok(request)) = request_stream.next().await {
141 if let StoreAccessorRequest::GetValue { responder, .. } = request {
142 responder.send(None).expect("should be able to send response");
143 } else {
144 panic!("unexpected request: {request:?}");
145 }
146 }
147 }))
148 }
149 }
150 for task in tasks {
151 task.await
152 }
153 });
154
155 let migration = V1653667208LightMigration(store_proxy);
156 let fs = tempfile::tempdir().expect("failed to create tempdir");
157 let directory = open_tempdir(&fs);
158 let file_generator = FileGenerator::new(0, migration.id(), Clone::clone(&directory));
159 assert_matches!(migration.migrate(file_generator).await, Err(MigrationError::NoData));
160
161 drop(migration);
162
163 task.await;
164 }
165
166 #[fuchsia::test]
168 async fn v1653667208_light_migration_test() {
169 let (store_proxy, server_end) = create_proxy::<StoreMarker>();
170 let mut request_stream = server_end.into_stream();
171 let task = fasync::Task::local(async move {
172 let mut tasks = vec![];
173 while let Some(Ok(request)) = request_stream.next().await {
174 if let StoreRequest::CreateAccessor { read_only, accessor_request, .. } = request {
175 assert!(read_only);
176 let mut request_stream = accessor_request.into_stream();
177 tasks.push(fasync::Task::local(async move {
178 while let Some(Ok(request)) = request_stream.next().await {
179 if let StoreAccessorRequest::GetValue { key, responder } = request {
180 assert_eq!(key, LIGHT_KEY);
181 responder
182 .send(Some(Value::Stringval(
183 r#"{
184 "light_groups":{
185 "abc":{
186 "name":"abc",
187 "enabled":false,
188 "light_type":"Brightness",
189 "lights":[{
190 "value": {
191 "Brightness": 0.5
192 }
193 }],
194 "hardware_index":[],
195 "disable_conditions":[]
196 },
197 "def":{
198 "name":"def",
199 "enabled":true,
200 "light_type":"Rgb",
201 "lights":[{
202 "value": {
203 "Rgb": {
204 "red": 1.0,
205 "green": 0.5,
206 "blue": 0.0
207 }
208 }
209 }],
210 "hardware_index":[1, 2, 3],
211 "disable_conditions":[]
212 },
213 "ghi":{
214 "name":"ghi",
215 "enabled":false,
216 "light_type":"Simple",
217 "lights":[{
218 "value": {
219 "Simple": true
220 }
221 }],
222 "hardware_index":[1],
223 "disable_conditions":[]
224 },
225 "jkl":{
226 "name":"jkl",
227 "enabled":false,
228 "light_type":"Simple",
229 "lights":[{
230 "value": null
231 }],
232 "hardware_index":[1],
233 "disable_conditions":["MicSwitch"]
234 }
235 }
236 }"#
237 .to_owned(),
238 )))
239 .expect("should be able to respond");
240 } else {
241 panic!("unexpected request: {request:?}");
242 }
243 }
244 }))
245 }
246 }
247 for task in tasks {
248 task.await
249 }
250 });
251
252 let migration = V1653667208LightMigration(store_proxy);
253 let fs = tempfile::tempdir().expect("failed to create tempdir");
254 let directory = open_tempdir(&fs);
255 let file_generator = FileGenerator::new(0, migration.id(), Clone::clone(&directory));
256 assert_matches!(migration.migrate(file_generator).await, Ok(()));
257
258 let file = fuchsia_fs::directory::open_file(
259 &directory,
260 "light_info_1653667208.pfidl",
261 fuchsia_fs::PERM_READABLE,
262 )
263 .await
264 .expect("file should exist");
265 let LightGroups { groups } = fuchsia_fs::file::read_fidl::<LightGroups>(&file)
266 .await
267 .expect("should be able to read and deserialize light groups");
268 assert_eq!(groups.len(), 4);
269 assert!(groups.contains(&LightGroup {
270 name: Some("abc".to_owned()),
271 enabled: Some(false),
272 type_: Some(LightType::Brightness),
273 lights: Some(vec![LightState {
274 value: Some(LightValue::Brightness(0.5)),
275 ..Default::default()
276 }]),
277 ..Default::default()
278 }));
279 assert!(groups.contains(&LightGroup {
280 name: Some("def".to_owned()),
281 enabled: Some(true),
282 type_: Some(LightType::Rgb),
283 lights: Some(vec![LightState {
284 value: Some(LightValue::Color(ColorRgb { red: 1.0, green: 0.5, blue: 0.0 })),
285 ..Default::default()
286 }]),
287 ..Default::default()
288 }));
289 assert!(groups.contains(&LightGroup {
290 name: Some("ghi".to_owned()),
291 enabled: Some(false),
292 type_: Some(LightType::Simple),
293 lights: Some(vec![LightState {
294 value: Some(LightValue::On(true)),
295 ..Default::default()
296 }]),
297 ..Default::default()
298 }));
299 assert!(groups.contains(&LightGroup {
300 name: Some("jkl".to_owned()),
301 enabled: Some(false),
302 type_: Some(LightType::Simple),
303 lights: Some(vec![LightState { value: None, ..Default::default() }]),
304 ..Default::default()
305 }));
306
307 drop(migration);
308
309 task.await;
310 }
311}