Merge branch 'develop'
This commit is contained in:
commit
5c7ff0c6b4
|
@ -18,7 +18,7 @@
|
||||||
"grunt": "*",
|
"grunt": "*",
|
||||||
"grunt-contrib-handlebars": "*",
|
"grunt-contrib-handlebars": "*",
|
||||||
"grunt-contrib-watch": "*",
|
"grunt-contrib-watch": "*",
|
||||||
"grunt-contrib-less": "*",
|
"grunt-contrib-less": "0.8.3",
|
||||||
"grunt-contrib-copy": "*",
|
"grunt-contrib-copy": "*",
|
||||||
"grunt-notify": "*",
|
"grunt-notify": "*",
|
||||||
"grunt-contrib-clean": "*",
|
"grunt-contrib-clean": "*",
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
using System.IO;
|
using System.IO;
|
||||||
|
using System.Text.RegularExpressions;
|
||||||
using Nancy;
|
using Nancy;
|
||||||
using NLog;
|
using NLog;
|
||||||
using NzbDrone.Common;
|
using NzbDrone.Common;
|
||||||
|
@ -12,6 +13,7 @@ namespace NzbDrone.Api.Frontend.Mappers
|
||||||
private readonly IDiskProvider _diskProvider;
|
private readonly IDiskProvider _diskProvider;
|
||||||
private readonly IConfigFileProvider _configFileProvider;
|
private readonly IConfigFileProvider _configFileProvider;
|
||||||
private readonly string _indexPath;
|
private readonly string _indexPath;
|
||||||
|
private static readonly Regex ReplaceRegex = new Regex("(?<=(?:href|src|data-main)=\").*?(?=\")", RegexOptions.Compiled | RegexOptions.IgnoreCase);
|
||||||
|
|
||||||
public IndexHtmlMapper(IAppFolderInfo appFolderInfo,
|
public IndexHtmlMapper(IAppFolderInfo appFolderInfo,
|
||||||
IDiskProvider diskProvider,
|
IDiskProvider diskProvider,
|
||||||
|
@ -47,13 +49,15 @@ namespace NzbDrone.Api.Frontend.Mappers
|
||||||
return StringToStream(GetIndexText());
|
return StringToStream(GetIndexText());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private string GetIndexText()
|
private string GetIndexText()
|
||||||
{
|
{
|
||||||
var text = _diskProvider.ReadAllText(_indexPath);
|
var text = _diskProvider.ReadAllText(_indexPath);
|
||||||
|
|
||||||
|
text = ReplaceRegex.Replace(text, match => _configFileProvider.UrlBase + match.Value);
|
||||||
|
|
||||||
text = text.Replace(".css", ".css?v=" + BuildInfo.Version);
|
text = text.Replace(".css", ".css?v=" + BuildInfo.Version);
|
||||||
text = text.Replace(".js", ".js?v=" + BuildInfo.Version);
|
text = text.Replace(".js", ".js?v=" + BuildInfo.Version);
|
||||||
|
text = text.Replace("API_ROOT", _configFileProvider.UrlBase + "/api");
|
||||||
text = text.Replace("API_KEY", _configFileProvider.ApiKey);
|
text = text.Replace("API_KEY", _configFileProvider.ApiKey);
|
||||||
text = text.Replace("APP_VERSION", BuildInfo.Version.ToString());
|
text = text.Replace("APP_VERSION", BuildInfo.Version.ToString());
|
||||||
|
|
||||||
|
|
|
@ -1,21 +1,25 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using Nancy.Responses;
|
||||||
using NLog;
|
using NLog;
|
||||||
using Nancy;
|
using Nancy;
|
||||||
using NzbDrone.Api.Frontend.Mappers;
|
using NzbDrone.Api.Frontend.Mappers;
|
||||||
|
using NzbDrone.Core.Configuration;
|
||||||
|
|
||||||
namespace NzbDrone.Api.Frontend
|
namespace NzbDrone.Api.Frontend
|
||||||
{
|
{
|
||||||
public class StaticResourceModule : NancyModule
|
public class StaticResourceModule : NancyModule
|
||||||
{
|
{
|
||||||
private readonly IEnumerable<IMapHttpRequestsToDisk> _requestMappers;
|
private readonly IEnumerable<IMapHttpRequestsToDisk> _requestMappers;
|
||||||
|
private readonly IConfigFileProvider _configFileProvider;
|
||||||
private readonly Logger _logger;
|
private readonly Logger _logger;
|
||||||
|
|
||||||
|
|
||||||
public StaticResourceModule(IEnumerable<IMapHttpRequestsToDisk> requestMappers, Logger logger)
|
public StaticResourceModule(IEnumerable<IMapHttpRequestsToDisk> requestMappers, IConfigFileProvider configFileProvider, Logger logger)
|
||||||
{
|
{
|
||||||
_requestMappers = requestMappers;
|
_requestMappers = requestMappers;
|
||||||
|
_configFileProvider = configFileProvider;
|
||||||
_logger = logger;
|
_logger = logger;
|
||||||
|
|
||||||
Get["/{resource*}"] = x => Index();
|
Get["/{resource*}"] = x => Index();
|
||||||
|
@ -34,8 +38,21 @@ namespace NzbDrone.Api.Frontend
|
||||||
return new NotFoundResponse();
|
return new NotFoundResponse();
|
||||||
}
|
}
|
||||||
|
|
||||||
var mapper = _requestMappers.SingleOrDefault(m => m.CanHandle(path));
|
//Redirect to the subfolder if the request went to the base URL
|
||||||
|
if (path.Equals("/"))
|
||||||
|
{
|
||||||
|
var urlBase = _configFileProvider.UrlBase;
|
||||||
|
|
||||||
|
if (!String.IsNullOrEmpty(urlBase))
|
||||||
|
{
|
||||||
|
if (Request.Url.BasePath != urlBase)
|
||||||
|
{
|
||||||
|
return new RedirectResponse(urlBase + "/");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var mapper = _requestMappers.SingleOrDefault(m => m.CanHandle(path));
|
||||||
|
|
||||||
if (mapper != null)
|
if (mapper != null)
|
||||||
{
|
{
|
||||||
|
|
|
@ -33,6 +33,13 @@ namespace NzbDrone.Api.History
|
||||||
SortDirection = pagingResource.SortDirection
|
SortDirection = pagingResource.SortDirection
|
||||||
};
|
};
|
||||||
|
|
||||||
|
//This is a hack to deal with backgrid setting the sortKey to the column name instead of sortValue
|
||||||
|
if (pagingSpec.SortKey.Equals("series", StringComparison.InvariantCultureIgnoreCase))
|
||||||
|
{
|
||||||
|
pagingSpec.SortKey = "series.title";
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
if (episodeId.HasValue)
|
if (episodeId.HasValue)
|
||||||
{
|
{
|
||||||
int i = (int)episodeId;
|
int i = (int)episodeId;
|
||||||
|
|
|
@ -43,27 +43,27 @@ namespace NzbDrone.Api
|
||||||
|
|
||||||
private List<TProviderResource> GetAll()
|
private List<TProviderResource> GetAll()
|
||||||
{
|
{
|
||||||
var indexerDefinitions = _providerFactory.All();
|
var providerDefinitions = _providerFactory.All();
|
||||||
|
|
||||||
var result = new List<TProviderResource>(indexerDefinitions.Count);
|
var result = new List<TProviderResource>(providerDefinitions.Count);
|
||||||
|
|
||||||
foreach (var definition in indexerDefinitions)
|
foreach (var definition in providerDefinitions)
|
||||||
{
|
{
|
||||||
var indexerResource = new TProviderResource();
|
var providerResource = new TProviderResource();
|
||||||
indexerResource.InjectFrom(definition);
|
providerResource.InjectFrom(definition);
|
||||||
indexerResource.Fields = SchemaBuilder.ToSchema(definition.Settings);
|
providerResource.Fields = SchemaBuilder.ToSchema(definition.Settings);
|
||||||
|
|
||||||
result.Add(indexerResource);
|
result.Add(providerResource);
|
||||||
}
|
}
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
private int CreateProvider(TProviderResource indexerResource)
|
private int CreateProvider(TProviderResource providerResource)
|
||||||
{
|
{
|
||||||
var indexer = GetDefinition(indexerResource);
|
var provider = GetDefinition(providerResource);
|
||||||
indexer = _providerFactory.Create(indexer);
|
provider = _providerFactory.Create(provider);
|
||||||
return indexer.Id;
|
return provider.Id;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void UpdateProvider(TProviderResource providerResource)
|
private void UpdateProvider(TProviderResource providerResource)
|
||||||
|
|
|
@ -43,7 +43,8 @@ namespace NzbDrone.Api.System
|
||||||
IsWindows = OsInfo.IsWindows,
|
IsWindows = OsInfo.IsWindows,
|
||||||
Branch = _configFileProvider.Branch,
|
Branch = _configFileProvider.Branch,
|
||||||
Authentication = _configFileProvider.AuthenticationEnabled,
|
Authentication = _configFileProvider.AuthenticationEnabled,
|
||||||
StartOfWeek = (int)OsInfo.FirstDayOfWeek
|
StartOfWeek = (int)OsInfo.FirstDayOfWeek,
|
||||||
|
UrlBase = _configFileProvider.UrlBase
|
||||||
}.AsResponse();
|
}.AsResponse();
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,8 +17,6 @@ namespace NzbDrone.Common.Composition
|
||||||
|
|
||||||
protected ContainerBuilderBase(IStartupContext args, params string[] assemblies)
|
protected ContainerBuilderBase(IStartupContext args, params string[] assemblies)
|
||||||
{
|
{
|
||||||
|
|
||||||
|
|
||||||
_loadedTypes = new List<Type>();
|
_loadedTypes = new List<Type>();
|
||||||
|
|
||||||
foreach (var assembly in assemblies)
|
foreach (var assembly in assemblies)
|
||||||
|
@ -55,8 +53,6 @@ namespace NzbDrone.Common.Composition
|
||||||
{
|
{
|
||||||
var implementations = Container.GetImplementations(contractType).Where(c => !c.IsGenericTypeDefinition).ToList();
|
var implementations = Container.GetImplementations(contractType).Where(c => !c.IsGenericTypeDefinition).ToList();
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
if (implementations.Count == 0)
|
if (implementations.Count == 0)
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
|
|
|
@ -139,7 +139,6 @@ namespace NzbDrone.Common
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public ServiceControllerStatus GetStatus(string serviceName)
|
public ServiceControllerStatus GetStatus(string serviceName)
|
||||||
{
|
{
|
||||||
return GetService(serviceName).Status;
|
return GetService(serviceName).Status;
|
||||||
|
|
|
@ -49,5 +49,12 @@ namespace NzbDrone.Core.Test.DecisionEngineTests
|
||||||
Mocker.GetMock<IConfigService>().SetupGet(c => c.ReleaseRestrictions).Returns(restrictions);
|
Mocker.GetMock<IConfigService>().SetupGet(c => c.ReleaseRestrictions).Returns(restrictions);
|
||||||
Subject.IsSatisfiedBy(_parseResult, null).Should().BeTrue();
|
Subject.IsSatisfiedBy(_parseResult, null).Should().BeTrue();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void should_not_try_to_find_empty_string_as_a_match()
|
||||||
|
{
|
||||||
|
Mocker.GetMock<IConfigService>().SetupGet(c => c.ReleaseRestrictions).Returns("test\n");
|
||||||
|
Subject.IsSatisfiedBy(_parseResult, null).Should().BeTrue();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -27,7 +27,6 @@ namespace NzbDrone.Core.Test.IndexerTests
|
||||||
_indexers.Add(new Wombles());
|
_indexers.Add(new Wombles());
|
||||||
|
|
||||||
Mocker.SetConstant<IEnumerable<IIndexer>>(_indexers);
|
Mocker.SetConstant<IEnumerable<IIndexer>>(_indexers);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
|
@ -61,7 +60,6 @@ namespace NzbDrone.Core.Test.IndexerTests
|
||||||
indexers.Select(c => c.Name).Should().OnlyHaveUniqueItems();
|
indexers.Select(c => c.Name).Should().OnlyHaveUniqueItems();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void should_remove_missing_indexers_on_startup()
|
public void should_remove_missing_indexers_on_startup()
|
||||||
{
|
{
|
||||||
|
@ -69,13 +67,11 @@ namespace NzbDrone.Core.Test.IndexerTests
|
||||||
|
|
||||||
Mocker.SetConstant<IIndexerRepository>(repo);
|
Mocker.SetConstant<IIndexerRepository>(repo);
|
||||||
|
|
||||||
|
|
||||||
var existingIndexers = Builder<IndexerDefinition>.CreateNew().BuildNew();
|
var existingIndexers = Builder<IndexerDefinition>.CreateNew().BuildNew();
|
||||||
existingIndexers.ConfigContract = typeof (NewznabSettings).Name;
|
existingIndexers.ConfigContract = typeof (NewznabSettings).Name;
|
||||||
|
|
||||||
repo.Insert(existingIndexers);
|
repo.Insert(existingIndexers);
|
||||||
|
|
||||||
|
|
||||||
Subject.Handle(new ApplicationStartedEvent());
|
Subject.Handle(new ApplicationStartedEvent());
|
||||||
|
|
||||||
AllStoredModels.Should().NotContain(c => c.Id == existingIndexers.Id);
|
AllStoredModels.Should().NotContain(c => c.Id == existingIndexers.Id);
|
||||||
|
|
|
@ -20,7 +20,6 @@ namespace NzbDrone.Core.Test.IndexerTests.IntegrationTests
|
||||||
public void SetUp()
|
public void SetUp()
|
||||||
{
|
{
|
||||||
UseRealHttp();
|
UseRealHttp();
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
|
@ -39,7 +38,6 @@ namespace NzbDrone.Core.Test.IndexerTests.IntegrationTests
|
||||||
ValidateResult(result, skipSize: true, skipInfo: true);
|
ValidateResult(result, skipSize: true, skipInfo: true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void extv_rss()
|
public void extv_rss()
|
||||||
{
|
{
|
||||||
|
@ -55,7 +53,6 @@ namespace NzbDrone.Core.Test.IndexerTests.IntegrationTests
|
||||||
ValidateTorrentResult(result, skipSize: false, skipInfo: true);
|
ValidateTorrentResult(result, skipSize: false, skipInfo: true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void nzbsorg_rss()
|
public void nzbsorg_rss()
|
||||||
{
|
{
|
||||||
|
@ -74,8 +71,6 @@ namespace NzbDrone.Core.Test.IndexerTests.IntegrationTests
|
||||||
ValidateResult(result);
|
ValidateResult(result);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
private void ValidateResult(IList<ReleaseInfo> reports, bool skipSize = false, bool skipInfo = false)
|
private void ValidateResult(IList<ReleaseInfo> reports, bool skipSize = false, bool skipInfo = false)
|
||||||
{
|
{
|
||||||
reports.Should().NotBeEmpty();
|
reports.Should().NotBeEmpty();
|
||||||
|
@ -97,7 +92,6 @@ namespace NzbDrone.Core.Test.IndexerTests.IntegrationTests
|
||||||
|
|
||||||
private void ValidateTorrentResult(IList<ReleaseInfo> reports, bool skipSize = false, bool skipInfo = false)
|
private void ValidateTorrentResult(IList<ReleaseInfo> reports, bool skipSize = false, bool skipInfo = false)
|
||||||
{
|
{
|
||||||
|
|
||||||
reports.Should().OnlyContain(c => c.GetType() == typeof(TorrentInfo));
|
reports.Should().OnlyContain(c => c.GetType() == typeof(TorrentInfo));
|
||||||
|
|
||||||
ValidateResult(reports, skipSize, skipInfo);
|
ValidateResult(reports, skipSize, skipInfo);
|
||||||
|
|
|
@ -0,0 +1,89 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using FizzWare.NBuilder;
|
||||||
|
using FluentValidation.Results;
|
||||||
|
using Moq;
|
||||||
|
using NUnit.Framework;
|
||||||
|
using NzbDrone.Common;
|
||||||
|
using NzbDrone.Core.Indexers;
|
||||||
|
using NzbDrone.Core.IndexerSearch.Definitions;
|
||||||
|
using NzbDrone.Core.Parser.Model;
|
||||||
|
using NzbDrone.Core.ThingiProvider;
|
||||||
|
using NzbDrone.Core.Tv;
|
||||||
|
using NzbDrone.Test.Common;
|
||||||
|
|
||||||
|
namespace NzbDrone.Core.Test.IndexerTests
|
||||||
|
{
|
||||||
|
[TestFixture]
|
||||||
|
public class SeasonSearchFixture : TestBase<FetchFeedService>
|
||||||
|
{
|
||||||
|
private Series _series;
|
||||||
|
|
||||||
|
[SetUp]
|
||||||
|
public void Setup()
|
||||||
|
{
|
||||||
|
_series = Builder<Series>.CreateNew().Build();
|
||||||
|
|
||||||
|
Mocker.GetMock<IHttpProvider>().Setup(s => s.DownloadString(It.IsAny<String>())).Returns("<xml></xml>");
|
||||||
|
}
|
||||||
|
|
||||||
|
private IndexerBase<TestIndexerSettings> WithIndexer(bool paging, int resultCount)
|
||||||
|
{
|
||||||
|
var results = Builder<ReleaseInfo>.CreateListOfSize(resultCount)
|
||||||
|
.Build();
|
||||||
|
|
||||||
|
var indexer = Mocker.GetMock<IndexerBase<TestIndexerSettings>>();
|
||||||
|
|
||||||
|
indexer.Setup(s => s.Parser.Process(It.IsAny<String>(), It.IsAny<String>()))
|
||||||
|
.Returns(results);
|
||||||
|
|
||||||
|
indexer.Setup(s => s.GetSeasonSearchUrls(It.IsAny<String>(), It.IsAny<Int32>(), It.IsAny<Int32>(), It.IsAny<Int32>()))
|
||||||
|
.Returns(new List<string> { "http://www.nzbdrone.com" });
|
||||||
|
|
||||||
|
indexer.SetupGet(s => s.SupportsPaging).Returns(paging);
|
||||||
|
|
||||||
|
var definition = new IndexerDefinition();
|
||||||
|
definition.Name = "Test";
|
||||||
|
|
||||||
|
indexer.SetupGet(s => s.Definition)
|
||||||
|
.Returns(definition);
|
||||||
|
|
||||||
|
return indexer.Object;
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void should_not_use_offset_if_result_count_is_less_than_90()
|
||||||
|
{
|
||||||
|
var indexer = WithIndexer(true, 25);
|
||||||
|
Subject.Fetch(indexer, new SeasonSearchCriteria { Series = _series, SceneTitle = _series.Title });
|
||||||
|
|
||||||
|
Mocker.GetMock<IHttpProvider>().Verify(v => v.DownloadString(It.IsAny<String>()), Times.Once());
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void should_not_use_offset_for_sites_that_do_not_support_it()
|
||||||
|
{
|
||||||
|
var indexer = WithIndexer(false, 125);
|
||||||
|
Subject.Fetch(indexer, new SeasonSearchCriteria { Series = _series, SceneTitle = _series.Title });
|
||||||
|
|
||||||
|
Mocker.GetMock<IHttpProvider>().Verify(v => v.DownloadString(It.IsAny<String>()), Times.Once());
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void should_not_use_offset_if_its_already_tried_10_times()
|
||||||
|
{
|
||||||
|
var indexer = WithIndexer(true, 100);
|
||||||
|
Subject.Fetch(indexer, new SeasonSearchCriteria { Series = _series, SceneTitle = _series.Title });
|
||||||
|
|
||||||
|
Mocker.GetMock<IHttpProvider>().Verify(v => v.DownloadString(It.IsAny<String>()), Times.Exactly(10));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class TestIndexerSettings : IProviderConfig
|
||||||
|
{
|
||||||
|
public ValidationResult Validate()
|
||||||
|
{
|
||||||
|
throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -139,6 +139,7 @@
|
||||||
<Compile Include="IndexerTests\IndexerServiceFixture.cs" />
|
<Compile Include="IndexerTests\IndexerServiceFixture.cs" />
|
||||||
<Compile Include="IndexerTests\IntegrationTests\IndexerIntegrationTests.cs" />
|
<Compile Include="IndexerTests\IntegrationTests\IndexerIntegrationTests.cs" />
|
||||||
<Compile Include="IndexerTests\NewznabTests\NewznabSettingFixture.cs" />
|
<Compile Include="IndexerTests\NewznabTests\NewznabSettingFixture.cs" />
|
||||||
|
<Compile Include="IndexerTests\SeasonSearchFixture.cs" />
|
||||||
<Compile Include="IndexerTests\XElementExtensionsFixture.cs" />
|
<Compile Include="IndexerTests\XElementExtensionsFixture.cs" />
|
||||||
<Compile Include="JobTests\JobRepositoryFixture.cs" />
|
<Compile Include="JobTests\JobRepositoryFixture.cs" />
|
||||||
<Compile Include="DecisionEngineTests\LanguageSpecificationFixture.cs" />
|
<Compile Include="DecisionEngineTests\LanguageSpecificationFixture.cs" />
|
||||||
|
|
|
@ -31,6 +31,7 @@ namespace NzbDrone.Core.Configuration
|
||||||
string ApiKey { get; }
|
string ApiKey { get; }
|
||||||
bool Torrent { get; }
|
bool Torrent { get; }
|
||||||
string SslCertHash { get; }
|
string SslCertHash { get; }
|
||||||
|
string UrlBase { get; }
|
||||||
}
|
}
|
||||||
|
|
||||||
public class ConfigFileProvider : IConfigFileProvider
|
public class ConfigFileProvider : IConfigFileProvider
|
||||||
|
@ -152,6 +153,21 @@ namespace NzbDrone.Core.Configuration
|
||||||
get { return GetValue("SslCertHash", ""); }
|
get { return GetValue("SslCertHash", ""); }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public string UrlBase
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
var urlBase = GetValue("UrlBase", "");
|
||||||
|
|
||||||
|
if (String.IsNullOrEmpty(urlBase))
|
||||||
|
{
|
||||||
|
return urlBase;
|
||||||
|
}
|
||||||
|
|
||||||
|
return "/" + urlBase.Trim('/').ToLower();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public int GetValueInt(string key, int defaultValue)
|
public int GetValueInt(string key, int defaultValue)
|
||||||
{
|
{
|
||||||
return Convert.ToInt32(GetValue(key, defaultValue));
|
return Convert.ToInt32(GetValue(key, defaultValue));
|
||||||
|
@ -181,7 +197,7 @@ namespace NzbDrone.Core.Configuration
|
||||||
var valueHolder = parentContainer.Descendants(key).ToList();
|
var valueHolder = parentContainer.Descendants(key).ToList();
|
||||||
|
|
||||||
if (valueHolder.Count() == 1)
|
if (valueHolder.Count() == 1)
|
||||||
return valueHolder.First().Value;
|
return valueHolder.First().Value.Trim();
|
||||||
|
|
||||||
//Save the value
|
//Save the value
|
||||||
if (persist)
|
if (persist)
|
||||||
|
@ -198,6 +214,7 @@ namespace NzbDrone.Core.Configuration
|
||||||
{
|
{
|
||||||
EnsureDefaultConfigFile();
|
EnsureDefaultConfigFile();
|
||||||
|
|
||||||
|
var valueString = value.ToString().Trim();
|
||||||
var xDoc = LoadConfigFile();
|
var xDoc = LoadConfigFile();
|
||||||
var config = xDoc.Descendants(CONFIG_ELEMENT_NAME).Single();
|
var config = xDoc.Descendants(CONFIG_ELEMENT_NAME).Single();
|
||||||
|
|
||||||
|
@ -207,15 +224,15 @@ namespace NzbDrone.Core.Configuration
|
||||||
|
|
||||||
if (keyHolder.Count() != 1)
|
if (keyHolder.Count() != 1)
|
||||||
{
|
{
|
||||||
parentContainer.Add(new XElement(key, value));
|
parentContainer.Add(new XElement(key, valueString));
|
||||||
}
|
}
|
||||||
|
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
parentContainer.Descendants(key).Single().Value = value.ToString();
|
parentContainer.Descendants(key).Single().Value = valueString;
|
||||||
}
|
}
|
||||||
|
|
||||||
_cache.Set(key, value.ToString());
|
_cache.Set(key, valueString);
|
||||||
|
|
||||||
xDoc.Save(_configFile);
|
xDoc.Save(_configFile);
|
||||||
}
|
}
|
||||||
|
|
|
@ -231,8 +231,8 @@ namespace NzbDrone.Core.Configuration
|
||||||
|
|
||||||
public string ReleaseRestrictions
|
public string ReleaseRestrictions
|
||||||
{
|
{
|
||||||
get { return GetValue("ReleaseRestrictions", String.Empty); }
|
get { return GetValue("ReleaseRestrictions", String.Empty).Trim('\r', '\n'); }
|
||||||
set { SetValue("ReleaseRestrictions", value); }
|
set { SetValue("ReleaseRestrictions", value.Trim('\r', '\n')); }
|
||||||
}
|
}
|
||||||
|
|
||||||
public Int32 RssSyncInterval
|
public Int32 RssSyncInterval
|
||||||
|
|
|
@ -1,8 +0,0 @@
|
||||||
using NzbDrone.Core.Messaging.Commands;
|
|
||||||
|
|
||||||
namespace NzbDrone.Core.DataAugmentation.Xem
|
|
||||||
{
|
|
||||||
public class RefreshXemCacheCommand : Command
|
|
||||||
{
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,15 +1,15 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using System.Web.UI.WebControls;
|
||||||
using NLog;
|
using NLog;
|
||||||
using NzbDrone.Common.Cache;
|
using NzbDrone.Common.Cache;
|
||||||
using NzbDrone.Core.Messaging.Commands;
|
|
||||||
using NzbDrone.Core.Messaging.Events;
|
using NzbDrone.Core.Messaging.Events;
|
||||||
using NzbDrone.Core.Tv;
|
using NzbDrone.Core.Tv;
|
||||||
using NzbDrone.Core.Tv.Events;
|
using NzbDrone.Core.Tv.Events;
|
||||||
|
|
||||||
namespace NzbDrone.Core.DataAugmentation.Xem
|
namespace NzbDrone.Core.DataAugmentation.Xem
|
||||||
{
|
{
|
||||||
public class XemService : IHandle<SeriesUpdatedEvent>, IExecute<RefreshXemCacheCommand>
|
public class XemService : IHandle<SeriesUpdatedEvent>, IHandle<SeriesRefreshStartingEvent>
|
||||||
{
|
{
|
||||||
private readonly IEpisodeService _episodeService;
|
private readonly IEpisodeService _episodeService;
|
||||||
private readonly IXemProxy _xemProxy;
|
private readonly IXemProxy _xemProxy;
|
||||||
|
@ -84,10 +84,13 @@ namespace NzbDrone.Core.DataAugmentation.Xem
|
||||||
|
|
||||||
private void RefreshCache()
|
private void RefreshCache()
|
||||||
{
|
{
|
||||||
_cache.Clear();
|
|
||||||
|
|
||||||
var ids = _xemProxy.GetXemSeriesIds();
|
var ids = _xemProxy.GetXemSeriesIds();
|
||||||
|
|
||||||
|
if (ids.Any())
|
||||||
|
{
|
||||||
|
_cache.Clear();
|
||||||
|
}
|
||||||
|
|
||||||
foreach (var id in ids)
|
foreach (var id in ids)
|
||||||
{
|
{
|
||||||
_cache.Set(id.ToString(), true, TimeSpan.FromHours(1));
|
_cache.Set(id.ToString(), true, TimeSpan.FromHours(1));
|
||||||
|
@ -110,7 +113,7 @@ namespace NzbDrone.Core.DataAugmentation.Xem
|
||||||
PerformUpdate(message.Series);
|
PerformUpdate(message.Series);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Execute(RefreshXemCacheCommand message)
|
public void Handle(SeriesRefreshStartingEvent message)
|
||||||
{
|
{
|
||||||
RefreshCache();
|
RefreshCache();
|
||||||
}
|
}
|
||||||
|
|
|
@ -37,7 +37,7 @@ namespace NzbDrone.Core.DecisionEngine.Specifications
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
var restrictions = restrictionsString.Split('\n');
|
var restrictions = restrictionsString.Split(new []{ '\n' }, StringSplitOptions.RemoveEmptyEntries);
|
||||||
|
|
||||||
foreach (var restriction in restrictions)
|
foreach (var restriction in restrictions)
|
||||||
{
|
{
|
||||||
|
|
|
@ -124,7 +124,7 @@ namespace NzbDrone.Core.Download
|
||||||
|
|
||||||
private List<History.History> GetHistoryItems(List<History.History> grabbedHistory, string downloadClientId)
|
private List<History.History> GetHistoryItems(List<History.History> grabbedHistory, string downloadClientId)
|
||||||
{
|
{
|
||||||
return grabbedHistory.Where(h => h.Data.ContainsKey(DOWNLOAD_CLIENT) &&
|
return grabbedHistory.Where(h => h.Data.ContainsKey(DOWNLOAD_CLIENT_ID) &&
|
||||||
h.Data[DOWNLOAD_CLIENT_ID].Equals(downloadClientId))
|
h.Data[DOWNLOAD_CLIENT_ID].Equals(downloadClientId))
|
||||||
.ToList();
|
.ToList();
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,6 +14,14 @@ namespace NzbDrone.Core.Indexers.Eztv
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public override bool SupportsPaging
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public override IParseFeed Parser
|
public override IParseFeed Parser
|
||||||
{
|
{
|
||||||
get
|
get
|
||||||
|
|
|
@ -8,6 +8,7 @@ namespace NzbDrone.Core.Indexers
|
||||||
{
|
{
|
||||||
IParseFeed Parser { get; }
|
IParseFeed Parser { get; }
|
||||||
DownloadProtocol Protocol { get; }
|
DownloadProtocol Protocol { get; }
|
||||||
|
Boolean SupportsPaging { get; }
|
||||||
|
|
||||||
IEnumerable<string> RecentFeed { get; }
|
IEnumerable<string> RecentFeed { get; }
|
||||||
IEnumerable<string> GetEpisodeSearchUrls(string seriesTitle, int tvRageId, int seasonNumber, int episodeNumber);
|
IEnumerable<string> GetEpisodeSearchUrls(string seriesTitle, int tvRageId, int seasonNumber, int episodeNumber);
|
||||||
|
|
|
@ -5,6 +5,6 @@ namespace NzbDrone.Core.Indexers
|
||||||
{
|
{
|
||||||
public interface IParseFeed
|
public interface IParseFeed
|
||||||
{
|
{
|
||||||
IEnumerable<ReleaseInfo> Process(string source, string url);
|
IEnumerable<ReleaseInfo> Process(string xml, string url);
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -34,6 +34,8 @@ namespace NzbDrone.Core.Indexers
|
||||||
|
|
||||||
public abstract DownloadProtocol Protocol { get; }
|
public abstract DownloadProtocol Protocol { get; }
|
||||||
|
|
||||||
|
public abstract bool SupportsPaging { get; }
|
||||||
|
|
||||||
protected TSettings Settings
|
protected TSettings Settings
|
||||||
{
|
{
|
||||||
get
|
get
|
||||||
|
|
|
@ -13,7 +13,6 @@ namespace NzbDrone.Core.Indexers
|
||||||
public interface IFetchFeedFromIndexers
|
public interface IFetchFeedFromIndexers
|
||||||
{
|
{
|
||||||
IList<ReleaseInfo> FetchRss(IIndexer indexer);
|
IList<ReleaseInfo> FetchRss(IIndexer indexer);
|
||||||
|
|
||||||
IList<ReleaseInfo> Fetch(IIndexer indexer, SeasonSearchCriteria searchCriteria);
|
IList<ReleaseInfo> Fetch(IIndexer indexer, SeasonSearchCriteria searchCriteria);
|
||||||
IList<ReleaseInfo> Fetch(IIndexer indexer, SingleEpisodeSearchCriteria searchCriteria);
|
IList<ReleaseInfo> Fetch(IIndexer indexer, SingleEpisodeSearchCriteria searchCriteria);
|
||||||
IList<ReleaseInfo> Fetch(IIndexer indexer, DailyEpisodeSearchCriteria searchCriteria);
|
IList<ReleaseInfo> Fetch(IIndexer indexer, DailyEpisodeSearchCriteria searchCriteria);
|
||||||
|
@ -24,7 +23,6 @@ namespace NzbDrone.Core.Indexers
|
||||||
private readonly Logger _logger;
|
private readonly Logger _logger;
|
||||||
private readonly IHttpProvider _httpProvider;
|
private readonly IHttpProvider _httpProvider;
|
||||||
|
|
||||||
|
|
||||||
public FetchFeedService(IHttpProvider httpProvider, Logger logger)
|
public FetchFeedService(IHttpProvider httpProvider, Logger logger)
|
||||||
{
|
{
|
||||||
_httpProvider = httpProvider;
|
_httpProvider = httpProvider;
|
||||||
|
@ -60,12 +58,13 @@ namespace NzbDrone.Core.Indexers
|
||||||
var searchUrls = indexer.GetSeasonSearchUrls(searchCriteria.QueryTitle, searchCriteria.Series.TvRageId, searchCriteria.SeasonNumber, offset);
|
var searchUrls = indexer.GetSeasonSearchUrls(searchCriteria.QueryTitle, searchCriteria.Series.TvRageId, searchCriteria.SeasonNumber, offset);
|
||||||
var result = Fetch(indexer, searchUrls);
|
var result = Fetch(indexer, searchUrls);
|
||||||
|
|
||||||
|
|
||||||
_logger.Info("{0} offset {1}. Found {2}", indexer, searchCriteria, result.Count);
|
_logger.Info("{0} offset {1}. Found {2}", indexer, searchCriteria, result.Count);
|
||||||
|
|
||||||
if (result.Count > 90)
|
if (result.Count > 90 &&
|
||||||
|
offset < 900 &&
|
||||||
|
indexer.SupportsPaging)
|
||||||
{
|
{
|
||||||
result.AddRange(Fetch(indexer, searchCriteria, offset + 90));
|
result.AddRange(Fetch(indexer, searchCriteria, offset + 100));
|
||||||
}
|
}
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
|
|
|
@ -55,7 +55,6 @@ namespace NzbDrone.Core.Indexers.Newznab
|
||||||
});
|
});
|
||||||
|
|
||||||
return list;
|
return list;
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -73,6 +72,14 @@ namespace NzbDrone.Core.Indexers.Newznab
|
||||||
return settings;
|
return settings;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public override bool SupportsPaging
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public override IEnumerable<string> RecentFeed
|
public override IEnumerable<string> RecentFeed
|
||||||
{
|
{
|
||||||
get
|
get
|
||||||
|
|
|
@ -66,5 +66,13 @@ namespace NzbDrone.Core.Indexers.Omgwtfnzbs
|
||||||
|
|
||||||
return searchUrls;
|
return searchUrls;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public override bool SupportsPaging
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,6 +14,14 @@ namespace NzbDrone.Core.Indexers.Wombles
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public override bool SupportsPaging
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public override IParseFeed Parser
|
public override IParseFeed Parser
|
||||||
{
|
{
|
||||||
get
|
get
|
||||||
|
@ -24,7 +32,7 @@ namespace NzbDrone.Core.Indexers.Wombles
|
||||||
|
|
||||||
public override IEnumerable<string> RecentFeed
|
public override IEnumerable<string> RecentFeed
|
||||||
{
|
{
|
||||||
get { yield return "http://nzb.isasecret.com/rss/?sec=TV&fr=false"; }
|
get { yield return "http://newshost.co.za/rss/?sec=TV&fr=false"; }
|
||||||
}
|
}
|
||||||
|
|
||||||
public override IEnumerable<string> GetEpisodeSearchUrls(string seriesTitle, int tvRageId, int seasonNumber, int episodeNumber)
|
public override IEnumerable<string> GetEpisodeSearchUrls(string seriesTitle, int tvRageId, int seasonNumber, int episodeNumber)
|
||||||
|
|
|
@ -53,10 +53,8 @@ namespace NzbDrone.Core.Jobs
|
||||||
new ScheduledTask{ Interval = 1*60, TypeName = typeof(ApplicationUpdateCommand).FullName},
|
new ScheduledTask{ Interval = 1*60, TypeName = typeof(ApplicationUpdateCommand).FullName},
|
||||||
new ScheduledTask{ Interval = 1*60, TypeName = typeof(TrimLogCommand).FullName},
|
new ScheduledTask{ Interval = 1*60, TypeName = typeof(TrimLogCommand).FullName},
|
||||||
new ScheduledTask{ Interval = 3*60, TypeName = typeof(UpdateSceneMappingCommand).FullName},
|
new ScheduledTask{ Interval = 3*60, TypeName = typeof(UpdateSceneMappingCommand).FullName},
|
||||||
new ScheduledTask{ Interval = 12*60, TypeName = typeof(RefreshXemCacheCommand).FullName},
|
|
||||||
new ScheduledTask{ Interval = 12*60, TypeName = typeof(RefreshSeriesCommand).FullName},
|
new ScheduledTask{ Interval = 12*60, TypeName = typeof(RefreshSeriesCommand).FullName},
|
||||||
new ScheduledTask{ Interval = 24*60, TypeName = typeof(HousekeepingCommand).FullName},
|
new ScheduledTask{ Interval = 24*60, TypeName = typeof(HousekeepingCommand).FullName},
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
var currentTasks = _scheduledTaskRepository.All();
|
var currentTasks = _scheduledTaskRepository.All();
|
||||||
|
|
|
@ -5,6 +5,7 @@ using System.Net;
|
||||||
using NLog;
|
using NLog;
|
||||||
using NzbDrone.Common;
|
using NzbDrone.Common;
|
||||||
using NzbDrone.Common.EnvironmentInfo;
|
using NzbDrone.Common.EnvironmentInfo;
|
||||||
|
using NzbDrone.Core.Configuration;
|
||||||
using NzbDrone.Core.Messaging.Events;
|
using NzbDrone.Core.Messaging.Events;
|
||||||
using NzbDrone.Core.Tv;
|
using NzbDrone.Core.Tv;
|
||||||
using NzbDrone.Core.Tv.Events;
|
using NzbDrone.Core.Tv.Events;
|
||||||
|
@ -19,16 +20,18 @@ namespace NzbDrone.Core.MediaCover
|
||||||
private readonly IHttpProvider _httpProvider;
|
private readonly IHttpProvider _httpProvider;
|
||||||
private readonly IDiskProvider _diskProvider;
|
private readonly IDiskProvider _diskProvider;
|
||||||
private readonly ICoverExistsSpecification _coverExistsSpecification;
|
private readonly ICoverExistsSpecification _coverExistsSpecification;
|
||||||
|
private readonly IConfigFileProvider _configFileProvider;
|
||||||
private readonly Logger _logger;
|
private readonly Logger _logger;
|
||||||
|
|
||||||
private readonly string _coverRootFolder;
|
private readonly string _coverRootFolder;
|
||||||
|
|
||||||
public MediaCoverService(IHttpProvider httpProvider, IDiskProvider diskProvider, IAppFolderInfo appFolderInfo,
|
public MediaCoverService(IHttpProvider httpProvider, IDiskProvider diskProvider, IAppFolderInfo appFolderInfo,
|
||||||
ICoverExistsSpecification coverExistsSpecification, Logger logger)
|
ICoverExistsSpecification coverExistsSpecification, IConfigFileProvider configFileProvider, Logger logger)
|
||||||
{
|
{
|
||||||
_httpProvider = httpProvider;
|
_httpProvider = httpProvider;
|
||||||
_diskProvider = diskProvider;
|
_diskProvider = diskProvider;
|
||||||
_coverExistsSpecification = coverExistsSpecification;
|
_coverExistsSpecification = coverExistsSpecification;
|
||||||
|
_configFileProvider = configFileProvider;
|
||||||
_logger = logger;
|
_logger = logger;
|
||||||
|
|
||||||
_coverRootFolder = appFolderInfo.GetMediaCoverPath();
|
_coverRootFolder = appFolderInfo.GetMediaCoverPath();
|
||||||
|
@ -96,7 +99,7 @@ namespace NzbDrone.Core.MediaCover
|
||||||
{
|
{
|
||||||
var filePath = GetCoverPath(seriesId, mediaCover.CoverType);
|
var filePath = GetCoverPath(seriesId, mediaCover.CoverType);
|
||||||
|
|
||||||
mediaCover.Url = @"/MediaCover/" + seriesId + "/" + mediaCover.CoverType.ToString().ToLower() + ".jpg";
|
mediaCover.Url = _configFileProvider.UrlBase + @"/MediaCover/" + seriesId + "/" + mediaCover.CoverType.ToString().ToLower() + ".jpg";
|
||||||
|
|
||||||
if (_diskProvider.FileExists(filePath))
|
if (_diskProvider.FileExists(filePath))
|
||||||
{
|
{
|
||||||
|
|
|
@ -7,18 +7,18 @@ namespace NzbDrone.Core.Notifications.PushBullet
|
||||||
{
|
{
|
||||||
public interface IPushBulletProxy
|
public interface IPushBulletProxy
|
||||||
{
|
{
|
||||||
void SendNotification(string title, string message, string apiKey, long deviceId);
|
void SendNotification(string title, string message, string apiKey, string deviceId);
|
||||||
}
|
}
|
||||||
|
|
||||||
public class PushBulletProxy : IPushBulletProxy, IExecute<TestPushBulletCommand>
|
public class PushBulletProxy : IPushBulletProxy, IExecute<TestPushBulletCommand>
|
||||||
{
|
{
|
||||||
private const string URL = "https://api.pushbullet.com/api/pushes";
|
private const string URL = "https://api.pushbullet.com/api/pushes";
|
||||||
|
|
||||||
public void SendNotification(string title, string message, string apiKey, long deviceId)
|
public void SendNotification(string title, string message, string apiKey, string deviceId)
|
||||||
{
|
{
|
||||||
var client = new RestClient(URL);
|
var client = new RestClient(URL);
|
||||||
var request = new RestRequest(Method.POST);
|
var request = BuildRequest(deviceId);
|
||||||
request.AddParameter("device_id", deviceId);
|
|
||||||
request.AddParameter("type", "note");
|
request.AddParameter("type", "note");
|
||||||
request.AddParameter("title", title);
|
request.AddParameter("title", title);
|
||||||
request.AddParameter("body", message);
|
request.AddParameter("body", message);
|
||||||
|
@ -27,6 +27,24 @@ namespace NzbDrone.Core.Notifications.PushBullet
|
||||||
client.ExecuteAndValidate(request);
|
client.ExecuteAndValidate(request);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public RestRequest BuildRequest(string deviceId)
|
||||||
|
{
|
||||||
|
var request = new RestRequest(Method.POST);
|
||||||
|
long integerId;
|
||||||
|
|
||||||
|
if (Int64.TryParse(deviceId, out integerId))
|
||||||
|
{
|
||||||
|
request.AddParameter("device_id", integerId);
|
||||||
|
}
|
||||||
|
|
||||||
|
else
|
||||||
|
{
|
||||||
|
request.AddParameter("device_iden", deviceId);
|
||||||
|
}
|
||||||
|
|
||||||
|
return request;
|
||||||
|
}
|
||||||
|
|
||||||
public void Execute(TestPushBulletCommand message)
|
public void Execute(TestPushBulletCommand message)
|
||||||
{
|
{
|
||||||
const string title = "Test Notification";
|
const string title = "Test Notification";
|
||||||
|
|
|
@ -11,7 +11,7 @@ namespace NzbDrone.Core.Notifications.PushBullet
|
||||||
public PushBulletSettingsValidator()
|
public PushBulletSettingsValidator()
|
||||||
{
|
{
|
||||||
RuleFor(c => c.ApiKey).NotEmpty();
|
RuleFor(c => c.ApiKey).NotEmpty();
|
||||||
RuleFor(c => c.DeviceId).GreaterThan(0);
|
RuleFor(c => c.DeviceId).NotEmpty();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -23,13 +23,13 @@ namespace NzbDrone.Core.Notifications.PushBullet
|
||||||
public String ApiKey { get; set; }
|
public String ApiKey { get; set; }
|
||||||
|
|
||||||
[FieldDefinition(1, Label = "Device ID")]
|
[FieldDefinition(1, Label = "Device ID")]
|
||||||
public Int64 DeviceId { get; set; }
|
public String DeviceId { get; set; }
|
||||||
|
|
||||||
public bool IsValid
|
public bool IsValid
|
||||||
{
|
{
|
||||||
get
|
get
|
||||||
{
|
{
|
||||||
return !String.IsNullOrWhiteSpace(ApiKey) && DeviceId > 0;
|
return !String.IsNullOrWhiteSpace(ApiKey) && !String.IsNullOrWhiteSpace(DeviceId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -13,6 +13,6 @@ namespace NzbDrone.Core.Notifications.PushBullet
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
public string ApiKey { get; set; }
|
public string ApiKey { get; set; }
|
||||||
public long DeviceId { get; set; }
|
public string DeviceId { get; set; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -139,7 +139,6 @@
|
||||||
<Compile Include="DataAugmentation\Xem\Model\XemResult.cs" />
|
<Compile Include="DataAugmentation\Xem\Model\XemResult.cs" />
|
||||||
<Compile Include="DataAugmentation\Xem\Model\XemSceneTvdbMapping.cs" />
|
<Compile Include="DataAugmentation\Xem\Model\XemSceneTvdbMapping.cs" />
|
||||||
<Compile Include="DataAugmentation\Xem\Model\XemValues.cs" />
|
<Compile Include="DataAugmentation\Xem\Model\XemValues.cs" />
|
||||||
<Compile Include="DataAugmentation\Xem\RefreshXemCacheCommand.cs" />
|
|
||||||
<Compile Include="DataAugmentation\Xem\XemProxy.cs" />
|
<Compile Include="DataAugmentation\Xem\XemProxy.cs" />
|
||||||
<Compile Include="DataAugmentation\Xem\XemService.cs" />
|
<Compile Include="DataAugmentation\Xem\XemService.cs" />
|
||||||
<Compile Include="Datastore\ConnectionStringFactory.cs" />
|
<Compile Include="Datastore\ConnectionStringFactory.cs" />
|
||||||
|
@ -477,6 +476,7 @@
|
||||||
<Compile Include="ThingiProvider\ProviderRepository.cs" />
|
<Compile Include="ThingiProvider\ProviderRepository.cs" />
|
||||||
<Compile Include="ThingiProvider\ProviderFactory.cs" />
|
<Compile Include="ThingiProvider\ProviderFactory.cs" />
|
||||||
<Compile Include="Tv\EpisodeService.cs" />
|
<Compile Include="Tv\EpisodeService.cs" />
|
||||||
|
<Compile Include="Tv\Events\SeriesRefreshStartingEvent.cs" />
|
||||||
<Compile Include="Tv\Events\EpisodeInfoDeletedEvent.cs" />
|
<Compile Include="Tv\Events\EpisodeInfoDeletedEvent.cs" />
|
||||||
<Compile Include="Tv\Events\EpisodeInfoUpdatedEvent.cs" />
|
<Compile Include="Tv\Events\EpisodeInfoUpdatedEvent.cs" />
|
||||||
<Compile Include="Tv\Events\EpisodeInfoAddedEvent.cs" />
|
<Compile Include="Tv\Events\EpisodeInfoAddedEvent.cs" />
|
||||||
|
|
|
@ -0,0 +1,8 @@
|
||||||
|
using NzbDrone.Common.Messaging;
|
||||||
|
|
||||||
|
namespace NzbDrone.Core.Tv.Events
|
||||||
|
{
|
||||||
|
public class SeriesRefreshStartingEvent : IEvent
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
|
@ -99,6 +99,8 @@ namespace NzbDrone.Core.Tv
|
||||||
|
|
||||||
public void Execute(RefreshSeriesCommand message)
|
public void Execute(RefreshSeriesCommand message)
|
||||||
{
|
{
|
||||||
|
_eventAggregator.PublishEvent(new SeriesRefreshStartingEvent());
|
||||||
|
|
||||||
if (message.SeriesId.HasValue)
|
if (message.SeriesId.HasValue)
|
||||||
{
|
{
|
||||||
var series = _seriesService.GetSeries(message.SeriesId.Value);
|
var series = _seriesService.GetSeries(message.SeriesId.Value);
|
||||||
|
|
|
@ -5,5 +5,13 @@ namespace NzbDrone.Core.Update.Commands
|
||||||
public class InstallUpdateCommand : Command
|
public class InstallUpdateCommand : Command
|
||||||
{
|
{
|
||||||
public UpdatePackage UpdatePackage { get; set; }
|
public UpdatePackage UpdatePackage { get; set; }
|
||||||
|
|
||||||
|
public override bool SendUpdatesToClient
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
using System;
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using NLog;
|
using NLog;
|
||||||
using NzbDrone.Common.EnvironmentInfo;
|
using NzbDrone.Common.EnvironmentInfo;
|
||||||
|
@ -9,8 +10,7 @@ namespace NzbDrone.Host.AccessControl
|
||||||
public interface IUrlAclAdapter
|
public interface IUrlAclAdapter
|
||||||
{
|
{
|
||||||
void ConfigureUrl();
|
void ConfigureUrl();
|
||||||
string Url { get; }
|
List<String> Urls { get; }
|
||||||
string HttpsUrl { get; }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public class UrlAclAdapter : IUrlAclAdapter
|
public class UrlAclAdapter : IUrlAclAdapter
|
||||||
|
@ -20,13 +20,7 @@ namespace NzbDrone.Host.AccessControl
|
||||||
private readonly IRuntimeInfo _runtimeInfo;
|
private readonly IRuntimeInfo _runtimeInfo;
|
||||||
private readonly Logger _logger;
|
private readonly Logger _logger;
|
||||||
|
|
||||||
public string Url { get; private set; }
|
public List<String> Urls { get; private set; }
|
||||||
public string HttpsUrl { get; private set; }
|
|
||||||
|
|
||||||
private string _localUrl;
|
|
||||||
private string _wildcardUrl;
|
|
||||||
private string _localHttpsUrl;
|
|
||||||
private string _wildcardHttpsUrl;
|
|
||||||
|
|
||||||
public UrlAclAdapter(INetshProvider netshProvider,
|
public UrlAclAdapter(INetshProvider netshProvider,
|
||||||
IConfigFileProvider configFileProvider,
|
IConfigFileProvider configFileProvider,
|
||||||
|
@ -38,36 +32,44 @@ namespace NzbDrone.Host.AccessControl
|
||||||
_runtimeInfo = runtimeInfo;
|
_runtimeInfo = runtimeInfo;
|
||||||
_logger = logger;
|
_logger = logger;
|
||||||
|
|
||||||
_localUrl = String.Format("http://localhost:{0}/", _configFileProvider.Port);
|
Urls = new List<String>();
|
||||||
_wildcardUrl = String.Format("http://*:{0}/", _configFileProvider.Port);
|
|
||||||
_localHttpsUrl = String.Format("https://localhost:{0}/", _configFileProvider.SslPort);
|
|
||||||
_wildcardHttpsUrl = String.Format("https://*:{0}/", _configFileProvider.SslPort);
|
|
||||||
|
|
||||||
Url = _wildcardUrl;
|
|
||||||
HttpsUrl = _wildcardHttpsUrl;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void ConfigureUrl()
|
public void ConfigureUrl()
|
||||||
{
|
{
|
||||||
if (!_runtimeInfo.IsAdmin)
|
var localHttpUrls = BuildUrls("http", "localhost", _configFileProvider.Port);
|
||||||
|
var wildcardHttpUrls = BuildUrls("http", "*", _configFileProvider.Port);
|
||||||
|
|
||||||
|
var localHttpsUrls = BuildUrls("https", "localhost", _configFileProvider.SslPort);
|
||||||
|
var wildcardHttpsUrls = BuildUrls("https", "*", _configFileProvider.SslPort);
|
||||||
|
|
||||||
|
if (OsInfo.IsWindows && !_runtimeInfo.IsAdmin)
|
||||||
{
|
{
|
||||||
if (!IsRegistered(_wildcardUrl)) Url = _localUrl;
|
var httpUrls = wildcardHttpUrls.All(IsRegistered) ? wildcardHttpUrls : localHttpUrls;
|
||||||
if (!IsRegistered(_wildcardHttpsUrl)) HttpsUrl = _localHttpsUrl;
|
var httpsUrls = wildcardHttpsUrls.All(IsRegistered) ? wildcardHttpsUrls : localHttpsUrls;
|
||||||
|
|
||||||
|
Urls.AddRange(httpUrls);
|
||||||
|
Urls.AddRange(httpsUrls);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (_runtimeInfo.IsAdmin)
|
else
|
||||||
|
{
|
||||||
|
Urls.AddRange(wildcardHttpUrls);
|
||||||
|
Urls.AddRange(wildcardHttpsUrls);
|
||||||
|
|
||||||
|
if (OsInfo.IsWindows)
|
||||||
{
|
{
|
||||||
RefreshRegistration();
|
RefreshRegistration();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private void RefreshRegistration()
|
private void RefreshRegistration()
|
||||||
{
|
{
|
||||||
if (OsInfo.Version.Major < 6)
|
if (OsInfo.Version.Major < 6)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
RegisterUrl(Url);
|
Urls.ForEach(RegisterUrl);
|
||||||
RegisterUrl(HttpsUrl);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private bool IsRegistered(string urlAcl)
|
private bool IsRegistered(string urlAcl)
|
||||||
|
@ -85,5 +87,28 @@ namespace NzbDrone.Host.AccessControl
|
||||||
var arguments = String.Format("http add urlacl {0} sddl=D:(A;;GX;;;S-1-1-0)", urlAcl);
|
var arguments = String.Format("http add urlacl {0} sddl=D:(A;;GX;;;S-1-1-0)", urlAcl);
|
||||||
_netshProvider.Run(arguments);
|
_netshProvider.Run(arguments);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private string BuildUrl(string protocol, string url, int port, string urlBase)
|
||||||
|
{
|
||||||
|
var result = protocol + "://" + url + ":" + port;
|
||||||
|
result += String.IsNullOrEmpty(urlBase) ? "/" : urlBase + "/";
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<String> BuildUrls(string protocol, string url, int port)
|
||||||
|
{
|
||||||
|
var urls = new List<String>();
|
||||||
|
var urlBase = _configFileProvider.UrlBase;
|
||||||
|
|
||||||
|
if (!String.IsNullOrEmpty(urlBase))
|
||||||
|
{
|
||||||
|
urls.Add(BuildUrl(protocol, url, port, urlBase));
|
||||||
|
}
|
||||||
|
|
||||||
|
urls.Add(BuildUrl(protocol, url, port, ""));
|
||||||
|
|
||||||
|
return urls;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -40,8 +40,11 @@ namespace NzbDrone.Host
|
||||||
startCallback(_container);
|
startCallback(_container);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
else
|
||||||
|
{
|
||||||
SpinToExit(appMode);
|
SpinToExit(appMode);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
catch (TerminateApplicationException e)
|
catch (TerminateApplicationException e)
|
||||||
{
|
{
|
||||||
Logger.Info(e.Message);
|
Logger.Info(e.Message);
|
||||||
|
|
|
@ -53,29 +53,25 @@ namespace NzbDrone.Host.Owin
|
||||||
_firewallAdapter.MakeAccessible();
|
_firewallAdapter.MakeAccessible();
|
||||||
_sslAdapter.Register();
|
_sslAdapter.Register();
|
||||||
}
|
}
|
||||||
|
|
||||||
_urlAclAdapter.ConfigureUrl();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var options = new StartOptions(_urlAclAdapter.Url)
|
_urlAclAdapter.ConfigureUrl();
|
||||||
|
|
||||||
|
var options = new StartOptions()
|
||||||
{
|
{
|
||||||
ServerFactory = "Microsoft.Owin.Host.HttpListener"
|
ServerFactory = "Microsoft.Owin.Host.HttpListener"
|
||||||
};
|
};
|
||||||
|
|
||||||
if (_configFileProvider.EnableSsl)
|
_urlAclAdapter.Urls.ForEach(options.Urls.Add);
|
||||||
{
|
|
||||||
_logger.Trace("SSL enabled, listening on: {0}", _urlAclAdapter.HttpsUrl);
|
|
||||||
options.Urls.Add(_urlAclAdapter.HttpsUrl);
|
|
||||||
}
|
|
||||||
|
|
||||||
_logger.Info("starting server on {0}", _urlAclAdapter.Url);
|
_logger.Info("Listening on the following URLs:");
|
||||||
|
foreach (var url in options.Urls)
|
||||||
|
{
|
||||||
|
_logger.Info(" {0}", url);
|
||||||
|
}
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
// options.ServerFactory = new
|
|
||||||
//_host = WebApp.Start(OwinServiceProviderFactory.Create(), options, BuildApp);
|
|
||||||
//_host = WebApp.Start(options, BuildApp);
|
|
||||||
|
|
||||||
_host = WebApp.Start(OwinServiceProviderFactory.Create(), options, BuildApp);
|
_host = WebApp.Start(OwinServiceProviderFactory.Create(), options, BuildApp);
|
||||||
}
|
}
|
||||||
catch (TargetInvocationException ex)
|
catch (TargetInvocationException ex)
|
||||||
|
|
|
@ -23,7 +23,6 @@ namespace NzbDrone.SysTray
|
||||||
_browserService = browserService;
|
_browserService = browserService;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public void Start()
|
public void Start()
|
||||||
{
|
{
|
||||||
Application.ThreadException += OnThreadException;
|
Application.ThreadException += OnThreadException;
|
||||||
|
|
|
@ -34,7 +34,5 @@ namespace NzbDrone
|
||||||
MessageBox.Show(text: message, buttons: MessageBoxButtons.OK, icon: MessageBoxIcon.Error, caption: "Epic Fail!");
|
MessageBox.Show(text: message, buttons: MessageBoxButtons.OK, icon: MessageBoxIcon.Error, caption: "Epic Fail!");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,69 +0,0 @@
|
||||||
'use strict';
|
|
||||||
|
|
||||||
define(
|
|
||||||
[
|
|
||||||
'backgrid',
|
|
||||||
'Shared/Grid/HeaderCell'
|
|
||||||
], function (Backgrid, NzbDroneHeaderCell) {
|
|
||||||
|
|
||||||
Backgrid.QualityHeaderCell = NzbDroneHeaderCell.extend({
|
|
||||||
events: {
|
|
||||||
'click': 'onClick'
|
|
||||||
},
|
|
||||||
|
|
||||||
onClick: function (e) {
|
|
||||||
e.preventDefault();
|
|
||||||
|
|
||||||
var self = this;
|
|
||||||
var columnName = this.column.get('name');
|
|
||||||
|
|
||||||
if (this.column.get('sortable')) {
|
|
||||||
if (this.direction() === 'ascending') {
|
|
||||||
this.sort(columnName, 'descending', function (left, right) {
|
|
||||||
var leftVal = left.get(columnName);
|
|
||||||
var rightVal = right.get(columnName);
|
|
||||||
|
|
||||||
return self._comparator(leftVal, rightVal);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
this.sort(columnName, 'ascending', function (left, right) {
|
|
||||||
var leftVal = left.get(columnName);
|
|
||||||
var rightVal = right.get(columnName);
|
|
||||||
|
|
||||||
return self._comparator(rightVal, leftVal);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
_comparator: function (leftVal, rightVal) {
|
|
||||||
var leftWeight = leftVal.quality.weight;
|
|
||||||
var rightWeight = rightVal.quality.weight;
|
|
||||||
|
|
||||||
if (!leftWeight && !rightWeight) {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!leftWeight) {
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!rightWeight) {
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (leftWeight === rightWeight) {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (leftWeight > rightWeight) {
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
return Backgrid.QualityHeaderCell;
|
|
||||||
});
|
|
|
@ -8,8 +8,11 @@ define(
|
||||||
className : 'season-folder-cell',
|
className : 'season-folder-cell',
|
||||||
|
|
||||||
render: function () {
|
render: function () {
|
||||||
var seasonFolder = this.model.get('seasonFolder');
|
this.$el.empty();
|
||||||
|
|
||||||
|
var seasonFolder = this.model.get(this.column.get('name'));
|
||||||
this.$el.html(seasonFolder.toString());
|
this.$el.html(seasonFolder.toString());
|
||||||
|
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
|
@ -2,46 +2,46 @@
|
||||||
font-family: 'Open Sans';
|
font-family: 'Open Sans';
|
||||||
font-style: normal;
|
font-style: normal;
|
||||||
font-weight: 300;
|
font-weight: 300;
|
||||||
src: url('/Content/fonts/opensans-light.eot');
|
src: url('./fonts/opensans-light.eot');
|
||||||
src: local('Open Sans Light'),
|
src: local('Open Sans Light'),
|
||||||
local('OpenSans-Light'),
|
local('OpenSans-Light'),
|
||||||
url('/Content/fonts/opensans-light.eot?#iefix') format('embedded-opentype'),
|
url('./fonts/opensans-light.eot?#iefix') format('embedded-opentype'),
|
||||||
url('/Content/fonts/opensans-light.woff') format('woff'),
|
url('./fonts/opensans-light.woff') format('woff'),
|
||||||
url('/Content/fonts/opensans-light.ttf') format('truetype');
|
url('./fonts/opensans-light.ttf') format('truetype');
|
||||||
}
|
}
|
||||||
|
|
||||||
@font-face {
|
@font-face {
|
||||||
font-family: 'Open Sans';
|
font-family: 'Open Sans';
|
||||||
font-style: normal;
|
font-style: normal;
|
||||||
font-weight: 400;
|
font-weight: 400;
|
||||||
src: url('/Content/fonts/opensans-regular.eot');
|
src: url('./fonts/opensans-regular.eot');
|
||||||
src: local('Open Sans'),
|
src: local('Open Sans'),
|
||||||
local('OpenSans'),
|
local('OpenSans'),
|
||||||
url('/Content/fonts/opensans-regular.eot?#iefix') format('embedded-opentype'),
|
url('./fonts/opensans-regular.eot?#iefix') format('embedded-opentype'),
|
||||||
url('/Content/fonts/opensans-regular.woff') format('woff'),
|
url('./fonts/opensans-regular.woff') format('woff'),
|
||||||
url('/Content/fonts/opensans-regular.ttf') format('truetype')
|
url('./fonts/opensans-regular.ttf') format('truetype')
|
||||||
}
|
}
|
||||||
|
|
||||||
@font-face {
|
@font-face {
|
||||||
font-family: 'Open Sans';
|
font-family: 'Open Sans';
|
||||||
font-style: normal;
|
font-style: normal;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
src: url('/Content/fonts/opensans-semibold.eot');
|
src: url('./fonts/opensans-semibold.eot');
|
||||||
src: local('Open Sans SemiBold'),
|
src: local('Open Sans SemiBold'),
|
||||||
local('OpenSans-SemiBold'),
|
local('OpenSans-SemiBold'),
|
||||||
url('/Content/fonts/opensans-semibold.eot?#iefix') format('embedded-opentype'),
|
url('./fonts/opensans-semibold.eot?#iefix') format('embedded-opentype'),
|
||||||
url('/Content/fonts/opensans-semibold.woff') format('woff'),
|
url('./fonts/opensans-semibold.woff') format('woff'),
|
||||||
url('/Content/fonts/opensans-semibold.ttf') format('truetype')
|
url('./fonts/opensans-semibold.ttf') format('truetype')
|
||||||
}
|
}
|
||||||
|
|
||||||
@font-face {
|
@font-face {
|
||||||
font-family: 'Ubuntu Mono';
|
font-family: 'Ubuntu Mono';
|
||||||
font-style: normal;
|
font-style: normal;
|
||||||
font-weight: 400;
|
font-weight: 400;
|
||||||
src: url('/Content/fonts/ubuntumono-regular.eot');
|
src: url('./fonts/ubuntumono-regular.eot');
|
||||||
src: local('Open Sans'),
|
src: local('Open Sans'),
|
||||||
local('OpenSans'),
|
local('OpenSans'),
|
||||||
url('/Content/fonts/ubuntumono-regular.eot?#iefix') format('embedded-opentype'),
|
url('./fonts/ubuntumono-regular.eot?#iefix') format('embedded-opentype'),
|
||||||
url('/Content/fonts/ubuntumono-regular.woff') format('woff'),
|
url('./fonts/ubuntumono-regular.woff') format('woff'),
|
||||||
url('/Content/fonts/ubuntumono-regular.ttf') format('truetype')
|
url('./fonts/ubuntumono-regular.ttf') format('truetype')
|
||||||
}
|
}
|
|
@ -46,6 +46,17 @@
|
||||||
.page-toolbar {
|
.page-toolbar {
|
||||||
margin-top : 10px;
|
margin-top : 10px;
|
||||||
margin-bottom : 30px;
|
margin-bottom : 30px;
|
||||||
|
|
||||||
|
.toolbar-group {
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sorting-buttons {
|
||||||
|
.sorting-title {
|
||||||
|
display: inline-block;
|
||||||
|
width: 110px;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.page-container {
|
.page-container {
|
||||||
|
|
|
@ -47,7 +47,7 @@ define(
|
||||||
this.model = options.model;
|
this.model = options.model;
|
||||||
this.series = options.series;
|
this.series = options.series;
|
||||||
|
|
||||||
this.collection = new HistoryCollection({ episodeId: this.model.id });
|
this.collection = new HistoryCollection({ episodeId: this.model.id, tableName: 'episodeActivity' });
|
||||||
this.collection.fetch();
|
this.collection.fetch();
|
||||||
this.listenTo(this.collection, 'sync', this._showTable);
|
this.listenTo(this.collection, 'sync', this._showTable);
|
||||||
},
|
},
|
||||||
|
|
|
@ -6,10 +6,8 @@ define(
|
||||||
'Cells/FileSizeCell',
|
'Cells/FileSizeCell',
|
||||||
'Cells/QualityCell',
|
'Cells/QualityCell',
|
||||||
'Cells/ApprovalStatusCell',
|
'Cells/ApprovalStatusCell',
|
||||||
'Release/DownloadReportCell',
|
'Release/DownloadReportCell'
|
||||||
'Cells/Header/QualityHeaderCell'
|
], function (Marionette, Backgrid, FileSizeCell, QualityCell, ApprovalStatusCell, DownloadReportCell) {
|
||||||
|
|
||||||
], function (Marionette, Backgrid, FileSizeCell, QualityCell, ApprovalStatusCell, DownloadReportCell, QualityHeaderCell) {
|
|
||||||
|
|
||||||
return Marionette.Layout.extend({
|
return Marionette.Layout.extend({
|
||||||
template: 'Episode/Search/ManualLayoutTemplate',
|
template: 'Episode/Search/ManualLayoutTemplate',
|
||||||
|
@ -49,7 +47,9 @@ define(
|
||||||
label : 'Quality',
|
label : 'Quality',
|
||||||
sortable : true,
|
sortable : true,
|
||||||
cell : QualityCell,
|
cell : QualityCell,
|
||||||
headerCell: QualityHeaderCell
|
sortValue : function (model) {
|
||||||
|
return model.get('quality').quality.weight;
|
||||||
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
{
|
{
|
||||||
|
|
|
@ -2,10 +2,11 @@
|
||||||
|
|
||||||
define(
|
define(
|
||||||
[
|
[
|
||||||
'handlebars'
|
'handlebars',
|
||||||
], function (Handlebars) {
|
'System/StatusModel'
|
||||||
|
], function (Handlebars, StatusModel) {
|
||||||
|
|
||||||
var placeHolder = '/Content/Images/poster-dark.jpg';
|
var placeHolder = StatusModel.get('urlBase') + '/Content/Images/poster-dark.jpg';
|
||||||
|
|
||||||
window.NzbDrone.imageError = function (img) {
|
window.NzbDrone.imageError = function (img) {
|
||||||
if (!img.src.contains(placeHolder)) {
|
if (!img.src.contains(placeHolder)) {
|
||||||
|
@ -17,4 +18,8 @@ define(
|
||||||
Handlebars.registerHelper('defaultImg', function () {
|
Handlebars.registerHelper('defaultImg', function () {
|
||||||
return new Handlebars.SafeString('onerror=window.NzbDrone.imageError(this)');
|
return new Handlebars.SafeString('onerror=window.NzbDrone.imageError(this)');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
Handlebars.registerHelper('UrlBase', function () {
|
||||||
|
return new Handlebars.SafeString(StatusModel.get('urlBase'));
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -2,8 +2,9 @@
|
||||||
define(
|
define(
|
||||||
[
|
[
|
||||||
'handlebars',
|
'handlebars',
|
||||||
|
'System/StatusModel',
|
||||||
'underscore'
|
'underscore'
|
||||||
], function (Handlebars, _) {
|
], function (Handlebars, StatusModel, _) {
|
||||||
Handlebars.registerHelper('poster', function () {
|
Handlebars.registerHelper('poster', function () {
|
||||||
|
|
||||||
var poster = _.where(this.images, {coverType: 'poster'});
|
var poster = _.where(this.images, {coverType: 'poster'});
|
||||||
|
@ -32,7 +33,7 @@ define(
|
||||||
});
|
});
|
||||||
|
|
||||||
Handlebars.registerHelper('route', function () {
|
Handlebars.registerHelper('route', function () {
|
||||||
return '/series/' + this.titleSlug;
|
return StatusModel.get('urlBase') + '/series/' + this.titleSlug;
|
||||||
});
|
});
|
||||||
|
|
||||||
Handlebars.registerHelper('percentOfEpisodes', function () {
|
Handlebars.registerHelper('percentOfEpisodes', function () {
|
||||||
|
|
|
@ -2,9 +2,10 @@
|
||||||
define(
|
define(
|
||||||
[
|
[
|
||||||
'History/HistoryModel',
|
'History/HistoryModel',
|
||||||
'backbone.pageable'
|
'backbone.pageable',
|
||||||
], function (HistoryModel, PageableCollection) {
|
'Mixins/AsPersistedStateCollection'
|
||||||
return PageableCollection.extend({
|
], function (HistoryModel, PageableCollection, AsPersistedStateCollection) {
|
||||||
|
var collection = PageableCollection.extend({
|
||||||
url : window.NzbDrone.ApiRoot + '/history',
|
url : window.NzbDrone.ApiRoot + '/history',
|
||||||
model: HistoryModel,
|
model: HistoryModel,
|
||||||
|
|
||||||
|
@ -48,4 +49,6 @@ define(
|
||||||
return resp;
|
return resp;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
return AsPersistedStateCollection.apply(collection);
|
||||||
});
|
});
|
||||||
|
|
|
@ -45,7 +45,8 @@ define(
|
||||||
{
|
{
|
||||||
name : 'series',
|
name : 'series',
|
||||||
label: 'Series',
|
label: 'Series',
|
||||||
cell : SeriesTitleCell
|
cell : SeriesTitleCell,
|
||||||
|
sortValue: 'series.title'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name : 'episode',
|
name : 'episode',
|
||||||
|
@ -80,7 +81,7 @@ define(
|
||||||
|
|
||||||
|
|
||||||
initialize: function () {
|
initialize: function () {
|
||||||
this.collection = new HistoryCollection();
|
this.collection = new HistoryCollection({ tableName: 'history' });
|
||||||
this.listenTo(this.collection, 'sync', this._showTable);
|
this.listenTo(this.collection, 'sync', this._showTable);
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -5,19 +5,202 @@
|
||||||
Copyright (c) 2013 Jimmy Yuen Ho Wong and contributors
|
Copyright (c) 2013 Jimmy Yuen Ho Wong and contributors
|
||||||
Licensed under the MIT @license.
|
Licensed under the MIT @license.
|
||||||
*/
|
*/
|
||||||
|
(function (factory) {
|
||||||
|
|
||||||
(function ($, _, Backbone, Backgrid) {
|
// CommonJS
|
||||||
|
if (typeof exports == "object") {
|
||||||
|
module.exports = factory(require("underscore"),
|
||||||
|
require("backbone"),
|
||||||
|
require("backgrid"),
|
||||||
|
require("backbone-pageable"));
|
||||||
|
}
|
||||||
|
// Browser
|
||||||
|
else if (typeof _ !== "undefined" &&
|
||||||
|
typeof Backbone !== "undefined" &&
|
||||||
|
typeof Backgrid !== "undefined") {
|
||||||
|
factory(_, Backbone, Backgrid);
|
||||||
|
}
|
||||||
|
|
||||||
|
}(function (_, Backbone, Backgrid) {
|
||||||
|
|
||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
|
/**
|
||||||
|
PageHandle is a class that renders the actual page handles and reacts to
|
||||||
|
click events for pagination.
|
||||||
|
|
||||||
|
This class acts in two modes - control or discrete page handle modes. If
|
||||||
|
one of the `is*` flags is `true`, an instance of this class is under
|
||||||
|
control page handle mode. Setting a `pageIndex` to an instance of this
|
||||||
|
class under control mode has no effect and the correct page index will
|
||||||
|
always be inferred from the `is*` flag. Only one of the `is*` flags should
|
||||||
|
be set to `true` at a time. For example, an instance of this class cannot
|
||||||
|
simultaneously be a rewind control and a fast forward control. A `label`
|
||||||
|
and a `title` template or a string are required to be passed to the
|
||||||
|
constuctor under this mode. If a `title` template is provided, it __MUST__
|
||||||
|
accept a parameter `label`. When the `label` is provided to the `title`
|
||||||
|
template function, its result will be used to render the generated anchor's
|
||||||
|
title attribute.
|
||||||
|
|
||||||
|
If all of the `is*` flags is set to `false`, which is the default, an
|
||||||
|
instance of this class will be in discrete page handle mode. An instance
|
||||||
|
under this mode requires the `pageIndex` to be passed from the constructor
|
||||||
|
as an option and it __MUST__ be a 0-based index of the list of page numbers
|
||||||
|
to render. The constuctor will normalize the base to the same base the
|
||||||
|
underlying PageableCollection collection instance uses. A `label` is not
|
||||||
|
required under this mode, which will default to the equivalent 1-based page
|
||||||
|
index calculated from `pageIndex` and the underlying PageableCollection
|
||||||
|
instance. A provided `label` will still be honored however. The `title`
|
||||||
|
parameter is also not required under this mode, in which case the default
|
||||||
|
`title` template will be used. You are encouraged to provide your own
|
||||||
|
`title` template however if you wish to localize the title strings.
|
||||||
|
|
||||||
|
If this page handle represents the current page, an `active` class will be
|
||||||
|
placed on the root list element.
|
||||||
|
|
||||||
|
if this page handle is at the border of the list of pages, a `disabled`
|
||||||
|
class will be placed on the root list element.
|
||||||
|
|
||||||
|
Only page handles that are neither `active` nor `disabled` will respond to
|
||||||
|
click events and triggers pagination.
|
||||||
|
|
||||||
|
@class Backgrid.Extension.PageHandle
|
||||||
|
*/
|
||||||
|
var PageHandle = Backgrid.Extension.PageHandle = Backbone.View.extend({
|
||||||
|
|
||||||
|
/** @property */
|
||||||
|
tagName: "li",
|
||||||
|
|
||||||
|
/** @property */
|
||||||
|
events: {
|
||||||
|
"click a": "changePage"
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
@property {string|function(Object.<string, string>): string} title
|
||||||
|
The title to use for the `title` attribute of the generated page handle
|
||||||
|
anchor elements. It can be a string or an Underscore template function
|
||||||
|
that takes a mandatory `label` parameter.
|
||||||
|
*/
|
||||||
|
title: _.template('Page <%- label %>', null, {variable: null}),
|
||||||
|
|
||||||
|
/**
|
||||||
|
@property {boolean} isRewind Whether this handle represents a rewind
|
||||||
|
control
|
||||||
|
*/
|
||||||
|
isRewind: false,
|
||||||
|
|
||||||
|
/**
|
||||||
|
@property {boolean} isBack Whether this handle represents a back
|
||||||
|
control
|
||||||
|
*/
|
||||||
|
isBack: false,
|
||||||
|
|
||||||
|
/**
|
||||||
|
@property {boolean} isForward Whether this handle represents a forward
|
||||||
|
control
|
||||||
|
*/
|
||||||
|
isForward: false,
|
||||||
|
|
||||||
|
/**
|
||||||
|
@property {boolean} isFastForward Whether this handle represents a fast
|
||||||
|
forward control
|
||||||
|
*/
|
||||||
|
isFastForward: false,
|
||||||
|
|
||||||
|
/**
|
||||||
|
Initializer.
|
||||||
|
|
||||||
|
@param {Object} options
|
||||||
|
@param {Backbone.Collection} options.collection
|
||||||
|
@param {number} pageIndex 0-based index of the page number this handle
|
||||||
|
handles. This parameter will be normalized to the base the underlying
|
||||||
|
PageableCollection uses.
|
||||||
|
@param {string} [options.label] If provided it is used to render the
|
||||||
|
anchor text, otherwise the normalized pageIndex will be used
|
||||||
|
instead. Required if any of the `is*` flags is set to `true`.
|
||||||
|
@param {string} [options.title]
|
||||||
|
@param {boolean} [options.isRewind=false]
|
||||||
|
@param {boolean} [options.isBack=false]
|
||||||
|
@param {boolean} [options.isForward=false]
|
||||||
|
@param {boolean} [options.isFastForward=false]
|
||||||
|
*/
|
||||||
|
initialize: function (options) {
|
||||||
|
Backbone.View.prototype.initialize.apply(this, arguments);
|
||||||
|
|
||||||
|
var collection = this.collection;
|
||||||
|
var state = collection.state;
|
||||||
|
var currentPage = state.currentPage;
|
||||||
|
var firstPage = state.firstPage;
|
||||||
|
var lastPage = state.lastPage;
|
||||||
|
|
||||||
|
_.extend(this, _.pick(options,
|
||||||
|
["isRewind", "isBack", "isForward", "isFastForward"]));
|
||||||
|
|
||||||
|
var pageIndex;
|
||||||
|
if (this.isRewind) pageIndex = firstPage;
|
||||||
|
else if (this.isBack) pageIndex = Math.max(firstPage, currentPage - 1);
|
||||||
|
else if (this.isForward) pageIndex = Math.min(lastPage, currentPage + 1);
|
||||||
|
else if (this.isFastForward) pageIndex = lastPage;
|
||||||
|
else {
|
||||||
|
pageIndex = +options.pageIndex;
|
||||||
|
pageIndex = (firstPage ? pageIndex + 1 : pageIndex);
|
||||||
|
}
|
||||||
|
this.pageIndex = pageIndex;
|
||||||
|
|
||||||
|
if (((this.isRewind || this.isBack) && currentPage == firstPage) ||
|
||||||
|
((this.isForward || this.isFastForward) && currentPage == lastPage)) {
|
||||||
|
this.$el.addClass("disabled");
|
||||||
|
}
|
||||||
|
else if (!(this.isRewind ||
|
||||||
|
this.isBack ||
|
||||||
|
this.isForward ||
|
||||||
|
this.isFastForward) &&
|
||||||
|
currentPage == pageIndex) {
|
||||||
|
this.$el.addClass("active");
|
||||||
|
}
|
||||||
|
|
||||||
|
this.label = (options.label || (firstPage ? pageIndex : pageIndex + 1)) + '';
|
||||||
|
var title = options.title || this.title;
|
||||||
|
this.title = _.isFunction(title) ? title({label: this.label}) : title;
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
Renders a clickable anchor element under a list item.
|
||||||
|
*/
|
||||||
|
render: function () {
|
||||||
|
this.$el.empty();
|
||||||
|
var anchor = document.createElement("a");
|
||||||
|
anchor.href = '#';
|
||||||
|
if (this.title) anchor.title = this.title;
|
||||||
|
anchor.innerHTML = this.label;
|
||||||
|
this.el.appendChild(anchor);
|
||||||
|
this.delegateEvents();
|
||||||
|
return this;
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
jQuery click event handler. Goes to the page this PageHandle instance
|
||||||
|
represents. No-op if this page handle is currently active or disabled.
|
||||||
|
*/
|
||||||
|
changePage: function (e) {
|
||||||
|
e.preventDefault();
|
||||||
|
var $el = this.$el;
|
||||||
|
if (!$el.hasClass("active") && !$el.hasClass("disabled")) {
|
||||||
|
this.collection.getPage(this.pageIndex);
|
||||||
|
}
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
/**
|
/**
|
||||||
Paginator is a Backgrid extension that renders a series of configurable
|
Paginator is a Backgrid extension that renders a series of configurable
|
||||||
pagination handles. This extension is best used for splitting a large data
|
pagination handles. This extension is best used for splitting a large data
|
||||||
set across multiple pages. If the number of pages is larger then a
|
set across multiple pages. If the number of pages is larger then a
|
||||||
threshold, which is set to 10 by default, the page handles are rendered
|
threshold, which is set to 10 by default, the page handles are rendered
|
||||||
within a sliding window, plus the fast forward, fast backward, previous and
|
within a sliding window, plus the rewind, back, forward and fast forward
|
||||||
next page handles. The fast forward, fast backward, previous and next page
|
control handles. The individual control handles can be turned off.
|
||||||
handles can be turned off.
|
|
||||||
|
|
||||||
@class Backgrid.Extension.Paginator
|
@class Backgrid.Extension.Paginator
|
||||||
*/
|
*/
|
||||||
|
@ -30,97 +213,63 @@
|
||||||
windowSize: 10,
|
windowSize: 10,
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@property {Object} fastForwardHandleLabels You can disable specific
|
@property {Object.<string, Object.<string, string>>} controls You can
|
||||||
handles by setting its value to `null`.
|
disable specific control handles by omitting certain keys.
|
||||||
*/
|
*/
|
||||||
fastForwardHandleLabels: {
|
controls: {
|
||||||
first: "《",
|
rewind: {
|
||||||
prev: "〈",
|
label: "《",
|
||||||
next: "〉",
|
title: "First"
|
||||||
last: "》"
|
},
|
||||||
|
back: {
|
||||||
|
label: "〈",
|
||||||
|
title: "Previous"
|
||||||
|
},
|
||||||
|
forward: {
|
||||||
|
label: "〉",
|
||||||
|
title: "Next"
|
||||||
|
},
|
||||||
|
fastForward: {
|
||||||
|
label: "》",
|
||||||
|
title: "Last"
|
||||||
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
/** @property */
|
/**
|
||||||
template: _.template('<ul><% _.each(handles, function (handle) { %><li <% if (handle.className) { %>class="<%= handle.className %>"<% } %>><a href="#" <% if (handle.title) {%> title="<%= handle.title %>"<% } %>><%= handle.label %></a></li><% }); %></ul>'),
|
@property {Backgrid.Extension.PageHandle} pageHandle. The PageHandle
|
||||||
|
class to use for rendering individual handles
|
||||||
|
*/
|
||||||
|
pageHandle: PageHandle,
|
||||||
|
|
||||||
/** @property */
|
/** @property */
|
||||||
events: {
|
goBackFirstOnSort: true,
|
||||||
"click a": "changePage"
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
Initializer.
|
Initializer.
|
||||||
|
|
||||||
@param {Object} options
|
@param {Object} options
|
||||||
@param {Backbone.Collection} options.collection
|
@param {Backbone.Collection} options.collection
|
||||||
@param {boolean} [options.fastForwardHandleLabels] Whether to render fast forward buttons.
|
@param {boolean} [options.controls]
|
||||||
|
@param {boolean} [options.pageHandle=Backgrid.Extension.PageHandle]
|
||||||
|
@param {boolean} [options.goBackFirstOnSort=true]
|
||||||
*/
|
*/
|
||||||
initialize: function (options) {
|
initialize: function (options) {
|
||||||
Backgrid.requireOptions(options, ["collection"]);
|
this.controls = options.controls || this.controls;
|
||||||
|
this.pageHandle = options.pageHandle || this.pageHandle;
|
||||||
|
|
||||||
var collection = this.collection;
|
var collection = this.collection;
|
||||||
var fullCollection = collection.fullCollection;
|
|
||||||
if (fullCollection) {
|
|
||||||
this.listenTo(fullCollection, "add", this.render);
|
|
||||||
this.listenTo(fullCollection, "remove", this.render);
|
|
||||||
this.listenTo(fullCollection, "reset", this.render);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
this.listenTo(collection, "add", this.render);
|
this.listenTo(collection, "add", this.render);
|
||||||
this.listenTo(collection, "remove", this.render);
|
this.listenTo(collection, "remove", this.render);
|
||||||
this.listenTo(collection, "reset", this.render);
|
this.listenTo(collection, "reset", this.render);
|
||||||
}
|
if ((options.goBackFirstOnSort || this.goBackFirstOnSort) &&
|
||||||
},
|
collection.fullCollection) {
|
||||||
|
this.listenTo(collection.fullCollection, "sort", function () {
|
||||||
/**
|
|
||||||
jQuery event handler for the page handlers. Goes to the right page upon
|
|
||||||
clicking.
|
|
||||||
|
|
||||||
@param {Event} e
|
|
||||||
*/
|
|
||||||
changePage: function (e) {
|
|
||||||
e.preventDefault();
|
|
||||||
|
|
||||||
var $li = $(e.target).parent();
|
|
||||||
if (!$li.hasClass("active") && !$li.hasClass("disabled")) {
|
|
||||||
|
|
||||||
var label = $(e.target).text();
|
|
||||||
var ffLabels = this.fastForwardHandleLabels;
|
|
||||||
|
|
||||||
var collection = this.collection;
|
|
||||||
|
|
||||||
if (ffLabels) {
|
|
||||||
switch (label) {
|
|
||||||
case ffLabels.first:
|
|
||||||
collection.getFirstPage();
|
collection.getFirstPage();
|
||||||
return;
|
});
|
||||||
case ffLabels.prev:
|
|
||||||
collection.getPreviousPage();
|
|
||||||
return;
|
|
||||||
case ffLabels.next:
|
|
||||||
collection.getNextPage();
|
|
||||||
return;
|
|
||||||
case ffLabels.last:
|
|
||||||
collection.getLastPage();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var state = collection.state;
|
|
||||||
var pageIndex = +label;
|
|
||||||
collection.getPage(state.firstPage === 0 ? pageIndex - 1 : pageIndex);
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
_calculateWindow: function () {
|
||||||
Internal method to create a list of page handle objects for the template
|
|
||||||
to render them.
|
|
||||||
|
|
||||||
@return {Array.<Object>} an array of page handle objects hashes
|
|
||||||
*/
|
|
||||||
makeHandles: function () {
|
|
||||||
|
|
||||||
var handles = [];
|
|
||||||
var collection = this.collection;
|
var collection = this.collection;
|
||||||
var state = collection.state;
|
var state = collection.state;
|
||||||
|
|
||||||
|
@ -132,48 +281,44 @@
|
||||||
currentPage = firstPage ? currentPage - 1 : currentPage;
|
currentPage = firstPage ? currentPage - 1 : currentPage;
|
||||||
var windowStart = Math.floor(currentPage / this.windowSize) * this.windowSize;
|
var windowStart = Math.floor(currentPage / this.windowSize) * this.windowSize;
|
||||||
var windowEnd = Math.min(lastPage + 1, windowStart + this.windowSize);
|
var windowEnd = Math.min(lastPage + 1, windowStart + this.windowSize);
|
||||||
|
return [windowStart, windowEnd];
|
||||||
|
},
|
||||||
|
|
||||||
if (collection.mode !== "infinite") {
|
/**
|
||||||
for (var i = windowStart; i < windowEnd; i++) {
|
Creates a list of page handle objects for rendering.
|
||||||
handles.push({
|
|
||||||
label: i + 1,
|
@return {Array.<Object>} an array of page handle objects hashes
|
||||||
title: "No. " + (i + 1),
|
*/
|
||||||
className: currentPage === i ? "active" : undefined
|
makeHandles: function () {
|
||||||
});
|
|
||||||
}
|
var handles = [];
|
||||||
|
var collection = this.collection;
|
||||||
|
|
||||||
|
var window = this._calculateWindow();
|
||||||
|
var winStart = window[0], winEnd = window[1];
|
||||||
|
|
||||||
|
for (var i = winStart; i < winEnd; i++) {
|
||||||
|
handles.push(new this.pageHandle({
|
||||||
|
collection: collection,
|
||||||
|
pageIndex: i
|
||||||
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
var ffLabels = this.fastForwardHandleLabels;
|
var controls = this.controls;
|
||||||
if (ffLabels) {
|
_.each(["back", "rewind", "forward", "fastForward"], function (key) {
|
||||||
|
var value = controls[key];
|
||||||
if (ffLabels.prev) {
|
if (value) {
|
||||||
handles.unshift({
|
var handleCtorOpts = {
|
||||||
label: ffLabels.prev,
|
collection: collection,
|
||||||
className: collection.hasPrevious() ? void 0 : "disabled"
|
title: value.title,
|
||||||
});
|
label: value.label
|
||||||
}
|
};
|
||||||
|
handleCtorOpts["is" + key.slice(0, 1).toUpperCase() + key.slice(1)] = true;
|
||||||
if (ffLabels.first) {
|
var handle = new this.pageHandle(handleCtorOpts);
|
||||||
handles.unshift({
|
if (key == "rewind" || key == "back") handles.unshift(handle);
|
||||||
label: ffLabels.first,
|
else handles.push(handle);
|
||||||
className: collection.hasPrevious() ? void 0 : "disabled"
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (ffLabels.next) {
|
|
||||||
handles.push({
|
|
||||||
label: ffLabels.next,
|
|
||||||
className: collection.hasNext() ? void 0 : "disabled"
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (ffLabels.last) {
|
|
||||||
handles.push({
|
|
||||||
label: ffLabels.last,
|
|
||||||
className: collection.hasNext() ? void 0 : "disabled"
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}, this);
|
||||||
|
|
||||||
return handles;
|
return handles;
|
||||||
},
|
},
|
||||||
|
@ -184,15 +329,24 @@
|
||||||
render: function () {
|
render: function () {
|
||||||
this.$el.empty();
|
this.$el.empty();
|
||||||
|
|
||||||
this.$el.append(this.template({
|
if (this.handles) {
|
||||||
handles: this.makeHandles()
|
for (var i = 0, l = this.handles.length; i < l; i++) {
|
||||||
}));
|
this.handles[i].remove();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
this.delegateEvents();
|
var handles = this.handles = this.makeHandles();
|
||||||
|
|
||||||
|
var ul = document.createElement("ul");
|
||||||
|
for (var i = 0; i < handles.length; i++) {
|
||||||
|
ul.appendChild(handles[i].render().el);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.el.appendChild(ul);
|
||||||
|
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
}(jQuery, _, Backbone, Backgrid));
|
}));
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
/*
|
/*
|
||||||
backbone-pageable 1.3.2
|
backbone-pageable 1.4.1
|
||||||
http://github.com/wyuenho/backbone-pageable
|
http://github.com/wyuenho/backbone-pageable
|
||||||
|
|
||||||
Copyright (c) 2013 Jimmy Yuen Ho Wong
|
Copyright (c) 2013 Jimmy Yuen Ho Wong
|
||||||
|
@ -83,7 +83,7 @@
|
||||||
for (var i = 0, l = kvps.length; i < l; i++) {
|
for (var i = 0, l = kvps.length; i < l; i++) {
|
||||||
var param = kvps[i];
|
var param = kvps[i];
|
||||||
kvp = param.split('='), k = kvp[0], v = kvp[1] || true;
|
kvp = param.split('='), k = kvp[0], v = kvp[1] || true;
|
||||||
k = decode(k), ls = params[k];
|
k = decode(k), v = decode(v), ls = params[k];
|
||||||
if (_isArray(ls)) ls.push(v);
|
if (_isArray(ls)) ls.push(v);
|
||||||
else if (ls) params[k] = [ls, v];
|
else if (ls) params[k] = [ls, v];
|
||||||
else params[k] = v;
|
else params[k] = v;
|
||||||
|
@ -91,6 +91,29 @@
|
||||||
return params;
|
return params;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// hack to make sure the whatever event handlers for this event is run
|
||||||
|
// before func is, and the event handlers that func will trigger.
|
||||||
|
function runOnceAtLastHandler (col, event, func) {
|
||||||
|
var eventHandlers = col._events[event];
|
||||||
|
if (eventHandlers && eventHandlers.length) {
|
||||||
|
var lastHandler = eventHandlers[eventHandlers.length - 1];
|
||||||
|
var oldCallback = lastHandler.callback;
|
||||||
|
lastHandler.callback = function () {
|
||||||
|
try {
|
||||||
|
oldCallback.apply(this, arguments);
|
||||||
|
func();
|
||||||
|
}
|
||||||
|
catch (e) {
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
finally {
|
||||||
|
lastHandler.callback = oldCallback;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
else func();
|
||||||
|
}
|
||||||
|
|
||||||
var PARAM_TRIM_RE = /[\s'"]/g;
|
var PARAM_TRIM_RE = /[\s'"]/g;
|
||||||
var URL_TRIM_RE = /[<>\s'"]/g;
|
var URL_TRIM_RE = /[<>\s'"]/g;
|
||||||
|
|
||||||
|
@ -256,7 +279,7 @@
|
||||||
*/
|
*/
|
||||||
constructor: function (models, options) {
|
constructor: function (models, options) {
|
||||||
|
|
||||||
Backbone.Collection.apply(this, arguments);
|
BBColProto.constructor.apply(this, arguments);
|
||||||
|
|
||||||
options = options || {};
|
options = options || {};
|
||||||
|
|
||||||
|
@ -299,7 +322,7 @@
|
||||||
var fullCollection = this.fullCollection;
|
var fullCollection = this.fullCollection;
|
||||||
|
|
||||||
if (comparator && options.full) {
|
if (comparator && options.full) {
|
||||||
delete this.comparator;
|
this.comparator = null;
|
||||||
fullCollection.comparator = comparator;
|
fullCollection.comparator = comparator;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -308,6 +331,7 @@
|
||||||
// make sure the models in the current page and full collection have the
|
// make sure the models in the current page and full collection have the
|
||||||
// same references
|
// same references
|
||||||
if (models && !_isEmpty(models)) {
|
if (models && !_isEmpty(models)) {
|
||||||
|
this.reset([].slice.call(models), _extend({silent: true}, options));
|
||||||
this.getPage(state.currentPage);
|
this.getPage(state.currentPage);
|
||||||
models.splice.apply(models, [0, models.length].concat(this.models));
|
models.splice.apply(models, [0, models.length].concat(this.models));
|
||||||
}
|
}
|
||||||
|
@ -412,22 +436,10 @@
|
||||||
pageCol.at(pageSize) :
|
pageCol.at(pageSize) :
|
||||||
null;
|
null;
|
||||||
if (modelToRemove) {
|
if (modelToRemove) {
|
||||||
var addHandlers = collection._events.add || [],
|
var popOptions = {onAdd: true};
|
||||||
popOptions = {onAdd: true};
|
runOnceAtLastHandler(collection, event, function () {
|
||||||
if (addHandlers.length) {
|
|
||||||
var lastAddHandler = addHandlers[addHandlers.length - 1];
|
|
||||||
var oldCallback = lastAddHandler.callback;
|
|
||||||
lastAddHandler.callback = function () {
|
|
||||||
try {
|
|
||||||
oldCallback.apply(this, arguments);
|
|
||||||
pageCol.remove(modelToRemove, popOptions);
|
pageCol.remove(modelToRemove, popOptions);
|
||||||
}
|
});
|
||||||
finally {
|
|
||||||
lastAddHandler.callback = oldCallback;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
else pageCol.remove(modelToRemove, popOptions);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -442,20 +454,25 @@
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
var totalPages = state.totalPages = ceil(state.totalRecords / pageSize);
|
var totalPages = state.totalPages = ceil(state.totalRecords / pageSize);
|
||||||
state.lastPage = firstPage === 0 ? totalPages - 1 : totalPages;
|
state.lastPage = firstPage === 0 ? totalPages - 1 : totalPages || firstPage;
|
||||||
if (state.currentPage > totalPages) state.currentPage = state.lastPage;
|
if (state.currentPage > totalPages) state.currentPage = state.lastPage;
|
||||||
}
|
}
|
||||||
pageCol.state = pageCol._checkState(state);
|
pageCol.state = pageCol._checkState(state);
|
||||||
|
|
||||||
var nextModel, removedIndex = options.index;
|
var nextModel, removedIndex = options.index;
|
||||||
if (collection == pageCol) {
|
if (collection == pageCol) {
|
||||||
if (nextModel = fullCol.at(pageEnd)) pageCol.push(nextModel);
|
if (nextModel = fullCol.at(pageEnd)) {
|
||||||
|
runOnceAtLastHandler(pageCol, event, function () {
|
||||||
|
pageCol.push(nextModel);
|
||||||
|
});
|
||||||
|
}
|
||||||
fullCol.remove(model);
|
fullCol.remove(model);
|
||||||
}
|
}
|
||||||
else if (removedIndex >= pageStart && removedIndex < pageEnd) {
|
else if (removedIndex >= pageStart && removedIndex < pageEnd) {
|
||||||
pageCol.remove(model);
|
pageCol.remove(model);
|
||||||
nextModel = fullCol.at(currentPage * (pageSize + removedIndex));
|
var at = removedIndex + 1
|
||||||
if (nextModel) pageCol.push(nextModel);
|
nextModel = fullCol.at(at) || fullCol.last();
|
||||||
|
if (nextModel) pageCol.add(nextModel, {at: at});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else delete options.onAdd;
|
else delete options.onAdd;
|
||||||
|
@ -466,13 +483,13 @@
|
||||||
collection = model;
|
collection = model;
|
||||||
|
|
||||||
// Reset that's not a result of getPage
|
// Reset that's not a result of getPage
|
||||||
if (collection === pageCol && options.from == null &&
|
if (collection == pageCol && options.from == null &&
|
||||||
options.to == null) {
|
options.to == null) {
|
||||||
var head = fullCol.models.slice(0, pageStart);
|
var head = fullCol.models.slice(0, pageStart);
|
||||||
var tail = fullCol.models.slice(pageStart + pageCol.models.length);
|
var tail = fullCol.models.slice(pageStart + pageCol.models.length);
|
||||||
fullCol.reset(head.concat(pageCol.models).concat(tail), options);
|
fullCol.reset(head.concat(pageCol.models).concat(tail), options);
|
||||||
}
|
}
|
||||||
else if (collection === fullCol) {
|
else if (collection == fullCol) {
|
||||||
if (!(state.totalRecords = fullCol.models.length)) {
|
if (!(state.totalRecords = fullCol.models.length)) {
|
||||||
state.totalRecords = null;
|
state.totalRecords = null;
|
||||||
state.totalPages = null;
|
state.totalPages = null;
|
||||||
|
@ -551,7 +568,7 @@
|
||||||
throw new RangeError("`firstPage must be 0 or 1`");
|
throw new RangeError("`firstPage must be 0 or 1`");
|
||||||
}
|
}
|
||||||
|
|
||||||
state.lastPage = firstPage === 0 ? max(0, totalPages - 1) : totalPages;
|
state.lastPage = firstPage === 0 ? max(0, totalPages - 1) : totalPages || firstPage;
|
||||||
|
|
||||||
if (mode == "infinite") {
|
if (mode == "infinite") {
|
||||||
if (!links[currentPage + '']) {
|
if (!links[currentPage + '']) {
|
||||||
|
@ -561,6 +578,8 @@
|
||||||
else if (currentPage < firstPage ||
|
else if (currentPage < firstPage ||
|
||||||
(totalPages > 0 &&
|
(totalPages > 0 &&
|
||||||
(firstPage ? currentPage > totalPages : currentPage >= totalPages))) {
|
(firstPage ? currentPage > totalPages : currentPage >= totalPages))) {
|
||||||
|
var op = firstPage ? ">=" : ">";
|
||||||
|
|
||||||
throw new RangeError("`currentPage` must be firstPage <= currentPage " +
|
throw new RangeError("`currentPage` must be firstPage <= currentPage " +
|
||||||
(firstPage ? ">" : ">=") +
|
(firstPage ? ">" : ">=") +
|
||||||
" totalPages if " + firstPage + "-based. Got " +
|
" totalPages if " + firstPage + "-based. Got " +
|
||||||
|
@ -681,7 +700,7 @@
|
||||||
var fullCollection = this.fullCollection;
|
var fullCollection = this.fullCollection;
|
||||||
var handlers = this._handlers = this._handlers || {}, handler;
|
var handlers = this._handlers = this._handlers || {}, handler;
|
||||||
if (mode != "server" && !fullCollection) {
|
if (mode != "server" && !fullCollection) {
|
||||||
fullCollection = this._makeFullCollection(options.models || []);
|
fullCollection = this._makeFullCollection(options.models || [], options);
|
||||||
fullCollection.pageableCollection = this;
|
fullCollection.pageableCollection = this;
|
||||||
this.fullCollection = fullCollection;
|
this.fullCollection = fullCollection;
|
||||||
var allHandler = this._makeCollectionEventHandler(this, fullCollection);
|
var allHandler = this._makeCollectionEventHandler(this, fullCollection);
|
||||||
|
@ -856,7 +875,8 @@
|
||||||
[];
|
[];
|
||||||
if ((mode == "client" || (mode == "infinite" && !_isEmpty(pageModels))) &&
|
if ((mode == "client" || (mode == "infinite" && !_isEmpty(pageModels))) &&
|
||||||
!options.fetch) {
|
!options.fetch) {
|
||||||
return this.reset(pageModels, _omit(options, "fetch"));
|
this.reset(pageModels, _omit(options, "fetch"));
|
||||||
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (mode == "infinite") options.url = this.links[pageNum];
|
if (mode == "infinite") options.url = this.links[pageNum];
|
||||||
|
@ -1310,8 +1330,8 @@
|
||||||
this.comparator = comparator;
|
this.comparator = comparator;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (delComp) delete this.comparator;
|
if (delComp) this.comparator = null;
|
||||||
if (delFullComp && fullCollection) delete fullCollection.comparator;
|
if (delFullComp && fullCollection) fullCollection.comparator = null;
|
||||||
|
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,11 +2,13 @@
|
||||||
define(
|
define(
|
||||||
[
|
[
|
||||||
'Series/EpisodeModel',
|
'Series/EpisodeModel',
|
||||||
'backbone.pageable'
|
'backbone.pageable',
|
||||||
], function (EpisodeModel, PagableCollection) {
|
'Mixins/AsPersistedStateCollection'
|
||||||
return PagableCollection.extend({
|
], function (EpisodeModel, PagableCollection, AsPersistedStateCollection) {
|
||||||
|
var collection = PagableCollection.extend({
|
||||||
url : window.NzbDrone.ApiRoot + '/missing',
|
url : window.NzbDrone.ApiRoot + '/missing',
|
||||||
model: EpisodeModel,
|
model: EpisodeModel,
|
||||||
|
tableName: 'missing',
|
||||||
|
|
||||||
state: {
|
state: {
|
||||||
pageSize: 15,
|
pageSize: 15,
|
||||||
|
@ -38,4 +40,6 @@ define(
|
||||||
return resp;
|
return resp;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
return AsPersistedStateCollection.call(collection);
|
||||||
});
|
});
|
|
@ -4,7 +4,7 @@ define(
|
||||||
'underscore',
|
'underscore',
|
||||||
'marionette',
|
'marionette',
|
||||||
'backgrid',
|
'backgrid',
|
||||||
'Missing/Collection',
|
'Missing/MissingCollection',
|
||||||
'Cells/SeriesTitleCell',
|
'Cells/SeriesTitleCell',
|
||||||
'Cells/EpisodeNumberCell',
|
'Cells/EpisodeNumberCell',
|
||||||
'Cells/EpisodeTitleCell',
|
'Cells/EpisodeTitleCell',
|
||||||
|
@ -121,7 +121,6 @@ define(
|
||||||
]
|
]
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
this.toolbar.show(new ToolbarLayout({
|
this.toolbar.show(new ToolbarLayout({
|
||||||
left :
|
left :
|
||||||
[
|
[
|
||||||
|
|
|
@ -0,0 +1,86 @@
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
define(
|
||||||
|
['underscore', 'Config'],
|
||||||
|
function (_, Config) {
|
||||||
|
|
||||||
|
return function () {
|
||||||
|
|
||||||
|
var originalInit = this.prototype.initialize;
|
||||||
|
|
||||||
|
this.prototype.initialize = function (options) {
|
||||||
|
|
||||||
|
options = options || {};
|
||||||
|
|
||||||
|
if (options.tableName) {
|
||||||
|
this.tableName = options.tableName;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!this.tableName && !options.tableName) {
|
||||||
|
throw 'tableName is required';
|
||||||
|
}
|
||||||
|
|
||||||
|
_setInitialState.call(this);
|
||||||
|
|
||||||
|
this.on('backgrid:sort', _storeStateFromBackgrid, this);
|
||||||
|
this.on('drone:sort', _storeState, this);
|
||||||
|
|
||||||
|
if (originalInit) {
|
||||||
|
originalInit.call(this, options);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
var _setInitialState = function () {
|
||||||
|
var key = Config.getValue('{0}.sortKey'.format(this.tableName), this.state.sortKey);
|
||||||
|
var direction = Config.getValue('{0}.sortDirection'.format(this.tableName), this.state.order);
|
||||||
|
var order = parseInt(direction, 10);
|
||||||
|
|
||||||
|
this.state.sortKey = key;
|
||||||
|
this.state.order = order;
|
||||||
|
};
|
||||||
|
|
||||||
|
var _storeStateFromBackgrid = function (column, sortDirection) {
|
||||||
|
var order = _convertDirectionToInt(sortDirection);
|
||||||
|
var sortKey = column.has('sortValue') && _.isString(column.get('sortValue')) ? column.get('sortValue') : column.get('name');
|
||||||
|
|
||||||
|
Config.setValue('{0}.sortKey'.format(this.tableName), sortKey);
|
||||||
|
Config.setValue('{0}.sortDirection'.format(this.tableName), order);
|
||||||
|
};
|
||||||
|
|
||||||
|
var _storeState = function (sortModel, sortDirection) {
|
||||||
|
var order = _convertDirectionToInt(sortDirection);
|
||||||
|
var sortKey = sortModel.get('name');
|
||||||
|
|
||||||
|
Config.setValue('{0}.sortKey'.format(this.tableName), sortKey);
|
||||||
|
Config.setValue('{0}.sortDirection'.format(this.tableName), order);
|
||||||
|
};
|
||||||
|
|
||||||
|
var _convertDirectionToInt = function (dir) {
|
||||||
|
if (dir === 'ascending') {
|
||||||
|
return '-1';
|
||||||
|
}
|
||||||
|
|
||||||
|
return '1';
|
||||||
|
};
|
||||||
|
|
||||||
|
_.extend(this.prototype, {
|
||||||
|
initialSort: function () {
|
||||||
|
var key = this.state.sortKey;
|
||||||
|
var order = this.state.order;
|
||||||
|
|
||||||
|
if (this[key] && this.mode === 'client') {
|
||||||
|
var sortValue = this[key];
|
||||||
|
|
||||||
|
this.setSorting(key, order, { sortValue: sortValue });
|
||||||
|
|
||||||
|
var comparator = this._makeComparator(key, order, sortValue);
|
||||||
|
this.fullCollection.comparator = comparator;
|
||||||
|
this.fullCollection.sort();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return this;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
);
|
|
@ -3,47 +3,47 @@
|
||||||
<div class="span12">
|
<div class="span12">
|
||||||
<ul id="main-menu-region">
|
<ul id="main-menu-region">
|
||||||
<div class="pull-left logo">
|
<div class="pull-left logo">
|
||||||
<a href="/">
|
<a href="{{UrlBase}}/">
|
||||||
<img src="/Content/Images/logo.png" alt="NzbDrone">
|
<img src="{{UrlBase}}/Content/Images/logo.png" alt="NzbDrone">
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
<li>
|
<li>
|
||||||
<a href="/">
|
<a href="{{UrlBase}}/">
|
||||||
<i class="icon-play"></i>
|
<i class="icon-play"></i>
|
||||||
<br>
|
<br>
|
||||||
Series
|
Series
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<a href="/calendar">
|
<a href="{{UrlBase}}/calendar">
|
||||||
<i class="icon-calendar"></i>
|
<i class="icon-calendar"></i>
|
||||||
<br>
|
<br>
|
||||||
Calendar
|
Calendar
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<a href="/history">
|
<a href="{{UrlBase}}/history">
|
||||||
<i class="icon-time"></i>
|
<i class="icon-time"></i>
|
||||||
<br>
|
<br>
|
||||||
History
|
History
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<a href="/missing">
|
<a href="{{UrlBase}}/missing">
|
||||||
<i class="icon-warning-sign"></i>
|
<i class="icon-warning-sign"></i>
|
||||||
<br>
|
<br>
|
||||||
Missing
|
Missing
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<a href="/settings">
|
<a href="{{UrlBase}}/settings">
|
||||||
<i class="icon-cogs"></i>
|
<i class="icon-cogs"></i>
|
||||||
<br>
|
<br>
|
||||||
Settings
|
Settings
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<a href="/system">
|
<a href="{{UrlBase}}/system">
|
||||||
<i class="icon-laptop"></i>
|
<i class="icon-laptop"></i>
|
||||||
<br>
|
<br>
|
||||||
System
|
System
|
||||||
|
|
|
@ -29,7 +29,7 @@ define(
|
||||||
//look down for <a/>
|
//look down for <a/>
|
||||||
var href = event.target.getAttribute('href');
|
var href = event.target.getAttribute('href');
|
||||||
|
|
||||||
//if couldn't find it look up
|
//if couldn't find it look up'
|
||||||
if (!href && target.parent('a') && target.parent('a')[0]) {
|
if (!href && target.parent('a') && target.parent('a')[0]) {
|
||||||
|
|
||||||
var linkElement = target.parent('a')[0];
|
var linkElement = target.parent('a')[0];
|
||||||
|
|
|
@ -6,7 +6,7 @@ define(
|
||||||
'Series/SeriesCollection'
|
'Series/SeriesCollection'
|
||||||
], function (Backbone, $, SeriesCollection) {
|
], function (Backbone, $, SeriesCollection) {
|
||||||
$(document).on('keydown', function (e) {
|
$(document).on('keydown', function (e) {
|
||||||
if ($(e.target).is('input')) {
|
if ($(e.target).is('input') || $(e.target).is('textarea')) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -94,6 +94,8 @@ define(
|
||||||
|
|
||||||
model.set('rootFolderPath', rootFolderPath.get('path'));
|
model.set('rootFolderPath', rootFolderPath.get('path'));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
model.edited = true;
|
||||||
});
|
});
|
||||||
|
|
||||||
SeriesCollection.save();
|
SeriesCollection.save();
|
||||||
|
@ -150,6 +152,7 @@ define(
|
||||||
|
|
||||||
SeriesCollection.each(function (model) {
|
SeriesCollection.each(function (model) {
|
||||||
model.trigger('backgrid:select', model, false);
|
model.trigger('backgrid:select', model, false);
|
||||||
|
model.edited = false;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
|
@ -68,7 +68,7 @@ define(
|
||||||
cell : QualityProfileCell
|
cell : QualityProfileCell
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name : 'monitored',
|
name : 'seasonFolder',
|
||||||
label: 'Season Folder',
|
label: 'Season Folder',
|
||||||
cell : SeasonFolderCell
|
cell : SeasonFolderCell
|
||||||
},
|
},
|
||||||
|
|
|
@ -13,7 +13,6 @@ define(
|
||||||
'Cells/QualityProfileCell',
|
'Cells/QualityProfileCell',
|
||||||
'Cells/EpisodeProgressCell',
|
'Cells/EpisodeProgressCell',
|
||||||
'Cells/SeriesActionsCell',
|
'Cells/SeriesActionsCell',
|
||||||
'Shared/Grid/DateHeaderCell',
|
|
||||||
'Cells/SeriesStatusCell',
|
'Cells/SeriesStatusCell',
|
||||||
'Series/Index/FooterView',
|
'Series/Index/FooterView',
|
||||||
'Series/Index/FooterModel',
|
'Series/Index/FooterModel',
|
||||||
|
@ -31,7 +30,6 @@ define(
|
||||||
QualityProfileCell,
|
QualityProfileCell,
|
||||||
EpisodeProgressCell,
|
EpisodeProgressCell,
|
||||||
SeriesActionsCell,
|
SeriesActionsCell,
|
||||||
DateHeaderCell,
|
|
||||||
SeriesStatusCell,
|
SeriesStatusCell,
|
||||||
FooterView,
|
FooterView,
|
||||||
FooterModel,
|
FooterModel,
|
||||||
|
@ -41,13 +39,12 @@ define(
|
||||||
template: 'Series/Index/SeriesIndexLayoutTemplate',
|
template: 'Series/Index/SeriesIndexLayoutTemplate',
|
||||||
|
|
||||||
regions: {
|
regions: {
|
||||||
seriesRegion: '#x-series',
|
seriesRegion : '#x-series',
|
||||||
toolbar : '#x-toolbar',
|
toolbar : '#x-toolbar',
|
||||||
footer : '#x-series-footer'
|
footer : '#x-series-footer'
|
||||||
},
|
},
|
||||||
|
|
||||||
columns:
|
columns: [
|
||||||
[
|
|
||||||
{
|
{
|
||||||
name : 'statusWeight',
|
name : 'statusWeight',
|
||||||
label : '',
|
label : '',
|
||||||
|
@ -78,7 +75,7 @@ define(
|
||||||
name : 'nextAiring',
|
name : 'nextAiring',
|
||||||
label : 'Next Airing',
|
label : 'Next Airing',
|
||||||
cell : RelativeDateCell,
|
cell : RelativeDateCell,
|
||||||
headerCell: DateHeaderCell
|
sortValue : SeriesCollection.nextAiring
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name : 'percentOfEpisodes',
|
name : 'percentOfEpisodes',
|
||||||
|
@ -130,25 +127,38 @@ define(
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
|
||||||
_showTable: function () {
|
sortingOptions: {
|
||||||
this.currentView = new Backgrid.Grid({
|
type : 'sorting',
|
||||||
collection: SeriesCollection,
|
storeState : false,
|
||||||
columns : this.columns,
|
viewCollection: SeriesCollection,
|
||||||
className : 'table table-hover'
|
items :
|
||||||
});
|
[
|
||||||
|
{
|
||||||
this._renderView();
|
title: 'Title',
|
||||||
this._fetchCollection();
|
name : 'title'
|
||||||
},
|
},
|
||||||
|
{
|
||||||
_showList: function () {
|
title: 'Seasons',
|
||||||
this.currentView = new ListCollectionView();
|
name : 'seasonCount'
|
||||||
this._fetchCollection();
|
|
||||||
},
|
},
|
||||||
|
{
|
||||||
_showPosters: function () {
|
title: 'Quality',
|
||||||
this.currentView = new PosterCollectionView();
|
name : 'qualityProfileId'
|
||||||
this._fetchCollection();
|
},
|
||||||
|
{
|
||||||
|
title: 'Network',
|
||||||
|
name : 'network'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title : 'Next Airing',
|
||||||
|
name : 'nextAiring',
|
||||||
|
sortValue : SeriesCollection.nextAiring
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'Episodes',
|
||||||
|
name : 'percentOfEpisodes'
|
||||||
|
}
|
||||||
|
]
|
||||||
},
|
},
|
||||||
|
|
||||||
initialize: function () {
|
initialize: function () {
|
||||||
|
@ -156,39 +166,8 @@ define(
|
||||||
|
|
||||||
this.listenTo(SeriesCollection, 'sync', this._renderView);
|
this.listenTo(SeriesCollection, 'sync', this._renderView);
|
||||||
this.listenTo(SeriesCollection, 'remove', this._renderView);
|
this.listenTo(SeriesCollection, 'remove', this._renderView);
|
||||||
},
|
|
||||||
|
|
||||||
_renderView: function () {
|
this.viewButtons = {
|
||||||
|
|
||||||
if (SeriesCollection.length === 0) {
|
|
||||||
this.seriesRegion.show(new EmptyView());
|
|
||||||
this.toolbar.close();
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
this.currentView.collection = SeriesCollection;
|
|
||||||
this.seriesRegion.show(this.currentView);
|
|
||||||
|
|
||||||
this._showToolbar();
|
|
||||||
this._showFooter();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
onShow: function () {
|
|
||||||
this._showToolbar();
|
|
||||||
this._renderView();
|
|
||||||
},
|
|
||||||
|
|
||||||
_fetchCollection: function () {
|
|
||||||
SeriesCollection.fetch();
|
|
||||||
},
|
|
||||||
|
|
||||||
_showToolbar: function () {
|
|
||||||
|
|
||||||
if (this.toolbar.currentView) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var viewButtons = {
|
|
||||||
type : 'radio',
|
type : 'radio',
|
||||||
storeState : true,
|
storeState : true,
|
||||||
menuKey : 'seriesViewMode',
|
menuKey : 'seriesViewMode',
|
||||||
|
@ -218,12 +197,67 @@ define(
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
};
|
};
|
||||||
|
},
|
||||||
|
|
||||||
|
_showTable: function () {
|
||||||
|
this.currentView = new Backgrid.Grid({
|
||||||
|
collection: SeriesCollection,
|
||||||
|
columns : this.columns,
|
||||||
|
className : 'table table-hover'
|
||||||
|
});
|
||||||
|
|
||||||
|
this._fetchCollection();
|
||||||
|
},
|
||||||
|
|
||||||
|
_showList: function () {
|
||||||
|
this.currentView = new ListCollectionView({ collection: SeriesCollection });
|
||||||
|
|
||||||
|
this._fetchCollection();
|
||||||
|
},
|
||||||
|
|
||||||
|
_showPosters: function () {
|
||||||
|
this.currentView = new PosterCollectionView({ collection: SeriesCollection });
|
||||||
|
|
||||||
|
this._fetchCollection();
|
||||||
|
},
|
||||||
|
|
||||||
|
_renderView: function () {
|
||||||
|
|
||||||
|
if (SeriesCollection.length === 0) {
|
||||||
|
this.seriesRegion.show(new EmptyView());
|
||||||
|
this.toolbar.close();
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
this.seriesRegion.show(this.currentView);
|
||||||
|
|
||||||
|
this._showToolbar();
|
||||||
|
this._showFooter();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
onShow: function () {
|
||||||
|
this._showToolbar();
|
||||||
|
this._renderView();
|
||||||
|
},
|
||||||
|
|
||||||
|
_fetchCollection: function () {
|
||||||
|
SeriesCollection.fetch();
|
||||||
|
},
|
||||||
|
|
||||||
|
_showToolbar: function () {
|
||||||
|
|
||||||
|
if (this.toolbar.currentView) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var rightButtons = [
|
||||||
|
this.viewButtons
|
||||||
|
];
|
||||||
|
|
||||||
|
rightButtons.splice(0, 0, this.sortingOptions);
|
||||||
|
|
||||||
this.toolbar.show(new ToolbarLayout({
|
this.toolbar.show(new ToolbarLayout({
|
||||||
right :
|
right : rightButtons,
|
||||||
[
|
|
||||||
viewButtons
|
|
||||||
],
|
|
||||||
left :
|
left :
|
||||||
[
|
[
|
||||||
this.leftSideButtons
|
this.leftSideButtons
|
||||||
|
|
|
@ -3,22 +3,25 @@ define(
|
||||||
[
|
[
|
||||||
'underscore',
|
'underscore',
|
||||||
'backbone',
|
'backbone',
|
||||||
|
'backbone.pageable',
|
||||||
'Series/SeriesModel',
|
'Series/SeriesModel',
|
||||||
'api!series'
|
'api!series',
|
||||||
], function (_, Backbone, SeriesModel, SeriesData) {
|
'Mixins/AsPersistedStateCollection',
|
||||||
var Collection = Backbone.Collection.extend({
|
'moment'
|
||||||
|
], function (_, Backbone, PageableCollection, SeriesModel, SeriesData, AsPersistedStateCollection, Moment) {
|
||||||
|
var Collection = PageableCollection.extend({
|
||||||
url : window.NzbDrone.ApiRoot + '/series',
|
url : window.NzbDrone.ApiRoot + '/series',
|
||||||
model: SeriesModel,
|
model: SeriesModel,
|
||||||
|
tableName: 'series',
|
||||||
comparator: function (model) {
|
|
||||||
return model.get('title');
|
|
||||||
},
|
|
||||||
|
|
||||||
state: {
|
state: {
|
||||||
sortKey: 'title',
|
sortKey: 'title',
|
||||||
order : -1
|
order : -1,
|
||||||
|
pageSize: 1000
|
||||||
},
|
},
|
||||||
|
|
||||||
|
mode: 'client',
|
||||||
|
|
||||||
save: function () {
|
save: function () {
|
||||||
var self = this;
|
var self = this;
|
||||||
|
|
||||||
|
@ -31,7 +34,7 @@ define(
|
||||||
toJSON: function()
|
toJSON: function()
|
||||||
{
|
{
|
||||||
return self.filter(function (model) {
|
return self.filter(function (model) {
|
||||||
return model.hasChanged();
|
return model.edited;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -42,9 +45,23 @@ define(
|
||||||
});
|
});
|
||||||
|
|
||||||
return proxy.save();
|
return proxy.save();
|
||||||
|
},
|
||||||
|
|
||||||
|
//Sorters
|
||||||
|
nextAiring: function (model, attr) {
|
||||||
|
var nextAiring = model.get(attr);
|
||||||
|
|
||||||
|
if (!nextAiring) {
|
||||||
|
return Number.MAX_VALUE;
|
||||||
|
}
|
||||||
|
|
||||||
|
return Moment(nextAiring).unix();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
var collection = new Collection(SeriesData);
|
var MixedIn = AsPersistedStateCollection.call(Collection);
|
||||||
|
var collection = new MixedIn(SeriesData);
|
||||||
|
collection.initialSort();
|
||||||
|
|
||||||
return collection;
|
return collection;
|
||||||
});
|
});
|
||||||
|
|
|
@ -17,7 +17,6 @@ define(
|
||||||
this.route('series/:query', this.seriesDetails);
|
this.route('series/:query', this.seriesDetails);
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
||||||
series: function () {
|
series: function () {
|
||||||
this.setTitle('NzbDrone');
|
this.setTitle('NzbDrone');
|
||||||
AppLayout.mainRegion.show(new SeriesIndexLayout());
|
AppLayout.mainRegion.show(new SeriesIndexLayout());
|
||||||
|
|
|
@ -6,8 +6,9 @@ define(
|
||||||
'Settings/Indexers/ItemView',
|
'Settings/Indexers/ItemView',
|
||||||
'Settings/Indexers/EditView',
|
'Settings/Indexers/EditView',
|
||||||
'Settings/Indexers/Collection',
|
'Settings/Indexers/Collection',
|
||||||
|
'System/StatusModel',
|
||||||
'underscore'
|
'underscore'
|
||||||
], function (AppLayout, Marionette, IndexerItemView, IndexerEditView, IndexerCollection, _) {
|
], function (AppLayout, Marionette, IndexerItemView, IndexerEditView, IndexerCollection, StatusModel, _) {
|
||||||
return Marionette.CompositeView.extend({
|
return Marionette.CompositeView.extend({
|
||||||
itemView : IndexerItemView,
|
itemView : IndexerItemView,
|
||||||
itemViewContainer: '#x-indexers',
|
itemViewContainer: '#x-indexers',
|
||||||
|
@ -29,10 +30,10 @@ define(
|
||||||
var self = this;
|
var self = this;
|
||||||
//TODO: Is there a better way to deal with changing URLs?
|
//TODO: Is there a better way to deal with changing URLs?
|
||||||
var schemaCollection = new IndexerCollection();
|
var schemaCollection = new IndexerCollection();
|
||||||
schemaCollection.url = '/api/indexer/schema';
|
schemaCollection.url = StatusModel.get('urlBase') + '/api/indexer/schema';
|
||||||
schemaCollection.fetch({
|
schemaCollection.fetch({
|
||||||
success: function (collection) {
|
success: function (collection) {
|
||||||
collection.url = '/api/indexer';
|
collection.url = StatusModel.get('urlBase') + '/api/indexer';
|
||||||
var model = _.first(collection.models);
|
var model = _.first(collection.models);
|
||||||
|
|
||||||
model.set({
|
model.set({
|
||||||
|
|
|
@ -2,15 +2,16 @@
|
||||||
define([
|
define([
|
||||||
'AppLayout',
|
'AppLayout',
|
||||||
'Settings/Notifications/Collection',
|
'Settings/Notifications/Collection',
|
||||||
'Settings/Notifications/AddView'
|
'Settings/Notifications/AddView',
|
||||||
], function (AppLayout, NotificationCollection, AddSelectionNotificationView) {
|
'System/StatusModel'
|
||||||
|
], function (AppLayout, NotificationCollection, AddSelectionNotificationView, StatusModel) {
|
||||||
return ({
|
return ({
|
||||||
|
|
||||||
open: function (collection) {
|
open: function (collection) {
|
||||||
var schemaCollection = new NotificationCollection();
|
var schemaCollection = new NotificationCollection();
|
||||||
schemaCollection.url = '/api/notification/schema';
|
schemaCollection.url = StatusModel.get('urlBase') + '/api/notification/schema';
|
||||||
schemaCollection.fetch();
|
schemaCollection.fetch();
|
||||||
schemaCollection.url = '/api/notification';
|
schemaCollection.url = StatusModel.get('urlBase') + '/api/notification';
|
||||||
|
|
||||||
var view = new AddSelectionNotificationView({ collection: schemaCollection, notificationCollection: collection});
|
var view = new AddSelectionNotificationView({ collection: schemaCollection, notificationCollection: collection});
|
||||||
AppLayout.modalRegion.show(view);
|
AppLayout.modalRegion.show(view);
|
||||||
|
|
|
@ -1,66 +0,0 @@
|
||||||
'use strict';
|
|
||||||
|
|
||||||
define(
|
|
||||||
[
|
|
||||||
'backgrid',
|
|
||||||
'Shared/Grid/HeaderCell'
|
|
||||||
], function (Backgrid, NzbDroneHeaderCell) {
|
|
||||||
|
|
||||||
Backgrid.DateHeaderCell = NzbDroneHeaderCell.extend({
|
|
||||||
events: {
|
|
||||||
'click': 'onClick'
|
|
||||||
},
|
|
||||||
|
|
||||||
onClick: function (e) {
|
|
||||||
e.preventDefault();
|
|
||||||
|
|
||||||
var self = this;
|
|
||||||
var columnName = this.column.get('name');
|
|
||||||
|
|
||||||
if (this.column.get('sortable')) {
|
|
||||||
if (this.direction() === 'ascending') {
|
|
||||||
this.sort(columnName, 'descending', function (left, right) {
|
|
||||||
var leftVal = left.get(columnName);
|
|
||||||
var rightVal = right.get(columnName);
|
|
||||||
|
|
||||||
return self._comparator(leftVal, rightVal);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
this.sort(columnName, 'ascending', function (left, right) {
|
|
||||||
var leftVal = left.get(columnName);
|
|
||||||
var rightVal = right.get(columnName);
|
|
||||||
|
|
||||||
return self._comparator(rightVal, leftVal);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
_comparator: function (leftVal, rightVal) {
|
|
||||||
if (!leftVal && !rightVal) {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!leftVal) {
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!rightVal) {
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (leftVal === rightVal) {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (leftVal > rightVal) {
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
return Backgrid.DateHeaderCell;
|
|
||||||
});
|
|
|
@ -6,95 +6,134 @@ define(
|
||||||
], function (Backgrid) {
|
], function (Backgrid) {
|
||||||
|
|
||||||
Backgrid.NzbDroneHeaderCell = Backgrid.HeaderCell.extend({
|
Backgrid.NzbDroneHeaderCell = Backgrid.HeaderCell.extend({
|
||||||
|
|
||||||
events: {
|
events: {
|
||||||
'click': 'onClick'
|
'click': 'onClick'
|
||||||
},
|
},
|
||||||
|
|
||||||
|
_originalInit: Backgrid.HeaderCell.prototype.initialize,
|
||||||
|
|
||||||
|
initialize: function (options) {
|
||||||
|
this._originalInit.call(this, options);
|
||||||
|
|
||||||
|
this.listenTo(this.collection, 'drone:sort', this.render);
|
||||||
|
},
|
||||||
|
|
||||||
render: function () {
|
render: function () {
|
||||||
this.$el.empty();
|
this.$el.empty();
|
||||||
this.$el.append(this.column.get('label'));
|
this.$el.append(this.column.get('label'));
|
||||||
|
|
||||||
if (this.column.get('sortable')) {
|
var column = this.column;
|
||||||
|
var sortable = Backgrid.callByNeed(column.sortable(), column, this.collection);
|
||||||
|
|
||||||
|
if (sortable)
|
||||||
|
{
|
||||||
this.$el.addClass('sortable');
|
this.$el.addClass('sortable');
|
||||||
this.$el.append(' <i class="pull-right"></i>');
|
this.$el.append(' <i class="pull-right"></i>');
|
||||||
|
}
|
||||||
|
|
||||||
|
//Do we need this?
|
||||||
|
this.$el.addClass(column.get('name'));
|
||||||
|
|
||||||
|
this.delegateEvents();
|
||||||
|
this.direction(column.get('direction'));
|
||||||
|
|
||||||
if (this.collection.state) {
|
if (this.collection.state) {
|
||||||
var sortKey = this.collection.state.sortKey;
|
var key = this.collection.state.sortKey;
|
||||||
var sortDir = this._convertIntToDirection(this.collection.state.order);
|
var order = this.collection.state.order;
|
||||||
|
|
||||||
if (sortKey === this.column.get('name')) {
|
if (key === this.column.get('name')) {
|
||||||
this.$el.children('i').addClass(this._convertDirectionToIcon(sortDir));
|
this._setSortIcon(order);
|
||||||
this._direction = sortDir;
|
}
|
||||||
|
|
||||||
|
else {
|
||||||
|
this._removeSortIcon();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
this.delegateEvents();
|
|
||||||
return this;
|
return this;
|
||||||
},
|
},
|
||||||
|
|
||||||
direction: function (dir) {
|
direction: function (dir) {
|
||||||
|
this.$el.children('i').removeClass('icon-sort-up icon-sort-down');
|
||||||
|
|
||||||
if (arguments.length) {
|
if (arguments.length) {
|
||||||
if (this._direction) {
|
if (dir)
|
||||||
this.$el.children('i').removeClass(this._convertDirectionToIcon(this._direction));
|
{
|
||||||
}
|
this._setSortIcon(dir);
|
||||||
if (dir) {
|
|
||||||
this.$el.children('i').addClass(this._convertDirectionToIcon(dir));
|
|
||||||
}
|
|
||||||
this._direction = dir;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return this._direction;
|
this.column.set('direction', dir);
|
||||||
|
}
|
||||||
|
|
||||||
|
var columnDirection = this.column.get('direction');
|
||||||
|
|
||||||
|
if (!columnDirection && this.collection.state) {
|
||||||
|
var key = this.collection.state.sortKey;
|
||||||
|
var order = this.collection.state.order;
|
||||||
|
|
||||||
|
if (key === this.column.get('name')) {
|
||||||
|
columnDirection = order;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return columnDirection;
|
||||||
},
|
},
|
||||||
|
|
||||||
onClick: function (e) {
|
onClick: function (e) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
|
|
||||||
var columnName = this.column.get('name');
|
var collection = this.collection;
|
||||||
|
var event = 'backgrid:sort';
|
||||||
|
|
||||||
if (this.column.get('sortable')) {
|
function toggleSort(header, col) {
|
||||||
if (this.direction() === 'ascending') {
|
collection.state.sortKey = col.get('name');
|
||||||
this.sort(columnName, 'descending', function (left, right) {
|
var direction = header.direction();
|
||||||
var leftVal = left.get(columnName);
|
if (direction === 'ascending' || direction === -1)
|
||||||
var rightVal = right.get(columnName);
|
{
|
||||||
if (leftVal === rightVal) {
|
collection.state.order = 'descending';
|
||||||
return 0;
|
collection.trigger(event, col, 'descending');
|
||||||
}
|
}
|
||||||
else if (leftVal > rightVal) {
|
else
|
||||||
return -1;
|
{
|
||||||
|
collection.state.order = 'ascending';
|
||||||
|
collection.trigger(event, col, 'ascending');
|
||||||
}
|
}
|
||||||
return 1;
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
else {
|
|
||||||
this.sort(columnName, 'ascending', function (left, right) {
|
var column = this.column;
|
||||||
var leftVal = left.get(columnName);
|
var sortable = Backgrid.callByNeed(column.sortable(), column, this.collection);
|
||||||
var rightVal = right.get(columnName);
|
if (sortable) {
|
||||||
if (leftVal === rightVal) {
|
toggleSort(this, column);
|
||||||
return 0;
|
|
||||||
}
|
}
|
||||||
else if (leftVal < rightVal) {
|
},
|
||||||
return -1;
|
|
||||||
}
|
_resetCellDirection: function (columnToSort, direction) {
|
||||||
return 1;
|
if (columnToSort !== this.column)
|
||||||
});
|
{
|
||||||
|
this.direction(null);
|
||||||
}
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
this.direction(direction);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
_convertDirectionToIcon: function (dir) {
|
_convertDirectionToIcon: function (dir) {
|
||||||
if (dir === 'ascending') {
|
if (dir === 'ascending' || dir === -1) {
|
||||||
return 'icon-sort-up';
|
return 'icon-sort-up';
|
||||||
}
|
}
|
||||||
|
|
||||||
return 'icon-sort-down';
|
return 'icon-sort-down';
|
||||||
},
|
},
|
||||||
|
|
||||||
_convertIntToDirection: function (dir) {
|
_setSortIcon: function (dir) {
|
||||||
if (dir === '-1') {
|
this._removeSortIcon();
|
||||||
return 'ascending';
|
this.$el.children('i').addClass(this._convertDirectionToIcon(dir));
|
||||||
}
|
},
|
||||||
|
|
||||||
return 'descending';
|
_removeSortIcon: function () {
|
||||||
|
this.$el.children('i').removeClass('icon-sort-up icon-sort-down');
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
<div>
|
<div>
|
||||||
<img src="/Content/Images/404.png" style="height:400px; margin-top: 50px"/>
|
<img src="{{UrlBase}}/Content/Images/404.png" style="height:400px; margin-top: 50px"/>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -30,7 +30,7 @@ define(
|
||||||
var tryingToReconnect = false;
|
var tryingToReconnect = false;
|
||||||
var messengerId = 'signalR';
|
var messengerId = 'signalR';
|
||||||
|
|
||||||
this.signalRconnection = $.connection('/signalr');
|
this.signalRconnection = $.connection(StatusModel.get('urlBase') + '/signalr');
|
||||||
|
|
||||||
this.signalRconnection.stateChanged(function (change) {
|
this.signalRconnection.stateChanged(function (change) {
|
||||||
console.debug('SignalR: [{0}]'.format(getStatus(change.newState)));
|
console.debug('SignalR: [{0}]'.format(getStatus(change.newState)));
|
||||||
|
|
|
@ -1,13 +1,29 @@
|
||||||
'use strict';
|
'use strict';
|
||||||
define(
|
define(
|
||||||
[
|
[
|
||||||
|
'underscore',
|
||||||
'backbone'
|
'backbone'
|
||||||
], function (Backbone) {
|
], function (_, Backbone) {
|
||||||
return Backbone.Model.extend({
|
return Backbone.Model.extend({
|
||||||
defaults: {
|
defaults: {
|
||||||
'target' : '/nzbdrone/route',
|
'target' : '/nzbdrone/route',
|
||||||
'title' : '',
|
'title' : '',
|
||||||
'active' : false,
|
'active' : false,
|
||||||
'tooltip': undefined }
|
'tooltip': undefined
|
||||||
|
},
|
||||||
|
|
||||||
|
sortValue: function () {
|
||||||
|
var sortValue = this.get('sortValue');
|
||||||
|
if (_.isString(sortValue)) {
|
||||||
|
return this[sortValue];
|
||||||
|
}
|
||||||
|
else if (_.isFunction(sortValue)) {
|
||||||
|
return sortValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
return function (model, colName) {
|
||||||
|
return model.get(colName);
|
||||||
|
};
|
||||||
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -13,7 +13,6 @@ define(
|
||||||
'click': 'onClick'
|
'click': 'onClick'
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
||||||
initialize: function () {
|
initialize: function () {
|
||||||
|
|
||||||
this.storageKey = this.model.get('menuKey') + ':' + this.model.get('key');
|
this.storageKey = this.model.get('menuKey') + ':' + this.model.get('key');
|
||||||
|
@ -53,7 +52,6 @@ define(
|
||||||
callback.call(this.model.ownerContext);
|
callback.call(this.model.ownerContext);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,87 @@
|
||||||
|
'use strict';
|
||||||
|
define(
|
||||||
|
[
|
||||||
|
'backbone.pageable',
|
||||||
|
'marionette',
|
||||||
|
'Shared/Toolbar/Sorting/SortingButtonView'
|
||||||
|
], function (PageableCollection, Marionette, ButtonView) {
|
||||||
|
return Marionette.CompositeView.extend({
|
||||||
|
itemView : ButtonView,
|
||||||
|
template : 'Shared/Toolbar/Sorting/SortingButtonCollectionViewTemplate',
|
||||||
|
itemViewContainer: '.dropdown-menu',
|
||||||
|
|
||||||
|
initialize: function (options) {
|
||||||
|
this.viewCollection = options.viewCollection;
|
||||||
|
this.listenTo(this.viewCollection, 'drone:sort', this.sort);
|
||||||
|
},
|
||||||
|
|
||||||
|
itemViewOptions: function () {
|
||||||
|
return {
|
||||||
|
viewCollection: this.viewCollection
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
|
sort: function (sortModel, sortDirection) {
|
||||||
|
var collection = this.viewCollection;
|
||||||
|
|
||||||
|
var order;
|
||||||
|
if (sortDirection === 'ascending') {
|
||||||
|
order = -1;
|
||||||
|
}
|
||||||
|
else if (sortDirection === 'descending') {
|
||||||
|
order = 1;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
order = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
var comparator = this.makeComparator(sortModel.get('name'), order,
|
||||||
|
order ?
|
||||||
|
sortModel.sortValue() :
|
||||||
|
function (model) {
|
||||||
|
return model.cid;
|
||||||
|
});
|
||||||
|
|
||||||
|
if (PageableCollection &&
|
||||||
|
collection instanceof PageableCollection) {
|
||||||
|
|
||||||
|
collection.setSorting(order && sortModel.get('name'), order,
|
||||||
|
{sortValue: sortModel.sortValue()});
|
||||||
|
|
||||||
|
if (collection.mode === 'client') {
|
||||||
|
if (collection.fullCollection.comparator === null) {
|
||||||
|
collection.fullCollection.comparator = comparator;
|
||||||
|
}
|
||||||
|
collection.fullCollection.sort();
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
collection.fetch({reset: true});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
collection.comparator = comparator;
|
||||||
|
collection.sort();
|
||||||
|
}
|
||||||
|
|
||||||
|
return this;
|
||||||
|
},
|
||||||
|
|
||||||
|
makeComparator: function (attr, order, func) {
|
||||||
|
|
||||||
|
return function (left, right) {
|
||||||
|
// extract the values from the models
|
||||||
|
var l = func(left, attr), r = func(right, attr), t;
|
||||||
|
|
||||||
|
// if descending order, swap left and right
|
||||||
|
if (order === 1) t = l, l = r, r = t;
|
||||||
|
|
||||||
|
// compare as usual
|
||||||
|
if (l === r) return 0;
|
||||||
|
else if (l < r) return -1;
|
||||||
|
return 1;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,8 @@
|
||||||
|
<div class="btn-group sorting-buttons">
|
||||||
|
<a class="btn dropdown-toggle" data-toggle="dropdown" href="#">
|
||||||
|
Sort <span class="caret"></span>
|
||||||
|
</a>
|
||||||
|
<ul class="dropdown-menu">
|
||||||
|
|
||||||
|
</ul>
|
||||||
|
</div>
|
|
@ -0,0 +1,84 @@
|
||||||
|
'use strict';
|
||||||
|
define(
|
||||||
|
[
|
||||||
|
'backbone',
|
||||||
|
'marionette',
|
||||||
|
'underscore'
|
||||||
|
], function (Backbone, Marionette, _) {
|
||||||
|
|
||||||
|
return Marionette.ItemView.extend({
|
||||||
|
template : 'Shared/Toolbar/Sorting/SortingButtonViewTemplate',
|
||||||
|
tagName : 'li',
|
||||||
|
|
||||||
|
ui: {
|
||||||
|
icon: 'i'
|
||||||
|
},
|
||||||
|
|
||||||
|
events: {
|
||||||
|
'click': 'onClick'
|
||||||
|
},
|
||||||
|
|
||||||
|
initialize: function (options) {
|
||||||
|
this.viewCollection = options.viewCollection;
|
||||||
|
this.listenTo(this.viewCollection, 'drone:sort', this.render);
|
||||||
|
this.listenTo(this.viewCollection, 'backgrid:sort', this.render);
|
||||||
|
},
|
||||||
|
|
||||||
|
onRender: function () {
|
||||||
|
if (this.viewCollection.state) {
|
||||||
|
var key = this.viewCollection.state.sortKey;
|
||||||
|
var order = this.viewCollection.state.order;
|
||||||
|
|
||||||
|
if (key === this.model.get('name')) {
|
||||||
|
this._setSortIcon(order);
|
||||||
|
}
|
||||||
|
|
||||||
|
else {
|
||||||
|
this._removeSortIcon();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
onClick: function (e) {
|
||||||
|
e.preventDefault();
|
||||||
|
|
||||||
|
var collection = this.viewCollection;
|
||||||
|
var event = 'drone:sort';
|
||||||
|
|
||||||
|
collection.state.sortKey = this.model.get('name');
|
||||||
|
var direction = collection.state.order;
|
||||||
|
|
||||||
|
if (direction === 'ascending' || direction === -1)
|
||||||
|
{
|
||||||
|
collection.state.order = 'descending';
|
||||||
|
collection.trigger(event, this.model, 'descending');
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
collection.state.order = 'ascending';
|
||||||
|
collection.trigger(event, this.model, 'ascending');
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
_convertDirectionToIcon: function (dir) {
|
||||||
|
if (dir === 'ascending' || dir === -1) {
|
||||||
|
return 'icon-sort-up';
|
||||||
|
}
|
||||||
|
|
||||||
|
return 'icon-sort-down';
|
||||||
|
},
|
||||||
|
|
||||||
|
_setSortIcon: function (dir) {
|
||||||
|
this._removeSortIcon();
|
||||||
|
this.ui.icon.addClass(this._convertDirectionToIcon(dir));
|
||||||
|
},
|
||||||
|
|
||||||
|
_removeSortIcon: function () {
|
||||||
|
this.ui.icon.removeClass('icon-sort-up icon-sort-down');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,4 @@
|
||||||
|
<a href="#">
|
||||||
|
<span class="sorting-title">{{title}}</span>
|
||||||
|
<i class=""></i>
|
||||||
|
</a>
|
|
@ -6,8 +6,9 @@ define(
|
||||||
'Shared/Toolbar/ButtonModel',
|
'Shared/Toolbar/ButtonModel',
|
||||||
'Shared/Toolbar/Radio/RadioButtonCollectionView',
|
'Shared/Toolbar/Radio/RadioButtonCollectionView',
|
||||||
'Shared/Toolbar/Button/ButtonCollectionView',
|
'Shared/Toolbar/Button/ButtonCollectionView',
|
||||||
|
'Shared/Toolbar/Sorting/SortingButtonCollectionView',
|
||||||
'underscore'
|
'underscore'
|
||||||
], function (Marionette, ButtonCollection, ButtonModel, RadioButtonCollectionView, ButtonCollectionView,_) {
|
], function (Marionette, ButtonCollection, ButtonModel, RadioButtonCollectionView, ButtonCollectionView, SortingButtonCollectionView, _) {
|
||||||
return Marionette.Layout.extend({
|
return Marionette.Layout.extend({
|
||||||
template: 'Shared/Toolbar/ToolbarLayoutTemplate',
|
template: 'Shared/Toolbar/ToolbarLayoutTemplate',
|
||||||
|
|
||||||
|
@ -78,6 +79,15 @@ define(
|
||||||
});
|
});
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
case 'sorting':
|
||||||
|
{
|
||||||
|
buttonGroupView = new SortingButtonCollectionView({
|
||||||
|
collection : groupCollection,
|
||||||
|
menu : buttonGroup,
|
||||||
|
viewCollection: buttonGroup.viewCollection
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
}
|
||||||
default :
|
default :
|
||||||
{
|
{
|
||||||
buttonGroupView = new ButtonCollectionView({
|
buttonGroupView = new ButtonCollectionView({
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
<div class="pull-left page-toolbar">
|
<div class="pull-left page-toolbar">
|
||||||
<div class="x-toolbar-left-1"/>
|
<div class="toolbar-group x-toolbar-left-1"/>
|
||||||
<div class="x-toolbar-left-2"/>
|
<div class="toolbar-group x-toolbar-left-2"/>
|
||||||
</div>
|
</div>
|
||||||
<div class="pull-right page-toolbar">
|
<div class="pull-right page-toolbar">
|
||||||
<div class="x-toolbar-right-1"/>
|
<div class="toolbar-group x-toolbar-right-1"/>
|
||||||
<div class="x-toolbar-right-2"/>
|
<div class="toolbar-group x-toolbar-right-2"/>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -1,11 +1,12 @@
|
||||||
'use strict';
|
'use strict';
|
||||||
define(
|
define(
|
||||||
[
|
[
|
||||||
'backbone'
|
'backbone',
|
||||||
], function (Backbone) {
|
'System/StatusModel'
|
||||||
|
], function (Backbone, StatusModel) {
|
||||||
return Backbone.Model.extend({
|
return Backbone.Model.extend({
|
||||||
url: function () {
|
url: function () {
|
||||||
return '/log/' + this.get('filename');
|
return StatusModel.get('urlBase') + '/log/' + this.get('filename');
|
||||||
},
|
},
|
||||||
|
|
||||||
parse: function (contents) {
|
parse: function (contents) {
|
||||||
|
|
|
@ -1,15 +1,16 @@
|
||||||
'use strict';
|
'use strict';
|
||||||
define(
|
define(
|
||||||
[
|
[
|
||||||
'Cells/NzbDroneCell'
|
'Cells/NzbDroneCell',
|
||||||
], function (NzbDroneCell) {
|
'System/StatusModel'
|
||||||
|
], function (NzbDroneCell, StatusModel) {
|
||||||
return NzbDroneCell.extend({
|
return NzbDroneCell.extend({
|
||||||
|
|
||||||
className: 'download-log-cell',
|
className: 'download-log-cell',
|
||||||
|
|
||||||
render: function () {
|
render: function () {
|
||||||
this.$el.empty();
|
this.$el.empty();
|
||||||
this.$el.html('<a href="/log/{0}" class="no-router" target="_blank">Download</a>'.format(this.cellValue));
|
this.$el.html('<a href="{0}/log/{1}" class="no-router" target="_blank">Download</a>'.format(StatusModel.get('urlBase'), this.cellValue));
|
||||||
|
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,10 +1,16 @@
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
define(['backbone.pageable', 'System/Logs/LogsModel'],
|
define(
|
||||||
function (PagableCollection, LogsModel) {
|
[
|
||||||
return PagableCollection.extend({
|
'backbone.pageable',
|
||||||
|
'System/Logs/LogsModel',
|
||||||
|
'Mixins/AsPersistedStateCollection'
|
||||||
|
],
|
||||||
|
function (PagableCollection, LogsModel, AsPersistedStateCollection) {
|
||||||
|
var collection = PagableCollection.extend({
|
||||||
url : window.NzbDrone.ApiRoot + '/log',
|
url : window.NzbDrone.ApiRoot + '/log',
|
||||||
model: LogsModel,
|
model: LogsModel,
|
||||||
|
tableName: 'logs',
|
||||||
|
|
||||||
state: {
|
state: {
|
||||||
pageSize: 50,
|
pageSize: 50,
|
||||||
|
@ -36,4 +42,6 @@ define(['backbone.pageable', 'System/Logs/LogsModel'],
|
||||||
return resp;
|
return resp;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
return AsPersistedStateCollection.call(collection);
|
||||||
});
|
});
|
||||||
|
|
|
@ -165,7 +165,8 @@ require.config({
|
||||||
renderable: true,
|
renderable: true,
|
||||||
formatter : undefined,
|
formatter : undefined,
|
||||||
cell : undefined,
|
cell : undefined,
|
||||||
headerCell: 'NzbDrone'
|
headerCell: 'NzbDrone',
|
||||||
|
sortType : 'toggle'
|
||||||
};
|
};
|
||||||
|
|
||||||
});
|
});
|
||||||
|
@ -231,7 +232,7 @@ define(
|
||||||
});
|
});
|
||||||
|
|
||||||
app.addInitializer(function () {
|
app.addInitializer(function () {
|
||||||
Backbone.history.start({ pushState: true });
|
Backbone.history.start({ pushState: true, root: serverStatusModel.get('urlBase') });
|
||||||
RouteBinder.bind();
|
RouteBinder.bind();
|
||||||
AppLayout.navbarRegion.show(new NavbarView());
|
AppLayout.navbarRegion.show(new NavbarView());
|
||||||
$('body').addClass('started');
|
$('body').addClass('started');
|
||||||
|
|
|
@ -65,7 +65,7 @@
|
||||||
<div id="errors"></div>
|
<div id="errors"></div>
|
||||||
<script type="text/javascript">
|
<script type="text/javascript">
|
||||||
window.NzbDrone = {
|
window.NzbDrone = {
|
||||||
ApiRoot: '/api',
|
ApiRoot: 'API_ROOT',
|
||||||
ApiKey : 'API_KEY',
|
ApiKey : 'API_KEY',
|
||||||
Version: 'APP_VERSION'
|
Version: 'APP_VERSION'
|
||||||
};
|
};
|
||||||
|
|
|
@ -2,8 +2,9 @@
|
||||||
define(
|
define(
|
||||||
[
|
[
|
||||||
'backbone',
|
'backbone',
|
||||||
'jquery'
|
'jquery',
|
||||||
], function (Backbone,$) {
|
'System/StatusModel'
|
||||||
|
], function (Backbone, $, StatusModel) {
|
||||||
//This module will automatically route all relative links through backbone router rather than
|
//This module will automatically route all relative links through backbone router rather than
|
||||||
//causing links to reload pages.
|
//causing links to reload pages.
|
||||||
|
|
||||||
|
@ -45,7 +46,9 @@ define(
|
||||||
|
|
||||||
|
|
||||||
if (!href.startsWith('http')) {
|
if (!href.startsWith('http')) {
|
||||||
Backbone.history.navigate(href, { trigger: true });
|
var relativeHref = href.replace(StatusModel.get('urlBase'), '');
|
||||||
|
|
||||||
|
Backbone.history.navigate(relativeHref, { trigger: true });
|
||||||
}
|
}
|
||||||
|
|
||||||
else {
|
else {
|
||||||
|
|
Loading…
Reference in New Issue