#![allow(non_camel_case_types)]
#![allow(clippy::missing_safety_doc)]
use std::{
ffi::CStr,
fs::File,
io::BufWriter,
os::raw::{c_char, c_int, c_uint, c_void},
ptr,
};
use cqdb::{CQDB, CQDBWriter, Flag};
use libc::FILE;
#[macro_use]
mod macros;
pub const CQDB_NONE: c_uint = 0;
pub const CQDB_ONEWAY: c_uint = 1;
pub const CQDB_SUCCESS: c_int = 0;
pub const CQDB_ERROR_INVALIDID: c_int = -1018;
pub const CQDB_ERROR_FILEWRITE: c_int = -1021;
pub const CQDB_ERROR_NOTFOUND: c_int = -1023;
#[derive(Debug, Clone, Copy)]
#[repr(C)]
pub struct tag_cqdb {
_unused: [u8; 0],
}
pub type cqdb_t = tag_cqdb;
#[derive(Debug, Clone, Copy)]
#[repr(C)]
struct tag_cqdb_writer_inner {
_unused: [u8; 0],
}
#[derive(Debug, Clone, Copy)]
#[repr(C)]
pub struct tag_cqdb_writer {
file: *mut FILE,
inner: *mut tag_cqdb_writer_inner,
}
pub type cqdb_writer_t = tag_cqdb_writer;
ffi_fn! {
fn cqdb_delete(db: *mut cqdb_t) {
if !db.is_null() {
unsafe { drop(Box::from_raw(db as *mut CQDB)) };
}
}
}
ffi_fn! {
fn cqdb_reader(buffer: *const c_void, size: usize) -> *mut cqdb_t {
let buf = unsafe { std::slice::from_raw_parts(buffer as *const u8, size) };
if let Ok(db) = CQDB::new(buf) {
Box::into_raw(Box::new(db)) as *mut cqdb_t
} else {
ptr::null_mut()
}
}
}
ffi_fn! {
fn cqdb_num(db: *mut cqdb_t) -> c_int {
let db = db as *mut CQDB;
unsafe {
(*db).num() as c_int
}
}
}
ffi_fn! {
fn cqdb_to_id(db: *mut cqdb_t, s: *const c_char) -> c_int {
let db = db as *mut CQDB;
unsafe {
let c_str = CStr::from_ptr(s).to_str().unwrap();
(*db).to_id(c_str).map(|id| id as c_int).unwrap_or(CQDB_ERROR_NOTFOUND)
}
}
}
ffi_fn! {
fn cqdb_to_string(db: *mut cqdb_t, id: c_int) -> *const c_char {
let db = db as *mut CQDB;
if let Some(s) = unsafe { (*db).to_str(id as u32) } {
s.as_ptr() as *const c_char
} else {
ptr::null_mut()
}
}
}
ffi_fn! {
fn cqdb_writer(fp: *mut FILE, flag: c_int) -> *mut cqdb_writer_t {
unsafe {
let file = new_file_from_libc(fp);
let buf_writer = BufWriter::new(file);
let flag = if flag as c_uint == CQDB_ONEWAY {
Flag::ONEWAY
} else {
Flag::NONE
};
match CQDBWriter::with_flag(buf_writer, flag) {
Ok(writer) => {
let inner = Box::into_raw(Box::new(writer)) as *mut tag_cqdb_writer_inner;
Box::into_raw(Box::new(cqdb_writer_t { file: fp, inner }))
}
Err(_) => ptr::null_mut(),
}
}
}
}
#[cfg(unix)]
unsafe fn new_file_from_libc(fp: *mut FILE) -> File {
use std::os::unix::io::FromRawFd;
unsafe {
libc::fflush(fp);
let fd = libc::dup(libc::fileno(fp));
File::from_raw_fd(fd)
}
}
#[cfg(windows)]
unsafe fn new_file_from_libc(fp: *mut FILE) -> File {
use std::os::windows::io::{FromRawHandle, RawHandle};
unsafe {
libc::fflush(fp);
let fd = libc::dup(libc::fileno(fp));
let handle = libc::get_osfhandle(fd) as RawHandle;
File::from_raw_handle(handle)
}
}
ffi_fn! {
fn cqdb_writer_close(dbw: *mut cqdb_writer_t) -> c_int {
if !dbw.is_null() {
unsafe {
let inner = (*dbw).inner as *mut CQDBWriter<BufWriter<File>>;
drop(Box::from_raw(inner));
let offset = libc::lseek(libc::fileno((*dbw).file), 0, libc::SEEK_CUR);
libc::fseek((*dbw).file, offset, libc::SEEK_SET);
drop(Box::from_raw(dbw));
}
}
CQDB_SUCCESS
}
}
ffi_fn! {
fn cqdb_writer_put(
dbw: *mut cqdb_writer_t,
s: *const c_char,
id: c_int
) -> c_int {
if id < 0 {
return CQDB_ERROR_INVALIDID;
}
unsafe {
let dbw = (*dbw).inner as *mut CQDBWriter<BufWriter<File>>;
let c_str = CStr::from_ptr(s).to_str().unwrap();
if (*dbw).put(c_str, id as u32).is_err() {
return CQDB_ERROR_FILEWRITE;
}
}
CQDB_SUCCESS
}
}
#[cfg(test)]
mod test {
use super::*;
use std::{ffi::CString, fs};
#[test]
fn test_cqdb_read_cqdb_ffi() {
let name = CString::new("../tests/output/cqdb-ffi-1.cqdb").unwrap();
let mode = CString::new("wb").unwrap();
unsafe {
let fp = libc::fopen(name.as_ptr(), mode.as_ptr());
assert!(!fp.is_null());
let writer = cqdb_writer(fp, 0);
assert!(!writer.is_null());
for i in 0..100 {
let s = CString::new(format!("{:08}", i)).unwrap();
assert_eq!(0, cqdb_writer_put(writer, s.as_ptr(), i));
}
let s = CString::new(format!("{:08}", 10)).unwrap();
assert_eq!(
CQDB_ERROR_INVALIDID,
cqdb_writer_put(writer, s.as_ptr(), -1)
);
assert_eq!(0, cqdb_writer_close(writer));
libc::fclose(fp);
}
let buf = std::fs::read("../tests/output/cqdb-ffi-1.cqdb").unwrap();
let db = CQDB::new(&buf).unwrap();
assert_eq!(100, db.num());
}
#[test]
fn test_cqdb_ffi_read_cqdb_writer() {
let file = fs::File::create("../tests/output/cqdb-ffi-2.cqdb").unwrap();
let mut writer = CQDBWriter::new(file).unwrap();
for id in 0..100 {
let key = format!("{:08}", id);
writer.put(&key, id).unwrap();
}
drop(writer);
let buf = fs::read("../tests/output/cqdb-ffi-2.cqdb").unwrap();
unsafe {
let db = cqdb_reader(buf.as_ptr() as _, buf.len());
assert!(!db.is_null());
let size = cqdb_num(db);
for id in 0..size {
let key = CString::new(format!("{:08}", id)).unwrap();
let j = cqdb_to_id(db, key.as_ptr());
assert_eq!(id, j);
}
let key = CString::new("non-exists-key").unwrap();
let j = cqdb_to_id(db, key.as_ptr());
assert!(j < 0);
for id in 0..size {
let ptr = cqdb_to_string(db, id);
assert!(!ptr.is_null());
let key = CStr::from_ptr(ptr).to_str().unwrap();
assert_eq!(key, format!("{:08}", id));
}
let ptr = cqdb_to_string(db, size + 100);
assert!(ptr.is_null());
cqdb_delete(db);
}
}
}