[UI Work] Interactive Import, More Artist Detail

pull/6/head
Qstick 7 years ago
parent 0054226307
commit f05332cf6e

@ -71,7 +71,7 @@ class ImportArtistRowConnector extends Component {
<ImportArtistRow <ImportArtistRow
{...this.props} {...this.props}
onInputChange={this.onInputChange} onInputChange={this.onInputChange}
onSeriesSelect={this.onSeriesSelect} onArtistSelect={this.onArtistSelect}
/> />
); );
} }

@ -99,10 +99,10 @@ class ImportArtistSelectArtist extends Component {
}); });
} }
onSeriesSelect = (foreignArtistId) => { onArtistSelect = (foreignArtistId) => {
this.setState({ isOpen: false }); this.setState({ isOpen: false });
this.props.onSeriesSelect(foreignArtistId); this.props.onArtistSelect(foreignArtistId);
} }
// //
@ -117,7 +117,7 @@ class ImportArtistSelectArtist extends Component {
error, error,
items, items,
queued, queued,
onSeriesSelect onArtistSelect
} = this.props; } = this.props;
const errorMessage = error && const errorMessage = error &&
@ -233,7 +233,7 @@ class ImportArtistSelectArtist extends Component {
overview={item.overview} overview={item.overview}
// year={item.year} // year={item.year}
// network={item.network} // network={item.network}
onPress={this.onSeriesSelect} onPress={this.onArtistSelect}
/> />
); );
}) })
@ -257,7 +257,7 @@ ImportArtistSelectArtist.propTypes = {
items: PropTypes.arrayOf(PropTypes.object).isRequired, items: PropTypes.arrayOf(PropTypes.object).isRequired,
queued: PropTypes.bool.isRequired, queued: PropTypes.bool.isRequired,
onSearchInputChange: PropTypes.func.isRequired, onSearchInputChange: PropTypes.func.isRequired,
onSeriesSelect: PropTypes.func.isRequired onArtistSelect: PropTypes.func.isRequired
}; };
ImportArtistSelectArtist.defaultProps = { ImportArtistSelectArtist.defaultProps = {

@ -33,7 +33,7 @@ class ImportArtistSelectArtistConnector extends Component {
}); });
} }
onSeriesSelect = (foreignArtistId) => { onArtistSelect = (foreignArtistId) => {
const { const {
id, id,
items items
@ -53,7 +53,7 @@ class ImportArtistSelectArtistConnector extends Component {
<ImportArtistSelectArtist <ImportArtistSelectArtist
{...this.props} {...this.props}
onSearchInputChange={this.onSearchInputChange} onSearchInputChange={this.onSearchInputChange}
onSeriesSelect={this.onSeriesSelect} onArtistSelect={this.onArtistSelect}
/> />
); );
} }

@ -45,7 +45,7 @@ function App({ store, history }) {
<PageConnector> <PageConnector>
<Switch> <Switch>
{/* {/*
Series Artist
*/} */}
<Route <Route

@ -112,7 +112,7 @@ class SeriesDetails extends Component {
this.setState(getExpandedState(selectAll(expandedState, !allExpanded))); this.setState(getExpandedState(selectAll(expandedState, !allExpanded)));
} }
onExpandPress = (seasonNumber, isExpanded) => { onExpandPress = (albumId, isExpanded) => {
this.setState((state) => { this.setState((state) => {
const convertedState = { const convertedState = {
allSelected: state.allExpanded, allSelected: state.allExpanded,
@ -120,7 +120,7 @@ class SeriesDetails extends Component {
selectedState: state.expandedState selectedState: state.expandedState
}; };
const newState = toggleSelected(convertedState, [], seasonNumber, isExpanded, false); const newState = toggleSelected(convertedState, [], albumId, isExpanded, false);
return getExpandedState(newState); return getExpandedState(newState);
}); });
@ -136,10 +136,9 @@ class SeriesDetails extends Component {
tvMazeId, tvMazeId,
imdbId, imdbId,
artistName, artistName,
runtime,
ratings, ratings,
sizeOnDisk, sizeOnDisk,
episodeFileCount, trackFileCount,
qualityProfileId, qualityProfileId,
monitored, monitored,
status, status,
@ -175,10 +174,10 @@ class SeriesDetails extends Component {
let episodeFilesCountMessage = 'No episode files'; let episodeFilesCountMessage = 'No episode files';
if (episodeFileCount === 1) { if (trackFileCount === 1) {
episodeFilesCountMessage = '1 episode file'; episodeFilesCountMessage = '1 episode file';
} else if (episodeFileCount > 1) { } else if (trackFileCount > 1) {
episodeFilesCountMessage = `${episodeFileCount} episode files`; episodeFilesCountMessage = `${trackFileCount} episode files`;
} }
let expandIcon = icons.EXPAND_INDETERMINATE; let expandIcon = icons.EXPAND_INDETERMINATE;
@ -310,13 +309,6 @@ class SeriesDetails extends Component {
<div className={styles.details}> <div className={styles.details}>
<div> <div>
{
!!runtime &&
<span className={styles.runtime}>
{runtime} Minutes
</span>
}
<HeartRating <HeartRating
rating={ratings.value} rating={ratings.value}
iconSize={20} iconSize={20}
@ -491,10 +483,12 @@ class SeriesDetails extends Component {
albums.slice(0).reverse().map((season) => { albums.slice(0).reverse().map((season) => {
return ( return (
<SeriesDetailsSeasonConnector <SeriesDetailsSeasonConnector
key={season.seasonNumber} key={season.id}
artistId={id} artistId={id}
albumId={season.id}
statistics={season.statistics}
{...season} {...season}
isExpanded={expandedState[season.seasonNumber]} isExpanded={expandedState[season.id]}
onExpandPress={this.onExpandPress} onExpandPress={this.onExpandPress}
/> />
); );
@ -548,10 +542,9 @@ SeriesDetails.propTypes = {
tvMazeId: PropTypes.number, tvMazeId: PropTypes.number,
imdbId: PropTypes.string, imdbId: PropTypes.string,
artistName: PropTypes.string.isRequired, artistName: PropTypes.string.isRequired,
runtime: PropTypes.number.isRequired,
ratings: PropTypes.object.isRequired, ratings: PropTypes.object.isRequired,
sizeOnDisk: PropTypes.number.isRequired, sizeOnDisk: PropTypes.number.isRequired,
episodeFileCount: PropTypes.number, trackFileCount: PropTypes.number,
qualityProfileId: PropTypes.number.isRequired, qualityProfileId: PropTypes.number.isRequired,
monitored: PropTypes.bool.isRequired, monitored: PropTypes.bool.isRequired,
status: PropTypes.string.isRequired, status: PropTypes.string.isRequired,

@ -16,30 +16,16 @@ function SeriesDetailsLinks(props) {
<div className={styles.links}> <div className={styles.links}>
<Link <Link
className={styles.link} className={styles.link}
to={`http://www.thetvdb.com/?tab=series&id=${foreignArtistId}`} to={`https://musicbrainz.org/artist/${foreignArtistId}`}
> >
<Label <Label
className={styles.linkLabel} className={styles.linkLabel}
kind={kinds.INFO} kind={kinds.INFO}
size={sizes.LARGE} size={sizes.LARGE}
> >
The TVDB Musicbrainz
</Label> </Label>
</Link> </Link>
<Link
className={styles.link}
to={`http://trakt.tv/search/tvdb/${foreignArtistId}?id_type=show`}
>
<Label
className={styles.linkLabel}
kind={kinds.INFO}
size={sizes.LARGE}
>
Trakt
</Label>
</Link>
{ {
!!tvMazeId && !!tvMazeId &&
<Link <Link

@ -38,7 +38,7 @@
.left { .left {
display: flex; display: flex;
align-items: center; align-items: center;
flex: 0 1 300px; flex: 0 1 600px;
} }
.left, .left,

@ -23,30 +23,6 @@ import OrganizePreviewModalConnector from 'Organize/OrganizePreviewModalConnecto
import EpisodeRowConnector from './EpisodeRowConnector'; import EpisodeRowConnector from './EpisodeRowConnector';
import styles from './SeriesDetailsSeason.css'; import styles from './SeriesDetailsSeason.css';
function getSeasonStatistics(episodes) {
let episodeCount = 0;
let episodeFileCount = 0;
let totalEpisodeCount = 0;
episodes.forEach((episode) => {
if (episode.episodeFileId || (episode.monitored && isBefore(episode.airDateUtc))) {
episodeCount++;
}
if (episode.episodeFileId) {
episodeFileCount++;
}
totalEpisodeCount++;
});
return {
episodeCount,
episodeFileCount,
totalEpisodeCount
};
}
function getEpisodeCountKind(monitored, episodeFileCount, episodeCount) { function getEpisodeCountKind(monitored, episodeFileCount, episodeCount) {
if (episodeFileCount === episodeCount && episodeCount > 0) { if (episodeFileCount === episodeCount && episodeCount > 0) {
return kinds.SUCCESS; return kinds.SUCCESS;
@ -89,7 +65,7 @@ class SeriesDetailsSeason extends Component {
_expandByDefault() { _expandByDefault() {
const { const {
seasonNumber, albumId,
onExpandPress, onExpandPress,
items items
} = this.props; } = this.props;
@ -99,7 +75,7 @@ class SeriesDetailsSeason extends Component {
isAfter(item.airDateUtc, { days: -30 }); isAfter(item.airDateUtc, { days: -30 });
}); });
onExpandPress(seasonNumber, expand && seasonNumber > 0); onExpandPress(albumId, expand && albumId > 0);
} }
// //
@ -123,11 +99,11 @@ class SeriesDetailsSeason extends Component {
onExpandPress = () => { onExpandPress = () => {
const { const {
seasonNumber, albumId,
isExpanded isExpanded
} = this.props; } = this.props;
this.props.onExpandPress(seasonNumber, !isExpanded); this.props.onExpandPress(albumId, !isExpanded);
} }
onMonitorEpisodePress = (episodeId, monitored, { shiftKey }) => { onMonitorEpisodePress = (episodeId, monitored, { shiftKey }) => {
@ -155,7 +131,10 @@ class SeriesDetailsSeason extends Component {
const { const {
artistId, artistId,
monitored, monitored,
seasonNumber, title,
releaseDate,
albumId,
statistics,
items, items,
columns, columns,
isSaving, isSaving,
@ -169,10 +148,10 @@ class SeriesDetailsSeason extends Component {
} = this.props; } = this.props;
const { const {
episodeCount, trackCount,
episodeFileCount, trackFileCount,
totalEpisodeCount totalTrackCount
} = getSeasonStatistics(items); } = statistics;
const { const {
isOrganizeModalOpen, isOrganizeModalOpen,
@ -194,22 +173,22 @@ class SeriesDetailsSeason extends Component {
/> />
{ {
seasonNumber === 0 ? albumId === 0 ?
<span className={styles.seasonNumber}> <span className={styles.seasonNumber}>
Specials Specials
</span> : </span> :
<span className={styles.seasonNumber}> <span className={styles.seasonNumber}>
Season {seasonNumber} {title}
</span> </span>
} }
<Label <Label
title={`${totalEpisodeCount} episodes total. ${episodeFileCount} episodes with files.`} title={`${totalTrackCount} tracks total. ${trackFileCount} tracks with files.`}
kind={getEpisodeCountKind(monitored, episodeFileCount, episodeCount)} kind={getEpisodeCountKind(monitored, trackFileCount, trackCount)}
size={sizes.LARGE} size={sizes.LARGE}
> >
{ {
<span>{episodeFileCount} / {episodeCount}</span> <span>{trackFileCount} / {trackCount}</span>
} }
</Label> </Label>
</div> </div>
@ -218,12 +197,7 @@ class SeriesDetailsSeason extends Component {
className={styles.expandButton} className={styles.expandButton}
onPress={this.onExpandPress} onPress={this.onExpandPress}
> >
<Icon
className={styles.expandButtonIcon}
name={isExpanded ? icons.COLLAPSE : icons.EXPAND}
title={isExpanded ? 'Hide episodes' : 'Show episodes'}
size={24}
/>
{ {
!isSmallScreen && !isSmallScreen &&
<span>&nbsp;</span> <span>&nbsp;</span>
@ -277,7 +251,7 @@ class SeriesDetailsSeason extends Component {
name={icons.EPISODE_FILE} name={icons.EPISODE_FILE}
/> />
Manage Episodes Manage Tracks
</MenuItem> </MenuItem>
</MenuContent> </MenuContent>
</Menu> : </Menu> :
@ -286,7 +260,7 @@ class SeriesDetailsSeason extends Component {
<SpinnerIconButton <SpinnerIconButton
className={styles.actionButton} className={styles.actionButton}
name={icons.SEARCH} name={icons.SEARCH}
title="Search for monitored episodes in this seasons" title="Search for album"
size={24} size={24}
isSpinning={isSearching} isSpinning={isSearching}
onPress={onSearchPress} onPress={onSearchPress}
@ -295,7 +269,7 @@ class SeriesDetailsSeason extends Component {
<IconButton <IconButton
className={styles.actionButton} className={styles.actionButton}
name={icons.ORGANIZE} name={icons.ORGANIZE}
title="Preview rename for this season" title="Preview rename for this album"
size={24} size={24}
onPress={this.onOrganizePress} onPress={this.onOrganizePress}
/> />
@ -303,7 +277,7 @@ class SeriesDetailsSeason extends Component {
<IconButton <IconButton
className={styles.actionButton} className={styles.actionButton}
name={icons.EPISODE_FILE} name={icons.EPISODE_FILE}
title="Manage episode files in this series" title="Manage track files in this artist"
size={24} size={24}
onPress={this.onManageEpisodesPress} onPress={this.onManageEpisodesPress}
/> />
@ -312,59 +286,17 @@ class SeriesDetailsSeason extends Component {
</div> </div>
<div>
{
isExpanded &&
<div className={styles.episodes}>
{
items.length ?
<Table
columns={columns}
onTableOptionChange={onTableOptionChange}
>
<TableBody>
{
items.map((item) => {
return (
<EpisodeRowConnector
key={item.id}
columns={columns}
{...item}
onMonitorEpisodePress={this.onMonitorEpisodePress}
/>
);
})
}
</TableBody>
</Table> :
<div className={styles.noEpisodes}>
No episodes in this season
</div>
}
<div className={styles.collapseButtonContainer}>
<IconButton
name={icons.COLLAPSE}
size={20}
title="Hide episodes"
onPress={this.onExpandPress}
/>
</div>
</div>
}
</div>
<OrganizePreviewModalConnector <OrganizePreviewModalConnector
isOpen={isOrganizeModalOpen} isOpen={isOrganizeModalOpen}
artistId={artistId} artistId={artistId}
seasonNumber={seasonNumber} albumId={albumId}
onModalClose={this.onOrganizeModalClose} onModalClose={this.onOrganizeModalClose}
/> />
<EpisodeFileEditorModal <EpisodeFileEditorModal
isOpen={isManageEpisodesOpen} isOpen={isManageEpisodesOpen}
artistId={artistId} artistId={artistId}
seasonNumber={seasonNumber} albumId={albumId}
onModalClose={this.onManageEpisodesModalClose} onModalClose={this.onManageEpisodesModalClose}
/> />
</div> </div>
@ -375,7 +307,10 @@ class SeriesDetailsSeason extends Component {
SeriesDetailsSeason.propTypes = { SeriesDetailsSeason.propTypes = {
artistId: PropTypes.number.isRequired, artistId: PropTypes.number.isRequired,
monitored: PropTypes.bool.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, items: PropTypes.arrayOf(PropTypes.object).isRequired,
columns: PropTypes.arrayOf(PropTypes.object).isRequired, columns: PropTypes.arrayOf(PropTypes.object).isRequired,
isSaving: PropTypes.bool, isSaving: PropTypes.bool,
@ -390,4 +325,12 @@ SeriesDetailsSeason.propTypes = {
onSearchPress: PropTypes.func.isRequired onSearchPress: PropTypes.func.isRequired
}; };
SeriesDetailsSeason.defaultProps = {
statistics: {
trackFileCount: 0,
totalTrackCount: 0,
percentOfTracks: 0
}
};
export default SeriesDetailsSeason; export default SeriesDetailsSeason;

@ -60,12 +60,12 @@ class SeriesDetailsSeasonConnector extends Component {
onMonitorSeasonPress = (monitored) => { onMonitorSeasonPress = (monitored) => {
const { const {
artistId, artistId,
seasonNumber albumId
} = this.props; } = this.props;
this.props.toggleSeasonMonitored({ this.props.toggleSeasonMonitored({
artistId, artistId,
seasonNumber, albumId,
monitored monitored
}); });
} }
@ -73,13 +73,13 @@ class SeriesDetailsSeasonConnector extends Component {
onSearchPress = () => { onSearchPress = () => {
const { const {
artistId, artistId,
seasonNumber albumId
} = this.props; } = this.props;
this.props.executeCommand({ this.props.executeCommand({
name: commandNames.SEASON_SEARCH, name: commandNames.SEASON_SEARCH,
artistId, artistId,
seasonNumber albumIds: [albumId]
}); });
} }
@ -108,7 +108,7 @@ class SeriesDetailsSeasonConnector extends Component {
SeriesDetailsSeasonConnector.propTypes = { SeriesDetailsSeasonConnector.propTypes = {
artistId: PropTypes.number.isRequired, artistId: PropTypes.number.isRequired,
seasonNumber: PropTypes.number.isRequired, albumId: PropTypes.number.isRequired,
toggleSeasonMonitored: PropTypes.func.isRequired, toggleSeasonMonitored: PropTypes.func.isRequired,
toggleEpisodesMonitored: PropTypes.func.isRequired, toggleEpisodesMonitored: PropTypes.func.isRequired,
setEpisodesTableOption: PropTypes.func.isRequired, setEpisodesTableOption: PropTypes.func.isRequired,

@ -15,5 +15,5 @@ export const RENAME_FILES = 'RenameFiles';
export const RENAME_ARTIST = 'RenameArtist'; export const RENAME_ARTIST = 'RenameArtist';
export const RESET_API_KEY = 'ResetApiKey'; export const RESET_API_KEY = 'ResetApiKey';
export const RSS_SYNC = 'RssSync'; export const RSS_SYNC = 'RssSync';
export const SEASON_SEARCH = 'SeasonSearch'; export const SEASON_SEARCH = 'AlbumSearch';
export const ARTIST_SEARCH = 'ArtistSearch'; export const ARTIST_SEARCH = 'ArtistSearch';

@ -1,9 +1,9 @@
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import React, { Component } from 'react'; import React, { Component } from 'react';
import Modal from 'Components/Modal/Modal'; import Modal from 'Components/Modal/Modal';
import SelectSeasonModalContentConnector from './SelectSeasonModalContentConnector'; import SelectAlbumModalContentConnector from './SelectAlbumModalContentConnector';
class SelectSeasonModal extends Component { class SelectAlbumModal extends Component {
// //
// Render // Render
@ -20,7 +20,7 @@ class SelectSeasonModal extends Component {
isOpen={isOpen} isOpen={isOpen}
onModalClose={onModalClose} onModalClose={onModalClose}
> >
<SelectSeasonModalContentConnector <SelectAlbumModalContentConnector
{...otherProps} {...otherProps}
onModalClose={onModalClose} onModalClose={onModalClose}
/> />
@ -29,9 +29,9 @@ class SelectSeasonModal extends Component {
} }
} }
SelectSeasonModal.propTypes = { SelectAlbumModal.propTypes = {
isOpen: PropTypes.bool.isRequired, isOpen: PropTypes.bool.isRequired,
onModalClose: PropTypes.func.isRequired onModalClose: PropTypes.func.isRequired
}; };
export default SelectSeasonModal; export default SelectAlbumModal;

@ -5,9 +5,9 @@ import ModalContent from 'Components/Modal/ModalContent';
import ModalHeader from 'Components/Modal/ModalHeader'; import ModalHeader from 'Components/Modal/ModalHeader';
import ModalBody from 'Components/Modal/ModalBody'; import ModalBody from 'Components/Modal/ModalBody';
import ModalFooter from 'Components/Modal/ModalFooter'; import ModalFooter from 'Components/Modal/ModalFooter';
import SelectSeasonRow from './SelectSeasonRow'; import SelectAlbumRow from './SelectAlbumRow';
class SelectSeasonModalContent extends Component { class SelectAlbumModalContent extends Component {
// //
// Render // Render
@ -15,24 +15,25 @@ class SelectSeasonModalContent extends Component {
render() { render() {
const { const {
items, items,
onSeasonSelect, onAlbumSelect,
onModalClose onModalClose
} = this.props; } = this.props;
return ( return (
<ModalContent onModalClose={onModalClose}> <ModalContent onModalClose={onModalClose}>
<ModalHeader> <ModalHeader>
Manual Import - Select Season Manual Import - Select Album
</ModalHeader> </ModalHeader>
<ModalBody> <ModalBody>
{ {
items.map((item) => { items.map((item) => {
return ( return (
<SelectSeasonRow <SelectAlbumRow
key={item.seasonNumber} key={item.id}
seasonNumber={item.seasonNumber} id={item.id}
onSeasonSelect={onSeasonSelect} title={item.title}
onAlbumSelect={onAlbumSelect}
/> />
); );
}) })
@ -49,10 +50,10 @@ class SelectSeasonModalContent extends Component {
} }
} }
SelectSeasonModalContent.propTypes = { SelectAlbumModalContent.propTypes = {
items: PropTypes.arrayOf(PropTypes.object).isRequired, items: PropTypes.arrayOf(PropTypes.object).isRequired,
onSeasonSelect: PropTypes.func.isRequired, onAlbumSelect: PropTypes.func.isRequired,
onModalClose: PropTypes.func.isRequired onModalClose: PropTypes.func.isRequired
}; };
export default SelectSeasonModalContent; export default SelectAlbumModalContent;

@ -1,17 +1,18 @@
import _ from 'lodash';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import React, { Component } from 'react'; import React, { Component } from 'react';
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import { createSelector } from 'reselect'; import { createSelector } from 'reselect';
import { updateInteractiveImportItem } from 'Store/Actions/interactiveImportActions'; import { updateInteractiveImportItem } from 'Store/Actions/interactiveImportActions';
import createArtistSelector from 'Store/Selectors/createArtistSelector'; import createArtistSelector from 'Store/Selectors/createArtistSelector';
import SelectSeasonModalContent from './SelectSeasonModalContent'; import SelectAlbumModalContent from './SelectAlbumModalContent';
function createMapStateToProps() { function createMapStateToProps() {
return createSelector( return createSelector(
createArtistSelector(), createArtistSelector(),
(series) => { (series) => {
return { return {
items: series.seasons items: series.albums
}; };
} }
); );
@ -21,16 +22,18 @@ const mapDispatchToProps = {
updateInteractiveImportItem updateInteractiveImportItem
}; };
class SelectSeasonModalContentConnector extends Component { class SelectAlbumModalContentConnector extends Component {
// //
// Listeners // Listeners
onSeasonSelect = (seasonNumber) => { onAlbumSelect = (albumId) => {
const album = _.find(this.props.items, { id: albumId });
this.props.ids.forEach((id) => { this.props.ids.forEach((id) => {
this.props.updateInteractiveImportItem({ this.props.updateInteractiveImportItem({
id, id,
seasonNumber, album,
episodes: [] episodes: []
}); });
}); });
@ -43,15 +46,15 @@ class SelectSeasonModalContentConnector extends Component {
render() { render() {
return ( return (
<SelectSeasonModalContent <SelectAlbumModalContent
{...this.props} {...this.props}
onSeasonSelect={this.onSeasonSelect} onAlbumSelect={this.onAlbumSelect}
/> />
); );
} }
} }
SelectSeasonModalContentConnector.propTypes = { SelectAlbumModalContentConnector.propTypes = {
ids: PropTypes.arrayOf(PropTypes.number).isRequired, ids: PropTypes.arrayOf(PropTypes.number).isRequired,
artistId: PropTypes.number.isRequired, artistId: PropTypes.number.isRequired,
items: PropTypes.arrayOf(PropTypes.object).isRequired, items: PropTypes.arrayOf(PropTypes.object).isRequired,
@ -59,4 +62,4 @@ SelectSeasonModalContentConnector.propTypes = {
onModalClose: PropTypes.func.isRequired onModalClose: PropTypes.func.isRequired
}; };
export default connect(createMapStateToProps, mapDispatchToProps)(SelectSeasonModalContentConnector); export default connect(createMapStateToProps, mapDispatchToProps)(SelectAlbumModalContentConnector);

@ -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 (
<Link
className={styles.season}
component="div"
onPress={this.onPress}
>
{this.props.title}
</Link>
);
}
}
SelectAlbumRow.propTypes = {
id: PropTypes.number.isRequired,
title: PropTypes.string.isRequired,
onAlbumSelect: PropTypes.func.isRequired
};
export default SelectAlbumRow;

@ -37,7 +37,7 @@ class SelectArtistModalContent extends Component {
render() { render() {
const { const {
items, items,
onSeriesSelect, onArtistSelect,
onModalClose onModalClose
} = this.props; } = this.props;
@ -46,7 +46,7 @@ class SelectArtistModalContent extends Component {
return ( return (
<ModalContent onModalClose={onModalClose}> <ModalContent onModalClose={onModalClose}>
<ModalHeader> <ModalHeader>
Manual Import - Select Series Manual Import - Select Artist
</ModalHeader> </ModalHeader>
<ModalBody <ModalBody
@ -55,7 +55,7 @@ class SelectArtistModalContent extends Component {
> >
<TextInput <TextInput
className={styles.filterInput} className={styles.filterInput}
placeholder="Filter series" placeholder="Filter artist"
name="filter" name="filter"
value={filter} value={filter}
autoFocus={true} autoFocus={true}
@ -65,13 +65,13 @@ class SelectArtistModalContent extends Component {
<Scroller className={styles.scroller}> <Scroller className={styles.scroller}>
{ {
items.map((item) => { items.map((item) => {
return item.title.toLowerCase().includes(filter) ? return item.artistName.toLowerCase().includes(filter) ?
( (
<SelectArtistRow <SelectArtistRow
key={item.id} key={item.id}
id={item.id} id={item.id}
title={item.title} artistName={item.artistName}
onSeriesSelect={onSeriesSelect} onArtistSelect={onArtistSelect}
/> />
) : ) :
null; null;
@ -92,7 +92,7 @@ class SelectArtistModalContent extends Component {
SelectArtistModalContent.propTypes = { SelectArtistModalContent.propTypes = {
items: PropTypes.arrayOf(PropTypes.object).isRequired, items: PropTypes.arrayOf(PropTypes.object).isRequired,
onSeriesSelect: PropTypes.func.isRequired, onArtistSelect: PropTypes.func.isRequired,
onModalClose: PropTypes.func.isRequired onModalClose: PropTypes.func.isRequired
}; };

@ -27,15 +27,15 @@ class SelectArtistModalContentConnector extends Component {
// //
// Listeners // Listeners
onSeriesSelect = (artistId) => { onArtistSelect = (artistId) => {
const series = _.find(this.props.items, { id: artistId }); const artist = _.find(this.props.items, { id: artistId });
this.props.ids.forEach((id) => { this.props.ids.forEach((id) => {
this.props.updateInteractiveImportItem({ this.props.updateInteractiveImportItem({
id, id,
series, artist,
seasonNumber: undefined, album: undefined,
episodes: [] tracks: []
}); });
}); });
@ -49,7 +49,7 @@ class SelectArtistModalContentConnector extends Component {
return ( return (
<SelectArtistModalContent <SelectArtistModalContent
{...this.props} {...this.props}
onSeriesSelect={this.onSeriesSelect} onArtistSelect={this.onArtistSelect}
/> />
); );
} }

@ -1,4 +1,4 @@
.series { .artist {
padding: 8px; padding: 8px;
border-bottom: 1px solid $borderColor; border-bottom: 1px solid $borderColor;
} }

@ -9,7 +9,7 @@ class SelectArtistRow extends Component {
// Listeners // Listeners
onPress = () => { onPress = () => {
this.props.onSeriesSelect(this.props.id); this.props.onArtistSelect(this.props.id);
} }
// //
@ -18,11 +18,11 @@ class SelectArtistRow extends Component {
render() { render() {
return ( return (
<Link <Link
className={styles.series} className={styles.artist}
component="div" component="div"
onPress={this.onPress} onPress={this.onPress}
> >
{this.props.title} {this.props.artistName}
</Link> </Link>
); );
} }
@ -30,8 +30,8 @@ class SelectArtistRow extends Component {
SelectArtistRow.propTypes = { SelectArtistRow.propTypes = {
id: PropTypes.number.isRequired, id: PropTypes.number.isRequired,
title: PropTypes.string.isRequired, artistName: PropTypes.string.isRequired,
onSeriesSelect: PropTypes.func.isRequired onArtistSelect: PropTypes.func.isRequired
}; };
export default SelectArtistRow; export default SelectArtistRow;

@ -15,8 +15,8 @@ import ModalBody from 'Components/Modal/ModalBody';
import ModalFooter from 'Components/Modal/ModalFooter'; import ModalFooter from 'Components/Modal/ModalFooter';
import Table from 'Components/Table/Table'; import Table from 'Components/Table/Table';
import TableBody from 'Components/Table/TableBody'; import TableBody from 'Components/Table/TableBody';
import SelectArtistModal from 'InteractiveImport/Series/SelectArtistModal'; import SelectArtistModal from 'InteractiveImport/Artist/SelectArtistModal';
import SelectSeasonModal from 'InteractiveImport/Season/SelectSeasonModal'; import SelectAlbumModal from 'InteractiveImport/Album/SelectAlbumModal';
import InteractiveImportRow from './InteractiveImportRow'; import InteractiveImportRow from './InteractiveImportRow';
import styles from './InteractiveImportModalContent.css'; import styles from './InteractiveImportModalContent.css';
@ -28,19 +28,19 @@ const columns = [
isVisible: true isVisible: true
}, },
{ {
name: 'series', name: 'artist',
label: 'Series', label: 'Artist',
isSortable: true, isSortable: true,
isVisible: true isVisible: true
}, },
{ {
name: 'season', name: 'album',
label: 'Season', label: 'Album',
isVisible: true isVisible: true
}, },
{ {
name: 'episodes', name: 'tracks',
label: 'Episode(s)', label: 'Track(s)',
isVisible: true isVisible: true
}, },
{ {
@ -79,7 +79,7 @@ class InteractiveImportModalContent extends Component {
selectedState: {}, selectedState: {},
invalidRowsSelected: [], invalidRowsSelected: [],
isSelectArtistModalOpen: false, isSelectArtistModalOpen: false,
isSelectSeasonModalOpen: false isSelectAlbumModalOpen: false
}; };
} }
@ -131,16 +131,16 @@ class InteractiveImportModalContent extends Component {
this.setState({ isSelectArtistModalOpen: true }); this.setState({ isSelectArtistModalOpen: true });
} }
onSelectSeasonPress = () => { onSelectAlbumPress = () => {
this.setState({ isSelectSeasonModalOpen: true }); this.setState({ isSelectAlbumModalOpen: true });
} }
onSelectArtistModalClose = () => { onSelectArtistModalClose = () => {
this.setState({ isSelectArtistModalOpen: false }); this.setState({ isSelectArtistModalOpen: false });
} }
onSelectSeasonModalClose = () => { onSelectAlbumModalClose = () => {
this.setState({ isSelectSeasonModalOpen: false }); this.setState({ isSelectAlbumModalOpen: false });
} }
// //
@ -169,7 +169,7 @@ class InteractiveImportModalContent extends Component {
selectedState, selectedState,
invalidRowsSelected, invalidRowsSelected,
isSelectArtistModalOpen, isSelectArtistModalOpen,
isSelectSeasonModalOpen isSelectAlbumModalOpen
} = this.state; } = this.state;
const selectedIds = this.getSelectedIds(); const selectedIds = this.getSelectedIds();
@ -250,11 +250,11 @@ class InteractiveImportModalContent extends Component {
<div className={downloadId ? styles.leftButtons : styles.centerButtons}> <div className={downloadId ? styles.leftButtons : styles.centerButtons}>
<Button onPress={this.onSelectArtistPress}> <Button onPress={this.onSelectArtistPress}>
Select Series Select Artist
</Button> </Button>
<Button onPress={this.onSelectSeasonPress}> <Button onPress={this.onSelectAlbumPress}>
Select Season Select Album
</Button> </Button>
</div> </div>
@ -284,11 +284,11 @@ class InteractiveImportModalContent extends Component {
onModalClose={this.onSelectArtistModalClose} onModalClose={this.onSelectArtistModalClose}
/> />
<SelectSeasonModal <SelectAlbumModal
isOpen={isSelectSeasonModalOpen} isOpen={isSelectAlbumModalOpen}
ids={selectedIds} ids={selectedIds}
artistId={selectedItem && selectedItem.series && selectedItem.series.id} artistId={selectedItem && selectedItem.series && selectedItem.series.id}
onModalClose={this.onSelectSeasonModalClose} onModalClose={this.onSelectAlbumModalClose}
/> />
</ModalContent> </ModalContent>
); );

@ -71,31 +71,32 @@ class InteractiveImportModalContentConnector extends Component {
if (isSelected) { if (isSelected) {
const { const {
series, artist,
seasonNumber, album,
episodes, tracks,
quality quality
} = item; } = item;
if (!series) { if (!artist) {
this.setState({ interactiveImportErrorMessage: 'Series must be chosen for each selected file' }); this.setState({ interactiveImportErrorMessage: 'Artist must be chosen for each selected file' });
return false; return false;
} }
if (isNaN(seasonNumber)) { if (!album) {
this.setState({ interactiveImportErrorMessage: 'Season must be chosen for each selected file' }); this.setState({ interactiveImportErrorMessage: 'Album must be chosen for each selected file' });
return false; return false;
} }
if (!episodes || !episodes.length) { if (!tracks || !tracks.length) {
this.setState({ interactiveImportErrorMessage: 'One or more episodes must be chosen for each selected file' }); this.setState({ interactiveImportErrorMessage: 'One or more tracks must be chosen for each selected file' });
return false; return false;
} }
files.push({ files.push({
path: item.path, path: item.path,
artistId: series.id, artistId: artist.id,
episodeIds: _.map(episodes, 'id'), albumId: album.id,
trackIds: _.map(tracks, 'id'),
quality, quality,
downloadId: this.props.downloadId downloadId: this.props.downloadId
}); });

@ -9,9 +9,9 @@ import TableRowCellButton from 'Components/Table/Cells/TableRowCellButton';
import TableSelectCell from 'Components/Table/Cells/TableSelectCell'; import TableSelectCell from 'Components/Table/Cells/TableSelectCell';
import Popover from 'Components/Tooltip/Popover'; import Popover from 'Components/Tooltip/Popover';
import EpisodeQuality from 'Episode/EpisodeQuality'; import EpisodeQuality from 'Episode/EpisodeQuality';
import SelectArtistModal from 'InteractiveImport/Series/SelectArtistModal'; import SelectArtistModal from 'InteractiveImport/Artist/SelectArtistModal';
import SelectSeasonModal from 'InteractiveImport/Season/SelectSeasonModal'; import SelectAlbumModal from 'InteractiveImport/Album/SelectAlbumModal';
import SelectEpisodeModal from 'InteractiveImport/Episode/SelectEpisodeModal'; import SelectTrackModal from 'InteractiveImport/Track/SelectTrackModal';
import SelectQualityModal from 'InteractiveImport/Quality/SelectQualityModal'; import SelectQualityModal from 'InteractiveImport/Quality/SelectQualityModal';
import InteractiveImportRowCellPlaceholder from './InteractiveImportRowCellPlaceholder'; import InteractiveImportRowCellPlaceholder from './InteractiveImportRowCellPlaceholder';
import styles from './InteractiveImportRow.css'; import styles from './InteractiveImportRow.css';
@ -26,8 +26,8 @@ class InteractiveImportRow extends Component {
this.state = { this.state = {
isSelectArtistModalOpen: false, isSelectArtistModalOpen: false,
isSelectSeasonModalOpen: false, isSelectAlbumModalOpen: false,
isSelectEpisodeModalOpen: false, isSelectTrackModalOpen: false,
isSelectQualityModalOpen: false isSelectQualityModalOpen: false
}; };
} }
@ -35,13 +35,13 @@ class InteractiveImportRow extends Component {
componentDidMount() { componentDidMount() {
const { const {
id, id,
series, artist,
seasonNumber, album,
episodes, tracks,
quality quality
} = this.props; } = this.props;
if (series && seasonNumber !== undefined && episodes.length && quality) { if (artist && album !== undefined && tracks.length && quality) {
this.props.onSelectedChange({ id, value: true }); this.props.onSelectedChange({ id, value: true });
} }
} }
@ -49,9 +49,9 @@ class InteractiveImportRow extends Component {
componentDidUpdate(prevProps) { componentDidUpdate(prevProps) {
const { const {
id, id,
series, artist,
seasonNumber, album,
episodes, tracks,
quality, quality,
isSelected, isSelected,
onValidRowChange onValidRowChange
@ -61,7 +61,7 @@ class InteractiveImportRow extends Component {
return; return;
} }
const isValid = !!(series && seasonNumber != null && episodes.length && quality); const isValid = !!(artist && album != null && tracks.length && quality);
if (isSelected && !isValid) { if (isSelected && !isValid) {
onValidRowChange(id, false); onValidRowChange(id, false);
@ -91,12 +91,12 @@ class InteractiveImportRow extends Component {
this.setState({ isSelectArtistModalOpen: true }); this.setState({ isSelectArtistModalOpen: true });
} }
onSelectSeasonPress = () => { onSelectAlbumPress = () => {
this.setState({ isSelectSeasonModalOpen: true }); this.setState({ isSelectAlbumModalOpen: true });
} }
onSelectEpisodePress = () => { onSelectTrackPress = () => {
this.setState({ isSelectEpisodeModalOpen: true }); this.setState({ isSelectTrackModalOpen: true });
} }
onSelectQualityPress = () => { onSelectQualityPress = () => {
@ -108,13 +108,13 @@ class InteractiveImportRow extends Component {
this.selectRowAfterChange(changed); this.selectRowAfterChange(changed);
} }
onSelectSeasonModalClose = (changed) => { onSelectAlbumModalClose = (changed) => {
this.setState({ isSelectSeasonModalOpen: false }); this.setState({ isSelectAlbumModalOpen: false });
this.selectRowAfterChange(changed); this.selectRowAfterChange(changed);
} }
onSelectEpisodeModalClose = (changed) => { onSelectTrackModalClose = (changed) => {
this.setState({ isSelectEpisodeModalOpen: false }); this.setState({ isSelectTrackModalOpen: false });
this.selectRowAfterChange(changed); this.selectRowAfterChange(changed);
} }
@ -130,9 +130,9 @@ class InteractiveImportRow extends Component {
const { const {
id, id,
relativePath, relativePath,
series, artist,
seasonNumber, album,
episodes, tracks,
quality, quality,
size, size,
rejections, rejections,
@ -142,18 +142,19 @@ class InteractiveImportRow extends Component {
const { const {
isSelectArtistModalOpen, isSelectArtistModalOpen,
isSelectSeasonModalOpen, isSelectAlbumModalOpen,
isSelectEpisodeModalOpen, isSelectTrackModalOpen,
isSelectQualityModalOpen isSelectQualityModalOpen
} = this.state; } = this.state;
const seriesTitle = series ? series.title : ''; const seriesTitle = artist ? artist.artistName : '';
const episodeNumbers = episodes.map((episode) => episode.episodeNumber) const albumTitle = album ? album.title : '';
const trackNumbers = tracks.map((episode) => episode.trackNumber)
.join(', '); .join(', ');
const showSeriesPlaceholder = isSelected && !series; const showSeriesPlaceholder = isSelected && !artist;
const showSeasonNumberPlaceholder = isSelected && !!series && isNaN(seasonNumber); const showSeasonNumberPlaceholder = isSelected && !!artist && !album;
const showEpisodeNumbersPlaceholder = isSelected && Number.isInteger(seasonNumber) && !episodes.length; const showEpisodeNumbersPlaceholder = isSelected && !!album && !tracks.length;
return ( return (
<TableRow> <TableRow>
@ -179,20 +180,20 @@ class InteractiveImportRow extends Component {
</TableRowCellButton> </TableRowCellButton>
<TableRowCellButton <TableRowCellButton
isDisabled={!series} isDisabled={!artist}
onPress={this.onSelectSeasonPress} onPress={this.onSelectAlbumPress}
> >
{ {
showSeasonNumberPlaceholder ? <InteractiveImportRowCellPlaceholder /> : seasonNumber showSeasonNumberPlaceholder ? <InteractiveImportRowCellPlaceholder /> : albumTitle
} }
</TableRowCellButton> </TableRowCellButton>
<TableRowCellButton <TableRowCellButton
isDisabled={!series || isNaN(seasonNumber)} isDisabled={!artist || !album}
onPress={this.onSelectEpisodePress} onPress={this.onSelectTrackPress}
> >
{ {
showEpisodeNumbersPlaceholder ? <InteractiveImportRowCellPlaceholder /> : episodeNumbers showEpisodeNumbersPlaceholder ? <InteractiveImportRowCellPlaceholder /> : trackNumbers
} }
</TableRowCellButton> </TableRowCellButton>
@ -244,19 +245,19 @@ class InteractiveImportRow extends Component {
onModalClose={this.onSelectArtistModalClose} onModalClose={this.onSelectArtistModalClose}
/> />
<SelectSeasonModal <SelectAlbumModal
isOpen={isSelectSeasonModalOpen} isOpen={isSelectAlbumModalOpen}
ids={[id]} ids={[id]}
artistId={series && series.id} artistId={artist && artist.id}
onModalClose={this.onSelectSeasonModalClose} onModalClose={this.onSelectAlbumModalClose}
/> />
<SelectEpisodeModal <SelectTrackModal
isOpen={isSelectEpisodeModalOpen} isOpen={isSelectTrackModalOpen}
id={id} id={id}
artistId={series && series.id} artistId={artist && artist.id}
seasonNumber={seasonNumber} albumId={album && album.id}
onModalClose={this.onSelectEpisodeModalClose} onModalClose={this.onSelectTrackModalClose}
/> />
<SelectQualityModal <SelectQualityModal
@ -276,9 +277,9 @@ class InteractiveImportRow extends Component {
InteractiveImportRow.propTypes = { InteractiveImportRow.propTypes = {
id: PropTypes.number.isRequired, id: PropTypes.number.isRequired,
relativePath: PropTypes.string.isRequired, relativePath: PropTypes.string.isRequired,
series: PropTypes.object, artist: PropTypes.object,
seasonNumber: PropTypes.number, album: PropTypes.object,
episodes: PropTypes.arrayOf(PropTypes.object).isRequired, tracks: PropTypes.arrayOf(PropTypes.object).isRequired,
quality: PropTypes.object, quality: PropTypes.object,
size: PropTypes.number.isRequired, size: PropTypes.number.isRequired,
rejections: PropTypes.arrayOf(PropTypes.object).isRequired, rejections: PropTypes.arrayOf(PropTypes.object).isRequired,
@ -288,7 +289,7 @@ InteractiveImportRow.propTypes = {
}; };
InteractiveImportRow.defaultProps = { InteractiveImportRow.defaultProps = {
episodes: [] tracks: []
}; };
export default InteractiveImportRow; export default InteractiveImportRow;

@ -1,40 +0,0 @@
import PropTypes from 'prop-types';
import React, { Component } from 'react';
import Link from 'Components/Link/Link';
import styles from './SelectSeasonRow.css';
class SelectSeasonRow extends Component {
//
// Listeners
onPress = () => {
this.props.onSeasonSelect(this.props.seasonNumber);
}
//
// Render
render() {
const seasonNumber = this.props.seasonNumber;
return (
<Link
className={styles.season}
component="div"
onPress={this.onPress}
>
{
seasonNumber === 0 ? 'Specials' : `Season ${seasonNumber}`
}
</Link>
);
}
}
SelectSeasonRow.propTypes = {
seasonNumber: PropTypes.number.isRequired,
onSeasonSelect: PropTypes.func.isRequired
};
export default SelectSeasonRow;

@ -1,9 +1,9 @@
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import React, { Component } from 'react'; import React, { Component } from 'react';
import Modal from 'Components/Modal/Modal'; import Modal from 'Components/Modal/Modal';
import SelectEpisodeModalContentConnector from './SelectEpisodeModalContentConnector'; import SelectTrackModalContentConnector from './SelectTrackModalContentConnector';
class SelectEpisodeModal extends Component { class SelectTrackModal extends Component {
// //
// Render // Render
@ -20,7 +20,7 @@ class SelectEpisodeModal extends Component {
isOpen={isOpen} isOpen={isOpen}
onModalClose={onModalClose} onModalClose={onModalClose}
> >
<SelectEpisodeModalContentConnector <SelectTrackModalContentConnector
{...otherProps} {...otherProps}
onModalClose={onModalClose} onModalClose={onModalClose}
/> />
@ -29,9 +29,9 @@ class SelectEpisodeModal extends Component {
} }
} }
SelectEpisodeModal.propTypes = { SelectTrackModal.propTypes = {
isOpen: PropTypes.bool.isRequired, isOpen: PropTypes.bool.isRequired,
onModalClose: PropTypes.func.isRequired onModalClose: PropTypes.func.isRequired
}; };
export default SelectEpisodeModal; export default SelectTrackModal;

@ -12,11 +12,11 @@ import ModalBody from 'Components/Modal/ModalBody';
import ModalFooter from 'Components/Modal/ModalFooter'; import ModalFooter from 'Components/Modal/ModalFooter';
import Table from 'Components/Table/Table'; import Table from 'Components/Table/Table';
import TableBody from 'Components/Table/TableBody'; import TableBody from 'Components/Table/TableBody';
import SelectEpisodeRow from './SelectEpisodeRow'; import SelectTrackRow from './SelectTrackRow';
const columns = [ const columns = [
{ {
name: 'episodeNumber', name: 'trackNumber',
label: '#', label: '#',
isSortable: true, isSortable: true,
isVisible: true isVisible: true
@ -25,15 +25,10 @@ const columns = [
name: 'title', name: 'title',
label: 'Title', label: 'Title',
isVisible: true isVisible: true
},
{
name: 'airDate',
label: 'Air Date',
isVisible: true
} }
]; ];
class SelectEpisodeModalContent extends Component { class SelectTrackModalContent extends Component {
// //
// Lifecycle // Lifecycle
@ -69,8 +64,8 @@ class SelectEpisodeModalContent extends Component {
}); });
} }
onEpisodesSelect = () => { onTracksSelect = () => {
this.props.onEpisodesSelect(this.getSelectedIds()); this.props.onTracksSelect(this.getSelectedIds());
} }
// //
@ -94,12 +89,12 @@ class SelectEpisodeModalContent extends Component {
selectedState selectedState
} = this.state; } = this.state;
const errorMessage = error && error.message || 'Unable to load episodes'; const errorMessage = error && error.message || 'Unable to load tracks';
return ( return (
<ModalContent onModalClose={onModalClose}> <ModalContent onModalClose={onModalClose}>
<ModalHeader> <ModalHeader>
Manual Import - Select Episode(s) Manual Import - Select Track(s)
</ModalHeader> </ModalHeader>
<ModalBody> <ModalBody>
@ -129,12 +124,11 @@ class SelectEpisodeModalContent extends Component {
{ {
items.map((item) => { items.map((item) => {
return ( return (
<SelectEpisodeRow <SelectTrackRow
key={item.id} key={item.id}
id={item.id} id={item.id}
episodeNumber={item.episodeNumber} trackNumber={item.trackNumber}
title={item.title} title={item.title}
airDate={item.airDate}
isSelected={selectedState[item.id]} isSelected={selectedState[item.id]}
onSelectedChange={this.onSelectedChange} onSelectedChange={this.onSelectedChange}
/> />
@ -147,7 +141,7 @@ class SelectEpisodeModalContent extends Component {
{ {
isPopulated && !items.length && isPopulated && !items.length &&
'No episodes were found for the selected season' 'No tracks were found for the selected album'
} }
</ModalBody> </ModalBody>
@ -158,9 +152,9 @@ class SelectEpisodeModalContent extends Component {
<Button <Button
kind={kinds.SUCCESS} kind={kinds.SUCCESS}
onPress={this.onEpisodesSelect} onPress={this.onTracksSelect}
> >
Select Episodes Select Tracks
</Button> </Button>
</ModalFooter> </ModalFooter>
</ModalContent> </ModalContent>
@ -168,7 +162,7 @@ class SelectEpisodeModalContent extends Component {
} }
} }
SelectEpisodeModalContent.propTypes = { SelectTrackModalContent.propTypes = {
isFetching: PropTypes.bool.isRequired, isFetching: PropTypes.bool.isRequired,
isPopulated: PropTypes.bool.isRequired, isPopulated: PropTypes.bool.isRequired,
error: PropTypes.object, error: PropTypes.object,
@ -176,8 +170,8 @@ SelectEpisodeModalContent.propTypes = {
sortKey: PropTypes.string, sortKey: PropTypes.string,
sortDirection: PropTypes.string, sortDirection: PropTypes.string,
onSortPress: PropTypes.func.isRequired, onSortPress: PropTypes.func.isRequired,
onEpisodesSelect: PropTypes.func.isRequired, onTracksSelect: PropTypes.func.isRequired,
onModalClose: PropTypes.func.isRequired onModalClose: PropTypes.func.isRequired
}; };
export default SelectEpisodeModalContent; export default SelectTrackModalContent;

@ -6,7 +6,7 @@ import connectSection from 'Store/connectSection';
import { fetchEpisodes, setEpisodesSort, clearEpisodes } from 'Store/Actions/episodeActions'; import { fetchEpisodes, setEpisodesSort, clearEpisodes } from 'Store/Actions/episodeActions';
import { updateInteractiveImportItem } from 'Store/Actions/interactiveImportActions'; import { updateInteractiveImportItem } from 'Store/Actions/interactiveImportActions';
import createClientSideCollectionSelector from 'Store/Selectors/createClientSideCollectionSelector'; import createClientSideCollectionSelector from 'Store/Selectors/createClientSideCollectionSelector';
import SelectEpisodeModalContent from './SelectEpisodeModalContent'; import SelectTrackModalContent from './SelectTrackModalContent';
function createMapStateToProps() { function createMapStateToProps() {
return createSelector( return createSelector(
@ -24,7 +24,7 @@ const mapDispatchToProps = {
updateInteractiveImportItem updateInteractiveImportItem
}; };
class SelectEpisodeModalContentConnector extends Component { class SelectTrackModalContentConnector extends Component {
// //
// Lifecycle // Lifecycle
@ -32,10 +32,10 @@ class SelectEpisodeModalContentConnector extends Component {
componentDidMount() { componentDidMount() {
const { const {
artistId, artistId,
seasonNumber albumId
} = this.props; } = this.props;
this.props.fetchEpisodes({ artistId, seasonNumber }); this.props.fetchEpisodes({ artistId, albumId });
} }
componentWillUnmount() { componentWillUnmount() {
@ -51,8 +51,8 @@ class SelectEpisodeModalContentConnector extends Component {
this.props.setEpisodesSort({ sortKey, sortDirection }); this.props.setEpisodesSort({ sortKey, sortDirection });
} }
onEpisodesSelect = (episodeIds) => { onTracksSelect = (episodeIds) => {
const episodes = _.reduce(this.props.items, (acc, item) => { const tracks = _.reduce(this.props.items, (acc, item) => {
if (episodeIds.indexOf(item.id) > -1) { if (episodeIds.indexOf(item.id) > -1) {
acc.push(item); acc.push(item);
} }
@ -62,7 +62,7 @@ class SelectEpisodeModalContentConnector extends Component {
this.props.updateInteractiveImportItem({ this.props.updateInteractiveImportItem({
id: this.props.id, id: this.props.id,
episodes: _.sortBy(episodes, 'episodeNumber') tracks: _.sortBy(tracks, 'trackNumber')
}); });
this.props.onModalClose(true); this.props.onModalClose(true);
@ -73,19 +73,19 @@ class SelectEpisodeModalContentConnector extends Component {
render() { render() {
return ( return (
<SelectEpisodeModalContent <SelectTrackModalContent
{...this.props} {...this.props}
onSortPress={this.onSortPress} onSortPress={this.onSortPress}
onEpisodesSelect={this.onEpisodesSelect} onTracksSelect={this.onTracksSelect}
/> />
); );
} }
} }
SelectEpisodeModalContentConnector.propTypes = { SelectTrackModalContentConnector.propTypes = {
id: PropTypes.number.isRequired, id: PropTypes.number.isRequired,
artistId: PropTypes.number.isRequired, artistId: PropTypes.number.isRequired,
seasonNumber: PropTypes.number.isRequired, albumId: PropTypes.number.isRequired,
items: PropTypes.arrayOf(PropTypes.object).isRequired, items: PropTypes.arrayOf(PropTypes.object).isRequired,
fetchEpisodes: PropTypes.func.isRequired, fetchEpisodes: PropTypes.func.isRequired,
setEpisodesSort: PropTypes.func.isRequired, setEpisodesSort: PropTypes.func.isRequired,
@ -100,4 +100,4 @@ export default connectSection(
undefined, undefined,
undefined, undefined,
{ section: 'episodes' } { section: 'episodes' }
)(SelectEpisodeModalContentConnector); )(SelectTrackModalContentConnector);

@ -4,7 +4,7 @@ import TableRowButton from 'Components/Table/TableRowButton';
import TableRowCell from 'Components/Table/Cells/TableRowCell'; import TableRowCell from 'Components/Table/Cells/TableRowCell';
import TableSelectCell from 'Components/Table/Cells/TableSelectCell'; import TableSelectCell from 'Components/Table/Cells/TableSelectCell';
class SelectEpisodeRow extends Component { class SelectTrackRow extends Component {
// //
// Listeners // Listeners
@ -24,9 +24,8 @@ class SelectEpisodeRow extends Component {
render() { render() {
const { const {
id, id,
episodeNumber, trackNumber,
title, title,
airDate,
isSelected, isSelected,
onSelectedChange onSelectedChange
} = this.props; } = this.props;
@ -40,28 +39,24 @@ class SelectEpisodeRow extends Component {
/> />
<TableRowCell> <TableRowCell>
{episodeNumber} {trackNumber}
</TableRowCell> </TableRowCell>
<TableRowCell> <TableRowCell>
{title} {title}
</TableRowCell> </TableRowCell>
<TableRowCell>
{airDate}
</TableRowCell>
</TableRowButton> </TableRowButton>
); );
} }
} }
SelectEpisodeRow.propTypes = { SelectTrackRow.propTypes = {
id: PropTypes.number.isRequired, id: PropTypes.number.isRequired,
episodeNumber: PropTypes.number.isRequired, trackNumber: PropTypes.number.isRequired,
title: PropTypes.string.isRequired, title: PropTypes.string.isRequired,
airDate: PropTypes.string.isRequired,
isSelected: PropTypes.bool, isSelected: PropTypes.bool,
onSelectedChange: PropTypes.func.isRequired onSelectedChange: PropTypes.func.isRequired
}; };
export default SelectEpisodeRow; export default SelectTrackRow;

@ -9,7 +9,7 @@ import { updateItem } from './baseActions';
const section = 'episodes'; const section = 'episodes';
const episodeActionHandlers = { const episodeActionHandlers = {
[types.FETCH_EPISODES]: createFetchHandler(section, '/episode'), [types.FETCH_EPISODES]: createFetchHandler(section, '/track'),
[types.TOGGLE_EPISODE_MONITORED]: function(payload) { [types.TOGGLE_EPISODE_MONITORED]: function(payload) {
return function(dispatch, getState) { return function(dispatch, getState) {

@ -8,10 +8,10 @@ import * as types from './actionTypes';
import { set, removeItem, updateItem } from './baseActions'; import { set, removeItem, updateItem } from './baseActions';
const section = 'episodeFiles'; const section = 'episodeFiles';
const deleteEpisodeFile = createRemoveItemHandler(section, '/episodeFile'); const deleteEpisodeFile = createRemoveItemHandler(section, '/trackFile');
const episodeFileActionHandlers = { const episodeFileActionHandlers = {
[types.FETCH_EPISODE_FILES]: createFetchHandler(section, '/episodeFile'), [types.FETCH_EPISODE_FILES]: createFetchHandler(section, '/trackFile'),
[types.DELETE_EPISODE_FILE]: function(payload) { [types.DELETE_EPISODE_FILE]: function(payload) {
return function(dispatch, getState) { return function(dispatch, getState) {

@ -13,8 +13,8 @@ export default function(history) {
isProduction isProduction
} = window.Sonarr; } = window.Sonarr;
const dsn = isProduction ? 'https://b80ca60625b443c38b242e0d21681eb7@sentry.sonarr.tv/13' : const dsn = isProduction ? 'https://c3a5b33e08de4e18b7d0505e942dbc95:e35e6d535b034995a6896022c6bfed04@sentry.io/216290' :
'https://8dbaacdfe2ff4caf97dc7945aecf9ace@sentry.sonarr.tv/12'; 'https://c3a5b33e08de4e18b7d0505e942dbc95:e35e6d535b034995a6896022c6bfed04@sentry.io/216290';
const middlewares = []; const middlewares = [];

@ -60,11 +60,6 @@ namespace Lidarr.Api.V3.Artist
//public bool SeasonFolder { get; set; } //public bool SeasonFolder { get; set; }
//public bool Monitored { 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? FirstAired { get; set; }
public DateTime? LastInfoSync { get; set; } public DateTime? LastInfoSync { get; set; }
////public SeriesTypes SeriesType { get; set; } ////public SeriesTypes SeriesType { get; set; }
@ -138,10 +133,6 @@ namespace Lidarr.Api.V3.Artist
//AlternateTitles //AlternateTitles
SortName = model.SortName, SortName = model.SortName,
//TotalEpisodeCount
//EpisodeCount
//EpisodeFileCount
//SizeOnDisk
Status = model.Status, Status = model.Status,
Overview = model.Overview, Overview = model.Overview,
//NextAiring //NextAiring
@ -160,12 +151,6 @@ namespace Lidarr.Api.V3.Artist
AlbumFolder = model.AlbumFolder, AlbumFolder = model.AlbumFolder,
Monitored = model.Monitored, Monitored = model.Monitored,
//UseSceneNumbering = model.UseSceneNumbering,
//Runtime = model.Runtime,
//TvdbId = model.TvdbId,
//TvRageId = model.TvRageId,
//TvMazeId = model.TvMazeId,
//FirstAired = model.FirstAired,
LastInfoSync = model.LastInfoSync, LastInfoSync = model.LastInfoSync,
//SeriesType = model.SeriesType, //SeriesType = model.SeriesType,
CleanName = model.CleanName, CleanName = model.CleanName,
@ -193,10 +178,6 @@ namespace Lidarr.Api.V3.Artist
//AlternateTitles //AlternateTitles
SortName = resource.SortName, SortName = resource.SortName,
//TotalEpisodeCount
//EpisodeCount
//EpisodeFileCount
//SizeOnDisk
Status = resource.Status, Status = resource.Status,
Overview = resource.Overview, Overview = resource.Overview,
//NextAiring //NextAiring
@ -215,12 +196,6 @@ namespace Lidarr.Api.V3.Artist
AlbumFolder = resource.AlbumFolder, AlbumFolder = resource.AlbumFolder,
Monitored = resource.Monitored, Monitored = resource.Monitored,
//UseSceneNumbering = resource.UseSceneNumbering,
//Runtime = resource.Runtime,
//TvdbId = resource.TvdbId,
//TvRageId = resource.TvRageId,
//TvMazeId = resource.TvMazeId,
//FirstAired = resource.FirstAired,
LastInfoSync = resource.LastInfoSync, LastInfoSync = resource.LastInfoSync,
//SeriesType = resource.SeriesType, //SeriesType = resource.SeriesType,
CleanName = resource.CleanName, CleanName = resource.CleanName,

@ -3,6 +3,7 @@ using NzbDrone.Core.DecisionEngine;
using NzbDrone.Core.MediaFiles.TrackImport.Manual; using NzbDrone.Core.MediaFiles.TrackImport.Manual;
using NzbDrone.Core.Qualities; using NzbDrone.Core.Qualities;
using Lidarr.Api.V3.Artist; using Lidarr.Api.V3.Artist;
using Lidarr.Api.V3.Albums;
using Lidarr.Api.V3.Tracks; using Lidarr.Api.V3.Tracks;
using Lidarr.Http.REST; using Lidarr.Http.REST;
using System.Collections.Generic; using System.Collections.Generic;
@ -16,9 +17,9 @@ namespace Lidarr.Api.V3.ManualImport
public string RelativePath { get; set; } public string RelativePath { get; set; }
public string Name { get; set; } public string Name { get; set; }
public long Size { get; set; } public long Size { get; set; }
//public ArtistResource Artist { get; set; } public ArtistResource Artist { get; set; }
public int? SeasonNumber { get; set; } public AlbumResource Album { get; set; }
public List<TrackResource> Episodes { get; set; } public List<TrackResource> Tracks { get; set; }
public QualityModel Quality { get; set; } public QualityModel Quality { get; set; }
public int QualityWeight { get; set; } public int QualityWeight { get; set; }
public string DownloadId { get; set; } public string DownloadId { get; set; }
@ -38,9 +39,9 @@ namespace Lidarr.Api.V3.ManualImport
RelativePath = model.RelativePath, RelativePath = model.RelativePath,
Name = model.Name, Name = model.Name,
Size = model.Size, Size = model.Size,
//Artist = model., Artist = model.Artist.ToResource(),
SeasonNumber = model.SeasonNumber, Album = model.Album.ToResource(),
//Episodes = model.Episodes.ToResource(), Tracks = model.Tracks.ToResource(),
Quality = model.Quality, Quality = model.Quality,
//QualityWeight //QualityWeight
DownloadId = model.DownloadId, DownloadId = model.DownloadId,

@ -15,9 +15,6 @@ namespace NzbDrone.Api.ManualImport
public string RelativePath { get; set; } public string RelativePath { get; set; }
public string Name { get; set; } public string Name { get; set; }
public long Size { get; set; } public long Size { get; set; }
public SeriesResource Series { get; set; }
public int? SeasonNumber { get; set; }
public List<EpisodeResource> Episodes { get; set; }
public QualityModel Quality { get; set; } public QualityModel Quality { get; set; }
public int QualityWeight { get; set; } public int QualityWeight { get; set; }
public string DownloadId { get; set; } public string DownloadId { get; set; }
@ -38,9 +35,6 @@ namespace NzbDrone.Api.ManualImport
RelativePath = model.RelativePath, RelativePath = model.RelativePath,
Name = model.Name, Name = model.Name,
Size = model.Size, Size = model.Size,
Series = model.Series.ToResource(),
SeasonNumber = model.SeasonNumber,
Episodes = model.Episodes.ToResource(),
Quality = model.Quality, Quality = model.Quality,
//QualityWeight //QualityWeight
DownloadId = model.DownloadId, DownloadId = model.DownloadId,

@ -84,7 +84,7 @@ namespace NzbDrone.Core.MediaFiles.TrackImport
trackFile.Size = _diskProvider.GetFileSize(localTrack.Path); trackFile.Size = _diskProvider.GetFileSize(localTrack.Path);
trackFile.Quality = localTrack.Quality; trackFile.Quality = localTrack.Quality;
trackFile.MediaInfo = localTrack.MediaInfo; 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.ReleaseGroup = localTrack.ParsedTrackInfo.ReleaseGroup;
trackFile.Tracks = localTrack.Tracks; trackFile.Tracks = localTrack.Tracks;
trackFile.Language = localTrack.Language; trackFile.Language = localTrack.Language;

@ -6,8 +6,9 @@ namespace NzbDrone.Core.MediaFiles.TrackImport.Manual
public class ManualImportFile public class ManualImportFile
{ {
public string Path { get; set; } public string Path { get; set; }
public int SeriesId { get; set; } public int ArtistId { get; set; }
public List<int> EpisodeIds { get; set; } public int AlbumId { get; set; }
public List<int> TrackIds { get; set; }
public QualityModel Quality { get; set; } public QualityModel Quality { get; set; }
public string DownloadId { get; set; } public string DownloadId { get; set; }
} }

@ -1,7 +1,7 @@
using System.Collections.Generic; using System.Collections.Generic;
using NzbDrone.Core.DecisionEngine; using NzbDrone.Core.DecisionEngine;
using NzbDrone.Core.Qualities; using NzbDrone.Core.Qualities;
using NzbDrone.Core.Tv; using NzbDrone.Core.Music;
namespace NzbDrone.Core.MediaFiles.TrackImport.Manual namespace NzbDrone.Core.MediaFiles.TrackImport.Manual
{ {
@ -11,9 +11,9 @@ namespace NzbDrone.Core.MediaFiles.TrackImport.Manual
public string RelativePath { get; set; } public string RelativePath { get; set; }
public string Name { get; set; } public string Name { get; set; }
public long Size { get; set; } public long Size { get; set; }
public Series Series { get; set; } public Artist Artist { get; set; }
public int? SeasonNumber { get; set; } public Album Album { get; set; }
public List<Episode> Episodes { get; set; } public List<Track> Tracks { get; set; }
public QualityModel Quality { get; set; } public QualityModel Quality { get; set; }
public string DownloadId { get; set; } public string DownloadId { get; set; }
public IEnumerable<Rejection> Rejections { get; set; } public IEnumerable<Rejection> Rejections { get; set; }

@ -14,7 +14,7 @@ using NzbDrone.Core.Messaging.Commands;
using NzbDrone.Core.Messaging.Events; using NzbDrone.Core.Messaging.Events;
using NzbDrone.Core.Parser; using NzbDrone.Core.Parser;
using NzbDrone.Core.Parser.Model; using NzbDrone.Core.Parser.Model;
using NzbDrone.Core.Tv; using NzbDrone.Core.Music;
namespace NzbDrone.Core.MediaFiles.TrackImport.Manual namespace NzbDrone.Core.MediaFiles.TrackImport.Manual
{ {
@ -29,10 +29,11 @@ namespace NzbDrone.Core.MediaFiles.TrackImport.Manual
private readonly IParsingService _parsingService; private readonly IParsingService _parsingService;
private readonly IDiskScanService _diskScanService; private readonly IDiskScanService _diskScanService;
private readonly IMakeImportDecision _importDecisionMaker; private readonly IMakeImportDecision _importDecisionMaker;
private readonly ISeriesService _seriesService; private readonly IArtistService _artistService;
private readonly IEpisodeService _episodeService; private readonly IAlbumService _albumService;
private readonly ITrackService _trackService;
private readonly IVideoFileInfoReader _videoFileInfoReader; private readonly IVideoFileInfoReader _videoFileInfoReader;
private readonly IImportApprovedEpisodes _importApprovedEpisodes; private readonly IImportApprovedTracks _importApprovedTracks;
private readonly ITrackedDownloadService _trackedDownloadService; private readonly ITrackedDownloadService _trackedDownloadService;
private readonly IDownloadedEpisodesImportService _downloadedEpisodesImportService; private readonly IDownloadedEpisodesImportService _downloadedEpisodesImportService;
private readonly IEventAggregator _eventAggregator; private readonly IEventAggregator _eventAggregator;
@ -42,10 +43,11 @@ namespace NzbDrone.Core.MediaFiles.TrackImport.Manual
IParsingService parsingService, IParsingService parsingService,
IDiskScanService diskScanService, IDiskScanService diskScanService,
IMakeImportDecision importDecisionMaker, IMakeImportDecision importDecisionMaker,
ISeriesService seriesService, IArtistService artistService,
IEpisodeService episodeService, IAlbumService albumService,
ITrackService trackService,
IVideoFileInfoReader videoFileInfoReader, IVideoFileInfoReader videoFileInfoReader,
IImportApprovedEpisodes importApprovedEpisodes, IImportApprovedTracks importApprovedTracks,
ITrackedDownloadService trackedDownloadService, ITrackedDownloadService trackedDownloadService,
IDownloadedEpisodesImportService downloadedEpisodesImportService, IDownloadedEpisodesImportService downloadedEpisodesImportService,
IEventAggregator eventAggregator, IEventAggregator eventAggregator,
@ -55,10 +57,11 @@ namespace NzbDrone.Core.MediaFiles.TrackImport.Manual
_parsingService = parsingService; _parsingService = parsingService;
_diskScanService = diskScanService; _diskScanService = diskScanService;
_importDecisionMaker = importDecisionMaker; _importDecisionMaker = importDecisionMaker;
_seriesService = seriesService; _artistService = artistService;
_episodeService = episodeService; _albumService = albumService;
_trackService = trackService;
_videoFileInfoReader = videoFileInfoReader; _videoFileInfoReader = videoFileInfoReader;
_importApprovedEpisodes = importApprovedEpisodes; _importApprovedTracks = importApprovedTracks;
_trackedDownloadService = trackedDownloadService; _trackedDownloadService = trackedDownloadService;
_downloadedEpisodesImportService = downloadedEpisodesImportService; _downloadedEpisodesImportService = downloadedEpisodesImportService;
_eventAggregator = eventAggregator; _eventAggregator = eventAggregator;
@ -94,179 +97,176 @@ namespace NzbDrone.Core.MediaFiles.TrackImport.Manual
private List<ManualImportItem> ProcessFolder(string folder, string downloadId) private List<ManualImportItem> ProcessFolder(string folder, string downloadId)
{ {
throw new System.NotImplementedException("TODO: This will be rewritten for Music"); var directoryInfo = new DirectoryInfo(folder);
//var directoryInfo = new DirectoryInfo(folder); var artist = _parsingService.GetArtist(directoryInfo.Name);
//var series = _parsingService.GetSeries(directoryInfo.Name);
//if (series == null && downloadId.IsNotNullOrWhiteSpace()) if (artist == null && downloadId.IsNotNullOrWhiteSpace())
//{ {
// var trackedDownload = _trackedDownloadService.Find(downloadId); var trackedDownload = _trackedDownloadService.Find(downloadId);
// series = trackedDownload.RemoteEpisode.Series; artist = trackedDownload.RemoteAlbum.Artist;
//} }
//if (series == null) if (artist == null)
//{ {
// var files = _diskScanService.GetVideoFiles(folder); 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 folderInfo = Parser.Parser.ParseMusicTitle(directoryInfo.Name);
//var seriesFiles = _diskScanService.GetVideoFiles(folder).ToList(); var artistFiles = _diskScanService.GetAudioFiles(folder).ToList();
//var decisions = _importDecisionMaker.GetImportDecisions(seriesFiles, series, folderInfo, SceneSource(series, folder)); 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) private ManualImportItem ProcessFile(string file, string downloadId, string folder = null)
{ {
throw new System.NotImplementedException("TODO: This will be rewritten for Music"); if (folder.IsNullOrWhiteSpace())
//if (folder.IsNullOrWhiteSpace()) {
//{ folder = new FileInfo(file).Directory.FullName;
// 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) if (artist == null)
//{ {
// series = _parsingService.GetSeries(relativeFile); artist = _parsingService.GetArtistFromTag(file);
//} }
//if (series == null && downloadId.IsNotNullOrWhiteSpace()) if (artist == null && downloadId.IsNotNullOrWhiteSpace())
//{ {
// var trackedDownload = _trackedDownloadService.Find(downloadId); var trackedDownload = _trackedDownloadService.Find(downloadId);
// series = trackedDownload.RemoteEpisode.Series; artist = trackedDownload.RemoteAlbum.Artist;
//} }
//if (series == null) if (artist == null)
//{ {
// var localEpisode = new LocalEpisode(); var localTrack = new LocalTrack();
// localEpisode.Path = file; localTrack.Path = file;
// localEpisode.Quality = QualityParser.ParseQuality(file); localTrack.Quality = QualityParser.ParseQuality(file);
// localEpisode.Size = _diskProvider.GetFileSize(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<string> {file}, var importDecisions = _importDecisionMaker.GetImportDecisions(new List<string> { file },
// series, null, SceneSource(series, folder)); 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) private ManualImportItem MapItem(ImportDecision decision, string folder, string downloadId)
{ {
throw new System.NotImplementedException("TODO: This will be rewritten for Music"); var item = new ManualImportItem();
//var item = new ManualImportItem();
item.Path = decision.LocalTrack.Path;
//item.Path = decision.LocalEpisode.Path; item.RelativePath = folder.GetRelativePath(decision.LocalTrack.Path);
//item.RelativePath = folder.GetRelativePath(decision.LocalEpisode.Path); item.Name = Path.GetFileNameWithoutExtension(decision.LocalTrack.Path);
//item.Name = Path.GetFileNameWithoutExtension(decision.LocalEpisode.Path); item.DownloadId = downloadId;
//item.DownloadId = downloadId;
if (decision.LocalTrack.Artist != null)
//if (decision.LocalEpisode.Series != null) {
//{ item.Artist = decision.LocalTrack.Artist;
// item.Series = decision.LocalEpisode.Series; }
//}
if (decision.LocalTrack.Tracks.Any())
//if (decision.LocalEpisode.Episodes.Any()) {
//{ item.Tracks = decision.LocalTrack.Tracks;
// item.SeasonNumber = decision.LocalEpisode.SeasonNumber; }
// item.Episodes = decision.LocalEpisode.Episodes;
//} item.Quality = decision.LocalTrack.Quality;
item.Size = _diskProvider.GetFileSize(decision.LocalTrack.Path);
//item.Quality = decision.LocalEpisode.Quality; item.Rejections = decision.Rejections;
//item.Size = _diskProvider.GetFileSize(decision.LocalEpisode.Path);
//item.Rejections = decision.Rejections; return item;
//return item;
} }
public void Execute(ManualImportCommand message) public void Execute(ManualImportCommand message)
{ {
_logger.ProgressTrace("Manually importing {0} files using mode {1}", message.Files.Count, message.ImportMode); _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<ImportResult>();
//var imported = new List<ImportResult>(); var importedTrackedDownload = new List<ManuallyImportedFile>();
//var importedTrackedDownload = new List<ManuallyImportedFile>();
for (int i = 0; i < message.Files.Count; i++)
//for (int i = 0; i < message.Files.Count; i++) {
//{ _logger.ProgressTrace("Processing file {0} of {1}", i + 1, message.Files.Count);
// _logger.ProgressTrace("Processing file {0} of {1}", i + 1, message.Files.Count);
var file = message.Files[i];
// var file = message.Files[i]; var artist = _artistService.GetArtist(file.ArtistId);
// var series = _seriesService.GetSeries(file.SeriesId); var album = _albumService.GetAlbum(file.AlbumId);
// var episodes = _episodeService.GetEpisodes(file.EpisodeIds); var tracks = _trackService.GetTracks(file.TrackIds);
// var parsedEpisodeInfo = Parser.Parser.ParsePath(file.Path) ?? new ParsedEpisodeInfo(); var parsedTrackInfo = Parser.Parser.ParseMusicPath(file.Path) ?? new ParsedTrackInfo();
// var mediaInfo = _videoFileInfoReader.GetMediaInfo(file.Path); var mediaInfo = _videoFileInfoReader.GetMediaInfo(file.Path);
// var existingFile = series.Path.IsParentPath(file.Path); var existingFile = artist.Path.IsParentPath(file.Path);
// var localEpisode = new LocalEpisode var localTrack = new LocalTrack
// { {
// ExistingFile = false, ExistingFile = false,
// Episodes = episodes, Tracks = tracks,
// MediaInfo = mediaInfo, MediaInfo = mediaInfo,
// ParsedEpisodeInfo = parsedEpisodeInfo, ParsedTrackInfo = parsedTrackInfo,
// Path = file.Path, Path = file.Path,
// Quality = file.Quality, Quality = file.Quality,
// Series = series, Artist = artist,
// Size = 0 Album = album,
// }; Size = 0
};
// //TODO: Cleanup non-tracked downloads
//TODO: Cleanup non-tracked downloads
// var importDecision = new ImportDecision(localEpisode);
var importDecision = new ImportDecision(localTrack);
// if (file.DownloadId.IsNullOrWhiteSpace())
// { if (file.DownloadId.IsNullOrWhiteSpace())
// imported.AddRange(_importApprovedEpisodes.Import(new List<ImportDecision> { importDecision }, !existingFile, null, message.ImportMode)); {
// } imported.AddRange(_importApprovedTracks.Import(new List<ImportDecision> { importDecision }, !existingFile, null, message.ImportMode));
}
// else
// { else
// var trackedDownload = _trackedDownloadService.Find(file.DownloadId); {
// var importResult = _importApprovedEpisodes.Import(new List<ImportDecision> { importDecision }, true, trackedDownload.DownloadItem, message.ImportMode).First(); var trackedDownload = _trackedDownloadService.Find(file.DownloadId);
var importResult = _importApprovedTracks.Import(new List<ImportDecision> { importDecision }, true, trackedDownload.DownloadItem, message.ImportMode).First();
// imported.Add(importResult);
imported.Add(importResult);
// importedTrackedDownload.Add(new ManuallyImportedFile
// { importedTrackedDownload.Add(new ManuallyImportedFile
// TrackedDownload = trackedDownload, {
// ImportResult = importResult TrackedDownload = trackedDownload,
// }); ImportResult = importResult
// } });
//} }
}
//_logger.ProgressTrace("Manually imported {0} files", imported.Count);
_logger.ProgressTrace("Manually imported {0} files", imported.Count);
//foreach (var groupedTrackedDownload in importedTrackedDownload.GroupBy(i => i.TrackedDownload.DownloadItem.DownloadId).ToList())
//{ foreach (var groupedTrackedDownload in importedTrackedDownload.GroupBy(i => i.TrackedDownload.DownloadItem.DownloadId).ToList())
// var trackedDownload = groupedTrackedDownload.First().TrackedDownload; {
var trackedDownload = groupedTrackedDownload.First().TrackedDownload;
// if (_diskProvider.FolderExists(trackedDownload.DownloadItem.OutputPath.FullPath))
// { if (_diskProvider.FolderExists(trackedDownload.DownloadItem.OutputPath.FullPath))
// if (_downloadedEpisodesImportService.ShouldDeleteFolder( {
// new DirectoryInfo(trackedDownload.DownloadItem.OutputPath.FullPath), if (_downloadedEpisodesImportService.ShouldDeleteFolder(
// trackedDownload.RemoteEpisode.Series) && !trackedDownload.DownloadItem.IsReadOnly) new DirectoryInfo(trackedDownload.DownloadItem.OutputPath.FullPath),
// { trackedDownload.RemoteEpisode.Series) && !trackedDownload.DownloadItem.IsReadOnly)
// _diskProvider.DeleteFolder(trackedDownload.DownloadItem.OutputPath.FullPath, true); {
// } _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))
// { 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)); trackedDownload.State = TrackedDownloadStage.Imported;
// } _eventAggregator.PublishEvent(new DownloadCompletedEvent(trackedDownload));
//} }
}
} }
} }
} }

@ -15,6 +15,9 @@ namespace NzbDrone.Core.Parser.Model
public string ArtistTitle { get; set; } public string ArtistTitle { get; set; }
public string AlbumTitle { get; set; } public string AlbumTitle { get; set; }
public ArtistTitleInfo ArtistTitleInfo { 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 QualityModel Quality { get; set; }
public int[] TrackNumbers { get; set; } public int[] TrackNumbers { get; set; }
public Language Language { get; set; } public Language Language { get; set; }
@ -26,21 +29,18 @@ namespace NzbDrone.Core.Parser.Model
TrackNumbers = new int[0]; TrackNumbers = new int[0];
} }
public override string ToString() public override string ToString()
{ {
string episodeString = "[Unknown Track]"; string trackString = "[Unknown Track]";
if (TrackNumbers != null && TrackNumbers.Any()) 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);
} }
} }
} }

@ -336,9 +336,16 @@ namespace NzbDrone.Core.Parser
var trackName = file.Tag.Title; var trackName = file.Tag.Title;
var trackNumber = file.Tag.Track; var trackNumber = file.Tag.Track;
var artist = file.Tag.FirstAlbumArtist;
if (artist.IsNotNullOrWhiteSpace())
{
artist = file.Tag.FirstPerformer;
}
var artistTitleInfo = new ArtistTitleInfo var artistTitleInfo = new ArtistTitleInfo
{ {
Title = file.Tag.Title, Title = artist,
Year = (int)file.Tag.Year Year = (int)file.Tag.Year
}; };
@ -348,7 +355,10 @@ namespace NzbDrone.Core.Parser
{ {
Language = Language.English, //TODO Parse from Tag/Mediainfo Language = Language.English, //TODO Parse from Tag/Mediainfo
AlbumTitle = file.Tag.Album, 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), Quality = QualityParser.ParseQuality(trackName),
TrackNumbers = temp, TrackNumbers = temp,
ArtistTitleInfo = artistTitleInfo, ArtistTitleInfo = artistTitleInfo,

@ -18,6 +18,8 @@ namespace NzbDrone.Core.Parser
LocalEpisode GetLocalEpisode(string filename, Series series); LocalEpisode GetLocalEpisode(string filename, Series series);
LocalEpisode GetLocalEpisode(string filename, Series series, ParsedEpisodeInfo folderInfo, bool sceneSource); LocalEpisode GetLocalEpisode(string filename, Series series, ParsedEpisodeInfo folderInfo, bool sceneSource);
Series GetSeries(string title); 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 tvdbId, int tvRageId, SearchCriteriaBase searchCriteria = null);
RemoteEpisode Map(ParsedEpisodeInfo parsedEpisodeInfo, int seriesId, IEnumerable<int> episodeIds); RemoteEpisode Map(ParsedEpisodeInfo parsedEpisodeInfo, int seriesId, IEnumerable<int> episodeIds);
RemoteAlbum Map(ParsedAlbumInfo parsedAlbumInfo, SearchCriteriaBase searchCriteria = null); RemoteAlbum Map(ParsedAlbumInfo parsedAlbumInfo, SearchCriteriaBase searchCriteria = null);
@ -137,6 +139,44 @@ namespace NzbDrone.Core.Parser
return series; 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")] [System.Obsolete("Used for sonarr, not lidarr")]
public RemoteEpisode Map(ParsedEpisodeInfo parsedEpisodeInfo, int tvdbId, int tvRageId, SearchCriteriaBase searchCriteria = null) 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 tracks = GetTracks(parsedTrackInfo, artist);
var album = _albumService.FindByTitle(artist.Id, parsedTrackInfo.AlbumTitle);
return new LocalTrack return new LocalTrack
{ {
Artist = artist, Artist = artist,
Album = album,
Quality = parsedTrackInfo.Quality, Quality = parsedTrackInfo.Quality,
Language = parsedTrackInfo.Language, Language = parsedTrackInfo.Language,
Tracks = tracks, Tracks = tracks,

Loading…
Cancel
Save