using System; using System.Collections.Generic; using System.Globalization; using System.IO; using System.ServiceModel.Syndication; using System.Text.RegularExpressions; using NLog; using NzbDrone.Core.Parser.Model; namespace NzbDrone.Core.Indexers { public interface IParseFeed { IEnumerable Process(Stream source); } public class BasicRssParser : IParseFeed { private readonly Logger _logger; public BasicRssParser() { _logger = LogManager.GetCurrentClassLogger(); } public IEnumerable Process(Stream source) { //TODO: replace this BS with plain Linq to XML var reader = new SyndicationFeedXmlReader(source); var feed = SyndicationFeed.Load(reader).Items; var result = new List(); foreach (var syndicationItem in feed) { try { var parsedEpisode = ParseFeed(syndicationItem); if (parsedEpisode != null) { parsedEpisode.NzbUrl = GetNzbUrl(syndicationItem); parsedEpisode.NzbInfoUrl = GetNzbInfoUrl(syndicationItem); result.Add(parsedEpisode); } } catch (Exception itemEx) { itemEx.Data.Add("Item", syndicationItem.Title); _logger.ErrorException("An error occurred while processing feed item", itemEx); } } return result; } protected virtual string GetTitle(SyndicationItem syndicationItem) { return syndicationItem.Title.Text; } protected virtual string GetNzbUrl(SyndicationItem item) { return item.Links[0].Uri.ToString(); } protected virtual string GetNzbInfoUrl(SyndicationItem item) { return String.Empty; } protected virtual ReportInfo PostProcessor(SyndicationItem item, ReportInfo currentResult) { return currentResult; } private ReportInfo ParseFeed(SyndicationItem item) { var title = GetTitle(item); var reportInfo = new ReportInfo(); reportInfo.Title = title; reportInfo.Age = DateTime.Now.Date.Subtract(item.PublishDate.Date).Days; reportInfo.ReleaseGroup = ParseReleaseGroup(title); _logger.Trace("Parsed: {0} from: {1}", reportInfo, item.Title.Text); return PostProcessor(item, reportInfo); } public static string ParseReleaseGroup(string title) { title = title.Trim(); var index = title.LastIndexOf('-'); if (index < 0) index = title.LastIndexOf(' '); if (index < 0) return String.Empty; var group = title.Substring(index + 1); if (@group.Length == title.Length) return String.Empty; return @group; } private static readonly Regex[] HeaderRegex = new[] { new Regex(@"(?:\[.+\]\-\[.+\]\-\[.+\]\-\[)(?.+)(?:\]\-.+)", RegexOptions.IgnoreCase | RegexOptions.Compiled), new Regex(@"(?:\[.+\]\W+\[.+\]\W+\[.+\]\W+\"")(?.+)(?:\"".+)", RegexOptions.IgnoreCase | RegexOptions.Compiled), new Regex(@"(?:\[)(?.+)(?:\]\-.+)", RegexOptions.IgnoreCase | RegexOptions.Compiled), }; public static string ParseHeader(string header) { foreach (var regex in HeaderRegex) { var match = regex.Matches(header); if (match.Count != 0) return match[0].Groups["nzbTitle"].Value.Trim(); } return header; } private static readonly Regex ReportSizeRegex = new Regex(@"(?\d+\.\d{1,2}|\d+\,\d+\.\d{1,2}|\d+)\W?(?GB|MB|GiB|MiB)", RegexOptions.IgnoreCase | RegexOptions.Compiled); public static long GetReportSize(string sizeString) { var match = ReportSizeRegex.Matches(sizeString); if (match.Count != 0) { var cultureInfo = new CultureInfo("en-US"); var value = Decimal.Parse(Regex.Replace(match[0].Groups["value"].Value, "\\,", ""), cultureInfo); var unit = match[0].Groups["unit"].Value; if (unit.Equals("MB", StringComparison.InvariantCultureIgnoreCase) || unit.Equals("MiB", StringComparison.InvariantCultureIgnoreCase)) { return ConvertToBytes(Convert.ToDouble(value), 2); } if (unit.Equals("GB", StringComparison.InvariantCultureIgnoreCase) || unit.Equals("GiB", StringComparison.InvariantCultureIgnoreCase)) { return ConvertToBytes(Convert.ToDouble(value), 3); } } return 0; } private static long ConvertToBytes(double value, int power) { var multiplier = Math.Pow(1024, power); var result = value*multiplier; return Convert.ToInt64(result); } } }