2 // ThunderbirdIndexer.cs: This class launches IndexableGenerators and makes sure instant-updates work
4 // Copyright (C) 2006 Pierre Östlund
8 // Permission is hereby granted, free of charge, to any person obtaining a copy
9 // of this software and associated documentation files (the "Software"), to deal
10 // in the Software without restriction, including without limitation the rights
11 // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
12 // copies of the Software, and to permit persons to whom the Software is
13 // furnished to do so, subject to the following conditions:
15 // The above copyright notice and this permission notice shall be included in all
16 // 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 FROM,
23 // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
29 using System
.Collections
;
30 using System
.Reflection
;
34 using TB
= Beagle
.Util
.Thunderbird
;
36 namespace Beagle
.Daemon
.ThunderbirdQueryable
{
38 public class ThunderbirdIndexer
{
39 private ThunderbirdQueryable queryable
;
41 private bool init_phase
, first_lap
;
42 private string[] root_paths
;
43 private Hashtable supported_types
;
44 private ArrayList account_list
;
45 private ThunderbirdInotify inotify
;
47 public delegate void NotificationEventHandler (object o
, NotificationEventArgs args
);
49 public ThunderbirdIndexer (ThunderbirdQueryable queryable
, string[] root_paths
)
51 this.queryable
= queryable
;
52 this.root_paths
= root_paths
;
53 this.supported_types
= new Hashtable ();
54 this.init_phase
= true;
55 this.first_lap
= true;
56 this.account_list
= new ArrayList ();
57 this.inotify
= new ThunderbirdInotify ();
59 LoadSupportedTypes ();
61 foreach (string path
in root_paths
) {
62 Inotify
.Subscribe (path
, OnInotifyEvent
,
63 Inotify
.EventType
.Delete
|
64 Inotify
.EventType
.MovedTo
|
65 Inotify
.EventType
.Modify
|
66 Inotify
.EventType
.Create
);
69 inotify
.InotifyEvent
+= OnInotifyEvent
;
72 // Loads all supported types, checks if they have a correct constructor and is enabled
73 private void LoadSupportedTypes ()
75 Assembly assembly
= Assembly
.GetCallingAssembly ();
77 foreach (Type type
in ReflectionFu
.ScanAssemblyForInterface (assembly
, typeof (IIndexableGenerator
))) {
79 foreach (ThunderbirdIndexableGeneratorAttribute attr
in
80 ReflectionFu
.ScanTypeForAttribute (type
, typeof (ThunderbirdIndexableGeneratorAttribute
))) {
82 foreach (ConstructorInfo constructor
in type
.GetConstructors ()) {
84 ParameterInfo
[] parameters
= constructor
.GetParameters ();
85 if(parameters
.Length
!= 3)
88 if (parameters
[0].ParameterType
.Equals (typeof (ThunderbirdIndexer
)) &&
89 parameters
[1].ParameterType
.Equals (typeof (TB
.Account
)) &&
90 parameters
[2].ParameterType
.Equals (typeof (string))) {
92 // Make sure we should enable this type
94 supported_types
[attr
.Type
] = type
;
97 Logger
.Log
.Debug ("{0} has an invalid constructor!", type
.ToString ());
107 foreach (string path
in root_paths
) {
108 foreach (TB
.Account account
in Thunderbird
.ReadAccounts (path
)) {
109 if (Shutdown
.ShutdownRequested
)
112 if (supported_types
[account
.Type
] == null)
115 IndexAccount (account
);
121 Logger
.Log
.Info ("Indexing {0} ({1}) Thunderbird account(s) spread over {2} profile(s)",
122 launched
, account_list
.Count
, root_paths
.Length
);
124 // Clean out old stuff in case no IndexableGenerator was launched
129 public void IndexAccount (TB
.Account account
)
131 TB
.Account stored_account
= GetParentAccount (account
.Path
);
133 // We need to act upon changes made to accounts during Thunderbird runtime.
134 // The user might change from plain to SSL, which leads to a new port number
135 // that has to be taken in account for when indexing.
136 if (stored_account
== null && Directory
.Exists (account
.Path
) && supported_types
[account
.Type
] != null) {
137 account_list
.Add (account
);
138 IndexDirectory (account
.Path
);
139 //Logger.Log.Info ("Indexing {0} account {1}", account.Type.ToString (), account.Server);
141 } else if (stored_account
== null && File
.Exists (account
.Path
) && supported_types
[account
.Type
] != null) {
142 account_list
.Add (account
);
143 IndexFile (account
.Path
);
144 //Logger.Log.Info ("Indexing {0} account {1}", account.Type.ToString (), account.Server);
146 } else if (stored_account
!= null &&
147 (stored_account
.Server
!= account
.Server
||
148 stored_account
.Port
!= account
.Port
||
149 stored_account
.Type
!= account
.Type
||
150 stored_account
.Delimiter
!= account
.Delimiter
)) {
152 account_list
.Remove (stored_account
);
153 account_list
.Add (account
);
155 // Make sure all running indexables are aware of this since it can affect the way they index
156 NotificationEventArgs args
;
157 args
= new NotificationEventArgs (NotificationType
.UpdateAccountInformation
, stored_account
);
158 args
.Data
= (object) account
;
159 OnNotification (args
);
161 Logger
.Log
.Info ("Updated {0} with new account details", account
.Server
);
165 public void IndexFile (string file
)
167 TB
.Account account
= GetParentAccount (file
);
169 if (account
== null || supported_types
[account
.Type
] == null || Thunderbird
.GetFileSize (file
) < 1)
172 object[] param
= new object[] {this, account, file}
;
173 ThunderbirdIndexableGenerator generator
= Activator
.CreateInstance (
174 (Type
) supported_types
[account
.Type
], param
) as ThunderbirdIndexableGenerator
;
176 AddIIndexableTask (generator
, file
);
179 private void IndexDirectory (string directory
)
181 Queue pending
= new Queue ();
183 pending
.Enqueue (directory
);
184 while (pending
.Count
> 0) {
185 string dir
= pending
.Dequeue () as string;
187 foreach (string subdir
in DirectoryWalker
.GetDirectories (dir
)) {
188 if (Shutdown
.ShutdownRequested
)
191 pending
.Enqueue (subdir
);
194 if (Inotify
.Enabled
) {
196 Inotify
.EventType
.Modify
|
197 Inotify
.EventType
.Create
|
198 Inotify
.EventType
.Delete
|
199 Inotify
.EventType
.MovedFrom
|
200 Inotify
.EventType
.MovedTo
);
203 foreach (string file
in DirectoryWalker
.GetItems
204 (dir
, new DirectoryWalker
.FileFilter (Thunderbird
.IsMorkFile
))) {
205 if (Shutdown
.ShutdownRequested
)
213 public void RemoveAccount (TB
.Account account
)
215 TB
.Account acc
= GetParentAccount (account
.Path
);
220 ScheduleRemoval (Property
.NewKeyword ("fixme:account", acc
.Server
), Scheduler
.Priority
.Delayed
);
221 OnNotification (new NotificationEventArgs (NotificationType
.StopIndexing
, account
));
222 account_list
.Remove (acc
);
225 private void AddIIndexableTask (IIndexableGenerator generator
, string tag
)
227 if (queryable
.ThisScheduler
.ContainsByTag (tag
)) {
228 Logger
.Log
.Debug ("Not adding a Task for already running: {0}", tag
);
232 Scheduler
.Task task
= queryable
.NewAddTask (generator
);
234 queryable
.ThisScheduler
.Add (task
);
237 private void ScheduleRemoval (Property prop
, Scheduler
.Priority priority
)
239 if (queryable
.ThisScheduler
.ContainsByTag (prop
.ToString ())) {
240 Logger
.Log
.Debug ("Not adding a Task for already running: {0}", prop
.ToString ());
244 Scheduler
.Task task
= queryable
.NewRemoveByPropertyTask (prop
);
245 task
.Priority
= priority
;
246 task
.SubPriority
= 0;
247 queryable
.ThisScheduler
.Add (task
);
250 public void ScheduleRemoval (Uri
[] uris
, string tag
, Scheduler
.Priority priority
)
252 if (queryable
.ThisScheduler
.ContainsByTag (tag
)) {
253 Logger
.Log
.Debug ("Not adding a Task for already running: {0}", tag
);
257 Scheduler
.Task task
= queryable
.NewAddTask (new UriRemovalIndexableGenerator (uris
));
258 task
.Priority
= priority
;
259 task
.SubPriority
= 0;
260 queryable
.ThisScheduler
.Add (task
);
263 public void UpdateAccounts (string root_path
)
265 ArrayList new_accounts
;
268 new_accounts
= Thunderbird
.ReadAccounts (root_path
);
269 } catch (Exception e
) {
270 Logger
.Log
.Warn ("Failed when reading Thunderbird accounts: {0}, account may have been added or removed", e
);
274 // Add all accounts again to make sure things are updated the way they should
275 foreach (TB
.Account account
in new_accounts
)
276 IndexAccount (account
);
278 // Remove non-existing accounts
279 foreach (TB
.Account existing
in account_list
) {
282 foreach (TB
.Account new_account
in new_accounts
) {
283 if (existing
.Path
== new_account
.Path
) {
290 RemoveAccount (existing
);
294 public TB
.Account
GetParentAccount (string directory
)
296 foreach (TB
.Account acc
in account_list
) {
297 if (directory
.StartsWith (acc
.Path
))
304 private void OnInotifyEvent (Inotify
.Watch watch
,
308 Inotify
.EventType type
)
313 string full_path
= Path
.Combine (path
, subitem
);
315 // If prefs.js is deleted... then we have nothing at all to index
316 if (((type
& Inotify
.EventType
.MovedTo
) != 0 && srcpath
== Path
.Combine (path
, "prefs.js")) ||
317 ((type
& Inotify
.EventType
.Delete
) != 0 && subitem
== "prefs.js")) {
319 foreach (TB
.Account account
in account_list
)
320 RemoveAccount (account
);
324 // Update in case an account was removed or added
325 // Thunderbird saves prefs.js with a different name and then replacing the old one
326 // by "moving" it over the existing prefs.js. That's why MoveTo is used as inotfy type.
327 if ((((type
& Inotify
.EventType
.Modify
) != 0 || (type
& Inotify
.EventType
.MovedTo
) != 0 ||
328 (type
& Inotify
.EventType
.Create
) != 0) && subitem
== "prefs.js")) {
330 UpdateAccounts (path
);
334 // In case the address book file have been moved or deleted, we have to stop indexing it
335 if (((type
& Inotify
.EventType
.MovedTo
) != 0 && srcpath
== Path
.Combine (path
, "abook.mab")) ||
336 ((type
& Inotify
.EventType
.Delete
) != 0 && subitem
== "abook.mab")) {
338 TB
.Account account
= GetParentAccount (full_path
);
341 RemoveAccount (account
);
346 // In case of a newly created addressbook, the current address book is modified or an old
347 // address book is moved to where the address book can be found: either start indexing
348 // or restart an already indexing IndeaxbleGenerator.
349 if ((((type
& Inotify
.EventType
.Modify
) != 0 || (type
& Inotify
.EventType
.MovedTo
) != 0 ||
350 (type
& Inotify
.EventType
.Create
) != 0) && subitem
== "abook.mab")) {
352 TB
.Account account
= GetParentAccount (full_path
);
354 if (account
== null && File
.Exists (full_path
)) {
355 UpdateAccounts (path
);
357 } else if (account
== null)
360 // Tell any running indexable about this or start a new one
361 if (queryable
.ThisScheduler
.ContainsByTag (full_path
))
362 OnNotification (new NotificationEventArgs (NotificationType
.RestartIndexing
, account
));
364 IndexFile (full_path
);
369 // Re-index files when needed
370 if ((type
& Inotify
.EventType
.Modify
) != 0) {
371 TB
.Account account
= GetParentAccount (full_path
);
373 if (account
== null || !Thunderbird
.IsMorkFile (path
, subitem
))
376 // In case we have a running IndexableGenerator, tell it that we have a file that needs to
378 if (queryable
.ThisScheduler
.ContainsByTag (full_path
))
379 OnNotification (new NotificationEventArgs (NotificationType
.RestartIndexing
, account
));
381 IndexFile (full_path
);
386 // Index newly created directories
387 if ((type
& Inotify
.EventType
.Create
) != 0 && (type
& Inotify
.EventType
.IsDirectory
) != 0) {
388 if (GetParentAccount (full_path
) != null && Inotify
.Enabled
)
389 Inotify
.Subscribe (full_path
, OnInotifyEvent
, Inotify
.EventType
.All
);
395 public void ChildComplete ()
397 if (NotificationEvent
!= null || init_phase
|| !first_lap
)
400 if (Thunderbird
.Debug
)
401 Logger
.Log
.Debug ("Removing old Thunderbird objects");
403 Scheduler
.Task task
= queryable
.NewRemoveTaskByDate (ThunderbirdQueryable
.IndexingStart
);
404 task
.Priority
= Scheduler
.Priority
.Idle
;
405 task
.Tag
= "RemoveOldThunderbirdMails";
406 queryable
.ThisScheduler
.Add (task
);
408 // This makes sure that ChildComplete will only clean out all mails once in a lifetime
409 // (of the Thunderbird backend that is)
413 protected virtual void OnNotification(NotificationEventArgs args
)
415 if (NotificationEvent
!= null)
416 NotificationEvent (this, args
);
419 public event NotificationEventHandler NotificationEvent
;
421 public LuceneAccess Lucene
{
422 get { return queryable.Lucene; }
426 /////////////////////////////////////////////////////////////////////////////////////
428 public enum NotificationType
{
431 UpdateAccountInformation
434 /////////////////////////////////////////////////////////////////////////////////////
436 public class NotificationEventArgs
: EventArgs
438 private NotificationType type
;
439 private TB
.Account account
;
442 public NotificationEventArgs (NotificationType type
, TB
.Account account
)
445 this.account
= account
;
448 public NotificationType Type
{
452 public TB
.Account Account
{
453 get { return account; }