File Coverage

blib/lib/Image/Delivery.pm
Criterion Covered Total %
statement 90 90 100.0
branch 29 48 60.4
condition 2 3 66.6
subroutine 24 24 100.0
pod 7 7 100.0
total 152 172 88.3


line stmt bran cond sub pod time code
1             package Image::Delivery;
2              
3             =pod
4              
5             =head1 NAME
6              
7             Image::Delivery - Efficient transformation and delivery of web images
8              
9             =head1 INTRODUCTION
10              
11             Many web applications generate or otherwise deliver graphics as part of their
12             interface. Getting the delivery of these images right is tricky, and
13             developers usually need to make trade-offs in order to get a usable mechanism.
14              
15             Image::Delivery is an extremely sophisticated module for delivering these
16             generated images. It is designed to be powerful, flexible, extensible,
17             scalable, secure, stable and correct, and use a minimum of resources.
18              
19             =head1 DESIGN
20              
21             Because it can take a little bit of work to set up Image::Delivery, we will
22             start with a quick once-over of the design of the API, and the reasons and
23             use cases that drove it.
24              
25             =head2 Preventing Multiple Server Calls
26              
27             =head3 Use Case 1: CVS Monitor
28              
29             The initial idea for Image::Delivery was due to some problems with
30             the design of CVS Monitor (L
31             but extremely resource-hungry MVC CGI application. Many of the CVS Monitor
32             views have a single large graph on them, which involves a second call to the
33             server that starts just before the previous call ends. Generating the graph
34             took minimal extra effort, but the overhead of starting another process and
35             loading another 100meg of data creates a double whammy hit to the server.
36            
37             What would be ideal would be to generate both at once and have the browser
38             get the image without a CGI hit.
39              
40             The solution to this problem, and the primary mechanism that Image::Delivery
41             implements could be called "Static Delivery via Cached Disk", but is best
42             demonstrated with the diagram outlined in General Structure below.
43              
44             =head2 Use Case 2: Thumbnails
45              
46             One problem with thumbnailing is the vast number that need to be generated.
47             When done on demand, if generated by the image request, you will have large
48             numbers of processes working. The normal solution is to pre-generate the
49             thumbnails, potentially polluting image directories.
50              
51             Image::Delivery stores all images in one central cache, so that the original
52             images are unaffected.
53              
54             =head2 General Structure
55              
56             Image Provider
57             |
58             |BLOB + TransformPath
59             |
60             \1/
61             Image::Delivery
62             | \
63             | |
64             | |
65             \2/ |
66             Hard Disk |
67             /5\ | |URI
68             | | |
69             | | |
70             | \6/ |
71             Web Server |
72             /4\ | /
73             | |gzip /
74             \ | /
75             \ \7/ \3/
76             Web Browser
77              
78             =head3 1) Image Data pulled from Object/Provider
79              
80             An Object, or a Provider that accesses the data from outside the API,
81             generates or obtains the image data and various metadata that describes
82             the image data.
83              
84             =head3 2) Image Written to File-System
85              
86             Image::Delivery writes the image to the filesystem with a specific file name
87              
88             =head3 3) URI sent to Browser in HTML
89              
90             Image::Delivery determines the matching URI that points to the location of
91             the written file, and provides it to be used in an C tag in the
92             generated HTML page.
93              
94             =head3 4) Web Browser Requests Image
95              
96             Having received the HTML, the browser requests the image from the web server.
97              
98             =head3 5) Web Server Finds Image File
99              
100             The web server receives the image request and finds the file that was
101             written at step 2)
102              
103             =head3 6) Web Server Retrieves Image File
104              
105             Web server reads the file like any other plain file
106              
107             =head3 7) Web Server Sends File to Browser
108              
109             Web server sends the file off to the browser
110              
111             =head2 Digest::TransformPath
112              
113             Image::Delivery works around source objects. Each source object may want to
114             work with more than one image, and each image may need to come in several
115             different versions. In short, there can be lots of variations of images.
116              
117             To handle this, we utilise (or SHOULD utilise)
118             L to help identify the images,
119             with a 10 digit digest built into the filename.
120              
121             =head2 Might as Well Cache Them
122              
123             Since we went to all that effort to write the file, its relatively easy to
124             add caching. But the most important thing if we are going to cache is to
125             have a good file naming scheme.
126              
127             =head2 Image::Delivery Naming Scheme
128              
129             In order to make this all work, the naming scheme is critical.
130              
131             The basic path format is:
132              
133             $ROOT/Object.id/checksum.type
134              
135             =head3 Object.id
136              
137             When an object is updated, it may have any number of Image fields, which
138             may each have any number of scaled/rotated/morphed/derived images. When a
139             source object is updated, some or all of these need to be cleared.
140              
141             =head3 checksum
142              
143             The checksum calculated from the TransformPath does not describe any of the
144             data, only the data source and modifications to it. This means that it is
145             possible to cheaply test if the image for a particular transform has already
146             been created, without having to access any of the data in the actual images.
147              
148             =head3 type
149              
150             Because we accept image data in a variety of formats, its not possible to
151             know what image type any given image should be. So when testing we simply
152             check the lot until we find one.
153              
154             Generally, rather than test 10-15 types, the Provider will inform us of the
155             types to expect. :)
156              
157             =head2 Operation Profile
158              
159             All of this junk gives the module the following properties
160              
161             - Intrinsicaly supports all major image types
162              
163             - No pre-generation of images, generates everything on-the-fly
164              
165             - Image names are secure and can't be predicted
166              
167             - All images for any page are processed in one process hit
168              
169             - Cache checking is extremely quick
170              
171             - Never touches image source data when not filling the cache
172              
173             - Handles many images. Storage extendable to support thousands to millions
174             of individual images
175              
176             - Multiple hosts can work with the same Image cache
177              
178             - Images can be delivered by a different web server to the application
179              
180             =head1 DESCRIPTION
181              
182             Image::Delivery is very powerful, but setting it up may take a little bit
183             of work.
184              
185             =head2 Setting up the URI <-> path mapping
186              
187             First, you need to become aquainted with L.
188             This is used as the basis for the mapping between the disc and a URI.
189              
190             You should also make sure that whatever process will be running will have
191             write permissions to the appropriate directory.
192              
193             For starters, we would suggest creating the cache directory just under the
194             root of a website, at C<$ROOT/cache>, which will be linked to
195             C.
196              
197             This will let you create your HTML::Location.
198              
199             # Set up the location of the cache
200             my $Location = HTML::Location->new(
201             "$ROOT/cache",
202             "http://yourwebsite.com/cache"
203             );
204              
205             This gives you the absolute minimum Image::Delivery itself needs to get
206             rolling. With a location to manage, you can then start to fire images at it,
207             and it will store them and hand you back a HTML::Location for the actual
208             file.
209              
210             # Create the Image::Delivery object
211             my $Delivery = Image::Delivery->new(
212             Location => $Location,
213             );
214              
215             However, the tricky bit is probably setting up your Provider class. Although
216             the abstract class implements much of the details and defaults for you, you
217             are probably still going to need to do some work to tie the two together.
218              
219             =head1 STATUS
220              
221             While the concept and design are fairly well understood and unlikely to
222             change, there is an unfortunate situation with regards to the Cache::
223             family of modules.
224              
225             Although originally written to live at Cache::Web and to be a little more
226             general, it was felt by the maintainer that Cache::Web would represent the
227             module as being a full member of the Cache:: family, which it is not.
228              
229             However, during the first few releases I hope to at least try to move the
230             API of Image::Delivery as close to Cache:: as possible, possibly under a
231             common Cache::Interface class, to gain some potential benefits from code
232             written on top of it.
233              
234             Until these comments are updated, you should assume that the API may undergo
235             some changes.
236              
237             =cut
238              
239 4     4   160314 use 5.005;
  4         17  
  4         175  
240 4     4   25 use strict;
  4         6  
  4         156  
241 4     4   37 use UNIVERSAL 'isa', 'can';
  4         9  
  4         26  
242 4     4   4365 use File::Spec ();
  4         10  
  4         73  
243 4     4   20 use File::Path ();
  4         9  
  4         91  
244 4     4   20 use File::Basename ();
  4         9  
  4         59  
245 4     4   3684 use File::Remove ();
  4         8382  
  4         83  
246 4     4   4127 use File::Slurp ();
  4         69479  
  4         98  
247 4     4   40 use List::Util ();
  4         7  
  4         62  
248 4     4   5226 use Digest::TransformPath ();
  4         1708  
  4         108  
249 4     4   2281 use Image::Delivery::Provider ();
  4         9  
  4         111  
250              
251             # Add the coercion methods
252 4     4   5183 use Params::Coerce '_Provider' => 'Image::Delivery::Provider';
  4         28466  
  4         28  
253 4     4   742 use Params::Coerce '_TransformPath' => 'Digest::TransformPath';
  4         6  
  4         17  
254              
255 4     4   641 use vars qw{$VERSION @FILETYPES};
  4         8  
  4         238  
256             BEGIN {
257 4     4   9 $VERSION = '0.14';
258 4         2944 @FILETYPES = qw{gif jpg png};
259             }
260              
261             =pod
262              
263             =head1 METHODS
264              
265             =head2 new %params
266              
267             The C constructor creates a new Image::Delivery object. It takes
268             a number of required and optional parameters, provided as a set of
269             key/value pairs.
270              
271             =over 4
272              
273             =item Location
274              
275             The required Location parameter
276              
277             =back
278              
279             =cut
280              
281             sub new {
282 6 50   6 1 415 my $class = ref $_[0] ? ref shift : shift;
283 6         12 my %params = @_;
284              
285             # Check the HTML::Location
286 6 100       38 isa(ref $params{Location}, 'HTML::Location') or return undef;
287 2 100 66     7 -d $params{Location}->path and -w _ or return undef;
288              
289             # Create the object
290 1         32 bless { Location => $params{Location} }, $class;
291             }
292              
293             =pod
294              
295             =head2 Location
296              
297             The C method returns the L
298             that was used when creating the Image::Delivery.
299              
300             =cut
301              
302 31     31 1 540 sub Location { $_[0]->{Location} }
303              
304             =pod
305              
306             =head2 filename $TransformPath | $Provider
307              
308             The C method determines, for a given $TransformPath or $Provider, the
309             file name that the Image should be written to, excluding the file type.
310              
311             This is the method most likely to be overloaded, so enable a different
312             naming scheme.
313              
314             =cut
315              
316             sub filename {
317 17     17 1 593 my $self = shift;
318 17 50       414 my $Path = $self->_TransformPath($_[0]) or return undef;
319              
320             # By default, lets go with digest-first-letter and 10-char digest file
321             # e.g. cd3732afc4
322 17         205 my $digest = $Path->digest(10);
323 17         393 File::Spec->catfile( substr($digest,0,1), $digest );
324             }
325              
326             =pod
327              
328             =head2 exists $TransformPath | $Provider
329              
330             For a given Digest::TransformPath, or a ::Provider which contains one, check
331             to see the a file exists for it in the cache already.
332              
333             Returns the HTML::Location of the image if it exists, false if it does not
334             exist, or C on error.
335              
336             =cut
337              
338             sub exists {
339 12     12 1 9070 my $self = shift;
340 12 50       32 my $filepath = $self->filename($_[0]) or return undef;
341 12         284 my $Provider = $self->_Provider($_[0]); # Optional
342 12 100       192 my @extentions = $Provider ? $Provider->filetypes : @FILETYPES;
343 20     20   206 my $filename = List::Util::first { $self->_exists($_) }
344 12 100       53 map { "$filepath.$_" } @extentions
  36         102  
345             or return '';
346 8         365 $self->Location->catfile( $filename );
347             }
348              
349             =pod
350              
351             =head2 get $TransformPath | $Provider
352              
353             The C methods gets the contents of a cached file from the cache, if it
354             exists. You should generally check that the image C first before
355             trying to get it.
356              
357             Returns a reference to a SCALAR containing the image data if the image
358             exists. Returns C if the image does not exist, or some other error
359             occurs.
360              
361             =cut
362              
363             sub get {
364 3     3 1 3833 my $self = shift;
365 3 50       8 my $Location = $self->exists(shift) or return undef;
366 3 50       265 File::Slurp::read_file( $Location->path, scalar_ref => 1 ) or undef;
367             }
368              
369             =pod
370              
371             =head2 set $Provider
372              
373             The C method stores an image in the cache, shortcutting if the image has
374             already been stored.
375              
376             Returns the HTML::Location of the stored image on success, or C on
377             error.
378              
379             =cut
380              
381             sub set {
382 2     2 1 617 my $self = shift;
383 2 100       51 my $Provider = $self->_Provider($_[0]) or return undef;
384              
385             # Is it already in the cache
386 1         15 my $Location = $self->exists($_[0]);
387 1 50       23 return undef unless defined $Location; # Pass up error
388 1 50       16 return $Location if $Location; # Already exists
389              
390             # Determine where to write the file
391 1 50       3 my $file = $self->filename($_[0]) or return undef;
392 1 50       6 my $ext = $Provider->extension or return undef;
393 1         3 $Location = $self->Location->catfile( "$file.$ext" );
394              
395             # Get the image data
396 1 50       76 my $image = $Provider->image or return undef;
397              
398             # Write the image to disk
399 1 50       4 my $directory = File::Basename::dirname($Location->path) or return undef;
400 1         65 eval { File::Path::mkpath($directory) };
  1         290244  
401 1 50       11 return undef if $@;
402 1 50       17 File::Slurp::write_file( $Location->path, $image ) or return undef;
403              
404 1         408 $Location;
405             }
406              
407             =pod
408              
409             =head2 clear $TransformPath
410              
411             The C method allows you to explicitly delete an image from the cache.
412             This would generally be done for security purposes, as the cache cleaners
413             will generally harvest files directly, rather than going via TransformPaths.
414              
415             Returns true if the image was removed, or did not exist. Returns C
416             on error.
417              
418             =cut
419              
420             sub clear {
421 1     1 1 885 my $self = shift;
422 1 50       29 my $TPath = $self->_TransformPath($_[0]) or return undef;
423              
424             # Does the image exist in the cache?
425 1         12 my $Location = $self->exists($_[0]);
426 1 50       62 return undef unless defined $Location;
427 1 50       5 return 1 unless $Location; # Already gone
428              
429             # Attempt to delete the file
430 1 50       58 return undef unless -f $Location->path;
431 1 50       21 File::Remove::remove( $Location->path ) ? 1 : undef;
432             }
433              
434              
435              
436              
437              
438             #####################################################################
439             # Support Methods
440              
441             # Does an image with a particular filename exist?
442             sub _exists {
443 20     20   24 my $self = shift;
444 20 50       37 my $filename = defined $_[0] ? shift : return undef;
445 20         42 -f File::Spec->catfile($self->Location->path, $filename);
446             }
447              
448             1;
449              
450             =pod
451              
452             =head1 TO DO
453              
454             - Add ability to mask indexes with empty HTML files
455              
456             - Add cache clearing capabilities
457              
458             - Add file locking to prevent race conditions in the cache
459              
460             - Add pluggable cache cleaners
461              
462             =head1 SUPPORT
463              
464             All bugs should be filed via the bug tracker at
465              
466             L
467              
468             For other issues, contact the author
469              
470             =head1 AUTHORS
471              
472             Adam Kennedy Eadamk@cpan.orgE
473              
474             =head1 COPYRIGHT
475              
476             Copyright 2004 - 2007 Adam Kennedy.
477              
478             This program is free software; you can redistribute
479             it and/or modify it under the same terms as Perl itself.
480              
481             The full text of the license can be found in the
482             LICENSE file included with this module.
483              
484             =cut