an efficient binary archive format
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

add remove

zach 470bea64 d30ce2cd

+134 -1
+7
include/bindle.h
··· 99 99 bool bindle_exists(const struct Bindle *ctx, const char *name); 100 100 101 101 /** 102 + * Remove an entry from the index. 103 + * The data remains in the file until bindle_vacuum is called. 104 + * Returns true if the entry existed and was removed, false otherwise. 105 + */ 106 + bool bindle_remove(struct Bindle *ctx, const char *name); 107 + 108 + /** 102 109 * Create a new Writer, while the stream is active (until bindle_stream_finish is called), the 103 110 * Bindle struct should not be accessed. 104 111 */
+56 -1
src/bin/bindle.rs
··· 37 37 /// Use zstd compression 38 38 #[arg(short, long)] 39 39 compress: bool, 40 + /// Run vacuum after adding 41 + #[arg(long)] 42 + vacuum: bool, 40 43 }, 41 44 42 45 /// Extract an entry's data to stdout ··· 46 49 bindle_file: PathBuf, 47 50 /// Name of the entry to extract 48 51 name: String, 52 + }, 53 + 54 + /// Remove an entry from the archive 55 + Remove { 56 + /// Bindle archive file 57 + #[arg(value_name = "BINDLE_FILE")] 58 + bindle_file: PathBuf, 59 + /// Name of the entry to remove 60 + name: String, 61 + /// Run vacuum after removing 62 + #[arg(long)] 63 + vacuum: bool, 49 64 }, 50 65 51 66 /// Pack an entire directory into the archive ··· 62 77 /// Append to existing file 63 78 #[arg(short, long)] 64 79 append: bool, 80 + /// Run vacuum after packing 81 + #[arg(long)] 82 + vacuum: bool, 65 83 }, 66 84 67 85 /// Unpack the archive to a local directory ··· 131 149 file_path, 132 150 compress, 133 151 bindle_file, 152 + vacuum, 134 153 } => { 135 154 let mut b = init(bindle_file.clone()); 136 155 let data = std::fs::read(&file_path)?; ··· 151 170 data.len() 152 171 ); 153 172 b.save()?; 173 + 174 + if vacuum { 175 + println!("VACUUM {}", bindle_file.display()); 176 + b.vacuum()?; 177 + } 154 178 155 179 println!("OK"); 156 180 } ··· 170 194 } 171 195 } 172 196 197 + Commands::Remove { 198 + name, 199 + bindle_file, 200 + vacuum, 201 + } => { 202 + let mut b = init(bindle_file.clone()); 203 + if b.remove(&name) { 204 + println!("REMOVE '{}' from {}", name, bindle_file.display()); 205 + b.save()?; 206 + 207 + if vacuum { 208 + println!("VACUUM {}", bindle_file.display()); 209 + b.vacuum()?; 210 + } 211 + 212 + println!("OK"); 213 + } else { 214 + return Err(io::Error::new( 215 + io::ErrorKind::NotFound, 216 + format!("ERROR '{}' not found in {}", name, bindle_file.display()), 217 + )); 218 + } 219 + } 220 + 173 221 Commands::Pack { 174 222 bindle_file, 175 223 src_dir, 176 224 compress, 177 225 append, 226 + vacuum, 178 227 } => { 179 228 println!("PACK {} -> {}", src_dir.display(), bindle_file.display()); 180 - let mut b = init(bindle_file); 229 + let mut b = init(bindle_file.clone()); 181 230 if !append { 182 231 b.clear(); 183 232 } ··· 190 239 }, 191 240 )?; 192 241 b.save()?; 242 + 243 + if vacuum { 244 + println!("VACUUM {}", bindle_file.display()); 245 + b.vacuum()?; 246 + } 247 + 193 248 println!("OK"); 194 249 } 195 250
+20
src/ffi.rs
··· 305 305 b.exists(name_str) 306 306 } 307 307 308 + /// Remove an entry from the index. 309 + /// The data remains in the file until bindle_vacuum is called. 310 + /// Returns true if the entry existed and was removed, false otherwise. 311 + #[unsafe(no_mangle)] 312 + pub unsafe extern "C" fn bindle_remove(ctx: *mut Bindle, name: *const c_char) -> bool { 313 + if ctx.is_null() || name.is_null() { 314 + return false; 315 + } 316 + 317 + let b = unsafe { &mut *ctx }; 318 + let name_str = unsafe { 319 + match CStr::from_ptr(name).to_str() { 320 + Ok(s) => s, 321 + Err(_) => return false, 322 + } 323 + }; 324 + 325 + b.remove(name_str) 326 + } 327 + 308 328 /// Create a new Writer, while the stream is active (until bindle_stream_finish is called), the 309 329 /// Bindle struct should not be accessed. 310 330 #[unsafe(no_mangle)]
+51
src/lib.rs
··· 405 405 magic: FOOTER_MAGIC, 406 406 }; 407 407 self.file.write_all(footer.as_bytes())?; 408 + 409 + // Truncate file to current position to remove any old data 410 + let current_pos = self.file.stream_position()?; 411 + self.file.set_len(current_pos)?; 408 412 self.file.flush()?; 413 + 409 414 self.mmap = Some(unsafe { Mmap::map(&self.file)? }); 410 415 self.file.lock_shared()?; 411 416 Ok(()) ··· 582 587 /// Checks if an entry exists in the archive index. 583 588 pub fn exists(&self, name: &str) -> bool { 584 589 self.index.contains_key(name) 590 + } 591 + 592 + /// Remove an entry from the index. 593 + /// The data remains in the file until vacuum() is called. 594 + /// Returns true if the entry existed and was removed. 595 + pub fn remove(&mut self, name: &str) -> bool { 596 + self.index.remove(name).is_some() 585 597 } 586 598 587 599 /// Recursively packs a directory into the archive. ··· 968 980 } 969 981 970 982 let _ = std::fs::remove_file(path); 983 + } 984 + 985 + #[test] 986 + fn test_remove_entry() { 987 + let path = "test_remove.bindl"; 988 + let _ = fs::remove_file(path); 989 + 990 + let mut b = Bindle::open(path).expect("Failed to open"); 991 + 992 + // Add some entries 993 + b.add("file1.txt", b"Content 1", Compress::None).unwrap(); 994 + b.add("file2.txt", b"Content 2", Compress::None).unwrap(); 995 + b.add("file3.txt", b"Content 3", Compress::None).unwrap(); 996 + b.save().unwrap(); 997 + 998 + assert_eq!(b.len(), 3); 999 + assert!(b.exists("file2.txt")); 1000 + 1001 + // Remove an entry 1002 + assert!(b.remove("file2.txt")); 1003 + assert_eq!(b.len(), 2); 1004 + assert!(!b.exists("file2.txt")); 1005 + 1006 + // Try to remove non-existent entry 1007 + assert!(!b.remove("nonexistent.txt")); 1008 + 1009 + // Save and reload to verify persistence 1010 + b.save().unwrap(); 1011 + let b2 = Bindle::open(path).unwrap(); 1012 + assert_eq!(b2.len(), 2); 1013 + assert!(b2.exists("file1.txt")); 1014 + assert!(!b2.exists("file2.txt")); 1015 + assert!(b2.exists("file3.txt")); 1016 + 1017 + // Verify data still readable for remaining entries 1018 + assert_eq!(b2.read("file1.txt").unwrap().as_ref(), b"Content 1"); 1019 + assert_eq!(b2.read("file3.txt").unwrap().as_ref(), b"Content 3"); 1020 + 1021 + fs::remove_file(path).ok(); 971 1022 } 972 1023 }