Cleanup Search UI, Newznab Caps API

pull/2/head
Qstick 4 years ago
parent f290afa68c
commit 84cbfe870f

@ -5,7 +5,7 @@ import NotFound from 'Components/NotFound';
import Switch from 'Components/Router/Switch';
import HistoryConnector from 'History/HistoryConnector';
import IndexerIndexConnector from 'Indexer/Index/IndexerIndexConnector';
import SearchConnector from 'Search/SearchConnector';
import SearchIndexConnector from 'Search/SearchIndexConnector';
import GeneralSettingsConnector from 'Settings/General/GeneralSettingsConnector';
import IndexerSettingsConnector from 'Settings/Indexers/IndexerSettingsConnector';
import NotificationSettings from 'Settings/Notifications/NotificationSettings';
@ -65,7 +65,7 @@ function AppRoutes(props) {
<Route
path="/search"
component={SearchConnector}
component={SearchIndexConnector}
/>
{/*

@ -5,7 +5,6 @@ import RelativeDateCellConnector from 'Components/Table/Cells/RelativeDateCellCo
import TableRowCell from 'Components/Table/Cells/TableRowCell';
import TableRow from 'Components/Table/TableRow';
import { icons } from 'Helpers/Props';
import MovieFormats from 'Indexer/MovieFormats';
import MovieLanguage from 'Indexer/MovieLanguage';
import MovieQuality from 'Indexer/MovieQuality';
import MovieTitleLink from 'Indexer/MovieTitleLink';
@ -54,7 +53,6 @@ class HistoryRow extends Component {
const {
movie,
quality,
customFormats,
languages,
qualityCutoffNotMet,
eventType,
@ -127,16 +125,6 @@ class HistoryRow extends Component {
);
}
if (name === 'customFormats') {
return (
<TableRowCell key={name}>
<MovieFormats
formats={customFormats}
/>
</TableRowCell>
);
}
if (name === 'date') {
return (
<RelativeDateCellConnector
@ -220,7 +208,6 @@ HistoryRow.propTypes = {
languages: PropTypes.arrayOf(PropTypes.object).isRequired,
quality: PropTypes.object.isRequired,
qualityCutoffNotMet: PropTypes.bool.isRequired,
customFormats: PropTypes.arrayOf(PropTypes.object).isRequired,
eventType: PropTypes.string.isRequired,
sourceTitle: PropTypes.string.isRequired,
date: PropTypes.string.isRequired,

@ -8,7 +8,6 @@ import RelativeDateCellConnector from 'Components/Table/Cells/RelativeDateCellCo
import TableRowCell from 'Components/Table/Cells/TableRowCell';
import TableRow from 'Components/Table/TableRow';
import { icons, kinds } from 'Helpers/Props';
import MovieFormats from 'Indexer/MovieFormats';
import MovieLanguage from 'Indexer/MovieLanguage';
import MovieQuality from 'Indexer/MovieQuality';
import translate from 'Utilities/String/translate';
@ -60,7 +59,6 @@ class MovieHistoryRow extends Component {
eventType,
sourceTitle,
quality,
customFormats,
languages,
qualityCutoffNotMet,
date,
@ -99,12 +97,6 @@ class MovieHistoryRow extends Component {
/>
</TableRowCell>
<TableRowCell key={name}>
<MovieFormats
formats={customFormats}
/>
</TableRowCell>
<RelativeDateCellConnector
date={date}
/>
@ -157,7 +149,6 @@ MovieHistoryRow.propTypes = {
sourceTitle: PropTypes.string.isRequired,
languages: PropTypes.arrayOf(PropTypes.object).isRequired,
quality: PropTypes.object.isRequired,
customFormats: PropTypes.arrayOf(PropTypes.object).isRequired,
qualityCutoffNotMet: PropTypes.bool.isRequired,
date: PropTypes.string.isRequired,
data: PropTypes.object.isRequired,

@ -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;

@ -4,7 +4,7 @@ import FilterMenu from 'Components/Menu/FilterMenu';
import { align } from 'Helpers/Props';
import MovieIndexFilterModalConnector from 'Indexer/Index/MovieIndexFilterModalConnector';
function MovieIndexFilterMenu(props) {
function SearchIndexFilterMenu(props) {
const {
selectedFilterKey,
filters,
@ -26,7 +26,7 @@ function MovieIndexFilterMenu(props) {
);
}
MovieIndexFilterMenu.propTypes = {
SearchIndexFilterMenu.propTypes = {
selectedFilterKey: PropTypes.oneOfType([PropTypes.string, PropTypes.number]).isRequired,
filters: PropTypes.arrayOf(PropTypes.object).isRequired,
customFilters: PropTypes.arrayOf(PropTypes.object).isRequired,
@ -34,8 +34,8 @@ MovieIndexFilterMenu.propTypes = {
onFilterSelect: PropTypes.func.isRequired
};
MovieIndexFilterMenu.defaultProps = {
SearchIndexFilterMenu.defaultProps = {
showCustomFilters: false
};
export default MovieIndexFilterMenu;
export default SearchIndexFilterMenu;

@ -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;

@ -3,9 +3,9 @@ import React, { Component } from 'react';
import TextInput from 'Components/Form/TextInput';
import SpinnerButton from 'Components/Link/SpinnerButton';
import PageContentFooter from 'Components/Page/PageContentFooter';
import styles from './MovieEditorFooter.css';
import styles from './SearchFooter.css';
class MovieEditorFooter extends Component {
class SearchFooter extends Component {
//
// Lifecycle
@ -87,10 +87,10 @@ class MovieEditorFooter extends Component {
}
}
MovieEditorFooter.propTypes = {
SearchFooter.propTypes = {
isFetching: PropTypes.bool.isRequired,
onSearchPress: PropTypes.func.isRequired,
searchError: PropTypes.object
};
export default MovieEditorFooter;
export default SearchFooter;

@ -16,14 +16,14 @@ import * as keyCodes from 'Utilities/Constants/keyCodes';
import getErrorMessage from 'Utilities/Object/getErrorMessage';
import hasDifferentItemsOrOrder from 'Utilities/Object/hasDifferentItemsOrOrder';
import translate from 'Utilities/String/translate';
import MovieIndexFilterMenu from './Menus/MovieIndexFilterMenu';
import MovieIndexSortMenu from './Menus/MovieIndexSortMenu';
import MovieEditorFooter from './MovieEditorFooter.js';
import MovieIndexTableConnector from './Table/MovieIndexTableConnector';
import styles from './IndexerIndex.css';
import SearchIndexFilterMenu from './Menus/SearchIndexFilterMenu';
import SearchIndexSortMenu from './Menus/SearchIndexSortMenu';
import SearchFooter from './SearchFooter.js';
import SearchIndexTableConnector from './Table/SearchIndexTableConnector';
import styles from './SearchIndex.css';
function getViewComponent() {
return MovieIndexTableConnector;
return SearchIndexTableConnector;
}
class SearchIndex extends Component {
@ -194,14 +194,14 @@ class SearchIndex extends Component {
<PageToolbarSeparator />
<MovieIndexSortMenu
<SearchIndexSortMenu
sortKey={sortKey}
sortDirection={sortDirection}
isDisabled={hasNoIndexer}
onSortSelect={onSortSelect}
/>
<MovieIndexFilterMenu
<SearchIndexFilterMenu
selectedFilterKey={selectedFilterKey}
filters={filters}
customFilters={customFilters}
@ -260,7 +260,7 @@ class SearchIndex extends Component {
}
</div>
<MovieEditorFooter
<SearchFooter
isFetching={isFetching}
onSearchPress={this.onSearchPress}
/>

@ -45,7 +45,7 @@ function createMapDispatchToProps(dispatch, props) {
};
}
class SearchConnector extends Component {
class SearchIndexConnector extends Component {
onScroll = ({ scrollTop }) => {
scrollPositions.movieIndex = scrollTop;
@ -64,13 +64,13 @@ class SearchConnector extends Component {
}
}
SearchConnector.propTypes = {
SearchIndexConnector.propTypes = {
isSmallScreen: PropTypes.bool.isRequired,
onSearchPress: PropTypes.func.isRequired,
items: PropTypes.arrayOf(PropTypes.object)
};
export default withScrollPosition(
connect(createMapStateToProps, createMapDispatchToProps)(SearchConnector),
connect(createMapStateToProps, createMapDispatchToProps)(SearchIndexConnector),
'releases'
);

@ -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;

@ -5,9 +5,9 @@ import TableOptionsModal from 'Components/Table/TableOptions/TableOptionsModal';
import VirtualTableHeader from 'Components/Table/VirtualTableHeader';
import VirtualTableHeaderCell from 'Components/Table/VirtualTableHeaderCell';
import { icons } from 'Helpers/Props';
import styles from './MovieIndexHeader.css';
import styles from './SearchIndexHeader.css';
class MovieIndexHeader extends Component {
class SearchIndexHeader extends Component {
//
// Lifecycle
@ -98,9 +98,9 @@ class MovieIndexHeader extends Component {
}
}
MovieIndexHeader.propTypes = {
SearchIndexHeader.propTypes = {
columns: PropTypes.arrayOf(PropTypes.object).isRequired,
onTableOptionChange: PropTypes.func.isRequired
};
export default MovieIndexHeader;
export default SearchIndexHeader;

@ -1,6 +1,6 @@
import { connect } from 'react-redux';
import { setMovieTableOption } from 'Store/Actions/indexerIndexActions';
import MovieIndexHeader from './MovieIndexHeader';
import SearchIndexHeader from './SearchIndexHeader';
function createMapDispatchToProps(dispatch, props) {
return {
@ -10,4 +10,4 @@ function createMapDispatchToProps(dispatch, props) {
};
}
export default connect(undefined, createMapDispatchToProps)(MovieIndexHeader);
export default connect(undefined, createMapDispatchToProps)(SearchIndexHeader);

@ -12,9 +12,9 @@ import formatBytes from 'Utilities/Number/formatBytes';
import translate from 'Utilities/String/translate';
import Peers from './Peers';
import ProtocolLabel from './ProtocolLabel';
import styles from './MovieIndexRow.css';
import styles from './SearchIndexRow.css';
class MovieIndexRow extends Component {
class SearchIndexRow extends Component {
//
// Render
@ -190,7 +190,7 @@ class MovieIndexRow extends Component {
}
}
MovieIndexRow.propTypes = {
SearchIndexRow.propTypes = {
guid: PropTypes.string.isRequired,
protocol: PropTypes.string.isRequired,
age: PropTypes.number.isRequired,
@ -210,4 +210,4 @@ MovieIndexRow.propTypes = {
timeFormat: PropTypes.string.isRequired
};
export default MovieIndexRow;
export default SearchIndexRow;

@ -4,12 +4,12 @@ import VirtualTable from 'Components/Table/VirtualTable';
import VirtualTableRow from 'Components/Table/VirtualTableRow';
import { sortDirections } from 'Helpers/Props';
import getIndexOfFirstCharacter from 'Utilities/Array/getIndexOfFirstCharacter';
import MovieIndexHeaderConnector from './MovieIndexHeaderConnector';
import MovieIndexItemConnector from './MovieIndexItemConnector';
import MovieIndexRow from './MovieIndexRow';
import styles from './MovieIndexTable.css';
import SearchIndexHeaderConnector from './SearchIndexHeaderConnector';
import SearchIndexItemConnector from './SearchIndexItemConnector';
import SearchIndexRow from './SearchIndexRow';
import styles from './SearchIndexTable.css';
class MovieIndexTable extends Component {
class SearchIndexTable extends Component {
//
// Lifecycle
@ -58,9 +58,9 @@ class MovieIndexTable extends Component {
key={key}
style={style}
>
<MovieIndexItemConnector
<SearchIndexItemConnector
key={release.guid}
component={MovieIndexRow}
component={SearchIndexRow}
columns={columns}
guid={release.guid}
longDateFormat={longDateFormat}
@ -95,7 +95,7 @@ class MovieIndexTable extends Component {
overscanRowCount={2}
rowRenderer={this.rowRenderer}
header={
<MovieIndexHeaderConnector
<SearchIndexHeaderConnector
columns={columns}
sortKey={sortKey}
sortDirection={sortDirection}
@ -108,7 +108,7 @@ class MovieIndexTable extends Component {
}
}
MovieIndexTable.propTypes = {
SearchIndexTable.propTypes = {
items: PropTypes.arrayOf(PropTypes.object).isRequired,
columns: PropTypes.arrayOf(PropTypes.object).isRequired,
sortKey: PropTypes.string,
@ -121,4 +121,4 @@ MovieIndexTable.propTypes = {
onSortPress: PropTypes.func.isRequired
};
export default MovieIndexTable;
export default SearchIndexTable;

@ -2,7 +2,7 @@ import { connect } from 'react-redux';
import { createSelector } from 'reselect';
import { setMovieSort } from 'Store/Actions/indexerIndexActions';
import createUISettingsSelector from 'Store/Selectors/createUISettingsSelector';
import MovieIndexTable from './MovieIndexTable';
import SearchIndexTable from './SearchIndexTable';
function createMapStateToProps() {
return createSelector(
@ -28,4 +28,4 @@ function createMapDispatchToProps(dispatch, props) {
};
}
export default connect(createMapStateToProps, createMapDispatchToProps)(MovieIndexTable);
export default connect(createMapStateToProps, createMapDispatchToProps)(SearchIndexTable);

@ -66,13 +66,13 @@ namespace NzbDrone.Common.Instrumentation
if (updateClient)
{
dsn = "https://d62a0313c35f4afc932b4a20e1072793@sentry.servarr.com/27";
dsn = "https://67e12d21f1c745da97f89dd32c1d783e@sentry.servarr.com/29";
}
else
{
dsn = RuntimeInfo.IsProduction
? "https://d62a0313c35f4afc932b4a20e1072793@sentry.servarr.com/27"
: "https://d62a0313c35f4afc932b4a20e1072793@sentry.servarr.com/27";
: "https://e38306161ff945999adf774a16e933c3@sentry.servarr.com/30";
}
var target = new SentryTarget(dsn)

@ -30,7 +30,7 @@ namespace NzbDrone.Core.Analytics
{
get
{
var lastRecord = _historyService.Paged(new PagingSpec<MovieHistory>() { Page = 0, PageSize = 1, SortKey = "date", SortDirection = SortDirection.Descending });
var lastRecord = _historyService.Paged(new PagingSpec<History.History>() { Page = 0, PageSize = 1, SortKey = "date", SortDirection = SortDirection.Descending });
var monthAgo = DateTime.UtcNow.AddMonths(-1);
return lastRecord.Records.Any(v => v.Date > monthAgo);

@ -45,17 +45,14 @@ namespace NzbDrone.Core.Datastore
.Ignore(i => i.Privacy)
.Ignore(i => i.SupportsRss)
.Ignore(i => i.SupportsSearch)
.Ignore(i => i.SupportsBooks)
.Ignore(i => i.SupportsMusic)
.Ignore(i => i.SupportsMovies)
.Ignore(i => i.SupportsTv)
.Ignore(i => i.Capabilities)
.Ignore(d => d.Tags);
Mapper.Entity<NotificationDefinition>("Notifications").RegisterModel()
.Ignore(x => x.ImplementationName)
.Ignore(i => i.SupportsOnHealthIssue);
Mapper.Entity<MovieHistory>("History").RegisterModel();
Mapper.Entity<History.History>("History").RegisterModel();
Mapper.Entity<Log>("Logs").RegisterModel();

@ -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
}
}

@ -6,47 +6,47 @@ using NzbDrone.Core.Messaging.Events;
namespace NzbDrone.Core.History
{
public interface IHistoryRepository : IBasicRepository<MovieHistory>
public interface IHistoryRepository : IBasicRepository<History>
{
MovieHistory MostRecentForDownloadId(string downloadId);
List<MovieHistory> FindByDownloadId(string downloadId);
List<MovieHistory> FindDownloadHistory(int movieId);
List<MovieHistory> GetByMovieId(int movieId, MovieHistoryEventType? eventType);
void DeleteForMovies(List<int> movieIds);
MovieHistory MostRecentForMovie(int movieId);
List<MovieHistory> Since(DateTime date, MovieHistoryEventType? eventType);
History MostRecentForDownloadId(string downloadId);
List<History> FindByDownloadId(string downloadId);
List<History> FindDownloadHistory(int indexerId);
List<History> GetByIndexerId(int indexerId, HistoryEventType? eventType);
void DeleteForIndexers(List<int> indexerIds);
History MostRecentForIndexer(int indexerId);
List<History> Since(DateTime date, HistoryEventType? eventType);
}
public class HistoryRepository : BasicRepository<MovieHistory>, IHistoryRepository
public class HistoryRepository : BasicRepository<History>, IHistoryRepository
{
public HistoryRepository(IMainDatabase database, IEventAggregator eventAggregator)
: base(database, eventAggregator)
{
}
public MovieHistory MostRecentForDownloadId(string downloadId)
public History MostRecentForDownloadId(string downloadId)
{
return FindByDownloadId(downloadId)
.OrderByDescending(h => h.Date)
.FirstOrDefault();
}
public List<MovieHistory> FindByDownloadId(string downloadId)
public List<History> FindByDownloadId(string downloadId)
{
return Query(x => x.DownloadId == downloadId);
}
public List<MovieHistory> FindDownloadHistory(int movieId)
public List<History> FindDownloadHistory(int indexerId)
{
var allowed = new[] { MovieHistoryEventType.Grabbed, MovieHistoryEventType.DownloadFailed, MovieHistoryEventType.DownloadFolderImported };
var allowed = new[] { HistoryEventType.ReleaseGrabbed };
return Query(h => h.MovieId == movieId &&
return Query(h => h.IndexerId == indexerId &&
allowed.Contains(h.EventType));
}
public List<MovieHistory> GetByMovieId(int movieId, MovieHistoryEventType? eventType)
public List<History> GetByIndexerId(int indexerId, HistoryEventType? eventType)
{
var query = Query(x => x.MovieId == movieId);
var query = Query(x => x.IndexerId == indexerId);
if (eventType.HasValue)
{
@ -56,25 +56,25 @@ namespace NzbDrone.Core.History
return query.OrderByDescending(h => h.Date).ToList();
}
public void DeleteForMovies(List<int> movieIds)
public void DeleteForIndexers(List<int> indexerIds)
{
Delete(c => movieIds.Contains(c.MovieId));
Delete(c => indexerIds.Contains(c.IndexerId));
}
public MovieHistory MostRecentForMovie(int movieId)
public History MostRecentForIndexer(int indexerId)
{
return Query(x => x.MovieId == movieId)
return Query(x => x.IndexerId == indexerId)
.OrderByDescending(h => h.Date)
.FirstOrDefault();
}
public List<MovieHistory> Since(DateTime date, MovieHistoryEventType? eventType)
public List<History> Since(DateTime date, HistoryEventType? eventType)
{
var builder = Builder().Where<MovieHistory>(x => x.Date >= date);
var builder = Builder().Where<History>(x => x.Date >= date);
if (eventType.HasValue)
{
builder.Where<MovieHistory>(h => h.EventType == eventType);
builder.Where<History>(h => h.EventType == eventType);
}
return Query(builder).OrderBy(h => h.Date).ToList();

@ -8,15 +8,15 @@ namespace NzbDrone.Core.History
{
public interface IHistoryService
{
PagingSpec<MovieHistory> Paged(PagingSpec<MovieHistory> pagingSpec);
MovieHistory MostRecentForMovie(int movieId);
MovieHistory MostRecentForDownloadId(string downloadId);
MovieHistory Get(int historyId);
List<MovieHistory> Find(string downloadId, MovieHistoryEventType eventType);
List<MovieHistory> FindByDownloadId(string downloadId);
List<MovieHistory> GetByMovieId(int movieId, MovieHistoryEventType? eventType);
void UpdateMany(List<MovieHistory> toUpdate);
List<MovieHistory> Since(DateTime date, MovieHistoryEventType? eventType);
PagingSpec<History> Paged(PagingSpec<History> pagingSpec);
History MostRecentForIndexer(int indexerId);
History MostRecentForDownloadId(string downloadId);
History Get(int historyId);
List<History> Find(string downloadId, HistoryEventType eventType);
List<History> FindByDownloadId(string downloadId);
List<History> GetByIndexerId(int indexerId, HistoryEventType? eventType);
void UpdateMany(List<History> toUpdate);
List<History> Since(DateTime date, HistoryEventType? eventType);
}
public class HistoryService : IHistoryService
@ -30,47 +30,47 @@ namespace NzbDrone.Core.History
_logger = logger;
}
public PagingSpec<MovieHistory> Paged(PagingSpec<MovieHistory> pagingSpec)
public PagingSpec<History> Paged(PagingSpec<History> pagingSpec)
{
return _historyRepository.GetPaged(pagingSpec);
}
public MovieHistory MostRecentForMovie(int movieId)
public History MostRecentForIndexer(int indexerId)
{
return _historyRepository.MostRecentForMovie(movieId);
return _historyRepository.MostRecentForIndexer(indexerId);
}
public MovieHistory MostRecentForDownloadId(string downloadId)
public History MostRecentForDownloadId(string downloadId)
{
return _historyRepository.MostRecentForDownloadId(downloadId);
}
public MovieHistory Get(int historyId)
public History Get(int historyId)
{
return _historyRepository.Get(historyId);
}
public List<MovieHistory> Find(string downloadId, MovieHistoryEventType eventType)
public List<History> Find(string downloadId, HistoryEventType eventType)
{
return _historyRepository.FindByDownloadId(downloadId).Where(c => c.EventType == eventType).ToList();
}
public List<MovieHistory> FindByDownloadId(string downloadId)
public List<History> FindByDownloadId(string downloadId)
{
return _historyRepository.FindByDownloadId(downloadId);
}
public List<MovieHistory> GetByMovieId(int movieId, MovieHistoryEventType? eventType)
public List<History> GetByIndexerId(int indexerId, HistoryEventType? eventType)
{
return _historyRepository.GetByMovieId(movieId, eventType);
return _historyRepository.GetByIndexerId(indexerId, eventType);
}
public void UpdateMany(List<MovieHistory> toUpdate)
public void UpdateMany(List<History> toUpdate)
{
_historyRepository.UpdateMany(toUpdate);
}
public List<MovieHistory> Since(DateTime date, MovieHistoryEventType? eventType)
public List<History> Since(DateTime date, HistoryEventType? eventType)
{
return _historyRepository.Since(date, eventType);
}

@ -14,19 +14,19 @@ namespace NzbDrone.Core.Housekeeping.Housekeepers
public void Clean()
{
CleanupOrphanedByMovie();
CleanupOrphanedByIndexer();
}
private void CleanupOrphanedByMovie()
private void CleanupOrphanedByIndexer()
{
using (var mapper = _database.OpenConnection())
{
mapper.Execute(@"DELETE FROM History
WHERE Id IN (
SELECT History.Id FROM History
LEFT OUTER JOIN Movies
ON History.MovieId = Movies.Id
WHERE Movies.Id IS NULL)");
LEFT OUTER JOIN Indexers
ON History.IndexerId = Indexers.Id
WHERE Indexers.Id IS NULL)");
}
}
}

@ -2,6 +2,7 @@ using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using NLog;
using NzbDrone.Common.Extensions;
using NzbDrone.Common.Instrumentation.Extensions;
using NzbDrone.Common.TPL;
using NzbDrone.Core.Indexers;
@ -56,7 +57,7 @@ namespace NzbDrone.Core.IndexerSearch
var reports = new List<ReleaseInfo>();
_logger.ProgressInfo("Searching {0} indexers for {1}", indexers.Count, criteriaBase);
_logger.ProgressInfo("Searching {0} indexers for {1}", indexers.Count, criteriaBase.QueryTitles.Join(", "));
var taskList = new List<Task>();
var taskFactory = new TaskFactory(TaskCreationOptions.LongRunning, TaskContinuationOptions.None);

@ -75,11 +75,7 @@ namespace NzbDrone.Core.Indexers.Newznab
Protocol = DownloadProtocol.Usenet,
Privacy = IndexerPrivacy.Private,
SupportsRss = SupportsRss,
SupportsSearch = SupportsSearch,
SupportsBooks = SupportsBooks,
SupportsMovies = SupportsMovies,
SupportsMusic = SupportsMusic,
SupportsTv = SupportsTv
SupportsSearch = SupportsSearch
};
}

@ -1,3 +1,4 @@
using System.Collections.Generic;
using NLog;
using NzbDrone.Common.Cache;
using NzbDrone.Common.Http;
@ -12,10 +13,15 @@ namespace NzbDrone.Core.Indexers.PassThePopcorn
public override IndexerPrivacy Privacy => IndexerPrivacy.Private;
public override bool SupportsRss => true;
public override bool SupportsSearch => true;
public override bool SupportsMusic => false;
public override bool SupportsTv => false;
public override bool SupportsMovies => true;
public override bool SupportsBooks => false;
public override IndexerCapabilities Capabilities => new IndexerCapabilities
{
MovieSearchParams = new List<MovieSearchParam>
{
MovieSearchParam.Q, MovieSearchParam.ImdbId
}
};
public override int PageSize => 50;
public PassThePopcorn(IHttpClient httpClient,

@ -23,10 +23,7 @@ namespace NzbDrone.Core.Indexers
public override bool SupportsRss => true;
public override bool SupportsSearch => true;
public override bool SupportsMusic => true;
public override bool SupportsTv => true;
public override bool SupportsMovies => true;
public override bool SupportsBooks => true;
public override IndexerCapabilities Capabilities => new IndexerCapabilities();
public bool SupportsPaging => PageSize > 0;

@ -9,10 +9,7 @@ namespace NzbDrone.Core.Indexers
{
bool SupportsRss { get; }
bool SupportsSearch { get; }
bool SupportsMusic { get; }
bool SupportsTv { get; }
bool SupportsMovies { get; }
bool SupportsBooks { get; }
IndexerCapabilities Capabilities { get; }
DownloadProtocol Protocol { get; }
IndexerPrivacy Privacy { get; }

@ -25,11 +25,7 @@ namespace NzbDrone.Core.Indexers
public abstract bool SupportsRss { get; }
public abstract bool SupportsSearch { get; }
public abstract bool SupportsMusic { get; }
public abstract bool SupportsTv { get; }
public abstract bool SupportsMovies { get; }
public abstract bool SupportsBooks { get; }
public abstract IndexerCapabilities Capabilities { get; }
public IndexerBase(IIndexerStatusService indexerStatusService, IConfigService configService, Logger logger)
{

@ -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;
}
}

@ -12,10 +12,7 @@ namespace NzbDrone.Core.Indexers
public IndexerPrivacy Privacy { get; set; }
public bool SupportsRss { get; set; }
public bool SupportsSearch { get; set; }
public bool SupportsMusic { get; set; }
public bool SupportsTv { get; set; }
public bool SupportsMovies { get; set; }
public bool SupportsBooks { get; set; }
public IndexerCapabilities Capabilities { get; set; }
public int Priority { get; set; } = 25;
public DateTime Added { get; set; }

@ -45,11 +45,7 @@ namespace NzbDrone.Core.Indexers
definition.Protocol = provider.Protocol;
definition.Privacy = provider.Privacy;
definition.SupportsRss = provider.SupportsRss;
definition.SupportsSearch = provider.SupportsSearch;
definition.SupportsBooks = provider.SupportsBooks;
definition.SupportsMovies = provider.SupportsMovies;
definition.SupportsMusic = provider.SupportsMusic;
definition.SupportsTv = provider.SupportsTv;
definition.Capabilities = provider.Capabilities;
}
public List<IIndexer> RssEnabled(bool filterBlockedIndexers = true)

Some files were not shown because too many files have changed in this diff Show More

Loading…
Cancel
Save