Compute lucene-style scores for our hits.
[beagle.git] / Filters / FilterMail.cs
blob92e14f7a1d055f10a7504817e890af14389567e7
2 //
3 // FilterMail.cs
4 //
5 // Copyright (C) 2004-2005 Novell, Inc.
6 //
7 //
8 //
9 // Permission is hereby granted, free of charge, to any person obtaining a
10 // copy of this software and associated documentation files (the "Software"),
11 // to deal in the Software without restriction, including without limitation
12 // the rights to use, copy, modify, merge, publish, distribute, sublicense,
13 // and/or sell copies of the Software, and to permit persons to whom the
14 // Software is furnished to do so, subject to the following conditions:
16 // The above copyright notice and this permission notice shall be included in
17 // all copies or substantial portions of the Software.
19 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
20 // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
21 // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
22 // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
23 // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
24 // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
25 // DEALINGS IN THE SOFTWARE.
28 using System;
29 using System.Collections;
30 using System.IO;
32 using Mono.Posix;
34 using GMime;
36 using Beagle;
37 using Beagle.Daemon;
39 namespace Beagle.Filters {
41 public class FilterMail : Beagle.Daemon.Filter, IDisposable {
43 private static bool gmime_initialized = false;
45 private GMime.Stream stream;
46 private GMime.Parser parser;
47 private GMime.Message message;
48 private PartHandler handler;
50 public FilterMail ()
52 AddSupportedFlavor (FilterFlavor.NewFromMimeType ("message/rfc822"));
55 protected override void DoOpen (FileInfo info)
57 if (!gmime_initialized) {
58 try {
59 GMime.Global.Init ();
60 gmime_initialized = true;
61 } catch {
62 Error ();
63 return;
67 int mail_fd = Syscall.open (info.FullName, OpenFlags.O_RDONLY);
69 if (mail_fd == -1)
70 throw new IOException (String.Format ("Unable to read {0} for parsing mail", info.FullName));
72 this.stream = new GMime.StreamFs (mail_fd);
73 this.parser = new GMime.Parser (stream);
76 protected override void DoPullProperties ()
78 this.message = this.parser.ConstructMessage ();
80 string subject = GMime.Utils.HeaderDecodePhrase (this.message.Subject);
81 AddProperty (Property.New ("dc:title", subject));
83 AddProperty (Property.NewDate ("fixme:date", message.Date));
85 GMime.InternetAddressList addrs;
86 addrs = this.message.GetRecipients (GMime.Message.RecipientType.To);
87 foreach (GMime.InternetAddress ia in addrs) {
88 AddProperty (Property.NewKeyword ("fixme:to", ia.ToString (false)));
89 AddProperty (Property.NewKeyword ("fixme:to_address", ia.Addr));
90 AddProperty (Property.New ("fixme:to_name", ia.Name));
92 addrs.Dispose ();
94 addrs = this.message.GetRecipients (GMime.Message.RecipientType.Cc);
95 foreach (GMime.InternetAddress ia in addrs) {
96 AddProperty (Property.NewKeyword ("fixme:cc", ia.ToString (false)));
97 AddProperty (Property.NewKeyword ("fixme:cc_address", ia.Addr));
98 AddProperty (Property.New ("fixme:cc_name", ia.Name));
100 addrs.Dispose ();
102 addrs = GMime.InternetAddressList.ParseString (GMime.Utils.HeaderDecodePhrase (this.message.Sender));
103 foreach (GMime.InternetAddress ia in addrs) {
104 AddProperty (Property.NewKeyword ("fixme:from", ia.ToString (false)));
105 AddProperty (Property.NewKeyword ("fixme:from_address", ia.Addr));
106 AddProperty (Property.New ("fixme:from_name", ia.Name));
108 addrs.Dispose ();
110 if (this.message.MimePart is GMime.Multipart || this.message.MimePart is GMime.MessagePart)
111 AddProperty (Property.NewFlag ("fixme:hasAttachments"));
113 string list_id = this.message.GetHeader ("List-Id");
115 if (list_id != null) {
116 // FIXME: Might need some additional parsing.
117 AddProperty (Property.NewKeyword ("fixme:mlist", GMime.Utils.HeaderDecodePhrase (list_id)));
121 protected override void DoPullSetup ()
123 this.handler = new PartHandler (this);
124 using (GMime.Object mime_part = this.message.MimePart)
125 this.handler.OnEachPart (mime_part);
127 AddChildIndexables (this.handler.ChildIndexables);
130 protected override void DoPull ()
132 if (handler.Reader == null) {
133 Finished ();
134 return;
137 string l = handler.Reader.ReadLine ();
139 if (l != null)
140 AppendText (l);
141 else
142 Finished ();
145 protected override void DoClose ()
147 Dispose ();
150 public void Dispose ()
152 if (this.message != null)
153 this.message.Dispose ();
155 if (this.stream != null) {
156 this.stream.Close ();
157 this.stream.Dispose ();
160 if (this.parser != null)
161 this.parser.Dispose ();
164 private class PartHandler {
165 private Beagle.Daemon.Filter filter;
166 private int count = 0; // parts handled so far
167 private int depth = 0; // part recursion depth
168 private ArrayList child_indexables = new ArrayList ();
169 private TextReader reader;
171 public PartHandler (Beagle.Daemon.Filter filter)
173 this.filter = filter;
176 public void OnEachPart (GMime.Object part)
178 //for (int i = 0; i < depth; i++)
179 // Console.Write (" ");
180 //Console.WriteLine ("Content-Type: {0}", part.ContentType);
182 ++depth;
184 if (part is GMime.MessagePart) {
185 GMime.MessagePart msg_part = (GMime.MessagePart) part;
187 using (GMime.Message message = msg_part.Message) {
188 using (GMime.Object subpart = message.MimePart)
189 this.OnEachPart (subpart);
191 } else if (part is GMime.Multipart) {
192 GMime.Multipart multipart = (GMime.Multipart) part;
194 int num_parts = multipart.Number;
195 for (int i = 0; i < num_parts; i++) {
196 using (GMime.Object subpart = multipart.GetPart (i))
197 this.OnEachPart (subpart);
199 } else if (part is GMime.Part) {
200 MemoryStream stream = null;
202 using (GMime.DataWrapper content_obj = ((GMime.Part) part).ContentObject) {
203 stream = new MemoryStream ();
204 content_obj.WriteToStream (stream);
205 stream.Seek (0, SeekOrigin.Begin);
208 // If this is the only part and it's plain text, we
209 // want to just attach it to our filter instead of
210 // creating a child indexable for it.
211 bool no_child_needed = false;
213 if (this.depth == 1 && this.count == 0) {
214 if (part.ContentType.ToString ().ToLower () == "text/plain") {
215 no_child_needed = true;
217 this.reader = new StreamReader (stream);
221 if (!no_child_needed) {
222 string sub_uri = this.filter.Uri.ToString () + "#" + this.count;
223 Indexable child = new Indexable (new Uri (sub_uri));
225 child.Type = "MailMessage";
226 child.MimeType = part.ContentType.ToString ();
227 child.CacheContent = false;
229 child.AddProperty (Property.NewKeyword ("fixme:attachment_title", ((GMime.Part)part).Filename));
231 if (part.ContentType.Type.ToLower () == "text")
232 child.SetTextReader (new StreamReader (stream));
233 else
234 child.SetBinaryStream (stream);
236 this.child_indexables.Add (child);
239 this.count++;
240 } else {
241 throw new Exception (String.Format ("Unknown part type: {0}", part.GetType ()));
244 --depth;
247 public ICollection ChildIndexables {
248 get { return this.child_indexables; }
251 public TextReader Reader {
252 get { return this.reader; }