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 Beagle
.Daemon
.ThunderbirdQueryable
;
35 using TB
= Beagle
.Util
.Thunderbird
;
37 [assembly
: ThunderbirdDefinedGenerators (
38 typeof (ContactIndexableGenerator
),
39 typeof (MailIndexableGenerator
),
40 typeof (MoveMailIndexableGenerator
),
41 typeof (NntpIndexableGenerator
),
42 typeof (RssIndexableGenerator
)
45 namespace Beagle
.Daemon
.ThunderbirdQueryable
{
47 public class ThunderbirdIndexer
{
48 private ThunderbirdQueryable queryable
;
50 private bool init_phase
, first_lap
;
51 private string[] root_paths
;
52 private Hashtable supported_types
;
53 private ArrayList account_list
;
54 private ThunderbirdInotify inotify
;
56 public delegate void NotificationEventHandler (object o
, NotificationEventArgs args
);
58 public ThunderbirdIndexer (ThunderbirdQueryable queryable
, string[] root_paths
)
60 this.queryable
= queryable
;
61 this.root_paths
= root_paths
;
62 this.supported_types
= new Hashtable ();
63 this.init_phase
= true;
64 this.first_lap
= true;
65 this.account_list
= new ArrayList ();
66 this.inotify
= new ThunderbirdInotify ();
68 LoadSupportedTypes ();
70 foreach (string path
in root_paths
) {
71 Inotify
.Subscribe (path
, OnInotifyEvent
,
72 Inotify
.EventType
.Delete
|
73 Inotify
.EventType
.MovedTo
|
74 Inotify
.EventType
.Modify
|
75 Inotify
.EventType
.Create
);
78 inotify
.InotifyEvent
+= OnInotifyEvent
;
81 // Loads all supported types, checks if they have a correct constructor and is enabled
82 private void LoadSupportedTypes ()
84 Assembly assembly
= Assembly
.GetCallingAssembly ();
86 foreach (Type type
in ReflectionFu
.GetTypesFromAssemblyAttribute (assembly
, typeof (ThunderbirdDefinedGeneratorsAttribute
))) {
88 foreach (ThunderbirdIndexableGeneratorAttribute attr
in
89 ReflectionFu
.ScanTypeForAttribute (type
, typeof (ThunderbirdIndexableGeneratorAttribute
))) {
91 foreach (ConstructorInfo constructor
in type
.GetConstructors ()) {
93 ParameterInfo
[] parameters
= constructor
.GetParameters ();
94 if(parameters
.Length
!= 3)
97 if (parameters
[0].ParameterType
.Equals (typeof (ThunderbirdIndexer
)) &&
98 parameters
[1].ParameterType
.Equals (typeof (TB
.Account
)) &&
99 parameters
[2].ParameterType
.Equals (typeof (string))) {
101 // Make sure we should enable this type
103 supported_types
[attr
.Type
] = type
;
106 Logger
.Log
.Debug ("{0} has an invalid constructor!", type
.ToString ());
115 TB
.AccountReader reader
= null;
117 foreach (string path
in root_paths
) {
119 reader
= new TB
.AccountReader (path
);
120 } catch (Exception e
) {
121 Logger
.Log
.Warn (e
, "Failed to load accounts: {0}", e
.Message
);
125 foreach (TB
.Account account
in reader
) {
126 if (Shutdown
.ShutdownRequested
)
129 if (supported_types
[account
.Type
] == null)
132 IndexAccount (account
);
138 Logger
.Log
.Info ("Indexing {0} ({1}) Thunderbird account(s) spread over {2} profile(s)",
139 launched
, account_list
.Count
, root_paths
.Length
);
141 // Clean out old stuff in case no IndexableGenerator was launched
146 public void IndexAccount (TB
.Account account
)
148 TB
.Account stored_account
= GetParentAccount (account
.Path
);
150 // We need to act upon changes made to accounts during Thunderbird runtime.
151 // The user might change from plain to SSL, which leads to a new port number
152 // that has to be taken in account for when indexing.
153 if (stored_account
== null && Directory
.Exists (account
.Path
) && supported_types
[account
.Type
] != null) {
154 account_list
.Add (account
);
155 IndexDirectory (account
.Path
);
156 //Logger.Log.Info ("Indexing {0} account {1}", account.Type.ToString (), account.Server);
158 } else if (stored_account
== null && File
.Exists (account
.Path
) && supported_types
[account
.Type
] != null) {
159 account_list
.Add (account
);
160 IndexFile (account
.Path
);
161 //Logger.Log.Info ("Indexing {0} account {1}", account.Type.ToString (), account.Server);
163 } else if (stored_account
!= null &&
164 (stored_account
.Server
!= account
.Server
||
165 stored_account
.Port
!= account
.Port
||
166 stored_account
.Type
!= account
.Type
||
167 stored_account
.Delimiter
!= account
.Delimiter
)) {
169 account_list
.Remove (stored_account
);
170 account_list
.Add (account
);
172 // Make sure all running indexables are aware of this since it can affect the way they index
173 NotificationEventArgs args
;
174 args
= new NotificationEventArgs (NotificationType
.UpdateAccountInformation
, stored_account
);
175 args
.Data
= (object) account
;
176 OnNotification (args
);
178 Logger
.Log
.Info ("Updated {0} with new account details", account
.Server
);
182 public void IndexFile (string file
)
184 TB
.Account account
= GetParentAccount (file
);
186 if (account
== null || supported_types
[account
.Type
] == null || Thunderbird
.GetFileSize (file
) < 1)
189 object[] param
= new object[] {this, account, file}
;
190 ThunderbirdIndexableGenerator generator
= Activator
.CreateInstance (
191 (Type
) supported_types
[account
.Type
], param
) as ThunderbirdIndexableGenerator
;
193 AddIIndexableTask (generator
, file
);
196 private void IndexDirectory (string directory
)
198 Queue pending
= new Queue ();
200 pending
.Enqueue (directory
);
201 while (pending
.Count
> 0) {
202 string dir
= pending
.Dequeue () as string;
204 foreach (string subdir
in DirectoryWalker
.GetDirectories (dir
)) {
205 if (Shutdown
.ShutdownRequested
)
208 pending
.Enqueue (subdir
);
211 if (Inotify
.Enabled
) {
213 Inotify
.EventType
.Modify
|
214 Inotify
.EventType
.Create
|
215 Inotify
.EventType
.Delete
|
216 Inotify
.EventType
.MovedFrom
|
217 Inotify
.EventType
.MovedTo
);
220 foreach (string file
in DirectoryWalker
.GetItems
221 (dir
, new DirectoryWalker
.FileFilter (Thunderbird
.IsMorkFile
))) {
222 if (Shutdown
.ShutdownRequested
)
230 public void RemoveAccount (TB
.Account account
)
232 TB
.Account acc
= GetParentAccount (account
.Path
);
237 ScheduleRemoval (Property
.NewKeyword ("fixme:account", acc
.Server
), Scheduler
.Priority
.Delayed
);
238 OnNotification (new NotificationEventArgs (NotificationType
.StopIndexing
, account
));
239 account_list
.Remove (acc
);
242 private void AddIIndexableTask (IIndexableGenerator generator
, string tag
)
244 if (queryable
.ThisScheduler
.ContainsByTag (tag
)) {
245 Logger
.Log
.Debug ("Not adding a Task for already running: {0}", tag
);
249 Scheduler
.Task task
= queryable
.NewAddTask (generator
);
251 queryable
.ThisScheduler
.Add (task
);
254 private void ScheduleRemoval (Property prop
, Scheduler
.Priority priority
)
256 if (queryable
.ThisScheduler
.ContainsByTag (prop
.ToString ())) {
257 Logger
.Log
.Debug ("Not adding a Task for already running: {0}", prop
.ToString ());
261 Scheduler
.Task task
= queryable
.NewRemoveByPropertyTask (prop
);
262 task
.Priority
= priority
;
263 task
.SubPriority
= 0;
264 queryable
.ThisScheduler
.Add (task
);
267 public void ScheduleRemoval (Uri
[] uris
, string tag
, Scheduler
.Priority priority
)
269 if (queryable
.ThisScheduler
.ContainsByTag (tag
)) {
270 Logger
.Log
.Debug ("Not adding a Task for already running: {0}", tag
);
274 Scheduler
.Task task
= queryable
.NewAddTask (new UriRemovalIndexableGenerator (uris
));
275 task
.Priority
= priority
;
276 task
.SubPriority
= 0;
277 queryable
.ThisScheduler
.Add (task
);
280 public void UpdateAccounts (string root_path
)
282 TB
.AccountReader new_accounts
= null;
285 new_accounts
= new TB
.AccountReader (root_path
);
286 } catch (Exception e
) {
287 Logger
.Log
.Warn ("Failed when reading Thunderbird accounts: {0}, account may have been added or removed", e
);
291 // Add all accounts again to make sure things are updated the way they should
292 foreach (TB
.Account account
in new_accounts
)
293 IndexAccount (account
);
295 // Remove non-existing accounts
296 foreach (TB
.Account existing
in account_list
) {
299 foreach (TB
.Account new_account
in new_accounts
) {
300 if (existing
.Path
== new_account
.Path
) {
307 RemoveAccount (existing
);
311 public TB
.Account
GetParentAccount (string directory
)
313 foreach (TB
.Account acc
in account_list
) {
314 if (directory
.StartsWith (acc
.Path
))
321 private void OnInotifyEvent (Inotify
.Watch watch
,
325 Inotify
.EventType type
)
330 string full_path
= Path
.Combine (path
, subitem
);
332 // If prefs.js is deleted... then we have nothing at all to index
333 if (((type
& Inotify
.EventType
.MovedTo
) != 0 && srcpath
== Path
.Combine (path
, "prefs.js")) ||
334 ((type
& Inotify
.EventType
.Delete
) != 0 && subitem
== "prefs.js")) {
336 foreach (TB
.Account account
in account_list
)
337 RemoveAccount (account
);
341 // Update in case an account was removed or added
342 // Thunderbird saves prefs.js with a different name and then replacing the old one
343 // by "moving" it over the existing prefs.js. That's why MoveTo is used as inotfy type.
344 if ((((type
& Inotify
.EventType
.Modify
) != 0 || (type
& Inotify
.EventType
.MovedTo
) != 0 ||
345 (type
& Inotify
.EventType
.Create
) != 0) && subitem
== "prefs.js")) {
347 UpdateAccounts (path
);
351 // In case the address book file have been moved or deleted, we have to stop indexing it
352 if (((type
& Inotify
.EventType
.MovedTo
) != 0 && srcpath
== Path
.Combine (path
, "abook.mab")) ||
353 ((type
& Inotify
.EventType
.Delete
) != 0 && subitem
== "abook.mab")) {
355 TB
.Account account
= GetParentAccount (full_path
);
358 RemoveAccount (account
);
363 // In case of a newly created addressbook, the current address book is modified or an old
364 // address book is moved to where the address book can be found: either start indexing
365 // or restart an already indexing IndeaxbleGenerator.
366 if ((((type
& Inotify
.EventType
.Modify
) != 0 || (type
& Inotify
.EventType
.MovedTo
) != 0 ||
367 (type
& Inotify
.EventType
.Create
) != 0) && subitem
== "abook.mab")) {
369 TB
.Account account
= GetParentAccount (full_path
);
371 if (account
== null && File
.Exists (full_path
)) {
372 UpdateAccounts (path
);
374 } else if (account
== null)
377 // Tell any running indexable about this or start a new one
378 if (queryable
.ThisScheduler
.ContainsByTag (full_path
))
379 OnNotification (new NotificationEventArgs (NotificationType
.RestartIndexing
, account
));
381 IndexFile (full_path
);
386 // Re-index files when needed
387 if ((type
& Inotify
.EventType
.Modify
) != 0) {
388 TB
.Account account
= GetParentAccount (full_path
);
390 if (account
== null || !Thunderbird
.IsMorkFile (path
, subitem
))
393 // In case we have a running IndexableGenerator, tell it that we have a file that needs to
395 if (queryable
.ThisScheduler
.ContainsByTag (full_path
))
396 OnNotification (new NotificationEventArgs (NotificationType
.RestartIndexing
, account
));
398 IndexFile (full_path
);
403 // Index newly created directories
404 if ((type
& Inotify
.EventType
.Create
) != 0 && (type
& Inotify
.EventType
.IsDirectory
) != 0) {
405 if (GetParentAccount (full_path
) != null && Inotify
.Enabled
)
406 Inotify
.Subscribe (full_path
, OnInotifyEvent
, Inotify
.EventType
.All
);
412 public void ChildComplete ()
414 if (NotificationEvent
!= null || init_phase
|| !first_lap
)
417 if (Thunderbird
.Debug
)
418 Logger
.Log
.Debug ("Removing old Thunderbird objects");
420 Scheduler
.Task task
= queryable
.NewRemoveTaskByDate (ThunderbirdQueryable
.IndexingStart
);
421 task
.Priority
= Scheduler
.Priority
.Idle
;
422 task
.Tag
= "RemoveOldThunderbirdMails";
423 queryable
.ThisScheduler
.Add (task
);
425 // This makes sure that ChildComplete will only clean out all mails once in a lifetime
426 // (of the Thunderbird backend that is)
430 protected virtual void OnNotification(NotificationEventArgs args
)
432 if (NotificationEvent
!= null)
433 NotificationEvent (this, args
);
436 public event NotificationEventHandler NotificationEvent
;
438 public LuceneAccess Lucene
{
439 get { return queryable.Lucene; }
443 /////////////////////////////////////////////////////////////////////////////////////
445 [AttributeUsage (AttributeTargets
.Assembly
)]
446 public class ThunderbirdDefinedGeneratorsAttribute
: TypeCacheAttribute
{
447 public ThunderbirdDefinedGeneratorsAttribute (params Type
[] types
) : base (types
) { }
450 /////////////////////////////////////////////////////////////////////////////////////
452 public enum NotificationType
{
455 UpdateAccountInformation
458 /////////////////////////////////////////////////////////////////////////////////////
460 public class NotificationEventArgs
: EventArgs
462 private NotificationType type
;
463 private TB
.Account account
;
466 public NotificationEventArgs (NotificationType type
, TB
.Account account
)
469 this.account
= account
;
472 public NotificationType Type
{
476 public TB
.Account Account
{
477 get { return account; }