Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Adding gexiv2_metadata_save_stream #26

Open
mexicantexan opened this issue Dec 20, 2023 · 8 comments
Open

Adding gexiv2_metadata_save_stream #26

mexicantexan opened this issue Dec 20, 2023 · 8 comments

Comments

@mexicantexan
Copy link

Adding this support would be great

@mexicantexan
Copy link
Author

mexicantexan commented Dec 20, 2023

@felixc would this work?

#[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);

#[repr(C)]
#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash)]
pub struct ManagedStreamCallbacks {
    handle: *mut c_void,
    can_seek: StreamCanSeek,
    can_read: StreamCanRead,
    can_write: StreamCanWrite,
    length: StreamLength,
    position: StreamPosition,
    read: StreamRead,
    write: StreamWrite,
    seek: StreamSeek,
    flush: StreamFlush,
}

// ... other code ...
extern {
    // ... other public functions ...
    pub fn gexiv2_metadata_save_stream(this: *mut GExiv2Metadata, callbacks: *mut ManagedStreamCallbacks, error: *mut *mut GError) -> c_int;
    pub fn gexiv2_metadata_open_stream(this: *mut GExiv2Metadata, callbacks: *mut ManagedStreamCallbacks, error: *mut *mut GError) -> c_int;

@mexicantexan
Copy link
Author

mexicantexan commented Dec 20, 2023

Possibly adding this to the test file as well?

extern "C" fn mock_can_seek(_handle: *mut c_void) -> bool {
    true
}

extern "C" fn mock_can_read(_handle: *mut c_void) -> bool {
    true
}

extern "C" fn mock_can_write(_handle: *mut c_void) -> bool {
    true
}

extern "C" fn mock_length(handle: *mut c_void) -> c_longlong {
    assert!(!handle.is_null(), "Handle is null");
    let vec: &Vec<u8> = unsafe { &*(handle as *mut Vec<u8>) };
    vec.len() as c_longlong
}

extern "C" fn mock_position(handle: *mut c_void) -> c_longlong {
    let cursor: &Cursor<Vec<u8>> = unsafe { &*(handle as *mut Cursor<Vec<u8>>) };
    cursor.position() as c_longlong
}

extern "C" fn mock_read(handle: *mut c_void, buffer: *mut c_void, _offset: c_int, count: c_int) -> c_int {
    let cursor: &mut Cursor<Vec<u8>> = unsafe { &mut *(handle as *mut Cursor<Vec<u8>>) };
    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);
        },
    }
}

extern "C" fn mock_seek(handle: *mut c_void, offset: c_longlong, origin: WrapperSeekOrigin) {
    let cursor: &mut Cursor<Vec<u8>> = unsafe { &mut *(handle as *mut Cursor<Vec<u8>>) };
    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),
    }
}

extern "C" fn mock_flush(handle: *mut c_void) {
    let cursor: &mut Cursor<Vec<u8>> = unsafe { &mut *(handle as *mut Cursor<Vec<u8>>) };
    match cursor.flush() {
        Ok(_) => (),
        Err(e) => panic!("Error flushing: {}", e),
    }
}

extern "C" fn mock_stream_write(handle: *mut c_void, buffer: *const c_void, offset: c_int, count: c_int) {
    let cursor: &mut Cursor<Vec<u8>> = unsafe { &mut *(handle as *mut Cursor<Vec<u8>>) };
    let buffer_slice = unsafe { std::slice::from_raw_parts(buffer as *const u8, count as usize) };

    match cursor.write_all(buffer_slice) {
        Ok(_) => {
            println!("Wrote {} bytes", count);
        },
        Err(e) => panic!("Error writing: {}", e),
    }
}

#[test]
fn test_metadata_save_to_stream() {
    unsafe {

        let meta: *mut GExiv2Metadata = make_new_metadata();

        let mut output_data: Vec<u8> = Vec::from(MINI_JPEG);
        let mut cursor = Cursor::new(output_data.clone());

        let mut callbacks = ManagedStreamCallbacks {
            handle: &mut cursor as *mut Cursor<Vec<u8>> as *mut c_void,
            can_seek: mock_can_seek,
            can_read: mock_can_read,
            can_write: mock_can_write,
            length: mock_length,
            position: mock_position,
            read: mock_read,
            write: mock_stream_write,
            seek: mock_seek,
            flush: mock_flush,
        };

        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 = mock_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 mut output_data: Vec<u8> = Vec::from(MINI_JPEG);
        let mut cursor = Cursor::new(output_data.clone());
        let mut callbacks = ManagedStreamCallbacks {
            handle: &mut cursor as *mut Cursor<Vec<u8>> as *mut c_void,
            can_seek: mock_can_seek,
            can_read: mock_can_read,
            can_write: mock_can_write,
            length: mock_length,
            position: mock_position,
            read: mock_read,
            write: mock_stream_write,
            seek: mock_seek,
            flush: mock_flush,
        };
        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 = mock_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);
    }
}

@phako
Copy link

phako commented Jan 8, 2024

I'd love to get rid of that API in gexiv2, it is cursed and IMHO broken and full of weird assumptions - already from the exiv2 side. I'd prefer to give you an _as_bytes() function instead.

@mexicantexan
Copy link
Author

mexicantexan commented Jan 8, 2024

That would be really amazing! I was able to get a version of that code working locally all in an attempt to avoid writing to disk, but having a supported/public as_bytes() method would be killer. Hopefully, it's not too much of a lift.

@mexicantexan
Copy link
Author

@phako what would it take to get a _as_bytes() function? Anything I could help with?

@phako
Copy link

phako commented Feb 2, 2024

Me not being sick or overworked else-wise for a change, mostly. So if you have any code or idea to share, go for it

@phako
Copy link

phako commented Mar 31, 2024

Btw, which information are you looking for? the whole image or XMP sidecar data?

@phako
Copy link

phako commented Apr 2, 2024

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants