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 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 ()
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
));
77 if (Inotify
.Enabled
) {
78 Inotify
.EventType mask
= Inotify
.EventType
.CloseWrite
79 | Inotify
.EventType
.MovedTo
;
80 Inotify
.Subscribe (knotes_dir
, OnInotifyEvent
, mask
);
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
))
101 private bool CheckForExistence ()
103 if (!Directory
.Exists (knotes_dir
))
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);
118 while (generator
.HasNextIndexable ())
119 generator
.GetNextIndexable ();
122 /////////////////////////////////////////////////
124 // Modified event using Inotify
125 private void OnInotifyEvent (Inotify
.Watch watch
,
129 Inotify
.EventType type
)
131 if (Path
.Combine (path
, subitem
) != knotes_file
)
137 // Modified/Created event using FSW
138 private void OnChangedEvent (object o
, FileSystemEventArgs args
)
143 /////////////////////////////////////////////////
145 private void Index ()
147 if (ThisScheduler
.ContainsByTag ("KNotes")) {
148 Logger
.Log
.Debug ("Not adding task for already running KNotes task");
152 // Then add the notes from the notes file
153 NotesIndexableGenerator generator
= new NotesIndexableGenerator (this, knotes_file
, last_modified_table
, false);
155 task
= NewAddTask (generator
);
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) {
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
,
201 Hashtable last_modified_table
,
204 this.queryable
= queryable
;
205 this.knotes_file
= knotes_file
;
206 this.initial_scan
= initial_scan
;
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;
240 Logger
.Log
.Debug ("Checking if {0} is a valid KNotes file.", knotes_file
);
241 /** KNotes file notes.ics should start with
243 PRODID:-//K Desktop Environment//NONSGML libkcal 3.5//EN
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;
254 } catch (Exception ex
) {
255 Logger
.Log
.Warn (ex
, "Caught exception parsing knotes file:");
256 is_valid_file
= false;
261 private StringBuilder string_builder
;
262 public bool HasNextIndexable ()
264 if (!is_valid_file
|| reader
== null)
269 while ((line
= reader
.ReadLine ()) != null) {
270 if (line
== "BEGIN:VJOURNAL")
272 else if (line
== "END:VCALENDAR") {
277 if (line
== null || end
) {
280 queryable
.RemoveDeletedNotes (deleted_notes
);
286 string[] fmts
= {"yyyyMMddTHHmmsszzz"}
;
288 public Indexable
GetNextIndexable ()
291 string_builder
.Length
= 0;
293 DateTime dt
= DateTime
.MinValue
;
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")
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 (
310 DateTimeFormatInfo
.InvariantInfo
,
311 DateTimeStyles
.AdjustToUniversal
);
313 string_builder
.Append (line
);
314 string_builder
.Append ('\n');
324 if (string_builder
.Length
== 0 ||
326 dt
== DateTime
.MinValue
)
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
339 Log
.Debug ("Updating last_mod_date [{0}] = {1}", uid
, dt
);
340 last_modified_table
[uid
] = dt
;
343 Log
.Debug ("Adding last_mod_date [{0}] = {1}", uid
, dt
);
344 last_modified_table
[uid
] = dt
;
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
);