4 // Copyright (C) 2005 Carl-Emil Lagerstedt
5 // Copyright (C) 2005 Novell, Inc.
8 // Permission is hereby granted, free of charge, to any person obtaining a
9 // copy of this software and associated documentation files (the "Software"),
10 // to deal in the Software without restriction, including without limitation
11 // the rights to use, copy, modify, merge, publish, distribute, sublicense,
12 // and/or sell copies of the Software, and to permit persons to whom the
13 // Software is furnished to do so, subject to the following conditions:
15 // The above copyright notice and this permission notice shall be included in
16 // all copies or substantial portions of the Software.
18 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19 // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
20 // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
21 // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
22 // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
23 // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
24 // DEALINGS IN THE SOFTWARE.
29 using System
.Collections
;
30 using System
.Threading
;
33 using System
.Xml
.Serialization
;
38 namespace Beagle
.Daemon
.LifereaQueryable
{
40 [QueryableFlavor (Name
="Liferea", Domain
=QueryDomain
.Local
, RequireInotify
=false)]
41 public class LifereaQueryable
: LuceneFileQueryable
, IIndexableGenerator
{
43 private static Logger log
= Logger
.Get ("LifereaQueryable");
47 public LifereaQueryable () : base ("LifereaIndex")
49 liferea_dir
= Path
.Combine (PathFinder
.HomeDir
, ".liferea");
50 liferea_dir
= Path
.Combine (liferea_dir
, "cache");
51 liferea_dir
= Path
.Combine (liferea_dir
, "feeds");
54 /////////////////////////////////////////////////
56 public override void Start ()
60 ExceptionHandlingThread
.Start (new ThreadStart (StartWorker
));
63 private void StartWorker ()
65 if (!Directory
.Exists (liferea_dir
)) {
66 GLib
.Timeout
.Add (60000, new GLib
.TimeoutHandler (CheckForExistence
));
70 if (Inotify
.Enabled
) {
71 Inotify
.EventType mask
= Inotify
.EventType
.CloseWrite
;
73 Inotify
.Subscribe (liferea_dir
, OnInotifyEvent
, mask
);
75 FileSystemWatcher fsw
= new FileSystemWatcher ();
76 fsw
.Path
= liferea_dir
;
78 fsw
.Changed
+= new FileSystemEventHandler (OnChanged
);
79 fsw
.Created
+= new FileSystemEventHandler (OnChanged
);
81 fsw
.EnableRaisingEvents
= true;
84 log
.Info ("Scanning Liferea feeds...");
86 Stopwatch stopwatch
= new Stopwatch ();
89 DirectoryInfo dir
= new DirectoryInfo (liferea_dir
);
90 this.files_to_parse
= dir
.GetFiles ();
92 Scheduler
.Task task
= NewAddTask (this);
94 ThisScheduler
.Add (task
);
97 log
.Info ("{0} files will be parsed (scanned in {1})", this.files_to_parse
.Count
, stopwatch
);
100 private bool CheckForExistence ()
102 if (!Directory
.Exists (liferea_dir
))
110 /////////////////////////////////////////////////
112 // Modified/Created event using Inotify
114 private void OnInotifyEvent (Inotify
.Watch watch
,
118 Inotify
.EventType type
)
123 IndexSingleFeed (Path
.Combine (path
, subitem
), Scheduler
.Priority
.Immediate
);
126 // Modified/Created event using FSW
128 private void OnChanged (object o
, FileSystemEventArgs args
)
130 IndexSingleFeed (args
.FullPath
, Scheduler
.Priority
.Immediate
);
133 /////////////////////////////////////////////////
135 private Indexable
FeedItemToIndexable (Feed feed
, Item item
, FileInfo file
)
137 Indexable indexable
= new Indexable (new Uri (String
.Format ("{0};item={1}", feed
.Source
, item
.Source
)));
138 indexable
.ParentUri
= UriFu
.PathToFileUri (file
.FullName
);
139 indexable
.MimeType
= "text/html";
140 indexable
.Type
= "FeedItem";
142 DateTime date
= new DateTime (1970, 1, 1);
143 date
= date
.AddSeconds (item
.Timestamp
);
144 date
= TimeZone
.CurrentTimeZone
.ToLocalTime (date
);
146 indexable
.Timestamp
= date
;
148 indexable
.AddProperty (Property
.New ("dc:title", item
.Title
));
149 indexable
.AddProperty (Property
.New ("fixme:author", item
.Attribs
.Author
));
150 indexable
.AddProperty (Property
.NewDate ("fixme:published", date
));
151 indexable
.AddProperty (Property
.NewKeyword ("fixme:itemuri", item
.Source
));
152 indexable
.AddProperty (Property
.NewKeyword ("fixme:webloguri", feed
.Source
));
154 StringReader reader
= new StringReader (item
.Description
);
155 indexable
.SetTextReader (reader
);
160 // Parse and index a single feed
162 private int IndexSingleFeed (string filename
, Scheduler
.Priority priority
)
164 FileInfo file
= new FileInfo(filename
);
169 if (IsUpToDate (file
.FullName
))
172 feed
= Feed
.LoadFromFile (file
.FullName
);
174 if (feed
== null || feed
.Items
== null)
177 foreach (Item item
in feed
.Items
) {
180 Indexable indexable
= FeedItemToIndexable (feed
, item
, file
);
182 Scheduler
.Task task
= NewAddTask (indexable
);
183 task
.Priority
= priority
;
184 task
.SubPriority
= 0;
185 ThisScheduler
.Add (task
);
191 ////////////////////////////////////////////////
193 // IIndexableGenerator implementation
195 private ICollection files_to_parse
;
196 private IEnumerator file_enumerator
= null;
197 private IEnumerator item_enumerator
= null;
198 private Feed current_feed
;
200 public Indexable
GetNextIndexable ()
202 Item item
= (Item
) this.item_enumerator
.Current
;
203 FileInfo file
= (FileInfo
) this.file_enumerator
.Current
;
204 return FeedItemToIndexable (this.current_feed
, item
, file
);
207 public bool HasNextIndexable ()
209 if (this.files_to_parse
.Count
== 0)
212 while (this.item_enumerator
== null || !this.item_enumerator
.MoveNext ()) {
213 if (this.file_enumerator
== null)
214 this.file_enumerator
= this.files_to_parse
.GetEnumerator ();
217 if (!this.file_enumerator
.MoveNext ())
220 FileInfo file
= (FileInfo
) this.file_enumerator
.Current
;
222 if (IsUpToDate (file
.FullName
))
225 Feed feed
= Feed
.LoadFromFile (file
.FullName
);
227 if (feed
== null || feed
.Items
== null)
230 this.current_feed
= feed
;
232 } while (this.current_feed
== null);
234 this.item_enumerator
= this.current_feed
.Items
.GetEnumerator ();
240 public string StatusName
{
246 ////////////////////////////////////////////////
248 // De-serialization classes
249 // FIXME: Change to standard stream parsing for performance?
252 [XmlElement ("title")] public string Title
= "";
253 [XmlElement ("description")] public string Description
="";
254 [XmlElement ("source")] public string Source
="";
255 [XmlElement ("attributes")] public Attributes Attribs
;
256 [XmlElement ("time")] public ulong Timestamp
;
259 public class Attributes
{
260 [XmlAttribute ("author")] public string Author
= "";
264 [XmlElement ("feedTitle")] public string Title
="";
265 [XmlElement ("feedSource")] public string Source
="";
266 [XmlElement ("feedDescription")] public string Description
="";
268 [XmlElement ("feedStatus")] public int Status
;
269 [XmlElement ("feedUpdateInterval")] public int UpdateInterval
;
270 [XmlElement ("feedDiscontinued")] public string Discontinued
="";
271 [XmlElement ("feedLastModified")] public string LastModified
="";
273 [XmlElement ("item", typeof (Item
))]
274 public ArrayList Items
{
275 get { return mItems; }
276 set { mItems = value; }
279 private ArrayList mItems
= new ArrayList ();
281 public static Feed
LoadFromFile (string filename
) {
283 XmlRootAttribute xRoot
= new XmlRootAttribute();
284 xRoot
.ElementName
= "feed";
286 XmlSerializer serializer
= new XmlSerializer (typeof (Feed
), xRoot
);
287 Stream stream
= new FileStream (filename
,
291 XmlTextReader reader
= new XmlTextReader (stream
);
293 if (!serializer
.CanDeserialize(reader
) )
294 Console
.WriteLine ("Muopp");
295 f
= (Feed
) serializer
.Deserialize (reader
);