mirror of
https://github.com/Prowlarr/Prowlarr.git
synced 2025-09-17 17:14:18 +02:00
Cleanup Search UI, Newznab Caps API
This commit is contained in:
@@ -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;
|
52
frontend/src/Search/Menus/SearchIndexSortMenu.js
Normal file
52
frontend/src/Search/Menus/SearchIndexSortMenu.js
Normal file
@@ -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)
|
||||
{
|
||||
|
308
src/NzbDrone.Core/Indexers/IndexerCapabilities.cs
Normal file
308
src/NzbDrone.Core/Indexers/IndexerCapabilities.cs
Normal file
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
38
src/NzbDrone.Core/Indexers/IndexerCategory.cs
Normal file
38
src/NzbDrone.Core/Indexers/IndexerCategory.cs
Normal file
@@ -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
Reference in New Issue
Block a user