Remove debug spew
[beagle.git] / beagled / KAddressBookQueryable / KabcQueryable.cs
blob3a830e1979c6de348bf7af3336ff131da854f6f9
1 //
2 // KabcQueryable.cs
3 //
4 // Copyright (C) 2006 Debajyoti Bera <dbera.web@gmail.com>
5 // Copyright (C) 2004 Novell, Inc.
6 //
8 //
9 // Permission is hereby granted, free of charge, to any person obtaining a
10 // copy of this software and associated documentation files (the "Software"),
11 // to deal in the Software without restriction, including without limitation
12 // the rights to use, copy, modify, merge, publish, distribute, sublicense,
13 // and/or sell copies of the Software, and to permit persons to whom the
14 // Software is furnished to do so, subject to the following conditions:
16 // The above copyright notice and this permission notice shall be included in
17 // all copies or substantial portions of the Software.
19 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
20 // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
21 // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
22 // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
23 // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
24 // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
25 // DEALINGS IN THE SOFTWARE.
28 using System;
29 using System.IO;
30 using System.Text;
31 using System.Collections;
32 using System.Threading;
33 using System.Globalization;
35 using Beagle.Daemon;
36 using Beagle.Util;
38 // Currently this backend is awfully similar to the KNotes backend! Tons of duplication of code.
39 // However, kabc will ultimately be different when it tries to grab data from non-disk stores.
41 namespace Beagle.Daemon.KabcQueryable {
43 [QueryableFlavor (Name="KAddressBook", Domain=QueryDomain.Local, RequireInotify=false)]
44 public class KabcQueryable : LuceneFileQueryable {
46 private string kabc_dir;
47 private string kabc_file;
48 private Hashtable last_modified_table;
50 // 1: Update KAddressbook filter.
51 const int MINOR_VERSION = 1;
53 public KabcQueryable () : base ("KAddressBookIndex", MINOR_VERSION)
55 kabc_dir = Path.Combine (PathFinder.HomeDir, ".kde");
56 kabc_dir = Path.Combine (kabc_dir, "share");
57 kabc_dir = Path.Combine (kabc_dir, "apps");
58 kabc_dir = Path.Combine (kabc_dir, "kabc");
60 kabc_file = Path.Combine (kabc_dir, "std.vcf");
62 last_modified_table = new Hashtable ();
65 /////////////////////////////////////////////////
67 public override void Start ()
69 base.Start ();
71 ExceptionHandlingThread.Start (new ThreadStart (StartWorker));
74 private void StartWorker ()
76 if (!Directory.Exists (kabc_dir)) {
77 GLib.Timeout.Add (60000, new GLib.TimeoutHandler (CheckForExistence));
78 return;
81 if (Inotify.Enabled) {
82 Inotify.EventType mask = Inotify.EventType.Create | Inotify.EventType.MovedTo;
83 Inotify.Subscribe (kabc_dir, OnInotifyEvent, mask);
84 } else {
85 FileSystemWatcher fsw = new FileSystemWatcher ();
86 fsw.Path = kabc_dir;
87 fsw.Filter = kabc_file;
89 fsw.Created += new FileSystemEventHandler (OnChangedEvent);
90 fsw.Renamed += new RenamedEventHandler (OnChangedEvent);
92 fsw.EnableRaisingEvents = true;
95 if (File.Exists (kabc_file)) {
96 if (! FileAttributesStore.IsUpToDate (kabc_file))
97 Index ();
98 else
99 ScanAddrInitial ();
103 private bool CheckForExistence ()
105 if (!Directory.Exists (kabc_dir))
106 return true;
108 this.Start ();
110 return false;
113 // We need to scan the addresses nitially to fill up last_modified_table
114 // Otherwise, we might miss deletions that occur before any addition
115 private void ScanAddrInitial ()
117 AddressIndexableGenerator generator = new AddressIndexableGenerator (this, kabc_file, last_modified_table, true);
119 // just a dummy scan
120 while (generator.HasNextIndexable ())
121 generator.GetNextIndexable ();
124 /////////////////////////////////////////////////
126 // Modified event using Inotify
127 private void OnInotifyEvent (Inotify.Watch watch,
128 string path,
129 string subitem,
130 string srcpath,
131 Inotify.EventType type)
133 if (Path.Combine (path, subitem) != kabc_file)
134 return;
136 Index ();
139 // Modified/Created event using FSW
140 private void OnChangedEvent (object o, FileSystemEventArgs args)
142 Index ();
145 /////////////////////////////////////////////////
147 private void Index ()
149 if (ThisScheduler.ContainsByTag ("KAddressBook")) {
150 Logger.Log.Debug ("Not adding task for already running Kabc task");
151 return;
154 AddressIndexableGenerator generator = new AddressIndexableGenerator (this, kabc_file, last_modified_table, false);
155 Scheduler.Task task;
156 task = NewAddTask (generator);
157 task.Tag = "KAddressBook";
158 task.Priority = Scheduler.Priority.Delayed;
159 task.SubPriority = 0;
160 ThisScheduler.Add (task);
163 internal void RemoveDeletedContacts (Hashtable deleted_contacts)
165 ArrayList to_delete = new ArrayList ();
166 lock (last_modified_table) {
167 foreach (string uid in last_modified_table.Keys) {
168 if (! deleted_contacts.Contains (uid) ||
169 (bool)deleted_contacts [uid] == true) {
170 to_delete.Add (uid);
175 foreach (string uid in to_delete) {
176 RemoveContact (uid);
177 last_modified_table.Remove (uid);
181 private void RemoveContact (string uid)
183 Uri uri = new Uri (String.Format ("kabc:///{0}", uid));
184 Logger.Log.Debug ("Removing contact {0}", uri);
185 Scheduler.Task task = NewRemoveTask (uri);
186 task.Priority = Scheduler.Priority.Immediate;
187 task.SubPriority = 0;
188 ThisScheduler.Add (task);
194 * Indexable generator for Kabc Feeds
196 internal class AddressIndexableGenerator : IIndexableGenerator {
197 private string kabc_file;
198 private StreamReader reader;
199 private KabcQueryable queryable;
200 private bool is_valid_file = true;
201 private Hashtable last_modified_table;
202 private Hashtable deleted_contacts;
203 private bool initial_scan;
204 private DateTime file_last_write_time;
205 private string current_uid = String.Empty;
206 private DateTime current_dt;
208 public AddressIndexableGenerator (KabcQueryable queryable,
209 string kabc_file,
210 Hashtable last_modified_table,
211 bool initial_scan)
213 this.queryable = queryable;
214 this.kabc_file = kabc_file;
215 this.initial_scan = initial_scan;
217 CheckContactHeader ();
218 if (is_valid_file)
219 string_builder = new StringBuilder ();
221 this.last_modified_table = last_modified_table;
222 lock (last_modified_table) {
223 this.deleted_contacts = new Hashtable (last_modified_table.Count);
224 foreach (string uid in last_modified_table.Keys)
225 this.deleted_contacts [uid] = true;
228 // cache the last write time
229 file_last_write_time = FileSystem.GetLastWriteTimeUtc (kabc_file);
232 public void PostFlushHook ()
234 //queryable.FileAttributesStore.AttachLastWriteTime (kabc_file, DateTime.UtcNow);
237 public string StatusName {
238 get { return current_uid; }
241 private bool IsUpToDate (string path)
243 return queryable.FileAttributesStore.IsUpToDate (path);
246 private void CheckContactHeader () {
247 if ( (! initial_scan) && IsUpToDate (kabc_file)) {
248 is_valid_file = false;
249 return;
251 try {
252 Logger.Log.Debug ("Checking if {0} is a valid Kabc file.", kabc_file);
253 reader = new StreamReader (kabc_file);
254 } catch (Exception ex) {
255 is_valid_file = false;
256 reader.Close ();
260 private StringBuilder string_builder;
262 public bool HasNextIndexable ()
264 if (!is_valid_file || reader == null)
265 return false;
267 string line;
268 while ((line = reader.ReadLine ()) != null) {
269 if (line == "BEGIN:VCARD")
270 if (ReadContact ())
271 return true;
274 reader.Close ();
275 if (! initial_scan)
276 queryable.RemoveDeletedContacts (deleted_contacts);
278 return false;
281 string[] fmts = {"yyyy-MM-ddTHH:mm:sszzz"};
283 private bool ReadContact ()
285 string line;
286 string_builder.Length = 0;
288 current_uid = null;
289 // date is set to REV:date if specified, o/w file write time
290 current_dt = file_last_write_time;
292 // Keep reading till "END:VCARD"
293 while ((line = reader.ReadLine ()) != null) {
294 //UID:4QBCOYKj4c
295 //REV:2006-09-30T17:41:52Z
296 if (line == "END:VCARD")
297 break;
298 else if (line.StartsWith ("UID:"))
299 current_uid = line.Substring (4);
300 else if (line.StartsWith ("REV:")) {
301 string dt_string = line.Substring (4);
302 dt_string = dt_string.Replace ("Z", "+00:00");
303 current_dt = DateTime.ParseExact (
304 dt_string,
305 fmts,
306 DateTimeFormatInfo.InvariantInfo,
307 DateTimeStyles.AdjustToUniversal);
308 } else {
309 string_builder.Append (line);
310 string_builder.Append ('\n');
314 if (line == null)
315 return false;
317 // Bad contact
318 if (string_builder.Length == 0 || current_uid == null)
319 return false;
321 // Mark address with current_uid as seen ('undeleted')
322 deleted_contacts [current_uid] = false;
324 lock (last_modified_table) {
325 if (last_modified_table.Contains (current_uid)) {
326 DateTime old_dt = (DateTime) last_modified_table [current_uid];
327 // Address up-to-date
328 if (current_dt == old_dt)
329 return false;
330 else {
331 //Log.Debug ("Updating last_mod_date [{0}] = {1}", current_uid, current_dt);
332 last_modified_table [current_uid] = current_dt;
334 } else {
335 //Log.Debug ("Adding last_mod_date [{0}] = {1}", current_uid, current_dt);
336 last_modified_table [current_uid] = current_dt;
340 return true;
344 public Indexable GetNextIndexable ()
346 if (initial_scan)
347 return null;
349 Uri uri = new Uri (String.Format ("kabc:///{0}", current_uid));
350 Indexable indexable = new Indexable (uri);
351 indexable.ParentUri = UriFu.PathToFileUri (kabc_file);
352 indexable.MimeType = ICalParser.KabcMimeType;
353 indexable.HitType = "Contact";
354 indexable.Timestamp = current_dt;
355 // Add uid as a keyword field for convenience
356 indexable.AddProperty (Property.NewUnsearched ("fixme:uid", current_uid));
358 // FIXME: Comment this Debug statement after the backend stabilizes
359 //Log.Debug ("Creating {0} from:[{1}]", uri, string_builder.ToString ());
360 StringReader string_reader = new StringReader (string_builder.ToString());
361 indexable.SetTextReader (string_reader);
363 return indexable;