cvsimport
[beagle.git] / beagled / KMailQueryable / KMailQueryable.cs
blobcb475936b4acda1be14944ea6a732b6c362cf652
1 //
2 // KMailQueryable.cs
3 //
4 // Copyright (C) 2005 Novell, Inc.
5 // Copyright (C) 2005 Debajyoti Bera
6 //
7 //
8 // Permission is hereby granted, free of charge, to any person obtaining a
9 // copy of this software and associated documentation files (the "Software"),
10 // to deal in the Software without restriction, including without limitation
11 // the rights to use, copy, modify, merge, publish, distribute, sublicense,
12 // and/or sell copies of the Software, and to permit persons to whom the
13 // Software is furnished to do so, subject to the following conditions:
15 // The above copyright notice and this permission notice shall be included in
16 // all copies or substantial portions of the Software.
18 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19 // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
20 // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
21 // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
22 // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
23 // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
24 // DEALINGS IN THE SOFTWARE.
28 using System;
29 using System.Collections;
30 using System.IO;
31 using System.Threading;
33 using Beagle.Util;
35 namespace Beagle.Daemon.KMailQueryable {
37 [QueryableFlavor (Name="KMail", Domain=QueryDomain.Local, RequireInotify=false)]
38 public class KMailQueryable : LuceneFileQueryable {
40 // for non-inotify case, poll after this number of seconds
41 public const int polling_interval_in_seconds = 300;
42 // mail folder paths
43 private string local_path, dimap_path;
44 // indexers - one for each mailfolder path
45 private KMailIndexer local_indexer, dimap_indexer;
46 // global variable
47 public static bool gmime_initialized = false;
48 public static void InitializeGMime ()
50 if (!gmime_initialized) {
51 GMime.Global.Init ();
52 gmime_initialized = true;
56 // name of the sentmail folder - should be parsed from kmailrc
57 private string sentmail_foldername;
58 public string SentMailFolderName {
59 get { return sentmail_foldername; }
62 public KMailQueryable () : base ("KMailIndex")
64 // the local mail path is different for different distributions
65 local_path = GuessLocalFolderPath ();
66 if (local_path == null) {
67 Logger.Log.Info ("KMail folders not found. Will keep trying ");
68 } else
69 Logger.Log.Info ("Guessing for location of KMail folders ... found at " + local_path);
70 // I hope there is no ambiguity over imap path :P
71 dimap_path = Path.Combine (PathFinder.HomeDir, ".kde");
72 dimap_path = Path.Combine (dimap_path, "share");
73 dimap_path = Path.Combine (dimap_path, "apps");
74 dimap_path = Path.Combine (dimap_path, "kmail");
75 dimap_path = Path.Combine (dimap_path, "dimap");
77 local_indexer = null;
78 dimap_indexer = null;
79 sentmail_foldername = "sent-mail";
82 //////////////////////////////////////////////////////////////////////////////////////////////
84 /**
85 * initial method called by the daemon
87 public override void Start ()
89 base.Start ();
90 ExceptionHandlingThread.Start (new ThreadStart (StartWorker));
93 /**
94 * for non-inotify case, this method is invoked repeatedly
96 private void CrawlHook (Scheduler.Task task)
98 if (local_indexer != null)
99 local_indexer.Crawl ();
100 if (dimap_indexer != null)
101 dimap_indexer.Crawl ();
102 task.Reschedule = true;
103 task.TriggerTime = DateTime.Now.AddSeconds (polling_interval_in_seconds);
107 * called by Start(), starts actual work
108 * create indexers
109 * ask indexers to crawl the mails
110 * for non-inotify case, ask to poll
112 private void StartWorker ()
114 Logger.Log.Info ("Starting KMail backend");
116 Stopwatch stopwatch = new Stopwatch ();
117 stopwatch.Start ();
119 // check if there is at all anything to crawl
120 if ( local_path == null && (!Directory.Exists (dimap_path))) {
121 GLib.Timeout.Add (60000, new GLib.TimeoutHandler (CheckForExistence));
122 Logger.Log.Debug ("KMail directories (local mail) " + dimap_path + " not found, will repoll.");
123 return;
126 Logger.Log.Debug ("Starting mail crawl");
127 if (local_path != null) {
128 local_indexer = new KMailIndexer (this, "local", local_path);
129 local_indexer.Crawl ();
131 // FIXME: parse kmailrc to get dimap account name
132 if (Directory.Exists (dimap_path)) {
133 dimap_indexer = new KMailIndexer (this, "dimap", dimap_path);
134 dimap_indexer.Crawl ();
136 Logger.Log.Debug ("Mail crawl done");
138 if (! Inotify.Enabled) {
139 Scheduler.Task task = Scheduler.TaskFromHook (new Scheduler.TaskHook (CrawlHook));
140 task.Tag = "Crawling Maildir directories";
141 task.Source = this;
142 task.TriggerTime = DateTime.Now.AddSeconds (polling_interval_in_seconds);
143 ThisScheduler.Add (task);
146 stopwatch.Stop ();
147 Logger.Log.Info ("KMail driver worker thread done in {0}", stopwatch);
150 /**
151 * use this method to determine if we have anything to crawl and index
153 private bool CheckForExistence ()
155 local_path = GuessLocalFolderPath ();
156 if (local_path == null && (!Directory.Exists (dimap_path)))
157 return true;
159 StartWorker();
160 return false;
163 /////////////////////////////////////////////////////////////////////////////
165 override public string GetSnippet (string[] query_terms, Hit hit)
167 Logger.Log.Debug ("Fetching snippet for " + hit.Uri.LocalPath);
168 // FIXME: Also handle mbox emails
169 if (! hit.Uri.IsFile)
170 return null;
172 // Dont get snippets from attachments, they arent even indexed currently
173 if (hit.ParentUri != null)
174 return null;
176 int mail_fd = Mono.Unix.Native.Syscall.open (hit.Uri.LocalPath, Mono.Unix.Native.OpenFlags.O_RDONLY);
177 if (mail_fd == -1)
178 return null;
180 InitializeGMime ();
181 GMime.StreamFs stream = new GMime.StreamFs (mail_fd);
182 GMime.Parser parser = new GMime.Parser (stream);
183 GMime.Message message = parser.ConstructMessage ();
184 stream.Dispose ();
185 parser.Dispose ();
187 bool html = false;
188 string body = message.GetBody (true, out html);
189 // FIXME: Also handle snippets from html message parts - involves invoking html filter
190 if (html) {
191 Logger.Log.Debug ("No text/plain message part in " + hit.Uri);
192 message.Dispose ();
193 return null;
196 StringReader reader = new StringReader (body);
197 string snippet = SnippetFu.GetSnippet (query_terms, reader);
198 message.Dispose ();
200 return snippet;
203 /////////////////////////////////////////////////////////////////////////////
205 // FIXME: How to determine if an mbox hit is valid without scanning the whole file
207 public string Name {
208 get { return "KMail"; }
211 /**
212 * path of local maildir - mine is in ~/.Mail
213 * This is distribution specific. Mandrake puts kmail mails in
214 * ~/.Mail whereas default kmail folder location is ~/Mail
215 * I guess each distribution can fix this path as they know what is
216 * the path.
217 * It is possible to have the path specified in kmailrc. It might not
218 * be present, in which case try to play a guessing game.
219 * Till then, using a guesser to find out which of ~/.Mail and ~/Mail
220 * is valid.
221 * Guesses the kmail local folder path
222 * first try ~/.Mail, then try ~/Mail
223 * then try ~/.kde/share/apps/kmail/mail
225 private string GuessLocalFolderPath ()
227 string locationrc = GetLocalFolderPathFromKmailrc ();
228 //Logger.Log.Debug ("Reading kmail local-mail location from kmailrc: " +
229 // (locationrc == null ? "Unavailable" : locationrc));
230 string location1 = Path.Combine (PathFinder.HomeDir, "Mail");
231 string location2 = Path.Combine (PathFinder.HomeDir, ".Mail");
233 string location3 = Path.Combine (PathFinder.HomeDir, ".kde");
234 location3 = Path.Combine (location3, "share");
235 location3 = Path.Combine (location3, "apps");
236 location3 = Path.Combine (location3, "kmail");
237 location3 = Path.Combine (location3, "mail");
239 if (locationrc != null && GuessLocalFolder (locationrc))
240 return locationrc;
241 else if (GuessLocalFolder (location1))
242 return location1;
243 else if (GuessLocalFolder (location2))
244 return location2;
245 else if (GuessLocalFolder (location3))
246 return location3;
247 else
248 return null;
252 * to check if the path represents a kmail directory:
253 * for all directories and files named "ddd" and not starting with a '.',
254 * there should be matching index file named .ddd.index
256 private bool GuessLocalFolder (string path)
258 if (! Directory.Exists (path))
259 return false;
260 bool flag = true;
262 foreach (string subdirname in DirectoryWalker.GetDirectoryNames (path)) {
263 if (subdirname.StartsWith ("."))
264 continue;
265 // index-file name is of pattern .name.index
266 string indexfile = Path.Combine (path, "." + subdirname + ".index");
267 if (! File.Exists (indexfile)) {
268 flag = false;
269 Logger.Log.Warn ( "KMail backend: " +
270 path +
271 " contains a maildir directory but no corresponding index file. Probably not a KMail mail directory. Ignoring this location!");
272 break;
276 if (! flag)
277 return false;
279 foreach (FileInfo file in DirectoryWalker.GetFileInfos (path)) {
280 if (file.Name.StartsWith ("."))
281 continue;
282 // index-file name is of pattern .name.index
283 string indexfile = Path.Combine (path, "." + file.Name + ".index");
284 if (! File.Exists (indexfile)) {
285 flag = false;
286 Logger.Log.Warn ( "KMail backend: " +
287 path +
288 " contains an mbox file but no corresponding index file. Probably not a KMail mail directory. Ignoring this location!");
289 break;
293 return flag;
297 * tries to extract folder name from ~/.kde/share/config/kmailrc
299 private string GetLocalFolderPathFromKmailrc ()
301 string kmailrc = Path.Combine (PathFinder.HomeDir, ".kde");
302 kmailrc = Path.Combine (kmailrc, "share");
303 kmailrc = Path.Combine (kmailrc, "config");
304 kmailrc = Path.Combine (kmailrc, "kmailrc");
306 if (File.Exists (kmailrc)) {
307 StreamReader reader = new StreamReader (kmailrc);
308 string section = "";
309 string line;
311 try {
312 while ((line = reader.ReadLine ()) != null) {
313 if (line.StartsWith ("[") && line.EndsWith ("]")) {
314 section = line;
316 if (section == "[General]") {
317 if (line.StartsWith ("folders=") && line.Length > 8) {
318 return StringFu.ExpandEnvVariables (line.Substring(8));
322 } finally {
323 reader.Close ();
327 return null;