diff --git a/NzbDrone.Core.Test/NzbDrone.Core.Test.csproj b/NzbDrone.Core.Test/NzbDrone.Core.Test.csproj
index 7c1333c11..9ec3f0fa0 100644
--- a/NzbDrone.Core.Test/NzbDrone.Core.Test.csproj
+++ b/NzbDrone.Core.Test/NzbDrone.Core.Test.csproj
@@ -60,6 +60,7 @@
+
diff --git a/NzbDrone.Core.Test/SabControllerTest.cs b/NzbDrone.Core.Test/SabControllerTest.cs
new file mode 100644
index 000000000..fe061bb79
--- /dev/null
+++ b/NzbDrone.Core.Test/SabControllerTest.cs
@@ -0,0 +1,39 @@
+using System;
+using System.Collections.Generic;
+using System.Text;
+using Gallio.Framework;
+using log4net;
+using MbUnit.Framework;
+using MbUnit.Framework.ContractVerifiers;
+using Moq;
+using NzbDrone.Core.Repository;
+using NzbDrone.Core.Controllers;
+using SubSonic.Repository;
+
+namespace NzbDrone.Core.Test
+{
+ [TestFixture]
+ public class SabControllerTest
+ {
+ [Test]
+ public void AddByUrl()
+ {
+ //Setup
+ String key = "SabnzbdInfo";
+ String value = "192.168.5.55:2222";
+
+ var repo = new Mock();
+ var config = new Mock();
+ config.Setup(c => c.SetValue("SabnzbdInfo", "192.168.5.55:2222"));
+
+ //var config = new Config() { Key = key, Value = value };
+ var target = new SabController(config.Object, new Mock().Object);
+
+ //Act
+ bool result = target.AddByUrl("http://www.nzbclub.com/nzb_download.aspx?mid=1950232");
+
+ //Assert
+ Assert.AreEqual(true, result);
+ }
+ }
+}
diff --git a/NzbDrone.Core/Controllers/IDownloadClientController.cs b/NzbDrone.Core/Controllers/IDownloadClientController.cs
index cf996005a..ea3fb5ffc 100644
--- a/NzbDrone.Core/Controllers/IDownloadClientController.cs
+++ b/NzbDrone.Core/Controllers/IDownloadClientController.cs
@@ -2,12 +2,13 @@
using System.Collections.Generic;
using System.Linq;
using System.Text;
+using NzbDrone.Core.Repository;
namespace NzbDrone.Core.Controllers
{
public interface IDownloadClientController
{
- string AddByUrl(string url); //Should accept something other than string (NzbInfo?) returns result if applicable
- bool IsInQueue(string goodName);//Should accept something other than string (Episode?) returns bool
+ bool AddByUrl(ItemInfo nzb); //Should accept something other than string (NzbInfo?) returns success or failure
+ bool IsInQueue(Episode episode);//Should accept something other than string (Episode?) returns bool
}
}
diff --git a/NzbDrone.Core/Controllers/SabController.cs b/NzbDrone.Core/Controllers/SabController.cs
index 409d2e066..ff36455e7 100644
--- a/NzbDrone.Core/Controllers/SabController.cs
+++ b/NzbDrone.Core/Controllers/SabController.cs
@@ -1,37 +1,108 @@
using System;
using System.Collections.Generic;
using System.Linq;
+using System.Net;
using System.Text;
+using log4net;
+using System.Xml.Linq;
+using System.Xml;
+using NzbDrone.Core.Repository;
namespace NzbDrone.Core.Controllers
{
public class SabController : IDownloadClientController
{
private readonly IConfigController _config;
+ private readonly ILog _logger;
- public SabController(IConfigController config)
+ public SabController(IConfigController config, ILog logger)
{
_config = config;
+ _logger = logger;
}
- public string AddByUrl(string url)
+ #region IDownloadClientController Members
+
+ public bool AddByUrl(ItemInfo nzb)
+ {
+ const string mode = "addurl";
+ const string cat = "tv";
+ string name = nzb.Link.ToString().Replace("&", "%26");
+ string nzbName = CleanUrlString(nzb.Title);
+
+ string action = string.Format("mode={0}&name={1}&cat={2}&nzbname={3}", mode, name, cat, nzbName);
+ string request = GetSabRequest(action);
+
+ _logger.DebugFormat("Adding report [{0}] to the queue.", nzbName);
+
+ if (SendRequest(request) == "ok")
+ return true;
+
+ return false;
+ }
+
+ public bool IsInQueue(Episode epsiode)
{
-
+ string action = "mode=queue&output=xml";
- return "";
+ XDocument xDoc = XDocument.Load(action);
+
+ //If an Error Occurred, retuyrn
+ if (xDoc.Descendants("error").Count() != 0)
+ return false;
+
+ if (xDoc.Descendants("queue").Count() == 0)
+ return false;
+
+ //Get the Count of Items in Queue where 'filename' is Equal to goodName, if not zero, return true (isInQueue)
+ if ((from s in xDoc.Descendants("slot") where s.Element("filename").ToString().Equals(epsiode.FeedItem.TitleFix, StringComparison.InvariantCultureIgnoreCase) select s).Count() != 0)
+ {
+ _logger.DebugFormat("Episode in queue - '{0}'", epsiode.FeedItem.TitleFix);
+
+ return true;
+ }
+
+ return false; //Not in Queue
}
- public string AddByPath(string path)
+ #endregion
+
+ private string GetSabRequest(string action)
{
+ string sabnzbdInfo = _config.GetValue("SabnzbdInfo", String.Empty, false);
+ string username = _config.GetValue("Username", String.Empty, false);
+ string password = _config.GetValue("Password", String.Empty, false);
+ string apiKey = _config.GetValue("ApiKey", String.Empty, false);
+ string priority = _config.GetValue("Priority", String.Empty, false);
- return "";
+ return string.Format(
+ @"http://{0}/sabnzbd/api?$Action&priority={1}&apikey={2}&ma_username={3}&ma_password={4}",
+ sabnzbdInfo, priority, apiKey, username, password).Replace("$Action", action);
}
+ private string SendRequest(string request)
+ {
+ var webClient = new WebClient();
+ string response = webClient.DownloadString(request).Replace("\n", String.Empty);
+ _logger.DebugFormat("Queue Repsonse: [{0}]", response);
+ return response;
+ }
- public bool IsInQueue(string goodName)
+ private string CleanUrlString(string name)
{
+ string result = name;
+ string[] badCharacters =
+ {
+ "%", "<", ">", "#", "{", "}", "|", "\\", "^", "`", "[", "]", "`", ";", "/", "?",
+ ":", "@", "=", "&", "$"
+ };
+ string[] goodCharacters =
+ {
+ "%25", "%3C", "%3E", "%23", "%7B", "%7D", "%7C", "%5C", "%5E", "%7E", "%5B",
+ "%5D", "%60", "%3B", "%2F", "%3F", "%3A", "%40", "%3D", "%26", "%24"
+ };
- return false;
+ return result.Trim();
}
}
}
diff --git a/NzbDrone.Core/NzbDrone.Core.csproj b/NzbDrone.Core/NzbDrone.Core.csproj
index 393b1637b..57a52338c 100644
--- a/NzbDrone.Core/NzbDrone.Core.csproj
+++ b/NzbDrone.Core/NzbDrone.Core.csproj
@@ -130,6 +130,8 @@
+
+
diff --git a/NzbDrone.Core/Repository/Episode.cs b/NzbDrone.Core/Repository/Episode.cs
new file mode 100644
index 000000000..2de356d68
--- /dev/null
+++ b/NzbDrone.Core/Repository/Episode.cs
@@ -0,0 +1,127 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Text.RegularExpressions;
+
+namespace NzbDrone.Core.Repository
+{
+ public class Episode
+ {
+ //Information about the current episode being processed
+
+ private const string EpisodePattern = @"
+(?.*)
+(?:
+ s(?\d+)e(?\d+)-?e(?\d+)
+| s(?\d+)e(?\d+)
+| (?\d+)x(?\d+)
+| (?\d{4}.\d{2}.\d{2})
+)
+(?:
+ (?.*?)
+ (?
+ (?:hdtv|pdtv|xvid|ws|720p|x264|bdrip|dvdrip|dsr|proper)
+ .*)
+| (?.*)
+)
+";
+ public FeedItem FeedItem { get; set; }
+ public string ShowName { get; set; }
+ public string EpisodeName { get; set; }
+ public string EpisodeName2 { get; set; }
+ public int SeasonNumber { get; set; }
+ public int EpisodeNumber { get; set; }
+ public int EpisodeNumber2 { get; set; }
+ public DateTime AirDate { get; set; }
+ public string Release { get; set; }
+ public int Quality { get; set; }
+
+ public bool IsProper
+ {
+ get { return FeedItem.Title.Contains("PROPER"); }
+ }
+
+ public bool IsDaily
+ {
+ get { return AirDate != DateTime.MinValue; }
+ }
+
+ public bool IsMulti
+ {
+ get { return SeasonNumber != 0 && EpisodeNumber != 0 && EpisodeNumber2 != 0; }
+ }
+
+ public string EpisodeTitle
+ {
+ get { return IsDaily ? GetDailyEpisode() : IsMulti ? GetMultiEpisode() : GetSeasonEpisode(); }
+ }
+
+ public string Title
+ {
+ get { return string.Format("{0} - {1}", ShowName, EpisodeTitle); }
+ }
+
+ public override string ToString()
+ {
+ return string.Format("{0} - {1}", ShowName, EpisodeTitle);
+ }
+
+ private string GetDailyEpisode()
+ {
+ return AirDate.ToString("yyyy-MM-dd");
+ }
+
+ private string GetMultiEpisode()
+ {
+ return string.Format("{0}x{1:D2}-{0}x{2:D2}", SeasonNumber, EpisodeNumber, EpisodeNumber2);
+ }
+
+ private string GetSeasonEpisode()
+ {
+ return string.Format("{0}x{1:D2}", SeasonNumber, EpisodeNumber);
+ }
+
+ public static Episode Parse(FeedItem feedItem)
+ {
+ Match match = Regex.Match(feedItem.Title, EpisodePattern,
+ RegexOptions.IgnorePatternWhitespace | RegexOptions.IgnoreCase);
+
+ if (!match.Success)
+ return null;
+
+ return new Episode
+ {
+ ShowName = ReplaceSeparatorChars(match.Groups["showName"].Value),
+ SeasonNumber = ParseInt(match.Groups["seasonNumber"].Value),
+ EpisodeNumber = ParseInt(match.Groups["episodeNumber"].Value),
+ EpisodeNumber2 = ParseInt(match.Groups["episodeNumber2"].Value),
+ EpisodeName = ReplaceSeparatorChars(match.Groups["episodeName"].Value),
+ Release = ReplaceSeparatorChars(match.Groups["release"].Value)
+ };
+ }
+
+ private static string ReplaceSeparatorChars(string s)
+ {
+ if (s == null) return string.Empty;
+ return s.Replace('.', ' ').Replace('-', ' ').Replace('_', ' ').Trim();
+ }
+
+ private static int ParseInt(string s)
+ {
+ int i;
+ int.TryParse(s, out i);
+ return i;
+ }
+
+ private static DateTime ParseAirDate(string s)
+ {
+ DateTime d;
+ if (DateTime.TryParse(ReplaceSeparatorChars(s).Replace(' ', '-'), out d))
+ return d;
+ return DateTime.MinValue;
+ }
+
+
+ }
+}
diff --git a/NzbDrone.Core/Repository/FeedItem.cs b/NzbDrone.Core/Repository/FeedItem.cs
new file mode 100644
index 000000000..d3b84da2c
--- /dev/null
+++ b/NzbDrone.Core/Repository/FeedItem.cs
@@ -0,0 +1,16 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+
+namespace NzbDrone.Core.Repository
+{
+ public class FeedItem
+ {
+ //Item from the NZB Feed waiting tp be processed.
+ public string Description { get; set; }
+ public string Title { get; set; }
+ public string TitleFix { get; set; }
+ public string NzbId { get; set; }
+ }
+}
diff --git a/NzbDrone.Core/Repository/ItemInfo.cs b/NzbDrone.Core/Repository/ItemInfo.cs
new file mode 100644
index 000000000..6482203f4
--- /dev/null
+++ b/NzbDrone.Core/Repository/ItemInfo.cs
@@ -0,0 +1,21 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+
+namespace NzbDrone.Core.Repository
+{
+ public class ItemInfo
+ {
+ public string Id { get; set; }
+ public string Title { get; set; }
+ public Site Site { get; set; }
+ public Uri Link { get; set; }
+ public string Description { get; set; }
+
+ public bool IsPassworded()
+ {
+ return Title.EndsWith("(Passworded)", StringComparison.InvariantCultureIgnoreCase);
+ }
+ }
+}
diff --git a/NzbDrone.Core/Repository/Site.cs b/NzbDrone.Core/Repository/Site.cs
new file mode 100644
index 000000000..8c53795b6
--- /dev/null
+++ b/NzbDrone.Core/Repository/Site.cs
@@ -0,0 +1,36 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Text.RegularExpressions;
+
+namespace NzbDrone.Core.Repository
+{
+ public class Site
+ {
+ private static readonly IList Sites = new List
+ {
+ new Site {Name = "nzbmatrix", Url = "nzbmatrix.com", Pattern = @"\d{6,10}"},
+ new Site {Name = "nzbsDotOrg", Url = "nzbs.org", Pattern = @"\d{5,10}"},
+ new Site {Name = "nzbsrus", Url = "nzbsrus.com", Pattern = @"\d{6,10}"},
+ new Site {Name = "lilx", Url = "lilx.net", Pattern = @"\d{6,10}"},
+ };
+
+ public string Name { get; set; }
+ public string Pattern { get; set; }
+ public string Url { get; set; }
+
+ // TODO: use HttpUtility.ParseQueryString();
+ // https://nzbmatrix.com/api-nzb-download.php?id=626526
+ public string ParseId(string url)
+ {
+ return Regex.Match(url, Pattern).Value;
+ }
+
+ public static Site Parse(string url)
+ {
+ return Sites.Where(site => url.Contains(site.Url)).SingleOrDefault() ??
+ new Site { Name = "unknown", Pattern = @"\d{6,10}" };
+ }
+ }
+}