From 32c75cfcbc8fd35e812d3eb41fb08667c3db127d Mon Sep 17 00:00:00 2001 From: ta264 Date: Mon, 19 Nov 2018 03:16:55 +0000 Subject: [PATCH] New: Manual import refreshes decisions when artist/album updated (#540) --- frontend/src/Components/SignalRConnector.js | 10 +++ .../Album/SelectAlbumModalContentConnector.js | 8 ++- .../SelectArtistModalContentConnector.js | 9 ++- .../Interactive/InteractiveImportRow.js | 1 + .../Track/SelectTrackModalContent.js | 9 ++- .../Store/Actions/interactiveImportActions.js | 6 ++ src/Lidarr.Api.V1/Lidarr.Api.V1.csproj | 1 + .../ManualImport/ManualImportModule.cs | 43 +++++++++++-- .../ManualImportModuleWithSignalR.cs | 45 ++++++++++++++ .../ManualImport/ManualImportResource.cs | 2 +- .../TrackImport/ImportDecisionMakerFixture.cs | 28 ++++----- .../TrackImport/ImportDecisionMaker.cs | 13 ++-- .../TrackImport/Manual/ManualImportItem.cs | 3 +- .../TrackImport/Manual/ManualImportService.cs | 61 +++++++++++++++++-- src/NzbDrone.Core/Parser/ParsingService.cs | 15 ++++- 15 files changed, 211 insertions(+), 43 deletions(-) create mode 100644 src/Lidarr.Api.V1/ManualImport/ManualImportModuleWithSignalR.cs diff --git a/frontend/src/Components/SignalRConnector.js b/frontend/src/Components/SignalRConnector.js index 72bc1d893..06c0cc0c5 100644 --- a/frontend/src/Components/SignalRConnector.js +++ b/frontend/src/Components/SignalRConnector.js @@ -224,6 +224,16 @@ class SignalRConnector extends Component { } } + handleManualimport = (body) => { + if (body.action === 'updated') { + this.props.dispatchUpdateItem({ + section: 'interactiveImport', + updateOnly: true, + ...body.resource + }); + } + } + handleQueue = () => { if (this.props.isQueuePopulated) { this.props.dispatchFetchQueue(); diff --git a/frontend/src/InteractiveImport/Album/SelectAlbumModalContentConnector.js b/frontend/src/InteractiveImport/Album/SelectAlbumModalContentConnector.js index 6a448be6e..494525a2d 100644 --- a/frontend/src/InteractiveImport/Album/SelectAlbumModalContentConnector.js +++ b/frontend/src/InteractiveImport/Album/SelectAlbumModalContentConnector.js @@ -5,6 +5,7 @@ import { connect } from 'react-redux'; import { createSelector } from 'reselect'; import { updateInteractiveImportItem, + saveInteractiveImportItem, fetchInteractiveImportAlbums, setInteractiveImportAlbumsSort, clearInteractiveImportAlbums @@ -25,7 +26,8 @@ const mapDispatchToProps = { fetchInteractiveImportAlbums, setInteractiveImportAlbumsSort, clearInteractiveImportAlbums, - updateInteractiveImportItem + updateInteractiveImportItem, + saveInteractiveImportItem }; class SelectAlbumModalContentConnector extends Component { @@ -61,8 +63,10 @@ class SelectAlbumModalContentConnector extends Component { this.props.updateInteractiveImportItem({ id, album, - tracks: [] + tracks: [], + rejections: [] }); + this.props.saveInteractiveImportItem({ id }); }); this.props.onModalClose(true); diff --git a/frontend/src/InteractiveImport/Artist/SelectArtistModalContentConnector.js b/frontend/src/InteractiveImport/Artist/SelectArtistModalContentConnector.js index 04e87f3c7..4fbedb64e 100644 --- a/frontend/src/InteractiveImport/Artist/SelectArtistModalContentConnector.js +++ b/frontend/src/InteractiveImport/Artist/SelectArtistModalContentConnector.js @@ -3,7 +3,7 @@ import PropTypes from 'prop-types'; import React, { Component } from 'react'; import { connect } from 'react-redux'; import { createSelector } from 'reselect'; -import { updateInteractiveImportItem } from 'Store/Actions/interactiveImportActions'; +import { updateInteractiveImportItem, saveInteractiveImportItem } from 'Store/Actions/interactiveImportActions'; import createAllArtistSelector from 'Store/Selectors/createAllArtistSelector'; import SelectArtistModalContent from './SelectArtistModalContent'; @@ -29,7 +29,8 @@ function createMapStateToProps() { } const mapDispatchToProps = { - updateInteractiveImportItem + updateInteractiveImportItem, + saveInteractiveImportItem }; class SelectArtistModalContentConnector extends Component { @@ -45,8 +46,10 @@ class SelectArtistModalContentConnector extends Component { id, artist, album: undefined, - tracks: [] + tracks: [], + rejections: [] }); + this.props.saveInteractiveImportItem({ id }); }); this.props.onModalClose(true); diff --git a/frontend/src/InteractiveImport/Interactive/InteractiveImportRow.js b/frontend/src/InteractiveImport/Interactive/InteractiveImportRow.js index 2d061e4c5..d6c5cdb3e 100644 --- a/frontend/src/InteractiveImport/Interactive/InteractiveImportRow.js +++ b/frontend/src/InteractiveImport/Interactive/InteractiveImportRow.js @@ -324,6 +324,7 @@ class InteractiveImportRow extends Component { id={id} artistId={artist && artist.id} albumId={album && album.id} + filename={relativePath} onModalClose={this.onSelectTrackModalClose} /> diff --git a/frontend/src/InteractiveImport/Track/SelectTrackModalContent.js b/frontend/src/InteractiveImport/Track/SelectTrackModalContent.js index eda1e2bfb..cfec2c163 100644 --- a/frontend/src/InteractiveImport/Track/SelectTrackModalContent.js +++ b/frontend/src/InteractiveImport/Track/SelectTrackModalContent.js @@ -87,7 +87,8 @@ class SelectTrackModalContent extends Component { sortKey, sortDirection, onSortPress, - onModalClose + onModalClose, + filename } = this.props; const { @@ -96,12 +97,13 @@ class SelectTrackModalContent extends Component { selectedState } = this.state; + const title = `Manual Import - Select Track(s): ${filename}`; const errorMessage = getErrorMessage(error, 'Unable to load tracks'); return ( - Manual Import - Select Track(s) + {title} @@ -179,7 +181,8 @@ SelectTrackModalContent.propTypes = { sortDirection: PropTypes.string, onSortPress: PropTypes.func.isRequired, onTracksSelect: PropTypes.func.isRequired, - onModalClose: PropTypes.func.isRequired + onModalClose: PropTypes.func.isRequired, + filename: PropTypes.string.isRequired }; export default SelectTrackModalContent; diff --git a/frontend/src/Store/Actions/interactiveImportActions.js b/frontend/src/Store/Actions/interactiveImportActions.js index b0cf0b3a4..03e1307bb 100644 --- a/frontend/src/Store/Actions/interactiveImportActions.js +++ b/frontend/src/Store/Actions/interactiveImportActions.js @@ -8,6 +8,7 @@ import { sortDirections } from 'Helpers/Props'; import createSetClientSideCollectionSortReducer from './Creators/Reducers/createSetClientSideCollectionSortReducer'; import createFetchHandler from './Creators/createFetchHandler'; import createHandleActions from './Creators/createHandleActions'; +import createSaveProviderHandler from './Creators/createSaveProviderHandler'; import { set, update } from './baseActions'; // @@ -25,6 +26,7 @@ export const defaultState = { isPopulated: false, error: null, items: [], + pendingChanges: {}, sortKey: 'quality', sortDirection: sortDirections.DESCENDING, recentFolders: [], @@ -67,6 +69,7 @@ export const persistState = [ export const FETCH_INTERACTIVE_IMPORT_ITEMS = 'FETCH_INTERACTIVE_IMPORT_ITEMS'; export const UPDATE_INTERACTIVE_IMPORT_ITEM = 'UPDATE_INTERACTIVE_IMPORT_ITEM'; +export const SAVE_INTERACTIVE_IMPORT_ITEM = 'SAVE_INTERACTIVE_IMPORT_ITEM'; export const SET_INTERACTIVE_IMPORT_SORT = 'SET_INTERACTIVE_IMPORT_SORT'; export const CLEAR_INTERACTIVE_IMPORT = 'CLEAR_INTERACTIVE_IMPORT'; export const ADD_RECENT_FOLDER = 'ADD_RECENT_FOLDER'; @@ -83,6 +86,7 @@ export const CLEAR_INTERACTIVE_IMPORT_ALBUMS = 'CLEAR_INTERACTIVE_IMPORT_ALBUMS' export const fetchInteractiveImportItems = createThunk(FETCH_INTERACTIVE_IMPORT_ITEMS); export const setInteractiveImportSort = createAction(SET_INTERACTIVE_IMPORT_SORT); export const updateInteractiveImportItem = createAction(UPDATE_INTERACTIVE_IMPORT_ITEM); +export const saveInteractiveImportItem = createThunk(SAVE_INTERACTIVE_IMPORT_ITEM); export const clearInteractiveImport = createAction(CLEAR_INTERACTIVE_IMPORT); export const addRecentFolder = createAction(ADD_RECENT_FOLDER); export const removeRecentFolder = createAction(REMOVE_RECENT_FOLDER); @@ -131,6 +135,8 @@ export const actionHandlers = handleThunks({ }); }, + [SAVE_INTERACTIVE_IMPORT_ITEM]: createSaveProviderHandler(section, '/manualimport'), + [FETCH_INTERACTIVE_IMPORT_ALBUMS]: createFetchHandler('interactiveImport.albums', '/album') }); diff --git a/src/Lidarr.Api.V1/Lidarr.Api.V1.csproj b/src/Lidarr.Api.V1/Lidarr.Api.V1.csproj index c3aeb676b..69a99b169 100644 --- a/src/Lidarr.Api.V1/Lidarr.Api.V1.csproj +++ b/src/Lidarr.Api.V1/Lidarr.Api.V1.csproj @@ -115,6 +115,7 @@ + diff --git a/src/Lidarr.Api.V1/ManualImport/ManualImportModule.cs b/src/Lidarr.Api.V1/ManualImport/ManualImportModule.cs index c59b259ef..faec43ecc 100644 --- a/src/Lidarr.Api.V1/ManualImport/ManualImportModule.cs +++ b/src/Lidarr.Api.V1/ManualImport/ManualImportModule.cs @@ -4,20 +4,30 @@ using NzbDrone.Core.MediaFiles.TrackImport.Manual; using NzbDrone.Core.Qualities; using Lidarr.Http; using Lidarr.Http.Extensions; - +using NzbDrone.SignalR; +using NzbDrone.Core.Datastore.Events; +using NzbDrone.Core.Music; +using NLog; namespace Lidarr.Api.V1.ManualImport { - public class ManualImportModule : LidarrRestModule + public class ManualImportModule : ManualImportModuleWithSignalR { - private readonly IManualImportService _manualImportService; + private readonly IArtistService _artistService; + private readonly IAlbumService _albumService; - public ManualImportModule(IManualImportService manualImportService) - : base("/manualimport") + public ManualImportModule(IManualImportService manualImportService, + IArtistService artistService, + IAlbumService albumService, + IBroadcastSignalRMessage signalRBroadcaster, + Logger logger) + : base(manualImportService, signalRBroadcaster, logger) { - _manualImportService = manualImportService; + _albumService = albumService; + _artistService = artistService; GetResourceAll = GetMediaFiles; + UpdateResource = UpdateImportItem; } private List GetMediaFiles() @@ -40,5 +50,26 @@ namespace Lidarr.Api.V1.ManualImport return item; } + + private void UpdateImportItem(ManualImportResource resource) + { + var item = new ManualImportItem{ + Id = resource.Id, + Path = resource.Path, + RelativePath = resource.RelativePath, + FolderName = resource.FolderName, + Name = resource.Name, + Size = resource.Size, + Artist = resource.Artist == null ? null : _artistService.GetArtist(resource.Artist.Id), + Album = resource.Album == null ? null : _albumService.GetAlbum(resource.Album.Id), + Quality = resource.Quality, + Language = resource.Language, + DownloadId = resource.DownloadId + }; + + //recalculate import and broadcast + _manualImportService.UpdateItem(item); + BroadcastResourceChange(ModelAction.Updated, item.Id); + } } } diff --git a/src/Lidarr.Api.V1/ManualImport/ManualImportModuleWithSignalR.cs b/src/Lidarr.Api.V1/ManualImport/ManualImportModuleWithSignalR.cs new file mode 100644 index 000000000..12a9cd6f6 --- /dev/null +++ b/src/Lidarr.Api.V1/ManualImport/ManualImportModuleWithSignalR.cs @@ -0,0 +1,45 @@ +using System.Collections.Generic; +using System.Linq; +using NzbDrone.Core.MediaFiles.TrackImport.Manual; +using NzbDrone.Core.Qualities; +using Lidarr.Http; +using Lidarr.Http.Extensions; +using NzbDrone.SignalR; +using NLog; + +namespace Lidarr.Api.V1.ManualImport +{ + public abstract class ManualImportModuleWithSignalR : LidarrRestModuleWithSignalR + { + protected readonly IManualImportService _manualImportService; + protected readonly Logger _logger; + + protected ManualImportModuleWithSignalR(IManualImportService manualImportService, + IBroadcastSignalRMessage signalRBroadcaster, + Logger logger) + : base(signalRBroadcaster) + { + _manualImportService = manualImportService; + _logger = logger; + + GetResourceById = GetManualImportItem; + } + + protected ManualImportModuleWithSignalR(IManualImportService manualImportService, + IBroadcastSignalRMessage signalRBroadcaster, + Logger logger, + string resource) + : base(signalRBroadcaster, resource) + { + _manualImportService = manualImportService; + _logger = logger; + + GetResourceById = GetManualImportItem; + } + + protected ManualImportResource GetManualImportItem(int id) + { + return _manualImportService.Find(id).ToResource(); + } + } +} diff --git a/src/Lidarr.Api.V1/ManualImport/ManualImportResource.cs b/src/Lidarr.Api.V1/ManualImport/ManualImportResource.cs index 18e901973..f09656fc3 100644 --- a/src/Lidarr.Api.V1/ManualImport/ManualImportResource.cs +++ b/src/Lidarr.Api.V1/ManualImport/ManualImportResource.cs @@ -37,7 +37,7 @@ namespace Lidarr.Api.V1.ManualImport return new ManualImportResource { - Id = HashConverter.GetHashInt31(model.Path), + Id = model.Id, Path = model.Path, RelativePath = model.RelativePath, FolderName = model.FolderName, diff --git a/src/NzbDrone.Core.Test/MediaFiles/TrackImport/ImportDecisionMakerFixture.cs b/src/NzbDrone.Core.Test/MediaFiles/TrackImport/ImportDecisionMakerFixture.cs index ac6be3a07..cbd2d3c02 100644 --- a/src/NzbDrone.Core.Test/MediaFiles/TrackImport/ImportDecisionMakerFixture.cs +++ b/src/NzbDrone.Core.Test/MediaFiles/TrackImport/ImportDecisionMakerFixture.cs @@ -71,7 +71,7 @@ namespace NzbDrone.Core.Test.MediaFiles.TrackImport }; Mocker.GetMock() - .Setup(c => c.GetLocalTrack(It.IsAny(), It.IsAny(), It.IsAny())) + .Setup(c => c.GetLocalTrack(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())) .Returns(_localTrack); GivenVideoFiles(new List { @"C:\Test\Unsorted\The.Office.S03E115.DVDRip.Spanish.XviD-OSiTV.avi".AsOsAgnostic() }); @@ -151,7 +151,7 @@ namespace NzbDrone.Core.Test.MediaFiles.TrackImport GivenSpecifications(_pass1); Mocker.GetMock() - .Setup(c => c.GetLocalTrack(It.IsAny(), It.IsAny(), It.IsAny())) + .Setup(c => c.GetLocalTrack(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())) .Throws(); _audioFiles = new List @@ -166,7 +166,7 @@ namespace NzbDrone.Core.Test.MediaFiles.TrackImport Subject.GetImportDecisions(_audioFiles, _artist); Mocker.GetMock() - .Verify(c => c.GetLocalTrack(It.IsAny(), It.IsAny(), It.IsAny()), Times.Exactly(_audioFiles.Count)); + .Verify(c => c.GetLocalTrack(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny()), Times.Exactly(_audioFiles.Count)); ExceptionVerification.ExpectedErrors(3); } @@ -260,7 +260,7 @@ namespace NzbDrone.Core.Test.MediaFiles.TrackImport GivenSpecifications(_pass1); Mocker.GetMock() - .Setup(c => c.GetLocalTrack(It.IsAny(), It.IsAny(), It.IsAny())) + .Setup(c => c.GetLocalTrack(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())) .Returns(new LocalTrack() { Path = "test" }); _audioFiles = new List @@ -275,7 +275,7 @@ namespace NzbDrone.Core.Test.MediaFiles.TrackImport var decisions = Subject.GetImportDecisions(_audioFiles, _artist); Mocker.GetMock() - .Verify(c => c.GetLocalTrack(It.IsAny(), It.IsAny(), It.IsAny()), Times.Exactly(_audioFiles.Count)); + .Verify(c => c.GetLocalTrack(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny()), Times.Exactly(_audioFiles.Count)); decisions.Should().HaveCount(3); decisions.First().Rejections.Should().NotBeEmpty(); @@ -299,10 +299,10 @@ namespace NzbDrone.Core.Test.MediaFiles.TrackImport Subject.GetImportDecisions(_audioFiles, _artist, folderInfo); Mocker.GetMock() - .Verify(c => c.GetLocalTrack(It.IsAny(), It.IsAny(), null), Times.Exactly(3)); + .Verify(c => c.GetLocalTrack(It.IsAny(), It.IsAny(), It.IsAny(), null), Times.Exactly(3)); Mocker.GetMock() - .Verify(c => c.GetLocalTrack(It.IsAny(), It.IsAny(), It.Is(p => p != null)), Times.Never()); + .Verify(c => c.GetLocalTrack(It.IsAny(), It.IsAny(), It.IsAny(), It.Is(p => p != null)), Times.Never()); } [Test] @@ -322,10 +322,10 @@ namespace NzbDrone.Core.Test.MediaFiles.TrackImport Subject.GetImportDecisions(_audioFiles, _artist, folderInfo); Mocker.GetMock() - .Verify(c => c.GetLocalTrack(It.IsAny(), It.IsAny(), null), Times.Exactly(2)); + .Verify(c => c.GetLocalTrack(It.IsAny(), It.IsAny(), It.IsAny(), null), Times.Exactly(2)); Mocker.GetMock() - .Verify(c => c.GetLocalTrack(It.IsAny(), It.IsAny(), It.Is(p => p != null)), Times.Never()); + .Verify(c => c.GetLocalTrack(It.IsAny(), It.IsAny(), It.IsAny(), It.Is(p => p != null)), Times.Never()); } [Test] @@ -344,10 +344,10 @@ namespace NzbDrone.Core.Test.MediaFiles.TrackImport Subject.GetImportDecisions(_audioFiles, _artist, folderInfo); Mocker.GetMock() - .Verify(c => c.GetLocalTrack(It.IsAny(), It.IsAny(), It.IsAny()), Times.Exactly(1)); + .Verify(c => c.GetLocalTrack(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny()), Times.Exactly(1)); Mocker.GetMock() - .Verify(c => c.GetLocalTrack(It.IsAny(), It.IsAny(), null), Times.Never()); + .Verify(c => c.GetLocalTrack(It.IsAny(), It.IsAny(), It.IsAny(), null), Times.Never()); } [Test] @@ -366,10 +366,10 @@ namespace NzbDrone.Core.Test.MediaFiles.TrackImport Subject.GetImportDecisions(_audioFiles, _artist, folderInfo); Mocker.GetMock() - .Verify(c => c.GetLocalTrack(It.IsAny(), It.IsAny(), null), Times.Exactly(1)); + .Verify(c => c.GetLocalTrack(It.IsAny(), It.IsAny(), It.IsAny(), null), Times.Exactly(1)); Mocker.GetMock() - .Verify(c => c.GetLocalTrack(It.IsAny(), It.IsAny(), It.Is(p => p != null)), Times.Never()); + .Verify(c => c.GetLocalTrack(It.IsAny(), It.IsAny(), It.IsAny(), It.Is(p => p != null)), Times.Never()); } [Test] @@ -394,7 +394,7 @@ namespace NzbDrone.Core.Test.MediaFiles.TrackImport public void should_return_a_decision_when_exception_is_caught() { Mocker.GetMock() - .Setup(c => c.GetLocalTrack(It.IsAny(), It.IsAny(), It.IsAny())) + .Setup(c => c.GetLocalTrack(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())) .Throws(); _audioFiles = new List diff --git a/src/NzbDrone.Core/MediaFiles/TrackImport/ImportDecisionMaker.cs b/src/NzbDrone.Core/MediaFiles/TrackImport/ImportDecisionMaker.cs index be0a00e59..6cca5f5ab 100644 --- a/src/NzbDrone.Core/MediaFiles/TrackImport/ImportDecisionMaker.cs +++ b/src/NzbDrone.Core/MediaFiles/TrackImport/ImportDecisionMaker.cs @@ -20,7 +20,7 @@ namespace NzbDrone.Core.MediaFiles.TrackImport List GetImportDecisions(List musicFiles, Artist artist); List GetImportDecisions(List musicFiles, Artist artist, ParsedTrackInfo folderInfo); List GetImportDecisions(List musicFiles, Artist artist, ParsedTrackInfo folderInfo, bool filterExistingFiles); - + ImportDecision GetImportDecision(string musicFile, Artist artist, Album album); } public class ImportDecisionMaker : IMakeImportDecision @@ -68,19 +68,24 @@ namespace NzbDrone.Core.MediaFiles.TrackImport foreach (var file in files) { - decisions.AddIfNotNull(GetDecision(file, artist, folderInfo, shouldUseFolderName)); + decisions.AddIfNotNull(GetDecision(file, artist, null, folderInfo, shouldUseFolderName)); } return decisions; } - private ImportDecision GetDecision(string file, Artist artist, ParsedTrackInfo folderInfo, bool shouldUseFolderName) + public ImportDecision GetImportDecision(string file, Artist artist, Album album) + { + return GetDecision(file, artist, album, null, false); + } + + private ImportDecision GetDecision(string file, Artist artist, Album album, ParsedTrackInfo folderInfo, bool shouldUseFolderName) { ImportDecision decision = null; try { - var localTrack = _parsingService.GetLocalTrack(file, artist, shouldUseFolderName ? folderInfo : null); + var localTrack = _parsingService.GetLocalTrack(file, artist, album, shouldUseFolderName ? folderInfo : null); if (localTrack != null) { diff --git a/src/NzbDrone.Core/MediaFiles/TrackImport/Manual/ManualImportItem.cs b/src/NzbDrone.Core/MediaFiles/TrackImport/Manual/ManualImportItem.cs index cd0472ded..7a171e71e 100644 --- a/src/NzbDrone.Core/MediaFiles/TrackImport/Manual/ManualImportItem.cs +++ b/src/NzbDrone.Core/MediaFiles/TrackImport/Manual/ManualImportItem.cs @@ -3,10 +3,11 @@ using NzbDrone.Core.DecisionEngine; using NzbDrone.Core.Qualities; using NzbDrone.Core.Languages; using NzbDrone.Core.Music; +using NzbDrone.Core.Datastore; namespace NzbDrone.Core.MediaFiles.TrackImport.Manual { - public class ManualImportItem + public class ManualImportItem : ModelBase { public string Path { get; set; } public string RelativePath { get; set; } diff --git a/src/NzbDrone.Core/MediaFiles/TrackImport/Manual/ManualImportService.cs b/src/NzbDrone.Core/MediaFiles/TrackImport/Manual/ManualImportService.cs index fba198b1d..ceb4d4960 100644 --- a/src/NzbDrone.Core/MediaFiles/TrackImport/Manual/ManualImportService.cs +++ b/src/NzbDrone.Core/MediaFiles/TrackImport/Manual/ManualImportService.cs @@ -15,12 +15,16 @@ using NzbDrone.Core.Messaging.Events; using NzbDrone.Core.Parser; using NzbDrone.Core.Parser.Model; using NzbDrone.Core.Music; +using NzbDrone.Common.Crypto; +using NzbDrone.Common.Cache; namespace NzbDrone.Core.MediaFiles.TrackImport.Manual { public interface IManualImportService { List GetMediaFiles(string path, string downloadId, bool filterExistingFiles); + void UpdateItem(ManualImportItem item); + ManualImportItem Find(int id); } public class ManualImportService : IExecute, IManualImportService @@ -35,7 +39,8 @@ namespace NzbDrone.Core.MediaFiles.TrackImport.Manual private readonly IVideoFileInfoReader _videoFileInfoReader; private readonly IImportApprovedTracks _importApprovedTracks; private readonly ITrackedDownloadService _trackedDownloadService; - private readonly IDownloadedTracksImportService _downloadedEpisodesImportService; + private readonly IDownloadedTracksImportService _downloadedTracksImportService; + private readonly ICached _cache; private readonly IEventAggregator _eventAggregator; private readonly Logger _logger; @@ -49,7 +54,8 @@ namespace NzbDrone.Core.MediaFiles.TrackImport.Manual IVideoFileInfoReader videoFileInfoReader, IImportApprovedTracks importApprovedTracks, ITrackedDownloadService trackedDownloadService, - IDownloadedTracksImportService downloadedEpisodesImportService, + IDownloadedTracksImportService downloadedTracksImportService, + ICacheManager cacheManager, IEventAggregator eventAggregator, Logger logger) { @@ -63,13 +69,21 @@ namespace NzbDrone.Core.MediaFiles.TrackImport.Manual _videoFileInfoReader = videoFileInfoReader; _importApprovedTracks = importApprovedTracks; _trackedDownloadService = trackedDownloadService; - _downloadedEpisodesImportService = downloadedEpisodesImportService; + _downloadedTracksImportService = downloadedTracksImportService; + _cache = cacheManager.GetCache(GetType()); _eventAggregator = eventAggregator; _logger = logger; } + public ManualImportItem Find(int id) + { + return _cache.Find(id.ToString()); + } + public List GetMediaFiles(string path, string downloadId, bool filterExistingFiles) { + _cache.Clear(); + if (downloadId.IsNotNullOrWhiteSpace()) { var trackedDownload = _trackedDownloadService.Find(downloadId); @@ -89,10 +103,19 @@ namespace NzbDrone.Core.MediaFiles.TrackImport.Manual return new List(); } - return new List { ProcessFile(path, downloadId) }; + var decision = ProcessFile(path, downloadId); + _cache.Set(decision.Id.ToString(), decision); + + return new List { decision }; } - return ProcessFolder(path, downloadId, filterExistingFiles); + var items = ProcessFolder(path, downloadId, filterExistingFiles); + foreach (var item in items) + { + _cache.Set(item.Id.ToString(), item); + } + + return items; } private List ProcessFolder(string folder, string downloadId, bool filterExistingFiles) @@ -120,6 +143,30 @@ namespace NzbDrone.Core.MediaFiles.TrackImport.Manual return decisions.Select(decision => MapItem(decision, folder, downloadId)).ToList(); } + public void UpdateItem(ManualImportItem item) + { + var decision = _importDecisionMaker.GetImportDecision(item.Path, item.Artist, item.Album); + + if (decision.LocalTrack.Artist != null) + { + item.Artist = decision.LocalTrack.Artist; + } + + if (decision.LocalTrack.Album != null) + { + item.Album = decision.LocalTrack.Album; + } + + if (decision.LocalTrack.Tracks.Any()) + { + item.Tracks = decision.LocalTrack.Tracks; + } + + item.Rejections = decision.Rejections; + + _cache.Set(item.Id.ToString(), item); + } + private ManualImportItem ProcessFile(string file, string downloadId, string folder = null) { if (folder.IsNullOrWhiteSpace()) @@ -158,6 +205,7 @@ namespace NzbDrone.Core.MediaFiles.TrackImport.Manual return importDecisions.Any() ? MapItem(importDecisions.First(), folder, downloadId) : new ManualImportItem { + Id = HashConverter.GetHashInt31(file), DownloadId = downloadId, Path = file, RelativePath = folder.GetRelativePath(file), @@ -178,6 +226,7 @@ namespace NzbDrone.Core.MediaFiles.TrackImport.Manual { var item = new ManualImportItem(); + item.Id = HashConverter.GetHashInt31(decision.LocalTrack.Path); item.Path = decision.LocalTrack.Path; item.RelativePath = folder.GetRelativePath(decision.LocalTrack.Path); item.Name = Path.GetFileNameWithoutExtension(decision.LocalTrack.Path); @@ -271,7 +320,7 @@ namespace NzbDrone.Core.MediaFiles.TrackImport.Manual if (_diskProvider.FolderExists(trackedDownload.DownloadItem.OutputPath.FullPath)) { - if (_downloadedEpisodesImportService.ShouldDeleteFolder( + if (_downloadedTracksImportService.ShouldDeleteFolder( new DirectoryInfo(trackedDownload.DownloadItem.OutputPath.FullPath), trackedDownload.RemoteAlbum.Artist) && trackedDownload.DownloadItem.CanMoveFiles) { diff --git a/src/NzbDrone.Core/Parser/ParsingService.cs b/src/NzbDrone.Core/Parser/ParsingService.cs index 9aa57c727..1823581b8 100644 --- a/src/NzbDrone.Core/Parser/ParsingService.cs +++ b/src/NzbDrone.Core/Parser/ParsingService.cs @@ -23,7 +23,7 @@ namespace NzbDrone.Core.Parser Album GetLocalAlbum(string filename, Artist artist); LocalTrack GetLocalTrack(string filename, Artist artist); LocalTrack GetLocalTrack(string filename, Artist artist, ParsedTrackInfo folderInfo); - + LocalTrack GetLocalTrack(string filename, Artist artist, Album album, ParsedTrackInfo folderInfo); } public class ParsingService : IParsingService @@ -245,6 +245,11 @@ namespace NzbDrone.Core.Parser } public LocalTrack GetLocalTrack(string filename, Artist artist, ParsedTrackInfo folderInfo) + { + return GetLocalTrack(filename, artist, null, folderInfo); + } + + public LocalTrack GetLocalTrack(string filename, Artist artist, Album album, ParsedTrackInfo folderInfo) { ParsedTrackInfo parsedTrackInfo; @@ -258,7 +263,7 @@ namespace NzbDrone.Core.Parser parsedTrackInfo = Parser.ParseMusicPath(filename); } - if (parsedTrackInfo == null || (parsedTrackInfo.AlbumTitle.IsNullOrWhiteSpace()) && parsedTrackInfo.ReleaseMBId.IsNullOrWhiteSpace()) + if (parsedTrackInfo == null || (parsedTrackInfo.AlbumTitle.IsNullOrWhiteSpace()) && parsedTrackInfo.ReleaseMBId.IsNullOrWhiteSpace() && album == null) { if (MediaFileExtensions.Extensions.Contains(Path.GetExtension(filename))) { @@ -268,7 +273,11 @@ namespace NzbDrone.Core.Parser return null; } - var album = GetAlbum(artist, parsedTrackInfo); + if (album == null) + { + album = GetAlbum(artist, parsedTrackInfo); + } + var tracks = new List(); if (album != null) {