mirror of
https://github.com/Radarr/Radarr.git
synced 2024-11-09 04:22:30 +01:00
Episode grid will show downloading on grab
New: Update episode status in UI on grab and download
This commit is contained in:
parent
b6693a20a9
commit
daeb2fc652
@ -1,16 +1,22 @@
|
|||||||
using System.Collections.Generic;
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
using NzbDrone.Api.REST;
|
using NzbDrone.Api.REST;
|
||||||
|
using NzbDrone.Core.Datastore.Events;
|
||||||
using NzbDrone.Core.MediaFiles;
|
using NzbDrone.Core.MediaFiles;
|
||||||
using NzbDrone.Api.Mapping;
|
using NzbDrone.Api.Mapping;
|
||||||
|
using NzbDrone.Core.MediaFiles.Events;
|
||||||
|
using NzbDrone.Core.Messaging.Commands;
|
||||||
|
using NzbDrone.Core.Messaging.Events;
|
||||||
|
|
||||||
namespace NzbDrone.Api.EpisodeFiles
|
namespace NzbDrone.Api.EpisodeFiles
|
||||||
{
|
{
|
||||||
public class EpisodeModule : NzbDroneRestModule<EpisodeFileResource>
|
public class EpisodeModule : NzbDroneRestModuleWithSignalR<EpisodeFileResource, EpisodeFile>,
|
||||||
|
IHandle<EpisodeFileAddedEvent>
|
||||||
{
|
{
|
||||||
private readonly IMediaFileService _mediaFileService;
|
private readonly IMediaFileService _mediaFileService;
|
||||||
|
|
||||||
public EpisodeModule(IMediaFileService mediaFileService)
|
public EpisodeModule(ICommandExecutor commandExecutor, IMediaFileService mediaFileService)
|
||||||
: base("/episodefile")
|
: base(commandExecutor)
|
||||||
{
|
{
|
||||||
_mediaFileService = mediaFileService;
|
_mediaFileService = mediaFileService;
|
||||||
GetResourceById = GetEpisodeFile;
|
GetResourceById = GetEpisodeFile;
|
||||||
@ -41,5 +47,10 @@ private void SetQuality(EpisodeFileResource episodeFileResource)
|
|||||||
episodeFile.Quality = episodeFileResource.Quality;
|
episodeFile.Quality = episodeFileResource.Quality;
|
||||||
_mediaFileService.Update(episodeFile);
|
_mediaFileService.Update(episodeFile);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void Handle(EpisodeFileAddedEvent message)
|
||||||
|
{
|
||||||
|
BroadcastResourceChange(ModelAction.Updated, message.EpisodeFile.Id);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -54,7 +54,10 @@ public void Handle(EpisodeGrabbedEvent message)
|
|||||||
{
|
{
|
||||||
foreach (var episode in message.Episode.Episodes)
|
foreach (var episode in message.Episode.Episodes)
|
||||||
{
|
{
|
||||||
BroadcastResourceChange(ModelAction.Updated, episode.Id);
|
var resource = episode.InjectTo<EpisodeResource>();
|
||||||
|
resource.Downloading = true;
|
||||||
|
|
||||||
|
BroadcastResourceChange(ModelAction.Updated, resource);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -26,5 +26,7 @@ public class EpisodeResource : RestResource
|
|||||||
public DateTime? GrabDate { get; set; }
|
public DateTime? GrabDate { get; set; }
|
||||||
public Core.Tv.Series Series { get; set; }
|
public Core.Tv.Series Series { get; set; }
|
||||||
public String SeriesTitle { get; set; }
|
public String SeriesTitle { get; set; }
|
||||||
|
|
||||||
|
public Boolean Downloading { get; set; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -126,6 +126,8 @@
|
|||||||
<Compile Include="Missing\MissingModule.cs" />
|
<Compile Include="Missing\MissingModule.cs" />
|
||||||
<Compile Include="Config\NamingSampleResource.cs" />
|
<Compile Include="Config\NamingSampleResource.cs" />
|
||||||
<Compile Include="NzbDroneRestModuleWithSignalR.cs" />
|
<Compile Include="NzbDroneRestModuleWithSignalR.cs" />
|
||||||
|
<Compile Include="Queue\QueueModule.cs" />
|
||||||
|
<Compile Include="Queue\QueueResource.cs" />
|
||||||
<Compile Include="ResourceChangeMessage.cs" />
|
<Compile Include="ResourceChangeMessage.cs" />
|
||||||
<Compile Include="Notifications\NotificationSchemaModule.cs" />
|
<Compile Include="Notifications\NotificationSchemaModule.cs" />
|
||||||
<Compile Include="Notifications\NotificationModule.cs" />
|
<Compile Include="Notifications\NotificationModule.cs" />
|
||||||
|
@ -34,6 +34,17 @@ public void Handle(ModelEvent<TModel> message)
|
|||||||
BroadcastResourceChange(message.Action, message.Model.Id);
|
BroadcastResourceChange(message.Action, message.Model.Id);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected void BroadcastResourceChange(ModelAction action, TResource resource)
|
||||||
|
{
|
||||||
|
var signalRMessage = new SignalRMessage
|
||||||
|
{
|
||||||
|
Name = Resource,
|
||||||
|
Body = new ResourceChangeMessage<TResource>(resource, action)
|
||||||
|
};
|
||||||
|
|
||||||
|
_commandExecutor.PublishCommand(new BroadcastSignalRMessage(signalRMessage));
|
||||||
|
}
|
||||||
|
|
||||||
protected void BroadcastResourceChange(ModelAction action, int id)
|
protected void BroadcastResourceChange(ModelAction action, int id)
|
||||||
{
|
{
|
||||||
var resource = GetResourceById(id);
|
var resource = GetResourceById(id);
|
||||||
|
32
NzbDrone.Api/Queue/QueueModule.cs
Normal file
32
NzbDrone.Api/Queue/QueueModule.cs
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
using System.Collections.Generic;
|
||||||
|
using NzbDrone.Core.Datastore.Events;
|
||||||
|
using NzbDrone.Core.Download;
|
||||||
|
using NzbDrone.Core.Messaging.Commands;
|
||||||
|
using NzbDrone.Core.Messaging.Events;
|
||||||
|
using NzbDrone.Core.Queue;
|
||||||
|
|
||||||
|
namespace NzbDrone.Api.Queue
|
||||||
|
{
|
||||||
|
public class QueueModule : NzbDroneRestModuleWithSignalR<QueueResource, Core.Queue.Queue>,
|
||||||
|
IHandle<UpdateQueueEvent>
|
||||||
|
{
|
||||||
|
private readonly IQueueService _queueService;
|
||||||
|
|
||||||
|
public QueueModule(ICommandExecutor commandExecutor, IQueueService queueService)
|
||||||
|
: base(commandExecutor)
|
||||||
|
{
|
||||||
|
_queueService = queueService;
|
||||||
|
GetResourceAll = GetQueue;
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<QueueResource> GetQueue()
|
||||||
|
{
|
||||||
|
return ToListResource(_queueService.GetQueue);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Handle(UpdateQueueEvent message)
|
||||||
|
{
|
||||||
|
BroadcastResourceChange(ModelAction.Sync);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
18
NzbDrone.Api/Queue/QueueResource.cs
Normal file
18
NzbDrone.Api/Queue/QueueResource.cs
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using NzbDrone.Api.REST;
|
||||||
|
using NzbDrone.Core.Tv;
|
||||||
|
|
||||||
|
namespace NzbDrone.Api.Queue
|
||||||
|
{
|
||||||
|
public class QueueResource : RestResource
|
||||||
|
{
|
||||||
|
public Core.Tv.Series Series { get; set; }
|
||||||
|
public Episode Episode { get; set; }
|
||||||
|
public QualityModel Quality { get; set; }
|
||||||
|
public Decimal Size { get; set; }
|
||||||
|
public String Title { get; set; }
|
||||||
|
public Decimal SizeLeft { get; set; }
|
||||||
|
public TimeSpan Timeleft { get; set; }
|
||||||
|
}
|
||||||
|
}
|
@ -11,7 +11,7 @@ public class ResourceChangeMessage<TResource> where TResource : RestResource
|
|||||||
|
|
||||||
public ResourceChangeMessage(ModelAction action)
|
public ResourceChangeMessage(ModelAction action)
|
||||||
{
|
{
|
||||||
if (action != ModelAction.Deleted || action != ModelAction.Sync)
|
if (action != ModelAction.Deleted && action != ModelAction.Sync)
|
||||||
{
|
{
|
||||||
throw new InvalidOperationException("Resource message without a resource needs to have Delete or Sync as action");
|
throw new InvalidOperationException("Resource message without a resource needs to have Delete or Sync as action");
|
||||||
}
|
}
|
||||||
|
@ -22,6 +22,4 @@ public enum ModelAction
|
|||||||
Deleted = 3,
|
Deleted = 3,
|
||||||
Sync = 4
|
Sync = 4
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
@ -4,14 +4,10 @@ namespace NzbDrone.Core.Download
|
|||||||
{
|
{
|
||||||
public class QueueItem
|
public class QueueItem
|
||||||
{
|
{
|
||||||
public decimal Size { get; set; }
|
|
||||||
|
|
||||||
public string Title { get; set; }
|
|
||||||
|
|
||||||
public decimal SizeLeft { get; set; }
|
|
||||||
|
|
||||||
public string Id { get; set; }
|
public string Id { get; set; }
|
||||||
|
public decimal Size { get; set; }
|
||||||
|
public string Title { get; set; }
|
||||||
|
public decimal SizeLeft { get; set; }
|
||||||
public TimeSpan Timeleft { get; set; }
|
public TimeSpan Timeleft { get; set; }
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -70,12 +70,16 @@ public List<ImportDecision> Import(List<ImportDecision> decisions, bool newDownl
|
|||||||
{
|
{
|
||||||
episodeFile.SceneName = Path.GetFileNameWithoutExtension(localEpisode.Path.CleanFilePath());
|
episodeFile.SceneName = Path.GetFileNameWithoutExtension(localEpisode.Path.CleanFilePath());
|
||||||
episodeFile.Path = _episodeFileUpgrader.UpgradeEpisodeFile(episodeFile, localEpisode);
|
episodeFile.Path = _episodeFileUpgrader.UpgradeEpisodeFile(episodeFile, localEpisode);
|
||||||
_eventAggregator.PublishEvent(new EpisodeImportedEvent(localEpisode, episodeFile));
|
|
||||||
_eventAggregator.PublishEvent(new EpisodeDownloadedEvent(localEpisode));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
_mediaFileService.Add(episodeFile);
|
_mediaFileService.Add(episodeFile);
|
||||||
imported.Add(importDecision);
|
imported.Add(importDecision);
|
||||||
|
|
||||||
|
if (newDownload)
|
||||||
|
{
|
||||||
|
_eventAggregator.PublishEvent(new EpisodeImportedEvent(localEpisode, episodeFile));
|
||||||
|
_eventAggregator.PublishEvent(new EpisodeDownloadedEvent(localEpisode));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
catch (Exception e)
|
catch (Exception e)
|
||||||
{
|
{
|
||||||
|
@ -110,6 +110,7 @@
|
|||||||
</Reference>
|
</Reference>
|
||||||
<Reference Include="System.Drawing" />
|
<Reference Include="System.Drawing" />
|
||||||
<Reference Include="System.Web" />
|
<Reference Include="System.Web" />
|
||||||
|
<Reference Include="System.Windows.Forms" />
|
||||||
<Reference Include="System.XML" />
|
<Reference Include="System.XML" />
|
||||||
<Reference Include="System.Xml.Linq" />
|
<Reference Include="System.Xml.Linq" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
@ -216,7 +217,7 @@
|
|||||||
<Compile Include="Download\DownloadApprovedReports.cs" />
|
<Compile Include="Download\DownloadApprovedReports.cs" />
|
||||||
<Compile Include="Download\DownloadClientProvider.cs" />
|
<Compile Include="Download\DownloadClientProvider.cs" />
|
||||||
<Compile Include="Download\DownloadClientType.cs" />
|
<Compile Include="Download\DownloadClientType.cs" />
|
||||||
<Compile Include="Download\SabQueueItem.cs" />
|
<Compile Include="Download\QueueItem.cs" />
|
||||||
<Compile Include="Exceptions\BadRequestException.cs" />
|
<Compile Include="Exceptions\BadRequestException.cs" />
|
||||||
<Compile Include="Exceptions\DownstreamException.cs" />
|
<Compile Include="Exceptions\DownstreamException.cs" />
|
||||||
<Compile Include="Exceptions\NzbDroneClientException.cs" />
|
<Compile Include="Exceptions\NzbDroneClientException.cs" />
|
||||||
@ -419,6 +420,10 @@
|
|||||||
<Compile Include="Qualities\QualityProfileInUseException.cs" />
|
<Compile Include="Qualities\QualityProfileInUseException.cs" />
|
||||||
<Compile Include="Qualities\QualitySizeRepository.cs" />
|
<Compile Include="Qualities\QualitySizeRepository.cs" />
|
||||||
<Compile Include="Qualities\QualityProfileRepository.cs" />
|
<Compile Include="Qualities\QualityProfileRepository.cs" />
|
||||||
|
<Compile Include="Queue\Queue.cs" />
|
||||||
|
<Compile Include="Queue\UpdateQueueEvent.cs" />
|
||||||
|
<Compile Include="Queue\QueueScheduler.cs" />
|
||||||
|
<Compile Include="Queue\QueueService.cs" />
|
||||||
<Compile Include="Rest\RestSharpExtensions.cs" />
|
<Compile Include="Rest\RestSharpExtensions.cs" />
|
||||||
<Compile Include="Rest\RestException.cs" />
|
<Compile Include="Rest\RestException.cs" />
|
||||||
<Compile Include="SeriesStats\SeriesStatisticsService.cs" />
|
<Compile Include="SeriesStats\SeriesStatisticsService.cs" />
|
||||||
|
17
NzbDrone.Core/Queue/Queue.cs
Normal file
17
NzbDrone.Core/Queue/Queue.cs
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
using System;
|
||||||
|
using NzbDrone.Core.Datastore;
|
||||||
|
using NzbDrone.Core.Tv;
|
||||||
|
|
||||||
|
namespace NzbDrone.Core.Queue
|
||||||
|
{
|
||||||
|
public class Queue : ModelBase
|
||||||
|
{
|
||||||
|
public Series Series { get; set; }
|
||||||
|
public Episode Episode { get; set; }
|
||||||
|
public QualityModel Quality { get; set; }
|
||||||
|
public Decimal Size { get; set; }
|
||||||
|
public String Title { get; set; }
|
||||||
|
public Decimal SizeLeft { get; set; }
|
||||||
|
public TimeSpan Timeleft { get; set; }
|
||||||
|
}
|
||||||
|
}
|
58
NzbDrone.Core/Queue/QueueScheduler.cs
Normal file
58
NzbDrone.Core/Queue/QueueScheduler.cs
Normal file
@ -0,0 +1,58 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using NzbDrone.Common.TPL;
|
||||||
|
using NzbDrone.Core.Lifecycle;
|
||||||
|
using NzbDrone.Core.Messaging.Events;
|
||||||
|
using Timer = System.Timers.Timer;
|
||||||
|
|
||||||
|
namespace NzbDrone.Core.Queue
|
||||||
|
{
|
||||||
|
public class QueueScheduler : IHandle<ApplicationStartedEvent>,
|
||||||
|
IHandle<ApplicationShutdownRequested>
|
||||||
|
{
|
||||||
|
private readonly IEventAggregator _eventAggregator;
|
||||||
|
private static readonly Timer Timer = new Timer();
|
||||||
|
private static CancellationTokenSource _cancellationTokenSource;
|
||||||
|
|
||||||
|
public QueueScheduler(IEventAggregator eventAggregator)
|
||||||
|
{
|
||||||
|
_eventAggregator = eventAggregator;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void CheckQueue()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
Timer.Enabled = false;
|
||||||
|
_eventAggregator.PublishEvent(new UpdateQueueEvent());
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
if (!_cancellationTokenSource.IsCancellationRequested)
|
||||||
|
{
|
||||||
|
Timer.Enabled = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Handle(ApplicationStartedEvent message)
|
||||||
|
{
|
||||||
|
_cancellationTokenSource = new CancellationTokenSource();
|
||||||
|
Timer.Interval = 1000 * 30;
|
||||||
|
Timer.Elapsed += (o, args) => Task.Factory.StartNew(CheckQueue, _cancellationTokenSource.Token)
|
||||||
|
.LogExceptions();
|
||||||
|
|
||||||
|
Timer.Start();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Handle(ApplicationShutdownRequested message)
|
||||||
|
{
|
||||||
|
_cancellationTokenSource.Cancel(true);
|
||||||
|
Timer.Stop();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
72
NzbDrone.Core/Queue/QueueService.cs
Normal file
72
NzbDrone.Core/Queue/QueueService.cs
Normal file
@ -0,0 +1,72 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using NLog;
|
||||||
|
using NzbDrone.Core.Download;
|
||||||
|
using NzbDrone.Core.Parser;
|
||||||
|
|
||||||
|
namespace NzbDrone.Core.Queue
|
||||||
|
{
|
||||||
|
public interface IQueueService
|
||||||
|
{
|
||||||
|
List<Queue> GetQueue();
|
||||||
|
}
|
||||||
|
|
||||||
|
public class QueueService : IQueueService
|
||||||
|
{
|
||||||
|
private readonly IProvideDownloadClient _downloadClientProvider;
|
||||||
|
private readonly IParsingService _parsingService;
|
||||||
|
private readonly Logger _logger;
|
||||||
|
|
||||||
|
public QueueService(IProvideDownloadClient downloadClientProvider, IParsingService parsingService, Logger logger)
|
||||||
|
{
|
||||||
|
_downloadClientProvider = downloadClientProvider;
|
||||||
|
_parsingService = parsingService;
|
||||||
|
_logger = logger;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<Queue> GetQueue()
|
||||||
|
{
|
||||||
|
var downloadClient = _downloadClientProvider.GetDownloadClient();
|
||||||
|
var queueItems = downloadClient.GetQueue();
|
||||||
|
|
||||||
|
return MapQueue(queueItems);
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<Queue> MapQueue(IEnumerable<QueueItem> queueItems)
|
||||||
|
{
|
||||||
|
var queued = new List<Queue>();
|
||||||
|
|
||||||
|
foreach (var queueItem in queueItems)
|
||||||
|
{
|
||||||
|
var parsedEpisodeInfo = Parser.Parser.ParseTitle(queueItem.Title);
|
||||||
|
|
||||||
|
if (parsedEpisodeInfo != null && !string.IsNullOrWhiteSpace(parsedEpisodeInfo.SeriesTitle))
|
||||||
|
{
|
||||||
|
var remoteEpisode = _parsingService.Map(parsedEpisodeInfo, 0);
|
||||||
|
|
||||||
|
if (remoteEpisode.Series == null)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var episode in remoteEpisode.Episodes)
|
||||||
|
{
|
||||||
|
var queue = new Queue();
|
||||||
|
queue.Id = queueItem.Id.GetHashCode();
|
||||||
|
queue.Series = remoteEpisode.Series;
|
||||||
|
queue.Episode = episode;
|
||||||
|
queue.Quality = remoteEpisode.ParsedEpisodeInfo.Quality;
|
||||||
|
queue.Title = queueItem.Title;
|
||||||
|
queue.Size = queueItem.Size;
|
||||||
|
queue.SizeLeft = queueItem.SizeLeft;
|
||||||
|
queue.Timeleft = queueItem.Timeleft;
|
||||||
|
queued.Add(queue);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return queued;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
8
NzbDrone.Core/Queue/UpdateQueueEvent.cs
Normal file
8
NzbDrone.Core/Queue/UpdateQueueEvent.cs
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
using NzbDrone.Common.Messaging;
|
||||||
|
|
||||||
|
namespace NzbDrone.Core.Queue
|
||||||
|
{
|
||||||
|
public class UpdateQueueEvent : IEvent
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
@ -1,4 +1,4 @@
|
|||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
define(
|
define(
|
||||||
[
|
[
|
||||||
|
@ -3,15 +3,25 @@
|
|||||||
define(
|
define(
|
||||||
[
|
[
|
||||||
'app',
|
'app',
|
||||||
|
'underscore',
|
||||||
'Cells/NzbDroneCell',
|
'Cells/NzbDroneCell',
|
||||||
|
'History/Queue/QueueCollection',
|
||||||
'moment',
|
'moment',
|
||||||
'Shared/FormatHelpers'
|
'Shared/FormatHelpers'
|
||||||
], function (App, NzbDroneCell, Moment, FormatHelpers) {
|
], function (App, _, NzbDroneCell, QueueCollection, Moment, FormatHelpers) {
|
||||||
return NzbDroneCell.extend({
|
return NzbDroneCell.extend({
|
||||||
|
|
||||||
className: 'episode-status-cell',
|
className: 'episode-status-cell',
|
||||||
|
|
||||||
render: function () {
|
render: function () {
|
||||||
|
this.listenTo(QueueCollection, 'sync', this._renderCell);
|
||||||
|
|
||||||
|
this._renderCell();
|
||||||
|
|
||||||
|
return this;
|
||||||
|
},
|
||||||
|
|
||||||
|
_renderCell: function () {
|
||||||
this.$el.empty();
|
this.$el.empty();
|
||||||
|
|
||||||
if (this.model) {
|
if (this.model) {
|
||||||
@ -41,10 +51,17 @@ define(
|
|||||||
this.$el.html('<span class="badge badge-inverse" title="{0}">{1}</span>'.format(title, quality.quality.name));
|
this.$el.html('<span class="badge badge-inverse" title="{0}">{1}</span>'.format(title, quality.quality.name));
|
||||||
}
|
}
|
||||||
|
|
||||||
return this;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
else {
|
else {
|
||||||
if (this.model.get('downloading')) {
|
var model = this.model;
|
||||||
|
|
||||||
|
var downloading = _.find(QueueCollection.models, function (queueModel) {
|
||||||
|
return queueModel.get('episode').id === model.get('id');
|
||||||
|
});
|
||||||
|
|
||||||
|
if (downloading || this.model.get('downloading')) {
|
||||||
icon = 'icon-nd-downloading';
|
icon = 'icon-nd-downloading';
|
||||||
tooltip = 'Episode is downloading';
|
tooltip = 'Episode is downloading';
|
||||||
}
|
}
|
||||||
@ -66,8 +83,6 @@ define(
|
|||||||
|
|
||||||
this.$el.html('<i class="{0}" title="{1}"/>'.format(icon, tooltip));
|
this.$el.html('<i class="{0}" title="{1}"/>'.format(icon, tooltip));
|
||||||
}
|
}
|
||||||
|
|
||||||
return this;
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -73,10 +73,10 @@ define(
|
|||||||
App.mainRegion.show(new MissingLayout());
|
App.mainRegion.show(new MissingLayout());
|
||||||
},
|
},
|
||||||
|
|
||||||
history: function () {
|
history: function (action) {
|
||||||
this._setTitle('History');
|
this._setTitle('History');
|
||||||
|
|
||||||
App.mainRegion.show(new HistoryLayout());
|
App.mainRegion.show(new HistoryLayout({ action: action }));
|
||||||
},
|
},
|
||||||
|
|
||||||
rss: function () {
|
rss: function () {
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
'use strict';
|
'use strict';
|
||||||
define(
|
define(
|
||||||
[
|
[
|
||||||
'History/Model',
|
'History/HistoryModel',
|
||||||
'backbone.pageable'
|
'backbone.pageable'
|
||||||
], function (HistoryModel, PageableCollection) {
|
], function (HistoryModel, PageableCollection) {
|
||||||
return PageableCollection.extend({
|
return PageableCollection.extend({
|
@ -1,108 +1,74 @@
|
|||||||
'use strict';
|
'use strict';
|
||||||
define(
|
define(
|
||||||
[
|
[
|
||||||
|
'app',
|
||||||
'marionette',
|
'marionette',
|
||||||
'backgrid',
|
'backgrid',
|
||||||
'History/Collection',
|
'History/Table/HistoryTableLayout',
|
||||||
'History/EventTypeCell',
|
'History/Queue/QueueLayout'
|
||||||
'Cells/SeriesTitleCell',
|
], function (App,
|
||||||
'Cells/EpisodeNumberCell',
|
Marionette,
|
||||||
'Cells/EpisodeTitleCell',
|
|
||||||
'Cells/QualityCell',
|
|
||||||
'Cells/RelativeDateCell',
|
|
||||||
'History/HistoryDetailsCell',
|
|
||||||
'Shared/Grid/Pager',
|
|
||||||
'Shared/LoadingView'
|
|
||||||
], function (Marionette,
|
|
||||||
Backgrid,
|
Backgrid,
|
||||||
HistoryCollection,
|
HistoryTableLayout,
|
||||||
EventTypeCell,
|
QueueLayout) {
|
||||||
SeriesTitleCell,
|
|
||||||
EpisodeNumberCell,
|
|
||||||
EpisodeTitleCell,
|
|
||||||
QualityCell,
|
|
||||||
RelativeDateCell,
|
|
||||||
HistoryDetailsCell,
|
|
||||||
GridPager,
|
|
||||||
LoadingView) {
|
|
||||||
return Marionette.Layout.extend({
|
return Marionette.Layout.extend({
|
||||||
template: 'History/HistoryLayoutTemplate',
|
template: 'History/HistoryLayoutTemplate',
|
||||||
|
|
||||||
regions: {
|
regions: {
|
||||||
history: '#x-history',
|
history: '#history',
|
||||||
toolbar: '#x-toolbar',
|
queueRegion : '#queue'
|
||||||
pager : '#x-pager'
|
|
||||||
},
|
},
|
||||||
|
|
||||||
columns:
|
ui: {
|
||||||
[
|
historyTab: '.x-history-tab',
|
||||||
{
|
queueTab : '.x-queue-tab'
|
||||||
name : 'eventType',
|
|
||||||
label : '',
|
|
||||||
cell : EventTypeCell,
|
|
||||||
cellValue: 'this'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name : 'series',
|
|
||||||
label: 'Series',
|
|
||||||
cell : SeriesTitleCell
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name : 'episode',
|
|
||||||
label : 'Episode',
|
|
||||||
sortable: false,
|
|
||||||
cell : EpisodeNumberCell
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name : 'episode',
|
|
||||||
label : 'Episode Title',
|
|
||||||
sortable: false,
|
|
||||||
cell : EpisodeTitleCell
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name : 'quality',
|
|
||||||
label : 'Quality',
|
|
||||||
cell : QualityCell,
|
|
||||||
sortable: false
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name : 'date',
|
|
||||||
label: 'Date',
|
|
||||||
cell : RelativeDateCell
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name : 'this',
|
|
||||||
label : '',
|
|
||||||
cell : HistoryDetailsCell,
|
|
||||||
sortable: false
|
|
||||||
}
|
|
||||||
],
|
|
||||||
|
|
||||||
|
|
||||||
initialize: function () {
|
|
||||||
this.collection = new HistoryCollection();
|
|
||||||
this.listenTo(this.collection, 'sync', this._showTable);
|
|
||||||
},
|
},
|
||||||
|
|
||||||
|
events: {
|
||||||
|
'click .x-history-tab' : '_showHistory',
|
||||||
|
'click .x-queue-tab' : '_showQueue'
|
||||||
|
},
|
||||||
|
|
||||||
_showTable: function (collection) {
|
initialize: function (options) {
|
||||||
|
if (options.action) {
|
||||||
this.history.show(new Backgrid.Grid({
|
this.action = options.action.toLowerCase();
|
||||||
columns : this.columns,
|
}
|
||||||
collection: collection,
|
|
||||||
className : 'table table-hover'
|
|
||||||
}));
|
|
||||||
|
|
||||||
this.pager.show(new GridPager({
|
|
||||||
columns : this.columns,
|
|
||||||
collection: collection
|
|
||||||
}));
|
|
||||||
},
|
},
|
||||||
|
|
||||||
onShow: function () {
|
onShow: function () {
|
||||||
this.history.show(new LoadingView());
|
switch (this.action) {
|
||||||
this.collection.fetch();
|
case 'queue':
|
||||||
}
|
this._showQueue();
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
this._showHistory();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
_navigate:function(route){
|
||||||
|
require(['Router'], function(){
|
||||||
|
App.Router.navigate(route);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
_showHistory: function (e) {
|
||||||
|
if (e) {
|
||||||
|
e.preventDefault();
|
||||||
|
}
|
||||||
|
|
||||||
|
this.history.show(new HistoryTableLayout());
|
||||||
|
this.ui.historyTab.tab('show');
|
||||||
|
this._navigate('/history');
|
||||||
|
},
|
||||||
|
|
||||||
|
_showQueue: function (e) {
|
||||||
|
if (e) {
|
||||||
|
e.preventDefault();
|
||||||
|
}
|
||||||
|
|
||||||
|
this.queueRegion.show(new QueueLayout());
|
||||||
|
this.ui.queueTab.tab('show');
|
||||||
|
this._navigate('/history/queue');
|
||||||
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -1,11 +1,9 @@
|
|||||||
<div id="x-toolbar"/>
|
<ul class="nav nav-tabs">
|
||||||
<div class="row">
|
<li><a href="#history" class="x-history-tab no-router">History</a></li>
|
||||||
<div class="span12">
|
<li><a href="#queue" class="x-queue-tab no-router">Queue</a></li>
|
||||||
<div id="x-history"/>
|
</ul>
|
||||||
</div>
|
|
||||||
</div>
|
<div class="tab-content">
|
||||||
<div class="row">
|
<div class="tab-pane" id="history"></div>
|
||||||
<div class="span12">
|
<div class="tab-pane" id="queue"></div>
|
||||||
<div id="x-pager"/>
|
</div>
|
||||||
</div>
|
|
||||||
</div>
|
|
@ -1 +0,0 @@
|
|||||||
<img src="favicon.ico" alt="{{indexer}}"/>
|
|
17
UI/History/Queue/QueueCollection.js
Normal file
17
UI/History/Queue/QueueCollection.js
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
'use strict';
|
||||||
|
define(
|
||||||
|
[
|
||||||
|
'backbone',
|
||||||
|
'History/Queue/QueueModel',
|
||||||
|
'Mixins/backbone.signalr.mixin'
|
||||||
|
], function (Backbone, QueueModel) {
|
||||||
|
var QueueCollection = Backbone.Collection.extend({
|
||||||
|
url : window.NzbDrone.ApiRoot + '/queue',
|
||||||
|
model: QueueModel
|
||||||
|
});
|
||||||
|
|
||||||
|
var collection = new QueueCollection().bindSignalR();
|
||||||
|
collection.fetch();
|
||||||
|
|
||||||
|
return collection;
|
||||||
|
});
|
77
UI/History/Queue/QueueLayout.js
Normal file
77
UI/History/Queue/QueueLayout.js
Normal file
@ -0,0 +1,77 @@
|
|||||||
|
'use strict';
|
||||||
|
define(
|
||||||
|
[
|
||||||
|
'marionette',
|
||||||
|
'backgrid',
|
||||||
|
'History/Queue/QueueCollection',
|
||||||
|
'Cells/SeriesTitleCell',
|
||||||
|
'Cells/EpisodeNumberCell',
|
||||||
|
'Cells/EpisodeTitleCell',
|
||||||
|
'Cells/QualityCell',
|
||||||
|
'History/Queue/TimeleftCell'
|
||||||
|
], function (Marionette,
|
||||||
|
Backgrid,
|
||||||
|
QueueCollection,
|
||||||
|
SeriesTitleCell,
|
||||||
|
EpisodeNumberCell,
|
||||||
|
EpisodeTitleCell,
|
||||||
|
QualityCell,
|
||||||
|
TimeleftCell) {
|
||||||
|
return Marionette.Layout.extend({
|
||||||
|
template: 'History/Queue/QueueLayoutTemplate',
|
||||||
|
|
||||||
|
regions: {
|
||||||
|
table: '#x-queue'
|
||||||
|
},
|
||||||
|
|
||||||
|
columns:
|
||||||
|
[
|
||||||
|
{
|
||||||
|
name : 'series',
|
||||||
|
label: 'Series',
|
||||||
|
cell : SeriesTitleCell
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name : 'episode',
|
||||||
|
label : 'Episode',
|
||||||
|
sortable: false,
|
||||||
|
cell : EpisodeNumberCell
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name : 'episode',
|
||||||
|
label : 'Episode Title',
|
||||||
|
sortable: false,
|
||||||
|
cell : EpisodeTitleCell
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name : 'quality',
|
||||||
|
label : 'Quality',
|
||||||
|
cell : QualityCell,
|
||||||
|
sortable: false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name : 'timeleft',
|
||||||
|
label : 'Timeleft',
|
||||||
|
cell : TimeleftCell,
|
||||||
|
cellValue : 'this'
|
||||||
|
}
|
||||||
|
],
|
||||||
|
|
||||||
|
|
||||||
|
initialize: function () {
|
||||||
|
this.listenTo(QueueCollection, 'sync', this._showTable);
|
||||||
|
},
|
||||||
|
|
||||||
|
onShow: function () {
|
||||||
|
this._showTable();
|
||||||
|
},
|
||||||
|
|
||||||
|
_showTable: function () {
|
||||||
|
this.table.show(new Backgrid.Grid({
|
||||||
|
columns : this.columns,
|
||||||
|
collection: QueueCollection,
|
||||||
|
className : 'table table-hover'
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
5
UI/History/Queue/QueueLayoutTemplate.html
Normal file
5
UI/History/Queue/QueueLayoutTemplate.html
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
<div class="row">
|
||||||
|
<div class="span12">
|
||||||
|
<div id="x-queue"/>
|
||||||
|
</div>
|
||||||
|
</div>
|
16
UI/History/Queue/QueueModel.js
Normal file
16
UI/History/Queue/QueueModel.js
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
'use strict';
|
||||||
|
define(
|
||||||
|
[
|
||||||
|
'backbone',
|
||||||
|
'Series/SeriesModel',
|
||||||
|
'Series/EpisodeModel'
|
||||||
|
], function (Backbone, SeriesModel, EpisodeModel) {
|
||||||
|
return Backbone.Model.extend({
|
||||||
|
parse: function (model) {
|
||||||
|
model.series = new SeriesModel(model.series);
|
||||||
|
model.episode = new EpisodeModel(model.episode);
|
||||||
|
model.episode.set('series', model.series);
|
||||||
|
return model;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
27
UI/History/Queue/TimeleftCell.js
Normal file
27
UI/History/Queue/TimeleftCell.js
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
'use strict';
|
||||||
|
|
||||||
|
define(
|
||||||
|
[
|
||||||
|
'Cells/NzbDroneCell'
|
||||||
|
], function (NzbDroneCell) {
|
||||||
|
return NzbDroneCell.extend({
|
||||||
|
|
||||||
|
className: 'history-event-type-cell',
|
||||||
|
|
||||||
|
render: function () {
|
||||||
|
this.$el.empty();
|
||||||
|
|
||||||
|
if (this.cellValue) {
|
||||||
|
|
||||||
|
var timeleft = this.cellValue.get('timeleft');
|
||||||
|
var size = this.cellValue.get('size');
|
||||||
|
var sizeLeft = this.cellValue.get('sizeLeft');
|
||||||
|
|
||||||
|
this.$el.html(timeleft);
|
||||||
|
this.$el.attr('title', '{0} MB / {1} MB'.format(sizeLeft, size));
|
||||||
|
}
|
||||||
|
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
108
UI/History/Table/HistoryTableLayout.js
Normal file
108
UI/History/Table/HistoryTableLayout.js
Normal file
@ -0,0 +1,108 @@
|
|||||||
|
'use strict';
|
||||||
|
define(
|
||||||
|
[
|
||||||
|
'marionette',
|
||||||
|
'backgrid',
|
||||||
|
'History/HistoryCollection',
|
||||||
|
'History/Table/EventTypeCell',
|
||||||
|
'Cells/SeriesTitleCell',
|
||||||
|
'Cells/EpisodeNumberCell',
|
||||||
|
'Cells/EpisodeTitleCell',
|
||||||
|
'Cells/QualityCell',
|
||||||
|
'Cells/RelativeDateCell',
|
||||||
|
'History/Table/HistoryDetailsCell',
|
||||||
|
'Shared/Grid/Pager',
|
||||||
|
'Shared/LoadingView'
|
||||||
|
], function (Marionette,
|
||||||
|
Backgrid,
|
||||||
|
HistoryCollection,
|
||||||
|
EventTypeCell,
|
||||||
|
SeriesTitleCell,
|
||||||
|
EpisodeNumberCell,
|
||||||
|
EpisodeTitleCell,
|
||||||
|
QualityCell,
|
||||||
|
RelativeDateCell,
|
||||||
|
HistoryDetailsCell,
|
||||||
|
GridPager,
|
||||||
|
LoadingView) {
|
||||||
|
return Marionette.Layout.extend({
|
||||||
|
template: 'History/Table/HistoryTableLayoutTemplate',
|
||||||
|
|
||||||
|
regions: {
|
||||||
|
history: '#x-history',
|
||||||
|
toolbar: '#x-toolbar',
|
||||||
|
pager : '#x-pager'
|
||||||
|
},
|
||||||
|
|
||||||
|
columns:
|
||||||
|
[
|
||||||
|
{
|
||||||
|
name : 'eventType',
|
||||||
|
label : '',
|
||||||
|
cell : EventTypeCell,
|
||||||
|
cellValue: 'this'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name : 'series',
|
||||||
|
label: 'Series',
|
||||||
|
cell : SeriesTitleCell
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name : 'episode',
|
||||||
|
label : 'Episode',
|
||||||
|
sortable: false,
|
||||||
|
cell : EpisodeNumberCell
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name : 'episode',
|
||||||
|
label : 'Episode Title',
|
||||||
|
sortable: false,
|
||||||
|
cell : EpisodeTitleCell
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name : 'quality',
|
||||||
|
label : 'Quality',
|
||||||
|
cell : QualityCell,
|
||||||
|
sortable: false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name : 'date',
|
||||||
|
label: 'Date',
|
||||||
|
cell : RelativeDateCell
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name : 'this',
|
||||||
|
label : '',
|
||||||
|
cell : HistoryDetailsCell,
|
||||||
|
sortable: false
|
||||||
|
}
|
||||||
|
],
|
||||||
|
|
||||||
|
|
||||||
|
initialize: function () {
|
||||||
|
this.collection = new HistoryCollection();
|
||||||
|
this.listenTo(this.collection, 'sync', this._showTable);
|
||||||
|
},
|
||||||
|
|
||||||
|
|
||||||
|
_showTable: function (collection) {
|
||||||
|
|
||||||
|
this.history.show(new Backgrid.Grid({
|
||||||
|
columns : this.columns,
|
||||||
|
collection: collection,
|
||||||
|
className : 'table table-hover'
|
||||||
|
}));
|
||||||
|
|
||||||
|
this.pager.show(new GridPager({
|
||||||
|
columns : this.columns,
|
||||||
|
collection: collection
|
||||||
|
}));
|
||||||
|
},
|
||||||
|
|
||||||
|
onShow: function () {
|
||||||
|
this.history.show(new LoadingView());
|
||||||
|
this.collection.fetch();
|
||||||
|
}
|
||||||
|
|
||||||
|
});
|
||||||
|
});
|
11
UI/History/Table/HistoryTableLayoutTemplate.html
Normal file
11
UI/History/Table/HistoryTableLayoutTemplate.html
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
<div id="x-toolbar"/>
|
||||||
|
<div class="row">
|
||||||
|
<div class="span12">
|
||||||
|
<div id="x-history"/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
<div class="span12">
|
||||||
|
<div id="x-pager"/>
|
||||||
|
</div>
|
||||||
|
</div>
|
@ -13,6 +13,13 @@ define(
|
|||||||
|
|
||||||
var processMessage = function (options) {
|
var processMessage = function (options) {
|
||||||
|
|
||||||
|
if (options.action === 'sync') {
|
||||||
|
console.log('sync received, refetching collection');
|
||||||
|
collection.fetch();
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
var model = new collection.model(options.resource, {parse: true});
|
var model = new collection.model(options.resource, {parse: true});
|
||||||
collection.add(model, {merge: true});
|
collection.add(model, {merge: true});
|
||||||
console.log(options.action + ': {0}}'.format(options.resource));
|
console.log(options.action + ': {0}}'.format(options.resource));
|
||||||
|
@ -8,6 +8,7 @@ require(
|
|||||||
'Series/SeriesCollection',
|
'Series/SeriesCollection',
|
||||||
'ProgressMessaging/ProgressMessageCollection',
|
'ProgressMessaging/ProgressMessageCollection',
|
||||||
'Commands/CommandMessengerCollectionView',
|
'Commands/CommandMessengerCollectionView',
|
||||||
|
'History/Queue/QueueCollection',
|
||||||
'Navbar/NavbarView',
|
'Navbar/NavbarView',
|
||||||
'jQuery/RouteBinder',
|
'jQuery/RouteBinder',
|
||||||
'jquery'
|
'jquery'
|
||||||
@ -18,6 +19,7 @@ require(
|
|||||||
SeriesCollection,
|
SeriesCollection,
|
||||||
ProgressMessageCollection,
|
ProgressMessageCollection,
|
||||||
CommandMessengerCollectionView,
|
CommandMessengerCollectionView,
|
||||||
|
QueueCollection,
|
||||||
NavbarView,
|
NavbarView,
|
||||||
RouterBinder,
|
RouterBinder,
|
||||||
$) {
|
$) {
|
||||||
@ -36,6 +38,7 @@ require(
|
|||||||
'settings/:action(/:query)' : 'settings',
|
'settings/:action(/:query)' : 'settings',
|
||||||
'missing' : 'missing',
|
'missing' : 'missing',
|
||||||
'history' : 'history',
|
'history' : 'history',
|
||||||
|
'history/:action' : 'history',
|
||||||
'rss' : 'rss',
|
'rss' : 'rss',
|
||||||
'system' : 'system',
|
'system' : 'system',
|
||||||
'system/:action' : 'system',
|
'system/:action' : 'system',
|
||||||
|
@ -150,7 +150,6 @@ define(
|
|||||||
},
|
},
|
||||||
|
|
||||||
_renameSeries: function () {
|
_renameSeries: function () {
|
||||||
|
|
||||||
CommandController.Execute('renameSeries', {
|
CommandController.Execute('renameSeries', {
|
||||||
name : 'renameSeries',
|
name : 'renameSeries',
|
||||||
seriesId: this.model.id
|
seriesId: this.model.id
|
||||||
@ -172,7 +171,7 @@ define(
|
|||||||
|
|
||||||
this.seasonCollection = new SeasonCollection(this.model.get('seasons'));
|
this.seasonCollection = new SeasonCollection(this.model.get('seasons'));
|
||||||
this.episodeCollection = new EpisodeCollection({ seriesId: this.model.id }).bindSignalR();
|
this.episodeCollection = new EpisodeCollection({ seriesId: this.model.id }).bindSignalR();
|
||||||
this.episodeFileCollection = new EpisodeFileCollection({ seriesId: this.model.id });
|
this.episodeFileCollection = new EpisodeFileCollection({ seriesId: this.model.id }).bindSignalR();
|
||||||
|
|
||||||
$.when(this.episodeCollection.fetch(), this.episodeFileCollection.fetch()).done(function () {
|
$.when(this.episodeCollection.fetch(), this.episodeFileCollection.fetch()).done(function () {
|
||||||
var seasonCollectionView = new SeasonCollectionView({
|
var seasonCollectionView = new SeasonCollectionView({
|
||||||
|
Loading…
Reference in New Issue
Block a user