/// /// 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 RssBanditImporter : IFeedImporter, IFeedElementParser { private const string _progressMessage = "Importing RSS Bandit subscriptions"; private const string _progressMessageCache = "Importing RSS Bandit cache"; private const string _rbNS = "http://www.25hoursaday.com/2003/RSSBandit/feeds/"; private string _subscriptionPath = null; private string _flaggedPath = null; private string _cachePath = null; private bool _parseCache = true; private struct FeedInfo { internal string url; internal string cacheFile; internal ArrayList readItems; internal IResource feed; } private ArrayList _importedFeeds = null; private string _currentURL = null; private IResource _currentFlag = null; private Hashtable _flagsMap = new Hashtable(); private IResource _defaultFlag = null; private RSSPlugin _plugin = RSSPlugin.GetInstance(); public RssBanditImporter() { bool RssBanditFound = false; string basePath = Environment.GetFolderPath( Environment.SpecialFolder.ApplicationData ); string banditPath = Path.Combine( basePath, @"RssBandit" ); _subscriptionPath = Path.Combine( banditPath, "subscriptions.xml" ); if( File.Exists( _subscriptionPath ) ) { RssBanditFound = true; } if( ! RssBanditFound ) { _subscriptionPath = Path.Combine( banditPath, "feedlist.xml" ); if( File.Exists( _subscriptionPath ) ) { RssBanditFound = true; } } if( ! RssBanditFound ) { // don't build additional data structures return; } _plugin.RegisterFeedImporter( "RSS Bandit", this ); // Calculate cache dir _cachePath = Path.Combine( banditPath , "Cache" ); // Try find flagged feed _flaggedPath = Path.Combine( banditPath, "flagitems.xml" ); if( ! File.Exists( _flaggedPath ) ) { // No stream with flags, Ok. _flaggedPath = null; } // Build flags hash _defaultFlag = Core.ResourceStore.FindUniqueResource( "Flag", "FlagId", "RedFlag" ); _flagsMap[ "FollowUp" ] = Core.ResourceStore.FindUniqueResource( "Flag", "FlagId", "RedFlag" ); _flagsMap[ "Review" ] = Core.ResourceStore.FindUniqueResource( "Flag", "FlagId", "YellowFlag" ); _flagsMap[ "Read" ] = Core.ResourceStore.FindUniqueResource( "Flag", "FlagId", "GreenFlag" ); _flagsMap[ "Forward" ] = Core.ResourceStore.FindUniqueResource( "Flag", "FlagId", "BlueFlag" ); _flagsMap[ "Complete" ] = Core.ResourceStore.FindUniqueResource( "Flag", "FlagId", "CompletedFlag" ); _flagsMap[ "Reply" ] = Core.ResourceStore.FindUniqueResource( "Flag", "FlagId", "BlueFlag" ); } /// /// 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 ) { IResource feedRes = null; importRoot = _plugin.FindOrCreateGroup( "RssBandit 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( "RssBandit subscrption load failed: '" + ex.Message + "'" ); RemoveFeedsAndGroupsAction.DeleteFeedGroup( importRoot ); ImportUtils.ReportError( "RSS Bandit Subscription Import", "Import of RSS Bandit subscription failed:\n" + ex.Message ); return; } ImportUtils.FeedUpdateData defaultUpdatePeriod; XmlAttribute period = feedlist.SelectSingleNode( "/feeds/@refresh-rate" ) as XmlAttribute; if( period != null ) { defaultUpdatePeriod = ImportUtils.ConvertUpdatePeriod( period.Value, 60000 ); } else { defaultUpdatePeriod = ImportUtils.ConvertUpdatePeriod( "", 60000 ); } XmlNodeList feeds = feedlist.GetElementsByTagName( "feed" ); int totalFeeds = Math.Max( feeds.Count, 1 ); int processedFeeds = 0; ImportUtils.UpdateProgress( processedFeeds / totalFeeds, _progressMessage ); foreach( XmlElement feed in feeds ) { string s = ImportUtils.GetUniqueChildText( feed, "link" ); 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; IResource group = AddCategory( importRoot, feed.GetAttribute( "category" ) ); // Ok, now we should create feed feedRes = Core.ResourceStore.NewResource( "RSSFeed" ); feedRes.BeginUpdate(); feedRes.SetProp( "URL", s ); s = ImportUtils.GetUniqueChildText( feed, "title" ); ImportUtils.Child2Prop( feed, "title", feedRes, Core.Props.Name, Props.OriginalName ); ImportUtils.Child2Prop( feed, "etag", feedRes, Props.ETag ); s = ImportUtils.GetUniqueChildText( feed, "last-retrieved" ); if( s != null ) { DateTime dt = DateTime.Parse( s ); feedRes.SetProp( "LastUpdateTime", dt ); } // Peridoically ImportUtils.FeedUpdateData upd; s = ImportUtils.GetUniqueChildText( feed, "refresh-rate" ); if( s != null ) { upd = ImportUtils.ConvertUpdatePeriod( s, 60000 ); } else { upd = defaultUpdatePeriod; } feedRes.SetProp( "UpdatePeriod", upd.period ); feedRes.SetProp( "UpdateFrequency", upd.freq ); // Cached? s = ImportUtils.GetUniqueChildText( feed, "cacheurl" ); if( s != null ) { info.cacheFile = s; } else { info.cacheFile = null; } // Login & Password ImportUtils.Child2Prop( feed, "auth-user", feedRes, Props.HttpUserName ); s = ImportUtils.GetUniqueChildText( feed, "auth-password" ); if( s != null ) { feedRes.SetProp( Props.HttpPassword, DecryptPassword( s ) ); } // Enclosures ImportUtils.Child2Prop( feed, "enclosure-folder", feedRes, Props.EnclosurePath ); // Try to load "read" list XmlElement read = ImportUtils.GetUniqueChild( feed, "stories-recently-viewed" ); if( read != null ) { ArrayList list = new ArrayList(); foreach( XmlElement story in read.GetElementsByTagName( "story" ) ) { list.Add( story.InnerText ); } if( list.Count > 0 ) { info.readItems = list; } else { info.readItems = null; } } // Feed is ready feedRes.AddLink( Core.Props.Parent, group ); feedRes.EndUpdate(); info.feed = feedRes; _importedFeeds.Add( info ); if( addToWorkspace ) { Core.WorkspaceManager.AddToActiveWorkspace( feedRes ); } processedFeeds += 100; ImportUtils.UpdateProgress( processedFeeds / totalFeeds, _progressMessage ); } return; } /// /// Import cached items, flags, etc. /// public void DoImportCache() { Stream stream = null; RSSParser parser = null; // Register our item processor _plugin.RegisterItemElementParser( FeedType.Rss, _rbNS, "flag-status", this ); _plugin.RegisterItemElementParser( FeedType.Atom, _rbNS, "flag-status", this ); ImportUtils.UpdateProgress( 0, _progressMessageCache ); int totalFeeds = Math.Max( _importedFeeds.Count, 1 ); int processedFeeds = 0; // Ok, all feeds are created. Try to import all caches and mark read items _parseCache = true; foreach( FeedInfo fi in _importedFeeds ) { ImportUtils.UpdateProgress( processedFeeds / totalFeeds, _progressMessageCache ); processedFeeds += 100; if( fi.feed.IsDeleted ) { continue; } if( null == fi.cacheFile ) { continue; } string path = Path.Combine( _cachePath, fi.cacheFile ); if( ! File.Exists( path ) ) { continue; } try { // Load feed! parser = new RSSParser( fi.feed ); using( stream = new FileStream( path, FileMode.Open, FileAccess.Read ) ) { parser.Parse( stream, Encoding.UTF8, true ); } } catch( Exception ex ) { Trace.WriteLine( "RSS Bandit cache '" + path + "' load failed: '" + ex.Message + "'" ); } if( fi.readItems != null ) { // And mark as read IResourceList allItems = fi.feed.GetLinksTo( "RSSItem", "From" ); foreach( string readOne in fi.readItems ) { IResourceList markAsRead = Core.ResourceStore.FindResources( "RSSItem", "GUID", readOne ).Intersect( allItems, true ); foreach( IResource item in markAsRead ) { item.DeleteProp( Core.Props.IsUnread ); } } } } ImportUtils.UpdateProgress( processedFeeds / totalFeeds, _progressMessageCache ); // And here we should import flags from flags stream if( _flaggedPath == null ) { return; } // Register two additional elements _plugin.RegisterItemElementParser( FeedType.Rss, _rbNS, "feed-url", this ); _plugin.RegisterItemElementParser( FeedType.Atom, _rbNS, "feed-url", this ); _parseCache = false; IResource pseudoFeed = Core.ResourceStore.NewResourceTransient( "RSSFeed" ); try { parser = new RSSParser( pseudoFeed ); parser.ItemParsed += this.FlaggedItemParsed; using( stream = new FileStream( _flaggedPath, FileMode.Open, FileAccess.Read ) ) { parser.Parse( stream, Encoding.UTF8, true ); } } catch( Exception ex ) { Trace.WriteLine( "RssBandit flags load failed: '" + ex.Message + "'" ); } pseudoFeed.Delete(); } /// /// Parses the value for the element. /// /// The resource in which the element data should be stored /// (of type RSSFeed for channel element parsers or RSSItem for item element /// parsers). /// The reader positioned after the starting tag of the element. public void ParseValue( IResource resource, XmlReader reader ) { if( _parseCache ) { if( reader.LocalName == "flag-status" ) { string flagName = reader.ReadElementString(); IResource flag; if( _flagsMap.ContainsKey( flagName ) ) { flag = (IResource) _flagsMap[ flagName ]; } else { flag = _defaultFlag; } resource.AddLink( "Flag", flag ); } } else { if( reader.LocalName == "flag-status" ) { string flagName = reader.ReadElementString(); if( _flagsMap.ContainsKey( flagName ) ) { _currentFlag = (IResource) _flagsMap[ flagName ]; } else { _currentFlag = _defaultFlag; } } else if( reader.LocalName == "feed-url" ) { _currentURL = reader.ReadElementString(); } } } /// /// Checks if a Read() call is needed to move to the next element after /// is completed. /// public bool SkipNextRead { get { return true; } } private IResource AddCategory( IResource parent, string groupname ) { string[] path = groupname.Split( '\\' ); for( int i = 0; i < path.Length; ++i ) { parent = _plugin.FindOrCreateGroup( path[i], parent ); } return parent; } public void FlaggedItemParsed( object sender, ResourceEventArgs e ) { if( _currentURL != null && _currentFlag != null ) { // Search for feed IResource feed = null; foreach( FeedInfo fi in _importedFeeds ) { if( fi.url == _currentURL ) { feed = fi.feed; break; } } if( feed != null ) { // IResource item = null; // Get item by feed and e.resource! // item.AddLink( "Flag", _currentFlag ); } } _currentURL = null; _currentFlag = null; // We don't need this one e.Resource.Delete(); } private static byte[] CalcPasswordKey() { string salt = "NewsComponents.4711"; byte[] b = Encoding.Unicode.GetBytes(salt); int bLen = b.GetLength(0); Random r = new Random(1500450271); byte[] res = new Byte[500]; int i = 0; for( i = 0; i < bLen && i < 500; ++i ) { res[i] = (byte)( b[i] ^ r.Next( 30, 127 ) ); } while( i < 500 ) { res[ i++ ] = (byte)r.Next( 30, 127 ); } return new MD5CryptoServiceProvider().ComputeHash(res); } private static string DecryptPassword( string password ) { byte[] base64; byte[] bytes; string ret; TripleDESCryptoServiceProvider des = new TripleDESCryptoServiceProvider(); des.Key = CalcPasswordKey(); des.Mode = CipherMode.ECB; try { base64 = Convert.FromBase64String( password ); bytes = des.CreateDecryptor().TransformFinalBlock(base64, 0, base64.GetLength(0)); ret = Encoding.Unicode.GetString(bytes); } catch { ret = String.Empty; } return ret; } } }