Remove debug spew
[beagle.git] / beagled / EvolutionMailDriver / EvolutionMailIndexableGenerator.cs
blob8f048c6454db0731e014cf75b3da82faa1822bf6
2 //
3 // EvolutionMailIndexableGenerator.cs
4 //
5 // Copyright (C) 2004 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;
31 using System.Runtime.Serialization.Formatters.Binary;
32 using System.Threading;
33 using System.Xml;
35 using Beagle.Util;
36 using Beagle.Daemon;
38 using Camel = Beagle.Util.Camel;
40 namespace Beagle.Daemon.EvolutionMailDriver {
42 public abstract class EvolutionMailIndexableGenerator : IIndexableGenerator {
44 protected static bool Debug = false;
46 private static bool gmime_initialized = false;
48 private static ArrayList excludes = new ArrayList ();
50 static EvolutionMailIndexableGenerator () {
51 foreach (ExcludeItem exclude in Conf.Indexing.Excludes)
52 if (exclude.Type == ExcludeType.MailFolder)
53 excludes.Add (exclude);
56 protected EvolutionMailQueryable queryable;
58 protected string account_name, folder_name;
59 protected int count, indexed_count;
61 protected EvolutionMailIndexableGenerator (EvolutionMailQueryable queryable)
63 this.queryable = queryable;
66 protected abstract string GetFolderName (FileSystemInfo info);
67 protected abstract bool Setup ();
68 protected abstract FileInfo CrawlFile { get; }
69 public abstract string GetTarget ();
70 public abstract bool HasNextIndexable ();
71 public abstract Indexable GetNextIndexable ();
72 public abstract void Checkpoint ();
75 protected double progress_percent;
76 public double ProgressPercent {
77 get { return progress_percent; }
80 public void PostFlushHook ()
82 Checkpoint ();
85 protected static void InitializeGMime ()
87 if (!gmime_initialized) {
88 GMime.Global.Init ();
89 gmime_initialized = true;
93 protected bool IsSpamFolder (string name)
95 if (name.ToLower () == "spam" || name.ToLower () == "junk")
96 return true;
97 else
98 return false;
101 protected bool IgnoreFolder (string path)
103 // FIXME: Use System.IO.Path
104 foreach (ExcludeItem exclude in excludes) {
105 if (exclude.IsMatch (path))
106 return true;
108 return false;
111 protected void CrawlFinished ()
113 this.queryable.FileAttributesStore.AttachLastWriteTime (this.CrawlFile.FullName, File.GetLastWriteTimeUtc (this.CrawlFile.FullName));
114 this.queryable.RemoveGenerator (this);
117 public string StatusName {
118 get { return this.CrawlFile.FullName; }
121 public override bool Equals (object o)
123 EvolutionMailIndexableGenerator generator = o as EvolutionMailIndexableGenerator;
125 if (generator == null)
126 return false;
128 if (Object.ReferenceEquals (this, generator))
129 return true;
131 if (this.CrawlFile.FullName == generator.CrawlFile.FullName)
132 return true;
133 else
134 return false;
137 public override int GetHashCode ()
139 return this.CrawlFile.FullName.GetHashCode ();
143 public class EvolutionMailIndexableGeneratorMbox : EvolutionMailIndexableGenerator {
144 private FileInfo mbox_info;
145 private int mbox_fd = -1;
146 private GMime.StreamFs mbox_stream;
147 private GMime.Parser mbox_parser;
148 private long file_size;
150 public EvolutionMailIndexableGeneratorMbox (EvolutionMailQueryable queryable, FileInfo mbox_info) : base (queryable)
152 this.mbox_info = mbox_info;
155 protected override string GetFolderName (FileSystemInfo info)
157 FileInfo file_info = (FileInfo) info;
158 DirectoryInfo di;
159 string folder_name = "";
161 di = file_info.Directory;
162 while (di != null) {
163 // Evo uses ".sbd" as the extension on a folder
164 if (di.Extension == ".sbd")
165 folder_name = Path.Combine (Path.GetFileNameWithoutExtension (di.Name), folder_name);
166 else
167 break;
169 di = di.Parent;
172 return Path.Combine (folder_name, file_info.Name);
175 protected override bool Setup ()
177 this.account_name = "local@local";
178 this.folder_name = this.GetFolderName (this.mbox_info);
180 if (this.IsSpamFolder (this.folder_name))
181 return false;
183 if (this.IgnoreFolder (this.mbox_info.FullName))
184 return false;
186 return true;
189 private long MboxLastOffset {
190 get {
191 string offset_str = this.queryable.ReadDataLine ("offset-" + this.folder_name.Replace ('/', '-'));
192 long offset = Convert.ToInt64 (offset_str);
194 return offset;
197 set {
198 this.queryable.WriteDataLine ("offset-" + this.folder_name.Replace ('/', '-'), value.ToString ());
202 public override bool HasNextIndexable ()
204 if (this.account_name == null) {
205 if (!Setup ()) {
206 this.queryable.RemoveGenerator (this);
207 return false;
211 if (this.mbox_fd < 0) {
212 Logger.Log.Debug ("Opening mbox {0}", this.mbox_info.Name);
214 try {
215 InitializeGMime ();
216 } catch (Exception e) {
217 Logger.Log.Warn (e, "Caught exception trying to initalize gmime:");
218 return false;
221 this.mbox_fd = Mono.Unix.Native.Syscall.open (this.mbox_info.FullName, Mono.Unix.Native.OpenFlags.O_RDONLY);
223 if (this.mbox_fd < 0) {
224 Log.Error ("Unable to open {0}: {1}", this.mbox_info.FullName, Mono.Unix.Native.Stdlib.strerror (Mono.Unix.Native.Stdlib.GetLastError ()));
225 return false;
228 this.mbox_stream = new GMime.StreamFs (this.mbox_fd);
229 this.mbox_stream.Seek ((int) this.MboxLastOffset);
230 this.mbox_parser = new GMime.Parser (this.mbox_stream);
231 this.mbox_parser.ScanFrom = true;
233 FileInfo info = new FileInfo (this.mbox_info.FullName);
234 this.file_size = info.Length;
237 if (this.mbox_parser.Eos ()) {
238 long offset = this.mbox_parser.FromOffset;
240 this.mbox_stream.Close ();
242 this.mbox_fd = -1;
243 this.mbox_stream.Dispose ();
244 this.mbox_stream = null;
245 this.mbox_parser.Dispose ();
246 this.mbox_parser = null;
248 Logger.Log.Debug ("{0}: Finished indexing {1} messages", this.folder_name, this.indexed_count);
250 if (offset >= 0)
251 this.MboxLastOffset = offset;
252 this.CrawlFinished ();
254 return false;
255 } else
256 return true;
259 public override Indexable GetNextIndexable ()
261 using (GMime.Message message = this.mbox_parser.ConstructMessage ()) {
262 // Work around what I think is a bug in GMime: If you
263 // have a zero-byte file or seek to the end of a
264 // file, parser.Eos () will return true until it
265 // actually tries to read something off the wire.
266 // Since parser.ConstructMessage() always returns a
267 // message (which may also be a bug), we'll often get
268 // one empty message which we need to deal with here.
270 // Check if its empty by seeing if the Headers
271 // property is null or empty.
272 if (message == null || message.Headers == null || message.Headers == "")
273 return null;
275 ++this.count;
277 string x_evolution = message.GetHeader ("X-Evolution");
278 if (x_evolution == null || x_evolution == "") {
279 Logger.Log.Info ("{0}: Message at offset {1} has no X-Evolution header!",
280 this.folder_name, this.mbox_parser.FromOffset);
281 return null;
284 // This extracts the UID and flags from the X-Evolution header.
285 // It may also contain user-defined flags and tags, but we don't
286 // support those right now.
287 int separator_idx = x_evolution.IndexOf ('-');
289 string uid_str = x_evolution.Substring (0, separator_idx);
290 string uid = Convert.ToUInt32 (uid_str, 16).ToString (); // ugh.
291 uint flags = Convert.ToUInt32 (x_evolution.Substring (separator_idx + 1, 4), 16);
293 Indexable indexable = this.GMimeMessageToIndexable (uid, message, flags);
295 if (Debug) {
296 Logger.Log.Debug ("Constructed message {0} with uid {1}, flags {2}. Indexable {3} null",
297 this.count, uid, flags, indexable == null ? "" : "not");
300 if (indexable == null)
301 return null;
303 ++this.indexed_count;
305 return indexable;
309 private static bool CheckFlags (uint flags, Camel.CamelFlags test)
311 return (flags & (uint) test) == (uint) test;
314 private Indexable GMimeMessageToIndexable (string uid, GMime.Message message, uint flags)
316 // Don't index messages flagged as junk
317 if (CheckFlags (flags, Camel.CamelFlags.Junk))
318 return null;
320 System.Uri uri = EvolutionMailQueryable.EmailUri (this.account_name, this.folder_name, uid);
321 Indexable indexable = new Indexable (uri);
323 indexable.Timestamp = message.Date.ToUniversalTime ();
324 indexable.HitType = "MailMessage";
325 indexable.MimeType = "message/rfc822";
326 indexable.CacheContent = false;
328 indexable.AddProperty (Property.NewUnsearched ("fixme:client", "evolution"));
329 indexable.AddProperty (Property.NewUnsearched ("fixme:account", "Local"));
330 indexable.AddProperty (Property.NewUnsearched ("fixme:folder", this.folder_name));
332 GMime.InternetAddressList addrs;
334 addrs = message.GetRecipients (GMime.Message.RecipientType.To);
335 foreach (GMime.InternetAddress ia in addrs) {
336 if (this.folder_name == "Sent" && ia.AddressType != GMime.InternetAddressType.Group)
337 indexable.AddProperty (Property.NewUnsearched ("fixme:sentTo", ia.Addr));
339 addrs.Dispose ();
341 addrs = message.GetRecipients (GMime.Message.RecipientType.Cc);
342 foreach (GMime.InternetAddress ia in addrs) {
343 if (this.folder_name == "Sent" && ia.AddressType != GMime.InternetAddressType.Group)
344 indexable.AddProperty (Property.NewUnsearched ("fixme:sentTo", ia.Addr));
346 addrs.Dispose ();
348 addrs = GMime.InternetAddressList.ParseString (GMime.Utils.HeaderDecodePhrase (message.Sender));
349 foreach (GMime.InternetAddress ia in addrs) {
350 if (this.folder_name != "Sent" && ia.AddressType != GMime.InternetAddressType.Group)
351 indexable.AddProperty (Property.NewUnsearched ("fixme:gotFrom", ia.Addr));
353 addrs.Dispose ();
355 if (this.folder_name == "Sent")
356 indexable.AddProperty (Property.NewFlag ("fixme:isSent"));
358 Property flag_prop = Property.NewUnsearched ("fixme:flags", flags);
359 flag_prop.IsMutable = true;
360 indexable.AddProperty (flag_prop);
362 if (CheckFlags (flags, Camel.CamelFlags.Answered))
363 indexable.AddProperty (Property.NewFlag ("fixme:isAnswered"));
365 if (CheckFlags (flags, Camel.CamelFlags.Deleted))
366 indexable.AddProperty (Property.NewFlag ("fixme:isDeleted"));
368 if (CheckFlags (flags, Camel.CamelFlags.Draft))
369 indexable.AddProperty (Property.NewFlag ("fixme:isDraft"));
371 if (CheckFlags (flags, Camel.CamelFlags.Flagged))
372 indexable.AddProperty (Property.NewFlag ("fixme:isFlagged"));
374 if (CheckFlags (flags, Camel.CamelFlags.Seen))
375 indexable.AddProperty (Property.NewFlag ("fixme:isSeen"));
377 if (CheckFlags (flags, Camel.CamelFlags.AnsweredAll))
378 indexable.AddProperty (Property.NewFlag ("fixme:isAnsweredAll"));
380 indexable.SetBinaryStream (message.Stream);
382 return indexable;
385 public override void Checkpoint ()
387 long offset = -1;
389 if (this.mbox_parser != null) {
390 offset = this.mbox_parser.FromOffset;
392 if (offset >= 0)
393 this.MboxLastOffset = offset;
396 string progress = "";
397 if (this.file_size > 0 && offset > 0) {
398 progress = String.Format (" ({0}/{1} bytes {2:###.0}%)",
399 offset, this.file_size, 100.0 * offset / this.file_size);
401 this.progress_percent = 100.0 * offset / this.file_size;
404 Logger.Log.Debug ("{0}: indexed {1} messages{2}",
405 this.folder_name, this.indexed_count, progress);
408 public override string GetTarget ()
410 return "mbox-file:" + mbox_info.FullName;
413 protected override FileInfo CrawlFile {
414 get { return this.mbox_info; }
418 public class EvolutionMailIndexableGeneratorImap : EvolutionMailIndexableGenerator {
419 private enum ImapBackendType {
420 Imap,
421 Imap4
424 private FileInfo summary_info;
425 private string imap_name;
426 private ImapBackendType backend_type;
427 private Camel.Summary summary;
428 private IEnumerator summary_enumerator;
429 private ICollection accounts;
430 private string folder_cache_name;
431 private Hashtable mapping;
432 private ArrayList deleted_list;
433 private bool delete_mode;
434 private int delete_count;
436 public EvolutionMailIndexableGeneratorImap (EvolutionMailQueryable queryable, FileInfo summary_info) : base (queryable)
438 this.summary_info = summary_info;
441 protected override string GetFolderName (FileSystemInfo info)
443 DirectoryInfo dir_info = (DirectoryInfo) info;
444 string folder_name = "";
446 while (dir_info != null) {
447 folder_name = Path.Combine (dir_info.Name, folder_name);
449 dir_info = dir_info.Parent;
451 if (dir_info.Name != "subfolders")
452 break;
453 else
454 dir_info = dir_info.Parent;
457 return folder_name;
460 protected override bool Setup ()
462 string dir_name = summary_info.DirectoryName;
463 int imap_start_idx;
465 int idx = dir_name.IndexOf (".evolution/mail/imap4/");
467 if (idx >= 0) {
468 this.backend_type = ImapBackendType.Imap4;
469 imap_start_idx = idx + 22;
470 } else {
471 this.backend_type = ImapBackendType.Imap;
472 imap_start_idx = dir_name.IndexOf (".evolution/mail/imap/") + 21;
475 string imap_start = dir_name.Substring (imap_start_idx);
476 this.imap_name = imap_start.Substring (0, imap_start.IndexOf ('/'));
478 try {
479 this.accounts = (ICollection) GConfThreadHelper.Get ("/apps/evolution/mail/accounts");
480 } catch (Exception ex) {
481 Logger.Log.Warn ("Caught exception in Setup(): " + ex.Message);
482 Logger.Log.Warn ("There are no configured evolution accounts, ignoring {0}", this.imap_name);
483 return false;
486 // This should only happen if we shut down while waiting for the GConf results to come back.
487 if (this.accounts == null)
488 return false;
490 foreach (string xml in this.accounts) {
491 XmlDocument xmlDoc = new XmlDocument ();
493 xmlDoc.LoadXml (xml);
495 XmlNode account = xmlDoc.SelectSingleNode ("//account");
497 if (account == null)
498 continue;
500 string uid = null;
502 foreach (XmlAttribute attr in account.Attributes) {
503 if (attr.Name == "uid") {
504 uid = attr.InnerText;
505 break;
509 if (uid == null)
510 continue;
512 XmlNode imap_url_node = xmlDoc.SelectSingleNode ("//source/url");
514 if (imap_url_node == null)
515 continue;
517 string imap_url = imap_url_node.InnerText;
518 // If there is a semicolon in the username part of the URL, it
519 // indicates that there's an auth scheme there. We don't care
520 // about that, so remove it.
521 int user_end = imap_url.IndexOf ('@');
522 int semicolon = imap_url.IndexOf (';', 0, user_end + 1);
524 if (semicolon != -1)
525 imap_url = imap_url.Substring (0, semicolon) + imap_url.Substring (user_end);
527 // Escape backslashes, which frequently appear when using IMAP against Exchange servers
528 this.imap_name = this.imap_name.Replace ("\\", "%5c");
530 // Escape out additional @s in the name. I hate the class libs so much.
531 int lastIdx = this.imap_name.LastIndexOf ('@');
532 if (this.imap_name.IndexOf ('@') != lastIdx) {
533 string toEscape = this.imap_name.Substring (0, lastIdx);
534 this.imap_name = toEscape.Replace ("@", "%40") + this.imap_name.Substring (lastIdx);
537 string backend_url_prefix;
538 if (this.backend_type == ImapBackendType.Imap)
539 backend_url_prefix = "imap";
540 else
541 backend_url_prefix = "imap4";
543 if (imap_url.StartsWith (backend_url_prefix + "://" + this.imap_name + "/")) {
544 this.account_name = uid;
545 break;
549 if (this.account_name == null) {
550 Logger.Log.Info ("Unable to determine account name for {0}", this.imap_name);
551 return false;
554 // Need to check the directory on disk to see if it's a junk/spam folder,
555 // since the folder name will be "foo/spam" and not match the check below.
556 DirectoryInfo dir_info = new DirectoryInfo (dir_name);
557 if (this.IsSpamFolder (dir_info.Name))
558 return false;
560 // Check if the folder is listed in the configuration as to be excluded from indexing
561 if (this.IgnoreFolder (dir_info.FullName))
562 return false;
564 this.folder_name = GetFolderName (new DirectoryInfo (dir_name));
566 return true;
569 private string FolderCacheName {
570 get {
571 if (this.account_name == null || this.folder_name == null)
572 return null;
574 if (this.folder_cache_name == null)
575 this.folder_cache_name = "status-" + this.account_name + "-" + this.folder_name.Replace ('/', '-');
577 return this.folder_cache_name;
581 private bool LoadCache ()
583 Stream cacheStream;
584 BinaryFormatter formatter;
586 if (this.FolderCacheName == null) {
587 this.mapping = new Hashtable ();
588 return false;
591 try {
592 cacheStream = this.queryable.ReadDataStream (this.FolderCacheName);
593 formatter = new BinaryFormatter ();
594 this.mapping = formatter.Deserialize (cacheStream) as Hashtable;
595 cacheStream.Close ();
596 Logger.Log.Debug ("Successfully loaded previous crawled data from disk: {0}", this.FolderCacheName);
598 return true;
599 } catch {
600 this.mapping = new Hashtable ();
602 return false;
606 private void SaveCache ()
608 Stream cacheStream;
609 BinaryFormatter formatter;
611 if (this.FolderCacheName == null)
612 return;
614 cacheStream = this.queryable.WriteDataStream (this.FolderCacheName);
615 formatter = new BinaryFormatter ();
616 formatter.Serialize (cacheStream, mapping);
617 cacheStream.Close ();
620 public override bool HasNextIndexable ()
622 if (this.account_name == null) {
623 if (!Setup ()) {
624 this.queryable.RemoveGenerator (this);
625 return false;
629 if (this.mapping == null) {
630 bool cache_loaded = this.LoadCache ();
632 this.deleted_list = new ArrayList (this.mapping.Keys);
633 this.deleted_list.Sort ();
634 Logger.Log.Debug ("Deleted list starting at {0} for {1}", this.deleted_list.Count, this.folder_name);
636 // Check to see if we even need to bother walking the summary
637 if (cache_loaded && this.queryable.FileAttributesStore.IsUpToDate (this.CrawlFile.FullName)) {
638 Logger.Log.Debug ("{0}: summary has not been updated; crawl unncessary", this.folder_name);
639 this.queryable.RemoveGenerator (this);
640 return false;
644 if (this.summary == null) {
645 try {
646 if (this.backend_type == ImapBackendType.Imap)
647 this.summary = Camel.Summary.LoadImapSummary (this.summary_info.FullName);
648 else
649 this.summary = Camel.Summary.LoadImap4Summary (this.summary_info.FullName);
650 } catch (Exception e) {
651 Logger.Log.Warn (e, "Unable to index {0}:", this.folder_name);
652 this.queryable.RemoveGenerator (this);
653 return false;
657 if (this.summary_enumerator == null)
658 this.summary_enumerator = this.summary.GetEnumerator ();
660 if (this.summary_enumerator.MoveNext ())
661 return true;
663 this.delete_mode = true;
665 if (this.deleted_list.Count > 0)
666 return true;
668 string progress = "";
669 if (this.count > 0 && this.summary.header.count > 0) {
670 progress = String.Format ("({0}/{1} {2:###.0}%)",
671 this.count,
672 this.summary.header.count,
673 100.0 * this.count / this.summary.header.count);
676 Logger.Log.Debug ("{0}: Finished indexing {1} messages {2}, {3} messages deleted", this.folder_name, this.indexed_count, progress, this.delete_count);
678 this.SaveCache ();
679 this.CrawlFinished ();
681 return false;
684 // Kind of nasty, but we need the function.
685 [System.Runtime.InteropServices.DllImport("libglib-2.0.so.0")]
686 static extern int g_str_hash (string str);
688 // Stolen from deep within e-d-s's camel-data-cache.c Very evil.
689 private const int CAMEL_DATA_CACHE_MASK = ((1 << 6) - 1);
691 public override Indexable GetNextIndexable ()
693 Indexable indexable = null;
695 // No more new messages to index, so start on the removals.
696 if (this.delete_mode) {
697 string uid = (string) this.deleted_list [0];
698 Uri uri = EvolutionMailQueryable.EmailUri (this.account_name, this.folder_name, uid);
700 indexable = new Indexable (IndexableType.Remove, uri);
702 this.deleted_list.RemoveAt (0);
703 this.mapping.Remove (uid);
705 this.delete_count++;
707 return indexable;
710 Camel.MessageInfo mi = (Camel.MessageInfo) this.summary_enumerator.Current;
712 ++this.count;
714 if (Debug) {
715 Logger.Log.Debug ("Constructed message {0} with uid {1}, flags {2}.",
716 this.count, mi.uid, mi.flags);
719 // Try to load the cached message data off disk
720 object flags = this.mapping[mi.uid];
722 if (flags == null) {
723 // New, previously unseen message
724 string msg_file;
726 if (this.backend_type == ImapBackendType.Imap)
727 msg_file = Path.Combine (summary_info.DirectoryName, mi.uid + ".");
728 else {
729 // This is taken from e-d-s's camel-data-cache.c. No doubt
730 // NotZed would scream bloody murder if he saw this here.
731 int hash = (g_str_hash (mi.uid) >> 5) & CAMEL_DATA_CACHE_MASK;
732 string cache_path = String.Format ("cache/{0:x}/{1}", hash, mi.uid);
733 msg_file = Path.Combine (summary_info.DirectoryName, cache_path);
736 indexable = this.CamelMessageToIndexable (mi, msg_file);
738 if (Debug)
739 Logger.Log.Debug ("Unseen message, indexable {0} null", indexable == null ? "" : "not");
741 this.mapping[mi.uid] = mi.flags;
743 if (indexable != null)
744 ++this.indexed_count;
745 } else if ((uint) flags != mi.flags) {
746 // Previously seen message, but flags have changed.
747 Uri uri = CamelMessageUri (mi);
748 indexable = new Indexable (uri);
749 indexable.Type = IndexableType.PropertyChange;
751 Property flag_prop = Property.NewUnsearched ("fixme:flags", mi.flags);
752 flag_prop.IsMutable = true;
753 indexable.AddProperty (flag_prop);
755 if (Debug)
756 Logger.Log.Debug ("Previously seen message, flags changed: {0} -> {1}", flags, mi.flags);
758 ++this.indexed_count;
759 } else {
760 if (Debug)
761 Logger.Log.Debug ("Previously seen message, unchanged.");
764 if (flags != null)
765 this.deleted_list.Remove (mi.uid);
767 return indexable;
770 private Uri CamelMessageUri (Camel.MessageInfo message_info)
772 return EvolutionMailQueryable.EmailUri (this.account_name, this.folder_name, message_info.uid);
775 private Indexable CamelMessageToIndexable (Camel.MessageInfo messageInfo, string msg_file)
777 // Don't index messages flagged as junk
778 if (messageInfo.IsJunk)
779 return null;
781 // Many properties will be set by the filter when
782 // processing the cached data, if it's there. So
783 // don't set a number of properties in that case.
784 bool have_content = File.Exists (msg_file);
786 Uri uri = CamelMessageUri (messageInfo);
787 Indexable indexable = new Indexable (uri);
789 indexable.Timestamp = messageInfo.SentDate;
790 indexable.MimeType = "message/rfc822";
791 indexable.HitType = "MailMessage";
793 indexable.AddProperty (Property.NewUnsearched ("fixme:account", this.imap_name));
794 indexable.AddProperty (Property.NewUnsearched ("fixme:folder", this.folder_name));
795 indexable.AddProperty (Property.NewUnsearched ("fixme:client", "evolution"));
797 if (!have_content) {
798 indexable.AddProperty (Property.New ("dc:title", GMime.Utils.HeaderDecodePhrase (messageInfo.subject)));
799 indexable.AddProperty (Property.NewDate ("fixme:date", messageInfo.SentDate));
802 GMime.InternetAddressList addrs;
803 addrs = GMime.InternetAddressList.ParseString (messageInfo.to);
804 foreach (GMime.InternetAddress ia in addrs) {
805 if (!have_content) {
806 indexable.AddProperty (Property.NewUnsearched ("fixme:to", ia.ToString (false)));
807 if (ia.AddressType != GMime.InternetAddressType.Group)
808 indexable.AddProperty (Property.New ("fixme:to_address", ia.Addr));
810 indexable.AddProperty (Property.New ("fixme:to_name", ia.Name));
813 if (this.folder_name == "Sent" && ia.AddressType != GMime.InternetAddressType.Group)
814 indexable.AddProperty (Property.NewUnsearched ("fixme:sentTo", ia.Addr));
816 addrs.Dispose ();
818 addrs = GMime.InternetAddressList.ParseString (messageInfo.cc);
819 foreach (GMime.InternetAddress ia in addrs) {
820 if (!have_content) {
821 indexable.AddProperty (Property.NewUnsearched ("fixme:cc", ia.ToString (false)));
822 if (ia.AddressType != GMime.InternetAddressType.Group)
823 indexable.AddProperty (Property.New ("fixme:cc_address", ia.Addr));
825 indexable.AddProperty (Property.New ("fixme:cc_name", ia.Name));
828 if (this.folder_name == "Sent" && ia.AddressType != GMime.InternetAddressType.Group)
829 indexable.AddProperty (Property.NewUnsearched ("fixme:sentTo", ia.Addr));
831 addrs.Dispose ();
833 addrs = GMime.InternetAddressList.ParseString (messageInfo.from);
834 foreach (GMime.InternetAddress ia in addrs) {
835 if (!have_content) {
836 indexable.AddProperty (Property.NewUnsearched ("fixme:from", ia.ToString (false)));
837 if (ia.AddressType != GMime.InternetAddressType.Group)
838 indexable.AddProperty (Property.New ("fixme:from_address", ia.Addr));
840 indexable.AddProperty (Property.New ("fixme:from_name", ia.Name));
843 if (this.folder_name != "Sent" && ia.AddressType != GMime.InternetAddressType.Group)
844 indexable.AddProperty (Property.NewUnsearched ("fixme:gotFrom", ia.Addr));
846 addrs.Dispose ();
848 indexable.AddProperty (Property.NewKeyword ("fixme:mlist", messageInfo.mlist));
850 Property flag_prop = Property.NewUnsearched ("fixme:flags", messageInfo.flags);
851 flag_prop.IsMutable = true;
852 indexable.AddProperty (flag_prop);
854 if (this.folder_name == "Sent")
855 indexable.AddProperty (Property.NewFlag ("fixme:isSent"));
857 if (messageInfo.IsAnswered)
858 indexable.AddProperty (Property.NewFlag ("fixme:isAnswered"));
860 if (messageInfo.IsDeleted)
861 indexable.AddProperty (Property.NewFlag ("fixme:isDeleted"));
863 if (messageInfo.IsDraft)
864 indexable.AddProperty (Property.NewFlag ("fixme:isDraft"));
866 if (messageInfo.IsFlagged)
867 indexable.AddProperty (Property.NewFlag ("fixme:isFlagged"));
869 if (messageInfo.IsSeen)
870 indexable.AddProperty (Property.NewFlag ("fixme:isSeen"));
872 if (messageInfo.HasAttachments && !have_content)
873 indexable.AddProperty (Property.NewFlag ("fixme:hasAttachments"));
875 if (messageInfo.IsAnsweredAll)
876 indexable.AddProperty (Property.NewFlag ("fixme:isAnsweredAll"));
878 if (have_content)
879 indexable.ContentUri = UriFu.PathToFileUri (msg_file);
880 else
881 indexable.NoContent = true;
883 return indexable;
886 public override void Checkpoint ()
888 if (this.summary != null) {
890 string progress = "";
891 if (this.count > 0 && this.summary.header.count > 0) {
892 progress = String.Format (" ({0}/{1} {2:###.0}%)",
893 this.count,
894 this.summary.header.count,
895 100.0 * this.count / this.summary.header.count);
897 this.progress_percent = 100.0 * this.count / this.summary.header.count;
901 Logger.Log.Debug ("{0}: indexed {1} messages{2}",
902 this.folder_name, this.indexed_count, progress);
905 this.SaveCache ();
908 public override string GetTarget ()
910 return "summary-file:" + summary_info.FullName;
913 protected override FileInfo CrawlFile {
914 get { return this.summary_info; }