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 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) {
175 foreach (string uid
in to_delete
) {
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
,
207 Hashtable last_modified_table
,
210 this.queryable
= queryable
;
211 this.knotes_file
= knotes_file
;
212 this.initial_scan
= initial_scan
;
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;
246 Logger
.Log
.Debug ("Checking if {0} is a valid KNotes file.", knotes_file
);
247 /** KNotes file notes.ics should start with
249 PRODID:-//K Desktop Environment//NONSGML libkcal 3.5//EN
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;
261 } catch (Exception ex
) {
262 Logger
.Log
.Warn (ex
, "Caught exception parsing knotes file:");
263 is_valid_file
= false;
268 private StringBuilder string_builder
;
269 public bool HasNextIndexable ()
271 if (!is_valid_file
|| reader
== null)
276 while ((line
= reader
.ReadLine ()) != null) {
277 if (line
== "BEGIN:VJOURNAL")
279 else if (line
== "END:VCALENDAR") {
284 if (line
== null || end
) {
287 queryable
.RemoveDeletedNotes (deleted_notes
);
293 string[] fmts
= {"yyyyMMddTHHmmsszzz"}
;
295 public Indexable
GetNextIndexable ()
298 string_builder
.Length
= 0;
300 DateTime dt
= DateTime
.MinValue
;
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")
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 (
317 DateTimeFormatInfo
.InvariantInfo
,
318 DateTimeStyles
.AdjustToUniversal
);
320 string_builder
.Append (line
);
321 string_builder
.Append ('\n');
331 if (string_builder
.Length
== 0 ||
333 dt
== DateTime
.MinValue
)
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
346 //Log.Debug ("Updating last_mod_date [{0}] = {1}", uid, dt);
347 last_modified_table
[uid
] = dt
;
350 //Log.Debug ("Adding last_mod_date [{0}] = {1}", uid, dt);
351 last_modified_table
[uid
] = dt
;
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
);