4 // Copyright (C) 2006 Debajyoti Bera <dbera.web@gmail.com>
5 // Copyright (C) 2004 Novell, Inc.
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.
31 using System
.Collections
;
32 using System
.Threading
;
33 using System
.Globalization
;
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 ()
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
));
83 if (Inotify
.Enabled
) {
84 Inotify
.EventType mask
= Inotify
.EventType
.Create
| Inotify
.EventType
.MovedTo
;
85 Inotify
.Subscribe (kabc_dir
, OnInotifyEvent
, mask
);
87 FileSystemWatcher fsw
= new FileSystemWatcher ();
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
))
105 private bool CheckForExistence ()
107 if (!Directory
.Exists (kabc_dir
))
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);
122 while (generator
.HasNextIndexable ())
123 generator
.GetNextIndexable ();
126 /////////////////////////////////////////////////
128 // Modified event using Inotify
129 private void OnInotifyEvent (Inotify
.Watch watch
,
133 Inotify
.EventType type
)
135 if (Path
.Combine (path
, subitem
) != kabc_file
)
141 // Modified/Created event using FSW
142 private void OnChangedEvent (object o
, FileSystemEventArgs args
)
147 /////////////////////////////////////////////////
149 private void Index ()
151 if (ThisScheduler
.ContainsByTag ("KAddressBook")) {
152 Logger
.Log
.Debug ("Not adding task for already running Kabc task");
156 AddressIndexableGenerator generator
= new AddressIndexableGenerator (this, kabc_file
, last_modified_table
, false);
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) {
177 foreach (string uid
in to_delete
) {
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
,
212 Hashtable last_modified_table
,
215 this.queryable
= queryable
;
216 this.kabc_file
= kabc_file
;
217 this.initial_scan
= initial_scan
;
219 CheckContactHeader ();
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;
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;
262 private StringBuilder string_builder
;
264 public bool HasNextIndexable ()
266 if (!is_valid_file
|| reader
== null)
270 while ((line
= reader
.ReadLine ()) != null) {
271 if (line
== "BEGIN:VCARD")
279 queryable
.RemoveDeletedContacts (deleted_contacts
);
286 string[] fmts
= {"yyyy-MM-ddTHH:mm:sszzz"}
;
288 private bool ReadContact ()
291 string_builder
.Length
= 0;
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) {
300 //REV:2006-09-30T17:41:52Z
301 if (line
== "END:VCARD")
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 (
311 DateTimeFormatInfo
.InvariantInfo
,
312 DateTimeStyles
.AdjustToUniversal
);
314 string_builder
.Append (line
);
315 string_builder
.Append ('\n');
325 if (string_builder
.Length
== 0 || current_uid
== null)
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
)
338 //Log.Debug ("Updating last_mod_date [{0}] = {1}", current_uid, current_dt);
339 last_modified_table
[current_uid
] = current_dt
;
342 //Log.Debug ("Adding last_mod_date [{0}] = {1}", current_uid, current_dt);
343 last_modified_table
[current_uid
] = current_dt
;
351 public Indexable
GetNextIndexable ()
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
);