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 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 ()
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
));
80 if (Inotify
.Enabled
) {
81 Inotify
.EventType mask
= Inotify
.EventType
.CloseWrite
82 | Inotify
.EventType
.MovedTo
;
83 Inotify
.Subscribe (kabc_dir
, OnInotifyEvent
, mask
);
85 FileSystemWatcher fsw
= new FileSystemWatcher ();
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
))
104 private bool CheckForExistence ()
106 if (!Directory
.Exists (kabc_dir
))
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);
121 while (generator
.HasNextIndexable ())
122 generator
.GetNextIndexable ();
125 /////////////////////////////////////////////////
127 // Modified event using Inotify
128 private void OnInotifyEvent (Inotify
.Watch watch
,
132 Inotify
.EventType type
)
134 if (Path
.Combine (path
, subitem
) != kabc_file
)
140 // Modified/Created event using FSW
141 private void OnChangedEvent (object o
, FileSystemEventArgs args
)
146 /////////////////////////////////////////////////
148 private void Index ()
150 if (ThisScheduler
.ContainsByTag ("KAddressBook")) {
151 Logger
.Log
.Debug ("Not adding task for already running Kabc task");
155 AddressIndexableGenerator generator
= new AddressIndexableGenerator (this, kabc_file
, last_modified_table
, false);
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) {
176 foreach (string uid
in to_delete
) {
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
,
211 Hashtable last_modified_table
,
214 this.queryable
= queryable
;
215 this.kabc_file
= kabc_file
;
216 this.initial_scan
= initial_scan
;
218 CheckContactHeader ();
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;
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;
261 private StringBuilder string_builder
;
263 public bool HasNextIndexable ()
265 if (!is_valid_file
|| reader
== null)
269 while ((line
= reader
.ReadLine ()) != null) {
270 if (line
== "BEGIN:VCARD")
278 queryable
.RemoveDeletedContacts (deleted_contacts
);
285 string[] fmts
= {"yyyy-MM-ddTHH:mm:sszzz"}
;
287 private bool ReadContact ()
290 string_builder
.Length
= 0;
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) {
299 //REV:2006-09-30T17:41:52Z
300 if (line
== "END:VCARD")
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 (
310 DateTimeFormatInfo
.InvariantInfo
,
311 DateTimeStyles
.AdjustToUniversal
);
313 string_builder
.Append (line
);
314 string_builder
.Append ('\n');
324 if (string_builder
.Length
== 0 || current_uid
== null)
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
)
337 Log
.Debug ("Updating last_mod_date [{0}] = {1}", current_uid
, current_dt
);
338 last_modified_table
[current_uid
] = current_dt
;
341 Log
.Debug ("Adding last_mod_date [{0}] = {1}", current_uid
, current_dt
);
342 last_modified_table
[current_uid
] = current_dt
;
350 public Indexable
GetNextIndexable ()
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
);