1
// org.freedesktop.Secret.Collection
2

            
3
use std::{
4
    collections::HashMap,
5
    sync::Arc,
6
    time::{Duration, SystemTime},
7
};
8

            
9
use oo7::{
10
    Secret,
11
    dbus::{
12
        ServiceError,
13
        api::{DBusSecretInner, Properties},
14
    },
15
    file::Keyring,
16
};
17
use tokio::sync::{Mutex, RwLock};
18
use zbus::{interface, object_server::SignalEmitter, proxy::Defaults, zvariant};
19
use zvariant::{ObjectPath, OwnedObjectPath};
20

            
21
use crate::{
22
    Service,
23
    error::{Error, custom_service_error},
24
    item,
25
};
26

            
27
#[derive(Clone)]
28
pub struct Collection {
29
    // Properties
30
    items: Arc<Mutex<Vec<item::Item>>>,
31
    label: Arc<Mutex<String>>,
32
    created: Duration,
33
    modified: Arc<Mutex<Duration>>,
34
    // Other attributes
35
    name: String,
36
    alias: Arc<Mutex<String>>,
37
    pub(crate) keyring: Arc<RwLock<Option<Keyring>>>,
38
    service: Service,
39
    item_index: Arc<RwLock<u32>>,
40
    path: OwnedObjectPath,
41
}
42

            
43
#[interface(name = "org.freedesktop.Secret.Collection")]
44
impl Collection {
45
    #[zbus(out_args("prompt"))]
46
8
    pub async fn delete(
47
        &self,
48
        #[zbus(header)] header: zbus::message::Header<'_>,
49
    ) -> Result<OwnedObjectPath, ServiceError> {
50
16
        let caller = if let Some(sender) = header.sender() {
51
            self.service.peer_display_name(sender).await
52
        } else {
53
16
            "unknown".to_string()
54
        };
55
        // Check if collection is locked
56
16
        if self.is_locked().await {
57
            // Create a prompt to unlock and delete the collection
58
            let prompt = crate::prompt::Prompt::new(
59
8
                self.service.clone(),
60
4
                crate::prompt::PromptRole::Unlock,
61
8
                self.label().await,
62
8
                Some(self.clone()),
63
            )
64
12
            .await;
65
8
            let prompt_path = OwnedObjectPath::from(prompt.path().clone());
66

            
67
4
            let collection = self.clone();
68
4
            let caller_owned = caller;
69
20
            let action =
70
                crate::prompt::PromptAction::new(move |unlock_secret: Option<Secret>| async move {
71
                    // Unlock the collection
72
8
                    collection.set_locked(false, unlock_secret).await?;
73

            
74
4
                    collection.delete_unlocked(&caller_owned).await?;
75

            
76
8
                    Ok(zvariant::Value::new(OwnedObjectPath::default())
77
4
                        .try_into_owned()
78
4
                        .unwrap())
79
                });
80

            
81
4
            prompt.set_action(action).await;
82

            
83
12
            self.service
84
8
                .register_prompt(prompt_path.clone(), prompt.clone())
85
8
                .await;
86

            
87
16
            self.service
88
                .object_server()
89
4
                .at(&prompt_path, prompt)
90
12
                .await?;
91

            
92
4
            tracing::debug!(
93
                "Delete prompt created at `{}` for locked collection `{}`",
94
                prompt_path,
95
                self.path
96
            );
97

            
98
4
            return Ok(prompt_path);
99
        }
100

            
101
21
        self.delete_unlocked(&caller).await?;
102
8
        Ok(OwnedObjectPath::default())
103
    }
104

            
105
37
    async fn delete_unlocked(&self, caller: &str) -> Result<(), ServiceError> {
106
        let keyring_path = {
107
16
            let keyring_guard = self.keyring.read().await;
108
16
            let keyring = match keyring_guard.as_ref() {
109
16
                Some(k) if !k.is_locked() => k.as_unlocked(),
110
                _ => return Err(ServiceError::IsLocked("Collection is locked".to_owned())),
111
            };
112
16
            keyring.path().map(|p| p.to_path_buf())
113
        };
114

            
115
16
        let object_server = self.service.object_server();
116

            
117
        // Remove all items from the object server
118
17
        let items = self.items.lock().await;
119
22
        for item in items.iter() {
120
12
            object_server.remove::<item::Item, _>(item.path()).await?;
121
        }
122
9
        drop(items);
123

            
124
        // Delete the keyring file if it's persistent
125
9
        if let Some(path) = keyring_path {
126
12
            tokio::fs::remove_file(&path).await.map_err(|err| {
127
                custom_service_error(&format!("Failed to delete keyring file: {err}"))
128
            })?;
129
4
            tracing::debug!("Deleted keyring file: {}", path.display());
130
        }
131

            
132
        // Emit CollectionDeleted signal before removing from object server
133
17
        let service_path = oo7::dbus::api::Service::PATH.as_ref().unwrap();
134
9
        let signal_emitter = self.service.signal_emitter(service_path)?;
135
18
        Service::collection_deleted(&signal_emitter, &self.path).await?;
136

            
137
        // Remove collection from object server
138
9
        object_server.remove::<Collection, _>(&self.path).await?;
139

            
140
        // Notify service to remove from collections list
141
8
        self.service.remove_collection(&self.path).await;
142

            
143
8
        tracing::info!("Collection `{}` deleted by {caller}.", self.path);
144

            
145
8
        Ok(())
146
    }
147

            
148
    #[zbus(out_args("results"))]
149
10
    pub async fn search_items(
150
        &self,
151
        attributes: HashMap<String, String>,
152
    ) -> Result<Vec<OwnedObjectPath>, ServiceError> {
153
45
        let results = self
154
10
            .search_inner_items(&attributes)
155
34
            .await?
156
            .iter()
157
31
            .map(|item| item.path().clone().into())
158
            .collect::<Vec<OwnedObjectPath>>();
159

            
160
11
        if results.is_empty() {
161
16
            tracing::debug!(
162
                "Items with attributes {:?} does not exist in collection: {}.",
163
                attributes,
164
                self.path
165
            );
166
        } else {
167
23
            tracing::debug!(
168
                "Items with attributes {:?} found in collection: {}.",
169
                attributes,
170
                self.path
171
            );
172
        }
173

            
174
11
        Ok(results)
175
    }
176

            
177
    #[zbus(out_args("item", "prompt"))]
178
17
    pub async fn create_item(
179
        &self,
180
        properties: Properties,
181
        secret: DBusSecretInner,
182
        replace: bool,
183
        #[zbus(header)] header: zbus::message::Header<'_>,
184
    ) -> Result<(OwnedObjectPath, OwnedObjectPath), ServiceError> {
185
35
        let caller = if let Some(sender) = header.sender() {
186
            self.service.peer_display_name(sender).await
187
        } else {
188
36
            "unknown".to_string()
189
        };
190
35
        tracing::debug!("CreateItem called by {caller} with session {}", secret.0);
191
35
        if self.is_locked().await {
192
            // Create a prompt to unlock the collection and create the item
193
            let prompt = crate::prompt::Prompt::new(
194
8
                self.service.clone(),
195
4
                crate::prompt::PromptRole::Unlock,
196
8
                self.label().await,
197
8
                Some(self.clone()),
198
            )
199
12
            .await;
200
8
            let prompt_path = OwnedObjectPath::from(prompt.path().clone());
201

            
202
4
            let collection = self.clone();
203
20
            let action =
204
                crate::prompt::PromptAction::new(move |unlock_secret: Option<Secret>| async move {
205
8
                    collection.set_locked(false, unlock_secret).await?;
206

            
207
14
                    let item_path = collection
208
4
                        .create_item_unlocked(properties, secret, replace)
209
14
                        .await?;
210

            
211
8
                    Ok(zvariant::Value::new(item_path).try_into_owned().unwrap())
212
                });
213

            
214
4
            prompt.set_action(action).await;
215

            
216
12
            self.service
217
8
                .register_prompt(prompt_path.clone(), prompt.clone())
218
8
                .await;
219

            
220
16
            self.service
221
                .object_server()
222
4
                .at(&prompt_path, prompt)
223
12
                .await?;
224

            
225
4
            tracing::debug!(
226
                "CreateItem prompt created at `{}` for locked collection `{}`",
227
                prompt_path,
228
                self.path
229
            );
230

            
231
8
            return Ok((OwnedObjectPath::default(), prompt_path));
232
        }
233

            
234
80
        let item_path = self
235
14
            .create_item_unlocked(properties, secret, replace)
236
53
            .await?;
237

            
238
16
        Ok((item_path, OwnedObjectPath::default()))
239
    }
240

            
241
19
    async fn create_item_unlocked(
242
        &self,
243
        properties: Properties,
244
        secret: DBusSecretInner,
245
        replace: bool,
246
    ) -> Result<OwnedObjectPath, ServiceError> {
247
33
        let keyring_guard = self.keyring.read().await;
248
33
        let keyring = match keyring_guard.as_ref() {
249
33
            Some(k) if !k.is_locked() => k.as_unlocked(),
250
            _ => return Err(ServiceError::IsLocked("Collection is locked".to_owned())),
251
        };
252

            
253
15
        let DBusSecretInner(ref session_path, ref iv, ref secret_bytes, ref content_type) = secret;
254
18
        let label = properties.label();
255
        // Safe to unwrap as an item always has attributes
256
15
        let mut attributes = properties.attributes().unwrap().to_owned();
257

            
258
33
        let Some(session) = self.service.session(session_path).await else {
259
8
            tracing::error!(
260
                "The session `{}` does not exist, referenced while creating item in collection `{}`.",
261
                session_path,
262
                self.path
263
            );
264
8
            return Err(ServiceError::NoSession(format!(
265
                "The session `{session_path}` does not exist."
266
            )));
267
        };
268

            
269
34
        let secret = match session.aes_key() {
270
39
            Some(key) => oo7::crypto::decrypt(secret_bytes, &key, iv)
271
26
                .map_err(|err| custom_service_error(&format!("Failed to decrypt secret {err}.")))?,
272
12
            None => zeroize::Zeroizing::new(secret_bytes.clone()),
273
        };
274

            
275
        // Ensure content-type attribute is stored
276
30
        if !attributes.contains_key(oo7::CONTENT_TYPE_ATTRIBUTE) {
277
29
            attributes.insert(
278
30
                oo7::CONTENT_TYPE_ATTRIBUTE.to_owned(),
279
30
                content_type.as_str().to_owned(),
280
            );
281
        }
282

            
283
76
        let item = keyring
284
16
            .create_item(label, &attributes, secret, replace)
285
55
            .await
286
15
            .map_err(|err| custom_service_error(&format!("Failed to create a new item {err}.")))?;
287

            
288
73
        let key = keyring
289
            .key()
290
48
            .await
291
19
            .map_err(|err| custom_service_error(&format!("Failed to derive key: {err}")))?;
292
19
        drop(keyring_guard);
293

            
294
16
        let n_items = *self.item_index.read().await;
295
33
        let item_path = OwnedObjectPath::try_from(format!("{}/{n_items}", self.path)).unwrap();
296

            
297
        let item = item::Item::new(
298
34
            item.into(),
299
33
            self.service.clone(),
300
33
            self.path.clone(),
301
17
            item_path.clone(),
302
        );
303
36
        *self.item_index.write().await = n_items + 1;
304

            
305
19
        let object_server = self.service.object_server();
306
20
        let signal_emitter = self.service.signal_emitter(&self.path)?;
307

            
308
        // Remove any existing items with the same attributes
309
15
        if replace {
310
49
            let existing_items = self
311
25
                .search_items_exact_with_key(&attributes, key.as_deref())
312
39
                .await?;
313
32
            if !existing_items.is_empty() {
314
12
                let mut items = self.items.lock().await;
315
24
                for existing in &existing_items {
316
12
                    let existing_path = existing.path();
317

            
318
18
                    items.retain(|i| i.path() != existing_path);
319
12
                    object_server.remove::<item::Item, _>(existing_path).await?;
320
12
                    Self::item_deleted(&signal_emitter, existing_path).await?;
321

            
322
6
                    tracing::debug!("Replaced item `{}`", existing_path);
323
                }
324
6
                drop(items);
325
            }
326
        }
327

            
328
33
        self.items.lock().await.push(item.clone());
329

            
330
16
        object_server.at(&item_path, item).await?;
331

            
332
14
        self.update_modified().await?;
333

            
334
16
        Self::item_created(&signal_emitter, &item_path).await?;
335
16
        self.items_changed(&signal_emitter).await?;
336

            
337
17
        match session.peer_info() {
338
            Some(info) => tracing::info!(
339
                "Item `{item_path}` created by {} ({info}).",
340
                session.sender()
341
            ),
342
18
            None => tracing::info!("Item `{item_path}` created by {}.", session.sender()),
343
        }
344

            
345
17
        Ok(item_path)
346
    }
347

            
348
    #[zbus(property, name = "Items")]
349
75
    pub async fn items(&self) -> Vec<OwnedObjectPath> {
350
99
        self.items
351
            .lock()
352
56
            .await
353
            .iter()
354
54
            .map(|i| i.path().to_owned().into())
355
            .collect()
356
    }
357

            
358
    #[zbus(property, name = "Label")]
359
43
    pub async fn label(&self) -> String {
360
24
        self.label.lock().await.clone()
361
    }
362

            
363
    #[zbus(property, name = "Label")]
364
32
    pub async fn set_label(&self, label: &str) -> Result<(), zbus::Error> {
365
16
        if self.is_locked().await {
366
8
            tracing::error!("Cannot set label of a locked collection `{}`", self.path);
367
4
            return Err(zbus::Error::FDO(Box::new(zbus::fdo::Error::Failed(
368
8
                format!("Cannot set label of a locked collection `{}`.", self.path),
369
            ))));
370
        }
371

            
372
16
        let old_label = self.label.lock().await.clone();
373
8
        *self.label.lock().await = label.to_owned();
374
8
        tracing::info!(
375
            "Collection `{}` label changed from \"{}\" to \"{}\".",
376
            self.path,
377
            old_label,
378
            label
379
        );
380

            
381
40
        self.update_modified()
382
24
            .await
383
8
            .map_err(|err| zbus::Error::FDO(Box::new(zbus::fdo::Error::Failed(err.to_string()))))?;
384

            
385
8
        let service_path = oo7::dbus::api::Service::PATH.as_ref().unwrap();
386
16
        let signal_emitter = self
387
            .service
388
8
            .signal_emitter(service_path)
389
8
            .map_err(|err| zbus::Error::FDO(Box::new(zbus::fdo::Error::Failed(err.to_string()))))?;
390
16
        Service::collection_changed(&signal_emitter, &self.path).await?;
391

            
392
16
        let signal_emitter = self
393
            .service
394
8
            .signal_emitter(&self.path)
395
8
            .map_err(|err| zbus::Error::FDO(Box::new(zbus::fdo::Error::Failed(err.to_string()))))?;
396
16
        self.label_changed(&signal_emitter).await?;
397

            
398
8
        Ok(())
399
    }
400

            
401
    #[zbus(property, name = "Locked")]
402
95
    pub async fn is_locked(&self) -> bool {
403
108
        self.keyring
404
            .read()
405
76
            .await
406
            .as_ref()
407
65
            .map(|k| k.is_locked())
408
            .unwrap_or(true)
409
    }
410

            
411
    #[zbus(property, name = "Created")]
412
11
    pub fn created_at(&self) -> u64 {
413
11
        self.created.as_secs()
414
    }
415

            
416
    #[zbus(property, name = "Modified")]
417
73
    pub async fn modified_at(&self) -> u64 {
418
35
        self.modified.lock().await.as_secs()
419
    }
420

            
421
    #[zbus(signal, name = "ItemCreated")]
422
    async fn item_created(
423
16
        signal_emitter: &SignalEmitter<'_>,
424
17
        item: &ObjectPath<'_>,
425
    ) -> zbus::Result<()>;
426

            
427
    #[zbus(signal, name = "ItemDeleted")]
428
    pub async fn item_deleted(
429
15
        signal_emitter: &SignalEmitter<'_>,
430
16
        item: &ObjectPath<'_>,
431
    ) -> zbus::Result<()>;
432

            
433
    #[zbus(signal, name = "ItemChanged")]
434
    pub async fn item_changed(
435
10
        signal_emitter: &SignalEmitter<'_>,
436
11
        item: &ObjectPath<'_>,
437
    ) -> zbus::Result<()>;
438
}
439

            
440
28
pub(crate) fn collection_path(label: &str) -> Result<OwnedObjectPath, zvariant::Error> {
441
31
    let sanitized_label = label
442
        .to_lowercase()
443
59
        .replace(|c: char| !c.is_ascii_alphanumeric() && c != '_', "_");
444

            
445
28
    OwnedObjectPath::try_from(format!(
446
        "/org/freedesktop/secrets/collection/{sanitized_label}"
447
    ))
448
}
449

            
450
impl Collection {
451
29
    pub async fn new(
452
        name: &str,
453
        label: &str,
454
        alias: &str,
455
        service: Service,
456
        keyring: Keyring,
457
    ) -> Self {
458
57
        let modified = keyring.modified_time().await;
459
41
        let created = keyring.created_time().await.unwrap_or(modified);
460

            
461
        Self {
462
28
            items: Default::default(),
463
50
            label: Arc::new(Mutex::new(label.to_owned())),
464
52
            modified: Arc::new(Mutex::new(modified)),
465
23
            name: name.to_owned(),
466
50
            alias: Arc::new(Mutex::new(alias.to_owned())),
467
52
            item_index: Arc::new(RwLock::new(0)),
468
31
            path: collection_path(label)
469
                .expect("Label should already be sanitized and produce valid object path"),
470
            created,
471
            service,
472
52
            keyring: Arc::new(RwLock::new(Some(keyring))),
473
        }
474
    }
475

            
476
27
    pub fn path(&self) -> &ObjectPath<'_> {
477
31
        &self.path
478
    }
479

            
480
8
    pub fn name(&self) -> &str {
481
8
        &self.name
482
    }
483

            
484
16
    pub async fn set_alias(&self, alias: &str) {
485
4
        *self.alias.lock().await = alias.to_owned();
486
    }
487

            
488
93
    pub async fn alias(&self) -> String {
489
45
        self.alias.lock().await.clone()
490
    }
491

            
492
10
    pub async fn search_inner_items(
493
        &self,
494
        attributes: &HashMap<String, String>,
495
    ) -> Result<Vec<item::Item>, ServiceError> {
496
21
        let keyring_guard = self.keyring.read().await;
497
21
        let Some(keyring) = keyring_guard.as_ref() else {
498
            return Ok(Vec::new());
499
        };
500
20
        if keyring.is_locked() {
501
            return Ok(Vec::new());
502
        }
503
43
        let key = keyring
504
            .as_unlocked()
505
            .key()
506
36
            .await
507
11
            .map_err(|err| custom_service_error(&format!("Failed to derive key: {err}")))?;
508
10
        drop(keyring_guard);
509

            
510
10
        self.search_items_with_key(attributes, key.as_deref()).await
511
    }
512

            
513
11
    async fn search_items_with_key(
514
        &self,
515
        attributes: &HashMap<String, String>,
516
        key: Option<&oo7::Key>,
517
    ) -> Result<Vec<item::Item>, ServiceError> {
518
11
        let mut matching_items = Vec::new();
519
22
        let items = self.items.lock().await;
520

            
521
45
        for item_wrapper in items.iter() {
522
31
            let inner = item_wrapper.inner.lock().await;
523
22
            let file_item = inner.as_ref().unwrap();
524

            
525
            // Use the oo7::file::Item's matches_attributes method
526
11
            if file_item.matches_attributes(attributes, key) {
527
13
                matching_items.push(item_wrapper.clone());
528
            }
529
        }
530

            
531
12
        Ok(matching_items)
532
    }
533

            
534
12
    async fn search_items_exact_with_key(
535
        &self,
536
        attributes: &HashMap<String, String>,
537
        key: Option<&oo7::Key>,
538
    ) -> Result<Vec<item::Item>, ServiceError> {
539
12
        let mut matching_items = Vec::new();
540
25
        let items = self.items.lock().await;
541

            
542
50
        for item_wrapper in items.iter() {
543
24
            let inner = item_wrapper.inner.lock().await;
544
16
            let Some(file_item) = inner.as_ref() else {
545
                continue;
546
            };
547

            
548
16
            if file_item.matches_attributes_exact(attributes, key) {
549
6
                matching_items.push(item_wrapper.clone());
550
            }
551
        }
552

            
553
13
        Ok(matching_items)
554
    }
555

            
556
56
    pub async fn item_from_path(&self, path: &ObjectPath<'_>) -> Option<item::Item> {
557
26
        let items = self.items.lock().await;
558

            
559
52
        items.iter().find(|i| i.path() == path).cloned()
560
    }
561

            
562
8
    pub async fn set_locked(
563
        &self,
564
        locked: bool,
565
        secret: Option<Secret>,
566
    ) -> Result<(), ServiceError> {
567
18
        let mut keyring_guard = self.keyring.write().await;
568

            
569
24
        if let Some(old_keyring) = keyring_guard.take() {
570
16
            let new_keyring = match (old_keyring, locked) {
571
8
                (Keyring::Unlocked(unlocked), true) => {
572
16
                    let items = self.items.lock().await;
573
22
                    for item in items.iter() {
574
18
                        item.set_locked(locked, &unlocked).await?;
575
                    }
576
8
                    drop(items);
577

            
578
8
                    Keyring::Locked(unlocked.lock())
579
                }
580
8
                (Keyring::Locked(locked_kr), false) => {
581
28
                    let keyring_path = locked_kr.path().map(|p| p.to_path_buf());
582

            
583
8
                    let unlock_result = if let Some(secret) = secret {
584
16
                        locked_kr.unlock(secret).await
585
                    } else {
586
8
                        locked_kr.unlock_unencrypted().await
587
                    };
588

            
589
8
                    let unlocked = match unlock_result {
590
8
                        Ok(unlocked) => unlocked,
591
4
                        Err(err) => {
592
                            // Reload the locked keyring from disk before returning error
593
8
                            if let Some(path) = keyring_path
594
16
                                && let Ok(reloaded) = oo7::file::LockedKeyring::load(&path).await
595
                            {
596
4
                                *keyring_guard = Some(Keyring::Locked(reloaded));
597
                            }
598
12
                            return Err(custom_service_error(&format!(
599
                                "Failed to unlock keyring: {err}"
600
                            )));
601
                        }
602
                    };
603

            
604
16
                    let items = self.items.lock().await;
605
23
                    for item in items.iter() {
606
20
                        item.set_locked(locked, &unlocked).await?;
607
                    }
608
8
                    drop(items);
609

            
610
8
                    Keyring::Unlocked(unlocked)
611
                }
612
4
                (other, _) => other,
613
            };
614
8
            *keyring_guard = Some(new_keyring);
615
        }
616

            
617
8
        drop(keyring_guard);
618

            
619
        // Emit signals
620
8
        let signal_emitter = self.service.signal_emitter(&self.path)?;
621
16
        self.locked_changed(&signal_emitter).await?;
622

            
623
8
        let service_path = oo7::dbus::api::Service::PATH.as_ref().unwrap();
624
8
        let signal_emitter = self.service.signal_emitter(service_path)?;
625
16
        Service::collection_changed(&signal_emitter, &self.path).await?;
626

            
627
5
        tracing::debug!(
628
            "Collection: {} is {}.",
629
            self.path,
630
            if locked { "locked" } else { "unlocked" }
631
        );
632

            
633
8
        Ok(())
634
    }
635

            
636
152
    pub async fn dispatch_items(&self) -> Result<(), Error> {
637
59
        let keyring_guard = self.keyring.read().await;
638
56
        let keyring = keyring_guard.as_ref().unwrap();
639

            
640
87
        let keyring_items = keyring.items().await?;
641
46
        tracing::debug!(
642
            "Dispatching {} items for collection `{}`.",
643
            keyring_items.len(),
644
            self.path
645
        );
646
72
        let mut items = self.items.lock().await;
647
72
        let object_server = self.service.object_server();
648
36
        let mut n_items = 1;
649

            
650
75
        for keyring_item in keyring_items {
651
8
            let item_path = OwnedObjectPath::try_from(format!("{}/{n_items}", self.path)).unwrap();
652
            let item = item::Item::new(
653
4
                keyring_item,
654
8
                self.service.clone(),
655
8
                self.path.clone(),
656
4
                item_path.clone(),
657
            );
658
4
            n_items += 1;
659

            
660
8
            items.push(item.clone());
661
12
            object_server.at(item_path, item).await?;
662
        }
663

            
664
37
        *self.item_index.write().await = n_items;
665

            
666
35
        Ok(())
667
    }
668

            
669
56
    pub async fn delete_item(&self, path: &ObjectPath<'_>) -> Result<(), ServiceError> {
670
28
        tracing::debug!("Deleting item `{}` from collection `{}`.", path, self.path);
671
28
        let Some(item) = self.item_from_path(path).await else {
672
            return Err(ServiceError::NoSuchObject(format!(
673
                "Item `{path}` does not exist."
674
            )));
675
        };
676

            
677
28
        if item.is_locked().await {
678
            return Err(ServiceError::IsLocked(format!(
679
                "Cannot delete a locked item `{path}`"
680
            )));
681
        }
682

            
683
28
        let attributes = item.attributes().await.map_err(|err| {
684
            custom_service_error(&format!("Failed to read item attributes {err}"))
685
        })?;
686

            
687
27
        let keyring_guard = self.keyring.read().await;
688
27
        let keyring = match keyring_guard.as_ref() {
689
28
            Some(k) if !k.is_locked() => k.as_unlocked(),
690
            _ => {
691
                return Err(ServiceError::IsLocked(format!(
692
                    "Cannot delete an item `{path}` in a locked collection"
693
                )));
694
            }
695
        };
696

            
697
57
        keyring
698
14
            .delete(&attributes)
699
41
            .await
700
15
            .map_err(|err| custom_service_error(&format!("Failed to deleted item {err}.")))?;
701

            
702
31
        let mut items = self.items.lock().await;
703
61
        items.retain(|item| item.path() != path);
704
15
        drop(items);
705

            
706
14
        self.update_modified().await?;
707

            
708
15
        let signal_emitter = self.service.signal_emitter(&self.path)?;
709
30
        self.items_changed(&signal_emitter).await?;
710

            
711
15
        Ok(())
712
    }
713

            
714
    /// Update the modified timestamp and emit the PropertiesChanged signal
715
65
    async fn update_modified(&self) -> Result<(), ServiceError> {
716
49
        let now = SystemTime::now()
717
17
            .duration_since(SystemTime::UNIX_EPOCH)
718
            .unwrap();
719
17
        *self.modified.lock().await = now;
720

            
721
16
        let signal_emitter = self.service.signal_emitter(&self.path)?;
722
32
        self.modified_changed(&signal_emitter).await?;
723

            
724
19
        Ok(())
725
    }
726
}
727

            
728
#[cfg(test)]
729
mod tests;