parent
f290afa68c
commit
84cbfe870f
@ -1,63 +0,0 @@
|
||||
import PropTypes from 'prop-types';
|
||||
import React, { Component } from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import { createSelector } from 'reselect';
|
||||
import createIndexerSelector from 'Store/Selectors/createIndexerSelector';
|
||||
import createMovieCollectionListSelector from 'Store/Selectors/createMovieCollectionListSelector';
|
||||
import MovieCollection from './MovieCollection';
|
||||
|
||||
function createMapStateToProps() {
|
||||
return createSelector(
|
||||
createIndexerSelector(),
|
||||
createMovieCollectionListSelector(),
|
||||
(movie, collectionList) => {
|
||||
const {
|
||||
monitored,
|
||||
qualityProfileId,
|
||||
minimumAvailability
|
||||
} = movie;
|
||||
|
||||
return {
|
||||
collectionList,
|
||||
monitored,
|
||||
qualityProfileId,
|
||||
minimumAvailability
|
||||
};
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
class MovieCollectionConnector extends Component {
|
||||
|
||||
//
|
||||
// Listeners
|
||||
|
||||
onMonitorTogglePress = (monitored) => {
|
||||
|
||||
}
|
||||
|
||||
//
|
||||
// Render
|
||||
|
||||
render() {
|
||||
return (
|
||||
<MovieCollection
|
||||
{...this.props}
|
||||
onMonitorTogglePress={this.onMonitorTogglePress}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
MovieCollectionConnector.propTypes = {
|
||||
tmdbId: PropTypes.number.isRequired,
|
||||
movieId: PropTypes.number.isRequired,
|
||||
name: PropTypes.string.isRequired,
|
||||
collectionList: PropTypes.object,
|
||||
monitored: PropTypes.bool.isRequired,
|
||||
qualityProfileId: PropTypes.number.isRequired,
|
||||
minimumAvailability: PropTypes.string.isRequired,
|
||||
isSaving: PropTypes.bool.isRequired
|
||||
};
|
||||
|
||||
export default connect(createMapStateToProps)(MovieCollectionConnector);
|
@ -1,4 +0,0 @@
|
||||
.center {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
}
|
@ -1,99 +0,0 @@
|
||||
import PropTypes from 'prop-types';
|
||||
import React from 'react';
|
||||
import Label from 'Components/Label';
|
||||
import { kinds } from 'Helpers/Props';
|
||||
import MovieQuality from 'Indexer/MovieQuality';
|
||||
import getQueueStatusText from 'Utilities/Movie/getQueueStatusText';
|
||||
import translate from 'Utilities/String/translate';
|
||||
import styles from './MovieFileStatus.css';
|
||||
|
||||
function MovieFileStatus(props) {
|
||||
const {
|
||||
isAvailable,
|
||||
monitored,
|
||||
movieFile,
|
||||
queueStatus,
|
||||
queueState
|
||||
} = props;
|
||||
|
||||
const hasMovieFile = !!movieFile;
|
||||
const hasReleased = isAvailable;
|
||||
|
||||
if (queueStatus) {
|
||||
const queueStatusText = getQueueStatusText(queueStatus, queueState);
|
||||
|
||||
return (
|
||||
<div className={styles.center}>
|
||||
<Label
|
||||
title={queueStatusText}
|
||||
kind={kinds.QUEUE}
|
||||
>
|
||||
{queueStatusText}
|
||||
</Label>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
if (hasMovieFile) {
|
||||
const quality = movieFile.quality;
|
||||
|
||||
return (
|
||||
<div className={styles.center}>
|
||||
<MovieQuality
|
||||
title={quality.quality.name}
|
||||
size={movieFile.size}
|
||||
quality={quality}
|
||||
isMonitored={monitored}
|
||||
isCutoffNotMet={movieFile.qualityCutoffNotMet}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
if (!monitored) {
|
||||
return (
|
||||
<div className={styles.center}>
|
||||
<Label
|
||||
title={translate('NotMonitored')}
|
||||
kind={kinds.WARNING}
|
||||
>
|
||||
{translate('NotMonitored')}
|
||||
</Label>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
if (hasReleased) {
|
||||
return (
|
||||
<div className={styles.center}>
|
||||
<Label
|
||||
title={translate('MovieAvailableButMissing')}
|
||||
kind={kinds.DANGER}
|
||||
>
|
||||
{translate('Missing')}
|
||||
</Label>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={styles.center}>
|
||||
<Label
|
||||
title={translate('NotAvailable')}
|
||||
kind={kinds.INFO}
|
||||
>
|
||||
{translate('NotAvailable')}
|
||||
</Label>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
MovieFileStatus.propTypes = {
|
||||
isAvailable: PropTypes.bool,
|
||||
monitored: PropTypes.bool.isRequired,
|
||||
movieFile: PropTypes.object,
|
||||
queueStatus: PropTypes.string,
|
||||
queueState: PropTypes.string
|
||||
};
|
||||
|
||||
export default MovieFileStatus;
|
@ -1,46 +0,0 @@
|
||||
import PropTypes from 'prop-types';
|
||||
import React, { Component } from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import { createSelector } from 'reselect';
|
||||
import createIndexerSelector from 'Store/Selectors/createIndexerSelector';
|
||||
import MovieFileStatus from './MovieFileStatus';
|
||||
|
||||
function createMapStateToProps() {
|
||||
return createSelector(
|
||||
createIndexerSelector(),
|
||||
(movie) => {
|
||||
return {
|
||||
inCinemas: movie.inCinemas,
|
||||
isAvailable: movie.isAvailable,
|
||||
monitored: movie.monitored,
|
||||
grabbed: movie.grabbed,
|
||||
movieFile: movie.movieFile
|
||||
};
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
const mapDispatchToProps = {
|
||||
};
|
||||
|
||||
class MovieFileStatusConnector extends Component {
|
||||
|
||||
//
|
||||
// Render
|
||||
|
||||
render() {
|
||||
return (
|
||||
<MovieFileStatus
|
||||
{...this.props}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
MovieFileStatusConnector.propTypes = {
|
||||
movieId: PropTypes.number.isRequired,
|
||||
queueStatus: PropTypes.string,
|
||||
queueState: PropTypes.string
|
||||
};
|
||||
|
||||
export default connect(createMapStateToProps, mapDispatchToProps)(MovieFileStatusConnector);
|
@ -1,33 +0,0 @@
|
||||
import PropTypes from 'prop-types';
|
||||
import React from 'react';
|
||||
import Label from 'Components/Label';
|
||||
import { kinds } from 'Helpers/Props';
|
||||
|
||||
function MovieFormats({ formats }) {
|
||||
return (
|
||||
<div>
|
||||
{
|
||||
formats.map((format) => {
|
||||
return (
|
||||
<Label
|
||||
key={format.id}
|
||||
kind={kinds.INFO}
|
||||
>
|
||||
{format.name}
|
||||
</Label>
|
||||
);
|
||||
})
|
||||
}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
MovieFormats.propTypes = {
|
||||
formats: PropTypes.arrayOf(PropTypes.object).isRequired
|
||||
};
|
||||
|
||||
MovieFormats.defaultProps = {
|
||||
formats: []
|
||||
};
|
||||
|
||||
export default MovieFormats;
|
@ -1,9 +0,0 @@
|
||||
export const CALENDAR = 'calendar';
|
||||
export const MOVIES = 'movies';
|
||||
export const INTERACTIVE_IMPORT = 'interactiveImport.movies';
|
||||
|
||||
export default {
|
||||
CALENDAR,
|
||||
MOVIES,
|
||||
INTERACTIVE_IMPORT
|
||||
};
|
@ -1,52 +0,0 @@
|
||||
import PropTypes from 'prop-types';
|
||||
import React, { Component } from 'react';
|
||||
import Menu from 'Components/Menu/Menu';
|
||||
import MenuContent from 'Components/Menu/MenuContent';
|
||||
import SearchMenuItem from 'Components/Menu/SearchMenuItem';
|
||||
import ToolbarMenuButton from 'Components/Menu/ToolbarMenuButton';
|
||||
import { align, icons } from 'Helpers/Props';
|
||||
|
||||
class MovieIndexSearchMenu extends Component {
|
||||
|
||||
render() {
|
||||
const {
|
||||
isDisabled,
|
||||
onSearchPress
|
||||
} = this.props;
|
||||
|
||||
return (
|
||||
<Menu
|
||||
isDisabled={isDisabled}
|
||||
alignMenu={align.RIGHT}
|
||||
>
|
||||
<ToolbarMenuButton
|
||||
iconName={icons.SEARCH}
|
||||
text="Search"
|
||||
isDisabled={isDisabled}
|
||||
/>
|
||||
<MenuContent>
|
||||
<SearchMenuItem
|
||||
name="missingMoviesSearch"
|
||||
onPress={onSearchPress}
|
||||
>
|
||||
Search Missing
|
||||
</SearchMenuItem>
|
||||
|
||||
<SearchMenuItem
|
||||
name="cutoffUnmetMoviesSearch"
|
||||
onPress={onSearchPress}
|
||||
>
|
||||
Search Cutoff Unmet
|
||||
</SearchMenuItem>
|
||||
</MenuContent>
|
||||
</Menu>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
MovieIndexSearchMenu.propTypes = {
|
||||
isDisabled: PropTypes.bool.isRequired,
|
||||
onSearchPress: PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
export default MovieIndexSearchMenu;
|
@ -1,142 +0,0 @@
|
||||
import PropTypes from 'prop-types';
|
||||
import React from 'react';
|
||||
import MenuContent from 'Components/Menu/MenuContent';
|
||||
import SortMenu from 'Components/Menu/SortMenu';
|
||||
import SortMenuItem from 'Components/Menu/SortMenuItem';
|
||||
import { align, sortDirections } from 'Helpers/Props';
|
||||
import translate from 'Utilities/String/translate';
|
||||
|
||||
function MovieIndexSortMenu(props) {
|
||||
const {
|
||||
sortKey,
|
||||
sortDirection,
|
||||
isDisabled,
|
||||
onSortSelect
|
||||
} = props;
|
||||
|
||||
return (
|
||||
<SortMenu
|
||||
isDisabled={isDisabled}
|
||||
alignMenu={align.RIGHT}
|
||||
>
|
||||
<MenuContent>
|
||||
<SortMenuItem
|
||||
name="status"
|
||||
sortKey={sortKey}
|
||||
sortDirection={sortDirection}
|
||||
onPress={onSortSelect}
|
||||
>
|
||||
Monitored/Status
|
||||
</SortMenuItem>
|
||||
|
||||
<SortMenuItem
|
||||
name="sortTitle"
|
||||
sortKey={sortKey}
|
||||
sortDirection={sortDirection}
|
||||
onPress={onSortSelect}
|
||||
>
|
||||
{translate('Title')}
|
||||
</SortMenuItem>
|
||||
|
||||
<SortMenuItem
|
||||
name="studio"
|
||||
sortKey={sortKey}
|
||||
sortDirection={sortDirection}
|
||||
onPress={onSortSelect}
|
||||
>
|
||||
{translate('Studio')}
|
||||
</SortMenuItem>
|
||||
|
||||
<SortMenuItem
|
||||
name="qualityProfileId"
|
||||
sortKey={sortKey}
|
||||
sortDirection={sortDirection}
|
||||
onPress={onSortSelect}
|
||||
>
|
||||
{translate('QualityProfile')}
|
||||
</SortMenuItem>
|
||||
|
||||
<SortMenuItem
|
||||
name="added"
|
||||
sortKey={sortKey}
|
||||
sortDirection={sortDirection}
|
||||
onPress={onSortSelect}
|
||||
>
|
||||
{translate('Added')}
|
||||
</SortMenuItem>
|
||||
|
||||
<SortMenuItem
|
||||
name="year"
|
||||
sortKey={sortKey}
|
||||
sortDirection={sortDirection}
|
||||
onPress={onSortSelect}
|
||||
>
|
||||
{translate('Year')}
|
||||
</SortMenuItem>
|
||||
|
||||
<SortMenuItem
|
||||
name="inCinemas"
|
||||
sortKey={sortKey}
|
||||
sortDirection={sortDirection}
|
||||
onPress={onSortSelect}
|
||||
>
|
||||
{translate('InCinemas')}
|
||||
</SortMenuItem>
|
||||
|
||||
<SortMenuItem
|
||||
name="physicalRelease"
|
||||
sortKey={sortKey}
|
||||
sortDirection={sortDirection}
|
||||
onPress={onSortSelect}
|
||||
>
|
||||
{translate('PhysicalRelease')}
|
||||
</SortMenuItem>
|
||||
|
||||
<SortMenuItem
|
||||
name="digitalRelease"
|
||||
sortKey={sortKey}
|
||||
sortDirection={sortDirection}
|
||||
onPress={onSortSelect}
|
||||
>
|
||||
{translate('DigitalRelease')}
|
||||
</SortMenuItem>
|
||||
|
||||
<SortMenuItem
|
||||
name="path"
|
||||
sortKey={sortKey}
|
||||
sortDirection={sortDirection}
|
||||
onPress={onSortSelect}
|
||||
>
|
||||
{translate('Path')}
|
||||
</SortMenuItem>
|
||||
|
||||
<SortMenuItem
|
||||
name="sizeOnDisk"
|
||||
sortKey={sortKey}
|
||||
sortDirection={sortDirection}
|
||||
onPress={onSortSelect}
|
||||
>
|
||||
{translate('SizeOnDisk')}
|
||||
</SortMenuItem>
|
||||
|
||||
<SortMenuItem
|
||||
name="certification"
|
||||
sortKey={sortKey}
|
||||
sortDirection={sortDirection}
|
||||
onPress={onSortSelect}
|
||||
>
|
||||
{translate('Certification')}
|
||||
</SortMenuItem>
|
||||
</MenuContent>
|
||||
</SortMenu>
|
||||
);
|
||||
}
|
||||
|
||||
MovieIndexSortMenu.propTypes = {
|
||||
sortKey: PropTypes.string,
|
||||
sortDirection: PropTypes.oneOf(sortDirections.all),
|
||||
isDisabled: PropTypes.bool.isRequired,
|
||||
onSortSelect: PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
export default MovieIndexSortMenu;
|
@ -0,0 +1,52 @@
|
||||
import PropTypes from 'prop-types';
|
||||
import React from 'react';
|
||||
import MenuContent from 'Components/Menu/MenuContent';
|
||||
import SortMenu from 'Components/Menu/SortMenu';
|
||||
import SortMenuItem from 'Components/Menu/SortMenuItem';
|
||||
import { align, sortDirections } from 'Helpers/Props';
|
||||
import translate from 'Utilities/String/translate';
|
||||
|
||||
function SearchIndexSortMenu(props) {
|
||||
const {
|
||||
sortKey,
|
||||
sortDirection,
|
||||
isDisabled,
|
||||
onSortSelect
|
||||
} = props;
|
||||
|
||||
return (
|
||||
<SortMenu
|
||||
isDisabled={isDisabled}
|
||||
alignMenu={align.RIGHT}
|
||||
>
|
||||
<MenuContent>
|
||||
<SortMenuItem
|
||||
name="status"
|
||||
sortKey={sortKey}
|
||||
sortDirection={sortDirection}
|
||||
onPress={onSortSelect}
|
||||
>
|
||||
Monitored/Status
|
||||
</SortMenuItem>
|
||||
|
||||
<SortMenuItem
|
||||
name="sortTitle"
|
||||
sortKey={sortKey}
|
||||
sortDirection={sortDirection}
|
||||
onPress={onSortSelect}
|
||||
>
|
||||
{translate('Title')}
|
||||
</SortMenuItem>
|
||||
</MenuContent>
|
||||
</SortMenu>
|
||||
);
|
||||
}
|
||||
|
||||
SearchIndexSortMenu.propTypes = {
|
||||
sortKey: PropTypes.string,
|
||||
sortDirection: PropTypes.oneOf(sortDirections.all),
|
||||
isDisabled: PropTypes.bool.isRequired,
|
||||
onSortSelect: PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
export default SearchIndexSortMenu;
|
@ -1,8 +0,0 @@
|
||||
.label {
|
||||
margin-bottom: 3px;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.savingIcon {
|
||||
margin-left: 8px;
|
||||
}
|
@ -1,40 +0,0 @@
|
||||
import PropTypes from 'prop-types';
|
||||
import React from 'react';
|
||||
import SpinnerIcon from 'Components/SpinnerIcon';
|
||||
import { icons } from 'Helpers/Props';
|
||||
import styles from './MovieEditorFooterLabel.css';
|
||||
|
||||
function MovieEditorFooterLabel(props) {
|
||||
const {
|
||||
className,
|
||||
label,
|
||||
isSaving
|
||||
} = props;
|
||||
|
||||
return (
|
||||
<div className={className}>
|
||||
{label}
|
||||
|
||||
{
|
||||
isSaving &&
|
||||
<SpinnerIcon
|
||||
className={styles.savingIcon}
|
||||
name={icons.SPINNER}
|
||||
isSpinning={true}
|
||||
/>
|
||||
}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
MovieEditorFooterLabel.propTypes = {
|
||||
className: PropTypes.string.isRequired,
|
||||
label: PropTypes.string.isRequired,
|
||||
isSaving: PropTypes.bool.isRequired
|
||||
};
|
||||
|
||||
MovieEditorFooterLabel.defaultProps = {
|
||||
className: styles.label
|
||||
};
|
||||
|
||||
export default MovieEditorFooterLabel;
|
@ -1,103 +0,0 @@
|
||||
import PropTypes from 'prop-types';
|
||||
import React, { Component } from 'react';
|
||||
import IconButton from 'Components/Link/IconButton';
|
||||
import SpinnerIconButton from 'Components/Link/SpinnerIconButton';
|
||||
import VirtualTableRowCell from 'Components/Table/Cells/VirtualTableRowCell';
|
||||
import { icons } from 'Helpers/Props';
|
||||
import DeleteMovieModal from 'Indexer/Delete/DeleteMovieModal';
|
||||
import EditMovieModalConnector from 'Indexer/Edit/EditMovieModalConnector';
|
||||
import translate from 'Utilities/String/translate';
|
||||
|
||||
class MovieIndexActionsCell extends Component {
|
||||
|
||||
//
|
||||
// Lifecycle
|
||||
|
||||
constructor(props, context) {
|
||||
super(props, context);
|
||||
|
||||
this.state = {
|
||||
isEditMovieModalOpen: false,
|
||||
isDeleteMovieModalOpen: false
|
||||
};
|
||||
}
|
||||
|
||||
//
|
||||
// Listeners
|
||||
|
||||
onEditMoviePress = () => {
|
||||
this.setState({ isEditMovieModalOpen: true });
|
||||
}
|
||||
|
||||
onEditMovieModalClose = () => {
|
||||
this.setState({ isEditMovieModalOpen: false });
|
||||
}
|
||||
|
||||
onDeleteMoviePress = () => {
|
||||
this.setState({
|
||||
isEditMovieModalOpen: false,
|
||||
isDeleteMovieModalOpen: true
|
||||
});
|
||||
}
|
||||
|
||||
onDeleteMovieModalClose = () => {
|
||||
this.setState({ isDeleteMovieModalOpen: false });
|
||||
}
|
||||
|
||||
//
|
||||
// Render
|
||||
|
||||
render() {
|
||||
const {
|
||||
id,
|
||||
isRefreshingMovie,
|
||||
onRefreshMoviePress,
|
||||
...otherProps
|
||||
} = this.props;
|
||||
|
||||
const {
|
||||
isEditMovieModalOpen,
|
||||
isDeleteMovieModalOpen
|
||||
} = this.state;
|
||||
|
||||
return (
|
||||
<VirtualTableRowCell
|
||||
{...otherProps}
|
||||
>
|
||||
<SpinnerIconButton
|
||||
name={icons.REFRESH}
|
||||
title={translate('RefreshMovie')}
|
||||
isSpinning={isRefreshingMovie}
|
||||
onPress={onRefreshMoviePress}
|
||||
/>
|
||||
|
||||
<IconButton
|
||||
name={icons.EDIT}
|
||||
title={translate('EditMovie')}
|
||||
onPress={this.onEditMoviePress}
|
||||
/>
|
||||
|
||||
<EditMovieModalConnector
|
||||
isOpen={isEditMovieModalOpen}
|
||||
movieId={id}
|
||||
onModalClose={this.onEditMovieModalClose}
|
||||
onDeleteMoviePress={this.onDeleteMoviePress}
|
||||
/>
|
||||
|
||||
<DeleteMovieModal
|
||||
isOpen={isDeleteMovieModalOpen}
|
||||
movieId={id}
|
||||
onModalClose={this.onDeleteMovieModalClose}
|
||||
/>
|
||||
</VirtualTableRowCell>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
MovieIndexActionsCell.propTypes = {
|
||||
id: PropTypes.number.isRequired,
|
||||
isRefreshingMovie: PropTypes.bool.isRequired,
|
||||
onRefreshMoviePress: PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
export default MovieIndexActionsCell;
|
@ -1,42 +1,28 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using NzbDrone.Core.Datastore;
|
||||
using NzbDrone.Core.Languages;
|
||||
|
||||
namespace NzbDrone.Core.History
|
||||
{
|
||||
public class MovieHistory : ModelBase
|
||||
public class History : ModelBase
|
||||
{
|
||||
public const string DOWNLOAD_CLIENT = "downloadClient";
|
||||
|
||||
public MovieHistory()
|
||||
public History()
|
||||
{
|
||||
Data = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
|
||||
}
|
||||
|
||||
public int MovieId { get; set; }
|
||||
public int IndexerId { get; set; }
|
||||
public string SourceTitle { get; set; }
|
||||
public DateTime Date { get; set; }
|
||||
public MovieHistoryEventType EventType { get; set; }
|
||||
public HistoryEventType EventType { get; set; }
|
||||
public Dictionary<string, string> Data { get; set; }
|
||||
public List<Language> Languages { get; set; }
|
||||
|
||||
public string DownloadId { get; set; }
|
||||
}
|
||||
|
||||
public enum MovieHistoryEventType
|
||||
public enum HistoryEventType
|
||||
{
|
||||
Unknown = 0,
|
||||
Grabbed = 1,
|
||||
|
||||
// SeriesFolderImported = 2, // deprecated
|
||||
DownloadFolderImported = 3,
|
||||
DownloadFailed = 4,
|
||||
|
||||
// EpisodeFileDeleted = 5, // deprecated
|
||||
MovieFileDeleted = 6,
|
||||
MovieFolderImported = 7, // not used yet
|
||||
MovieFileRenamed = 8,
|
||||
DownloadIgnored = 9
|
||||
ReleaseGrabbed = 1
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,308 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace NzbDrone.Core.Indexers
|
||||
{
|
||||
public enum TvSearchParam
|
||||
{
|
||||
Q,
|
||||
Season,
|
||||
Ep,
|
||||
ImdbId,
|
||||
TvdbId,
|
||||
RId,
|
||||
}
|
||||
|
||||
public enum MovieSearchParam
|
||||
{
|
||||
Q,
|
||||
ImdbId,
|
||||
TmdbId
|
||||
}
|
||||
|
||||
public enum MusicSearchParam
|
||||
{
|
||||
Q,
|
||||
Album,
|
||||
Artist,
|
||||
Label,
|
||||
Year
|
||||
}
|
||||
|
||||
public class IndexerCapabilities
|
||||
{
|
||||
public int? LimitsMax { get; set; }
|
||||
public int? LimitsDefault { get; set; }
|
||||
|
||||
public bool SearchAvailable { get; set; }
|
||||
|
||||
public List<TvSearchParam> TvSearchParams;
|
||||
public bool TvSearchAvailable => TvSearchParams.Count > 0;
|
||||
public bool TvSearchSeasonAvailable => TvSearchParams.Contains(TvSearchParam.Season);
|
||||
public bool TvSearchEpAvailable => TvSearchParams.Contains(TvSearchParam.Ep);
|
||||
public bool TvSearchImdbAvailable => TvSearchParams.Contains(TvSearchParam.ImdbId);
|
||||
public bool TvSearchTvdbAvailable => TvSearchParams.Contains(TvSearchParam.TvdbId);
|
||||
public bool TvSearchTvRageAvailable => TvSearchParams.Contains(TvSearchParam.RId);
|
||||
|
||||
public List<MovieSearchParam> MovieSearchParams;
|
||||
public bool MovieSearchAvailable => MovieSearchParams.Count > 0;
|
||||
public bool MovieSearchImdbAvailable => MovieSearchParams.Contains(MovieSearchParam.ImdbId);
|
||||
public bool MovieSearchTmdbAvailable => MovieSearchParams.Contains(MovieSearchParam.TmdbId);
|
||||
|
||||
public List<MusicSearchParam> MusicSearchParams;
|
||||
public bool MusicSearchAvailable => MusicSearchParams.Count > 0;
|
||||
public bool MusicSearchAlbumAvailable => MusicSearchParams.Contains(MusicSearchParam.Album);
|
||||
public bool MusicSearchArtistAvailable => MusicSearchParams.Contains(MusicSearchParam.Artist);
|
||||
public bool MusicSearchLabelAvailable => MusicSearchParams.Contains(MusicSearchParam.Label);
|
||||
public bool MusicSearchYearAvailable => MusicSearchParams.Contains(MusicSearchParam.Year);
|
||||
|
||||
public bool BookSearchAvailable { get; set; }
|
||||
|
||||
public List<IndexerCategory> Categories { get; private set; }
|
||||
|
||||
public IndexerCapabilities()
|
||||
{
|
||||
SearchAvailable = true;
|
||||
TvSearchParams = new List<TvSearchParam>();
|
||||
MovieSearchParams = new List<MovieSearchParam>();
|
||||
MusicSearchParams = new List<MusicSearchParam>();
|
||||
BookSearchAvailable = false;
|
||||
Categories = new List<IndexerCategory>();
|
||||
}
|
||||
|
||||
public void ParseTvSearchParams(IEnumerable<string> paramsList)
|
||||
{
|
||||
if (paramsList == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
foreach (var paramStr in paramsList)
|
||||
{
|
||||
if (Enum.TryParse(paramStr, true, out TvSearchParam param))
|
||||
{
|
||||
if (!TvSearchParams.Contains(param))
|
||||
{
|
||||
TvSearchParams.Add(param);
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new Exception($"Duplicate tv-search param: {paramStr}");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new Exception($"Not supported tv-search param: {paramStr}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void ParseMovieSearchParams(IEnumerable<string> paramsList)
|
||||
{
|
||||
if (paramsList == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
foreach (var paramStr in paramsList)
|
||||
{
|
||||
if (Enum.TryParse(paramStr, true, out MovieSearchParam param))
|
||||
{
|
||||
if (!MovieSearchParams.Contains(param))
|
||||
{
|
||||
MovieSearchParams.Add(param);
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new Exception($"Duplicate movie-search param: {paramStr}");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new Exception($"Not supported movie-search param: {paramStr}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void ParseMusicSearchParams(IEnumerable<string> paramsList)
|
||||
{
|
||||
if (paramsList == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
foreach (var paramStr in paramsList)
|
||||
{
|
||||
if (Enum.TryParse(paramStr, true, out MusicSearchParam param))
|
||||
{
|
||||
if (!MusicSearchParams.Contains(param))
|
||||
{
|
||||
MusicSearchParams.Add(param);
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new Exception($"Duplicate music-search param: {paramStr}");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new Exception($"Not supported Music-search param: {paramStr}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private string SupportedTvSearchParams()
|
||||
{
|
||||
var parameters = new List<string> { "q" }; // q is always enabled
|
||||
if (TvSearchSeasonAvailable)
|
||||
{
|
||||
parameters.Add("season");
|
||||
}
|
||||
|
||||
if (TvSearchEpAvailable)
|
||||
{
|
||||
parameters.Add("ep");
|
||||
}
|
||||
|
||||
if (TvSearchImdbAvailable)
|
||||
{
|
||||
parameters.Add("imdbid");
|
||||
}
|
||||
|
||||
if (TvSearchTvdbAvailable)
|
||||
{
|
||||
parameters.Add("tvdbid");
|
||||
}
|
||||
|
||||
if (TvSearchTvRageAvailable)
|
||||
{
|
||||
parameters.Add("rid");
|
||||
}
|
||||
|
||||
return string.Join(",", parameters);
|
||||
}
|
||||
|
||||
private string SupportedMovieSearchParams()
|
||||
{
|
||||
var parameters = new List<string> { "q" }; // q is always enabled
|
||||
if (MovieSearchImdbAvailable)
|
||||
{
|
||||
parameters.Add("imdbid");
|
||||
}
|
||||
|
||||
if (MovieSearchTmdbAvailable)
|
||||
{
|
||||
parameters.Add("tmdbid");
|
||||
}
|
||||
|
||||
return string.Join(",", parameters);
|
||||
}
|
||||
|
||||
private string SupportedMusicSearchParams()
|
||||
{
|
||||
var parameters = new List<string> { "q" }; // q is always enabled
|
||||
if (MusicSearchAlbumAvailable)
|
||||
{
|
||||
parameters.Add("album");
|
||||
}
|
||||
|
||||
if (MusicSearchArtistAvailable)
|
||||
{
|
||||
parameters.Add("artist");
|
||||
}
|
||||
|
||||
if (MusicSearchLabelAvailable)
|
||||
{
|
||||
parameters.Add("label");
|
||||
}
|
||||
|
||||
if (MusicSearchYearAvailable)
|
||||
{
|
||||
parameters.Add("year");
|
||||
}
|
||||
|
||||
return string.Join(",", parameters);
|
||||
}
|
||||
|
||||
private string SupportedBookSearchParams
|
||||
{
|
||||
get
|
||||
{
|
||||
var parameters = new List<string>() { "q" };
|
||||
if (BookSearchAvailable)
|
||||
{
|
||||
parameters.Add("author,title");
|
||||
}
|
||||
|
||||
return string.Join(",", parameters);
|
||||
}
|
||||
}
|
||||
|
||||
public bool SupportsCategories(int[] categories)
|
||||
{
|
||||
var subCategories = Categories.SelectMany(c => c.SubCategories);
|
||||
var allCategories = Categories.Concat(subCategories);
|
||||
var supportsCategory = allCategories.Any(i => categories.Any(c => c == i.ID));
|
||||
return supportsCategory;
|
||||
}
|
||||
|
||||
public XDocument GetXDocument()
|
||||
{
|
||||
var xdoc = new XDocument(
|
||||
new XDeclaration("1.0", "UTF-8", null),
|
||||
new XElement("caps",
|
||||
new XElement("server",
|
||||
new XAttribute("title", "Prowlarr")),
|
||||
LimitsMax != null || LimitsDefault != null ?
|
||||
new XElement("limits",
|
||||
LimitsMax != null ? new XAttribute("max", LimitsMax) : null,
|
||||
LimitsDefault != null ? new XAttribute("default", LimitsDefault) : null)
|
||||
: null,
|
||||
new XElement("searching",
|
||||
new XElement("search",
|
||||
new XAttribute("available", SearchAvailable ? "yes" : "no"),
|
||||
new XAttribute("supportedParams", "q")),
|
||||
new XElement("tv-search",
|
||||
new XAttribute("available", TvSearchAvailable ? "yes" : "no"),
|
||||
new XAttribute("supportedParams", SupportedTvSearchParams())),
|
||||
new XElement("movie-search",
|
||||
new XAttribute("available", MovieSearchAvailable ? "yes" : "no"),
|
||||
new XAttribute("supportedParams", SupportedMovieSearchParams())),
|
||||
new XElement("music-search",
|
||||
new XAttribute("available", MusicSearchAvailable ? "yes" : "no"),
|
||||
new XAttribute("supportedParams", SupportedMusicSearchParams())),
|
||||
new XElement("audio-search",
|
||||
new XAttribute("available", MusicSearchAvailable ? "yes" : "no"),
|
||||
new XAttribute("supportedParams", SupportedMusicSearchParams())),
|
||||
new XElement("book-search",
|
||||
new XAttribute("available", BookSearchAvailable ? "yes" : "no"),
|
||||
new XAttribute("supportedParams", SupportedBookSearchParams))),
|
||||
new XElement("categories",
|
||||
from c in Categories.OrderBy(x => x.ID < 100000 ? "z" + x.ID.ToString() : x.Name)
|
||||
select new XElement("category",
|
||||
new XAttribute("id", c.ID),
|
||||
new XAttribute("name", c.Name),
|
||||
from sc in c.SubCategories
|
||||
select new XElement("subcat",
|
||||
new XAttribute("id", sc.ID),
|
||||
new XAttribute("name", sc.Name))))));
|
||||
return xdoc;
|
||||
}
|
||||
|
||||
public string ToXml() =>
|
||||
GetXDocument().Declaration + Environment.NewLine + GetXDocument();
|
||||
|
||||
public static IndexerCapabilities Concat(IndexerCapabilities left, IndexerCapabilities right)
|
||||
{
|
||||
left.SearchAvailable = left.SearchAvailable || right.SearchAvailable;
|
||||
left.TvSearchParams = left.TvSearchParams.Union(right.TvSearchParams).ToList();
|
||||
left.MovieSearchParams = left.MovieSearchParams.Union(right.MovieSearchParams).ToList();
|
||||
left.MusicSearchParams = left.MusicSearchParams.Union(right.MusicSearchParams).ToList();
|
||||
left.BookSearchAvailable = left.BookSearchAvailable || right.BookSearchAvailable;
|
||||
left.Categories.AddRange(right.Categories.Where(x => x.ID < 100000).Except(left.Categories)); // exclude indexer specific categories (>= 100000)
|
||||
return left;
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,38 @@
|
||||
using System.Collections.Generic;
|
||||
using Newtonsoft.Json.Linq;
|
||||
|
||||
namespace NzbDrone.Core.Indexers
|
||||
{
|
||||
public class IndexerCategory
|
||||
{
|
||||
public int ID { get; set; }
|
||||
public string Name { get; set; }
|
||||
|
||||
public List<IndexerCategory> SubCategories { get; private set; }
|
||||
|
||||
public IndexerCategory() => SubCategories = new List<IndexerCategory>();
|
||||
|
||||
public IndexerCategory(int id, string name)
|
||||
{
|
||||
ID = id;
|
||||
Name = name;
|
||||
SubCategories = new List<IndexerCategory>();
|
||||
}
|
||||
|
||||
public bool Contains(IndexerCategory cat) =>
|
||||
Equals(this, cat) || SubCategories.Contains(cat);
|
||||
|
||||
public JToken ToJson() =>
|
||||
new JObject
|
||||
{
|
||||
["ID"] = ID,
|
||||
["Name"] = Name
|
||||
};
|
||||
|
||||
public override bool Equals(object obj) => (obj as IndexerCategory)?.ID == ID;
|
||||
|
||||
// Get Hash code should be calculated off read only properties.
|
||||
// ID is not readonly
|
||||
public override int GetHashCode() => ID;
|
||||
}
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in new issue