Fixed #374055:Only the first "tag" is detected in digikam.
[beagle.git] / beagled / KAddressBookQueryable / KabcQueryable.cs
blobca8b36db1c4babc6e845dbecc6286512576de593
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 // 1: Update KAddressbook filter.
53 const int MINOR_VERSION = 1;
55 public KabcQueryable () : base ("KAddressBookIndex", MINOR_VERSION)
57 kabc_dir = Path.Combine (PathFinder.HomeDir, ".kde");
58 kabc_dir = Path.Combine (kabc_dir, "share");
59 kabc_dir = Path.Combine (kabc_dir, "apps");
60 kabc_dir = Path.Combine (kabc_dir, "kabc");
62 kabc_file = Path.Combine (kabc_dir, "std.vcf");
64 last_modified_table = new Hashtable ();
67 /////////////////////////////////////////////////
69 public override void Start ()
71 base.Start ();
73 ExceptionHandlingThread.Start (new ThreadStart (StartWorker));
76 private void StartWorker ()
78 if (!Directory.Exists (kabc_dir)) {
79 GLib.Timeout.Add (60000, new GLib.TimeoutHandler (CheckForExistence));
80 return;
83 if (Inotify.Enabled) {
84 Inotify.EventType mask = Inotify.EventType.Create | Inotify.EventType.MovedTo;
85 Inotify.Subscribe (kabc_dir, OnInotifyEvent, mask);
86 } else {
87 FileSystemWatcher fsw = new FileSystemWatcher ();
88 fsw.Path = kabc_dir;
89 fsw.Filter = kabc_file;
91 fsw.Created += new FileSystemEventHandler (OnChangedEvent);
92 fsw.Renamed += new RenamedEventHandler (OnChangedEvent);
94 fsw.EnableRaisingEvents = true;
97 if (File.Exists (kabc_file)) {
98 if (! FileAttributesStore.IsUpToDate (kabc_file))
99 Index ();
100 else
101 ScanAddrInitial ();
105 private bool CheckForExistence ()
107 if (!Directory.Exists (kabc_dir))
108 return true;
110 this.Start ();
112 return false;
115 // We need to scan the addresses nitially to fill up last_modified_table
116 // Otherwise, we might miss deletions that occur before any addition
117 private void ScanAddrInitial ()
119 AddressIndexableGenerator generator = new AddressIndexableGenerator (this, kabc_file, last_modified_table, true);
121 // just a dummy scan
122 while (generator.HasNextIndexable ())
123 generator.GetNextIndexable ();
126 /////////////////////////////////////////////////
128 // Modified event using Inotify
129 private void OnInotifyEvent (Inotify.Watch watch,
130 string path,
131 string subitem,
132 string srcpath,
133 Inotify.EventType type)
135 if (Path.Combine (path, subitem) != kabc_file)
136 return;
138 Index ();
141 // Modified/Created event using FSW
142 private void OnChangedEvent (object o, FileSystemEventArgs args)
144 Index ();
147 /////////////////////////////////////////////////
149 private void Index ()
151 if (ThisScheduler.ContainsByTag ("KAddressBook")) {
152 Logger.Log.Debug ("Not adding task for already running Kabc task");
153 return;
156 AddressIndexableGenerator generator = new AddressIndexableGenerator (this, kabc_file, last_modified_table, false);
157 Scheduler.Task task;
158 task = NewAddTask (generator);
159 task.Tag = "KAddressBook";
160 task.Priority = Scheduler.Priority.Delayed;
161 task.SubPriority = 0;
162 ThisScheduler.Add (task);
165 internal void RemoveDeletedContacts (Hashtable deleted_contacts)
167 ArrayList to_delete = new ArrayList ();
168 lock (last_modified_table) {
169 foreach (string uid in last_modified_table.Keys) {
170 if (! deleted_contacts.Contains (uid) ||
171 (bool)deleted_contacts [uid] == true) {
172 to_delete.Add (uid);
177 foreach (string uid in to_delete) {
178 RemoveContact (uid);
179 last_modified_table.Remove (uid);
183 private void RemoveContact (string uid)
185 Uri uri = new Uri (String.Format ("kabc:///{0}", uid));
186 Logger.Log.Debug ("Removing contact {0}", uri);
187 Scheduler.Task task = NewRemoveTask (uri);
188 task.Priority = Scheduler.Priority.Immediate;
189 task.SubPriority = 0;
190 ThisScheduler.Add (task);
196 * Indexable generator for Kabc Feeds
198 internal class AddressIndexableGenerator : IIndexableGenerator {
199 private string kabc_file;
200 private StreamReader reader;
201 private KabcQueryable queryable;
202 private bool is_valid_file = true;
203 private Hashtable last_modified_table;
204 private Hashtable deleted_contacts;
205 private bool initial_scan;
206 private DateTime file_last_write_time;
207 private string current_uid = String.Empty;
208 private DateTime current_dt;
210 public AddressIndexableGenerator (KabcQueryable queryable,
211 string kabc_file,
212 Hashtable last_modified_table,
213 bool initial_scan)
215 this.queryable = queryable;
216 this.kabc_file = kabc_file;
217 this.initial_scan = initial_scan;
219 CheckContactHeader ();
220 if (is_valid_file)
221 string_builder = new StringBuilder ();
223 this.last_modified_table = last_modified_table;
224 lock (last_modified_table) {
225 this.deleted_contacts = new Hashtable (last_modified_table.Count);
226 foreach (string uid in last_modified_table.Keys)
227 this.deleted_contacts [uid] = true;
230 // cache the last write time
231 file_last_write_time = FileSystem.GetLastWriteTimeUtc (kabc_file);
234 public void PostFlushHook ()
236 //queryable.FileAttributesStore.AttachLastWriteTime (kabc_file, DateTime.UtcNow);
239 public string StatusName {
240 get { return current_uid; }
243 private bool IsUpToDate (string path)
245 return queryable.FileAttributesStore.IsUpToDate (path);
248 private void CheckContactHeader () {
249 if ( (! initial_scan) && IsUpToDate (kabc_file)) {
250 is_valid_file = false;
251 return;
253 try {
254 Logger.Log.Debug ("Checking if {0} is a valid Kabc file.", kabc_file);
255 reader = new StreamReader (kabc_file);
256 } catch (Exception ex) {
257 is_valid_file = false;
258 reader.Close ();
262 private StringBuilder string_builder;
264 public bool HasNextIndexable ()
266 if (!is_valid_file || reader == null)
267 return false;
269 string line;
270 while ((line = reader.ReadLine ()) != null) {
271 if (line == "BEGIN:VCARD")
272 if (ReadContact ())
273 return true;
276 if (line == null) {
277 reader.Close ();
278 if (! initial_scan)
279 queryable.RemoveDeletedContacts (deleted_contacts);
280 return false;
283 return false;
286 string[] fmts = {"yyyy-MM-ddTHH:mm:sszzz"};
288 private bool ReadContact ()
290 string line;
291 string_builder.Length = 0;
293 current_uid = null;
294 // date is set to REV:date if specified, o/w file write time
295 current_dt = file_last_write_time;
297 // Keep reading till "END:VCARD"
298 while ((line = reader.ReadLine ()) != null) {
299 //UID:4QBCOYKj4c
300 //REV:2006-09-30T17:41:52Z
301 if (line == "END:VCARD")
302 break;
303 else if (line.StartsWith ("UID:"))
304 current_uid = line.Substring (4);
305 else if (line.StartsWith ("REV:")) {
306 string dt_string = line.Substring (4);
307 dt_string = dt_string.Replace ("Z", "+00:00");
308 current_dt = DateTime.ParseExact (
309 dt_string,
310 fmts,
311 DateTimeFormatInfo.InvariantInfo,
312 DateTimeStyles.AdjustToUniversal);
313 } else {
314 string_builder.Append (line);
315 string_builder.Append ('\n');
319 if (line == null) {
320 reader.Close ();
321 return false;
324 // Bad contact
325 if (string_builder.Length == 0 || current_uid == null)
326 return false;
328 // Mark address with current_uid as seen ('undeleted')
329 deleted_contacts [current_uid] = false;
331 lock (last_modified_table) {
332 if (last_modified_table.Contains (current_uid)) {
333 DateTime old_dt = (DateTime) last_modified_table [current_uid];
334 // Address up-to-date
335 if (current_dt == old_dt)
336 return false;
337 else {
338 //Log.Debug ("Updating last_mod_date [{0}] = {1}", current_uid, current_dt);
339 last_modified_table [current_uid] = current_dt;
341 } else {
342 //Log.Debug ("Adding last_mod_date [{0}] = {1}", current_uid, current_dt);
343 last_modified_table [current_uid] = current_dt;
347 return true;
351 public Indexable GetNextIndexable ()
353 if (initial_scan)
354 return null;
356 Uri uri = new Uri (String.Format ("kabc:///{0}", current_uid));
357 Indexable indexable = new Indexable (uri);
358 indexable.ParentUri = UriFu.PathToFileUri (kabc_file);
359 indexable.MimeType = ICalParser.KabcMimeType;
360 indexable.HitType = "Contact";
361 indexable.Timestamp = current_dt;
362 // Add uid as a keyword field for convenience
363 indexable.AddProperty (Property.NewUnsearched ("fixme:uid", current_uid));
365 // FIXME: Comment this Debug statement after the backend stabilizes
366 //Log.Debug ("Creating {0} from:[{1}]", uri, string_builder.ToString ());
367 StringReader string_reader = new StringReader (string_builder.ToString());
368 indexable.SetTextReader (string_reader);
370 return indexable;