File Coverage

blib/lib/Acme/State.pm
Criterion Covered Total %
statement 80 88 90.9
branch 24 34 70.5
condition 4 12 33.3
subroutine 15 15 100.0
pod 1 2 50.0
total 124 151 82.1


line stmt bran cond sub pod time code
1             package Acme::State;
2              
3 1     1   22977 use 5.008000;
  1         4  
  1         35  
4 1     1   5 use strict;
  1         3  
  1         33  
5 1     1   5 use warnings;
  1         7  
  1         52  
6              
7             our $VERSION = '0.03';
8              
9 1     1   6 use B;
  1         1  
  1         63  
10 1     1   1074 use Storable;
  1         4221  
  1         79  
11 1     1   2099 use Devel::Caller 'caller_cv';
  1         5540  
  1         87  
12 1     1   1094 use IO::Handle;
  1         8456  
  1         213  
13              
14             my @stop_modules = (
15             '1' .. '9', ':',
16             'SIG', 'stderr', '__ANON__', 'utf8::', 'CORE::', 'DynaLoader::', 'strict::',
17             'stdout', 'attributes::', 'stdin', 'ARGV', 'INC', 'Scalar::', 'ENV',
18             'Regexp::', 'XSLoader::', 'UNIVERSAL::', 'overload::', 'B::', 'Carp::',
19             'Data::', 'PerlIO::', '0', 'BEGIN', 'STDOUT', 'IO::', '_', 'Dumper',
20             'Exporter::', 'bytes::', 'STDERR', 'Internals::', 'STDIN', 'Config::',
21             'warnings::', 'DB::',
22             'APR::', 'Apache2::', 'Apache::', 'autobox::', 'BSD::', 'CGITempFile::', 'Compress::',
23             'Devel::', 'Dos::', 'EPOC::', 'Encode::', 'Fh::', 'File::', 'HTTP::', 'LWP::', 'List::', 'Log::',
24             'MIME::', 'Mac::', 'MacPerl::', 'O::', 'POSIX::', 'Scope::', 'Sys::', 'Term::', 'Thread::', 'Time::', 'VMS::',
25             'fields::', 'blackhole::', 'Autobox::', 'Module::', 'Win32::', 'MultipartBuffer::', 'q::', 'sort::',
26             );
27              
28             sub import {
29              
30 1     1   12 my $save_fn = save_file_name();
31              
32 1 50       25 if(-f $save_fn) {
33 1         2 local $Storable::Eval = 1;
34 1         7 my $save = Storable::retrieve $save_fn;
35             sub {
36 16     16   109 my $package = shift;
37 16         18 my $tree = shift;
38 1     1   9 no strict 'refs';
  1         2  
  1         400  
39 16         83 for my $k (keys %$tree) {
40 21 100       72 if($k =~ m/::$/) {
    50          
41 15         44 caller_cv(0)->($package.$k, $tree->{$k});
42             } elsif(ref($tree->{$k})) {
43 6         9 *{$package.$k} = $tree->{$k};
  6         37  
44             } else {
45 0         0 die $package.$k . " doesn't contain a ref";
46             }
47             }
48 1         169 }->('main::', $save);
49             }
50              
51             }
52              
53             sub save_file_name {
54 2   50 2 0 13 my $zero = $0 || 'untitledprogram';
55 2         11 $zero =~ s{.*/}{};
56 2         1233 return +(getpwuid $<)[7].'/'.$zero.'.store';
57             }
58              
59             sub save_state {
60              
61 1     1 1 2 our $wantcoderefs;
62              
63             my $tree = sub {
64 30     30   585 my $package = shift;
65 30   100     69 my $node = shift() || { };
66 1     1   6 no strict 'refs';
  1         2  
  1         652  
67 30         258 for my $k (keys %$package) {
68 377 100       1369 next if $k =~ m/main::$/;
69 376 100       2058 next if $k =~ m/[^\w:]/;
70 205 100       2387 next if grep $_ eq $k, @stop_modules;
71 152 100       455 if($k =~ m/::$/) {
  96 100       311  
    100          
    100          
72             # recurse into that namespace unless it corresponds to a .pm module that got used at some point
73 56         99 my $modulepath = $package.$k;
74 56         85 for($modulepath) { s{^main::}{}; s{::$}{}; s{::}{/}g; $_ .= '.pm'; }
  56         149  
  56         399  
  56         105  
  56         117  
75 56 100       174 next if exists $INC{$modulepath};
76 29   50     136 $node->{$k} ||= { };
77 29         102 caller_cv(0)->($package.$k, $node->{$k});
78 95         299 } elsif( *{$package.$k}{HASH} ) {
79 1         2 $node->{$k} = *{$package.$k}{HASH};
  1         7  
80 93         378 } elsif( *{$package.$k}{ARRAY} ) {
81 2         3 $node->{$k} = *{$package.$k}{ARRAY};
  2         9  
82             } elsif( *{$package.$k}{CODE} ) {
83 85 50       255 next unless $wantcoderefs;
84             # save coderefs but only if they aren't XS (can't serialize those) and weren't exported from elsewhere.
85 0         0 my $ob = B::svref_2object(*{$package . $k}{CODE});
  0         0  
86 0         0 my $rootop = $ob->ROOT;
87 0 0       0 my $stashname = $$rootop ? $ob->STASH->NAME . '::' : '(none)';
88 0 0 0     0 if($$rootop and ($stashname eq $package or 'main::'.$stashname eq $package or $stashname eq 'main::' )) {
      0        
89             # when we eval something in code in main::, it comes up as being exported from main::. *sigh*
90 0         0 $node->{$k} = *{$package . $k}{CODE};
  0         0  
91             }
92             } else {
93 8 50       9 $node->{$k} = *{$package.$k}{SCALAR} unless ref(*{$package.$k}{SCALAR}) eq 'GLOB';
  8         34  
  8         61  
94             }
95             }
96 30         111 return $node;
97 1         13 }->('main::');
98              
99             # use Data::Dumper; print "debug: ", Data::Dumper::Dumper($tree), "\n";
100              
101 1         22 local $Storable::Deparse = $wantcoderefs;
102              
103 1         5 my $save_fn = save_file_name();
104              
105             # $save_fn =~ s{/-}{/x}g; warn "saving to: ``$save_fn.new''";
106              
107 1 50       11 Storable::nstore $tree, $save_fn.'.new' or die "saving state failed: $!";
108              
109             # warn "okay, Storable::nstore done";
110              
111 1         492 rename $save_fn, $save_fn.'.last'; # it's okay if it fails... file might not exist
112 1 50       52 rename $save_fn.'.new', $save_fn or die "renaming new save file into place as ``$save_fn'' failed: $!";
113              
114 1         24 return 1;
115             }
116              
117             END {
118 1     1   18 STDERR->print("Acme::State: Saving program state!\n\n");
119 1         38 save_state();
120             };
121              
122              
123              
124             =head1 NAME
125              
126             Acme::State - Save application state on exit and restores state on startup
127              
128             =head1 SYNOPSIS
129              
130             use Acme::State;
131             our $t;
132             print "t: $t\n";
133             $t = int rand 100;
134             print "new t: $t\n";
135              
136             ... and then run it again.
137              
138             =head1 DESCRIPTION
139              
140             Crawls the package hierarchy looking for C variables.
141             Stores them all off in a file in the home directory of the user running the script.
142             When the script using this module starts up, this same file is read in and the
143             variables are restored.
144              
145             Serializes scalars, hashes, and arrays declared using C, C, or otherwise
146             not declared using C.
147             Uses L to write the data.
148             The save is placed in the home directory of the user the script is executing as.
149             The file name is the same as the script's name (C<$0>) plus ".save".
150             It also keeps one backup around, named C<$0.save.last>, and it may leave a
151             C<$0.save.new> if interrupted.
152              
153             Web apps written using L get persistant state, so why shouldn't command
154             line apps?
155             Hey, and maybe L apps want to persist some state in case the server implodes.
156             Who knows.
157              
158             C<$Acme::State::wantcoderefs>, if set true, takes things a step further and tells
159             L to also serialize subroutines it finds.
160             Nothing says fun like persisting coderefs from the stash and a 40 of Mickey's.
161              
162             This code reserves the right to C if anything goes horribly wrong.
163              
164             =head2 Acme::State::save_state()
165              
166             Explicitly request a snapshot of state be written to disc.
167             Cs if unable to write the save file or if a sanity check fails.
168              
169             =head2 Todo
170              
171             Optionally also use L to create an execution context that runs peroidically to save snapshots.
172              
173             =head1 HISTORY
174              
175             =over 8
176              
177             =item 0.01
178              
179             Original version; created by h2xs 1.23 with options
180              
181             -A -C -X -b 5.8.0 -c -n Stupid::State
182              
183             =item 0.02
184              
185             PAUSE rejected the first one because it didn't like the permissions h2xs left for the
186             automatically generated META.yml file so it wouldn't index it, but it also wouldn't let me
187             delete it, so this version is actually identical to 0.01.
188              
189             =item 0.03
190              
191             Ooops, actually C<< use IO::Handle >>. Not every program already does that for us.
192              
193             =back
194              
195             =head1 BUGS
196              
197             What could possibily go wrong?
198              
199             =head1 SEE ALSO
200              
201             You *could* use an ORM, and wind up translating all of your data to a relational schema you
202             don't care about or else have it automatically mapped and completely miss the point of
203             using a relational database.
204             You *could* just store your data in the Ether with Memcached.
205             You could C and C manually against a database to store every little tidbit and factoid
206             as they're computed.
207             You could use BerekelyDB, including the build-in legacy C and mangle everything
208             down to a flat associative list.
209             You could use L to write a structure to a file and C that on startup
210             and keep all of your precious application data in one big datastructure and still not be able to
211             persist entire objects.
212             You could use C and keep waiting for the day that someone finally writes C.
213              
214             But what's the fun in that?
215             None of those are one C line and then never another thought.
216             That's like work for something.
217             Work is for suckers.
218             We're Perl programmers.
219             If it's not automatic, it's not worth doing.
220              
221             =head1 AUTHOR
222              
223             Scott Walters, Escott@slowass.netE
224              
225             =head1 COPYRIGHT AND LICENSE
226              
227             Copyright (C) 2009 by Scott Walters
228              
229             This library is free software; you can redistribute it and/or modify
230             it under the same terms as Perl itself, either Perl version 5.8.0 or,
231             at your option, any later version of Perl 5 you may have available.
232              
233             =cut
234              
235             __END__