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 State
= QueryableState
.Crawling
;
128 if (local_path
!= null) {
129 local_indexer
= new KMailIndexer (this, "local", local_path
);
130 local_indexer
.Crawl ();
132 // FIXME: parse kmailrc to get dimap account name
133 if (Directory
.Exists (dimap_path
)) {
134 dimap_indexer
= new KMailIndexer (this, "dimap", dimap_path
);
135 dimap_indexer
.Crawl ();
137 State
= QueryableState
.Idle
;
138 Logger
.Log
.Debug ("Mail crawl done");
140 if (! Inotify
.Enabled
) {
141 Scheduler
.Task task
= Scheduler
.TaskFromHook (new Scheduler
.TaskHook (CrawlHook
));
142 task
.Tag
= "Crawling Maildir directories";
144 task
.TriggerTime
= DateTime
.Now
.AddSeconds (polling_interval_in_seconds
);
145 ThisScheduler
.Add (task
);
149 Logger
.Log
.Info ("KMail driver worker thread done in {0}", stopwatch
);
153 * use this method to determine if we have anything to crawl and index
155 private bool CheckForExistence ()
157 local_path
= GuessLocalFolderPath ();
158 if (local_path
== null && (!Directory
.Exists (dimap_path
)))
165 /////////////////////////////////////////////////////////////////////////////
167 override public string GetSnippet (string[] query_terms
, Hit hit
)
169 Logger
.Log
.Debug ("Fetching snippet for " + hit
.Uri
.LocalPath
);
170 // FIXME: Also handle mbox emails
171 if (! hit
.Uri
.IsFile
)
174 // Dont get snippets from attachments, they arent even indexed currently
175 if (hit
.ParentUri
!= null)
178 int mail_fd
= Mono
.Unix
.Native
.Syscall
.open (hit
.Uri
.LocalPath
, Mono
.Unix
.Native
.OpenFlags
.O_RDONLY
);
183 GMime
.StreamFs stream
= new GMime
.StreamFs (mail_fd
);
184 GMime
.Parser parser
= new GMime
.Parser (stream
);
185 GMime
.Message message
= parser
.ConstructMessage ();
190 string body
= message
.GetBody (true, out html
);
191 // FIXME: Also handle snippets from html message parts - involves invoking html filter
193 Logger
.Log
.Debug ("No text/plain message part in " + hit
.Uri
);
198 StringReader reader
= new StringReader (body
);
199 string snippet
= SnippetFu
.GetSnippet (query_terms
, reader
);
205 /////////////////////////////////////////////////////////////////////////////
207 // FIXME: How to determine if an mbox hit is valid without scanning the whole file
210 get { return "KMail"; }
214 * path of local maildir - mine is in ~/.Mail
215 * This is distribution specific. Mandrake puts kmail mails in
216 * ~/.Mail whereas default kmail folder location is ~/Mail
217 * I guess each distribution can fix this path as they know what is
219 * It is possible to have the path specified in kmailrc. It might not
220 * be present, in which case try to play a guessing game.
221 * Till then, using a guesser to find out which of ~/.Mail and ~/Mail
223 * Guesses the kmail local folder path
224 * first try ~/.Mail, then try ~/Mail
225 * then try ~/.kde/share/apps/kmail/mail
227 private string GuessLocalFolderPath ()
229 string locationrc
= GetLocalFolderPathFromKmailrc ();
230 //Logger.Log.Debug ("Reading kmail local-mail location from kmailrc: " +
231 // (locationrc == null ? "Unavailable" : locationrc));
232 string location1
= Path
.Combine (PathFinder
.HomeDir
, "Mail");
233 string location2
= Path
.Combine (PathFinder
.HomeDir
, ".Mail");
235 string location3
= Path
.Combine (PathFinder
.HomeDir
, ".kde");
236 location3
= Path
.Combine (location3
, "share");
237 location3
= Path
.Combine (location3
, "apps");
238 location3
= Path
.Combine (location3
, "kmail");
239 location3
= Path
.Combine (location3
, "mail");
241 if (locationrc
!= null && GuessLocalFolder (locationrc
))
243 else if (GuessLocalFolder (location1
))
245 else if (GuessLocalFolder (location2
))
247 else if (GuessLocalFolder (location3
))
254 * to check if the path represents a kmail directory:
255 * for all directories and files named "ddd" and not starting with a '.',
256 * there should be matching index file named .ddd.index
258 private bool GuessLocalFolder (string path
)
260 if (! Directory
.Exists (path
))
264 foreach (string subdirname
in DirectoryWalker
.GetDirectoryNames (path
)) {
265 if (subdirname
.StartsWith ("."))
267 // index-file name is of pattern .name.index
268 string indexfile
= Path
.Combine (path
, "." + subdirname
+ ".index");
269 if (! File
.Exists (indexfile
)) {
271 Logger
.Log
.Warn ( "KMail backend: " +
273 " contains a maildir directory but no corresponding index file. Probably not a KMail mail directory. Ignoring this location!");
281 foreach (FileInfo file
in DirectoryWalker
.GetFileInfos (path
)) {
282 if (file
.Name
.StartsWith ("."))
284 // index-file name is of pattern .name.index
285 string indexfile
= Path
.Combine (path
, "." + file
.Name
+ ".index");
286 if (! File
.Exists (indexfile
)) {
288 Logger
.Log
.Warn ( "KMail backend: " +
290 " contains an mbox file but no corresponding index file. Probably not a KMail mail directory. Ignoring this location!");
299 * tries to extract folder name from ~/.kde/share/config/kmailrc
301 private string GetLocalFolderPathFromKmailrc ()
303 string kmailrc
= Path
.Combine (PathFinder
.HomeDir
, ".kde");
304 kmailrc
= Path
.Combine (kmailrc
, "share");
305 kmailrc
= Path
.Combine (kmailrc
, "config");
306 kmailrc
= Path
.Combine (kmailrc
, "kmailrc");
308 if (File
.Exists (kmailrc
)) {
309 StreamReader reader
= new StreamReader (kmailrc
);
314 while ((line
= reader
.ReadLine ()) != null) {
315 if (line
.StartsWith ("[") && line
.EndsWith ("]")) {
318 if (section
== "[General]") {
319 if (line
.StartsWith ("folders=") && line
.Length
> 8) {
320 return StringFu
.ExpandEnvVariables (line
.Substring(8));