package
Net::DirectConnect::filelist;
use
5.10.0;
no
warnings
qw(uninitialized)
;
$Data::Dumper::Sortkeys
=
$Data::Dumper::Useqq
=
$Data::Dumper::Indent
= 1;
our
$VERSION
= (
split
(
' '
,
'$Revision: 966 $'
) )[1];
our
%config
;
*config
=
*main::config
;
$config
{
'log_'
.
$_
} //= 0
for
qw (dmp
dcdmp dcdbg trace);
$config
{
'log_'
.
$_
} //= 1
for
qw (screen
default
);
Net::DirectConnect::use_try
'Sys::Sendfile'
unless
$^O =~ /win/i;
Net::DirectConnect::use_try
'Sys::Sendfile::FreeBSD'
if
$^O =~ /freebsd/i;
my
(
$tq
,
$rq
,
$vq
);
sub
skip ($$) {
my
(
$file
,
$match
) =
@_
;
return
unless
length
$match
;
for
my
$m
(
ref
$match
eq
'ARRAY'
?
@$match
:
$match
) {
return
1
if
ref
$m
eq
'Regexp'
and
$file
=~
$m
;
return
1
if
!
ref
$m
and
$file
eq
$m
;
}
}
sub
new {
my
$standalone
= !
ref
$_
[0];
my
$self
=
ref
$_
[0] ?
shift
() :
bless
{},
$_
[0];
shift
if
$_
[0] eq __PACKAGE__;
$self
->func(
@_
);
$self
->init_main(
@_
);
$self
->{
'log'
} =
sub
(@) {
my
$dc
=
ref
$_
[0] ?
shift
:
$self
|| {};
psmisc::printlog
shift
(),
"[$dc->{'number'}]"
,
@_
,;
},;
$self
->{no_sql} //= 0;
$self
->{files} //=
'files.xml'
;
$self
->{tth_cheat} //= 1_000_000;
$self
->{tth_cheat_no_date} //= 0;
$self
->{file_min} //= 0;
$self
->{filelist_scan} //= 3600 * 1;
$self
->{filelist_reload} //= 300;
$self
->{file_send_by} //= 1024 * 1024 * 1;
$self
->{skip_hidden} //= 1;
$self
->{skip_symlink} //= 0;
$self
->{skip_dir} //= [
qr'(?:^|/)Incomplete(?:/|$)'
, ( !
$self
->{skip_hidden} ? () :
qr{(?:^|/)\.}
), ];
$self
->{skip_file} //=
[
qr/\.(?:partial|(?:dc)tmp)$/
i,
qr/^~uTorrentPartFile_/
i, ( !
$self
->{skip_hidden} ? () :
qr{(?:^|/)\.}
), ];
$self
->{
'share'
} = [
$self
->{
'share'
} ]
unless
ref
$self
->{
'share'
};
tr
{\\}{/}
for
@{
$self
->{
'share'
} || [] };
Net::DirectConnect::adc::func(
$self
);
$self
->ID_get();
unless
(
$self
->{no_sql} ) {
local
%_
= (
'driver'
=>
'sqlite'
,
'database'
=>
'files'
,
'log'
=>
$self
->{
'log'
},
'connect_tries'
=> 0,
'connect_chain_tries'
=> 0,
'error_tries'
=> 0,
'error_chain_tries'
=> 0,
'upgrade'
=>
sub
{
my
$db
=
shift
if
ref
$_
[0];
$db
->
do
(
"ALTER TABLE filelist ADD COLUMN $_"
)
for
'hit INTEGER UNSIGNED NOT NULL DEFAULT 0 '
,
'sch INTEGER UNSIGNED NOT NULL DEFAULT 0 '
;
},
);
$self
->{sql}{
$_
} //=
$_
{
$_
}
for
keys
%_
;
my
(
$short
) =
$self
->{sql}{
'driver'
} =~ /mysql/;
my
%table
= (
'filelist'
=> {
'path'
=> pssql::row(
undef
,
'type'
=>
'VARCHAR'
,
'length'
=> (
$short
? 150 : 255 ),
'default'
=>
''
,
'index'
=> 1,
'primary'
=> 1
),
'file'
=> pssql::row(
undef
,
'type'
=>
'VARCHAR'
,
'length'
=> (
$short
? 150 : 255 ),
'default'
=>
''
,
'index'
=> 1,
'primary'
=> 1
),
'tth'
=> pssql::row(
undef
,
'type'
=>
'VARCHAR'
,
'length'
=> 40,
'default'
=>
''
,
'index'
=> 1 ),
'size'
=> pssql::row(
undef
,
'type'
=>
'BIGINT'
,
'index'
=> 1, ),
'time'
=> pssql::row(
'time'
, ),
'hit'
=> pssql::row(
undef
,
'type'
=>
'INTEGER UNSIGNED NOT NULL DEFAULT 0 '
, ),
'sch'
=> pssql::row(
undef
,
'type'
=>
'INTEGER UNSIGNED NOT NULL DEFAULT 0'
, ),
},
);
if
(
$self
->{db} ) {
$self
->{db}{table}{
$_
} =
$table
{
$_
}
for
keys
%table
;
$self
->{db}{upgrade} =
$_
{upgrade};
}
local
%_
= (
'table'
=> \
%table
, );
$self
->{sql}{
$_
} //=
$_
{
$_
}
for
keys
%_
;
$self
->{db} ||= pssql->new( %{
$self
->{
'sql'
} || {} }, );
(
$tq
,
$rq
,
$vq
) =
$self
->{db}->quotes();
}
$self
->{filelist_make} //=
sub
{
my
$self
=
shift
if
ref
$_
[0];
my
$notth
;
return
unless
psmisc::
lock
(
'sharescan'
,
timeout
=> 0,
old
=> 86400 );
$self
->
log
(
'err'
,
"sorry, cant load Net::DirectConnect::TigerHash for hashing"
),
$notth
= 1,
unless
Net::DirectConnect::use_try
'Net::DirectConnect::TigerHash'
;
$self
->
log
(
'err'
,
'forced db upgrade on make'
),
$self
->{db}->upgrade()
if
$self
->{upgrade_force};
my
$stopscan
;
my
$level
= 0;
my
$levelreal
= 0;
my
(
$sharesize
,
$sharefiles
);
my
$interrupted
;
my
$printinfo
=
sub
() {
$self
->
log
(
'sharesize'
, psmisc::human(
'size'
,
$sharesize
),
$sharefiles
,
scalar
keys
%{
$self
->{share_full} } );
};
local
$SIG
{INT} =
sub
{ ++
$stopscan
; ++
$interrupted
;
$self
->
log
(
'warn'
,
"INT rec, stopscan"
) };
local
$SIG
{INFO} =
sub
{
$printinfo
->(); };
psmisc::file_rewrite
$self
->{files},
qq{<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<FileListing Version="1" }
, ( !
$self
->{
'INF'
}{
'ID'
} ? () :
qq{CID="$self->{'INF'}
{
'ID'
}" } ),
qq{Base="/" Generator="Net::DirectConnect $Net::DirectConnect::VERSION">
}
;
my
%o
;
my
$o
=
sub
{
our
$n
;
$o
{
$_
[0] } = ++
$n
;
@_
};
our
%table2filelist
= (
$o
->(
file
=>
'Name'
),
$o
->(
size
=>
'Size'
),
$o
->(
tth
=>
'TTH'
),
$o
->(
time
=>
'TS'
),
$o
->(
hit
=>
'HIT'
),
$o
->(
sch
=>
'SCH'
)
);
my
$filelist_line
=
sub
($) {
for
my
$f
(
@_
) {
next
if
!
length
$f
->{file} or !
length
$f
->{
'tth'
};
$sharesize
+=
$f
->{size};
++
$sharefiles
if
$f
->{size};
psmisc::file_append
$self
->{files},
"\t"
x
$level
,
qq{<File}
, (
map
{
qq{ $table2filelist{$_}
=
"} . psmisc::html_chars( $a = $f->{$_} ) . qq{"
} }
sort
{
$o
{
$a
} <=>
$o
{
$b
} }
grep
{
$table2filelist
{
$_
} and
$f
->{
$_
} }
keys
%$f
),
qq{/>\n}
;
$f
->{
'full'
} ||=
$f
->{
'path'
} .
'/'
.
$f
->{
'file'
};
}
};
my
$scandir
;
$scandir
=
sub
(@) {
for
my
$dir
(
@_
) {
last
if
$stopscan
;
$dir
=~
tr
{\\}{/};
$dir
=~ s{/+$}{};
opendir
(
my
$dh
,
$dir
) or (
$self
->
log
(
'err'
,
"can't opendir [$dir]: $!\n"
),
next
);
(
my
$dirname
=
$dir
);
$dirname
=
Encode::decode
$self
->{charset_fs},
$dirname
, Encode::FB_WARN
if
$self
->{charset_fs};
next
if
skip(
$dirname
,
$self
->{skip_dir} ) or (
$self
->{skip_symlink} and -l
$dirname
);
unless
(
$level
) {
for
(
split
'/'
,
$dirname
) {
psmisc::file_append(
$self
->{files},
"\t"
x
$level
,
qq{<Directory Name="$_">\n}
), ++
$level
,
if
length
$_
;
}
}
else
{
$dirname
=~
s{.*/}{};
psmisc::file_append(
$self
->{files},
"\t"
x
$level
,
qq{<Directory Name="$dirname">\n}
), ++
$level
, ++
$levelreal
,
if
length
$dirname
;
}
psmisc::schedule( [ 10, 10 ],
our
$my_every_10sec_sub__
||=
sub
{
$printinfo
->() } );
FILE:
for
my
$file
(
readdir
(
$dh
) ) {
last
if
$stopscan
;
next
if
$file
=~ /^\.\.?$/;
my
$f
= {
path
=>
$dir
,
path_local
=>
$dir
,
file
=>
$file
,
file_local
=>
$file
,
full_local
=>
"$dir/$file"
, };
$f
->{dir} = -d
$f
->{full_local};
if
(
$f
->{dir} ) {
$scandir
->(
$f
->{full_local} );
next
;
}
$f
->{size} = -s
$f
->{full_local}
if
-f
$f
->{full_local};
next
if
$f
->{size} <
$self
->{file_min};
$f
->{file} =
Encode::decode
$self
->{charset_fs},
$f
->{file}, Encode::FB_WARN
if
$self
->{charset_fs};
$f
->{path} =
Encode::decode
$self
->{charset_fs},
$f
->{path}, Encode::FB_WARN
if
$self
->{charset_fs};
next
FILE
if
skip(
$f
->{file},
$self
->{skip_file} ) or (
$self
->{skip_symlink} and -l
$f
->{file} );
$f
->{full} =
"$f->{path}/$f->{file}"
;
$f
->{
time
} =
int
( $^T - 86400 * -M
$f
->{full_local} );
unless
(
$self
->{no_sql} ) {
my
$indb
=
$self
->{db}->line(
"SELECT * FROM ${tq}filelist${tq} WHERE"
.
" ${rq}path${rq}="
.
$self
->{db}->quote(
$f
->{path} )
.
" AND ${rq}file${rq}="
.
$self
->{db}->quote(
$f
->{file} )
.
" AND ${rq}size${rq}="
.
$self
->{db}->quote(
$f
->{size} )
.
" AND ${rq}time${rq}="
.
$self
->{db}->quote(
$f
->{
time
} )
.
" LIMIT 1"
);
$filelist_line
->( {
%$f
,
%$indb
} ),
next
,
if
$indb
->{size} ~~
$f
->{size};
if
(
$f
->{size} >
$self
->{tth_cheat} ) {
my
$indb
=
$self
->{db}->line(
"SELECT * FROM ${tq}filelist${tq} WHERE "
.
"${rq}file${rq}="
.
$self
->{db}->quote(
$f
->{file} )
.
" AND ${rq}size${rq}="
.
$self
->{db}->quote(
$f
->{size} )
. (
$self
->{tth_cheat_no_date} ? () :
" AND ${rq}time${rq}="
.
$self
->{db}->quote(
$f
->{
time
} ) )
.
" LIMIT 1"
);
if
(
$indb
->{tth} ) {
$self
->
log
(
'dev'
,
"already summed"
,
%$f
,
' as '
,
%$indb
);
$f
->{
$_
} ||=
$indb
->{
$_
}
for
keys
%$indb
;
}
}
}
if
( !
$notth
and !
$f
->{tth} ) {
my
$time
=
time
();
$f
->{tth} = Net::DirectConnect::TigerHash::tthfile(
$f
->{full_local} );
my
$per
=
time
-
$time
;
$self
->
log
(
'time'
,
$f
->{full}, psmisc::human(
'size'
,
$f
->{size} ),
'per'
, psmisc::human(
'time_period'
,
$per
),
'speed ps'
, psmisc::human(
'size'
,
$f
->{size} / (
$per
or 1 ) ),
'total'
, psmisc::human(
'size'
,
$sharesize
)
)
if
$per
> 1;
}
$filelist_line
->(
$f
);
$self
->{db}->insert_hash(
'filelist'
,
$f
)
if
!
$self
->{no_sql} and
$f
->{tth};
}
--
$level
;
--
$levelreal
;
psmisc::file_append
$self
->{files},
"\t"
x
$level
,
qq{</Directory>\n}
;
closedir
$dh
;
}
if
(
$levelreal
< 0 ) {
psmisc::file_append
$self
->{files},
"\t"
x
$level
,
qq{</Directory>\n}
while
--
$level
>= 0;
$levelreal
=
$level
= 0;
}
};
$self
->
log
(
'info'
,
"making filelist $self->{files} from"
,
@_
, @{
$self
->{
'share'
} || [] },
'EXISTS='
,
grep
{ -d }
@_
,
@{
$self
->{
'share'
} || [] },
);
$self
->{db}->analyze(
'filelist'
)
unless
$self
->{no_sql};
local
%_
;
$scandir
->(
$_
)
for
(
grep
{ !
$_
{
$_
}++ and -d }
@_
, @{
$self
->{
'share'
} || [] }, );
psmisc::file_append
$self
->{files},
qq{</FileListing>}
;
psmisc::file_append
$self
->{files};
$self
->{db}->flush_insert()
unless
$self
->{no_sql};
local
$_
;
if
(
psmisc::use_try
'IO::Compress::Bzip2'
and (
$_
= !IO::Compress::Bzip2::bzip2(
$self
->{files} =>
$self
->{files} .
'.bz2'
)
or
$self
->
log
(
"bzip2 failed: "
,
$IO::Compress::Bzip2::Bzip2Error
) and 0 )
)
{
() =
$IO::Compress::Bzip2::Bzip2Error
;
}
else
{
$self
->
log
(
'dev'
,
'using system bzip2'
,
$_
, $!,
':'
, `bzip2 --force --keep
"$self->{files}"
` );
}
psmisc::unlock(
'sharescan'
);
$printinfo
->();
return
(
$sharesize
,
$sharefiles
);
};
$self
->{share_add_file} //=
sub
{
my
$self
=
shift
if
ref
$_
[0];
my
(
$full_local
,
$tth
,
$file
) =
@_
;
$full_local
=~ m{([^/\\]+)$}
unless
$file
;
$file
//= $1;
$self
->{share_full}{
$tth
} =
$full_local
,
$self
->{share_tth}{
$full_local
} =
$tth
,
$self
->{share_tth}{
$file
} =
$tth
,
if
$tth
;
$self
->{share_full}{
$file
} ||=
$full_local
if
$file
;
};
$self
->{share_changed} //=
sub
{
my
$self
=
shift
if
ref
$_
[0];
if
(
$self
->{
'status'
} eq
'connected'
) {
if
(
$self
->{adc} ) {
$self
->cmd(
'I'
,
'INF'
,
undef
,
'SS'
,
'SF'
); }
else
{
$self
->cmd(
'MyINFO'
); }
}
};
$self
->{filelist_load} //=
sub
{
my
$self
=
shift
if
ref
$_
[0];
$self
->
log
(
'err'
,
'forced db upgrade on load'
),
$self
->{db}->upgrade()
if
$self
->{upgrade_force};
return
if
!
$self
->{files}
or
$Net::DirectConnect::global
{shareloaded} == -s
$self
->{files}
or
(
$Net::DirectConnect::global
{shareloaded} and !psmisc::
lock
(
'sharescan'
,
readonly
=> 1,
timeout
=> 0,
old
=> 86400 ) )
or !
open
my
$f
,
'<:encoding(utf8)'
,
$self
->{files};
my
(
$sharesize
,
$sharefiles
);
$Net::DirectConnect::global
{shareloaded} = -s
$f
;
local
$/ =
'<'
;
%{
$self
->{share_full} } = %{
$self
->{share_tth} } = ();
my
$dir
;
while
(<
$f
>) {
if
(
my
(
$file
,
$size
,
$tth
,
$ts
) = m{^File Name=
"([^"
]+)
" Size="
(\d+)
" TTH="
([^
"]+)"
}i ) {
my
$full_local
= (
my
$full
=
"$dir/$file"
);
$full_local
= Encode::encode
$self
->{charset_fs},
$full_local
, Encode::FB_WARN;
$self
->share_add_file(
$full_local
,
$tth
,
$file
);
++
$sharefiles
;
$sharesize
+=
$size
;
}
elsif
(
my
(
$curdir
) = m{^Directory Name=
"([^"
]+)">}i ) {
$dir
.= ( ( !
length
$dir
and $^O ~~ [
'MSWin32'
,
'cygwin'
] ) ? () :
'/'
) .
$curdir
;
}
elsif
(m{^/Directory>}i) {
$dir
=~ s{(?:^|/)[^/]+$}{};
}
}
$self
->{share_full}{
$self
->{files} .
'.bz2'
} =
$self
->{files} .
'.bz2'
;
$self
->{share_full}{
$self
->{files} } =
$self
->{files};
$self
->
log
(
'info'
,
"loaded filelist size"
,
$Net::DirectConnect::global
{shareloaded},
' : files='
,
$sharefiles
,
'bytes='
,
psmisc::human(
'size'
,
$sharesize
),
scalar
keys
%{
$self
->{share_full} },
"bzsize="
, -s
$self
->{files} .
'.bz2'
,
);
psmisc::unlock(
'sharescan'
);
$sharefiles
*=
$self
->{sharefiles_mul}
if
$self
->{sharefiles_mul};
$sharefiles
+=
$self
->{sharefiles_add};
$sharesize
*=
$self
->{sharesize_mul}
if
$self
->{sharesize_mul};
$sharesize
+=
$self
->{sharesize_add};
$self
->{sharefiles} =
$self
->{INF}{SF} =
$sharefiles
,
$self
->{INF}{SS} =
$self
->{sharesize} =
$sharesize
,
if
$sharesize
;
$self
->share_changed();
return
(
$sharesize
,
$sharefiles
);
};
$self
->{search_stat_update} =
sub
{
my
$self
=
shift
if
ref
$_
[0];
my
$tth
=
shift
or
return
;
my
$field
=
shift
||
'hit'
;
my
$updated
=
$self
->{db}->
do
(
"UPDATE ${tq}filelist${tq} SET ${rq}$field${rq}=${rq}$field${rq}+${vq}1${vq} WHERE "
.
"${rq}tth${rq}="
.
$self
->{db}->quote(
$tth
)
. (
$self
->{db}{no_update_limit} ? () :
" LIMIT 1"
)
);
$self
->
log
(
'dev'
,
"counter $field increased[$updated] on [$tth]"
)
if
$updated
;
};
$self
->{handler_int}{Search} //=
sub
{
my
$self
=
shift
if
ref
$_
[0];
$self
->search_stat_update(
$_
[1]{tth},
'sch'
);
};
$self
->{handler_int}{SCH} //=
sub
{
my
$self
=
shift
if
ref
$_
[0];
$self
->search_stat_update(
$_
[-1]{TR},
'sch'
);
};
$self
->{
'periodic'
}{ __FILE__ . __LINE__ } =
sub
{
my
$self
=
shift
if
ref
$_
[0];
psmisc::schedule(
$self
->{filelist_scan},
our
$sharescan_sub__
||=
sub
{
my
$self
=
shift
;
$self
->
log
(
'info'
,
'filelist actual age seconds:'
,
(
time
- $^T + 86400 * -M
$self
->{files} ),
'<'
,
$self
->{filelist_scan}
);
return
if
-e
$self
->{files}
and -s
$self
->{files} > 200
and
$self
->{filelist_scan} >
time
- $^T + 86400 * -M
$self
->{files};
$self
->{
'filelist_builder'
} ? psmisc::start
$self
->{
'filelist_builder'
}, @{
$self
->{
'share'
} } : psmisc::start $^X,
$INC
{
'Net/DirectConnect/filelist.pm'
}, @{
$self
->{
'share'
} };
},
$self
)
if
$self
->{filelist_scan};
psmisc::schedule(
$self
->{filelist_reload},
sub
{
my
$self
=
shift
;
$self
->filelist_load(
);
},
$self
)
if
$self
->{filelist_scan};
},
$self
->{handler_int}{file_recieved} =
sub
{
my
$self
=
shift
if
ref
$_
[0];
my
(
$full
,
$filename
) =
@_
;
my
(
$tth
) =
$filename
=~ m{^TTH/(\w+)};
$self
->
log
(
'dev'
,
'adding downloaded file to share'
,
$full
,
$tth
),
$self
->share_add_file(
$full
,
$tth
),
$self
->share_changed()
if
!
$self
->{
'file_recv_filelist'
} and !
$self
->{
'no_auto_share_downloaded'
};
;
};
$self
->filelist_load()
unless
$standalone
;
return
$self
;
}
eval
q{ #do
use lib '../..';
use Net::DirectConnect;
print "making\n";
__PACKAGE__->new(@ARGV)->{db}
->upgrade(),
exit
if
$ARGV
[0] eq
'upgrade'
;
__PACKAGE__->new(
@ARGV
)->filelist_make(
@ARGV
),;
},
print
$@
unless
caller
;
1;