mirror of
https://github.com/lidarr/lidarr.git
synced 2025-07-07 05:22:14 -07:00
New: Manual import refreshes decisions when artist/album updated (#540)
This commit is contained in:
parent
d62b4e49f9
commit
32c75cfcbc
15 changed files with 211 additions and 43 deletions
|
@ -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 };
|
||||||
}
|
}
|
||||||
|
|
||||||
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<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…
Add table
Add a link
Reference in a new issue