Click here to Skip to main content
Click here to Skip to main content
Add your own
alternative version

RSS Feed Aggregator and Blogging Smart Client

, 16 Aug 2005 CPOL
RSS Feed aggregator and blogging Smart Client which uses Enterprise Library, Updater Application Block, lots of XML hacks and desktop tricks. A comprehensive guide to real life hurdles of Smart Client development.
rssfeeder_src.zip
RSSFeeder
RSSBlogAPI
RSSBlogAPI.csproj.user
Web References
CommunityServer
blogservice.disco
blogservice.wsdl
Reference.map
DotText
Reference.map
simpleblogservice.disco
simpleblogservice.wsdl
RSSCommon
Helper
PropertyEditor
RSSCommon.csproj.user
RSSFeeder
Controls
docs
dropshadow.png
My Pic 7.jpg
WIndows98.jpg
Helpers
Resources
E-mail.ico
rss.ico
RSSFeeder.mdb
RSSFeeder.csproj.user
RSSStarter.exe
RSSFeederResources
PublicQueue.ico
RSSFeeder.mdb
RSSFeederResources.csproj.user
RSSFeederSetup
Messages.ico
RSSFeederSetup.vdproj
Test
RSSStarter
DummyForm.frm
DummyForm.frx
MainModule.bas
RSSStarter.exe
RSSStarter.vbp
RSSStarter.vbw
RSSTests
App.ico
RSSTests.csproj.user
Thirdparty
AxInterop.DHTMLEDLib.dll
AxInterop.SHDocVw.dll
Eyefinder.dll
GotDotNet.Exslt.dll
HttpDownloader.dll
Interop.DHTMLEDLib.dll
Interop.SHDocVw.dll
Interop.WindowsInstaller.dll
Microsoft.ApplicationBlocks.Updater.ActivationProcessors.dll
Microsoft.ApplicationBlocks.Updater.dll
Microsoft.ApplicationBlocks.Updater.Downloaders.dll
Microsoft.Practices.EnterpriseLibrary.Caching.dll
Microsoft.Practices.EnterpriseLibrary.Common.dll
Microsoft.Practices.EnterpriseLibrary.Configuration.dll
Microsoft.Practices.EnterpriseLibrary.ExceptionHandling.dll
Microsoft.Practices.EnterpriseLibrary.ExceptionHandling.Logging.dll
Microsoft.Practices.EnterpriseLibrary.Logging.dll
Microsoft.Practices.EnterpriseLibrary.Security.Cache.CachingStore.dll
Microsoft.Practices.EnterpriseLibrary.Security.Cryptography.dll
Microsoft.Practices.EnterpriseLibrary.Security.dll
NotifyIconBalloon.dll
SandBar.dll
SandDock.dll
SgmlReaderDll.dll
// Copyright � 2005 by Omar Al Zabir. All rights are reserved.
// 
// If you like this code then feel free to go ahead and use it.
// The only thing I ask is that you don't remove or alter my copyright notice.
//
// Your use of this software is entirely at your own risk. I make no claims or
// warrantees about the reliability or fitness of this code for any particular purpose.
// If you make changes or additions to this code please mark your code as being yours.
// 
// website http://www.oazabir.com, email OmarAlZabir@gmail.com, msn oazabir@hotmail.com

using System;
using System.Text;
using System.Diagnostics;
using System.Threading;
using System.Drawing;
using System.Collections;
using System.ComponentModel;
using System.Windows.Forms;
using System.IO;
using System.Xml;
using System.Runtime.InteropServices;
using System.Reflection;

namespace RSSFeeder
{
	using RSSBlogAPI;
	using RSSCommon;
	using RSSCommon.Helper;
	using Helpers;

	/// <summary>
	/// Summary description for FeedDownloader.
	/// </summary>
	public class FeedDownloader : System.Windows.Forms.Form
	{
		#region Designer 

		private System.Windows.Forms.ListView progressView;
		private System.Windows.Forms.ColumnHeader titleColumn;
		private System.Windows.Forms.ColumnHeader statusColumn;
		private System.Windows.Forms.ColumnHeader urlColumn;
		private System.Windows.Forms.ContextMenu progressMenu;
		private System.Windows.Forms.MenuItem viewStatusMenuItem;
		private System.Windows.Forms.MenuItem visitChannelMenuItem;
		private System.Windows.Forms.ImageList icons;
		private System.Windows.Forms.Timer startTimer;
		private System.Windows.Forms.Timer closeTimer;
		private System.ComponentModel.IContainer components;

		public event EventHandler DownloadComplete;

		public FeedDownloader( )
		{
			//
			// Required for Windows Form Designer support
			//
			InitializeComponent();

			//
			// TODO: Add any constructor code after InitializeComponent call
			//
		}

		/// <summary>
		/// Clean up any resources being used.
		/// </summary>
		protected override void Dispose( bool disposing )
		{
			this.DisposeOutlook();

			if( disposing )
			{

				if(components != null)
				{
					components.Dispose();
				}
			}
			base.Dispose( disposing );
		}

		#region Windows Form Designer generated code
		/// <summary>
		/// Required method for Designer support - do not modify
		/// the contents of this method with the code editor.
		/// </summary>
		private void InitializeComponent()
		{
			this.components = new System.ComponentModel.Container();
			System.Resources.ResourceManager resources = new System.Resources.ResourceManager(typeof(FeedDownloader));
			this.progressView = new System.Windows.Forms.ListView();
			this.titleColumn = new System.Windows.Forms.ColumnHeader();
			this.statusColumn = new System.Windows.Forms.ColumnHeader();
			this.urlColumn = new System.Windows.Forms.ColumnHeader();
			this.progressMenu = new System.Windows.Forms.ContextMenu();
			this.viewStatusMenuItem = new System.Windows.Forms.MenuItem();
			this.visitChannelMenuItem = new System.Windows.Forms.MenuItem();
			this.icons = new System.Windows.Forms.ImageList(this.components);
			this.startTimer = new System.Windows.Forms.Timer(this.components);
			this.closeTimer = new System.Windows.Forms.Timer(this.components);
			this.SuspendLayout();
			// 
			// progressView
			// 
			this.progressView.BorderStyle = System.Windows.Forms.BorderStyle.None;
			this.progressView.Columns.AddRange(new System.Windows.Forms.ColumnHeader[] {
																						   this.titleColumn,
																						   this.statusColumn,
																						   this.urlColumn});
			this.progressView.ContextMenu = this.progressMenu;
			this.progressView.Cursor = System.Windows.Forms.Cursors.Hand;
			this.progressView.Dock = System.Windows.Forms.DockStyle.Fill;
			this.progressView.FullRowSelect = true;
			this.progressView.GridLines = true;
			this.progressView.HeaderStyle = System.Windows.Forms.ColumnHeaderStyle.None;
			this.progressView.HideSelection = false;
			this.progressView.HoverSelection = true;
			this.progressView.Location = new System.Drawing.Point(0, 0);
			this.progressView.Name = "progressView";
			this.progressView.Size = new System.Drawing.Size(400, 110);
			this.progressView.SmallImageList = this.icons;
			this.progressView.TabIndex = 0;
			this.progressView.View = System.Windows.Forms.View.Details;
			this.progressView.DoubleClick += new System.EventHandler(this.progressView_DoubleClick);
			// 
			// titleColumn
			// 
			this.titleColumn.Text = "Title";
			this.titleColumn.Width = 250;
			// 
			// statusColumn
			// 
			this.statusColumn.Text = "Status";
			this.statusColumn.Width = 150;
			// 
			// urlColumn
			// 
			this.urlColumn.Text = "URL";
			this.urlColumn.Width = 200;
			// 
			// progressMenu
			// 
			this.progressMenu.MenuItems.AddRange(new System.Windows.Forms.MenuItem[] {
																						 this.viewStatusMenuItem,
																						 this.visitChannelMenuItem});
			// 
			// viewStatusMenuItem
			// 
			this.viewStatusMenuItem.Index = 0;
			this.viewStatusMenuItem.Text = "View Status";
			this.viewStatusMenuItem.Click += new System.EventHandler(this.viewStatusMenuItem_Click);
			// 
			// visitChannelMenuItem
			// 
			this.visitChannelMenuItem.Index = 1;
			this.visitChannelMenuItem.Text = "Visit channel";
			this.visitChannelMenuItem.Click += new System.EventHandler(this.visitChannelMenuItem_Click);
			// 
			// icons
			// 
			this.icons.ImageSize = new System.Drawing.Size(16, 16);
			this.icons.ImageStream = ((System.Windows.Forms.ImageListStreamer)(resources.GetObject("icons.ImageStream")));
			this.icons.TransparentColor = System.Drawing.Color.Transparent;
			// 
			// startTimer
			// 
			this.startTimer.Enabled = true;
			this.startTimer.Interval = 1000;
			this.startTimer.Tick += new System.EventHandler(this.timer_Tick);
			// 
			// closeTimer
			// 
			this.closeTimer.Interval = 20000;
			this.closeTimer.Tick += new System.EventHandler(this.closeTimer_Tick);
			// 
			// FeedDownloader
			// 
			this.AutoScaleBaseSize = new System.Drawing.Size(5, 13);
			this.ClientSize = new System.Drawing.Size(400, 110);
			this.Controls.Add(this.progressView);
			this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.SizableToolWindow;
			this.Icon = ((System.Drawing.Icon)(resources.GetObject("$this.Icon")));
			this.Name = "FeedDownloader";
			this.ShowInTaskbar = false;
			this.StartPosition = System.Windows.Forms.FormStartPosition.Manual;
			this.Text = "Synchronizing Feeds...";
			this.Closing += new System.ComponentModel.CancelEventHandler(this.FeedDownloader_Closing);
			this.Load += new System.EventHandler(this.FeedDownloader_Load);
			this.ResumeLayout(false);

		}
		#endregion

		#endregion

		#region Private Fields
		
		private bool _IsClosing = false;

		private Thread _DownloadThread = null;

		private IList _Channels;

		private Hashtable _RssChannels = new Hashtable();

		private int _CurrentFeedIndex = 0;

		// These are the most dangerous object references
		private object _Application; 

		private object _Session;

		#endregion

		#region Public Properties

		public IList Channels
		{
			get
			{
				return this._Channels;
			}
			set
			{
				this._Channels = value;
			}
		}


		public IDictionary RssChannels
		{
			get
			{
				return this._RssChannels;
			}
		}

		#endregion

		#region Public Methods

		public void Position()
		{
			UIHelper.PositionForm( this, Configuration.Instance.PopupWindowPosition );
		}

		#endregion

		#region Private Methods

		/// <summary>
		/// Dump the list view content in a temporary text file and show it to user
		/// </summary>
		private void ViewStatus()
		{
			string tempFileName = Path.GetFullPath( Path.GetTempFileName() + ".txt" );
			using( StreamWriter writer = new StreamWriter( tempFileName ) )
			{
				// Dump all items
				foreach( ListViewItem item in progressView.Items )
				{
					writer.WriteLine( item.Text );

					// dump all subitems in one line
					foreach( ListViewItem.ListViewSubItem subItem in item.SubItems )
					{
						writer.Write( '\t' );
						writer.WriteLine( subItem.Text );
					}		
			
					writer.Write( writer.NewLine );
				}

				writer.Close();

				// Open the log file using default viewer
				using( Process p = Process.Start( new ProcessStartInfo( tempFileName ) ) )
				{
				}

				// Wait till the file is displayed properly
				System.Threading.Thread.Sleep( 1000 );

				// Delete the file
				try
				{
					File.Delete( tempFileName );
				}
				catch
				{
					// Don't bog down, who cares if the file is not deleted
				}
			}
		}

		/// <summary>
		/// View the source of the first selected channel
		/// </summary>
		private void VisitChannel()
		{
			if( 0 == progressView.SelectedItems.Count ) return;

			Channel channel = (Channel) this._Channels[ progressView.SelectedIndices[0] ];
			BrowserHelper.BrowseURL( channel.FeedURL.ToString() );
		}

		private void StartDownload()
		{
			// If outlook sync enabled, start outlook
			if( Configuration.Instance.SyncWithOutlook )
			{
				OutlookHelper.StartOutlook( ref this._Application, ref this._Session );

				if( null == this._Application || null == this._Session )
				{
					RSSServer.TrayNotifyIcon.ShowBalloon("Outlook Problem", "Cannot initialize Outlook. You should turn off Outlook synchronization", NotifyIcon.BalloonTip.NotifyInfoFlags.Error, 1000);
				}
			}

			#region Log
			EntLibHelper.Debug("StartDownload", "Starting downloader thread...");
			#endregion

			this._DownloadThread = new Thread( new ThreadStart( DownloadFeedThread ) );
			this._DownloadThread.Start();			
		}

		
		private void DownloadProgress( object sender, ProgressEventArgs e )
		{
			if( base.InvokeRequired )
			{
				this.Invoke( new ProgressEventHandler( DownloadProgress ), new object[] { sender, (ProgressEventArgs)e } );
			}
			else
			{
				this.UpdateProgress( this._CurrentFeedIndex, e.Message, Color.Black	);
			}
		}

		/// <summary>
		/// Callback from the download thread.
		/// 
		/// Download all channels one by one in this thread
		/// </summary>
		private void DownloadFeedThread()
		{
			// Suspend the thread until the handle of the necessary components are created
			while( !base.IsHandleCreated || !this.progressView.IsHandleCreated )
				Thread.Sleep( 1000 );

			try
			{
				UpdateProgressDelegate progressDelegate = new UpdateProgressDelegate( this.UpdateProgress );

				while( this._CurrentFeedIndex < this._Channels.Count )
				{
					// User may have shown interest to abort download
					if( this._IsClosing ) return;

					Channel channel = (Channel) this._Channels[ this._CurrentFeedIndex ];

					// Update progress by marshalling to UI thread
					this.Invoke( progressDelegate, new object [] { this._CurrentFeedIndex, "Downloading...", Color.Black } );

					try
					{
						RssChannel rssChannel = null;

						// Perform 3 times retry on web or IO errors
						int retryCount = 3;
						while( retryCount > 0 )
						{
							try
							{
								IList channels;
								IList channelSources;

								RSSHelper.DownloadFeeds( channel.FeedURL, 
									Configuration.Instance.ProxyName, 
									Configuration.Instance.ProxyPort, 
									Configuration.Instance.ProxyUserName,
									Configuration.Instance.ProxyUserPassword,
									out channels, out channelSources, 
									new ProgressEventHandler( DownloadProgress ) );

								if( channels.Count > 0 )
								{
									rssChannel = channels[0] as RssChannel;
									this.Invoke( progressDelegate, new object [] { this._CurrentFeedIndex, "Downloaded", Color.Black } );
								}
								else
								{
									if( channelSources.Count > 0 )
									{
										this.Invoke( progressDelegate, new object [] { this._CurrentFeedIndex, "HTML Page with RSS Feeds", Color.Red } );
									}
									else
									{
										this.Invoke( progressDelegate, new object [] { this._CurrentFeedIndex, "No Feed Found.", Color.Red } );
									}
								}

								retryCount = 0;
								
							}
							#region Catch Blocks
							catch( System.Net.WebException x )
							{
								#region Log
								EntLibHelper.Exception(channel.Title, x);
								#endregion
								this.Invoke( progressDelegate, new object [] { this._CurrentFeedIndex, x.Message, Color.Red } );
								retryCount --;
							}
							catch( System.IO.IOException x )
							{
								#region Log
								EntLibHelper.Exception(channel.Title, x);
								#endregion
								this.Invoke( progressDelegate, new object [] { this._CurrentFeedIndex, x.Message, Color.Red } );
								retryCount --;
							}
							catch( ThreadInterruptedException x )
							{
								Debug.WriteLine( x );
								retryCount --;
								break;
							}	
							catch( ThreadAbortException x )
							{
								Debug.WriteLine( x );
								retryCount --;
								break;
							}					
							catch( Exception x )
							{
								#region Log
								EntLibHelper.Exception(channel.Title, x);
								ErrorReportForm.QueueErrorReport( new ErrorReportItem( x.Message, x.ToString() ) );
								#endregion
								this.Invoke( progressDelegate, new object [] { this._CurrentFeedIndex, x.Message, Color.Red } );
								retryCount = 0;
							}
							#endregion
						} // while( retryCount > 0 )

						// If the download was not aborted and we have a valid RSS Channel downloaded,
						// let's get the feeds and store the feeds in database and optionally in outlook
						if( !this._IsClosing && null != rssChannel )
						{
							#region Log
							EntLibHelper.Debug("DownloadFeedThread", channel.Title + " Downloaded");
							#endregion

							this._RssChannels.Add( channel, rssChannel );

							// Store feeds in database	
							Channel updatedChannel = RSSHelper.UpdateChannel( channel, rssChannel );

							// Sync Outlook
							try
							{
								this.Invoke( progressDelegate, new object [] { this._CurrentFeedIndex, "Outlook...", Color.Black } );

								#region Log
								EntLibHelper.Debug("DownloadFeedThread", channel.Title + " SyncOutlook");
								#endregion

								int itemsAdded = this.SyncOutlook( channel );

								// Update progress by marshalling to UI thread
								string msg = string.Format( "Unread:{0}, Outlook:{1}", 
									updatedChannel.UnreadCount, itemsAdded );

								this.Invoke( progressDelegate, new object [] { this._CurrentFeedIndex, 
																				 msg, Color.Black } );
							}
								#region Catch Blocks
							catch( ThreadAbortException x )
							{
								Debug.WriteLine( x );
								break;
							}
							catch( ThreadInterruptedException x )
							{
								Debug.WriteLine( x );
								break;
							}
							catch( Exception x  )
							{
								EntLibHelper.Exception( channel.Title, x );
								this.Invoke( progressDelegate, new object [] { this._CurrentFeedIndex, x.Message, Color.Red } );								
							}
							#endregion
						} // if( !this._IsClosing && null != rssChannel )
					}
						#region Catch Blocks
					catch( ThreadAbortException x )
					{
						Debug.WriteLine( x );
						break;
					}
					catch( ThreadInterruptedException x )
					{
						Debug.WriteLine( x );
						break;
					}
					catch( Exception x )
					{
						EntLibHelper.Exception( channel.Title, x );
						// Update progress by marshalling to UI thread
						this.Invoke( progressDelegate, new object [] { this._CurrentFeedIndex, x.Message, Color.DarkRed } );
					}
					#endregion
					this._CurrentFeedIndex ++;
				}
			}
			#region Catch Blocks
			catch( ThreadInterruptedException x )
			{
				Debug.WriteLine( x );
			}
			catch( ThreadAbortException x )
			{
				Debug.WriteLine( x );
			}		
			#endregion

			
			// Get posts in outlook and deliver
			this.Invoke( new MethodInvoker( this.PostWeblogs ) );

			// Complete download of RSS feeds and perform post download tasks
			this.Invoke( new MethodInvoker( DownloadCompleted ) );
		}

		/// <summary>
		/// Get pending items from weblog folders and post to weblogs
		/// </summary>
		private void PostWeblogs()
		{
			string statusMessage = "";

			bool isAnyError = false;

			// Add the weblogs
			foreach( WebLog log in WebLogProvider.Instance.WebLogs )
			{
				this.AddWebLog( log );

				// Delivery queued posts
				int index = 0;
				while( index < log.Posts.Count )
				{
					Post post = log.Posts[index] as Post;
					// Post only those which are marked to be sent
					if( post.IsSend && !post.IsSent )
					{
						try
						{
							// Here, we do the actual posting. Cross your finger, this
							// is going to be a bumpy ride.
							string id = WebLogProvider.Instance.PostBlog( log, post );
							statusMessage += " Posted: " + id;

							// At last, sucess!
							post.IsSent = true;
							post.IsSend = false;

							post.ErrorInPost = string.Empty;
						}
						catch( Exception x )
						{
							post.ErrorInPost = x.Message;
							statusMessage += " Error Occured while posting: " + post.Title 
								+ Environment.NewLine +
								" Error Message: " + x.Message;

							isAnyError = true;
						}
					}

					index ++;
				}

				// If outlook is available, see if there's any pending blog in outlook which 
				// needs to be delivered to blog site
				if( null != this._Session )
				{
					if( null != log.OutlookFolder && log.OutlookFolder.Trim().Length > 0 )
					{
						this.PostFromOutlook( log );
					}
				}
				
				// Post complete
				this.UpdateWebLogProgress( log, new ProgressEventArgs( statusMessage, 0 ) );
				
			}			

			// Save the recent state
			WebLogProvider.SaveWebLogs();

			if( isAnyError )
			{
				MessageBox.Show( this, "Some error occured while posting to weblog. " +
					"Please view the posts in order to see the error messages", "Post Weblog", 
					MessageBoxButtons.OK, MessageBoxIcon.Error );
			}
		}

		/// <summary>
		/// Post pending posts in outlook folder which needs to be posted to weblog
		/// </summary>
		/// <param name="folderPath"></param>
		/// <returns></returns>
		private void PostFromOutlook( WebLog webLog )
		{
			// Ensure that the destination folder exists. If not, create it
			// Cross your finger, this takes a lot of time
			object folder = OutlookHelper.EnsureFolderPath( this._Session, webLog.OutlookFolder );
			object sentFolder = OutlookHelper.EnsureFolderPath( this._Session, Path.Combine( webLog.OutlookFolder, "Sent" ) );

			if( null == folder || null == sentFolder )
			{
				throw new ApplicationException("Cannot create folder: " + webLog.OutlookFolder);
			}
			else
			{
				// Get reference to folder items because we need to add new posts
				object folderItems = OutlookHelper.GetProperty( folder, "Items" );

				int index = 1;
				while( index <= (int)OutlookHelper.GetProperty( folderItems, "Count" ) )
				{
					// Get the first item in the folder
					object item = OutlookHelper.GetItem( folderItems, index );
				
					string messageClass = (string)OutlookHelper.GetProperty( item, "MessageClass" );

					if( "IPM.Post" == messageClass )
					{
						string subject = (string)OutlookHelper.GetProperty( item, "Subject" );				

						this.UpdateWebLogProgress( webLog, new ProgressEventArgs( "Posting: " + subject,  0 ) );

						string categories = (string)OutlookHelper.GetProperty( item, "Categories", "" );				
						string html;

						try
						{
							html = (string)OutlookHelper.GetProperty( item, "HTMLBody" );
						}
						catch
						{
							base.Show();
							MessageBox.Show( this, "You did not allow me to read the post from Outlook. Please allow it next time.",
								"Outlook Error", MessageBoxButtons.OK, MessageBoxIcon.Exclamation );
				
							try
							{
								html = (string)OutlookHelper.GetProperty( item, "HTMLBody" );
							}
							catch
							{
								base.Show();
								MessageBox.Show( this, "You did not allow me to read the post from Outlook.",
									"Outlook Error", MessageBoxButtons.OK, MessageBoxIcon.Exclamation );

								// Read from the next post because this one has permanent error
								index ++;
				
							}
							continue;								
						}

						// Contruct a new post
						Post p = new Post();
						p.Title = subject;
						p.Text = html;
						p.Date = DateTime.Now;
						
						p.Categories = webLog.GetCategories( categories );
	
						// Post to web log, note this goes over web
						try
						{
							WebLogProvider.Instance.PostBlog( webLog, p );

							this.UpdateWebLogProgress( webLog, new ProgressEventArgs( "Posted: " + subject,  0 ) );

							// After successful post, move the post to "Sent" folder
							OutlookHelper.CallMethod( item, "Move", sentFolder );

							this.UpdateWebLogProgress( webLog, new ProgressEventArgs( "Completed: " + subject,  0 ) );

						}
						catch( Exception x )
						{
							base.Show();
							MessageBox.Show( this, "Problem while posting: " + x.Message, "Post problem", MessageBoxButtons.OK,
								MessageBoxIcon.Exclamation );

							index++;
						}
					}
				
					Marshal.ReleaseComObject( item );
				}

				Marshal.ReleaseComObject( folderItems );
				Marshal.ReleaseComObject( folder );
			}

			this.UpdateWebLogProgress( this, new ProgressEventArgs( "All Done",  0 ) );

		}

		/// <summary>
		/// Add the specified weblog in the progress list view in order to inform user about post
		/// progress
		/// </summary>
		/// <param name="weblogObject"></param>
		private void AddWebLog( WebLog log )
		{
			ListViewItem item = new ListViewItem( new string [] { log.ToString(), "posting...", log.ServiceUrl } , 3 );
			item.Tag = log;
			item.ImageIndex = 0;
			progressView.Items.Add( item );

			base.Refresh();
		}

		/// <summary>
		/// Update the progress list view with the current status of the specified object log
		/// </summary>
		/// <param name="sender">The weblog to update</param>
		/// <param name="e">Progress message and status</param>
		private void UpdateWebLogProgress( object sender, ProgressEventArgs e )
		{
			if( base.InvokeRequired )
			{
				this.Invoke( new ProgressEventHandler( this.UpdateWebLogProgress ), new object[] { sender, e } );
			}
			else
			{
				// Update the message of the item which represents the weblog
				foreach( ListViewItem item in progressView.Items )
				{
					if( item.Tag == sender )
					{
						ListViewItem.ListViewSubItem subitem = item.SubItems[ 1 ];
						subitem.Text = e.Message;			

						base.Refresh();
						break;
					}
				}
			}
		}

		/// <summary>
		/// Delegate to call <code>UpdateProgress</code>
		/// </summary>
		delegate void UpdateProgressDelegate( int channelIndex, string message, Color color );
		/// <summary>
		/// Update progress of a RSS Feed item in the progress list view
		/// </summary>
		/// <param name="channelIndex"></param>
		/// <param name="message"></param>
		/// <param name="color"></param>
		private void UpdateProgress( int channelIndex, string message, Color color )
		{
			if( base.Disposing ) return;

			ListViewItem item = progressView.Items[ channelIndex ];
			ListViewItem.ListViewSubItem subitem = item.SubItems[ 1 ];
			subitem.Text = message;
			item.ForeColor = color;

			// Select the updated item
			progressView.SelectedItems.Clear();
			item.Selected = true;
			item.EnsureVisible();
			
		}

		/// <summary>
		/// Stop and notify feed download status
		/// </summary>
		private void CloseDownload( object state )
		{
			this._IsClosing = true;

			// Goto sleep and let the background thread do its work
			Thread.Sleep( 100 );

			// If the download thread is running, abort the thread
			if( null != this._DownloadThread )
			{
				if( this._DownloadThread.IsAlive )
				{
					this._DownloadThread.Abort();
					this._DownloadThread = null;
				}
			}

		}

		/// <summary>
		/// Perform post download tasks
		/// </summary>
		private void DownloadCompleted()
		{
			// Add an item in the list view which says download complete
			ListViewItem completeItem = new ListViewItem( "Complete." );
			this.progressView.Items.Add( completeItem );
			this.progressView.SelectedItems.Clear();
			completeItem.EnsureVisible();
			completeItem.Selected = true;
			
			base.Refresh();

			// Raise event to notify downloaded feeds
			if( null != DownloadComplete )
			{
				DownloadComplete( null, new DownloadEventArgs( this._Channels, this._RssChannels ) );
			}		

			// Update the title of the form with "Complete" status
			if( !Configuration.Instance.SilentMode )
			{
				base.Text = "Complete.";
				base.Activate();
				base.TopMost = true;
			}

			// Notify user what happened
			if( this._Channels.Count == this._RssChannels.Count )
			{
				if( !Configuration.Instance.NoBaloonPopup )
					RSSServer.TrayNotifyIcon.ShowBalloon( "RSS Feeder", "Feed synchronization complete", NotifyIcon.BalloonTip.NotifyInfoFlags.Info, 2000 );
			}
			else
			{
				if( !Configuration.Instance.NoBaloonPopup )
					RSSServer.TrayNotifyIcon.ShowBalloon( "RSS Feeder", "Not all channels were downloaded successfully.", NotifyIcon.BalloonTip.NotifyInfoFlags.Warning, 10000 );
			}

			this.closeTimer.Start();
		}

		/// <summary>
		/// Synchronize outlook.
		/// </summary>
		private int SyncOutlook( Channel channel )
		{
			int itemsAdded = 0;

			if( !Configuration.Instance.SyncWithOutlook ) return 0;
			if( null == this._Session ) return 0;
			if( this._IsClosing ) return 0;
			
			// We will go for outlook only when the outlook folder is outdated
			if( channel.LastUpdatedInOutlook < channel.LastUpdated )
			{
				object folder = null;
			
				// Reset the auto folder type to determine outlook folder name from current title of the
				// channel
				if( channel.FolderType == Channel.FolderCreationTypeEnum.Automatic_under_Base_Folder )
					channel.FolderType = Channel.FolderCreationTypeEnum.Automatic_under_Base_Folder;

				#region Log
				EntLibHelper.Debug("SyncOutlook", "Ensuring folder: " + channel.FolderPath);
				#endregion

				// Ensure that the destination folder exists. If not, create it
				// Cross your finger, this takes a lot of time
				folder = OutlookHelper.EnsureFolderPath( this._Session, channel.FolderPath );

				if( null == folder )
				{
					throw new ApplicationException("Cannot create folder:" + channel.FolderPath);
				}
				else
				{
					try
					{
						// Ensure whether proper view has been set for the folder. 
						if( 0 == channel.OutlookViewXmlPath.Length )
							OutlookHelper.EnsureView( folder, Configuration.Instance.OutlookViewXmlPath );
						else
							OutlookHelper.EnsureView( folder, channel.OutlookViewXmlPath );
					}
					catch( Exception x )
					{
						EntLibHelper.Exception( channel.Title, x );
					}
				
					// Get items not added in outlook yet
					IList rssItems = DatabaseHelper.GetRssItemsPendingForOutlook( channel.Id );

					// Get reference to folder items because we need to add new posts
					object folderItems = OutlookHelper.GetProperty( folder, "Items" );

					// Get the XSL used to transform RSS Feeds in this channel
					string xsltFileName = channel.OutlookXSL;
					if( 0 == xsltFileName.Length )
						xsltFileName = Configuration.Instance.OutlookXSL;

					// Create a string buffer to hold transformed items
					MemoryStream stream = new MemoryStream( 10 * 1024 );
					StreamReader reader = new StreamReader( stream, System.Text.Encoding.ASCII );
							
					foreach( RssFeed item in rssItems )
					{
						// Create outlook item
						object post = OutlookHelper.CallMethod( folderItems, "Add", 6 ); //Outlook.OlItemType.olPostItem = 6
						
						//post.CreationTime = item.PublishDate;
						OutlookHelper.SetProperty( post, "Subject", item.Title );

						// Clear stream
						stream.Position = 0;
						stream.SetLength(0);

						// Transform RSS item XML to HTML
						XMLHelper.TransformXml( xsltFileName, item.XML, stream );
							
						// Read the content from the stream
						stream.Position = 0;
						string html = reader.ReadToEnd();

						#region Bypass
						/*
						// replace the ##DESCRIPTION## with actual description of the post
						// XSL parser does not decode the HTML content inside the description node. It keeps
						// it as it is. So, we need to manually put the description without relying on the XSL
						// parser
						XmlDocument doc = new XmlDocument();
						doc.LoadXml( item.XML );

						// Choose the best content node, first priority is "content:encoded". Then "content"
						// and then "description"
						XmlElement contentNode = null;
						foreach( XmlNode node in doc.DocumentElement.ChildNodes )
						{
							if( node.Name == "content:encoded" )
							{
								contentNode = node as XmlElement;
								break;
							}
							else if( node.Name == "content" )
							{
								contentNode = node as XmlElement;								
							}
							else if( node.Name == "description" )
							{
								contentNode = node as XmlElement;
							}
						}
						
						if( null != contentNode )
						{
							string description = contentNode.InnerText;
							html = html.Replace( "##DESCRIPTION##", description );
						}
						*/
						#endregion

						OutlookHelper.SetProperty( post, "HTMLBody", html );
						OutlookHelper.SetProperty( post, "BodyFormat", 2 ); // Outlook.OlBodyFormat.olFormatHTML = 2

						// Store the actual item XML in an user property

						object userProperties = OutlookHelper.GetProperty( post, "UserProperties" );	
						object missing = System.Reflection.Missing.Value;
						object xmlProperty = OutlookHelper.CallMethod( userProperties, "Add", "XML", 1, missing, missing );
						// Outlook.OlUserPropertyType.olText = 1
						Marshal.ReleaseComObject( userProperties );

						OutlookHelper.SetProperty( xmlProperty, "Value", item.XML );
						OutlookHelper.SetProperty( post, "UnRead", 1 );

						OutlookHelper.CallMethod( post, "Post" ); 

						Marshal.ReleaseComObject( post );

						itemsAdded ++;
					}

					Marshal.ReleaseComObject( folderItems );
					Marshal.ReleaseComObject( folder );

					folderItems = null;
					folder = null;

					// Update channel properties for outlook
					channel.LastUpdatedInOutlook = DateTime.Now;
					DatabaseHelper.UpdateChannel( channel );

					// Mark as all items are in outlook now
					DatabaseHelper.RssInOutlook( channel.Id );

					#region Log
					EntLibHelper.Debug(channel.Title, " is in outlook now");
					#endregion
				}
			}

			return itemsAdded;
		}

		private string ExtractDescription( string nodeStart, string nodeEnd, string xml )
		{
			int beginPos = xml.IndexOf(nodeStart);

			if( beginPos < 0 ) return string.Empty;

			int startPos = beginPos + nodeStart.Length;

			int endPos = xml.LastIndexOf(nodeEnd);

			return xml.Substring( startPos, endPos - startPos );
		}

		/// <summary>
		/// Populate the progress list view with channel names
		/// </summary>
		private void PopulateListWithChannels()
		{
			progressView.Items.Clear();

			foreach( Channel channel in this._Channels )
			{
				ListViewItem item = new ListViewItem( new string [] { channel.Title, "", channel.FeedURL.ToString() } );
				item.Tag = channel;
				item.ImageIndex = 0;
				progressView.Items.Add( item );
			}
		}

		private void DisposeOutlook()
		{
			try
			{
				if( null != this._Session )
				{
					Marshal.ReleaseComObject( this._Session );				
				}
				if( null != this._Application )
				{
					Marshal.ReleaseComObject( this._Application );
				}

				this._Session = null;
				this._Application = null;
			}
			catch
			{
			}

			// Run garbage collector to free of all .NET handles to COM objects
			GC.Collect();
		}

		#endregion

		#region Control Events
		
		private void closeTimer_Tick(object sender, System.EventArgs e)
		{
			this.closeTimer.Stop();
			this.Close();
		}

		private void FeedDownloader_Closing(object sender, System.ComponentModel.CancelEventArgs e)
		{
			this.CloseDownload( null );		
			
			// Dispose outlook if it was running
			this.DisposeOutlook();

		}

		private void FeedDownloader_Load(object sender, System.EventArgs e)
		{
			this.PopulateListWithChannels();

			Rectangle rScreen = Screen.GetWorkingArea (Screen.PrimaryScreen.WorkingArea);
			base.Top = rScreen.Bottom - base.Height;
			base.Left = rScreen.Width - Width - 11;
		}

		private void timer_Tick(object sender, System.EventArgs e)
		{
			startTimer.Enabled = false;
			this.StartDownload();
		}

		private void progressView_DoubleClick(object sender, System.EventArgs e)
		{
			// 1. Get the selected item from list view
			if( 0 == progressView.SelectedItems.Count ) return;
			ListViewItem item = progressView.SelectedItems[0];

			// 2. Get the channel and show it
			Channel channel = item.Tag as Channel;
			EntryPoint.ShowMainForm( channel.Id );
		}

		
		private void viewStatusMenuItem_Click(object sender, System.EventArgs e)
		{
			this.ViewStatus();
		}

		private void visitChannelMenuItem_Click(object sender, System.EventArgs e)
		{
			this.VisitChannel();
		}

		#endregion

	}
}

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 Code Project Open License (CPOL)

Share

About the Author

Omar Al Zabir
Architect BT, UK (ex British Telecom)
United Kingdom United Kingdom

| Advertise | Privacy | Terms of Use | Mobile
Web01 | 2.8.150327.1 | Last Updated 16 Aug 2005
Article Copyright 2005 by Omar Al Zabir
Everything else Copyright © CodeProject, 1999-2015
Layout: fixed | fluid