NoiseFilter: Dont drop last word of apparent hostnames. Too many non-hostnames can...
[beagle.git] / beagled / KAddressBookQueryable / KabcQueryable.cs
blobec0b8597fc491721fc3ad2b4f3105c03ed4ac9b5
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 ArrayList to_delete = new ArrayList ();
167 lock (last_modified_table) {
168 foreach (string uid in last_modified_table.Keys) {
169 if (! deleted_contacts.Contains (uid) ||
170 (bool)deleted_contacts [uid] == true) {
171 to_delete.Add (uid);
176 foreach (string uid in to_delete) {
177 RemoveContact (uid);
178 last_modified_table.Remove (uid);
182 private void RemoveContact (string uid)
184 Uri uri = new Uri (String.Format ("kabc:///{0}", uid));
185 Logger.Log.Debug ("Removing contact {0}", uri);
186 Scheduler.Task task = NewRemoveTask (uri);
187 task.Priority = Scheduler.Priority.Immediate;
188 task.SubPriority = 0;
189 ThisScheduler.Add (task);
195 * Indexable generator for Kabc Feeds
197 internal class AddressIndexableGenerator : IIndexableGenerator {
198 private string kabc_file;
199 private StreamReader reader;
200 private KabcQueryable queryable;
201 private bool is_valid_file = true;
202 private Hashtable last_modified_table;
203 private Hashtable deleted_contacts;
204 private bool initial_scan;
205 private DateTime file_last_write_time;
206 private string current_uid = String.Empty;
207 private DateTime current_dt;
209 public AddressIndexableGenerator (KabcQueryable queryable,
210 string kabc_file,
211 Hashtable last_modified_table,
212 bool initial_scan)
214 this.queryable = queryable;
215 this.kabc_file = kabc_file;
216 this.initial_scan = initial_scan;
218 CheckContactHeader ();
219 if (is_valid_file)
220 string_builder = new StringBuilder ();
222 this.last_modified_table = last_modified_table;
223 lock (last_modified_table) {
224 this.deleted_contacts = new Hashtable (last_modified_table.Count);
225 foreach (string uid in last_modified_table.Keys)
226 this.deleted_contacts [uid] = true;
229 // cache the last write time
230 file_last_write_time = FileSystem.GetLastWriteTimeUtc (kabc_file);
233 public void PostFlushHook ()
235 //queryable.FileAttributesStore.AttachLastWriteTime (kabc_file, DateTime.UtcNow);
238 public string StatusName {
239 get { return current_uid; }
242 private bool IsUpToDate (string path)
244 return queryable.FileAttributesStore.IsUpToDate (path);
247 private void CheckContactHeader () {
248 if ( (! initial_scan) && IsUpToDate (kabc_file)) {
249 is_valid_file = false;
250 return;
252 try {
253 Logger.Log.Debug ("Checking if {0} is a valid Kabc file.", kabc_file);
254 reader = new StreamReader (kabc_file);
255 } catch (Exception ex) {
256 is_valid_file = false;
257 reader.Close ();
261 private StringBuilder string_builder;
263 public bool HasNextIndexable ()
265 if (!is_valid_file || reader == null)
266 return false;
268 string line;
269 while ((line = reader.ReadLine ()) != null) {
270 if (line == "BEGIN:VCARD")
271 if (ReadContact ())
272 return true;
275 if (line == null) {
276 reader.Close ();
277 if (! initial_scan)
278 queryable.RemoveDeletedContacts (deleted_contacts);
279 return false;
282 return false;
285 string[] fmts = {"yyyy-MM-ddTHH:mm:sszzz"};
287 private bool ReadContact ()
289 string line;
290 string_builder.Length = 0;
292 current_uid = null;
293 // date is set to REV:date if specified, o/w file write time
294 current_dt = file_last_write_time;
296 // Keep reading till "END:VCARD"
297 while ((line = reader.ReadLine ()) != null) {
298 //UID:4QBCOYKj4c
299 //REV:2006-09-30T17:41:52Z
300 if (line == "END:VCARD")
301 break;
302 else if (line.StartsWith ("UID:"))
303 current_uid = line.Substring (4);
304 else if (line.StartsWith ("REV:")) {
305 string dt_string = line.Substring (4);
306 dt_string = dt_string.Replace ("Z", "+00:00");
307 current_dt = DateTime.ParseExact (
308 dt_string,
309 fmts,
310 DateTimeFormatInfo.InvariantInfo,
311 DateTimeStyles.AdjustToUniversal);
312 } else {
313 string_builder.Append (line);
314 string_builder.Append ('\n');
318 if (line == null) {
319 reader.Close ();
320 return false;
323 // Bad contact
324 if (string_builder.Length == 0 || current_uid == null)
325 return false;
327 // Mark address with current_uid as seen ('undeleted')
328 deleted_contacts [current_uid] = false;
330 lock (last_modified_table) {
331 if (last_modified_table.Contains (current_uid)) {
332 DateTime old_dt = (DateTime) last_modified_table [current_uid];
333 // Address up-to-date
334 if (current_dt == old_dt)
335 return false;
336 else {
337 Log.Debug ("Updating last_mod_date [{0}] = {1}", current_uid, current_dt);
338 last_modified_table [current_uid] = current_dt;
340 } else {
341 Log.Debug ("Adding last_mod_date [{0}] = {1}", current_uid, current_dt);
342 last_modified_table [current_uid] = current_dt;
346 return true;
350 public Indexable GetNextIndexable ()
352 if (initial_scan)
353 return null;
355 Uri uri = new Uri (String.Format ("kabc:///{0}", current_uid));
356 Indexable indexable = new Indexable (uri);
357 indexable.ParentUri = UriFu.PathToFileUri (kabc_file);
358 indexable.MimeType = ICalParser.KabcMimeType;
359 indexable.HitType = "Contact";
360 indexable.Timestamp = current_dt;
361 // Add uid as a keyword field for convenience
362 indexable.AddProperty (Property.NewUnsearched ("fixme:uid", current_uid));
364 // FIXME: Comment this Debug statement after the backend stabilizes
365 Log.Debug ("Creating {0} from:[{1}]", uri, string_builder.ToString ());
366 StringReader string_reader = new StringReader (string_builder.ToString());
367 indexable.SetTextReader (string_reader);
369 return indexable;