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 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) {
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
,
205 Hashtable last_modified_table
,
208 this.queryable
= queryable
;
209 this.kabc_file
= kabc_file
;
210 this.initial_scan
= initial_scan
;
212 CheckContactHeader ();
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;
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;
255 private StringBuilder string_builder
;
257 public bool HasNextIndexable ()
259 if (!is_valid_file
|| reader
== null)
263 while ((line
= reader
.ReadLine ()) != null) {
264 if (line
== "BEGIN:VCARD")
272 queryable
.RemoveDeletedContacts (deleted_contacts
);
279 string[] fmts
= {"yyyy-MM-ddTHH:mm:sszzz"}
;
281 private bool ReadContact ()
284 string_builder
.Length
= 0;
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) {
293 //REV:2006-09-30T17:41:52Z
294 if (line
== "END:VCARD")
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 (
304 DateTimeFormatInfo
.InvariantInfo
,
305 DateTimeStyles
.AdjustToUniversal
);
307 string_builder
.Append (line
);
308 string_builder
.Append ('\n');
318 if (string_builder
.Length
== 0 || current_uid
== null)
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
)
331 Log
.Debug ("Updating last_mod_date [{0}] = {1}", current_uid
, current_dt
);
332 last_modified_table
[current_uid
] = current_dt
;
335 Log
.Debug ("Adding last_mod_date [{0}] = {1}", current_uid
, current_dt
);
336 last_modified_table
[current_uid
] = current_dt
;
344 public Indexable
GetNextIndexable ()
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
);