File Coverage

blib/lib/Web/Dash.pm
Criterion Covered Total %
statement 16 18 88.8
branch n/a
condition n/a
subroutine 6 6 100.0
pod n/a
total 22 24 91.6


line stmt bran cond sub pod time code
1             package Web::Dash;
2              
3 2     2   32342 use strict;
  2         4  
  2         78  
4 2     2   9 use warnings;
  2         4  
  2         61  
5 2     2   1142 use Plack::Request;
  2         120671  
  2         62  
6 2     2   1018 use Plack::Util;
  2         4180  
  2         51  
7 2     2   11 use File::Find ();
  2         2  
  2         30  
8 2     2   867 use Web::Dash::Lens;
  0            
  0            
9             use Encode;
10             use Future::Q 0.012;
11             use AnyEvent::DBus 0.31;
12             use AnyEvent;
13             use JSON qw(to_json);
14             use Try::Tiny;
15             use Carp;
16              
17             my $index_page_template = <<'EOD';
18            
19            
20            
21             Web Dash
22            
111            
112            
113            
114            
115            
116            
117            
118            
119             [%LENSES_LIST%]
120            
121            
122            
Hint: You can change lens with arrow Up and Down keys.
123            
124            
125            
377            
378            
379             EOD
380              
381             sub _render_lenses_info {
382             my ($lenses_info_ref) = @_;
383             return join "", map {
384             my $lens = $lenses_info_ref->[$_];
385             my $name = Plack::Util::encode_html($lens->{name});
386             my $hint = Plack::Util::encode_html($lens->{hint});
387             qq{
  • \n};
    388             } 0 .. $#$lenses_info_ref;
    389             }
    390              
    391             sub new {
    392             my ($class, %args) = @_;
    393             my $self = bless {
    394             lenses => [],
    395             lens_for_service_name => {},
    396             cache_index_page => undef,
    397             }, $class;
    398             if(defined $args{lenses}) {
    399             croak "lenses param must be an array-ref" if ref($args{lenses}) ne 'ARRAY';
    400             $self->{lenses} = $args{lenses};
    401             }else {
    402             $self->_init_lenses(defined($args{lenses_dir}) ? $args{lenses_dir} : '/usr/share/unity/lenses');
    403             }
    404              
    405             if(@{$self->{lenses}}) {
    406             ## ** Wait for all the lenses to respond and recreate them.
    407             ## ** This is because lenses can be unstable at first.
    408             ## ** See xt/spawning-lens.t for detail.
    409             my $cv = AnyEvent->condvar;
    410             foreach my $lens (@{$self->{lenses}}) {
    411             $cv->begin;
    412             $lens->search_hint->then(sub { $cv->end })
    413             }
    414             $cv->recv;
    415             $self->_recreate_lenses();
    416             %{$self->{lens_for_service_name}} = map { $_->service_name => $_ } @{$self->{lenses}};
    417             }
    418            
    419             return $self;
    420             }
    421              
    422             sub _init_lenses {
    423             my ($self, @search_dirs) = @_;
    424             File::Find::find(sub {
    425             my $filepath = $File::Find::name;
    426             return if $filepath !~ /\.lens$/;
    427             push(@{$self->{lenses}}, Web::Dash::Lens->new(lens_file => $filepath));
    428             }, @search_dirs);
    429             }
    430              
    431             sub _recreate_lenses {
    432             my ($self) = @_;
    433             my @new_lenses = map { $_->clone } @{$self->{lenses}};
    434             $self->{lenses} = \@new_lenses;
    435             }
    436              
    437             sub _render_index {
    438             my ($self, $req) = @_;
    439             return sub {
    440             my ($responder) = @_;
    441             if(defined $self->{cache_index_page}) {
    442             $responder->([
    443             200, ['Content-Type', 'text/html; charset=utf8'],
    444             [$self->{cache_index_page}]
    445             ]);
    446             return;
    447             }
    448             Future::Q->wait_all(map { $_->search_hint } @{$self->{lenses}})->then(sub {
    449             my (@search_hints) = map { $_->get } @_;
    450             my @lenses_info = map {
    451             my $hint = $search_hints[$_];
    452             my $lens = $self->{lenses}[$_];
    453             +{hint => $hint, name => $lens->service_name};
    454             } (0 .. $#search_hints);
    455             my $lenses_list = _render_lenses_info(\@lenses_info);
    456             my $page = $index_page_template;
    457             $page =~ s/\[%LENSES_LIST%\]/$lenses_list/;
    458             $page = Encode::encode('utf8', $page);
    459             $self->{cache_index_page} = $page;
    460             $responder->([
    461             200, ['Content-Type', 'text/html; charset=utf8'],
    462             [$page]
    463             ]);
    464             })->catch(sub {
    465             my $error = shift;
    466             $responder->([
    467             500, ['Content-Type', 'text/plain'],
    468             [Encode::encode('utf8', $error)]
    469             ]);
    470             });
    471             };
    472             }
    473              
    474             sub _json_response {
    475             my ($response_object, $code) = @_;
    476             if(!defined($code)) {
    477             $code = $response_object->{error} ? 500 : 200;
    478             }
    479             return [
    480             $code, ['Content-Type', 'application/json; charset=utf8'],
    481             [to_json($response_object, {ascii => 1})]
    482             ];
    483             }
    484              
    485             sub _render_search {
    486             my ($self, $req) = @_;
    487             return sub {
    488             my $responder = shift;
    489             my $lens_name = $req->query_parameters->{lens} || 0;
    490             my $query_string = Encode::decode('utf8', scalar($req->query_parameters->{'q'}) || '');
    491             my $lens = $self->{lens_for_service_name}{$lens_name};
    492             Future::Q->try(sub {
    493             if(not defined $lens) {
    494             die "Unknown lens name: $lens_name\n";
    495             }
    496             return $lens->search($query_string);
    497             })->then(sub {
    498             my @results = @_;
    499             if(@results) {
    500             return Future::Q->needs_all(map { $lens->category($_->{category_index}) } @results)->then(sub {
    501             my (@categories) = @_;
    502             foreach my $i (0 .. $#categories) {
    503             $results[$i]{category} = $categories[$i];
    504             }
    505             return @results;
    506             })->catch(sub {
    507             my $e = shift;
    508             warn "WARN: $e";
    509             return @results;
    510             });
    511             }
    512             return @results;
    513             })->then(sub {
    514             $responder->(_json_response({error => undef, results => \@_}), 200);
    515             })->catch(sub {
    516             my $e = shift;
    517             $responder->(_json_response({error => $e}, 500));
    518             });
    519             };
    520             }
    521              
    522             sub to_app {
    523             my $self = shift;
    524             return sub {
    525             my ($env) = @_;
    526             my $req = Plack::Request->new($env);
    527             if($req->path eq '/') {
    528             return $self->_render_index($req);
    529             }elsif($req->path eq '/search.json') {
    530             return $self->_render_search($req);
    531             }else {
    532             return [404, ['Content-Type', 'text/plain'], ['Not Found']];
    533             }
    534             };
    535             }
    536              
    537              
    538             our $VERSION = '0.04';
    539              
    540             1;
    541              
    542             __END__