File Coverage

blib/lib/JSAN/Transport.pm
Criterion Covered Total %
statement 89 92 96.7
branch 20 30 66.6
condition 5 8 62.5
subroutine 22 22 100.0
pod 8 9 88.8
total 144 161 89.4


line stmt bran cond sub pod time code
1             package JSAN::Transport;
2              
3             =pod
4              
5             =head1 NAME
6              
7             JSAN::Transport - JavaScript Archive Network Transport and Resources
8              
9             =head1 SYNOPSIS
10              
11             # Create a transport
12             my $transport = JSAN::Transport->new(
13             verbose => 1,
14             mirror_remote => 'http://openjsan.org/',
15             mirror_local => '~/.jsan',
16             );
17            
18             # Retrieve a file
19             $transport->file_get( 'index.sqlite' );
20            
21             # Mirror a file
22             $transport->mirror( '/dist/a/ad/adamk/Display.Swap-0.01.tar.gz' );
23              
24             =head1 DESCRIPTION
25              
26             C provides the primary programatic interface for creating
27             and manipulating a JSAN client transport and resource manager.
28              
29             It controls connection to JSAN, retrieval and mirroring of files.
30              
31             =head1 METHODS
32              
33             =cut
34              
35 7     7   183011 use 5.008005;
  7         31  
  7         341  
36 7     7   44 use strict;
  7         14  
  7         230  
37 7     7   48 use Carp ();
  7         28  
  7         132  
38 7     7   38 use Digest::MD5 ();
  7         13  
  7         161  
39 7     7   43 use File::Spec ();
  7         10  
  7         155  
40 7     7   38 use File::Path ();
  7         13  
  7         118  
41 7     7   6739 use File::HomeDir ();
  7         46657  
  7         182  
42 7     7   57 use File::Basename ();
  7         17  
  7         153  
43 7     7   5239 use URI::ToDisk ();
  7         51067  
  7         173  
44 7     7   1108 use LWP::Simple ();
  7         76563  
  7         17739  
45              
46             our $VERSION = '0.29';
47              
48              
49             # The path to the index
50             my $SQLITE_INDEX = 'index.sqlite';
51              
52              
53              
54              
55              
56             #####################################################################
57             # Constructor
58              
59             =pod
60              
61             =head2 new param => $value, ...
62              
63             The C method initializes the JSAN client adapter. It takes a set of
64             parameters and initializes the C class.
65              
66             =over 4
67              
68             =item mirror_remote
69              
70             The C param specifies the JSAN mirror to be retrieve packages
71             and other files from. If you do not specify a location, a default value of
72             C will be used.
73              
74             =item mirror_local
75              
76             A JSAN client downloads and caches various files from the repository. This
77             primarily means the JSAN packages themselves, but also includes things like
78             the index and other control files. This effectively creates a partial local
79             mirror of the repository, although it will also include some working files
80             not found on the server.
81              
82             The C param specifies the path for the local mirror. If the
83             path provided does not exist, the constructor will attempt to create it. If
84             creation fails the constructor will throw an exception.
85              
86             =item verbose
87              
88             Mainly used for console interfaces for user feedback, the C flag
89             causes the client to print various information to C as it does
90             various tasks.
91              
92             =back
93              
94             Returns true, or will throw an exception (i.e. die) on error.
95              
96             =cut
97              
98              
99             sub new {
100 6     6 1 29 my $class = shift;
101 6         35 my $params = { @_ };
102              
103             # Create the empty object
104 6         41 my $self = bless {
105             verbose => !! $params->{verbose},
106             mirror => undef,
107             }, $class;
108              
109             # Apply defaults
110 6   50     58 my $uri = $params->{mirror_remote} || 'http://openjsan.org';
111 6   33     35 my $path = $params->{mirror_local}
112             ||
113             File::Spec->catdir(
114             File::HomeDir::home(), '.jsan'
115             );
116              
117             # Strip superfluous trailing slashes
118 6         22 $path =~ s/\/+$//;
119 6         35 $uri =~ s/\/+$//;
120              
121             # To ensure we don't overwrite cache, hash the uri
122 6         51 my $digest = Digest::MD5::md5_hex("$uri");
123 6         190 $path = File::Spec->catdir( $path, $digest );
124              
125             # Create the mirror_local path if needed
126 6 50       2442 -e $path or File::Path::mkpath($path);
127 6 50       166 -d $path or Carp::croak("mirror_local: Path '$path' is not a directory");
128 6 50       98 -w $path or Carp::croak("mirror_local: No write permissions to path '$path'");
129              
130             # Create the location object
131 6 50       81 $self->{mirror} = URI::ToDisk->new( $path => $uri )
132             or Carp::croak("Unexpected error creating URI::ToDisk object");
133              
134 6         68303 return $self;
135             }
136              
137              
138             =pod
139              
140             The C accessor returns the L of the
141             L to local path map.
142              
143             =cut
144              
145 35     35 0 902 sub mirror_location { shift->{mirror} }
146              
147             =pod
148              
149             The C accessor returns the location of the remote mirror
150             configured when the object was created.
151              
152             =cut
153              
154 1     1 1 6 sub mirror_remote { shift->{mirror}->uri }
155              
156             =pod
157              
158             The C accessor returns the location of the local mirror
159             configured when the object was created.
160              
161             =cut
162              
163 1     1 1 445 sub mirror_local { shift->{mirror}->path }
164              
165             =pod
166              
167             The C accessor returns the boolean flag on whether the object
168             is running in verbose mode.
169              
170             =cut
171              
172 1     1 1 503 sub verbose { shift->{verbose} }
173              
174              
175              
176              
177             #####################################################################
178             # JSAN::Transport Methods
179              
180             =pod
181              
182             =head2 file_location path/to/file.txt
183              
184             The C method takes the path of a file within the
185             repository, and returns a L object representing
186             it's location on both the server, and on the local filesystem.
187              
188             Paths should B be provided in unix/web format, not the
189             local filesystem's format.
190              
191             Returns a L or throws an exception if passed a
192             bad path.
193              
194             =cut
195              
196             sub file_location {
197 34     34 1 406362 my $self = shift;
198 34         99 my $path = $self->_path(shift);
199              
200             # Strip any leading slash
201 34         80 $path =~ s/^\///;
202              
203             # Split into parts and find the location for it.
204 34         194 my @parts = split /\//, $path;
205 34         136 my $mirror = $self->mirror_location;
206 34         221 $mirror->catfile( @parts );
207             }
208              
209             =pod
210              
211             =head2 file_get path/to/file.txt
212              
213             The C method takes the path of a file within the
214             repository, and fetches it from the remote repository, storing
215             it at the appropriate local path.
216              
217             As all C operations, paths should B be provided
218             in unix/web format, not the local filesystem's format.
219              
220             Returns the L for the file if retrieved successfully,
221             false (C<''>) if the file did not exist in the repository, or C
222             on error.
223              
224             =cut
225              
226             sub file_get {
227 3     3 1 15581 my $self = shift;
228 3         17 my $location = $self->file_location(shift);
229              
230             # Check local dir exists
231 3         295 my $dir = File::Basename::dirname($location->path);
232 3 50       363 -d $dir or File::Path::mkpath($dir);
233              
234             # Fetch the file from the server
235 3         14 my $rc = LWP::Simple::getstore( $location->uri, $location->path );
236 3 100       1500425 if ( LWP::Simple::is_success($rc) ) {
    50          
237 1         12 return $location;
238             } elsif ( $rc == LWP::Simple::RC_NOT_FOUND ) {
239 2         60 return '';
240             } else {
241 0         0 Carp::croak("$rc error retriving file " . $location->uri);
242             }
243             }
244              
245             =pod
246              
247             =head2 file_mirror path/to/file.txt
248              
249             The C method takes the path of a file within the
250             repository, and mirrors it from the remote repository, storing
251             it at the appropriate local path.
252              
253             Using this method if preferable for items like indexs for which
254             want to ensure you have the current version, but do not want to
255             freshly download each time.
256              
257             As all C operations, paths should B be provided
258             in unix/web format, not the local filesystem's format.
259              
260             Returns the L for the file if mirrored successfully,
261             false (C<''>) if the file did not exist in the repository, or
262             C on error.
263              
264             =cut
265              
266             sub file_mirror {
267 26     26 1 68 my $self = shift;
268 26         113 my $path = $self->_path(shift);
269 26         112 my $location = $self->file_location($path);
270              
271             # If any only if a path is "stable" and the file already exists,
272             # it is guarenteed not to change, and we don't have to do the
273             # mirroring operation.
274 26 100 100     3308 if ( $self->_path_stable($path) and -f $location->path ) {
275 12         500 return $location;
276             }
277              
278             # Check local dir exists
279 14         335 my $dir = File::Basename::dirname($location->path);
280 14 100       4147 -d $dir or File::Path::mkpath($dir);
281              
282             # Fetch the file from the server
283 14         95 my $rc = LWP::Simple::mirror( $location->uri, $location->path );
284 14 100       8866085 if ( LWP::Simple::is_success($rc) ) {
    50          
    0          
285 13         161 return $location;
286             } elsif ( $rc == LWP::Simple::RC_NOT_MODIFIED ) {
287 1         11 return $location;
288             } elsif ( $rc == LWP::Simple::RC_NOT_FOUND ) {
289 0         0 return '';
290             } else {
291 0         0 Carp::croak("HTTP error $rc while syncing file " . $location->uri);
292             }
293             }
294              
295             =pod
296              
297             =head2 index_file
298              
299             The C method checks that the SQLite index is up to date, and
300             returns the path to it on the filesystem.
301              
302             =cut
303              
304             sub index_file {
305 7     7 1 749 shift->_index_synced->path;
306             }
307              
308              
309              
310             #####################################################################
311             # Support Methods
312              
313             # Validate a JSAN file path
314             sub _path {
315 86     86   135 my $self = shift;
316 86 50       298 my $path = shift or Carp::croak("No JSAN file path provided");
317              
318             # Strip any leading slash
319 86         1430 $path =~ s(^\/)();
320              
321 86         240 $path;
322             }
323              
324             # Is a path considered "stable" (does not change over time)
325             sub _path_stable {
326 26     26   55 my $self = shift;
327 26         74 my $path = $self->_path(shift);
328              
329             # Paths under the "dist" path are stable
330 26 100       137 if ( $path =~ m{^dist/} ) {
331 19         211 return 1;
332             }
333              
334 7         42 '';
335             }
336              
337             # Returns the location of the SQLite index, syncronising it if needed
338             sub _index_synced {
339 7     7   21 my $self = shift;
340 7 100       38 if ( $self->{index_synced} ) {
341 1         7 return $self->file_location($SQLITE_INDEX);
342             }
343 6         44 my $location = $self->file_mirror($SQLITE_INDEX);
344 6         27 $self->{index_synced}++;
345 6         59 $location;
346             }
347              
348              
349             1;
350              
351             =pod
352              
353             =head1 TO DO
354              
355             - Add verbose support
356              
357             =head1 SUPPORT
358              
359             Bugs should be reported via the CPAN bug tracker at
360              
361             L
362              
363             For other issues, contact the author.
364              
365             =head1 AUTHOR
366              
367             Adam Kennedy Eadamk@cpan.orgE
368              
369             =head1 COPYRIGHT
370              
371             Copyright 2005 - 2010 Adam Kennedy.
372              
373             This program is free software; you can redistribute
374             it and/or modify it under the same terms as Perl itself.
375              
376             The full text of the license can be found in the
377             LICENSE file included with this module.
378              
379             =cut