mirror of
https://github.com/GraphiteEditor/Graphite.git
synced 2026-09-21 23:48:12 +08:00
Add document-container crate (#4191)
* Add document-container crate: container backends and archive codecs * Address PR review: path/prefix split, safe size casts, OPFS stream aborts * Address PR review round 2: mmap read check, UTF8 entry names, prefix normalization * Make MmappedBytes::new fallible so mmap reads can't silently degrade * Address PR review round 3: backend contract uniformity (symlinks, remove, list) * Apply symlink-component check to FolderBackend listing paths * Address PR review: idempotent OPFS delete logging, tar default-features, shared entry-size cap * Fix validate_path doc: dotfiles pass, CurDir/ParentDir rejected * Omit symlink entries from FolderBackend listings for consistency with resolve * Preserve zip I/O errors and reject non-canonical paths in validate_path * Extend archive apis to return the archive writer * Rename document/document-container directory to document/container * Add archive format sniffing and deserialize_auto * Drop temporal hedge from checked_entry_size comment * Tighten verbose doc comments in document-container * Coalesce consecutive same-path OPFS appends to avoid O(n^2) file copies * Review * Update document-container for the deserialize/store_non_blocking renames --------- Co-authored-by: Timon <me@timon.zip>
This commit is contained in:
committed by
Keavon Chambers
co-authored by
Timon
parent
08c6d02e5b
commit
6fe1af3afe
@@ -0,0 +1,89 @@
|
||||
//! In-memory backend. Useful for tests and as the deserialize target for archive codecs.
|
||||
|
||||
use crate::{ByteHolder, Container, ContainerError, Result, validate_path, validate_prefix, with_trailing_slash};
|
||||
use std::collections::{HashMap, HashSet};
|
||||
use std::sync::Mutex;
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct MemoryBackend {
|
||||
files: Mutex<HashMap<String, Vec<u8>>>,
|
||||
}
|
||||
|
||||
impl MemoryBackend {
|
||||
pub fn new() -> Self {
|
||||
Self::default()
|
||||
}
|
||||
}
|
||||
|
||||
impl Container for MemoryBackend {
|
||||
fn read(&self, path: &str) -> Result<ByteHolder> {
|
||||
validate_path(path)?;
|
||||
self.files
|
||||
.lock()
|
||||
.unwrap()
|
||||
.get(path)
|
||||
.map(|bytes| ByteHolder::Owned(bytes.clone()))
|
||||
.ok_or_else(|| ContainerError::NotFound(path.to_string()))
|
||||
}
|
||||
|
||||
fn write(&self, path: &str, bytes: &[u8]) -> Result<()> {
|
||||
validate_path(path)?;
|
||||
self.files.lock().unwrap().insert(path.to_string(), bytes.to_vec());
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn append(&self, path: &str, bytes: &[u8]) -> Result<()> {
|
||||
validate_path(path)?;
|
||||
self.files.lock().unwrap().entry(path.to_string()).or_default().extend_from_slice(bytes);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn list(&self, prefix: &str) -> Result<Vec<String>> {
|
||||
validate_prefix(prefix)?;
|
||||
let files = self.files.lock().unwrap();
|
||||
// A prefix that is itself a stored key names a file, not a directory.
|
||||
if files.contains_key(prefix) {
|
||||
return Err(ContainerError::NotADirectory(prefix.to_string()));
|
||||
}
|
||||
|
||||
let normalized = with_trailing_slash(prefix);
|
||||
let results = files.keys().filter(|path| path.starts_with(&normalized) && !path[normalized.len()..].contains('/')).cloned().collect();
|
||||
Ok(results)
|
||||
}
|
||||
|
||||
fn list_dirs(&self, prefix: &str) -> Result<Vec<String>> {
|
||||
validate_prefix(prefix)?;
|
||||
let files = self.files.lock().unwrap();
|
||||
if files.contains_key(prefix) {
|
||||
return Err(ContainerError::NotADirectory(prefix.to_string()));
|
||||
}
|
||||
|
||||
let normalized = with_trailing_slash(prefix);
|
||||
let mut seen = HashSet::new();
|
||||
let mut results = Vec::new();
|
||||
for path in files.keys() {
|
||||
if !path.starts_with(&normalized) {
|
||||
continue;
|
||||
}
|
||||
let remainder = &path[normalized.len()..];
|
||||
if let Some((segment, _)) = remainder.split_once('/') {
|
||||
let dir = format!("{normalized}{segment}");
|
||||
if seen.insert(dir.clone()) {
|
||||
results.push(dir);
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(results)
|
||||
}
|
||||
|
||||
fn exists(&self, path: &str) -> bool {
|
||||
validate_path(path).is_ok() && self.files.lock().unwrap().contains_key(path)
|
||||
}
|
||||
|
||||
fn remove(&self, path: &str) -> Result<()> {
|
||||
validate_path(path)?;
|
||||
// Idempotent: removing a missing path is not an error.
|
||||
self.files.lock().unwrap().remove(path);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user