Skip to main content

edf_rs/
file.rs

1use std::fs::{File, OpenOptions};
2use std::io::{BufRead, BufReader, Cursor, Seek, SeekFrom, Write};
3use std::iter::repeat_n;
4use std::os::unix::fs::FileExt;
5use std::path::{Path, PathBuf};
6
7use crate::EDFSpecifications;
8use crate::error::edf_error::EDFError;
9use crate::headers::annotation_list::AnnotationList;
10use crate::headers::edf_header::EDFHeader;
11use crate::headers::signal_header::SignalHeader;
12use crate::record::{Record, SpanningRecord};
13use crate::save::{SaveInstruction, SaveValue, normalize_instructions};
14use crate::utils::take_vec;
15
16/// The desired strategy to delete data-records with. This option only has an effect on EDF+ files and
17/// not on regular EDF files. It determines whether or not to shift the timestamps of data-records
18/// following a deleted data-record
19#[derive(Debug, Default, Clone, PartialEq)]
20pub enum RecordDeleteStrategy {
21    /// In case a record was deleted in the middle of a recording, the timestamps of all
22    /// records following the deleted one will be shifted forward by the duration of 1 data-record.
23    /// Therefore EDF+ files will keep the `is_continuous` state they had before deleting.
24    ///
25    /// # Note
26    /// This is not yet implemented and currently removes the record without adjusting the timestamps
27    /// of following records while keeping `is_continuous` unchanged.
28    Continuous,
29
30    /// In case a record was deleted in the middle of a recording, the timestamps of all
31    /// records following the deleted one will remain the same as they were before. This will
32    /// create a time gap between two data-records. Therefore EDF+ files will become discontinuous
33    /// if they were continuous before deleting
34    #[default]
35    Discontinuous,
36}
37
38/// The mode the EDF file is currently being edited in. It primarily handles the way the data-record count field
39/// in the file header is being treated on save. This option will not have any effect on read-only operations
40/// on an EDF file
41#[derive(Debug, Default, Clone, PartialEq)]
42pub enum SaveMode {
43    /// This mode is supposed to be used for editing / creating EDF files which are not currently being recorded
44    /// in a live setting. It primarily affects the way the header updates its data-record count field. The data-record
45    /// count will always be updated to the
46    #[default]
47    Default,
48
49    /// This mode is supposed to be used when the EDF file is constantly being updated due to currently
50    /// recording data in a live setting. It primarily affects the way the header updates its data-record count
51    /// field. While recording the count will remain at -1. Therefore after finishing the recording, the save mode
52    /// has to be changed to `SaveMode::Default` and saved again. This ensures the correct data-record count
53    /// is being saved after finishing with the recording
54    Recording,
55}
56
57pub struct EDFFile {
58    pub header: EDFHeader,
59    path: PathBuf,
60    reader: BufReader<File>,
61    record_read_offset_ns: u128,
62    instructions: Vec<SaveInstruction>,
63    signal_instructions: Vec<SaveInstruction>,
64    record_counter: usize,
65    signal_counter: usize,
66    record_delete_strategy: RecordDeleteStrategy,
67    save_mode: SaveMode,
68}
69
70impl EDFFile {
71    pub fn open<P: AsRef<Path>>(path: P) -> Result<Self, EDFError> {
72        let file = File::open(&path).map_err(EDFError::FileReadError)?;
73        let mut reader = BufReader::new(file);
74        let header = EDFHeader::deserialize(&mut reader)?;
75
76        Ok(Self {
77            record_counter: header.record_count.unwrap_or(0),
78            signal_counter: header.signal_count,
79            path: path.as_ref().to_path_buf(),
80            record_read_offset_ns: 0,
81            signal_instructions: Vec::new(),
82            instructions: Vec::new(),
83            header,
84            reader,
85            record_delete_strategy: RecordDeleteStrategy::default(),
86            save_mode: SaveMode::default(),
87        })
88    }
89
90    pub fn new<P: AsRef<Path>>(path: P) -> Result<Self, EDFError> {
91        // Ensure the provided file does not exist yet and create the empty file
92        if path.as_ref().exists() {
93            return Err(EDFError::FileAlreadyExists);
94        }
95        File::create(&path).map_err(EDFError::FileWriteError)?;
96
97        let file = File::open(&path).map_err(EDFError::FileReadError)?;
98        let reader = BufReader::new(file);
99        let header = EDFHeader::new();
100
101        Ok(Self {
102            header,
103            reader,
104            path: path.as_ref().to_path_buf(),
105            record_read_offset_ns: 0,
106            signal_counter: 0,
107            record_counter: 0,
108            signal_instructions: Vec::new(),
109            instructions: vec![SaveInstruction::WriteHeader],
110            record_delete_strategy: RecordDeleteStrategy::default(),
111            save_mode: SaveMode::default(),
112        })
113    }
114
115    /// Updates the mode for the save strategy. Setting this value will cause an updated EDF file header
116    /// on the next call of the `save()` function. See `SaveMode` for more details.
117    pub fn set_save_mode(&mut self, mode: SaveMode) {
118        self.save_mode = mode;
119        self.instructions.insert(0, SaveInstruction::WriteHeader);
120    }
121
122    pub fn insert_signal(&mut self, index: usize, signal: SignalHeader) -> Result<(), EDFError> {
123        let instruction = SaveInstruction::Insert(index, SaveValue::Signal(signal.clone()));
124        self.header.modify_signals().insert(index, signal);
125
126        // Patch all records in pending instructions
127        self.patch_records_with_instruction(instruction.clone())?;
128
129        // Add the instruction
130        self.signal_counter += 1;
131        self.signal_instructions.push(instruction);
132
133        Ok(())
134    }
135
136    pub fn update_signal(&mut self, index: usize, signal: SignalHeader) -> Result<(), EDFError> {
137        let instruction = SaveInstruction::Update(index, SaveValue::Signal(signal.clone()));
138        self.header.modify_signals()[index] = signal;
139
140        // Patch all records in pending instructions
141        self.patch_records_with_instruction(instruction.clone())?;
142
143        // Add the instruction
144        self.signal_instructions.push(instruction);
145
146        Ok(())
147    }
148
149    pub fn remove_signal(&mut self, index: usize) -> Result<(), EDFError> {
150        if self.signal_counter <= index {
151            return Err(EDFError::IndexOutOfBounds);
152        }
153        let instruction = SaveInstruction::Remove(index);
154        self.header.modify_signals().remove(index);
155
156        // Patch all records in pending instructions
157        self.patch_records_with_instruction(instruction.clone())?;
158
159        // Add the instruction
160        self.signal_counter -= 1;
161        self.signal_instructions.push(instruction);
162
163        Ok(())
164    }
165
166    fn patch_records_with_instruction(
167        &mut self,
168        instruction: SaveInstruction,
169    ) -> Result<(), EDFError> {
170        let instruction_listed = vec![instruction];
171        for record in self.instructions.iter_mut().filter_map(|i| match i {
172            SaveInstruction::Append(SaveValue::Record(record))
173            | SaveInstruction::Insert(_, SaveValue::Record(record))
174            | SaveInstruction::Update(_, SaveValue::Record(record)) => Some(record),
175            _ => None,
176        }) {
177            record.patch_record(&instruction_listed)?;
178        }
179
180        Ok(())
181    }
182
183    pub fn insert_record(&mut self, index: usize, record: Record) -> Result<(), EDFError> {
184        if !record.matches_signals(self.header.get_signals()) {
185            return Err(EDFError::InvalidRecordSignals);
186        }
187
188        self.record_counter += 1;
189        self.instructions
190            .push(SaveInstruction::Insert(index, SaveValue::Record(record)));
191
192        Ok(())
193    }
194
195    pub fn update_record(&mut self, index: usize, record: Record) -> Result<(), EDFError> {
196        if !record.matches_signals(self.header.get_signals()) {
197            return Err(EDFError::InvalidRecordSignals);
198        }
199
200        self.instructions
201            .push(SaveInstruction::Update(index, SaveValue::Record(record)));
202
203        Ok(())
204    }
205
206    pub fn append_record(&mut self, record: Record) -> Result<(), EDFError> {
207        if !record.matches_signals(self.header.get_signals()) {
208            return Err(EDFError::InvalidRecordSignals);
209        }
210
211        self.record_counter += 1;
212        self.instructions
213            .push(SaveInstruction::Append(SaveValue::Record(record)));
214
215        Ok(())
216    }
217
218    /// Removes the record at the given index. If the file is an EDF+ file, it will adjust the offset
219    /// of data-records after the given record in case `record_delete_strategy` is `ShiftOffsets` (Therefore keeping continuous
220    /// EDF+ files continuous). Otherwise it will remove the data-record without shifting the offset of subsequent
221    /// data-records (Therefore making continuous EDF+ files discontinuous).
222    pub fn remove_record(&mut self, index: usize) -> Result<(), EDFError> {
223        if self.record_counter <= index {
224            return Err(EDFError::IndexOutOfBounds);
225        }
226
227        self.record_counter -= 1;
228        self.instructions.push(SaveInstruction::Remove(index));
229
230        Ok(())
231    }
232
233    fn records_match_signals(&self) -> bool {
234        !self
235            .instructions
236            .iter()
237            .filter_map(|i| match i {
238                SaveInstruction::Append(SaveValue::Record(record))
239                | SaveInstruction::Insert(_, SaveValue::Record(record))
240                | SaveInstruction::Update(_, SaveValue::Record(record)) => Some(record),
241                _ => None,
242            })
243            .any(|record| !record.matches_signals(self.header.get_signals()))
244    }
245
246    pub fn save(&mut self) -> Result<(), EDFError> {
247        let mut file = OpenOptions::new()
248            .read(true)
249            .write(true)
250            .create(true)
251            .open(&self.path)
252            .map_err(EDFError::FileWriteError)?;
253
254        let initial_filesize = file.metadata().map_err(EDFError::FileWriteError)?.len();
255        let initial_signal_count = self.header.signal_count;
256        let initial_record_count = self.header.record_count.unwrap_or(0);
257        let initial_signals = self.header.signals.clone();
258        let initial_record_duration = self.header.record_duration;
259        let initial_header_size = self.header.header_bytes as u64;
260        let initial_record_bytes = self.header.get_initial_record_bytes();
261
262        // Update all header values to match the new state
263
264        // Update the record count if not currently recording
265        if self.save_mode == SaveMode::Default {
266            self.header.record_count = Some(self.record_counter);
267        }
268
269        // Set the new signals and update the signal count
270        if let Some(updated) = self.header.updated_signals.take() {
271            self.header.signals = updated;
272        }
273        self.header.signal_count = self.header.signals.len();
274
275        // In case there are no signals, remove all records as they will all have a length of 0 bytes
276        if self.header.signal_count == 0 {
277            self.header.record_count = self.header.record_count.map(|_| 0);
278        }
279
280        // Calculate new header and record sizes
281        self.header.header_bytes = self.header.calculate_header_bytes();
282        let new_record_bytes = self.header.data_record_bytes();
283
284        // Ensure WriteHeader is at max once (and at index 0) and automatically add it if the header changed and it is not yet present
285        let header_size_diff = self.header.header_bytes as i64 - initial_header_size as i64;
286        let header_changed =
287            *self.header.get_initial_header_sha256() != self.header.get_sha256()?;
288        let header_instruct_positions = self
289            .instructions
290            .iter()
291            .enumerate()
292            .filter_map(|(i, x)| (*x == SaveInstruction::WriteHeader).then_some(i))
293            .collect::<Vec<_>>();
294        if header_instruct_positions.len() >= 1 && header_instruct_positions[0] != 0 {
295            for i in header_instruct_positions.iter().rev() {
296                self.instructions.remove(*i);
297            }
298            self.instructions.insert(0, SaveInstruction::WriteHeader);
299        } else if header_instruct_positions.len() >= 1 {
300            for i in header_instruct_positions.iter().skip(1).rev() {
301                self.instructions.remove(*i);
302            }
303        } else if header_instruct_positions.is_empty() && header_changed {
304            self.instructions.insert(0, SaveInstruction::WriteHeader);
305        }
306
307        // Try to get the current read position to go back to after saving
308        let initial_read_position = self
309            .reader
310            .stream_position()
311            .map_err(EDFError::FileWriteError)?;
312        let initial_record_position = if initial_record_bytes == 0 {
313            None
314        } else {
315            initial_read_position
316                .checked_sub(initial_header_size)
317                .map(|pos| pos % initial_record_bytes as u64)
318        };
319
320        // If there are no instructions at all, nothing has to be done and the input remains the same
321        if self.instructions.is_empty() && self.signal_instructions.is_empty() {
322            return Ok(());
323        }
324
325        // Transform the list of instructions into a simplified sorted list of instructions
326        let instructions = normalize_instructions(&self.instructions, initial_record_count);
327        let signal_instructions =
328            normalize_instructions(&self.signal_instructions, initial_signal_count);
329
330        // Ensure all records have the correct signal layout before writing anything to disk
331        if !self.records_match_signals() {
332            return Err(EDFError::InvalidRecordSignals);
333        }
334
335        // If there were instructions, but all cancelled out, nothing has to be done either and the
336        // input remains the same again. e.g. Insert at index 1 followed by Delete at index 1.
337        if instructions.is_empty() && signal_instructions.is_empty() {
338            self.instructions.clear();
339            return Ok(());
340        }
341
342        // Depending on the delete strategy, update EDF+ files to be discontinuous after deleting a record
343        let removes_middle_record = instructions.iter().any(|i| matches!(i, SaveInstruction::Remove(idx) if *idx > 0 && *idx < self.record_counter - 1));
344        if self.header.specification == EDFSpecifications::EDFPlus
345            && self.header.is_continuous
346            && removes_middle_record
347            && self.record_delete_strategy == RecordDeleteStrategy::Discontinuous
348        {
349            self.header.is_continuous = false;
350        }
351
352        let patch_trailing_records = !signal_instructions.is_empty();
353        let mut overwrite_counter = header_size_diff;
354        let mut overwrite_buffer = Vec::new();
355        let mut record_counter = instructions
356            .iter()
357            .filter(|i| i.has_record_index())
358            .map(SaveInstruction::record_index)
359            .next()
360            .unwrap_or(0);
361        let mut instruction_idx = 0;
362
363        if patch_trailing_records {
364            record_counter = 0;
365        }
366
367        // Seek to first data-record edit position. In case the file header has to be written, this seek operation will be useless
368        file.seek(SeekFrom::Start(
369            initial_header_size + record_counter as u64 * initial_record_bytes as u64,
370        ))
371        .map_err(EDFError::FileWriteError)?;
372
373        // Loop through all instructions and perform each of them
374        loop {
375            let instruct = match instructions.get(instruction_idx) {
376                Some(instruct) => instruct,
377                None => {
378                    if patch_trailing_records {
379                        &SaveInstruction::Patch
380                    } else {
381                        break;
382                    }
383                }
384            };
385
386            match instruct {
387                SaveInstruction::WriteHeader => {
388                    instruction_idx += 1;
389
390                    // NOTE: This instruction must not be called more than once per save operation and
391                    // must be called as the first instruction if it is present!
392
393                    file.seek(SeekFrom::Start(0))
394                        .map_err(EDFError::FileWriteError)?;
395                    if let Ok(read_length) = usize::try_from(overwrite_counter)
396                        && read_length > 0
397                    {
398                        let read_max = initial_filesize.saturating_sub(initial_header_size);
399                        let read_length = read_length.min(read_max as usize);
400                        if read_length > 0 {
401                            let mut buffer = vec![0; read_length]; // TODO: This should be a function global defined buffer
402                            file.read_exact_at(&mut buffer, initial_header_size)
403                                .map_err(EDFError::FileWriteError)?;
404                            overwrite_buffer.append(&mut buffer);
405                        }
406                    }
407
408                    file.write_all(self.header.serialize()?.as_bytes())
409                        .map_err(EDFError::FileWriteError)?;
410
411                    // Require re-writing all data-records from the beginning due to a change in offset
412                    if overwrite_counter != 0 {
413                        record_counter = 0;
414                    } else if !patch_trailing_records {
415                        file.seek(SeekFrom::Start(
416                            initial_header_size
417                                + record_counter as u64 * initial_record_bytes as u64,
418                        ))
419                        .map_err(EDFError::FileWriteError)?;
420                    }
421                }
422                SaveInstruction::Remove(idx) if *idx == record_counter => {
423                    instruction_idx += 1;
424                    _ = overwrite_buffer
425                        .drain(0..initial_record_bytes.min(overwrite_buffer.len()))
426                        .count();
427                    overwrite_counter -= initial_record_bytes as i64;
428                }
429                SaveInstruction::Insert(idx, SaveValue::Record(value))
430                    if *idx == record_counter =>
431                {
432                    instruction_idx += 1;
433                    record_counter += 1;
434
435                    // println!("I - BEFORE {} / {} / {}", overwrite_counter, overwrite_buffer.len(), file.stream_position().map_err(SerializeError::FileWriteError)?.saturating_sub(1024));
436                    let read_offset = if overwrite_counter < 0 {
437                        overwrite_counter.abs()
438                    } else {
439                        0
440                    } as u64;
441                    let current_file_position =
442                        file.stream_position().map_err(EDFError::FileWriteError)? + read_offset;
443                    let read_max = initial_filesize.saturating_sub(current_file_position);
444                    if let Ok(new_buffer_length) =
445                        usize::try_from(overwrite_counter + new_record_bytes as i64)
446                        && new_buffer_length > 0
447                    {
448                        let read_length = new_buffer_length - overwrite_buffer.len(); // This will only ever be different from `new_record_bytes` in case `overwrite_counter` was negative and < `new_record_bytes`
449                        let read_length = read_length.min(read_max as usize);
450                        if read_length > 0 {
451                            let mut buffer = vec![0; read_length]; // TODO: This should be a function global defined buffer
452                            file.read_exact_at(&mut buffer, current_file_position)
453                                .map_err(EDFError::FileWriteError)?;
454                            overwrite_buffer.append(&mut buffer);
455                        }
456                    }
457
458                    file.write_all(&value.serialize()?)
459                        .map_err(EDFError::FileWriteError)?;
460                    overwrite_counter += new_record_bytes.min(read_max as usize) as i64;
461                }
462                SaveInstruction::Update(idx, SaveValue::Record(value))
463                    if *idx == record_counter =>
464                {
465                    instruction_idx += 1;
466                    record_counter += 1;
467
468                    let buffer_read_count = overwrite_buffer
469                        .drain(0..initial_record_bytes.min(overwrite_buffer.len()))
470                        .count();
471                    let disk_read_count = initial_record_bytes.saturating_sub(buffer_read_count);
472
473                    let read_offset = if overwrite_counter < 0 {
474                        overwrite_counter.abs()
475                    } else {
476                        0
477                    } as u64;
478                    let buffered_offset = if overwrite_counter > 0 {
479                        overwrite_counter
480                    } else {
481                        0
482                    } as u64;
483                    let current_file_position =
484                        file.stream_position().map_err(EDFError::FileWriteError)? + read_offset;
485
486                    // Add data to overwrite buffer which would be overwritten after writing the current record
487                    let read_max = initial_filesize.saturating_sub(current_file_position);
488                    let target_read_length =
489                        overwrite_counter + new_record_bytes as i64 - buffered_offset as i64;
490                    let read_length = u64::try_from(target_read_length)
491                        .map(|len| len.min(read_max).saturating_sub(disk_read_count as u64))
492                        .unwrap_or(0) as usize;
493                    if read_length > 0 {
494                        let mut buffer = vec![0; read_length]; // TODO: This should be a function global defined buffer
495                        file.read_exact_at(
496                            &mut buffer,
497                            current_file_position + disk_read_count as u64,
498                        )
499                        .map_err(EDFError::FileWriteError)?;
500                        overwrite_buffer.append(&mut buffer);
501                    }
502
503                    // When coming to end of file, instead of adding the diff, remove everything that would read past the file end
504                    let exceed = if target_read_length.max(0) as u64 > read_max {
505                        target_read_length.max(0) as u64 - read_max
506                    } else {
507                        0
508                    };
509                    overwrite_counter += new_record_bytes as i64
510                        - disk_read_count as i64
511                        - buffer_read_count as i64
512                        - exceed as i64;
513
514                    file.write_all(&value.serialize()?)
515                        .map_err(EDFError::FileWriteError)?;
516                }
517                SaveInstruction::Patch | _ => {
518                    // Break if the last available record has already been written
519                    if record_counter == self.record_counter {
520                        break;
521                    }
522
523                    // In case the file offset is at the beginning of the next data-record, seek to the next position
524                    // in the file where another instruction has to be handled. All records between the current
525                    // position and the target position remain entirely unchanged (as long as signals have not changed)
526                    if overwrite_counter == 0 && !patch_trailing_records {
527                        record_counter = instructions
528                            .iter()
529                            .skip(instruction_idx)
530                            .find_map(|i| {
531                                if i.record_index() != usize::MAX {
532                                    Some(i.record_index())
533                                } else {
534                                    None
535                                }
536                            })
537                            .unwrap_or(0);
538                        file.seek(SeekFrom::Start(
539                            initial_header_size
540                                + record_counter as u64 * initial_record_bytes as u64,
541                        ))
542                        .map_err(EDFError::FileWriteError)?;
543                        continue;
544                    }
545
546                    // Try to read the record from the overwrite buffer
547                    let read_offset = if overwrite_counter < 0 {
548                        overwrite_counter.abs()
549                    } else {
550                        0
551                    } as u64;
552                    let buffered_offset = if overwrite_counter > 0 {
553                        overwrite_counter
554                    } else {
555                        0
556                    } as u64;
557                    let mut buffer_read = overwrite_buffer
558                        .drain(0..initial_record_bytes.min(overwrite_buffer.len()))
559                        .collect::<Vec<_>>();
560                    let buffer_read_count = buffer_read.len();
561                    let disk_read_count = initial_record_bytes - buffer_read_count;
562
563                    // Read the remaining bytes of the current record from disk if it was not entirely in the buffer
564                    let current_file_position =
565                        file.stream_position().map_err(EDFError::FileWriteError)? + read_offset;
566                    if disk_read_count > 0 {
567                        let mut buffer = vec![0; disk_read_count]; // TODO: This should be a function global defined buffer
568                        file.read_exact_at(&mut buffer, current_file_position)
569                            .map_err(EDFError::FileWriteError)?;
570                        buffer_read.append(&mut buffer);
571                    }
572
573                    // Add data to overwrite buffer which would be overwritten after writing the current record
574                    let read_max = initial_filesize.saturating_sub(current_file_position);
575                    let target_read_length =
576                        overwrite_counter + new_record_bytes as i64 - buffered_offset as i64;
577                    let read_length = u64::try_from(target_read_length)
578                        .map(|len| len.min(read_max).saturating_sub(disk_read_count as u64))
579                        .unwrap_or(0) as usize;
580                    if read_length > 0 {
581                        let mut buffer = vec![0; read_length]; // TODO: This should be a function global defined buffer
582                        file.read_exact_at(
583                            &mut buffer,
584                            current_file_position + disk_read_count as u64,
585                        )
586                        .map_err(EDFError::FileWriteError)?;
587                        overwrite_buffer.append(&mut buffer);
588                    }
589
590                    // When coming to end of file, instead of adding the diff, remove everything that would read past the file end
591                    let exceed = if target_read_length.max(0) as u64 > read_max {
592                        target_read_length.max(0) as u64 - read_max
593                    } else {
594                        0
595                    };
596                    overwrite_counter += new_record_bytes as i64
597                        - disk_read_count as i64
598                        - buffer_read_count as i64
599                        - exceed as i64;
600
601                    // In case the signals changed, patch the record and update the buffer with the patched data
602                    if !signal_instructions.is_empty() {
603                        let cursor = Cursor::new(buffer_read);
604                        let mut reader = BufReader::new(cursor);
605                        let mut record = Self::read_record_data(
606                            &mut reader,
607                            0,
608                            &initial_signals,
609                            initial_record_duration,
610                        )?;
611                        record.patch_record(&signal_instructions)?;
612                        buffer_read = record.serialize()?;
613                    }
614
615                    file.write_all(&buffer_read)
616                        .map_err(EDFError::FileWriteError)?;
617                    record_counter += 1;
618                }
619            }
620        }
621
622        // The file size has changed, therefore either write the remaining buffered data to disk, or
623        // truncate the file (and replacing the content to be truncated with NUL bytes before)
624        if overwrite_counter != 0 {
625            if overwrite_counter > 0 {
626                file.write_all(&overwrite_buffer)
627                    .map_err(EDFError::FileWriteError)?;
628                overwrite_buffer.clear();
629            } else {
630                let reduced_by_length = overwrite_counter.abs() as usize;
631                let position = file.stream_position().map_err(EDFError::FileWriteError)?;
632                file.write_all(&repeat_n(0, reduced_by_length).collect::<Vec<_>>())
633                    .map_err(EDFError::FileWriteError)?;
634                file.set_len(position).map_err(EDFError::FileWriteError)?;
635            }
636        }
637
638        // Flush the write buffer, clear the pending instructions and get the new file length
639        file.flush().map_err(EDFError::FileWriteError)?;
640        self.instructions.clear();
641        let new_file_size = file.metadata().map_err(EDFError::FileWriteError)?.len();
642
643        // Update the initial record size and header hash so they are valid for the current state.
644        // This ensures the next save action works with the right offsets and instructions
645        self.header.update_initial_record_bytes();
646        self.header.update_initial_header_sha256()?;
647
648        // Try to seek to the position the reader initially was at
649        if let Some(record_idx) = initial_record_position {
650            let seek_pos = self.header.header_bytes as u64 + record_idx * new_record_bytes as u64;
651            self.reader
652                .seek(SeekFrom::Start(seek_pos.min(new_file_size)))
653                .map_err(EDFError::FileWriteError)?;
654        } else {
655            self.reader
656                .seek(SeekFrom::Start(0))
657                .map_err(EDFError::FileWriteError)?;
658        }
659
660        Ok(())
661    }
662
663    pub fn read_record(&mut self) -> Result<Option<Record>, EDFError> {
664        // TODO: Try to read the record from a state after save in case it was not yet saved. Meaning e.g.
665        // records A, B, C, D are stored in the EDF and then E was inserted at index 2, the records returned
666        // by reading all records from the front should result in A, B, E, C, D before and after saving. Therefore
667        // the records would have to be able to read either from in-memory pending instructions or from disk (at the
668        // correct offset). Also handle add/remove of signals to those in-memory records as well
669
670        let position = self
671            .reader
672            .stream_position()
673            .map_err(EDFError::FileReadError)?;
674
675        // Ensure the reader position is in the data-record section of the file
676        if position < self.header.header_bytes as u64 {
677            return Err(EDFError::InvalidReadRange);
678        }
679
680        // Ensure the reader position is at the beginning of a data-record
681        let record_size = self.header.data_record_bytes() as u64;
682        let record_offset = position - self.header.header_bytes as u64;
683        if record_offset % record_size != 0 {
684            return Err(EDFError::InvalidReadRange);
685        }
686
687        // Get the data-record index and check if there are any records left in the file
688        let record_idx = (position - self.header.header_bytes as u64) / record_size;
689        let record_count = self
690            .header
691            .record_count
692            .ok_or(EDFError::ReadWhileRecording)?;
693        if record_idx + 1 > record_count as u64 {
694            return Ok(None);
695        }
696
697        // Read and parse the record from disk
698        let mut record = Self::read_record_data(
699            &mut self.reader,
700            record_idx,
701            &self.header.signals,
702            self.header.record_duration,
703        )?;
704
705        // Patch the record to match the new signal definitions
706        record.patch_record(&self.instructions)?;
707
708        Ok(Some(record))
709    }
710
711    fn read_record_data<R: BufRead + Seek>(
712        reader: &mut R,
713        record_idx: u64,
714        signals: &Vec<SignalHeader>,
715        record_duration: f64,
716    ) -> Result<Record, EDFError> {
717        let mut sample_buffer = [0; 2];
718        let mut tal_buffer = vec![];
719        let mut record = Record::new(&signals);
720        record.default_offset = record_idx as f64 * record_duration;
721
722        for (i, signal) in signals.iter().enumerate() {
723            if signal.is_annotation() {
724                // Samples are 16 bit integers (1 sample has 2 bytes) therefore annotation samples are * 2
725                // as only single byte values are being read
726                let mut tals = Vec::new();
727                let mut total_read = 0;
728                while total_read < signal.samples_count * 2 {
729                    total_read += reader
730                        .read_until(b'\x00', &mut tal_buffer)
731                        .map_err(EDFError::FileReadError)?;
732
733                    // Check if EOF has been reached
734                    if tal_buffer.is_empty() {
735                        break;
736                    }
737
738                    // Check if the read value is a NUL byte, meaning it most likely reached the
739                    // padding of the TAL in the current data-record. This would mean it should probably
740                    // seek to the end of the data-record instead of reading every byte individually. There
741                    // should not be any other TAL following then
742                    if tal_buffer.len() == 1 && tal_buffer[0] == b'\x00' {
743                        tal_buffer.clear();
744                        continue;
745                    }
746
747                    // Parse the TAL and add it to the list of TALs in the current signal
748                    let tal = AnnotationList::deserialize(&take_vec(&mut tal_buffer))?;
749                    tals.push(tal);
750                }
751                record.set_annotation(i, tals)?;
752            } else {
753                let mut samples = Vec::with_capacity(signal.samples_count);
754                for _ in 0..signal.samples_count {
755                    reader
756                        .read_exact(&mut sample_buffer)
757                        .map_err(EDFError::FileReadError)?;
758                    let sample = i16::from_le_bytes(sample_buffer);
759                    samples.push(sample);
760                }
761                record.set_samples(i, samples)?;
762            }
763        }
764
765        Ok(record)
766    }
767
768    pub fn read_record_at(&mut self, index: usize) -> Result<Option<Record>, EDFError> {
769        self.seek_to_record(index)?;
770        self.read_record()
771    }
772
773    pub fn seek_to_record(&mut self, index: usize) -> Result<(), EDFError> {
774        self.reader
775            .seek(SeekFrom::Start(
776                self.header.header_bytes as u64
777                    + index as u64 * self.header.data_record_bytes() as u64,
778            ))
779            .map_err(EDFError::FileReadError)?;
780        Ok(())
781    }
782
783    pub fn seek_previous_record(&mut self) -> Result<bool, EDFError> {
784        // Check if the current reader position is already at or before the first data-record.
785        // In that case, this function will not do anything and return false.
786        let position = self
787            .reader
788            .stream_position()
789            .map_err(EDFError::FileReadError)?;
790        if position <= self.header.header_bytes as u64 {
791            return Ok(false);
792        }
793
794        self.reader
795            .seek(SeekFrom::Current(-(self.header.data_record_bytes() as i64)))
796            .map_err(EDFError::FileReadError)?;
797
798        Ok(true)
799    }
800
801    /// Reads samples and annotations for the given duration starting at the current reader position.
802    /// Regular EDF files and continuous EDF+ files will return a Vec with exactly 1 entry in each signal
803    /// in the `signal_samples` array when any data-records could be read. Discontinuous EDF+ files though can
804    /// return a Vec of any size. It will be of length 0 when the read duration is entirely between two records.
805    /// An additional item in the samples vec indicates a gap between the 2 data-records. This means e.g. the length will be 2
806    /// if you were to read 90 seconds and the first data-record is at offset 0 and the second data-record is at offset
807    /// 60 and the data-record duration is 30 seconds. Therefore there would be a gap of 30 seconds
808    /// between both of the data-records.
809    ///
810    /// Note: In case of EDF+ files the list of annotations returned will contain all
811    /// `Time-keeping Timestamped-Annotation-List` entries. Therefore if you were to read across 5 data-records,
812    /// you will get at least 5 Time-keeping TALs returned in the `annotations` of the `SpanningRecord`
813    pub fn read_nanos(&mut self, nanoseconds: u128) -> Result<SpanningRecord, EDFError> {
814        let offset_end = self.record_read_offset_ns + nanoseconds;
815        let record_duration_ns = (self.header.record_duration * 1_000_000_000.0) as u128;
816
817        // Note: In case of an error while reading a record, the buffer reader
818        // is not being reset to the original position. This means e.g. when trying to
819        // read 6 records, the reader might have read 2 records, failed at the 3rd one
820        // and did not read the rest. When calling `read_record` after this now (if it were
821        // to succeed then), it would return the 3rd record.
822        let mut records = SpanningRecord::new(&self.header);
823        let mut offset_current = self.record_read_offset_ns;
824        let mut read_start_ns = if self.seek_previous_record()? {
825            self.read_record()?
826                .map(|r| (r.get_start_offset() * 1_000_000_000.0) as u128)
827        } else {
828            None
829        };
830        let mut remaining_record_ns = 0;
831
832        // Read until either reaching the desired read duration or until no more records are available
833        while offset_current < offset_end {
834            let Some(mut record) = self.read_record()? else {
835                remaining_record_ns = 0;
836                break;
837            };
838
839            // Get the amount of nano seconds to in between the previous record and the current
840            let onset = (record.get_start_offset() * 1_000_000_000.0) as u128;
841            let skip_duration_ns = if self.header.specification == EDFSpecifications::EDF {
842                0
843            } else if let Some(previous_onset) = &read_start_ns {
844                onset - previous_onset - record_duration_ns
845            } else {
846                let already_skipped = onset - offset_current;
847                onset - already_skipped
848            };
849
850            if read_start_ns.is_none() {
851                read_start_ns = Some(onset);
852            }
853
854            let sample_frequencies = record
855                .raw_signal_samples
856                .iter()
857                .map(|s| s.len() as f64 / self.header.record_duration)
858                .collect::<Vec<_>>();
859
860            // Get the read offset in nano seconds within the current record and remove
861            // signal samples and annotations which occur before / do not last until the offset
862            let record_offset_ns = if offset_current == self.record_read_offset_ns {
863                records.insert_spanning_wait(
864                    record.get_start_offset() + self.record_read_offset_ns as f64 / 1_000_000_000.0,
865                );
866
867                // Drop samples and annotations before and not lasting until the current offset
868                if self.record_read_offset_ns > 0 {
869                    // Remove all signal samples which are before the current read offset
870                    for signal in record.raw_signal_samples.iter_mut() {
871                        let sample_freq = signal.len() as f64 / self.header.record_duration;
872                        let sample_count = (self.record_read_offset_ns as f64 / 1_000_000_000.0
873                            * sample_freq)
874                            .floor() as usize;
875                        signal.drain(..sample_count);
876                    }
877
878                    // Remove all annotations which are not record global (duration of 0) and are not
879                    // within the record read start time frame
880                    record.annotations.iter_mut().for_each(|a| {
881                        a.retain(|annotation| {
882                            if annotation.duration == 0.0 {
883                                return true;
884                            }
885                            let annotation_onset_ns = (annotation.onset * 1_000_000_000.0) as u128;
886                            let annotation_duration_ns =
887                                (annotation.duration * 1_000_000_000.0) as u128;
888                            return annotation_onset_ns + annotation_duration_ns
889                                >= read_start_ns.unwrap() + self.record_read_offset_ns;
890                        })
891                    });
892                }
893
894                self.record_read_offset_ns
895            } else {
896                0
897            };
898
899            // If there is a time gap in between records, start a new spanning entry
900            if skip_duration_ns != 0 && !records.is_spanning_wait() {
901                records.insert_spanning_wait(
902                    record.get_start_offset() + record_offset_ns as f64 / 1_000_000_000.0,
903                );
904            }
905
906            // Add the skip duration and break if the read duration has been reached (meaning the
907            // rest of the data to read was within the time gap)
908            offset_current += skip_duration_ns;
909            if offset_current >= offset_end {
910                self.seek_previous_record()?;
911                break;
912            }
913
914            // Take the desired amount of samples from the current record
915            remaining_record_ns = offset_end - offset_current;
916            let record_remaining_ns = record_duration_ns - self.record_read_offset_ns;
917            if remaining_record_ns >= record_remaining_ns {
918                for (i, signal) in record.raw_signal_samples.iter().enumerate() {
919                    records.extend_samples(i, signal.to_vec())
920                }
921
922                records.annotations.extend(record.annotations);
923                offset_current += record_remaining_ns;
924                remaining_record_ns -= record_remaining_ns;
925
926                // Reading the record has finished and therefore the next record should be read from the start again
927                self.record_read_offset_ns = 0;
928            } else {
929                for (i, signal) in record.raw_signal_samples.iter().enumerate() {
930                    let sample_freq = sample_frequencies[i];
931                    let prev_sample_count =
932                        self.record_read_offset_ns as f64 / 1_000_000_000.0 * sample_freq;
933                    let current_sample_count =
934                        remaining_record_ns as f64 / 1_000_000_000.0 * sample_freq;
935                    let total_sample_count = prev_sample_count + current_sample_count;
936                    let sample_count =
937                        (total_sample_count - prev_sample_count.floor()).floor() as usize;
938                    records.extend_samples(i, signal[..sample_count].to_vec())
939                }
940
941                // Add all annotations which start before the end of the desired read duration
942                for tal_list in record.annotations {
943                    let mut tals = Vec::new();
944                    for annotation_list in tal_list {
945                        let annotation_onset_ns = (annotation_list.onset * 1_000_000_000.0) as u128;
946                        let is_entire_record = annotation_list.duration == 0.0;
947                        let is_starting_until_read_end =
948                            annotation_onset_ns <= read_start_ns.unwrap() + offset_end;
949                        if is_entire_record || is_starting_until_read_end {
950                            tals.push(annotation_list);
951                        }
952                    }
953                    records.annotations.push(tals);
954                }
955
956                self.seek_previous_record()?;
957                break;
958            }
959
960            read_start_ns = Some(onset);
961        }
962
963        // Finish the record (to remove any potentially trailing empty spans)
964        records.finish();
965
966        // Update the current record offset after reading
967        self.record_read_offset_ns += remaining_record_ns;
968
969        Ok(records)
970    }
971
972    pub fn read_micros(&mut self, microseconds: u128) -> Result<SpanningRecord, EDFError> {
973        self.read_nanos(microseconds * 1_000)
974    }
975
976    pub fn read_millis(&mut self, milliseconds: u128) -> Result<SpanningRecord, EDFError> {
977        self.read_nanos(milliseconds * 1_000_000)
978    }
979
980    pub fn read_seconds(&mut self, seconds: u128) -> Result<SpanningRecord, EDFError> {
981        self.read_nanos(seconds * 1_000_000_000)
982    }
983
984    /// Reads samples and annotations for the given duration starting at the current reader position.
985    /// Regular EDF files and continuous EDF+ files will return a Vec with exactly 1 entry in each signal
986    /// in the `signal_samples` array when any data-records could be read. Discontinuous EDF+ files though can
987    /// return a Vec of any size. It will be of length 0 when the read duration is entirely between two records.
988    /// An additional item in the samples vec indicates a gap between the 2 data-records. This means e.g. the length will be 2
989    /// if you were to read 90 seconds and the first data-record is at offset 0 and the second data-record is at offset
990    /// 60 and the data-record duration is 30 seconds. Therefore there would be a gap of 30 seconds
991    /// between both of the data-records.
992    ///
993    /// Note: In case of EDF+ files the list of annotations returned will contain all
994    /// `Time-keeping Timestamped-Annotation-List` entries. Therefore if you were to read across 5 data-records,
995    /// you will get at least 5 Time-keeping TALs returned in the `annotations` of the `SpanningRecord`
996    ///
997    /// Note: This function converts seconds to nano seconds internally. This will result in slightly inaccurate
998    /// results due to floating point imprecision. For more exact values, use the `read_nanos(...)` or `read_millis(...)` functions
999    pub fn read_seconds_approx(&mut self, seconds: f32) -> Result<SpanningRecord, EDFError> {
1000        if seconds <= 0.0 {
1001            return Err(EDFError::InvalidReadRange);
1002        }
1003
1004        self.read_nanos((seconds as f64 * 1_000_000_000.0) as u128)
1005    }
1006}