Skip to content

Commit c7df9b0

Browse files
committed
Add error to progress reporter
1 parent 3da73a6 commit c7df9b0

2 files changed

Lines changed: 90 additions & 45 deletions

File tree

src/c_api.rs

Lines changed: 40 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -43,11 +43,12 @@
4343
//!
4444
//! it will build `target/aarch64-apple-ios/release/libgifski.a` (ignore the warning about cdylib).
4545
46-
use crate::{Collector, NoProgress, ProgressCallback, ProgressReporter, Repeat, Settings, Writer};
46+
use crate::progress::ProgressCallback;
47+
use crate::{CCallbacks, Collector, ErrorCallback, ProgressReporter, Repeat, Settings, Writer};
4748
use imgref::{Img, ImgVec};
4849
use rgb::{RGB8, RGBA8};
4950
use std::fs;
50-
use std::ffi::{CStr, CString};
51+
use std::ffi::CStr;
5152
use std::fs::File;
5253
use std::io;
5354
use std::io::Write;
@@ -57,7 +58,7 @@ use std::path::{Path, PathBuf};
5758
use std::ptr;
5859
use std::slice;
5960
use std::thread;
60-
use std::sync::{Arc, Mutex};
61+
use std::sync::{Arc, Mutex, MutexGuard};
6162
mod c_api_error;
6263
use self::c_api_error::GifskiError;
6364
use std::panic::catch_unwind;
@@ -96,8 +97,8 @@ pub struct GifskiHandle {
9697
pub struct GifskiHandleInternal {
9798
writer: Mutex<Option<Writer>>,
9899
collector: Mutex<Option<Collector>>,
99-
progress: Mutex<Option<ProgressCallback>>,
100-
error_callback: Mutex<Option<Box<dyn Fn(String) + 'static + Sync + Send>>>,
100+
/// Progress and error callbacks
101+
callbacks: Mutex<CCallbacks>,
101102
/// Bool set to true when the thread has been set up,
102103
/// prevents re-setting of the thread after `finish()`
103104
write_thread: Mutex<(bool, Option<thread::JoinHandle<GifskiError>>)>,
@@ -126,8 +127,10 @@ pub unsafe extern "C" fn gifski_new(settings: *const GifskiSettings) -> *const G
126127
writer: Mutex::new(Some(writer)),
127128
write_thread: Mutex::new((false, None)),
128129
collector: Mutex::new(Some(collector)),
129-
progress: Mutex::new(None),
130-
error_callback: Mutex::new(None),
130+
callbacks: Mutex::new(CCallbacks {
131+
progress: None,
132+
error: None,
133+
}),
131134
}))
132135
.cast::<GifskiHandle>()
133136
} else {
@@ -378,18 +381,12 @@ pub unsafe extern "C" fn gifski_add_frame_rgb(handle: *const GifskiHandle, frame
378381
/// This function must be called before `gifski_set_file_output()` to take effect.
379382
#[no_mangle]
380383
pub unsafe extern "C" fn gifski_set_progress_callback(handle: *const GifskiHandle, cb: unsafe extern "C" fn(*mut c_void) -> c_int, user_data: *mut c_void) -> GifskiError {
381-
let Some(g) = borrow(handle) else { return GifskiError::NULL_ARG };
382-
383-
if g.write_thread.lock().map_or(true, |t| t.0) {
384-
g.print_error("tried to set progress callback after writing has already started".into());
385-
return GifskiError::INVALID_STATE;
386-
}
387-
match g.progress.lock() {
388-
Ok(mut progress) => {
389-
*progress = Some(ProgressCallback::new(cb, user_data));
384+
match set_callbacks(borrow(handle)) {
385+
Ok(mut callbacks) => {
386+
callbacks.progress = Some(ProgressCallback::new(cb, user_data));
390387
GifskiError::OK
391388
},
392-
Err(_) => GifskiError::THREAD_LOST,
389+
Err(e) => e,
393390
}
394391
}
395392

@@ -408,27 +405,28 @@ pub unsafe extern "C" fn gifski_set_progress_callback(handle: *const GifskiHandl
408405
/// This function must be called before `gifski_set_file_output()` to take effect.
409406
#[no_mangle]
410407
pub unsafe extern "C" fn gifski_set_error_message_callback(handle: *const GifskiHandle, cb: unsafe extern "C" fn(*const c_char, *mut c_void), user_data: *mut c_void) -> GifskiError {
411-
let Some(g) = borrow(handle) else { return GifskiError::NULL_ARG };
412-
413-
let user_data = SendableUserData(user_data);
414-
match g.error_callback.lock() {
415-
Ok(mut error_callback) => {
416-
*error_callback = Some(Box::new(move |mut s: String| {
417-
s.reserve_exact(1);
418-
s.push('\0');
419-
let cstring = CString::from_vec_with_nul(s.into_bytes()).unwrap_or_default();
420-
unsafe { cb(cstring.as_ptr(), user_data.clone().0) } // the clone is a no-op, only to force closure to own it
421-
}));
408+
match set_callbacks(borrow(handle)) {
409+
Ok(mut callbacks) => {
410+
callbacks.error = Some(ErrorCallback {
411+
callback: cb,
412+
user_data,
413+
});
422414
GifskiError::OK
423415
},
424-
Err(_) => GifskiError::THREAD_LOST,
416+
Err(e) => e,
425417
}
426418
}
427419

428-
#[derive(Clone)]
429-
struct SendableUserData(*mut c_void);
430-
unsafe impl Send for SendableUserData {}
431-
unsafe impl Sync for SendableUserData {}
420+
fn set_callbacks(g: Option<&GifskiHandleInternal>) -> Result<MutexGuard<'_, CCallbacks>, GifskiError> {
421+
let g = g.ok_or(GifskiError::NULL_ARG)?;
422+
423+
if g.write_thread.lock().map_or(true, |t| t.0) {
424+
g.print_error("tried to set a callback after writing has already started".into());
425+
return Err(GifskiError::INVALID_STATE);
426+
}
427+
428+
g.callbacks.lock().map_err(|_| GifskiError::THREAD_LOST)
429+
}
432430

433431
/// Start writing to the `destination`. This has to be called before any frames are added.
434432
///
@@ -523,11 +521,10 @@ fn gifski_write_thread_start<W: 'static + Write + Send>(g: &GifskiHandleInterna
523521
return Err(GifskiError::INVALID_STATE);
524522
}
525523
let writer = g.writer.lock().map_err(|_| GifskiError::THREAD_LOST)?.take();
526-
let mut user_progress = g.progress.lock().map_err(|_| GifskiError::THREAD_LOST)?.take();
524+
let mut user_progress = g.callbacks.lock().map_err(|_| GifskiError::THREAD_LOST)?.clone();
527525
let handle = thread::Builder::new().name("c-write".into()).spawn(move || {
528526
if let Some(writer) = writer {
529-
let progress = user_progress.as_mut().map(|m| m as &mut dyn ProgressReporter);
530-
match writer.write(file, progress.unwrap_or(&mut NoProgress {})).into() {
527+
match writer.write(file, &mut user_progress).into() {
531528
res @ (GifskiError::OK | GifskiError::ALREADY_EXISTS) => res,
532529
err => {
533530
if let Some(path) = path {
@@ -596,16 +593,18 @@ pub unsafe extern "C" fn gifski_finish(g: *const GifskiHandle) -> GifskiError {
596593
}
597594

598595
impl GifskiHandleInternal {
596+
#[cold]
599597
fn print_error(&self, mut err: String) {
600-
if let Ok(Some(cb)) = self.error_callback.lock().as_deref() {
601-
cb(err);
598+
if let Ok(reporter) = self.callbacks.lock().as_deref_mut() {
599+
reporter.error(err)
602600
} else {
603601
err.reserve_exact(1);
604602
err.push('\n');
605603
let _ = std::io::stderr().write_all(err.as_bytes());
606604
}
607605
}
608606

607+
#[cold]
609608
fn print_panic(&self, e: Box<dyn std::any::Any + Send>) {
610609
let msg = e.downcast_ref::<String>().map(|s| s.as_str())
611610
.or_else(|| e.downcast_ref::<&str>().copied()).unwrap_or("unknown panic");
@@ -643,7 +642,7 @@ fn c_cb() {
643642
assert_eq!(GifskiError::OK, gifski_set_write_callback(g, Some(cb), ptr::addr_of_mut!(write_called).cast()));
644643
assert_eq!(GifskiError::INVALID_STATE, gifski_set_progress_callback(g, pcb, ptr::addr_of_mut!(progress_called).cast()));
645644
assert_eq!(GifskiError::OK, gifski_add_frame_rgb(g, 0, 1, 3, 1, &RGB::new(0,0,0), 3.));
646-
assert_eq!(GifskiError::OK, gifski_add_frame_rgb(g, 0, 1, 3, 1, &RGB::new(0,0,0), 10.));
645+
assert_eq!(GifskiError::OK, gifski_add_frame_rgb(g, 1, 1, 3, 1, &RGB::new(0,0,0), 10.));
647646
assert_eq!(GifskiError::OK, gifski_finish(g));
648647
}
649648
assert!(write_called);
@@ -673,7 +672,7 @@ fn progress_abort() {
673672
assert_eq!(GifskiError::OK, gifski_set_progress_callback(g, pcb, ptr::null_mut()));
674673
assert_eq!(GifskiError::OK, gifski_set_write_callback(g, Some(cb), ptr::null_mut()));
675674
assert_eq!(GifskiError::OK, gifski_add_frame_rgb(g, 0, 1, 3, 1, &RGB::new(0, 0, 0), 3.));
676-
assert_eq!(GifskiError::OK, gifski_add_frame_rgb(g, 0, 1, 3, 1, &RGB::new(0, 0, 0), 10.));
675+
assert_eq!(GifskiError::OK, gifski_add_frame_rgb(g, 1, 1, 3, 1, &RGB::new(0, 0, 0), 10.));
677676
assert_eq!(GifskiError::ABORTED, gifski_finish(g));
678677
}
679678
}
@@ -740,7 +739,7 @@ fn test_error_callback() {
740739
assert_eq!(GifskiError::OK, gifski_set_write_callback(g, Some(cb), 1 as _));
741740
assert_eq!(GifskiError::INVALID_STATE, gifski_set_write_callback(g, Some(cb), 1 as _));
742741
assert_eq!(GifskiError::INVALID_STATE, gifski_finish(g));
743-
assert_eq!("gifski_set_file_output/gifski_set_write_callback has been called already", callback_msg.unwrap());
742+
assert_eq!("gifski_set_file_output/gifski_set_write_callback has been called already", callback_msg.expect("set msg"));
744743
}
745744
}
746745

src/progress.rs

Lines changed: 50 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,8 @@
55
#[deprecated(note = "The pbr dependency is no longer exposed. Please use a newtype pattern and write your own trait impl for it")]
66
pub use pbr::ProgressBar;
77

8-
use std::os::raw::{c_int, c_void};
8+
use std::os::raw::{c_int, c_char, c_void};
9+
use std::ffi::CString;
910

1011
/// A trait that is used to report progress to some consumer.
1112
pub trait ProgressReporter: Send {
@@ -17,6 +18,10 @@ pub trait ProgressReporter: Send {
1718
/// File size so far
1819
fn written_bytes(&mut self, _current_file_size_in_bytes: u64) {}
1920

21+
/// Log an incorrect use of the library
22+
#[cold]
23+
fn error(&mut self, _message: String) {}
24+
2025
/// Not used :(
2126
/// Writing is done when `Writer::write()` call returns
2227
fn done(&mut self, _msg: &str) {}
@@ -26,16 +31,31 @@ pub trait ProgressReporter: Send {
2631
pub struct NoProgress {}
2732

2833
/// For C
34+
#[derive(Clone)]
2935
pub struct ProgressCallback {
3036
callback: unsafe extern "C" fn(*mut c_void) -> c_int,
31-
arg: *mut c_void,
37+
user_data: *mut c_void,
38+
}
39+
40+
#[derive(Clone)]
41+
pub(crate) struct ErrorCallback {
42+
pub callback: unsafe extern "C" fn(*const c_char, *mut c_void),
43+
pub user_data: *mut c_void,
44+
}
45+
46+
#[derive(Clone)]
47+
pub(crate) struct CCallbacks {
48+
pub progress: Option<ProgressCallback>,
49+
pub error: Option<ErrorCallback>,
3250
}
3351

3452
unsafe impl Send for ProgressCallback {}
53+
unsafe impl Send for ErrorCallback {}
3554

3655
impl ProgressCallback {
56+
/// The callback must be thread-safe
3757
pub fn new(callback: unsafe extern "C" fn(*mut c_void) -> c_int, arg: *mut c_void) -> Self {
38-
Self { callback, arg }
58+
Self { callback, user_data: arg }
3959
}
4060
}
4161

@@ -49,7 +69,33 @@ impl ProgressReporter for NoProgress {
4969

5070
impl ProgressReporter for ProgressCallback {
5171
fn increase(&mut self) -> bool {
52-
unsafe { (self.callback)(self.arg) == 1 }
72+
unsafe { (self.callback)(self.user_data) == 1 }
73+
}
74+
75+
fn done(&mut self, _msg: &str) {}
76+
}
77+
78+
impl ProgressReporter for CCallbacks {
79+
fn increase(&mut self) -> bool {
80+
if let Some(p) = &mut self.progress {
81+
p.increase()
82+
} else {
83+
true
84+
}
85+
}
86+
87+
#[cold]
88+
fn error(&mut self, mut msg: String) {
89+
msg.reserve_exact(1);
90+
if let Some(err) = &self.error {
91+
let cstring = CString::new(msg);
92+
let cstring = cstring.as_deref().unwrap_or_default();
93+
unsafe { (err.callback)(cstring.as_ptr(), err.user_data) }
94+
} else {
95+
use std::io::Write;
96+
msg.push('\n');
97+
let _ = std::io::stderr().write_all(msg.as_bytes());
98+
}
5399
}
54100

55101
fn done(&mut self, _msg: &str) {}

0 commit comments

Comments
 (0)