package tasks::SourceUploader;
=pod
=begin metadata
Task: SourceUploader
BRFA: N/A
Status: Begun 2008-08-15
Rate: On bot startup only, max 6 edits/minute
Updates the pages under [[User:AnomieBOT/source]] to reflect the current source
of the bot.
=end metadata
=cut
use strict;
use AnomieBOT::Task;
use vars qw/@ISA/;
@ISA=qw/AnomieBOT::Task/;
use POSIX qw/strftime/;
use Pod::Simple::Wiki;
use tasks::SourceUploader::Pod;
use Data::Dumper;
use Fcntl ':mode';
my %extensions=(
'pl' => 'perl',
'pm' => 'perl'
);
my @sizes=('', 'KiB', 'MiB', 'GiB', 'TiB', 'PiB', 'EiB', 'ZiB', 'YiB');
=pod
=for info
Per [[WP:BOT#Approval]], any bot or automated editing process that only
affects only the operators' user and talk pages (or subpages thereof),
and which are not otherwise disruptive, may be run without prior
approval.
=cut
sub approved {
return 1;
}
sub new {
my $class=shift;
my $self=$class->SUPER::new();
$self->{'pages'}={};
$self->{'loadexisting'}=1;
my $basedir=$0;
$basedir=~s{/[^/]*$}{};
if(!-d $basedir){
$self->warn("Cannot find source directory\n");
return $self;
}
$self->{'summary'}='Updating published sources: ';
if(!open(X, "<", 'ChangeLog')){
$self->warn("Cannot load changelog\n");
return $self;
}
local $_;
my $intro=1;
while(<X>){
if(/^==.*==$/){
last if !$intro;
$intro=0;
next;
}
$self->{'summary'}.=$_ if !$intro;
}
close(X);
$self->{'summary'}=~s/\s+/ /g;
$self->{'summary'}=~s/\s+$//;
$self->{'summary'}=substr($self->{'summary'},0,255);
my %pages=();
my @dirs=($basedir);
while(my $dir=shift @dirs){
if(!opendir(D, $dir)){
$self->warn("Cannot open directory $dir: $!\n");
return $self;
}
my $dirpage=substr($dir,length($basedir));
$dirpage='/'.$dirpage if(substr($dirpage,0,1) ne '/');
$dirpage ="==Index of $dirpage==\n";
$dirpage.="{| class=\"wikitable sortable\" style=\"width:100%\"\n";
$dirpage.="! Filename !! Size !! Modified\n";
while(my $page=readdir(D)){
next if substr($page,0,1) eq '.';
my $p="$dir/$page";
my $pp=substr($p,length($basedir));
my @stat=stat($p);
my $img='Gnome-fs-executable.svg';
if(-d $p){
next if ($stat[2]&(S_IROTH|S_IXOTH))!=(S_IROTH|S_IXOTH);
push @dirs, $p;
$img='Gnome-fs-directory-visiting.svg';
} elsif(-f $p){
next if ($stat[2]&(S_IROTH))!=(S_IROTH);
if(!open(X, '<:utf8', $p)){
$self->warn("Cannot open file $p: $!\n");
return $self;
}
do {
local $/=undef;
$pages{$pp}=<X>;
};
close(X);
my $top='';
if($page eq 'ChangeLog'){
# Pull out most recent 64K of changelog entries
my @x=split(/\n/, $pages{$pp});
$pages{$pp}='';
while(defined(my $x=shift @x)){
last if(length($pages{$pp})>65536 && $x=~/^==.*==$/);
$pages{$pp}.=$x."\n";
}
$pages{$pp}.="\x7b\x7bombox|type=notice|text=See the \x7b\x7bsubst:history|\x7b\x7bsubst:FULLPAGENAME\x7d\x7d|page history|subst=subst:\x7d\x7d for further ChangeLog entries\x7d\x7d\n" if @x;
} elsif($page=~/\.([^.]+)$/ && exists($extensions{$1})){
if($extensions{$1} eq 'perl'){
# Try to construct POD documentation
my $parser=Pod::Simple::Wiki->new('mediawiki');
my $x='';
$parser->output_string(\$x);
$parser->parse_string_document($pages{$pp});
$pages{"$pp/doc"}=$x if($parser->content_seen);
# Handle embedded notices and metadata
$parser=tasks::SourceUploader::Pod->new;
$parser->output_string(\$x);
$parser->parse_string_document($pages{$pp});
$top.=$x if($parser->content_seen);
my %metadata=$parser->metadata;
if(keys %metadata){
$x ="<noinclude>\n";
$x.="{| class=\"wikitable\"\n";
$x.="! Task !! Disable !! Status !! Rate !! Description\n";
$x.="</noinclude>\n";
$x.="|- valign=\"top\"\n";
my $task=$metadata{'task'};
my $bot="\x02BOT\x03";
$x.="|align=\"center\"| [[User:$bot/source$pp|$task]]\n";
$x.="|align=\"center\"| <span class=\"plainlinks\">[{{fullurl:User:$bot/shutoff/$task|action=edit&editintro=User:$bot/task-disable-editintro}} Here]</span>\n";
my $brfa=$metadata{'brfa'};
my $status=$metadata{'status'};
if($brfa eq 'N/A'){
$x.="| \x7b\x7bsort|$status|\x7d\x7d\x5b\x5bWikipedia:Bot policy#Approval|N/A\x5d\x5d, only edits bot's/owner's userspace. $status\n";
} elsif($brfa eq 'None'){
$x.="| $status\n";
} else {
$x.="| \x5b\x5b$brfa|$status\x5d\x5d\n";
}
$x.="| ".$metadata{'rate'}."\n";
$x.="|\n".$metadata{'*'}."\n";
$x.="<noinclude>\n";
$x.="|}\n";
$x.="</noinclude>";
$pages{"$pp/metadata"}=$x;
}
}
$pages{$pp}="<source lang=\"".$extensions{$1}."\">\n".$pages{$pp}."\n</so"."urce>";
} else {
$pages{$pp}="<pre>\n".$pages{$pp}."\n</pre>";
}
$top.="\x7b\x7bombox|text=See \x5b\x5b/doc\x5d\x5d for formatted documentation\x7d\x7d\n" if(exists($pages{"$pp/doc"}));
$pages{$pp}=$top.$pages{$pp};
$img='Gnome-fs-regular.svg';
} else {
$self->warn("Unusual filetype on $p\n");
}
my $sz=$stat[7];
my $i=0;
while($sz>=1024 && $i<@sizes){ $sz/=1024; $i++; }
($sz="$sz")=~s/(\.\d\d)\d+/$1/;
$sz.=' '.$sizes[$i] if $i>0;
$dirpage.="|-\n";
$dirpage.="| \x5b\x5bImage:$img|32x32px\x5d\x5d";
$dirpage.=" \x5b\x5b/$page|$page\x5d\x5d\n";
$dirpage.="|align=\"right\"| \x7b\x7bsort|".sprintf("%020d",$stat[7])."|$sz\x7d\x7d\n";
$dirpage.="|align=\"center\"| \x7b\x7bsort|".sprintf("%020d",$stat[9]).strftime("|%F %T (UTC)", gmtime($stat[9]))."\x7d\x7d\n";
}
closedir(D);
$dirpage.="|}";
$pages{substr($dir,length($basedir))}=$dirpage;
}
$self->{'pages'}={%pages};
return $self;
}
sub run {
my ($self, $api)=@_;
my @keys=keys(%{$self->{'pages'}});
if(!@keys){
$self->warn("Terminating task");
return undef;
}
$api->task('SourceUploader');
$api->read_throttle(0);
$api->edit_throttle(10);
my $src='User:'.$api->user.'/source';
if($self->{'loadexisting'}){
my %q=(
list => 'allpages',
apprefix => $api->user.'/source',
apnamespace => '2',
aplimit => 'max'
);
my ($k,$v,$k2,$v2,@x);
my $res;
do {
$res=$api->query(%q);
if($res->{'code'} ne 'success'){
$self->warn("Failed to retrieve source tree: ".$res->{'error'});
return 300;
}
if(exists($res->{'query-continue'})){
$q{'apfrom'}=$res->{'query-continue'}{'allpages'}{'apfrom'};
}
foreach (@{$res->{'query'}{'allpages'}}){
$v2=substr($_->{'title'}, length($src));
if(!exists($self->{'pages'}{$v2})){
$self->{'pages'}{$v2}='';
push @keys, $v2;
}
}
} while(exists($res->{'query-continue'}));
$self->{'loadexisting'}=0;
}
my $end=time()+300;
while(@keys){
last if time()>$end;
my $k=shift @keys;
my $tok=$api->edittoken($src.$k);
if($tok->{'code'} eq 'shutoff'){
$self->warn("Task disabled: ".$tok->{'content'}."\n");
return 300;
}
next if($tok->{'code'} ne 'success');
my $page=$self->{'pages'}{$k};
my $bot=$api->user;
$page=~s/\x02BOT\x03/$bot/go;
$page=~s/\s+$//o;
$tok->{'revisions'}[0]{'*'}=~s/\s+$//o;
if(exists($tok->{'missing'}) ||
$tok->{'revisions'}[0]{'*'} ne $page){
my $r=$api->edit($tok, $page, $self->{'summary'}, 0, 1);
if($r->{'code'} ne 'success'){
$self->warn("Write error for $k: ".$r->{'error'});
next;
} else {
$self->warn("Updated $k");
}
} else {
$self->warn("No update needed for $k");
}
delete($self->{'pages'}{$k});
}
return 0;
}
1;