1use std::ffi::c_void;
2use std::fmt::{Debug, Formatter};
3use std::path::Path;
4use std::ptr::NonNull;
5use std::time::SystemTime;
6
7use binaryninjacore_sys::*;
8
9use super::{
10 sync, DatabaseConflictHandler, DatabaseConflictHandlerFail, NameChangeset, NoNameChangeset,
11 Remote, RemoteFolder, RemoteProject, RemoteSnapshot,
12};
13
14use crate::binary_view::BinaryView;
15use crate::database::Database;
16use crate::file_metadata::FileMetadata;
17use crate::progress::{NoProgressCallback, ProgressCallback, SplitProgressBuilder};
18use crate::project::file::ProjectFile;
19use crate::rc::{Array, CoreArrayProvider, CoreArrayProviderInner, Guard, Ref, RefCountable};
20use crate::string::{BnString, IntoCStr};
21
22pub type RemoteFileType = BNRemoteFileType;
23
24#[repr(transparent)]
26pub struct RemoteFile {
27 pub(crate) handle: NonNull<BNRemoteFile>,
28}
29
30impl RemoteFile {
31 pub(crate) unsafe fn from_raw(handle: NonNull<BNRemoteFile>) -> Self {
32 Self { handle }
33 }
34
35 pub(crate) unsafe fn ref_from_raw(handle: NonNull<BNRemoteFile>) -> Ref<Self> {
36 Ref::new(Self { handle })
37 }
38
39 pub fn get_for_local_database(database: &Database) -> Result<Option<Ref<RemoteFile>>, ()> {
43 if !sync::pull_files(database)? {
45 return Ok(None);
46 }
47 sync::get_remote_file_for_local_database(database)
48 }
49
50 pub fn get_for_binary_view(bv: &BinaryView) -> Result<Option<Ref<RemoteFile>>, ()> {
53 let file = bv.file();
54 let Some(database) = file.database() else {
55 return Ok(None);
56 };
57 RemoteFile::get_for_local_database(&database)
58 }
59
60 pub fn core_file(&self) -> Result<Ref<ProjectFile>, ()> {
61 let result = unsafe { BNRemoteFileGetCoreFile(self.handle.as_ptr()) };
62 NonNull::new(result)
63 .map(|handle| unsafe { ProjectFile::ref_from_raw(handle) })
64 .ok_or(())
65 }
66
67 pub fn project(&self) -> Result<Ref<RemoteProject>, ()> {
68 let result = unsafe { BNRemoteFileGetProject(self.handle.as_ptr()) };
69 NonNull::new(result)
70 .map(|handle| unsafe { RemoteProject::ref_from_raw(handle) })
71 .ok_or(())
72 }
73
74 pub fn remote(&self) -> Result<Ref<Remote>, ()> {
75 let result = unsafe { BNRemoteFileGetRemote(self.handle.as_ptr()) };
76 NonNull::new(result)
77 .map(|handle| unsafe { Remote::ref_from_raw(handle) })
78 .ok_or(())
79 }
80
81 pub fn folder(&self) -> Result<Option<Ref<RemoteFolder>>, ()> {
83 let project = self.project()?;
84 if !project.has_pulled_folders() {
85 project.pull_folders()?;
86 }
87 let result = unsafe { BNRemoteFileGetFolder(self.handle.as_ptr()) };
88 Ok(NonNull::new(result).map(|handle| unsafe { RemoteFolder::ref_from_raw(handle) }))
89 }
90
91 pub fn set_folder(&self, folder: Option<&RemoteFolder>) -> Result<(), ()> {
93 let folder_raw = folder.map_or(std::ptr::null_mut(), |f| f.handle.as_ptr());
94 let success = unsafe { BNRemoteFileSetFolder(self.handle.as_ptr(), folder_raw) };
95 success.then_some(()).ok_or(())
96 }
97
98 pub fn set_metadata(&self, folder: &str) -> Result<(), ()> {
99 let folder_raw = folder.to_cstr();
100 let success = unsafe { BNRemoteFileSetMetadata(self.handle.as_ptr(), folder_raw.as_ptr()) };
101 success.then_some(()).ok_or(())
102 }
103
104 pub fn url(&self) -> String {
106 let result = unsafe { BNRemoteFileGetUrl(self.handle.as_ptr()) };
107 assert!(!result.is_null());
108 unsafe { BnString::into_string(result) }
109 }
110
111 pub fn chat_log_url(&self) -> String {
113 let result = unsafe { BNRemoteFileGetChatLogUrl(self.handle.as_ptr()) };
114 assert!(!result.is_null());
115 unsafe { BnString::into_string(result) }
116 }
117
118 pub fn user_positions_url(&self) -> String {
119 let result = unsafe { BNRemoteFileGetUserPositionsUrl(self.handle.as_ptr()) };
120 assert!(!result.is_null());
121 unsafe { BnString::into_string(result) }
122 }
123
124 pub fn id(&self) -> String {
126 let result = unsafe { BNRemoteFileGetId(self.handle.as_ptr()) };
127 assert!(!result.is_null());
128 unsafe { BnString::into_string(result) }
129 }
130
131 pub fn file_type(&self) -> RemoteFileType {
134 unsafe { BNRemoteFileGetType(self.handle.as_ptr()) }
135 }
136
137 pub fn created(&self) -> SystemTime {
139 let result = unsafe { BNRemoteFileGetCreated(self.handle.as_ptr()) };
140 crate::ffi::time_from_bn(result.try_into().unwrap())
141 }
142
143 pub fn created_by(&self) -> String {
144 let result = unsafe { BNRemoteFileGetCreatedBy(self.handle.as_ptr()) };
145 assert!(!result.is_null());
146 unsafe { BnString::into_string(result) }
147 }
148
149 pub fn last_modified(&self) -> SystemTime {
151 let result = unsafe { BNRemoteFileGetLastModified(self.handle.as_ptr()) };
152 crate::ffi::time_from_bn(result.try_into().unwrap())
153 }
154
155 pub fn last_snapshot(&self) -> SystemTime {
157 let result = unsafe { BNRemoteFileGetLastSnapshot(self.handle.as_ptr()) };
158 crate::ffi::time_from_bn(result.try_into().unwrap())
159 }
160
161 pub fn last_snapshot_by(&self) -> String {
163 let result = unsafe { BNRemoteFileGetLastSnapshotBy(self.handle.as_ptr()) };
164 assert!(!result.is_null());
165 unsafe { BnString::into_string(result) }
166 }
167
168 pub fn last_snapshot_name(&self) -> String {
169 let result = unsafe { BNRemoteFileGetLastSnapshotName(self.handle.as_ptr()) };
170 assert!(!result.is_null());
171 unsafe { BnString::into_string(result) }
172 }
173
174 pub fn hash(&self) -> String {
176 let result = unsafe { BNRemoteFileGetHash(self.handle.as_ptr()) };
177 assert!(!result.is_null());
178 unsafe { BnString::into_string(result) }
179 }
180
181 pub fn name(&self) -> String {
183 let result = unsafe { BNRemoteFileGetName(self.handle.as_ptr()) };
184 assert!(!result.is_null());
185 unsafe { BnString::into_string(result) }
186 }
187
188 pub fn set_name(&self, name: &str) -> Result<(), ()> {
190 let name = name.to_cstr();
191 let success = unsafe { BNRemoteFileSetName(self.handle.as_ptr(), name.as_ptr()) };
192 success.then_some(()).ok_or(())
193 }
194
195 pub fn description(&self) -> String {
197 let result = unsafe { BNRemoteFileGetDescription(self.handle.as_ptr()) };
198 assert!(!result.is_null());
199 unsafe { BnString::into_string(result) }
200 }
201
202 pub fn set_description(&self, description: &str) -> Result<(), ()> {
204 let description = description.to_cstr();
205 let success =
206 unsafe { BNRemoteFileSetDescription(self.handle.as_ptr(), description.as_ptr()) };
207 success.then_some(()).ok_or(())
208 }
209
210 pub fn metadata(&self) -> String {
211 let result = unsafe { BNRemoteFileGetMetadata(self.handle.as_ptr()) };
212 assert!(!result.is_null());
213 unsafe { BnString::into_string(result) }
214 }
215
216 pub fn size(&self) -> u64 {
218 unsafe { BNRemoteFileGetSize(self.handle.as_ptr()) }
219 }
220
221 pub fn default_path(&self) -> String {
225 let result = unsafe { BNCollaborationDefaultFilePath(self.handle.as_ptr()) };
226 assert!(!result.is_null());
227 unsafe { BnString::into_string(result) }
228 }
229
230 pub fn has_pulled_snapshots(&self) -> bool {
232 unsafe { BNRemoteFileHasPulledSnapshots(self.handle.as_ptr()) }
233 }
234
235 pub fn snapshots(&self) -> Result<Array<RemoteSnapshot>, ()> {
239 if !self.has_pulled_snapshots() {
241 self.pull_snapshots()?;
242 }
243 let mut count = 0;
244 let result = unsafe { BNRemoteFileGetSnapshots(self.handle.as_ptr(), &mut count) };
245 (!result.is_null())
246 .then(|| unsafe { Array::new(result, count, ()) })
247 .ok_or(())
248 }
249
250 pub fn snapshot_by_id(&self, id: &str) -> Result<Option<Ref<RemoteSnapshot>>, ()> {
254 if !self.has_pulled_snapshots() {
256 self.pull_snapshots()?;
257 }
258 let id = id.to_cstr();
259 let result = unsafe { BNRemoteFileGetSnapshotById(self.handle.as_ptr(), id.as_ptr()) };
260 Ok(NonNull::new(result).map(|handle| unsafe { RemoteSnapshot::ref_from_raw(handle) }))
261 }
262
263 pub fn pull_snapshots(&self) -> Result<(), ()> {
265 self.pull_snapshots_with_progress(NoProgressCallback)
266 }
267
268 pub fn pull_snapshots_with_progress<P: ProgressCallback>(
270 &self,
271 mut progress: P,
272 ) -> Result<(), ()> {
273 let success = unsafe {
274 BNRemoteFilePullSnapshots(
275 self.handle.as_ptr(),
276 Some(P::cb_progress_callback),
277 &mut progress as *mut P as *mut c_void,
278 )
279 };
280 success.then_some(()).ok_or(())
281 }
282
283 pub fn create_snapshot<I>(
291 &self,
292 name: &str,
293 contents: &mut [u8],
294 analysis_cache_contexts: &mut [u8],
295 file: &mut [u8],
296 parent_ids: I,
297 ) -> Result<Ref<RemoteSnapshot>, ()>
298 where
299 I: IntoIterator<Item = String>,
300 {
301 self.create_snapshot_with_progress(
302 name,
303 contents,
304 analysis_cache_contexts,
305 file,
306 parent_ids,
307 NoProgressCallback,
308 )
309 }
310
311 pub fn create_snapshot_with_progress<I, P>(
320 &self,
321 name: &str,
322 contents: &mut [u8],
323 analysis_cache_contexts: &mut [u8],
324 file: &mut [u8],
325 parent_ids: I,
326 mut progress: P,
327 ) -> Result<Ref<RemoteSnapshot>, ()>
328 where
329 I: IntoIterator<Item = String>,
330 P: ProgressCallback,
331 {
332 let name = name.to_cstr();
333 let parent_ids: Vec<_> = parent_ids.into_iter().map(|id| id.to_cstr()).collect();
334 let mut parent_ids_raw: Vec<_> = parent_ids.iter().map(|x| x.as_ptr()).collect();
335 let result = unsafe {
336 BNRemoteFileCreateSnapshot(
337 self.handle.as_ptr(),
338 name.as_ptr(),
339 contents.as_mut_ptr(),
340 contents.len(),
341 analysis_cache_contexts.as_mut_ptr(),
342 analysis_cache_contexts.len(),
343 file.as_mut_ptr(),
344 file.len(),
345 parent_ids_raw.as_mut_ptr(),
346 parent_ids_raw.len(),
347 Some(P::cb_progress_callback),
348 &mut progress as *mut P as *mut c_void,
349 )
350 };
351 let handle = NonNull::new(result).ok_or(())?;
352 Ok(unsafe { RemoteSnapshot::ref_from_raw(handle) })
353 }
354
355 pub fn delete_snapshot(&self, snapshot: &RemoteSnapshot) -> Result<(), ()> {
357 let success =
358 unsafe { BNRemoteFileDeleteSnapshot(self.handle.as_ptr(), snapshot.handle.as_ptr()) };
359 success.then_some(()).ok_or(())
360 }
361
362 pub fn request_user_positions(&self) -> String {
386 let result = unsafe { BNRemoteFileRequestUserPositions(self.handle.as_ptr()) };
387 assert!(!result.is_null());
388 unsafe { BnString::into_string(result) }
389 }
390
391 pub fn request_chat_log(&self) -> String {
392 let result = unsafe { BNRemoteFileRequestChatLog(self.handle.as_ptr()) };
393 assert!(!result.is_null());
394 unsafe { BnString::into_string(result) }
395 }
396
397 pub fn download(&self) -> Result<(), ()> {
402 self.download_with_progress(NoProgressCallback)
403 }
404
405 pub fn download_with_progress<P>(&self, mut progress: P) -> Result<(), ()>
410 where
411 P: ProgressCallback,
412 {
413 let success = unsafe {
414 BNRemoteFileDownload(
415 self.handle.as_ptr(),
416 Some(P::cb_progress_callback),
417 &mut progress as *mut P as *mut c_void,
418 )
419 };
420 success.then_some(()).ok_or(())
421 }
422
423 pub fn download_database(&self, path: impl AsRef<Path>) -> Result<Ref<FileMetadata>, ()> {
429 self.download_database_with_progress(path, NoProgressCallback)
431 }
432
433 pub fn download_database_with_progress(
440 &self,
441 path: impl AsRef<Path>,
442 progress: impl ProgressCallback,
443 ) -> Result<Ref<FileMetadata>, ()> {
444 let mut progress = progress.split(&[50, 50]);
446 let file = sync::download_file_with_progress(
447 self,
448 path.as_ref(),
449 progress.next_subpart().unwrap(),
450 )?;
451 self.sync_with_progress(
452 &file,
453 DatabaseConflictHandlerFail,
454 NoNameChangeset,
455 progress.next_subpart().unwrap(),
456 )?;
457 Ok(file)
458 }
459
460 pub fn sync<C: DatabaseConflictHandler, N: NameChangeset>(
466 &self,
467 metadata: &FileMetadata,
468 conflict_handler: C,
469 name_changeset: N,
470 ) -> Result<(), ()> {
471 sync::sync_database(metadata, self, conflict_handler, name_changeset)
472 }
473
474 pub fn sync_with_progress<C: DatabaseConflictHandler, P: ProgressCallback, N: NameChangeset>(
481 &self,
482 metadata: &FileMetadata,
483 conflict_handler: C,
484 name_changeset: N,
485 progress: P,
486 ) -> Result<(), ()> {
487 sync::sync_database_with_progress(
488 metadata,
489 self,
490 conflict_handler,
491 name_changeset,
492 progress,
493 )
494 }
495
496 pub fn pull<C, N>(
503 &self,
504 database: &Database,
505 conflict_handler: C,
506 name_changeset: N,
507 ) -> Result<usize, ()>
508 where
509 C: DatabaseConflictHandler,
510 N: NameChangeset,
511 {
512 sync::pull_database(database, self, conflict_handler, name_changeset)
513 }
514
515 pub fn pull_with_progress<C, P, N>(
523 &self,
524 database: &Database,
525 conflict_handler: C,
526 name_changeset: N,
527 progress: P,
528 ) -> Result<usize, ()>
529 where
530 C: DatabaseConflictHandler,
531 P: ProgressCallback,
532 N: NameChangeset,
533 {
534 sync::pull_database_with_progress(
535 database,
536 self,
537 conflict_handler,
538 name_changeset,
539 progress,
540 )
541 }
542
543 pub fn push<P>(&self, database: &Database) -> Result<usize, ()>
547 where
548 P: ProgressCallback,
549 {
550 sync::push_database(database, self)
551 }
552
553 pub fn push_with_progress<P>(&self, database: &Database, progress: P) -> Result<usize, ()>
558 where
559 P: ProgressCallback,
560 {
561 sync::push_database_with_progress(database, self, progress)
562 }
563}
564
565impl Debug for RemoteFile {
566 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
567 f.debug_struct("RemoteFile")
568 .field("id", &self.id())
569 .field("name", &self.name())
570 .field("description", &self.description())
571 .field("metadata", &self.metadata())
572 .field("size", &self.size())
573 .field(
574 "snapshot_count",
575 &self.snapshots().map(|s| s.len()).unwrap_or(0),
576 )
577 .finish()
578 }
579}
580
581impl PartialEq for RemoteFile {
582 fn eq(&self, other: &Self) -> bool {
583 self.id() == other.id()
584 }
585}
586
587impl Eq for RemoteFile {}
588
589impl ToOwned for RemoteFile {
590 type Owned = Ref<Self>;
591
592 fn to_owned(&self) -> Self::Owned {
593 unsafe { RefCountable::inc_ref(self) }
594 }
595}
596
597unsafe impl Send for RemoteFile {}
598unsafe impl Sync for RemoteFile {}
599
600unsafe impl RefCountable for RemoteFile {
601 unsafe fn inc_ref(handle: &Self) -> Ref<Self> {
602 Ref::new(Self {
603 handle: NonNull::new(BNNewRemoteFileReference(handle.handle.as_ptr())).unwrap(),
604 })
605 }
606
607 unsafe fn dec_ref(handle: &Self) {
608 BNFreeRemoteFile(handle.handle.as_ptr());
609 }
610}
611
612impl CoreArrayProvider for RemoteFile {
613 type Raw = *mut BNRemoteFile;
614 type Context = ();
615 type Wrapped<'a> = Guard<'a, Self>;
616}
617
618unsafe impl CoreArrayProviderInner for RemoteFile {
619 unsafe fn free(raw: *mut Self::Raw, count: usize, _context: &Self::Context) {
620 BNFreeRemoteFileList(raw, count)
621 }
622
623 unsafe fn wrap_raw<'a>(raw: &'a Self::Raw, context: &'a Self::Context) -> Self::Wrapped<'a> {
624 let raw_ptr = NonNull::new(*raw).unwrap();
625 Guard::new(Self::from_raw(raw_ptr), context)
626 }
627}