]> git.deb.at Git - deb/packages.git/blob - lib/Packages/Search.pm
a7a76d072bc69a9038a5d95db4eb6c9741455ba6
[deb/packages.git] / lib / Packages / Search.pm
1 #
2 # Packages::Search
3 #
4 # Copyright (C) 2004-2006 Frank Lichtenheld <frank@lichtenheld.de>
5
6 # The code is based on the old search_packages.pl script that
7 # was:
8 #
9 # Copyright (C) 1998 James Treacy
10 # Copyright (C) 2000, 2001 Josip Rodin
11 # Copyright (C) 2001 Adam Heath
12 # Copyright (C) 2004 Martin Schulze
13 #
14 #    This program is free software; you can redistribute it and/or modify
15 #    it under the terms of the GNU General Public License as published by
16 #    the Free Software Foundation; either version 1 of the License, or
17 #    (at your option) any later version.
18 #
19 #    This program is distributed in the hope that it will be useful,
20 #    but WITHOUT ANY WARRANTY; without even the implied warranty of
21 #    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
22 #    GNU General Public License for more details.
23 #
24 #    You should have received a copy of the GNU General Public License
25 #    along with this program; if not, write to the Free Software
26 #    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
27 #
28
29 =head1 NAME
30
31 Packages::Search - 
32
33 =head1 SYNOPSIS
34
35 =head1 DESCRIPTION
36
37 =over 4
38
39 =cut
40
41 package Packages::Search;
42
43 use strict;
44 use warnings;
45
46 use CGI qw( -oldstyle_urls );
47 use POSIX;
48 use HTML::Entities;
49 use DB_File;
50
51 use Deb::Versions;
52 use Packages::CGI;
53 use Exporter;
54
55 our @ISA = qw( Exporter );
56
57 our @EXPORT_OK = qw( nextlink prevlink indexline
58                      resperpagelink
59                      read_entry read_entry_all read_entry_simple
60                      read_src_entry find_binaries
61                      do_names_search do_fulltext_search
62                      printindexline multipageheader );
63 our %EXPORT_TAGS = ( all => [ @EXPORT_OK ] );
64
65 our $VERSION = 0.01;
66
67 our $USE_PAGED_MODE = 1;
68 use constant DEFAULT_PAGE => 1;
69 use constant DEFAULT_RES_PER_PAGE => 50;
70 our %page_params = ( page => { default => DEFAULT_PAGE,
71                                match => '(\d+)' },
72                      number => { default => DEFAULT_RES_PER_PAGE,
73                                  match => '(\d+)' } );
74
75 our $too_many_hits = 0;
76
77 sub parse_params {
78     my ( $cgi, $params_def, $opts ) = @_;
79
80     my %params_ret = ( values => {}, errors => {} );
81     my %params;
82     if ($USE_PAGED_MODE) {
83         debug( "Use PAGED_MODE", 2 );
84         %params = %$params_def;
85         foreach (keys %page_params) {
86             delete $params{$_};
87         }
88         %params = ( %params, %page_params );
89     } else {
90         %params = %$params_def;
91     }
92
93     foreach my $param ( keys %params ) {
94         
95         debug( "Param <strong>$param</strong>", 2 );
96
97         my $p_value_orig = $cgi->param($param);
98
99         if (!defined($p_value_orig)
100             && defined $params_def->{$param}{alias}
101             && defined $cgi->param($params_def->{$param}{alias})) {
102             $p_value_orig = $cgi->param($params_def->{$param}{alias});
103             debug( "Used alias <strong>$params_def->{$param}{alias}</strong>",
104                    2 );
105         }
106
107         my @p_value = ($p_value_orig);
108
109         debug( "Value (Orig) ".($p_value_orig||""), 2 );
110
111         if ($params_def->{$param}{array} && defined $p_value_orig) {
112             @p_value = split /$params_def->{$param}{array}/, $p_value_orig;
113             debug( "Value (Array Split) ". join('##',@p_value), 2 );
114         }
115
116         if ($params_def->{$param}{match} && defined $p_value_orig) {
117             @p_value = map
118             { $_ =~ m/$params_def->{$param}{match}/; $_ = $1 }
119             @p_value;
120         }
121         @p_value = grep { defined $_ } @p_value;
122
123         debug( "Value (Match) ". join('##',@p_value), 2 );
124
125         unless (@p_value) {
126             if (defined $params{$param}{default}) {
127                 @p_value = ($params{$param}{default});
128             } else {
129                 @p_value = undef;
130                 $params_ret{errors}{$param} = "undef";
131                 next;
132             }
133         }
134
135         debug( "Value (Default) ". join('##',@p_value), 2 );
136         my @p_value_no_replace = @p_value;
137
138         if ($params{$param}{replace} && @p_value) {
139             @p_value = ();
140             foreach my $pattern (keys %{$params{$param}{replace}}) {
141                 foreach (@p_value_no_replace) {
142                     if ($_ eq $pattern) {
143                         my $replacement = $params{$param}{replace}{$_};
144                         if (ref $replacement) {
145                             push @p_value, @$replacement;
146                         } else {
147                             push @p_value, $replacement;
148                         }
149                     } else {
150                         push @p_value, $_;
151                     }
152                 }
153             }
154         }
155         
156         debug( "Value (Final) ". join('##',@p_value), 2 );
157
158         if ($params_def->{$param}{array}) {
159             $params_ret{values}{$param} = {
160                 orig => $p_value_orig,
161                 no_replace => \@p_value_no_replace,
162                 final => \@p_value,
163             };
164             @{$params_def->{$param}{var}} = @p_value
165                 if $params_def->{$param}{var};
166         } else {
167             $params_ret{values}{$param} = {
168                 orig => $p_value_orig,
169                 no_replace => $p_value_no_replace[0],
170                 final => $p_value[0],
171             };
172             ${$params_def->{$param}{var}} = $p_value[0]
173                 if $params_def->{$param}{var};
174         }
175         $opts->{$param} = $params_ret{values}{$param}{final} if $opts;
176     }
177
178     if ($USE_PAGED_MODE) {
179         $cgi->delete( "page" );
180         $cgi->delete( "number" );
181     }
182
183     return %params_ret;
184 }
185
186 sub start { 
187     my $params = shift;
188
189     my $page = $params->{values}{page}{final}
190     || DEFAULT_PAGE;
191     my $res_per_page = $params->{values}{number}{final}
192     || DEFAULT_RES_PER_PAGE;
193
194     return 1 if $res_per_page =~ /^all$/i;
195     return $res_per_page * ($page - 1) + 1;
196 }
197
198 sub end {
199     my $params = shift;
200
201     use Data::Dumper;
202     debug( "end: ".Dumper($params) );
203     my $page = $params->{page}
204     || DEFAULT_PAGE;
205     my $res_per_page = $params->{number}
206     || DEFAULT_RES_PER_PAGE;
207
208     return $page * $res_per_page;
209 }
210
211 sub indexline {
212     my ($cgi, $params, $num_res) = @_;
213
214     my $index_line = "";
215     my $page = $params->{page}
216     || DEFAULT_PAGE;
217     my $res_per_page = $params->{number}
218     || DEFAULT_RES_PER_PAGE;
219     my $numpages = ceil($num_res /
220                         $res_per_page);
221     for (my $i = 1; $i <= $numpages; $i++) {
222         if ($i == $page) {
223             $index_line .= $i;
224         } else {
225             $index_line .= "<a href=\"".encode_entities($cgi->self_url).
226                 "&amp;page=$i&amp;number=$res_per_page\">".
227                 "$i</a>";
228         }
229         if ($i < $numpages) {
230            $index_line .= " | ";
231         }
232     }
233     return $index_line;
234 }
235
236 sub nextlink {
237     my ($cgi, $params, $no_results ) = @_;
238
239     my $page = $params->{page}
240     || DEFAULT_PAGE;
241     $page++;
242     my $res_per_page = $params->{number}
243     || DEFAULT_RES_PER_PAGE;
244
245     if ((($page-1)*$res_per_page + 1) > $no_results) {
246         return "&gt;&gt;";
247     }
248
249     return "<a href=\"".encode_entities($cgi->self_url).
250         "&amp;page=$page&amp;number=$res_per_page\">&gt;&gt;</a>";
251 }
252
253 sub prevlink {
254     my ($cgi, $params ) = @_;
255
256     my $page = $params->{page}
257     || DEFAULT_PAGE;
258     $page--;
259     if (!$page) {
260         return "&lt;&lt;";
261     }
262
263     my $res_per_page = $params->{number}
264     || DEFAULT_RES_PER_PAGE;
265
266     return "<a href=\"".encode_entities($cgi->self_url).
267         "&amp;page=$page&amp;number=$res_per_page\">&lt;&lt;</a>";
268 }
269
270 sub resperpagelink {
271     my ($cgi, $params, $res_per_page ) = @_;
272
273     my $page;
274     if ($res_per_page =~ /^all$/i) {
275         $page = 1;
276     } else {
277         $page = ceil(start( $params ) / $res_per_page);
278     }
279
280     return "<a href=\"".encode_entities($cgi->self_url).
281         "&amp;page=$page&amp;number=$res_per_page\">$res_per_page</a>";
282 }
283
284 sub printindexline {
285     my ( $input, $no_results, $opts ) = @_;
286
287     my $index_line;
288     if ($no_results > $opts->{number}) {
289         
290         $index_line = prevlink( $input, $opts)." | ".
291             indexline( $input, $opts, $no_results)." | ".
292             nextlink( $input, $opts, $no_results);
293         
294         print "<p style=\"text-align:center\">$index_line</p>";
295     }
296 }
297
298 sub multipageheader {
299     my ( $input, $no_results, $opts ) = @_;
300
301     my ($start, $end);
302     if ($opts->{number} =~ /^all$/i) {
303         $start = 1;
304         $end = $no_results;
305         $opts->{number} = $no_results;
306         $opts->{number_all}++;
307     } else {
308         $start = Packages::Search::start( $opts );
309         $end = Packages::Search::end( $opts );
310         if ($end > $no_results) { $end = $no_results; }
311     }
312
313     print "<p>Found <em>$no_results</em> matching packages,";
314     if ($end == $start) {
315         print " displaying package $end.</p>";
316     } else {
317         print " displaying packages $start to $end.</p>";
318     }
319
320     printindexline( $input, $no_results, $opts );
321
322     if ($no_results > 100) {
323         print "<p>Results per page: ";
324         my @resperpagelinks;
325         for (50, 100, 200) {
326             if ($opts->{number} == $_) {
327                 push @resperpagelinks, $_;
328             } else {
329                 push @resperpagelinks, resperpagelink($input,$opts,$_);
330             }
331         }
332         if ($opts->{number_all}) {
333             push @resperpagelinks, "all";
334         } else {
335             push @resperpagelinks, resperpagelink($input, $opts, "all");
336         }
337         print join( " | ", @resperpagelinks )."</p>";
338     }
339     return ( $start, $end );
340 }
341
342 sub read_entry_all {
343     my ($hash, $key, $results, $non_results, $opts) = @_;
344     my $result = $hash->{$key} || '';
345     foreach (split /\000/o, $result) {
346         my @data = split ( /\s/o, $_, 8 );
347         debug( "Considering entry ".join( ':', @data), 2);
348         if ($opts->{h_archives}{$data[0]} && $opts->{h_suites}{$data[1]}
349             && ($opts->{h_archs}{$data[2]} || $data[2] eq 'all')
350             && $opts->{h_sections}{$data[3]}) {
351             debug( "Using entry ".join( ':', @data), 2);
352             push @$results, [ $key, @data ];
353         } else {
354             push @$non_results, [ $key, @data ];
355         }
356     }
357 }
358 sub read_entry {
359     my ($hash, $key, $results, $opts) = @_;
360     my @non_results;
361     read_entry_all( $hash, $key, $results, \@non_results, $opts );
362 }
363 sub read_entry_simple {
364     my ($hash, $key, $suite) = @_;
365     my $result = $hash->{$key} || '';
366     foreach (split /\000/o, $result) {
367         my @data = split ( /\s/o, $_, 8 );
368         debug( "Considering entry ".join( ':', @data), 2);
369         if ($data[1] eq $suite) {
370             debug( "Using entry ".join( ':', @data), 2);
371             return \@data;
372         }
373     }
374 }
375 sub read_src_entry {
376     my ($hash, $key, $results, $opts) = @_;
377     my $result = $hash->{$key} || '';
378     foreach (split /\000/o, $result) {
379         my @data = split ( /\s/o, $_, 6 );
380         debug( "Considering entry ".join( ':', @data), 2);
381         if ($opts->{h_archives}{$data[0]}
382             && $opts->{h_suites}{$data[1]}
383             && $opts->{h_sections}{$data[2]}) {
384             debug( "Using entry ".join( ':', @data), 2);
385             push @$results, [ $key, @data ];
386         }
387     }
388 }
389 sub do_names_search {
390     my ($keyword, $packages, $postfixes, $read_entry, $opts) = @_;
391     my @results;
392
393     $keyword = lc $keyword unless $opts->{case_bool};
394         
395     if ($opts->{exact}) {
396         &$read_entry( $packages, $keyword, \@results, $opts );
397     } else {
398         my ($key, $prefixes) = ($keyword, '');
399         my %pkgs;
400         $postfixes->seq( $key, $prefixes, R_CURSOR );
401         while (index($key, $keyword) >= 0) {
402             if ($prefixes =~ /^\001(\d+)/o) {
403                 $too_many_hits += $1;
404             } else {
405                 foreach (split /\000/o, $prefixes) {
406                     $_ = '' if $_ eq '^';
407                     debug( "add word $_$key", 2);
408                     $pkgs{$_.$key}++;
409                 }
410             }
411             last if $postfixes->seq( $key, $prefixes, R_NEXT ) != 0;
412             last if $too_many_hits or keys %pkgs >= 100;
413         }
414         
415         my $no_results = keys %pkgs;
416         if ($too_many_hits || ($no_results >= 100)) {
417             $too_many_hits += $no_results;
418             %pkgs = ( $keyword => 1 );
419         }
420         foreach my $pkg (sort keys %pkgs) {
421             &$read_entry( $packages, $pkg, \@results, $opts );
422         }
423     }
424     return \@results;
425 }
426 sub do_fulltext_search {
427     my ($keyword, $file, $did2pkg, $packages, $read_entry, $opts) = @_;
428     my @results;
429
430     my @lines;
431     my $regex;
432     if ($opts->{case_bool}) {
433         if ($opts->{exact}) {
434             $regex = qr/\b\Q$keyword\E\b/o;
435         } else {
436             $regex = qr/\Q$keyword\E/o;
437         }
438     } else {
439         if ($opts->{exact}) {
440             $regex = qr/\b\Q$keyword\E\b/io;
441         } else {
442             $regex = qr/\Q$keyword\E/io;
443         }
444     }
445
446     open DESC, '<', "$file"
447         or die "couldn't open $file: $!";
448     while (<DESC>) {
449         $_ =~ $regex or next;
450         debug( "Matched line $.", 2);
451         push @lines, $.;
452     }
453     close DESC;
454
455     my %tmp_results;
456     foreach my $l (@lines) {
457         my $result = $did2pkg->{$l};
458         foreach (split /\000/o, $result) {
459             my @data = split /\s/, $_, 3;
460             next unless $opts->{h_archs}{$data[2]};
461             $tmp_results{$data[0]}++;
462         }
463     }
464     foreach my $pkg (keys %tmp_results) {
465         &$read_entry( $packages, $pkg, \@results, $opts );
466     }
467     return \@results;
468 }
469
470 sub find_binaries {
471     my ($pkg, $archive, $suite, $src2bin) = @_;
472
473     my $bins = $src2bin->{$pkg} || '';
474     my %bins;
475     foreach (split /\000/o, $bins) {
476         my @data = split /\s/, $_, 5;
477
478         if (($data[0] eq $archive)
479             && ($data[1] eq $suite)) {
480             $bins{$data[2]}++;
481         }
482     }
483
484     return [ keys %bins ];
485 }
486
487
488 1;