mirror of https://github.com/lidarr/Lidarr
New: Indexer flags
(cherry picked from commit 7a768b5d0faf9aa57e78aee19cefee8fb19a42d5)
This commit is contained in:
parent
967b58017a
commit
1db0eb1029
|
@ -35,3 +35,9 @@
|
||||||
|
|
||||||
width: 55px;
|
width: 55px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.indexerFlags {
|
||||||
|
composes: cell from '~Components/Table/Cells/TableRowCell.css';
|
||||||
|
|
||||||
|
width: 50px;
|
||||||
|
}
|
||||||
|
|
|
@ -4,6 +4,7 @@ interface CssExports {
|
||||||
'audio': string;
|
'audio': string;
|
||||||
'customFormatScore': string;
|
'customFormatScore': string;
|
||||||
'duration': string;
|
'duration': string;
|
||||||
|
'indexerFlags': string;
|
||||||
'monitored': string;
|
'monitored': string;
|
||||||
'size': string;
|
'size': string;
|
||||||
'status': string;
|
'status': string;
|
||||||
|
|
|
@ -2,15 +2,19 @@ import PropTypes from 'prop-types';
|
||||||
import React, { Component } from 'react';
|
import React, { Component } from 'react';
|
||||||
import AlbumFormats from 'Album/AlbumFormats';
|
import AlbumFormats from 'Album/AlbumFormats';
|
||||||
import EpisodeStatusConnector from 'Album/EpisodeStatusConnector';
|
import EpisodeStatusConnector from 'Album/EpisodeStatusConnector';
|
||||||
|
import IndexerFlags from 'Album/IndexerFlags';
|
||||||
|
import Icon from 'Components/Icon';
|
||||||
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 { tooltipPositions } from 'Helpers/Props';
|
import { icons, kinds, tooltipPositions } from 'Helpers/Props';
|
||||||
import MediaInfoConnector from 'TrackFile/MediaInfoConnector';
|
import MediaInfoConnector from 'TrackFile/MediaInfoConnector';
|
||||||
import * as mediaInfoTypes from 'TrackFile/mediaInfoTypes';
|
import * as mediaInfoTypes from 'TrackFile/mediaInfoTypes';
|
||||||
import formatTimeSpan from 'Utilities/Date/formatTimeSpan';
|
import formatTimeSpan from 'Utilities/Date/formatTimeSpan';
|
||||||
import formatBytes from 'Utilities/Number/formatBytes';
|
import formatBytes from 'Utilities/Number/formatBytes';
|
||||||
import formatCustomFormatScore from 'Utilities/Number/formatCustomFormatScore';
|
import formatCustomFormatScore from 'Utilities/Number/formatCustomFormatScore';
|
||||||
|
import translate from 'Utilities/String/translate';
|
||||||
import TrackActionsCell from './TrackActionsCell';
|
import TrackActionsCell from './TrackActionsCell';
|
||||||
import styles from './TrackRow.css';
|
import styles from './TrackRow.css';
|
||||||
|
|
||||||
|
@ -32,6 +36,7 @@ class TrackRow extends Component {
|
||||||
trackFileSize,
|
trackFileSize,
|
||||||
customFormats,
|
customFormats,
|
||||||
customFormatScore,
|
customFormatScore,
|
||||||
|
indexerFlags,
|
||||||
columns,
|
columns,
|
||||||
deleteTrackFile
|
deleteTrackFile
|
||||||
} = this.props;
|
} = this.props;
|
||||||
|
@ -141,12 +146,30 @@ class TrackRow extends Component {
|
||||||
customFormats.length
|
customFormats.length
|
||||||
)}
|
)}
|
||||||
tooltip={<AlbumFormats formats={customFormats} />}
|
tooltip={<AlbumFormats formats={customFormats} />}
|
||||||
position={tooltipPositions.BOTTOM}
|
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 === 'size') {
|
if (name === 'size') {
|
||||||
return (
|
return (
|
||||||
<TableRowCell
|
<TableRowCell
|
||||||
|
@ -208,12 +231,14 @@ TrackRow.propTypes = {
|
||||||
trackFileSize: PropTypes.number,
|
trackFileSize: PropTypes.number,
|
||||||
customFormats: PropTypes.arrayOf(PropTypes.object),
|
customFormats: PropTypes.arrayOf(PropTypes.object),
|
||||||
customFormatScore: PropTypes.number.isRequired,
|
customFormatScore: PropTypes.number.isRequired,
|
||||||
|
indexerFlags: PropTypes.number.isRequired,
|
||||||
mediaInfo: PropTypes.object,
|
mediaInfo: PropTypes.object,
|
||||||
columns: PropTypes.arrayOf(PropTypes.object).isRequired
|
columns: PropTypes.arrayOf(PropTypes.object).isRequired
|
||||||
};
|
};
|
||||||
|
|
||||||
TrackRow.defaultProps = {
|
TrackRow.defaultProps = {
|
||||||
customFormats: []
|
customFormats: [],
|
||||||
|
indexerFlags: 0
|
||||||
};
|
};
|
||||||
|
|
||||||
export default TrackRow;
|
export default TrackRow;
|
||||||
|
|
|
@ -13,7 +13,8 @@ function createMapStateToProps() {
|
||||||
trackFilePath: trackFile ? trackFile.path : null,
|
trackFilePath: trackFile ? trackFile.path : null,
|
||||||
trackFileSize: trackFile ? trackFile.size : null,
|
trackFileSize: trackFile ? trackFile.size : null,
|
||||||
customFormats: trackFile ? trackFile.customFormats : [],
|
customFormats: trackFile ? trackFile.customFormats : [],
|
||||||
customFormatScore: trackFile ? trackFile.customFormatScore : 0
|
customFormatScore: trackFile ? trackFile.customFormatScore : 0,
|
||||||
|
indexerFlags: trackFile ? trackFile.indexerFlags : 0
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
|
@ -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;
|
|
@ -6,6 +6,7 @@ import AppSectionState, {
|
||||||
import DownloadClient from 'typings/DownloadClient';
|
import DownloadClient from 'typings/DownloadClient';
|
||||||
import ImportList from 'typings/ImportList';
|
import ImportList from 'typings/ImportList';
|
||||||
import Indexer from 'typings/Indexer';
|
import Indexer from 'typings/Indexer';
|
||||||
|
import IndexerFlag from 'typings/IndexerFlag';
|
||||||
import MetadataProfile from 'typings/MetadataProfile';
|
import MetadataProfile from 'typings/MetadataProfile';
|
||||||
import Notification from 'typings/Notification';
|
import Notification from 'typings/Notification';
|
||||||
import QualityProfile from 'typings/QualityProfile';
|
import QualityProfile from 'typings/QualityProfile';
|
||||||
|
@ -44,11 +45,13 @@ export interface RootFolderAppState
|
||||||
AppSectionDeleteState,
|
AppSectionDeleteState,
|
||||||
AppSectionSaveState {}
|
AppSectionSaveState {}
|
||||||
|
|
||||||
|
export type IndexerFlagSettingsAppState = AppSectionState<IndexerFlag>;
|
||||||
export type UiSettingsAppState = AppSectionState<UiSettings>;
|
export type UiSettingsAppState = AppSectionState<UiSettings>;
|
||||||
|
|
||||||
interface SettingsAppState {
|
interface SettingsAppState {
|
||||||
downloadClients: DownloadClientAppState;
|
downloadClients: DownloadClientAppState;
|
||||||
importLists: ImportListAppState;
|
importLists: ImportListAppState;
|
||||||
|
indexerFlags: IndexerFlagSettingsAppState;
|
||||||
indexers: IndexerAppState;
|
indexers: IndexerAppState;
|
||||||
metadataProfiles: MetadataProfilesAppState;
|
metadataProfiles: MetadataProfilesAppState;
|
||||||
notifications: NotificationAppState;
|
notifications: NotificationAppState;
|
||||||
|
|
|
@ -12,6 +12,7 @@ import DownloadClientSelectInputConnector from './DownloadClientSelectInputConne
|
||||||
import EnhancedSelectInput from './EnhancedSelectInput';
|
import EnhancedSelectInput from './EnhancedSelectInput';
|
||||||
import EnhancedSelectInputConnector from './EnhancedSelectInputConnector';
|
import EnhancedSelectInputConnector from './EnhancedSelectInputConnector';
|
||||||
import FormInputHelpText from './FormInputHelpText';
|
import FormInputHelpText from './FormInputHelpText';
|
||||||
|
import IndexerFlagsSelectInput from './IndexerFlagsSelectInput';
|
||||||
import IndexerSelectInputConnector from './IndexerSelectInputConnector';
|
import IndexerSelectInputConnector from './IndexerSelectInputConnector';
|
||||||
import KeyValueListInput from './KeyValueListInput';
|
import KeyValueListInput from './KeyValueListInput';
|
||||||
import MetadataProfileSelectInputConnector from './MetadataProfileSelectInputConnector';
|
import MetadataProfileSelectInputConnector from './MetadataProfileSelectInputConnector';
|
||||||
|
@ -83,6 +84,9 @@ function getComponent(type) {
|
||||||
case inputTypes.INDEXER_SELECT:
|
case inputTypes.INDEXER_SELECT:
|
||||||
return IndexerSelectInputConnector;
|
return IndexerSelectInputConnector;
|
||||||
|
|
||||||
|
case inputTypes.INDEXER_FLAGS_SELECT:
|
||||||
|
return IndexerFlagsSelectInput;
|
||||||
|
|
||||||
case inputTypes.DOWNLOAD_CLIENT_SELECT:
|
case inputTypes.DOWNLOAD_CLIENT_SELECT:
|
||||||
return DownloadClientSelectInputConnector;
|
return DownloadClientSelectInputConnector;
|
||||||
|
|
||||||
|
@ -292,6 +296,7 @@ FormInputGroup.propTypes = {
|
||||||
includeNoChangeDisabled: PropTypes.bool,
|
includeNoChangeDisabled: PropTypes.bool,
|
||||||
includeNone: PropTypes.bool,
|
includeNone: 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),
|
||||||
|
|
|
@ -0,0 +1,62 @@
|
||||||
|
import React, { useCallback } from 'react';
|
||||||
|
import { useSelector } from 'react-redux';
|
||||||
|
import { createSelector } from 'reselect';
|
||||||
|
import AppState from 'App/State/AppState';
|
||||||
|
import EnhancedSelectInput from './EnhancedSelectInput';
|
||||||
|
|
||||||
|
const selectIndexerFlagsValues = (selectedFlags: number) =>
|
||||||
|
createSelector(
|
||||||
|
(state: AppState) => state.settings.indexerFlags,
|
||||||
|
(indexerFlags) => {
|
||||||
|
const value = indexerFlags.items.reduce((acc: number[], { id }) => {
|
||||||
|
// eslint-disable-next-line no-bitwise
|
||||||
|
if ((selectedFlags & id) === id) {
|
||||||
|
acc.push(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
return acc;
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const values = indexerFlags.items.map(({ id, name }) => ({
|
||||||
|
key: id,
|
||||||
|
value: name,
|
||||||
|
}));
|
||||||
|
|
||||||
|
return {
|
||||||
|
value,
|
||||||
|
values,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
interface IndexerFlagsSelectInputProps {
|
||||||
|
name: string;
|
||||||
|
indexerFlags: number;
|
||||||
|
onChange(payload: object): void;
|
||||||
|
}
|
||||||
|
|
||||||
|
function IndexerFlagsSelectInput(props: IndexerFlagsSelectInputProps) {
|
||||||
|
const { indexerFlags, onChange } = props;
|
||||||
|
|
||||||
|
const { value, values } = useSelector(selectIndexerFlagsValues(indexerFlags));
|
||||||
|
|
||||||
|
const onChangeWrapper = useCallback(
|
||||||
|
({ name, value }: { name: string; value: number[] }) => {
|
||||||
|
const indexerFlags = value.reduce((acc, flagId) => acc + flagId, 0);
|
||||||
|
|
||||||
|
onChange({ name, value: indexerFlags });
|
||||||
|
},
|
||||||
|
[onChange]
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<EnhancedSelectInput
|
||||||
|
{...props}
|
||||||
|
value={value}
|
||||||
|
values={values}
|
||||||
|
onChange={onChangeWrapper}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default IndexerFlagsSelectInput;
|
|
@ -6,7 +6,14 @@ import { createSelector } from 'reselect';
|
||||||
import { fetchTranslations, saveDimensions, setIsSidebarVisible } from 'Store/Actions/appActions';
|
import { fetchTranslations, saveDimensions, setIsSidebarVisible } from 'Store/Actions/appActions';
|
||||||
import { fetchArtist } from 'Store/Actions/artistActions';
|
import { fetchArtist } from 'Store/Actions/artistActions';
|
||||||
import { fetchCustomFilters } from 'Store/Actions/customFilterActions';
|
import { fetchCustomFilters } from 'Store/Actions/customFilterActions';
|
||||||
import { fetchImportLists, fetchLanguages, fetchMetadataProfiles, fetchQualityProfiles, fetchUISettings } from 'Store/Actions/settingsActions';
|
import {
|
||||||
|
fetchImportLists,
|
||||||
|
fetchIndexerFlags,
|
||||||
|
fetchLanguages,
|
||||||
|
fetchMetadataProfiles,
|
||||||
|
fetchQualityProfiles,
|
||||||
|
fetchUISettings
|
||||||
|
} from 'Store/Actions/settingsActions';
|
||||||
import { fetchStatus } from 'Store/Actions/systemActions';
|
import { fetchStatus } from 'Store/Actions/systemActions';
|
||||||
import { fetchTags } from 'Store/Actions/tagActions';
|
import { fetchTags } from 'Store/Actions/tagActions';
|
||||||
import createDimensionsSelector from 'Store/Selectors/createDimensionsSelector';
|
import createDimensionsSelector from 'Store/Selectors/createDimensionsSelector';
|
||||||
|
@ -51,6 +58,7 @@ const selectIsPopulated = createSelector(
|
||||||
(state) => state.settings.qualityProfiles.isPopulated,
|
(state) => state.settings.qualityProfiles.isPopulated,
|
||||||
(state) => state.settings.metadataProfiles.isPopulated,
|
(state) => state.settings.metadataProfiles.isPopulated,
|
||||||
(state) => state.settings.importLists.isPopulated,
|
(state) => state.settings.importLists.isPopulated,
|
||||||
|
(state) => state.settings.indexerFlags.isPopulated,
|
||||||
(state) => state.system.status.isPopulated,
|
(state) => state.system.status.isPopulated,
|
||||||
(state) => state.app.translations.isPopulated,
|
(state) => state.app.translations.isPopulated,
|
||||||
(
|
(
|
||||||
|
@ -61,6 +69,7 @@ const selectIsPopulated = createSelector(
|
||||||
qualityProfilesIsPopulated,
|
qualityProfilesIsPopulated,
|
||||||
metadataProfilesIsPopulated,
|
metadataProfilesIsPopulated,
|
||||||
importListsIsPopulated,
|
importListsIsPopulated,
|
||||||
|
indexerFlagsIsPopulated,
|
||||||
systemStatusIsPopulated,
|
systemStatusIsPopulated,
|
||||||
translationsIsPopulated
|
translationsIsPopulated
|
||||||
) => {
|
) => {
|
||||||
|
@ -72,6 +81,7 @@ const selectIsPopulated = createSelector(
|
||||||
qualityProfilesIsPopulated &&
|
qualityProfilesIsPopulated &&
|
||||||
metadataProfilesIsPopulated &&
|
metadataProfilesIsPopulated &&
|
||||||
importListsIsPopulated &&
|
importListsIsPopulated &&
|
||||||
|
indexerFlagsIsPopulated &&
|
||||||
systemStatusIsPopulated &&
|
systemStatusIsPopulated &&
|
||||||
translationsIsPopulated
|
translationsIsPopulated
|
||||||
);
|
);
|
||||||
|
@ -86,6 +96,7 @@ const selectErrors = createSelector(
|
||||||
(state) => state.settings.qualityProfiles.error,
|
(state) => state.settings.qualityProfiles.error,
|
||||||
(state) => state.settings.metadataProfiles.error,
|
(state) => state.settings.metadataProfiles.error,
|
||||||
(state) => state.settings.importLists.error,
|
(state) => state.settings.importLists.error,
|
||||||
|
(state) => state.settings.indexerFlags.error,
|
||||||
(state) => state.system.status.error,
|
(state) => state.system.status.error,
|
||||||
(state) => state.app.translations.error,
|
(state) => state.app.translations.error,
|
||||||
(
|
(
|
||||||
|
@ -96,6 +107,7 @@ const selectErrors = createSelector(
|
||||||
qualityProfilesError,
|
qualityProfilesError,
|
||||||
metadataProfilesError,
|
metadataProfilesError,
|
||||||
importListsError,
|
importListsError,
|
||||||
|
indexerFlagsError,
|
||||||
systemStatusError,
|
systemStatusError,
|
||||||
translationsError
|
translationsError
|
||||||
) => {
|
) => {
|
||||||
|
@ -107,6 +119,7 @@ const selectErrors = createSelector(
|
||||||
qualityProfilesError ||
|
qualityProfilesError ||
|
||||||
metadataProfilesError ||
|
metadataProfilesError ||
|
||||||
importListsError ||
|
importListsError ||
|
||||||
|
indexerFlagsError ||
|
||||||
systemStatusError ||
|
systemStatusError ||
|
||||||
translationsError
|
translationsError
|
||||||
);
|
);
|
||||||
|
@ -120,6 +133,7 @@ const selectErrors = createSelector(
|
||||||
qualityProfilesError,
|
qualityProfilesError,
|
||||||
metadataProfilesError,
|
metadataProfilesError,
|
||||||
importListsError,
|
importListsError,
|
||||||
|
indexerFlagsError,
|
||||||
systemStatusError,
|
systemStatusError,
|
||||||
translationsError
|
translationsError
|
||||||
};
|
};
|
||||||
|
@ -177,6 +191,9 @@ function createMapDispatchToProps(dispatch, props) {
|
||||||
dispatchFetchImportLists() {
|
dispatchFetchImportLists() {
|
||||||
dispatch(fetchImportLists());
|
dispatch(fetchImportLists());
|
||||||
},
|
},
|
||||||
|
dispatchFetchIndexerFlags() {
|
||||||
|
dispatch(fetchIndexerFlags());
|
||||||
|
},
|
||||||
dispatchFetchUISettings() {
|
dispatchFetchUISettings() {
|
||||||
dispatch(fetchUISettings());
|
dispatch(fetchUISettings());
|
||||||
},
|
},
|
||||||
|
@ -217,6 +234,7 @@ class PageConnector extends Component {
|
||||||
this.props.dispatchFetchQualityProfiles();
|
this.props.dispatchFetchQualityProfiles();
|
||||||
this.props.dispatchFetchMetadataProfiles();
|
this.props.dispatchFetchMetadataProfiles();
|
||||||
this.props.dispatchFetchImportLists();
|
this.props.dispatchFetchImportLists();
|
||||||
|
this.props.dispatchFetchIndexerFlags();
|
||||||
this.props.dispatchFetchUISettings();
|
this.props.dispatchFetchUISettings();
|
||||||
this.props.dispatchFetchStatus();
|
this.props.dispatchFetchStatus();
|
||||||
this.props.dispatchFetchTranslations();
|
this.props.dispatchFetchTranslations();
|
||||||
|
@ -243,6 +261,7 @@ class PageConnector extends Component {
|
||||||
dispatchFetchQualityProfiles,
|
dispatchFetchQualityProfiles,
|
||||||
dispatchFetchMetadataProfiles,
|
dispatchFetchMetadataProfiles,
|
||||||
dispatchFetchImportLists,
|
dispatchFetchImportLists,
|
||||||
|
dispatchFetchIndexerFlags,
|
||||||
dispatchFetchUISettings,
|
dispatchFetchUISettings,
|
||||||
dispatchFetchStatus,
|
dispatchFetchStatus,
|
||||||
dispatchFetchTranslations,
|
dispatchFetchTranslations,
|
||||||
|
@ -284,6 +303,7 @@ PageConnector.propTypes = {
|
||||||
dispatchFetchQualityProfiles: PropTypes.func.isRequired,
|
dispatchFetchQualityProfiles: PropTypes.func.isRequired,
|
||||||
dispatchFetchMetadataProfiles: PropTypes.func.isRequired,
|
dispatchFetchMetadataProfiles: PropTypes.func.isRequired,
|
||||||
dispatchFetchImportLists: PropTypes.func.isRequired,
|
dispatchFetchImportLists: PropTypes.func.isRequired,
|
||||||
|
dispatchFetchIndexerFlags: PropTypes.func.isRequired,
|
||||||
dispatchFetchUISettings: PropTypes.func.isRequired,
|
dispatchFetchUISettings: PropTypes.func.isRequired,
|
||||||
dispatchFetchStatus: PropTypes.func.isRequired,
|
dispatchFetchStatus: PropTypes.func.isRequired,
|
||||||
dispatchFetchTranslations: PropTypes.func.isRequired,
|
dispatchFetchTranslations: PropTypes.func.isRequired,
|
||||||
|
|
|
@ -60,6 +60,7 @@ import {
|
||||||
faFileImport as fasFileImport,
|
faFileImport as fasFileImport,
|
||||||
faFileInvoice as farFileInvoice,
|
faFileInvoice as farFileInvoice,
|
||||||
faFilter as fasFilter,
|
faFilter as fasFilter,
|
||||||
|
faFlag as fasFlag,
|
||||||
faFolderOpen as fasFolderOpen,
|
faFolderOpen as fasFolderOpen,
|
||||||
faForward as fasForward,
|
faForward as fasForward,
|
||||||
faHeart as fasHeart,
|
faHeart as fasHeart,
|
||||||
|
@ -158,6 +159,7 @@ export const FILE = farFile;
|
||||||
export const FILE_IMPORT = fasFileImport;
|
export const FILE_IMPORT = fasFileImport;
|
||||||
export const FILE_MISSING = fasFileCircleQuestion;
|
export const FILE_MISSING = fasFileCircleQuestion;
|
||||||
export const FILTER = fasFilter;
|
export const FILTER = fasFilter;
|
||||||
|
export const FLAG = fasFlag;
|
||||||
export const FOLDER = farFolder;
|
export const FOLDER = farFolder;
|
||||||
export const FOLDER_OPEN = fasFolderOpen;
|
export const FOLDER_OPEN = fasFolderOpen;
|
||||||
export const GROUP = farObjectGroup;
|
export const GROUP = farObjectGroup;
|
||||||
|
|
|
@ -15,6 +15,7 @@ export const QUALITY_PROFILE_SELECT = 'qualityProfileSelect';
|
||||||
export const METADATA_PROFILE_SELECT = 'metadataProfileSelect';
|
export const METADATA_PROFILE_SELECT = 'metadataProfileSelect';
|
||||||
export const ALBUM_RELEASE_SELECT = 'albumReleaseSelect';
|
export const ALBUM_RELEASE_SELECT = 'albumReleaseSelect';
|
||||||
export const INDEXER_SELECT = 'indexerSelect';
|
export const INDEXER_SELECT = 'indexerSelect';
|
||||||
|
export const INDEXER_FLAGS_SELECT = 'indexerFlagsSelect';
|
||||||
export const DOWNLOAD_CLIENT_SELECT = 'downloadClientSelect';
|
export const DOWNLOAD_CLIENT_SELECT = 'downloadClientSelect';
|
||||||
export const ROOT_FOLDER_SELECT = 'rootFolderSelect';
|
export const ROOT_FOLDER_SELECT = 'rootFolderSelect';
|
||||||
export const SELECT = 'select';
|
export const SELECT = 'select';
|
||||||
|
|
|
@ -0,0 +1,37 @@
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import React, { Component } from 'react';
|
||||||
|
import Modal from 'Components/Modal/Modal';
|
||||||
|
import SelectIndexerFlagsModalContentConnector from './SelectIndexerFlagsModalContentConnector';
|
||||||
|
|
||||||
|
class SelectIndexerFlagsModal extends Component {
|
||||||
|
|
||||||
|
//
|
||||||
|
// Render
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const {
|
||||||
|
isOpen,
|
||||||
|
onModalClose,
|
||||||
|
...otherProps
|
||||||
|
} = this.props;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Modal
|
||||||
|
isOpen={isOpen}
|
||||||
|
onModalClose={onModalClose}
|
||||||
|
>
|
||||||
|
<SelectIndexerFlagsModalContentConnector
|
||||||
|
{...otherProps}
|
||||||
|
onModalClose={onModalClose}
|
||||||
|
/>
|
||||||
|
</Modal>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
SelectIndexerFlagsModal.propTypes = {
|
||||||
|
isOpen: PropTypes.bool.isRequired,
|
||||||
|
onModalClose: PropTypes.func.isRequired
|
||||||
|
};
|
||||||
|
|
||||||
|
export default SelectIndexerFlagsModal;
|
|
@ -0,0 +1,7 @@
|
||||||
|
.modalBody {
|
||||||
|
composes: modalBody from '~Components/Modal/ModalBody.css';
|
||||||
|
|
||||||
|
display: flex;
|
||||||
|
flex: 1 1 auto;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
7
frontend/src/InteractiveImport/IndexerFlags/SelectIndexerFlagsModalContent.css.d.ts
vendored
Normal file
7
frontend/src/InteractiveImport/IndexerFlags/SelectIndexerFlagsModalContent.css.d.ts
vendored
Normal file
|
@ -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,106 @@
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import React, { Component } 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';
|
||||||
|
|
||||||
|
class SelectIndexerFlagsModalContent extends Component {
|
||||||
|
|
||||||
|
//
|
||||||
|
// Lifecycle
|
||||||
|
|
||||||
|
constructor(props, context) {
|
||||||
|
super(props, context);
|
||||||
|
|
||||||
|
const {
|
||||||
|
indexerFlags
|
||||||
|
} = props;
|
||||||
|
|
||||||
|
this.state = {
|
||||||
|
indexerFlags
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
//
|
||||||
|
// Listeners
|
||||||
|
|
||||||
|
onIndexerFlagsChange = ({ value }) => {
|
||||||
|
this.setState({ indexerFlags: value });
|
||||||
|
};
|
||||||
|
|
||||||
|
onIndexerFlagsSelect = () => {
|
||||||
|
this.props.onIndexerFlagsSelect(this.state);
|
||||||
|
};
|
||||||
|
|
||||||
|
//
|
||||||
|
// Render
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const {
|
||||||
|
onModalClose
|
||||||
|
} = this.props;
|
||||||
|
|
||||||
|
const {
|
||||||
|
indexerFlags
|
||||||
|
} = this.state;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ModalContent onModalClose={onModalClose}>
|
||||||
|
<ModalHeader>
|
||||||
|
Manual Import - Set indexer Flags
|
||||||
|
</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={this.onIndexerFlagsChange}
|
||||||
|
/>
|
||||||
|
</FormGroup>
|
||||||
|
</Form>
|
||||||
|
</ModalBody>
|
||||||
|
|
||||||
|
<ModalFooter>
|
||||||
|
<Button onPress={onModalClose}>
|
||||||
|
{translate('Cancel')}
|
||||||
|
</Button>
|
||||||
|
|
||||||
|
<Button
|
||||||
|
kind={kinds.SUCCESS}
|
||||||
|
onPress={this.onIndexerFlagsSelect}
|
||||||
|
>
|
||||||
|
{translate('SetIndexerFlags')}
|
||||||
|
</Button>
|
||||||
|
</ModalFooter>
|
||||||
|
</ModalContent>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
SelectIndexerFlagsModalContent.propTypes = {
|
||||||
|
indexerFlags: PropTypes.number.isRequired,
|
||||||
|
onIndexerFlagsSelect: PropTypes.func.isRequired,
|
||||||
|
onModalClose: PropTypes.func.isRequired
|
||||||
|
};
|
||||||
|
|
||||||
|
export default SelectIndexerFlagsModalContent;
|
|
@ -0,0 +1,54 @@
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import React, { Component } from 'react';
|
||||||
|
import { connect } from 'react-redux';
|
||||||
|
import { saveInteractiveImportItem, updateInteractiveImportItems } from 'Store/Actions/interactiveImportActions';
|
||||||
|
import SelectIndexerFlagsModalContent from './SelectIndexerFlagsModalContent';
|
||||||
|
|
||||||
|
const mapDispatchToProps = {
|
||||||
|
dispatchUpdateInteractiveImportItems: updateInteractiveImportItems,
|
||||||
|
dispatchSaveInteractiveImportItems: saveInteractiveImportItem
|
||||||
|
};
|
||||||
|
|
||||||
|
class SelectIndexerFlagsModalContentConnector extends Component {
|
||||||
|
|
||||||
|
//
|
||||||
|
// Listeners
|
||||||
|
|
||||||
|
onIndexerFlagsSelect = ({ indexerFlags }) => {
|
||||||
|
const {
|
||||||
|
ids,
|
||||||
|
dispatchUpdateInteractiveImportItems,
|
||||||
|
dispatchSaveInteractiveImportItems
|
||||||
|
} = this.props;
|
||||||
|
|
||||||
|
dispatchUpdateInteractiveImportItems({
|
||||||
|
ids,
|
||||||
|
indexerFlags
|
||||||
|
});
|
||||||
|
|
||||||
|
dispatchSaveInteractiveImportItems({ ids });
|
||||||
|
|
||||||
|
this.props.onModalClose(true);
|
||||||
|
};
|
||||||
|
|
||||||
|
//
|
||||||
|
// Render
|
||||||
|
|
||||||
|
render() {
|
||||||
|
return (
|
||||||
|
<SelectIndexerFlagsModalContent
|
||||||
|
{...this.props}
|
||||||
|
onIndexerFlagsSelect={this.onIndexerFlagsSelect}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
SelectIndexerFlagsModalContentConnector.propTypes = {
|
||||||
|
ids: PropTypes.arrayOf(PropTypes.number).isRequired,
|
||||||
|
dispatchUpdateInteractiveImportItems: PropTypes.func.isRequired,
|
||||||
|
dispatchSaveInteractiveImportItems: PropTypes.func.isRequired,
|
||||||
|
onModalClose: PropTypes.func.isRequired
|
||||||
|
};
|
||||||
|
|
||||||
|
export default connect(null, mapDispatchToProps)(SelectIndexerFlagsModalContentConnector);
|
|
@ -20,6 +20,7 @@ import SelectAlbumModal from 'InteractiveImport/Album/SelectAlbumModal';
|
||||||
import SelectAlbumReleaseModal from 'InteractiveImport/AlbumRelease/SelectAlbumReleaseModal';
|
import SelectAlbumReleaseModal from 'InteractiveImport/AlbumRelease/SelectAlbumReleaseModal';
|
||||||
import SelectArtistModal from 'InteractiveImport/Artist/SelectArtistModal';
|
import SelectArtistModal from 'InteractiveImport/Artist/SelectArtistModal';
|
||||||
import ConfirmImportModal from 'InteractiveImport/Confirmation/ConfirmImportModal';
|
import ConfirmImportModal from 'InteractiveImport/Confirmation/ConfirmImportModal';
|
||||||
|
import SelectIndexerFlagsModal from 'InteractiveImport/IndexerFlags/SelectIndexerFlagsModal';
|
||||||
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 getErrorMessage from 'Utilities/Object/getErrorMessage';
|
import getErrorMessage from 'Utilities/Object/getErrorMessage';
|
||||||
|
@ -30,7 +31,7 @@ import toggleSelected from 'Utilities/Table/toggleSelected';
|
||||||
import InteractiveImportRow from './InteractiveImportRow';
|
import InteractiveImportRow from './InteractiveImportRow';
|
||||||
import styles from './InteractiveImportModalContent.css';
|
import styles from './InteractiveImportModalContent.css';
|
||||||
|
|
||||||
const columns = [
|
const COLUMNS = [
|
||||||
{
|
{
|
||||||
name: 'path',
|
name: 'path',
|
||||||
label: () => translate('Path'),
|
label: () => translate('Path'),
|
||||||
|
@ -79,11 +80,21 @@ 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, {
|
||||||
name: icons.DANGER,
|
name: icons.DANGER,
|
||||||
kind: kinds.DANGER
|
kind: kinds.DANGER,
|
||||||
|
title: () => translate('Rejections')
|
||||||
}),
|
}),
|
||||||
isSortable: true,
|
isSortable: true,
|
||||||
isVisible: true
|
isVisible: true
|
||||||
|
@ -107,6 +118,7 @@ const ALBUM = 'album';
|
||||||
const ALBUM_RELEASE = 'albumRelease';
|
const ALBUM_RELEASE = 'albumRelease';
|
||||||
const RELEASE_GROUP = 'releaseGroup';
|
const RELEASE_GROUP = 'releaseGroup';
|
||||||
const QUALITY = 'quality';
|
const QUALITY = 'quality';
|
||||||
|
const INDEXER_FLAGS = 'indexerFlags';
|
||||||
|
|
||||||
const replaceExistingFilesOptions = {
|
const replaceExistingFilesOptions = {
|
||||||
COMBINE: 'combine',
|
COMBINE: 'combine',
|
||||||
|
@ -301,6 +313,21 @@ class InteractiveImportModalContent extends Component {
|
||||||
inconsistentAlbumReleases
|
inconsistentAlbumReleases
|
||||||
} = this.state;
|
} = this.state;
|
||||||
|
|
||||||
|
const allColumns = _.cloneDeep(COLUMNS);
|
||||||
|
const columns = allColumns.map((column) => {
|
||||||
|
const showIndexerFlags = items.some((item) => item.indexerFlags);
|
||||||
|
|
||||||
|
if (!showIndexerFlags) {
|
||||||
|
const indexerFlagsColumn = allColumns.find((c) => c.name === 'indexerFlags');
|
||||||
|
|
||||||
|
if (indexerFlagsColumn) {
|
||||||
|
indexerFlagsColumn.isVisible = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return column;
|
||||||
|
});
|
||||||
|
|
||||||
const selectedIds = this.getSelectedIds();
|
const selectedIds = this.getSelectedIds();
|
||||||
const selectedItem = selectedIds.length ? _.find(items, { id: selectedIds[0] }) : null;
|
const selectedItem = selectedIds.length ? _.find(items, { id: selectedIds[0] }) : null;
|
||||||
const errorMessage = getErrorMessage(error, 'Unable to load manual import items');
|
const errorMessage = getErrorMessage(error, 'Unable to load manual import items');
|
||||||
|
@ -310,7 +337,8 @@ class InteractiveImportModalContent extends Component {
|
||||||
{ key: ALBUM, value: translate('SelectAlbum') },
|
{ key: ALBUM, value: translate('SelectAlbum') },
|
||||||
{ key: ALBUM_RELEASE, value: translate('SelectAlbumRelease') },
|
{ key: ALBUM_RELEASE, value: translate('SelectAlbumRelease') },
|
||||||
{ key: QUALITY, value: translate('SelectQuality') },
|
{ key: QUALITY, value: translate('SelectQuality') },
|
||||||
{ key: RELEASE_GROUP, value: translate('SelectReleaseGroup') }
|
{ key: RELEASE_GROUP, value: translate('SelectReleaseGroup') },
|
||||||
|
{ key: INDEXER_FLAGS, value: translate('SelectIndexerFlags') }
|
||||||
];
|
];
|
||||||
|
|
||||||
if (allowArtistChange) {
|
if (allowArtistChange) {
|
||||||
|
@ -433,6 +461,7 @@ class InteractiveImportModalContent extends Component {
|
||||||
isSaving={isSaving}
|
isSaving={isSaving}
|
||||||
{...item}
|
{...item}
|
||||||
allowArtistChange={allowArtistChange}
|
allowArtistChange={allowArtistChange}
|
||||||
|
columns={columns}
|
||||||
onSelectedChange={this.onSelectedChange}
|
onSelectedChange={this.onSelectedChange}
|
||||||
onValidRowChange={this.onValidRowChange}
|
onValidRowChange={this.onValidRowChange}
|
||||||
/>
|
/>
|
||||||
|
@ -547,6 +576,13 @@ class InteractiveImportModalContent extends Component {
|
||||||
onModalClose={this.onSelectModalClose}
|
onModalClose={this.onSelectModalClose}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
<SelectIndexerFlagsModal
|
||||||
|
isOpen={selectModalOpen === INDEXER_FLAGS}
|
||||||
|
ids={selectedIds}
|
||||||
|
indexerFlags={0}
|
||||||
|
onModalClose={this.onSelectModalClose}
|
||||||
|
/>
|
||||||
|
|
||||||
<ConfirmImportModal
|
<ConfirmImportModal
|
||||||
isOpen={isConfirmImportModalOpen}
|
isOpen={isConfirmImportModalOpen}
|
||||||
albums={albumsImported}
|
albums={albumsImported}
|
||||||
|
|
|
@ -135,6 +135,7 @@ class InteractiveImportModalContentConnector extends Component {
|
||||||
albumReleaseId,
|
albumReleaseId,
|
||||||
tracks,
|
tracks,
|
||||||
quality,
|
quality,
|
||||||
|
indexerFlags,
|
||||||
disableReleaseSwitching
|
disableReleaseSwitching
|
||||||
} = item;
|
} = item;
|
||||||
|
|
||||||
|
@ -165,6 +166,7 @@ class InteractiveImportModalContentConnector extends Component {
|
||||||
albumReleaseId,
|
albumReleaseId,
|
||||||
trackIds: _.map(tracks, 'id'),
|
trackIds: _.map(tracks, 'id'),
|
||||||
quality,
|
quality,
|
||||||
|
indexerFlags,
|
||||||
downloadId: this.props.downloadId,
|
downloadId: this.props.downloadId,
|
||||||
disableReleaseSwitching
|
disableReleaseSwitching
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import React, { Component } from 'react';
|
import React, { Component } from 'react';
|
||||||
import AlbumFormats from 'Album/AlbumFormats';
|
import AlbumFormats from 'Album/AlbumFormats';
|
||||||
|
import IndexerFlags from 'Album/IndexerFlags';
|
||||||
import TrackQuality from 'Album/TrackQuality';
|
import TrackQuality from 'Album/TrackQuality';
|
||||||
import Icon from 'Components/Icon';
|
import Icon from 'Components/Icon';
|
||||||
import LoadingIndicator from 'Components/Loading/LoadingIndicator';
|
import LoadingIndicator from 'Components/Loading/LoadingIndicator';
|
||||||
|
@ -13,6 +14,7 @@ import Tooltip from 'Components/Tooltip/Tooltip';
|
||||||
import { icons, kinds, sortDirections, tooltipPositions } from 'Helpers/Props';
|
import { icons, kinds, sortDirections, tooltipPositions } from 'Helpers/Props';
|
||||||
import SelectAlbumModal from 'InteractiveImport/Album/SelectAlbumModal';
|
import SelectAlbumModal from 'InteractiveImport/Album/SelectAlbumModal';
|
||||||
import SelectArtistModal from 'InteractiveImport/Artist/SelectArtistModal';
|
import SelectArtistModal from 'InteractiveImport/Artist/SelectArtistModal';
|
||||||
|
import SelectIndexerFlagsModal from 'InteractiveImport/IndexerFlags/SelectIndexerFlagsModal';
|
||||||
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 SelectTrackModal from 'InteractiveImport/Track/SelectTrackModal';
|
import SelectTrackModal from 'InteractiveImport/Track/SelectTrackModal';
|
||||||
|
@ -35,7 +37,8 @@ class InteractiveImportRow extends Component {
|
||||||
isSelectAlbumModalOpen: false,
|
isSelectAlbumModalOpen: false,
|
||||||
isSelectTrackModalOpen: false,
|
isSelectTrackModalOpen: false,
|
||||||
isSelectReleaseGroupModalOpen: false,
|
isSelectReleaseGroupModalOpen: false,
|
||||||
isSelectQualityModalOpen: false
|
isSelectQualityModalOpen: false,
|
||||||
|
isSelectIndexerFlagsModalOpen: false
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -130,6 +133,10 @@ class InteractiveImportRow extends Component {
|
||||||
this.setState({ isSelectQualityModalOpen: true });
|
this.setState({ isSelectQualityModalOpen: true });
|
||||||
};
|
};
|
||||||
|
|
||||||
|
onSelectIndexerFlagsPress = () => {
|
||||||
|
this.setState({ isSelectIndexerFlagsModalOpen: true });
|
||||||
|
};
|
||||||
|
|
||||||
onSelectArtistModalClose = (changed) => {
|
onSelectArtistModalClose = (changed) => {
|
||||||
this.setState({ isSelectArtistModalOpen: false });
|
this.setState({ isSelectArtistModalOpen: false });
|
||||||
this.selectRowAfterChange(changed);
|
this.selectRowAfterChange(changed);
|
||||||
|
@ -155,6 +162,11 @@ class InteractiveImportRow extends Component {
|
||||||
this.selectRowAfterChange(changed);
|
this.selectRowAfterChange(changed);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
onSelectIndexerFlagsModalClose = (changed) => {
|
||||||
|
this.setState({ isSelectIndexerFlagsModalOpen: false });
|
||||||
|
this.selectRowAfterChange(changed);
|
||||||
|
};
|
||||||
|
|
||||||
//
|
//
|
||||||
// Render
|
// Render
|
||||||
|
|
||||||
|
@ -171,7 +183,9 @@ class InteractiveImportRow extends Component {
|
||||||
releaseGroup,
|
releaseGroup,
|
||||||
size,
|
size,
|
||||||
customFormats,
|
customFormats,
|
||||||
|
indexerFlags,
|
||||||
rejections,
|
rejections,
|
||||||
|
columns,
|
||||||
isReprocessing,
|
isReprocessing,
|
||||||
audioTags,
|
audioTags,
|
||||||
additionalFile,
|
additionalFile,
|
||||||
|
@ -184,7 +198,8 @@ class InteractiveImportRow extends Component {
|
||||||
isSelectAlbumModalOpen,
|
isSelectAlbumModalOpen,
|
||||||
isSelectTrackModalOpen,
|
isSelectTrackModalOpen,
|
||||||
isSelectReleaseGroupModalOpen,
|
isSelectReleaseGroupModalOpen,
|
||||||
isSelectQualityModalOpen
|
isSelectQualityModalOpen,
|
||||||
|
isSelectIndexerFlagsModalOpen
|
||||||
} = this.state;
|
} = this.state;
|
||||||
|
|
||||||
const artistName = artist ? artist.artistName : '';
|
const artistName = artist ? artist.artistName : '';
|
||||||
|
@ -204,6 +219,7 @@ class InteractiveImportRow extends Component {
|
||||||
const showTrackNumbersLoading = isReprocessing && !tracks.length;
|
const showTrackNumbersLoading = isReprocessing && !tracks.length;
|
||||||
const showReleaseGroupPlaceholder = isSelected && !releaseGroup;
|
const showReleaseGroupPlaceholder = isSelected && !releaseGroup;
|
||||||
const showQualityPlaceholder = isSelected && !quality;
|
const showQualityPlaceholder = isSelected && !quality;
|
||||||
|
const showIndexerFlagsPlaceholder = isSelected && !indexerFlags;
|
||||||
|
|
||||||
const pathCellContents = (
|
const pathCellContents = (
|
||||||
<div>
|
<div>
|
||||||
|
@ -219,6 +235,8 @@ class InteractiveImportRow extends Component {
|
||||||
/>
|
/>
|
||||||
) : pathCellContents;
|
) : pathCellContents;
|
||||||
|
|
||||||
|
const isIndexerFlagsColumnVisible = columns.find((c) => c.name === 'indexerFlags')?.isVisible ?? false;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<TableRow
|
<TableRow
|
||||||
className={additionalFile ? styles.additionalFile : undefined}
|
className={additionalFile ? styles.additionalFile : undefined}
|
||||||
|
@ -322,6 +340,28 @@ class InteractiveImportRow extends Component {
|
||||||
}
|
}
|
||||||
</TableRowCell>
|
</TableRowCell>
|
||||||
|
|
||||||
|
{isIndexerFlagsColumnVisible ? (
|
||||||
|
<TableRowCellButton
|
||||||
|
title={translate('ClickToChangeIndexerFlags')}
|
||||||
|
onPress={this.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 ?
|
||||||
|
@ -395,6 +435,13 @@ class InteractiveImportRow extends Component {
|
||||||
real={quality ? quality.revision.real > 0 : false}
|
real={quality ? quality.revision.real > 0 : false}
|
||||||
onModalClose={this.onSelectQualityModalClose}
|
onModalClose={this.onSelectQualityModalClose}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
<SelectIndexerFlagsModal
|
||||||
|
isOpen={isSelectIndexerFlagsModalOpen}
|
||||||
|
ids={[id]}
|
||||||
|
indexerFlags={indexerFlags ?? 0}
|
||||||
|
onModalClose={this.onSelectIndexerFlagsModalClose}
|
||||||
|
/>
|
||||||
</TableRow>
|
</TableRow>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -413,7 +460,9 @@ InteractiveImportRow.propTypes = {
|
||||||
quality: PropTypes.object,
|
quality: PropTypes.object,
|
||||||
size: PropTypes.number.isRequired,
|
size: PropTypes.number.isRequired,
|
||||||
customFormats: PropTypes.arrayOf(PropTypes.object),
|
customFormats: PropTypes.arrayOf(PropTypes.object),
|
||||||
|
indexerFlags: PropTypes.number.isRequired,
|
||||||
rejections: PropTypes.arrayOf(PropTypes.object).isRequired,
|
rejections: PropTypes.arrayOf(PropTypes.object).isRequired,
|
||||||
|
columns: PropTypes.arrayOf(PropTypes.object).isRequired,
|
||||||
audioTags: PropTypes.object.isRequired,
|
audioTags: PropTypes.object.isRequired,
|
||||||
additionalFile: PropTypes.bool.isRequired,
|
additionalFile: PropTypes.bool.isRequired,
|
||||||
isReprocessing: PropTypes.bool,
|
isReprocessing: PropTypes.bool,
|
||||||
|
|
|
@ -65,6 +65,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, {
|
||||||
|
|
|
@ -35,6 +35,7 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
.rejected,
|
.rejected,
|
||||||
|
.indexerFlags,
|
||||||
.download {
|
.download {
|
||||||
composes: cell from '~Components/Table/Cells/TableRowCell.css';
|
composes: cell from '~Components/Table/Cells/TableRowCell.css';
|
||||||
|
|
||||||
|
|
|
@ -5,6 +5,7 @@ interface CssExports {
|
||||||
'customFormatScore': string;
|
'customFormatScore': string;
|
||||||
'download': string;
|
'download': string;
|
||||||
'indexer': string;
|
'indexer': string;
|
||||||
|
'indexerFlags': string;
|
||||||
'peers': string;
|
'peers': string;
|
||||||
'protocol': string;
|
'protocol': string;
|
||||||
'quality': string;
|
'quality': string;
|
||||||
|
|
|
@ -2,6 +2,7 @@ import PropTypes from 'prop-types';
|
||||||
import React, { Component } from 'react';
|
import React, { Component } from 'react';
|
||||||
import ProtocolLabel from 'Activity/Queue/ProtocolLabel';
|
import ProtocolLabel from 'Activity/Queue/ProtocolLabel';
|
||||||
import AlbumFormats from 'Album/AlbumFormats';
|
import AlbumFormats from 'Album/AlbumFormats';
|
||||||
|
import IndexerFlags from 'Album/IndexerFlags';
|
||||||
import TrackQuality from 'Album/TrackQuality';
|
import TrackQuality from 'Album/TrackQuality';
|
||||||
import Icon from 'Components/Icon';
|
import Icon from 'Components/Icon';
|
||||||
import Link from 'Components/Link/Link';
|
import Link from 'Components/Link/Link';
|
||||||
|
@ -129,6 +130,7 @@ class InteractiveSearchRow extends Component {
|
||||||
quality,
|
quality,
|
||||||
customFormatScore,
|
customFormatScore,
|
||||||
customFormats,
|
customFormats,
|
||||||
|
indexerFlags = 0,
|
||||||
rejections,
|
rejections,
|
||||||
downloadAllowed,
|
downloadAllowed,
|
||||||
isGrabbing,
|
isGrabbing,
|
||||||
|
@ -187,10 +189,21 @@ class InteractiveSearchRow extends Component {
|
||||||
formatCustomFormatScore(customFormatScore, customFormats.length)
|
formatCustomFormatScore(customFormatScore, customFormats.length)
|
||||||
}
|
}
|
||||||
tooltip={<AlbumFormats formats={customFormats} />}
|
tooltip={<AlbumFormats formats={customFormats} />}
|
||||||
position={tooltipPositions.BOTTOM}
|
position={tooltipPositions.LEFT}
|
||||||
/>
|
/>
|
||||||
</TableRowCell>
|
</TableRowCell>
|
||||||
|
|
||||||
|
<TableRowCell 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>
|
||||||
|
|
||||||
<TableRowCell className={styles.rejected}>
|
<TableRowCell className={styles.rejected}>
|
||||||
{
|
{
|
||||||
!!rejections.length &&
|
!!rejections.length &&
|
||||||
|
@ -265,6 +278,7 @@ InteractiveSearchRow.propTypes = {
|
||||||
quality: PropTypes.object.isRequired,
|
quality: PropTypes.object.isRequired,
|
||||||
customFormats: PropTypes.arrayOf(PropTypes.object),
|
customFormats: PropTypes.arrayOf(PropTypes.object),
|
||||||
customFormatScore: PropTypes.number.isRequired,
|
customFormatScore: PropTypes.number.isRequired,
|
||||||
|
indexerFlags: PropTypes.number.isRequired,
|
||||||
rejections: PropTypes.arrayOf(PropTypes.string).isRequired,
|
rejections: PropTypes.arrayOf(PropTypes.string).isRequired,
|
||||||
downloadAllowed: PropTypes.bool.isRequired,
|
downloadAllowed: PropTypes.bool.isRequired,
|
||||||
isGrabbing: PropTypes.bool.isRequired,
|
isGrabbing: PropTypes.bool.isRequired,
|
||||||
|
@ -277,6 +291,7 @@ InteractiveSearchRow.propTypes = {
|
||||||
};
|
};
|
||||||
|
|
||||||
InteractiveSearchRow.defaultProps = {
|
InteractiveSearchRow.defaultProps = {
|
||||||
|
indexerFlags: 0,
|
||||||
rejections: [],
|
rejections: [],
|
||||||
isGrabbing: false,
|
isGrabbing: false,
|
||||||
isGrabbed: false
|
isGrabbed: false
|
||||||
|
|
|
@ -0,0 +1,48 @@
|
||||||
|
import createFetchHandler from 'Store/Actions/Creators/createFetchHandler';
|
||||||
|
import { createThunk } from 'Store/thunks';
|
||||||
|
|
||||||
|
//
|
||||||
|
// Variables
|
||||||
|
|
||||||
|
const section = 'settings.indexerFlags';
|
||||||
|
|
||||||
|
//
|
||||||
|
// Actions Types
|
||||||
|
|
||||||
|
export const FETCH_INDEXER_FLAGS = 'settings/indexerFlags/fetchIndexerFlags';
|
||||||
|
|
||||||
|
//
|
||||||
|
// Action Creators
|
||||||
|
|
||||||
|
export const fetchIndexerFlags = createThunk(FETCH_INDEXER_FLAGS);
|
||||||
|
|
||||||
|
//
|
||||||
|
// Details
|
||||||
|
|
||||||
|
export default {
|
||||||
|
|
||||||
|
//
|
||||||
|
// State
|
||||||
|
|
||||||
|
defaultState: {
|
||||||
|
isFetching: false,
|
||||||
|
isPopulated: false,
|
||||||
|
error: null,
|
||||||
|
items: []
|
||||||
|
},
|
||||||
|
|
||||||
|
//
|
||||||
|
// Action Handlers
|
||||||
|
|
||||||
|
actionHandlers: {
|
||||||
|
[FETCH_INDEXER_FLAGS]: createFetchHandler(section, '/indexerFlag')
|
||||||
|
},
|
||||||
|
|
||||||
|
//
|
||||||
|
// Reducers
|
||||||
|
|
||||||
|
reducers: {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
};
|
|
@ -208,6 +208,7 @@ export const actionHandlers = handleThunks({
|
||||||
trackIds: (item.tracks || []).map((e) => e.id),
|
trackIds: (item.tracks || []).map((e) => e.id),
|
||||||
quality: item.quality,
|
quality: item.quality,
|
||||||
releaseGroup: item.releaseGroup,
|
releaseGroup: item.releaseGroup,
|
||||||
|
indexerFlags: item.indexerFlags,
|
||||||
downloadId: item.downloadId,
|
downloadId: item.downloadId,
|
||||||
additionalFile: item.additionalFile,
|
additionalFile: item.additionalFile,
|
||||||
replaceExistingFiles: item.replaceExistingFiles,
|
replaceExistingFiles: item.replaceExistingFiles,
|
||||||
|
|
|
@ -11,6 +11,7 @@ import downloadClients from './Settings/downloadClients';
|
||||||
import general from './Settings/general';
|
import general from './Settings/general';
|
||||||
import importListExclusions from './Settings/importListExclusions';
|
import importListExclusions from './Settings/importListExclusions';
|
||||||
import importLists from './Settings/importLists';
|
import importLists from './Settings/importLists';
|
||||||
|
import indexerFlags from './Settings/indexerFlags';
|
||||||
import indexerOptions from './Settings/indexerOptions';
|
import indexerOptions from './Settings/indexerOptions';
|
||||||
import indexers from './Settings/indexers';
|
import indexers from './Settings/indexers';
|
||||||
import languages from './Settings/languages';
|
import languages from './Settings/languages';
|
||||||
|
@ -38,6 +39,7 @@ export * from './Settings/downloadClientOptions';
|
||||||
export * from './Settings/general';
|
export * from './Settings/general';
|
||||||
export * from './Settings/importLists';
|
export * from './Settings/importLists';
|
||||||
export * from './Settings/importListExclusions';
|
export * from './Settings/importListExclusions';
|
||||||
|
export * from './Settings/indexerFlags';
|
||||||
export * from './Settings/indexerOptions';
|
export * from './Settings/indexerOptions';
|
||||||
export * from './Settings/indexers';
|
export * from './Settings/indexers';
|
||||||
export * from './Settings/languages';
|
export * from './Settings/languages';
|
||||||
|
@ -73,6 +75,7 @@ export const defaultState = {
|
||||||
downloadClients: downloadClients.defaultState,
|
downloadClients: downloadClients.defaultState,
|
||||||
downloadClientOptions: downloadClientOptions.defaultState,
|
downloadClientOptions: downloadClientOptions.defaultState,
|
||||||
general: general.defaultState,
|
general: general.defaultState,
|
||||||
|
indexerFlags: indexerFlags.defaultState,
|
||||||
indexerOptions: indexerOptions.defaultState,
|
indexerOptions: indexerOptions.defaultState,
|
||||||
indexers: indexers.defaultState,
|
indexers: indexers.defaultState,
|
||||||
importLists: importLists.defaultState,
|
importLists: importLists.defaultState,
|
||||||
|
@ -119,6 +122,7 @@ export const actionHandlers = handleThunks({
|
||||||
...downloadClients.actionHandlers,
|
...downloadClients.actionHandlers,
|
||||||
...downloadClientOptions.actionHandlers,
|
...downloadClientOptions.actionHandlers,
|
||||||
...general.actionHandlers,
|
...general.actionHandlers,
|
||||||
|
...indexerFlags.actionHandlers,
|
||||||
...indexerOptions.actionHandlers,
|
...indexerOptions.actionHandlers,
|
||||||
...indexers.actionHandlers,
|
...indexers.actionHandlers,
|
||||||
...importLists.actionHandlers,
|
...importLists.actionHandlers,
|
||||||
|
@ -156,6 +160,7 @@ export const reducers = createHandleActions({
|
||||||
...downloadClients.reducers,
|
...downloadClients.reducers,
|
||||||
...downloadClientOptions.reducers,
|
...downloadClientOptions.reducers,
|
||||||
...general.reducers,
|
...general.reducers,
|
||||||
|
...indexerFlags.reducers,
|
||||||
...indexerOptions.reducers,
|
...indexerOptions.reducers,
|
||||||
...indexers.reducers,
|
...indexers.reducers,
|
||||||
...importLists.reducers,
|
...importLists.reducers,
|
||||||
|
|
|
@ -77,6 +77,15 @@ export const defaultState = {
|
||||||
}),
|
}),
|
||||||
isVisible: false
|
isVisible: false
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: 'indexerFlags',
|
||||||
|
columnLabel: () => translate('IndexerFlags'),
|
||||||
|
label: React.createElement(Icon, {
|
||||||
|
name: icons.FLAG,
|
||||||
|
title: () => translate('IndexerFlags')
|
||||||
|
}),
|
||||||
|
isVisible: false
|
||||||
|
},
|
||||||
{
|
{
|
||||||
name: 'status',
|
name: 'status',
|
||||||
label: () => translate('Status'),
|
label: () => translate('Status'),
|
||||||
|
|
|
@ -0,0 +1,9 @@
|
||||||
|
import { createSelector } from 'reselect';
|
||||||
|
import AppState from 'App/State/AppState';
|
||||||
|
|
||||||
|
const createIndexerFlagsSelector = createSelector(
|
||||||
|
(state: AppState) => state.settings.indexerFlags,
|
||||||
|
(indexerFlags) => indexerFlags
|
||||||
|
);
|
||||||
|
|
||||||
|
export default createIndexerFlagsSelector;
|
|
@ -13,6 +13,7 @@ export interface TrackFile extends ModelBase {
|
||||||
releaseGroup: string;
|
releaseGroup: string;
|
||||||
quality: QualityModel;
|
quality: QualityModel;
|
||||||
customFormats: CustomFormat[];
|
customFormats: CustomFormat[];
|
||||||
|
indexerFlags: number;
|
||||||
mediaInfo: MediaInfo;
|
mediaInfo: MediaInfo;
|
||||||
qualityCutoffNotMet: boolean;
|
qualityCutoffNotMet: boolean;
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,6 @@
|
||||||
|
interface IndexerFlag {
|
||||||
|
id: number;
|
||||||
|
name: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default IndexerFlag;
|
|
@ -0,0 +1,23 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using Lidarr.Http;
|
||||||
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
using NzbDrone.Core.Parser.Model;
|
||||||
|
|
||||||
|
namespace Lidarr.Api.V1.Indexers
|
||||||
|
{
|
||||||
|
[V1ApiController]
|
||||||
|
public class IndexerFlagController : Controller
|
||||||
|
{
|
||||||
|
[HttpGet]
|
||||||
|
public List<IndexerFlagResource> GetAll()
|
||||||
|
{
|
||||||
|
return Enum.GetValues(typeof(IndexerFlags)).Cast<IndexerFlags>().Select(f => new IndexerFlagResource
|
||||||
|
{
|
||||||
|
Id = (int)f,
|
||||||
|
Name = f.ToString()
|
||||||
|
}).ToList();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,13 @@
|
||||||
|
using Lidarr.Http.REST;
|
||||||
|
using Newtonsoft.Json;
|
||||||
|
|
||||||
|
namespace Lidarr.Api.V1.Indexers
|
||||||
|
{
|
||||||
|
public class IndexerFlagResource : RestResource
|
||||||
|
{
|
||||||
|
[JsonProperty(DefaultValueHandling = DefaultValueHandling.Include)]
|
||||||
|
public new int Id { get; set; }
|
||||||
|
public string Name { get; set; }
|
||||||
|
public string NameLower => Name.ToLowerInvariant();
|
||||||
|
}
|
||||||
|
}
|
|
@ -49,6 +49,7 @@ namespace Lidarr.Api.V1.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,6 +77,7 @@ namespace Lidarr.Api.V1.Indexers
|
||||||
var parsedAlbumInfo = model.RemoteAlbum.ParsedAlbumInfo;
|
var parsedAlbumInfo = model.RemoteAlbum.ParsedAlbumInfo;
|
||||||
var remoteAlbum = model.RemoteAlbum;
|
var remoteAlbum = model.RemoteAlbum;
|
||||||
var torrentInfo = (model.RemoteAlbum.Release as TorrentInfo) ?? new TorrentInfo();
|
var torrentInfo = (model.RemoteAlbum.Release as TorrentInfo) ?? new TorrentInfo();
|
||||||
|
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
|
||||||
|
@ -115,6 +117,7 @@ namespace Lidarr.Api.V1.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 = (int)indexerFlags,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -80,6 +80,7 @@ namespace Lidarr.Api.V1.ManualImport
|
||||||
Release = resource.AlbumReleaseId.HasValue ? _releaseService.GetRelease(resource.AlbumReleaseId.Value) : null,
|
Release = resource.AlbumReleaseId.HasValue ? _releaseService.GetRelease(resource.AlbumReleaseId.Value) : null,
|
||||||
Quality = resource.Quality,
|
Quality = resource.Quality,
|
||||||
ReleaseGroup = resource.ReleaseGroup,
|
ReleaseGroup = resource.ReleaseGroup,
|
||||||
|
IndexerFlags = resource.IndexerFlags,
|
||||||
DownloadId = resource.DownloadId,
|
DownloadId = resource.DownloadId,
|
||||||
AdditionalFile = resource.AdditionalFile,
|
AdditionalFile = resource.AdditionalFile,
|
||||||
ReplaceExistingFiles = resource.ReplaceExistingFiles,
|
ReplaceExistingFiles = resource.ReplaceExistingFiles,
|
||||||
|
|
|
@ -24,6 +24,7 @@ namespace Lidarr.Api.V1.ManualImport
|
||||||
public string ReleaseGroup { get; set; }
|
public string ReleaseGroup { get; set; }
|
||||||
public int QualityWeight { get; set; }
|
public int QualityWeight { get; set; }
|
||||||
public string DownloadId { get; set; }
|
public string DownloadId { get; set; }
|
||||||
|
public int IndexerFlags { get; set; }
|
||||||
public IEnumerable<Rejection> Rejections { get; set; }
|
public IEnumerable<Rejection> Rejections { get; set; }
|
||||||
public ParsedTrackInfo AudioTags { get; set; }
|
public ParsedTrackInfo AudioTags { get; set; }
|
||||||
public bool AdditionalFile { get; set; }
|
public bool AdditionalFile { get; set; }
|
||||||
|
@ -55,7 +56,9 @@ namespace Lidarr.Api.V1.ManualImport
|
||||||
|
|
||||||
// QualityWeight
|
// QualityWeight
|
||||||
DownloadId = model.DownloadId,
|
DownloadId = model.DownloadId,
|
||||||
|
IndexerFlags = model.IndexerFlags,
|
||||||
Rejections = model.Rejections,
|
Rejections = model.Rejections,
|
||||||
|
|
||||||
AudioTags = model.Tags,
|
AudioTags = model.Tags,
|
||||||
AdditionalFile = model.AdditionalFile,
|
AdditionalFile = model.AdditionalFile,
|
||||||
ReplaceExistingFiles = model.ReplaceExistingFiles,
|
ReplaceExistingFiles = model.ReplaceExistingFiles,
|
||||||
|
|
|
@ -17,6 +17,7 @@ namespace Lidarr.Api.V1.ManualImport
|
||||||
public List<int> TrackIds { get; set; }
|
public List<int> TrackIds { get; set; }
|
||||||
public QualityModel Quality { get; set; }
|
public QualityModel Quality { 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 bool AdditionalFile { get; set; }
|
public bool AdditionalFile { get; set; }
|
||||||
public bool ReplaceExistingFiles { get; set; }
|
public bool ReplaceExistingFiles { get; set; }
|
||||||
|
|
|
@ -24,6 +24,7 @@ namespace Lidarr.Api.V1.TrackFiles
|
||||||
public int QualityWeight { get; set; }
|
public int QualityWeight { 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 bool QualityCutoffNotMet { get; set; }
|
public bool QualityCutoffNotMet { get; set; }
|
||||||
|
@ -94,7 +95,8 @@ namespace Lidarr.Api.V1.TrackFiles
|
||||||
MediaInfo = model.MediaInfo.ToResource(),
|
MediaInfo = model.MediaInfo.ToResource(),
|
||||||
QualityCutoffNotMet = upgradableSpecification.QualityCutoffNotMet(artist.QualityProfile.Value, model.Quality),
|
QualityCutoffNotMet = upgradableSpecification.QualityCutoffNotMet(artist.QualityProfile.Value, model.Quality),
|
||||||
CustomFormats = customFormats.ToResource(false),
|
CustomFormats = customFormats.ToResource(false),
|
||||||
CustomFormatScore = customFormatScore
|
CustomFormatScore = customFormatScore,
|
||||||
|
IndexerFlags = (int)model.IndexerFlags
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,6 +8,7 @@ using Moq;
|
||||||
using NUnit.Framework;
|
using NUnit.Framework;
|
||||||
using NzbDrone.Core.DecisionEngine;
|
using NzbDrone.Core.DecisionEngine;
|
||||||
using NzbDrone.Core.Download;
|
using NzbDrone.Core.Download;
|
||||||
|
using NzbDrone.Core.History;
|
||||||
using NzbDrone.Core.MediaFiles;
|
using NzbDrone.Core.MediaFiles;
|
||||||
using NzbDrone.Core.MediaFiles.TrackImport;
|
using NzbDrone.Core.MediaFiles.TrackImport;
|
||||||
using NzbDrone.Core.MediaFiles.TrackImport.Aggregation;
|
using NzbDrone.Core.MediaFiles.TrackImport.Aggregation;
|
||||||
|
@ -130,6 +131,10 @@ namespace NzbDrone.Core.Test.MediaFiles.TrackImport
|
||||||
.Setup(c => c.FilterUnchangedFiles(It.IsAny<List<IFileInfo>>(), It.IsAny<FilterFilesType>()))
|
.Setup(c => c.FilterUnchangedFiles(It.IsAny<List<IFileInfo>>(), It.IsAny<FilterFilesType>()))
|
||||||
.Returns((List<IFileInfo> files, FilterFilesType filter) => files);
|
.Returns((List<IFileInfo> files, FilterFilesType filter) => files);
|
||||||
|
|
||||||
|
Mocker.GetMock<IHistoryService>()
|
||||||
|
.Setup(x => x.FindByDownloadId(It.IsAny<string>()))
|
||||||
|
.Returns(new List<EntityHistory>());
|
||||||
|
|
||||||
GivenSpecifications(_albumpass1);
|
GivenSpecifications(_albumpass1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -3,6 +3,7 @@ using System.Collections.Generic;
|
||||||
using NzbDrone.Core.Datastore;
|
using NzbDrone.Core.Datastore;
|
||||||
using NzbDrone.Core.Indexers;
|
using NzbDrone.Core.Indexers;
|
||||||
using NzbDrone.Core.Music;
|
using NzbDrone.Core.Music;
|
||||||
|
using NzbDrone.Core.Parser.Model;
|
||||||
using NzbDrone.Core.Qualities;
|
using NzbDrone.Core.Qualities;
|
||||||
|
|
||||||
namespace NzbDrone.Core.Blocklisting
|
namespace NzbDrone.Core.Blocklisting
|
||||||
|
@ -19,6 +20,7 @@ namespace NzbDrone.Core.Blocklisting
|
||||||
public long? Size { get; set; }
|
public long? Size { get; set; }
|
||||||
public DownloadProtocol Protocol { get; set; }
|
public DownloadProtocol Protocol { get; set; }
|
||||||
public string Indexer { get; set; }
|
public string Indexer { get; set; }
|
||||||
|
public IndexerFlags IndexerFlags { get; set; }
|
||||||
public string Message { get; set; }
|
public string Message { get; set; }
|
||||||
public string TorrentInfoHash { get; set; }
|
public string TorrentInfoHash { get; set; }
|
||||||
}
|
}
|
||||||
|
|
|
@ -188,6 +188,11 @@ namespace NzbDrone.Core.Blocklisting
|
||||||
TorrentInfoHash = message.Data.GetValueOrDefault("torrentInfoHash")
|
TorrentInfoHash = message.Data.GetValueOrDefault("torrentInfoHash")
|
||||||
};
|
};
|
||||||
|
|
||||||
|
if (Enum.TryParse(message.Data.GetValueOrDefault("indexerFlags"), true, out IndexerFlags flags))
|
||||||
|
{
|
||||||
|
blocklist.IndexerFlags = flags;
|
||||||
|
}
|
||||||
|
|
||||||
_blocklistRepository.Insert(blocklist);
|
_blocklistRepository.Insert(blocklist);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
@ -38,7 +39,8 @@ namespace NzbDrone.Core.CustomFormats
|
||||||
{
|
{
|
||||||
AlbumInfo = remoteAlbum.ParsedAlbumInfo,
|
AlbumInfo = remoteAlbum.ParsedAlbumInfo,
|
||||||
Artist = remoteAlbum.Artist,
|
Artist = remoteAlbum.Artist,
|
||||||
Size = size
|
Size = size,
|
||||||
|
IndexerFlags = remoteAlbum.Release?.IndexerFlags ?? 0
|
||||||
};
|
};
|
||||||
|
|
||||||
return ParseCustomFormat(input);
|
return ParseCustomFormat(input);
|
||||||
|
@ -70,7 +72,8 @@ namespace NzbDrone.Core.CustomFormats
|
||||||
{
|
{
|
||||||
AlbumInfo = albumInfo,
|
AlbumInfo = albumInfo,
|
||||||
Artist = artist,
|
Artist = artist,
|
||||||
Size = blocklist.Size ?? 0
|
Size = blocklist.Size ?? 0,
|
||||||
|
IndexerFlags = blocklist.IndexerFlags
|
||||||
};
|
};
|
||||||
|
|
||||||
return ParseCustomFormat(input);
|
return ParseCustomFormat(input);
|
||||||
|
@ -81,6 +84,7 @@ namespace NzbDrone.Core.CustomFormats
|
||||||
var parsed = Parser.Parser.ParseAlbumTitle(history.SourceTitle);
|
var parsed = Parser.Parser.ParseAlbumTitle(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 indexerFlags);
|
||||||
|
|
||||||
var albumInfo = new ParsedAlbumInfo
|
var albumInfo = new ParsedAlbumInfo
|
||||||
{
|
{
|
||||||
|
@ -94,7 +98,8 @@ namespace NzbDrone.Core.CustomFormats
|
||||||
{
|
{
|
||||||
AlbumInfo = albumInfo,
|
AlbumInfo = albumInfo,
|
||||||
Artist = artist,
|
Artist = artist,
|
||||||
Size = size
|
Size = size,
|
||||||
|
IndexerFlags = indexerFlags
|
||||||
};
|
};
|
||||||
|
|
||||||
return ParseCustomFormat(input);
|
return ParseCustomFormat(input);
|
||||||
|
@ -115,7 +120,8 @@ namespace NzbDrone.Core.CustomFormats
|
||||||
AlbumInfo = albumInfo,
|
AlbumInfo = albumInfo,
|
||||||
Artist = localTrack.Artist,
|
Artist = localTrack.Artist,
|
||||||
Size = localTrack.Size,
|
Size = localTrack.Size,
|
||||||
Filename = Path.GetFileName(localTrack.Path)
|
Filename = Path.GetFileName(localTrack.Path),
|
||||||
|
IndexerFlags = localTrack.IndexerFlags,
|
||||||
};
|
};
|
||||||
|
|
||||||
return ParseCustomFormat(input);
|
return ParseCustomFormat(input);
|
||||||
|
@ -182,6 +188,7 @@ namespace NzbDrone.Core.CustomFormats
|
||||||
AlbumInfo = albumInfo,
|
AlbumInfo = albumInfo,
|
||||||
Artist = artist,
|
Artist = artist,
|
||||||
Size = trackFile.Size,
|
Size = trackFile.Size,
|
||||||
|
IndexerFlags = trackFile.IndexerFlags,
|
||||||
Filename = Path.GetFileName(trackFile.Path)
|
Filename = Path.GetFileName(trackFile.Path)
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -8,6 +8,7 @@ namespace NzbDrone.Core.CustomFormats
|
||||||
public ParsedAlbumInfo AlbumInfo { get; set; }
|
public ParsedAlbumInfo AlbumInfo { get; set; }
|
||||||
public Artist Artist { get; set; }
|
public Artist Artist { get; set; }
|
||||||
public long Size { get; set; }
|
public long Size { get; set; }
|
||||||
|
public IndexerFlags IndexerFlags { get; set; }
|
||||||
public string Filename { get; set; }
|
public string Filename { get; set; }
|
||||||
|
|
||||||
// public CustomFormatInput(ParsedEpisodeInfo episodeInfo, Series series)
|
// public CustomFormatInput(ParsedEpisodeInfo episodeInfo, Series series)
|
||||||
|
|
|
@ -0,0 +1,44 @@
|
||||||
|
using System;
|
||||||
|
using FluentValidation;
|
||||||
|
using NzbDrone.Core.Annotations;
|
||||||
|
using NzbDrone.Core.Parser.Model;
|
||||||
|
using NzbDrone.Core.Validation;
|
||||||
|
|
||||||
|
namespace NzbDrone.Core.CustomFormats
|
||||||
|
{
|
||||||
|
public class IndexerFlagSpecificationValidator : AbstractValidator<IndexerFlagSpecification>
|
||||||
|
{
|
||||||
|
public IndexerFlagSpecificationValidator()
|
||||||
|
{
|
||||||
|
RuleFor(c => c.Value).NotEmpty();
|
||||||
|
RuleFor(c => c.Value).Custom((flag, context) =>
|
||||||
|
{
|
||||||
|
if (!Enum.IsDefined(typeof(IndexerFlags), flag))
|
||||||
|
{
|
||||||
|
context.AddFailure($"Invalid indexer flag condition value: {flag}");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class IndexerFlagSpecification : CustomFormatSpecificationBase
|
||||||
|
{
|
||||||
|
private static readonly IndexerFlagSpecificationValidator Validator = new ();
|
||||||
|
|
||||||
|
public override int Order => 4;
|
||||||
|
public override string ImplementationName => "Indexer Flag";
|
||||||
|
|
||||||
|
[FieldDefinition(1, Label = "CustomFormatsSpecificationFlag", Type = FieldType.Select, SelectOptions = typeof(IndexerFlags))]
|
||||||
|
public int Value { get; set; }
|
||||||
|
|
||||||
|
protected override bool IsSatisfiedByWithoutNegate(CustomFormatInput input)
|
||||||
|
{
|
||||||
|
return input.IndexerFlags.HasFlag((IndexerFlags)Value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override NzbDroneValidationResult Validate()
|
||||||
|
{
|
||||||
|
return new NzbDroneValidationResult(Validator.Validate(this));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,15 @@
|
||||||
|
using FluentMigrator;
|
||||||
|
using NzbDrone.Core.Datastore.Migration.Framework;
|
||||||
|
|
||||||
|
namespace NzbDrone.Core.Datastore.Migration
|
||||||
|
{
|
||||||
|
[Migration(078)]
|
||||||
|
public class add_indexer_flags : NzbDroneMigrationBase
|
||||||
|
{
|
||||||
|
protected override void MainDbUpgrade()
|
||||||
|
{
|
||||||
|
Alter.Table("Blocklist").AddColumn("IndexerFlags").AsInt32().WithDefaultValue(0);
|
||||||
|
Alter.Table("TrackFiles").AddColumn("IndexerFlags").AsInt32().WithDefaultValue(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -12,6 +12,7 @@ using NzbDrone.Core.Messaging.Events;
|
||||||
using NzbDrone.Core.Music;
|
using NzbDrone.Core.Music;
|
||||||
using NzbDrone.Core.Music.Events;
|
using NzbDrone.Core.Music.Events;
|
||||||
using NzbDrone.Core.Parser;
|
using NzbDrone.Core.Parser;
|
||||||
|
using NzbDrone.Core.Parser.Model;
|
||||||
|
|
||||||
namespace NzbDrone.Core.Download.TrackedDownloads
|
namespace NzbDrone.Core.Download.TrackedDownloads
|
||||||
{
|
{
|
||||||
|
@ -144,12 +145,11 @@ namespace NzbDrone.Core.Download.TrackedDownloads
|
||||||
var firstHistoryItem = historyItems.First();
|
var firstHistoryItem = historyItems.First();
|
||||||
var grabbedEvent = historyItems.FirstOrDefault(v => v.EventType == EntityHistoryEventType.Grabbed);
|
var grabbedEvent = historyItems.FirstOrDefault(v => v.EventType == EntityHistoryEventType.Grabbed);
|
||||||
|
|
||||||
trackedDownload.Indexer = grabbedEvent?.Data["indexer"];
|
trackedDownload.Indexer = grabbedEvent?.Data?.GetValueOrDefault("indexer");
|
||||||
trackedDownload.Added = grabbedEvent?.Date;
|
trackedDownload.Added = grabbedEvent?.Date;
|
||||||
|
|
||||||
if (parsedAlbumInfo == null ||
|
if (parsedAlbumInfo == null ||
|
||||||
trackedDownload.RemoteAlbum == null ||
|
trackedDownload.RemoteAlbum?.Artist == null ||
|
||||||
trackedDownload.RemoteAlbum.Artist == null ||
|
|
||||||
trackedDownload.RemoteAlbum.Albums.Empty())
|
trackedDownload.RemoteAlbum.Albums.Empty())
|
||||||
{
|
{
|
||||||
// Try parsing the original source title and if that fails, try parsing it as a special
|
// Try parsing the original source title and if that fails, try parsing it as a special
|
||||||
|
@ -181,6 +181,13 @@ namespace NzbDrone.Core.Download.TrackedDownloads
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (trackedDownload.RemoteAlbum != null &&
|
||||||
|
Enum.TryParse(grabbedEvent?.Data?.GetValueOrDefault("indexerFlags"), true, out IndexerFlags flags))
|
||||||
|
{
|
||||||
|
trackedDownload.RemoteAlbum.Release ??= new ReleaseInfo();
|
||||||
|
trackedDownload.RemoteAlbum.Release.IndexerFlags = flags;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Calculate custom formats
|
// Calculate custom formats
|
||||||
|
|
|
@ -166,6 +166,7 @@ namespace NzbDrone.Core.History
|
||||||
history.Data.Add("DownloadForced", (!message.Album.DownloadAllowed).ToString());
|
history.Data.Add("DownloadForced", (!message.Album.DownloadAllowed).ToString());
|
||||||
history.Data.Add("CustomFormatScore", message.Album.CustomFormatScore.ToString());
|
history.Data.Add("CustomFormatScore", message.Album.CustomFormatScore.ToString());
|
||||||
history.Data.Add("ReleaseSource", message.Album.ReleaseSource.ToString());
|
history.Data.Add("ReleaseSource", message.Album.ReleaseSource.ToString());
|
||||||
|
history.Data.Add("IndexerFlags", message.Album.Release.IndexerFlags.ToString());
|
||||||
|
|
||||||
if (!message.Album.ParsedAlbumInfo.ReleaseHash.IsNullOrWhiteSpace())
|
if (!message.Album.ParsedAlbumInfo.ReleaseHash.IsNullOrWhiteSpace())
|
||||||
{
|
{
|
||||||
|
@ -203,6 +204,8 @@ namespace NzbDrone.Core.History
|
||||||
|
|
||||||
history.Data.Add("StatusMessages", message.TrackedDownload.StatusMessages.ToJson());
|
history.Data.Add("StatusMessages", message.TrackedDownload.StatusMessages.ToJson());
|
||||||
history.Data.Add("ReleaseGroup", message.TrackedDownload?.RemoteAlbum?.ParsedAlbumInfo?.ReleaseGroup);
|
history.Data.Add("ReleaseGroup", message.TrackedDownload?.RemoteAlbum?.ParsedAlbumInfo?.ReleaseGroup);
|
||||||
|
history.Data.Add("IndexerFlags", message.TrackedDownload?.RemoteAlbum?.Release?.IndexerFlags.ToString());
|
||||||
|
|
||||||
_historyRepository.Insert(history);
|
_historyRepository.Insert(history);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -241,6 +244,7 @@ namespace NzbDrone.Core.History
|
||||||
history.Data.Add("DownloadClient", message.DownloadClientInfo?.Name);
|
history.Data.Add("DownloadClient", message.DownloadClientInfo?.Name);
|
||||||
history.Data.Add("ReleaseGroup", message.TrackInfo.ReleaseGroup);
|
history.Data.Add("ReleaseGroup", message.TrackInfo.ReleaseGroup);
|
||||||
history.Data.Add("Size", message.TrackInfo.Size.ToString());
|
history.Data.Add("Size", message.TrackInfo.Size.ToString());
|
||||||
|
history.Data.Add("IndexerFlags", message.ImportedTrack.IndexerFlags.ToString());
|
||||||
|
|
||||||
_historyRepository.Insert(history);
|
_historyRepository.Insert(history);
|
||||||
}
|
}
|
||||||
|
@ -324,6 +328,7 @@ namespace NzbDrone.Core.History
|
||||||
history.Data.Add("Reason", message.Reason.ToString());
|
history.Data.Add("Reason", message.Reason.ToString());
|
||||||
history.Data.Add("ReleaseGroup", message.TrackFile.ReleaseGroup);
|
history.Data.Add("ReleaseGroup", message.TrackFile.ReleaseGroup);
|
||||||
history.Data.Add("Size", message.TrackFile.Size.ToString());
|
history.Data.Add("Size", message.TrackFile.Size.ToString());
|
||||||
|
history.Data.Add("IndexerFlags", message.TrackFile.IndexerFlags.ToString());
|
||||||
|
|
||||||
_historyRepository.Insert(history);
|
_historyRepository.Insert(history);
|
||||||
}
|
}
|
||||||
|
@ -351,6 +356,7 @@ namespace NzbDrone.Core.History
|
||||||
history.Data.Add("Path", path);
|
history.Data.Add("Path", path);
|
||||||
history.Data.Add("ReleaseGroup", message.TrackFile.ReleaseGroup);
|
history.Data.Add("ReleaseGroup", message.TrackFile.ReleaseGroup);
|
||||||
history.Data.Add("Size", message.TrackFile.Size.ToString());
|
history.Data.Add("Size", message.TrackFile.Size.ToString());
|
||||||
|
history.Data.Add("IndexerFlags", message.TrackFile.IndexerFlags.ToString());
|
||||||
|
|
||||||
_historyRepository.Insert(history);
|
_historyRepository.Insert(history);
|
||||||
}
|
}
|
||||||
|
|
|
@ -38,8 +38,7 @@ namespace NzbDrone.Core.Indexers.FileList
|
||||||
{
|
{
|
||||||
var id = result.Id;
|
var id = result.Id;
|
||||||
|
|
||||||
// if (result.FreeLeech)
|
torrentInfos.Add(new TorrentInfo
|
||||||
torrentInfos.Add(new TorrentInfo()
|
|
||||||
{
|
{
|
||||||
Guid = $"FileList-{id}",
|
Guid = $"FileList-{id}",
|
||||||
Title = result.Name,
|
Title = result.Name,
|
||||||
|
@ -48,13 +47,31 @@ namespace NzbDrone.Core.Indexers.FileList
|
||||||
InfoUrl = GetInfoUrl(id),
|
InfoUrl = GetInfoUrl(id),
|
||||||
Seeders = result.Seeders,
|
Seeders = result.Seeders,
|
||||||
Peers = result.Leechers + result.Seeders,
|
Peers = result.Leechers + result.Seeders,
|
||||||
PublishDate = result.UploadDate.ToUniversalTime()
|
PublishDate = result.UploadDate.ToUniversalTime(),
|
||||||
|
IndexerFlags = GetIndexerFlags(result)
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
return torrentInfos.ToArray();
|
return torrentInfos.ToArray();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static IndexerFlags GetIndexerFlags(FileListTorrent item)
|
||||||
|
{
|
||||||
|
IndexerFlags flags = 0;
|
||||||
|
|
||||||
|
if (item.FreeLeech)
|
||||||
|
{
|
||||||
|
flags |= IndexerFlags.Freeleech;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (item.Internal)
|
||||||
|
{
|
||||||
|
flags |= IndexerFlags.Internal;
|
||||||
|
}
|
||||||
|
|
||||||
|
return flags;
|
||||||
|
}
|
||||||
|
|
||||||
private string GetDownloadUrl(string torrentId)
|
private string GetDownloadUrl(string torrentId)
|
||||||
{
|
{
|
||||||
var url = new HttpUri(_settings.BaseUrl)
|
var url = new HttpUri(_settings.BaseUrl)
|
||||||
|
|
|
@ -16,6 +16,7 @@ namespace NzbDrone.Core.Indexers.FileList
|
||||||
public uint Files { get; set; }
|
public uint Files { get; set; }
|
||||||
[JsonProperty(PropertyName = "imdb")]
|
[JsonProperty(PropertyName = "imdb")]
|
||||||
public string ImdbId { get; set; }
|
public string ImdbId { get; set; }
|
||||||
|
public bool Internal { get; set; }
|
||||||
[JsonProperty(PropertyName = "freeleech")]
|
[JsonProperty(PropertyName = "freeleech")]
|
||||||
public bool FreeLeech { get; set; }
|
public bool FreeLeech { get; set; }
|
||||||
[JsonProperty(PropertyName = "upload_date")]
|
[JsonProperty(PropertyName = "upload_date")]
|
||||||
|
|
|
@ -34,6 +34,7 @@ namespace NzbDrone.Core.Indexers.Gazelle
|
||||||
public string Leechers { get; set; }
|
public string Leechers { get; set; }
|
||||||
public bool IsFreeLeech { get; set; }
|
public bool IsFreeLeech { get; set; }
|
||||||
public bool IsNeutralLeech { get; set; }
|
public bool IsNeutralLeech { get; set; }
|
||||||
|
public bool IsFreeload { get; set; }
|
||||||
public bool IsPersonalFreeLeech { get; set; }
|
public bool IsPersonalFreeLeech { get; set; }
|
||||||
public bool CanUseToken { get; set; }
|
public bool CanUseToken { get; set; }
|
||||||
}
|
}
|
||||||
|
|
|
@ -63,7 +63,7 @@ namespace NzbDrone.Core.Indexers.Gazelle
|
||||||
title += " [Cue]";
|
title += " [Cue]";
|
||||||
}
|
}
|
||||||
|
|
||||||
torrentInfos.Add(new GazelleInfo()
|
torrentInfos.Add(new GazelleInfo
|
||||||
{
|
{
|
||||||
Guid = string.Format("Gazelle-{0}", id),
|
Guid = string.Format("Gazelle-{0}", id),
|
||||||
Artist = artist,
|
Artist = artist,
|
||||||
|
@ -79,7 +79,7 @@ namespace NzbDrone.Core.Indexers.Gazelle
|
||||||
Seeders = int.Parse(torrent.Seeders),
|
Seeders = int.Parse(torrent.Seeders),
|
||||||
Peers = int.Parse(torrent.Leechers) + int.Parse(torrent.Seeders),
|
Peers = int.Parse(torrent.Leechers) + int.Parse(torrent.Seeders),
|
||||||
PublishDate = torrent.Time.ToUniversalTime(),
|
PublishDate = torrent.Time.ToUniversalTime(),
|
||||||
Scene = torrent.Scene,
|
IndexerFlags = GetIndexerFlags(torrent)
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -92,6 +92,23 @@ namespace NzbDrone.Core.Indexers.Gazelle
|
||||||
.ToArray();
|
.ToArray();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static IndexerFlags GetIndexerFlags(GazelleTorrent torrent)
|
||||||
|
{
|
||||||
|
IndexerFlags flags = 0;
|
||||||
|
|
||||||
|
if (torrent.IsFreeLeech || torrent.IsNeutralLeech || torrent.IsFreeload || torrent.IsPersonalFreeLeech)
|
||||||
|
{
|
||||||
|
flags |= IndexerFlags.Freeleech;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (torrent.Scene)
|
||||||
|
{
|
||||||
|
flags |= IndexerFlags.Scene;
|
||||||
|
}
|
||||||
|
|
||||||
|
return flags;
|
||||||
|
}
|
||||||
|
|
||||||
private string GetDownloadUrl(int torrentId)
|
private string GetDownloadUrl(int torrentId)
|
||||||
{
|
{
|
||||||
var url = new HttpUri(_settings.BaseUrl)
|
var url = new HttpUri(_settings.BaseUrl)
|
||||||
|
|
|
@ -65,7 +65,7 @@ namespace NzbDrone.Core.Indexers.Redacted
|
||||||
Seeders = int.Parse(torrent.Seeders),
|
Seeders = int.Parse(torrent.Seeders),
|
||||||
Peers = int.Parse(torrent.Leechers) + int.Parse(torrent.Seeders),
|
Peers = int.Parse(torrent.Leechers) + int.Parse(torrent.Seeders),
|
||||||
PublishDate = torrent.Time.ToUniversalTime(),
|
PublishDate = torrent.Time.ToUniversalTime(),
|
||||||
Scene = torrent.Scene
|
IndexerFlags = GetIndexerFlags(torrent)
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -111,6 +111,23 @@ namespace NzbDrone.Core.Indexers.Redacted
|
||||||
return $"{title} [{string.Join(" / ", flags)}]";
|
return $"{title} [{string.Join(" / ", flags)}]";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static IndexerFlags GetIndexerFlags(GazelleTorrent torrent)
|
||||||
|
{
|
||||||
|
IndexerFlags flags = 0;
|
||||||
|
|
||||||
|
if (torrent.IsFreeLeech || torrent.IsNeutralLeech || torrent.IsFreeload || torrent.IsPersonalFreeLeech)
|
||||||
|
{
|
||||||
|
flags |= IndexerFlags.Freeleech;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (torrent.Scene)
|
||||||
|
{
|
||||||
|
flags |= IndexerFlags.Scene;
|
||||||
|
}
|
||||||
|
|
||||||
|
return flags;
|
||||||
|
}
|
||||||
|
|
||||||
private string GetDownloadUrl(int torrentId, bool canUseToken)
|
private string GetDownloadUrl(int torrentId, bool canUseToken)
|
||||||
{
|
{
|
||||||
var url = new HttpUri(_settings.BaseUrl)
|
var url = new HttpUri(_settings.BaseUrl)
|
||||||
|
|
|
@ -74,6 +74,18 @@ namespace NzbDrone.Core.Indexers.Torznab
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected override ReleaseInfo ProcessItem(XElement item, ReleaseInfo releaseInfo)
|
||||||
|
{
|
||||||
|
var torrentInfo = base.ProcessItem(item, releaseInfo) as TorrentInfo;
|
||||||
|
|
||||||
|
if (torrentInfo != null)
|
||||||
|
{
|
||||||
|
torrentInfo.IndexerFlags = GetFlags(item);
|
||||||
|
}
|
||||||
|
|
||||||
|
return torrentInfo;
|
||||||
|
}
|
||||||
|
|
||||||
protected override string GetInfoUrl(XElement item)
|
protected override string GetInfoUrl(XElement item)
|
||||||
{
|
{
|
||||||
return ParseUrl(item.TryGetValue("comments").TrimEnd("#comments"));
|
return ParseUrl(item.TryGetValue("comments").TrimEnd("#comments"));
|
||||||
|
@ -180,6 +192,53 @@ namespace NzbDrone.Core.Indexers.Torznab
|
||||||
return base.GetPeers(item);
|
return base.GetPeers(item);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected IndexerFlags GetFlags(XElement item)
|
||||||
|
{
|
||||||
|
IndexerFlags flags = 0;
|
||||||
|
|
||||||
|
var downloadFactor = TryGetFloatTorznabAttribute(item, "downloadvolumefactor", 1);
|
||||||
|
var uploadFactor = TryGetFloatTorznabAttribute(item, "uploadvolumefactor", 1);
|
||||||
|
|
||||||
|
if (downloadFactor == 0.5)
|
||||||
|
{
|
||||||
|
flags |= IndexerFlags.Halfleech;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (downloadFactor == 0.75)
|
||||||
|
{
|
||||||
|
flags |= IndexerFlags.Freeleech25;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (downloadFactor == 0.25)
|
||||||
|
{
|
||||||
|
flags |= IndexerFlags.Freeleech75;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (downloadFactor == 0.0)
|
||||||
|
{
|
||||||
|
flags |= IndexerFlags.Freeleech;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (uploadFactor == 2.0)
|
||||||
|
{
|
||||||
|
flags |= IndexerFlags.DoubleUpload;
|
||||||
|
}
|
||||||
|
|
||||||
|
var tags = TryGetMultipleTorznabAttributes(item, "tag");
|
||||||
|
|
||||||
|
if (tags.Any(t => t.EqualsIgnoreCase("internal")))
|
||||||
|
{
|
||||||
|
flags |= IndexerFlags.Internal;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (tags.Any(t => t.EqualsIgnoreCase("scene")))
|
||||||
|
{
|
||||||
|
flags |= IndexerFlags.Scene;
|
||||||
|
}
|
||||||
|
|
||||||
|
return flags;
|
||||||
|
}
|
||||||
|
|
||||||
protected string TryGetTorznabAttribute(XElement item, string key, string defaultValue = "")
|
protected string TryGetTorznabAttribute(XElement item, string key, string defaultValue = "")
|
||||||
{
|
{
|
||||||
var attrElement = item.Elements(ns + "attr").FirstOrDefault(e => e.Attribute("name").Value.Equals(key, StringComparison.OrdinalIgnoreCase));
|
var attrElement = item.Elements(ns + "attr").FirstOrDefault(e => e.Attribute("name").Value.Equals(key, StringComparison.OrdinalIgnoreCase));
|
||||||
|
@ -195,6 +254,13 @@ namespace NzbDrone.Core.Indexers.Torznab
|
||||||
return defaultValue;
|
return defaultValue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected float TryGetFloatTorznabAttribute(XElement item, string key, float defaultValue = 0)
|
||||||
|
{
|
||||||
|
var attr = TryGetTorznabAttribute(item, key, defaultValue.ToString());
|
||||||
|
|
||||||
|
return float.TryParse(attr, out var result) ? result : defaultValue;
|
||||||
|
}
|
||||||
|
|
||||||
protected List<string> TryGetMultipleTorznabAttributes(XElement item, string key)
|
protected List<string> TryGetMultipleTorznabAttributes(XElement item, string key)
|
||||||
{
|
{
|
||||||
var attrElements = item.Elements(ns + "attr").Where(e => e.Attribute("name").Value.Equals(key, StringComparison.OrdinalIgnoreCase));
|
var attrElements = item.Elements(ns + "attr").Where(e => e.Attribute("name").Value.Equals(key, StringComparison.OrdinalIgnoreCase));
|
||||||
|
|
|
@ -201,6 +201,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",
|
||||||
"ClickToChangeQuality": "Click to change quality",
|
"ClickToChangeQuality": "Click to change quality",
|
||||||
"ClickToChangeReleaseGroup": "Click to change release group",
|
"ClickToChangeReleaseGroup": "Click to change release group",
|
||||||
"ClientPriority": "Client Priority",
|
"ClientPriority": "Client Priority",
|
||||||
|
@ -257,6 +258,7 @@
|
||||||
"CustomFormats": "Custom Formats",
|
"CustomFormats": "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",
|
||||||
"Customformat": "Custom Format",
|
"Customformat": "Custom Format",
|
||||||
|
@ -574,6 +576,7 @@
|
||||||
"Indexer": "Indexer",
|
"Indexer": "Indexer",
|
||||||
"IndexerDownloadClientHealthCheckMessage": "Indexers with invalid download clients: {0}.",
|
"IndexerDownloadClientHealthCheckMessage": "Indexers with invalid download clients: {0}.",
|
||||||
"IndexerDownloadClientHelpText": "Specify which download client is used for grabs from this indexer",
|
"IndexerDownloadClientHelpText": "Specify which download client is used for grabs from this indexer",
|
||||||
|
"IndexerFlags": "Indexer Flags",
|
||||||
"IndexerIdHelpText": "Specify what indexer the profile applies to",
|
"IndexerIdHelpText": "Specify what indexer the profile applies to",
|
||||||
"IndexerIdHelpTextWarning": "Using a specific indexer with preferred words can lead to duplicate releases being grabbed",
|
"IndexerIdHelpTextWarning": "Using a specific indexer with preferred words can lead to duplicate releases being grabbed",
|
||||||
"IndexerJackettAll": "Indexers using the unsupported Jackett 'all' endpoint: {0}",
|
"IndexerJackettAll": "Indexers using the unsupported Jackett 'all' endpoint: {0}",
|
||||||
|
@ -901,6 +904,7 @@
|
||||||
"RegularExpressionsCanBeTested": "Regular expressions can be tested [here](http://regexstorm.net/tester).",
|
"RegularExpressionsCanBeTested": "Regular expressions can be tested [here](http://regexstorm.net/tester).",
|
||||||
"RegularExpressionsTutorialLink": "More details on regular expressions can be found [here](https://www.regular-expressions.info/tutorial.html).",
|
"RegularExpressionsTutorialLink": "More details on regular expressions can be found [here](https://www.regular-expressions.info/tutorial.html).",
|
||||||
"RejectionCount": "Rejection Count",
|
"RejectionCount": "Rejection Count",
|
||||||
|
"Rejections": "Rejections",
|
||||||
"Release": " Release",
|
"Release": " Release",
|
||||||
"ReleaseDate": "Release Date",
|
"ReleaseDate": "Release Date",
|
||||||
"ReleaseGroup": "Release Group",
|
"ReleaseGroup": "Release Group",
|
||||||
|
@ -1043,12 +1047,14 @@
|
||||||
"SelectAlbumRelease": "Select Album Release",
|
"SelectAlbumRelease": "Select Album Release",
|
||||||
"SelectArtist": "Select Artist",
|
"SelectArtist": "Select Artist",
|
||||||
"SelectFolder": "Select Folder",
|
"SelectFolder": "Select Folder",
|
||||||
|
"SelectIndexerFlags": "Select Indexer Flags",
|
||||||
"SelectQuality": "Select Quality",
|
"SelectQuality": "Select Quality",
|
||||||
"SelectReleaseGroup": "Select Release Group",
|
"SelectReleaseGroup": "Select Release Group",
|
||||||
"SelectTracks": "Select Tracks",
|
"SelectTracks": "Select Tracks",
|
||||||
"SelectedCountArtistsSelectedInterp": "{selectedCount} Artist(s) Selected",
|
"SelectedCountArtistsSelectedInterp": "{selectedCount} Artist(s) Selected",
|
||||||
"SendAnonymousUsageData": "Send Anonymous Usage Data",
|
"SendAnonymousUsageData": "Send Anonymous Usage Data",
|
||||||
"SetAppTags": "Set {appName} Tags",
|
"SetAppTags": "Set {appName} Tags",
|
||||||
|
"SetIndexerFlags": "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.",
|
||||||
|
|
|
@ -19,6 +19,7 @@ namespace NzbDrone.Core.MediaFiles
|
||||||
public string SceneName { get; set; }
|
public string SceneName { get; set; }
|
||||||
public string ReleaseGroup { get; set; }
|
public string ReleaseGroup { get; set; }
|
||||||
public QualityModel Quality { get; set; }
|
public QualityModel Quality { get; set; }
|
||||||
|
public IndexerFlags IndexerFlags { get; set; }
|
||||||
public MediaInfoModel MediaInfo { get; set; }
|
public MediaInfoModel MediaInfo { get; set; }
|
||||||
public int AlbumId { get; set; }
|
public int AlbumId { get; set; }
|
||||||
|
|
||||||
|
|
|
@ -9,6 +9,7 @@ using NzbDrone.Common.Instrumentation.Extensions;
|
||||||
using NzbDrone.Core.DecisionEngine;
|
using NzbDrone.Core.DecisionEngine;
|
||||||
using NzbDrone.Core.Download;
|
using NzbDrone.Core.Download;
|
||||||
using NzbDrone.Core.Extras;
|
using NzbDrone.Core.Extras;
|
||||||
|
using NzbDrone.Core.History;
|
||||||
using NzbDrone.Core.MediaFiles.Events;
|
using NzbDrone.Core.MediaFiles.Events;
|
||||||
using NzbDrone.Core.Messaging.Commands;
|
using NzbDrone.Core.Messaging.Commands;
|
||||||
using NzbDrone.Core.Messaging.Events;
|
using NzbDrone.Core.Messaging.Events;
|
||||||
|
@ -40,6 +41,7 @@ namespace NzbDrone.Core.MediaFiles.TrackImport
|
||||||
private readonly IRecycleBinProvider _recycleBinProvider;
|
private readonly IRecycleBinProvider _recycleBinProvider;
|
||||||
private readonly IExtraService _extraService;
|
private readonly IExtraService _extraService;
|
||||||
private readonly IDiskProvider _diskProvider;
|
private readonly IDiskProvider _diskProvider;
|
||||||
|
private readonly IHistoryService _historyService;
|
||||||
private readonly IReleaseService _releaseService;
|
private readonly IReleaseService _releaseService;
|
||||||
private readonly IEventAggregator _eventAggregator;
|
private readonly IEventAggregator _eventAggregator;
|
||||||
private readonly IManageCommandQueue _commandQueueManager;
|
private readonly IManageCommandQueue _commandQueueManager;
|
||||||
|
@ -57,6 +59,7 @@ namespace NzbDrone.Core.MediaFiles.TrackImport
|
||||||
IRecycleBinProvider recycleBinProvider,
|
IRecycleBinProvider recycleBinProvider,
|
||||||
IExtraService extraService,
|
IExtraService extraService,
|
||||||
IDiskProvider diskProvider,
|
IDiskProvider diskProvider,
|
||||||
|
IHistoryService historyService,
|
||||||
IReleaseService releaseService,
|
IReleaseService releaseService,
|
||||||
IEventAggregator eventAggregator,
|
IEventAggregator eventAggregator,
|
||||||
IManageCommandQueue commandQueueManager,
|
IManageCommandQueue commandQueueManager,
|
||||||
|
@ -74,6 +77,7 @@ namespace NzbDrone.Core.MediaFiles.TrackImport
|
||||||
_recycleBinProvider = recycleBinProvider;
|
_recycleBinProvider = recycleBinProvider;
|
||||||
_extraService = extraService;
|
_extraService = extraService;
|
||||||
_diskProvider = diskProvider;
|
_diskProvider = diskProvider;
|
||||||
|
_historyService = historyService;
|
||||||
_releaseService = releaseService;
|
_releaseService = releaseService;
|
||||||
_eventAggregator = eventAggregator;
|
_eventAggregator = eventAggregator;
|
||||||
_commandQueueManager = commandQueueManager;
|
_commandQueueManager = commandQueueManager;
|
||||||
|
@ -197,6 +201,22 @@ namespace NzbDrone.Core.MediaFiles.TrackImport
|
||||||
Tracks = localTrack.Tracks
|
Tracks = localTrack.Tracks
|
||||||
};
|
};
|
||||||
|
|
||||||
|
if (downloadClientItem?.DownloadId.IsNotNullOrWhiteSpace() == true)
|
||||||
|
{
|
||||||
|
var grabHistory = _historyService.FindByDownloadId(downloadClientItem.DownloadId)
|
||||||
|
.OrderByDescending(h => h.Date)
|
||||||
|
.FirstOrDefault(h => h.EventType == EntityHistoryEventType.Grabbed);
|
||||||
|
|
||||||
|
if (Enum.TryParse(grabHistory?.Data.GetValueOrDefault("indexerFlags"), true, out IndexerFlags flags))
|
||||||
|
{
|
||||||
|
trackFile.IndexerFlags = flags;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
trackFile.IndexerFlags = localTrack.IndexerFlags;
|
||||||
|
}
|
||||||
|
|
||||||
bool copyOnly;
|
bool copyOnly;
|
||||||
switch (importMode)
|
switch (importMode)
|
||||||
{
|
{
|
||||||
|
|
|
@ -13,6 +13,7 @@ namespace NzbDrone.Core.MediaFiles.TrackImport.Manual
|
||||||
public int AlbumReleaseId { get; set; }
|
public int AlbumReleaseId { get; set; }
|
||||||
public List<int> TrackIds { get; set; }
|
public List<int> TrackIds { get; set; }
|
||||||
public QualityModel Quality { get; set; }
|
public QualityModel Quality { get; set; }
|
||||||
|
public int IndexerFlags { get; set; }
|
||||||
public string DownloadId { get; set; }
|
public string DownloadId { get; set; }
|
||||||
public bool DisableReleaseSwitching { get; set; }
|
public bool DisableReleaseSwitching { get; set; }
|
||||||
|
|
||||||
|
|
|
@ -27,6 +27,7 @@ namespace NzbDrone.Core.MediaFiles.TrackImport.Manual
|
||||||
public string ReleaseGroup { get; set; }
|
public string ReleaseGroup { get; set; }
|
||||||
public string DownloadId { get; set; }
|
public string DownloadId { get; set; }
|
||||||
public List<CustomFormat> CustomFormats { get; set; }
|
public List<CustomFormat> CustomFormats { get; set; }
|
||||||
|
public int IndexerFlags { get; set; }
|
||||||
public IEnumerable<Rejection> Rejections { get; set; }
|
public IEnumerable<Rejection> Rejections { get; set; }
|
||||||
public ParsedTrackInfo Tags { get; set; }
|
public ParsedTrackInfo Tags { get; set; }
|
||||||
public bool AdditionalFile { get; set; }
|
public bool AdditionalFile { get; set; }
|
||||||
|
|
|
@ -293,6 +293,7 @@ namespace NzbDrone.Core.MediaFiles.TrackImport.Manual
|
||||||
}
|
}
|
||||||
|
|
||||||
item.Quality = decision.Item.Quality;
|
item.Quality = decision.Item.Quality;
|
||||||
|
item.IndexerFlags = (int)decision.Item.IndexerFlags;
|
||||||
item.Size = _diskProvider.GetFileSize(decision.Item.Path);
|
item.Size = _diskProvider.GetFileSize(decision.Item.Path);
|
||||||
item.Rejections = decision.Rejections;
|
item.Rejections = decision.Rejections;
|
||||||
item.Tags = decision.Item.FileTrackInfo;
|
item.Tags = decision.Item.FileTrackInfo;
|
||||||
|
@ -344,6 +345,7 @@ namespace NzbDrone.Core.MediaFiles.TrackImport.Manual
|
||||||
Size = fileInfo.Length,
|
Size = fileInfo.Length,
|
||||||
Modified = fileInfo.LastWriteTimeUtc,
|
Modified = fileInfo.LastWriteTimeUtc,
|
||||||
Quality = file.Quality,
|
Quality = file.Quality,
|
||||||
|
IndexerFlags = (IndexerFlags)file.IndexerFlags,
|
||||||
Artist = artist,
|
Artist = artist,
|
||||||
Album = album,
|
Album = album,
|
||||||
Release = release
|
Release = release
|
||||||
|
|
|
@ -75,6 +75,7 @@ namespace NzbDrone.Core.Notifications.CustomScript
|
||||||
environmentVariables.Add("Lidarr_Release_Quality", remoteAlbum.ParsedAlbumInfo.Quality.Quality.Name);
|
environmentVariables.Add("Lidarr_Release_Quality", remoteAlbum.ParsedAlbumInfo.Quality.Quality.Name);
|
||||||
environmentVariables.Add("Lidarr_Release_QualityVersion", remoteAlbum.ParsedAlbumInfo.Quality.Revision.Version.ToString());
|
environmentVariables.Add("Lidarr_Release_QualityVersion", remoteAlbum.ParsedAlbumInfo.Quality.Revision.Version.ToString());
|
||||||
environmentVariables.Add("Lidarr_Release_ReleaseGroup", releaseGroup ?? string.Empty);
|
environmentVariables.Add("Lidarr_Release_ReleaseGroup", releaseGroup ?? string.Empty);
|
||||||
|
environmentVariables.Add("Lidarr_Release_IndexerFlags", remoteAlbum.Release.IndexerFlags.ToString());
|
||||||
environmentVariables.Add("Lidarr_Download_Client", message.DownloadClientName ?? string.Empty);
|
environmentVariables.Add("Lidarr_Download_Client", message.DownloadClientName ?? string.Empty);
|
||||||
environmentVariables.Add("Lidarr_Download_Client_Type", message.DownloadClientType ?? string.Empty);
|
environmentVariables.Add("Lidarr_Download_Client_Type", message.DownloadClientType ?? string.Empty);
|
||||||
environmentVariables.Add("Lidarr_Download_Id", message.DownloadId ?? string.Empty);
|
environmentVariables.Add("Lidarr_Download_Id", message.DownloadId ?? string.Empty);
|
||||||
|
|
|
@ -26,6 +26,7 @@ namespace NzbDrone.Core.Parser.Model
|
||||||
public List<Track> Tracks { get; set; }
|
public List<Track> Tracks { get; set; }
|
||||||
public Distance Distance { get; set; }
|
public Distance Distance { get; set; }
|
||||||
public QualityModel Quality { get; set; }
|
public QualityModel Quality { get; set; }
|
||||||
|
public IndexerFlags IndexerFlags { get; set; }
|
||||||
public bool ExistingFile { get; set; }
|
public bool ExistingFile { get; set; }
|
||||||
public bool AdditionalFile { get; set; }
|
public bool AdditionalFile { get; set; }
|
||||||
public bool SceneSource { get; set; }
|
public bool SceneSource { get; set; }
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using Newtonsoft.Json;
|
using System.Text.Json.Serialization;
|
||||||
using NzbDrone.Core.Download.Pending;
|
using NzbDrone.Core.Download.Pending;
|
||||||
using NzbDrone.Core.Indexers;
|
using NzbDrone.Core.Indexers;
|
||||||
using NzbDrone.Core.Languages;
|
using NzbDrone.Core.Languages;
|
||||||
|
@ -37,6 +37,9 @@ namespace NzbDrone.Core.Parser.Model
|
||||||
|
|
||||||
public List<Language> Languages { get; set; }
|
public List<Language> Languages { get; set; }
|
||||||
|
|
||||||
|
[JsonIgnore]
|
||||||
|
public IndexerFlags IndexerFlags { get; set; }
|
||||||
|
|
||||||
// Used to track pending releases that are being reprocessed
|
// Used to track pending releases that are being reprocessed
|
||||||
[JsonIgnore]
|
[JsonIgnore]
|
||||||
public PendingReleaseReason? PendingReleaseReason { get; set; }
|
public PendingReleaseReason? PendingReleaseReason { get; set; }
|
||||||
|
@ -85,4 +88,16 @@ namespace NzbDrone.Core.Parser.Model
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Flags]
|
||||||
|
public enum IndexerFlags
|
||||||
|
{
|
||||||
|
Freeleech = 1, // General
|
||||||
|
Halfleech = 2, // General, only 1/2 of download counted
|
||||||
|
DoubleUpload = 4, // General
|
||||||
|
Internal = 8, // General, uploader is an internal release group
|
||||||
|
Scene = 16, // General, the torrent comes from a "scene" group
|
||||||
|
Freeleech75 = 32, // Signifies a torrent counts towards 75 percent of your download quota.
|
||||||
|
Freeleech25 = 64, // Signifies a torrent counts towards 25 percent of your download quota.
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue