dbase/
file.rs

1use crate::encoding::DynEncoding;
2use crate::field::{DeletionFlag, FieldsInfo, DELETION_FLAG_SIZE};
3use crate::header::Header;
4use crate::memo::MemoReader;
5use crate::reading::{ReadingOptions, BACKLINK_SIZE, TERMINATOR_VALUE};
6use crate::writing::{write_header_parts, WritableAsDbaseField};
7use crate::ErrorKind::UnsupportedCodePage;
8use crate::{
9    Error, ErrorKind, FieldConversionError, FieldIOError, FieldInfo, FieldIterator, FieldValue,
10    FieldWriter, ReadableRecord, TableInfo, WritableRecord,
11};
12use byteorder::ReadBytesExt;
13use std::fmt::{Debug, Formatter};
14use std::io::{BufReader, BufWriter, Cursor, Read, Seek, SeekFrom, Write};
15use std::path::Path;
16
17pub struct BufReadWriteFile {
18    input: BufReader<std::fs::File>,
19    output: BufWriter<std::fs::File>,
20}
21
22impl BufReadWriteFile {
23    fn new(file: std::fs::File) -> std::io::Result<Self> {
24        let input = BufReader::new(file.try_clone()?);
25        let output = BufWriter::new(file);
26
27        Ok(Self { input, output })
28    }
29}
30
31impl Read for BufReadWriteFile {
32    fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
33        self.input.read(buf)
34    }
35}
36
37impl Write for BufReadWriteFile {
38    fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
39        self.output.write(buf)
40    }
41
42    fn flush(&mut self) -> std::io::Result<()> {
43        self.output.flush()
44    }
45}
46
47impl Seek for BufReadWriteFile {
48    fn seek(&mut self, pos: SeekFrom) -> std::io::Result<u64> {
49        self.output.seek(pos)?;
50        self.input.seek(pos)
51    }
52}
53
54/// Index to a field in a record
55#[derive(Copy, Clone, Ord, PartialOrd, Eq, PartialEq, Debug)]
56pub struct FieldIndex(pub usize);
57
58/// Index to a record in a dBase file
59#[derive(Copy, Clone, Ord, PartialOrd, Eq, PartialEq, Debug)]
60pub struct RecordIndex(pub usize);
61
62/// 'reference' to a field in a dBase file.
63///
64/// - Allows to read the field content via [Self::read] or [Self::read_as]
65/// - Allows to overwrite the field content via [Self::write]
66pub struct FieldRef<'a, T> {
67    file: &'a mut File<T>,
68    record_index: RecordIndex,
69    field_index: FieldIndex,
70}
71
72impl<'a, T> Debug for FieldRef<'a, T> {
73    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
74        f.debug_struct("FieldRef")
75            .field("record_index", &self.record_index)
76            .field("field_index", &self.field_index)
77            .finish()
78    }
79}
80
81impl<'a, T> FieldRef<'a, T> {
82    fn position_in_source(&self) -> u64 {
83        let record_position = self
84            .file
85            .header
86            .record_position(self.record_index.0)
87            .unwrap() as u64;
88
89        record_position + self.position_in_record() as u64
90    }
91
92    fn position_in_record(&self) -> usize {
93        self.file
94            .fields_info
95            .field_position_in_record(self.field_index.0)
96            .expect("internal error: invalid field index")
97    }
98}
99
100impl<'a, T> FieldRef<'a, T>
101where
102    T: Seek,
103{
104    fn seek_to_beginning(&mut self) -> Result<u64, FieldIOError> {
105        let field_info = &self.file.fields_info[self.field_index.0];
106
107        self.file
108            .inner
109            .seek(SeekFrom::Start(self.position_in_source()))
110            .map_err(|e| FieldIOError::new(ErrorKind::IoError(e), Some(field_info.clone())))
111    }
112}
113
114impl<'a, T> FieldRef<'a, T>
115where
116    T: Seek + Read,
117{
118    /// Reads and returns the value
119    pub fn read(&mut self) -> Result<FieldValue, Error> {
120        self.file
121            .ensure_record_has_been_read_into_buffer(self.record_index)?;
122
123        let field_info = &self.file.fields_info[self.field_index.0];
124
125        let start_pos = self.position_in_record();
126        let field_bytes = &mut self.file.record_data_buffer.get_mut()
127            [start_pos..start_pos + field_info.field_length as usize];
128
129        FieldValue::read_from(
130            field_bytes,
131            &mut self.file.memo_reader,
132            field_info,
133            &self.file.encoding,
134            self.file.options.character_trim,
135        )
136        .map_err(|e| {
137            Error::new(
138                FieldIOError::new(e, Some(field_info.clone())),
139                self.record_index.0,
140            )
141        })
142    }
143
144    /// Reads and returns the value converted to the requested type
145    pub fn read_as<ValueType>(&mut self) -> Result<ValueType, Error>
146    where
147        ValueType: TryFrom<FieldValue, Error = FieldConversionError>,
148    {
149        let value = self.read()?;
150
151        let converted_value = ValueType::try_from(value).map_err(|e| {
152            let field_info = &self.file.fields_info[self.field_index.0];
153            Error::new(
154                FieldIOError::new(ErrorKind::BadConversion(e), Some(field_info.clone())),
155                self.record_index.0,
156            )
157        })?;
158
159        Ok(converted_value)
160    }
161}
162
163impl<'a, T> FieldRef<'a, T>
164where
165    T: Seek + Write,
166{
167    /// Writes the value
168    pub fn write<ValueType>(&mut self, value: &ValueType) -> Result<(), Error>
169    where
170        ValueType: WritableAsDbaseField,
171    {
172        self.file.file_position = self
173            .seek_to_beginning()
174            .map_err(|e| Error::new(e, self.record_index.0))?;
175
176        let field_info = &self.file.fields_info[self.field_index.0];
177
178        let start_pos = self.position_in_record();
179        let field_bytes = &mut self.file.record_data_buffer.get_mut()
180            [start_pos..start_pos + field_info.field_length as usize];
181        field_bytes.fill(0);
182
183        // Note that since we modify the internal buffer, we don't need to re-read the
184        // record / buffer, meaning if a user writes then reads it should get correct
185        // value, and we did not re-read from file.
186        let mut cursor = Cursor::new(field_bytes);
187        value
188            .write_as(field_info, &self.file.encoding, &mut cursor)
189            .map_err(|e| {
190                Error::new(
191                    FieldIOError::new(e, Some(field_info.clone())),
192                    self.record_index.0,
193                )
194            })?;
195
196        let buffer = cursor.into_inner();
197
198        self.file.inner.write_all(buffer).map_err(|e| {
199            Error::new(
200                FieldIOError::new(ErrorKind::IoError(e), Some(field_info.clone())),
201                self.record_index.0,
202            )
203        })?;
204
205        self.file.file_position += buffer.len() as u64;
206
207        Ok(())
208    }
209}
210
211/// 'reference' to a record in a dBase file.
212///
213/// This can be used to read/write the whole record at once,
214/// or select a particular field in the file [Self::field].
215///
216/// - Allows to read the field content via [Self::read] or [Self::read_as]
217/// - Allows to overwrite the field content via [Self::write]
218pub struct RecordRef<'a, T> {
219    file: &'a mut File<T>,
220    index: RecordIndex,
221}
222
223impl<'a, T> Debug for RecordRef<'a, T> {
224    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
225        f.debug_struct("RecordRef")
226            .field("index", &self.index)
227            .finish()
228    }
229}
230
231impl<'a, T> RecordRef<'a, T> {
232    pub fn field<'b>(&'b mut self, index: FieldIndex) -> Option<FieldRef<'b, T>> {
233        if index.0 >= self.file.fields_info.len() {
234            return None;
235        }
236        Some(FieldRef {
237            file: self.file,
238            record_index: self.index,
239            field_index: index,
240        })
241    }
242
243    fn position_in_source(&self) -> u64 {
244        self.file.header.record_position(self.index.0).unwrap() as u64
245    }
246}
247
248impl<'a, T> RecordRef<'a, T>
249where
250    T: Seek,
251{
252    pub fn seek_before_deletion_flag(&mut self) -> Result<u64, FieldIOError> {
253        self.file
254            .inner
255            .seek(SeekFrom::Start(self.position_in_source()))
256            .map_err(|e| FieldIOError::new(ErrorKind::IoError(e), None))
257    }
258}
259
260impl<'a, T> RecordRef<'a, T>
261where
262    T: Read + Seek,
263{
264    /// Returns the value of the special deletion flag
265    ///
266    /// - true -> the record is marked as deleted
267    /// - false -> the record is **not** marked as deleted
268    pub fn is_deleted(&mut self) -> Result<bool, Error> {
269        self.file
270            .ensure_record_has_been_read_into_buffer(self.index)?;
271        let deletion_flag = DeletionFlag::from_byte(self.file.record_data_buffer.get_ref()[0]);
272
273        Ok(deletion_flag == DeletionFlag::Deleted)
274    }
275
276    /// reads a field from the record
277    ///
278    /// Shortcut for `.field(index).unwrap().read().unwrap();`
279    pub fn read_field(&mut self, field_index: FieldIndex) -> Result<FieldValue, Error> {
280        let record_index = self.index.0;
281        let mut field = self
282            .field(field_index)
283            .ok_or_else(|| Error::new(FieldIOError::end_of_record(), record_index))?;
284        field.read()
285    }
286
287    /// reads a field from the record
288    ///
289    /// Shortcut for `.field(index).unwrap().read_as().unwrap();`
290    pub fn read_field_as<ValueType>(&mut self, field_index: FieldIndex) -> Result<ValueType, Error>
291    where
292        ValueType: TryFrom<FieldValue, Error = FieldConversionError>,
293    {
294        let record_index = self.index.0;
295        let mut field = self
296            .field(field_index)
297            .ok_or_else(|| Error::new(FieldIOError::end_of_record(), record_index))?;
298        field.read_as()
299    }
300
301    /// Reads the record
302    pub fn read(&mut self) -> Result<crate::Record, Error> {
303        self.read_as()
304    }
305
306    /// Reads the record as the given type
307    pub fn read_as<R>(&mut self) -> Result<R, Error>
308    where
309        R: ReadableRecord,
310    {
311        self.file
312            .ensure_record_has_been_read_into_buffer(self.index)?;
313        self.file
314            .record_data_buffer
315            .set_position(DELETION_FLAG_SIZE as u64);
316        let mut field_iterator = FieldIterator {
317            source: &mut self.file.record_data_buffer,
318            fields_info: self.file.fields_info.iter().peekable(),
319            memo_reader: &mut self.file.memo_reader,
320            field_data_buffer: &mut self.file.field_data_buffer,
321            encoding: &self.file.encoding,
322            options: self.file.options,
323        };
324
325        R::read_using(&mut field_iterator).map_err(|error| Error::new(error, self.index.0))
326    }
327}
328
329impl<'a, T> RecordRef<'a, T>
330where
331    T: Write + Seek,
332{
333    /// writes a field to the record
334    ///
335    /// Shortcut for `.field(index).unwrap().write(&value).unwrap();`
336    pub fn write_field<ValueType>(
337        &mut self,
338        field_index: FieldIndex,
339        value: &ValueType,
340    ) -> Result<(), Error>
341    where
342        ValueType: WritableAsDbaseField,
343    {
344        let record_index = self.index.0;
345        let mut field = self
346            .field(field_index)
347            .ok_or_else(|| Error::new(FieldIOError::end_of_record(), record_index))?;
348        field.write(value)
349    }
350
351    /// Writes the content of `record` ath the position
352    /// pointed by `self`.
353    pub fn write<R>(&mut self, record: &R) -> Result<(), Error>
354    where
355        R: WritableRecord,
356    {
357        self.file.record_data_buffer.get_mut().fill(0);
358        self.file.record_data_buffer.get_mut()[0] = DeletionFlag::NotDeleted.to_byte();
359        self.file.record_data_buffer.set_position(1);
360
361        let mut field_writer = FieldWriter {
362            dst: &mut self.file.record_data_buffer,
363            fields_info: self.file.fields_info.iter().peekable(),
364            field_buffer: &mut Cursor::new(&mut self.file.field_data_buffer),
365            encoding: &self.file.encoding,
366        };
367
368        record
369            .write_using(&mut field_writer)
370            .map_err(|error| Error::new(error, self.index.0))?;
371
372        self.seek_before_deletion_flag()
373            .map_err(|error| Error::new(error, self.index.0))?;
374
375        self.file
376            .inner
377            .write_all(self.file.record_data_buffer.get_ref())
378            .map_err(|error| Error::io_error(error, self.index.0))?;
379
380        // We don't need to update the file's inner position as we re-wrote the whole record
381        debug_assert_eq!(
382            self.file.file_position,
383            self.file.inner.stream_position().unwrap()
384        );
385
386        Ok(())
387    }
388}
389
390/// Iterator over the records in a File
391pub struct FileRecordIterator<'a, T> {
392    file: &'a mut File<T>,
393    current_record: RecordIndex,
394}
395
396impl<'a, T> FileRecordIterator<'a, T>
397where
398    T: Seek + Read,
399{
400    // To implement iterator we need the Iterator trait to make use of GATs
401    // which is not the case, to iteration will have to use the while let Some() pattern
402    pub fn next<'s>(&'s mut self) -> Option<RecordRef<'s, T>> {
403        let record_ref = self.file.record(self.current_record.0);
404        if let Some(_) = record_ref {
405            self.current_record.0 += 1
406        }
407        record_ref
408    }
409}
410
411/// Handle to a dBase File.
412///
413/// A `File`, allows to both read and write, it also
414/// allows to do modifications on an existing file,
415/// and enables to only read/modify parts of a file without
416/// first having to fully read it.
417///
418/// # Example
419///
420/// ```
421/// # fn main() -> Result<(), Box<dyn std::error::Error>> {
422/// let mut file = dbase::File::open_read_only("tests/data/stations.dbf")?;
423///
424/// assert_eq!(file.num_records(), 86);
425///
426/// let name_idx = file.field_index("name").unwrap();
427/// let marker_color_idx = file.field_index("marker-col").unwrap();
428/// let marker_symbol_idx = file.field_index("marker-sym").unwrap();
429///
430/// // Test manually reading fields (not in correct order) to FieldValue
431/// let mut rh = file.record(3).unwrap();
432/// let marker_color = rh.field(marker_color_idx).unwrap().read()?;
433/// assert_eq!(
434///    marker_color,
435///    dbase::FieldValue::Character(Some("#ff0000".to_string()))
436/// );
437/// let name = rh.field(name_idx).unwrap().read()?;
438/// assert_eq!(
439///    name,
440///    dbase::FieldValue::Character(Some("Judiciary Sq".to_string()))
441/// );
442/// let marker_symbol = rh.field(marker_symbol_idx).unwrap().read()?;
443/// assert_eq!(
444///    marker_symbol,
445///    dbase::FieldValue::Character(Some("rail-metro".to_string()))
446/// );
447/// # Ok(())
448/// # }
449/// ```
450pub struct File<T> {
451    pub(crate) inner: T,
452    memo_reader: Option<MemoReader<T>>,
453    pub(crate) header: Header,
454    pub(crate) fields_info: FieldsInfo,
455    pub(crate) encoding: DynEncoding,
456    /// Buffer that contains a whole record worth of data
457    /// It also contains the deletion flag
458    record_data_buffer: Cursor<Vec<u8>>,
459    /// Non-Memo field length is stored on a u8,
460    /// so fields cannot exceed 255 bytes
461    field_data_buffer: [u8; 255],
462    pub(crate) options: ReadingOptions,
463    /// We track the position in the file
464    /// to avoid calling `seek` when we are reading buffer
465    /// in order (0, 1, 2, etc)
466    file_position: u64,
467}
468
469impl<T> File<T> {
470    /// Returns the information about fields present in the records
471    pub fn fields(&self) -> &[FieldInfo] {
472        self.fields_info.as_ref()
473    }
474
475    /// Returns the field index that corresponds to the given name
476    pub fn field_index(&self, name: &str) -> Option<FieldIndex> {
477        self.fields_info
478            .iter()
479            .position(|info| info.name.eq_ignore_ascii_case(name))
480            .map(FieldIndex)
481    }
482
483    /// Returns the number of records in the file
484    pub fn num_records(&self) -> usize {
485        self.header.num_records as usize
486    }
487
488    pub fn set_options(&mut self, options: ReadingOptions) {
489        self.options = options;
490    }
491}
492
493impl<T: Read + Seek> File<T> {
494    /// creates of File using source as the storage space.
495    pub fn open(mut source: T) -> Result<Self, Error> {
496        let mut header =
497            Header::read_from(&mut source).map_err(|error| Error::io_error(error, 0))?;
498
499        let offset = if header.file_type.is_visual_fox_pro() {
500            if BACKLINK_SIZE > header.offset_to_first_record {
501                panic!("Invalid file");
502            }
503            header.offset_to_first_record - BACKLINK_SIZE
504        } else {
505            header.offset_to_first_record
506        };
507        let num_fields =
508            (offset as usize - Header::SIZE - std::mem::size_of::<u8>()) / FieldInfo::SIZE;
509
510        let fields_info =
511            FieldsInfo::read_from(&mut source, num_fields).map_err(|error| Error {
512                record_num: 0,
513                field: None,
514                kind: error,
515            })?;
516
517        let terminator = source
518            .read_u8()
519            .map_err(|error| Error::io_error(error, 0))?;
520
521        debug_assert_eq!(terminator, TERMINATOR_VALUE);
522
523        source
524            .seek(SeekFrom::Start(u64::from(header.offset_to_first_record)))
525            .map_err(|error| Error::io_error(error, 0))?;
526
527        let encoding = header.code_page_mark.to_encoding().ok_or_else(|| {
528            let field_error = FieldIOError::new(UnsupportedCodePage(header.code_page_mark), None);
529            Error::new(field_error, 0)
530        })?;
531
532        let record_size: usize = DELETION_FLAG_SIZE + fields_info.size_of_all_fields();
533        let record_data_buffer = Cursor::new(vec![0u8; record_size]);
534        // Some file seems not to include the DELETION_FLAG_SIZE into the record size,
535        // but we rely on it
536        header.size_of_record = record_size as u16;
537        // debug_assert_eq!(record_size - DELETION_FLAG_SIZE, header.size_of_record as usize);
538
539        Ok(Self {
540            inner: source,
541            memo_reader: None,
542            header,
543            fields_info,
544            encoding,
545            record_data_buffer,
546            field_data_buffer: [0u8; 255],
547            options: ReadingOptions::default(),
548            file_position: header.offset_to_first_record as u64,
549        })
550    }
551
552    /// Returns a reference to the record at the given index.
553    ///
554    /// Returns None if no record exist for the given index
555    pub fn record(&mut self, index: usize) -> Option<RecordRef<'_, T>> {
556        if index >= self.header.num_records as usize {
557            None
558        } else {
559            let record_ref = RecordRef {
560                file: self,
561                index: RecordIndex(index),
562            };
563            Some(record_ref)
564        }
565    }
566
567    /// Returns an iterator over the records in the file.
568    ///
569    /// Always starts at the first record
570    pub fn records(&mut self) -> FileRecordIterator<'_, T> {
571        FileRecordIterator {
572            file: self,
573            current_record: RecordIndex(0),
574        }
575    }
576
577    /// Returns true if it read from the source, false otherwise (used in tests).
578    fn ensure_record_has_been_read_into_buffer(
579        &mut self,
580        record_index: RecordIndex,
581    ) -> Result<bool, Error> {
582        let record_ref = RecordRef {
583            file: self,
584            index: record_index,
585        };
586        let start_of_record_pos = record_ref.position_in_source();
587        let end_of_record_pos = start_of_record_pos + u64::from(self.header.size_of_record);
588
589        if self.file_position > start_of_record_pos && self.file_position <= end_of_record_pos {
590            // If pos is in this range, then the record was already read into the buffer
591            return Ok(false);
592        }
593
594        if start_of_record_pos != self.file_position {
595            // Only call seek of the record we need to read
596            // is the just after the one we read last
597            self.file_position = self
598                .inner
599                .seek(SeekFrom::Start(start_of_record_pos))
600                .map_err(|e| Error::io_error(e, record_index.0))?;
601        }
602
603        self.inner
604            .read_exact(self.record_data_buffer.get_mut())
605            .map_err(|e| Error::io_error(e, record_index.0))?;
606        self.file_position += self.record_data_buffer.get_mut().len() as u64;
607        Ok(true)
608    }
609}
610
611impl<T: Write + Seek> File<T> {
612    pub fn create_new(mut dst: T, table_info: TableInfo) -> Result<Self, Error> {
613        write_header_parts(&mut dst, &table_info.header, &table_info.fields_info)?;
614        let record_size: usize = DELETION_FLAG_SIZE
615            + table_info
616                .fields_info
617                .iter()
618                .map(|i| i.field_length as usize)
619                .sum::<usize>();
620        let record_data_buffer = Cursor::new(vec![0u8; record_size]);
621        let file_position = table_info.header.offset_to_first_record as u64;
622        debug_assert_eq!(file_position, dst.stream_position().unwrap());
623        Ok(Self {
624            inner: dst,
625            memo_reader: None,
626            header: table_info.header,
627            fields_info: FieldsInfo {
628                inner: table_info.fields_info,
629            },
630            encoding: table_info.encoding,
631            record_data_buffer,
632            field_data_buffer: [0u8; 255],
633            options: ReadingOptions::default(),
634            file_position,
635        })
636    }
637
638    pub fn append_record<R>(&mut self, record: &R) -> Result<(), Error>
639    where
640        R: WritableRecord,
641    {
642        self.append_records(std::slice::from_ref(record))
643    }
644
645    pub fn append_records<R>(&mut self, records: &[R]) -> Result<(), Error>
646    where
647        R: WritableRecord,
648    {
649        assert_eq!(
650            self.header
651                .num_records
652                .overflowing_add(records.len() as u32)
653                .1,
654            false,
655            "Too many records (u32 overflow)"
656        );
657
658        let end_of_last_record = self.header.offset_to_first_record as u64
659            + (self.num_records() as u64 * self.header.size_of_record as u64);
660
661        self.inner
662            .seek(SeekFrom::Start(end_of_last_record))
663            .map_err(|error| Error::io_error(error, self.num_records()))?;
664
665        for record in records {
666            let current_record_index = self.header.num_records + 1;
667
668            let mut field_writer = FieldWriter {
669                dst: &mut self.inner,
670                fields_info: self.fields_info.iter().peekable(),
671                field_buffer: &mut Cursor::new(&mut self.field_data_buffer),
672                encoding: &self.encoding,
673            };
674
675            field_writer
676                .write_deletion_flag()
677                .map_err(|error| Error::io_error(error, current_record_index as usize))?;
678
679            record
680                .write_using(&mut field_writer)
681                .map_err(|error| Error::new(error, current_record_index as usize))?;
682
683            self.header.num_records = current_record_index;
684        }
685
686        self.sync_all()
687            .map_err(|error| Error::io_error(error, self.num_records()))?;
688
689        Ok(())
690    }
691
692    pub fn sync_all(&mut self) -> std::io::Result<()> {
693        let current_pos = self.inner.stream_position()?;
694        self.inner.seek(SeekFrom::Start(0))?;
695        self.header.write_to(&mut self.inner)?;
696        self.inner.seek(SeekFrom::Start(current_pos))?;
697        Ok(())
698    }
699}
700
701impl File<BufReadWriteFile> {
702    pub fn open_with_options<P: AsRef<Path>>(
703        path: P,
704        options: std::fs::OpenOptions,
705    ) -> Result<Self, Error> {
706        let file = options
707            .open(path)
708            .map_err(|error| Error::io_error(error, 0))?;
709        File::open(BufReadWriteFile::new(file).unwrap())
710    }
711
712    /// Opens an existing dBase file in read only mode
713    pub fn open_read_only<P: AsRef<Path>>(path: P) -> Result<Self, Error> {
714        let file = std::fs::File::open(path.as_ref()).map_err(|error| Error::io_error(error, 0))?;
715
716        let mut file = File::open(BufReadWriteFile::new(file).unwrap())?;
717        if file.fields_info.at_least_one_field_is_memo() {
718            let p = path.as_ref();
719            let memo_type = file.header.file_type.supported_memo_type();
720            if let Some(mt) = memo_type {
721                let memo_path = p.with_extension(mt.extension());
722
723                let memo_file = std::fs::File::open(memo_path).map_err(|error| Error {
724                    record_num: 0,
725                    field: None,
726                    kind: ErrorKind::ErrorOpeningMemoFile(error),
727                })?;
728
729                let memo_reader = BufReadWriteFile::new(memo_file)
730                    .and_then(|memo_file| MemoReader::new(mt, memo_file))
731                    .map_err(|error| Error::io_error(error, 0))?;
732
733                file.memo_reader = Some(memo_reader);
734            }
735        }
736        Ok(file)
737    }
738
739    /// Opens an existing dBase file in write only mode
740    pub fn open_write_only<P: AsRef<Path>>(path: P) -> Result<Self, Error> {
741        let mut options = std::fs::OpenOptions::new();
742        options
743            .read(false)
744            .write(true)
745            .create(false)
746            .truncate(false);
747
748        File::open_with_options(path, options)
749    }
750
751    /// Opens an existing dBase file in read **and** write mode
752    pub fn open_read_write<P: AsRef<Path>>(path: P) -> Result<Self, Error> {
753        let mut options = std::fs::OpenOptions::new();
754        options.read(true).write(true).create(false).truncate(false);
755
756        File::open_with_options(path, options)
757    }
758
759    /// This function will create a file if it does not exist, and will truncate it if it does.
760    pub fn create<P: AsRef<Path>>(path: P, table_info: TableInfo) -> Result<Self, Error> {
761        let file = std::fs::File::create(path).map_err(|error| Error::io_error(error, 0))?;
762
763        File::create_new(BufReadWriteFile::new(file).unwrap(), table_info)
764    }
765}
766
767#[cfg(test)]
768mod tests {
769    #[test]
770    fn ensure_record_has_been_read_into_buffer() {
771        let mut file = crate::File::open_read_only("tests/data/stations.dbf").unwrap();
772
773        {
774            let mut record = file.record(0).unwrap();
775            let _ = record.read_field(crate::FieldIndex(0)).unwrap();
776            // Must return false, meaning it correctly understands the record 0 is in memory
777            assert!(!file
778                .ensure_record_has_been_read_into_buffer(crate::RecordIndex(0))
779                .unwrap());
780            assert!(file
781                .ensure_record_has_been_read_into_buffer(crate::RecordIndex(1))
782                .unwrap());
783        }
784
785        {
786            let mut record = file.record(4).unwrap();
787            let _ = record.read_field(crate::FieldIndex(3)).unwrap();
788            // Must return false, meaning it correctly understands the record 4 is in memory
789            assert!(!file
790                .ensure_record_has_been_read_into_buffer(crate::RecordIndex(4))
791                .unwrap());
792            assert!(file
793                .ensure_record_has_been_read_into_buffer(crate::RecordIndex(1))
794                .unwrap());
795        }
796
797        // Make sure writing a field still work with the ensure mechanism
798        {
799            let mut record = file.record(10).unwrap();
800            let value = record.read_field(crate::FieldIndex(2)).unwrap();
801            // Use record.file to avoid double borrow
802            assert!(!record
803                .file
804                .ensure_record_has_been_read_into_buffer(crate::RecordIndex(10))
805                .unwrap());
806            record.write_field(crate::FieldIndex(2), &value).unwrap();
807            assert!(!file
808                .ensure_record_has_been_read_into_buffer(crate::RecordIndex(10))
809                .unwrap());
810        }
811    }
812}