![]() | 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. |
package tasks::SourceUploader;
use strict;
use AnomieBOT::Task;
use vars qw/@ISA/;
@ISA=qw/AnomieBOT::Task/;
use POSIX qw/strftime/;
use Pod::Simple::Wiki;
use Data::Dumper;
use Fcntl ':mode';
my %extensions=(
'pl' => 'perl',
'pm' => 'perl'
);
my @sizes=('', 'KiB', 'MiB', 'GiB', 'TiB', 'PiB', 'EiB', 'ZiB', 'YiB');
sub approved {
### notice type=notice
# 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.
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 32K of changelog entries
my @x=split(/\n/, $pages{$pp});
$pages{$pp}='';
while(my $x=shift @x){
last if $x=~/^==.*==$/ && length($x)>32767;
$pages{$pp}.=$x."\n";
}
$pages{$pp}.="\x7b\x7bombox|type=notice|See the \x7b\x7bsubst:history|\x7b\x7bFULLPAGENAME\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
if($pages{$pp}=~/^\s*### notice( .*\S)?\s+((?:^\s*# .*\s+^)+)/m){
my ($p,$t)=($1,$2);
$p=~s/^\s+|\s+$//g;
$t=~s/^\s+# |\s+$//gm;
$top.="\x7b\x7bombox|$p|text=$t\x7d\x7d\n";
}
}
$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'}});
return undef unless @keys;
$api->task('SourceUploader');
$api->read_throttle(6);
$api->edit_throttle(10);
#return 300 if $self->check_shutoff($api, 'SourceUploader');
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);
next if($tok->{'code'} ne 'success');
if(exists($tok->{'missing'}) ||
$tok->{'revisions'}[0]{'*'} ne $self->{'pages'}{$k}){
my $r=$api->edit($tok, $self->{'pages'}{$k}, $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;