From f05332cf6ead7c9f428f4176203fdd632cca36c0 Mon Sep 17 00:00:00 2001 From: Qstick Date: Tue, 12 Sep 2017 23:28:45 -0400 Subject: [PATCH] [UI Work] Interactive Import, More Artist Detail --- .../Import/ImportArtistRowConnector.js | 2 +- .../SelectArtist/ImportArtistSelectArtist.js | 10 +- .../ImportArtistSelectArtistConnector.js | 4 +- frontend/src/App/App.js | 2 +- frontend/src/Artist/Details/SeriesDetails.js | 29 +- .../src/Artist/Details/SeriesDetailsLinks.js | 18 +- .../Artist/Details/SeriesDetailsSeason.css | 2 +- .../src/Artist/Details/SeriesDetailsSeason.js | 129 ++------ .../Details/SeriesDetailsSeasonConnector.js | 10 +- frontend/src/Commands/commandNames.js | 2 +- .../SelectAlbumModal.js} | 10 +- .../SelectAlbumModalContent.js} | 23 +- .../SelectAlbumModalContentConnector.js} | 21 +- .../SelectAlbumRow.css} | 0 .../InteractiveImport/Album/SelectAlbumRow.js | 37 +++ .../{Series => Artist}/SelectArtistModal.js | 0 .../SelectArtistModalContent.css | 0 .../SelectArtistModalContent.js | 14 +- .../SelectArtistModalContentConnector.js | 12 +- .../{Series => Artist}/SelectArtistRow.css | 2 +- .../{Series => Artist}/SelectArtistRow.js | 10 +- .../InteractiveImportModalContent.js | 40 +-- .../InteractiveImportModalContentConnector.js | 23 +- .../Interactive/InteractiveImportRow.js | 101 +++--- .../Season/SelectSeasonRow.js | 40 --- .../SelectTrackModal.js} | 10 +- .../SelectTrackModalContent.js} | 36 +- .../SelectTrackModalContentConnector.js} | 24 +- .../SelectTrackRow.js} | 17 +- .../Store/Actions/episodeActionHandlers.js | 2 +- .../Actions/episodeFileActionHandlers.js | 4 +- frontend/src/Store/Middleware/middlewares.js | 4 +- src/Lidarr.Api.V3/Artist/ArtistResource.cs | 25 -- .../ManualImport/ManualImportResource.cs | 13 +- .../ManualImport/ManualImportResource.cs | 6 - .../TrackImport/ImportApprovedTracks.cs | 2 +- .../TrackImport/Manual/ManualImportFile.cs | 5 +- .../TrackImport/Manual/ManualImportItem.cs | 8 +- .../TrackImport/Manual/ManualImportService.cs | 308 +++++++++--------- .../Parser/Model/ParsedTrackInfo.cs | 12 +- src/NzbDrone.Core/Parser/Parser.cs | 14 +- src/NzbDrone.Core/Parser/ParsingService.cs | 42 +++ 42 files changed, 505 insertions(+), 568 deletions(-) rename frontend/src/InteractiveImport/{Season/SelectSeasonModal.js => Album/SelectAlbumModal.js} (68%) rename frontend/src/InteractiveImport/{Season/SelectSeasonModalContent.js => Album/SelectAlbumModalContent.js} (68%) rename frontend/src/InteractiveImport/{Season/SelectSeasonModalContentConnector.js => Album/SelectAlbumModalContentConnector.js} (71%) rename frontend/src/InteractiveImport/{Season/SelectSeasonRow.css => Album/SelectAlbumRow.css} (100%) create mode 100644 frontend/src/InteractiveImport/Album/SelectAlbumRow.js rename frontend/src/InteractiveImport/{Series => Artist}/SelectArtistModal.js (100%) rename frontend/src/InteractiveImport/{Series => Artist}/SelectArtistModalContent.css (100%) rename frontend/src/InteractiveImport/{Series => Artist}/SelectArtistModalContent.js (87%) rename frontend/src/InteractiveImport/{Series => Artist}/SelectArtistModalContentConnector.js (86%) rename frontend/src/InteractiveImport/{Series => Artist}/SelectArtistRow.css (85%) rename frontend/src/InteractiveImport/{Series => Artist}/SelectArtistRow.js (71%) delete mode 100644 frontend/src/InteractiveImport/Season/SelectSeasonRow.js rename frontend/src/InteractiveImport/{Episode/SelectEpisodeModal.js => Track/SelectTrackModal.js} (68%) rename frontend/src/InteractiveImport/{Episode/SelectEpisodeModalContent.js => Track/SelectTrackModalContent.js} (83%) rename frontend/src/InteractiveImport/{Episode/SelectEpisodeModalContentConnector.js => Track/SelectTrackModalContentConnector.js} (78%) rename frontend/src/InteractiveImport/{Episode/SelectEpisodeRow.js => Track/SelectTrackRow.js} (76%) diff --git a/frontend/src/AddArtist/ImportArtist/Import/ImportArtistRowConnector.js b/frontend/src/AddArtist/ImportArtist/Import/ImportArtistRowConnector.js index d72afc6bf..a16183125 100644 --- a/frontend/src/AddArtist/ImportArtist/Import/ImportArtistRowConnector.js +++ b/frontend/src/AddArtist/ImportArtist/Import/ImportArtistRowConnector.js @@ -71,7 +71,7 @@ class ImportArtistRowConnector extends Component { ); } diff --git a/frontend/src/AddArtist/ImportArtist/Import/SelectArtist/ImportArtistSelectArtist.js b/frontend/src/AddArtist/ImportArtist/Import/SelectArtist/ImportArtistSelectArtist.js index 9e0a85f59..dcabc12a0 100644 --- a/frontend/src/AddArtist/ImportArtist/Import/SelectArtist/ImportArtistSelectArtist.js +++ b/frontend/src/AddArtist/ImportArtist/Import/SelectArtist/ImportArtistSelectArtist.js @@ -99,10 +99,10 @@ class ImportArtistSelectArtist extends Component { }); } - onSeriesSelect = (foreignArtistId) => { + onArtistSelect = (foreignArtistId) => { this.setState({ isOpen: false }); - this.props.onSeriesSelect(foreignArtistId); + this.props.onArtistSelect(foreignArtistId); } // @@ -117,7 +117,7 @@ class ImportArtistSelectArtist extends Component { error, items, queued, - onSeriesSelect + onArtistSelect } = this.props; const errorMessage = error && @@ -233,7 +233,7 @@ class ImportArtistSelectArtist extends Component { overview={item.overview} // year={item.year} // network={item.network} - onPress={this.onSeriesSelect} + onPress={this.onArtistSelect} /> ); }) @@ -257,7 +257,7 @@ ImportArtistSelectArtist.propTypes = { items: PropTypes.arrayOf(PropTypes.object).isRequired, queued: PropTypes.bool.isRequired, onSearchInputChange: PropTypes.func.isRequired, - onSeriesSelect: PropTypes.func.isRequired + onArtistSelect: PropTypes.func.isRequired }; ImportArtistSelectArtist.defaultProps = { diff --git a/frontend/src/AddArtist/ImportArtist/Import/SelectArtist/ImportArtistSelectArtistConnector.js b/frontend/src/AddArtist/ImportArtist/Import/SelectArtist/ImportArtistSelectArtistConnector.js index d4e450e94..cc55f53cd 100644 --- a/frontend/src/AddArtist/ImportArtist/Import/SelectArtist/ImportArtistSelectArtistConnector.js +++ b/frontend/src/AddArtist/ImportArtist/Import/SelectArtist/ImportArtistSelectArtistConnector.js @@ -33,7 +33,7 @@ class ImportArtistSelectArtistConnector extends Component { }); } - onSeriesSelect = (foreignArtistId) => { + onArtistSelect = (foreignArtistId) => { const { id, items @@ -53,7 +53,7 @@ class ImportArtistSelectArtistConnector extends Component { ); } diff --git a/frontend/src/App/App.js b/frontend/src/App/App.js index c81fd1253..4295ab2af 100644 --- a/frontend/src/App/App.js +++ b/frontend/src/App/App.js @@ -45,7 +45,7 @@ function App({ store, history }) { {/* - Series + Artist */} { + onExpandPress = (albumId, isExpanded) => { this.setState((state) => { const convertedState = { allSelected: state.allExpanded, @@ -120,7 +120,7 @@ class SeriesDetails extends Component { selectedState: state.expandedState }; - const newState = toggleSelected(convertedState, [], seasonNumber, isExpanded, false); + const newState = toggleSelected(convertedState, [], albumId, isExpanded, false); return getExpandedState(newState); }); @@ -136,10 +136,9 @@ class SeriesDetails extends Component { tvMazeId, imdbId, artistName, - runtime, ratings, sizeOnDisk, - episodeFileCount, + trackFileCount, qualityProfileId, monitored, status, @@ -175,10 +174,10 @@ class SeriesDetails extends Component { let episodeFilesCountMessage = 'No episode files'; - if (episodeFileCount === 1) { + if (trackFileCount === 1) { episodeFilesCountMessage = '1 episode file'; - } else if (episodeFileCount > 1) { - episodeFilesCountMessage = `${episodeFileCount} episode files`; + } else if (trackFileCount > 1) { + episodeFilesCountMessage = `${trackFileCount} episode files`; } let expandIcon = icons.EXPAND_INDETERMINATE; @@ -310,13 +309,6 @@ class SeriesDetails extends Component {
- { - !!runtime && - - {runtime} Minutes - - } - { return ( ); @@ -548,10 +542,9 @@ SeriesDetails.propTypes = { tvMazeId: PropTypes.number, imdbId: PropTypes.string, artistName: PropTypes.string.isRequired, - runtime: PropTypes.number.isRequired, ratings: PropTypes.object.isRequired, sizeOnDisk: PropTypes.number.isRequired, - episodeFileCount: PropTypes.number, + trackFileCount: PropTypes.number, qualityProfileId: PropTypes.number.isRequired, monitored: PropTypes.bool.isRequired, status: PropTypes.string.isRequired, diff --git a/frontend/src/Artist/Details/SeriesDetailsLinks.js b/frontend/src/Artist/Details/SeriesDetailsLinks.js index 2733c0edb..d1be3d409 100644 --- a/frontend/src/Artist/Details/SeriesDetailsLinks.js +++ b/frontend/src/Artist/Details/SeriesDetailsLinks.js @@ -16,30 +16,16 @@ function SeriesDetailsLinks(props) {
- - - - - { !!tvMazeId && { - if (episode.episodeFileId || (episode.monitored && isBefore(episode.airDateUtc))) { - episodeCount++; - } - - if (episode.episodeFileId) { - episodeFileCount++; - } - - totalEpisodeCount++; - }); - - return { - episodeCount, - episodeFileCount, - totalEpisodeCount - }; -} - function getEpisodeCountKind(monitored, episodeFileCount, episodeCount) { if (episodeFileCount === episodeCount && episodeCount > 0) { return kinds.SUCCESS; @@ -89,7 +65,7 @@ class SeriesDetailsSeason extends Component { _expandByDefault() { const { - seasonNumber, + albumId, onExpandPress, items } = this.props; @@ -99,7 +75,7 @@ class SeriesDetailsSeason extends Component { isAfter(item.airDateUtc, { days: -30 }); }); - onExpandPress(seasonNumber, expand && seasonNumber > 0); + onExpandPress(albumId, expand && albumId > 0); } // @@ -123,11 +99,11 @@ class SeriesDetailsSeason extends Component { onExpandPress = () => { const { - seasonNumber, + albumId, isExpanded } = this.props; - this.props.onExpandPress(seasonNumber, !isExpanded); + this.props.onExpandPress(albumId, !isExpanded); } onMonitorEpisodePress = (episodeId, monitored, { shiftKey }) => { @@ -155,7 +131,10 @@ class SeriesDetailsSeason extends Component { const { artistId, monitored, - seasonNumber, + title, + releaseDate, + albumId, + statistics, items, columns, isSaving, @@ -169,10 +148,10 @@ class SeriesDetailsSeason extends Component { } = this.props; const { - episodeCount, - episodeFileCount, - totalEpisodeCount - } = getSeasonStatistics(items); + trackCount, + trackFileCount, + totalTrackCount + } = statistics; const { isOrganizeModalOpen, @@ -194,22 +173,22 @@ class SeriesDetailsSeason extends Component { /> { - seasonNumber === 0 ? + albumId === 0 ? Specials : - Season {seasonNumber} + {title} }
@@ -218,12 +197,7 @@ class SeriesDetailsSeason extends Component { className={styles.expandButton} onPress={this.onExpandPress} > - + { !isSmallScreen &&   @@ -277,7 +251,7 @@ class SeriesDetailsSeason extends Component { name={icons.EPISODE_FILE} /> - Manage Episodes + Manage Tracks : @@ -286,7 +260,7 @@ class SeriesDetailsSeason extends Component { @@ -303,7 +277,7 @@ class SeriesDetailsSeason extends Component { @@ -312,59 +286,17 @@ class SeriesDetailsSeason extends Component {
-
- { - isExpanded && -
- { - items.length ? - - - { - items.map((item) => { - return ( - - ); - }) - } - -
: - -
- No episodes in this season -
- } -
- -
-
- } -
-
@@ -375,7 +307,10 @@ class SeriesDetailsSeason extends Component { SeriesDetailsSeason.propTypes = { artistId: PropTypes.number.isRequired, monitored: PropTypes.bool.isRequired, - seasonNumber: PropTypes.number.isRequired, + title: PropTypes.string.isRequired, + releaseDate: PropTypes.string.isRequired, + albumId: PropTypes.number.isRequired, + statistics: PropTypes.object.isRequired, items: PropTypes.arrayOf(PropTypes.object).isRequired, columns: PropTypes.arrayOf(PropTypes.object).isRequired, isSaving: PropTypes.bool, @@ -390,4 +325,12 @@ SeriesDetailsSeason.propTypes = { onSearchPress: PropTypes.func.isRequired }; +SeriesDetailsSeason.defaultProps = { + statistics: { + trackFileCount: 0, + totalTrackCount: 0, + percentOfTracks: 0 + } +}; + export default SeriesDetailsSeason; diff --git a/frontend/src/Artist/Details/SeriesDetailsSeasonConnector.js b/frontend/src/Artist/Details/SeriesDetailsSeasonConnector.js index a46b71e0f..6035eb73e 100644 --- a/frontend/src/Artist/Details/SeriesDetailsSeasonConnector.js +++ b/frontend/src/Artist/Details/SeriesDetailsSeasonConnector.js @@ -60,12 +60,12 @@ class SeriesDetailsSeasonConnector extends Component { onMonitorSeasonPress = (monitored) => { const { artistId, - seasonNumber + albumId } = this.props; this.props.toggleSeasonMonitored({ artistId, - seasonNumber, + albumId, monitored }); } @@ -73,13 +73,13 @@ class SeriesDetailsSeasonConnector extends Component { onSearchPress = () => { const { artistId, - seasonNumber + albumId } = this.props; this.props.executeCommand({ name: commandNames.SEASON_SEARCH, artistId, - seasonNumber + albumIds: [albumId] }); } @@ -108,7 +108,7 @@ class SeriesDetailsSeasonConnector extends Component { SeriesDetailsSeasonConnector.propTypes = { artistId: PropTypes.number.isRequired, - seasonNumber: PropTypes.number.isRequired, + albumId: PropTypes.number.isRequired, toggleSeasonMonitored: PropTypes.func.isRequired, toggleEpisodesMonitored: PropTypes.func.isRequired, setEpisodesTableOption: PropTypes.func.isRequired, diff --git a/frontend/src/Commands/commandNames.js b/frontend/src/Commands/commandNames.js index a80471b98..47b273248 100644 --- a/frontend/src/Commands/commandNames.js +++ b/frontend/src/Commands/commandNames.js @@ -15,5 +15,5 @@ export const RENAME_FILES = 'RenameFiles'; export const RENAME_ARTIST = 'RenameArtist'; export const RESET_API_KEY = 'ResetApiKey'; export const RSS_SYNC = 'RssSync'; -export const SEASON_SEARCH = 'SeasonSearch'; +export const SEASON_SEARCH = 'AlbumSearch'; export const ARTIST_SEARCH = 'ArtistSearch'; diff --git a/frontend/src/InteractiveImport/Season/SelectSeasonModal.js b/frontend/src/InteractiveImport/Album/SelectAlbumModal.js similarity index 68% rename from frontend/src/InteractiveImport/Season/SelectSeasonModal.js rename to frontend/src/InteractiveImport/Album/SelectAlbumModal.js index 9de9ee493..d4f26f4ff 100644 --- a/frontend/src/InteractiveImport/Season/SelectSeasonModal.js +++ b/frontend/src/InteractiveImport/Album/SelectAlbumModal.js @@ -1,9 +1,9 @@ import PropTypes from 'prop-types'; import React, { Component } from 'react'; import Modal from 'Components/Modal/Modal'; -import SelectSeasonModalContentConnector from './SelectSeasonModalContentConnector'; +import SelectAlbumModalContentConnector from './SelectAlbumModalContentConnector'; -class SelectSeasonModal extends Component { +class SelectAlbumModal extends Component { // // Render @@ -20,7 +20,7 @@ class SelectSeasonModal extends Component { isOpen={isOpen} onModalClose={onModalClose} > - @@ -29,9 +29,9 @@ class SelectSeasonModal extends Component { } } -SelectSeasonModal.propTypes = { +SelectAlbumModal.propTypes = { isOpen: PropTypes.bool.isRequired, onModalClose: PropTypes.func.isRequired }; -export default SelectSeasonModal; +export default SelectAlbumModal; diff --git a/frontend/src/InteractiveImport/Season/SelectSeasonModalContent.js b/frontend/src/InteractiveImport/Album/SelectAlbumModalContent.js similarity index 68% rename from frontend/src/InteractiveImport/Season/SelectSeasonModalContent.js rename to frontend/src/InteractiveImport/Album/SelectAlbumModalContent.js index 267174491..f5965fde2 100644 --- a/frontend/src/InteractiveImport/Season/SelectSeasonModalContent.js +++ b/frontend/src/InteractiveImport/Album/SelectAlbumModalContent.js @@ -5,9 +5,9 @@ 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 SelectSeasonRow from './SelectSeasonRow'; +import SelectAlbumRow from './SelectAlbumRow'; -class SelectSeasonModalContent extends Component { +class SelectAlbumModalContent extends Component { // // Render @@ -15,24 +15,25 @@ class SelectSeasonModalContent extends Component { render() { const { items, - onSeasonSelect, + onAlbumSelect, onModalClose } = this.props; return ( - Manual Import - Select Season + Manual Import - Select Album { items.map((item) => { return ( - ); }) @@ -49,10 +50,10 @@ class SelectSeasonModalContent extends Component { } } -SelectSeasonModalContent.propTypes = { +SelectAlbumModalContent.propTypes = { items: PropTypes.arrayOf(PropTypes.object).isRequired, - onSeasonSelect: PropTypes.func.isRequired, + onAlbumSelect: PropTypes.func.isRequired, onModalClose: PropTypes.func.isRequired }; -export default SelectSeasonModalContent; +export default SelectAlbumModalContent; diff --git a/frontend/src/InteractiveImport/Season/SelectSeasonModalContentConnector.js b/frontend/src/InteractiveImport/Album/SelectAlbumModalContentConnector.js similarity index 71% rename from frontend/src/InteractiveImport/Season/SelectSeasonModalContentConnector.js rename to frontend/src/InteractiveImport/Album/SelectAlbumModalContentConnector.js index 65304b339..7adb8a1f7 100644 --- a/frontend/src/InteractiveImport/Season/SelectSeasonModalContentConnector.js +++ b/frontend/src/InteractiveImport/Album/SelectAlbumModalContentConnector.js @@ -1,17 +1,18 @@ +import _ from 'lodash'; import PropTypes from 'prop-types'; import React, { Component } from 'react'; import { connect } from 'react-redux'; import { createSelector } from 'reselect'; import { updateInteractiveImportItem } from 'Store/Actions/interactiveImportActions'; import createArtistSelector from 'Store/Selectors/createArtistSelector'; -import SelectSeasonModalContent from './SelectSeasonModalContent'; +import SelectAlbumModalContent from './SelectAlbumModalContent'; function createMapStateToProps() { return createSelector( createArtistSelector(), (series) => { return { - items: series.seasons + items: series.albums }; } ); @@ -21,16 +22,18 @@ const mapDispatchToProps = { updateInteractiveImportItem }; -class SelectSeasonModalContentConnector extends Component { +class SelectAlbumModalContentConnector extends Component { // // Listeners - onSeasonSelect = (seasonNumber) => { + onAlbumSelect = (albumId) => { + const album = _.find(this.props.items, { id: albumId }); + this.props.ids.forEach((id) => { this.props.updateInteractiveImportItem({ id, - seasonNumber, + album, episodes: [] }); }); @@ -43,15 +46,15 @@ class SelectSeasonModalContentConnector extends Component { render() { return ( - ); } } -SelectSeasonModalContentConnector.propTypes = { +SelectAlbumModalContentConnector.propTypes = { ids: PropTypes.arrayOf(PropTypes.number).isRequired, artistId: PropTypes.number.isRequired, items: PropTypes.arrayOf(PropTypes.object).isRequired, @@ -59,4 +62,4 @@ SelectSeasonModalContentConnector.propTypes = { onModalClose: PropTypes.func.isRequired }; -export default connect(createMapStateToProps, mapDispatchToProps)(SelectSeasonModalContentConnector); +export default connect(createMapStateToProps, mapDispatchToProps)(SelectAlbumModalContentConnector); diff --git a/frontend/src/InteractiveImport/Season/SelectSeasonRow.css b/frontend/src/InteractiveImport/Album/SelectAlbumRow.css similarity index 100% rename from frontend/src/InteractiveImport/Season/SelectSeasonRow.css rename to frontend/src/InteractiveImport/Album/SelectAlbumRow.css diff --git a/frontend/src/InteractiveImport/Album/SelectAlbumRow.js b/frontend/src/InteractiveImport/Album/SelectAlbumRow.js new file mode 100644 index 000000000..eb163a7c2 --- /dev/null +++ b/frontend/src/InteractiveImport/Album/SelectAlbumRow.js @@ -0,0 +1,37 @@ +import PropTypes from 'prop-types'; +import React, { Component } from 'react'; +import Link from 'Components/Link/Link'; +import styles from './SelectAlbumRow.css'; + +class SelectAlbumRow extends Component { + + // + // Listeners + + onPress = () => { + this.props.onAlbumSelect(this.props.id); + } + + // + // Render + + render() { + return ( + + {this.props.title} + + ); + } +} + +SelectAlbumRow.propTypes = { + id: PropTypes.number.isRequired, + title: PropTypes.string.isRequired, + onAlbumSelect: PropTypes.func.isRequired +}; + +export default SelectAlbumRow; diff --git a/frontend/src/InteractiveImport/Series/SelectArtistModal.js b/frontend/src/InteractiveImport/Artist/SelectArtistModal.js similarity index 100% rename from frontend/src/InteractiveImport/Series/SelectArtistModal.js rename to frontend/src/InteractiveImport/Artist/SelectArtistModal.js diff --git a/frontend/src/InteractiveImport/Series/SelectArtistModalContent.css b/frontend/src/InteractiveImport/Artist/SelectArtistModalContent.css similarity index 100% rename from frontend/src/InteractiveImport/Series/SelectArtistModalContent.css rename to frontend/src/InteractiveImport/Artist/SelectArtistModalContent.css diff --git a/frontend/src/InteractiveImport/Series/SelectArtistModalContent.js b/frontend/src/InteractiveImport/Artist/SelectArtistModalContent.js similarity index 87% rename from frontend/src/InteractiveImport/Series/SelectArtistModalContent.js rename to frontend/src/InteractiveImport/Artist/SelectArtistModalContent.js index 32fd6f82a..fd767575a 100644 --- a/frontend/src/InteractiveImport/Series/SelectArtistModalContent.js +++ b/frontend/src/InteractiveImport/Artist/SelectArtistModalContent.js @@ -37,7 +37,7 @@ class SelectArtistModalContent extends Component { render() { const { items, - onSeriesSelect, + onArtistSelect, onModalClose } = this.props; @@ -46,7 +46,7 @@ class SelectArtistModalContent extends Component { return ( - Manual Import - Select Series + Manual Import - Select Artist { items.map((item) => { - return item.title.toLowerCase().includes(filter) ? + return item.artistName.toLowerCase().includes(filter) ? ( ) : null; @@ -92,7 +92,7 @@ class SelectArtistModalContent extends Component { SelectArtistModalContent.propTypes = { items: PropTypes.arrayOf(PropTypes.object).isRequired, - onSeriesSelect: PropTypes.func.isRequired, + onArtistSelect: PropTypes.func.isRequired, onModalClose: PropTypes.func.isRequired }; diff --git a/frontend/src/InteractiveImport/Series/SelectArtistModalContentConnector.js b/frontend/src/InteractiveImport/Artist/SelectArtistModalContentConnector.js similarity index 86% rename from frontend/src/InteractiveImport/Series/SelectArtistModalContentConnector.js rename to frontend/src/InteractiveImport/Artist/SelectArtistModalContentConnector.js index 95b0a02a4..78c5631f0 100644 --- a/frontend/src/InteractiveImport/Series/SelectArtistModalContentConnector.js +++ b/frontend/src/InteractiveImport/Artist/SelectArtistModalContentConnector.js @@ -27,15 +27,15 @@ class SelectArtistModalContentConnector extends Component { // // Listeners - onSeriesSelect = (artistId) => { - const series = _.find(this.props.items, { id: artistId }); + onArtistSelect = (artistId) => { + const artist = _.find(this.props.items, { id: artistId }); this.props.ids.forEach((id) => { this.props.updateInteractiveImportItem({ id, - series, - seasonNumber: undefined, - episodes: [] + artist, + album: undefined, + tracks: [] }); }); @@ -49,7 +49,7 @@ class SelectArtistModalContentConnector extends Component { return ( ); } diff --git a/frontend/src/InteractiveImport/Series/SelectArtistRow.css b/frontend/src/InteractiveImport/Artist/SelectArtistRow.css similarity index 85% rename from frontend/src/InteractiveImport/Series/SelectArtistRow.css rename to frontend/src/InteractiveImport/Artist/SelectArtistRow.css index f2573d585..376c3fe84 100644 --- a/frontend/src/InteractiveImport/Series/SelectArtistRow.css +++ b/frontend/src/InteractiveImport/Artist/SelectArtistRow.css @@ -1,4 +1,4 @@ -.series { +.artist { padding: 8px; border-bottom: 1px solid $borderColor; } diff --git a/frontend/src/InteractiveImport/Series/SelectArtistRow.js b/frontend/src/InteractiveImport/Artist/SelectArtistRow.js similarity index 71% rename from frontend/src/InteractiveImport/Series/SelectArtistRow.js rename to frontend/src/InteractiveImport/Artist/SelectArtistRow.js index 08ed42fa1..dcf252bb6 100644 --- a/frontend/src/InteractiveImport/Series/SelectArtistRow.js +++ b/frontend/src/InteractiveImport/Artist/SelectArtistRow.js @@ -9,7 +9,7 @@ class SelectArtistRow extends Component { // Listeners onPress = () => { - this.props.onSeriesSelect(this.props.id); + this.props.onArtistSelect(this.props.id); } // @@ -18,11 +18,11 @@ class SelectArtistRow extends Component { render() { return ( - {this.props.title} + {this.props.artistName} ); } @@ -30,8 +30,8 @@ class SelectArtistRow extends Component { SelectArtistRow.propTypes = { id: PropTypes.number.isRequired, - title: PropTypes.string.isRequired, - onSeriesSelect: PropTypes.func.isRequired + artistName: PropTypes.string.isRequired, + onArtistSelect: PropTypes.func.isRequired }; export default SelectArtistRow; diff --git a/frontend/src/InteractiveImport/Interactive/InteractiveImportModalContent.js b/frontend/src/InteractiveImport/Interactive/InteractiveImportModalContent.js index fdc70e7c2..b3c111d5f 100644 --- a/frontend/src/InteractiveImport/Interactive/InteractiveImportModalContent.js +++ b/frontend/src/InteractiveImport/Interactive/InteractiveImportModalContent.js @@ -15,8 +15,8 @@ import ModalBody from 'Components/Modal/ModalBody'; import ModalFooter from 'Components/Modal/ModalFooter'; import Table from 'Components/Table/Table'; import TableBody from 'Components/Table/TableBody'; -import SelectArtistModal from 'InteractiveImport/Series/SelectArtistModal'; -import SelectSeasonModal from 'InteractiveImport/Season/SelectSeasonModal'; +import SelectArtistModal from 'InteractiveImport/Artist/SelectArtistModal'; +import SelectAlbumModal from 'InteractiveImport/Album/SelectAlbumModal'; import InteractiveImportRow from './InteractiveImportRow'; import styles from './InteractiveImportModalContent.css'; @@ -28,19 +28,19 @@ const columns = [ isVisible: true }, { - name: 'series', - label: 'Series', + name: 'artist', + label: 'Artist', isSortable: true, isVisible: true }, { - name: 'season', - label: 'Season', + name: 'album', + label: 'Album', isVisible: true }, { - name: 'episodes', - label: 'Episode(s)', + name: 'tracks', + label: 'Track(s)', isVisible: true }, { @@ -79,7 +79,7 @@ class InteractiveImportModalContent extends Component { selectedState: {}, invalidRowsSelected: [], isSelectArtistModalOpen: false, - isSelectSeasonModalOpen: false + isSelectAlbumModalOpen: false }; } @@ -131,16 +131,16 @@ class InteractiveImportModalContent extends Component { this.setState({ isSelectArtistModalOpen: true }); } - onSelectSeasonPress = () => { - this.setState({ isSelectSeasonModalOpen: true }); + onSelectAlbumPress = () => { + this.setState({ isSelectAlbumModalOpen: true }); } onSelectArtistModalClose = () => { this.setState({ isSelectArtistModalOpen: false }); } - onSelectSeasonModalClose = () => { - this.setState({ isSelectSeasonModalOpen: false }); + onSelectAlbumModalClose = () => { + this.setState({ isSelectAlbumModalOpen: false }); } // @@ -169,7 +169,7 @@ class InteractiveImportModalContent extends Component { selectedState, invalidRowsSelected, isSelectArtistModalOpen, - isSelectSeasonModalOpen + isSelectAlbumModalOpen } = this.state; const selectedIds = this.getSelectedIds(); @@ -250,11 +250,11 @@ class InteractiveImportModalContent extends Component {
-
@@ -284,11 +284,11 @@ class InteractiveImportModalContent extends Component { onModalClose={this.onSelectArtistModalClose} /> -
); diff --git a/frontend/src/InteractiveImport/Interactive/InteractiveImportModalContentConnector.js b/frontend/src/InteractiveImport/Interactive/InteractiveImportModalContentConnector.js index 030bb5c96..234ed7921 100644 --- a/frontend/src/InteractiveImport/Interactive/InteractiveImportModalContentConnector.js +++ b/frontend/src/InteractiveImport/Interactive/InteractiveImportModalContentConnector.js @@ -71,31 +71,32 @@ class InteractiveImportModalContentConnector extends Component { if (isSelected) { const { - series, - seasonNumber, - episodes, + artist, + album, + tracks, quality } = item; - if (!series) { - this.setState({ interactiveImportErrorMessage: 'Series must be chosen for each selected file' }); + if (!artist) { + this.setState({ interactiveImportErrorMessage: 'Artist must be chosen for each selected file' }); return false; } - if (isNaN(seasonNumber)) { - this.setState({ interactiveImportErrorMessage: 'Season must be chosen for each selected file' }); + if (!album) { + this.setState({ interactiveImportErrorMessage: 'Album must be chosen for each selected file' }); return false; } - if (!episodes || !episodes.length) { - this.setState({ interactiveImportErrorMessage: 'One or more episodes must be chosen for each selected file' }); + if (!tracks || !tracks.length) { + this.setState({ interactiveImportErrorMessage: 'One or more tracks must be chosen for each selected file' }); return false; } files.push({ path: item.path, - artistId: series.id, - episodeIds: _.map(episodes, 'id'), + artistId: artist.id, + albumId: album.id, + trackIds: _.map(tracks, 'id'), quality, downloadId: this.props.downloadId }); diff --git a/frontend/src/InteractiveImport/Interactive/InteractiveImportRow.js b/frontend/src/InteractiveImport/Interactive/InteractiveImportRow.js index 139722ab2..365196fb4 100644 --- a/frontend/src/InteractiveImport/Interactive/InteractiveImportRow.js +++ b/frontend/src/InteractiveImport/Interactive/InteractiveImportRow.js @@ -9,9 +9,9 @@ import TableRowCellButton from 'Components/Table/Cells/TableRowCellButton'; import TableSelectCell from 'Components/Table/Cells/TableSelectCell'; import Popover from 'Components/Tooltip/Popover'; import EpisodeQuality from 'Episode/EpisodeQuality'; -import SelectArtistModal from 'InteractiveImport/Series/SelectArtistModal'; -import SelectSeasonModal from 'InteractiveImport/Season/SelectSeasonModal'; -import SelectEpisodeModal from 'InteractiveImport/Episode/SelectEpisodeModal'; +import SelectArtistModal from 'InteractiveImport/Artist/SelectArtistModal'; +import SelectAlbumModal from 'InteractiveImport/Album/SelectAlbumModal'; +import SelectTrackModal from 'InteractiveImport/Track/SelectTrackModal'; import SelectQualityModal from 'InteractiveImport/Quality/SelectQualityModal'; import InteractiveImportRowCellPlaceholder from './InteractiveImportRowCellPlaceholder'; import styles from './InteractiveImportRow.css'; @@ -26,8 +26,8 @@ class InteractiveImportRow extends Component { this.state = { isSelectArtistModalOpen: false, - isSelectSeasonModalOpen: false, - isSelectEpisodeModalOpen: false, + isSelectAlbumModalOpen: false, + isSelectTrackModalOpen: false, isSelectQualityModalOpen: false }; } @@ -35,13 +35,13 @@ class InteractiveImportRow extends Component { componentDidMount() { const { id, - series, - seasonNumber, - episodes, + artist, + album, + tracks, quality } = this.props; - if (series && seasonNumber !== undefined && episodes.length && quality) { + if (artist && album !== undefined && tracks.length && quality) { this.props.onSelectedChange({ id, value: true }); } } @@ -49,9 +49,9 @@ class InteractiveImportRow extends Component { componentDidUpdate(prevProps) { const { id, - series, - seasonNumber, - episodes, + artist, + album, + tracks, quality, isSelected, onValidRowChange @@ -61,7 +61,7 @@ class InteractiveImportRow extends Component { return; } - const isValid = !!(series && seasonNumber != null && episodes.length && quality); + const isValid = !!(artist && album != null && tracks.length && quality); if (isSelected && !isValid) { onValidRowChange(id, false); @@ -91,12 +91,12 @@ class InteractiveImportRow extends Component { this.setState({ isSelectArtistModalOpen: true }); } - onSelectSeasonPress = () => { - this.setState({ isSelectSeasonModalOpen: true }); + onSelectAlbumPress = () => { + this.setState({ isSelectAlbumModalOpen: true }); } - onSelectEpisodePress = () => { - this.setState({ isSelectEpisodeModalOpen: true }); + onSelectTrackPress = () => { + this.setState({ isSelectTrackModalOpen: true }); } onSelectQualityPress = () => { @@ -108,13 +108,13 @@ class InteractiveImportRow extends Component { this.selectRowAfterChange(changed); } - onSelectSeasonModalClose = (changed) => { - this.setState({ isSelectSeasonModalOpen: false }); + onSelectAlbumModalClose = (changed) => { + this.setState({ isSelectAlbumModalOpen: false }); this.selectRowAfterChange(changed); } - onSelectEpisodeModalClose = (changed) => { - this.setState({ isSelectEpisodeModalOpen: false }); + onSelectTrackModalClose = (changed) => { + this.setState({ isSelectTrackModalOpen: false }); this.selectRowAfterChange(changed); } @@ -130,9 +130,9 @@ class InteractiveImportRow extends Component { const { id, relativePath, - series, - seasonNumber, - episodes, + artist, + album, + tracks, quality, size, rejections, @@ -142,18 +142,19 @@ class InteractiveImportRow extends Component { const { isSelectArtistModalOpen, - isSelectSeasonModalOpen, - isSelectEpisodeModalOpen, + isSelectAlbumModalOpen, + isSelectTrackModalOpen, isSelectQualityModalOpen } = this.state; - const seriesTitle = series ? series.title : ''; - const episodeNumbers = episodes.map((episode) => episode.episodeNumber) + const seriesTitle = artist ? artist.artistName : ''; + const albumTitle = album ? album.title : ''; + const trackNumbers = tracks.map((episode) => episode.trackNumber) .join(', '); - const showSeriesPlaceholder = isSelected && !series; - const showSeasonNumberPlaceholder = isSelected && !!series && isNaN(seasonNumber); - const showEpisodeNumbersPlaceholder = isSelected && Number.isInteger(seasonNumber) && !episodes.length; + const showSeriesPlaceholder = isSelected && !artist; + const showSeasonNumberPlaceholder = isSelected && !!artist && !album; + const showEpisodeNumbersPlaceholder = isSelected && !!album && !tracks.length; return ( @@ -179,20 +180,20 @@ class InteractiveImportRow extends Component { { - showSeasonNumberPlaceholder ? : seasonNumber + showSeasonNumberPlaceholder ? : albumTitle } { - showEpisodeNumbersPlaceholder ? : episodeNumbers + showEpisodeNumbersPlaceholder ? : trackNumbers } @@ -244,19 +245,19 @@ class InteractiveImportRow extends Component { onModalClose={this.onSelectArtistModalClose} /> - - { - this.props.onSeasonSelect(this.props.seasonNumber); - } - - // - // Render - - render() { - const seasonNumber = this.props.seasonNumber; - - return ( - - { - seasonNumber === 0 ? 'Specials' : `Season ${seasonNumber}` - } - - ); - } -} - -SelectSeasonRow.propTypes = { - seasonNumber: PropTypes.number.isRequired, - onSeasonSelect: PropTypes.func.isRequired -}; - -export default SelectSeasonRow; diff --git a/frontend/src/InteractiveImport/Episode/SelectEpisodeModal.js b/frontend/src/InteractiveImport/Track/SelectTrackModal.js similarity index 68% rename from frontend/src/InteractiveImport/Episode/SelectEpisodeModal.js rename to frontend/src/InteractiveImport/Track/SelectTrackModal.js index 31ea74d23..f8c9c4160 100644 --- a/frontend/src/InteractiveImport/Episode/SelectEpisodeModal.js +++ b/frontend/src/InteractiveImport/Track/SelectTrackModal.js @@ -1,9 +1,9 @@ import PropTypes from 'prop-types'; import React, { Component } from 'react'; import Modal from 'Components/Modal/Modal'; -import SelectEpisodeModalContentConnector from './SelectEpisodeModalContentConnector'; +import SelectTrackModalContentConnector from './SelectTrackModalContentConnector'; -class SelectEpisodeModal extends Component { +class SelectTrackModal extends Component { // // Render @@ -20,7 +20,7 @@ class SelectEpisodeModal extends Component { isOpen={isOpen} onModalClose={onModalClose} > - @@ -29,9 +29,9 @@ class SelectEpisodeModal extends Component { } } -SelectEpisodeModal.propTypes = { +SelectTrackModal.propTypes = { isOpen: PropTypes.bool.isRequired, onModalClose: PropTypes.func.isRequired }; -export default SelectEpisodeModal; +export default SelectTrackModal; diff --git a/frontend/src/InteractiveImport/Episode/SelectEpisodeModalContent.js b/frontend/src/InteractiveImport/Track/SelectTrackModalContent.js similarity index 83% rename from frontend/src/InteractiveImport/Episode/SelectEpisodeModalContent.js rename to frontend/src/InteractiveImport/Track/SelectTrackModalContent.js index c6b6154ff..519ea930d 100644 --- a/frontend/src/InteractiveImport/Episode/SelectEpisodeModalContent.js +++ b/frontend/src/InteractiveImport/Track/SelectTrackModalContent.js @@ -12,11 +12,11 @@ import ModalBody from 'Components/Modal/ModalBody'; import ModalFooter from 'Components/Modal/ModalFooter'; import Table from 'Components/Table/Table'; import TableBody from 'Components/Table/TableBody'; -import SelectEpisodeRow from './SelectEpisodeRow'; +import SelectTrackRow from './SelectTrackRow'; const columns = [ { - name: 'episodeNumber', + name: 'trackNumber', label: '#', isSortable: true, isVisible: true @@ -25,15 +25,10 @@ const columns = [ name: 'title', label: 'Title', isVisible: true - }, - { - name: 'airDate', - label: 'Air Date', - isVisible: true } ]; -class SelectEpisodeModalContent extends Component { +class SelectTrackModalContent extends Component { // // Lifecycle @@ -69,8 +64,8 @@ class SelectEpisodeModalContent extends Component { }); } - onEpisodesSelect = () => { - this.props.onEpisodesSelect(this.getSelectedIds()); + onTracksSelect = () => { + this.props.onTracksSelect(this.getSelectedIds()); } // @@ -94,12 +89,12 @@ class SelectEpisodeModalContent extends Component { selectedState } = this.state; - const errorMessage = error && error.message || 'Unable to load episodes'; + const errorMessage = error && error.message || 'Unable to load tracks'; return ( - Manual Import - Select Episode(s) + Manual Import - Select Track(s) @@ -129,12 +124,11 @@ class SelectEpisodeModalContent extends Component { { items.map((item) => { return ( - @@ -147,7 +141,7 @@ class SelectEpisodeModalContent extends Component { { isPopulated && !items.length && - 'No episodes were found for the selected season' + 'No tracks were found for the selected album' } @@ -158,9 +152,9 @@ class SelectEpisodeModalContent extends Component { @@ -168,7 +162,7 @@ class SelectEpisodeModalContent extends Component { } } -SelectEpisodeModalContent.propTypes = { +SelectTrackModalContent.propTypes = { isFetching: PropTypes.bool.isRequired, isPopulated: PropTypes.bool.isRequired, error: PropTypes.object, @@ -176,8 +170,8 @@ SelectEpisodeModalContent.propTypes = { sortKey: PropTypes.string, sortDirection: PropTypes.string, onSortPress: PropTypes.func.isRequired, - onEpisodesSelect: PropTypes.func.isRequired, + onTracksSelect: PropTypes.func.isRequired, onModalClose: PropTypes.func.isRequired }; -export default SelectEpisodeModalContent; +export default SelectTrackModalContent; diff --git a/frontend/src/InteractiveImport/Episode/SelectEpisodeModalContentConnector.js b/frontend/src/InteractiveImport/Track/SelectTrackModalContentConnector.js similarity index 78% rename from frontend/src/InteractiveImport/Episode/SelectEpisodeModalContentConnector.js rename to frontend/src/InteractiveImport/Track/SelectTrackModalContentConnector.js index e107e9dd5..368b7b9fd 100644 --- a/frontend/src/InteractiveImport/Episode/SelectEpisodeModalContentConnector.js +++ b/frontend/src/InteractiveImport/Track/SelectTrackModalContentConnector.js @@ -6,7 +6,7 @@ import connectSection from 'Store/connectSection'; import { fetchEpisodes, setEpisodesSort, clearEpisodes } from 'Store/Actions/episodeActions'; import { updateInteractiveImportItem } from 'Store/Actions/interactiveImportActions'; import createClientSideCollectionSelector from 'Store/Selectors/createClientSideCollectionSelector'; -import SelectEpisodeModalContent from './SelectEpisodeModalContent'; +import SelectTrackModalContent from './SelectTrackModalContent'; function createMapStateToProps() { return createSelector( @@ -24,7 +24,7 @@ const mapDispatchToProps = { updateInteractiveImportItem }; -class SelectEpisodeModalContentConnector extends Component { +class SelectTrackModalContentConnector extends Component { // // Lifecycle @@ -32,10 +32,10 @@ class SelectEpisodeModalContentConnector extends Component { componentDidMount() { const { artistId, - seasonNumber + albumId } = this.props; - this.props.fetchEpisodes({ artistId, seasonNumber }); + this.props.fetchEpisodes({ artistId, albumId }); } componentWillUnmount() { @@ -51,8 +51,8 @@ class SelectEpisodeModalContentConnector extends Component { this.props.setEpisodesSort({ sortKey, sortDirection }); } - onEpisodesSelect = (episodeIds) => { - const episodes = _.reduce(this.props.items, (acc, item) => { + onTracksSelect = (episodeIds) => { + const tracks = _.reduce(this.props.items, (acc, item) => { if (episodeIds.indexOf(item.id) > -1) { acc.push(item); } @@ -62,7 +62,7 @@ class SelectEpisodeModalContentConnector extends Component { this.props.updateInteractiveImportItem({ id: this.props.id, - episodes: _.sortBy(episodes, 'episodeNumber') + tracks: _.sortBy(tracks, 'trackNumber') }); this.props.onModalClose(true); @@ -73,19 +73,19 @@ class SelectEpisodeModalContentConnector extends Component { render() { return ( - ); } } -SelectEpisodeModalContentConnector.propTypes = { +SelectTrackModalContentConnector.propTypes = { id: PropTypes.number.isRequired, artistId: PropTypes.number.isRequired, - seasonNumber: PropTypes.number.isRequired, + albumId: PropTypes.number.isRequired, items: PropTypes.arrayOf(PropTypes.object).isRequired, fetchEpisodes: PropTypes.func.isRequired, setEpisodesSort: PropTypes.func.isRequired, @@ -100,4 +100,4 @@ export default connectSection( undefined, undefined, { section: 'episodes' } - )(SelectEpisodeModalContentConnector); + )(SelectTrackModalContentConnector); diff --git a/frontend/src/InteractiveImport/Episode/SelectEpisodeRow.js b/frontend/src/InteractiveImport/Track/SelectTrackRow.js similarity index 76% rename from frontend/src/InteractiveImport/Episode/SelectEpisodeRow.js rename to frontend/src/InteractiveImport/Track/SelectTrackRow.js index ba455121a..fda48c917 100644 --- a/frontend/src/InteractiveImport/Episode/SelectEpisodeRow.js +++ b/frontend/src/InteractiveImport/Track/SelectTrackRow.js @@ -4,7 +4,7 @@ import TableRowButton from 'Components/Table/TableRowButton'; import TableRowCell from 'Components/Table/Cells/TableRowCell'; import TableSelectCell from 'Components/Table/Cells/TableSelectCell'; -class SelectEpisodeRow extends Component { +class SelectTrackRow extends Component { // // Listeners @@ -24,9 +24,8 @@ class SelectEpisodeRow extends Component { render() { const { id, - episodeNumber, + trackNumber, title, - airDate, isSelected, onSelectedChange } = this.props; @@ -40,28 +39,24 @@ class SelectEpisodeRow extends Component { /> - {episodeNumber} + {trackNumber} {title} - - {airDate} - ); } } -SelectEpisodeRow.propTypes = { +SelectTrackRow.propTypes = { id: PropTypes.number.isRequired, - episodeNumber: PropTypes.number.isRequired, + trackNumber: PropTypes.number.isRequired, title: PropTypes.string.isRequired, - airDate: PropTypes.string.isRequired, isSelected: PropTypes.bool, onSelectedChange: PropTypes.func.isRequired }; -export default SelectEpisodeRow; +export default SelectTrackRow; diff --git a/frontend/src/Store/Actions/episodeActionHandlers.js b/frontend/src/Store/Actions/episodeActionHandlers.js index 2d2c3ed65..1fa8034bd 100644 --- a/frontend/src/Store/Actions/episodeActionHandlers.js +++ b/frontend/src/Store/Actions/episodeActionHandlers.js @@ -9,7 +9,7 @@ import { updateItem } from './baseActions'; const section = 'episodes'; const episodeActionHandlers = { - [types.FETCH_EPISODES]: createFetchHandler(section, '/episode'), + [types.FETCH_EPISODES]: createFetchHandler(section, '/track'), [types.TOGGLE_EPISODE_MONITORED]: function(payload) { return function(dispatch, getState) { diff --git a/frontend/src/Store/Actions/episodeFileActionHandlers.js b/frontend/src/Store/Actions/episodeFileActionHandlers.js index a0a661f03..086c4c61d 100644 --- a/frontend/src/Store/Actions/episodeFileActionHandlers.js +++ b/frontend/src/Store/Actions/episodeFileActionHandlers.js @@ -8,10 +8,10 @@ import * as types from './actionTypes'; import { set, removeItem, updateItem } from './baseActions'; const section = 'episodeFiles'; -const deleteEpisodeFile = createRemoveItemHandler(section, '/episodeFile'); +const deleteEpisodeFile = createRemoveItemHandler(section, '/trackFile'); const episodeFileActionHandlers = { - [types.FETCH_EPISODE_FILES]: createFetchHandler(section, '/episodeFile'), + [types.FETCH_EPISODE_FILES]: createFetchHandler(section, '/trackFile'), [types.DELETE_EPISODE_FILE]: function(payload) { return function(dispatch, getState) { diff --git a/frontend/src/Store/Middleware/middlewares.js b/frontend/src/Store/Middleware/middlewares.js index 0367e06e1..e4901c5ee 100644 --- a/frontend/src/Store/Middleware/middlewares.js +++ b/frontend/src/Store/Middleware/middlewares.js @@ -13,8 +13,8 @@ export default function(history) { isProduction } = window.Sonarr; - const dsn = isProduction ? 'https://b80ca60625b443c38b242e0d21681eb7@sentry.sonarr.tv/13' : - 'https://8dbaacdfe2ff4caf97dc7945aecf9ace@sentry.sonarr.tv/12'; + const dsn = isProduction ? 'https://c3a5b33e08de4e18b7d0505e942dbc95:e35e6d535b034995a6896022c6bfed04@sentry.io/216290' : + 'https://c3a5b33e08de4e18b7d0505e942dbc95:e35e6d535b034995a6896022c6bfed04@sentry.io/216290'; const middlewares = []; diff --git a/src/Lidarr.Api.V3/Artist/ArtistResource.cs b/src/Lidarr.Api.V3/Artist/ArtistResource.cs index 19e79c297..ef819e8ad 100644 --- a/src/Lidarr.Api.V3/Artist/ArtistResource.cs +++ b/src/Lidarr.Api.V3/Artist/ArtistResource.cs @@ -60,11 +60,6 @@ namespace Lidarr.Api.V3.Artist //public bool SeasonFolder { get; set; } //public bool Monitored { get; set; } - //public bool UseSceneNumbering { get; set; } - //public int Runtime { get; set; } - //public int TvdbId { get; set; } - //public int TvRageId { get; set; } - //public int TvMazeId { get; set; } //public DateTime? FirstAired { get; set; } public DateTime? LastInfoSync { get; set; } ////public SeriesTypes SeriesType { get; set; } @@ -138,10 +133,6 @@ namespace Lidarr.Api.V3.Artist //AlternateTitles SortName = model.SortName, - //TotalEpisodeCount - //EpisodeCount - //EpisodeFileCount - //SizeOnDisk Status = model.Status, Overview = model.Overview, //NextAiring @@ -160,12 +151,6 @@ namespace Lidarr.Api.V3.Artist AlbumFolder = model.AlbumFolder, Monitored = model.Monitored, - //UseSceneNumbering = model.UseSceneNumbering, - //Runtime = model.Runtime, - //TvdbId = model.TvdbId, - //TvRageId = model.TvRageId, - //TvMazeId = model.TvMazeId, - //FirstAired = model.FirstAired, LastInfoSync = model.LastInfoSync, //SeriesType = model.SeriesType, CleanName = model.CleanName, @@ -193,10 +178,6 @@ namespace Lidarr.Api.V3.Artist //AlternateTitles SortName = resource.SortName, - //TotalEpisodeCount - //EpisodeCount - //EpisodeFileCount - //SizeOnDisk Status = resource.Status, Overview = resource.Overview, //NextAiring @@ -215,12 +196,6 @@ namespace Lidarr.Api.V3.Artist AlbumFolder = resource.AlbumFolder, Monitored = resource.Monitored, - //UseSceneNumbering = resource.UseSceneNumbering, - //Runtime = resource.Runtime, - //TvdbId = resource.TvdbId, - //TvRageId = resource.TvRageId, - //TvMazeId = resource.TvMazeId, - //FirstAired = resource.FirstAired, LastInfoSync = resource.LastInfoSync, //SeriesType = resource.SeriesType, CleanName = resource.CleanName, diff --git a/src/Lidarr.Api.V3/ManualImport/ManualImportResource.cs b/src/Lidarr.Api.V3/ManualImport/ManualImportResource.cs index c27d6e7f6..75b63e606 100644 --- a/src/Lidarr.Api.V3/ManualImport/ManualImportResource.cs +++ b/src/Lidarr.Api.V3/ManualImport/ManualImportResource.cs @@ -3,6 +3,7 @@ using NzbDrone.Core.DecisionEngine; using NzbDrone.Core.MediaFiles.TrackImport.Manual; using NzbDrone.Core.Qualities; using Lidarr.Api.V3.Artist; +using Lidarr.Api.V3.Albums; using Lidarr.Api.V3.Tracks; using Lidarr.Http.REST; using System.Collections.Generic; @@ -16,9 +17,9 @@ namespace Lidarr.Api.V3.ManualImport public string RelativePath { get; set; } public string Name { get; set; } public long Size { get; set; } - //public ArtistResource Artist { get; set; } - public int? SeasonNumber { get; set; } - public List Episodes { get; set; } + public ArtistResource Artist { get; set; } + public AlbumResource Album { get; set; } + public List Tracks { get; set; } public QualityModel Quality { get; set; } public int QualityWeight { get; set; } public string DownloadId { get; set; } @@ -38,9 +39,9 @@ namespace Lidarr.Api.V3.ManualImport RelativePath = model.RelativePath, Name = model.Name, Size = model.Size, - //Artist = model., - SeasonNumber = model.SeasonNumber, - //Episodes = model.Episodes.ToResource(), + Artist = model.Artist.ToResource(), + Album = model.Album.ToResource(), + Tracks = model.Tracks.ToResource(), Quality = model.Quality, //QualityWeight DownloadId = model.DownloadId, diff --git a/src/NzbDrone.Api/ManualImport/ManualImportResource.cs b/src/NzbDrone.Api/ManualImport/ManualImportResource.cs index e1844d718..7a7dad567 100644 --- a/src/NzbDrone.Api/ManualImport/ManualImportResource.cs +++ b/src/NzbDrone.Api/ManualImport/ManualImportResource.cs @@ -15,9 +15,6 @@ namespace NzbDrone.Api.ManualImport public string RelativePath { get; set; } public string Name { get; set; } public long Size { get; set; } - public SeriesResource Series { get; set; } - public int? SeasonNumber { get; set; } - public List Episodes { get; set; } public QualityModel Quality { get; set; } public int QualityWeight { get; set; } public string DownloadId { get; set; } @@ -38,9 +35,6 @@ namespace NzbDrone.Api.ManualImport RelativePath = model.RelativePath, Name = model.Name, Size = model.Size, - Series = model.Series.ToResource(), - SeasonNumber = model.SeasonNumber, - Episodes = model.Episodes.ToResource(), Quality = model.Quality, //QualityWeight DownloadId = model.DownloadId, diff --git a/src/NzbDrone.Core/MediaFiles/TrackImport/ImportApprovedTracks.cs b/src/NzbDrone.Core/MediaFiles/TrackImport/ImportApprovedTracks.cs index 71024a420..0578ede09 100644 --- a/src/NzbDrone.Core/MediaFiles/TrackImport/ImportApprovedTracks.cs +++ b/src/NzbDrone.Core/MediaFiles/TrackImport/ImportApprovedTracks.cs @@ -84,7 +84,7 @@ namespace NzbDrone.Core.MediaFiles.TrackImport trackFile.Size = _diskProvider.GetFileSize(localTrack.Path); trackFile.Quality = localTrack.Quality; trackFile.MediaInfo = localTrack.MediaInfo; - trackFile.AlbumId = _albumRepository.FindByArtistAndName(localTrack.Artist.Name, Parser.Parser.CleanArtistTitle(localTrack.ParsedTrackInfo.AlbumTitle)).Id; + trackFile.AlbumId = localTrack.Album.Id; trackFile.ReleaseGroup = localTrack.ParsedTrackInfo.ReleaseGroup; trackFile.Tracks = localTrack.Tracks; trackFile.Language = localTrack.Language; diff --git a/src/NzbDrone.Core/MediaFiles/TrackImport/Manual/ManualImportFile.cs b/src/NzbDrone.Core/MediaFiles/TrackImport/Manual/ManualImportFile.cs index 44eb491c3..2db3f8ec1 100644 --- a/src/NzbDrone.Core/MediaFiles/TrackImport/Manual/ManualImportFile.cs +++ b/src/NzbDrone.Core/MediaFiles/TrackImport/Manual/ManualImportFile.cs @@ -6,8 +6,9 @@ namespace NzbDrone.Core.MediaFiles.TrackImport.Manual public class ManualImportFile { public string Path { get; set; } - public int SeriesId { get; set; } - public List EpisodeIds { get; set; } + public int ArtistId { get; set; } + public int AlbumId { get; set; } + public List TrackIds { get; set; } public QualityModel Quality { get; set; } public string DownloadId { get; set; } } diff --git a/src/NzbDrone.Core/MediaFiles/TrackImport/Manual/ManualImportItem.cs b/src/NzbDrone.Core/MediaFiles/TrackImport/Manual/ManualImportItem.cs index 9d335f258..4e52ee645 100644 --- a/src/NzbDrone.Core/MediaFiles/TrackImport/Manual/ManualImportItem.cs +++ b/src/NzbDrone.Core/MediaFiles/TrackImport/Manual/ManualImportItem.cs @@ -1,7 +1,7 @@ using System.Collections.Generic; using NzbDrone.Core.DecisionEngine; using NzbDrone.Core.Qualities; -using NzbDrone.Core.Tv; +using NzbDrone.Core.Music; namespace NzbDrone.Core.MediaFiles.TrackImport.Manual { @@ -11,9 +11,9 @@ namespace NzbDrone.Core.MediaFiles.TrackImport.Manual public string RelativePath { get; set; } public string Name { get; set; } public long Size { get; set; } - public Series Series { get; set; } - public int? SeasonNumber { get; set; } - public List Episodes { get; set; } + public Artist Artist { get; set; } + public Album Album { get; set; } + public List Tracks { get; set; } public QualityModel Quality { get; set; } public string DownloadId { get; set; } public IEnumerable Rejections { get; set; } diff --git a/src/NzbDrone.Core/MediaFiles/TrackImport/Manual/ManualImportService.cs b/src/NzbDrone.Core/MediaFiles/TrackImport/Manual/ManualImportService.cs index b259d7745..9c194abd2 100644 --- a/src/NzbDrone.Core/MediaFiles/TrackImport/Manual/ManualImportService.cs +++ b/src/NzbDrone.Core/MediaFiles/TrackImport/Manual/ManualImportService.cs @@ -14,7 +14,7 @@ using NzbDrone.Core.Messaging.Commands; using NzbDrone.Core.Messaging.Events; using NzbDrone.Core.Parser; using NzbDrone.Core.Parser.Model; -using NzbDrone.Core.Tv; +using NzbDrone.Core.Music; namespace NzbDrone.Core.MediaFiles.TrackImport.Manual { @@ -29,10 +29,11 @@ namespace NzbDrone.Core.MediaFiles.TrackImport.Manual private readonly IParsingService _parsingService; private readonly IDiskScanService _diskScanService; private readonly IMakeImportDecision _importDecisionMaker; - private readonly ISeriesService _seriesService; - private readonly IEpisodeService _episodeService; + private readonly IArtistService _artistService; + private readonly IAlbumService _albumService; + private readonly ITrackService _trackService; private readonly IVideoFileInfoReader _videoFileInfoReader; - private readonly IImportApprovedEpisodes _importApprovedEpisodes; + private readonly IImportApprovedTracks _importApprovedTracks; private readonly ITrackedDownloadService _trackedDownloadService; private readonly IDownloadedEpisodesImportService _downloadedEpisodesImportService; private readonly IEventAggregator _eventAggregator; @@ -42,10 +43,11 @@ namespace NzbDrone.Core.MediaFiles.TrackImport.Manual IParsingService parsingService, IDiskScanService diskScanService, IMakeImportDecision importDecisionMaker, - ISeriesService seriesService, - IEpisodeService episodeService, + IArtistService artistService, + IAlbumService albumService, + ITrackService trackService, IVideoFileInfoReader videoFileInfoReader, - IImportApprovedEpisodes importApprovedEpisodes, + IImportApprovedTracks importApprovedTracks, ITrackedDownloadService trackedDownloadService, IDownloadedEpisodesImportService downloadedEpisodesImportService, IEventAggregator eventAggregator, @@ -55,10 +57,11 @@ namespace NzbDrone.Core.MediaFiles.TrackImport.Manual _parsingService = parsingService; _diskScanService = diskScanService; _importDecisionMaker = importDecisionMaker; - _seriesService = seriesService; - _episodeService = episodeService; + _artistService = artistService; + _albumService = albumService; + _trackService = trackService; _videoFileInfoReader = videoFileInfoReader; - _importApprovedEpisodes = importApprovedEpisodes; + _importApprovedTracks = importApprovedTracks; _trackedDownloadService = trackedDownloadService; _downloadedEpisodesImportService = downloadedEpisodesImportService; _eventAggregator = eventAggregator; @@ -94,179 +97,176 @@ namespace NzbDrone.Core.MediaFiles.TrackImport.Manual private List ProcessFolder(string folder, string downloadId) { - throw new System.NotImplementedException("TODO: This will be rewritten for Music"); - //var directoryInfo = new DirectoryInfo(folder); - //var series = _parsingService.GetSeries(directoryInfo.Name); + var directoryInfo = new DirectoryInfo(folder); + var artist = _parsingService.GetArtist(directoryInfo.Name); - //if (series == null && downloadId.IsNotNullOrWhiteSpace()) - //{ - // var trackedDownload = _trackedDownloadService.Find(downloadId); - // series = trackedDownload.RemoteEpisode.Series; - //} + if (artist == null && downloadId.IsNotNullOrWhiteSpace()) + { + var trackedDownload = _trackedDownloadService.Find(downloadId); + artist = trackedDownload.RemoteAlbum.Artist; + } - //if (series == null) - //{ - // var files = _diskScanService.GetVideoFiles(folder); + if (artist == null) + { + var files = _diskScanService.GetAudioFiles(folder); - // return files.Select(file => ProcessFile(file, downloadId, folder)).Where(i => i != null).ToList(); - //} + return files.Select(file => ProcessFile(file, downloadId, folder)).Where(i => i != null).ToList(); + } - //var folderInfo = Parser.Parser.ParseTitle(directoryInfo.Name); - //var seriesFiles = _diskScanService.GetVideoFiles(folder).ToList(); - //var decisions = _importDecisionMaker.GetImportDecisions(seriesFiles, series, folderInfo, SceneSource(series, folder)); + var folderInfo = Parser.Parser.ParseMusicTitle(directoryInfo.Name); + var artistFiles = _diskScanService.GetAudioFiles(folder).ToList(); + var decisions = _importDecisionMaker.GetImportDecisions(artistFiles, artist, folderInfo); - //return decisions.Select(decision => MapItem(decision, folder, downloadId)).ToList(); + return decisions.Select(decision => MapItem(decision, folder, downloadId)).ToList(); } private ManualImportItem ProcessFile(string file, string downloadId, string folder = null) { - throw new System.NotImplementedException("TODO: This will be rewritten for Music"); - //if (folder.IsNullOrWhiteSpace()) - //{ - // folder = new FileInfo(file).Directory.FullName; - //} + if (folder.IsNullOrWhiteSpace()) + { + folder = new FileInfo(file).Directory.FullName; + } - //var relativeFile = folder.GetRelativePath(file); + var relativeFile = folder.GetRelativePath(file); - //var series = _parsingService.GetSeries(relativeFile.Split('\\', '/')[0]); + var artist = _parsingService.GetArtist(relativeFile.Split('\\', '/')[0]); - //if (series == null) - //{ - // series = _parsingService.GetSeries(relativeFile); - //} + if (artist == null) + { + artist = _parsingService.GetArtistFromTag(file); + } - //if (series == null && downloadId.IsNotNullOrWhiteSpace()) - //{ - // var trackedDownload = _trackedDownloadService.Find(downloadId); - // series = trackedDownload.RemoteEpisode.Series; - //} + if (artist == null && downloadId.IsNotNullOrWhiteSpace()) + { + var trackedDownload = _trackedDownloadService.Find(downloadId); + artist = trackedDownload.RemoteAlbum.Artist; + } - //if (series == null) - //{ - // var localEpisode = new LocalEpisode(); - // localEpisode.Path = file; - // localEpisode.Quality = QualityParser.ParseQuality(file); - // localEpisode.Size = _diskProvider.GetFileSize(file); + if (artist == null) + { + var localTrack = new LocalTrack(); + localTrack.Path = file; + localTrack.Quality = QualityParser.ParseQuality(file); + localTrack.Size = _diskProvider.GetFileSize(file); - // return MapItem(new ImportDecision(localEpisode, new Rejection("Unknown Series")), folder, downloadId); - //} + return MapItem(new ImportDecision(localTrack, new Rejection("Unknown Artist")), folder, downloadId); + } - //var importDecisions = _importDecisionMaker.GetImportDecisions(new List {file}, - // series, null, SceneSource(series, folder)); + var importDecisions = _importDecisionMaker.GetImportDecisions(new List { file }, + artist, null); - //return importDecisions.Any() ? MapItem(importDecisions.First(), folder, downloadId) : null; + return importDecisions.Any() ? MapItem(importDecisions.First(), folder, downloadId) : null; } - private bool SceneSource(Series series, string folder) + private bool SceneSource(Artist artist, string folder) { - return !(series.Path.PathEquals(folder) || series.Path.IsParentPath(folder)); + return !(artist.Path.PathEquals(folder) || artist.Path.IsParentPath(folder)); } private ManualImportItem MapItem(ImportDecision decision, string folder, string downloadId) { - throw new System.NotImplementedException("TODO: This will be rewritten for Music"); - //var item = new ManualImportItem(); - - //item.Path = decision.LocalEpisode.Path; - //item.RelativePath = folder.GetRelativePath(decision.LocalEpisode.Path); - //item.Name = Path.GetFileNameWithoutExtension(decision.LocalEpisode.Path); - //item.DownloadId = downloadId; - - //if (decision.LocalEpisode.Series != null) - //{ - // item.Series = decision.LocalEpisode.Series; - //} - - //if (decision.LocalEpisode.Episodes.Any()) - //{ - // item.SeasonNumber = decision.LocalEpisode.SeasonNumber; - // item.Episodes = decision.LocalEpisode.Episodes; - //} - - //item.Quality = decision.LocalEpisode.Quality; - //item.Size = _diskProvider.GetFileSize(decision.LocalEpisode.Path); - //item.Rejections = decision.Rejections; - - //return item; + var item = new ManualImportItem(); + + item.Path = decision.LocalTrack.Path; + item.RelativePath = folder.GetRelativePath(decision.LocalTrack.Path); + item.Name = Path.GetFileNameWithoutExtension(decision.LocalTrack.Path); + item.DownloadId = downloadId; + + if (decision.LocalTrack.Artist != null) + { + item.Artist = decision.LocalTrack.Artist; + } + + if (decision.LocalTrack.Tracks.Any()) + { + item.Tracks = decision.LocalTrack.Tracks; + } + + item.Quality = decision.LocalTrack.Quality; + item.Size = _diskProvider.GetFileSize(decision.LocalTrack.Path); + item.Rejections = decision.Rejections; + + return item; } public void Execute(ManualImportCommand message) { _logger.ProgressTrace("Manually importing {0} files using mode {1}", message.Files.Count, message.ImportMode); - throw new System.NotImplementedException("TODO: This will be rewritten for Music"); - - //var imported = new List(); - //var importedTrackedDownload = new List(); - - //for (int i = 0; i < message.Files.Count; i++) - //{ - // _logger.ProgressTrace("Processing file {0} of {1}", i + 1, message.Files.Count); - - // var file = message.Files[i]; - // var series = _seriesService.GetSeries(file.SeriesId); - // var episodes = _episodeService.GetEpisodes(file.EpisodeIds); - // var parsedEpisodeInfo = Parser.Parser.ParsePath(file.Path) ?? new ParsedEpisodeInfo(); - // var mediaInfo = _videoFileInfoReader.GetMediaInfo(file.Path); - // var existingFile = series.Path.IsParentPath(file.Path); - - // var localEpisode = new LocalEpisode - // { - // ExistingFile = false, - // Episodes = episodes, - // MediaInfo = mediaInfo, - // ParsedEpisodeInfo = parsedEpisodeInfo, - // Path = file.Path, - // Quality = file.Quality, - // Series = series, - // Size = 0 - // }; - - // //TODO: Cleanup non-tracked downloads - - // var importDecision = new ImportDecision(localEpisode); - - // if (file.DownloadId.IsNullOrWhiteSpace()) - // { - // imported.AddRange(_importApprovedEpisodes.Import(new List { importDecision }, !existingFile, null, message.ImportMode)); - // } - - // else - // { - // var trackedDownload = _trackedDownloadService.Find(file.DownloadId); - // var importResult = _importApprovedEpisodes.Import(new List { importDecision }, true, trackedDownload.DownloadItem, message.ImportMode).First(); - - // imported.Add(importResult); - - // importedTrackedDownload.Add(new ManuallyImportedFile - // { - // TrackedDownload = trackedDownload, - // ImportResult = importResult - // }); - // } - //} - - //_logger.ProgressTrace("Manually imported {0} files", imported.Count); - - //foreach (var groupedTrackedDownload in importedTrackedDownload.GroupBy(i => i.TrackedDownload.DownloadItem.DownloadId).ToList()) - //{ - // var trackedDownload = groupedTrackedDownload.First().TrackedDownload; - - // if (_diskProvider.FolderExists(trackedDownload.DownloadItem.OutputPath.FullPath)) - // { - // if (_downloadedEpisodesImportService.ShouldDeleteFolder( - // new DirectoryInfo(trackedDownload.DownloadItem.OutputPath.FullPath), - // trackedDownload.RemoteEpisode.Series) && !trackedDownload.DownloadItem.IsReadOnly) - // { - // _diskProvider.DeleteFolder(trackedDownload.DownloadItem.OutputPath.FullPath, true); - // } - // } - - // if (groupedTrackedDownload.Select(c => c.ImportResult).Count(c => c.Result == ImportResultType.Imported) >= Math.Max(1, trackedDownload.RemoteEpisode.Episodes.Count)) - // { - // trackedDownload.State = TrackedDownloadStage.Imported; - // _eventAggregator.PublishEvent(new DownloadCompletedEvent(trackedDownload)); - // } - //} + + var imported = new List(); + var importedTrackedDownload = new List(); + + for (int i = 0; i < message.Files.Count; i++) + { + _logger.ProgressTrace("Processing file {0} of {1}", i + 1, message.Files.Count); + + var file = message.Files[i]; + var artist = _artistService.GetArtist(file.ArtistId); + var album = _albumService.GetAlbum(file.AlbumId); + var tracks = _trackService.GetTracks(file.TrackIds); + var parsedTrackInfo = Parser.Parser.ParseMusicPath(file.Path) ?? new ParsedTrackInfo(); + var mediaInfo = _videoFileInfoReader.GetMediaInfo(file.Path); + var existingFile = artist.Path.IsParentPath(file.Path); + + var localTrack = new LocalTrack + { + ExistingFile = false, + Tracks = tracks, + MediaInfo = mediaInfo, + ParsedTrackInfo = parsedTrackInfo, + Path = file.Path, + Quality = file.Quality, + Artist = artist, + Album = album, + Size = 0 + }; + + //TODO: Cleanup non-tracked downloads + + var importDecision = new ImportDecision(localTrack); + + if (file.DownloadId.IsNullOrWhiteSpace()) + { + imported.AddRange(_importApprovedTracks.Import(new List { importDecision }, !existingFile, null, message.ImportMode)); + } + + else + { + var trackedDownload = _trackedDownloadService.Find(file.DownloadId); + var importResult = _importApprovedTracks.Import(new List { importDecision }, true, trackedDownload.DownloadItem, message.ImportMode).First(); + + imported.Add(importResult); + + importedTrackedDownload.Add(new ManuallyImportedFile + { + TrackedDownload = trackedDownload, + ImportResult = importResult + }); + } + } + + _logger.ProgressTrace("Manually imported {0} files", imported.Count); + + foreach (var groupedTrackedDownload in importedTrackedDownload.GroupBy(i => i.TrackedDownload.DownloadItem.DownloadId).ToList()) + { + var trackedDownload = groupedTrackedDownload.First().TrackedDownload; + + if (_diskProvider.FolderExists(trackedDownload.DownloadItem.OutputPath.FullPath)) + { + if (_downloadedEpisodesImportService.ShouldDeleteFolder( + new DirectoryInfo(trackedDownload.DownloadItem.OutputPath.FullPath), + trackedDownload.RemoteEpisode.Series) && !trackedDownload.DownloadItem.IsReadOnly) + { + _diskProvider.DeleteFolder(trackedDownload.DownloadItem.OutputPath.FullPath, true); + } + } + + if (groupedTrackedDownload.Select(c => c.ImportResult).Count(c => c.Result == ImportResultType.Imported) >= Math.Max(1, trackedDownload.RemoteAlbum.Albums.Count)) + { + trackedDownload.State = TrackedDownloadStage.Imported; + _eventAggregator.PublishEvent(new DownloadCompletedEvent(trackedDownload)); + } + } } } } diff --git a/src/NzbDrone.Core/Parser/Model/ParsedTrackInfo.cs b/src/NzbDrone.Core/Parser/Model/ParsedTrackInfo.cs index 4c1989ccd..cdf1593ce 100644 --- a/src/NzbDrone.Core/Parser/Model/ParsedTrackInfo.cs +++ b/src/NzbDrone.Core/Parser/Model/ParsedTrackInfo.cs @@ -15,6 +15,9 @@ namespace NzbDrone.Core.Parser.Model public string ArtistTitle { get; set; } public string AlbumTitle { get; set; } public ArtistTitleInfo ArtistTitleInfo { get; set; } + public string ArtistMBId { get; set; } + public string AlbumMBId { get; set; } + public string TrackMBId { get; set; } public QualityModel Quality { get; set; } public int[] TrackNumbers { get; set; } public Language Language { get; set; } @@ -26,21 +29,18 @@ namespace NzbDrone.Core.Parser.Model TrackNumbers = new int[0]; } - - - public override string ToString() { - string episodeString = "[Unknown Track]"; + string trackString = "[Unknown Track]"; if (TrackNumbers != null && TrackNumbers.Any()) { - episodeString = string.Format("T{0}", string.Join("-", TrackNumbers.Select(c => c.ToString("00")))); + trackString = string.Format("T{0}", string.Join("-", TrackNumbers.Select(c => c.ToString("00")))); } - return string.Format("{0} - {1} {2}", ArtistTitle, episodeString, Quality); + return string.Format("{0} - {1} {2}", ArtistTitle, trackString, Quality); } } } diff --git a/src/NzbDrone.Core/Parser/Parser.cs b/src/NzbDrone.Core/Parser/Parser.cs index bfdcd9146..39c386867 100644 --- a/src/NzbDrone.Core/Parser/Parser.cs +++ b/src/NzbDrone.Core/Parser/Parser.cs @@ -336,9 +336,16 @@ namespace NzbDrone.Core.Parser var trackName = file.Tag.Title; var trackNumber = file.Tag.Track; + var artist = file.Tag.FirstAlbumArtist; + + if (artist.IsNotNullOrWhiteSpace()) + { + artist = file.Tag.FirstPerformer; + } + var artistTitleInfo = new ArtistTitleInfo { - Title = file.Tag.Title, + Title = artist, Year = (int)file.Tag.Year }; @@ -348,7 +355,10 @@ namespace NzbDrone.Core.Parser { Language = Language.English, //TODO Parse from Tag/Mediainfo AlbumTitle = file.Tag.Album, - ArtistTitle = file.Tag.FirstAlbumArtist, + ArtistTitle = artist, + ArtistMBId = file.Tag.MusicBrainzArtistId, + AlbumMBId = file.Tag.MusicBrainzReleaseId, + TrackMBId = file.Tag.MusicBrainzReleaseType, Quality = QualityParser.ParseQuality(trackName), TrackNumbers = temp, ArtistTitleInfo = artistTitleInfo, diff --git a/src/NzbDrone.Core/Parser/ParsingService.cs b/src/NzbDrone.Core/Parser/ParsingService.cs index 5926b6d5e..8061c4c3b 100644 --- a/src/NzbDrone.Core/Parser/ParsingService.cs +++ b/src/NzbDrone.Core/Parser/ParsingService.cs @@ -18,6 +18,8 @@ namespace NzbDrone.Core.Parser LocalEpisode GetLocalEpisode(string filename, Series series); LocalEpisode GetLocalEpisode(string filename, Series series, ParsedEpisodeInfo folderInfo, bool sceneSource); Series GetSeries(string title); + Artist GetArtist(string title); + Artist GetArtistFromTag(string file); RemoteEpisode Map(ParsedEpisodeInfo parsedEpisodeInfo, int tvdbId, int tvRageId, SearchCriteriaBase searchCriteria = null); RemoteEpisode Map(ParsedEpisodeInfo parsedEpisodeInfo, int seriesId, IEnumerable episodeIds); RemoteAlbum Map(ParsedAlbumInfo parsedAlbumInfo, SearchCriteriaBase searchCriteria = null); @@ -137,6 +139,44 @@ namespace NzbDrone.Core.Parser return series; } + public Artist GetArtist(string title) + { + var parsedAlbumInfo = Parser.ParseAlbumTitle(title); + + if (parsedAlbumInfo == null || parsedAlbumInfo.ArtistName.IsNullOrWhiteSpace()) + { + return _artistService.FindByName(title); + } + + return _artistService.FindByName(parsedAlbumInfo.ArtistName); + + } + + public Artist GetArtistFromTag(string file) + { + var parsedTrackInfo = Parser.ParseMusicPath(file); + + var artist = new Artist(); + + if (parsedTrackInfo.ArtistMBId.IsNotNullOrWhiteSpace()) + { + artist = _artistService.FindById(parsedTrackInfo.ArtistMBId); + + if (artist != null) + { + return artist; + } + } + + if (parsedTrackInfo == null || parsedTrackInfo.ArtistTitle.IsNullOrWhiteSpace()) + { + return null; + } + + return _artistService.FindByName(parsedTrackInfo.ArtistTitle); + + } + [System.Obsolete("Used for sonarr, not lidarr")] public RemoteEpisode Map(ParsedEpisodeInfo parsedEpisodeInfo, int tvdbId, int tvRageId, SearchCriteriaBase searchCriteria = null) { @@ -670,10 +710,12 @@ namespace NzbDrone.Core.Parser } var tracks = GetTracks(parsedTrackInfo, artist); + var album = _albumService.FindByTitle(artist.Id, parsedTrackInfo.AlbumTitle); return new LocalTrack { Artist = artist, + Album = album, Quality = parsedTrackInfo.Quality, Language = parsedTrackInfo.Language, Tracks = tracks,