Icon for Dimension.orgIcon for TalentIcon for ToolsIcon for Doorways.org
Icon for ToolsIcon for Perl Tools

MMAgic Demo: MMAgic/Data.pm Source File

  • Main Page
  • Related Pages
  • Namespaces
  • Classes
  • Files
  • File List
  • File Members

MMAgic/Data.pm

Go to the documentation of this file.
00001 package MMAgic::Data;
00002 
00003 =head1 NAME
00004 
00005 MMAgic::Data - Data object stored as file in Perl data structure.
00006 
00007 =head1 SYNOPSIS
00008 
00009     my  $data;
00010     tie $data, 'MMAgic::Data', '/tmp/some.data';
00011     
00012     print $data->{field}->[0], "\n";
00013     if ( $somethingHasChanged && tied($var)->lock)
00014         {
00015         my  $temp = $data;  # cause re-read
00016         # ...statements...  # make changes
00017         $data->flush;       # push changes out
00018         }
00019 
00020 =head1 ABSTRACT
00021 
00022 Tie variable to contents of data file expressed as Perl data structure.
00023 Fetch and store entire contents.
00024 Use non-tie methods lock() and flush() to update parts of the data structure.
00025 
00026 =head1 DESCRIPTION
00027 
00028 =head1 CAVEATS
00029 
00030 =head2 C<ARRAY>
00031 
00032 An attempt was made to add an C<ARRAY> tie but it doesn't work very well.
00033 Maybe someday...
00034 
00035 =head2 Opcodes
00036 
00037 The C<Safe> module uses opcodes that have changed over time.
00038 Current code is working for Perl 5.8.8.
00039 It will work as far back as 5.006 but may require different opcodes.
00040 The C<allw> flag in the constructor or C<$ENV{MMAgic_Data_allw}> can
00041 be used to override the opcodes.
00042 
00043 When opcode issues arise, it is often difficult to figure out which
00044 opcodes to add.
00045 One way is to look up the C<opcode.h> file in the Perl source and
00046 cross-reference C<PL_op_desc[]> (which is what shows up in error
00047 messages at run-time) to C<PL_op_name[]> (which is what is required
00048 for opcode names in the C<allw> list) by array index.
00049 
00050 =head1 METHODS
00051 
00052 =over
00053 
00054 =cut
00055 
00056 require 5.006; # 5.003 WILL NOT work due to Safe and fcntl :seek issues.
00057 
00058 use strict;
00059 use warnings;
00060 
00061 use Fcntl ':seek', ':flock';
00062 use File::Basename;
00063 use Safe;
00064 use Symbol;
00065 use UNIVERSAL   qw(isa);
00066 
00067 use MMAgic::File::Status;
00068 use MMAgic::Log;
00069 use MMAgic::Stack;
00070 use MMAgic::Trace;
00071 
00072 our $poll  = 300;
00073 
00074 # Default allowable opcodes for Safe:
00075 our @allow = qw
00076     (
00077     abs
00078     add i_add
00079     anonhash
00080     anonlist
00081     chr ord
00082     const
00083     divide i_divide
00084     hex int oct
00085     leaveeval
00086     length
00087     lineseq
00088     list
00089     modulo i_modulo
00090     multiply i_multiply
00091     padany
00092     pow
00093     pushmark
00094     refgen
00095     scalar
00096     stub
00097     subtract i_subtract
00098     );
00099 
00100 ###########################################################################
00101 
00102 =item   C<_arg($arg, $init, $dflt)>
00103 
00104 Look for intialization parameter.
00105 
00106 Check in C<$init> value, then environment variable of the form
00107 C<$ENV{"MMAgic_Data_$arg}>, then C<$dflt> value.
00108 
00109 Intended for internal use.
00110 
00111 =cut
00112 
00113 sub _arg
00114     {
00115     my ($arg, $init, $dflt) = @_;
00116     my  $val = isa($init, 'HASH') && $init->{$arg};
00117     
00118     $val = $ENV{"MMAgic_Data_$arg"} unless defined $val;
00119     $val = $dflt                    unless defined $val;
00120     $val
00121     }
00122 
00123 ###########################################################################
00124 ###########################################################################
00125 
00126 =item   C<new($class, $path, %init)>
00127 
00128 Constructor for MMAgic::Data object.
00129 
00130     Flags:      (default)   (description)
00131     ----------------------------------------------------------------------
00132       * allw     (below)    allowable operations during file eval
00133       * barf        1       die() if file doesn't exist
00134       * dpth       15       max depth to store when flushing to file
00135       * dflt                default value for data if no file
00136         goto                label in file to skip past before loading
00137         make                create file if necessary to store data (???)
00138       * poll      300       time (sec) to wait before checking to see if
00139                             file has been changed and requires reloading
00140     ----------------------------------------------------------------------
00141       * Flag is settable via $ENV{MMAgic_Data_$flag}
00142 
00143 =cut
00144 
00145 #=| @param  $path   Pathname of data file.
00146 #=| @param  %init   Flags used in object creation
00147 
00148 sub new
00149     {
00150     my  $class = shift;
00151     my ($name, $path, $suff) = fileparse(shift, '\.[^\.]*');
00152 #!# my  $auto  = Log->method($class, $path, '%', @_)->block;
00153     my  %init  = @_;
00154     my  $self  = bless { }, $class;
00155     
00156     $suff = '.data' unless $suff;
00157     
00158     $self->{allw} = isa($init{allow}, 'ARRAY') ? $init{allow} : \@allow;
00159     $self->{barf} = _arg('barf', \%init,  1);
00160     $self->{dflt} = _arg('dflt', \%init);
00161     $self->{dpth} = _arg('dpth', \%init, 15);
00162     $self->{labl} = $init{'goto'};
00163     $self->{lazy} = $init{'lazy'};
00164     $self->{make} = $init{'make'};
00165     $self->{path} = "$path$name$suff";
00166     $self->{poll} = _arg('poll', \%init, $poll);
00167     $self->{fsts} =
00168         new MMAgic::File::Status($path, poll => $self->{poll});
00169 
00170     return $self
00171         if $self->{lazy};
00172 
00173     $self->fetch && $self
00174     }
00175 
00176 ###########################################################################
00177 
00178 =item   C<DESTROY($self)>
00179 
00180 Destructor for MMAgic::Data object.
00181 
00182 Invokes cleanup() routine.
00183 
00184 =cut
00185 
00186 sub DESTROY
00187     {
00188 #!# my  $auto = Log->method($_[1])->block;
00189 #!# MMAgic::Stack::trace(1);
00190     $_[0]->cleanup;
00191     }
00192 
00193 ###########################################################################
00194 ###########################################################################
00195 
00196 =item   C<cleanup($self)>
00197 
00198 Cleanup open file and locks.
00199 
00200 Intended for internal use.
00201 
00202 =cut
00203 
00204 sub cleanup
00205     {
00206 #!# my  $auto = Log->method($_[1])->block;
00207     my  $self = shift;
00208     
00209     $self->unlock;
00210         
00211     if ($self->{handle})
00212         {
00213         close $self->{handle};
00214         undef $self->{handle};
00215         }
00216     
00217     undef
00218     }
00219 
00220 ###########################################################################
00221 
00222 =item   C<handle($self, $writable)>
00223 
00224 Open file, return file handle.
00225 
00226 Intended for internal use.
00227 
00228 =cut
00229 
00230 sub handle
00231     {
00232 #!# my  $auto = Log->method($_[1])->block;
00233     my  $self = shift;
00234     
00235     return $self->{handle} if $self->{locked};
00236     
00237     my  $writable = shift;
00238     
00239     unless ($self->{handle} && ! $writable)
00240         {
00241 #!#     Log->trace('creating new handle');  #[detail]
00242         
00243         if (defined $self->{handle})
00244             {
00245             # Open for reading, must be closed
00246             #   and re-opened for writing:
00247             close $self->{handle};
00248             undef $self->{handle};
00249             }
00250         
00251         Log->error('No pathname in Data tie'),
00252 #!#     MMAgic::Stack::trace(3),
00253         return undef
00254             unless $self->{path};
00255         
00256 #!#     Log->trace('check for non-existent data file'); #[detail]
00257         
00258         unless ($writable || -e $self->{path})
00259             {
00260             Log->crash("Non-existent data file:\n  ", $self->{path})
00261                 if $self->{barf} && ! $self->{dflt};
00262             
00263             return undef;
00264             }
00265         
00266         $self->{handle} = gensym;
00267         
00268 #       my  $mode = ! -e $self->{path} ?  '>' :
00269 #                   $writable          ? '+<' : '<';
00270 
00271         my  $mode = ($writable        ? '+' : '')
00272                   . (-e $self->{path} ? '<' : '>');
00273         
00274 #!#     Log->trace('opening file ', $self->{path}); #[detail]
00275         
00276         unless (open($self->{handle}, "$mode $self->{path}"))
00277             {
00278             Log->crash("Unable to '$mode' open data file:\n    ",
00279                        $self->{path}, "\n  $!")
00280                 if $self->{barf} && ! $self->{dflt};
00281 
00282             return undef($self->{handle})
00283             }
00284         }
00285     
00286     $self->{handle}
00287     }
00288 
00289 ###########################################################################
00290 
00291 =item   C<_load($self, $handle)>
00292 
00293 Load and return data from file open on handle.
00294 Default version does an eval on the file contents
00295 protected by a Safe object with only basic opcodes allowed.
00296 
00297 Intended for internal use.
00298 
00299 =cut
00300 
00301 sub _load
00302     {
00303     my  $self   = shift;
00304 #!# my  $auto   = Log->method(@_)->block;
00305     my  $handle = shift;
00306     my  $result = undef;
00307     my  @text;
00308     
00309     seek $handle, 0, SEEK_SET;
00310     
00311     if (@text = <$handle>)
00312         {
00313         if ($self->{labl})
00314             {
00315             while (my $text = shift @text)
00316                 {
00317                 chomp $text;
00318 #!#             Log->trace('Skipping:  ', $text),
00319                 last if $text eq $self->{labl};
00320                 }
00321             }
00322     
00323         # Use Safe for evaluation:
00324         my  $safe = new Safe;
00325     
00326         Log->warning("Unable to create Safe object:\n  $!"),
00327         return undef
00328             unless new Safe;
00329         
00330         $safe->permit_only(@{$self->{allw}});
00331         $result = $safe->reval(join '', @text);
00332     
00333         if ($@)
00334             {
00335             undef $result;
00336             Log->crash("Unable to parse data file:\n    ",
00337                        $self->{path}, "\n  $@")
00338                 if $self->{barf}
00339                 && ! $self->{dflt};
00340             }
00341         
00342         elsif (! defined $result)
00343             {
00344             Log->crash("Unable to execute data file:\n    ",
00345                        $self->{path}, "\n  $!")
00346                 if $self->{barf}
00347                 && ! $self->{dflt};
00348             }
00349         
00350         elsif (! $self->{type})
00351             {
00352             # No pre-specified type to check against.
00353             }
00354         
00355         elsif (! isa($result, $self->{type}))
00356             {
00357             undef $result;
00358             Log->crash("Data file didn't return $self->{type}:\n  ",
00359                        ref($result), "\n");
00360             }
00361         }
00362     
00363     elsif ($self->{barf} && ! $self->{dflt})
00364         {
00365         Log->crash("No lines of text in data file:\n  ", $self->{path});
00366 #!#     MMAgic::Stack::trace(5),
00367         }
00368 
00369     $result = $self->{dflt}
00370         unless defined $result;
00371 
00372     $self->{loaded} = time
00373         if defined $result;
00374         
00375     $result
00376     }
00377 
00378 ###########################################################################
00379 ###########################################################################
00380 
00381 =item   C<fetch($self)>
00382 
00383 Fetch data value itself.
00384 Value is a scalar reference.
00385 
00386 =cut
00387 
00388 sub fetch
00389     {
00390     my  $self = shift;
00391 #!# my  $auto = Log->method($self)->block;
00392     
00393     # Then check to see if the file has newer data than what we have:
00394     unless ($self->upToDate)
00395         {
00396 #!#     Log->trace("Reloading data from:\n    ", $self->{path});   #[reload]
00397 #!#     Log->trace(' no data cached')    unless $self->{data};  #[reload]
00398 #!#     Log->trace(' data file changed') unless $self->{loaded} >= $self->{mtime};  #[reload]
00399         
00400         undef $self->{data} if defined $self->{data};
00401         
00402         my  $handle = $self->handle;
00403 
00404 #!#     Log->trace('Handle:  ', $handle);
00405         
00406         Log->warning("Unable to lock data file for reading:\n  ",
00407                      $self->{path}, "\n  $!"),
00408         return $self->cleanup,
00409             unless $self->{locked} || flock($handle, LOCK_SH);
00410         
00411         $self->{data} = $self->_load($handle);
00412     
00413         # After fetching from the file, close it:
00414         unless ($self->{locked})
00415             {
00416             # If we're a FETCH() nested w/in a pending lock()/flush(),
00417             #   don't close open file handle or lose the lock!!!
00418 #!#         Log->trace("closing file handle for\n  ", $self->{path});   #[detail]
00419             close $self->{handle};
00420             undef $self->{handle};
00421             }
00422         }
00423     
00424 #!# Log->trace('Returning data:  ', $self->{data});
00425     
00426     $self->{data}
00427     }
00428 
00429 ###########################################################################
00430 
00431 =item   C<flush($self)>
00432 
00433 Update current value to backing store.
00434 If it fails, the data remains the same.
00435 
00436 =cut
00437 
00438 sub flush
00439     {
00440 #!# my  $auto = Log->method($_[1])->block;
00441     my  $self = shift;
00442     
00443     return undef unless $self->{locked};
00444     
00445     # File is open and locked, OK to make changes,
00446     #   begin by rewinding and truncating (hope this works):
00447     seek     $self->{handle}, 0, SEEK_SET;
00448     truncate $self->{handle}, 0;
00449     
00450     my  $out = new MMAgic::Output(output => $self->{handle});
00451     
00452     $self->cleanup,         # well,
00453     undef($self->{data}),   #   we're fucked anyway
00454     return undef
00455         unless isa($out, 'MMAgic::Output');
00456     
00457 #!# Log->trace('dumping:')->reference($self->{data});   #[detail]
00458     
00459     $out->dumpref($self->{data}, maxDepth => $self->{dpth});
00460     $out->close;
00461     undef $out;
00462         
00463     $self->cleanup;
00464     undef $self->{data};    # force re-read next time
00465     
00466     1
00467     }
00468 
00469 ###########################################################################
00470 
00471 =item   C<loaded($self)>
00472 
00473 Return load time if data currently loaded.
00474 
00475 =cut
00476 
00477 sub loaded
00478     {
00479     my  $self = shift;
00480     
00481     $self->{data} && $self->{loaded}
00482     }
00483 
00484 ###########################################################################
00485 
00486 =item   C<lock($self)>
00487 
00488 Lock for (reading and) writing preparatory to making changes.
00489 Next read from variable will return new data.
00490 Return true if successful, C<undef> on failure.
00491 
00492 Call using:
00493 
00494     tied($data)->lock;
00495 
00496 Note that file is also opened now!!!
00497 
00498 =cut
00499 
00500 sub lock
00501     {
00502 #!# my  $auto = Log->method($_[1])->block;
00503     my  $self = shift;
00504     
00505     return 1     if     $self->{locked};
00506     return undef unless $self->handle(1);
00507     
00508     Log->warning("Unable to lock data file for writing:\n  ",
00509                  $self->{path}, "\n  $!"),
00510     return $self->cleanup,
00511         unless flock($self->{handle}, LOCK_EX);
00512     
00513 #!# Log->trace(' - locked');    #[detail]
00514     undef $self->{data};    # force re-read next time
00515     $self->{locked} = 1
00516     }
00517 
00518 ###########################################################################
00519 
00520 =item   C<path($self)>
00521 
00522 Return the path of the file containing the underlying data.
00523 
00524 =cut
00525 
00526 sub path
00527     {
00528     $_[0]->{path}
00529     }
00530 
00531 ###########################################################################
00532 
00533 =item   C<refresh($self)>
00534 
00535 Cause underlying data to be refreshed.
00536 
00537 =cut
00538 
00539 sub refresh
00540     {
00541 #!# my  $auto = Log->method($_[1])->block;
00542     my  $self = shift;
00543     
00544     return if $self->{locked};
00545 
00546     undef $self->{data} if defined $self->{data};
00547     }
00548 
00549 ###########################################################################
00550 
00551 =item   C<store($self, $data)>
00552 
00553 Store data into backing file.
00554 Data is undefined in case of error.
00555 New data returned.
00556 
00557 =cut
00558 
00559 sub store
00560     {
00561     my  $self = shift;
00562 #!# my  $auto = Log->method($self)->block;
00563     my  $data = shift;
00564     
00565 #!# Log->reference($data);  #[detail]
00566 #!# MMAgic::Stack::trace(3);
00567     
00568     Log->warning("Data object undefined during STORE"),
00569 #!# MMAgic::Stack::trace,
00570     return undef($self->{data})
00571         unless defined $data;
00572     
00573     Log->warning("Data object not a reference"),
00574     # TBD:  but could it be a scalar?
00575     return undef($self->{data})
00576         unless ref $data;
00577     
00578     return undef($self->{data})
00579         unless $self->lock;
00580     
00581     $self->{data} = $data;
00582     
00583     return undef($self->{data})
00584         unless $self->flush;
00585     
00586     $self->{data}
00587     }
00588 
00589 ###########################################################################
00590 
00591 =item   C<unlock($self)>
00592 
00593 Release lock on tied object.
00594 Returns without error if not locked.
00595 
00596 =cut
00597 
00598 sub unlock
00599     {
00600     my  $self = shift;
00601 #!# my  $auto = Log->method(@_)->block;
00602     
00603     return unless $self->{locked};
00604 
00605     flock($self->{handle}, LOCK_UN);
00606     close($self->{handle});
00607     undef $self->{handle};
00608     undef $self->{locked};
00609     
00610     # don't force re-read, this is done in flush(),
00611     #   which makes sense as there changes have been made
00612     }
00613 
00614 ###########################################################################
00615 
00616 =item   C<upToDate($self)>
00617 
00618 Return true if the data is current.
00619 
00620 =cut
00621 
00622 sub upToDate    # $self
00623     {
00624     my  $self = shift;
00625 #!# my  $auto = Log->method(@_)->block;
00626 
00627 #!# Log->trace('Data:  ', $self->{data});
00628 #!# Log->trace('Load:  ', $self->{loaded});
00629 #!# Log->trace('mTim:  ', $self->{fsts}->mtime);
00630 
00631     return undef unless $self->{data};
00632     return undef unless $self->{loaded};
00633     return 1     unless $self->{fsts}->mtime;   # non-sensical?
00634     
00635     $self->{loaded} >= $self->{fsts}->mtime
00636     }
00637 
00638 ###########################################################################
00639 ###########################################################################
00640 
00641 sub TIESCALAR   # $class, $path, %init
00642     {
00643 #!# my  $auto  = Log->method(@_)->block;
00644 
00645     new(@_)
00646     }
00647 
00648 ###########################################################################
00649 ###########################################################################
00650 
00651 sub TIEARRAY    # $class, $path, %init
00652     {
00653 #!# my  $auto  = Log->method(@_)->block;
00654 
00655     new(@_, type => 'ARRAY')
00656     }
00657 
00658 ###########################################################################
00659 
00660 sub CLEAR       # $self, $index
00661     {
00662     Log->warning('No array data for CLEAR'),
00663     return
00664         unless isa($_[0]->fetch, 'ARRAY');
00665     
00666     $_[0]->{data} = [ ]
00667     }
00668 
00669 ###########################################################################
00670 
00671 sub DELETE      # $self, $index
00672     {
00673     Log->warning('No array data for DELETE'),
00674     return
00675         unless isa($_[0]->fetch, 'ARRAY');
00676     
00677     delete $_[0]->[$_[1]]
00678     }
00679 
00680 ###########################################################################
00681 
00682 sub EXISTS      # $self, $index
00683     {
00684     isa($_[0]->fetch, 'ARRAY') && defined $_[0]->[$_[1]]
00685     }
00686 
00687 ###########################################################################
00688 
00689 sub EXTEND      # $self, $count
00690     {
00691     STORESIZE(@_)
00692     }
00693 
00694 ###########################################################################
00695 
00696 sub FETCHSIZE   # $self
00697     {
00698     scalar @{$_[0]->fetch}
00699     }
00700 
00701 ###########################################################################
00702 
00703 sub POP         # $self
00704     {
00705     Log->warning('No array data for POP'),
00706     return
00707         unless isa($_[0]->fetch, 'ARRAY');
00708     
00709     pop @{$_[0]->{data}}
00710     }
00711 
00712 ###########################################################################
00713 
00714 sub PUSH        # $self, $value
00715     {
00716     Log->warning('No array data for PUSH'),
00717     return
00718         unless isa($_[0]->fetch, 'ARRAY');
00719     
00720     push @{$_[0]->{data}}, $_[1]
00721     }
00722 
00723 ###########################################################################
00724 
00725 sub SHIFT       # $self
00726     {
00727     Log->warning('No array data for SHIFT'),
00728     return
00729         unless isa($_[0]->fetch, 'ARRAY');
00730     
00731     shift @{$_[0]->{data}}
00732     }
00733 
00734 ###########################################################################
00735 
00736 sub SPLICE      # $self, $offset, $length, @list
00737     {
00738     my  $self = shift;
00739     
00740     Log->warning('No array data for SPLICE'),
00741     return
00742         unless isa($self->fetch, 'ARRAY');
00743     
00744     splice @{$self->{data}}, @_
00745     }
00746 
00747 ###########################################################################
00748 
00749 sub STORESIZE   # $self, $count
00750     {
00751     my ($self, $count) = @_;
00752     
00753     Log->warning('No array data for STORESIZE'),
00754     return
00755         unless isa($self->fetch, 'ARRAY');
00756     
00757     push @{$self->{data}}, undef
00758         while @{$self->{data}} < $count
00759     }
00760 
00761 ###########################################################################
00762 
00763 sub UNSHIFT     # $self, $value
00764     {
00765     Log->warning('No array data for UNSHIFT'),
00766     return
00767         unless isa($_[0]->fetch, 'ARRAY');
00768     
00769     unshift @{$_[0]->{data}}, $_[1]
00770     }
00771 
00772 ###########################################################################
00773 ###########################################################################
00774 
00775 sub FETCH       # $self [, $index ]
00776     {
00777     my  $self = shift;
00778     
00779     return $self->{data}->[$_[0]]
00780         if isa($self->fetch, 'ARRAY')
00781         && defined $_[0];
00782    
00783     # default version:
00784     $self->fetch
00785     }
00786 
00787 ###########################################################################
00788 
00789 sub STORE       # $self, [ $index, ] $value
00790     {
00791     my  $self = shift;
00792 #!# my  $auto = Log->method(@_)->block;
00793     
00794     if (isa($self->fetch, 'ARRAY') && $_[1] && $self->{locked})
00795         {
00796         $self->{data}->[$_[0]] = $_[1]
00797         }
00798     
00799     else
00800         {
00801         $self->store(@_)
00802         }
00803     }
00804 
00805 ###########################################################################
00806 
00807 sub UNTIE       # $self
00808     {
00809 #!# my  $auto = Log->method($_[1])->block;
00810     $_[0]->cleanup;
00811     }
00812 
00813 ###########################################################################
00814 ###########################################################################
00815 
00816 1
00817 
00818 __END__
00819 
00820 =back
00821 
00822 =head1 AUTHOR
00823 
00824 Marc M. Adkins, L<mailto:Perl@Doorways.org>
00825 
00826 =head1 COPYRIGHT AND LICENSE
00827 
00828 Copyright 2001-2008 by Marc M. Adkins
00829 
00830 =cut

Generated on Mon Dec 27 2010 15:15:42 for MMAgic Demo by  doxygen 1.7.1

www.dimension.org logo

(C)opyright 1998 - 2012 Dimension.org

WebMaster