Thumbnail file hits. Based on a patch from D Bera
[beagle.git] / beagled / EvolutionDataServerQueryable / EvolutionDataServerQueryable.cs
blob9d822eac7040554270329c60f7420c3772888eb7
1 //
2 // EvolutionDataServerQueryable.cs
3 //
4 // Copyright (C) 2004 Novell, Inc.
5 //
7 //
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.
28 using System;
29 using System.Collections;
30 using System.Globalization;
31 using System.Text;
32 using System.Threading;
33 using System.IO;
35 using Beagle.Daemon;
36 using Beagle.Util;
38 using Evolution;
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 ()
61 base.Start ();
63 Logger.Log.Info ("Scanning addressbooks and calendars");
64 Stopwatch timer = new Stopwatch ();
65 timer.Start ();
67 start_time = DateTime.Now;
69 EdsSource src;
71 src = new EdsSource ("/apps/evolution/addressbook/sources");
72 src.IndexSourceAll += AddressbookIndexSourceAll;
73 src.IndexSourceChanges += AddressbookIndexSourceChanges;
74 src.RemoveSource += AddressbookRemoveSource;
75 src.Index ();
77 src = new EdsSource ("/apps/evolution/calendar/sources");
78 src.IndexSourceAll += CalendarIndexSourceAll;
79 src.IndexSourceChanges += CalendarIndexSourceChanges;
80 src.RemoveSource += CalendarRemoveSource;
81 src.Index ();
83 timer.Stop ();
84 Logger.Log.Info ("Scanned addressbooks and calendars in {0}", timer);
87 public void Add (Indexable indexable, Scheduler.Priority priority)
89 Scheduler.Task task;
90 task = NewAddTask (indexable);
91 task.Priority = priority;
92 ThisScheduler.Add (task);
95 public void Remove (Uri uri)
97 Scheduler.Task task;
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);
109 return;
112 Logger.Log.Debug ("Indexing all data in this addressbook ({0})!", src.Uri);
114 Book book = new Book (src);
115 book.Open (true);
117 BookView book_view = book.GetBookView (BookQuery.AnyFieldContains (""),
118 new object [0],
119 -1);
121 book_view.ContactsAdded += OnContactsAdded;
122 book_view.ContactsRemoved += OnContactsRemoved;
123 book_view.ContactsChanged += OnContactsChanged;
124 book_view.SequenceComplete += OnSequenceComplete;
126 book_view.Start ();
129 private void AddressbookIndexSourceChanges (Evolution.Source src)
131 if (!src.IsLocal ()) {
132 Logger.Log.Debug ("Skipping remote addressbook {0}", src.Uri);
133 return;
136 Book book = new Book (src);
137 book.Open (true);
139 Contact[] added, changed;
140 string[] removed;
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)
154 RemoveContact (id);
156 BookView book_view = book.GetBookView (BookQuery.AnyFieldContains (""),
157 new object [0],
158 -1);
160 book_view.ContactsAdded += OnContactsAdded;
161 book_view.ContactsRemoved += OnContactsRemoved;
162 book_view.ContactsChanged += OnContactsChanged;
163 book_view.SequenceComplete += OnSequenceComplete;
165 book_view.Start ();
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 {
186 get {
187 if (addressbook_indexed_through == DateTime.MinValue) {
188 string filename = Path.Combine (IndexDirectory, "AddressbookIndexedThrough");
190 string line = null;
191 try {
192 StreamReader sr = new StreamReader (filename);
193 line = sr.ReadLine ();
194 sr.Close ();
195 } catch (Exception ex) { }
197 if (line != null)
198 addressbook_indexed_through = StringFu.StringToDateTime (line);
200 return addressbook_indexed_through;
203 set {
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));
209 sw.Close ();
213 private static DateTime RevStringToDateTime (string date_str)
215 if (date_str == null)
216 return DateTime.MinValue;
218 string[] formats = {
219 "yyyy'-'MM'-'dd'T'HH':'mm':'ss'Z'",
220 "yyyyMMdd'T'HHmmss'Z'"
223 try {
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)
238 return null;
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);
296 w.Close ();
297 s.Close ();
299 indexable.AddProperty (Property.NewUnsearched ("beagle:Photo", photo_filename));
301 // FIXME: List?
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]));
318 String name = "";
319 if (contact.GivenName != null && contact.GivenName != "")
320 name = contact.GivenName;
321 if (contact.FamilyName != null && contact.FamilyName != "")
322 name += " " + contact.FamilyName;
323 if (name.Length > 0)
324 indexable.AddProperty (Property.New ("fixme:Name", name));
326 if (contact.Email1 != null)
327 indexable.AddProperty (Property.NewKeyword ("fixme:Email",
328 contact.Email1));
329 return indexable;
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,
371 typeof (string));
374 foreach (string id in id_list)
375 RemoveContact (id);
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
394 // Immediate mode
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);
404 return;
407 Logger.Log.Debug ("Indexing all data in this calendar ({0})!", src.Uri);
409 Cal cal = new Cal (src, CalSourceType.Event);
410 cal.Open (true);
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;
424 cal_view.Start ();
427 private void CalendarIndexSourceChanges (Evolution.Source src)
429 if (!src.IsLocal ()) {
430 Logger.Log.Debug ("Skipping remote calendar {0}", src.Uri);
431 return;
434 Cal cal = new Cal (src, CalSourceType.Event);
435 cal.Open (true);
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;
461 cal_view.Start ();
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 {
482 get {
483 if (calendar_indexed_through == DateTime.MinValue) {
484 string filename = Path.Combine (IndexDirectory, "CalendarIndexedThrough");
486 string line = null;
487 try {
488 StreamReader sr = new StreamReader (filename);
489 line = sr.ReadLine ();
490 sr.Close ();
491 } catch (Exception ex) { }
493 if (line != null)
494 calendar_indexed_through = StringFu.StringToDateTime (line);
496 return calendar_indexed_through;
499 set {
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));
505 sw.Close ();
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));
549 return indexable;
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,
598 typeof (string));
600 foreach (string uid in id_list) {
601 Scheduler.Task task;
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;