New: Manual import refreshes decisions when artist/album updated (#540)

pull/6/head
ta264 6 years ago committed by Qstick
parent d62b4e49f9
commit 32c75cfcbc

@ -224,6 +224,16 @@ class SignalRConnector extends Component {
} }
} }
handleManualimport = (body) => {
if (body.action === 'updated') {
this.props.dispatchUpdateItem({
section: 'interactiveImport',
updateOnly: true,
...body.resource
});
}
}
handleQueue = () => { handleQueue = () => {
if (this.props.isQueuePopulated) { if (this.props.isQueuePopulated) {
this.props.dispatchFetchQueue(); this.props.dispatchFetchQueue();

@ -5,6 +5,7 @@ import { connect } from 'react-redux';
import { createSelector } from 'reselect'; import { createSelector } from 'reselect';
import { import {
updateInteractiveImportItem, updateInteractiveImportItem,
saveInteractiveImportItem,
fetchInteractiveImportAlbums, fetchInteractiveImportAlbums,
setInteractiveImportAlbumsSort, setInteractiveImportAlbumsSort,
clearInteractiveImportAlbums clearInteractiveImportAlbums
@ -25,7 +26,8 @@ const mapDispatchToProps = {
fetchInteractiveImportAlbums, fetchInteractiveImportAlbums,
setInteractiveImportAlbumsSort, setInteractiveImportAlbumsSort,
clearInteractiveImportAlbums, clearInteractiveImportAlbums,
updateInteractiveImportItem updateInteractiveImportItem,
saveInteractiveImportItem
}; };
class SelectAlbumModalContentConnector extends Component { class SelectAlbumModalContentConnector extends Component {
@ -61,8 +63,10 @@ class SelectAlbumModalContentConnector extends Component {
this.props.updateInteractiveImportItem({ this.props.updateInteractiveImportItem({
id, id,
album, album,
tracks: [] tracks: [],
rejections: []
}); });
this.props.saveInteractiveImportItem({ id });
}); });
this.props.onModalClose(true); this.props.onModalClose(true);

@ -3,7 +3,7 @@ import PropTypes from 'prop-types';
import React, { Component } from 'react'; import React, { Component } from 'react';
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import { createSelector } from 'reselect'; import { createSelector } from 'reselect';
import { updateInteractiveImportItem } from 'Store/Actions/interactiveImportActions'; import { updateInteractiveImportItem, saveInteractiveImportItem } from 'Store/Actions/interactiveImportActions';
import createAllArtistSelector from 'Store/Selectors/createAllArtistSelector'; import createAllArtistSelector from 'Store/Selectors/createAllArtistSelector';
import SelectArtistModalContent from './SelectArtistModalContent'; import SelectArtistModalContent from './SelectArtistModalContent';
@ -29,7 +29,8 @@ function createMapStateToProps() {
} }
const mapDispatchToProps = { const mapDispatchToProps = {
updateInteractiveImportItem updateInteractiveImportItem,
saveInteractiveImportItem
}; };
class SelectArtistModalContentConnector extends Component { class SelectArtistModalContentConnector extends Component {
@ -45,8 +46,10 @@ class SelectArtistModalContentConnector extends Component {
id, id,
artist, artist,
album: undefined, album: undefined,
tracks: [] tracks: [],
rejections: []
}); });
this.props.saveInteractiveImportItem({ id });
}); });
this.props.onModalClose(true); this.props.onModalClose(true);

@ -324,6 +324,7 @@ class InteractiveImportRow extends Component {
id={id} id={id}
artistId={artist && artist.id} artistId={artist && artist.id}
albumId={album && album.id} albumId={album && album.id}
filename={relativePath}
onModalClose={this.onSelectTrackModalClose} onModalClose={this.onSelectTrackModalClose}
/> />

@ -87,7 +87,8 @@ class SelectTrackModalContent extends Component {
sortKey, sortKey,
sortDirection, sortDirection,
onSortPress, onSortPress,
onModalClose onModalClose,
filename
} = this.props; } = this.props;
const { const {
@ -96,12 +97,13 @@ class SelectTrackModalContent extends Component {
selectedState selectedState
} = this.state; } = this.state;
const title = `Manual Import - Select Track(s): ${filename}`;
const errorMessage = getErrorMessage(error, 'Unable to load tracks'); const errorMessage = getErrorMessage(error, 'Unable to load tracks');
return ( return (
<ModalContent onModalClose={onModalClose}> <ModalContent onModalClose={onModalClose}>
<ModalHeader> <ModalHeader>
Manual Import - Select Track(s) {title}
</ModalHeader> </ModalHeader>
<ModalBody> <ModalBody>
@ -179,7 +181,8 @@ SelectTrackModalContent.propTypes = {
sortDirection: PropTypes.string, sortDirection: PropTypes.string,
onSortPress: PropTypes.func.isRequired, onSortPress: PropTypes.func.isRequired,
onTracksSelect: PropTypes.func.isRequired, onTracksSelect: PropTypes.func.isRequired,
onModalClose: PropTypes.func.isRequired onModalClose: PropTypes.func.isRequired,
filename: PropTypes.string.isRequired
}; };
export default SelectTrackModalContent; export default SelectTrackModalContent;

@ -8,6 +8,7 @@ import { sortDirections } from 'Helpers/Props';
import createSetClientSideCollectionSortReducer from './Creators/Reducers/createSetClientSideCollectionSortReducer'; import createSetClientSideCollectionSortReducer from './Creators/Reducers/createSetClientSideCollectionSortReducer';
import createFetchHandler from './Creators/createFetchHandler'; import createFetchHandler from './Creators/createFetchHandler';
import createHandleActions from './Creators/createHandleActions'; import createHandleActions from './Creators/createHandleActions';
import createSaveProviderHandler from './Creators/createSaveProviderHandler';
import { set, update } from './baseActions'; import { set, update } from './baseActions';
// //
@ -25,6 +26,7 @@ export const defaultState = {
isPopulated: false, isPopulated: false,
error: null, error: null,
items: [], items: [],
pendingChanges: {},
sortKey: 'quality', sortKey: 'quality',
sortDirection: sortDirections.DESCENDING, sortDirection: sortDirections.DESCENDING,
recentFolders: [], recentFolders: [],
@ -67,6 +69,7 @@ export const persistState = [
export const FETCH_INTERACTIVE_IMPORT_ITEMS = 'FETCH_INTERACTIVE_IMPORT_ITEMS'; export const FETCH_INTERACTIVE_IMPORT_ITEMS = 'FETCH_INTERACTIVE_IMPORT_ITEMS';
export const UPDATE_INTERACTIVE_IMPORT_ITEM = 'UPDATE_INTERACTIVE_IMPORT_ITEM'; 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 SET_INTERACTIVE_IMPORT_SORT = 'SET_INTERACTIVE_IMPORT_SORT';
export const CLEAR_INTERACTIVE_IMPORT = 'CLEAR_INTERACTIVE_IMPORT'; export const CLEAR_INTERACTIVE_IMPORT = 'CLEAR_INTERACTIVE_IMPORT';
export const ADD_RECENT_FOLDER = 'ADD_RECENT_FOLDER'; 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 fetchInteractiveImportItems = createThunk(FETCH_INTERACTIVE_IMPORT_ITEMS);
export const setInteractiveImportSort = createAction(SET_INTERACTIVE_IMPORT_SORT); export const setInteractiveImportSort = createAction(SET_INTERACTIVE_IMPORT_SORT);
export const updateInteractiveImportItem = createAction(UPDATE_INTERACTIVE_IMPORT_ITEM); 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 clearInteractiveImport = createAction(CLEAR_INTERACTIVE_IMPORT);
export const addRecentFolder = createAction(ADD_RECENT_FOLDER); export const addRecentFolder = createAction(ADD_RECENT_FOLDER);
export const removeRecentFolder = createAction(REMOVE_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') [FETCH_INTERACTIVE_IMPORT_ALBUMS]: createFetchHandler('interactiveImport.albums', '/album')
}); });

@ -115,6 +115,7 @@
<Compile Include="Parse\ParseModule.cs" /> <Compile Include="Parse\ParseModule.cs" />
<Compile Include="Parse\ParseResource.cs" /> <Compile Include="Parse\ParseResource.cs" />
<Compile Include="ManualImport\ManualImportModule.cs" /> <Compile Include="ManualImport\ManualImportModule.cs" />
<Compile Include="ManualImport\ManualImportModuleWithSignalR.cs" />
<Compile Include="ManualImport\ManualImportResource.cs" /> <Compile Include="ManualImport\ManualImportResource.cs" />
<Compile Include="Profiles\Delay\DelayProfileModule.cs" /> <Compile Include="Profiles\Delay\DelayProfileModule.cs" />
<Compile Include="Profiles\Delay\DelayProfileResource.cs" /> <Compile Include="Profiles\Delay\DelayProfileResource.cs" />

@ -4,20 +4,30 @@ using NzbDrone.Core.MediaFiles.TrackImport.Manual;
using NzbDrone.Core.Qualities; using NzbDrone.Core.Qualities;
using Lidarr.Http; using Lidarr.Http;
using Lidarr.Http.Extensions; using Lidarr.Http.Extensions;
using NzbDrone.SignalR;
using NzbDrone.Core.Datastore.Events;
using NzbDrone.Core.Music;
using NLog;
namespace Lidarr.Api.V1.ManualImport namespace Lidarr.Api.V1.ManualImport
{ {
public class ManualImportModule : LidarrRestModule<ManualImportResource> public class ManualImportModule : ManualImportModuleWithSignalR
{ {
private readonly IManualImportService _manualImportService; private readonly IArtistService _artistService;
private readonly IAlbumService _albumService;
public ManualImportModule(IManualImportService manualImportService) public ManualImportModule(IManualImportService manualImportService,
: base("/manualimport") IArtistService artistService,
IAlbumService albumService,
IBroadcastSignalRMessage signalRBroadcaster,
Logger logger)
: base(manualImportService, signalRBroadcaster, logger)
{ {
_manualImportService = manualImportService; _albumService = albumService;
_artistService = artistService;
GetResourceAll = GetMediaFiles; GetResourceAll = GetMediaFiles;
UpdateResource = UpdateImportItem;
} }
private List<ManualImportResource> GetMediaFiles() private List<ManualImportResource> GetMediaFiles()
@ -40,5 +50,26 @@ namespace Lidarr.Api.V1.ManualImport
return item; 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);
}
} }
} }

@ -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<ManualImportResource, ManualImportItem>
{
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();
}
}
}

@ -37,7 +37,7 @@ namespace Lidarr.Api.V1.ManualImport
return new ManualImportResource return new ManualImportResource
{ {
Id = HashConverter.GetHashInt31(model.Path), Id = model.Id,
Path = model.Path, Path = model.Path,
RelativePath = model.RelativePath, RelativePath = model.RelativePath,
FolderName = model.FolderName, FolderName = model.FolderName,

@ -71,7 +71,7 @@ namespace NzbDrone.Core.Test.MediaFiles.TrackImport
}; };
Mocker.GetMock<IParsingService>() Mocker.GetMock<IParsingService>()
.Setup(c => c.GetLocalTrack(It.IsAny<string>(), It.IsAny<Artist>(), It.IsAny<ParsedTrackInfo>())) .Setup(c => c.GetLocalTrack(It.IsAny<string>(), It.IsAny<Artist>(), It.IsAny<Album>(), It.IsAny<ParsedTrackInfo>()))
.Returns(_localTrack); .Returns(_localTrack);
GivenVideoFiles(new List<string> { @"C:\Test\Unsorted\The.Office.S03E115.DVDRip.Spanish.XviD-OSiTV.avi".AsOsAgnostic() }); GivenVideoFiles(new List<string> { @"C:\Test\Unsorted\The.Office.S03E115.DVDRip.Spanish.XviD-OSiTV.avi".AsOsAgnostic() });
@ -151,7 +151,7 @@ namespace NzbDrone.Core.Test.MediaFiles.TrackImport
GivenSpecifications(_pass1); GivenSpecifications(_pass1);
Mocker.GetMock<IParsingService>() Mocker.GetMock<IParsingService>()
.Setup(c => c.GetLocalTrack(It.IsAny<string>(), It.IsAny<Artist>(), It.IsAny<ParsedTrackInfo>())) .Setup(c => c.GetLocalTrack(It.IsAny<string>(), It.IsAny<Artist>(), It.IsAny<Album>(), It.IsAny<ParsedTrackInfo>()))
.Throws<TestException>(); .Throws<TestException>();
_audioFiles = new List<string> _audioFiles = new List<string>
@ -166,7 +166,7 @@ namespace NzbDrone.Core.Test.MediaFiles.TrackImport
Subject.GetImportDecisions(_audioFiles, _artist); Subject.GetImportDecisions(_audioFiles, _artist);
Mocker.GetMock<IParsingService>() Mocker.GetMock<IParsingService>()
.Verify(c => c.GetLocalTrack(It.IsAny<string>(), It.IsAny<Artist>(), It.IsAny<ParsedTrackInfo>()), Times.Exactly(_audioFiles.Count)); .Verify(c => c.GetLocalTrack(It.IsAny<string>(), It.IsAny<Artist>(), It.IsAny<Album>(), It.IsAny<ParsedTrackInfo>()), Times.Exactly(_audioFiles.Count));
ExceptionVerification.ExpectedErrors(3); ExceptionVerification.ExpectedErrors(3);
} }
@ -260,7 +260,7 @@ namespace NzbDrone.Core.Test.MediaFiles.TrackImport
GivenSpecifications(_pass1); GivenSpecifications(_pass1);
Mocker.GetMock<IParsingService>() Mocker.GetMock<IParsingService>()
.Setup(c => c.GetLocalTrack(It.IsAny<string>(), It.IsAny<Artist>(), It.IsAny<ParsedTrackInfo>())) .Setup(c => c.GetLocalTrack(It.IsAny<string>(), It.IsAny<Artist>(), It.IsAny<Album>(), It.IsAny<ParsedTrackInfo>()))
.Returns(new LocalTrack() { Path = "test" }); .Returns(new LocalTrack() { Path = "test" });
_audioFiles = new List<string> _audioFiles = new List<string>
@ -275,7 +275,7 @@ namespace NzbDrone.Core.Test.MediaFiles.TrackImport
var decisions = Subject.GetImportDecisions(_audioFiles, _artist); var decisions = Subject.GetImportDecisions(_audioFiles, _artist);
Mocker.GetMock<IParsingService>() Mocker.GetMock<IParsingService>()
.Verify(c => c.GetLocalTrack(It.IsAny<string>(), It.IsAny<Artist>(), It.IsAny<ParsedTrackInfo>()), Times.Exactly(_audioFiles.Count)); .Verify(c => c.GetLocalTrack(It.IsAny<string>(), It.IsAny<Artist>(), It.IsAny<Album>(), It.IsAny<ParsedTrackInfo>()), Times.Exactly(_audioFiles.Count));
decisions.Should().HaveCount(3); decisions.Should().HaveCount(3);
decisions.First().Rejections.Should().NotBeEmpty(); decisions.First().Rejections.Should().NotBeEmpty();
@ -299,10 +299,10 @@ namespace NzbDrone.Core.Test.MediaFiles.TrackImport
Subject.GetImportDecisions(_audioFiles, _artist, folderInfo); Subject.GetImportDecisions(_audioFiles, _artist, folderInfo);
Mocker.GetMock<IParsingService>() Mocker.GetMock<IParsingService>()
.Verify(c => c.GetLocalTrack(It.IsAny<string>(), It.IsAny<Artist>(), null), Times.Exactly(3)); .Verify(c => c.GetLocalTrack(It.IsAny<string>(), It.IsAny<Artist>(), It.IsAny<Album>(), null), Times.Exactly(3));
Mocker.GetMock<IParsingService>() Mocker.GetMock<IParsingService>()
.Verify(c => c.GetLocalTrack(It.IsAny<string>(), It.IsAny<Artist>(), It.Is<ParsedTrackInfo>(p => p != null)), Times.Never()); .Verify(c => c.GetLocalTrack(It.IsAny<string>(), It.IsAny<Artist>(), It.IsAny<Album>(), It.Is<ParsedTrackInfo>(p => p != null)), Times.Never());
} }
[Test] [Test]
@ -322,10 +322,10 @@ namespace NzbDrone.Core.Test.MediaFiles.TrackImport
Subject.GetImportDecisions(_audioFiles, _artist, folderInfo); Subject.GetImportDecisions(_audioFiles, _artist, folderInfo);
Mocker.GetMock<IParsingService>() Mocker.GetMock<IParsingService>()
.Verify(c => c.GetLocalTrack(It.IsAny<string>(), It.IsAny<Artist>(), null), Times.Exactly(2)); .Verify(c => c.GetLocalTrack(It.IsAny<string>(), It.IsAny<Artist>(), It.IsAny<Album>(), null), Times.Exactly(2));
Mocker.GetMock<IParsingService>() Mocker.GetMock<IParsingService>()
.Verify(c => c.GetLocalTrack(It.IsAny<string>(), It.IsAny<Artist>(), It.Is<ParsedTrackInfo>(p => p != null)), Times.Never()); .Verify(c => c.GetLocalTrack(It.IsAny<string>(), It.IsAny<Artist>(), It.IsAny<Album>(), It.Is<ParsedTrackInfo>(p => p != null)), Times.Never());
} }
[Test] [Test]
@ -344,10 +344,10 @@ namespace NzbDrone.Core.Test.MediaFiles.TrackImport
Subject.GetImportDecisions(_audioFiles, _artist, folderInfo); Subject.GetImportDecisions(_audioFiles, _artist, folderInfo);
Mocker.GetMock<IParsingService>() Mocker.GetMock<IParsingService>()
.Verify(c => c.GetLocalTrack(It.IsAny<string>(), It.IsAny<Artist>(), It.IsAny<ParsedTrackInfo>()), Times.Exactly(1)); .Verify(c => c.GetLocalTrack(It.IsAny<string>(), It.IsAny<Artist>(), It.IsAny<Album>(), It.IsAny<ParsedTrackInfo>()), Times.Exactly(1));
Mocker.GetMock<IParsingService>() Mocker.GetMock<IParsingService>()
.Verify(c => c.GetLocalTrack(It.IsAny<string>(), It.IsAny<Artist>(), null), Times.Never()); .Verify(c => c.GetLocalTrack(It.IsAny<string>(), It.IsAny<Artist>(), It.IsAny<Album>(), null), Times.Never());
} }
[Test] [Test]
@ -366,10 +366,10 @@ namespace NzbDrone.Core.Test.MediaFiles.TrackImport
Subject.GetImportDecisions(_audioFiles, _artist, folderInfo); Subject.GetImportDecisions(_audioFiles, _artist, folderInfo);
Mocker.GetMock<IParsingService>() Mocker.GetMock<IParsingService>()
.Verify(c => c.GetLocalTrack(It.IsAny<string>(), It.IsAny<Artist>(), null), Times.Exactly(1)); .Verify(c => c.GetLocalTrack(It.IsAny<string>(), It.IsAny<Artist>(), It.IsAny<Album>(), null), Times.Exactly(1));
Mocker.GetMock<IParsingService>() Mocker.GetMock<IParsingService>()
.Verify(c => c.GetLocalTrack(It.IsAny<string>(), It.IsAny<Artist>(), It.Is<ParsedTrackInfo>(p => p != null)), Times.Never()); .Verify(c => c.GetLocalTrack(It.IsAny<string>(), It.IsAny<Artist>(), It.IsAny<Album>(), It.Is<ParsedTrackInfo>(p => p != null)), Times.Never());
} }
[Test] [Test]
@ -394,7 +394,7 @@ namespace NzbDrone.Core.Test.MediaFiles.TrackImport
public void should_return_a_decision_when_exception_is_caught() public void should_return_a_decision_when_exception_is_caught()
{ {
Mocker.GetMock<IParsingService>() Mocker.GetMock<IParsingService>()
.Setup(c => c.GetLocalTrack(It.IsAny<string>(), It.IsAny<Artist>(), It.IsAny<ParsedTrackInfo>())) .Setup(c => c.GetLocalTrack(It.IsAny<string>(), It.IsAny<Artist>(), It.IsAny<Album>(), It.IsAny<ParsedTrackInfo>()))
.Throws<TestException>(); .Throws<TestException>();
_audioFiles = new List<string> _audioFiles = new List<string>

@ -20,7 +20,7 @@ namespace NzbDrone.Core.MediaFiles.TrackImport
List<ImportDecision> GetImportDecisions(List<string> musicFiles, Artist artist); List<ImportDecision> GetImportDecisions(List<string> musicFiles, Artist artist);
List<ImportDecision> GetImportDecisions(List<string> musicFiles, Artist artist, ParsedTrackInfo folderInfo); List<ImportDecision> GetImportDecisions(List<string> musicFiles, Artist artist, ParsedTrackInfo folderInfo);
List<ImportDecision> GetImportDecisions(List<string> musicFiles, Artist artist, ParsedTrackInfo folderInfo, bool filterExistingFiles); List<ImportDecision> GetImportDecisions(List<string> musicFiles, Artist artist, ParsedTrackInfo folderInfo, bool filterExistingFiles);
ImportDecision GetImportDecision(string musicFile, Artist artist, Album album);
} }
public class ImportDecisionMaker : IMakeImportDecision public class ImportDecisionMaker : IMakeImportDecision
@ -68,19 +68,24 @@ namespace NzbDrone.Core.MediaFiles.TrackImport
foreach (var file in files) foreach (var file in files)
{ {
decisions.AddIfNotNull(GetDecision(file, artist, folderInfo, shouldUseFolderName)); decisions.AddIfNotNull(GetDecision(file, artist, null, folderInfo, shouldUseFolderName));
} }
return decisions; 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; ImportDecision decision = null;
try try
{ {
var localTrack = _parsingService.GetLocalTrack(file, artist, shouldUseFolderName ? folderInfo : null); var localTrack = _parsingService.GetLocalTrack(file, artist, album, shouldUseFolderName ? folderInfo : null);
if (localTrack != null) if (localTrack != null)
{ {

@ -3,10 +3,11 @@ using NzbDrone.Core.DecisionEngine;
using NzbDrone.Core.Qualities; using NzbDrone.Core.Qualities;
using NzbDrone.Core.Languages; using NzbDrone.Core.Languages;
using NzbDrone.Core.Music; using NzbDrone.Core.Music;
using NzbDrone.Core.Datastore;
namespace NzbDrone.Core.MediaFiles.TrackImport.Manual namespace NzbDrone.Core.MediaFiles.TrackImport.Manual
{ {
public class ManualImportItem public class ManualImportItem : ModelBase
{ {
public string Path { get; set; } public string Path { get; set; }
public string RelativePath { get; set; } public string RelativePath { get; set; }

@ -15,12 +15,16 @@ using NzbDrone.Core.Messaging.Events;
using NzbDrone.Core.Parser; using NzbDrone.Core.Parser;
using NzbDrone.Core.Parser.Model; using NzbDrone.Core.Parser.Model;
using NzbDrone.Core.Music; using NzbDrone.Core.Music;
using NzbDrone.Common.Crypto;
using NzbDrone.Common.Cache;
namespace NzbDrone.Core.MediaFiles.TrackImport.Manual namespace NzbDrone.Core.MediaFiles.TrackImport.Manual
{ {
public interface IManualImportService public interface IManualImportService
{ {
List<ManualImportItem> GetMediaFiles(string path, string downloadId, bool filterExistingFiles); List<ManualImportItem> GetMediaFiles(string path, string downloadId, bool filterExistingFiles);
void UpdateItem(ManualImportItem item);
ManualImportItem Find(int id);
} }
public class ManualImportService : IExecute<ManualImportCommand>, IManualImportService public class ManualImportService : IExecute<ManualImportCommand>, IManualImportService
@ -35,7 +39,8 @@ namespace NzbDrone.Core.MediaFiles.TrackImport.Manual
private readonly IVideoFileInfoReader _videoFileInfoReader; private readonly IVideoFileInfoReader _videoFileInfoReader;
private readonly IImportApprovedTracks _importApprovedTracks; private readonly IImportApprovedTracks _importApprovedTracks;
private readonly ITrackedDownloadService _trackedDownloadService; private readonly ITrackedDownloadService _trackedDownloadService;
private readonly IDownloadedTracksImportService _downloadedEpisodesImportService; private readonly IDownloadedTracksImportService _downloadedTracksImportService;
private readonly ICached<ManualImportItem> _cache;
private readonly IEventAggregator _eventAggregator; private readonly IEventAggregator _eventAggregator;
private readonly Logger _logger; private readonly Logger _logger;
@ -49,7 +54,8 @@ namespace NzbDrone.Core.MediaFiles.TrackImport.Manual
IVideoFileInfoReader videoFileInfoReader, IVideoFileInfoReader videoFileInfoReader,
IImportApprovedTracks importApprovedTracks, IImportApprovedTracks importApprovedTracks,
ITrackedDownloadService trackedDownloadService, ITrackedDownloadService trackedDownloadService,
IDownloadedTracksImportService downloadedEpisodesImportService, IDownloadedTracksImportService downloadedTracksImportService,
ICacheManager cacheManager,
IEventAggregator eventAggregator, IEventAggregator eventAggregator,
Logger logger) Logger logger)
{ {
@ -63,13 +69,21 @@ namespace NzbDrone.Core.MediaFiles.TrackImport.Manual
_videoFileInfoReader = videoFileInfoReader; _videoFileInfoReader = videoFileInfoReader;
_importApprovedTracks = importApprovedTracks; _importApprovedTracks = importApprovedTracks;
_trackedDownloadService = trackedDownloadService; _trackedDownloadService = trackedDownloadService;
_downloadedEpisodesImportService = downloadedEpisodesImportService; _downloadedTracksImportService = downloadedTracksImportService;
_cache = cacheManager.GetCache<ManualImportItem>(GetType());
_eventAggregator = eventAggregator; _eventAggregator = eventAggregator;
_logger = logger; _logger = logger;
} }
public ManualImportItem Find(int id)
{
return _cache.Find(id.ToString());
}
public List<ManualImportItem> GetMediaFiles(string path, string downloadId, bool filterExistingFiles) public List<ManualImportItem> GetMediaFiles(string path, string downloadId, bool filterExistingFiles)
{ {
_cache.Clear();
if (downloadId.IsNotNullOrWhiteSpace()) if (downloadId.IsNotNullOrWhiteSpace())
{ {
var trackedDownload = _trackedDownloadService.Find(downloadId); var trackedDownload = _trackedDownloadService.Find(downloadId);
@ -89,10 +103,19 @@ namespace NzbDrone.Core.MediaFiles.TrackImport.Manual
return new List<ManualImportItem>(); return new List<ManualImportItem>();
} }
return new List<ManualImportItem> { ProcessFile(path, downloadId) }; var decision = ProcessFile(path, downloadId);
_cache.Set(decision.Id.ToString(), decision);
return new List<ManualImportItem> { decision };
}
var items = ProcessFolder(path, downloadId, filterExistingFiles);
foreach (var item in items)
{
_cache.Set(item.Id.ToString(), item);
} }
return ProcessFolder(path, downloadId, filterExistingFiles); return items;
} }
private List<ManualImportItem> ProcessFolder(string folder, string downloadId, bool filterExistingFiles) private List<ManualImportItem> 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(); 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) private ManualImportItem ProcessFile(string file, string downloadId, string folder = null)
{ {
if (folder.IsNullOrWhiteSpace()) if (folder.IsNullOrWhiteSpace())
@ -158,6 +205,7 @@ namespace NzbDrone.Core.MediaFiles.TrackImport.Manual
return importDecisions.Any() ? MapItem(importDecisions.First(), folder, downloadId) : new ManualImportItem return importDecisions.Any() ? MapItem(importDecisions.First(), folder, downloadId) : new ManualImportItem
{ {
Id = HashConverter.GetHashInt31(file),
DownloadId = downloadId, DownloadId = downloadId,
Path = file, Path = file,
RelativePath = folder.GetRelativePath(file), RelativePath = folder.GetRelativePath(file),
@ -178,6 +226,7 @@ namespace NzbDrone.Core.MediaFiles.TrackImport.Manual
{ {
var item = new ManualImportItem(); var item = new ManualImportItem();
item.Id = HashConverter.GetHashInt31(decision.LocalTrack.Path);
item.Path = decision.LocalTrack.Path; item.Path = decision.LocalTrack.Path;
item.RelativePath = folder.GetRelativePath(decision.LocalTrack.Path); item.RelativePath = folder.GetRelativePath(decision.LocalTrack.Path);
item.Name = Path.GetFileNameWithoutExtension(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 (_diskProvider.FolderExists(trackedDownload.DownloadItem.OutputPath.FullPath))
{ {
if (_downloadedEpisodesImportService.ShouldDeleteFolder( if (_downloadedTracksImportService.ShouldDeleteFolder(
new DirectoryInfo(trackedDownload.DownloadItem.OutputPath.FullPath), new DirectoryInfo(trackedDownload.DownloadItem.OutputPath.FullPath),
trackedDownload.RemoteAlbum.Artist) && trackedDownload.DownloadItem.CanMoveFiles) trackedDownload.RemoteAlbum.Artist) && trackedDownload.DownloadItem.CanMoveFiles)
{ {

@ -23,7 +23,7 @@ namespace NzbDrone.Core.Parser
Album GetLocalAlbum(string filename, Artist artist); Album GetLocalAlbum(string filename, Artist artist);
LocalTrack GetLocalTrack(string filename, Artist artist); LocalTrack GetLocalTrack(string filename, Artist artist);
LocalTrack GetLocalTrack(string filename, Artist artist, ParsedTrackInfo folderInfo); LocalTrack GetLocalTrack(string filename, Artist artist, ParsedTrackInfo folderInfo);
LocalTrack GetLocalTrack(string filename, Artist artist, Album album, ParsedTrackInfo folderInfo);
} }
public class ParsingService : IParsingService public class ParsingService : IParsingService
@ -245,6 +245,11 @@ namespace NzbDrone.Core.Parser
} }
public LocalTrack GetLocalTrack(string filename, Artist artist, ParsedTrackInfo folderInfo) 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; ParsedTrackInfo parsedTrackInfo;
@ -258,7 +263,7 @@ namespace NzbDrone.Core.Parser
parsedTrackInfo = Parser.ParseMusicPath(filename); 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))) if (MediaFileExtensions.Extensions.Contains(Path.GetExtension(filename)))
{ {
@ -268,7 +273,11 @@ namespace NzbDrone.Core.Parser
return null; return null;
} }
var album = GetAlbum(artist, parsedTrackInfo); if (album == null)
{
album = GetAlbum(artist, parsedTrackInfo);
}
var tracks = new List<Track>(); var tracks = new List<Track>();
if (album != null) if (album != null)
{ {

Loading…
Cancel
Save