our
$VERSION
= v0.06;
my
%_new_subobjects
= (
extractor
=>
'Data::URIID'
,
db
=>
'Data::TagDB'
,
);
sub
new {
my
(
$pkg
,
%opts
) =
@_
;
my
$self
=
bless
{};
foreach
my
$key
(
keys
%_new_subobjects
) {
if
(
defined
(
$opts
{
$key
})) {
croak
'Bad package for option '
.
$key
unless
eval
{
$opts
{
$key
}->isa(
$_new_subobjects
{
$key
})};
$self
->{
$key
} =
$opts
{
$key
};
}
}
$self
->{
$_
} =
$opts
{
$_
}
foreach
qw(tagpool_rc tagpool_path device_path digest_sizelimit mountinfo_path)
;
$self
->{digest_sizelimit} //= 512*1024*1024;
if
(
$self
->{digest_sizelimit} eq
'infinite'
) {
$self
->{digest_sizelimit} = -1;
}
else
{
$self
->{digest_sizelimit} =
int
(
$self
->{digest_sizelimit});
$self
->{digest_sizelimit} = 0
if
$self
->{digest_sizelimit} < 0;
}
if
(
defined
$opts
{digest_unsafe}) {
my
$unsafe
=
$opts
{digest_unsafe};
$unsafe
= [
$unsafe
]
unless
ref
(
$unsafe
) eq
'ARRAY'
;
$_
->{unsafe} = 1
foreach
$self
->digest_info(@{
$unsafe
});
}
$self
->_tagpool_locate;
return
$self
;
}
sub
for_link {
my
(
$self
,
%opts
);
if
(
scalar
(
@_
) == 2) {
(
$self
,
$opts
{path}) =
@_
;
}
else
{
(
$self
,
%opts
) =
@_
;
}
return
File::Information::Link->_new(
instance
=>
$self
, (
map
{
$_
=>
$opts
{
$_
}}
qw(path symlinks)
));
}
sub
for_handle {
my
(
$self
,
%opts
);
if
(
scalar
(
@_
) == 2) {
(
$self
,
$opts
{handle}) =
@_
;
}
else
{
(
$self
,
%opts
) =
@_
;
}
return
File::Information::Inode->_new(
instance
=>
$self
, (
map
{
$_
=>
$opts
{
$_
}}
qw(handle)
));
}
sub
for_identifier {
my
(
$self
,
%opts
);
if
(
scalar
(
@_
) == 2) {
(
$self
,
$opts
{identifier}) =
@_
;
}
elsif
(
scalar
(
@_
) == 3) {
(
$self
,
$opts
{type},
$opts
{identifier}) =
@_
;
}
else
{
(
$self
,
%opts
) =
@_
;
}
croak
'No identifier given'
unless
defined
$opts
{identifier};
if
(!
defined
(
$opts
{type}) &&
ref
(
$opts
{identifier})) {
my
$id
=
$opts
{identifier};
if
(
$id
->isa(
'Data::URIID::Result'
)) {
$opts
{type} =
$id
->id_type;
unless
(
defined
$opts
{type}) {
return
File::Information::Remote->_new(
instance
=>
$self
,
data_uriid_result
=>
$id
);
}
}
elsif
(
$id
->isa(
'Data::Identifier'
)) {
$opts
{type} =
$id
->type;
}
}
croak
'No type given'
unless
defined
$opts
{type};
if
(
ref
$opts
{type}) {
$opts
{type} =
$opts
{type}->ise;
}
elsif
(
$opts
{type} eq
'uuid'
) {
$opts
{type} =
'8be115d2-dc2f-4a98-91e1-a6e3075cbc31'
;
}
if
(
ref
(
my
$id
=
$opts
{identifier})) {
if
(
$id
->isa(
'Data::URIID::Result'
) ||
$id
->isa(
'Data::Identifier'
)) {
$opts
{identifier} =
$id
->id;
}
elsif
(
$id
->isa(
'Data::URIID::Base'
)) {
$opts
{identifier} =
$id
->ise;
}
}
if
(
$opts
{type} eq
'8be115d2-dc2f-4a98-91e1-a6e3075cbc31'
) {
if
(
$opts
{identifier} !~ /^[0-9a-f]{8}-(?:[0-9a-f]{4}-){3}[0-9a-f]{12}$/) {
croak
'Invalid format for UUID'
;
}
$self
->_load_filesystems;
foreach
my
$fs
(
values
%{
$self
->{filesystems}}) {
my
$ise
=
$fs
->get(
'uuid'
,
as
=>
'uuid'
,
default
=>
undef
) //
next
;
return
$fs
if
$ise
eq
$opts
{identifier};
}
{
my
$uuid
=
$opts
{identifier};
foreach
my
$pool
(
$self
->tagpool) {
my
$ise
=
$pool
->get(
'uuid'
,
as
=>
'uuid'
);
my
$hash
;
return
$pool
if
$ise
eq
$uuid
;
$hash
=
eval
{
my
$reader
= File::ValueFile::Simple::Reader->new(
$pool
->_catfile(
'data'
,
'info.'
.
$uuid
));
$reader
->read_as_hash;
};
next
unless
defined
$hash
;
next
unless
defined
$hash
->{
'pool-name-suffix'
};
return
$self
->for_link(
path
=>
$pool
->_catfile(
'data'
,
$hash
->{
'pool-name-suffix'
}),
%opts
);
}
}
}
if
(
defined
(
my
$extractor
=
eval
{
$self
->extractor})) {
if
(
defined
(
my
$result
=
eval
{
$extractor
->lookup(
$opts
{type},
$opts
{identifier})})) {
return
File::Information::Remote->_new(
instance
=>
$self
,
data_uriid_result
=>
$result
);
}
}
croak
'Not found'
;
}
sub
tagpool {
my
(
$self
) =
@_
;
return
values
%{
$self
->_tagpool}
}
sub
extractor {
my
(
$self
) =
@_
;
return
$self
->{extractor} // croak
'No extractor available'
;
}
sub
db {
my
(
$self
) =
@_
;
return
$self
->{db} // croak
'No database available'
;
}
sub
lifecycles {
return
qw(initial last current final)
;
}
sub
digest_info {
my
(
$self
,
@algos
) =
@_
;
my
@ret
;
if
(
scalar
(
@algos
) == 1 &&
$algos
[0] =~ /^v0m / &&
wantarray
) {
while
(
$algos
[-1] =~ s
unshift
(
@algos
, $1);
}
}
unless
(
$self
->{hash_info}) {
my
%hashes
=
map
{
$_
=> {
name
=>
$_
,
bits
=>
int
((
$_
=~ /-([0-9]+)$/)[0]),
aliases
=> [],
%{
$File::Information::Base::_digest_info_extra
{
$_
}//{}},
}} (
values
(
%File::Information::Base::_digest_name_converter
),
qw(md-4-128 ripemd-1-160 tiger-1-192 tiger-2-192)
,
);
$self
->{hash_info} = \
%hashes
;
$hashes
{
$_
}{unsafe} = 1
foreach
qw(md-4-128 md-5-128 sha-1-160)
;
push
(@{
$hashes
{
$File::Information::Base::_digest_name_converter
{
$_
}}{aliases}},
$_
)
foreach
keys
%File::Information::Base::_digest_name_converter
;
}
@algos
=
keys
%{
$self
->{hash_info}}
unless
scalar
@algos
;
croak
'Request for more than one hash in scalar context'
if
!
wantarray
&&
scalar
(
@algos
) != 1;
@ret
=
map
{
$self
->{hash_info}{
$_
} ||
$self
->{hash_info}{
$File::Information::Base::_digest_name_converter
{fc(
$_
)} //
''
} ||
croak
'Unknown digest: '
.
$_
}
map
{ s
if
(
wantarray
) {
return
@ret
;
}
else
{
return
$ret
[0];
}
}
sub
_home {
my
(
$self
) =
@_
;
my
$home
;
return
$self
->{home}
if
defined
$self
->{home};
if
($^O eq
'MSWin32'
) {
return
$self
->{home} =
$home
if
defined
(
$home
=
$ENV
{USERPROFILE}) &&
length
(
$home
);
if
(
defined
(
$ENV
{HOMEDRIVE}) &&
defined
(
$ENV
{HOMEPATH})) {
$home
=
$ENV
{HOMEDRIVE}.
$ENV
{HOMEPATH};
return
$self
->{home} =
$home
if
length
(
$home
);
}
return
$self
->{home} =
'C:\\'
;
}
else
{
return
$self
->{home} =
$home
if
defined
(
$home
=
$ENV
{HOME}) &&
length
(
$home
);
return
$self
->{home} =
$home
if
defined
(
$home
=
eval
{ [
getpwuid
($>)]->[7] }) &&
length
(
$home
);
return
$self
->{home} = File::Spec->rootdir;
}
croak
'BUG'
;
}
sub
_path {
my
(
$self
,
$xdg
,
$type
,
@el
) =
@_
;
my
$base
;
if
(
defined
$xdg
) {
$base
=
$ENV
{
$xdg
} //
$self
->{
$xdg
};
if
(!
defined
(
$base
) || !
length
(
$base
)) {
if
(
$xdg
eq
'XDG_CACHE_HOME'
) {
$base
= File::Spec->catdir(
$self
->_home,
qw(.cache)
);
}
elsif
(
$xdg
eq
'XDG_DATA_HOME'
) {
$base
= File::Spec->catdir(
$self
->_home,
qw(.local share)
);
}
elsif
(
$xdg
eq
'XDG_CONFIG_HOME'
) {
$base
= File::Spec->catdir(
$self
->_home,
qw(.config)
);
}
elsif
(
$xdg
eq
'XDG_STATE_HOME'
) {
$base
= File::Spec->catdir(
$self
->_home,
qw(.local state)
);
}
else
{
croak
'Unknown XDG path: '
.
$xdg
;
}
$self
->{
$xdg
} =
$base
;
}
}
else
{
$base
=
$self
->_home;
}
if
(
$type
eq
'file'
) {
return
File::Spec->catfile(
$base
,
@el
);
}
else
{
return
File::Spec->catdir(
$base
,
@el
);
}
}
sub
_tagpool_locate {
my
(
$self
) =
@_
;
my
%candidates
;
return
unless
$HAVE_FILE_VALUEFILE
;
unless
(
defined
$self
->{tagpool_rc}) {
$self
->{tagpool_rc} = [
'/etc/tagpoolrc'
,
$self
->_path(
undef
,
file
=>
'.tagpoolrc'
)];
}
unless
(
defined
$self
->{tagpool_path}) {
$self
->{tagpool_path} = [];
}
$self
->{tagpool_rc} = [
$self
->{tagpool_rc}]
unless
ref
$self
->{tagpool_rc};
$self
->{tagpool_path} = [
$self
->{tagpool_path}]
unless
ref
$self
->{tagpool_path};
%candidates
=
map
{
$_
=>
undef
}
grep
{
defined
} @{
$self
->{tagpool_path}};
foreach
my
$tagpool_rc_path
(@{
$self
->{tagpool_rc}}) {
my
$hash
=
eval
{File::ValueFile::Simple::Reader->new(
$tagpool_rc_path
)->read_as_hash};
if
(
defined
$hash
) {
foreach
my
$key
(
qw(pool-path pool)
) {
if
(
defined
$hash
->{
$key
}) {
$candidates
{
$hash
->{
$key
}} =
undef
;
}
}
}
}
foreach
my
$path
(
keys
%candidates
) {
unless
(-d
$path
) {
delete
$candidates
{
$path
};
next
;
}
foreach
my
$subdir
(
qw(data temp)
) {
unless
(-d File::Spec->catdir(
$path
,
$subdir
)) {
delete
$candidates
{
$path
};
next
;
}
}
foreach
my
$subfile
(
qw(config)
) {
unless
(-f File::Spec->catfile(
$path
,
$subfile
)) {
delete
$candidates
{
$path
};
next
;
}
}
}
$self
->{tagpool_path} = [
keys
%candidates
];
}
sub
_tagpool_path {
my
(
$self
) =
@_
;
return
$self
->{tagpool_path};
}
sub
_tagpool_sysfile_cache {
my
(
$self
) =
@_
;
return
$self
->{_tagpool_sysfile_cache} //= {};
}
sub
_tagpool {
my
(
$self
) =
@_
;
my
$pools
=
$self
->{tagpool} //= {
map
{
$_
=> File::Information::Tagpool->_new(
instance
=>
$self
,
path
=>
$_
)} @{
$self
->_tagpool_path}
};
return
$pools
;
}
sub
_load_filesystems {
my
(
$self
) =
@_
;
unless
(
defined
$self
->{filesystems}) {
my
%dirs
;
my
%found
;
my
%filesystems
;
$self
->{device_path} //= File::Information::Filesystem->_default_device_search_paths;
$self
->{device_path} = [
$self
->{device_path}]
unless
ref
(
$self
->{device_path}) eq
'ARRAY'
;
%dirs
=
map
{
$_
=>
undef
} @{
$self
->{device_path}};
foreach
my
$dir_path
(
keys
%dirs
) {
if
(
opendir
(
my
$dir
,
$dir_path
)) {
while
(
my
$entry
=
readdir
(
$dir
)) {
my
$devpath
= File::Spec->catfile(
$dir_path
,
$entry
);
my
@stat
=
stat
(
$devpath
);
next
unless
scalar
@stat
;
next
unless
S_ISBLK(
$stat
[2]);
$found
{
$stat
[6]} //= {};
$found
{
$stat
[6]}{
stat
} = \
@stat
;
$found
{
$stat
[6]}{paths} //= {};
$found
{
$stat
[6]}{paths}{
$dir_path
} //= [];
push
(@{
$found
{
$stat
[6]}{paths}{
$dir_path
}},
$entry
);
}
}
}
if
($^O eq
'MSWin32'
) {
foreach
my
$dos_device
(
'A'
..
'Z'
) {
my
$dos_path
=
$dos_device
.
':\\'
;
my
@stat
=
stat
(
$dos_path
);
next
unless
scalar
@stat
;
$found
{
$stat
[0]} //= {};
$found
{
$stat
[0]}{dirstat} = \
@stat
;
$found
{
$stat
[0]}{dos_device} =
$dos_device
;
$found
{
$stat
[0]}{dos_path} =
$dos_path
;
$found
{
$stat
[0]}{paths} //= {};
}
}
if
(
$HAVE_UNIX_MKNOD
&& $^O eq
'linux'
) {
$self
->{mountinfo_path} //=
'/proc/self/mountinfo'
;
if
(
open
(
my
$mountinfo
,
'<'
,
$self
->{mountinfo_path})) {
while
(
defined
(
my
$line
= <
$mountinfo
>)) {
my
(
$mount_id
,
$parent_id
,
$major
,
$minor
,
$root
,
$mountpoint
,
$mount_options
,
$options
,
$fs_type
,
$source
,
$super_options
) =
$line
=~ m
my
$dev
;
my
$entry
;
next
unless
defined
$mount_id
;
s/\\([0-9]{3})/
chr
(
oct
($1))/ge
foreach
$mount_id
,
$parent_id
,
$major
,
$minor
,
$root
,
$mountpoint
,
$mount_options
,
$options
,
$fs_type
,
$source
,
$super_options
;
$dev
= Unix::Mknod::makedev(
$major
,
$minor
);
$entry
=
$found
{
$dev
} //= {};
$entry
->{paths} //= {};
if
(!
defined
(
$entry
->{
stat
}) &&
$source
=~ m
my
@stat
=
eval
{
stat
(
$source
)};
$entry
->{
stat
} = \
@stat
if
scalar
@stat
;
}
if
(!
defined
(
$entry
->{dirstat}) &&
$mountpoint
=~ m
my
@stat
=
eval
{
stat
(
$mountpoint
)};
$entry
->{dirstat} = \
@stat
if
scalar
@stat
;
}
$entry
->{mountpoint} //=
$mountpoint
if
$mountpoint
=~ m
$entry
->{fs_type} //=
$fs_type
;
$entry
->{linux_mount_options} //=
$mount_options
;
$entry
->{linux_superblock_options} //=
$super_options
;
if
(
$source
=~ m
my
(
$volume
,
$directories
,
$file
) = File::Spec->splitpath(
$source
);
my
$dir
= File::Spec->catdir(
$volume
,
$directories
);
$entry
->{paths}{
$dir
} //= [];
push
(@{
$entry
->{paths}{
$dir
}},
$file
);
}
}
}
}
foreach
my
$key
(
keys
%found
) {
$filesystems
{
$key
} = File::Information::Filesystem->_new(
instance
=>
$self
, %{
$found
{
$key
}});
}
$self
->{dev_found} = \
%found
;
$self
->{filesystems} = \
%filesystems
;
}
}
sub
_filesystem_for {
my
(
$self
,
$dev
) =
@_
;
$self
->_load_filesystems;
return
$self
->{filesystems}{
$dev
};
}
1;