binaryninja/
project.rs

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