1use memmap2::{Mmap, MmapOptions};
2use rayon::prelude::*;
3use std::cmp::min;
4use std::fs::File;
5use std::io::{BufReader, Read, Seek, SeekFrom};
6use std::path::{Path, PathBuf};
7use std::sync::Arc;
8use tracing::{debug, info, trace, warn};
9
10use crate::errors::{Result, WinxError};
11
12pub const DIRECT_READ_THRESHOLD: u64 = 10_000_000;
14
15pub const MAX_MMAP_SIZE: u64 = 1_000_000_000;
17
18pub const MAX_SEGMENTED_MMAP_SIZE: u64 = 4_000_000_000;
20
21pub const SEGMENT_SIZE: u64 = 256_000_000;
23
24const DIRECT_READ_CHUNK_SIZE: usize = 1_048_576;
25const STREAMING_CHUNK_SIZE: usize = 4_194_304;
26
27const MAX_PREALLOC_BYTES: usize = 64 * 1024 * 1024;
34
35pub fn read_file_optimized(path: &Path, max_file_size: u64) -> Result<Vec<u8>> {
56 let file = File::open(path).map_err(|e| WinxError::FileAccessError {
58 path: path.to_path_buf(),
59 message: format!("Error opening file: {e}"),
60 })?;
61
62 let metadata = file.metadata().map_err(|e| WinxError::FileAccessError {
63 path: path.to_path_buf(),
64 message: format!("Failed to get file metadata: {e}"),
65 })?;
66
67 let file_size = metadata.len();
69 if file_size > max_file_size {
70 return Err(WinxError::FileTooLarge {
71 path: path.to_path_buf(),
72 size: file_size,
73 max_size: max_file_size,
74 });
75 }
76
77 if file_size < DIRECT_READ_THRESHOLD {
79 debug!("Using direct read for file: {}", path.display());
80 read_direct(&file, file_size, path)
81 } else if file_size < MAX_MMAP_SIZE {
82 debug!("Using memory-mapped read for file: {}", path.display());
83 read_mmap(&file, path)
84 } else if file_size < MAX_SEGMENTED_MMAP_SIZE {
85 debug!("Using segmented memory-mapped read for file: {}", path.display());
86 read_segmented_mmap(&file, file_size, path)
87 } else {
88 debug!("Using streaming read for extremely large file: {}", path.display());
89 read_streaming(&file, file_size, path)
90 }
91}
92
93fn read_direct(file: &File, file_size: u64, path: &Path) -> Result<Vec<u8>> {
111 if file_size < 1_000_000 {
113 let mut buffer = Vec::with_capacity(min(file_size as usize, MAX_PREALLOC_BYTES));
115
116 let mut file_handle = file.try_clone().map_err(|e| WinxError::FileAccessError {
118 path: path.to_path_buf(),
119 message: format!("Error cloning file handle: {e}"),
120 })?;
121
122 file_handle.seek(SeekFrom::Start(0)).map_err(|e| WinxError::FileAccessError {
123 path: path.to_path_buf(),
124 message: format!("Error seeking to start of file: {e}"),
125 })?;
126
127 let mut reader = BufReader::with_capacity(min(file_size as usize, 64 * 1024), file_handle);
129
130 reader.read_to_end(&mut buffer).map_err(|e| WinxError::FileAccessError {
132 path: path.to_path_buf(),
133 message: format!("Error reading file: {e}"),
134 })?;
135
136 return Ok(buffer);
137 }
138
139 let mut buffer = Vec::with_capacity(file_size as usize);
141
142 let mut file_handle = file.try_clone().map_err(|e| WinxError::FileAccessError {
144 path: path.to_path_buf(),
145 message: format!("Error cloning file handle: {e}"),
146 })?;
147
148 file_handle.seek(SeekFrom::Start(0)).map_err(|e| WinxError::FileAccessError {
149 path: path.to_path_buf(),
150 message: format!("Error seeking to start of file: {e}"),
151 })?;
152
153 let mut reader = BufReader::with_capacity(262_144, file_handle); let mut chunk = vec![0; DIRECT_READ_CHUNK_SIZE];
156 let mut bytes_read = 0;
157
158 loop {
159 match reader.read(&mut chunk) {
160 Ok(0) => break, Ok(n) => {
162 buffer.extend_from_slice(&chunk[..n]);
163 bytes_read += n as u64;
164
165 if file_size > 5_000_000 && bytes_read % 5_000_000 < DIRECT_READ_CHUNK_SIZE as u64 {
167 trace!(
168 "Read progress for {}: {}MB/{}MB ({}%)",
169 path.display(),
170 bytes_read / 1_000_000,
171 file_size / 1_000_000,
172 bytes_read * 100 / file_size
173 );
174 }
175 }
176 Err(e) => {
177 return Err(WinxError::FileAccessError {
178 path: path.to_path_buf(),
179 message: format!("Error reading file chunk: {e}"),
180 });
181 }
182 }
183 }
184
185 Ok(buffer)
186}
187
188fn read_mmap(file: &File, path: &Path) -> Result<Vec<u8>> {
206 if file.metadata().map_or(0, |m| m.len()) == 0 {
208 return Ok(Vec::new());
209 }
210
211 let mmap = unsafe { MmapOptions::new().map(file) }.map_err(|e| WinxError::FileAccessError {
219 path: path.to_path_buf(),
220 message: format!("Failed to memory-map file: {e}"),
221 })?;
222
223 Ok(mmap.to_vec())
230}
231
232fn read_segmented_mmap(file: &File, file_size: u64, path: &Path) -> Result<Vec<u8>> {
251 let segment_count = file_size.div_ceil(SEGMENT_SIZE);
253 debug!(
254 "Reading file {} in {} segments of {}MB each",
255 path.display(),
256 segment_count,
257 SEGMENT_SIZE / 1_000_000
258 );
259
260 let mut result = Vec::with_capacity(min(file_size as usize, MAX_PREALLOC_BYTES));
262
263 for i in 0..segment_count {
265 let segment_start = i * SEGMENT_SIZE;
266 let segment_size = min(SEGMENT_SIZE, file_size - segment_start);
267
268 info!(
269 "Processing segment {}/{} of file {} ({:.1}%)",
270 i + 1,
271 segment_count,
272 path.display(),
273 (segment_start as f64 / file_size as f64) * 100.0
274 );
275
276 let segment_mmap = unsafe {
281 MmapOptions::new().offset(segment_start).len(segment_size as usize).map(file)
282 }
283 .map_err(|e| WinxError::FileAccessError {
284 path: path.to_path_buf(),
285 message: format!("Failed to memory-map file segment {i}: {e}"),
286 })?;
287
288 result.extend_from_slice(&segment_mmap);
290 }
291
292 Ok(result)
293}
294
295fn read_streaming(file: &File, file_size: u64, path: &Path) -> Result<Vec<u8>> {
314 warn!(
315 "Reading extremely large file ({}GB) with streaming approach: {}",
316 file_size / 1_000_000_000,
317 path.display()
318 );
319
320 let initial_capacity = min(file_size as usize, 1_000_000_000); let mut buffer = Vec::with_capacity(initial_capacity);
323
324 let mut reader = BufReader::with_capacity(STREAMING_CHUNK_SIZE, file);
325 let mut chunk = vec![0; STREAMING_CHUNK_SIZE];
326 let mut bytes_read = 0;
327
328 loop {
329 match reader.read(&mut chunk) {
330 Ok(0) => break, Ok(n) => {
332 buffer.extend_from_slice(&chunk[..n]);
333 bytes_read += n as u64;
334
335 if bytes_read % 100_000_000 < STREAMING_CHUNK_SIZE as u64 {
337 info!(
338 "Read progress for large file {}: {:.2}GB/{:.2}GB ({:.1}%)",
339 path.display(),
340 bytes_read as f64 / 1_000_000_000.0,
341 file_size as f64 / 1_000_000_000.0,
342 bytes_read as f64 * 100.0 / file_size as f64
343 );
344 }
345 }
346 Err(e) => {
347 return Err(WinxError::FileAccessError {
348 path: path.to_path_buf(),
349 message: format!("Error reading file chunk at position {bytes_read}: {e}"),
350 });
351 }
352 }
353 }
354
355 Ok(buffer)
356}
357
358pub fn read_file_segment(
378 path: &Path,
379 offset: u64,
380 length: u64,
381 max_file_size: u64,
382) -> Result<Vec<u8>> {
383 let file = File::open(path).map_err(|e| WinxError::FileAccessError {
385 path: path.to_path_buf(),
386 message: format!("Error opening file: {e}"),
387 })?;
388
389 let metadata = file.metadata().map_err(|e| WinxError::FileAccessError {
390 path: path.to_path_buf(),
391 message: format!("Failed to get file metadata: {e}"),
392 })?;
393
394 let file_size = metadata.len();
396 if file_size > max_file_size {
397 return Err(WinxError::FileTooLarge {
398 path: path.to_path_buf(),
399 size: file_size,
400 max_size: max_file_size,
401 });
402 }
403
404 if offset >= file_size {
406 return Err(WinxError::FileAccessError {
407 path: path.to_path_buf(),
408 message: format!("Offset {offset} exceeds file size {file_size}"),
409 });
410 }
411
412 let length = min(length, file_size - offset);
414
415 if length < DIRECT_READ_THRESHOLD {
417 debug!("Using direct read for file segment: {}", path.display());
418 read_segment_direct(&file, offset, length, path)
419 } else {
420 debug!("Using memory-mapped read for file segment: {}", path.display());
421 read_segment_mmap(&file, offset, length, path)
422 }
423}
424
425fn read_segment_direct(file: &File, offset: u64, length: u64, path: &Path) -> Result<Vec<u8>> {
442 let mut seekable_file = file.try_clone().map_err(|e| WinxError::FileAccessError {
444 path: path.to_path_buf(),
445 message: format!("Failed to clone file handle: {e}"),
446 })?;
447
448 seekable_file.seek(SeekFrom::Start(offset)).map_err(|e| WinxError::FileAccessError {
450 path: path.to_path_buf(),
451 message: format!("Failed to seek to offset {offset}: {e}"),
452 })?;
453
454 let mut buffer = Vec::with_capacity(length as usize);
456 let reader = BufReader::with_capacity(min(length as usize, 64 * 1024), seekable_file);
457
458 reader.take(length).read_to_end(&mut buffer).map_err(|e| WinxError::FileAccessError {
460 path: path.to_path_buf(),
461 message: format!("Error reading file segment: {e}"),
462 })?;
463
464 Ok(buffer)
465}
466
467fn read_segment_mmap(file: &File, offset: u64, length: u64, path: &Path) -> Result<Vec<u8>> {
484 let segment_mmap = unsafe { MmapOptions::new().offset(offset).len(length as usize).map(file) }
489 .map_err(|e| WinxError::FileAccessError {
490 path: path.to_path_buf(),
491 message: format!("Failed to memory-map file segment: {e}"),
492 })?;
493
494 Ok(segment_mmap.to_vec())
496}
497
498pub fn read_file_to_string(path: &Path, max_file_size: u64) -> Result<String> {
516 let bytes = read_file_optimized(path, max_file_size)?;
517
518 String::from_utf8(bytes).map_err(|e| WinxError::FileAccessError {
519 path: path.to_path_buf(),
520 message: format!("Failed to decode file as UTF-8: {e}"),
521 })
522}
523
524pub fn process_text_file_parallel<F>(
543 path: &Path,
544 max_file_size: u64,
545 line_processor: F,
546) -> Result<()>
547where
548 F: Fn(&str) + Sync,
549{
550 let content = read_file_to_string(path, max_file_size)?;
551
552 if content.len() > 1_000_000 {
554 content.par_lines().for_each(|line| {
556 line_processor(line);
557 });
558 } else {
559 content.lines().for_each(|line| {
561 line_processor(line);
562 });
563 }
564
565 Ok(())
566}
567
568pub fn read_file_segment_to_string(
585 path: &Path,
586 offset: u64,
587 length: u64,
588 max_file_size: u64,
589) -> Result<String> {
590 let bytes = read_file_segment(path, offset, length, max_file_size)?;
591
592 String::from_utf8(bytes).map_err(|e| WinxError::FileAccessError {
593 path: path.to_path_buf(),
594 message: format!("Failed to decode file segment as UTF-8: {e}"),
595 })
596}
597
598#[derive(Clone)]
603pub struct ShareableMap {
604 data: Arc<Mmap>,
606 path: PathBuf,
608}
609
610impl ShareableMap {
611 pub fn new(path: &Path) -> Result<Self> {
625 let file = File::open(path).map_err(|e| WinxError::FileAccessError {
626 path: path.to_path_buf(),
627 message: format!("Error opening file: {e}"),
628 })?;
629
630 if file
632 .metadata()
633 .map_err(|e| WinxError::FileAccessError {
634 path: path.to_path_buf(),
635 message: format!("Failed to get metadata: {e}"),
636 })?
637 .len()
638 == 0
639 {
640 return Err(WinxError::FileAccessError {
641 path: path.to_path_buf(),
642 message: "Cannot memory map empty file".to_string(),
643 });
644 }
645
646 let mmap =
651 unsafe { MmapOptions::new().map(&file) }.map_err(|e| WinxError::FileAccessError {
652 path: path.to_path_buf(),
653 message: format!("Failed to memory-map file: {e}"),
654 })?;
655
656 Ok(Self { data: Arc::new(mmap), path: path.to_path_buf() })
657 }
658
659 pub fn new_segment(path: &Path, offset: u64, length: u64) -> Result<Self> {
675 if length == 0 {
676 return Err(WinxError::FileAccessError {
677 path: path.to_path_buf(),
678 message: "Cannot memory map segment of length 0".to_string(),
679 });
680 }
681
682 let file = File::open(path).map_err(|e| WinxError::FileAccessError {
683 path: path.to_path_buf(),
684 message: format!("Error opening file: {e}"),
685 })?;
686
687 let mmap = unsafe { MmapOptions::new().offset(offset).len(length as usize).map(&file) }
691 .map_err(|e| WinxError::FileAccessError {
692 path: path.to_path_buf(),
693 message: format!("Failed to memory-map file segment: {e}"),
694 })?;
695
696 Ok(Self { data: Arc::new(mmap), path: path.to_path_buf() })
697 }
698
699 pub fn as_slice(&self) -> &[u8] {
701 &self.data
702 }
703
704 pub fn path(&self) -> &Path {
706 &self.path
707 }
708
709 pub fn len(&self) -> usize {
711 self.data.len()
712 }
713
714 pub fn is_empty(&self) -> bool {
716 self.data.is_empty()
717 }
718}
719
720#[cfg(test)]
721mod tests {
722 use super::*;
723 use std::io::Write;
724 use tempfile::NamedTempFile;
725
726 fn create_test_file(size: usize) -> Result<(NamedTempFile, Vec<u8>)> {
727 let mut file = NamedTempFile::new()?;
728 let mut data = Vec::with_capacity(size);
729
730 for i in 0..size {
732 data.push((i % 256) as u8);
733 }
734
735 file.write_all(&data)?;
736 file.flush()?;
737
738 Ok((file, data))
739 }
740
741 #[test]
742 fn test_direct_read_small_file() -> Result<()> {
743 let size = 10 * 1024; let (file, expected_data) = create_test_file(size)?;
745
746 let result = read_direct(file.as_file(), size as u64, file.path())?;
747 assert_eq!(result, expected_data);
748 Ok(())
749 }
750
751 #[test]
752 fn test_mmap_read() -> Result<()> {
753 let size = 1024 * 1024; let (file, expected_data) = create_test_file(size)?;
755
756 let result = read_mmap(file.as_file(), file.path())?;
757 assert_eq!(result, expected_data);
758 Ok(())
759 }
760
761 #[test]
762 fn test_file_segment_read() -> Result<()> {
763 let size = 1024 * 1024; let (file, data) = create_test_file(size)?;
765
766 let offset = 100 * 1024; let length = 200 * 1024; let expected_segment = &data[offset as usize..(offset + length) as usize];
770
771 let result = read_segment_direct(file.as_file(), offset, length, file.path())?;
772 assert_eq!(result, expected_segment);
773
774 let result = read_segment_mmap(file.as_file(), offset, length, file.path())?;
775 assert_eq!(result, expected_segment);
776 Ok(())
777 }
778
779 #[test]
780 fn test_shareable_map() -> Result<()> {
781 let size = 100 * 1024; let (file, data) = create_test_file(size)?;
783
784 let map = ShareableMap::new(file.path())?;
785 assert_eq!(map.as_slice(), &data);
786
787 let offset = 10 * 1024; let length = 20 * 1024; let segment_map = ShareableMap::new_segment(file.path(), offset, length)?;
791 assert_eq!(segment_map.as_slice(), &data[offset as usize..(offset + length) as usize]);
792 Ok(())
793 }
794
795 #[test]
796 fn test_parallel_processing() -> Result<()> {
797 let mut file = NamedTempFile::new()?;
799 let mut lines = Vec::new();
800
801 for i in 0..1000 {
802 let line = format!("Line {i}\n");
803 file.write_all(line.as_bytes())?;
804 lines.push(format!("Line {i}"));
805 }
806 file.flush()?;
807
808 let processed_lines = std::sync::Mutex::new(Vec::new());
810
811 process_text_file_parallel(file.path(), 1_000_000, |line| {
812 if let Ok(mut lines) = processed_lines.lock() {
813 lines.push(line.to_string());
814 }
815 })?;
816
817 let result =
819 processed_lines.lock().map_err(|error| WinxError::ResourceAllocationError {
820 message: format!("Failed to lock processed lines: {error}"),
821 })?;
822 assert_eq!(result.len(), lines.len());
823
824 for line in &lines {
826 assert!(result.contains(line));
827 }
828 Ok(())
829 }
830}