steam_vent/auth/
guard_data.rs1use directories::ProjectDirs;
2use std::collections::HashMap;
3use std::convert::Infallible;
4use std::error::Error;
5use std::fs::{create_dir_all, read_to_string, write};
6use std::path::PathBuf;
7use thiserror::Error;
8
9pub trait GuardDataStore {
11 type Err: Error;
12
13 fn store(
15 &mut self,
16 account: &str,
17 machine_token: String,
18 ) -> impl std::future::Future<Output = Result<(), Self::Err>> + Send;
19
20 fn load(
22 &mut self,
23 account: &str,
24 ) -> impl std::future::Future<Output = Result<Option<String>, Self::Err>> + Send;
25}
26
27#[derive(Debug, Error)]
29#[non_exhaustive]
30pub enum FileStoreError {
31 #[error("error while reading tokens from {}: {:#}", path.display(), err)]
33 Read { err: std::io::Error, path: PathBuf },
34 #[error("error while writing tokens to {}: {:#}", path.display(), err)]
36 Write { err: std::io::Error, path: PathBuf },
37 #[error("error while parsing tokens from {}: {:#}", path.display(), err)]
39 Json {
40 err: serde_json::error::Error,
41 path: PathBuf,
42 },
43 #[error("error while directory {} for tokens: {:#}", path.display(), err)]
45 DirCreation { err: std::io::Error, path: PathBuf },
46}
47
48pub struct FileGuardDataStore {
50 path: PathBuf,
51}
52
53impl FileGuardDataStore {
54 pub fn new(path: PathBuf) -> Self {
56 FileGuardDataStore { path }
57 }
58
59 pub fn user_cache() -> Self {
66 let project_dirs = ProjectDirs::from("", "steam-vent", "steam-vent")
67 .expect("user cache not supported on this platform");
68 Self::new(project_dirs.cache_dir().join("machine_tokens.json"))
69 }
70
71 fn all_tokens(&self) -> Result<HashMap<String, String>, FileStoreError> {
72 if !self.path.exists() {
73 return Ok(HashMap::default());
74 }
75 let raw = read_to_string(&self.path).map_err(|err| FileStoreError::Read {
76 err,
77 path: self.path.clone(),
78 })?;
79 serde_json::from_str(&raw).map_err(|err| FileStoreError::Json {
80 err,
81 path: self.path.clone(),
82 })
83 }
84
85 fn save(&self, tokens: HashMap<String, String>) -> Result<(), FileStoreError> {
86 if let Some(parent) = self.path.parent() {
87 create_dir_all(parent).map_err(|err| FileStoreError::DirCreation {
88 err,
89 path: parent.into(),
90 })?;
91 }
92
93 let raw = serde_json::to_string(&tokens).map_err(|err| FileStoreError::Json {
94 err,
95 path: self.path.clone(),
96 })?;
97 write(&self.path, raw).map_err(|err| FileStoreError::Write {
98 err,
99 path: self.path.clone(),
100 })?;
101 Ok(())
102 }
103}
104
105impl GuardDataStore for FileGuardDataStore {
106 type Err = FileStoreError;
107
108 async fn store(&mut self, account: &str, machine_token: String) -> Result<(), Self::Err> {
109 if !machine_token.is_empty() {
110 let mut tokens = self.all_tokens()?;
111 tokens.insert(account.into(), machine_token);
112 self.save(tokens)
113 } else {
114 Ok(())
115 }
116 }
117
118 async fn load(&mut self, account: &str) -> Result<Option<String>, Self::Err> {
119 let mut tokens = self.all_tokens()?;
120 Ok(tokens.remove(account).filter(|token| !token.is_empty()))
121 }
122}
123
124pub struct NullGuardDataStore;
126
127impl GuardDataStore for NullGuardDataStore {
128 type Err = Infallible;
129
130 async fn store(&mut self, _account: &str, _machine_token: String) -> Result<(), Self::Err> {
131 Ok(())
132 }
133
134 async fn load(&mut self, _account: &str) -> Result<Option<String>, Self::Err> {
135 Ok(None)
136 }
137}