Start of AutoConfigureSab

pull/4/head
Mark McDowall 14 years ago
parent ea2e520632
commit a34bd818cf

1
.gitignore vendored

@ -32,3 +32,4 @@ _ReSharper*/
/[Pp]ackage/ /[Pp]ackage/
#NZBDrone specific #NZBDrone specific
*.db *.db
*Web.Publish.xml

@ -0,0 +1,15 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace NzbDrone.Core.Model
{
public class SabnzbdInfoModel
{
public string ApiKey { get; set; }
public int Port { get; set; }
public string Username { get; set; }
public string Password { get; set; }
}
}

@ -166,6 +166,8 @@
<Compile Include="Instrumentation\SubsonicTarget.cs" /> <Compile Include="Instrumentation\SubsonicTarget.cs" />
<Compile Include="Instrumentation\ExceptioneerTarget.cs" /> <Compile Include="Instrumentation\ExceptioneerTarget.cs" />
<Compile Include="Instrumentation\NlogWriter.cs" /> <Compile Include="Instrumentation\NlogWriter.cs" />
<Compile Include="Model\SabnzbdInfoModel.cs" />
<Compile Include="Providers\AutoConfigureProvider.cs" />
<Compile Include="Providers\Indexer\NzbMatrixProvider.cs" /> <Compile Include="Providers\Indexer\NzbMatrixProvider.cs" />
<Compile Include="Providers\Jobs\NewSeriesUpdate.cs" /> <Compile Include="Providers\Jobs\NewSeriesUpdate.cs" />
<Compile Include="Providers\Jobs\JobProvider.cs" /> <Compile Include="Providers\Jobs\JobProvider.cs" />

@ -1,290 +0,0 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Text.RegularExpressions;
using NLog;
using NzbDrone.Core.Model;
using NzbDrone.Core.Repository.Quality;
namespace NzbDrone.Core
{
public static class Parser
{
private static readonly Logger Logger = LogManager.GetCurrentClassLogger();
private static readonly Regex[] ReportTitleRegex = new[]
{
new Regex(@"^(?<title>.+?)?\W?(?<year>\d{4}?)?\W+(?<airyear>\d{4})\W+(?<airmonth>\d{2})\W+(?<airday>\d{2})\W?(?!\\)",
RegexOptions.IgnoreCase | RegexOptions.Compiled),
new Regex(@"^(?<title>.*?)?(?:\W?S?(?<season>\d{1,2}(?!\d+))(?:(?:\-|\.|[ex]|\s|to)+(?<episode>\d{1,2}(?!\d+)))+)+\W?(?!\\)",
RegexOptions.IgnoreCase | RegexOptions.Compiled),
new Regex(@"^(?<title>.+?)?\W?(?<year>\d{4}?)?(?:\W(?<season>\d+)(?<episode>\d{2}))+\W?(?!\\)",
RegexOptions.IgnoreCase | RegexOptions.Compiled),
//Supports 103/113 naming
new Regex(@"^(?<title>.*?)?(?:\W?S?(?<season>\d{1,2}(?!\d+))(?:(?:\-|\.|[ex]|\s|to)+(?<episode>\d+))+)+\W?(?!\\)",
RegexOptions.IgnoreCase | RegexOptions.Compiled)
};
private static readonly Regex[] SeasonReportTitleRegex = new[]
{
new Regex(
@"(?<title>.+?)?\W?(?<year>\d{4}?)?\W(?:S|Season)?\W?(?<season>\d+)(?!\\)",
RegexOptions.IgnoreCase |
RegexOptions.Compiled),
};
private static readonly Regex NormalizeRegex = new Regex(@"((^|\W)(a|an|the|and|or|of)($|\W))|\W|\b(?!(?:19\d{2}|20\d{2}))\d+\b",
RegexOptions.IgnoreCase | RegexOptions.Compiled);
/// <summary>
/// Parses a post title into list of episodes it contains
/// </summary>
/// <param name = "title">Title of the report</param>
/// <returns>List of episodes contained to the post</returns>
internal static EpisodeParseResult ParseEpisodeInfo(string title)
{
Logger.Trace("Parsing string '{0}'", title);
foreach (var regex in ReportTitleRegex)
{
var simpleTitle = Regex.Replace(title, @"480[i|p]|720[i|p]|1080[i|p]|[x|h]264", String.Empty, RegexOptions.IgnoreCase | RegexOptions.Compiled);
var match = regex.Matches(simpleTitle);
if (match.Count != 0)
{
var seriesName = NormalizeTitle(match[0].Groups["title"].Value);
var airyear = 0;
Int32.TryParse(match[0].Groups["airyear"].Value, out airyear);
EpisodeParseResult parsedEpisode;
if (airyear < 1 )
{
var season = 0;
Int32.TryParse(match[0].Groups["season"].Value, out season);
parsedEpisode = new EpisodeParseResult
{
Proper = title.ToLower().Contains("proper"),
CleanTitle = seriesName,
SeasonNumber = season,
Episodes = new List<int>()
};
foreach (Match matchGroup in match)
{
var count = matchGroup.Groups["episode"].Captures.Count;
var first = Convert.ToInt32(matchGroup.Groups["episode"].Captures[0].Value);
var last = Convert.ToInt32(matchGroup.Groups["episode"].Captures[count - 1].Value);
for (int i = first; i <= last; i++)
{
parsedEpisode.Episodes.Add(i);
}
}
}
else
{
//Try to Parse as a daily show
if (airyear > 0)
{
var airmonth = Convert.ToInt32(match[0].Groups["airmonth"].Value);
var airday = Convert.ToInt32(match[0].Groups["airday"].Value);
parsedEpisode = new EpisodeParseResult
{
Proper = title.ToLower().Contains("proper"),
CleanTitle = seriesName,
AirDate = new DateTime(airyear, airmonth, airday)
};
}
//Something went wrong with this one... return null
else
return null;
}
parsedEpisode.Quality = ParseQuality(title);
Logger.Trace("Episode Parsed. {0}", parsedEpisode);
return parsedEpisode;
}
}
Logger.Warn("Unable to parse text into episode info. {0}", title);
return null;
}
/// <summary>
/// Parses a post title into season it contains
/// </summary>
/// <param name = "title">Title of the report</param>
/// <returns>Season information contained in the post</returns>
internal static SeasonParseResult ParseSeasonInfo(string title)
{
Logger.Trace("Parsing string '{0}'", title);
foreach (var regex in ReportTitleRegex)
{
var match = regex.Matches(title);
if (match.Count != 0)
{
var seriesName = NormalizeTitle(match[0].Groups["title"].Value);
int year;
Int32.TryParse(match[0].Groups["year"].Value, out year);
if (year < 1900 || year > DateTime.Now.Year + 1)
{
year = 0;
}
var seasonNumber = Convert.ToInt32(match[0].Groups["season"].Value);
var result = new SeasonParseResult
{
SeriesTitle = seriesName,
SeasonNumber = seasonNumber,
Year = year,
Quality = ParseQuality(title)
};
Logger.Trace("Season Parsed. {0}", result);
return result;
}
}
return null; //Return null
}
/// <summary>
/// Parses a post title to find the series that relates to it
/// </summary>
/// <param name = "title">Title of the report</param>
/// <returns>Normalized Series Name</returns>
internal static string ParseSeriesName(string title)
{
Logger.Trace("Parsing string '{0}'", title);
foreach (var regex in ReportTitleRegex)
{
var match = regex.Matches(title);
if (match.Count != 0)
{
var seriesName = NormalizeTitle(match[0].Groups["title"].Value);
Logger.Trace("Series Parsed. {0}", seriesName);
return seriesName;
}
}
return String.Empty;
}
/// <summary>
/// Parses proper status out of a report title
/// </summary>
/// <param name = "title">Title of the report</param>
/// <returns></returns>
internal static bool ParseProper(string title)
{
return title.ToLower().Contains("proper");
}
internal static QualityTypes ParseQuality(string name)
{
Logger.Trace("Trying to parse quality for {0}", name);
var result = QualityTypes.Unknown;
name = name.ToLowerInvariant();
if (name.Contains("dvd"))
return QualityTypes.DVD;
if (name.Contains("bdrip") || name.Contains("brrip"))
{
return QualityTypes.BDRip;
}
if (name.Contains("xvid") || name.Contains("divx"))
{
if (name.Contains("bluray"))
{
return QualityTypes.BDRip;
}
return QualityTypes.TV;
}
if (name.Contains("bluray"))
{
if (name.Contains("720p"))
return QualityTypes.Bluray720;
if (name.Contains("1080p"))
return QualityTypes.Bluray1080;
return QualityTypes.Bluray720;
}
if (name.Contains("web-dl"))
return QualityTypes.WEBDL;
if (name.Contains("x264") || name.Contains("h264") || name.Contains("720p"))
return QualityTypes.HDTV;
//Based on extension
if (result == QualityTypes.Unknown)
{
switch (new FileInfo(name).Extension.ToLower())
{
case ".avi":
case ".xvid":
case ".wmv":
{
result = QualityTypes.TV;
break;
}
case ".mkv":
{
result = QualityTypes.HDTV;
break;
}
}
}
Logger.Trace("Quality Parsed:{0} Title:", result, name);
return result;
}
/// <summary>
/// Normalizes the title. removing all non-word characters as well as common tokens
/// such as 'the' and 'and'
/// </summary>
/// <param name = "title">title</param>
/// <returns></returns>
public static string NormalizeTitle(string title)
{
return NormalizeRegex.Replace(title, String.Empty).ToLower();
}
public static string NormalizePath(string path)
{
if (String.IsNullOrWhiteSpace(path))
throw new ArgumentException("Path can not be null or empty");
var info = new FileInfo(path);
if (info.FullName.StartsWith(@"\\")) //UNC
{
return info.FullName.TrimEnd('/', '\\', ' ');
}
return info.FullName.Trim('/', '\\', ' ');
}
}
}

@ -0,0 +1,67 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using NzbDrone.Core.Model;
using NzbDrone.Core.Providers.Core;
namespace NzbDrone.Core.Providers
{
public class AutoConfigureProvider
{
private HttpProvider _httpProvider;
private ConfigProvider _configProvider;
public AutoConfigureProvider(HttpProvider httpProvider, ConfigProvider configProvider)
{
_httpProvider = httpProvider;
_configProvider = configProvider;
}
public SabnzbdInfoModel AutoConfigureSab(string username, string password)
{
//Get Output from Netstat
var netStatOutput = String.Empty;
//var port = GetSabnzbdPort(netStatOutput);
var port = 2222;
var apiKey = GetSabnzbdApiKey(port);
if (port > 0 && !String.IsNullOrEmpty(apiKey))
{
return new SabnzbdInfoModel
{
ApiKey = apiKey,
Port = port,
Username = username,
Password = password
};
}
return null;
}
private int GetSabnzbdPort(string netstatOutput)
{
Regex regex = new Regex(@"^(?:TCP\W+127.0.0.1:(?<port>\d+\W+).+?\r\n\W+\[sabnzbd.exe\])", RegexOptions.IgnoreCase
| RegexOptions.Compiled);
var match = regex.Match(netstatOutput);
var port = 0;
Int32.TryParse(match.Groups["port"].Value, out port);
return port;
}
private string GetSabnzbdApiKey(int port, string ipAddress = "127.0.0.1")
{
var request = String.Format("http://{0}:{1}/config/general/", ipAddress, port);
var result = _httpProvider.DownloadString(request);
Regex regex = new Regex("\\<input\\Wtype\\=\\\"text\\\"\\Wid\\=\\\"apikey\\\"\\Wvalue\\=\\\"(?<apikey>\\w+)\\W", RegexOptions.IgnoreCase
| RegexOptions.Compiled);
var match = regex.Match(result);
return match.Groups["apikey"].Value;
}
}
}

@ -61,13 +61,13 @@ namespace NzbDrone.Core.Providers.Indexer
/// </summary> /// </summary>
public void Fetch() public void Fetch()
{ {
_logger.Info("Fetching feeds from " + Settings.Name); _logger.Debug("Fetching feeds from " + Settings.Name);
foreach (var url in Urls) foreach (var url in Urls)
{ {
try try
{ {
_logger.Debug("Downloading RSS " + url); _logger.Trace("Downloading RSS " + url);
var feed = SyndicationFeed.Load(_httpProvider.DownloadXml(url)).Items; var feed = SyndicationFeed.Load(_httpProvider.DownloadXml(url)).Items;
foreach (var item in feed) foreach (var item in feed)

@ -78,7 +78,6 @@ namespace NzbDrone.Core.Providers.Jobs
try try
{ {
Logger.Trace("Getting list of jobs needing to be executed");
var pendingJobs = All().Where( var pendingJobs = All().Where(
t => t.Enable && t => t.Enable &&
@ -114,16 +113,14 @@ namespace NzbDrone.Core.Providers.Jobs
{ {
if (_isRunning) if (_isRunning)
{ {
Logger.Info("Another instance of this job is already running. Ignoring request."); Logger.Info("Another job is already running. Ignoring request.");
return false; return false;
} }
_isRunning = true; _isRunning = true;
} }
Logger.Info("User has requested a manual execution of {0}", jobType.Name);
if (_jobThread == null || !_jobThread.IsAlive) if (_jobThread == null || !_jobThread.IsAlive)
{ {
Logger.Debug("Initializing background thread"); Logger.Trace("Initializing background thread");
ThreadStart starter = () => ThreadStart starter = () =>
{ {
@ -170,7 +167,7 @@ namespace NzbDrone.Core.Providers.Jobs
{ {
try try
{ {
Logger.Info("Starting job '{0}'. Last execution {1}", settings.Name, settings.LastExecution); Logger.Debug("Starting job '{0}'. Last execution {1}", settings.Name, settings.LastExecution);
settings.LastExecution = DateTime.Now; settings.LastExecution = DateTime.Now;
var sw = Stopwatch.StartNew(); var sw = Stopwatch.StartNew();
@ -180,7 +177,7 @@ namespace NzbDrone.Core.Providers.Jobs
settings.Success = true; settings.Success = true;
sw.Stop(); sw.Stop();
Logger.Info("Job '{0}' successfully completed in {1} seconds", timerClass.Name, sw.Elapsed.Minutes, Logger.Debug("Job '{0}' successfully completed in {1} seconds", timerClass.Name, sw.Elapsed.Minutes,
sw.Elapsed.Seconds); sw.Elapsed.Seconds);
} }
catch (Exception e) catch (Exception e)
@ -201,7 +198,7 @@ namespace NzbDrone.Core.Providers.Jobs
/// </summary> /// </summary>
public virtual void Initialize() public virtual void Initialize()
{ {
Logger.Info("Initializing jobs. Count {0}", _jobs.Count()); Logger.Debug("Initializing jobs. Count {0}", _jobs.Count());
var currentTimer = All(); var currentTimer = All();
foreach (var timer in _jobs) foreach (var timer in _jobs)

@ -24,14 +24,17 @@ namespace NzbDrone.Web.Controllers
private readonly IndexerProvider _indexerProvider; private readonly IndexerProvider _indexerProvider;
private readonly QualityProvider _qualityProvider; private readonly QualityProvider _qualityProvider;
private readonly RootDirProvider _rootDirProvider; private readonly RootDirProvider _rootDirProvider;
private readonly AutoConfigureProvider _autoConfigureProvider;
public SettingsController(ConfigProvider configProvider, IndexerProvider indexerProvider, public SettingsController(ConfigProvider configProvider, IndexerProvider indexerProvider,
QualityProvider qualityProvider, RootDirProvider rootDirProvider) QualityProvider qualityProvider, RootDirProvider rootDirProvider,
AutoConfigureProvider autoConfigureProvider)
{ {
_configProvider = configProvider; _configProvider = configProvider;
_indexerProvider = indexerProvider; _indexerProvider = indexerProvider;
_qualityProvider = qualityProvider; _qualityProvider = qualityProvider;
_rootDirProvider = rootDirProvider; _rootDirProvider = rootDirProvider;
_autoConfigureProvider = autoConfigureProvider;
} }
public ActionResult Index(string viewName) public ActionResult Index(string viewName)
@ -276,6 +279,30 @@ namespace NzbDrone.Web.Controllers
return new JsonResult { Data = "ok" }; return new JsonResult { Data = "ok" };
} }
public JsonResult AutoConfigureSab(string username, string password)
{
SabnzbdInfoModel info;
try
{
//info = _autoConfigureProvider.AutoConfigureSab(username, password);
info = new SabnzbdInfoModel
{
ApiKey = "123456",
Port = 2222
};
}
catch (Exception)
{
return new JsonResult { Data = "failed" };
}
return Json(info);
}
[HttpPost] [HttpPost]
public ActionResult SaveGeneral(SettingsModel data) public ActionResult SaveGeneral(SettingsModel data)
{ {

File diff suppressed because it is too large Load Diff

@ -94,6 +94,8 @@
<fieldset class="sub-field"> <fieldset class="sub-field">
<legend>SABnzbd</legend> <legend>SABnzbd</legend>
<button type="button" onclick="autoConfigureSab()">Auto-Configure</button>
<div class="config-section"> <div class="config-section">
<div class="config-group"> <div class="config-group">
<div class="config-title">@Html.LabelFor(m => m.SabHost)</div> <div class="config-title">@Html.LabelFor(m => m.SabHost)</div>
@ -173,3 +175,25 @@
} }
<div id="result"></div> <div id="result"></div>
<script type="text/javascript">
var autoConfigureSabUrl = '@Url.Action("AutoConfigureSab", "Settings")';
function autoConfigureSab() {
$.ajax({
type: "POST",
url: autoConfigureSabUrl,
data: jQuery.param({ username: $('#SabUsername').val(), password: $('#SabPassword').val() }),
error: function (req, status, error) {
alert("Sorry! We could not autoconfigure SABnzbd for you");
},
success: autoConfigureSuccess
});
function autoConfigureSuccess(data) {
$('#SabApiKey').val(data.ApiKey);
$('#SabPort').val(data.Port);
$('#SabUsername').val(data.Username);
$('#SabPassword').val(data.Password);
}
}
</script>

@ -44,7 +44,8 @@ namespace NzbDrone
IISProcess.StartInfo.CreateNoWindow = true; IISProcess.StartInfo.CreateNoWindow = true;
IISProcess.OutputDataReceived += (OnDataReceived); IISProcess.OutputDataReceived += (OnOutputDataReceived);
IISProcess.ErrorDataReceived += (OnErrorDataReceived);
//Set Variables for the config file. //Set Variables for the config file.
Environment.SetEnvironmentVariable("NZBDRONE_PATH", Config.ProjectRoot); Environment.SetEnvironmentVariable("NZBDRONE_PATH", Config.ProjectRoot);
@ -60,6 +61,9 @@ namespace NzbDrone
Logger.Info("Starting process. [{0}]", IISProcess.StartInfo.FileName); Logger.Info("Starting process. [{0}]", IISProcess.StartInfo.FileName);
IISProcess.Start(); IISProcess.Start();
IISProcess.BeginErrorReadLine(); IISProcess.BeginErrorReadLine();
@ -73,6 +77,14 @@ namespace NzbDrone
return IISProcess; return IISProcess;
} }
private static void OnErrorDataReceived(object sender, DataReceivedEventArgs e)
{
if (e == null || String.IsNullOrWhiteSpace(e.Data))
return;
IISLogger.Error(e.Data);
}
internal static void StopServer() internal static void StopServer()
{ {
KillProcess(IISProcess); KillProcess(IISProcess);
@ -82,7 +94,7 @@ namespace NzbDrone
{ {
string processPath = process.MainModule.FileName; string processPath = process.MainModule.FileName;
Logger.Info("[{0}]IIS Process found. Path:{1}", process.Id, processPath); Logger.Info("[{0}]IIS Process found. Path:{1}", process.Id, processPath);
if (CleanPath(processPath) == CleanPath(IISExe)) if (NormalizePath(processPath) == NormalizePath(IISExe))
{ {
Logger.Info("[{0}]Process is considered orphaned.", process.Id); Logger.Info("[{0}]Process is considered orphaned.", process.Id);
KillProcess(process); KillProcess(process);
@ -124,7 +136,7 @@ namespace NzbDrone
} }
} }
private static void OnDataReceived(object s, DataReceivedEventArgs e) private static void OnOutputDataReceived(object s, DataReceivedEventArgs e)
{ {
if (e == null || String.IsNullOrWhiteSpace(e.Data) || e.Data.StartsWith("Request started:") || if (e == null || String.IsNullOrWhiteSpace(e.Data) || e.Data.StartsWith("Request started:") ||
e.Data.StartsWith("Request ended:") || e.Data == ("IncrementMessages called")) e.Data.StartsWith("Request ended:") || e.Data == ("IncrementMessages called"))
@ -167,9 +179,19 @@ namespace NzbDrone
} }
} }
private static string CleanPath(string path) public static string NormalizePath(string path)
{
if (String.IsNullOrWhiteSpace(path))
throw new ArgumentException("Path can not be null or empty");
var info = new FileInfo(path);
if (info.FullName.StartsWith(@"\\")) //UNC
{ {
return path.ToLower().Replace("\\", "").Replace("//", "//"); return info.FullName.TrimEnd('/', '\\', ' ');
}
return info.FullName.Trim('/', '\\', ' ').ToLower();
} }

@ -13,6 +13,7 @@
<TargetFrameworkVersion>v4.0</TargetFrameworkVersion> <TargetFrameworkVersion>v4.0</TargetFrameworkVersion>
<FileAlignment>512</FileAlignment> <FileAlignment>512</FileAlignment>
<TargetFrameworkProfile /> <TargetFrameworkProfile />
<IsWebBootstrapper>false</IsWebBootstrapper>
<PublishUrl>publish\</PublishUrl> <PublishUrl>publish\</PublishUrl>
<Install>true</Install> <Install>true</Install>
<InstallFrom>Disk</InstallFrom> <InstallFrom>Disk</InstallFrom>
@ -25,7 +26,6 @@
<MapFileExtensions>true</MapFileExtensions> <MapFileExtensions>true</MapFileExtensions>
<ApplicationRevision>0</ApplicationRevision> <ApplicationRevision>0</ApplicationRevision>
<ApplicationVersion>1.0.0.%2a</ApplicationVersion> <ApplicationVersion>1.0.0.%2a</ApplicationVersion>
<IsWebBootstrapper>false</IsWebBootstrapper>
<UseApplicationTrust>false</UseApplicationTrust> <UseApplicationTrust>false</UseApplicationTrust>
<BootstrapperEnabled>true</BootstrapperEnabled> <BootstrapperEnabled>true</BootstrapperEnabled>
</PropertyGroup> </PropertyGroup>

@ -21,6 +21,7 @@ namespace NzbDrone
AppDomain.CurrentDomain.UnhandledException += ((s, e) => AppDomainException(e)); AppDomain.CurrentDomain.UnhandledException += ((s, e) => AppDomainException(e));
AppDomain.CurrentDomain.ProcessExit += ProgramExited; AppDomain.CurrentDomain.ProcessExit += ProgramExited;
AppDomain.CurrentDomain.DomainUnload += ProgramExited; AppDomain.CurrentDomain.DomainUnload += ProgramExited;
Process.GetCurrentProcess().EnableRaisingEvents = true;
Process.GetCurrentProcess().Exited += ProgramExited; Process.GetCurrentProcess().Exited += ProgramExited;
Config.ConfigureNlog(); Config.ConfigureNlog();

Loading…
Cancel
Save