root/veekun/trunk/lib/Vee/Controller/Root.pm @ 425

Revision 425, 7.8 KB (checked in by eevee, 20 months ago)

Hurka-hur removed some warnings. (#289)

Line 
1package Vee::Controller::Root;
2
3use strict;
4use warnings;
5use base 'Catalyst::Controller';
6
7use Vee::Authorization;
8use Vee::Utils;
9
10use Data::Dumper;
11use DBIx::Class::QueryLog;
12use File::Path;
13use Time::HiRes;
14use Time::Out qw/timeout/;
15use URI;
16use XML::Feed;
17
18# Sets the actions in this controller to be registered with no prefix
19# so they function identically to actions created in Vee.pm
20__PACKAGE__->config->{namespace} = '';
21
22=head1 NAME
23
24Vee::Controller::Root - Root Controller
25
26=head1 SYNOPSIS
27
28See L<Vee>.
29
30=head1 DESCRIPTION
31
32Root Controller, containing global defaults and begin/end actions.
33
34=head1 METHODS
35
36=cut
37
38=head2 auto
39
40=cut
41
42sub auto : Private {
43    my ($self, $c) = @_;
44    my $s = $c->stash;
45   
46    # for execution time
47    $s->{start_time} = Time::HiRes::time;
48   
49    # track SQL queries
50    my $storage = $c->model('DBIC')->schema->storage;
51    $s->{querylog} = new DBIx::Class::QueryLog;
52    $storage->debugobj($s->{querylog});
53    $storage->debug(1);
54
55    if ($c->user) { $c->user->obj->time_active(time); $c->user->obj->update; }
56   
57    if ($c->flash and %{$c->flash}) {
58        $s->{$_} = $c->flash->{$_} for qw/error_msg info_msg success_msg/;
59    }
60
61    $self->_do_periodic_tasks($c);
62       
63    return 1;
64}
65
66=head2 default
67
68=cut
69
70sub default : Private {
71    my ($self, $c) = @_;
72
73    my @path_parts = @{ $c->req->args };
74
75    if (not -e $c->path_to('static', @path_parts) . '.tt') {
76        $c->stash->{template} = '404.tt';
77        return;
78    }
79   
80    $c->stash->{additional_template_paths} = [ $c->path_to('static') ];
81    $c->stash->{template} = join('/', @path_parts) . '.tt';
82
83    if ($path_parts[0] eq 'dex') {
84        $c->stash( %Vee::Dex::all );
85    }
86
87    # TODO: 404?
88}
89
90=head2 end
91
92End action.  Does various debugging, error-handling, statistics, and caching
93thingies.
94
95=cut
96
97sub end : ActionClass('RenderView') {
98    my ($self, $c) = @_;
99    my $s = $c->stash;
100
101    # capture flagrant errors in prod
102    if (not $c->debug and scalar @{ $c->error }) {
103        eval {
104            $c->model('DBIC::ErrorLog')->create({
105                time    => time,
106                user_id => $c->user ? $c->user->obj->id : 0,
107                ip      => Vee::Utils::inet_aton($c->req->address),
108                path    => $c->req->path,
109                method  => $c->req->method,
110                query   => Dumper($c->req->params),
111                error   => join "\n", @{$c->error},
112            });
113        };
114
115        $c->res->status(500);
116       
117        $s->{insert_error} = $@;
118        $s->{errors}       = $c->error;  # XXX: not sure if I should expose these to the user
119
120        $s->{template} = 'fatal.tt';
121       
122        $c->forward('Vee::View::TT');
123        $c->error(0);
124    }
125
126    # count up SQL queries used
127    if ($c->debug or $c->req->params->{dump_sql}) {
128        my %perl_queries;
129        for my $qt (@{ $s->{querylog}->log }) {
130            if ($qt->isa('DBIx::Class::QueryLog::Query')) {
131                $perl_queries{ $qt->sql }++
132            } else {
133                $perl_queries{ $_->sql }++ for @{ $qt->queries }
134            }
135        }
136
137        # can't generate this list here as it won't count SQL run in the template
138        $s->{generate_query_list} = sub {
139            my ($log) = @_;
140            # group queries together...
141            my %queries;
142            for my $query (@$log) {
143                $queries{ $query->sql } ||= {
144                    count    => 0,
145                    count_pl => 0,
146                    time     => 0,
147                    sql      => $query->sql,
148                };
149
150                $queries{ $query->sql }{count} += $query->count;
151                $queries{ $query->sql }{time} += $query->time_elapsed;
152            }
153            for my $query (keys %perl_queries) {
154                $queries{$query}{count_pl} = $perl_queries{$query};
155            }
156
157            # ...and sort by time taken
158            my @queries = sort { $b->{time} <=> $a->{time} } values %queries;
159            return \@queries;
160        }
161    }
162
163    # if _cache_fh is set, grab the output manually and cache it to disk
164    # NOTE: THIS EXPECTS THAT A WRAPPER IS USED
165    if ($s->{_cache_fh}) {
166        # disable wrapper, then get just the body
167        my $skip_wrapper = $s->{skip_wrapper};
168        $s->{skip_wrapper} = 1;
169        my $body = $c->view->render($c, $s->{template});
170
171        # cache
172        my $cache_file = $s->{_cache_fh};
173        print $cache_file $body;
174        flock $cache_file, 8;
175        close $cache_file;
176
177        $s->{skip_wrapper} = $skip_wrapper;
178        $c->forward('/render_from_cache');
179
180    # this is done here so the above querylog/etc code can run first
181    } elsif ($s->{from_cache}) {
182        $c->forward('/render_from_cache');
183    }
184}
185
186=head2 cache
187
188Uses the template in the stash and a provided scalar as a key to cache this
189page.  If the cache already exists, it will be used and the rest of the calling
190controller will be skipped.
191
192=cut
193
194sub cache : Private {
195    my ($self, $c, $key) = @_;
196    my $s = $c->stash;
197
198    return if $c->debug;
199    return if not $s->{template};
200
201    $key = '' if not defined $key;
202    $key =~ tr/-_a-zA-Z0-9//cd;
203    $key = 'default' if not length $key;
204
205    $s->{_cache_file} = "$s->{template}/$key";
206    my $cache_path = $c->path_to(qw/ tmp cache /) . '/' . $s->{_cache_file};
207
208    if (-e $cache_path) {
209        # if the cache file exists, flag end() to spit it out
210        $s->{from_cache} = 1;
211        $c->detach;
212    } else {
213        # create directory tree; if there be a problem, just don't cache
214        eval { mkpath( $c->path_to(qw/ tmp cache /) . '/' . $s->{template} ) };
215        return if $@;
216
217        # open here and flock to prevent race condition
218        open my $cache_file, '>:encoding(UTF-8)',
219            $c->path_to(qw/ tmp cache /, $s->{_cache_file});
220        flock $cache_file, 2;
221        $s->{_cache_fh} = $cache_file;
222    }
223}
224
225=head2 render_from_cache
226
227Used to...  render a template from the cache.  Mostly used as a hack to avoid
228template compilation, but I'd also like to figure out how to get TT to simply
229not parse the cached file at all.
230
231=cut
232
233sub render_from_cache : Private {
234    my ($self, $c) = @_;
235    my $s = $c->stash;
236
237    open my $cache_file, '<:encoding(UTF-8)',
238        $c->path_to(qw/ tmp cache /, $s->{_cache_file});
239    $c->res->body( $c->view->render($c, $cache_file) );
240    close $cache_file;
241}
242
243=head2 _do_periodic_tasks
244
245Performs various actions if they haven't been done for some amount of time.
246
247Regular method!  Not an action.
248
249=cut
250
251sub _do_periodic_tasks
252{
253    my ($self, $c) = @_;
254
255    if ($c->cache->{_last_performed} and
256        $c->cache->{_last_performed} > time - 15 * 60)
257    {
258        return;
259    }
260
261    # Last subversion commit
262eval {
263    open my $svn_fh, '-|', 'svn', 'log', '--limit' => 1, '--incremental', $c->site_opts->{svn_url};
264    my (undef, $details, undef, @log) = <$svn_fh>;
265    close $svn_fh;
266    my ($rev, $user, $time, undef) = split / \| /, $details;
267    $rev =~ s/^r//;
268    my ($yr, $mo, $day, $hr, $min, $sec, $tz) = split /[- :]/, $time, 7;
269
270    $c->cache->{svn} = {
271        revision     => $rev,
272        user         => $user,
273        time         => new DateTime(
274            year   => $yr,
275            month  => $mo,
276            day    => $day,
277            hour   => $hr,
278            minute => $min,
279            second => $sec,
280            time_zone => substr $tz, 0, 5,
281        ),
282        log          => join '', @log,
283    };
284};
285
286    # Refresh Bulbapedia feeds
287eval {
288    timeout 5, sub {
289        my $uri = URI->new($c->site_opts->{rss_url});
290        my @entries = XML::Feed->parse($uri)->entries;
291        @entries = grep { defined }
292            @entries[ 0 .. $c->site_opts->{page_sizes}{index}{rss} - 1 ];
293        $c->cache->{rss_entries} = \@entries;
294    };
295};
296
297    $c->cache->{_last_performed} = time;
298}
299
300=head1 AUTHOR
301
302Maintainer: Alex "Eevee" Munroe (C<veekun@veekun.com>)
303
304See the included F<AUTHORS> file for a full list of contributers.
305
306=head1 LICENSE
307
308See the included F<LICENSE> file.
309
310=cut
311
3121;
Note: See TracBrowser for help on using the browser.