using System; using System.Collections.Generic; using System.Globalization; using System.IO; using System.Linq; using System.Text.RegularExpressions; using System.Xml.Linq; using NLog; using NzbDrone.Core.Parser.Model; namespace NzbDrone.Core.Indexers { public interface IParseFeed { IEnumerable Process(Stream source, string url); } public class BasicRssParser : IParseFeed { private readonly Logger _logger; public BasicRssParser() { _logger = LogManager.GetCurrentClassLogger(); } public IEnumerable Process(Stream source, string url) { var document = XDocument.Load(source); var items = document.Descendants("item"); var result = new List(); foreach (var item in items) { try { var reportInfo = ParseFeedItem(item); if (reportInfo != null) { reportInfo.NzbUrl = GetNzbUrl(item); reportInfo.NzbInfoUrl = GetNzbInfoUrl(item); result.Add(reportInfo); } } catch (Exception itemEx) { itemEx.Data.Add("Item", item.Title()); _logger.ErrorException("An error occurred while processing feed item from " + url, itemEx); } } return result; } protected virtual string GetTitle(XElement item) { return item.Title(); } protected virtual string GetNzbUrl(XElement item) { return item.Links().First(); } protected virtual string GetNzbInfoUrl(XElement item) { return String.Empty; } protected virtual ReportInfo PostProcessor(XElement item, ReportInfo currentResult) { return currentResult; } private ReportInfo ParseFeedItem(XElement 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()); 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), new Regex(@"(?:\[.+\]\W+\[.+\]\W+\[.+\]\W+\"")(?.+)(?:\"".+)", RegexOptions.IgnoreCase), new Regex(@"(?:\[)(?.+)(?:\]\-.+)", RegexOptions.IgnoreCase), }; 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); } } }