mirror of
https://github.com/GraphiteEditor/Graphite.git
synced 2026-09-17 07:18:04 +08:00
* 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>
90 lines
2.5 KiB
Rust
90 lines
2.5 KiB
Rust
//! 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(())
|
|
}
|
|
}
|