Remove some debug spew
[beagle.git] / beagled / KNotesQueryable / KNotesQueryable.cs
blob85f3cfd2a283f485920137b2f1e0a8244bcb4e4c
1 //
2 // KNotesQueryable.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 namespace Beagle.Daemon.KNotesQueryable {
40 [QueryableFlavor (Name="KNotes", Domain=QueryDomain.Local, RequireInotify=false)]
41 public class KNotesQueryable : LuceneFileQueryable {
43 private static Logger log = Logger.Get ("KNotesQueryable");
45 private string knotes_dir;
46 private string knotes_file;
47 private Hashtable last_modified_table;
49 public KNotesQueryable () : base ("KNotesIndex")
51 knotes_dir = Path.Combine (PathFinder.HomeDir, ".kde");
52 knotes_dir = Path.Combine (knotes_dir, "share");
53 knotes_dir = Path.Combine (knotes_dir, "apps");
54 knotes_dir = Path.Combine (knotes_dir, "knotes");
56 knotes_file = Path.Combine (knotes_dir, "notes.ics");
58 last_modified_table = new Hashtable ();
61 /////////////////////////////////////////////////
63 public override void Start ()
65 base.Start ();
67 ExceptionHandlingThread.Start (new ThreadStart (StartWorker));
70 private void StartWorker ()
72 if (!Directory.Exists (knotes_dir)) {
73 GLib.Timeout.Add (60000, new GLib.TimeoutHandler (CheckForExistence));
74 return;
77 if (Inotify.Enabled) {
78 Inotify.EventType mask = Inotify.EventType.CloseWrite
79 | Inotify.EventType.MovedTo;
80 Inotify.Subscribe (knotes_dir, OnInotifyEvent, mask);
81 } else {
82 FileSystemWatcher fsw = new FileSystemWatcher ();
83 fsw.Path = knotes_dir;
84 fsw.Filter = knotes_file;
86 fsw.Changed += new FileSystemEventHandler (OnChangedEvent);
87 fsw.Created += new FileSystemEventHandler (OnChangedEvent);
88 fsw.Renamed += new RenamedEventHandler (OnChangedEvent);
90 fsw.EnableRaisingEvents = true;
93 if (File.Exists (knotes_file)) {
94 if (! FileAttributesStore.IsUpToDate (knotes_file))
95 Index ();
96 else
97 ScanNotesInitial ();
101 private bool CheckForExistence ()
103 if (!Directory.Exists (knotes_dir))
104 return true;
106 this.Start ();
108 return false;
111 // We need to scan the notes initially to fill up last_modified_table
112 // Otherwise, we might miss deletions that occur before any addition
113 private void ScanNotesInitial ()
115 NotesIndexableGenerator generator = new NotesIndexableGenerator (this, knotes_file, last_modified_table, true);
117 // just a dummy scan
118 while (generator.HasNextIndexable ())
119 generator.GetNextIndexable ();
122 /////////////////////////////////////////////////
124 // Modified event using Inotify
125 private void OnInotifyEvent (Inotify.Watch watch,
126 string path,
127 string subitem,
128 string srcpath,
129 Inotify.EventType type)
131 if (Path.Combine (path, subitem) != knotes_file)
132 return;
134 Index ();
137 // Modified/Created event using FSW
138 private void OnChangedEvent (object o, FileSystemEventArgs args)
140 Index ();
143 /////////////////////////////////////////////////
145 private void Index ()
147 if (ThisScheduler.ContainsByTag ("KNotes")) {
148 Logger.Log.Debug ("Not adding task for already running KNotes task");
149 return;
152 // Then add the notes from the notes file
153 NotesIndexableGenerator generator = new NotesIndexableGenerator (this, knotes_file, last_modified_table, false);
154 Scheduler.Task task;
155 task = NewAddTask (generator);
156 task.Tag = "KNotes";
157 // Make sure add task gets scheduled after delete task
158 task.Priority = Scheduler.Priority.Delayed;
159 task.SubPriority = 0;
160 ThisScheduler.Add (task);
163 internal void RemoveDeletedNotes (Hashtable deleted_notes)
165 lock (last_modified_table) {
166 foreach (string uid in last_modified_table.Keys) {
167 if (! deleted_notes.Contains (uid) ||
168 (bool)deleted_notes [uid] == true) {
169 RemoveNote (uid);
175 private void RemoveNote (string uid)
177 Uri uri = new Uri (String.Format ("knotes:///{0}", uid));
178 Logger.Log.Debug ("Removing note {0}", uri);
179 Scheduler.Task task = NewRemoveTask (uri);
180 task.Priority = Scheduler.Priority.Immediate;
181 task.SubPriority = 0;
182 ThisScheduler.Add (task);
188 * Indexable generator for KNotes Feeds
190 internal class NotesIndexableGenerator : IIndexableGenerator {
191 private string knotes_file;
192 private StreamReader reader;
193 private KNotesQueryable queryable;
194 private bool is_valid_file = true;
195 private Hashtable last_modified_table;
196 private Hashtable deleted_notes;
197 private bool initial_scan;
199 public NotesIndexableGenerator (KNotesQueryable queryable,
200 string knotes_file,
201 Hashtable last_modified_table,
202 bool initial_scan)
204 this.queryable = queryable;
205 this.knotes_file = knotes_file;
206 this.initial_scan = initial_scan;
208 CheckNoteHeader ();
209 if (is_valid_file)
210 string_builder = new StringBuilder ();
212 this.last_modified_table = last_modified_table;
213 lock (last_modified_table) {
214 this.deleted_notes = new Hashtable (last_modified_table.Count);
215 foreach (string uid in last_modified_table.Keys)
216 this.deleted_notes [uid] = true;
220 public void PostFlushHook ()
222 //queryable.FileAttributesStore.AttachLastWriteTime (knotes_file, DateTime.UtcNow);
225 public string StatusName {
226 get { return knotes_file; }
229 private bool IsUpToDate (string path)
231 return queryable.FileAttributesStore.IsUpToDate (path);
234 private void CheckNoteHeader () {
235 if ( (! initial_scan) && IsUpToDate (knotes_file)) {
236 is_valid_file = false;
237 return;
239 try {
240 Logger.Log.Debug ("Checking if {0} is a valid KNotes file.", knotes_file);
241 /** KNotes file notes.ics should start with
242 BEGIN:VCALENDAR
243 PRODID:-//K Desktop Environment//NONSGML libkcal 3.5//EN
244 VERSION:2.0
246 // FIXME: Encoding of notes.ics
247 reader = new StreamReader (knotes_file);
248 if (reader.ReadLine () != "BEGIN:VCALENDAR" ||
249 reader.ReadLine () != "PRODID:-//K Desktop Environment//NONSGML libkcal 3.5//EN" ||
250 reader.ReadLine () != "VERSION:2.0") {
251 is_valid_file = false;
252 return;
254 } catch (Exception ex) {
255 Logger.Log.Warn (ex, "Caught exception parsing knotes file:");
256 is_valid_file = false;
257 reader.Close ();
261 private StringBuilder string_builder;
262 public bool HasNextIndexable ()
264 if (!is_valid_file || reader == null)
265 return false;
267 string line;
268 bool end = false;
269 while ((line = reader.ReadLine ()) != null) {
270 if (line == "BEGIN:VJOURNAL")
271 break;
272 else if (line == "END:VCALENDAR") {
273 end = true;
274 break;
277 if (line == null || end) {
278 reader.Close ();
279 if (! initial_scan)
280 queryable.RemoveDeletedNotes (deleted_notes);
281 return false;
283 return true;
286 string[] fmts = {"yyyyMMddTHHmmsszzz"};
288 public Indexable GetNextIndexable ()
290 string line;
291 string_builder.Length = 0;
293 DateTime dt = DateTime.MinValue;
294 string uid = null;
296 // Keep reading till "END:VJOURNAL"
297 while ((line = reader.ReadLine ()) != null) {
298 //UID:libkcal-1467827482.768
299 //LAST-MODIFIED:20061015T085606Z
300 if (line == "END:VJOURNAL")
301 break;
302 else if (line.StartsWith ("UID:"))
303 uid = line.Substring (4);
304 else if (line.StartsWith ("LAST-MODIFIED:")) {
305 string dt_string = line.Substring (14);
306 dt_string = dt_string.Replace ("Z", "+00:00");
307 dt = DateTime.ParseExact (
308 dt_string,
309 fmts,
310 DateTimeFormatInfo.InvariantInfo,
311 DateTimeStyles.AdjustToUniversal);
312 } else {
313 string_builder.Append (line);
314 string_builder.Append ('\n');
318 if (line == null) {
319 reader.Close ();
320 return null;
323 // Bad note
324 if (string_builder.Length == 0 ||
325 uid == null ||
326 dt == DateTime.MinValue)
327 return null;
329 // Mark note with uid as seen ('undeleted')
330 deleted_notes [uid] = false;
332 lock (last_modified_table) {
333 if (last_modified_table.Contains (uid)) {
334 DateTime old_dt = (DateTime) last_modified_table [uid];
335 // FIXME: Returning null for more than 179 times will cause trouble
336 if (dt == old_dt)
337 return null;
338 else {
339 Log.Debug ("Updating last_mod_date [{0}] = {1}", uid, dt);
340 last_modified_table [uid] = dt;
342 } else {
343 Log.Debug ("Adding last_mod_date [{0}] = {1}", uid, dt);
344 last_modified_table [uid] = dt;
348 if (initial_scan)
349 return null;
351 // Open knotes notes as
352 //dcop knotes KNotesIface text <UID>
353 // where the uri is given as knotes://uid
354 Uri uri = new Uri (String.Format ("knotes:///{0}", uid));
355 Indexable indexable = new Indexable (uri);
356 indexable.ParentUri = UriFu.PathToFileUri (knotes_file);
357 indexable.MimeType = ICalParser.KnotesMimeType;
358 indexable.HitType = "Note";
359 indexable.Timestamp = dt;
360 // Add uid as a keyword field for convenience
361 indexable.AddProperty (Property.NewUnsearched ("fixme:uid", uid));
363 // FIXME: Comment this Debug statement after the backend stabilizes
364 Log.Debug ("Creating {0} from:[{1}]", uri, string_builder.ToString ());
365 StringReader string_reader = new StringReader (string_builder.ToString());
366 indexable.SetTextReader (string_reader);
368 return indexable;