mirror of
https://github.com/Radarr/Radarr
synced 2025-12-31 20:55:25 +01:00
Merged branch develop into feature/net-import
This commit is contained in:
commit
451f2d30e4
100 changed files with 12216 additions and 7936 deletions
2
.github/ISSUE_TEMPLATE.md
vendored
2
.github/ISSUE_TEMPLATE.md
vendored
|
|
@ -1,6 +1,8 @@
|
|||
|
||||
|
||||
|
||||
Please use the search bar and make sure you are not submitting an already submitted issue.
|
||||
|
||||
Provide a description of the feature request or bug, the more details the better.
|
||||
When possible include a log!
|
||||
|
||||
|
|
|
|||
59
README.md
59
README.md
|
|
@ -4,13 +4,17 @@
|
|||
[](https://github.com/Radarr/Radarr/pulls)
|
||||
[](http://www.gnu.org/licenses/gpl.html)
|
||||
[](https://github.com/Radarr/Radarr)
|
||||
[](https://github.com/Radar/Radarr/releases/latest)
|
||||
[](https://hub.docker.com/r/linuxserver/radarr/)
|
||||
|
||||
| Service | Master | Develop |
|
||||
|----------|:---------------------------:|:----------------------------:|
|
||||
| AppVeyor | [](https://ci.appveyor.com/project/galli-leo/Radarr) | [](https://ci.appveyor.com/project/galli-leo/Radarr-usby1) |
|
||||
| Travis | [](https://travis-ci.org/Radarr/Radarr) | [](https://travis-ci.org/Radarr/Radarr) |
|
||||
|
||||
This fork of Sonarr aims to turn it into something like CouchPotato.
|
||||
A fork of [Sonarr](https://github.com/Sonarr/Sonarr) to work with movies à la Couchpotato.
|
||||
|
||||
**This fork works independently of Sonarr and will not interfere with it.**
|
||||
|
||||
## Downloads
|
||||
|
||||
|
|
@ -18,42 +22,46 @@ This fork of Sonarr aims to turn it into something like CouchPotato.
|
|||
|
||||
[](https://ci.appveyor.com/project/galli-leo/radarr-usby1/build/artifacts)
|
||||
|
||||
[](https://store.docker.com/community/images/linuxserver/radarr)
|
||||
[](https://store.docker.com/community/images/lsioarmhf/radarr)
|
||||
[](https://store.docker.com/community/images/lsioarmhf/radarr-aarch64)
|
||||
[](https://store.docker.com/community/images/linuxserver/radarr)
|
||||
[](https://store.docker.com/community/images/lsioarmhf/radarr)
|
||||
[](https://store.docker.com/community/images/lsioarmhf/radarr-aarch64)
|
||||
|
||||
To connect to the UI, fire up your browser and open <http://localhost:7878> or <http://your-ip:7878>.
|
||||
|
||||
## Support
|
||||
|
||||
[](https://discord.gg/AD3UP37)
|
||||
[](https://www.reddit.com/r/radarr)
|
||||
[](https://github.com/Radarr/Radarr/issues)
|
||||
|
||||
## Features
|
||||
|
||||
### Currently Working
|
||||
|
||||
* Adding new movies
|
||||
* Manually searching for releases of movies
|
||||
* Automatically searching for releases
|
||||
* Automatically importing downloaded movies
|
||||
* Recognizing Special Editions, Director's Cut, etc.
|
||||
* Identifying releases with hardcoded subs
|
||||
* Rarbg.to, Torznab and Newznab Indexer
|
||||
* QBittorrent and Deluge download client (Other clients are coming)
|
||||
* New TorrentPotato Indexer (Works well with [Jackett](https://github.com/Jackett/Jackett))
|
||||
|
||||
### Planned Features
|
||||
|
||||
* Scanning PreDB to know when a new release is available
|
||||
* Fixing the other Indexers and download clients
|
||||
* Importing of Sonarr config
|
||||
|
||||
### Major Features
|
||||
### Current Features
|
||||
|
||||
* Adding new movies with lots of information, such as trailers, ratings, etc.
|
||||
* Support for major platforms: Windows, Linux, macOS, Raspberry Pi, etc.
|
||||
* Can watch for better quality of the movies you have and do an automatic upgrade. *eg. from DVD to Blu-Ray*
|
||||
* Automatic failed download handling will try another release if one fails
|
||||
* Manual search so you can pick any release or to see why a release was not downloaded automatically
|
||||
* Full integration with SABnzbd and NZBGet
|
||||
* Full integration with Kodi, Plex (notification, library update, metadata)
|
||||
* Automatically searching for releases as well as RSS Sync
|
||||
* Automatically importing downloaded movies
|
||||
* Recognizing Special Editions, Director's Cut, etc.
|
||||
* Identifying releases with hardcoded subs
|
||||
* All indexers supported by Sonarr also supported
|
||||
* New PassThePopcorn Indexer
|
||||
* QBittorrent, Deluge, rTorrent, Transmission and uTorrent download client (Other clients are coming)
|
||||
* New TorrentPotato Indexer (Works well with [Jackett](https://github.com/Jackett/Jackett))
|
||||
* And a beautiful UI
|
||||
|
||||
### Planned Features
|
||||
|
||||
* Scanning PreDB to know when a new release is available
|
||||
* Fixing the other Indexers and download clients
|
||||
* Importing movies from various online sources, such as IMDb Watchlists (A complete list can be found [here](https://github.com/Radarr/Radarr/issues/114))
|
||||
* Full integration with Kodi, Plex (notification, library update, metadata)
|
||||
|
||||
|
||||
## Configuring Development Environment
|
||||
|
||||
### Requirements
|
||||
|
|
@ -70,7 +78,8 @@ To connect to the UI, fire up your browser and open <http://localhost:7878> or <
|
|||
* Install the required Node Packages `npm install`
|
||||
* Start gulp to monitor your dev environment for any changes that need post processing using `npm start` command.
|
||||
|
||||
*Please note: gulp must be running at all times while you are working with Radarr client source files.*
|
||||
> **Notice**
|
||||
> Gulp must be running at all times while you are working with Radarr client source files.
|
||||
|
||||
### Development
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using NzbDrone.Api.Movie;
|
||||
using NzbDrone.Api.REST;
|
||||
using NzbDrone.Core.Qualities;
|
||||
using NzbDrone.Api.Series;
|
||||
|
|
@ -11,13 +12,14 @@ public class BlacklistResource : RestResource
|
|||
{
|
||||
public int SeriesId { get; set; }
|
||||
public List<int> EpisodeIds { get; set; }
|
||||
public int MovieId { get; set; }
|
||||
public string SourceTitle { get; set; }
|
||||
public QualityModel Quality { get; set; }
|
||||
public DateTime Date { get; set; }
|
||||
public DownloadProtocol Protocol { get; set; }
|
||||
public string Indexer { get; set; }
|
||||
public string Message { get; set; }
|
||||
|
||||
public MovieResource Movie { get; set; }
|
||||
public SeriesResource Series { get; set; }
|
||||
}
|
||||
|
||||
|
|
@ -30,7 +32,7 @@ public static BlacklistResource MapToResource(this Core.Blacklisting.Blacklist m
|
|||
return new BlacklistResource
|
||||
{
|
||||
Id = model.Id,
|
||||
|
||||
MovieId = model.MovieId,
|
||||
SeriesId = model.SeriesId,
|
||||
EpisodeIds = model.EpisodeIds,
|
||||
SourceTitle = model.SourceTitle,
|
||||
|
|
@ -39,7 +41,7 @@ public static BlacklistResource MapToResource(this Core.Blacklisting.Blacklist m
|
|||
Protocol = model.Protocol,
|
||||
Indexer = model.Indexer,
|
||||
Message = model.Message,
|
||||
|
||||
Movie = model.Movie.ToResource(),
|
||||
Series = model.Series.ToResource()
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,24 +2,38 @@
|
|||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using NzbDrone.Api.Episodes;
|
||||
using NzbDrone.Api.Movie;
|
||||
using NzbDrone.Core.Datastore.Events;
|
||||
using NzbDrone.Core.MediaCover;
|
||||
using NzbDrone.Core.MediaFiles;
|
||||
using NzbDrone.Core.MediaFiles.Events;
|
||||
using NzbDrone.Core.Messaging.Events;
|
||||
using NzbDrone.Core.MovieStats;
|
||||
using NzbDrone.Core.Tv;
|
||||
using NzbDrone.Core.Tv.Events;
|
||||
using NzbDrone.Core.Validation.Paths;
|
||||
using NzbDrone.Core.DataAugmentation.Scene;
|
||||
using NzbDrone.Core.Validation;
|
||||
using NzbDrone.Core.DecisionEngine;
|
||||
using NzbDrone.Core.Tv;
|
||||
using NzbDrone.SignalR;
|
||||
|
||||
namespace NzbDrone.Api.Calendar
|
||||
{
|
||||
public class CalendarModule : EpisodeModuleWithSignalR
|
||||
public class CalendarModule : MovieModule
|
||||
{
|
||||
public CalendarModule(IEpisodeService episodeService,
|
||||
ISeriesService seriesService,
|
||||
IQualityUpgradableSpecification qualityUpgradableSpecification,
|
||||
IBroadcastSignalRMessage signalRBroadcaster)
|
||||
: base(episodeService, seriesService, qualityUpgradableSpecification, signalRBroadcaster, "calendar")
|
||||
public CalendarModule(IBroadcastSignalRMessage signalR,
|
||||
IMovieService moviesService,
|
||||
IMovieStatisticsService moviesStatisticsService,
|
||||
ISceneMappingService sceneMappingService,
|
||||
IMapCoversToLocal coverMapper)
|
||||
: base(signalR, moviesService, moviesStatisticsService, sceneMappingService, coverMapper, "calendar")
|
||||
{
|
||||
|
||||
GetResourceAll = GetCalendar;
|
||||
}
|
||||
|
||||
private List<EpisodeResource> GetCalendar()
|
||||
private List<MovieResource> GetCalendar()
|
||||
{
|
||||
var start = DateTime.Today;
|
||||
var end = DateTime.Today.AddDays(2);
|
||||
|
|
@ -33,9 +47,9 @@ private List<EpisodeResource> GetCalendar()
|
|||
if (queryEnd.HasValue) end = DateTime.Parse(queryEnd.Value);
|
||||
if (queryIncludeUnmonitored.HasValue) includeUnmonitored = Convert.ToBoolean(queryIncludeUnmonitored.Value);
|
||||
|
||||
var resources = MapToResource(_episodeService.EpisodesBetweenDates(start, end, includeUnmonitored), true, true);
|
||||
var resources = _moviesService.GetMoviesBetweenDates(start, end, includeUnmonitored).Select(MapToResource);
|
||||
|
||||
return resources.OrderBy(e => e.AirDateUtc).ToList();
|
||||
return resources.OrderBy(e => e.InCinemas).ToList();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@ public class RenameMovieModule : NzbDroneRestModule<RenameMovieResource>
|
|||
private readonly IRenameMovieFileService _renameMovieFileService;
|
||||
|
||||
public RenameMovieModule(IRenameMovieFileService renameMovieFileService)
|
||||
: base("rename")
|
||||
: base("renameMovie")
|
||||
{
|
||||
_renameMovieFileService = renameMovieFileService;
|
||||
|
||||
|
|
|
|||
|
|
@ -264,6 +264,7 @@
|
|||
<Compile Include="Wanted\CutoffModule.cs" />
|
||||
<Compile Include="Wanted\LegacyMissingModule.cs" />
|
||||
<Compile Include="Wanted\MissingModule.cs" />
|
||||
<Compile Include="Wanted\MovieMissingModule.cs" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<None Include="app.config" />
|
||||
|
|
@ -298,4 +299,4 @@
|
|||
<Target Name="AfterBuild">
|
||||
</Target>
|
||||
-->
|
||||
</Project>
|
||||
</Project>
|
||||
|
|
|
|||
|
|
@ -28,7 +28,7 @@ public class MovieModule : NzbDroneRestModuleWithSignalR<MovieResource, Core.Tv.
|
|||
IHandle<MediaCoversUpdatedEvent>
|
||||
|
||||
{
|
||||
private readonly IMovieService _moviesService;
|
||||
protected readonly IMovieService _moviesService;
|
||||
private readonly IMovieStatisticsService _moviesStatisticsService;
|
||||
private readonly IMapCoversToLocal _coverMapper;
|
||||
|
||||
|
|
@ -78,13 +78,33 @@ ProfileExistsValidator profileExistsValidator
|
|||
PutValidator.RuleFor(s => s.Path).IsValidPath();
|
||||
}
|
||||
|
||||
public MovieModule(IBroadcastSignalRMessage signalRBroadcaster,
|
||||
IMovieService moviesService,
|
||||
IMovieStatisticsService moviesStatisticsService,
|
||||
ISceneMappingService sceneMappingService,
|
||||
IMapCoversToLocal coverMapper,
|
||||
string resource)
|
||||
: base(signalRBroadcaster, resource)
|
||||
{
|
||||
_moviesService = moviesService;
|
||||
_moviesStatisticsService = moviesStatisticsService;
|
||||
|
||||
_coverMapper = coverMapper;
|
||||
|
||||
GetResourceAll = AllMovie;
|
||||
GetResourceById = GetMovie;
|
||||
CreateResource = AddMovie;
|
||||
UpdateResource = UpdateMovie;
|
||||
DeleteResource = DeleteMovie;
|
||||
}
|
||||
|
||||
private MovieResource GetMovie(int id)
|
||||
{
|
||||
var movies = _moviesService.GetMovie(id);
|
||||
return MapToResource(movies);
|
||||
}
|
||||
|
||||
private MovieResource MapToResource(Core.Tv.Movie movies)
|
||||
protected MovieResource MapToResource(Core.Tv.Movie movies)
|
||||
{
|
||||
if (movies == null) return null;
|
||||
|
||||
|
|
@ -181,6 +201,8 @@ private void PopulateAlternateTitles(MovieResource resource)
|
|||
//var mappings = null;//_sceneMappingService.FindByTvdbId(resource.TvdbId);
|
||||
|
||||
//if (mappings == null) return;
|
||||
|
||||
//Not necessary anymore
|
||||
|
||||
//resource.AlternateTitles = mappings.Select(v => new AlternateTitleResource { Title = v.Title, SeasonNumber = v.SeasonNumber, SceneSeasonNumber = v.SceneSeasonNumber }).ToList();
|
||||
}
|
||||
|
|
@ -219,7 +241,7 @@ public void Handle(MovieRenamedEvent message)
|
|||
|
||||
public void Handle(MediaCoversUpdatedEvent message)
|
||||
{
|
||||
//BroadcastResourceChange(ModelAction.Updated, message.Movie.Id);
|
||||
BroadcastResourceChange(ModelAction.Updated, message.Movie.Id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -34,6 +34,8 @@ public MovieResource()
|
|||
public string RemotePoster { get; set; }
|
||||
public int Year { get; set; }
|
||||
public bool HasFile { get; set; }
|
||||
public string YouTubeTrailerId { get; set; }
|
||||
public string Studio { get; set; }
|
||||
|
||||
//View & Edit
|
||||
public string Path { get; set; }
|
||||
|
|
@ -144,7 +146,9 @@ public static MovieResource ToResource(this Core.Tv.Movie model)
|
|||
AddOptions = model.AddOptions,
|
||||
AlternativeTitles = model.AlternativeTitles,
|
||||
Ratings = model.Ratings,
|
||||
MovieFile = movieFile
|
||||
MovieFile = movieFile,
|
||||
YouTubeTrailerId = model.YouTubeTrailerId,
|
||||
Studio = model.Studio
|
||||
};
|
||||
}
|
||||
|
||||
|
|
@ -191,7 +195,9 @@ public static Core.Tv.Movie ToModel(this MovieResource resource)
|
|||
Added = resource.Added,
|
||||
AddOptions = resource.AddOptions,
|
||||
AlternativeTitles = resource.AlternativeTitles,
|
||||
Ratings = resource.Ratings
|
||||
Ratings = resource.Ratings,
|
||||
YouTubeTrailerId = resource.YouTubeTrailerId,
|
||||
Studio = resource.Studio
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -236,7 +236,7 @@ public void Handle(SeriesRenamedEvent message)
|
|||
|
||||
public void Handle(MediaCoversUpdatedEvent message)
|
||||
{
|
||||
BroadcastResourceChange(ModelAction.Updated, message.Series.Id);
|
||||
//BroadcastResourceChange(ModelAction.Updated, message.Series.Id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@ public MissingModule(IEpisodeService episodeService,
|
|||
ISeriesService seriesService,
|
||||
IQualityUpgradableSpecification qualityUpgradableSpecification,
|
||||
IBroadcastSignalRMessage signalRBroadcaster)
|
||||
: base(episodeService, seriesService, qualityUpgradableSpecification, signalRBroadcaster, "wanted/missing")
|
||||
: base(episodeService, seriesService, qualityUpgradableSpecification, signalRBroadcaster, "wanted/missing_episodes")
|
||||
{
|
||||
GetResourcePaged = GetMissingEpisodes;
|
||||
}
|
||||
|
|
|
|||
77
src/NzbDrone.Api/Wanted/MovieMissingModule.cs
Normal file
77
src/NzbDrone.Api/Wanted/MovieMissingModule.cs
Normal file
|
|
@ -0,0 +1,77 @@
|
|||
using NzbDrone.Api.Movie;
|
||||
using NzbDrone.Api.Movies;
|
||||
using NzbDrone.Core.DecisionEngine;
|
||||
using NzbDrone.Core.Tv;
|
||||
using NzbDrone.Core.Datastore;
|
||||
using NzbDrone.SignalR;
|
||||
using NzbDrone.Core.Download;
|
||||
using NzbDrone.Core.MediaFiles.Events;
|
||||
using NzbDrone.Core.Messaging.Events;
|
||||
using System;
|
||||
using NzbDrone.Core.Datastore.Events;
|
||||
|
||||
namespace NzbDrone.Api.Wanted
|
||||
{
|
||||
class MovieMissingModule : NzbDroneRestModuleWithSignalR<MovieResource, Core.Tv.Movie>,
|
||||
IHandle<MovieGrabbedEvent>,
|
||||
IHandle<MovieDownloadedEvent>
|
||||
{
|
||||
protected readonly IMovieService _movieService;
|
||||
|
||||
public MovieMissingModule(IMovieService movieService,
|
||||
IQualityUpgradableSpecification qualityUpgradableSpecification,
|
||||
IBroadcastSignalRMessage signalRBroadcaster)
|
||||
: base(signalRBroadcaster, "wanted/missing")
|
||||
{
|
||||
|
||||
_movieService = movieService;
|
||||
GetResourcePaged = GetMissingMovies;
|
||||
}
|
||||
|
||||
private PagingResource<MovieResource> GetMissingMovies(PagingResource<MovieResource> pagingResource)
|
||||
{
|
||||
var pagingSpec = pagingResource.MapToPagingSpec<MovieResource, Core.Tv.Movie>("physicalRelease", SortDirection.Descending);
|
||||
|
||||
if (pagingResource.FilterKey == "monitored" && pagingResource.FilterValue == "false")
|
||||
{
|
||||
pagingSpec.FilterExpression = v => v.Monitored == false;
|
||||
}
|
||||
else
|
||||
{
|
||||
pagingSpec.FilterExpression = v => v.Monitored == true;
|
||||
}
|
||||
|
||||
var resource = ApplyToPage(_movieService.MoviesWithoutFiles, pagingSpec, v => MapToResource(v, false));
|
||||
|
||||
return resource;
|
||||
}
|
||||
|
||||
private MovieResource GetMovie(int id)
|
||||
{
|
||||
var movie = _movieService.GetMovie(id);
|
||||
var resource = MapToResource(movie, true);
|
||||
return resource;
|
||||
}
|
||||
|
||||
private MovieResource MapToResource(Core.Tv.Movie movie, bool includeMovieFile)
|
||||
{
|
||||
var resource = movie.ToResource();
|
||||
return resource;
|
||||
}
|
||||
|
||||
public void Handle(MovieGrabbedEvent message)
|
||||
{
|
||||
var resource = message.Movie.Movie.ToResource();
|
||||
|
||||
//add a grabbed field in MovieResource?
|
||||
//resource.Grabbed = true;
|
||||
|
||||
BroadcastResourceChange(ModelAction.Updated, resource);
|
||||
}
|
||||
|
||||
public void Handle(MovieDownloadedEvent message)
|
||||
{
|
||||
BroadcastResourceChange(ModelAction.Updated, message.Movie.Movie.Id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -92,14 +92,14 @@ public void Setup()
|
|||
protected void GivenFailedDownload()
|
||||
{
|
||||
Mocker.GetMock<INzbgetProxy>()
|
||||
.Setup(s => s.DownloadNzb(It.IsAny<byte[]>(), It.IsAny<string>(), It.IsAny<string>(), It.IsAny<int>(), It.IsAny<NzbgetSettings>()))
|
||||
.Setup(s => s.DownloadNzb(It.IsAny<byte[]>(), It.IsAny<string>(), It.IsAny<string>(), It.IsAny<int>(), It.IsAny<bool>(), It.IsAny<NzbgetSettings>()))
|
||||
.Returns((string)null);
|
||||
}
|
||||
|
||||
protected void GivenSuccessfulDownload()
|
||||
{
|
||||
Mocker.GetMock<INzbgetProxy>()
|
||||
.Setup(s => s.DownloadNzb(It.IsAny<byte[]>(), It.IsAny<string>(), It.IsAny<string>(), It.IsAny<int>(), It.IsAny<NzbgetSettings>()))
|
||||
.Setup(s => s.DownloadNzb(It.IsAny<byte[]>(), It.IsAny<string>(), It.IsAny<string>(), It.IsAny<int>(), It.IsAny<bool>(), It.IsAny<NzbgetSettings>()))
|
||||
.Returns(Guid.NewGuid().ToString().Replace("-", ""));
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -47,5 +47,17 @@ public void should_parse_version_from_title(string title, int version)
|
|||
{
|
||||
QualityParser.ParseQuality(title).Revision.Version.Should().Be(version);
|
||||
}
|
||||
|
||||
[TestCase("Deadpool 2016 2160p 4K UltraHD BluRay DTS-HD MA 7 1 x264-Whatevs", 19)]
|
||||
[TestCase("Deadpool 2016 2160p 4K UltraHD DTS-HD MA 7 1 x264-Whatevs", 16)]
|
||||
[TestCase("Deadpool 2016 4K 2160p UltraHD BluRay AAC2 0 HEVC x265", 19)]
|
||||
[TestCase("The Revenant 2015 2160p UHD BluRay DTS x264-Whatevs", 19)]
|
||||
[TestCase("The Revenant 2015 2160p UHD BluRay FLAC 7 1 x264-Whatevs", 19)]
|
||||
[TestCase("The Martian 2015 2160p Ultra HD BluRay DTS-HD MA 7 1 x264-Whatevs", 19)]
|
||||
[TestCase("Into the Inferno 2016 2160p Netflix WEBRip DD5 1 x264-Whatevs", 18)]
|
||||
public void should_parse_ultrahd_from_title(string title, int version)
|
||||
{
|
||||
QualityParser.ParseQuality(title).Quality.Id.Should().Be(version);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -11,6 +11,8 @@ public class Blacklist : ModelBase
|
|||
{
|
||||
public int SeriesId { get; set; }
|
||||
public Series Series { get; set; }
|
||||
public int MovieId { get; set; }
|
||||
public Movie Movie { get; set; }
|
||||
public List<int> EpisodeIds { get; set; }
|
||||
public string SourceTitle { get; set; }
|
||||
public QualityModel Quality { get; set; }
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ public interface IBlacklistRepository : IBasicRepository<Blacklist>
|
|||
{
|
||||
List<Blacklist> BlacklistedByTitle(int seriesId, string sourceTitle);
|
||||
List<Blacklist> BlacklistedByTorrentInfoHash(int seriesId, string torrentInfoHash);
|
||||
List<Blacklist> BlacklistedBySeries(int seriesId);
|
||||
List<Blacklist> BlacklistedByMovie(int seriesId);
|
||||
}
|
||||
|
||||
public class BlacklistRepository : BasicRepository<Blacklist>, IBlacklistRepository
|
||||
|
|
@ -20,15 +20,15 @@ public BlacklistRepository(IMainDatabase database, IEventAggregator eventAggrega
|
|||
{
|
||||
}
|
||||
|
||||
public List<Blacklist> BlacklistedByTitle(int seriesId, string sourceTitle)
|
||||
public List<Blacklist> BlacklistedByTitle(int movieId, string sourceTitle)
|
||||
{
|
||||
return Query.Where(e => e.SeriesId == seriesId)
|
||||
return Query.Where(e => e.MovieId == movieId)
|
||||
.AndWhere(e => e.SourceTitle.Contains(sourceTitle));
|
||||
}
|
||||
|
||||
public List<Blacklist> BlacklistedByTorrentInfoHash(int seriesId, string torrentInfoHash)
|
||||
public List<Blacklist> BlacklistedByTorrentInfoHash(int movieId, string torrentInfoHash)
|
||||
{
|
||||
return Query.Where(e => e.SeriesId == seriesId)
|
||||
return Query.Where(e => e.MovieId == movieId)
|
||||
.AndWhere(e => e.TorrentInfoHash.Contains(torrentInfoHash));
|
||||
}
|
||||
|
||||
|
|
@ -37,9 +37,14 @@ public List<Blacklist> BlacklistedBySeries(int seriesId)
|
|||
return Query.Where(b => b.SeriesId == seriesId);
|
||||
}
|
||||
|
||||
public List<Blacklist> BlacklistedByMovie(int movieId)
|
||||
{
|
||||
return Query.Where(b => b.MovieId == movieId);
|
||||
}
|
||||
|
||||
protected override SortBuilder<Blacklist> GetPagedQuery(QueryBuilder<Blacklist> query, PagingSpec<Blacklist> pagingSpec)
|
||||
{
|
||||
var baseQuery = query.Join<Blacklist, Series>(JoinType.Inner, h => h.Series, (h, s) => h.SeriesId == s.Id);
|
||||
var baseQuery = query.Join<Blacklist, Movie>(JoinType.Inner, h => h.Movie, (h, s) => h.MovieId == s.Id);
|
||||
|
||||
return base.GetPagedQuery(baseQuery, pagingSpec);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -21,7 +21,7 @@ public class BlacklistService : IBlacklistService,
|
|||
|
||||
IExecute<ClearBlacklistCommand>,
|
||||
IHandle<DownloadFailedEvent>,
|
||||
IHandleAsync<SeriesDeletedEvent>
|
||||
IHandleAsync<MovieDeletedEvent>
|
||||
{
|
||||
private readonly IBlacklistRepository _blacklistRepository;
|
||||
|
||||
|
|
@ -128,8 +128,9 @@ public void Handle(DownloadFailedEvent message)
|
|||
{
|
||||
var blacklist = new Blacklist
|
||||
{
|
||||
SeriesId = message.SeriesId,
|
||||
SeriesId = 0,
|
||||
EpisodeIds = message.EpisodeIds,
|
||||
MovieId = message.MovieId,
|
||||
SourceTitle = message.SourceTitle,
|
||||
Quality = message.Quality,
|
||||
Date = DateTime.UtcNow,
|
||||
|
|
@ -144,9 +145,9 @@ public void Handle(DownloadFailedEvent message)
|
|||
_blacklistRepository.Insert(blacklist);
|
||||
}
|
||||
|
||||
public void HandleAsync(SeriesDeletedEvent message)
|
||||
public void HandleAsync(MovieDeletedEvent message)
|
||||
{
|
||||
var blacklisted = _blacklistRepository.BlacklistedBySeries(message.Series.Id);
|
||||
var blacklisted = _blacklistRepository.BlacklistedByMovie(message.Movie.Id);
|
||||
|
||||
_blacklistRepository.DeleteMany(blacklisted);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,21 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using FluentMigrator;
|
||||
using NzbDrone.Core.Datastore.Migration.Framework;
|
||||
using System.Data;
|
||||
|
||||
namespace NzbDrone.Core.Datastore.Migration
|
||||
{
|
||||
[Migration(119)]
|
||||
public class add_youtube_trailer_id : NzbDroneMigrationBase
|
||||
{
|
||||
protected override void MainDbUpgrade()
|
||||
{
|
||||
Alter.Table("Movies").AddColumn("YouTubeTrailerId").AsString().Nullable();
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,20 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using FluentMigrator;
|
||||
using NzbDrone.Core.Datastore.Migration.Framework;
|
||||
using System.Data;
|
||||
|
||||
namespace NzbDrone.Core.Datastore.Migration
|
||||
{
|
||||
[Migration(120)]
|
||||
public class add_studio : NzbDroneMigrationBase
|
||||
{
|
||||
protected override void MainDbUpgrade()
|
||||
{
|
||||
Alter.Table("Movies").AddColumn("Studio").AsString().Nullable();
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,67 @@
|
|||
using System.Data;
|
||||
using FluentMigrator;
|
||||
using NzbDrone.Core.Datastore.Migration.Framework;
|
||||
using System.Text;
|
||||
using System.Text.RegularExpressions;
|
||||
|
||||
namespace NzbDrone.Core.Datastore.Migration
|
||||
{
|
||||
[Migration(121)]
|
||||
public class update_filedate_config : NzbDroneMigrationBase
|
||||
{
|
||||
protected override void MainDbUpgrade()
|
||||
{
|
||||
Execute.WithConnection(SetTitleSlug);
|
||||
}
|
||||
|
||||
private void SetTitleSlug(IDbConnection conn, IDbTransaction tran)
|
||||
{
|
||||
using (IDbCommand getSeriesCmd = conn.CreateCommand())
|
||||
{
|
||||
getSeriesCmd.Transaction = tran;
|
||||
getSeriesCmd.CommandText = @"SELECT Id, Value FROM Config WHERE Key = 'filedate'";
|
||||
using (IDataReader seriesReader = getSeriesCmd.ExecuteReader())
|
||||
{
|
||||
while (seriesReader.Read())
|
||||
{
|
||||
var id = seriesReader.GetInt32(0);
|
||||
var value = seriesReader.GetString(1);
|
||||
|
||||
using (IDbCommand updateCmd = conn.CreateCommand())
|
||||
{
|
||||
updateCmd.Transaction = tran;
|
||||
updateCmd.CommandText = "UPDATE Config SET Value = 'Release' WHERE Id = ?";
|
||||
updateCmd.AddParameter(id);
|
||||
|
||||
updateCmd.ExecuteNonQuery();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static string ToUrlSlug(string value)
|
||||
{
|
||||
//First to lower case
|
||||
value = value.ToLowerInvariant();
|
||||
|
||||
//Remove all accents
|
||||
var bytes = Encoding.GetEncoding("Cyrillic").GetBytes(value);
|
||||
value = Encoding.ASCII.GetString(bytes);
|
||||
|
||||
//Replace spaces
|
||||
value = Regex.Replace(value, @"\s", "-", RegexOptions.Compiled);
|
||||
|
||||
//Remove invalid chars
|
||||
value = Regex.Replace(value, @"[^a-z0-9\s-_]", "", RegexOptions.Compiled);
|
||||
|
||||
//Trim dashes from end
|
||||
value = value.Trim('-', '_');
|
||||
|
||||
//Replace double occurences of - or _
|
||||
value = Regex.Replace(value, @"([-_]){2,}", "$1", RegexOptions.Compiled);
|
||||
|
||||
return value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,22 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using FluentMigrator;
|
||||
using NzbDrone.Core.Datastore.Migration.Framework;
|
||||
using System.Data;
|
||||
|
||||
namespace NzbDrone.Core.Datastore.Migration
|
||||
{
|
||||
[Migration(122)]
|
||||
public class add_movieid_to_blacklist : NzbDroneMigrationBase
|
||||
{
|
||||
protected override void MainDbUpgrade()
|
||||
{
|
||||
Alter.Table("Blacklist").AddColumn("MovieId").AsInt32().Nullable().WithDefaultValue(0);
|
||||
Alter.Table("Blacklist").AlterColumn("SeriesId").AsInt32().Nullable();
|
||||
Alter.Table("Blacklist").AlterColumn("EpisodeIds").AsString().Nullable();
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
|
@ -68,6 +68,17 @@ private int CompareQuality(DownloadDecision x, DownloadDecision y)
|
|||
private int CompareProtocol(DownloadDecision x, DownloadDecision y)
|
||||
{
|
||||
|
||||
|
||||
if (x.IsForMovie)
|
||||
{
|
||||
return CompareBy(x.RemoteMovie, y.RemoteMovie, remoteEpisode =>
|
||||
{
|
||||
var delayProfile = _delayProfileService.BestForTags(remoteEpisode.Movie.Tags);
|
||||
var downloadProtocol = remoteEpisode.Release.DownloadProtocol;
|
||||
return downloadProtocol == delayProfile.PreferredProtocol;
|
||||
});
|
||||
}
|
||||
|
||||
var result = CompareBy(x.RemoteEpisode, y.RemoteEpisode, remoteEpisode =>
|
||||
{
|
||||
var delayProfile = _delayProfileService.BestForTags(remoteEpisode.Series.Tags);
|
||||
|
|
@ -75,15 +86,7 @@ private int CompareProtocol(DownloadDecision x, DownloadDecision y)
|
|||
return downloadProtocol == delayProfile.PreferredProtocol;
|
||||
});
|
||||
|
||||
if (x.IsForMovie)
|
||||
{
|
||||
result = CompareBy(x.RemoteMovie, y.RemoteMovie, remoteEpisode =>
|
||||
{
|
||||
var delayProfile = _delayProfileService.BestForTags(remoteEpisode.Movie.Tags);
|
||||
var downloadProtocol = remoteEpisode.Release.DownloadProtocol;
|
||||
return downloadProtocol == delayProfile.PreferredProtocol;
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
return result;
|
||||
}
|
||||
|
|
@ -125,8 +128,8 @@ private int ComparePeersIfTorrent(DownloadDecision x, DownloadDecision y)
|
|||
|
||||
private int CompareAgeIfUsenet(DownloadDecision x, DownloadDecision y)
|
||||
{
|
||||
if (x.RemoteEpisode.Release.DownloadProtocol != DownloadProtocol.Usenet ||
|
||||
y.RemoteEpisode.Release.DownloadProtocol != DownloadProtocol.Usenet)
|
||||
if (x.RemoteMovie.Release.DownloadProtocol != DownloadProtocol.Usenet ||
|
||||
y.RemoteMovie.Release.DownloadProtocol != DownloadProtocol.Usenet)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -32,7 +32,7 @@ public DownloadDecisionMaker(IEnumerable<IDecisionEngineSpecification> specifica
|
|||
|
||||
public List<DownloadDecision> GetRssDecision(List<ReleaseInfo> reports)
|
||||
{
|
||||
return GetDecisions(reports).ToList();
|
||||
return GetMovieDecisions(reports).ToList();
|
||||
}
|
||||
|
||||
public List<DownloadDecision> GetSearchDecision(List<ReleaseInfo> reports, SearchCriteriaBase searchCriteriaBase)
|
||||
|
|
@ -83,7 +83,7 @@ private IEnumerable<DownloadDecision> GetMovieDecisions(List<ReleaseInfo> report
|
|||
{
|
||||
if (parsedEpisodeInfo.Quality.HardcodedSubs.IsNotNullOrWhiteSpace())
|
||||
{
|
||||
remoteEpisode.DownloadAllowed = false;
|
||||
remoteEpisode.DownloadAllowed = true;
|
||||
decision = new DownloadDecision(remoteEpisode, new Rejection("Hardcoded subs found: " + parsedEpisodeInfo.Quality.HardcodedSubs));
|
||||
}
|
||||
else
|
||||
|
|
|
|||
|
|
@ -34,11 +34,11 @@ public List<DownloadDecision> PrioritizeDecisions(List<DownloadDecision> decisio
|
|||
public List<DownloadDecision> PrioritizeDecisionsForMovies(List<DownloadDecision> decisions)
|
||||
{
|
||||
return decisions.Where(c => c.RemoteMovie.Movie != null)
|
||||
/*.GroupBy(c => c.RemoteMovie.Movie.Id, (movieId, downloadDecisions) =>
|
||||
.GroupBy(c => c.RemoteMovie.Movie.Id, (movieId, downloadDecisions) =>
|
||||
{
|
||||
return downloadDecisions.OrderByDescending(decision => decision, new DownloadDecisionComparer(_delayProfileService));
|
||||
})
|
||||
.SelectMany(c => c)*/
|
||||
.SelectMany(c => c)
|
||||
.Union(decisions.Where(c => c.RemoteMovie.Movie == null))
|
||||
.ToList();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -33,7 +33,11 @@ public Decision IsSatisfiedBy(RemoteEpisode subject, SearchCriteriaBase searchCr
|
|||
public Decision IsSatisfiedBy(RemoteMovie subject, SearchCriteriaBase searchCriteria)
|
||||
{
|
||||
|
||||
throw new NotImplementedException();
|
||||
if (_blacklistService.Blacklisted(subject.Movie.Id, subject.Release))
|
||||
{
|
||||
_logger.Debug("{0} is blacklisted, rejecting.", subject.Release.Title);
|
||||
return Decision.Reject("Release is blacklisted");
|
||||
}
|
||||
|
||||
return Decision.Accept();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -32,9 +32,12 @@ public Nzbget(INzbgetProxy proxy,
|
|||
protected override string AddFromNzbFile(RemoteEpisode remoteEpisode, string filename, byte[] fileContents)
|
||||
{
|
||||
var category = Settings.TvCategory;
|
||||
|
||||
var priority = remoteEpisode.IsRecentEpisode() ? Settings.RecentTvPriority : Settings.OlderTvPriority;
|
||||
|
||||
var response = _proxy.DownloadNzb(fileContents, filename, category, priority, Settings);
|
||||
var addpaused = Settings.AddPaused;
|
||||
|
||||
var response = _proxy.DownloadNzb(fileContents, filename, category, priority, addpaused, Settings);
|
||||
|
||||
if (response == null)
|
||||
{
|
||||
|
|
@ -47,9 +50,12 @@ protected override string AddFromNzbFile(RemoteEpisode remoteEpisode, string fil
|
|||
protected override string AddFromNzbFile(RemoteMovie remoteMovie, string filename, byte[] fileContents)
|
||||
{
|
||||
var category = Settings.TvCategory; // TODO: Update this to MovieCategory?
|
||||
|
||||
var priority = Settings.RecentTvPriority;
|
||||
|
||||
var response = _proxy.DownloadNzb(fileContents, filename, category, priority, Settings);
|
||||
var addpaused = Settings.AddPaused;
|
||||
|
||||
var response = _proxy.DownloadNzb(fileContents, filename, category, priority, addpaused, Settings);
|
||||
|
||||
if(response == null)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ namespace NzbDrone.Core.Download.Clients.Nzbget
|
|||
{
|
||||
public interface INzbgetProxy
|
||||
{
|
||||
string DownloadNzb(byte[] nzbData, string title, string category, int priority, NzbgetSettings settings);
|
||||
string DownloadNzb(byte[] nzbData, string title, string category, int priority, bool addpaused, NzbgetSettings settings);
|
||||
NzbgetGlobalStatus GetGlobalStatus(NzbgetSettings settings);
|
||||
List<NzbgetQueueItem> GetQueue(NzbgetSettings settings);
|
||||
List<NzbgetHistoryItem> GetHistory(NzbgetSettings settings);
|
||||
|
|
@ -45,12 +45,12 @@ private bool HasVersion(int minimumVersion, NzbgetSettings settings)
|
|||
return version >= minimumVersion;
|
||||
}
|
||||
|
||||
public string DownloadNzb(byte[] nzbData, string title, string category, int priority, NzbgetSettings settings)
|
||||
public string DownloadNzb(byte[] nzbData, string title, string category, int priority, bool addpaused, NzbgetSettings settings)
|
||||
{
|
||||
if (HasVersion(16, settings))
|
||||
{
|
||||
var droneId = Guid.NewGuid().ToString().Replace("-", "");
|
||||
var response = ProcessRequest<int>(settings, "append", title, nzbData, category, priority, false, false, string.Empty, 0, "all", new string[] { "drone", droneId });
|
||||
var response = ProcessRequest<int>(settings, "append", title, nzbData, category, priority, false, addpaused, string.Empty, 0, "all", new string[] { "drone", droneId });
|
||||
if (response <= 0)
|
||||
{
|
||||
return null;
|
||||
|
|
|
|||
|
|
@ -57,6 +57,9 @@ public NzbgetSettings()
|
|||
[FieldDefinition(7, Label = "Use SSL", Type = FieldType.Checkbox)]
|
||||
public bool UseSsl { get; set; }
|
||||
|
||||
[FieldDefinition(8, Label = "Add Paused", Type = FieldType.Checkbox, HelpText = "This option requires at least NzbGet version 16.0")]
|
||||
public bool AddPaused { get; set; }
|
||||
|
||||
public NzbDroneValidationResult Validate()
|
||||
{
|
||||
return new NzbDroneValidationResult(Validator.Validate(this));
|
||||
|
|
|
|||
|
|
@ -124,7 +124,7 @@ public IndexerPageableRequestChain GetSearchRequests(MovieSearchCriteria searchC
|
|||
{
|
||||
var pageableRequests = new IndexerPageableRequestChain();
|
||||
|
||||
if (false)
|
||||
if (SupportsMovieSearch)
|
||||
{
|
||||
pageableRequests.Add(GetPagedRequests(MaxPages, Settings.Categories, "movie",
|
||||
string.Format("&imdbid={0}", searchCriteria.Movie.ImdbId.Substring(2)))); //strip off the "tt" - VERY HACKY
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
using System.Collections.Generic;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Net;
|
||||
using System.Text.RegularExpressions;
|
||||
using NzbDrone.Common.Http;
|
||||
|
|
@ -36,12 +37,11 @@ public IList<ReleaseInfo> ParseResponse(IndexerResponse indexerResponse)
|
|||
torrentInfo.Size = (long)torrent.size*1000*1000;
|
||||
torrentInfo.DownloadUrl = torrent.download_url;
|
||||
torrentInfo.InfoUrl = torrent.details_url;
|
||||
torrentInfo.PublishDate = new System.DateTime();
|
||||
torrentInfo.PublishDate = torrent.publish_date.ToUniversalTime();
|
||||
torrentInfo.Seeders = torrent.seeders;
|
||||
torrentInfo.Peers = torrent.leechers + torrent.seeders;
|
||||
torrentInfo.Freeleech = torrent.freeleech;
|
||||
torrentInfo.PublishDate = torrent.publishdate.ToUniversalTime();
|
||||
|
||||
|
||||
results.Add(torrentInfo);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -21,7 +21,7 @@ public class Result
|
|||
public int size { get; set; }
|
||||
public int leechers { get; set; }
|
||||
public int seeders { get; set; }
|
||||
public DateTime publishdate { get; set; }
|
||||
public DateTime publish_date { get; set; }
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -39,7 +39,13 @@ protected override bool PreProcess(IndexerResponse indexerResponse)
|
|||
protected override ReleaseInfo ProcessItem(XElement item, ReleaseInfo releaseInfo)
|
||||
{
|
||||
var torrentInfo = base.ProcessItem(item, releaseInfo) as TorrentInfo;
|
||||
torrentInfo.ImdbId = int.Parse(GetImdbId(item).Substring(2));
|
||||
if (GetImdbId(item) != null)
|
||||
{
|
||||
if (torrentInfo != null)
|
||||
{
|
||||
torrentInfo.ImdbId = int.Parse(GetImdbId(item).Substring(2));
|
||||
}
|
||||
}
|
||||
return torrentInfo;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -64,9 +64,9 @@ public void Handle(ApplicationStartedEvent message)
|
|||
new ScheduledTask{ Interval = 0.25f, TypeName = typeof(CheckForFinishedDownloadCommand).FullName},
|
||||
new ScheduledTask{ Interval = 5, TypeName = typeof(MessagingCleanupCommand).FullName},
|
||||
new ScheduledTask{ Interval = 6*60, TypeName = typeof(ApplicationUpdateCommand).FullName},
|
||||
new ScheduledTask{ Interval = 3*60, TypeName = typeof(UpdateSceneMappingCommand).FullName},
|
||||
// new ScheduledTask{ Interval = 3*60, TypeName = typeof(UpdateSceneMappingCommand).FullName},
|
||||
new ScheduledTask{ Interval = 6*60, TypeName = typeof(CheckHealthCommand).FullName},
|
||||
new ScheduledTask{ Interval = 12*60, TypeName = typeof(RefreshSeriesCommand).FullName},
|
||||
new ScheduledTask{ Interval = 24*60, TypeName = typeof(RefreshMovieCommand).FullName},
|
||||
new ScheduledTask{ Interval = 24*60, TypeName = typeof(HousekeepingCommand).FullName},
|
||||
new ScheduledTask{ Interval = 7*24*60, TypeName = typeof(BackupCommand).FullName},
|
||||
|
||||
|
|
@ -80,6 +80,7 @@ public void Handle(ApplicationStartedEvent message)
|
|||
{
|
||||
Interval = _configService.DownloadedEpisodesScanInterval,
|
||||
TypeName = typeof(DownloadedEpisodesScanCommand).FullName
|
||||
//TypeName = typeof(DownloadedMovieScanCommand).FullName
|
||||
},
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,8 @@
|
|||
using NzbDrone.Common.Disk;
|
||||
using System;
|
||||
using NzbDrone.Common.Disk;
|
||||
using NzbDrone.Common.Http;
|
||||
using System.Drawing;
|
||||
using NLog;
|
||||
|
||||
namespace NzbDrone.Core.MediaCover
|
||||
{
|
||||
|
|
@ -12,11 +15,13 @@ public class CoverAlreadyExistsSpecification : ICoverExistsSpecification
|
|||
{
|
||||
private readonly IDiskProvider _diskProvider;
|
||||
private readonly IHttpClient _httpClient;
|
||||
private readonly Logger _logger;
|
||||
|
||||
public CoverAlreadyExistsSpecification(IDiskProvider diskProvider, IHttpClient httpClient)
|
||||
public CoverAlreadyExistsSpecification(IDiskProvider diskProvider, IHttpClient httpClient, Logger logger)
|
||||
{
|
||||
_diskProvider = diskProvider;
|
||||
_httpClient = httpClient;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
public bool AlreadyExists(string url, string path)
|
||||
|
|
@ -26,9 +31,31 @@ public bool AlreadyExists(string url, string path)
|
|||
return false;
|
||||
}
|
||||
|
||||
if (!IsValidGDIPlusImage(path))
|
||||
{
|
||||
_diskProvider.DeleteFile(path);
|
||||
return false;
|
||||
}
|
||||
|
||||
var headers = _httpClient.Head(new HttpRequest(url)).Headers;
|
||||
var fileSize = _diskProvider.GetFileSize(path);
|
||||
return fileSize == headers.ContentLength;
|
||||
}
|
||||
|
||||
private bool IsValidGDIPlusImage(string filename)
|
||||
{
|
||||
try
|
||||
{
|
||||
using (var bmp = new Bitmap(filename))
|
||||
{
|
||||
}
|
||||
return true;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.Debug(ex, "Corrupted image found at: {0}. Redownloading...", filename);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -114,7 +114,7 @@ private void EnsureCovers(Series series)
|
|||
}
|
||||
}
|
||||
|
||||
private void EnsureCovers(Movie movie)
|
||||
private void EnsureCovers(Movie movie, int retried = 0)
|
||||
{
|
||||
foreach (var cover in movie.Images)
|
||||
{
|
||||
|
|
@ -130,7 +130,25 @@ private void EnsureCovers(Movie movie)
|
|||
}
|
||||
catch (WebException e)
|
||||
{
|
||||
_logger.Warn(string.Format("Couldn't download media cover for {0}. {1}", movie, e.Message));
|
||||
if (e.Status == WebExceptionStatus.ProtocolError)
|
||||
{
|
||||
_logger.Warn(e, "Server returned different code than 200. The poster is probably not available yet.");
|
||||
return;
|
||||
}
|
||||
|
||||
_logger.Warn(e, string.Format("Couldn't download media cover for {0}. {1}", movie, e.Message));
|
||||
if (retried < 3)
|
||||
{
|
||||
retried = +1;
|
||||
_logger.Warn("Retrying for the {0}. time in ten seconds.", retried);
|
||||
System.Threading.Thread.Sleep(10*1000);
|
||||
EnsureCovers(movie, retried);
|
||||
}
|
||||
else
|
||||
{
|
||||
_logger.Warn(e, "Couldn't download media cover even after retrying five times :(.");
|
||||
}
|
||||
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -68,22 +68,22 @@ public List<ImportResult> Import(List<ImportDecision> decisions, bool newDownloa
|
|||
{
|
||||
//check if already imported
|
||||
if (importResults.Select(r => r.ImportDecision.LocalMovie.Movie)
|
||||
.Select(e => e.Id).Contains(localMovie.Movie.Id))
|
||||
.Select(m => m.Id).Contains(localMovie.Movie.Id))
|
||||
{
|
||||
importResults.Add(new ImportResult(importDecision, "Movie has already been imported"));
|
||||
continue;
|
||||
}
|
||||
|
||||
var episodeFile = new MovieFile();
|
||||
episodeFile.DateAdded = DateTime.UtcNow;
|
||||
episodeFile.MovieId = localMovie.Movie.Id;
|
||||
episodeFile.Path = localMovie.Path.CleanFilePath();
|
||||
episodeFile.Size = _diskProvider.GetFileSize(localMovie.Path);
|
||||
episodeFile.Quality = localMovie.Quality;
|
||||
episodeFile.MediaInfo = localMovie.MediaInfo;
|
||||
episodeFile.Movie = localMovie.Movie;
|
||||
episodeFile.ReleaseGroup = localMovie.ParsedMovieInfo.ReleaseGroup;
|
||||
episodeFile.Edition = localMovie.ParsedMovieInfo.Edition;
|
||||
var movieFile = new MovieFile();
|
||||
movieFile.DateAdded = DateTime.UtcNow;
|
||||
movieFile.MovieId = localMovie.Movie.Id;
|
||||
movieFile.Path = localMovie.Path.CleanFilePath();
|
||||
movieFile.Size = _diskProvider.GetFileSize(localMovie.Path);
|
||||
movieFile.Quality = localMovie.Quality;
|
||||
movieFile.MediaInfo = localMovie.MediaInfo;
|
||||
movieFile.Movie = localMovie.Movie;
|
||||
movieFile.ReleaseGroup = localMovie.ParsedMovieInfo.ReleaseGroup;
|
||||
movieFile.Edition = localMovie.ParsedMovieInfo.Edition;
|
||||
|
||||
bool copyOnly;
|
||||
switch (importMode)
|
||||
|
|
@ -102,17 +102,17 @@ public List<ImportResult> Import(List<ImportDecision> decisions, bool newDownloa
|
|||
|
||||
if (newDownload)
|
||||
{
|
||||
episodeFile.SceneName = GetSceneName(downloadClientItem, localMovie);
|
||||
movieFile.SceneName = GetSceneName(downloadClientItem, localMovie);
|
||||
|
||||
var moveResult = _episodeFileUpgrader.UpgradeMovieFile(episodeFile, localMovie, copyOnly);
|
||||
var moveResult = _episodeFileUpgrader.UpgradeMovieFile(movieFile, localMovie, copyOnly); //TODO: Check if this works
|
||||
oldFiles = moveResult.OldFiles;
|
||||
}
|
||||
else
|
||||
{
|
||||
episodeFile.RelativePath = localMovie.Movie.Path.GetRelativePath(episodeFile.Path);
|
||||
movieFile.RelativePath = localMovie.Movie.Path.GetRelativePath(movieFile.Path);
|
||||
}
|
||||
|
||||
_mediaFileService.Add(episodeFile);
|
||||
_mediaFileService.Add(movieFile);
|
||||
importResults.Add(new ImportResult(importDecision));
|
||||
|
||||
if (newDownload)
|
||||
|
|
@ -122,22 +122,22 @@ public List<ImportResult> Import(List<ImportDecision> decisions, bool newDownloa
|
|||
|
||||
if (downloadClientItem != null)
|
||||
{
|
||||
_eventAggregator.PublishEvent(new MovieImportedEvent(localMovie, episodeFile, newDownload, downloadClientItem.DownloadClient, downloadClientItem.DownloadId, downloadClientItem.IsReadOnly));
|
||||
_eventAggregator.PublishEvent(new MovieImportedEvent(localMovie, movieFile, newDownload, downloadClientItem.DownloadClient, downloadClientItem.DownloadId, downloadClientItem.IsReadOnly));
|
||||
}
|
||||
else
|
||||
{
|
||||
_eventAggregator.PublishEvent(new MovieImportedEvent(localMovie, episodeFile, newDownload));
|
||||
_eventAggregator.PublishEvent(new MovieImportedEvent(localMovie, movieFile, newDownload));
|
||||
}
|
||||
|
||||
if (newDownload)
|
||||
{
|
||||
_eventAggregator.PublishEvent(new MovieDownloadedEvent(localMovie, episodeFile, oldFiles));
|
||||
_eventAggregator.PublishEvent(new MovieDownloadedEvent(localMovie, movieFile, oldFiles));
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
_logger.Warn(e, "Couldn't import episode " + localMovie);
|
||||
importResults.Add(new ImportResult(importDecision, "Failed to import episode"));
|
||||
_logger.Warn(e, "Couldn't import movie " + localMovie);
|
||||
importResults.Add(new ImportResult(importDecision, "Failed to import movie"));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
public enum FileDateType
|
||||
{
|
||||
None = 0,
|
||||
LocalAirDate = 1,
|
||||
UtcAirDate = 2
|
||||
Cinemas = 1,
|
||||
Release = 2
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -111,11 +111,11 @@ public List<string> FilterExistingFiles(List<string> files, Series series)
|
|||
|
||||
public List<string> FilterExistingFiles(List<string> files, Movie movie)
|
||||
{
|
||||
var seriesFiles = GetFilesBySeries(movie.Id).Select(f => Path.Combine(movie.Path, f.RelativePath)).ToList();
|
||||
var movieFiles = GetFilesByMovie(movie.Id).Select(f => Path.Combine(movie.Path, f.RelativePath)).ToList();
|
||||
|
||||
if (!seriesFiles.Any()) return files;
|
||||
if (!movieFiles.Any()) return files;
|
||||
|
||||
return files.Except(seriesFiles, PathEqualityComparer.Instance).ToList();
|
||||
return files.Except(movieFiles, PathEqualityComparer.Instance).ToList();
|
||||
}
|
||||
|
||||
public EpisodeFile Get(int id)
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
using System;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using NLog;
|
||||
|
|
@ -18,14 +18,17 @@ public interface IMediaFileTableCleanupService
|
|||
public class MediaFileTableCleanupService : IMediaFileTableCleanupService
|
||||
{
|
||||
private readonly IMediaFileService _mediaFileService;
|
||||
private readonly IMovieService _movieService;
|
||||
private readonly IEpisodeService _episodeService;
|
||||
private readonly Logger _logger;
|
||||
|
||||
public MediaFileTableCleanupService(IMediaFileService mediaFileService,
|
||||
IMovieService movieService,
|
||||
IEpisodeService episodeService,
|
||||
Logger logger)
|
||||
{
|
||||
_mediaFileService = mediaFileService;
|
||||
_movieService = movieService;
|
||||
_episodeService = episodeService;
|
||||
_logger = logger;
|
||||
}
|
||||
|
|
@ -89,61 +92,39 @@ public void Clean(Series series, List<string> filesOnDisk)
|
|||
|
||||
public void Clean(Movie movie, List<string> filesOnDisk)
|
||||
{
|
||||
|
||||
//TODO: Update implementation for movies.
|
||||
var seriesFiles = _mediaFileService.GetFilesBySeries(movie.Id);
|
||||
var episodes = _episodeService.GetEpisodeBySeries(movie.Id);
|
||||
var movieFiles = _mediaFileService.GetFilesByMovie(movie.Id);
|
||||
|
||||
var filesOnDiskKeys = new HashSet<string>(filesOnDisk, PathEqualityComparer.Instance);
|
||||
|
||||
foreach (var seriesFile in seriesFiles)
|
||||
foreach(var movieFile in movieFiles)
|
||||
{
|
||||
var episodeFile = seriesFile;
|
||||
var episodeFilePath = Path.Combine(movie.Path, episodeFile.RelativePath);
|
||||
var movieFilePath = Path.Combine(movie.Path, movieFile.RelativePath);
|
||||
|
||||
try
|
||||
{
|
||||
if (!filesOnDiskKeys.Contains(episodeFilePath))
|
||||
if (!filesOnDiskKeys.Contains(movieFilePath))
|
||||
{
|
||||
_logger.Debug("File [{0}] no longer exists on disk, removing from db", episodeFilePath);
|
||||
_mediaFileService.Delete(seriesFile, DeleteMediaFileReason.MissingFromDisk);
|
||||
_logger.Debug("File [{0}] no longer exists on disk, removing from db", movieFilePath);
|
||||
_mediaFileService.Delete(movieFile, DeleteMediaFileReason.MissingFromDisk);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (episodes.None(e => e.EpisodeFileId == episodeFile.Id))
|
||||
{
|
||||
_logger.Debug("File [{0}] is not assigned to any episodes, removing from db", episodeFilePath);
|
||||
_mediaFileService.Delete(episodeFile, DeleteMediaFileReason.NoLinkedEpisodes);
|
||||
continue;
|
||||
}
|
||||
//var localMovie = _parsingService.GetLocalMovie(movieFile.Path, movie);
|
||||
|
||||
// var localEpsiode = _parsingService.GetLocalEpisode(episodeFile.Path, series);
|
||||
//
|
||||
// if (localEpsiode == null || episodes.Count != localEpsiode.Episodes.Count)
|
||||
// {
|
||||
// _logger.Debug("File [{0}] parsed episodes has changed, removing from db", episodeFile.Path);
|
||||
// _mediaFileService.Delete(episodeFile);
|
||||
// continue;
|
||||
// }
|
||||
//if (localMovie == null)
|
||||
//{
|
||||
// _logger.Debug("File [{0}] parsed episodes has changed, removing from db", localMovie.Path);
|
||||
// _mediaFileService.Delete(localMovie);
|
||||
// continue;
|
||||
//}
|
||||
}
|
||||
|
||||
catch (Exception ex)
|
||||
{
|
||||
var errorMessage = string.Format("Unable to cleanup EpisodeFile in DB: {0}", episodeFile.Id);
|
||||
var errorMessage = string.Format("Unable to cleanup MovieFile in DB: {0}", movieFile.Id);
|
||||
_logger.Error(ex, errorMessage);
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var e in episodes)
|
||||
{
|
||||
var episode = e;
|
||||
|
||||
if (episode.EpisodeFileId > 0 && seriesFiles.None(f => f.Id == episode.EpisodeFileId))
|
||||
{
|
||||
episode.EpisodeFileId = 0;
|
||||
_episodeService.UpdateEpisode(episode);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -48,7 +48,11 @@ public decimal FormattedAudioChannels
|
|||
return AudioChannelPositionsText.ContainsIgnoreCase("LFE") ? AudioChannels - 1 + 0.1m : AudioChannels;
|
||||
}
|
||||
|
||||
return AudioChannelPositions.Split('/').Sum(s => decimal.Parse(s, CultureInfo.InvariantCulture));
|
||||
decimal channels = 0;
|
||||
|
||||
decimal.TryParse(AudioChannelPositions.Split('/').First(), out channels);
|
||||
|
||||
return channels;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -49,7 +49,7 @@ private bool ChangeFileDate(EpisodeFile episodeFile, Series series, List<Episode
|
|||
|
||||
switch (_configService.FileDate)
|
||||
{
|
||||
case FileDateType.LocalAirDate:
|
||||
case FileDateType.Release:
|
||||
{
|
||||
var airDate = episodes.First().AirDate;
|
||||
var airTime = series.AirTime;
|
||||
|
|
@ -62,7 +62,7 @@ private bool ChangeFileDate(EpisodeFile episodeFile, Series series, List<Episode
|
|||
return ChangeFileDateToLocalAirDate(episodeFilePath, airDate, airTime);
|
||||
}
|
||||
|
||||
case FileDateType.UtcAirDate:
|
||||
case FileDateType.Cinemas:
|
||||
{
|
||||
var airDateUtc = episodes.First().AirDateUtc;
|
||||
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
using System;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
|
|
@ -20,7 +20,7 @@ public interface IUpdateMovieFileService
|
|||
}
|
||||
|
||||
public class UpdateMovieFileService : IUpdateMovieFileService,
|
||||
IHandle<SeriesScannedEvent>
|
||||
IHandle<MovieScannedEvent>
|
||||
{
|
||||
private readonly IDiskProvider _diskProvider;
|
||||
private readonly IConfigService _configService;
|
||||
|
|
@ -47,17 +47,67 @@ private bool ChangeFileDate(MovieFile movieFile, Movie movie)
|
|||
{
|
||||
var movieFilePath = Path.Combine(movie.Path, movieFile.RelativePath);
|
||||
|
||||
switch (_configService.FileDate)
|
||||
{
|
||||
case FileDateType.Release:
|
||||
{
|
||||
var airDate = movie.PhysicalRelease;
|
||||
|
||||
if (airDate == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return ChangeFileDate(movieFilePath, airDate.Value);
|
||||
}
|
||||
|
||||
case FileDateType.Cinemas:
|
||||
{
|
||||
var airDate = movie.InCinemas;
|
||||
|
||||
if (airDate == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return ChangeFileDate(movieFilePath, airDate.Value);
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public void Handle(SeriesScannedEvent message)
|
||||
private bool ChangeFileDate(string filePath, DateTime date)
|
||||
{
|
||||
DateTime oldDateTime = _diskProvider.FileGetLastWrite(filePath);
|
||||
|
||||
if (!DateTime.Equals(date, oldDateTime))
|
||||
{
|
||||
try
|
||||
{
|
||||
_diskProvider.FileSetLastWriteTime(filePath, date);
|
||||
_logger.Debug("Date of file [{0}] changed from '{1}' to '{2}'", filePath, oldDateTime, date);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.Warn(ex, "Unable to set date of file [" + filePath + "]");
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public void Handle(MovieScannedEvent message)
|
||||
{
|
||||
if (_configService.FileDate == FileDateType.None)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
/* var movies = _movieService.MoviesWithFiles(message.Series.Id);
|
||||
var movies = _movieService.MoviesWithFiles(message.Movie.Id);
|
||||
|
||||
var movieFiles = new List<MovieFile>();
|
||||
var updated = new List<MovieFile>();
|
||||
|
|
@ -69,7 +119,7 @@ public void Handle(SeriesScannedEvent message)
|
|||
|
||||
movieFiles.Add(movieFile);
|
||||
|
||||
if (ChangeFileDate(movieFile, message.Series, moviesInFile))
|
||||
if (ChangeFileDate(movieFile, message.Movie))
|
||||
{
|
||||
updated.Add(movieFile);
|
||||
}
|
||||
|
|
@ -77,13 +127,13 @@ public void Handle(SeriesScannedEvent message)
|
|||
|
||||
if (updated.Any())
|
||||
{
|
||||
_logger.ProgressDebug("Changed file date for {0} files of {1} in {2}", updated.Count, movieFiles.Count, message.Series.Title);
|
||||
_logger.ProgressDebug("Changed file date for {0} files of {1} in {2}", updated.Count, movieFiles.Count, message.Movie.Title);
|
||||
}
|
||||
|
||||
else
|
||||
{
|
||||
_logger.ProgressDebug("No file dates changed for {0}", message.Series.Title);
|
||||
}*/
|
||||
_logger.ProgressDebug("No file dates changed for {0}", message.Movie.Title);
|
||||
}
|
||||
}
|
||||
|
||||
private bool ChangeFileDateToLocalAirDate(string filePath, string fileDate, string fileTime)
|
||||
|
|
|
|||
|
|
@ -38,10 +38,13 @@ public UpgradeMediaFileService(IRecycleBinProvider recycleBinProvider,
|
|||
|
||||
public MovieFileMoveResult UpgradeMovieFile(MovieFile episodeFile, LocalMovie localEpisode, bool copyOnly = false)
|
||||
{
|
||||
_logger.Trace("Upgrading existing episode file.");
|
||||
var moveFileResult = new MovieFileMoveResult();
|
||||
localEpisode.Movie.MovieFile.LazyLoad();
|
||||
var existingFile = localEpisode.Movie.MovieFile;
|
||||
existingFile.LazyLoad();
|
||||
|
||||
if (existingFile.IsLoaded)
|
||||
if (existingFile.IsLoaded && existingFile.Value != null)
|
||||
{
|
||||
var file = existingFile.Value;
|
||||
var episodeFilePath = Path.Combine(localEpisode.Movie.Path, file.RelativePath);
|
||||
|
|
@ -55,6 +58,10 @@ public MovieFileMoveResult UpgradeMovieFile(MovieFile episodeFile, LocalMovie lo
|
|||
moveFileResult.OldFiles.Add(file);
|
||||
_mediaFileService.Delete(file, DeleteMediaFileReason.Upgrade);
|
||||
}
|
||||
else
|
||||
{
|
||||
_logger.Warn("The existing movie file was not lazy loaded.");
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -66,6 +66,7 @@ public class MovieResourceRoot
|
|||
public int vote_count { get; set; }
|
||||
public AlternativeTitles alternative_titles { get; set; }
|
||||
public ReleaseDatesResource release_dates { get; set; }
|
||||
public VideosResource videos { get; set; }
|
||||
}
|
||||
|
||||
public class ReleaseDatesResource
|
||||
|
|
@ -130,4 +131,21 @@ public class Title
|
|||
public string iso_3166_1 { get; set; }
|
||||
public string title { get; set; }
|
||||
}
|
||||
|
||||
public class VideosResource
|
||||
{
|
||||
public List<Video> results { get; set; }
|
||||
}
|
||||
|
||||
public class Video
|
||||
{
|
||||
public string id { get; set; }
|
||||
public string iso_639_1 { get; set; }
|
||||
public string iso_3166_1 { get; set; }
|
||||
public string key { get; set; }
|
||||
public string name { get; set; }
|
||||
public string site { get; set; }
|
||||
public string size { get; set; }
|
||||
public string type { get; set; }
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -73,7 +73,7 @@ public Movie GetMovieInfo(int TmdbId)
|
|||
.SetSegment("route", "movie")
|
||||
.SetSegment("id", TmdbId.ToString())
|
||||
.SetSegment("secondaryRoute", "")
|
||||
.AddQueryParam("append_to_response", "alternative_titles,release_dates")
|
||||
.AddQueryParam("append_to_response", "alternative_titles,release_dates,videos")
|
||||
.AddQueryParam("country", "US")
|
||||
.Build();
|
||||
|
||||
|
|
@ -89,9 +89,9 @@ public Movie GetMovieInfo(int TmdbId)
|
|||
movie.TmdbId = TmdbId;
|
||||
movie.ImdbId = resource.imdb_id;
|
||||
movie.Title = resource.title;
|
||||
movie.TitleSlug = ToUrlSlug(movie.Title);
|
||||
movie.CleanTitle = Parser.Parser.CleanSeriesTitle(movie.Title);
|
||||
movie.SortTitle = Parser.Parser.NormalizeTitle(movie.Title);
|
||||
movie.TitleSlug = ToUrlSlug(resource.title);
|
||||
movie.CleanTitle = Parser.Parser.CleanSeriesTitle(resource.title);
|
||||
movie.SortTitle = Parser.Parser.NormalizeTitle(resource.title);
|
||||
movie.Overview = resource.overview;
|
||||
movie.Website = resource.homepage;
|
||||
if (resource.release_date.IsNotNullOrWhiteSpace())
|
||||
|
|
@ -149,6 +149,29 @@ public Movie GetMovieInfo(int TmdbId)
|
|||
{
|
||||
movie.Status = MovieStatusType.Announced;
|
||||
}
|
||||
|
||||
if (resource.videos != null)
|
||||
{
|
||||
foreach (Video video in resource.videos.results)
|
||||
{
|
||||
if (video.type == "Trailer" && video.site == "YouTube")
|
||||
{
|
||||
if (video.key != null)
|
||||
{
|
||||
movie.YouTubeTrailerId = video.key;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (resource.production_companies != null)
|
||||
{
|
||||
if (resource.production_companies.Any())
|
||||
{
|
||||
movie.Studio = resource.production_companies[0].name;
|
||||
}
|
||||
}
|
||||
|
||||
return movie;
|
||||
}
|
||||
|
|
@ -186,6 +209,8 @@ public List<Movie> SearchForNewMovie(string title)
|
|||
{
|
||||
var lowerTitle = title.ToLower();
|
||||
|
||||
lowerTitle = lowerTitle.Replace(".", "");
|
||||
|
||||
var parserResult = Parser.Parser.ParseMovieTitle(title, true);
|
||||
|
||||
var yearTerm = "";
|
||||
|
|
@ -193,7 +218,7 @@ public List<Movie> SearchForNewMovie(string title)
|
|||
if (parserResult != null && parserResult.MovieTitle != title)
|
||||
{
|
||||
//Parser found something interesting!
|
||||
lowerTitle = parserResult.MovieTitle.ToLower();
|
||||
lowerTitle = parserResult.MovieTitle.ToLower().Replace(".", " "); //TODO Update so not every period gets replaced (e.g. R.I.P.D.)
|
||||
if (parserResult.Year > 1800)
|
||||
{
|
||||
yearTerm = parserResult.Year.ToString();
|
||||
|
|
@ -326,25 +351,19 @@ private Movie MapMovie(MovieResult result)
|
|||
{
|
||||
imdbMovie.SortTitle = Parser.Parser.NormalizeTitle(result.title);
|
||||
imdbMovie.Title = result.title;
|
||||
string titleSlug = ToUrlSlug(result.title);
|
||||
imdbMovie.TitleSlug = titleSlug.ToLower().Replace(" ", "-");
|
||||
imdbMovie.TitleSlug = ToUrlSlug(result.title);
|
||||
|
||||
if (result.release_date.IsNotNullOrWhiteSpace())
|
||||
{
|
||||
imdbMovie.Year = DateTime.Parse(result.release_date).Year;
|
||||
}
|
||||
//var slugResult = _movieService.FindByTitleSlug(imdbMovie.TitleSlug);
|
||||
//if (slugResult != null)
|
||||
//{
|
||||
// _logger.Debug("Movie with this title slug already exists. Adding year...");
|
||||
//}
|
||||
imdbMovie.TitleSlug += "-" + imdbMovie.Year.ToString();
|
||||
|
||||
imdbMovie.TitleSlug += "-" + imdbMovie.Year;
|
||||
|
||||
imdbMovie.Images = new List<MediaCover.MediaCover>();
|
||||
imdbMovie.Overview = result.overview;
|
||||
try
|
||||
{
|
||||
string url = result.poster_path;
|
||||
var imdbPoster = _configService.GetCoverForURL(result.poster_path, MediaCoverTypes.Poster);
|
||||
imdbMovie.Images.Add(imdbPoster);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
{
|
||||
public enum PushoverPriority
|
||||
{
|
||||
Silent = -1,
|
||||
Silent = -2,
|
||||
Quiet = -1,
|
||||
Normal = 0,
|
||||
High = 1,
|
||||
|
|
|
|||
|
|
@ -204,6 +204,10 @@
|
|||
<Compile Include="Datastore\Migration\002_remove_tvrage_imdb_unique_constraint.cs" />
|
||||
<Compile Include="Datastore\Migration\003_remove_clean_title_from_scene_mapping.cs" />
|
||||
<Compile Include="Datastore\Migration\004_updated_history.cs" />
|
||||
<Compile Include="Datastore\Migration\122_add_movieid_to_blacklist.cs" />
|
||||
<Compile Include="Datastore\Migration\121_update_filedate_config.cs" />
|
||||
<Compile Include="Datastore\Migration\120_add_studio_to_table.cs" />
|
||||
<Compile Include="Datastore\Migration\119_add_youtube_trailer_id_table .cs" />
|
||||
<Compile Include="Datastore\Migration\118_update_movie_slug.cs" />
|
||||
<Compile Include="Datastore\Migration\117_update_movie_file.cs" />
|
||||
<Compile Include="Datastore\Migration\116_update_movie_sorttitle_again.cs" />
|
||||
|
|
|
|||
|
|
@ -58,7 +58,7 @@ public class FileNameBuilder : IBuildFileNames
|
|||
public static readonly Regex SeriesTitleRegex = new Regex(@"(?<token>\{(?:Series)(?<separator>[- ._])(Clean)?Title\})",
|
||||
RegexOptions.Compiled | RegexOptions.IgnoreCase);
|
||||
|
||||
public static readonly Regex MovieTitleRegex = new Regex(@"(?<token>\{((?:(Movie|Original))(?<separator>[- ._])(Clean)?Title)\})",
|
||||
public static readonly Regex MovieTitleRegex = new Regex(@"(?<token>\{((?:(Movie|Original))(?<separator>[- ._])(Clean)?Title(The)?)\})",
|
||||
RegexOptions.Compiled | RegexOptions.IgnoreCase);
|
||||
|
||||
private static readonly Regex FileNameCleanupRegex = new Regex(@"([- ._])(\1)+", RegexOptions.Compiled);
|
||||
|
|
@ -161,6 +161,7 @@ public string BuildFileName(Movie movie, MovieFile movieFile, NamingConfig namin
|
|||
|
||||
AddMovieTokens(tokenHandlers, movie);
|
||||
AddReleaseDateTokens(tokenHandlers, movie.Year); //In case we want to separate the year
|
||||
AddImdbIdTokens(tokenHandlers, movie.ImdbId);
|
||||
AddQualityTokens(tokenHandlers, movie, movieFile);
|
||||
AddMediaInfoTokens(tokenHandlers, movieFile);
|
||||
AddMovieFileTokens(tokenHandlers, movieFile);
|
||||
|
|
@ -301,6 +302,7 @@ public string GetMovieFolder(Movie movie, NamingConfig namingConfig = null)
|
|||
|
||||
AddMovieTokens(tokenHandlers, movie);
|
||||
AddReleaseDateTokens(tokenHandlers, movie.Year);
|
||||
AddImdbIdTokens(tokenHandlers, movie.ImdbId);
|
||||
|
||||
return CleanFolderName(ReplaceTokens(namingConfig.MovieFolderFormat, tokenHandlers, namingConfig));
|
||||
}
|
||||
|
|
@ -314,11 +316,27 @@ public static string CleanTitle(string title)
|
|||
return title;
|
||||
}
|
||||
|
||||
public static string TitleThe(string title)
|
||||
{
|
||||
string[] prefixes = { "The ", "An ", "A " };
|
||||
foreach (string prefix in prefixes)
|
||||
{
|
||||
int prefix_length = prefix.Length;
|
||||
if (prefix.ToLower() == title.Substring(0, prefix_length).ToLower())
|
||||
{
|
||||
title = title.Substring(prefix_length) + ", " + prefix.Trim();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return title.Trim();
|
||||
}
|
||||
|
||||
public static string CleanFileName(string name, bool replace = true)
|
||||
{
|
||||
string result = name;
|
||||
string[] badCharacters = { "\\", "/", "<", ">", "?", "*", ":", "|", "\"" };
|
||||
string[] goodCharacters = { "+", "+", "", "", "!", "-", "-", "", "" };
|
||||
string[] goodCharacters = { "+", "+", "", "", "!", "-", "", "", "" };
|
||||
|
||||
for (int i = 0; i < badCharacters.Length; i++)
|
||||
{
|
||||
|
|
@ -470,6 +488,7 @@ private void AddMovieTokens(Dictionary<string, Func<TokenMatch, string>> tokenHa
|
|||
{
|
||||
tokenHandlers["{Movie Title}"] = m => movie.Title;
|
||||
tokenHandlers["{Movie CleanTitle}"] = m => CleanTitle(movie.Title);
|
||||
tokenHandlers["{Movie Title The}"] = m => TitleThe(movie.Title);
|
||||
}
|
||||
|
||||
private void AddReleaseDateTokens(Dictionary<string, Func<TokenMatch, string>> tokenHandlers, int releaseYear)
|
||||
|
|
@ -477,6 +496,11 @@ private void AddReleaseDateTokens(Dictionary<string, Func<TokenMatch, string>> t
|
|||
tokenHandlers["{Release Year}"] = m => string.Format("{0}", releaseYear.ToString()); //Do I need m.CustomFormat?
|
||||
}
|
||||
|
||||
private void AddImdbIdTokens(Dictionary<string, Func<TokenMatch, string>> tokenHandlers, string imdbId)
|
||||
{
|
||||
tokenHandlers["{IMDb Id}"] = m => $"{imdbId}";
|
||||
}
|
||||
|
||||
private void AddSeasonTokens(Dictionary<string, Func<TokenMatch, string>> tokenHandlers, int seasonNumber)
|
||||
{
|
||||
tokenHandlers["{Season}"] = m => seasonNumber.ToString(m.CustomFormat);
|
||||
|
|
@ -508,6 +532,7 @@ private void AddMovieFileTokens(Dictionary<string, Func<TokenMatch, string>> tok
|
|||
{
|
||||
tokenHandlers["{Original Title}"] = m => GetOriginalTitle(episodeFile);
|
||||
tokenHandlers["{Original Filename}"] = m => GetOriginalFileName(episodeFile);
|
||||
//tokenHandlers["{IMDb Id}"] = m =>
|
||||
tokenHandlers["{Release Group}"] = m => episodeFile.ReleaseGroup ?? m.DefaultValue("Sonarr");
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -46,8 +46,9 @@ public FileNameSampleService(IBuildFileNames buildFileNames)
|
|||
|
||||
_movie = new Movie
|
||||
{
|
||||
Title = "Movie Title",
|
||||
Year = 2010
|
||||
Title = "The Movie Title",
|
||||
Year = 2010,
|
||||
ImdbId = "tt0066921"
|
||||
};
|
||||
|
||||
_standardSeries = new Series
|
||||
|
|
|
|||
|
|
@ -268,7 +268,7 @@ public static class Parser
|
|||
|
||||
private static readonly Regex ReportImdbId = new Regex(@"(?<imdbid>tt\d{7})", RegexOptions.IgnoreCase | RegexOptions.Compiled);
|
||||
|
||||
private static readonly Regex SimpleTitleRegex = new Regex(@"(?:480[ip]|576[ip]|720[ip]|1080[ip]|[xh][\W_]?26[45]|DD\W?5\W1|[<>?*:|]|848x480|1280x720|1920x1080|(8|10)b(it)?)\s*",
|
||||
private static readonly Regex SimpleTitleRegex = new Regex(@"(?:480[ip]|576[ip]|720[ip]|1080[ip]|2160[ip]|[xh][\W_]?26[45]|DD\W?5\W1|[<>?*:|]|848x480|1280x720|1920x1080|(8|10)b(it)?)\s*",
|
||||
RegexOptions.IgnoreCase | RegexOptions.Compiled);
|
||||
|
||||
private static readonly Regex WebsitePrefixRegex = new Regex(@"^\[\s*[a-z]+(\.[a-z]+)+\s*\][- ]*",
|
||||
|
|
|
|||
|
|
@ -28,7 +28,7 @@ public class QualityParser
|
|||
)\b",
|
||||
RegexOptions.Compiled | RegexOptions.IgnoreCase | RegexOptions.IgnorePatternWhitespace);
|
||||
|
||||
private static readonly Regex HardcodedSubsRegex = new Regex(@"\b(?<hcsub>(\w+SUB))|(?<hc>(HC))\b", RegexOptions.Compiled | RegexOptions.IgnoreCase | RegexOptions.IgnorePatternWhitespace);
|
||||
private static readonly Regex HardcodedSubsRegex = new Regex(@"\b(?<hcsub>(\w+SUB)\b)|(?<hc>(HC))\b", RegexOptions.Compiled | RegexOptions.IgnoreCase | RegexOptions.IgnorePatternWhitespace);
|
||||
|
||||
private static readonly Regex RawHDRegex = new Regex(@"\b(?<rawhd>RawHD|1080i[-_. ]HDTV|Raw[-_. ]HD|MPEG[-_. ]?2)\b",
|
||||
RegexOptions.Compiled | RegexOptions.IgnoreCase);
|
||||
|
|
|
|||
|
|
@ -48,6 +48,8 @@ public Movie()
|
|||
public LazyLoaded<MovieFile> MovieFile { get; set; }
|
||||
public int MovieFileId { get; set; }
|
||||
public List<string> AlternativeTitles { get; set; }
|
||||
public string YouTubeTrailerId{ get; set; }
|
||||
public string Studio { get; set; }
|
||||
|
||||
public bool HasFile => MovieFileId > 0;
|
||||
|
||||
|
|
|
|||
|
|
@ -3,7 +3,9 @@
|
|||
using System.Collections.Generic;
|
||||
using NzbDrone.Core.Datastore;
|
||||
using NzbDrone.Core.Messaging.Events;
|
||||
|
||||
using NzbDrone.Core.Datastore.Extensions;
|
||||
using Marr.Data.QGen;
|
||||
using NzbDrone.Core.MediaFiles;
|
||||
|
||||
namespace NzbDrone.Core.Tv
|
||||
{
|
||||
|
|
@ -14,6 +16,9 @@ public interface IMovieRepository : IBasicRepository<Movie>
|
|||
Movie FindByTitle(string cleanTitle, int year);
|
||||
Movie FindByImdbId(string imdbid);
|
||||
Movie FindByTitleSlug(string slug);
|
||||
List<Movie> MoviesBetweenDates(DateTime start, DateTime end, bool includeUnmonitored);
|
||||
List<Movie> MoviesWithFiles(int movieId);
|
||||
PagingSpec<Movie> MoviesWithoutFiles(PagingSpec<Movie> pagingSpec);
|
||||
List<Movie> GetMoviesByFileId(int fileId);
|
||||
void SetFileId(int fileId, int movieId);
|
||||
}
|
||||
|
|
@ -119,5 +124,42 @@ public Movie FindByTitleSlug(string slug)
|
|||
{
|
||||
return Query.Where(m => m.TitleSlug == slug).FirstOrDefault();
|
||||
}
|
||||
|
||||
public List<Movie> MoviesBetweenDates(DateTime start, DateTime end, bool includeUnmonitored)
|
||||
{
|
||||
var query = Query.Where(m => m.InCinemas >= start && m.InCinemas <= end).OrWhere(m => m.PhysicalRelease >= start && m.PhysicalRelease <= end);
|
||||
|
||||
if (!includeUnmonitored)
|
||||
{
|
||||
query.AndWhere(e => e.Monitored);
|
||||
}
|
||||
|
||||
return query.ToList();
|
||||
}
|
||||
|
||||
public List<Movie> MoviesWithFiles(int movieId)
|
||||
{
|
||||
return Query.Join<Movie, MovieFile>(JoinType.Inner, m => m.MovieFile, (m, mf) => m.MovieFileId == mf.Id)
|
||||
.Where(m => m.Id == movieId);
|
||||
}
|
||||
|
||||
public PagingSpec<Movie> MoviesWithoutFiles(PagingSpec<Movie> pagingSpec)
|
||||
{
|
||||
|
||||
pagingSpec.TotalRecords = GetMoviesWithoutFilesQuery(pagingSpec).GetRowCount();
|
||||
pagingSpec.Records = GetMoviesWithoutFilesQuery(pagingSpec).ToList();
|
||||
|
||||
return pagingSpec;
|
||||
}
|
||||
|
||||
public SortBuilder<Movie> GetMoviesWithoutFilesQuery(PagingSpec<Movie> pagingSpec)
|
||||
{
|
||||
return Query.Where(pagingSpec.FilterExpression)
|
||||
.AndWhere(m => m.MovieFileId == 0)
|
||||
.AndWhere(m => m.Status == MovieStatusType.Released)
|
||||
.OrderBy(pagingSpec.OrderByClause(), pagingSpec.ToSortDirection())
|
||||
.Skip(pagingSpec.PagingOffset())
|
||||
.Take(pagingSpec.PageSize);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -12,6 +12,7 @@
|
|||
using NzbDrone.Core.Tv.Events;
|
||||
using NzbDrone.Core.MediaFiles;
|
||||
using NzbDrone.Core.MediaFiles.Events;
|
||||
using NzbDrone.Core.Datastore;
|
||||
|
||||
namespace NzbDrone.Core.Tv
|
||||
{
|
||||
|
|
@ -26,12 +27,15 @@ public interface IMovieService
|
|||
Movie FindByTitleInexact(string title);
|
||||
Movie FindByTitleSlug(string slug);
|
||||
Movie GetMovieByFileId(int fileId);
|
||||
List<Movie> GetMoviesBetweenDates(DateTime start, DateTime end, bool includeUnmonitored);
|
||||
PagingSpec<Movie> MoviesWithoutFiles(PagingSpec<Movie> pagingSpec);
|
||||
void DeleteMovie(int movieId, bool deleteFiles);
|
||||
List<Movie> GetAllMovies();
|
||||
Movie UpdateMovie(Movie movie);
|
||||
List<Movie> UpdateMovie(List<Movie> movie);
|
||||
bool MoviePathExists(string folder);
|
||||
void RemoveAddOptions(Movie movie);
|
||||
List<Movie> MoviesWithFiles(int movieId);
|
||||
}
|
||||
|
||||
public class MovieService : IMovieService, IHandle<MovieFileAddedEvent>,
|
||||
|
|
@ -224,5 +228,24 @@ public Movie FindByTitleSlug(string slug)
|
|||
{
|
||||
return _movieRepository.FindByTitleSlug(slug);
|
||||
}
|
||||
|
||||
public List<Movie> GetMoviesBetweenDates(DateTime start, DateTime end, bool includeUnmonitored)
|
||||
{
|
||||
var episodes = _movieRepository.MoviesBetweenDates(start.ToUniversalTime(), end.ToUniversalTime(), includeUnmonitored);
|
||||
|
||||
return episodes;
|
||||
}
|
||||
|
||||
public List<Movie> MoviesWithFiles(int movieId)
|
||||
{
|
||||
return _movieRepository.MoviesWithFiles(movieId);
|
||||
}
|
||||
|
||||
public PagingSpec<Movie> MoviesWithoutFiles(PagingSpec<Movie> pagingSpec)
|
||||
{
|
||||
var movieResult = _movieRepository.MoviesWithoutFiles(pagingSpec);
|
||||
|
||||
return movieResult;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -84,6 +84,8 @@ private void RefreshMovieInfo(Movie movie)
|
|||
movie.AlternativeTitles = movieInfo.AlternativeTitles;
|
||||
movie.Year = movieInfo.Year;
|
||||
movie.PhysicalRelease = movieInfo.PhysicalRelease;
|
||||
movie.YouTubeTrailerId = movieInfo.YouTubeTrailerId;
|
||||
movie.Studio = movieInfo.Studio;
|
||||
|
||||
try
|
||||
{
|
||||
|
|
|
|||
|
|
@ -26,6 +26,12 @@ var QueueCollection = PageableCollection.extend({
|
|||
});
|
||||
},
|
||||
|
||||
findMovie : function(movieId) {
|
||||
return _.find(this.fullCollection.models, function(queueModel) {
|
||||
return queueModel.get('movie').id === movieId;
|
||||
});
|
||||
},
|
||||
|
||||
sortMappings : {
|
||||
series : {
|
||||
sortValue : function(model, attr) {
|
||||
|
|
|
|||
|
|
@ -17,7 +17,8 @@ module.exports = Marionette.Layout.extend({
|
|||
|
||||
events : {
|
||||
'click .x-import' : '_importMovies',
|
||||
'click .x-add-new' : '_addMovies'
|
||||
'click .x-add-new' : '_addMovies',
|
||||
'click .x-show-existing' : '_toggleExisting'
|
||||
},
|
||||
|
||||
attributes : {
|
||||
|
|
@ -31,13 +32,20 @@ module.exports = Marionette.Layout.extend({
|
|||
});
|
||||
},
|
||||
|
||||
_toggleExisting : function(e) {
|
||||
var showExisting = e.target.checked;
|
||||
|
||||
vent.trigger(vent.Commands.ShowExistingCommand, {
|
||||
showExisting: showExisting
|
||||
});
|
||||
},
|
||||
|
||||
onShow : function() {
|
||||
this.workspace.show(new AddMoviesView());
|
||||
},
|
||||
|
||||
_folderSelected : function(options) {
|
||||
vent.trigger(vent.Commands.CloseModalCommand);
|
||||
//TODO: Fix this shit.
|
||||
this.workspace.show(new ExistingMoviesCollectionView({ model : options.model }));
|
||||
},
|
||||
|
||||
|
|
|
|||
|
|
@ -9,6 +9,33 @@
|
|||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<div class="form-horizontal" style="margin-top: 15px;">
|
||||
<div class="form-group" style="margin-bottom: 0px;">
|
||||
<label class="col-sm-3 control-label">Display Existing Movies</label>
|
||||
|
||||
<div class="col-sm-8">
|
||||
<div class="input-group">
|
||||
<label class="checkbox toggle well">
|
||||
<input class="x-show-existing" type="checkbox" checked="checked" name="showExisting"/>
|
||||
<p>
|
||||
<span>Yes</span>
|
||||
<span>No</span>
|
||||
</p>
|
||||
|
||||
<div class="btn btn-primary slide-button"/>
|
||||
</label>
|
||||
|
||||
<span class="help-inline-checkbox">
|
||||
<i class="icon-sonarr-form-info" title="Should Radarr display movies already in your collection?"/>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<div id="add-movies-workspace"></div>
|
||||
|
|
|
|||
|
|
@ -9,177 +9,178 @@ var ErrorView = require('./ErrorView');
|
|||
var LoadingView = require('../Shared/LoadingView');
|
||||
|
||||
module.exports = Marionette.Layout.extend({
|
||||
template : 'AddMovies/AddMoviesViewTemplate',
|
||||
template : 'AddMovies/AddMoviesViewTemplate',
|
||||
|
||||
regions : {
|
||||
searchResult : '#search-result'
|
||||
},
|
||||
regions : {
|
||||
searchResult : '#search-result'
|
||||
},
|
||||
|
||||
ui : {
|
||||
moviesSearch : '.x-movies-search',
|
||||
searchBar : '.x-search-bar',
|
||||
loadMore : '.x-load-more'
|
||||
},
|
||||
ui : {
|
||||
moviesSearch : '.x-movies-search',
|
||||
searchBar : '.x-search-bar',
|
||||
loadMore : '.x-load-more'
|
||||
},
|
||||
|
||||
events : {
|
||||
'click .x-load-more' : '_onLoadMore'
|
||||
},
|
||||
events : {
|
||||
'click .x-load-more' : '_onLoadMore'
|
||||
},
|
||||
|
||||
initialize : function(options) {
|
||||
console.log(options);
|
||||
this.isExisting = options.isExisting;
|
||||
this.collection = new AddMoviesCollection();
|
||||
initialize : function(options) {
|
||||
console.log(options);
|
||||
|
||||
if (this.isExisting) {
|
||||
this.collection.unmappedFolderModel = this.model;
|
||||
}
|
||||
this.isExisting = options.isExisting;
|
||||
this.collection = new AddMoviesCollection();
|
||||
|
||||
if (this.isExisting) {
|
||||
this.className = 'existing-movies';
|
||||
} else {
|
||||
this.className = 'new-movies';
|
||||
}
|
||||
if (this.isExisting) {
|
||||
this.collection.unmappedFolderModel = this.model;
|
||||
}
|
||||
|
||||
this.listenTo(vent, vent.Events.MoviesAdded, this._onMoviesAdded);
|
||||
this.listenTo(this.collection, 'sync', this._showResults);
|
||||
if (this.isExisting) {
|
||||
this.className = 'existing-movies';
|
||||
} else {
|
||||
this.className = 'new-movies';
|
||||
}
|
||||
|
||||
this.resultCollectionView = new SearchResultCollectionView({
|
||||
collection : this.collection,
|
||||
isExisting : this.isExisting
|
||||
});
|
||||
this.listenTo(vent, vent.Events.MoviesAdded, this._onMoviesAdded);
|
||||
this.listenTo(this.collection, 'sync', this._showResults);
|
||||
|
||||
this.throttledSearch = _.debounce(this.search, 1000, { trailing : true }).bind(this);
|
||||
},
|
||||
this.resultCollectionView = new SearchResultCollectionView({
|
||||
collection : this.collection,
|
||||
isExisting : this.isExisting
|
||||
});
|
||||
|
||||
onRender : function() {
|
||||
var self = this;
|
||||
this.throttledSearch = _.debounce(this.search, 1000, { trailing : true }).bind(this);
|
||||
},
|
||||
|
||||
this.$el.addClass(this.className);
|
||||
onRender : function() {
|
||||
var self = this;
|
||||
|
||||
this.ui.moviesSearch.keyup(function(e) {
|
||||
this.$el.addClass(this.className);
|
||||
|
||||
if (_.contains([
|
||||
9,
|
||||
16,
|
||||
17,
|
||||
18,
|
||||
19,
|
||||
20,
|
||||
33,
|
||||
34,
|
||||
35,
|
||||
36,
|
||||
37,
|
||||
38,
|
||||
39,
|
||||
40,
|
||||
91,
|
||||
92,
|
||||
93
|
||||
], e.keyCode)) {
|
||||
return;
|
||||
}
|
||||
this.ui.moviesSearch.keyup(function(e) {
|
||||
|
||||
self._abortExistingSearch();
|
||||
self.throttledSearch({
|
||||
term : self.ui.moviesSearch.val()
|
||||
});
|
||||
});
|
||||
if (_.contains([
|
||||
9,
|
||||
16,
|
||||
17,
|
||||
18,
|
||||
19,
|
||||
20,
|
||||
33,
|
||||
34,
|
||||
35,
|
||||
36,
|
||||
37,
|
||||
38,
|
||||
39,
|
||||
40,
|
||||
91,
|
||||
92,
|
||||
93
|
||||
], e.keyCode)) {
|
||||
return;
|
||||
}
|
||||
|
||||
this._clearResults();
|
||||
self._abortExistingSearch();
|
||||
self.throttledSearch({
|
||||
term : self.ui.moviesSearch.val()
|
||||
});
|
||||
});
|
||||
|
||||
if (this.isExisting) {
|
||||
this.ui.searchBar.hide();
|
||||
}
|
||||
},
|
||||
this._clearResults();
|
||||
|
||||
onShow : function() {
|
||||
this.ui.moviesSearch.focus();
|
||||
},
|
||||
if (this.isExisting) {
|
||||
this.ui.searchBar.hide();
|
||||
}
|
||||
},
|
||||
|
||||
search : function(options) {
|
||||
var self = this;
|
||||
onShow : function() {
|
||||
this.ui.moviesSearch.focus();
|
||||
},
|
||||
|
||||
this.collection.reset();
|
||||
search : function(options) {
|
||||
var self = this;
|
||||
|
||||
if (!options.term || options.term === this.collection.term) {
|
||||
return Marionette.$.Deferred().resolve();
|
||||
}
|
||||
this.collection.reset();
|
||||
|
||||
this.searchResult.show(new LoadingView());
|
||||
this.collection.term = options.term;
|
||||
this.currentSearchPromise = this.collection.fetch({
|
||||
data : { term : options.term }
|
||||
});
|
||||
if (!options.term || options.term === this.collection.term) {
|
||||
return Marionette.$.Deferred().resolve();
|
||||
}
|
||||
|
||||
this.currentSearchPromise.fail(function() {
|
||||
self._showError();
|
||||
});
|
||||
this.searchResult.show(new LoadingView());
|
||||
this.collection.term = options.term;
|
||||
this.currentSearchPromise = this.collection.fetch({
|
||||
data : { term : options.term }
|
||||
});
|
||||
|
||||
return this.currentSearchPromise;
|
||||
},
|
||||
this.currentSearchPromise.fail(function() {
|
||||
self._showError();
|
||||
});
|
||||
|
||||
_onMoviesAdded : function(options) {
|
||||
if (this.isExisting && options.movie.get('path') === this.model.get('folder').path) {
|
||||
this.close();
|
||||
}
|
||||
return this.currentSearchPromise;
|
||||
},
|
||||
|
||||
else if (!this.isExisting) {
|
||||
this.resultCollectionView.setExisting(options.movie.get('tmdbId'));
|
||||
/*this.collection.term = '';
|
||||
this.collection.reset();
|
||||
this._clearResults();
|
||||
this.ui.moviesSearch.val('');
|
||||
this.ui.moviesSearch.focus();*/ //TODO: Maybe add option wheter to clear search result.
|
||||
}
|
||||
},
|
||||
_onMoviesAdded : function(options) {
|
||||
if (this.isExisting && options.movie.get('path') === this.model.get('folder').path) {
|
||||
this.close();
|
||||
}
|
||||
|
||||
_onLoadMore : function() {
|
||||
var showingAll = this.resultCollectionView.showMore();
|
||||
this.ui.searchBar.show();
|
||||
else if (!this.isExisting) {
|
||||
this.resultCollectionView.setExisting(options.movie.get('tmdbId'));
|
||||
/*this.collection.term = '';
|
||||
this.collection.reset();
|
||||
this._clearResults();
|
||||
this.ui.moviesSearch.val('');
|
||||
this.ui.moviesSearch.focus();*/ //TODO: Maybe add option wheter to clear search result.
|
||||
}
|
||||
},
|
||||
|
||||
if (showingAll) {
|
||||
this.ui.loadMore.hide();
|
||||
}
|
||||
},
|
||||
_onLoadMore : function() {
|
||||
var showingAll = this.resultCollectionView.showMore();
|
||||
this.ui.searchBar.show();
|
||||
|
||||
_clearResults : function() {
|
||||
if (showingAll) {
|
||||
this.ui.loadMore.hide();
|
||||
}
|
||||
},
|
||||
|
||||
if (!this.isExisting) {
|
||||
this.searchResult.show(new EmptyView());
|
||||
} else {
|
||||
this.searchResult.close();
|
||||
}
|
||||
},
|
||||
_clearResults : function() {
|
||||
|
||||
_showResults : function() {
|
||||
if (!this.isClosed) {
|
||||
if (this.collection.length === 0) {
|
||||
this.ui.searchBar.show();
|
||||
this.searchResult.show(new NotFoundView({ term : this.collection.term }));
|
||||
} else {
|
||||
this.searchResult.show(this.resultCollectionView);
|
||||
if (!this.showingAll && this.isExisting) {
|
||||
this.ui.loadMore.show();
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
if (!this.isExisting) {
|
||||
this.searchResult.show(new EmptyView());
|
||||
} else {
|
||||
this.searchResult.close();
|
||||
}
|
||||
},
|
||||
|
||||
_abortExistingSearch : function() {
|
||||
if (this.currentSearchPromise && this.currentSearchPromise.readyState > 0 && this.currentSearchPromise.readyState < 4) {
|
||||
console.log('aborting previous pending search request.');
|
||||
this.currentSearchPromise.abort();
|
||||
} else {
|
||||
this._clearResults();
|
||||
}
|
||||
},
|
||||
_showResults : function() {
|
||||
if (!this.isClosed) {
|
||||
if (this.collection.length === 0) {
|
||||
this.ui.searchBar.show();
|
||||
this.searchResult.show(new NotFoundView({ term : this.collection.term }));
|
||||
} else {
|
||||
this.searchResult.show(this.resultCollectionView);
|
||||
if (!this.showingAll) {
|
||||
this.ui.loadMore.show();
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
_showError : function() {
|
||||
if (!this.isClosed) {
|
||||
this.ui.searchBar.show();
|
||||
this.searchResult.show(new ErrorView({ term : this.collection.term }));
|
||||
this.collection.term = '';
|
||||
}
|
||||
}
|
||||
_abortExistingSearch : function() {
|
||||
if (this.currentSearchPromise && this.currentSearchPromise.readyState > 0 && this.currentSearchPromise.readyState < 4) {
|
||||
console.log('aborting previous pending search request.');
|
||||
this.currentSearchPromise.abort();
|
||||
} else {
|
||||
this._clearResults();
|
||||
}
|
||||
},
|
||||
|
||||
_showError : function() {
|
||||
if (!this.isClosed) {
|
||||
this.ui.searchBar.show();
|
||||
this.searchResult.show(new ErrorView({ term : this.collection.term }));
|
||||
this.collection.term = '';
|
||||
}
|
||||
}
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
var Marionette = require('marionette');
|
||||
var AddMoviesView = require('../AddMoviesView');
|
||||
var UnmappedFolderCollection = require('./UnmappedFolderCollection');
|
||||
var vent = require('vent');
|
||||
|
||||
module.exports = Marionette.CompositeView.extend({
|
||||
itemView : AddMoviesView,
|
||||
|
|
|
|||
|
|
@ -48,7 +48,7 @@ var Layout = Marionette.Layout.extend({
|
|||
var self = this;
|
||||
|
||||
var newDir = new RootFolderModel({
|
||||
Path : this.ui.pathInput.val()
|
||||
Path : this.ui.pathInput.val(),
|
||||
});
|
||||
|
||||
this.bindToModelValidation(newDir);
|
||||
|
|
|
|||
|
|
@ -31,6 +31,8 @@
|
|||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
|
||||
|
||||
<button class="btn" data-dismiss="modal">Close</button>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -1,41 +1,65 @@
|
|||
var Marionette = require('marionette');
|
||||
var SearchResultView = require('./SearchResultView');
|
||||
var MoviesCollection = require('../Movies/MoviesCollection');
|
||||
var vent = require('vent');
|
||||
|
||||
module.exports = Marionette.CollectionView.extend({
|
||||
itemView : SearchResultView,
|
||||
itemView : SearchResultView,
|
||||
|
||||
initialize : function(options) {
|
||||
this.isExisting = options.isExisting;
|
||||
this.showing = 1;
|
||||
},
|
||||
initialize : function(options) {
|
||||
this.showExisting = true;
|
||||
this.isExisting = options.isExisting;
|
||||
this.showing = 5;
|
||||
if (this.isExisting) {
|
||||
this.showing = 1;
|
||||
}
|
||||
vent.on(vent.Commands.ShowExistingCommand, this._onExistingToggle.bind(this));
|
||||
},
|
||||
|
||||
showAll : function() {
|
||||
this.showingAll = true;
|
||||
this.render();
|
||||
},
|
||||
_onExistingToggle : function(data) {
|
||||
this.showExisting = data.showExisting;
|
||||
|
||||
showMore : function() {
|
||||
this.showing += 5;
|
||||
this.render();
|
||||
this.render();
|
||||
},
|
||||
|
||||
return this.showing >= this.collection.length;
|
||||
},
|
||||
showAll : function() {
|
||||
this.showingAll = true;
|
||||
this.render();
|
||||
},
|
||||
|
||||
setExisting : function(tmdbid) {
|
||||
var movies = this.collection.where({ tmdbId : tmdbid });
|
||||
console.warn(movies);
|
||||
//debugger;
|
||||
if (movies.length > 0) {
|
||||
this.children.findByModel(movies[0])._configureTemplateHelpers();
|
||||
//this.children.findByModel(movies[0])._configureTemplateHelpers();
|
||||
this.children.findByModel(movies[0]).render();
|
||||
//this.templateHelpers.existing = existingMovies[0].toJSON();
|
||||
}
|
||||
},
|
||||
showMore : function() {
|
||||
this.showing += 5;
|
||||
this.render();
|
||||
|
||||
appendHtml : function(collectionView, itemView, index) {
|
||||
if (!this.isExisting || index < this.showing || index === 0) {
|
||||
collectionView.$el.append(itemView.el);
|
||||
}
|
||||
}
|
||||
return this.showing >= this.collection.length;
|
||||
},
|
||||
|
||||
setExisting : function(tmdbid) {
|
||||
var movies = this.collection.where({ tmdbId : tmdbid });
|
||||
console.warn(movies);
|
||||
//debugger;
|
||||
if (movies.length > 0) {
|
||||
this.children.findByModel(movies[0])._configureTemplateHelpers();
|
||||
//this.children.findByModel(movies[0])._configureTemplateHelpers();
|
||||
this.children.findByModel(movies[0]).render();
|
||||
//this.templateHelpers.existing = existingMovies[0].toJSON();
|
||||
}
|
||||
},
|
||||
|
||||
appendHtml : function(collectionView, itemView, index) {
|
||||
var tmdbId = itemView.model.get('tmdbId');
|
||||
var existingMovies = MoviesCollection.where({ tmdbId: tmdbId });
|
||||
if(existingMovies.length > 0) {
|
||||
if(this.showExisting) {
|
||||
if (index < this.showing || index === 0) {
|
||||
collectionView.$el.append(itemView.el);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (index < this.showing || index === 0) {
|
||||
collectionView.$el.append(itemView.el);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
});
|
||||
|
|
|
|||
|
|
@ -43,7 +43,7 @@ var view = Marionette.ItemView.extend({
|
|||
throw 'model is required';
|
||||
}
|
||||
|
||||
console.log(this.route);
|
||||
//console.log(this.route);
|
||||
|
||||
this.templateHelpers = {};
|
||||
this._configureTemplateHelpers();
|
||||
|
|
@ -92,14 +92,12 @@ var view = Marionette.ItemView.extend({
|
|||
|
||||
_configureTemplateHelpers : function() {
|
||||
var existingMovies = MoviesCollection.where({ tmdbId : this.model.get('tmdbId') });
|
||||
console.log(existingMovies);
|
||||
if (existingMovies.length > 0) {
|
||||
this.templateHelpers.existing = existingMovies[0].toJSON();
|
||||
}
|
||||
|
||||
this.templateHelpers.profiles = Profiles.toJSON();
|
||||
console.log(this.model);
|
||||
console.log(this.templateHelpers.existing);
|
||||
//console.log(this.templateHelpers.isExisting);
|
||||
if (!this.model.get('isExisting')) {
|
||||
this.templateHelpers.rootFolders = RootFolders.toJSON();
|
||||
}
|
||||
|
|
@ -185,8 +183,8 @@ var view = Marionette.ItemView.extend({
|
|||
var self = this;
|
||||
var promise = this.model.save();
|
||||
|
||||
console.log(this.model.save);
|
||||
console.log(promise);
|
||||
//console.log(this.model.save);
|
||||
//console.log(promise);
|
||||
|
||||
if (searchForMovie) {
|
||||
this.ui.addSearchButton.spinForPromise(promise);
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
var Backbone = require('backbone');
|
||||
var EpisodeModel = require('../Series/EpisodeModel');
|
||||
var EpisodeModel = require('../Movies/MovieModel');
|
||||
|
||||
module.exports = Backbone.Collection.extend({
|
||||
url : window.NzbDrone.ApiRoot + '/calendar',
|
||||
|
|
@ -7,8 +7,8 @@ module.exports = Backbone.Collection.extend({
|
|||
tableName : 'calendar',
|
||||
|
||||
comparator : function(model) {
|
||||
var date = new Date(model.get('airDateUtc'));
|
||||
var date = new Date(model.get('inCinemas'));
|
||||
var time = date.getTime();
|
||||
return time;
|
||||
}
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,75 +1,57 @@
|
|||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<button type="button" class="close" data-dismiss="modal" aria-hidden="true">×</button>
|
||||
<h3>Radarr Calendar feed</h3>
|
||||
</div>
|
||||
<div class="modal-body edit-series-modal">
|
||||
<div class="form-horizontal">
|
||||
<div class="form-group">
|
||||
<label class="col-sm-3 control-label">Include Unmonitored</label>
|
||||
<div class="modal-header">
|
||||
<button type="button" class="close" data-dismiss="modal" aria-hidden="true">×</button>
|
||||
<h3>Radarr Calendar feed</h3>
|
||||
</div>
|
||||
<div class="modal-body edit-series-modal">
|
||||
<div class="form-horizontal">
|
||||
<div class="form-group">
|
||||
<label class="col-sm-3 control-label">Include Unmonitored</label>
|
||||
|
||||
<div class="col-sm-4">
|
||||
<div class="input-group">
|
||||
<label class="checkbox toggle well">
|
||||
<input type="checkbox" name="includeUnmonitored" class="form-control x-includeUnmonitored"/>
|
||||
<div class="col-sm-4">
|
||||
<div class="input-group">
|
||||
<label class="checkbox toggle well">
|
||||
<input type="checkbox" name="includeUnmonitored" class="form-control x-includeUnmonitored"/>
|
||||
|
||||
<p>
|
||||
<span>Yes</span>
|
||||
<span>No</span>
|
||||
</p>
|
||||
<p>
|
||||
<span>Yes</span>
|
||||
<span>No</span>
|
||||
</p>
|
||||
|
||||
<div class="btn btn-primary slide-button"/>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="col-sm-3 control-label">Season Premiers Only</label>
|
||||
<div class="btn btn-primary slide-button"/>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="col-sm-3 control-label">Tags</label>
|
||||
|
||||
<div class="col-sm-4">
|
||||
<div class="input-group">
|
||||
<label class="checkbox toggle well">
|
||||
<input type="checkbox" name="premiersOnly" class="form-control x-premiersOnly"/>
|
||||
<div class="col-sm-1 col-sm-push-5 help-inline">
|
||||
<i class="icon-sonarr-form-info" title="One or more tags only show matching series" />
|
||||
</div>
|
||||
|
||||
<p>
|
||||
<span>Yes</span>
|
||||
<span>No</span>
|
||||
</p>
|
||||
|
||||
<div class="btn btn-primary slide-button"/>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="col-sm-3 control-label">Tags</label>
|
||||
|
||||
<div class="col-sm-1 col-sm-push-5 help-inline">
|
||||
<i class="icon-sonarr-form-info" title="One or more tags only show matching series" />
|
||||
</div>
|
||||
|
||||
<div class="col-sm-5 col-sm-pull-1">
|
||||
<input type="text" class="form-control x-tags">
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="col-sm-3 control-label">iCal feed</label>
|
||||
<div class="col-sm-1 col-sm-push-8 help-inline">
|
||||
<i class="icon-sonarr-form-info" title="Copy this url into your clients subscription form or use the subscribe button if your browser support webcal" />
|
||||
</div>
|
||||
<div class="col-sm-8 col-sm-pull-1">
|
||||
<div class="input-group ical-url">
|
||||
<input type="text" class="form-control x-ical-url" readonly="readonly" />
|
||||
<div class="input-group-btn">
|
||||
<button class="btn btn-icon-only x-ical-copy"><i class="icon-sonarr-copy"></i></button>
|
||||
<button class="btn btn-icon-only no-router x-ical-webcal" title="Subscribe" target="_blank"><i class="icon-sonarr-calendar-o"></i></button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button class="btn" data-dismiss="modal">Close</button>
|
||||
</div>
|
||||
<div class="col-sm-5 col-sm-pull-1">
|
||||
<input type="text" class="form-control x-tags">
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="col-sm-3 control-label">iCal feed</label>
|
||||
<div class="col-sm-1 col-sm-push-8 help-inline">
|
||||
<i class="icon-sonarr-form-info" title="Copy this url into your clients subscription form or use the subscribe button if your browser support webcal" />
|
||||
</div>
|
||||
<div class="col-sm-8 col-sm-pull-1">
|
||||
<div class="input-group ical-url">
|
||||
<input type="text" class="form-control x-ical-url" readonly="readonly" />
|
||||
<div class="input-group-btn">
|
||||
<button class="btn btn-icon-only x-ical-copy"><i class="icon-sonarr-copy"></i></button>
|
||||
<button class="btn btn-icon-only no-router x-ical-webcal" title="Subscribe" target="_blank"><i class="icon-sonarr-calendar-o"></i></button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button class="btn" data-dismiss="modal">Close</button>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -10,13 +10,13 @@
|
|||
<div id="x-calendar" class="calendar"/>
|
||||
<div class="legend calendar">
|
||||
<ul class='legend-labels'>
|
||||
<li class="legend-label"><span class="premiere" title="Premiere episode hasn't aired yet"></span>Unaired Premiere</li>
|
||||
<li class="legend-label"><span class="primary" title="Episode hasn't aired yet"></span>Unaired</li>
|
||||
<li class="legend-label"><span class="warning" title="Episode is currently airing"></span>On Air</li>
|
||||
<li class="legend-label"><span class="purple" title="Episode is currently downloading"></span>Downloading</li>
|
||||
<li class="legend-label"><span class="danger" title="Episode file has not been found"></span>Missing</li>
|
||||
<li class="legend-label"><span class="success" title="Episode was downloaded and sorted"></span>Downloaded</li>
|
||||
<li class="legend-label"><span class="unmonitored" title="Episode is unmonitored"></span>Unmonitored</li>
|
||||
<li class="legend-label"><span class="premiere" title="This Movie is still in cinemas and hasn't been released yet. Only poor qualities will be available"></span>In Cinemas</li>
|
||||
<li class="legend-label"><span class="primary" title="This movie has only been announced yet."></span>Announced</li>
|
||||
<!--<li class="legend-label"><span class="warning" title="Episode is currently airing"></span>On Air</li>-->
|
||||
<li class="legend-label"><span class="purple" title="Movie is currently downloading"></span>Downloading</li>
|
||||
<li class="legend-label"><span class="danger" title="Movie file has not been found"></span>Missing</li>
|
||||
<li class="legend-label"><span class="success" title="Movie was downloaded and sorted"></span>Downloaded</li>
|
||||
<li class="legend-label"><span class="unmonitored" title="Movie is unmonitored"></span>Unmonitored</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -12,273 +12,274 @@ require('fullcalendar');
|
|||
require('jquery.easypiechart');
|
||||
|
||||
module.exports = Marionette.ItemView.extend({
|
||||
storageKey : 'calendar.view',
|
||||
storageKey : 'calendar.view',
|
||||
|
||||
initialize : function() {
|
||||
this.showUnmonitored = Config.getValue('calendar.show', 'monitored') === 'all';
|
||||
this.collection = new CalendarCollection().bindSignalR({ updateOnly : true });
|
||||
this.listenTo(this.collection, 'change', this._reloadCalendarEvents);
|
||||
this.listenTo(QueueCollection, 'sync', this._reloadCalendarEvents);
|
||||
},
|
||||
initialize : function() {
|
||||
this.showUnmonitored = Config.getValue('calendar.show', 'monitored') === 'all';
|
||||
this.collection = new CalendarCollection().bindSignalR({ updateOnly : true });
|
||||
this.listenTo(this.collection, 'change', this._reloadCalendarEvents);
|
||||
this.listenTo(QueueCollection, 'sync', this._reloadCalendarEvents);
|
||||
},
|
||||
|
||||
render : function() {
|
||||
this.$el.empty().fullCalendar(this._getOptions());
|
||||
},
|
||||
render : function() {
|
||||
this.$el.empty().fullCalendar(this._getOptions());
|
||||
},
|
||||
|
||||
onShow : function() {
|
||||
this.$('.fc-today-button').click();
|
||||
},
|
||||
onShow : function() {
|
||||
this.$('.fc-today-button').click();
|
||||
},
|
||||
|
||||
setShowUnmonitored : function (showUnmonitored) {
|
||||
if (this.showUnmonitored !== showUnmonitored) {
|
||||
this.showUnmonitored = showUnmonitored;
|
||||
this._getEvents(this.$el.fullCalendar('getView'));
|
||||
}
|
||||
},
|
||||
setShowUnmonitored : function (showUnmonitored) {
|
||||
if (this.showUnmonitored !== showUnmonitored) {
|
||||
this.showUnmonitored = showUnmonitored;
|
||||
this._getEvents(this.$el.fullCalendar('getView'));
|
||||
}
|
||||
},
|
||||
|
||||
_viewRender : function(view, element) {
|
||||
if (Config.getValue(this.storageKey) !== view.name) {
|
||||
Config.setValue(this.storageKey, view.name);
|
||||
}
|
||||
_viewRender : function(view, element) {
|
||||
if (Config.getValue(this.storageKey) !== view.name) {
|
||||
Config.setValue(this.storageKey, view.name);
|
||||
}
|
||||
|
||||
this._getEvents(view);
|
||||
element.find('.fc-day-grid-container').css('height', '');
|
||||
},
|
||||
this._getEvents(view);
|
||||
element.find('.fc-day-grid-container').css('height', '');
|
||||
},
|
||||
|
||||
_eventRender : function(event, element) {
|
||||
element.addClass(event.statusLevel);
|
||||
element.children('.fc-content').addClass(event.statusLevel);
|
||||
_eventRender : function(event, element) {
|
||||
element.addClass(event.statusLevel);
|
||||
element.children('.fc-content').addClass(event.statusLevel);
|
||||
|
||||
if (event.downloading) {
|
||||
var progress = 100 - event.downloading.get('sizeleft') / event.downloading.get('size') * 100;
|
||||
var releaseTitle = event.downloading.get('title');
|
||||
var estimatedCompletionTime = moment(event.downloading.get('estimatedCompletionTime')).fromNow();
|
||||
var status = event.downloading.get('status').toLocaleLowerCase();
|
||||
var errorMessage = event.downloading.get('errorMessage');
|
||||
if (event.downloading) {
|
||||
var progress = 100 - event.downloading.get('sizeleft') / event.downloading.get('size') * 100;
|
||||
var releaseTitle = event.downloading.get('title');
|
||||
var estimatedCompletionTime = moment(event.downloading.get('estimatedCompletionTime')).fromNow();
|
||||
var status = event.downloading.get('status').toLocaleLowerCase();
|
||||
var errorMessage = event.downloading.get('errorMessage');
|
||||
|
||||
if (status === 'pending') {
|
||||
this._addStatusIcon(element, 'icon-sonarr-pending', 'Release will be processed {0}'.format(estimatedCompletionTime));
|
||||
}
|
||||
if (status === 'pending') {
|
||||
this._addStatusIcon(element, 'icon-sonarr-pending', 'Release will be processed {0}'.format(estimatedCompletionTime));
|
||||
}
|
||||
|
||||
else if (errorMessage) {
|
||||
if (status === 'completed') {
|
||||
this._addStatusIcon(element, 'icon-sonarr-import-failed', 'Import failed: {0}'.format(errorMessage));
|
||||
} else {
|
||||
this._addStatusIcon(element, 'icon-sonarr-download-failed', 'Download failed: {0}'.format(errorMessage));
|
||||
}
|
||||
}
|
||||
else if (errorMessage) {
|
||||
if (status === 'completed') {
|
||||
this._addStatusIcon(element, 'icon-sonarr-import-failed', 'Import failed: {0}'.format(errorMessage));
|
||||
} else {
|
||||
this._addStatusIcon(element, 'icon-sonarr-download-failed', 'Download failed: {0}'.format(errorMessage));
|
||||
}
|
||||
}
|
||||
|
||||
else if (status === 'failed') {
|
||||
this._addStatusIcon(element, 'icon-sonarr-download-failed', 'Download failed: check download client for more details');
|
||||
}
|
||||
else if (status === 'failed') {
|
||||
this._addStatusIcon(element, 'icon-sonarr-download-failed', 'Download failed: check download client for more details');
|
||||
}
|
||||
|
||||
else if (status === 'warning') {
|
||||
this._addStatusIcon(element, 'icon-sonarr-download-warning', 'Download warning: check download client for more details');
|
||||
}
|
||||
else if (status === 'warning') {
|
||||
this._addStatusIcon(element, 'icon-sonarr-download-warning', 'Download warning: check download client for more details');
|
||||
}
|
||||
|
||||
else {
|
||||
element.find('.fc-time').after('<span class="chart pull-right" data-percent="{0}"></span>'.format(progress));
|
||||
else {
|
||||
element.find('.fc-time').after('<span class="chart pull-right" data-percent="{0}"></span>'.format(progress));
|
||||
|
||||
element.find('.chart').easyPieChart({
|
||||
barColor : '#ffffff',
|
||||
trackColor : false,
|
||||
scaleColor : false,
|
||||
lineWidth : 2,
|
||||
size : 14,
|
||||
animate : false
|
||||
});
|
||||
element.find('.chart').easyPieChart({
|
||||
barColor : '#ffffff',
|
||||
trackColor : false,
|
||||
scaleColor : false,
|
||||
lineWidth : 2,
|
||||
size : 14,
|
||||
animate : false
|
||||
});
|
||||
|
||||
element.find('.chart').tooltip({
|
||||
title : 'Episode is downloading - {0}% {1}'.format(progress.toFixed(1), releaseTitle),
|
||||
container : '.fc'
|
||||
});
|
||||
}
|
||||
}
|
||||
element.find('.chart').tooltip({
|
||||
title : 'Episode is downloading - {0}% {1}'.format(progress.toFixed(1), releaseTitle),
|
||||
container : '.fc'
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
else if (event.model.get('unverifiedSceneNumbering')) {
|
||||
this._addStatusIcon(element, 'icon-sonarr-form-warning', 'Scene number hasn\'t been verified yet.');
|
||||
}
|
||||
else if (event.model.get('unverifiedSceneNumbering')) {
|
||||
this._addStatusIcon(element, 'icon-sonarr-form-warning', 'Scene number hasn\'t been verified yet.');
|
||||
}
|
||||
},
|
||||
|
||||
else if (event.model.get('series').seriesType === 'anime' && event.model.get('seasonNumber') > 0 && !event.model.has('absoluteEpisodeNumber')) {
|
||||
this._addStatusIcon(element, 'icon-sonarr-form-warning', 'Episode does not have an absolute episode number');
|
||||
}
|
||||
},
|
||||
_eventAfterAllRender : function () {
|
||||
if ($(window).width() < 768) {
|
||||
this.$('.fc-center').show();
|
||||
this.$('.calendar-title').remove();
|
||||
|
||||
_eventAfterAllRender : function () {
|
||||
if ($(window).width() < 768) {
|
||||
this.$('.fc-center').show();
|
||||
this.$('.calendar-title').remove();
|
||||
var title = this.$('.fc-center').html();
|
||||
var titleDiv = '<div class="calendar-title">{0}</div>'.format(title);
|
||||
|
||||
var title = this.$('.fc-center').html();
|
||||
var titleDiv = '<div class="calendar-title">{0}</div>'.format(title);
|
||||
this.$('.fc-toolbar').before(titleDiv);
|
||||
this.$('.fc-center').hide();
|
||||
}
|
||||
|
||||
this.$('.fc-toolbar').before(titleDiv);
|
||||
this.$('.fc-center').hide();
|
||||
}
|
||||
this._clearScrollBar();
|
||||
},
|
||||
|
||||
this._clearScrollBar();
|
||||
},
|
||||
_windowResize : function () {
|
||||
this._clearScrollBar();
|
||||
},
|
||||
|
||||
_windowResize : function () {
|
||||
this._clearScrollBar();
|
||||
},
|
||||
_getEvents : function(view) {
|
||||
var start = moment(view.start.toISOString()).toISOString();
|
||||
var end = moment(view.end.toISOString()).toISOString();
|
||||
|
||||
_getEvents : function(view) {
|
||||
var start = moment(view.start.toISOString()).toISOString();
|
||||
var end = moment(view.end.toISOString()).toISOString();
|
||||
this.$el.fullCalendar('removeEvents');
|
||||
|
||||
this.$el.fullCalendar('removeEvents');
|
||||
this.collection.fetch({
|
||||
data : {
|
||||
start : start,
|
||||
end : end,
|
||||
unmonitored : this.showUnmonitored
|
||||
},
|
||||
success : this._setEventData.bind(this, new Date(start), new Date(end))
|
||||
});
|
||||
},
|
||||
|
||||
this.collection.fetch({
|
||||
data : {
|
||||
start : start,
|
||||
end : end,
|
||||
unmonitored : this.showUnmonitored
|
||||
},
|
||||
success : this._setEventData.bind(this)
|
||||
});
|
||||
},
|
||||
_setEventData : function(startD, endD, collection) {
|
||||
if (collection.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
_setEventData : function(collection) {
|
||||
if (collection.length === 0) {
|
||||
return;
|
||||
}
|
||||
var events = [];
|
||||
var self = this;
|
||||
|
||||
var events = [];
|
||||
var self = this;
|
||||
collection.each(function(model) {
|
||||
var seriesTitle = model.get('title');
|
||||
var start = model.get('inCinemas');
|
||||
var startDate = new Date(start);
|
||||
if (!(startD <= startDate && startDate <= endD)) {
|
||||
start = model.get("physicalRelease");
|
||||
}
|
||||
var runtime = model.get('runtime');
|
||||
var end = moment(start).add('minutes', runtime).toISOString();
|
||||
|
||||
collection.each(function(model) {
|
||||
var seriesTitle = model.get('series').title;
|
||||
var start = model.get('airDateUtc');
|
||||
var runtime = model.get('series').runtime;
|
||||
var end = moment(start).add('minutes', runtime).toISOString();
|
||||
var event = {
|
||||
title : seriesTitle,
|
||||
start : moment(start),
|
||||
end : moment(end),
|
||||
allDay : true,
|
||||
statusLevel : self._getStatusLevel(model, end),
|
||||
downloading : QueueCollection.findMovie(model.get('id')),
|
||||
model : model,
|
||||
sortOrder : 0
|
||||
};
|
||||
|
||||
var event = {
|
||||
title : seriesTitle,
|
||||
start : moment(start),
|
||||
end : moment(end),
|
||||
allDay : false,
|
||||
statusLevel : self._getStatusLevel(model, end),
|
||||
downloading : QueueCollection.findEpisode(model.get('id')),
|
||||
model : model,
|
||||
sortOrder : (model.get('seasonNumber') === 0 ? 1000000 : model.get('seasonNumber') * 10000) + model.get('episodeNumber')
|
||||
};
|
||||
events.push(event);
|
||||
});
|
||||
|
||||
events.push(event);
|
||||
});
|
||||
this.$el.fullCalendar('addEventSource', events);
|
||||
},
|
||||
|
||||
this.$el.fullCalendar('addEventSource', events);
|
||||
},
|
||||
_getStatusLevel : function(element, endTime) {
|
||||
var hasFile = element.get('hasFile');
|
||||
var downloading = QueueCollection.findMovie(element.get('id')) || element.get('grabbed');
|
||||
var currentTime = moment();
|
||||
var start = moment(element.get('inCinemas'));
|
||||
var status = element.getStatus();
|
||||
var end = moment(endTime);
|
||||
var monitored = element.get('monitored');
|
||||
|
||||
_getStatusLevel : function(element, endTime) {
|
||||
var hasFile = element.get('hasFile');
|
||||
var downloading = QueueCollection.findEpisode(element.get('id')) || element.get('grabbed');
|
||||
var currentTime = moment();
|
||||
var start = moment(element.get('airDateUtc'));
|
||||
var end = moment(endTime);
|
||||
var monitored = element.get('series').monitored && element.get('monitored');
|
||||
var statusLevel = 'primary';
|
||||
|
||||
var statusLevel = 'primary';
|
||||
if (hasFile) {
|
||||
statusLevel = 'success';
|
||||
}
|
||||
|
||||
if (hasFile) {
|
||||
statusLevel = 'success';
|
||||
}
|
||||
else if (downloading) {
|
||||
statusLevel = 'purple';
|
||||
}
|
||||
|
||||
else if (downloading) {
|
||||
statusLevel = 'purple';
|
||||
}
|
||||
else if (!monitored) {
|
||||
statusLevel = 'unmonitored';
|
||||
}
|
||||
|
||||
else if (!monitored) {
|
||||
statusLevel = 'unmonitored';
|
||||
}
|
||||
else if (status == "inCinemas") {
|
||||
statusLevel = 'premiere';
|
||||
}
|
||||
|
||||
else if (currentTime.isAfter(start) && currentTime.isBefore(end)) {
|
||||
statusLevel = 'warning';
|
||||
}
|
||||
else if (status == "released") {
|
||||
statusLevel = 'danger';
|
||||
}
|
||||
|
||||
else if (start.isBefore(currentTime) && !hasFile) {
|
||||
statusLevel = 'danger';
|
||||
}
|
||||
else if (status == "announced") {
|
||||
statusLevel = 'primary';
|
||||
}
|
||||
|
||||
else if (element.get('episodeNumber') === 1) {
|
||||
statusLevel = 'premiere';
|
||||
}
|
||||
if (end.isBefore(currentTime.startOf('day'))) {
|
||||
statusLevel += ' past';
|
||||
}
|
||||
|
||||
if (end.isBefore(currentTime.startOf('day'))) {
|
||||
statusLevel += ' past';
|
||||
}
|
||||
return statusLevel;
|
||||
},
|
||||
|
||||
return statusLevel;
|
||||
},
|
||||
_reloadCalendarEvents : function() {
|
||||
this.$el.fullCalendar('removeEvents');
|
||||
var view = this.$el.fullCalendar('getView');
|
||||
var start = moment(view.start.toISOString()).toISOString();
|
||||
var end = moment(view.end.toISOString()).toISOString();
|
||||
this._setEventData(new Date(start), new Date(end), this.collection);
|
||||
},
|
||||
|
||||
_reloadCalendarEvents : function() {
|
||||
this.$el.fullCalendar('removeEvents');
|
||||
this._setEventData(this.collection);
|
||||
},
|
||||
_getOptions : function() {
|
||||
var options = {
|
||||
allDayDefault : true,
|
||||
weekMode : 'variable',
|
||||
firstDay : UiSettings.get('firstDayOfWeek'),
|
||||
timeFormat : 'h(:mm)t',
|
||||
viewRender : this._viewRender.bind(this),
|
||||
eventRender : this._eventRender.bind(this),
|
||||
eventAfterAllRender : this._eventAfterAllRender.bind(this),
|
||||
windowResize : this._windowResize.bind(this),
|
||||
eventClick : function(event) {
|
||||
//vent.trigger(vent.Commands.ShowMovieDetails, { movie : event.model });
|
||||
window.location.href = "movies/"+event.model.get("titleSlug");
|
||||
}
|
||||
};
|
||||
|
||||
_getOptions : function() {
|
||||
var options = {
|
||||
allDayDefault : false,
|
||||
weekMode : 'variable',
|
||||
firstDay : UiSettings.get('firstDayOfWeek'),
|
||||
timeFormat : 'h(:mm)t',
|
||||
viewRender : this._viewRender.bind(this),
|
||||
eventRender : this._eventRender.bind(this),
|
||||
eventAfterAllRender : this._eventAfterAllRender.bind(this),
|
||||
windowResize : this._windowResize.bind(this),
|
||||
eventClick : function(event) {
|
||||
vent.trigger(vent.Commands.ShowEpisodeDetails, { episode : event.model });
|
||||
}
|
||||
};
|
||||
if ($(window).width() < 768) {
|
||||
options.defaultView = Config.getValue(this.storageKey, 'listYear');
|
||||
|
||||
if ($(window).width() < 768) {
|
||||
options.defaultView = Config.getValue(this.storageKey, 'basicDay');
|
||||
options.header = {
|
||||
left : 'prev,next today',
|
||||
center : 'title',
|
||||
right : 'listYear'
|
||||
};
|
||||
}
|
||||
|
||||
options.header = {
|
||||
left : 'prev,next today',
|
||||
center : 'title',
|
||||
right : 'basicWeek,basicDay'
|
||||
};
|
||||
}
|
||||
else {
|
||||
options.defaultView = Config.getValue(this.storageKey, 'month');
|
||||
|
||||
else {
|
||||
options.defaultView = Config.getValue(this.storageKey, 'basicWeek');
|
||||
options.header = {
|
||||
left : 'prev,next today',
|
||||
center : 'title',
|
||||
right : 'month,listYear'
|
||||
};
|
||||
}
|
||||
|
||||
options.header = {
|
||||
left : 'prev,next today',
|
||||
center : 'title',
|
||||
right : 'month,basicWeek,basicDay'
|
||||
};
|
||||
}
|
||||
options.titleFormat = "L";
|
||||
|
||||
options.titleFormat = {
|
||||
month : 'MMMM YYYY',
|
||||
week : UiSettings.get('shortDateFormat'),
|
||||
day : UiSettings.get('longDateFormat')
|
||||
};
|
||||
options.columnFormat = "L"/*{
|
||||
month : 'ddd',
|
||||
week : UiSettings.get('calendarWeekColumnHeader'),
|
||||
day : 'dddd'
|
||||
};*///For now ignore settings. TODO update that.
|
||||
|
||||
options.columnFormat = {
|
||||
month : 'ddd',
|
||||
week : UiSettings.get('calendarWeekColumnHeader'),
|
||||
day : 'dddd'
|
||||
};
|
||||
options.timeFormat = UiSettings.get('timeFormat');
|
||||
|
||||
options.timeFormat = UiSettings.get('timeFormat');
|
||||
return options;
|
||||
},
|
||||
|
||||
return options;
|
||||
},
|
||||
_addStatusIcon : function(element, icon, tooltip) {
|
||||
element.find('.fc-time').after('<span class="status pull-right"><i class="{0}"></i></span>'.format(icon));
|
||||
element.find('.status').tooltip({
|
||||
title : tooltip,
|
||||
container : '.fc'
|
||||
});
|
||||
},
|
||||
|
||||
_addStatusIcon : function(element, icon, tooltip) {
|
||||
element.find('.fc-time').after('<span class="status pull-right"><i class="{0}"></i></span>'.format(icon));
|
||||
element.find('.status').tooltip({
|
||||
title : tooltip,
|
||||
container : '.fc'
|
||||
});
|
||||
},
|
||||
|
||||
_clearScrollBar : function () {
|
||||
// Remove height from calendar so we don't have another scroll bar
|
||||
this.$('.fc-day-grid-container').css('height', '');
|
||||
this.$('.fc-row.fc-widget-header').attr('style', '');
|
||||
}
|
||||
});
|
||||
_clearScrollBar : function () {
|
||||
// Remove height from calendar so we don't have another scroll bar
|
||||
this.$('.fc-day-grid-container').css('height', '');
|
||||
this.$('.fc-row.fc-widget-header').attr('style', '');
|
||||
}
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,17 +1,17 @@
|
|||
var Backbone = require('backbone');
|
||||
var moment = require('moment');
|
||||
var EpisodeModel = require('../Series/EpisodeModel');
|
||||
var EpisodeModel = require('../Movies/MovieModel');
|
||||
|
||||
module.exports = Backbone.Collection.extend({
|
||||
url : window.NzbDrone.ApiRoot + '/calendar',
|
||||
model : EpisodeModel,
|
||||
|
||||
comparator : function(model1, model2) {
|
||||
var airDate1 = model1.get('airDateUtc');
|
||||
var airDate1 = model1.get('inCinemas');
|
||||
var date1 = moment(airDate1);
|
||||
var time1 = date1.unix();
|
||||
|
||||
var airDate2 = model2.get('airDateUtc');
|
||||
var airDate2 = model2.get('inCinemas');
|
||||
var date2 = moment(airDate2);
|
||||
var time2 = date2.unix();
|
||||
|
||||
|
|
@ -25,4 +25,4 @@ module.exports = Backbone.Collection.extend({
|
|||
|
||||
return 0;
|
||||
}
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -11,8 +11,8 @@ module.exports = Marionette.ItemView.extend({
|
|||
},
|
||||
|
||||
initialize : function() {
|
||||
var start = this.model.get('airDateUtc');
|
||||
var runtime = this.model.get('series').runtime;
|
||||
var start = this.model.get('inCinemas');
|
||||
var runtime = this.model.get('runtime');
|
||||
var end = moment(start).add('minutes', runtime);
|
||||
|
||||
this.model.set({
|
||||
|
|
@ -25,4 +25,4 @@ module.exports = Marionette.ItemView.extend({
|
|||
_showEpisodeDetails : function() {
|
||||
vent.trigger(vent.Commands.ShowEpisodeDetails, { episode : this.model });
|
||||
}
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -7,248 +7,259 @@
|
|||
@import "../Content/Overrides/bootstrap";
|
||||
|
||||
.calendar {
|
||||
width: 100%;
|
||||
width: 100%;
|
||||
|
||||
th, td {
|
||||
border-color : #eeeeee;
|
||||
}
|
||||
th, td {
|
||||
border-color : #eeeeee;
|
||||
}
|
||||
|
||||
.fc-event-skin {
|
||||
background-color : #007ccd;
|
||||
border : 1px solid #007ccd;
|
||||
border-radius : 4px;
|
||||
text-align : center;
|
||||
}
|
||||
.fc-event-skin {
|
||||
background-color : #007ccd;
|
||||
border : 1px solid #007ccd;
|
||||
border-radius : 4px;
|
||||
text-align : center;
|
||||
}
|
||||
|
||||
.fc-event {
|
||||
.clickable;
|
||||
.fc-event {
|
||||
.clickable;
|
||||
|
||||
.status {
|
||||
margin-right : 4px;
|
||||
}
|
||||
}
|
||||
.status {
|
||||
margin-right : 4px;
|
||||
}
|
||||
}
|
||||
|
||||
th {
|
||||
background-color : #eeeeee;
|
||||
}
|
||||
th {
|
||||
background-color : #eeeeee;
|
||||
}
|
||||
|
||||
h2 {
|
||||
font-size : 17.5px;
|
||||
}
|
||||
h2 {
|
||||
font-size : 17.5px;
|
||||
}
|
||||
|
||||
.fc-state-highlight {
|
||||
background : #dbdbdb;
|
||||
}
|
||||
.fc-state-highlight {
|
||||
background : #dbdbdb;
|
||||
}
|
||||
|
||||
.past {
|
||||
opacity : 0.8;
|
||||
}
|
||||
.past {
|
||||
opacity : 0.8;
|
||||
}
|
||||
|
||||
.fc-title {
|
||||
white-space: normal;
|
||||
}
|
||||
|
||||
.fc-list-table {
|
||||
.past {
|
||||
opacity: 1.0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.event {
|
||||
display : inline-block;
|
||||
width : 100%;
|
||||
margin-bottom : 10px;
|
||||
border-top : 1px solid #eeeeee;
|
||||
padding-top : 10px;
|
||||
display : inline-block;
|
||||
width : 100%;
|
||||
margin-bottom : 10px;
|
||||
border-top : 1px solid #eeeeee;
|
||||
padding-top : 10px;
|
||||
|
||||
h4 {
|
||||
font-weight : 500;
|
||||
color : #008dcd;
|
||||
margin : 5px 0px;
|
||||
}
|
||||
h4 {
|
||||
font-weight : 500;
|
||||
color : #008dcd;
|
||||
margin : 5px 0px;
|
||||
}
|
||||
|
||||
p {
|
||||
color : #999999;
|
||||
margin : 0px;
|
||||
}
|
||||
p {
|
||||
color : #999999;
|
||||
margin : 0px;
|
||||
}
|
||||
|
||||
.date {
|
||||
text-align : center;
|
||||
display : inline-block;
|
||||
border-left : 4px solid #eeeeee;
|
||||
padding-left : 16px;
|
||||
float : left;
|
||||
margin-right : 20px;
|
||||
.date {
|
||||
text-align : center;
|
||||
display : inline-block;
|
||||
border-left : 4px solid #eeeeee;
|
||||
padding-left : 16px;
|
||||
float : left;
|
||||
margin-right : 20px;
|
||||
|
||||
h4 {
|
||||
line-height : 1em;
|
||||
color : #555555;
|
||||
font-weight : 300;
|
||||
text-transform : uppercase;
|
||||
}
|
||||
h4 {
|
||||
line-height : 1em;
|
||||
color : #555555;
|
||||
font-weight : 300;
|
||||
text-transform : uppercase;
|
||||
}
|
||||
|
||||
h1 {
|
||||
font-weight : 500;
|
||||
line-height : 0.8em;
|
||||
}
|
||||
}
|
||||
h1 {
|
||||
font-weight : 500;
|
||||
line-height : 0.8em;
|
||||
}
|
||||
}
|
||||
|
||||
.primary {
|
||||
border-color : @btn-primary-bg;
|
||||
}
|
||||
.primary {
|
||||
border-color : @btn-primary-bg;
|
||||
}
|
||||
|
||||
.info {
|
||||
border-color : @btn-info-bg;
|
||||
}
|
||||
.info {
|
||||
border-color : @btn-info-bg;
|
||||
}
|
||||
|
||||
.inverse {
|
||||
border-color : @btn-link-disabled-color;
|
||||
}
|
||||
.inverse {
|
||||
border-color : @btn-link-disabled-color;
|
||||
}
|
||||
|
||||
.warning {
|
||||
border-color : @btn-warning-bg;
|
||||
}
|
||||
.warning {
|
||||
border-color : @btn-warning-bg;
|
||||
}
|
||||
|
||||
.danger {
|
||||
border-color : @btn-danger-bg;
|
||||
}
|
||||
.danger {
|
||||
border-color : @btn-danger-bg;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.success {
|
||||
border-color : @btn-success-bg;
|
||||
}
|
||||
.success {
|
||||
border-color : @btn-success-bg;
|
||||
}
|
||||
|
||||
.purple {
|
||||
border-color : @nzbdronePurple;
|
||||
}
|
||||
.purple {
|
||||
border-color : @nzbdronePurple;
|
||||
}
|
||||
|
||||
.pink {
|
||||
border-color : @nzbdronePink;
|
||||
}
|
||||
.pink {
|
||||
border-color : @nzbdronePink;
|
||||
}
|
||||
|
||||
.premiere {
|
||||
border-color : @droneTeal;
|
||||
}
|
||||
.premiere {
|
||||
border-color : @droneTeal;
|
||||
}
|
||||
|
||||
.unmonitored {
|
||||
border-color : grey;
|
||||
}
|
||||
.unmonitored {
|
||||
border-color : grey;
|
||||
}
|
||||
|
||||
.episode-title {
|
||||
.btn-link;
|
||||
.text-overflow;
|
||||
color : @link-color;
|
||||
margin-top : 1px;
|
||||
display : inline-block;
|
||||
.episode-title {
|
||||
.btn-link;
|
||||
.text-overflow;
|
||||
color : @link-color;
|
||||
margin-top : 1px;
|
||||
display : inline-block;
|
||||
|
||||
@media (max-width: @screen-xs-min) {
|
||||
width : 140px;
|
||||
}
|
||||
@media (max-width: @screen-xs-min) {
|
||||
width : 140px;
|
||||
}
|
||||
|
||||
@media (min-width: @screen-md-min) {
|
||||
width : 135px;
|
||||
}
|
||||
}
|
||||
@media (min-width: @screen-md-min) {
|
||||
width : 135px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.calendar {
|
||||
|
||||
// background-position : -160px -128px;
|
||||
|
||||
.primary {
|
||||
border-color : @btn-primary-bg;
|
||||
background-color : @btn-primary-bg;
|
||||
.primary {
|
||||
border-color : @btn-primary-bg;
|
||||
background-color : @btn-primary-bg;
|
||||
|
||||
.color-impaired-background-gradient(90deg, @btn-primary-bg);
|
||||
}
|
||||
.color-impaired-background-gradient(90deg, @btn-primary-bg);
|
||||
}
|
||||
|
||||
.info {
|
||||
border-color : @btn-info-bg;
|
||||
background-color : @btn-info-bg;
|
||||
}
|
||||
.info {
|
||||
border-color : @btn-info-bg;
|
||||
background-color : @btn-info-bg;
|
||||
}
|
||||
|
||||
.inverse {
|
||||
border-color : @btn-link-disabled-color;
|
||||
background-color : @btn-link-disabled-color;
|
||||
}
|
||||
.inverse {
|
||||
border-color : @btn-link-disabled-color;
|
||||
background-color : @btn-link-disabled-color;
|
||||
}
|
||||
|
||||
.warning {
|
||||
border-color : @btn-warning-bg;
|
||||
background-color : @btn-warning-bg;
|
||||
.warning {
|
||||
border-color : @btn-warning-bg;
|
||||
background-color : @btn-warning-bg;
|
||||
|
||||
.color-impaired-background-gradient(90deg, @btn-warning-bg);
|
||||
}
|
||||
.color-impaired-background-gradient(90deg, @btn-warning-bg);
|
||||
}
|
||||
|
||||
.danger {
|
||||
border-color : @btn-danger-bg;
|
||||
background-color : @btn-danger-bg;
|
||||
.danger {
|
||||
border-color : @btn-danger-bg;
|
||||
background-color : @btn-danger-bg;
|
||||
color: white;
|
||||
.color-impaired-background-gradient(90deg, @btn-danger-bg);
|
||||
}
|
||||
|
||||
.color-impaired-background-gradient(90deg, @btn-danger-bg);
|
||||
}
|
||||
.success {
|
||||
border-color : @btn-success-bg;
|
||||
background-color : @btn-success-bg;
|
||||
}
|
||||
|
||||
.success {
|
||||
border-color : @btn-success-bg;
|
||||
background-color : @btn-success-bg;
|
||||
}
|
||||
.purple {
|
||||
border-color : @nzbdronePurple;
|
||||
background-color : @nzbdronePurple;
|
||||
}
|
||||
|
||||
.purple {
|
||||
border-color : @nzbdronePurple;
|
||||
background-color : @nzbdronePurple;
|
||||
}
|
||||
.pink {
|
||||
border-color : @nzbdronePink;
|
||||
background-color : @nzbdronePink;
|
||||
}
|
||||
|
||||
.pink {
|
||||
border-color : @nzbdronePink;
|
||||
background-color : @nzbdronePink;
|
||||
}
|
||||
.premiere {
|
||||
border-color : @droneTeal;
|
||||
background-color : @droneTeal;
|
||||
|
||||
.premiere {
|
||||
border-color : @droneTeal;
|
||||
background-color : @droneTeal;
|
||||
.color-impaired-background-gradient(90deg, @droneTeal);
|
||||
}
|
||||
|
||||
.color-impaired-background-gradient(90deg, @droneTeal);
|
||||
}
|
||||
.unmonitored {
|
||||
border-color : grey;
|
||||
background-color : grey;
|
||||
|
||||
.unmonitored {
|
||||
border-color : grey;
|
||||
background-color : grey;
|
||||
.color-impaired-background-gradient(45deg, grey);
|
||||
}
|
||||
|
||||
.color-impaired-background-gradient(45deg, grey);
|
||||
}
|
||||
.chart {
|
||||
margin-top : 2px;
|
||||
margin-right : 2px;
|
||||
line-height : 12px;
|
||||
}
|
||||
|
||||
.chart {
|
||||
margin-top : 2px;
|
||||
margin-right : 2px;
|
||||
line-height : 12px;
|
||||
}
|
||||
.legend-labels {
|
||||
max-width : 100%;
|
||||
width : 500px;
|
||||
|
||||
.legend-labels {
|
||||
max-width : 100%;
|
||||
width : 500px;
|
||||
@media (max-width: @screen-xs-min) {
|
||||
width : 100%;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: @screen-xs-min) {
|
||||
width : 100%;
|
||||
}
|
||||
}
|
||||
|
||||
.legend-label {
|
||||
display : inline-block;
|
||||
width : 150px;
|
||||
}
|
||||
.legend-label {
|
||||
display : inline-block;
|
||||
width : 150px;
|
||||
}
|
||||
}
|
||||
|
||||
.ical {
|
||||
color: @btn-link-disabled-color;
|
||||
cursor: pointer;
|
||||
color: @btn-link-disabled-color;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.ical-url {
|
||||
|
||||
input, input[readonly] {
|
||||
cursor : text;
|
||||
}
|
||||
input, input[readonly] {
|
||||
cursor : text;
|
||||
}
|
||||
}
|
||||
|
||||
.calendar-title {
|
||||
text-align : center;
|
||||
text-align : center;
|
||||
|
||||
h2 {
|
||||
margin-top : 0px;
|
||||
margin-bottom : 5px;
|
||||
}
|
||||
h2 {
|
||||
margin-top : 0px;
|
||||
margin-bottom : 5px;
|
||||
}
|
||||
}
|
||||
|
||||
.calendar-toolbar {
|
||||
.page-toolbar {
|
||||
margin-bottom : 10px;
|
||||
}
|
||||
.page-toolbar {
|
||||
margin-bottom : 10px;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,4 +8,9 @@
|
|||
{{#if imdbId}}
|
||||
<a href="{{imdbUrl}}" class="label label-info">IMDB</a>
|
||||
{{/if}}
|
||||
|
||||
{{#if youTubeTrailerId}}
|
||||
<a href="{{youTubeTrailerUrl}}" class="label label-info">Trailer</a>
|
||||
{{/if}}
|
||||
|
||||
</span>
|
||||
|
|
|
|||
42
src/UI/Cells/MovieStatusWithTextCell.js
Normal file
42
src/UI/Cells/MovieStatusWithTextCell.js
Normal file
|
|
@ -0,0 +1,42 @@
|
|||
var NzbDroneCell = require('./NzbDroneCell');
|
||||
|
||||
//used in Wanted tab
|
||||
module.exports = NzbDroneCell.extend({
|
||||
className : 'movie-status-text-cell',
|
||||
|
||||
render : function() {
|
||||
this.$el.empty();
|
||||
var monitored = this.model.get('monitored');
|
||||
var status = this.model.get('status');
|
||||
var inCinemas = this.model.get("inCinemas");
|
||||
var date = new Date(inCinemas);
|
||||
var timeSince = new Date().getTime() - date.getTime();
|
||||
var numOfMonths = timeSince / 1000 / 60 / 60 / 24 / 30;
|
||||
|
||||
if (status === 'released') {
|
||||
this.$el.html('<div class="released-banner"><i class="icon-sonarr-movie-released grid-icon" title=""></i> Released</div>');
|
||||
this._setStatusWeight(3);
|
||||
}
|
||||
|
||||
if (numOfMonths > 3) {
|
||||
this.$el.html('<div class="released-banner"><i class="icon-sonarr-movie-released grid-icon" title=""></i> Released</div>');//TODO: Update for PreDB.me
|
||||
this._setStatusWeight(2);
|
||||
}
|
||||
|
||||
if (numOfMonths < 3) {
|
||||
this.$el.html('<div class="cinemas-banner"><i class="icon-sonarr-movie-cinemas grid-icon" title=""></i> In Cinemas</div>');
|
||||
this._setStatusWeight(2);
|
||||
}
|
||||
|
||||
if (status === "announced") {
|
||||
this.$el.html('<div class="announced-banner"><i class="icon-sonarr-movie-announced grid-icon" title=""></i> Announced</div>');
|
||||
this._setStatusWeight(1);
|
||||
}
|
||||
|
||||
return this;
|
||||
},
|
||||
|
||||
_setStatusWeight : function(weight) {
|
||||
this.model.set('statusWeight', weight, { silent : true });
|
||||
}
|
||||
});
|
||||
|
|
@ -55,6 +55,10 @@
|
|||
width : 150px;
|
||||
}
|
||||
|
||||
.movie-status-text-cell {
|
||||
width : 150px;
|
||||
}
|
||||
|
||||
.history-event-type-cell {
|
||||
width : 10px;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
/*!
|
||||
* FullCalendar v2.3.2 Stylesheet
|
||||
* FullCalendar v3.1.0 Stylesheet
|
||||
* Docs & License: http://fullcalendar.io/
|
||||
* (c) 2015 Adam Shaw
|
||||
* (c) 2016 Adam Shaw
|
||||
*/
|
||||
|
||||
|
||||
|
|
@ -28,7 +28,10 @@ body .fc { /* extra precedence to overcome jqui */
|
|||
.fc-unthemed tbody,
|
||||
.fc-unthemed .fc-divider,
|
||||
.fc-unthemed .fc-row,
|
||||
.fc-unthemed .fc-popover {
|
||||
.fc-unthemed .fc-content, /* for gutter border */
|
||||
.fc-unthemed .fc-popover,
|
||||
.fc-unthemed .fc-list-view,
|
||||
.fc-unthemed .fc-list-heading td {
|
||||
border-color: #ddd;
|
||||
}
|
||||
|
||||
|
|
@ -37,7 +40,8 @@ body .fc { /* extra precedence to overcome jqui */
|
|||
}
|
||||
|
||||
.fc-unthemed .fc-divider,
|
||||
.fc-unthemed .fc-popover .fc-header {
|
||||
.fc-unthemed .fc-popover .fc-header,
|
||||
.fc-unthemed .fc-list-heading td {
|
||||
background: #eee;
|
||||
}
|
||||
|
||||
|
|
@ -45,20 +49,18 @@ body .fc { /* extra precedence to overcome jqui */
|
|||
color: #666;
|
||||
}
|
||||
|
||||
.fc-unthemed .fc-today {
|
||||
.fc-unthemed td.fc-today {
|
||||
background: #fcf8e3;
|
||||
}
|
||||
|
||||
.fc-highlight { /* when user is selecting cells */
|
||||
background: #bce8f1;
|
||||
opacity: .3;
|
||||
filter: alpha(opacity=30); /* for IE */
|
||||
}
|
||||
|
||||
.fc-bgevent { /* default look for background events */
|
||||
background: rgb(143, 223, 130);
|
||||
opacity: .3;
|
||||
filter: alpha(opacity=30); /* for IE */
|
||||
}
|
||||
|
||||
.fc-nonbusiness { /* default look for non-business-hours areas */
|
||||
|
|
@ -72,7 +74,6 @@ body .fc { /* extra precedence to overcome jqui */
|
|||
|
||||
.fc-icon {
|
||||
display: inline-block;
|
||||
width: 1em;
|
||||
height: 1em;
|
||||
line-height: 1em;
|
||||
font-size: 1em;
|
||||
|
|
@ -99,7 +100,6 @@ NOTE: use percentage font sizes or else old IE chokes
|
|||
|
||||
.fc-icon:after {
|
||||
position: relative;
|
||||
margin: 0 -1em; /* ensures character will be centered, regardless of width */
|
||||
}
|
||||
|
||||
.fc-icon-left-single-arrow:after {
|
||||
|
|
@ -107,7 +107,6 @@ NOTE: use percentage font sizes or else old IE chokes
|
|||
font-weight: bold;
|
||||
font-size: 200%;
|
||||
top: -7%;
|
||||
left: 3%;
|
||||
}
|
||||
|
||||
.fc-icon-right-single-arrow:after {
|
||||
|
|
@ -115,7 +114,6 @@ NOTE: use percentage font sizes or else old IE chokes
|
|||
font-weight: bold;
|
||||
font-size: 200%;
|
||||
top: -7%;
|
||||
left: -3%;
|
||||
}
|
||||
|
||||
.fc-icon-left-double-arrow:after {
|
||||
|
|
@ -134,14 +132,12 @@ NOTE: use percentage font sizes or else old IE chokes
|
|||
content: "\25C4";
|
||||
font-size: 125%;
|
||||
top: 3%;
|
||||
left: -2%;
|
||||
}
|
||||
|
||||
.fc-icon-right-triangle:after {
|
||||
content: "\25BA";
|
||||
font-size: 125%;
|
||||
top: 3%;
|
||||
left: 2%;
|
||||
}
|
||||
|
||||
.fc-icon-down-triangle:after {
|
||||
|
|
@ -252,7 +248,6 @@ NOTE: use percentage font sizes or else old IE chokes
|
|||
cursor: default;
|
||||
background-image: none;
|
||||
opacity: 0.65;
|
||||
filter: alpha(opacity=65);
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
|
|
@ -372,6 +367,7 @@ hr.fc-divider {
|
|||
|
||||
.fc table {
|
||||
width: 100%;
|
||||
box-sizing: border-box; /* fix scrollbar issue in firefox */
|
||||
table-layout: fixed;
|
||||
border-collapse: collapse;
|
||||
border-spacing: 0;
|
||||
|
|
@ -395,6 +391,18 @@ hr.fc-divider {
|
|||
}
|
||||
|
||||
|
||||
/* Internal Nav Links
|
||||
--------------------------------------------------------------------------------------------------*/
|
||||
|
||||
a[data-goto] {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
a[data-goto]:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
|
||||
/* Fake Table Rows
|
||||
--------------------------------------------------------------------------------------------------*/
|
||||
|
||||
|
|
@ -491,15 +499,15 @@ temporary rendered events).
|
|||
/* Scrolling Container
|
||||
--------------------------------------------------------------------------------------------------*/
|
||||
|
||||
.fc-scroller { /* this class goes on elements for guaranteed vertical scrollbars */
|
||||
overflow-y: scroll;
|
||||
overflow-x: hidden;
|
||||
.fc-scroller {
|
||||
-webkit-overflow-scrolling: touch;
|
||||
}
|
||||
|
||||
.fc-scroller > * { /* we expect an immediate inner element */
|
||||
/* TODO: move to agenda/basic */
|
||||
.fc-scroller > .fc-day-grid,
|
||||
.fc-scroller > .fc-time-grid {
|
||||
position: relative; /* re-scope all positions */
|
||||
width: 100%; /* hack to force re-sizing this inner element when scrollbars appear/disappear */
|
||||
overflow: hidden; /* don't let negative margins or absolute positioning create further scroll */
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -513,10 +521,14 @@ temporary rendered events).
|
|||
line-height: 1.3;
|
||||
border-radius: 3px;
|
||||
border: 1px solid #3a87ad; /* default BORDER color */
|
||||
background-color: #3a87ad; /* default BACKGROUND color */
|
||||
font-weight: normal; /* undo jqui's ui-widget-header bold */
|
||||
}
|
||||
|
||||
.fc-event,
|
||||
.fc-event-dot {
|
||||
background-color: #3a87ad; /* default BACKGROUND color */
|
||||
}
|
||||
|
||||
/* overpower some of bootstrap's and jqui's styles on <a> tags */
|
||||
.fc-event,
|
||||
.fc-event:hover,
|
||||
|
|
@ -539,7 +551,6 @@ temporary rendered events).
|
|||
z-index: 1;
|
||||
background: #fff;
|
||||
opacity: .25;
|
||||
filter: alpha(opacity=25); /* for IE */
|
||||
}
|
||||
|
||||
.fc-event .fc-content {
|
||||
|
|
@ -547,15 +558,68 @@ temporary rendered events).
|
|||
z-index: 2;
|
||||
}
|
||||
|
||||
/* resizer (cursor AND touch devices) */
|
||||
|
||||
.fc-event .fc-resizer {
|
||||
position: absolute;
|
||||
z-index: 3;
|
||||
z-index: 4;
|
||||
}
|
||||
|
||||
/* resizer (touch devices) */
|
||||
|
||||
.fc-event .fc-resizer {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.fc-event.fc-allow-mouse-resize .fc-resizer,
|
||||
.fc-event.fc-selected .fc-resizer {
|
||||
/* only show when hovering or selected (with touch) */
|
||||
display: block;
|
||||
}
|
||||
|
||||
/* hit area */
|
||||
|
||||
.fc-event.fc-selected .fc-resizer:before {
|
||||
/* 40x40 touch area */
|
||||
content: "";
|
||||
position: absolute;
|
||||
z-index: 9999; /* user of this util can scope within a lower z-index */
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
margin-left: -20px;
|
||||
margin-top: -20px;
|
||||
}
|
||||
|
||||
|
||||
/* Event Selection (only for touch devices)
|
||||
--------------------------------------------------------------------------------------------------*/
|
||||
|
||||
.fc-event.fc-selected {
|
||||
z-index: 9999 !important; /* overcomes inline z-index */
|
||||
box-shadow: 0 2px 5px rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
|
||||
.fc-event.fc-selected.fc-dragging {
|
||||
box-shadow: 0 2px 7px rgba(0, 0, 0, 0.3);
|
||||
}
|
||||
|
||||
|
||||
/* Horizontal Events
|
||||
--------------------------------------------------------------------------------------------------*/
|
||||
|
||||
/* bigger touch area when selected */
|
||||
.fc-h-event.fc-selected:before {
|
||||
content: "";
|
||||
position: absolute;
|
||||
z-index: 3; /* below resizers */
|
||||
top: -10px;
|
||||
bottom: -10px;
|
||||
left: 0;
|
||||
right: 0;
|
||||
}
|
||||
|
||||
/* events that are continuing to/from another week. kill rounded corners and butt up against edge */
|
||||
|
||||
.fc-ltr .fc-h-event.fc-not-start,
|
||||
|
|
@ -576,36 +640,56 @@ temporary rendered events).
|
|||
border-bottom-right-radius: 0;
|
||||
}
|
||||
|
||||
/* resizer */
|
||||
|
||||
.fc-h-event .fc-resizer { /* positioned it to overcome the event's borders */
|
||||
top: -1px;
|
||||
bottom: -1px;
|
||||
left: -1px;
|
||||
right: -1px;
|
||||
width: 5px;
|
||||
}
|
||||
/* resizer (cursor AND touch devices) */
|
||||
|
||||
/* left resizer */
|
||||
.fc-ltr .fc-h-event .fc-start-resizer,
|
||||
.fc-ltr .fc-h-event .fc-start-resizer:before,
|
||||
.fc-ltr .fc-h-event .fc-start-resizer:after,
|
||||
.fc-rtl .fc-h-event .fc-end-resizer,
|
||||
.fc-rtl .fc-h-event .fc-end-resizer:before,
|
||||
.fc-rtl .fc-h-event .fc-end-resizer:after {
|
||||
right: auto; /* ignore the right and only use the left */
|
||||
.fc-rtl .fc-h-event .fc-end-resizer {
|
||||
cursor: w-resize;
|
||||
left: -1px; /* overcome border */
|
||||
}
|
||||
|
||||
/* right resizer */
|
||||
.fc-ltr .fc-h-event .fc-end-resizer,
|
||||
.fc-ltr .fc-h-event .fc-end-resizer:before,
|
||||
.fc-ltr .fc-h-event .fc-end-resizer:after,
|
||||
.fc-rtl .fc-h-event .fc-start-resizer,
|
||||
.fc-rtl .fc-h-event .fc-start-resizer:before,
|
||||
.fc-rtl .fc-h-event .fc-start-resizer:after {
|
||||
left: auto; /* ignore the left and only use the right */
|
||||
.fc-rtl .fc-h-event .fc-start-resizer {
|
||||
cursor: e-resize;
|
||||
right: -1px; /* overcome border */
|
||||
}
|
||||
|
||||
/* resizer (mouse devices) */
|
||||
|
||||
.fc-h-event.fc-allow-mouse-resize .fc-resizer {
|
||||
width: 7px;
|
||||
top: -1px; /* overcome top border */
|
||||
bottom: -1px; /* overcome bottom border */
|
||||
}
|
||||
|
||||
/* resizer (touch devices) */
|
||||
|
||||
.fc-h-event.fc-selected .fc-resizer {
|
||||
/* 8x8 little dot */
|
||||
border-radius: 4px;
|
||||
border-width: 1px;
|
||||
width: 6px;
|
||||
height: 6px;
|
||||
border-style: solid;
|
||||
border-color: inherit;
|
||||
background: #fff;
|
||||
/* vertically center */
|
||||
top: 50%;
|
||||
margin-top: -4px;
|
||||
}
|
||||
|
||||
/* left resizer */
|
||||
.fc-ltr .fc-h-event.fc-selected .fc-start-resizer,
|
||||
.fc-rtl .fc-h-event.fc-selected .fc-end-resizer {
|
||||
margin-left: -4px; /* centers the 8x8 dot on the left edge */
|
||||
}
|
||||
|
||||
/* right resizer */
|
||||
.fc-ltr .fc-h-event.fc-selected .fc-end-resizer,
|
||||
.fc-rtl .fc-h-event.fc-selected .fc-start-resizer {
|
||||
margin-right: -4px; /* centers the 8x8 dot on the right edge */
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -620,6 +704,23 @@ be a descendant of the grid when it is being dragged.
|
|||
padding: 0 1px;
|
||||
}
|
||||
|
||||
tr:first-child > td > .fc-day-grid-event {
|
||||
margin-top: 2px; /* a little bit more space before the first event */
|
||||
}
|
||||
|
||||
.fc-day-grid-event.fc-selected:after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
z-index: 1; /* same z-index as fc-bg, behind text */
|
||||
/* overcome the borders */
|
||||
top: -1px;
|
||||
right: -1px;
|
||||
bottom: -1px;
|
||||
left: -1px;
|
||||
/* darkening effect */
|
||||
background: #000;
|
||||
opacity: .25;
|
||||
}
|
||||
|
||||
.fc-day-grid-event .fc-content { /* force events to be one-line tall */
|
||||
white-space: nowrap;
|
||||
|
|
@ -630,10 +731,18 @@ be a descendant of the grid when it is being dragged.
|
|||
font-weight: bold;
|
||||
}
|
||||
|
||||
.fc-day-grid-event .fc-resizer { /* enlarge the default hit area */
|
||||
left: -3px;
|
||||
right: -3px;
|
||||
width: 7px;
|
||||
/* resizer (cursor devices) */
|
||||
|
||||
/* left resizer */
|
||||
.fc-ltr .fc-day-grid-event.fc-allow-mouse-resize .fc-start-resizer,
|
||||
.fc-rtl .fc-day-grid-event.fc-allow-mouse-resize .fc-end-resizer {
|
||||
margin-left: -2px; /* to the day cell's edge */
|
||||
}
|
||||
|
||||
/* right resizer */
|
||||
.fc-ltr .fc-day-grid-event.fc-allow-mouse-resize .fc-end-resizer,
|
||||
.fc-rtl .fc-day-grid-event.fc-allow-mouse-resize .fc-start-resizer {
|
||||
margin-right: -2px; /* to the day cell's edge */
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -672,14 +781,46 @@ a.fc-more:hover {
|
|||
padding: 10px;
|
||||
}
|
||||
|
||||
|
||||
/* Now Indicator
|
||||
--------------------------------------------------------------------------------------------------*/
|
||||
|
||||
.fc-now-indicator {
|
||||
position: absolute;
|
||||
border: 0 solid red;
|
||||
}
|
||||
|
||||
|
||||
/* Utilities
|
||||
--------------------------------------------------------------------------------------------------*/
|
||||
|
||||
.fc-unselectable {
|
||||
-webkit-user-select: none;
|
||||
-khtml-user-select: none;
|
||||
-moz-user-select: none;
|
||||
-ms-user-select: none;
|
||||
user-select: none;
|
||||
-webkit-touch-callout: none;
|
||||
-webkit-tap-highlight-color: rgba(0, 0, 0, 0);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/* Toolbar
|
||||
--------------------------------------------------------------------------------------------------*/
|
||||
|
||||
.fc-toolbar {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.fc-toolbar.fc-header-toolbar {
|
||||
margin-bottom: 1em;
|
||||
}
|
||||
|
||||
.fc-toolbar.fc-footer-toolbar {
|
||||
margin-top: 1em;
|
||||
}
|
||||
|
||||
.fc-toolbar .fc-left {
|
||||
float: left;
|
||||
}
|
||||
|
|
@ -753,6 +894,8 @@ a.fc-more:hover {
|
|||
z-index: 1;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/* BasicView
|
||||
--------------------------------------------------------------------------------------------------*/
|
||||
|
||||
|
|
@ -760,8 +903,7 @@ a.fc-more:hover {
|
|||
|
||||
.fc-basicWeek-view .fc-content-skeleton,
|
||||
.fc-basicDay-view .fc-content-skeleton {
|
||||
/* we are sure there are no day numbers in these views, so... */
|
||||
padding-top: 1px; /* add a pixel to make sure there are 2px padding above events */
|
||||
/* there may be week numbers in these views, so no padding-top */
|
||||
padding-bottom: 1em; /* ensure a space at bottom of cell for user selecting/clicking */
|
||||
}
|
||||
|
||||
|
|
@ -784,42 +926,45 @@ a.fc-more:hover {
|
|||
|
||||
/* week and day number styling */
|
||||
|
||||
.fc-day-top.fc-other-month {
|
||||
opacity: 0.3;
|
||||
}
|
||||
|
||||
.fc-basic-view .fc-week-number,
|
||||
.fc-basic-view .fc-day-number {
|
||||
padding: 0 2px;
|
||||
padding: 2px;
|
||||
}
|
||||
|
||||
.fc-basic-view td.fc-week-number span,
|
||||
.fc-basic-view td.fc-day-number {
|
||||
padding-top: 2px;
|
||||
padding-bottom: 2px;
|
||||
.fc-basic-view th.fc-week-number,
|
||||
.fc-basic-view th.fc-day-number {
|
||||
padding: 0 2px; /* column headers can't have as much v space */
|
||||
}
|
||||
|
||||
.fc-basic-view .fc-week-number {
|
||||
.fc-ltr .fc-basic-view .fc-day-top .fc-day-number { float: right; }
|
||||
.fc-rtl .fc-basic-view .fc-day-top .fc-day-number { float: left; }
|
||||
|
||||
.fc-ltr .fc-basic-view .fc-day-top .fc-week-number { float: left; border-radius: 0 0 3px 0; }
|
||||
.fc-rtl .fc-basic-view .fc-day-top .fc-week-number { float: right; border-radius: 0 0 0 3px; }
|
||||
|
||||
.fc-basic-view .fc-day-top .fc-week-number {
|
||||
min-width: 1.5em;
|
||||
text-align: center;
|
||||
background-color: #f2f2f2;
|
||||
color: #808080;
|
||||
}
|
||||
|
||||
/* when week/day number have own column */
|
||||
|
||||
.fc-basic-view td.fc-week-number {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.fc-basic-view .fc-week-number span {
|
||||
.fc-basic-view td.fc-week-number > * {
|
||||
/* work around the way we do column resizing and ensure a minimum width */
|
||||
display: inline-block;
|
||||
min-width: 1.25em;
|
||||
}
|
||||
|
||||
.fc-ltr .fc-basic-view .fc-day-number {
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.fc-rtl .fc-basic-view .fc-day-number {
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.fc-day-number.fc-other-month {
|
||||
opacity: 0.3;
|
||||
filter: alpha(opacity=30); /* for IE */
|
||||
/* opacity with small font can sometimes look too faded
|
||||
might want to set the 'color' property instead
|
||||
making day-numbers bold also fixes the problem */
|
||||
}
|
||||
|
||||
/* AgendaView all-day area
|
||||
--------------------------------------------------------------------------------------------------*/
|
||||
|
|
@ -834,7 +979,6 @@ a.fc-more:hover {
|
|||
}
|
||||
|
||||
.fc-agenda-view .fc-day-grid .fc-row .fc-content-skeleton {
|
||||
padding-top: 1px; /* add a pixel to make sure there are 2px padding above events */
|
||||
padding-bottom: 1em; /* give space underneath events for clicking/selecting days */
|
||||
}
|
||||
|
||||
|
|
@ -888,27 +1032,46 @@ a.fc-more:hover {
|
|||
z-index: 2;
|
||||
}
|
||||
|
||||
.fc-time-grid .fc-bgevent-skeleton,
|
||||
.fc-time-grid .fc-content-col {
|
||||
position: relative; /* because now-indicator lives directly inside */
|
||||
}
|
||||
|
||||
.fc-time-grid .fc-content-skeleton {
|
||||
position: absolute;
|
||||
z-index: 3;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
}
|
||||
|
||||
.fc-time-grid .fc-bgevent-skeleton {
|
||||
/* divs within a cell within the fc-content-skeleton */
|
||||
|
||||
.fc-time-grid .fc-business-container {
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.fc-time-grid .fc-bgevent-container {
|
||||
position: relative;
|
||||
z-index: 2;
|
||||
}
|
||||
|
||||
.fc-time-grid .fc-highlight-container {
|
||||
position: relative;
|
||||
z-index: 3;
|
||||
}
|
||||
|
||||
.fc-time-grid .fc-highlight-skeleton {
|
||||
.fc-time-grid .fc-event-container {
|
||||
position: relative;
|
||||
z-index: 4;
|
||||
}
|
||||
|
||||
.fc-time-grid .fc-content-skeleton {
|
||||
.fc-time-grid .fc-now-indicator-line {
|
||||
z-index: 5;
|
||||
}
|
||||
|
||||
.fc-time-grid .fc-helper-skeleton {
|
||||
.fc-time-grid .fc-helper-container { /* also is fc-event-container */
|
||||
position: relative;
|
||||
z-index: 6;
|
||||
}
|
||||
|
||||
|
|
@ -948,11 +1111,6 @@ a.fc-more:hover {
|
|||
/* TimeGrid Event Containment
|
||||
--------------------------------------------------------------------------------------------------*/
|
||||
|
||||
.fc-time-grid .fc-event-container, /* a div within a cell within the fc-content-skeleton */
|
||||
.fc-time-grid .fc-bgevent-container { /* a div within a cell within the fc-bgevent-skeleton */
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.fc-ltr .fc-time-grid .fc-event-container { /* space on the sides of events for LTR (default) */
|
||||
margin: 0 2.5% 0 2px;
|
||||
}
|
||||
|
|
@ -1008,6 +1166,20 @@ be a descendant of the grid when it is being dragged.
|
|||
overflow: hidden; /* don't let the bg flow over rounded corners */
|
||||
}
|
||||
|
||||
.fc-time-grid-event.fc-selected {
|
||||
/* need to allow touch resizers to extend outside event's bounding box */
|
||||
/* common fc-selected styles hide the fc-bg, so don't need this anyway */
|
||||
overflow: visible;
|
||||
}
|
||||
|
||||
.fc-time-grid-event.fc-selected .fc-bg {
|
||||
display: none; /* hide semi-white background, to appear darker */
|
||||
}
|
||||
|
||||
.fc-time-grid-event .fc-content {
|
||||
overflow: hidden; /* for when .fc-selected */
|
||||
}
|
||||
|
||||
.fc-time-grid-event .fc-time,
|
||||
.fc-time-grid-event .fc-title {
|
||||
padding: 0 1px;
|
||||
|
|
@ -1049,9 +1221,9 @@ be a descendant of the grid when it is being dragged.
|
|||
padding: 0; /* undo padding from above */
|
||||
}
|
||||
|
||||
/* resizer */
|
||||
/* resizer (cursor device) */
|
||||
|
||||
.fc-time-grid-event .fc-resizer {
|
||||
.fc-time-grid-event.fc-allow-mouse-resize .fc-resizer {
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
|
|
@ -1064,6 +1236,169 @@ be a descendant of the grid when it is being dragged.
|
|||
cursor: s-resize;
|
||||
}
|
||||
|
||||
.fc-time-grid-event .fc-resizer:after {
|
||||
.fc-time-grid-event.fc-allow-mouse-resize .fc-resizer:after {
|
||||
content: "=";
|
||||
}
|
||||
|
||||
/* resizer (touch device) */
|
||||
|
||||
.fc-time-grid-event.fc-selected .fc-resizer {
|
||||
/* 10x10 dot */
|
||||
border-radius: 5px;
|
||||
border-width: 1px;
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
border-style: solid;
|
||||
border-color: inherit;
|
||||
background: #fff;
|
||||
/* horizontally center */
|
||||
left: 50%;
|
||||
margin-left: -5px;
|
||||
/* center on the bottom edge */
|
||||
bottom: -5px;
|
||||
}
|
||||
|
||||
|
||||
/* Now Indicator
|
||||
--------------------------------------------------------------------------------------------------*/
|
||||
|
||||
.fc-time-grid .fc-now-indicator-line {
|
||||
border-top-width: 1px;
|
||||
left: 0;
|
||||
right: 0;
|
||||
}
|
||||
|
||||
/* arrow on axis */
|
||||
|
||||
.fc-time-grid .fc-now-indicator-arrow {
|
||||
margin-top: -5px; /* vertically center on top coordinate */
|
||||
}
|
||||
|
||||
.fc-ltr .fc-time-grid .fc-now-indicator-arrow {
|
||||
left: 0;
|
||||
/* triangle pointing right... */
|
||||
border-width: 5px 0 5px 6px;
|
||||
border-top-color: transparent;
|
||||
border-bottom-color: transparent;
|
||||
}
|
||||
|
||||
.fc-rtl .fc-time-grid .fc-now-indicator-arrow {
|
||||
right: 0;
|
||||
/* triangle pointing left... */
|
||||
border-width: 5px 6px 5px 0;
|
||||
border-top-color: transparent;
|
||||
border-bottom-color: transparent;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/* List View
|
||||
--------------------------------------------------------------------------------------------------*/
|
||||
|
||||
/* possibly reusable */
|
||||
|
||||
.fc-event-dot {
|
||||
display: inline-block;
|
||||
width: 10px;
|
||||
height: 10px;
|
||||
border-radius: 5px;
|
||||
}
|
||||
|
||||
/* view wrapper */
|
||||
|
||||
.fc-rtl .fc-list-view {
|
||||
direction: rtl; /* unlike core views, leverage browser RTL */
|
||||
}
|
||||
|
||||
.fc-list-view {
|
||||
border-width: 1px;
|
||||
border-style: solid;
|
||||
}
|
||||
|
||||
/* table resets */
|
||||
|
||||
.fc .fc-list-table {
|
||||
table-layout: auto; /* for shrinkwrapping cell content */
|
||||
}
|
||||
|
||||
.fc-list-table td {
|
||||
border-width: 1px 0 0;
|
||||
padding: 8px 14px;
|
||||
}
|
||||
|
||||
.fc-list-table tr:first-child td {
|
||||
border-top-width: 0;
|
||||
}
|
||||
|
||||
/* day headings with the list */
|
||||
|
||||
.fc-list-heading {
|
||||
border-bottom-width: 1px;
|
||||
}
|
||||
|
||||
.fc-list-heading td {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.fc-ltr .fc-list-heading-main { float: left; }
|
||||
.fc-ltr .fc-list-heading-alt { float: right; }
|
||||
|
||||
.fc-rtl .fc-list-heading-main { float: right; }
|
||||
.fc-rtl .fc-list-heading-alt { float: left; }
|
||||
|
||||
/* event list items */
|
||||
|
||||
.fc-list-item.fc-has-url {
|
||||
cursor: pointer; /* whole row will be clickable */
|
||||
}
|
||||
|
||||
.fc-list-item:hover td {
|
||||
background-color: #f5f5f5;
|
||||
}
|
||||
|
||||
.fc-list-item-marker,
|
||||
.fc-list-item-time {
|
||||
white-space: nowrap;
|
||||
width: 1px;
|
||||
}
|
||||
|
||||
/* make the dot closer to the event title */
|
||||
.fc-ltr .fc-list-item-marker { padding-right: 0; }
|
||||
.fc-rtl .fc-list-item-marker { padding-left: 0; }
|
||||
|
||||
.fc-list-item-title a {
|
||||
/* every event title cell has an <a> tag */
|
||||
text-decoration: none;
|
||||
color: inherit;
|
||||
}
|
||||
|
||||
.fc-list-item-title a[href]:hover {
|
||||
/* hover effect only on titles with hrefs */
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
/* message when no events */
|
||||
|
||||
.fc-list-empty-wrap2 {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
}
|
||||
|
||||
.fc-list-empty-wrap1 {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
display: table;
|
||||
}
|
||||
|
||||
.fc-list-empty {
|
||||
display: table-cell;
|
||||
vertical-align: middle;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.fc-unthemed .fc-list-empty { /* theme will provide own background */
|
||||
background-color: #eee;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -21,7 +21,7 @@ Handlebars.registerHelper('StatusLevel', function() {
|
|||
var start = moment(this.airDateUtc);
|
||||
var end = moment(this.end);
|
||||
var monitored = this.series.monitored && this.monitored;
|
||||
|
||||
debugger;
|
||||
if (hasFile) {
|
||||
return 'success';
|
||||
}
|
||||
|
|
@ -63,4 +63,4 @@ Handlebars.registerHelper('EpisodeProgressClass', function() {
|
|||
}
|
||||
|
||||
return 'progress-bar-warning';
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -51,6 +51,10 @@ Handlebars.registerHelper('tmdbUrl', function() {
|
|||
return 'https://www.themoviedb.org/movie/' + this.tmdbId;
|
||||
});
|
||||
|
||||
Handlebars.registerHelper('youTubeTrailerUrl', function() {
|
||||
return 'https://www.youtube.com/watch?v=' + this.youTubeTrailerId;
|
||||
});
|
||||
|
||||
Handlebars.registerHelper('homepage', function() {
|
||||
return this.website;
|
||||
});
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
|
|
@ -6,6 +6,9 @@
|
|||
<span class="label label-info">{{network}}</span>
|
||||
{{/if}}
|
||||
|
||||
{{#if studio}}
|
||||
<span class="label label-info">{{studio}}</span>
|
||||
{{/if}}
|
||||
<span class="label label-info">{{runtime}} minutes</span>
|
||||
<span class="label label-info">{{path}}</span>
|
||||
|
||||
|
|
@ -33,6 +36,10 @@
|
|||
{{#if imdbId}}
|
||||
<a href="{{imdbUrl}}" class="label label-info">IMDB</a>
|
||||
{{/if}}
|
||||
|
||||
{{#if youTubeTrailerId}}
|
||||
<a href="{{youTubeTrailerUrl}}" class="label label-info">Trailer</a>
|
||||
{{/if}}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -109,7 +109,7 @@ module.exports = Marionette.Layout.extend({
|
|||
element : this.ui.rename,
|
||||
command : {
|
||||
name : 'renameMovieFiles',
|
||||
movieId : this.model.id,
|
||||
movieId : this.model.id,
|
||||
seasonNumber : -1
|
||||
}
|
||||
});
|
||||
|
|
|
|||
|
|
@ -11,9 +11,9 @@
|
|||
|
||||
Are you sure you want to update all files in the {{numberOfMovies}} selected movies?
|
||||
|
||||
{{debug}}
|
||||
|
||||
<ul class="selected-series">
|
||||
{{#each movie}}
|
||||
{{#each movies}}
|
||||
<li>{{title}}</li>
|
||||
{{/each}}
|
||||
</ul>
|
||||
|
|
|
|||
|
|
@ -54,6 +54,10 @@
|
|||
{{#if imdbId}}
|
||||
<a href="{{imdbUrl}}" class="label label-info">IMDB</a>
|
||||
{{/if}}
|
||||
|
||||
{{#if youTubeTrailerId}}
|
||||
<a href="{{youTubeTrailerUrl}}" class="label label-info">Trailer</a>
|
||||
{{/if}}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -18,7 +18,7 @@
|
|||
|
||||
<div class="center">
|
||||
<div class="labels">
|
||||
|
||||
<span class="label label-{{DownloadedStatusColor}}" title="{{DownloadedQuality}}">{{DownloadedStatus}}</span>
|
||||
{{#if website}}
|
||||
<a href="{{homepage}}" class="label label-info">Homepage</a>
|
||||
{{/if}}
|
||||
|
|
@ -26,7 +26,9 @@
|
|||
{{#if imdbId}}
|
||||
<a href="{{imdbUrl}}" class="label label-info">IMDB</a>
|
||||
{{/if}}
|
||||
|
||||
{{#if youTubeTrailerId}}
|
||||
<a href="{{youTubeTrailerUrl}}" class="label label-info">Trailer</a>
|
||||
{{/if}}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -6,33 +6,33 @@ var MoviesDetailsLayout = require('./Details/MoviesDetailsLayout');
|
|||
var SeriesDetailsLayout = require('../Series/Details/SeriesDetailsLayout');
|
||||
|
||||
module.exports = NzbDroneController.extend({
|
||||
_originalInit : NzbDroneController.prototype.initialize,
|
||||
_originalInit : NzbDroneController.prototype.initialize,
|
||||
|
||||
initialize : function() {
|
||||
this.route('', this.series);
|
||||
this.route('movies', this.series);
|
||||
this.route('movies/:query', this.seriesDetails);
|
||||
initialize : function() {
|
||||
this.route('', this.series);
|
||||
this.route('movies', this.series);
|
||||
this.route('movies/:query', this.seriesDetails);
|
||||
|
||||
this._originalInit.apply(this, arguments);
|
||||
},
|
||||
this._originalInit.apply(this, arguments);
|
||||
},
|
||||
|
||||
series : function() {
|
||||
this.setTitle('Movies');
|
||||
this.showMainRegion(new MoviesIndexLayout());
|
||||
},
|
||||
series : function() {
|
||||
this.setTitle('Movies');
|
||||
this.showMainRegion(new MoviesIndexLayout());
|
||||
},
|
||||
|
||||
seriesDetails : function(query) {
|
||||
var series = MoviesCollection.where({ titleSlug : query });
|
||||
if (series.length !== 0) {
|
||||
var targetMovie = series[0];
|
||||
console.log(AppLayout.mainRegion);
|
||||
seriesDetails : function(query) {
|
||||
var series = MoviesCollection.where({ titleSlug : query });
|
||||
if (series.length !== 0) {
|
||||
var targetMovie = series[0];
|
||||
console.log(AppLayout.mainRegion);
|
||||
|
||||
this.setTitle(targetMovie.get('title'));
|
||||
//this.showNotFound();
|
||||
//this.showMainRegion(new SeriesDetailsLayout({model : targetMovie}));
|
||||
this.showMainRegion(new MoviesDetailsLayout({ model : targetMovie }));
|
||||
} else {
|
||||
this.showNotFound();
|
||||
}
|
||||
}
|
||||
this.setTitle(targetMovie.get('title'));
|
||||
//this.showNotFound();
|
||||
//this.showMainRegion(new SeriesDetailsLayout({model : targetMovie}));
|
||||
this.showMainRegion(new MoviesDetailsLayout({ model : targetMovie }));
|
||||
} else {
|
||||
this.showNotFound();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
|
|
|||
|
|
@ -126,7 +126,7 @@
|
|||
.card;
|
||||
.clickable;
|
||||
margin-bottom : 20px;
|
||||
height : 324px;
|
||||
height : 363px;
|
||||
|
||||
.center {
|
||||
display : block;
|
||||
|
|
@ -166,7 +166,7 @@
|
|||
}
|
||||
|
||||
@media (max-width: @screen-xs-max) {
|
||||
height : 235px;
|
||||
height : 283px;
|
||||
margin : 5px;
|
||||
padding : 6px 5px;
|
||||
|
||||
|
|
|
|||
|
|
@ -1,44 +1,44 @@
|
|||
<!-- Static navbar -->
|
||||
<div class="navbar navbar-nzbdrone" role="navigation">
|
||||
<div class="container-fluid">
|
||||
<div class="navbar-header">
|
||||
<button type="button" class="navbar-toggle navbar-inverse" data-toggle="collapse" data-target=".navbar-collapse">
|
||||
<span class="sr-only">Toggle navigation</span>
|
||||
<span class="icon-sonarr-navbar-collapsed fa-lg"></span>
|
||||
</button>
|
||||
<a class="navbar-brand" href="{{UrlBase}}/">
|
||||
<!--<img src="{{UrlBase}}/Content/Images/logo.png?v=2" alt="Radarr">-->
|
||||
<img src="{{UrlBase}}/Content/Images/logos/128.png" class="visible-lg"/>
|
||||
<img src="{{UrlBase}}/Content/Images/logos/64.png" class="visible-md visible-sm"/>
|
||||
<span class="visible-xs">
|
||||
<img src="{{UrlBase}}/Content/Images/logos/32.png"/>
|
||||
<span class="logo-text">Radarr</span>
|
||||
</span>
|
||||
<div class="container-fluid">
|
||||
<div class="navbar-header">
|
||||
<button type="button" class="navbar-toggle navbar-inverse" data-toggle="collapse" data-target=".navbar-collapse">
|
||||
<span class="sr-only">Toggle navigation</span>
|
||||
<span class="icon-sonarr-navbar-collapsed fa-lg"></span>
|
||||
</button>
|
||||
<a class="navbar-brand" href="{{UrlBase}}/">
|
||||
<!--<img src="{{UrlBase}}/Content/Images/logo.png?v=2" alt="Radarr">-->
|
||||
<img src="{{UrlBase}}/Content/Images/logos/128.png" class="visible-lg"/>
|
||||
<img src="{{UrlBase}}/Content/Images/logos/64.png" class="visible-md visible-sm"/>
|
||||
<span class="visible-xs">
|
||||
<img src="{{UrlBase}}/Content/Images/logos/32.png"/>
|
||||
<span class="logo-text">Radarr</span>
|
||||
</span>
|
||||
|
||||
</a>
|
||||
</div>
|
||||
<div class="navbar-collapse collapse x-navbar-collapse">
|
||||
<ul class="nav navbar-nav">
|
||||
<li><a href="{{UrlBase}}/" class="x-series-nav"><i class="icon-sonarr-navbar-icon icon-sonarr-navbar-series"></i> Movies</a></li>
|
||||
<li><a href="{{UrlBase}}/calendar" class="x-calendar-nav"><i class="icon-sonarr-navbar-icon icon-sonarr-navbar-calendar"></i> Calendar</a></li>
|
||||
<li><a href="{{UrlBase}}/activity" class="x-activity-nav"><i class="icon-sonarr-navbar-icon icon-sonarr-navbar-activity"></i> Activity<span id="x-queue-count" class="navbar-info"></span></a></li>
|
||||
<li><a href="{{UrlBase}}/wanted" class="x-wanted-nav"><i class="icon-sonarr-navbar-icon icon-sonarr-navbar-wanted"></i> Wanted</a></li>
|
||||
<li><a href="{{UrlBase}}/settings" class="x-settings-nav"><i class="icon-sonarr-navbar-icon icon-sonarr-navbar-settings"></i> Settings</a></li>
|
||||
<li><a href="{{UrlBase}}/system" class="x-system-nav"><i class="icon-sonarr-navbar-icon icon-sonarr-navbar-system"></i> System<span id="x-health" class="navbar-info"></span></a></li>
|
||||
<li><a href="https://sonarr.tv/donate" target="_blank"><i class="icon-sonarr-navbar-icon icon-sonarr-navbar-donate"></i> Donate</a></li>
|
||||
</ul>
|
||||
<ul class="nav navbar-nav navbar-right">
|
||||
<li class="active screen-size"></li>
|
||||
</ul>
|
||||
</div><!--/.nav-collapse -->
|
||||
</div><!--/.container-fluid -->
|
||||
</a>
|
||||
</div>
|
||||
<div class="navbar-collapse collapse x-navbar-collapse">
|
||||
<ul class="nav navbar-nav">
|
||||
<li><a href="{{UrlBase}}/" class="x-series-nav"><i class="icon-sonarr-navbar-icon icon-sonarr-navbar-series"></i> Movies</a></li>
|
||||
<li><a href="{{UrlBase}}/calendar" class="x-calendar-nav"><i class="icon-sonarr-navbar-icon icon-sonarr-navbar-calendar"></i> Calendar</a></li>
|
||||
<li><a href="{{UrlBase}}/activity" class="x-activity-nav"><i class="icon-sonarr-navbar-icon icon-sonarr-navbar-activity"></i> Activity<span id="x-queue-count" class="navbar-info"></span></a></li>
|
||||
<li><a href="{{UrlBase}}/wanted" class="x-wanted-nav"><i class="icon-sonarr-navbar-icon icon-sonarr-navbar-wanted"></i> Wanted</a></li>
|
||||
<li><a href="{{UrlBase}}/settings" class="x-settings-nav"><i class="icon-sonarr-navbar-icon icon-sonarr-navbar-settings"></i> Settings</a></li>
|
||||
<li><a href="{{UrlBase}}/system" class="x-system-nav"><i class="icon-sonarr-navbar-icon icon-sonarr-navbar-system"></i> System<span id="x-health" class="navbar-info"></span></a></li>
|
||||
<li><a href="https://radarr.video/donate.html" target="_blank"><i class="icon-sonarr-navbar-icon icon-sonarr-navbar-donate"></i> Donate</a></li>
|
||||
</ul>
|
||||
<ul class="nav navbar-nav navbar-right">
|
||||
<li class="active screen-size"></li>
|
||||
</ul>
|
||||
</div><!--/.nav-collapse -->
|
||||
</div><!--/.container-fluid -->
|
||||
|
||||
<div class="col-md-12 search">
|
||||
<div class="col-md-6 col-md-offset-3">
|
||||
<div class="input-group">
|
||||
<span class="input-group-addon"><i class="fa fa-search"></i></span>
|
||||
<input type="text" class="col-md-6 form-control x-series-search" placeholder="Search the movies in your library">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-12 search">
|
||||
<div class="col-md-6 col-md-offset-3">
|
||||
<div class="input-group">
|
||||
<span class="input-group-addon"><i class="fa fa-search"></i></span>
|
||||
<input type="text" class="col-md-6 form-control x-series-search" placeholder="Search the movies in your library">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -25,7 +25,9 @@ $.fn.bindSearch = function() {
|
|||
minLength : 1
|
||||
}, {
|
||||
name : 'series',
|
||||
displayKey : 'title',
|
||||
displayKey : function(series) {
|
||||
return series.title + ' (' + series.year + ')';
|
||||
},
|
||||
source : substringMatcher()
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -1,43 +1,8 @@
|
|||
// var Backbone = require('backbone');
|
||||
// var RenamePreviewModel = require('./RenamePreviewModel');
|
||||
|
||||
// module.exports = Backbone.Collection.extend({
|
||||
// url : window.NzbDrone.ApiRoot + '/rename',
|
||||
// model : RenamePreviewModel,
|
||||
|
||||
// originalFetch : Backbone.Collection.prototype.fetch,
|
||||
|
||||
// initialize : function(options) {
|
||||
// if (!options.seriesId) {
|
||||
// throw 'seriesId is required';
|
||||
// }
|
||||
|
||||
// this.seriesId = options.seriesId;
|
||||
// this.seasonNumber = options.seasonNumber;
|
||||
// },
|
||||
|
||||
// fetch : function(options) {
|
||||
// if (!this.seriesId) {
|
||||
// throw 'seriesId is required';
|
||||
// }
|
||||
|
||||
// options = options || {};
|
||||
// options.data = {};
|
||||
// options.data.seriesId = this.seriesId;
|
||||
|
||||
// if (this.seasonNumber !== undefined) {
|
||||
// options.data.seasonNumber = this.seasonNumber;
|
||||
// }
|
||||
|
||||
// return this.originalFetch.call(this, options);
|
||||
// }
|
||||
// });
|
||||
|
||||
var Backbone = require('backbone');
|
||||
var RenamePreviewModel = require('./RenamePreviewModel');
|
||||
|
||||
module.exports = Backbone.Collection.extend({
|
||||
url : window.NzbDrone.ApiRoot + '/rename',
|
||||
url : window.NzbDrone.ApiRoot + '/renameMovie',
|
||||
model : RenamePreviewModel,
|
||||
|
||||
originalFetch : Backbone.Collection.prototype.fetch,
|
||||
|
|
|
|||
|
|
@ -6,10 +6,10 @@ module.exports = Marionette.ItemView.extend({
|
|||
template : 'Rename/RenamePreviewFormatViewTemplate',
|
||||
|
||||
templateHelpers : function() {
|
||||
var type = this.model.get('seriesType');
|
||||
//var type = this.model.get('seriesType');
|
||||
return {
|
||||
rename : this.naming.get('renameEpisodes'),
|
||||
format : this.naming.get(type + 'EpisodeFormat')
|
||||
format : this.naming.get('standardMovieFormat')
|
||||
};
|
||||
},
|
||||
|
||||
|
|
|
|||
|
|
@ -1,98 +1,98 @@
|
|||
<fieldset>
|
||||
<legend>File Management</legend>
|
||||
<legend>File Management</legend>
|
||||
|
||||
<div class="form-group">
|
||||
<label class="col-sm-3 control-label">Ignore Deleted Movies</label>
|
||||
<div class="form-group">
|
||||
<label class="col-sm-3 control-label">Ignore Deleted Movies</label>
|
||||
|
||||
<div class="col-sm-9">
|
||||
<div class="input-group">
|
||||
<label class="checkbox toggle well">
|
||||
<input type="checkbox" name="autoUnmonitorPreviouslyDownloadedEpisodes"/>
|
||||
<p>
|
||||
<span>Yes</span>
|
||||
<span>No</span>
|
||||
</p>
|
||||
<div class="col-sm-9">
|
||||
<div class="input-group">
|
||||
<label class="checkbox toggle well">
|
||||
<input type="checkbox" name="autoUnmonitorPreviouslyDownloadedEpisodes"/>
|
||||
<p>
|
||||
<span>Yes</span>
|
||||
<span>No</span>
|
||||
</p>
|
||||
|
||||
<div class="btn btn-primary slide-button"/>
|
||||
</label>
|
||||
<div class="btn btn-primary slide-button"/>
|
||||
</label>
|
||||
|
||||
<span class="help-inline-checkbox">
|
||||
<i class="icon-sonarr-form-info" title="Movies deleted from disk are automatically unmonitored in Radarr"/>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<span class="help-inline-checkbox">
|
||||
<i class="icon-sonarr-form-info" title="Movies deleted from disk are automatically unmonitored in Radarr"/>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group advanced-setting">
|
||||
<label class="col-sm-3 control-label">Download Propers</label>
|
||||
<div class="form-group advanced-setting">
|
||||
<label class="col-sm-3 control-label">Download Propers</label>
|
||||
|
||||
<div class="col-sm-9">
|
||||
<div class="input-group">
|
||||
<label class="checkbox toggle well">
|
||||
<input type="checkbox" name="autoDownloadPropers"/>
|
||||
<p>
|
||||
<span>Yes</span>
|
||||
<span>No</span>
|
||||
</p>
|
||||
<div class="col-sm-9">
|
||||
<div class="input-group">
|
||||
<label class="checkbox toggle well">
|
||||
<input type="checkbox" name="autoDownloadPropers"/>
|
||||
<p>
|
||||
<span>Yes</span>
|
||||
<span>No</span>
|
||||
</p>
|
||||
|
||||
<div class="btn btn-primary slide-button"/>
|
||||
</label>
|
||||
<div class="btn btn-primary slide-button"/>
|
||||
</label>
|
||||
|
||||
<span class="help-inline-checkbox">
|
||||
<i class="icon-sonarr-form-info" title="Should Radarr automatically upgrade to propers when available?"/>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<span class="help-inline-checkbox">
|
||||
<i class="icon-sonarr-form-info" title="Should Radarr automatically upgrade to propers when available?"/>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group advanced-setting">
|
||||
<label class="col-sm-3 control-label">Analyse video files</label>
|
||||
<div class="form-group advanced-setting">
|
||||
<label class="col-sm-3 control-label">Analyse video files</label>
|
||||
|
||||
<div class="col-sm-9">
|
||||
<div class="input-group">
|
||||
<label class="checkbox toggle well">
|
||||
<input type="checkbox" name="enableMediaInfo"/>
|
||||
<p>
|
||||
<span>Yes</span>
|
||||
<span>No</span>
|
||||
</p>
|
||||
<div class="col-sm-9">
|
||||
<div class="input-group">
|
||||
<label class="checkbox toggle well">
|
||||
<input type="checkbox" name="enableMediaInfo"/>
|
||||
<p>
|
||||
<span>Yes</span>
|
||||
<span>No</span>
|
||||
</p>
|
||||
|
||||
<div class="btn btn-primary slide-button"/>
|
||||
</label>
|
||||
<div class="btn btn-primary slide-button"/>
|
||||
</label>
|
||||
|
||||
<span class="help-inline-checkbox">
|
||||
<i class="icon-sonarr-form-info" title="Extract video information such as resolution, runtime and codec information from files. This requires Radarr to read parts of the file which may cause high disk or network activity during scans."/>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<span class="help-inline-checkbox">
|
||||
<i class="icon-sonarr-form-info" title="Extract video information such as resolution, runtime and codec information from files. This requires Radarr to read parts of the file which may cause high disk or network activity during scans."/>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group advanced-setting">
|
||||
<label class="col-sm-3 control-label">Change File Date</label>
|
||||
<div class="form-group advanced-setting">
|
||||
<label class="col-sm-3 control-label">Change File Date</label>
|
||||
|
||||
<div class="col-sm-1 col-sm-push-2 help-inline">
|
||||
<i class="icon-sonarr-form-info" title="Change file date on import/rescan"/>
|
||||
</div>
|
||||
|
||||
<div class="col-sm-2 col-sm-pull-1">
|
||||
<select class="form-control" name="fileDate">
|
||||
<option value="none">None</option>
|
||||
<option value="localAirDate">Local Air Date</option>
|
||||
<option value="utcAirDate">UTC Air Date</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-sm-1 col-sm-push-2 help-inline">
|
||||
<i class="icon-sonarr-form-info" title="Change file date on import/rescan"/>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label class="col-sm-3 control-label">Recycling Bin</label>
|
||||
<div class="col-sm-2 col-sm-pull-1">
|
||||
<select class="form-control" name="fileDate">
|
||||
<option value="none">None</option>
|
||||
<option value="cinemas">In Cinemas Date</option>
|
||||
<option value="release">Physical Release Date</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-sm-1 col-sm-push-8 help-inline">
|
||||
<i class="icon-sonarr-form-info" title="Episode files will go here when deleted instead of being permanently deleted"/>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="col-sm-3 control-label">Recycling Bin</label>
|
||||
|
||||
<div class="col-sm-8 col-sm-pull-1">
|
||||
<input type="text" name="recycleBin" class="form-control x-path"/>
|
||||
</div>
|
||||
<div class="col-sm-1 col-sm-push-8 help-inline">
|
||||
<i class="icon-sonarr-form-info" title="Episode files will go here when deleted instead of being permanently deleted"/>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<div class="col-sm-8 col-sm-pull-1">
|
||||
<input type="text" name="recycleBin" class="form-control x-path"/>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</fieldset>
|
||||
|
|
|
|||
|
|
@ -72,6 +72,7 @@
|
|||
{{> MediaInfoNamingPartial}}
|
||||
{{> ReleaseGroupNamingPartial}}
|
||||
{{> OriginalTitleNamingPartial}}
|
||||
{{> ImdbIdNamingPartial}}
|
||||
{{> SeparatorNamingPartial}}
|
||||
</ul>
|
||||
</div>
|
||||
|
|
@ -161,6 +162,7 @@
|
|||
<ul class="dropdown-menu">
|
||||
{{> MovieTitleNamingPartial}}
|
||||
{{> ReleaseYearNamingPartial}}
|
||||
{{> ImdbIdNamingPartial}}
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -0,0 +1 @@
|
|||
<li><a href="#" data-token="IMDb Id">IMDb Id</a></li>
|
||||
|
|
@ -4,6 +4,7 @@
|
|||
<li><a href="#" data-token="Movie Title">Movie Title</a></li>
|
||||
<li><a href="#" data-token="Movie.Title">Movie.Title</a></li>
|
||||
<li><a href="#" data-token="Movie_Title">Movie_Title</a></li>
|
||||
<li><a href="#" data-token="Movie TitleThe">Movie Title, The</a></li>
|
||||
<li><a href="#" data-token="Movie CleanTitle">Movie CleanTitle</a></li>
|
||||
<li><a href="#" data-token="Movie.CleanTitle">Movie.CleanTitle</a></li>
|
||||
<li><a href="#" data-token="Movie_CleanTitle">Movie_CleanTitle</a></li>
|
||||
|
|
|
|||
|
|
@ -1,16 +1,16 @@
|
|||
<fieldset>
|
||||
<legend>Quality Definitions</legend>
|
||||
<div class="col-md-11">
|
||||
<div id="quality-definition-list">
|
||||
<div class="quality-header x-header hidden-xs">
|
||||
<div class="row">
|
||||
<span class="col-md-2 col-sm-3">Quality</span>
|
||||
<span class="col-md-2 col-sm-3">Title</span>
|
||||
<span class="col-md-4 col-sm-6">Size Limit <i class="icon-sonarr-info" title="Limits are automatically adjusted for the series runtime and number of episodes in the file." /></span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="rows x-rows">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<legend>Quality Definitions</legend>
|
||||
<div class="col-md-11">
|
||||
<div id="quality-definition-list">
|
||||
<div class="quality-header x-header hidden-xs">
|
||||
<div class="row">
|
||||
<span class="col-md-2 col-sm-3">Quality</span>
|
||||
<span class="col-md-2 col-sm-3">Title</span>
|
||||
<span class="col-md-4 col-sm-6">Size Limit <i class="icon-sonarr-warning" title="Limits are automatically adjusted for the movie runtime." /></span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="rows x-rows">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</fieldset>
|
||||
|
|
|
|||
|
|
@ -4,92 +4,92 @@ require('jquery-ui');
|
|||
var FormatHelpers = require('../../../Shared/FormatHelpers');
|
||||
|
||||
var view = Marionette.ItemView.extend({
|
||||
template : 'Settings/Quality/Definition/QualityDefinitionItemViewTemplate',
|
||||
className : 'row',
|
||||
|
||||
slider : {
|
||||
min : 0,
|
||||
max : 200,
|
||||
step : 0.1
|
||||
},
|
||||
template : 'Settings/Quality/Definition/QualityDefinitionItemViewTemplate',
|
||||
className : 'row',
|
||||
|
||||
ui : {
|
||||
sizeSlider : '.x-slider',
|
||||
thirtyMinuteMinSize : '.x-min-thirty',
|
||||
sixtyMinuteMinSize : '.x-min-sixty',
|
||||
thirtyMinuteMaxSize : '.x-max-thirty',
|
||||
sixtyMinuteMaxSize : '.x-max-sixty'
|
||||
},
|
||||
slider : {
|
||||
min : 0,
|
||||
max : 200,
|
||||
step : 0.1
|
||||
},
|
||||
|
||||
events : {
|
||||
'slide .x-slider' : '_updateSize'
|
||||
},
|
||||
ui : {
|
||||
sizeSlider : '.x-slider',
|
||||
thirtyMinuteMinSize : '.x-min-thirty',
|
||||
sixtyMinuteMinSize : '.x-min-sixty',
|
||||
thirtyMinuteMaxSize : '.x-max-thirty',
|
||||
sixtyMinuteMaxSize : '.x-max-sixty'
|
||||
},
|
||||
|
||||
initialize : function(options) {
|
||||
this.profileCollection = options.profiles;
|
||||
},
|
||||
events : {
|
||||
'slide .x-slider' : '_updateSize'
|
||||
},
|
||||
|
||||
onRender : function() {
|
||||
if (this.model.get('quality').id === 0) {
|
||||
this.$el.addClass('row advanced-setting');
|
||||
}
|
||||
initialize : function(options) {
|
||||
this.profileCollection = options.profiles;
|
||||
},
|
||||
|
||||
this.ui.sizeSlider.slider({
|
||||
range : true,
|
||||
min : this.slider.min,
|
||||
max : this.slider.max,
|
||||
step : this.slider.step,
|
||||
values : [
|
||||
this.model.get('minSize') || this.slider.min,
|
||||
this.model.get('maxSize') || this.slider.max
|
||||
]
|
||||
});
|
||||
onRender : function() {
|
||||
if (this.model.get('quality').id === 0) {
|
||||
this.$el.addClass('row advanced-setting');
|
||||
}
|
||||
|
||||
this._changeSize();
|
||||
},
|
||||
this.ui.sizeSlider.slider({
|
||||
range : true,
|
||||
min : this.slider.min,
|
||||
max : this.slider.max,
|
||||
step : this.slider.step,
|
||||
values : [
|
||||
this.model.get('minSize') || this.slider.min,
|
||||
this.model.get('maxSize') || this.slider.max
|
||||
]
|
||||
});
|
||||
|
||||
_updateSize : function(event, ui) {
|
||||
var minSize = ui.values[0];
|
||||
var maxSize = ui.values[1];
|
||||
|
||||
if (maxSize === this.slider.max) {
|
||||
maxSize = null;
|
||||
}
|
||||
|
||||
this.model.set('minSize', minSize);
|
||||
this.model.set('maxSize', maxSize);
|
||||
this._changeSize();
|
||||
},
|
||||
|
||||
this._changeSize();
|
||||
},
|
||||
_updateSize : function(event, ui) {
|
||||
var minSize = ui.values[0];
|
||||
var maxSize = ui.values[1];
|
||||
|
||||
_changeSize : function() {
|
||||
var minSize = this.model.get('minSize') || this.slider.min;
|
||||
var maxSize = this.model.get('maxSize') || null;
|
||||
{
|
||||
var minBytes = minSize * 1024 * 1024;
|
||||
var minThirty = FormatHelpers.bytes(minBytes * 30, 2);
|
||||
var minSixty = FormatHelpers.bytes(minBytes * 60, 2);
|
||||
if (maxSize === this.slider.max) {
|
||||
maxSize = null;
|
||||
}
|
||||
|
||||
this.ui.thirtyMinuteMinSize.html(minThirty);
|
||||
this.ui.sixtyMinuteMinSize.html(minSixty);
|
||||
}
|
||||
this.model.set('minSize', minSize);
|
||||
this.model.set('maxSize', maxSize);
|
||||
|
||||
{
|
||||
if (maxSize === 0 || maxSize === null) {
|
||||
this.ui.thirtyMinuteMaxSize.html('Unlimited');
|
||||
this.ui.sixtyMinuteMaxSize.html('Unlimited');
|
||||
} else {
|
||||
var maxBytes = maxSize * 1024 * 1024;
|
||||
var maxThirty = FormatHelpers.bytes(maxBytes * 30, 2);
|
||||
var maxSixty = FormatHelpers.bytes(maxBytes * 60, 2);
|
||||
this._changeSize();
|
||||
},
|
||||
|
||||
this.ui.thirtyMinuteMaxSize.html(maxThirty);
|
||||
this.ui.sixtyMinuteMaxSize.html(maxSixty);
|
||||
}
|
||||
}
|
||||
}
|
||||
_changeSize : function() {
|
||||
var minSize = this.model.get('minSize') || this.slider.min;
|
||||
var maxSize = this.model.get('maxSize') || null;
|
||||
{
|
||||
var minBytes = minSize * 1024 * 1024;
|
||||
var minThirty = FormatHelpers.bytes(minBytes * 90, 2);
|
||||
var minSixty = FormatHelpers.bytes(minBytes * 140, 2);
|
||||
|
||||
this.ui.thirtyMinuteMinSize.html(minThirty);
|
||||
this.ui.sixtyMinuteMinSize.html(minSixty);
|
||||
}
|
||||
|
||||
{
|
||||
if (maxSize === 0 || maxSize === null) {
|
||||
this.ui.thirtyMinuteMaxSize.html('Unlimited');
|
||||
this.ui.sixtyMinuteMaxSize.html('Unlimited');
|
||||
} else {
|
||||
var maxBytes = maxSize * 1024 * 1024;
|
||||
var maxThirty = FormatHelpers.bytes(maxBytes * 90, 2);
|
||||
var maxSixty = FormatHelpers.bytes(maxBytes * 140, 2);
|
||||
|
||||
this.ui.thirtyMinuteMaxSize.html(maxThirty);
|
||||
this.ui.sixtyMinuteMaxSize.html(maxSixty);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
view = AsModelBoundView.call(view);
|
||||
|
||||
module.exports = view;
|
||||
module.exports = view;
|
||||
|
|
|
|||
|
|
@ -1,31 +1,31 @@
|
|||
<span class="col-md-2 col-sm-3">
|
||||
{{quality.name}}
|
||||
</span>
|
||||
<span class="col-md-2 col-sm-3">
|
||||
<input type="text" class="form-control" name="title">
|
||||
</span>
|
||||
<span class="col-md-4 col-sm-6">
|
||||
<div class="x-slider"></div>
|
||||
<div class="size-label-wrapper">
|
||||
<div class="pull-left">
|
||||
<span class="label label-warning x-min-thirty"
|
||||
name="thirtyMinuteMinSize"
|
||||
title="Minimum size for a 30 minute episode">
|
||||
</span>
|
||||
<span class="label label-info x-min-sixty"
|
||||
name="sixtyMinuteMinSize"
|
||||
title="Minimum size for a 60 minute episode">
|
||||
</span>
|
||||
</div>
|
||||
<div class="pull-right">
|
||||
<span class="label label-warning x-max-thirty"
|
||||
name="thirtyMinuteMaxSize"
|
||||
title="Maximum size for a 30 minute episode">
|
||||
</span>
|
||||
<span class="label label-info x-max-sixty"
|
||||
name="sixtyMinuteMaxSize"
|
||||
title="Maximum size for a 60 minute episode">
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</span>
|
||||
<span class="col-md-2 col-sm-3">
|
||||
{{quality.name}}
|
||||
</span>
|
||||
<span class="col-md-2 col-sm-3">
|
||||
<input type="text" class="form-control" name="title">
|
||||
</span>
|
||||
<span class="col-md-4 col-sm-6">
|
||||
<div class="x-slider"></div>
|
||||
<div class="size-label-wrapper">
|
||||
<div class="pull-left">
|
||||
<span class="label label-warning x-min-thirty"
|
||||
name="thirtyMinuteMinSize"
|
||||
title="Minimum size for a 90 minute episode">
|
||||
</span>
|
||||
<span class="label label-info x-min-sixty"
|
||||
name="sixtyMinuteMinSize"
|
||||
title="Minimum size for a 140 minute episode">
|
||||
</span>
|
||||
</div>
|
||||
<div class="pull-right">
|
||||
<span class="label label-warning x-max-thirty"
|
||||
name="thirtyMinuteMaxSize"
|
||||
title="Maximum size for a 90 minute movie">
|
||||
</span>
|
||||
<span class="label label-info x-max-sixty"
|
||||
name="sixtyMinuteMaxSize"
|
||||
title="Maximum size for a 140 minute movie">
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</span>
|
||||
|
|
|
|||
|
|
@ -1,32 +1,32 @@
|
|||
<fieldset>
|
||||
<legend>More Info</legend>
|
||||
<legend>More Info</legend>
|
||||
|
||||
<dl class="dl-horizontal info">
|
||||
<dt>Discord</dt>
|
||||
<dd><a href="https://discord.gg/AD3UP37">Radarr on Discord</a>
|
||||
<dl class="dl-horizontal info">
|
||||
<dt>Discord</dt>
|
||||
<dd><a href="https://discord.gg/AD3UP37">Radarr on Discord</a>
|
||||
|
||||
<dt>Reddit</dt>
|
||||
<dd><a href="https://www.reddit.com/r/radarr/">Radarr Subreddit</a>
|
||||
{{!--<dt>Home page</dt>
|
||||
<dd><a href="https://radarr.tdb/">radarr.tdb</a></dd>
|
||||
<dt>Reddit</dt>
|
||||
<dd><a href="https://www.reddit.com/r/radarr/">Radarr Subreddit</a>
|
||||
<dt>Home page</dt>
|
||||
<dd><a href="https://radarr.video/">radarr.video</a></dd>
|
||||
|
||||
<dt>Wiki</dt>
|
||||
<dd><a href="https://wiki.radarr.tdb/">wiki.radarr.tdb</a></dd>
|
||||
{{!--<dt>Wiki</dt>
|
||||
<dd><a href="https://wiki.radarr.tdb/">wiki.radarr.tdb</a></dd>
|
||||
|
||||
<dt>Forums</dt>
|
||||
<dd><a href="https://forums.sonarr.tv/">forums.sonarr.tv</a></dd>
|
||||
<dt>Forums</dt>
|
||||
<dd><a href="https://forums.sonarr.tv/">forums.sonarr.tv</a></dd>
|
||||
|
||||
<dt>Twitter</dt>
|
||||
<dd><a href="https://twitter.com/sonarrtv">@sonarrtv</a></dd>
|
||||
<dt>Twitter</dt>
|
||||
<dd><a href="https://twitter.com/sonarrtv">@sonarrtv</a></dd>
|
||||
|
||||
<dt>IRC</dt>
|
||||
<dd><a href="irc://irc.freenode.net/#sonarr">#sonarr on Freenode</a> or (<a href="http://webchat.freenode.net/?channels=#sonarr">webchat</a>)</dd>--}}
|
||||
<dt>IRC</dt>
|
||||
<dd><a href="irc://irc.freenode.net/#sonarr">#sonarr on Freenode</a> or (<a href="http://webchat.freenode.net/?channels=#sonarr">webchat</a>)</dd>--}}
|
||||
|
||||
<dt>Source</dt>
|
||||
<dd><a href="https://github.com/Radarr/Radarr">Radarr on Github</a></dd>
|
||||
<dt>Source</dt>
|
||||
<dd><a href="https://github.com/Radarr/Radarr">Radarr on Github</a></dd>
|
||||
|
||||
<dt>Feature Requests</dt>
|
||||
<!--<dd><a href="https://forums.sonarr.tv/">forums.sonarr.tv</a></dd>-->
|
||||
<dd><a href="https://github.com/Radarr/Radarr/issues">Github Issues</a></dd>
|
||||
</dl>
|
||||
<dt>Feature Requests</dt>
|
||||
<!--<dd><a href="https://forums.sonarr.tv/">forums.sonarr.tv</a></dd>-->
|
||||
<dd><a href="https://github.com/Radarr/Radarr/issues">Github Issues</a></dd>
|
||||
</dl>
|
||||
</fieldset>
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
var _ = require('underscore');
|
||||
var EpisodeModel = require('../../Series/EpisodeModel');
|
||||
var MovieModel = require('../../Movies/MovieModel');
|
||||
var PagableCollection = require('backbone.pageable');
|
||||
var AsFilteredCollection = require('../../Mixins/AsFilteredCollection');
|
||||
var AsSortedCollection = require('../../Mixins/AsSortedCollection');
|
||||
|
|
@ -7,13 +7,13 @@ var AsPersistedStateCollection = require('../../Mixins/AsPersistedStateCollectio
|
|||
|
||||
var Collection = PagableCollection.extend({
|
||||
url : window.NzbDrone.ApiRoot + '/wanted/missing',
|
||||
model : EpisodeModel,
|
||||
model : MovieModel,
|
||||
tableName : 'wanted.missing',
|
||||
|
||||
state : {
|
||||
pageSize : 15,
|
||||
sortKey : 'airDateUtc',
|
||||
order : 1
|
||||
sortKey : 'inCinemas',
|
||||
order : -1
|
||||
},
|
||||
|
||||
queryParams : {
|
||||
|
|
@ -39,10 +39,6 @@ var Collection = PagableCollection.extend({
|
|||
]
|
||||
},
|
||||
|
||||
sortMappings : {
|
||||
'series' : { sortKey : 'series.sortTitle' }
|
||||
},
|
||||
|
||||
parseState : function(resp) {
|
||||
return { totalRecords : resp.totalRecords };
|
||||
},
|
||||
|
|
@ -58,4 +54,4 @@ var Collection = PagableCollection.extend({
|
|||
Collection = AsFilteredCollection.call(Collection);
|
||||
Collection = AsSortedCollection.call(Collection);
|
||||
|
||||
module.exports = AsPersistedStateCollection.call(Collection);
|
||||
module.exports = AsPersistedStateCollection.call(Collection);
|
||||
|
|
|
|||
|
|
@ -5,11 +5,9 @@ var Marionette = require('marionette');
|
|||
var Backgrid = require('backgrid');
|
||||
var MissingCollection = require('./MissingCollection');
|
||||
var SelectAllCell = require('../../Cells/SelectAllCell');
|
||||
var SeriesTitleCell = require('../../Cells/SeriesTitleCell');
|
||||
var EpisodeNumberCell = require('../../Cells/EpisodeNumberCell');
|
||||
var EpisodeTitleCell = require('../../Cells/EpisodeTitleCell');
|
||||
var MovieTitleCell = require('../../Cells/MovieTitleCell');
|
||||
var RelativeDateCell = require('../../Cells/RelativeDateCell');
|
||||
var EpisodeStatusCell = require('../../Cells/EpisodeStatusCell');
|
||||
var MovieStatusWithTextCell = require('../../Cells/MovieStatusWithTextCell');
|
||||
var GridPager = require('../../Shared/Grid/Pager');
|
||||
var ToolbarLayout = require('../../Shared/Toolbar/ToolbarLayout');
|
||||
var LoadingView = require('../../Shared/LoadingView');
|
||||
|
|
@ -39,35 +37,29 @@ module.exports = Marionette.Layout.extend({
|
|||
headerCell : 'select-all',
|
||||
sortable : false
|
||||
},
|
||||
{
|
||||
name : 'series',
|
||||
label : 'Series Title',
|
||||
cell : SeriesTitleCell,
|
||||
sortValue : 'series.sortTitle'
|
||||
},
|
||||
{
|
||||
name : 'this',
|
||||
label : 'Episode',
|
||||
cell : EpisodeNumberCell,
|
||||
label : 'Movie Title',
|
||||
cell : MovieTitleCell,
|
||||
sortable : false
|
||||
},
|
||||
{
|
||||
name : 'this',
|
||||
label : 'Episode Title',
|
||||
cell : EpisodeTitleCell,
|
||||
sortable : false
|
||||
name : 'inCinemas',
|
||||
label : 'In Cinemas',
|
||||
cell : RelativeDateCell
|
||||
},
|
||||
{
|
||||
name : 'airDateUtc',
|
||||
label : 'Air Date',
|
||||
name : 'physicalRelease',
|
||||
label : 'PhysicalRelease',
|
||||
cell : RelativeDateCell
|
||||
},
|
||||
{
|
||||
name : 'status',
|
||||
label : 'Status',
|
||||
cell : EpisodeStatusCell,
|
||||
cell : MovieStatusWithTextCell,
|
||||
sortable : false
|
||||
}
|
||||
},
|
||||
|
||||
],
|
||||
|
||||
initialize : function() {
|
||||
|
|
@ -206,8 +198,8 @@ module.exports = Marionette.Layout.extend({
|
|||
});
|
||||
},
|
||||
_searchMissing : function() {
|
||||
if (window.confirm('Are you sure you want to search for {0} missing episodes? '.format(this.collection.state.totalRecords) +
|
||||
'One API request to each indexer will be used for each episode. ' + 'This cannot be stopped once started.')) {
|
||||
if (window.confirm('Are you sure you want to search for {0} missing movies? '.format(this.collection.state.totalRecords) +
|
||||
'One API request to each indexer will be used for each movie. ' + 'This cannot be stopped once started.')) {
|
||||
CommandController.Execute('missingEpisodeSearch', { name : 'missingEpisodeSearch' });
|
||||
}
|
||||
},
|
||||
|
|
@ -237,4 +229,4 @@ module.exports = Marionette.Layout.extend({
|
|||
_manualImport : function () {
|
||||
vent.trigger(vent.Commands.ShowManualImport);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -29,7 +29,8 @@ vent.Commands = {
|
|||
ShowFileBrowser : 'showFileBrowser',
|
||||
CloseFileBrowser : 'closeFileBrowser',
|
||||
OpenControlPanelCommand : 'OpenControlPanelCommand',
|
||||
CloseControlPanelCommand : 'CloseControlPanelCommand'
|
||||
CloseControlPanelCommand : 'CloseControlPanelCommand',
|
||||
ShowExistingCommand : 'ShowExistingCommand'
|
||||
};
|
||||
|
||||
vent.Hotkeys = {
|
||||
|
|
|
|||
Loading…
Reference in a new issue