4 // Copyright (C) 2005 Novell, Inc.
5 // Copyright (C) 2005 Debajyoti Bera
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.
29 using System
.Collections
;
31 using System
.Threading
;
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;
43 private string local_path
, dimap_path
;
44 // indexers - one for each mailfolder path
45 private KMailIndexer local_indexer
, dimap_indexer
;
47 public static bool gmime_initialized
= false;
48 public static void InitializeGMime ()
50 if (!gmime_initialized
) {
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 ");
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");
79 sentmail_foldername
= "sent-mail";
82 //////////////////////////////////////////////////////////////////////////////////////////////
85 * initial method called by the daemon
87 public override void Start ()
90 ExceptionHandlingThread
.Start (new ThreadStart (StartWorker
));
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
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 ();
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.");
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";
142 task
.TriggerTime
= DateTime
.Now
.AddSeconds (polling_interval_in_seconds
);
143 ThisScheduler
.Add (task
);
147 Logger
.Log
.Info ("KMail driver worker thread done in {0}", stopwatch
);
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
)))
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
)
172 // Dont get snippets from attachments, they arent even indexed currently
173 if (hit
.ParentUri
!= null)
176 int mail_fd
= Mono
.Unix
.Native
.Syscall
.open (hit
.Uri
.LocalPath
, Mono
.Unix
.Native
.OpenFlags
.O_RDONLY
);
181 GMime
.StreamFs stream
= new GMime
.StreamFs (mail_fd
);
182 GMime
.Parser parser
= new GMime
.Parser (stream
);
183 GMime
.Message message
= parser
.ConstructMessage ();
188 string body
= message
.GetBody (true, out html
);
189 // FIXME: Also handle snippets from html message parts - involves invoking html filter
191 Logger
.Log
.Debug ("No text/plain message part in " + hit
.Uri
);
196 StringReader reader
= new StringReader (body
);
197 string snippet
= SnippetFu
.GetSnippet (query_terms
, reader
);
203 /////////////////////////////////////////////////////////////////////////////
205 // FIXME: How to determine if an mbox hit is valid without scanning the whole file
208 get { return "KMail"; }
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
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
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
))
241 else if (GuessLocalFolder (location1
))
243 else if (GuessLocalFolder (location2
))
245 else if (GuessLocalFolder (location3
))
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
))
262 foreach (string subdirname
in DirectoryWalker
.GetDirectoryNames (path
)) {
263 if (subdirname
.StartsWith ("."))
265 // index-file name is of pattern .name.index
266 string indexfile
= Path
.Combine (path
, "." + subdirname
+ ".index");
267 if (! File
.Exists (indexfile
)) {
269 Logger
.Log
.Warn ( "KMail backend: " +
271 " contains a maildir directory but no corresponding index file. Probably not a KMail mail directory. Ignoring this location!");
279 foreach (FileInfo file
in DirectoryWalker
.GetFileInfos (path
)) {
280 if (file
.Name
.StartsWith ("."))
282 // index-file name is of pattern .name.index
283 string indexfile
= Path
.Combine (path
, "." + file
.Name
+ ".index");
284 if (! File
.Exists (indexfile
)) {
286 Logger
.Log
.Warn ( "KMail backend: " +
288 " contains an mbox file but no corresponding index file. Probably not a KMail mail directory. Ignoring this location!");
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
);
312 while ((line
= reader
.ReadLine ()) != null) {
313 if (line
.StartsWith ("[") && line
.EndsWith ("]")) {
316 if (section
== "[General]") {
317 if (line
.StartsWith ("folders=") && line
.Length
> 8) {
318 return StringFu
.ExpandEnvVariables (line
.Substring(8));