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 sequence_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
.IndexDirectory
, "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 ();
69 src
= new EdsSource ("/apps/evolution/addressbook/sources");
70 src
.IndexSourceAll
+= AddressbookIndexSourceAll
;
71 src
.IndexSourceChanges
+= AddressbookIndexSourceChanges
;
72 src
.RemoveSource
+= AddressbookRemoveSource
;
75 src
= new EdsSource ("/apps/evolution/calendar/sources");
76 src
.IndexSourceAll
+= CalendarIndexSourceAll
;
77 src
.IndexSourceChanges
+= CalendarIndexSourceChanges
;
78 src
.RemoveSource
+= CalendarRemoveSource
;
82 Logger
.Log
.Info ("Scanned addressbooks and calendars in {0}", timer
);
85 public void Add (Indexable indexable
, Scheduler
.Priority priority
)
88 task
= NewAddTask (indexable
);
89 task
.Priority
= priority
;
90 ThisScheduler
.Add (task
);
93 public void Remove (Uri uri
)
96 task
= NewRemoveTask (uri
);
97 task
.Priority
= Scheduler
.Priority
.Immediate
;
98 ThisScheduler
.Add (task
);
101 ///////////////////////////////////////
103 private void AddressbookIndexSourceAll (Evolution
.Source src
)
105 if (!src
.IsLocal ()) {
106 Logger
.Log
.Debug ("Skipping remote addressbook {0}", src
.Uri
);
110 Logger
.Log
.Debug ("Indexing all data in this addressbook ({0})!", src
.Uri
);
112 Book book
= new Book (src
);
115 BookView book_view
= book
.GetBookView (BookQuery
.AnyFieldContains (""),
119 book_view
.ContactsAdded
+= OnContactsAdded
;
120 book_view
.ContactsRemoved
+= OnContactsRemoved
;
121 book_view
.ContactsChanged
+= OnContactsChanged
;
122 book_view
.SequenceComplete
+= OnSequenceComplete
;
127 private void AddressbookIndexSourceChanges (Evolution
.Source src
)
129 if (!src
.IsLocal ()) {
130 Logger
.Log
.Debug ("Skipping remote addressbook {0}", src
.Uri
);
134 Book book
= new Book (src
);
137 Contact
[] added
, changed
;
140 Logger
.Log
.Debug ("Getting addressbook changes for {0}", src
.Uri
);
141 book
.GetChanges ("beagle-" + Driver
.Fingerprint
, out added
, out changed
, out removed
);
142 Logger
.Log
.Debug ("Addressbook {0}: {1} added, {2} changed, {3} removed",
143 book
.Uri
, added
.Length
, changed
.Length
, removed
.Length
);
145 foreach (Contact contact
in added
)
146 AddContact (contact
);
148 foreach (Contact contact
in changed
)
149 AddContact (contact
);
151 foreach (string id
in removed
)
154 BookView book_view
= book
.GetBookView (BookQuery
.AnyFieldContains (""),
158 book_view
.ContactsAdded
+= OnContactsAdded
;
159 book_view
.ContactsRemoved
+= OnContactsRemoved
;
160 book_view
.ContactsChanged
+= OnContactsChanged
;
161 book_view
.SequenceComplete
+= OnSequenceComplete
;
166 private void AddressbookRemoveSource (Evolution
.Source src
)
168 // FIXME: We need to index the group's UID and then
169 // we need a way to schedule removal tasks for
170 // anything that matches that lucene property
171 Logger
.Log
.Debug ("FIXME: Remove addressbook source {0}", src
.Uri
);
174 private static Uri
GetContactUri (Evolution
.Contact contact
) {
175 return GetContactUri (contact
.Id
);
178 private static Uri
GetContactUri (string id
) {
179 return new Uri ("contact://" + id
, true); // FIXME!
182 private DateTime AddressbookIndexedThrough
{
185 if (addressbook_indexed_through
== DateTime
.MinValue
) {
186 string filename
= Path
.Combine (IndexStoreDirectory
, "AddressbookIndexedThrough");
190 StreamReader sr
= new StreamReader (filename
);
191 line
= sr
.ReadLine ();
193 } catch (Exception ex
) { }
196 addressbook_indexed_through
= StringFu
.StringToDateTime (line
);
198 return addressbook_indexed_through
;
202 addressbook_indexed_through
= value;
204 string filename
= Path
.Combine (IndexStoreDirectory
, "AddressbookIndexedThrough");
205 StreamWriter sw
= new StreamWriter (filename
);
206 sw
.WriteLine (StringFu
.DateTimeToString (addressbook_indexed_through
));
211 private static DateTime
RevStringToDateTime (string date_str
)
213 if (date_str
== null)
214 return DateTime
.MinValue
;
217 "yyyy'-'MM'-'dd'T'HH':'mm':'ss'Z'",
218 "yyyyMMdd'T'HHmmss'Z'"
222 return DateTime
.ParseExact (date_str
, formats
,
223 CultureInfo
.InvariantCulture
,
224 DateTimeStyles
.None
);
225 } catch (FormatException
) {
226 Logger
.Log
.Warn ("Unable to parse last revision string: {0}", date_str
);
227 return DateTime
.MinValue
;
231 private Indexable
ContactToIndexable (Evolution
.Contact contact
)
233 DateTime rev
= RevStringToDateTime (contact
.Rev
);
235 if (rev
!= DateTime
.MinValue
&& rev
< AddressbookIndexedThrough
)
238 Indexable indexable
= new Indexable (GetContactUri (contact
));
239 indexable
.Timestamp
= rev
;
240 indexable
.Type
= "Contact";
242 indexable
.AddProperty (Property
.New ("fixme:FileAs", contact
.FileAs
));
243 indexable
.AddProperty (Property
.New ("fixme:GivenName", contact
.GivenName
));
244 indexable
.AddProperty (Property
.New ("fixme:FamilyName", contact
.FamilyName
));
245 indexable
.AddProperty (Property
.New ("fixme:Nickname", contact
.Nickname
));
246 indexable
.AddProperty (Property
.New ("fixme:AddressLabelHome", contact
.AddressLabelHome
));
247 indexable
.AddProperty (Property
.New ("fixme:AddressLabelWork", contact
.AddressLabelWork
));
248 indexable
.AddProperty (Property
.New ("fixme:AddressLabelOther", contact
.AddressLabelOther
));
249 indexable
.AddProperty (Property
.New ("fixme:AssistantPhone", contact
.AssistantPhone
));
250 indexable
.AddProperty (Property
.New ("fixme:BusinessPhone", contact
.BusinessPhone
));
251 indexable
.AddProperty (Property
.New ("fixme:BusinessPhone2", contact
.BusinessPhone2
));
252 indexable
.AddProperty (Property
.New ("fixme:BusinessFax", contact
.BusinessFax
));
253 indexable
.AddProperty (Property
.New ("fixme:CallbackPhone", contact
.CallbackPhone
));
254 indexable
.AddProperty (Property
.New ("fixme:CarPhone", contact
.CarPhone
));
255 indexable
.AddProperty (Property
.New ("fixme:CompanyPhone", contact
.CompanyPhone
));
256 indexable
.AddProperty (Property
.New ("fixme:HomePhone", contact
.HomePhone
));
257 indexable
.AddProperty (Property
.New ("fixme:HomePhone2", contact
.HomePhone2
));
258 indexable
.AddProperty (Property
.New ("fixme:HomeFax", contact
.HomeFax
));
259 indexable
.AddProperty (Property
.New ("fixme:IsdnPhone", contact
.IsdnPhone
));
260 indexable
.AddProperty (Property
.New ("fixme:MobilePhone", contact
.MobilePhone
));
261 indexable
.AddProperty (Property
.New ("fixme:OtherPhone", contact
.OtherPhone
));
262 indexable
.AddProperty (Property
.New ("fixme:OtherFax", contact
.OtherFax
));
263 indexable
.AddProperty (Property
.New ("fixme:Pager", contact
.Pager
));
264 indexable
.AddProperty (Property
.New ("fixme:PrimaryPhone", contact
.PrimaryPhone
));
265 indexable
.AddProperty (Property
.New ("fixme:Radio", contact
.Radio
));
266 indexable
.AddProperty (Property
.New ("fixme:Telex", contact
.Telex
));
267 indexable
.AddProperty (Property
.NewKeyword ("fixme:Tty", contact
.Tty
));
268 indexable
.AddProperty (Property
.NewKeyword ("fixme:Email1", contact
.Email1
));
269 indexable
.AddProperty (Property
.NewKeyword ("fixme:Email2", contact
.Email2
));
270 indexable
.AddProperty (Property
.NewKeyword ("fixme:Email3", contact
.Email3
));
271 indexable
.AddProperty (Property
.NewKeyword ("fixme:Mailer", contact
.Mailer
));
272 indexable
.AddProperty (Property
.New ("fixme:Org", contact
.Org
));
273 indexable
.AddProperty (Property
.New ("fixme:OrgUnit", contact
.OrgUnit
));
274 indexable
.AddProperty (Property
.New ("fixme:Office", contact
.Office
));
275 indexable
.AddProperty (Property
.New ("fixme:Title", contact
.Title
));
276 indexable
.AddProperty (Property
.New ("fixme:Role", contact
.Role
));
277 indexable
.AddProperty (Property
.New ("fixme:Manager", contact
.Manager
));
278 indexable
.AddProperty (Property
.New ("fixme:Assistant", contact
.Assistant
));
279 indexable
.AddProperty (Property
.NewKeyword ("fixme:HomepageUrl", contact
.HomepageUrl
));
280 indexable
.AddProperty (Property
.NewKeyword ("fixme:BlogUrl", contact
.BlogUrl
));
281 indexable
.AddProperty (Property
.NewKeyword ("fixme:Categories", contact
.Categories
));
282 indexable
.AddProperty (Property
.NewKeyword ("fixme:Caluri", contact
.Caluri
));
283 indexable
.AddProperty (Property
.NewKeyword ("fixme:Icscalendar", contact
.Icscalendar
));
284 indexable
.AddProperty (Property
.New ("fixme:Spouse", contact
.Spouse
));
285 indexable
.AddProperty (Property
.New ("fixme:Note", contact
.Note
));
287 Evolution
.ContactPhoto photo
= contact
.Photo
;
289 if (photo
.Data
!= null && photo
.Data
.Length
> 0) {
290 string photo_filename
= GetPhotoFilename (contact
.Id
);
291 Stream s
= new FileStream (photo_filename
, FileMode
.Create
, FileAccess
.Write
, FileShare
.ReadWrite
);
292 BinaryWriter w
= new BinaryWriter (s
);
293 w
.Write (photo
.Data
);
297 indexable
.AddProperty (Property
.NewUnsearched ("beagle:Photo", photo_filename
));
300 // FIXME: ListShowAddresses?
302 // FIXME: Should we not drop the extra Im addresses?
303 if (contact
.ImAim
.Length
> 0)
304 indexable
.AddProperty (Property
.NewKeyword ("fixme:ImAim", contact
.ImAim
[0]));
305 if (contact
.ImIcq
.Length
> 0)
306 indexable
.AddProperty (Property
.NewKeyword ("fixme:ImIcq", contact
.ImIcq
[0]));
307 if (contact
.ImJabber
.Length
> 0)
308 indexable
.AddProperty (Property
.NewKeyword ("fixme:ImJabber", contact
.ImJabber
[0]));
309 if (contact
.ImMsn
.Length
> 0)
310 indexable
.AddProperty (Property
.NewKeyword ("fixme:ImMsn", contact
.ImMsn
[0]));
311 if (contact
.ImYahoo
.Length
> 0)
312 indexable
.AddProperty (Property
.NewKeyword ("fixme:ImYahoo", contact
.ImYahoo
[0]));
313 if (contact
.ImGroupwise
.Length
> 0)
314 indexable
.AddProperty (Property
.NewKeyword ("fixme:ImGroupWise", contact
.ImGroupwise
[0]));
317 if (contact
.GivenName
!= null && contact
.GivenName
!= "")
318 name
= contact
.GivenName
;
319 if (contact
.FamilyName
!= null && contact
.FamilyName
!= "")
320 name
+= " " + contact
.FamilyName
;
322 indexable
.AddProperty (Property
.New ("fixme:Name", name
));
324 if (contact
.Email1
!= null)
325 indexable
.AddProperty (Property
.NewKeyword ("fixme:Email",
330 private void AddContact (Contact contact
)
332 Indexable indexable
= ContactToIndexable (contact
);
333 if (indexable
!= null)
334 Add (indexable
, priority
);
337 private void RemoveContact (string id
)
339 Remove (GetContactUri (id
));
341 string filename
= GetPhotoFilename (id
);
342 if (filename
!= null && File
.Exists (filename
))
343 File
.Delete (filename
);
346 private void OnContactsAdded (object o
,
347 Evolution
.ContactsAddedArgs args
)
349 foreach (Evolution
.Contact contact
in args
.Contacts
)
350 AddContact (contact
);
353 private void OnContactsChanged (object o
,
354 Evolution
.ContactsChangedArgs args
)
356 foreach (Evolution
.Contact contact
in args
.Contacts
)
357 AddContact (contact
);
360 private void OnContactsRemoved (object o
,
361 Evolution
.ContactsRemovedArgs args
)
363 // FIXME: This is a temporary workaround for the
364 // fact that the evolution bindings return a
365 // GLib.List with an object type, but there
366 // are really strings in there
368 GLib
.List id_list
= new GLib
.List (args
.Ids
.Handle
,
372 foreach (string id
in id_list
)
376 private string GetPhotoFilename (string id
)
378 return Path
.Combine (photo_dir
, id
);
381 private void OnSequenceComplete (object o
,
382 Evolution
.SequenceCompleteArgs args
)
384 // Contacts that get changed while the beagled is
385 // running will be re-indexed during the next scan.
386 // That isn't optimal, but is much better than the
387 // current situation.
388 AddressbookIndexedThrough
= sequence_start_time
;
390 // Now that we're done synching with the original
391 // state of the addressbook, switch all new changes to
393 priority
= Scheduler
.Priority
.Immediate
;
396 ///////////////////////////////////////
398 private void CalendarIndexSourceAll (Evolution
.Source src
)
400 if (!src
.IsLocal ()) {
401 Logger
.Log
.Debug ("Skipping remote calendar {0}", src
.Uri
);
405 Logger
.Log
.Debug ("Indexing all data in this calendar ({0})!", src
.Uri
);
407 Cal cal
= new Cal (src
, CalSourceType
.Event
);
410 CalComponent
[] event_list
= cal
.GetItems ("#t");
412 Logger
.Log
.Debug ("Calendar has {0} items", event_list
.Length
);
414 foreach (CalComponent cc
in event_list
)
415 IndexCalComponent (cc
, Scheduler
.Priority
.Immediate
);
417 CalView cal_view
= cal
.GetCalView ("#t");
418 cal_view
.ObjectsAdded
+= OnObjectsAdded
;
419 cal_view
.ObjectsModified
+= OnObjectsModified
;
420 cal_view
.ObjectsRemoved
+= OnObjectsRemoved
;
424 private void CalendarIndexSourceChanges (Evolution
.Source src
)
426 if (!src
.IsLocal ()) {
427 Logger
.Log
.Debug ("Skipping remote calendar {0}", src
.Uri
);
431 Cal cal
= new Cal (src
, CalSourceType
.Event
);
434 CalComponent
[] new_items
, update_items
;
435 string[] remove_items
;
437 Logger
.Log
.Debug ("Getting calendar changes for {0}", src
.Uri
);
438 cal
.GetChanges ("beagle-" + this.Driver
.Fingerprint
, out new_items
, out update_items
, out remove_items
);
439 Logger
.Log
.Debug ("Calendar {0}: {1} new items, {2} updated items, {3} removed items",
440 cal
.Uri
, new_items
.Length
, update_items
.Length
, remove_items
.Length
);
442 foreach (CalComponent cc
in new_items
)
443 IndexCalComponent (cc
, Scheduler
.Priority
.Immediate
);
445 foreach (CalComponent cc
in update_items
)
446 IndexCalComponent (cc
, Scheduler
.Priority
.Immediate
);
448 foreach (string id
in remove_items
) {
449 // FIXME: Broken in evo-sharp right now
450 //RemoveCalComponent (id);
453 CalView cal_view
= cal
.GetCalView ("#t");
454 cal_view
.ObjectsAdded
+= OnObjectsAdded
;
455 cal_view
.ObjectsModified
+= OnObjectsModified
;
456 cal_view
.ObjectsRemoved
+= OnObjectsRemoved
;
460 private void CalendarRemoveSource (Evolution
.Source src
)
462 // FIXME: We need to index the group's UID and then
463 // we need a way to schedule removal tasks for
464 // anything that matches that lucene property
465 Logger
.Log
.Debug ("FIXME: Remove calendar source {0}", src
.Uri
);
468 private static Uri
GetCalendarUri (CalComponent cc
) {
469 return GetContactUri (cc
.Uid
);
472 private static Uri
GetCalendarUri (string id
) {
473 return new Uri ("calendar://" + id
, true); // FIXME!
476 private DateTime CalendarIndexedThrough
{
479 if (calendar_indexed_through
== DateTime
.MinValue
) {
480 string filename
= Path
.Combine (IndexStoreDirectory
, "CalendarIndexedThrough");
484 StreamReader sr
= new StreamReader (filename
);
485 line
= sr
.ReadLine ();
487 } catch (Exception ex
) { }
490 calendar_indexed_through
= StringFu
.StringToDateTime (line
);
492 return calendar_indexed_through
;
496 calendar_indexed_through
= value;
498 string filename
= Path
.Combine (IndexStoreDirectory
, "CalendarIndexedThrough");
499 StreamWriter sw
= new StreamWriter (filename
);
500 sw
.WriteLine (StringFu
.DateTimeToString (calendar_indexed_through
));
505 private void IndexCalComponent (CalComponent cc
, Scheduler
.Priority priority
)
507 Indexable indexable
= CalComponentToIndexable (cc
);
508 Add (indexable
, priority
);
511 private void RemoveCalComponent (string id
)
513 Remove (GetCalendarUri (id
));
516 private Indexable
CalComponentToIndexable (CalComponent cc
)
518 Indexable indexable
= new Indexable (new Uri ("calendar:///" + cc
.Uid
));
520 indexable
.Timestamp
= cc
.Dtstart
;
521 indexable
.Type
= "Calendar";
523 indexable
.AddProperty (Property
.NewKeyword ("fixme:uid", cc
.Uid
));
524 indexable
.AddProperty (Property
.NewDate ("fixme:starttime", cc
.Dtstart
));
525 indexable
.AddProperty (Property
.NewDate ("fixme:endtime", cc
.Dtend
));
527 foreach (string attendee
in cc
.Attendees
)
528 indexable
.AddProperty (Property
.New ("fixme:attendee", attendee
));
530 foreach (string comment
in cc
.Comments
)
531 indexable
.AddProperty (Property
.New ("fixme:comment", comment
));
533 foreach (string description
in cc
.Descriptions
)
534 indexable
.AddProperty (Property
.New ("fixme:description", description
));
536 foreach (string summary
in cc
.Summaries
)
537 indexable
.AddProperty (Property
.New ("fixme:summary", summary
));
539 foreach (string category
in cc
.Categories
)
540 indexable
.AddProperty (Property
.NewKeyword ("fixme:category", category
));
542 foreach (string location
in cc
.Location
)
543 indexable
.AddProperty (Property
.New ("fixme:location", location
));
548 private void OnObjectsAdded (object o
, ObjectsAddedArgs args
)
550 CalView cal_view
= (CalView
) o
;
552 foreach (CalComponent cc
in CalUtil
.CalCompFromICal (args
.Objects
.Handle
, cal_view
.Client
)) {
553 // If the minimum date is unset/invalid,
555 if (cc
.LastModified
<= CalUtil
.MinDate
|| cc
.LastModified
> CalendarIndexedThrough
)
556 IndexCalComponent (cc
, Scheduler
.Priority
.Immediate
);
560 private void OnObjectsModified (object o
, ObjectsModifiedArgs args
)
562 CalView cal_view
= (CalView
) o
;
564 foreach (CalComponent cc
in CalUtil
.CalCompFromICal (args
.Objects
.Handle
, cal_view
.Client
))
565 IndexCalComponent (cc
, Scheduler
.Priority
.Immediate
);
568 private void OnObjectsRemoved (object o
, ObjectsRemovedArgs args
)
570 // FIXME: This is a temporary workaround for the
571 // fact that the evolution bindings return a
572 // GLib.List with an object type, but there
573 // are really strings in there
575 GLib
.List id_list
= new GLib
.List (args
.Uids
.Handle
,
578 foreach (string uid
in id_list
) {
580 task
= NewRemoveTask (new Uri ("calendar:///" + uid
));
581 task
.Priority
= Scheduler
.Priority
.Immediate
;
582 ThisScheduler
.Add (task
);