use
5.008008;
use
Fcntl
qw/:DEFAULT :flock/
;
root
_cache timestamp/
;
our
@CLASS_MEMBERS
;
no
warnings
qw(uninitialized)
;
undef
$^W;
our
$VERSION
=
'0.06'
;
sub
new {
my
$parent
=
shift
;
my
$class
=
ref
(
$parent
) ||
$parent
;
my
$I
=
bless
{}=>
$class
;
my
$x
=0;
my
%o
=
map
{(
$x
=!
$x
) ?
lc
(
$_
) :
$_
}
@_
;
if
(
ref
(
$parent
) ) {
foreach
my
$m
(
@CLASS_MEMBERS
) {
$I
->
$m
=
$parent
->
$m
;
}
}
foreach
my
$m
(
@CLASS_MEMBERS
) {
$I
->
$m
=
$o
{
$m
}
if
(
exists
$o
{
$m
} );
}
$I
->_cache={};
return
$I
;
}
sub
_in {
my
(
$sym
)=
@_
;
if
( @{
*$sym
} ) {
local
$
"="
";
return
shift
@{
*$sym
};
}
else
{
return
if
( ${
*$sym
} );
my
$l
=<
$sym
>;
if
(
defined
$l
) {
return
$l
;
}
else
{
${
*$sym
}=1;
return
;
}
}
}
sub
_unin {
my
$sym
=
shift
;
push
@{
*$sym
},
@_
;
}
sub
start {
my
$I
=
shift
;
my
$time
;
my
$fname
;
if
(
ref
(
$I
->configfile) ) {
$time
=1;
}
else
{
$fname
=
$I
->configfile;
if
(
length
$I
->root ) {
unless
( File::Spec->file_name_is_absolute(
$fname
) ) {
$fname
=File::Spec->catfile(
$I
->root,
$fname
);
}
}
$time
=(
stat
$fname
)[9];
}
if
(
$time
!=
$I
->timestamp ) {
$I
->timestamp=
$time
;
%{
$I
->_cache}=();
my
$f
;
if
(
ref
(
$I
->configfile) ) {
$f
=
$I
->configfile;
}
else
{
open
$f
,
$fname
or
do
{
warn
(
"ERROR: Cannot open translation provider config file: "
.
$fname
.
": $!\n"
);
return
;
};
flock
$f
, LOCK_SH or
die
"ERROR: Cannot flock $fname: $!\n"
;
}
my
$l
;
my
$cache
=
$I
->_cache;
while
(
defined
(
$l
=_in
$f
) ) {
if
(
$l
=~s!^>>>\s*!! ) {
chomp
$l
;
my
@l
=
split
/\s+/,
$l
, 5;
if
(
@l
==5 ) {
my
$k
=
join
(
"\0"
,
@l
[1,2]);
my
$a
=
''
;
while
(
defined
(
$l
=_in
$f
) ) {
next
if
(
$l
=~/^
if
(
$l
=~m!^>>>! ) {
_unin
$f
,
$l
;
last
;
}
else
{
$a
.=
$l
;
}
}
chomp
$a
;
if
(
exists
$cache
->{
$k
} ) {
push
@{
$cache
->{
$k
}}, [
@l
[3..4],
$a
,
@l
[0..2]];
}
else
{
$cache
->{
$k
}=[[
@l
[3..4],
$a
,
@l
[0..2]]]
}
}
}
}
close
$f
;
foreach
my
$list
(
values
%{
$I
->_cache}) {
@$list
=
sort
{
$a
->[BLOCK] <=>
$b
->[BLOCK] or
$a
->[ORDER] <=>
$b
->[ORDER]}
@$list
;
}
}
}
sub
stop {}
sub
_getnote {
my
(
$I
,
$id
)=
@_
;
my
$dname
=
$I
->notesdir;
if
(
defined
$dname
and
length
$I
->root ) {
unless
( File::Spec->file_name_is_absolute(
$dname
) ) {
$dname
=File::Spec->catdir(
$I
->root,
$dname
);
}
}
my
$content
;
my
$f
=
undef
;
open
$f
, File::Spec->catfile(
$dname
,
$id
) and
$content
=<
$f
>;
close
$f
;
return
$content
;
}
sub
fetch {
my
$I
=
shift
;
my
(
$key
,
$uri
,
$with_notes
)=
@_
;
if
(
$with_notes
and
length
$I
->notesdir ) {
local
$/;
return
map
{[@{
$_
}[BLOCK,ORDER,ACTION,ID],
$I
->_getnote(
$_
->[ID])]}
@{
$I
->_cache->{
join
"\0"
,
$key
,
$uri
} || []};
}
else
{
return
map
{[@{
$_
}[BLOCK,ORDER,ACTION,ID]]}
@{
$I
->_cache->{
join
"\0"
,
$key
,
$uri
} || []};
}
}
sub
can_notes {
defined
$_
[0]->notesdir;}
sub
list_keys {
my
$I
=
shift
;
my
%h
;
foreach
my
$v
(
values
%{
$I
->_cache}) {
$h
{
$v
->[0]->[4]}=1;
}
return
map
{[
$_
]}
sort
keys
%h
;
}
sub
list_keys_and_uris {
my
$I
=
shift
;
if
(
@_
and
length
$_
[0] ) {
return
sort
{
$a
->[1] cmp
$b
->[1]}
map
{
my
@l
=
split
"\0"
,
$_
, 2;
$l
[0] eq
$_
[0] ? [
@l
] : ()}
keys
%{
$I
->_cache};
}
else
{
return
sort
{
$a
->[0] cmp
$b
->[0] or
$a
->[1] cmp
$b
->[1]}
map
{[@{
$_
->[0]}[4,5]]}
values
%{
$I
->_cache};
}
}
sub
begin {}
sub
commit {
my
$I
=
shift
;
return
"0 but true"
if
(
ref
$I
->configfile );
my
$fname
=
$I
->configfile;
if
(
length
$I
->root ) {
unless
( File::Spec->file_name_is_absolute(
$fname
) ) {
$fname
=File::Spec->catfile(
$I
->root,
$fname
);
}
}
my
$dname
=
$I
->notesdir;
if
(
defined
$dname
and
length
$I
->root ) {
unless
( File::Spec->file_name_is_absolute(
$dname
) ) {
$dname
=File::Spec->catdir(
$I
->root,
$dname
);
}
}
my
(
$w_id
,
$w_key
,
$w_uri
,
$w_blk
,
$w_ord
)=((3)x5);
foreach
my
$v
(
values
%{
$I
->_cache}) {
foreach
my
$el
(@{
$v
}) {
$w_id
=
length
(
$el
->[3])
if
(
length
(
$el
->[3])>
$w_id
);
$w_key
=
length
(
$el
->[4])
if
(
length
(
$el
->[4])>
$w_id
);
$w_uri
=
length
(
$el
->[5])
if
(
length
(
$el
->[5])>
$w_id
);
$w_blk
=
length
(
$el
->[0])
if
(
length
(
$el
->[0])>
$w_id
);
$w_ord
=
length
(
$el
->[1])
if
(
length
(
$el
->[1])>
$w_id
);
}
}
sysopen
my
(
$fh
),
$fname
, O_RDWR | O_CREAT or
do
{
die
"ERROR: Cannot open $fname: $!\n"
;
};
flock
$fh
, LOCK_EX or
die
"ERROR: Cannot flock $fname: $!\n"
;
my
$oldtime
=(
stat
$fname
)[9];
truncate
$fh
, 0 or
do
{
close
$fh
;
die
"ERROR: Cannot truncate to $fname: $!\n"
};
my
$fmt
=
">>> %@{[$w_id-1]}s %-${w_key}s %-${w_uri}s %${w_blk}s %${w_ord}s\n"
;
printf
$fh
'#'
.
$fmt
,
qw/id key uri blk ord/
or
do
{
close
$fh
;
die
"ERROR: Cannot write to $fname: $!\n"
};
print
$fh
"# action\n"
or
do
{
close
$fh
;
die
"ERROR: Cannot write to $fname: $!\n"
};
$fmt
=(
"##################################################################\n"
.
">>> %${w_id}s %-${w_key}s %-${w_uri}s %${w_blk}s %${w_ord}s\n%s\n"
);
foreach
my
$v
(
map
{
$I
->_cache->{
$_
}}
sort
keys
%{
$I
->_cache}) {
foreach
my
$el
(
sort
{
$a
->[0] <=>
$b
->[0] or
$a
->[1] <=>
$b
->[1]} @{
$v
}) {
printf
$fh
$fmt
, @{
$el
}[3..5,0..2] or
do
{
close
$fh
;
die
"ERROR: Cannot write to $fname: $!\n"
};
if
(
defined
$dname
and
length
$el
->[6] ) {
my
$notesf
=
undef
;
if
(
open
$notesf
,
'>'
.File::Spec->catfile(
$dname
,
$el
->[3]) ) {
print
$notesf
$el
->[6];
close
$notesf
;
}
else
{
warn
"WARNING: Cannot open "
.File::Spec->catfile(
$dname
,
$el
->[3]).
": $!\n"
;
}
}
$
}
}
select
( (
select
(
$fh
), $|=1)[0] );
my
$time
=
time
;
$time
=
$oldtime
+1
if
(
$time
<=
$oldtime
);
utime
(
$time
,
$time
,
$fname
);
$I
->timestamp=
$time
;
if
(
defined
$dname
) {
opendir
my
(
$d
),
$dname
;
if
(
$d
) {
my
%h
=
map
{(
$_
->[3]=>1)}
map
{
@$_
}
values
%{
$I
->_cache};
while
(
my
$el
=
readdir
$d
) {
unlink
File::Spec->catfile(
$dname
,
$el
)
if
(
$el
=~/^\d+$/ and !
exists
$h
{
$el
} );
}
closedir
$d
;
}
}
close
$fh
or
die
"ERROR: Cannot write to $fname: $!\n"
;
return
"0 but true"
;
}
sub
rollback {
my
$I
=
shift
;
$I
->timestamp=0;
$I
->start;
}
sub
update {
my
$I
=
shift
;
my
$old
=
shift
;
my
$new
=
shift
;
my
$list
=
$I
->_cache->{
join
"\0"
, @{
$old
}[0,1]};
return
"0 but true"
unless
(
$list
);
if
(
$old
->[oKEY] eq
$new
->[oKEY] and
$old
->[oURI] eq
$new
->[oURI] ) {
for
(
my
$i
=0;
$i
<@{
$list
};
$i
++ ) {
if
(
$list
->[
$i
]->[ID] ==
$old
->[oID] and
$list
->[
$i
]->[BLOCK] ==
$old
->[oBLOCK] and
$list
->[
$i
]->[ORDER] ==
$old
->[oORDER] ) {
@{
$list
->[
$i
]}[BLOCK,ORDER,ACTION,NOTE]
= @{
$new
}[nBLOCK,nORDER,nACTION,nNOTE];
@{
$list
}=
sort
{
$a
->[BLOCK] <=>
$b
->[BLOCK] or
$a
->[ORDER] <=>
$b
->[ORDER]} @{
$list
};
return
1;
}
}
}
else
{
die
"ERROR: KEY must not contain spaces.\n"
if
(
$new
->[0]=~/\s/ );
die
"ERROR: URI must not contain spaces.\n"
if
(
$new
->[1]=~/\s/ );
for
(
my
$i
=0;
$i
<@{
$list
};
$i
++ ) {
if
(
$list
->[
$i
]->[ID] ==
$old
->[oID] and
$list
->[
$i
]->[BLOCK] ==
$old
->[oBLOCK] and
$list
->[
$i
]->[ORDER] ==
$old
->[oORDER] ) {
my
(
$el
)=
splice
@{
$list
},
$i
, 1;
delete
$I
->_cache->{
join
"\0"
, @{
$old
}[oKEY,oURI]}
unless
( @{
$list
} );
@{
$el
}[KEY,URI,BLOCK,ORDER,ACTION,NOTE]
= @{
$new
}[nKEY,nURI,nBLOCK,nORDER,nACTION,nNOTE];
my
$k
=
join
(
"\0"
,@{
$new
}[nKEY,nURI]);
if
(
exists
$I
->_cache->{
$k
} ) {
push
@{
$I
->_cache->{
$k
}},
$el
;
$I
->_cache->{
$k
}=[
sort
{
$a
->[BLOCK] <=>
$b
->[BLOCK] or
$a
->[ORDER] <=>
$b
->[ORDER]}
@{
$I
->_cache->{
$k
}}];
}
else
{
$I
->_cache->{
$k
}=[
$el
]
}
return
1;
}
}
}
return
"0 but true"
;
}
sub
insert {
my
$I
=
shift
;
my
$new
=
shift
;
die
"ERROR: KEY must not contain spaces.\n"
if
(
$new
->[0]=~/\s/ );
die
"ERROR: URI must not contain spaces.\n"
if
(
$new
->[1]=~/\s/ );
my
$newid
=0;
foreach
my
$v
(
values
%{
$I
->_cache}) {
foreach
my
$el
(@{
$v
}) {
$newid
=
$el
->[3]
if
(
$el
->[3]>
$newid
);
}
}
$newid
++;
my
$newel
=[];
@{
$newel
}[BLOCK,ORDER,ACTION,KEY,URI,NOTE,ID]=
(@{
$new
}[nBLOCK,nORDER,nACTION,nKEY,nURI,nNOTE],
$newid
);
my
$k
=
join
(
"\0"
,@{
$new
}[nKEY,nURI]);
if
(
exists
$I
->_cache->{
$k
} ) {
push
@{
$I
->_cache->{
$k
}},
$newel
;
$I
->_cache->{
$k
}=[
sort
{
$a
->[BLOCK] <=>
$b
->[BLOCK] or
$a
->[ORDER] <=>
$b
->[ORDER]}
@{
$I
->_cache->{
$k
}}];
}
else
{
$I
->_cache->{
$k
}=[
$newel
];
}
return
1;
}
sub
delete
{
my
$I
=
shift
;
my
$old
=
shift
;
my
$list
=
$I
->_cache->{
join
"\0"
, @{
$old
}[oKEY,oURI]};
return
"0 but true"
unless
(
$list
);
for
(
my
$i
=0;
$i
<@{
$list
};
$i
++ ) {
if
(
$list
->[
$i
]->[ID] ==
$old
->[oID] and
$list
->[
$i
]->[BLOCK] ==
$old
->[oBLOCK] and
$list
->[
$i
]->[ORDER] ==
$old
->[oORDER] ) {
splice
@{
$list
},
$i
, 1;
delete
$I
->_cache->{
join
"\0"
, @{
$old
}[oKEY,oURI]}
unless
( @{
$list
} );
return
1;
}
}
return
"0 but true"
;
}
sub
clear {
my
(
$I
)=
@_
;
%{
$I
->_cache}=();
return
"0 but true"
;
}
sub
iterator {
my
(
$I
)=
@_
;
my
$c
;
$c
=[0,
sort
{
$a
->[0]->[KEY] cmp
$b
->[0]->[KEY] or
$a
->[0]->[URI] cmp
$b
->[0]->[URI]}
values
%{
$I
->_cache}];
return
sub
{
my
$i
=
$c
->[0]++;
my
$arr
=
$c
->[1];
unless
(
$i
<=$
return
unless
( @{
$c
}>2 );
$c
=[0, @{
$c
}[2..$
$i
=
$c
->[0]++;
$arr
=
$c
->[1];
}
my
$new
=[];
@{
$new
}[nBLOCK,nORDER,nACTION,nKEY,nURI,nID]=
@{
$arr
->[
$i
]}[BLOCK,ORDER,ACTION,KEY,URI,ID];
$new
->[nNOTE]=
$I
->_getnote(
$new
->[nID]);
return
$new
;
};
}
1;