diff --git a/src/NzbDrone.Api/Config/NamingConfigResource.cs b/src/NzbDrone.Api/Config/NamingConfigResource.cs index c37fffaa9..9ca779dac 100644 --- a/src/NzbDrone.Api/Config/NamingConfigResource.cs +++ b/src/NzbDrone.Api/Config/NamingConfigResource.cs @@ -10,5 +10,12 @@ namespace NzbDrone.Api.Config public string StandardEpisodeFormat { get; set; } public string DailyEpisodeFormat { get; set; } public string SeasonFolderFormat { get; set; } + + public bool IncludeSeriesTitle { get; set; } + public bool IncludeEpisodeTitle { get; set; } + public bool IncludeQuality { get; set; } + public bool ReplaceSpaces { get; set; } + public string Separator { get; set; } + public string NumberStyle { get; set; } } } \ No newline at end of file diff --git a/src/NzbDrone.Api/Config/NamingModule.cs b/src/NzbDrone.Api/Config/NamingModule.cs index d72d97483..4daad949e 100644 --- a/src/NzbDrone.Api/Config/NamingModule.cs +++ b/src/NzbDrone.Api/Config/NamingModule.cs @@ -8,6 +8,7 @@ using NzbDrone.Core.Organizer; using Nancy.ModelBinding; using NzbDrone.Api.Mapping; using NzbDrone.Api.Extensions; +using Omu.ValueInjecter; namespace NzbDrone.Api.Config { @@ -16,15 +17,18 @@ namespace NzbDrone.Api.Config private readonly INamingConfigService _namingConfigService; private readonly IFilenameSampleService _filenameSampleService; private readonly IFilenameValidationService _filenameValidationService; + private readonly IBuildFileNames _filenameBuilder; public NamingModule(INamingConfigService namingConfigService, IFilenameSampleService filenameSampleService, - IFilenameValidationService filenameValidationService) + IFilenameValidationService filenameValidationService, + IBuildFileNames filenameBuilder) : base("config/naming") { _namingConfigService = namingConfigService; _filenameSampleService = filenameSampleService; _filenameValidationService = filenameValidationService; + _filenameBuilder = filenameBuilder; GetResourceSingle = GetNamingConfig; GetResourceById = GetNamingConfig; UpdateResource = UpdateNamingConfig; @@ -50,7 +54,12 @@ namespace NzbDrone.Api.Config private NamingConfigResource GetNamingConfig() { - return _namingConfigService.GetConfig().InjectTo(); + var nameSpec = _namingConfigService.GetConfig(); + var resource = nameSpec.InjectTo(); + var basicConfig = _filenameBuilder.GetBasicNamingConfig(nameSpec); + resource.InjectFrom(basicConfig); + + return resource; } private NamingConfigResource GetNamingConfig(int id) diff --git a/src/NzbDrone.Core/NzbDrone.Core.csproj b/src/NzbDrone.Core/NzbDrone.Core.csproj index 315176686..142c9d9e8 100644 --- a/src/NzbDrone.Core/NzbDrone.Core.csproj +++ b/src/NzbDrone.Core/NzbDrone.Core.csproj @@ -324,6 +324,7 @@ + diff --git a/src/NzbDrone.Core/Organizer/BasicNamingConfig.cs b/src/NzbDrone.Core/Organizer/BasicNamingConfig.cs new file mode 100644 index 000000000..62d476701 --- /dev/null +++ b/src/NzbDrone.Core/Organizer/BasicNamingConfig.cs @@ -0,0 +1,17 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace NzbDrone.Core.Organizer +{ + public class BasicNamingConfig + { + public bool IncludeSeriesTitle { get; set; } + public bool IncludeEpisodeTitle { get; set; } + public bool IncludeQuality { get; set; } + public bool ReplaceSpaces { get; set; } + public string Separator { get; set; } + public string NumberStyle { get; set; } + } +} diff --git a/src/NzbDrone.Core/Organizer/FileNameBuilder.cs b/src/NzbDrone.Core/Organizer/FileNameBuilder.cs index 9d10eb608..618f22ea5 100644 --- a/src/NzbDrone.Core/Organizer/FileNameBuilder.cs +++ b/src/NzbDrone.Core/Organizer/FileNameBuilder.cs @@ -16,6 +16,7 @@ namespace NzbDrone.Core.Organizer string BuildFilename(IList episodes, Series series, EpisodeFile episodeFile); string BuildFilename(IList episodes, Series series, EpisodeFile episodeFile, NamingConfig namingConfig); string BuildFilePath(Series series, int seasonNumber, string fileName, string extension); + BasicNamingConfig GetBasicNamingConfig(NamingConfig nameSpec); } public class FileNameBuilder : IBuildFileNames @@ -25,7 +26,7 @@ namespace NzbDrone.Core.Organizer private readonly ICached _patternCache; private readonly Logger _logger; - private static readonly Regex TitleRegex = new Regex(@"(?\{(?:\w+)(?\s|\W|_)\w+\})", + private static readonly Regex TitleRegex = new Regex(@"(?\{(?:\w+)(?\s|\.|-|_)\w+\})", RegexOptions.Compiled | RegexOptions.IgnoreCase); private static readonly Regex EpisodeRegex = new Regex(@"(?\{episode(?:\:0+)?})", @@ -176,6 +177,47 @@ namespace NzbDrone.Core.Organizer return Path.Combine(path, fileName + extension); } + public BasicNamingConfig GetBasicNamingConfig(NamingConfig nameSpec) + { + var episodeFormat = GetEpisodeFormat(nameSpec.StandardEpisodeFormat); + + var basicNamingConfig = new BasicNamingConfig + { + Separator = episodeFormat.Separator, + NumberStyle = episodeFormat.SeasonEpisodePattern + }; + + var titleTokens = TitleRegex.Matches(nameSpec.StandardEpisodeFormat); + + foreach (Match match in titleTokens) + { + var separator = match.Groups["separator"].Value; + var token = match.Groups["token"].Value; + + if (!separator.Equals(" ")) + { + basicNamingConfig.ReplaceSpaces = true; + } + + if (token.StartsWith("{Series", StringComparison.InvariantCultureIgnoreCase)) + { + basicNamingConfig.IncludeSeriesTitle = true; + } + + if (token.StartsWith("{Episode", StringComparison.InvariantCultureIgnoreCase)) + { + basicNamingConfig.IncludeEpisodeTitle = true; + } + + if (token.StartsWith("{Quality", StringComparison.InvariantCultureIgnoreCase)) + { + basicNamingConfig.IncludeQuality = true; + } + } + + return basicNamingConfig; + } + public static string CleanFilename(string name) { string result = name; diff --git a/src/UI/Settings/MediaManagement/Naming/Basic/BasicNamingView.js b/src/UI/Settings/MediaManagement/Naming/Basic/BasicNamingView.js new file mode 100644 index 000000000..616a20483 --- /dev/null +++ b/src/UI/Settings/MediaManagement/Naming/Basic/BasicNamingView.js @@ -0,0 +1,102 @@ +'use strict'; +define( + [ + 'underscore', + 'vent', + 'marionette', + 'Settings/MediaManagement/Naming/NamingSampleModel', + 'Settings/MediaManagement/Naming/Wizard/NamingWizardModel', + 'Mixins/AsModelBoundView' + ], function (_, vent, Marionette, NamingSampleModel, NamingWizardModel, AsModelBoundView) { + + var view = Marionette.ItemView.extend({ + template: 'Settings/MediaManagement/Naming/Basic/BasicNamingViewTemplate', + + ui: { + namingOptions : '.x-naming-options', + singleEpisodeExample : '.x-single-episode-example', + multiEpisodeExample : '.x-multi-episode-example', + dailyEpisodeExample : '.x-daily-episode-example' + }, + + onRender: function () { + this.listenTo(this.model, 'change', this._buildFormat); + this._buildFormat(); + }, + + _updateSamples: function () { + var data = { + renameEpisodes: true, + standardEpisodeFormat: this.standardEpisodeFormat, + dailyEpisodeFormat: this.dailyEpisodeFormat, + multiEpisodeStyle: this.model.get('multiEpisodeStyle') + }; + + this.namingSampleModel.fetch({data: data}); + }, + + _buildFormat: function () { + if (_.has(this.model.changed, 'standardEpisodeFormat') || _.has(this.model.changed, 'dailyEpisodeFormat')) { + return; + } + + var standardEpisodeFormat = ''; + var dailyEpisodeFormat = ''; + + if (this.model.get('includeSeriesTitle')) { + if (this.model.get('replaceSpaces')) { + standardEpisodeFormat += '{Series.Title}'; + dailyEpisodeFormat += '{Series.Title}'; + } + + else { + standardEpisodeFormat += '{Series Title}'; + dailyEpisodeFormat += '{Series Title}'; + } + + standardEpisodeFormat += this.model.get('separator'); + dailyEpisodeFormat += this.model.get('separator'); + } + + standardEpisodeFormat += this.model.get('numberStyle'); + dailyEpisodeFormat += '{Air-Date}'; + + if (this.model.get('includeEpisodeTitle')) { + standardEpisodeFormat += this.model.get('separator'); + dailyEpisodeFormat += this.model.get('separator'); + + if (this.model.get('replaceSpaces')) { + standardEpisodeFormat += '{Episode.Title}'; + dailyEpisodeFormat += '{Episode.Title}'; + } + + else { + standardEpisodeFormat += '{Episode Title}'; + dailyEpisodeFormat += '{Episode Title}'; + } + } + + if (this.model.get('includeQuality')) { + if (this.model.get('replaceSpaces')) { + standardEpisodeFormat += ' {Quality.Title}'; + dailyEpisodeFormat += ' {Quality.Title}'; + } + + else { + standardEpisodeFormat += ' {Quality Title}'; + dailyEpisodeFormat += ' {Quality Title}'; + } + } + + if (this.model.get('replaceSpaces')) { + standardEpisodeFormat = standardEpisodeFormat.replace(/\s/g, '.'); + dailyEpisodeFormat = dailyEpisodeFormat.replace(/\s/g, '.'); + } + + this.model.set('standardEpisodeFormat', standardEpisodeFormat); + this.model.set('dailyEpisodeFormat', dailyEpisodeFormat); + } + }); + + return AsModelBoundView.call(view); + }); diff --git a/src/UI/Settings/MediaManagement/Naming/Basic/BasicNamingViewTemplate.html b/src/UI/Settings/MediaManagement/Naming/Basic/BasicNamingViewTemplate.html new file mode 100644 index 000000000..53cddefa4 --- /dev/null +++ b/src/UI/Settings/MediaManagement/Naming/Basic/BasicNamingViewTemplate.html @@ -0,0 +1,92 @@ +
+ + +
+
+ +
+ + +
+
+ +
+ + +
+
+ +
+ + +
+
+ +
+ + +
+ +
+
+ +
+ + +
+ +
+
diff --git a/src/UI/Settings/MediaManagement/Naming/Model.js b/src/UI/Settings/MediaManagement/Naming/NamingModel.js similarity index 100% rename from src/UI/Settings/MediaManagement/Naming/Model.js rename to src/UI/Settings/MediaManagement/Naming/NamingModel.js diff --git a/src/UI/Settings/MediaManagement/Naming/NamingView.js b/src/UI/Settings/MediaManagement/Naming/NamingView.js index 8592aa87e..7d50f9cf8 100644 --- a/src/UI/Settings/MediaManagement/Naming/NamingView.js +++ b/src/UI/Settings/MediaManagement/Naming/NamingView.js @@ -1,15 +1,16 @@ 'use strict'; define( [ - 'vent', + 'underscore', 'marionette', + 'Config', 'Settings/MediaManagement/Naming/NamingSampleModel', - 'Settings/MediaManagement/Naming/Wizard/NamingWizardView', + 'Settings/MediaManagement/Naming/Basic/BasicNamingView', 'Mixins/AsModelBoundView', 'Mixins/AsValidatedView' - ], function (vent, Marionette, NamingSampleModel, NamingWizardView, AsModelBoundView, AsValidatedView) { + ], function (_, Marionette, Config, NamingSampleModel, BasicNamingView, AsModelBoundView, AsValidatedView) { - var view = Marionette.ItemView.extend({ + var view = Marionette.Layout.extend({ template: 'Settings/MediaManagement/Naming/NamingViewTemplate', ui: { @@ -27,11 +28,17 @@ define( 'click .x-naming-token-helper a' : '_addToken' }, + regions: { + basicNamingRegion: '.x-basic-naming' + }, + onRender: function () { - if (!this.model.has('renameEpisodes')) { + if (!this.model.get('renameEpisodes')) { this.ui.namingOptions.hide(); } + this.basicNamingView = new BasicNamingView({ model: this.model }); + this.basicNamingRegion.show(this.basicNamingView); this.namingSampleModel = new NamingSampleModel(); this.listenTo(this.model, 'change', this._updateSamples); @@ -51,6 +58,10 @@ define( }, _updateSamples: function () { + if (!_.has(this.model.changed, 'standardEpisodeFormat') && !_.has(this.model.changed, 'dailyEpisodeFormat')) { + return; + } + this.namingSampleModel.fetch({ data: this.model.toJSON() }); }, @@ -60,14 +71,6 @@ define( this.ui.dailyEpisodeExample.html(this.namingSampleModel.get('dailyEpisodeExample')); }, - _showWizard: function () { - var modalView = new NamingWizardView(); - vent.trigger(vent.Commands.OpenModalCommand, modalView); - this.listenTo(modalView, modalView.formatsUpdated, this._updateFormats); - - vent.trigger(vent.Commands.ShowNamingWizard, { model: this.model }); - }, - _addToken: function (e) { e.preventDefault(); e.stopPropagation(); @@ -88,12 +91,6 @@ define( this.ui.namingTokenHelper.removeClass('open'); input.focus(); - }, - - _updateFormats: function (options) { - this.model.set('standardEpisodeFormat', options.standardEpisodeFormat); - this.model.set('dailyEpisodeFormat', options.dailyEpisodeFormat); - this.model.set('multiEpisodeStyle', options.multiEpisodeStyle); } }); diff --git a/src/UI/Settings/MediaManagement/Naming/NamingViewTemplate.html b/src/UI/Settings/MediaManagement/Naming/NamingViewTemplate.html index 6eb23c4ef..a8eb890d2 100644 --- a/src/UI/Settings/MediaManagement/Naming/NamingViewTemplate.html +++ b/src/UI/Settings/MediaManagement/Naming/NamingViewTemplate.html @@ -23,17 +23,7 @@
-
- - -
- - - - - -
-
+
@@ -90,7 +80,7 @@
-
+
@@ -108,10 +98,19 @@
- - - - +
+ +
+ + +
+
diff --git a/src/UI/Settings/MediaManagement/Naming/Wizard/NamingWizardModel.js b/src/UI/Settings/MediaManagement/Naming/Wizard/NamingWizardModel.js deleted file mode 100644 index 6d6806538..000000000 --- a/src/UI/Settings/MediaManagement/Naming/Wizard/NamingWizardModel.js +++ /dev/null @@ -1,17 +0,0 @@ -'use strict'; -define( - [ - 'backbone' - ], function (Backbone) { - return Backbone.Model.extend({ - defaults: { - includeSeriesTitle : true, - includeEpisodeTitle: true, - includeQuality : true, - replaceSpaces : false, - separator : ' - ', - numberStyle : 'S{season:00}E{episode:00}', - multiEpisodeStyle : 0 - } - }); - }); diff --git a/src/UI/Settings/MediaManagement/Naming/Wizard/NamingWizardView.js b/src/UI/Settings/MediaManagement/Naming/Wizard/NamingWizardView.js deleted file mode 100644 index eb48dd081..000000000 --- a/src/UI/Settings/MediaManagement/Naming/Wizard/NamingWizardView.js +++ /dev/null @@ -1,127 +0,0 @@ -'use strict'; -define( - [ - 'vent', - 'marionette', - 'Settings/MediaManagement/Naming/NamingSampleModel', - 'Settings/MediaManagement/Naming/Wizard/NamingWizardModel', - 'Mixins/AsModelBoundView' - ], function (vent, Marionette, NamingSampleModel, NamingWizardModel, AsModelBoundView) { - - var view = Marionette.ItemView.extend({ - template: 'Settings/MediaManagement/Naming/Wizard/NamingWizardViewTemplate', - - ui: { - namingOptions : '.x-naming-options', - singleEpisodeExample : '.x-single-episode-example', - multiEpisodeExample : '.x-multi-episode-example', - dailyEpisodeExample : '.x-daily-episode-example' - }, - - events: { - 'click .x-apply': '_applyNaming' - }, - - formatsUpdated: 'formatsUpdated', - - initialize: function () { - this.model = new NamingWizardModel(); - this.namingSampleModel = new NamingSampleModel(); - }, - - onRender: function () { - this.listenTo(this.model, 'change', this._buildFormat); - this.listenTo(this.namingSampleModel, 'sync', this._showSamples); - this._buildFormat(); - }, - - _updateSamples: function () { - var data = { - renameEpisodes: true, - standardEpisodeFormat: this.standardEpisodeFormat, - dailyEpisodeFormat: this.dailyEpisodeFormat, - multiEpisodeStyle: this.model.get('multiEpisodeStyle') - }; - - this.namingSampleModel.fetch({data: data}); - }, - - _showSamples: function () { - this.ui.singleEpisodeExample.html(this.namingSampleModel.get('singleEpisodeExample')); - this.ui.multiEpisodeExample.html(this.namingSampleModel.get('multiEpisodeExample')); - this.ui.dailyEpisodeExample.html(this.namingSampleModel.get('dailyEpisodeExample')); - }, - - _applyNaming: function () { - var options = { - standardEpisodeFormat: this.standardEpisodeFormat, - dailyEpisodeFormat: this.dailyEpisodeFormat, - multiEpisodeStyle: this.model.get('multiEpisodeStyle') - }; - - this.trigger(this.formatsUpdated, options); - - - vent.trigger(vent.Commands.CloseModalCommand); - }, - - _buildFormat: function () { - this.standardEpisodeFormat = ''; - this.dailyEpisodeFormat = ''; - - if (this.model.get('includeSeriesTitle')) { - if (this.model.get('replaceSpaces')) { - this.standardEpisodeFormat += '{Series.Title}'; - this.dailyEpisodeFormat += '{Series.Title}'; - } - - else { - this.standardEpisodeFormat += '{Series Title}'; - this.dailyEpisodeFormat += '{Series Title}'; - } - - this.standardEpisodeFormat += this.model.get('separator'); - this.dailyEpisodeFormat += this.model.get('separator'); - } - - this.standardEpisodeFormat += this.model.get('numberStyle'); - this.dailyEpisodeFormat += '{Air-Date}'; - - if (this.model.get('includeEpisodeTitle')) { - this.standardEpisodeFormat += this.model.get('separator'); - this.dailyEpisodeFormat += this.model.get('separator'); - - if (this.model.get('replaceSpaces')) { - this.standardEpisodeFormat += '{Episode.Title}'; - this.dailyEpisodeFormat += '{Episode.Title}'; - } - - else { - this.standardEpisodeFormat += '{Episode Title}'; - this.dailyEpisodeFormat += '{Episode Title}'; - } - } - - if (this.model.get('includeQuality')) { - if (this.model.get('replaceSpaces')) { - this.standardEpisodeFormat += ' {Quality.Title}'; - this.dailyEpisodeFormat += ' {Quality.Title}'; - } - - else { - this.standardEpisodeFormat += ' {Quality Title}'; - this.dailyEpisodeFormat += ' {Quality Title}'; - } - } - - if (this.model.get('replaceSpaces')) { - this.standardEpisodeFormat = this.standardEpisodeFormat.replace(/\s/g, '.'); - this.dailyEpisodeFormat = this.dailyEpisodeFormat.replace(/\s/g, '.'); - } - - this._updateSamples(); - } - }); - - return AsModelBoundView.call(view); - }); diff --git a/src/UI/Settings/MediaManagement/Naming/Wizard/NamingWizardViewTemplate.html b/src/UI/Settings/MediaManagement/Naming/Wizard/NamingWizardViewTemplate.html deleted file mode 100644 index 484ad6b5a..000000000 --- a/src/UI/Settings/MediaManagement/Naming/Wizard/NamingWizardViewTemplate.html +++ /dev/null @@ -1,141 +0,0 @@ - -