File Coverage

blib/lib/File/Open/NoCache/ReadOnly.pm
Criterion Covered Total %
statement 44 53 83.0
branch 13 20 65.0
condition 1 3 33.3
subroutine 11 11 100.0
pod 4 4 100.0
total 73 91 80.2


line stmt bran cond sub pod time code
1             package File::Open::NoCache::ReadOnly;
2              
3             # Author Nigel Horne: njh@bandsman.co.uk
4              
5             # Usage is subject to licence terms.
6             # The licence terms of this software are as follows:
7             # Personal single user, single computer use: GPL2
8             # All other users (including Commercial, Charity, Educational, Government)
9             # must apply in writing for a licence for use from Nigel Horne at the
10             # above e-mail.
11              
12 6     6   1714957 use strict;
  6         14  
  6         221  
13 6     6   27 use warnings;
  6         10  
  6         304  
14 6     6   50 use Carp;
  6         10  
  6         418  
15 6     6   4913 use IO::AIO;
  6         56671  
  6         1532  
16 6     6   2947 use Params::Get;
  6         72352  
  6         370  
17 6     6   53 use Scalar::Util;
  6         11  
  6         3223  
18              
19             =head1 NAME
20              
21             File::Open::NoCache::ReadOnly - Open a file and flush from memory on closing
22              
23             =head1 VERSION
24              
25             Version 0.06
26              
27             =cut
28              
29             our $VERSION = '0.06';
30              
31             =head1 DESCRIPTION
32              
33             The C module is designed to open files for sequential,
34             read-only access while optimizing memory usage by minimizing filesystem caching.
35             This is particularly useful for processing large data files that only need to be read once,
36             such as during database population or bulk data imports.
37             The C method facilitates opening files with options to specify file paths directly or via a parameter hash,
38             and it can enforce fatal errors on failure if desired.
39             The module uses L to signal the operating system to avoid retaining file data in cache,
40             improving memory efficiency.
41             The module provides a C method to retrieve the file descriptor and a C method for explicit resource cleanup.
42             The destructor also ensures file closure when the object is destroyed,
43             with safeguards to prevent redundant closure attempts.
44              
45             =head1 SUBROUTINES/METHODS
46              
47             =head2 new
48              
49             Open a file that will be read once sequentially and not again,
50             optimising the filesystem cache accordingly.
51             One use case is building a large database from smaller files that are
52             only read in once,
53             once the file has been used it's a waste of RAM to keep it in cache.
54              
55             use File::Open::NoCache::ReadOnly;
56             my $fh = File::Open::NoCache::ReadOnly->new('/etc/passwd');
57             my $fh2 = File::Open::NoCache::ReadOnly->new(filename => '/etc/group', fatal => 1);
58              
59             =cut
60              
61             sub new {
62 16     16 1 630099 my $class = shift;
63              
64             # Handle hash or hashref arguments
65 16         136 my $params = Params::Get::get_params('filename', @_);
66              
67 13 50       504 if(!defined($class)) {
    50          
68 0 0       0 if((scalar keys %{$params}) > 0) {
  0         0  
69             # Using File::Open::NoCache::ReadOnly:new(), not File::Open::NoCache::ReadOnly->new()
70 0         0 carp(__PACKAGE__, ' use ->new() not ::new() to instantiate');
71 0         0 return;
72             }
73              
74             # FIXME: this only works when no arguments are given
75 0         0 $class = __PACKAGE__;
76             } elsif(Scalar::Util::blessed($class)) {
77             # If $class is an object, clone it with new arguments
78 0         0 return bless { %{$class}, %{$params} }, ref($class);
  0         0  
  0         0  
79             }
80              
81             # Open file if filename is provided
82 13 50       53 if(my $filename = $params->{'filename'}) {
83 13 100       1070 if(open(my $fd, '<', $filename)) {
84 8         210 IO::AIO::fadvise($fd, 0, 0, IO::AIO::FADV_SEQUENTIAL|IO::AIO::FADV_NOREUSE|IO::AIO::FADV_DONTNEED);
85 8         97 return bless { filename => $filename, fd => $fd }, $class;
86             }
87 5 100       28 if($params->{'fatal'}) {
88 2         32 Carp::croak("$filename: $!");
89             }
90 3         78 Carp::carp("$filename: $!");
91             } else {
92 0         0 Carp::carp('Usage: ', __PACKAGE__, '->new(filename => $filename)');
93             }
94              
95 3         7191 return; # Return undef if unsuccessful
96             }
97              
98             =head2 fd
99              
100             Returns the file descriptor of the file
101              
102             my $fd = $fh->fd();
103             my $line = <$fd>;
104              
105             =cut
106              
107             sub fd {
108 3     3 1 3489 my $self = shift;
109              
110 3         19 return $self->{'fd'};
111             }
112              
113             =head2 readline
114              
115             Read a line from the file
116              
117             =cut
118              
119             sub readline {
120 1     1 1 523 my $self = shift;
121 1         3 my $fd = $self->{'fd'};
122              
123             return <$fd>
124 1         74 }
125              
126             =head2 close
127              
128             Shouldn't be needed as close happens automatically when the variable goes out of scope.
129             However Perl isn't as good at reaping as it'd have you believe, so this is here to force it when you
130             know you're finished with the object.
131              
132             =cut
133              
134             sub close {
135 10     10 1 9577 my $self = shift;
136              
137 10 100       44 if(my $fd = delete $self->{'fd'}) {
138             # my @statb = stat($fd);
139             # IO::AIO::fadvise($fd, 0, $statb[7] - 1, IO::AIO::FADV_DONTNEED);
140 8         150 IO::AIO::fadvise($fd, 0, 0, IO::AIO::FADV_DONTNEED);
141              
142 8         289 close $fd;
143             } else {
144 2         50 Carp::carp('Attempt to close object twice');
145             }
146             }
147              
148             sub DESTROY {
149 8 50 33 8   11413 if(defined($^V) && ($^V ge 'v5.14.0')) {
150 8 50       37 return if ${^GLOBAL_PHASE} eq 'DESTRUCT'; # >= 5.14.0 only
151             }
152 8         28 my $self = shift;
153              
154 8 100       49 if($self->{'fd'}) {
155 5         30 $self->close();
156             }
157             }
158              
159             =head1 AUTHOR
160              
161             Nigel Horne, C<< >>
162              
163             =head1 SUPPORT
164              
165             This module is provided as-is without any warranty.
166              
167             Please report any bugs or feature requests to
168             C,
169             or through the web interface at
170             L.
171             I will be notified, and then you'll
172             automatically be notified of the progress on your bug as I make changes.
173              
174             You can find documentation for this module with the perldoc command.
175              
176             perldoc File::Open::NoCache::ReadOnly
177              
178             You can also look for information at:
179              
180             =over 4
181              
182             =item * RT: CPAN's request tracker
183              
184             L
185              
186             =item * Search CPAN
187              
188             L
189              
190             =back
191              
192             =head1 LICENSE AND COPYRIGHT
193              
194             Copyright 2019-2025 Nigel Horne.
195              
196             Usage is subject to licence terms.
197              
198             The licence terms of this software are as follows:
199              
200             =over 4
201              
202             =item * Personal single user, single computer use: GPL2
203              
204             =item * All other users (including Commercial, Charity, Educational, Government)
205             must apply in writing for a licence for use from Nigel Horne at the
206             above e-mail.
207              
208             =back
209              
210             =cut
211              
212             1;