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(err))
40
                    if matches!(*err, file::Error::Portal(ashpd::Error::PortalNotFound(_))) =>
41
                {
42
                    #[cfg(feature = "tracing")]
43
                    tracing::debug!(
44
                        "org.freedesktop.portal.Secrets is not available, falling back to the Secret Service backend"
45
                    );
46
                    Self::host().await
47
                }
48
                Err(e) => Err(e),
49
            }
50
        } else {
51
            Self::host().await
52
        }
53
    }
54

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

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

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

            
78
10
        let file = file::UnlockedKeyring::load(path, Some(secret.clone())).await?;
79
7
        Ok(Self::File(
80
12
            Arc::new(RwLock::new(Some(file::Keyring::Unlocked(file)))),
81
5
            secret,
82
        ))
83
    }
84

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

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

            
119
        let secret = Secret::sandboxed().await?;
120
        let path = crate::file::api::Keyring::default_path()?;
121
        Self::sandboxed_with_path_unchecked(&path, secret).await
122
    }
123

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

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

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

            
154
        let service = dbus::Service::new().await?;
155
        let collection = service.default_collection().await?;
156
        Ok(Self::DBus(collection))
157
    }
158

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

            
164
20
        let service = dbus::Service::new_with_connection(&connection).await?;
165
12
        let collection = service.default_collection().await?;
166
7
        Ok(Self::DBus(collection))
167
    }
168

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

            
180
12
                    let unlocked = locked
181
4
                        .unlock(secret.clone())
182
6
                        .await
183
2
                        .map_err(|e| crate::Error::File(Box::new(e)))?;
184
2
                    *kg = Some(file::Keyring::Unlocked(unlocked));
185
                } else {
186
2
                    *kg = kg_value;
187
                }
188
            }
189
        };
190
2
        Ok(())
191
    }
192

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

            
204
2
                    let locked = unlocked.lock();
205
2
                    *kg = Some(file::Keyring::Locked(locked));
206
                } else {
207
2
                    *kg = kg_value;
208
                }
209
            }
210
        };
211
2
        Ok(())
212
    }
213

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

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

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

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

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

            
350
/// A generic secret with a label and attributes.
351
#[derive(Debug)]
352
pub enum Item {
353
    #[doc(hidden)]
354
    File(
355
        RwLock<Option<file::Item>>,
356
        Arc<RwLock<Option<file::Keyring>>>,
357
    ),
358
    #[doc(hidden)]
359
    DBus(dbus::Item),
360
}
361

            
362
impl Item {
363
2
    fn for_file(item: file::Item, backend: Arc<RwLock<Option<file::Keyring>>>) -> Self {
364
4
        Self::File(RwLock::new(Some(item)), backend)
365
    }
366

            
367
2
    fn for_dbus(item: dbus::Item) -> Self {
368
2
        Self::DBus(item)
369
    }
370

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

            
387
    /// Sets the item label.
388
12
    pub async fn set_label(&self, label: &str) -> Result<()> {
389
2
        match self {
390
2
            Self::File(item, keyring) => {
391
4
                let mut item_guard = item.write().await;
392
4
                let file_item = item_guard.as_mut().expect("Item must exist");
393

            
394
2
                match file_item {
395
2
                    file::Item::Unlocked(unlocked) => {
396
2
                        unlocked.set_label(label);
397

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

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

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

            
481
    /// Sets the item attributes.
482
24
    pub async fn set_attributes(&self, attributes: &impl AsAttributes) -> Result<()> {
483
4
        match self {
484
4
            Self::File(item, keyring) => {
485
8
                let kg = keyring.read().await;
486

            
487
8
                match kg.as_ref() {
488
4
                    Some(file::Keyring::Unlocked(backend)) => {
489
8
                        let mut item_guard = item.write().await;
490
8
                        let file_item = item_guard.as_mut().expect("Item must exist");
491

            
492
4
                        match file_item {
493
2
                            file::Item::Unlocked(unlocked) => {
494
10
                                let index = backend
495
4
                                    .lookup_item_index(&unlocked.attributes())
496
6
                                    .await
497
2
                                    .map_err(|e| crate::Error::File(Box::new(e)))?;
498

            
499
2
                                unlocked.set_attributes(attributes);
500

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

            
534
    /// Sets a new secret.
535
12
    pub async fn set_secret(&self, secret: impl Into<Secret>) -> Result<()> {
536
2
        match self {
537
2
            Self::File(item, keyring) => {
538
4
                let mut item_guard = item.write().await;
539
4
                let file_item = item_guard.as_mut().expect("Item must exist");
540

            
541
2
                match file_item {
542
2
                    file::Item::Unlocked(unlocked) => {
543
2
                        unlocked.set_secret(secret);
544

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

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

            
590
    /// Whether the item is locked or not
591
9
    pub async fn is_locked(&self) -> Result<bool> {
592
2
        match self {
593
6
            Self::DBus(item) => item.is_locked().await.map_err(From::from),
594
2
            Self::File(item, _) => {
595
4
                let item_guard = item.read().await;
596
4
                let file_item = item_guard.as_ref().expect("Item must exist");
597
3
                Ok(file_item.is_locked())
598
            }
599
        }
600
    }
601

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

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

            
664
    /// Delete the item.
665
12
    pub async fn delete(&self) -> Result<()> {
666
2
        match self {
667
2
            Self::File(item, keyring) => {
668
4
                let item_guard = item.read().await;
669
4
                let file_item = item_guard.as_ref().expect("Item must exist");
670

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

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

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