rs_teststand/users/users_file.rs
1//! The file that holds the station's users and groups.
2
3use rs_teststand_sys::Dispatch;
4
5use crate::Error;
6use crate::dispids::users_file;
7use crate::property::{PropertyObject, PropertyObjectFile};
8
9/// The station's user list, as a file (`UsersFile`).
10///
11/// Reached from [`Engine::users_file`](crate::Engine::users_file). Everything a
12/// station knows about who may log in and what they may do lives here, and this
13/// is the only route to **persisting** it: [`User`](crate::User) objects created
14/// through [`Engine::new_user`](crate::Engine::new_user) exist in memory until
15/// this file is saved.
16///
17/// The lists are [`PropertyObject`] arrays rather than typed collections,
18/// because that is what the engine returns and how it expects them to be
19/// edited. Add or remove an element with the property-object API and the change
20/// lands in the list itself; see [`user_list`](Self::user_list).
21///
22/// # Saving
23///
24/// Go through [`as_property_object_file`](Self::as_property_object_file) and
25/// call [`PropertyObjectFile::save_file_if_modified`], passing `false` so the
26/// engine writes the file instead of asking a person first.
27#[derive(Debug)]
28pub struct UsersFile {
29 dispatch: Box<dyn Dispatch>,
30}
31
32impl UsersFile {
33 /// Wraps a dispatch handle returned by the engine.
34 pub(crate) fn new(dispatch: Box<dyn Dispatch>) -> Self {
35 Self { dispatch }
36 }
37
38 /// The users, as an array of `User` objects (`UsersFile.UserList`).
39 ///
40 /// Editing happens through this array, not through a setter: use the
41 /// property-object API to append or remove an element and the station's
42 /// user list changes with it.
43 ///
44 /// # Errors
45 /// [`Error`] if the COM call fails or returns an unexpected type.
46 pub fn user_list(&self) -> Result<PropertyObject, Error> {
47 Ok(PropertyObject::new(
48 self.dispatch.get(users_file::USER_LIST)?.into_object()?,
49 ))
50 }
51
52 /// The user groups (`UsersFile.UserGroupList`).
53 ///
54 /// # Errors
55 /// [`Error`] if the COM call fails or returns an unexpected type.
56 pub fn user_group_list(&self) -> Result<PropertyObject, Error> {
57 Ok(PropertyObject::new(
58 self.dispatch
59 .get(users_file::USER_GROUP_LIST)?
60 .into_object()?,
61 ))
62 }
63
64 /// The user profiles (`UsersFile.UserProfileList`).
65 ///
66 /// # Errors
67 /// [`Error`] if the COM call fails or returns an unexpected type.
68 pub fn user_profile_list(&self) -> Result<PropertyObject, Error> {
69 Ok(PropertyObject::new(
70 self.dispatch
71 .get(users_file::USER_PROFILE_LIST)?
72 .into_object()?,
73 ))
74 }
75
76 /// Re-reads the file from disk (`UsersFile.ReloadFromDisk`).
77 ///
78 /// **Everything read from this file beforehand is stale afterwards.** The
79 /// engine documents that references to the user list and to individual
80 /// users are out of date once this returns, so drop what you are holding
81 /// and read it again rather than reusing it. Consuming `&self` cannot
82 /// express that, since the file object itself stays valid; only what came
83 /// out of it does not.
84 ///
85 /// # Errors
86 /// [`Error`] if the COM call fails.
87 pub fn reload_from_disk(&self) -> Result<(), Error> {
88 self.dispatch.call(users_file::RELOAD_FROM_DISK, &[])?;
89 Ok(())
90 }
91
92 /// The file view of this object (`UsersFile.AsPropertyObjectFile`).
93 ///
94 /// Where the path lives, and where saving happens.
95 ///
96 /// # Errors
97 /// [`Error`] if the COM call fails or returns an unexpected type.
98 pub fn as_property_object_file(&self) -> Result<PropertyObjectFile, Error> {
99 Ok(PropertyObjectFile::new(
100 self.dispatch
101 .call(users_file::AS_PROPERTY_OBJECT_FILE, &[])?
102 .into_object()?,
103 ))
104 }
105}