]> Sergey Matveev's repositories - public-inbox.git/blob - lib/PublicInbox/SearchMsg.pm
search: make search results more OO
[public-inbox.git] / lib / PublicInbox / SearchMsg.pm
1 # Copyright (C) 2015, all contributors <meta@public-inbox.org>
2 # License: GPLv3 or later (https://www.gnu.org/licenses/agpl-3.0.txt)
3 # based on notmuch, but with no concept of folders, files or flags
4 package PublicInbox::SearchMsg;
5 use strict;
6 use warnings;
7 use Search::Xapian;
8 use Email::Address qw//;
9 use POSIX qw//;
10 use Date::Parse qw/str2time/;
11 use PublicInbox::MID qw/mid_clean mid_compressed/;
12 our $PFX2TERM_RE = undef;
13
14 sub new {
15         my ($class, $mime) = @_;
16         my $doc = Search::Xapian::Document->new;
17         $doc->add_term(PublicInbox::Search::xpfx('type') . 'mail');
18
19         bless { type => 'mail', doc => $doc, mime => $mime }, $class;
20 }
21
22 sub wrap {
23         my ($class, $doc, $mid) = @_;
24         bless { doc => $doc, mime => undef, mid => $mid }, $class;
25 }
26
27 sub load_doc {
28         my ($class, $doc) = @_;
29         my ($mid, $subj, $from, $date) = split(/\n/, $doc->get_data);
30         bless {
31                 doc => $doc,
32                 mid => $mid,
33                 subject => $subj,
34                 date => $date,
35                 from_name => $from,
36         }, $class;
37 }
38
39 sub subject {
40         my ($self) = @_;
41         my $subj = $self->{subject};
42         return $subj if defined $subj;
43         $subj = $self->{mime}->header('Subject');
44         $subj = '' unless defined $subj;
45         $subj =~ tr/\n/ /;
46         $self->{subject} = $subj;
47 }
48
49 sub from {
50         my ($self) = @_;
51         my $from = $self->mime->header('From') || '';
52         my @from;
53
54         if ($from) {
55                 @from = Email::Address->parse($from);
56                 $self->{from} = $from[0];
57                 $from = $from[0]->name;
58         }
59         $from =~ tr/\n/ /;
60         $self->{from_name} = $from;
61         $self->{from};
62 }
63
64 sub from_name {
65         my ($self) = @_;
66         my $from_name = $self->{from_name};
67         return $from_name if defined $from_name;
68         $self->from;
69         $self->{from_name};
70 }
71
72 sub ts {
73         my ($self) = @_;
74         my $ts = $self->{ts};
75         return $ts if $ts;
76         $self->{date} = undef;
77         $self->date;
78         $self->{ts};
79 }
80
81 sub date {
82         my ($self) = @_;
83         my $date = $self->{date};
84         return $date if $date;
85         my $ts = eval { str2time($self->mime->header('Date')) } || 0;
86         $self->{ts} = $ts;
87         $self->{date} = POSIX::strftime('%Y-%m-%d %H:%M', gmtime($ts));
88 }
89
90 sub to_doc_data {
91         my ($self) = @_;
92
93         $self->mid . "\n" .
94         $self->subject . "\n" .
95         $self->from_name . "\n".
96         $self->date;
97 }
98
99 sub ensure_metadata {
100         my ($self) = @_;
101         my $doc = $self->{doc};
102         my $i = $doc->termlist_begin;
103         my $end = $doc->termlist_end;
104
105         unless (defined $PFX2TERM_RE) {
106                 my $or = join('|', keys %PublicInbox::Search::PFX2TERM_RMAP);
107                 $PFX2TERM_RE = qr/\A($or)/;
108         }
109
110         for (; $i != $end; $i->inc) {
111                 my $val = $i->get_termname;
112
113                 if ($val =~ s/$PFX2TERM_RE//o) {
114                         my $field = $PublicInbox::Search::PFX2TERM_RMAP{$1};
115                         if ($field eq 'references') {
116                                 my $refs = $self->{references} ||= [];
117                                 push @$refs, $val;
118                         } else {
119                                 $self->{$field} = $val;
120                         }
121                 }
122         }
123 }
124
125 sub mid {
126         my ($self, $mid) = @_;
127
128         if (defined $mid) {
129             $self->{mid} = $mid;
130         } else {
131             $self->{mid} ||= $self->_extract_mid;
132         }
133 }
134
135 sub _extract_mid {
136         my ($self) = @_;
137
138         my $mid = $self->mime->header('Message-ID');
139         $mid ? mid_compressed(mid_clean($mid)) : $mid;
140 }
141
142 sub mime {
143         my ($self, $mime) = @_;
144         if (defined $mime) {
145                 $self->{mime} = $mime;
146         } else {
147                 # TODO load from git
148                 $self->{mime};
149         }
150 }
151
152 sub doc_id {
153         my ($self, $doc_id) = @_;
154         if (defined $doc_id) {
155                 $self->{doc_id} = $doc_id;
156         } else {
157                 # TODO load from xapian
158                 $self->{doc_id};
159         }
160 }
161
162 sub thread_id {
163         my ($self) = @_;
164         my $tid = $self->{thread};
165         return $tid if defined $tid;
166         $self->ensure_metadata;
167         $self->{thread};
168 }
169
170 sub path {
171         my ($self) = @_;
172         my $path = $self->{path};
173         return $path if defined $path;
174         $self->ensure_metadata;
175         $self->{path};
176 }
177
178 1;