]> Sergey Matveev's repositories - public-inbox.git/blobdiff - lib/PublicInbox/View.pm
cleanup: use '$ibx' consistently when referring to Inbox refs
[public-inbox.git] / lib / PublicInbox / View.pm
index 416cb4a8001a9040afabff4ec606a2ceacb6c67b..f94daaf2bd65521a97ab591eacb102fc71056ddd 100644 (file)
@@ -6,6 +6,7 @@
 package PublicInbox::View;
 use strict;
 use warnings;
+use bytes (); # only for bytes::length
 use PublicInbox::MsgTime qw(msg_datestamp);
 use PublicInbox::Hval qw/ascii_html obfuscate_addrs/;
 use PublicInbox::Linkify;
@@ -14,6 +15,7 @@ use PublicInbox::MsgIter;
 use PublicInbox::Address;
 use PublicInbox::WwwStream;
 use PublicInbox::Reply;
+use PublicInbox::ViewDiff qw(flush_diff);
 require POSIX;
 use Time::Local qw(timegm);
 
@@ -28,7 +30,7 @@ sub msg_html {
        my ($ctx, $mime, $more, $smsg) = @_;
        my $hdr = $mime->header_obj;
        my $ibx = $ctx->{-inbox};
-       my $obfs_ibx = $ctx->{-obfs_ibx} = $ibx->{obfuscate} ? $ibx : undef;
+       $ctx->{-obfs_ibx} = $ibx->{obfuscate} ? $ibx : undef;
        my $tip = _msg_html_prepare($hdr, $ctx, $more, 0);
        my $end = 2;
        PublicInbox::WwwStream->response($ctx, 200, sub {
@@ -36,7 +38,7 @@ sub msg_html {
                if ($nr == 1) {
                        # $more cannot be true w/o $smsg being defined:
                        my $upfx = $more ? '../'.mid_escape($smsg->mid).'/' : '';
-                       $tip . multipart_text_as_html($mime, $upfx, $obfs_ibx) .
+                       $tip . multipart_text_as_html($mime, $upfx, $ctx) .
                                '</pre><hr>'
                } elsif ($more && @$more) {
                        ++$end;
@@ -81,15 +83,15 @@ sub msg_html_more {
        my $str = eval {
                my ($id, $prev, $smsg) = @$more;
                my $mid = $ctx->{mid};
-               $smsg = $ctx->{-inbox}->smsg_mime($smsg);
+               my $ibx = $ctx->{-inbox};
+               $smsg = $ibx->smsg_mime($smsg);
                my $next = $ctx->{srch}->next_by_mid($mid, \$id, \$prev);
                @$more = $next ? ($id, $prev, $next) : ();
                if ($smsg) {
                        my $mime = $smsg->{mime};
                        my $upfx = '../' . mid_escape($smsg->mid) . '/';
                        _msg_html_prepare($mime->header_obj, $ctx, $more, $nr) .
-                               multipart_text_as_html($mime, $upfx,
-                                                       $ctx->{-obfs_ibx}) .
+                               multipart_text_as_html($mime, $upfx, $ctx) .
                                '</pre><hr>'
                } else {
                        '';
@@ -218,11 +220,15 @@ sub index_entry {
        $rv .= $subj . "\n";
        $rv .= _th_index_lite($mid_raw, \$irt, $id, $ctx);
        my @tocc;
-       my $mime = delete $smsg->{mime}; # critical to memory use
+       my $ds = $smsg->ds; # for v1 non-Xapian/SQLite users
+       # deleting {mime} is critical to memory use,
+       # the rest of the fields saves about 400K as we iterate across 1K msgs
+       my ($mime) = delete @$smsg{qw(mime ds ts blob subject)};
+
        my $hdr = $mime->header_obj;
        my $from = _hdr_names_html($hdr, 'From');
        obfuscate_addrs($obfs_ibx, $from) if $obfs_ibx;
-       $rv .= "From: $from @ ".fmt_ts($smsg->ds)." UTC";
+       $rv .= "From: $from @ ".fmt_ts($ds)." UTC";
        my $upfx = $ctx->{-upfx};
        my $mhref = $upfx . mid_escape($mid_raw) . '/';
        $rv .= qq{ (<a\nhref="$mhref">permalink</a> / };
@@ -256,7 +262,8 @@ sub index_entry {
        $rv .= "\n";
 
        # scan through all parts, looking for displayable text
-       msg_iter($mime, sub { $rv .= add_text_body($mhref, $obfs_ibx, $_[0]) });
+       my $ibx = $ctx->{-inbox};
+       msg_iter($mime, sub { $rv .= add_text_body($mhref, $ctx, $_[0]) });
 
        # add the footer
        $rv .= "\n<a\nhref=#$id_m\nid=e$id>^</a> ".
@@ -310,7 +317,10 @@ sub _th_index_lite {
        my $nr_s = 0;
        my $siblings;
        if (my $smsg = $node->{smsg}) {
-               ($$irt) = (($smsg->{references} || '') =~ m/<([^>]+)>\z/);
+               # delete saves about 200KB on a 1K message thread
+               if (my $refs = delete $smsg->{references}) {
+                       ($$irt) = ($refs =~ m/<([^>]+)>\z/);
+               }
        }
        my $irt_map = $mapping->{$$irt} if defined $$irt;
        if (defined $irt_map) {
@@ -389,7 +399,7 @@ sub thread_index_entry {
 
 sub stream_thread ($$) {
        my ($rootset, $ctx) = @_;
-       my $inbox = $ctx->{-inbox};
+       my $ibx = $ctx->{-inbox};
        my @q = map { (0, $_) } @$rootset;
        my $level;
        my $smsg;
@@ -398,11 +408,11 @@ sub stream_thread ($$) {
                my $node = shift @q or next;
                my $cl = $level + 1;
                unshift @q, map { ($cl, $_) } @{$node->{children}};
-               $smsg = $inbox->smsg_mime($node->{smsg}) and last;
+               $smsg = $ibx->smsg_mime($node->{smsg}) and last;
        }
        return missing_thread($ctx) unless $smsg;
 
-       $ctx->{-obfs_ibx} = $inbox->{obfuscate} ? $inbox : undef;
+       $ctx->{-obfs_ibx} = $ibx->{obfuscate} ? $ibx : undef;
        $ctx->{-title_html} = ascii_html($smsg->subject);
        $ctx->{-html_tip} = thread_index_entry($ctx, $level, $smsg);
        $smsg = undef;
@@ -413,7 +423,7 @@ sub stream_thread ($$) {
                        my $node = shift @q or next;
                        my $cl = $level + 1;
                        unshift @q, map { ($cl, $_) } @{$node->{children}};
-                       if ($smsg = $inbox->smsg_mime($node->{smsg})) {
+                       if ($smsg = $ibx->smsg_mime($node->{smsg})) {
                                return thread_index_entry($ctx, $level, $smsg);
                        } else {
                                return ghost_index_entry($ctx, $level, $node);
@@ -452,8 +462,8 @@ sub thread_html {
        my $rootset = thread_results($ctx, $msgs);
 
        # reduce hash lookups in pre_thread->skel_dump
-       my $inbox = $ctx->{-inbox};
-       $ctx->{-obfs_ibx} = $inbox->{obfuscate} ? $inbox : undef;
+       my $ibx = $ctx->{-inbox};
+       $ctx->{-obfs_ibx} = $ibx->{obfuscate} ? $ibx : undef;
        walk_thread($rootset, $ctx, *pre_thread);
 
        $skel .= '</pre>';
@@ -462,7 +472,7 @@ sub thread_html {
        # flat display: lazy load the full message from smsg
        my $smsg;
        while (my $m = shift @$msgs) {
-               $smsg = $inbox->smsg_mime($m) and last;
+               $smsg = $ibx->smsg_mime($m) and last;
        }
        return missing_thread($ctx) unless $smsg;
        $ctx->{-title_html} = ascii_html($smsg->subject);
@@ -472,7 +482,7 @@ sub thread_html {
                return unless $msgs;
                $smsg = undef;
                while (my $m = shift @$msgs) {
-                       $smsg = $inbox->smsg_mime($m) and last;
+                       $smsg = $ibx->smsg_mime($m) and last;
                }
                return index_entry($smsg, $ctx, scalar @$msgs) if $smsg;
                $msgs = undef;
@@ -481,11 +491,11 @@ sub thread_html {
 }
 
 sub multipart_text_as_html {
-       my ($mime, $upfx, $obfs_ibx) = @_;
+       my ($mime, $upfx, $ctx) = @_;
        my $rv = "";
 
        # scan through all parts, looking for displayable text
-       msg_iter($mime, sub { $rv .= add_text_body($upfx, $obfs_ibx, $_[0]) });
+       msg_iter($mime, sub { $rv .= add_text_body($upfx, $ctx, $_[0]) });
        $rv;
 }
 
@@ -494,12 +504,12 @@ sub flush_quote {
 
        # show everything in the full version with anchor from
        # short version (see above)
-       my $rv = $l->linkify_1(join('', @$quot));
-       @$quot = ();
+       my $rv = $l->linkify_1($$quot);
 
        # we use a <span> here to allow users to specify their own
        # color for quoted text
        $rv = $l->linkify_2(ascii_html($rv));
+       $$quot = undef;
        $$s .= qq(<span\nclass="q">) . $rv . '</span>'
 }
 
@@ -538,49 +548,79 @@ sub attach_link ($$$$;$) {
 }
 
 sub add_text_body {
-       my ($upfx, $obfs_ibx, $p) = @_;
+       my ($upfx, $ctx, $p) = @_;
+       my $ibx = $ctx->{-inbox};
+       my $obfs_ibx = $ibx->{obfuscate} ? $ibx : undef;
        # $p - from msg_iter: [ Email::MIME, depth, @idx ]
-       my ($part, $depth) = @$p; # attachment @idx is unused
+       my ($part, $depth, @idx) = @$p;
        my $ct = $part->content_type || 'text/plain';
        my $fn = $part->filename;
        my ($s, $err) = msg_part_text($part, $ct);
 
        return attach_link($upfx, $ct, $p, $fn) unless defined $s;
 
-       my @lines = split(/^/m, $s);
+       # makes no difference to browsers, and don't screw up filename
+       # link generation in diffs with the extra '%0D'
+       $s =~ s/\r\n/\n/sg;
+
+       # always support diff-highlighting, but we can't linkify hunk
+       # headers for solver unless some coderepo are configured:
+       my $diff;
+       if ($s =~ /^(?:diff|---|\+{3}) /ms) {
+               # diffstat anchors do not link across attachments or messages:
+               $idx[0] = $upfx . $idx[0] if $upfx ne '';
+               $ctx->{-apfx} = join('/', @idx);
+               $ctx->{-anchors} = {}; # attr => filename
+               $ctx->{-diff} = $diff = [];
+               delete $ctx->{-long_path};
+               my $spfx;
+               if ($ibx->{-repo_objs}) {
+                       if (index($upfx, '//') >= 0) { # absolute URL (Atom feeds)
+                               $spfx = $upfx;
+                               $spfx =~ s!/([^/]*)/\z!/!;
+                       } else {
+                               my $n_slash = $upfx =~ tr!/!/!;
+                               if ($n_slash == 0) {
+                                       $spfx = '../';
+                               } elsif ($n_slash == 1) {
+                                       $spfx = '';
+                               } else { # nslash == 2
+                                       $spfx = '../../';
+                               }
+                       }
+               }
+               $ctx->{-spfx} = $spfx;
+       };
+
+       # some editors don't put trailing newlines at the end:
+       $s .= "\n" unless $s =~ /\n\z/s;
+
+       # split off quoted and unquoted blocks:
+       my @sections = split(/((?:^>[^\n]*\n)+)/sm, $s);
        $s = '';
        if (defined($fn) || $depth > 0 || $err) {
                # badly-encoded message with $err? tell the world about it!
                $s .= attach_link($upfx, $ct, $p, $fn, $err);
                $s .= "\n";
        }
-       my @quot;
        my $l = PublicInbox::Linkify->new;
-       foreach my $cur (@lines) {
-               if ($cur !~ /^>/) {
-                       # show the previously buffered quote inline
-                       flush_quote(\$s, $l, \@quot) if @quot;
-
-                       # regular line, OK
+       foreach my $cur (@sections) {
+               if ($cur =~ /\A>/) {
+                       flush_quote(\$s, $l, \$cur);
+               } elsif ($diff) {
+                       @$diff = split(/^/m, $cur);
+                       $cur = undef;
+                       flush_diff(\$s, $ctx, $l);
+               } else {
+                       # regular lines, OK
                        $l->linkify_1($cur);
                        $s .= $l->linkify_2(ascii_html($cur));
-               } else {
-                       push @quot, $cur;
+                       $cur = undef;
                }
        }
 
-       if (@quot) { # ugh, top posted
-               flush_quote(\$s, $l, \@quot);
-               obfuscate_addrs($obfs_ibx, $s) if $obfs_ibx;
-               $s;
-       } else {
-               obfuscate_addrs($obfs_ibx, $s) if $obfs_ibx;
-               if ($s =~ /\n\z/s) { # common, last line ends with a newline
-                       $s;
-               } else { # some editors don't do newlines...
-                       $s .= "\n";
-               }
-       }
+       obfuscate_addrs($obfs_ibx, $s) if $obfs_ibx;
+       $s;
 }
 
 sub _msg_html_prepare {
@@ -860,11 +900,6 @@ sub missing_thread {
        PublicInbox::ExtMsg::ext_msg($ctx);
 }
 
-sub _msg_date {
-       my ($hdr) = @_;
-       fmt_ts(msg_datestamp($hdr));
-}
-
 sub fmt_ts { POSIX::strftime('%Y-%m-%d %k:%M', gmtime($_[0])) }
 
 sub dedupe_subject {
@@ -1068,7 +1103,7 @@ sub dump_topics {
 
                my $mbox = qq(<a\nhref="$href/t.mbox.gz">mbox.gz</a>);
                my $atom = qq(<a\nhref="$href/t.atom">Atom</a>);
-               my $s = "<a\nhref=\"$href/T/$anchor\"><b>$top</b></a>\n" .
+               my $s = "<a\nhref=\"$href/T/$anchor\">$top</a>\n" .
                        " $ds UTC $n - $mbox / $atom\n";
                for (my $i = 0; $i < scalar(@ex); $i += 2) {
                        my $level = $ex[$i];