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
9
                self.service.clone(),
60
4
                crate::prompt::PromptRole::Unlock,
61
10
                self.label().await,
62
10
                Some(self.clone()),
63
            )
64
15
            .await;
65
9
            let prompt_path = OwnedObjectPath::from(prompt.path().clone());
66

            
67
4
            let collection = self.clone();
68
5
            let caller_owned = caller;
69
21
            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
9
                    Ok(zvariant::Value::new(OwnedObjectPath::default())
77
4
                        .try_into_owned()
78
4
                        .unwrap())
79
                });
80

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

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

            
87
20
            self.service
88
                .object_server()
89
5
                .at(&prompt_path, prompt)
90
15
                .await?;
91

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

            
98
5
            return Ok(prompt_path);
99
        }
100

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

            
105
36
    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
17
            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
20
        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
13
            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
8
        let signal_emitter = self.service.signal_emitter(service_path)?;
135
16
        Service::collection_deleted(&signal_emitter, &self.path).await?;
136

            
137
        // Remove collection from object server
138
8
        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
9
        tracing::info!("Collection `{}` deleted by {caller}.", self.path);
144

            
145
9
        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
40
        let results = self
154
10
            .search_inner_items(&attributes)
155
30
            .await?
156
            .iter()
157
30
            .map(|item| item.path().clone().into())
158
            .collect::<Vec<OwnedObjectPath>>();
159

            
160
10
        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
20
            tracing::debug!(
168
                "Items with attributes {:?} found in collection: {}.",
169
                attributes,
170
                self.path
171
            );
172
        }
173

            
174
10
        Ok(results)
175
    }
176

            
177
    #[zbus(out_args("item", "prompt"))]
178
16
    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
30
        let caller = if let Some(sender) = header.sender() {
186
            self.service.peer_display_name(sender).await
187
        } else {
188
32
            "unknown".to_string()
189
        };
190
32
        tracing::debug!("CreateItem called by {caller} with session {}", secret.0);
191
32
        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
24
            let action =
204
                crate::prompt::PromptAction::new(move |unlock_secret: Option<Secret>| async move {
205
10
                    collection.set_locked(false, unlock_secret).await?;
206

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

            
211
11
                    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
69
        let item_path = self
235
15
            .create_item_unlocked(properties, secret, replace)
236
53
            .await?;
237

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

            
241
16
    async fn create_item_unlocked(
242
        &self,
243
        properties: Properties,
244
        secret: DBusSecretInner,
245
        replace: bool,
246
    ) -> Result<OwnedObjectPath, ServiceError> {
247
31
        let keyring_guard = self.keyring.read().await;
248
31
        let keyring = match keyring_guard.as_ref() {
249
31
            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
14
        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
29
        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
29
        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
10
            None => zeroize::Zeroizing::new(secret_bytes.clone()),
273
        };
274

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

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

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

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

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

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

            
308
        // Remove any existing items with the same attributes
309
16
        if replace {
310
59
            let existing_items = self
311
26
                .search_items_with_key(&attributes, key.as_deref())
312
33
                .await?;
313
33
            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
32
        self.items.lock().await.push(item.clone());
329

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

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

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

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

            
345
15
        Ok(item_path)
346
    }
347

            
348
    #[zbus(property, name = "Items")]
349
82
    pub async fn items(&self) -> Vec<OwnedObjectPath> {
350
96
        self.items
351
            .lock()
352
52
            .await
353
            .iter()
354
53
            .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
88
    pub async fn is_locked(&self) -> bool {
403
89
        self.keyring
404
            .read()
405
65
            .await
406
            .as_ref()
407
64
            .map(|k| k.is_locked())
408
            .unwrap_or(true)
409
    }
410

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

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

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

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

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

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

            
445
30
    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
61
        let modified = keyring.modified_time().await;
459
38
        let created = keyring.created_time().await.unwrap_or(modified);
460

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

            
476
29
    pub fn path(&self) -> &ObjectPath<'_> {
477
27
        &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
82
    pub async fn alias(&self) -> String {
489
41
        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
24
        let Some(keyring) = keyring_guard.as_ref() else {
498
            return Ok(Vec::new());
499
        };
500
23
        if keyring.is_locked() {
501
            return Ok(Vec::new());
502
        }
503
40
        let key = keyring
504
            .as_unlocked()
505
            .key()
506
36
            .await
507
10
            .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
16
    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
16
        let mut matching_items = Vec::new();
519
28
        let items = self.items.lock().await;
520

            
521
56
        for item_wrapper in items.iter() {
522
33
            let inner = item_wrapper.inner.lock().await;
523
24
            let file_item = inner.as_ref().unwrap();
524

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

            
531
12
        Ok(matching_items)
532
    }
533

            
534
49
    pub async fn item_from_path(&self, path: &ObjectPath<'_>) -> Option<item::Item> {
535
24
        let items = self.items.lock().await;
536

            
537
48
        items.iter().find(|i| i.path() == path).cloned()
538
    }
539

            
540
8
    pub async fn set_locked(
541
        &self,
542
        locked: bool,
543
        secret: Option<Secret>,
544
    ) -> Result<(), ServiceError> {
545
18
        let mut keyring_guard = self.keyring.write().await;
546

            
547
24
        if let Some(old_keyring) = keyring_guard.take() {
548
16
            let new_keyring = match (old_keyring, locked) {
549
8
                (Keyring::Unlocked(unlocked), true) => {
550
16
                    let items = self.items.lock().await;
551
22
                    for item in items.iter() {
552
18
                        item.set_locked(locked, &unlocked).await?;
553
                    }
554
8
                    drop(items);
555

            
556
8
                    Keyring::Locked(unlocked.lock())
557
                }
558
9
                (Keyring::Locked(locked_kr), false) => {
559
30
                    let keyring_path = locked_kr.path().map(|p| p.to_path_buf());
560

            
561
9
                    let unlock_result = if let Some(secret) = secret {
562
18
                        locked_kr.unlock(secret).await
563
                    } else {
564
8
                        locked_kr.unlock_unencrypted().await
565
                    };
566

            
567
8
                    let unlocked = match unlock_result {
568
8
                        Ok(unlocked) => unlocked,
569
4
                        Err(err) => {
570
                            // Reload the locked keyring from disk before returning error
571
8
                            if let Some(path) = keyring_path
572
16
                                && let Ok(reloaded) = oo7::file::LockedKeyring::load(&path).await
573
                            {
574
4
                                *keyring_guard = Some(Keyring::Locked(reloaded));
575
                            }
576
12
                            return Err(custom_service_error(&format!(
577
                                "Failed to unlock keyring: {err}"
578
                            )));
579
                        }
580
                    };
581

            
582
16
                    let items = self.items.lock().await;
583
22
                    for item in items.iter() {
584
18
                        item.set_locked(locked, &unlocked).await?;
585
                    }
586
8
                    drop(items);
587

            
588
8
                    Keyring::Unlocked(unlocked)
589
                }
590
4
                (other, _) => other,
591
            };
592
8
            *keyring_guard = Some(new_keyring);
593
        }
594

            
595
8
        drop(keyring_guard);
596

            
597
        // Emit signals
598
8
        let signal_emitter = self.service.signal_emitter(&self.path)?;
599
16
        self.locked_changed(&signal_emitter).await?;
600

            
601
8
        let service_path = oo7::dbus::api::Service::PATH.as_ref().unwrap();
602
8
        let signal_emitter = self.service.signal_emitter(service_path)?;
603
16
        Service::collection_changed(&signal_emitter, &self.path).await?;
604

            
605
3
        tracing::debug!(
606
            "Collection: {} is {}.",
607
            self.path,
608
            if locked { "locked" } else { "unlocked" }
609
        );
610

            
611
8
        Ok(())
612
    }
613

            
614
139
    pub async fn dispatch_items(&self) -> Result<(), Error> {
615
52
        let keyring_guard = self.keyring.read().await;
616
53
        let keyring = keyring_guard.as_ref().unwrap();
617

            
618
85
        let keyring_items = keyring.items().await?;
619
28
        tracing::debug!(
620
            "Dispatching {} items for collection `{}`.",
621
            keyring_items.len(),
622
            self.path
623
        );
624
68
        let mut items = self.items.lock().await;
625
68
        let object_server = self.service.object_server();
626
32
        let mut n_items = 1;
627

            
628
72
        for keyring_item in keyring_items {
629
8
            let item_path = OwnedObjectPath::try_from(format!("{}/{n_items}", self.path)).unwrap();
630
            let item = item::Item::new(
631
4
                keyring_item,
632
8
                self.service.clone(),
633
8
                self.path.clone(),
634
4
                item_path.clone(),
635
            );
636
4
            n_items += 1;
637

            
638
8
            items.push(item.clone());
639
12
            object_server.at(item_path, item).await?;
640
        }
641

            
642
36
        *self.item_index.write().await = n_items;
643

            
644
32
        Ok(())
645
    }
646

            
647
50
    pub async fn delete_item(&self, path: &ObjectPath<'_>) -> Result<(), ServiceError> {
648
26
        tracing::debug!("Deleting item `{}` from collection `{}`.", path, self.path);
649
25
        let Some(item) = self.item_from_path(path).await else {
650
            return Err(ServiceError::NoSuchObject(format!(
651
                "Item `{path}` does not exist."
652
            )));
653
        };
654

            
655
24
        if item.is_locked().await {
656
            return Err(ServiceError::IsLocked(format!(
657
                "Cannot delete a locked item `{path}`"
658
            )));
659
        }
660

            
661
24
        let attributes = item.attributes().await.map_err(|err| {
662
            custom_service_error(&format!("Failed to read item attributes {err}"))
663
        })?;
664

            
665
24
        let keyring_guard = self.keyring.read().await;
666
24
        let keyring = match keyring_guard.as_ref() {
667
25
            Some(k) if !k.is_locked() => k.as_unlocked(),
668
            _ => {
669
                return Err(ServiceError::IsLocked(format!(
670
                    "Cannot delete an item `{path}` in a locked collection"
671
                )));
672
            }
673
        };
674

            
675
53
        keyring
676
13
            .delete(&attributes)
677
38
            .await
678
13
            .map_err(|err| custom_service_error(&format!("Failed to deleted item {err}.")))?;
679

            
680
27
        let mut items = self.items.lock().await;
681
54
        items.retain(|item| item.path() != path);
682
14
        drop(items);
683

            
684
14
        self.update_modified().await?;
685

            
686
14
        let signal_emitter = self.service.signal_emitter(&self.path)?;
687
29
        self.items_changed(&signal_emitter).await?;
688

            
689
15
        Ok(())
690
    }
691

            
692
    /// Update the modified timestamp and emit the PropertiesChanged signal
693
68
    async fn update_modified(&self) -> Result<(), ServiceError> {
694
55
        let now = SystemTime::now()
695
15
            .duration_since(SystemTime::UNIX_EPOCH)
696
            .unwrap();
697
17
        *self.modified.lock().await = now;
698

            
699
21
        let signal_emitter = self.service.signal_emitter(&self.path)?;
700
39
        self.modified_changed(&signal_emitter).await?;
701

            
702
16
        Ok(())
703
    }
704
}
705

            
706
#[cfg(test)]
707
mod tests;