Changed: Alternative Titles were reworked greatly. This should speed up RSS Sync massively, especially for large libraries (up to 4x).
parent
8927e7c2c6
commit
cfcb66fba1
@ -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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -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())
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,22 @@
|
|||||||
|
var NzbDroneCell = require('../../Cells/NzbDroneCell');
|
||||||
|
|
||||||
|
module.exports = NzbDroneCell.extend({
|
||||||
|
className : 'language-cell',
|
||||||
|
|
||||||
|
render : function() {
|
||||||
|
this.$el.empty();
|
||||||
|
|
||||||
|
var language = this.model.get("language");
|
||||||
|
|
||||||
|
this.$el.html(this.toTitleCase(language));
|
||||||
|
|
||||||
|
return this;
|
||||||
|
},
|
||||||
|
|
||||||
|
toTitleCase : function(str)
|
||||||
|
{
|
||||||
|
return str.replace(/\w\S*/g, function(txt){return txt.charAt(0).toUpperCase() + txt.substr(1).toLowerCase();});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
});
|
@ -0,0 +1,5 @@
|
|||||||
|
var Marionette = require('marionette');
|
||||||
|
|
||||||
|
module.exports = Marionette.ItemView.extend({
|
||||||
|
template : 'Movies/Titles/NoTitlesViewTemplate'
|
||||||
|
});
|
@ -0,0 +1,3 @@
|
|||||||
|
<p class="text-warning">
|
||||||
|
No alternative titles for this movie.
|
||||||
|
</p>
|
@ -0,0 +1,42 @@
|
|||||||
|
var NzbDroneCell = require('../../Cells/NzbDroneCell');
|
||||||
|
|
||||||
|
module.exports = NzbDroneCell.extend({
|
||||||
|
className : 'title-source-cell',
|
||||||
|
|
||||||
|
render : function() {
|
||||||
|
this.$el.empty();
|
||||||
|
|
||||||
|
var link = undefined;
|
||||||
|
var sourceTitle = this.model.get("sourceType");
|
||||||
|
var sourceId = this.model.get("sourceId");
|
||||||
|
|
||||||
|
switch (sourceTitle) {
|
||||||
|
case "tmdb":
|
||||||
|
sourceTitle = "TMDB";
|
||||||
|
link = "https://themoviedb.org/movie/" + sourceId;
|
||||||
|
break;
|
||||||
|
case "mappings":
|
||||||
|
sourceTitle = "Radarr Mappings";
|
||||||
|
link = "https://mappings.radarr.video/mapping/" + sourceId;
|
||||||
|
break;
|
||||||
|
case "user":
|
||||||
|
sourceTitle = "Force Download";
|
||||||
|
break;
|
||||||
|
case "indexer":
|
||||||
|
sourceTitle = "Indexer";
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
var a = "{0}";
|
||||||
|
|
||||||
|
if (link) {
|
||||||
|
a = "<a href='"+link+"' target='_blank'>{0}</a>"
|
||||||
|
}
|
||||||
|
|
||||||
|
this.$el.html(a.format(sourceTitle));
|
||||||
|
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
});
|
@ -0,0 +1,6 @@
|
|||||||
|
var TemplatedCell = require('../../Cells/TemplatedCell');
|
||||||
|
|
||||||
|
module.exports = TemplatedCell.extend({
|
||||||
|
className : 'series-title-cell',
|
||||||
|
template : 'Movies/Titles/TitleTemplate'
|
||||||
|
});
|
@ -0,0 +1,3 @@
|
|||||||
|
var Backbone = require('backbone');
|
||||||
|
|
||||||
|
module.exports = Backbone.Model.extend({});
|
@ -0,0 +1 @@
|
|||||||
|
{{this}}
|
@ -0,0 +1,30 @@
|
|||||||
|
var PagableCollection = require('backbone.pageable');
|
||||||
|
var TitleModel = require('./TitleModel');
|
||||||
|
var AsSortedCollection = require('../../Mixins/AsSortedCollection');
|
||||||
|
|
||||||
|
var Collection = PagableCollection.extend({
|
||||||
|
url : window.NzbDrone.ApiRoot + "/aka",
|
||||||
|
model : TitleModel,
|
||||||
|
|
||||||
|
state : {
|
||||||
|
pageSize : 2000,
|
||||||
|
sortKey : 'title',
|
||||||
|
order : -1
|
||||||
|
},
|
||||||
|
|
||||||
|
mode : 'client',
|
||||||
|
|
||||||
|
sortMappings : {
|
||||||
|
"source" : {
|
||||||
|
sortKey : "sourceType"
|
||||||
|
},
|
||||||
|
"language" : {
|
||||||
|
sortKey : "language"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
Collection = AsSortedCollection.call(Collection);
|
||||||
|
|
||||||
|
module.exports = Collection;
|
@ -0,0 +1,117 @@
|
|||||||
|
var vent = require('vent');
|
||||||
|
var Marionette = require('marionette');
|
||||||
|
var Backgrid = require('backgrid');
|
||||||
|
//var ButtonsView = require('./ButtonsView');
|
||||||
|
//var ManualSearchLayout = require('./ManualLayout');
|
||||||
|
var TitlesCollection = require('./TitlesCollection');
|
||||||
|
var CommandController = require('../../Commands/CommandController');
|
||||||
|
var LoadingView = require('../../Shared/LoadingView');
|
||||||
|
var NoResultsView = require('./NoTitlesView');
|
||||||
|
var TitleModel = require("./TitleModel");
|
||||||
|
var TitleCell = require("./TitleCell");
|
||||||
|
var SourceCell = require("./SourceCell");
|
||||||
|
var LanguageCell = require("./LanguageCell");
|
||||||
|
|
||||||
|
module.exports = Marionette.Layout.extend({
|
||||||
|
template : 'Movies/Titles/TitlesLayoutTemplate',
|
||||||
|
|
||||||
|
regions : {
|
||||||
|
main : '#movie-titles-region',
|
||||||
|
grid : "#movie-titles-grid"
|
||||||
|
},
|
||||||
|
|
||||||
|
events : {
|
||||||
|
'click .x-search-auto' : '_searchAuto',
|
||||||
|
'click .x-search-manual' : '_searchManual',
|
||||||
|
'click .x-search-back' : '_showButtons'
|
||||||
|
},
|
||||||
|
|
||||||
|
columns : [
|
||||||
|
{
|
||||||
|
name : 'title',
|
||||||
|
label : 'Title',
|
||||||
|
cell : Backgrid.StringCell
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name : "this",
|
||||||
|
label : "Source",
|
||||||
|
cell : SourceCell,
|
||||||
|
sortKey : "sourceType",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name : "this",
|
||||||
|
label : "Language",
|
||||||
|
cell : LanguageCell
|
||||||
|
}
|
||||||
|
],
|
||||||
|
|
||||||
|
|
||||||
|
initialize : function(movie) {
|
||||||
|
this.titlesCollection = new TitlesCollection();
|
||||||
|
var titles = movie.model.get("alternativeTitles");
|
||||||
|
this.movie = movie;
|
||||||
|
this.titlesCollection.add(titles);
|
||||||
|
//this.listenTo(this.releaseCollection, 'sync', this._showSearchResults);
|
||||||
|
this.listenTo(this.model, 'change', function(model, options) {
|
||||||
|
if (options && options.changeSource === 'signalr') {
|
||||||
|
this._refresh(model);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
//vent.on(vent.Commands.MovieFileEdited, this._showGrid, this);
|
||||||
|
},
|
||||||
|
|
||||||
|
_refresh : function(model) {
|
||||||
|
this.titlesCollection = new TitlesCollection();
|
||||||
|
var file = model.get("alternativeTitles");
|
||||||
|
this.titlesCollection.add(file);
|
||||||
|
|
||||||
|
|
||||||
|
this.onShow();
|
||||||
|
},
|
||||||
|
|
||||||
|
_refreshClose : function(options) {
|
||||||
|
this.titlesCollection = new TitlesCollection();
|
||||||
|
var file = this.movie.model.get("alternativeTitles");
|
||||||
|
this.titlesCollection.add(file);
|
||||||
|
this._showGrid();
|
||||||
|
},
|
||||||
|
|
||||||
|
onShow : function() {
|
||||||
|
this.grid.show(new Backgrid.Grid({
|
||||||
|
row : Backgrid.Row,
|
||||||
|
columns : this.columns,
|
||||||
|
collection : this.titlesCollection,
|
||||||
|
className : 'table table-hover'
|
||||||
|
}));
|
||||||
|
},
|
||||||
|
|
||||||
|
_showGrid : function() {
|
||||||
|
this.regionManager.get('grid').show(new Backgrid.Grid({
|
||||||
|
row : Backgrid.Row,
|
||||||
|
columns : this.columns,
|
||||||
|
collection : this.titlesCollection,
|
||||||
|
className : 'table table-hover'
|
||||||
|
}));
|
||||||
|
},
|
||||||
|
|
||||||
|
_showMainView : function() {
|
||||||
|
this.main.show(this.mainView);
|
||||||
|
},
|
||||||
|
|
||||||
|
_showButtons : function() {
|
||||||
|
this._showMainView();
|
||||||
|
},
|
||||||
|
|
||||||
|
_showSearchResults : function() {
|
||||||
|
if (this.releaseCollection.length === 0) {
|
||||||
|
this.mainView = new NoResultsView();
|
||||||
|
}
|
||||||
|
|
||||||
|
else {
|
||||||
|
//this.mainView = new ManualSearchLayout({ collection : this.releaseCollection });
|
||||||
|
}
|
||||||
|
|
||||||
|
this._showMainView();
|
||||||
|
}
|
||||||
|
});
|
@ -0,0 +1,3 @@
|
|||||||
|
<div id="movie-titles-region">
|
||||||
|
<div id="movie-titles-grid" class="table-responsive"></div>
|
||||||
|
</div>
|
@ -0,0 +1,6 @@
|
|||||||
|
var Backbone = require('backbone');
|
||||||
|
var _ = require('underscore');
|
||||||
|
|
||||||
|
module.exports = Backbone.Model.extend({
|
||||||
|
urlRoot : window.NzbDrone.ApiRoot + '/alttitle',
|
||||||
|
});
|
@ -0,0 +1,6 @@
|
|||||||
|
var Backbone = require('backbone');
|
||||||
|
var _ = require('underscore');
|
||||||
|
|
||||||
|
module.exports = Backbone.Model.extend({
|
||||||
|
urlRoot : window.NzbDrone.ApiRoot + '/altyear',
|
||||||
|
});
|
@ -0,0 +1,81 @@
|
|||||||
|
var _ = require('underscore');
|
||||||
|
var $ = require('jquery');
|
||||||
|
var vent = require('vent');
|
||||||
|
var AppLayout = require('../AppLayout');
|
||||||
|
var Marionette = require('marionette');
|
||||||
|
var Config = require('../Config');
|
||||||
|
var LanguageCollection = require('../Settings/Profile/Language/LanguageCollection');
|
||||||
|
var AltTitleModel = require("./AlternativeTitleModel");
|
||||||
|
var AltYearModel = require("./AlternativeYearModel");
|
||||||
|
var Messenger = require('../Shared/Messenger');
|
||||||
|
require('../Form/FormBuilder');
|
||||||
|
require('bootstrap');
|
||||||
|
|
||||||
|
module.exports = Marionette.ItemView.extend({
|
||||||
|
template : 'Release/ForceDownloadViewTemplate',
|
||||||
|
|
||||||
|
events : {
|
||||||
|
'click .x-download' : '_forceDownload',
|
||||||
|
},
|
||||||
|
|
||||||
|
ui : {
|
||||||
|
titleMapping : "#title-mapping",
|
||||||
|
yearMapping : "#year-mapping",
|
||||||
|
language : "#language-selection",
|
||||||
|
indicator : ".x-indicator",
|
||||||
|
},
|
||||||
|
|
||||||
|
initialize : function(options) {
|
||||||
|
this.release = options.release;
|
||||||
|
this.templateHelpers = {};
|
||||||
|
|
||||||
|
this._configureTemplateHelpers();
|
||||||
|
},
|
||||||
|
|
||||||
|
onShow : function() {
|
||||||
|
if (this.release.get("mappingResult") == "wrongYear") {
|
||||||
|
this.ui.titleMapping.hide();
|
||||||
|
} else {
|
||||||
|
this.ui.yearMapping.hide();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
_configureTemplateHelpers : function() {
|
||||||
|
this.templateHelpers.release = this.release.toJSON();
|
||||||
|
this.templateHelpers.languages = LanguageCollection.toJSON()
|
||||||
|
},
|
||||||
|
|
||||||
|
_forceDownload : function() {
|
||||||
|
this.ui.indicator.show();
|
||||||
|
var self = this;
|
||||||
|
|
||||||
|
if (this.release.get("mappingResult") == "wrongYear") {
|
||||||
|
var altYear = new AltYearModel({
|
||||||
|
movieId : this.release.get("suspectedMovieId"),
|
||||||
|
year : this.release.get("year")
|
||||||
|
});
|
||||||
|
this.savePromise = altYear.save();
|
||||||
|
} else {
|
||||||
|
var altTitle = new AltTitleModel({
|
||||||
|
movieId : this.release.get("suspectedMovieId"),
|
||||||
|
title : this.release.get("movieTitle"),
|
||||||
|
language : this.ui.language.val(),
|
||||||
|
});
|
||||||
|
|
||||||
|
this.savePromise = altTitle.save();
|
||||||
|
}
|
||||||
|
|
||||||
|
this.savePromise.always(function(){
|
||||||
|
self.ui.indicator.hide();
|
||||||
|
});
|
||||||
|
|
||||||
|
this.savePromise.success(function(){
|
||||||
|
self.release.save(null, {
|
||||||
|
success : function() {
|
||||||
|
self.release.set('queued', true);
|
||||||
|
vent.trigger(vent.Commands.CloseModalCommand);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
},
|
||||||
|
});
|
@ -0,0 +1,44 @@
|
|||||||
|
<div class="modal-content">
|
||||||
|
<div class="modal-header">
|
||||||
|
<button type="button" class="close" aria-hidden="true" data-dismiss="modal">×</button>
|
||||||
|
<h3>Force Download</h3>
|
||||||
|
</div>
|
||||||
|
<div class="modal-body indexer-modal">
|
||||||
|
<div id="title-mapping">
|
||||||
|
<p>The title "{{release.movieTitle}}" could not be found amongst the alternative titles of the movie. This could lead to problems when Radarr wants to import your movie.
|
||||||
|
If you click force download below, the title will be added to the alternative titles using the language selected below.</p>
|
||||||
|
<div class="form-horizontal">
|
||||||
|
<div class="form-group">
|
||||||
|
<label class="col-sm-3 control-label">Language</label>
|
||||||
|
|
||||||
|
<div class="col-sm-5">
|
||||||
|
<select id="language-selection" class="form-control" name="language">
|
||||||
|
{{#each languages}}
|
||||||
|
{{#unless_eq nameLower compare="unknown"}}
|
||||||
|
<option value="{{nameLower}}" {{#if_eq nameLower compare="english"}} selected {{/if_eq}}>{{name}}</option>
|
||||||
|
{{/unless_eq}}
|
||||||
|
{{/each}}
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col-sm-1 help-inline">
|
||||||
|
<i class="icon-sonarr-form-info" title="Language of the alternative title."/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
<div id="year-mapping">
|
||||||
|
<p>The year {{release.year}} does not match the expected release year. This could lead to problems when Radarr wants to import your movie.
|
||||||
|
If you click force download below, the year will be added as a secondary year for this movie.</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="modal-footer">
|
||||||
|
<span class="indicator x-indicator"><i class="icon-sonarr-spinner fa-spin"></i></span>
|
||||||
|
<button class="btn" data-dismiss="modal">Cancel</button>
|
||||||
|
|
||||||
|
<div class="btn-group">
|
||||||
|
<button class="btn btn-primary x-download">Force Download</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
@ -1,3 +1,11 @@
|
|||||||
var Backbone = require('backbone');
|
var Backbone = require('backbone');
|
||||||
|
|
||||||
module.exports = Backbone.Model.extend({});
|
module.exports = Backbone.Model.extend({
|
||||||
|
downloadOk : function() {
|
||||||
|
return this.get("mappingResult") == "success" || this.get("mappingResult") == "successLenientMapping";
|
||||||
|
},
|
||||||
|
|
||||||
|
forceDownloadOk : function() {
|
||||||
|
return this.get("mappingResult") == "wrongYear" || this.get("mappingResult") == "wrongTitle";
|
||||||
|
}
|
||||||
|
});
|
Loading…
Reference in new issue