A custom OS for the xteink x4 ebook reader
at main 789 lines 26 kB view raw
1// sd card file operations 2// 3// all I/O through embedded-sdmmc AsyncVolumeManager; functions are 4// synchronous, wrapping async ops with poll_once (SPI bus is blocking 5// so every .await resolves immediately) 6// 7// returns the unified Error type (re-exported as StorageError for 8// backward compat); apps receive it through KernelHandle 9 10use core::ops::ControlFlow; 11 12use embedded_sdmmc::{Mode, RawFile}; 13 14use crate::drivers::sdcard::{SdStorage, SdStorageInner, poll_once}; 15use crate::error::{Error, ErrorKind}; 16 17pub const PLUMP_DIR: &str = "_PLUMP"; 18pub const TITLES_FILE: &str = "TITLES.BIN"; 19pub const TITLE_CAP: usize = 64; 20 21// backward-compatible alias 22pub type StorageError = Error; 23 24#[derive(Clone, Copy)] 25pub struct DirEntry { 26 pub name: [u8; 13], 27 pub name_len: u8, 28 pub is_dir: bool, 29 pub size: u32, 30 pub title: [u8; TITLE_CAP], 31 pub title_len: u8, 32} 33 34impl DirEntry { 35 pub const EMPTY: Self = Self { 36 name: [0u8; 13], 37 name_len: 0, 38 is_dir: false, 39 size: 0, 40 title: [0u8; TITLE_CAP], 41 title_len: 0, 42 }; 43 44 pub fn name_str(&self) -> &str { 45 core::str::from_utf8(&self.name[..self.name_len as usize]).unwrap_or("?") 46 } 47 48 pub fn display_name(&self) -> &str { 49 let len = (self.title_len & 0x7F) as usize; 50 if len > 0 { 51 core::str::from_utf8(&self.title[..len]).unwrap_or(self.name_str()) 52 } else { 53 self.name_str() 54 } 55 } 56 57 pub fn has_real_title(&self) -> bool { 58 self.title_len > 0 && self.title_len & 0x80 == 0 59 } 60 61 pub fn set_title(&mut self, s: &[u8]) { 62 let n = s.len().min(TITLE_CAP); 63 self.title[..n].copy_from_slice(&s[..n]); 64 self.title_len = n as u8; 65 } 66 67 // write a humanized SFN into the title buffer as a soft fallback; 68 // does not prevent the title scanner from resolving a real title 69 pub fn humanize_sfn(&mut self) { 70 let nlen = self.name_len as usize; 71 if nlen == 0 || self.has_real_title() { 72 return; 73 } 74 let src = &self.name[..nlen]; 75 // check if name is all-uppercase (typical 8.3 SFN) 76 let all_upper = src.iter().all(|&b| !b.is_ascii_lowercase()); 77 if !all_upper { 78 return; // mixed case: user-supplied LFN, leave as-is 79 } 80 let n = nlen.min(TITLE_CAP); 81 let dot_pos = src.iter().position(|&b| b == b'.').unwrap_or(n); 82 for i in 0..n { 83 if i == 0 { 84 self.title[i] = src[i]; // keep first char uppercase 85 } else if i > dot_pos { 86 self.title[i] = src[i].to_ascii_lowercase(); // lowercase ext 87 } else { 88 self.title[i] = src[i].to_ascii_lowercase(); 89 } 90 } 91 self.title_len = 0x80 | n as u8; 92 } 93} 94 95pub struct DirPage { 96 pub total: usize, 97 pub count: usize, 98} 99 100fn ext_eq(name: &[u8], target: &[u8]) -> bool { 101 let dot = match name.iter().rposition(|&b| b == b'.') { 102 Some(p) => p, 103 None => return false, 104 }; 105 let ext = &name[dot + 1..]; 106 ext.len() == target.len() && ext.eq_ignore_ascii_case(target) 107} 108 109fn has_supported_ext(name: &[u8]) -> bool { 110 ext_eq(name, b"TXT") || ext_eq(name, b"EPUB") || ext_eq(name, b"EPU") || ext_eq(name, b"MD") 111} 112 113// build "NAME.EXT" bytes from a ShortFileName 114 115fn sfn_to_bytes(name: &embedded_sdmmc::ShortFileName, out: &mut [u8; 13]) -> u8 { 116 let base = name.base_name(); 117 let ext = name.extension(); 118 let mut pos = 0usize; 119 let blen = base.len().min(8); 120 out[..blen].copy_from_slice(&base[..blen]); 121 pos += blen; 122 if !ext.is_empty() { 123 out[pos] = b'.'; 124 pos += 1; 125 let elen = ext.len().min(3); 126 out[pos..pos + elen].copy_from_slice(&ext[..elen]); 127 pos += elen; 128 } 129 pos as u8 130} 131 132// file-operation macros; each evaluates to Result<T, Error> 133// none use ? internally so caller cleanup is never bypassed 134 135macro_rules! op_file_size { 136 ($inner:expr, $dir:expr, $name:expr) => { 137 $inner 138 .mgr 139 .find_directory_entry($dir, $name) 140 .await 141 .map(|e| e.size) 142 .map_err(|_| Error::new(ErrorKind::OpenFile, "file_size")) 143 }; 144} 145 146macro_rules! op_read_chunk { 147 ($inner:expr, $dir:expr, $name:expr, $offset:expr, $buf:expr) => { 148 match $inner 149 .mgr 150 .open_file_in_dir($dir, $name, Mode::ReadOnly) 151 .await 152 { 153 Err(_) => Err(Error::new(ErrorKind::OpenFile, "read_chunk")), 154 Ok(file) => { 155 let result = match $inner.mgr.file_seek_from_start(file, $offset) { 156 Ok(()) => $inner 157 .mgr 158 .read(file, $buf) 159 .await 160 .map_err(|_| Error::new(ErrorKind::ReadFailed, "read_chunk")), 161 Err(_) => Err(Error::new(ErrorKind::SeekFailed, "read_chunk")), 162 }; 163 let _ = $inner.mgr.close_file(file).await; 164 if let Ok(n) = &result { 165 $crate::perf::counters::inc_sd_reads(); 166 $crate::perf::counters::add_sd_bytes_read(*n as u32); 167 } 168 result 169 } 170 } 171 }; 172} 173 174macro_rules! op_read_start { 175 ($inner:expr, $dir:expr, $name:expr, $buf:expr) => { 176 match $inner 177 .mgr 178 .open_file_in_dir($dir, $name, Mode::ReadOnly) 179 .await 180 { 181 Err(_) => Err(Error::new(ErrorKind::OpenFile, "read_start")), 182 Ok(file) => { 183 let size = $inner.mgr.file_length(file).unwrap_or(0); 184 let result = $inner 185 .mgr 186 .read(file, $buf) 187 .await 188 .map_err(|_| Error::new(ErrorKind::ReadFailed, "read_start")); 189 let _ = $inner.mgr.close_file(file).await; 190 let mapped = result.map(|n| { 191 $crate::perf::counters::inc_sd_reads(); 192 $crate::perf::counters::add_sd_bytes_read(n as u32); 193 (size, n) 194 }); 195 mapped 196 } 197 } 198 }; 199} 200 201macro_rules! op_write { 202 ($inner:expr, $dir:expr, $name:expr, $data:expr) => { 203 match $inner 204 .mgr 205 .open_file_in_dir($dir, $name, Mode::ReadWriteCreateOrTruncate) 206 .await 207 { 208 Err(_) => Err(Error::new(ErrorKind::OpenFile, "write")), 209 Ok(file) => { 210 let data_ref = $data; 211 let result = if data_ref.is_empty() { 212 Ok(()) 213 } else { 214 $inner 215 .mgr 216 .write(file, data_ref) 217 .await 218 .map_err(|_| Error::new(ErrorKind::WriteFailed, "write")) 219 }; 220 let _ = $inner.mgr.close_file(file).await; 221 if result.is_ok() { 222 $crate::perf::counters::inc_sd_writes(); 223 $crate::perf::counters::add_sd_bytes_written(data_ref.len() as u32); 224 } 225 result 226 } 227 } 228 }; 229} 230 231macro_rules! op_append { 232 ($inner:expr, $dir:expr, $name:expr, $data:expr) => { 233 match $inner 234 .mgr 235 .open_file_in_dir($dir, $name, Mode::ReadWriteCreateOrAppend) 236 .await 237 { 238 Err(_) => Err(Error::new(ErrorKind::OpenFile, "append")), 239 Ok(file) => { 240 let data_ref = $data; 241 let result = if data_ref.is_empty() { 242 Ok(()) 243 } else { 244 $inner 245 .mgr 246 .write(file, data_ref) 247 .await 248 .map_err(|_| Error::new(ErrorKind::WriteFailed, "append")) 249 }; 250 let _ = $inner.mgr.close_file(file).await; 251 if result.is_ok() { 252 $crate::perf::counters::inc_sd_writes(); 253 $crate::perf::counters::add_sd_bytes_written(data_ref.len() as u32); 254 } 255 result 256 } 257 } 258 }; 259} 260 261macro_rules! op_delete { 262 ($inner:expr, $dir:expr, $name:expr) => {{ 263 $inner 264 .mgr 265 .delete_entry_in_dir($dir, $name) 266 .await 267 .map_err(|_| Error::new(ErrorKind::DeleteFailed, "delete")) 268 }}; 269} 270 271// dir-scoping macros; open subdir, execute body, close handle 272 273macro_rules! in_dir { 274 ($inner:expr, $dirname:expr, |$dir:ident| $body:expr) => { 275 match $inner.mgr.open_dir($inner.root, $dirname).await { 276 Err(_) => Err(Error::new(ErrorKind::OpenDir, "in_dir")), 277 Ok($dir) => { 278 let _r = $body; 279 let _ = $inner.mgr.close_dir($dir); 280 _r 281 } 282 } 283 }; 284} 285 286macro_rules! in_subdir { 287 ($inner:expr, $d1:expr, $d2:expr, |$dir:ident| $body:expr) => { 288 match $inner.mgr.open_dir($inner.root, $d1).await { 289 Err(_) => Err(Error::new(ErrorKind::OpenDir, "in_subdir")), 290 Ok(_mid) => match $inner.mgr.open_dir(_mid, $d2).await { 291 Err(_) => { 292 let _ = $inner.mgr.close_dir(_mid); 293 Err(Error::new(ErrorKind::OpenDir, "in_subdir")) 294 } 295 Ok($dir) => { 296 let _r = $body; 297 let _ = $inner.mgr.close_dir($dir); 298 let _ = $inner.mgr.close_dir(_mid); 299 _r 300 } 301 }, 302 } 303 }; 304} 305 306fn borrow(sd: &SdStorage) -> core::result::Result<core::cell::RefMut<'_, SdStorageInner>, Error> { 307 sd.borrow_inner() 308 .ok_or(Error::new(ErrorKind::NoCard, "storage::borrow")) 309} 310 311// streaming file handle — keeps one file open across multiple writes 312// 313// must be closed via close(); dropping without closing leaks the 314// handle in the volume manager (it will refuse to open the file again). 315// debug builds panic on leak; release builds log an error. 316 317/// Handle to an open file on the SD card. 318/// 319/// Created via [`SdStorage::create_file`]. Must be consumed via 320/// [`close()`](OpenFile::close) — dropping without closing leaks the 321/// handle inside the volume manager. 322pub struct OpenFile { 323 raw: Option<RawFile>, 324} 325 326impl OpenFile { 327 /// Write a chunk of data to the open file. 328 pub fn write(&self, sd: &SdStorage, data: &[u8]) -> crate::error::Result<()> { 329 if data.is_empty() { 330 return Ok(()); 331 } 332 let raw = self.raw.expect("OpenFile::write after close"); 333 poll_once(async { 334 let mut guard = borrow(sd)?; 335 guard 336 .mgr 337 .write(raw, data) 338 .await 339 .map_err(|_| Error::new(ErrorKind::WriteFailed, "OpenFile::write"))?; 340 crate::perf::counters::inc_sd_writes(); 341 crate::perf::counters::add_sd_bytes_written(data.len() as u32); 342 Ok(()) 343 }) 344 } 345 346 /// Close the file, flushing metadata to SD. Consumes self. 347 pub fn close(mut self, sd: &SdStorage) -> crate::error::Result<()> { 348 let raw = self.raw.take().expect("OpenFile::close called twice"); 349 poll_once(async { 350 let mut guard = borrow(sd)?; 351 guard 352 .mgr 353 .close_file(raw) 354 .await 355 .map_err(|_| Error::new(ErrorKind::WriteFailed, "OpenFile::close")) 356 }) 357 } 358} 359 360impl Drop for OpenFile { 361 fn drop(&mut self) { 362 if self.raw.is_some() { 363 // file handle leaked — volume manager still thinks it is open 364 log::error!("OpenFile dropped without close()! Handle leaked."); 365 debug_assert!(false, "OpenFile dropped without close()"); 366 } 367 } 368} 369 370impl SdStorage { 371 /// Create (or truncate) a file in the root directory and return 372 /// an [`OpenFile`] handle for streaming writes. 373 pub fn create_file(&self, name: &str) -> crate::error::Result<OpenFile> { 374 poll_once(async { 375 let mut guard = borrow(self)?; 376 let inner = &mut *guard; 377 let raw = inner 378 .mgr 379 .open_file_in_dir(inner.root, name, Mode::ReadWriteCreateOrTruncate) 380 .await 381 .map_err(|_| Error::new(ErrorKind::OpenFile, "SdStorage::create_file"))?; 382 Ok(OpenFile { raw: Some(raw) }) 383 }) 384 } 385 386 /// Write an entire file atomically (create/truncate + write + close). 387 pub fn write_file(&self, name: &str, data: &[u8]) -> crate::error::Result<()> { 388 poll_once(async { 389 let mut guard = borrow(self)?; 390 let inner = &mut *guard; 391 op_write!(inner, inner.root, name, data) 392 }) 393 } 394 395 /// Append data to an existing file (open + seek-to-end + write + close). 396 pub fn append_root_file(&self, name: &str, data: &[u8]) -> crate::error::Result<()> { 397 poll_once(async { 398 let mut guard = borrow(self)?; 399 let inner = &mut *guard; 400 op_append!(inner, inner.root, name, data) 401 }) 402 } 403 404 /// Delete a file from the root directory. 405 pub fn delete_file(&self, name: &str) -> crate::error::Result<()> { 406 poll_once(async { 407 let mut guard = borrow(self)?; 408 let inner = &mut *guard; 409 op_delete!(inner, inner.root, name) 410 }) 411 } 412 413 /// List supported files in the root directory. 414 pub fn list_root_files(&self, buf: &mut [DirEntry]) -> crate::error::Result<usize> { 415 poll_once(async { 416 let mut guard = borrow(self)?; 417 let inner = &mut *guard; 418 419 let mut count = 0usize; 420 let mut total = 0usize; 421 422 inner 423 .mgr 424 .iterate_dir(inner.root, |entry| { 425 if entry.attributes.is_volume() || entry.attributes.is_directory() { 426 return ControlFlow::Continue(()); 427 } 428 429 let mut name_buf = [0u8; 13]; 430 let name_len = sfn_to_bytes(&entry.name, &mut name_buf); 431 let sfn = &name_buf[..name_len as usize]; 432 433 if sfn.is_empty() || sfn[0] == b'.' || sfn[0] == b'_' { 434 return ControlFlow::Continue(()); 435 } 436 if !has_supported_ext(sfn) { 437 return ControlFlow::Continue(()); 438 } 439 440 total += 1; 441 442 if count < buf.len() { 443 buf[count] = DirEntry { 444 name: name_buf, 445 name_len, 446 is_dir: false, 447 size: entry.size, 448 title: [0u8; TITLE_CAP], 449 title_len: 0, 450 }; 451 count += 1; 452 } 453 ControlFlow::Continue(()) 454 }) 455 .await 456 .map_err(|_| Error::new(ErrorKind::ReadFailed, "list_root_files"))?; 457 458 if total > count { 459 log::warn!( 460 "dir: {} supported files on SD, only {} fit in buffer (max {})", 461 total, 462 count, 463 buf.len(), 464 ); 465 } 466 Ok(count) 467 }) 468 } 469 470 // root file reads 471 472 /// Get the size of a file in the root directory. 473 pub fn file_size(&self, name: &str) -> crate::error::Result<u32> { 474 poll_once(async { 475 let mut guard = borrow(self)?; 476 let inner = &mut *guard; 477 op_file_size!(inner, inner.root, name) 478 }) 479 } 480 481 /// Read a chunk from a file in the root directory at the given offset. 482 pub fn read_file_chunk( 483 &self, 484 name: &str, 485 offset: u32, 486 buf: &mut [u8], 487 ) -> crate::error::Result<usize> { 488 poll_once(async { 489 let mut guard = borrow(self)?; 490 let inner = &mut *guard; 491 op_read_chunk!(inner, inner.root, name, offset, buf) 492 }) 493 } 494 495 /// Read from the start of a file in the root directory. 496 /// Returns (file_size, bytes_read). 497 pub fn read_file_start( 498 &self, 499 name: &str, 500 buf: &mut [u8], 501 ) -> crate::error::Result<(u32, usize)> { 502 poll_once(async { 503 let mut guard = borrow(self)?; 504 let inner = &mut *guard; 505 op_read_start!(inner, inner.root, name, buf) 506 }) 507 } 508 509 // named-directory file operations 510 511 /// Write a file in a named subdirectory of root. 512 pub fn write_file_in_dir( 513 &self, 514 dir: &str, 515 name: &str, 516 data: &[u8], 517 ) -> crate::error::Result<()> { 518 poll_once(async { 519 let mut guard = borrow(self)?; 520 let inner = &mut *guard; 521 in_dir!(inner, dir, |dir_h| op_write!(inner, dir_h, name, data)) 522 }) 523 } 524 525 /// Read from the start of a file in a named subdirectory of root. 526 /// Returns (file_size, bytes_read). 527 pub fn read_file_start_in_dir( 528 &self, 529 dir: &str, 530 name: &str, 531 buf: &mut [u8], 532 ) -> crate::error::Result<(u32, usize)> { 533 poll_once(async { 534 let mut guard = borrow(self)?; 535 let inner = &mut *guard; 536 in_dir!(inner, dir, |dir_h| op_read_start!(inner, dir_h, name, buf)) 537 }) 538 } 539 540 // _PLUMP/ directory management 541 542 /// Ensure the _PLUMP directory exists (async, for boot path). 543 pub async fn ensure_plump_dir_async(&self) -> crate::error::Result<()> { 544 let mut guard = borrow(self)?; 545 let inner = &mut *guard; 546 547 if let Ok(dir) = inner.mgr.open_dir(inner.root, PLUMP_DIR).await { 548 let _ = inner.mgr.close_dir(dir); 549 return Ok(()); 550 } 551 match inner.mgr.make_dir_in_dir(inner.root, PLUMP_DIR).await { 552 Ok(()) => Ok(()), 553 Err(embedded_sdmmc::Error::DirAlreadyExists) => Ok(()), 554 Err(_) => Err(Error::new(ErrorKind::WriteFailed, "ensure_plump_dir_async")), 555 } 556 } 557 558 /// Ensure a subdirectory exists under _PLUMP/. 559 pub fn ensure_plump_subdir(&self, name: &str) -> crate::error::Result<()> { 560 let exists = poll_once(async { 561 let mut guard = borrow(self)?; 562 let inner = &mut *guard; 563 in_dir!(inner, PLUMP_DIR, |plump_h| { 564 match inner.mgr.open_dir(plump_h, name).await { 565 Ok(sub) => { 566 let _ = inner.mgr.close_dir(sub); 567 Ok::<_, Error>(true) 568 } 569 Err(_) => Ok(false), 570 } 571 }) 572 })?; 573 574 if exists { 575 return Ok(()); 576 } 577 578 poll_once(async { 579 let mut guard = borrow(self)?; 580 let inner = &mut *guard; 581 in_dir!(inner, PLUMP_DIR, |plump_h| { 582 match inner.mgr.make_dir_in_dir(plump_h, name).await { 583 Ok(()) => Ok::<_, Error>(()), 584 Err(embedded_sdmmc::Error::DirAlreadyExists) => Ok(()), 585 Err(_) => Err(Error::new(ErrorKind::WriteFailed, "ensure_plump_subdir")), 586 } 587 }) 588 }) 589 } 590 591 // _PLUMP/ direct file operations (cache files live directly in _PLUMP/) 592 593 /// Read a chunk from a file in _PLUMP/. 594 pub fn read_chunk_in_plump( 595 &self, 596 name: &str, 597 offset: u32, 598 buf: &mut [u8], 599 ) -> crate::error::Result<usize> { 600 poll_once(async { 601 let mut guard = borrow(self)?; 602 let inner = &mut *guard; 603 in_dir!(inner, PLUMP_DIR, |dir_h| op_read_chunk!( 604 inner, dir_h, name, offset, buf 605 )) 606 }) 607 } 608 609 /// Write (create/truncate) a file in _PLUMP/. 610 pub fn write_in_plump(&self, name: &str, data: &[u8]) -> crate::error::Result<()> { 611 poll_once(async { 612 let mut guard = borrow(self)?; 613 let inner = &mut *guard; 614 in_dir!(inner, PLUMP_DIR, |dir_h| op_write!(inner, dir_h, name, data)) 615 }) 616 } 617 618 /// Append data to a file in _PLUMP/. 619 pub fn append_in_plump(&self, name: &str, data: &[u8]) -> crate::error::Result<()> { 620 poll_once(async { 621 let mut guard = borrow(self)?; 622 let inner = &mut *guard; 623 in_dir!(inner, PLUMP_DIR, |dir_h| op_append!( 624 inner, dir_h, name, data 625 )) 626 }) 627 } 628 629 /// Get the size of a file in _PLUMP/. 630 pub fn file_size_in_plump(&self, name: &str) -> crate::error::Result<u32> { 631 poll_once(async { 632 let mut guard = borrow(self)?; 633 let inner = &mut *guard; 634 in_dir!(inner, PLUMP_DIR, |dir_h| op_file_size!(inner, dir_h, name)) 635 }) 636 } 637 638 /// Delete a file in _PLUMP/. 639 pub fn delete_in_plump(&self, name: &str) -> crate::error::Result<()> { 640 poll_once(async { 641 let mut guard = borrow(self)?; 642 let inner = &mut *guard; 643 in_dir!(inner, PLUMP_DIR, |dir_h| op_delete!(inner, dir_h, name)) 644 }) 645 } 646 647 /// Seek to offset and write data in a file in _PLUMP/. 648 /// Used to update the chapter offset table after all chapters are appended. 649 pub fn write_at_in_plump( 650 &self, 651 name: &str, 652 offset: u32, 653 data: &[u8], 654 ) -> crate::error::Result<()> { 655 poll_once(async { 656 let mut guard = borrow(self)?; 657 let inner = &mut *guard; 658 in_dir!(inner, PLUMP_DIR, |dir_h| { 659 match inner 660 .mgr 661 .open_file_in_dir(dir_h, name, Mode::ReadWriteCreateOrAppend) 662 .await 663 { 664 Err(_) => Err(Error::new(ErrorKind::OpenFile, "write_at")), 665 Ok(file) => { 666 let result = match inner.mgr.file_seek_from_start(file, offset) { 667 Ok(()) => inner 668 .mgr 669 .write(file, data) 670 .await 671 .map_err(|_| Error::new(ErrorKind::WriteFailed, "write_at")), 672 Err(_) => Err(Error::new(ErrorKind::SeekFailed, "write_at")), 673 }; 674 let _ = inner.mgr.close_file(file).await; 675 if result.is_ok() { 676 crate::perf::counters::inc_sd_writes(); 677 crate::perf::counters::add_sd_bytes_written(data.len() as u32); 678 } 679 result 680 } 681 } 682 }) 683 }) 684 } 685 686 // _PLUMP subdirectory file operations 687 688 /// Write (create/truncate) a file in _PLUMP/<dir>/. 689 pub fn write_in_plump_subdir( 690 &self, 691 dir: &str, 692 name: &str, 693 data: &[u8], 694 ) -> crate::error::Result<()> { 695 poll_once(async { 696 let mut guard = borrow(self)?; 697 let inner = &mut *guard; 698 in_subdir!(inner, PLUMP_DIR, dir, |sub_h| op_write!( 699 inner, sub_h, name, data 700 )) 701 }) 702 } 703 704 /// Append data to a file in _PLUMP/<dir>/. 705 pub fn append_in_plump_subdir( 706 &self, 707 dir: &str, 708 name: &str, 709 data: &[u8], 710 ) -> crate::error::Result<()> { 711 poll_once(async { 712 let mut guard = borrow(self)?; 713 let inner = &mut *guard; 714 in_subdir!(inner, PLUMP_DIR, dir, |sub_h| op_append!( 715 inner, sub_h, name, data 716 )) 717 }) 718 } 719 720 /// Read a chunk from a file in _PLUMP/<dir>/. 721 pub fn read_chunk_in_plump_subdir( 722 &self, 723 dir: &str, 724 name: &str, 725 offset: u32, 726 buf: &mut [u8], 727 ) -> crate::error::Result<usize> { 728 poll_once(async { 729 let mut guard = borrow(self)?; 730 let inner = &mut *guard; 731 in_subdir!(inner, PLUMP_DIR, dir, |sub_h| op_read_chunk!( 732 inner, sub_h, name, offset, buf 733 )) 734 }) 735 } 736 737 /// Get the size of a file in _PLUMP/<dir>/. 738 pub fn file_size_in_plump_subdir( 739 &self, 740 dir: &str, 741 name: &str, 742 ) -> crate::error::Result<u32> { 743 poll_once(async { 744 let mut guard = borrow(self)?; 745 let inner = &mut *guard; 746 in_subdir!(inner, PLUMP_DIR, dir, |sub_h| op_file_size!( 747 inner, sub_h, name 748 )) 749 }) 750 } 751 752 /// Delete a file in _PLUMP/<dir>/. 753 pub fn delete_in_plump_subdir( 754 &self, 755 dir: &str, 756 name: &str, 757 ) -> crate::error::Result<()> { 758 poll_once(async { 759 let mut guard = borrow(self)?; 760 let inner = &mut *guard; 761 in_subdir!(inner, PLUMP_DIR, dir, |sub_h| op_delete!(inner, sub_h, name)) 762 }) 763 } 764 765 // title mapping 766 767 /// Append a title line to _PLUMP/TITLES.BIN. 768 pub fn save_title(&self, filename: &str, title: &str) -> crate::error::Result<()> { 769 let name_bytes = filename.as_bytes(); 770 let title_bytes = title.as_bytes(); 771 let title_len = title_bytes.len().min(TITLE_CAP); 772 let line_len = name_bytes.len() + 1 + title_len + 1; 773 if line_len > 128 { 774 return Err(Error::new( 775 ErrorKind::WriteFailed, 776 "save_title: line too long", 777 )); 778 } 779 let mut line = [0u8; 128]; 780 line[..name_bytes.len()].copy_from_slice(name_bytes); 781 line[name_bytes.len()] = b'\t'; 782 line[name_bytes.len() + 1..name_bytes.len() + 1 + title_len] 783 .copy_from_slice(&title_bytes[..title_len]); 784 line[name_bytes.len() + 1 + title_len] = b'\n'; 785 786 self.append_in_plump(TITLES_FILE, &line[..line_len]) 787 } 788} 789