Click here to Skip to main content
15,881,866 members
Articles / Web Development / ASP.NET

RSS 2.0 Framework

Rate me:
Please Sign up or sign in to vote.
4.92/5 (67 votes)
19 Jan 2013LGPL37 min read 506.8K   15.2K   361  
RSS 2.0 framework implements the RSS 2.0 specification in strongly typed classes. The framework enables you to create and consume valid RSS 2.0 feeds in your code in just a few minutes.
// Copyright � 2009 by Christoph Richner. All rights are reserved.
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation; either version 2 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
//
// website http://www.raccoom.net, email support@raccoom.net, msn chrisdarebell@msn.com

using System;
using System.Xml;
using System.Xml.Serialization;
using System.Reflection;
using System.Net;

namespace Raccoom.Xml
{	
	/// <summary>
	/// Rss Cache Manager handles the following things
	/// <list type="bullet">
	/// <item>Rss xml cache</item>
	/// <item>Opml based favorites</item>
	/// <item>Opml based favorite history</item>
	/// </list>
	/// </summary>
	[System.Runtime.InteropServices.ComVisible(true), System.Runtime.InteropServices.Guid("028FF54F-98DF-4879-A355-880832C49A1C")]
	[System.Runtime.InteropServices.ClassInterface(System.Runtime.InteropServices.ClassInterfaceType.None)]
	[System.Runtime.InteropServices.ProgId("Raccoom.RssFeedManager")]
	public class RssFeedManager
	:	ConcreteRssFactory
	{
		#region fields
		
		internal delegate void OutlineCallbackDelegate (OpmlOutlineFavorite item);
		
		/// <summary>opml based favorite's document</summary>
		private readonly OpmlDocumentFavorites _favoritesDocument;
		private readonly OpmlDocumentFavorites _historyDocument = new OpmlDocumentFavorites ( );
		/// <summary>non persistent rss channel cache list</summary>
		private System.Collections.Hashtable _rssChannelCacheList = new System.Collections.Hashtable ( );
		private string _cachePath = string.Empty;
		
		#endregion
		
		#region constructors
		
		/// <summary>
		/// Initializes a new instance of RssFeedManager using the specified favorites.
		/// </summary>
		/// <param name="opmlDocument">The opml document which holds the managed favorites</param>
		public RssFeedManager (string cacheRootPath, OpmlDocumentFavorites favoriteDocument)
		{
			_cachePath = System.IO.Path.Combine(cacheRootPath, "Cache");
			if(!System.IO.Directory.Exists(_cachePath)) System.IO.Directory.CreateDirectory(_cachePath);
			//
			_favoritesDocument = favoriteDocument;
			//
			BuildHistory();
		}
		
		#endregion
		
		#region public interface
		
		/// <summary>
		/// RssFeedManager implements the operations to create <see cref="RssChannel"/> objects.
		/// This class is responsible to manage the cache and is build to work with <see cref="OpmlOutlineFavorite"/> outline.
		/// </summary>
		/// <param name="outline">The <see cref="OpmlOutlineFavorite"/> of the resource to receive the data.</param>
		/// <returns>The requested see cref="IRssChannel" instance</returns>		
		public IRssChannel GetFeed (OpmlOutlineFavorite outline)
		{
			Uri uri = new Uri(outline.XmlUrl);
			IRssChannel rssChannel = null;
			//System.Windows.Forms.Cursor.Current = System.Windows.Forms.Cursors.WaitCursor;
			try
			{
				// never fetched before
				if(outline.ExpireDate == DateTime.MinValue || outline.ExpireDate < DateTime.Now)
				{
					// fetch channel from web
					rssChannel = InternalGetFeed(uri);
					// save channel to cache
					rssChannel.Save(GetCachePathFromUri(uri));					
					//
					if(rssChannel.Ttl > 0)
					{
						outline.ExpireDate = DateTime.Now.AddMinutes(rssChannel.Ttl);					
					}
					else
					{
						outline.ExpireDate = DateTime.Now.AddMinutes(3600);				
					}
					//
					outline.State = CacheState.Cached;
					//
					// add/refresh loaded channel to internal non persistent cache list
					_rssChannelCacheList[outline] = rssChannel;
				}
				// check cache
				else
				{				
					// cached and not out of date
					if (_rssChannelCacheList.Contains(outline))
					{
						rssChannel = _rssChannelCacheList[outline] as IRssChannel;
					}
						// cached but out of date, refresh
					else
					{
						// fetch channel from web
						rssChannel = InternalGetFeed(new Uri(GetCachePathFromUri(uri)));
						outline.State = CacheState.Cached;
						// update cache list
						_rssChannelCacheList[outline] = rssChannel;
					}
				}
			} 
			catch (System.Exception e)
			{
				System.Diagnostics.Debug.WriteLine(e.Message);
				outline.State = CacheState.Error;
				throw e;
			}
			finally
			{	
				//System.Windows.Forms.Cursor.Current = System.Windows.Forms.Cursors.Default;
			}
			//
			outline.TimesVisited++;
			outline.LastVisited = DateTime.Now;
			UpdateHistory(outline);
			//
			return rssChannel;
		}
		
		/// <summary>
		/// Refresh all managed favorite's
		/// </summary>
		public void Refresh ()
		{
			new RecursiveWorkerThread(_favoritesDocument.Body.Items, new OutlineCallbackDelegate(RefreshOutline));
		}
		
		/// <summary>
		/// Reset all managed favorite's to it's default values
		/// </summary>
		public void Reset ()
		{
			new RecursiveWorkerThread(_favoritesDocument.Body.Items, new OutlineCallbackDelegate(ResetOutline));
		}
		
		public System.IO.DirectoryInfo CachePath
		{
			get
			{
				return new System.IO.DirectoryInfo(_cachePath);
			}
		}
		
		#endregion
		
		#region private interface
		
		/// <summary>
		/// Reset the favorite to it's default values.
		/// </summary>
		/// <param name="item"></param>
		private void ResetOutline (OpmlOutlineFavorite item)
		{
			System.IO.File.Delete( GetCachePathFromUri(new Uri(item.XmlUrl)));
			item.State = CacheState.None;
			item.TimesVisited = 0;
			item.LastVisited = DateTime.MinValue;
		}
		
		private void RefreshOutline (OpmlOutlineFavorite item)
		{
			if(item.State == CacheState.Cached) return;
			//
			this.GetFeed(item);
		}
		
		/// <summary>
		/// Creates the histroy for the current favorites
		/// </summary>
		private void BuildHistory ()
		{
			try
			{				
				new RecursiveWorkerThread(this._favoritesDocument.Body.Items, new OutlineCallbackDelegate(UpdateHistory));
			} 
			catch (System.Exception e)
			{
				System.Diagnostics.Debug.WriteLine(e.Message);
			}
		}
		
		/// <summary>
		/// Updates the history with the specified item
		/// </summary>
		/// <param name="item"></param>
		private void UpdateHistory (OpmlOutlineFavorite item)
		{
			if(item.State!= CacheState.Cached) return;
			//
			if(FindOutline(item, _historyDocument.Body.Items)) return;
			//			
			OpmlOutlineFavorite historyDayItem = null;
			foreach(OpmlOutlineFavorite historyItem in _historyDocument.Body.Items)
			{
				TimeSpan span = historyItem.LastVisited.Subtract(item.LastVisited);
				if(span.Days==0)
				{
					historyDayItem = historyItem;
					break;
				}
			}
			// create on demand
			if(historyDayItem==null)
			{
				historyDayItem = new OpmlOutlineFavorite();
				historyDayItem.LastVisited = item.LastVisited;
				historyDayItem.Text = item.LastVisited.ToString("dddd");				
		 		//
				_historyDocument.Body.Items.Add(historyDayItem);
			}
			// add new item			
			historyDayItem.Items.Add(item);			
		}
		
		/// <summary>
		/// Get the feed from the origin uri
		/// </summary>
		/// <param name="uri">The URI of the resource to receive the data.</param>
		/// <returns>IRssChannel instance</returns>
		private IRssChannel InternalGetFeed (Uri uri)
		{
			return base.GetFeed(uri);
		}
		
		/// <summary>
		/// Opml document that holds the favorites
		/// </summary>
		internal OpmlDocumentFavorites Favorites
		{
			get
			{
				return _favoritesDocument;
			}
		}
		
		/// <summary>
		/// Opml document that holds the histroy
		/// </summary>
		public OpmlDocumentFavorites History
		{
			get
			{
				return _historyDocument;
			}
		}
		
		/// <summary>
		/// Recursive contains over the specified collection to find the outline item.
		/// </summary>
		/// <param name="uri">The URI of the outline item to find.</param>
		/// <param name="collection">RootCollection to search in.</param>
		/// <returns>True if the collection's hold this instance, otherwise False if not found.</returns>
		private bool FindOutline (OpmlOutlineFavorite item, OpmlOutlineCollection collection)
		{
			if(collection.Contains(item)) return true;
			//
			foreach(OpmlOutlineFavorite o in collection)
			{				
				// recursive call
				if(FindOutline(item,o.Items)) return true;				
			}
			return false;
		}
		
		/// <summary>
		/// Find the outline item that holds the requested uri.
		/// </summary>
		/// <param name="uri">The URI of the outline item to find.</param>
		/// <param name="collection">RootCollection to search in.</param>
		/// <returns></returns>
		private OpmlOutlineFavorite FindOutlineByUri (Uri uri, System.Collections.ICollection collection)
		{
			foreach(OpmlOutlineFavorite o in collection)
			{
				if(object.Equals(o.XmlUrl, uri.AbsoluteUri))
				{
					return o;
				}
				// recursive call
				OpmlOutlineFavorite outline = FindOutlineByUri(uri, o.Items);
				if(outline!=null) return outline;
			}
			return null;
		}
		
		/// <summary>
		/// Gets the local cache path mapping from uri
		/// </summary>
		/// <param name="uri">The URI of the resource to receive the data.</param>
		/// <returns></returns>
		private string GetCachePathFromUri (System.Uri uri)
		{
			string filename = uri.GetHashCode().ToString();
			filename = filename.Substring(1, filename.Length-1);
			return System.IO.Path.Combine(_cachePath, filename+".xml");
		}
		
		#endregion
		
		#region nested classes
		
		internal class RecursiveWorkerThread
		{
			private OpmlOutlineCollection _collection;
			private OutlineCallbackDelegate _callback;
			private System.Collections.Hashtable _exceptionTable = new System.Collections.Hashtable ( );
			private System.Threading.Thread thread = null;
			
			internal RecursiveWorkerThread (OpmlOutlineCollection collection, OutlineCallbackDelegate callback)
			{
				_collection = collection;
				_callback = callback;
				// let's go
				//System.Threading.ThreadPool.QueueUserWorkItem(new System.Threading.WaitCallback(Start));
				System.Threading.Thread thread = new System.Threading.Thread(new System.Threading.ThreadStart(Run));
				thread.IsBackground = true;
				thread.Start();
			}
			
			private void Run ()
			{
				RecursiveWorker(_collection, _callback);			
			}
			
			public bool IsAlive
			{
				get
				{
					return thread.IsAlive;
				}
			}
			
			/// <summary>
			/// Calls the specified delegate recursively for each outline element
			/// </summary>
			/// <param name="collection">Collection with outline items</param>
			/// <param name="callback">Delegate to call for each item</param>
			private void RecursiveWorker (OpmlOutlineCollection collection, OutlineCallbackDelegate callback)
			{
				//
				for(int i = 0;i<collection.Count;i++)
				//foreach(OpmlOutlineFavorite o in collection)
				{
					OpmlOutlineFavorite o = (OpmlOutlineFavorite) collection[i];
					//
					if(o.Items.Count > 0)
					{				
						RecursiveWorker(o.Items, callback);				
					} 
					else
					{
						try
						{
							callback(o);
						}
						catch(System.Exception e)
						{
							_exceptionTable.Add(o, e);
						}
					}
				}
			}
		}
		
		#endregion
	}
	
	/// <summary>
	/// Opml Document to hold favorite rss feed url's
	/// </summary>
	[XmlRoot("opml"), Serializable]
	public class OpmlDocumentFavorites
	:	OpmlDocument
	{
		/// <summary>
		/// Initializes a new instance of OpmlDocumentFavorites
		/// </summary>
		public OpmlDocumentFavorites (): base()
		{
		
		}
		
		/// <summary>
		/// Initializes a new instance of OpmlDocumentFavorites
		/// </summary>
		/// <param name="uri">The URI of the resource to receive the data.</param>
		public OpmlDocumentFavorites (Uri uri): base(uri)
		{
		
		}
		
		/// <summary>
		/// Write the object to the specified stream.
		/// </summary>
		/// <param name="stream">The Stream used to write the XML document.</param>
		public override void Save (System.IO.Stream stream)
		{
			try
			{	
				XmlTextWriter writer = new XmlTextWriter (stream, System.Text.Encoding.UTF8);
				//Use indenting for readability.
				writer.Formatting = Formatting.Indented;
				//Write the XML delcaration. 
				writer.WriteStartDocument();
				// determine type of Outline
				//
				System.Xml.Serialization.XmlSerializer ser = new System.Xml.Serialization.XmlSerializer(this.GetType(), new Type[] {typeof(OpmlOutlineFavorite)});
				ser.Serialize(writer, this);												
				writer.WriteEndDocument();
				writer.Flush();
			} 
			finally
			{				
				//if(writer!=null) writer.Close();
			}
		}
		
		/// <summary>
		/// Initializes a new instance of OpmlOutlineFavorite
		/// </summary>
		/// <param name="xmlTextReader"></param>
		/// <returns></returns>
		protected override OpmlOutline OnCreateOutline (XmlReader xmlTextReader)
		{
			return new OpmlOutlineFavorite(xmlTextReader);
		}
	}
	
	/// <summary>
	/// Favorite rss feed outline item
	/// </summary>
	[Serializable]
	public class OpmlOutlineFavorite
	:	OpmlOutline
	{
		#region fields
		
		private CacheState _cacheState;
		private DateTime _expireDate;
		private DateTime _tastVisited;
		private int _timesVisited;
		
		#endregion
		
		#region constructors
		
		/// <summary>
		/// Initializes a new instance of OpmlOutlineFavorite
		/// </summary>		
		public OpmlOutlineFavorite (): base()
		{
			Reset();
		}
		
		/// <summary>
		/// Initializes a new instance of OpmlDocumentFavorites
		/// </summary>
		public OpmlOutlineFavorite (XmlReader xmlTextReader): base(xmlTextReader)
		{
		
		}
		
		#endregion
		
		#region public interface
		
		/// <summary>
		/// Gets or sets the state cache state for this rss channel.
		/// </summary>
		[System.Xml.Serialization.XmlAttribute("cacheState")]
		public CacheState State
		{
			get
			{
				if(DateTime.Now > this.ExpireDate)
				{
					_cacheState = CacheState.Expired;
				}
				return _cacheState;
			}
			
			set
			{
				_cacheState = value;
			}
		}
		
		/// <summary>
		/// Gets or sets the expire date for this rss channel.
		/// </summary>
		[System.Xml.Serialization.XmlAttribute("expireDate")]
		public DateTime ExpireDate
		{
			get
			{
				return _expireDate;
			}
			
			set
			{
				bool changed = !object.Equals(_expireDate, value);
				_expireDate = value;
				if(changed) OnPropertyChanged(new System.ComponentModel.PropertyChangedEventArgs(Fields.ExpireDate));
			}
		}
		
		/// <summary>
		/// Gets or sets the last visited date for this rss channel.
		/// </summary>
		[System.Xml.Serialization.XmlAttribute("lastVisited")]
		public DateTime LastVisited
		{
			get
			{
				return _tastVisited;
			}
			
			set
			{
				bool changed = !object.Equals(_expireDate, value);
				_tastVisited = value;
				if(changed) OnPropertyChanged(new System.ComponentModel.PropertyChangedEventArgs(Fields.LastVisited));
			}
		}
		
		/// <summary>
		/// Gets or sets the times visited counter for this rss channel
		/// </summary>
		[System.Xml.Serialization.XmlAttribute("timesVisited")]
		public int TimesVisited
		{
			get
			{
				return _timesVisited;
			}
			
			set
			{
				bool changed = !object.Equals(_timesVisited, value);
				_timesVisited = value;
				if(changed) OnPropertyChanged(new System.ComponentModel.PropertyChangedEventArgs(Fields.ClickCounter));
			}
		}
		
		protected internal virtual void Reset ()
		{
			this.ExpireDate = DateTime.MinValue;
			this.LastVisited = DateTime.MinValue;
			this.State = CacheState.None;
		}
		
		#endregion
		
		#region nested classes
		
		/// <summary>
		/// public writeable class properties
		/// </summary>		
		internal new struct Fields
		{
			public const string ClickCounter = "ClickCounter";
			public const string ExpireDate = "ExpireDate";
			public const string LastVisited = "LastVisited";
		}
		
		#endregion
	}
	
	/// <summary>
	/// Defines states for <see cref="OpmlOutlineFavorite"/>.
	/// </summary>
	public enum CacheState
	{
		/// <summary>The item is stored in the cache</summary>
		Cached,
		/// <summary>There was an error while loading</summary>
		Error,
		/// <summary>The cached item is out of date</summary>
		Expired,
		/// <summary>The item is untouched</summary>
		None
	}
}

By viewing downloads associated with this article you agree to the Terms of Service and the article's licence.

If a file you wish to view isn't highlighted, and is a text file (not binary), please let us know and we'll add colourisation support for it.

License

This article, along with any associated source code and files, is licensed under The GNU Lesser General Public License (LGPLv3)


Written By
Software Developer (Senior)
Switzerland Switzerland
My interest is in the future because I am going to spend the rest of my life there. (Charles Kettering)

Biography

  • 1996 - 1998 PC Board PPL, HTML, DHTML, Javascript and ASP
  • 1999 - 2001 coding Centura against Sql Database (SqlBase,MSSQL,Oracle)
  • 2002 - 2004 C# Windows Forms
  • 2005 - 2006 C# ASP.NET, Windows Forms
  • 2006 - 2009 C#, WCF, WF, WPF
  • 2010 - 2012 C#, Dynamics CRM, Sharepoint, Silverlight
  • 2013 - 2013 C#, WCF DS (OData), WF, WPF
  • 2014 - 2016 C#, Azure PaaS, Identity, OWIN, OData, Web Api
  • 2017 - now C#, aspnet.core, IdentityServer4, TypeScript & Angular @ Azure IaaS or PaaS

Interests

  • family & friends
  • chilaxing ,)
  • coding

Comments and Discussions