First Pass At AlbumStudio Page, Also Fixes Monitoring (#16)

* First Pass At AlbumPass Page, Also Fixes Monitoring

First Pass At AlbumPass Page, Also Fixes Monitoring

* Catchy New Name, Fix Crash when visit Wanted Page

Catchy New Name, Fix Crash when visit Wanted Page

* Rename API to match

Rename API to match

* Get Bulk Monitoring Working on Album Studio Page

Get Bulk Monitoring Working on Album Studio Page

* Fix Wanted Query

Fix Wanted Query

* Codacy

Codacy

* Fix Cutoff Link To AlbumStudio

Fix Cutoff Link To AlbumStudio

* Add Header, Move Artist Monitor, Change Artist Column Heading

Add Header, Move Artist Monitor, Change Artist Column Heading
pull/20/head
Qstick 8 years ago committed by Joseph Milazzo
parent bb196e19e1
commit dcde579a43

@ -0,0 +1,31 @@
using Nancy;
using NzbDrone.Api.Extensions;
using NzbDrone.Core.Music;
namespace NzbDrone.Api.AlbumPass
{
public class AlbumStudioModule : NzbDroneApiModule
{
private readonly IAlbumMonitoredService _albumMonitoredService;
public AlbumStudioModule(IAlbumMonitoredService albumMonitoredService)
: base("/albumstudio")
{
_albumMonitoredService = albumMonitoredService;
Post["/"] = artist => UpdateAll();
}
private Response UpdateAll()
{
//Read from request
var request = Request.Body.FromJson<AlbumStudioResource>();
foreach (var s in request.Artist)
{
_albumMonitoredService.SetAlbumMonitoredStatus(s, request.MonitoringOptions);
}
return "ok".AsResponse(HttpStatusCode.Accepted);
}
}
}

@ -0,0 +1,11 @@
using System.Collections.Generic;
using NzbDrone.Core.Music;
namespace NzbDrone.Api.AlbumPass
{
public class AlbumStudioResource
{
public List<Core.Music.Artist> Artist { get; set; }
public MonitoringOptions MonitoringOptions { get; set; }
}
}

@ -1,6 +1,7 @@
using System.Collections.Generic;
using NzbDrone.Api.REST;
using NzbDrone.Core.Music;
using NzbDrone.Core.ArtistStats;
using NzbDrone.Core.DecisionEngine;
using NzbDrone.SignalR;
@ -9,10 +10,11 @@ namespace NzbDrone.Api.Albums
public class AlbumModule : AlbumModuleWithSignalR
{
public AlbumModule(IArtistService artistService,
IArtistStatisticsService artistStatisticsService,
IAlbumService albumService,
IQualityUpgradableSpecification qualityUpgradableSpecification,
IBroadcastSignalRMessage signalRBroadcaster)
: base(albumService, artistService, qualityUpgradableSpecification, signalRBroadcaster)
: base(albumService, artistStatisticsService, artistService, qualityUpgradableSpecification, signalRBroadcaster)
{
GetResourceAll = GetAlbums;
UpdateResource = SetMonitored;

@ -1,4 +1,7 @@
using System.Collections.Generic;
using System;
using System.Collections.Generic;
using System.Linq;
using FluentValidation;
using NzbDrone.Common.Extensions;
using NzbDrone.Api.TrackFiles;
using NzbDrone.Api.Music;
@ -8,6 +11,7 @@ using NzbDrone.Core.Download;
using NzbDrone.Core.MediaFiles.Events;
using NzbDrone.Core.Messaging.Events;
using NzbDrone.Core.Music;
using NzbDrone.Core.ArtistStats;
using NzbDrone.SignalR;
namespace NzbDrone.Api.Albums
@ -15,16 +19,19 @@ namespace NzbDrone.Api.Albums
public abstract class AlbumModuleWithSignalR : NzbDroneRestModuleWithSignalR<AlbumResource, Track>
{
protected readonly IAlbumService _albumService;
protected readonly IArtistStatisticsService _artistStatisticsService;
protected readonly IArtistService _artistService;
protected readonly IQualityUpgradableSpecification _qualityUpgradableSpecification;
protected AlbumModuleWithSignalR(IAlbumService albumService,
IArtistStatisticsService artistStatisticsService,
IArtistService artistService,
IQualityUpgradableSpecification qualityUpgradableSpecification,
IBroadcastSignalRMessage signalRBroadcaster)
: base(signalRBroadcaster)
{
_albumService = albumService;
_artistStatisticsService = artistStatisticsService;
_artistService = artistService;
_qualityUpgradableSpecification = qualityUpgradableSpecification;
@ -32,6 +39,7 @@ namespace NzbDrone.Api.Albums
}
protected AlbumModuleWithSignalR(IAlbumService albumService,
IArtistStatisticsService artistStatisticsService,
IArtistService artistService,
IQualityUpgradableSpecification qualityUpgradableSpecification,
IBroadcastSignalRMessage signalRBroadcaster,
@ -39,6 +47,7 @@ namespace NzbDrone.Api.Albums
: base(signalRBroadcaster, resource)
{
_albumService = albumService;
_artistStatisticsService = artistStatisticsService;
_artistService = artistService;
_qualityUpgradableSpecification = qualityUpgradableSpecification;
@ -66,6 +75,8 @@ namespace NzbDrone.Api.Albums
}
}
FetchAndLinkAlbumStatistics(resource);
return resource;
}
@ -91,9 +102,32 @@ namespace NzbDrone.Api.Albums
}
}
for (var i = 0; i < albums.Count; i++)
{
var resource = result[i];
FetchAndLinkAlbumStatistics(resource);
}
return result;
}
private void FetchAndLinkAlbumStatistics(AlbumResource resource)
{
LinkArtistStatistics(resource, _artistStatisticsService.ArtistStatistics(resource.ArtistId));
}
private void LinkArtistStatistics(AlbumResource resource, ArtistStatistics artistStatistics)
{
if (artistStatistics.AlbumStatistics != null)
{
var dictAlbumStats = artistStatistics.AlbumStatistics.ToDictionary(v => v.AlbumId);
resource.Statistics = dictAlbumStats.GetValueOrDefault(resource.Id).ToResource();
}
}
//TODO: Implement Track or Album Grabbed/Dowloaded Events
//public void Handle(TrackGrabbedEvent message)

@ -23,6 +23,7 @@ namespace NzbDrone.Api.Albums
public List<string> Genres { get; set; }
public ArtistResource Artist { get; set; }
public List<MediaCover> Images { get; set; }
public AlbumStatisticsResource Statistics { get; set; }
}

@ -0,0 +1,45 @@
using System;
using NzbDrone.Core.ArtistStats;
namespace NzbDrone.Api.Albums
{
public class AlbumStatisticsResource
{
public int TrackFileCount { get; set; }
public int TrackCount { get; set; }
public int TotalTrackCount { get; set; }
public long SizeOnDisk { get; set; }
public decimal PercentOfTracks
{
get
{
if (TrackCount == 0)
{
return 0;
}
return (decimal)TrackFileCount / (decimal)TrackCount * 100;
}
}
}
public static class AlbumStatisticsResourceMapper
{
public static AlbumStatisticsResource ToResource(this AlbumStatistics model)
{
if (model == null)
{
return null;
}
return new AlbumStatisticsResource
{
TrackFileCount = model.TrackFileCount,
TrackCount = model.TrackCount,
TotalTrackCount = model.TotalTrackCount,
SizeOnDisk = model.SizeOnDisk
};
}
}
}

@ -6,6 +6,7 @@ using NzbDrone.Api.Albums;
using NzbDrone.Core.DecisionEngine;
using NzbDrone.Core.Tv;
using NzbDrone.Core.Music;
using NzbDrone.Core.ArtistStats;
using NzbDrone.SignalR;
namespace NzbDrone.Api.Calendar
@ -13,10 +14,11 @@ namespace NzbDrone.Api.Calendar
public class CalendarModule : AlbumModuleWithSignalR
{
public CalendarModule(IAlbumService albumService,
IArtistStatisticsService artistStatisticsService,
IArtistService artistService,
IQualityUpgradableSpecification qualityUpgradableSpecification,
IBroadcastSignalRMessage signalRBroadcaster)
: base(albumService, artistService, qualityUpgradableSpecification, signalRBroadcaster, "calendar")
: base(albumService, artistStatisticsService, artistService, qualityUpgradableSpecification, signalRBroadcaster, "calendar")
{
GetResourceAll = GetCalendar;
}

@ -52,6 +52,7 @@ namespace NzbDrone.Api.Music
public string RootFolderPath { get; set; }
//public string Certification { get; set; }
public List<string> Genres { get; set; }
public string CleanName { get; set; }
public HashSet<int> Tags { get; set; }
public DateTime Added { get; set; }
public AddArtistOptions AddOptions { get; set; }
@ -73,6 +74,7 @@ namespace NzbDrone.Api.Music
DiscogsId = model.DiscogsId,
AllMusicId = model.AMId,
Name = model.Name,
CleanName = model.CleanName,
//AlternateTitles
//SortTitle = resource.SortTitle,
@ -88,8 +90,7 @@ namespace NzbDrone.Api.Music
//AirTime = resource.AirTime,
Images = model.Images,
Members = model.Members,
Albums = model.Albums.ToResource(),
//Albums = model.Albums.ToResource(),
//Year = resource.Year,
Path = model.Path,
@ -127,6 +128,7 @@ namespace NzbDrone.Api.Music
Id = resource.Id,
Name = resource.Name,
CleanName = resource.CleanName,
//AlternateTitles
//SortTitle = resource.SortTitle,
MBId = resource.MBId,
@ -145,8 +147,7 @@ namespace NzbDrone.Api.Music
//AirTime = resource.AirTime,
Images = resource.Images,
Members = resource.Members,
Albums = resource.Albums.ToModel(),
//Albums = resource.Albums.ToModel(),
//Year = resource.Year,
Path = resource.Path,

@ -89,6 +89,7 @@
<Link>Properties\SharedAssemblyInfo.cs</Link>
</Compile>
<Compile Include="Albums\AlbumModuleWithSignalR.cs" />
<Compile Include="Albums\AlbumStatisticsResource.cs" />
<Compile Include="Authentication\AuthenticationService.cs" />
<Compile Include="Authentication\EnableAuthInNancy.cs" />
<Compile Include="Authentication\AuthenticationModule.cs" />
@ -105,6 +106,8 @@
<Compile Include="ClientSchema\SelectOption.cs" />
<Compile Include="Commands\CommandModule.cs" />
<Compile Include="Commands\CommandResource.cs" />
<Compile Include="AlbumStudio\AlbumStudioModule.cs" />
<Compile Include="AlbumStudio\AlbumStudioResource.cs" />
<Compile Include="TrackFiles\TrackFileModule.cs" />
<Compile Include="TrackFiles\TrackFileResource.cs" />
<Compile Include="Albums\AlbumModule.cs" />

@ -2,7 +2,7 @@
using NzbDrone.Api.Albums;
using NzbDrone.Core.Datastore;
using NzbDrone.Core.DecisionEngine;
using NzbDrone.Core.Tv;
using NzbDrone.Core.ArtistStats;
using NzbDrone.Core.Music;
using NzbDrone.SignalR;
@ -11,10 +11,11 @@ namespace NzbDrone.Api.Wanted
public class MissingModule : AlbumModuleWithSignalR
{
public MissingModule(IAlbumService albumService,
IArtistStatisticsService artistStatisticsService,
IArtistService artistService,
IQualityUpgradableSpecification qualityUpgradableSpecification,
IBroadcastSignalRMessage signalRBroadcaster)
: base(albumService, artistService, qualityUpgradableSpecification, signalRBroadcaster, "wanted/missing")
: base(albumService, artistStatisticsService, artistService, qualityUpgradableSpecification, signalRBroadcaster, "wanted/missing")
{
GetResourcePaged = GetMissingAlbums;
}

@ -0,0 +1,69 @@
using System;
using System.Collections.Generic;
using System.Linq;
using NLog;
using NzbDrone.Common.Extensions;
namespace NzbDrone.Core.Music
{
public interface IAlbumMonitoredService
{
void SetAlbumMonitoredStatus(Artist album, MonitoringOptions monitoringOptions);
}
public class AlbumMonitoredService : IAlbumMonitoredService
{
private readonly IArtistService _artistService;
private readonly IAlbumService _albumService;
private readonly ITrackService _trackService;
private readonly Logger _logger;
public AlbumMonitoredService(IArtistService artistService, IAlbumService albumService, ITrackService trackService, Logger logger)
{
_artistService = artistService;
_albumService = albumService;
_trackService = trackService;
_logger = logger;
}
public void SetAlbumMonitoredStatus(Artist artist, MonitoringOptions monitoringOptions)
{
if (monitoringOptions != null)
{
_logger.Debug("[{0}] Setting album monitored status.", artist.Name);
var albums = _albumService.GetAlbumsByArtist(artist.Id);
if (monitoringOptions.Monitored)
{
ToggleAlbumsMonitoredState(albums, true);
}
else
{
ToggleAlbumsMonitoredState(albums, false);
}
//TODO Add Other Options for Future/Exisitng/Missing Once we have a good way to check for Album Related Files.
_albumService.UpdateAlbums(albums);
}
_artistService.UpdateArtist(artist);
}
private void ToggleAlbumsMonitoredState(IEnumerable<Album> albums, bool monitored)
{
foreach (var album in albums)
{
album.Monitored = monitored;
var tracks = _trackService.GetTracksByAlbum(album.ArtistId, album.Id);
foreach (var track in tracks)
{
track.Monitored = monitored;
}
_trackService.UpdateTracks(tracks);
}
}
}
}

@ -75,11 +75,11 @@ namespace NzbDrone.Core.Music
private QueryBuilder<Album> GetMissingAlbumsQuery(PagingSpec<Album> pagingSpec, DateTime currentTime)
{
string sortKey;
int monitored = 0;
string monitored = "([t0].[Monitored] = 0) OR ([t1].[Monitored] = 0)";
if (pagingSpec.FilterExpression.ToString().Contains("True"))
{
monitored = 1;
monitored = "([t0].[Monitored] = 1) AND ([t1].[Monitored] = 1)";
}
if (pagingSpec.SortKey == "releaseDate")
@ -96,7 +96,7 @@ namespace NzbDrone.Core.Music
}
string query = string.Format("SELECT * FROM Albums [t0] INNER JOIN Artists [t1] ON ([t0].[ArtistId] = [t1].[Id])" +
"WHERE (([t0].[Monitored] = {0}) AND ([t1].[Monitored] = {0})) AND {1}" +
"WHERE ({0}) AND {1}" +
" AND NOT EXISTS (SELECT 1 from Tracks [t2] WHERE [t2].albumId = [t0].id AND [t2].trackFileId <> 0) ORDER BY {2} {3} LIMIT {4} OFFSET {5}",
monitored, BuildReleaseDateCutoffWhereClause(currentTime), sortKey, pagingSpec.ToSortDirection(), pagingSpec.PageSize, pagingSpec.PagingOffset());

@ -58,30 +58,19 @@ namespace NzbDrone.Core.Music
{
ForeignArtistId = otherArtist.ForeignArtistId;
MBId = otherArtist.MBId;
TADBId = otherArtist.TADBId;
DiscogsId = otherArtist.DiscogsId;
AMId = otherArtist.AMId;
Name = otherArtist.Name;
NameSlug = otherArtist.NameSlug;
CleanName = otherArtist.CleanName;
Monitored = otherArtist.Monitored;
AlbumFolder = otherArtist.AlbumFolder;
LastInfoSync = otherArtist.LastInfoSync;
Images = otherArtist.Images;
Path = otherArtist.Path;
Genres = otherArtist.Genres;
RootFolderPath = otherArtist.RootFolderPath;
Added = otherArtist.Added;
Profile = otherArtist.Profile;
ProfileId = otherArtist.ProfileId;
Albums = otherArtist.Albums;
ProfileId = otherArtist.ProfileId;
Tags = otherArtist.Tags;
AddOptions = otherArtist.AddOptions;
Ratings = otherArtist.Ratings;
Members = otherArtist.Members;
RootFolderPath = otherArtist.RootFolderPath;
Monitored = otherArtist.Monitored;
AlbumFolder = otherArtist.AlbumFolder;
}
}

@ -0,0 +1,11 @@
using NzbDrone.Core.Datastore;
namespace NzbDrone.Core.Music
{
public class MonitoringOptions : IEmbeddedDocument
{
public bool IgnoreTracksWithFiles { get; set; }
public bool IgnoreTracksWithoutFiles { get; set; }
public bool Monitored { get; set; }
}
}

@ -0,0 +1,73 @@
using System;
using System.Collections.Generic;
using System.Linq;
using NLog;
using NzbDrone.Common.Extensions;
namespace NzbDrone.Core.Music
{
public interface ITrackMonitoredService
{
void SetTrackMonitoredStatus(Artist album, MonitoringOptions monitoringOptions);
}
public class TrackMonitoredService : ITrackMonitoredService
{
private readonly IArtistService _albumService;
private readonly ITrackService _trackService;
private readonly Logger _logger;
public TrackMonitoredService(IArtistService albumService, ITrackService trackService, Logger logger)
{
_albumService = albumService;
_trackService = trackService;
_logger = logger;
}
public void SetTrackMonitoredStatus(Artist album, MonitoringOptions monitoringOptions)
{
if (monitoringOptions != null)
{
_logger.Debug("[{0}] Setting track monitored status.", album.Name);
var tracks = _trackService.GetTracksByArtist(album.Id);
if (monitoringOptions.IgnoreTracksWithFiles)
{
_logger.Debug("Ignoring Tracks with Files");
ToggleTracksMonitoredState(tracks.Where(e => e.HasFile), false);
}
else
{
_logger.Debug("Monitoring Tracks with Files");
ToggleTracksMonitoredState(tracks.Where(e => e.HasFile), true);
}
if (monitoringOptions.IgnoreTracksWithoutFiles)
{
_logger.Debug("Ignoring Tracks without Files");
ToggleTracksMonitoredState(tracks.Where(e => !e.HasFile), false);
}
else
{
_logger.Debug("Monitoring Episodes without Files");
ToggleTracksMonitoredState(tracks.Where(e => !e.HasFile), true);
}
_trackService.UpdateTracks(tracks);
}
_albumService.UpdateArtist(album);
}
private void ToggleTracksMonitoredState(IEnumerable<Track> tracks, bool monitored)
{
foreach (var track in tracks)
{
track.Monitored = monitored;
}
}
}
}

@ -865,6 +865,9 @@
<Compile Include="Extras\Metadata\MetadataType.cs" />
<Compile Include="MetadataSource\IProvideSeriesInfo.cs" />
<Compile Include="MetadataSource\ISearchForNewSeries.cs" />
<Compile Include="Music\MonitoringOptions.cs" />
<Compile Include="Music\AlbumMonitoredService.cs" />
<Compile Include="Music\TrackMonitoredService.cs" />
<Compile Include="Music\Member.cs" />
<Compile Include="Music\AddArtistOptions.cs" />
<Compile Include="Music\AddArtistService.cs" />

@ -6,8 +6,5 @@ namespace NzbDrone.Core.Tv
{
public bool IgnoreEpisodesWithFiles { get; set; }
public bool IgnoreEpisodesWithoutFiles { get; set; }
public bool IgnoreTracksWithFiles { get; set; }
public bool IgnoreTracksWithoutFiles { get; set; }
}
}

@ -0,0 +1,25 @@
var _ = require('underscore');
var Marionette = require('marionette');
var SingleAlbumCell = require('./SingleAlbumCell');
var AsSortedCollectionView = require('../Mixins/AsSortedCollectionView');
var view = Marionette.CollectionView.extend({
itemView : SingleAlbumCell,
initialize : function(options) {
this.albumCollection = options.collection;
this.artist = options.artist;
},
itemViewOptions : function() {
return {
albumCollection : this.albumCollection,
artist : this.artist
};
}
});
AsSortedCollectionView.call(view);
module.exports = view;

@ -5,13 +5,13 @@ var vent = require('vent');
var RootFolders = require('../AddArtist/RootFolders/RootFolderCollection');
module.exports = Marionette.ItemView.extend({
template : 'SeasonPass/SeasonPassFooterViewTemplate',
template : 'AlbumStudio/AlbumStudioFooterViewTemplate',
ui : {
seriesMonitored : '.x-series-monitored',
artistMonitored : '.x-artist-monitored',
monitor : '.x-monitor',
selectedCount : '.x-selected-count',
container : '.series-editor-footer',
container : '.artist-editor-footer',
actions : '.x-action',
indicator : '.x-indicator',
indicatorIcon : '.x-indicator-icon'
@ -22,14 +22,14 @@ module.exports = Marionette.ItemView.extend({
},
initialize : function(options) {
this.seriesCollection = options.collection;
this.artistCollection = options.collection;
RootFolders.fetch().done(function() {
RootFolders.synced = true;
});
this.editorGrid = options.editorGrid;
this.listenTo(this.seriesCollection, 'backgrid:selected', this._updateInfo);
this.listenTo(this.artistCollection, 'backgrid:selected', this._updateInfo);
},
onRender : function() {
@ -39,13 +39,13 @@ module.exports = Marionette.ItemView.extend({
_update : function() {
var self = this;
var selected = this.editorGrid.getSelectedModels();
var seriesMonitored = this.ui.seriesMonitored.val();
var artistMonitored = this.ui.artistMonitored.val();
var monitoringOptions;
_.each(selected, function(model) {
if (seriesMonitored === 'true') {
if (artistMonitored === 'true') {
model.set('monitored', true);
} else if (seriesMonitored === 'false') {
} else if (artistMonitored === 'false') {
model.set('monitored', false);
}
@ -54,10 +54,10 @@ module.exports = Marionette.ItemView.extend({
});
var promise = $.ajax({
url : window.NzbDrone.ApiRoot + '/seasonpass',
url : window.NzbDrone.ApiRoot + '/albumstudio',
type : 'POST',
data : JSON.stringify({
series : _.map(selected, function (model) {
artist : _.map(selected, function (model) {
return model.toJSON();
}),
monitoringOptions : monitoringOptions
@ -71,7 +71,7 @@ module.exports = Marionette.ItemView.extend({
});
promise.done(function () {
self.seriesCollection.trigger('seasonpass:saved');
self.artistCollection.trigger('albumstudio:saved');
});
},
@ -79,7 +79,7 @@ module.exports = Marionette.ItemView.extend({
var selected = this.editorGrid.getSelectedModels();
var selectedCount = selected.length;
this.ui.selectedCount.html('{0} series selected'.format(selectedCount));
this.ui.selectedCount.html('{0} artists selected'.format(selectedCount));
if (selectedCount === 0) {
this.ui.actions.attr('disabled', 'disabled');
@ -90,48 +90,25 @@ module.exports = Marionette.ItemView.extend({
_getMonitoringOptions : function(model) {
var monitor = this.ui.monitor.val();
var lastSeason = _.max(model.get('seasons'), 'seasonNumber');
var firstSeason = _.min(_.reject(model.get('seasons'), { seasonNumber : 0 }), 'seasonNumber');
if (monitor === 'noChange') {
return null;
}
model.setSeasonPass(firstSeason.seasonNumber);
model.setAlbumPass(0);
var options = {
ignoreEpisodesWithFiles : false,
ignoreEpisodesWithoutFiles : false
ignoreTracksWithFiles : false,
ignoreTracksWithoutFiles : false,
monitored : true
};
if (monitor === 'all') {
return options;
}
else if (monitor === 'future') {
options.ignoreEpisodesWithFiles = true;
options.ignoreEpisodesWithoutFiles = true;
}
else if (monitor === 'latest') {
model.setSeasonPass(lastSeason.seasonNumber);
}
else if (monitor === 'first') {
model.setSeasonPass(lastSeason.seasonNumber + 1);
model.setSeasonMonitored(firstSeason.seasonNumber);
}
else if (monitor === 'missing') {
options.ignoreEpisodesWithFiles = true;
}
else if (monitor === 'existing') {
options.ignoreEpisodesWithoutFiles = true;
}
else if (monitor === 'none') {
model.setSeasonPass(lastSeason.seasonNumber + 1);
options.monitored = false;
}
return options;

@ -1,9 +1,9 @@
<div class="series-editor-footer">
<div class="artist-editor-footer">
<div class="row">
<div class="form-group col-md-2">
<label>Monitor series</label>
<label>Monitor artist</label>
<select class="form-control x-action x-series-monitored">
<select class="form-control x-action x-artist-monitored">
<option value="noChange">No change</option>
<option value="true">Monitored</option>
<option value="false">Unmonitored</option>
@ -11,24 +11,19 @@
</div>
<div class="form-group col-md-2">
<label>Monitor episodes</label>
<label>Monitor albums</label>
<select class="form-control x-action x-monitor">
<option value="noChange">No change</option>
<option value="all">All</option>
<option value="future">Future</option>
<option value="missing">Missing</option>
<option value="existing">Existing</option>
<option value="first">First Season</option>
<option value="latest">Latest Season</option>
<option value="none">None</option>
</select>
</div>
<div class="form-group col-md-3 actions">
<label class="x-selected-count">0 series selected</label>
<label class="x-selected-count">0 artists selected</label>
<div>
<button class="btn btn-primary x-action x-update">Update Selected Series</button>
<button class="btn btn-primary x-action x-update">Update Selected Artist</button>
<span class="indicator x-indicator"><i class="icon-lidarr-spinner fa-spin"></i></span>
</div>
</div>

@ -2,23 +2,23 @@ var _ = require('underscore');
var vent = require('vent');
var Backgrid = require('backgrid');
var Marionette = require('marionette');
var EmptyView = require('../Series/Index/EmptyView');
var SeriesCollection = require('../Series/SeriesCollection');
var EmptyView = require('../Artist/Index/EmptyView');
var ArtistCollection = require('../Artist/ArtistCollection');
var ToolbarLayout = require('../Shared/Toolbar/ToolbarLayout');
var FooterView = require('./SeasonPassFooterView');
var FooterView = require('./AlbumStudioFooterView');
var SelectAllCell = require('../Cells/SelectAllCell');
var SeriesStatusCell = require('../Cells/SeriesStatusCell');
var SeriesTitleCell = require('../Cells/SeriesTitleCell');
var SeriesMonitoredCell = require('../Cells/ToggleCell');
var SeasonsCell = require('./SeasonsCell');
var ArtistStatusCell = require('../Cells/ArtistStatusCell');
var ArtistTitleCell = require('../Cells/ArtistTitleCell');
var ArtistMonitoredCell = require('../Cells/ArtistMonitoredCell');
var AlbumsCell = require('./AlbumsCell');
require('../Mixins/backbone.signalr.mixin');
module.exports = Marionette.Layout.extend({
template : 'SeasonPass/SeasonPassLayoutTemplate',
template : 'AlbumStudio/AlbumStudioLayoutTemplate',
regions : {
toolbar : '#x-toolbar',
series : '#x-series'
artist : '#x-artist'
},
columns : [
@ -31,42 +31,38 @@ module.exports = Marionette.Layout.extend({
{
name : 'statusWeight',
label : '',
cell : SeriesStatusCell
},
{
name : 'title',
label : 'Title',
cell : SeriesTitleCell,
cellValue : 'this'
cell : ArtistStatusCell
},
{
name : 'monitored',
label : '',
cell : SeriesMonitoredCell,
label : 'Artist',
cell : ArtistMonitoredCell,
trueClass : 'icon-lidarr-monitored',
falseClass : 'icon-lidarr-unmonitored',
tooltip : 'Toggle series monitored status',
tooltip : 'Toggle artist monitored status',
sortable : false
},
{
name : 'seasons',
label : 'Seasons',
cell : SeasonsCell,
name : 'albums',
label : 'Albums',
cell : AlbumsCell,
cellValue : 'this'
}
],
initialize : function() {
this.seriesCollection = SeriesCollection.clone();
this.seriesCollection.shadowCollection.bindSignalR();
this.artistCollection = ArtistCollection.clone();
this.artistCollection.shadowCollection.bindSignalR();
// this.listenTo(this.seriesCollection, 'sync', this.render);
this.listenTo(this.seriesCollection, 'seasonpass:saved', this.render);
this.listenTo(this.artistCollection, 'sync', this.render);
this.listenTo(this.artistCollection, 'albumstudio:saved', this.render);
this.filteringOptions = {
type : 'radio',
storeState : true,
menuKey : 'seasonpass.filterMode',
menuKey : 'albumstudio.filterMode',
defaultAction : 'all',
items : [
{
@ -87,14 +83,14 @@ module.exports = Marionette.Layout.extend({
key : 'continuing',
title : '',
tooltip : 'Continuing Only',
icon : 'icon-lidarr-series-continuing',
icon : 'icon-lidarr-artist-continuing',
callback : this._setFilter
},
{
key : 'ended',
title : '',
tooltip : 'Ended Only',
icon : 'icon-lidarr-series-ended',
icon : 'icon-lidarr-artist-ended',
callback : this._setFilter
}
]
@ -119,34 +115,34 @@ module.exports = Marionette.Layout.extend({
},
_showTable : function() {
if (this.seriesCollection.shadowCollection.length === 0) {
this.series.show(new EmptyView());
if (this.artistCollection.shadowCollection.length === 0) {
this.artist.show(new EmptyView());
this.toolbar.close();
return;
}
this.columns[0].sortedCollection = this.seriesCollection;
this.columns[0].sortedCollection = this.artistCollection;
this.editorGrid = new Backgrid.Grid({
collection : this.seriesCollection,
collection : this.artistCollection,
columns : this.columns,
className : 'table table-hover'
});
this.series.show(this.editorGrid);
this.artist.show(this.editorGrid);
this._showFooter();
},
_showFooter : function() {
vent.trigger(vent.Commands.OpenControlPanelCommand, new FooterView({
editorGrid : this.editorGrid,
collection : this.seriesCollection
collection : this.artistCollection
}));
},
_setFilter : function(buttonContext) {
var mode = buttonContext.model.get('key');
this.seriesCollection.setFilterMode(mode);
this.artistCollection.setFilterMode(mode);
}
});

@ -0,0 +1,14 @@
<h1>Album Studio</h1>
<div id="x-toolbar"></div>
<div class="row">
<div class="col-md-12">
<div class="alert alert-info">Album Studio allows you to quickly change the monitored status of albums for all your artists in one place</div>
</div>
</div>
<div class="row">
<div class="col-md-12">
<div id="x-artist"></div>
</div>
</div>

@ -0,0 +1,58 @@
var $ = require('jquery');
var _ = require('underscore');
var vent = require('vent');
var Marionette = require('marionette');
var TemplatedCell = require('../Cells/TemplatedCell');
var AlbumCollection = require('../Artist/AlbumCollection');
var LoadingView = require('../Shared/LoadingView');
var ArtistCollection = require('../Artist/ArtistCollection');
var AlbumCollectionView = require('./AlbumStudioCollectionView');
//require('../Handlebars/Helpers/Numbers');
module.exports = Marionette.Layout.extend({
template : 'AlbumStudio/AlbumsCellTemplate',
regions : {
albums : '#albums'
},
initialize : function() {
this.artistCollection = ArtistCollection.clone();
this.artistCollection.shadowCollection.bindSignalR();
this.listenTo(this.model, 'change:monitored', this._setMonitoredState);
this.listenTo(this.model, 'remove', this._artistRemoved);
this.listenTo(vent, vent.Events.CommandComplete, this._commandComplete);
this.listenTo(this.model, 'change', function(model, options) {
if (options && options.changeSource === 'signalr') {
this._refresh();
}
});
},
onRender : function(){
this._showAlbums();
},
_showAlbums : function() {
var self = this;
this.albums.show(new LoadingView());
this.albumCollection = new AlbumCollection({ artistId : this.model.id }).bindSignalR();
$.when(this.albumCollection.fetch()).done(function() {
var albumCollectionView = new AlbumCollectionView({
collection : self.albumCollection,
artist : self.model
});
if (!self.isClosed) {
self.albums.show(albumCollectionView);
}
});
},
});

@ -0,0 +1 @@
<div id="albums" class="artist-albums"></div>

@ -0,0 +1,64 @@
var vent = require('vent');
var Marionette = require('marionette');
var Backgrid = require('backgrid');
var ToggleCell = require('../Cells/TrackMonitoredCell');
var CommandController = require('../Commands/CommandController');
var moment = require('moment');
var _ = require('underscore');
var Messenger = require('../Shared/Messenger');
module.exports = Marionette.Layout.extend({
template : 'AlbumStudio/SingleAlbumCellTemplate',
ui : {
albumMonitored : '.x-album-monitored'
},
events : {
'click .x-album-monitored' : '_albumMonitored'
},
initialize : function(options) {
this.artist = options.artist;
this.listenTo(this.model, 'sync', this._afterAlbumMonitored);
},
onRender : function() {
this._setAlbumMonitoredState();
},
_albumMonitored : function() {
if (!this.artist.get('monitored')) {
Messenger.show({
message : 'Unable to change monitored state when artist is not monitored',
type : 'error'
});
return;
}
var savePromise = this.model.save('monitored', !this.model.get('monitored'), { wait : true });
this.ui.albumMonitored.spinForPromise(savePromise);
},
_afterAlbumMonitored : function() {
this.render();
},
_setAlbumMonitoredState : function() {
this.ui.albumMonitored.removeClass('icon-lidarr-spinner fa-spin');
if (this.model.get('monitored')) {
this.ui.albumMonitored.addClass('icon-lidarr-monitored');
this.ui.albumMonitored.removeClass('icon-lidarr-unmonitored');
} else {
this.ui.albumMonitored.addClass('icon-lidarr-unmonitored');
this.ui.albumMonitored.removeClass('icon-lidarr-monitored');
}
}
});

@ -0,0 +1,30 @@
{{#if_eq statistics.totalTrackCount compare=0}}
<span class="album album-unaired">
{{else}}
{{#if_eq statistics.percentOfTracks compare=100}}
<span class="album album-all">
{{else}}
<span class="album album-partial">
{{/if_eq}}
{{/if_eq}}
<span class="label">
<span class="x-album-monitored album-monitored" title="Toggle album monitored status" data-album-number="{{seasonNumber}}">
</span>
<span class="album-number">{{Pad2 title}}</span>
</span><span class="label">
{{#with statistics}}
{{#if_eq trackCount compare=0}}
<span class="album-status" title="No aired tracks">&nbsp;</span>
{{else}}
{{#if_eq percentOfEpisodes compare=100}}
<span class="album-status" title="{{trackFileCount}}/{{trackCount}} tracks downloaded">{{trackFileCount}}/{{trackCount}}</span>
{{else}}
<span class="album-status" title="{{trackFileCount}}/{{trackCount}} tracks downloaded">{{trackFileCount}}/{{trackCount}}</span>
{{/if_eq}}
{{/if_eq}}
{{else}}
<span class="album-status" title="No aired tracks">&nbsp;</span>
{{/with}}
</span>
</span>

@ -1,7 +1,14 @@
@import "../Content/badges.less";
@import "../Shared/Styles/clickable.less";
.season {
.artist-albums {
div {
display : inline-block;
padding : 4px;
}
}
.album {
display : inline-block;
margin-bottom : 4px;
@ -31,11 +38,11 @@
background-color : #f7f7f7;
}
&.season-all .label:last-child {
&.album-all .label:last-child {
background-color : #e0ffe0;
}
.season-monitored {
.album-monitored {
width : 16px;
i {
@ -43,11 +50,11 @@
}
}
.season-number {
.album-number {
font-size : 12px;
}
.season-status {
.album-status {
display : inline-block;
vertical-align : baseline !important;
}

@ -11,18 +11,17 @@ module.exports = Backbone.Model.extend({
status : 0
},
setAlbumsMonitored : function(albumName) {
setAlbumsMonitored : function(albumId) {
_.each(this.get('albums'), function(album) {
console.log(album);
if (album.albumName === albumName) {
if (album.albumId === albumId) {
album.monitored = !album.monitored;
}
});
},
setAlbumPass : function(seasonNumber) {
setAlbumPass : function(monitored) {
_.each(this.get('albums'), function(album) {
if (album.seasonNumber >= seasonNumber) {
if (monitored === 0) {
album.monitored = true;
} else {
album.monitored = false;

@ -11,7 +11,7 @@ var view = Marionette.CollectionView.extend({
if (!options.trackCollection) {
throw 'trackCollection is needed';
}
console.log(options);
this.albumCollection = options.collection;
this.trackCollection = options.trackCollection;
this.artist = options.artist;

@ -132,11 +132,9 @@ module.exports = Marionette.Layout.extend({
this.trackCollection = this.fullTrackCollection.byAlbum(this.model.get('id'));
this._updateTrackCollection();
console.log(options);
this.showingTracks = this._shouldShowTracks();
this.listenTo(this.model, 'sync', this._afterSeasonMonitored);
this.listenTo(this.model, 'sync', this._afterAlbumMonitored);
this.listenTo(this.trackCollection, 'sync', this.render);
this.listenTo(this.fullTrackCollection, 'sync', this._refreshTracks);
this.listenTo(this.model, 'change:images', this._updateImages);
@ -203,28 +201,20 @@ module.exports = Marionette.Layout.extend({
if (!this.artist.get('monitored')) {
Messenger.show({
message : 'Unable to change monitored state when series is not monitored',
message : 'Unable to change monitored state when artist is not monitored',
type : 'error'
});
return;
}
var name = 'monitored';
this.model.set(name, !this.model.get(name));
this.artist.setSeasonMonitored(this.model.get('albumId'));
var savePromise = this.artist.save().always(this._afterSeasonMonitored.bind(this));
//var savePromise = this.model.save('monitored', !this.model.get('monitored'), { wait : true });
var savePromise = this.model.save('monitored', !this.model.get('monitored'), { wait : true });
this.ui.albumMonitored.spinForPromise(savePromise);
},
_afterSeasonMonitored : function() {
var self = this;
_.each(this.trackCollection.models, function(track) {
track.set({ monitored : self.model.get('monitored') });
});
_afterAlbumMonitored : function() {
this.render();
},

@ -127,11 +127,11 @@ module.exports = Marionette.Layout.extend({
if (monitored) {
this.ui.monitored.addClass('icon-lidarr-monitored');
this.ui.monitored.removeClass('icon-lidarr-unmonitored');
this.$el.removeClass('series-not-monitored');
this.$el.removeClass('artist-not-monitored');
} else {
this.ui.monitored.addClass('icon-lidarr-unmonitored');
this.ui.monitored.removeClass('icon-lidarr-monitored');
this.$el.addClass('series-not-monitored');
this.$el.addClass('artist-not-monitored');
}
},
@ -172,8 +172,6 @@ module.exports = Marionette.Layout.extend({
this.trackCollection = new TrackCollection({ artistId : this.model.id }).bindSignalR();
this.trackFileCollection = new TrackFileCollection({ artistId : this.model.id }).bindSignalR();
console.log (this.trackCollection);
reqres.setHandler(reqres.Requests.GetEpisodeFileById, function(trackFileId) {
return self.trackFileCollection.get(trackFileId);
});

@ -72,9 +72,9 @@ module.exports = Marionette.Layout.extend({
storeState : false,
items : [
{
title : 'Season Pass',
title : 'Album Studio',
icon : 'icon-lidarr-monitored',
route : 'seasonpass'
route : 'albumstudio'
},
{
title : 'Update Library',

@ -86,9 +86,9 @@ module.exports = Marionette.Layout.extend({
route : 'addartist'
},
{
title : 'Season Pass',
title : 'Album Studio',
icon : 'icon-lidarr-monitored',
route : 'seasonpass'
route : 'albumstudio'
},
{
title : 'Artist Editor',

@ -0,0 +1,39 @@
var ToggleCell = require('./ToggleCell');
var Handlebars = require('handlebars');
module.exports = ToggleCell.extend({
className : 'artist-monitored-cell',
events : {
'click i' : '_onClick'
},
render : function() {
this.$el.empty();
this.$el.html('<i /><a href=""><span class="artist-monitored-name"></span></a>');
var name = this.column.get('name');
if (this.model.get(name)) {
this.$('i').addClass(this.column.get('trueClass'));
} else {
this.$('i').addClass(this.column.get('falseClass'));
}
var link = "/artist/" + this.model.get('nameSlug');
var artistName = this.model.get('name');
this.$('a').attr('href', link );
this.$('span').html(artistName);
var tooltip = this.column.get('tooltip');
if (tooltip) {
this.$('i').attr('title', tooltip);
}
this.delegateEvents();
return this;
}
});

@ -15,7 +15,7 @@ module.exports = ToggleCell.extend({
if (!artist.get('monitored')) {
Messenger.show({
message : 'Unable to change monitored state when series is not monitored',
message : 'Unable to change monitored state when artist is not monitored',
type : 'error'
});

@ -15,6 +15,14 @@
}
}
.artist-monitored-cell {
.text-overflow();
.artist-monitored-name {
padding-left: 10px;
}
}
.track-title-cell {
.text-overflow();

@ -20,7 +20,7 @@
@import "../Shared/FileBrowser/filebrowser";
@import "badges";
@import "../ManualImport/manualimport";
@import "../SeasonPass/seasonpass";
@import "../AlbumStudio/albumstudio";
.main-region {
@media (min-width : @screen-lg-min) {

@ -9,7 +9,7 @@ var WantedLayout = require('./Wanted/WantedLayout');
var CalendarLayout = require('./Calendar/CalendarLayout');
var ReleaseLayout = require('./Release/ReleaseLayout');
var SystemLayout = require('./System/SystemLayout');
var SeasonPassLayout = require('./SeasonPass/SeasonPassLayout');
var AlbumStudioLayout = require('./AlbumStudio/AlbumStudioLayout');
//var SeriesEditorLayout = require('./Series/Editor/SeriesEditorLayout');
var ArtistEditorLayout = require('./Artist/Editor/ArtistEditorLayout');
@ -49,9 +49,9 @@ module.exports = NzbDroneController.extend({
this.showMainRegion(new SystemLayout({ action : action }));
},
seasonPass : function() {
this.setTitle('Season Pass');
this.showMainRegion(new SeasonPassLayout());
albumStudio : function() {
this.setTitle('Album Studio');
this.showMainRegion(new AlbumStudioLayout());
},
artistEditor : function() {

@ -18,7 +18,7 @@ module.exports = Marionette.AppRouter.extend({
'rss' : 'rss',
'system' : 'system',
'system/:action' : 'system',
'seasonpass' : 'seasonPass',
'albumstudio' : 'albumStudio',
'artisteditor' : 'artistEditor',
':whatever' : 'showNotFound'
}

@ -1,13 +0,0 @@
<div id="x-toolbar"></div>
<div class="row">
<div class="col-md-12">
<div class="alert alert-info">Season Pass allows you to quickly change the monitored status of seasons for all your series in one place</div>
</div>
</div>
<div class="row">
<div class="col-md-12">
<div id="x-series"></div>
</div>
</div>

@ -1,26 +0,0 @@
var _ = require('underscore');
var TemplatedCell = require('../Cells/TemplatedCell');
//require('../Handlebars/Helpers/Numbers');
module.exports = TemplatedCell.extend({
className : 'seasons-cell',
template : 'SeasonPass/SeasonsCellTemplate',
events : {
'click .x-season-monitored' : '_toggleSeasonMonitored'
},
_toggleSeasonMonitored : function(e) {
var target = this.$(e.target).closest('.x-season-monitored');
var seasonNumber = parseInt(this.$(target).data('season-number'), 10);
var icon = this.$(target).children('.x-season-monitored-icon');
this.model.setSeasonMonitored(seasonNumber);
//TODO: unbounce the save so we don't multiple to the server at the same time
var savePromise = this.model.save();
icon.spinForPromise(savePromise);
savePromise.always(this.render.bind(this));
}
});

@ -1,37 +0,0 @@
{{#each seasons}}
{{debug}}
{{#if_eq statistics.totalEpisodeCount compare=0}}
<span class="season season-unaired">
{{else}}
{{#if_eq statistics.percentOfEpisodes compare=100}}
<span class="season season-all">
{{else}}
<span class="season season-partial">
{{/if_eq}}
{{/if_eq}}
<span class="label">
<span class="x-season-monitored season-monitored" title="Toggle season monitored status" data-season-number="{{seasonNumber}}">
<i class="x-season-monitored-icon {{#if monitored}}icon-lidarr-monitored{{else}}icon-lidarr-unmonitored{{/if}}"/>
</span>
{{#if_eq seasonNumber compare="0"}}
<span class="season-number">Specials</span>
{{else}}
<span class="season-number">S{{Pad2 seasonNumber}}</span>
{{/if_eq}}
</span><span class="label">
{{#with statistics}}
{{#if_eq totalEpisodeCount compare=0}}
<span class="season-status" title="No aired episodes">&nbsp;</span>
{{else}}
{{#if_eq percentOfEpisodes compare=100}}
<span class="season-status" title="{{episodeFileCount}}/{{totalEpisodeCount}} episodes downloaded">{{episodeFileCount}}/{{totalEpisodeCount}}</span>
{{else}}
<span class="season-status" title="{{episodeFileCount}}/{{totalEpisodeCount}} episodes downloaded">{{episodeFileCount}}/{{totalEpisodeCount}}</span>
{{/if_eq}}
{{/if_eq}}
{{else}}
<span class="season-status" title="No aired episodes">&nbsp;</span>
{{/with}}
</span>
</span>
{{/each}}

@ -107,9 +107,9 @@ module.exports = Marionette.Layout.extend({
className : 'x-search-selected'
},
{
title : 'Season Pass',
title : 'Album Studio',
icon : 'icon-lidarr-monitored',
route : 'seasonpass'
route : 'albumstudio'
}
]
};

@ -126,9 +126,9 @@ module.exports = Marionette.Layout.extend({
className : 'x-unmonitor-selected'
},
{
title : 'Season Pass',
title : 'Album Studio',
icon : 'icon-lidarr-monitored',
route : 'seasonpass'
route : 'albumstudio'
},
{
title : 'Rescan Drone Factory Folder',

Loading…
Cancel
Save