binaryninja/
project.rs

1pub mod file;
2pub mod folder;
3
4use std::ffi::c_void;
5use std::fmt::Debug;
6use std::path::Path;
7use std::ptr::{null_mut, NonNull};
8use std::time::{Duration, SystemTime, UNIX_EPOCH};
9
10use binaryninjacore_sys::*;
11
12use crate::metadata::Metadata;
13use crate::progress::{NoProgressCallback, ProgressCallback};
14use crate::project::file::ProjectFile;
15use crate::project::folder::ProjectFolder;
16use crate::rc::{Array, CoreArrayProvider, CoreArrayProviderInner, Guard, Ref, RefCountable};
17use crate::string::{BnString, IntoCStr};
18
19pub struct Project {
20    pub(crate) handle: NonNull<BNProject>,
21}
22
23impl Project {
24    pub unsafe fn from_raw(handle: NonNull<BNProject>) -> Self {
25        Project { handle }
26    }
27
28    pub unsafe fn ref_from_raw(handle: NonNull<BNProject>) -> Ref<Self> {
29        Ref::new(Self { handle })
30    }
31
32    pub fn all_open() -> Array<Project> {
33        let mut count = 0;
34        let result = unsafe { BNGetOpenProjects(&mut count) };
35        assert!(!result.is_null());
36        unsafe { Array::new(result, count, ()) }
37    }
38
39    // TODO: Path here is actually local path?
40    /// Create a new project
41    ///
42    /// * `path` - Path to the project directory (.bnpr)
43    /// * `name` - Name of the new project
44    pub fn create(path: impl AsRef<Path>, name: &str) -> Option<Ref<Self>> {
45        let path_raw = path.as_ref().to_cstr();
46        let name_raw = name.to_cstr();
47        let handle = unsafe { BNCreateProject(path_raw.as_ptr(), name_raw.as_ptr()) };
48        NonNull::new(handle).map(|h| unsafe { Self::ref_from_raw(h) })
49    }
50
51    // TODO: Path here is actually local path?
52    /// Open an existing project
53    ///
54    /// * `path` - Path to the project directory (.bnpr) or project metadata file (.bnpm)
55    pub fn open_project(path: impl AsRef<Path>) -> Option<Ref<Self>> {
56        let path_raw = path.as_ref().to_cstr();
57        let handle = unsafe { BNOpenProject(path_raw.as_ptr()) };
58        NonNull::new(handle).map(|h| unsafe { Self::ref_from_raw(h) })
59    }
60
61    /// Check if the project is currently open
62    pub fn is_open(&self) -> bool {
63        unsafe { BNProjectIsOpen(self.handle.as_ptr()) }
64    }
65
66    /// Open a closed project
67    pub fn open(&self) -> Result<(), ()> {
68        if unsafe { BNProjectOpen(self.handle.as_ptr()) } {
69            Ok(())
70        } else {
71            Err(())
72        }
73    }
74
75    /// Close a open project
76    pub fn close(&self) -> Result<(), ()> {
77        if unsafe { BNProjectClose(self.handle.as_ptr()) } {
78            Ok(())
79        } else {
80            Err(())
81        }
82    }
83
84    /// Get the unique id of this project
85    pub fn id(&self) -> String {
86        unsafe { BnString::into_string(BNProjectGetId(self.handle.as_ptr())) }
87    }
88
89    /// Get the path of the project
90    pub fn path(&self) -> String {
91        unsafe { BnString::into_string(BNProjectGetPath(self.handle.as_ptr())) }
92    }
93
94    /// Get the name of the project
95    pub fn name(&self) -> String {
96        unsafe { BnString::into_string(BNProjectGetName(self.handle.as_ptr())) }
97    }
98
99    /// Set the name of the project
100    pub fn set_name(&self, value: &str) -> bool {
101        let value = value.to_cstr();
102        unsafe { BNProjectSetName(self.handle.as_ptr(), value.as_ptr()) }
103    }
104
105    /// Get the description of the project
106    pub fn description(&self) -> String {
107        unsafe { BnString::into_string(BNProjectGetDescription(self.handle.as_ptr())) }
108    }
109
110    /// Set the description of the project
111    pub fn set_description(&self, value: &str) -> bool {
112        let value = value.to_cstr();
113        unsafe { BNProjectSetDescription(self.handle.as_ptr(), value.as_ptr()) }
114    }
115
116    /// Retrieves metadata stored under a key from the project
117    pub fn query_metadata(&self, key: &str) -> Ref<Metadata> {
118        let key = key.to_cstr();
119        let result = unsafe { BNProjectQueryMetadata(self.handle.as_ptr(), key.as_ptr()) };
120        unsafe { Metadata::ref_from_raw(result) }
121    }
122
123    /// Stores metadata within the project,
124    ///
125    /// * `key` - Key under which to store the Metadata object
126    /// * `value` - Object to store
127    pub fn store_metadata(&self, key: &str, value: &Metadata) -> bool {
128        let key_raw = key.to_cstr();
129        unsafe { BNProjectStoreMetadata(self.handle.as_ptr(), key_raw.as_ptr(), value.handle) }
130    }
131
132    /// Removes the metadata associated with this `key` from the project
133    pub fn remove_metadata(&self, key: &str) -> bool {
134        let key_raw = key.to_cstr();
135        unsafe { BNProjectRemoveMetadata(self.handle.as_ptr(), key_raw.as_ptr()) }
136    }
137
138    pub fn push_folder(&self, file: &ProjectFolder) -> bool {
139        unsafe { BNProjectPushFolder(self.handle.as_ptr(), file.handle.as_ptr()) }
140    }
141
142    /// Recursively create files and folders in the project from a path on disk
143    ///
144    /// * `path` - Path to folder on disk
145    /// * `parent` - Parent folder in the project that will contain the new contents
146    /// * `description` - Description for created root folder
147    pub fn create_folder_from_path(
148        &self,
149        path: impl AsRef<Path>,
150        parent: Option<&ProjectFolder>,
151        description: &str,
152    ) -> Result<Ref<ProjectFolder>, ()> {
153        self.create_folder_from_path_with_progress(path, parent, description, NoProgressCallback)
154    }
155
156    /// Recursively create files and folders in the project from a path on disk
157    ///
158    /// * `path` - Path to folder on disk
159    /// * `parent` - Parent folder in the project that will contain the new contents
160    /// * `description` - Description for created root folder
161    /// * `progress` - [`ProgressCallback`] that will be called as the [`ProjectFolder`] is being created
162    pub fn create_folder_from_path_with_progress<PC>(
163        &self,
164        path: impl AsRef<Path>,
165        parent: Option<&ProjectFolder>,
166        description: &str,
167        mut progress: PC,
168    ) -> Result<Ref<ProjectFolder>, ()>
169    where
170        PC: ProgressCallback,
171    {
172        let path_raw = path.as_ref().to_cstr();
173        let description_raw = description.to_cstr();
174        let parent_ptr = parent.map(|p| p.handle.as_ptr()).unwrap_or(null_mut());
175
176        unsafe {
177            let result = BNProjectCreateFolderFromPath(
178                self.handle.as_ptr(),
179                path_raw.as_ptr(),
180                parent_ptr,
181                description_raw.as_ptr(),
182                &mut progress as *mut PC as *mut c_void,
183                Some(PC::cb_progress_callback),
184            );
185            Ok(ProjectFolder::ref_from_raw(NonNull::new(result).ok_or(())?))
186        }
187    }
188
189    /// Recursively create files and folders in the project from a path on disk
190    ///
191    /// * `parent` - Parent folder in the project that will contain the new folder
192    /// * `name` - Name for the created folder
193    /// * `description` - Description for created folder
194    pub fn create_folder(
195        &self,
196        parent: Option<&ProjectFolder>,
197        name: &str,
198        description: &str,
199    ) -> Result<Ref<ProjectFolder>, ()> {
200        let name_raw = name.to_cstr();
201        let description_raw = description.to_cstr();
202        let parent_ptr = parent.map(|p| p.handle.as_ptr()).unwrap_or(null_mut());
203        unsafe {
204            let result = BNProjectCreateFolder(
205                self.handle.as_ptr(),
206                parent_ptr,
207                name_raw.as_ptr(),
208                description_raw.as_ptr(),
209            );
210            Ok(ProjectFolder::ref_from_raw(NonNull::new(result).ok_or(())?))
211        }
212    }
213
214    /// Recursively create files and folders in the project from a path on disk
215    ///
216    /// * `parent` - Parent folder in the project that will contain the new folder
217    /// * `name` - Name for the created folder
218    /// * `description` - Description for created folder
219    /// * `id` - id unique ID
220    pub unsafe fn create_folder_unsafe(
221        &self,
222        parent: Option<&ProjectFolder>,
223        name: &str,
224        description: &str,
225        id: &str,
226    ) -> Result<Ref<ProjectFolder>, ()> {
227        let name_raw = name.to_cstr();
228        let description_raw = description.to_cstr();
229        let parent_ptr = parent.map(|p| p.handle.as_ptr()).unwrap_or(null_mut());
230        let id_raw = id.to_cstr();
231        unsafe {
232            let result = BNProjectCreateFolderUnsafe(
233                self.handle.as_ptr(),
234                parent_ptr,
235                name_raw.as_ptr(),
236                description_raw.as_ptr(),
237                id_raw.as_ptr(),
238            );
239            Ok(ProjectFolder::ref_from_raw(NonNull::new(result).ok_or(())?))
240        }
241    }
242
243    /// Get a list of folders in the project
244    pub fn folders(&self) -> Array<ProjectFolder> {
245        let mut count = 0;
246        let result = unsafe { BNProjectGetFolders(self.handle.as_ptr(), &mut count) };
247        assert!(!result.is_null());
248        unsafe { Array::new(result, count, ()) }
249    }
250
251    /// Retrieve a folder in the project by unique folder `id`
252    pub fn folder_by_id(&self, id: &str) -> Option<Ref<ProjectFolder>> {
253        let raw_id = id.to_cstr();
254        let result = unsafe { BNProjectGetFolderById(self.handle.as_ptr(), raw_id.as_ptr()) };
255        let handle = NonNull::new(result)?;
256        Some(unsafe { ProjectFolder::ref_from_raw(handle) })
257    }
258
259    /// Recursively delete a [`ProjectFolder`] from the [`Project`].
260    ///
261    /// * `folder` - [`ProjectFolder`] to delete recursively
262    pub fn delete_folder(&self, folder: &ProjectFolder) -> Result<(), ()> {
263        self.delete_folder_with_progress(folder, NoProgressCallback)
264    }
265
266    /// Recursively delete a [`ProjectFolder`] from the [`Project`].
267    ///
268    /// * `folder` - [`ProjectFolder`] to delete recursively
269    /// * `progress` - [`ProgressCallback`] that will be called as objects get deleted
270    pub fn delete_folder_with_progress<PC: ProgressCallback>(
271        &self,
272        folder: &ProjectFolder,
273        mut progress: PC,
274    ) -> Result<(), ()> {
275        let result = unsafe {
276            BNProjectDeleteFolder(
277                self.handle.as_ptr(),
278                folder.handle.as_ptr(),
279                &mut progress as *mut PC as *mut c_void,
280                Some(PC::cb_progress_callback),
281            )
282        };
283
284        if result {
285            Ok(())
286        } else {
287            Err(())
288        }
289    }
290
291    pub fn push_file(&self, file: &ProjectFile) -> bool {
292        unsafe { BNProjectPushFile(self.handle.as_ptr(), file.handle.as_ptr()) }
293    }
294
295    /// Create a file in the project from a path on disk
296    ///
297    /// * `path` - Path on disk
298    /// * `folder` - Folder to place the created file in
299    /// * `name` - Name to assign to the created file
300    /// * `description` - Description to assign to the created file
301    pub fn create_file_from_path(
302        &self,
303        path: impl AsRef<Path>,
304        folder: Option<&ProjectFolder>,
305        name: &str,
306        description: &str,
307    ) -> Result<Ref<ProjectFile>, ()> {
308        self.create_file_from_path_with_progress(
309            path,
310            folder,
311            name,
312            description,
313            NoProgressCallback,
314        )
315    }
316
317    /// Create a file in the project from a path on disk
318    ///
319    /// * `path` - Path on disk
320    /// * `folder` - Folder to place the created file in
321    /// * `name` - Name to assign to the created file
322    /// * `description` - Description to assign to the created file
323    /// * `progress` - [`ProgressCallback`] that will be called as the [`ProjectFile`] is being added
324    pub fn create_file_from_path_with_progress<PC>(
325        &self,
326        path: impl AsRef<Path>,
327        folder: Option<&ProjectFolder>,
328        name: &str,
329        description: &str,
330        mut progress: PC,
331    ) -> Result<Ref<ProjectFile>, ()>
332    where
333        PC: ProgressCallback,
334    {
335        let path_raw = path.as_ref().to_cstr();
336        let name_raw = name.to_cstr();
337        let description_raw = description.to_cstr();
338        let folder_ptr = folder.map(|p| p.handle.as_ptr()).unwrap_or(null_mut());
339
340        unsafe {
341            let result = BNProjectCreateFileFromPath(
342                self.handle.as_ptr(),
343                path_raw.as_ptr(),
344                folder_ptr,
345                name_raw.as_ptr(),
346                description_raw.as_ptr(),
347                &mut progress as *mut PC as *mut c_void,
348                Some(PC::cb_progress_callback),
349            );
350            Ok(ProjectFile::ref_from_raw(NonNull::new(result).ok_or(())?))
351        }
352    }
353
354    /// Create a file in the project from a path on disk
355    ///
356    /// * `path` - Path on disk
357    /// * `folder` - Folder to place the created file in
358    /// * `name` - Name to assign to the created file
359    /// * `description` - Description to assign to the created file
360    /// * `id` - id unique ID
361    /// * `creation_time` - Creation time of the file
362    pub unsafe fn create_file_from_path_unsafe(
363        &self,
364        path: impl AsRef<Path>,
365        folder: Option<&ProjectFolder>,
366        name: &str,
367        description: &str,
368        id: &str,
369        creation_time: SystemTime,
370    ) -> Result<Ref<ProjectFile>, ()> {
371        self.create_file_from_path_unsafe_with_progress(
372            path,
373            folder,
374            name,
375            description,
376            id,
377            creation_time,
378            NoProgressCallback,
379        )
380    }
381
382    /// Create a file in the project from a path on disk
383    ///
384    /// * `path` - Path on disk
385    /// * `folder` - Folder to place the created file in
386    /// * `name` - Name to assign to the created file
387    /// * `description` - Description to assign to the created file
388    /// * `id` - id unique ID
389    /// * `creation_time` - Creation time of the file
390    /// * `progress` - [`ProgressCallback`] that will be called as the [`ProjectFile`] is being created
391    #[allow(clippy::too_many_arguments)]
392    pub unsafe fn create_file_from_path_unsafe_with_progress<PC>(
393        &self,
394        path: impl AsRef<Path>,
395        folder: Option<&ProjectFolder>,
396        name: &str,
397        description: &str,
398        id: &str,
399        creation_time: SystemTime,
400        mut progress: PC,
401    ) -> Result<Ref<ProjectFile>, ()>
402    where
403        PC: ProgressCallback,
404    {
405        let path_raw = path.as_ref().to_cstr();
406        let name_raw = name.to_cstr();
407        let description_raw = description.to_cstr();
408        let id_raw = id.to_cstr();
409        let folder_ptr = folder.map(|p| p.handle.as_ptr()).unwrap_or(null_mut());
410
411        unsafe {
412            let result = BNProjectCreateFileFromPathUnsafe(
413                self.handle.as_ptr(),
414                path_raw.as_ptr(),
415                folder_ptr,
416                name_raw.as_ptr(),
417                description_raw.as_ptr(),
418                id_raw.as_ptr(),
419                systime_to_bntime(creation_time).unwrap(),
420                &mut progress as *mut PC as *mut c_void,
421                Some(PC::cb_progress_callback),
422            );
423            Ok(ProjectFile::ref_from_raw(NonNull::new(result).ok_or(())?))
424        }
425    }
426
427    /// Create a file in the project
428    ///
429    /// * `contents` - Bytes of the file that will be created
430    /// * `folder` - Folder to place the created file in
431    /// * `name` - Name to assign to the created file
432    /// * `description` - Description to assign to the created file
433    pub fn create_file(
434        &self,
435        contents: &[u8],
436        folder: Option<&ProjectFolder>,
437        name: &str,
438        description: &str,
439    ) -> Result<Ref<ProjectFile>, ()> {
440        self.create_file_with_progress(contents, folder, name, description, NoProgressCallback)
441    }
442
443    /// Create a file in the project
444    ///
445    /// * `contents` - Bytes of the file that will be created
446    /// * `folder` - Folder to place the created file in
447    /// * `name` - Name to assign to the created file
448    /// * `description` - Description to assign to the created file
449    /// * `progress` - [`ProgressCallback`] that will be called as the [`ProjectFile`] is being created
450    pub fn create_file_with_progress<PC>(
451        &self,
452        contents: &[u8],
453        folder: Option<&ProjectFolder>,
454        name: &str,
455        description: &str,
456        mut progress: PC,
457    ) -> Result<Ref<ProjectFile>, ()>
458    where
459        PC: ProgressCallback,
460    {
461        let name_raw = name.to_cstr();
462        let description_raw = description.to_cstr();
463        let folder_ptr = folder.map(|p| p.handle.as_ptr()).unwrap_or(null_mut());
464
465        unsafe {
466            let result = BNProjectCreateFile(
467                self.handle.as_ptr(),
468                contents.as_ptr(),
469                contents.len(),
470                folder_ptr,
471                name_raw.as_ptr(),
472                description_raw.as_ptr(),
473                &mut progress as *mut PC as *mut c_void,
474                Some(PC::cb_progress_callback),
475            );
476            Ok(ProjectFile::ref_from_raw(NonNull::new(result).ok_or(())?))
477        }
478    }
479
480    /// Create a file in the project
481    ///
482    /// * `contents` - Bytes of the file that will be created
483    /// * `folder` - Folder to place the created file in
484    /// * `name` - Name to assign to the created file
485    /// * `description` - Description to assign to the created file
486    /// * `id` - id unique ID
487    /// * `creation_time` - Creation time of the file
488    pub unsafe fn create_file_unsafe(
489        &self,
490        contents: &[u8],
491        folder: Option<&ProjectFolder>,
492        name: &str,
493        description: &str,
494        id: &str,
495        creation_time: SystemTime,
496    ) -> Result<Ref<ProjectFile>, ()> {
497        self.create_file_unsafe_with_progress(
498            contents,
499            folder,
500            name,
501            description,
502            id,
503            creation_time,
504            NoProgressCallback,
505        )
506    }
507
508    /// Create a file in the project
509    ///
510    /// * `contents` - Bytes of the file that will be created
511    /// * `folder` - Folder to place the created file in
512    /// * `name` - Name to assign to the created file
513    /// * `description` - Description to assign to the created file
514    /// * `id` - id unique ID
515    /// * `creation_time` - Creation time of the file
516    /// * `progress` - [`ProgressCallback`] that will be called as the [`ProjectFile`] is being created
517    #[allow(clippy::too_many_arguments)]
518    pub unsafe fn create_file_unsafe_with_progress<PC>(
519        &self,
520        contents: &[u8],
521        folder: Option<&ProjectFolder>,
522        name: &str,
523        description: &str,
524        id: &str,
525        creation_time: SystemTime,
526        mut progress: PC,
527    ) -> Result<Ref<ProjectFile>, ()>
528    where
529        PC: ProgressCallback,
530    {
531        let name_raw = name.to_cstr();
532        let description_raw = description.to_cstr();
533        let id_raw = id.to_cstr();
534        let folder_ptr = folder.map(|p| p.handle.as_ptr()).unwrap_or(null_mut());
535
536        unsafe {
537            let result = BNProjectCreateFileUnsafe(
538                self.handle.as_ptr(),
539                contents.as_ptr(),
540                contents.len(),
541                folder_ptr,
542                name_raw.as_ptr(),
543                description_raw.as_ptr(),
544                id_raw.as_ptr(),
545                systime_to_bntime(creation_time).unwrap(),
546                &mut progress as *mut PC as *mut c_void,
547                Some(PC::cb_progress_callback),
548            );
549            Ok(ProjectFile::ref_from_raw(NonNull::new(result).ok_or(())?))
550        }
551    }
552
553    /// Get a list of files in the project
554    pub fn files(&self) -> Array<ProjectFile> {
555        let mut count = 0;
556        let result = unsafe { BNProjectGetFiles(self.handle.as_ptr(), &mut count) };
557        assert!(!result.is_null());
558        unsafe { Array::new(result, count, ()) }
559    }
560
561    /// Retrieve a file in the project by unique `id`
562    pub fn file_by_id(&self, id: &str) -> Option<Ref<ProjectFile>> {
563        let raw_id = id.to_cstr();
564        let result = unsafe { BNProjectGetFileById(self.handle.as_ptr(), raw_id.as_ptr()) };
565        let handle = NonNull::new(result)?;
566        Some(unsafe { ProjectFile::ref_from_raw(handle) })
567    }
568
569    /// Retrieve a file in the project by the `path` on disk
570    pub fn file_by_path(&self, path: &Path) -> Option<Ref<ProjectFile>> {
571        let path_raw = path.to_cstr();
572        let result =
573            unsafe { BNProjectGetFileByPathOnDisk(self.handle.as_ptr(), path_raw.as_ptr()) };
574        let handle = NonNull::new(result)?;
575        Some(unsafe { ProjectFile::ref_from_raw(handle) })
576    }
577
578    /// Retrieve a list of files in the project by the `path` inside the project.
579    ///
580    /// Because a [`ProjectFile`] name is not unique, this returns a list instead of a single [`ProjectFile`].
581    pub fn files_by_project_path(&self, path: &Path) -> Array<ProjectFile> {
582        let path_raw = path.to_cstr();
583        let mut count = 0;
584        let result = unsafe {
585            BNProjectGetFilesByPathInProject(self.handle.as_ptr(), path_raw.as_ptr(), &mut count)
586        };
587        unsafe { Array::new(result, count, ()) }
588    }
589
590    /// Retrieve a list of files in the project in a given folder.
591    pub fn files_in_folder(&self, folder: Option<&ProjectFolder>) -> Array<ProjectFile> {
592        let folder_ptr = folder.map(|f| f.handle.as_ptr()).unwrap_or(null_mut());
593        let mut count = 0;
594        let result =
595            unsafe { BNProjectGetFilesInFolder(self.handle.as_ptr(), folder_ptr, &mut count) };
596        unsafe { Array::new(result, count, ()) }
597    }
598
599    /// Delete a file from the project
600    pub fn delete_file(&self, file: &ProjectFile) -> bool {
601        unsafe { BNProjectDeleteFile(self.handle.as_ptr(), file.handle.as_ptr()) }
602    }
603
604    /// A context manager to speed up bulk project operations.
605    /// Project modifications are synced to disk in chunks,
606    /// and the project on disk vs in memory may not agree on state
607    /// if an exception occurs while a bulk operation is happening.
608    ///
609    /// ```no_run
610    /// # use binaryninja::project::Project;
611    /// # let mut project: Project = todo!();
612    /// if let Ok(bulk) = project.bulk_operation() {
613    ///     for file in std::fs::read_dir("/bin/").unwrap().into_iter() {
614    ///         let file = file.unwrap();
615    ///         let file_type = file.file_type().unwrap();
616    ///         if file_type.is_file() && !file_type.is_symlink() {
617    ///             bulk.create_file_from_path("/bin/", None, &file.file_name().to_string_lossy(), "")
618    ///                 .unwrap();
619    ///         }
620    ///     }
621    /// }
622    /// ```
623    // NOTE mut is used here, so only one lock can be acquired at once
624    pub fn bulk_operation(&mut self) -> Result<ProjectBulkOperationLock<'_>, ()> {
625        Ok(ProjectBulkOperationLock::lock(self))
626    }
627}
628
629impl Debug for Project {
630    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
631        f.debug_struct("Project")
632            .field("id", &self.id())
633            .field("name", &self.name())
634            .field("description", &self.description())
635            .finish()
636    }
637}
638
639impl ToOwned for Project {
640    type Owned = Ref<Self>;
641
642    fn to_owned(&self) -> Self::Owned {
643        unsafe { RefCountable::inc_ref(self) }
644    }
645}
646
647unsafe impl RefCountable for Project {
648    unsafe fn inc_ref(handle: &Self) -> Ref<Self> {
649        Ref::new(Self {
650            handle: NonNull::new(BNNewProjectReference(handle.handle.as_ptr())).unwrap(),
651        })
652    }
653
654    unsafe fn dec_ref(handle: &Self) {
655        BNFreeProject(handle.handle.as_ptr());
656    }
657}
658
659unsafe impl Send for Project {}
660unsafe impl Sync for Project {}
661
662impl CoreArrayProvider for Project {
663    type Raw = *mut BNProject;
664    type Context = ();
665    type Wrapped<'a> = Guard<'a, Project>;
666}
667
668unsafe impl CoreArrayProviderInner for Project {
669    unsafe fn free(raw: *mut Self::Raw, count: usize, _context: &Self::Context) {
670        BNFreeProjectList(raw, count)
671    }
672
673    unsafe fn wrap_raw<'a>(raw: &'a Self::Raw, context: &'a Self::Context) -> Self::Wrapped<'a> {
674        let raw_ptr = NonNull::new(*raw).unwrap();
675        Guard::new(Self::from_raw(raw_ptr), context)
676    }
677}
678
679// TODO: Rename to bulk operation guard?
680pub struct ProjectBulkOperationLock<'a> {
681    lock: &'a mut Project,
682}
683
684impl<'a> ProjectBulkOperationLock<'a> {
685    pub fn lock(project: &'a mut Project) -> Self {
686        unsafe { BNProjectBeginBulkOperation(project.handle.as_ptr()) };
687        Self { lock: project }
688    }
689
690    pub fn unlock(self) {
691        // NOTE does nothing, just drop self
692    }
693}
694
695impl std::ops::Deref for ProjectBulkOperationLock<'_> {
696    type Target = Project;
697    fn deref(&self) -> &Self::Target {
698        self.lock
699    }
700}
701
702impl Drop for ProjectBulkOperationLock<'_> {
703    fn drop(&mut self) {
704        unsafe { BNProjectEndBulkOperation(self.lock.handle.as_ptr()) };
705    }
706}
707
708fn systime_from_bntime(time: i64) -> Option<SystemTime> {
709    let m = Duration::from_secs(time.try_into().ok()?);
710    Some(UNIX_EPOCH + m)
711}
712
713fn systime_to_bntime(time: SystemTime) -> Option<i64> {
714    time.duration_since(UNIX_EPOCH)
715        .ok()?
716        .as_secs()
717        .try_into()
718        .ok()
719}