our
$VERSION
=
'0.32'
;
sub
new {
my
$package
=
shift
;
my
$input
=
shift
;
my
%args
= (
scalar
(
@_
) &&
ref
(
$_
[0])) ? %{
$_
[0]} :
@_
;
my
$self
=
bless
{
args
=> \
%args
},
$package
;
if
(
ref
(
$input
) eq
'GLOB'
) {
DEBUG
$package
,
': input is a GLOB'
;
$self
->{filename} =
'<GLOB>'
;
binmode
$input
,
":raw"
or LOGCROAK
"binmode() to ':raw' failed"
;
$self
->{fh} =
$input
;
}
else
{
DEBUG
$package
,
': input is a file or other thing that can be open-ed'
;
$self
->{filename} =
$input
;
open
$self
->{fh},
'<'
,
$input
or LOGCROAK
"open('$input'): $OS_ERROR"
;
binmode
$self
->{fh},
':raw'
;
}
return
$self
;
}
sub
files {
my
$files
=
shift
->_ensure_index()->{files};
return
wantarray
() ?
@$files
:
$files
;
}
sub
prefix {
return
shift
->_ensure_index()->{_prefix};
}
sub
reset
{
my
$self
=
shift
;
delete
$self
->{
$_
}
for
qw< files index >
;
return
$self
;
}
sub
_index {
return
shift
->_ensure_index()->{_index}; }
sub
_ensure_index {
my
$self
=
shift
;
if
(!
exists
$self
->{files}) {
my
$index
=
$self
->_load_index()
|| {
_prefix
=> Data::Embed::File->new(
fh
=>
$self
->{fh},
filename
=>
$self
->{filename},
name
=>
'Data::Embed prefix data'
,
offset
=> 0,
length
=>
scalar
(__size(
$self
->{fh})),
),
files
=> [],
_index
=> Data::Embed::File->new(
fh
=>
$self
->{fh},
filename
=>
$self
->{filename},
name
=>
'Data::Embed index'
,
length
=> 0,
offset
=>
scalar
(__size(
$self
->{fh})),
),
};
%$self
= (
%$self
,
%$index
);
}
return
$self
;
}
sub
_load_index {
my
$self
=
shift
;
defined
(
my
$index_text
=
$self
->_read_index())
or
return
;
my
$index_length
=
length
(
$index_text
);
my
$terminator_length
=
length
TERMINATOR();
substr
$index_text
, 0,
length
(STARTER()),
''
;
substr
$index_text
, -
$terminator_length
,
$terminator_length
,
''
;
DEBUG
"index contents is '$index_text'"
;
my
$data_length
= 0;
my
(
$fh
,
$filename
) = @{
$self
}{
qw< fh filename >
};
my
@files
=
map
{
my
(
$length
,
$name
) = m{\A \s* (\d+) \s+ (\S*) \s*\z}mxs
or LOGCROAK
"index line is not compliant: >$_<"
;
$name
= Data::Embed::Util::unescape(
$name
);
my
$offset
=
$data_length
;
$data_length
+=
$length
+ 2;
{
fh
=>
$fh
,
filename
=>
$filename
,
name
=>
$name
,
length
=>
$length
,
offset
=>
$offset
,
};
}
split
/\n+/,
$index_text
;
my
$full_length
= __size(
$fh
);
my
$offset_correction
=
$full_length
-
$index_length
-
$data_length
;
for
my
$file
(
@files
) {
$file
=
Data::Embed::File->new(
%$file
,
offset
=> (
$file
->{offset} +
$offset_correction
),);
}
return
{
_prefix
=> Data::Embed::File->new(
fh
=>
$fh
,
filename
=>
$filename
,
name
=>
'Data::Embed prefix data'
,
length
=>
$offset_correction
,
offset
=> 0,
),
files
=> \
@files
,
_index
=> Data::Embed::File->new(
fh
=>
$fh
,
filename
=>
$filename
,
name
=>
'Data::Embed index'
,
length
=>
$index_length
,
offset
=>
$data_length
+
$offset_correction
,
),
};
}
sub
__size {
my
$fh
=
shift
;
my
$size
= -s
$fh
;
if
(!
defined
$size
) {
DEBUG
"getting size via seek"
;
my
$current
=
tell
$fh
;
seek
$fh
, 0, SEEK_END;
$size
=
tell
$fh
;
DEBUG
"size: $size"
;
seek
$fh
,
$current
, SEEK_SET;
}
return
$size
;
}
sub
_read_index {
my
$self
=
shift
;
my
(
$fh
,
$filename
) = @{
$self
}{
qw< fh filename >
};
DEBUG
"_read_index(): fh[$fh] filename[$filename]"
;
my
$full_length
= __size(
$fh
);
my
$terminator
= TERMINATOR;
my
$terminator_length
=
length
$terminator
;
return
unless
$full_length
>
$terminator_length
;
my
$ending
=
$self
->_read((
$terminator_length
) x 2);
return
unless
$ending
eq
$terminator
;
DEBUG
"found terminator"
;
my
$starter
= STARTER;
my
$readable
=
$full_length
-
$terminator_length
;
my
$chunk_size
= 80;
my
$starter_position
;
while
(
$readable
) {
my
$n
= (
$readable
>
$chunk_size
) ?
$chunk_size
:
$readable
;
my
$chunk
=
$self
->_read(
$n
,
$n
+
length
$ending
);
$ending
=
$chunk
.
$ending
;
TRACE
sub
{
"ENDING: >$ending<"
};
$starter_position
= CORE::
index
$ending
,
$starter
;
last
if
$starter_position
>= 0;
$readable
-=
$n
;
}
return
unless
$starter_position
>= 0;
DEBUG
"found starter"
;
substr
$ending
, 0,
$starter_position
,
''
;
return
$ending
;
}
sub
_read {
my
$self
=
shift
;
my
@args
=
my
(
$count
,
$offset_from_end
) =
@_
;
my
(
$fh
,
$filename
) = @{
$self
}{
qw< fh filename >
};
DEBUG
sub
{
my
$args
=
join
', '
,
@args
;
"_read($args) [file: $filename]"
};
LOGDIE
'_read(): offset from end cannot be less than count'
if
$offset_from_end
<
$count
;
DEBUG
"seeking $offset_from_end to the end"
;
seek
$fh
, -
$offset_from_end
, SEEK_END
or LOGCROAK
"seek('$filename'): $OS_ERROR"
;
my
$buffer
=
''
;
while
(
$count
) {
my
$chunk
;
defined
(
my
$nread
=
read
$fh
,
$chunk
,
$count
)
or LOGCROAK
"read('$filename'): $OS_ERROR"
;
TRACE
sub
{
"read $nread bytes, '$chunk'"
};
DEBUG
"read $nread out of $count bytes needed"
;
LOGCROAK
"unexpectedly reached end of file"
unless
$nread
;
$buffer
.=
$chunk
;
$count
-=
$nread
;
}
return
$buffer
;
}
1;