@@ -97,7 +98,7 @@ class AddNewArtist extends Component {
{
items.map((item) => {
- return (
-
- );
+ if (item.artist) {
+ const artist = item.artist;
+ return (
+
+ );
+ } else if (item.album) {
+ const album = item.album;
+ return (
+
+ );
+ }
+ return null;
})
}
@@ -145,11 +160,10 @@ class AddNewArtist extends Component {
!isFetching && !error && !items.length && !!term &&
Couldn't find any results for '{term}'
-
You can also search using MusicBrainz ID of an artist. eg. lidarr:cc197bad-dc9c-440d-a5b5-d52ba2e14234
-
- Why can't I find my artist?
-
+ You can also search using the
+ MusicBrainz ID
+ of an artist e.g. lidarr:cc197bad-dc9c-440d-a5b5-d52ba2e14234
}
@@ -157,8 +171,12 @@ class AddNewArtist extends Component {
{
!term &&
-
It's easy to add a new artist, just start typing the name the artist you want to add.
-
You can also search using MusicBrainz ID of an artist. eg. lidarr:cc197bad-dc9c-440d-a5b5-d52ba2e14234
+
It's easy to add a new artist, just start typing the name of the artist you want to add.
+
+ You can also search using the
+ MusicBrainz ID
+ of an artist e.g. lidarr:cc197bad-dc9c-440d-a5b5-d52ba2e14234
+
}
@@ -169,15 +187,15 @@ class AddNewArtist extends Component {
}
}
-AddNewArtist.propTypes = {
+AddNewItem.propTypes = {
term: PropTypes.string,
isFetching: PropTypes.bool.isRequired,
error: PropTypes.object,
isAdding: PropTypes.bool.isRequired,
addError: PropTypes.object,
items: PropTypes.arrayOf(PropTypes.object).isRequired,
- onArtistLookupChange: PropTypes.func.isRequired,
- onClearArtistLookup: PropTypes.func.isRequired
+ onSearchChange: PropTypes.func.isRequired,
+ onClearSearch: PropTypes.func.isRequired
};
-export default AddNewArtist;
+export default AddNewItem;
diff --git a/frontend/src/AddArtist/AddNewArtist/AddNewArtistConnector.js b/frontend/src/Search/AddNewItemConnector.js
similarity index 52%
rename from frontend/src/AddArtist/AddNewArtist/AddNewArtistConnector.js
rename to frontend/src/Search/AddNewItemConnector.js
index 50cc07cd2..ea9961ad9 100644
--- a/frontend/src/AddArtist/AddNewArtist/AddNewArtistConnector.js
+++ b/frontend/src/Search/AddNewItemConnector.js
@@ -3,32 +3,32 @@ import React, { Component } from 'react';
import { connect } from 'react-redux';
import { createSelector } from 'reselect';
import parseUrl from 'Utilities/String/parseUrl';
-import { lookupArtist, clearAddArtist } from 'Store/Actions/addArtistActions';
+import { getSearchResults, clearSearchResults } from 'Store/Actions/searchActions';
import { fetchRootFolders } from 'Store/Actions/rootFolderActions';
-import AddNewArtist from './AddNewArtist';
+import AddNewItem from './AddNewItem';
function createMapStateToProps() {
return createSelector(
- (state) => state.addArtist,
+ (state) => state.search,
(state) => state.router.location,
- (addArtist, location) => {
+ (search, location) => {
const { params } = parseUrl(location.search);
return {
term: params.term,
- ...addArtist
+ ...search
};
}
);
}
const mapDispatchToProps = {
- lookupArtist,
- clearAddArtist,
+ getSearchResults,
+ clearSearchResults,
fetchRootFolders
};
-class AddNewArtistConnector extends Component {
+class AddNewItemConnector extends Component {
//
// Lifecycle
@@ -36,7 +36,7 @@ class AddNewArtistConnector extends Component {
constructor(props, context) {
super(props, context);
- this._artistLookupTimeout = null;
+ this._searchTimeout = null;
}
componentDidMount() {
@@ -44,32 +44,32 @@ class AddNewArtistConnector extends Component {
}
componentWillUnmount() {
- if (this._artistLookupTimeout) {
- clearTimeout(this._artistLookupTimeout);
+ if (this._searchTimeout) {
+ clearTimeout(this._searchTimeout);
}
- this.props.clearAddArtist();
+ this.props.clearSearchResults();
}
//
// Listeners
- onArtistLookupChange = (term) => {
- if (this._artistLookupTimeout) {
- clearTimeout(this._artistLookupTimeout);
+ onSearchChange = (term) => {
+ if (this._searchTimeout) {
+ clearTimeout(this._searchTimeout);
}
if (term.trim() === '') {
- this.props.clearAddArtist();
+ this.props.clearSearchResults();
} else {
- this._artistLookupTimeout = setTimeout(() => {
- this.props.lookupArtist({ term });
+ this._searchTimeout = setTimeout(() => {
+ this.props.getSearchResults({ term });
}, 300);
}
}
- onClearArtistLookup = () => {
- this.props.clearAddArtist();
+ onClearSearch = () => {
+ this.props.clearSearchResults();
}
//
@@ -82,21 +82,21 @@ class AddNewArtistConnector extends Component {
} = this.props;
return (
-
);
}
}
-AddNewArtistConnector.propTypes = {
+AddNewItemConnector.propTypes = {
term: PropTypes.string,
- lookupArtist: PropTypes.func.isRequired,
- clearAddArtist: PropTypes.func.isRequired,
+ getSearchResults: PropTypes.func.isRequired,
+ clearSearchResults: PropTypes.func.isRequired,
fetchRootFolders: PropTypes.func.isRequired
};
-export default connect(createMapStateToProps, mapDispatchToProps)(AddNewArtistConnector);
+export default connect(createMapStateToProps, mapDispatchToProps)(AddNewItemConnector);
diff --git a/frontend/src/Search/Album/AddNewAlbumModal.js b/frontend/src/Search/Album/AddNewAlbumModal.js
new file mode 100644
index 000000000..7f1d678a9
--- /dev/null
+++ b/frontend/src/Search/Album/AddNewAlbumModal.js
@@ -0,0 +1,31 @@
+import PropTypes from 'prop-types';
+import React from 'react';
+import Modal from 'Components/Modal/Modal';
+import AddNewAlbumModalContentConnector from './AddNewAlbumModalContentConnector';
+
+function AddNewAlbumModal(props) {
+ const {
+ isOpen,
+ onModalClose,
+ ...otherProps
+ } = props;
+
+ return (
+
+
+
+ );
+}
+
+AddNewAlbumModal.propTypes = {
+ isOpen: PropTypes.bool.isRequired,
+ onModalClose: PropTypes.func.isRequired
+};
+
+export default AddNewAlbumModal;
diff --git a/frontend/src/Search/Album/AddNewAlbumModalContent.css b/frontend/src/Search/Album/AddNewAlbumModalContent.css
new file mode 100644
index 000000000..d1d6bb24d
--- /dev/null
+++ b/frontend/src/Search/Album/AddNewAlbumModalContent.css
@@ -0,0 +1,126 @@
+.container {
+ display: flex;
+}
+
+.poster {
+ flex: 0 0 170px;
+ margin-right: 20px;
+ height: 250px;
+}
+
+.info {
+ flex-grow: 1;
+}
+
+.name {
+ font-weight: 300;
+ font-size: 36px;
+}
+
+.artistName {
+ margin-bottom: 20px;
+ font-weight: 300;
+ font-size: 20px;
+}
+
+.disambiguation {
+ margin-bottom: 20px;
+ color: $disabledColor;
+ font-weight: 300;
+ font-size: 20px;
+}
+
+.overview {
+ margin-bottom: 30px;
+ max-height: 230px;
+ text-align: justify;
+}
+
+.header {
+ position: relative;
+ display: flex;
+ align-items: center;
+ margin-top: 5px;
+ margin-bottom: 5px;
+ width: 100%;
+ font-size: 24px;
+ cursor: pointer;
+}
+
+.left {
+ display: flex;
+ align-items: center;
+ flex: 0 1 300px;
+}
+
+.albumType {
+ margin-bottom: 20px;
+ border: 1px solid $borderColor;
+ border-radius: 4px;
+ background-color: $white;
+
+ &:last-of-type {
+ margin-bottom: 0;
+ }
+}
+
+.albumTypeLabel {
+ margin-right: 5px;
+ margin-left: 5px;
+}
+
+.albumCount {
+ color: #8895aa;
+ font-style: italic;
+ font-size: 18px;
+}
+
+.expandButton {
+ composes: link from '~Components/Link/Link.css';
+
+ flex-grow: 1;
+ width: 100%;
+ text-align: center;
+}
+
+.searchForNewAlbumLabelContainer {
+ display: flex;
+ margin-top: 2px;
+}
+
+.searchForNewAlbumLabel {
+ margin-right: 8px;
+ font-weight: normal;
+}
+
+.searchForNewAlbumContainer {
+ composes: container from '~Components/Form/CheckInput.css';
+
+ flex: 0 1 0;
+}
+
+.searchForNewAlbumInput {
+ composes: input from '~Components/Form/CheckInput.css';
+
+ margin-top: 0;
+}
+
+.modalFooter {
+ composes: modalFooter from '~Components/Modal/ModalFooter.css';
+}
+
+.addButton {
+ @add-mixin truncate;
+ composes: button from '~Components/Link/SpinnerButton.css';
+}
+
+@media only screen and (max-width: $breakpointSmall) {
+ .modalFooter {
+ display: block;
+ text-align: center;
+ }
+
+ .addButton {
+ margin-top: 10px;
+ }
+}
diff --git a/frontend/src/Search/Album/AddNewAlbumModalContent.js b/frontend/src/Search/Album/AddNewAlbumModalContent.js
new file mode 100644
index 000000000..d0a91c12a
--- /dev/null
+++ b/frontend/src/Search/Album/AddNewAlbumModalContent.js
@@ -0,0 +1,157 @@
+import PropTypes from 'prop-types';
+import React, { Component } from 'react';
+import TextTruncate from 'react-text-truncate';
+import { kinds } from 'Helpers/Props';
+import SpinnerButton from 'Components/Link/SpinnerButton';
+import CheckInput from 'Components/Form/CheckInput';
+import ModalContent from 'Components/Modal/ModalContent';
+import ModalHeader from 'Components/Modal/ModalHeader';
+import ModalBody from 'Components/Modal/ModalBody';
+import ModalFooter from 'Components/Modal/ModalFooter';
+import AlbumCover from 'Album/AlbumCover';
+import AddArtistOptionsForm from '../Common/AddArtistOptionsForm.js';
+import styles from './AddNewAlbumModalContent.css';
+
+class AddNewAlbumModalContent extends Component {
+
+ //
+ // Lifecycle
+
+ constructor(props, context) {
+ super(props, context);
+
+ this.state = {
+ searchForNewAlbum: false
+ };
+ }
+
+ //
+ // Listeners
+
+ onSearchForNewAlbumChange = ({ value }) => {
+ this.setState({ searchForNewAlbum: value });
+ }
+
+ onAddAlbumPress = () => {
+ this.props.onAddAlbumPress(this.state.searchForNewAlbum);
+ }
+
+ //
+ // Render
+
+ render() {
+ const {
+ albumTitle,
+ artistName,
+ disambiguation,
+ overview,
+ images,
+ isAdding,
+ isExistingArtist,
+ isSmallScreen,
+ onModalClose,
+ ...otherProps
+ } = this.props;
+
+ return (
+
+
+ Add new Album
+
+
+
+
+ {
+ isSmallScreen ?
+ null:
+
+ }
+
+
+
+ {albumTitle}
+
+
+ {
+ !!disambiguation &&
+
({disambiguation})
+ }
+
+
+ By: {artistName}
+
+
+ {
+ overview ?
+
+
+
:
+ null
+ }
+
+ {
+ !isExistingArtist &&
+
+ }
+
+
+
+
+
+
+
+ Start search for new album
+
+
+
+
+
+
+ Add {albumTitle}
+
+
+
+ );
+ }
+}
+
+AddNewAlbumModalContent.propTypes = {
+ albumTitle: PropTypes.string.isRequired,
+ artistName: PropTypes.string.isRequired,
+ disambiguation: PropTypes.string.isRequired,
+ overview: PropTypes.string,
+ images: PropTypes.arrayOf(PropTypes.object).isRequired,
+ isAdding: PropTypes.bool.isRequired,
+ addError: PropTypes.object,
+ isExistingArtist: PropTypes.bool.isRequired,
+ isSmallScreen: PropTypes.bool.isRequired,
+ onModalClose: PropTypes.func.isRequired,
+ onAddAlbumPress: PropTypes.func.isRequired
+};
+
+export default AddNewAlbumModalContent;
diff --git a/frontend/src/Search/Album/AddNewAlbumModalContentConnector.js b/frontend/src/Search/Album/AddNewAlbumModalContentConnector.js
new file mode 100644
index 000000000..074c666b5
--- /dev/null
+++ b/frontend/src/Search/Album/AddNewAlbumModalContentConnector.js
@@ -0,0 +1,135 @@
+import PropTypes from 'prop-types';
+import React, { Component } from 'react';
+import { connect } from 'react-redux';
+import { createSelector } from 'reselect';
+import { metadataProfileNames } from 'Helpers/Props';
+import { setAddDefault, addAlbum } from 'Store/Actions/searchActions';
+import createDimensionsSelector from 'Store/Selectors/createDimensionsSelector';
+import selectSettings from 'Store/Selectors/selectSettings';
+import AddNewAlbumModalContent from './AddNewAlbumModalContent';
+
+function createMapStateToProps() {
+ return createSelector(
+ (state, { isExistingArtist }) => isExistingArtist,
+ (state) => state.search,
+ (state) => state.settings.metadataProfiles,
+ createDimensionsSelector(),
+ (isExistingArtist, searchState, metadataProfiles, dimensions) => {
+ const {
+ isAdding,
+ addError,
+ defaults
+ } = searchState;
+
+ const {
+ settings,
+ validationErrors,
+ validationWarnings
+ } = selectSettings(defaults, {}, addError);
+
+ // For adding single albums, default to None profile
+ const noneProfile = metadataProfiles.items.find((item) => item.name === metadataProfileNames.NONE);
+
+ return {
+ isAdding,
+ addError,
+ showMetadataProfile: true,
+ isSmallScreen: dimensions.isSmallScreen,
+ validationErrors,
+ validationWarnings,
+ noneMetadataProfileId: noneProfile.id,
+ ...settings
+ };
+ }
+ );
+}
+
+const mapDispatchToProps = {
+ setAddDefault,
+ addAlbum
+};
+
+class AddNewAlbumModalContentConnector extends Component {
+
+ //
+ // Lifecycle
+
+ constructor(props, context) {
+ super(props, context);
+
+ this.state = {
+ metadataProfileIdDefault: props.metadataProfileId.value
+ };
+
+ // select none as default
+ this.onInputChange({
+ name: 'metadataProfileId',
+ value: props.noneMetadataProfileId
+ });
+ }
+
+ componentWillUnmount() {
+ // reinstate standard default
+ this.props.setAddDefault({ metadataProfileId: this.state.metadataProfileIdDefault });
+ }
+
+ //
+ // Listeners
+
+ onInputChange = ({ name, value }) => {
+ this.props.setAddDefault({ [name]: value });
+ }
+
+ onAddAlbumPress = (searchForNewAlbum) => {
+ const {
+ foreignAlbumId,
+ rootFolderPath,
+ monitor,
+ qualityProfileId,
+ metadataProfileId,
+ albumFolder,
+ tags
+ } = this.props;
+
+ this.props.addAlbum({
+ foreignAlbumId,
+ rootFolderPath: rootFolderPath.value,
+ monitor: monitor.value,
+ qualityProfileId: qualityProfileId.value,
+ metadataProfileId: metadataProfileId.value,
+ albumFolder: albumFolder.value,
+ tags: tags.value,
+ searchForNewAlbum
+ });
+ }
+
+ //
+ // Render
+
+ render() {
+ return (
+
+ );
+ }
+}
+
+AddNewAlbumModalContentConnector.propTypes = {
+ isExistingArtist: PropTypes.bool.isRequired,
+ foreignAlbumId: PropTypes.string.isRequired,
+ rootFolderPath: PropTypes.object,
+ monitor: PropTypes.object.isRequired,
+ qualityProfileId: PropTypes.object,
+ metadataProfileId: PropTypes.object,
+ noneMetadataProfileId: PropTypes.number.isRequired,
+ albumFolder: PropTypes.object.isRequired,
+ tags: PropTypes.object.isRequired,
+ onModalClose: PropTypes.func.isRequired,
+ setAddDefault: PropTypes.func.isRequired,
+ addAlbum: PropTypes.func.isRequired
+};
+
+export default connect(createMapStateToProps, mapDispatchToProps)(AddNewAlbumModalContentConnector);
diff --git a/frontend/src/Search/Album/AddNewAlbumSearchResult.css b/frontend/src/Search/Album/AddNewAlbumSearchResult.css
new file mode 100644
index 000000000..c8f7dd3cb
--- /dev/null
+++ b/frontend/src/Search/Album/AddNewAlbumSearchResult.css
@@ -0,0 +1,64 @@
+.searchResult {
+ display: flex;
+ margin: 20px 0;
+ padding: 20px;
+ width: 100%;
+ background-color: $white;
+ color: inherit;
+ transition: background 500ms;
+
+ &:hover {
+ background-color: #eaf2ff;
+ color: inherit;
+ text-decoration: none;
+ }
+}
+
+.poster {
+ flex: 0 0 170px;
+ margin-right: 20px;
+ height: 250px;
+}
+
+.content {
+ flex: 0 1 100%;
+}
+
+.name {
+ display: flex;
+ font-weight: 300;
+ font-size: 36px;
+}
+
+.artistName {
+ font-weight: 300;
+ font-size: 20px;
+}
+
+.year {
+ margin-left: 10px;
+ color: $disabledColor;
+}
+
+.mbLink {
+ composes: link from '~Components/Link/Link.css';
+
+ margin-top: -4px;
+ margin-left: auto;
+ color: $textColor;
+}
+
+.mbLinkIcon {
+ margin-left: 10px;
+}
+
+.alreadyExistsIcon {
+ margin-left: 10px;
+ color: #37bc9b;
+}
+
+.overview {
+ overflow: hidden;
+ margin-top: 20px;
+ text-align: justify;
+}
diff --git a/frontend/src/Search/Album/AddNewAlbumSearchResult.js b/frontend/src/Search/Album/AddNewAlbumSearchResult.js
new file mode 100644
index 000000000..7d7e4a36f
--- /dev/null
+++ b/frontend/src/Search/Album/AddNewAlbumSearchResult.js
@@ -0,0 +1,250 @@
+import moment from 'moment';
+import PropTypes from 'prop-types';
+import React, { Component } from 'react';
+import TextTruncate from 'react-text-truncate';
+import dimensions from 'Styles/Variables/dimensions';
+import fonts from 'Styles/Variables/fonts';
+import { icons, sizes } from 'Helpers/Props';
+import HeartRating from 'Components/HeartRating';
+import Icon from 'Components/Icon';
+import Label from 'Components/Label';
+import Link from 'Components/Link/Link';
+import AlbumCover from 'Album/AlbumCover';
+import AddNewAlbumModal from './AddNewAlbumModal';
+import styles from './AddNewAlbumSearchResult.css';
+
+const columnPadding = parseInt(dimensions.artistIndexColumnPadding);
+const columnPaddingSmallScreen = parseInt(dimensions.artistIndexColumnPaddingSmallScreen);
+const defaultFontSize = parseInt(fonts.defaultFontSize);
+const lineHeight = parseFloat(fonts.lineHeight);
+
+function calculateHeight(rowHeight, isSmallScreen) {
+ let height = rowHeight - 70;
+
+ if (isSmallScreen) {
+ height -= columnPaddingSmallScreen;
+ } else {
+ height -= columnPadding;
+ }
+
+ return height;
+}
+
+class AddNewAlbumSearchResult extends Component {
+
+ //
+ // Lifecycle
+
+ constructor(props, context) {
+ super(props, context);
+
+ this.state = {
+ isNewAddAlbumModalOpen: false
+ };
+ }
+
+ componentDidUpdate(prevProps) {
+ if (!prevProps.isExistingAlbum && this.props.isExistingAlbum) {
+ this.onAddAlbumModalClose();
+ }
+ }
+
+ //
+ // Listeners
+
+ onPress = () => {
+ this.setState({ isNewAddAlbumModalOpen: true });
+ }
+
+ onAddAlbumModalClose = () => {
+ this.setState({ isNewAddAlbumModalOpen: false });
+ }
+
+ onMBLinkPress = (event) => {
+ event.stopPropagation();
+ }
+
+ //
+ // Render
+
+ render() {
+ const {
+ foreignAlbumId,
+ title,
+ releaseDate,
+ disambiguation,
+ albumType,
+ secondaryTypes,
+ overview,
+ ratings,
+ images,
+ releases,
+ artist,
+ isExistingAlbum,
+ isExistingArtist,
+ isSmallScreen
+ } = this.props;
+
+ const {
+ isNewAddAlbumModalOpen
+ } = this.state;
+
+ const linkProps = isExistingAlbum ? { to: `/album/${foreignAlbumId}` } : { onPress: this.onPress };
+
+ const height = calculateHeight(230, isSmallScreen);
+
+ return (
+
+
+ {
+ !isSmallScreen &&
+
+ }
+
+
+
+ {title}
+
+ {
+ !!disambiguation &&
+ ({disambiguation})
+ }
+
+ {
+ isExistingAlbum ?
+ :
+ null
+ }
+
+
+
+
+
+
+
+
+ By: {artist.artistName}
+
+ {
+ isExistingArtist ?
+ :
+ null
+ }
+
+
+
+
+
+
+
+ {
+ !!releaseDate &&
+
+ {moment(releaseDate).format('YYYY')}
+
+ }
+
+
+ {releases.length} release{releases.length > 0 ? 's' : null}
+
+
+ {
+ !!albumType &&
+
+ {albumType}
+
+ }
+
+ {
+ !!secondaryTypes &&
+ secondaryTypes.map((item, i) => {
+ return (
+
+ {item}
+
+ );
+ })
+ }
+
+
+
+
+
+
+
+
+
+
+
+ );
+ }
+}
+
+AddNewAlbumSearchResult.propTypes = {
+ foreignAlbumId: PropTypes.string.isRequired,
+ title: PropTypes.string.isRequired,
+ releaseDate: PropTypes.string.isRequired,
+ disambiguation: PropTypes.string,
+ albumType: PropTypes.string,
+ secondaryTypes: PropTypes.arrayOf(PropTypes.string).isRequired,
+ overview: PropTypes.string,
+ ratings: PropTypes.object.isRequired,
+ artist: PropTypes.object,
+ images: PropTypes.arrayOf(PropTypes.object).isRequired,
+ releases: PropTypes.arrayOf(PropTypes.object).isRequired,
+ isExistingAlbum: PropTypes.bool.isRequired,
+ isExistingArtist: PropTypes.bool.isRequired,
+ isSmallScreen: PropTypes.bool.isRequired
+};
+
+export default AddNewAlbumSearchResult;
diff --git a/frontend/src/Search/Album/AddNewAlbumSearchResultConnector.js b/frontend/src/Search/Album/AddNewAlbumSearchResultConnector.js
new file mode 100644
index 000000000..626e32c94
--- /dev/null
+++ b/frontend/src/Search/Album/AddNewAlbumSearchResultConnector.js
@@ -0,0 +1,17 @@
+import { connect } from 'react-redux';
+import { createSelector } from 'reselect';
+import createDimensionsSelector from 'Store/Selectors/createDimensionsSelector';
+import AddNewAlbumSearchResult from './AddNewAlbumSearchResult';
+
+function createMapStateToProps() {
+ return createSelector(
+ createDimensionsSelector(),
+ (dimensions) => {
+ return {
+ isSmallScreen: dimensions.isSmallScreen
+ };
+ }
+ );
+}
+
+export default connect(createMapStateToProps)(AddNewAlbumSearchResult);
diff --git a/frontend/src/AddArtist/AddNewArtist/AddNewArtistModal.js b/frontend/src/Search/Artist/AddNewArtistModal.js
similarity index 100%
rename from frontend/src/AddArtist/AddNewArtist/AddNewArtistModal.js
rename to frontend/src/Search/Artist/AddNewArtistModal.js
diff --git a/frontend/src/AddArtist/AddNewArtist/AddNewArtistModalContent.css b/frontend/src/Search/Artist/AddNewArtistModalContent.css
similarity index 86%
rename from frontend/src/AddArtist/AddNewArtist/AddNewArtistModalContent.css
rename to frontend/src/Search/Artist/AddNewArtistModalContent.css
index 4c5c747a8..dcc427693 100644
--- a/frontend/src/AddArtist/AddNewArtist/AddNewArtistModalContent.css
+++ b/frontend/src/Search/Artist/AddNewArtistModalContent.css
@@ -17,16 +17,24 @@
flex-grow: 1;
}
+.name {
+ font-weight: 300;
+ font-size: 36px;
+}
+
+.disambiguation {
+ margin-bottom: 20px;
+ color: $disabledColor;
+ font-weight: 300;
+ font-size: 20px;
+}
+
.overview {
margin-bottom: 30px;
max-height: 230px;
text-align: justify;
}
-.labelIcon {
- margin-left: 8px;
-}
-
.searchForMissingAlbumsLabelContainer {
display: flex;
margin-top: 2px;
@@ -58,12 +66,6 @@
composes: button from '~Components/Link/SpinnerButton.css';
}
-.hideMetadataProfile {
- composes: group from '~Components/Form/FormGroup.css';
-
- display: none;
-}
-
@media only screen and (max-width: $breakpointSmall) {
.modalFooter {
display: block;
diff --git a/frontend/src/Search/Artist/AddNewArtistModalContent.js b/frontend/src/Search/Artist/AddNewArtistModalContent.js
new file mode 100644
index 000000000..7ea5865e8
--- /dev/null
+++ b/frontend/src/Search/Artist/AddNewArtistModalContent.js
@@ -0,0 +1,146 @@
+import PropTypes from 'prop-types';
+import React, { Component } from 'react';
+import TextTruncate from 'react-text-truncate';
+import { kinds } from 'Helpers/Props';
+import SpinnerButton from 'Components/Link/SpinnerButton';
+import CheckInput from 'Components/Form/CheckInput';
+import ModalContent from 'Components/Modal/ModalContent';
+import ModalHeader from 'Components/Modal/ModalHeader';
+import ModalBody from 'Components/Modal/ModalBody';
+import ModalFooter from 'Components/Modal/ModalFooter';
+import ArtistPoster from 'Artist/ArtistPoster';
+import AddArtistOptionsForm from '../Common/AddArtistOptionsForm.js';
+import styles from './AddNewArtistModalContent.css';
+
+class AddNewArtistModalContent extends Component {
+
+ //
+ // Lifecycle
+
+ constructor(props, context) {
+ super(props, context);
+
+ this.state = {
+ searchForMissingAlbums: false
+ };
+ }
+
+ //
+ // Listeners
+
+ onSearchForMissingAlbumsChange = ({ value }) => {
+ this.setState({ searchForMissingAlbums: value });
+ }
+
+ onAddArtistPress = () => {
+ this.props.onAddArtistPress(this.state.searchForMissingAlbums);
+ }
+
+ //
+ // Render
+
+ render() {
+ const {
+ artistName,
+ disambiguation,
+ overview,
+ images,
+ isAdding,
+ isSmallScreen,
+ onModalClose,
+ ...otherProps
+ } = this.props;
+
+ return (
+
+
+ Add new Artist
+
+
+
+
+ {
+ isSmallScreen ?
+ null:
+
+ }
+
+
+
+ {artistName}
+
+
+ {
+ !!disambiguation &&
+
({disambiguation})
+ }
+
+ {
+ overview ?
+
+
+
:
+ null
+ }
+
+
+
+
+
+
+
+
+
+
+ Start search for missing albums
+
+
+
+
+
+
+ Add {artistName}
+
+
+
+ );
+ }
+}
+
+AddNewArtistModalContent.propTypes = {
+ artistName: PropTypes.string.isRequired,
+ disambiguation: PropTypes.string.isRequired,
+ overview: PropTypes.string,
+ images: PropTypes.arrayOf(PropTypes.object).isRequired,
+ isAdding: PropTypes.bool.isRequired,
+ addError: PropTypes.object,
+ isSmallScreen: PropTypes.bool.isRequired,
+ onModalClose: PropTypes.func.isRequired,
+ onAddArtistPress: PropTypes.func.isRequired
+};
+
+export default AddNewArtistModalContent;
diff --git a/frontend/src/AddArtist/AddNewArtist/AddNewArtistModalContentConnector.js b/frontend/src/Search/Artist/AddNewArtistModalContentConnector.js
similarity index 85%
rename from frontend/src/AddArtist/AddNewArtist/AddNewArtistModalContentConnector.js
rename to frontend/src/Search/Artist/AddNewArtistModalContentConnector.js
index 049d05813..5a7159010 100644
--- a/frontend/src/AddArtist/AddNewArtist/AddNewArtistModalContentConnector.js
+++ b/frontend/src/Search/Artist/AddNewArtistModalContentConnector.js
@@ -2,22 +2,22 @@ import PropTypes from 'prop-types';
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { createSelector } from 'reselect';
-import { setAddArtistDefault, addArtist } from 'Store/Actions/addArtistActions';
+import { setAddDefault, addArtist } from 'Store/Actions/searchActions';
import createDimensionsSelector from 'Store/Selectors/createDimensionsSelector';
import selectSettings from 'Store/Selectors/selectSettings';
import AddNewArtistModalContent from './AddNewArtistModalContent';
function createMapStateToProps() {
return createSelector(
- (state) => state.addArtist,
+ (state) => state.search,
(state) => state.settings.metadataProfiles,
createDimensionsSelector(),
- (addArtistState, metadataProfiles, dimensions) => {
+ (searchState, metadataProfiles, dimensions) => {
const {
isAdding,
addError,
defaults
- } = addArtistState;
+ } = searchState;
const {
settings,
@@ -28,7 +28,7 @@ function createMapStateToProps() {
return {
isAdding,
addError,
- showMetadataProfile: metadataProfiles.items.length > 1,
+ showMetadataProfile: metadataProfiles.items.length > 2, // NONE (not allowed for artists) and one other
isSmallScreen: dimensions.isSmallScreen,
validationErrors,
validationWarnings,
@@ -39,7 +39,7 @@ function createMapStateToProps() {
}
const mapDispatchToProps = {
- setAddArtistDefault,
+ setAddDefault,
addArtist
};
@@ -49,7 +49,7 @@ class AddNewArtistModalContentConnector extends Component {
// Listeners
onInputChange = ({ name, value }) => {
- this.props.setAddArtistDefault({ [name]: value });
+ this.props.setAddDefault({ [name]: value });
}
onAddArtistPress = (searchForMissingAlbums) => {
@@ -98,7 +98,7 @@ AddNewArtistModalContentConnector.propTypes = {
albumFolder: PropTypes.object.isRequired,
tags: PropTypes.object.isRequired,
onModalClose: PropTypes.func.isRequired,
- setAddArtistDefault: PropTypes.func.isRequired,
+ setAddDefault: PropTypes.func.isRequired,
addArtist: PropTypes.func.isRequired
};
diff --git a/frontend/src/AddArtist/AddNewArtist/AddNewArtistSearchResult.css b/frontend/src/Search/Artist/AddNewArtistSearchResult.css
similarity index 100%
rename from frontend/src/AddArtist/AddNewArtist/AddNewArtistSearchResult.css
rename to frontend/src/Search/Artist/AddNewArtistSearchResult.css
diff --git a/frontend/src/AddArtist/AddNewArtist/AddNewArtistSearchResult.js b/frontend/src/Search/Artist/AddNewArtistSearchResult.js
similarity index 99%
rename from frontend/src/AddArtist/AddNewArtist/AddNewArtistSearchResult.js
rename to frontend/src/Search/Artist/AddNewArtistSearchResult.js
index c74ba8afd..4b2319127 100644
--- a/frontend/src/AddArtist/AddNewArtist/AddNewArtistSearchResult.js
+++ b/frontend/src/Search/Artist/AddNewArtistSearchResult.js
@@ -196,6 +196,7 @@ class AddNewArtistSearchResult extends Component {
isOpen={isNewAddArtistModalOpen && !isExistingArtist}
foreignArtistId={foreignArtistId}
artistName={artistName}
+ disambiguation={disambiguation}
year={year}
overview={overview}
images={images}
diff --git a/frontend/src/AddArtist/AddNewArtist/AddNewArtistSearchResultConnector.js b/frontend/src/Search/Artist/AddNewArtistSearchResultConnector.js
similarity index 100%
rename from frontend/src/AddArtist/AddNewArtist/AddNewArtistSearchResultConnector.js
rename to frontend/src/Search/Artist/AddNewArtistSearchResultConnector.js
diff --git a/frontend/src/Search/Common/AddArtistOptionsForm.css b/frontend/src/Search/Common/AddArtistOptionsForm.css
new file mode 100644
index 000000000..8e3ca9540
--- /dev/null
+++ b/frontend/src/Search/Common/AddArtistOptionsForm.css
@@ -0,0 +1,9 @@
+.labelIcon {
+ margin-left: 8px;
+}
+
+.hideMetadataProfile {
+ composes: group from '~Components/Form/FormGroup.css';
+
+ display: none;
+}
diff --git a/frontend/src/Search/Common/AddArtistOptionsForm.js b/frontend/src/Search/Common/AddArtistOptionsForm.js
new file mode 100644
index 000000000..8203c3e67
--- /dev/null
+++ b/frontend/src/Search/Common/AddArtistOptionsForm.js
@@ -0,0 +1,160 @@
+import PropTypes from 'prop-types';
+import React, { Component } from 'react';
+import { icons, inputTypes, tooltipPositions } from 'Helpers/Props';
+import Form from 'Components/Form/Form';
+import FormGroup from 'Components/Form/FormGroup';
+import FormLabel from 'Components/Form/FormLabel';
+import FormInputGroup from 'Components/Form/FormInputGroup';
+import Icon from 'Components/Icon';
+import Popover from 'Components/Tooltip/Popover';
+import ArtistMonitoringOptionsPopoverContent from 'AddArtist/ArtistMonitoringOptionsPopoverContent';
+import ArtistMetadataProfilePopoverContent from 'AddArtist/ArtistMetadataProfilePopoverContent';
+import styles from './AddArtistOptionsForm.css';
+
+class AddArtistOptionsForm extends Component {
+
+ //
+ // Listeners
+
+ onQualityProfileIdChange = ({ value }) => {
+ this.props.onInputChange({ name: 'qualityProfileId', value: parseInt(value) });
+ }
+
+ onMetadataProfileIdChange = ({ value }) => {
+ this.props.onInputChange({ name: 'metadataProfileId', value: parseInt(value) });
+ }
+
+ //
+ // Render
+
+ render() {
+ const {
+ rootFolderPath,
+ monitor,
+ qualityProfileId,
+ metadataProfileId,
+ includeNoneMetadataProfile,
+ showMetadataProfile,
+ albumFolder,
+ tags,
+ onInputChange,
+ ...otherProps
+ } = this.props;
+
+ return (
+
+ );
+ }
+}
+
+AddArtistOptionsForm.propTypes = {
+ rootFolderPath: PropTypes.object,
+ monitor: PropTypes.object.isRequired,
+ qualityProfileId: PropTypes.object,
+ metadataProfileId: PropTypes.object,
+ showMetadataProfile: PropTypes.bool.isRequired,
+ includeNoneMetadataProfile: PropTypes.bool.isRequired,
+ albumFolder: PropTypes.object.isRequired,
+ tags: PropTypes.object.isRequired,
+ onInputChange: PropTypes.func.isRequired
+};
+
+export default AddArtistOptionsForm;
diff --git a/frontend/src/Settings/ImportLists/ImportListExclusions/EditImportListExclusionModalContent.js b/frontend/src/Settings/ImportLists/ImportListExclusions/EditImportListExclusionModalContent.js
index ccb2fa04a..5aa9d269b 100644
--- a/frontend/src/Settings/ImportLists/ImportListExclusions/EditImportListExclusionModalContent.js
+++ b/frontend/src/Settings/ImportLists/ImportListExclusions/EditImportListExclusionModalContent.js
@@ -58,12 +58,12 @@ function EditImportListExclusionModalContent(props) {
{...otherProps}
>
- Artist Name
+ Entity Name
@@ -75,7 +75,7 @@ function EditImportListExclusionModalContent(props) {
diff --git a/frontend/src/Settings/Profiles/Metadata/MetadataProfiles.js b/frontend/src/Settings/Profiles/Metadata/MetadataProfiles.js
index feccfb77f..236836112 100644
--- a/frontend/src/Settings/Profiles/Metadata/MetadataProfiles.js
+++ b/frontend/src/Settings/Profiles/Metadata/MetadataProfiles.js
@@ -1,7 +1,7 @@
import PropTypes from 'prop-types';
import React, { Component } from 'react';
import sortByName from 'Utilities/Array/sortByName';
-import { icons } from 'Helpers/Props';
+import { icons, metadataProfileNames } from 'Helpers/Props';
import FieldSet from 'Components/FieldSet';
import Card from 'Components/Card';
import Icon from 'Components/Icon';
@@ -58,7 +58,7 @@ class MetadataProfiles extends Component {
>
{
- items.sort(sortByName).map((item) => {
+ items.filter((item) => item.name !== metadataProfileNames.NONE).sort(sortByName).map((item) => {
return (
{
+ return {
+ ...payload,
+ queryParams: {
+ deleteFiles: payload.deleteFiles,
+ addImportListExclusion: payload.addImportListExclusion
+ }
+ };
+});
+
export const setAlbumValue = createAction(SET_ALBUM_VALUE, (payload) => {
return {
section: 'albums',
@@ -141,6 +153,7 @@ export const setAlbumValue = createAction(SET_ALBUM_VALUE, (payload) => {
export const actionHandlers = handleThunks({
[FETCH_ALBUMS]: createFetchHandler(section, '/album'),
[SAVE_ALBUM]: createSaveProviderHandler(section, '/album'),
+ [DELETE_ALBUM]: createRemoveItemHandler(section, '/album'),
[TOGGLE_ALBUM_MONITORED]: function(getState, payload, dispatch) {
const {
diff --git a/frontend/src/Store/Actions/index.js b/frontend/src/Store/Actions/index.js
index 8e04a20cf..ccabf68f0 100644
--- a/frontend/src/Store/Actions/index.js
+++ b/frontend/src/Store/Actions/index.js
@@ -1,4 +1,3 @@
-import * as addArtist from './addArtistActions';
import * as app from './appActions';
import * as blacklist from './blacklistActions';
import * as calendar from './calendarActions';
@@ -24,6 +23,7 @@ import * as artist from './artistActions';
import * as artistEditor from './artistEditorActions';
import * as artistHistory from './artistHistoryActions';
import * as artistIndex from './artistIndexActions';
+import * as search from './searchActions';
import * as settings from './settingsActions';
import * as system from './systemActions';
import * as tags from './tagActions';
@@ -31,7 +31,6 @@ import * as tracks from './trackActions';
import * as wanted from './wantedActions';
export default [
- addArtist,
app,
blacklist,
captcha,
@@ -57,6 +56,7 @@ export default [
artistEditor,
artistHistory,
artistIndex,
+ search,
settings,
system,
tags,
diff --git a/frontend/src/Store/Actions/addArtistActions.js b/frontend/src/Store/Actions/searchActions.js
similarity index 60%
rename from frontend/src/Store/Actions/addArtistActions.js
rename to frontend/src/Store/Actions/searchActions.js
index 44496f6d5..806430fc2 100644
--- a/frontend/src/Store/Actions/addArtistActions.js
+++ b/frontend/src/Store/Actions/searchActions.js
@@ -6,15 +6,15 @@ import getSectionState from 'Utilities/State/getSectionState';
import updateSectionState from 'Utilities/State/updateSectionState';
import createAjaxRequest from 'Utilities/createAjaxRequest';
import getNewArtist from 'Utilities/Artist/getNewArtist';
+import getNewAlbum from 'Utilities/Album/getNewAlbum';
import { createThunk, handleThunks } from 'Store/thunks';
-import createSetSettingValueReducer from './Creators/Reducers/createSetSettingValueReducer';
import createHandleActions from './Creators/createHandleActions';
import { set, update, updateItem } from './baseActions';
//
// Variables
-export const section = 'addArtist';
+export const section = 'search';
let abortCurrentRequest = null;
//
@@ -40,39 +40,33 @@ export const defaultState = {
};
export const persistState = [
- 'addArtist.defaults'
+ 'search.defaults'
];
//
// Actions Types
-export const LOOKUP_ARTIST = 'addArtist/lookupArtist';
-export const ADD_ARTIST = 'addArtist/addArtist';
-export const SET_ADD_ARTIST_VALUE = 'addArtist/setAddArtistValue';
-export const CLEAR_ADD_ARTIST = 'addArtist/clearAddArtist';
-export const SET_ADD_ARTIST_DEFAULT = 'addArtist/setAddArtistDefault';
+export const GET_SEARCH_RESULTS = 'search/getSearchResults';
+export const ADD_ARTIST = 'search/addArtist';
+export const ADD_ALBUM = 'search/addAlbum';
+export const CLEAR_SEARCH_RESULTS = 'search/clearSearchResults';
+export const SET_ADD_DEFAULT = 'search/setAddDefault';
//
// Action Creators
-export const lookupArtist = createThunk(LOOKUP_ARTIST);
+export const getSearchResults = createThunk(GET_SEARCH_RESULTS);
export const addArtist = createThunk(ADD_ARTIST);
-export const clearAddArtist = createAction(CLEAR_ADD_ARTIST);
-export const setAddArtistDefault = createAction(SET_ADD_ARTIST_DEFAULT);
-
-export const setAddArtistValue = createAction(SET_ADD_ARTIST_VALUE, (payload) => {
- return {
- section,
- ...payload
- };
-});
+export const addAlbum = createThunk(ADD_ALBUM);
+export const clearSearchResults = createAction(CLEAR_SEARCH_RESULTS);
+export const setAddDefault = createAction(SET_ADD_DEFAULT);
//
// Action Handlers
export const actionHandlers = handleThunks({
- [LOOKUP_ARTIST]: function(getState, payload, dispatch) {
+ [GET_SEARCH_RESULTS]: function(getState, payload, dispatch) {
dispatch(set({ section, isFetching: true }));
if (abortCurrentRequest) {
@@ -80,7 +74,7 @@ export const actionHandlers = handleThunks({
}
const { request, abortRequest } = createAjaxRequest({
- url: '/artist/lookup',
+ url: '/search',
data: {
term: payload.term
}
@@ -115,8 +109,9 @@ export const actionHandlers = handleThunks({
dispatch(set({ section, isAdding: true }));
const foreignArtistId = payload.foreignArtistId;
- const items = getState().addArtist.items;
- const newArtist = getNewArtist(_.cloneDeep(_.find(items, { foreignArtistId })), payload);
+ const items = getState().search.items;
+ const itemToAdd = _.find(items, { foreignId: foreignArtistId });
+ const newArtist = getNewArtist(_.cloneDeep(itemToAdd.artist), payload);
const promise = createAjaxRequest({
url: '/artist',
@@ -138,6 +133,47 @@ export const actionHandlers = handleThunks({
]));
});
+ promise.fail((xhr) => {
+ dispatch(set({
+ section,
+ isAdding: false,
+ isAdded: false,
+ addError: xhr
+ }));
+ });
+ },
+
+ [ADD_ALBUM]: function(getState, payload, dispatch) {
+ dispatch(set({ section, isAdding: true }));
+
+ const foreignAlbumId = payload.foreignAlbumId;
+ const items = getState().search.items;
+ const itemToAdd = _.find(items, { foreignId: foreignAlbumId });
+ const newAlbum = getNewAlbum(_.cloneDeep(itemToAdd.album), payload);
+
+ const promise = createAjaxRequest({
+ url: '/album',
+ method: 'POST',
+ contentType: 'application/json',
+ data: JSON.stringify(newAlbum)
+ }).request;
+
+ promise.done((data) => {
+ data.releases = itemToAdd.album.releases;
+ itemToAdd.album = data;
+ dispatch(batchActions([
+ updateItem({ section: 'artist', ...data.artist }),
+ updateItem({ section, ...itemToAdd }),
+
+ set({
+ section,
+ isAdding: false,
+ isAdded: true,
+ addError: null
+ })
+ ]));
+ });
+
promise.fail((xhr) => {
dispatch(set({
section,
@@ -154,9 +190,7 @@ export const actionHandlers = handleThunks({
export const reducers = createHandleActions({
- [SET_ADD_ARTIST_VALUE]: createSetSettingValueReducer(section),
-
- [SET_ADD_ARTIST_DEFAULT]: function(state, { payload }) {
+ [SET_ADD_DEFAULT]: function(state, { payload }) {
const newState = getSectionState(state, section);
newState.defaults = {
@@ -167,7 +201,7 @@ export const reducers = createHandleActions({
return updateSectionState(state, section, newState);
},
- [CLEAR_ADD_ARTIST]: function(state) {
+ [CLEAR_SEARCH_RESULTS]: function(state) {
const {
defaults,
...otherDefaultState
diff --git a/frontend/src/Utilities/Album/getNewAlbum.js b/frontend/src/Utilities/Album/getNewAlbum.js
new file mode 100644
index 000000000..2635c4fd9
--- /dev/null
+++ b/frontend/src/Utilities/Album/getNewAlbum.js
@@ -0,0 +1,18 @@
+import getNewArtist from 'Utilities/Artist/getNewArtist';
+
+function getNewAlbum(album, payload) {
+ const {
+ searchForNewAlbum = false
+ } = payload;
+
+ getNewArtist(album.artist, payload);
+
+ album.addOptions = {
+ searchForNewAlbum
+ };
+ album.monitored = true;
+
+ return album;
+}
+
+export default getNewAlbum;
diff --git a/src/Lidarr.Api.V1/Albums/AlbumModule.cs b/src/Lidarr.Api.V1/Albums/AlbumModule.cs
index 6b4dc827a..997c0d5ae 100644
--- a/src/Lidarr.Api.V1/Albums/AlbumModule.cs
+++ b/src/Lidarr.Api.V1/Albums/AlbumModule.cs
@@ -14,31 +14,51 @@ using NzbDrone.Core.Music.Events;
using NzbDrone.Core.MediaFiles.Events;
using NzbDrone.Core.MediaCover;
using NzbDrone.Core.MediaFiles;
+using NzbDrone.Core.Validation.Paths;
+using FluentValidation;
+using NzbDrone.Common.Extensions;
+using NzbDrone.Core.Validation;
namespace Lidarr.Api.V1.Albums
{
public class AlbumModule : AlbumModuleWithSignalR,
IHandle,
IHandle,
+ IHandle,
IHandle,
IHandle,
IHandle
{
protected readonly IReleaseService _releaseService;
+ protected readonly IAddAlbumService _addAlbumService;
public AlbumModule(IAlbumService albumService,
+ IAddAlbumService addAlbumService,
IReleaseService releaseService,
IArtistStatisticsService artistStatisticsService,
IMapCoversToLocal coverMapper,
IUpgradableSpecification upgradableSpecification,
- IBroadcastSignalRMessage signalRBroadcaster)
+ IBroadcastSignalRMessage signalRBroadcaster,
+ ProfileExistsValidator profileExistsValidator,
+ MetadataProfileExistsValidator metadataProfileExistsValidator)
+
: base(albumService, artistStatisticsService, coverMapper, upgradableSpecification, signalRBroadcaster)
{
_releaseService = releaseService;
+ _addAlbumService = addAlbumService;
+
GetResourceAll = GetAlbums;
+ CreateResource = AddAlbum;
UpdateResource = UpdateAlbum;
+ DeleteResource = DeleteAlbum;
Put("/monitor", x => SetAlbumsMonitored());
+
+ PostValidator.RuleFor(s => s.ForeignAlbumId).NotEmpty();
+ PostValidator.RuleFor(s => s.Artist.QualityProfileId).SetValidator(profileExistsValidator);
+ PostValidator.RuleFor(s => s.Artist.MetadataProfileId).SetValidator(metadataProfileExistsValidator);
+ PostValidator.RuleFor(s => s.Artist.RootFolderPath).IsValidPath().When(s => s.Artist.Path.IsNullOrWhiteSpace());
+ PostValidator.RuleFor(s => s.Artist.ForeignArtistId).NotEmpty();
}
private List GetAlbums()
@@ -66,6 +86,11 @@ namespace Lidarr.Api.V1.Albums
var album = _albumService.FindById(foreignAlbumId);
+ if (album == null)
+ {
+ return MapToResource(new List(), false);
+ }
+
if (includeAllArtistAlbumsQuery.HasValue && Convert.ToBoolean(includeAllArtistAlbumsQuery.Value))
{
return MapToResource(_albumService.GetAlbumsByArtist(album.ArtistId), false);
@@ -85,6 +110,13 @@ namespace Lidarr.Api.V1.Albums
return MapToResource(_albumService.GetAlbums(albumIds), false);
}
+ private int AddAlbum(AlbumResource albumResource)
+ {
+ var album = _addAlbumService.AddAlbum(albumResource.ToModel());
+
+ return album.Id;
+ }
+
private void UpdateAlbum(AlbumResource albumResource)
{
var album = _albumService.GetAlbum(albumResource.Id);
@@ -97,6 +129,14 @@ namespace Lidarr.Api.V1.Albums
BroadcastResourceChange(ModelAction.Updated, model.Id);
}
+ private void DeleteAlbum(int id)
+ {
+ var deleteFiles = Request.GetBooleanQueryParameter("deleteFiles");
+ var addImportListExclusion = Request.GetBooleanQueryParameter("addImportListExclusion");
+
+ _albumService.DeleteAlbum(id, deleteFiles, addImportListExclusion);
+ }
+
private object SetAlbumsMonitored()
{
var resource = Request.Body.FromJson();
@@ -122,6 +162,16 @@ namespace Lidarr.Api.V1.Albums
BroadcastResourceChange(ModelAction.Updated, MapToResource(message.Album, true));
}
+ public void Handle(AlbumUpdatedEvent message)
+ {
+ BroadcastResourceChange(ModelAction.Updated, MapToResource(message.Album, true));
+ }
+
+ public void Handle(AlbumDeletedEvent message)
+ {
+ BroadcastResourceChange(ModelAction.Deleted, message.Album.ToResource());
+ }
+
public void Handle(AlbumImportedEvent message)
{
BroadcastResourceChange(ModelAction.Updated, MapToResource(message.Album, true));
diff --git a/src/Lidarr.Api.V1/Albums/AlbumResource.cs b/src/Lidarr.Api.V1/Albums/AlbumResource.cs
index 446df75ef..26b0c00f5 100644
--- a/src/Lidarr.Api.V1/Albums/AlbumResource.cs
+++ b/src/Lidarr.Api.V1/Albums/AlbumResource.cs
@@ -43,7 +43,7 @@ namespace Lidarr.Api.V1.Albums
public List Images { get; set; }
public List Links { get; set; }
public AlbumStatisticsResource Statistics { get; set; }
-
+ public AddAlbumOptions AddOptions { get; set; }
public string RemoteCover { get; set; }
//Hiding this so people don't think its usable (only used to set the initial state)
@@ -88,6 +88,8 @@ namespace Lidarr.Api.V1.Albums
{
if (resource == null) return null;
+ var artist = resource.Artist?.ToModel() ?? new NzbDrone.Core.Music.Artist();
+
return new Album
{
Id = resource.Id,
@@ -96,9 +98,13 @@ namespace Lidarr.Api.V1.Albums
Disambiguation = resource.Disambiguation,
Overview = resource.Overview,
Images = resource.Images,
+ AlbumType = resource.AlbumType,
Monitored = resource.Monitored,
AnyReleaseOk = resource.AnyReleaseOk,
- AlbumReleases = resource.Releases.ToModel()
+ AlbumReleases = resource.Releases.ToModel(),
+ AddOptions = resource.AddOptions,
+ Artist = artist,
+ ArtistMetadata = artist.Metadata.Value
};
}
diff --git a/src/Lidarr.Api.V1/Profiles/Metadata/MetadataProfileModule.cs b/src/Lidarr.Api.V1/Profiles/Metadata/MetadataProfileModule.cs
index fc7774b96..59648b120 100644
--- a/src/Lidarr.Api.V1/Profiles/Metadata/MetadataProfileModule.cs
+++ b/src/Lidarr.Api.V1/Profiles/Metadata/MetadataProfileModule.cs
@@ -12,7 +12,7 @@ namespace Lidarr.Api.V1.Profiles.Metadata
public MetadataProfileModule(IMetadataProfileService profileService)
{
_profileService = profileService;
- SharedValidator.RuleFor(c => c.Name).NotEmpty();
+ SharedValidator.RuleFor(c => c.Name).NotEqual("None").WithMessage("'None' is a reserved profile name").NotEmpty();
SharedValidator.RuleFor(c => c.PrimaryAlbumTypes).MustHaveAllowedPrimaryType();
SharedValidator.RuleFor(c => c.SecondaryAlbumTypes).MustHaveAllowedSecondaryType();
SharedValidator.RuleFor(c => c.ReleaseStatuses).MustHaveAllowedReleaseStatus();
diff --git a/src/Lidarr.Api.V1/Search/SearchModule.cs b/src/Lidarr.Api.V1/Search/SearchModule.cs
new file mode 100644
index 000000000..537acdbec
--- /dev/null
+++ b/src/Lidarr.Api.V1/Search/SearchModule.cs
@@ -0,0 +1,71 @@
+using System.Collections.Generic;
+using System.Linq;
+using Nancy;
+using NzbDrone.Core.MediaCover;
+using NzbDrone.Core.MetadataSource;
+using Lidarr.Http;
+using Lidarr.Api.V1.Artist;
+using Lidarr.Api.V1.Albums;
+using System;
+
+namespace Lidarr.Api.V1.Search
+{
+ public class SearchModule : LidarrRestModule
+ {
+ private readonly ISearchForNewEntity _searchProxy;
+
+ public SearchModule(ISearchForNewEntity searchProxy)
+ : base("/search")
+ {
+ _searchProxy = searchProxy;
+ Get("/", x => Search());
+ }
+
+ private object Search()
+ {
+ var searchResults = _searchProxy.SearchForNewEntity((string)Request.Query.term);
+ return MapToResource(searchResults).ToList();
+ }
+
+ private static IEnumerable MapToResource(IEnumerable results)
+ {
+ int id = 1;
+ foreach (var result in results)
+ {
+ var resource = new SearchResource();
+ resource.Id = id++;
+
+ if (result is NzbDrone.Core.Music.Artist)
+ {
+ var artist = (NzbDrone.Core.Music.Artist) result;
+ resource.Artist = artist.ToResource();
+ resource.ForeignId = artist.ForeignArtistId;
+
+ var poster = artist.Metadata.Value.Images.FirstOrDefault(c => c.CoverType == MediaCoverTypes.Poster);
+ if (poster != null)
+ {
+ resource.Artist.RemotePoster = poster.Url;
+ }
+ }
+ else if (result is NzbDrone.Core.Music.Album)
+ {
+ var album = (NzbDrone.Core.Music.Album) result;
+ resource.Album = album.ToResource();
+ resource.ForeignId = album.ForeignAlbumId;
+
+ var cover = album.Images.FirstOrDefault(c => c.CoverType == MediaCoverTypes.Cover);
+ if (cover != null)
+ {
+ resource.Album.RemoteCover = cover.Url;
+ }
+ }
+ else
+ {
+ throw new NotImplementedException("Bad response from search all proxy");
+ }
+
+ yield return resource;
+ }
+ }
+ }
+}
diff --git a/src/Lidarr.Api.V1/Search/SearchResource.cs b/src/Lidarr.Api.V1/Search/SearchResource.cs
new file mode 100644
index 000000000..bdcff3784
--- /dev/null
+++ b/src/Lidarr.Api.V1/Search/SearchResource.cs
@@ -0,0 +1,14 @@
+using Lidarr.Http.REST;
+using Lidarr.Api.V1.Artist;
+using Lidarr.Api.V1.Albums;
+
+namespace Lidarr.Api.V1.Search
+{
+ public class
+ SearchResource : RestResource
+ {
+ public string ForeignId { get; set; }
+ public ArtistResource Artist { get; set; }
+ public AlbumResource Album { get; set; }
+ }
+}
diff --git a/src/NzbDrone.Automation.Test/MainPagesTest.cs b/src/NzbDrone.Automation.Test/MainPagesTest.cs
index 95eae8f8d..4f818d92d 100644
--- a/src/NzbDrone.Automation.Test/MainPagesTest.cs
+++ b/src/NzbDrone.Automation.Test/MainPagesTest.cs
@@ -73,7 +73,7 @@ namespace NzbDrone.Automation.Test
page.WaitForNoSpinner();
- page.Find(By.CssSelector("input[class*='AddNewArtist-searchInput']")).Should().NotBeNull();
+ page.Find(By.CssSelector("input[class*='AddNewItem-searchInput']")).Should().NotBeNull();
}
}
}
diff --git a/src/NzbDrone.Core.Test/Download/TrackedDownloads/TrackedDownloadServiceFixture.cs b/src/NzbDrone.Core.Test/Download/TrackedDownloads/TrackedDownloadServiceFixture.cs
index 1335e6186..23b9006b4 100644
--- a/src/NzbDrone.Core.Test/Download/TrackedDownloads/TrackedDownloadServiceFixture.cs
+++ b/src/NzbDrone.Core.Test/Download/TrackedDownloads/TrackedDownloadServiceFixture.cs
@@ -115,7 +115,7 @@ namespace NzbDrone.Core.Test.Download.TrackedDownloads
.Returns(default(RemoteAlbum));
// handle deletion event
- Subject.Handle(new AlbumDeletedEvent(remoteAlbum.Albums.First(), false));
+ Subject.Handle(new AlbumDeletedEvent(remoteAlbum.Albums.First(), false, false));
// verify download has null remote album
var trackedDownloads = Subject.GetTrackedDownloads();
diff --git a/src/NzbDrone.Core.Test/ImportListTests/ImportListSyncServiceFixture.cs b/src/NzbDrone.Core.Test/ImportListTests/ImportListSyncServiceFixture.cs
index 20e36bdc9..9baafa8dc 100644
--- a/src/NzbDrone.Core.Test/ImportListTests/ImportListSyncServiceFixture.cs
+++ b/src/NzbDrone.Core.Test/ImportListTests/ImportListSyncServiceFixture.cs
@@ -72,6 +72,13 @@ namespace NzbDrone.Core.Test.ImportListTests
.Returns(new Artist{ForeignArtistId = _importListReports.First().ArtistMusicBrainzId });
}
+ private void WithExistingAlbum()
+ {
+ Mocker.GetMock()
+ .Setup(v => v.FindById(_importListReports.First().AlbumMusicBrainzId))
+ .Returns(new Album{ForeignAlbumId = _importListReports.First().AlbumMusicBrainzId });
+ }
+
private void WithExcludedArtist()
{
Mocker.GetMock()
@@ -83,6 +90,17 @@ namespace NzbDrone.Core.Test.ImportListTests
});
}
+ private void WithExcludedAlbum()
+ {
+ Mocker.GetMock()
+ .Setup(v => v.All())
+ .Returns(new List {
+ new ImportListExclusion {
+ ForeignId = "09474d62-17dd-3a4f-98fb-04c65f38a479"
+ }
+ });
+ }
+
private void WithMonitorType(ImportListMonitorType monitor)
{
Mocker.GetMock()
@@ -120,17 +138,7 @@ namespace NzbDrone.Core.Test.ImportListTests
}
[Test]
- public void should_search_with_lidarr_id_if_album_id_and_no_artist_id()
- {
- WithAlbumId();
- Subject.Execute(new ImportListSyncCommand());
-
- Mocker.GetMock()
- .Verify(v => v.SearchForNewAlbum($"lidarr:{_importListReports.First().AlbumMusicBrainzId}", null), Times.Once());
- }
-
- [Test]
- public void should_not_search_if_album_id_and_artist_id()
+ public void should_not_search_if_album_title_and_album_id()
{
WithArtistId();
WithAlbumId();
@@ -159,8 +167,6 @@ namespace NzbDrone.Core.Test.ImportListTests
public void should_not_add_if_existing_artist()
{
WithArtistId();
- WithAlbum();
- WithAlbumId();
WithExistingArtist();
Subject.Execute(new ImportListSyncCommand());
@@ -169,14 +175,37 @@ namespace NzbDrone.Core.Test.ImportListTests
.Verify(v => v.AddArtists(It.Is>(t=>t.Count == 0)));
}
+ [Test]
+ public void should_not_add_if_existing_album()
+ {
+ WithAlbumId();
+ WithExistingAlbum();
+
+ Subject.Execute(new ImportListSyncCommand());
+
+ Mocker.GetMock()
+ .Verify(v => v.AddArtists(It.Is>(t=>t.Count == 0)));
+ }
+
+ [Test]
+ public void should_add_if_existing_artist_but_new_album()
+ {
+ WithAlbumId();
+ WithExistingArtist();
+
+ Subject.Execute(new ImportListSyncCommand());
+
+ Mocker.GetMock()
+ .Verify(v => v.AddAlbums(It.Is>(t=>t.Count == 1)));
+ }
+
+
[TestCase(ImportListMonitorType.None, false)]
[TestCase(ImportListMonitorType.SpecificAlbum, true)]
[TestCase(ImportListMonitorType.EntireArtist, true)]
public void should_add_if_not_existing_artist(ImportListMonitorType monitor, bool expectedArtistMonitored)
{
WithArtistId();
- WithAlbum();
- WithAlbumId();
WithMonitorType(monitor);
Subject.Execute(new ImportListSyncCommand());
@@ -185,57 +214,55 @@ namespace NzbDrone.Core.Test.ImportListTests
.Verify(v => v.AddArtists(It.Is>(t => t.Count == 1 && t.First().Monitored == expectedArtistMonitored)));
}
- [Test]
- public void should_not_add_if_excluded_artist()
+ [TestCase(ImportListMonitorType.None, false)]
+ [TestCase(ImportListMonitorType.SpecificAlbum, true)]
+ [TestCase(ImportListMonitorType.EntireArtist, true)]
+ public void should_add_if_not_existing_album(ImportListMonitorType monitor, bool expectedAlbumMonitored)
{
- WithArtistId();
- WithAlbum();
WithAlbumId();
- WithExcludedArtist();
+ WithMonitorType(monitor);
Subject.Execute(new ImportListSyncCommand());
- Mocker.GetMock()
- .Verify(v => v.AddArtists(It.Is>(t => t.Count == 0)));
+ Mocker.GetMock()
+ .Verify(v => v.AddAlbums(It.Is>(t => t.Count == 1 && t.First().Monitored == expectedAlbumMonitored)));
}
[Test]
- public void should_mark_album_for_monitor_if_album_id_and_specific_monitor_selected()
+ public void should_not_add_artist_if_excluded_artist()
{
WithArtistId();
- WithAlbum();
- WithAlbumId();
- WithMonitorType(ImportListMonitorType.SpecificAlbum);
+ WithExcludedArtist();
Subject.Execute(new ImportListSyncCommand());
Mocker.GetMock()
- .Verify(v => v.AddArtists(It.Is>(t => t.Count == 1 && t.First().AddOptions.AlbumsToMonitor.Contains("09474d62-17dd-3a4f-98fb-04c65f38a479"))));
+ .Verify(v => v.AddArtists(It.Is>(t => t.Count == 0)));
}
[Test]
- public void should_not_mark_album_for_monitor_if_album_id_and_monitor_all_selected()
+ public void should_not_add_album_if_excluded_album()
{
- WithArtistId();
- WithAlbum();
WithAlbumId();
- WithMonitorType(ImportListMonitorType.EntireArtist);
+ WithExcludedAlbum();
Subject.Execute(new ImportListSyncCommand());
- Mocker.GetMock()
- .Verify(v => v.AddArtists(It.Is>(t => t.Count == 1 && !t.First().AddOptions.AlbumsToMonitor.Any())));
+ Mocker.GetMock()
+ .Verify(v => v.AddAlbums(It.Is>(t => t.Count == 0)));
}
[Test]
- public void should_not_mark_album_for_monitor_if_no_album_id()
+ public void should_not_add_album_if_excluded_artist()
{
+ WithAlbumId();
WithArtistId();
+ WithExcludedArtist();
Subject.Execute(new ImportListSyncCommand());
- Mocker.GetMock()
- .Verify(v => v.AddArtists(It.Is>(t => t.Count == 1 && t.First().AddOptions.AlbumsToMonitor.Count == 0)));
+ Mocker.GetMock()
+ .Verify(v => v.AddAlbums(It.Is>(t => t.Count == 0)));
}
}
}
diff --git a/src/NzbDrone.Core.Test/MetadataSource/SkyHook/SkyHookProxyFixture.cs b/src/NzbDrone.Core.Test/MetadataSource/SkyHook/SkyHookProxyFixture.cs
index 50607b816..fe5ef827c 100644
--- a/src/NzbDrone.Core.Test/MetadataSource/SkyHook/SkyHookProxyFixture.cs
+++ b/src/NzbDrone.Core.Test/MetadataSource/SkyHook/SkyHookProxyFixture.cs
@@ -15,7 +15,6 @@ using NzbDrone.Core.MetadataSource.SkyHook.Resource;
namespace NzbDrone.Core.Test.MetadataSource.SkyHook
{
[TestFixture]
- [IntegrationTest]
public class SkyHookProxyFixture : CoreTest
{
private MetadataProfile _metadataProfile;
diff --git a/src/NzbDrone.Core.Test/MetadataSource/SkyHook/SkyHookProxySearchFixture.cs b/src/NzbDrone.Core.Test/MetadataSource/SkyHook/SkyHookProxySearchFixture.cs
index d1666981d..fede0a9b5 100644
--- a/src/NzbDrone.Core.Test/MetadataSource/SkyHook/SkyHookProxySearchFixture.cs
+++ b/src/NzbDrone.Core.Test/MetadataSource/SkyHook/SkyHookProxySearchFixture.cs
@@ -3,16 +3,15 @@ using NUnit.Framework;
using NzbDrone.Core.MetadataSource.SkyHook;
using NzbDrone.Core.Test.Framework;
using NzbDrone.Test.Common;
-using NzbDrone.Test.Common.Categories;
using Moq;
using NzbDrone.Core.Profiles.Metadata;
using NzbDrone.Core.Music;
using System.Collections.Generic;
+using System;
namespace NzbDrone.Core.Test.MetadataSource.SkyHook
{
[TestFixture]
- [IntegrationTest]
public class SkyHookProxySearchFixture : CoreTest
{
[SetUp]
@@ -107,5 +106,28 @@ namespace NzbDrone.Core.Test.MetadataSource.SkyHook
ExceptionVerification.IgnoreWarns();
}
+
+ [TestCase("Eminem", 0, typeof(Artist), "Eminem")]
+ [TestCase("Eminem Kamikaze", 0, typeof(Artist), "Eminem")]
+ [TestCase("Eminem Kamikaze", 1, typeof(Album), "Kamikaze")]
+ public void successful_combined_search(string query, int position, Type resultType, string expected)
+ {
+ var result = Subject.SearchForNewEntity(query);
+ result.Should().NotBeEmpty();
+ result[position].GetType().Should().Be(resultType);
+
+ if (resultType == typeof(Artist))
+ {
+ var cast = result[position] as Artist;
+ cast.Should().NotBeNull();
+ cast.Name.Should().Be(expected);
+ }
+ else
+ {
+ var cast = result[position] as Album;
+ cast.Should().NotBeNull();
+ cast.Title.Should().Be(expected);
+ }
+ }
}
}
diff --git a/src/NzbDrone.Core.Test/MusicTests/AddAlbumFixture.cs b/src/NzbDrone.Core.Test/MusicTests/AddAlbumFixture.cs
new file mode 100644
index 000000000..44e934e86
--- /dev/null
+++ b/src/NzbDrone.Core.Test/MusicTests/AddAlbumFixture.cs
@@ -0,0 +1,96 @@
+using System.Collections.Generic;
+using FizzWare.NBuilder;
+using FluentAssertions;
+using FluentValidation;
+using Moq;
+using NUnit.Framework;
+using NzbDrone.Core.Exceptions;
+using NzbDrone.Core.MetadataSource;
+using NzbDrone.Core.Organizer;
+using NzbDrone.Core.Music;
+using NzbDrone.Core.Test.Framework;
+using NzbDrone.Test.Common;
+using System;
+
+namespace NzbDrone.Core.Test.MusicTests
+{
+ [TestFixture]
+ public class AddAlbumFixture : CoreTest
+ {
+ private Artist _fakeArtist;
+ private Album _fakeAlbum;
+
+ [SetUp]
+ public void Setup()
+ {
+ _fakeAlbum = Builder
+ .CreateNew()
+ .Build();
+
+ _fakeArtist = Builder
+ .CreateNew()
+ .With(s => s.Path = null)
+ .With(s => s.Metadata = Builder.CreateNew().Build())
+ .Build();
+ }
+
+ private void GivenValidAlbum(string lidarrId)
+ {
+ Mocker.GetMock()
+ .Setup(s => s.GetAlbumInfo(lidarrId))
+ .Returns(Tuple.Create(_fakeArtist.Metadata.Value.ForeignArtistId,
+ _fakeAlbum,
+ new List { _fakeArtist.Metadata.Value }));
+
+ Mocker.GetMock()
+ .Setup(s => s.AddArtist(It.IsAny(), It.IsAny()))
+ .Returns(_fakeArtist);
+ }
+
+ private void GivenValidPath()
+ {
+ Mocker.GetMock()
+ .Setup(s => s.GetArtistFolder(It.IsAny(), null))
+ .Returns((c, n) => c.Name);
+ }
+
+ private Album AlbumToAdd(string albumId, string artistId)
+ {
+ return new Album
+ {
+ ForeignAlbumId = albumId,
+ ArtistMetadata = new ArtistMetadata
+ {
+ ForeignArtistId = artistId
+ }
+ };
+ }
+
+ [Test]
+ public void should_be_able_to_add_a_album_without_passing_in_name()
+ {
+ var newAlbum = AlbumToAdd("5537624c-3d2f-4f5c-8099-df916082c85c", "cc2c9c3c-b7bc-4b8b-84d8-4fbd8779e493");
+
+ GivenValidAlbum(newAlbum.ForeignAlbumId);
+ GivenValidPath();
+
+ var album = Subject.AddAlbum(newAlbum);
+
+ album.Title.Should().Be(_fakeAlbum.Title);
+ }
+
+ [Test]
+ public void should_throw_if_album_cannot_be_found()
+ {
+ var newAlbum = AlbumToAdd("5537624c-3d2f-4f5c-8099-df916082c85c", "cc2c9c3c-b7bc-4b8b-84d8-4fbd8779e493");
+
+ Mocker.GetMock()
+ .Setup(s => s.GetAlbumInfo(newAlbum.ForeignAlbumId))
+ .Throws(new AlbumNotFoundException(newAlbum.ForeignAlbumId));
+
+ Assert.Throws(() => Subject.AddAlbum(newAlbum));
+
+ ExceptionVerification.ExpectedErrors(1);
+ }
+ }
+}
diff --git a/src/NzbDrone.Core.Test/MusicTests/AddArtistFixture.cs b/src/NzbDrone.Core.Test/MusicTests/AddArtistFixture.cs
index 1f04fa6a3..49f101465 100644
--- a/src/NzbDrone.Core.Test/MusicTests/AddArtistFixture.cs
+++ b/src/NzbDrone.Core.Test/MusicTests/AddArtistFixture.cs
@@ -1,4 +1,3 @@
-using System;
using System.Collections.Generic;
using System.IO;
using FizzWare.NBuilder;
diff --git a/src/NzbDrone.Core.Test/MusicTests/RefreshAlbumServiceFixture.cs b/src/NzbDrone.Core.Test/MusicTests/RefreshAlbumServiceFixture.cs
index d1a44e647..fdd4ac8c2 100644
--- a/src/NzbDrone.Core.Test/MusicTests/RefreshAlbumServiceFixture.cs
+++ b/src/NzbDrone.Core.Test/MusicTests/RefreshAlbumServiceFixture.cs
@@ -172,7 +172,7 @@ namespace NzbDrone.Core.Test.MusicTests
Subject.RefreshAlbumInfo(album, null, false);
Mocker.GetMock()
- .Verify(x => x.DeleteAlbum(album.Id, true),
+ .Verify(x => x.DeleteAlbum(album.Id, true, false),
Times.Once());
ExceptionVerification.ExpectedWarns(1);
diff --git a/src/NzbDrone.Core.Test/MusicTests/RefreshArtistServiceFixture.cs b/src/NzbDrone.Core.Test/MusicTests/RefreshArtistServiceFixture.cs
index be648afb6..2f7c1631e 100644
--- a/src/NzbDrone.Core.Test/MusicTests/RefreshArtistServiceFixture.cs
+++ b/src/NzbDrone.Core.Test/MusicTests/RefreshArtistServiceFixture.cs
@@ -1,6 +1,4 @@
-using System;
using System.Collections.Generic;
-using System.Linq;
using FizzWare.NBuilder;
using Moq;
using NUnit.Framework;
@@ -14,6 +12,7 @@ using NzbDrone.Test.Common;
using NzbDrone.Core.MediaFiles;
using NzbDrone.Core.History;
using NzbDrone.Core.Music.Events;
+using NzbDrone.Core.ImportLists.Exclusions;
namespace NzbDrone.Core.Test.MusicTests
{
@@ -24,6 +23,7 @@ namespace NzbDrone.Core.Test.MusicTests
private Album _album1;
private Album _album2;
private List _albums;
+ private List _remoteAlbums;
[SetUp]
public void Setup()
@@ -38,6 +38,9 @@ namespace NzbDrone.Core.Test.MusicTests
_albums = new List {_album1, _album2};
+ _remoteAlbums = _albums.JsonClone();
+ _remoteAlbums.ForEach(x => x.Id = 0);
+
var metadata = Builder.CreateNew().Build();
_artist = Builder.CreateNew()
@@ -62,6 +65,10 @@ namespace NzbDrone.Core.Test.MusicTests
Mocker.GetMock()
.Setup(x => x.GetByArtist(It.IsAny(), It.IsAny()))
.Returns(new List());
+
+ Mocker.GetMock()
+ .Setup(x => x.FindByForeignId(It.IsAny>()))
+ .Returns(new List());
}
private void GivenNewArtistInfo(Artist artist)
@@ -78,11 +85,11 @@ namespace NzbDrone.Core.Test.MusicTests
.Returns(Builder.CreateListOfSize(1).BuildList());
}
- private void GivenAlbumsForRefresh()
+ private void GivenAlbumsForRefresh(List albums)
{
Mocker.GetMock(MockBehavior.Strict)
.Setup(s => s.GetAlbumsForRefresh(It.IsAny(), It.IsAny>()))
- .Returns(new List());
+ .Returns(albums);
}
private void AllowArtistUpdate()
@@ -97,10 +104,10 @@ namespace NzbDrone.Core.Test.MusicTests
{
var newArtistInfo = _artist.JsonClone();
newArtistInfo.Metadata = _artist.Metadata.Value.JsonClone();
- newArtistInfo.Albums = _albums;
+ newArtistInfo.Albums = _remoteAlbums;
GivenNewArtistInfo(newArtistInfo);
- GivenAlbumsForRefresh();
+ GivenAlbumsForRefresh(_albums);
AllowArtistUpdate();
Subject.Execute(new RefreshArtistCommand(_artist.Id));
@@ -117,10 +124,10 @@ namespace NzbDrone.Core.Test.MusicTests
newArtistInfo.Metadata.Value.Images = new List {
new MediaCover.MediaCover(MediaCover.MediaCoverTypes.Logo, "dummy")
};
- newArtistInfo.Albums = _albums;
+ newArtistInfo.Albums = _remoteAlbums;
GivenNewArtistInfo(newArtistInfo);
- GivenAlbumsForRefresh();
+ GivenAlbumsForRefresh(new List());
AllowArtistUpdate();
Subject.Execute(new RefreshArtistCommand(_artist.Id));
@@ -151,7 +158,7 @@ namespace NzbDrone.Core.Test.MusicTests
public void should_log_error_but_not_delete_if_musicbrainz_id_not_found_and_artist_has_files()
{
GivenArtistFiles();
- GivenAlbumsForRefresh();
+ GivenAlbumsForRefresh(new List());
Subject.Execute(new RefreshArtistCommand(_artist.Id));
@@ -169,7 +176,7 @@ namespace NzbDrone.Core.Test.MusicTests
{
var newArtistInfo = _artist.JsonClone();
newArtistInfo.Metadata = _artist.Metadata.Value.JsonClone();
- newArtistInfo.Albums = _albums;
+ newArtistInfo.Albums = _remoteAlbums;
newArtistInfo.ForeignArtistId = _artist.ForeignArtistId + 1;
newArtistInfo.Metadata.Value.Id = 100;
@@ -222,8 +229,7 @@ namespace NzbDrone.Core.Test.MusicTests
var newArtistInfo = clash.JsonClone();
newArtistInfo.Metadata = clash.Metadata.Value.JsonClone();
- newArtistInfo.Albums = _albums.JsonClone();
- newArtistInfo.Albums.Value.ForEach(x => x.Id = 0);
+ newArtistInfo.Albums = _remoteAlbums;
GivenNewArtistInfo(newArtistInfo);
diff --git a/src/NzbDrone.Core.Test/Profiles/Metadata/MetadataProfileServiceFixture.cs b/src/NzbDrone.Core.Test/Profiles/Metadata/MetadataProfileServiceFixture.cs
index 729b68c34..ffc369b22 100644
--- a/src/NzbDrone.Core.Test/Profiles/Metadata/MetadataProfileServiceFixture.cs
+++ b/src/NzbDrone.Core.Test/Profiles/Metadata/MetadataProfileServiceFixture.cs
@@ -3,11 +3,12 @@ using FizzWare.NBuilder;
using Moq;
using NUnit.Framework;
using NzbDrone.Core.ImportLists;
-using NzbDrone.Core.ImportLists.HeadphonesImport;
using NzbDrone.Core.Lifecycle;
using NzbDrone.Core.Test.Framework;
using NzbDrone.Core.Music;
using NzbDrone.Core.Profiles.Metadata;
+using System.Collections.Generic;
+using System;
namespace NzbDrone.Core.Test.Profiles.Metadata
{
@@ -21,7 +22,7 @@ namespace NzbDrone.Core.Test.Profiles.Metadata
Subject.Handle(new ApplicationStartedEvent());
Mocker.GetMock()
- .Verify(v => v.Insert(It.IsAny()), Times.Once());
+ .Verify(v => v.Insert(It.IsAny()), Times.Exactly(2));
}
[Test]
@@ -36,9 +37,72 @@ namespace NzbDrone.Core.Test.Profiles.Metadata
Subject.Handle(new ApplicationStartedEvent());
Mocker.GetMock()
- .Verify(v => v.Insert(It.IsAny()), Times.Never());
+ .Verify(v => v.Insert(It.Is(x => x.Name == "Standard")), Times.Never());
}
+ [Test]
+ public void init_should_add_none_profile_if_it_doesnt_exist()
+ {
+ Mocker.GetMock()
+ .Setup(s => s.All())
+ .Returns(Builder.CreateListOfSize(2).Build().ToList());
+
+ Subject.Handle(new ApplicationStartedEvent());
+
+ Mocker.GetMock()
+ .Verify(v => v.Insert(It.Is(x => x.Name == "None")), Times.Once());
+ }
+
+ [Test]
+ public void init_should_move_existing_none_profile()
+ {
+ var profiles = Builder.CreateListOfSize(2)
+ .TheFirst(1)
+ .With(x => x.Name = MetadataProfileService.NONE_PROFILE_NAME)
+ .With(x => x.PrimaryAlbumTypes = new List
+ {
+ new ProfilePrimaryAlbumTypeItem
+ {
+ PrimaryAlbumType = PrimaryAlbumType.Album,
+ Allowed = true
+ }
+ })
+ .BuildList();
+
+ Mocker.GetMock()
+ .Setup(s => s.All())
+ .Returns(profiles);
+
+ Subject.Handle(new ApplicationStartedEvent());
+
+ Mocker.GetMock()
+ .Verify(v => v.Update(It.Is(x => x.Id == profiles.First().Id && x.Name == "None.1")), Times.Once());
+
+ Mocker.GetMock()
+ .Verify(v => v.Insert(It.Is(x => x.Name == "None")), Times.Once());
+ }
+
+ [Test]
+ public void should_not_be_able_to_edit_none_profile()
+ {
+ var profile = Builder.CreateNew()
+ .With(p => p.Name = MetadataProfileService.NONE_PROFILE_NAME)
+ .Build();
+
+ Assert.Throws(() => Subject.Update(profile));
+ }
+
+ [Test]
+ public void should_not_be_able_to_delete_none_profile()
+ {
+ var profile = Builder.CreateNew()
+ .With(p => p.Name = MetadataProfileService.NONE_PROFILE_NAME)
+ .Build();
+
+ Mocker.GetMock().Setup(c => c.Get(profile.Id)).Returns(profile);
+
+ Assert.Throws(() => Subject.Delete(profile.Id));
+ }
[Test]
public void should_not_be_able_to_delete_profile_if_assigned_to_artist()
@@ -94,10 +158,13 @@ namespace NzbDrone.Core.Test.Profiles.Metadata
}
-
[Test]
public void should_delete_profile_if_not_assigned_to_artist_or_import_list()
{
+ var profile = Builder.CreateNew()
+ .With(p => p.Id = 1)
+ .Build();
+
var artistList = Builder.CreateListOfSize(3)
.All()
.With(c => c.MetadataProfileId = 2)
@@ -110,6 +177,7 @@ namespace NzbDrone.Core.Test.Profiles.Metadata
Mocker.GetMock().Setup(c => c.GetAllArtists()).Returns(artistList);
Mocker.GetMock().Setup(c => c.All()).Returns(importLists);
+ Mocker.GetMock().Setup(c => c.Get(profile.Id)).Returns(profile);
Subject.Delete(1);
diff --git a/src/NzbDrone.Core/ArtistStats/ArtistStatisticsService.cs b/src/NzbDrone.Core/ArtistStats/ArtistStatisticsService.cs
index 1d77713b9..2ebf8d61c 100644
--- a/src/NzbDrone.Core/ArtistStats/ArtistStatisticsService.cs
+++ b/src/NzbDrone.Core/ArtistStats/ArtistStatisticsService.cs
@@ -17,6 +17,8 @@ namespace NzbDrone.Core.ArtistStats
public class ArtistStatisticsService : IArtistStatisticsService,
IHandle,
IHandle,
+ IHandle,
+ IHandle,
IHandle,
IHandle,
IHandle
@@ -77,6 +79,20 @@ namespace NzbDrone.Core.ArtistStats
_cache.Remove(message.Artist.Id.ToString());
}
+ [EventHandleOrder(EventHandleOrder.First)]
+ public void Handle(AlbumAddedEvent message)
+ {
+ _cache.Remove("AllArtists");
+ _cache.Remove(message.Album.ArtistId.ToString());
+ }
+
+ [EventHandleOrder(EventHandleOrder.First)]
+ public void Handle(AlbumDeletedEvent message)
+ {
+ _cache.Remove("AllArtists");
+ _cache.Remove(message.Album.ArtistId.ToString());
+ }
+
[EventHandleOrder(EventHandleOrder.First)]
public void Handle(AlbumImportedEvent message)
{
diff --git a/src/NzbDrone.Core/ImportLists/Exclusions/ImportListExclusionRepository.cs b/src/NzbDrone.Core/ImportLists/Exclusions/ImportListExclusionRepository.cs
index 11e538374..d980b776f 100644
--- a/src/NzbDrone.Core/ImportLists/Exclusions/ImportListExclusionRepository.cs
+++ b/src/NzbDrone.Core/ImportLists/Exclusions/ImportListExclusionRepository.cs
@@ -1,5 +1,6 @@
using NzbDrone.Core.Datastore;
using NzbDrone.Core.Messaging.Events;
+using System.Collections.Generic;
using System.Linq;
namespace NzbDrone.Core.ImportLists.Exclusions
@@ -7,6 +8,7 @@ namespace NzbDrone.Core.ImportLists.Exclusions
public interface IImportListExclusionRepository : IBasicRepository
{
ImportListExclusion FindByForeignId(string foreignId);
+ List FindByForeignId(List ids);
}
public class ImportListExclusionRepository : BasicRepository, IImportListExclusionRepository
@@ -20,5 +22,10 @@ namespace NzbDrone.Core.ImportLists.Exclusions
{
return Query.Where(m => m.ForeignId == foreignId).SingleOrDefault();
}
+
+ public List FindByForeignId(List ids)
+ {
+ return Query.Where($"[ForeignId] IN ('{string.Join("', '", ids)}')").ToList();
+ }
}
}
diff --git a/src/NzbDrone.Core/ImportLists/Exclusions/ImportListExclusionService.cs b/src/NzbDrone.Core/ImportLists/Exclusions/ImportListExclusionService.cs
index 343e04a88..a91c837d8 100644
--- a/src/NzbDrone.Core/ImportLists/Exclusions/ImportListExclusionService.cs
+++ b/src/NzbDrone.Core/ImportLists/Exclusions/ImportListExclusionService.cs
@@ -10,12 +10,16 @@ namespace NzbDrone.Core.ImportLists.Exclusions
ImportListExclusion Add(ImportListExclusion importListExclusion);
List All();
void Delete(int id);
+ void Delete(string foreignId);
ImportListExclusion Get(int id);
ImportListExclusion FindByForeignId(string foreignId);
+ List FindByForeignId(List foreignIds);
ImportListExclusion Update(ImportListExclusion importListExclusion);
}
- public class ImportListExclusionService : IImportListExclusionService, IHandleAsync
+ public class ImportListExclusionService : IImportListExclusionService,
+ IHandleAsync,
+ IHandleAsync
{
private readonly IImportListExclusionRepository _repo;
@@ -39,6 +43,15 @@ namespace NzbDrone.Core.ImportLists.Exclusions
_repo.Delete(id);
}
+ public void Delete(string foreignId)
+ {
+ var exclusion = FindByForeignId(foreignId);
+ if (exclusion != null)
+ {
+ Delete(exclusion.Id);
+ }
+ }
+
public ImportListExclusion Get(int id)
{
return _repo.Get(id);
@@ -49,6 +62,11 @@ namespace NzbDrone.Core.ImportLists.Exclusions
return _repo.FindByForeignId(foreignId);
}
+ public List FindByForeignId(List foreignIds)
+ {
+ return _repo.FindByForeignId(foreignIds);
+ }
+
public List All()
{
return _repo.All().ToList();
@@ -76,5 +94,28 @@ namespace NzbDrone.Core.ImportLists.Exclusions
_repo.Insert(importExclusion);
}
+
+ public void HandleAsync(AlbumDeletedEvent message)
+ {
+ if (!message.AddImportListExclusion)
+ {
+ return;
+ }
+
+ var existingExclusion = _repo.FindByForeignId(message.Album.ForeignAlbumId);
+
+ if (existingExclusion != null)
+ {
+ return;
+ }
+
+ var importExclusion = new ImportListExclusion
+ {
+ ForeignId = message.Album.ForeignAlbumId,
+ Name = $"{message.Album.ArtistMetadata.Value.Name} - {message.Album.Title}"
+ };
+
+ _repo.Insert(importExclusion);
+ }
}
}
diff --git a/src/NzbDrone.Core/ImportLists/ImportListSyncService.cs b/src/NzbDrone.Core/ImportLists/ImportListSyncService.cs
index 438e69596..03acd940e 100644
--- a/src/NzbDrone.Core/ImportLists/ImportListSyncService.cs
+++ b/src/NzbDrone.Core/ImportLists/ImportListSyncService.cs
@@ -20,19 +20,23 @@ namespace NzbDrone.Core.ImportLists
private readonly ISearchForNewAlbum _albumSearchService;
private readonly ISearchForNewArtist _artistSearchService;
private readonly IArtistService _artistService;
+ private readonly IAlbumService _albumService;
private readonly IAddArtistService _addArtistService;
+ private readonly IAddAlbumService _addAlbumService;
private readonly IEventAggregator _eventAggregator;
private readonly Logger _logger;
public ImportListSyncService(IImportListFactory importListFactory,
- IImportListExclusionService importListExclusionService,
- IFetchAndParseImportList listFetcherAndParser,
- ISearchForNewAlbum albumSearchService,
- ISearchForNewArtist artistSearchService,
- IArtistService artistService,
- IAddArtistService addArtistService,
- IEventAggregator eventAggregator,
- Logger logger)
+ IImportListExclusionService importListExclusionService,
+ IFetchAndParseImportList listFetcherAndParser,
+ ISearchForNewAlbum albumSearchService,
+ ISearchForNewArtist artistSearchService,
+ IArtistService artistService,
+ IAlbumService albumService,
+ IAddArtistService addArtistService,
+ IAddAlbumService addAlbumService,
+ IEventAggregator eventAggregator,
+ Logger logger)
{
_importListFactory = importListFactory;
_importListExclusionService = importListExclusionService;
@@ -40,7 +44,9 @@ namespace NzbDrone.Core.ImportLists
_albumSearchService = albumSearchService;
_artistSearchService = artistSearchService;
_artistService = artistService;
+ _albumService = albumService;
_addArtistService = addArtistService;
+ _addAlbumService = addAlbumService;
_eventAggregator = eventAggregator;
_logger = logger;
}
@@ -74,6 +80,7 @@ namespace NzbDrone.Core.ImportLists
{
var processed = new List();
var artistsToAdd = new List();
+ var albumsToAdd = new List();
_logger.ProgressInfo("Processing {0} list items", reports.Count);
@@ -89,68 +96,156 @@ namespace NzbDrone.Core.ImportLists
var importList = _importListFactory.Get(report.ImportListId);
- // Map MBid if we only have an album title
- if (report.AlbumMusicBrainzId.IsNullOrWhiteSpace() && report.Album.IsNotNullOrWhiteSpace())
+ if (report.Album.IsNotNullOrWhiteSpace() || report.AlbumMusicBrainzId.IsNotNullOrWhiteSpace())
{
- var mappedAlbum = _albumSearchService.SearchForNewAlbum(report.Album, report.Artist)
- .FirstOrDefault();
-
- if (mappedAlbum == null) continue; // Break if we are looking for an album and cant find it. This will avoid us from adding the artist and possibly getting it wrong.
+ if (report.AlbumMusicBrainzId.IsNullOrWhiteSpace() || report.ArtistMusicBrainzId.IsNullOrWhiteSpace())
+ {
+ MapAlbumReport(report);
+ }
- report.AlbumMusicBrainzId = mappedAlbum.ForeignAlbumId;
- report.Album = mappedAlbum.Title;
- report.Artist = mappedAlbum.ArtistMetadata?.Value?.Name;
- report.ArtistMusicBrainzId = mappedAlbum?.ArtistMetadata?.Value?.ForeignArtistId;
+ ProcessAlbumReport(importList, report, listExclusions, albumsToAdd);
+ }
+ else if (report.Artist.IsNotNullOrWhiteSpace() || report.ArtistMusicBrainzId.IsNotNullOrWhiteSpace())
+ {
+ if (report.ArtistMusicBrainzId.IsNullOrWhiteSpace())
+ {
+ MapArtistReport(report);
+ }
+ ProcessArtistReport(importList, report, listExclusions, artistsToAdd);
}
+ }
- // Map artist ID if we only have album ID
- if (report.AlbumMusicBrainzId.IsNotNullOrWhiteSpace() && report.ArtistMusicBrainzId.IsNullOrWhiteSpace())
- {
- var mappedAlbum = _albumSearchService.SearchForNewAlbum($"lidarr:{report.AlbumMusicBrainzId}", null)
- .FirstOrDefault();
+ _addArtistService.AddArtists(artistsToAdd);
+ _addAlbumService.AddAlbums(albumsToAdd);
- if (mappedAlbum == null) continue;
+ var message = string.Format($"Import List Sync Completed. Items found: {reports.Count}, Artists added: {artistsToAdd.Count}, Albums added: {albumsToAdd.Count}");
- report.Artist = mappedAlbum.ArtistMetadata?.Value?.Name;
- report.ArtistMusicBrainzId = mappedAlbum?.ArtistMetadata?.Value?.ForeignArtistId;
- }
+ _logger.ProgressInfo(message);
- // Map MBid if we only have a artist name
- if (report.ArtistMusicBrainzId.IsNullOrWhiteSpace() && report.Artist.IsNotNullOrWhiteSpace())
- {
- var mappedArtist = _artistSearchService.SearchForNewArtist(report.Artist)
- .FirstOrDefault();
- report.ArtistMusicBrainzId = mappedArtist?.Metadata.Value?.ForeignArtistId;
- report.Artist = mappedArtist?.Metadata.Value?.Name;
- }
+ return processed;
+ }
- // Check to see if artist in DB
- var existingArtist = _artistService.FindById(report.ArtistMusicBrainzId);
+ private void MapAlbumReport(ImportListItemInfo report)
+ {
+ var albumQuery = report.AlbumMusicBrainzId.IsNotNullOrWhiteSpace() ? $"lidarr:{report.AlbumMusicBrainzId}" : report.Album;
+ var mappedAlbum = _albumSearchService.SearchForNewAlbum(albumQuery, report.Artist)
+ .FirstOrDefault();
- // TODO: Rework this for albums when we can add albums seperate from Artists
- // (If list contains albums we should not break for an existing artist, we should add new albums that are not in DB)
- if (existingArtist != null)
- {
- _logger.Debug("{0} [{1}] Rejected, Artist Exists in DB", report.ArtistMusicBrainzId, report.Artist);
- continue;
- }
+ // Break if we are looking for an album and cant find it. This will avoid us from adding the artist and possibly getting it wrong.
+ if (mappedAlbum == null)
+ {
+ return;
+ }
- // Check to see if artist excluded
- var excludedArtist = listExclusions.Where(s => s.ForeignId == report.ArtistMusicBrainzId).SingleOrDefault();
+ report.AlbumMusicBrainzId = mappedAlbum.ForeignAlbumId;
+ report.Album = mappedAlbum.Title;
+ report.Artist = mappedAlbum.ArtistMetadata?.Value?.Name;
+ report.ArtistMusicBrainzId = mappedAlbum.ArtistMetadata?.Value?.ForeignArtistId;
+ }
- if (excludedArtist != null)
- {
- _logger.Debug("{0} [{1}] Rejected due to list exlcusion", report.ArtistMusicBrainzId, report.Artist);
- continue;
- }
+ private void ProcessAlbumReport(ImportListDefinition importList, ImportListItemInfo report, List listExclusions, List albumsToAdd)
+ {
+ if (report.AlbumMusicBrainzId == null)
+ {
+ return;
+ }
- // Append Artist if not already in DB or already on add list
- if (artistsToAdd.All(s => s.Metadata.Value.ForeignArtistId != report.ArtistMusicBrainzId))
- {
- var monitored = importList.ShouldMonitor != ImportListMonitorType.None;
+ // Check to see if album in DB
+ var existingAlbum = _albumService.FindById(report.AlbumMusicBrainzId);
+
+ if (existingAlbum != null)
+ {
+ _logger.Debug("{0} [{1}] Rejected, Album Exists in DB", report.AlbumMusicBrainzId, report.Album);
+ return;
+ }
+
+ // Check to see if album excluded
+ var excludedAlbum = listExclusions.SingleOrDefault(s => s.ForeignId == report.AlbumMusicBrainzId);
+
+ if (excludedAlbum != null)
+ {
+ _logger.Debug("{0} [{1}] Rejected due to list exlcusion", report.AlbumMusicBrainzId, report.Album);
+ return;
+ }
+
+ // Check to see if artist excluded
+ var excludedArtist = listExclusions.SingleOrDefault(s => s.ForeignId == report.ArtistMusicBrainzId);
+
+ if (excludedArtist != null)
+ {
+ _logger.Debug("{0} [{1}] Rejected due to list exlcusion for parent artist", report.AlbumMusicBrainzId, report.Album);
+ return;
+ }
+
+ // Append Album if not already in DB or already on add list
+ if (albumsToAdd.All(s => s.ForeignAlbumId != report.AlbumMusicBrainzId))
+ {
+ var monitored = importList.ShouldMonitor != ImportListMonitorType.None;
- artistsToAdd.Add(new Artist
+ albumsToAdd.Add(new Album
+ {
+ ForeignAlbumId = report.AlbumMusicBrainzId,
+ Monitored = monitored,
+ Artist = new Artist
+ {
+ Monitored = monitored,
+ RootFolderPath = importList.RootFolderPath,
+ QualityProfileId = importList.ProfileId,
+ MetadataProfileId = importList.MetadataProfileId,
+ Tags = importList.Tags,
+ AlbumFolder = true,
+ AddOptions = new AddArtistOptions
+ {
+ SearchForMissingAlbums = monitored,
+ Monitored = monitored,
+ Monitor = monitored ? MonitorTypes.All : MonitorTypes.None
+ }
+ },
+
+ });
+ }
+ }
+
+ private void MapArtistReport(ImportListItemInfo report)
+ {
+ var mappedArtist = _artistSearchService.SearchForNewArtist(report.Artist)
+ .FirstOrDefault();
+ report.ArtistMusicBrainzId = mappedArtist?.Metadata.Value?.ForeignArtistId;
+ report.Artist = mappedArtist?.Metadata.Value?.Name;
+ }
+
+ private void ProcessArtistReport(ImportListDefinition importList, ImportListItemInfo report, List listExclusions, List artistsToAdd)
+ {
+ if (report.ArtistMusicBrainzId == null)
+ {
+ return;
+ }
+
+ // Check to see if artist in DB
+ var existingArtist = _artistService.FindById(report.ArtistMusicBrainzId);
+
+ if (existingArtist != null)
+ {
+ _logger.Debug("{0} [{1}] Rejected, Artist Exists in DB", report.ArtistMusicBrainzId, report.Artist);
+ return;
+ }
+
+ // Check to see if artist excluded
+ var excludedArtist = listExclusions.Where(s => s.ForeignId == report.ArtistMusicBrainzId).SingleOrDefault();
+
+ if (excludedArtist != null)
+ {
+ _logger.Debug("{0} [{1}] Rejected due to list exlcusion", report.ArtistMusicBrainzId, report.Artist);
+ return;
+ }
+
+ // Append Artist if not already in DB or already on add list
+ if (artistsToAdd.All(s => s.Metadata.Value.ForeignArtistId != report.ArtistMusicBrainzId))
+ {
+ var monitored = importList.ShouldMonitor != ImportListMonitorType.None;
+
+ artistsToAdd.Add(new Artist
{
Metadata = new ArtistMetadata
{
@@ -170,22 +265,7 @@ namespace NzbDrone.Core.ImportLists
Monitor = monitored ? MonitorTypes.All : MonitorTypes.None
}
});
- }
-
- // Add Album so we know what to monitor
- if (report.AlbumMusicBrainzId.IsNotNullOrWhiteSpace() && artistsToAdd.Any(s => s.Metadata.Value.ForeignArtistId == report.ArtistMusicBrainzId) && importList.ShouldMonitor == ImportListMonitorType.SpecificAlbum)
- {
- artistsToAdd.Find(s => s.Metadata.Value.ForeignArtistId == report.ArtistMusicBrainzId).AddOptions.AlbumsToMonitor.Add(report.AlbumMusicBrainzId);
- }
}
-
- _addArtistService.AddArtists(artistsToAdd);
-
- var message = string.Format("Import List Sync Completed. Items found: {0}, Artists added: {1}", reports.Count, artistsToAdd.Count);
-
- _logger.ProgressInfo(message);
-
- return processed;
}
public void Execute(ImportListSyncCommand message)
diff --git a/src/NzbDrone.Core/MediaCover/MediaCoverService.cs b/src/NzbDrone.Core/MediaCover/MediaCoverService.cs
index 99cc1090c..9e63225d3 100644
--- a/src/NzbDrone.Core/MediaCover/MediaCoverService.cs
+++ b/src/NzbDrone.Core/MediaCover/MediaCoverService.cs
@@ -26,6 +26,7 @@ namespace NzbDrone.Core.MediaCover
public class MediaCoverService :
IHandleAsync,
IHandleAsync,
+ IHandleAsync,
IMapCoversToLocal
{
private readonly IImageResizer _resizer;
@@ -292,5 +293,13 @@ namespace NzbDrone.Core.MediaCover
}
}
+ public void HandleAsync(AlbumDeletedEvent message)
+ {
+ var path = GetAlbumCoverPath(message.Album.Id);
+ if (_diskProvider.FolderExists(path))
+ {
+ _diskProvider.DeleteFolder(path, true);
+ }
+ }
}
}
diff --git a/src/NzbDrone.Core/MediaFiles/MediaFileDeletionService.cs b/src/NzbDrone.Core/MediaFiles/MediaFileDeletionService.cs
index c8124a94d..463f893e6 100644
--- a/src/NzbDrone.Core/MediaFiles/MediaFileDeletionService.cs
+++ b/src/NzbDrone.Core/MediaFiles/MediaFileDeletionService.cs
@@ -22,6 +22,7 @@ namespace NzbDrone.Core.MediaFiles
public class MediaFileDeletionService : IDeleteMediaFiles,
IHandleAsync,
+ IHandleAsync,
IHandle
{
private readonly IDiskProvider _diskProvider;
@@ -128,6 +129,18 @@ namespace NzbDrone.Core.MediaFiles
}
}
+ public void HandleAsync(AlbumDeletedEvent message)
+ {
+ if (message.DeleteFiles)
+ {
+ var files = _mediaFileService.GetFilesByAlbum(message.Album.Id);
+ foreach (var file in files)
+ {
+ _recycleBinProvider.DeleteFile(file.Path);
+ }
+ }
+ }
+
[EventHandleOrder(EventHandleOrder.Last)]
public void Handle(TrackFileDeletedEvent message)
{
diff --git a/src/NzbDrone.Core/MediaFiles/MediaFileRepository.cs b/src/NzbDrone.Core/MediaFiles/MediaFileRepository.cs
index 175d13f10..b8f0da825 100644
--- a/src/NzbDrone.Core/MediaFiles/MediaFileRepository.cs
+++ b/src/NzbDrone.Core/MediaFiles/MediaFileRepository.cs
@@ -17,6 +17,7 @@ namespace NzbDrone.Core.MediaFiles
List GetFilesWithBasePath(string path);
TrackFile GetFileWithPath(string path);
void DeleteFilesByAlbum(int albumId);
+ void UnlinkFilesByAlbum(int albumId);
}
@@ -70,6 +71,13 @@ namespace NzbDrone.Core.MediaFiles
DeleteMany(ids);
}
+ public void UnlinkFilesByAlbum(int albumId)
+ {
+ var files = DataMapper.Query().Where(x => x.AlbumId == albumId).ToList();
+ files.ForEach(x => x.AlbumId = 0);
+ SetFields(files, f => f.AlbumId);
+ }
+
public List GetFilesByRelease(int releaseId)
{
return Query
diff --git a/src/NzbDrone.Core/MediaFiles/MediaFileService.cs b/src/NzbDrone.Core/MediaFiles/MediaFileService.cs
index ea947992f..f446e0ca3 100644
--- a/src/NzbDrone.Core/MediaFiles/MediaFileService.cs
+++ b/src/NzbDrone.Core/MediaFiles/MediaFileService.cs
@@ -160,7 +160,14 @@ namespace NzbDrone.Core.MediaFiles
public void HandleAsync(AlbumDeletedEvent message)
{
- _mediaFileRepository.DeleteFilesByAlbum(message.Album.Id);
+ if (message.DeleteFiles)
+ {
+ _mediaFileRepository.DeleteFilesByAlbum(message.Album.Id);
+ }
+ else
+ {
+ _mediaFileRepository.UnlinkFilesByAlbum(message.Album.Id);
+ }
}
public List GetFilesByArtist(int artistId)
diff --git a/src/NzbDrone.Core/MetadataSource/ISearchForNewEntity.cs b/src/NzbDrone.Core/MetadataSource/ISearchForNewEntity.cs
new file mode 100644
index 000000000..30a0125f2
--- /dev/null
+++ b/src/NzbDrone.Core/MetadataSource/ISearchForNewEntity.cs
@@ -0,0 +1,10 @@
+using System;
+using System.Collections.Generic;
+
+namespace NzbDrone.Core.MetadataSource
+{
+ public interface ISearchForNewEntity
+ {
+ List SearchForNewEntity(string title);
+ }
+}
diff --git a/src/NzbDrone.Core/MetadataSource/SkyHook/Resource/EntityResource.cs b/src/NzbDrone.Core/MetadataSource/SkyHook/Resource/EntityResource.cs
new file mode 100644
index 000000000..96e803ab0
--- /dev/null
+++ b/src/NzbDrone.Core/MetadataSource/SkyHook/Resource/EntityResource.cs
@@ -0,0 +1,10 @@
+namespace NzbDrone.Core.MetadataSource.SkyHook.Resource
+{
+ public class EntityResource
+ {
+ public int Score { get; set; }
+ public ArtistResource Artist { get; set; }
+ public AlbumResource Album { get; set; }
+
+ }
+}
diff --git a/src/NzbDrone.Core/MetadataSource/SkyHook/SkyHookProxy.cs b/src/NzbDrone.Core/MetadataSource/SkyHook/SkyHookProxy.cs
index a5af778b9..351714efb 100644
--- a/src/NzbDrone.Core/MetadataSource/SkyHook/SkyHookProxy.cs
+++ b/src/NzbDrone.Core/MetadataSource/SkyHook/SkyHookProxy.cs
@@ -3,7 +3,6 @@ using System.Collections.Generic;
using System.Linq;
using System.Net;
using NLog;
-using NzbDrone.Common.Cloud;
using NzbDrone.Common.Extensions;
using NzbDrone.Common.Http;
using NzbDrone.Core.Exceptions;
@@ -15,7 +14,7 @@ using NzbDrone.Core.Profiles.Metadata;
namespace NzbDrone.Core.MetadataSource.SkyHook
{
- public class SkyHookProxy : IProvideArtistInfo, ISearchForNewArtist, IProvideAlbumInfo, ISearchForNewAlbum
+ public class SkyHookProxy : IProvideArtistInfo, ISearchForNewArtist, IProvideAlbumInfo, ISearchForNewAlbum, ISearchForNewEntity
{
private readonly IHttpClient _httpClient;
private readonly Logger _logger;
@@ -247,6 +246,7 @@ namespace NzbDrone.Core.MetadataSource.SkyHook
.AddQueryParam("type", "album")
.AddQueryParam("query", title.ToLower().Trim())
.AddQueryParam("artist", artist.IsNotNullOrWhiteSpace() ? artist.ToLower().Trim() : string.Empty)
+ .AddQueryParam("includeTracks", "1")
.Build();
@@ -266,6 +266,31 @@ namespace NzbDrone.Core.MetadataSource.SkyHook
}
}
+ public List SearchForNewEntity(string title)
+ {
+ try
+ {
+ var httpRequest = _requestBuilder.GetRequestBuilder().Create()
+ .SetSegment("route", "search")
+ .AddQueryParam("type", "all")
+ .AddQueryParam("query", title.ToLower().Trim())
+ .Build();
+
+ var httpResponse = _httpClient.Get>(httpRequest);
+
+ return httpResponse.Resource.SelectList(MapSearchResult);
+ }
+ catch (HttpException)
+ {
+ throw new SkyHookException("Search for '{0}' failed. Unable to communicate with LidarrAPI.", title);
+ }
+ catch (Exception ex)
+ {
+ _logger.Warn(ex, ex.Message);
+ throw new SkyHookException("Search for '{0}' failed. Invalid response received from LidarrAPI.", title);
+ }
+ }
+
private Artist MapSearchResult(ArtistResource resource)
{
var artist = _artistService.FindById(resource.Id);
@@ -280,20 +305,34 @@ namespace NzbDrone.Core.MetadataSource.SkyHook
private Album MapSearchResult(AlbumResource resource)
{
- var album = _albumService.FindById(resource.Id) ?? MapAlbum(resource, null);
+ var artists = resource.Artists.Select(MapArtistMetadata).ToDictionary(x => x.ForeignArtistId, x => x);
var artist = _artistService.FindById(resource.ArtistId);
if (artist == null)
{
artist = new Artist();
- artist.Metadata = MapArtistMetadata(resource.Artists.Single(x => x.Id == resource.ArtistId));
+ artist.Metadata = artists[resource.ArtistId];
}
+
+ var album = _albumService.FindById(resource.Id) ?? MapAlbum(resource, artists);
album.Artist = artist;
- album.ArtistMetadata = artist.Metadata;
+ album.ArtistMetadata = artist.Metadata.Value;
return album;
}
+ private Object MapSearchResult(EntityResource resource)
+ {
+ if (resource.Artist != null)
+ {
+ return MapSearchResult(resource.Artist);
+ }
+ else
+ {
+ return MapSearchResult(resource.Album);
+ }
+ }
+
private static Album MapAlbum(AlbumResource resource, Dictionary artistDict)
{
Album album = new Album();
diff --git a/src/NzbDrone.Core/Music/ArtistNameNormalizer.cs b/src/NzbDrone.Core/Music/ArtistNameNormalizer.cs
deleted file mode 100644
index 30bebbc71..000000000
--- a/src/NzbDrone.Core/Music/ArtistNameNormalizer.cs
+++ /dev/null
@@ -1,28 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Text;
-
-namespace NzbDrone.Core.Music
-{
-
- public static class ArtistNameNormalizer
- {
- private readonly static Dictionary PreComputedTitles = new Dictionary
- {
- { "281588", "a to z" },
- { "266757", "ad trials triumph early church" },
- { "289260", "ad bible continues"}
- };
-
- public static string Normalize(string title, string mbID)
- {
- if (PreComputedTitles.ContainsKey(mbID))
- {
- return PreComputedTitles[mbID];
- }
-
- return Parser.Parser.NormalizeTitle(title).ToLower();
- }
- }
-}
diff --git a/src/NzbDrone.Core/Music/Events/AlbumAddedEvent.cs b/src/NzbDrone.Core/Music/Events/AlbumAddedEvent.cs
index 985cc419a..bbd98811e 100644
--- a/src/NzbDrone.Core/Music/Events/AlbumAddedEvent.cs
+++ b/src/NzbDrone.Core/Music/Events/AlbumAddedEvent.cs
@@ -1,8 +1,4 @@
using NzbDrone.Common.Messaging;
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Text;
namespace NzbDrone.Core.Music.Events
{
diff --git a/src/NzbDrone.Core/Music/Events/AlbumDeletedEvent.cs b/src/NzbDrone.Core/Music/Events/AlbumDeletedEvent.cs
index 28548116c..f5630c30d 100644
--- a/src/NzbDrone.Core/Music/Events/AlbumDeletedEvent.cs
+++ b/src/NzbDrone.Core/Music/Events/AlbumDeletedEvent.cs
@@ -10,11 +10,13 @@ namespace NzbDrone.Core.Music.Events
{
public Album Album { get; private set; }
public bool DeleteFiles { get; private set; }
+ public bool AddImportListExclusion { get; private set; }
- public AlbumDeletedEvent(Album album, bool deleteFiles)
+ public AlbumDeletedEvent(Album album, bool deleteFiles, bool addImportListExclusion)
{
Album = album;
DeleteFiles = deleteFiles;
+ AddImportListExclusion = addImportListExclusion;
}
}
}
diff --git a/src/NzbDrone.Core/Music/Events/AlbumUpdatedEvent.cs b/src/NzbDrone.Core/Music/Events/AlbumUpdatedEvent.cs
new file mode 100644
index 000000000..30fc7b86b
--- /dev/null
+++ b/src/NzbDrone.Core/Music/Events/AlbumUpdatedEvent.cs
@@ -0,0 +1,14 @@
+using NzbDrone.Common.Messaging;
+
+namespace NzbDrone.Core.Music.Events
+{
+ public class AlbumUpdatedEvent : IEvent
+ {
+ public Album Album { get; private set; }
+
+ public AlbumUpdatedEvent(Album album)
+ {
+ Album = album;
+ }
+ }
+}
diff --git a/src/NzbDrone.Core/Music/Events/ArtistAddedEvent.cs b/src/NzbDrone.Core/Music/Events/ArtistAddedEvent.cs
index d8b374ac3..b4d827df1 100644
--- a/src/NzbDrone.Core/Music/Events/ArtistAddedEvent.cs
+++ b/src/NzbDrone.Core/Music/Events/ArtistAddedEvent.cs
@@ -1,18 +1,16 @@
using NzbDrone.Common.Messaging;
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Text;
namespace NzbDrone.Core.Music.Events
{
public class ArtistAddedEvent : IEvent
{
public Artist Artist { get; private set; }
+ public bool DoRefresh { get; private set; }
- public ArtistAddedEvent(Artist artist)
+ public ArtistAddedEvent(Artist artist, bool doRefresh = true)
{
Artist = artist;
+ DoRefresh = doRefresh;
}
}
}
diff --git a/src/NzbDrone.Core/Music/Handlers/AlbumAddedHandler.cs b/src/NzbDrone.Core/Music/Handlers/AlbumAddedHandler.cs
new file mode 100644
index 000000000..e0c4da146
--- /dev/null
+++ b/src/NzbDrone.Core/Music/Handlers/AlbumAddedHandler.cs
@@ -0,0 +1,22 @@
+using NzbDrone.Core.Messaging.Commands;
+using NzbDrone.Core.Messaging.Events;
+using NzbDrone.Core.Music.Commands;
+using NzbDrone.Core.Music.Events;
+
+namespace NzbDrone.Core.Music
+{
+ public class AlbumAddedHandler : IHandle
+ {
+ private readonly IManageCommandQueue _commandQueueManager;
+
+ public AlbumAddedHandler(IManageCommandQueue commandQueueManager)
+ {
+ _commandQueueManager = commandQueueManager;
+ }
+
+ public void Handle(AlbumAddedEvent message)
+ {
+ _commandQueueManager.Push(new RefreshArtistCommand(message.Album.Artist.Value.Id));
+ }
+ }
+}
diff --git a/src/NzbDrone.Core/Music/ArtistAddedHandler.cs b/src/NzbDrone.Core/Music/Handlers/ArtistAddedHandler.cs
similarity index 84%
rename from src/NzbDrone.Core/Music/ArtistAddedHandler.cs
rename to src/NzbDrone.Core/Music/Handlers/ArtistAddedHandler.cs
index 5e98d6cc0..a7cad3030 100644
--- a/src/NzbDrone.Core/Music/ArtistAddedHandler.cs
+++ b/src/NzbDrone.Core/Music/Handlers/ArtistAddedHandler.cs
@@ -18,7 +18,10 @@ namespace NzbDrone.Core.Music
public void Handle(ArtistAddedEvent message)
{
- _commandQueueManager.Push(new RefreshArtistCommand(message.Artist.Id, true));
+ if (message.DoRefresh)
+ {
+ _commandQueueManager.Push(new RefreshArtistCommand(message.Artist.Id, true));
+ }
}
public void Handle(ArtistsImportedEvent message)
diff --git a/src/NzbDrone.Core/Music/ArtistScannedHandler.cs b/src/NzbDrone.Core/Music/Handlers/ArtistScannedHandler.cs
similarity index 64%
rename from src/NzbDrone.Core/Music/ArtistScannedHandler.cs
rename to src/NzbDrone.Core/Music/Handlers/ArtistScannedHandler.cs
index db21cdfcd..0be7f1089 100644
--- a/src/NzbDrone.Core/Music/ArtistScannedHandler.cs
+++ b/src/NzbDrone.Core/Music/Handlers/ArtistScannedHandler.cs
@@ -12,41 +12,40 @@ namespace NzbDrone.Core.Music
private readonly IAlbumMonitoredService _albumMonitoredService;
private readonly IArtistService _artistService;
private readonly IManageCommandQueue _commandQueueManager;
- //private readonly IEpisodeAddedService _episodeAddedService;
+ private readonly IAlbumAddedService _albumAddedService;
private readonly Logger _logger;
public ArtistScannedHandler(IAlbumMonitoredService albumMonitoredService,
IArtistService artistService,
IManageCommandQueue commandQueueManager,
- //IEpisodeAddedService episodeAddedService,
+ IAlbumAddedService albumAddedService,
Logger logger)
{
_albumMonitoredService = albumMonitoredService;
_artistService = artistService;
_commandQueueManager = commandQueueManager;
- //_episodeAddedService = episodeAddedService;
+ _albumAddedService = albumAddedService;
_logger = logger;
}
private void HandleScanEvents(Artist artist)
{
- if (artist.AddOptions == null)
+ if (artist.AddOptions != null)
{
- //_episodeAddedService.SearchForRecentlyAdded(series.Id);
- return;
- }
+ _logger.Info("[{0}] was recently added, performing post-add actions", artist.Name);
+ _albumMonitoredService.SetAlbumMonitoredStatus(artist, artist.AddOptions);
- _logger.Info("[{0}] was recently added, performing post-add actions", artist.Name);
- _albumMonitoredService.SetAlbumMonitoredStatus(artist, artist.AddOptions);
+ if (artist.AddOptions.SearchForMissingAlbums)
+ {
+ _commandQueueManager.Push(new MissingAlbumSearchCommand(artist.Id));
+ }
- if (artist.AddOptions.SearchForMissingAlbums)
- {
- _commandQueueManager.Push(new MissingAlbumSearchCommand(artist.Id));
+ artist.AddOptions = null;
+ _artistService.RemoveAddOptions(artist);
}
- artist.AddOptions = null;
- _artistService.RemoveAddOptions(artist);
+ _albumAddedService.SearchForRecentlyAdded(artist.Id);
}
public void Handle(ArtistScannedEvent message)
diff --git a/src/NzbDrone.Core/Music/Model/AddAlbumOptions.cs b/src/NzbDrone.Core/Music/Model/AddAlbumOptions.cs
new file mode 100644
index 000000000..3e059c7e8
--- /dev/null
+++ b/src/NzbDrone.Core/Music/Model/AddAlbumOptions.cs
@@ -0,0 +1,22 @@
+using NzbDrone.Core.Datastore;
+
+namespace NzbDrone.Core.Music
+{
+ public class AddAlbumOptions : IEmbeddedDocument
+ {
+ public AddAlbumOptions()
+ {
+ // default in case not set in db
+ AddType = AlbumAddType.Automatic;
+ }
+
+ public AlbumAddType AddType { get; set; }
+ public bool SearchForNewAlbum { get; set; }
+ }
+
+ public enum AlbumAddType
+ {
+ Automatic,
+ Manual
+ }
+}
diff --git a/src/NzbDrone.Core/Music/AddArtistOptions.cs b/src/NzbDrone.Core/Music/Model/AddArtistOptions.cs
similarity index 64%
rename from src/NzbDrone.Core/Music/AddArtistOptions.cs
rename to src/NzbDrone.Core/Music/Model/AddArtistOptions.cs
index 842a44aae..8ef3ca72c 100644
--- a/src/NzbDrone.Core/Music/AddArtistOptions.cs
+++ b/src/NzbDrone.Core/Music/Model/AddArtistOptions.cs
@@ -1,8 +1,3 @@
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Text;
-
namespace NzbDrone.Core.Music
{
public class AddArtistOptions : MonitoringOptions
diff --git a/src/NzbDrone.Core/Music/Album.cs b/src/NzbDrone.Core/Music/Model/Album.cs
similarity index 88%
rename from src/NzbDrone.Core/Music/Album.cs
rename to src/NzbDrone.Core/Music/Model/Album.cs
index 2ece54b34..df339537d 100644
--- a/src/NzbDrone.Core/Music/Album.cs
+++ b/src/NzbDrone.Core/Music/Model/Album.cs
@@ -12,14 +12,14 @@ namespace NzbDrone.Core.Music
public Album()
{
OldForeignAlbumIds = new List();
-
+ Overview = string.Empty;
Images = new List();
Links = new List();
Genres = new List();
SecondaryTypes = new List();
Ratings = new Ratings();
Artist = new Artist();
-
+ AddOptions = new AddAlbumOptions();
}
// These correspond to columns in the Albums table
@@ -34,7 +34,7 @@ namespace NzbDrone.Core.Music
public List Images { get; set; }
public List Links { get; set; }
public List Genres { get; set; }
- public String AlbumType { get; set; }
+ public string AlbumType { get; set; }
public List SecondaryTypes { get; set; }
public Ratings Ratings { get; set; }
@@ -46,7 +46,7 @@ namespace NzbDrone.Core.Music
public DateTime? LastInfoSync { get; set; }
public DateTime Added { get; set; }
[MemberwiseEqualityIgnore]
- public AddArtistOptions AddOptions { get; set; }
+ public AddAlbumOptions AddOptions { get; set; }
// These are dynamically queried from other tables
[MemberwiseEqualityIgnore]
@@ -94,13 +94,13 @@ namespace NzbDrone.Core.Music
AddOptions = other.AddOptions;
}
- public override void ApplyChanges(Album otherAlbum)
+ public override void ApplyChanges(Album other)
{
- ForeignAlbumId = otherAlbum.ForeignAlbumId;
- ProfileId = otherAlbum.ProfileId;
- AddOptions = otherAlbum.AddOptions;
- Monitored = otherAlbum.Monitored;
- AnyReleaseOk = otherAlbum.AnyReleaseOk;
+ ForeignAlbumId = other.ForeignAlbumId;
+ ProfileId = other.ProfileId;
+ AddOptions = other.AddOptions;
+ Monitored = other.Monitored;
+ AnyReleaseOk = other.AnyReleaseOk;
}
}
}
diff --git a/src/NzbDrone.Core/Music/Artist.cs b/src/NzbDrone.Core/Music/Model/Artist.cs
similarity index 82%
rename from src/NzbDrone.Core/Music/Artist.cs
rename to src/NzbDrone.Core/Music/Model/Artist.cs
index 77636b27e..54421bbcb 100644
--- a/src/NzbDrone.Core/Music/Artist.cs
+++ b/src/NzbDrone.Core/Music/Model/Artist.cs
@@ -75,22 +75,20 @@ namespace NzbDrone.Core.Music
AddOptions = other.AddOptions;
}
- public override void ApplyChanges(Artist otherArtist)
+ public override void ApplyChanges(Artist other)
{
+ Path = other.Path;
+ QualityProfileId = other.QualityProfileId;
+ QualityProfile = other.QualityProfile;
+ MetadataProfileId = other.MetadataProfileId;
+ MetadataProfile = other.MetadataProfile;
- Path = otherArtist.Path;
- QualityProfileId = otherArtist.QualityProfileId;
- QualityProfile = otherArtist.QualityProfile;
- MetadataProfileId = otherArtist.MetadataProfileId;
- MetadataProfile = otherArtist.MetadataProfile;
-
- Albums = otherArtist.Albums;
- Tags = otherArtist.Tags;
- AddOptions = otherArtist.AddOptions;
- RootFolderPath = otherArtist.RootFolderPath;
- Monitored = otherArtist.Monitored;
- AlbumFolder = otherArtist.AlbumFolder;
-
+ Albums = other.Albums;
+ Tags = other.Tags;
+ AddOptions = other.AddOptions;
+ RootFolderPath = other.RootFolderPath;
+ Monitored = other.Monitored;
+ AlbumFolder = other.AlbumFolder;
}
}
}
diff --git a/src/NzbDrone.Core/Music/ArtistMetadata.cs b/src/NzbDrone.Core/Music/Model/ArtistMetadata.cs
similarity index 100%
rename from src/NzbDrone.Core/Music/ArtistMetadata.cs
rename to src/NzbDrone.Core/Music/Model/ArtistMetadata.cs
diff --git a/src/NzbDrone.Core/Music/ArtistStatusType.cs b/src/NzbDrone.Core/Music/Model/ArtistStatusType.cs
similarity index 100%
rename from src/NzbDrone.Core/Music/ArtistStatusType.cs
rename to src/NzbDrone.Core/Music/Model/ArtistStatusType.cs
diff --git a/src/NzbDrone.Core/Music/Entity.cs b/src/NzbDrone.Core/Music/Model/Entity.cs
similarity index 100%
rename from src/NzbDrone.Core/Music/Entity.cs
rename to src/NzbDrone.Core/Music/Model/Entity.cs
diff --git a/src/NzbDrone.Core/Music/Links.cs b/src/NzbDrone.Core/Music/Model/Links.cs
similarity index 100%
rename from src/NzbDrone.Core/Music/Links.cs
rename to src/NzbDrone.Core/Music/Model/Links.cs
diff --git a/src/NzbDrone.Core/Music/Medium.cs b/src/NzbDrone.Core/Music/Model/Medium.cs
similarity index 100%
rename from src/NzbDrone.Core/Music/Medium.cs
rename to src/NzbDrone.Core/Music/Model/Medium.cs
diff --git a/src/NzbDrone.Core/Music/Member.cs b/src/NzbDrone.Core/Music/Model/Member.cs
similarity index 100%
rename from src/NzbDrone.Core/Music/Member.cs
rename to src/NzbDrone.Core/Music/Model/Member.cs
diff --git a/src/NzbDrone.Core/Music/MonitoringOptions.cs b/src/NzbDrone.Core/Music/Model/MonitoringOptions.cs
similarity index 100%
rename from src/NzbDrone.Core/Music/MonitoringOptions.cs
rename to src/NzbDrone.Core/Music/Model/MonitoringOptions.cs
diff --git a/src/NzbDrone.Core/Music/PrimaryAlbumType.cs b/src/NzbDrone.Core/Music/Model/PrimaryAlbumType.cs
similarity index 100%
rename from src/NzbDrone.Core/Music/PrimaryAlbumType.cs
rename to src/NzbDrone.Core/Music/Model/PrimaryAlbumType.cs
diff --git a/src/NzbDrone.Core/Music/Ratings.cs b/src/NzbDrone.Core/Music/Model/Ratings.cs
similarity index 100%
rename from src/NzbDrone.Core/Music/Ratings.cs
rename to src/NzbDrone.Core/Music/Model/Ratings.cs
diff --git a/src/NzbDrone.Core/Music/Release.cs b/src/NzbDrone.Core/Music/Model/Release.cs
similarity index 100%
rename from src/NzbDrone.Core/Music/Release.cs
rename to src/NzbDrone.Core/Music/Model/Release.cs
diff --git a/src/NzbDrone.Core/Music/ReleaseStatus.cs b/src/NzbDrone.Core/Music/Model/ReleaseStatus.cs
similarity index 100%
rename from src/NzbDrone.Core/Music/ReleaseStatus.cs
rename to src/NzbDrone.Core/Music/Model/ReleaseStatus.cs
diff --git a/src/NzbDrone.Core/Music/SecondaryAlbumType.cs b/src/NzbDrone.Core/Music/Model/SecondaryAlbumType.cs
similarity index 100%
rename from src/NzbDrone.Core/Music/SecondaryAlbumType.cs
rename to src/NzbDrone.Core/Music/Model/SecondaryAlbumType.cs
diff --git a/src/NzbDrone.Core/Music/Track.cs b/src/NzbDrone.Core/Music/Model/Track.cs
similarity index 100%
rename from src/NzbDrone.Core/Music/Track.cs
rename to src/NzbDrone.Core/Music/Model/Track.cs
diff --git a/src/NzbDrone.Core/Music/AlbumRepository.cs b/src/NzbDrone.Core/Music/Repositories/AlbumRepository.cs
similarity index 98%
rename from src/NzbDrone.Core/Music/AlbumRepository.cs
rename to src/NzbDrone.Core/Music/Repositories/AlbumRepository.cs
index 252af816a..d2e1f5db0 100644
--- a/src/NzbDrone.Core/Music/AlbumRepository.cs
+++ b/src/NzbDrone.Core/Music/Repositories/AlbumRepository.cs
@@ -1,6 +1,5 @@
using System;
using System.Linq;
-using NLog;
using Marr.Data.QGen;
using NzbDrone.Core.Datastore;
using NzbDrone.Core.Datastore.Extensions;
@@ -17,9 +16,9 @@ namespace NzbDrone.Core.Music
List GetLastAlbums(IEnumerable artistMetadataIds);
List GetNextAlbums(IEnumerable artistMetadataIds);
List GetAlbumsByArtistMetadataId(int artistMetadataId);
- List GetAlbumsForRefresh(int artistId, IEnumerable foreignIds);
+ List GetAlbumsForRefresh(int artistMetadataId, IEnumerable foreignIds);
Album FindByTitle(int artistMetadataId, string title);
- Album FindById(string foreignId);
+ Album FindById(string foreignAlbumId);
PagingSpec AlbumsWithoutFiles(PagingSpec pagingSpec);
PagingSpec AlbumsWhereCutoffUnmet(PagingSpec pagingSpec, List qualitiesBelowCutoff);
List AlbumsBetweenDates(DateTime startDate, DateTime endDate, bool includeUnmonitored);
@@ -34,13 +33,11 @@ namespace NzbDrone.Core.Music
public class AlbumRepository : BasicRepository, IAlbumRepository
{
private readonly IMainDatabase _database;
- private readonly Logger _logger;
- public AlbumRepository(IMainDatabase database, IEventAggregator eventAggregator, Logger logger)
+ public AlbumRepository(IMainDatabase database, IEventAggregator eventAggregator)
: base(database, eventAggregator)
{
_database = database;
- _logger = logger;
}
public List GetAlbums(int artistId)
diff --git a/src/NzbDrone.Core/Music/ArtistMetadataRepository.cs b/src/NzbDrone.Core/Music/Repositories/ArtistMetadataRepository.cs
similarity index 97%
rename from src/NzbDrone.Core/Music/ArtistMetadataRepository.cs
rename to src/NzbDrone.Core/Music/Repositories/ArtistMetadataRepository.cs
index 8ef16f41a..78cb763b3 100644
--- a/src/NzbDrone.Core/Music/ArtistMetadataRepository.cs
+++ b/src/NzbDrone.Core/Music/Repositories/ArtistMetadataRepository.cs
@@ -9,7 +9,7 @@ namespace NzbDrone.Core.Music
public interface IArtistMetadataRepository : IBasicRepository
{
List FindById(List foreignIds);
- bool UpsertMany(List artists);
+ bool UpsertMany(List data);
}
public class ArtistMetadataRepository : BasicRepository, IArtistMetadataRepository
diff --git a/src/NzbDrone.Core/Music/ArtistRepository.cs b/src/NzbDrone.Core/Music/Repositories/ArtistRepository.cs
similarity index 97%
rename from src/NzbDrone.Core/Music/ArtistRepository.cs
rename to src/NzbDrone.Core/Music/Repositories/ArtistRepository.cs
index c5bee6ecd..baca718da 100644
--- a/src/NzbDrone.Core/Music/ArtistRepository.cs
+++ b/src/NzbDrone.Core/Music/Repositories/ArtistRepository.cs
@@ -9,7 +9,7 @@ namespace NzbDrone.Core.Music
public interface IArtistRepository : IBasicRepository
{
bool ArtistPathExists(string path);
- Artist FindByName(string cleanTitle);
+ Artist FindByName(string cleanName);
Artist FindById(string foreignArtistId);
Artist GetArtistByMetadataId(int artistMetadataId);
}
diff --git a/src/NzbDrone.Core/Music/ReleaseRepository.cs b/src/NzbDrone.Core/Music/Repositories/ReleaseRepository.cs
similarity index 100%
rename from src/NzbDrone.Core/Music/ReleaseRepository.cs
rename to src/NzbDrone.Core/Music/Repositories/ReleaseRepository.cs
diff --git a/src/NzbDrone.Core/Music/TrackRepository.cs b/src/NzbDrone.Core/Music/Repositories/TrackRepository.cs
similarity index 96%
rename from src/NzbDrone.Core/Music/TrackRepository.cs
rename to src/NzbDrone.Core/Music/Repositories/TrackRepository.cs
index ee8aa1e6d..883588430 100644
--- a/src/NzbDrone.Core/Music/TrackRepository.cs
+++ b/src/NzbDrone.Core/Music/Repositories/TrackRepository.cs
@@ -1,6 +1,5 @@
-using NzbDrone.Core.Datastore;
using System.Collections.Generic;
-using NLog;
+using NzbDrone.Core.Datastore;
using NzbDrone.Core.Messaging.Events;
namespace NzbDrone.Core.Music
@@ -10,7 +9,7 @@ namespace NzbDrone.Core.Music
List GetTracks(int artistId);
List GetTracksByAlbum(int albumId);
List GetTracksByRelease(int albumReleaseId);
- List GetTracksByReleases(List albumReleaseId);
+ List GetTracksByReleases(List albumReleaseIds);
List GetTracksForRefresh(int albumReleaseId, IEnumerable foreignTrackIds);
List GetTracksByFileId(int fileId);
List GetTracksByFileId(IEnumerable ids);
@@ -22,14 +21,9 @@ namespace NzbDrone.Core.Music
public class TrackRepository : BasicRepository, ITrackRepository
{
- private readonly IMainDatabase _database;
- private readonly Logger _logger;
-
- public TrackRepository(IMainDatabase database, IEventAggregator eventAggregator, Logger logger)
+ public TrackRepository(IMainDatabase database, IEventAggregator eventAggregator)
: base(database, eventAggregator)
{
- _database = database;
- _logger = logger;
}
public List GetTracks(int artistId)
diff --git a/src/NzbDrone.Core/Music/Services/AddAlbumService.cs b/src/NzbDrone.Core/Music/Services/AddAlbumService.cs
new file mode 100644
index 000000000..8bee983cd
--- /dev/null
+++ b/src/NzbDrone.Core/Music/Services/AddAlbumService.cs
@@ -0,0 +1,113 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using FluentValidation;
+using FluentValidation.Results;
+using NLog;
+using NzbDrone.Core.Exceptions;
+using NzbDrone.Core.ImportLists.Exclusions;
+using NzbDrone.Core.MetadataSource;
+
+namespace NzbDrone.Core.Music
+{
+ public interface IAddAlbumService
+ {
+ Album AddAlbum(Album album);
+ List AddAlbums(List albums);
+ }
+
+ public class AddAlbumService : IAddAlbumService
+ {
+ private readonly IArtistService _artistService;
+ private readonly IAddArtistService _addArtistService;
+ private readonly IAlbumService _albumService;
+ private readonly IProvideAlbumInfo _albumInfo;
+ private readonly IImportListExclusionService _importListExclusionService;
+ private readonly Logger _logger;
+
+ public AddAlbumService(IArtistService artistService,
+ IAddArtistService addArtistService,
+ IAlbumService albumService,
+ IProvideAlbumInfo albumInfo,
+ IImportListExclusionService importListExclusionService,
+ Logger logger)
+ {
+ _artistService = artistService;
+ _addArtistService = addArtistService;
+ _albumService = albumService;
+ _albumInfo = albumInfo;
+ _importListExclusionService = importListExclusionService;
+ _logger = logger;
+ }
+
+ public Album AddAlbum(Album album)
+ {
+ _logger.Debug($"Adding album {album}");
+
+ album = AddSkyhookData(album);
+
+ // Remove any import list exclusions preventing addition
+ _importListExclusionService.Delete(album.ForeignAlbumId);
+ _importListExclusionService.Delete(album.ArtistMetadata.Value.ForeignArtistId);
+
+ // Note it's a manual addition so it's not deleted on next refresh
+ album.AddOptions.AddType = AlbumAddType.Manual;
+
+ // Add the artist if necessary
+ var dbArtist = _artistService.FindById(album.ArtistMetadata.Value.ForeignArtistId);
+ if (dbArtist == null)
+ {
+ var artist = album.Artist.Value;
+
+ artist.Metadata.Value.ForeignArtistId = album.ArtistMetadata.Value.ForeignArtistId;
+
+ dbArtist = _addArtistService.AddArtist(artist, false);
+ }
+
+ album.ArtistMetadataId = dbArtist.ArtistMetadataId;
+ _albumService.AddAlbum(album);
+
+ return album;
+ }
+
+ public List AddAlbums(List albums)
+ {
+ var added = DateTime.UtcNow;
+ var addedAlbums = new List();
+
+ foreach (var a in albums)
+ {
+ a.Added = added;
+ addedAlbums.Add(AddAlbum(a));
+ }
+
+ return addedAlbums;
+ }
+
+ private Album AddSkyhookData(Album newAlbum)
+ {
+ Tuple> tuple = null;
+ try
+ {
+ tuple = _albumInfo.GetAlbumInfo(newAlbum.ForeignAlbumId);
+ }
+ catch (AlbumNotFoundException)
+ {
+ _logger.Error("Album with MusicBrainz Id {0} was not found, it may have been removed from Musicbrainz.", newAlbum.ForeignAlbumId);
+
+ throw new ValidationException(new List
+ {
+ new ValidationFailure("MusicbrainzId", "An album with this ID was not found", newAlbum.ForeignAlbumId)
+ });
+ }
+
+ newAlbum.UseMetadataFrom(tuple.Item2);
+ newAlbum.Added = DateTime.UtcNow;
+
+ var metadata = tuple.Item3.Single(x => x.ForeignArtistId == tuple.Item1);
+ newAlbum.ArtistMetadata = metadata;
+
+ return newAlbum;
+ }
+ }
+}
diff --git a/src/NzbDrone.Core/Music/AddArtistService.cs b/src/NzbDrone.Core/Music/Services/AddArtistService.cs
similarity index 85%
rename from src/NzbDrone.Core/Music/AddArtistService.cs
rename to src/NzbDrone.Core/Music/Services/AddArtistService.cs
index e56127443..c0affd051 100644
--- a/src/NzbDrone.Core/Music/AddArtistService.cs
+++ b/src/NzbDrone.Core/Music/Services/AddArtistService.cs
@@ -15,7 +15,7 @@ namespace NzbDrone.Core.Music
{
public interface IAddArtistService
{
- Artist AddArtist(Artist newArtist);
+ Artist AddArtist(Artist newArtist, bool doRefresh = true);
List AddArtists(List newArtists);
}
@@ -43,7 +43,7 @@ namespace NzbDrone.Core.Music
_logger = logger;
}
- public Artist AddArtist(Artist newArtist)
+ public Artist AddArtist(Artist newArtist, bool doRefresh = true)
{
Ensure.That(newArtist, () => newArtist).IsNotNull();
@@ -57,7 +57,7 @@ namespace NzbDrone.Core.Music
newArtist.ArtistMetadataId = newArtist.Metadata.Value.Id;
// add the artist itself
- _artistService.AddArtist(newArtist);
+ _artistService.AddArtist(newArtist, doRefresh);
return newArtist;
}
@@ -69,7 +69,6 @@ namespace NzbDrone.Core.Music
foreach (var s in newArtists)
{
- // TODO: Verify if adding skyhook data will be slow
try
{
var artist = AddSkyhookData(s);
@@ -102,20 +101,14 @@ namespace NzbDrone.Core.Music
}
catch (ArtistNotFoundException)
{
- _logger.Error("LidarrId {0} was not found, it may have been removed from Lidarr.", newArtist.Metadata.Value.ForeignArtistId);
+ _logger.Error("LidarrId {0} was not found, it may have been removed from Musicbrainz.", newArtist.Metadata.Value.ForeignArtistId);
throw new ValidationException(new List
{
- new ValidationFailure("SpotifyId", "An artist with this ID was not found", newArtist.Metadata.Value.ForeignArtistId)
+ new ValidationFailure("MusicbrainzId", "An artist with this ID was not found", newArtist.Metadata.Value.ForeignArtistId)
});
}
- // If albums were passed in on the new artist use them, otherwise use the albums from Skyhook
- if (newArtist.Albums == null || newArtist.Albums.Value == null || !newArtist.Albums.Value.Any())
- {
- newArtist.Albums = artist.Albums.Value;
- }
-
artist.ApplyChanges(newArtist);
return artist;
@@ -130,7 +123,7 @@ namespace NzbDrone.Core.Music
}
newArtist.CleanName = newArtist.Metadata.Value.Name.CleanArtistName();
- newArtist.SortName = ArtistNameNormalizer.Normalize(newArtist.Metadata.Value.Name, newArtist.Metadata.Value.ForeignArtistId);
+ newArtist.SortName = Parser.Parser.NormalizeTitle(newArtist.Metadata.Value.Name).ToLower();
newArtist.Added = DateTime.UtcNow;
if (newArtist.AddOptions != null && newArtist.AddOptions.Monitor == MonitorTypes.None)
diff --git a/src/NzbDrone.Core/Music/AlbumAddedService.cs b/src/NzbDrone.Core/Music/Services/AlbumAddedService.cs
similarity index 78%
rename from src/NzbDrone.Core/Music/AlbumAddedService.cs
rename to src/NzbDrone.Core/Music/Services/AlbumAddedService.cs
index ea1557a51..4568614f8 100644
--- a/src/NzbDrone.Core/Music/AlbumAddedService.cs
+++ b/src/NzbDrone.Core/Music/Services/AlbumAddedService.cs
@@ -36,16 +36,25 @@ namespace NzbDrone.Core.Music
public void SearchForRecentlyAdded(int artistId)
{
- var previouslyReleased = _addedAlbumsCache.Find(artistId.ToString());
+ var allAlbums = _albumService.GetAlbumsByArtist(artistId);
+ var toSearch = allAlbums.Where(x => x.AddOptions.SearchForNewAlbum).ToList();
- if (previouslyReleased != null && previouslyReleased.Any())
+ if (toSearch.Any())
{
- var missing = previouslyReleased.Select(e => _albumService.GetAlbum(e)).ToList();
+ toSearch.ForEach(x => x.AddOptions.SearchForNewAlbum = false);
- if (missing.Any())
- {
- _commandQueueManager.Push(new AlbumSearchCommand(missing.Select(e => e.Id).ToList()));
- }
+ _albumService.SetAddOptions(toSearch);
+ }
+
+ var recentlyAddedIds = _addedAlbumsCache.Find(artistId.ToString());
+ if (recentlyAddedIds != null)
+ {
+ toSearch.AddRange(allAlbums.Where(x => recentlyAddedIds.Contains(x.Id)));
+ }
+
+ if (toSearch.Any())
+ {
+ _commandQueueManager.Push(new AlbumSearchCommand(toSearch.Select(e => e.Id).ToList()));
}
_addedAlbumsCache.Remove(artistId.ToString());
diff --git a/src/NzbDrone.Core/Music/AlbumCutoffService.cs b/src/NzbDrone.Core/Music/Services/AlbumCutoffService.cs
similarity index 91%
rename from src/NzbDrone.Core/Music/AlbumCutoffService.cs
rename to src/NzbDrone.Core/Music/Services/AlbumCutoffService.cs
index 879abd3d0..a83252fcb 100644
--- a/src/NzbDrone.Core/Music/AlbumCutoffService.cs
+++ b/src/NzbDrone.Core/Music/Services/AlbumCutoffService.cs
@@ -1,6 +1,5 @@
using System.Collections.Generic;
using System.Linq;
-using NLog;
using NzbDrone.Core.Datastore;
using NzbDrone.Core.Profiles.Qualities;
using NzbDrone.Core.Qualities;
@@ -16,13 +15,11 @@ namespace NzbDrone.Core.Music
{
private readonly IAlbumRepository _albumRepository;
private readonly IProfileService _profileService;
- private readonly Logger _logger;
- public AlbumCutoffService(IAlbumRepository albumRepository, IProfileService profileService, Logger logger)
+ public AlbumCutoffService(IAlbumRepository albumRepository, IProfileService profileService)
{
_albumRepository = albumRepository;
_profileService = profileService;
- _logger = logger;
}
public PagingSpec AlbumsWhereCutoffUnmet(PagingSpec pagingSpec)
diff --git a/src/NzbDrone.Core/Music/AlbumEditedService.cs b/src/NzbDrone.Core/Music/Services/AlbumEditedService.cs
similarity index 94%
rename from src/NzbDrone.Core/Music/AlbumEditedService.cs
rename to src/NzbDrone.Core/Music/Services/AlbumEditedService.cs
index 26cacfc4e..4fefd8fbc 100644
--- a/src/NzbDrone.Core/Music/AlbumEditedService.cs
+++ b/src/NzbDrone.Core/Music/Services/AlbumEditedService.cs
@@ -1,10 +1,10 @@
+using System.Collections.Generic;
+using System.Linq;
+using NzbDrone.Core.MediaFiles;
+using NzbDrone.Core.MediaFiles.Commands;
using NzbDrone.Core.Messaging.Commands;
using NzbDrone.Core.Messaging.Events;
-using NzbDrone.Core.MediaFiles.Commands;
using NzbDrone.Core.Music.Events;
-using System.Linq;
-using System.Collections.Generic;
-using NzbDrone.Core.MediaFiles;
namespace NzbDrone.Core.Music
{
@@ -27,7 +27,7 @@ namespace NzbDrone.Core.Music
var new_monitored = new HashSet(message.Album.AlbumReleases.Value.Where(x => x.Monitored).Select(x => x.Id));
var old_monitored = new HashSet(message.OldAlbum.AlbumReleases.Value.Where(x => x.Monitored).Select(x => x.Id));
if (!new_monitored.SetEquals(old_monitored) ||
- (message.OldAlbum.AnyReleaseOk == false && message.Album.AnyReleaseOk == true))
+ (!message.OldAlbum.AnyReleaseOk && message.Album.AnyReleaseOk))
{
// Unlink any old track files
var tracks = _trackService.GetTracksByAlbum(message.Album.Id);
diff --git a/src/NzbDrone.Core/Music/AlbumMonitoredService.cs b/src/NzbDrone.Core/Music/Services/AlbumMonitoredService.cs
similarity index 94%
rename from src/NzbDrone.Core/Music/AlbumMonitoredService.cs
rename to src/NzbDrone.Core/Music/Services/AlbumMonitoredService.cs
index c9c6756f9..87417d926 100644
--- a/src/NzbDrone.Core/Music/AlbumMonitoredService.cs
+++ b/src/NzbDrone.Core/Music/Services/AlbumMonitoredService.cs
@@ -1,11 +1,7 @@
using System;
using System.Collections.Generic;
using System.Linq;
-using System.Linq.Expressions;
-using System.Net.Sockets;
using NLog;
-using NzbDrone.Common.Extensions;
-using NzbDrone.Core.Datastore;
namespace NzbDrone.Core.Music
{
@@ -18,14 +14,12 @@ namespace NzbDrone.Core.Music
{
private readonly IArtistService _artistService;
private readonly IAlbumService _albumService;
- private readonly ITrackService _trackService;
private readonly Logger _logger;
- public AlbumMonitoredService(IArtistService artistService, IAlbumService albumService, ITrackService trackService, Logger logger)
+ public AlbumMonitoredService(IArtistService artistService, IAlbumService albumService, Logger logger)
{
_artistService = artistService;
_albumService = albumService;
- _trackService = trackService;
_logger = logger;
}
diff --git a/src/NzbDrone.Core/Music/AlbumService.cs b/src/NzbDrone.Core/Music/Services/AlbumService.cs
similarity index 90%
rename from src/NzbDrone.Core/Music/AlbumService.cs
rename to src/NzbDrone.Core/Music/Services/AlbumService.cs
index 6165b1c98..faa90be6a 100644
--- a/src/NzbDrone.Core/Music/AlbumService.cs
+++ b/src/NzbDrone.Core/Music/Services/AlbumService.cs
@@ -23,8 +23,8 @@ namespace NzbDrone.Core.Music
Album FindById(string foreignId);
Album FindByTitle(int artistMetadataId, string title);
Album FindByTitleInexact(int artistMetadataId, string title);
- List GetCandidates(int artistId, string title);
- void DeleteAlbum(int albumId, bool deleteFiles);
+ List GetCandidates(int artistMetadataId, string title);
+ void DeleteAlbum(int albumId, bool deleteFiles, bool addImportListExclusion = false);
List GetAllAlbums();
Album UpdateAlbum(Album album);
void SetAlbumMonitored(int albumId, bool monitored);
@@ -35,7 +35,7 @@ namespace NzbDrone.Core.Music
void InsertMany(List albums);
void UpdateMany(List albums);
void DeleteMany(List albums);
- void RemoveAddOptions(Album album);
+ void SetAddOptions(IEnumerable albums);
Album FindAlbumByRelease(string albumReleaseId);
Album FindAlbumByTrackId(int trackId);
List GetArtistAlbumsWithFiles(Artist artist);
@@ -61,21 +61,22 @@ namespace NzbDrone.Core.Music
{
_albumRepository.Insert(newAlbum);
- //_eventAggregator.PublishEvent(new AlbumAddedEvent(GetAlbum(newAlbum.Id)));
+ _eventAggregator.PublishEvent(new AlbumAddedEvent(GetAlbum(newAlbum.Id)));
return newAlbum;
}
- public void DeleteAlbum(int albumId, bool deleteFiles)
+ public void DeleteAlbum(int albumId, bool deleteFiles, bool addImportListExclusion = false)
{
var album = _albumRepository.Get(albumId);
+ album.Artist.LazyLoad();
_albumRepository.Delete(albumId);
- _eventAggregator.PublishEvent(new AlbumDeletedEvent(album, deleteFiles));
+ _eventAggregator.PublishEvent(new AlbumDeletedEvent(album, deleteFiles, addImportListExclusion));
}
- public Album FindById(string lidarrId)
+ public Album FindById(string foreignId)
{
- return _albumRepository.FindById(lidarrId);
+ return _albumRepository.FindById(foreignId);
}
public Album FindByTitle(int artistMetadataId, string title)
@@ -146,7 +147,7 @@ namespace NzbDrone.Core.Music
title,
string.Join("\n", sortedAlbums.Select(x => $"[{x.Album.Title}] {x.Album.CleanTitle}: {x.MatchProb}")));
- return sortedAlbums.TakeWhile((x, i) => i == 0 ? true : sortedAlbums[i - 1].MatchProb - x.MatchProb < fuzzGap)
+ return sortedAlbums.TakeWhile((x, i) => i == 0 || sortedAlbums[i - 1].MatchProb - x.MatchProb < fuzzGap)
.TakeWhile((x, i) => x.MatchProb > fuzzThreshold || (i > 0 && sortedAlbums[i - 1].MatchProb > fuzzThreshold))
.Select(x => x.Album)
.ToList();
@@ -187,9 +188,9 @@ namespace NzbDrone.Core.Music
return _albumRepository.GetAlbumsByArtistMetadataId(artistMetadataId).ToList();
}
- public List GetAlbumsForRefresh(int artistId, IEnumerable foreignIds)
+ public List GetAlbumsForRefresh(int artistMetadataId, IEnumerable foreignIds)
{
- return _albumRepository.GetAlbumsForRefresh(artistId, foreignIds);
+ return _albumRepository.GetAlbumsForRefresh(artistMetadataId, foreignIds);
}
public Album FindAlbumByRelease(string albumReleaseId)
@@ -202,10 +203,9 @@ namespace NzbDrone.Core.Music
return _albumRepository.FindAlbumByTrack(trackId);
}
- public void RemoveAddOptions(Album album)
+ public void SetAddOptions(IEnumerable albums)
{
- var rg = _albumRepository.Get(album.Id);
- _albumRepository.SetFields(rg, s => s.AddOptions);
+ _albumRepository.SetFields(albums, s => s.AddOptions);
}
public PagingSpec AlbumsWithoutFiles(PagingSpec pagingSpec)
@@ -250,7 +250,7 @@ namespace NzbDrone.Core.Music
foreach (var album in albums)
{
- _eventAggregator.PublishEvent(new AlbumDeletedEvent(album, false));
+ _eventAggregator.PublishEvent(new AlbumDeletedEvent(album, false, false));
}
}
diff --git a/src/NzbDrone.Core/Music/ArtistEditedService.cs b/src/NzbDrone.Core/Music/Services/ArtistEditedService.cs
similarity index 100%
rename from src/NzbDrone.Core/Music/ArtistEditedService.cs
rename to src/NzbDrone.Core/Music/Services/ArtistEditedService.cs
diff --git a/src/NzbDrone.Core/Music/ArtistMetadataService.cs b/src/NzbDrone.Core/Music/Services/ArtistMetadataService.cs
similarity index 84%
rename from src/NzbDrone.Core/Music/ArtistMetadataService.cs
rename to src/NzbDrone.Core/Music/Services/ArtistMetadataService.cs
index 8988067dc..1afc91732 100644
--- a/src/NzbDrone.Core/Music/ArtistMetadataService.cs
+++ b/src/NzbDrone.Core/Music/Services/ArtistMetadataService.cs
@@ -1,4 +1,3 @@
-using NLog;
using System.Collections.Generic;
namespace NzbDrone.Core.Music
@@ -12,13 +11,10 @@ namespace NzbDrone.Core.Music
public class ArtistMetadataService : IArtistMetadataService
{
private readonly IArtistMetadataRepository _artistMetadataRepository;
- private readonly Logger _logger;
- public ArtistMetadataService(IArtistMetadataRepository artistMetadataRepository,
- Logger logger)
+ public ArtistMetadataService(IArtistMetadataRepository artistMetadataRepository)
{
_artistMetadataRepository = artistMetadataRepository;
- _logger = logger;
}
public bool Upsert(ArtistMetadata artist)
diff --git a/src/NzbDrone.Core/Music/ArtistService.cs b/src/NzbDrone.Core/Music/Services/ArtistService.cs
similarity index 90%
rename from src/NzbDrone.Core/Music/ArtistService.cs
rename to src/NzbDrone.Core/Music/Services/ArtistService.cs
index 192bb718c..8fdc0db7c 100644
--- a/src/NzbDrone.Core/Music/ArtistService.cs
+++ b/src/NzbDrone.Core/Music/Services/ArtistService.cs
@@ -6,7 +6,6 @@ using System.Collections.Generic;
using System.Linq;
using NzbDrone.Common.Extensions;
using NzbDrone.Common.Cache;
-using NzbDrone.Core.ImportLists.Exclusions;
using NzbDrone.Core.Parser;
namespace NzbDrone.Core.Music
@@ -16,7 +15,7 @@ namespace NzbDrone.Core.Music
Artist GetArtist(int artistId);
Artist GetArtistByMetadataId(int artistMetadataId);
List GetArtists(IEnumerable artistIds);
- Artist AddArtist(Artist newArtist);
+ Artist AddArtist(Artist newArtist, bool doRefresh);
List AddArtists(List newArtists);
Artist FindById(string foreignArtistId);
Artist FindByName(string title);
@@ -35,34 +34,28 @@ namespace NzbDrone.Core.Music
{
private readonly IArtistRepository _artistRepository;
private readonly IEventAggregator _eventAggregator;
- private readonly ITrackService _trackService;
- private readonly IImportListExclusionService _importListExclusionService;
private readonly IBuildArtistPaths _artistPathBuilder;
private readonly Logger _logger;
private readonly ICached> _cache;
public ArtistService(IArtistRepository artistRepository,
IEventAggregator eventAggregator,
- ITrackService trackService,
- IImportListExclusionService importListExclusionService,
IBuildArtistPaths artistPathBuilder,
ICacheManager cacheManager,
Logger logger)
{
_artistRepository = artistRepository;
_eventAggregator = eventAggregator;
- _trackService = trackService;
- _importListExclusionService = importListExclusionService;
_artistPathBuilder = artistPathBuilder;
_cache = cacheManager.GetCache>(GetType());
_logger = logger;
}
- public Artist AddArtist(Artist newArtist)
+ public Artist AddArtist(Artist newArtist, bool doRefresh)
{
_cache.Clear();
_artistRepository.Insert(newArtist);
- _eventAggregator.PublishEvent(new ArtistAddedEvent(GetArtist(newArtist.Id)));
+ _eventAggregator.PublishEvent(new ArtistAddedEvent(GetArtist(newArtist.Id), doRefresh));
return newArtist;
}
@@ -166,7 +159,7 @@ namespace NzbDrone.Core.Music
title,
string.Join("\n", sortedArtists.Select(x => $"[{x.Artist.Name}] {x.Artist.CleanName}: {x.MatchProb}")));
- return sortedArtists.TakeWhile((x, i) => i == 0 ? true : sortedArtists[i - 1].MatchProb - x.MatchProb < fuzzGap)
+ return sortedArtists.TakeWhile((x, i) => i == 0 || sortedArtists[i - 1].MatchProb - x.MatchProb < fuzzGap)
.TakeWhile((x, i) => x.MatchProb > fuzzThreshold || (i > 0 && sortedArtists[i - 1].MatchProb > fuzzThreshold))
.Select(x => x.Artist)
.ToList();
@@ -183,9 +176,9 @@ namespace NzbDrone.Core.Music
.ToList();
}
- public Artist GetArtist(int artistDBId)
+ public Artist GetArtist(int artistId)
{
- return _artistRepository.Get(artistDBId);
+ return _artistRepository.Get(artistId);
}
public Artist GetArtistByMetadataId(int artistMetadataId)
@@ -226,8 +219,6 @@ namespace NzbDrone.Core.Music
{
s.Path = _artistPathBuilder.BuildPath(s, useExistingRelativeFolder);
- //s.Path = Path.Combine(s.RootFolderPath, _fileNameBuilder.GetArtistFolder(s));
-
_logger.Trace("Changing path for {0} to {1}", s.Name, s.Path);
}
else
diff --git a/src/NzbDrone.Core/Music/MoveArtistService.cs b/src/NzbDrone.Core/Music/Services/MoveArtistService.cs
similarity index 99%
rename from src/NzbDrone.Core/Music/MoveArtistService.cs
rename to src/NzbDrone.Core/Music/Services/MoveArtistService.cs
index 846639e76..14d5d91a5 100644
--- a/src/NzbDrone.Core/Music/MoveArtistService.cs
+++ b/src/NzbDrone.Core/Music/Services/MoveArtistService.cs
@@ -5,7 +5,6 @@ using NzbDrone.Common.Instrumentation.Extensions;
using NzbDrone.Core.Messaging.Commands;
using NzbDrone.Core.Messaging.Events;
using NzbDrone.Core.Organizer;
-using NzbDrone.Core.RootFolders;
using NzbDrone.Core.Music.Commands;
using NzbDrone.Core.Music.Events;
diff --git a/src/NzbDrone.Core/Music/RefreshAlbumReleaseService.cs b/src/NzbDrone.Core/Music/Services/RefreshAlbumReleaseService.cs
similarity index 94%
rename from src/NzbDrone.Core/Music/RefreshAlbumReleaseService.cs
rename to src/NzbDrone.Core/Music/Services/RefreshAlbumReleaseService.cs
index ef8cec763..c6ab26e3a 100644
--- a/src/NzbDrone.Core/Music/RefreshAlbumReleaseService.cs
+++ b/src/NzbDrone.Core/Music/Services/RefreshAlbumReleaseService.cs
@@ -1,9 +1,8 @@
-using NLog;
-using NzbDrone.Common.Extensions;
using System;
using System.Collections.Generic;
using System.Linq;
-using NzbDrone.Core.MediaFiles;
+using NLog;
+using NzbDrone.Common.Extensions;
namespace NzbDrone.Core.Music
{
@@ -18,22 +17,17 @@ namespace NzbDrone.Core.Music
private readonly IReleaseService _releaseService;
private readonly IRefreshTrackService _refreshTrackService;
private readonly ITrackService _trackService;
- private readonly IMediaFileService _mediaFileService;
- private readonly Logger _logger;
public RefreshAlbumReleaseService(IReleaseService releaseService,
IArtistMetadataService artistMetadataService,
IRefreshTrackService refreshTrackService,
ITrackService trackService,
- IMediaFileService mediaFileService,
Logger logger)
: base(logger, artistMetadataService)
{
_releaseService = releaseService;
_trackService = trackService;
_refreshTrackService = refreshTrackService;
- _mediaFileService = mediaFileService;
- _logger = logger;
}
protected override RemoteData GetRemoteData(AlbumRelease local, List remote)
diff --git a/src/NzbDrone.Core/Music/RefreshAlbumService.cs b/src/NzbDrone.Core/Music/Services/RefreshAlbumService.cs
similarity index 96%
rename from src/NzbDrone.Core/Music/RefreshAlbumService.cs
rename to src/NzbDrone.Core/Music/Services/RefreshAlbumService.cs
index 8aa740d07..d5b7e73df 100644
--- a/src/NzbDrone.Core/Music/RefreshAlbumService.cs
+++ b/src/NzbDrone.Core/Music/Services/RefreshAlbumService.cs
@@ -1,18 +1,18 @@
-using NLog;
-using NzbDrone.Common.Extensions;
-using NzbDrone.Core.Messaging.Events;
-using NzbDrone.Core.Music.Events;
using System;
using System.Collections.Generic;
using System.Linq;
-using NzbDrone.Core.MetadataSource;
+using NLog;
+using NzbDrone.Common.Extensions;
using NzbDrone.Common.Instrumentation.Extensions;
using NzbDrone.Core.Exceptions;
-using NzbDrone.Core.Messaging.Commands;
-using NzbDrone.Core.Music.Commands;
-using NzbDrone.Core.MediaFiles;
using NzbDrone.Core.History;
using NzbDrone.Core.MediaCover;
+using NzbDrone.Core.MediaFiles;
+using NzbDrone.Core.Messaging.Commands;
+using NzbDrone.Core.Messaging.Events;
+using NzbDrone.Core.MetadataSource;
+using NzbDrone.Core.Music.Commands;
+using NzbDrone.Core.Music.Events;
namespace NzbDrone.Core.Music
{
@@ -130,7 +130,9 @@ namespace NzbDrone.Core.Music
protected override bool ShouldDelete(Album local)
{
- return !_mediaFileService.GetFilesByAlbum(local.Id).Any();
+ // not manually added and has no files
+ return local.AddOptions.AddType != AlbumAddType.Manual &&
+ !_mediaFileService.GetFilesByAlbum(local.Id).Any();
}
protected override void LogProgress(Album local)
@@ -146,7 +148,9 @@ namespace NzbDrone.Core.Music
protected override UpdateResult UpdateEntity(Album local, Album remote)
{
UpdateResult result;
-
+
+ remote.UseDbFieldsFrom(local);
+
if (local.Title != (remote.Title ?? "Unknown") ||
local.ForeignAlbumId != remote.ForeignAlbumId ||
local.ArtistMetadata.Value.ForeignArtistId != remote.ArtistMetadata.Value.ForeignArtistId)
@@ -302,6 +306,12 @@ namespace NzbDrone.Core.Music
return _refreshAlbumReleaseService.RefreshEntityInfo(refreshList, remoteChildren, forceChildRefresh, forceUpdateFileTags);
}
+ protected override void PublishEntityUpdatedEvent(Album entity)
+ {
+ // Fetch fresh from DB so all lazy loads are available
+ _eventAggregator.PublishEvent(new AlbumUpdatedEvent(_albumService.GetAlbum(entity.Id)));
+ }
+
public bool RefreshAlbumInfo(List albums, List remoteAlbums, bool forceAlbumRefresh, bool forceUpdateFileTags)
{
bool updated = false;
@@ -330,6 +340,7 @@ namespace NzbDrone.Core.Music
if (updated)
{
_eventAggregator.PublishEvent(new ArtistUpdatedEvent(artist));
+ _eventAggregator.PublishEvent(new AlbumUpdatedEvent(album));
}
}
}
diff --git a/src/NzbDrone.Core/Music/RefreshArtistService.cs b/src/NzbDrone.Core/Music/Services/RefreshArtistService.cs
similarity index 96%
rename from src/NzbDrone.Core/Music/RefreshArtistService.cs
rename to src/NzbDrone.Core/Music/Services/RefreshArtistService.cs
index 5d3dd2fda..52e00c7fa 100644
--- a/src/NzbDrone.Core/Music/RefreshArtistService.cs
+++ b/src/NzbDrone.Core/Music/Services/RefreshArtistService.cs
@@ -193,7 +193,10 @@ namespace NzbDrone.Core.Music
protected override List GetRemoteChildren(Artist remote)
{
- return remote.Albums.Value.DistinctBy(m => m.ForeignAlbumId).ToList();
+ var all = remote.Albums.Value.DistinctBy(m => m.ForeignAlbumId).ToList();
+ var ids = all.SelectMany(x => x.OldForeignAlbumIds.Concat(new List { x.ForeignAlbumId })).ToList();
+ var excluded = _importListExclusionService.FindByForeignId(ids).Select(x => x.ForeignId).ToList();
+ return all.Where(x => !excluded.Contains(x.ForeignAlbumId) && !x.OldForeignAlbumIds.Any(y => excluded.Contains(y))).ToList();
}
protected override List