diff options
Diffstat (limited to 'plugins/warp/src/container')
| -rw-r--r-- | plugins/warp/src/container/disk.rs | 402 | ||||
| -rw-r--r-- | plugins/warp/src/container/memory.rs | 307 | ||||
| -rw-r--r-- | plugins/warp/src/container/network.rs | 13 |
3 files changed, 722 insertions, 0 deletions
diff --git a/plugins/warp/src/container/disk.rs b/plugins/warp/src/container/disk.rs new file mode 100644 index 00000000..ca685a5f --- /dev/null +++ b/plugins/warp/src/container/disk.rs @@ -0,0 +1,402 @@ +use crate::container::{Container, ContainerError, ContainerResult, SourceId, SourcePath}; +use std::collections::HashMap; +use std::fmt::{Debug, Display, Formatter}; +use std::hash::{Hash, Hasher}; +use std::path::PathBuf; +use uuid::{uuid, Uuid}; +use walkdir::{DirEntry, WalkDir}; +use warp::chunk::{Chunk, ChunkKind, CompressionType}; +use warp::r#type::chunk::TypeChunk; +use warp::r#type::guid::TypeGUID; +use warp::r#type::{ComputedType, Type}; +use warp::signature::chunk::SignatureChunk; +use warp::signature::function::{Function, FunctionGUID}; +use warp::target::Target; +use warp::{WarpFile, WarpFileHeader}; + +pub const NAMESPACE_DISK_SOURCE: Uuid = uuid!("ea89e8ab-a27a-432b-8fbd-77b026cd5f41"); + +// TODO: How to support remote projects? I.e. collaboration? +pub struct DiskContainer { + pub name: String, + pub sources: HashMap<SourceId, DiskContainerSource>, +} + +impl DiskContainer { + pub fn new(name: String, sources: HashMap<SourceId, DiskContainerSource>) -> Self { + Self { name, sources } + } + + pub fn new_from_dir(dir_path: PathBuf) -> Self { + let source_from_entry = |entry: DirEntry| { + let path = SourcePath(entry.into_path()); + let source_id = path.to_source_id(); + let path_ext = path.0.extension().unwrap_or_default().to_str(); + match (DiskContainerSource::new_from_path(path.clone()), path_ext) { + (Ok(source), _) => Some((source_id, source)), + (Err(err), Some("warp")) => { + log::error!("Failed to load source '{}' from disk: {}", path, err); + None + } + // We don't care to show errors loading for non-warp files. + (Err(_), _) => None, + } + }; + + // TODO: For now, any file that does not have the "warp" extension will be filtered out. + // TODO: cont. in the future we might want to remove this for convenience. + let name = dir_path.to_string_lossy().to_string(); + let sources = WalkDir::new(dir_path) + .into_iter() + .filter_map(|e| e.ok()) + .filter(|e| e.file_type().is_file()) + .filter(|e| e.path().extension().is_some_and(|e| e == "warp")) + .filter_map(source_from_entry) + .collect(); + + Self::new(name, sources) + } +} + +impl Container for DiskContainer { + fn sources(&self) -> ContainerResult<Vec<SourceId>> { + Ok(self.sources.keys().copied().collect()) + } + + fn add_source(&mut self, path: SourcePath) -> ContainerResult<SourceId> { + // Disk sources have there source id computed from the path. + let source_id = path.to_source_id(); + if self.sources.contains_key(&source_id) { + return Err(ContainerError::SourceAlreadyExists(path)); + } + // NOTE: We let anyone add a file from anywhere on the file system because of this. + match path.0.exists() { + true => { + let disk_source = DiskContainerSource::new_from_path(path.clone())?; + self.sources.insert(source_id, disk_source); + Ok(source_id) + } + false => { + let file = WarpFile::new(WarpFileHeader::new(), vec![]); + let disk_source = DiskContainerSource::new(path, file); + self.sources.insert(source_id, disk_source); + Ok(source_id) + } + } + } + + fn commit_source(&mut self, source: &SourceId) -> ContainerResult<bool> { + let disk_source = self + .sources + .get_mut(source) + .ok_or(ContainerError::SourceNotFound(*source))?; + + disk_source.commit_to_disk() + } + + fn is_source_writable(&self, source: &SourceId) -> ContainerResult<bool> { + let _disk_source = self + .sources + .get(source) + .ok_or(ContainerError::SourceNotFound(*source))?; + // TODO: I think this should be up to the container. (cant write to bundled files) + Ok(true) + } + + fn is_source_uncommitted(&self, source: &SourceId) -> ContainerResult<bool> { + let disk_source = self + .sources + .get(source) + .ok_or(ContainerError::SourceNotFound(*source))?; + Ok(disk_source.uncommitted) + } + + fn source_path(&self, source: &SourceId) -> ContainerResult<SourcePath> { + let disk_source = self + .sources + .get(source) + .ok_or(ContainerError::SourceNotFound(*source))?; + Ok(disk_source.path.clone()) + } + + fn add_computed_types( + &mut self, + source: &SourceId, + types: &[ComputedType], + ) -> ContainerResult<()> { + let disk_source = self + .sources + .get_mut(source) + .ok_or(ContainerError::SourceNotFound(*source))?; + + disk_source.add_computed_types(types) + } + + // TODO: I believe any remove has to happen immediately, i.e. we cant add an uncommitted for this? + fn remove_types(&mut self, source: &SourceId, _guids: &[TypeGUID]) -> ContainerResult<()> { + let _disk_source = self + .sources + .get(source) + .ok_or(ContainerError::SourceNotFound(*source))?; + + // TODO: Do this. + Err(ContainerError::SourceNotWritable(*source)) + } + + fn add_functions( + &mut self, + target: &Target, + source: &SourceId, + functions: &[Function], + ) -> ContainerResult<()> { + let disk_source = self + .sources + .get_mut(source) + .ok_or(ContainerError::SourceNotFound(*source))?; + + disk_source.add_functions(target.clone(), functions) + } + + // TODO: I believe any remove has to happen immediately, i.e. we cant add an uncommitted for this? + fn remove_functions( + &mut self, + _target: &Target, + source: &SourceId, + _functions: &[Function], + ) -> ContainerResult<()> { + let _disk_source = self + .sources + .get(source) + .ok_or(ContainerError::SourceNotFound(*source))?; + + // TODO: Do this. + Err(ContainerError::SourceNotWritable(*source)) + } + + fn sources_with_type_guid(&self, guid: &TypeGUID) -> ContainerResult<Vec<SourceId>> { + let sources = self + .sources + .iter() + .filter(|(_, source)| source.has_type_with_guid(guid)) + .map(|(id, _)| *id) + .collect(); + Ok(sources) + } + + fn sources_with_type_guids<'a>( + &'a self, + guids: &'a [TypeGUID], + ) -> ContainerResult<HashMap<TypeGUID, Vec<SourceId>>> { + let mut result: HashMap<TypeGUID, Vec<SourceId>> = HashMap::new(); + for (source_id, source) in &self.sources { + guids + .iter() + .filter(|guid| source.has_type_with_guid(guid)) + .for_each(|guid| result.entry(*guid).or_default().push(*source_id)); + } + Ok(result) + } + + fn type_guids_with_name( + &self, + source: &SourceId, + name: &str, + ) -> ContainerResult<Vec<TypeGUID>> { + let disk_source = self + .sources + .get(source) + .ok_or(ContainerError::SourceNotFound(*source))?; + Ok(disk_source.type_guids_with_name(name)) + } + + fn type_with_guid(&self, source: &SourceId, guid: &TypeGUID) -> ContainerResult<Option<Type>> { + let disk_source = self + .sources + .get(source) + .ok_or(ContainerError::SourceNotFound(*source))?; + Ok(disk_source.type_with_guid(guid)) + } + + fn sources_with_function_guid( + &self, + target: &Target, + guid: &FunctionGUID, + ) -> ContainerResult<Vec<SourceId>> { + let sources = self + .sources + .iter() + .filter(|(_, source)| source.has_function_with_guid(target, guid)) + .map(|(id, _)| *id) + .collect(); + Ok(sources) + } + + fn sources_with_function_guids<'a>( + &self, + target: &Target, + guids: &[FunctionGUID], + ) -> ContainerResult<HashMap<FunctionGUID, Vec<SourceId>>> { + let mut result: HashMap<FunctionGUID, Vec<SourceId>> = HashMap::new(); + for (source_id, source) in &self.sources { + guids + .iter() + .filter(|guid| source.has_function_with_guid(target, guid)) + .for_each(|guid| result.entry(*guid).or_default().push(*source_id)); + } + Ok(result) + } + + fn functions_with_guid( + &self, + target: &Target, + source: &SourceId, + guid: &FunctionGUID, + ) -> ContainerResult<Vec<Function>> { + let disk_source = self + .sources + .get(source) + .ok_or(ContainerError::SourceNotFound(*source))?; + Ok(disk_source.functions_with_guid(target, guid)) + } +} + +impl Display for DiskContainer { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + write!(f, "{}", self.name) + } +} + +impl Debug for DiskContainer { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + f.debug_struct("DiskContainer") + .field("name", &self.name) + .field("sources", &self.sources) + .finish() + } +} + +pub struct DiskContainerSource { + pub path: SourcePath, + file: WarpFile<'static>, + uncommitted: bool, +} + +impl DiskContainerSource { + pub fn new(path: SourcePath, file: WarpFile<'static>) -> Self { + Self { + path, + file, + uncommitted: false, + } + } + + pub fn new_from_path(path: SourcePath) -> ContainerResult<Self> { + // TODO: To keep the lifetime out of DiskContainerSource we do not allow mapping file to memory. + let contents = std::fs::read(&path).map_err(|e| ContainerError::FailedIO(e.kind()))?; + let file = WarpFile::from_owned_bytes(contents).ok_or(ContainerError::CorruptedData( + "file data failed to validate", + ))?; + Ok(Self::new(path, file)) + } + + fn add_computed_types(&mut self, types: &[ComputedType]) -> ContainerResult<()> { + let type_chunk = TypeChunk::new_with_computed(types).ok_or( + ContainerError::CorruptedData("type chunk failed to validate"), + )?; + let chunk = Chunk::new(ChunkKind::Type(type_chunk), CompressionType::None); + self.file.chunks.push(chunk); + self.uncommitted = true; + Ok(()) + } + + fn add_functions(&mut self, target: Target, functions: &[Function]) -> ContainerResult<()> { + let signature_chunk = SignatureChunk::new(functions).ok_or( + ContainerError::CorruptedData("signature chunk failed to validate"), + )?; + let chunk = Chunk::new_with_target( + ChunkKind::Signature(signature_chunk), + CompressionType::None, + target, + ); + self.file.chunks.push(chunk); + self.uncommitted = true; + Ok(()) + } + + fn commit_to_disk(&mut self) -> ContainerResult<bool> { + let file = self.file.to_bytes(); + std::fs::write(&self.path, file).map_err(|e| ContainerError::FailedIO(e.kind()))?; + self.uncommitted = false; + Ok(true) + } + + fn type_guids_with_name(&self, name: &str) -> Vec<TypeGUID> { + let mut found: Vec<TypeGUID> = Vec::new(); + for chunk in &self.file.chunks { + if let ChunkKind::Type(tc) = &chunk.kind { + found.extend( + tc.raw_type_with_name(name) + .into_iter() + .map(|t| TypeGUID::from(t.guid())), + ); + } + } + found + } + + fn type_with_guid(&self, guid: &TypeGUID) -> Option<Type> { + self.file.chunks.iter().find_map(|chunk| { + if let ChunkKind::Type(tc) = &chunk.kind { + tc.type_with_guid(guid) + } else { + None + } + }) + } + + // TODO: When we support reading lazily instead of all in memory. + fn has_type_with_guid(&self, guid: &TypeGUID) -> bool { + self.type_with_guid(guid).is_some() + } + + fn functions_with_guid(&self, target: &Target, guid: &FunctionGUID) -> Vec<Function> { + let mut found: Vec<Function> = Vec::new(); + for chunk in &self.file.chunks { + if chunk.header.target != *target { + continue; + } + if let ChunkKind::Signature(sc) = &chunk.kind { + found.extend(sc.functions_with_guid(guid)); + } + } + found + } + + // TODO: When we support reading lazily instead of all in memory. + fn has_function_with_guid(&self, target: &Target, guid: &FunctionGUID) -> bool { + // TODO: How about we dont clone. + !self.functions_with_guid(target, guid).is_empty() + } +} + +impl Hash for DiskContainerSource { + fn hash<H: Hasher>(&self, state: &mut H) { + self.path.hash(state); + } +} + +impl Display for DiskContainerSource { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + write!(f, "{}", self.path) + } +} + +impl Debug for DiskContainerSource { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + f.debug_struct("DiskContainerSource") + .field("path", &self.path) + .field("file_header", &self.file.header) + .field("file_chunks", &self.file.chunks.len()) + .finish() + } +} diff --git a/plugins/warp/src/container/memory.rs b/plugins/warp/src/container/memory.rs new file mode 100644 index 00000000..cf53390e --- /dev/null +++ b/plugins/warp/src/container/memory.rs @@ -0,0 +1,307 @@ +use crate::container::{Container, ContainerError, ContainerResult, SourceId, SourcePath}; +use std::collections::HashMap; +use std::fmt::Display; +use warp::r#type::guid::TypeGUID; +use warp::r#type::{ComputedType, Type}; +use warp::signature::function::{Function, FunctionGUID}; +use warp::target::Target; + +#[derive(Debug, Clone, PartialEq, Eq, Default)] +pub struct MemoryContainer { + sources: HashMap<SourceId, MemorySource>, +} + +impl MemoryContainer { + pub fn new() -> Self { + MemoryContainer::default() + } + + pub fn with_source(mut self, id: SourceId, source: MemorySource) -> Self { + self.sources.insert(id, source); + self + } + + pub fn with_source_function( + mut self, + id: SourceId, + guid: FunctionGUID, + func: Function, + ) -> Self { + self.sources + .entry(id) + .or_default() + .functions + .entry(guid) + .or_default() + .push(func); + self + } + + pub fn with_source_type(mut self, id: SourceId, guid: TypeGUID, ty: Type) -> Self { + self.sources.entry(id).or_default().types.insert(guid, ty); + self + } +} + +impl Container for MemoryContainer { + fn sources(&self) -> ContainerResult<Vec<SourceId>> { + todo!() + } + + fn add_source(&mut self, path: SourcePath) -> ContainerResult<SourceId> { + Err(ContainerError::CannotCreateSource(path)) + } + + fn commit_source(&mut self, _source: &SourceId) -> ContainerResult<bool> { + Ok(false) + } + + fn is_source_writable(&self, source: &SourceId) -> ContainerResult<bool> { + let memory_source = self + .sources + .get(source) + .ok_or(ContainerError::SourceNotFound(*source))?; + Ok(memory_source.writable) + } + + fn is_source_uncommitted(&self, source: &SourceId) -> ContainerResult<bool> { + let _memory_source = self + .sources + .get(source) + .ok_or(ContainerError::SourceNotFound(*source))?; + // NOTE: Memory containers do not have a notion of uncommitted data. + Ok(false) + } + + fn source_path(&self, source: &SourceId) -> ContainerResult<SourcePath> { + Err(ContainerError::SourcePathUnavailable(*source)) + } + + fn add_computed_types( + &mut self, + source: &SourceId, + types: &[ComputedType], + ) -> ContainerResult<()> { + let memory_source = self + .sources + .get_mut(source) + .ok_or(ContainerError::SourceNotFound(*source))?; + match memory_source.writable { + true => { + for ty in types { + memory_source.types.insert(ty.guid, ty.ty.clone()); + } + Ok(()) + } + false => Err(ContainerError::SourceNotWritable(*source)), + } + } + + fn remove_types(&mut self, source: &SourceId, guids: &[TypeGUID]) -> ContainerResult<()> { + let memory_source = self + .sources + .get_mut(source) + .ok_or(ContainerError::SourceNotFound(*source))?; + match memory_source.writable { + true => { + for guid in guids { + memory_source.types.remove(guid); + } + Ok(()) + } + false => Err(ContainerError::SourceNotWritable(*source)), + } + } + + fn add_functions( + &mut self, + _target: &Target, + source: &SourceId, + functions: &[Function], + ) -> ContainerResult<()> { + let memory_source = self + .sources + .get_mut(source) + .ok_or(ContainerError::SourceNotFound(*source))?; + match memory_source.writable { + true => { + for function in functions { + memory_source + .functions + .entry(function.guid) + .or_default() + .push(function.clone()); + } + Ok(()) + } + false => Err(ContainerError::SourceNotWritable(*source)), + } + } + + fn remove_functions( + &mut self, + _target: &Target, + source: &SourceId, + functions: &[Function], + ) -> ContainerResult<()> { + let memory_source = self + .sources + .get_mut(source) + .ok_or(ContainerError::SourceNotFound(*source))?; + match memory_source.writable { + true => { + for function in functions { + if let Some(src_funcs) = memory_source.functions.get_mut(&function.guid) { + src_funcs.retain(|f| f != function); + if src_funcs.is_empty() { + memory_source.functions.remove(&function.guid); + } + } + } + Ok(()) + } + false => Err(ContainerError::SourceNotWritable(*source)), + } + } + + fn sources_with_type_guid(&self, guid: &TypeGUID) -> ContainerResult<Vec<SourceId>> { + let sources = self + .sources + .iter() + .filter(|(_, source)| source.has_type_with_guid(guid)) + .map(|(id, _)| *id) + .collect(); + Ok(sources) + } + + fn sources_with_type_guids( + &self, + guids: &[TypeGUID], + ) -> ContainerResult<HashMap<TypeGUID, Vec<SourceId>>> { + let mut result: HashMap<TypeGUID, Vec<SourceId>> = HashMap::new(); + for (source_id, source) in &self.sources { + guids + .iter() + .filter(|guid| source.has_type_with_guid(guid)) + .for_each(|guid| result.entry(*guid).or_default().push(*source_id)); + } + Ok(result) + } + + fn type_guids_with_name( + &self, + source: &SourceId, + name: &str, + ) -> ContainerResult<Vec<TypeGUID>> { + let memory_source = self + .sources + .get(source) + .ok_or(ContainerError::SourceNotFound(*source))?; + Ok(memory_source.type_guids_with_name(name)) + } + + fn type_with_guid(&self, source: &SourceId, guid: &TypeGUID) -> ContainerResult<Option<Type>> { + let memory_source = self + .sources + .get(source) + .ok_or(ContainerError::SourceNotFound(*source))?; + Ok(memory_source.type_with_guid(guid)) + } + + fn sources_with_function_guid( + &self, + _target: &Target, + guid: &FunctionGUID, + ) -> ContainerResult<Vec<SourceId>> { + let sources = self + .sources + .iter() + .filter(|(_, source)| source.has_function_with_guid(guid)) + .map(|(id, _)| *id) + .collect(); + Ok(sources) + } + + fn sources_with_function_guids( + &self, + _target: &Target, + guids: &[FunctionGUID], + ) -> ContainerResult<HashMap<FunctionGUID, Vec<SourceId>>> { + let mut result: HashMap<FunctionGUID, Vec<SourceId>> = HashMap::new(); + for (source_id, source) in &self.sources { + guids + .iter() + .filter(|guid| source.has_function_with_guid(guid)) + .for_each(|guid| result.entry(*guid).or_default().push(*source_id)); + } + Ok(result) + } + + fn functions_with_guid( + &self, + _target: &Target, + source: &SourceId, + guid: &FunctionGUID, + ) -> ContainerResult<Vec<Function>> { + let memory_source = self + .sources + .get(source) + .ok_or(ContainerError::SourceNotFound(*source))?; + Ok(memory_source.functions_with_guid(guid)) + } +} + +impl Display for MemoryContainer { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.write_str("MemoryContainer") + } +} + +/// An in-memory store of functions. +/// +/// This is typically an overlay on top of a container source. +#[derive(Eq, PartialEq, Debug, Clone)] +pub struct MemorySource { + pub writable: bool, + pub functions: HashMap<FunctionGUID, Vec<Function>>, + pub types: HashMap<TypeGUID, Type>, + pub named_types: HashMap<String, Vec<TypeGUID>>, +} + +impl MemorySource { + pub fn type_guids_with_name(&self, name: &str) -> Vec<TypeGUID> { + // TODO: The function here is a little goofy. + // TODO: This is cloned. + self.named_types.get(name).cloned().unwrap_or_default() + } + + pub fn type_with_guid(&self, guid: &TypeGUID) -> Option<Type> { + // TODO: This is cloned. + self.types.get(guid).cloned() + } + + pub fn functions_with_guid(&self, guid: &FunctionGUID) -> Vec<Function> { + // TODO: The function here is a little goofy. + // TODO: This is cloned. + self.functions.get(guid).cloned().unwrap_or_default() + } + + pub fn has_type_with_guid(&self, guid: &TypeGUID) -> bool { + self.type_with_guid(guid).is_some() + } + + pub fn has_function_with_guid(&self, guid: &FunctionGUID) -> bool { + !self.functions_with_guid(guid).is_empty() + } +} + +impl Default for MemorySource { + fn default() -> Self { + Self { + writable: true, + functions: HashMap::new(), + types: HashMap::new(), + named_types: HashMap::new(), + } + } +} diff --git a/plugins/warp/src/container/network.rs b/plugins/warp/src/container/network.rs new file mode 100644 index 00000000..ffbe6108 --- /dev/null +++ b/plugins/warp/src/container/network.rs @@ -0,0 +1,13 @@ +pub struct NetworkContainer {} + +// TODO: The container is populated as the user is navigating a binary +// TODO: We need to have a few helper functions here to post and pull +// TODO: Then in the interface we operate off the network cache +// TODO: The network cache could just be a disk container? Or disk container sources? +// TODO: We should also store the cache on the filesystem for a certain time, will need to timestamp +// TODO: When we commit we need to actually POST i believe. +// TODO: There needs to be a setting that adjusts the sweep size of functions at the cursor. +// TODO: Probably need a callback or something to tell the network containers to refresh from the network. +// TODO: The network container should never instantiate itself, unless its gurenteed to not have any data in it? + +// TODO: Need to PUSH chunks and PULL chunks |
