--- Util/Makefile.am	2006-04-12 21:56:27.000000000 +0200
+++ Util/Makefile.am	2006-04-12 22:02:27.000000000 +0200
@@ -65,6 +65,7 @@
 	$(srcdir)/Log.cs			\
 	$(srcdir)/Logger.cs             	\
 	$(srcdir)/MetadataStore.cs		\
+	$(srcdir)/Mork.cs			\
 	$(srcdir)/Mozilla.cs	        	\
 	$(srcdir)/MultiReader.cs        	\
 	$(srcdir)/NautilusTools.cs      	\
--- Util/Mork.cs	1970-01-01 01:00:00.000000000 +0100
+++ Util/Mork.cs	2006-04-12 22:02:27.000000000 +0200
@@ -0,0 +1,234 @@
+//
+// Mork.cs
+//
+// Copyright (C) 2006 Pierre Östlund
+//
+
+//
+// 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.IO;
+using System.Collections;
+using System.Text.RegularExpressions;
+
+using GMime;
+
+namespace Beagle.Util
+{
+	public enum DataType {
+		Key,
+		Value,
+		PointerValue
+	}
+	
+	public class MorkDatabase : IEnumerable
+	{
+		private string mork_file;
+		private int post_header = 0;
+
+		private Queue interesting_temp;
+		private Hashtable interesting_keys;
+		private Hashtable db_content;
+		private ArrayList List;
+
+		// Regex related stuff
+		private string regex_header = @"<\s*<\(a=c\)> ([^>]*)>";
+		private string regex_entries = @"\[(-|)(?<id>([0-9A-Fa-f]+))(?<entry>\s*\(\^88(?>[^\[\]]+))*\]";
+		private string regex_content = @"\((?<key>\w+)\s*=(?<value>(?>[^()]+))";
+
+		public MorkDatabase (string file)
+		{
+			mork_file = file;
+			interesting_temp = new Queue ();
+			interesting_keys = new Hashtable ();
+			db_content = new Hashtable ();
+			List = new ArrayList ();
+		}
+
+		public void ReadDatabase ()
+		{
+			string file_content;
+			StreamReader reader;
+			
+			reader = new StreamReader (mork_file);
+			file_content = reader.ReadToEnd ();
+			reader.Close ();
+			
+			// Read and parse the different types stored in this database
+			ReadHeader (file_content);
+
+			// Read database entries
+			ReadEntries (file_content);
+			
+			// Read the actual content (usernames, email-addresses etc)
+			ReadContent (file_content.Substring (post_header));
+		}
+
+		private void ReadHeader (string content)
+		{
+			Match match = Regex.Match (content, 
+				regex_header, RegexOptions.Compiled);
+
+			while (interesting_temp.Count > 0) {
+				string key = (string) interesting_temp.Dequeue ();
+				interesting_keys [key] = Extract (match.Value, key, DataType.Key);
+			}
+			
+			post_header = match.Length + 34;
+		}
+		
+		private void ReadEntries (string content)
+		{
+			Regex entries = new Regex (regex_entries,
+				RegexOptions.Compiled | RegexOptions.IgnorePatternWhitespace);
+
+			foreach (Match m in entries.Matches (content)) {
+				// Add the id as any other entry
+				string entry = m.Result ("(id=${id})${entry}");
+
+				if (entry.Length > 0)
+					List.Add (entry);
+			}
+		}
+		
+		private void ReadContent (string content)
+		{
+			Regex reg = new Regex (regex_content,
+				RegexOptions.Compiled | RegexOptions.IgnorePatternWhitespace);
+			
+			foreach (Match m in reg.Matches (content))
+				db_content [m.Result ("${key}")] = m.Result ("${value}");
+		}
+		
+		public static string Extract (string str, string match, DataType type)
+		{
+			Regex reg;
+			string value = null;
+
+			try {
+				
+				switch (type) {
+				case DataType.Key:
+					reg = new Regex (@"(?<key>\w+)=(" + match + ")", RegexOptions.Compiled);
+					value = reg.Match (str).Result ("${key}");
+					break;
+				case DataType.Value:
+					reg = new Regex (@"(" + match + @")=(?<value>\w+)", RegexOptions.Compiled);
+					value = reg.Match (str).Result ("${value}");
+					break;
+				case DataType.PointerValue:
+					reg = new Regex (@"(\^" + match + @")\^(?<value>\w+)", RegexOptions.Compiled);
+					value = reg.Match (str).Result ("${value}");
+					break;
+				}
+				
+			} catch (Exception e) { }
+			
+			return value;
+		}
+
+		public Hashtable CompileTable (string raw)
+		{
+			Hashtable tbl = new Hashtable ();
+
+			foreach (string id in interesting_keys.Keys) {
+				string data = Extract (raw, (string) interesting_keys [id], DataType.PointerValue);
+
+				if (data == null) {
+					// We are probably dealing with inline values if we reach this point
+					data = Extract (raw, (string) interesting_keys [id], DataType.Value);
+					
+					tbl [id] = data;
+				} else
+					tbl [id] = db_content [data];
+			}
+			
+			tbl ["id"] = Extract (raw, "id", DataType.Value);
+
+			return tbl;
+		}
+		
+		public void AddHeaderKey (string key)
+		{
+			interesting_temp.Enqueue (key);
+		}
+		
+		public void AddHeaderKey (string[] key)
+		{
+			foreach (string tmp in key)
+				AddHeaderKey (tmp);
+		}
+		
+		public void Reset ()
+		{
+			mork_file = null;
+			post_header = 0;
+			interesting_temp.Clear ();
+			interesting_keys.Clear ();
+			db_content.Clear ();
+			List.Clear ();
+		}
+
+		public string Filename {
+			get { return mork_file; }
+			set { mork_file = value; }
+		}
+		
+		public int Count {
+			get { return List.Count; }
+		}
+		
+		public IEnumerator GetEnumerator ()
+		{
+			return new MorkEnumerator (this);
+		}
+		
+		public class MorkEnumerator : IEnumerator
+		{
+			private MorkDatabase md;
+			private int position = -1;
+			
+			public MorkEnumerator (MorkDatabase t)
+			{
+				this.md = t;
+			}
+			
+			public bool MoveNext ()
+			{
+				if (position < md.List.Count -1) {
+					position++;
+					return true;
+				} else
+					return false;
+			}
+			
+			public void Reset ()
+			{
+				position = -1;
+			}
+			
+			public object Current {
+				get { return md.List [position]; }
+			}
+		}
+	}
+}
+
--- beagled/Makefile.am	2006-04-12 21:56:28.000000000 +0200
+++ beagled/Makefile.am	2006-04-12 22:34:01.000000000 +0200
@@ -290,6 +290,14 @@
 	$(kmailqueryable)/KMailIndexableGenerator.cs	\
 	$(kmailqueryable)/KMailIndexer.cs
 
+thunderbirdqueryable = $(srcdir)/ThunderbirdQueryable
+THUNDERBIRD_QUERYABLE_CSFILES =				\
+	$(thunderbirdqueryable)/ThunderbirdAccount.cs	\
+	$(thunderbirdqueryable)/ThunderbirdIndexableGenerator.cs \
+	$(thunderbirdqueryable)/ThunderbirdIndexer.cs	\
+	$(thunderbirdqueryable)/ThunderbirdMail.cs	\
+	$(thunderbirdqueryable)/ThunderbirdQueryable.cs
+
 blamqueryable = $(srcdir)/BlamQueryable
 BLAM_QUERYABLE_CSFILES =				\
 	$(blamqueryable)/BlamQueryable.cs
@@ -318,6 +326,7 @@
 DAEMON_DLL_CSFILES = 						\
 	$(LUCENE_CSFILES)					\
 	$(KMAIL_QUERYABLE_CSFILES)				\
+	$(THUNDERBIRD_QUERYABLE_CSFILES)			\
 	$(FILE_SYSTEM_QUERYABLE_CSFILES)			\
 	$(GAIM_LOG_QUERYABLE_CSFILES)				\
 	$(INDEXING_SERVICE_QUERYABLE_CSFILES)			\
--- beagled/ThunderbirdQueryable/ThunderbirdAccount.cs	1970-01-01 01:00:00.000000000 +0100
+++ beagled/ThunderbirdQueryable/ThunderbirdAccount.cs	2006-04-12 22:02:28.000000000 +0200
@@ -0,0 +1,69 @@
+//
+// ThunderbirdAccount.cs
+//
+// Copyright (C) 2006 Pierre Östlund
+//
+
+//
+// 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;
+
+namespace Beagle.Daemon.ThunderbirdQueryable {
+
+	public enum AccountType {
+		Pop3,
+		Imap,
+		Invalid
+	}
+
+	public class ThunderbirdAccount
+	{
+		private string account_name;
+		private int server_port = -1;
+		private AccountType account_type;
+		private string directory;
+		
+		public ThunderbirdAccount (string name, int server_port, AccountType type, string directory)
+		{
+			this.account_name = name;
+			this.server_port = server_port;
+			this.account_type = type;
+			this.directory = directory;
+		}
+
+		public string Name {
+			get { return account_name; }
+		}
+		
+		public int Port {
+			get { return server_port; }
+		}
+		
+		public AccountType Type {
+			get { return account_type; } 
+		}
+		
+		public string Directory {
+			get { return directory; }
+		}
+	}
+}
+
--- beagled/ThunderbirdQueryable/ThunderbirdIndexableGenerator.cs	1970-01-01 01:00:00.000000000 +0100
+++ beagled/ThunderbirdQueryable/ThunderbirdIndexableGenerator.cs	2006-04-12 22:02:28.000000000 +0200
@@ -0,0 +1,307 @@
+//
+// ThunderbirdIndexableGenerator.cs
+//
+// Copyright (C) 2006 Pierre Östlund
+//
+
+//
+// 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.IO;
+using System.Collections;
+
+using Beagle.Util;
+using Beagle.Daemon;
+
+namespace Beagle.Daemon.ThunderbirdQueryable {
+	
+	public class ThunderbirdIndexableGenerator : IIndexableGenerator
+	{
+		private ThunderbirdIndexer indexer;
+		private ThunderbirdAccount account;
+		private MorkDatabase database = null;
+		private Queue mork_files;
+		
+		private bool full_index = false;
+		private string current_mork_file = null;
+		private IEnumerator mail_enumerator = null; // used with database
+
+		private Inotify.Watch inotify_watch = null;
+		private FileSystemWatcher filesystem_watch = null;
+		
+		public ThunderbirdIndexableGenerator (
+			ThunderbirdIndexer indexer, ThunderbirdAccount account)
+		{
+			this.indexer = indexer;
+			this.account = account;
+			this.mork_files = new Queue ();
+			
+			// Read all mork files inside this accounts directory
+			Queue pending = new Queue ();
+			pending.Enqueue (account.Directory);
+			
+			// Find all .msf files (mork files)
+			while (pending.Count > 0) {
+				string dir = (pending.Dequeue () as string);
+
+				foreach (string subdir in DirectoryWalker.GetDirectories (dir)) {
+					if (Shutdown.ShutdownRequested)
+						return;
+					
+					pending.Enqueue (subdir);
+				}
+				
+				foreach (string file in DirectoryWalker.GetItems (dir, new DirectoryWalker.FileFilter(IsMorkFile)))
+					mork_files.Enqueue (file);
+			}
+			
+			Subscribe ();
+			
+			try {
+				UpdateCurrentMailbox ();
+			} catch (Exception e) {
+				Logger.Log.Warn ("Failed updating mailbox: {0}", e.Message);
+			}
+		}
+		
+		private bool IsMorkFile (string path, string filename)
+		{
+			if (Path.GetExtension (filename) == ".msf")
+				return true;
+		
+			return false;
+		}
+		
+		public bool HasNextIndexable ()
+		{
+			do {
+				// Move on as long as we have emails to index
+				while (mail_enumerator == null || !mail_enumerator.MoveNext ()) {
+
+					if (mork_files.Count <= 0 || 
+					    (current_mork_file = (string) mork_files.Dequeue ()) ==null ||
+					    Shutdown.ShutdownRequested) {
+						Unsubscribe ();
+						return false;
+					}
+				
+					// We've moved to a new mailbox, we need to update our database object
+					try {
+						UpdateCurrentMailbox ();
+					} catch (Exception e) {
+							Logger.Log.Warn ("Failed updating mailbox: {0}", e.Message);
+					}
+				}
+					
+			} while (IsUpToDate ((string) mail_enumerator.Current));
+			
+			return true;
+		}
+
+		public Indexable GetNextIndexable ()
+		{
+			GMime.Message message = null;
+
+			try {
+				// Compile the basic mail structure (we _really_ need this!)
+				Hashtable tbl = database.CompileTable ((string) mail_enumerator.Current);
+				ThunderbirdMail mail = new ThunderbirdMail (tbl);
+
+				// Try to create a GMime.Message
+				if (full_index) {
+					try {
+						message = indexer.FullMessage (mail, indexer.GetMailboxFile (database.Filename));
+					} catch (Exception e) { 
+						Logger.Log.Debug ("Full index failed {0}: {1}", mail.Id, e.Message);
+					}
+				}
+			
+				// Fallback code in case we can't do a full index
+				if (message == null)
+					message = indexer.PartialMessage (mail);
+				
+				Indexable indexable = indexer.MorkMailToIndexable (account, mail, message, GetCurrentMailbox());
+
+				return indexable;
+			} finally {
+				if (message != null)
+					message.Dispose ();
+			}
+		}
+		
+		public void PostFlushHook ()
+		{
+		}
+		
+		private void UpdateCurrentMailbox ()
+		{
+			if (current_mork_file == null)
+				current_mork_file = (string) mork_files.Dequeue ();
+
+			// Try to load this mailbox
+			database = new MorkDatabase (current_mork_file);
+			database.AddHeaderKey (new string[] {"id", "sender", "subject", "recipients", "date"});
+			
+			switch (account.Type) {
+			case AccountType.Pop3:
+			case AccountType.Invalid: // FIXME: we use pop3 as a fallback for now
+				database.AddHeaderKey ( "size");
+				break;
+			case AccountType.Imap:
+				database.AddHeaderKey (new string[] {"msgOffset", "offlineMsgSize"});
+				break;
+			}
+			
+			try {
+				database.ReadDatabase ();
+			} catch (Exception e) {
+				// If we reached this point we probably found a malformed or empty
+				// mork-file. Just exit silently and let other metods take care of the rest.
+				mail_enumerator = null;
+				return;
+			}
+
+			Logger.Log.Debug ("Switched mailbox to {0} ({1})", GetCurrentMailbox (), account.Name);
+
+			// If full_index is set to true, that means all mails are stored on the harddrive and
+			// mails are to be fully indexed
+			full_index = (indexer.IsFullyIndexable (database.Filename) ? true : false);
+
+			mail_enumerator = database.GetEnumerator ();
+		}
+		
+		public string GetCurrentMailbox ()
+		{
+			string mailbox = null;
+			
+			if (Path.GetExtension (database.Filename) != ".msf")
+				return mailbox;
+			
+			switch (account.Type) {
+			case AccountType.Pop3:
+			case AccountType.Invalid:
+				mailbox = database.Filename.Substring (account.Directory.Length+1);
+				mailbox = indexer.GetMailboxFile (mailbox);
+				break;
+			case AccountType.Imap:
+				if (Path.GetFileName (database.Filename) == "INBOX.msf")
+					mailbox = ".INBOX";
+				else {
+					mailbox = database.Filename.Substring (account.Directory.Length+1);
+					mailbox = indexer.GetMailboxFile (mailbox);
+					mailbox = "." + mailbox.Replace (".sbd/", ".");	
+				}
+				
+				break;
+			}
+			
+			return mailbox;
+		}
+		
+		public void Subscribe ()
+		{
+			if (Inotify.Enabled && inotify_watch == null) {			
+				Inotify.EventType mask = Inotify.EventType.Delete | 
+					Inotify.EventType.MovedTo | Inotify.EventType.MovedFrom |
+					Inotify.EventType.Modify | Inotify.EventType.Create;
+
+				inotify_watch = Inotify.Subscribe (account.Directory, OnInotifyEvent, mask);
+				
+			} else if (filesystem_watch == null) {
+				filesystem_watch = new FileSystemWatcher ();
+				filesystem_watch.Path = account.Directory;
+
+				filesystem_watch.Changed += new FileSystemEventHandler (OnFileSystemWatcher);
+				filesystem_watch.Created += new FileSystemEventHandler (OnFileSystemWatcher);
+				filesystem_watch.Deleted += new FileSystemEventHandler (OnFileSystemWatcher);
+
+				filesystem_watch.EnableRaisingEvents = true;
+			}
+		}
+		
+		public void Unsubscribe ()
+		{
+			if (Inotify.Enabled && inotify_watch != null)
+				inotify_watch.Unsubscribe ();
+			else if (filesystem_watch != null) {
+				filesystem_watch.Changed -= new FileSystemEventHandler (OnFileSystemWatcher);
+				filesystem_watch.Created -= new FileSystemEventHandler (OnFileSystemWatcher);
+				filesystem_watch.Deleted -= new FileSystemEventHandler (OnFileSystemWatcher);
+				filesystem_watch.EnableRaisingEvents = false;
+				filesystem_watch = null;
+			}
+		}
+		
+		public bool AddMorkFile (string filename)
+		{
+			if (!filename.EndsWith (".msf"))
+				return false;
+			
+			foreach (string existing in mork_files)
+				if (existing == filename)
+					return false;
+			
+			mork_files.Enqueue (filename);
+			
+			return true;
+		}
+		
+		// FIXME: Use this method to check if a mail needs to be indexed
+		public bool IsUpToDate (string filename)
+		{
+			return false;
+		}
+		
+		public void CheckEvent (string filename)
+		{
+			bool update = false;
+			
+			if (!(update = AddMorkFile (filename))) {
+				if (File.Exists (filename + ".msf"))
+					update = AddMorkFile (filename + ".msf");
+			}
+
+			if (update) {
+				mail_enumerator = null;
+				Logger.Log.Info ("Update in {0}: this file will be re-indexed", filename);
+			}
+		}
+		
+		private void OnInotifyEvent (Inotify.Watch watch,
+						string path,
+						string subItem,
+						string srcpath,
+						Inotify.EventType type)
+		{
+			CheckEvent (Path.Combine (path, subItem));
+		}
+		
+		private void OnFileSystemWatcher (object o, FileSystemEventArgs args)
+		{
+			CheckEvent (args.FullPath);
+		}
+
+		public string StatusName {
+			get { return account.Name; }
+		}
+	}
+}
+
--- beagled/ThunderbirdQueryable/ThunderbirdIndexer.cs	1970-01-01 01:00:00.000000000 +0100
+++ beagled/ThunderbirdQueryable/ThunderbirdIndexer.cs	2006-04-12 22:02:28.000000000 +0200
@@ -0,0 +1,205 @@
+//
+// ThunderbirdIndexer.cs
+//
+// Copyright (C) 2006 Pierre Östlund
+//
+
+//
+// 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.IO;
+using System.Collections;
+
+using Beagle.Util;
+using Beagle.Daemon;
+
+using GMime;
+
+namespace Beagle.Daemon.ThunderbirdQueryable {
+
+	public class ThunderbirdIndexer
+	{
+		private ArrayList account_list;
+		private ThunderbirdQueryable queryable;
+		
+		public ThunderbirdIndexer (ThunderbirdQueryable queryable, ArrayList account_list)
+		{
+			this.account_list = account_list;
+			this.queryable = queryable;
+		}
+
+		public void Crawl ()
+		{
+			// Should not happen!
+			if (account_list == null)
+				return;
+
+			foreach (ThunderbirdAccount account in account_list) {
+				if (Shutdown.ShutdownRequested)
+					return;
+					
+				IndexAccount(account);
+			}
+		}
+
+		// Use this method to index a file
+		public void IndexAccount (ThunderbirdAccount account)
+		{
+			IIndexableGenerator generator = null;;
+			
+			// Make sure we don't index this if it's already being indexed
+			if (queryable.ThisScheduler.ContainsByTag (account.Name)) {
+				Logger.Log.Debug ("Not adding a task for already running task: {0}", account.Name);
+				return;
+			}
+
+			generator = new ThunderbirdIndexableGenerator (this, account);
+			
+			if (generator != null)
+				AddIIndexableTask (generator, account.Directory);
+			else
+				Logger.Log.Debug ("Unable to create generator for: {0}", account.Name);
+		}
+		
+		public bool IsFullyIndexable (string mork_file)
+		{
+			FileInfo file;
+			
+			try {
+				file = new FileInfo (GetMailboxFile (mork_file));
+
+				return (file.Length > 0 ? true : false);
+				
+			} catch (Exception e) { }
+
+			return false;
+		}
+		
+		public  string GetMailboxFile (string mork_file)
+		{
+			string mailbox_file = Path.Combine (
+				Path.GetDirectoryName (mork_file), 
+				Path.GetFileNameWithoutExtension (mork_file));
+			
+			return mailbox_file;
+		}
+		
+		// Add this object to indexing queue
+		private void AddIIndexableTask (IIndexableGenerator generator, string tag)
+		{
+			if (generator == null)
+				return;
+
+			Scheduler.Task task = queryable.NewAddTask (generator);
+			task.Tag = tag;
+			queryable.ThisScheduler.Add (task);
+		}
+
+		public static Uri NewMailUri (ThunderbirdAccount account, string mailbox, string id)
+		{
+			Uri uri = null;
+			
+			switch (account.Type) {
+				case AccountType.Pop3:
+				case AccountType.Invalid:
+					uri = new Uri (String.Format ("mailbox://{0}/{1}?number={2}", 
+						account.Directory, mailbox, Convert.ToInt32 (id, 16))); 
+					break;
+				case AccountType.Imap:
+					uri = new Uri (String.Format ("imap://{0}:{1}/fetch%3EUID%3E{2}%3E{3}",
+						account.Name, account.Port, mailbox, Convert.ToInt32 (id, 16)));
+					break;
+			}
+
+			return uri;
+		}
+		
+		// Partially stolen from the Evolution backend
+		public Indexable MorkMailToIndexable (ThunderbirdAccount account,
+			ThunderbirdMail mail, GMime.Message message, string mailbox)
+		{
+			Indexable indexable;
+
+			indexable = new Indexable (NewMailUri (account, mailbox, mail.Id));		
+			indexable.HitType = "MailMessage";
+			indexable.MimeType = "message/rfc822";
+			indexable.CacheContent = false;
+
+			indexable.AddProperty (Property.NewKeyword ("fixme:client", "thunderbird"));
+			indexable.AddProperty (Property.NewKeyword ("fixme:account", account.Name));
+			indexable.AddProperty (Property.NewKeyword ("fixme:folder", mailbox));
+
+			GMime.InternetAddressList addrs;
+
+			addrs = message.GetRecipients (GMime.Message.RecipientType.To);
+			foreach (GMime.InternetAddress ia in addrs) {
+				if (ia.AddressType != GMime.InternetAddressType.Group)
+					indexable.AddProperty (Property.NewKeyword ("fixme:sentTo", ia.Addr));
+			}
+			addrs.Dispose ();
+
+			addrs = message.GetRecipients (GMime.Message.RecipientType.Cc);
+			foreach (GMime.InternetAddress ia in addrs) {
+				if (ia.AddressType != GMime.InternetAddressType.Group)
+					indexable.AddProperty (Property.NewKeyword ("fixme:sentTo", ia.Addr));
+			}
+			addrs.Dispose ();
+
+			addrs = GMime.InternetAddressList.ParseString (GMime.Utils.HeaderDecodePhrase (message.Sender));
+			foreach (GMime.InternetAddress ia in addrs) {
+				if (ia.AddressType != GMime.InternetAddressType.Group)
+					indexable.AddProperty (Property.NewKeyword ("fixme:gotFrom", ia.Addr));
+			}
+			addrs.Dispose ();
+			
+			indexable.SetBinaryStream (message.Stream);
+
+			return indexable;
+		}
+
+		public GMime.Message PartialMessage (ThunderbirdMail mail)
+		{
+			GMime.Message message = new GMime.Message (true);
+
+			message.Subject = mail.Subject;
+			message.Sender = mail.Sender;
+			message.SetDate (DateTime.Parse (mail.Date), 0); // FIXME: Timezone awareness
+
+			return message;
+		}
+
+		public GMime.Message FullMessage (ThunderbirdMail mail, string mail_file)
+		{
+			int fd = Mono.Unix.Native.Syscall.open (
+				mail_file, Mono.Unix.Native.OpenFlags.O_RDONLY);
+			
+			StreamFs stream = new StreamFs (fd, mail.MsgOffset, mail.MsgOffset + mail.OfflineMsgSize);
+			Parser parser = new Parser (stream);
+			GMime.Message message = parser.ConstructMessage ();
+			
+			stream.Dispose ();
+			parser.Dispose ();
+
+			return message;
+		}
+	}
+}
+
--- beagled/ThunderbirdQueryable/ThunderbirdMail.cs	1970-01-01 01:00:00.000000000 +0100
+++ beagled/ThunderbirdQueryable/ThunderbirdMail.cs	2006-04-12 22:02:28.000000000 +0200
@@ -0,0 +1,111 @@
+//
+// ThunderbirdMail.cs
+//
+// Copyright (C) 2006 Pierre Östlund
+//
+
+//
+// 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.IO;
+using System.Collections;
+using System.Globalization;
+
+using GMime;
+
+namespace Beagle.Daemon.ThunderbirdQueryable
+{
+	public class ThunderbirdMail
+	{
+		private string id;
+		private string sender;
+		private string subject;
+		private string recipients;
+		private string date;
+		private int size = -1;
+		private int msg_offset = -1;
+		private int offline_msg_size = -1;
+
+		public ThunderbirdMail (Hashtable tbl)
+		{
+			foreach (string key in tbl.Keys) {
+				if (key == "id")
+					id = (string) tbl [key];
+				else if (key == "sender")
+					sender = Utils.HeaderDecodePhrase ((string) tbl [key]);
+				else if (key == "subject")
+					subject = Utils.HeaderDecodeText ((string) tbl [key]);
+				else if (key == "recipients")
+					recipients = Utils.HeaderDecodePhrase ((string) tbl [key]);
+				else if (key == "date")
+					date = HexDateToString ((string) tbl [key]);
+				else if (key == "size")
+					size = Convert.ToInt32((string) tbl [key], 16);
+				else if (key == "msgOffset")
+					msg_offset = Convert.ToInt32 ((string) tbl [key], 16);
+				else if (key == "offlineMsgSize")
+					offline_msg_size = Convert.ToInt32 ((string) tbl [key], 16);
+			}
+		}
+		
+		// FIXME: Timezone-awareness has to be added
+		public static string HexDateToString (string hex)
+		{
+			DateTime time = new DateTime (1970,1,1,0,0,0);
+
+			try {
+				time = time.AddSeconds (
+					Int32.Parse (hex, NumberStyles.HexNumber));
+			} catch (Exception e) { }
+			
+			return time.ToString ();
+		}
+
+		public string Id {
+			get { return id; }
+		}
+
+		public string Sender {
+			get { return sender; }
+		}
+
+		public string Subject {
+			get { return subject; }
+		}
+		
+		public string Recipients {
+			get { return recipients; }
+		}
+		
+		public string Date {
+			get { return  date; }
+		}
+
+		public int MsgOffset {
+			get { return (msg_offset >= 0 ? msg_offset : Convert.ToInt32 (id, 16)); }
+		}
+		
+		public int OfflineMsgSize {
+			get { return (offline_msg_size >= 0 ? offline_msg_size : size); }
+		}
+	}
+}
+
--- beagled/ThunderbirdQueryable/ThunderbirdQueryable.cs	1970-01-01 01:00:00.000000000 +0100
+++ beagled/ThunderbirdQueryable/ThunderbirdQueryable.cs	2006-04-12 22:02:28.000000000 +0200
@@ -0,0 +1,191 @@
+//
+// ThunderbirdQueryable.cs
+//
+// Copyright (C) 2006 Pierre Östlund
+//
+
+//
+// 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.IO;
+using System.Threading;
+using System.Collections;
+using System.Text.RegularExpressions;
+
+using Beagle.Util;
+
+namespace Beagle.Daemon.ThunderbirdQueryable {
+
+	[QueryableFlavor (Name = "Thunderbird", Domain = QueryDomain.Local, RequireInotify = false)]
+	public class ThunderbirdQueryable : LuceneFileQueryable {
+	
+		private string root_dir;
+		private string profile_dir = null;
+		private ArrayList account_list;
+		private ThunderbirdIndexer thunderbird_indexer;
+	
+		public ThunderbirdQueryable () :
+			base ("ThunderbirdIndex")
+		{
+			thunderbird_indexer = null;
+			account_list = new ArrayList ();
+			root_dir = Path.Combine (PathFinder.HomeDir, ".thunderbird");
+			
+			GMime.Global.Init ();
+		}
+		
+		public override void Start ()
+		{
+			base.Start ();
+			ExceptionHandlingThread.Start (new ThreadStart (StartWorker));
+		}
+		
+		private void StartWorker ()
+		{
+			Logger.Log.Info ("Starting Thunderbird backend");
+
+			try {
+				// Check if the root dir of thunderbird exists
+				if (!Directory.Exists (root_dir))
+					throw new Exception ("No Thunderbird data to index found in " + 
+						root_dir + "! Trying again in a minute...");
+				
+				// Get profile path from profiles.ini
+				profile_dir = GetProfilePath (root_dir);
+				
+				// Try to read all mail accounts available
+				ReadThunderbirdProfiles ();
+				
+			} catch (Exception e) {
+				// An error or no data to index
+				WaitForData (e.ToString ());
+				return;
+			}
+			
+			// Begin to crawl available mailboxes
+			Logger.Log.Debug ("Starting Thunderbird mail crawling ({0} accounts)", account_list.Count);
+			State = QueryableState.Crawling;
+
+			thunderbird_indexer = new ThunderbirdIndexer (this, account_list);
+			thunderbird_indexer.Crawl ();
+			
+			// We're done with initial scan
+			State = QueryableState.Idle;
+			Logger.Log.Debug ("Done crawling.");
+			
+			Logger.Log.Info ("Thunderbird backend done.");
+		}
+
+		private bool IndexDataCheck ()
+		{
+			if (!Directory.Exists (root_dir))
+				return true;
+			
+			StartWorker ();
+			return false;
+		}
+		
+		internal void WaitForData (string msg)
+		{
+			GLib.Timeout.Add (60000, new GLib.TimeoutHandler (IndexDataCheck));
+			Logger.Log.Debug (msg);
+		}
+
+		public static string GetProfilePath (string root)
+		{
+			string line, profile = null;
+			StreamReader reader;
+			
+			try {
+				reader = new StreamReader (Path.Combine (root, "profiles.ini"));
+			} catch (Exception e) { 
+				return profile;
+			}
+			
+			// Read the profile path (is multi-profile support needed?)
+			while ((line = reader.ReadLine ()) != null) {
+				if (line.StartsWith ("Path=")) {
+					profile = line.Substring (5); // Do not include the beginning "Path="
+					break;
+				}
+			}
+
+			return profile;
+		}
+
+		// FIXME: This is a _really_ ugly method, should perhaps be rewritten?
+		public void ReadThunderbirdProfiles ()
+		{
+			string line;
+			Hashtable table = new Hashtable ();
+			StreamReader reader = new StreamReader (root_dir + "/" + profile_dir + "/prefs.js");
+			Regex reg = new Regex (@"user_pref\(" + "\"" + @"mail\.(?<key>.*)" + 
+				"\"" + @"\s*,\s*" + "(\"" + @"(?<value>.*)" + "\"|" + @"(?<value>.*)" + @")\);",
+				RegexOptions.Compiled | RegexOptions.IgnorePatternWhitespace);
+			
+			// Read all mail-accounts and it's data
+			while ((line = reader.ReadLine ()) != null) {
+				try {
+					string key = reg.Match (line).Result ("${key}");
+					string value = reg.Match (line).Result ("${value}");
+					
+					if (key.StartsWith ("account.") || key.StartsWith ("server"))
+						table [key] = value;
+
+				} catch (Exception e) { }
+			}
+			reader.Close ();
+			
+			// Parse the data we actually need
+			foreach (string raw in table.Keys) {
+				Regex tmp;
+				
+				if (!raw.StartsWith ("account.account")|| !raw.EndsWith ("server"))
+					continue;
+				
+				tmp = new Regex (@"account.account(?<nr>\d)");
+				
+				try {
+					AccountType type;
+					int index = Convert.ToInt32 (tmp.Match (raw).Result ("${nr}"));
+					string type_str = (string) table ["server.server" + index + ".type"];
+					
+					if (type_str == "pop3")
+						type = AccountType.Pop3;
+					else if (type_str == "imap")
+						type = AccountType.Imap;
+					else
+						type = AccountType.Invalid;
+					
+					account_list.Add (new ThunderbirdAccount (
+						(string) table ["server.server" + index + ".name"],
+						Convert.ToInt32 ((string) table["server.server" + index + ".port"]), type,
+						(string) table ["server.server" + index + ".directory"]));
+
+				} catch (Exception e) {
+					// Just continue with the next one if we fail
+					continue;
+				}
+			}
+		}
+	}
+}
+
--- search/Tiles/MailAttachment.cs	2006-04-12 21:56:27.000000000 +0200
+++ search/Tiles/MailAttachment.cs	2006-04-12 22:02:28.000000000 +0200
@@ -64,19 +64,32 @@
 			else
 				return hit ["parent:" + name];
 		}
+		
+		public static string GetCommand(string client)
+		{
+			string command = null;
+			
+			if (client == "evolution")
+				command = "evolution";
+			else if (client == "thunderbird")
+				command = "thunderbird-bin";
+			
+			return command;
+		}
 
 		public override void Open ()
 		{
 			string uri_str;
+			string command = GetCommand (GetHitProperty (Hit, "fixme:client"));
 
-			if (GetHitProperty (Hit, "fixme:client") != "evolution") {
+			if (command == null) {
 				OpenFromMime (Hit);
 				return;
 			}
 
 			Process p = new Process ();
 			p.StartInfo.UseShellExecute = false;
-			p.StartInfo.FileName = "evolution";
+			p.StartInfo.FileName = command;
 
 			if (Hit.ParentUriAsString != null)
 				uri_str = Hit.ParentUriAsString;
--- search/Tiles/MailMessage.cs	2006-04-12 21:56:27.000000000 +0200
+++ search/Tiles/MailMessage.cs	2006-04-12 22:02:28.000000000 +0200
@@ -94,19 +94,32 @@
 
 			return details;
 		}
+		
+		public static string GetCommand(string client)
+		{
+			string command = null;
+			
+			if (client == "evolution")
+				command = "evolution";
+			else if (client == "thunderbird")
+				command = "thunderbird-bin";
+			
+			return command;
+		}
 
 		public override void Open ()
 		{
 			string uri_str;
+			string command = GetCommand (Hit.GetFirstProperty ("fixme:client"));
 
-			if (Hit.GetFirstProperty ("fixme:client") != "evolution") {
+			if (command == null) {
 				OpenFromMime (Hit);
 				return;
 			}
 
 			Process p = new Process ();
 			p.StartInfo.UseShellExecute = false;
-			p.StartInfo.FileName = "evolution";
+			p.StartInfo.FileName = command;
 
 			if (Hit.ParentUriAsString != null)
 				uri_str = Hit.ParentUriAsString;
