diff --git a/NzbDrone.Api/RootFolders/RootFolderModule.cs b/NzbDrone.Api/RootFolders/RootFolderModule.cs index 3accd37df..f160570ff 100644 --- a/NzbDrone.Api/RootFolders/RootFolderModule.cs +++ b/NzbDrone.Api/RootFolders/RootFolderModule.cs @@ -5,9 +5,9 @@ namespace NzbDrone.Api.RootFolders { public class RootFolderModule : NzbDroneRestModule { - private readonly RootFolderService _rootFolderService; + private readonly IRootFolderService _rootFolderService; - public RootFolderModule(RootFolderService rootFolderService) + public RootFolderModule(IRootFolderService rootFolderService) { _rootFolderService = rootFolderService; diff --git a/NzbDrone.Common/NzbDrone.Common.csproj b/NzbDrone.Common/NzbDrone.Common.csproj index 313cdbc0a..e6d441aa7 100644 --- a/NzbDrone.Common/NzbDrone.Common.csproj +++ b/NzbDrone.Common/NzbDrone.Common.csproj @@ -91,6 +91,7 @@ + diff --git a/NzbDrone.Common/PathEqualityComparer.cs b/NzbDrone.Common/PathEqualityComparer.cs new file mode 100644 index 000000000..c70abb7b7 --- /dev/null +++ b/NzbDrone.Common/PathEqualityComparer.cs @@ -0,0 +1,20 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace NzbDrone.Common +{ + public class PathEqualityComparer : IEqualityComparer + { + public bool Equals(string x, string y) + { + return x.PathEquals(y); + } + + public int GetHashCode(string obj) + { + return obj.CleanFilePath().GetHashCode(); + } + } +} diff --git a/NzbDrone.Common/PathExtensions.cs b/NzbDrone.Common/PathExtensions.cs index 4e7d2251c..15e3b8a08 100644 --- a/NzbDrone.Common/PathExtensions.cs +++ b/NzbDrone.Common/PathExtensions.cs @@ -35,11 +35,12 @@ namespace NzbDrone.Common return info.FullName.TrimEnd('/').Trim('\\', ' '); } - public static bool PathEquals(this string firstPath, string secondPath) { - Ensure.That(() => firstPath).IsValidPath(); - Ensure.That(() => secondPath).IsValidPath(); + if (OsInfo.IsLinux) + { + return String.Equals(firstPath.CleanFilePath(), secondPath.CleanFilePath()); + } return String.Equals(firstPath.CleanFilePath(), secondPath.CleanFilePath(), StringComparison.InvariantCultureIgnoreCase); } diff --git a/NzbDrone.Core/RootFolders/RootFolderService.cs b/NzbDrone.Core/RootFolders/RootFolderService.cs index a27fa01a7..f85a371f6 100644 --- a/NzbDrone.Core/RootFolders/RootFolderService.cs +++ b/NzbDrone.Core/RootFolders/RootFolderService.cs @@ -1,4 +1,5 @@ -using System.Linq; +using System.Diagnostics; +using System.Linq; using System; using System.Collections.Generic; using System.IO; @@ -40,14 +41,14 @@ namespace NzbDrone.Core.RootFolders _configService = configService; } - public virtual List All() + public List All() { var rootFolders = _rootFolderRepository.All().ToList(); return rootFolders; } - public virtual List AllWithUnmappedFolders() + public List AllWithUnmappedFolders() { var rootFolders = _rootFolderRepository.All().ToList(); @@ -63,7 +64,7 @@ namespace NzbDrone.Core.RootFolders return rootFolders; } - public virtual RootFolder Add(RootFolder rootFolder) + public RootFolder Add(RootFolder rootFolder) { var all = All(); @@ -87,12 +88,12 @@ namespace NzbDrone.Core.RootFolders return rootFolder; } - public virtual void Remove(int id) + public void Remove(int id) { _rootFolderRepository.Delete(id); } - public virtual List GetUnmappedFolders(string path) + public List GetUnmappedFolders(string path) { Logger.Debug("Generating list of unmapped folders"); if (String.IsNullOrEmpty(path)) @@ -107,13 +108,13 @@ namespace NzbDrone.Core.RootFolders return results; } - foreach (string seriesFolder in _diskProvider.GetDirectories(path)) + var seriesFolders = _diskProvider.GetDirectories(path).ToList(); + var unmappedFolders = seriesFolders.Except(series.Select(s => s.Path), new PathEqualityComparer()).ToList(); + + foreach (string unmappedFolder in unmappedFolders) { - if (!series.Any(s => s.Path.PathEquals(seriesFolder))) - { - var di = new DirectoryInfo(seriesFolder.Normalize()); - results.Add(new UnmappedFolder { Name = di.Name, Path = di.FullName }); - } + var di = new DirectoryInfo(unmappedFolder.Normalize()); + results.Add(new UnmappedFolder { Name = di.Name, Path = di.FullName }); } if (Path.GetPathRoot(path).Equals(path, StringComparison.InvariantCultureIgnoreCase)) @@ -126,7 +127,7 @@ namespace NzbDrone.Core.RootFolders return results; } - public virtual Dictionary FreeSpaceOnDrives() + public Dictionary FreeSpaceOnDrives() { var freeSpace = new Dictionary(); diff --git a/UI/Cells/Edit/QualityCellEditor.js b/UI/Cells/Edit/QualityCellEditor.js new file mode 100644 index 000000000..a9bb9c856 --- /dev/null +++ b/UI/Cells/Edit/QualityCellEditor.js @@ -0,0 +1,69 @@ +'use strict'; +define( + [ + 'backgrid', + 'Settings/Quality/Profile/QualityProfileSchemaCollection' + ], function (Backgrid, QualityProfileSchemaCollection) { + return Backgrid.CellEditor.extend({ + + className: 'quality-cell-editor', + template : 'Cells/Edit/QualityCellEditorTemplate', + tagName : 'select', + + events: { + 'change': 'save', + 'blur': 'close', + 'keydown': 'close' + }, + + render: function () { + var self = this; + + var qualityProfileSchemaCollection = new QualityProfileSchemaCollection(); + var promise = qualityProfileSchemaCollection.fetch(); + + promise.done(function () { + var templateName = self.template; + self.schema = qualityProfileSchemaCollection.first(); + + var selected = _.find(self.schema.get('available'), { 'id': self.cell.cellValue.get('quality').id }); + selected.selected = true; + + self.templateFunction = Marionette.TemplateCache.get(templateName); + var data = self.schema.toJSON(); + var html = self.templateFunction(data); + self.$el.html(html); + }); + + return this; + }, + + save: function (e) { + var model = this.model; + var column = this.column; + var selected = parseInt(this.$el.val()); + + var quality = _.find(this.schema.get('available'), { 'id': selected }); + + var newQuality = { + proper: false, + quality: quality + }; + + model.set(column.get("name"), newQuality); + model.trigger("backgrid:edited", model, column, new Backgrid.Command(e)); + }, + + close: function (e) { + var model = this.model; + var column = this.column; + var command = new Backgrid.Command(e); + + model.trigger("backgrid:edited", model, column, command); + }, + + _setOptions: function (options) { + this.cell = options.cell + } + }); + }); diff --git a/UI/Cells/Edit/QualityCellEditorTemplate.html b/UI/Cells/Edit/QualityCellEditorTemplate.html new file mode 100644 index 000000000..92d3bea99 --- /dev/null +++ b/UI/Cells/Edit/QualityCellEditorTemplate.html @@ -0,0 +1,7 @@ +{{#each available}} + {{#if selected}} + + {{else}} + + {{/if}} +{{/each}} \ No newline at end of file diff --git a/UI/Cells/NzbDroneCell.js b/UI/Cells/NzbDroneCell.js index 4f23f251a..706eb058e 100644 --- a/UI/Cells/NzbDroneCell.js +++ b/UI/Cells/NzbDroneCell.js @@ -14,6 +14,12 @@ define( this.cellValue = this._getValue(); this.listenTo(this.model, 'change', this._refresh); + + this.listenTo(this.model, "backgrid:edit", function (model, column, cell, editor) { + if (column.get("name") == this.column.get("name")) { + this._startEditing(model, column, cell, editor); + } + }); }, _refresh: function () { diff --git a/UI/Cells/QualityCell.js b/UI/Cells/QualityCell.js index 65d5b28cc..0dba839aa 100644 --- a/UI/Cells/QualityCell.js +++ b/UI/Cells/QualityCell.js @@ -1,12 +1,17 @@ 'use strict'; define( [ - 'Cells/TemplatedCell' - ], function (TemplatedCell) { + 'Cells/TemplatedCell', + 'Cells/Edit/QualityCellEditor' + ], function (TemplatedCell, QualityCellEditor) { return TemplatedCell.extend({ className: 'quality-cell', - template : 'Cells/QualityCellTemplate' + template : 'Cells/QualityCellTemplate', + editor : QualityCellEditor, + _startEditing: function (model, column, cell, editor) { + editor._setOptions({ cell: cell }); + } }); }); diff --git a/UI/Cells/TemplatedCell.js b/UI/Cells/TemplatedCell.js index 1c82ef9d6..1da28d5ed 100644 --- a/UI/Cells/TemplatedCell.js +++ b/UI/Cells/TemplatedCell.js @@ -7,7 +7,6 @@ define( ], function (Marionette, NzbDroneCell) { return NzbDroneCell.extend({ - render: function () { var templateName = this.column.get('template') || this.template; @@ -17,6 +16,7 @@ define( var html = this.templateFunction(data); this.$el.html(html); + this.delegateEvents(); return this; } }); diff --git a/UI/Episode/LayoutTemplate.html b/UI/Episode/LayoutTemplate.html index 7fd09cef6..982110d6a 100644 --- a/UI/Episode/LayoutTemplate.html +++ b/UI/Episode/LayoutTemplate.html @@ -5,11 +5,10 @@

{{#if episodeTitle}} - {{episodeTitle}} + {{title}} - {{EpisodeNumber}} - {{episodeTitle}} {{else}} - {{title}} + {{series.title}} - {{EpisodeNumber}} - {{title}} {{/if}} - - {{EpisodeNumber}}

diff --git a/UI/Episode/Summary/Layout.js b/UI/Episode/Summary/Layout.js index 72f0c08b0..f47d3ae15 100644 --- a/UI/Episode/Summary/Layout.js +++ b/UI/Episode/Summary/Layout.js @@ -34,7 +34,8 @@ define( name : 'quality', label : 'Quality', cell : QualityCell, - sortable: false + sortable: false, + editable: true } ], diff --git a/UI/Episode/Summary/LayoutTemplate.html b/UI/Episode/Summary/LayoutTemplate.html index fe5ad02c9..1263ad98e 100644 --- a/UI/Episode/Summary/LayoutTemplate.html +++ b/UI/Episode/Summary/LayoutTemplate.html @@ -1,4 +1,14 @@ -
+
+ {{#with series}} + {{qualityProfile qualityProfileId}} + {{network}} + {{/with}} + {{StartTime airDateUtc}} + {{NextAiring airDateUtc}} +
+ +
{{overview}}
+
diff --git a/UI/Series/series.less b/UI/Series/series.less index 7dda48f7a..f405ea210 100644 --- a/UI/Series/series.less +++ b/UI/Series/series.less @@ -197,13 +197,15 @@ .episode-detail-modal { - .episode-overview { + .episode-info { + margin-bottom: 10px; + } + .episode-overview { font-style : italic; } .episode-file-info { - margin-top : 30px; font-size : 12px; } diff --git a/UI/Settings/Quality/Profile/QualityProfileCollectionView.js b/UI/Settings/Quality/Profile/QualityProfileCollectionView.js index 2e504d1b4..7ea2e822c 100644 --- a/UI/Settings/Quality/Profile/QualityProfileCollectionView.js +++ b/UI/Settings/Quality/Profile/QualityProfileCollectionView.js @@ -4,8 +4,8 @@ define(['app', 'marionette', 'Settings/Quality/Profile/QualityProfileView', 'Settings/Quality/Profile/EditQualityProfileView', - 'Settings/Quality/Profile/QualityProfileSchemaCollection'], - function (App, Marionette, QualityProfileView, EditProfileView, ProfileCollection) { + 'Settings/Quality/Profile/QualityProfileSchemaCollection' +], function (App, Marionette, QualityProfileView, EditProfileView, ProfileCollection) { return Marionette.CompositeView.extend({ itemView : QualityProfileView, @@ -24,7 +24,6 @@ define(['app', collectionView.ui.addCard.parent('li').before(itemView.el); }, - _addProfile: function () { var self = this; var schemaCollection = new ProfileCollection();