Don't set Hidden=true when disabling autostart, because gnome-session skips
[beagle.git] / beagled / KNotesQueryable / KNotesQueryable.cs
blobb6007f419c2b140b02b4690b3c5bf1721944ac36
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 ArrayList to_delete = new ArrayList ();
166 lock (last_modified_table) {
167 foreach (string uid in last_modified_table.Keys) {
168 if (! deleted_notes.Contains (uid) ||
169 (bool)deleted_notes [uid] == true) {
170 to_delete.Add (uid);
175 foreach (string uid in to_delete) {
176 RemoveNote (uid);
177 last_modified_table.Remove (uid);
181 private void RemoveNote (string uid)
183 Uri uri = new Uri (String.Format ("knotes:///{0}", uid));
184 Logger.Log.Debug ("Removing note {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 KNotes Feeds
196 internal class NotesIndexableGenerator : IIndexableGenerator {
197 private string knotes_file;
198 private StreamReader reader;
199 private KNotesQueryable queryable;
200 private bool is_valid_file = true;
201 private Hashtable last_modified_table;
202 private Hashtable deleted_notes;
203 private bool initial_scan;
205 public NotesIndexableGenerator (KNotesQueryable queryable,
206 string knotes_file,
207 Hashtable last_modified_table,
208 bool initial_scan)
210 this.queryable = queryable;
211 this.knotes_file = knotes_file;
212 this.initial_scan = initial_scan;
214 CheckNoteHeader ();
215 if (is_valid_file)
216 string_builder = new StringBuilder ();
218 this.last_modified_table = last_modified_table;
219 lock (last_modified_table) {
220 this.deleted_notes = new Hashtable (last_modified_table.Count);
221 foreach (string uid in last_modified_table.Keys)
222 this.deleted_notes [uid] = true;
226 public void PostFlushHook ()
228 //queryable.FileAttributesStore.AttachLastWriteTime (knotes_file, DateTime.UtcNow);
231 public string StatusName {
232 get { return knotes_file; }
235 private bool IsUpToDate (string path)
237 return queryable.FileAttributesStore.IsUpToDate (path);
240 private void CheckNoteHeader () {
241 if ( (! initial_scan) && IsUpToDate (knotes_file)) {
242 is_valid_file = false;
243 return;
245 try {
246 Logger.Log.Debug ("Checking if {0} is a valid KNotes file.", knotes_file);
247 /** KNotes file notes.ics should start with
248 BEGIN:VCALENDAR
249 PRODID:-//K Desktop Environment//NONSGML libkcal 3.5//EN
250 VERSION:2.0
252 // FIXME: Encoding of notes.ics
253 reader = new StreamReader (knotes_file);
254 if (reader.ReadLine () != "BEGIN:VCALENDAR" ||
255 reader.ReadLine () != "PRODID:-//K Desktop Environment//NONSGML libkcal 3.5//EN" ||
256 reader.ReadLine () != "VERSION:2.0") {
257 is_valid_file = false;
258 reader.Close ();
259 return;
261 } catch (Exception ex) {
262 Logger.Log.Warn (ex, "Caught exception parsing knotes file:");
263 is_valid_file = false;
264 reader.Close ();
268 private StringBuilder string_builder;
269 public bool HasNextIndexable ()
271 if (!is_valid_file || reader == null)
272 return false;
274 string line;
275 bool end = false;
276 while ((line = reader.ReadLine ()) != null) {
277 if (line == "BEGIN:VJOURNAL")
278 break;
279 else if (line == "END:VCALENDAR") {
280 end = true;
281 break;
284 if (line == null || end) {
285 reader.Close ();
286 if (! initial_scan)
287 queryable.RemoveDeletedNotes (deleted_notes);
288 return false;
290 return true;
293 string[] fmts = {"yyyyMMddTHHmmsszzz"};
295 public Indexable GetNextIndexable ()
297 string line;
298 string_builder.Length = 0;
300 DateTime dt = DateTime.MinValue;
301 string uid = null;
303 // Keep reading till "END:VJOURNAL"
304 while ((line = reader.ReadLine ()) != null) {
305 //UID:libkcal-1467827482.768
306 //LAST-MODIFIED:20061015T085606Z
307 if (line == "END:VJOURNAL")
308 break;
309 else if (line.StartsWith ("UID:"))
310 uid = line.Substring (4);
311 else if (line.StartsWith ("LAST-MODIFIED:")) {
312 string dt_string = line.Substring (14);
313 dt_string = dt_string.Replace ("Z", "+00:00");
314 dt = DateTime.ParseExact (
315 dt_string,
316 fmts,
317 DateTimeFormatInfo.InvariantInfo,
318 DateTimeStyles.AdjustToUniversal);
319 } else {
320 string_builder.Append (line);
321 string_builder.Append ('\n');
325 if (line == null) {
326 reader.Close ();
327 return null;
330 // Bad note
331 if (string_builder.Length == 0 ||
332 uid == null ||
333 dt == DateTime.MinValue)
334 return null;
336 // Mark note with uid as seen ('undeleted')
337 deleted_notes [uid] = false;
339 lock (last_modified_table) {
340 if (last_modified_table.Contains (uid)) {
341 DateTime old_dt = (DateTime) last_modified_table [uid];
342 // FIXME: Returning null for more than 179 times will cause trouble
343 if (dt == old_dt)
344 return null;
345 else {
346 //Log.Debug ("Updating last_mod_date [{0}] = {1}", uid, dt);
347 last_modified_table [uid] = dt;
349 } else {
350 //Log.Debug ("Adding last_mod_date [{0}] = {1}", uid, dt);
351 last_modified_table [uid] = dt;
355 if (initial_scan)
356 return null;
358 // Open knotes notes as
359 //dcop knotes KNotesIface text <UID>
360 // where the uri is given as knotes://uid
361 Uri uri = new Uri (String.Format ("knotes:///{0}", uid));
362 Indexable indexable = new Indexable (uri);
363 indexable.ParentUri = UriFu.PathToFileUri (knotes_file);
364 indexable.MimeType = ICalParser.KnotesMimeType;
365 indexable.HitType = "Note";
366 indexable.Timestamp = dt;
367 // Add uid as a keyword field for convenience
368 indexable.AddProperty (Property.NewUnsearched ("fixme:uid", uid));
370 // FIXME: Comment this Debug statement after the backend stabilizes
371 //Log.Debug ("Creating {0} from:[{1}]", uri, string_builder.ToString ());
372 StringReader string_reader = new StringReader (string_builder.ToString());
373 indexable.SetTextReader (string_reader);
375 return indexable;