1
0
mirror of https://github.com/Radarr/Radarr.git synced 2026-03-25 17:35:35 -04:00

Compare commits

...

67 Commits

Author SHA1 Message Date
Leonardo Galli
e7c9217dc0 Merge remote-tracking branch 'origin/develop' into develop 2017-09-10 15:58:47 +02:00
Leonardo Galli
8caf648a0b Fixed: Alt titles with less than 4 votes being used. 2017-09-10 15:58:31 +02:00
Leonardo Galli
349be2c454 Fixed: Cleanup of unused alt titles. 2017-09-10 15:56:45 +02:00
James White
ba0c5bc5f4 Fixed: Scripts should be included before closing body tag (#1995) 2017-09-10 15:55:36 +02:00
James White
1c1723b4f7 Fixed: Allow toolbar buttons to be full width on x-small and small breakpoints (#1972) 2017-09-10 15:55:03 +02:00
James White
c4957fffee Added: WOFF2 fonts for better performance in supported browsers (#1994) 2017-09-10 15:54:39 +02:00
James White
e6fee32cf2 Changed: Tweak NavbarLayoutTemplate.hbs (#1996) 2017-09-10 15:54:16 +02:00
Leonardo Galli
fc805e5078 Fixed: Refresh movie failing. 2017-08-27 18:55:55 +02:00
Leonardo Galli
ba531fff4d Added: Ability to force download movies that could not be mapped correctly. This also shares these mappings with other users, so everyone can profit :)
(This is just for the automatic changelog, hacky I know)
2017-08-27 18:12:09 +02:00
Leonardo Galli
1d7ce68431 Fixed: Refresh movie failing for most movies. (Fixes #2007)
Fixed: Invisible download button if no release name was found.
2017-08-27 18:05:28 +02:00
Leonardo Galli
09d51dca0f Fixed: A few issues with the new alternative titles feature. (#2008) (Fixes #1919, #1927 and #1917)
Added: Ability to force download movies that could not be mapped correctly. This also shares these mappings with other users, so everyone can profit :)
2017-08-27 16:42:11 +02:00
James White
efe49ef3c4 @cosmetic Make file name match naming format (#1997) 2017-08-25 08:37:24 +02:00
Leonardo Galli
a5823bb15f Fixed: A lot of small ui errors (e.g. More not showing) (Revert of #1959) 2017-08-24 07:57:31 +02:00
James White
38af8edd59 Fixed: Adjust Sonarr references to Radarr (#1977) 2017-08-21 21:48:45 +02:00
Leonardo Galli
685c5daf36 Fixed: Rename movie not working (#1970) (Fixes #1908) 2017-08-20 22:12:02 +02:00
Leonardo Galli
4d5a5ed2c1 Merge remote-tracking branch 'origin/develop' into develop 2017-08-20 20:13:48 +02:00
Leonardo Galli
d44de777c1 Fixed: Error with CP Import when no info is present. Fixes #1792 2017-08-20 20:13:42 +02:00
James White
dc2740aeb7 Added: Display breakpoint name in browser window in debug mode (#1968) 2017-08-20 20:00:37 +02:00
Leonardo Galli
fb53fc68a9 Added: Ability to delete multiple movies at once via the movie editor. 2017-08-20 18:58:29 +02:00
Tom
8de87bb516 Fixed: Movie files & folders will actually get deleted now (#1966) (Fixes #694) 2017-08-20 14:40:02 +02:00
James White
0c8e264668 Fixed: Bulk UI cleanup, fixes and consistency improvements (#1959) 2017-08-20 14:38:24 +02:00
MangaValk
4d8a270170 Fixed: Parser error when using (year) name folder (#1956) (Fixes #1951) 2017-08-19 00:24:41 +02:00
hotio
f184fc2827 @cosmetic Indicate proper release types for docker image (#1953) 2017-08-17 19:24:39 +02:00
hotio
4e48e6ea21 @cosmetic Update appveyor builds url to exclude pull requests (#1946) 2017-08-15 23:23:38 +02:00
Leonardo Galli
b1e75ffc57 Merge remote-tracking branch 'origin/develop' into develop 2017-08-15 23:22:43 +02:00
Leonardo Galli
285ffb19d8 Fixed: Non-Freeleech torrents showing as freeleech for AHD. 2017-08-15 23:22:00 +02:00
hotio
aea7a3f48f @cosmetic Update docker nightly image reference (#1945) 2017-08-15 23:07:34 +02:00
James White
edbf1cb4e4 Fixed: Missing icon preventing detailed explanation validation errors explanations from appearing. (#1944) 2017-08-15 22:54:59 +02:00
Leonardo Galli
2154c48304 @cosmetic Removed Tests from AppVeyor 2017-08-15 20:23:02 +02:00
James White
7a95f040c9 Added: Deprecation warning about Drone Factory to front end (#1938) 2017-08-15 20:22:17 +02:00
James White
cc1966230a Added: Package-lock.json for npm 5 (#1939) 2017-08-15 20:21:59 +02:00
James White
e97fe7b3a7 Fixed: Mask-icon and other resources when UrlBase is in use (#1933) 2017-08-13 22:56:56 +02:00
James White
6500edbd14 Fixed: Slack/Mattermost notifications improvements from Sonarr (#1930) 2017-08-13 19:42:00 +02:00
James White
e155585198 Fixed: fontawesome path (Icons disappearing) (#1929) 2017-08-13 14:18:54 +02:00
James White
d9da32173f Fixed: Charset and meta in index.html and login.html (#1926) 2017-08-13 12:57:10 +02:00
Rotem
9db46ba154 Fixed: Removed hebrew ISO, since english movies are still in english. (#1922) 2017-08-13 12:55:25 +02:00
James White
eff6e33a0b Fixed: Adjust column sizes relative to size of dropdown values (#1923) 2017-08-13 12:54:44 +02:00
James White
724097d276 Updated FontAwesome to 4.7.0 (#1928) 2017-08-13 12:54:16 +02:00
James White
7a06ead806 Fixed: Additional jshint warnings (#1921) 2017-08-12 17:11:34 +02:00
James White
d773da60e8 Fixed: Minor issues on MoreInfoViewTemplate.hbs (#1916) 2017-08-12 15:16:31 +02:00
James White
1f7b03d321 Fixed: gulp jshint warnings (#1873) 2017-08-12 15:16:12 +02:00
James White
a07ef20410 Fixed: Replace GitHub wiki references to Radarr's wiki URL (#1914) 2017-08-12 15:15:23 +02:00
Rotem
7c4c6ccd5c Added: Hebrew language (#1909) 2017-08-12 00:48:57 +02:00
James White
c754edc4b8 Fixed: Hanging form-group div (#1911) 2017-08-12 00:29:31 +02:00
Mike
7f3ab36c4f New: Run tests through powershell. (#1903) 2017-08-10 20:11:12 +02:00
Leonardo Galli
17387c8b50 Fixed: Migration failing and thus making Radarr unable to start. 2017-08-10 18:51:05 +02:00
Leonardo Galli
0d40ed7ec0 Added: Alternative Titles are now also pulled from mappings.radarr.video.
Added: A secondary year will also be pulled from mappings.radarr.video.
Added: An option to force download a movie with wrong year / title. Radarr then adds the year / title to mappings.radarr.video.
2017-08-09 22:18:04 +02:00
Leonardo Galli
cfcb66fba1 Changed: Alternative Titles were reworked greatly. This should speed up RSS Sync massively, especially for large libraries (up to 4x). 2017-08-09 22:14:01 +02:00
James White
8927e7c2c6 Fixed: Task name of PreDB Sync task (#1875) 2017-08-06 17:47:23 +02:00
Qstick
4e9a931537 Fixed: Check that Quality Profile is not in use before deleting it. 2017-08-06 12:10:17 +02:00
James White
17feedaf53 Fixed: Category not setting with qBitTorrent 3.3.14 and other api errors. (upstream from Sonarr) 2017-08-06 12:09:16 +02:00
Leonardo Galli
b06108fb45 Fixed: Older movies (released more than 30 days ago) are now not refreshed as often anymore (every 30 days) 2017-07-28 16:59:06 +02:00
Leonardo Galli
67dd498576 Fixed: (Hopefully) Bug where movie file was not correctly linked to movie. 2017-07-28 16:57:26 +02:00
Leonardo Galli
3fb356ddb6 Fixed: No API Key required with SignalR connections. 2017-07-26 23:24:28 +02:00
Leonardo Galli
605b8f9645 Fixed: Guard agains null reference exception with newznab capabilities. 2017-07-26 23:23:36 +02:00
Marc Runkel
668ef336fb Fixed: Update Info.plist to avoid conflict with Sonarr (#1783) 2017-07-16 14:09:03 +01:00
Richard Schwab
b82f2376a7 Fixed: Relax SingleInstancePolicy when using a custom data directory Fixes #1765 (#1782) 2017-07-16 14:08:16 +01:00
Leonardo Galli
964c18b236 Fixed: Lists are fetched much more efficiently. (Up to 40x loading time improvement with large lists!) 2017-07-08 16:13:28 +02:00
Taloth Saldono
fb4f510909 Revert "Fixed: Support for Mono 5.x with the newer BoringTLS provider."
This reverts commit 5b722a3a48.
2017-07-06 21:45:37 +02:00
Taloth Saldono
5b722a3a48 Fixed: Support for Mono 5.x with the newer BoringTLS provider. 2017-07-06 21:43:34 +02:00
Sandro Stikić
0892f20298 Fixed: Old Plex ValidationFailure message (#1770) 2017-07-06 18:48:46 +02:00
Leonardo Galli
ac5732536d Fixed: BDRemux not recognized as such and BDRips without resolution recognized as DVD. Fixes #1755 2017-07-01 14:01:38 +02:00
Leonardo Galli
1f4c2ad946 Fixed: A as part of an acronym being removed from clean title. Fixes #1747 2017-07-01 11:01:48 +02:00
Leonardo Galli
7712aa62da Fixed: Folder in List settings appearing blank in some browsers. Fixes #1711 2017-06-27 12:53:50 +02:00
Leonardo Galli
58b9c9d3d1 Fixed: Minimum Seeders not saving for Torznab indexer. Fixes #1736 2017-06-27 12:36:24 +02:00
Leonardo Galli
b2115e2066 Removed most of changelog. Generate changelog using gitchangelog v0.2.0.18..HEAD. @cosmetic 2017-06-20 14:29:32 +02:00
Leonardo Galli
4e4873271b Added Changelog, just for fun :D @cosmetic 2017-06-20 14:21:19 +02:00
172 changed files with 11693 additions and 1061 deletions

293
.gitchangelog.rc Normal file
View File

@@ -0,0 +1,293 @@
# -*- coding: utf-8; mode: python -*-
##
## Format
##
## ACTION: [AUDIENCE:] COMMIT_MSG [!TAG ...]
##
## Description
##
## ACTION is one of 'chg', 'fix', 'new'
##
## Is WHAT the change is about.
##
## 'chg' is for refactor, small improvement, cosmetic changes...
## 'fix' is for bug fixes
## 'new' is for new features, big improvement
##
## AUDIENCE is optional and one of 'dev', 'usr', 'pkg', 'test', 'doc'
##
## Is WHO is concerned by the change.
##
## 'dev' is for developpers (API changes, refactors...)
## 'usr' is for final users (UI changes)
## 'pkg' is for packagers (packaging changes)
## 'test' is for testers (test only related changes)
## 'doc' is for doc guys (doc only changes)
##
## COMMIT_MSG is ... well ... the commit message itself.
##
## TAGs are additionnal adjective as 'refactor' 'minor' 'cosmetic'
##
## They are preceded with a '!' or a '@' (prefer the former, as the
## latter is wrongly interpreted in github.) Commonly used tags are:
##
## 'refactor' is obviously for refactoring code only
## 'minor' is for a very meaningless change (a typo, adding a comment)
## 'cosmetic' is for cosmetic driven change (re-indentation, 80-col...)
## 'wip' is for partial functionality but complete subfunctionality.
##
## Example:
##
## new: usr: support of bazaar implemented
## chg: re-indentend some lines !cosmetic
## new: dev: updated code to be compatible with last version of killer lib.
## fix: pkg: updated year of licence coverage.
## new: test: added a bunch of test around user usability of feature X.
## fix: typo in spelling my name in comment. !minor
##
## Please note that multi-line commit message are supported, and only the
## first line will be considered as the "summary" of the commit message. So
## tags, and other rules only applies to the summary. The body of the commit
## message will be displayed in the changelog without reformatting.
##
## ``ignore_regexps`` is a line of regexps
##
## Any commit having its full commit message matching any regexp listed here
## will be ignored and won't be reported in the changelog.
##
ignore_regexps = [
r'@minor', r'!minor',
r'@cosmetic', r'!cosmetic',
r'@refactor', r'!refactor',
r'@wip', r'!wip',
r'^([cC]hg|[fF]ix|[nN]ew)\s*:\s*[p|P]kg:',
r'^([cC]hg|[fF]ix|[nN]ew)\s*:\s*[d|D]ev:',
r'^(.{3,3}\s*:)?\s*[fF]irst commit.?\s*$',
r'^$', ## ignore commits with empty messages
]
## ``section_regexps`` is a list of 2-tuples associating a string label and a
## list of regexp
##
## Commit messages will be classified in sections thanks to this. Section
## titles are the label, and a commit is classified under this section if any
## of the regexps associated is matching.
##
## Please note that ``section_regexps`` will only classify commits and won't
## make any changes to the contents. So you'll probably want to go check
## ``subject_process`` (or ``body_process``) to do some changes to the subject,
## whenever you are tweaking this variable.
##
section_regexps = [
('**New Features**', [
r'^[aA]dded?\s*:?\s*((dev|use?r|pkg|test|doc)\s*:\s*)?([^\n]*)$',
r'^[uU]pdated?\s*:?\s*((dev|use?r|pkg|test|doc)\s*:\s*)?([^\n]*)$',
r'^[cC]hanged?\s*:?\s*((dev|use?r|pkg|test|doc)\s*:\s*)?([^\n]*)$',
]),
('**Fixes**', [
r'^(?![mM]erge\s*)'
]
),
]
## ``body_process`` is a callable
##
## This callable will be given the original body and result will
## be used in the changelog.
##
## Available constructs are:
##
## - any python callable that take one txt argument and return txt argument.
##
## - ReSub(pattern, replacement): will apply regexp substitution.
##
## - Indent(chars=" "): will indent the text with the prefix
## Please remember that template engines gets also to modify the text and
## will usually indent themselves the text if needed.
##
## - Wrap(regexp=r"\n\n"): re-wrap text in separate paragraph to fill 80-Columns
##
## - noop: do nothing
##
## - ucfirst: ensure the first letter is uppercase.
## (usually used in the ``subject_process`` pipeline)
##
## - final_dot: ensure text finishes with a dot
## (usually used in the ``subject_process`` pipeline)
##
## - strip: remove any spaces before or after the content of the string
##
## - SetIfEmpty(msg="No commit message."): will set the text to
## whatever given ``msg`` if the current text is empty.
##
## Additionally, you can `pipe` the provided filters, for instance:
#body_process = Wrap(regexp=r'\n(?=\w+\s*:)') | Indent(chars=" ")
#body_process = Wrap(regexp=r'\n(?=\w+\s*:)')
#body_process = noop
body_process = ReSub(r'((^|\n)[A-Z]\w+(-\w+)*: .*(\n\s+.*)*)+$', r'') | strip
## ``subject_process`` is a callable
##
## This callable will be given the original subject and result will
## be used in the changelog.
##
## Available constructs are those listed in ``body_process`` doc.
subject_process = (strip |
ReSub(r'^([cC]hanged|[fF]ixed|[aA]dded|[uU]pdated)\s*:\s*((dev|use?r|pkg|test|doc)\s*:\s*)?([^\n@]*)(@[a-z]+\s+)*$', r'\4') |
SetIfEmpty("No commit message.") | ucfirst | final_dot)
## ``tag_filter_regexp`` is a regexp
##
## Tags that will be used for the changelog must match this regexp.
##
tag_filter_regexp = r'^v[0]+\.[2-9]+\.[0-9]+\.[0-9]+$'
## ``unreleased_version_label`` is a string or a callable that outputs a string
##
## This label will be used as the changelog Title of the last set of changes
## between last valid tag and HEAD if any.
unreleased_version_label = "(unreleased)"
## ``output_engine`` is a callable
##
## This will change the output format of the generated changelog file
##
## Available choices are:
##
## - rest_py
##
## Legacy pure python engine, outputs ReSTructured text.
## This is the default.
##
## - mustache(<template_name>)
##
## Template name could be any of the available templates in
## ``templates/mustache/*.tpl``.
## Requires python package ``pystache``.
## Examples:
## - mustache("markdown")
## - mustache("restructuredtext")
##
## - makotemplate(<template_name>)
##
## Template name could be any of the available templates in
## ``templates/mako/*.tpl``.
## Requires python package ``mako``.
## Examples:
## - makotemplate("restructuredtext")
##
#output_engine = rest_py
#output_engine = mustache("restructuredtext")
output_engine = mustache("changelog.tpl")
#output_engine = makotemplate("restructuredtext")
## ``include_merge`` is a boolean
##
## This option tells git-log whether to include merge commits in the log.
## The default is to include them.
include_merge = False
## ``log_encoding`` is a string identifier
##
## This option tells gitchangelog what encoding is outputed by ``git log``.
## The default is to be clever about it: it checks ``git config`` for
## ``i18n.logOutputEncoding``, and if not found will default to git's own
## default: ``utf-8``.
#log_encoding = 'utf-8'
## ``publish`` is a callable
##
## Sets what ``gitchangelog`` should do with the output generated by
## the output engine. ``publish`` is a callable taking one argument
## that is an interator on lines from the output engine.
##
## Some helper callable are provided:
##
## Available choices are:
##
## - stdout
##
## Outputs directly to standard output
## (This is the default)
##
## - FileInsertAtFirstRegexMatch(file, pattern, idx=lamda m: m.start())
##
## Creates a callable that will parse given file for the given
## regex pattern and will insert the output in the file.
## ``idx`` is a callable that receive the matching object and
## must return a integer index point where to insert the
## the output in the file. Default is to return the position of
## the start of the matched string.
##
## - FileRegexSubst(file, pattern, replace, flags)
##
## Apply a replace inplace in the given file. Your regex pattern must
## take care of everything and might be more complex. Check the README
## for a complete copy-pastable example.
##
# publish = FileInsertIntoFirstRegexMatch(
# "CHANGELOG.rst",
# r'/(?P<rev>[0-9]+\.[0-9]+(\.[0-9]+)?)\s+\([0-9]+-[0-9]{2}-[0-9]{2}\)\n--+\n/',
# idx=lambda m: m.start(1)
# )
#publish = stdout
def write_to_file(content):
with open("CHANGELOG.md", "w+") as f:
for chunk in content:
f.write(chunk)
publish = write_to_file
## ``revs`` is a list of callable or a list of string
##
## callable will be called to resolve as strings and allow dynamical
## computation of these. The result will be used as revisions for
## gitchangelog (as if directly stated on the command line). This allows
## to filter exaclty which commits will be read by gitchangelog.
##
## To get a full documentation on the format of these strings, please
## refer to the ``git rev-list`` arguments. There are many examples.
##
## Using callables is especially useful, for instance, if you
## are using gitchangelog to generate incrementally your changelog.
##
## Some helpers are provided, you can use them::
##
## - FileFirstRegexMatch(file, pattern): will return a callable that will
## return the first string match for the given pattern in the given file.
## If you use named sub-patterns in your regex pattern, it'll output only
## the string matching the regex pattern named "rev".
##
## - Caret(rev): will return the rev prefixed by a "^", which is a
## way to remove the given revision and all its ancestor.
##
## Please note that if you provide a rev-list on the command line, it'll
## replace this value (which will then be ignored).
##
## If empty, then ``gitchangelog`` will act as it had to generate a full
## changelog.
##
## The default is to use all commits to make the changelog.
#revs = ["^1.0.3", ]
#revs = [
# Caret(
# FileFirstRegexMatch(
# "CHANGELOG.rst",
# r"(?P<rev>[0-9]+\.[0-9]+(\.[0-9]+)?)\s+\([0-9]+-[0-9]{2}-[0-9]{2}\)\n--+\n")),
# "HEAD"
#]
revs = []

1566
CHANGELOG.md Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -21,10 +21,10 @@ The project was inspired by other Usenet/BitTorrent movie downloaders such as Co
## 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)
[![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/branch/develop/artifacts)
[![Docker release](https://img.shields.io/badge/docker-release-blue.svg?colorB=1488C6&maxAge=60&style=flat-square)](https://store.docker.com/community/images/linuxserver/radarr)
[![Docker nightly](https://img.shields.io/badge/docker-nightly-blue.svg?colorB=1488C6&maxAge=60&style=flat-square)](https://store.docker.com/community/images/hotio/radarr)
[![Docker nightly](https://img.shields.io/badge/docker-release/nightly-blue.svg?colorB=1488C6&maxAge=60&style=flat-square)](https://store.docker.com/community/images/hotio/suitarr)
[![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)
@@ -50,8 +50,6 @@ The project was inspired by other Usenet/BitTorrent movie downloaders such as Co
| 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 project works independently of Sonarr and will not interfere with it.**
Radarr is currently undergoing rapid development and pull requests are actively added into the repository.
## Features

View File

@@ -16,14 +16,14 @@ install:
build_script:
- ps: ./build-appveyor.ps1
# test: off
test:
assemblies:
- '_tests\*Test.dll'
categories:
except:
- IntegrationTest
- AutomationTest
test: off
#test:
# assemblies:
# - '_tests\*Test.dll'
# categories:
# except:
# - IntegrationTest
# - AutomationTest
artifacts:
- path: '_artifacts\*.zip'

15
changelog.tpl Normal file
View File

@@ -0,0 +1,15 @@
# Changelog
{{#versions}}
## {{{label}}}
{{#sections}}
### {{{label}}}
{{#commits}}
- {{{subject}}} [{{{author}}}]
{{/commits}}
{{/sections}}
{{/versions}}

View File

@@ -15,7 +15,7 @@
<key>CFBundleIconFile</key>
<string>radarr.icns</string>
<key>CFBundleIdentifier</key>
<string>com.osx.sonarr.tv</string>
<string>com.osx.radarr.video</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>

4190
package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -1,7 +1,7 @@
{
"name": "Sonarr",
"name": "Radarr",
"version": "2.0.0",
"description": "Sonarr",
"description": "Radarr",
"main": "main.js",
"scripts": {
"build": "gulp build",
@@ -9,7 +9,7 @@
},
"repository": {
"type": "git",
"url": "git://github.com/Sonarr/Sonarr.git"
"url": "git://github.com/Radarr/Radarr.git"
},
"author": "",
"license": "GPL-3.0",

View File

@@ -14,8 +14,10 @@ You should have received a copy of the GNU Lesser General Public
License along with this library. If not, see <http://www.gnu.org/licenses/>. */
using System;
using System.Collections.Generic;
using System.Data;
using System.Data.Common;
using System.Runtime.InteropServices.ComTypes;
using Marr.Data.Converters;
namespace Marr.Data.Parameters
@@ -42,15 +44,25 @@ namespace Marr.Data.Parameters
Type valueType = value.GetType();
// Check for a registered IConverter
IConverter converter = MapRepository.Instance.GetConverter(valueType);
if (converter != null)
//If we have a list of ints, we ignore the converter since we want to do an in statement!
var list = value as List<int>;
if (list != null)
{
Parameter.Value = converter.ToDB(value);
Parameter.Value = $"{string.Join(",", list)}";
}
else
{
Parameter.Value = value;
}
IConverter converter = MapRepository.Instance.GetConverter(valueType);
if (converter != null)
{
Parameter.Value = converter.ToDB(value);
}
else
{
Parameter.Value = value;
}
}
//// Determine the correct DbType based on the passed in value type
//IDbTypeBuilder typeBuilder = MapRepository.Instance.DbTypeBuilder;

View File

@@ -68,5 +68,13 @@ namespace Marr.Data.QGen.Dialects
{
get { return "({0} LIKE '%' + {1} + '%')"; }
}
public virtual string InFormat
{
get
{
return "({0} in ({1}))";
}
}
}
}

View File

@@ -1,4 +1,5 @@
using System.Text;
using System.Linq;
using System.Text;
using Marr.Data.Mapping;
using Marr.Data.QGen.Dialects;
@@ -129,7 +130,16 @@ namespace Marr.Data.QGen
public void BuildOrderClause(StringBuilder sql)
{
sql.Append(OrderBy.ToString());
}
}
public void BuildGroupBy(StringBuilder sql)
{
var baseTable = this.Tables.First();
var primaryKeyColumn = baseTable.Columns.Single(c => c.ColumnInfo.IsPrimaryKey);
string token = this.Dialect.CreateToken(string.Concat(baseTable.Alias, ".", primaryKeyColumn.ColumnInfo.Name));
sql.AppendFormat(" GROUP BY {0}", token);
}
private string TranslateJoin(JoinType join)
{

View File

@@ -14,8 +14,22 @@ namespace Marr.Data.QGen
public string Generate()
{
StringBuilder sql = new StringBuilder();
BuildSelectCountClause(sql);
if (_innerQuery.IsJoin)
{
sql.Append(" FROM (");
_innerQuery.BuildSelectClause(sql);
_innerQuery.BuildFromClause(sql);
_innerQuery.BuildJoinClauses(sql);
_innerQuery.BuildWhereClause(sql);
_innerQuery.BuildGroupBy(sql);
sql.Append(") ");
return sql.ToString();
}
_innerQuery.BuildFromClause(sql);
_innerQuery.BuildJoinClauses(sql);
_innerQuery.BuildWhereClause(sql);

View File

@@ -1,4 +1,5 @@
using System;
using System.Collections.Generic;
using System.Text;
using System.Linq.Expressions;
using System.Data.Common;
@@ -92,6 +93,10 @@ namespace Marr.Data.QGen
case "EndsWith":
Write_EndsWith(expression);
break;
case "In":
Write_In(expression);
break;
default:
string msg = string.Format("'{0}' expressions are not yet implemented in the where clause expression tree parser.", method);
@@ -140,31 +145,47 @@ namespace Marr.Data.QGen
return expression;
}
private object GetRightValue(Expression rightExpression)
private object GetRightValue(Expression expression)
{
object rightValue = null;
var right = rightExpression as ConstantExpression;
if (right == null) // Value is not directly passed in as a constant
var simpleConstExp = expression as ConstantExpression;
if (simpleConstExp == null) // Value is not directly passed in as a constant
{
var rightMemberExp = (rightExpression as MemberExpression);
var parentMemberExpression = rightMemberExp.Expression as MemberExpression;
if (parentMemberExpression != null) // Value is passed in as a property on a parent entity
MemberExpression memberExp = expression as MemberExpression;
ConstantExpression constExp = null;
// Value may be nested in multiple levels of objects/properties, so traverse the MemberExpressions
// until a ConstantExpression property value is found, and then unwind the stack to get the value.
var memberNames = new Stack<string>();
while (memberExp != null)
{
string entityName = (rightMemberExp.Expression as MemberExpression).Member.Name;
var container = ((rightMemberExp.Expression as MemberExpression).Expression as ConstantExpression).Value;
var entity = _repos.ReflectionStrategy.GetFieldValue(container, entityName);
rightValue = _repos.ReflectionStrategy.GetFieldValue(entity, rightMemberExp.Member.Name);
memberNames.Push(memberExp.Member.Name);
// Function calls are not supported - user needs to simplify their Where expression.
var methodExp = memberExp.Expression as MethodCallExpression;
if (methodExp != null)
{
var errMsg = string.Format("Function calls are not supported by the Where clause expression parser. Please evaluate your function call, '{0}', manually and then use the resulting paremeter value in your Where expression.", methodExp.Method.Name);
throw new NotSupportedException(errMsg);
}
constExp = memberExp.Expression as ConstantExpression;
memberExp = memberExp.Expression as MemberExpression;
}
else // Value is passed in as a variable
object entity = constExp.Value;
while (memberNames.Count > 0)
{
var parent = (rightMemberExp.Expression as ConstantExpression).Value;
rightValue = _repos.ReflectionStrategy.GetFieldValue(parent, rightMemberExp.Member.Name);
string entityName = memberNames.Pop();
entity = _repos.ReflectionStrategy.GetFieldValue(entity, entityName);
}
rightValue = entity;
}
else // Value is passed in directly as a constant
{
rightValue = right.Value;
rightValue = simpleConstExp.Value;
}
return rightValue;
@@ -238,6 +259,17 @@ namespace Marr.Data.QGen
_sb.AppendFormat(_dialect.ContainsFormat, fqColumn, paramName);
}
private void Write_In(MethodCallExpression body)
{
object value = GetRightValue(body.Arguments[1]);
//string paramName = string.Concat(_paramPrefix, "P", _command.Parameters.Count.ToString());
//var parameter = new ParameterChainMethods(_command, paramName, value).Parameter;
MemberExpression memberExp = (body.Arguments[0] as MemberExpression);
string fqColumn = GetFullyQualifiedColumnName(memberExp.Member, memberExp.Expression.Type);
_sb.AppendFormat(_dialect.InFormat, fqColumn, string.Join(",", value as List<int>));
}
private void Write_StartsWith(MethodCallExpression body)
{
// Add parameter to Command.Parameters

View File

@@ -1,4 +1,4 @@
using System;
using System;
using System.IO;
using System.Text.RegularExpressions;
using Nancy;
@@ -17,7 +17,7 @@ namespace NzbDrone.Api.Frontend.Mappers
private readonly IAnalyticsService _analyticsService;
private readonly Func<ICacheBreakerProvider> _cacheBreakProviderFactory;
private readonly string _indexPath;
private static readonly Regex ReplaceRegex = new Regex(@"(?:(?<attribute>href|src)=\"")(?<path>.*?(?<extension>css|js|png|ico|ics))(?:\"")(?:\s(?<nohash>data-no-hash))?", RegexOptions.Compiled | RegexOptions.IgnoreCase);
private static readonly Regex ReplaceRegex = new Regex(@"(?:(?<attribute>href|src|content)=\"")(?<path>.*?(?<extension>css|js|png|ico|ics|svg|json|xml))(?:\"")(?:\s(?<nohash>data-no-hash))?", RegexOptions.Compiled | RegexOptions.IgnoreCase);
private static string API_KEY;
private static string URL_BASE;

View File

@@ -46,7 +46,7 @@ namespace NzbDrone.Api.Indexers
GetResourceAll = GetReleases;
Post["/"] = x => DownloadRelease(this.Bind<ReleaseResource>());
PostValidator.RuleFor(s => s.DownloadAllowed).Equal(true);
//PostValidator.RuleFor(s => s.DownloadAllowed).Equal(true);
PostValidator.RuleFor(s => s.Guid).NotEmpty();
_remoteEpisodeCache = cacheManager.GetCache<RemoteEpisode>(GetType(), "remoteEpisodes");
@@ -70,7 +70,7 @@ namespace NzbDrone.Api.Indexers
try
{
_downloadService.DownloadReport(remoteMovie);
_downloadService.DownloadReport(remoteMovie, false);
}
catch (ReleaseDownloadException ex)
{

View File

@@ -8,6 +8,7 @@ using NzbDrone.Core.Indexers;
using NzbDrone.Core.Parser.Model;
using NzbDrone.Core.DecisionEngine;
using System.Linq;
using NzbDrone.Core.Datastore.Migration;
namespace NzbDrone.Api.Indexers
{
@@ -29,8 +30,8 @@ namespace NzbDrone.Api.Indexers
public bool FullSeason { get; set; }
public int SeasonNumber { get; set; }
public Language Language { get; set; }
public string AirDate { get; set; }
public string SeriesTitle { get; set; }
public int Year { get; set; }
public string MovieTitle { get; set; }
public int[] EpisodeNumbers { get; set; }
public int[] AbsoluteEpisodeNumbers { get; set; }
public bool Approved { get; set; }
@@ -43,8 +44,9 @@ namespace NzbDrone.Api.Indexers
public string CommentUrl { get; set; }
public string DownloadUrl { get; set; }
public string InfoUrl { get; set; }
public bool DownloadAllowed { get; set; }
public MappingResultType MappingResult { get; set; }
public int ReleaseWeight { get; set; }
public int SuspectedMovieId { get; set; }
public IEnumerable<string> IndexerFlags { get; set; }
@@ -88,11 +90,12 @@ namespace NzbDrone.Api.Indexers
var parsedEpisodeInfo = model.RemoteEpisode.ParsedEpisodeInfo;
var remoteEpisode = model.RemoteEpisode;
var torrentInfo = (model.RemoteEpisode.Release as TorrentInfo) ?? new TorrentInfo();
var downloadAllowed = model.RemoteEpisode.DownloadAllowed;
var mappingResult = MappingResultType.Success;
if (model.IsForMovie)
{
downloadAllowed = model.RemoteMovie.DownloadAllowed;
mappingResult = model.RemoteMovie.MappingResult;
var parsedMovieInfo = model.RemoteMovie.ParsedMovieInfo;
var movieId = model.RemoteMovie.Movie?.Id ?? 0;
return new ReleaseResource
{
@@ -111,8 +114,8 @@ namespace NzbDrone.Api.Indexers
//FullSeason = parsedMovieInfo.FullSeason,
//SeasonNumber = parsedMovieInfo.SeasonNumber,
Language = parsedMovieInfo.Language,
AirDate = "",
SeriesTitle = parsedMovieInfo.MovieTitle,
Year = parsedMovieInfo.Year,
MovieTitle = parsedMovieInfo.MovieTitle,
EpisodeNumbers = new int[0],
AbsoluteEpisodeNumbers = new int[0],
Approved = model.Approved,
@@ -125,8 +128,10 @@ namespace NzbDrone.Api.Indexers
CommentUrl = releaseInfo.CommentUrl,
DownloadUrl = releaseInfo.DownloadUrl,
InfoUrl = releaseInfo.InfoUrl,
DownloadAllowed = downloadAllowed,
MappingResult = mappingResult,
//ReleaseWeight
SuspectedMovieId = movieId,
MagnetUrl = torrentInfo.MagnetUrl,
InfoHash = torrentInfo.InfoHash,
@@ -161,8 +166,8 @@ namespace NzbDrone.Api.Indexers
FullSeason = parsedEpisodeInfo.FullSeason,
SeasonNumber = parsedEpisodeInfo.SeasonNumber,
Language = parsedEpisodeInfo.Language,
AirDate = parsedEpisodeInfo.AirDate,
SeriesTitle = parsedEpisodeInfo.SeriesTitle,
//AirDate = parsedEpisodeInfo.AirDate,
//SeriesTitle = parsedEpisodeInfo.SeriesTitle,
EpisodeNumbers = parsedEpisodeInfo.EpisodeNumbers,
AbsoluteEpisodeNumbers = parsedEpisodeInfo.AbsoluteEpisodeNumbers,
Approved = model.Approved,
@@ -175,7 +180,7 @@ namespace NzbDrone.Api.Indexers
CommentUrl = releaseInfo.CommentUrl,
DownloadUrl = releaseInfo.DownloadUrl,
InfoUrl = releaseInfo.InfoUrl,
DownloadAllowed = downloadAllowed,
//DownloadAllowed = downloadAllowed,
//ReleaseWeight
MagnetUrl = torrentInfo.MagnetUrl,

View File

@@ -0,0 +1,57 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Marr.Data;
using Nancy;
using NzbDrone.Api;
using NzbDrone.Api.Movie;
using NzbDrone.Common.Cache;
using NzbDrone.Common.Extensions;
using NzbDrone.Core.MediaCover;
using NzbDrone.Core.MediaFiles;
using NzbDrone.Core.MediaFiles.EpisodeImport;
using NzbDrone.Core.Messaging.Events;
using NzbDrone.Core.MetadataSource;
using NzbDrone.Core.MetadataSource.RadarrAPI;
using NzbDrone.Core.Movies.AlternativeTitles;
using NzbDrone.Core.RootFolders;
using NzbDrone.Core.Tv;
using NzbDrone.Core.Tv.Events;
namespace NzbDrone.Api.Movie
{
public class AlternativeTitleModule : NzbDroneRestModule<AlternativeTitleResource>
{
private readonly IAlternativeTitleService _altTitleService;
private readonly IMovieService _movieService;
private readonly IRadarrAPIClient _radarrApi;
private readonly IEventAggregator _eventAggregator;
public AlternativeTitleModule(IAlternativeTitleService altTitleService, IMovieService movieService, IRadarrAPIClient radarrApi, IEventAggregator eventAggregator)
: base("/alttitle")
{
_altTitleService = altTitleService;
_movieService = movieService;
_radarrApi = radarrApi;
CreateResource = AddTitle;
GetResourceById = GetTitle;
_eventAggregator = eventAggregator;
}
private int AddTitle(AlternativeTitleResource altTitle)
{
var title = altTitle.ToModel();
var movie = _movieService.GetMovie(altTitle.MovieId);
var newTitle = _radarrApi.AddNewAlternativeTitle(title, movie.TmdbId);
var addedTitle = _altTitleService.AddAltTitle(newTitle, movie);
_eventAggregator.PublishEvent(new MovieUpdatedEvent(movie));
return addedTitle.Id;
}
private AlternativeTitleResource GetTitle(int id)
{
return _altTitleService.GetById(id).ToResource();
}
}
}

View File

@@ -0,0 +1,63 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Marr.Data;
using Nancy;
using NzbDrone.Api;
using NzbDrone.Api.Movie;
using NzbDrone.Common.Cache;
using NzbDrone.Common.Extensions;
using NzbDrone.Common.Messaging;
using NzbDrone.Core.MediaCover;
using NzbDrone.Core.MediaFiles;
using NzbDrone.Core.MediaFiles.EpisodeImport;
using NzbDrone.Core.Messaging.Events;
using NzbDrone.Core.MetadataSource;
using NzbDrone.Core.MetadataSource.RadarrAPI;
using NzbDrone.Core.Movies.AlternativeTitles;
using NzbDrone.Core.RootFolders;
using NzbDrone.Core.Tv;
using NzbDrone.Core.Tv.Events;
namespace NzbDrone.Api.Movie
{
public class AlternativeYearModule : NzbDroneRestModule<AlternativeYearResource>
{
private readonly IMovieService _movieService;
private readonly IRadarrAPIClient _radarrApi;
private readonly ICached<int> _yearCache;
private readonly IEventAggregator _eventAggregator;
public AlternativeYearModule(IMovieService movieService, IRadarrAPIClient radarrApi, ICacheManager cacheManager, IEventAggregator eventAggregator)
: base("/altyear")
{
_movieService = movieService;
_radarrApi = radarrApi;
CreateResource = AddYear;
GetResourceById = GetYear;
_yearCache = cacheManager.GetCache<int>(GetType(), "altYears");
_eventAggregator = eventAggregator;
}
private int AddYear(AlternativeYearResource altYear)
{
var id = new Random().Next();
_yearCache.Set(id.ToString(), altYear.Year, TimeSpan.FromMinutes(1));
var movie = _movieService.GetMovie(altYear.MovieId);
var newYear = _radarrApi.AddNewAlternativeYear(altYear.Year, movie.TmdbId);
movie.SecondaryYear = newYear.Year;
movie.SecondaryYearSourceId = newYear.SourceId;
_movieService.UpdateMovie(movie);
_eventAggregator.PublishEvent(new MovieUpdatedEvent(movie));
return id;
}
private AlternativeYearResource GetYear(int id)
{
return new AlternativeYearResource
{
Year = _yearCache.Find(id.ToString())
};
}
}
}

View File

@@ -0,0 +1,75 @@
using System;
using System.Collections.Generic;
using System.Linq;
using NzbDrone.Api.REST;
using NzbDrone.Core.MediaCover;
using NzbDrone.Core.Tv;
using NzbDrone.Core.Qualities;
using NzbDrone.Api.Series;
using NzbDrone.Core.MediaFiles;
using NzbDrone.Core.Movies.AlternativeTitles;
using NzbDrone.Core.Parser;
namespace NzbDrone.Api.Movie
{
public class AlternativeYearResource : RestResource
{
public AlternativeYearResource()
{
}
//Todo: Sorters should be done completely on the client
//Todo: Is there an easy way to keep IgnoreArticlesWhenSorting in sync between, Series, History, Missing?
//Todo: We should get the entire Profile instead of ID and Name separately
public int MovieId { get; set; }
public int Year { get; set; }
//TODO: Add series statistics as a property of the series (instead of individual properties)
}
/*public static class AlternativeYearResourceMapper
{
/*public static AlternativeYearResource ToResource(this AlternativeTitle model)
{
if (model == null) return null;
AlternativeTitleResource resource = null;
return new AlternativeTitleResource
{
Id = model.Id,
SourceType = model.SourceType,
MovieId = model.MovieId,
Title = model.Title,
SourceId = model.SourceId,
Votes = model.Votes,
VoteCount = model.VoteCount,
Language = model.Language
};
}
public static AlternativeTitle ToModel(this AlternativeTitleResource resource)
{
if (resource == null) return null;
return new AlternativeTitle
{
Id = resource.Id,
SourceType = resource.SourceType,
MovieId = resource.MovieId,
Title = resource.Title,
SourceId = resource.SourceId,
Votes = resource.Votes,
VoteCount = resource.VoteCount,
Language = resource.Language
};
}
public static List<AlternativeTitleResource> ToResource(this IEnumerable<AlternativeTitle> movies)
{
return movies.Select(ToResource).ToList();
}
}*/
}

View File

@@ -1,7 +1,10 @@
using System.Collections.Generic;
using System;
using System.Collections.Generic;
using System.Linq;
using Nancy;
using Nancy.Responses;
using NzbDrone.Api.Extensions;
using NzbDrone.Api.REST;
using NzbDrone.Core.Tv;
namespace NzbDrone.Api.Movie
@@ -15,6 +18,7 @@ namespace NzbDrone.Api.Movie
{
_movieService = movieService;
Put["/"] = Movie => SaveAll();
Put["/delete"] = Movie => DeleteSelected();
}
private Response SaveAll()
@@ -27,5 +31,33 @@ namespace NzbDrone.Api.Movie
.ToResource()
.AsResponse(HttpStatusCode.Accepted);
}
private Response DeleteSelected()
{
var deleteFiles = false;
var addExclusion = false;
var deleteFilesQuery = Request.Query.deleteFiles;
var addExclusionQuery = Request.Query.addExclusion;
if (deleteFilesQuery.HasValue)
{
deleteFiles = Convert.ToBoolean(deleteFilesQuery.Value);
}
if (addExclusionQuery.HasValue)
{
addExclusion = Convert.ToBoolean(addExclusionQuery.Value);
}
var ids = Request.Body.FromJson<List<int>>();
foreach (var id in ids)
{
_movieService.DeleteMovie(id, deleteFiles, addExclusion);
}
return new Response
{
StatusCode = HttpStatusCode.Accepted
};
}
}
}

View File

@@ -9,7 +9,7 @@ namespace NzbDrone.Api.NetImport
{
public NetImportModule(NetImportFactory netImportFactory) : base(netImportFactory, "netimport")
{
PostValidator.RuleFor(c => c.RootFolderPath).NotNull();
PostValidator.RuleFor(c => c.RootFolderPath).IsValidPath();
PostValidator.RuleFor(c => c.MinimumAvailability).NotNull();
PostValidator.RuleFor(c => c.ProfileId).NotNull();
}

View File

@@ -119,6 +119,9 @@
<Compile Include="Frontend\Mappers\RobotsTxtMapper.cs" />
<Compile Include="Indexers\ReleaseModuleBase.cs" />
<Compile Include="Indexers\ReleasePushModule.cs" />
<Compile Include="Movies\AlternativeTitleModule.cs" />
<Compile Include="Movies\AlternativeYearResource.cs" />
<Compile Include="Movies\AlternativeYearModule.cs" />
<Compile Include="Movies\MovieModuleWithSignalR.cs" />
<Compile Include="Movies\MovieBulkImportModule.cs" />
<Compile Include="Movies\MovieFileModule.cs" />
@@ -240,7 +243,7 @@
<Compile Include="RootFolders\RootFolderModule.cs" />
<Compile Include="RootFolders\RootFolderResource.cs" />
<Compile Include="SeasonPass\SeasonPassResource.cs" />
<Compile Include="Series\AlternateTitleResource.cs" />
<Compile Include="Series\AlternativeTitleResource.cs" />
<Compile Include="Series\MovieFileResource.cs" />
<Compile Include="Series\FetchMovieListModule.cs" />
<Compile Include="Series\SeasonResource.cs" />

View File

@@ -105,7 +105,7 @@ namespace NzbDrone.Api.Queue
throw new NotFoundException();
}
_downloadService.DownloadReport(pendingRelease.RemoteMovie);
_downloadService.DownloadReport(pendingRelease.RemoteMovie, false);
return resource.AsResponse();
}

View File

@@ -1,9 +0,0 @@
namespace NzbDrone.Api.Series
{
public class AlternateTitleResource
{
public string Title { get; set; }
public int? SeasonNumber { get; set; }
public int? SceneSeasonNumber { get; set; }
}
}

View File

@@ -0,0 +1,81 @@
using System;
using System.Collections.Generic;
using System.Linq;
using NzbDrone.Api.REST;
using NzbDrone.Core.MediaCover;
using NzbDrone.Core.Tv;
using NzbDrone.Core.Qualities;
using NzbDrone.Api.Series;
using NzbDrone.Core.MediaFiles;
using NzbDrone.Core.Movies.AlternativeTitles;
using NzbDrone.Core.Parser;
namespace NzbDrone.Api.Movie
{
public class AlternativeTitleResource : RestResource
{
public AlternativeTitleResource()
{
}
//Todo: Sorters should be done completely on the client
//Todo: Is there an easy way to keep IgnoreArticlesWhenSorting in sync between, Series, History, Missing?
//Todo: We should get the entire Profile instead of ID and Name separately
public SourceType SourceType { get; set; }
public int MovieId { get; set; }
public string Title { get; set; }
public string CleanTitle { get; set; }
public int SourceId { get; set; }
public int Votes { get; set; }
public int VoteCount { get; set; }
public Language Language { get; set; }
//TODO: Add series statistics as a property of the series (instead of individual properties)
}
public static class AlternativeTitleResourceMapper
{
public static AlternativeTitleResource ToResource(this AlternativeTitle model)
{
if (model == null) return null;
AlternativeTitleResource resource = null;
return new AlternativeTitleResource
{
Id = model.Id,
SourceType = model.SourceType,
MovieId = model.MovieId,
Title = model.Title,
SourceId = model.SourceId,
Votes = model.Votes,
VoteCount = model.VoteCount,
Language = model.Language
};
}
public static AlternativeTitle ToModel(this AlternativeTitleResource resource)
{
if (resource == null) return null;
return new AlternativeTitle
{
Id = resource.Id,
SourceType = resource.SourceType,
MovieId = resource.MovieId,
Title = resource.Title,
SourceId = resource.SourceId,
Votes = resource.Votes,
VoteCount = resource.VoteCount,
Language = resource.Language
};
}
public static List<AlternativeTitleResource> ToResource(this IEnumerable<AlternativeTitle> movies)
{
return movies.Select(ToResource).ToList();
}
}
}

View File

@@ -21,7 +21,9 @@ namespace NzbDrone.Api.Movie
//View Only
public string Title { get; set; }
public List<AlternateTitleResource> AlternateTitles { get; set; }
public List<AlternativeTitleResource> AlternativeTitles { get; set; }
public int? SecondaryYear { get; set; }
public int SecondaryYearSourceId { get; set; }
public string SortTitle { get; set; }
public long? SizeOnDisk { get; set; }
public MovieStatusType Status { get; set; }
@@ -62,7 +64,7 @@ namespace NzbDrone.Api.Movie
public DateTime Added { get; set; }
public AddMovieOptions AddOptions { get; set; }
public Ratings Ratings { get; set; }
public List<string> AlternativeTitles { get; set; }
//public List<string> AlternativeTitles { get; set; }
public MovieFileResource MovieFile { get; set; }
//TODO: Add series statistics as a property of the series (instead of individual properties)
@@ -107,6 +109,8 @@ namespace NzbDrone.Api.Movie
downloaded = true;
movieFile = model.MovieFile.Value.ToResource();
}
//model.AlternativeTitles.LazyLoad();
return new MovieResource
{
@@ -131,6 +135,8 @@ namespace NzbDrone.Api.Movie
Images = model.Images,
Year = model.Year,
SecondaryYear = model.SecondaryYear,
SecondaryYearSourceId = model.SecondaryYearSourceId,
Path = model.Path,
ProfileId = model.ProfileId,
@@ -156,7 +162,7 @@ namespace NzbDrone.Api.Movie
Tags = model.Tags,
Added = model.Added,
AddOptions = model.AddOptions,
AlternativeTitles = model.AlternativeTitles,
AlternativeTitles = model.AlternativeTitles.ToResource(),
Ratings = model.Ratings,
MovieFile = movieFile,
YouTubeTrailerId = model.YouTubeTrailerId,
@@ -189,6 +195,8 @@ namespace NzbDrone.Api.Movie
Images = resource.Images,
Year = resource.Year,
SecondaryYear = resource.SecondaryYear,
SecondaryYearSourceId = resource.SecondaryYearSourceId,
Path = resource.Path,
ProfileId = resource.ProfileId,
@@ -209,7 +217,7 @@ namespace NzbDrone.Api.Movie
Tags = resource.Tags,
Added = resource.Added,
AddOptions = resource.AddOptions,
AlternativeTitles = resource.AlternativeTitles,
//AlternativeTitles = resource.AlternativeTitles,
Ratings = resource.Ratings,
YouTubeTrailerId = resource.YouTubeTrailerId,
Studio = resource.Studio

View File

@@ -199,7 +199,7 @@ namespace NzbDrone.Api.Series
if (mappings == null) return;
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();
}
public void Handle(EpisodeImportedEvent message)

View File

@@ -20,7 +20,7 @@ namespace NzbDrone.Api.Series
//View Only
public string Title { get; set; }
public List<AlternateTitleResource> AlternateTitles { get; set; }
//public List<AlternativeTitleResource> AlternateTitles { get; set; }
public string SortTitle { get; set; }
public int SeasonCount

View File

@@ -1,4 +1,4 @@
using System.Collections.Generic;
using System.Collections.Generic;
using System.Linq;
using System.Text.RegularExpressions;
using NzbDrone.Core.Datastore.Events;
@@ -12,7 +12,7 @@ namespace NzbDrone.Api.System.Tasks
{
private readonly ITaskManager _taskManager;
private static readonly Regex NameRegex = new Regex("(?<!^)[A-Z]", RegexOptions.Compiled);
private static readonly Regex NameRegex = new Regex("(?<!^)[A-Z][a-z]", RegexOptions.Compiled);
public TaskModule(ITaskManager taskManager, IBroadcastSignalRMessage broadcastSignalRMessage)
: base(broadcastSignalRMessage, "system/task")

View File

@@ -1,4 +1,4 @@
using System;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
@@ -108,7 +108,7 @@ namespace NzbDrone.Common.Disk
}
}
}
public bool CanUseGDIPlus()
{
try
@@ -129,7 +129,7 @@ namespace NzbDrone.Common.Disk
{
return true;
}
try
{
using (var bmp = new Bitmap(filename))
@@ -150,7 +150,7 @@ namespace NzbDrone.Common.Disk
try
{
var testPath = Path.Combine(path, "sonarr_write_test.txt");
var testPath = Path.Combine(path, "radarr_write_test.txt");
var testContent = string.Format("This file was created to verify if '{0}' is writable. It should've been automatically deleted. Feel free to delete it.", path);
File.WriteAllText(testPath, testContent);
File.Delete(testPath);

View File

@@ -105,5 +105,9 @@ namespace NzbDrone.Common.Extensions
yield return buffer.Dequeue();
}
}
public static bool In<T>(this T source, List<T> list)
{
return list.Contains(source);
}
}
}

View File

@@ -136,7 +136,7 @@ namespace NzbDrone.Common.Http.Dispatchers
webRequest.TransferEncoding = header.Value;
break;
case "User-Agent":
throw new NotSupportedException("User-Agent other than Sonarr not allowed.");
throw new NotSupportedException("User-Agent other than Radarr not allowed.");
case "Proxy-Connection":
throw new NotImplementedException();
default:

View File

@@ -1,4 +1,4 @@
using System;
using System;
using System.Net;
using NLog;
using NzbDrone.Common.EnvironmentInfo;
@@ -24,7 +24,7 @@ namespace NzbDrone.Common.Http
public HttpProvider(Logger logger)
{
_logger = logger;
_userAgent = string.Format("Sonarr {0}", BuildInfo.Version);
_userAgent = string.Format("Radarr {0}", BuildInfo.Version);
ServicePointManager.Expect100Continue = false;
}
@@ -58,6 +58,6 @@ namespace NzbDrone.Common.Http
}
}
}
}
}

View File

@@ -263,7 +263,7 @@ namespace NzbDrone.Core.Test.DecisionEngineTests
result.Should().HaveCount(1);
result.First().RemoteMovie.DownloadAllowed.Should().BeFalse();
//result.First().RemoteMovie.DownloadAllowed.Should().BeFalse();
}
[Test]
@@ -278,7 +278,7 @@ namespace NzbDrone.Core.Test.DecisionEngineTests
result.Should().HaveCount(1);
result.First().RemoteMovie.DownloadAllowed.Should().BeFalse();
//result.First().RemoteMovie.DownloadAllowed.Should().BeFalse();
}
[Test]

View File

@@ -76,7 +76,7 @@ namespace NzbDrone.Core.Test.Download.DownloadApprovedReportsTests
decisions.Add(new DownloadDecision(remoteMovie));
Subject.ProcessDecisions(decisions);
Mocker.GetMock<IDownloadService>().Verify(v => v.DownloadReport(It.IsAny<RemoteMovie>()), Times.Once());
Mocker.GetMock<IDownloadService>().Verify(v => v.DownloadReport(It.IsAny<RemoteMovie>(), false), Times.Once());
}
[Test]
@@ -89,7 +89,7 @@ namespace NzbDrone.Core.Test.Download.DownloadApprovedReportsTests
decisions.Add(new DownloadDecision(remoteMovie));
Subject.ProcessDecisions(decisions);
Mocker.GetMock<IDownloadService>().Verify(v => v.DownloadReport(It.IsAny<RemoteMovie>()), Times.Once());
Mocker.GetMock<IDownloadService>().Verify(v => v.DownloadReport(It.IsAny<RemoteMovie>(), false), Times.Once());
}
[Test]
@@ -157,7 +157,7 @@ namespace NzbDrone.Core.Test.Download.DownloadApprovedReportsTests
var decisions = new List<DownloadDecision>();
decisions.Add(new DownloadDecision(remoteMovie));
Mocker.GetMock<IDownloadService>().Setup(s => s.DownloadReport(It.IsAny<RemoteMovie>())).Throws(new Exception());
Mocker.GetMock<IDownloadService>().Setup(s => s.DownloadReport(It.IsAny<RemoteMovie>(), false)).Throws(new Exception());
Subject.ProcessDecisions(decisions).Grabbed.Should().BeEmpty();
ExceptionVerification.ExpectedWarns(1);
}
@@ -183,7 +183,7 @@ namespace NzbDrone.Core.Test.Download.DownloadApprovedReportsTests
decisions.Add(new DownloadDecision(remoteMovie));
Subject.ProcessDecisions(decisions);
Mocker.GetMock<IDownloadService>().Verify(v => v.DownloadReport(It.IsAny<RemoteMovie>()), Times.Never());
Mocker.GetMock<IDownloadService>().Verify(v => v.DownloadReport(It.IsAny<RemoteMovie>(), false), Times.Never());
}
[Test]

View File

@@ -54,7 +54,7 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.QBittorrentTests
protected void GivenRedirectToTorrent()
{
var httpHeader = new HttpHeader();
httpHeader["Location"] = "http://test.sonarr.tv/not-a-real-torrent.torrent";
httpHeader["Location"] = "http://test.radarr.video/not-a-real-torrent.torrent";
Mocker.GetMock<IHttpClient>()
.Setup(s => s.Get(It.Is<HttpRequest>(h => h.Url.FullUri == _downloadUrl)))
@@ -405,7 +405,7 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.QBittorrentTests
[Test]
public void should_get_category_from_the_category_if_set()
{
const string category = "tv-sonarr";
const string category = "movies-radarr";
GivenMaxRatio(1.0f);
var torrent = new QBittorrentTorrent
@@ -430,7 +430,7 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.QBittorrentTests
[Test]
public void should_get_category_from_the_label_if_the_category_is_not_available()
{
const string category = "tv-sonarr";
const string category = "movies-radarr";
GivenMaxRatio(1.0f);
var torrent = new QBittorrentTorrent

View File

@@ -107,7 +107,7 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.UTorrentTests
protected void GivenRedirectToTorrent()
{
var httpHeader = new HttpHeader();
httpHeader["Location"] = "http://test.sonarr.tv/not-a-real-torrent.torrent";
httpHeader["Location"] = "http://test.radarr.video/not-a-real-torrent.torrent";
Mocker.GetMock<IHttpClient>()
.Setup(s => s.Get(It.Is<HttpRequest>(h => h.Url.ToString() == _downloadUrl)))

View File

@@ -8,7 +8,7 @@ namespace NzbDrone.Core.Test.HealthCheck
[TestFixture]
public class HealthCheckFixture : CoreTest
{
private const string WikiRoot = "https://github.com/Sonarr/Sonarr/wiki/";
private const string WikiRoot = "https://github.com/Radarr/Radarr/wiki/";
[TestCase("I blew up because of some weird user mistake", null, WikiRoot + "Health-checks#i-blew-up-because-of-some-weird-user-mistake")]
[TestCase("I blew up because of some weird user mistake", "#my-health-check", WikiRoot + "Health-checks#my-health-check")]

View File

@@ -54,6 +54,7 @@ namespace NzbDrone.Core.Test.ParserTests
[TestCase("Der.Soldat.James.German.Bluray.FuckYou.Pso.Why.cant.you.follow.scene.rules.1998", Language.German)]
[TestCase("Passengers.German.DL.AC3.Dubbed..BluRay.x264-PsO", Language.German)]
[TestCase("Valana la Legende FRENCH BluRay 720p 2016 kjhlj", Language.French)]
[TestCase("Smurfs.The.Lost.Village.2017.1080p.BluRay.HebDub.x264-iSrael",Language.Hebrew)]
public void should_parse_language(string postTitle, Language language)
{
var result = Parser.Parser.ParseMovieTitle(postTitle, true);

View File

@@ -72,6 +72,40 @@ namespace NzbDrone.Core.Test.ParserTests
}
}
[Test]
public void should_not_remove_a_when_at_start_of_acronym()
{
var dirtyFormat = new[]
{
"word.{0}.N.K.L.E.word",
"word {0} N K L E word",
"word-{0}-N-K-L-E-word",
};
foreach (var s in dirtyFormat)
{
var dirty = string.Format(s, "a");
dirty.CleanSeriesTitle().Should().Be("wordankleword");
}
}
[Test]
public void should_not_remove_a_when_at_end_of_acronym()
{
var dirtyFormat = new[]
{
"word.N.K.L.E.{0}.word",
"word N K L E {0} word",
"word-N-K-L-E-{0}-word",
};
foreach (var s in dirtyFormat)
{
var dirty = string.Format(s, "a");
dirty.CleanSeriesTitle().Should().Be("wordnkleaword");
}
}
[TestCase("the")]
[TestCase("and")]
[TestCase("or")]

View File

@@ -86,10 +86,16 @@ namespace NzbDrone.Core.Test.ParserTests
[TestCase("My Movie 1999 German Bluray", "My Movie")]
public void should_parse_movie_title(string postTitle, string title)
{
Parser.Parser.ParseMovieTitle(postTitle, true).MovieTitle.Should().Be(title);
}
Parser.Parser.ParseMovieTitle(postTitle, true).MovieTitle.Should().Be(title);
}
[TestCase("1941.1979.EXTENDED.720p.BluRay.X264-AMIABLE", 1979)]
[TestCase("(1995) Ghost in the Shell", "Ghost in the Shell")]
public void should_parse_movie_folder_name(string postTitle, string title)
{
Parser.Parser.ParseMovieTitle(postTitle, true, true).MovieTitle.Should().Be(title);
}
[TestCase("1941.1979.EXTENDED.720p.BluRay.X264-AMIABLE", 1979)]
[TestCase("Valana la Legende FRENCH BluRay 720p 2016 kjhlj", 2016)]
[TestCase("Der.Soldat.James.German.Bluray.FuckYou.Pso.Why.cant.you.follow.scene.rules.1998", 1998)]
public void should_parse_movie_year(string postTitle, int year)

View File

@@ -6,7 +6,9 @@ using FluentAssertions;
using Moq;
using NUnit.Framework;
using NzbDrone.Core.DataAugmentation.Scene;
using NzbDrone.Core.Datastore;
using NzbDrone.Core.IndexerSearch.Definitions;
using NzbDrone.Core.Movies.AlternativeTitles;
using NzbDrone.Core.Parser;
using NzbDrone.Core.Parser.Model;
using NzbDrone.Core.Tv;
@@ -43,7 +45,7 @@ namespace NzbDrone.Core.Test.ParserTests.ParsingServiceTests
.With(m => m.Title = "Fack Ju Göthe 2")
.With(m => m.CleanTitle = "fackjugoethe2")
.With(m => m.Year = 2015)
.With(m => m.AlternativeTitles = new List<string> { "Fack Ju Göthe 2: Same same" })
.With(m => m.AlternativeTitles = new LazyList<AlternativeTitle>( new List<AlternativeTitle> {new AlternativeTitle("Fack Ju Göthe 2: Same same")}))
.Build();
_episodes = Builder<Episode>.CreateListOfSize(1)
@@ -80,7 +82,7 @@ namespace NzbDrone.Core.Test.ParserTests.ParsingServiceTests
_alternativeTitleInfo = new ParsedMovieInfo
{
MovieTitle = _movie.AlternativeTitles.First(),
MovieTitle = _movie.AlternativeTitles.First().Title,
Year = _movie.Year,
};

View File

@@ -74,8 +74,7 @@ namespace NzbDrone.Core.Test.ParserTests
[TestCase("WEEDS.S03E01-06.DUAL.XviD.Bluray.AC3-REPACK.-HELLYWOOD.avi", true)]
[TestCase("The.Shield.S01E13.NTSC.x264-CtrlSD", false)]
[TestCase("WEEDS.S03E01-06.DUAL.BDRip.XviD.AC3.-HELLYWOOD", false)]
[TestCase("WEEDS.S03E01-06.DUAL.BDRip.X-viD.AC3.-HELLYWOOD", false)]
[TestCase("WEEDS.S03E01-06.DUAL.BDRip.AC3.-HELLYWOOD", false)]
[TestCase("WEEDS.S03E01-06.DUAL.BDRip.X-viD.AC3.-HELLYWOOD", false)]
[TestCase("WEEDS.S03E01-06.DUAL.BDRip.XviD.AC3.-HELLYWOOD.avi", false)]
[TestCase("WEEDS.S03E01-06.DUAL.XviD.Bluray.AC3.-HELLYWOOD.avi", false)]
[TestCase("The.Girls.Next.Door.S03E06.DVDRip.XviD-WiDE", false)]
@@ -83,8 +82,6 @@ namespace NzbDrone.Core.Test.ParserTests
[TestCase("the.shield.1x13.circles.ws.xvidvd-tns", false)]
[TestCase("the_x-files.9x18.sunshine_days.ac3.ws_dvdrip_xvid-fov.avi", false)]
[TestCase("[FroZen] Miyuki - 23 [DVD][7F6170E6]", false)]
[TestCase("Hannibal.S01E05.480p.BluRay.DD5.1.x264-HiSD", false)]
[TestCase("Heidi Girl of the Alps (BD)(640x480(RAW) (BATCH 1) (1-13)", false)]
[TestCase("[Doki] Clannad - 02 (848x480 XviD BD MP3) [95360783]", false)]
public void should_parse_dvd_quality(string title, bool proper)
{
@@ -100,6 +97,14 @@ namespace NzbDrone.Core.Test.ParserTests
ParseAndVerifyQuality(title, Quality.WEBDL480p, proper);
}
[TestCase("Heidi Girl of the Alps (BD)(640x480(RAW) (BATCH 1) (1-13)", false)]
[TestCase("Hannibal.S01E05.480p.BluRay.DD5.1.x264-HiSD", false)]
[TestCase("WEEDS.S03E01-06.DUAL.BDRip.AC3.-HELLYWOOD", false)]
public void should_parse_bluray480p_quality(string title, bool proper)
{
ParseAndVerifyQuality(title, Quality.Bluray480p, proper);
}
[TestCase("Dexter - S01E01 - Title [HDTV]", false)]
[TestCase("Dexter - S01E01 - Title [HDTV-720p]", false)]
[TestCase("Pawn Stars S04E87 REPACK 720p HDTV x264 aAF", true)]
@@ -227,6 +232,7 @@ namespace NzbDrone.Core.Test.ParserTests
[TestCase("Contract.to.Kill.2016.REMUX.1080p.BluRay.AVC.DTS-HD.MA.5.1-iFT")]
[TestCase("27.Dresses.2008.REMUX.1080p.Bluray.AVC.DTS-HR.MA.5.1-LEGi0N")]
[TestCase("27.Dresses.2008.BDREMUX.1080p.Bluray.AVC.DTS-HR.MA.5.1-LEGi0N")]
public void should_parse_remux1080p_quality(string title)
{
ParseAndVerifyQuality(title, Quality.Remux1080p, false);
@@ -301,6 +307,7 @@ namespace NzbDrone.Core.Test.ParserTests
[TestCase("Movie.Title.2016.1080p.KORSUB.WEBRip.x264.AAC2.0-RADARR", "korsub")]
[TestCase("Movie.Title.2016.1080p.KORSUBS.WEBRip.x264.AAC2.0-RADARR", "korsubs")]
[TestCase("Wonder Woman 2017 HC 720p HDRiP DD5 1 x264-LEGi0N", "Generic Hardcoded Subs")]
public void should_parse_hardcoded_subs(string postTitle, string sub)
{
QualityParser.ParseQuality(postTitle).HardcodedSubs.Should().Be(sub);

View File

@@ -1,4 +1,4 @@
using System.Linq;
using System.Linq;
using FizzWare.NBuilder;
using Moq;
using NUnit.Framework;
@@ -39,15 +39,15 @@ namespace NzbDrone.Core.Test.Profiles
[Test]
public void should_not_be_able_to_delete_profile_if_assigned_to_series()
public void should_not_be_able_to_delete_profile_if_assigned_to_movie()
{
var seriesList = Builder<Series>.CreateListOfSize(3)
var movieList = Builder<Movie>.CreateListOfSize(3)
.Random(1)
.With(c => c.ProfileId = 2)
.Build().ToList();
Mocker.GetMock<ISeriesService>().Setup(c => c.GetAllSeries()).Returns(seriesList);
Mocker.GetMock<IMovieService>().Setup(c => c.GetAllMovies()).Returns(movieList);
Assert.Throws<ProfileInUseException>(() => Subject.Delete(2));
@@ -57,15 +57,15 @@ namespace NzbDrone.Core.Test.Profiles
[Test]
public void should_delete_profile_if_not_assigned_to_series()
public void should_delete_profile_if_not_assigned_to_movie()
{
var seriesList = Builder<Series>.CreateListOfSize(3)
var movieList = Builder<Movie>.CreateListOfSize(3)
.All()
.With(c => c.ProfileId = 2)
.Build().ToList();
Mocker.GetMock<ISeriesService>().Setup(c => c.GetAllSeries()).Returns(seriesList);
Mocker.GetMock<IMovieService>().Setup(c => c.GetAllMovies()).Returns(movieList);
Subject.Delete(1);

View File

@@ -3,8 +3,10 @@ using System.Collections.Generic;
using System.Data;
using System.Linq;
using System.Linq.Expressions;
using System.Web.Hosting;
using Marr.Data;
using Marr.Data.QGen;
using NLog;
using NzbDrone.Common.Extensions;
using NzbDrone.Core.Datastore.Events;
using NzbDrone.Core.Datastore.Extensions;
@@ -48,7 +50,7 @@ namespace NzbDrone.Core.Datastore
_eventAggregator = eventAggregator;
}
protected QueryBuilder<TModel> Query => DataMapper.Query<TModel>();
protected QueryBuilder<TModel> Query => AddJoinQueries(DataMapper.Query<TModel>());
protected void Delete(Expression<Func<TModel, bool>> filter)
{
@@ -81,11 +83,12 @@ namespace NzbDrone.Core.Datastore
{
var idList = ids.ToList();
var query = string.Format("Id IN ({0})", string.Join(",", idList));
var result = Query.Where(query).ToList();
var result = Query.Where(m => m.Id.In(idList)).ToList();
//var result = Query.Where(query).ToList();
if (result.Count != idList.Count())
{
throw new ApplicationException("Expected query to return {0} rows but returned {1}".Inject(idList.Count(), result.Count));
throw new ApplicationException("Expected query to return {0} rows but returned {1}.".Inject(idList.Count(), result.Count));
}
return result;
@@ -246,7 +249,8 @@ namespace NzbDrone.Core.Datastore
public virtual PagingSpec<TModel> GetPaged(PagingSpec<TModel> pagingSpec)
{
pagingSpec.Records = GetPagedQuery(Query, pagingSpec).ToList();
pagingSpec.Records = GetPagedQuery(Query, pagingSpec).Skip(pagingSpec.PagingOffset())
.Take(pagingSpec.PageSize).ToList();
pagingSpec.TotalRecords = GetPagedQuery(Query, pagingSpec).GetRowCount();
return pagingSpec;
@@ -255,9 +259,7 @@ namespace NzbDrone.Core.Datastore
protected virtual SortBuilder<TModel> GetPagedQuery(QueryBuilder<TModel> query, PagingSpec<TModel> pagingSpec)
{
return query.Where(pagingSpec.FilterExpression)
.OrderBy(pagingSpec.OrderByClause(), pagingSpec.ToSortDirection())
.Skip(pagingSpec.PagingOffset())
.Take(pagingSpec.PageSize);
.OrderBy(pagingSpec.OrderByClause(), pagingSpec.ToSortDirection());
}
protected void ModelCreated(TModel model)
@@ -283,6 +285,11 @@ namespace NzbDrone.Core.Datastore
}
}
protected virtual QueryBuilder<TModel> AddJoinQueries(QueryBuilder<TModel> baseQuery)
{
return baseQuery;
}
protected virtual bool PublishModelEvents => false;
}
}

View File

@@ -110,10 +110,10 @@ namespace NzbDrone.Core.Datastore
{
if (OsInfo.IsOsx)
{
throw new CorruptDatabaseException("Database file: {0} is corrupt, restore from backup if available. See: https://github.com/Sonarr/Sonarr/wiki/FAQ#i-use-sonarr-on-a-mac-and-it-suddenly-stopped-working-what-happened", ex, fileName);
throw new CorruptDatabaseException("Database file: {0} is corrupt, restore from backup if available. See: https://github.com/Radarr/Radarr/wiki/FAQ#i-use-radarr-on-a-mac-and-it-suddenly-stopped-working-what-happened", ex, fileName);
}
throw new CorruptDatabaseException("Database file: {0} is corrupt, restore from backup if available. See: https://github.com/Sonarr/Sonarr/wiki/FAQ#i-am-getting-an-error-database-disk-image-is-malformed", ex, fileName);
throw new CorruptDatabaseException("Database file: {0} is corrupt, restore from backup if available. See: https://github.com/Radarr/Radarr/wiki/FAQ#i-am-getting-an-error-database-disk-image-is-malformed", ex, fileName);
}
}

View File

@@ -28,12 +28,12 @@ namespace NzbDrone.Core.Datastore.Extensions
return mapBuilder.Relationships.AutoMapComplexTypeProperties<ILazyLoaded>();
}
public static RelationshipBuilder<TParent> HasMany<TParent, TChild>(this RelationshipBuilder<TParent> relationshipBuilder, Expression<Func<TParent, LazyList<TChild>>> portalExpression, Func<TParent, int> childIdSelector)
public static RelationshipBuilder<TParent> HasMany<TParent, TChild>(this RelationshipBuilder<TParent> relationshipBuilder, Expression<Func<TParent, LazyList<TChild>>> portalExpression, Func<TChild, int> parentIdSelector)
where TParent : ModelBase
where TChild : ModelBase
{
return relationshipBuilder.For(portalExpression.GetMemberName())
.LazyLoad((db, parent) => db.Query<TChild>().Where(c => c.Id == childIdSelector(parent)).ToList());
.LazyLoad((db, parent) => db.Query<TChild>().Where(c => parentIdSelector(c) == parent.Id).ToList());
}
private static string GetMemberName<T, TMember>(this Expression<Func<T, TMember>> member)

View File

@@ -0,0 +1,39 @@
using System.Data;
using FluentMigrator;
using NzbDrone.Core.Datastore.Migration.Framework;
using System.Text;
using System.Collections.Generic;
using System.Collections;
using System.Linq;
using System.Text.RegularExpressions;
using System.Globalization;
using Marr.Data.QGen;
using NzbDrone.Common.Extensions;
namespace NzbDrone.Core.Datastore.Migration
{
[Migration(140)]
public class add_alternative_titles_table : NzbDroneMigrationBase
{
protected override void MainDbUpgrade()
{
if (!this.Schema.Schema("dbo").Table("alternative_titles").Exists())
{
Create.TableForModel("AlternativeTitles")
.WithColumn("MovieId").AsInt64().NotNullable()
.WithColumn("Title").AsString().NotNullable()
.WithColumn("CleanTitle").AsString().NotNullable()
.WithColumn("SourceType").AsInt64().WithDefault(0)
.WithColumn("SourceId").AsInt64().WithDefault(0)
.WithColumn("Votes").AsInt64().WithDefault(0)
.WithColumn("VoteCount").AsInt64().WithDefault(0)
.WithColumn("Language").AsInt64().WithDefault(0);
Delete.Column("AlternativeTitles").FromTable("Movies");
}
Alter.Table("Movies").AddColumn("SecondaryYear").AsInt32().Nullable();
Alter.Table("Movies").AddColumn("SecondaryYearSourceId").AsInt64().Nullable().WithDefault(0);
}
}
}

View File

@@ -36,6 +36,45 @@ using NzbDrone.Core.Extras.Subtitles;
using NzbDrone.Core.Messaging.Commands;
using NzbDrone.Core.NetImport;
using NzbDrone.Core.NetImport.ImportExclusions;
using System;
using System.Collections.Generic;
using Marr.Data;
using Marr.Data.Mapping;
using NzbDrone.Common.Reflection;
using NzbDrone.Core.Blacklisting;
using NzbDrone.Core.Configuration;
using NzbDrone.Core.DataAugmentation.Scene;
using NzbDrone.Core.Datastore.Converters;
using NzbDrone.Core.Datastore.Extensions;
using NzbDrone.Core.Download;
using NzbDrone.Core.Download.Pending;
using NzbDrone.Core.Indexers;
using NzbDrone.Core.Instrumentation;
using NzbDrone.Core.Jobs;
using NzbDrone.Core.MediaFiles;
using NzbDrone.Core.Profiles.Delay;
using NzbDrone.Core.RemotePathMappings;
using NzbDrone.Core.Notifications;
using NzbDrone.Core.Organizer;
using NzbDrone.Core.Parser.Model;
using NzbDrone.Core.Profiles;
using NzbDrone.Core.Qualities;
using NzbDrone.Core.Restrictions;
using NzbDrone.Core.RootFolders;
using NzbDrone.Core.SeriesStats;
using NzbDrone.Core.Tags;
using NzbDrone.Core.ThingiProvider;
using NzbDrone.Core.Tv;
using NzbDrone.Common.Disk;
using NzbDrone.Core.Authentication;
using NzbDrone.Core.Extras.Metadata;
using NzbDrone.Core.Extras.Metadata.Files;
using NzbDrone.Core.Extras.Others;
using NzbDrone.Core.Extras.Subtitles;
using NzbDrone.Core.Messaging.Commands;
using NzbDrone.Core.Movies.AlternativeTitles;
using NzbDrone.Core.NetImport;
using NzbDrone.Core.NetImport.ImportExclusions;
namespace NzbDrone.Core.Datastore
{
@@ -101,12 +140,19 @@ namespace NzbDrone.Core.Datastore
query: (db, parent) => db.Query<Movie>().Where(c => c.MovieFileId == parent.Id).ToList())
.HasOne(file => file.Movie, file => file.MovieId);
Mapper.Entity<Movie>().RegisterModel("Movies")
Mapper.Entity<Movie>().RegisterModel("Movies")
.Ignore(s => s.RootFolderPath)
.Relationship()
.HasOne(s => s.Profile, s => s.ProfileId)
.HasOne(m => m.MovieFile, m => m.MovieFileId);
Mapper.Entity<AlternativeTitle>().RegisterModel("AlternativeTitles")
.For(t => t.Id)
.SetAltName("AltTitle_Id")
.Relationship()
.HasOne(t => t.Movie, t => t.MovieId);
Mapper.Entity<ImportExclusion>().RegisterModel("ImportExclusions");

View File

@@ -113,11 +113,11 @@ namespace NzbDrone.Core.DecisionEngine
var remoteMovie = result.RemoteMovie;
remoteMovie.Release = report;
remoteMovie.MappingResult = result.MappingResultType;
if (result.MappingResultType != MappingResultType.Success && result.MappingResultType != MappingResultType.SuccessLenientMapping)
{
var rejection = result.ToRejection();
remoteMovie.Movie = null; // HACK: For now!
decision = new DownloadDecision(remoteMovie, rejection);
}
@@ -125,7 +125,7 @@ namespace NzbDrone.Core.DecisionEngine
{
if (parsedMovieInfo.Quality.HardcodedSubs.IsNotNullOrWhiteSpace())
{
remoteMovie.DownloadAllowed = true;
//remoteMovie.DownloadAllowed = true;
if (_configService.AllowHardcodedSubs)
{
decision = GetDecisionForReport(remoteMovie, searchCriteria);
@@ -146,7 +146,7 @@ namespace NzbDrone.Core.DecisionEngine
}
else
{
remoteMovie.DownloadAllowed = true;
//remoteMovie.DownloadAllowed = true;
decision = GetDecisionForReport(remoteMovie, searchCriteria);
}

View File

@@ -2,6 +2,7 @@
using System.Collections.Generic;
using NzbDrone.Core.Profiles.Delay;
using NzbDrone.Core.Configuration;
using NzbDrone.Core.Parser;
namespace NzbDrone.Core.DecisionEngine
{
@@ -36,13 +37,13 @@ namespace NzbDrone.Core.DecisionEngine
public List<DownloadDecision> PrioritizeDecisionsForMovies(List<DownloadDecision> decisions)
{
return decisions.Where(c => c.RemoteMovie.Movie != null)
return decisions.Where(c => c.RemoteMovie.MappingResult == MappingResultType.Success || c.RemoteMovie.MappingResult == MappingResultType.SuccessLenientMapping)
.GroupBy(c => c.RemoteMovie.Movie.Id, (movieId, downloadDecisions) =>
{
return downloadDecisions.OrderByDescending(decision => decision, new DownloadDecisionComparer(_delayProfileService, _configService));
})
.SelectMany(c => c)
.Union(decisions.Where(c => c.RemoteMovie.Movie == null))
.Union(decisions.Where(c => c.RemoteMovie.MappingResult != MappingResultType.Success || c.RemoteMovie.MappingResult != MappingResultType.SuccessLenientMapping))
.ToList();
}
}

View File

@@ -1,4 +1,4 @@
using System.Text.RegularExpressions;
using System.Text.RegularExpressions;
using FluentValidation;
using NzbDrone.Common.Extensions;
using NzbDrone.Core.Annotations;
@@ -42,7 +42,7 @@ namespace NzbDrone.Core.Download.Clients.DownloadStation
[FieldDefinition(3, Label = "Password", Type = FieldType.Password)]
public string Password { get; set; }
[FieldDefinition(4, Label = "Category", Type = FieldType.Textbox, HelpText = "Adding a category specific to Sonarr avoids conflicts with unrelated downloads, but it's optional. Creates a [category] subdirectory in the output directory.")]
[FieldDefinition(4, Label = "Category", Type = FieldType.Textbox, HelpText = "Adding a category specific to Radarr avoids conflicts with unrelated downloads, but it's optional. Creates a [category] subdirectory in the output directory.")]
public string TvCategory { get; set; }
[FieldDefinition(5, Label = "Directory", Type = FieldType.Textbox, HelpText = "Optional shared folder to put downloads into, leave blank to use the default Download Station location")]

View File

@@ -1,4 +1,4 @@
using System;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
@@ -345,7 +345,7 @@ namespace NzbDrone.Core.Download.Clients.DownloadStation
_logger.Error(ex, ex.Message);
return new NzbDroneValidationFailure("Username", "Authentication failure")
{
DetailedDescription = $"Please verify your username and password. Also verify if the host running Sonarr isn't blocked from accessing {Name} by WhiteList limitations in the {Name} configuration."
DetailedDescription = $"Please verify your username and password. Also verify if the host running Radarr isn't blocked from accessing {Name} by WhiteList limitations in the {Name} configuration."
};
}
catch (WebException ex)

View File

@@ -1,4 +1,4 @@
using System;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
@@ -259,7 +259,7 @@ namespace NzbDrone.Core.Download.Clients.DownloadStation
_logger.Error(ex, ex.Message);
return new NzbDroneValidationFailure("Username", "Authentication failure")
{
DetailedDescription = $"Please verify your username and password. Also verify if the host running Sonarr isn't blocked from accessing {Name} by WhiteList limitations in the {Name} configuration."
DetailedDescription = $"Please verify your username and password. Also verify if the host running Radarr isn't blocked from accessing {Name} by WhiteList limitations in the {Name} configuration."
};
}
catch (WebException ex)

View File

@@ -1,4 +1,4 @@
using System;
using System;
using System.Linq;
using System.Collections.Generic;
using NzbDrone.Common.Disk;
@@ -111,7 +111,7 @@ namespace NzbDrone.Core.Download.Clients.QBittorrent
{
case "error": // some error occurred, applies to paused torrents
item.Status = DownloadItemStatus.Failed;
item.Message = "QBittorrent is reporting an error";
item.Message = "qBittorrent is reporting an error";
break;
case "pausedDL": // torrent is paused and has NOT finished downloading
@@ -212,7 +212,7 @@ namespace NzbDrone.Core.Download.Clients.QBittorrent
var config = _proxy.GetConfig(Settings);
if (config.MaxRatioEnabled && config.RemoveOnMaxRatio)
{
return new NzbDroneValidationFailure(String.Empty, "QBittorrent is configured to remove torrents when they reach their Share Ratio Limit")
return new NzbDroneValidationFailure(String.Empty, "qBittorrent is configured to remove torrents when they reach their Share Ratio Limit")
{
DetailedDescription = "Radarr will be unable to perform Completed Download Handling as configured. You can fix this in qBittorrent ('Tools -> Options...' in the menu) by changing 'Options -> BitTorrent -> Share Ratio Limiting' from 'Remove them' to 'Pause them'."
};

View File

@@ -1,4 +1,4 @@
using System;
using System;
using System.Collections.Generic;
using System.Net;
using NLog;
@@ -72,7 +72,13 @@ namespace NzbDrone.Core.Download.Clients.QBittorrent
.Post()
.AddFormParameter("urls", torrentUrl);
ProcessRequest<object>(request, settings);
var result = ProcessRequest(request, settings);
// Note: Older qbit versions returned nothing, so we can't do != "Ok." here.
if (result == "Fails.")
{
throw new DownloadClientException("Download client failed to add torrent by url");
}
}
public void AddTorrentFromFile(string fileName, Byte[] fileContent, QBittorrentSettings settings)
@@ -81,7 +87,13 @@ namespace NzbDrone.Core.Download.Clients.QBittorrent
.Post()
.AddFormUpload("torrents", fileName, fileContent);
ProcessRequest<object>(request, settings);
var result = ProcessRequest(request, settings);
// Note: Current qbit versions return nothing, so we can't do != "Ok." here.
if (result == "Fails.")
{
throw new DownloadClientException("Download client failed to add torrent");
}
}
public void RemoveTorrent(string hash, Boolean removeData, QBittorrentSettings settings)
@@ -90,7 +102,7 @@ namespace NzbDrone.Core.Download.Clients.QBittorrent
.Post()
.AddFormParameter("hashes", hash);
ProcessRequest<object>(request, settings);
ProcessRequest(request, settings);
}
public void SetTorrentLabel(string hash, string label, QBittorrentSettings settings)
@@ -101,7 +113,7 @@ namespace NzbDrone.Core.Download.Clients.QBittorrent
.AddFormParameter("category", label);
try
{
ProcessRequest<object>(setCategoryRequest, settings);
ProcessRequest(setCategoryRequest, settings);
}
catch(DownloadClientException ex)
{
@@ -112,7 +124,8 @@ namespace NzbDrone.Core.Download.Clients.QBittorrent
.Post()
.AddFormParameter("hashes", hash)
.AddFormParameter("label", label);
ProcessRequest<object>(setLabelRequest, settings);
ProcessRequest(setLabelRequest, settings);
}
}
}
@@ -125,7 +138,7 @@ namespace NzbDrone.Core.Download.Clients.QBittorrent
try
{
var response = ProcessRequest<object>(request, settings);
ProcessRequest(request, settings);
}
catch (DownloadClientException ex)
{
@@ -152,10 +165,18 @@ namespace NzbDrone.Core.Download.Clients.QBittorrent
private TResult ProcessRequest<TResult>(HttpRequestBuilder requestBuilder, QBittorrentSettings settings)
where TResult : new()
{
var responseContent = ProcessRequest(requestBuilder, settings);
return Json.Deserialize<TResult>(responseContent);
}
private string ProcessRequest(HttpRequestBuilder requestBuilder, QBittorrentSettings settings)
{
AuthenticateClient(requestBuilder, settings);
var request = requestBuilder.Build();
request.LogResponseContent = true;
HttpResponse response;
try
@@ -176,15 +197,15 @@ namespace NzbDrone.Core.Download.Clients.QBittorrent
}
else
{
throw new DownloadClientException("Failed to connect to qBitTorrent, check your settings.", ex);
throw new DownloadClientException("Failed to connect to qBittorrent, check your settings.", ex);
}
}
catch (WebException ex)
{
throw new DownloadClientException("Failed to connect to qBitTorrent, please check your settings.", ex);
throw new DownloadClientException("Failed to connect to qBittorrent, please check your settings.", ex);
}
return Json.Deserialize<TResult>(response.Content);
return response.Content;
}
private void AuthenticateClient(HttpRequestBuilder requestBuilder, QBittorrentSettings settings, bool reauthenticate = false)
@@ -218,23 +239,23 @@ namespace NzbDrone.Core.Download.Clients.QBittorrent
_logger.Debug("qbitTorrent authentication failed.");
if (ex.Response.StatusCode == HttpStatusCode.Forbidden)
{
throw new DownloadClientAuthenticationException("Failed to authenticate with qbitTorrent.", ex);
throw new DownloadClientAuthenticationException("Failed to authenticate with qBittorrent.", ex);
}
throw new DownloadClientException("Failed to connect to qBitTorrent, please check your settings.", ex);
throw new DownloadClientException("Failed to connect to qBittorrent, please check your settings.", ex);
}
catch (WebException ex)
{
throw new DownloadClientException("Failed to connect to qBitTorrent, please check your settings.", ex);
throw new DownloadClientException("Failed to connect to qBittorrent, please check your settings.", ex);
}
if (response.Content != "Ok.") // returns "Fails." on bad login
{
_logger.Debug("qbitTorrent authentication failed.");
throw new DownloadClientAuthenticationException("Failed to authenticate with qbitTorrent.");
throw new DownloadClientAuthenticationException("Failed to authenticate with qBittorrent.");
}
_logger.Debug("qbitTorrent authentication succeeded.");
_logger.Debug("qBittorrent authentication succeeded.");
cookies = response.GetCookies();

View File

@@ -14,7 +14,7 @@ namespace NzbDrone.Core.Download
public interface IDownloadService
{
void DownloadReport(RemoteEpisode remoteEpisode);
void DownloadReport(RemoteMovie remoteMovie);
void DownloadReport(RemoteMovie remoteMovie, bool forceDownload);
}
@@ -92,7 +92,7 @@ namespace NzbDrone.Core.Download
_eventAggregator.PublishEvent(episodeGrabbedEvent);
}
public void DownloadReport(RemoteMovie remoteMovie)
public void DownloadReport(RemoteMovie remoteMovie, bool foceDownload = false)
{
//Ensure.That(remoteEpisode.Series, () => remoteEpisode.Series).IsNotNull();
//Ensure.That(remoteEpisode.Episodes, () => remoteEpisode.Episodes).HasItems(); TODO update this shit

View File

@@ -88,7 +88,7 @@ namespace NzbDrone.Core.Download
try
{
_downloadService.DownloadReport(remoteMovie);
_downloadService.DownloadReport(remoteMovie, false);
grabbed.Add(report);
}
catch (Exception e)

View File

@@ -39,7 +39,7 @@ namespace NzbDrone.Core.HealthCheck
private static HttpUri MakeWikiUrl(string fragment)
{
return new HttpUri("https://github.com/Sonarr/Sonarr/wiki/Health-checks") + new HttpUri(fragment);
return new HttpUri("https://github.com/Radarr/Radarr/wiki/Health-checks") + new HttpUri(fragment);
}
}

View File

@@ -0,0 +1,26 @@
using NzbDrone.Core.Datastore;
namespace NzbDrone.Core.Housekeeping.Housekeepers
{
public class CleanupOrphanedAlternativeTitles : IHousekeepingTask
{
private readonly IMainDatabase _database;
public CleanupOrphanedAlternativeTitles(IMainDatabase database)
{
_database = database;
}
public void Clean()
{
var mapper = _database.GetDataMapper();
mapper.ExecuteNonQuery(@"DELETE FROM AlternativeTitles
WHERE Id IN (
SELECT AlternativeTitles.Id FROM AlternativeTitles
LEFT OUTER JOIN Movies
ON AlternativeTitles.MovieId = Movies.Id
WHERE Movies.Id IS NULL)");
}
}
}

View File

@@ -71,11 +71,16 @@ namespace NzbDrone.Core.Indexers.AwesomeHD
var title = $"{torrent.Name}.{torrent.Year}.{torrent.Resolution}.{torrent.Media}.{torrent.Encoding}.{torrent.AudioFormat}-{torrent.ReleaseGroup}";
IndexerFlags flags = 0;
if (torrent.Freeleech == "1.00")
if (torrent.Freeleech == "0.00" || torrent.Freeleech == "0.25")
{
flags |= IndexerFlags.G_Freeleech;
}
if (torrent.Freeleech == "0.50")
{
flags |= IndexerFlags.G_Halfleech;
}
torrentInfos.Add(new TorrentInfo()
{
Guid = string.Format("AwesomeHD-{0}", id),

View File

@@ -1,4 +1,4 @@
using System;
using System;
using System.Collections.Generic;
using System.Net;
using System.Xml;
@@ -59,7 +59,7 @@ namespace NzbDrone.Core.Indexers.Newznab
}
catch (Exception ex)
{
_logger.Debug(ex, "Failed to get newznab api capabilities from {0}", indexerSettings.BaseUrl);
_logger.Debug(ex, "Failed to get Newznab API capabilities from {0}", indexerSettings.BaseUrl);
throw;
}
@@ -74,7 +74,7 @@ namespace NzbDrone.Core.Indexers.Newznab
}
catch (Exception ex)
{
_logger.Error(ex, "Failed to determine newznab api capabilities for {0}, using the defaults instead till Sonarr restarts.", indexerSettings.BaseUrl);
_logger.Error(ex, "Failed to determine newznab api capabilities for {0}, using the defaults instead till Radarr restarts.", indexerSettings.BaseUrl);
}
return capabilities;
@@ -84,7 +84,19 @@ namespace NzbDrone.Core.Indexers.Newznab
{
var capabilities = new NewznabCapabilities();
var xmlRoot = XDocument.Parse(response.Content).Element("caps");
var xDoc = XDocument.Parse(response.Content);
if (xDoc == null)
{
throw new XmlException("Invalid XML");
}
var xmlRoot = xDoc.Element("caps");
if (xmlRoot == null)
{
throw new XmlException("Unexpected XML");
}
var xmlLimits = xmlRoot.Element("limits");
if (xmlLimits != null)

View File

@@ -59,7 +59,7 @@ namespace NzbDrone.Core.Indexers.Newznab
else
{
var searchTitle = System.Web.HttpUtility.UrlPathEncode(Parser.Parser.ReplaceGermanUmlauts(Parser.Parser.NormalizeTitle(searchCriteria.Movie.Title)));
var altTitles = searchCriteria.Movie.AlternativeTitles.DistinctBy(t => Parser.Parser.CleanSeriesTitle(t)).Take(5).ToList();
var altTitles = searchCriteria.Movie.AlternativeTitles.Take(5).Select(t => t.Title).ToList();
var realMaxPages = (int)MaxPages / (altTitles.Count() + 1);

View File

@@ -56,7 +56,7 @@ namespace NzbDrone.Core.Indexers.Torznab
MinimumSeeders = IndexerDefaults.MINIMUM_SEEDERS;
}
[FieldDefinition(6, Type = FieldType.Textbox, Label = "Minimum Seeders", HelpText = "Minimum number of seeders required.", Advanced = true)]
[FieldDefinition(5, Type = FieldType.Textbox, Label = "Minimum Seeders", HelpText = "Minimum number of seeders required.", Advanced = true)]
public int MinimumSeeders { get; set; }
public override NzbDroneValidationResult Validate()

View File

@@ -151,7 +151,7 @@ namespace NzbDrone.Core.MediaFiles
{
_logger.Error("Movie is null for the file {0}. Please run the houskeeping command to ensure movies and files are linked correctly.");
}
_movieService.SetFileId(addedFile.Movie.Value, addedFile); //Should not be necessary, but sometimes below fails?
//_movieService.SetFileId(addedFile.Movie.Value, addedFile); //Should not be necessary, but sometimes below fails?
_eventAggregator.PublishEvent(new MovieFileAddedEvent(addedFile));
return addedFile;

View File

@@ -20,7 +20,7 @@ namespace NzbDrone.Core.MediaFiles
void Cleanup();
}
public class RecycleBinProvider : IHandleAsync<SeriesDeletedEvent>, IExecute<CleanUpRecycleBinCommand>, IRecycleBinProvider
public class RecycleBinProvider : IHandleAsync<SeriesDeletedEvent>, IExecute<CleanUpRecycleBinCommand>, IRecycleBinProvider, IHandleAsync<MovieDeletedEvent>
{
private readonly IDiskTransferService _diskTransferService;
private readonly IDiskProvider _diskProvider;
@@ -201,6 +201,17 @@ namespace NzbDrone.Core.MediaFiles
}
}
public void HandleAsync(MovieDeletedEvent message)
{
if (message.DeleteFiles)
{
if (_diskProvider.FolderExists(message.Movie.Path))
{
DeleteFolder(message.Movie.Path);
}
}
}
public void Execute(CleanUpRecycleBinCommand message)
{
Cleanup();

View File

@@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
using System.Data.SQLite;
using System.Linq;
using System.Text;
using System.IO;
@@ -198,14 +199,22 @@ namespace NzbDrone.Core.MediaFiles
public void Execute(RenameMovieFolderCommand message)
{
_logger.Debug("Renaming movie folder for selected movie if necessary");
var moviesToRename = _movieService.GetMovies(message.MovieIds);
foreach(var movie in moviesToRename)
{
var movieFiles = _mediaFileService.GetFilesByMovie(movie.Id);
_logger.ProgressInfo("Renaming movie folder for {0}", movie.Title);
RenameMoviePath(movie);
}
try
{
_logger.Debug("Renaming movie folder for selected movie if necessary");
var moviesToRename = _movieService.GetMovies(message.MovieIds);
foreach(var movie in moviesToRename)
{
var movieFiles = _mediaFileService.GetFilesByMovie(movie.Id);
_logger.ProgressInfo("Renaming movie folder for {0}", movie.Title);
RenameMoviePath(movie);
}
}
catch (SQLiteException ex)
{
_logger.Warn(ex, "wtf: {0}, {1}", ex.ResultCode, ex.Data);
}
}
}
}

View File

@@ -3,7 +3,10 @@ using NzbDrone.Core.Configuration;
using System;
using Newtonsoft.Json;
using System.Collections.Generic;
using System.Linq;
using NzbDrone.Core.MetadataSource.SkyHook.Resource;
using NzbDrone.Core.Movies.AlternativeTitles;
using NzbDrone.Core.Parser;
namespace NzbDrone.Core.MetadataSource.RadarrAPI
{
@@ -11,6 +14,10 @@ namespace NzbDrone.Core.MetadataSource.RadarrAPI
{
IHttpRequestBuilderFactory RadarrAPI { get; }
List<MovieResult> DiscoverMovies(string action, Func<HttpRequest, HttpRequest> enhanceRequest);
List<AlternativeTitle> AlternativeTitlesForMovie(int TmdbId);
Tuple<List<AlternativeTitle>, AlternativeYear> AlternativeTitlesAndYearForMovie(int tmdbId);
AlternativeTitle AddNewAlternativeTitle(AlternativeTitle title, int TmdbId);
AlternativeYear AddNewAlternativeYear(int year, int tmdbId);
string APIURL { get; }
}
@@ -65,7 +72,7 @@ namespace NzbDrone.Core.MetadataSource.RadarrAPI
{
var error = JsonConvert.DeserializeObject<RadarrError>(response.Content);
if (error != null && error.Errors.Count != 0)
if (error != null && error.Errors != null && error.Errors.Count != 0)
{
throw new RadarrAPIException(error);
}
@@ -96,6 +103,83 @@ namespace NzbDrone.Core.MetadataSource.RadarrAPI
return Execute<List<MovieResult>>(request);
}
public List<AlternativeTitle> AlternativeTitlesForMovie(int TmdbId)
{
var request = RadarrAPI.Create().SetSegment("route", "mappings").SetSegment("action", "find").AddQueryParam("tmdbid", TmdbId).Build();
var mappings = Execute<Mapping>(request);
var titles = new List<NzbDrone.Core.Movies.AlternativeTitles.AlternativeTitle>();
foreach (var altTitle in mappings.Mappings.Titles)
{
titles.Add(new NzbDrone.Core.Movies.AlternativeTitles.AlternativeTitle(altTitle.Info.AkaTitle, SourceType.Mappings, altTitle.Id));
}
return titles;
}
public Tuple<List<AlternativeTitle>, AlternativeYear> AlternativeTitlesAndYearForMovie(int tmdbId)
{
var request = RadarrAPI.Create().SetSegment("route", "mappings").SetSegment("action", "find").AddQueryParam("tmdbid", tmdbId).Build();
var mappings = Execute<Mapping>(request);
var titles = new List<NzbDrone.Core.Movies.AlternativeTitles.AlternativeTitle>();
foreach (var altTitle in mappings.Mappings.Titles)
{
titles.Add(new NzbDrone.Core.Movies.AlternativeTitles.AlternativeTitle(altTitle.Info.AkaTitle, SourceType.Mappings, altTitle.Id));
}
var year = mappings.Mappings.Years.Where(y => y.Votes >= 3).OrderBy(y => y.Votes).FirstOrDefault();
AlternativeYear newYear = null;
if (year != null)
{
newYear = new AlternativeYear
{
Year = year.Info.AkaYear,
SourceId = year.Id
};
}
return new Tuple<List<AlternativeTitle>, AlternativeYear>(titles, newYear);
}
public AlternativeTitle AddNewAlternativeTitle(AlternativeTitle title, int TmdbId)
{
var request = RadarrAPI.Create().SetSegment("route", "mappings").SetSegment("action", "add")
.AddQueryParam("tmdbid", TmdbId).AddQueryParam("type", "title")
.AddQueryParam("language", IsoLanguages.Get(title.Language).TwoLetterCode)
.AddQueryParam("aka_title", title.Title).Build();
var newMapping = Execute<AddTitleMapping>(request);
var newTitle = new AlternativeTitle(newMapping.Info.AkaTitle, SourceType.Mappings, newMapping.Id, title.Language);
newTitle.VoteCount = newMapping.VoteCount;
newTitle.Votes = newMapping.Votes;
return newTitle;
}
public AlternativeYear AddNewAlternativeYear(int year, int tmdbId)
{
var request = RadarrAPI.Create().SetSegment("route", "mappings").SetSegment("action", "add")
.AddQueryParam("tmdbid", tmdbId).AddQueryParam("type", "year")
.AddQueryParam("aka_year", year).Build();
var newYear = Execute<AddYearMapping>(request);
return new AlternativeYear
{
Year = newYear.Info.AkaYear,
SourceId = newYear.Id
};
}
public IHttpRequestBuilderFactory RadarrAPI { get; private set; }
}
}

View File

@@ -27,21 +27,183 @@ namespace NzbDrone.Core.MetadataSource.RadarrAPI
public class RadarrAPIException : Exception
{
RadarrError APIErrors;
public RadarrError APIErrors;
public RadarrAPIException(RadarrError apiError) : base(HumanReadable(apiError))
{
APIErrors = apiError;
}
private static string HumanReadable(RadarrError APIErrors)
private static string HumanReadable(RadarrError apiErrors)
{
var firstError = APIErrors.Errors.First();
var details = string.Join("\n", APIErrors.Errors.Select(error =>
var firstError = apiErrors.Errors.First();
var details = string.Join("\n", apiErrors.Errors.Select(error =>
{
return $"{error.Title} ({error.Status}, RayId: {error.RayId}), Details: {error.Detail}";
}));
return $"Error while calling api: {firstError.Title}\nFull error(s): {details}";
}
}
public class TitleInfo
{
[JsonProperty("id")]
public int Id { get; set; }
[JsonProperty("aka_title")]
public string AkaTitle { get; set; }
[JsonProperty("aka_clean_title")]
public string AkaCleanTitle { get; set; }
}
public class YearInfo
{
[JsonProperty("id")]
public int Id { get; set; }
[JsonProperty("aka_year")]
public int AkaYear { get; set; }
}
public class Title
{
[JsonProperty("id")]
public int Id { get; set; }
[JsonProperty("tmdbid")]
public int Tmdbid { get; set; }
[JsonProperty("votes")]
public int Votes { get; set; }
[JsonProperty("vote_count")]
public int VoteCount { get; set; }
[JsonProperty("locked")]
public bool Locked { get; set; }
[JsonProperty("info_type")]
public string InfoType { get; set; }
[JsonProperty("info_id")]
public int InfoId { get; set; }
[JsonProperty("info")]
public TitleInfo Info { get; set; }
}
public class Year
{
[JsonProperty("id")]
public int Id { get; set; }
[JsonProperty("tmdbid")]
public int Tmdbid { get; set; }
[JsonProperty("votes")]
public int Votes { get; set; }
[JsonProperty("vote_count")]
public int VoteCount { get; set; }
[JsonProperty("locked")]
public bool Locked { get; set; }
[JsonProperty("info_type")]
public string InfoType { get; set; }
[JsonProperty("info_id")]
public int InfoId { get; set; }
[JsonProperty("info")]
public YearInfo Info { get; set; }
}
public class Mappings
{
[JsonProperty("titles")]
public IList<Title> Titles { get; set; }
[JsonProperty("years")]
public IList<Year> Years { get; set; }
}
public class Mapping
{
[JsonProperty("id")]
public int Id { get; set; }
[JsonProperty("title")]
public string Title { get; set; }
[JsonProperty("imdb_id")]
public string ImdbId { get; set; }
[JsonProperty("mappings")]
public Mappings Mappings { get; set; }
}
public class AddTitleMapping
{
[JsonProperty("tmdbid")]
public string Tmdbid { get; set; }
[JsonProperty("info_type")]
public string InfoType { get; set; }
[JsonProperty("info_id")]
public int InfoId { get; set; }
[JsonProperty("id")]
public int Id { get; set; }
[JsonProperty("info")]
public TitleInfo Info { get; set; }
[JsonProperty("votes")]
public int Votes { get; set; }
[JsonProperty("vote_count")]
public int VoteCount { get; set; }
[JsonProperty("locked")]
public bool Locked { get; set; }
}
public class AddYearMapping
{
[JsonProperty("tmdbid")]
public string Tmdbid { get; set; }
[JsonProperty("info_type")]
public string InfoType { get; set; }
[JsonProperty("info_id")]
public int InfoId { get; set; }
[JsonProperty("id")]
public int Id { get; set; }
[JsonProperty("info")]
public YearInfo Info { get; set; }
[JsonProperty("votes")]
public int Votes { get; set; }
[JsonProperty("vote_count")]
public int VoteCount { get; set; }
[JsonProperty("locked")]
public bool Locked { get; set; }
}
}

View File

@@ -2,7 +2,8 @@
using System.Collections.Generic;
using System.Linq;
using System.Net;
using NLog;
using System.ServiceModel;
using NLog;
using NzbDrone.Common.Cloud;
using NzbDrone.Common.Extensions;
using NzbDrone.Common.Http;
@@ -19,6 +20,7 @@ using NzbDrone.Common.Serializer;
using NzbDrone.Core.NetImport.ImportExclusions;
using NzbDrone.Core.Configuration;
using NzbDrone.Core.MetadataSource.RadarrAPI;
using NzbDrone.Core.Movies.AlternativeTitles;
namespace NzbDrone.Core.MetadataSource.SkyHook
{
@@ -33,12 +35,13 @@ namespace NzbDrone.Core.MetadataSource.SkyHook
private readonly IMovieService _movieService;
private readonly IPreDBService _predbService;
private readonly IImportExclusionsService _exclusionService;
private readonly IAlternativeTitleService _altTitleService;
private readonly IRadarrAPIClient _radarrAPI;
private readonly IHttpRequestBuilderFactory _apiBuilder;
public SkyHookProxy(IHttpClient httpClient, ISonarrCloudRequestBuilder requestBuilder, ITmdbConfigService configService, IMovieService movieService,
IPreDBService predbService, IImportExclusionsService exclusionService, IRadarrAPIClient radarrAPI, Logger logger)
IPreDBService predbService, IImportExclusionsService exclusionService, IAlternativeTitleService altTitleService, IRadarrAPIClient radarrAPI, Logger logger)
{
_httpClient = httpClient;
_requestBuilder = requestBuilder.SkyHookTvdb;
@@ -47,6 +50,7 @@ namespace NzbDrone.Core.MetadataSource.SkyHook
_movieService = movieService;
_predbService = predbService;
_exclusionService = exclusionService;
_altTitleService = altTitleService;
_radarrAPI = radarrAPI;
_logger = logger;
@@ -133,21 +137,28 @@ namespace NzbDrone.Core.MetadataSource.SkyHook
}
var movie = new Movie();
var altTitles = new List<AlternativeTitle>();
if (langCode != "us")
if (langCode != "en")
{
movie.AlternativeTitles.Add(resource.original_title);
var iso = IsoLanguages.Find(resource.original_language);
if (iso != null)
{
altTitles.Add(new AlternativeTitle(resource.original_title, SourceType.TMDB, TmdbId, iso.Language));
}
//movie.AlternativeTitles.Add(resource.original_title);
}
foreach (var alternativeTitle in resource.alternative_titles.titles)
{
if (alternativeTitle.iso_3166_1.ToLower() == langCode)
{
movie.AlternativeTitles.Add(alternativeTitle.title);
altTitles.Add(new AlternativeTitle(alternativeTitle.title, SourceType.TMDB, TmdbId, IsoLanguages.Find(alternativeTitle.iso_3166_1.ToLower())?.Language ?? Language.English));
}
else if (alternativeTitle.iso_3166_1.ToLower() == "us")
{
movie.AlternativeTitles.Add(alternativeTitle.title);
altTitles.Add(new AlternativeTitle(alternativeTitle.title, SourceType.TMDB, TmdbId, Language.English));
}
}
@@ -321,6 +332,8 @@ namespace NzbDrone.Core.MetadataSource.SkyHook
}
}
movie.AlternativeTitles.AddRange(altTitles);
return movie;
}

View File

@@ -0,0 +1,77 @@
using System;
using Marr.Data;
using NzbDrone.Core.Datastore;
using NzbDrone.Core.Parser;
using NzbDrone.Core.Tv;
namespace NzbDrone.Core.Movies.AlternativeTitles
{
public class AlternativeTitle : ModelBase
{
public SourceType SourceType { get; set; }
public int MovieId { get; set; }
public string Title { get; set; }
public string CleanTitle { get; set; }
public int SourceId { get; set; }
public int Votes { get; set; }
public int VoteCount { get; set; }
public Language Language { get; set; }
public LazyLoaded<Movie> Movie { get; set; }
public AlternativeTitle()
{
}
public AlternativeTitle(string title, SourceType sourceType = SourceType.TMDB, int sourceId = 0, Language language = Language.English)
{
Title = title;
CleanTitle = title.CleanSeriesTitle();
SourceType = sourceType;
SourceId = sourceId;
Language = language;
}
public bool IsTrusted(int minVotes = 3)
{
switch (SourceType)
{
case SourceType.TMDB:
return Votes >= minVotes;
default:
return true;
}
}
public override bool Equals(object obj)
{
var item = obj as AlternativeTitle;
if (item == null)
{
return false;
}
return item.CleanTitle == this.CleanTitle;
}
public override String ToString()
{
return Title;
}
}
public enum SourceType
{
TMDB = 0,
Mappings = 1,
User = 2,
Indexer = 3
}
public class AlternativeYear
{
public int Year { get; set; }
public int SourceId { get; set; }
}
}

View File

@@ -0,0 +1,35 @@
using System.Collections.Generic;
using System.Linq;
using NzbDrone.Common.Extensions;
using NzbDrone.Core.Datastore;
using NzbDrone.Core.Messaging.Events;
namespace NzbDrone.Core.Movies.AlternativeTitles
{
public interface IAlternativeTitleRepository : IBasicRepository<AlternativeTitle>
{
AlternativeTitle FindBySourceId(int sourceId);
List<AlternativeTitle> FindBySourceIds(List<int> sourceIds);
}
public class AlternativeTitleRepository : BasicRepository<AlternativeTitle>, IAlternativeTitleRepository
{
protected IMainDatabase _database;
public AlternativeTitleRepository(IMainDatabase database, IEventAggregator eventAggregator)
: base(database, eventAggregator)
{
_database = database;
}
public AlternativeTitle FindBySourceId(int sourceId)
{
return Query.Where(t => t.SourceId == sourceId).FirstOrDefault();
}
public List<AlternativeTitle> FindBySourceIds(List<int> sourceIds)
{
return Query.Where(t => t.SourceId.In(sourceIds)).ToList();
}
}
}

View File

@@ -0,0 +1,83 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using NLog;
using NzbDrone.Common.EnsureThat;
using NzbDrone.Common.Extensions;
using NzbDrone.Core.DataAugmentation.Scene;
using NzbDrone.Core.Messaging.Events;
using NzbDrone.Core.Organizer;
using NzbDrone.Core.Tv;
using NzbDrone.Core.Tv.Events;
using NzbDrone.Core.MediaFiles;
using NzbDrone.Core.MediaFiles.Events;
using NzbDrone.Core.Datastore;
using NzbDrone.Core.Configuration;
namespace NzbDrone.Core.Movies.AlternativeTitles
{
public interface IAlternativeTitleService
{
List<AlternativeTitle> GetAllTitlesForMovie(Movie movie);
AlternativeTitle AddAltTitle(AlternativeTitle title, Movie movie);
List<AlternativeTitle> AddAltTitles(List<AlternativeTitle> titles, Movie movie);
AlternativeTitle GetById(int id);
void DeleteNotEnoughVotes(List<AlternativeTitle> mappingsTitles);
}
public class AlternativeTitleService : IAlternativeTitleService
{
private readonly IAlternativeTitleRepository _titleRepo;
private readonly IConfigService _configService;
private readonly IEventAggregator _eventAggregator;
private readonly Logger _logger;
public AlternativeTitleService(IAlternativeTitleRepository titleRepo,
IEventAggregator eventAggregator,
IConfigService configService,
Logger logger)
{
_titleRepo = titleRepo;
_eventAggregator = eventAggregator;
_configService = configService;
_logger = logger;
}
public List<AlternativeTitle> GetAllTitlesForMovie(Movie movie)
{
return _titleRepo.All().ToList();
}
public AlternativeTitle AddAltTitle(AlternativeTitle title, Movie movie)
{
title.MovieId = movie.Id;
return _titleRepo.Insert(title);
}
public List<AlternativeTitle> AddAltTitles(List<AlternativeTitle> titles, Movie movie)
{
titles.ForEach(t => t.MovieId = movie.Id);
_titleRepo.InsertMany(titles);
return titles;
}
public AlternativeTitle GetById(int id)
{
return _titleRepo.Get(id);
}
public void RemoveTitle(AlternativeTitle title)
{
_titleRepo.Delete(title);
}
public void DeleteNotEnoughVotes(List<AlternativeTitle> mappingsTitles)
{
var toRemove = mappingsTitles.Where(t => t.SourceType == SourceType.Mappings && t.Votes < 4);
var realT = _titleRepo.FindBySourceIds(toRemove.Select(t => t.SourceId).ToList());
_titleRepo.DeleteMany(realT);
}
}
}

View File

@@ -43,7 +43,7 @@ namespace NzbDrone.Core.NetImport.CouchPotato
foreach (var item in responseData)
{
int tmdbid = item.info.tmdb_id ?? 0;
int tmdbid = item.info?.tmdb_id ?? 0;
// Fix weird error reported by Madmanali93
if (item.type != null && item.releases != null)

View File

@@ -63,7 +63,7 @@ namespace NzbDrone.Core.NetImport
{
var movies = MovieListSearch(listId, onlyEnableAuto);
return movies.Where(x => !_movieService.MovieExists(x)).ToList();
return _movieService.FilterExistingMovies(movies);
}
public List<Movie> MovieListSearch(int listId, bool onlyEnableAuto = false)

View File

@@ -1,4 +1,4 @@
using FluentValidation.Results;
using FluentValidation.Results;
using Growl.Connector;
using Growl.CoreLibrary;
using NzbDrone.Common.Extensions;
@@ -102,7 +102,7 @@ namespace NzbDrone.Core.Notifications.Growl
private void Register(string host, int port, string password)
{
_logger.Debug("Registering Sonarr with Growl host: {0}:{1}", host, port);
_logger.Debug("Registering Radarr with Growl host: {0}:{1}", host, port);
var growlConnector = GetGrowlConnector(host, port, password);

View File

@@ -1,4 +1,4 @@
using System;
using System;
using System.Net;
using FluentValidation.Results;
using NLog;
@@ -49,13 +49,13 @@ namespace NzbDrone.Core.Notifications.MediaBrowser
{
_logger.Debug("Testing connection to MediaBrowser: {0}", settings.Address);
Notify(settings, "Test from Sonarr", "Success! MediaBrowser has been successfully configured!");
Notify(settings, "Test from Radarr", "Success! MediaBrowser has been successfully configured!");
}
catch (RestException ex)
{
if (ex.Response.StatusCode == HttpStatusCode.Unauthorized)
{
return new ValidationFailure("ApiKey", "API Key is incorrect");
return new ValidationFailure("ApiKey", "API key is incorrect");
}
}
catch (Exception ex)

View File

@@ -203,7 +203,7 @@ namespace NzbDrone.Core.Notifications.Plex
if (sections.Empty())
{
return new ValidationFailure("Host", "At least one TV library is required");
return new ValidationFailure("Host", "At least one movie library is required");
}
}
catch(PlexAuthenticationException ex)

View File

@@ -12,6 +12,9 @@ namespace NzbDrone.Core.Notifications.Slack.Payloads
[JsonProperty("icon_emoji")]
public string IconEmoji { get; set; }
[JsonProperty("icon_url")]
public string IconUrl { get; set; }
public List<Attachment> Attachments { get; set; }
}
}

View File

@@ -3,6 +3,8 @@ using System.Collections.Generic;
using FluentValidation.Results;
using NLog;
using NzbDrone.Common.Extensions;
using NzbDrone.Common.Http;
using NzbDrone.Common.Serializer;
using NzbDrone.Core.Notifications.Slack.Payloads;
using NzbDrone.Core.Rest;
using NzbDrone.Core.Tv;
@@ -14,10 +16,12 @@ namespace NzbDrone.Core.Notifications.Slack
{
public class Slack : NotificationBase<SlackSettings>
{
private readonly ISlackProxy _proxy;
private readonly Logger _logger;
public Slack(Logger logger)
public Slack(ISlackProxy proxy, Logger logger)
{
_proxy = proxy;
_logger = logger;
}
@@ -27,86 +31,68 @@ namespace NzbDrone.Core.Notifications.Slack
public override void OnGrab(GrabMessage message)
{
var payload = new SlackPayload
{
IconEmoji = Settings.Icon,
Username = Settings.Username,
Text = $"Grabbed: {message.Message}",
Attachments = new List<Attachment>
{
new Attachment
{
Fallback = message.Message,
Title = message.Movie.Title,
Text = message.Message,
Color = "warning"
}
}
};
var attachments = new List<Attachment>
{
new Attachment
{
Fallback = message.Message,
Title = message.Movie.Title,
Text = message.Message,
Color = "warning"
}
};
var payload = CreatePayload($"Grabbed: {message.Message}", attachments);
NotifySlack(payload);
_proxy.SendPayload(payload, Settings);
}
public override void OnDownload(DownloadMessage message)
{
var payload = new SlackPayload
{
IconEmoji = Settings.Icon,
Username = Settings.Username,
Text = $"Imported: {message.Message}",
Attachments = new List<Attachment>
{
new Attachment
{
Fallback = message.Message,
Title = message.Movie.Title,
Text = message.Message,
Color = "good"
}
}
};
var attachments = new List<Attachment>
{
new Attachment
{
Fallback = message.Message,
Title = message.Movie.Title,
Text = message.Message,
Color = "good"
}
};
var payload = CreatePayload($"Imported: {message.Message}", attachments);
NotifySlack(payload);
_proxy.SendPayload(payload, Settings);
}
public override void OnMovieRename(Movie movie)
{
var payload = new SlackPayload
{
IconEmoji = Settings.Icon,
Username = Settings.Username,
Text = "Renamed",
Attachments = new List<Attachment>
{
new Attachment
{
Title = movie.Title,
}
}
};
var attachments = new List<Attachment>
{
new Attachment
{
Title = movie.Title,
}
};
var payload = CreatePayload("Renamed", attachments);
NotifySlack(payload);
_proxy.SendPayload(payload, Settings);
}
public override void OnRename(Series series)
{
var payload = new SlackPayload
{
IconEmoji = Settings.Icon,
Username = Settings.Username,
Text = "Renamed",
Attachments = new List<Attachment>
{
new Attachment
{
Title = series.Title,
}
}
};
var attachments = new List<Attachment>
{
new Attachment
{
Title = series.Title,
}
};
NotifySlack(payload);
var payload = CreatePayload("Renamed", attachments);
_proxy.SendPayload(payload, Settings);
}
public override ValidationResult Test()
{
var failures = new List<ValidationFailure>();
@@ -121,14 +107,10 @@ namespace NzbDrone.Core.Notifications.Slack
try
{
var message = $"Test message from Radarr posted at {DateTime.Now}";
var payload = new SlackPayload
{
IconEmoji = Settings.Icon,
Username = Settings.Username,
Text = message
};
NotifySlack(payload);
var payload = CreatePayload(message);
_proxy.SendPayload(payload, Settings);
}
catch (SlackExeption ex)
@@ -139,24 +121,31 @@ namespace NzbDrone.Core.Notifications.Slack
return null;
}
private void NotifySlack(SlackPayload payload)
private SlackPayload CreatePayload(string message, List<Attachment> attachments = null)
{
try
var icon = Settings.Icon;
var payload = new SlackPayload
{
var client = RestClientFactory.BuildClient(Settings.WebHookUrl);
var request = new RestRequest(Method.POST)
Username = Settings.Username,
Text = message,
Attachments = attachments
};
if (icon.IsNotNullOrWhiteSpace())
{
// Set the correct icon based on the value
if (icon.StartsWith(":") && icon.EndsWith(":"))
{
RequestFormat = DataFormat.Json,
JsonSerializer = new JsonNetSerializer()
};
request.AddBody(payload);
client.ExecuteAndValidate(request);
}
catch (RestException ex)
{
_logger.Error(ex, "Unable to post payload {0}", payload);
throw new SlackExeption("Unable to post payload", ex);
payload.IconEmoji = icon;
}
else
{
payload.IconUrl = icon;
}
}
return payload;
}
}
}

View File

@@ -0,0 +1,46 @@
using NLog;
using NzbDrone.Common.Http;
using NzbDrone.Common.Serializer;
using NzbDrone.Core.Notifications.Slack.Payloads;
using NzbDrone.Core.Rest;
namespace NzbDrone.Core.Notifications.Slack
{
public interface ISlackProxy
{
void SendPayload(SlackPayload payload, SlackSettings settings);
}
public class SlackProxy : ISlackProxy
{
private readonly IHttpClient _httpClient;
private readonly Logger _logger;
public SlackProxy(IHttpClient httpClient, Logger logger)
{
_httpClient = httpClient;
_logger = logger;
}
public void SendPayload(SlackPayload payload, SlackSettings settings)
{
try
{
var request = new HttpRequestBuilder(settings.WebHookUrl)
.Accept(HttpAccept.Json)
.Build();
request.Method = HttpMethod.POST;
request.Headers.ContentType = "application/json";
request.SetContent(payload.ToJson());
_httpClient.Execute(request);
}
catch (RestException ex)
{
_logger.Error(ex, "Unable to post payload {0}", payload);
throw new SlackExeption("Unable to post payload", ex);
}
}
}
}

View File

@@ -24,7 +24,7 @@ namespace NzbDrone.Core.Notifications.Slack
[FieldDefinition(1, Label = "Username", HelpText = "Choose the username that this integration will post as", Type = FieldType.Textbox)]
public string Username { get; set; }
[FieldDefinition(2, Label = "Icon", HelpText = "Change the icon that is used for messages from this integration", Type = FieldType.Textbox, HelpLink = "http://www.emoji-cheat-sheet.com/")]
[FieldDefinition(2, Label = "Icon", HelpText = "Change the icon that is used for messages from this integration (Emoji or URL)", Type = FieldType.Textbox, HelpLink = "http://www.emoji-cheat-sheet.com/")]
public string Icon { get; set; }
public NzbDroneValidationResult Validate()

View File

@@ -37,10 +37,10 @@ namespace NzbDrone.Core.Notifications.Twitter
AuthorizeNotification = "step1";
}
[FieldDefinition(0, Label = "Consumer Key", HelpText = "Consumer key from a Twitter application", HelpLink = "https://github.com/Sonarr/Sonarr/wiki/Twitter-Notifications")]
[FieldDefinition(0, Label = "Consumer Key", HelpText = "Consumer key from a Twitter application", HelpLink = "https://github.com/Radarr/Radarr/wiki/Twitter-Notifications")]
public string ConsumerKey { get; set; }
[FieldDefinition(1, Label = "Consumer Secret", HelpText = "Consumer secret from a Twitter application", HelpLink = "https://github.com/Sonarr/Sonarr/wiki/Twitter-Notifications")]
[FieldDefinition(1, Label = "Consumer Secret", HelpText = "Consumer secret from a Twitter application", HelpLink = "https://github.com/Radarr/Radarr/wiki/Twitter-Notifications")]
public string ConsumerSecret { get; set; }
[FieldDefinition(2, Label = "Access Token", Advanced = true)]

View File

@@ -15,7 +15,7 @@ namespace NzbDrone.Core.Notifications.Webhook
_service = service;
}
public override string Link => "https://github.com/Sonarr/Sonarr/wiki/Webhook";
public override string Link => "https://github.com/Radarr/Radarr/wiki/Webhook";
public override void OnGrab(GrabMessage message)
{

View File

@@ -125,6 +125,8 @@
<Compile Include="Authentication\UserRepository.cs" />
<Compile Include="Authentication\UserService.cs" />
<Compile Include="Datastore\Migration\123_create_netimport_table.cs" />
<Compile Include="Datastore\Migration\140_add_alternative_titles_table.cs" />
<Compile Include="Housekeeping\Housekeepers\CleanupOrphanedAlternativeTitles.cs" />
<Compile Include="MediaFiles\Events\MovieFileUpdatedEvent.cs" />
<Compile Include="Datastore\Migration\134_add_remux_qualities_for_the_wankers.cs" />
<Compile Include="Datastore\Migration\129_add_parsed_movie_info_to_pending_release.cs" />
@@ -134,6 +136,9 @@
<Compile Include="Datastore\Migration\133_add_minimumavailability.cs" />
<Compile Include="IndexerSearch\CutoffUnmetMoviesSearchCommand.cs" />
<Compile Include="Indexers\HDBits\HDBitsInfo.cs" />
<Compile Include="Movies\AlternativeTitles\AlternativeTitle.cs" />
<Compile Include="Movies\AlternativeTitles\AlternativeTitleRepository.cs" />
<Compile Include="Movies\AlternativeTitles\AlternativeTitleService.cs" />
<Compile Include="NetImport\NetImportListLevels.cs" />
<Compile Include="NetImport\TMDb\TMDbLanguageCodes.cs" />
<Compile Include="NetImport\TMDb\TMDbSettings.cs" />
@@ -966,6 +971,7 @@
<Compile Include="Notifications\Slack\Payloads\SlackPayload.cs" />
<Compile Include="Notifications\Slack\Slack.cs" />
<Compile Include="Notifications\Slack\SlackExeption.cs" />
<Compile Include="Notifications\Slack\SlackProxy.cs" />
<Compile Include="Notifications\Slack\SlackSettings.cs" />
<Compile Include="Notifications\Synology\SynologyException.cs" />
<Compile Include="Notifications\Synology\SynologyIndexer.cs" />

View File

@@ -1,9 +1,13 @@
namespace NzbDrone.Core.Parser
using System.Collections.Generic;
using System.Linq;
namespace NzbDrone.Core.Parser
{
public class IsoLanguage
{
public string TwoLetterCode { get; set; }
public string ThreeLetterCode { get; set; }
public List<string> AltCodes = new List<string>();
public Language Language { get; set; }
public IsoLanguage(string twoLetterCode, string threeLetterCode, Language language)
@@ -13,5 +17,14 @@
Language = language;
}
public IsoLanguage(List<string> twoLetterCodes, string threeLetterCode, Language language)
{
TwoLetterCode = twoLetterCodes.First();
twoLetterCodes.RemoveAt(0);
ThreeLetterCode = threeLetterCode;
Language = language;
AltCodes.AddRange(twoLetterCodes);
}
}
}

View File

@@ -7,7 +7,7 @@ namespace NzbDrone.Core.Parser
{
private static readonly HashSet<IsoLanguage> All = new HashSet<IsoLanguage>
{
new IsoLanguage("en", "eng", Language.English),
new IsoLanguage(new List<string> {"en", "us", "uk"}, "eng", Language.English),
new IsoLanguage("fr", "fra", Language.French),
new IsoLanguage("es", "spa", Language.Spanish),
new IsoLanguage("de", "deu", Language.German),
@@ -28,7 +28,8 @@ namespace NzbDrone.Core.Parser
// new IsoLanguage("nl", "nld", Language.Flemish),
new IsoLanguage("el", "ell", Language.Greek),
new IsoLanguage("ko", "kor", Language.Korean),
new IsoLanguage("hu", "hun", Language.Hungarian)
new IsoLanguage("hu", "hun", Language.Hungarian)//,
//new IsoLanguage("he", "heb", Language.Hebrew)
};
public static IsoLanguage Find(string isoCode)
@@ -36,7 +37,7 @@ namespace NzbDrone.Core.Parser
if (isoCode.Length == 2)
{
//Lookup ISO639-1 code
return All.SingleOrDefault(l => l.TwoLetterCode == isoCode);
return All.SingleOrDefault(l => l.TwoLetterCode == isoCode) ?? All.SingleOrDefault(l => l.AltCodes.Contains(isoCode));
}
else if (isoCode.Length == 3)
{

View File

@@ -24,6 +24,7 @@
Flemish = 19,
Greek = 20,
Korean = 21,
Hungarian = 22
Hungarian = 22,
Hebrew = 23
}
}

View File

@@ -11,7 +11,7 @@ namespace NzbDrone.Core.Parser
{
private static readonly Logger Logger = NzbDroneLogger.GetLogger(typeof(LanguageParser));
private static readonly Regex LanguageRegex = new Regex(@"(?:\W|_)(?<italian>\b(?:ita|italian)\b)|(?<german>german\b|videomann)|(?<flemish>flemish)|(?<greek>greek)|(?<french>(?:\W|_)(?:FR|VOSTFR|VO|VFF|VFQ|TRUEFRENCH)(?:\W|_))|(?<russian>\brus\b)|(?<dutch>nl\W?subs?)|(?<hungarian>\b(?:HUNDUB|HUN)\b)",
private static readonly Regex LanguageRegex = new Regex(@"(?:\W|_)(?<italian>\b(?:ita|italian)\b)|(?<german>german\b|videomann)|(?<flemish>flemish)|(?<greek>greek)|(?<french>(?:\W|_)(?:FR|VOSTFR|VO|VFF|VFQ|TRUEFRENCH)(?:\W|_))|(?<russian>\brus\b)|(?<dutch>nl\W?subs?)|(?<hungarian>\b(?:HUNDUB|HUN)\b)|(?<hebrew>\bHebDub\b)",
RegexOptions.IgnoreCase | RegexOptions.Compiled);
private static readonly Regex SubtitleLanguageRegex = new Regex(".+?[-_. ](?<iso_code>[a-z]{2,3})$", RegexOptions.Compiled | RegexOptions.IgnoreCase);
@@ -77,6 +77,9 @@ namespace NzbDrone.Core.Parser
if (lowerTitle.Contains("hungarian"))
return Language.Hungarian;
if (lowerTitle.Contains("hebrew"))
return Language.Hebrew;
var match = LanguageRegex.Match(title);
if (match.Groups["italian"].Captures.Cast<Capture>().Any())
@@ -103,6 +106,9 @@ namespace NzbDrone.Core.Parser
if (match.Groups["hungarian"].Success)
return Language.Hungarian;
if (match.Groups["hebrew"].Success)
return Language.Hebrew;
return Language.English;
}

View File

@@ -11,7 +11,7 @@ namespace NzbDrone.Core.Parser.Model
public ParsedEpisodeInfo ParsedEpisodeInfo { get; set; } //TODO: Change to ParsedMovieInfo, for now though ParsedEpisodeInfo will do.
public ParsedMovieInfo ParsedMovieInfo { get; set; }
public Movie Movie { get; set; }
public bool DownloadAllowed { get; set; }
public MappingResultType MappingResult { get; set; }
public override string ToString()
{

View File

@@ -278,7 +278,7 @@ namespace NzbDrone.Core.Parser
//Regex to detect whether the title was reversed.
private static readonly Regex ReversedTitleRegex = new Regex(@"[-._ ](p027|p0801|\d{2}E\d{2}S)[-._ ]", RegexOptions.Compiled);
private static readonly Regex NormalizeRegex = new Regex(@"((?:\b|_)(?<!^)(a(?!$)|an|the|and|or|of)(?:\b|_))|\W|_",
private static readonly Regex NormalizeRegex = new Regex(@"((?:\b|_)(?<!^|\W\w\W)(a(?!$|\W\w\W)|an|the|and|or|of)(?:\b|_))|\W|_",
RegexOptions.IgnoreCase | RegexOptions.Compiled);
private static readonly Regex FileExtensionRegex = new Regex(@"\.[a-z0-9]{2,4}$",
@@ -848,7 +848,7 @@ namespace NzbDrone.Core.Parser
private static ParsedMovieInfo ParseMovieMatchCollection(MatchCollection matchCollection)
{
if (!matchCollection[0].Groups["title"].Success)
if (!matchCollection[0].Groups["title"].Success || matchCollection[0].Groups["title"].Value == "(")
{
return null;
}

View File

@@ -9,6 +9,7 @@ using NzbDrone.Core.DataAugmentation.Scene;
using NzbDrone.Core.DecisionEngine;
using NzbDrone.Core.IndexerSearch.Definitions;
using NzbDrone.Core.MediaFiles;
using NzbDrone.Core.Movies.AlternativeTitles;
using NzbDrone.Core.Parser.Model;
using NzbDrone.Core.Parser.RomanNumerals;
using NzbDrone.Core.Tv;
@@ -381,7 +382,7 @@ namespace NzbDrone.Core.Parser
{
var movie = _movieService.FindByImdbId(imdbId);
//Should fix practically all problems, where indexer is shite at adding correct imdbids to movies.
if (movie != null && parsedMovieInfo.Year > 1800 && parsedMovieInfo.Year != movie.Year)
if (movie != null && parsedMovieInfo.Year > 1800 && (parsedMovieInfo.Year != movie.Year && movie.SecondaryYear != parsedMovieInfo.Year))
{
result = new MappingResult { Movie = movie, MappingResultType = MappingResultType.WrongYear};
return false;
@@ -458,9 +459,9 @@ namespace NzbDrone.Core.Parser
possibleTitles.Add(searchCriteria.Movie.CleanTitle);
foreach (string altTitle in searchCriteria.Movie.AlternativeTitles)
foreach (AlternativeTitle altTitle in searchCriteria.Movie.AlternativeTitles)
{
possibleTitles.Add(altTitle.CleanSeriesTitle());
possibleTitles.Add(altTitle.CleanTitle);
}
string cleanTitle = parsedMovieInfo.MovieTitle.CleanSeriesTitle();
@@ -494,7 +495,7 @@ namespace NzbDrone.Core.Parser
if (possibleMovie != null)
{
if (parsedMovieInfo.Year < 1800 || possibleMovie.Year == parsedMovieInfo.Year)
if (parsedMovieInfo.Year < 1800 || possibleMovie.Year == parsedMovieInfo.Year || possibleMovie.SecondaryYear == parsedMovieInfo.Year)
{
result = new MappingResult { Movie = possibleMovie, MappingResultType = MappingResultType.Success };
return true;
@@ -509,7 +510,7 @@ namespace NzbDrone.Core.Parser
cleanTitle.Contains(searchCriteria.Movie.CleanTitle))
{
possibleMovie = searchCriteria.Movie;
if (parsedMovieInfo.Year > 1800 && parsedMovieInfo.Year == possibleMovie.Year)
if (parsedMovieInfo.Year > 1800 && parsedMovieInfo.Year == possibleMovie.Year || possibleMovie.SecondaryYear == parsedMovieInfo.Year)
{
result = new MappingResult {Movie = possibleMovie, MappingResultType = MappingResultType.SuccessLenientMapping};
return true;

View File

@@ -50,7 +50,7 @@ namespace NzbDrone.Core.Parser
private static readonly Regex HardcodedSubsRegex = new Regex(@"\b(?<hcsub>(\w+SUBS?)\b)|(?<hc>(HC))\b", RegexOptions.Compiled | RegexOptions.IgnoreCase | RegexOptions.IgnorePatternWhitespace);
private static readonly Regex RemuxRegex = new Regex(@"\b(?<remux>Remux)\b",
private static readonly Regex RemuxRegex = new Regex(@"\b(?<remux>(BD)?Remux)\b",
RegexOptions.Compiled | RegexOptions.IgnoreCase);
private static readonly Regex ProperRegex = new Regex(@"\b(?<proper>proper|repack|rerip)\b",
@@ -65,7 +65,7 @@ namespace NzbDrone.Core.Parser
private static readonly Regex ResolutionRegex = new Regex(@"\b(?:(?<R480p>480p|640x480|848x480)|(?<R576p>576p)|(?<R720p>720p|1280x720)|(?<R1080p>1080p|1920x1080)|(?<R2160p>2160p))\b",
RegexOptions.Compiled | RegexOptions.IgnoreCase);
private static readonly Regex CodecRegex = new Regex(@"\b(?:(?<x264>x264)|(?<h264>h264)|(?<xvidhd>XvidHD)|(?<xvid>Xvid)|(?<divx>divx))\b",
private static readonly Regex CodecRegex = new Regex(@"\b(?:(?<x264>x264)|(?<h264>h264)|(?<xvidhd>XvidHD)|(?<xvid>X-?vid)|(?<divx>divx))\b",
RegexOptions.Compiled | RegexOptions.IgnoreCase);
private static readonly Regex OtherSourceRegex = new Regex(@"(?<hdtv>HD[-_. ]TV)|(?<sdtv>SD[-_. ]TV)", RegexOptions.Compiled | RegexOptions.IgnoreCase);
@@ -144,7 +144,7 @@ namespace NzbDrone.Core.Parser
if (resolution == Resolution.R480P)
{
result.Quality = Quality.DVD;
result.Quality = Quality.Bluray480p;
return result;
}
@@ -215,6 +215,12 @@ namespace NzbDrone.Core.Parser
if (sourceMatch.Groups["bdrip"].Success ||
sourceMatch.Groups["brrip"].Success)
{
if (codecRegex.Groups["xvid"].Success || codecRegex.Groups["divx"].Success)
{
result.Quality = Quality.DVD;
return result;
}
switch (resolution)
{
case Resolution.R720p:
@@ -230,7 +236,7 @@ namespace NzbDrone.Core.Parser
result.Quality = Quality.Bluray480p;
return result;
default:
result.Quality = Quality.DVD;
result.Quality = Quality.Bluray480p;
return result;
}
}

View File

@@ -22,13 +22,13 @@ namespace NzbDrone.Core.Profiles
public class ProfileService : IProfileService, IHandle<ApplicationStartedEvent>
{
private readonly IProfileRepository _profileRepository;
private readonly ISeriesService _seriesService;
private readonly IMovieService _movieService;
private readonly Logger _logger;
public ProfileService(IProfileRepository profileRepository, ISeriesService seriesService, Logger logger)
public ProfileService(IProfileRepository profileRepository, IMovieService movieService, Logger logger)
{
_profileRepository = profileRepository;
_seriesService = seriesService;
_movieService = movieService;
_logger = logger;
}
@@ -44,7 +44,7 @@ namespace NzbDrone.Core.Profiles
public void Delete(int id)
{
if (_seriesService.GetAllSeries().Any(c => c.ProfileId == id))
if (_movieService.GetAllMovies().Any(c => c.ProfileId == id))
{
throw new ProfileInUseException(id);
}

View File

@@ -1,4 +1,4 @@
using RestSharp;
using RestSharp;
using NzbDrone.Common.EnvironmentInfo;
namespace NzbDrone.Core.Rest
@@ -9,7 +9,7 @@ namespace NzbDrone.Core.Rest
{
var restClient = new RestClient(baseUrl);
restClient.UserAgent = string.Format("Sonarr/{0} (RestSharp/{1}; {2}/{3})",
restClient.UserAgent = string.Format("Radarr/{0} (RestSharp/{1}; {2}/{3})",
BuildInfo.Version,
restClient.GetType().Assembly.GetName().Version,
OsInfo.Os, OsInfo.Version.ToString(2));

View File

@@ -6,6 +6,8 @@ using NzbDrone.Core.Datastore;
using NzbDrone.Core.Profiles;
using NzbDrone.Core.MediaFiles;
using System.IO;
using NzbDrone.Core.Movies;
using NzbDrone.Core.Movies.AlternativeTitles;
namespace NzbDrone.Core.Tv
{
@@ -17,7 +19,7 @@ namespace NzbDrone.Core.Tv
Genres = new List<string>();
Actors = new List<Actor>();
Tags = new HashSet<int>();
AlternativeTitles = new List<string>();
AlternativeTitles = new List<AlternativeTitle>();
}
public int TmdbId { get; set; }
public string ImdbId { get; set; }
@@ -52,7 +54,10 @@ namespace NzbDrone.Core.Tv
public LazyLoaded<MovieFile> MovieFile { get; set; }
public bool HasPreDBEntry { get; set; }
public int MovieFileId { get; set; }
public List<string> AlternativeTitles { get; set; }
//Get Loaded via a Join Query
public List<AlternativeTitle> AlternativeTitles { get; set; }
public int? SecondaryYear { get; set; }
public int SecondaryYearSourceId { get; set; }
public string YouTubeTrailerId{ get; set; }
public string Studio { get; set; }
@@ -109,6 +114,11 @@ namespace NzbDrone.Core.Tv
return DateTime.Now >= MinimumAvailabilityDate.AddDays((double)delay);
}
public DateTime PhysicalReleaseDate()
{
return PhysicalRelease ?? (InCinemas?.AddDays(90) ?? DateTime.MaxValue);
}
public override string ToString()
{
return string.Format("[{0}][{1} ({2})]", ImdbId, Title.NullSafe(), Year.NullSafe());

View File

@@ -6,6 +6,7 @@ using NzbDrone.Core.Messaging.Events;
using NzbDrone.Core.Datastore.Extensions;
using Marr.Data.QGen;
using NzbDrone.Core.MediaFiles;
using NzbDrone.Core.Movies.AlternativeTitles;
using NzbDrone.Core.Parser.RomanNumerals;
using NzbDrone.Core.Qualities;
using CoreParser = NzbDrone.Core.Parser.Parser;
@@ -125,6 +126,8 @@ namespace NzbDrone.Core.Tv
else
{
pagingSpec = base.GetPaged(pagingSpec);
//pagingSpec.Records = GetPagedQuery(Query, pagingSpec).ToList();
//pagingSpec.TotalRecords = GetPagedQuery(Query, pagingSpec).GetRowCount();
}
if (pagingSpec.Records.Count == 0 && pagingSpec.Page != 1)
@@ -136,6 +139,22 @@ namespace NzbDrone.Core.Tv
return pagingSpec;
}
/*protected override SortBuilder<Movie> GetPagedQuery(QueryBuilder<Movie> query, PagingSpec<Movie> pagingSpec)
{
return DataMapper.Query<Movie>().Join<Movie, AlternativeTitle>(JoinType.Left, m => m.AlternativeTitles,
(m, t) => m.Id == t.MovieId).Where(pagingSpec.FilterExpression)
.OrderBy(pagingSpec.OrderByClause(), pagingSpec.ToSortDirection())
.Skip(pagingSpec.PagingOffset())
.Take(pagingSpec.PageSize);
}*/
/*protected override SortBuilder<Movie> GetPagedQuery(QueryBuilder<Movie> query, PagingSpec<Movie> pagingSpec)
{
var newQuery = base.GetPagedQuery(query.Join<Movie, AlternativeTitle>(JoinType.Left, m => m.JoinAlternativeTitles, (movie, title) => title.MovieId == movie.Id), pagingSpec);
System.Console.WriteLine(newQuery.ToString());
return newQuery;
}*/
public SortBuilder<Movie> GetMoviesWithoutFilesQuery(PagingSpec<Movie> pagingSpec)
{
@@ -201,7 +220,7 @@ namespace NzbDrone.Core.Tv
{
foreach (var belowCutoff in profile.QualityIds)
{
clauses.Add(string.Format("([t0].[ProfileId] = {0} AND [t1].[Quality] LIKE '%_quality_: {1},%')", profile.ProfileId, belowCutoff));
clauses.Add(string.Format("([t0].[ProfileId] = {0} AND [t2].[Quality] LIKE '%_quality_: {1},%')", profile.ProfileId, belowCutoff));
}
}
@@ -247,22 +266,39 @@ namespace NzbDrone.Core.Tv
if (result == null)
{
IEnumerable<Movie> movies = All();
/*IEnumerable<Movie> movies = All();
Func<string, string> titleCleaner = title => CoreParser.CleanSeriesTitle(title.ToLower());
Func<IEnumerable<string>, string, bool> altTitleComparer =
Func<IEnumerable<AlternativeTitle>, string, bool> altTitleComparer =
(alternativeTitles, atitle) =>
alternativeTitles.Any(altTitle => titleCleaner(altTitle) == atitle);
alternativeTitles.Any(altTitle => altTitle.CleanTitle == atitle);*/
result = movies.Where(m => altTitleComparer(m.AlternativeTitles, cleanTitle) ||
/*result = movies.Where(m => altTitleComparer(m.AlternativeTitles, cleanTitle) ||
altTitleComparer(m.AlternativeTitles, cleanTitleWithRomanNumbers) ||
altTitleComparer(m.AlternativeTitles, cleanTitleWithArabicNumbers)).FirstWithYear(year);
altTitleComparer(m.AlternativeTitles, cleanTitleWithArabicNumbers)).FirstWithYear(year);*/
//result = Query.Join<Movie, AlternativeTitle>(JoinType.Inner, m => m._newAltTitles,
//(m, t) => m.Id == t.MovieId && (t.CleanTitle == cleanTitle)).FirstWithYear(year);
result = Query.Where<AlternativeTitle>(t =>
t.CleanTitle == cleanTitle || t.CleanTitle == cleanTitleWithArabicNumbers
|| t.CleanTitle == cleanTitleWithRomanNumbers).FirstWithYear(year);
}
}
return result;
/*return year.HasValue
? results?.FirstOrDefault(movie => movie.Year == year.Value)
: results?.FirstOrDefault();*/
: results?.FirstOrDefault();*/
}
protected override QueryBuilder<Movie> AddJoinQueries(QueryBuilder<Movie> baseQuery)
{
baseQuery = base.AddJoinQueries(baseQuery);
baseQuery = baseQuery.Join<Movie, AlternativeTitle>(JoinType.Left, m => m.AlternativeTitles,
(m, t) => m.Id == t.MovieId);
return baseQuery;
}
public Movie FindByTmdbId(int tmdbid)

View File

@@ -1,4 +1,5 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.Linq;
@@ -39,6 +40,7 @@ namespace NzbDrone.Core.Tv
List<Movie> GetAllMovies();
Movie UpdateMovie(Movie movie);
List<Movie> UpdateMovie(List<Movie> movie);
List<Movie> FilterExistingMovies(List<Movie> movies);
bool MoviePathExists(string folder);
void RemoveAddOptions(Movie movie);
List<Movie> MoviesWithFiles(int movieId);
@@ -357,7 +359,10 @@ namespace NzbDrone.Core.Tv
public void Handle(MovieFileAddedEvent message)
{
_movieRepository.SetFileId(message.MovieFile.Id, message.MovieFile.Movie.Value.Id);
var movie = message.MovieFile.Movie.Value;
movie.MovieFileId = message.MovieFile.Id;
_movieRepository.Update(movie);
//_movieRepository.SetFileId(message.MovieFile.Id, message.MovieFile.Movie.Value.Id);
_logger.Info("Linking [{0}] > [{1}]", message.MovieFile.RelativePath, message.MovieFile.Movie.Value);
}
@@ -444,5 +449,21 @@ namespace NzbDrone.Core.Tv
return false;
}
public List<Movie> FilterExistingMovies(List<Movie> movies)
{
var allMovies = GetAllMovies();
var withTmdbid = movies.Where(m => m.TmdbId != 0).ToList();
var withoutTmdbid = movies.Where(m => m.TmdbId == 0).ToList();
var withImdbid = withoutTmdbid.Where(m => m.ImdbId.IsNotNullOrWhiteSpace());
var rest = withoutTmdbid.Where(m => m.ImdbId.IsNullOrWhiteSpace());
var ret = withTmdbid.ExceptBy(m => m.TmdbId, allMovies, m => m.TmdbId, EqualityComparer<int>.Default)
.Union(withImdbid.ExceptBy(m => m.ImdbId, allMovies, m => m.ImdbId, EqualityComparer<string>.Default))
.Union(rest.ExceptBy(m => m.Title.CleanSeriesTitle(), allMovies, m => m.CleanTitle, EqualityComparer<string>.Default)).ToList();
return ret;
}
}
}

View File

@@ -16,7 +16,7 @@ namespace NzbDrone.Core
{
public static Movie FirstWithYear(this SortBuilder<Movie> query, int? year)
{
return year.HasValue ? query.FirstOrDefault(movie => movie.Year == year) : query.FirstOrDefault();
return year.HasValue ? query.FirstOrDefault(movie => movie.Year == year || movie.SecondaryYear == year) : query.FirstOrDefault();
}
}
@@ -24,7 +24,7 @@ namespace NzbDrone.Core
{
public static Movie FirstWithYear(this IEnumerable<Movie> query, int? year)
{
return year.HasValue ? query.FirstOrDefault(movie => movie.Year == year) : query.FirstOrDefault();
return year.HasValue ? query.FirstOrDefault(movie => movie.Year == year || movie.SecondaryYear == year) : query.FirstOrDefault();
}
}
}

View File

@@ -14,6 +14,8 @@ using NzbDrone.Core.MetadataSource;
using NzbDrone.Core.Tv.Commands;
using NzbDrone.Core.Tv.Events;
using NzbDrone.Core.MediaFiles.Commands;
using NzbDrone.Core.MetadataSource.RadarrAPI;
using NzbDrone.Core.Movies.AlternativeTitles;
namespace NzbDrone.Core.Tv
{
@@ -21,27 +23,34 @@ namespace NzbDrone.Core.Tv
{
private readonly IProvideMovieInfo _movieInfo;
private readonly IMovieService _movieService;
private readonly IAlternativeTitleService _titleService;
private readonly IRefreshEpisodeService _refreshEpisodeService;
private readonly IEventAggregator _eventAggregator;
private readonly IManageCommandQueue _commandQueueManager;
private readonly IDiskScanService _diskScanService;
private readonly ICheckIfMovieShouldBeRefreshed _checkIfMovieShouldBeRefreshed;
private readonly IRadarrAPIClient _apiClient;
private readonly Logger _logger;
public RefreshMovieService(IProvideMovieInfo movieInfo,
IMovieService movieService,
IAlternativeTitleService titleService,
IRefreshEpisodeService refreshEpisodeService,
IEventAggregator eventAggregator,
IDiskScanService diskScanService,
IRadarrAPIClient apiClient,
ICheckIfMovieShouldBeRefreshed checkIfMovieShouldBeRefreshed,
IManageCommandQueue commandQueue,
Logger logger)
{
_movieInfo = movieInfo;
_movieService = movieService;
_titleService = titleService;
_refreshEpisodeService = refreshEpisodeService;
_eventAggregator = eventAggregator;
_commandQueueManager = commandQueue;
_apiClient = apiClient;
_commandQueueManager = commandQueue;
_diskScanService = diskScanService;
_checkIfMovieShouldBeRefreshed = checkIfMovieShouldBeRefreshed;
_logger = logger;
@@ -85,7 +94,7 @@ namespace NzbDrone.Core.Tv
movie.Certification = movieInfo.Certification;
movie.InCinemas = movieInfo.InCinemas;
movie.Website = movieInfo.Website;
movie.AlternativeTitles = movieInfo.AlternativeTitles;
//movie.AlternativeTitles = movieInfo.AlternativeTitles;
movie.Year = movieInfo.Year;
movie.PhysicalRelease = movieInfo.PhysicalRelease;
movie.YouTubeTrailerId = movieInfo.YouTubeTrailerId;
@@ -102,8 +111,61 @@ namespace NzbDrone.Core.Tv
_logger.Warn(e, "Couldn't update movie path for " + movie.Path);
}
movieInfo.AlternativeTitles = movieInfo.AlternativeTitles.Where(t => t.CleanTitle != movie.CleanTitle)
.DistinctBy(t => t.CleanTitle)
.ExceptBy(t => t.CleanTitle, movie.AlternativeTitles, t => t.CleanTitle, EqualityComparer<string>.Default).ToList();
try
{
var mappings = _apiClient.AlternativeTitlesAndYearForMovie(movieInfo.TmdbId);
var mappingsTitles = mappings.Item1;
movie.AlternativeTitles.AddRange(_titleService.AddAltTitles(movieInfo.AlternativeTitles, movie));
_titleService.DeleteNotEnoughVotes(mappingsTitles);
mappingsTitles = mappingsTitles.ExceptBy(t => t.CleanTitle, movie.AlternativeTitles,
t => t.CleanTitle, EqualityComparer<string>.Default).ToList();
mappingsTitles = mappingsTitles.Where(t => t.Votes > 3).ToList();
movie.AlternativeTitles.AddRange(_titleService.AddAltTitles(mappingsTitles, movie));
if (mappings.Item2 != null)
{
movie.SecondaryYear = mappings.Item2.Year;
movie.SecondaryYearSourceId = mappings.Item2.SourceId;
}
else
{
movie.SecondaryYear = null;
movie.SecondaryYearSourceId = 0;
}
}
catch (RadarrAPIException ex)
{
//Not that wild, could just be a 404.
}
catch (Exception ex)
{
_logger.Info(ex, "Unable to communicate with Mappings Server.");
}
_movieService.UpdateMovie(movie);
try
{
var newTitles = movieInfo.AlternativeTitles.Except(movie.AlternativeTitles);
//_titleService.AddAltTitles(newTitles.ToList(), movie);
}
catch (Exception e)
{
_logger.Debug(e, "Failed adding alternative titles.");
throw;
}
_logger.Debug("Finished movie refresh for {0}", movie.Title);
_eventAggregator.PublishEvent(new MovieUpdatedEvent(movie));
}

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