Logo Search packages:      
Sourcecode: beagle version File versions  Download package

EvolutionMailIndexableGenerator.cs

//
// EvolutionMailIndexableGenerator.cs
//
// Copyright (C) 2004 Novell, Inc.
//
//
//
// Permission is hereby granted, free of charge, to any person obtaining a
// copy of this software and associated documentation files (the "Software"),
// to deal in the Software without restriction, including without limitation
// the rights to use, copy, modify, merge, publish, distribute, sublicense,
// and/or sell copies of the Software, and to permit persons to whom the
// Software is furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
// DEALINGS IN THE SOFTWARE.
//

using System;
using System.Collections;
using System.IO;
using System.Runtime.Serialization.Formatters.Binary;
using System.Threading;
using System.Xml;

using Beagle.Util;
using Beagle.Daemon;

using Camel = Beagle.Util.Camel;

namespace Beagle.Daemon.EvolutionMailDriver {

      public abstract class EvolutionMailIndexableGenerator : IIndexableGenerator {

            protected static bool Debug = false;

            private static bool gmime_initialized = false;

            private static ArrayList excludes = new ArrayList ();

            static EvolutionMailIndexableGenerator () {
                  foreach (ExcludeItem exclude in Conf.Indexing.Excludes)
                        if (exclude.Type == ExcludeType.MailFolder)
                              excludes.Add (exclude);
            }

            protected EvolutionMailQueryable queryable;

            protected string account_name, folder_name;
            protected int count, indexed_count;

            protected EvolutionMailIndexableGenerator (EvolutionMailQueryable queryable)
            {
                  this.queryable = queryable;
            }

            protected abstract string GetFolderName (FileSystemInfo info);
            protected abstract bool Setup ();
            protected abstract FileInfo CrawlFile { get; }
            public abstract string GetTarget ();
            public abstract bool HasNextIndexable ();
            public abstract Indexable GetNextIndexable ();
            public abstract void Checkpoint ();

            public void PostFlushHook ()
            {
                  Checkpoint ();
            }

            protected static void InitializeGMime ()
            {
                  if (!gmime_initialized) {
                        GMime.Global.Init ();
                        gmime_initialized = true;
                  }
            }

            protected bool IsSpamFolder (string name)
            {
                  if (name.ToLower () == "spam" || name.ToLower () == "junk")
                        return true;
                  else
                        return false;
            }

            protected bool IgnoreFolder (string path)
            {
                  // FIXME: Use System.IO.Path
                  foreach (ExcludeItem exclude in excludes) {
                        if (exclude.IsMatch (path))
                              return true;
                  }
                  return false;
            }
                              
            protected void CrawlFinished ()
            {
                  // FIXME: This is a little sketchy
                  this.queryable.FileAttributesStore.AttachLastWriteTime (this.CrawlFile.FullName, DateTime.UtcNow);

                  this.queryable.SetGeneratorProgress (this, 100);
            }

            public string StatusName {
                  get { return this.CrawlFile.FullName; }
            }

            public override bool Equals (object o)
            {
                  EvolutionMailIndexableGenerator generator = o as EvolutionMailIndexableGenerator;

                  if (generator == null)
                        return false;

                  if (Object.ReferenceEquals (this, generator))
                        return true;

                  if (this.CrawlFile.FullName == generator.CrawlFile.FullName)
                        return true;
                  else
                        return false;
            }

            public override int GetHashCode ()
            {
                  return this.CrawlFile.FullName.GetHashCode ();
            }
      }

      public class EvolutionMailIndexableGeneratorMbox : EvolutionMailIndexableGenerator {
            private FileInfo mbox_info;
            private int mbox_fd = -1;
            private GMime.StreamFs mbox_stream;
            private GMime.Parser mbox_parser;
            private long file_size;

            public EvolutionMailIndexableGeneratorMbox (EvolutionMailQueryable queryable, FileInfo mbox_info) : base (queryable)
            {
                  this.mbox_info = mbox_info;
            }

            protected override string GetFolderName (FileSystemInfo info)
            {
                  FileInfo file_info = (FileInfo) info;
                  DirectoryInfo di;
                  string folder_name = "";

                  di = file_info.Directory;
                  while (di != null) {
                        // Evo uses ".sbd" as the extension on a folder
                        if (di.Extension == ".sbd")
                              folder_name = Path.Combine (Path.GetFileNameWithoutExtension (di.Name), folder_name);
                        else
                              break;

                        di = di.Parent;
                  }

                  return Path.Combine (folder_name, file_info.Name);
            }

            protected override bool Setup ()
            {
                  this.account_name = "local@local";
                  this.folder_name = this.GetFolderName (this.mbox_info);

                  if (this.IsSpamFolder (this.folder_name))
                        return false;

                  if (this.IgnoreFolder (this.mbox_info.FullName))
                        return false;

                  return true;
            }

            private long MboxLastOffset {
                  get {
                        string offset_str = this.queryable.ReadDataLine ("offset-" + this.folder_name.Replace ('/', '-'));
                        long offset = Convert.ToInt64 (offset_str);

                        return offset;
                  }

                  set {
                        this.queryable.WriteDataLine ("offset-" + this.folder_name.Replace ('/', '-'), value.ToString ());
                  }
            }

            public override bool HasNextIndexable ()
            {
                  if (this.account_name == null) {
                        if (!Setup ()) {
                              this.queryable.RemoveGeneratorProgress (this);
                              return false;
                        }
                  }

                  if (this.mbox_fd < 0) {
                        Logger.Log.Debug ("Opening mbox {0}", this.mbox_info.Name);

                        try {
                              InitializeGMime ();
                        } catch (Exception e) {
                              Logger.Log.Warn (e, "Caught exception trying to initalize gmime:");
                              return false;
                        }

                        this.mbox_fd = Mono.Unix.Native.Syscall.open (this.mbox_info.FullName, Mono.Unix.Native.OpenFlags.O_RDONLY);

                        if (this.mbox_fd < 0) {
                              Log.Error ("Unable to open {0}: {1}", this.mbox_info.FullName, Mono.Unix.Native.Stdlib.strerror (Mono.Unix.Native.Stdlib.GetLastError ()));
                              return false;
                        }

                        this.mbox_stream = new GMime.StreamFs (this.mbox_fd);
                        this.mbox_stream.Seek ((int) this.MboxLastOffset);
                        this.mbox_parser = new GMime.Parser (this.mbox_stream);
                        this.mbox_parser.ScanFrom = true;

                        FileInfo info = new FileInfo (this.mbox_info.FullName);
                        this.file_size = info.Length;
                  }

                  if (this.mbox_parser.Eos ()) {
                        long offset = this.mbox_parser.FromOffset;

                        this.mbox_stream.Close ();

                        this.mbox_fd = -1;
                        this.mbox_stream.Dispose ();
                        this.mbox_stream = null;
                        this.mbox_parser.Dispose ();
                        this.mbox_parser = null;
                        
                        Logger.Log.Debug ("{0}: Finished indexing {1} messages", this.folder_name, this.indexed_count);

                        if (offset >= 0)
                              this.MboxLastOffset = offset;
                        this.CrawlFinished ();

                        return false;
                  } else
                        return true;
            }

            public override Indexable GetNextIndexable ()
            {
                  using (GMime.Message message = this.mbox_parser.ConstructMessage ()) {
                        // Work around what I think is a bug in GMime: If you
                        // have a zero-byte file or seek to the end of a
                        // file, parser.Eos () will return true until it
                        // actually tries to read something off the wire.
                        // Since parser.ConstructMessage() always returns a
                        // message (which may also be a bug), we'll often get
                        // one empty message which we need to deal with here.
                        //
                        // Check if its empty by seeing if the Headers
                        // property is null or empty.
                        if (message == null || message.Headers == null || message.Headers == "")
                              return null;
                        
                        ++this.count;

                        string x_evolution = message.GetHeader ("X-Evolution");
                        if (x_evolution == null || x_evolution == "") {
                              Logger.Log.Info ("{0}: Message at offset {1} has no X-Evolution header!",
                                           this.folder_name, this.mbox_parser.FromOffset);
                              return null;
                        }
                        
                        // This extracts the UID and flags from the X-Evolution header.
                        // It may also contain user-defined flags and tags, but we don't
                        // support those right now.
                        int separator_idx = x_evolution.IndexOf ('-');

                        string uid_str = x_evolution.Substring (0, separator_idx);
                        string uid = Convert.ToUInt32 (uid_str, 16).ToString (); // ugh.
                        uint flags = Convert.ToUInt32 (x_evolution.Substring (separator_idx + 1, 4), 16);

                        Indexable indexable = this.GMimeMessageToIndexable (uid, message, flags);

                        if (Debug) {
                              Logger.Log.Debug ("Constructed message {0} with uid {1}, flags {2}.  Indexable {3} null",
                                            this.count, uid, flags, indexable == null ? "" : "not");
                        }
                        
                        if (indexable == null)
                              return null;

                        ++this.indexed_count;

                        return indexable;
                  }
            }

            private static bool CheckFlags (uint flags, Camel.CamelFlags test)
            {
                  return (flags & (uint) test) == (uint) test;
            }

            private Indexable GMimeMessageToIndexable (string uid, GMime.Message message, uint flags)
            {
                  // Don't index messages flagged as junk
                  if (CheckFlags (flags, Camel.CamelFlags.Junk))
                        return null;

                  System.Uri uri = EvolutionMailQueryable.EmailUri (this.account_name, this.folder_name, uid);
                  Indexable indexable = new Indexable (uri);

                  indexable.Timestamp = message.Date.ToUniversalTime ();
                  indexable.HitType = "MailMessage";
                  indexable.MimeType = "message/rfc822";
                  indexable.CacheContent = false;

                  indexable.AddProperty (Property.NewUnsearched ("fixme:client", "evolution"));
                  indexable.AddProperty (Property.NewUnsearched ("fixme:account", "Local"));
                        indexable.AddProperty (Property.NewUnsearched ("fixme:folder", this.folder_name));

                  GMime.InternetAddressList addrs;

                  addrs = message.GetRecipients (GMime.Message.RecipientType.To);
                  foreach (GMime.InternetAddress ia in addrs) {
                        if (this.folder_name == "Sent" && ia.AddressType != GMime.InternetAddressType.Group)
                              indexable.AddProperty (Property.NewUnsearched ("fixme:sentTo", ia.Addr));
                  }
                  addrs.Dispose ();

                  addrs = message.GetRecipients (GMime.Message.RecipientType.Cc);
                  foreach (GMime.InternetAddress ia in addrs) {
                        if (this.folder_name == "Sent"  && ia.AddressType != GMime.InternetAddressType.Group)
                              indexable.AddProperty (Property.NewUnsearched ("fixme:sentTo", ia.Addr));
                  }
                  addrs.Dispose ();

                  addrs = GMime.InternetAddressList.ParseString (GMime.Utils.HeaderDecodePhrase (message.Sender));
                  foreach (GMime.InternetAddress ia in addrs) {
                        if (this.folder_name != "Sent"  && ia.AddressType != GMime.InternetAddressType.Group)
                              indexable.AddProperty (Property.NewUnsearched ("fixme:gotFrom", ia.Addr));
                  }
                  addrs.Dispose ();

                  if (this.folder_name == "Sent")
                        indexable.AddProperty (Property.NewFlag ("fixme:isSent"));

                      Property flag_prop = Property.NewUnsearched ("fixme:flags", flags);
                  flag_prop.IsMutable = true;
                  indexable.AddProperty (flag_prop);

                  if (CheckFlags (flags, Camel.CamelFlags.Answered))
                        indexable.AddProperty (Property.NewFlag ("fixme:isAnswered"));

                  if (CheckFlags (flags, Camel.CamelFlags.Deleted))
                        indexable.AddProperty (Property.NewFlag ("fixme:isDeleted"));

                  if (CheckFlags (flags, Camel.CamelFlags.Draft))
                        indexable.AddProperty (Property.NewFlag ("fixme:isDraft"));

                  if (CheckFlags (flags, Camel.CamelFlags.Flagged))
                        indexable.AddProperty (Property.NewFlag ("fixme:isFlagged"));

                  if (CheckFlags (flags, Camel.CamelFlags.Seen))
                        indexable.AddProperty (Property.NewFlag ("fixme:isSeen"));

                  if (CheckFlags (flags, Camel.CamelFlags.AnsweredAll))
                        indexable.AddProperty (Property.NewFlag ("fixme:isAnsweredAll"));

                  indexable.SetBinaryStream (message.Stream);

                  return indexable;
            }

            public override void Checkpoint ()
            {
                  long offset = -1;

                  if (this.mbox_parser != null) {
                        offset = this.mbox_parser.FromOffset;

                        if (offset >= 0)
                              this.MboxLastOffset = offset;
                  }

                  string progress = "";
                  if (this.file_size > 0 && offset > 0) {
                        progress = String.Format (" ({0}/{1} bytes {2:###.0}%)",
                                            offset, this.file_size, 100.0 * offset / this.file_size);

                        this.queryable.SetGeneratorProgress (this, (int) (100.0 * offset / this.file_size));
                  }

                  Logger.Log.Debug ("{0}: indexed {1} messages{2}",
                                this.folder_name, this.indexed_count, progress);
            }

            public override string GetTarget ()
            {
                  return "mbox-file:" + mbox_info.FullName;
            }

            protected override FileInfo CrawlFile {
                  get { return this.mbox_info; }
            }
      }

      public class EvolutionMailIndexableGeneratorImap : EvolutionMailIndexableGenerator {
            private enum ImapBackendType {
                  Imap,
                  Imap4
            };

            private FileInfo summary_info;
            private string imap_name;
            private ImapBackendType backend_type;
            private Camel.Summary summary;
            private IEnumerator summary_enumerator;
            private ICollection accounts;
            private string folder_cache_name;
            private Hashtable mapping;
            private ArrayList deleted_list;
            private bool delete_mode;
            private int delete_count;

            public EvolutionMailIndexableGeneratorImap (EvolutionMailQueryable queryable, FileInfo summary_info) : base (queryable)
            {
                  this.summary_info = summary_info;
            }

            protected override string GetFolderName (FileSystemInfo info)
            {
                  DirectoryInfo dir_info = (DirectoryInfo) info;
                  string folder_name = "";

                  while (dir_info != null) {
                        folder_name = Path.Combine (dir_info.Name, folder_name);

                        dir_info = dir_info.Parent;

                        if (dir_info.Name != "subfolders")
                              break;
                        else
                              dir_info = dir_info.Parent;
                  }

                  return folder_name;
            }

            protected override bool Setup ()
            {
                  string dir_name = summary_info.DirectoryName;
                  int imap_start_idx;

                  int idx = dir_name.IndexOf (".evolution/mail/imap4/");

                  if (idx >= 0) {
                        this.backend_type = ImapBackendType.Imap4;
                        imap_start_idx = idx + 22;
                  } else {
                        this.backend_type = ImapBackendType.Imap;
                        imap_start_idx = dir_name.IndexOf (".evolution/mail/imap/") + 21;
                  }

                  string imap_start = dir_name.Substring (imap_start_idx);
                  this.imap_name = imap_start.Substring (0, imap_start.IndexOf ('/'));

                  try {
                        this.accounts = (ICollection) GConfThreadHelper.Get ("/apps/evolution/mail/accounts");
                  } catch (Exception ex) {
                        Logger.Log.Warn ("Caught exception in Setup(): " + ex.Message);
                        Logger.Log.Warn ("There are no configured evolution accounts, ignoring {0}", this.imap_name);
                        return false;
                  }

                  // This should only happen if we shut down while waiting for the GConf results to come back.
                  if (this.accounts == null)
                        return false;

                  foreach (string xml in this.accounts) {
                        XmlDocument xmlDoc = new XmlDocument ();

                        xmlDoc.LoadXml (xml);
                        
                        XmlNode account = xmlDoc.SelectSingleNode ("//account");

                        if (account == null)
                              continue;

                        string uid = null;

                        foreach (XmlAttribute attr in account.Attributes) {
                              if (attr.Name == "uid") {
                                    uid = attr.InnerText;
                                    break;
                              }
                        }

                        if (uid == null)
                              continue;

                        XmlNode imap_url_node = xmlDoc.SelectSingleNode ("//source/url");

                        if (imap_url_node == null)
                              continue;

                        string imap_url = imap_url_node.InnerText;
                        // If there is a semicolon in the username part of the URL, it
                        // indicates that there's an auth scheme there.  We don't care
                        // about that, so remove it.
                        int user_end = imap_url.IndexOf ('@');
                        int semicolon = imap_url.IndexOf (';', 0, user_end + 1);

                        if (semicolon != -1)
                              imap_url = imap_url.Substring (0, semicolon) + imap_url.Substring (user_end);

                        // Escape backslashes, which frequently appear when using IMAP against Exchange servers
                        this.imap_name = this.imap_name.Replace ("\\", "%5c");

                        // Escape out additional @s in the name.  I hate the class libs so much.
                        int lastIdx = this.imap_name.LastIndexOf ('@');
                        if (this.imap_name.IndexOf ('@') != lastIdx) {
                              string toEscape = this.imap_name.Substring (0, lastIdx);
                              this.imap_name = toEscape.Replace ("@", "%40") + this.imap_name.Substring (lastIdx);
                        }

                        string backend_url_prefix;
                        if (this.backend_type == ImapBackendType.Imap)
                              backend_url_prefix = "imap";
                        else
                              backend_url_prefix = "imap4";

                        if (imap_url.StartsWith (backend_url_prefix + "://" + this.imap_name + "/")) {
                              this.account_name = uid;
                              break;
                        }
                  }

                  if (this.account_name == null) {
                        Logger.Log.Info ("Unable to determine account name for {0}", this.imap_name);
                        return false;
                  }

                  // Need to check the directory on disk to see if it's a junk/spam folder,
                  // since the folder name will be "foo/spam" and not match the check below.
                  DirectoryInfo dir_info = new DirectoryInfo (dir_name);
                  if (this.IsSpamFolder (dir_info.Name))
                        return false;

                  // Check if the folder is listed in the configuration as to be excluded from indexing
                  if (this.IgnoreFolder (dir_info.FullName))
                        return false;
                              
                  this.folder_name = GetFolderName (new DirectoryInfo (dir_name));

                  return true;
            }

            private string FolderCacheName {
                  get {
                        if (this.account_name == null || this.folder_name == null)
                              return null;

                        if (this.folder_cache_name == null)
                              this.folder_cache_name = "status-" + this.account_name + "-" + this.folder_name.Replace ('/', '-');

                        return this.folder_cache_name;
                  }
            }

            private bool LoadCache ()
            {
                  Stream cacheStream;
                  BinaryFormatter formatter;

                  if (this.FolderCacheName == null) {
                        this.mapping = new Hashtable ();
                        return false;
                  }

                  try {
                        cacheStream = this.queryable.ReadDataStream (this.FolderCacheName);
                        formatter = new BinaryFormatter ();
                        this.mapping = formatter.Deserialize (cacheStream) as Hashtable;
                        cacheStream.Close ();
                        Logger.Log.Debug ("Successfully loaded previous crawled data from disk: {0}", this.FolderCacheName);

                        return true;
                  } catch {
                        this.mapping = new Hashtable ();

                        return false;
                  }
            }

            private void SaveCache ()
            {
                  Stream cacheStream;
                  BinaryFormatter formatter;

                  if (this.FolderCacheName == null)
                        return;
                  
                  cacheStream = this.queryable.WriteDataStream (this.FolderCacheName);
                  formatter = new BinaryFormatter ();
                  formatter.Serialize (cacheStream, mapping);
                  cacheStream.Close ();
            }

            public override bool HasNextIndexable ()
            {
                  if (this.account_name == null) {
                        if (!Setup ()) {
                              this.queryable.RemoveGeneratorProgress (this);
                              return false;
                        }
                  }

                  if (this.mapping == null) {
                        bool cache_loaded = this.LoadCache ();

                        this.deleted_list = new ArrayList (this.mapping.Keys);
                        this.deleted_list.Sort ();
                        Logger.Log.Debug ("Deleted list starting at {0} for {1}", this.deleted_list.Count, this.folder_name);

                        // Check to see if we even need to bother walking the summary
                        if (cache_loaded && this.queryable.FileAttributesStore.IsUpToDate (this.CrawlFile.FullName)) {
                              Logger.Log.Debug ("{0}: summary has not been updated; crawl unncessary", this.folder_name);
                              this.queryable.RemoveGeneratorProgress (this);
                              return false;
                        }
                  }

                  if (this.summary == null) {
                        try {
                              if (this.backend_type == ImapBackendType.Imap)
                                    this.summary = Camel.Summary.LoadImapSummary (this.summary_info.FullName);
                              else
                                    this.summary = Camel.Summary.LoadImap4Summary (this.summary_info.FullName);
                        } catch (Exception e) {
                              Logger.Log.Warn (e, "Unable to index {0}:", this.folder_name);
                              this.queryable.RemoveGeneratorProgress (this);
                              return false;
                        }
                  }

                  if (this.summary_enumerator == null)
                        this.summary_enumerator = this.summary.GetEnumerator ();

                  if (this.summary_enumerator.MoveNext ())
                        return true;

                  this.delete_mode = true;

                  if (this.deleted_list.Count > 0)
                        return true;

                  string progress = "";
                  if (this.count > 0 && this.summary.header.count > 0) {
                        progress = String.Format ("({0}/{1} {2:###.0}%)",
                                            this.count,
                                            this.summary.header.count,
                                            100.0 * this.count / this.summary.header.count);
                  }

                  Logger.Log.Debug ("{0}: Finished indexing {1} messages {2}, {3} messages deleted", this.folder_name, this.indexed_count, progress, this.delete_count);

                  this.SaveCache ();
                  this.CrawlFinished ();

                  return false;
            }

            // Kind of nasty, but we need the function.
            [System.Runtime.InteropServices.DllImport("libglib-2.0.so.0")]
            static extern int g_str_hash (string str);

            // Stolen from deep within e-d-s's camel-data-cache.c  Very evil.
            private const int CAMEL_DATA_CACHE_MASK = ((1 << 6) - 1);

            public override Indexable GetNextIndexable ()
            {
                  Indexable indexable = null;

                  // No more new messages to index, so start on the removals.
                  if (this.delete_mode) {
                        string uid = (string) this.deleted_list [0];
                        Uri uri = EvolutionMailQueryable.EmailUri (this.account_name, this.folder_name, uid);
                        
                        indexable = new Indexable (IndexableType.Remove, uri);

                        this.deleted_list.RemoveAt (0);
                        this.mapping.Remove (uid);

                        this.delete_count++;

                        return indexable;
                  }

                  Camel.MessageInfo mi = (Camel.MessageInfo) this.summary_enumerator.Current;

                  ++this.count;

                  if (Debug) {
                        Logger.Log.Debug ("Constructed message {0} with uid {1}, flags {2}.",
                                      this.count, mi.uid, mi.flags);
                  }

                  // Try to load the cached message data off disk
                  object flags = this.mapping[mi.uid];

                  if (flags == null) {
                        // New, previously unseen message
                        string msg_file;

                        if (this.backend_type == ImapBackendType.Imap)
                              msg_file = Path.Combine (summary_info.DirectoryName, mi.uid + ".");
                        else {
                              // This is taken from e-d-s's camel-data-cache.c.  No doubt
                              // NotZed would scream bloody murder if he saw this here.
                              int hash = (g_str_hash (mi.uid) >> 5) & CAMEL_DATA_CACHE_MASK;
                              string cache_path = String.Format ("cache/{0:x}/{1}", hash, mi.uid);
                              msg_file = Path.Combine (summary_info.DirectoryName, cache_path);
                        }

                        indexable = this.CamelMessageToIndexable (mi, msg_file);

                        if (Debug)
                              Logger.Log.Debug ("Unseen message, indexable {0} null", indexable == null ? "" : "not");

                        this.mapping[mi.uid] = mi.flags;

                        if (indexable != null)
                              ++this.indexed_count;
                  } else if ((uint) flags != mi.flags) {
                        // Previously seen message, but flags have changed.
                        Uri uri = CamelMessageUri (mi);
                        indexable = new Indexable (uri);
                        indexable.Type = IndexableType.PropertyChange;

                        Property flag_prop = Property.NewUnsearched ("fixme:flags", mi.flags);
                        flag_prop.IsMutable = true;
                        indexable.AddProperty (flag_prop);

                        if (Debug)
                              Logger.Log.Debug ("Previously seen message, flags changed: {0} -> {1}", flags, mi.flags);

                        ++this.indexed_count;
                  } else {
                        if (Debug)
                              Logger.Log.Debug ("Previously seen message, unchanged.");
                  }

                  if (flags != null)
                        this.deleted_list.Remove (mi.uid);

                  return indexable;
            }

            private Uri CamelMessageUri (Camel.MessageInfo message_info)
            {
                  return EvolutionMailQueryable.EmailUri (this.account_name, this.folder_name, message_info.uid);
            }

            private Indexable CamelMessageToIndexable (Camel.MessageInfo messageInfo, string msg_file)
            {
                  // Don't index messages flagged as junk
                  if (messageInfo.IsJunk)
                        return null;

                  // Many properties will be set by the filter when
                  // processing the cached data, if it's there.  So
                  // don't set a number of properties in that case.
                  bool have_content = File.Exists (msg_file);

                  Uri uri = CamelMessageUri (messageInfo);
                  Indexable indexable = new Indexable (uri);

                  indexable.Timestamp = messageInfo.SentDate;
                  indexable.MimeType = "message/rfc822";
                  indexable.HitType = "MailMessage";

                  indexable.AddProperty (Property.NewUnsearched ("fixme:account",  this.imap_name));
                        indexable.AddProperty (Property.NewUnsearched ("fixme:folder",   this.folder_name));
                  indexable.AddProperty (Property.NewUnsearched ("fixme:client", "evolution"));
                  
                  if (!have_content) {
                        indexable.AddProperty (Property.New ("dc:title", GMime.Utils.HeaderDecodePhrase (messageInfo.subject)));
                        indexable.AddProperty (Property.NewDate ("fixme:date", messageInfo.SentDate));
                  }

                  GMime.InternetAddressList addrs;
                  addrs = GMime.InternetAddressList.ParseString (messageInfo.to);
                  foreach (GMime.InternetAddress ia in addrs) {
                        if (!have_content) {
                              indexable.AddProperty (Property.NewUnsearched ("fixme:to", ia.ToString (false)));
                              if (ia.AddressType != GMime.InternetAddressType.Group)
                                    indexable.AddProperty (Property.New ("fixme:to_address", ia.Addr));

                              indexable.AddProperty (Property.New ("fixme:to_name", ia.Name));
                        }

                        if (this.folder_name == "Sent" && ia.AddressType != GMime.InternetAddressType.Group)
                              indexable.AddProperty (Property.NewUnsearched ("fixme:sentTo", ia.Addr));
                  }
                  addrs.Dispose ();

                  addrs = GMime.InternetAddressList.ParseString (messageInfo.cc);
                  foreach (GMime.InternetAddress ia in addrs) {
                        if (!have_content) {
                              indexable.AddProperty (Property.NewUnsearched ("fixme:cc", ia.ToString (false)));
                              if (ia.AddressType != GMime.InternetAddressType.Group)
                                    indexable.AddProperty (Property.New ("fixme:cc_address", ia.Addr));

                              indexable.AddProperty (Property.New ("fixme:cc_name", ia.Name));
                        }

                        if (this.folder_name == "Sent" && ia.AddressType != GMime.InternetAddressType.Group)
                              indexable.AddProperty (Property.NewUnsearched ("fixme:sentTo", ia.Addr));
                  }
                  addrs.Dispose ();

                  addrs = GMime.InternetAddressList.ParseString (messageInfo.from);
                  foreach (GMime.InternetAddress ia in addrs) {
                        if (!have_content) {
                              indexable.AddProperty (Property.NewUnsearched ("fixme:from", ia.ToString (false)));
                              if (ia.AddressType != GMime.InternetAddressType.Group)
                                    indexable.AddProperty (Property.New ("fixme:from_address", ia.Addr));

                              indexable.AddProperty (Property.New ("fixme:from_name", ia.Name));
                        }

                        if (this.folder_name != "Sent" && ia.AddressType != GMime.InternetAddressType.Group)
                              indexable.AddProperty (Property.NewUnsearched ("fixme:gotFrom", ia.Addr));
                  }
                  addrs.Dispose ();

                        indexable.AddProperty (Property.NewKeyword ("fixme:mlist", messageInfo.mlist));

                  Property flag_prop = Property.NewUnsearched ("fixme:flags", messageInfo.flags);
                  flag_prop.IsMutable = true;
                  indexable.AddProperty (flag_prop);

                  if (this.folder_name == "Sent")
                        indexable.AddProperty (Property.NewFlag ("fixme:isSent"));

                  if (messageInfo.IsAnswered)
                        indexable.AddProperty (Property.NewFlag ("fixme:isAnswered"));

                  if (messageInfo.IsDeleted)
                        indexable.AddProperty (Property.NewFlag ("fixme:isDeleted"));

                  if (messageInfo.IsDraft)
                        indexable.AddProperty (Property.NewFlag ("fixme:isDraft"));

                  if (messageInfo.IsFlagged)
                        indexable.AddProperty (Property.NewFlag ("fixme:isFlagged"));

                  if (messageInfo.IsSeen)
                        indexable.AddProperty (Property.NewFlag ("fixme:isSeen"));

                  if (messageInfo.HasAttachments && !have_content)
                        indexable.AddProperty (Property.NewFlag ("fixme:hasAttachments"));

                  if (messageInfo.IsAnsweredAll)
                        indexable.AddProperty (Property.NewFlag ("fixme:isAnsweredAll"));

                  if (have_content)
                        indexable.ContentUri = UriFu.PathToFileUri (msg_file);
                  else 
                        indexable.NoContent = true;

                  return indexable;
            }

            public override void Checkpoint ()
            {
                  if (this.summary != null) {

                        string progress = "";
                        if (this.count > 0 && this.summary.header.count > 0) {
                              progress = String.Format (" ({0}/{1} {2:###.0}%)",
                                                  this.count,
                                                  this.summary.header.count,
                                                  100.0 * this.count / this.summary.header.count);

                              this.queryable.SetGeneratorProgress (this, (int) (100.0 * this.count / this.summary.header.count));

                        }
                        
                        Logger.Log.Debug ("{0}: indexed {1} messages{2}",
                                      this.folder_name, this.indexed_count, progress);
                  }

                  this.SaveCache ();
            }

            public override string GetTarget ()
            {
                  return "summary-file:" + summary_info.FullName;
            }

            protected override FileInfo CrawlFile {
                  get { return this.summary_info; }
            }
      }
}

Generated by  Doxygen 1.6.0   Back to index