diff --git a/src/lib.rs b/src/lib.rs index fbbebe7..d5b1645 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -26,7 +26,8 @@ extern crate libc; -use self::libc::{c_char, c_double, c_int, c_long, c_uchar, c_uint}; +use std::io::{Cursor, Read, Seek, SeekFrom, Write}; +use self::libc::{c_char, c_double, c_int, c_long, c_uchar, c_uint, c_longlong, c_void}; /// An opaque structure that serves as a container for a media file's metadata. /// @@ -80,6 +81,121 @@ impl Default for Orientation { fn default() -> Orientation { Orientation::Unspecified } } +#[repr(C)] +#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash)] +pub enum WrapperSeekOrigin { + Begin = 0, + Current = 1, + End = 2, +} + +type StreamCanSeek = extern "C" fn(handle: *mut c_void) -> bool; +type StreamCanRead = extern "C" fn(handle: *mut c_void) -> bool; +type StreamCanWrite = extern "C" fn(handle: *mut c_void) -> bool; +type StreamLength = extern "C" fn(handle: *mut c_void) -> c_longlong; +type StreamPosition = extern "C" fn(handle: *mut c_void) -> c_longlong; +type StreamRead = extern "C" fn(handle: *mut c_void, buffer: *mut c_void, offset: c_int, count: c_int) -> c_int; +type StreamWrite = extern "C" fn(handle: *mut c_void, buffer: *const c_void, offset: c_int, count: c_int); +type StreamSeek = extern "C" fn(handle: *mut c_void, offset: c_longlong, origin: WrapperSeekOrigin); +type StreamFlush = extern "C" fn(handle: *mut c_void); + +pub extern "C" fn stream_can_seek(_handle: *mut c_void) -> bool { + true +} + +pub extern "C" fn stream_can_read(_handle: *mut c_void) -> bool { + true +} + +pub extern "C" fn stream_can_write(_handle: *mut c_void) -> bool { + true +} + +pub extern "C" fn stream_length(handle: *mut c_void) -> c_longlong { + let vec: &Vec = unsafe { &*(handle as *mut Vec) }; + vec.len() as c_longlong +} + +pub extern "C" fn stream_position(handle: *mut c_void) -> c_longlong { + let cursor: &Cursor> = unsafe { &*(handle as *mut Cursor>) }; + cursor.position() as c_longlong +} + +pub extern "C" fn stream_read(handle: *mut c_void, buffer: *mut c_void, _offset: c_int, count: c_int) -> c_int { + let cursor: &mut Cursor> = unsafe { &mut *(handle as *mut Cursor>) }; + let buffer_slice = unsafe { std::slice::from_raw_parts_mut(buffer as *mut u8, count as usize) }; + + match cursor.read(buffer_slice) { + Ok(bytes_read) => bytes_read as c_int, + Err(e) => { + panic!("Error reading: {}", e); + }, + } +} + +pub extern "C" fn stream_seek(handle: *mut c_void, offset: c_longlong, origin: WrapperSeekOrigin) { + let cursor: &mut Cursor> = unsafe { &mut *(handle as *mut Cursor>) }; + let pos = match origin { + WrapperSeekOrigin::Begin => SeekFrom::Start(offset as u64), + WrapperSeekOrigin::Current => SeekFrom::Current(offset), + WrapperSeekOrigin::End => SeekFrom::End(offset), + }; + match cursor.seek(pos) { + Ok(_) => (), + Err(e) => panic!("Error seeking: {}", e), + } +} + +pub extern "C" fn stream_flush(handle: *mut c_void) { + let cursor: &mut Cursor> = unsafe { &mut *(handle as *mut Cursor>) }; + match cursor.flush() { + Ok(_) => (), + Err(e) => panic!("Error flushing: {}", e), + } +} + +pub extern "C" fn stream_stream_write(handle: *mut c_void, buffer: *const c_void, _offset: c_int, count: c_int) { + let cursor: &mut Cursor> = unsafe { &mut *(handle as *mut Cursor>) }; + let buffer_slice = unsafe { std::slice::from_raw_parts(buffer as *const u8, count as usize) }; + + match cursor.write_all(buffer_slice) { + Ok(_) => (), + Err(e) => panic!("Error writing: {}", e), + } +} + +#[repr(C)] +#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash)] +pub struct ManagedStreamCallbacks { + pub handle: *mut c_void, + pub can_seek: StreamCanSeek, + pub can_read: StreamCanRead, + pub can_write: StreamCanWrite, + pub length: StreamLength, + pub position: StreamPosition, + pub read: StreamRead, + pub write: StreamWrite, + pub seek: StreamSeek, + pub flush: StreamFlush, +} + +impl ManagedStreamCallbacks { + pub fn new(handle: *mut c_void) -> ManagedStreamCallbacks { + ManagedStreamCallbacks { + handle, + can_seek: stream_can_seek, + can_read: stream_can_read, + can_write: stream_can_write, + length: stream_length, + position: stream_position, + read: stream_read, + write: stream_stream_write, + seek: stream_seek, + flush: stream_flush, + } + } +} + /// Log levels. #[repr(C)] #[derive(Clone, Copy, Debug, Eq, PartialEq, Hash)] @@ -107,8 +223,10 @@ extern { pub fn gexiv2_metadata_free(this: *mut GExiv2Metadata); pub fn gexiv2_metadata_open_path(this: *mut GExiv2Metadata, path: *const c_char, error: *mut *mut GError) -> c_int; pub fn gexiv2_metadata_open_buf(Gthis: *mut GExiv2Metadata, data: *const u8, data_len: c_long, error: *mut *mut GError) -> c_int; + pub fn gexiv2_metadata_open_stream(this: *mut GExiv2Metadata, callbacks: *mut ManagedStreamCallbacks, error: *mut *mut GError) -> c_int; pub fn gexiv2_metadata_from_app1_segment(Gthis: *mut GExiv2Metadata, data: *const u8, data_len: c_long, error: *mut *mut GError) -> c_int; pub fn gexiv2_metadata_save_file(this: *mut GExiv2Metadata, path: *const c_char, error: *mut *mut GError) -> c_int; + pub fn gexiv2_metadata_save_stream(this: *mut GExiv2Metadata, callbacks: *mut ManagedStreamCallbacks, error: *mut *mut GError) -> c_int; // Image information. pub fn gexiv2_metadata_get_supports_exif(this: *mut GExiv2Metadata) -> c_int; diff --git a/src/test.rs b/src/test.rs index fe4baa5..114d735 100644 --- a/src/test.rs +++ b/src/test.rs @@ -19,10 +19,12 @@ extern crate libc; extern crate tempfile; use std::ffi; +use std::ffi::c_void; use std::fs; -use std::io::Write; +use std::io::{Cursor, Write}; use std::ptr; use std::slice; +use libc::c_longlong; use super::*; @@ -271,3 +273,66 @@ fn log_get_and_set_level() { assert_eq!(gexiv2_log_get_level(), GExiv2LogLevel::INFO); } } + +#[test] +fn test_metadata_save_to_stream() { + unsafe { + + let meta: *mut GExiv2Metadata = make_new_metadata(); + + let output_data: Vec = Vec::from(MINI_JPEG); + let mut cursor = Cursor::new(output_data.clone()); + + let mut callbacks = ManagedStreamCallbacks::new(&mut cursor as *mut Cursor> as *mut c_void); + + let mut err: *mut GError = ptr::null_mut(); + + let result = gexiv2_metadata_save_stream(meta, &mut callbacks as *mut ManagedStreamCallbacks, &mut err); + + if !err.is_null() { + let error_msg = ffi::CStr::from_ptr((*err).message).to_str().unwrap(); + println!("Error: {}", error_msg); + } + + assert_eq!(result, 1, "Failed to save metadata to stream."); + + // verify the streamed data + let stream_length = stream_length(callbacks.handle); + assert_eq!(stream_length, MINI_JPEG.len() as c_longlong, "Stream length does not match original data length"); + + // Clean up + gexiv2_metadata_free(meta); + } +} + +#[test] +fn test_metadata_open_and_save_to_stream() { + unsafe { + let meta: *mut GExiv2Metadata = gexiv2_metadata_new(); + + let output_data: Vec = Vec::from(MINI_JPEG); + let mut cursor = Cursor::new(output_data.clone()); + let mut callbacks = ManagedStreamCallbacks::new(&mut cursor as *mut Cursor> as *mut c_void); + let mut err: *mut GError = ptr::null_mut(); + let _ = gexiv2_metadata_open_stream(meta, &mut callbacks as *mut ManagedStreamCallbacks, &mut err); + if !err.is_null() { + let error_msg = ffi::CStr::from_ptr((*err).message).to_str().unwrap(); + println!("Error: {}", error_msg); + } + let mut err: *mut GError = ptr::null_mut(); + let result = gexiv2_metadata_save_stream(meta, &mut callbacks as *mut ManagedStreamCallbacks, &mut err); + if !err.is_null() { + let error_msg = ffi::CStr::from_ptr((*err).message).to_str().unwrap(); + println!("Error: {}", error_msg); + } + + assert_eq!(result, 1, "Failed to save metadata to stream."); + + // verify the streamed data + let stream_length = stream_length(callbacks.handle); + assert_eq!(stream_length, MINI_JPEG.len() as c_longlong, "Stream length does not match original data length"); + + // Clean up + gexiv2_metadata_free(meta); + } +}