1
use std::{collections::HashMap, sync::Arc, time::Duration};
2

            
3
#[cfg(feature = "async-std")]
4
use async_lock::RwLock;
5
#[cfg(feature = "tokio")]
6
use tokio::sync::RwLock;
7

            
8
use crate::{AsAttributes, Result, Secret, dbus, file};
9

            
10
/// A [Secret Service](crate::dbus) or [file](crate::file) backed keyring
11
/// implementation.
12
///
13
/// It will automatically use the file backend if the application is sandboxed
14
/// and otherwise falls back to the DBus service using it [default
15
/// collection](crate::dbus::Service::default_collection).
16
///
17
/// The File backend requires a [`org.freedesktop.portal.Secret`](https://flatpak.github.io/xdg-desktop-portal/docs/doc-org.freedesktop.portal.Secret.html) implementation
18
/// to retrieve the key that will be used to encrypt the backend file.
19
#[derive(Debug)]
20
pub enum Keyring {
21
    #[doc(hidden)]
22
    File(Arc<RwLock<Option<file::Keyring>>>, Secret),
23
    #[doc(hidden)]
24
    DBus(dbus::Collection),
25
}
26

            
27
impl Keyring {
28
    /// Create a new instance of the Keyring.
29
    ///
30
    /// Auto-detects whether the application is sandboxed and uses the
31
    /// appropriate backend (file backend for sandboxed apps, D-Bus service
32
    /// for host apps). Falls back to D-Bus if the secret portal is not
33
    /// available.
34
    pub async fn new() -> Result<Self> {
35
        if ashpd::is_sandboxed() {
36
            match Self::sandboxed().await {
37
                Ok(keyring) => Ok(keyring),
38
                // Fallback to host keyring if portal is not available
39
                Err(crate::Error::File(file::Error::Portal(ashpd::Error::PortalNotFound(_)))) => {
40
                    #[cfg(feature = "tracing")]
41
                    tracing::debug!(
42
                        "org.freedesktop.portal.Secrets is not available, falling back to the Secret Service backend"
43
                    );
44
                    Self::host().await
45
                }
46
                Err(e) => Err(e),
47
            }
48
        } else {
49
            Self::host().await
50
        }
51
    }
52

            
53
    /// Use the file backend with secret portal (for sandboxed apps).
54
    pub async fn sandboxed() -> Result<Self> {
55
        #[cfg(feature = "tracing")]
56
        tracing::debug!("Using file backend (sandboxed mode)");
57

            
58
        let secret = Secret::sandboxed().await?;
59
        let path = crate::file::api::Keyring::default_path()?;
60
        Self::sandboxed_with_path(&path, secret).await
61
    }
62

            
63
    /// Use the file backend with a custom path.
64
    ///
65
    /// # Arguments
66
    /// * `path` - Path to the keyring file
67
    /// * `secret` - Secret to unlock the keyring (use `Secret::sandboxed()` or
68
    ///   `Secret::random()` for tests)
69
7
    pub async fn sandboxed_with_path(
70
        path: impl AsRef<std::path::Path>,
71
        secret: Secret,
72
    ) -> Result<Self> {
73
        #[cfg(feature = "tracing")]
74
        tracing::debug!("Using file backend with custom path");
75

            
76
13
        let file = file::UnlockedKeyring::load(path, Some(secret.clone())).await?;
77
4
        Ok(Self::File(
78
8
            Arc::new(RwLock::new(Some(file::Keyring::Unlocked(file)))),
79
4
            secret,
80
        ))
81
    }
82

            
83
    /// Variant of [`new`](Self::new) that skips existing items validation
84
    /// when using the file backend.
85
    ///
86
    /// See [`sandboxed_with_path_unchecked`](Self::sandboxed_with_path_unchecked)
87
    /// for details.
88
    pub async fn new_unchecked() -> Result<Self> {
89
        if ashpd::is_sandboxed() {
90
            match Self::sandboxed_unchecked().await {
91
                Ok(keyring) => Ok(keyring),
92
                Err(crate::Error::File(file::Error::Portal(ashpd::Error::PortalNotFound(_)))) => {
93
                    #[cfg(feature = "tracing")]
94
                    tracing::debug!(
95
                        "org.freedesktop.portal.Secrets is not available, falling back to the Secret Service backend"
96
                    );
97
                    Self::host().await
98
                }
99
                Err(e) => Err(e),
100
            }
101
        } else {
102
            Self::host().await
103
        }
104
    }
105

            
106
    /// Variant of [`sandboxed`](Self::sandboxed) that skips existing items
107
    /// validation.
108
    ///
109
    /// See [`sandboxed_with_path_unchecked`](Self::sandboxed_with_path_unchecked)
110
    /// for details.
111
    pub async fn sandboxed_unchecked() -> Result<Self> {
112
        #[cfg(feature = "tracing")]
113
        tracing::debug!("Using file backend (sandboxed unchecked mode)");
114

            
115
        let secret = Secret::sandboxed().await?;
116
        let path = crate::file::api::Keyring::default_path()?;
117
        Self::sandboxed_with_path_unchecked(&path, secret).await
118
    }
119

            
120
    /// Variant of [`sandboxed_with_path`](Self::sandboxed_with_path) that does
121
    /// not verify that existing items can be decrypted with the provided
122
    /// secret.
123
    ///
124
    /// Items encrypted with a previous secret will fail to decrypt individually
125
    /// but won't prevent the keyring from being used. Use
126
    /// [`UnlockedKeyring::delete_broken_items`](file::UnlockedKeyring::delete_broken_items)
127
    /// to remove unreadable items.
128
    #[allow(unsafe_code)]
129
    pub async fn sandboxed_with_path_unchecked(
130
        path: impl AsRef<std::path::Path>,
131
        secret: Secret,
132
    ) -> Result<Self> {
133
        #[cfg(feature = "tracing")]
134
        tracing::debug!("Using file backend with custom path (unchecked mode)");
135

            
136
        // SAFETY: this is not truly unsafe in the memory-safety sense;
137
        // `load_unchecked` merely skips item validation.
138
        let file = unsafe { file::UnlockedKeyring::load_unchecked(path, secret.clone()).await? };
139
        Ok(Self::File(
140
            Arc::new(RwLock::new(Some(file::Keyring::Unlocked(file)))),
141
            secret,
142
        ))
143
    }
144

            
145
    /// Use the D-Bus Secret Service.
146
    pub async fn host() -> Result<Self> {
147
        #[cfg(feature = "tracing")]
148
        tracing::debug!("Using D-Bus Secret Service (host mode)");
149

            
150
        let service = dbus::Service::new().await?;
151
        let collection = service.default_collection().await?;
152
        Ok(Self::DBus(collection))
153
    }
154

            
155
    /// Use the D-Bus Secret Service with a custom connection.
156
36
    pub async fn host_with_connection(connection: zbus::Connection) -> Result<Self> {
157
        #[cfg(feature = "tracing")]
158
        tracing::debug!("Using D-Bus Secret Service with custom connection (test mode)");
159

            
160
18
        let service = dbus::Service::new_with_connection(&connection).await?;
161
20
        let collection = service.default_collection().await?;
162
4
        Ok(Self::DBus(collection))
163
    }
164

            
165
    /// Unlock the used collection.
166
8
    pub async fn unlock(&self) -> Result<()> {
167
2
        match self {
168
6
            Self::DBus(backend) => backend.unlock(None).await?,
169
2
            Self::File(keyring, secret) => {
170
4
                let mut kg = keyring.write().await;
171
4
                let kg_value = kg.take();
172
6
                if let Some(file::Keyring::Locked(locked)) = kg_value {
173
                    #[cfg(feature = "tracing")]
174
                    tracing::debug!("Unlocking file backend keyring");
175

            
176
12
                    let unlocked = locked
177
4
                        .unlock(secret.clone())
178
6
                        .await
179
2
                        .map_err(crate::Error::File)?;
180
2
                    *kg = Some(file::Keyring::Unlocked(unlocked));
181
                } else {
182
2
                    *kg = kg_value;
183
                }
184
            }
185
        };
186
2
        Ok(())
187
    }
188

            
189
    /// Lock the used collection.
190
8
    pub async fn lock(&self) -> Result<()> {
191
2
        match self {
192
6
            Self::DBus(backend) => backend.lock(None).await?,
193
2
            Self::File(keyring, _) => {
194
4
                let mut kg = keyring.write().await;
195
4
                let kg_value = kg.take();
196
6
                if let Some(file::Keyring::Unlocked(unlocked)) = kg_value {
197
                    #[cfg(feature = "tracing")]
198
                    tracing::debug!("Locking file backend keyring");
199

            
200
2
                    let locked = unlocked.lock();
201
2
                    *kg = Some(file::Keyring::Locked(locked));
202
                } else {
203
2
                    *kg = kg_value;
204
                }
205
            }
206
        };
207
2
        Ok(())
208
    }
209

            
210
    /// Whether the keyring is locked or not.
211
8
    pub async fn is_locked(&self) -> Result<bool> {
212
2
        match self {
213
6
            Self::DBus(collection) => collection.is_locked().await.map_err(From::from),
214
2
            Self::File(keyring, _) => {
215
4
                let keyring_guard = keyring.read().await;
216
4
                Ok(keyring_guard
217
2
                    .as_ref()
218
2
                    .expect("Keyring must exist")
219
2
                    .is_locked())
220
            }
221
        }
222
    }
223

            
224
    /// Remove items that matches the attributes.
225
36
    pub async fn delete(&self, attributes: &impl AsAttributes) -> Result<()> {
226
6
        match self {
227
6
            Self::DBus(backend) => {
228
18
                let items = backend.search_items(attributes).await?;
229
24
                for item in items {
230
30
                    item.delete(None).await?;
231
                }
232
            }
233
6
            Self::File(keyring, _) => {
234
12
                let kg = keyring.read().await;
235
12
                match kg.as_ref() {
236
6
                    Some(file::Keyring::Unlocked(backend)) => {
237
27
                        backend
238
6
                            .delete(attributes)
239
25
                            .await
240
7
                            .map_err(crate::Error::File)?;
241
                    }
242
                    Some(file::Keyring::Locked(_)) => {
243
2
                        return Err(crate::file::Error::Locked.into());
244
                    }
245
                    _ => unreachable!("A keyring must exist"),
246
                }
247
            }
248
        };
249
7
        Ok(())
250
    }
251

            
252
    /// Retrieve all the items.
253
8
    pub async fn items(&self) -> Result<Vec<Item>> {
254
2
        let items = match self {
255
2
            Self::DBus(backend) => {
256
6
                let items = backend.items().await?;
257
4
                items.into_iter().map(Item::for_dbus).collect::<Vec<_>>()
258
            }
259
2
            Self::File(keyring, _) => {
260
4
                let kg = keyring.read().await;
261
4
                match kg.as_ref() {
262
2
                    Some(file::Keyring::Unlocked(backend)) => {
263
4
                        let items = backend.items().await.map_err(crate::Error::File)?;
264
2
                        items
265
                            .into_iter()
266
6
                            .map(|i| Item::for_file(i.into(), Arc::clone(keyring)))
267
                            .collect::<Vec<_>>()
268
                    }
269
                    Some(file::Keyring::Locked(_)) => {
270
2
                        return Err(crate::file::Error::Locked.into());
271
                    }
272
                    _ => unreachable!("A keyring must exist"),
273
                }
274
            }
275
        };
276
2
        Ok(items)
277
    }
278

            
279
    /// Create a new item.
280
8
    pub async fn create_item(
281
        &self,
282
        label: &str,
283
        attributes: &impl AsAttributes,
284
        secret: impl Into<Secret>,
285
        replace: bool,
286
    ) -> Result<()> {
287
8
        match self {
288
7
            Self::DBus(backend) => {
289
19
                backend
290
7
                    .create_item(label, attributes, secret, replace, None)
291
33
                    .await?;
292
            }
293
8
            Self::File(keyring, _) => {
294
16
                let kg = keyring.read().await;
295
16
                match kg.as_ref() {
296
8
                    Some(file::Keyring::Unlocked(backend)) => {
297
28
                        backend
298
8
                            .create_item(label, attributes, secret, replace)
299
30
                            .await
300
12
                            .map_err(crate::Error::File)?;
301
                    }
302
                    Some(file::Keyring::Locked(_)) => {
303
2
                        return Err(crate::file::Error::Locked.into());
304
                    }
305
                    _ => unreachable!("A keyring must exist"),
306
                }
307
            }
308
        };
309
6
        Ok(())
310
    }
311

            
312
    /// Find items based on their attributes.
313
24
    pub async fn search_items(&self, attributes: &impl AsAttributes) -> Result<Vec<Item>> {
314
6
        let items = match self {
315
6
            Self::DBus(backend) => {
316
18
                let items = backend.search_items(attributes).await?;
317
12
                items.into_iter().map(Item::for_dbus).collect::<Vec<_>>()
318
            }
319
6
            Self::File(keyring, _) => {
320
12
                let kg = keyring.read().await;
321
12
                match kg.as_ref() {
322
6
                    Some(file::Keyring::Unlocked(backend)) => {
323
24
                        let items = backend
324
6
                            .search_items(attributes)
325
18
                            .await
326
6
                            .map_err(crate::Error::File)?;
327
6
                        items
328
                            .into_iter()
329
18
                            .map(|i| Item::for_file(i.into(), Arc::clone(keyring)))
330
                            .collect::<Vec<_>>()
331
                    }
332
                    Some(file::Keyring::Locked(_)) => {
333
2
                        return Err(crate::file::Error::Locked.into());
334
                    }
335
                    _ => unreachable!("A keyring must exist"),
336
                }
337
            }
338
        };
339
6
        Ok(items)
340
    }
341
}
342

            
343
/// A generic secret with a label and attributes.
344
#[derive(Debug)]
345
pub enum Item {
346
    #[doc(hidden)]
347
    File(
348
        RwLock<Option<file::Item>>,
349
        Arc<RwLock<Option<file::Keyring>>>,
350
    ),
351
    #[doc(hidden)]
352
    DBus(dbus::Item),
353
}
354

            
355
impl Item {
356
2
    fn for_file(item: file::Item, backend: Arc<RwLock<Option<file::Keyring>>>) -> Self {
357
4
        Self::File(RwLock::new(Some(item)), backend)
358
    }
359

            
360
2
    fn for_dbus(item: dbus::Item) -> Self {
361
2
        Self::DBus(item)
362
    }
363

            
364
    /// The item label.
365
10
    pub async fn label(&self) -> Result<String> {
366
2
        let label = match self {
367
2
            Self::File(item, _) => {
368
4
                let item_guard = item.read().await;
369
4
                let file_item = item_guard.as_ref().expect("Item must exist");
370
2
                match file_item {
371
4
                    file::Item::Unlocked(unlocked) => unlocked.label().to_owned(),
372
2
                    file::Item::Locked(_) => return Err(crate::file::Error::Locked.into()),
373
                }
374
            }
375
6
            Self::DBus(item) => item.label().await?,
376
        };
377
2
        Ok(label)
378
    }
379

            
380
    /// Sets the item label.
381
12
    pub async fn set_label(&self, label: &str) -> Result<()> {
382
2
        match self {
383
2
            Self::File(item, keyring) => {
384
4
                let mut item_guard = item.write().await;
385
4
                let file_item = item_guard.as_mut().expect("Item must exist");
386

            
387
2
                match file_item {
388
2
                    file::Item::Unlocked(unlocked) => {
389
2
                        unlocked.set_label(label);
390

            
391
2
                        let kg = keyring.read().await;
392
4
                        match kg.as_ref() {
393
2
                            Some(file::Keyring::Unlocked(backend)) => {
394
8
                                backend
395
                                    .create_item(
396
2
                                        unlocked.label(),
397
2
                                        &unlocked.attributes(),
398
2
                                        unlocked.secret(),
399
                                        true,
400
                                    )
401
8
                                    .await
402
4
                                    .map_err(crate::Error::File)?;
403
                            }
404
                            Some(file::Keyring::Locked(_)) => {
405
2
                                return Err(crate::file::Error::Locked.into());
406
                            }
407
                            None => unreachable!("A keyring must exist"),
408
                        }
409
                    }
410
                    file::Item::Locked(_) => {
411
2
                        return Err(crate::file::Error::Locked.into());
412
                    }
413
                }
414
            }
415
6
            Self::DBus(item) => item.set_label(label).await?,
416
        };
417
2
        Ok(())
418
    }
419

            
420
    /// Retrieve the item attributes.
421
10
    pub async fn attributes(&self) -> Result<HashMap<String, String>> {
422
2
        let attributes = match self {
423
2
            Self::File(item, _) => {
424
4
                let item_guard = item.read().await;
425
4
                let file_item = item_guard.as_ref().expect("Item must exist");
426
2
                match file_item {
427
2
                    file::Item::Unlocked(unlocked) => unlocked
428
                        .attributes()
429
                        .iter()
430
6
                        .map(|(k, v)| (k.to_owned(), v.to_string()))
431
                        .collect::<HashMap<_, _>>(),
432
2
                    file::Item::Locked(_) => return Err(crate::file::Error::Locked.into()),
433
                }
434
            }
435
6
            Self::DBus(item) => item.attributes().await?,
436
        };
437
2
        Ok(attributes)
438
    }
439

            
440
    /// Retrieve the item attributes as a typed schema.
441
    ///
442
    /// # Example
443
    ///
444
    /// ```no_run
445
    /// # use oo7::{SecretSchema, Item};
446
    /// # #[derive(SecretSchema, Debug)]
447
    /// # #[schema(name = "org.example.Password")]
448
    /// # struct PasswordSchema {
449
    /// #     username: String,
450
    /// #     server: String,
451
    /// # }
452
    /// # async fn example(item: &Item) -> Result<(), oo7::Error> {
453
    /// let schema = item.attributes_as::<PasswordSchema>().await?;
454
    /// println!("Username: {}", schema.username);
455
    /// # Ok(())
456
    /// # }
457
    /// ```
458
    #[cfg(feature = "schema")]
459
    #[cfg_attr(docsrs, doc(cfg(feature = "schema")))]
460
4
    pub async fn attributes_as<T>(&self) -> Result<T>
461
    where
462
        T: for<'a> std::convert::TryFrom<&'a HashMap<String, String>, Error = crate::SchemaError>,
463
    {
464
2
        match self {
465
4
            Self::File(..) => T::try_from(&self.attributes().await?)
466
2
                .map_err(crate::file::Error::Schema)
467
2
                .map_err(Into::into),
468
4
            Self::DBus(_) => T::try_from(&self.attributes().await?)
469
2
                .map_err(crate::dbus::Error::Schema)
470
2
                .map_err(Into::into),
471
        }
472
    }
473

            
474
    /// Sets the item attributes.
475
24
    pub async fn set_attributes(&self, attributes: &impl AsAttributes) -> Result<()> {
476
4
        match self {
477
4
            Self::File(item, keyring) => {
478
8
                let kg = keyring.read().await;
479

            
480
8
                match kg.as_ref() {
481
4
                    Some(file::Keyring::Unlocked(backend)) => {
482
8
                        let mut item_guard = item.write().await;
483
8
                        let file_item = item_guard.as_mut().expect("Item must exist");
484

            
485
4
                        match file_item {
486
2
                            file::Item::Unlocked(unlocked) => {
487
10
                                let index = backend
488
4
                                    .lookup_item_index(&unlocked.attributes())
489
6
                                    .await
490
2
                                    .map_err(crate::Error::File)?;
491

            
492
2
                                unlocked.set_attributes(attributes);
493

            
494
4
                                if let Some(index) = index {
495
10
                                    backend
496
2
                                        .replace_item_index(index, unlocked)
497
8
                                        .await
498
2
                                        .map_err(crate::Error::File)?;
499
                                } else {
500
10
                                    backend
501
                                        .create_item(
502
2
                                            unlocked.label(),
503
2
                                            attributes,
504
2
                                            unlocked.secret(),
505
                                            true,
506
                                        )
507
8
                                        .await
508
4
                                        .map_err(crate::Error::File)?;
509
                                }
510
                            }
511
                            file::Item::Locked(_) => {
512
2
                                return Err(crate::file::Error::Locked.into());
513
                            }
514
                        }
515
                    }
516
                    Some(file::Keyring::Locked(_)) => {
517
2
                        return Err(crate::file::Error::Locked.into());
518
                    }
519
                    None => unreachable!("A keyring must exist"),
520
                }
521
            }
522
12
            Self::DBus(item) => item.set_attributes(attributes).await?,
523
        };
524
2
        Ok(())
525
    }
526

            
527
    /// Sets a new secret.
528
12
    pub async fn set_secret(&self, secret: impl Into<Secret>) -> Result<()> {
529
2
        match self {
530
2
            Self::File(item, keyring) => {
531
4
                let mut item_guard = item.write().await;
532
4
                let file_item = item_guard.as_mut().expect("Item must exist");
533

            
534
2
                match file_item {
535
2
                    file::Item::Unlocked(unlocked) => {
536
2
                        unlocked.set_secret(secret);
537

            
538
2
                        let kg = keyring.read().await;
539
4
                        match kg.as_ref() {
540
2
                            Some(file::Keyring::Unlocked(backend)) => {
541
8
                                backend
542
                                    .create_item(
543
2
                                        unlocked.label(),
544
2
                                        &unlocked.attributes(),
545
2
                                        unlocked.secret(),
546
                                        true,
547
                                    )
548
8
                                    .await
549
4
                                    .map_err(crate::Error::File)?;
550
                            }
551
                            Some(file::Keyring::Locked(_)) => {
552
2
                                return Err(crate::file::Error::Locked.into());
553
                            }
554
                            None => unreachable!("A keyring must exist"),
555
                        }
556
                    }
557
                    file::Item::Locked(_) => {
558
2
                        return Err(crate::file::Error::Locked.into());
559
                    }
560
                }
561
            }
562
8
            Self::DBus(item) => item.set_secret(secret).await?,
563
        };
564
2
        Ok(())
565
    }
566

            
567
    /// Retrieves the stored secret.
568
10
    pub async fn secret(&self) -> Result<Secret> {
569
2
        let secret = match self {
570
2
            Self::File(item, _) => {
571
4
                let item_guard = item.read().await;
572
4
                let file_item = item_guard.as_ref().expect("Item must exist");
573
2
                match file_item {
574
2
                    file::Item::Unlocked(unlocked) => unlocked.secret(),
575
2
                    file::Item::Locked(_) => return Err(crate::file::Error::Locked.into()),
576
                }
577
            }
578
6
            Self::DBus(item) => item.secret().await?,
579
        };
580
2
        Ok(secret)
581
    }
582

            
583
    /// Whether the item is locked or not
584
8
    pub async fn is_locked(&self) -> Result<bool> {
585
2
        match self {
586
6
            Self::DBus(item) => item.is_locked().await.map_err(From::from),
587
2
            Self::File(item, _) => {
588
4
                let item_guard = item.read().await;
589
4
                let file_item = item_guard.as_ref().expect("Item must exist");
590
2
                Ok(file_item.is_locked())
591
            }
592
        }
593
    }
594

            
595
    /// Lock the item
596
8
    pub async fn lock(&self) -> Result<()> {
597
2
        match self {
598
8
            Self::DBus(item) => item.lock(None).await?,
599
2
            Self::File(item, keyring) => {
600
4
                let mut item_guard = item.write().await;
601
4
                let item_value = item_guard.take();
602
6
                if let Some(file::Item::Unlocked(unlocked)) = item_value {
603
4
                    let kg = keyring.read().await;
604
4
                    match kg.as_ref() {
605
2
                        Some(file::Keyring::Unlocked(backend)) => {
606
8
                            let locked = backend
607
2
                                .lock_item(unlocked)
608
6
                                .await
609
2
                                .map_err(crate::Error::File)?;
610
2
                            *item_guard = Some(file::Item::Locked(locked));
611
                        }
612
                        Some(file::Keyring::Locked(_)) => {
613
2
                            *item_guard = Some(file::Item::Unlocked(unlocked));
614
2
                            return Err(crate::file::Error::Locked.into());
615
                        }
616
                        None => unreachable!("A keyring must exist"),
617
                    }
618
                } else {
619
2
                    *item_guard = item_value;
620
                }
621
            }
622
        }
623
2
        Ok(())
624
    }
625

            
626
    /// Unlock the item
627
8
    pub async fn unlock(&self) -> Result<()> {
628
2
        match self {
629
6
            Self::DBus(item) => item.unlock(None).await?,
630
2
            Self::File(item, keyring) => {
631
4
                let mut item_guard = item.write().await;
632
4
                let item_value = item_guard.take();
633
6
                if let Some(file::Item::Locked(locked)) = item_value {
634
4
                    let kg = keyring.read().await;
635
4
                    match kg.as_ref() {
636
2
                        Some(file::Keyring::Unlocked(backend)) => {
637
8
                            let unlocked = backend
638
2
                                .unlock_item(locked)
639
6
                                .await
640
2
                                .map_err(crate::Error::File)?;
641
2
                            *item_guard = Some(file::Item::Unlocked(unlocked));
642
                        }
643
                        Some(file::Keyring::Locked(_)) => {
644
                            *item_guard = Some(file::Item::Locked(locked));
645
                            return Err(crate::file::Error::Locked.into());
646
                        }
647
                        None => unreachable!("A keyring must exist"),
648
                    }
649
                } else {
650
2
                    *item_guard = item_value;
651
                }
652
            }
653
        }
654
2
        Ok(())
655
    }
656

            
657
    /// Delete the item.
658
12
    pub async fn delete(&self) -> Result<()> {
659
2
        match self {
660
2
            Self::File(item, keyring) => {
661
4
                let item_guard = item.read().await;
662
4
                let file_item = item_guard.as_ref().expect("Item must exist");
663

            
664
2
                match file_item {
665
2
                    file::Item::Unlocked(unlocked) => {
666
4
                        let kg = keyring.read().await;
667
4
                        match kg.as_ref() {
668
2
                            Some(file::Keyring::Unlocked(backend)) => {
669
8
                                backend
670
4
                                    .delete(&unlocked.attributes())
671
8
                                    .await
672
2
                                    .map_err(crate::Error::File)?;
673
                            }
674
                            Some(file::Keyring::Locked(_)) => {
675
2
                                return Err(crate::file::Error::Locked.into());
676
                            }
677
                            None => unreachable!("A keyring must exist"),
678
                        }
679
                    }
680
                    file::Item::Locked(_) => {
681
                        return Err(crate::file::Error::Locked.into());
682
                    }
683
                }
684
            }
685
2
            Self::DBus(item) => {
686
6
                item.delete(None).await?;
687
            }
688
        };
689
2
        Ok(())
690
    }
691

            
692
    /// The UNIX time when the item was created.
693
8
    pub async fn created(&self) -> Result<Duration> {
694
2
        match self {
695
8
            Self::DBus(item) => Ok(item.created().await?),
696
2
            Self::File(item, _) => {
697
4
                let item_guard = item.read().await;
698
4
                let file_item = item_guard.as_ref().expect("Item must exist");
699
2
                match file_item {
700
4
                    file::Item::Unlocked(unlocked) => Ok(unlocked.created()),
701
2
                    file::Item::Locked(_) => Err(crate::file::Error::Locked.into()),
702
                }
703
            }
704
        }
705
    }
706

            
707
    /// The UNIX time when the item was modified.
708
8
    pub async fn modified(&self) -> Result<Duration> {
709
2
        match self {
710
8
            Self::DBus(item) => Ok(item.modified().await?),
711
2
            Self::File(item, _) => {
712
4
                let item_guard = item.read().await;
713
4
                let file_item = item_guard.as_ref().expect("Item must exist");
714
2
                match file_item {
715
4
                    file::Item::Unlocked(unlocked) => Ok(unlocked.modified()),
716
2
                    file::Item::Locked(_) => Err(crate::file::Error::Locked.into()),
717
                }
718
            }
719
        }
720
    }
721
}