3 use anyhow
::{bail, format_err, Error}
;
4 use serde
::{Serialize, Deserialize}
;
7 api
::{api, Router, SubdirMap}
,
8 list_subdirs_api_method
,
18 MEDIA_POOL_NAME_SCHEMA
,
41 schema
: MEDIA_POOL_NAME_SCHEMA
,
47 description
: "List of registered backup media.",
55 pub async
fn list_media(pool
: Option
<String
>) -> Result
<Vec
<MediaListEntry
>, Error
> {
57 let (config
, _digest
) = config
::media_pool
::config()?
;
59 let status_path
= Path
::new(TAPE_STATUS_DIR
);
61 let catalogs
= tokio
::task
::spawn_blocking(move || {
62 // update online media status
63 if let Err(err
) = update_online_status(status_path
) {
65 eprintln
!("update online media status failed - using old state");
67 // test what catalog files we have
68 MediaCatalog
::media_with_catalogs(status_path
)
71 let mut list
= Vec
::new();
73 for (_section_type
, data
) in config
.sections
.values() {
74 let pool_name
= match data
["name"].as_str() {
78 if let Some(ref name
) = pool
{
79 if name
!= pool_name
{
84 let config
: MediaPoolConfig
= config
.lookup("pool", pool_name
)?
;
86 let use_offline_media
= true; // does not matter here
87 let pool
= MediaPool
::with_config(status_path
, &config
, use_offline_media
)?
;
89 let current_time
= proxmox
::tools
::time
::epoch_i64();
91 for media
in pool
.list_media() {
92 let expired
= pool
.media_is_expired(&media
, current_time
);
94 let media_set_uuid
= media
.media_set_label()
95 .map(|set
| set
.uuid
.to_string());
97 let seq_nr
= media
.media_set_label()
98 .map(|set
| set
.seq_nr
);
100 let media_set_name
= media
.media_set_label()
102 pool
.generate_media_set_name(&set
.uuid
, config
.template
.clone())
103 .unwrap_or_else(|_
| set
.uuid
.to_string())
106 let catalog_ok
= if media
.media_set_label().is_none() {
107 // Media is empty, we need no catalog
110 catalogs
.contains(media
.uuid())
113 list
.push(MediaListEntry
{
114 uuid
: media
.uuid().to_string(),
115 changer_id
: media
.changer_id().to_string(),
116 ctime
: media
.ctime(),
117 pool
: Some(pool_name
.to_string()),
118 location
: media
.location().clone(),
119 status
: *media
.status(),
122 media_set_ctime
: media
.media_set_label().map(|set
| set
.ctime
),
132 let inventory
= Inventory
::load(status_path
)?
;
134 for media_id
in inventory
.list_unassigned_media() {
136 let (mut status
, location
) = inventory
.status_and_location(&media_id
.label
.uuid
);
138 if status
== MediaStatus
::Unknown
{
139 status
= MediaStatus
::Writable
;
142 list
.push(MediaListEntry
{
143 uuid
: media_id
.label
.uuid
.to_string(),
144 ctime
: media_id
.label
.ctime
,
145 changer_id
: media_id
.label
.changer_id
.to_string(),
148 catalog
: true, // empty, so we do not need a catalog
150 media_set_uuid
: None
,
151 media_set_name
: None
,
152 media_set_ctime
: None
,
166 schema
: MEDIA_LABEL_SCHEMA
,
169 description
: "Force removal (even if media is used in a media set).",
176 /// Destroy media (completely remove from database)
177 pub fn destroy_media(changer_id
: String
, force
: Option
<bool
>,) -> Result
<(), Error
> {
179 let force
= force
.unwrap_or(false);
181 let status_path
= Path
::new(TAPE_STATUS_DIR
);
182 let mut inventory
= Inventory
::load(status_path
)?
;
184 let media_id
= inventory
.find_media_by_changer_id(&changer_id
)
185 .ok_or_else(|| format_err
!("no such media '{}'", changer_id
))?
;
188 if let Some(ref set
) = media_id
.media_set_label
{
189 let is_empty
= set
.uuid
.as_ref() == [0u8;16];
191 bail
!("media '{}' contains data (please use 'force' flag to remove.", changer_id
);
196 let uuid
= media_id
.label
.uuid
.clone();
199 inventory
.remove_media(&uuid
)?
;
207 schema
: MEDIA_POOL_NAME_SCHEMA
,
211 schema
: MEDIA_LABEL_SCHEMA
,
215 description
: "Filter by media UUID.",
220 description
: "Filter by media set UUID.",
225 schema
: BACKUP_TYPE_SCHEMA
,
229 schema
: BACKUP_ID_SCHEMA
,
234 #[derive(Serialize,Deserialize)]
235 #[serde(rename_all="kebab-case")]
236 /// Content list filter parameters
237 pub struct MediaContentListFilter
{
238 pub pool
: Option
<String
>,
239 pub changer_id
: Option
<String
>,
240 pub media
: Option
<String
>,
241 pub media_set
: Option
<String
>,
242 pub backup_type
: Option
<String
>,
243 pub backup_id
: Option
<String
>,
250 type: MediaContentListFilter
,
256 description
: "Media content list.",
259 type: MediaContentEntry
,
263 /// List media content
265 filter
: MediaContentListFilter
,
266 ) -> Result
<Vec
<MediaContentEntry
>, Error
> {
268 let (config
, _digest
) = config
::media_pool
::config()?
;
270 let status_path
= Path
::new(TAPE_STATUS_DIR
);
271 let inventory
= Inventory
::load(status_path
)?
;
273 let media_uuid
= filter
.media
.and_then(|s
| s
.parse().ok());
274 let media_set_uuid
= filter
.media_set
.and_then(|s
| s
.parse().ok());
276 let mut list
= Vec
::new();
278 for media_id
in inventory
.list_used_media() {
279 let set
= media_id
.media_set_label
.as_ref().unwrap();
281 if let Some(ref changer_id
) = filter
.changer_id
{
282 if &media_id
.label
.changer_id
!= changer_id { continue; }
285 if let Some(ref pool
) = filter
.pool
{
286 if &set
.pool
!= pool { continue; }
289 if let Some(ref media_uuid
) = media_uuid
{
290 if &media_id
.label
.uuid
!= media_uuid { continue; }
293 if let Some(ref media_set_uuid
) = media_set_uuid
{
294 if &set
.uuid
!= media_set_uuid { continue; }
297 let config
: MediaPoolConfig
= config
.lookup("pool", &set
.pool
)?
;
299 let media_set_name
= inventory
300 .generate_media_set_name(&set
.uuid
, config
.template
.clone())
301 .unwrap_or_else(|_
| set
.uuid
.to_string());
303 let catalog
= MediaCatalog
::open(status_path
, &media_id
.label
.uuid
, false, false)?
;
305 for snapshot
in catalog
.snapshot_index().keys() {
306 let backup_dir
: BackupDir
= snapshot
.parse()?
;
308 if let Some(ref backup_type
) = filter
.backup_type
{
309 if backup_dir
.group().backup_type() != backup_type { continue; }
311 if let Some(ref backup_id
) = filter
.backup_id
{
312 if backup_dir
.group().backup_id() != backup_id { continue; }
315 list
.push(MediaContentEntry
{
316 uuid
: media_id
.label
.uuid
.to_string(),
317 changer_id
: media_id
.label
.changer_id
.to_string(),
318 pool
: set
.pool
.clone(),
319 media_set_name
: media_set_name
.clone(),
320 media_set_uuid
: set
.uuid
.to_string(),
322 snapshot
: snapshot
.to_owned(),
323 backup_time
: backup_dir
.backup_time(),
331 const SUBDIRS
: SubdirMap
= &[
335 .get(&API_METHOD_LIST_CONTENT
)
340 .get(&API_METHOD_DESTROY_MEDIA
)
345 .get(&API_METHOD_LIST_MEDIA
)
350 pub const ROUTER
: Router
= Router
::new()
351 .get(&list_subdirs_api_method
!(SUBDIRS
))