1
0
mirror of https://github.com/Radarr/Radarr.git synced 2026-04-18 21:35:51 -04:00

Compare commits

..

89 Commits

Author SHA1 Message Date
Leonardo Galli 3d67f6237e Should fix 4K releases not getting parsed. 2017-01-21 00:39:44 +01:00
Krystian Charubin a691ffa7b7 adds 'Movie Title, The' filename option (#359)
* adds 'Movie Title, The' filename option
* updates the FileNameBuilder.MovieTitleRegex to include new format
2017-01-20 17:42:14 -05:00
Leonardo Galli aa9537c201 Fix issue when movie file is null. 2017-01-20 20:30:36 +01:00
Leonardo Galli a3d9fb1c20 Should fix upgrading of existing movie files. 2017-01-20 18:26:18 +01:00
Leonardo Galli 62a1e70c86 Add tests for 4K quality. 2017-01-20 18:21:31 +01:00
Leonardo Galli 93d0d21846 Hopefully a fix for corrupt media covers. 2017-01-20 18:06:58 +01:00
Leonardo Galli b1c5a3ac14 Fixed blacklist being ignored by download decision maker. 2017-01-20 17:48:47 +01:00
vertigo235 55a525ba2f Add helptext to nzbget "add paused" settings. (#363)
Requires nzbget 16 or greater.
2017-01-20 09:14:15 -05:00
Devin Buhl a53768463b Merge pull request #358 from Radarr/patch/add-year-to-search
add year to search
2017-01-19 20:07:33 -05:00
Tim Turner 24cbd6bcef Fix issue with reimporting on movie fresh (#357)
Fixes #314. Still have multiple movieFiles issue to clean up.
2017-01-19 19:58:57 -05:00
Devin Buhl 3ab3e66853 Add year to quick search results 2017-01-19 19:56:26 -05:00
Leonardo Galli 40ca469339 Update MovieModule 2017-01-20 00:00:12 +01:00
Tim Turner 2cbd2f719f Merge branch 'develop' of https://github.com/Radarr/Radarr into develop 2017-01-19 16:45:39 -05:00
Tim Turner 53cbfa803b Fix MediaCoversUpdatedEvent broadcast
Cleans exception when updating media covers expects a series Id.
2017-01-19 16:45:29 -05:00
Leonardo Galli c0b0310bbd Update ISSUE_TEMPLATE.md 2017-01-19 22:10:59 +01:00
Devin Buhl 30e50062a8 Merge pull request #352 from baltoaca/develop
bug fix for 15 movie wanted tab limit
2017-01-19 13:17:40 -05:00
Vlad Ilies 85fd8f2c65 bug fix for 15 movie wanted tab (#348) 2017-01-19 20:08:15 +02:00
Leonardo Galli f72b042d5d Blacklisting works now. 2017-01-19 17:43:23 +01:00
Leonardo Galli 2d3a3a0677 Update sizing information in settings tab. 2017-01-19 17:40:25 +01:00
Tim Turner 2bb21fedab Update height of posters to accomodate additional labels 2017-01-18 17:24:32 -05:00
vertigo235 91c820f98b Fix pushover priority values. 2017-01-18 23:16:57 +01:00
Leonardo Galli 7d3118aece Hopefully fix issue when importing
existing scene named movies
2017-01-18 23:02:05 +01:00
Tim Turner 4f4ad77ad1 Add download status to poster view
Fixes #319
2017-01-18 16:54:17 -05:00
Leonardo Galli 42f205a731 Update SkyHookProxy.cs 2017-01-18 22:50:12 +01:00
Devin Buhl cbb2b778a6 Merge pull request #336 from Radarr/patch/add-imdb-to-naming
Add IMDb ID to file naming
2017-01-18 15:11:16 -05:00
Devin Buhl b3e03a648d Add IMDb ID to file naming 2017-01-18 14:53:29 -05:00
Devin Buhl acf45a79e8 Merge pull request #333 from baltoaca/develop
basic implementation of the wanted tab
2017-01-18 14:36:48 -05:00
Devin Buhl b5d8ac852e Merge pull request #335 from Radarr/patch/task-updates
Turn off scene mapping task #329, update TaskManager to use 'DownloadedMovieScanCommand
2017-01-18 14:36:29 -05:00
Vlad Ilies 4aec0e8fc6 fixed build 2017-01-18 21:09:00 +02:00
Devin Buhl ecea417fd8 Revert DownloadedMovieScanCommand to DownloadedEpisodesScanCommand
Not sure if it will break anything so putting it back.
2017-01-18 14:07:51 -05:00
Devin Buhl 6a41f6a435 Turn off scene mapping task #329, update TaskManager to use 'DownloadedMovieScanCommand' 2017-01-18 14:02:04 -05:00
Vlad Ilies da2d075aa8 basic implementation of the wanted tab (#31)
* top buttons don't yet work

* new missing module for movies

*  find missing movies method to movie service

* new movie status cell with text

* adapted UI missing collection and layout
2017-01-18 20:53:17 +02:00
Devin Buhl 10dc3993df Merge pull request #332 from Radarr/revert-318-sonarr/sqlite-updates
Revert "Sonarr/sqlite updates"
2017-01-18 13:33:46 -05:00
Devin Buhl 7e5020db9a Merge pull request #331 from vertigo235/nzbgetaddpaused
Nzbgetaddpaused
2017-01-18 13:20:12 -05:00
Devin Buhl c48fe9de12 Revert "Sonarr/sqlite updates" 2017-01-18 13:17:35 -05:00
vertigo235 421e827a95 Update Test Files for AddPaused to NZBGET 2017-01-18 13:08:59 -05:00
vertigo235 34d8045cf4 Add "Add Paused" option for NZBGET downloader
Adds option to pause nzbs uppon sending to NZBGET downloader.
2017-01-18 12:42:33 -05:00
Devin Buhl c6de163748 Merge pull request #318 from Radarr/sonarr/sqlite-updates
Sonarr/sqlite updates
2017-01-17 18:59:11 -05:00
Keivan Beigi d9e2b22e74 Upgraded System.Data.SQLite to 1.0.104.0 2017-01-17 18:45:23 -05:00
Keivan Beigi 65c0137964 Revert "Upgraded System.Data.SQLite to 1.0.104.0"
This reverts commit a6f3ac219d61964f1b923cfd89382f94c4c74243.
2017-01-17 18:44:37 -05:00
Keivan ae19424ce7 New: Upgraded SQLite binares for macOS
Upgraded from 3.8.1 to 3.9.1
2017-01-17 18:41:11 -05:00
Keivan Beigi 7527ec52b7 New: Upgraded SQLite binaries for Windows (3.16.0) 2017-01-17 18:41:05 -05:00
Leonardo Galli 640fcf3eaf Remove series references 2017-01-17 23:57:04 +01:00
Leonardo Galli 3ce8232777 Hopefully fix download ordering. 2017-01-17 23:30:11 +01:00
Devin Buhl 864b441d8e Merge pull request #316 from Radarr/patch/skyhook-cleanup
Maybe this will solve the error Object reference not set to an instance of an object
2017-01-17 17:20:49 -05:00
Devin Buhl bc2ff149b4 Maybe this will solve the error
Couldn't refresh info for [tt2032572][USS Indianapolis: Men of Courage]: Object reference not set to an instance of an object
2017-01-17 17:05:19 -05:00
Devin Buhl dea305e921 Fix Issue when adding some movies. 2017-01-17 22:25:49 +01:00
Leonardo Galli e2eab31548 Hopefully fix RSSSync 2017-01-17 21:22:51 +01:00
Devin Buhl fe62e18f0d Merge pull request #308 from Radarr/patch/jackett
Fix publish date for jackett #239
2017-01-17 13:56:47 -05:00
Devin Buhl f1fa1553cf Merge pull request #307 from aenima99x/aenima99x-issue91
Fix: Issue #91 - "Search All Missing" wording
2017-01-17 13:48:30 -05:00
Devin Buhl b576ae813d Fix publish date #239 2017-01-17 13:47:11 -05:00
Aenima99x 99123be936 Fix: Issue #91 - "Search All Missing" wording 2017-01-17 09:00:56 -08:00
Leonardo Galli dd0a033b0f Add Support for changing file date to either cinema or physical release.
Fixes #124
2017-01-17 15:02:48 +01:00
Leonardo Galli c64597c9f1 Fix for movies with . in title when importing them. Fixes #268 2017-01-17 14:47:23 +01:00
Leonardo Galli 6d2f81e3ed Remove - as replacement for : 2017-01-17 14:28:30 +01:00
Leonardo Galli 4263808360 Fix only one movie showing. Fix more button not showing up. 2017-01-17 13:21:40 +01:00
Leonardo Galli c5ca2babf7 Updated website and donation links 2017-01-17 13:07:02 +01:00
Leonardo Galli 08db74d6e6 Fix Audiochannels just being added together. 2017-01-17 13:03:20 +01:00
Leonardo Galli b309a9b01f Change Scheduled Refresh Series to Refresh Movie. Fixes #301 2017-01-17 12:55:32 +01:00
Tim Turner 2730745607 Clean up rename preview & organize
Fixes #125, #129,

BE SURE TO RUN "Update Library" before renaming/organizing.
2017-01-16 18:43:32 -05:00
Tim Turner ae0df2aef0 Disambiguate Movie from Episode Renaming
Fixes #84
2017-01-16 17:12:27 -05:00
Leonardo Galli 6d665aeb21 Fix for hardcoded subs regex. 2017-01-16 22:51:45 +01:00
Leonardo Galli 967d3fd5c0 Add Calendar Tab back. Fixes #32 2017-01-16 22:40:59 +01:00
Devin Buhl 199d9c93ed Merge pull request #287 from baltoaca/develop
Added movie studio to movie details page (#262)
2017-01-16 14:30:21 -05:00
Vlad Ilies 30d2b41fbb Added movie studio to movie details page (#262)
* modified Movie model

* db migration

* ui template modification
2017-01-16 20:57:43 +02:00
Devin Buhl 75bb2533a3 Merge pull request #286 from Radarr/onedr0p-patch-1
Removed duplicate PublishDate
2017-01-16 13:46:29 -05:00
Devin Buhl 9b3b4eb55b Removed duplicate PublishDate 2017-01-16 13:25:59 -05:00
Leonardo Galli 42f84b830c Update NewznabRequestGenerator.cs 2017-01-16 12:54:51 +01:00
Leonardo Galli eb0f825cfc Update README.md 2017-01-16 00:10:30 +01:00
Vlad Ilies 56a5b6ec89 Added trailer link to movie links (#255) (#282)
* added YouTubeTrailerId to movie model
* db migration for new column

* added videos to append_to_response for tmdb
* increased height of .series-posters-item
* new handlebar helper to build the trailer url
2017-01-15 15:17:24 -05:00
hotio af478d3799 Add support section to README (#281)
* Add support section to README

* Update README.md

* Change docker color badge
2017-01-15 18:23:49 +01:00
Tim Turner c2d40051d4 First pass at hiding existing movies upon import
Fixes #183
2017-01-15 11:34:43 -05:00
Tim Turner 281e516495 Merge branch 'develop' of https://github.com/Radarr/Radarr into develop 2017-01-15 11:07:41 -05:00
hotio f63c3091f4 Reworked README (#280) 2017-01-15 13:50:45 +01:00
Leonardo Galli 50f49863b7 Update README.md 2017-01-15 11:42:15 +01:00
Mitchell Cash 941b3bd701 Move Travis builds to container-based infrastructure (#273)
* Remove example warning as it does not apply

* Modify the way apt packages work in .travis.yml

By modifying the way apt packages work (remove the need to directly require sudo), the builds can now run on container-based infrastructure.
2017-01-15 11:39:00 +01:00
hotio f60b8cefca Update .gitignore and remove Thumbs.db files (#276)
* Update .gitignore

* Delete Thumbs.db

* Delete Thumbs.db

* Delete Thumbs.db

* Update .gitignore
2017-01-15 11:38:54 +01:00
Tim Turner 7c5f2ca54e 95% done with hiding existing movies 2017-01-14 23:04:31 -05:00
Leonardo Galli 401a650273 Adding only original title is now allowed. Fixes #272 2017-01-15 01:36:05 +01:00
Leonardo Galli 105af5cf11 Fix for special characters when searching with title in Newznab. Fixes #97 2017-01-15 01:20:07 +01:00
hotio 637c2e43eb Update README.md (#271)
* Update README.md

* Update README.md
2017-01-15 00:56:52 +01:00
Leonardo Galli 53373e6f4a Add {Original Title} to FileNameBuilder. Fixes #103 2017-01-15 00:55:15 +01:00
Leonardo Galli ad147ed425 Release Group should now be available for renamer to use. 2017-01-15 00:49:37 +01:00
Devin Buhl ed35e2f194 Merge pull request #270 from hotio/patch-1
Update README.md
2017-01-14 18:10:58 -05:00
hotio 674919dbf5 Update README.md 2017-01-15 00:06:18 +01:00
Devin Buhl 15b77e303f Merge pull request #266 from hotio/patch-1
Update README.md
2017-01-14 17:53:01 -05:00
hotio 757ca1d72c Update README.md
Some small additional fixes to README
2017-01-14 23:48:04 +01:00
Devin Buhl 8c8c7a99e3 Merge pull request #265 from mitchellcash/readme
Cleanup README
2017-01-14 17:35:33 -05:00
Mitchell Cash b3dfb960b5 Cleanup README 2017-01-15 08:23:35 +10:00
105 changed files with 12278 additions and 8005 deletions
+2
View File
@@ -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. Provide a description of the feature request or bug, the more details the better.
When possible include a log! When possible include a log!
+17 -9
View File
@@ -101,16 +101,21 @@ App_Data/*.ldf
_NCrunch_* _NCrunch_*
_TeamCity* _TeamCity*
# Sonarr # Radarr
config.xml Backups/
nzbdrone.log*txt logs/
MediaCover/
UpdateLogs/ UpdateLogs/
xdg/
config.xml
logs.db*
nzbdrone.db*
nzbdrone.pid
*workspace.xml *workspace.xml
*.test-cache *.test-cache
*.userprefs *.userprefs
*/test-results/* */test-results/*
src/UI/.idea/* src/UI/.idea/*
*log.txt
node_modules/ node_modules/
_output* _output*
_rawPackage/ _rawPackage/
@@ -122,23 +127,26 @@ setup/Output/
UI.Phantom/ UI.Phantom/
#VS outout folders # VS outout folders
bin bin
obj obj
output/* output/*
#Packages # Packages
Radarr_*/ Radarr_*/
Radarr_*.zip Radarr_*.zip
Radarr_*.gz Radarr_*.gz
#OS X metadata files # macOS metadata files
._* ._*
.DS_Store .DS_Store
_start _start
_temp_*/**/* _temp_*/**/*
#AppVeyor # Windows thumbnail cache files
Thumbs.db
# AppVeyor
/tools-cake/ /tools-cake/
/_artifacts/ /_artifacts/
+6 -4
View File
@@ -1,12 +1,14 @@
language: csharp language: csharp
solution: src/NzbDrone.sln solution: src/NzbDrone.sln
script: # the following commands are just examples, use whatever your build process requires addons:
apt:
packages:
- nodejs
- npm
script:
- ./build.sh - ./build.sh
- chmod +x test.sh - chmod +x test.sh
# - ./test.sh Linux Unit Takes far too long, maybe even crashes travis :/ # - ./test.sh Linux Unit Takes far too long, maybe even crashes travis :/
install:
- sudo apt-get install nodejs
- sudo apt-get install npm
after_success: after_success:
- chmod +x package.sh - chmod +x package.sh
- ./package.sh - ./package.sh
BIN
View File
Binary file not shown.
+94
View File
@@ -0,0 +1,94 @@
## Status
[![GitHub issues](https://img.shields.io/github/issues/radarr/radarr.svg?maxAge=60&style=flat-square)](https://github.com/Radarr/Radarr/issues)
[![GitHub pull requests](https://img.shields.io/github/issues-pr/radarr/radarr.svg?maxAge=60&style=flat-square)](https://github.com/Radarr/Radarr/pulls)
[![GNU GPL v3](https://img.shields.io/badge/license-GNU%20GPL%20v3-blue.svg?maxAge=60&style=flat-square)](http://www.gnu.org/licenses/gpl.html)
[![Copyright 2010-2017](https://img.shields.io/badge/copyright-2017-blue.svg?maxAge=60&style=flat-square)](https://github.com/Radarr/Radarr)
[![Github Releases](https://img.shields.io/github/downloads/Radarr/Radarr/total.svg?maxAge=60&style=flat-square)](https://github.com/Radar/Radarr/releases/latest)
[![Docker Pulls](https://img.shields.io/docker/pulls/linuxserver/radarr.svg?maxAge=60&style=flat-square)](https://hub.docker.com/r/linuxserver/radarr/)
| Service | Master | Develop |
|----------|:---------------------------:|:----------------------------:|
| AppVeyor | [![AppVeyor](https://img.shields.io/appveyor/ci/galli-leo/Radarr/master.svg?maxAge=60&style=flat-square)](https://ci.appveyor.com/project/galli-leo/Radarr) | [![AppVeyor](https://img.shields.io/appveyor/ci/galli-leo/Radarr-usby1/develop.svg?maxAge=60&style=flat-square)](https://ci.appveyor.com/project/galli-leo/Radarr-usby1) |
| Travis | [![Travis](https://img.shields.io/travis/Radarr/Radarr/master.svg?maxAge=60&style=flat-square)](https://travis-ci.org/Radarr/Radarr) | [![Travis](https://img.shields.io/travis/Radarr/Radarr/develop.svg?maxAge=60&style=flat-square)](https://travis-ci.org/Radarr/Radarr) |
This fork of Sonarr aims to turn it into something like CouchPotato.
## Downloads
[![GitHub Releases](https://img.shields.io/badge/downloads-releases-brightgreen.svg?maxAge=60&style=flat-square)](https://github.com/Radarr/Radarr/releases)
[![AppVeyor Builds](https://img.shields.io/badge/downloads-continuous-green.svg?maxAge=60&style=flat-square)](https://ci.appveyor.com/project/galli-leo/radarr-usby1/build/artifacts)
[![Docker x64](https://img.shields.io/badge/docker-x64-blue.svg?colorB=1488C6&maxAge=60&style=flat-square)](https://store.docker.com/community/images/linuxserver/radarr)
[![Docker armhf](https://img.shields.io/badge/docker-armhf-blue.svg?colorB=1488C6&maxAge=60&style=flat-square)](https://store.docker.com/community/images/lsioarmhf/radarr)
[![Docker aarch64](https://img.shields.io/badge/docker-aarch64-blue.svg?colorB=1488C6&maxAge=60&style=flat-square)](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
[![Discord](https://img.shields.io/badge/discord-chat-7289DA.svg?maxAge=60&style=flat-square)](https://discord.gg/AD3UP37)
[![Reddit](https://img.shields.io/badge/reddit-discussion-FF4500.svg?maxAge=60&style=flat-square)](https://www.reddit.com/r/radarr)
[![GitHub](https://img.shields.io/badge/github-issues-181717.svg?maxAge=60&style=flat-square)](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
* 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)
* And a beautiful UI
## Configuring Development Environment
### Requirements
* [Visual Studio Community](https://www.visualstudio.com/vs/community/) or [MonoDevelop](http://www.monodevelop.com)
* [Git](https://git-scm.com/downloads)
* [Node.js](https://nodejs.org/en/download/)
### Setup
* Make sure all the required software mentioned above are installed
* Clone the repository into your development machine ([*info*](https://help.github.com/desktop/guides/contributing/working-with-your-remote-repository-on-github-or-github-enterprise))
* Grab the submodules `git submodule init && git submodule update`
* 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.
> **Notice**
> Gulp must be running at all times while you are working with Radarr client source files.
### Development
* Open `NzbDrone.sln` in Visual Studio or run the build.sh script, if Mono is installed
* Make sure `NzbDrone.Console` is set as the startup project
## Sponsors
[JetBrains](http://www.jetbrains.com) for providing us with free licenses to their great tools:
* [ReSharper](http://www.jetbrains.com/resharper)
* [WebStorm](http://www.jetbrains.com/webstorm)
* [TeamCity](http://www.jetbrains.com/teamcity)
-83
View File
@@ -1,83 +0,0 @@
# Radarr
| Service | Master | Develop |
|----------|:---------------------------:|:----------------------------:|
| AppVeyor | [![AppVeyor](https://img.shields.io/appveyor/ci/galli-leo/Radarr/master.svg?maxAge=60&style=flat-square)](https://ci.appveyor.com/project/galli-leo/Radarr) | [![AppVeyor](https://img.shields.io/appveyor/ci/galli-leo/Radarr-usby1/develop.svg?maxAge=60&style=flat-square)](https://ci.appveyor.com/project/galli-leo/Radarr-usby1) |
| Travis | [![Travis](https://img.shields.io/travis/galli-leo/Radarr/master.svg?maxAge=60&style=flat-square)](https://travis-ci.org/galli-leo/Radarr) | [![Travis](https://img.shields.io/travis/galli-leo/Radarr/develop.svg?maxAge=60&style=flat-square)](https://travis-ci.org/galli-leo/Radarr) |
This fork of Sonarr aims to turn it into something like Couchpotato.
## 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.
## Download
The latest precompiled binary versions can be found here: https://github.com/galli-leo/Radarr/releases.
To connect to the UI, fire up your browser and open localhost:7878 or your-ip:7878.
Docker containers from [linuxserver.io](https://linuxserver.io) can be found here.
* [Radarr (x64)](https://hub.docker.com/r/linuxserver/radarr/)
* [Radarr (armhf)](https://hub.docker.com/r/lsioarmhf/radarr/)
* [Radarr (aarch64)](https://hub.docker.com/r/lsioarmhf/radarr-aarch64/)
For more up to date versions (but also sometimes broken), daily builds can be found here:
* [OSX](https://leonardogalli.ch/radarr/builds/latest.php?os=osx)
* [Windows](https://leonardogalli.ch/radarr/builds/latest.php?os=windows)
* [Linux](https://leonardogalli.ch/radarr/builds/latest.php?os=mono)
## Major Features Include: ##
* Support for major platforms: Windows, Linux, OSX, Raspberry Pi, etc.
* Can watch for better quality of the movies you have and do an upgrade.
* 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 XBMC, Plex (notification, library update, metadata).
* And a beautiful UI
## Configuring Development Environment: ##
### Requirements ###
- Visual Studio 2015 [Free Community Edition](https://www.visualstudio.com/en-us/products/visual-studio-community-vs.aspx) or Mono
- [Git](http://git-scm.com/downloads)
- [NodeJS](http://nodejs.org/download/)
### Setup ###
- Make sure all the required software mentioned above are installed.
- Clone the repository into your development machine. [*info*](https://help.github.com/articles/working-with-repositories)
- Grab the submodules `git submodule init && git submodule update`
- 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 Sonarr client source files.*
### Development ###
- Open `NzbDrone.sln` in Visual Studio or run the build.sh script, if Mono is installed.
- Make sure `NzbDrone.Console` is set as the startup project
### License ###
* [GNU GPL v3](http://www.gnu.org/licenses/gpl.html)
Copyright 2010-2016
### Sponsors ###
- [JetBrains](http://www.jetbrains.com/) for providing us with free licenses to their great tools
- [ReSharper](http://www.jetbrains.com/resharper/)
- [WebStorm](http://www.jetbrains.com/webstorm/)
- [TeamCity](http://www.jetbrains.com/teamcity/)
@@ -1,5 +1,6 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using NzbDrone.Api.Movie;
using NzbDrone.Api.REST; using NzbDrone.Api.REST;
using NzbDrone.Core.Qualities; using NzbDrone.Core.Qualities;
using NzbDrone.Api.Series; using NzbDrone.Api.Series;
@@ -11,13 +12,14 @@ namespace NzbDrone.Api.Blacklist
{ {
public int SeriesId { get; set; } public int SeriesId { get; set; }
public List<int> EpisodeIds { get; set; } public List<int> EpisodeIds { get; set; }
public int MovieId { get; set; }
public string SourceTitle { get; set; } public string SourceTitle { get; set; }
public QualityModel Quality { get; set; } public QualityModel Quality { get; set; }
public DateTime Date { get; set; } public DateTime Date { get; set; }
public DownloadProtocol Protocol { get; set; } public DownloadProtocol Protocol { get; set; }
public string Indexer { get; set; } public string Indexer { get; set; }
public string Message { get; set; } public string Message { get; set; }
public MovieResource Movie { get; set; }
public SeriesResource Series { get; set; } public SeriesResource Series { get; set; }
} }
@@ -30,7 +32,7 @@ namespace NzbDrone.Api.Blacklist
return new BlacklistResource return new BlacklistResource
{ {
Id = model.Id, Id = model.Id,
MovieId = model.MovieId,
SeriesId = model.SeriesId, SeriesId = model.SeriesId,
EpisodeIds = model.EpisodeIds, EpisodeIds = model.EpisodeIds,
SourceTitle = model.SourceTitle, SourceTitle = model.SourceTitle,
@@ -39,7 +41,7 @@ namespace NzbDrone.Api.Blacklist
Protocol = model.Protocol, Protocol = model.Protocol,
Indexer = model.Indexer, Indexer = model.Indexer,
Message = model.Message, Message = model.Message,
Movie = model.Movie.ToResource(),
Series = model.Series.ToResource() Series = model.Series.ToResource()
}; };
} }
+23 -9
View File
@@ -2,24 +2,38 @@
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using NzbDrone.Api.Episodes; 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.DecisionEngine;
using NzbDrone.Core.Tv; using NzbDrone.Core.Tv;
using NzbDrone.SignalR; using NzbDrone.SignalR;
namespace NzbDrone.Api.Calendar namespace NzbDrone.Api.Calendar
{ {
public class CalendarModule : EpisodeModuleWithSignalR public class CalendarModule : MovieModule
{ {
public CalendarModule(IEpisodeService episodeService, public CalendarModule(IBroadcastSignalRMessage signalR,
ISeriesService seriesService, IMovieService moviesService,
IQualityUpgradableSpecification qualityUpgradableSpecification, IMovieStatisticsService moviesStatisticsService,
IBroadcastSignalRMessage signalRBroadcaster) ISceneMappingService sceneMappingService,
: base(episodeService, seriesService, qualityUpgradableSpecification, signalRBroadcaster, "calendar") IMapCoversToLocal coverMapper)
: base(signalR, moviesService, moviesStatisticsService, sceneMappingService, coverMapper, "calendar")
{ {
GetResourceAll = GetCalendar; GetResourceAll = GetCalendar;
} }
private List<EpisodeResource> GetCalendar() private List<MovieResource> GetCalendar()
{ {
var start = DateTime.Today; var start = DateTime.Today;
var end = DateTime.Today.AddDays(2); var end = DateTime.Today.AddDays(2);
@@ -33,9 +47,9 @@ namespace NzbDrone.Api.Calendar
if (queryEnd.HasValue) end = DateTime.Parse(queryEnd.Value); if (queryEnd.HasValue) end = DateTime.Parse(queryEnd.Value);
if (queryIncludeUnmonitored.HasValue) includeUnmonitored = Convert.ToBoolean(queryIncludeUnmonitored.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();
} }
} }
} }
+1 -1
View File
@@ -12,7 +12,7 @@ namespace NzbDrone.Api.Movies
private readonly IRenameMovieFileService _renameMovieFileService; private readonly IRenameMovieFileService _renameMovieFileService;
public RenameMovieModule(IRenameMovieFileService renameMovieFileService) public RenameMovieModule(IRenameMovieFileService renameMovieFileService)
: base("rename") : base("renameMovie")
{ {
_renameMovieFileService = renameMovieFileService; _renameMovieFileService = renameMovieFileService;
+2 -1
View File
@@ -260,6 +260,7 @@
<Compile Include="Wanted\CutoffModule.cs" /> <Compile Include="Wanted\CutoffModule.cs" />
<Compile Include="Wanted\LegacyMissingModule.cs" /> <Compile Include="Wanted\LegacyMissingModule.cs" />
<Compile Include="Wanted\MissingModule.cs" /> <Compile Include="Wanted\MissingModule.cs" />
<Compile Include="Wanted\MovieMissingModule.cs" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<None Include="app.config" /> <None Include="app.config" />
@@ -294,4 +295,4 @@
<Target Name="AfterBuild"> <Target Name="AfterBuild">
</Target> </Target>
--> -->
</Project> </Project>
+25 -3
View File
@@ -28,7 +28,7 @@ namespace NzbDrone.Api.Movie
IHandle<MediaCoversUpdatedEvent> IHandle<MediaCoversUpdatedEvent>
{ {
private readonly IMovieService _moviesService; protected readonly IMovieService _moviesService;
private readonly IMovieStatisticsService _moviesStatisticsService; private readonly IMovieStatisticsService _moviesStatisticsService;
private readonly IMapCoversToLocal _coverMapper; private readonly IMapCoversToLocal _coverMapper;
@@ -78,13 +78,33 @@ namespace NzbDrone.Api.Movie
PutValidator.RuleFor(s => s.Path).IsValidPath(); 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) private MovieResource GetMovie(int id)
{ {
var movies = _moviesService.GetMovie(id); var movies = _moviesService.GetMovie(id);
return MapToResource(movies); return MapToResource(movies);
} }
private MovieResource MapToResource(Core.Tv.Movie movies) protected MovieResource MapToResource(Core.Tv.Movie movies)
{ {
if (movies == null) return null; if (movies == null) return null;
@@ -181,6 +201,8 @@ namespace NzbDrone.Api.Movie
//var mappings = null;//_sceneMappingService.FindByTvdbId(resource.TvdbId); //var mappings = null;//_sceneMappingService.FindByTvdbId(resource.TvdbId);
//if (mappings == null) return; //if (mappings == null) return;
//Not necessary anymore
//resource.AlternateTitles = mappings.Select(v => new AlternateTitleResource { Title = v.Title, SeasonNumber = v.SeasonNumber, SceneSeasonNumber = v.SceneSeasonNumber }).ToList(); //resource.AlternateTitles = mappings.Select(v => new AlternateTitleResource { Title = v.Title, SeasonNumber = v.SeasonNumber, SceneSeasonNumber = v.SceneSeasonNumber }).ToList();
} }
@@ -219,7 +241,7 @@ namespace NzbDrone.Api.Movie
public void Handle(MediaCoversUpdatedEvent message) public void Handle(MediaCoversUpdatedEvent message)
{ {
//BroadcastResourceChange(ModelAction.Updated, message.Movie.Id); BroadcastResourceChange(ModelAction.Updated, message.Movie.Id);
} }
} }
} }
+8 -2
View File
@@ -34,6 +34,8 @@ namespace NzbDrone.Api.Movie
public string RemotePoster { get; set; } public string RemotePoster { get; set; }
public int Year { get; set; } public int Year { get; set; }
public bool HasFile { get; set; } public bool HasFile { get; set; }
public string YouTubeTrailerId { get; set; }
public string Studio { get; set; }
//View & Edit //View & Edit
public string Path { get; set; } public string Path { get; set; }
@@ -144,7 +146,9 @@ namespace NzbDrone.Api.Movie
AddOptions = model.AddOptions, AddOptions = model.AddOptions,
AlternativeTitles = model.AlternativeTitles, AlternativeTitles = model.AlternativeTitles,
Ratings = model.Ratings, Ratings = model.Ratings,
MovieFile = movieFile MovieFile = movieFile,
YouTubeTrailerId = model.YouTubeTrailerId,
Studio = model.Studio
}; };
} }
@@ -191,7 +195,9 @@ namespace NzbDrone.Api.Movie
Added = resource.Added, Added = resource.Added,
AddOptions = resource.AddOptions, AddOptions = resource.AddOptions,
AlternativeTitles = resource.AlternativeTitles, AlternativeTitles = resource.AlternativeTitles,
Ratings = resource.Ratings Ratings = resource.Ratings,
YouTubeTrailerId = resource.YouTubeTrailerId,
Studio = resource.Studio
}; };
} }
+1 -1
View File
@@ -236,7 +236,7 @@ namespace NzbDrone.Api.Series
public void Handle(MediaCoversUpdatedEvent message) public void Handle(MediaCoversUpdatedEvent message)
{ {
BroadcastResourceChange(ModelAction.Updated, message.Series.Id); //BroadcastResourceChange(ModelAction.Updated, message.Series.Id);
} }
} }
} }
+1 -1
View File
@@ -12,7 +12,7 @@ namespace NzbDrone.Api.Wanted
ISeriesService seriesService, ISeriesService seriesService,
IQualityUpgradableSpecification qualityUpgradableSpecification, IQualityUpgradableSpecification qualityUpgradableSpecification,
IBroadcastSignalRMessage signalRBroadcaster) IBroadcastSignalRMessage signalRBroadcaster)
: base(episodeService, seriesService, qualityUpgradableSpecification, signalRBroadcaster, "wanted/missing") : base(episodeService, seriesService, qualityUpgradableSpecification, signalRBroadcaster, "wanted/missing_episodes")
{ {
GetResourcePaged = GetMissingEpisodes; GetResourcePaged = GetMissingEpisodes;
} }
@@ -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 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.NzbgetTests
protected void GivenFailedDownload() protected void GivenFailedDownload()
{ {
Mocker.GetMock<INzbgetProxy>() 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); .Returns((string)null);
} }
protected void GivenSuccessfulDownload() protected void GivenSuccessfulDownload()
{ {
Mocker.GetMock<INzbgetProxy>() 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("-", "")); .Returns(Guid.NewGuid().ToString().Replace("-", ""));
} }
@@ -47,5 +47,17 @@ namespace NzbDrone.Core.Test.ParserTests
{ {
QualityParser.ParseQuality(title).Revision.Version.Should().Be(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 @@ namespace NzbDrone.Core.Blacklisting
{ {
public int SeriesId { get; set; } public int SeriesId { get; set; }
public Series Series { get; set; } public Series Series { get; set; }
public int MovieId { get; set; }
public Movie Movie { get; set; }
public List<int> EpisodeIds { get; set; } public List<int> EpisodeIds { get; set; }
public string SourceTitle { get; set; } public string SourceTitle { get; set; }
public QualityModel Quality { get; set; } public QualityModel Quality { get; set; }
@@ -10,7 +10,7 @@ namespace NzbDrone.Core.Blacklisting
{ {
List<Blacklist> BlacklistedByTitle(int seriesId, string sourceTitle); List<Blacklist> BlacklistedByTitle(int seriesId, string sourceTitle);
List<Blacklist> BlacklistedByTorrentInfoHash(int seriesId, string torrentInfoHash); List<Blacklist> BlacklistedByTorrentInfoHash(int seriesId, string torrentInfoHash);
List<Blacklist> BlacklistedBySeries(int seriesId); List<Blacklist> BlacklistedByMovie(int seriesId);
} }
public class BlacklistRepository : BasicRepository<Blacklist>, IBlacklistRepository public class BlacklistRepository : BasicRepository<Blacklist>, IBlacklistRepository
@@ -20,15 +20,15 @@ namespace NzbDrone.Core.Blacklisting
{ {
} }
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)); .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)); .AndWhere(e => e.TorrentInfoHash.Contains(torrentInfoHash));
} }
@@ -37,9 +37,14 @@ namespace NzbDrone.Core.Blacklisting
return Query.Where(b => b.SeriesId == 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) 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); return base.GetPagedQuery(baseQuery, pagingSpec);
} }
@@ -21,7 +21,7 @@ namespace NzbDrone.Core.Blacklisting
IExecute<ClearBlacklistCommand>, IExecute<ClearBlacklistCommand>,
IHandle<DownloadFailedEvent>, IHandle<DownloadFailedEvent>,
IHandleAsync<SeriesDeletedEvent> IHandleAsync<MovieDeletedEvent>
{ {
private readonly IBlacklistRepository _blacklistRepository; private readonly IBlacklistRepository _blacklistRepository;
@@ -128,8 +128,9 @@ namespace NzbDrone.Core.Blacklisting
{ {
var blacklist = new Blacklist var blacklist = new Blacklist
{ {
SeriesId = message.SeriesId, SeriesId = 0,
EpisodeIds = message.EpisodeIds, EpisodeIds = message.EpisodeIds,
MovieId = message.MovieId,
SourceTitle = message.SourceTitle, SourceTitle = message.SourceTitle,
Quality = message.Quality, Quality = message.Quality,
Date = DateTime.UtcNow, Date = DateTime.UtcNow,
@@ -144,9 +145,9 @@ namespace NzbDrone.Core.Blacklisting
_blacklistRepository.Insert(blacklist); _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); _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 @@ namespace NzbDrone.Core.DecisionEngine
private int CompareProtocol(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 result = CompareBy(x.RemoteEpisode, y.RemoteEpisode, remoteEpisode =>
{ {
var delayProfile = _delayProfileService.BestForTags(remoteEpisode.Series.Tags); var delayProfile = _delayProfileService.BestForTags(remoteEpisode.Series.Tags);
@@ -75,15 +86,7 @@ namespace NzbDrone.Core.DecisionEngine
return downloadProtocol == delayProfile.PreferredProtocol; 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; return result;
} }
@@ -125,8 +128,8 @@ namespace NzbDrone.Core.DecisionEngine
private int CompareAgeIfUsenet(DownloadDecision x, DownloadDecision y) private int CompareAgeIfUsenet(DownloadDecision x, DownloadDecision y)
{ {
if (x.RemoteEpisode.Release.DownloadProtocol != DownloadProtocol.Usenet || if (x.RemoteMovie.Release.DownloadProtocol != DownloadProtocol.Usenet ||
y.RemoteEpisode.Release.DownloadProtocol != DownloadProtocol.Usenet) y.RemoteMovie.Release.DownloadProtocol != DownloadProtocol.Usenet)
{ {
return 0; return 0;
} }
@@ -32,7 +32,7 @@ namespace NzbDrone.Core.DecisionEngine
public List<DownloadDecision> GetRssDecision(List<ReleaseInfo> reports) public List<DownloadDecision> GetRssDecision(List<ReleaseInfo> reports)
{ {
return GetDecisions(reports).ToList(); return GetMovieDecisions(reports).ToList();
} }
public List<DownloadDecision> GetSearchDecision(List<ReleaseInfo> reports, SearchCriteriaBase searchCriteriaBase) public List<DownloadDecision> GetSearchDecision(List<ReleaseInfo> reports, SearchCriteriaBase searchCriteriaBase)
@@ -83,7 +83,7 @@ namespace NzbDrone.Core.DecisionEngine
{ {
if (parsedEpisodeInfo.Quality.HardcodedSubs.IsNotNullOrWhiteSpace()) if (parsedEpisodeInfo.Quality.HardcodedSubs.IsNotNullOrWhiteSpace())
{ {
remoteEpisode.DownloadAllowed = false; remoteEpisode.DownloadAllowed = true;
decision = new DownloadDecision(remoteEpisode, new Rejection("Hardcoded subs found: " + parsedEpisodeInfo.Quality.HardcodedSubs)); decision = new DownloadDecision(remoteEpisode, new Rejection("Hardcoded subs found: " + parsedEpisodeInfo.Quality.HardcodedSubs));
} }
else else
@@ -34,11 +34,11 @@ namespace NzbDrone.Core.DecisionEngine
public List<DownloadDecision> PrioritizeDecisionsForMovies(List<DownloadDecision> decisions) public List<DownloadDecision> PrioritizeDecisionsForMovies(List<DownloadDecision> decisions)
{ {
return decisions.Where(c => c.RemoteMovie.Movie != null) 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)); return downloadDecisions.OrderByDescending(decision => decision, new DownloadDecisionComparer(_delayProfileService));
}) })
.SelectMany(c => c)*/ .SelectMany(c => c)
.Union(decisions.Where(c => c.RemoteMovie.Movie == null)) .Union(decisions.Where(c => c.RemoteMovie.Movie == null))
.ToList(); .ToList();
} }
@@ -33,7 +33,11 @@ namespace NzbDrone.Core.DecisionEngine.Specifications
public Decision IsSatisfiedBy(RemoteMovie subject, SearchCriteriaBase searchCriteria) 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(); return Decision.Accept();
} }
@@ -32,9 +32,12 @@ namespace NzbDrone.Core.Download.Clients.Nzbget
protected override string AddFromNzbFile(RemoteEpisode remoteEpisode, string filename, byte[] fileContents) protected override string AddFromNzbFile(RemoteEpisode remoteEpisode, string filename, byte[] fileContents)
{ {
var category = Settings.TvCategory; var category = Settings.TvCategory;
var priority = remoteEpisode.IsRecentEpisode() ? Settings.RecentTvPriority : Settings.OlderTvPriority; 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) if (response == null)
{ {
@@ -47,9 +50,12 @@ namespace NzbDrone.Core.Download.Clients.Nzbget
protected override string AddFromNzbFile(RemoteMovie remoteMovie, string filename, byte[] fileContents) protected override string AddFromNzbFile(RemoteMovie remoteMovie, string filename, byte[] fileContents)
{ {
var category = Settings.TvCategory; // TODO: Update this to MovieCategory? var category = Settings.TvCategory; // TODO: Update this to MovieCategory?
var priority = Settings.RecentTvPriority; 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) if(response == null)
{ {
@@ -11,7 +11,7 @@ namespace NzbDrone.Core.Download.Clients.Nzbget
{ {
public interface INzbgetProxy 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); NzbgetGlobalStatus GetGlobalStatus(NzbgetSettings settings);
List<NzbgetQueueItem> GetQueue(NzbgetSettings settings); List<NzbgetQueueItem> GetQueue(NzbgetSettings settings);
List<NzbgetHistoryItem> GetHistory(NzbgetSettings settings); List<NzbgetHistoryItem> GetHistory(NzbgetSettings settings);
@@ -45,12 +45,12 @@ namespace NzbDrone.Core.Download.Clients.Nzbget
return version >= minimumVersion; 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)) if (HasVersion(16, settings))
{ {
var droneId = Guid.NewGuid().ToString().Replace("-", ""); 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) if (response <= 0)
{ {
return null; return null;
@@ -57,6 +57,9 @@ namespace NzbDrone.Core.Download.Clients.Nzbget
[FieldDefinition(7, Label = "Use SSL", Type = FieldType.Checkbox)] [FieldDefinition(7, Label = "Use SSL", Type = FieldType.Checkbox)]
public bool UseSsl { get; set; } 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() public NzbDroneValidationResult Validate()
{ {
return new NzbDroneValidationResult(Validator.Validate(this)); return new NzbDroneValidationResult(Validator.Validate(this));
@@ -133,7 +133,7 @@ namespace NzbDrone.Core.Indexers.Newznab
{ {
//Let's try anyways with q parameter, worst case nothing found. //Let's try anyways with q parameter, worst case nothing found.
pageableRequests.Add(GetPagedRequests(MaxPages, Settings.Categories, "search", pageableRequests.Add(GetPagedRequests(MaxPages, Settings.Categories, "search",
string.Format("&q={0}", searchCriteria.Movie.Title))); string.Format("&q={0}", Parser.Parser.NormalizeTitle(searchCriteria.Movie.Title))));
} }
return pageableRequests; return pageableRequests;
@@ -1,4 +1,5 @@
using System.Collections.Generic; using System;
using System.Collections.Generic;
using System.Net; using System.Net;
using System.Text.RegularExpressions; using System.Text.RegularExpressions;
using NzbDrone.Common.Http; using NzbDrone.Common.Http;
@@ -36,12 +37,11 @@ namespace NzbDrone.Core.Indexers.TorrentPotato
torrentInfo.Size = (long)torrent.size*1000*1000; torrentInfo.Size = (long)torrent.size*1000*1000;
torrentInfo.DownloadUrl = torrent.download_url; torrentInfo.DownloadUrl = torrent.download_url;
torrentInfo.InfoUrl = torrent.details_url; torrentInfo.InfoUrl = torrent.details_url;
torrentInfo.PublishDate = new System.DateTime(); torrentInfo.PublishDate = torrent.publish_date.ToUniversalTime();
torrentInfo.Seeders = torrent.seeders; torrentInfo.Seeders = torrent.seeders;
torrentInfo.Peers = torrent.leechers + torrent.seeders; torrentInfo.Peers = torrent.leechers + torrent.seeders;
torrentInfo.Freeleech = torrent.freeleech; torrentInfo.Freeleech = torrent.freeleech;
torrentInfo.PublishDate = torrent.publishdate.ToUniversalTime();
results.Add(torrentInfo); results.Add(torrentInfo);
} }
@@ -21,7 +21,7 @@ namespace NzbDrone.Core.Indexers.TorrentPotato
public int size { get; set; } public int size { get; set; }
public int leechers { get; set; } public int leechers { get; set; }
public int seeders { get; set; } public int seeders { get; set; }
public DateTime publishdate { get; set; } public DateTime publish_date { get; set; }
} }
} }
@@ -39,7 +39,13 @@ namespace NzbDrone.Core.Indexers.Torznab
protected override ReleaseInfo ProcessItem(XElement item, ReleaseInfo releaseInfo) protected override ReleaseInfo ProcessItem(XElement item, ReleaseInfo releaseInfo)
{ {
var torrentInfo = base.ProcessItem(item, releaseInfo) as TorrentInfo; 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; return torrentInfo;
} }
+3 -2
View File
@@ -64,9 +64,9 @@ namespace NzbDrone.Core.Jobs
new ScheduledTask{ Interval = 0.25f, TypeName = typeof(CheckForFinishedDownloadCommand).FullName}, new ScheduledTask{ Interval = 0.25f, TypeName = typeof(CheckForFinishedDownloadCommand).FullName},
new ScheduledTask{ Interval = 5, TypeName = typeof(MessagingCleanupCommand).FullName}, new ScheduledTask{ Interval = 5, TypeName = typeof(MessagingCleanupCommand).FullName},
new ScheduledTask{ Interval = 6*60, TypeName = typeof(ApplicationUpdateCommand).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 = 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 = 24*60, TypeName = typeof(HousekeepingCommand).FullName},
new ScheduledTask{ Interval = 7*24*60, TypeName = typeof(BackupCommand).FullName}, new ScheduledTask{ Interval = 7*24*60, TypeName = typeof(BackupCommand).FullName},
@@ -80,6 +80,7 @@ namespace NzbDrone.Core.Jobs
{ {
Interval = _configService.DownloadedEpisodesScanInterval, Interval = _configService.DownloadedEpisodesScanInterval,
TypeName = typeof(DownloadedEpisodesScanCommand).FullName TypeName = typeof(DownloadedEpisodesScanCommand).FullName
//TypeName = typeof(DownloadedMovieScanCommand).FullName
}, },
}; };
@@ -114,7 +114,7 @@ namespace NzbDrone.Core.MediaCover
} }
} }
private void EnsureCovers(Movie movie) private void EnsureCovers(Movie movie, int retried = 0)
{ {
foreach (var cover in movie.Images) foreach (var cover in movie.Images)
{ {
@@ -130,7 +130,25 @@ namespace NzbDrone.Core.MediaCover
} }
catch (WebException e) 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) catch (Exception e)
{ {
@@ -68,22 +68,22 @@ namespace NzbDrone.Core.MediaFiles.EpisodeImport
{ {
//check if already imported //check if already imported
if (importResults.Select(r => r.ImportDecision.LocalMovie.Movie) 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")); importResults.Add(new ImportResult(importDecision, "Movie has already been imported"));
continue; continue;
} }
var episodeFile = new MovieFile(); var movieFile = new MovieFile();
episodeFile.DateAdded = DateTime.UtcNow; movieFile.DateAdded = DateTime.UtcNow;
episodeFile.MovieId = localMovie.Movie.Id; movieFile.MovieId = localMovie.Movie.Id;
episodeFile.Path = localMovie.Path.CleanFilePath(); movieFile.Path = localMovie.Path.CleanFilePath();
episodeFile.Size = _diskProvider.GetFileSize(localMovie.Path); movieFile.Size = _diskProvider.GetFileSize(localMovie.Path);
episodeFile.Quality = localMovie.Quality; movieFile.Quality = localMovie.Quality;
episodeFile.MediaInfo = localMovie.MediaInfo; movieFile.MediaInfo = localMovie.MediaInfo;
episodeFile.Movie = localMovie.Movie; movieFile.Movie = localMovie.Movie;
episodeFile.ReleaseGroup = localMovie.ParsedMovieInfo.ReleaseGroup; movieFile.ReleaseGroup = localMovie.ParsedMovieInfo.ReleaseGroup;
episodeFile.Edition = localMovie.ParsedMovieInfo.Edition; movieFile.Edition = localMovie.ParsedMovieInfo.Edition;
bool copyOnly; bool copyOnly;
switch (importMode) switch (importMode)
@@ -102,17 +102,17 @@ namespace NzbDrone.Core.MediaFiles.EpisodeImport
if (newDownload) 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; oldFiles = moveResult.OldFiles;
} }
else 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)); importResults.Add(new ImportResult(importDecision));
if (newDownload) if (newDownload)
@@ -122,22 +122,22 @@ namespace NzbDrone.Core.MediaFiles.EpisodeImport
if (downloadClientItem != null) 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 else
{ {
_eventAggregator.PublishEvent(new MovieImportedEvent(localMovie, episodeFile, newDownload)); _eventAggregator.PublishEvent(new MovieImportedEvent(localMovie, movieFile, newDownload));
} }
if (newDownload) if (newDownload)
{ {
_eventAggregator.PublishEvent(new MovieDownloadedEvent(localMovie, episodeFile, oldFiles)); _eventAggregator.PublishEvent(new MovieDownloadedEvent(localMovie, movieFile, oldFiles));
} }
} }
catch (Exception e) catch (Exception e)
{ {
_logger.Warn(e, "Couldn't import episode " + localMovie); _logger.Warn(e, "Couldn't import movie " + localMovie);
importResults.Add(new ImportResult(importDecision, "Failed to import episode")); importResults.Add(new ImportResult(importDecision, "Failed to import movie"));
} }
} }
+2 -2
View File
@@ -3,7 +3,7 @@
public enum FileDateType public enum FileDateType
{ {
None = 0, None = 0,
LocalAirDate = 1, Cinemas = 1,
UtcAirDate = 2 Release = 2
} }
} }
@@ -111,11 +111,11 @@ namespace NzbDrone.Core.MediaFiles
public List<string> FilterExistingFiles(List<string> files, Movie movie) 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) public EpisodeFile Get(int id)
@@ -1,4 +1,4 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO; using System.IO;
using NLog; using NLog;
@@ -18,14 +18,17 @@ namespace NzbDrone.Core.MediaFiles
public class MediaFileTableCleanupService : IMediaFileTableCleanupService public class MediaFileTableCleanupService : IMediaFileTableCleanupService
{ {
private readonly IMediaFileService _mediaFileService; private readonly IMediaFileService _mediaFileService;
private readonly IMovieService _movieService;
private readonly IEpisodeService _episodeService; private readonly IEpisodeService _episodeService;
private readonly Logger _logger; private readonly Logger _logger;
public MediaFileTableCleanupService(IMediaFileService mediaFileService, public MediaFileTableCleanupService(IMediaFileService mediaFileService,
IMovieService movieService,
IEpisodeService episodeService, IEpisodeService episodeService,
Logger logger) Logger logger)
{ {
_mediaFileService = mediaFileService; _mediaFileService = mediaFileService;
_movieService = movieService;
_episodeService = episodeService; _episodeService = episodeService;
_logger = logger; _logger = logger;
} }
@@ -89,61 +92,39 @@ namespace NzbDrone.Core.MediaFiles
public void Clean(Movie movie, List<string> filesOnDisk) public void Clean(Movie movie, List<string> filesOnDisk)
{ {
var movieFiles = _mediaFileService.GetFilesByMovie(movie.Id);
//TODO: Update implementation for movies.
var seriesFiles = _mediaFileService.GetFilesBySeries(movie.Id);
var episodes = _episodeService.GetEpisodeBySeries(movie.Id);
var filesOnDiskKeys = new HashSet<string>(filesOnDisk, PathEqualityComparer.Instance); var filesOnDiskKeys = new HashSet<string>(filesOnDisk, PathEqualityComparer.Instance);
foreach (var seriesFile in seriesFiles) foreach(var movieFile in movieFiles)
{ {
var episodeFile = seriesFile; var movieFilePath = Path.Combine(movie.Path, movieFile.RelativePath);
var episodeFilePath = Path.Combine(movie.Path, episodeFile.RelativePath);
try try
{ {
if (!filesOnDiskKeys.Contains(episodeFilePath)) if (!filesOnDiskKeys.Contains(movieFilePath))
{ {
_logger.Debug("File [{0}] no longer exists on disk, removing from db", episodeFilePath); _logger.Debug("File [{0}] no longer exists on disk, removing from db", movieFilePath);
_mediaFileService.Delete(seriesFile, DeleteMediaFileReason.MissingFromDisk); _mediaFileService.Delete(movieFile, DeleteMediaFileReason.MissingFromDisk);
continue; continue;
} }
if (episodes.None(e => e.EpisodeFileId == episodeFile.Id)) //var localMovie = _parsingService.GetLocalMovie(movieFile.Path, movie);
{
_logger.Debug("File [{0}] is not assigned to any episodes, removing from db", episodeFilePath);
_mediaFileService.Delete(episodeFile, DeleteMediaFileReason.NoLinkedEpisodes);
continue;
}
// var localEpsiode = _parsingService.GetLocalEpisode(episodeFile.Path, series); //if (localMovie == null)
// //{
// if (localEpsiode == null || episodes.Count != localEpsiode.Episodes.Count) // _logger.Debug("File [{0}] parsed episodes has changed, removing from db", localMovie.Path);
// { // _mediaFileService.Delete(localMovie);
// _logger.Debug("File [{0}] parsed episodes has changed, removing from db", episodeFile.Path); // continue;
// _mediaFileService.Delete(episodeFile); //}
// continue;
// }
} }
catch (Exception ex) 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); _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 @@ namespace NzbDrone.Core.MediaFiles.MediaInfo
return AudioChannelPositionsText.ContainsIgnoreCase("LFE") ? AudioChannels - 1 + 0.1m : AudioChannels; 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 @@ namespace NzbDrone.Core.MediaFiles
switch (_configService.FileDate) switch (_configService.FileDate)
{ {
case FileDateType.LocalAirDate: case FileDateType.Release:
{ {
var airDate = episodes.First().AirDate; var airDate = episodes.First().AirDate;
var airTime = series.AirTime; var airTime = series.AirTime;
@@ -62,7 +62,7 @@ namespace NzbDrone.Core.MediaFiles
return ChangeFileDateToLocalAirDate(episodeFilePath, airDate, airTime); return ChangeFileDateToLocalAirDate(episodeFilePath, airDate, airTime);
} }
case FileDateType.UtcAirDate: case FileDateType.Cinemas:
{ {
var airDateUtc = episodes.First().AirDateUtc; var airDateUtc = episodes.First().AirDateUtc;
@@ -1,4 +1,4 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
@@ -20,7 +20,7 @@ namespace NzbDrone.Core.MediaFiles
} }
public class UpdateMovieFileService : IUpdateMovieFileService, public class UpdateMovieFileService : IUpdateMovieFileService,
IHandle<SeriesScannedEvent> IHandle<MovieScannedEvent>
{ {
private readonly IDiskProvider _diskProvider; private readonly IDiskProvider _diskProvider;
private readonly IConfigService _configService; private readonly IConfigService _configService;
@@ -47,17 +47,67 @@ namespace NzbDrone.Core.MediaFiles
{ {
var movieFilePath = Path.Combine(movie.Path, movieFile.RelativePath); 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; 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) if (_configService.FileDate == FileDateType.None)
{ {
return; return;
} }
/* var movies = _movieService.MoviesWithFiles(message.Series.Id); var movies = _movieService.MoviesWithFiles(message.Movie.Id);
var movieFiles = new List<MovieFile>(); var movieFiles = new List<MovieFile>();
var updated = new List<MovieFile>(); var updated = new List<MovieFile>();
@@ -69,7 +119,7 @@ namespace NzbDrone.Core.MediaFiles
movieFiles.Add(movieFile); movieFiles.Add(movieFile);
if (ChangeFileDate(movieFile, message.Series, moviesInFile)) if (ChangeFileDate(movieFile, message.Movie))
{ {
updated.Add(movieFile); updated.Add(movieFile);
} }
@@ -77,13 +127,13 @@ namespace NzbDrone.Core.MediaFiles
if (updated.Any()) 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 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) private bool ChangeFileDateToLocalAirDate(string filePath, string fileDate, string fileTime)
@@ -38,10 +38,13 @@ namespace NzbDrone.Core.MediaFiles
public MovieFileMoveResult UpgradeMovieFile(MovieFile episodeFile, LocalMovie localEpisode, bool copyOnly = false) public MovieFileMoveResult UpgradeMovieFile(MovieFile episodeFile, LocalMovie localEpisode, bool copyOnly = false)
{ {
_logger.Trace("Upgrading existing episode file.");
var moveFileResult = new MovieFileMoveResult(); var moveFileResult = new MovieFileMoveResult();
localEpisode.Movie.MovieFile.LazyLoad();
var existingFile = localEpisode.Movie.MovieFile; var existingFile = localEpisode.Movie.MovieFile;
existingFile.LazyLoad();
if (existingFile.IsLoaded) if (existingFile.IsLoaded && existingFile.Value != null)
{ {
var file = existingFile.Value; var file = existingFile.Value;
var episodeFilePath = Path.Combine(localEpisode.Movie.Path, file.RelativePath); var episodeFilePath = Path.Combine(localEpisode.Movie.Path, file.RelativePath);
@@ -55,6 +58,10 @@ namespace NzbDrone.Core.MediaFiles
moveFileResult.OldFiles.Add(file); moveFileResult.OldFiles.Add(file);
_mediaFileService.Delete(file, DeleteMediaFileReason.Upgrade); _mediaFileService.Delete(file, DeleteMediaFileReason.Upgrade);
} }
else
{
_logger.Warn("The existing movie file was not lazy loaded.");
}
@@ -66,6 +66,7 @@ namespace NzbDrone.Core.MetadataSource.SkyHook.Resource
public int vote_count { get; set; } public int vote_count { get; set; }
public AlternativeTitles alternative_titles { get; set; } public AlternativeTitles alternative_titles { get; set; }
public ReleaseDatesResource release_dates { get; set; } public ReleaseDatesResource release_dates { get; set; }
public VideosResource videos { get; set; }
} }
public class ReleaseDatesResource public class ReleaseDatesResource
@@ -130,4 +131,21 @@ namespace NzbDrone.Core.MetadataSource.SkyHook.Resource
public string iso_3166_1 { get; set; } public string iso_3166_1 { get; set; }
public string title { 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 @@ namespace NzbDrone.Core.MetadataSource.SkyHook
.SetSegment("route", "movie") .SetSegment("route", "movie")
.SetSegment("id", TmdbId.ToString()) .SetSegment("id", TmdbId.ToString())
.SetSegment("secondaryRoute", "") .SetSegment("secondaryRoute", "")
.AddQueryParam("append_to_response", "alternative_titles,release_dates") .AddQueryParam("append_to_response", "alternative_titles,release_dates,videos")
.AddQueryParam("country", "US") .AddQueryParam("country", "US")
.Build(); .Build();
@@ -89,9 +89,9 @@ namespace NzbDrone.Core.MetadataSource.SkyHook
movie.TmdbId = TmdbId; movie.TmdbId = TmdbId;
movie.ImdbId = resource.imdb_id; movie.ImdbId = resource.imdb_id;
movie.Title = resource.title; movie.Title = resource.title;
movie.TitleSlug = ToUrlSlug(movie.Title); movie.TitleSlug = ToUrlSlug(resource.title);
movie.CleanTitle = Parser.Parser.CleanSeriesTitle(movie.Title); movie.CleanTitle = Parser.Parser.CleanSeriesTitle(resource.title);
movie.SortTitle = Parser.Parser.NormalizeTitle(movie.Title); movie.SortTitle = Parser.Parser.NormalizeTitle(resource.title);
movie.Overview = resource.overview; movie.Overview = resource.overview;
movie.Website = resource.homepage; movie.Website = resource.homepage;
if (resource.release_date.IsNotNullOrWhiteSpace()) if (resource.release_date.IsNotNullOrWhiteSpace())
@@ -149,6 +149,29 @@ namespace NzbDrone.Core.MetadataSource.SkyHook
{ {
movie.Status = MovieStatusType.Announced; 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; return movie;
} }
@@ -186,6 +209,8 @@ namespace NzbDrone.Core.MetadataSource.SkyHook
{ {
var lowerTitle = title.ToLower(); var lowerTitle = title.ToLower();
lowerTitle = lowerTitle.Replace(".", "");
var parserResult = Parser.Parser.ParseMovieTitle(title, true); var parserResult = Parser.Parser.ParseMovieTitle(title, true);
var yearTerm = ""; var yearTerm = "";
@@ -193,7 +218,7 @@ namespace NzbDrone.Core.MetadataSource.SkyHook
if (parserResult != null && parserResult.MovieTitle != title) if (parserResult != null && parserResult.MovieTitle != title)
{ {
//Parser found something interesting! //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) if (parserResult.Year > 1800)
{ {
yearTerm = parserResult.Year.ToString(); yearTerm = parserResult.Year.ToString();
@@ -326,25 +351,19 @@ namespace NzbDrone.Core.MetadataSource.SkyHook
{ {
imdbMovie.SortTitle = Parser.Parser.NormalizeTitle(result.title); imdbMovie.SortTitle = Parser.Parser.NormalizeTitle(result.title);
imdbMovie.Title = result.title; imdbMovie.Title = result.title;
string titleSlug = ToUrlSlug(result.title); imdbMovie.TitleSlug = ToUrlSlug(result.title);
imdbMovie.TitleSlug = titleSlug.ToLower().Replace(" ", "-");
if (result.release_date.IsNotNullOrWhiteSpace()) if (result.release_date.IsNotNullOrWhiteSpace())
{ {
imdbMovie.Year = DateTime.Parse(result.release_date).Year; imdbMovie.Year = DateTime.Parse(result.release_date).Year;
} }
//var slugResult = _movieService.FindByTitleSlug(imdbMovie.TitleSlug);
//if (slugResult != null) imdbMovie.TitleSlug += "-" + imdbMovie.Year;
//{
// _logger.Debug("Movie with this title slug already exists. Adding year...");
//}
imdbMovie.TitleSlug += "-" + imdbMovie.Year.ToString();
imdbMovie.Images = new List<MediaCover.MediaCover>(); imdbMovie.Images = new List<MediaCover.MediaCover>();
imdbMovie.Overview = result.overview; imdbMovie.Overview = result.overview;
try try
{ {
string url = result.poster_path;
var imdbPoster = _configService.GetCoverForURL(result.poster_path, MediaCoverTypes.Poster); var imdbPoster = _configService.GetCoverForURL(result.poster_path, MediaCoverTypes.Poster);
imdbMovie.Images.Add(imdbPoster); imdbMovie.Images.Add(imdbPoster);
} }
@@ -2,7 +2,7 @@
{ {
public enum PushoverPriority public enum PushoverPriority
{ {
Silent = -1, Silent = -2,
Quiet = -1, Quiet = -1,
Normal = 0, Normal = 0,
High = 1, High = 1,
+4
View File
@@ -183,6 +183,10 @@
<Compile Include="Datastore\Migration\002_remove_tvrage_imdb_unique_constraint.cs" /> <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\003_remove_clean_title_from_scene_mapping.cs" />
<Compile Include="Datastore\Migration\004_updated_history.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\118_update_movie_slug.cs" />
<Compile Include="Datastore\Migration\117_update_movie_file.cs" /> <Compile Include="Datastore\Migration\117_update_movie_file.cs" />
<Compile Include="Datastore\Migration\116_update_movie_sorttitle_again.cs" /> <Compile Include="Datastore\Migration\116_update_movie_sorttitle_again.cs" />
+35 -2
View File
@@ -58,7 +58,7 @@ namespace NzbDrone.Core.Organizer
public static readonly Regex SeriesTitleRegex = new Regex(@"(?<token>\{(?:Series)(?<separator>[- ._])(Clean)?Title\})", public static readonly Regex SeriesTitleRegex = new Regex(@"(?<token>\{(?:Series)(?<separator>[- ._])(Clean)?Title\})",
RegexOptions.Compiled | RegexOptions.IgnoreCase); RegexOptions.Compiled | RegexOptions.IgnoreCase);
public static readonly Regex MovieTitleRegex = new Regex(@"(?<token>\{(?:Movie)(?<separator>[- ._])(Clean)?Title\})", public static readonly Regex MovieTitleRegex = new Regex(@"(?<token>\{((?:(Movie|Original))(?<separator>[- ._])(Clean)?Title(The)?)\})",
RegexOptions.Compiled | RegexOptions.IgnoreCase); RegexOptions.Compiled | RegexOptions.IgnoreCase);
private static readonly Regex FileNameCleanupRegex = new Regex(@"([- ._])(\1)+", RegexOptions.Compiled); private static readonly Regex FileNameCleanupRegex = new Regex(@"([- ._])(\1)+", RegexOptions.Compiled);
@@ -161,8 +161,10 @@ namespace NzbDrone.Core.Organizer
AddMovieTokens(tokenHandlers, movie); AddMovieTokens(tokenHandlers, movie);
AddReleaseDateTokens(tokenHandlers, movie.Year); //In case we want to separate the year AddReleaseDateTokens(tokenHandlers, movie.Year); //In case we want to separate the year
AddImdbIdTokens(tokenHandlers, movie.ImdbId);
AddQualityTokens(tokenHandlers, movie, movieFile); AddQualityTokens(tokenHandlers, movie, movieFile);
AddMediaInfoTokens(tokenHandlers, movieFile); AddMediaInfoTokens(tokenHandlers, movieFile);
AddMovieFileTokens(tokenHandlers, movieFile);
var fileName = ReplaceTokens(pattern, tokenHandlers, namingConfig).Trim(); var fileName = ReplaceTokens(pattern, tokenHandlers, namingConfig).Trim();
fileName = FileNameCleanupRegex.Replace(fileName, match => match.Captures[0].Value[0].ToString()); fileName = FileNameCleanupRegex.Replace(fileName, match => match.Captures[0].Value[0].ToString());
@@ -300,6 +302,7 @@ namespace NzbDrone.Core.Organizer
AddMovieTokens(tokenHandlers, movie); AddMovieTokens(tokenHandlers, movie);
AddReleaseDateTokens(tokenHandlers, movie.Year); AddReleaseDateTokens(tokenHandlers, movie.Year);
AddImdbIdTokens(tokenHandlers, movie.ImdbId);
return CleanFolderName(ReplaceTokens(namingConfig.MovieFolderFormat, tokenHandlers, namingConfig)); return CleanFolderName(ReplaceTokens(namingConfig.MovieFolderFormat, tokenHandlers, namingConfig));
} }
@@ -313,11 +316,27 @@ namespace NzbDrone.Core.Organizer
return 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) public static string CleanFileName(string name, bool replace = true)
{ {
string result = name; string result = name;
string[] badCharacters = { "\\", "/", "<", ">", "?", "*", ":", "|", "\"" }; string[] badCharacters = { "\\", "/", "<", ">", "?", "*", ":", "|", "\"" };
string[] goodCharacters = { "+", "+", "", "", "!", "-", "-", "", "" }; string[] goodCharacters = { "+", "+", "", "", "!", "-", "", "", "" };
for (int i = 0; i < badCharacters.Length; i++) for (int i = 0; i < badCharacters.Length; i++)
{ {
@@ -469,6 +488,7 @@ namespace NzbDrone.Core.Organizer
{ {
tokenHandlers["{Movie Title}"] = m => movie.Title; tokenHandlers["{Movie Title}"] = m => movie.Title;
tokenHandlers["{Movie CleanTitle}"] = m => CleanTitle(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) private void AddReleaseDateTokens(Dictionary<string, Func<TokenMatch, string>> tokenHandlers, int releaseYear)
@@ -476,6 +496,11 @@ namespace NzbDrone.Core.Organizer
tokenHandlers["{Release Year}"] = m => string.Format("{0}", releaseYear.ToString()); //Do I need m.CustomFormat? 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) private void AddSeasonTokens(Dictionary<string, Func<TokenMatch, string>> tokenHandlers, int seasonNumber)
{ {
tokenHandlers["{Season}"] = m => seasonNumber.ToString(m.CustomFormat); tokenHandlers["{Season}"] = m => seasonNumber.ToString(m.CustomFormat);
@@ -503,6 +528,14 @@ namespace NzbDrone.Core.Organizer
tokenHandlers["{Release Group}"] = m => episodeFile.ReleaseGroup ?? m.DefaultValue("Sonarr"); tokenHandlers["{Release Group}"] = m => episodeFile.ReleaseGroup ?? m.DefaultValue("Sonarr");
} }
private void AddMovieFileTokens(Dictionary<string, Func<TokenMatch, string>> tokenHandlers, MovieFile episodeFile)
{
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");
}
private void AddQualityTokens(Dictionary<string, Func<TokenMatch, string>> tokenHandlers, Series series, EpisodeFile episodeFile) private void AddQualityTokens(Dictionary<string, Func<TokenMatch, string>> tokenHandlers, Series series, EpisodeFile episodeFile)
{ {
var qualityTitle = _qualityDefinitionService.Get(episodeFile.Quality.Quality).Title; var qualityTitle = _qualityDefinitionService.Get(episodeFile.Quality.Quality).Title;
@@ -46,8 +46,9 @@ namespace NzbDrone.Core.Organizer
_movie = new Movie _movie = new Movie
{ {
Title = "Movie Title", Title = "The Movie Title",
Year = 2010 Year = 2010,
ImdbId = "tt0066921"
}; };
_standardSeries = new Series _standardSeries = new Series
+1 -1
View File
@@ -268,7 +268,7 @@ namespace NzbDrone.Core.Parser
private static readonly Regex ReportImdbId = new Regex(@"(?<imdbid>tt\d{9})", RegexOptions.IgnoreCase | RegexOptions.Compiled); private static readonly Regex ReportImdbId = new Regex(@"(?<imdbid>tt\d{9})", 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); RegexOptions.IgnoreCase | RegexOptions.Compiled);
private static readonly Regex WebsitePrefixRegex = new Regex(@"^\[\s*[a-z]+(\.[a-z]+)+\s*\][- ]*", private static readonly Regex WebsitePrefixRegex = new Regex(@"^\[\s*[a-z]+(\.[a-z]+)+\s*\][- ]*",
+1 -1
View File
@@ -28,7 +28,7 @@ namespace NzbDrone.Core.Parser
)\b", )\b",
RegexOptions.Compiled | RegexOptions.IgnoreCase | RegexOptions.IgnorePatternWhitespace); 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", private static readonly Regex RawHDRegex = new Regex(@"\b(?<rawhd>RawHD|1080i[-_. ]HDTV|Raw[-_. ]HD|MPEG[-_. ]?2)\b",
RegexOptions.Compiled | RegexOptions.IgnoreCase); RegexOptions.Compiled | RegexOptions.IgnoreCase);
+2
View File
@@ -48,6 +48,8 @@ namespace NzbDrone.Core.Tv
public LazyLoaded<MovieFile> MovieFile { get; set; } public LazyLoaded<MovieFile> MovieFile { get; set; }
public int MovieFileId { get; set; } public int MovieFileId { get; set; }
public List<string> AlternativeTitles { get; set; } public List<string> AlternativeTitles { get; set; }
public string YouTubeTrailerId{ get; set; }
public string Studio { get; set; }
public bool HasFile => MovieFileId > 0; public bool HasFile => MovieFileId > 0;
+43 -1
View File
@@ -3,7 +3,9 @@ using System.Linq;
using System.Collections.Generic; using System.Collections.Generic;
using NzbDrone.Core.Datastore; using NzbDrone.Core.Datastore;
using NzbDrone.Core.Messaging.Events; using NzbDrone.Core.Messaging.Events;
using NzbDrone.Core.Datastore.Extensions;
using Marr.Data.QGen;
using NzbDrone.Core.MediaFiles;
namespace NzbDrone.Core.Tv namespace NzbDrone.Core.Tv
{ {
@@ -14,6 +16,9 @@ namespace NzbDrone.Core.Tv
Movie FindByTitle(string cleanTitle, int year); Movie FindByTitle(string cleanTitle, int year);
Movie FindByImdbId(string imdbid); Movie FindByImdbId(string imdbid);
Movie FindByTitleSlug(string slug); 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); List<Movie> GetMoviesByFileId(int fileId);
void SetFileId(int fileId, int movieId); void SetFileId(int fileId, int movieId);
} }
@@ -119,5 +124,42 @@ namespace NzbDrone.Core.Tv
{ {
return Query.Where(m => m.TitleSlug == slug).FirstOrDefault(); 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);
}
} }
} }
+23
View File
@@ -12,6 +12,7 @@ using NzbDrone.Core.Parser;
using NzbDrone.Core.Tv.Events; using NzbDrone.Core.Tv.Events;
using NzbDrone.Core.MediaFiles; using NzbDrone.Core.MediaFiles;
using NzbDrone.Core.MediaFiles.Events; using NzbDrone.Core.MediaFiles.Events;
using NzbDrone.Core.Datastore;
namespace NzbDrone.Core.Tv namespace NzbDrone.Core.Tv
{ {
@@ -26,12 +27,15 @@ namespace NzbDrone.Core.Tv
Movie FindByTitleInexact(string title); Movie FindByTitleInexact(string title);
Movie FindByTitleSlug(string slug); Movie FindByTitleSlug(string slug);
Movie GetMovieByFileId(int fileId); 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); void DeleteMovie(int movieId, bool deleteFiles);
List<Movie> GetAllMovies(); List<Movie> GetAllMovies();
Movie UpdateMovie(Movie movie); Movie UpdateMovie(Movie movie);
List<Movie> UpdateMovie(List<Movie> movie); List<Movie> UpdateMovie(List<Movie> movie);
bool MoviePathExists(string folder); bool MoviePathExists(string folder);
void RemoveAddOptions(Movie movie); void RemoveAddOptions(Movie movie);
List<Movie> MoviesWithFiles(int movieId);
} }
public class MovieService : IMovieService, IHandle<MovieFileAddedEvent>, public class MovieService : IMovieService, IHandle<MovieFileAddedEvent>,
@@ -224,5 +228,24 @@ namespace NzbDrone.Core.Tv
{ {
return _movieRepository.FindByTitleSlug(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 @@ namespace NzbDrone.Core.Tv
movie.AlternativeTitles = movieInfo.AlternativeTitles; movie.AlternativeTitles = movieInfo.AlternativeTitles;
movie.Year = movieInfo.Year; movie.Year = movieInfo.Year;
movie.PhysicalRelease = movieInfo.PhysicalRelease; movie.PhysicalRelease = movieInfo.PhysicalRelease;
movie.YouTubeTrailerId = movieInfo.YouTubeTrailerId;
movie.Studio = movieInfo.Studio;
try try
{ {
Binary file not shown.
BIN
View File
Binary file not shown.
+6
View File
@@ -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 : { sortMappings : {
series : { series : {
sortValue : function(model, attr) { sortValue : function(model, attr) {
+10 -2
View File
@@ -17,7 +17,8 @@ module.exports = Marionette.Layout.extend({
events : { events : {
'click .x-import' : '_importMovies', 'click .x-import' : '_importMovies',
'click .x-add-new' : '_addMovies' 'click .x-add-new' : '_addMovies',
'click .x-show-existing' : '_toggleExisting'
}, },
attributes : { 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() { onShow : function() {
this.workspace.show(new AddMoviesView()); this.workspace.show(new AddMoviesView());
}, },
_folderSelected : function(options) { _folderSelected : function(options) {
vent.trigger(vent.Commands.CloseModalCommand); vent.trigger(vent.Commands.CloseModalCommand);
//TODO: Fix this shit.
this.workspace.show(new ExistingMoviesCollectionView({ model : options.model })); this.workspace.show(new ExistingMoviesCollectionView({ model : options.model }));
}, },
@@ -9,6 +9,33 @@
</div> </div>
</div> </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="row">
<div class="col-md-12"> <div class="col-md-12">
<div id="add-movies-workspace"></div> <div id="add-movies-workspace"></div>
+142 -141
View File
@@ -9,177 +9,178 @@ var ErrorView = require('./ErrorView');
var LoadingView = require('../Shared/LoadingView'); var LoadingView = require('../Shared/LoadingView');
module.exports = Marionette.Layout.extend({ module.exports = Marionette.Layout.extend({
template : 'AddMovies/AddMoviesViewTemplate', template : 'AddMovies/AddMoviesViewTemplate',
regions : { regions : {
searchResult : '#search-result' searchResult : '#search-result'
}, },
ui : { ui : {
moviesSearch : '.x-movies-search', moviesSearch : '.x-movies-search',
searchBar : '.x-search-bar', searchBar : '.x-search-bar',
loadMore : '.x-load-more' loadMore : '.x-load-more'
}, },
events : { events : {
'click .x-load-more' : '_onLoadMore' 'click .x-load-more' : '_onLoadMore'
}, },
initialize : function(options) { initialize : function(options) {
console.log(options); console.log(options);
this.isExisting = options.isExisting;
this.collection = new AddMoviesCollection();
if (this.isExisting) { this.isExisting = options.isExisting;
this.collection.unmappedFolderModel = this.model; this.collection = new AddMoviesCollection();
}
if (this.isExisting) { if (this.isExisting) {
this.className = 'existing-movies'; this.collection.unmappedFolderModel = this.model;
} else { }
this.className = 'new-movies';
}
this.listenTo(vent, vent.Events.MoviesAdded, this._onMoviesAdded); if (this.isExisting) {
this.listenTo(this.collection, 'sync', this._showResults); this.className = 'existing-movies';
} else {
this.className = 'new-movies';
}
this.resultCollectionView = new SearchResultCollectionView({ this.listenTo(vent, vent.Events.MoviesAdded, this._onMoviesAdded);
collection : this.collection, this.listenTo(this.collection, 'sync', this._showResults);
isExisting : this.isExisting
});
this.throttledSearch = _.debounce(this.search, 1000, { trailing : true }).bind(this); this.resultCollectionView = new SearchResultCollectionView({
}, collection : this.collection,
isExisting : this.isExisting
});
onRender : function() { this.throttledSearch = _.debounce(this.search, 1000, { trailing : true }).bind(this);
var self = this; },
this.$el.addClass(this.className); onRender : function() {
var self = this;
this.ui.moviesSearch.keyup(function(e) { this.$el.addClass(this.className);
if (_.contains([ this.ui.moviesSearch.keyup(function(e) {
9,
16,
17,
18,
19,
20,
33,
34,
35,
36,
37,
38,
39,
40,
91,
92,
93
], e.keyCode)) {
return;
}
self._abortExistingSearch(); if (_.contains([
self.throttledSearch({ 9,
term : self.ui.moviesSearch.val() 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._clearResults();
this.ui.searchBar.hide();
}
},
onShow : function() { if (this.isExisting) {
this.ui.moviesSearch.focus(); this.ui.searchBar.hide();
}, }
},
search : function(options) { onShow : function() {
var self = this; this.ui.moviesSearch.focus();
},
this.collection.reset(); search : function(options) {
var self = this;
if (!options.term || options.term === this.collection.term) { this.collection.reset();
return Marionette.$.Deferred().resolve();
}
this.searchResult.show(new LoadingView()); if (!options.term || options.term === this.collection.term) {
this.collection.term = options.term; return Marionette.$.Deferred().resolve();
this.currentSearchPromise = this.collection.fetch({ }
data : { term : options.term }
});
this.currentSearchPromise.fail(function() { this.searchResult.show(new LoadingView());
self._showError(); 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) { return this.currentSearchPromise;
if (this.isExisting && options.movie.get('path') === this.model.get('folder').path) { },
this.close();
}
else if (!this.isExisting) { _onMoviesAdded : function(options) {
this.resultCollectionView.setExisting(options.movie.get('tmdbId')); if (this.isExisting && options.movie.get('path') === this.model.get('folder').path) {
/*this.collection.term = ''; this.close();
this.collection.reset(); }
this._clearResults();
this.ui.moviesSearch.val('');
this.ui.moviesSearch.focus();*/ //TODO: Maybe add option wheter to clear search result.
}
},
_onLoadMore : function() { else if (!this.isExisting) {
var showingAll = this.resultCollectionView.showMore(); this.resultCollectionView.setExisting(options.movie.get('tmdbId'));
this.ui.searchBar.show(); /*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) { _onLoadMore : function() {
this.ui.loadMore.hide(); var showingAll = this.resultCollectionView.showMore();
} this.ui.searchBar.show();
},
_clearResults : function() { if (showingAll) {
this.ui.loadMore.hide();
}
},
if (!this.isExisting) { _clearResults : function() {
this.searchResult.show(new EmptyView());
} else {
this.searchResult.close();
}
},
_showResults : function() { if (!this.isExisting) {
if (!this.isClosed) { this.searchResult.show(new EmptyView());
if (this.collection.length === 0) { } else {
this.ui.searchBar.show(); this.searchResult.close();
this.searchResult.show(new NotFoundView({ term : this.collection.term })); }
} else { },
this.searchResult.show(this.resultCollectionView);
if (!this.showingAll && this.isExisting) {
this.ui.loadMore.show();
}
}
}
},
_abortExistingSearch : function() { _showResults : function() {
if (this.currentSearchPromise && this.currentSearchPromise.readyState > 0 && this.currentSearchPromise.readyState < 4) { if (!this.isClosed) {
console.log('aborting previous pending search request.'); if (this.collection.length === 0) {
this.currentSearchPromise.abort(); this.ui.searchBar.show();
} else { this.searchResult.show(new NotFoundView({ term : this.collection.term }));
this._clearResults(); } else {
} this.searchResult.show(this.resultCollectionView);
}, if (!this.showingAll) {
this.ui.loadMore.show();
}
}
}
},
_showError : function() { _abortExistingSearch : function() {
if (!this.isClosed) { if (this.currentSearchPromise && this.currentSearchPromise.readyState > 0 && this.currentSearchPromise.readyState < 4) {
this.ui.searchBar.show(); console.log('aborting previous pending search request.');
this.searchResult.show(new ErrorView({ term : this.collection.term })); this.currentSearchPromise.abort();
this.collection.term = ''; } 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 Marionette = require('marionette');
var AddMoviesView = require('../AddMoviesView'); var AddMoviesView = require('../AddMoviesView');
var UnmappedFolderCollection = require('./UnmappedFolderCollection'); var UnmappedFolderCollection = require('./UnmappedFolderCollection');
var vent = require('vent');
module.exports = Marionette.CompositeView.extend({ module.exports = Marionette.CompositeView.extend({
itemView : AddMoviesView, itemView : AddMoviesView,
@@ -48,7 +48,7 @@ var Layout = Marionette.Layout.extend({
var self = this; var self = this;
var newDir = new RootFolderModel({ var newDir = new RootFolderModel({
Path : this.ui.pathInput.val() Path : this.ui.pathInput.val(),
}); });
this.bindToModelValidation(newDir); this.bindToModelValidation(newDir);
@@ -31,6 +31,8 @@
</div> </div>
</div> </div>
<div class="modal-footer"> <div class="modal-footer">
<button class="btn" data-dismiss="modal">Close</button> <button class="btn" data-dismiss="modal">Close</button>
</div> </div>
</div> </div>
+54 -30
View File
@@ -1,41 +1,65 @@
var Marionette = require('marionette'); var Marionette = require('marionette');
var SearchResultView = require('./SearchResultView'); var SearchResultView = require('./SearchResultView');
var MoviesCollection = require('../Movies/MoviesCollection');
var vent = require('vent');
module.exports = Marionette.CollectionView.extend({ module.exports = Marionette.CollectionView.extend({
itemView : SearchResultView, itemView : SearchResultView,
initialize : function(options) { initialize : function(options) {
this.isExisting = options.isExisting; this.showExisting = true;
this.showing = 1; this.isExisting = options.isExisting;
}, this.showing = 5;
if (this.isExisting) {
this.showing = 1;
}
vent.on(vent.Commands.ShowExistingCommand, this._onExistingToggle.bind(this));
},
showAll : function() { _onExistingToggle : function(data) {
this.showingAll = true; this.showExisting = data.showExisting;
this.render();
},
showMore : function() { this.render();
this.showing += 5; },
this.render();
return this.showing >= this.collection.length; showAll : function() {
}, this.showingAll = true;
this.render();
},
setExisting : function(tmdbid) { showMore : function() {
var movies = this.collection.where({ tmdbId : tmdbid }); this.showing += 5;
console.warn(movies); this.render();
//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) { return this.showing >= this.collection.length;
if (!this.isExisting || index < this.showing || index === 0) { },
collectionView.$el.append(itemView.el);
} 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);
}
}
}
}); });
+4 -6
View File
@@ -43,7 +43,7 @@ var view = Marionette.ItemView.extend({
throw 'model is required'; throw 'model is required';
} }
console.log(this.route); //console.log(this.route);
this.templateHelpers = {}; this.templateHelpers = {};
this._configureTemplateHelpers(); this._configureTemplateHelpers();
@@ -92,14 +92,12 @@ var view = Marionette.ItemView.extend({
_configureTemplateHelpers : function() { _configureTemplateHelpers : function() {
var existingMovies = MoviesCollection.where({ tmdbId : this.model.get('tmdbId') }); var existingMovies = MoviesCollection.where({ tmdbId : this.model.get('tmdbId') });
console.log(existingMovies);
if (existingMovies.length > 0) { if (existingMovies.length > 0) {
this.templateHelpers.existing = existingMovies[0].toJSON(); this.templateHelpers.existing = existingMovies[0].toJSON();
} }
this.templateHelpers.profiles = Profiles.toJSON(); this.templateHelpers.profiles = Profiles.toJSON();
console.log(this.model); //console.log(this.templateHelpers.isExisting);
console.log(this.templateHelpers.existing);
if (!this.model.get('isExisting')) { if (!this.model.get('isExisting')) {
this.templateHelpers.rootFolders = RootFolders.toJSON(); this.templateHelpers.rootFolders = RootFolders.toJSON();
} }
@@ -185,8 +183,8 @@ var view = Marionette.ItemView.extend({
var self = this; var self = this;
var promise = this.model.save(); var promise = this.model.save();
console.log(this.model.save); //console.log(this.model.save);
console.log(promise); //console.log(promise);
if (searchForMovie) { if (searchForMovie) {
this.ui.addSearchButton.spinForPromise(promise); this.ui.addSearchButton.spinForPromise(promise);
+3 -3
View File
@@ -1,5 +1,5 @@
var Backbone = require('backbone'); var Backbone = require('backbone');
var EpisodeModel = require('../Series/EpisodeModel'); var EpisodeModel = require('../Movies/MovieModel');
module.exports = Backbone.Collection.extend({ module.exports = Backbone.Collection.extend({
url : window.NzbDrone.ApiRoot + '/calendar', url : window.NzbDrone.ApiRoot + '/calendar',
@@ -7,8 +7,8 @@ module.exports = Backbone.Collection.extend({
tableName : 'calendar', tableName : 'calendar',
comparator : function(model) { comparator : function(model) {
var date = new Date(model.get('airDateUtc')); var date = new Date(model.get('inCinemas'));
var time = date.getTime(); var time = date.getTime();
return time; return time;
} }
}); });
+50 -68
View File
@@ -1,75 +1,57 @@
<div class="modal-content"> <div class="modal-content">
<div class="modal-header"> <div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-hidden="true">&times;</button> <button type="button" class="close" data-dismiss="modal" aria-hidden="true">&times;</button>
<h3>Radarr Calendar feed</h3> <h3>Radarr Calendar feed</h3>
</div> </div>
<div class="modal-body edit-series-modal"> <div class="modal-body edit-series-modal">
<div class="form-horizontal"> <div class="form-horizontal">
<div class="form-group"> <div class="form-group">
<label class="col-sm-3 control-label">Include Unmonitored</label> <label class="col-sm-3 control-label">Include Unmonitored</label>
<div class="col-sm-4"> <div class="col-sm-4">
<div class="input-group"> <div class="input-group">
<label class="checkbox toggle well"> <label class="checkbox toggle well">
<input type="checkbox" name="includeUnmonitored" class="form-control x-includeUnmonitored"/> <input type="checkbox" name="includeUnmonitored" class="form-control x-includeUnmonitored"/>
<p> <p>
<span>Yes</span> <span>Yes</span>
<span>No</span> <span>No</span>
</p> </p>
<div class="btn btn-primary slide-button"/> <div class="btn btn-primary slide-button"/>
</label> </label>
</div> </div>
</div> </div>
</div> </div>
<div class="form-group"> <div class="form-group">
<label class="col-sm-3 control-label">Season Premiers Only</label> <label class="col-sm-3 control-label">Tags</label>
<div class="col-sm-4"> <div class="col-sm-1 col-sm-push-5 help-inline">
<div class="input-group"> <i class="icon-sonarr-form-info" title="One or more tags only show matching series" />
<label class="checkbox toggle well"> </div>
<input type="checkbox" name="premiersOnly" class="form-control x-premiersOnly"/>
<p> <div class="col-sm-5 col-sm-pull-1">
<span>Yes</span> <input type="text" class="form-control x-tags">
<span>No</span> </div>
</p> </div>
<div class="form-group">
<div class="btn btn-primary slide-button"/> <label class="col-sm-3 control-label">iCal feed</label>
</label> <div class="col-sm-1 col-sm-push-8 help-inline">
</div> <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>
</div> <div class="col-sm-8 col-sm-pull-1">
<div class="form-group"> <div class="input-group ical-url">
<label class="col-sm-3 control-label">Tags</label> <input type="text" class="form-control x-ical-url" readonly="readonly" />
<div class="input-group-btn">
<div class="col-sm-1 col-sm-push-5 help-inline"> <button class="btn btn-icon-only x-ical-copy"><i class="icon-sonarr-copy"></i></button>
<i class="icon-sonarr-form-info" title="One or more tags only show matching series" /> <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 class="col-sm-5 col-sm-pull-1"> </div>
<input type="text" class="form-control x-tags"> </div>
</div> </div>
</div> </div>
<div class="form-group"> <div class="modal-footer">
<label class="col-sm-3 control-label">iCal feed</label> <button class="btn" data-dismiss="modal">Close</button>
<div class="col-sm-1 col-sm-push-8 help-inline"> </div>
<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> </div>
+7 -7
View File
@@ -10,13 +10,13 @@
<div id="x-calendar" class="calendar"/> <div id="x-calendar" class="calendar"/>
<div class="legend calendar"> <div class="legend calendar">
<ul class='legend-labels'> <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="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="Episode hasn't aired yet"></span>Unaired</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="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="purple" title="Movie 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="danger" title="Movie 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="success" title="Movie 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="unmonitored" title="Movie is unmonitored"></span>Unmonitored</li>
</ul> </ul>
</div> </div>
</div> </div>
+219 -218
View File
@@ -12,273 +12,274 @@ require('fullcalendar');
require('jquery.easypiechart'); require('jquery.easypiechart');
module.exports = Marionette.ItemView.extend({ module.exports = Marionette.ItemView.extend({
storageKey : 'calendar.view', storageKey : 'calendar.view',
initialize : function() { initialize : function() {
this.showUnmonitored = Config.getValue('calendar.show', 'monitored') === 'all'; this.showUnmonitored = Config.getValue('calendar.show', 'monitored') === 'all';
this.collection = new CalendarCollection().bindSignalR({ updateOnly : true }); this.collection = new CalendarCollection().bindSignalR({ updateOnly : true });
this.listenTo(this.collection, 'change', this._reloadCalendarEvents); this.listenTo(this.collection, 'change', this._reloadCalendarEvents);
this.listenTo(QueueCollection, 'sync', this._reloadCalendarEvents); this.listenTo(QueueCollection, 'sync', this._reloadCalendarEvents);
}, },
render : function() { render : function() {
this.$el.empty().fullCalendar(this._getOptions()); this.$el.empty().fullCalendar(this._getOptions());
}, },
onShow : function() { onShow : function() {
this.$('.fc-today-button').click(); this.$('.fc-today-button').click();
}, },
setShowUnmonitored : function (showUnmonitored) { setShowUnmonitored : function (showUnmonitored) {
if (this.showUnmonitored !== showUnmonitored) { if (this.showUnmonitored !== showUnmonitored) {
this.showUnmonitored = showUnmonitored; this.showUnmonitored = showUnmonitored;
this._getEvents(this.$el.fullCalendar('getView')); this._getEvents(this.$el.fullCalendar('getView'));
} }
}, },
_viewRender : function(view, element) { _viewRender : function(view, element) {
if (Config.getValue(this.storageKey) !== view.name) { if (Config.getValue(this.storageKey) !== view.name) {
Config.setValue(this.storageKey, view.name); Config.setValue(this.storageKey, view.name);
} }
this._getEvents(view); this._getEvents(view);
element.find('.fc-day-grid-container').css('height', ''); element.find('.fc-day-grid-container').css('height', '');
}, },
_eventRender : function(event, element) { _eventRender : function(event, element) {
element.addClass(event.statusLevel); element.addClass(event.statusLevel);
element.children('.fc-content').addClass(event.statusLevel); element.children('.fc-content').addClass(event.statusLevel);
if (event.downloading) { if (event.downloading) {
var progress = 100 - event.downloading.get('sizeleft') / event.downloading.get('size') * 100; var progress = 100 - event.downloading.get('sizeleft') / event.downloading.get('size') * 100;
var releaseTitle = event.downloading.get('title'); var releaseTitle = event.downloading.get('title');
var estimatedCompletionTime = moment(event.downloading.get('estimatedCompletionTime')).fromNow(); var estimatedCompletionTime = moment(event.downloading.get('estimatedCompletionTime')).fromNow();
var status = event.downloading.get('status').toLocaleLowerCase(); var status = event.downloading.get('status').toLocaleLowerCase();
var errorMessage = event.downloading.get('errorMessage'); var errorMessage = event.downloading.get('errorMessage');
if (status === 'pending') { if (status === 'pending') {
this._addStatusIcon(element, 'icon-sonarr-pending', 'Release will be processed {0}'.format(estimatedCompletionTime)); this._addStatusIcon(element, 'icon-sonarr-pending', 'Release will be processed {0}'.format(estimatedCompletionTime));
} }
else if (errorMessage) { else if (errorMessage) {
if (status === 'completed') { if (status === 'completed') {
this._addStatusIcon(element, 'icon-sonarr-import-failed', 'Import failed: {0}'.format(errorMessage)); this._addStatusIcon(element, 'icon-sonarr-import-failed', 'Import failed: {0}'.format(errorMessage));
} else { } else {
this._addStatusIcon(element, 'icon-sonarr-download-failed', 'Download failed: {0}'.format(errorMessage)); this._addStatusIcon(element, 'icon-sonarr-download-failed', 'Download failed: {0}'.format(errorMessage));
} }
} }
else if (status === 'failed') { else if (status === 'failed') {
this._addStatusIcon(element, 'icon-sonarr-download-failed', 'Download failed: check download client for more details'); this._addStatusIcon(element, 'icon-sonarr-download-failed', 'Download failed: check download client for more details');
} }
else if (status === 'warning') { else if (status === 'warning') {
this._addStatusIcon(element, 'icon-sonarr-download-warning', 'Download warning: check download client for more details'); this._addStatusIcon(element, 'icon-sonarr-download-warning', 'Download warning: check download client for more details');
} }
else { else {
element.find('.fc-time').after('<span class="chart pull-right" data-percent="{0}"></span>'.format(progress)); element.find('.fc-time').after('<span class="chart pull-right" data-percent="{0}"></span>'.format(progress));
element.find('.chart').easyPieChart({ element.find('.chart').easyPieChart({
barColor : '#ffffff', barColor : '#ffffff',
trackColor : false, trackColor : false,
scaleColor : false, scaleColor : false,
lineWidth : 2, lineWidth : 2,
size : 14, size : 14,
animate : false animate : false
}); });
element.find('.chart').tooltip({ element.find('.chart').tooltip({
title : 'Episode is downloading - {0}% {1}'.format(progress.toFixed(1), releaseTitle), title : 'Episode is downloading - {0}% {1}'.format(progress.toFixed(1), releaseTitle),
container : '.fc' container : '.fc'
}); });
} }
} }
else if (event.model.get('unverifiedSceneNumbering')) { else if (event.model.get('unverifiedSceneNumbering')) {
this._addStatusIcon(element, 'icon-sonarr-form-warning', 'Scene number hasn\'t been verified yet.'); 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')) { _eventAfterAllRender : function () {
this._addStatusIcon(element, 'icon-sonarr-form-warning', 'Episode does not have an absolute episode number'); if ($(window).width() < 768) {
} this.$('.fc-center').show();
}, this.$('.calendar-title').remove();
_eventAfterAllRender : function () { var title = this.$('.fc-center').html();
if ($(window).width() < 768) { var titleDiv = '<div class="calendar-title">{0}</div>'.format(title);
this.$('.fc-center').show();
this.$('.calendar-title').remove();
var title = this.$('.fc-center').html(); this.$('.fc-toolbar').before(titleDiv);
var titleDiv = '<div class="calendar-title">{0}</div>'.format(title); this.$('.fc-center').hide();
}
this.$('.fc-toolbar').before(titleDiv); this._clearScrollBar();
this.$('.fc-center').hide(); },
}
this._clearScrollBar(); _windowResize : function () {
}, this._clearScrollBar();
},
_windowResize : function () { _getEvents : function(view) {
this._clearScrollBar(); var start = moment(view.start.toISOString()).toISOString();
}, var end = moment(view.end.toISOString()).toISOString();
_getEvents : function(view) { this.$el.fullCalendar('removeEvents');
var start = moment(view.start.toISOString()).toISOString();
var end = moment(view.end.toISOString()).toISOString();
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({ _setEventData : function(startD, endD, collection) {
data : { if (collection.length === 0) {
start : start, return;
end : end, }
unmonitored : this.showUnmonitored
},
success : this._setEventData.bind(this)
});
},
_setEventData : function(collection) { var events = [];
if (collection.length === 0) { var self = this;
return;
}
var events = []; collection.each(function(model) {
var self = this; 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 event = {
var seriesTitle = model.get('series').title; title : seriesTitle,
var start = model.get('airDateUtc'); start : moment(start),
var runtime = model.get('series').runtime; end : moment(end),
var end = moment(start).add('minutes', runtime).toISOString(); allDay : true,
statusLevel : self._getStatusLevel(model, end),
downloading : QueueCollection.findMovie(model.get('id')),
model : model,
sortOrder : 0
};
var event = { events.push(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); 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 statusLevel = 'primary';
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'; if (hasFile) {
statusLevel = 'success';
}
if (hasFile) { else if (downloading) {
statusLevel = 'success'; statusLevel = 'purple';
} }
else if (downloading) { else if (!monitored) {
statusLevel = 'purple'; statusLevel = 'unmonitored';
} }
else if (!monitored) { else if (status == "inCinemas") {
statusLevel = 'unmonitored'; statusLevel = 'premiere';
} }
else if (currentTime.isAfter(start) && currentTime.isBefore(end)) { else if (status == "released") {
statusLevel = 'warning'; statusLevel = 'danger';
} }
else if (start.isBefore(currentTime) && !hasFile) { else if (status == "announced") {
statusLevel = 'danger'; statusLevel = 'primary';
} }
else if (element.get('episodeNumber') === 1) { if (end.isBefore(currentTime.startOf('day'))) {
statusLevel = 'premiere'; statusLevel += ' past';
} }
if (end.isBefore(currentTime.startOf('day'))) { return statusLevel;
statusLevel += ' past'; },
}
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() { _getOptions : function() {
this.$el.fullCalendar('removeEvents'); var options = {
this._setEventData(this.collection); 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() { if ($(window).width() < 768) {
var options = { options.defaultView = Config.getValue(this.storageKey, 'listYear');
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.header = {
options.defaultView = Config.getValue(this.storageKey, 'basicDay'); left : 'prev,next today',
center : 'title',
right : 'listYear'
};
}
options.header = { else {
left : 'prev,next today', options.defaultView = Config.getValue(this.storageKey, 'month');
center : 'title',
right : 'basicWeek,basicDay'
};
}
else { options.header = {
options.defaultView = Config.getValue(this.storageKey, 'basicWeek'); left : 'prev,next today',
center : 'title',
right : 'month,listYear'
};
}
options.header = { options.titleFormat = "L";
left : 'prev,next today',
center : 'title',
right : 'month,basicWeek,basicDay'
};
}
options.titleFormat = { options.columnFormat = "L"/*{
month : 'MMMM YYYY', month : 'ddd',
week : UiSettings.get('shortDateFormat'), week : UiSettings.get('calendarWeekColumnHeader'),
day : UiSettings.get('longDateFormat') day : 'dddd'
}; };*///For now ignore settings. TODO update that.
options.columnFormat = { options.timeFormat = UiSettings.get('timeFormat');
month : 'ddd',
week : UiSettings.get('calendarWeekColumnHeader'),
day : 'dddd'
};
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) { _clearScrollBar : function () {
element.find('.fc-time').after('<span class="status pull-right"><i class="{0}"></i></span>'.format(icon)); // Remove height from calendar so we don't have another scroll bar
element.find('.status').tooltip({ this.$('.fc-day-grid-container').css('height', '');
title : tooltip, this.$('.fc-row.fc-widget-header').attr('style', '');
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', '');
}
});
+4 -4
View File
@@ -1,17 +1,17 @@
var Backbone = require('backbone'); var Backbone = require('backbone');
var moment = require('moment'); var moment = require('moment');
var EpisodeModel = require('../Series/EpisodeModel'); var EpisodeModel = require('../Movies/MovieModel');
module.exports = Backbone.Collection.extend({ module.exports = Backbone.Collection.extend({
url : window.NzbDrone.ApiRoot + '/calendar', url : window.NzbDrone.ApiRoot + '/calendar',
model : EpisodeModel, model : EpisodeModel,
comparator : function(model1, model2) { comparator : function(model1, model2) {
var airDate1 = model1.get('airDateUtc'); var airDate1 = model1.get('inCinemas');
var date1 = moment(airDate1); var date1 = moment(airDate1);
var time1 = date1.unix(); var time1 = date1.unix();
var airDate2 = model2.get('airDateUtc'); var airDate2 = model2.get('inCinemas');
var date2 = moment(airDate2); var date2 = moment(airDate2);
var time2 = date2.unix(); var time2 = date2.unix();
@@ -25,4 +25,4 @@ module.exports = Backbone.Collection.extend({
return 0; return 0;
} }
}); });
+3 -3
View File
@@ -11,8 +11,8 @@ module.exports = Marionette.ItemView.extend({
}, },
initialize : function() { initialize : function() {
var start = this.model.get('airDateUtc'); var start = this.model.get('inCinemas');
var runtime = this.model.get('series').runtime; var runtime = this.model.get('runtime');
var end = moment(start).add('minutes', runtime); var end = moment(start).add('minutes', runtime);
this.model.set({ this.model.set({
@@ -25,4 +25,4 @@ module.exports = Marionette.ItemView.extend({
_showEpisodeDetails : function() { _showEpisodeDetails : function() {
vent.trigger(vent.Commands.ShowEpisodeDetails, { episode : this.model }); vent.trigger(vent.Commands.ShowEpisodeDetails, { episode : this.model });
} }
}); });
+189 -178
View File
@@ -7,248 +7,259 @@
@import "../Content/Overrides/bootstrap"; @import "../Content/Overrides/bootstrap";
.calendar { .calendar {
width: 100%; width: 100%;
th, td { th, td {
border-color : #eeeeee; border-color : #eeeeee;
} }
.fc-event-skin { .fc-event-skin {
background-color : #007ccd; background-color : #007ccd;
border : 1px solid #007ccd; border : 1px solid #007ccd;
border-radius : 4px; border-radius : 4px;
text-align : center; text-align : center;
} }
.fc-event { .fc-event {
.clickable; .clickable;
.status { .status {
margin-right : 4px; margin-right : 4px;
} }
} }
th { th {
background-color : #eeeeee; background-color : #eeeeee;
} }
h2 { h2 {
font-size : 17.5px; font-size : 17.5px;
} }
.fc-state-highlight { .fc-state-highlight {
background : #dbdbdb; background : #dbdbdb;
} }
.past { .past {
opacity : 0.8; opacity : 0.8;
} }
.fc-title {
white-space: normal;
}
.fc-list-table {
.past {
opacity: 1.0;
}
}
} }
.event { .event {
display : inline-block; display : inline-block;
width : 100%; width : 100%;
margin-bottom : 10px; margin-bottom : 10px;
border-top : 1px solid #eeeeee; border-top : 1px solid #eeeeee;
padding-top : 10px; padding-top : 10px;
h4 { h4 {
font-weight : 500; font-weight : 500;
color : #008dcd; color : #008dcd;
margin : 5px 0px; margin : 5px 0px;
} }
p { p {
color : #999999; color : #999999;
margin : 0px; margin : 0px;
} }
.date { .date {
text-align : center; text-align : center;
display : inline-block; display : inline-block;
border-left : 4px solid #eeeeee; border-left : 4px solid #eeeeee;
padding-left : 16px; padding-left : 16px;
float : left; float : left;
margin-right : 20px; margin-right : 20px;
h4 { h4 {
line-height : 1em; line-height : 1em;
color : #555555; color : #555555;
font-weight : 300; font-weight : 300;
text-transform : uppercase; text-transform : uppercase;
} }
h1 { h1 {
font-weight : 500; font-weight : 500;
line-height : 0.8em; line-height : 0.8em;
} }
} }
.primary { .primary {
border-color : @btn-primary-bg; border-color : @btn-primary-bg;
} }
.info { .info {
border-color : @btn-info-bg; border-color : @btn-info-bg;
} }
.inverse { .inverse {
border-color : @btn-link-disabled-color; border-color : @btn-link-disabled-color;
} }
.warning { .warning {
border-color : @btn-warning-bg; border-color : @btn-warning-bg;
} }
.danger { .danger {
border-color : @btn-danger-bg; border-color : @btn-danger-bg;
} color: white;
}
.success { .success {
border-color : @btn-success-bg; border-color : @btn-success-bg;
} }
.purple { .purple {
border-color : @nzbdronePurple; border-color : @nzbdronePurple;
} }
.pink { .pink {
border-color : @nzbdronePink; border-color : @nzbdronePink;
} }
.premiere { .premiere {
border-color : @droneTeal; border-color : @droneTeal;
} }
.unmonitored { .unmonitored {
border-color : grey; border-color : grey;
} }
.episode-title { .episode-title {
.btn-link; .btn-link;
.text-overflow; .text-overflow;
color : @link-color; color : @link-color;
margin-top : 1px; margin-top : 1px;
display : inline-block; display : inline-block;
@media (max-width: @screen-xs-min) { @media (max-width: @screen-xs-min) {
width : 140px; width : 140px;
} }
@media (min-width: @screen-md-min) { @media (min-width: @screen-md-min) {
width : 135px; width : 135px;
} }
} }
} }
.calendar { .calendar {
// background-position : -160px -128px; // background-position : -160px -128px;
.primary { .primary {
border-color : @btn-primary-bg; border-color : @btn-primary-bg;
background-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 { .info {
border-color : @btn-info-bg; border-color : @btn-info-bg;
background-color : @btn-info-bg; background-color : @btn-info-bg;
} }
.inverse { .inverse {
border-color : @btn-link-disabled-color; border-color : @btn-link-disabled-color;
background-color : @btn-link-disabled-color; background-color : @btn-link-disabled-color;
} }
.warning { .warning {
border-color : @btn-warning-bg; border-color : @btn-warning-bg;
background-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 { .danger {
border-color : @btn-danger-bg; border-color : @btn-danger-bg;
background-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 { .purple {
border-color : @btn-success-bg; border-color : @nzbdronePurple;
background-color : @btn-success-bg; background-color : @nzbdronePurple;
} }
.purple { .pink {
border-color : @nzbdronePurple; border-color : @nzbdronePink;
background-color : @nzbdronePurple; background-color : @nzbdronePink;
} }
.pink { .premiere {
border-color : @nzbdronePink; border-color : @droneTeal;
background-color : @nzbdronePink; background-color : @droneTeal;
}
.premiere { .color-impaired-background-gradient(90deg, @droneTeal);
border-color : @droneTeal; }
background-color : @droneTeal;
.color-impaired-background-gradient(90deg, @droneTeal); .unmonitored {
} border-color : grey;
background-color : grey;
.unmonitored { .color-impaired-background-gradient(45deg, grey);
border-color : grey; }
background-color : grey;
.color-impaired-background-gradient(45deg, grey); .chart {
} margin-top : 2px;
margin-right : 2px;
line-height : 12px;
}
.chart { .legend-labels {
margin-top : 2px; max-width : 100%;
margin-right : 2px; width : 500px;
line-height : 12px;
}
.legend-labels { @media (max-width: @screen-xs-min) {
max-width : 100%; width : 100%;
width : 500px; }
}
@media (max-width: @screen-xs-min) { .legend-label {
width : 100%; display : inline-block;
} width : 150px;
} }
.legend-label {
display : inline-block;
width : 150px;
}
} }
.ical { .ical {
color: @btn-link-disabled-color; color: @btn-link-disabled-color;
cursor: pointer; cursor: pointer;
} }
.ical-url { .ical-url {
input, input[readonly] { input, input[readonly] {
cursor : text; cursor : text;
} }
} }
.calendar-title { .calendar-title {
text-align : center; text-align : center;
h2 { h2 {
margin-top : 0px; margin-top : 0px;
margin-bottom : 5px; margin-bottom : 5px;
} }
} }
.calendar-toolbar { .calendar-toolbar {
.page-toolbar { .page-toolbar {
margin-bottom : 10px; margin-bottom : 10px;
} }
} }
+5
View File
@@ -8,4 +8,9 @@
{{#if imdbId}} {{#if imdbId}}
<a href="{{imdbUrl}}" class="label label-info">IMDB</a> <a href="{{imdbUrl}}" class="label label-info">IMDB</a>
{{/if}} {{/if}}
{{#if youTubeTrailerId}}
<a href="{{youTubeTrailerUrl}}" class="label label-info">Trailer</a>
{{/if}}
</span> </span>
+42
View 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>&nbsp;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>&nbsp;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>&nbsp;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>&nbsp;Announced</div>');
this._setStatusWeight(1);
}
return this;
},
_setStatusWeight : function(weight) {
this.model.set('statusWeight', weight, { silent : true });
}
});
+4
View File
@@ -55,6 +55,10 @@
width : 150px; width : 150px;
} }
.movie-status-text-cell {
width : 150px;
}
.history-event-type-cell { .history-event-type-cell {
width : 10px; width : 10px;
} }
+420 -85
View File
@@ -1,7 +1,7 @@
/*! /*!
* FullCalendar v2.3.2 Stylesheet * FullCalendar v3.1.0 Stylesheet
* Docs & License: http://fullcalendar.io/ * 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 tbody,
.fc-unthemed .fc-divider, .fc-unthemed .fc-divider,
.fc-unthemed .fc-row, .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; border-color: #ddd;
} }
@@ -37,7 +40,8 @@ body .fc { /* extra precedence to overcome jqui */
} }
.fc-unthemed .fc-divider, .fc-unthemed .fc-divider,
.fc-unthemed .fc-popover .fc-header { .fc-unthemed .fc-popover .fc-header,
.fc-unthemed .fc-list-heading td {
background: #eee; background: #eee;
} }
@@ -45,20 +49,18 @@ body .fc { /* extra precedence to overcome jqui */
color: #666; color: #666;
} }
.fc-unthemed .fc-today { .fc-unthemed td.fc-today {
background: #fcf8e3; background: #fcf8e3;
} }
.fc-highlight { /* when user is selecting cells */ .fc-highlight { /* when user is selecting cells */
background: #bce8f1; background: #bce8f1;
opacity: .3; opacity: .3;
filter: alpha(opacity=30); /* for IE */
} }
.fc-bgevent { /* default look for background events */ .fc-bgevent { /* default look for background events */
background: rgb(143, 223, 130); background: rgb(143, 223, 130);
opacity: .3; opacity: .3;
filter: alpha(opacity=30); /* for IE */
} }
.fc-nonbusiness { /* default look for non-business-hours areas */ .fc-nonbusiness { /* default look for non-business-hours areas */
@@ -72,7 +74,6 @@ body .fc { /* extra precedence to overcome jqui */
.fc-icon { .fc-icon {
display: inline-block; display: inline-block;
width: 1em;
height: 1em; height: 1em;
line-height: 1em; line-height: 1em;
font-size: 1em; font-size: 1em;
@@ -99,7 +100,6 @@ NOTE: use percentage font sizes or else old IE chokes
.fc-icon:after { .fc-icon:after {
position: relative; position: relative;
margin: 0 -1em; /* ensures character will be centered, regardless of width */
} }
.fc-icon-left-single-arrow:after { .fc-icon-left-single-arrow:after {
@@ -107,7 +107,6 @@ NOTE: use percentage font sizes or else old IE chokes
font-weight: bold; font-weight: bold;
font-size: 200%; font-size: 200%;
top: -7%; top: -7%;
left: 3%;
} }
.fc-icon-right-single-arrow:after { .fc-icon-right-single-arrow:after {
@@ -115,7 +114,6 @@ NOTE: use percentage font sizes or else old IE chokes
font-weight: bold; font-weight: bold;
font-size: 200%; font-size: 200%;
top: -7%; top: -7%;
left: -3%;
} }
.fc-icon-left-double-arrow:after { .fc-icon-left-double-arrow:after {
@@ -134,14 +132,12 @@ NOTE: use percentage font sizes or else old IE chokes
content: "\25C4"; content: "\25C4";
font-size: 125%; font-size: 125%;
top: 3%; top: 3%;
left: -2%;
} }
.fc-icon-right-triangle:after { .fc-icon-right-triangle:after {
content: "\25BA"; content: "\25BA";
font-size: 125%; font-size: 125%;
top: 3%; top: 3%;
left: 2%;
} }
.fc-icon-down-triangle:after { .fc-icon-down-triangle:after {
@@ -252,7 +248,6 @@ NOTE: use percentage font sizes or else old IE chokes
cursor: default; cursor: default;
background-image: none; background-image: none;
opacity: 0.65; opacity: 0.65;
filter: alpha(opacity=65);
box-shadow: none; box-shadow: none;
} }
@@ -372,6 +367,7 @@ hr.fc-divider {
.fc table { .fc table {
width: 100%; width: 100%;
box-sizing: border-box; /* fix scrollbar issue in firefox */
table-layout: fixed; table-layout: fixed;
border-collapse: collapse; border-collapse: collapse;
border-spacing: 0; 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 /* Fake Table Rows
--------------------------------------------------------------------------------------------------*/ --------------------------------------------------------------------------------------------------*/
@@ -491,15 +499,15 @@ temporary rendered events).
/* Scrolling Container /* Scrolling Container
--------------------------------------------------------------------------------------------------*/ --------------------------------------------------------------------------------------------------*/
.fc-scroller { /* this class goes on elements for guaranteed vertical scrollbars */ .fc-scroller {
overflow-y: scroll; -webkit-overflow-scrolling: touch;
overflow-x: hidden;
} }
.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 */ position: relative; /* re-scope all positions */
width: 100%; /* hack to force re-sizing this inner element when scrollbars appear/disappear */ 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; line-height: 1.3;
border-radius: 3px; border-radius: 3px;
border: 1px solid #3a87ad; /* default BORDER color */ border: 1px solid #3a87ad; /* default BORDER color */
background-color: #3a87ad; /* default BACKGROUND color */
font-weight: normal; /* undo jqui's ui-widget-header bold */ 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 */ /* overpower some of bootstrap's and jqui's styles on <a> tags */
.fc-event, .fc-event,
.fc-event:hover, .fc-event:hover,
@@ -539,7 +551,6 @@ temporary rendered events).
z-index: 1; z-index: 1;
background: #fff; background: #fff;
opacity: .25; opacity: .25;
filter: alpha(opacity=25); /* for IE */
} }
.fc-event .fc-content { .fc-event .fc-content {
@@ -547,15 +558,68 @@ temporary rendered events).
z-index: 2; z-index: 2;
} }
/* resizer (cursor AND touch devices) */
.fc-event .fc-resizer { .fc-event .fc-resizer {
position: absolute; 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 /* 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 */ /* events that are continuing to/from another week. kill rounded corners and butt up against edge */
.fc-ltr .fc-h-event.fc-not-start, .fc-ltr .fc-h-event.fc-not-start,
@@ -576,36 +640,56 @@ temporary rendered events).
border-bottom-right-radius: 0; border-bottom-right-radius: 0;
} }
/* resizer */ /* resizer (cursor AND touch devices) */
.fc-h-event .fc-resizer { /* positioned it to overcome the event's borders */
top: -1px;
bottom: -1px;
left: -1px;
right: -1px;
width: 5px;
}
/* left resizer */ /* left resizer */
.fc-ltr .fc-h-event .fc-start-resizer, .fc-ltr .fc-h-event .fc-start-resizer,
.fc-ltr .fc-h-event .fc-start-resizer:before, .fc-rtl .fc-h-event .fc-end-resizer {
.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 */
cursor: w-resize; cursor: w-resize;
left: -1px; /* overcome border */
} }
/* right resizer */ /* right resizer */
.fc-ltr .fc-h-event .fc-end-resizer, .fc-ltr .fc-h-event .fc-end-resizer,
.fc-ltr .fc-h-event .fc-end-resizer:before, .fc-rtl .fc-h-event .fc-start-resizer {
.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 */
cursor: e-resize; 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; 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 */ .fc-day-grid-event .fc-content { /* force events to be one-line tall */
white-space: nowrap; white-space: nowrap;
@@ -630,10 +731,18 @@ be a descendant of the grid when it is being dragged.
font-weight: bold; font-weight: bold;
} }
.fc-day-grid-event .fc-resizer { /* enlarge the default hit area */ /* resizer (cursor devices) */
left: -3px;
right: -3px; /* left resizer */
width: 7px; .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; 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 /* Toolbar
--------------------------------------------------------------------------------------------------*/ --------------------------------------------------------------------------------------------------*/
.fc-toolbar { .fc-toolbar {
text-align: center; text-align: center;
}
.fc-toolbar.fc-header-toolbar {
margin-bottom: 1em; margin-bottom: 1em;
} }
.fc-toolbar.fc-footer-toolbar {
margin-top: 1em;
}
.fc-toolbar .fc-left { .fc-toolbar .fc-left {
float: left; float: left;
} }
@@ -753,6 +894,8 @@ a.fc-more:hover {
z-index: 1; z-index: 1;
} }
/* BasicView /* BasicView
--------------------------------------------------------------------------------------------------*/ --------------------------------------------------------------------------------------------------*/
@@ -760,8 +903,7 @@ a.fc-more:hover {
.fc-basicWeek-view .fc-content-skeleton, .fc-basicWeek-view .fc-content-skeleton,
.fc-basicDay-view .fc-content-skeleton { .fc-basicDay-view .fc-content-skeleton {
/* we are sure there are no day numbers in these views, so... */ /* there may be week numbers in these views, so no padding-top */
padding-top: 1px; /* add a pixel to make sure there are 2px padding above events */
padding-bottom: 1em; /* ensure a space at bottom of cell for user selecting/clicking */ 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 */ /* week and day number styling */
.fc-day-top.fc-other-month {
opacity: 0.3;
}
.fc-basic-view .fc-week-number, .fc-basic-view .fc-week-number,
.fc-basic-view .fc-day-number { .fc-basic-view .fc-day-number {
padding: 0 2px; padding: 2px;
} }
.fc-basic-view td.fc-week-number span, .fc-basic-view th.fc-week-number,
.fc-basic-view td.fc-day-number { .fc-basic-view th.fc-day-number {
padding-top: 2px; padding: 0 2px; /* column headers can't have as much v space */
padding-bottom: 2px;
} }
.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; 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 */ /* work around the way we do column resizing and ensure a minimum width */
display: inline-block; display: inline-block;
min-width: 1.25em; 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 /* AgendaView all-day area
--------------------------------------------------------------------------------------------------*/ --------------------------------------------------------------------------------------------------*/
@@ -834,7 +979,6 @@ a.fc-more:hover {
} }
.fc-agenda-view .fc-day-grid .fc-row .fc-content-skeleton { .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 */ padding-bottom: 1em; /* give space underneath events for clicking/selecting days */
} }
@@ -888,27 +1032,46 @@ a.fc-more:hover {
z-index: 2; 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 { .fc-time-grid .fc-content-skeleton {
position: absolute; position: absolute;
z-index: 3;
top: 0; top: 0;
left: 0; left: 0;
right: 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; z-index: 3;
} }
.fc-time-grid .fc-highlight-skeleton { .fc-time-grid .fc-event-container {
position: relative;
z-index: 4; z-index: 4;
} }
.fc-time-grid .fc-content-skeleton { .fc-time-grid .fc-now-indicator-line {
z-index: 5; 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; z-index: 6;
} }
@@ -948,11 +1111,6 @@ a.fc-more:hover {
/* TimeGrid Event Containment /* 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) */ .fc-ltr .fc-time-grid .fc-event-container { /* space on the sides of events for LTR (default) */
margin: 0 2.5% 0 2px; 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 */ 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-time,
.fc-time-grid-event .fc-title { .fc-time-grid-event .fc-title {
padding: 0 1px; padding: 0 1px;
@@ -1049,9 +1221,9 @@ be a descendant of the grid when it is being dragged.
padding: 0; /* undo padding from above */ 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; left: 0;
right: 0; right: 0;
bottom: 0; bottom: 0;
@@ -1064,6 +1236,169 @@ be a descendant of the grid when it is being dragged.
cursor: s-resize; cursor: s-resize;
} }
.fc-time-grid-event .fc-resizer:after { .fc-time-grid-event.fc-allow-mouse-resize .fc-resizer:after {
content: "="; 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;
}
+2 -2
View File
@@ -21,7 +21,7 @@ Handlebars.registerHelper('StatusLevel', function() {
var start = moment(this.airDateUtc); var start = moment(this.airDateUtc);
var end = moment(this.end); var end = moment(this.end);
var monitored = this.series.monitored && this.monitored; var monitored = this.series.monitored && this.monitored;
debugger;
if (hasFile) { if (hasFile) {
return 'success'; return 'success';
} }
@@ -63,4 +63,4 @@ Handlebars.registerHelper('EpisodeProgressClass', function() {
} }
return 'progress-bar-warning'; return 'progress-bar-warning';
}); });
+4
View File
@@ -51,6 +51,10 @@ Handlebars.registerHelper('tmdbUrl', function() {
return 'https://www.themoviedb.org/movie/' + this.tmdbId; 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() { Handlebars.registerHelper('homepage', function() {
return this.website; return this.website;
}); });
File diff suppressed because it is too large Load Diff
+3047 -3047
View File
File diff suppressed because it is too large Load Diff
@@ -6,6 +6,9 @@
<span class="label label-info">{{network}}</span> <span class="label label-info">{{network}}</span>
{{/if}} {{/if}}
{{#if studio}}
<span class="label label-info">{{studio}}</span>
{{/if}}
<span class="label label-info">{{runtime}} minutes</span> <span class="label label-info">{{runtime}} minutes</span>
<span class="label label-info">{{path}}</span> <span class="label label-info">{{path}}</span>
@@ -33,6 +36,10 @@
{{#if imdbId}} {{#if imdbId}}
<a href="{{imdbUrl}}" class="label label-info">IMDB</a> <a href="{{imdbUrl}}" class="label label-info">IMDB</a>
{{/if}} {{/if}}
{{#if youTubeTrailerId}}
<a href="{{youTubeTrailerUrl}}" class="label label-info">Trailer</a>
{{/if}}
</span> </span>
</div> </div>
</div> </div>
+1 -1
View File
@@ -109,7 +109,7 @@ module.exports = Marionette.Layout.extend({
element : this.ui.rename, element : this.ui.rename,
command : { command : {
name : 'renameMovieFiles', name : 'renameMovieFiles',
movieId : this.model.id, movieId : this.model.id,
seasonNumber : -1 seasonNumber : -1
} }
}); });
@@ -11,9 +11,9 @@
Are you sure you want to update all files in the {{numberOfMovies}} selected movies? Are you sure you want to update all files in the {{numberOfMovies}} selected movies?
{{debug}}
<ul class="selected-series"> <ul class="selected-series">
{{#each movie}} {{#each movies}}
<li>{{title}}</li> <li>{{title}}</li>
{{/each}} {{/each}}
</ul> </ul>
@@ -54,6 +54,10 @@
{{#if imdbId}} {{#if imdbId}}
<a href="{{imdbUrl}}" class="label label-info">IMDB</a> <a href="{{imdbUrl}}" class="label label-info">IMDB</a>
{{/if}} {{/if}}
{{#if youTubeTrailerId}}
<a href="{{youTubeTrailerUrl}}" class="label label-info">Trailer</a>
{{/if}}
</span> </span>
</div> </div>
</div> </div>
@@ -18,7 +18,7 @@
<div class="center"> <div class="center">
<div class="labels"> <div class="labels">
<span class="label label-{{DownloadedStatusColor}}" title="{{DownloadedQuality}}">{{DownloadedStatus}}</span>
{{#if website}} {{#if website}}
<a href="{{homepage}}" class="label label-info">Homepage</a> <a href="{{homepage}}" class="label label-info">Homepage</a>
{{/if}} {{/if}}
@@ -26,7 +26,9 @@
{{#if imdbId}} {{#if imdbId}}
<a href="{{imdbUrl}}" class="label label-info">IMDB</a> <a href="{{imdbUrl}}" class="label label-info">IMDB</a>
{{/if}} {{/if}}
{{#if youTubeTrailerId}}
<a href="{{youTubeTrailerUrl}}" class="label label-info">Trailer</a>
{{/if}}
</div> </div>
</div> </div>
</div> </div>
+24 -24
View File
@@ -6,33 +6,33 @@ var MoviesDetailsLayout = require('./Details/MoviesDetailsLayout');
var SeriesDetailsLayout = require('../Series/Details/SeriesDetailsLayout'); var SeriesDetailsLayout = require('../Series/Details/SeriesDetailsLayout');
module.exports = NzbDroneController.extend({ module.exports = NzbDroneController.extend({
_originalInit : NzbDroneController.prototype.initialize, _originalInit : NzbDroneController.prototype.initialize,
initialize : function() { initialize : function() {
this.route('', this.series); this.route('', this.series);
this.route('movies', this.series); this.route('movies', this.series);
this.route('movies/:query', this.seriesDetails); this.route('movies/:query', this.seriesDetails);
this._originalInit.apply(this, arguments); this._originalInit.apply(this, arguments);
}, },
series : function() { series : function() {
this.setTitle('Movies'); this.setTitle('Movies');
this.showMainRegion(new MoviesIndexLayout()); this.showMainRegion(new MoviesIndexLayout());
}, },
seriesDetails : function(query) { seriesDetails : function(query) {
var series = MoviesCollection.where({ titleSlug : query }); var series = MoviesCollection.where({ titleSlug : query });
if (series.length !== 0) { if (series.length !== 0) {
var targetMovie = series[0]; var targetMovie = series[0];
console.log(AppLayout.mainRegion); console.log(AppLayout.mainRegion);
this.setTitle(targetMovie.get('title')); this.setTitle(targetMovie.get('title'));
//this.showNotFound(); //this.showNotFound();
//this.showMainRegion(new SeriesDetailsLayout({model : targetMovie})); //this.showMainRegion(new SeriesDetailsLayout({model : targetMovie}));
this.showMainRegion(new MoviesDetailsLayout({ model : targetMovie })); this.showMainRegion(new MoviesDetailsLayout({ model : targetMovie }));
} else { } else {
this.showNotFound(); this.showNotFound();
} }
} }
}); });
+2 -2
View File
@@ -126,7 +126,7 @@
.card; .card;
.clickable; .clickable;
margin-bottom : 20px; margin-bottom : 20px;
height : 324px; height : 363px;
.center { .center {
display : block; display : block;
@@ -166,7 +166,7 @@
} }
@media (max-width: @screen-xs-max) { @media (max-width: @screen-xs-max) {
height : 235px; height : 283px;
margin : 5px; margin : 5px;
padding : 6px 5px; padding : 6px 5px;
+39 -39
View File
@@ -1,44 +1,44 @@
<!-- Static navbar --> <!-- Static navbar -->
<div class="navbar navbar-nzbdrone" role="navigation"> <div class="navbar navbar-nzbdrone" role="navigation">
<div class="container-fluid"> <div class="container-fluid">
<div class="navbar-header"> <div class="navbar-header">
<button type="button" class="navbar-toggle navbar-inverse" data-toggle="collapse" data-target=".navbar-collapse"> <button type="button" class="navbar-toggle navbar-inverse" data-toggle="collapse" data-target=".navbar-collapse">
<span class="sr-only">Toggle navigation</span> <span class="sr-only">Toggle navigation</span>
<span class="icon-sonarr-navbar-collapsed fa-lg"></span> <span class="icon-sonarr-navbar-collapsed fa-lg"></span>
</button> </button>
<a class="navbar-brand" href="{{UrlBase}}/"> <a class="navbar-brand" href="{{UrlBase}}/">
<!--<img src="{{UrlBase}}/Content/Images/logo.png?v=2" alt="Radarr">--> <!--<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/128.png" class="visible-lg"/>
<img src="{{UrlBase}}/Content/Images/logos/64.png" class="visible-md visible-sm"/> <img src="{{UrlBase}}/Content/Images/logos/64.png" class="visible-md visible-sm"/>
<span class="visible-xs"> <span class="visible-xs">
<img src="{{UrlBase}}/Content/Images/logos/32.png"/> <img src="{{UrlBase}}/Content/Images/logos/32.png"/>
<span class="logo-text">Radarr</span> <span class="logo-text">Radarr</span>
</span> </span>
</a> </a>
</div> </div>
<div class="navbar-collapse collapse x-navbar-collapse"> <div class="navbar-collapse collapse x-navbar-collapse">
<ul class="nav navbar-nav"> <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}}/" 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}}/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}}/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}}/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}}/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="{{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> <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>
<ul class="nav navbar-nav navbar-right"> <ul class="nav navbar-nav navbar-right">
<li class="active screen-size"></li> <li class="active screen-size"></li>
</ul> </ul>
</div><!--/.nav-collapse --> </div><!--/.nav-collapse -->
</div><!--/.container-fluid --> </div><!--/.container-fluid -->
<div class="col-md-12 search"> <div class="col-md-12 search">
<div class="col-md-6 col-md-offset-3"> <div class="col-md-6 col-md-offset-3">
<div class="input-group"> <div class="input-group">
<span class="input-group-addon"><i class="fa fa-search"></i></span> <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"> <input type="text" class="col-md-6 form-control x-series-search" placeholder="Search the movies in your library">
</div> </div>
</div> </div>
</div> </div>
</div> </div>
+3 -1
View File
@@ -25,7 +25,9 @@ $.fn.bindSearch = function() {
minLength : 1 minLength : 1
}, { }, {
name : 'series', name : 'series',
displayKey : 'title', displayKey : function(series) {
return series.title + ' (' + series.year + ')';
},
source : substringMatcher() source : substringMatcher()
}); });
+1 -36
View File
@@ -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 Backbone = require('backbone');
var RenamePreviewModel = require('./RenamePreviewModel'); var RenamePreviewModel = require('./RenamePreviewModel');
module.exports = Backbone.Collection.extend({ module.exports = Backbone.Collection.extend({
url : window.NzbDrone.ApiRoot + '/rename', url : window.NzbDrone.ApiRoot + '/renameMovie',
model : RenamePreviewModel, model : RenamePreviewModel,
originalFetch : Backbone.Collection.prototype.fetch, originalFetch : Backbone.Collection.prototype.fetch,
+2 -2
View File
@@ -6,10 +6,10 @@ module.exports = Marionette.ItemView.extend({
template : 'Rename/RenamePreviewFormatViewTemplate', template : 'Rename/RenamePreviewFormatViewTemplate',
templateHelpers : function() { templateHelpers : function() {
var type = this.model.get('seriesType'); //var type = this.model.get('seriesType');
return { return {
rename : this.naming.get('renameEpisodes'), rename : this.naming.get('renameEpisodes'),
format : this.naming.get(type + 'EpisodeFormat') format : this.naming.get('standardMovieFormat')
}; };
}, },
@@ -1,98 +1,98 @@
<fieldset> <fieldset>
<legend>File Management</legend> <legend>File Management</legend>
<div class="form-group"> <div class="form-group">
<label class="col-sm-3 control-label">Ignore Deleted Movies</label> <label class="col-sm-3 control-label">Ignore Deleted Movies</label>
<div class="col-sm-9"> <div class="col-sm-9">
<div class="input-group"> <div class="input-group">
<label class="checkbox toggle well"> <label class="checkbox toggle well">
<input type="checkbox" name="autoUnmonitorPreviouslyDownloadedEpisodes"/> <input type="checkbox" name="autoUnmonitorPreviouslyDownloadedEpisodes"/>
<p> <p>
<span>Yes</span> <span>Yes</span>
<span>No</span> <span>No</span>
</p> </p>
<div class="btn btn-primary slide-button"/> <div class="btn btn-primary slide-button"/>
</label> </label>
<span class="help-inline-checkbox"> <span class="help-inline-checkbox">
<i class="icon-sonarr-form-info" title="Movies deleted from disk are automatically unmonitored in Radarr"/> <i class="icon-sonarr-form-info" title="Movies deleted from disk are automatically unmonitored in Radarr"/>
</span> </span>
</div> </div>
</div> </div>
</div> </div>
<div class="form-group advanced-setting"> <div class="form-group advanced-setting">
<label class="col-sm-3 control-label">Download Propers</label> <label class="col-sm-3 control-label">Download Propers</label>
<div class="col-sm-9"> <div class="col-sm-9">
<div class="input-group"> <div class="input-group">
<label class="checkbox toggle well"> <label class="checkbox toggle well">
<input type="checkbox" name="autoDownloadPropers"/> <input type="checkbox" name="autoDownloadPropers"/>
<p> <p>
<span>Yes</span> <span>Yes</span>
<span>No</span> <span>No</span>
</p> </p>
<div class="btn btn-primary slide-button"/> <div class="btn btn-primary slide-button"/>
</label> </label>
<span class="help-inline-checkbox"> <span class="help-inline-checkbox">
<i class="icon-sonarr-form-info" title="Should Radarr automatically upgrade to propers when available?"/> <i class="icon-sonarr-form-info" title="Should Radarr automatically upgrade to propers when available?"/>
</span> </span>
</div> </div>
</div> </div>
</div> </div>
<div class="form-group advanced-setting"> <div class="form-group advanced-setting">
<label class="col-sm-3 control-label">Analyse video files</label> <label class="col-sm-3 control-label">Analyse video files</label>
<div class="col-sm-9"> <div class="col-sm-9">
<div class="input-group"> <div class="input-group">
<label class="checkbox toggle well"> <label class="checkbox toggle well">
<input type="checkbox" name="enableMediaInfo"/> <input type="checkbox" name="enableMediaInfo"/>
<p> <p>
<span>Yes</span> <span>Yes</span>
<span>No</span> <span>No</span>
</p> </p>
<div class="btn btn-primary slide-button"/> <div class="btn btn-primary slide-button"/>
</label> </label>
<span class="help-inline-checkbox"> <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."/> <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> </span>
</div> </div>
</div> </div>
</div> </div>
<div class="form-group advanced-setting"> <div class="form-group advanced-setting">
<label class="col-sm-3 control-label">Change File Date</label> <label class="col-sm-3 control-label">Change File Date</label>
<div class="col-sm-1 col-sm-push-2 help-inline"> <div class="col-sm-1 col-sm-push-2 help-inline">
<i class="icon-sonarr-form-info" title="Change file date on import/rescan"/> <i class="icon-sonarr-form-info" title="Change file date on import/rescan"/>
</div> </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="form-group"> <div class="col-sm-2 col-sm-pull-1">
<label class="col-sm-3 control-label">Recycling Bin</label> <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"> <div class="form-group">
<i class="icon-sonarr-form-info" title="Episode files will go here when deleted instead of being permanently deleted"/> <label class="col-sm-3 control-label">Recycling Bin</label>
</div>
<div class="col-sm-8 col-sm-pull-1"> <div class="col-sm-1 col-sm-push-8 help-inline">
<input type="text" name="recycleBin" class="form-control x-path"/> <i class="icon-sonarr-form-info" title="Episode files will go here when deleted instead of being permanently deleted"/>
</div> </div>
</div> <div class="col-sm-8 col-sm-pull-1">
<input type="text" name="recycleBin" class="form-control x-path"/>
</div>
</div>
</fieldset> </fieldset>
@@ -72,6 +72,7 @@
{{> MediaInfoNamingPartial}} {{> MediaInfoNamingPartial}}
{{> ReleaseGroupNamingPartial}} {{> ReleaseGroupNamingPartial}}
{{> OriginalTitleNamingPartial}} {{> OriginalTitleNamingPartial}}
{{> ImdbIdNamingPartial}}
{{> SeparatorNamingPartial}} {{> SeparatorNamingPartial}}
</ul> </ul>
</div> </div>
@@ -161,6 +162,7 @@
<ul class="dropdown-menu"> <ul class="dropdown-menu">
{{> MovieTitleNamingPartial}} {{> MovieTitleNamingPartial}}
{{> ReleaseYearNamingPartial}} {{> ReleaseYearNamingPartial}}
{{> ImdbIdNamingPartial}}
</ul> </ul>
</div> </div>
</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.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> <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> <fieldset>
<legend>Quality Definitions</legend> <legend>Quality Definitions</legend>
<div class="col-md-11"> <div class="col-md-11">
<div id="quality-definition-list"> <div id="quality-definition-list">
<div class="quality-header x-header hidden-xs"> <div class="quality-header x-header hidden-xs">
<div class="row"> <div class="row">
<span class="col-md-2 col-sm-3">Quality</span> <span class="col-md-2 col-sm-3">Quality</span>
<span class="col-md-2 col-sm-3">Title</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> <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> </div>
<div class="rows x-rows"> <div class="rows x-rows">
</div> </div>
</div> </div>
</div> </div>
</fieldset> </fieldset>
@@ -4,92 +4,92 @@ require('jquery-ui');
var FormatHelpers = require('../../../Shared/FormatHelpers'); var FormatHelpers = require('../../../Shared/FormatHelpers');
var view = Marionette.ItemView.extend({ var view = Marionette.ItemView.extend({
template : 'Settings/Quality/Definition/QualityDefinitionItemViewTemplate', template : 'Settings/Quality/Definition/QualityDefinitionItemViewTemplate',
className : 'row', className : 'row',
slider : {
min : 0,
max : 200,
step : 0.1
},
ui : { slider : {
sizeSlider : '.x-slider', min : 0,
thirtyMinuteMinSize : '.x-min-thirty', max : 200,
sixtyMinuteMinSize : '.x-min-sixty', step : 0.1
thirtyMinuteMaxSize : '.x-max-thirty', },
sixtyMinuteMaxSize : '.x-max-sixty'
},
events : { ui : {
'slide .x-slider' : '_updateSize' sizeSlider : '.x-slider',
}, thirtyMinuteMinSize : '.x-min-thirty',
sixtyMinuteMinSize : '.x-min-sixty',
thirtyMinuteMaxSize : '.x-max-thirty',
sixtyMinuteMaxSize : '.x-max-sixty'
},
initialize : function(options) { events : {
this.profileCollection = options.profiles; 'slide .x-slider' : '_updateSize'
}, },
onRender : function() { initialize : function(options) {
if (this.model.get('quality').id === 0) { this.profileCollection = options.profiles;
this.$el.addClass('row advanced-setting'); },
}
this.ui.sizeSlider.slider({ onRender : function() {
range : true, if (this.model.get('quality').id === 0) {
min : this.slider.min, this.$el.addClass('row advanced-setting');
max : this.slider.max, }
step : this.slider.step,
values : [
this.model.get('minSize') || this.slider.min,
this.model.get('maxSize') || this.slider.max
]
});
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) { this._changeSize();
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(); _updateSize : function(event, ui) {
}, var minSize = ui.values[0];
var maxSize = ui.values[1];
_changeSize : function() { if (maxSize === this.slider.max) {
var minSize = this.model.get('minSize') || this.slider.min; maxSize = null;
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);
this.ui.thirtyMinuteMinSize.html(minThirty); this.model.set('minSize', minSize);
this.ui.sixtyMinuteMinSize.html(minSixty); this.model.set('maxSize', maxSize);
}
{ this._changeSize();
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.ui.thirtyMinuteMaxSize.html(maxThirty); _changeSize : function() {
this.ui.sixtyMinuteMaxSize.html(maxSixty); 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); view = AsModelBoundView.call(view);
module.exports = view; module.exports = view;

Some files were not shown because too many files have changed in this diff Show More