2 // EvolutionDataServerQueryable.cs
4 // Copyright (C) 2004 Novell, Inc.
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
;
30 using System
.Globalization
;
32 using System
.Threading
;
40 namespace Beagle
.Daemon
.EvolutionDataServerQueryable
{
42 [QueryableFlavor (Name
="EvolutionDataServer", Domain
=QueryDomain
.Local
, RequireInotify
=false)]
43 public class EvolutionDataServerQueryable
: LuceneQueryable
{
44 //private Scheduler.Priority priority = Scheduler.Priority.Immediate;
45 private Scheduler
.Priority priority
= Scheduler
.Priority
.Delayed
;
47 private string photo_dir
;
48 private DateTime start_time
;
50 private DateTime addressbook_indexed_through
= DateTime
.MinValue
;
51 private DateTime calendar_indexed_through
= DateTime
.MinValue
;
53 public EvolutionDataServerQueryable () : base ("EvolutionDataServerIndex")
55 photo_dir
= Path
.Combine (Driver
.TopDirectory
, "Photos");
56 System
.IO
.Directory
.CreateDirectory (photo_dir
);
59 public override void Start ()
63 Logger
.Log
.Info ("Scanning addressbooks and calendars");
64 Stopwatch timer
= new Stopwatch ();
67 start_time
= DateTime
.Now
;
71 src
= new EdsSource ("/apps/evolution/addressbook/sources");
72 src
.IndexSourceAll
+= AddressbookIndexSourceAll
;
73 src
.IndexSourceChanges
+= AddressbookIndexSourceChanges
;
74 src
.RemoveSource
+= AddressbookRemoveSource
;
77 src
= new EdsSource ("/apps/evolution/calendar/sources");
78 src
.IndexSourceAll
+= CalendarIndexSourceAll
;
79 src
.IndexSourceChanges
+= CalendarIndexSourceChanges
;
80 src
.RemoveSource
+= CalendarRemoveSource
;
84 Logger
.Log
.Info ("Scanned addressbooks and calendars in {0}", timer
);
87 public void Add (Indexable indexable
, Scheduler
.Priority priority
)
90 task
= NewAddTask (indexable
);
91 task
.Priority
= priority
;
92 ThisScheduler
.Add (task
);
95 public void Remove (Uri uri
)
98 task
= NewRemoveTask (uri
);
99 task
.Priority
= Scheduler
.Priority
.Immediate
;
100 ThisScheduler
.Add (task
);
103 ///////////////////////////////////////
105 private void AddressbookIndexSourceAll (Evolution
.Source src
)
107 if (!src
.IsLocal ()) {
108 Logger
.Log
.Debug ("Skipping remote addressbook {0}", src
.Uri
);
112 Logger
.Log
.Debug ("Indexing all data in this addressbook ({0})!", src
.Uri
);
114 Book book
= new Book (src
);
117 BookView book_view
= book
.GetBookView (BookQuery
.AnyFieldContains (""),
121 book_view
.ContactsAdded
+= OnContactsAdded
;
122 book_view
.ContactsRemoved
+= OnContactsRemoved
;
123 book_view
.ContactsChanged
+= OnContactsChanged
;
124 book_view
.SequenceComplete
+= OnSequenceComplete
;
129 private void AddressbookIndexSourceChanges (Evolution
.Source src
)
131 if (!src
.IsLocal ()) {
132 Logger
.Log
.Debug ("Skipping remote addressbook {0}", src
.Uri
);
136 Book book
= new Book (src
);
139 Contact
[] added
, changed
;
142 Logger
.Log
.Debug ("Getting addressbook changes for {0}", src
.Uri
);
143 book
.GetChanges ("beagle-" + Driver
.Fingerprint
, out added
, out changed
, out removed
);
144 Logger
.Log
.Debug ("Addressbook {0}: {1} added, {2} changed, {3} removed",
145 book
.Uri
, added
.Length
, changed
.Length
, removed
.Length
);
147 foreach (Contact contact
in added
)
148 AddContact (contact
);
150 foreach (Contact contact
in changed
)
151 AddContact (contact
);
153 foreach (string id
in removed
)
156 BookView book_view
= book
.GetBookView (BookQuery
.AnyFieldContains (""),
160 book_view
.ContactsAdded
+= OnContactsAdded
;
161 book_view
.ContactsRemoved
+= OnContactsRemoved
;
162 book_view
.ContactsChanged
+= OnContactsChanged
;
163 book_view
.SequenceComplete
+= OnSequenceComplete
;
168 private void AddressbookRemoveSource (Evolution
.Source src
)
170 // FIXME: We need to index the group's UID and then
171 // we need a way to schedule removal tasks for
172 // anything that matches that lucene property
173 Logger
.Log
.Debug ("FIXME: Remove addressbook source {0}", src
.Uri
);
176 private static Uri
GetContactUri (Evolution
.Contact contact
) {
177 return GetContactUri (contact
.Id
);
180 private static Uri
GetContactUri (string id
) {
181 return new Uri ("contact://" + id
, true); // FIXME!
184 private DateTime AddressbookIndexedThrough
{
187 if (addressbook_indexed_through
== DateTime
.MinValue
) {
188 string filename
= Path
.Combine (IndexDirectory
, "AddressbookIndexedThrough");
192 StreamReader sr
= new StreamReader (filename
);
193 line
= sr
.ReadLine ();
195 } catch (Exception ex
) { }
198 addressbook_indexed_through
= StringFu
.StringToDateTime (line
);
200 return addressbook_indexed_through
;
204 addressbook_indexed_through
= value;
206 string filename
= Path
.Combine (IndexDirectory
, "AddressbookIndexedThrough");
207 StreamWriter sw
= new StreamWriter (filename
);
208 sw
.WriteLine (StringFu
.DateTimeToString (addressbook_indexed_through
));
213 private static DateTime
RevStringToDateTime (string date_str
)
215 if (date_str
== null)
216 return DateTime
.MinValue
;
219 "yyyy'-'MM'-'dd'T'HH':'mm':'ss'Z'",
220 "yyyyMMdd'T'HHmmss'Z'"
224 return DateTime
.ParseExact (date_str
, formats
,
225 CultureInfo
.InvariantCulture
,
226 DateTimeStyles
.None
);
227 } catch (FormatException
) {
228 Logger
.Log
.Warn ("Unable to parse last revision string: {0}", date_str
);
229 return DateTime
.MinValue
;
233 private Indexable
ContactToIndexable (Evolution
.Contact contact
)
235 DateTime rev
= RevStringToDateTime (contact
.Rev
);
237 if (rev
!= DateTime
.MinValue
&& rev
< AddressbookIndexedThrough
)
240 Indexable indexable
= new Indexable (GetContactUri (contact
));
241 indexable
.Timestamp
= rev
;
242 indexable
.Type
= "Contact";
244 indexable
.AddProperty (Property
.New ("fixme:FileAs", contact
.FileAs
));
245 indexable
.AddProperty (Property
.New ("fixme:GivenName", contact
.GivenName
));
246 indexable
.AddProperty (Property
.New ("fixme:FamilyName", contact
.FamilyName
));
247 indexable
.AddProperty (Property
.New ("fixme:Nickname", contact
.Nickname
));
248 indexable
.AddProperty (Property
.New ("fixme:AddressLabelHome", contact
.AddressLabelHome
));
249 indexable
.AddProperty (Property
.New ("fixme:AddressLabelWork", contact
.AddressLabelWork
));
250 indexable
.AddProperty (Property
.New ("fixme:AddressLabelOther", contact
.AddressLabelOther
));
251 indexable
.AddProperty (Property
.New ("fixme:AssistantPhone", contact
.AssistantPhone
));
252 indexable
.AddProperty (Property
.New ("fixme:BusinessPhone", contact
.BusinessPhone
));
253 indexable
.AddProperty (Property
.New ("fixme:BusinessPhone2", contact
.BusinessPhone2
));
254 indexable
.AddProperty (Property
.New ("fixme:BusinessFax", contact
.BusinessFax
));
255 indexable
.AddProperty (Property
.New ("fixme:CallbackPhone", contact
.CallbackPhone
));
256 indexable
.AddProperty (Property
.New ("fixme:CarPhone", contact
.CarPhone
));
257 indexable
.AddProperty (Property
.New ("fixme:CompanyPhone", contact
.CompanyPhone
));
258 indexable
.AddProperty (Property
.New ("fixme:HomePhone", contact
.HomePhone
));
259 indexable
.AddProperty (Property
.New ("fixme:HomePhone2", contact
.HomePhone2
));
260 indexable
.AddProperty (Property
.New ("fixme:HomeFax", contact
.HomeFax
));
261 indexable
.AddProperty (Property
.New ("fixme:IsdnPhone", contact
.IsdnPhone
));
262 indexable
.AddProperty (Property
.New ("fixme:MobilePhone", contact
.MobilePhone
));
263 indexable
.AddProperty (Property
.New ("fixme:OtherPhone", contact
.OtherPhone
));
264 indexable
.AddProperty (Property
.New ("fixme:OtherFax", contact
.OtherFax
));
265 indexable
.AddProperty (Property
.New ("fixme:Pager", contact
.Pager
));
266 indexable
.AddProperty (Property
.New ("fixme:PrimaryPhone", contact
.PrimaryPhone
));
267 indexable
.AddProperty (Property
.New ("fixme:Radio", contact
.Radio
));
268 indexable
.AddProperty (Property
.New ("fixme:Telex", contact
.Telex
));
269 indexable
.AddProperty (Property
.NewKeyword ("fixme:Tty", contact
.Tty
));
270 indexable
.AddProperty (Property
.NewKeyword ("fixme:Email1", contact
.Email1
));
271 indexable
.AddProperty (Property
.NewKeyword ("fixme:Email2", contact
.Email2
));
272 indexable
.AddProperty (Property
.NewKeyword ("fixme:Email3", contact
.Email3
));
273 indexable
.AddProperty (Property
.NewKeyword ("fixme:Mailer", contact
.Mailer
));
274 indexable
.AddProperty (Property
.New ("fixme:Org", contact
.Org
));
275 indexable
.AddProperty (Property
.New ("fixme:OrgUnit", contact
.OrgUnit
));
276 indexable
.AddProperty (Property
.New ("fixme:Office", contact
.Office
));
277 indexable
.AddProperty (Property
.New ("fixme:Title", contact
.Title
));
278 indexable
.AddProperty (Property
.New ("fixme:Role", contact
.Role
));
279 indexable
.AddProperty (Property
.New ("fixme:Manager", contact
.Manager
));
280 indexable
.AddProperty (Property
.New ("fixme:Assistant", contact
.Assistant
));
281 indexable
.AddProperty (Property
.NewKeyword ("fixme:HomepageUrl", contact
.HomepageUrl
));
282 indexable
.AddProperty (Property
.NewKeyword ("fixme:BlogUrl", contact
.BlogUrl
));
283 indexable
.AddProperty (Property
.NewKeyword ("fixme:Categories", contact
.Categories
));
284 indexable
.AddProperty (Property
.NewKeyword ("fixme:Caluri", contact
.Caluri
));
285 indexable
.AddProperty (Property
.NewKeyword ("fixme:Icscalendar", contact
.Icscalendar
));
286 indexable
.AddProperty (Property
.New ("fixme:Spouse", contact
.Spouse
));
287 indexable
.AddProperty (Property
.New ("fixme:Note", contact
.Note
));
289 Evolution
.ContactPhoto photo
= contact
.Photo
;
291 if (photo
.Data
!= null && photo
.Data
.Length
> 0) {
292 string photo_filename
= GetPhotoFilename (contact
.Id
);
293 Stream s
= new FileStream (photo_filename
, FileMode
.Create
, FileAccess
.Write
, FileShare
.ReadWrite
);
294 BinaryWriter w
= new BinaryWriter (s
);
295 w
.Write (photo
.Data
);
299 indexable
.AddProperty (Property
.NewUnsearched ("beagle:Photo", photo_filename
));
302 // FIXME: ListShowAddresses?
304 // FIXME: Should we not drop the extra Im addresses?
305 if (contact
.ImAim
.Length
> 0)
306 indexable
.AddProperty (Property
.NewKeyword ("fixme:ImAim", contact
.ImAim
[0]));
307 if (contact
.ImIcq
.Length
> 0)
308 indexable
.AddProperty (Property
.NewKeyword ("fixme:ImIcq", contact
.ImIcq
[0]));
309 if (contact
.ImJabber
.Length
> 0)
310 indexable
.AddProperty (Property
.NewKeyword ("fixme:ImJabber", contact
.ImJabber
[0]));
311 if (contact
.ImMsn
.Length
> 0)
312 indexable
.AddProperty (Property
.NewKeyword ("fixme:ImMsn", contact
.ImMsn
[0]));
313 if (contact
.ImYahoo
.Length
> 0)
314 indexable
.AddProperty (Property
.NewKeyword ("fixme:ImYahoo", contact
.ImYahoo
[0]));
315 if (contact
.ImGroupwise
.Length
> 0)
316 indexable
.AddProperty (Property
.NewKeyword ("fixme:ImGroupWise", contact
.ImGroupwise
[0]));
319 if (contact
.GivenName
!= null && contact
.GivenName
!= "")
320 name
= contact
.GivenName
;
321 if (contact
.FamilyName
!= null && contact
.FamilyName
!= "")
322 name
+= " " + contact
.FamilyName
;
324 indexable
.AddProperty (Property
.New ("fixme:Name", name
));
326 if (contact
.Email1
!= null)
327 indexable
.AddProperty (Property
.NewKeyword ("fixme:Email",
332 private void AddContact (Contact contact
)
334 Indexable indexable
= ContactToIndexable (contact
);
335 if (indexable
!= null)
336 Add (indexable
, priority
);
339 private void RemoveContact (string id
)
341 Remove (GetContactUri (id
));
343 string filename
= GetPhotoFilename (id
);
344 if (filename
!= null && File
.Exists (filename
))
345 File
.Delete (filename
);
348 private void OnContactsAdded (object o
,
349 Evolution
.ContactsAddedArgs args
)
351 foreach (Evolution
.Contact contact
in args
.Contacts
)
352 AddContact (contact
);
355 private void OnContactsChanged (object o
,
356 Evolution
.ContactsChangedArgs args
)
358 foreach (Evolution
.Contact contact
in args
.Contacts
)
359 AddContact (contact
);
362 private void OnContactsRemoved (object o
,
363 Evolution
.ContactsRemovedArgs args
)
365 // FIXME: This is a temporary workaround for the
366 // fact that the evolution bindings return a
367 // GLib.List with an object type, but there
368 // are really strings in there
370 GLib
.List id_list
= new GLib
.List (args
.Ids
.Handle
,
374 foreach (string id
in id_list
)
378 private string GetPhotoFilename (string id
)
380 return Path
.Combine (photo_dir
, id
);
383 private void OnSequenceComplete (object o
,
384 Evolution
.SequenceCompleteArgs args
)
386 // Contacts that get changed while the beagled is
387 // running will be re-indexed during the next scan.
388 // That isn't optimal, but is much better than the
389 // current situation.
390 AddressbookIndexedThrough
= start_time
;
392 // Now that we're done synching with the original
393 // state of the addressbook, switch all new changes to
395 priority
= Scheduler
.Priority
.Immediate
;
398 ///////////////////////////////////////
400 private void CalendarIndexSourceAll (Evolution
.Source src
)
402 if (!src
.IsLocal ()) {
403 Logger
.Log
.Debug ("Skipping remote calendar {0}", src
.Uri
);
407 Logger
.Log
.Debug ("Indexing all data in this calendar ({0})!", src
.Uri
);
409 Cal cal
= new Cal (src
, CalSourceType
.Event
);
412 CalComponent
[] event_list
= cal
.GetItems ("#t");
414 Logger
.Log
.Debug ("Calendar has {0} items", event_list
.Length
);
416 foreach (CalComponent cc
in event_list
)
417 IndexCalComponent (cc
, Scheduler
.Priority
.Immediate
);
419 CalView cal_view
= cal
.GetCalView ("#t");
420 cal_view
.ObjectsAdded
+= OnObjectsAdded
;
421 cal_view
.ObjectsModified
+= OnObjectsModified
;
422 cal_view
.ObjectsRemoved
+= OnObjectsRemoved
;
423 cal_view
.ViewDone
+= OnViewDone
;
427 private void CalendarIndexSourceChanges (Evolution
.Source src
)
429 if (!src
.IsLocal ()) {
430 Logger
.Log
.Debug ("Skipping remote calendar {0}", src
.Uri
);
434 Cal cal
= new Cal (src
, CalSourceType
.Event
);
437 CalComponent
[] new_items
, update_items
;
438 string[] remove_items
;
440 Logger
.Log
.Debug ("Getting calendar changes for {0}", src
.Uri
);
441 cal
.GetChanges ("beagle-" + this.Driver
.Fingerprint
, out new_items
, out update_items
, out remove_items
);
442 Logger
.Log
.Debug ("Calendar {0}: {1} new items, {2} updated items, {3} removed items",
443 cal
.Uri
, new_items
.Length
, update_items
.Length
, remove_items
.Length
);
445 foreach (CalComponent cc
in new_items
)
446 IndexCalComponent (cc
, Scheduler
.Priority
.Immediate
);
448 foreach (CalComponent cc
in update_items
)
449 IndexCalComponent (cc
, Scheduler
.Priority
.Immediate
);
451 foreach (string id
in remove_items
) {
452 // FIXME: Broken in e-d-s right now
453 //RemoveCalComponent (id);
456 CalView cal_view
= cal
.GetCalView ("#t");
457 cal_view
.ObjectsAdded
+= OnObjectsAdded
;
458 cal_view
.ObjectsModified
+= OnObjectsModified
;
459 cal_view
.ObjectsRemoved
+= OnObjectsRemoved
;
460 cal_view
.ViewDone
+= OnViewDone
;
464 private void CalendarRemoveSource (Evolution
.Source src
)
466 // FIXME: We need to index the group's UID and then
467 // we need a way to schedule removal tasks for
468 // anything that matches that lucene property
469 Logger
.Log
.Debug ("FIXME: Remove calendar source {0}", src
.Uri
);
472 private static Uri
GetCalendarUri (CalComponent cc
) {
473 return GetContactUri (cc
.Uid
);
476 private static Uri
GetCalendarUri (string id
) {
477 return new Uri ("calendar://" + id
, true); // FIXME!
480 private DateTime CalendarIndexedThrough
{
483 if (calendar_indexed_through
== DateTime
.MinValue
) {
484 string filename
= Path
.Combine (IndexDirectory
, "CalendarIndexedThrough");
488 StreamReader sr
= new StreamReader (filename
);
489 line
= sr
.ReadLine ();
491 } catch (Exception ex
) { }
494 calendar_indexed_through
= StringFu
.StringToDateTime (line
);
496 return calendar_indexed_through
;
500 calendar_indexed_through
= value;
502 string filename
= Path
.Combine (IndexDirectory
, "CalendarIndexedThrough");
503 StreamWriter sw
= new StreamWriter (filename
);
504 sw
.WriteLine (StringFu
.DateTimeToString (calendar_indexed_through
));
509 private void IndexCalComponent (CalComponent cc
, Scheduler
.Priority priority
)
511 Indexable indexable
= CalComponentToIndexable (cc
);
512 Add (indexable
, priority
);
515 private void RemoveCalComponent (string id
)
517 Remove (GetCalendarUri (id
));
520 private Indexable
CalComponentToIndexable (CalComponent cc
)
522 Indexable indexable
= new Indexable (new Uri ("calendar:///" + cc
.Uid
));
524 indexable
.Timestamp
= cc
.Dtstart
;
525 indexable
.Type
= "Calendar";
527 indexable
.AddProperty (Property
.NewKeyword ("fixme:uid", cc
.Uid
));
528 indexable
.AddProperty (Property
.NewDate ("fixme:starttime", cc
.Dtstart
));
529 indexable
.AddProperty (Property
.NewDate ("fixme:endtime", cc
.Dtend
));
531 foreach (string attendee
in cc
.Attendees
)
532 indexable
.AddProperty (Property
.New ("fixme:attendee", attendee
));
534 foreach (string comment
in cc
.Comments
)
535 indexable
.AddProperty (Property
.New ("fixme:comment", comment
));
537 foreach (string description
in cc
.Descriptions
)
538 indexable
.AddProperty (Property
.New ("fixme:description", description
));
540 foreach (string summary
in cc
.Summaries
)
541 indexable
.AddProperty (Property
.New ("fixme:summary", summary
));
543 foreach (string category
in cc
.Categories
)
544 indexable
.AddProperty (Property
.NewKeyword ("fixme:category", category
));
546 foreach (string location
in cc
.Location
)
547 indexable
.AddProperty (Property
.New ("fixme:location", location
));
552 private void OnObjectsAdded (object o
, ObjectsAddedArgs args
)
554 CalView cal_view
= (CalView
) o
;
556 foreach (CalComponent cc
in CalUtil
.CalCompFromICal (args
.Objects
.Handle
, cal_view
.Client
)) {
557 DateTime compare_date
= DateTime
.MinValue
;
559 if (cc
.LastModified
> CalUtil
.MinDate
)
560 compare_date
= cc
.LastModified
;
561 else if (cc
.Dtstamp
> CalUtil
.MinDate
)
562 compare_date
= cc
.Dtstamp
;
563 else if (cc
.Created
> CalUtil
.MinDate
)
564 compare_date
= cc
.Created
;
566 if (compare_date
== DateTime
.MinValue
|| compare_date
.ToLocalTime () > CalendarIndexedThrough
)
567 IndexCalComponent (cc
, Scheduler
.Priority
.Immediate
);
571 private void OnObjectsModified (object o
, ObjectsModifiedArgs args
)
573 CalView cal_view
= (CalView
) o
;
575 foreach (CalComponent cc
in CalUtil
.CalCompFromICal (args
.Objects
.Handle
, cal_view
.Client
)) {
576 DateTime compare_date
= DateTime
.MinValue
;
578 if (cc
.LastModified
> CalUtil
.MinDate
)
579 compare_date
= cc
.LastModified
;
580 else if (cc
.Dtstamp
> CalUtil
.MinDate
)
581 compare_date
= cc
.Dtstamp
;
582 else if (cc
.Created
> CalUtil
.MinDate
)
583 compare_date
= cc
.Created
;
585 if (compare_date
== DateTime
.MinValue
|| compare_date
.ToLocalTime () > CalendarIndexedThrough
)
586 IndexCalComponent (cc
, Scheduler
.Priority
.Immediate
);
590 private void OnObjectsRemoved (object o
, ObjectsRemovedArgs args
)
592 // FIXME: This is a temporary workaround for the
593 // fact that the evolution bindings return a
594 // GLib.List with an object type, but there
595 // are really strings in there
597 GLib
.List id_list
= new GLib
.List (args
.Uids
.Handle
,
600 foreach (string uid
in id_list
) {
602 task
= NewRemoveTask (new Uri ("calendar:///" + uid
));
603 task
.Priority
= Scheduler
.Priority
.Immediate
;
604 ThisScheduler
.Add (task
);
608 private void OnViewDone (object o
, ViewDoneArgs args
)
610 // Contacts that get changed while the beagled is
611 // running will be re-indexed during the next scan.
612 // That isn't optimal, but is much better than the
613 // current situation.
614 CalendarIndexedThrough
= start_time
;