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

Revision 406, 6.3 KB (checked in by eevee, 22 months ago)

Database refactoring. Renamed columns and tables to be more consistent and more readable. (#58)

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