User:AnomieBOT/source/tasks/SourceUploader.pm

This is an old revision of this page, as edited by AnomieBOT (talk | contribs) at 17:58, 19 August 2008 (Updating published sources: General: * BUGFIX: UTF-8 encode all parameters in all API requests * NEW: Persistant storage * NEW: Nowiki/comment handling * NEW: Template search-and-replace * NEW: Mode for BRFA trials * Testing mode should keep going un). The present address (URL) is a permanent link to this revision, which may differ significantly from the current revision.
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.='&nbsp;'.$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;