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, BinaryViewExt};
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<ProjectFile, ()> {
61 let result = unsafe { BNRemoteFileGetCoreFile(self.handle.as_ptr()) };
62 NonNull::new(result)
63 .map(|handle| unsafe { ProjectFile::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 let database = file.database().ok_or(())?;
452 self.sync_with_progress(
453 &database,
454 DatabaseConflictHandlerFail,
455 NoNameChangeset,
456 progress.next_subpart().unwrap(),
457 )?;
458 Ok(file)
459 }
460
461 pub fn sync<C: DatabaseConflictHandler, N: NameChangeset>(
467 &self,
468 database: &Database,
469 conflict_handler: C,
470 name_changeset: N,
471 ) -> Result<(), ()> {
472 sync::sync_database(database, self, conflict_handler, name_changeset)
473 }
474
475 pub fn sync_with_progress<C: DatabaseConflictHandler, P: ProgressCallback, N: NameChangeset>(
482 &self,
483 database: &Database,
484 conflict_handler: C,
485 name_changeset: N,
486 progress: P,
487 ) -> Result<(), ()> {
488 sync::sync_database_with_progress(
489 database,
490 self,
491 conflict_handler,
492 name_changeset,
493 progress,
494 )
495 }
496
497 pub fn pull<C, N>(
504 &self,
505 database: &Database,
506 conflict_handler: C,
507 name_changeset: N,
508 ) -> Result<usize, ()>
509 where
510 C: DatabaseConflictHandler,
511 N: NameChangeset,
512 {
513 sync::pull_database(database, self, conflict_handler, name_changeset)
514 }
515
516 pub fn pull_with_progress<C, P, N>(
524 &self,
525 database: &Database,
526 conflict_handler: C,
527 name_changeset: N,
528 progress: P,
529 ) -> Result<usize, ()>
530 where
531 C: DatabaseConflictHandler,
532 P: ProgressCallback,
533 N: NameChangeset,
534 {
535 sync::pull_database_with_progress(
536 database,
537 self,
538 conflict_handler,
539 name_changeset,
540 progress,
541 )
542 }
543
544 pub fn push<P>(&self, database: &Database) -> Result<usize, ()>
548 where
549 P: ProgressCallback,
550 {
551 sync::push_database(database, self)
552 }
553
554 pub fn push_with_progress<P>(&self, database: &Database, progress: P) -> Result<usize, ()>
559 where
560 P: ProgressCallback,
561 {
562 sync::push_database_with_progress(database, self, progress)
563 }
564}
565
566impl Debug for RemoteFile {
567 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
568 f.debug_struct("RemoteFile")
569 .field("id", &self.id())
570 .field("name", &self.name())
571 .field("description", &self.description())
572 .field("metadata", &self.metadata())
573 .field("size", &self.size())
574 .field(
575 "snapshot_count",
576 &self.snapshots().map(|s| s.len()).unwrap_or(0),
577 )
578 .finish()
579 }
580}
581
582impl PartialEq for RemoteFile {
583 fn eq(&self, other: &Self) -> bool {
584 self.id() == other.id()
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 RefCountable for RemoteFile {
598 unsafe fn inc_ref(handle: &Self) -> Ref<Self> {
599 Ref::new(Self {
600 handle: NonNull::new(BNNewRemoteFileReference(handle.handle.as_ptr())).unwrap(),
601 })
602 }
603
604 unsafe fn dec_ref(handle: &Self) {
605 BNFreeRemoteFile(handle.handle.as_ptr());
606 }
607}
608
609impl CoreArrayProvider for RemoteFile {
610 type Raw = *mut BNRemoteFile;
611 type Context = ();
612 type Wrapped<'a> = Guard<'a, Self>;
613}
614
615unsafe impl CoreArrayProviderInner for RemoteFile {
616 unsafe fn free(raw: *mut Self::Raw, count: usize, _context: &Self::Context) {
617 BNFreeRemoteFileList(raw, count)
618 }
619
620 unsafe fn wrap_raw<'a>(raw: &'a Self::Raw, context: &'a Self::Context) -> Self::Wrapped<'a> {
621 let raw_ptr = NonNull::new(*raw).unwrap();
622 Guard::new(Self::from_raw(raw_ptr), context)
623 }
624}