Remove some debug spew
[beagle.git] / beagled / KAddressBookQueryable / KabcQueryable.cs
blob946a61a1dcbfbac09d8ea5b70e49dd54146aaeb3
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 static Logger log = Logger.Get ("KAddressBookQueryable");
48 private string kabc_dir;
49 private string kabc_file;
50 private Hashtable last_modified_table;
52 public KabcQueryable () : base ("KAddressBookIndex")
54 kabc_dir = Path.Combine (PathFinder.HomeDir, ".kde");
55 kabc_dir = Path.Combine (kabc_dir, "share");
56 kabc_dir = Path.Combine (kabc_dir, "apps");
57 kabc_dir = Path.Combine (kabc_dir, "kabc");
59 kabc_file = Path.Combine (kabc_dir, "std.vcf");
61 last_modified_table = new Hashtable ();
64 /////////////////////////////////////////////////
66 public override void Start ()
68 base.Start ();
70 ExceptionHandlingThread.Start (new ThreadStart (StartWorker));
73 private void StartWorker ()
75 if (!Directory.Exists (kabc_dir)) {
76 GLib.Timeout.Add (60000, new GLib.TimeoutHandler (CheckForExistence));
77 return;
80 if (Inotify.Enabled) {
81 Inotify.EventType mask = Inotify.EventType.CloseWrite
82 | 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.Changed += new FileSystemEventHandler (OnChangedEvent);
90 fsw.Created += new FileSystemEventHandler (OnChangedEvent);
91 fsw.Renamed += new RenamedEventHandler (OnChangedEvent);
93 fsw.EnableRaisingEvents = true;
96 if (File.Exists (kabc_file)) {
97 if (! FileAttributesStore.IsUpToDate (kabc_file))
98 Index ();
99 else
100 ScanAddrInitial ();
104 private bool CheckForExistence ()
106 if (!Directory.Exists (kabc_dir))
107 return true;
109 this.Start ();
111 return false;
114 // We need to scan the addresses nitially to fill up last_modified_table
115 // Otherwise, we might miss deletions that occur before any addition
116 private void ScanAddrInitial ()
118 AddressIndexableGenerator generator = new AddressIndexableGenerator (this, kabc_file, last_modified_table, true);
120 // just a dummy scan
121 while (generator.HasNextIndexable ())
122 generator.GetNextIndexable ();
125 /////////////////////////////////////////////////
127 // Modified event using Inotify
128 private void OnInotifyEvent (Inotify.Watch watch,
129 string path,
130 string subitem,
131 string srcpath,
132 Inotify.EventType type)
134 if (Path.Combine (path, subitem) != kabc_file)
135 return;
137 Index ();
140 // Modified/Created event using FSW
141 private void OnChangedEvent (object o, FileSystemEventArgs args)
143 Index ();
146 /////////////////////////////////////////////////
148 private void Index ()
150 if (ThisScheduler.ContainsByTag ("KAddressBook")) {
151 Logger.Log.Debug ("Not adding task for already running Kabc task");
152 return;
155 AddressIndexableGenerator generator = new AddressIndexableGenerator (this, kabc_file, last_modified_table, false);
156 Scheduler.Task task;
157 task = NewAddTask (generator);
158 task.Tag = "KAddressBook";
159 task.Priority = Scheduler.Priority.Delayed;
160 task.SubPriority = 0;
161 ThisScheduler.Add (task);
164 internal void RemoveDeletedContacts (Hashtable deleted_contacts)
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 RemoveContact (uid);
176 private void RemoveContact (string uid)
178 Uri uri = new Uri (String.Format ("kabc:///{0}", uid));
179 Logger.Log.Debug ("Removing contact {0}", uri);
180 Scheduler.Task task = NewRemoveTask (uri);
181 task.Priority = Scheduler.Priority.Immediate;
182 task.SubPriority = 0;
183 ThisScheduler.Add (task);
189 * Indexable generator for Kabc Feeds
191 internal class AddressIndexableGenerator : IIndexableGenerator {
192 private string kabc_file;
193 private StreamReader reader;
194 private KabcQueryable queryable;
195 private bool is_valid_file = true;
196 private Hashtable last_modified_table;
197 private Hashtable deleted_contacts;
198 private bool initial_scan;
199 private DateTime file_last_write_time;
200 private string current_uid = String.Empty;
201 private DateTime current_dt;
203 public AddressIndexableGenerator (KabcQueryable queryable,
204 string kabc_file,
205 Hashtable last_modified_table,
206 bool initial_scan)
208 this.queryable = queryable;
209 this.kabc_file = kabc_file;
210 this.initial_scan = initial_scan;
212 CheckContactHeader ();
213 if (is_valid_file)
214 string_builder = new StringBuilder ();
216 this.last_modified_table = last_modified_table;
217 lock (last_modified_table) {
218 this.deleted_contacts = new Hashtable (last_modified_table.Count);
219 foreach (string uid in last_modified_table.Keys)
220 this.deleted_contacts [uid] = true;
223 // cache the last write time
224 file_last_write_time = FileSystem.GetLastWriteTimeUtc (kabc_file);
227 public void PostFlushHook ()
229 //queryable.FileAttributesStore.AttachLastWriteTime (kabc_file, DateTime.UtcNow);
232 public string StatusName {
233 get { return current_uid; }
236 private bool IsUpToDate (string path)
238 return queryable.FileAttributesStore.IsUpToDate (path);
241 private void CheckContactHeader () {
242 if ( (! initial_scan) && IsUpToDate (kabc_file)) {
243 is_valid_file = false;
244 return;
246 try {
247 Logger.Log.Debug ("Checking if {0} is a valid Kabc file.", kabc_file);
248 reader = new StreamReader (kabc_file);
249 } catch (Exception ex) {
250 is_valid_file = false;
251 reader.Close ();
255 private StringBuilder string_builder;
257 public bool HasNextIndexable ()
259 if (!is_valid_file || reader == null)
260 return false;
262 string line;
263 while ((line = reader.ReadLine ()) != null) {
264 if (line == "BEGIN:VCARD")
265 if (ReadContact ())
266 return true;
269 if (line == null) {
270 reader.Close ();
271 if (! initial_scan)
272 queryable.RemoveDeletedContacts (deleted_contacts);
273 return false;
276 return false;
279 string[] fmts = {"yyyy-MM-ddTHH:mm:sszzz"};
281 private bool ReadContact ()
283 string line;
284 string_builder.Length = 0;
286 current_uid = null;
287 // date is set to REV:date if specified, o/w file write time
288 current_dt = file_last_write_time;
290 // Keep reading till "END:VCARD"
291 while ((line = reader.ReadLine ()) != null) {
292 //UID:4QBCOYKj4c
293 //REV:2006-09-30T17:41:52Z
294 if (line == "END:VCARD")
295 break;
296 else if (line.StartsWith ("UID:"))
297 current_uid = line.Substring (4);
298 else if (line.StartsWith ("REV:")) {
299 string dt_string = line.Substring (4);
300 dt_string = dt_string.Replace ("Z", "+00:00");
301 current_dt = DateTime.ParseExact (
302 dt_string,
303 fmts,
304 DateTimeFormatInfo.InvariantInfo,
305 DateTimeStyles.AdjustToUniversal);
306 } else {
307 string_builder.Append (line);
308 string_builder.Append ('\n');
312 if (line == null) {
313 reader.Close ();
314 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;