diff options
Diffstat (limited to 'rust/src/project.rs')
| -rw-r--r-- | rust/src/project.rs | 1142 |
1 files changed, 226 insertions, 916 deletions
diff --git a/rust/src/project.rs b/rust/src/project.rs index f08ad63e..fad1e5ef 100644 --- a/rust/src/project.rs +++ b/rust/src/project.rs @@ -1,16 +1,22 @@ +pub mod file; +pub mod folder; + +use std::ffi::{c_char, c_void}; +use std::fmt::Debug; use std::ptr::{null_mut, NonNull}; use std::time::{Duration, SystemTime, UNIX_EPOCH}; -use std::{ffi, mem}; use binaryninjacore_sys::*; use crate::metadata::Metadata; -use crate::rc::{Array, CoreArrayProvider, CoreArrayProviderInner, Ref}; +use crate::progress::{NoProgressCallback, ProgressCallback}; +use crate::project::file::ProjectFile; +use crate::project::folder::ProjectFolder; +use crate::rc::{Array, CoreArrayProvider, CoreArrayProviderInner, Guard, Ref, RefCountable}; use crate::string::{BnStrCompatible, BnString}; -#[repr(C)] pub struct Project { - handle: NonNull<BNProject>, + pub(crate) handle: NonNull<BNProject>, } impl Project { @@ -18,14 +24,8 @@ impl Project { Project { handle } } - pub(crate) unsafe fn ref_from_raw(handle: &*mut BNProject) -> &Self { - debug_assert!(!handle.is_null()); - mem::transmute(handle) - } - - #[allow(clippy::mut_from_ref)] - pub(crate) unsafe fn as_raw(&self) -> &mut BNProject { - &mut *self.handle.as_ptr() + pub(crate) unsafe fn ref_from_raw(handle: NonNull<BNProject>) -> Ref<Self> { + Ref::new(Self { handle }) } pub fn all_open() -> Array<Project> { @@ -39,35 +39,35 @@ impl Project { /// /// * `path` - Path to the project directory (.bnpr) /// * `name` - Name of the new project - pub fn create<P: BnStrCompatible, S: BnStrCompatible>(path: P, name: S) -> Self { + pub fn create<P: BnStrCompatible, S: BnStrCompatible>(path: P, name: S) -> Option<Ref<Self>> { let path_raw = path.into_bytes_with_nul(); let name_raw = name.into_bytes_with_nul(); let handle = unsafe { BNCreateProject( - path_raw.as_ref().as_ptr() as *const ffi::c_char, - name_raw.as_ref().as_ptr() as *const ffi::c_char, + path_raw.as_ref().as_ptr() as *const c_char, + name_raw.as_ref().as_ptr() as *const c_char, ) }; - unsafe { Self::from_raw(NonNull::new(handle).unwrap()) } + NonNull::new(handle).map(|h| unsafe { Self::ref_from_raw(h) }) } /// Open an existing project /// /// * `path` - Path to the project directory (.bnpr) or project metadata file (.bnpm) - pub fn open_project<P: BnStrCompatible>(path: P) -> Self { + pub fn open_project<P: BnStrCompatible>(path: P) -> Option<Ref<Self>> { let path_raw = path.into_bytes_with_nul(); - let handle = unsafe { BNOpenProject(path_raw.as_ref().as_ptr() as *const ffi::c_char) }; - unsafe { Self::from_raw(NonNull::new(handle).unwrap()) } + let handle = unsafe { BNOpenProject(path_raw.as_ref().as_ptr() as *const c_char) }; + NonNull::new(handle).map(|h| unsafe { Self::ref_from_raw(h) }) } /// Check if the project is currently open pub fn is_open(&self) -> bool { - unsafe { BNProjectIsOpen(self.as_raw()) } + unsafe { BNProjectIsOpen(self.handle.as_ptr()) } } /// Open a closed project pub fn open(&self) -> Result<(), ()> { - if unsafe { BNProjectOpen(self.as_raw()) } { + if unsafe { BNProjectOpen(self.handle.as_ptr()) } { Ok(()) } else { Err(()) @@ -76,7 +76,7 @@ impl Project { /// Close a open project pub fn close(&self) -> Result<(), ()> { - if unsafe { BNProjectClose(self.as_raw()) } { + if unsafe { BNProjectClose(self.handle.as_ptr()) } { Ok(()) } else { Err(()) @@ -85,35 +85,43 @@ impl Project { /// Get the unique id of this project pub fn id(&self) -> BnString { - unsafe { BnString::from_raw(BNProjectGetId(self.as_raw())) } + unsafe { BnString::from_raw(BNProjectGetId(self.handle.as_ptr())) } } /// Get the path of the project pub fn path(&self) -> BnString { - unsafe { BnString::from_raw(BNProjectGetPath(self.as_raw())) } + unsafe { BnString::from_raw(BNProjectGetPath(self.handle.as_ptr())) } } /// Get the name of the project pub fn name(&self) -> BnString { - unsafe { BnString::from_raw(BNProjectGetName(self.as_raw())) } + unsafe { BnString::from_raw(BNProjectGetName(self.handle.as_ptr())) } } /// Set the name of the project pub fn set_name<S: BnStrCompatible>(&self, value: S) { let value = value.into_bytes_with_nul(); - unsafe { BNProjectSetName(self.as_raw(), value.as_ref().as_ptr() as *const ffi::c_char) } + unsafe { + BNProjectSetName( + self.handle.as_ptr(), + value.as_ref().as_ptr() as *const c_char, + ) + } } /// Get the description of the project pub fn description(&self) -> BnString { - unsafe { BnString::from_raw(BNProjectGetDescription(self.as_raw())) } + unsafe { BnString::from_raw(BNProjectGetDescription(self.handle.as_ptr())) } } /// Set the description of the project pub fn set_description<S: BnStrCompatible>(&self, value: S) { let value = value.into_bytes_with_nul(); unsafe { - BNProjectSetDescription(self.as_raw(), value.as_ref().as_ptr() as *const ffi::c_char) + BNProjectSetDescription( + self.handle.as_ptr(), + value.as_ref().as_ptr() as *const c_char, + ) } } @@ -121,7 +129,7 @@ impl Project { pub fn query_metadata<S: BnStrCompatible>(&self, key: S) -> Ref<Metadata> { let key = key.into_bytes_with_nul(); let result = unsafe { - BNProjectQueryMetadata(self.as_raw(), key.as_ref().as_ptr() as *const ffi::c_char) + BNProjectQueryMetadata(self.handle.as_ptr(), key.as_ref().as_ptr() as *const c_char) }; unsafe { Metadata::ref_from_raw(result) } } @@ -134,8 +142,8 @@ impl Project { let key_raw = key.into_bytes_with_nul(); unsafe { BNProjectStoreMetadata( - self.as_raw(), - key_raw.as_ref().as_ptr() as *const ffi::c_char, + self.handle.as_ptr(), + key_raw.as_ref().as_ptr() as *const c_char, value.handle, ) } @@ -146,14 +154,14 @@ impl Project { let key_raw = key.into_bytes_with_nul(); unsafe { BNProjectRemoveMetadata( - self.as_raw(), - key_raw.as_ref().as_ptr() as *const ffi::c_char, + self.handle.as_ptr(), + key_raw.as_ref().as_ptr() as *const c_char, ) } } pub fn push_folder(&self, file: &ProjectFolder) { - unsafe { BNProjectPushFolder(self.as_raw(), file.as_raw()) } + unsafe { BNProjectPushFolder(self.handle.as_ptr(), file.handle.as_ptr()) } } /// Recursively create files and folders in the project from a path on disk @@ -166,28 +174,12 @@ impl Project { path: P, parent: Option<&ProjectFolder>, description: D, - ) -> Result<ProjectFolder, ()> + ) -> Result<Ref<ProjectFolder>, ()> where P: BnStrCompatible, D: BnStrCompatible, { - let path_raw = path.into_bytes_with_nul(); - let description_raw = description.into_bytes_with_nul(); - let parent_ptr = parent - .map(|p| unsafe { p.as_raw() as *mut _ }) - .unwrap_or(null_mut()); - - unsafe { - let result = BNProjectCreateFolderFromPath( - self.as_raw(), - path_raw.as_ref().as_ptr() as *const ffi::c_char, - parent_ptr, - description_raw.as_ref().as_ptr() as *const ffi::c_char, - null_mut(), - Some(cb_progress_func_nop), - ); - Ok(ProjectFolder::from_raw(NonNull::new(result).ok_or(())?)) - } + self.create_folder_from_path_with_progress(path, parent, description, NoProgressCallback) } /// Recursively create files and folders in the project from a path on disk @@ -195,36 +187,33 @@ impl Project { /// * `path` - Path to folder on disk /// * `parent` - Parent folder in the project that will contain the new contents /// * `description` - Description for created root folder - /// * `progress_func` - Progress function that will be called - pub fn create_folder_from_path_with_progress<P, D, F>( + /// * `progress` - [`ProgressCallback`] that will be called as the [`ProjectFolder`] is being created + pub fn create_folder_from_path_with_progress<P, D, PC>( &self, path: P, parent: Option<&ProjectFolder>, description: D, - mut progress_func: F, - ) -> Result<ProjectFolder, ()> + mut progress: PC, + ) -> Result<Ref<ProjectFolder>, ()> where P: BnStrCompatible, D: BnStrCompatible, - F: FnMut(usize, usize) -> bool, + PC: ProgressCallback, { let path_raw = path.into_bytes_with_nul(); let description_raw = description.into_bytes_with_nul(); - let parent_ptr = parent - .map(|p| unsafe { p.as_raw() as *mut _ }) - .unwrap_or(null_mut()); + let parent_ptr = parent.map(|p| p.handle.as_ptr()).unwrap_or(null_mut()); - let progress_ctx = &mut progress_func as *mut F as *mut ffi::c_void; unsafe { let result = BNProjectCreateFolderFromPath( - self.as_raw(), - path_raw.as_ref().as_ptr() as *const ffi::c_char, + self.handle.as_ptr(), + path_raw.as_ref().as_ptr() as *const c_char, parent_ptr, - description_raw.as_ref().as_ptr() as *const ffi::c_char, - progress_ctx, - Some(cb_progress_func::<F>), + description_raw.as_ref().as_ptr() as *const c_char, + &mut progress as *mut PC as *mut c_void, + Some(PC::cb_progress_callback), ); - Ok(ProjectFolder::from_raw(NonNull::new(result).ok_or(())?)) + Ok(ProjectFolder::ref_from_raw(NonNull::new(result).ok_or(())?)) } } @@ -238,24 +227,22 @@ impl Project { parent: Option<&ProjectFolder>, name: N, description: D, - ) -> Result<ProjectFolder, ()> + ) -> Result<Ref<ProjectFolder>, ()> where N: BnStrCompatible, D: BnStrCompatible, { let name_raw = name.into_bytes_with_nul(); let description_raw = description.into_bytes_with_nul(); - let parent_ptr = parent - .map(|p| unsafe { p.as_raw() as *mut _ }) - .unwrap_or(null_mut()); + let parent_ptr = parent.map(|p| p.handle.as_ptr()).unwrap_or(null_mut()); unsafe { let result = BNProjectCreateFolder( - self.as_raw(), + self.handle.as_ptr(), parent_ptr, - name_raw.as_ref().as_ptr() as *const ffi::c_char, - description_raw.as_ref().as_ptr() as *const ffi::c_char, + name_raw.as_ref().as_ptr() as *const c_char, + description_raw.as_ref().as_ptr() as *const c_char, ); - Ok(ProjectFolder::from_raw(NonNull::new(result).ok_or(())?)) + Ok(ProjectFolder::ref_from_raw(NonNull::new(result).ok_or(())?)) } } @@ -271,7 +258,7 @@ impl Project { name: N, description: D, id: I, - ) -> Result<ProjectFolder, ()> + ) -> Result<Ref<ProjectFolder>, ()> where N: BnStrCompatible, D: BnStrCompatible, @@ -279,26 +266,24 @@ impl Project { { let name_raw = name.into_bytes_with_nul(); let description_raw = description.into_bytes_with_nul(); - let parent_ptr = parent - .map(|p| unsafe { p.as_raw() as *mut _ }) - .unwrap_or(null_mut()); + let parent_ptr = parent.map(|p| p.handle.as_ptr()).unwrap_or(null_mut()); let id_raw = id.into_bytes_with_nul(); unsafe { let result = BNProjectCreateFolderUnsafe( - self.as_raw(), + self.handle.as_ptr(), parent_ptr, - name_raw.as_ref().as_ptr() as *const ffi::c_char, - description_raw.as_ref().as_ptr() as *const ffi::c_char, - id_raw.as_ref().as_ptr() as *const ffi::c_char, + name_raw.as_ref().as_ptr() as *const c_char, + description_raw.as_ref().as_ptr() as *const c_char, + id_raw.as_ref().as_ptr() as *const c_char, ); - Ok(ProjectFolder::from_raw(NonNull::new(result).ok_or(())?)) + Ok(ProjectFolder::ref_from_raw(NonNull::new(result).ok_or(())?)) } } /// Get a list of folders in the project pub fn folders(&self) -> Result<Array<ProjectFolder>, ()> { let mut count = 0; - let result = unsafe { BNProjectGetFolders(self.as_raw(), &mut count) }; + let result = unsafe { BNProjectGetFolders(self.handle.as_ptr(), &mut count) }; if result.is_null() { return Err(()); } @@ -307,55 +292,39 @@ impl Project { } /// Retrieve a folder in the project by unique folder `id` - pub fn folder_by_id<S: BnStrCompatible>(&self, id: S) -> Option<ProjectFolder> { + pub fn folder_by_id<S: BnStrCompatible>(&self, id: S) -> Option<Ref<ProjectFolder>> { let id_raw = id.into_bytes_with_nul(); - let id_ptr = id_raw.as_ref().as_ptr() as *const ffi::c_char; - - let result = unsafe { BNProjectGetFolderById(self.as_raw(), id_ptr) }; + let id_ptr = id_raw.as_ref().as_ptr() as *const c_char; + let result = unsafe { BNProjectGetFolderById(self.handle.as_ptr(), id_ptr) }; let handle = NonNull::new(result)?; - Some(unsafe { ProjectFolder::from_raw(handle) }) + Some(unsafe { ProjectFolder::ref_from_raw(handle) }) } - /// Recursively delete a folder from the project + /// Recursively delete a [`ProjectFolder`] from the [`Project`]. /// - /// * `folder` - Folder to delete recursively + /// * `folder` - [`ProjectFolder`] to delete recursively pub fn delete_folder(&self, folder: &ProjectFolder) -> Result<(), ()> { - let result = unsafe { - BNProjectDeleteFolder( - self.as_raw(), - folder.as_raw(), - null_mut(), - Some(cb_progress_func_nop), - ) - }; - if result { - Ok(()) - } else { - Err(()) - } + self.delete_folder_with_progress(folder, NoProgressCallback) } - /// Recursively delete a folder from the project + /// Recursively delete a [`ProjectFolder`] from the [`Project`]. /// - /// * `folder` - Folder to delete recursively - /// * `progress_func` - Progress function that will be called as objects get deleted - pub fn delete_folder_with_progress<F>( + /// * `folder` - [`ProjectFolder`] to delete recursively + /// * `progress` - [`ProgressCallback`] that will be called as objects get deleted + pub fn delete_folder_with_progress<P: ProgressCallback>( &self, folder: &ProjectFolder, - mut progress_func: F, - ) -> Result<(), ()> - where - F: FnMut(usize, usize) -> bool, - { - let progress_ctx = &mut progress_func as *mut F as *mut ffi::c_void; + mut progress: P, + ) -> Result<(), ()> { let result = unsafe { BNProjectDeleteFolder( - self.as_raw(), - folder.as_raw(), - progress_ctx, - Some(cb_progress_func::<F>), + self.handle.as_ptr(), + folder.handle.as_ptr(), + &mut progress as *mut P as *mut c_void, + Some(P::cb_progress_callback), ) }; + if result { Ok(()) } else { @@ -364,7 +333,7 @@ impl Project { } pub fn push_file(&self, file: &ProjectFile) { - unsafe { BNProjectPushFile(self.as_raw(), file.as_raw()) } + unsafe { BNProjectPushFile(self.handle.as_ptr(), file.handle.as_ptr()) } } /// Create a file in the project from a path on disk @@ -379,27 +348,19 @@ impl Project { folder: Option<&ProjectFolder>, name: N, description: D, - ) -> Result<ProjectFile, ()> + ) -> Result<Ref<ProjectFile>, ()> where P: BnStrCompatible, N: BnStrCompatible, D: BnStrCompatible, { - let path_raw = path.into_bytes_with_nul(); - let name_raw = name.into_bytes_with_nul(); - let description_raw = description.into_bytes_with_nul(); - unsafe { - let result = BNProjectCreateFileFromPath( - self.as_raw(), - path_raw.as_ref().as_ptr() as *const ffi::c_char, - folder.map(|x| x.as_raw() as *mut _).unwrap_or(null_mut()), - name_raw.as_ref().as_ptr() as *const ffi::c_char, - description_raw.as_ref().as_ptr() as *const ffi::c_char, - null_mut(), - Some(cb_progress_func_nop), - ); - Ok(ProjectFile::from_raw(NonNull::new(result).ok_or(())?)) - } + self.create_file_from_path_with_progress( + path, + folder, + name, + description, + NoProgressCallback, + ) } /// Create a file in the project from a path on disk @@ -408,36 +369,37 @@ impl Project { /// * `folder` - Folder to place the created file in /// * `name` - Name to assign to the created file /// * `description` - Description to assign to the created file - /// * `progress_func` - Progress function that will be called as the file is being added - pub fn create_file_from_path_with_progress<P, N, D, F>( + /// * `progress` - [`ProgressCallback`] that will be called as the [`ProjectFile`] is being added + pub fn create_file_from_path_with_progress<P, N, D, PC>( &self, path: P, folder: Option<&ProjectFolder>, name: N, description: D, - mut progress_func: F, - ) -> Result<ProjectFile, ()> + mut progress: PC, + ) -> Result<Ref<ProjectFile>, ()> where P: BnStrCompatible, N: BnStrCompatible, D: BnStrCompatible, - F: FnMut(usize, usize) -> bool, + PC: ProgressCallback, { let path_raw = path.into_bytes_with_nul(); let name_raw = name.into_bytes_with_nul(); let description_raw = description.into_bytes_with_nul(); - let progress_ctx = &mut progress_func as *mut F as *mut ffi::c_void; + let folder_ptr = folder.map(|p| p.handle.as_ptr()).unwrap_or(null_mut()); + unsafe { let result = BNProjectCreateFileFromPath( - self.as_raw(), - path_raw.as_ref().as_ptr() as *const ffi::c_char, - folder.map(|x| x.as_raw() as *mut _).unwrap_or(null_mut()), - name_raw.as_ref().as_ptr() as *const ffi::c_char, - description_raw.as_ref().as_ptr() as *const ffi::c_char, - progress_ctx, - Some(cb_progress_func::<F>), + self.handle.as_ptr(), + path_raw.as_ref().as_ptr() as *const c_char, + folder_ptr, + name_raw.as_ref().as_ptr() as *const c_char, + description_raw.as_ref().as_ptr() as *const c_char, + &mut progress as *mut PC as *mut c_void, + Some(PC::cb_progress_callback), ); - Ok(ProjectFile::from_raw(NonNull::new(result).ok_or(())?)) + Ok(ProjectFile::ref_from_raw(NonNull::new(result).ok_or(())?)) } } @@ -457,31 +419,22 @@ impl Project { description: D, id: I, creation_time: SystemTime, - ) -> Result<ProjectFile, ()> + ) -> Result<Ref<ProjectFile>, ()> where P: BnStrCompatible, N: BnStrCompatible, D: BnStrCompatible, I: BnStrCompatible, { - let path_raw = path.into_bytes_with_nul(); - let name_raw = name.into_bytes_with_nul(); - let description_raw = description.into_bytes_with_nul(); - let id_raw = id.into_bytes_with_nul(); - unsafe { - let result = BNProjectCreateFileFromPathUnsafe( - self.as_raw(), - path_raw.as_ref().as_ptr() as *const ffi::c_char, - folder.map(|x| x.as_raw() as *mut _).unwrap_or(null_mut()), - name_raw.as_ref().as_ptr() as *const ffi::c_char, - description_raw.as_ref().as_ptr() as *const ffi::c_char, - id_raw.as_ref().as_ptr() as *const ffi::c_char, - systime_to_bntime(creation_time).unwrap(), - null_mut(), - Some(cb_progress_func_nop), - ); - Ok(ProjectFile::from_raw(NonNull::new(result).ok_or(())?)) - } + self.create_file_from_path_unsafe_with_progress( + path, + folder, + name, + description, + id, + creation_time, + NoProgressCallback, + ) } /// Create a file in the project from a path on disk @@ -492,9 +445,9 @@ impl Project { /// * `description` - Description to assign to the created file /// * `id` - id unique ID /// * `creation_time` - Creation time of the file - /// * `progress_func` - Progress function that will be called as the file is being added + /// * `progress` - [`ProgressCallback`] that will be called as the [`ProjectFile`] is being created #[allow(clippy::too_many_arguments)] - pub unsafe fn create_file_from_path_with_progress_unsafe<P, N, D, I, F>( + pub unsafe fn create_file_from_path_unsafe_with_progress<P, N, D, I, PC>( &self, path: P, folder: Option<&ProjectFolder>, @@ -502,33 +455,34 @@ impl Project { description: D, id: I, creation_time: SystemTime, - mut progress_func: F, - ) -> Result<ProjectFile, ()> + mut progress: PC, + ) -> Result<Ref<ProjectFile>, ()> where P: BnStrCompatible, N: BnStrCompatible, D: BnStrCompatible, I: BnStrCompatible, - F: FnMut(usize, usize) -> bool, + PC: ProgressCallback, { let path_raw = path.into_bytes_with_nul(); let name_raw = name.into_bytes_with_nul(); let description_raw = description.into_bytes_with_nul(); let id_raw = id.into_bytes_with_nul(); - let progress_ctx = &mut progress_func as *mut F as *mut ffi::c_void; + let folder_ptr = folder.map(|p| p.handle.as_ptr()).unwrap_or(null_mut()); + unsafe { let result = BNProjectCreateFileFromPathUnsafe( - self.as_raw(), - path_raw.as_ref().as_ptr() as *const ffi::c_char, - folder.map(|x| x.as_raw() as *mut _).unwrap_or(null_mut()), - name_raw.as_ref().as_ptr() as *const ffi::c_char, - description_raw.as_ref().as_ptr() as *const ffi::c_char, - id_raw.as_ref().as_ptr() as *const ffi::c_char, + self.handle.as_ptr(), + path_raw.as_ref().as_ptr() as *const c_char, + folder_ptr, + name_raw.as_ref().as_ptr() as *const c_char, + description_raw.as_ref().as_ptr() as *const c_char, + id_raw.as_ref().as_ptr() as *const c_char, systime_to_bntime(creation_time).unwrap(), - progress_ctx, - Some(cb_progress_func::<F>), + &mut progress as *mut PC as *mut c_void, + Some(PC::cb_progress_callback), ); - Ok(ProjectFile::from_raw(NonNull::new(result).ok_or(())?)) + Ok(ProjectFile::ref_from_raw(NonNull::new(result).ok_or(())?)) } } @@ -544,26 +498,12 @@ impl Project { folder: Option<&ProjectFolder>, name: N, description: D, - ) -> Result<ProjectFile, ()> + ) -> Result<Ref<ProjectFile>, ()> where N: BnStrCompatible, D: BnStrCompatible, { - let name_raw = name.into_bytes_with_nul(); - let description_raw = description.into_bytes_with_nul(); - unsafe { - let result = BNProjectCreateFile( - self.as_raw(), - contents.as_ptr(), - contents.len(), - folder.map(|x| x.as_raw() as *mut _).unwrap_or(null_mut()), - name_raw.as_ref().as_ptr() as *const ffi::c_char, - description_raw.as_ref().as_ptr() as *const ffi::c_char, - null_mut(), - Some(cb_progress_func_nop), - ); - Ok(ProjectFile::from_raw(NonNull::new(result).ok_or(())?)) - } + self.create_file_with_progress(contents, folder, name, description, NoProgressCallback) } /// Create a file in the project @@ -572,35 +512,36 @@ impl Project { /// * `folder` - Folder to place the created file in /// * `name` - Name to assign to the created file /// * `description` - Description to assign to the created file - /// * `progress_func` - Progress function that will be called as the file is being added - pub fn create_file_with_progress<N, D, F>( + /// * `progress` - [`ProgressCallback`] that will be called as the [`ProjectFile`] is being created + pub fn create_file_with_progress<N, D, P>( &self, contents: &[u8], folder: Option<&ProjectFolder>, name: N, description: D, - mut progress_func: F, - ) -> Result<ProjectFile, ()> + mut progress: P, + ) -> Result<Ref<ProjectFile>, ()> where N: BnStrCompatible, D: BnStrCompatible, - F: FnMut(usize, usize) -> bool, + P: ProgressCallback, { let name_raw = name.into_bytes_with_nul(); let description_raw = description.into_bytes_with_nul(); - let progress_ctx = &mut progress_func as *mut F as *mut ffi::c_void; + let folder_ptr = folder.map(|p| p.handle.as_ptr()).unwrap_or(null_mut()); + unsafe { let result = BNProjectCreateFile( - self.as_raw(), + self.handle.as_ptr(), contents.as_ptr(), contents.len(), - folder.map(|x| x.as_raw() as *mut _).unwrap_or(null_mut()), - name_raw.as_ref().as_ptr() as *const ffi::c_char, - description_raw.as_ref().as_ptr() as *const ffi::c_char, - progress_ctx, - Some(cb_progress_func::<F>), + folder_ptr, + name_raw.as_ref().as_ptr() as *const c_char, + description_raw.as_ref().as_ptr() as *const c_char, + &mut progress as *mut P as *mut c_void, + Some(P::cb_progress_callback), ); - Ok(ProjectFile::from_raw(NonNull::new(result).ok_or(())?)) + Ok(ProjectFile::ref_from_raw(NonNull::new(result).ok_or(())?)) } } @@ -620,30 +561,21 @@ impl Project { description: D, id: I, creation_time: SystemTime, - ) -> Result<ProjectFile, ()> + ) -> Result<Ref<ProjectFile>, ()> where N: BnStrCompatible, D: BnStrCompatible, I: BnStrCompatible, { - let name_raw = name.into_bytes_with_nul(); - let description_raw = description.into_bytes_with_nul(); - let id_raw = id.into_bytes_with_nul(); - unsafe { - let result = BNProjectCreateFileUnsafe( - self.as_raw(), - contents.as_ptr(), - contents.len(), - folder.map(|x| x.as_raw() as *mut _).unwrap_or(null_mut()), - name_raw.as_ref().as_ptr() as *const ffi::c_char, - description_raw.as_ref().as_ptr() as *const ffi::c_char, - id_raw.as_ref().as_ptr() as *const ffi::c_char, - systime_to_bntime(creation_time).unwrap(), - null_mut(), - Some(cb_progress_func_nop), - ); - Ok(ProjectFile::from_raw(NonNull::new(result).ok_or(())?)) - } + self.create_file_unsafe_with_progress( + contents, + folder, + name, + description, + id, + creation_time, + NoProgressCallback, + ) } /// Create a file in the project @@ -654,9 +586,9 @@ impl Project { /// * `description` - Description to assign to the created file /// * `id` - id unique ID /// * `creation_time` - Creation time of the file - /// * `progress_func` - Progress function that will be called as the file is being added + /// * `progress` - [`ProgressCallback`] that will be called as the [`ProjectFile`] is being created #[allow(clippy::too_many_arguments)] - pub unsafe fn create_file_with_progress_unsafe<N, D, I, F>( + pub unsafe fn create_file_unsafe_with_progress<N, D, I, P>( &self, contents: &[u8], folder: Option<&ProjectFolder>, @@ -664,66 +596,67 @@ impl Project { description: D, id: I, creation_time: SystemTime, - mut progress_func: F, - ) -> Result<ProjectFile, ()> + mut progress: P, + ) -> Result<Ref<ProjectFile>, ()> where N: BnStrCompatible, D: BnStrCompatible, I: BnStrCompatible, - F: FnMut(usize, usize) -> bool, + P: ProgressCallback, { let name_raw = name.into_bytes_with_nul(); let description_raw = description.into_bytes_with_nul(); let id_raw = id.into_bytes_with_nul(); - let progress_ctx = &mut progress_func as *mut F as *mut ffi::c_void; + let folder_ptr = folder.map(|p| p.handle.as_ptr()).unwrap_or(null_mut()); + unsafe { let result = BNProjectCreateFileUnsafe( - self.as_raw(), + self.handle.as_ptr(), contents.as_ptr(), contents.len(), - folder.map(|x| x.as_raw() as *mut _).unwrap_or(null_mut()), - name_raw.as_ref().as_ptr() as *const ffi::c_char, - description_raw.as_ref().as_ptr() as *const ffi::c_char, - id_raw.as_ref().as_ptr() as *const ffi::c_char, + folder_ptr, + name_raw.as_ref().as_ptr() as *const c_char, + description_raw.as_ref().as_ptr() as *const c_char, + id_raw.as_ref().as_ptr() as *const c_char, systime_to_bntime(creation_time).unwrap(), - progress_ctx, - Some(cb_progress_func::<F>), + &mut progress as *mut P as *mut c_void, + Some(P::cb_progress_callback), ); - Ok(ProjectFile::from_raw(NonNull::new(result).ok_or(())?)) + Ok(ProjectFile::ref_from_raw(NonNull::new(result).ok_or(())?)) } } /// Get a list of files in the project - pub fn files(&self) -> Result<Array<ProjectFile>, ()> { + pub fn files(&self) -> Array<ProjectFile> { let mut count = 0; - let result = unsafe { BNProjectGetFiles(self.as_raw(), &mut count) }; + let result = unsafe { BNProjectGetFiles(self.handle.as_ptr(), &mut count) }; assert!(!result.is_null()); - Ok(unsafe { Array::new(result, count, ()) }) + unsafe { Array::new(result, count, ()) } } /// Retrieve a file in the project by unique `id` - pub fn file_by_id<S: BnStrCompatible>(&self, id: S) -> Option<ProjectFile> { + pub fn file_by_id<S: BnStrCompatible>(&self, id: S) -> Option<Ref<ProjectFile>> { let id_raw = id.into_bytes_with_nul(); - let id_ptr = id_raw.as_ref().as_ptr() as *const ffi::c_char; + let id_ptr = id_raw.as_ref().as_ptr() as *const c_char; - let result = unsafe { BNProjectGetFileById(self.as_raw(), id_ptr) }; + let result = unsafe { BNProjectGetFileById(self.handle.as_ptr(), id_ptr) }; let handle = NonNull::new(result)?; - Some(unsafe { ProjectFile::from_raw(handle) }) + Some(unsafe { ProjectFile::ref_from_raw(handle) }) } /// Retrieve a file in the project by the `path` on disk - pub fn file_by_path<S: BnStrCompatible>(&self, path: S) -> Option<ProjectFile> { + pub fn file_by_path<S: BnStrCompatible>(&self, path: S) -> Option<Ref<ProjectFile>> { let path_raw = path.into_bytes_with_nul(); - let path_ptr = path_raw.as_ref().as_ptr() as *const ffi::c_char; + let path_ptr = path_raw.as_ref().as_ptr() as *const c_char; - let result = unsafe { BNProjectGetFileByPathOnDisk(self.as_raw(), path_ptr) }; + let result = unsafe { BNProjectGetFileByPathOnDisk(self.handle.as_ptr(), path_ptr) }; let handle = NonNull::new(result)?; - Some(unsafe { ProjectFile::from_raw(handle) }) + Some(unsafe { ProjectFile::ref_from_raw(handle) }) } /// Delete a file from the project pub fn delete_file(&self, file: &ProjectFile) -> bool { - unsafe { BNProjectDeleteFile(self.as_raw(), file.as_raw()) } + unsafe { BNProjectDeleteFile(self.handle.as_ptr(), file.handle.as_ptr()) } } /// A context manager to speed up bulk project operations. @@ -733,44 +666,58 @@ impl Project { /// /// ```no_run /// # use binaryninja::project::Project; - /// # let project: Project = todo!(); + /// # let mut project: Project = todo!(); /// if let Ok(bulk) = project.bulk_operation() { /// for file in std::fs::read_dir("/bin/").unwrap().into_iter() { /// let file = file.unwrap(); /// let file_type = file.file_type().unwrap(); /// if file_type.is_file() && !file_type.is_symlink() { - /// bulk.create_file_from_path( - /// "/bin/", - /// None, - /// &file.file_name().to_string_lossy(), - /// "", - /// ).unwrap(); + /// bulk.create_file_from_path("/bin/", None, &file.file_name().to_string_lossy(), "") + /// .unwrap(); /// } /// } /// } /// ``` - // NOTE mut is used here, so only one lock can be aquired at once + // NOTE mut is used here, so only one lock can be acquired at once pub fn bulk_operation(&mut self) -> Result<ProjectBulkOperationLock, ()> { Ok(ProjectBulkOperationLock::lock(self)) } } -impl Drop for Project { - fn drop(&mut self) { - unsafe { BNFreeProject(self.as_raw()) } +impl Debug for Project { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("Project") + .field("id", &self.id()) + .field("name", &self.name()) + .field("description", &self.description()) + .finish() } } -impl Clone for Project { - fn clone(&self) -> Self { - unsafe { Self::from_raw(NonNull::new(BNNewProjectReference(self.as_raw())).unwrap()) } +impl ToOwned for Project { + type Owned = Ref<Self>; + + fn to_owned(&self) -> Self::Owned { + unsafe { RefCountable::inc_ref(self) } + } +} + +unsafe impl RefCountable for Project { + unsafe fn inc_ref(handle: &Self) -> Ref<Self> { + Ref::new(Self { + handle: NonNull::new(BNNewProjectReference(handle.handle.as_ptr())).unwrap(), + }) + } + + unsafe fn dec_ref(handle: &Self) { + BNFreeProject(handle.handle.as_ptr()); } } impl CoreArrayProvider for Project { type Raw = *mut BNProject; type Context = (); - type Wrapped<'a> = &'a Project; + type Wrapped<'a> = Guard<'a, Project>; } unsafe impl CoreArrayProviderInner for Project { @@ -778,18 +725,20 @@ unsafe impl CoreArrayProviderInner for Project { BNFreeProjectList(raw, count) } - unsafe fn wrap_raw<'a>(raw: &'a Self::Raw, _context: &'a Self::Context) -> Self::Wrapped<'a> { - Self::ref_from_raw(raw) + unsafe fn wrap_raw<'a>(raw: &'a Self::Raw, context: &'a Self::Context) -> Self::Wrapped<'a> { + let raw_ptr = NonNull::new(*raw).unwrap(); + Guard::new(Self::from_raw(raw_ptr), context) } } +// TODO: Rename to bulk operation guard? pub struct ProjectBulkOperationLock<'a> { lock: &'a mut Project, } impl<'a> ProjectBulkOperationLock<'a> { pub fn lock(project: &'a mut Project) -> Self { - unsafe { BNProjectBeginBulkOperation(project.as_raw()) }; + unsafe { BNProjectBeginBulkOperation(project.handle.as_ptr()) }; Self { lock: project } } @@ -807,282 +756,7 @@ impl std::ops::Deref for ProjectBulkOperationLock<'_> { impl Drop for ProjectBulkOperationLock<'_> { fn drop(&mut self) { - unsafe { BNProjectEndBulkOperation(self.lock.as_raw()) }; - } -} - -#[repr(transparent)] -pub struct ProjectFolder { - handle: NonNull<BNProjectFolder>, -} - -impl ProjectFolder { - pub(crate) unsafe fn from_raw(handle: NonNull<BNProjectFolder>) -> Self { - Self { handle } - } - - pub(crate) unsafe fn ref_from_raw(handle: &*mut BNProjectFolder) -> &Self { - debug_assert!(!handle.is_null()); - mem::transmute(handle) - } - - #[allow(clippy::mut_from_ref)] - pub(crate) unsafe fn as_raw(&self) -> &mut BNProjectFolder { - &mut *self.handle.as_ptr() - } - - /// Get the project that owns this folder - pub fn project(&self) -> Project { - unsafe { - Project::from_raw(NonNull::new(BNProjectFolderGetProject(self.as_raw())).unwrap()) - } - } - - /// Get the unique id of this folder - pub fn id(&self) -> BnString { - unsafe { BnString::from_raw(BNProjectFolderGetId(self.as_raw())) } - } - - /// Get the name of this folder - pub fn name(&self) -> BnString { - unsafe { BnString::from_raw(BNProjectFolderGetName(self.as_raw())) } - } - - /// Set the name of this folder - pub fn set_name<S: BnStrCompatible>(&self, value: S) -> bool { - let value_raw = value.into_bytes_with_nul(); - unsafe { - BNProjectFolderSetName( - self.as_raw(), - value_raw.as_ref().as_ptr() as *const ffi::c_char, - ) - } - } - - /// Get the description of this folder - pub fn description(&self) -> BnString { - unsafe { BnString::from_raw(BNProjectFolderGetDescription(self.as_raw())) } - } - - /// Set the description of this folder - pub fn set_description<S: BnStrCompatible>(&self, value: S) -> bool { - let value_raw = value.into_bytes_with_nul(); - unsafe { - BNProjectFolderSetDescription( - self.as_raw(), - value_raw.as_ref().as_ptr() as *const ffi::c_char, - ) - } - } - - /// Get the folder that contains this folder - pub fn parent(&self) -> Option<ProjectFolder> { - let result = unsafe { BNProjectFolderGetParent(self.as_raw()) }; - NonNull::new(result).map(|handle| unsafe { ProjectFolder::from_raw(handle) }) - } - - /// Set the folder that contains this folder - pub fn set_folder(&self, folder: Option<&ProjectFolder>) -> bool { - let folder_handle = folder - .map(|x| unsafe { x.as_raw() as *mut _ }) - .unwrap_or(null_mut()); - unsafe { BNProjectFolderSetParent(self.as_raw(), folder_handle) } - } - - /// Recursively export this folder to disk, returns `true' if the export succeeded - /// - /// * `dest` - Destination path for the exported contents - pub fn export<S: BnStrCompatible>(&self, dest: S) -> bool { - let dest_raw = dest.into_bytes_with_nul(); - unsafe { - BNProjectFolderExport( - self.as_raw(), - dest_raw.as_ref().as_ptr() as *const ffi::c_char, - null_mut(), - Some(cb_progress_func_nop), - ) - } - } - - /// Recursively export this folder to disk, returns `true' if the export succeeded - /// - /// * `dest` - Destination path for the exported contents - /// * `progress_func` - Progress function that will be called as contents are exporting - pub fn export_with_progress<S, F>(&self, dest: S, mut progress: F) -> bool - where - S: BnStrCompatible, - F: FnMut(usize, usize) -> bool, - { - let dest_raw = dest.into_bytes_with_nul(); - unsafe { - BNProjectFolderExport( - self.as_raw(), - dest_raw.as_ref().as_ptr() as *const ffi::c_char, - &mut progress as *mut _ as *mut ffi::c_void, - Some(cb_progress_func::<F>), - ) - } - } -} - -impl Drop for ProjectFolder { - fn drop(&mut self) { - unsafe { BNFreeProjectFolder(self.as_raw()) } - } -} - -impl Clone for ProjectFolder { - fn clone(&self) -> Self { - unsafe { Self::from_raw(NonNull::new(BNNewProjectFolderReference(self.as_raw())).unwrap()) } - } -} - -impl CoreArrayProvider for ProjectFolder { - type Raw = *mut BNProjectFolder; - type Context = (); - type Wrapped<'a> = &'a Self; -} - -unsafe impl CoreArrayProviderInner for ProjectFolder { - unsafe fn free(raw: *mut Self::Raw, count: usize, _context: &Self::Context) { - BNFreeProjectFolderList(raw, count) - } - - unsafe fn wrap_raw<'a>(raw: &'a Self::Raw, _context: &'a Self::Context) -> Self::Wrapped<'a> { - Self::ref_from_raw(raw) - } -} - -#[repr(transparent)] -pub struct ProjectFile { - handle: NonNull<BNProjectFile>, -} - -impl ProjectFile { - pub(crate) unsafe fn from_raw(handle: NonNull<BNProjectFile>) -> Self { - Self { handle } - } - - pub(crate) unsafe fn ref_from_raw(handle: &*mut BNProjectFile) -> &Self { - debug_assert!(!handle.is_null()); - mem::transmute(handle) - } - - #[allow(clippy::mut_from_ref)] - pub(crate) unsafe fn as_raw(&self) -> &mut BNProjectFile { - &mut *self.handle.as_ptr() - } - - /// Get the project that owns this file - pub fn project(&self) -> Project { - unsafe { Project::from_raw(NonNull::new(BNProjectFileGetProject(self.as_raw())).unwrap()) } - } - - /// Get the path on disk to this file's contents - pub fn path_on_disk(&self) -> BnString { - unsafe { BnString::from_raw(BNProjectFileGetPathOnDisk(self.as_raw())) } - } - - /// Check if this file's contents exist on disk - pub fn exists_on_disk(&self) -> bool { - unsafe { BNProjectFileExistsOnDisk(self.as_raw()) } - } - - /// Get the unique id of this file - pub fn id(&self) -> BnString { - unsafe { BnString::from_raw(BNProjectFileGetId(self.as_raw())) } - } - - /// Get the name of this file - pub fn name(&self) -> BnString { - unsafe { BnString::from_raw(BNProjectFileGetName(self.as_raw())) } - } - - /// Set the name of this file - pub fn set_name<S: BnStrCompatible>(&self, value: S) -> bool { - let value_raw = value.into_bytes_with_nul(); - unsafe { - BNProjectFileSetName( - self.as_raw(), - value_raw.as_ref().as_ptr() as *const ffi::c_char, - ) - } - } - - /// Get the description of this file - pub fn description(&self) -> BnString { - unsafe { BnString::from_raw(BNProjectFileGetDescription(self.as_raw())) } - } - - /// Set the description of this file - pub fn set_description<S: BnStrCompatible>(&self, value: S) -> bool { - let value_raw = value.into_bytes_with_nul(); - unsafe { - BNProjectFileSetDescription( - self.as_raw(), - value_raw.as_ref().as_ptr() as *const ffi::c_char, - ) - } - } - - /// Get the file creation time - pub fn creation_time(&self) -> SystemTime { - systime_from_bntime(unsafe { BNProjectFileGetCreationTimestamp(self.as_raw()) }).unwrap() - } - - /// Get the folder that contains this file - pub fn folder(&self) -> Option<ProjectFolder> { - let result = unsafe { BNProjectFileGetFolder(self.as_raw()) }; - NonNull::new(result).map(|handle| unsafe { ProjectFolder::from_raw(handle) }) - } - - /// Set the folder that contains this file - pub fn set_folder(&self, folder: Option<&ProjectFolder>) -> bool { - let folder_handle = folder - .map(|x| unsafe { x.as_raw() as *mut _ }) - .unwrap_or(null_mut()); - unsafe { BNProjectFileSetFolder(self.as_raw(), folder_handle) } - } - - /// Export this file to disk, `true' if the export succeeded - /// - /// * `dest` - Destination path for the exported contents - pub fn export<S: BnStrCompatible>(&self, dest: S) -> bool { - let dest_raw = dest.into_bytes_with_nul(); - unsafe { - BNProjectFileExport( - self.as_raw(), - dest_raw.as_ref().as_ptr() as *const ffi::c_char, - ) - } - } -} - -impl Drop for ProjectFile { - fn drop(&mut self) { - unsafe { BNFreeProjectFile(self.as_raw()) } - } -} - -impl Clone for ProjectFile { - fn clone(&self) -> Self { - unsafe { Self::from_raw(NonNull::new(BNNewProjectFileReference(self.as_raw())).unwrap()) } - } -} - -impl CoreArrayProvider for ProjectFile { - type Raw = *mut BNProjectFile; - type Context = (); - type Wrapped<'a> = &'a Self; -} - -unsafe impl CoreArrayProviderInner for ProjectFile { - unsafe fn free(raw: *mut Self::Raw, count: usize, _context: &Self::Context) { - BNFreeProjectFileList(raw, count) - } - - unsafe fn wrap_raw<'a>(raw: &'a Self::Raw, _context: &'a Self::Context) -> Self::Wrapped<'a> { - Self::ref_from_raw(raw) + unsafe { BNProjectEndBulkOperation(self.lock.handle.as_ptr()) }; } } @@ -1098,367 +772,3 @@ fn systime_to_bntime(time: SystemTime) -> Option<i64> { .try_into() .ok() } - -unsafe extern "C" fn cb_progress_func<F: FnMut(usize, usize) -> bool>( - ctxt: *mut ffi::c_void, - progress: usize, - total: usize, -) -> bool { - if ctxt.is_null() { - return true; - } - let closure: &mut F = mem::transmute(ctxt); - closure(progress, total) -} - -unsafe extern "C" fn cb_progress_func_nop( - _ctxt: *mut ffi::c_void, - _progress: usize, - _total: usize, -) -> bool { - true -} - -#[cfg(test)] -mod test { - use std::time::SystemTime; - - use crate::metadata::Metadata; - use crate::rc::Ref; - - use super::Project; - - fn unique_project(name: &str) -> String { - format!("{}/{}", std::env::temp_dir().to_str().unwrap(), name) - } - - #[test] - fn create_delete_empty() { - crate::headless::init(); - - let project_name = "create_delete_empty_project"; - let project_path = unique_project(project_name); - // create the project - let project = Project::create(&project_path, project_name); - project.open().unwrap(); - assert!(project.is_open()); - - // check project data - let project_path_received = project.path(); - assert_eq!(&project_path, project_path_received.as_str()); - let project_name_received = project.name(); - assert_eq!(project_name, project_name_received.as_str()); - - // close the project - project.close().unwrap(); - assert!(!project.is_open()); - drop(project); - - // delete the project - std::fs::remove_dir_all(project_path).unwrap(); - - crate::headless::shutdown(); - } - - #[test] - fn create_close_open_close() { - crate::headless::init(); - - let project_name = "create_close_open_close"; - let project_path = unique_project(project_name); - // create the project - let project = Project::create(&project_path, project_name); - project.open().unwrap(); - - // get the project id - let id = project.id(); - - // close the project - project.close().unwrap(); - drop(project); - - let project = Project::open_project(&project_path); - // assert same id - let new_id = project.id(); - assert_eq!(id, new_id); - - // close the project - project.close().unwrap(); - drop(project); - - // delete the project - std::fs::remove_dir_all(project_path).unwrap(); - - crate::headless::shutdown(); - } - - #[test] - fn modify_project() { - crate::headless::init(); - - let project_name = "modify_project"; - let project_path = unique_project(project_name); - // create the project - let project = Project::create(&project_path, project_name); - project.open().unwrap(); - - // get project id - let id = project.id(); - - // create data and verify that data was created - let data_1: Ref<Metadata> = "data1".into(); - let data_2: Ref<Metadata> = "data2".into(); - assert!(project.store_metadata("key", data_1.as_ref())); - assert_eq!( - data_1.get_string().unwrap(), - project.query_metadata("key").get_string().unwrap() - ); - project.remove_metadata("key"); - assert!(project.store_metadata("key", data_2.as_ref())); - assert_eq!( - data_2.get_string().unwrap(), - project.query_metadata("key").get_string().unwrap() - ); - - // create file that will be imported to the project - let tmp_folder_1_name = format!( - "tmp_folder_{}", - std::time::SystemTime::now() - .duration_since(std::time::UNIX_EPOCH) - .unwrap() - .as_millis() - ); - let tmp_folder_2_name = format!("{tmp_folder_1_name }_2"); - let tmp_folder_1 = format!( - "{}/{tmp_folder_1_name}", - std::env::temp_dir().to_str().unwrap() - ); - let tmp_folder_2 = format!( - "{}/{tmp_folder_2_name}", - std::env::temp_dir().to_str().unwrap() - ); - std::fs::create_dir(&tmp_folder_1).unwrap(); - std::fs::create_dir(&tmp_folder_2).unwrap(); - let input_file_1 = format!("{tmp_folder_2}/input_1"); - let input_file_2 = format!("{tmp_folder_2}/input_2"); - let input_file_1_data = b"input_1_data"; - let input_file_2_data = b"input_1_data"; - std::fs::write(&input_file_1, input_file_1_data).unwrap(); - std::fs::write(&input_file_2, input_file_2_data).unwrap(); - - // create and delete folders - let folder_1_desc = "desc_folder_1"; - let folder_1 = project - .create_folder(None, "folder_1", folder_1_desc) - .unwrap(); - let folder_2_desc = "AAAAA"; - let folder_2_id = "1717416787371"; - let folder_2 = unsafe { - project - .create_folder_unsafe(Some(&folder_1), "folder_2", folder_2_desc, folder_2_id) - .unwrap() - }; - let folder_3_desc = ""; // TODO "çàáÁÀ"; - let folder_3 = project - .create_folder_from_path(&tmp_folder_1, None, folder_3_desc) - .unwrap(); - let folder_4_desc = ""; - let _folder_4 = project - .create_folder_from_path_with_progress( - &tmp_folder_2, - Some(&folder_3), - folder_4_desc, - |_, _| true, - ) - .unwrap(); - let folder_5 = project - .create_folder(None, "deleted_folder", folder_4_desc) - .unwrap(); - - assert_eq!(project.folders().unwrap().len(), 5); - let last_folder = project.folder_by_id(folder_5.id()).unwrap(); - project.delete_folder(&last_folder).unwrap(); - assert_eq!(project.folders().unwrap().len(), 4); - drop(folder_5); - - // create, import and delete file - let file_1_data = b"data_1"; - let file_1_desc = "desc_file_1"; - let _file_1 = project - .create_file(file_1_data, None, "file_1", file_1_desc) - .unwrap(); - let file_2_data = b"data_2"; - let file_2_desc = "my desc"; - let file_2_id = "12334545"; - let _file_2 = unsafe { - project.create_file_unsafe( - file_2_data, - Some(&folder_2), - "file_2", - file_2_desc, - file_2_id, - SystemTime::UNIX_EPOCH, - ) - } - .unwrap(); - let file_3_data = b"data\x023"; - let file_3_desc = "!"; - let _file_3 = project - .create_file_with_progress( - file_3_data, - Some(&folder_1), - "file_3", - file_3_desc, - |_, _| true, - ) - .unwrap(); - let file_4_time = SystemTime::now(); - let file_4_data = b"data_4\x00_4"; - let file_4_desc = ""; - let file_4_id = "123123123"; - let _file_4 = unsafe { - project.create_file_with_progress_unsafe( - file_4_data, - Some(&folder_3), - "file_4", - file_4_desc, - file_4_id, - file_4_time, - |_, _| true, - ) - } - .unwrap(); - let file_5_desc = "desc"; - let _file_5 = project - .create_file_from_path(&input_file_1, None, "file_5", file_5_desc) - .unwrap(); - let file_6_time = SystemTime::now(); - let file_6_desc = "de"; - let file_6_id = "90218347"; - let _file_6 = unsafe { - project.create_file_from_path_unsafe( - &input_file_2, - Some(&folder_3), - "file_6", - file_6_desc, - file_6_id, - file_6_time, - ) - } - .unwrap(); - let file_7 = project - .create_file_from_path_with_progress( - &input_file_2, - Some(&folder_2), - "file_7", - "no", - |_, _| true, - ) - .unwrap(); - let file_8 = unsafe { - project.create_file_from_path_with_progress_unsafe( - &input_file_1, - None, - "file_7", - "no", - "92736528", - SystemTime::now(), - |_, _| true, - ) - } - .unwrap(); - - assert_eq!(project.files().unwrap().len(), 10); - let file_a = project.file_by_id(file_8.id()).unwrap(); - let file_b = project.file_by_path(file_7.path_on_disk()).unwrap(); - project.delete_file(&file_a); - project.delete_file(&file_b); - assert_eq!(project.files().unwrap().len(), 8); - drop(file_8); - drop(file_7); - - project.set_name("project_name"); - project.set_description("project_description"); - - // close the project - project.close().unwrap(); - drop(project); - drop(folder_1); - drop(folder_2); - drop(folder_3); - - // reopen the project and verify the information store on it - let project = Project::open_project(&project_path); - - // assert same id - assert_eq!(id, project.id()); - - // verify metadata - assert_eq!( - data_2.get_string().unwrap(), - project.query_metadata("key").get_string().unwrap() - ); - - // check folders - let folders = [ - ("folder_1", None), - ("folder_2", Some(folder_2_id)), - (&tmp_folder_1_name, None), - (&tmp_folder_2_name, None), - ]; - for folder in project.folders().unwrap().iter() { - let found = folders - .iter() - .find(|f| folder.name().as_str() == f.0) - .unwrap(); - if let Some(id) = found.1 { - assert_eq!(folder.id().as_str(), id); - } - } - - // check files - #[rustfmt::skip] - let files = [ - ("file_1", &file_1_data[..], None, None), - ("file_2", &file_2_data[..], Some(file_2_id), None), - ("file_3", &file_3_data[..], None, None), - ("file_4", &file_4_data[..], Some(file_4_id), Some(file_4_time)), - ("file_5", &input_file_1_data[..], None, None), - ("file_6", &input_file_2_data[..], Some(file_6_id), Some(file_6_time)), - ("input_1", &input_file_1_data[..], None, None), - ("input_2", &input_file_2_data[..], None, None), - ]; - for file in project.files().unwrap().iter() { - let found = files.iter().find(|f| file.name().as_str() == f.0).unwrap(); - if let Some(id) = found.2 { - assert_eq!(file.id().as_str(), id); - } - if let Some(time) = found.3 { - assert_eq!( - file.creation_time() - .duration_since(SystemTime::UNIX_EPOCH) - .unwrap() - .as_secs(), - time.duration_since(SystemTime::UNIX_EPOCH) - .unwrap() - .as_secs() - ); - } - let content = std::fs::read(file.path_on_disk().as_str()).unwrap(); - assert_eq!(content, found.1); - } - - assert_eq!(project.name().as_str(), "project_name"); - assert_eq!(project.description().as_str(), "project_description"); - - // close the project - project.close().unwrap(); - - // delete the project - std::fs::remove_dir_all(project_path).unwrap(); - std::fs::remove_dir_all(tmp_folder_1).unwrap(); - std::fs::remove_dir_all(tmp_folder_2).unwrap(); - - crate::headless::shutdown(); - } -} |
