|
1 | 1 | //! Objects related to [`FilesystemStoreV2`] live here. |
2 | | -use crate::fs_store::common::{get_key_from_dir_entry_path, FilesystemStoreState}; |
| 2 | +use crate::fs_store::common::{ |
| 3 | + dir_entry_is_key, get_key_from_dir_entry_path, FilesystemStoreState, |
| 4 | +}; |
3 | 5 |
|
4 | 6 | use lightning::util::persist::{ |
5 | 7 | KVStoreSync, MigratableKVStore, PageToken, PaginatedKVStoreSync, PaginatedListResponse, |
@@ -108,6 +110,16 @@ impl FilesystemStoreState { |
108 | 110 | for dir_entry in fs::read_dir(&prefixed_dest)? { |
109 | 111 | let dir_entry = dir_entry?; |
110 | 112 |
|
| 113 | + match dir_entry_is_key(&dir_entry) { |
| 114 | + // Entry is not a key (e.g., .tmp file, directory), skip it. |
| 115 | + Ok(false) => continue, |
| 116 | + // Entry is a valid key file, proceed to collect it. |
| 117 | + Ok(true) => {}, |
| 118 | + // Entry may have been deleted between read_dir and our check. Include |
| 119 | + // it anyway to give a more consistent view, matching list's behavior. |
| 120 | + Err(_) => {}, |
| 121 | + } |
| 122 | + |
111 | 123 | let key = |
112 | 124 | get_key_from_dir_entry_path(&dir_entry.path(), prefixed_dest.as_path(), false)?; |
113 | 125 | // Get modification time as millis since epoch |
@@ -616,6 +628,33 @@ mod tests { |
616 | 628 | assert_eq!(response.keys[3], "apple"); |
617 | 629 | } |
618 | 630 |
|
| 631 | + #[test] |
| 632 | + fn test_paginated_listing_skips_tmp_files() { |
| 633 | + use lightning::util::persist::{KVStoreSync, PaginatedKVStoreSync}; |
| 634 | + |
| 635 | + let mut temp_path = std::env::temp_dir(); |
| 636 | + temp_path.push("test_paginated_listing_skips_tmp_files_v2"); |
| 637 | + let fs_store = FilesystemStoreV2::new(temp_path.clone()).unwrap(); |
| 638 | + |
| 639 | + let data = vec![42u8; 32]; |
| 640 | + |
| 641 | + // Write some real keys |
| 642 | + KVStoreSync::write(&fs_store, "ns", "sub", "key0", data.clone()).unwrap(); |
| 643 | + std::thread::sleep(std::time::Duration::from_millis(10)); |
| 644 | + KVStoreSync::write(&fs_store, "ns", "sub", "key1", data.clone()).unwrap(); |
| 645 | + |
| 646 | + // Create a .tmp file and a subdirectory directly on disk |
| 647 | + let dir = temp_path.join("ns").join("sub"); |
| 648 | + fs::write(dir.join("inflight.tmp"), &data).unwrap(); |
| 649 | + fs::create_dir_all(dir.join("stray_dir")).unwrap(); |
| 650 | + |
| 651 | + // Paginated listing should only return the two real keys |
| 652 | + let response = PaginatedKVStoreSync::list_paginated(&fs_store, "ns", "sub", None).unwrap(); |
| 653 | + assert_eq!(response.keys.len(), 2); |
| 654 | + assert!(response.keys.contains(&"key0".to_string())); |
| 655 | + assert!(response.keys.contains(&"key1".to_string())); |
| 656 | + } |
| 657 | + |
619 | 658 | #[test] |
620 | 659 | fn test_rejects_v1_data_directory() { |
621 | 660 | let mut temp_path = std::env::temp_dir(); |
|
0 commit comments