1 use std
::path
::{Path, PathBuf}
;
2 use std
::collections
::BTreeMap
;
5 use ::serde
::{Deserialize, Serialize}
;
27 #[derive(Debug, PartialEq, Clone, Serialize, Deserialize)]
29 pub enum MediaLocation
{
30 /// Ready for use (inside tape library)
32 /// Local available, but need to be mounted (insert into tape
35 /// Media is inside a Vault
39 #[derive(Serialize,Deserialize)]
40 struct MediaStateEntry
{
42 #[serde(skip_serializing_if="Option::is_none")]
43 l
: Option
<MediaLocation
>,
44 #[serde(skip_serializing_if="Option::is_none")]
45 s
: Option
<MediaStatus
>,
48 impl MediaStateEntry
{
49 fn new(uuid
: Uuid
) -> Self {
50 MediaStateEntry { u: uuid, l: None, s: None }
54 /// Stores MediaLocation and MediaState persistently
55 pub struct MediaStateDatabase
{
57 map
: BTreeMap
<Uuid
, MediaStateEntry
>,
59 database_path
: PathBuf
,
60 lockfile_path
: PathBuf
,
63 impl MediaStateDatabase
{
65 pub const MEDIA_STATUS_DATABASE_FILENAME
: &'
static str = "media-status-db.json";
66 pub const MEDIA_STATUS_DATABASE_LOCKFILE
: &'
static str = ".media-status-db.lck";
70 pub fn lock(&self) -> Result
<std
::fs
::File
, Error
> {
71 open_file_locked(&self.lockfile_path
, std
::time
::Duration
::new(10, 0), true)
74 /// Returns status and location with reasonable defaults.
76 /// Default status is 'MediaStatus::Unknown'.
77 /// Default location is 'MediaLocation::Offline'.
78 pub fn status_and_location(&self, uuid
: &Uuid
) -> (MediaStatus
, MediaLocation
) {
80 match self.map
.get(uuid
) {
82 // no info stored - assume media is writable/offline
83 (MediaStatus
::Unknown
, MediaLocation
::Offline
)
86 let location
= entry
.l
.clone().unwrap_or(MediaLocation
::Offline
);
87 let status
= entry
.s
.unwrap_or(MediaStatus
::Unknown
);
93 fn load_media_db(path
: &Path
) -> Result
<BTreeMap
<Uuid
, MediaStateEntry
>, Error
> {
95 let data
= file_get_json(path
, Some(json
!([])))?
;
96 let list
: Vec
<MediaStateEntry
> = serde_json
::from_value(data
)?
;
98 let mut map
= BTreeMap
::new();
99 for entry
in list
.into_iter() {
100 map
.insert(entry
.u
.clone(), entry
);
106 /// Load the database into memory
107 pub fn load(base_path
: &Path
) -> Result
<MediaStateDatabase
, Error
> {
109 let mut database_path
= base_path
.to_owned();
110 database_path
.push(Self::MEDIA_STATUS_DATABASE_FILENAME
);
112 let mut lockfile_path
= base_path
.to_owned();
113 lockfile_path
.push(Self::MEDIA_STATUS_DATABASE_LOCKFILE
);
115 Ok(MediaStateDatabase
{
116 map
: Self::load_media_db(&database_path
)?
,
122 /// Lock database, reload database, set status to Full, store database
123 pub fn set_media_status_full(&mut self, uuid
: &Uuid
) -> Result
<(), Error
> {
124 let _lock
= self.lock()?
;
125 self.map
= Self::load_media_db(&self.database_path
)?
;
126 let entry
= self.map
.entry(uuid
.clone()).or_insert(MediaStateEntry
::new(uuid
.clone()));
127 entry
.s
= Some(MediaStatus
::Full
);
131 /// Update online status
132 pub fn update_online_status(&mut self, online_map
: &OnlineStatusMap
) -> Result
<(), Error
> {
133 let _lock
= self.lock()?
;
134 self.map
= Self::load_media_db(&self.database_path
)?
;
136 for (_uuid
, entry
) in self.map
.iter_mut() {
137 if let Some(changer_name
) = online_map
.lookup_changer(&entry
.u
) {
138 entry
.l
= Some(MediaLocation
::Online(changer_name
.to_string()));
140 if let Some(MediaLocation
::Online(ref changer_name
)) = entry
.l
{
141 match online_map
.online_map(changer_name
) {
143 // no such changer device
144 entry
.l
= Some(MediaLocation
::Offline
);
147 // got no info - do nothing
150 // media changer changed
151 entry
.l
= Some(MediaLocation
::Offline
);
158 for (uuid
, changer_name
) in online_map
.changer_map() {
159 if self.map
.contains_key(uuid
) { continue; }
160 let mut entry
= MediaStateEntry
::new(uuid
.clone());
161 entry
.l
= Some(MediaLocation
::Online(changer_name
.to_string()));
162 self.map
.insert(uuid
.clone(), entry
);
168 /// Lock database, reload database, set status to Damaged, store database
169 pub fn set_media_status_damaged(&mut self, uuid
: &Uuid
) -> Result
<(), Error
> {
170 let _lock
= self.lock()?
;
171 self.map
= Self::load_media_db(&self.database_path
)?
;
172 let entry
= self.map
.entry(uuid
.clone()).or_insert(MediaStateEntry
::new(uuid
.clone()));
173 entry
.s
= Some(MediaStatus
::Damaged
);
177 /// Lock database, reload database, set status to None, store database
178 pub fn clear_media_status(&mut self, uuid
: &Uuid
) -> Result
<(), Error
> {
179 let _lock
= self.lock()?
;
180 self.map
= Self::load_media_db(&self.database_path
)?
;
181 let entry
= self.map
.entry(uuid
.clone()).or_insert(MediaStateEntry
::new(uuid
.clone()));
186 /// Lock database, reload database, set location to vault, store database
187 pub fn set_media_location_vault(&mut self, uuid
: &Uuid
, vault
: &str) -> Result
<(), Error
> {
188 let _lock
= self.lock()?
;
189 self.map
= Self::load_media_db(&self.database_path
)?
;
190 let entry
= self.map
.entry(uuid
.clone()).or_insert(MediaStateEntry
::new(uuid
.clone()));
191 entry
.l
= Some(MediaLocation
::Vault(vault
.to_string()));
195 /// Lock database, reload database, set location to offline, store database
196 pub fn set_media_location_offline(&mut self, uuid
: &Uuid
) -> Result
<(), Error
> {
197 let _lock
= self.lock()?
;
198 self.map
= Self::load_media_db(&self.database_path
)?
;
199 let entry
= self.map
.entry(uuid
.clone()).or_insert(MediaStateEntry
::new(uuid
.clone()));
200 entry
.l
= Some(MediaLocation
::Offline
);
204 fn store(&self) -> Result
<(), Error
> {
206 let mut list
= Vec
::new();
207 for entry
in self.map
.values() {
211 let raw
= serde_json
::to_string_pretty(&serde_json
::to_value(list
)?
)?
;
213 let backup_user
= crate::backup
::backup_user()?
;
214 let mode
= nix
::sys
::stat
::Mode
::from_bits_truncate(0o0640);
215 let options
= CreateOptions
::new()
217 .owner(backup_user
.uid
)
218 .group(backup_user
.gid
);
220 replace_file(&self.database_path
, raw
.as_bytes(), options
)?
;