/// /// Copyright © 2003-2008 JetBrains s.r.o. /// You may distribute under the terms of the GNU General Public License, as published by the Free Software Foundation, version 2 (see License.txt in the repository root folder). /// using System; using System.Collections; using System.Diagnostics; using System.IO; using System.Security.Cryptography; using System.Text; using System.Xml; using JetBrains.Omea.OpenAPI; namespace JetBrains.Omea.RSSPlugin { /// /// Summary description for Bloglines. /// internal class SharpReaderImporter : IFeedImporter { private const string _progressMessage = "Importing SharpReader subscriptions"; private const string _progressMessageCache = "Importing SharpReader cache"; private static readonly string[] _flagsIDs = new string[] { "CompletedFlag", // 0 - BLACK !! "RedFlag", // 1 - RED "GreenFlag", // 2 - GREEN "PurpleFlag", // 3 - PURPLE "BlueFlag", // 4 - BLUE "RedFlag", // 5 - PINK !! "RedFlag", // 6 - DARK RED !! "BlueFlag", // 7 - DARK BLUE !! "PurpleFlag", // 8 - DARK PURPLE !! "OrangeFlag" // 9 - ORANGE }; private string _subscriptionPath = null; private string _cachePath = null; private struct FeedInfo { internal string url; internal string cacheFile; internal IResource feed; } private ArrayList _importedFeeds = null; private Hashtable _flagsMap = new Hashtable(); private IResource _defaultFlag = null; private RSSPlugin _plugin = RSSPlugin.GetInstance(); public SharpReaderImporter() { bool SharpReaderFound = false; string basePath = Environment.GetFolderPath( Environment.SpecialFolder.ApplicationData ); string sharpPath = Path.Combine( basePath, @"SharpReader" ); _subscriptionPath = Path.Combine( sharpPath, "subscriptions.xml" ); if( File.Exists( _subscriptionPath ) ) { SharpReaderFound = true; } if( ! SharpReaderFound ) { // don't build additional data structures return; } _plugin.RegisterFeedImporter( "SharpReader", this ); // Calculate cache dir _cachePath = Path.Combine( sharpPath , "Cache" ); // Build flags hash _defaultFlag = Core.ResourceStore.FindUniqueResource( "Flag", "FlagId", "RedFlag" ); for( int i = 0; i < _flagsIDs.Length; ++i ) { _flagsMap[ i.ToString() ] = Core.ResourceStore.FindUniqueResource( "Flag", "FlagId", _flagsIDs[i] ); } } /// /// Check if importer needs configuration before import starts. /// public bool HasSettings { get { return false; } } /// /// Returns creator of options pane. /// public OptionsPaneCreator GetSettingsPaneCreator() { return null; } /// /// Import subscription /// public void DoImport( IResource importRoot, bool addToWorkspace ) { importRoot = _plugin.FindOrCreateGroup( "SharpReader subscriptions", importRoot ); // We will add info about imported feeds here _importedFeeds = new ArrayList(); ImportUtils.UpdateProgress( 0, _progressMessage ); // Start to import feeds structure XmlDocument feedlist = new XmlDocument(); try { feedlist.Load( _subscriptionPath ); } catch(Exception ex) { Trace.WriteLine( "SharpReader subscrption load failed: '" + ex.Message + "'" ); RemoveFeedsAndGroupsAction.DeleteFeedGroup( importRoot ); ImportUtils.ReportError( "SharpReader Subscription Import", "Import of SharpReader subscription failed:\n" + ex.Message ); return; } ImportUtils.FeedUpdateData defaultUpdatePeriod; XmlAttribute period = feedlist.SelectSingleNode( "/feeds/@refreshMinutes" ) as XmlAttribute; if( period != null ) { defaultUpdatePeriod = ImportUtils.ConvertUpdatePeriod( period.Value, 1 ); } else { defaultUpdatePeriod = ImportUtils.ConvertUpdatePeriod( "", 1 ); } ImportUtils.UpdateProgress( 10, _progressMessage ); XmlElement root = (XmlElement)feedlist.SelectSingleNode( "/feeds" ); ImportGroup( root, importRoot, defaultUpdatePeriod, addToWorkspace ); ImportUtils.UpdateProgress( 100, _progressMessage ); return; } /// /// Import cached items, flags, etc. /// public void DoImportCache() { int totalFeeds = Math.Max( _importedFeeds.Count, 1 ); int processedFeeds = 0; // Import cache foreach( FeedInfo fi in _importedFeeds ) { ImportUtils.UpdateProgress( processedFeeds / totalFeeds, _progressMessageCache ); processedFeeds += 100; if( fi.feed.IsDeleted ) { continue; } string path = Path.Combine( _cachePath, fi.cacheFile ); if( ! File.Exists( path ) ) { continue; } XmlDocument feed = new XmlDocument(); try { feed.Load( path ); } catch(Exception ex) { Trace.WriteLine( "SharpReader cache '" + path + "' load failed: '" + ex.Message + "'" ); continue; } // Load info from feed string s = null; ImportUtils.InnerText2Prop( feed.SelectSingleNode( "/rss/Description" ) as XmlElement, fi.feed, Props.Description ); ImportUtils.InnerText2Prop( feed.SelectSingleNode( "/rss/WebPageUrl" ) as XmlElement, fi.feed, Props.HomePage ); ImportUtils.InnerText2Prop( feed.SelectSingleNode( "/rss/Image/Title" ) as XmlElement, fi.feed, Props.ImageTitle ); ImportUtils.InnerText2Prop( feed.SelectSingleNode( "/rss/Image/Url" ) as XmlElement, fi.feed, Props.ImageURL ); ImportUtils.InnerText2Prop( feed.SelectSingleNode( "/rss/Image/Link" ) as XmlElement, fi.feed, Props.ImageLink ); // Load items from feed FeedAuthorParser authParser = new FeedAuthorParser(); foreach( XmlElement item in feed.SelectNodes( "/rss/Items" ) ) { IResource feedItem = Core.ResourceStore.BeginNewResource( "RSSItem" ); ImportUtils.Child2Prop( item, "Title", feedItem, Core.Props.Subject ); ImportUtils.Child2Prop( item, "Description", feedItem, Core.Props.LongBody ); ImportUtils.Child2Prop( item, "Link", feedItem, Props.Link ); ImportUtils.Child2Prop( item, "Guid", feedItem, Props.GUID ); feedItem.SetProp( Core.Props.LongBodyIsHTML, true ); ImportUtils.Child2Prop( item, "CommentsUrl", feedItem, Props.CommentURL ); ImportUtils.Child2Prop( item, "CommentsRss", feedItem, Props.CommentRSS ); ImportUtils.Child2Prop( item, "Subject", feedItem, Props.RSSCategory ); s = ImportUtils.GetUniqueChildText( item, "CommentCount" ); if( s != null ) { try { feedItem.SetProp( Props.CommentCount, Int32.Parse( s ) ); } catch( FormatException ) { Trace.WriteLine( "SharpReader cache: invalid comment-count" ); } catch( OverflowException ) { Trace.WriteLine( "SharpReader cache: invalid comment-count" ); } } // Date s = ImportUtils.GetUniqueChildText( item, "PubDate" ); if( s != null ) { DateTime dt = DateTime.Parse( s ); feedItem.SetProp( Core.Props.Date, dt ); } // Read/unread s = ImportUtils.GetUniqueChildText( item, "IsRead" ); if( s == null || s != "true" ) { feedItem.SetProp( Core.Props.IsUnread, true ); } // Flag s = ImportUtils.GetUniqueChildText( item, "Flag" ); if( s != null ) { IResource flag = null; if( _flagsMap.ContainsKey( s ) ) { flag = (IResource) _flagsMap[ s ]; } else { flag = _defaultFlag; } feedItem.AddLink( "Flag", flag ); } // Author s = ImportUtils.GetUniqueChildText( item, "Author" ); if( s != null ) { authParser.ParseAuthorString( feedItem, s ); } else { feedItem.AddLink( Core.ContactManager.Props.LinkFrom, fi.feed ); } feedItem.EndUpdate(); fi.feed.AddLink( Props.RSSItem, feedItem ); } } ImportUtils.UpdateProgress( processedFeeds / totalFeeds, _progressMessageCache ); } private void ImportGroup( XmlElement root, IResource rootGroup, ImportUtils.FeedUpdateData defaultUpdatePeriod, bool addToWorkspace ) { // Import all groups XmlNodeList l = root.SelectNodes( "RssFeedsCategory" ); if( l != null ) { foreach( XmlElement group in l ) { string name = group.GetAttribute( "name" ); if( name == null ) { name = "Unknown group"; } IResource iGroup = _plugin.FindOrCreateGroup( name, rootGroup ); ImportGroup( group, iGroup, defaultUpdatePeriod, addToWorkspace ); } } // Import all elements IResource feedRes = null; l = root.SelectNodes( "RssFeed" ); if( l != null ) { foreach( XmlElement feed in l ) { string s = feed.GetAttribute( "url" ); if( s == null ) { continue; } // May be, we are already subscribed? if( Core.ResourceStore.FindUniqueResource( "RSSFeed", "URL", s ) != null ) { continue; } FeedInfo info = new FeedInfo(); info.url = s; // Ok, now we should create feed feedRes = Core.ResourceStore.NewResource( "RSSFeed" ); feedRes.BeginUpdate(); feedRes.SetProp( "URL", s ); ImportUtils.Attrib2Prop( feed, "name", feedRes, Core.Props.Name, Props.OriginalName ); ImportUtils.Attrib2Prop( feed, "etag", feedRes, Props.ETag ); ImportUtils.Attrib2Prop( feed, "authUserName", feedRes, Props.HttpUserName ); s = feed.GetAttribute( "authPassword" ); if( s != null ) { string sharpReaderPassword = "VeRyToPsEcReTpAsSwOrDHuShHuShDoN'tTeLlAnYoNe"; MD5CryptoServiceProvider MD5 = new MD5CryptoServiceProvider(); TripleDESCryptoServiceProvider DES = new TripleDESCryptoServiceProvider(); DES.Key = MD5.ComputeHash( Encoding.ASCII.GetBytes( sharpReaderPassword ) ); DES.Mode = CipherMode.ECB; byte[] DESed = Convert.FromBase64String( s ); byte[] DeDESed = DES.CreateDecryptor().TransformFinalBlock( DESed, 0, DESed.Length); // Huuray! feedRes.SetProp( Props.HttpPassword, Encoding.Unicode.GetString( DeDESed ) ); } s = feed.GetAttribute( "lastRefresh" ); if( s != null ) { DateTime dt = DateTime.Parse( s ); feedRes.SetProp( Props.LastUpdateTime, dt ); } // Peridoically ImportUtils.FeedUpdateData upd; s = feed.GetAttribute( "refreshMinutes" ); if( s != null ) { upd = ImportUtils.ConvertUpdatePeriod( s, 1 ); } else { upd = defaultUpdatePeriod; } feedRes.SetProp( Props.UpdatePeriod, upd.period ); feedRes.SetProp( Props.UpdateFrequency, upd.freq ); // Cached? info.cacheFile = GetCacheNameByURL( info.url ); // Feed is ready feedRes.AddLink( Core.Props.Parent, rootGroup ); feedRes.EndUpdate(); info.feed = feedRes; _importedFeeds.Add( info ); if( addToWorkspace ) { Core.WorkspaceManager.AddToActiveWorkspace( feedRes ); } } } } private string GetCacheNameByURL( string url ) { string name = url; int pos = name.IndexOf("://"); if( pos >= 0 ) { name = name.Substring( pos + 3 ); } name = name.Replace('/', '-').Replace('\\', '-').Replace(':', '-') .Replace('?', '-').Replace('*', '-').Replace('"', '-') .Replace('<', '-').Replace('>', '-').Replace('|', '-'); if( ! name.EndsWith(".xml") && ! name.EndsWith(".XML") ) { name = name + ".xml"; } if( name.Length > 128 ) { name = md5_hex( url.ToLower() ); } return name; } private readonly static char[] _hexDigits = new char[ 16 ] { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f' }; private string md5_hex( string s ) { byte[] md5 = new MD5CryptoServiceProvider().ComputeHash( Encoding.ASCII.GetBytes( s ) ); StringBuilder res = new StringBuilder( md5.Length * 2 ); for( int i = 0; i < md5.Length; ++i ) { res.Append( _hexDigits[ md5[i] >> 4 ] ); res.Append( _hexDigits[ md5[i] & 0x0F ] ); } return res.ToString(); } } }