Files tab is now present. (#245)

* Adding file info tab. Not finished yet.

* Adding more media info options.

* Deleting files now works. Fixes #127

* Fix button for modifying episode files.

* Get Media Info when running DiskScanService.
pull/247/head
Leonardo Galli 8 years ago committed by Tim Turner
parent 5daece0ed4
commit 29586667cb

@ -0,0 +1,89 @@
using System.Collections.Generic;
using System.IO;
using NLog;
using NzbDrone.Api.REST;
using NzbDrone.Api.Movie;
using NzbDrone.Core.Datastore.Events;
using NzbDrone.Core.MediaFiles;
using NzbDrone.Core.MediaFiles.Events;
using NzbDrone.Core.Messaging.Events;
using NzbDrone.Core.Tv;
using NzbDrone.Core.DecisionEngine;
using NzbDrone.SignalR;
namespace NzbDrone.Api.EpisodeFiles
{
public class MovieFileModule : NzbDroneRestModuleWithSignalR<MovieFileResource, MovieFile>
//IHandle<EpisodeFileAddedEvent>
{
private readonly IMediaFileService _mediaFileService;
private readonly IRecycleBinProvider _recycleBinProvider;
private readonly IMovieService _seriesService;
private readonly IQualityUpgradableSpecification _qualityUpgradableSpecification;
private readonly Logger _logger;
public MovieFileModule(IBroadcastSignalRMessage signalRBroadcaster,
IMediaFileService mediaFileService,
IRecycleBinProvider recycleBinProvider,
IMovieService seriesService,
IQualityUpgradableSpecification qualityUpgradableSpecification,
Logger logger)
: base(signalRBroadcaster)
{
_mediaFileService = mediaFileService;
_recycleBinProvider = recycleBinProvider;
_seriesService = seriesService;
_qualityUpgradableSpecification = qualityUpgradableSpecification;
_logger = logger;
/*GetResourceById = GetEpisodeFile;
GetResourceAll = GetEpisodeFiles;
UpdateResource = SetQuality;*/
DeleteResource = DeleteEpisodeFile;
}
/*private EpisodeFileResource GetEpisodeFile(int id)
{
var episodeFile = _mediaFileService.Get(id);
var series = _seriesService.GetSeries(episodeFile.SeriesId);
return episodeFile.ToResource(series, _qualityUpgradableSpecification);
}
private List<EpisodeFileResource> GetEpisodeFiles()
{
if (!Request.Query.SeriesId.HasValue)
{
throw new BadRequestException("seriesId is missing");
}
var seriesId = (int)Request.Query.SeriesId;
var series = _seriesService.GetSeries(seriesId);
return _mediaFileService.GetFilesBySeries(seriesId).ConvertAll(f => f.ToResource(series, _qualityUpgradableSpecification));
}
private void SetQuality(EpisodeFileResource episodeFileResource)
{
var episodeFile = _mediaFileService.Get(episodeFileResource.Id);
episodeFile.Quality = episodeFileResource.Quality;
_mediaFileService.Update(episodeFile);
}*/
private void DeleteEpisodeFile(int id)
{
var episodeFile = _mediaFileService.GetMovie(id);
var series = _seriesService.GetMovie(episodeFile.MovieId);
var fullPath = Path.Combine(series.Path, episodeFile.RelativePath);
_logger.Info("Deleting episode file: {0}", fullPath);
_recycleBinProvider.DeleteFile(fullPath);
_mediaFileService.Delete(episodeFile, DeleteMediaFileReason.Manual);
}
public void Handle(EpisodeFileAddedEvent message)
{
BroadcastResourceChange(ModelAction.Updated, message.EpisodeFile.Id);
}
}
}

@ -116,6 +116,7 @@
<Compile Include="Frontend\Mappers\RobotsTxtMapper.cs" />
<Compile Include="Indexers\ReleaseModuleBase.cs" />
<Compile Include="Indexers\ReleasePushModule.cs" />
<Compile Include="Movies\MovieFileModule.cs" />
<Compile Include="Movies\MovieModule.cs" />
<Compile Include="Movies\RenameMovieModule.cs" />
<Compile Include="Movies\RenameMovieResource.cs" />

@ -30,8 +30,8 @@ namespace NzbDrone.Api.Movie
public string ReleaseGroup { get; set; }
public QualityModel Quality { get; set; }
public MovieResource Movie { get; set; }
public string Edition { get; set; }
public Core.MediaFiles.MediaInfo.MediaInfoModel MediaInfo { get; set; }
//TODO: Add series statistics as a property of the series (instead of individual properties)
}
@ -63,7 +63,8 @@ namespace NzbDrone.Api.Movie
ReleaseGroup = model.ReleaseGroup,
Quality = model.Quality,
Movie = movie,
MediaInfo = model.MediaInfo,
Edition = model.Edition
};
}

@ -0,0 +1,45 @@
using System.Data;
using FluentMigrator;
using NzbDrone.Core.Datastore.Migration.Framework;
namespace NzbDrone.Core.Datastore.Migration
{
[Migration(117)]
public class update_movie_file : NzbDroneMigrationBase
{
protected override void MainDbUpgrade()
{
Create.Column("Edition").OnTable("MovieFiles").AsString().Nullable();
Execute.WithConnection(SetSortTitles);
}
private void SetSortTitles(IDbConnection conn, IDbTransaction tran)
{
using (IDbCommand getSeriesCmd = conn.CreateCommand())
{
getSeriesCmd.Transaction = tran;
getSeriesCmd.CommandText = @"SELECT Id, RelativePath FROM MovieFiles";
using (IDataReader seriesReader = getSeriesCmd.ExecuteReader())
{
while (seriesReader.Read())
{
var id = seriesReader.GetInt32(0);
var relativePath = seriesReader.GetString(1);
var edition = Parser.Parser.ParseMovieTitle(relativePath).Edition;
using (IDbCommand updateCmd = conn.CreateCommand())
{
updateCmd.Transaction = tran;
updateCmd.CommandText = "UPDATE MovieFiles SET Edition = ? WHERE Id = ?";
updateCmd.AddParameter(edition);
updateCmd.AddParameter(id);
updateCmd.ExecuteNonQuery();
}
}
}
}
}
}
}

@ -83,6 +83,7 @@ namespace NzbDrone.Core.MediaFiles.EpisodeImport
episodeFile.MediaInfo = localMovie.MediaInfo;
episodeFile.Movie = localMovie.Movie;
episodeFile.ReleaseGroup = localMovie.ParsedMovieInfo.ReleaseGroup;
episodeFile.Edition = localMovie.ParsedMovieInfo.Edition;
bool copyOnly;
switch (importMode)

@ -57,7 +57,7 @@ namespace NzbDrone.Core.MediaFiles.EpisodeImport
public List<ImportDecision> GetImportDecisions(List<string> videoFiles, Movie movie)
{
return GetImportDecisions(videoFiles, movie, null, false);
return GetImportDecisions(videoFiles, movie, null, true);
}
public List<ImportDecision> GetImportDecisions(List<string> videoFiles, Series series, ParsedEpisodeInfo folderInfo, bool sceneSource)

@ -26,6 +26,7 @@ namespace NzbDrone.Core.MediaFiles
List<string> FilterExistingFiles(List<string> files, Series series);
List<string> FilterExistingFiles(List<string> files, Movie movie);
EpisodeFile Get(int id);
MovieFile GetMovie(int id);
List<EpisodeFile> Get(IEnumerable<int> ids);
List<MovieFile> GetMovies(IEnumerable<int> ids);
@ -150,5 +151,9 @@ namespace NzbDrone.Core.MediaFiles
_movieFileRepository.Update(episodeFile);
}
public MovieFile GetMovie(int id)
{
return _movieFileRepository.Get(id);
}
}
}

@ -19,6 +19,7 @@ namespace NzbDrone.Core.MediaFiles
public string ReleaseGroup { get; set; }
public QualityModel Quality { get; set; }
public MediaInfoModel MediaInfo { get; set; }
public string Edition { get; set; }
public LazyLoaded<Movie> Movie { get; set; }
public override string ToString()

@ -183,6 +183,7 @@
<Compile Include="Datastore\Migration\002_remove_tvrage_imdb_unique_constraint.cs" />
<Compile Include="Datastore\Migration\003_remove_clean_title_from_scene_mapping.cs" />
<Compile Include="Datastore\Migration\004_updated_history.cs" />
<Compile Include="Datastore\Migration\117_update_movie_file.cs" />
<Compile Include="Datastore\Migration\116_update_movie_sorttitle_again.cs" />
<Compile Include="Datastore\Migration\115_update_movie_sorttitle.cs" />
<Compile Include="Datastore\Migration\111_remove_bitmetv.cs" />

@ -0,0 +1,15 @@
var NzbDroneCell = require('./NzbDroneCell');
module.exports = NzbDroneCell.extend({
className : 'file-title-cell',
render : function() {
this.$el.empty();
var title = this.model.get('relativePath');
this.$el.html(title);
return this;
}
});

@ -0,0 +1,23 @@
var NzbDroneCell = require('./NzbDroneCell');
module.exports = NzbDroneCell.extend({
className : 'release-title-cell',
render : function() {
this.$el.empty();
var info = this.model.get('mediaInfo');
if (info) {
var runtime = info.runTime;
if (runtime) {
runtime = runtime.split(".")[0];
}
var video = "{0} ({1}x{2}) ({3})".format(info.videoCodec, info.width, info.height, runtime);
var audio = "{0} ({1})".format(info.audioFormat, info.audioLanguages);
this.$el.html(video + " " + audio);
}
return this;
}
});

@ -1,5 +1,5 @@
{{#if_gt proper compare="1"}}
<span class="badge badge-info" title="PROPER">{{quality.name}}</span>
{{else}}
<span class="badge">{{quality.name}}</span>
{{/if_gt}}
<span class="badge" title="{{#if hardcodedSubs}}Warning: {{hardcodedSubs}}{{/if}}">{{quality.name}}</span>
{{/if_gt}}

@ -17,4 +17,4 @@ module.exports = NzbDroneCell.extend({
return this;
}
});
});

@ -11,6 +11,7 @@ var LoadingView = require('../../Shared/LoadingView');
var EpisodeFileEditorLayout = require('../../EpisodeFile/Editor/EpisodeFileEditorLayout');
var HistoryLayout = require('../History/MovieHistoryLayout');
var SearchLayout = require('../Search/MovieSearchLayout');
var FilesLayout = require("../Files/FilesLayout");
require('backstrech');
require('../../Mixins/backbone.signalr.mixin');
@ -22,7 +23,8 @@ module.exports = Marionette.Layout.extend({
seasons : '#seasons',
info : '#info',
search : '#movie-search',
history : '#movie-history'
history : '#movie-history',
files : "#movie-files"
},
@ -36,11 +38,12 @@ module.exports = Marionette.Layout.extend({
poster : '.x-movie-poster',
manualSearch : '.x-manual-search',
history : '.x-movie-history',
search : '.x-movie-search'
search : '.x-movie-search',
files : ".x-movie-files"
},
events : {
'click .x-episode-file-editor' : '_openEpisodeFileEditor',
'click .x-episode-file-editor' : '_showFiles',
'click .x-monitored' : '_toggleMonitored',
'click .x-edit' : '_editMovie',
'click .x-refresh' : '_refreshMovies',
@ -48,7 +51,8 @@ module.exports = Marionette.Layout.extend({
'click .x-search' : '_moviesSearch',
'click .x-manual-search' : '_showSearch',
'click .x-movie-history' : '_showHistory',
'click .x-movie-search' : '_showSearch'
'click .x-movie-search' : '_showSearch',
"click .x-movie-files" : "_showFiles",
},
initialize : function() {
@ -72,11 +76,18 @@ module.exports = Marionette.Layout.extend({
this.searchLayout = new SearchLayout({ model : this.model });
this.searchLayout.startManualSearch = true;
this.filesLayout = new FilesLayout({ model : this.model });
this._showBackdrop();
this._showSeasons();
this._setMonitoredState();
this._showInfo();
this._showHistory();
if (this.model.get("movieFile")) {
this._showFiles()
} else {
this._showHistory();
}
},
onRender : function() {
@ -144,6 +155,15 @@ module.exports = Marionette.Layout.extend({
this.search.show(this.searchLayout);
},
_showFiles : function(e) {
if (e) {
e.preventDefault();
}
this.ui.files.tab('show');
this.files.show(this.filesLayout);
},
_toggleMonitored : function() {
var savePromise = this.model.save('monitored', !this.model.get('monitored'), { wait : true });

@ -9,7 +9,7 @@
{{title}}
<div class="movie-actions pull-right">
<div class="x-episode-file-editor">
<i class="icon-sonarr-episode-file" title="Modify episode files for movie"/>
<i class="icon-sonarr-episode-file" title="Modify movie files"/>
</div>
<div class="x-refresh">
<i class="icon-sonarr-refresh icon-can-spin" title="Update movie info and scan disk"/>
@ -42,10 +42,12 @@
<ul class="nav nav-tabs" id="myTab">
<li><a href="#movie-history" class="x-movie-history">History</a></li>
<li><a href="#movie-search" class="x-movie-search">Search</a></li>
<li><a href="#movie-files" class="x-movie-files">Files</a></li>
</ul>
<div class="tab-content">
<div class="tab-pane" id="movie-history"/>
<div class="tab-pane" id="movie-search"/>
<div class="tab-pane" id="movie-files"/>
</div>
</div>
</div>

@ -0,0 +1,26 @@
var vent = require('vent');
var Backgrid = require('backgrid');
module.exports = Backgrid.Cell.extend({
className : 'delete-episode-file-cell',
events : {
'click' : '_onClick'
},
render : function() {
this.$el.empty();
this.$el.html('<i class="icon-sonarr-delete" title="Delete movie file from disk"></i>');
return this;
},
_onClick : function() {
var self = this;
if (window.confirm('Are you sure you want to delete \'{0}\' from disk?'.format(this.model.get('relativePath')))) {
this.model.destroy().done(function() {
vent.trigger(vent.Events.MovieFileDeleted, { movieFile : self.model });
});
}
}
});

@ -0,0 +1,3 @@
var Backbone = require('backbone');
module.exports = Backbone.Model.extend({});

@ -0,0 +1,30 @@
var PagableCollection = require('backbone.pageable');
var FileModel = require('./FileModel');
var AsSortedCollection = require('../../Mixins/AsSortedCollection');
var Collection = PagableCollection.extend({
url : window.NzbDrone.ApiRoot + "/moviefile",
model : FileModel,
state : {
pageSize : 2000,
sortKey : 'title',
order : -1
},
mode : 'client',
sortMappings : {
'quality' : {
sortKey : "qualityWeight"
},
"edition" : {
sortKey : "edition"
}
},
});
Collection = AsSortedCollection.call(Collection);
module.exports = Collection;

@ -0,0 +1,107 @@
var vent = require('vent');
var Marionette = require('marionette');
var Backgrid = require('backgrid');
//var ButtonsView = require('./ButtonsView');
//var ManualSearchLayout = require('./ManualLayout');
var FilesCollection = require('./FilesCollection');
var CommandController = require('../../Commands/CommandController');
var LoadingView = require('../../Shared/LoadingView');
var NoResultsView = require('./NoFilesView');
var FileModel = require("./FileModel");
var FileTitleCell = require('../../Cells/FileTitleCell');
var FileSizeCell = require('../../Cells/FileSizeCell');
var QualityCell = require('../../Cells/QualityCell');
var MediaInfoCell = require('../../Cells/MediaInfoCell');
var ApprovalStatusCell = require('../../Cells/ApprovalStatusCell');
var DownloadReportCell = require('../../Release/DownloadReportCell');
var AgeCell = require('../../Release/AgeCell');
var ProtocolCell = require('../../Release/ProtocolCell');
var PeersCell = require('../../Release/PeersCell');
var EditionCell = require('../../Cells/EditionCell');
var DeleteFileCell = require("./DeleteFileCell");
module.exports = Marionette.Layout.extend({
template : 'Movies/Files/FilesLayoutTemplate',
regions : {
main : '#movie-files-region',
grid : "#movie-files-grid"
},
events : {
'click .x-search-auto' : '_searchAuto',
'click .x-search-manual' : '_searchManual',
'click .x-search-back' : '_showButtons'
},
columns : [
{
name : 'title',
label : 'Title',
cell : FileTitleCell
},
{
name : "mediaInfo",
label : "Media Info",
cell : MediaInfoCell
},
{
name : 'edition',
label : 'Edition',
cell : EditionCell,
title : "Edition",
},
{
name : 'size',
label : 'Size',
cell : FileSizeCell
},
{
name : 'quality',
label : 'Quality',
cell : QualityCell,
},
{
name : "delete",
label : "",
cell : DeleteFileCell,
}
],
initialize : function(movie) {
this.filesCollection = new FilesCollection();
var file = movie.model.get("movieFile");
this.filesCollection.add(file);
//this.listenTo(this.releaseCollection, 'sync', this._showSearchResults);
},
onShow : function() {
this.grid.show(new Backgrid.Grid({
row : Backgrid.Row,
columns : this.columns,
collection : this.filesCollection,
className : 'table table-hover'
}));
},
_showMainView : function() {
this.main.show(this.mainView);
},
_showButtons : function() {
this._showMainView();
},
_showSearchResults : function() {
if (this.releaseCollection.length === 0) {
this.mainView = new NoResultsView();
}
else {
//this.mainView = new ManualSearchLayout({ collection : this.releaseCollection });
}
this._showMainView();
}
});

@ -0,0 +1 @@
<div id="movie-files-region"><div id="movie-files-grid" class="table-responsive"></div></div>

@ -0,0 +1,5 @@
var Marionette = require('marionette');
module.exports = Marionette.ItemView.extend({
template : 'Movies/Files/NoFilesViewTemplate'
});

@ -0,0 +1,3 @@
<p class="text-warning">
No files for this movie.
</p>
Loading…
Cancel
Save