2 // KonqBookmarkQueryable.cs
4 // Copyright (C) 2006 Debajyoti Bera
7 // Permission is hereby granted, free of charge, to any person obtaining a
8 // copy of this software and associated documentation files (the "Software"),
9 // to deal in the Software without restriction, including without limitation
10 // the rights to use, copy, modify, merge, publish, distribute, sublicense,
11 // and/or sell copies of the Software, and to permit persons to whom the
12 // Software is furnished to do so, subject to the following conditions:
14 // The above copyright notice and this permission notice shall be included in
15 // all copies or substantial portions of the Software.
17 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18 // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19 // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
20 // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21 // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
22 // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
23 // DEALINGS IN THE SOFTWARE.
28 using System
.Collections
;
29 using System
.Threading
;
32 using System
.Xml
.Serialization
;
33 using System
.Globalization
;
38 namespace Beagle
.Daemon
.KBookmarkQueryable
{
40 [QueryableFlavor (Name
="KonqBookmark", Domain
=QueryDomain
.Local
, RequireInotify
=false)]
41 public class KonqBookmarkQueryable
: LuceneFileQueryable
{
43 private string konq_dir
;
44 private string bookmark_file
;
45 private Hashtable last_modified_table
;
47 // construct a serializer and keep it handy for indexablegenerator to use
48 private XmlSerializer serializer
= null;
49 public XmlSerializer Serializer
{
51 if (serializer
== null)
52 serializer
= new XmlSerializer (typeof (Bookmark
));
57 // 1: Store URL as text
58 const int MINOR_VERSION
= 1;
60 public KonqBookmarkQueryable () : base ("KonqBookmarkIndex", MINOR_VERSION
)
62 konq_dir
= Path
.Combine (PathFinder
.HomeDir
, ".kde");
63 konq_dir
= Path
.Combine (konq_dir
, "share");
64 konq_dir
= Path
.Combine (konq_dir
, "apps");
65 konq_dir
= Path
.Combine (konq_dir
, "konqueror");
67 bookmark_file
= Path
.Combine (konq_dir
, "bookmarks.xml");
69 last_modified_table
= new Hashtable ();
72 /////////////////////////////////////////////////
74 public override void Start ()
78 ExceptionHandlingThread
.Start (new ThreadStart (StartWorker
));
81 private void StartWorker ()
83 if (!Directory
.Exists (konq_dir
)) {
84 GLib
.Timeout
.Add (60000, new GLib
.TimeoutHandler (CheckForExistence
));
88 if (Inotify
.Enabled
) {
89 Inotify
.EventType mask
= Inotify
.EventType
.CloseWrite
90 | Inotify
.EventType
.MovedTo
;
92 Inotify
.Subscribe (konq_dir
, OnInotifyEvent
, mask
);
94 FileSystemWatcher fsw
= new FileSystemWatcher ();
96 fsw
.Filter
= bookmark_file
;
98 fsw
.Changed
+= new FileSystemEventHandler (OnChanged
);
99 fsw
.Created
+= new FileSystemEventHandler (OnChanged
);
100 fsw
.Renamed
+= new RenamedEventHandler (OnChanged
);
102 fsw
.EnableRaisingEvents
= true;
105 if (File
.Exists (bookmark_file
)) {
106 if (! FileAttributesStore
.IsUpToDate (bookmark_file
))
109 ScanBookmarkInitial ();
113 private bool CheckForExistence ()
115 if (!Directory
.Exists (konq_dir
))
123 // We need to scan the bookmark file nitially to fill up last_modified_table
124 // Otherwise, we might miss deletions that occur before any addition
125 private void ScanBookmarkInitial ()
127 BookmarkIndexableGenerator generator
= new BookmarkIndexableGenerator (this, bookmark_file
, last_modified_table
, true);
130 while (generator
.HasNextIndexable ())
131 generator
.GetNextIndexable ();
134 /////////////////////////////////////////////////
136 // Modified/Created event using Inotify
138 private void OnInotifyEvent (Inotify
.Watch watch
,
142 Inotify
.EventType type
)
144 if (Path
.Combine (path
, subitem
) != bookmark_file
)
150 // Modified/Created event using FSW
152 private void OnChanged (object o
, FileSystemEventArgs args
)
157 /////////////////////////////////////////////////
159 private void Index ()
161 if (ThisScheduler
.ContainsByTag ("KonqBookmark")) {
162 Log
.Debug ("Not adding task for already running KonqBookmark task");
166 BookmarkIndexableGenerator generator
= new BookmarkIndexableGenerator (this, bookmark_file
, last_modified_table
, false);
168 task
= NewAddTask (generator
);
169 task
.Tag
= "KonqBookmark";
170 task
.Priority
= Scheduler
.Priority
.Delayed
;
171 task
.SubPriority
= 0;
172 ThisScheduler
.Add (task
);
175 internal void RemoveDeletedBookmarks (Hashtable deleted_bookmarks
)
177 ArrayList to_delete
= new ArrayList ();
178 lock (last_modified_table
) {
179 foreach (string uid
in last_modified_table
.Keys
) {
180 if (! deleted_bookmarks
.Contains (uid
) ||
181 (bool)deleted_bookmarks
[uid
] == true) {
187 foreach (string uid
in to_delete
) {
188 RemoveBookmark (uid
);
189 last_modified_table
.Remove (uid
);
193 private void RemoveBookmark (string uid
)
195 Uri uri
= new Uri (uid
);
196 Log
.Debug ("Removing contact {0}", uri
);
197 Scheduler
.Task task
= NewRemoveTask (uri
);
198 task
.Priority
= Scheduler
.Priority
.Immediate
;
199 task
.SubPriority
= 0;
200 ThisScheduler
.Add (task
);
206 * Indexable generator for Konqueror Bookmarks
208 public class BookmarkIndexableGenerator
: IIndexableGenerator
{
209 private string bookmark_file
;
210 private KonqBookmarkQueryable queryable
;
211 private XmlTextReader reader
;
212 private bool is_valid_file
= true;
213 private bool initial_scan
= false;
214 private Hashtable last_modified_table
;
215 private Hashtable deleted_bookmarks
;
216 private DateTime file_last_write_time
;
218 private ArrayList folder_stack
; // simulate a stack
219 private string current_folder
= String
.Empty
;
220 private string current_title
= String
.Empty
;
221 private string current_folder_title
= String
.Empty
;
222 private DateTime current_dt
;
224 private string current_bookmark_id
= String
.Empty
;
225 private Bookmark current_bookmark
;
226 private XmlSerializer serializer
;
228 public BookmarkIndexableGenerator (KonqBookmarkQueryable queryable
,
229 string bookmark_file
,
230 Hashtable last_modified_table
,
233 this.queryable
= queryable
;
234 this.bookmark_file
= bookmark_file
;
235 this.initial_scan
= initial_scan
;
237 ReadBookmarkHeader ();
241 this.serializer
= queryable
.Serializer
;
242 this.last_modified_table
= last_modified_table
;
243 this.folder_stack
= new ArrayList ();
245 lock (last_modified_table
) {
246 this.deleted_bookmarks
= new Hashtable (last_modified_table
.Count
);
247 foreach (string bookmark_path
in last_modified_table
.Keys
)
248 this.deleted_bookmarks
[bookmark_path
] = true;
251 // cache the last write time
252 file_last_write_time
= FileSystem
.GetLastWriteTimeUtc (bookmark_file
);
255 public void PostFlushHook ()
259 public string StatusName
{
260 get { return current_bookmark_id; }
263 private bool IsUpToDate (string path
)
265 return queryable
.FileAttributesStore
.IsUpToDate (path
);
268 private void ReadBookmarkHeader () {
269 if ( (! initial_scan
) && IsUpToDate (bookmark_file
)) {
270 is_valid_file
= false;
274 Log
.Debug ("Opening bookmark file: {0}", bookmark_file
);
275 reader
= new XmlTextReader (bookmark_file
);
276 reader
.WhitespaceHandling
= WhitespaceHandling
.None
;
278 is_valid_file
= true;
280 // move to beginning of document
281 reader
.MoveToContent();
282 // move to <xbel> node
283 reader
.ReadStartElement ("xbel");
284 } catch (XmlException ex
) {
285 Logger
.Log
.Warn (ex
, "Caught exception parsing bookmark file:");
286 is_valid_file
= false;
291 public bool HasNextIndexable ()
293 if (!is_valid_file
|| reader
== null)
296 current_bookmark
= null;
297 string bookmark_string
= "";
299 while (! reader
.EOF
) {
300 if (ReadNextBookmark ())
303 } catch (XmlException
) {
304 // probably no more <item>
307 is_valid_file
= false;
310 queryable
.RemoveDeletedBookmarks (deleted_bookmarks
);
314 private bool ReadNextBookmark ()
316 while (reader
.NodeType
== XmlNodeType
.EndElement
) {
317 if (reader
.Name
== "folder") {
318 folder_stack
.RemoveAt (folder_stack
.Count
- 1);
319 ResetCurrentFolder ();
320 //Log.Debug ("Resetting folder to [{0}]", current_folder);
325 while (!reader
.EOF
&& reader
.NodeType
== XmlNodeType
.Element
) {
326 string elementname
= reader
.Name
;
327 if (elementname
== "title") {
328 string title
= reader
.ReadString ();
329 current_folder_title
= title
;
330 folder_stack
.Add (title
);
331 ResetCurrentFolder ();
332 reader
.ReadEndElement ();
334 } else if (elementname
== "bookmark") {
335 string bookmark_string
= reader
.ReadOuterXml ();
336 current_bookmark
= (Bookmark
) serializer
.Deserialize (new StringReader (bookmark_string
));
337 if (SetLastModifiedTable ())
347 private bool SetLastModifiedTable ()
349 current_bookmark_id
= String
.Format ("kbookmark:///{0};title={1}", current_folder
, current_bookmark
.Title
);
351 if (current_bookmark
.Info
!= null &&
352 current_bookmark
.Info
.Metadata
!= null &&
353 current_bookmark
.Info
.Metadata
.TimeLastVisited
!= 0) {
354 DateTime date
= DateTimeUtil
.UnixToDateTimeUtc (0);
355 current_dt
= date
.AddSeconds (current_bookmark
.Info
.Metadata
.TimeLastVisited
);
357 current_dt
= file_last_write_time
;
359 // Mark bookmark seen ('undeleted')
360 deleted_bookmarks
[current_bookmark_id
] = false;
362 lock (last_modified_table
) {
363 if (last_modified_table
.Contains (current_bookmark_id
)) {
364 DateTime old_dt
= (DateTime
) last_modified_table
[current_bookmark_id
];
365 // Address up-to-date
366 if (current_dt
== old_dt
) {
367 //Log.Debug ("Datetime unchanged [{0}] = {1}", current_bookmark_id, current_dt);
370 //Log.Debug ("Updating last_mod_date [{0}] = {1}", current_bookmark_id, current_dt);
371 last_modified_table
[current_bookmark_id
] = current_dt
;
374 //Log.Debug ("Adding last_mod_date [{0}] = {1}", current_bookmark_id, current_dt);
375 last_modified_table
[current_bookmark_id
] = current_dt
;
381 private void ResetCurrentFolder ()
383 // FIXME: Joining using '/' isn't safe since
384 // Konqueror allows '/' in the path name
385 // But we need to create a unique URI from the folder path
387 StringBuilder sb
= new StringBuilder ();
388 foreach (string folder
in folder_stack
) {
392 current_folder
= sb
.ToString ();
395 public Indexable
GetNextIndexable ()
397 if (current_bookmark
!= null)
398 return BookmarkToIndexable ();
403 private Indexable
BookmarkToIndexable ()
405 Indexable indexable
= new Indexable (new Uri (current_bookmark_id
));
406 indexable
.ParentUri
= UriFu
.PathToFileUri (bookmark_file
);
407 indexable
.HitType
= "Bookmark";
408 indexable
.Timestamp
= current_dt
;
409 indexable
.NoContent
= true;
411 indexable
.AddProperty (Property
.New ("dc:title", current_bookmark
.Title
));
412 indexable
.AddProperty (Property
.New ("dc:identifier", current_bookmark
.Href
));
413 indexable
.AddProperty (Property
.NewUnsearched ("fixme:icon", current_bookmark
.Icon
));
414 if (current_bookmark
.Info
!= null &&
415 current_bookmark
.Info
.Metadata
!= null &&
416 current_bookmark
.Info
.Metadata
.NumVisited
!= 0)
417 indexable
.AddProperty (Property
.NewUnsearched ("fixme:visit_count", current_bookmark
.Info
.Metadata
.NumVisited
));
418 foreach (string folder
in folder_stack
)
419 indexable
.AddProperty (Property
.New ("fixme:folder", folder
));
426 // we will deserialize XML fragments, so there wont be any <? xml ... ?>
427 [System
.Xml
.Serialization
.XmlRoot("bookmark", Namespace
="", IsNullable
=false)]
428 [System
.Xml
.Serialization
.XmlType("bookmark", Namespace
="")]
429 public class Bookmark
{
430 [XmlAttribute ("icon")] public string Icon
= "";
431 [XmlAttribute ("href")] public string Href
= "";
432 [XmlElement ("title")] public string Title
;
433 [XmlElement ("info")] public Info Info
;
437 [XmlElement ("metadata")] public Metadata Metadata
;
440 public class Metadata
{
441 [XmlElement ("time_added")] public long TimeAdded
= 0;
442 [XmlElement ("time_visited")] public long TimeLastVisited
= 0;
443 [XmlElement ("visit_count")] public int NumVisited
= 0;