1
0
mirror of https://github.com/Radarr/Radarr.git synced 2026-03-11 15:20:34 -04:00

Compare commits

..

3 Commits

Author SHA1 Message Date
Qstick
04b15f2178 Revert cover mapping for collections 2023-10-07 14:46:07 -05:00
Qstick
39d7320a75 Test mapping before collections loop 2023-10-07 13:47:46 -05:00
Qstick
207a4b19dc Test Logging 2023-10-07 13:47:46 -05:00
127 changed files with 764 additions and 1623 deletions

View File

@@ -9,7 +9,7 @@ variables:
testsFolder: './_tests'
yarnCacheFolder: $(Pipeline.Workspace)/.yarn
nugetCacheFolder: $(Pipeline.Workspace)/.nuget/packages
majorVersion: '5.1.1'
majorVersion: '5.0.3'
minorVersion: $[counter('minorVersion', 2000)]
radarrVersion: '$(majorVersion).$(minorVersion)'
buildName: '$(Build.SourceBranchName).$(radarrVersion)'

View File

@@ -14,7 +14,6 @@ import TableOptionsModalWrapper from 'Components/Table/TableOptions/TableOptions
import TablePager from 'Components/Table/TablePager';
import { align, icons, kinds } from 'Helpers/Props';
import translate from 'Utilities/String/translate';
import HistoryFilterModal from './HistoryFilterModal';
import HistoryRowConnector from './HistoryRowConnector';
class History extends Component {
@@ -34,7 +33,6 @@ class History extends Component {
columns,
selectedFilterKey,
filters,
customFilters,
totalRecords,
onFilterSelect,
onFirstPagePress,
@@ -72,8 +70,7 @@ class History extends Component {
alignMenu={align.RIGHT}
selectedFilterKey={selectedFilterKey}
filters={filters}
customFilters={customFilters}
filterModalConnectorComponent={HistoryFilterModal}
customFilters={[]}
onFilterSelect={onFilterSelect}
/>
</PageToolbarSection>
@@ -147,9 +144,8 @@ History.propTypes = {
moviesError: PropTypes.object,
items: PropTypes.arrayOf(PropTypes.object).isRequired,
columns: PropTypes.arrayOf(PropTypes.object).isRequired,
selectedFilterKey: PropTypes.oneOfType([PropTypes.string, PropTypes.number]).isRequired,
selectedFilterKey: PropTypes.string.isRequired,
filters: PropTypes.arrayOf(PropTypes.object).isRequired,
customFilters: PropTypes.arrayOf(PropTypes.object).isRequired,
totalRecords: PropTypes.number,
onFilterSelect: PropTypes.func.isRequired,
onFirstPagePress: PropTypes.func.isRequired

View File

@@ -4,7 +4,6 @@ import { connect } from 'react-redux';
import { createSelector } from 'reselect';
import withCurrentPage from 'Components/withCurrentPage';
import * as historyActions from 'Store/Actions/historyActions';
import { createCustomFiltersSelector } from 'Store/Selectors/createClientSideCollectionSelector';
import { registerPagePopulator, unregisterPagePopulator } from 'Utilities/pagePopulator';
import History from './History';
@@ -12,13 +11,11 @@ function createMapStateToProps() {
return createSelector(
(state) => state.history,
(state) => state.movies,
createCustomFiltersSelector('history'),
(history, movies, customFilters) => {
(history, movies) => {
return {
isMoviesFetching: movies.isFetching,
isMoviesPopulated: movies.isPopulated,
moviesError: movies.error,
customFilters,
...history
};
}

View File

@@ -1,54 +0,0 @@
import React, { useCallback } from 'react';
import { useDispatch, useSelector } from 'react-redux';
import { createSelector } from 'reselect';
import AppState from 'App/State/AppState';
import FilterModal from 'Components/Filter/FilterModal';
import { setHistoryFilter } from 'Store/Actions/historyActions';
function createHistorySelector() {
return createSelector(
(state: AppState) => state.history.items,
(queueItems) => {
return queueItems;
}
);
}
function createFilterBuilderPropsSelector() {
return createSelector(
(state: AppState) => state.history.filterBuilderProps,
(filterBuilderProps) => {
return filterBuilderProps;
}
);
}
interface HistoryFilterModalProps {
isOpen: boolean;
}
export default function HistoryFilterModal(props: HistoryFilterModalProps) {
const sectionItems = useSelector(createHistorySelector());
const filterBuilderProps = useSelector(createFilterBuilderPropsSelector());
const customFilterType = 'history';
const dispatch = useDispatch();
const dispatchSetFilter = useCallback(
(payload: unknown) => {
dispatch(setHistoryFilter(payload));
},
[dispatch]
);
return (
<FilterModal
// TODO: Don't spread all the props
{...props}
sectionItems={sectionItems}
filterBuilderProps={filterBuilderProps}
customFilterType={customFilterType}
dispatchSetFilter={dispatchSetFilter}
/>
);
}

View File

@@ -3,7 +3,6 @@ import PropTypes from 'prop-types';
import React, { Component } from 'react';
import Alert from 'Components/Alert';
import LoadingIndicator from 'Components/Loading/LoadingIndicator';
import FilterMenu from 'Components/Menu/FilterMenu';
import PageContent from 'Components/Page/PageContent';
import PageContentBody from 'Components/Page/PageContentBody';
import PageToolbar from 'Components/Page/Toolbar/PageToolbar';
@@ -22,7 +21,6 @@ import getSelectedIds from 'Utilities/Table/getSelectedIds';
import removeOldSelectedState from 'Utilities/Table/removeOldSelectedState';
import selectAll from 'Utilities/Table/selectAll';
import toggleSelected from 'Utilities/Table/toggleSelected';
import QueueFilterModal from './QueueFilterModal';
import QueueOptionsConnector from './QueueOptionsConnector';
import QueueRowConnector from './QueueRowConnector';
import RemoveQueueItemsModal from './RemoveQueueItemsModal';
@@ -155,16 +153,11 @@ class Queue extends Component {
isMoviesPopulated,
moviesError,
columns,
selectedFilterKey,
filters,
customFilters,
count,
totalRecords,
isGrabbing,
isRemoving,
isRefreshMonitoredDownloadsExecuting,
onRefreshPress,
onFilterSelect,
...otherProps
} = this.props;
@@ -227,15 +220,6 @@ class Queue extends Component {
iconName={icons.TABLE}
/>
</TableOptionsModalWrapper>
<FilterMenu
alignMenu={align.RIGHT}
selectedFilterKey={selectedFilterKey}
filters={filters}
customFilters={customFilters}
filterModalConnectorComponent={QueueFilterModal}
onFilterSelect={onFilterSelect}
/>
</PageToolbarSection>
</PageToolbar>
@@ -257,11 +241,7 @@ class Queue extends Component {
{
isAllPopulated && !hasError && !items.length ?
<Alert kind={kinds.INFO}>
{
selectedFilterKey !== 'all' && count > 0 ?
translate('QueueFilterHasNoItems') :
translate('QueueIsEmpty')
}
{translate('QueueIsEmpty')}
</Alert> :
null
}
@@ -345,22 +325,13 @@ Queue.propTypes = {
moviesError: PropTypes.object,
items: PropTypes.arrayOf(PropTypes.object).isRequired,
columns: PropTypes.arrayOf(PropTypes.object).isRequired,
selectedFilterKey: PropTypes.oneOfType([PropTypes.string, PropTypes.number]).isRequired,
filters: PropTypes.arrayOf(PropTypes.object).isRequired,
customFilters: PropTypes.arrayOf(PropTypes.object).isRequired,
count: PropTypes.number.isRequired,
totalRecords: PropTypes.number,
isGrabbing: PropTypes.bool.isRequired,
isRemoving: PropTypes.bool.isRequired,
isRefreshMonitoredDownloadsExecuting: PropTypes.bool.isRequired,
onRefreshPress: PropTypes.func.isRequired,
onGrabSelectedPress: PropTypes.func.isRequired,
onRemoveSelectedPress: PropTypes.func.isRequired,
onFilterSelect: PropTypes.func.isRequired
};
Queue.defaultProps = {
count: 0
onRemoveSelectedPress: PropTypes.func.isRequired
};
export default Queue;

View File

@@ -6,7 +6,6 @@ import * as commandNames from 'Commands/commandNames';
import withCurrentPage from 'Components/withCurrentPage';
import { executeCommand } from 'Store/Actions/commandActions';
import * as queueActions from 'Store/Actions/queueActions';
import { createCustomFiltersSelector } from 'Store/Selectors/createClientSideCollectionSelector';
import createCommandExecutingSelector from 'Store/Selectors/createCommandExecutingSelector';
import { registerPagePopulator, unregisterPagePopulator } from 'Utilities/pagePopulator';
import Queue from './Queue';
@@ -16,16 +15,12 @@ function createMapStateToProps() {
(state) => state.movies,
(state) => state.queue.options,
(state) => state.queue.paged,
(state) => state.queue.status.item,
createCustomFiltersSelector('queue'),
createCommandExecutingSelector(commandNames.REFRESH_MONITORED_DOWNLOADS),
(movies, options, queue, status, customFilters, isRefreshMonitoredDownloadsExecuting) => {
(movies, options, queue, isRefreshMonitoredDownloadsExecuting) => {
return {
count: options.includeUnknownMovieItems ? status.totalCount : status.count,
isMoviesFetching: movies.isFetching,
isMoviesPopulated: movies.isPopulated,
moviesError: movies.error,
customFilters,
isRefreshMonitoredDownloadsExecuting,
...options,
...queue
@@ -111,10 +106,6 @@ class QueueConnector extends Component {
this.props.setQueueSort({ sortKey });
};
onFilterSelect = (selectedFilterKey) => {
this.props.setQueueFilter({ selectedFilterKey });
};
onTableOptionChange = (payload) => {
this.props.setQueueTableOption(payload);
@@ -149,7 +140,6 @@ class QueueConnector extends Component {
onLastPagePress={this.onLastPagePress}
onPageSelect={this.onPageSelect}
onSortPress={this.onSortPress}
onFilterSelect={this.onFilterSelect}
onTableOptionChange={this.onTableOptionChange}
onRefreshPress={this.onRefreshPress}
onGrabSelectedPress={this.onGrabSelectedPress}
@@ -172,7 +162,6 @@ QueueConnector.propTypes = {
gotoQueueLastPage: PropTypes.func.isRequired,
gotoQueuePage: PropTypes.func.isRequired,
setQueueSort: PropTypes.func.isRequired,
setQueueFilter: PropTypes.func.isRequired,
setQueueTableOption: PropTypes.func.isRequired,
clearQueue: PropTypes.func.isRequired,
grabQueueItems: PropTypes.func.isRequired,

View File

@@ -1,54 +0,0 @@
import React, { useCallback } from 'react';
import { useDispatch, useSelector } from 'react-redux';
import { createSelector } from 'reselect';
import AppState from 'App/State/AppState';
import FilterModal from 'Components/Filter/FilterModal';
import { setQueueFilter } from 'Store/Actions/queueActions';
function createQueueSelector() {
return createSelector(
(state: AppState) => state.queue.paged.items,
(queueItems) => {
return queueItems;
}
);
}
function createFilterBuilderPropsSelector() {
return createSelector(
(state: AppState) => state.queue.paged.filterBuilderProps,
(filterBuilderProps) => {
return filterBuilderProps;
}
);
}
interface QueueFilterModalProps {
isOpen: boolean;
}
export default function QueueFilterModal(props: QueueFilterModalProps) {
const sectionItems = useSelector(createQueueSelector());
const filterBuilderProps = useSelector(createFilterBuilderPropsSelector());
const customFilterType = 'queue';
const dispatch = useDispatch();
const dispatchSetFilter = useCallback(
(payload: unknown) => {
dispatch(setQueueFilter(payload));
},
[dispatch]
);
return (
<FilterModal
// TODO: Don't spread all the props
{...props}
sectionItems={sectionItems}
filterBuilderProps={filterBuilderProps}
customFilterType={customFilterType}
dispatchSetFilter={dispatchSetFilter}
/>
);
}

View File

@@ -1,5 +1,4 @@
import SortDirection from 'Helpers/Props/SortDirection';
import { FilterBuilderProp } from './AppState';
export interface Error {
responseJSON: {
@@ -21,10 +20,6 @@ export interface PagedAppSectionState {
pageSize: number;
}
export interface AppSectionFilterState<T> {
filterBuilderProps: FilterBuilderProp<T>[];
}
export interface AppSectionSchemaState<T> {
isSchemaFetching: boolean;
isSchemaPopulated: boolean;

View File

@@ -1,7 +1,6 @@
import InteractiveImportAppState from 'App/State/InteractiveImportAppState';
import CalendarAppState from './CalendarAppState';
import CommandAppState from './CommandAppState';
import HistoryAppState from './HistoryAppState';
import MovieCollectionAppState from './MovieCollectionAppState';
import MovieFilesAppState from './MovieFilesAppState';
import MoviesAppState, { MovieIndexAppState } from './MoviesAppState';
@@ -47,7 +46,6 @@ export interface CustomFilter {
interface AppState {
calendar: CalendarAppState;
commands: CommandAppState;
history: HistoryAppState;
interactiveImport: InteractiveImportAppState;
movieCollections: MovieCollectionAppState;
movieFiles: MovieFilesAppState;

View File

@@ -1,10 +1,9 @@
import AppSectionState, {
AppSectionFilterState,
} from 'App/State/AppSectionState';
import AppSectionState from 'App/State/AppSectionState';
import Movie from 'Movie/Movie';
import { FilterBuilderProp } from './AppState';
interface CalendarAppState
extends AppSectionState<Movie>,
AppSectionFilterState<Movie> {}
interface CalendarAppState extends AppSectionState<Movie> {
filterBuilderProps: FilterBuilderProp<Movie>[];
}
export default CalendarAppState;

View File

@@ -1,10 +0,0 @@
import AppSectionState, {
AppSectionFilterState,
} from 'App/State/AppSectionState';
import History from 'typings/History';
interface HistoryAppState
extends AppSectionState<History>,
AppSectionFilterState<History> {}
export default HistoryAppState;

View File

@@ -1,8 +1,6 @@
import AppSectionState from 'App/State/AppSectionState';
import MovieCollection from 'typings/MovieCollection';
interface MovieCollectionAppState extends AppSectionState<MovieCollection> {
itemMap: Record<number, number>;
}
type MovieCollectionAppState = AppSectionState<MovieCollection>;
export default MovieCollectionAppState;

View File

@@ -2,11 +2,7 @@ import ModelBase from 'App/ModelBase';
import Language from 'Language/Language';
import { QualityModel } from 'Quality/Quality';
import CustomFormat from 'typings/CustomFormat';
import AppSectionState, {
AppSectionFilterState,
AppSectionItemState,
Error,
} from './AppSectionState';
import AppSectionState, { AppSectionItemState, Error } from './AppSectionState';
export interface StatusMessage {
title: string;
@@ -39,9 +35,7 @@ export interface QueueDetailsAppState extends AppSectionState<Queue> {
params: unknown;
}
export interface QueuePagedAppState
extends AppSectionState<Queue>,
AppSectionFilterState<Queue> {
export interface QueuePagedAppState extends AppSectionState<Queue> {
isGrabbing: boolean;
grabError: Error;
isRemoving: boolean;

View File

@@ -42,9 +42,9 @@ function Agenda(props) {
<div className={styles.agenda}>
{
items.map((item, index) => {
const momentDate = moment(item.sortDate);
const momentDate = moment(item.inCinemas);
const showDate = index === 0 ||
!moment(items[index - 1].sortDate).isSame(momentDate, 'day');
!moment(items[index - 1].inCinemas).isSame(momentDate, 'day');
return (
<AgendaEventConnector

View File

@@ -88,7 +88,7 @@
}
@media only screen and (max-width: $breakpointSmall) {
.overlay {
.event {
flex-direction: column;
}
@@ -111,4 +111,5 @@
.releaseIcon {
margin-right: 20px;
width: 25px;
text-align: right;
}

View File

@@ -95,7 +95,7 @@ class AgendaEvent extends Component {
<div className={styles.overlay}>
<div className={styles.date}>
{showDate ? startTime.format(longDateFormat) : null}
{(showDate) ? startTime.format(longDateFormat) : null}
</div>
<div className={styles.releaseIcon}>

View File

@@ -23,11 +23,13 @@ function createFilterBuilderPropsSelector() {
);
}
interface CalendarFilterModalProps {
interface SeriesIndexFilterModalProps {
isOpen: boolean;
}
export default function CalendarFilterModal(props: CalendarFilterModalProps) {
export default function CalendarFilterModal(
props: SeriesIndexFilterModalProps
) {
const sectionItems = useSelector(createCalendarSelector());
const filterBuilderProps = useSelector(createFilterBuilderPropsSelector());
const customFilterType = 'calendar';

View File

@@ -21,7 +21,6 @@ function createMapStateToProps() {
return {
...collection,
movies: [...collection.movies].sort((a, b) => b.year - a.year),
genres: Array.from(new Set(allGenres)).slice(0, 3)
};
}

View File

@@ -61,7 +61,6 @@ class CollectionMovie extends Component {
const {
id,
title,
status,
overview,
year,
tmdbId,
@@ -124,11 +123,11 @@ class CollectionMovie extends Component {
<div className={styles.overlay}>
<div className={styles.overlayTitle}>
{title} {year > 0 ? `(${year})` : ''}
{title}
</div>
{
id ?
id &&
<div className={styles.overlayStatus}>
<MovieIndexProgressBar
monitored={monitored}
@@ -139,8 +138,7 @@ class CollectionMovie extends Component {
detailedProgressBar={detailedProgressBar}
isAvailable={isAvailable}
/>
</div> :
null
</div>
}
</div>
</Link>
@@ -173,7 +171,6 @@ CollectionMovie.propTypes = {
id: PropTypes.number,
title: PropTypes.string.isRequired,
year: PropTypes.number.isRequired,
status: PropTypes.string.isRequired,
overview: PropTypes.string.isRequired,
monitored: PropTypes.bool,
collectionId: PropTypes.number.isRequired,

View File

@@ -5,7 +5,7 @@
margin: 2px 4px;
border: 1px solid var(--borderColor);
border-radius: 4px;
background-color: var(--inputBackgroundColor);
background-color: #eee;
cursor: default;
}
@@ -17,7 +17,7 @@
padding: 0 4px;
border-left: 4px;
border-left-style: solid;
background-color: var(--themeLightColor);
background-color: var(--white);
color: var(--defaultColor);
}

View File

@@ -14,7 +14,6 @@ class CollectionMovieLabel extends Component {
const {
id,
title,
year,
status,
monitored,
isAvailable,
@@ -36,7 +35,9 @@ class CollectionMovieLabel extends Component {
}
<span>
{title} {year > 0 ? `(${year})` : ''}
{
title
}
</span>
</div>
@@ -61,7 +62,6 @@ class CollectionMovieLabel extends Component {
CollectionMovieLabel.propTypes = {
id: PropTypes.number,
title: PropTypes.string.isRequired,
year: PropTypes.number.isRequired,
status: PropTypes.string,
isAvailable: PropTypes.bool,
monitored: PropTypes.bool,

View File

@@ -28,6 +28,7 @@ function calculatePosterWidth(posterSize, isSmallScreen) {
}
function calculateRowHeight(posterHeight, sortKey, isSmallScreen, overviewOptions) {
const heights = [
overviewOptions.showPosters ? posterHeight : 75,
isSmallScreen ? columnPaddingSmallScreen : columnPadding
@@ -121,8 +122,8 @@ class CollectionOverviews extends Component {
overviewOptions
} = this.props;
const posterWidth = overviewOptions.showPosters ? calculatePosterWidth(overviewOptions.size, isSmallScreen) : 0;
const posterHeight = overviewOptions.showPosters ? calculatePosterHeight(posterWidth) : 0;
const posterWidth = calculatePosterWidth(overviewOptions.size, isSmallScreen);
const posterHeight = calculatePosterHeight(posterWidth);
const rowHeight = calculateRowHeight(posterHeight, sortKey, isSmallScreen, overviewOptions);
this.setState({

View File

@@ -6,12 +6,9 @@ import { filterBuilderTypes, filterBuilderValueTypes, icons } from 'Helpers/Prop
import BoolFilterBuilderRowValue from './BoolFilterBuilderRowValue';
import DateFilterBuilderRowValue from './DateFilterBuilderRowValue';
import FilterBuilderRowValueConnector from './FilterBuilderRowValueConnector';
import HistoryEventTypeFilterBuilderRowValue from './HistoryEventTypeFilterBuilderRowValue';
import ImportListFilterBuilderRowValueConnector from './ImportListFilterBuilderRowValueConnector';
import IndexerFilterBuilderRowValueConnector from './IndexerFilterBuilderRowValueConnector';
import LanguageFilterBuilderRowValue from './LanguageFilterBuilderRowValue';
import MinimumAvailabilityFilterBuilderRowValue from './MinimumAvailabilityFilterBuilderRowValue';
import MovieFilterBuilderRowValue from './MovieFilterBuilderRowValue';
import ProtocolFilterBuilderRowValue from './ProtocolFilterBuilderRowValue';
import QualityFilterBuilderRowValueConnector from './QualityFilterBuilderRowValueConnector';
import QualityProfileFilterBuilderRowValueConnector from './QualityProfileFilterBuilderRowValueConnector';
@@ -61,15 +58,9 @@ function getRowValueConnector(selectedFilterBuilderProp) {
case filterBuilderValueTypes.DATE:
return DateFilterBuilderRowValue;
case filterBuilderValueTypes.HISTORY_EVENT_TYPE:
return HistoryEventTypeFilterBuilderRowValue;
case filterBuilderValueTypes.INDEXER:
return IndexerFilterBuilderRowValueConnector;
case filterBuilderValueTypes.LANGUAGE:
return LanguageFilterBuilderRowValue;
case filterBuilderValueTypes.PROTOCOL:
return ProtocolFilterBuilderRowValue;
@@ -79,9 +70,6 @@ function getRowValueConnector(selectedFilterBuilderProp) {
case filterBuilderValueTypes.QUALITY_PROFILE:
return QualityProfileFilterBuilderRowValueConnector;
case filterBuilderValueTypes.MOVIE:
return MovieFilterBuilderRowValue;
case filterBuilderValueTypes.RELEASE_STATUS:
return ReleaseStatusFilterBuilderRowValue;

View File

@@ -1,16 +0,0 @@
import { FilterBuilderProp } from 'App/State/AppState';
interface FilterBuilderRowOnChangeProps {
name: string;
value: unknown[];
}
interface FilterBuilderRowValueProps {
filterType?: string;
filterValue: string | number | object | string[] | number[] | object[];
selectedFilterBuilderProp: FilterBuilderProp<unknown>;
sectionItem: unknown[];
onChange: (payload: FilterBuilderRowOnChangeProps) => void;
}
export default FilterBuilderRowValueProps;

View File

@@ -1,5 +1,5 @@
.tag {
display: flex;
height: 21px;
&.isLastTag {
.or {
@@ -18,5 +18,4 @@
.or {
margin: 0 3px;
color: var(--themeDarkColor);
line-height: 31px;
}

View File

@@ -7,7 +7,7 @@ import styles from './FilterBuilderRowValueTag.css';
function FilterBuilderRowValueTag(props) {
return (
<div
<span
className={styles.tag}
>
<TagInputTag
@@ -22,7 +22,7 @@ function FilterBuilderRowValueTag(props) {
{translate('Or')}
</div>
}
</div>
</span>
);
}

View File

@@ -1,51 +0,0 @@
import React from 'react';
import translate from 'Utilities/String/translate';
import FilterBuilderRowValue from './FilterBuilderRowValue';
import FilterBuilderRowValueProps from './FilterBuilderRowValueProps';
const EVENT_TYPE_OPTIONS = [
{
id: 1,
get name() {
return translate('Grabbed');
},
},
{
id: 3,
get name() {
return translate('Imported');
},
},
{
id: 4,
get name() {
return translate('Failed');
},
},
{
id: 5,
get name() {
return translate('Deleted');
},
},
{
id: 6,
get name() {
return translate('Renamed');
},
},
{
id: 7,
get name() {
return translate('Ignored');
},
},
];
function HistoryEventTypeFilterBuilderRowValue(
props: FilterBuilderRowValueProps
) {
return <FilterBuilderRowValue {...props} tagList={EVENT_TYPE_OPTIONS} />;
}
export default HistoryEventTypeFilterBuilderRowValue;

View File

@@ -1,13 +0,0 @@
import React from 'react';
import { useSelector } from 'react-redux';
import createLanguagesSelector from 'Store/Selectors/createLanguagesSelector';
import FilterBuilderRowValue from './FilterBuilderRowValue';
import FilterBuilderRowValueProps from './FilterBuilderRowValueProps';
function LanguageFilterBuilderRowValue(props: FilterBuilderRowValueProps) {
const { items } = useSelector(createLanguagesSelector());
return <FilterBuilderRowValue {...props} tagList={items} />;
}
export default LanguageFilterBuilderRowValue;

View File

@@ -1,19 +0,0 @@
import React from 'react';
import { useSelector } from 'react-redux';
import Movie from 'Movie/Movie';
import createAllMoviesSelector from 'Store/Selectors/createAllMoviesSelector';
import sortByName from 'Utilities/Array/sortByName';
import FilterBuilderRowValue from './FilterBuilderRowValue';
import FilterBuilderRowValueProps from './FilterBuilderRowValueProps';
function MovieFilterBuilderRowValue(props: FilterBuilderRowValueProps) {
const allMovies: Movie[] = useSelector(createAllMoviesSelector());
const tagList = allMovies
.map((movie) => ({ id: movie.id, name: movie.title }))
.sort(sortByName);
return <FilterBuilderRowValue {...props} tagList={tagList} />;
}
export default MovieFilterBuilderRowValue;

View File

@@ -2,10 +2,8 @@
display: flex;
justify-content: flex-end;
margin-right: $formLabelRightMarginWidth;
padding-top: 8px;
min-height: 35px;
text-align: end;
font-weight: bold;
line-height: 35px;
}
.hasError {

View File

@@ -37,8 +37,6 @@ function getType({ type, selectOptionsProviderAction }) {
return inputTypes.OAUTH;
case 'rootFolder':
return inputTypes.ROOT_FOLDER_SELECT;
case 'qualityProfile':
return inputTypes.QUALITY_PROFILE_SELECT;
default:
return inputTypes.TEXT;
}

View File

@@ -3,7 +3,6 @@ import React from 'react';
import Icon from 'Components/Icon';
import Link from 'Components/Link/Link';
import { icons } from 'Helpers/Props';
import translate from 'Utilities/String/translate';
import styles from './ModalContent.css';
function ModalContent(props) {
@@ -29,7 +28,6 @@ function ModalContent(props) {
<Icon
name={icons.CLOSE}
size={18}
title={translate('Close')}
/>
</Link>
}

View File

@@ -82,7 +82,6 @@ class PageHeader extends Component {
aria-label="Donate"
to="https://radarr.video/donate"
size={14}
title={translate('Donate')}
/>
<IconButton
className={styles.translate}

View File

@@ -24,7 +24,6 @@ function PageHeaderActionsMenu(props) {
<MenuButton className={styles.menuButton} aria-label="Menu Button">
<Icon
name={icons.INTERACTIVE}
title={translate('Menu')}
/>
</MenuButton>

View File

@@ -2,13 +2,10 @@ export const BOOL = 'bool';
export const BYTES = 'bytes';
export const DATE = 'date';
export const DEFAULT = 'default';
export const HISTORY_EVENT_TYPE = 'historyEventType';
export const INDEXER = 'indexer';
export const LANGUAGE = 'language';
export const PROTOCOL = 'protocol';
export const QUALITY = 'quality';
export const QUALITY_PROFILE = 'qualityProfile';
export const MOVIE = 'movie';
export const RELEASE_STATUS = 'releaseStatus';
export const MINIMUM_AVAILABILITY = 'minimumAvailability';
export const TAG = 'tag';

View File

@@ -202,12 +202,6 @@
.headerContent {
padding: 15px;
}
.title {
font-weight: 300;
font-size: 30px;
line-height: 30px;
}
}
@media only screen and (max-width: $breakpointLarge) {

View File

@@ -44,7 +44,7 @@ import MovieCollectionLabelConnector from './../MovieCollectionLabelConnector';
import MovieCastPostersConnector from './Credits/Cast/MovieCastPostersConnector';
import MovieCrewPostersConnector from './Credits/Crew/MovieCrewPostersConnector';
import MovieDetailsLinks from './MovieDetailsLinks';
import MovieReleaseDates from './MovieReleaseDates';
import MovieReleaseDatesConnector from './MovieReleaseDatesConnector';
import MovieStatusLabel from './MovieStatusLabel';
import MovieTagsConnector from './MovieTagsConnector';
import MovieTitlesTable from './Titles/MovieTitlesTable';
@@ -433,7 +433,7 @@ class MovieDetails extends Component {
}
title={translate('ReleaseDates')}
body={
<MovieReleaseDates
<MovieReleaseDatesConnector
inCinemas={inCinemas}
physicalRelease={physicalRelease}
digitalRelease={digitalRelease}

View File

@@ -0,0 +1,67 @@
import PropTypes from 'prop-types';
import React from 'react';
import Icon from 'Components/Icon';
import { icons } from 'Helpers/Props';
import getRelativeDate from 'Utilities/Date/getRelativeDate';
import styles from './MovieReleaseDates.css';
function MovieReleaseDates(props) {
const {
showRelativeDates,
shortDateFormat,
timeFormat,
inCinemas,
physicalRelease,
digitalRelease
} = props;
return (
<div>
{
!!inCinemas &&
<div >
<div className={styles.dateIcon}>
<Icon
name={icons.IN_CINEMAS}
/>
</div>
{getRelativeDate(inCinemas, shortDateFormat, showRelativeDates, { timeFormat, timeForToday: false })}
</div>
}
{
!!digitalRelease &&
<div >
<div className={styles.dateIcon}>
<Icon
name={icons.MOVIE_FILE}
/>
</div>
{getRelativeDate(digitalRelease, shortDateFormat, showRelativeDates, { timeFormat, timeForToday: false })}
</div>
}
{
!!physicalRelease &&
<div >
<div className={styles.dateIcon}>
<Icon
name={icons.DISC}
/>
</div>
{getRelativeDate(physicalRelease, shortDateFormat, showRelativeDates, { timeFormat, timeForToday: false })}
</div>
}
</div>
);
}
MovieReleaseDates.propTypes = {
showRelativeDates: PropTypes.bool.isRequired,
shortDateFormat: PropTypes.string.isRequired,
longDateFormat: PropTypes.string.isRequired,
timeFormat: PropTypes.string.isRequired,
inCinemas: PropTypes.string,
physicalRelease: PropTypes.string,
digitalRelease: PropTypes.string
};
export default MovieReleaseDates;

View File

@@ -1,64 +0,0 @@
import React from 'react';
import { useSelector } from 'react-redux';
import Icon from 'Components/Icon';
import { icons } from 'Helpers/Props';
import createUISettingsSelector from 'Store/Selectors/createUISettingsSelector';
import getRelativeDate from 'Utilities/Date/getRelativeDate';
import translate from 'Utilities/String/translate';
import styles from './MovieReleaseDates.css';
interface MovieReleaseDatesProps {
inCinemas: string;
physicalRelease: string;
digitalRelease: string;
}
function MovieReleaseDates(props: MovieReleaseDatesProps) {
const { inCinemas, physicalRelease, digitalRelease } = props;
const { showRelativeDates, shortDateFormat, timeFormat } = useSelector(
createUISettingsSelector()
);
return (
<div>
{inCinemas ? (
<div title={translate('InCinemas')}>
<div className={styles.dateIcon}>
<Icon name={icons.IN_CINEMAS} />
</div>
{getRelativeDate(inCinemas, shortDateFormat, showRelativeDates, {
timeFormat,
timeForToday: false,
})}
</div>
) : null}
{digitalRelease ? (
<div title={translate('DigitalRelease')}>
<div className={styles.dateIcon}>
<Icon name={icons.MOVIE_FILE} />
</div>
{getRelativeDate(digitalRelease, shortDateFormat, showRelativeDates, {
timeFormat,
timeForToday: false,
})}
</div>
) : null}
{physicalRelease ? (
<div title={translate('PhysicalRelease')}>
<div className={styles.dateIcon}>
<Icon name={icons.DISC} />
</div>
{getRelativeDate(
physicalRelease,
shortDateFormat,
showRelativeDates,
{ timeFormat, timeForToday: false }
)}
</div>
) : null}
</div>
);
}
export default MovieReleaseDates;

View File

@@ -0,0 +1,20 @@
import { connect } from 'react-redux';
import { createSelector } from 'reselect';
import createUISettingsSelector from 'Store/Selectors/createUISettingsSelector';
import MovieReleaseDates from './MovieReleaseDates';
function createMapStateToProps() {
return createSelector(
createUISettingsSelector(),
(uiSettings) => {
return {
showRelativeDates: uiSettings.showRelativeDates,
shortDateFormat: uiSettings.shortDateFormat,
longDateFormat: uiSettings.longDateFormat,
timeFormat: uiSettings.timeFormat
};
}
);
}
export default connect(createMapStateToProps, null)(MovieReleaseDates);

View File

@@ -100,15 +100,6 @@ function MovieIndexSortMenu(props: MovieIndexSortMenuProps) {
{translate('DigitalRelease')}
</SortMenuItem>
<SortMenuItem
name="releaseDate"
sortKey={sortKey}
sortDirection={sortDirection}
onPress={onSortSelect}
>
{translate('ReleaseDates')}
</SortMenuItem>
<SortMenuItem
name="tmdbRating"
sortKey={sortKey}

View File

@@ -80,12 +80,8 @@ function DownloadClientOptions(props) {
legend={translate('FailedDownloadHandling')}
>
<Form>
<FormGroup
advancedSettings={advancedSettings}
isAdvanced={true}
size={sizes.MEDIUM}
>
<FormLabel>{translate('AutoRedownloadFailed')}</FormLabel>
<FormGroup size={sizes.MEDIUM}>
<FormLabel>{translate('Redownload')}</FormLabel>
<FormInputGroup
type={inputTypes.CHECK}
@@ -95,28 +91,7 @@ function DownloadClientOptions(props) {
{...settings.autoRedownloadFailed}
/>
</FormGroup>
{
settings.autoRedownloadFailed.value ?
<FormGroup
advancedSettings={advancedSettings}
isAdvanced={true}
size={sizes.MEDIUM}
>
<FormLabel>{translate('AutoRedownloadFailedFromInteractiveSearch')}</FormLabel>
<FormInputGroup
type={inputTypes.CHECK}
name="autoRedownloadFailedFromInteractiveSearch"
helpText={translate('AutoRedownloadFailedFromInteractiveSearchHelpText')}
onChange={onInputChange}
{...settings.autoRedownloadFailedFromInteractiveSearch}
/>
</FormGroup> :
null
}
</Form>
<Alert kind={kinds.INFO}>
{translate('RemoveDownloadsAlert')}
</Alert>

View File

@@ -6,8 +6,6 @@ import getSectionState from 'Utilities/State/getSectionState';
import { set, updateServerSideCollection } from '../baseActions';
function createFetchServerSideCollectionHandler(section, url, fetchDataAugmenter) {
const [baseSection] = section.split('.');
return function(getState, payload, dispatch) {
dispatch(set({ section, isFetching: true }));
@@ -27,13 +25,10 @@ function createFetchServerSideCollectionHandler(section, url, fetchDataAugmenter
const {
selectedFilterKey,
filters
filters,
customFilters
} = sectionState;
const customFilters = getState().customFilters.items.filter((customFilter) => {
return customFilter.type === section || customFilter.type === baseSection;
});
const selectedFilters = findSelectedFilters(selectedFilterKey, filters, customFilters);
selectedFilters.forEach((filter) => {
@@ -42,8 +37,7 @@ function createFetchServerSideCollectionHandler(section, url, fetchDataAugmenter
const promise = createAjaxRequest({
url,
data,
traditional: true
data
}).request;
promise.done((response) => {

View File

@@ -49,6 +49,8 @@ export const defaultState = {
selectedFilterKey: 'monitored',
customFilters: [],
filters: [
{
key: 'all',

View File

@@ -1,7 +1,7 @@
import React from 'react';
import { createAction } from 'redux-actions';
import Icon from 'Components/Icon';
import { filterBuilderTypes, filterBuilderValueTypes, filterTypes, icons, sortDirections } from 'Helpers/Props';
import { filterTypes, icons, sortDirections } from 'Helpers/Props';
import { createThunk, handleThunks } from 'Store/thunks';
import createAjaxRequest from 'Utilities/createAjaxRequest';
import serverSideCollectionHandlers from 'Utilities/serverSideCollectionHandlers';
@@ -177,33 +177,6 @@ export const defaultState = {
}
]
}
],
filterBuilderProps: [
{
name: 'eventType',
label: () => translate('EventType'),
type: filterBuilderTypes.EQUAL,
valueType: filterBuilderValueTypes.HISTORY_EVENT_TYPE
},
{
name: 'movieIds',
label: () => translate('Movie'),
type: filterBuilderTypes.EQUAL,
valueType: filterBuilderValueTypes.MOVIE
},
{
name: 'quality',
label: () => translate('Quality'),
type: filterBuilderTypes.EQUAL,
valueType: filterBuilderValueTypes.QUALITY
},
{
name: 'languages',
label: () => translate('Languages'),
type: filterBuilderTypes.CONTAINS,
valueType: filterBuilderValueTypes.LANGUAGE
}
]
};

View File

@@ -1,5 +1,4 @@
import _ from 'lodash';
import moment from 'moment';
import { createAction } from 'redux-actions';
import { batchActions } from 'redux-batched-actions';
import { filterTypePredicates, filterTypes, sortDirections } from 'Helpers/Props';
@@ -241,55 +240,16 @@ export const sortPredicates = {
return item.year || undefined;
},
inCinemas: function(item, direction) {
if (item.inCinemas) {
return moment(item.inCinemas).unix();
}
if (direction === sortDirections.DESCENDING) {
return -1 * Number.MAX_VALUE;
}
return Number.MAX_VALUE;
inCinemas: function(item) {
return item.inCinemas || '';
},
physicalRelease: function(item, direction) {
if (item.physicalRelease) {
return moment(item.physicalRelease).unix();
}
if (direction === sortDirections.DESCENDING) {
return -1 * Number.MAX_VALUE;
}
return Number.MAX_VALUE;
physicalRelease: function(item) {
return item.physicalRelease || '';
},
digitalRelease: function(item, direction) {
if (item.digitalRelease) {
return moment(item.digitalRelease).unix();
}
if (direction === sortDirections.DESCENDING) {
return -1 * Number.MAX_VALUE;
}
return Number.MAX_VALUE;
},
releaseDate: function(item, direction) {
const { inCinemas, digitalRelease, physicalRelease } = item;
const releaseDate = digitalRelease || physicalRelease || inCinemas;
if (releaseDate) {
return moment(releaseDate).unix();
}
if (direction === sortDirections.DESCENDING) {
return -1 * Number.MAX_VALUE;
}
return Number.MAX_VALUE;
digitalRelease: function(item) {
return item.digitalRelease || '';
}
};

View File

@@ -4,7 +4,7 @@ import React from 'react';
import { createAction } from 'redux-actions';
import { batchActions } from 'redux-batched-actions';
import Icon from 'Components/Icon';
import { filterBuilderTypes, filterBuilderValueTypes, icons, sortDirections } from 'Helpers/Props';
import { icons, sortDirections } from 'Helpers/Props';
import { createThunk, handleThunks } from 'Store/thunks';
import createAjaxRequest from 'Utilities/createAjaxRequest';
import serverSideCollectionHandlers from 'Utilities/serverSideCollectionHandlers';
@@ -159,43 +159,6 @@ export const defaultState = {
isVisible: true,
isModifiable: false
}
],
selectedFilterKey: 'all',
filters: [
{
key: 'all',
label: 'All',
filters: []
}
],
filterBuilderProps: [
{
name: 'movieIds',
label: () => translate('Movie'),
type: filterBuilderTypes.EQUAL,
valueType: filterBuilderValueTypes.MOVIE
},
{
name: 'quality',
label: () => translate('Quality'),
type: filterBuilderTypes.EQUAL,
valueType: filterBuilderValueTypes.QUALITY
},
{
name: 'languages',
label: () => translate('Languages'),
type: filterBuilderTypes.CONTAINS,
valueType: filterBuilderValueTypes.LANGUAGE
},
{
name: 'protocol',
label: () => translate('Protocol'),
type: filterBuilderTypes.EQUAL,
valueType: filterBuilderValueTypes.PROTOCOL
}
]
},
sortPredicates: {
@@ -210,8 +173,7 @@ export const persistState = [
'queue.paged.pageSize',
'queue.paged.sortKey',
'queue.paged.sortDirection',
'queue.paged.columns',
'queue.paged.selectedFilterKey'
'queue.paged.columns'
];
//
@@ -236,7 +198,6 @@ export const GOTO_NEXT_QUEUE_PAGE = 'queue/gotoQueueNextPage';
export const GOTO_LAST_QUEUE_PAGE = 'queue/gotoQueueLastPage';
export const GOTO_QUEUE_PAGE = 'queue/gotoQueuePage';
export const SET_QUEUE_SORT = 'queue/setQueueSort';
export const SET_QUEUE_FILTER = 'queue/setQueueFilter';
export const SET_QUEUE_TABLE_OPTION = 'queue/setQueueTableOption';
export const SET_QUEUE_OPTION = 'queue/setQueueOption';
export const CLEAR_QUEUE = 'queue/clearQueue';
@@ -261,7 +222,6 @@ export const gotoQueueNextPage = createThunk(GOTO_NEXT_QUEUE_PAGE);
export const gotoQueueLastPage = createThunk(GOTO_LAST_QUEUE_PAGE);
export const gotoQueuePage = createThunk(GOTO_QUEUE_PAGE);
export const setQueueSort = createThunk(SET_QUEUE_SORT);
export const setQueueFilter = createThunk(SET_QUEUE_FILTER);
export const setQueueTableOption = createAction(SET_QUEUE_TABLE_OPTION);
export const setQueueOption = createAction(SET_QUEUE_OPTION);
export const clearQueue = createAction(CLEAR_QUEUE);
@@ -308,8 +268,7 @@ export const actionHandlers = handleThunks({
[serverSideCollectionHandlers.NEXT_PAGE]: GOTO_NEXT_QUEUE_PAGE,
[serverSideCollectionHandlers.LAST_PAGE]: GOTO_LAST_QUEUE_PAGE,
[serverSideCollectionHandlers.EXACT_PAGE]: GOTO_QUEUE_PAGE,
[serverSideCollectionHandlers.SORT]: SET_QUEUE_SORT,
[serverSideCollectionHandlers.FILTER]: SET_QUEUE_FILTER
[serverSideCollectionHandlers.SORT]: SET_QUEUE_SORT
},
fetchDataAugmenter
),

View File

@@ -0,0 +1,17 @@
import { createSelector } from 'reselect';
function createCollectionSelector() {
return createSelector(
(state, { collectionId }) => collectionId,
(state) => state.movieCollections.itemMap,
(state) => state.movieCollections.items,
(collectionId, itemMap, allCollections) => {
if (allCollections && itemMap && collectionId in itemMap) {
return allCollections[itemMap[collectionId]];
}
return undefined;
}
);
}
export default createCollectionSelector;

View File

@@ -1,17 +0,0 @@
import { createSelector } from 'reselect';
import AppState from 'App/State/AppState';
function createCollectionSelector() {
return createSelector(
(_: AppState, { collectionId }: { collectionId: number }) => collectionId,
(state: AppState) => state.movieCollections.itemMap,
(state: AppState) => state.movieCollections.items,
(collectionId, itemMap, allCollections) => {
return allCollections && itemMap && collectionId in itemMap
? allCollections[itemMap[collectionId]]
: undefined;
}
);
}
export default createCollectionSelector;

View File

@@ -72,7 +72,6 @@ function getInternalLink(source) {
function getTestLink(source, props) {
switch (source) {
case 'IndexerStatusCheck':
case 'IndexerLongTermStatusCheck':
return (
<SpinnerIconButton
name={icons.TEST}

View File

@@ -1,27 +0,0 @@
import Language from 'Language/Language';
import { QualityModel } from 'Quality/Quality';
import CustomFormat from './CustomFormat';
export type HistoryEventType =
| 'grabbed'
| 'downloadFolderImported'
| 'downloadFailed'
| 'movieFileDeleted'
| 'movieFolderImported'
| 'movieFileRenamed'
| 'downloadIgnored';
export default interface History {
movieId: number;
sourceTitle: string;
languages: Language[];
quality: QualityModel;
customFormats: CustomFormat[];
customFormatScore: number;
qualityCutoffNotMet: boolean;
date: string;
downloadId: string;
eventType: HistoryEventType;
data: unknown;
id: number;
}

View File

@@ -73,15 +73,15 @@ namespace NzbDrone.Common.Test.InstrumentationTests
[TestCase(@"[Info] MigrationController: *** Migrating Database=radarr-main;Host=postgres14;Username=mySecret;Password=mySecret;Port=5432;token=mySecret;Enlist=False&username=mySecret;mypassword=mySecret;mypass=shouldkeep1;test_token=mySecret;password=123%@%_@!#^#@;use_password=mySecret;get_token=shouldkeep2;usetoken=shouldkeep3;passwrd=mySecret;")]
// Announce URLs (passkeys) Magnet & Tracker
[TestCase(@"magnet_uri"":""magnet:?xt=urn:btih:9pr04sgkillroyimaveql2tyu8xyui&dn=&tr=https%3a%2f%2fxxx.yyy%2f9pr04sg601233210IMAveQL2tyu8xyui%2fannounce""}")]
[TestCase(@"magnet_uri"":""magnet:?xt=urn:btih:9pr04sgkillroyimaveql2tyu8xyui&dn=&tr=https%3a%2f%2fxxx.yyy%2ftracker.php%2f9pr04sg601233210IMAveQL2tyu8xyui%2fannounce""}")]
[TestCase(@"magnet_uri"":""magnet:?xt=urn:btih:9pr04sgkillroyimaveql2tyu8xyui&dn=&tr=https%3a%2f%2fxxx.yyy%2fannounce%2f9pr04sg601233210IMAveQL2tyu8xyui""}")]
[TestCase(@"magnet_uri"":""magnet:?xt=urn:btih:9pr04sgkillroyimaveql2tyu8xyui&dn=&tr=https%3a%2f%2fxxx.yyy%2fannounce.php%3fpasskey%3d9pr04sg601233210IMAveQL2tyu8xyui""}")]
[TestCase(@"tracker"":""https://xxx.yyy/9pr04sg601233210IMAveQL2tyu8xyui/announce""}")]
[TestCase(@"tracker"":""https://xxx.yyy/tracker.php/9pr04sg601233210IMAveQL2tyu8xyui/announce""}")]
[TestCase(@"tracker"":""https://xxx.yyy/announce/9pr04sg601233210IMAveQL2tyu8xyui""}")]
[TestCase(@"tracker"":""https://xxx.yyy/announce.php?passkey=9pr04sg601233210IMAveQL2tyu8xyui""}")]
[TestCase(@"tracker"":""http://xxx.yyy/announce.php?passkey=9pr04sg601233210IMAveQL2tyu8xyui"",""info"":""http://xxx.yyy/info?a=b""")]
[TestCase(@"magnet_uri"":""magnet:?xt=urn:btih:9pr04sgkillroyimaveql2tyu8xyui&dn=&tr=https%3a%2f%2fxxx.yyy%2f9pr04sg601233210imaveql2tyu8xyui%2fannounce""}")]
[TestCase(@"magnet_uri"":""magnet:?xt=urn:btih:9pr04sgkillroyimaveql2tyu8xyui&dn=&tr=https%3a%2f%2fxxx.yyy%2ftracker.php%2f9pr04sg601233210imaveql2tyu8xyui%2fannounce""}")]
[TestCase(@"magnet_uri"":""magnet:?xt=urn:btih:9pr04sgkillroyimaveql2tyu8xyui&dn=&tr=https%3a%2f%2fxxx.yyy%2fannounce%2f9pr04sg601233210imaveql2tyu8xyui""}")]
[TestCase(@"magnet_uri"":""magnet:?xt=urn:btih:9pr04sgkillroyimaveql2tyu8xyui&dn=&tr=https%3a%2f%2fxxx.yyy%2fannounce.php%3fpasskey%3d9pr04sg601233210imaveql2tyu8xyui""}")]
[TestCase(@"tracker"":""https://xxx.yyy/9pr04sg601233210imaveql2tyu8xyui/announce""}")]
[TestCase(@"tracker"":""https://xxx.yyy/tracker.php/9pr04sg601233210imaveql2tyu8xyui/announce""}")]
[TestCase(@"tracker"":""https://xxx.yyy/announce/9pr04sg601233210imaveql2tyu8xyui""}")]
[TestCase(@"tracker"":""https://xxx.yyy/announce.php?passkey=9pr04sg601233210imaveql2tyu8xyui""}")]
[TestCase(@"tracker"":""http://xxx.yyy/announce.php?passkey=9pr04sg601233210imaveql2tyu8xyui"",""info"":""http://xxx.yyy/info?a=b""")]
// Notifiarr
[TestCase(@"https://xxx.yyy/api/v1/notification/radarr/9pr04sg6-0123-3210-imav-eql2tyu8xyui")]

View File

@@ -20,7 +20,7 @@ namespace NzbDrone.Common.Instrumentation
new (@"\b(\w*)?(_?(?<!use|get_)token|username|passwo?rd)=(?<secret>[^&=]+?)(?= |&|$|;)", RegexOptions.Compiled | RegexOptions.IgnoreCase),
// Trackers Announce Keys; Designed for Qbit Json; should work for all in theory
new (@"announce(\.php)?(/|%2f|%3fpasskey%3d)(?<secret>[a-z0-9]{16,})|(?<secret>[a-z0-9]{16,})(/|%2f)announce", RegexOptions.Compiled | RegexOptions.IgnoreCase),
new (@"announce(\.php)?(/|%2f|%3fpasskey%3d)(?<secret>[a-z0-9]{16,})|(?<secret>[a-z0-9]{16,})(/|%2f)announce"),
// Path
new (@"C:\\Users\\(?<secret>[^\""]+?)(\\|$)", RegexOptions.Compiled | RegexOptions.IgnoreCase),

View File

@@ -12,7 +12,7 @@ namespace NzbDrone.Common.Instrumentation
{
public static class NzbDroneLogger
{
private const string FILE_LOG_LAYOUT = @"${date:format=yyyy-MM-dd HH\:mm\:ss.f}|${level}|${logger}|${message}${onexception:inner=${newline}${newline}[v${assembly-version}] ${exception:format=ToString}${newline}${exception:format=Data}${newline}}";
private const string FILE_LOG_LAYOUT = @"${date:format=yyyy-MM-dd HH\:mm\:ss.fff}|${level}|${logger}|${message}${onexception:inner=${newline}${newline}[v${assembly-version}] ${exception:format=ToString}${newline}${exception:format=Data}${newline}}";
private static bool _isConfigured;

View File

@@ -16,7 +16,7 @@
<PackageReference Include="SharpZipLib" Version="1.3.3" />
<PackageReference Include="System.Text.Json" Version="6.0.8" />
<PackageReference Include="System.ValueTuple" Version="4.5.0" />
<PackageReference Include="System.Data.SQLite.Core.Servarr" Version="1.0.115.5-18" />
<PackageReference Include="System.Data.SQLite.Core.Servarr" Version="1.0.118-22" />
<PackageReference Include="System.Configuration.ConfigurationManager" Version="6.0.1" />
<PackageReference Include="System.IO.FileSystem.AccessControl" Version="5.0.0" />
<PackageReference Include="System.Runtime.Loader" Version="4.3.0" />

View File

@@ -72,15 +72,11 @@ namespace NzbDrone.Core.Test.DecisionEngineTests
Subject.IsSatisfiedBy(_remoteMovie, null).Accepted.Should().BeFalse();
}
[TestCase("Series Title S02 Disc 1 1080i Blu-ray DTS-HD MA 2.0 AVC-TrollHD")]
[TestCase("Series Title S03 Disc 1 1080p Blu-ray LPCM 2.0 AVC-TrollHD")]
[TestCase("SERIES TITLE S02 1080P FULL BLURAY AVC DTS-HD MA 5 1")]
[TestCase("Series.Title.S06.2016.DISC.3.BluRay.1080p.AVC.Atmos.TrueHD7.1-MTeam")]
[TestCase("Series Title S05 Disc 1 BluRay 1080p AVC Atmos TrueHD 7 1-MTeam")]
[TestCase("Series Title S05 Disc 1 BluRay 1080p AVC Atmos TrueHD 7 1-MTeam")]
[TestCase("Someone.the.Entertainer.Presents.S01.NTSC.3xDVD9.MPEG-2.DD2.0")]
[TestCase("Series.Title.S00.The.Christmas.Special.2011.PAL.DVD5.DD2.0")]
[TestCase("Series.of.Desire.2000.S1_D01.NTSC.DVD5")]
[TestCase("How the Earth Was Made S02 Disc 1 1080i Blu-ray DTS-HD MA 2.0 AVC-TrollHD")]
[TestCase("The Universe S03 Disc 1 1080p Blu-ray LPCM 2.0 AVC-TrollHD")]
[TestCase("HELL ON WHEELS S02 1080P FULL BLURAY AVC DTS-HD MA 5 1")]
[TestCase("Game.of.Thrones.S06.2016.DISC.3.BluRay.1080p.AVC.Atmos.TrueHD7.1-MTeam")]
[TestCase("Game of Thrones S05 Disc 1 BluRay 1080p AVC Atmos TrueHD 7 1-MTeam")]
public void should_return_false_if_matches_disc_format(string title)
{
_remoteMovie.Release.Title = title;

View File

@@ -364,7 +364,6 @@ namespace NzbDrone.Core.Test.ParserTests
[TestCase("German.Only.Movie.2021.French.1080p.BluRay.AVC-UNTAVC")]
[TestCase("Movie.Title.2008.US.Directors.Cut.UHD.BD66.Blu-ray")]
[TestCase("Movie.2009.Blu.ray.AVC.DTS.HD.MA.5.1")]
[TestCase("[BD]Movie.Title.2008.2023.1080p.COMPLETE.BLURAY-RlsGrp")]
public void should_parse_brdisk_1080p_quality(string title)
{
ParseAndVerifyQuality(title, QualitySource.BLURAY, false, Resolution.R1080p, Modifier.BRDISK);

View File

@@ -5,7 +5,7 @@
<ItemGroup>
<PackageReference Include="Dapper" Version="2.0.143" />
<PackageReference Include="NBuilder" Version="6.1.0" />
<PackageReference Include="System.Data.SQLite.Core.Servarr" Version="1.0.115.5-18" />
<PackageReference Include="System.Data.SQLite.Core.Servarr" Version="1.0.118-22" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\NzbDrone.Test.Common\Radarr.Test.Common.csproj" />

View File

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

View File

@@ -66,8 +66,7 @@ namespace NzbDrone.Core.Annotations
OAuth,
Device,
TagSelect,
RootFolder,
QualityProfile
RootFolder
}
public enum HiddenType

View File

@@ -1,36 +0,0 @@
using FluentValidation;
using NzbDrone.Core.Annotations;
using NzbDrone.Core.Movies;
using NzbDrone.Core.Validation;
namespace NzbDrone.Core.AutoTagging.Specifications
{
public class QualityProfileSpecificationValidator : AbstractValidator<QualityProfileSpecification>
{
public QualityProfileSpecificationValidator()
{
RuleFor(c => c.Value).GreaterThan(0);
}
}
public class QualityProfileSpecification : AutoTaggingSpecificationBase
{
private static readonly QualityProfileSpecificationValidator Validator = new ();
public override int Order => 1;
public override string ImplementationName => "Quality Profile";
[FieldDefinition(1, Label = "Quality Profile", Type = FieldType.QualityProfile)]
public int Value { get; set; }
protected override bool IsSatisfiedByWithoutNegate(Movie movie)
{
return Value == movie.QualityProfileId;
}
public override NzbDroneValidationResult Validate()
{
return new NzbDroneValidationResult(Validator.Validate(this));
}
}
}

View File

@@ -190,13 +190,6 @@ namespace NzbDrone.Core.Configuration
set { SetValue("AutoRedownloadFailed", value); }
}
public bool AutoRedownloadFailedFromInteractiveSearch
{
get { return GetValueBoolean("AutoRedownloadFailedFromInteractiveSearch", true); }
set { SetValue("AutoRedownloadFailedFromInteractiveSearch", value); }
}
public bool CreateEmptyMovieFolders
{
get { return GetValueBoolean("CreateEmptyMovieFolders", false); }

View File

@@ -22,7 +22,6 @@ namespace NzbDrone.Core.Configuration
bool EnableCompletedDownloadHandling { get; set; }
bool AutoRedownloadFailed { get; set; }
bool AutoRedownloadFailedFromInteractiveSearch { get; set; }
// Media Management
bool AutoUnmonitorPreviouslyDownloadedMovies { get; set; }

View File

@@ -407,7 +407,7 @@ namespace NzbDrone.Core.Datastore
return pagingSpec;
}
protected void AddFilters(SqlBuilder builder, PagingSpec<TModel> pagingSpec)
private void AddFilters(SqlBuilder builder, PagingSpec<TModel> pagingSpec)
{
var filters = pagingSpec.FilterExpressions;

View File

@@ -41,16 +41,14 @@ namespace NzbDrone.Core.Datastore
private static string GetConnectionString(string dbPath)
{
var connectionBuilder = new SQLiteConnectionStringBuilder
{
DataSource = dbPath,
CacheSize = (int)-20000,
DateTimeKind = DateTimeKind.Utc,
JournalMode = OsInfo.IsOsx ? SQLiteJournalModeEnum.Truncate : SQLiteJournalModeEnum.Wal,
Pooling = true,
Version = 3,
BusyTimeout = 100
};
var connectionBuilder = new SQLiteConnectionStringBuilder();
connectionBuilder.DataSource = dbPath;
connectionBuilder.CacheSize = (int)-20000;
connectionBuilder.DateTimeKind = DateTimeKind.Utc;
connectionBuilder.JournalMode = OsInfo.IsOsx ? SQLiteJournalModeEnum.Truncate : SQLiteJournalModeEnum.Wal;
connectionBuilder.Pooling = true;
connectionBuilder.Version = 3;
if (OsInfo.IsOsx)
{

View File

@@ -12,8 +12,7 @@ namespace NzbDrone.Core.DecisionEngine.Specifications
private static readonly Regex[] DiscRegex = new[]
{
new Regex(@"(?:dis[ck])(?:[-_. ]\d+[-_. ])(?:(?:(?:480|720|1080|2160)[ip]|)[-_. ])?(?:Blu\-?ray)", RegexOptions.Compiled | RegexOptions.IgnoreCase),
new Regex(@"(?:(?:480|720|1080|2160)[ip]|)[-_. ](?:full)[-_. ](?:Blu\-?ray)", RegexOptions.Compiled | RegexOptions.IgnoreCase),
new Regex(@"(?:\d?x?M?DVD-?[R59])", RegexOptions.Compiled | RegexOptions.IgnoreCase)
new Regex(@"(?:(?:480|720|1080|2160)[ip]|)[-_. ](?:full)[-_. ](?:Blu\-?ray)", RegexOptions.Compiled | RegexOptions.IgnoreCase)
};
private static readonly string[] _dvdContainerTypes = new[] { "vob", "iso" };
@@ -40,8 +39,8 @@ namespace NzbDrone.Core.DecisionEngine.Specifications
{
if (regex.IsMatch(subject.Release.Title))
{
_logger.Debug("Release contains raw Bluray/DVD, rejecting.");
return Decision.Reject("Raw Bluray/DVD release");
_logger.Debug("Release contains raw Bluray, rejecting.");
return Decision.Reject("Raw Bluray release");
}
}

View File

@@ -2,7 +2,6 @@ using System.Collections.Generic;
using NzbDrone.Common.Messaging;
using NzbDrone.Core.Download.TrackedDownloads;
using NzbDrone.Core.Languages;
using NzbDrone.Core.Parser.Model;
using NzbDrone.Core.Qualities;
namespace NzbDrone.Core.Download
@@ -24,6 +23,5 @@ namespace NzbDrone.Core.Download
public TrackedDownload TrackedDownload { get; set; }
public List<Language> Languages { get; set; }
public bool SkipRedownload { get; set; }
public ReleaseSourceType ReleaseSource { get; set; }
}
}

View File

@@ -1,11 +1,9 @@
using System;
using System.Collections.Generic;
using System.Linq;
using NzbDrone.Common.Extensions;
using NzbDrone.Core.Download.TrackedDownloads;
using NzbDrone.Core.History;
using NzbDrone.Core.Messaging.Events;
using NzbDrone.Core.Parser.Model;
namespace NzbDrone.Core.Download
{
@@ -130,7 +128,6 @@ namespace NzbDrone.Core.Download
private void PublishDownloadFailedEvent(List<MovieHistory> historyItems, string message, TrackedDownload trackedDownload = null, bool skipRedownload = false)
{
var historyItem = historyItems.First();
Enum.TryParse(historyItem.Data.GetValueOrDefault(MovieHistory.RELEASE_SOURCE, ReleaseSourceType.Unknown.ToString()), out ReleaseSourceType releaseSource);
var downloadFailedEvent = new DownloadFailedEvent
{
@@ -143,8 +140,7 @@ namespace NzbDrone.Core.Download
Data = historyItem.Data,
TrackedDownload = trackedDownload,
Languages = historyItem.Languages,
SkipRedownload = skipRedownload,
ReleaseSource = releaseSource
SkipRedownload = skipRedownload
};
_eventAggregator.PublishEvent(downloadFailedEvent);

View File

@@ -5,7 +5,6 @@ using NzbDrone.Core.IndexerSearch;
using NzbDrone.Core.Messaging;
using NzbDrone.Core.Messaging.Commands;
using NzbDrone.Core.Messaging.Events;
using NzbDrone.Core.Parser.Model;
namespace NzbDrone.Core.Download
{
@@ -39,12 +38,6 @@ namespace NzbDrone.Core.Download
return;
}
if (message.ReleaseSource == ReleaseSourceType.InteractiveSearch && !_configService.AutoRedownloadFailedFromInteractiveSearch)
{
_logger.Debug("Auto redownloading failed movies from interactive search is disabled");
return;
}
if (message.MovieId != 0)
{
_logger.Debug("Failed download contains a movie, searching again.");

View File

@@ -7,7 +7,7 @@ using NzbDrone.Core.Movies.Events;
namespace NzbDrone.Core.HealthCheck.Checks
{
[CheckOn(typeof(MovieUpdatedEvent))]
[CheckOn(typeof(MoviesDeletedEvent))]
[CheckOn(typeof(MoviesDeletedEvent), CheckOnCondition.FailedOnly)]
[CheckOn(typeof(MovieRefreshCompleteEvent))]
public class RemovedMovieCheck : HealthCheckBase, ICheckOnCondition<MovieUpdatedEvent>, ICheckOnCondition<MoviesDeletedEvent>
{

View File

@@ -1,7 +1,6 @@
using System;
using System.Collections.Generic;
using System.Linq;
using NLog;
using NzbDrone.Common.Cache;
using NzbDrone.Common.EnvironmentInfo;
using NzbDrone.Common.Messaging;
@@ -29,7 +28,6 @@ namespace NzbDrone.Core.HealthCheck
private readonly IProvideHealthCheck[] _scheduledHealthChecks;
private readonly Dictionary<Type, IEventDrivenHealthCheck[]> _eventDrivenHealthChecks;
private readonly IEventAggregator _eventAggregator;
private readonly Logger _logger;
private readonly ICached<HealthCheck> _healthCheckResults;
private readonly HashSet<IProvideHealthCheck> _pendingHealthChecks;
@@ -42,12 +40,10 @@ namespace NzbDrone.Core.HealthCheck
IEventAggregator eventAggregator,
ICacheManager cacheManager,
IDebounceManager debounceManager,
IRuntimeInfo runtimeInfo,
Logger logger)
IRuntimeInfo runtimeInfo)
{
_healthChecks = healthChecks.ToArray();
_eventAggregator = eventAggregator;
_logger = logger;
_healthCheckResults = cacheManager.GetCache<HealthCheck>(GetType());
_pendingHealthChecks = new HashSet<IProvideHealthCheck>();
@@ -92,14 +88,7 @@ namespace NzbDrone.Core.HealthCheck
try
{
var results = healthChecks.Select(c =>
{
_logger.Trace("Check health -> {0}", c.GetType().Name);
var result = c.Check();
_logger.Trace("Check health <- {0}", c.GetType().Name);
return result;
})
var results = healthChecks.Select(c => c.Check())
.ToList();
foreach (var result in results)

View File

@@ -19,7 +19,6 @@ namespace NzbDrone.Core.History
void DeleteForMovies(List<int> movieIds);
MovieHistory MostRecentForMovie(int movieId);
List<MovieHistory> Since(DateTime date, MovieHistoryEventType? eventType);
PagingSpec<MovieHistory> GetPaged(PagingSpec<MovieHistory> pagingSpec, int[] languages, int[] qualities);
}
public class HistoryRepository : BasicRepository<MovieHistory>, IHistoryRepository
@@ -75,6 +74,19 @@ namespace NzbDrone.Core.History
Delete(c => movieIds.Contains(c.MovieId));
}
protected override SqlBuilder PagedBuilder() => new SqlBuilder(_database.DatabaseType)
.Join<MovieHistory, Movie>((h, m) => h.MovieId == m.Id)
.Join<Movie, QualityProfile>((m, p) => m.QualityProfileId == p.Id)
.LeftJoin<Movie, MovieMetadata>((m, mm) => m.MovieMetadataId == mm.Id);
protected override IEnumerable<MovieHistory> PagedQuery(SqlBuilder sql) =>
_database.QueryJoined<MovieHistory, Movie, QualityProfile>(sql, (hist, movie, profile) =>
{
hist.Movie = movie;
hist.Movie.QualityProfile = profile;
return hist;
});
public MovieHistory MostRecentForMovie(int movieId)
{
return Query(x => x.MovieId == movieId).MaxBy(h => h.Date);
@@ -94,77 +106,5 @@ namespace NzbDrone.Core.History
return PagedQuery(builder).OrderBy(h => h.Date).ToList();
}
public PagingSpec<MovieHistory> GetPaged(PagingSpec<MovieHistory> pagingSpec, int[] languages, int[] qualities)
{
pagingSpec.Records = GetPagedRecords(PagedBuilder(pagingSpec, languages, qualities), pagingSpec, PagedQuery);
var countTemplate = $"SELECT COUNT(*) FROM (SELECT /**select**/ FROM \"{TableMapping.Mapper.TableNameMapping(typeof(MovieHistory))}\" /**join**/ /**innerjoin**/ /**leftjoin**/ /**where**/ /**groupby**/ /**having**/) AS \"Inner\"";
pagingSpec.TotalRecords = GetPagedRecordCount(PagedBuilder(pagingSpec, languages, qualities).Select(typeof(MovieHistory)), pagingSpec, countTemplate);
return pagingSpec;
}
private SqlBuilder PagedBuilder(PagingSpec<MovieHistory> pagingSpec, int[] languages, int[] qualities)
{
var builder = Builder()
.Join<MovieHistory, Movie>((h, m) => h.MovieId == m.Id)
.Join<Movie, QualityProfile>((m, p) => m.QualityProfileId == p.Id)
.LeftJoin<Movie, MovieMetadata>((m, mm) => m.MovieMetadataId == mm.Id);
AddFilters(builder, pagingSpec);
if (languages is { Length: > 0 })
{
builder.Where($"({BuildLanguageWhereClause(languages)})");
}
if (qualities is { Length: > 0 })
{
builder.Where($"({BuildQualityWhereClause(qualities)})");
}
return builder;
}
protected override IEnumerable<MovieHistory> PagedQuery(SqlBuilder builder) =>
_database.QueryJoined<MovieHistory, Movie, QualityProfile>(builder, (hist, movie, profile) =>
{
hist.Movie = movie;
hist.Movie.QualityProfile = profile;
return hist;
});
private string BuildLanguageWhereClause(int[] languages)
{
var clauses = new List<string>();
foreach (var language in languages)
{
// There are 4 different types of values we should see:
// - Not the last value in the array
// - When it's the last value in the array and on different OSes
// - When it was converted from a single language
clauses.Add($"\"{TableMapping.Mapper.TableNameMapping(typeof(MovieHistory))}\".\"Languages\" LIKE '[% {language},%]'");
clauses.Add($"\"{TableMapping.Mapper.TableNameMapping(typeof(MovieHistory))}\".\"Languages\" LIKE '[% {language}' || CHAR(13) || '%]'");
clauses.Add($"\"{TableMapping.Mapper.TableNameMapping(typeof(MovieHistory))}\".\"Languages\" LIKE '[% {language}' || CHAR(10) || '%]'");
clauses.Add($"\"{TableMapping.Mapper.TableNameMapping(typeof(MovieHistory))}\".\"Languages\" LIKE '[{language}]'");
}
return $"({string.Join(" OR ", clauses)})";
}
private string BuildQualityWhereClause(int[] qualities)
{
var clauses = new List<string>();
foreach (var quality in qualities)
{
clauses.Add($"\"{TableMapping.Mapper.TableNameMapping(typeof(MovieHistory))}\".\"Quality\" LIKE '%_quality_: {quality},%'");
}
return $"({string.Join(" OR ", clauses)})";
}
}
}

View File

@@ -19,7 +19,7 @@ namespace NzbDrone.Core.History
public interface IHistoryService
{
QualityModel GetBestQualityInHistory(QualityProfile profile, int movieId);
PagingSpec<MovieHistory> Paged(PagingSpec<MovieHistory> pagingSpec, int[] languages, int[] qualities);
PagingSpec<MovieHistory> Paged(PagingSpec<MovieHistory> pagingSpec);
MovieHistory MostRecentForMovie(int movieId);
MovieHistory MostRecentForDownloadId(string downloadId);
MovieHistory Get(int historyId);
@@ -49,9 +49,9 @@ namespace NzbDrone.Core.History
_logger = logger;
}
public PagingSpec<MovieHistory> Paged(PagingSpec<MovieHistory> pagingSpec, int[] languages, int[] qualities)
public PagingSpec<MovieHistory> Paged(PagingSpec<MovieHistory> pagingSpec)
{
return _historyRepository.GetPaged(pagingSpec, languages, qualities);
return _historyRepository.GetPaged(pagingSpec);
}
public MovieHistory MostRecentForMovie(int movieId)

View File

@@ -1,6 +1,4 @@
using System.Collections.Generic;
using System.Linq;
using Dapper;
using NzbDrone.Core.Datastore;
using NzbDrone.Core.Messaging.Events;
@@ -10,7 +8,6 @@ namespace NzbDrone.Core.ImportLists.ImportExclusions
{
bool IsMovieExcluded(int tmdbid);
ImportExclusion GetByTmdbid(int tmdbid);
List<int> AllExcludedTmdbIds();
}
public class ImportExclusionsRepository : BasicRepository<ImportExclusion>, IImportExclusionsRepository
@@ -29,12 +26,5 @@ namespace NzbDrone.Core.ImportLists.ImportExclusions
{
return Query(x => x.TmdbId == tmdbid).First();
}
public List<int> AllExcludedTmdbIds()
{
using var conn = _database.OpenConnection();
return conn.Query<int>("SELECT \"TmdbId\" FROM \"ImportExclusions\"").ToList();
}
}
}

View File

@@ -41,7 +41,7 @@ namespace NzbDrone.Core.ImportLists.ImportExclusions
public List<ImportExclusion> AddExclusions(List<ImportExclusion> exclusions)
{
_exclusionRepository.InsertMany(DeDupeExclusions(exclusions));
_exclusionRepository.InsertMany(exclusions);
return exclusions;
}
@@ -76,20 +76,8 @@ namespace NzbDrone.Core.ImportLists.ImportExclusions
if (message.AddExclusion)
{
_logger.Debug("Adding {0} Deleted Movies to Import Exclusions", message.Movies.Count);
var exclusions = message.Movies.Select(m => new ImportExclusion { TmdbId = m.TmdbId, MovieTitle = m.Title, MovieYear = m.Year }).ToList();
_exclusionRepository.InsertMany(DeDupeExclusions(exclusions));
_exclusionRepository.InsertMany(message.Movies.Select(m => new ImportExclusion { TmdbId = m.TmdbId, MovieTitle = m.Title, MovieYear = m.Year }).ToList());
}
}
private List<ImportExclusion> DeDupeExclusions(List<ImportExclusion> exclusions)
{
var existingExclusions = _exclusionRepository.AllExcludedTmdbIds();
return exclusions
.DistinctBy(x => x.TmdbId)
.Where(x => !existingExclusions.Contains(x.TmdbId))
.ToList();
}
}
}

View File

@@ -43,9 +43,9 @@ namespace NzbDrone.Core.ImportLists
private void SyncAll()
{
if (_importListFactory.Enabled().Empty())
if (_importListFactory.Enabled().Where(a => ((ImportListDefinition)a.Definition).EnableAuto).Empty())
{
_logger.Debug("No enabled import lists, skipping sync and cleaning");
_logger.Debug("No import lists with automatic add enabled, skipping sync and cleaning");
return;
}

View File

@@ -147,6 +147,7 @@
"RefreshInformationAndScanDisk": "تحديث المعلومات ومسح القرص",
"RefreshAndScan": "التحديث والمسح الضوئي",
"Refresh": "تحديث",
"Redownload": "إعادة التنزيل",
"RecyclingBinCleanup": "تنظيف سلة إعادة التدوير",
"RecyclingBin": "صندوق إعادة التدوير",
"RecycleBinHelpText": "ستنتقل ملفات الأفلام إلى هنا عند حذفها بدلاً من حذفها نهائيًا",

View File

@@ -23,6 +23,7 @@
"ReadTheWikiForMoreInformation": "Прочетете Wiki за повече информация",
"Reason": "Причина",
"RecyclingBinCleanup": "Почистване на кошчето за рециклиране",
"Redownload": "Презареждане",
"Refresh": "Обнови",
"RefreshMovie": "Опресняване на филма",
"RegularExpressionsCanBeTested": "Регулярните изрази могат да бъдат тествани ",

View File

@@ -730,6 +730,7 @@
"RecycleBinCleanupDaysHelpTextWarning": "Els fitxers de la paperera de reciclatge més antics que el nombre de dies seleccionat es netejaran automàticament",
"RecycleBinHelpText": "Els fitxers de pel·lícula aniran aquí quan se suprimeixin en lloc de suprimir-se permanentment",
"RecyclingBinCleanup": "Neteja de la paperera de reciclatge",
"Redownload": "Torna a baixar",
"RefreshCollections": "Actualitza col·leccions",
"RefreshInformationAndScanDisk": "Actualitza la informació i escaneja el disc",
"RefreshLists": "Actualitza llistes",

View File

@@ -315,6 +315,7 @@
"ReadTheWikiForMoreInformation": "Další informace najdete na Wiki",
"Reason": "Důvod",
"RecyclingBinCleanup": "Recyklace koše Vyčištění",
"Redownload": "Znovu stáhnout",
"Refresh": "Obnovit",
"RefreshMovie": "Obnovit film",
"RegularExpressionsCanBeTested": "Regulární výrazy lze testovat ",

View File

@@ -313,6 +313,7 @@
"ReadTheWikiForMoreInformation": "Læs Wiki for mere information",
"Reason": "Grund",
"RecyclingBinCleanup": "Oprydning af papirkurven",
"Redownload": "Genindlæs",
"Refresh": "Opdater",
"RefreshMovie": "Opdater film",
"RegularExpressionsCanBeTested": "Regulære udtryk kan testes ",

View File

@@ -461,6 +461,7 @@
"RecycleBinHelpText": "Gelöschte Filmdateien werden hierher verschoben anstatt sie direkt endgültig zu löschen",
"RecyclingBin": "Papierkorb",
"RecyclingBinCleanup": "Papierkorb aufräumen",
"Redownload": "Nochmal herunterladen",
"RefreshInformationAndScanDisk": "Metadaten aktualisieren und Festplatte scannen",
"RefreshMovie": "Film aktualisieren",
"ReleaseRejected": "Release abgelehnt",
@@ -959,7 +960,7 @@
"AddRootFolder": "Stammordner hinzufügen",
"AddQualityProfile": "Qualitätsprofil hinzufügen",
"AddedToDownloadQueue": "Zur Downloadwarteschlange hinzugefügt",
"AddDownloadClient": "Downloadmanager hinzufügen",
"AddDownloadClient": "Zum Downloader hinzufügen",
"AddCustomFormat": "Eigenes Format hinzufügen",
"AddDelayProfile": "Verzögerungsprofil hinzufügen",
"Add": "Hinzufügen",
@@ -1169,14 +1170,12 @@
"ApplyTagsHelpTextHowToApplyImportLists": "Wie werden Tags zu ausgewählten Filmen zugeteilt",
"DeleteRootFolderMessageText": "Indexer '{0}' wirklich löschen?",
"DeleteRootFolder": "Stammordner löschen",
"AddConnection": "Verbindung hinzufügen",
"AddConnection": "Sammlung bearbeiten",
"BypassDelayIfAboveCustomFormatScoreMinimumScore": "Minimum der eigenen Formate Bewertungspunkte",
"NotificationStatusAllClientHealthCheckMessage": "Wegen Fehlern sind keine Applikationen verfügbar",
"NotificationStatusSingleClientHealthCheckMessage": "Applikationen wegen folgender Fehler nicht verfügbar: {0}",
"RemoveFromDownloadClientHelpTextWarning": "Dies wird den Download und alle bereits heruntergeladenen Dateien aus dem Downloader entfernen.",
"DownloadClientsLoadError": "Downloader konnten nicht geladen werden",
"IMDbId": "TMDb ID",
"DisabledForLocalAddresses": "Für Lokale Adressen deaktivieren",
"AddCondition": "Bedingung hinzufügen",
"AddAutoTag": "Automatisches Tag hinzufügen"
"DisabledForLocalAddresses": "Für Lokale Adressen deaktivieren"
}

View File

@@ -197,6 +197,7 @@
"AutomaticSearch": "Αυτόματη αναζήτηση",
"ChmodGroup": "Ομάδα chmod",
"Queued": "Σε ουρά",
"Redownload": "Κατεβάστε ξανά",
"Refresh": "Φρεσκάρω",
"RelativePath": "Σχετική διαδρομή",
"Released": "Κυκλοφόρησε",

View File

@@ -89,9 +89,6 @@
"AuthenticationRequiredUsernameHelpTextWarning": "Enter a new username",
"AuthenticationRequiredWarning": "To prevent remote access without authentication, {appName} now requires authentication to be enabled. You can optionally disable authentication from local addresses.",
"Auto": "Auto",
"AutoRedownloadFailed": "Redownload Failed",
"AutoRedownloadFailedFromInteractiveSearch": "Redownload Failed from Interactive Search",
"AutoRedownloadFailedFromInteractiveSearchHelpText": "Automatically search for and attempt to download a different release when failed release was grabbed from interactive search",
"AutoRedownloadFailedHelpText": "Automatically search for and attempt to download a different release",
"AutoTagging": "Auto Tagging",
"AutoTaggingNegateHelpText": "If checked, the auto tagging rule will not apply if this {0} condition matches.",
@@ -881,7 +878,6 @@
"QualitySettings": "Quality Settings",
"QualitySettingsSummary": "Quality sizes and naming",
"Queue": "Queue",
"QueueFilterHasNoItems": "Selected queue filter has no items",
"QueueIsEmpty": "Queue is empty",
"QueueLoadError": "Failed to load Queue",
"Queued": "Queued",
@@ -912,6 +908,7 @@
"RecyclingBin": "Recycling Bin",
"RecyclingBinCleanup": "Recycling Bin Cleanup",
"Reddit": "Reddit",
"Redownload": "Redownload",
"Refresh": "Refresh",
"RefreshAndScan": "Refresh & Scan",
"RefreshCollections": "Refresh Collections",

View File

@@ -479,6 +479,7 @@
"ReleaseDates": "Fechas de Estreno",
"RefreshMovie": "Actualizar película",
"RefreshInformationAndScanDisk": "Actualizar la información al escanear el disco",
"Redownload": "Volver a descargar",
"RecyclingBinCleanup": "Limpieza de Papelera de Reciclaje",
"RecyclingBin": "Papelera de Reciclaje",
"RecycleBinHelpText": "Los archivos iran aquí una vez se hayan borrado en vez de ser borrados permanentemente",

View File

@@ -324,6 +324,7 @@
"AlternativeTitle": "Vaihtoehtoinen nimi",
"Age": "Ikä",
"RecyclingBinCleanup": "Roskakorin tyhjennys",
"Redownload": "Lataa uudelleen",
"RefreshMovie": "Päivitä elokuva",
"RejectionCount": "Hylkäämisluku",
"RelativePath": "Suhteellinen polku",

File diff suppressed because it is too large Load Diff

View File

@@ -227,6 +227,7 @@
"ReadTheWikiForMoreInformation": "קרא את הוויקי למידע נוסף",
"Reason": "סיבה",
"RecyclingBinCleanup": "ניקוי סל המיחזור",
"Redownload": "הורד מחדש",
"Refresh": "לְרַעֲנֵן",
"RefreshMovie": "רענן סרט",
"RejectionCount": "ספירת דחייה",

View File

@@ -442,6 +442,7 @@
"Presets": "प्रीसेट",
"Profiles": "प्रोफाइल",
"Reason": "कारण",
"Redownload": "redownload",
"Refresh": "ताज़ा करना",
"RefreshMovie": "फिल्म को रिफ्रेश करें",
"Reset": "रीसेट",

View File

@@ -137,9 +137,9 @@
"CertificateValidation": "Tanúsítvány érvényesítése",
"Cast": "Szereplők",
"CantFindMovie": "Miért nem találom a filmemet?",
"CancelProcessing": "Folyamat leállítása",
"CancelProcessing": "Feldolgozás Megállítása",
"CancelPendingTask": "Biztosan törlöd ezt a függőben lévő feladatot?",
"Cancel": "Mégse",
"Cancel": "Vissza",
"Calendar": "Naptár",
"BypassProxyForLocalAddresses": "Proxy megkerülése a helyi hálózatos címekhez",
"BranchUpdateMechanism": "A külső frissítési mechanizmus által használt ágazat",
@@ -148,7 +148,7 @@
"BindAddressHelpText": "Érvényes IP-cím, localhost vagy '*' minden interfészhez",
"BindAddress": "Kapcsolási Cím",
"BeforeUpdate": "Alkalmazásfrissítés előtt",
"Backups": "Biztonsági mentések",
"Backups": "Biztonsági Mentés",
"BackupRetentionHelpText": "A megőrzési időnél régebbi automatikus biztonsági másolatok automatikusan törlésre kerülnek",
"BackupNow": "Biztonsági Mentés Most",
"BackupIntervalHelpText": "Időeltérés a biztonsági mentések között",
@@ -158,7 +158,7 @@
"AvailabilityDelay": "Elérhetőség Késleltetése",
"AutoUnmonitorPreviouslyDownloadedMoviesHelpText": "A lemezről törölt filmeket a Radarr automatikusan eltávolítja a megfigyelt filmek közül",
"AutoRedownloadFailedHelpText": "Másik kiadás automatikus keresése és letöltése",
"AutomaticSearch": "Automatikus keresés",
"AutomaticSearch": "Automatikus Keresés",
"Automatic": "Automatikus",
"AnalyticsEnabledHelpText": "Küldjön névtelen használati és hibainformációkat a Radarr szervereire. Ez magában foglalja a böngészőjéről szóló információkat, mely Radarr WebUI oldalakat használja, a hibajelentést, valamint az operációs rendszer adatait. Ezeket az információkat a funkciók és a hibajavítások rangsorolására használjuk fel.",
"AuthenticationMethodHelpText": "Felhasználónév és Jelszó szükséges a Radarr-hoz való hozzáféréshez",
@@ -167,7 +167,7 @@
"AsAllDayHelpText": "Az események egész napos eseményként jelennek meg a naptáradban",
"ApplyTags": "Címkék alkalmazása",
"AgeWhenGrabbed": "Kora (mikor hozzáadásra került)",
"Age": "Kor",
"Age": "Kora",
"AddListExclusion": "Kizárási Lista Hozzáadása",
"ApiKey": "API Kulcs",
"All": "Összes",
@@ -392,6 +392,7 @@
"RefreshInformationAndScanDisk": "Információk frissítése és lemez átvizsgálása",
"RefreshAndScan": "Frissítés & Keresés",
"Refresh": "Frissítés",
"Redownload": "Letöltés újra",
"RecyclingBinCleanup": "Lomtár kiürítése",
"RecyclingBin": "Lomtár",
"RecycleBinHelpText": "A filmfájlok végleges törlés helyett ide kerülnek törléskor",
@@ -741,7 +742,7 @@
"Unmonitored": "Nem felügyelt",
"UnmappedFolders": "Feltérképezetlen mappák",
"Ungroup": "Csoport eltávolítása",
"Unavailable": "Nem elérhető",
"Unavailable": "Nem érhető el",
"UnableToLoadUISettings": "Nem sikerült betölteni a felhasználói felület beállításait",
"UnableToLoadTheCalendar": "Nem sikerült betölteni a naptárat",
"UnableToLoadTags": "Nem sikerült betölteni a címkéket",
@@ -817,7 +818,7 @@
"CurrentlyInstalled": "Jelenleg telepítve",
"Connection": "Kapcsolat",
"CertValidationNoLocal": "Letiltva a helyi címeknél",
"CalendarOptions": "Naptár beállítások",
"CalendarOptions": "Naptár opciók",
"BuiltIn": "Beépített",
"AuthForm": "Felhasználó (Bejelentkezési oldal)",
"AuthBasic": "Alap (Böngésző felugró ablak)",
@@ -829,7 +830,7 @@
"AfterManualRefresh": "A kézi frissítés után",
"AddToDownloadQueue": "Hozzáadás a letöltési sorhoz",
"AddRootFolder": "Gyökérmappa hozzáadása",
"AddQualityProfile": "Minőségi Profil hozzáadása",
"AddQualityProfile": "Minőségi profil hozzáadása",
"AddedToDownloadQueue": "Hozzáadva a letöltési sorhoz",
"AddDownloadClient": "Letöltőkliens hozzáadása",
"AddDelayProfile": "Késleltetési profil hozzáadása",
@@ -1080,7 +1081,7 @@
"RottenTomatoesRating": "Tomato Értékelés",
"TotalMovies": "Összes film",
"ApplicationUrlHelpText": "Az alkalmazás külső URL-címe, beleértve a http(s)://-t, a portot és az URL-alapot",
"ApplicationURL": "Alkalmazás URL",
"ApplicationURL": "Alkalmazás URL-je",
"PreferredProtocol": "Preferált protokoll",
"SettingsTheme": "Téma",
"SettingsThemeHelpText": "Változtasd meg az alkalmazás felhasználói felület témáját, az „Auto” téma az operációs rendszer témáját használja a Világos vagy Sötét mód beállításához. A Theme.Park ihlette",
@@ -1101,10 +1102,5 @@
"DeleteCustomFormatMessageText": "Biztosan törölni akarod a/az '{0}' egyéni formátumot?",
"RemoveSelectedItemsQueueMessageText": "Biztosan el akar távolítani {0} elemet a várólistáról?",
"RemoveSelectedItemQueueMessageText": "Biztosan el akar távolítani 1 elemet a várólistáról?",
"ApiKeyValidationHealthCheckMessage": "Kérlek frissítsd az API kulcsot, ami legalább {0} karakter hosszú. Ezt megteheted a Beállításokban, vagy a config file-ban",
"AuthenticationRequiredPasswordHelpTextWarning": "Adjon meg új jelszót",
"AuthenticationRequiredUsernameHelpTextWarning": "Adjon meg új felhasználónevet",
"AutomaticAdd": "Automatikus hozzáadás",
"Unknown": "Ismeretlen",
"UnknownEventTooltip": "Ismeretlen Esemény"
"ApiKeyValidationHealthCheckMessage": "Kérlek frissítsd az API kulcsot, ami legalább {0} karakter hosszú. Ezt megteheted a Beállításokban, vagy a config file-ban"
}

View File

@@ -289,6 +289,7 @@
"RadarrCalendarFeed": "Radarr dagatalstraumur",
"ReadTheWikiForMoreInformation": "Lestu Wiki fyrir frekari upplýsingar",
"RecyclingBinCleanup": "Hreinsun ruslakörfu",
"Redownload": "Endurhlaða",
"Refresh": "Hressa",
"RefreshMovie": "Hressa kvikmynd",
"RelativePath": "Hlutfallsleg leið",

View File

@@ -415,6 +415,7 @@
"RegularExpressionsCanBeTested": "Le espressioni regolari possono essere testate ",
"RefreshMovie": "Aggiorna il Film",
"RefreshInformationAndScanDisk": "Aggiorna le informazioni e scansiona il disco",
"Redownload": "Riscarica",
"RecyclingBinCleanup": "Pulizia del cestino",
"RecyclingBin": "Cestino",
"RecycleBinHelpText": "I file dei film andranno qui quando cancellati invece che venire eliminati definitivamente",

View File

@@ -268,6 +268,7 @@
"ReadTheWikiForMoreInformation": "詳細については、Wikiをお読みください",
"Reason": "理由",
"RecyclingBinCleanup": "ごみ箱のクリーンアップ",
"Redownload": "再ダウンロード",
"Refresh": "更新",
"RefreshMovie": "映画を更新する",
"RegularExpressionsCanBeTested": "正規表現をテストできます ",

View File

@@ -270,6 +270,7 @@
"ReadTheWikiForMoreInformation": "자세한 내용은 Wiki를 참조하십시오.",
"Reason": "이유",
"RecyclingBinCleanup": "재활용 빈 정리",
"Redownload": "다시 다운로드",
"Refresh": "새롭게 하다",
"RefreshMovie": "영화 새로 고침",
"RegularExpressionsCanBeTested": "정규식을 테스트 할 수 있습니다. ",

View File

@@ -512,6 +512,7 @@
"RecycleBinCleanupDaysHelpTextWarning": "Bestanden in de prullenbak ouder dan het geselecteerde aantal dagen zullen automatisch opgeschoond worden",
"RecyclingBin": "Prullenbak",
"RecyclingBinCleanup": "Prullenbak Opruimen",
"Redownload": "Opnieuw downloaden",
"RefreshInformationAndScanDisk": "Informatie vernieuwen en schijf herscannen",
"RefreshMovie": "Film vernieuwen",
"ProxyType": "Proxy Type",

View File

@@ -272,6 +272,7 @@
"RadarrCalendarFeed": "Kanał kalendarza radarowego",
"Reason": "Powód",
"RecyclingBinCleanup": "Czyszczenie kosza na śmieci",
"Redownload": "Pobierz ponownie",
"Refresh": "Odświeżać",
"RefreshMovie": "Odśwież film",
"RegularExpressionsCanBeTested": "Można testować wyrażenia regularne ",

View File

@@ -533,6 +533,7 @@
"Remove": "Remover",
"RefreshMovie": "Atualizar filme",
"RefreshInformationAndScanDisk": "Atualizar informações e analisar o disco",
"Redownload": "Transferir novamente",
"RecyclingBinCleanup": "Limpeza da reciclagem",
"RecyclingBin": "Reciclagem",
"Reason": "Razão",

View File

@@ -546,6 +546,7 @@
"RetentionHelpText": "Somente Usenet: defina como zero para definir a retenção ilimitada",
"RestartReloadNote": "Observação: o Radarr reiniciará automaticamente e recarregará a interface durante o processo de restauração.",
"RejectionCount": "Número de rejeição",
"Redownload": "Baixar novamente",
"UpgradeAllowedHelpText": "Se desabilitada, as qualidades não serão atualizadas",
"UpgradesAllowed": "Atualizações Permitidas",
"UpgradeUntilCustomFormatScore": "Atualizar até pontuação de formato personalizado",
@@ -1260,7 +1261,7 @@
"True": "Verdadeiro",
"HealthMessagesInfoBox": "Você pode encontrar mais informações sobre a causa dessas mensagens de verificação de integridade clicando no link da wiki (ícone do livro) no final da linha ou verificando seus [logs]({link}). Se tiver dificuldade em interpretar essas mensagens, você pode entrar em contato com nosso suporte, nos links abaixo.",
"DefaultNameCopiedProfile": "{name} - Cópia",
"InvalidUILanguage": "Sua IU está configurada com um idioma inválido, corrija-a e salve suas configurações",
"InvalidUILanguage": "Sua IU está definida com um idioma inválido, corrija-a e salve suas configurações",
"AuthenticationMethod": "Método de Autenticação",
"AuthenticationMethodHelpTextWarning": "Selecione um método de autenticação válido",
"AuthenticationRequiredPasswordHelpTextWarning": "Insira uma nova senha",
@@ -1329,8 +1330,5 @@
"OrganizeRelativePaths": "Todos os caminhos são relativos a: `{path}`",
"OrganizeRenamingDisabled": "A renomeação está desativada, nada para renomear",
"DownloadClientRemovesCompletedDownloadsHealthCheckMessage": "O cliente de download {0} está configurado para remover downloads concluídos. Isso pode resultar na remoção dos downloads do seu cliente antes que {1} possa importá-los.",
"Umask": "Desmascarar",
"AutoRedownloadFailedFromInteractiveSearch": "Falha no Novo Download pela Pesquisa Interativa",
"AutoRedownloadFailedFromInteractiveSearchHelpText": "Procure e tente baixar automaticamente uma versão diferente quando a versão com falha for obtida pela pesquisa interativa",
"AutoRedownloadFailed": "Falha no Novo Download"
"Umask": "Desmascarar"
}

View File

@@ -533,6 +533,7 @@
"Local": "Local",
"MovieYear": "Anul filmului",
"RecyclingBinCleanup": "Curățarea coșului de reciclare",
"Redownload": "Redescărcați",
"RefreshMovie": "Reîmprospătați filmul",
"MovieYearHelpText": "Anul filmului de exclus",
"MustContain": "Trebuie sa contina",

View File

@@ -367,6 +367,7 @@
"RefreshInformationAndScanDisk": "Обновить информацию и просканировать диск",
"RefreshAndScan": "Обновить & сканировать",
"Refresh": "Обновить",
"Redownload": "Перезакачать",
"RecyclingBinCleanup": "Очистка мусорной корзины",
"RecyclingBin": "Мусорная корзина",
"RecycleBinHelpText": "Файлы фильмов будут попадать сюда при удалении",

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