Index: Tiles/TileMailMessage.cs
===================================================================
RCS file: /cvs/gnome/beagle/Tiles/TileMailMessage.cs,v
retrieving revision 1.28
diff -r1.28 TileMailMessage.cs
196,197c196,198
<                       p.StartInfo.FileName = "evolution";
<                       p.StartInfo.Arguments = "'" + Hit.Uri + "'";
---
>                       p.StartInfo.FileName = "kmail"; // FIXME: Allow the user to choose what to launche to see an email
>                       p.StartInfo.Arguments = "--view '" + Hit.Uri + "'";
>                       Console.WriteLine ("Runing {0}: {1}", p.StartInfo.FileName, p.StartInfo.Arguments);
215c216
<                       p.StartInfo.FileName = "evolution";
---
>                       p.StartInfo.FileName = "kmail"; // FIXME: Allow the user to choose what to launche to see an email


===================================================================
RCS file: /cvs/gnome/beagle/beagled/Makefile.am,v
retrieving revision 1.103
diff -r1.103 Makefile.am
251a252,258
> maildirqueryable = $(srcdir)/MailDirDriver
> MAIL_DIR_QUERYABLE_CSFILES =                    \
>     $(maildirqueryable)/MailDirDriver.cs      \
>     $(maildirqueryable)/MailDirIndexableGenerator.cs  \
>     $(maildirqueryable)/MailDirCrawler.cs
>
>
277a285
>       $(MAIL_DIR_QUERYABLE_CSFILES)               \


Index: Util/Makefile.am
===================================================================
RCS file: /cvs/gnome/beagle/Util/Makefile.am,v
retrieving revision 1.50
diff -r1.50 Makefile.am
56a57
>       $(srcdir)/Rfc822DateTime.cs                     \



Index: Util/Rfc822DateTime.cs
===================================================================
using System;
using System.Collections;
using System.Globalization;
using System.IO;
using System.Text;
using System.Xml;

using Mono.Posix;

//
// Credit should go to: http://www.kbcafe.com/juice/?date=200412
// Dunno who the author is
/////////////////////////////////////////////////////////////////

namespace Beagle.Util {

	public class Rfc822DateTime 
	{
		static public System.DateTime FromString(string date)
		{
			System.DateTime dt;
			int pos = date.LastIndexOf(" ");

			try
			{
				dt = Convert.ToDateTime(date);
				if (date.Substring(pos+1) == "Z")
				{
					dt = dt.ToUniversalTime();
				}
				else if (date.Substring(pos+1) == "GMT")
				{
					dt = dt.ToUniversalTime();
				}
				return dt;
			}
			catch (System.Exception x)
			{ 
				System.Diagnostics.Trace.WriteLine(x.Message);
			}
			
			dt = Convert.ToDateTime(date.Substring(0, pos));
			if (date[pos+1] == '+')
			{
				int h = Convert.ToInt32(date.Substring(pos+2, 2));
				dt = dt.AddHours(-h);
				int m = Convert.ToInt32(date.Substring(pos+4, 2));
				dt = dt.AddMinutes(-m);
			}
			else if (date[pos+1] == '-')
			{
				int h = Convert.ToInt32(date.Substring(pos+2, 2));
				dt = dt.AddHours(h);
				int m = Convert.ToInt32(date.Substring(pos+4, 2));
				dt = dt.AddMinutes(m);
			}
			else if (date.Substring(pos+1) == "A")
			{
				dt = dt.AddHours(1);
			}
			else if (date.Substring(pos+1) == "B")
			{
				dt = dt.AddHours(2);
			}
			else if (date.Substring(pos+1) == "C")
			{
				dt = dt.AddHours(3);
			}
			else if (date.Substring(pos+1) == "D")
			{
				dt = dt.AddHours(4);
			}
			else if (date.Substring(pos+1) == "E")
			{
				dt = dt.AddHours(5);
			}
			else if (date.Substring(pos+1) == "F")
			{
				dt = dt.AddHours(6);
			}
			else if (date.Substring(pos+1) == "G")
			{
				dt = dt.AddHours(7);
			}
			else if (date.Substring(pos+1) == "H")
			{
				dt = dt.AddHours(8);
			}
			else if (date.Substring(pos+1) == "I")
			{
				dt = dt.AddHours(9);
			}
			else if (date.Substring(pos+1) == "K")
			{
				dt = dt.AddHours(10);
			}
			else if (date.Substring(pos+1) == "L")
			{
				dt = dt.AddHours(11);
			}
			else if (date.Substring(pos+1) == "M")
			{
				dt = dt.AddHours(12);
			}
			else if (date.Substring(pos+1) == "N")
			{
				dt = dt.AddHours(-1);
			}
			else if (date.Substring(pos+1) == "O")
			{
				dt = dt.AddHours(-2);
			}
			else if (date.Substring(pos+1) == "P")
			{
				dt = dt.AddHours(-3);
			}
			else if (date.Substring(pos+1) == "Q")
			{
				dt = dt.AddHours(-4);
			}
			else if (date.Substring(pos+1) == "R")
			{
				dt = dt.AddHours(-5);
			}
			else if (date.Substring(pos+1) == "S")
			{
				dt = dt.AddHours(-6);
			}
			else if (date.Substring(pos+1) == "T")
			{
				dt = dt.AddHours(-7);
			}
			else if (date.Substring(pos+1) == "U")
			{
				dt = dt.AddHours(-8);
			}
			else if (date.Substring(pos+1) == "V")
			{
				dt = dt.AddHours(-9);
			}
			else if (date.Substring(pos+1) == "W")
			{
				dt = dt.AddHours(-10);
			}
			else if (date.Substring(pos+1) == "X")
			{
				dt = dt.AddHours(-11);
			}
			else if (date.Substring(pos+1) == "Y")
			{
				dt = dt.AddHours(-12);
			}
			else if (date.Substring(pos+1) == "EST")
			{
				dt = dt.AddHours(5);
			}
			else if (date.Substring(pos+1) == "EDT")
			{
				dt = dt.AddHours(4);
			}
			else if (date.Substring(pos+1) == "CST")
			{
				dt = dt.AddHours(6);
			}
			else if (date.Substring(pos+1) == "CDT")
			{
				dt = dt.AddHours(5);
			}
			else if (date.Substring(pos+1) == "MST")
			{
				dt = dt.AddHours(7);
			}
			else if (date.Substring(pos+1) == "MDT")
			{
				dt = dt.AddHours(6);
			}
			else if (date.Substring(pos+1) == "PST")
			{
				dt = dt.AddHours(8);
			}
			else if (date.Substring(pos+1) == "PDT")
			{
				dt = dt.AddHours(7);
			}

			return dt;
		}
	}
}
===================================================================

Index: beagled/MailDirDriver/MailDirDriver.cs
===================================================================

//
// MailDirDriver.cs
//
// Copyright (C) 2005 Noam Rathaus, GPLv2
//
//
//
// 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.Threading;

using Beagle.Util;
using Camel = Beagle.Util.Camel;

namespace Beagle.Daemon.MailDirDriver {

	[QueryableFlavor (Name="Mail", Domain=QueryDomain.Local, RequireInotify=false)]
	public class MailDirQueryable : LuceneQueryable {

		public int polling_interval_in_seconds = 60;

		public static Logger log = Logger.Get ("mail");
		private string path;

		private SortedList watched = new SortedList ();
		private MailDirCrawler crawler;

		private object lockObj = new object ();
		private ArrayList AddedUris = new ArrayList ();
		private bool queryRunning = false;

		public MailDirQueryable () : base ("MailIndex")
		{
			Logger.Log.Info ("path configured to: {0}", "Mail/sent-mail");
			this.path = Path.Combine (PathFinder.HomeDir, "Mail/sent-mail");
		}

		private void Crawl ()
		{
			crawler.Crawl ();
			foreach (FileInfo file in crawler.Emails)
				IndexEmail (file);
		}

		private void CrawlHook (Scheduler.Task task)
		{
			Crawl ();
			task.Reschedule = true;
			task.TriggerTime = DateTime.Now.AddSeconds (polling_interval_in_seconds);
		}

		private void StartWorker ()
		{
			Logger.Log.Info ("Starting Mail Dir mail backend");

			Stopwatch stopwatch = new Stopwatch ();
			stopwatch.Start ();

			// Check that we have data to index
			if (! Directory.Exists (this.path)) {
				// No mails present, repoll every minute
				log.Warn ("Mail Dir mail store not found, watching for it.");
				GLib.Timeout.Add (60000, new GLib.TimeoutHandler (CheckForMailData));
				return;
			}
			else
			{
				Logger.Log.Info ("Mail Dir found, path: {0}", this.path);
			}

			// Get notification when an index or summary file changes
			if (Inotify.Enabled) {
				Inotify.Event += OnInotifyEvent;
				Watch (this.path);
			}

			Logger.Log.Debug ("Starting Mail Dir crawl");
			crawler = new MailDirCrawler (this.path);
			Crawl ();
			Logger.Log.Debug ("Mail Dir crawl finished");

			// If we don't have inotify, we have to poll the file system.  Ugh.
			if (! Inotify.Enabled) {
				Logger.Log.Info ("INotifty missing, Mail Dir will be watched manaually");

				Scheduler.Task task = Scheduler.TaskFromHook (new Scheduler.TaskHook (CrawlHook));
				task.Tag = "Crawling Kmail dir to find summary changes";
				ThisScheduler.Add (task);
			}

			stopwatch.Stop ();
			Logger.Log.Info ("Mail Dir driver worker thread done in {0}",
					 stopwatch);
		}

		public override void Start () 
		{
			Logger.Log.Info ("Starting Mail Dir backend");
			base.Start ();
			
			ExceptionHandlingThread.Start (new ThreadStart (StartWorker));
		}

		private void Watch (string path)
		{
			DirectoryInfo root = new DirectoryInfo (path);
			if (! root.Exists)
				return;

			Queue queue = new Queue ();
			queue.Enqueue (root);

			while (queue.Count > 0) {
				DirectoryInfo dir = queue.Dequeue () as DirectoryInfo;

				if (! dir.Exists)
					continue;
				// FIXME: Support for INotify missing
//				int wd = Inotify.Watch (dir.FullName,
//							Inotify.EventType.CreateSubdir
//							Inotify.EventType.DeleteSubdir
//							| Inotify.EventType.MovedTo);
//				watched [wd] = dir.FullName;

				foreach (DirectoryInfo subdir in dir.GetDirectories ())
					queue.Enqueue (subdir);
			}
		}

		private void Ignore (string path)
		{
			Inotify.Ignore (path);
			watched.RemoveAt (watched.IndexOfValue (path));
		}

		private void OnInotifyEvent (int wd,
					     string path,
					     string subitem,
					     string srcpath,
					     Inotify.EventType type)
		{
			if (subitem == "" || ! watched.Contains (wd))
				return;

			string fullPath = Path.Combine (path, subitem);

//			switch (type) { // FIXME: Support for INotify missing
				
//			case Inotify.EventType.CreateSubdir:
//				Watch (fullPath);
//				break;

//			case Inotify.EventType.DeleteSubdir:
//				Ignore (fullPath);
//				break;

//			case Inotify.EventType.MovedTo:
//				if (subitem == "summary") {
//					// IMAP summary
//					log.Info ("Reindexing updated IMAP summary: {0}", fullPath);
//					this.IndexEmail (new FileInfo (fullPath));
//				}

//				break;
//			}
		}

		private bool CheckForMailData ()
		{
			if (! Directory.Exists (this.path))
				return true; // continue polling
			
			// Otherwise stop polling and start indexing
			StartWorker();
			return false;
		}

		public string Name {
			get { return "MailDir"; }
		}

		public void IndexEmail (FileInfo emailInfo)
		{
			Logger.Log.Info ("IndexEmail({0})", emailInfo);
			MailDirIndexableGenerator generator = new MailDirIndexableGenerator (this, emailInfo);
			Scheduler.Task task;
			task = NewAddTask (generator, new Scheduler.Hook (generator.Checkpoint));
			// IndexableGenerator tasks default to having priority Scheduler.Priority Generator
			ThisScheduler.Add (task);
			Logger.Log.Info ("IndexEmail({0}) done", emailInfo);
		}

		public static Uri EmailUri (string accountName, string folderName, string uid)
		{
			return new Uri (String.Format ("email://{0}/{1};uid={2}",
						       accountName, folderName, uid));
		}

		override protected double RelevancyMultiplier (Hit hit)
		{
			return HalfLifeMultiplierFromProperty (hit, 0.25,
							       "fixme:received", "fixme:sentdate");
		}
	}

}
===================================================================

Index: MailDirCrawler.cs
===================================================================

//
// MailDirCrawler.cs
//
// Copyright (C) 2005, Noam Rathaus, GPLv2
//
//
//
// 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 Beagle.Util;
using Beagle.Daemon;

namespace Beagle.Daemon.MailDirDriver {
	
	class MailDirCrawler {
		ArrayList roots = new ArrayList ();

		Hashtable last_write_time_cache = new Hashtable ();
		ArrayList emails = new ArrayList ();
		
		public MailDirCrawler (params string[] paths)
		{
			foreach (string p in paths) {
				if (Directory.Exists (p))
					roots.Add (p);
			}
		}

		private bool FileIsInteresting (FileInfo file)
		{
			DateTime cached_time = new DateTime ();
			if (last_write_time_cache.Contains (file.FullName))
				cached_time = (DateTime) last_write_time_cache [file.FullName];
			
			last_write_time_cache [file.FullName] = file.LastWriteTime;
			
			return cached_time < file.LastWriteTime;
		}

		public void Crawl ()
		{
			emails.Clear ();

			Queue pending = new Queue ();

			foreach (string root in roots)
				pending.Enqueue (new DirectoryInfo (root));

			Logger.Log.Info ("Crawls pending: {0}", pending.Count);

			while (pending.Count > 0) {

				DirectoryInfo dir = pending.Dequeue () as DirectoryInfo;

				Logger.Log.Info ("dir: {0}", dir);

				foreach (DirectoryInfo subdir in dir.GetDirectories ())
					pending.Enqueue (subdir);

				foreach (FileInfo file in dir.GetFiles ()) {
					Logger.Log.Info ("FileIsInteresting({0})", file);
					if (file.Extension == ".index" || file.Extension == ".ids")
					{
						Logger.Log.Info ("Index file, ignoring");
					}
					else
					{
						FileIsInteresting (file);
						emails.Add(file);
					}
				}
			}
		}

		public ICollection Emails {
			get { return emails; } 
		}

	}
}
===================================================================


Index: MailDirIndexableGenerator.cs
===================================================================

//
// MailDirIndexableGenerator.cs
//
// Copyright (C) 2005, Noam Rathaus, GPLv2
//
//
//
// 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 Mozilla = Beagle.Util.Mozilla;
using GConf;
using Mono.Posix;

namespace Beagle.Daemon.MailDirDriver {

	public class MailDirIndexableGenerator : IIndexableGenerator {
		private FileInfo email_info;
		protected bool parsed;
		private MailDirQueryable queryable;
		protected string folder_name;
		protected int count, indexed_count;

		public MailDirIndexableGenerator (MailDirQueryable queryable, FileInfo file)
		{
			MailDirQueryable.log.Debug("MailDirIndexableGenerator({0}, {1})", queryable, file);
			this.queryable = queryable;
			this.email_info = file;
			this.parsed = false;
		}

		public MailDirQueryable Queryable {
			get { return this.queryable; }
		}

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

		public Indexable GetNextIndexable ()
		{
			MailDirQueryable.log.Debug("GetNextIndexable()");
			string filename = email_info.FullName;
			MailDirQueryable.log.Debug("Invoking Mozilla.MessageReader({0})", filename);

			Mozilla.MessageReader mr = new Mozilla.MessageReader(filename);
			Mozilla.Message message = mr.NextMessage;

			MailDirQueryable.log.Debug("message from: {0} to: {1} subject: {2}", message.From, message.To, message.Subject);
			
			++this.count;

			Indexable indexable = this.MailDirMessageToIndexable (message);
			++this.indexed_count;
			this.parsed = true;

			return indexable;
		}
		
		public bool HasNextIndexable ()
		{
			MailDirQueryable.log.Debug("HasNextIndexable()");
			if (this.parsed)
			{
				CrawlFinished();
				return false;
			}
			else
			{
				return true;
			}
		}


		protected bool CrawlNeeded ()
		{
			string timeStr;

			try {
				timeStr = ExtendedAttribute.Get (this.CrawlFile.FullName, "LastCrawl");
			} catch {
				MailDirQueryable.log.Debug ("CrawlNeeded() Unable to get last crawl time on {0}", this.CrawlFile.FullName);
				return true;
			}

			DateTime lastCrawl = StringFu.StringToDateTime (timeStr);

			if (this.CrawlFile.LastWriteTime > lastCrawl)
				return true;
			else
				return false;
		}

		protected void CrawlFinished ()
		{
			try {
				ExtendedAttribute.Set (this.CrawlFile.FullName, "LastCrawl",
						       StringFu.DateTimeToString (DateTime.Now));
			} catch {
				MailDirQueryable.log.Debug ("CrawlFinished() Unable to set last crawl time on {0}", this.CrawlFile.FullName);
			}
		}

		static public bool SupportedContentType (GMime.ContentType type)
		{
			return type.Type == "text" || type.Type == "Text" || type.Type == "TEXT";
		}

		protected static Stream ReadAppData (string name)
		{
			string path = Path.Combine (Path.Combine (PathFinder.StorageDir, "MailIndex"), name);
			return new FileStream (path, System.IO.FileMode.Open, FileAccess.Read, FileShare.Read);
		}

		protected static string ReadAppDataLine (string name)
		{
			Stream stream;

			try {
				stream = ReadAppData (name);
			} catch (FileNotFoundException) {
				return null;
			}

			StreamReader sr = new StreamReader (stream);
			string line = sr.ReadLine ();
			sr.Close ();
			return line;
		}

		protected static Stream WriteAppData (string name)
		{
			string path = Path.Combine (Path.Combine (PathFinder.StorageDir, "MailIndex"), name);
			return new FileStream (path, System.IO.FileMode.Create, FileAccess.Write, FileShare.None);
		}

		protected static void WriteAppDataLine (string name, string line)
		{
			if (line == null) {
				string path = Path.Combine (Path.Combine (PathFinder.StorageDir, "MailIndex"), name);

				if (File.Exists (path))
					File.Delete (path);

				return;
			}

			StreamWriter sw = new StreamWriter (WriteAppData (name));
			sw.WriteLine (line);
			sw.Close ();
		}

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

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

			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 ()
		{
			MailDirQueryable.log.Debug("this.CrawlFile.FullName.GetHashCode (): {0}", this.CrawlFile.FullName.GetHashCode ());
			return this.CrawlFile.FullName.GetHashCode ();
		}

		private Indexable MailDirMessageToIndexable (Mozilla.Message message)
		{
			MailDirQueryable.log.Debug("MailDirMessageToIndexable()");
			
			MailDirQueryable.log.Debug("UriFu.PathToFileUri({0})", email_info.FullName);
			Uri file_uri = UriFu.PathToFileUri(email_info.FullName);
			
			Indexable indexable = new FilteredIndexable (file_uri, true);

			MailDirQueryable.log.Debug("Rfc822DateTime.StringToDateTime({0})", message.Date);
			DateTime timestamp = Rfc822DateTime.FromString(message.Date);
			indexable.Timestamp = timestamp;
			indexable.Type = "MailMessage";
			indexable.MimeType = "text/plain"; // FIXME

			indexable.AddProperty (Property.NewKeyword ("dc:title", message.Subject));

            indexable.AddProperty (Property.NewKeyword ("fixme:folder",  this.folder_name));
			indexable.AddProperty (Property.NewKeyword ("fixme:subject", message.Subject));
            indexable.AddProperty (Property.NewKeyword ("fixme:to",      message.To));
			if (email_info.FullName.IndexOf("sent-mail") == -1)
				indexable.AddProperty (Property.NewKeyword ("fixme:from",    message.From));
			else
				indexable.AddProperty (Property.NewKeyword ("fixme:from",    "local@localhost")); // FIXME - where do I know the email of the sender?

			if (email_info.FullName.IndexOf("sent-mail") != -1)
				indexable.AddProperty (Property.NewDate ("fixme:sentdate", timestamp));
            else
				indexable.AddProperty (Property.NewDate ("fixme:received", timestamp));
			
			if (email_info.FullName.IndexOf("sent-mail") != -1)
				indexable.AddProperty (Property.NewFlag ("fixme:isSent"));

			StringReader reader = new StringReader (message.Body);
			indexable.SetTextReader (reader);
						
			return indexable;
		}

		public void Checkpoint ()
		{
			MailDirQueryable.log.Debug ("{0} indexed", this.folder_name);
		}

		protected FileInfo CrawlFile {
			get { return this.email_info; }
		}
	}
}

===================================================================