File Coverage

blib/lib/E2/Interface.pm
Criterion Covered Total %
statement 42 493 8.5
branch 3 210 1.4
condition 1 32 3.1
subroutine 10 42 23.8
pod 21 30 70.0
total 77 807 9.5


line stmt bran cond sub pod time code
1             # E2::Interface
2             # Jose M. Weeks
3             # 07 August 2003
4             #
5             # See bottom for pod documentation.
6              
7             package E2::Interface;
8              
9 8     8   37824 use 5.006;
  8         62  
  8         766  
10 8     8   59 use strict;
  8         14  
  8         304  
11 8     8   38 use warnings;
  8         12  
  8         243  
12 8     8   38 use Carp;
  8         19  
  8         2249  
13              
14             our $VERSION = "0.34";
15              
16             # This module also require()s the following modules in the body of
17             # certain methods (threading loads faster this way than with use.
18             # XML::Twig
19             # LWP::UserAgent;
20             # HTTP::Request::Common qw(GET HEAD POST);
21             # HTTP::Cookies;
22             # URI::Escape;
23             # E2::Ticker;
24              
25             # Threading, if supported
26              
27 8     8   34729 eval "
  0         0  
  0         0  
28             use threads;
29             use threads::shared;
30             use Thread::Queue;
31             ";
32             our $THREADED = !$@;
33              
34             # Unicode
35              
36 8     8   8252 eval "
  8         144941  
  8         916  
37             use Encode;
38             ";
39             our $ENCODED = !$@;
40              
41             our $DEBUG = 0; # Debug info: set to 1 for basic debug info,
42             # 2 to add a message for each sub,
43             # 3 to add data dumping
44              
45             # Get OS string
46              
47             our $OS_STRING;
48              
49             BEGIN {
50 8 50   8   221 if( -x '/bin/uname' ) {
51 8         68750 $OS_STRING = `/bin/uname -srmo`;
52 8         39586 chomp( $OS_STRING );
53             } else {
54 0         0 $OS_STRING = $^O;
55 0 0       0 if( $OS_STRING eq 'MSWin32' ) {
56 0         0 my $s;
57 0         0 eval "use Win32";
58 0 0       0 if( !$@ ) {
59 0         0 $s = join ' ', &Win32::GetOSName;
60             }
61              
62 0 0       0 $OS_STRING = $s if $s;
63             }
64             }
65             }
66              
67             sub new;
68             sub clone;
69              
70             sub login;
71             sub verify_login;
72             sub logout;
73             sub process_request;
74              
75             sub domain;
76             sub cookie;
77             sub parse_links;
78             sub document;
79             sub logged_in;
80             sub agentstring;
81              
82             sub version;
83             sub client_name;
84             sub debug;
85              
86             sub this_username;
87             sub this_user_id;
88              
89             sub decode_xml;
90              
91             sub use_threads;
92             sub job_id;
93             sub thread_then;
94             sub finish;
95              
96             # Private
97              
98             sub start_job;
99             sub extract_cookie;
100             sub post_process;
101             sub process_request_raw;
102              
103             ################################################################################
104             # Class methods
105             ################################################################################
106              
107 0     0 1 0 sub version { return $VERSION }
108 0     0 1 0 sub client_name { return "e2interface-perl" }
109             sub decode_xml {
110 0     0 0 0 my( undef, $s ) = @_;
111 0 0       0 return $s if !$ENCODED;
112 0   0     0 return decode_utf8($s) || $s;
113             }
114              
115             sub debug {
116 0     0 1 0 my (undef, $d) = @_;
117              
118 0 0 0     0 if( $d && !$DEBUG ) {
119              
120             # Print e2interface info
121              
122 0         0 print '-' x 80 . "\n";
123 0         0 print &client_name . '/' . &version .
124             " by Jose M. Weeks (Simpleton)\n";
125 0         0 printf "Perl v%vd", $^V;
126 0 0       0 print "; $OS_STRING;" . ' Threads ' .
127             ($THREADED ? '' : 'UN' ) . "AVAILABLE\n";
128 0         0 print '-' x 80 . "\n";
129             }
130              
131 0         0 $DEBUG = $d;
132             }
133              
134             sub new {
135 6     6 1 16 my $arg = shift;
136 6   33     53 my $class = ref( $arg ) || $arg;
137 6         17 my $self = {};
138              
139 6 50       25 warn "Creating $class object" if $DEBUG > 1;
140              
141             # All of these are references so that we can clone()
142             # copies and any changes after the cloning affect all
143             # clones.
144              
145 6         48 $self->{this_username} = \(my $a = 'Guest User');
146 6         18 $self->{this_user_id} = \(my $b);
147            
148 6         16 $self->{agentstring} = \(my $c);
149 6         19 $self->{cookie} = \(my $d);
150              
151 6         19 $self->{parse_links} = \(my $e);
152 6         15 $self->{domain} = \(my $f = "everything2.com" );
153              
154 6         17 $self->{threads} = \(my $ta);
155 6         23 $self->{next_job_id} = \(my $tb = 1);
156 6         12 $self->{job_to_thread} = \(my $tc);
157 6         16 $self->{post_commands} = \(my $td);
158 6         14 $self->{final_commands} = \(my $te);
159 6         15 $self->{finished} = \(my $tf);
160            
161 6         37 return bless $self, $class;
162             }
163              
164             ################################################################################
165             # Object Methods
166             ################################################################################
167              
168             sub clone {
169 0 0   0 1 0 my $self = shift or croak "Usage: clone E2INTERFACE_DEST, E2INTERFACE_SRC";
170 0 0       0 my $src = shift or croak "Usage: clone E2INTERFACE_DEST, E2INTERFACE_SRC";
171              
172 0 0       0 warn "E2::Interface::clone\n" if $DEBUG > 1;
173              
174 0         0 $self->{agentstring} = $src->{agentstring};
175 0         0 $self->{this_username} = $src->{this_username};
176 0         0 $self->{this_user_id} = $src->{this_user_id};
177 0         0 $self->{parse_links} = $src->{parse_links};
178 0         0 $self->{domain} = $src->{domain};
179 0         0 $self->{cookie} = $src->{cookie};
180 0         0 $self->{threads} = $src->{threads};
181 0         0 $self->{next_job_id} = $src->{next_job_id};
182 0         0 $self->{job_to_thread} = $src->{job_to_thread};
183 0         0 $self->{post_commands} = $src->{post_commands};
184 0         0 $self->{final_commands} = $src->{final_commands};
185 0         0 $self->{finished} = $src->{finished};
186            
187 0         0 return $self;
188             }
189              
190             sub login {
191 0 0   0 1 0 my $self = shift or croak( "Usage: login E2INTERFACE, USERNAME, PASSWORD" );
192 0 0       0 my $username = shift or croak( "Usage: login E2INTERFACE, USERNAME, PASSWORD" );
193 0 0       0 my $password = shift or croak( "Usage: login E2INTERFACE, USERNAME, PASSWORD" );
194              
195 0 0       0 warn "E2::Interface::login\n" if $DEBUG > 1;
196              
197 0         0 require E2::Ticker;
198              
199             return $self->thread_then(
200             [
201             \&process_request,
202             $self,
203             op => 'login',
204             user => $username,
205             passwd => $password,
206             node => $E2::Ticker::xml_title{session}
207             ],
208             sub {
209 0     0   0 my $xml = shift;
210              
211 0 0       0 if( $xml =~ /(.*?)
212 0         0 ${$self->{this_username}} = $2;
  0         0  
213 0         0 ${$self->{this_user_id}} = $1;
  0         0  
214             } else {
215 0         0 croak "Invalid document";
216             }
217              
218 0   0     0 return $self->cookie && 1;
219 0         0 });
220             }
221              
222             sub verify_login {
223 0     0 1 0 my $self = shift;
224            
225 0         0 require E2::Ticker;
226              
227 0 0       0 warn "E2::Interface::verify_login\n" if $DEBUG > 1;
228              
229 0 0       0 return undef if !$self->logged_in;
230              
231             return $self->thread_then(
232             [
233             \&process_request,
234             $self,
235             node => $E2::Ticker::xml_title{session}
236             ],
237             sub {
238 0     0   0 my $xml = shift;
239            
240 0 0       0 if( $xml =~ /(.*?)
241 0         0 ${$self->{this_username}} = $2;
  0         0  
242 0         0 ${$self->{this_user_id}} = $1;
  0         0  
243             } else {
244 0         0 croak "Invalid document";
245             }
246              
247 0   0     0 return $self->cookie && 1;
248 0         0 });
249             }
250              
251             sub logout {
252 0 0   0 1 0 my $self = shift or croak "Usage: logout E2INTERFACE";
253              
254 0 0       0 warn "E2::Interface::logout\n" if $DEBUG > 1;
255              
256 0         0 $self->cookie( undef );
257 0         0 ${$self->{this_username}} = 'Guest User';
  0         0  
258 0         0 ${$self->{this_user_id}} = undef;
  0         0  
259              
260 0         0 return 1;
261             }
262              
263             sub process_request {
264 0 0   0 1 0 my $self = shift
265             or croak "Usage: process_request E2INTERFACE, [ ATTR => VAL [ , ATTR2 => VAL2 , ... ] ]";
266 0 0       0 my %pairs = @_
267             or croak "Usage: process_request E2INTERFACE, [ ATTR => VAL [ , ATTR2 => VAL2 , ... ] ]";
268              
269 0 0       0 warn "E2::Interface::process_request\n" if $DEBUG > 1;
270              
271             # If we're dealing with threads, send a process_request message
272              
273 0 0       0 if( ${$self->{threads}} ) {
  0         0  
274 0         0 return $self->start_job(
275             'POST',
276             'http://' . $self->domain . '/',
277             $self->cookie,
278 0 0       0 ${$self->{agentstring}},
279             ($self->parse_links ? () : (links_noparse => 1)),
280             %pairs
281             );
282             }
283              
284             # Otherwise, just process the request
285              
286 0         0 my $response = process_request_raw(
287             'POST',
288             'http://' . $self->domain . '/',
289             $self->cookie,
290 0 0       0 ${$self->{agentstring}},
291             ($self->parse_links?():(links_noparse => 1)),
292             %pairs
293             );
294              
295 0         0 my $c = extract_cookie( $response );
296 0 0       0 $self->cookie( $c ) if $c;
297              
298 0         0 return $self->{last_document} = post_process( $response );
299             }
300              
301             sub this_username {
302 0 0   0 1 0 my $self = shift or croak "Usage: this_username E2INTERFACE";
303 0         0 return ${$self->{this_username}};
  0         0  
304             }
305              
306             sub this_user_id {
307 0 0   0 1 0 my $self = shift or croak "Usage: this_user_id E2INTERFACE";
308 0         0 return ${$self->{this_user_id}};
  0         0  
309             }
310              
311             sub logged_in {
312 0 0   0 1 0 my $self = shift or croak "Usage: logged_in E2INTERFACE";
313              
314 0   0     0 return ${$self->{cookie}} && 1;
315             }
316              
317             sub domain {
318 0 0   0 1 0 my $self = shift or croak "Usage: domain E2INTERFACE [, DOMAIN ]";
319            
320 0 0       0 ${$self->{domain}} = $_[0] if $_[0];
  0         0  
321            
322 0         0 return ${$self->{domain}};
  0         0  
323             }
324              
325             sub cookie {
326 0 0   0 1 0 my $self = shift or croak "Usage: cookie E2INTERFACE [, COOKIE ]";
327              
328 0 0       0 if( @_ ) {
329 0         0 ${$self->{cookie}} = $_[0];
  0         0  
330              
331 0 0       0 if( $_[0] =~ /(.*?)%257C/ ) {
332 0         0 ${$self->{this_username}} = $1;
  0         0  
333             }
334             }
335              
336 0         0 return ${$self->{cookie}};
  0         0  
337             }
338              
339             sub agentstring {
340 0 0   0 1 0 my $self = shift or croak "Usage: agentstring E2INTERFACE [, STRING ]";
341              
342 0 0       0 ${$self->{agentstring}} = $_[0] if @_;
  0         0  
343              
344 0         0 return ${$self->{agentstring}};
  0         0  
345             }
346              
347             sub parse_links {
348 0 0   0 0 0 my $self = shift or croak "Usage: parse_links E2INTERFACE [ , BOOL ]";
349              
350 0 0       0 ${$self->{parse_links}} = $_[0] if @_;
  0         0  
351              
352 0         0 return ${$self->{parse_links}};
  0         0  
353             }
354              
355             sub document {
356 0 0   0 1 0 my $self = shift or croak "Usage: xml E2INTERFACE";
357              
358 0         0 return $self->{last_document};
359             }
360              
361             sub parse_twig {
362 6 50   6 0 27 if( @_ != 3 ) { croak "Usage: parse_twig E2INTERFACE, XML, HANDLERS"; }
  0         0  
363 6         23 my ( $self, $xml, $handlers ) = @_;
364            
365 6         4381 require XML::Twig;
366              
367 0 0         warn "E2::Interface::parse_twig\n" if $DEBUG > 1;
368              
369 0           my $twig = new XML::Twig(
370             # keep_encoding => 1,
371             twig_handlers => $handlers
372             );
373              
374             # If we're using a version of perl that allows us to do it, make sure
375             # the string is in perl's internal representation, then encode into
376             # UTF8.
377              
378 0 0         if( $ENCODED ) {
379 0   0       $xml = decode_utf8( $xml ) || $xml;
380 0           $xml = encode_utf8( $xml );
381             }
382              
383 0 0         if( !$twig->safe_parse( $xml, ProtocolEncoding => 'UTF-8' ) ) {
384 0           chomp $@;
385 0           croak "Parse error: $@";
386             }
387             }
388              
389             ################################################################################
390             #
391             # Threading in e2interface.
392             #
393             # (The background thread)
394             #
395             # 1: thread_then creates NUM background threads (sub _thread), each
396             # with its own input and output queue
397             # 2: each _thread waits for a two value list on its input queue:
398             # a. job_id
399             # b. reference to a list identical to the parameter list to
400             # process_request_raw
401             # 3: each _thread calls process_request_raw, then calls extract_cookie
402             # and post_process on the response, and returns the following on
403             # it output queue:
404             # a. job_id
405             # b. reference to a hash with the following keys:
406             # exception - exception string: only defined on exception
407             # cookie - the return value of extract_cookie
408             # text - the return value of post_process
409             #
410             # (The main thread) -- (these bubble upward from the lowlevel methods, so
411             # this mainly ordered backward, but follows the return
412             # values upward)
413             #
414             # 1: start_job takes the same parameters as process_request_raw. It
415             # passes this list off to the first convenient background thread
416             # and stores the job_id -> thread mapping. It returns (-1, job_id)
417             # 2: process_request, if threading has been enabled, calls start_job
418             # and returns (-1, job_id)
419             # 3: thread_then takes two code references as parameters. It calls the
420             # first code reference.
421             # a. if this reference returns (-1,job_id), it stores the second
422             # reference to be executed when that first one finishes,
423             # and to be passed its return value as its parameters.
424             # This allows thread_then to be chained, each return value
425             # passed to the next stored code reference.
426             # b. If this reference returns anything else, it passes this
427             # value directly to the second code reference and then
428             # returns the subsequent return value.
429             # in effect, thread_then allows code to be executed regardless of
430             # whether or not it calls a method that gets passed to a background
431             # thread.
432             # 4: finish checks the output queue of the background threads. If
433             # the specified job hasn't finished yet, it returns (-1, job_id).
434             # If it has finished, finish executes any stored code references
435             # (those that thread_then stored). It returns the return value of
436             # the final stored code reference.
437             #
438             ################################################################################
439              
440             sub use_threads {
441 0 0   0 1   my $self = shift or croak "Usage: use_threads E2INTERFACE [ COUNT ]";
442 0   0       my $count = shift || 1;
443              
444 0 0         warn "E2::Interface::use_threads\n" if $DEBUG > 1;
445              
446 0 0         if( ! $THREADED ) {
447 0 0         warn "Unable to use_threads: ithreads not available" if $DEBUG;
448 0           return undef;
449             }
450              
451 0 0         if( $count < 1 ) {
452 0 0         warn "Unable to use_threads: invalid number $count" if $DEBUG;
453 0           return undef;
454             }
455              
456 0 0         if( ${$self->{threads}} ) {
  0            
457 0 0         warn "Unable to use_threads: threads already in use" if $DEBUG;
458 0           return undef;
459             }
460              
461 0 0         warn "Threading enabled (using $count thread" .
    0          
462             ($count > 1 ? 's' : '') . ")\n" if $DEBUG;
463              
464 0           ${$self->{threads}} = [];
  0            
465 0           for( my $i = 0; $i < $count; $i++ ) {
466 0           my %t = (
467             to_q => Thread::Queue->new,
468             from_q => Thread::Queue->new,
469             );
470            
471 0           $t{thread} = threads->create(
472             \&_thread,
473             $t{to_q},
474             $t{from_q}
475             );
476              
477 0 0         if( ! $t{thread} ) {
478 0           croak "Unable to create thread";
479             }
480              
481 0           push @{${$self->{threads}}}, \%t;
  0            
  0            
482             }
483              
484 0           return 1;
485              
486             # _thread( INPUT_QUEUE, OUTPUT_QUEUE )
487              
488             sub _thread {
489 0     0     my $from_q = shift;
490 0           my $to_q = shift;
491 0           my $id;
492              
493 0 0         warn "Spawned new thread\n" if $DEBUG;
494              
495 0           while( $id = $from_q->dequeue ) {
496 0           my $req = $from_q->dequeue;
497 0           my $resp;
498 8     8   14194 my %r : shared;
  8         14629  
  8         41951  
  0            
499              
500 0 0         warn "Processing job $id" if $DEBUG > 1;
501              
502 0           eval { $resp = process_request_raw( @$req ) };
  0            
503 0 0         if( $@ ) {
504 0           $r{exception} = $@;
505             } else {
506 0           $r{cookie} = extract_cookie( $resp );
507 0           $r{text} = post_process( $resp );
508             }
509            
510 0           $to_q->enqueue( $id, \%r );
511             }
512             }
513             }
514              
515             sub join_threads {
516 0     0 1   my $self = shift;
517              
518 0           foreach( @{${$self->{threads}}} ) {
  0            
  0            
519 0           $_->{to_q}->enqueue( 0 );
520 0           $_->{thread}->join;
521             }
522              
523             # Finish the jobs
524              
525 0           my @r; my @i;
526 0 0         while( @i = $self->finish ) { push @r, \@i if $i[0] ne "-1" }
  0            
527              
528             # Dismantle the threading
529              
530 0           ${$self->{threads}} = undef;
  0            
531 0           ${$self->{next_job_id}} = undef;
  0            
532 0           ${$self->{job_to_thread}} = undef;
  0            
533 0           ${$self->{post_commands}} = undef;
  0            
534 0           ${$self->{final_commands}} = undef;
  0            
535 0           ${$self->{finished}} = undef;
  0            
536              
537 0           return @r;
538             }
539              
540             sub detach_threads {
541 0     0 1   my $self = shift;
542            
543 0           foreach( @{${$self->{threads}}} ) {
  0            
  0            
544 0           $_->{to_q}->enqueue( 0 );
545 0           $_->{thread}->detach;
546             }
547              
548             # Finish all jobs that are ready to be finished
549              
550 0           my @r; my @i;
551 0 0         while( @i = $self->finish ) { push @r, \@i if $i[0] ne "-1" }
  0            
552              
553             # Dismantle the threading
554              
555 0           ${$self->{threads}} = undef;
  0            
556 0           ${$self->{next_job_id}} = undef;
  0            
557 0           ${$self->{job_to_thread}} = undef;
  0            
558 0           ${$self->{post_commands}} = undef;
  0            
559 0           ${$self->{final_commands}} = undef;
  0            
560 0           ${$self->{finished}} = undef;
  0            
561            
562 0           return @r;
563             }
564              
565             sub thread_then {
566 0     0 1   my $self = shift;
567 0           my $cmd = shift;
568 0           my $post = shift;
569 0           my $final = shift;
570              
571 0 0         warn "E2::Interface::thread_then\n" if $DEBUG > 1;
572            
573             # warn 'Dump of $cmd:' . Dumper( $cmd ) if $DEBUG > 2;
574 0 0 0       warn 'Adding post-command' if $post && $DEBUG > 2;
575 0           my @response;
576              
577             # Run command. If not threaded, run its post command and
578             # return
579              
580 0 0         if( ref $cmd ) {
581 0           my $c = shift @$cmd;
582 0           @response = &$c( @$cmd );
583             } else {
584 0           @response = &$cmd( @_ );
585             }
586            
587 0 0 0       if( !$response[0] || $response[0] ne "-1" ) {
588 0           my @r = &$post( @response );
589 0 0         &$final if $final;
590 0 0         return ( @r>1 ? @r : $r[0] );
591             }
592              
593             # If we're here, we called a threaded routine. Add the post
594             # command to its caller's list
595              
596 0 0         warn "Job deferred and assigned id $response[1]" if $DEBUG > 2;
597              
598 0           push @{${$self->{post_commands}}->{$response[1]}}, $post;
  0            
  0            
599 0 0         push @{${$self->{final_commands}}->{$response[1]}}, $final if $final;
  0            
  0            
600              
601 0           return @response;
602             }
603              
604             sub finish {
605 0     0 1   my $self = shift;
606 0           my $job = shift;
607              
608 0           my $response;
609              
610 0 0         warn "E2::Interface::finish\n" if $DEBUG > 1;
611 0 0         warn "Job id = $job" if $DEBUG > 2;
612              
613             # What we're going to do here is get a $job (if we haven't been passed
614             # one), and get a $response hash for that job. Otherwise, return.
615              
616             # If $job is undefined, find the first finished job and return it
617              
618 0 0         if( ! defined $job ) {
  0 0          
619              
620             # Get it off the list of finished jobs, if possible;
621              
622 0           (my $k) = keys %{${$self->{finished}}};
  0            
  0            
623 0 0         if( $k ) {
624 0 0         warn "Job previously finished, returning" if $DEBUG > 2;
625 0           $job = $k;
626 0           $response = delete ${$self->{finished}}->{$k};
  0            
627              
628             # Otherwise, check all the queues for finished jobs
629              
630             } else {
631 0           my $pending = 0; # Count pending jobs, so we know
632             # whether there are any left or not
633            
634 0           for( my $i = 0; $i < @{${$self->{threads}}}; $i++ ) {
  0            
  0            
635 0           my $t = ${$self->{threads}}->[$i];
  0            
636 0           my $pending += $t->{to_q}->pending;
637 0           my $id = $t->{from_q}->dequeue_nb;
638              
639 0 0         if( $id ) { # Got one
640 0           $response = $t->{from_q}->dequeue;
641 0           $job = $id;
642 0           last;
643             }
644             }
645              
646 0 0         if( ! $response ) {
647              
648             # If there are no pending jobs, return a
649             # false value. otherwise, return a
650             # non-specific deferred value
651              
652 0 0         if( ! $pending ) {
653 0           return ();
654             }
655              
656 0           return (-1, -1);
657             }
658             }
659            
660             # Otherwise ($job _is_ defined), so first check to see if
661             # we've already pulled this job off the queue.
662              
663             } elsif( ${$self->{finished}}->{$job} ) {
664 0 0         warn "Job previously finished, returning" if $DEBUG > 2;
665 0           $response = ${$self->{finished}}->{$job};
  0            
666 0           delete ${$self->{finished}}->{$job};
  0            
667              
668             # Otherwise, try to get it off the queue; return a deferred value
669             # if we can't.
670              
671             } else {
672 0           my $thr = ${$self->{job_to_thread}}->{$job};
  0            
673            
674 0 0 0       warn "Unable to find thread for job $job" if $DEBUG && !$thr;
675            
676 0 0         return () if !$thr;
677              
678 0           while( my $id = $thr->{from_q}->dequeue_nb ) {
679              
680             # Get response
681              
682 0           my $r = $thr->{from_q}->dequeue;
683            
684 0 0         warn "Retrieved job $id" if $DEBUG > 2;
685              
686 0           delete ${$self->{job_to_thread}}->{$id};
  0            
687              
688 0 0         if( $id == $job ) { # The right job?
689 0           $response = $r;
690 0           last;
691             } else {
692             # Store for later
693 0           ${$self->{finished}}->{$id} = $r;
  0            
694             }
695             }
696            
697             # Now, if the job is complete, $response will contain
698             # a value. If it doesn't, return -1 and set job_id
699             # (tell the caller that the command is still deferred).
700              
701 0 0         if( ! $response ) {
702 0 0         warn "Deferring job $job" if $DEBUG > 2;
703 0           return (-1, $job);
704             }
705             }
706              
707             # At this point, we have a valid $job and $response. Do
708             # post-processing, exception-handling, etc., and return.
709              
710             # If we've received an exception, now is the time to
711             # throw it.
712              
713 0 0         if( $response->{exception} ) {
714            
715             # Execute any final commands and clear all commands
716            
717 0           foreach( @{${$self->{final_commands}}->{$job}} ) { &$_ }
  0            
  0            
  0            
718 0           delete ${$self->{post_commands}}->{$job};
  0            
719 0           delete ${$self->{final_commands}}->{$job};
  0            
720            
721             # throw
722              
723 0           die $response->{exception};
724             }
725              
726             # Now, finish the command and return
727              
728 0 0         $self->cookie( $response->{cookie} ) if $response->{cookie};
729              
730             # Save document
731              
732 0           $self->{last_document} = $response->{text};
733              
734             # Execute any post code, passing the return values of one
735             # as the parameters of the next
736            
737 0           my @param = ( $response->{text} );
738 0           my @ret = ( $response->{text} );
739            
740 0 0         warn "Executing " . scalar @{${$self->{post_commands}}->{$job}} .
  0            
  0            
741             "post-commands" if $DEBUG > 2;
742              
743 0           eval {
744 0           while( my $c = shift @{${$self->{post_commands}}->{$job}} ) {
  0            
  0            
745 0           @ret = &$c( @param );
746 0           @param = @ret;
747             }
748             };
749 0           my $exc = $@;
750            
751             # Execute any 'final' commands. These have no return values.
752              
753 0           foreach( @{${$self->{final_commands}}->{$job}} ) { &$_ }
  0            
  0            
  0            
754 0           delete ${$self->{post_commands}}->{$job};
  0            
755 0           delete ${$self->{final_commands}}->{$job};
  0            
756              
757             # If post-processing threw any exceptions, re-throw them
758              
759 0 0         die $exc if $exc;
760            
761 0           return ( $job, @ret );
762             }
763              
764             sub start_job {
765 0     0 0   my $self = shift;
766              
767 0 0         warn "E2::Interface::start_job\n" if $DEBUG > 1;
768            
769             # Find the first open thread, or the one with the
770             # least jobs pending.
771              
772 0           my $min = 9999;
773 0           my $thr = ${$self->{threads}}->[0];
  0            
774              
775 0           foreach( @{${$self->{threads}}} ) {
  0            
  0            
776 0 0         if( !$_->{to_q}->pending ) {
    0          
777 0           $thr = $_;
778 0           last;
779             } elsif( $_->{to_q}->pending < $min ) {
780 0           $min = $_->{to_q}->pending;
781 0           $thr = $_;
782             }
783             }
784              
785             # Send the message
786              
787 0           my $job = ${$self->{next_job_id}}++;
  0            
788 0           my @job : shared = @_;
789              
790 0 0         warn "Handing $job off to $thr" if $DEBUG > 2;
791              
792 0           $thr->{to_q}->enqueue( $job, \@job );
793              
794 0           ${$self->{job_to_thread}}->{$job} = $thr;
  0            
795              
796 0           return (-1, $job);
797             }
798              
799             ################################################################################
800             # Private, non-method subroutines
801             ################################################################################
802              
803             # Usage: my $cookie = extract_cookie( RESPONSE )
804             #
805             # Extracts a cookie from an LWP::UserAgent object.
806              
807             sub extract_cookie {
808 0     0 0   require HTTP::Cookies;
809              
810 0           my $response = shift;
811 0           my $c = HTTP::Cookies->new;
812              
813 0 0         warn "E2::Interface::extract_cookie\n" if $DEBUG > 1;
814            
815 0           $c->extract_cookies( $response );
816              
817             # It seems that the cookie value may or may not be surrounded by
818             # quotation marks, so deal with either eventuality.
819              
820 0           $c->as_string =~ /userpass=(.*?);/;
821 0           my $s = $1;
822 0 0         $s =~ s/^"(.*)"$/$1/ if $s;
823              
824 0 0 0       warn "Cookie found: $s" if $1 && $DEBUG > 2;
825            
826 0           return $s;
827             }
828              
829             # Usage: $string = post_process STRING
830             #
831             # Turns the return value of process_request_raw into a
832             # string. Fixes encoding as well.
833              
834             sub post_process {
835 0 0   0 0   my $resp = shift or croak "Usage: post_process RESPONSE";
836              
837 0           require HTTP::Request;
838              
839 0 0         warn "E2::Interface::post_process\n" if $DEBUG > 1;
840            
841 0           my $s = $resp->as_string;
842              
843             # Strip HTTP headers
844              
845 0           $s =~ s/.*?\n\n//s;
846              
847             ##### These are workarounds for some of the broken XML that
848             ##### displaytype=xmltrue outputs due to unescaped text.
849              
850             # E2 doesn't properly escape a number of titles in e2links, so
851             # do it here....
852              
853             my $encode = sub {
854 0     0     local $_ = shift;
855 0           s/
856 0           s/>/>/sg;
857 0           s/&/amp;/sg;
858 0           return $_;
859 0           };
860            
861 0           $s =~ s/()(.*?)(<\/e2link>)/$1 . &$encode($2) . $3/esg;
  0            
862              
863             # Escape the various entities that have not been escaped
864              
865             #my %valid = ( amp => 1, lt => 1, gt => 1 );
866             #$s =~ s/\&(\w+?);/$valid{lc($1)} ? "\&$1;" : "\&$1;"/sge;
867              
868             # For &, <, and > which haven't been escaped, escape them (if we
869             # can be sure they're not valid xml.
870              
871             # $s =~ s/\&(?!\w+;)/&/sg;
872             #$s =~ s/<(?![\w\/?][^<]*>)/</sg;
873             #$s =~ s/>/($` =~ m-<[\w\/?][^>]*$-s) ? '>' : '>'/sge;
874              
875             # Demoronize and return
876              
877 0           return &demoronise($s);
878             }
879              
880             sub demoronise {
881 0     0 0   local $_ = shift;
882              
883             # This has been adapted from a public domain script called
884             # demoroniser.pl by John Walker (can be found at
885             # http://www.fourmilab.ch/webtools/demoroniser/ ). That script
886             # replaced MS "smart quotes" and other nonstandard characters
887             # with their plaintext equivalents.
888             #
889             # I've modified them to convert, instead, to their HTML entity
890             # equivalents.
891            
892             # Map strategically incompatible non-ISO characters in the
893             # range 0x82 -- 0x9F into plausible substitutes where
894             # possible.
895              
896 0           if( 0 ) { # Convert to html entities
897            
898             s/\x82/&sbquo;/sg;
899             s/\x83/&fnof;/sg;
900             s/\x84/&bdquo;/sg;
901             s/\x85/&hellip;/sg;
902             s/\x86/&dagger;/sg;
903             s/\x87/&Dagger;/sg;
904             s/\x88/&circ;/sg;
905             s/\x89/&permil;/sg;
906             s/\x8A/&Scaron;/sg;
907             s/\x8B/&lsaquo;/sg;
908             s/\x8C/&OElig;/sg;
909              
910             s/\x91/&lsquo;/sg;
911             s/\x92/&rsquo;/sg;
912             s/\x93/&ldquo;/sg;
913             s/\x94/&rdquo;/sg;
914             s/\x95/&bull;/sg;
915             s/\x96/&ndash;/sg;
916             s/\x97/&mdash;/sg;
917             s/\x98/&tilde;/sg;
918             s/\x99/&trade;/sg;
919             s/\x9A/&scaron;/sg;
920             s/\x9B/&rsaquo;/sg;
921             s/\x9C/&oelig;/sg;
922              
923             } else { # This is not executed; if it were, it would convert
924             # broken MS encoding to plaintext equiv (this is how
925             # demoronise.pl handled it).
926            
927 0           s/\x82/,/g;
928 0           s-\x83-f-g;
929 0           s/\x84/,,/g;
930 0           s/\x85/.../g;
931              
932 0           s/\x88/^/g;
933 0           s-\x89- °/°°-g;
934              
935 0           s/\x8B/
936 0           s/\x8C/Oe/g;
937              
938 0           s/\x91/`/g;
939 0           s/\x92/'/g;
940 0           s/\x93/"/g;
941 0           s/\x94/"/g;
942 0           s/\x95/*/g;
943 0           s/\x96/-/g;
944 0           s/\x97/--/g;
945 0           s-\x98-~-g;
946 0           s-\x99-TM-g;
947              
948 0           s/\x9B/>/g;
949 0           s/\x9C/oe/g;
950             }
951              
952             # Supply missing semicolon at end of numeric entity if
953             # Billy's bozos left it out.
954              
955 0           s/(&#[0-2]\d\d)\s/$1; /g;
956              
957             # Fix dimbulb obscure numeric rendering of < > &
958              
959 0           s/&/&/g;
960 0           s/</</g;
961 0           s/>/>/g;
962              
963 0           return $_;
964             }
965              
966             sub old_demoronise {
967 0     0 0   my $s = shift;
968            
969             # This has been adapted from a public domain script called
970             # demoronizer.pl by John Walker (can be found at
971             # http://www.fourmilab.ch/webtools/demoroniser/ ). That script
972             # replaced MS "smart quotes" and other nonstandard characters
973             # with their plaintext equivalents.
974             #
975             # I've modified them to convert, instead, to their UTF-8
976             # equivalents.
977              
978             # (Christ this is some line noise...)
979              
980 0           $s =~ s/\xC2\x82/\xE2\x80\x98/sg; # ‚
981 0           $s =~ s/\xC2\x83/\xC6\x92/sg; # ƒ
982 0           $s =~ s/\xC2\x84/\xE2\x80\x9E/sg; # „
983 0           $s =~ s/\xC2\x85/\xE2\x80\xA6/sg; # …
984 0           $s =~ s/\xC2\x86/\xE2\x80\xA0/sg; # †
985 0           $s =~ s/\xC2\x87/\xE2\x80\xA1/sg; # ‡
986 0           $s =~ s/\xC2\x88/\xCB\x86/sg; # ˆ
987 0           $s =~ s/\xC2\x89/\xE2\x80\xB0/sg; # ‰
988 0           $s =~ s/\xC2\x8A/\xC5\xA0/sg; # Š
989 0           $s =~ s/\xC2\x8B/\xE2\x80\xB9/sg; # ‹
990 0           $s =~ s/\xC2\x8C/\xC5\x92/sg; # Œ
991 0           $s =~ s/\xC2\x91/\xE2\x80\x98/sg; # ‘
992 0           $s =~ s/\xC2\x92/\xE2\x80\x99/sg; # ’
993 0           $s =~ s/\xC2\x93/\xE2\x80\x9C/sg; # “
994 0           $s =~ s/\xC2\x94/\xE2\x80\x9D/sg; # ”
995 0           $s =~ s/\xC2\x95/\xE2\x80\xA2/sg; # •
996 0           $s =~ s/\xC2\x96/\xE2\x80\x93/sg; # –
997 0           $s =~ s/\xC2\x97/\xE2\x80\x94/sg; # —
998 0           $s =~ s/\xC2\x98/\xDC\xB2/sg; # ˜
999 0           $s =~ s/\xC2\x99/\xE2\x84\xA2/sg; # ™
1000 0           $s =~ s/\xC2\x9A/\xC5\xA1/sg; # š
1001 0           $s =~ s/\xC2\x9B/\xE2\x80\xBA/sg; # ›
1002 0           $s =~ s/\xC2\x9C/\xC5\x93/sg; # œ
1003            
1004 0           return $s;
1005             }
1006              
1007             # Usage: process_request_raw METHOD, URL, COOKIE, AGENTSTR [, ATTR_PAIRS ... ]
1008             # METHOD is one of 'GET', 'POST', 'HEAD', etc.
1009             # URL is the base url of the request (the part before the '?')
1010             # COOKIE is an attribute=value pair to be used as a cookie
1011             # AGENTSTR is the agent string to be used for the request
1012             # ATTR_PAIRS is a set of list of attribute=value pairs to be
1013             # used to fetch the url.
1014             # Returns: a LWP::UserAgent response object
1015              
1016             sub process_request_raw {
1017 0 0   0 0   if( @_ < 3 ) {
1018 0           croak "Usage: process_request_raw" .
1019             "METHOD, URL, COOKIE, AGENTSTR [, ATTR_PAIRS ]";
1020             }
1021            
1022 0           require LWP::UserAgent;
1023 0           require HTTP::Request::Common;
1024 0           import HTTP::Request::Common 'POST';
1025 0           require HTTP::Cookies;
1026              
1027 0           my $req = shift;
1028 0           my $url = shift;
1029 0           my $cookie = shift;
1030 0           my $agentstr = shift;
1031 0           my %pairs = @_;
1032              
1033 0 0         warn "E2::Interface::process_request_raw\n" if $DEBUG > 1;
1034              
1035             # Put together an agentstring and cookie, and create an
1036             # LWP::UserAgent object to hold them
1037              
1038 0           my $str = client_name . '/' . version . " ($OS_STRING)";
1039 0 0         $str = "$agentstr $str" if $agentstr;
1040            
1041             # warn "\$req = $req\n\$url = $url\n\$cookie = $cookie\n" .
1042             # "\$agentstr = $agentstr\nAttribute pairs:" . Dumper( \%pairs )
1043             # if $DEBUG > 2;
1044              
1045 0           my $agent = LWP::UserAgent->new(
1046             agent => $str,
1047             cookie_jar => HTTP::Cookies->new
1048             );
1049            
1050 0 0         if( $cookie ) {
1051 0           $url =~ m-//(.*?)/-; # extract domain
1052            
1053 0           $agent->cookie_jar->set_cookie(
1054             0,
1055             'userpass',
1056             $cookie,
1057             '/',
1058             $1,
1059             undef,
1060             1,
1061             0,
1062             9999999
1063             );
1064             }
1065              
1066              
1067             # Execute the request
1068              
1069 0           my $request;
1070              
1071 0 0         if( $req eq "POST" ) {
1072              
1073 0           $request = POST( $url => [ %pairs ] );
1074              
1075             } else {
1076            
1077 0           my $s = "$url?";
1078 0           my $prepend = "";
1079              
1080 0           foreach( keys %pairs ) {
1081 0           $s .= $prepend . uri_escape( $_ ) . "=" .
1082             uri_escape( $pairs{$_} );
1083 0 0         if( !$prepend ) { $prepend = '&'; }
  0            
1084             }
1085            
1086 0           $request = HTTP::Request->new( $req => $s );
1087             }
1088              
1089 0           my $response = $agent->simple_request( $request );
1090 0 0         if( !$response->is_success ) {
1091 0           croak "Unable to process request";
1092             }
1093 0           return $response;
1094             }
1095              
1096             1;
1097             __END__