New: Set Indexer flags in Manual Import

pull/9806/head
Bogdan 1 year ago
parent 25ab396a2c
commit 9dd31be7b3

@ -282,6 +282,7 @@ FormInputGroup.propTypes = {
includeNoChange: PropTypes.bool, includeNoChange: PropTypes.bool,
includeNoChangeDisabled: PropTypes.bool, includeNoChangeDisabled: PropTypes.bool,
selectedValueOptions: PropTypes.object, selectedValueOptions: PropTypes.object,
indexerFlags: PropTypes.number,
pending: PropTypes.bool, pending: PropTypes.bool,
errors: PropTypes.arrayOf(PropTypes.object), errors: PropTypes.arrayOf(PropTypes.object),
warnings: PropTypes.arrayOf(PropTypes.object), warnings: PropTypes.arrayOf(PropTypes.object),

@ -4,22 +4,18 @@ import { createSelector } from 'reselect';
import AppState from 'App/State/AppState'; import AppState from 'App/State/AppState';
import EnhancedSelectInput from './EnhancedSelectInput'; import EnhancedSelectInput from './EnhancedSelectInput';
interface IndexerFlagsSelectInputProps {
name: string;
indexerFlags: number;
onChange(payload: object): void;
}
const selectIndexerFlagsValues = (selectedFlags: number) => const selectIndexerFlagsValues = (selectedFlags: number) =>
createSelector( createSelector(
(state: AppState) => state.settings.indexerFlags, (state: AppState) => state.settings.indexerFlags,
(indexerFlags) => { (indexerFlags) => {
const value = indexerFlags.items const value = indexerFlags.items.reduce((acc: number[], { id }) => {
.filter( // eslint-disable-next-line no-bitwise
// eslint-disable-next-line no-bitwise if ((selectedFlags & id) === id) {
(item) => (selectedFlags & item.id) === item.id acc.push(id);
) }
.map(({ id }) => id);
return acc;
}, []);
const values = indexerFlags.items.map(({ id, name }) => ({ const values = indexerFlags.items.map(({ id, name }) => ({
key: id, key: id,
@ -33,6 +29,12 @@ const selectIndexerFlagsValues = (selectedFlags: number) =>
} }
); );
interface IndexerFlagsSelectInputProps {
name: string;
indexerFlags: number;
onChange(payload: object): void;
}
function IndexerFlagsSelectInput(props: IndexerFlagsSelectInputProps) { function IndexerFlagsSelectInput(props: IndexerFlagsSelectInputProps) {
const { indexerFlags, onChange } = props; const { indexerFlags, onChange } = props;

@ -0,0 +1,34 @@
import React from 'react';
import Modal from 'Components/Modal/Modal';
import SelectIndexerFlagsModalContent from './SelectIndexerFlagsModalContent';
interface SelectIndexerFlagsModalProps {
isOpen: boolean;
indexerFlags: number;
modalTitle: string;
onIndexerFlagsSelect(indexerFlags: number): void;
onModalClose(): void;
}
function SelectIndexerFlagsModal(props: SelectIndexerFlagsModalProps) {
const {
isOpen,
indexerFlags,
modalTitle,
onIndexerFlagsSelect,
onModalClose,
} = props;
return (
<Modal isOpen={isOpen} onModalClose={onModalClose}>
<SelectIndexerFlagsModalContent
indexerFlags={indexerFlags}
modalTitle={modalTitle}
onIndexerFlagsSelect={onIndexerFlagsSelect}
onModalClose={onModalClose}
/>
</Modal>
);
}
export default SelectIndexerFlagsModal;

@ -0,0 +1,7 @@
.modalBody {
composes: modalBody from '~Components/Modal/ModalBody.css';
display: flex;
flex: 1 1 auto;
flex-direction: column;
}

@ -0,0 +1,7 @@
// This file is automatically generated.
// Please do not change this file!
interface CssExports {
'modalBody': string;
}
export const cssExports: CssExports;
export default cssExports;

@ -0,0 +1,75 @@
import React, { useCallback, useState } from 'react';
import Form from 'Components/Form/Form';
import FormGroup from 'Components/Form/FormGroup';
import FormInputGroup from 'Components/Form/FormInputGroup';
import FormLabel from 'Components/Form/FormLabel';
import Button from 'Components/Link/Button';
import ModalBody from 'Components/Modal/ModalBody';
import ModalContent from 'Components/Modal/ModalContent';
import ModalFooter from 'Components/Modal/ModalFooter';
import ModalHeader from 'Components/Modal/ModalHeader';
import { inputTypes, kinds, scrollDirections } from 'Helpers/Props';
import translate from 'Utilities/String/translate';
import styles from './SelectIndexerFlagsModalContent.css';
interface SelectIndexerFlagsModalContentProps {
indexerFlags: number;
modalTitle: string;
onIndexerFlagsSelect(indexerFlags: number): void;
onModalClose(): void;
}
function SelectIndexerFlagsModalContent(
props: SelectIndexerFlagsModalContentProps
) {
const { modalTitle, onIndexerFlagsSelect, onModalClose } = props;
const [indexerFlags, setIndexerFlags] = useState(props.indexerFlags);
const onIndexerFlagsChange = useCallback(
({ value }: { value: number }) => {
setIndexerFlags(value);
},
[setIndexerFlags]
);
const onIndexerFlagsSelectWrapper = useCallback(() => {
onIndexerFlagsSelect(indexerFlags);
}, [indexerFlags, onIndexerFlagsSelect]);
return (
<ModalContent onModalClose={onModalClose}>
<ModalHeader>
{translate('SetIndexerFlagsModalTitle', { modalTitle })}
</ModalHeader>
<ModalBody
className={styles.modalBody}
scrollDirection={scrollDirections.NONE}
>
<Form>
<FormGroup>
<FormLabel>{translate('IndexerFlags')}</FormLabel>
<FormInputGroup
type={inputTypes.INDEXER_FLAGS_SELECT}
name="indexerFlags"
indexerFlags={indexerFlags}
autoFocus={true}
onChange={onIndexerFlagsChange}
/>
</FormGroup>
</Form>
</ModalBody>
<ModalFooter>
<Button onPress={onModalClose}>{translate('Cancel')}</Button>
<Button kind={kinds.SUCCESS} onPress={onIndexerFlagsSelectWrapper}>
{translate('SetIndexerFlags')}
</Button>
</ModalFooter>
</ModalContent>
);
}
export default SelectIndexerFlagsModalContent;

@ -26,6 +26,7 @@ import usePrevious from 'Helpers/Hooks/usePrevious';
import useSelectState from 'Helpers/Hooks/useSelectState'; import useSelectState from 'Helpers/Hooks/useSelectState';
import { align, icons, kinds, scrollDirections } from 'Helpers/Props'; import { align, icons, kinds, scrollDirections } from 'Helpers/Props';
import ImportMode from 'InteractiveImport/ImportMode'; import ImportMode from 'InteractiveImport/ImportMode';
import SelectIndexerFlagsModal from 'InteractiveImport/IndexerFlags/SelectIndexerFlagsModal';
import InteractiveImport, { import InteractiveImport, {
InteractiveImportCommandOptions, InteractiveImportCommandOptions,
} from 'InteractiveImport/InteractiveImport'; } from 'InteractiveImport/InteractiveImport';
@ -59,7 +60,13 @@ import getSelectedIds from 'Utilities/Table/getSelectedIds';
import InteractiveImportRow from './InteractiveImportRow'; import InteractiveImportRow from './InteractiveImportRow';
import styles from './InteractiveImportModalContent.css'; import styles from './InteractiveImportModalContent.css';
type SelectType = 'select' | 'movie' | 'releaseGroup' | 'quality' | 'language'; type SelectType =
| 'select'
| 'movie'
| 'releaseGroup'
| 'quality'
| 'language'
| 'indexerFlags';
type FilterExistingFiles = 'all' | 'new'; type FilterExistingFiles = 'all' | 'new';
@ -113,6 +120,15 @@ const COLUMNS = [
isSortable: true, isSortable: true,
isVisible: true, isVisible: true,
}, },
{
name: 'indexerFlags',
label: React.createElement(Icon, {
name: icons.FLAG,
title: () => translate('IndexerFlags'),
}),
isSortable: true,
isVisible: true,
},
{ {
name: 'rejections', name: 'rejections',
label: React.createElement(Icon, { label: React.createElement(Icon, {
@ -257,8 +273,18 @@ function InteractiveImportModalContent(
} }
} }
const showIndexerFlags = items.some((item) => item.indexerFlags);
if (!showIndexerFlags) {
const indexerFlagsColumn = result.find((c) => c.name === 'indexerFlags');
if (indexerFlagsColumn) {
indexerFlagsColumn.isVisible = false;
}
}
return result; return result;
}, [showMovie]); }, [showMovie, items]);
const selectedIds: number[] = useMemo(() => { const selectedIds: number[] = useMemo(() => {
return getSelectedIds(selectedState); return getSelectedIds(selectedState);
@ -283,6 +309,10 @@ function InteractiveImportModalContent(
key: 'language', key: 'language',
value: translate('SelectLanguage'), value: translate('SelectLanguage'),
}, },
{
key: 'indexerFlags',
value: translate('SelectIndexerFlags'),
},
]; ];
if (allowMovieChange) { if (allowMovieChange) {
@ -416,7 +446,14 @@ function InteractiveImportModalContent(
const isSelected = selectedIds.indexOf(item.id) > -1; const isSelected = selectedIds.indexOf(item.id) > -1;
if (isSelected) { if (isSelected) {
const { movie, releaseGroup, quality, languages, movieFileId } = item; const {
movie,
releaseGroup,
quality,
languages,
indexerFlags,
movieFileId,
} = item;
if (!movie) { if (!movie) {
setInteractiveImportErrorMessage( setInteractiveImportErrorMessage(
@ -450,6 +487,7 @@ function InteractiveImportModalContent(
releaseGroup, releaseGroup,
quality, quality,
languages, languages,
indexerFlags,
}); });
return; return;
@ -463,6 +501,7 @@ function InteractiveImportModalContent(
releaseGroup, releaseGroup,
quality, quality,
languages, languages,
indexerFlags,
downloadId, downloadId,
movieFileId, movieFileId,
}); });
@ -620,6 +659,22 @@ function InteractiveImportModalContent(
[selectedIds, dispatch] [selectedIds, dispatch]
); );
const onIndexerFlagsSelect = useCallback(
(indexerFlags: number) => {
dispatch(
updateInteractiveImportItems({
ids: selectedIds,
indexerFlags,
})
);
dispatch(reprocessInteractiveImportItems({ ids: selectedIds }));
setSelectModalOpen(null);
},
[selectedIds, dispatch]
);
const errorMessage = getErrorMessage( const errorMessage = getErrorMessage(
error, error,
translate('InteractiveImportLoadError') translate('InteractiveImportLoadError')
@ -794,6 +849,14 @@ function InteractiveImportModalContent(
onModalClose={onSelectModalClose} onModalClose={onSelectModalClose}
/> />
<SelectIndexerFlagsModal
isOpen={selectModalOpen === 'indexerFlags'}
indexerFlags={0}
modalTitle={modalTitle}
onIndexerFlagsSelect={onIndexerFlagsSelect}
onModalClose={onSelectModalClose}
/>
<ConfirmModal <ConfirmModal
isOpen={isConfirmDeleteModalOpen} isOpen={isConfirmDeleteModalOpen}
kind={kinds.DANGER} kind={kinds.DANGER}

@ -8,11 +8,13 @@ import Column from 'Components/Table/Column';
import TableRow from 'Components/Table/TableRow'; import TableRow from 'Components/Table/TableRow';
import Popover from 'Components/Tooltip/Popover'; import Popover from 'Components/Tooltip/Popover';
import { icons, kinds, tooltipPositions } from 'Helpers/Props'; import { icons, kinds, tooltipPositions } from 'Helpers/Props';
import SelectIndexerFlagsModal from 'InteractiveImport/IndexerFlags/SelectIndexerFlagsModal';
import SelectLanguageModal from 'InteractiveImport/Language/SelectLanguageModal'; import SelectLanguageModal from 'InteractiveImport/Language/SelectLanguageModal';
import SelectMovieModal from 'InteractiveImport/Movie/SelectMovieModal'; import SelectMovieModal from 'InteractiveImport/Movie/SelectMovieModal';
import SelectQualityModal from 'InteractiveImport/Quality/SelectQualityModal'; import SelectQualityModal from 'InteractiveImport/Quality/SelectQualityModal';
import SelectReleaseGroupModal from 'InteractiveImport/ReleaseGroup/SelectReleaseGroupModal'; import SelectReleaseGroupModal from 'InteractiveImport/ReleaseGroup/SelectReleaseGroupModal';
import Language from 'Language/Language'; import Language from 'Language/Language';
import IndexerFlags from 'Movie/IndexerFlags';
import Movie from 'Movie/Movie'; import Movie from 'Movie/Movie';
import MovieFormats from 'Movie/MovieFormats'; import MovieFormats from 'Movie/MovieFormats';
import MovieLanguage from 'Movie/MovieLanguage'; import MovieLanguage from 'Movie/MovieLanguage';
@ -30,7 +32,12 @@ import translate from 'Utilities/String/translate';
import InteractiveImportRowCellPlaceholder from './InteractiveImportRowCellPlaceholder'; import InteractiveImportRowCellPlaceholder from './InteractiveImportRowCellPlaceholder';
import styles from './InteractiveImportRow.css'; import styles from './InteractiveImportRow.css';
type SelectType = 'movie' | 'releaseGroup' | 'quality' | 'language'; type SelectType =
| 'movie'
| 'releaseGroup'
| 'quality'
| 'language'
| 'indexerFlags';
type SelectedChangeProps = SelectStateInputProps & { type SelectedChangeProps = SelectStateInputProps & {
hasMovieFileId: boolean; hasMovieFileId: boolean;
@ -47,6 +54,7 @@ interface InteractiveImportRowProps {
size: number; size: number;
customFormats?: object[]; customFormats?: object[];
customFormatScore?: number; customFormatScore?: number;
indexerFlags: number;
rejections: Rejection[]; rejections: Rejection[];
columns: Column[]; columns: Column[];
movieFileId?: number; movieFileId?: number;
@ -69,6 +77,7 @@ function InteractiveImportRow(props: InteractiveImportRowProps) {
size, size,
customFormats, customFormats,
customFormatScore, customFormatScore,
indexerFlags,
rejections, rejections,
isSelected, isSelected,
modalTitle, modalTitle,
@ -84,6 +93,10 @@ function InteractiveImportRow(props: InteractiveImportRowProps) {
() => columns.find((c) => c.name === 'movie')?.isVisible ?? false, () => columns.find((c) => c.name === 'movie')?.isVisible ?? false,
[columns] [columns]
); );
const isIndexerFlagsColumnVisible = useMemo(
() => columns.find((c) => c.name === 'indexerFlags')?.isVisible ?? false,
[columns]
);
const [selectModalOpen, setSelectModalOpen] = useState<SelectType | null>( const [selectModalOpen, setSelectModalOpen] = useState<SelectType | null>(
null null
@ -223,12 +236,34 @@ function InteractiveImportRow(props: InteractiveImportRowProps) {
[id, dispatch, setSelectModalOpen, selectRowAfterChange] [id, dispatch, setSelectModalOpen, selectRowAfterChange]
); );
const onSelectIndexerFlagsPress = useCallback(() => {
setSelectModalOpen('indexerFlags');
}, [setSelectModalOpen]);
const onIndexerFlagsSelect = useCallback(
(indexerFlags: number) => {
dispatch(
updateInteractiveImportItem({
id,
indexerFlags,
})
);
dispatch(reprocessInteractiveImportItems({ ids: [id] }));
setSelectModalOpen(null);
selectRowAfterChange();
},
[id, dispatch, setSelectModalOpen, selectRowAfterChange]
);
const movieTitle = movie ? movie.title : ''; const movieTitle = movie ? movie.title : '';
const showMoviePlaceholder = isSelected && !movie; const showMoviePlaceholder = isSelected && !movie;
const showReleaseGroupPlaceholder = isSelected && !releaseGroup; const showReleaseGroupPlaceholder = isSelected && !releaseGroup;
const showQualityPlaceholder = isSelected && !quality; const showQualityPlaceholder = isSelected && !quality;
const showLanguagePlaceholder = isSelected && !languages; const showLanguagePlaceholder = isSelected && !languages;
const showIndexerFlagsPlaceholder = isSelected && !indexerFlags;
return ( return (
<TableRow> <TableRow>
@ -311,6 +346,28 @@ function InteractiveImportRow(props: InteractiveImportRowProps) {
) : null} ) : null}
</TableRowCell> </TableRowCell>
{isIndexerFlagsColumnVisible ? (
<TableRowCellButton
title={translate('ClickToChangeIndexerFlags')}
onPress={onSelectIndexerFlagsPress}
>
{showIndexerFlagsPlaceholder ? (
<InteractiveImportRowCellPlaceholder isOptional={true} />
) : (
<>
{indexerFlags ? (
<Popover
anchor={<Icon name={icons.FLAG} kind={kinds.PRIMARY} />}
title={translate('IndexerFlags')}
body={<IndexerFlags indexerFlags={indexerFlags} />}
position={tooltipPositions.LEFT}
/>
) : null}
</>
)}
</TableRowCellButton>
) : null}
<TableRowCell> <TableRowCell>
{rejections.length ? ( {rejections.length ? (
<Popover <Popover
@ -361,6 +418,14 @@ function InteractiveImportRow(props: InteractiveImportRowProps) {
onLanguagesSelect={onLanguagesSelect} onLanguagesSelect={onLanguagesSelect}
onModalClose={onSelectModalClose} onModalClose={onSelectModalClose}
/> />
<SelectIndexerFlagsModal
isOpen={selectModalOpen === 'indexerFlags'}
indexerFlags={indexerFlags ?? 0}
modalTitle={modalTitle}
onIndexerFlagsSelect={onIndexerFlagsSelect}
onModalClose={onSelectModalClose}
/>
</TableRow> </TableRow>
); );
} }

@ -11,6 +11,7 @@ export interface InteractiveImportCommandOptions {
releaseGroup?: string; releaseGroup?: string;
quality: QualityModel; quality: QualityModel;
languages: Language[]; languages: Language[];
indexerFlags: number;
downloadId?: string; downloadId?: string;
movieFileId?: number; movieFileId?: number;
} }
@ -27,6 +28,7 @@ interface InteractiveImport extends ModelBase {
movie?: Movie; movie?: Movie;
qualityWeight: number; qualityWeight: number;
customFormats: object[]; customFormats: object[];
indexerFlags: number;
rejections: Rejection[]; rejections: Rejection[];
movieFileId?: number; movieFileId?: number;
} }

@ -39,8 +39,7 @@
} }
.rejected, .rejected,
.indexerFlags, .indexerFlags {
.download {
composes: cell from '~Components/Table/Cells/TableRowCell.css'; composes: cell from '~Components/Table/Cells/TableRowCell.css';
width: 50px; width: 50px;

@ -11,6 +11,7 @@ import Tooltip from 'Components/Tooltip/Tooltip';
import type DownloadProtocol from 'DownloadClient/DownloadProtocol'; import type DownloadProtocol from 'DownloadClient/DownloadProtocol';
import { icons, kinds, tooltipPositions } from 'Helpers/Props'; import { icons, kinds, tooltipPositions } from 'Helpers/Props';
import Language from 'Language/Language'; import Language from 'Language/Language';
import IndexerFlags from 'Movie/IndexerFlags';
import MovieFormats from 'Movie/MovieFormats'; import MovieFormats from 'Movie/MovieFormats';
import MovieLanguage from 'Movie/MovieLanguage'; import MovieLanguage from 'Movie/MovieLanguage';
import MovieQuality from 'Movie/MovieQuality'; import MovieQuality from 'Movie/MovieQuality';
@ -90,8 +91,8 @@ interface InteractiveSearchRowProps {
customFormats: CustomFormat[]; customFormats: CustomFormat[];
customFormatScore: number; customFormatScore: number;
mappedMovieId?: number; mappedMovieId?: number;
indexerFlags: number;
rejections: string[]; rejections: string[];
indexerFlags: string[];
downloadAllowed: boolean; downloadAllowed: boolean;
isGrabbing: boolean; isGrabbing: boolean;
isGrabbed: boolean; isGrabbed: boolean;
@ -125,8 +126,8 @@ function InteractiveSearchRow(props: InteractiveSearchRowProps) {
customFormatScore, customFormatScore,
customFormats, customFormats,
mappedMovieId, mappedMovieId,
indexerFlags = 0,
rejections = [], rejections = [],
indexerFlags = [],
downloadAllowed, downloadAllowed,
isGrabbing = false, isGrabbing = false,
isGrabbed = false, isGrabbed = false,
@ -276,22 +277,16 @@ function InteractiveSearchRow(props: InteractiveSearchRowProps) {
customFormats.length customFormats.length
)} )}
tooltip={<MovieFormats formats={customFormats} />} tooltip={<MovieFormats formats={customFormats} />}
position={tooltipPositions.TOP} position={tooltipPositions.LEFT}
/> />
</TableRowCell> </TableRowCell>
<TableRowCell className={styles.indexerFlags}> <TableRowCell className={styles.indexerFlags}>
{indexerFlags.length ? ( {indexerFlags ? (
<Popover <Popover
anchor={<Icon name={icons.FLAG} kind={kinds.PRIMARY} />} anchor={<Icon name={icons.FLAG} kind={kinds.PRIMARY} />}
title={translate('IndexerFlags')} title={translate('IndexerFlags')}
body={ body={<IndexerFlags indexerFlags={indexerFlags} />}
<ul>
{indexerFlags.map((flag, index) => {
return <li key={index}>{flag}</li>;
})}
</ul>
}
position={tooltipPositions.LEFT} position={tooltipPositions.LEFT}
/> />
) : null} ) : null}

@ -0,0 +1,26 @@
import React from 'react';
import { useSelector } from 'react-redux';
import createIndexerFlagsSelector from 'Store/Selectors/createIndexerFlagsSelector';
interface IndexerFlagsProps {
indexerFlags: number;
}
function IndexerFlags({ indexerFlags = 0 }: IndexerFlagsProps) {
const allIndexerFlags = useSelector(createIndexerFlagsSelector);
const flags = allIndexerFlags.items.filter(
// eslint-disable-next-line no-bitwise
(item) => (indexerFlags & item.id) === item.id
);
return flags.length ? (
<ul>
{flags.map((flag, index) => {
return <li key={index}>{flag.name}</li>;
})}
</ul>
) : null;
}
export default IndexerFlags;

@ -57,3 +57,9 @@
width: 55px; width: 55px;
} }
.indexerFlags {
composes: cell from '~Components/Table/Cells/TableRowCell.css';
width: 50px;
}

@ -9,6 +9,7 @@ interface CssExports {
'dateAdded': string; 'dateAdded': string;
'download': string; 'download': string;
'formats': string; 'formats': string;
'indexerFlags': string;
'language': string; 'language': string;
'languages': string; 'languages': string;
'quality': string; 'quality': string;

@ -1,12 +1,15 @@
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import React, { Component } from 'react'; import React, { Component } from 'react';
import Icon from 'Components/Icon';
import IconButton from 'Components/Link/IconButton'; import IconButton from 'Components/Link/IconButton';
import ConfirmModal from 'Components/Modal/ConfirmModal'; import ConfirmModal from 'Components/Modal/ConfirmModal';
import RelativeDateCellConnector from 'Components/Table/Cells/RelativeDateCellConnector'; import RelativeDateCellConnector from 'Components/Table/Cells/RelativeDateCellConnector';
import TableRowCell from 'Components/Table/Cells/TableRowCell'; import TableRowCell from 'Components/Table/Cells/TableRowCell';
import TableRow from 'Components/Table/TableRow'; import TableRow from 'Components/Table/TableRow';
import Popover from 'Components/Tooltip/Popover';
import Tooltip from 'Components/Tooltip/Tooltip'; import Tooltip from 'Components/Tooltip/Tooltip';
import { icons, kinds, tooltipPositions } from 'Helpers/Props'; import { icons, kinds, tooltipPositions } from 'Helpers/Props';
import IndexerFlags from 'Movie/IndexerFlags';
import MovieFormats from 'Movie/MovieFormats'; import MovieFormats from 'Movie/MovieFormats';
import MovieLanguage from 'Movie/MovieLanguage'; import MovieLanguage from 'Movie/MovieLanguage';
import MovieQuality from 'Movie/MovieQuality'; import MovieQuality from 'Movie/MovieQuality';
@ -82,6 +85,7 @@ class MovieFileEditorRow extends Component {
qualityCutoffNotMet, qualityCutoffNotMet,
customFormats, customFormats,
customFormatScore, customFormatScore,
indexerFlags,
languages, languages,
dateAdded, dateAdded,
columns columns
@ -143,12 +147,30 @@ class MovieFileEditorRow extends Component {
customFormats.length customFormats.length
)} )}
tooltip={<MovieFormats formats={customFormats} />} tooltip={<MovieFormats formats={customFormats} />}
position={tooltipPositions.TOP} position={tooltipPositions.LEFT}
/> />
</TableRowCell> </TableRowCell>
); );
} }
if (name === 'indexerFlags') {
return (
<TableRowCell
key={name}
className={styles.indexerFlags}
>
{indexerFlags ? (
<Popover
anchor={<Icon name={icons.FLAG} kind={kinds.PRIMARY} />}
title={translate('IndexerFlags')}
body={<IndexerFlags indexerFlags={indexerFlags} />}
position={tooltipPositions.LEFT}
/>
) : null}
</TableRowCell>
);
}
if (name === 'languages') { if (name === 'languages') {
return ( return (
<TableRowCell <TableRowCell
@ -363,6 +385,7 @@ MovieFileEditorRow.propTypes = {
releaseGroup: PropTypes.string, releaseGroup: PropTypes.string,
customFormats: PropTypes.arrayOf(PropTypes.object).isRequired, customFormats: PropTypes.arrayOf(PropTypes.object).isRequired,
customFormatScore: PropTypes.number.isRequired, customFormatScore: PropTypes.number.isRequired,
indexerFlags: PropTypes.number.isRequired,
qualityCutoffNotMet: PropTypes.bool.isRequired, qualityCutoffNotMet: PropTypes.bool.isRequired,
languages: PropTypes.arrayOf(PropTypes.object).isRequired, languages: PropTypes.arrayOf(PropTypes.object).isRequired,
mediaInfo: PropTypes.object, mediaInfo: PropTypes.object,
@ -372,7 +395,8 @@ MovieFileEditorRow.propTypes = {
}; };
MovieFileEditorRow.defaultProps = { MovieFileEditorRow.defaultProps = {
customFormats: [] customFormats: [],
indexerFlags: 0
}; };
export default MovieFileEditorRow; export default MovieFileEditorRow;

@ -15,6 +15,7 @@ export interface MovieFile extends ModelBase {
languages: Language[]; languages: Language[];
quality: QualityModel; quality: QualityModel;
customFormats: CustomFormat[]; customFormats: CustomFormat[];
indexerFlags: number;
mediaInfo: MediaInfo; mediaInfo: MediaInfo;
qualityCutoffNotMet: boolean; qualityCutoffNotMet: boolean;
} }

@ -159,6 +159,7 @@ export const actionHandlers = handleThunks({
quality: item.quality, quality: item.quality,
languages: item.languages, languages: item.languages,
releaseGroup: item.releaseGroup, releaseGroup: item.releaseGroup,
indexerFlags: item.indexerFlags,
downloadId: item.downloadId downloadId: item.downloadId
}; };
}); });

@ -104,6 +104,15 @@ export const defaultState = {
isVisible: true, isVisible: true,
isSortable: true isSortable: true
}, },
{
name: 'indexerFlags',
columnLabel: () => translate('IndexerFlags'),
label: React.createElement(Icon, {
name: icons.FLAG,
title: () => translate('IndexerFlags')
}),
isVisible: false
},
{ {
name: 'dateAdded', name: 'dateAdded',
label: () => translate('Added'), label: () => translate('Added'),

@ -78,8 +78,8 @@ namespace NzbDrone.Core.CustomFormats
MovieInfo = movieInfo, MovieInfo = movieInfo,
Movie = movie, Movie = movie,
Size = blocklist.Size ?? 0, Size = blocklist.Size ?? 0,
IndexerFlags = blocklist.IndexerFlags, Languages = blocklist.Languages,
Languages = blocklist.Languages IndexerFlags = blocklist.IndexerFlags
}; };
return ParseCustomFormat(input); return ParseCustomFormat(input);
@ -90,7 +90,7 @@ namespace NzbDrone.Core.CustomFormats
var parsed = Parser.Parser.ParseMovieTitle(history.SourceTitle); var parsed = Parser.Parser.ParseMovieTitle(history.SourceTitle);
long.TryParse(history.Data.GetValueOrDefault("size"), out var size); long.TryParse(history.Data.GetValueOrDefault("size"), out var size);
Enum.TryParse(history.Data.GetValueOrDefault("indexerFlags"), true, out IndexerFlags flags); Enum.TryParse(history.Data.GetValueOrDefault("indexerFlags"), true, out IndexerFlags indexerFlags);
var movieInfo = new ParsedMovieInfo var movieInfo = new ParsedMovieInfo
{ {
@ -108,8 +108,8 @@ namespace NzbDrone.Core.CustomFormats
MovieInfo = movieInfo, MovieInfo = movieInfo,
Movie = movie, Movie = movie,
Size = size, Size = size,
IndexerFlags = flags, Languages = history.Languages,
Languages = history.Languages IndexerFlags = indexerFlags
}; };
return ParseCustomFormat(input); return ParseCustomFormat(input);
@ -117,7 +117,7 @@ namespace NzbDrone.Core.CustomFormats
public List<CustomFormat> ParseCustomFormat(LocalMovie localMovie) public List<CustomFormat> ParseCustomFormat(LocalMovie localMovie)
{ {
var episodeInfo = new ParsedMovieInfo var movieInfo = new ParsedMovieInfo
{ {
MovieTitles = new List<string>() { localMovie.Movie.Title }, MovieTitles = new List<string>() { localMovie.Movie.Title },
SimpleReleaseTitle = localMovie.SceneName.IsNotNullOrWhiteSpace() ? localMovie.SceneName.SimplifyReleaseTitle() : Path.GetFileName(localMovie.Path).SimplifyReleaseTitle(), SimpleReleaseTitle = localMovie.SceneName.IsNotNullOrWhiteSpace() ? localMovie.SceneName.SimplifyReleaseTitle() : Path.GetFileName(localMovie.Path).SimplifyReleaseTitle(),
@ -130,10 +130,11 @@ namespace NzbDrone.Core.CustomFormats
var input = new CustomFormatInput var input = new CustomFormatInput
{ {
MovieInfo = episodeInfo, MovieInfo = movieInfo,
Movie = localMovie.Movie, Movie = localMovie.Movie,
Size = localMovie.Size, Size = localMovie.Size,
Languages = localMovie.Languages, Languages = localMovie.Languages,
IndexerFlags = localMovie.IndexerFlags,
Filename = Path.GetFileName(localMovie.Path) Filename = Path.GetFileName(localMovie.Path)
}; };
@ -203,8 +204,8 @@ namespace NzbDrone.Core.CustomFormats
MovieInfo = movieInfo, MovieInfo = movieInfo,
Movie = movie, Movie = movie,
Size = movieFile.Size, Size = movieFile.Size,
IndexerFlags = movieFile.IndexerFlags,
Languages = movieFile.Languages, Languages = movieFile.Languages,
IndexerFlags = movieFile.IndexerFlags,
Filename = Path.GetFileName(movieFile.RelativePath) Filename = Path.GetFileName(movieFile.RelativePath)
}; };

@ -15,7 +15,7 @@ namespace NzbDrone.Core.CustomFormats
{ {
if (!Enum.IsDefined(typeof(IndexerFlags), qualityValue)) if (!Enum.IsDefined(typeof(IndexerFlags), qualityValue))
{ {
context.AddFailure(string.Format("Invalid indexer flag condition value: {0}", qualityValue)); context.AddFailure($"Invalid indexer flag condition value: {qualityValue}");
} }
}); });
} }
@ -23,17 +23,17 @@ namespace NzbDrone.Core.CustomFormats
public class IndexerFlagSpecification : CustomFormatSpecificationBase public class IndexerFlagSpecification : CustomFormatSpecificationBase
{ {
private static readonly IndexerFlagSpecificationValidator Validator = new IndexerFlagSpecificationValidator(); private static readonly IndexerFlagSpecificationValidator Validator = new ();
public override int Order => 4; public override int Order => 4;
public override string ImplementationName => "Indexer Flag"; public override string ImplementationName => "Indexer Flag";
[FieldDefinition(1, Label = "Flag", Type = FieldType.Select, SelectOptions = typeof(IndexerFlags))] [FieldDefinition(1, Label = "CustomFormatsSpecificationFlag", Type = FieldType.Select, SelectOptions = typeof(IndexerFlags))]
public int Value { get; set; } public int Value { get; set; }
protected override bool IsSatisfiedByWithoutNegate(CustomFormatInput input) protected override bool IsSatisfiedByWithoutNegate(CustomFormatInput input)
{ {
return input.IndexerFlags.HasFlag((IndexerFlags)Value) == true; return input.IndexerFlags.HasFlag((IndexerFlags)Value);
} }
public override NzbDroneValidationResult Validate() public override NzbDroneValidationResult Validate()

@ -140,12 +140,11 @@ namespace NzbDrone.Core.Download.TrackedDownloads
var firstHistoryItem = historyItems.FirstOrDefault(); var firstHistoryItem = historyItems.FirstOrDefault();
var grabbedEvent = historyItems.FirstOrDefault(v => v.EventType == MovieHistoryEventType.Grabbed); var grabbedEvent = historyItems.FirstOrDefault(v => v.EventType == MovieHistoryEventType.Grabbed);
trackedDownload.Indexer = grabbedEvent?.Data["indexer"]; trackedDownload.Indexer = grabbedEvent?.Data?.GetValueOrDefault("indexer");
trackedDownload.Added = grabbedEvent?.Date; trackedDownload.Added = grabbedEvent?.Date;
if (parsedMovieInfo == null || if (parsedMovieInfo == null ||
trackedDownload.RemoteMovie == null || trackedDownload.RemoteMovie?.Movie == null)
trackedDownload.RemoteMovie.Movie == null)
{ {
parsedMovieInfo = Parser.Parser.ParseMovieTitle(firstHistoryItem.SourceTitle); parsedMovieInfo = Parser.Parser.ParseMovieTitle(firstHistoryItem.SourceTitle);

@ -39,18 +39,6 @@ namespace NzbDrone.Core.Indexers.FileList
{ {
var id = result.Id; var id = result.Id;
IndexerFlags flags = 0;
if (result.FreeLeech)
{
flags |= IndexerFlags.G_Freeleech;
}
if (result.Internal)
{
flags |= IndexerFlags.G_Internal;
}
var imdbId = 0; var imdbId = 0;
if (result.ImdbId != null && result.ImdbId.Length > 2) if (result.ImdbId != null && result.ImdbId.Length > 2)
{ {
@ -68,14 +56,29 @@ namespace NzbDrone.Core.Indexers.FileList
Peers = result.Leechers + result.Seeders, Peers = result.Leechers + result.Seeders,
PublishDate = result.UploadDate, PublishDate = result.UploadDate,
ImdbId = imdbId, ImdbId = imdbId,
IndexerFlags = flags IndexerFlags = GetIndexerFlags(result)
}); });
} }
return torrentInfos.ToArray(); return torrentInfos.ToArray();
} }
public Action<IDictionary<string, string>, DateTime?> CookiesUpdater { get; set; } private static IndexerFlags GetIndexerFlags(FileListTorrent item)
{
IndexerFlags flags = 0;
if (item.FreeLeech)
{
flags |= IndexerFlags.G_Freeleech;
}
if (item.Internal)
{
flags |= IndexerFlags.G_Internal;
}
return flags;
}
private string GetDownloadUrl(string torrentId) private string GetDownloadUrl(string torrentId)
{ {
@ -95,5 +98,7 @@ namespace NzbDrone.Core.Indexers.FileList
return url.FullUri; return url.FullUri;
} }
public Action<IDictionary<string, string>, DateTime?> CookiesUpdater { get; set; }
} }
} }

@ -50,19 +50,6 @@ namespace NzbDrone.Core.Indexers.HDBits
foreach (var result in queryResults) foreach (var result in queryResults)
{ {
var id = result.Id; var id = result.Id;
var internalRelease = result.TypeOrigin == 1;
IndexerFlags flags = 0;
if (result.FreeLeech == "yes")
{
flags |= IndexerFlags.G_Freeleech;
}
if (internalRelease)
{
flags |= IndexerFlags.G_Internal;
}
torrentInfos.Add(new HDBitsInfo torrentInfos.Add(new HDBitsInfo
{ {
@ -75,16 +62,30 @@ namespace NzbDrone.Core.Indexers.HDBits
Seeders = result.Seeders, Seeders = result.Seeders,
Peers = result.Leechers + result.Seeders, Peers = result.Leechers + result.Seeders,
PublishDate = result.Added.ToUniversalTime(), PublishDate = result.Added.ToUniversalTime(),
Internal = internalRelease,
ImdbId = result.ImdbInfo?.Id ?? 0, ImdbId = result.ImdbInfo?.Id ?? 0,
IndexerFlags = flags IndexerFlags = GetIndexerFlags(result)
}); });
} }
return torrentInfos.ToArray(); return torrentInfos.ToArray();
} }
public Action<IDictionary<string, string>, DateTime?> CookiesUpdater { get; set; } private static IndexerFlags GetIndexerFlags(TorrentQueryResponse item)
{
IndexerFlags flags = 0;
if (item.FreeLeech == "yes")
{
flags |= IndexerFlags.G_Freeleech;
}
if (item.TypeOrigin == 1)
{
flags |= IndexerFlags.G_Internal;
}
return flags;
}
private string GetDownloadUrl(string torrentId) private string GetDownloadUrl(string torrentId)
{ {
@ -104,5 +105,7 @@ namespace NzbDrone.Core.Indexers.HDBits
return url.FullUri; return url.FullUri;
} }
public Action<IDictionary<string, string>, DateTime?> CookiesUpdater { get; set; }
} }
} }

@ -193,6 +193,7 @@
"Clear": "Clear", "Clear": "Clear",
"ClearBlocklist": "Clear blocklist", "ClearBlocklist": "Clear blocklist",
"ClearBlocklistMessageText": "Are you sure you want to clear all items from the blocklist?", "ClearBlocklistMessageText": "Are you sure you want to clear all items from the blocklist?",
"ClickToChangeIndexerFlags": "Click to change indexer flags",
"ClickToChangeLanguage": "Click to change language", "ClickToChangeLanguage": "Click to change language",
"ClickToChangeMovie": "Click to change movie", "ClickToChangeMovie": "Click to change movie",
"ClickToChangeQuality": "Click to change quality", "ClickToChangeQuality": "Click to change quality",
@ -255,6 +256,7 @@
"CustomFormatsLoadError": "Unable to load Custom Formats", "CustomFormatsLoadError": "Unable to load Custom Formats",
"CustomFormatsSettings": "Custom Formats Settings", "CustomFormatsSettings": "Custom Formats Settings",
"CustomFormatsSettingsSummary": "Custom Formats and Settings", "CustomFormatsSettingsSummary": "Custom Formats and Settings",
"CustomFormatsSpecificationFlag": "Flag",
"CustomFormatsSpecificationRegularExpression": "Regular Expression", "CustomFormatsSpecificationRegularExpression": "Regular Expression",
"CustomFormatsSpecificationRegularExpressionHelpText": "Custom Format RegEx is Case Insensitive", "CustomFormatsSpecificationRegularExpressionHelpText": "Custom Format RegEx is Case Insensitive",
"Cutoff": "Cutoff", "Cutoff": "Cutoff",
@ -1483,6 +1485,7 @@
"SelectDropdown": "Select...", "SelectDropdown": "Select...",
"SelectFolder": "Select Folder", "SelectFolder": "Select Folder",
"SelectFolderModalTitle": "{modalTitle} - Select Folder", "SelectFolderModalTitle": "{modalTitle} - Select Folder",
"SelectIndexerFlags": "Select Indexer Flags",
"SelectLanguage": "Select Language", "SelectLanguage": "Select Language",
"SelectLanguageModalTitle": "{modalTitle} - Select Language", "SelectLanguageModalTitle": "{modalTitle} - Select Language",
"SelectLanguages": "Select Languages", "SelectLanguages": "Select Languages",
@ -1490,6 +1493,8 @@
"SelectQuality": "Select Quality", "SelectQuality": "Select Quality",
"SelectReleaseGroup": "Select Release Group", "SelectReleaseGroup": "Select Release Group",
"SendAnonymousUsageData": "Send Anonymous Usage Data", "SendAnonymousUsageData": "Send Anonymous Usage Data",
"SetIndexerFlags": "Set Indexer Flags",
"SetIndexerFlagsModalTitle": "{modalTitle} - Set Indexer Flags",
"SetPermissions": "Set Permissions", "SetPermissions": "Set Permissions",
"SetPermissionsLinuxHelpText": "Should chmod be run when files are imported/renamed?", "SetPermissionsLinuxHelpText": "Should chmod be run when files are imported/renamed?",
"SetPermissionsLinuxHelpTextWarning": "If you're unsure what these settings do, do not alter them.", "SetPermissionsLinuxHelpTextWarning": "If you're unsure what these settings do, do not alter them.",

@ -20,12 +20,12 @@ namespace NzbDrone.Core.MediaFiles
public string OriginalFilePath { get; set; } public string OriginalFilePath { get; set; }
public string SceneName { get; set; } public string SceneName { get; set; }
public string ReleaseGroup { get; set; } public string ReleaseGroup { get; set; }
public IndexerFlags IndexerFlags { get; set; }
public QualityModel Quality { get; set; } public QualityModel Quality { get; set; }
public List<Language> Languages { get; set; } public IndexerFlags IndexerFlags { get; set; }
public MediaInfoModel MediaInfo { get; set; } public MediaInfoModel MediaInfo { get; set; }
public string Edition { get; set; } public string Edition { get; set; }
public Movie Movie { get; set; } public Movie Movie { get; set; }
public List<Language> Languages { get; set; }
public override string ToString() public override string ToString()
{ {

@ -108,6 +108,10 @@ namespace NzbDrone.Core.MediaFiles.MovieImport
movieFile.IndexerFlags = flags; movieFile.IndexerFlags = flags;
} }
} }
else
{
movieFile.IndexerFlags = localMovie.IndexerFlags;
}
bool copyOnly; bool copyOnly;
switch (importMode) switch (importMode)

@ -13,6 +13,7 @@ namespace NzbDrone.Core.MediaFiles.MovieImport.Manual
public QualityModel Quality { get; set; } public QualityModel Quality { get; set; }
public List<Language> Languages { get; set; } public List<Language> Languages { get; set; }
public string ReleaseGroup { get; set; } public string ReleaseGroup { get; set; }
public int IndexerFlags { get; set; }
public string DownloadId { get; set; } public string DownloadId { get; set; }
public int MovieId { get; set; } public int MovieId { get; set; }

@ -20,6 +20,7 @@ namespace NzbDrone.Core.MediaFiles.MovieImport.Manual
public string DownloadId { get; set; } public string DownloadId { get; set; }
public List<CustomFormat> CustomFormats { get; set; } public List<CustomFormat> CustomFormats { get; set; }
public int CustomFormatScore { get; set; } public int CustomFormatScore { get; set; }
public int IndexerFlags { get; set; }
public IEnumerable<Rejection> Rejections { get; set; } public IEnumerable<Rejection> Rejections { get; set; }
public Movie Movie { get; set; } public Movie Movie { get; set; }

@ -24,7 +24,7 @@ namespace NzbDrone.Core.MediaFiles.MovieImport.Manual
public interface IManualImportService public interface IManualImportService
{ {
List<ManualImportItem> GetMediaFiles(string path, string downloadId, int? movieId, bool filterExistingFiles); List<ManualImportItem> GetMediaFiles(string path, string downloadId, int? movieId, bool filterExistingFiles);
ManualImportItem ReprocessItem(string path, string downloadId, int movieId, string releaseGroup, QualityModel quality, List<Language> languages); ManualImportItem ReprocessItem(string path, string downloadId, int movieId, string releaseGroup, QualityModel quality, List<Language> languages, int indexerFlags);
} }
public class ManualImportService : IExecute<ManualImportCommand>, IManualImportService public class ManualImportService : IExecute<ManualImportCommand>, IManualImportService
@ -97,7 +97,7 @@ namespace NzbDrone.Core.MediaFiles.MovieImport.Manual
return ProcessFolder(path, path, downloadId, movieId, filterExistingFiles); return ProcessFolder(path, path, downloadId, movieId, filterExistingFiles);
} }
public ManualImportItem ReprocessItem(string path, string downloadId, int movieId, string releaseGroup, QualityModel quality, List<Language> languages) public ManualImportItem ReprocessItem(string path, string downloadId, int movieId, string releaseGroup, QualityModel quality, List<Language> languages, int indexerFlags)
{ {
var rootFolder = Path.GetDirectoryName(path); var rootFolder = Path.GetDirectoryName(path);
var movie = _movieService.GetMovie(movieId); var movie = _movieService.GetMovie(movieId);
@ -122,9 +122,10 @@ namespace NzbDrone.Core.MediaFiles.MovieImport.Manual
SceneSource = SceneSource(movie, rootFolder), SceneSource = SceneSource(movie, rootFolder),
ExistingFile = movie.Path.IsParentPath(path), ExistingFile = movie.Path.IsParentPath(path),
Size = _diskProvider.GetFileSize(path), Size = _diskProvider.GetFileSize(path),
ReleaseGroup = releaseGroup.IsNullOrWhiteSpace() ? Parser.Parser.ParseReleaseGroup(path) : releaseGroup,
Languages = languages?.Count <= 1 && (languages?.SingleOrDefault() ?? Language.Unknown) == Language.Unknown ? languageParse : languages, Languages = languages?.Count <= 1 && (languages?.SingleOrDefault() ?? Language.Unknown) == Language.Unknown ? languageParse : languages,
Quality = (quality?.Quality ?? Quality.Unknown) == Quality.Unknown ? QualityParser.ParseQuality(path) : quality, Quality = (quality?.Quality ?? Quality.Unknown) == Quality.Unknown ? QualityParser.ParseQuality(path) : quality,
ReleaseGroup = releaseGroup.IsNullOrWhiteSpace() ? Parser.Parser.ParseReleaseGroup(path) : releaseGroup, IndexerFlags = (IndexerFlags)indexerFlags
}; };
return MapItem(_importDecisionMaker.GetDecision(localMovie, downloadClientItem), rootFolder, downloadId, null); return MapItem(_importDecisionMaker.GetDecision(localMovie, downloadClientItem), rootFolder, downloadId, null);
@ -320,6 +321,7 @@ namespace NzbDrone.Core.MediaFiles.MovieImport.Manual
item.Languages = decision.LocalMovie.Languages; item.Languages = decision.LocalMovie.Languages;
item.ReleaseGroup = decision.LocalMovie.ReleaseGroup; item.ReleaseGroup = decision.LocalMovie.ReleaseGroup;
item.Rejections = decision.Rejections; item.Rejections = decision.Rejections;
item.IndexerFlags = (int)decision.LocalMovie.IndexerFlags;
return item; return item;
} }
@ -346,9 +348,10 @@ namespace NzbDrone.Core.MediaFiles.MovieImport.Manual
ExistingFile = existingFile, ExistingFile = existingFile,
FileMovieInfo = fileMovieInfo, FileMovieInfo = fileMovieInfo,
Path = file.Path, Path = file.Path,
ReleaseGroup = file.ReleaseGroup,
Quality = file.Quality, Quality = file.Quality,
Languages = file.Languages, Languages = file.Languages,
ReleaseGroup = file.ReleaseGroup, IndexerFlags = (IndexerFlags)file.IndexerFlags,
Movie = movie, Movie = movie,
Size = 0 Size = 0
}; };
@ -373,9 +376,10 @@ namespace NzbDrone.Core.MediaFiles.MovieImport.Manual
// Apply the user-chosen values. // Apply the user-chosen values.
localMovie.Movie = movie; localMovie.Movie = movie;
localMovie.ReleaseGroup = file.ReleaseGroup;
localMovie.Quality = file.Quality; localMovie.Quality = file.Quality;
localMovie.Languages = file.Languages; localMovie.Languages = file.Languages;
localMovie.ReleaseGroup = file.ReleaseGroup; localMovie.IndexerFlags = (IndexerFlags)file.IndexerFlags;
// TODO: Cleanup non-tracked downloads // TODO: Cleanup non-tracked downloads
var importDecision = new ImportDecision(localMovie); var importDecision = new ImportDecision(localMovie);

@ -26,6 +26,7 @@ namespace NzbDrone.Core.Parser.Model
public List<DeletedMovieFile> OldFiles { get; set; } public List<DeletedMovieFile> OldFiles { get; set; }
public QualityModel Quality { get; set; } public QualityModel Quality { get; set; }
public List<Language> Languages { get; set; } public List<Language> Languages { get; set; }
public IndexerFlags IndexerFlags { get; set; }
public MediaInfoModel MediaInfo { get; set; } public MediaInfoModel MediaInfo { get; set; }
public bool ExistingFile { get; set; } public bool ExistingFile { get; set; }
public bool SceneSource { get; set; } public bool SceneSource { get; set; }

@ -108,12 +108,12 @@ namespace NzbDrone.Core.Parser.Model
G_DoubleUpload = 4, // General G_DoubleUpload = 4, // General
PTP_Golden = 8, // PTP PTP_Golden = 8, // PTP
PTP_Approved = 16, // PTP PTP_Approved = 16, // PTP
G_Internal = 32, // General, internal G_Internal = 32, // General, uploader is an internal release group
[Obsolete] [Obsolete]
AHD_Internal = 64, // AHD, internal AHD_Internal = 64, // AHD, internal
G_Scene = 128, // General, the torrent comes from the "scene" G_Scene = 128, // General, the torrent comes from a "scene" group
G_Freeleech75 = 256, // Currently only used for AHD, signifies a torrent counts towards 75 percent of your download quota. G_Freeleech75 = 256, // Signifies a torrent counts towards 75 percent of your download quota.
G_Freeleech25 = 512, // Currently only used for AHD, signifies a torrent counts towards 25 percent of your download quota. G_Freeleech25 = 512, // Signifies a torrent counts towards 25 percent of your download quota.
[Obsolete] [Obsolete]
AHD_UserRelease = 1024 // AHD, internal AHD_UserRelease = 1024 // AHD, internal
} }

@ -45,7 +45,6 @@ namespace Radarr.Api.V3.Indexers
public string InfoUrl { get; set; } public string InfoUrl { get; set; }
public bool DownloadAllowed { get; set; } public bool DownloadAllowed { get; set; }
public int ReleaseWeight { get; set; } public int ReleaseWeight { get; set; }
public IEnumerable<string> IndexerFlags { get; set; }
public string Edition { get; set; } public string Edition { get; set; }
public string MagnetUrl { get; set; } public string MagnetUrl { get; set; }
@ -53,6 +52,7 @@ namespace Radarr.Api.V3.Indexers
public int? Seeders { get; set; } public int? Seeders { get; set; }
public int? Leechers { get; set; } public int? Leechers { get; set; }
public DownloadProtocol Protocol { get; set; } public DownloadProtocol Protocol { get; set; }
public int IndexerFlags { get; set; }
// Sent when queuing an unknown release // Sent when queuing an unknown release
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingDefault)] [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingDefault)]
@ -76,7 +76,7 @@ namespace Radarr.Api.V3.Indexers
var parsedMovieInfo = model.RemoteMovie.ParsedMovieInfo; var parsedMovieInfo = model.RemoteMovie.ParsedMovieInfo;
var remoteMovie = model.RemoteMovie; var remoteMovie = model.RemoteMovie;
var torrentInfo = (model.RemoteMovie.Release as TorrentInfo) ?? new TorrentInfo(); var torrentInfo = (model.RemoteMovie.Release as TorrentInfo) ?? new TorrentInfo();
var indexerFlags = torrentInfo.IndexerFlags.ToString().Split(new string[] { ", " }, StringSplitOptions.None).Where(x => x != "0"); var indexerFlags = torrentInfo.IndexerFlags;
// TODO: Clean this mess up. don't mix data from multiple classes, use sub-resources instead? (Got a huge Deja Vu, didn't we talk about this already once?) // TODO: Clean this mess up. don't mix data from multiple classes, use sub-resources instead? (Got a huge Deja Vu, didn't we talk about this already once?)
return new ReleaseResource return new ReleaseResource
@ -118,7 +118,7 @@ namespace Radarr.Api.V3.Indexers
Seeders = torrentInfo.Seeders, Seeders = torrentInfo.Seeders,
Leechers = (torrentInfo.Peers.HasValue && torrentInfo.Seeders.HasValue) ? (torrentInfo.Peers.Value - torrentInfo.Seeders.Value) : (int?)null, Leechers = (torrentInfo.Peers.HasValue && torrentInfo.Seeders.HasValue) ? (torrentInfo.Peers.Value - torrentInfo.Seeders.Value) : (int?)null,
Protocol = releaseInfo.DownloadProtocol, Protocol = releaseInfo.DownloadProtocol,
IndexerFlags = indexerFlags IndexerFlags = (int)indexerFlags
}; };
} }

@ -34,9 +34,10 @@ namespace Radarr.Api.V3.ManualImport
{ {
foreach (var item in items) foreach (var item in items)
{ {
var processedItem = _manualImportService.ReprocessItem(item.Path, item.DownloadId, item.MovieId, item.ReleaseGroup, item.Quality, item.Languages); var processedItem = _manualImportService.ReprocessItem(item.Path, item.DownloadId, item.MovieId, item.ReleaseGroup, item.Quality, item.Languages, item.IndexerFlags);
item.Movie = processedItem.Movie.ToResource(0); item.Movie = processedItem.Movie.ToResource(0);
item.IndexerFlags = processedItem.IndexerFlags;
item.Rejections = processedItem.Rejections; item.Rejections = processedItem.Rejections;
item.CustomFormats = processedItem.CustomFormats.ToResource(false); item.CustomFormats = processedItem.CustomFormats.ToResource(false);
item.CustomFormatScore = processedItem.CustomFormatScore; item.CustomFormatScore = processedItem.CustomFormatScore;

@ -19,6 +19,7 @@ namespace Radarr.Api.V3.ManualImport
public string DownloadId { get; set; } public string DownloadId { get; set; }
public List<CustomFormatResource> CustomFormats { get; set; } public List<CustomFormatResource> CustomFormats { get; set; }
public int CustomFormatScore { get; set; } public int CustomFormatScore { get; set; }
public int IndexerFlags { get; set; }
public IEnumerable<Rejection> Rejections { get; set; } public IEnumerable<Rejection> Rejections { get; set; }
} }
} }

@ -26,6 +26,7 @@ namespace Radarr.Api.V3.ManualImport
public string DownloadId { get; set; } public string DownloadId { get; set; }
public List<CustomFormatResource> CustomFormats { get; set; } public List<CustomFormatResource> CustomFormats { get; set; }
public int CustomFormatScore { get; set; } public int CustomFormatScore { get; set; }
public int IndexerFlags { get; set; }
public IEnumerable<Rejection> Rejections { get; set; } public IEnumerable<Rejection> Rejections { get; set; }
} }
@ -58,6 +59,7 @@ namespace Radarr.Api.V3.ManualImport
// QualityWeight // QualityWeight
DownloadId = model.DownloadId, DownloadId = model.DownloadId,
IndexerFlags = model.IndexerFlags,
Rejections = model.Rejections Rejections = model.Rejections
}; };
} }

@ -19,16 +19,17 @@ namespace Radarr.Api.V3.MovieFiles
public long Size { get; set; } public long Size { get; set; }
public DateTime DateAdded { get; set; } public DateTime DateAdded { get; set; }
public string SceneName { get; set; } public string SceneName { get; set; }
public int IndexerFlags { get; set; } public string ReleaseGroup { get; set; }
public string Edition { get; set; }
public List<Language> Languages { get; set; }
public QualityModel Quality { get; set; } public QualityModel Quality { get; set; }
public List<CustomFormatResource> CustomFormats { get; set; } public List<CustomFormatResource> CustomFormats { get; set; }
public int CustomFormatScore { get; set; } public int CustomFormatScore { get; set; }
public int? IndexerFlags { get; set; }
public MediaInfoResource MediaInfo { get; set; } public MediaInfoResource MediaInfo { get; set; }
public string OriginalFilePath { get; set; } public string OriginalFilePath { get; set; }
public bool QualityCutoffNotMet { get; set; } public bool QualityCutoffNotMet { get; set; }
public List<Language> Languages { get; set; }
public string ReleaseGroup { get; set; }
public string Edition { get; set; }
} }
public static class MovieFileResourceMapper public static class MovieFileResourceMapper
@ -78,14 +79,14 @@ namespace Radarr.Api.V3.MovieFiles
Size = model.Size, Size = model.Size,
DateAdded = model.DateAdded, DateAdded = model.DateAdded,
SceneName = model.SceneName, SceneName = model.SceneName,
IndexerFlags = (int)model.IndexerFlags,
Quality = model.Quality, Quality = model.Quality,
Languages = model.Languages, Languages = model.Languages,
Edition = model.Edition, Edition = model.Edition,
ReleaseGroup = model.ReleaseGroup, ReleaseGroup = model.ReleaseGroup,
MediaInfo = model.MediaInfo.ToResource(model.SceneName), MediaInfo = model.MediaInfo.ToResource(model.SceneName),
QualityCutoffNotMet = upgradableSpecification?.QualityCutoffNotMet(movie.QualityProfile, model.Quality) ?? false, QualityCutoffNotMet = upgradableSpecification?.QualityCutoffNotMet(movie.QualityProfile, model.Quality) ?? false,
OriginalFilePath = model.OriginalFilePath OriginalFilePath = model.OriginalFilePath,
IndexerFlags = (int)model.IndexerFlags
}; };
if (formatCalculationService != null) if (formatCalculationService != null)

Loading…
Cancel
Save