use std::fs::File;
use std::io::{BufWriter, Cursor, Seek, SeekFrom, Write};
use std::path::Path;
use byteorder::WriteBytesExt;
use crate::encoding::{AsCodePageMark, DynEncoding};
use crate::field::{types::FieldType, DeletionFlag, FieldInfo, FieldName};
use crate::header::Header;
use crate::reading::TERMINATOR_VALUE;
use crate::reading::{TableInfo, BACKLINK_SIZE};
use crate::{Encoding, Error, ErrorKind, FieldIOError, Record, UnicodeLossy};
const FILE_TERMINATOR: u8 = 0x1A;
pub(crate) fn write_header_parts<W>(
dst: &mut W,
header: &Header,
fields_info: &[FieldInfo],
) -> Result<(), Error>
where
W: Write,
{
header
.write_to(dst)
.map_err(|error| Error::io_error(error, 0))?;
for record_info in fields_info.iter() {
record_info
.write_to(dst)
.map_err(|error| Error::io_error(error, 0))?;
}
dst.write_u8(TERMINATOR_VALUE)
.map_err(|error| Error::io_error(error, 0))?;
if header.file_type.is_visual_fox_pro() {
for _ in 0..BACKLINK_SIZE {
dst.write_u8(0).map_err(|error| Error::io_error(error, 0))?;
}
}
Ok(())
}
pub struct TableWriterBuilder {
v: Vec<FieldInfo>,
hdr: Header,
encoding: DynEncoding,
}
impl TableWriterBuilder {
pub fn new() -> Self {
Self {
v: vec![],
hdr: Header::new(0, 0, 0),
encoding: DynEncoding::new(UnicodeLossy),
}
}
pub fn with_encoding<E: Encoding + 'static>(encoding: E) -> Self {
Self {
v: vec![],
hdr: Header::new(0, 0, 0),
encoding: DynEncoding::new(encoding),
}
}
pub fn from_reader<T: std::io::Read + Seek>(reader: crate::reading::Reader<T>) -> Self {
Self::from_table_info(reader.into_table_info())
}
pub fn from_table_info(table_info: TableInfo) -> Self {
let fields_info = table_info.fields_info;
let mut hdr = table_info.header;
hdr.update_date();
hdr.num_records = 0;
Self {
v: fields_info,
hdr,
encoding: table_info.encoding,
}
}
pub fn set_encoding<E: Encoding + 'static>(mut self, encoding: E) -> Self {
self.encoding = DynEncoding::new(encoding);
self
}
pub fn add_character_field(mut self, name: FieldName, length: u8) -> Self {
self.v
.push(FieldInfo::new(name, FieldType::Character, length));
self
}
pub fn add_date_field(mut self, name: FieldName) -> Self {
self.v.push(FieldInfo::new(
name,
FieldType::Date,
FieldType::Date.size().unwrap(),
));
self
}
pub fn add_numeric_field(mut self, name: FieldName, length: u8, num_decimals: u8) -> Self {
let mut info = FieldInfo::new(name, FieldType::Numeric, length);
info.num_decimal_places = num_decimals;
self.v.push(info);
self
}
pub fn add_float_field(mut self, name: FieldName, length: u8, num_decimals: u8) -> Self {
let mut info = FieldInfo::new(name, FieldType::Float, length);
info.num_decimal_places = num_decimals;
self.v.push(info);
self
}
pub fn add_logical_field(mut self, name: FieldName) -> Self {
self.v.push(FieldInfo::new(
name,
FieldType::Logical,
FieldType::Logical
.size()
.expect("Internal error Logical field date should be known"),
));
self
}
pub fn add_integer_field(mut self, name: FieldName) -> Self {
self.v.push(FieldInfo::new(
name,
FieldType::Integer,
FieldType::Integer
.size()
.expect("Internal error Integer field date should be known"),
));
self.hdr.file_type = crate::header::Version::FoxPro2 {
supports_memo: false,
};
self
}
pub fn add_datetime_field(mut self, name: FieldName) -> Self {
self.v.push(FieldInfo::new(
name,
FieldType::DateTime,
FieldType::DateTime
.size()
.expect("Internal error datetime field date should be known"),
));
self.hdr.file_type = crate::header::Version::FoxPro2 {
supports_memo: false,
};
self
}
pub fn add_double_field(mut self, name: FieldName) -> Self {
self.v.push(FieldInfo::new(
name,
FieldType::Double,
FieldType::Double
.size()
.expect("Internal error Double field date should be known"),
));
self.hdr.file_type = crate::header::Version::FoxPro2 {
supports_memo: false,
};
self
}
pub fn add_currency_field(mut self, name: FieldName) -> Self {
self.v.push(FieldInfo::new(
name,
FieldType::Currency,
FieldType::Currency
.size()
.expect("Internal error Currency field date should be known"),
));
self.hdr.file_type = crate::header::Version::FoxPro2 {
supports_memo: false,
};
self
}
fn sync_header(&mut self) {
let mut offset_to_first_record =
Header::SIZE + (self.v.len() * FieldInfo::SIZE) + std::mem::size_of::<u8>();
if self.hdr.file_type.is_visual_fox_pro() {
offset_to_first_record += BACKLINK_SIZE as usize;
}
let size_of_record = self
.v
.iter()
.fold(1u16, |s, info| s + info.field_length as u16);
self.hdr.offset_to_first_record = offset_to_first_record as u16;
self.hdr.size_of_record = size_of_record;
self.hdr.code_page_mark = self.encoding.code_page_mark();
}
pub fn build_with_dest<W: Write + Seek>(mut self, dst: W) -> TableWriter<W> {
self.sync_header();
TableWriter::new(dst, self.v, self.hdr, self.encoding)
}
pub fn build_with_file_dest<P: AsRef<Path>>(
self,
path: P,
) -> Result<TableWriter<BufWriter<File>>, Error> {
let file = File::create(path).map_err(|err| Error::io_error(err, 0))?;
let dst = BufWriter::new(file);
Ok(self.build_with_dest(dst))
}
pub fn build_table_info(mut self) -> TableInfo {
self.sync_header();
TableInfo {
header: self.hdr,
fields_info: self.v,
encoding: self.encoding,
}
}
}
mod private {
pub trait Sealed {}
macro_rules! impl_sealed_for {
($type:ty) => {
impl Sealed for $type {}
};
}
impl_sealed_for!(bool);
impl_sealed_for!(Option<bool>);
impl_sealed_for!(std::string::String);
impl_sealed_for!(Option<std::string::String>);
impl_sealed_for!(&str);
impl_sealed_for!(f64);
impl_sealed_for!(f32);
impl_sealed_for!(i32);
impl_sealed_for!(Option<f64>);
impl_sealed_for!(Option<f32>);
impl_sealed_for!(crate::field::types::Date);
impl_sealed_for!(Option<crate::field::types::Date>);
impl_sealed_for!(crate::field::types::FieldValue);
impl_sealed_for!(crate::field::types::DateTime);
}
pub trait WritableAsDbaseField: private::Sealed {
fn write_as<E: Encoding, W: Write>(
&self,
field_info: &FieldInfo,
encoding: &E,
dst: &mut W,
) -> Result<(), ErrorKind>;
}
pub trait WritableRecord {
fn write_using<'a, W: Write>(
&self,
field_writer: &mut FieldWriter<'a, W>,
) -> Result<(), FieldIOError>;
}
impl WritableRecord for Record {
fn write_using<'a, W: Write>(
&self,
field_writer: &mut FieldWriter<'a, W>,
) -> Result<(), FieldIOError> {
while let Some(name) = field_writer.next_field_name() {
let value = self.get(name).ok_or_else(|| {
FieldIOError::new(
ErrorKind::Message(format!(
"Could not find field named '{}' in the record map",
name
)),
None,
)
})?;
field_writer.write_next_field_value(value)?;
}
Ok(())
}
}
pub struct FieldWriter<'a, W: Write> {
pub(crate) dst: &'a mut W,
pub(crate) fields_info: std::iter::Peekable<std::slice::Iter<'a, FieldInfo>>,
pub(crate) field_buffer: &'a mut Cursor<&'a mut [u8]>,
pub(crate) encoding: &'a DynEncoding,
}
impl<'a, W: Write> FieldWriter<'a, W> {
pub fn next_field_name(&mut self) -> Option<&'a str> {
self.fields_info.peek().map(|info| info.name.as_str())
}
pub fn write_next_field_value<T: WritableAsDbaseField>(
&mut self,
field_value: &T,
) -> Result<(), FieldIOError> {
if let Some(field_info) = self.fields_info.next() {
let pad_before = matches!(
field_info.field_type(),
FieldType::Numeric | FieldType::Float | FieldType::Memo
);
self.field_buffer.set_position(0);
field_value
.write_as(field_info, self.encoding, &mut self.field_buffer)
.map_err(|kind| FieldIOError::new(kind, Some(field_info.clone())))?;
let value_len = self.field_buffer.position() as usize;
let bytes_to_pad = usize::from(field_info.field_length).saturating_sub(value_len);
if bytes_to_pad > 0 && pad_before {
self.write_pad(bytes_to_pad, field_info)?;
}
let write_len = value_len.min(field_info.field_length as usize);
let field_bytes = self.field_buffer.get_ref();
self.dst
.write_all(&field_bytes[..write_len])
.map_err(|error| {
FieldIOError::new(ErrorKind::IoError(error), Some(field_info.clone()))
})?;
if bytes_to_pad > 0 && !pad_before {
self.write_pad(bytes_to_pad, field_info)?;
}
Ok(())
} else {
Err(FieldIOError::new(ErrorKind::TooManyFields, None))
}
}
fn write_pad(&mut self, len: usize, field_info: &FieldInfo) -> Result<(), FieldIOError> {
for _ in 0..len {
write!(self.dst, " ").map_err(|error| {
FieldIOError::new(ErrorKind::IoError(error), Some(field_info.clone()))
})?;
}
Ok(())
}
#[cfg(feature = "serde")]
pub(crate) fn write_next_field_raw(&mut self, value: &[u8]) -> Result<(), FieldIOError> {
if let Some(field_info) = self.fields_info.next() {
let pad_before = matches!(
field_info.field_type(),
FieldType::Numeric | FieldType::Float | FieldType::Memo
);
if value.len() == field_info.field_length as usize {
self.dst.write_all(value).map_err(|error| {
FieldIOError::new(ErrorKind::IoError(error), Some(field_info.clone()))
})?;
} else if value.len() < field_info.field_length as usize {
if pad_before {
self.write_pad(field_info.field_length as usize - value.len(), field_info)?;
}
self.dst.write_all(value).map_err(|error| {
FieldIOError::new(ErrorKind::IoError(error), Some(field_info.clone()))
})?;
if !pad_before {
self.write_pad(field_info.field_length as usize - value.len(), field_info)?;
}
} else {
self.dst
.write_all(&value[..field_info.field_length as usize])
.map_err(|error| {
FieldIOError::new(ErrorKind::IoError(error), Some(field_info.clone()))
})?;
}
Ok(())
} else {
Err(FieldIOError::new(ErrorKind::EndOfRecord, None))
}
}
pub(crate) fn write_deletion_flag(&mut self) -> std::io::Result<()> {
DeletionFlag::NotDeleted.write_to(self.dst)
}
fn all_fields_were_written(&mut self) -> bool {
self.fields_info.peek().is_none()
}
}
pub struct TableWriter<W: Write + Seek> {
dst: W,
fields_info: Vec<FieldInfo>,
header: Header,
buffer: [u8; 255],
closed: bool,
encoding: DynEncoding,
}
impl<W: Write + Seek> TableWriter<W> {
fn new(
dst: W,
fields_info: Vec<FieldInfo>,
origin_header: Header,
encoding: DynEncoding,
) -> Self {
Self {
dst,
fields_info,
header: origin_header,
buffer: [0u8; 255],
closed: false,
encoding,
}
}
pub fn write_record<R: WritableRecord>(&mut self, record: &R) -> Result<(), Error> {
if self.header.num_records == 0 {
self.write_header()?;
}
let mut field_writer = FieldWriter {
dst: &mut self.dst,
fields_info: self.fields_info.iter().peekable(),
field_buffer: &mut Cursor::new(&mut self.buffer),
encoding: &self.encoding,
};
let current_record_num = self.header.num_records as usize;
field_writer
.write_deletion_flag()
.map_err(|error| Error::io_error(error, current_record_num))?;
record
.write_using(&mut field_writer)
.map_err(|error| Error::new(error, current_record_num))?;
if !field_writer.all_fields_were_written() {
return Err(Error {
record_num: current_record_num,
field: None,
kind: ErrorKind::NotEnoughFields,
});
}
self.header.num_records += 1;
Ok(())
}
pub fn write_records<'a, R: WritableRecord + 'a, C: IntoIterator<Item = &'a R>>(
mut self,
records: C,
) -> Result<(), Error> {
for record in records.into_iter() {
self.write_record(record)?;
}
Ok(())
}
pub fn close(&mut self) -> Result<(), Error> {
if !self.closed {
self.dst
.seek(SeekFrom::Start(0))
.map_err(|error| Error::io_error(error, self.header.num_records as usize))?;
self.write_header()?;
self.dst
.seek(SeekFrom::End(0))
.map_err(|error| Error::io_error(error, self.header.num_records as usize))?;
self.dst
.write_u8(FILE_TERMINATOR)
.map_err(|error| Error::io_error(error, self.header.num_records as usize))?;
self.closed = true;
}
Ok(())
}
fn write_header(&mut self) -> Result<(), Error> {
write_header_parts(&mut self.dst, &self.header, &self.fields_info)
}
}
impl<T: Write + Seek> Drop for TableWriter<T> {
fn drop(&mut self) {
let _ = self.close();
}
}