Compare commits

...

11 Commits

Author SHA1 Message Date
bakerboy448
dd27d69e97 fix erroneous logging for windows service on non-windows 2021-12-10 03:14:23 -06:00
Robin Dadswell
32fd0911a2 New: Application Placeholders instead of default values 2021-12-09 15:19:11 -06:00
Robin Dadswell
0e6ec58a83 New: Placeholders in notification fields 2021-12-09 15:19:11 -06:00
Robin Dadswell
69f5963f6f New: Frontend Placeholders from the Backend 2021-12-09 15:19:11 -06:00
PearsonFlyer
6ca708f523 Fixed: (HDTorrents) Remove . from searches 2021-12-08 14:26:46 -06:00
ta264
9e7af8369e Fixed: Support older glibc in libMonoPosixHelper 2021-12-08 19:14:25 +00:00
Qstick
b05d8c930d Date Routines Test Cases 2021-12-07 20:19:05 -06:00
Qstick
6b886b938c New: Better Fuzzy DateTime Parse 2021-12-07 18:48:46 -06:00
ta264
4a7bf39723 Fixed: Speed up parsing DateTime 2021-12-06 21:57:22 +00:00
bakerboy448
7fcd320e23 Fixed: (PrivateHD) Drop support for IMDB search
imdb search does not support S/E params
this matches the behavior for TorrentLeech
Fixes #119
2021-12-06 21:12:37 -06:00
Qstick
88677ce236 Bump to 0.1.8 2021-12-06 17:19:04 -06:00
24 changed files with 570 additions and 64 deletions

View File

@@ -7,7 +7,7 @@ variables:
outputFolder: './_output'
artifactsFolder: './_artifacts'
testsFolder: './_tests'
majorVersion: '0.1.7'
majorVersion: '0.1.8'
minorVersion: $[counter('minorVersion', 1)]
prowlarrVersion: '$(majorVersion).$(minorVersion)'
buildName: '$(Build.SourceBranchName).$(prowlarrVersion)'

View File

@@ -68,6 +68,7 @@ function ProviderFieldFormGroup(props) {
label,
helpText,
helpLink,
placeholder,
value,
type,
advanced,
@@ -100,6 +101,7 @@ function ProviderFieldFormGroup(props) {
label={label}
helpText={helpText}
helpLink={helpLink}
placeholder={placeholder}
value={value}
values={getSelectValues(selectOptions)}
errors={errors}
@@ -125,6 +127,7 @@ ProviderFieldFormGroup.propTypes = {
label: PropTypes.string,
helpText: PropTypes.string,
helpLink: PropTypes.string,
placeholder: PropTypes.string,
value: PropTypes.any,
type: PropTypes.string.isRequired,
advanced: PropTypes.bool.isRequired,

View File

@@ -0,0 +1,54 @@
using System;
using System.Collections;
using NUnit.Framework;
using NzbDrone.Core.Parser;
using NzbDrone.Core.Test.Framework;
namespace NzbDrone.Core.Test.ParserTests
{
[TestFixture]
public class DateTimeRoutinesFixture : CoreTest
{
public static IEnumerable DateTimeTestCases
{
get
{
yield return new TestCaseData(@"Member since: 10-Feb-2008").Returns(new DateTime(2008, 2, 10, 0, 0, 0));
yield return new TestCaseData(@"Last Update: 18:16 11 Feb '08 ").Returns(new DateTime(2008, 2, 11, 18, 16, 0));
yield return new TestCaseData(@"date Tue, Feb 10, 2008 at 11:06 AM").Returns(new DateTime(2008, 2, 10, 11, 06, 0));
yield return new TestCaseData(@"see at 12/31/2007 14:16:32").Returns(new DateTime(2007, 12, 31, 14, 16, 32));
yield return new TestCaseData(@"sack finish 14:16:32 November 15 2008, 1-144 app").Returns(new DateTime(2008, 11, 15, 14, 16, 32));
yield return new TestCaseData(@"Genesis Message - Wed 04 Feb 08 - 19:40").Returns(new DateTime(2008, 2, 4, 19, 40, 0));
yield return new TestCaseData(@"The day 07/31/07 14:16:32 is ").Returns(new DateTime(2007, 7, 31, 14, 16, 32));
yield return new TestCaseData(@"Shipping is on us until December 24, 2008 within the U.S. ").Returns(new DateTime(2008, 12, 24, 0, 0, 0));
yield return new TestCaseData(@" 2008 within the U.S. at 14:16:32").Returns(new DateTime(DateTime.Now.Year, DateTime.Now.Month, DateTime.Now.Day, 14, 16, 32));
yield return new TestCaseData(@"5th November, 1994, 8:15:30 pm").Returns(new DateTime(1994, 11, 5, 20, 15, 30));
yield return new TestCaseData(@"7 boxes January 31 , 14:16:32.").Returns(new DateTime(DateTime.Now.Year, 1, 31, 14, 16, 32));
yield return new TestCaseData(@"the blue sky of Sept 30th 2008 14:16:32").Returns(new DateTime(2008, 9, 30, 14, 16, 32));
yield return new TestCaseData(@" e.g. 1997-07-16T19:20:30+01:00").Returns(new DateTime(1997, 7, 16, 19, 20, 30));
yield return new TestCaseData(@"Apr 1st, 2008 14:16:32 tufa 6767").Returns(new DateTime(2008, 4, 1, 14, 16, 32));
yield return new TestCaseData(@"wait for 07/31/07 14:16:32").Returns(new DateTime(2007, 7, 31, 14, 16, 32));
yield return new TestCaseData(@"later 12.31.08 and before 1.01.09").Returns(new DateTime(2008, 12, 31, 0, 0, 0));
yield return new TestCaseData(@"Expires: Sept 30th 2008 14:16:32").Returns(new DateTime(2008, 9, 30, 14, 16, 32));
yield return new TestCaseData(@"Offer expires Apr 1st, 2007, 14:16:32").Returns(new DateTime(2007, 4, 1, 14, 16, 32));
yield return new TestCaseData(@"Expires 14:16:32 January 31.").Returns(new DateTime(DateTime.Now.Year, 1, 31, 14, 16, 32));
yield return new TestCaseData(@"Expires 14:16:32 January 31-st.").Returns(new DateTime(DateTime.Now.Year, 1, 31, 14, 16, 32));
yield return new TestCaseData(@"Expires 23rd January 2010.").Returns(new DateTime(2010, 1, 23, 0, 0, 0));
yield return new TestCaseData(@"Expires January 22nd, 2010.").Returns(new DateTime(2010, 1, 22, 0, 0, 0));
yield return new TestCaseData(@"Expires DEC 22, 2010.").Returns(new DateTime(2010, 12, 22, 0, 0, 0));
yield return new TestCaseData(@"Version: 1.0.0.692 6/1/2010 2:28:04 AM ").Returns(new DateTime(2010, 6, 1, 2, 28, 4));
yield return new TestCaseData(@"Version: 1.0.0.692 04/21/11 12:30am ").Returns(new DateTime(2011, 4, 21, 00, 30, 00));
yield return new TestCaseData(@"Version: 1.0.0.692 04/21/11 12:30pm ").Returns(new DateTime(2011, 4, 21, 12, 30, 00));
yield return new TestCaseData(@"Version: Thu Aug 06 22:32:15 MDT 2009 ").Returns(new DateTime(2009, 8, 6, 22, 32, 15));
}
}
[TestCaseSource("DateTimeTestCases")]
public DateTime should_parse_date(string date)
{
DateTimeRoutines.TryParseDateOrTime(date, DateTimeRoutines.DateTimeFormat.USDate, out var parsedDateTime);
return parsedDateTime.DateTime;
}
}
}

View File

@@ -23,6 +23,7 @@ namespace NzbDrone.Core.Annotations
public string Section { get; set; }
public HiddenType Hidden { get; set; }
public PrivacyLevel Privacy { get; set; }
public string Placeholder { get; set; }
}
[AttributeUsage(AttributeTargets.Property | AttributeTargets.Field, AllowMultiple = false)]

View File

@@ -23,8 +23,6 @@ namespace NzbDrone.Core.Applications.LazyLibrarian
public LazyLibrarianSettings()
{
ProwlarrUrl = "http://localhost:9696";
BaseUrl = "http://localhost:5299";
SyncCategories = new[]
{
NewznabStandardCategory.AudioAudiobook.Id,
@@ -38,10 +36,10 @@ namespace NzbDrone.Core.Applications.LazyLibrarian
};
}
[FieldDefinition(0, Label = "Prowlarr Server", HelpText = "Prowlarr server URL as LazyLibrarian sees it, including http(s)://, port, and urlbase if needed")]
[FieldDefinition(0, Label = "Prowlarr Server", HelpText = "Prowlarr server URL as LazyLibrarian sees it, including http(s)://, port, and urlbase if needed", Placeholder = "http://localhost:9696")]
public string ProwlarrUrl { get; set; }
[FieldDefinition(1, Label = "LazyLibrarian Server", HelpText = "URL used to connect to LazyLibrarian server, including http(s)://, port, and urlbase if required")]
[FieldDefinition(1, Label = "LazyLibrarian Server", HelpText = "URL used to connect to LazyLibrarian server, including http(s)://, port, and urlbase if required", Placeholder = "http://localhost:5299")]
public string BaseUrl { get; set; }
[FieldDefinition(2, Label = "ApiKey", Privacy = PrivacyLevel.ApiKey, HelpText = "The ApiKey generated by LazyLibrarian in Settings/Web Interface")]

View File

@@ -22,15 +22,13 @@ namespace NzbDrone.Core.Applications.Lidarr
public LidarrSettings()
{
ProwlarrUrl = "http://localhost:9696";
BaseUrl = "http://localhost:8686";
SyncCategories = new[] { 3000, 3010, 3030, 3040, 3050, 3060 };
}
[FieldDefinition(0, Label = "Prowlarr Server", HelpText = "Prowlarr server URL as Lidarr sees it, including http(s)://, port, and urlbase if needed")]
[FieldDefinition(0, Label = "Prowlarr Server", HelpText = "Prowlarr server URL as Lidarr sees it, including http(s)://, port, and urlbase if needed", Placeholder = "http://localhost:9696")]
public string ProwlarrUrl { get; set; }
[FieldDefinition(1, Label = "Lidarr Server", HelpText = "URL used to connect to Lidarr server, including http(s)://, port, and urlbase if required")]
[FieldDefinition(1, Label = "Lidarr Server", HelpText = "URL used to connect to Lidarr server, including http(s)://, port, and urlbase if required", Placeholder = "http://localhost:8686")]
public string BaseUrl { get; set; }
[FieldDefinition(2, Label = "ApiKey", Privacy = PrivacyLevel.ApiKey, HelpText = "The ApiKey generated by Lidarr in Settings/General")]

View File

@@ -23,15 +23,13 @@ namespace NzbDrone.Core.Applications.Mylar
public MylarSettings()
{
ProwlarrUrl = "http://localhost:9696";
BaseUrl = "http://localhost:8090";
SyncCategories = new[] { NewznabStandardCategory.BooksComics.Id };
}
[FieldDefinition(0, Label = "Prowlarr Server", HelpText = "Prowlarr server URL as Mylar sees it, including http(s)://, port, and urlbase if needed")]
[FieldDefinition(0, Label = "Prowlarr Server", HelpText = "Prowlarr server URL as Mylar sees it, including http(s)://, port, and urlbase if needed", Placeholder = "http://localhost:9696")]
public string ProwlarrUrl { get; set; }
[FieldDefinition(1, Label = "Mylar Server", HelpText = "URL used to connect to Mylar server, including http(s)://, port, and urlbase if required")]
[FieldDefinition(1, Label = "Mylar Server", HelpText = "URL used to connect to Mylar server, including http(s)://, port, and urlbase if required", Placeholder = "http://localhost:8090")]
public string BaseUrl { get; set; }
[FieldDefinition(2, Label = "ApiKey", Privacy = PrivacyLevel.ApiKey, HelpText = "The ApiKey generated by Mylar in Settings/Web Interface")]

View File

@@ -23,15 +23,13 @@ namespace NzbDrone.Core.Applications.Radarr
public RadarrSettings()
{
ProwlarrUrl = "http://localhost:9696";
BaseUrl = "http://localhost:7878";
SyncCategories = new[] { 2000, 2010, 2020, 2030, 2040, 2045, 2050, 2060, 2070, 2080 };
}
[FieldDefinition(0, Label = "Prowlarr Server", HelpText = "Prowlarr server URL as Radarr sees it, including http(s)://, port, and urlbase if needed")]
[FieldDefinition(0, Label = "Prowlarr Server", HelpText = "Prowlarr server URL as Radarr sees it, including http(s)://, port, and urlbase if needed", Placeholder = "http://localhost:9696")]
public string ProwlarrUrl { get; set; }
[FieldDefinition(1, Label = "Radarr Server", HelpText = "URL used to connect to Radarr server, including http(s)://, port, and urlbase if required")]
[FieldDefinition(1, Label = "Radarr Server", HelpText = "URL used to connect to Radarr server, including http(s)://, port, and urlbase if required", Placeholder = "http://localhost:7878")]
public string BaseUrl { get; set; }
[FieldDefinition(2, Label = "ApiKey", Privacy = PrivacyLevel.ApiKey, HelpText = "The ApiKey generated by Radarr in Settings/General")]

View File

@@ -23,15 +23,13 @@ namespace NzbDrone.Core.Applications.Readarr
public ReadarrSettings()
{
ProwlarrUrl = "http://localhost:9696";
BaseUrl = "http://localhost:8787";
SyncCategories = new[] { 3030, 7000, 7010, 7020, 7030, 7040, 7050, 7060 };
}
[FieldDefinition(0, Label = "Prowlarr Server", HelpText = "Prowlarr server URL as Readarr sees it, including http(s)://, port, and urlbase if needed")]
[FieldDefinition(0, Label = "Prowlarr Server", HelpText = "Prowlarr server URL as Readarr sees it, including http(s)://, port, and urlbase if needed", Placeholder = "http://localhost:9696")]
public string ProwlarrUrl { get; set; }
[FieldDefinition(1, Label = "Readarr Server", HelpText = "URL used to connect to Readarr server, including http(s)://, port, and urlbase if required")]
[FieldDefinition(1, Label = "Readarr Server", HelpText = "URL used to connect to Readarr server, including http(s)://, port, and urlbase if required", Placeholder = "http://localhost:8787")]
public string BaseUrl { get; set; }
[FieldDefinition(2, Label = "ApiKey", Privacy = PrivacyLevel.ApiKey, HelpText = "The ApiKey generated by Readarr in Settings/General")]

View File

@@ -22,16 +22,14 @@ namespace NzbDrone.Core.Applications.Sonarr
public SonarrSettings()
{
ProwlarrUrl = "http://localhost:9696";
BaseUrl = "http://localhost:8989";
SyncCategories = new[] { 5000, 5010, 5020, 5030, 5040, 5045, 5050 };
AnimeSyncCategories = new[] { 5070 };
}
[FieldDefinition(0, Label = "Prowlarr Server", HelpText = "Prowlarr server URL as Sonarr sees it, including http(s)://, port, and urlbase if needed")]
[FieldDefinition(0, Label = "Prowlarr Server", HelpText = "Prowlarr server URL as Sonarr sees it, including http(s)://, port, and urlbase if needed", Placeholder = "http://localhost:9696")]
public string ProwlarrUrl { get; set; }
[FieldDefinition(1, Label = "Sonarr Server", HelpText = "URL used to connect to Sonarr server, including http(s)://, port, and urlbase if required")]
[FieldDefinition(1, Label = "Sonarr Server", HelpText = "URL used to connect to Sonarr server, including http(s)://, port, and urlbase if required", Placeholder = "http://localhost:8989")]
public string BaseUrl { get; set; }
[FieldDefinition(2, Label = "ApiKey", Privacy = PrivacyLevel.ApiKey, HelpText = "The ApiKey generated by Sonarr in Settings/General")]

View File

@@ -633,7 +633,7 @@ namespace NzbDrone.Core.Indexers.Cardigann
var date = DateTimeUtil.ParseDateTimeGoLang(data, layout);
data = date.ToString(DateTimeUtil.Rfc1123ZPattern);
}
catch (FormatException ex)
catch (InvalidDateException ex)
{
_logger.Debug(ex.Message);
}

View File

@@ -158,7 +158,7 @@ namespace NzbDrone.Core.Indexers.Definitions
};
// manually url encode parenthesis to prevent "hacking" detection
searchUrl += queryCollection.GetQueryString().Replace("(", "%28").Replace(")", "%29");
searchUrl += queryCollection.GetQueryString().Replace("(", "%28").Replace(")", "%29").Replace(".", " ");
var request = new IndexerRequest(searchUrl, HttpAccept.Rss);

View File

@@ -36,7 +36,7 @@ namespace NzbDrone.Core.Indexers.Definitions
{
TvSearchParams = new List<TvSearchParam>
{
TvSearchParam.Q, TvSearchParam.Season, TvSearchParam.Ep, TvSearchParam.ImdbId
TvSearchParam.Q, TvSearchParam.Season, TvSearchParam.Ep
},
MovieSearchParams = new List<MovieSearchParam>
{

View File

@@ -32,15 +32,13 @@ namespace NzbDrone.Core.Notifications.Email
public EmailSettings()
{
Server = "smtp.gmail.com";
Port = 587;
To = Array.Empty<string>();
CC = Array.Empty<string>();
Bcc = Array.Empty<string>();
}
[FieldDefinition(0, Label = "Server", HelpText = "Hostname or IP of Email server")]
[FieldDefinition(0, Label = "Server", HelpText = "Hostname or IP of Email server", Placeholder = "smtp.gmail.com")]
public string Server { get; set; }
[FieldDefinition(1, Label = "Port")]
@@ -55,16 +53,16 @@ namespace NzbDrone.Core.Notifications.Email
[FieldDefinition(4, Label = "Password", HelpText = "Password", Type = FieldType.Password, Privacy = PrivacyLevel.Password)]
public string Password { get; set; }
[FieldDefinition(5, Label = "From Address")]
[FieldDefinition(5, Label = "From Address", Placeholder = "example@email.com")]
public string From { get; set; }
[FieldDefinition(6, Label = "Recipient Address(es)", HelpText = "Comma separated list of email recipients")]
[FieldDefinition(6, Label = "Recipient Address(es)", HelpText = "Comma separated list of email recipients", Placeholder = "example@email.com,example1@email.com")]
public IEnumerable<string> To { get; set; }
[FieldDefinition(7, Label = "CC Address(es)", HelpText = "Comma separated list of email cc recipients", Advanced = true)]
[FieldDefinition(7, Label = "CC Address(es)", HelpText = "Comma separated list of email cc recipients", Placeholder = "example@email.com,example1@email.com", Advanced = true)]
public IEnumerable<string> CC { get; set; }
[FieldDefinition(8, Label = "BCC Address(es)", HelpText = "Comma separated list of email bcc recipients", Advanced = true)]
[FieldDefinition(8, Label = "BCC Address(es)", HelpText = "Comma separated list of email bcc recipients", Placeholder = "example@email.com,example1@email.com", Advanced = true)]
public IEnumerable<string> Bcc { get; set; }
public NzbDroneValidationResult Validate()

View File

@@ -28,10 +28,10 @@ namespace NzbDrone.Core.Notifications.PushBullet
[FieldDefinition(0, Label = "Access Token", Privacy = PrivacyLevel.ApiKey, HelpLink = "https://www.pushbullet.com/#settings/account")]
public string ApiKey { get; set; }
[FieldDefinition(1, Label = "Device IDs", HelpText = "List of device IDs (leave blank to send to all devices)", Type = FieldType.Device)]
[FieldDefinition(1, Label = "Device IDs", HelpText = "List of device IDs (leave blank to send to all devices)", Type = FieldType.Device, Placeholder = "123456789,987654321")]
public IEnumerable<string> DeviceIds { get; set; }
[FieldDefinition(2, Label = "Channel Tags", HelpText = "List of Channel Tags to send notifications to", Type = FieldType.Tag)]
[FieldDefinition(2, Label = "Channel Tags", HelpText = "List of Channel Tags to send notifications to", Type = FieldType.Tag, Placeholder = "Channel1234,Channel4321")]
public IEnumerable<string> ChannelTags { get; set; }
[FieldDefinition(3, Label = "Sender ID", HelpText = "The device ID to send notifications from, use device_iden in the device's URL on pushbullet.com (leave blank to send from yourself)")]

View File

@@ -1,3 +1,4 @@
using System;
using System.Collections.Generic;
using FluentValidation;
using NzbDrone.Core.Annotations;
@@ -23,7 +24,7 @@ namespace NzbDrone.Core.Notifications.Pushover
public PushoverSettings()
{
Priority = 0;
Devices = System.Array.Empty<string>();
Devices = Array.Empty<string>();
}
//TODO: Get Pushover to change our app name (or create a new app) when we have a new logo
@@ -33,7 +34,7 @@ namespace NzbDrone.Core.Notifications.Pushover
[FieldDefinition(1, Label = "User Key", Privacy = PrivacyLevel.UserName, HelpLink = "https://pushover.net/")]
public string UserKey { get; set; }
[FieldDefinition(2, Label = "Devices", HelpText = "List of device names (leave blank to send to all devices)", Type = FieldType.Tag)]
[FieldDefinition(2, Label = "Devices", HelpText = "List of device names (leave blank to send to all devices)", Type = FieldType.Tag, Placeholder = "device1")]
public IEnumerable<string> Devices { get; set; }
[FieldDefinition(3, Label = "Priority", Type = FieldType.Select, SelectOptions = typeof(PushoverPriority))]

View File

@@ -36,7 +36,7 @@ namespace NzbDrone.Core.Notifications.SendGrid
[FieldDefinition(2, Label = "From Address")]
public string From { get; set; }
[FieldDefinition(3, Label = "Recipient Address(es)", Type = FieldType.Tag)]
[FieldDefinition(3, Label = "Recipient Address(es)", Type = FieldType.Tag, Placeholder = "example@email.com,example1@email.com")]
public IEnumerable<string> Recipients { get; set; }
public NzbDroneValidationResult Validate()

View File

@@ -0,0 +1,466 @@
//********************************************************************************************
//Author: Sergey Stoyan, CliverSoft.com
// http://cliversoft.com
// stoyan@cliversoft.com
// sergey.stoyan@gmail.com
// 27 February 2007
//********************************************************************************************
using System;
using System.Text.RegularExpressions;
namespace NzbDrone.Core.Parser
{
public static class DateTimeRoutines
{
public class ParsedDateTime
{
public readonly int IndexOfDate = -1;
public readonly int LengthOfDate = -1;
public readonly int IndexOfTime = -1;
public readonly int LengthOfTime = -1;
public readonly DateTime DateTime;
public readonly bool IsDateFound;
public readonly bool IsTimeFound;
public readonly TimeSpan UtcOffset;
public readonly bool IsUtcOffsetFound;
public DateTime UtcDateTime;
internal ParsedDateTime(int index_of_date, int length_of_date, int index_of_time, int length_of_time, DateTime date_time)
{
IndexOfDate = index_of_date;
LengthOfDate = length_of_date;
IndexOfTime = index_of_time;
LengthOfTime = length_of_time;
DateTime = date_time;
IsDateFound = index_of_date > -1;
IsTimeFound = index_of_time > -1;
UtcOffset = new TimeSpan(25, 0, 0);
IsUtcOffsetFound = false;
UtcDateTime = new DateTime(1, 1, 1);
}
internal ParsedDateTime(int index_of_date, int length_of_date, int index_of_time, int length_of_time, DateTime date_time, TimeSpan utc_offset)
{
IndexOfDate = index_of_date;
LengthOfDate = length_of_date;
IndexOfTime = index_of_time;
LengthOfTime = length_of_time;
DateTime = date_time;
IsDateFound = index_of_date > -1;
IsTimeFound = index_of_time > -1;
UtcOffset = utc_offset;
IsUtcOffsetFound = Math.Abs(utc_offset.TotalHours) < 12;
if (!IsUtcOffsetFound)
{
UtcDateTime = new DateTime(1, 1, 1);
}
else
{
if (index_of_date < 0)
{
//to avoid negative date exception when date is undefined
var ts = date_time.TimeOfDay + utc_offset;
if (ts < new TimeSpan(0))
{
UtcDateTime = new DateTime(1, 1, 2) + ts;
}
else
{
UtcDateTime = new DateTime(1, 1, 1) + ts;
}
}
else
{
UtcDateTime = date_time + utc_offset;
}
}
}
}
public static DateTime DefaultDate
{
get
{
if (DefaultDateIsNow)
{
return DateTime.Now;
}
else
{
return _DefaultDate;
}
}
set
{
_DefaultDate = value;
DefaultDateIsNow = false;
}
}
private static DateTime _DefaultDate = DateTime.Now;
public static bool DefaultDateIsNow = true;
public enum DateTimeFormat
{
USDate,
UKDate,
}
public static bool TryParseDateOrTime(this string str, DateTimeFormat default_format, out ParsedDateTime parsed_date_time)
{
parsed_date_time = null;
ParsedDateTime parsed_date;
ParsedDateTime parsed_time;
if (!TryParseDate(str, default_format, out parsed_date))
{
if (!TryParseTime(str, default_format, out parsed_time, null))
{
return false;
}
var date_time = new DateTime(DefaultDate.Year, DefaultDate.Month, DefaultDate.Day, parsed_time.DateTime.Hour, parsed_time.DateTime.Minute, parsed_time.DateTime.Second);
parsed_date_time = new ParsedDateTime(-1, -1, parsed_time.IndexOfTime, parsed_time.LengthOfTime, date_time, parsed_time.UtcOffset);
}
else
{
if (!TryParseTime(str, default_format, out parsed_time, parsed_date))
{
var date_time = new DateTime(parsed_date.DateTime.Year, parsed_date.DateTime.Month, parsed_date.DateTime.Day, 0, 0, 0);
parsed_date_time = new ParsedDateTime(parsed_date.IndexOfDate, parsed_date.LengthOfDate, -1, -1, date_time);
}
else
{
var date_time = new DateTime(parsed_date.DateTime.Year, parsed_date.DateTime.Month, parsed_date.DateTime.Day, parsed_time.DateTime.Hour, parsed_time.DateTime.Minute, parsed_time.DateTime.Second);
parsed_date_time = new ParsedDateTime(parsed_date.IndexOfDate, parsed_date.LengthOfDate, parsed_time.IndexOfTime, parsed_time.LengthOfTime, date_time, parsed_time.UtcOffset);
}
}
return true;
}
public static bool TryParseTime(this string str, DateTimeFormat default_format, out ParsedDateTime parsed_time, ParsedDateTime parsed_date)
{
parsed_time = null;
string time_zone_r;
if (default_format == DateTimeFormat.USDate)
{
time_zone_r = @"(?:\s*(?'time_zone'UTC|GMT|CST|EST))?";
}
else
{
time_zone_r = @"(?:\s*(?'time_zone'UTC|GMT))?";
}
Match m;
if (parsed_date != null && parsed_date.IndexOfDate > -1)
{
//look around the found date
//look for <date> hh:mm:ss <UTC offset>
m = Regex.Match(str.Substring(parsed_date.IndexOfDate + parsed_date.LengthOfDate), @"(?<=^\s*,?\s+|^\s*at\s*|^\s*[T\-]\s*)(?'hour'\d{2})\s*:\s*(?'minute'\d{2})\s*:\s*(?'second'\d{2})\s+(?'offset_sign'[\+\-])(?'offset_hh'\d{2}):?(?'offset_mm'\d{2})(?=$|[^\d\w])", RegexOptions.Compiled);
if (!m.Success)
{
//look for <date> [h]h:mm[:ss] [PM/AM] [UTC/GMT]
m = Regex.Match(str.Substring(parsed_date.IndexOfDate + parsed_date.LengthOfDate), @"(?<=^\s*,?\s+|^\s*at\s*|^\s*[T\-]\s*)(?'hour'\d{1,2})\s*:\s*(?'minute'\d{2})\s*(?::\s*(?'second'\d{2}))?(?:\s*(?'ampm'AM|am|PM|pm))?" + time_zone_r + @"(?=$|[^\d\w])", RegexOptions.Compiled);
}
if (!m.Success)
{
//look for [h]h:mm:ss [PM/AM] [UTC/GMT] <date>
m = Regex.Match(str.Substring(0, parsed_date.IndexOfDate), @"(?<=^|[^\d])(?'hour'\d{1,2})\s*:\s*(?'minute'\d{2})\s*(?::\s*(?'second'\d{2}))?(?:\s*(?'ampm'AM|am|PM|pm))?" + time_zone_r + @"(?=$|[\s,]+)", RegexOptions.Compiled);
}
if (!m.Success)
{
//look for [h]h:mm:ss [PM/AM] [UTC/GMT] within <date>
m = Regex.Match(str.Substring(parsed_date.IndexOfDate, parsed_date.LengthOfDate), @"(?<=^|[^\d])(?'hour'\d{1,2})\s*:\s*(?'minute'\d{2})\s*(?::\s*(?'second'\d{2}))?(?:\s*(?'ampm'AM|am|PM|pm))?" + time_zone_r + @"(?=$|[\s,]+)", RegexOptions.Compiled);
}
}
else
{
//look anywhere within string
//look for hh:mm:ss <UTC offset>
m = Regex.Match(str, @"(?<=^|\s+|\s*T\s*)(?'hour'\d{2})\s*:\s*(?'minute'\d{2})\s*:\s*(?'second'\d{2})\s+(?'offset_sign'[\+\-])(?'offset_hh'\d{2}):?(?'offset_mm'\d{2})?(?=$|[^\d\w])", RegexOptions.Compiled);
if (!m.Success)
{
//look for [h]h:mm[:ss] [PM/AM] [UTC/GMT]
m = Regex.Match(str, @"(?<=^|\s+|\s*T\s*)(?'hour'\d{1,2})\s*:\s*(?'minute'\d{2})\s*(?::\s*(?'second'\d{2}))?(?:\s*(?'ampm'AM|am|PM|pm))?" + time_zone_r + @"(?=$|[^\d\w])", RegexOptions.Compiled);
}
}
if (!m.Success)
{
return false;
}
//try
//{
var hour = int.Parse(m.Groups["hour"].Value);
if (hour < 0 || hour > 23)
{
return false;
}
var minute = int.Parse(m.Groups["minute"].Value);
if (minute < 0 || minute > 59)
{
return false;
}
var second = 0;
if (!string.IsNullOrEmpty(m.Groups["second"].Value))
{
second = int.Parse(m.Groups["second"].Value);
if (second < 0 || second > 59)
{
return false;
}
}
if (string.Compare(m.Groups["ampm"].Value, "PM", true) == 0 && hour < 12)
{
hour += 12;
}
else if (string.Compare(m.Groups["ampm"].Value, "AM", true) == 0 && hour == 12)
{
hour -= 12;
}
var date_time = new DateTime(1, 1, 1, hour, minute, second);
if (m.Groups["offset_hh"].Success)
{
var offset_hh = int.Parse(m.Groups["offset_hh"].Value);
var offset_mm = 0;
if (m.Groups["offset_mm"].Success)
{
offset_mm = int.Parse(m.Groups["offset_mm"].Value);
}
var utc_offset = new TimeSpan(offset_hh, offset_mm, 0);
if (m.Groups["offset_sign"].Value == "-")
{
utc_offset = -utc_offset;
}
parsed_time = new ParsedDateTime(-1, -1, m.Index, m.Length, date_time, utc_offset);
return true;
}
if (m.Groups["time_zone"].Success)
{
TimeSpan utc_offset;
switch (m.Groups["time_zone"].Value)
{
case "UTC":
case "GMT":
utc_offset = new TimeSpan(0, 0, 0);
break;
case "CST":
utc_offset = new TimeSpan(-6, 0, 0);
break;
case "EST":
utc_offset = new TimeSpan(-5, 0, 0);
break;
default:
throw new Exception("Time zone: " + m.Groups["time_zone"].Value + " is not defined.");
}
parsed_time = new ParsedDateTime(-1, -1, m.Index, m.Length, date_time, utc_offset);
return true;
}
parsed_time = new ParsedDateTime(-1, -1, m.Index, m.Length, date_time);
return true;
}
public static bool TryParseDate(this string str, DateTimeFormat default_format, out ParsedDateTime parsed_date)
{
parsed_date = null;
if (string.IsNullOrEmpty(str))
{
return false;
}
//look for dd/mm/yy
var m = Regex.Match(str, @"(?<=^|[^\d])(?'day'\d{1,2})\s*(?'separator'[\\/\.])+\s*(?'month'\d{1,2})\s*\'separator'+\s*(?'year'\d{2}|\d{4})(?=$|[^\d])", RegexOptions.Compiled | RegexOptions.IgnoreCase);
if (m.Success)
{
DateTime date;
if ((default_format ^ DateTimeFormat.USDate) == DateTimeFormat.USDate)
{
if (!ConvertToDate(int.Parse(m.Groups["year"].Value), int.Parse(m.Groups["day"].Value), int.Parse(m.Groups["month"].Value), out date))
{
return false;
}
}
else
{
if (!ConvertToDate(int.Parse(m.Groups["year"].Value), int.Parse(m.Groups["month"].Value), int.Parse(m.Groups["day"].Value), out date))
{
return false;
}
}
parsed_date = new ParsedDateTime(m.Index, m.Length, -1, -1, date);
return true;
}
//look for [yy]yy-mm-dd
m = Regex.Match(str, @"(?<=^|[^\d])(?'year'\d{2}|\d{4})\s*(?'separator'[\-])\s*(?'month'\d{1,2})\s*\'separator'+\s*(?'day'\d{1,2})(?=$|[^\d])", RegexOptions.Compiled | RegexOptions.IgnoreCase);
if (m.Success)
{
DateTime date;
if (!ConvertToDate(int.Parse(m.Groups["year"].Value), int.Parse(m.Groups["month"].Value), int.Parse(m.Groups["day"].Value), out date))
{
return false;
}
parsed_date = new ParsedDateTime(m.Index, m.Length, -1, -1, date);
return true;
}
//look for month dd yyyy
m = Regex.Match(str, @"(?:^|[^\d\w])(?'month'Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec)[uarychilestmbro]*\s+(?'day'\d{1,2})(?:-?st|-?th|-?rd|-?nd)?\s*,?\s*(?'year'\d{4})(?=$|[^\d\w])", RegexOptions.Compiled | RegexOptions.IgnoreCase);
if (!m.Success)
{
//look for dd month [yy]yy
m = Regex.Match(str, @"(?:^|[^\d\w:])(?'day'\d{1,2})(?:-?st\s+|-?th\s+|-?rd\s+|-?nd\s+|-|\s+)(?'month'Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec)[uarychilestmbro]*(?:\s*,?\s*|-)'?(?'year'\d{2}|\d{4})(?=$|[^\d\w])", RegexOptions.Compiled | RegexOptions.IgnoreCase);
}
if (!m.Success)
{
//look for yyyy month dd
m = Regex.Match(str, @"(?:^|[^\d\w])(?'year'\d{4})\s+(?'month'Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec)[uarychilestmbro]*\s+(?'day'\d{1,2})(?:-?st|-?th|-?rd|-?nd)?(?=$|[^\d\w])", RegexOptions.Compiled | RegexOptions.IgnoreCase);
}
if (!m.Success)
{
//look for month dd hh:mm:ss MDT|UTC yyyy
m = Regex.Match(str, @"(?:^|[^\d\w])(?'month'Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec)[uarychilestmbro]*\s+(?'day'\d{1,2})\s+\d{2}\:\d{2}\:\d{2}\s+(?:MDT|UTC)\s+(?'year'\d{4})(?=$|[^\d\w])", RegexOptions.Compiled | RegexOptions.IgnoreCase);
}
if (!m.Success)
{
//look for month dd [yyyy]
m = Regex.Match(str, @"(?:^|[^\d\w])(?'month'Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec)[uarychilestmbro]*\s+(?'day'\d{1,2})(?:-?st|-?th|-?rd|-?nd)?(?:\s*,?\s*(?'year'\d{4}))?(?=$|[^\d\w])", RegexOptions.Compiled | RegexOptions.IgnoreCase);
}
if (m.Success)
{
var month = -1;
var index_of_date = m.Index;
var length_of_date = m.Length;
switch (m.Groups["month"].Value)
{
case "Jan":
case "JAN":
month = 1;
break;
case "Feb":
case "FEB":
month = 2;
break;
case "Mar":
case "MAR":
month = 3;
break;
case "Apr":
case "APR":
month = 4;
break;
case "May":
case "MAY":
month = 5;
break;
case "Jun":
case "JUN":
month = 6;
break;
case "Jul":
month = 7;
break;
case "Aug":
case "AUG":
month = 8;
break;
case "Sep":
case "SEP":
month = 9;
break;
case "Oct":
case "OCT":
month = 10;
break;
case "Nov":
case "NOV":
month = 11;
break;
case "Dec":
case "DEC":
month = 12;
break;
}
int year;
if (!string.IsNullOrEmpty(m.Groups["year"].Value))
{
year = int.Parse(m.Groups["year"].Value);
}
else
{
year = DefaultDate.Year;
}
DateTime date;
if (!ConvertToDate(year, month, int.Parse(m.Groups["day"].Value), out date))
{
return false;
}
parsed_date = new ParsedDateTime(index_of_date, length_of_date, -1, -1, date);
return true;
}
return false;
}
private static bool ConvertToDate(int year, int month, int day, out DateTime date)
{
if (year >= 100)
{
if (year < 1000)
{
date = new DateTime(1, 1, 1);
return false;
}
}
else
if (year > 30)
{
year += 1900;
}
else
{
year += 2000;
}
try
{
date = new DateTime(year, month, day);
}
catch
{
date = new DateTime(1, 1, 1);
return false;
}
return true;
}
}
}

View File

@@ -91,7 +91,7 @@ namespace NzbDrone.Core.Parser
}
else
{
throw new Exception("TimeAgo parsing failed, unknown unit: " + unit);
throw new InvalidDateException("TimeAgo parsing failed, unknown unit: " + unit);
}
}
@@ -102,19 +102,17 @@ namespace NzbDrone.Core.Parser
// http://www.codeproject.com/Articles/33298/C-Date-Time-Parser
public static DateTime FromFuzzyTime(string str, string format = null)
{
//var dtFormat = format == "UK" ?
// DateTimeRoutines.DateTimeRoutines.DateTimeFormat.UkDate :
// DateTimeRoutines.DateTimeRoutines.DateTimeFormat.UsaDate;
var dtFormat = format == "UK" ?
DateTimeRoutines.DateTimeFormat.UKDate :
DateTimeRoutines.DateTimeFormat.USDate;
//if (DateTimeRoutines.DateTimeRoutines.TryParseDateOrTime(
// str, dtFormat, out DateTimeRoutines.DateTimeRoutines.ParsedDateTime dt))
// return dt.DateTime;
if (DateTime.TryParse(str, out var dateTimeParsed))
if (DateTimeRoutines.TryParseDateOrTime(
str, dtFormat, out DateTimeRoutines.ParsedDateTime dt))
{
return dateTimeParsed;
return dt.DateTime;
}
throw new Exception($"FromFuzzyTime parsing failed for string {str}");
throw new InvalidDateException($"FromFuzzyTime parsing failed for string {str}");
}
public static DateTime FromUnknown(string str, string format = null)
@@ -214,16 +212,13 @@ namespace NzbDrone.Core.Parser
return dt;
}
try
// try parsing the str as an unix timestamp
if (long.TryParse(str, out var unixTimeStamp))
{
// try parsing the str as an unix timestamp
var unixTimeStamp = long.Parse(str);
return UnixTimestampToDateTime(unixTimeStamp);
}
catch (FormatException)
{
// it wasn't a timestamp, continue....
}
// it wasn't a timestamp, continue....
// add missing year
match = _MissingYearRegexp.Match(str);
@@ -247,7 +242,7 @@ namespace NzbDrone.Core.Parser
}
catch (Exception ex)
{
throw new Exception($"DateTime parsing failed for \"{str}\": {ex}");
throw new InvalidDateException($"DateTime parsing failed for \"{str}\": {ex}");
}
}
@@ -317,7 +312,7 @@ namespace NzbDrone.Core.Parser
}
catch (FormatException ex)
{
throw new FormatException($"Error while parsing DateTime \"{date}\", using layout \"{layout}\" ({pattern}): {ex.Message}");
throw new InvalidDateException($"Error while parsing DateTime \"{date}\", using layout \"{layout}\" ({pattern}): {ex.Message}");
}
}

View File

@@ -171,8 +171,6 @@ namespace NzbDrone.Host
return ApplicationModes.UninstallService;
}
Logger.Debug("Getting windows service status");
// IsWindowsService can throw sometimes, so wrap it
var isWindowsService = false;
try

View File

@@ -3,7 +3,7 @@
<TargetFrameworks>net6.0</TargetFrameworks>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Mono.Posix.NETStandard" Version="5.20.1.34-servarr17" />
<PackageReference Include="Mono.Posix.NETStandard" Version="5.20.1.34-servarr18" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\NzbDrone.Common.Test\Prowlarr.Common.Test.csproj" />

View File

@@ -3,7 +3,7 @@
<TargetFrameworks>net6.0</TargetFrameworks>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Mono.Posix.NETStandard" Version="5.20.1.34-servarr17" />
<PackageReference Include="Mono.Posix.NETStandard" Version="5.20.1.34-servarr18" />
<PackageReference Include="System.IO.FileSystem.AccessControl" Version="5.0.0" />
</ItemGroup>
<ItemGroup>

View File

@@ -19,6 +19,7 @@ namespace Prowlarr.Http.ClientSchema
public string SelectOptionsProviderAction { get; set; }
public string Section { get; set; }
public string Hidden { get; set; }
public string Placeholder { get; set; }
public Field Clone()
{

View File

@@ -101,7 +101,8 @@ namespace Prowlarr.Http.ClientSchema
Order = fieldAttribute.Order,
Advanced = fieldAttribute.Advanced,
Type = fieldAttribute.Type.ToString().FirstCharToLower(),
Section = fieldAttribute.Section
Section = fieldAttribute.Section,
Placeholder = fieldAttribute.Placeholder
};
if (fieldAttribute.Type == FieldType.Select || fieldAttribute.Type == FieldType.TagSelect)