File Coverage

blib/lib/Dancer/SearchApp.pm
Criterion Covered Total %
statement 30 55 54.5
branch 0 8 0.0
condition 0 3 0.0
subroutine 10 13 76.9
pod 0 3 0.0
total 40 82 48.7


line stmt bran cond sub pod time code
1             package Dancer::SearchApp;
2 2     2   13988 use strict;
  2         2  
  2         53  
3 2     2   6 use File::Basename 'basename';
  2         3  
  2         136  
4 2     2   1264 use Dancer;
  2         334837  
  2         10  
5 2     2   2873 use Search::Elasticsearch::Async;
  2         66678  
  2         53  
6 2     2   14 use URI::Escape 'uri_unescape';
  2         2  
  2         106  
7 2     2   942 use URI::file;
  2         8076  
  2         57  
8             #use Search::Elasticsearch::TestServer;
9              
10 2     2   954 use Dancer::SearchApp::Defaults 'get_defaults';
  2         3  
  2         90  
11              
12 2     2   622 use Dancer::SearchApp::Entry;
  2         7  
  2         84  
13              
14 2     2   12 use vars qw($VERSION $es %indices);
  2         3  
  2         126  
15             $VERSION = '0.05';
16              
17             =head1 NAME
18              
19             Dancer::SearchApp - A simple local search engine
20              
21             =head1 SYNOPSIS
22              
23             =head1 QUICKSTART
24              
25             Also see L.
26              
27             cpanm --look Dancer::SearchApp
28            
29             # Install prerequisites
30             cpanm --installdeps .
31              
32             # Install Elasticsearch https://www.elastic.co/downloads/elasticsearch
33             # Start Elasticsearch
34             # Install Apache Tika from https://tika.apache.org/download.html into jar/
35              
36             # Launch the web frontend
37             plackup --host 127.0.0.1 -p 8080 -Ilib -a bin\app.pl
38              
39             # Edit filesystem configuration
40             cat >>fs-import.yml
41             fs:
42             directories:
43             - folder: "C:\\Users\\Corion\\Projekte\\App-StarTraders"
44             recurse: true
45             exclude:
46             - ".git"
47             - folder: "t\\documents"
48             recurse: true
49              
50             # Collect some content
51             perl -Ilib -w bin/index-filesystem.pl -f
52              
53             # Search in your browser
54              
55             =head1 CONFIGURATION
56              
57             Configuration happens through config.yml
58              
59             elastic_search:
60             home: "./elasticsearch-2.1.1/"
61             index: "dancer-searchapp"
62              
63             The Elasticsearch instance to used can also be passed in C<%ENV>
64             as C.
65              
66             =cut
67              
68 2     2   9 use Data::Dumper;
  2         3  
  2         1727  
69             $Data::Dumper::Sortkeys = 1;
70              
71             my $config = get_defaults(
72             env => \%ENV,
73             config => config(),
74             #defaults => \%
75             names => [
76             ['elastic_search/index' => 'elastic_search/index' => 'SEARCHAPP_ES_INDEX', 'searchapp'],
77             ['elastic_search/nodes' => 'elastic_search/nodes' => 'SEARCHAPP_ES_NODES', 'localhost:9200'],
78             ],
79             );
80              
81             sub search {
82 0 0   0 0   if( ! $es ) {
83 0           my $nodes = config->{elastic_search}->{nodes};
84 0           $es = Search::Elasticsearch->new(
85             nodes => $nodes,
86             );
87             };
88            
89 0           $es
90             };
91              
92             $Template::Stash::PRIVATE = $Template::Stash::PRIVATE = 1;
93              
94             get '/' => sub {
95             # Later, separate out the code paths between
96             # search and index page only, to serve the index
97             # page as a static file
98            
99             my $statistics;
100             my $results;
101            
102             my $from = params->{'from'} || 0;
103             $from =~ s!\D!!g;
104             my $size = params->{'size'} || 25;
105             $size =~ s!\D!!g;
106             my $search_term = params->{'q'};
107            
108             if( defined $search_term) {
109            
110             #warning "Reading ES indices\n";
111             %indices = %{ search->indices->get({index => ['*']}) };
112             #warning $_ for sort keys %indices;
113              
114             my @restrict_type;
115             my $type;
116             if( $type = params->{'type'} and $type =~ m!([a-z0-9+-]+)/[a-z0-9+-]+!i) {
117             #warn "Filtering for '$type'";
118             @restrict_type = (filter => { term => { mime_type => $type }});
119             };
120            
121             # Move this to an async query, later
122             my $index = config->{elastic_search}->{index};
123             $results = search->search(
124             # Wir suchen in allen Sprachindices
125             index => [ grep { /^\Q$index\E/ } sort keys %indices ],
126             body => {
127             from => $from,
128             size => $size,
129             query => {
130             filtered => {
131             query => {
132             query_string => {
133             query => $search_term,
134             fields => ['title','folder','content', 'author'] #'creation_date']
135             },
136             },
137             @restrict_type,
138             },
139             },
140             sort => {
141             _score => { order => 'desc' },
142             },
143             "highlight" => {
144             "pre_tags" => '',
145             "post_tags" => '',
146             "fields" => {
147             "content" => {}
148             }
149             }
150             }
151             );
152            
153             #warn Dumper $results->{hits};
154             } else {
155             # Update the statistics
156             #$statistics = search->search(
157             # search_type => 'count',
158             # index => config->{index},
159             # body => {
160             # query => {
161             # match_all => {}
162             # }
163             # }
164             #);
165             #warn Dumper $statistics;
166             };
167            
168             if( $results ) {
169             for( @{ $results->{ hits }->{hits} } ) {
170             $_->{source} = Dancer::SearchApp::Entry->from_es( $_ );
171             for my $key ( qw( id index type )) {
172             $_->{$key} = $_->{"_$key"}; # thanks, Template::Toolkit
173             };
174            
175             };
176             };
177            
178             # Output the search results
179             template 'index', {
180             results => ($results ? $results->{hits} : undef ),
181             params => {
182             q => $search_term,
183             from => $from,
184             size => $size,
185             },
186             };
187             };
188              
189             # Show (cached) elements
190             get '/cache/:index/:type/:id' => sub {
191             my $index = params->{index};
192             my $type = params->{type};
193             my $id = uri_unescape( params->{id} );
194             my $document = retrieve($index,$type,$id);
195             #warn $document->basic_mime_type;
196            
197             $document->{type} = $type;
198             $document->{index} = $index;
199            
200             if( $document ) {
201             return template 'view_document', {
202             result => $document,
203             backlink => scalar( request->referer ),
204             }
205             } else {
206             status 404;
207             return <
208             That file does (not) exist anymore in the index.
209             SORRY
210             # We could delete that item from the index here...
211             # Or schedule reindexing of the resource?
212             }
213             };
214              
215             # Reproxy elements from disk
216             sub reproxy {
217 0     0 0   my( $document, $local, $disposition, %options ) = @_;
218            
219             # Now, if the file exists both in the index and locally, let's reproxy the content
220 0 0 0       if( $document and -f $local) {
221 0           status 200;
222 0           content_type( $document->mime_type );
223 0           header( "Content-Disposition" => sprintf '%s; filename="%s"', $disposition, basename $local);
224 0           my $abs = File::Spec->rel2abs( $local, '.' );
225 0 0         open my $fh, '<', $local
226             or die "Couldn't read local file '$local': $!";
227 0           binmode $fh;
228 0           local $/;
229             <$fh>
230            
231 0           } else {
232 0           status 404; # sorry
233             return <
234             That file does (not) exist anymore or is currently unreachable
235             for this webserver. We'll need to implement
236             cleaning up the index from dead items.
237             SORRY
238             # We could delete that item from the index here...
239             # Or schedule reindexing of the resource?
240 0           }
241             };
242              
243             sub retrieve {
244 0     0 0   my( $index, $type, $id ) = @_;
245 0           my $document;
246 0 0         if( eval {
247 0           $document = search->get(index => $index, type => $type, id => $id);
248 0           1
249             }) {
250 0           my $res = Dancer::SearchApp::Entry->from_es($document);
251 0           return $res
252             } else {
253 0           warn "$@";
254             };
255             # Not found in the Elasticsearch index
256             return undef
257 0           }
258              
259             get '/open/:index/:type/:id' => sub {
260             my $index = params->{index};
261             my $type = params->{type};
262             my $id = uri_unescape params->{id};
263             my $document = retrieve($index,$type,$id);
264             if( $type eq 'http' ) {
265             return
266             redirect $id
267             } else {
268             my $local = URI::file->new( $id )->file;
269             return
270             reproxy( $document, $local, 'Attachment',
271             index => $index,
272             type => $type,
273             );
274             }
275             };
276              
277             get '/inline/:index/:type/:id' => sub {
278             my $index = params->{index};
279             my $type = params->{type};
280             my $id = uri_unescape params->{id};
281             my $document = retrieve($index,$type,$id);
282            
283             my $local;
284             if( 'http' eq $type ) {
285             $document->content
286             } else {
287             $local = URI::file->new( $id )->file;
288             };
289            
290             reproxy( $document, $local, 'Inline',
291             index => $index,
292             type => $type,
293             );
294            
295             };
296              
297             true;
298              
299             __END__