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