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 string kabc_dir
;
47 private string kabc_file
;
48 private Hashtable last_modified_table
;
50 // 1: Update KAddressbook filter.
51 const int MINOR_VERSION
= 1;
53 public KabcQueryable () : base ("KAddressBookIndex", MINOR_VERSION
)
55 kabc_dir
= Path
.Combine (PathFinder
.HomeDir
, ".kde");
56 kabc_dir
= Path
.Combine (kabc_dir
, "share");
57 kabc_dir
= Path
.Combine (kabc_dir
, "apps");
58 kabc_dir
= Path
.Combine (kabc_dir
, "kabc");
60 kabc_file
= Path
.Combine (kabc_dir
, "std.vcf");
62 last_modified_table
= new Hashtable ();
65 /////////////////////////////////////////////////
67 public override void Start ()
71 ExceptionHandlingThread
.Start (new ThreadStart (StartWorker
));
74 private void StartWorker ()
76 if (!Directory
.Exists (kabc_dir
)) {
77 GLib
.Timeout
.Add (60000, new GLib
.TimeoutHandler (CheckForExistence
));
81 if (Inotify
.Enabled
) {
82 Inotify
.EventType mask
= Inotify
.EventType
.Create
| Inotify
.EventType
.MovedTo
;
83 Inotify
.Subscribe (kabc_dir
, OnInotifyEvent
, mask
);
85 FileSystemWatcher fsw
= new FileSystemWatcher ();
87 fsw
.Filter
= kabc_file
;
89 fsw
.Created
+= new FileSystemEventHandler (OnChangedEvent
);
90 fsw
.Renamed
+= new RenamedEventHandler (OnChangedEvent
);
92 fsw
.EnableRaisingEvents
= true;
95 if (File
.Exists (kabc_file
)) {
96 if (! FileAttributesStore
.IsUpToDate (kabc_file
))
103 private bool CheckForExistence ()
105 if (!Directory
.Exists (kabc_dir
))
113 // We need to scan the addresses nitially to fill up last_modified_table
114 // Otherwise, we might miss deletions that occur before any addition
115 private void ScanAddrInitial ()
117 AddressIndexableGenerator generator
= new AddressIndexableGenerator (this, kabc_file
, last_modified_table
, true);
120 while (generator
.HasNextIndexable ())
121 generator
.GetNextIndexable ();
124 /////////////////////////////////////////////////
126 // Modified event using Inotify
127 private void OnInotifyEvent (Inotify
.Watch watch
,
131 Inotify
.EventType type
)
133 if (Path
.Combine (path
, subitem
) != kabc_file
)
139 // Modified/Created event using FSW
140 private void OnChangedEvent (object o
, FileSystemEventArgs args
)
145 /////////////////////////////////////////////////
147 private void Index ()
149 if (ThisScheduler
.ContainsByTag ("KAddressBook")) {
150 Logger
.Log
.Debug ("Not adding task for already running Kabc task");
154 AddressIndexableGenerator generator
= new AddressIndexableGenerator (this, kabc_file
, last_modified_table
, false);
156 task
= NewAddTask (generator
);
157 task
.Tag
= "KAddressBook";
158 task
.Priority
= Scheduler
.Priority
.Delayed
;
159 task
.SubPriority
= 0;
160 ThisScheduler
.Add (task
);
163 internal void RemoveDeletedContacts (Hashtable deleted_contacts
)
165 ArrayList to_delete
= new ArrayList ();
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) {
175 foreach (string uid
in to_delete
) {
177 last_modified_table
.Remove (uid
);
181 private void RemoveContact (string uid
)
183 Uri uri
= new Uri (String
.Format ("kabc:///{0}", uid
));
184 Logger
.Log
.Debug ("Removing contact {0}", uri
);
185 Scheduler
.Task task
= NewRemoveTask (uri
);
186 task
.Priority
= Scheduler
.Priority
.Immediate
;
187 task
.SubPriority
= 0;
188 ThisScheduler
.Add (task
);
194 * Indexable generator for Kabc Feeds
196 internal class AddressIndexableGenerator
: IIndexableGenerator
{
197 private string kabc_file
;
198 private StreamReader reader
;
199 private KabcQueryable queryable
;
200 private bool is_valid_file
= true;
201 private Hashtable last_modified_table
;
202 private Hashtable deleted_contacts
;
203 private bool initial_scan
;
204 private DateTime file_last_write_time
;
205 private string current_uid
= String
.Empty
;
206 private DateTime current_dt
;
208 public AddressIndexableGenerator (KabcQueryable queryable
,
210 Hashtable last_modified_table
,
213 this.queryable
= queryable
;
214 this.kabc_file
= kabc_file
;
215 this.initial_scan
= initial_scan
;
217 CheckContactHeader ();
219 string_builder
= new StringBuilder ();
221 this.last_modified_table
= last_modified_table
;
222 lock (last_modified_table
) {
223 this.deleted_contacts
= new Hashtable (last_modified_table
.Count
);
224 foreach (string uid
in last_modified_table
.Keys
)
225 this.deleted_contacts
[uid
] = true;
228 // cache the last write time
229 file_last_write_time
= FileSystem
.GetLastWriteTimeUtc (kabc_file
);
232 public void PostFlushHook ()
234 //queryable.FileAttributesStore.AttachLastWriteTime (kabc_file, DateTime.UtcNow);
237 public string StatusName
{
238 get { return current_uid; }
241 private bool IsUpToDate (string path
)
243 return queryable
.FileAttributesStore
.IsUpToDate (path
);
246 private void CheckContactHeader () {
247 if ( (! initial_scan
) && IsUpToDate (kabc_file
)) {
248 is_valid_file
= false;
252 Logger
.Log
.Debug ("Checking if {0} is a valid Kabc file.", kabc_file
);
253 reader
= new StreamReader (kabc_file
);
254 } catch (Exception ex
) {
255 is_valid_file
= false;
260 private StringBuilder string_builder
;
262 public bool HasNextIndexable ()
264 if (!is_valid_file
|| reader
== null)
268 while ((line
= reader
.ReadLine ()) != null) {
269 if (line
== "BEGIN:VCARD")
276 queryable
.RemoveDeletedContacts (deleted_contacts
);
281 string[] fmts
= {"yyyy-MM-ddTHH:mm:sszzz"}
;
283 private bool ReadContact ()
286 string_builder
.Length
= 0;
289 // date is set to REV:date if specified, o/w file write time
290 current_dt
= file_last_write_time
;
292 // Keep reading till "END:VCARD"
293 while ((line
= reader
.ReadLine ()) != null) {
295 //REV:2006-09-30T17:41:52Z
296 if (line
== "END:VCARD")
298 else if (line
.StartsWith ("UID:"))
299 current_uid
= line
.Substring (4);
300 else if (line
.StartsWith ("REV:")) {
301 string dt_string
= line
.Substring (4);
302 dt_string
= dt_string
.Replace ("Z", "+00:00");
303 current_dt
= DateTime
.ParseExact (
306 DateTimeFormatInfo
.InvariantInfo
,
307 DateTimeStyles
.AdjustToUniversal
);
309 string_builder
.Append (line
);
310 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
);