File Coverage

blib/lib/YAML/CacheLoader.pm
Criterion Covered Total %
statement 24 57 42.1
branch 0 18 0.0
condition 0 9 0.0
subroutine 8 14 57.1
pod 4 4 100.0
total 36 102 35.2


line stmt bran cond sub pod time code
1 2     2   138298 use strict;
  2         4  
  2         66  
2 2     2   8 use warnings;
  2         2  
  2         76  
3              
4             # ABSTRACT: load YAML from cache or disk, whichever seems better
5             package YAML::CacheLoader;
6             our $VERSION = '0.017';
7              
8 2     2   6 use base qw( Exporter );
  2         10  
  2         152  
9             our @EXPORT_OK = qw( LoadFile DumpFile FlushCache FreshenCache);
10              
11 2     2   14 use constant CACHE_SECONDS => 3607; # Relatively nice prime number just over 1 hour.
  2         4  
  2         92  
12 2     2   10 use constant CACHE_NAMESPACE => 'YAML-CACHELOADER'; # Make clear who dirtied up the memory
  2         2  
  2         84  
13              
14 2     2   854 use Cache::RedisDB 0.07;
  2         108146  
  2         66  
15 2     2   16 use Path::Tiny 0.061;
  2         30  
  2         98  
16 2     2   6 use YAML::XS 0.59;
  2         32  
  2         962  
17              
18             =head1 FUNCTIONS
19              
20             =over
21              
22             =item LoadFile
23              
24             my $structure = LoadFile('/path/to/yml'[, $force_reload]);
25              
26             Loads the structure from '/path/to/yml' into $structure, preferring the cached version if available,
27             otherwise reading the file and caching the result for 593 seconds (about 10 minutes).
28              
29             If $force_reload is set to a true value, the file will be loaded from disk without regard
30             to the current cache status.
31              
32             =cut
33              
34             sub LoadFile {
35 0     0 1   my ($path, $force_reload) = @_;
36              
37 0           my $file_loc = path($path)->canonpath; # realpath would be more accurate, but slower.
38 0           my $structure;
39 0 0         if ($force_reload) {
40 0           $structure = _load_and_cache($file_loc);
41             } else {
42 0           FreshenCache($file_loc);
43 0   0       $structure = Cache::RedisDB->get(CACHE_NAMESPACE, $file_loc) // _load_and_cache($file_loc);
44             }
45              
46 0           return $structure;
47             }
48              
49             sub _load_and_cache {
50 0     0     my $loc = shift;
51              
52 0           my $structure = YAML::XS::LoadFile($loc); # Let this fail in whatever ways it might.
53 0 0         Cache::RedisDB->set(CACHE_NAMESPACE, $loc, $structure, CACHE_SECONDS) if ($structure);
54              
55 0           return $structure;
56             }
57              
58             =item DumpFile
59              
60             DumpFile('/path/to/yml', $structure);
61              
62             Dump the structure from $structure into '/path/to/yml', filling the cache along the way.
63              
64             =cut
65              
66             sub DumpFile {
67 0     0 1   my ($path, $structure) = @_;
68              
69 0           my $file_loc = path($path)->canonpath; # realpath would be more accurate, but slower.
70              
71 0 0         if ($structure) {
72 0           YAML::XS::DumpFile($file_loc, $structure);
73 0           Cache::RedisDB->set(CACHE_NAMESPACE, $file_loc, $structure, CACHE_SECONDS);
74             }
75              
76 0           return $structure;
77             }
78              
79             =item FlushCache
80              
81             FlushCache();
82              
83             Remove all currently cached YAML documents from the cache server.
84              
85             =cut
86              
87             sub FlushCache {
88 0     0 1   my @cached_files = _cached_files_list();
89              
90 0 0         return (@cached_files) ? Cache::RedisDB->del(CACHE_NAMESPACE, @cached_files) : 0;
91             }
92              
93             =item FreshenCache
94              
95             FreshenCache();
96              
97             Freshen currently cached files which may be out of date, either by deleting the cache (for now deleted files) or reloading from the disk (for changed ones)
98              
99             May optionally provide a list of files to check, otherwise all known cached files are checked.
100              
101             Returns a stats hash-ref.
102              
103             =back
104             =cut
105              
106             sub FreshenCache {
107 0     0 1   my (@file_list) = @_;
108              
109 0 0         @file_list = _cached_files_list() unless @file_list; # By default check all currently cached.
110              
111 0           my @to_check = map { path($_) } @file_list;
  0            
112             # A good rough cut is to see if something _might_ have changed in the meantime
113 0           my $cutoff = time - CACHE_SECONDS;
114              
115 0           my $stats = {
116             examined => scalar @to_check,
117             cleared => 0,
118             freshened => 0,
119             };
120              
121 0           foreach my $file (@to_check) {
122 0 0 0       if (!$file->exists) {
    0          
123 0 0         $stats->{cleared}++ if (Cache::RedisDB->del(CACHE_NAMESPACE, $file->canonpath)); # Let's not cache things which don't exist.
124             } elsif ((my $mtime = $file->stat->mtime) > $cutoff
125             && (my $reloaded_ago = CACHE_SECONDS - Cache::RedisDB->ttl(CACHE_NAMESPACE, $file->canonpath)))
126             {
127 0 0 0       $stats->{freshened}++ if (time - $reloaded_ago < $mtime && LoadFile($file, 1));
128             }
129             }
130              
131 0           return $stats;
132             }
133              
134             sub _cached_files_list {
135              
136 0     0     return @{Cache::RedisDB->keys(CACHE_NAMESPACE)};
  0            
137              
138             }
139              
140             1;