| line | stmt | bran | cond | sub | pod | time | code | 
| 1 |  |  |  |  |  |  | #  You may distribute under the terms of either the GNU General Public License | 
| 2 |  |  |  |  |  |  | #  or the Artistic License (the same terms as Perl itself) | 
| 3 |  |  |  |  |  |  | # | 
| 4 |  |  |  |  |  |  | #  (C) Paul Evans, 2015 -- leonerd@leonerd.org.uk | 
| 5 |  |  |  |  |  |  |  | 
| 6 |  |  |  |  |  |  | package App::MatrixTool::ServerIdStore; | 
| 7 |  |  |  |  |  |  |  | 
| 8 | 1 |  |  | 1 |  | 692 | use strict; | 
|  | 1 |  |  |  |  | 2 |  | 
|  | 1 |  |  |  |  | 22 |  | 
| 9 | 1 |  |  | 1 |  | 3 | use warnings; | 
|  | 1 |  |  |  |  | 1 |  | 
|  | 1 |  |  |  |  | 30 |  | 
| 10 |  |  |  |  |  |  |  | 
| 11 |  |  |  |  |  |  | our $VERSION = '0.06'; | 
| 12 |  |  |  |  |  |  |  | 
| 13 | 1 |  |  | 1 |  | 4 | use Errno qw( ENOENT ); | 
|  | 1 |  |  |  |  | 0 |  | 
|  | 1 |  |  |  |  | 37 |  | 
| 14 | 1 |  |  | 1 |  | 4 | use File::Basename qw( dirname ); | 
|  | 1 |  |  |  |  | 1 |  | 
|  | 1 |  |  |  |  | 46 |  | 
| 15 | 1 |  |  | 1 |  | 3 | use File::Path qw( make_path ); | 
|  | 1 |  |  |  |  | 1 |  | 
|  | 1 |  |  |  |  | 37 |  | 
| 16 | 1 |  |  | 1 |  | 7 | use MIME::Base64 qw( encode_base64 decode_base64 ); | 
|  | 1 |  |  |  |  | 1 |  | 
|  | 1 |  |  |  |  | 565 |  | 
| 17 |  |  |  |  |  |  |  | 
| 18 |  |  |  |  |  |  | =head1 NAME | 
| 19 |  |  |  |  |  |  |  | 
| 20 |  |  |  |  |  |  | C - storage keyed by server name and an ID | 
| 21 |  |  |  |  |  |  |  | 
| 22 |  |  |  |  |  |  | =head1 DESCRIPTION | 
| 23 |  |  |  |  |  |  |  | 
| 24 |  |  |  |  |  |  | Provides a simple flat-file database that stores data keyed by a remote server | 
| 25 |  |  |  |  |  |  | name and ID field. This is persisted in a human-readable file. | 
| 26 |  |  |  |  |  |  |  | 
| 27 |  |  |  |  |  |  | =cut | 
| 28 |  |  |  |  |  |  |  | 
| 29 |  |  |  |  |  |  | sub new | 
| 30 |  |  |  |  |  |  | { | 
| 31 | 0 |  |  | 0 | 0 |  | my $class = shift; | 
| 32 | 0 |  |  |  |  |  | my %args = @_; | 
| 33 |  |  |  |  |  |  |  | 
| 34 |  |  |  |  |  |  | return bless { | 
| 35 |  |  |  |  |  |  | path => $args{path}, | 
| 36 |  |  |  |  |  |  | data => {}, | 
| 37 | 0 |  | 0 |  |  |  | encode => $args{encode} // "base64", | 
| 38 |  |  |  |  |  |  | }, $class; | 
| 39 |  |  |  |  |  |  | } | 
| 40 |  |  |  |  |  |  |  | 
| 41 |  |  |  |  |  |  | =head1 METHODS | 
| 42 |  |  |  |  |  |  |  | 
| 43 |  |  |  |  |  |  | =cut | 
| 44 |  |  |  |  |  |  |  | 
| 45 |  |  |  |  |  |  | sub _open_file | 
| 46 |  |  |  |  |  |  | { | 
| 47 | 0 |  |  | 0 |  |  | my $self = shift; | 
| 48 | 0 |  |  |  |  |  | my ( $mode ) = @_; | 
| 49 |  |  |  |  |  |  |  | 
| 50 | 0 |  |  |  |  |  | my $path = $self->{path}; | 
| 51 |  |  |  |  |  |  |  | 
| 52 | 0 | 0 | 0 |  |  |  | if( $mode eq ">>" and not -f $path ) { | 
| 53 | 0 |  |  |  |  |  | make_path( dirname( $path ) ); | 
| 54 |  |  |  |  |  |  | } | 
| 55 |  |  |  |  |  |  |  | 
| 56 | 0 | 0 |  |  |  |  | if( open my $fh, $mode, $path ) { | 
| 57 | 0 |  |  |  |  |  | return $fh; | 
| 58 |  |  |  |  |  |  | } | 
| 59 |  |  |  |  |  |  |  | 
| 60 | 0 | 0 | 0 |  |  |  | return undef if $! == ENOENT and $mode eq "<"; | 
| 61 | 0 |  |  |  |  |  | die "Cannot open $path - $!\n"; | 
| 62 |  |  |  |  |  |  | } | 
| 63 |  |  |  |  |  |  |  | 
| 64 |  |  |  |  |  |  | sub _read_file | 
| 65 |  |  |  |  |  |  | { | 
| 66 | 0 |  |  | 0 |  |  | my $self = shift; | 
| 67 | 0 | 0 |  |  |  |  | return if $self->{have_read}; | 
| 68 |  |  |  |  |  |  |  | 
| 69 | 0 | 0 |  |  |  |  | if( my $fh = $self->_open_file( "<" ) ) { | 
| 70 | 0 |  |  |  |  |  | while( <$fh> ) { | 
| 71 | 0 | 0 |  |  |  |  | m/^\s*#/ and next; # ignore comment lines | 
| 72 | 0 |  |  |  |  |  | my ( $server, $id, $key ) = split m/\s+/, $_; | 
| 73 |  |  |  |  |  |  |  | 
| 74 | 0 | 0 |  |  |  |  | defined $key or warn( "Unable to parse line $_" ), next; | 
| 75 |  |  |  |  |  |  |  | 
| 76 | 0 |  |  |  |  |  | $self->{data}{$server}{$id} = $self->_decode( $key ); | 
| 77 |  |  |  |  |  |  | } | 
| 78 |  |  |  |  |  |  | } | 
| 79 |  |  |  |  |  |  |  | 
| 80 | 0 |  |  |  |  |  | $self->{have_read}++; | 
| 81 |  |  |  |  |  |  | } | 
| 82 |  |  |  |  |  |  |  | 
| 83 |  |  |  |  |  |  | sub _encode | 
| 84 |  |  |  |  |  |  | { | 
| 85 | 0 |  |  | 0 |  |  | my $self = shift; | 
| 86 | 0 | 0 |  |  |  |  | return encode_base64( $_[0] ) if $self->{encode} eq "base64"; | 
| 87 | 0 |  |  |  |  |  | return $_[0]; | 
| 88 |  |  |  |  |  |  | } | 
| 89 |  |  |  |  |  |  |  | 
| 90 |  |  |  |  |  |  | sub _decode | 
| 91 |  |  |  |  |  |  | { | 
| 92 | 0 |  |  | 0 |  |  | my $self = shift; | 
| 93 | 0 | 0 |  |  |  |  | return decode_base64( $_[0] ) if $self->{encode} eq "base64"; | 
| 94 | 0 |  |  |  |  |  | return $_[0]; | 
| 95 |  |  |  |  |  |  | } | 
| 96 |  |  |  |  |  |  |  | 
| 97 |  |  |  |  |  |  | =head2 list | 
| 98 |  |  |  |  |  |  |  | 
| 99 |  |  |  |  |  |  | %id_data = $store->list( server => $name ) | 
| 100 |  |  |  |  |  |  |  | 
| 101 |  |  |  |  |  |  | Returns a kvlist associating IDs to byte strings of data stored for the given | 
| 102 |  |  |  |  |  |  | server. | 
| 103 |  |  |  |  |  |  |  | 
| 104 |  |  |  |  |  |  | =cut | 
| 105 |  |  |  |  |  |  |  | 
| 106 |  |  |  |  |  |  | sub list | 
| 107 |  |  |  |  |  |  | { | 
| 108 | 0 |  |  | 0 | 1 |  | my $self = shift; | 
| 109 | 0 |  |  |  |  |  | my %args = @_; | 
| 110 |  |  |  |  |  |  |  | 
| 111 | 0 |  |  |  |  |  | my $server = $args{server}; | 
| 112 | 0 |  |  |  |  |  | $self->_read_file; | 
| 113 |  |  |  |  |  |  |  | 
| 114 | 0 |  |  |  |  |  | my %ret; | 
| 115 | 0 |  |  |  |  |  | foreach my $id ( keys %{ $self->{data}{$server} } ) { | 
|  | 0 |  |  |  |  |  |  | 
| 116 | 0 |  |  |  |  |  | $ret{$id} = $self->{data}{$server}{$id}; | 
| 117 |  |  |  |  |  |  | } | 
| 118 |  |  |  |  |  |  |  | 
| 119 | 0 |  |  |  |  |  | return %ret; | 
| 120 |  |  |  |  |  |  | } | 
| 121 |  |  |  |  |  |  |  | 
| 122 |  |  |  |  |  |  | =head2 get | 
| 123 |  |  |  |  |  |  |  | 
| 124 |  |  |  |  |  |  | $key = $store->get( server => $name, id => $id ) | 
| 125 |  |  |  |  |  |  |  | 
| 126 |  |  |  |  |  |  | Returns a byte string associated with the given server and ID, or C if | 
| 127 |  |  |  |  |  |  | no such is known. | 
| 128 |  |  |  |  |  |  |  | 
| 129 |  |  |  |  |  |  | =cut | 
| 130 |  |  |  |  |  |  |  | 
| 131 |  |  |  |  |  |  | sub get | 
| 132 |  |  |  |  |  |  | { | 
| 133 | 0 |  |  | 0 | 1 |  | my $self = shift; | 
| 134 | 0 |  |  |  |  |  | my %args = @_; | 
| 135 |  |  |  |  |  |  |  | 
| 136 | 0 |  |  |  |  |  | my $server = $args{server}; | 
| 137 | 0 |  |  |  |  |  | my $id     = $args{id}; | 
| 138 | 0 |  |  |  |  |  | $self->_read_file; | 
| 139 |  |  |  |  |  |  |  | 
| 140 | 0 | 0 |  |  |  |  | return unless $self->{data}{$server}; | 
| 141 | 0 |  |  |  |  |  | return $self->{data}{$server}{$id}; | 
| 142 |  |  |  |  |  |  | } | 
| 143 |  |  |  |  |  |  |  | 
| 144 |  |  |  |  |  |  | =head2 put | 
| 145 |  |  |  |  |  |  |  | 
| 146 |  |  |  |  |  |  | $store->put( server => $name, id => $id, data => $bytes ) | 
| 147 |  |  |  |  |  |  |  | 
| 148 |  |  |  |  |  |  | Stores a byte string associated with the server and ID. | 
| 149 |  |  |  |  |  |  |  | 
| 150 |  |  |  |  |  |  | =cut | 
| 151 |  |  |  |  |  |  |  | 
| 152 |  |  |  |  |  |  | sub put | 
| 153 |  |  |  |  |  |  | { | 
| 154 | 0 |  |  | 0 | 1 |  | my $self = shift; | 
| 155 | 0 |  |  |  |  |  | my %args = @_; | 
| 156 |  |  |  |  |  |  |  | 
| 157 | 0 |  |  |  |  |  | my $server = $args{server}; | 
| 158 | 0 |  |  |  |  |  | my $id     = $args{id}; | 
| 159 |  |  |  |  |  |  |  | 
| 160 | 0 | 0 |  |  |  |  | if( exists $self->{data}{$server}{$id} ) { | 
| 161 | 0 | 0 |  |  |  |  | return if $self->{data}{$server}{$id} eq $args{data}; | 
| 162 | 0 |  |  |  |  |  | warn "ServerIdStore is overwriting a key with a different value!\n"; | 
| 163 |  |  |  |  |  |  | } | 
| 164 |  |  |  |  |  |  |  | 
| 165 | 0 |  |  |  |  |  | my $fh = $self->_open_file( ">>" ); | 
| 166 | 0 |  |  |  |  |  | $fh->print( "$server $id " . $self->_encode( $args{data}, "" ) . "\n" ); | 
| 167 |  |  |  |  |  |  |  | 
| 168 | 0 |  |  |  |  |  | $self->{data}{$server}{$id} = $args{data}; | 
| 169 |  |  |  |  |  |  | } | 
| 170 |  |  |  |  |  |  |  | 
| 171 |  |  |  |  |  |  | =head1 AUTHOR | 
| 172 |  |  |  |  |  |  |  | 
| 173 |  |  |  |  |  |  | Paul Evans | 
| 174 |  |  |  |  |  |  |  | 
| 175 |  |  |  |  |  |  | =cut | 
| 176 |  |  |  |  |  |  |  | 
| 177 |  |  |  |  |  |  | 0x55AA; |