File Coverage

blib/lib/LCC.pm
Criterion Covered Total %
statement 47 209 22.4
branch 8 108 7.4
condition 0 50 0.0
subroutine 13 30 43.3
pod 15 16 93.7
total 83 413 20.1


line stmt bran cond sub pod time code
1             package LCC;
2              
3             # Make sure we do everything by the book
4             # Set modules to inherit from
5             # Set version information
6              
7 1     1   4634 use strict;
  1         2  
  1         39  
8             @LCC::ISA = qw();
9             $LCC::VERSION = '0.02';
10              
11             # Use the internal modules that we always need
12              
13 1     1   361 use LCC::Documents ();
  1         3  
  1         20  
14 1     1   333 use LCC::Backend ();
  1         2  
  1         19  
15 1     1   325 use LCC::UNS ();
  1         2  
  1         21  
16              
17             # Use the external modules that we always need
18              
19 1     1   391 use IO::File ();
  1         7243  
  1         23  
20 1     1   415 use IO::Socket ();
  1         13853  
  1         548  
21              
22             # Make sure that a true value is returned from -use-
23              
24             1;
25              
26             #-------------------------------------------------------------------------
27              
28             # Following subroutines are for instantiating objects
29              
30             #-------------------------------------------------------------------------
31              
32             # IN: 1 reference to hash with method value pairs
33             # OUT: 1 instantiated LCC object
34              
35             sub new {
36              
37             # Find out what class we need to be blessing
38             # If we're not trying to make a LCC object
39             # Warn the user it shouldn't be done and return
40              
41 1     1 1 100 my $class = shift;
42 1 50       18 if ($class ne 'LCC') {
43 0         0 warn "Can only call 'new' on LCC itself\n";
44 0         0 return;
45             }
46              
47             # Create the object and bless the object
48             # Set any parameters if they are specified
49             # Save version information in the object
50             # Return something that we can work with
51              
52 1         2 my $self = bless {},$class;
53 1 50       5 $self->Set( shift ) if $_[0];
54 1         2 $self->_variable( qw(version 1.0) );
55 1         3 return $self;
56             } #new
57              
58             #-------------------------------------------------------------------------
59              
60             # Create the Backend to be used (only once during lifetime of LCC object)
61             # IN: 1 instantiated LCC object
62             # 2 (optional) type of backend (e.g. 'Storable' or 'DBI')
63             # 3 source specification (filename for Storable, $dbh for DBI)
64             # 4 (optional) ref to hash with parameters
65             # OUT: 1 instantiated LCC::Backend::xxx object
66              
67             # or:
68              
69             # Obtain existing Backend object
70             # IN: 1 instantiated LCC object
71             # OUT: 1 instantiated LCC::Backend::xxx object
72              
73             sub Backend {
74              
75             # Obtain the object
76             # If we already have a Backend object
77             # Add error if we're trying to create a new one
78             # Return the existing Backend object
79              
80 0     0 1 0 my $self = shift;
81 0 0       0 if (exists $self->{'Backend'}) {
82 0 0       0 $self->_add_error( "Can only create Backend once" ) if @_;
83 0         0 return $self->{'Backend'};
84             }
85              
86             # Obtain the type of backend
87             # If it is not a known type of backend
88             # Move the type back into the parameters (it needs to be deduced)
89              
90 0         0 my $type = shift;
91 0 0       0 unless ($type =~ m#^(?:DBI|Storable|textfile)$#) {
92 0         0 unshift( @_,$type );
93              
94             # If we have an object of some kind
95             # If it is a DBI database handle
96             # Set to use DBI
97             # Else (unknown type of object)
98             # Reset type (will cause error later)
99              
100 0 0       0 if (my $ref = ref($type)) {
101 0 0       0 if ($ref eq 'DBI::db') {
102 0         0 $type = 'DBI';
103             } else {
104 0         0 $type = '';
105             }
106              
107             # Else (not an object)
108             # If Storable is available
109             # Assume user wants Storable
110             # Else
111             # Assume user wants plain textfiles
112              
113             } else {
114 0 0       0 if (defined($Storable::VERSION)) {
115 0         0 $type = 'Storable';
116             } else {
117 0         0 $type = 'textfile';
118             }
119             }
120             }
121              
122             # Add error if we don't have a type by now
123             # Return the result of the object creation, saving it in the LCC on the fly
124              
125 0 0       0 $self->_add_error( "Unable to determine type of Backend" ) unless $type;
126 0         0 return $self->{'Backend'} = "LCC::Backend::$type"->_new( $self,@_ );
127             } #Backend
128              
129             #-------------------------------------------------------------------------
130              
131             # Create a new set of documents to be checked
132             # IN: 1 instantiated LCC object
133             # 2 (optional) storage type documents
134             # allowed are: DBI, filesystem, module and queue
135             # 3 (optional) source specification (depending on type of storage)
136             # 4 (optional) ref to hash with parameters
137             # OUT: 1 instantiated LCC::Documents::xxx object
138              
139             sub Documents {
140              
141             # Obtain the object
142             # Obtain the storage type of documents
143             # If it is not a known type of storage
144             # Move the type back into the parameters (it needs to be deduced)
145              
146 0     0 1 0 my $self = shift;
147 0   0     0 my $type = shift || '';
148 0 0       0 unless ($type =~ m#^(?:DBI|filesystem|module|queue)$#) {
149 0         0 unshift( @_,$type );
150              
151             # If we have an object of some kind
152             # If it is a DBI database handle
153             # Set to use DBI
154             # Elseif it is a queue object
155             # Set to use queue
156             # Else (unknown type of object)
157             # Set type to module
158             # Else (not an object)
159             # Assume wants documents stored on filesystem
160              
161 0 0       0 if (my $ref = ref($type)) {
162 0 0       0 if ($ref eq 'DBI::db') {
    0          
163 0         0 $type = 'DBI';
164             } elsif ($ref eq 'threads::shared::queue') {
165 0         0 $type = 'queue';
166             } else {
167 0         0 $type = 'module';
168             }
169             } else {
170 0         0 $type = 'filesystem';
171             }
172             }
173              
174             # Add error if we don't have a type by now
175             # Save the result of the object creation
176             # Return the last object created
177              
178 0 0       0 $self->_add_error( "Unable to determine storage type of Documents" )
179             unless $type;
180 0         0 push( @{$self->{'Documents'}},"LCC::Documents::$type"->_new( $self,@_ ) );
  0         0  
181 0         0 return $self->{'Documents'}->[-1];
182             } #Documents
183              
184             #-------------------------------------------------------------------------
185              
186             # IN: 1 instantiated LCC object
187             # 2 server:port specification
188             # OUT: 1 instantiated LCC::UNS object
189              
190 0   0 0 1 0 sub UNS { $_[0]->{'UNS'} ||= 'LCC::UNS'->_new( @_ ) } #UNS
191              
192             #-------------------------------------------------------------------------
193              
194             # Inheritable methods
195              
196             #-------------------------------------------------------------------------
197              
198             # OUT: 1..N errors accumulated so far and removed from object if in list context
199              
200             sub Errors {
201              
202             # Obtain the object
203             # Create the name of the field
204             # Initialize the list of errors
205              
206 0     0 1 0 my $self = shift;
207 0         0 my $name = ref($self).'::Errors';
208 0         0 my @error;
209              
210             # If there are errors
211             # Obtain them
212             # Delete them from the object if we're returning the content of the errors
213             # Return whatever we found
214              
215 0 0       0 if (exists $self->{$name}) {
216 0         0 @error = @{$self->{$name}};
  0         0  
217 0 0       0 delete( $self->{$name} ) if wantarray;
218             }
219 0         0 return @error;
220             } #Errors
221              
222             #-------------------------------------------------------------------------
223              
224             # IN: 1..N names of methods to apply to object
225             # OUT: 1..N values returned by the methods
226              
227             sub Get {
228              
229             # Obtain the object
230             # Initialize the list of values
231             # Allow for non-strict references
232              
233 0     0 1 0 my $self = shift;
234 0         0 my @value;
235 1     1   7 no strict 'refs';
  1         2  
  1         290  
236              
237             # If were supposed to return something
238             # For all of the methods specified
239             # Execute the method and return its value
240             # Return the list of values
241              
242 0 0       0 if (defined(wantarray)) {
243 0         0 foreach my $method (@_) {
244 0         0 push( @value,scalar($self->$method()) );
245             }
246 0         0 return @value;
247             }
248              
249             # Obtain the namespace of the caller
250             # For all of the methods specified
251             # Call the method and put the result in the caller's namespace
252              
253 0         0 my $namespace = caller().'::';
254 0         0 foreach my $method (@_) {
255 0         0 ${$namespace.$method} = $self->$method();
  0         0  
256             }
257             } #Get
258              
259             #-------------------------------------------------------------------------
260              
261             # IN: 1 new setting for PrintError (default: no change)
262             # OUT: 1 current/old setting for PrintError
263              
264             sub PrintError {
265              
266             # Obtain the object
267             # Return now if just returning
268              
269 0     0 1 0 my $self = shift;
270 0 0       0 return $self->_variable( 'PrintError' ) unless @_;
271              
272             # Obtain the value
273             # If it is 'cluck'
274             # Load the 'Carp' module
275             # Set the reference to the cluck routine if Carp is available
276             # Handle as normal setting from here on
277              
278 0         0 my $value = shift;
279 0 0       0 if ($value eq 'cluck') {
280 0         0 eval( 'use Carp ();' );
281 0 0       0 $SIG{__WARN__} = \&Carp::cluck if defined(&Carp::cluck);
282             }
283 0         0 return $self->_variable( 'PrintError',$value,@_ );
284             } #PrintError
285              
286             #-------------------------------------------------------------------------
287              
288             # IN: 1 new setting for RaiseError (default: no change)
289             # OUT: 1 current/old setting for RaiseError
290              
291             sub RaiseError {
292              
293             # Obtain the object
294             # Return now if just returning
295              
296 1     1 1 2 my $self = shift;
297 1 50       3 return $self->_variable( 'RaiseError' ) unless @_;
298              
299             # Obtain the value
300             # If it is 'confess'
301             # Load the 'Carp' module
302             # Set the reference to the confess routine if Carp is available
303             # Handle as normal setting from here on
304              
305 1         1 my $value = shift;
306 1 50       4 if ($value eq 'confess') {
307 0         0 eval( 'use Carp ();' );
308 0 0       0 $SIG{__DIE__} = \&Carp::confess if defined(&Carp::confess);
309             }
310 1         3 return $self->_variable( 'RaiseError',$value,@_ );
311             } #RaiseError
312              
313             #-------------------------------------------------------------------------
314              
315             # IN: 1 reference to a hash or list with values keyed to method names
316              
317             sub Set {
318              
319             # Obtain the object
320             # Obtain the reference
321             # Obtain the type of reference
322             # Allow for non-strict references
323              
324 1     1 1 2 my $self = shift;
325 1         2 my $ref = shift;
326 1         2 my $type = ref($ref);
327 1     1   6 no strict 'refs';
  1         2  
  1         1327  
328              
329             # If we have a hash reference
330             # For all of the methods specified
331             # Execute the method with the given parameters
332              
333 1 50       4 if ($type eq 'HASH') {
    0          
334 1         1 foreach my $method (keys %{$ref}) {
  1         4  
335             $self->$method( ref($ref->{$method}) eq 'ARRAY' ?
336 1 50       7 @{$ref->{$method}} : $ref->{$method} );
  0         0  
337             }
338              
339             # Elseif we have a list reference
340             # While there are methods to be handled
341             # Obtain the parameters
342             # Execute the method with the given parameters
343              
344             } elsif ($type eq 'ARRAY') {
345 0         0 while (my $method = shift( @{$ref} )) {
  0         0  
346 0         0 my $parameters = shift( @{$ref} );
  0         0  
347             $self->$method( ref($parameters) eq 'ARRAY' ?
348 0 0       0 @{$parameters} : $parameters );
  0         0  
349             }
350              
351             # Else (we don't know what to do with it)
352             # Add error
353              
354             } else {
355 0         0 $self->_add_error( "Cannot handle value of type '$type'" );
356             }
357             } #Set
358              
359             #-------------------------------------------------------------------------
360              
361             # Methods for the LCC object only
362              
363             #-------------------------------------------------------------------------
364              
365             # IN: 1 instantiated LCC object
366             # OUT: 1..N ID's of new/changed documents (# of documents in scalar context)
367              
368             sub check {
369              
370             # Obtain the object
371             # Add error if there is no Backend
372             # Obtain local copy of backend
373             # Add error if unclear what kind of update must be done
374             # Add error if there are no Documents to check
375              
376 0     0 1 0 my $self = shift;
377             $self->_add_error( "Must have a Backend first before checking" )
378 0 0       0 unless exists( $self->{'Backend'} );
379 0         0 my $backend = $self->{'Backend'};
380             $self->_add_error( "Unclear whether 'complete' or 'partial' update" )
381 0 0       0 unless exists( $backend->{'old'} );
382             $self->_add_error( "No Documents to be checked" )
383 0 0 0     0 unless exists( $self->{'Documents'} ) and @{$self->{'Documents'}};
  0         0  
384              
385             # Obtain local copy of list of documents
386             # Obtain local copy of old ID's
387             # Obtain local copy of new ID's
388             # Obtain local copy of URL information
389             # Create a reference to an empty subroutine doing nothing
390              
391 0   0     0 my $documents = $self->{'Documents'} || [];
392 0         0 my $old = $backend->{'old'};
393 0   0     0 my $new = $backend->{'new'} ||= {};
394 0   0     0 my $url = $backend->{'url'} ||= {};
395              
396             # Obtain ordinal of first Documents to check
397             # Obtain ordinal if the Documents after the last one now
398             # Loop for all the documents that we need to do now
399             # Create local copy of Documents object for this iteration
400              
401 0   0     0 my $first = $self->{'_next_documents'} || 0;
402 0         0 my $next = $self->{'_next_documents'} = @{$documents};
  0         0  
403 0         0 for (my $i = $first; $i < $next; $i++) {
404 0         0 my $thistime = $documents->[$i];
405              
406             # Obtain the browse URL code reference
407             # Obtain the conceptual URL code reference
408             # Obtain the fetch URL code reference
409              
410 0   0     0 my $burl = $thistime->browse_url || $thistime->_browse_url;
411 0   0     0 my $curl = $thistime->conceptual_url || $thistime->_conceptual_url;
412 0   0     0 my $furl = $thistime->fetch_url || $thistime->_fetch_url;
413              
414             # While there are document to be fetched
415             # Create the string for the list
416             # Reloop if there was no change
417              
418 0         0 while (my ($id,@list) = $thistime->next_document) {
419 0         0 my $list = join( "\0",@list );
420 0 0       0 next if $list eq $old->{$id};
421              
422             # Add error if we did this one already and reloop
423             # Add this document to the list to be done
424             # Add URL information for this document ID
425              
426             $self->_add_error( "Document with ID '$id' was already added" ), next
427 0 0       0 if exists( $new->{$id} );
428 0         0 $new->{$id} = $list;
429 0         0 $url->{$id} = {
430             burl => $burl->( $id ),
431             curl => $curl->( $id ),
432             furl => $furl->( $id ),
433             };
434             }
435             }
436              
437             # Return indicating how many new documents there are now
438              
439 0         0 return keys %{$backend->{'new'}};
  0         0  
440             } #check
441              
442             #-------------------------------------------------------------------------
443              
444             # IN: 1 instantiated LCC object
445              
446 0     0 1 0 sub complete { shift->_backend_method( 'complete',@_ ) } #complete
447              
448             #-------------------------------------------------------------------------
449              
450             # IN: 1 instantiated LCC object
451             # 2 (optional) flag to force partial document set
452              
453 0     0 1 0 sub partial { shift->_backend_method( 'partial',@_ ) } #partial
454              
455             #-------------------------------------------------------------------------
456              
457             # IN: 1 instantiated LCC object
458              
459 0     0 1 0 sub update { shift->_backend_method( 'update',@_ ) } #update
460              
461             #-------------------------------------------------------------------------
462              
463             # IN: 1 instantiated object
464             # 2 reference to hash with provider credentials (id and password)
465             # 3 handle to write XML to or reference to list of handles to write to
466             # (default: just return the resulting XML)
467             # OUT: 1 resulting XML
468              
469             sub update_notification_xml {
470              
471             # Obtain the object
472             # Obtain the credentials
473             # Obtain the handles to write to
474             # Initialize the XML
475              
476 0     0 1 0 my $self = shift;
477 0         0 my $credentials = shift;
478 0 0       0 my @handle = ref($_[0]) eq 'ARRAY' ? @{(shift)} : shift;
  0         0  
479              
480             # Create a local copy to the backend
481             # Create the type of set we're working with
482              
483 0         0 my $backend = $self->{'Backend'};
484 0 0       0 my $set = keys %{$backend->{'old'}} ? 'partial' : 'complete';
  0         0  
485              
486             # Start the XML
487             # Send it to the handles (if appropriate)
488            
489 0         0 my $xml = <
490            
491            
492            
493            
494            
495             EOD
496 0         0 print $_ $xml foreach @handle;
497              
498             # Create local copy of list of new documents
499             # Create local copy of URL info of new documents
500             # While there are documents to be processed
501             # Obtain the constituent parts
502             # Initialize the line for this document
503              
504 0   0     0 my $new = $backend->{'new'} || {};
505 0   0     0 my $url = $backend->{'url'} || {};
506 0         0 while (my ($id,$value) = each %{$new}) {
  0         0  
507 0         0 my ($mtime,$length,$md5,$mimetype,$subtype) = split( m#\0#,$value );
508 0         0 my $line = "
509              
510             # If there is URL info (there should be, really)
511             # Foreach of the special fields
512             # Reloop if no specific info
513             # Add field to this documents XML
514              
515 0 0 0     0 if (my $urlid = $url->{$id} || '') {
516 0         0 foreach (qw(curl burl furl)) {
517 0 0 0     0 next unless $urlid->{$_} || '';
518 0         0 $line .= qq( $_="$urlid->{$_}");
519             }
520             }
521              
522             # Add the constituent parts if applicable
523              
524 0 0 0     0 $line .= qq( mtime="$mtime") if $mtime || '';
525 0 0 0     0 $line .= qq( len="$length") if $length || '';
526 0 0 0     0 $line .= qq( md5="$md5") if $md5 || '';
527 0 0 0     0 $line .= qq( mimetype="$mimetype") if $mimetype || '';
528 0 0 0     0 $line .= qq( subtype="$subtype") if $subtype || '';
529              
530             # Finish off this line
531             # Print the line to each appropriate handle
532             # Add the line to the XML
533              
534 0         0 $line .= "/>\n";
535 0         0 print $_ $line foreach @handle;
536 0         0 $xml .= $line;
537             }
538              
539             # Create the finish up XML
540             # Send it to the handles (if appropriate)
541             # Return the final XML if appropriate
542              
543 0         0 my $last = <
544            
545            
546             EOD
547 0         0 print $_ $last foreach @handle;
548 0 0       0 return $xml.$last if defined( wantarray );
549             } #update_notification_xml
550              
551             #-------------------------------------------------------------------------
552              
553             # IN: 1 instantiated LCC object
554             # OUT: 1 current setting for version of LCC
555              
556 1     1 0 48 sub version { shift->_variable( 'version' ) } #version
557              
558             #-------------------------------------------------------------------------
559              
560             # Following subroutines are for internal use only
561              
562             #-------------------------------------------------------------------------
563              
564             # IN: 1 instantiated object
565             # 2 name of method to execute on Backend
566             # 3..N parameters to be passed
567              
568             sub _backend_method {
569              
570             # Obtain object
571             # Add error if we don't have a backend yet
572             # Obtain the method name
573             # Perform the complete method on the Backend object
574              
575 0     0   0 my $self = shift;
576             $self->_add_error( "Must have a Backend first" )
577 0 0       0 unless exists( $self->{'Backend'} );
578 0         0 my $method = shift;
579 0         0 $self->{'Backend'}->$method( @_ );
580             } #_backend_method
581              
582             #------------------------------------------------------------------------
583              
584             # IN: 1 class to create object in
585             # 2 LCC object
586             # 3 reference to hash with method/value pairs
587             # OUT: 1 instantiated LCC::xxxxx object
588              
589             sub _new {
590              
591             # Create an empty object and bless it
592             # Inherit anything that needs to be inherited
593             # Set any fields that are specified
594             # Return the object
595              
596 0     0   0 my $self = bless {},shift;
597 0         0 $self->_inherit( shift );
598 0 0       0 $self->Set( shift ) if $_[0];
599 0         0 return $self;
600             } #_new
601              
602             #-------------------------------------------------------------------------
603              
604             # IN: 1 instantiated object
605             # 2 object to inherit from
606             # OUT: 1 object itself
607              
608             sub _inherit {
609              
610             # Obtain the objects to work on
611              
612 0     0   0 my ($self,$parent) = @_;
613              
614             # For names of all of the fields that we need to copy
615             # Copy the value
616              
617 0         0 foreach (qw(
618             PrintError
619             RaiseError
620             )) {
621 0 0 0     0 $self->{$_} ||= $parent->{$_} if exists $parent->{$_};
622             }
623 0         0 return $self;
624             } #_inherit
625              
626             #-------------------------------------------------------------------------
627              
628             # IN: 1 error message to add
629             # OUT: 1 object itself (for handy oneliners)
630              
631             sub _add_error {
632              
633             # Obtain the object
634             # Save whatever was specified as an error
635             # Save the error on the list
636             # Show the warning if we're supposed to
637              
638 0     0   0 my $self = shift;
639 0         0 my $message = shift;
640 0         0 push( @{$self->{ref($self).'::Errors'}},$message );
  0         0  
641 0 0 0     0 warn "$message\n" if $self->{'PrintError'} || '';
642              
643             # If we're to die on errors
644             # If it is a code reference
645             # Execute it, passing the message as a parameter
646             # Else
647             # Eval what we had as a value
648             # Die now if we hadn't died already
649            
650 0 0 0     0 if (my $action = $self->{'RaiseError'} || '') {
651 0 0       0 if (ref($action) eq 'CODE') {
652 0         0 &{$action}( $message );
  0         0  
653             } else {
654 0         0 eval( $action );
655             }
656 0         0 die "$message\n";
657             }
658              
659             # Return the object again
660              
661 0         0 return $self;
662             } #_add_error
663              
664             #-------------------------------------------------------------------------
665              
666             # IN: 1 server:port or port specification
667             # 2..N any other parameters to IO::Socket::INET
668             # OUT: 1 socket (undef if error)
669              
670             sub _socket {
671              
672             # Obtain the object
673             # Obtain the server:port specification
674             # Set the default host if only a port number specified
675              
676 0     0   0 my $self = shift;
677 0         0 my $serverport = shift;
678 0 0       0 $serverport = "localhost:$serverport" if $serverport =~ m#^\d+$#;
679              
680             # Attempt to open a socket there
681             # Set error if failed
682             # Return whatever we got
683              
684 0         0 my $socket = IO::Socket::INET->new( $serverport,@_ );
685 0 0       0 $self->_add_error( "Error connecting to socket: $@" )
686             unless $socket;
687 0         0 return $socket;
688             } #_socket
689              
690             #-------------------------------------------------------------------------
691              
692             # The following methods are for setting and obtaining values
693              
694             #-------------------------------------------------------------------------
695              
696             # IN: 1 name of field in hash
697             # 2 new value (default: no change)
698             # OUT: 1 current/old value
699              
700             sub _variable {
701              
702             # Obtain the parameters
703             # Obtain the current value
704             # Set the new value if there is a new value specified
705             # Return the current/old value
706              
707 3     3   6 my ($self,$name) = @_;
708 3         8 my $value = $self->{$name};
709 3 100       7 $self->{$name} = $_[2] if @_ > 2;
710 3         7 return $value;
711             } #_variable
712              
713             #-------------------------------------------------------------------------
714              
715             # subroutines for standard Perl features
716              
717             #-------------------------------------------------------------------------
718              
719             # Debugging tools
720              
721             #-------------------------------------------------------------------------
722              
723             # IN: 1..N variables to be dumped also, apart from object itself
724             # OUT: 1 Dumper output (if Data::Dumper available)
725              
726             sub Dump {
727              
728             # Obtain the object
729             # Attempt to get the Data::Dumper module if not availabl already
730             # If the module is available
731             # Return the result of the dump if we're expecting something
732             # Output the result the dump as a warning (in void context)
733              
734 0     0 1   my $self = shift;
735 0 0         eval 'use Data::Dumper ();' unless defined( $Data::Dumper::VERSION );
736 0 0         if (defined( $Data::Dumper::VERSION )) {
737 0 0         return Data::Dumper->Dump( [$self,@_] ) if defined( wantarray );
738 0           warn Data::Dumper->Dump( [$self,@_] );
739             }
740             }
741              
742             #-------------------------------------------------------------------------
743              
744             __END__