File Coverage

blib/lib/WWW/PAUSE/CleanUpHomeDir.pm
Criterion Covered Total %
statement 39 137 28.4
branch 3 62 4.8
condition 0 24 0.0
subroutine 11 18 61.1
pod 6 6 100.0
total 59 247 23.8


line stmt bran cond sub pod time code
1             package WWW::PAUSE::CleanUpHomeDir;
2              
3 1     1   176622 use warnings;
  1         3  
  1         26  
4 1     1   3 use strict;
  1         1  
  1         32  
5              
6             our $VERSION = '1.001002'; # VERSION
7              
8 1     1   3 use Carp;
  1         1  
  1         48  
9 1     1   8 use URI;
  1         2  
  1         14  
10 1     1   3 use WWW::Mechanize;
  1         1  
  1         14  
11 1     1   3 use HTML::TokeParser::Simple;
  1         1  
  1         15  
12 1     1   3 use File::Basename;
  1         1  
  1         50  
13 1     1   4 use Devel::TakeHashArgs;
  1         1  
  1         34  
14 1     1   4 use Sort::Versions;
  1         0  
  1         90  
15 1     1   13 use base 'Class::Accessor::Grouped';
  1         1  
  1         1281  
16             __PACKAGE__->mk_group_accessors(simple => qw(
17             error
18             last_list
19             deleted_list
20             _mech
21             _is_use_http
22             ));
23              
24             sub new {
25 1     1 1 306 my $self = bless {}, shift;
26              
27 1         5 my ( $login, $pass ) = splice @_, 0, 2;
28              
29 1 50       6 croak 'Missing mandatory PAUSE login argument'
30             unless defined $login;
31              
32 1 50       3 croak 'Missing mandatory PAUSE password argument'
33             unless defined $pass;
34              
35 1 50       9 get_args_as_hash(\@_, \ my %args, { timeout => 30 } )
36             or croak $@;
37              
38 1         29 $self->_is_use_http( $args{use_http} );
39 1         249 $self->_mech( WWW::Mechanize->new( timeout => $args{timeout} ) );
40 1         15181 $self->_mech->credentials( $login, $pass );
41              
42 1         17 return $self;
43             }
44              
45             sub fetch_list {
46 0     0 1   my $self = shift;
47              
48 0           $self->$_(undef) for qw(last_list error);
49              
50 0 0         my $uri =
51             URI->new(
52             ($self->_is_use_http ? 'http' : 'https')
53             . '://pause.perl.org/pause/authenquery?ACTION=delete_files'
54             );
55              
56 0           my $mech = $self->_mech;
57 0           my $response = $mech->get($uri);
58 0 0         if ( $response->is_success ) {
59 0           return $self->last_list( $self->_parse_list( $mech->content ) );
60             }
61             else {
62 0           return $self->_set_error( $response, 'net' );
63             }
64             }
65              
66             sub list_scheduled {
67 0     0 1   my $self = shift;
68              
69 0           my $list_ref = $self->last_list;
70              
71 0 0         $list_ref = $self->fetch_list
72             unless ref $list_ref eq 'HASH';
73              
74 0 0         return unless defined $list_ref;
75              
76 0           my @scheduled_keys = grep {
77 0           $list_ref->{$_}{status} =~ /Scheduled for deletion/
78             } keys %$list_ref;
79              
80 0 0         return sort @scheduled_keys
81             if wantarray;
82              
83 0           return { map { $_ => $list_ref->{$_} } @scheduled_keys };
  0            
84             }
85              
86             sub list_old {
87 0     0 1   my $self = shift;
88              
89 0           my $list_ref = $self->last_list;
90              
91 0 0         $list_ref = $self->fetch_list
92             unless ref $list_ref eq 'HASH';
93              
94 0 0         return unless defined $list_ref;
95              
96 0           my @suf = qw(.meta .readme .tar.gz .tgz .tar .gz .zip .bz2 .bz );
97 0           my $scheduled_re = qr/Scheduled for deletion/;
98 0           my $extracted_re = qr/\.(?:readme|meta)$/;
99 0 0 0       my %files = map { (fileparse $_, @suf )[0,2] }
  0            
100             grep {
101 0           $_ ne 'CHECKSUMS'
102             and $_ !~ /$extracted_re/
103             and $list_ref->{$_}{status} !~ /$scheduled_re/
104             } keys %$list_ref;
105              
106 0           my @files = sort {
107 0           my ($na, $va) = $a =~ /(.+)-(\d.+)/;
108 0           my ($nb, $vb) = $b =~ /(.+)-(\d.+)/;
109 0 0         $na cmp $nb || versioncmp($va, $vb);
110             } grep !/
111             -(?!.*-) # last dash in the filename
112             .*(TRIAL|_) # trial versions
113             /x, keys %files;
114              
115 0           my @old;
116 0           my $re = qr/([^.]+)-/;
117 0           for ( 0 .. $#files-1) {
118 0           my $name = ($files[ $_ ] =~ /$re/)[0];
119 0           my $next_name = ($files[ $_+1 ] =~ /$re/)[0];
120             next
121 0 0 0       unless ( defined $name and defined $next_name )
      0        
122             or $next_name =~ /
123             -(?!.*-) # last dash in the filename
124             .*(TRIAL|_) # trial versions
125             /x;
126              
127 0 0         push @old, $files[$_]
128             if $name eq $next_name;
129             }
130              
131 0 0         return sort @old
132             if wantarray;
133              
134 0           return { map { $_ => $files{$_} } @old };
  0            
135             }
136              
137             sub clean_up {
138 0     0 1   my $self = shift;
139 0           my $only_these_ref = shift;
140              
141 0           $self->$_(undef) for qw(last_list deleted_list list_old);
142             # make sure ->list_old reloads the page to avoid surprises with mech
143              
144 0           my $to_delete_ref = $self->list_old;
145 0 0 0       if ( defined $only_these_ref and @$only_these_ref ) {
146 0           $to_delete_ref = {
147 0           map { $_ => $to_delete_ref->{$_} }
148             @$only_these_ref
149             };
150             }
151              
152 0           my @files = map +("$_$to_delete_ref->{$_}", "$_.meta", "$_.readme"),
153             sort keys %$to_delete_ref;
154              
155 0 0         return $self->_set_error('No files to delete')
156             unless @files;
157              
158 0           my $mech = $self->_mech;
159 0           $mech->form_number(1); # we already loaded the page from ->list_old
160              
161             $mech->tick('pause99_delete_files_FILE', $_ )
162 0           for @files;
163              
164 0           my $response = $mech->click('SUBMIT_pause99_delete_files_delete');
165              
166 0 0         if ( $response->is_success ) {
167 0           $self->last_list(undef); # reset list again it's too old now
168              
169 0           return $self->deleted_list( \@files );
170             }
171             else {
172 0           return $self->_set_error( $response, 'net' );
173             }
174             }
175              
176             sub undelete {
177 0     0 1   my $self = shift;
178 0           my $only_these_ref = shift;
179              
180 0 0         my @files = @{ $self->deleted_list || [] };
  0            
181 0 0 0       if ( defined $only_these_ref and @$only_these_ref ) {
182 0           @files = @$only_these_ref;
183             }
184              
185 0 0         return $self->_set_error('No files to undelete')
186             unless @files;
187              
188 0 0         my $uri =
189             URI->new(
190             ($self->_is_use_http ? 'http' : 'https')
191             . '://pause.perl.org/pause/authenquery?ACTION=delete_files'
192             );
193              
194 0           my $mech = $self->_mech;
195 0           my $response = $mech->get($uri);
196 0 0         return $self->_set_error( $response, 'net' )
197             unless $mech->success;
198              
199 0           $mech->form_number(1); # we already loaded the page from ->list_old
200             $mech->tick('pause99_delete_files_FILE', $_)
201 0           for @files;
202              
203 0           $response = $mech->click('SUBMIT_pause99_delete_files_undelete');
204              
205 0 0         if ( $response->is_success ) {
206 0           $self->deleted_list(undef); # we successfully undeleted all these
207              
208 0           return \@files;
209             }
210             else {
211 0           return $self->_set_error( $response, 'net' );
212             }
213             }
214              
215             sub _parse_list {
216 0     0     my ( $self, $content ) = @_;
217              
218 0           my $parser = HTML::TokeParser::Simple->new( \$content );
219              
220 0           my %data;
221             my %nav;
222 0           my $current_line = 0;
223 0           @nav{ qw(level start get_text) } = (0) x 3;
224 0           while ( my $t = $parser->get_token ) {
225 0 0 0       if ( $t->is_start_tag('pre') ) {
    0 0        
    0          
    0          
226 0           @nav{ qw(level start) } = ( 1, 1 );
227             }
228             elsif ( $t->is_end_tag('pre') ) {
229 0           @nav{ qw(level start is_success) } = ( 2, 0, 1);
230 0           last;
231             }
232             elsif ( $nav{start} == 1 and $t->is_start_tag('span') ) {
233 0           $current_line = $t->get_attr('class');
234 0           @nav{ qw(level get_text) } = ( 3, 1 );
235             }
236             elsif ( $nav{get_text} == 1 and $t->is_text ) {
237 0 0         if ( my ( $name, $size, $status ) = $t->as_is
238             =~ /^\s*(\S+)\s+(\d+)\s+(.+)/s
239             ) {
240 0           $data{$name} = {
241             size => $size,
242             status => $status,
243             };
244              
245 0           @nav{ qw(level get_text) } = ( 4, 0 );
246             }
247             }
248             }
249 0 0         croak "Parser error! (level: $nav{level}) Content: $content"
250             unless $nav{is_success};
251              
252 0           return \%data;
253             }
254              
255             sub _set_error {
256 0     0     my ( $self, $error, $type ) = @_;
257 0 0 0       if ( defined $type and $type eq 'net' ) {
258 0           $self->error( 'Network error: ' . $error->status_line );
259             }
260             else {
261 0           $self->error( $error );
262             }
263 0           return;
264             }
265              
266             1;
267             __END__