]> git.deb.at Git - deb/packages.git/blob - lib/Packages/DoShow.pm
Display translated descriptions
[deb/packages.git] / lib / Packages / DoShow.pm
1 package Packages::DoShow;
2
3 use strict;
4 use warnings;
5
6 use POSIX;
7 use URI::Escape;
8 use HTML::Entities;
9 use DB_File;
10 use Benchmark ':hireswallclock';
11 use Exporter;
12
13 use Deb::Versions;
14 use Packages::Config qw( $DBDIR @SUITES @ARCHIVES @SECTIONS
15                          @ARCHITECTURES %FTP_SITES @DDTP_LANGUAGES);
16 use Packages::I18N::Locale;
17 use Packages::CGI qw( :DEFAULT make_url make_search_url note );
18 use Packages::DB;
19 use Packages::Search qw( :all );
20 use Packages::Page ();
21 use Packages::SrcPage ();
22
23 our @ISA = qw( Exporter );
24 our @EXPORT = qw( do_show );
25
26 sub do_show {
27     my ($params, $opts, $html_header, $page_contents) = @_;
28
29     if ($params->{errors}{package}) {
30         fatal_error( _g( "package not valid or not specified" ) );
31     }
32     if ($params->{errors}{suite}) {
33         fatal_error( _g( "suite not valid or not specified" ) );
34     }
35     if (@{$opts->{suite}} > 1) {
36         fatal_error( sprintf( _g( "more than one suite specified for show (%s)" ), "@{$opts->{suite}}" ) );
37     }
38
39     my %contents;
40     $contents{make_url} = sub { return &Packages::CGI::make_url(@_) };
41
42     my $pkg = $opts->{package};
43     $contents{pkg} = $pkg;
44     my $suite = $opts->{suite}[0];
45     $contents{suite} = $suite;
46     my $archive = $opts->{archive}[0] ||'';
47     
48     our (%packages_all, %sources_all);
49     my (@results, @non_results);
50     my $page = $opts->{source} ?
51         new Packages::SrcPage( $pkg ) :
52         new Packages::Page( $pkg );
53     my ($short_desc, $version, $section, $subsection) = ("")x5;
54     
55     my $st0 = new Benchmark;
56     unless (@Packages::CGI::fatal_errors) {
57         tie %packages_all, 'DB_File', "$DBDIR/packages_all_$suite.db",
58         O_RDONLY, 0666, $DB_BTREE
59             or die "couldn't tie DB $DBDIR/packages_all_$suite.db: $!";
60         tie %sources_all, 'DB_File', "$DBDIR/sources_all_$suite.db",
61         O_RDONLY, 0666, $DB_BTREE
62             or die "couldn't tie DB $DBDIR/sources_all_$suite.db: $!";
63
64         unless ($opts->{source}) {
65             read_entry_all( \%packages, $pkg, \@results, \@non_results, $opts );
66         } else {
67             read_src_entry_all( \%sources, $pkg, \@results, \@non_results, $opts );
68         }
69
70         unless (@results || @non_results ) {
71             fatal_error( _g( "No such package." )."<br>".
72                          sprintf( _g( '<a href="%s">Search for the package</a>' ), make_search_url('','keywords='.uri_escape($pkg)) ) );
73         } else {
74             my %all_suites;
75             foreach (@results, @non_results) {
76                 my $a = $_->[1];
77                 my $s = $_->[2];
78                 $all_suites{$s}++;
79             }
80             $contents{suites} = [ suites_sort(keys %all_suites) ];
81
82             unless (@results) {
83                 fatal_error( _g( "Package not available in this suite." ) );
84             } else {
85                 $contents{page} = $page;
86                 unless ($opts->{source}) {
87
88                     for my $entry (@results) {
89                         debug( join(":", @$entry), 1 ) if DEBUG;
90                         my (undef, $archive, undef, $arch, $section, $subsection,
91                             $priority, $version, $provided_by) = @$entry;
92                         
93                         if ($arch ne 'virtual') {
94                             my %data = split /\000/, $packages_all{"$pkg $arch $version"};
95                             $data{package} = $pkg;
96                             $data{architecture} = $arch;
97                             $data{version} = $version;
98                             $page->merge_package(\%data)
99                                 or debug( "Merging $pkg $arch $version FAILED", 2 ) if DEBUG;
100                         } else {
101                             $page->add_provided_by([split /\s+/, $provided_by]);
102                         }
103                     }
104
105                     unless ($page->is_virtual()) {
106                         $version = $page->{newest};
107                         $contents{version} = $version;
108                         my $source = $page->get_newest( 'source' );
109                         $archive = $page->get_newest( 'archive' );
110                         $contents{archive} = $archive;
111
112                         debug( "find source package: source=$source", 1) if DEBUG;
113                         my $src_data = $sources_all{"$archive $suite $source"};
114                         #FIXME: should be $main_archive or similar, not hardcoded "us"
115                         $src_data = $sources_all{"us $suite $source"} unless $src_data;
116                         $page->add_src_data( $source, $src_data )
117                             if $src_data;
118
119                         my $st1 = new Benchmark;
120                         my $std = timediff($st1, $st0);
121                         debug( "Data search and merging took ".timestr($std) ) if DEBUG;
122
123                         my $did = $page->get_newest( 'description' );
124                         my $desc_md5 = $page->get_newest( 'description-md5' );
125                         my @complete_tags = split(/, /, $page->get_newest( 'tag' ));
126                         my @tags;
127                         foreach (@complete_tags) {
128                             my ($facet, $tag) = split( /::/, $_, 2);
129                             # handle tags like devel::{lang:c,lang:c++}
130                             if ($tag =~ s/^\{(.+)\}$/$1/) {
131                                 foreach (split( /,/, $tag )) {
132                                     next if $tag =~ /^special:/;
133                                     push @tags, [ $facet, $_ ];
134                                 }
135                             } else {
136                                 next if $tag =~ /^special:/;
137                                 push @tags, [ $facet, $tag ];
138                             }
139                         }
140
141                         $contents{tags} = \@tags;
142                         $contents{debtags_voc} = \%debtags;
143
144                         $section = $page->get_newest( 'section' );
145                         $contents{section} = $section;
146                         $subsection = $page->get_newest( 'subsection' );
147                         $contents{subsection} = $subsection;
148
149                         my $archives = $page->get_arch_field( 'archive' );
150                         my $versions = $page->get_arch_field( 'version' );
151                         my $sizes_inst = $page->get_arch_field( 'installed-size' );
152                         my $sizes_deb = $page->get_arch_field( 'size' );
153                         my @archs = sort $page->get_architectures;
154
155                         # process description
156                         #
157                         sub process_description {
158                             my ($desc) = @_;
159
160                             my $short_desc = encode_entities( $1, "<>&\"" )
161                                 if $desc =~ s/^(.*)$//m;
162                             my $long_desc = encode_entities( $desc, "<>&\"" );
163
164                             $long_desc =~ s,((ftp|http|https)://[\S~-]+?/?)((\&gt\;)?[)]?[']?[:.\,]?(\s|$)),<a href=\"$1\">$1</a>$3,go; # syntax highlighting -> '];
165                             $long_desc =~ s/\A //o;
166                             $long_desc =~ s/\n /\n/sgo;
167                             $long_desc =~ s/\n.\n/\n<p>\n/go;
168                             $long_desc =~ s/(((\n|\A) [^\n]*)+)/\n<pre>$1\n<\/pre>/sgo;
169
170                             return ($short_desc, $long_desc);
171                         }
172
173                         my $desc = $descriptions{$did};
174                         my $long_desc;
175                         ($short_desc, $long_desc) = process_description($desc);
176
177                         $contents{desc}{en} = { short => $short_desc,
178                                                 long => $long_desc, };
179
180                         debug( "desc_md5=$desc_md5", 2)
181                             if DEBUG;
182                         my $trans_desc = $desctrans{$desc_md5};
183                         if ($trans_desc) {
184                             my %trans_desc = split /\000|\001/, $trans_desc;
185                             debug( "TRANSLATIONS: ".join(" ",keys %trans_desc), 2)
186                                 if DEBUG;
187                             while (my ($l, $d) = each %trans_desc) {
188                                 my ($short_t, $long_t) = process_description($d);
189
190                                 $contents{desc}{$l} = { short => $short_t,
191                                                         long => $long_t, };
192                             }
193                         }
194
195                         my $v_str = $version;
196                         my $multiple_versions = grep { $_ ne $version } values %$versions;
197                         $v_str .= _g(" and others") if $multiple_versions;
198                         $contents{versions} = { short => $v_str,
199                                                 multiple => $multiple_versions };
200
201                         my $provided_by = $page->{provided_by};
202                         $contents{providers} = [];
203                         pkg_list( \%packages, $opts, $provided_by, 'en', $contents{providers} ) if $provided_by;
204
205                         #
206                         # display dependencies
207                         #
208                         build_deps( \%packages, $opts, $pkg,
209                                     $page->get_dep_field('pre-depends'),
210                                     'depends', \%contents );
211                         build_deps( \%packages, $opts, $pkg,
212                                     $page->get_dep_field('depends'),
213                                     'depends', \%contents );
214                         build_deps( \%packages, $opts, $pkg,
215                                     $page->get_dep_field('recommends'),
216                                     'recommends', \%contents );
217                         build_deps( \%packages, $opts, $pkg,
218                                     $page->get_dep_field('suggests'),
219                                     'suggests', \%contents );
220
221                         #
222                         # Download package
223                         #
224                         my @downloads;
225                         foreach my $a ( @archs ) {
226                             my %d = ( arch => $a,
227                                       pkgsize => sprintf( '%.1f', floor(($sizes_deb->{$a}/102.4)+0.5)/10 ),
228                                       instsize => $sizes_inst->{$a}, );
229
230                             $d{version} = $versions->{$a} if $multiple_versions;
231                             $d{archive} = $archives->{$a};
232                             if ( ($suite ne "experimental")
233                                  && ($subsection ne 'debian-installer')) {
234                                 $d{contents_avail} = 1;
235                             }
236                             push @downloads, \%d;
237                         }
238                         $contents{downloads} = \@downloads;
239
240                         #
241                         # more information
242                         #
243                         moreinfo( name => $pkg, data => $page, vars => \%contents,
244                                   opts => $opts,
245                                   env => \%FTP_SITES,
246                                   bugreports => 1, sourcedownload => 1,
247                                   changesandcopy => 1, maintainers => 1,
248                                   search => 1 );
249                     } else { # unless $page->is_virtual
250                         $contents{is_virtual} = 1;
251                         $contents{desc}{short} = _g( "virtual package" );
252                         $contents{subsection} = 'virtual';
253
254                         my $provided_by = $page->{provided_by};
255                         $contents{providers} = [];
256                         pkg_list( \%packages, $opts, $provided_by, 'en', $contents{providers} );
257
258                     } # else (unless $page->is_virtual)
259                 } else { # unless $opts->{source}
260                     $contents{is_source} = 1;
261
262                     for my $entry (@results) {
263                         debug( join(":", @$entry), 1 ) if DEBUG;
264                         my (undef, $archive, undef, $section, $subsection,
265                             $priority, $version) = @$entry;
266
267                         my $data = $sources_all{"$archive $suite $pkg"};
268                         $page->merge_data($pkg, $suite, $archive, $data)
269                             or debug( "Merging $pkg $version FAILED", 2 ) if DEBUG;
270                     }
271                     $version = $page->{version};
272                     $contents{version} = $version;
273
274                     my $st1 = new Benchmark;
275                     my $std = timediff($st1, $st0);
276                     debug( "Data search and merging took ".timestr($std) ) if DEBUG;
277
278                     $archive = $page->get_newest( 'archive' );
279                     $contents{archive} = $archive;
280                     $section = $page->get_newest( 'section' );
281                     $contents{section} = $section;
282                     $subsection = $page->get_newest( 'subsection' );
283                     $contents{subsection} = $subsection;
284
285                     my $binaries = find_binaries( $pkg, $archive, $suite, \%src2bin );
286                     if ($binaries && @$binaries) {
287                         $contents{binaries} = [];
288                         pkg_list( \%packages, $opts, $binaries, 'en', $contents{binaries} );
289                     }
290
291                     #
292                     # display dependencies
293                     #
294                     build_deps( \%packages, $opts, $pkg,
295                                 $page->get_dep_field('build-depends'),
296                                 'build-depends', \%contents );
297                     build_deps( \%packages, $opts, $pkg,
298                                 $page->get_dep_field('build-depends-indep'),
299                                 'build-depends-indep', \%contents );
300
301                     #
302                     # Source package download
303                     #
304                     my $source_files = $page->get_src( 'files' );
305                     my $source_dir = $page->get_src( 'directory' );
306
307                     $contents{srcfiles} = [];
308                     foreach( @$source_files ) {
309                         my ($src_file_md5, $src_file_size, $src_file_name)
310                             = split /\s+/, $_;
311                         (my $server = lc $archive) =~ s/-//go; # non-US hack
312                         $server = $FTP_SITES{$server}
313                             || $FTP_SITES{us};
314                         my $path = "/$source_dir/$src_file_name";
315
316                         push @{$contents{srcfiles}}, { server => $server, path => $path, filename => $src_file_name,
317                                                        size => sprintf("%.1f", (floor(($src_file_size/102.4)+0.5)/10)),
318                                                        md5sum => $src_file_md5 };
319                     }
320
321                     #
322                     # more information
323                     #
324                     moreinfo( name => $pkg, data => $page, vars => \%contents,
325                               opts => $opts,
326                               env => \%FTP_SITES,
327                               bugreports => 1,
328                               changesandcopy => 1, maintainers => 1,
329                               search => 1, is_source => 1 );
330
331                 } # else (unless $opts->{source})
332             } # else (unless @results)
333         } # else (unless (@results || @non_results ))
334     }
335
336 #    use Data::Dumper;
337 #    debug( "Final page object:\n".Dumper(\%contents), 3 ) if DEBUG;
338
339     %$page_contents = %contents;
340 }
341
342 sub moreinfo {
343     my %info = @_;
344     
345     my $name = $info{name} or return;
346     my $env = $info{env} or return;
347     my $opts = $info{opts} or return;
348     my $page = $info{data} or return;
349     my $contents = $info{vars} or return;
350     my $is_source = $info{is_source};
351     my $suite = $opts->{suite}[0];
352
353     my $source = $page->get_src( 'package' );
354     my $source_version = $page->get_src( 'version' );
355     my $src_dir = $page->get_src('directory');
356     if ($info{sourcedownload}) {
357         $contents->{src}{url} = make_url($source,'',{source=>'source'});
358         $contents->{src}{pkg} = $source;
359
360         my @downloads;
361         my $files = $page->get_src( 'files' );
362         if (defined($files) and @$files) {
363             foreach( @$files ) {
364                 my ($src_file_md5, $src_file_size, $src_file_name) = split /\s/o, $_;
365                 my ($server, $path);
366                 # non-US hack
367                 ($server = lc $page->get_newest('archive')) =~ s/-//go;
368                 $server = $env->{$server}||$env->{us};
369                 $path = "/$src_dir/$src_file_name";
370                 push @downloads, { name => $src_file_name, server => $server, path => $path };
371             }
372         }
373         $contents->{src}{downloads} = \@downloads;
374     }
375
376     if ($info{changesandcopy}) {
377         if ( $src_dir ) {
378             (my $src_basename = $source_version) =~ s,^\d+:,,; # strip epoche
379             $src_basename = "${source}_$src_basename";
380             $src_dir =~ s,pool/updates,pool,o;
381             $src_dir =~ s,pool/non-US,pool,o;
382
383             $contents->{files}{changelog}{path} = "$src_dir/$src_basename/changelog";
384             $contents->{files}{copyright}{path} = "$src_dir/$src_basename/".( $is_source ? 'copyright' : "$name.copyright" );
385         }
386    }
387
388     if ($info{maintainers}) {
389         my $uploaders = $page->get_src( 'uploaders' );
390         if ($uploaders && @$uploaders) {
391             my @maintainers = map { { name => $_->[0], mail => $_->[1] } } @$uploaders;
392             $contents->{maintainers} = \@maintainers;
393         }
394     }
395
396 }
397
398 sub providers {
399     my ($suite, $entry, $also) = @_;
400     my %tmp = map { $_ => 1 } split /\s/, $entry;
401     my @provided_by = keys %tmp; # weed out duplicates
402     my %out = ( also => $also,
403                 pkgs => \@provided_by );
404     return \%out;
405 }
406
407 sub build_deps {
408     my ( $packages, $opts, $pkg, $relations, $type, $contents) = @_;
409     my %dep_type = ('depends' => 'dep', 'recommends' => 'rec', 
410                     'suggests' => 'sug', 'build-depends' => 'adep',
411                     'build-depends-indep' => 'idep' );
412     my $suite = $opts->{suite}[0];
413
414     my %out = ( id => $dep_type{$type}, terms => [] );
415
416 #    use Data::Dumper;
417 #    debug( "print_deps called:\n".Dumper( $pkg, $relations, \$type ), 3 ) if DEBUG;
418
419     foreach my $rel (@$relations) {
420         my %rel_out;
421         $rel_out{is_old_pkgs} = $rel->[0];
422         $rel_out{alternatives} = [];
423
424         foreach my $rel_alt ( @$rel ) {
425             next unless ref($rel_alt);
426             my ( $p_name, $pkg_version, $arch_neg,
427                  $arch_str, $subsection, $available ) = @$rel_alt;
428
429             if ($arch_str ||= '') {
430                 if ($arch_neg) {
431                     $arch_str = _g("not")." $arch_str";
432                 } else {
433                     $arch_str = $arch_str;
434                 }
435             }
436
437             my %rel_alt_out = ( name => $p_name,
438                                 version => $pkg_version,
439                                 arch_str => $arch_str,
440                                 arch_neg => $arch_neg );
441                              
442             my @results;
443             my %entries;
444             my $entry = $entries{$p_name} ||
445                 read_entry_simple( $packages, $p_name, $opts->{h_archives}, $suite);
446             my $short_desc = $entry->[-1];
447             my $arch = $entry->[3];
448             my $archive = $entry->[1];
449             my $p_suite = $entry->[2];
450             if ( $short_desc ) {
451                 $rel_alt_out{desc} = $short_desc;
452                 $rel_alt_out{suite} = $p_suite;
453                 if ( $rel_out{is_old_pkgs} ) {
454                 } elsif (defined $entry->[1]) {
455                     $entries{$p_name} ||= $entry;
456                     $rel_alt_out{providers} = providers( $p_suite,
457                                                         $entry->[0],
458                                                         1 ) if defined $entry->[0];
459                 } elsif (defined $entry->[0]) {
460                     $rel_alt_out{desc} = undef;
461                     $rel_alt_out{providers} = providers( $p_suite,
462                                                         $entry->[0] );
463                     #FIXME: we don't handle virtual packages from
464                     # the fallback suite correctly here
465                     $rel_alt_out{suite} = $suite;
466                 }
467             } elsif ( $rel_out{is_old_pkgs} ) {
468             } else {
469                 $rel_alt_out{desc} = _g( "Package not available" );
470                 $rel_alt_out{suite} = '';
471             }
472             push @{$rel_out{alternatives}}, \%rel_alt_out;
473         }
474
475         push @{$out{terms}}, \%rel_out;
476     }
477
478     $contents->{relations} ||= [];
479     push @{$contents->{relations}}, \%out if @{$out{terms}};
480 } # end print_deps
481
482 sub pkg_list {
483     my ( $packages, $opts, $pkgs, $lang, $list ) = @_;
484     my $suite = $opts->{suite}[0];
485
486     foreach my $p ( sort @$pkgs ) {
487
488         # we don't deal with virtual packages here because for the
489         # current uses of this function this isn't needed
490         my $short_desc = (read_entry_simple( $packages, $p, $opts->{h_archives}, $suite))->[-1];
491
492         if ( $short_desc ) {
493             push @$list, { name => $p, desc => $short_desc, available => 1 };
494         } else {
495             push @$list, { name => $p, desc => _g("Not available") };
496         }
497     }
498 }
499
500
501 1;
502