mirror of
https://github.com/Readarr/Readarr.git
synced 2026-03-24 17:24:30 -04:00
Compare commits
72 Commits
v0.1.6.186
...
v0.2.2.194
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c3c9b9afbb | ||
|
|
f225a742cc | ||
|
|
f4fd36061c | ||
|
|
38e39449aa | ||
|
|
484c255fd4 | ||
|
|
f341b5f449 | ||
|
|
eb5654c634 | ||
|
|
e843046d76 | ||
|
|
ef57545221 | ||
|
|
09d44726a4 | ||
|
|
0e2d39f580 | ||
|
|
dbcb0e77a8 | ||
|
|
0186900a54 | ||
|
|
941b30edac | ||
|
|
5c61b6ceb3 | ||
|
|
55959e1112 | ||
|
|
07451cbcde | ||
|
|
1ebdffcd26 | ||
|
|
75119ce9df | ||
|
|
668dc6dfde | ||
|
|
ee989c9c67 | ||
|
|
acac3bd680 | ||
|
|
3b18f3206d | ||
|
|
fcf057a019 | ||
|
|
c7399cdd2b | ||
|
|
08a3682b89 | ||
|
|
3da00f75dc | ||
|
|
60abb298b2 | ||
|
|
c710b117ab | ||
|
|
816f53b36b | ||
|
|
749684e24a | ||
|
|
3a0ca45aa9 | ||
|
|
595efd498e | ||
|
|
dea1060d61 | ||
|
|
f6049b8bf2 | ||
|
|
53ced38221 | ||
|
|
3a3cf8511e | ||
|
|
9ec913337d | ||
|
|
9a2120ae92 | ||
|
|
818d3a94d5 | ||
|
|
4e493b74e6 | ||
|
|
c7eaf1e85c | ||
|
|
31fe15c911 | ||
|
|
2c36a6c25f | ||
|
|
6af56f7a15 | ||
|
|
6e13191c25 | ||
|
|
921ddfc962 | ||
|
|
22f977401a | ||
|
|
113d9a07ef | ||
|
|
0560d65ea1 | ||
|
|
94ff105104 | ||
|
|
9bcf258aa9 | ||
|
|
54985bd4ca | ||
|
|
9e4d551f08 | ||
|
|
8390da1c2a | ||
|
|
44ae043c58 | ||
|
|
bb7e2fc70c | ||
|
|
b05938a9a8 | ||
|
|
1e42ac572e | ||
|
|
649dd0bda0 | ||
|
|
de24aef059 | ||
|
|
10766dd227 | ||
|
|
257d279e43 | ||
|
|
1db333088a | ||
|
|
d1aff31593 | ||
|
|
89dd4d3271 | ||
|
|
fc6c78a54e | ||
|
|
c98f4512df | ||
|
|
0a43481aed | ||
|
|
df6c142250 | ||
|
|
58cf93e360 | ||
|
|
7be282ad12 |
@@ -36,12 +36,18 @@ dotnet_naming_style.instance_field_style.capitalization = camel_case
|
||||
dotnet_naming_style.instance_field_style.required_prefix = _
|
||||
|
||||
# Prefer "var" everywhere
|
||||
csharp_style_var_for_built_in_types = true:suggestion
|
||||
csharp_style_var_when_type_is_apparent = true:suggestion
|
||||
csharp_style_var_elsewhere = true:suggestion
|
||||
csharp_style_var_for_built_in_types = true
|
||||
csharp_style_var_when_type_is_apparent = true
|
||||
csharp_style_var_elsewhere = true
|
||||
# Prefer "out" variables to be declared inline
|
||||
csharp_style_inlined_variable_declaration = true
|
||||
|
||||
# Using directive is unnecessary.
|
||||
dotnet_diagnostic.IDE0005.severity = error
|
||||
# Use var instead of explicit type
|
||||
dotnet_diagnostic.IDE0007.severity = error
|
||||
# Inline variable declaration
|
||||
dotnet_diagnostic.IDE0018.severity = error
|
||||
|
||||
# Stylecop Rules
|
||||
dotnet_diagnostic.SA0001.severity = none
|
||||
|
||||
4
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
4
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
@@ -76,7 +76,7 @@ body:
|
||||
- type: checkboxes
|
||||
attributes:
|
||||
label: Trace Logs have been provided as applicable. Reports may be closed if the required logs are not provided.
|
||||
description: Trace logs are generally required for all bug reports
|
||||
description: Trace logs are generally required for all bug reports and contain `trace`. Info logs are invalid for bug reports and do not contain `debug` nor `trace`
|
||||
options:
|
||||
- label: I have followed the steps in the wiki link above and provided the required trace logs that are relevant and show this issue.
|
||||
- label: I have read and followed the steps in the wiki link above and provided the required trace logs - the logs contain `trace` - that are relevant and show this issue.
|
||||
required: true
|
||||
|
||||
@@ -9,7 +9,7 @@ variables:
|
||||
testsFolder: './_tests'
|
||||
yarnCacheFolder: $(Pipeline.Workspace)/.yarn
|
||||
nugetCacheFolder: $(Pipeline.Workspace)/.nuget/packages
|
||||
majorVersion: '0.1.6'
|
||||
majorVersion: '0.2.2'
|
||||
minorVersion: $[counter('minorVersion', 1)]
|
||||
readarrVersion: '$(majorVersion).$(minorVersion)'
|
||||
buildName: '$(Build.SourceBranchName).$(readarrVersion)'
|
||||
@@ -382,7 +382,7 @@ stages:
|
||||
- bash: |
|
||||
echo "Uploading source maps to sentry"
|
||||
curl -sL https://sentry.io/get-cli/ | bash
|
||||
RELEASENAME="${READARRVERSION}-${BUILD_SOURCEBRANCHNAME}"
|
||||
RELEASENAME="Readarr@${READARRVERSION}-${BUILD_SOURCEBRANCHNAME}"
|
||||
sentry-cli releases new --finalize -p readarr -p readarr-ui -p readarr-update "${RELEASENAME}"
|
||||
sentry-cli releases -p readarr-ui files "${RELEASENAME}" upload-sourcemaps _output/UI/ --rewrite
|
||||
sentry-cli releases set-commits --auto "${RELEASENAME}"
|
||||
@@ -984,7 +984,7 @@ stages:
|
||||
git status
|
||||
if git status | grep modified
|
||||
then
|
||||
git commit -am 'Automated API Docs update'
|
||||
git commit -am 'Automated API Docs update [skip ci]'
|
||||
git push -f --set-upstream origin api-docs
|
||||
curl -X POST -H "Authorization: token ${GITHUBTOKEN}" -H "Accept: application/vnd.github.v3+json" https://api.github.com/repos/readarr/readarr/pulls -d '{"head":"api-docs","base":"develop","title":"Update API docs"}'
|
||||
else
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import PropTypes from 'prop-types';
|
||||
import React, { Component } from 'react';
|
||||
import Alert from 'Components/Alert';
|
||||
import LoadingIndicator from 'Components/Loading/LoadingIndicator';
|
||||
import ConfirmModal from 'Components/Modal/ConfirmModal';
|
||||
import PageContent from 'Components/Page/PageContent';
|
||||
@@ -161,16 +162,16 @@ class Blocklist extends Component {
|
||||
|
||||
{
|
||||
!isAnyFetching && !!error &&
|
||||
<div>
|
||||
<Alert kind={kinds.DANGER}>
|
||||
{translate('UnableToLoadBlocklist')}
|
||||
</div>
|
||||
</Alert>
|
||||
}
|
||||
|
||||
{
|
||||
isAllPopulated && !error && !items.length &&
|
||||
<div>
|
||||
<Alert kind={kinds.INFO}>
|
||||
{translate('NoHistoryBlocklist')}
|
||||
</div>
|
||||
</Alert>
|
||||
}
|
||||
|
||||
{
|
||||
@@ -214,7 +215,7 @@ class Blocklist extends Component {
|
||||
isOpen={isConfirmRemoveModalOpen}
|
||||
kind={kinds.DANGER}
|
||||
title={translate('RemoveSelected')}
|
||||
message={translate('RemoveSelectedMessageText')}
|
||||
message={translate('RemoveSelectedItemBlocklistMessageText')}
|
||||
confirmLabel={translate('RemoveSelected')}
|
||||
onConfirm={this.onRemoveSelectedConfirmed}
|
||||
onCancel={this.onConfirmRemoveModalClose}
|
||||
|
||||
@@ -1,5 +1,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';
|
||||
@@ -11,7 +12,7 @@ import Table from 'Components/Table/Table';
|
||||
import TableBody from 'Components/Table/TableBody';
|
||||
import TableOptionsModalWrapper from 'Components/Table/TableOptions/TableOptionsModalWrapper';
|
||||
import TablePager from 'Components/Table/TablePager';
|
||||
import { align, icons } from 'Helpers/Props';
|
||||
import { align, icons, kinds } from 'Helpers/Props';
|
||||
import translate from 'Utilities/String/translate';
|
||||
import HistoryRowConnector from './HistoryRowConnector';
|
||||
|
||||
@@ -85,9 +86,9 @@ class History extends Component {
|
||||
|
||||
{
|
||||
!isFetchingAny && hasError &&
|
||||
<div>
|
||||
<Alert kind={kinds.DANGER}>
|
||||
{translate('UnableToLoadHistory')}
|
||||
</div>
|
||||
</Alert>
|
||||
}
|
||||
|
||||
{
|
||||
@@ -95,9 +96,9 @@ class History extends Component {
|
||||
// wait for the books to populate because they are never coming.
|
||||
|
||||
isPopulated && !hasError && !items.length &&
|
||||
<div>
|
||||
No history found
|
||||
</div>
|
||||
<Alert kind={kinds.INFO}>
|
||||
{translate('NoHistory')}
|
||||
</Alert>
|
||||
}
|
||||
|
||||
{
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import _ from 'lodash';
|
||||
import PropTypes from 'prop-types';
|
||||
import React, { Component } from 'react';
|
||||
import Alert from 'Components/Alert';
|
||||
import LoadingIndicator from 'Components/Loading/LoadingIndicator';
|
||||
import PageContent from 'Components/Page/PageContent';
|
||||
import PageContentBody from 'Components/Page/PageContentBody';
|
||||
@@ -12,7 +13,7 @@ import Table from 'Components/Table/Table';
|
||||
import TableBody from 'Components/Table/TableBody';
|
||||
import TableOptionsModalWrapper from 'Components/Table/TableOptions/TableOptionsModalWrapper';
|
||||
import TablePager from 'Components/Table/TablePager';
|
||||
import { align, icons } from 'Helpers/Props';
|
||||
import { align, icons, kinds } from 'Helpers/Props';
|
||||
import getRemovedItems from 'Utilities/Object/getRemovedItems';
|
||||
import hasDifferentItems from 'Utilities/Object/hasDifferentItems';
|
||||
import translate from 'Utilities/String/translate';
|
||||
@@ -233,17 +234,17 @@ class Queue extends Component {
|
||||
|
||||
{
|
||||
!isRefreshing && hasError ?
|
||||
<div>
|
||||
<Alert kind={kinds.DANGER}>
|
||||
{translate('FailedToLoadQueue')}
|
||||
</div> :
|
||||
</Alert> :
|
||||
null
|
||||
}
|
||||
|
||||
{
|
||||
isAllPopulated && !hasError && !items.length ?
|
||||
<div>
|
||||
<Alert kind={kinds.INFO}>
|
||||
{translate('QueueIsEmpty')}
|
||||
</div> :
|
||||
</Alert> :
|
||||
null
|
||||
}
|
||||
|
||||
|
||||
@@ -124,7 +124,7 @@ class RemoveQueueItemModal extends Component {
|
||||
type={inputTypes.CHECK}
|
||||
name="blocklist"
|
||||
value={blocklist}
|
||||
helpText={translate('BlocklistHelpText')}
|
||||
helpText={translate('BlocklistReleaseHelpText')}
|
||||
onChange={this.onBlocklistChange}
|
||||
/>
|
||||
</FormGroup>
|
||||
|
||||
@@ -89,12 +89,12 @@ class RemoveQueueItemsModal extends Component {
|
||||
onModalClose={this.onModalClose}
|
||||
>
|
||||
<ModalHeader>
|
||||
Remove Selected Item{selectedCount > 1 ? 's' : ''}
|
||||
{selectedCount > 1 ? translate('RemoveSelectedItems') : translate('RemoveSelectedItem')}
|
||||
</ModalHeader>
|
||||
|
||||
<ModalBody>
|
||||
<div className={styles.message}>
|
||||
Are you sure you want to remove {selectedCount} item{selectedCount > 1 ? 's' : ''} from the queue?
|
||||
{selectedCount > 1 ? translate('RemoveSelectedItemsQueueMessageText', selectedCount) : translate('RemoveSelectedItemQueueMessageText')}
|
||||
</div>
|
||||
|
||||
{
|
||||
@@ -118,14 +118,14 @@ class RemoveQueueItemsModal extends Component {
|
||||
|
||||
<FormGroup>
|
||||
<FormLabel>
|
||||
Add Release{selectedCount > 1 ? 's' : ''} To Blocklist
|
||||
{selectedCount > 1 ? translate('BlocklistReleases') : translate('BlocklistRelease')}
|
||||
</FormLabel>
|
||||
|
||||
<FormInputGroup
|
||||
type={inputTypes.CHECK}
|
||||
name="blocklist"
|
||||
value={blocklist}
|
||||
helpText={translate('BlocklistHelpText')}
|
||||
helpText={translate('BlocklistReleaseHelpText')}
|
||||
onChange={this.onBlocklistChange}
|
||||
/>
|
||||
</FormGroup>
|
||||
@@ -150,14 +150,14 @@ class RemoveQueueItemsModal extends Component {
|
||||
|
||||
<ModalFooter>
|
||||
<Button onPress={this.onModalClose}>
|
||||
Close
|
||||
{translate('Close')}
|
||||
</Button>
|
||||
|
||||
<Button
|
||||
kind={kinds.DANGER}
|
||||
onPress={this.onRemoveConfirmed}
|
||||
>
|
||||
Remove
|
||||
{translate('Remove')}
|
||||
</Button>
|
||||
</ModalFooter>
|
||||
</ModalContent>
|
||||
|
||||
@@ -92,6 +92,7 @@ class AuthorDetailsHeader extends Component {
|
||||
titleWidth
|
||||
} = this.state;
|
||||
|
||||
const fanartUrl = getFanartUrl(images);
|
||||
const marqueeWidth = titleWidth - (isSmallScreen ? 85 : 160);
|
||||
|
||||
const continuing = status === 'continuing';
|
||||
@@ -108,9 +109,11 @@ class AuthorDetailsHeader extends Component {
|
||||
<div className={styles.header} style={{ width }} >
|
||||
<div
|
||||
className={styles.backdrop}
|
||||
style={{
|
||||
backgroundImage: `url(${getFanartUrl(images)})`
|
||||
}}
|
||||
style={
|
||||
fanartUrl ?
|
||||
{ backgroundImage: `url(${fanartUrl})` } :
|
||||
null
|
||||
}
|
||||
>
|
||||
<div className={styles.backdropOverlay} />
|
||||
</div>
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
import PropTypes from 'prop-types';
|
||||
import React, { Component } from 'react';
|
||||
import Alert from 'Components/Alert';
|
||||
import LoadingIndicator from 'Components/Loading/LoadingIndicator';
|
||||
import Table from 'Components/Table/Table';
|
||||
import TableBody from 'Components/Table/TableBody';
|
||||
import { kinds } from 'Helpers/Props';
|
||||
import translate from 'Utilities/String/translate';
|
||||
import AuthorHistoryRowConnector from './AuthorHistoryRowConnector';
|
||||
|
||||
@@ -70,9 +72,9 @@ class AuthorHistoryTableContent extends Component {
|
||||
|
||||
{
|
||||
!isFetching && !!error &&
|
||||
<div>
|
||||
<Alert kind={kinds.DANGER}>
|
||||
{translate('UnableToLoadHistory')}
|
||||
</div>
|
||||
</Alert>
|
||||
}
|
||||
|
||||
{
|
||||
|
||||
@@ -83,15 +83,18 @@ class BookDetailsHeader extends Component {
|
||||
titleWidth
|
||||
} = this.state;
|
||||
|
||||
const fanartUrl = getFanartUrl(author.images);
|
||||
const marqueeWidth = titleWidth - (isSmallScreen ? 85 : 160);
|
||||
|
||||
return (
|
||||
<div className={styles.header} style={{ width }}>
|
||||
<div
|
||||
className={styles.backdrop}
|
||||
style={{
|
||||
backgroundImage: `url(${getFanartUrl(author.images)})`
|
||||
}}
|
||||
style={
|
||||
fanartUrl ?
|
||||
{ backgroundImage: `url(${fanartUrl})` } :
|
||||
null
|
||||
}
|
||||
>
|
||||
<div className={styles.backdropOverlay} />
|
||||
</div>
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
import PropTypes from 'prop-types';
|
||||
import React, { Component } from 'react';
|
||||
import Alert from 'Components/Alert';
|
||||
import LoadingIndicator from 'Components/Loading/LoadingIndicator';
|
||||
import { kinds } from 'Helpers/Props';
|
||||
import translate from 'Utilities/String/translate';
|
||||
import AgendaConnector from './Agenda/AgendaConnector';
|
||||
import * as calendarViews from './calendarViews';
|
||||
@@ -31,9 +33,9 @@ class Calendar extends Component {
|
||||
|
||||
{
|
||||
!isFetching && !!error &&
|
||||
<div>
|
||||
<Alert kind={kinds.DANGER}>
|
||||
{translate('UnableToLoadTheCalendar')}
|
||||
</div>
|
||||
</Alert>
|
||||
}
|
||||
|
||||
{
|
||||
|
||||
@@ -578,7 +578,7 @@ EnhancedSelectInput.propTypes = {
|
||||
className: PropTypes.string,
|
||||
disabledClassName: PropTypes.string,
|
||||
name: PropTypes.string.isRequired,
|
||||
value: PropTypes.oneOfType([PropTypes.string, PropTypes.number, PropTypes.arrayOf(PropTypes.number)]).isRequired,
|
||||
value: PropTypes.oneOfType([PropTypes.string, PropTypes.number, PropTypes.arrayOf(PropTypes.string), PropTypes.arrayOf(PropTypes.number)]).isRequired,
|
||||
values: PropTypes.arrayOf(PropTypes.object).isRequired,
|
||||
isDisabled: PropTypes.bool.isRequired,
|
||||
isFetching: PropTypes.bool.isRequired,
|
||||
|
||||
@@ -62,6 +62,7 @@ function ProviderFieldFormGroup(props) {
|
||||
name,
|
||||
label,
|
||||
helpText,
|
||||
helpTextWarning,
|
||||
helpLink,
|
||||
placeholder,
|
||||
value,
|
||||
@@ -95,6 +96,7 @@ function ProviderFieldFormGroup(props) {
|
||||
name={name}
|
||||
label={label}
|
||||
helpText={helpText}
|
||||
helpTextWarning={helpTextWarning}
|
||||
helpLink={helpLink}
|
||||
placeholder={placeholder}
|
||||
value={value}
|
||||
@@ -121,6 +123,7 @@ ProviderFieldFormGroup.propTypes = {
|
||||
name: PropTypes.string.isRequired,
|
||||
label: PropTypes.string.isRequired,
|
||||
helpText: PropTypes.string,
|
||||
helpTextWarning: PropTypes.string,
|
||||
helpLink: PropTypes.string,
|
||||
placeholder: PropTypes.string,
|
||||
value: PropTypes.any,
|
||||
|
||||
@@ -26,6 +26,7 @@ function createCleanAuthorSelector() {
|
||||
sortName,
|
||||
titleSlug,
|
||||
images,
|
||||
firstCharacter: authorName.charAt(0).toLowerCase(),
|
||||
tags: tags.reduce((acc, id) => {
|
||||
const matchingTag = allTags.find((tag) => tag.id === id);
|
||||
|
||||
@@ -58,6 +59,7 @@ function createCleanBookSelector() {
|
||||
sortName: title,
|
||||
titleSlug,
|
||||
images,
|
||||
firstCharacter: title.charAt(0).toLowerCase(),
|
||||
tags: []
|
||||
};
|
||||
});
|
||||
|
||||
@@ -15,9 +15,36 @@ const fuseOptions = {
|
||||
|
||||
function getSuggestions(items, value) {
|
||||
const limit = 10;
|
||||
let suggestions = [];
|
||||
|
||||
const fuse = new Fuse(items, fuseOptions);
|
||||
return fuse.search(value, { limit });
|
||||
if (value.length === 1) {
|
||||
for (let i = 0; i < items.length; i++) {
|
||||
const s = items[i];
|
||||
if (s.firstCharacter === value.toLowerCase()) {
|
||||
suggestions.push({
|
||||
item: items[i],
|
||||
indices: [
|
||||
[0, 0]
|
||||
],
|
||||
matches: [
|
||||
{
|
||||
value: s.title,
|
||||
key: 'title'
|
||||
}
|
||||
],
|
||||
arrayIndex: 0
|
||||
});
|
||||
if (suggestions.length > limit) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
const fuse = new Fuse(items, fuseOptions);
|
||||
suggestions = fuse.search(value, { limit });
|
||||
}
|
||||
|
||||
return suggestions;
|
||||
}
|
||||
|
||||
onmessage = function(e) {
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
import PropTypes from 'prop-types';
|
||||
import React from 'react';
|
||||
import Alert from 'Components/Alert';
|
||||
import LoadingIndicator from 'Components/Loading/LoadingIndicator';
|
||||
import { kinds } from 'Helpers/Props';
|
||||
|
||||
function PageSectionContent(props) {
|
||||
const {
|
||||
@@ -17,7 +19,7 @@ function PageSectionContent(props) {
|
||||
);
|
||||
} else if (!isFetching && !!error) {
|
||||
return (
|
||||
<div>{errorMessage}</div>
|
||||
<Alert kind={kinds.DANGER}>{errorMessage}</Alert>
|
||||
);
|
||||
} else if (isPopulated && !error) {
|
||||
return (
|
||||
|
||||
@@ -16,6 +16,38 @@
|
||||
box-shadow: inset 0 -1px 0 rgba(0, 0, 0, 0.15);
|
||||
color: var(--white);
|
||||
transition: width 0.6s ease;
|
||||
|
||||
&.primary {
|
||||
background-color: var(--primaryColor);
|
||||
}
|
||||
|
||||
&.danger {
|
||||
background-color: var(--dangerColor);
|
||||
|
||||
&:global(.colorImpaired) {
|
||||
background: repeating-linear-gradient(90deg, color(#f05050 shade(5%)), color(#f05050 shade(5%)) 5px, color(#f05050 shade(15%)) 5px, color(#f05050 shade(15%)) 10px);
|
||||
}
|
||||
}
|
||||
|
||||
&.success {
|
||||
background-color: var(--successColor);
|
||||
}
|
||||
|
||||
&.purple {
|
||||
background-color: var(--purple);
|
||||
}
|
||||
|
||||
&.warning {
|
||||
background-color: var(--warningColor);
|
||||
|
||||
&:global(.colorImpaired) {
|
||||
background: repeating-linear-gradient(45deg, #ffa500, #ffa500 5px, color(#ffa500 tint(15%)) 5px, color(#ffa500 tint(15%)) 10px);
|
||||
}
|
||||
}
|
||||
|
||||
&.info {
|
||||
background-color: var(--infoColor);
|
||||
}
|
||||
}
|
||||
|
||||
.frontTextContainer {
|
||||
@@ -45,38 +77,6 @@
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
.primary {
|
||||
background-color: var(--primaryColor);
|
||||
}
|
||||
|
||||
.danger {
|
||||
background-color: var(--dangerColor);
|
||||
|
||||
&:global(.colorImpaired) {
|
||||
background: repeating-linear-gradient(90deg, color(#f05050 shade(5%)), color(#f05050 shade(5%)) 5px, color(#f05050 shade(15%)) 5px, color(#f05050 shade(15%)) 10px);
|
||||
}
|
||||
}
|
||||
|
||||
.success {
|
||||
background-color: var(--successColor);
|
||||
}
|
||||
|
||||
.purple {
|
||||
background-color: var(--purple);
|
||||
}
|
||||
|
||||
.warning {
|
||||
background-color: var(--warningColor);
|
||||
|
||||
&:global(.colorImpaired) {
|
||||
background: repeating-linear-gradient(45deg, #ffa500, #ffa500 5px, color(#ffa500 tint(15%)) 5px, color(#ffa500 tint(15%)) 10px);
|
||||
}
|
||||
}
|
||||
|
||||
.info {
|
||||
background-color: var(--infoColor);
|
||||
}
|
||||
|
||||
.small {
|
||||
height: $progressBarSmallHeight;
|
||||
|
||||
|
||||
@@ -38,7 +38,7 @@ function ProgressBar(props) {
|
||||
{
|
||||
showText && width ?
|
||||
<div
|
||||
className={styles.backTextContainer}
|
||||
className={classNames(styles.backTextContainer, styles[kind])}
|
||||
style={{ width: actualWidth }}
|
||||
>
|
||||
<div className={styles.backText}>
|
||||
@@ -67,7 +67,7 @@ function ProgressBar(props) {
|
||||
{
|
||||
showText ?
|
||||
<div
|
||||
className={styles.frontTextContainer}
|
||||
className={classNames(styles.frontTextContainer, styles[kind])}
|
||||
style={{ width: progressPercent }}
|
||||
>
|
||||
<div
|
||||
|
||||
@@ -1,18 +1,19 @@
|
||||
{
|
||||
"name": "",
|
||||
"name": "Readarr",
|
||||
"icons": [
|
||||
{
|
||||
"src": "/Content/Images/Icons/android-chrome-192x192.png",
|
||||
"src": "android-chrome-192x192.png",
|
||||
"sizes": "192x192",
|
||||
"type": "image/png"
|
||||
},
|
||||
{
|
||||
"src": "/Content/Images/Icons/android-chrome-512x512.png",
|
||||
"src": "android-chrome-512x512.png",
|
||||
"sizes": "512x512",
|
||||
"type": "image/png"
|
||||
}
|
||||
],
|
||||
"start_url": "../../../../",
|
||||
"theme_color": "#3a3f51",
|
||||
"background_color": "#3a3f51",
|
||||
"display": "standalone"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import PropTypes from 'prop-types';
|
||||
import React, { Component } from 'react';
|
||||
import Alert from 'Components/Alert';
|
||||
import Form from 'Components/Form/Form';
|
||||
import FormGroup from 'Components/Form/FormGroup';
|
||||
import FormInputGroup from 'Components/Form/FormInputGroup';
|
||||
@@ -92,9 +93,9 @@ class SelectQualityModalContent extends Component {
|
||||
|
||||
{
|
||||
!isFetching && !!error &&
|
||||
<div>
|
||||
<Alert kind={kinds.DANGER}>
|
||||
{translate('UnableToLoadQualities')}
|
||||
</div>
|
||||
</Alert>
|
||||
}
|
||||
|
||||
{
|
||||
|
||||
@@ -7,3 +7,9 @@
|
||||
.filteredMessage {
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
.blankpad {
|
||||
padding-top: 10px;
|
||||
padding-bottom: 10px;
|
||||
padding-left: 2em;
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
// This file is automatically generated.
|
||||
// Please do not change this file!
|
||||
interface CssExports {
|
||||
'blankpad': string;
|
||||
'filterMenuContainer': string;
|
||||
'filteredMessage': string;
|
||||
}
|
||||
|
||||
@@ -104,7 +104,7 @@ function InteractiveSearch(props) {
|
||||
|
||||
{
|
||||
!isFetching && error ?
|
||||
<div>
|
||||
<div className={styles.blankpad}>
|
||||
Unable to load results for this book search. Try again later
|
||||
</div> :
|
||||
null
|
||||
@@ -112,7 +112,7 @@ function InteractiveSearch(props) {
|
||||
|
||||
{
|
||||
!isFetching && isPopulated && !totalReleasesCount ?
|
||||
<div>
|
||||
<div className={styles.blankpad}>
|
||||
No results found
|
||||
</div> :
|
||||
null
|
||||
@@ -120,7 +120,7 @@ function InteractiveSearch(props) {
|
||||
|
||||
{
|
||||
!!totalReleasesCount && isPopulated && !items.length ?
|
||||
<div>
|
||||
<div className={styles.blankpad}>
|
||||
All results are hidden by the applied filter
|
||||
</div> :
|
||||
null
|
||||
|
||||
@@ -78,7 +78,7 @@ class Specification extends Component {
|
||||
|
||||
<IconButton
|
||||
className={styles.cloneButton}
|
||||
title={translate('Clone')}
|
||||
title={translate('CloneCondition')}
|
||||
name={icons.CLONE}
|
||||
onPress={this.onCloneSpecificationPress}
|
||||
/>
|
||||
@@ -92,14 +92,14 @@ class Specification extends Component {
|
||||
{
|
||||
negate &&
|
||||
<Label kind={kinds.DANGER}>
|
||||
Negated
|
||||
{translate('Negated')}
|
||||
</Label>
|
||||
}
|
||||
|
||||
{
|
||||
required &&
|
||||
<Label kind={kinds.SUCCESS}>
|
||||
Required
|
||||
{translate('Required')}
|
||||
</Label>
|
||||
}
|
||||
</div>
|
||||
@@ -114,8 +114,8 @@ class Specification extends Component {
|
||||
<ConfirmModal
|
||||
isOpen={this.state.isDeleteSpecificationModalOpen}
|
||||
kind={kinds.DANGER}
|
||||
title={translate('DeleteFormat')}
|
||||
message={translate('DeleteFormatMessageText', [name])}
|
||||
title={translate('DeleteCondition')}
|
||||
message={translate('DeleteConditionMessageText', [name])}
|
||||
confirmLabel={translate('Delete')}
|
||||
onConfirm={this.onConfirmDeleteSpecification}
|
||||
onCancel={this.onDeleteSpecificationModalClose}
|
||||
|
||||
@@ -1,12 +1,13 @@
|
||||
import PropTypes from 'prop-types';
|
||||
import React from 'react';
|
||||
import Alert from 'Components/Alert';
|
||||
import FieldSet from 'Components/FieldSet';
|
||||
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 LoadingIndicator from 'Components/Loading/LoadingIndicator';
|
||||
import { inputTypes, sizes } from 'Helpers/Props';
|
||||
import { inputTypes, kinds, sizes } from 'Helpers/Props';
|
||||
import translate from 'Utilities/String/translate';
|
||||
|
||||
function DownloadClientOptions(props) {
|
||||
@@ -28,9 +29,9 @@ function DownloadClientOptions(props) {
|
||||
|
||||
{
|
||||
!isFetching && error &&
|
||||
<div>
|
||||
<Alert kind={kinds.DANGER}>
|
||||
{translate('UnableToLoadDownloadClientOptions')}
|
||||
</div>
|
||||
</Alert>
|
||||
}
|
||||
|
||||
{
|
||||
|
||||
@@ -88,8 +88,8 @@ class RemotePathMapping extends Component {
|
||||
<ConfirmModal
|
||||
isOpen={this.state.isDeleteRemotePathMappingModalOpen}
|
||||
kind={kinds.DANGER}
|
||||
title={translate('DeleteDelayProfile')}
|
||||
message={translate('DeleteDelayProfileMessageText')}
|
||||
title={translate('DeleteRemotePathMapping')}
|
||||
message={translate('DeleteRemotePathMappingMessageText')}
|
||||
confirmLabel={translate('Delete')}
|
||||
onConfirm={this.onConfirmDeleteRemotePathMapping}
|
||||
onCancel={this.onDeleteRemotePathMappingModalClose}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import _ from 'lodash';
|
||||
import PropTypes from 'prop-types';
|
||||
import React, { Component } from 'react';
|
||||
import Alert from 'Components/Alert';
|
||||
import Form from 'Components/Form/Form';
|
||||
import LoadingIndicator from 'Components/Loading/LoadingIndicator';
|
||||
import ConfirmModal from 'Components/Modal/ConfirmModal';
|
||||
@@ -124,9 +125,9 @@ class GeneralSettings extends Component {
|
||||
|
||||
{
|
||||
!isFetching && error &&
|
||||
<div>
|
||||
<Alert kind={kinds.DANGER}>
|
||||
{translate('UnableToLoadGeneralSettings')}
|
||||
</div>
|
||||
</Alert>
|
||||
}
|
||||
|
||||
{
|
||||
|
||||
@@ -1,12 +1,13 @@
|
||||
import PropTypes from 'prop-types';
|
||||
import React from 'react';
|
||||
import Alert from 'Components/Alert';
|
||||
import FieldSet from 'Components/FieldSet';
|
||||
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 LoadingIndicator from 'Components/Loading/LoadingIndicator';
|
||||
import { inputTypes } from 'Helpers/Props';
|
||||
import { inputTypes, kinds } from 'Helpers/Props';
|
||||
import translate from 'Utilities/String/translate';
|
||||
|
||||
function IndexerOptions(props) {
|
||||
@@ -28,9 +29,9 @@ function IndexerOptions(props) {
|
||||
|
||||
{
|
||||
!isFetching && error &&
|
||||
<div>
|
||||
<Alert kind={kinds.DANGER}>
|
||||
{translate('UnableToLoadIndexerOptions')}
|
||||
</div>
|
||||
</Alert>
|
||||
}
|
||||
|
||||
{
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import PropTypes from 'prop-types';
|
||||
import React, { Component } from 'react';
|
||||
import Alert from 'Components/Alert';
|
||||
import FieldSet from 'Components/FieldSet';
|
||||
import Form from 'Components/Form/Form';
|
||||
import FormGroup from 'Components/Form/FormGroup';
|
||||
@@ -8,7 +9,7 @@ import FormLabel from 'Components/Form/FormLabel';
|
||||
import LoadingIndicator from 'Components/Loading/LoadingIndicator';
|
||||
import PageContent from 'Components/Page/PageContent';
|
||||
import PageContentBody from 'Components/Page/PageContentBody';
|
||||
import { inputTypes, sizes } from 'Helpers/Props';
|
||||
import { inputTypes, kinds, sizes } from 'Helpers/Props';
|
||||
import RemotePathMappingsConnector from 'Settings/DownloadClients/RemotePathMappings/RemotePathMappingsConnector';
|
||||
import SettingsToolbarConnector from 'Settings/SettingsToolbarConnector';
|
||||
import translate from 'Utilities/String/translate';
|
||||
@@ -79,9 +80,9 @@ class MediaManagement extends Component {
|
||||
{
|
||||
!isFetching && error &&
|
||||
<FieldSet legend={translate('NamingSettings')}>
|
||||
<div>
|
||||
<Alert kind={kinds.DANGER}>
|
||||
{translate('UnableToLoadMediaManagementSettings')}
|
||||
</div>
|
||||
</Alert>
|
||||
</FieldSet>
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import PropTypes from 'prop-types';
|
||||
import React, { Component } from 'react';
|
||||
import Alert from 'Components/Alert';
|
||||
import FieldSet from 'Components/FieldSet';
|
||||
import Form from 'Components/Form/Form';
|
||||
import FormGroup from 'Components/Form/FormGroup';
|
||||
@@ -7,7 +8,7 @@ import FormInputButton from 'Components/Form/FormInputButton';
|
||||
import FormInputGroup from 'Components/Form/FormInputGroup';
|
||||
import FormLabel from 'Components/Form/FormLabel';
|
||||
import LoadingIndicator from 'Components/Loading/LoadingIndicator';
|
||||
import { inputTypes, sizes } from 'Helpers/Props';
|
||||
import { inputTypes, kinds, sizes } from 'Helpers/Props';
|
||||
import translate from 'Utilities/String/translate';
|
||||
import NamingModal from './NamingModal';
|
||||
import styles from './Naming.css';
|
||||
@@ -118,9 +119,9 @@ class Naming extends Component {
|
||||
|
||||
{
|
||||
!isFetching && error &&
|
||||
<div>
|
||||
<Alert kind={kinds.DANGER}>
|
||||
{translate('UnableToLoadNamingSettings')}
|
||||
</div>
|
||||
</Alert>
|
||||
}
|
||||
|
||||
{
|
||||
|
||||
@@ -1,12 +1,13 @@
|
||||
import PropTypes from 'prop-types';
|
||||
import React from 'react';
|
||||
import Alert from 'Components/Alert';
|
||||
import FieldSet from 'Components/FieldSet';
|
||||
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 LoadingIndicator from 'Components/Loading/LoadingIndicator';
|
||||
import { inputTypes } from 'Helpers/Props';
|
||||
import { inputTypes, kinds } from 'Helpers/Props';
|
||||
import translate from 'Utilities/String/translate';
|
||||
|
||||
const writeAudioTagOptions = [
|
||||
@@ -41,9 +42,9 @@ function MetadataProvider(props) {
|
||||
|
||||
{
|
||||
!isFetching && error &&
|
||||
<div>
|
||||
<Alert kind={kinds.DANGER}>
|
||||
{translate('UnableToLoadMetadataProviderSettings')}
|
||||
</div>
|
||||
</Alert>
|
||||
}
|
||||
|
||||
{
|
||||
|
||||
@@ -15,9 +15,6 @@ import { inputTypes, kinds } from 'Helpers/Props';
|
||||
import translate from 'Utilities/String/translate';
|
||||
import styles from './EditMetadataProfileModalContent.css';
|
||||
|
||||
// Tab, enter, and comma
|
||||
const tagInputDelimiters = [9, 13, 188];
|
||||
|
||||
function EditMetadataProfileModalContent(props) {
|
||||
const {
|
||||
isFetching,
|
||||
@@ -189,7 +186,7 @@ function EditMetadataProfileModalContent(props) {
|
||||
helpText={translate('IgnoredMetaHelpText')}
|
||||
kind={kinds.DANGER}
|
||||
placeholder={translate('IgnoredPlaceHolder')}
|
||||
delimiters={tagInputDelimiters}
|
||||
delimiters={['Tab', 'Enter', ',']}
|
||||
{...ignored}
|
||||
onChange={onInputChange}
|
||||
/>
|
||||
|
||||
@@ -23,9 +23,14 @@
|
||||
height: 36px;
|
||||
}
|
||||
|
||||
.bookTypes {
|
||||
.enabled {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
margin-top: 5px;
|
||||
pointer-events: all;
|
||||
}
|
||||
|
||||
.label {
|
||||
composes: label from '~Components/Label.css';
|
||||
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
// This file is automatically generated.
|
||||
// Please do not change this file!
|
||||
interface CssExports {
|
||||
'bookTypes': string;
|
||||
'cloneButton': string;
|
||||
'enabled': string;
|
||||
'label': string;
|
||||
'metadataProfile': string;
|
||||
'name': string;
|
||||
'nameContainer': string;
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
import PropTypes from 'prop-types';
|
||||
import React, { Component } from 'react';
|
||||
import MiddleTruncate from 'react-middle-truncate';
|
||||
import Card from 'Components/Card';
|
||||
import Label from 'Components/Label';
|
||||
import IconButton from 'Components/Link/IconButton';
|
||||
import ConfirmModal from 'Components/Modal/ConfirmModal';
|
||||
import { icons, kinds } from 'Helpers/Props';
|
||||
@@ -64,6 +66,9 @@ class MetadataProfile extends Component {
|
||||
const {
|
||||
id,
|
||||
name,
|
||||
minPopularity,
|
||||
minPages,
|
||||
ignored,
|
||||
isDeleting
|
||||
} = this.props;
|
||||
|
||||
@@ -86,6 +91,48 @@ class MetadataProfile extends Component {
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className={styles.enabled}>
|
||||
{
|
||||
minPopularity ?
|
||||
<Label kind={kinds.DEFAULT}>
|
||||
{translate('MinimumPopularity')}: {minPopularity}
|
||||
</Label> :
|
||||
null
|
||||
}
|
||||
|
||||
{
|
||||
minPages ?
|
||||
<Label kind={kinds.DEFAULT}>
|
||||
{translate('MinimumPages')}: {minPages}
|
||||
</Label> :
|
||||
null
|
||||
}
|
||||
</div>
|
||||
|
||||
<div>
|
||||
{
|
||||
ignored.map((item) => {
|
||||
if (!item) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<Label
|
||||
className={styles.label}
|
||||
key={item}
|
||||
kind={kinds.DANGER}
|
||||
>
|
||||
<MiddleTruncate
|
||||
text={item}
|
||||
start={10}
|
||||
end={10}
|
||||
/>
|
||||
</Label>
|
||||
);
|
||||
})
|
||||
}
|
||||
</div>
|
||||
|
||||
<EditMetadataProfileModalConnector
|
||||
id={id}
|
||||
isOpen={this.state.isEditMetadataProfileModalOpen}
|
||||
@@ -111,10 +158,19 @@ class MetadataProfile extends Component {
|
||||
MetadataProfile.propTypes = {
|
||||
id: PropTypes.number.isRequired,
|
||||
name: PropTypes.string.isRequired,
|
||||
minPopularity: PropTypes.number.isRequired,
|
||||
minPages: PropTypes.number.isRequired,
|
||||
ignored: PropTypes.arrayOf(PropTypes.string).isRequired,
|
||||
isDeleting: PropTypes.bool.isRequired,
|
||||
onConfirmDeleteMetadataProfile: PropTypes.func.isRequired,
|
||||
onCloneMetadataProfilePress: PropTypes.func.isRequired
|
||||
|
||||
};
|
||||
|
||||
MetadataProfile.defaultProps = {
|
||||
minPopularity: 0,
|
||||
minPages: 0,
|
||||
ignored: []
|
||||
};
|
||||
|
||||
export default MetadataProfile;
|
||||
|
||||
@@ -55,12 +55,12 @@ class ResetQualityDefinitionsModalContent extends Component {
|
||||
onModalClose={onModalClose}
|
||||
>
|
||||
<ModalHeader>
|
||||
Reset Quality Definitions
|
||||
{translate('ResetQualityDefinitions')}
|
||||
</ModalHeader>
|
||||
|
||||
<ModalBody>
|
||||
<div className={styles.messageContainer}>
|
||||
Are you sure you want to reset quality definitions?
|
||||
{translate('ResetQualityDefinitionsMessageText')}
|
||||
</div>
|
||||
|
||||
<FormGroup>
|
||||
@@ -81,7 +81,7 @@ class ResetQualityDefinitionsModalContent extends Component {
|
||||
|
||||
<ModalFooter>
|
||||
<Button onPress={onModalClose}>
|
||||
Cancel
|
||||
{translate('Cancel')}
|
||||
</Button>
|
||||
|
||||
<Button
|
||||
@@ -89,7 +89,7 @@ class ResetQualityDefinitionsModalContent extends Component {
|
||||
onPress={this.onResetQualityDefinitionsConfirmed}
|
||||
isDisabled={isResettingQualityDefinitions}
|
||||
>
|
||||
Reset
|
||||
{translate('Reset')}
|
||||
</Button>
|
||||
</ModalFooter>
|
||||
</ModalContent>
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
import PropTypes from 'prop-types';
|
||||
import React from 'react';
|
||||
import Alert from 'Components/Alert';
|
||||
import FieldSet from 'Components/FieldSet';
|
||||
import Link from 'Components/Link/Link';
|
||||
import PageSectionContent from 'Components/Page/PageSectionContent';
|
||||
import { kinds } from 'Helpers/Props';
|
||||
import translate from 'Utilities/String/translate';
|
||||
import TagConnector from './TagConnector';
|
||||
import styles from './Tags.css';
|
||||
@@ -16,9 +18,9 @@ function Tags(props) {
|
||||
if (!items.length) {
|
||||
const wikiLink = <Link to='https://wiki.servarr.com/readarr/settings#tags'>here</Link>;
|
||||
return (
|
||||
<div>
|
||||
<Alert kind={kinds.INFO}>
|
||||
{translate('NoTagsHaveBeenAddedYet', [wikiLink])}
|
||||
</div>
|
||||
</Alert>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import PropTypes from 'prop-types';
|
||||
import React, { Component } from 'react';
|
||||
import Alert from 'Components/Alert';
|
||||
import FieldSet from 'Components/FieldSet';
|
||||
import Form from 'Components/Form/Form';
|
||||
import FormGroup from 'Components/Form/FormGroup';
|
||||
@@ -8,7 +9,7 @@ import FormLabel from 'Components/Form/FormLabel';
|
||||
import LoadingIndicator from 'Components/Loading/LoadingIndicator';
|
||||
import PageContent from 'Components/Page/PageContent';
|
||||
import PageContentBody from 'Components/Page/PageContentBody';
|
||||
import { inputTypes } from 'Helpers/Props';
|
||||
import { inputTypes, kinds } from 'Helpers/Props';
|
||||
import SettingsToolbarConnector from 'Settings/SettingsToolbarConnector';
|
||||
import themes from 'Styles/Themes';
|
||||
import titleCase from 'Utilities/String/titleCase';
|
||||
@@ -81,9 +82,9 @@ class UISettings extends Component {
|
||||
|
||||
{
|
||||
!isFetching && error &&
|
||||
<div>
|
||||
<Alert kind={kinds.DANGER}>
|
||||
{translate('UnableToLoadUISettings')}
|
||||
</div>
|
||||
</Alert>
|
||||
}
|
||||
|
||||
{
|
||||
|
||||
@@ -32,9 +32,9 @@ function createSaveProviderHandler(section, url, options = {}, removeStale = fal
|
||||
const params = { ...queryParams };
|
||||
|
||||
// If the user is re-saving the same provider without changes
|
||||
// force it to be saved. Only applies to editing existing providers.
|
||||
// force it to be saved.
|
||||
|
||||
if (id && _.isEqual(saveData, lastSaveData)) {
|
||||
if (_.isEqual(saveData, lastSaveData)) {
|
||||
params.forceSave = true;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import PropTypes from 'prop-types';
|
||||
import React, { Component } from 'react';
|
||||
import Alert from 'Components/Alert';
|
||||
import LoadingIndicator from 'Components/Loading/LoadingIndicator';
|
||||
import PageContent from 'Components/Page/PageContent';
|
||||
import PageContentBody from 'Components/Page/PageContentBody';
|
||||
@@ -8,7 +9,7 @@ import PageToolbarButton from 'Components/Page/Toolbar/PageToolbarButton';
|
||||
import PageToolbarSection from 'Components/Page/Toolbar/PageToolbarSection';
|
||||
import Table from 'Components/Table/Table';
|
||||
import TableBody from 'Components/Table/TableBody';
|
||||
import { icons } from 'Helpers/Props';
|
||||
import { icons, kinds } from 'Helpers/Props';
|
||||
import translate from 'Utilities/String/translate';
|
||||
import BackupRow from './BackupRow';
|
||||
import RestoreBackupModalConnector from './RestoreBackupModalConnector';
|
||||
@@ -107,16 +108,16 @@ class Backups extends Component {
|
||||
|
||||
{
|
||||
!isFetching && !!error &&
|
||||
<div>
|
||||
<Alert kind={kinds.DANGER}>
|
||||
{translate('UnableToLoadBackups')}
|
||||
</div>
|
||||
</Alert>
|
||||
}
|
||||
|
||||
{
|
||||
noBackups &&
|
||||
<div>
|
||||
<Alert kind={kinds.INFO}>
|
||||
{translate('NoBackupsAreAvailable')}
|
||||
</div>
|
||||
</Alert>
|
||||
}
|
||||
|
||||
{
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import PropTypes from 'prop-types';
|
||||
import React 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';
|
||||
@@ -11,7 +12,7 @@ import Table from 'Components/Table/Table';
|
||||
import TableBody from 'Components/Table/TableBody';
|
||||
import TableOptionsModalWrapper from 'Components/Table/TableOptions/TableOptionsModalWrapper';
|
||||
import TablePager from 'Components/Table/TablePager';
|
||||
import { align, icons } from 'Helpers/Props';
|
||||
import { align, icons, kinds } from 'Helpers/Props';
|
||||
import translate from 'Utilities/String/translate';
|
||||
import LogsTableRow from './LogsTableRow';
|
||||
|
||||
@@ -82,9 +83,9 @@ function LogsTable(props) {
|
||||
|
||||
{
|
||||
isPopulated && !error && !items.length &&
|
||||
<div>
|
||||
No events found
|
||||
</div>
|
||||
<Alert kind={kinds.INFO}>
|
||||
{translate('NoEventsFound')}
|
||||
</Alert>
|
||||
}
|
||||
|
||||
{
|
||||
|
||||
@@ -11,7 +11,7 @@ import PageToolbarSection from 'Components/Page/Toolbar/PageToolbarSection';
|
||||
import PageToolbarSeparator from 'Components/Page/Toolbar/PageToolbarSeparator';
|
||||
import Table from 'Components/Table/Table';
|
||||
import TableBody from 'Components/Table/TableBody';
|
||||
import { icons } from 'Helpers/Props';
|
||||
import { icons, kinds } from 'Helpers/Props';
|
||||
import translate from 'Utilities/String/translate';
|
||||
import LogsNavMenu from '../LogsNavMenu';
|
||||
import LogFilesTableRow from './LogFilesTableRow';
|
||||
@@ -118,9 +118,9 @@ class LogFiles extends Component {
|
||||
|
||||
{
|
||||
!isFetching && !items.length &&
|
||||
<div>
|
||||
<Alert kind={kinds.INFO}>
|
||||
{translate('NoLogFiles')}
|
||||
</div>
|
||||
</Alert>
|
||||
}
|
||||
</PageContentBody>
|
||||
</PageContent>
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import _ from 'lodash';
|
||||
import PropTypes from 'prop-types';
|
||||
import React, { Component, Fragment } from 'react';
|
||||
import Alert from 'Components/Alert';
|
||||
import Icon from 'Components/Icon';
|
||||
import Label from 'Components/Label';
|
||||
import SpinnerButton from 'Components/Link/SpinnerButton';
|
||||
@@ -61,9 +62,9 @@ class Updates extends Component {
|
||||
|
||||
{
|
||||
noUpdates &&
|
||||
<div>
|
||||
<Alert kind={kinds.INFO}>
|
||||
{translate('NoUpdatesAreAvailable')}
|
||||
</div>
|
||||
</Alert>
|
||||
}
|
||||
|
||||
{
|
||||
|
||||
@@ -1,5 +1,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 ConfirmModal from 'Components/Modal/ConfirmModal';
|
||||
@@ -190,16 +191,16 @@ class CutoffUnmet extends Component {
|
||||
|
||||
{
|
||||
!isAnyFetching && error &&
|
||||
<div>
|
||||
<Alert kind={kinds.DANGER}>
|
||||
Error fetching cutoff unmet
|
||||
</div>
|
||||
</Alert>
|
||||
}
|
||||
|
||||
{
|
||||
isAllPopulated && !error && !items.length &&
|
||||
<div>
|
||||
No cutoff unmet items
|
||||
</div>
|
||||
<Alert kind={kinds.INFO}>
|
||||
{translate('NoCutoffUnmetItems')}
|
||||
</Alert>
|
||||
}
|
||||
|
||||
{
|
||||
|
||||
@@ -1,5 +1,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 ConfirmModal from 'Components/Modal/ConfirmModal';
|
||||
@@ -207,16 +208,16 @@ class Missing extends Component {
|
||||
|
||||
{
|
||||
!isAnyFetching && error &&
|
||||
<div>
|
||||
<Alert kind={kinds.DANGER}>
|
||||
Error fetching missing items
|
||||
</div>
|
||||
</Alert>
|
||||
}
|
||||
|
||||
{
|
||||
isAllPopulated && !error && !items.length &&
|
||||
<div>
|
||||
No missing items
|
||||
</div>
|
||||
<Alert kind={kinds.INFO}>
|
||||
{translate('NoMissingItems')}
|
||||
</Alert>
|
||||
}
|
||||
|
||||
{
|
||||
|
||||
@@ -134,7 +134,7 @@
|
||||
"run-sequence": "2.2.1",
|
||||
"streamqueue": "1.1.2",
|
||||
"style-loader": "3.3.2",
|
||||
"stylelint": "15.6.1",
|
||||
"stylelint": "15.10.1",
|
||||
"stylelint-order": "6.0.3",
|
||||
"terser-webpack-plugin": "5.3.8",
|
||||
"ts-loader": "9.4.3",
|
||||
|
||||
@@ -30,6 +30,13 @@
|
||||
<!-- A test project gets the test sdk packages automatically added -->
|
||||
<TestProject>false</TestProject>
|
||||
<TestProject Condition="$(MSBuildProjectName.EndsWith('.Test'))">true</TestProject>
|
||||
|
||||
<!-- XML documentation comments are needed to enforce rule IDE0005 on build -->
|
||||
<GenerateDocumentationFile>true</GenerateDocumentationFile>
|
||||
<!--
|
||||
CS1591: Missing XML comment for publicly visible type or member 'Type_or_Member'
|
||||
-->
|
||||
<NoWarn>$(NoWarn);CS1591</NoWarn>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup>
|
||||
|
||||
@@ -27,35 +27,36 @@ namespace NzbDrone.Api.Test.ClientSchemaTests
|
||||
|
||||
var schema = SchemaBuilder.ToSchema(model);
|
||||
|
||||
schema.Should().Contain(c =>
|
||||
c.Order == 1 && c.Name == "lastName" && c.Label == "Last Name" && c.HelpText == "Your Last Name" &&
|
||||
(string)c.Value == "Poop");
|
||||
schema.Should().Contain(c =>
|
||||
c.Order == 0 && c.Name == "firstName" && c.Label == "First Name" && c.HelpText == "Your First Name" &&
|
||||
(string)c.Value == "Bob");
|
||||
schema.Should().Contain(c => c.Order == 1 && c.Name == "lastName" && c.Label == "Last Name" && c.HelpText == "Your Last Name" && c.HelpTextWarning == "Mandatory Last Name" && (string)c.Value == "Poop");
|
||||
schema.Should().Contain(c => c.Order == 0 && c.Name == "firstName" && c.Label == "First Name" && c.HelpText == "Your First Name" && c.HelpTextWarning == "Mandatory First Name" && (string)c.Value == "Bob");
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void schema_should_have_nested_fields()
|
||||
{
|
||||
var model = new NestedTestModel();
|
||||
model.Name.FirstName = "Bob";
|
||||
model.Name.LastName = "Poop";
|
||||
var model = new NestedTestModel
|
||||
{
|
||||
Name =
|
||||
{
|
||||
FirstName = "Bob",
|
||||
LastName = "Poop"
|
||||
}
|
||||
};
|
||||
|
||||
var schema = SchemaBuilder.ToSchema(model);
|
||||
|
||||
schema.Should().Contain(c => c.Order == 0 && c.Name == "name.firstName" && c.Label == "First Name" && c.HelpText == "Your First Name" && (string)c.Value == "Bob");
|
||||
schema.Should().Contain(c => c.Order == 1 && c.Name == "name.lastName" && c.Label == "Last Name" && c.HelpText == "Your Last Name" && (string)c.Value == "Poop");
|
||||
schema.Should().Contain(c => c.Order == 0 && c.Name == "name.firstName" && c.Label == "First Name" && c.HelpText == "Your First Name" && c.HelpTextWarning == "Mandatory First Name" && (string)c.Value == "Bob");
|
||||
schema.Should().Contain(c => c.Order == 1 && c.Name == "name.lastName" && c.Label == "Last Name" && c.HelpText == "Your Last Name" && c.HelpTextWarning == "Mandatory Last Name" && (string)c.Value == "Poop");
|
||||
schema.Should().Contain(c => c.Order == 2 && c.Name == "quote" && c.Label == "Quote" && c.HelpText == "Your Favorite Quote");
|
||||
}
|
||||
}
|
||||
|
||||
public class TestModel
|
||||
{
|
||||
[FieldDefinition(0, Label = "First Name", HelpText = "Your First Name")]
|
||||
[FieldDefinition(0, Label = "First Name", HelpText = "Your First Name", HelpTextWarning = "Mandatory First Name")]
|
||||
public string FirstName { get; set; }
|
||||
|
||||
[FieldDefinition(1, Label = "Last Name", HelpText = "Your Last Name")]
|
||||
[FieldDefinition(1, Label = "Last Name", HelpText = "Your Last Name", HelpTextWarning = "Mandatory Last Name")]
|
||||
public string LastName { get; set; }
|
||||
|
||||
public string Other { get; set; }
|
||||
|
||||
@@ -68,7 +68,7 @@ namespace NzbDrone.Automation.Test
|
||||
{
|
||||
try
|
||||
{
|
||||
Screenshot image = ((ITakesScreenshot)driver).GetScreenshot();
|
||||
var image = ((ITakesScreenshot)driver).GetScreenshot();
|
||||
image.SaveAsFile($"./{name}_test_screenshot.png", ScreenshotImageFormat.Png);
|
||||
}
|
||||
catch (Exception ex)
|
||||
|
||||
@@ -37,7 +37,7 @@ namespace NzbDrone.Automation.Test.PageModel
|
||||
{
|
||||
try
|
||||
{
|
||||
IWebElement element = d.FindElement(By.ClassName("followingBalls"));
|
||||
var element = d.FindElement(By.ClassName("followingBalls"));
|
||||
return !element.Displayed;
|
||||
}
|
||||
catch (NoSuchElementException)
|
||||
|
||||
@@ -65,9 +65,9 @@ namespace NzbDrone.Common.Test.CacheTests
|
||||
[Test]
|
||||
public void should_store_null()
|
||||
{
|
||||
int hitCount = 0;
|
||||
var hitCount = 0;
|
||||
|
||||
for (int i = 0; i < 10; i++)
|
||||
for (var i = 0; i < 10; i++)
|
||||
{
|
||||
_cachedString.Get("key", () =>
|
||||
{
|
||||
@@ -83,10 +83,10 @@ namespace NzbDrone.Common.Test.CacheTests
|
||||
[Platform(Exclude = "MacOsX")]
|
||||
public void should_honor_ttl()
|
||||
{
|
||||
int hitCount = 0;
|
||||
var hitCount = 0;
|
||||
_cachedString = new Cached<string>();
|
||||
|
||||
for (int i = 0; i < 10; i++)
|
||||
for (var i = 0; i < 10; i++)
|
||||
{
|
||||
_cachedString.Get("key",
|
||||
() =>
|
||||
@@ -107,10 +107,10 @@ namespace NzbDrone.Common.Test.CacheTests
|
||||
[Platform(Exclude = "MacOsX")]
|
||||
public void should_clear_expired_when_they_expire()
|
||||
{
|
||||
int hitCount = 0;
|
||||
var hitCount = 0;
|
||||
_cachedString = new Cached<string>(rollingExpiry: true);
|
||||
|
||||
for (int i = 0; i < 10; i++)
|
||||
for (var i = 0; i < 10; i++)
|
||||
{
|
||||
_cachedString.Get("key",
|
||||
() =>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Generic;
|
||||
using FluentAssertions;
|
||||
using Moq;
|
||||
using NUnit.Framework;
|
||||
@@ -142,7 +142,7 @@ namespace NzbDrone.Common.Test
|
||||
[Test]
|
||||
public void SaveDictionary_should_save_proper_value()
|
||||
{
|
||||
int port = 20555;
|
||||
var port = 20555;
|
||||
|
||||
var dic = Subject.GetConfigDictionary();
|
||||
dic["Port"] = 20555;
|
||||
@@ -155,9 +155,9 @@ namespace NzbDrone.Common.Test
|
||||
[Test]
|
||||
public void SaveDictionary_should_only_save_specified_values()
|
||||
{
|
||||
int port = 20555;
|
||||
int origSslPort = 20551;
|
||||
int sslPort = 20552;
|
||||
var port = 20555;
|
||||
var origSslPort = 20551;
|
||||
var sslPort = 20552;
|
||||
|
||||
var dic = Subject.GetConfigDictionary();
|
||||
dic["Port"] = port;
|
||||
|
||||
@@ -43,7 +43,7 @@ namespace NzbDrone.Common.Test.DiskTests
|
||||
[Test]
|
||||
public void should_not_contain_recycling_bin_for_root_of_drive()
|
||||
{
|
||||
string root = @"C:\".AsOsAgnostic();
|
||||
var root = @"C:\".AsOsAgnostic();
|
||||
SetupFolders(root);
|
||||
|
||||
Mocker.GetMock<IDiskProvider>()
|
||||
@@ -56,7 +56,7 @@ namespace NzbDrone.Common.Test.DiskTests
|
||||
[Test]
|
||||
public void should_not_contain_system_volume_information()
|
||||
{
|
||||
string root = @"C:\".AsOsAgnostic();
|
||||
var root = @"C:\".AsOsAgnostic();
|
||||
SetupFolders(root);
|
||||
|
||||
Mocker.GetMock<IDiskProvider>()
|
||||
@@ -69,7 +69,7 @@ namespace NzbDrone.Common.Test.DiskTests
|
||||
[Test]
|
||||
public void should_not_contain_recycling_bin_or_system_volume_information_for_root_of_drive()
|
||||
{
|
||||
string root = @"C:\".AsOsAgnostic();
|
||||
var root = @"C:\".AsOsAgnostic();
|
||||
SetupFolders(root);
|
||||
|
||||
Mocker.GetMock<IDiskProvider>()
|
||||
|
||||
@@ -791,7 +791,7 @@ namespace NzbDrone.Common.Test.Http
|
||||
try
|
||||
{
|
||||
// the date is bad in the below - should be 13-Jul-2026
|
||||
string malformedCookie = @"__cfduid=d29e686a9d65800021c66faca0a29b4261436890790; expires=Mon, 13-Jul-26 16:19:50 GMT; path=/; HttpOnly";
|
||||
var malformedCookie = @"__cfduid=d29e686a9d65800021c66faca0a29b4261436890790; expires=Mon, 13-Jul-26 16:19:50 GMT; path=/; HttpOnly";
|
||||
var requestSet = new HttpRequestBuilder($"https://{_httpBinHost}/response-headers")
|
||||
.AddQueryParam("Set-Cookie", malformedCookie)
|
||||
.Build();
|
||||
@@ -825,7 +825,7 @@ namespace NzbDrone.Common.Test.Http
|
||||
{
|
||||
try
|
||||
{
|
||||
string url = $"https://{_httpBinHost}/response-headers?Set-Cookie={Uri.EscapeDataString(malformedCookie)}";
|
||||
var url = $"https://{_httpBinHost}/response-headers?Set-Cookie={Uri.EscapeDataString(malformedCookie)}";
|
||||
|
||||
var requestSet = new HttpRequest(url);
|
||||
requestSet.AllowAutoRedirect = false;
|
||||
|
||||
@@ -10,7 +10,9 @@ namespace NzbDrone.Common.Test.InstrumentationTests
|
||||
// Indexer Urls
|
||||
[TestCase(@"https://iptorrents.com/torrents/rss?u=mySecret;tp=mySecret;l5;download")]
|
||||
[TestCase(@"http://rss.torrentleech.org/mySecret")]
|
||||
[TestCase(@"http://rss.torrentleech.org/rss/download/12345/01233210/filename.torrent")]
|
||||
[TestCase(@"https://rss24h.torrentleech.org/mySecret")]
|
||||
[TestCase(@"http://rss.torrentleech.org/rss/download/12345/01233210/file.name-RLSGRP.torrent")]
|
||||
[TestCase(@"https://www.torrentleech.org/rss/download/12345/01233210/file.name-RLSGRP.torrent")]
|
||||
[TestCase(@"http://www.bitmetv.org/rss.php?uid=mySecret&passkey=mySecret")]
|
||||
[TestCase(@"https://rss.omgwtfnzbs.org/rss-search.php?catid=19,20&user=Readarr&api=mySecret&eng=1")]
|
||||
[TestCase(@"https://dognzb.cr/fetch/2b51db35e1912ffc138825a12b9933d2/2b51db35e1910123321025a12b9933d2")]
|
||||
@@ -44,6 +46,7 @@ namespace NzbDrone.Common.Test.InstrumentationTests
|
||||
// Deluge
|
||||
[TestCase(@",{""download_location"": ""C:\Users\\mySecret mySecret\\Downloads""}")]
|
||||
[TestCase(@",{""download_location"": ""/home/mySecret/Downloads""}")]
|
||||
[TestCase(@",{""download_location"": ""/Users/mySecret/Downloads""}")]
|
||||
[TestCase(@"auth.login(""mySecret"")")]
|
||||
|
||||
// Download Station
|
||||
@@ -56,8 +59,11 @@ namespace NzbDrone.Common.Test.InstrumentationTests
|
||||
|
||||
// Internal
|
||||
[TestCase(@"OutputPath=/home/mySecret/Downloads")]
|
||||
[TestCase(@"OutputPath=/Users/mySecret/Downloads")]
|
||||
[TestCase("Hardlinking episode file: /home/mySecret/Downloads to /media/abc.mkv")]
|
||||
[TestCase("Hardlinking episode file: /Users/mySecret/Downloads to /media/abc.mkv")]
|
||||
[TestCase("Hardlink '/home/mySecret/Downloads/abs.mkv' to '/media/abc.mkv' failed.")]
|
||||
[TestCase("Hardlink '/Users/mySecret/Downloads/abs.mkv' to '/media/abc.mkv' failed.")]
|
||||
[TestCase("https://notifiarr.com/notifier.php: api=1234530f-422f-4aac-b6b3-01233210aaaa&radarr_health_issue_message=Download")]
|
||||
[TestCase("/readarr/signalr/messages/negotiate?access_token=1234530f422f4aacb6b301233210aaaa&negotiateVersion=1")]
|
||||
[TestCase(@"[Info] MigrationController: *** Migrating Database=readarr-main;Host=postgres14;Username=mySecret;Password=mySecret;Port=5432;Enlist=False ***")]
|
||||
|
||||
@@ -74,17 +74,17 @@ namespace NzbDrone.Common
|
||||
continue; // Ignore directories
|
||||
}
|
||||
|
||||
string entryFileName = zipEntry.Name;
|
||||
var entryFileName = zipEntry.Name;
|
||||
|
||||
// to remove the folder from the entry:- entryFileName = Path.GetFileName(entryFileName);
|
||||
// Optionally match entrynames against a selection list here to skip as desired.
|
||||
// The unpacked length is available in the zipEntry.Size property.
|
||||
byte[] buffer = new byte[4096]; // 4K is optimum
|
||||
Stream zipStream = zipFile.GetInputStream(zipEntry);
|
||||
var buffer = new byte[4096]; // 4K is optimum
|
||||
var zipStream = zipFile.GetInputStream(zipEntry);
|
||||
|
||||
// Manipulate the output filename here as desired.
|
||||
string fullZipToPath = Path.Combine(destination, entryFileName);
|
||||
string directoryName = Path.GetDirectoryName(fullZipToPath);
|
||||
var fullZipToPath = Path.Combine(destination, entryFileName);
|
||||
var directoryName = Path.GetDirectoryName(fullZipToPath);
|
||||
if (directoryName.Length > 0)
|
||||
{
|
||||
Directory.CreateDirectory(directoryName);
|
||||
@@ -93,7 +93,7 @@ namespace NzbDrone.Common
|
||||
// Unzip file in buffered chunks. This is just as fast as unpacking to a buffer the full size
|
||||
// of the file, but does not waste memory.
|
||||
// The "using" will close the stream even if an exception occurs.
|
||||
using (FileStream streamWriter = File.Create(fullZipToPath))
|
||||
using (var streamWriter = File.Create(fullZipToPath))
|
||||
{
|
||||
StreamUtils.Copy(zipStream, streamWriter, buffer);
|
||||
}
|
||||
@@ -106,7 +106,7 @@ namespace NzbDrone.Common
|
||||
Stream inStream = File.OpenRead(compressedFile);
|
||||
Stream gzipStream = new GZipInputStream(inStream);
|
||||
|
||||
TarArchive tarArchive = TarArchive.CreateInputTarArchive(gzipStream, null);
|
||||
var tarArchive = TarArchive.CreateInputTarArchive(gzipStream, null);
|
||||
tarArchive.ExtractContents(destination);
|
||||
tarArchive.Close();
|
||||
|
||||
|
||||
@@ -54,8 +54,7 @@ namespace NzbDrone.Common.Cache
|
||||
|
||||
public T Find(string key)
|
||||
{
|
||||
CacheItem cacheItem;
|
||||
if (!_store.TryGetValue(key, out cacheItem))
|
||||
if (!_store.TryGetValue(key, out var cacheItem))
|
||||
{
|
||||
return default(T);
|
||||
}
|
||||
@@ -84,8 +83,7 @@ namespace NzbDrone.Common.Cache
|
||||
|
||||
public void Remove(string key)
|
||||
{
|
||||
CacheItem value;
|
||||
_store.TryRemove(key, out value);
|
||||
_store.TryRemove(key, out _);
|
||||
}
|
||||
|
||||
public int Count => _store.Count;
|
||||
@@ -96,9 +94,7 @@ namespace NzbDrone.Common.Cache
|
||||
|
||||
lifeTime = lifeTime ?? _defaultLifeTime;
|
||||
|
||||
CacheItem cacheItem;
|
||||
|
||||
if (_store.TryGetValue(key, out cacheItem) && !cacheItem.IsExpired())
|
||||
if (_store.TryGetValue(key, out var cacheItem) && !cacheItem.IsExpired())
|
||||
{
|
||||
if (_rollingExpiry && lifeTime.HasValue)
|
||||
{
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using System;
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
@@ -86,9 +86,7 @@ namespace NzbDrone.Common.Cache
|
||||
{
|
||||
RefreshIfExpired();
|
||||
|
||||
TValue result;
|
||||
|
||||
if (!_items.TryGetValue(key, out result))
|
||||
if (!_items.TryGetValue(key, out var result))
|
||||
{
|
||||
throw new KeyNotFoundException(string.Format("Item {0} not found in cache.", key));
|
||||
}
|
||||
@@ -100,9 +98,7 @@ namespace NzbDrone.Common.Cache
|
||||
{
|
||||
RefreshIfExpired();
|
||||
|
||||
TValue result;
|
||||
|
||||
_items.TryGetValue(key, out result);
|
||||
_items.TryGetValue(key, out var result);
|
||||
|
||||
return result;
|
||||
}
|
||||
@@ -128,8 +124,7 @@ namespace NzbDrone.Common.Cache
|
||||
|
||||
public void Remove(string key)
|
||||
{
|
||||
TValue item;
|
||||
_items.TryRemove(key, out item);
|
||||
_items.TryRemove(key, out _);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
namespace NzbDrone.Common
|
||||
namespace NzbDrone.Common
|
||||
{
|
||||
public static class ConvertBase32
|
||||
{
|
||||
@@ -6,17 +6,17 @@
|
||||
|
||||
public static byte[] FromBase32String(string str)
|
||||
{
|
||||
int numBytes = str.Length * 5 / 8;
|
||||
byte[] bytes = new byte[numBytes];
|
||||
var numBytes = str.Length * 5 / 8;
|
||||
var bytes = new byte[numBytes];
|
||||
|
||||
// all UPPERCASE chars
|
||||
str = str.ToUpper();
|
||||
|
||||
int bitBuffer = 0;
|
||||
int bitBufferCount = 0;
|
||||
int index = 0;
|
||||
var bitBuffer = 0;
|
||||
var bitBufferCount = 0;
|
||||
var index = 0;
|
||||
|
||||
for (int i = 0; i < str.Length; i++)
|
||||
for (var i = 0; i < str.Length; i++)
|
||||
{
|
||||
bitBuffer = (bitBuffer << 5) | ValidChars.IndexOf(str[i]);
|
||||
bitBufferCount += 5;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using System;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using NzbDrone.Common.Extensions;
|
||||
|
||||
@@ -255,7 +255,7 @@ namespace NzbDrone.Common.Disk
|
||||
|
||||
var stringComparison = (Kind == OsPathKind.Windows || other.Kind == OsPathKind.Windows) ? StringComparison.InvariantCultureIgnoreCase : StringComparison.InvariantCulture;
|
||||
|
||||
for (int i = 0; i < leftFragments.Length; i++)
|
||||
for (var i = 0; i < leftFragments.Length; i++)
|
||||
{
|
||||
if (!string.Equals(leftFragments[i], rightFragments[i], stringComparison))
|
||||
{
|
||||
@@ -372,12 +372,12 @@ namespace NzbDrone.Common.Disk
|
||||
|
||||
var newFragments = new List<string>();
|
||||
|
||||
for (int j = i; j < rightFragments.Length; j++)
|
||||
for (var j = i; j < rightFragments.Length; j++)
|
||||
{
|
||||
newFragments.Add("..");
|
||||
}
|
||||
|
||||
for (int j = i; j < leftFragments.Length; j++)
|
||||
for (var j = i; j < leftFragments.Length; j++)
|
||||
{
|
||||
newFragments.Add(leftFragments[j]);
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using System;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
@@ -107,7 +107,7 @@ namespace NzbDrone.Common.EnvironmentInfo
|
||||
|
||||
private static string RunAndCapture(string filename, string args)
|
||||
{
|
||||
Process p = new Process();
|
||||
var p = new Process();
|
||||
p.StartInfo.FileName = filename;
|
||||
p.StartInfo.Arguments = args;
|
||||
p.StartInfo.UseShellExecute = false;
|
||||
@@ -117,7 +117,7 @@ namespace NzbDrone.Common.EnvironmentInfo
|
||||
p.Start();
|
||||
|
||||
// To avoid deadlocks, always read the output stream first and then wait.
|
||||
string output = p.StandardOutput.ReadToEnd();
|
||||
var output = p.StandardOutput.ReadToEnd();
|
||||
p.WaitForExit(1000);
|
||||
|
||||
return output;
|
||||
|
||||
@@ -356,34 +356,34 @@ namespace NzbDrone.Common.Extensions
|
||||
}
|
||||
|
||||
/**
|
||||
* Computes the highest row in which the distance {@code p} appears
|
||||
* in diagonal {@code k} of the edit distance computation for
|
||||
* strings {@code a} and {@code b}. The diagonal number is
|
||||
* represented by the difference in the indices for the two strings;
|
||||
* it can range from {@code -b.length()} through {@code a.length()}.
|
||||
*
|
||||
* More precisely, this computes the highest value x such that
|
||||
* <pre>
|
||||
* p = edit-distance(a[0:(x+k)), b[0:x)).
|
||||
* </pre>
|
||||
*
|
||||
* This is the "f" function described by Ukkonen.
|
||||
*
|
||||
* The caller must assure that abs(k) ≤ p, the only values for
|
||||
* which this is well-defined.
|
||||
*
|
||||
* The implementation depends on the cached results of prior
|
||||
* computeRow calls for diagonals k-1, k, and k+1 for distance p-1.
|
||||
* These must be supplied in {@code knownLeft}, {@code knownAbove},
|
||||
* and {@code knownRight}, respectively.
|
||||
* @param k diagonal number
|
||||
* @param p edit distance
|
||||
* @param a one string to be compared
|
||||
* @param b other string to be compared
|
||||
* @param knownLeft value of {@code computeRow(k-1, p-1, ...)}
|
||||
* @param knownAbove value of {@code computeRow(k, p-1, ...)}
|
||||
* @param knownRight value of {@code computeRow(k+1, p-1, ...)}
|
||||
*/
|
||||
* Computes the highest row in which the distance {@code p} appears
|
||||
* in diagonal {@code k} of the edit distance computation for
|
||||
* strings {@code a} and {@code b}. The diagonal number is
|
||||
* represented by the difference in the indices for the two strings;
|
||||
* it can range from {@code -b.length()} through {@code a.length()}.
|
||||
*
|
||||
* More precisely, this computes the highest value x such that
|
||||
* <pre>
|
||||
* p = edit-distance(a[0:(x+k)), b[0:x)).
|
||||
* </pre>
|
||||
*
|
||||
* This is the "f" function described by Ukkonen.
|
||||
*
|
||||
* The caller must assure that abs(k) ≤ p, the only values for
|
||||
* which this is well-defined.
|
||||
*
|
||||
* The implementation depends on the cached results of prior
|
||||
* computeRow calls for diagonals k-1, k, and k+1 for distance p-1.
|
||||
* These must be supplied in {@code knownLeft}, {@code knownAbove},
|
||||
* and {@code knownRight}, respectively.
|
||||
* @param k diagonal number
|
||||
* @param p edit distance
|
||||
* @param a one string to be compared
|
||||
* @param b other string to be compared
|
||||
* @param knownLeft value of {@code computeRow(k-1, p-1, ...)}
|
||||
* @param knownAbove value of {@code computeRow(k, p-1, ...)}
|
||||
* @param knownRight value of {@code computeRow(k+1, p-1, ...)}
|
||||
*/
|
||||
private static int ComputeRow(int k,
|
||||
int p,
|
||||
char[] a,
|
||||
|
||||
@@ -136,7 +136,7 @@ namespace NzbDrone.Common.Extensions
|
||||
|
||||
for (var j = finish; j >= start; j--)
|
||||
{
|
||||
T charMatch = charMatches[j - 1];
|
||||
var charMatch = charMatches[j - 1];
|
||||
|
||||
if (d == 0)
|
||||
{
|
||||
@@ -181,7 +181,7 @@ namespace NzbDrone.Common.Extensions
|
||||
// match. But check anyway.
|
||||
var score = BitapScore(d, pattern);
|
||||
|
||||
bool isOnWordBoundary = true;
|
||||
var isOnWordBoundary = true;
|
||||
|
||||
if (wordDelimiters != null)
|
||||
{
|
||||
@@ -233,8 +233,8 @@ namespace NzbDrone.Common.Extensions
|
||||
return new List<char>();
|
||||
}
|
||||
|
||||
char curr = text[j - 1];
|
||||
bool take = true;
|
||||
var curr = text[j - 1];
|
||||
var take = true;
|
||||
|
||||
if (!s.TryGetValue(curr, out var charMatch))
|
||||
{
|
||||
|
||||
@@ -255,13 +255,13 @@ namespace NzbDrone.Common.Extensions
|
||||
var firstPath = paths.First();
|
||||
var length = firstPath.Length;
|
||||
|
||||
for (int i = 1; i < paths.Count; i++)
|
||||
for (var i = 1; i < paths.Count; i++)
|
||||
{
|
||||
var path = paths[i];
|
||||
|
||||
length = Math.Min(length, path.Length);
|
||||
|
||||
for (int characterIndex = 0; characterIndex < length; characterIndex++)
|
||||
for (var characterIndex = 0; characterIndex < length; characterIndex++)
|
||||
{
|
||||
if (path[characterIndex] != firstPath[characterIndex])
|
||||
{
|
||||
|
||||
@@ -188,11 +188,11 @@ namespace NzbDrone.Common.Extensions
|
||||
{
|
||||
double weightDenom = Math.Max(a.Length, b.Length);
|
||||
double sum = 0;
|
||||
for (int i = 0; i < a.Length; i++)
|
||||
for (var i = 0; i < a.Length; i++)
|
||||
{
|
||||
double high = 0.0;
|
||||
int indexDistance = 0;
|
||||
for (int x = 0; x < b.Length; x++)
|
||||
var high = 0.0;
|
||||
var indexDistance = 0;
|
||||
for (var x = 0; x < b.Length; x++)
|
||||
{
|
||||
var coef = LevenshteinCoefficient(a[i], b[x]);
|
||||
if (coef > high)
|
||||
|
||||
@@ -6,9 +6,7 @@ namespace NzbDrone.Common.Extensions
|
||||
{
|
||||
public static int? ParseInt32(this string source)
|
||||
{
|
||||
int result;
|
||||
|
||||
if (int.TryParse(source, out result))
|
||||
if (int.TryParse(source, out var result))
|
||||
{
|
||||
return result;
|
||||
}
|
||||
@@ -18,9 +16,7 @@ namespace NzbDrone.Common.Extensions
|
||||
|
||||
public static long? ParseInt64(this string source)
|
||||
{
|
||||
long result;
|
||||
|
||||
if (long.TryParse(source, out result))
|
||||
if (long.TryParse(source, out var result))
|
||||
{
|
||||
return result;
|
||||
}
|
||||
@@ -30,9 +26,7 @@ namespace NzbDrone.Common.Extensions
|
||||
|
||||
public static double? ParseDouble(this string source)
|
||||
{
|
||||
double result;
|
||||
|
||||
if (double.TryParse(source.Replace(',', '.'), NumberStyles.Number, CultureInfo.InvariantCulture, out result))
|
||||
if (double.TryParse(source.Replace(',', '.'), NumberStyles.Number, CultureInfo.InvariantCulture, out var result))
|
||||
{
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using System;
|
||||
using System;
|
||||
using System.Text;
|
||||
|
||||
namespace NzbDrone.Common
|
||||
@@ -7,9 +7,9 @@ namespace NzbDrone.Common
|
||||
{
|
||||
public static string CalculateCrc(string input)
|
||||
{
|
||||
uint mCrc = 0xffffffff;
|
||||
byte[] bytes = Encoding.UTF8.GetBytes(input);
|
||||
foreach (byte myByte in bytes)
|
||||
var mCrc = 0xffffffff;
|
||||
var bytes = Encoding.UTF8.GetBytes(input);
|
||||
foreach (var myByte in bytes)
|
||||
{
|
||||
mCrc ^= (uint)myByte << 24;
|
||||
for (var i = 0; i < 8; i++)
|
||||
|
||||
@@ -60,8 +60,7 @@ namespace NzbDrone.Common.Http
|
||||
|
||||
if (request.AllowAutoRedirect && response.HasHttpRedirect)
|
||||
{
|
||||
var autoRedirectChain = new List<string>();
|
||||
autoRedirectChain.Add(request.Url.ToString());
|
||||
var autoRedirectChain = new List<string> { request.Url.ToString() };
|
||||
|
||||
do
|
||||
{
|
||||
@@ -75,6 +74,14 @@ namespace NzbDrone.Common.Http
|
||||
throw new WebException($"Too many automatic redirections were attempted for {autoRedirectChain.Join(" -> ")}", WebExceptionStatus.ProtocolError);
|
||||
}
|
||||
|
||||
// 302 or 303 should default to GET on redirect even if POST on original
|
||||
if (RequestRequiresForceGet(response.StatusCode, response.Request.Method))
|
||||
{
|
||||
request.Method = HttpMethod.Get;
|
||||
request.ContentData = null;
|
||||
request.ContentSummary = null;
|
||||
}
|
||||
|
||||
response = ExecuteRequest(request, cookieContainer);
|
||||
}
|
||||
while (response.HasHttpRedirect);
|
||||
@@ -105,6 +112,16 @@ namespace NzbDrone.Common.Http
|
||||
return response;
|
||||
}
|
||||
|
||||
private static bool RequestRequiresForceGet(HttpStatusCode statusCode, HttpMethod requestMethod)
|
||||
{
|
||||
return statusCode switch
|
||||
{
|
||||
HttpStatusCode.Moved or HttpStatusCode.Found or HttpStatusCode.MultipleChoices => requestMethod == HttpMethod.Post,
|
||||
HttpStatusCode.SeeOther => requestMethod != HttpMethod.Get && requestMethod != HttpMethod.Head,
|
||||
_ => false,
|
||||
};
|
||||
}
|
||||
|
||||
private HttpResponse ExecuteRequest(HttpRequest request, CookieContainer cookieContainer)
|
||||
{
|
||||
foreach (var interceptor in _requestInterceptors)
|
||||
|
||||
@@ -50,6 +50,8 @@ namespace NzbDrone.Common.Http
|
||||
|
||||
public bool HasHttpError => (int)StatusCode >= 400;
|
||||
|
||||
public bool HasHttpServerError => (int)StatusCode >= 500;
|
||||
|
||||
public bool HasHttpRedirect => StatusCode == HttpStatusCode.Moved ||
|
||||
StatusCode == HttpStatusCode.MovedPermanently ||
|
||||
StatusCode == HttpStatusCode.Found ||
|
||||
|
||||
@@ -22,7 +22,7 @@ namespace NzbDrone.Common.Http
|
||||
|
||||
public HttpUri(string scheme, string host, int? port, string path, string query, string fragment)
|
||||
{
|
||||
StringBuilder builder = new StringBuilder();
|
||||
var builder = new StringBuilder();
|
||||
|
||||
if (scheme.IsNotNullOrWhiteSpace())
|
||||
{
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using NzbDrone.Common.Extensions;
|
||||
using NzbDrone.Common.Extensions;
|
||||
|
||||
namespace NzbDrone.Common.Http.Proxy
|
||||
{
|
||||
@@ -30,7 +30,7 @@ namespace NzbDrone.Common.Http.Proxy
|
||||
if (!string.IsNullOrWhiteSpace(BypassFilter))
|
||||
{
|
||||
var hostlist = BypassFilter.Split(',');
|
||||
for (int i = 0; i < hostlist.Length; i++)
|
||||
for (var i = 0; i < hostlist.Length; i++)
|
||||
{
|
||||
if (hostlist[i].StartsWith("*"))
|
||||
{
|
||||
|
||||
@@ -12,14 +12,12 @@ namespace NzbDrone.Common.Http
|
||||
if (response.Headers.ContainsKey("Retry-After"))
|
||||
{
|
||||
var retryAfter = response.Headers["Retry-After"].ToString();
|
||||
int seconds;
|
||||
DateTime date;
|
||||
|
||||
if (int.TryParse(retryAfter, out seconds))
|
||||
if (int.TryParse(retryAfter, out var seconds))
|
||||
{
|
||||
RetryAfter = TimeSpan.FromSeconds(seconds);
|
||||
}
|
||||
else if (DateTime.TryParse(retryAfter, out date))
|
||||
else if (DateTime.TryParse(retryAfter, out var date))
|
||||
{
|
||||
RetryAfter = date.ToUniversalTime() - DateTime.UtcNow;
|
||||
}
|
||||
|
||||
@@ -7,56 +7,56 @@ namespace NzbDrone.Common.Instrumentation
|
||||
{
|
||||
public class CleanseLogMessage
|
||||
{
|
||||
private static readonly Regex[] CleansingRules = new[]
|
||||
{
|
||||
// Url
|
||||
new Regex(@"(?<=\?|&|: )((?:api|auth|pass)?key|(?:access[-_]?)?token|auth|user|uid|api|[a-z_]*apikey|account|passwd)=(?<secret>[^&=""]+?)(?=[ ""&=]|$)", RegexOptions.Compiled | RegexOptions.IgnoreCase),
|
||||
new Regex(@"(?<=\?|&)[^=]*?(username|password)=(?<secret>[^&=]+?)(?= |&|$)", RegexOptions.Compiled | RegexOptions.IgnoreCase),
|
||||
new Regex(@"torrentleech\.org/(?!rss)(?<secret>[0-9a-z]+)", RegexOptions.Compiled | RegexOptions.IgnoreCase),
|
||||
new Regex(@"torrentleech\.org/rss/download/[0-9]+/(?<secret>[0-9a-z]+)", RegexOptions.Compiled | RegexOptions.IgnoreCase),
|
||||
new Regex(@"iptorrents\.com/[/a-z0-9?&;]*?(?:[?&;](u|tp)=(?<secret>[^&=;]+?))+(?= |;|&|$)", RegexOptions.Compiled | RegexOptions.IgnoreCase),
|
||||
new Regex(@"/fetch/[a-z0-9]{32}/(?<secret>[a-z0-9]{32})", RegexOptions.Compiled),
|
||||
new Regex(@"getnzb.*?(?<=\?|&)(r)=(?<secret>[^&=]+?)(?= |&|$)", RegexOptions.Compiled | RegexOptions.IgnoreCase),
|
||||
new Regex(@"\b(\w*)?(_?(?<!use|get_)token|username|passwo?rd)=(?<secret>[^&=]+?)(?= |&|$|;)", RegexOptions.Compiled | RegexOptions.IgnoreCase),
|
||||
private static readonly Regex[] CleansingRules =
|
||||
{
|
||||
// Url
|
||||
new (@"(?<=\?|&|: )((?:api|auth|pass)?key|(?:access[-_]?)?token|auth|user|uid|api|[a-z_]*apikey|account|passwd)=(?<secret>[^&=""]+?)(?=[ ""&=]|$)", RegexOptions.Compiled | RegexOptions.IgnoreCase),
|
||||
new (@"(?<=\?|&)[^=]*?(username|password)=(?<secret>[^&=]+?)(?= |&|$)", RegexOptions.Compiled | RegexOptions.IgnoreCase),
|
||||
new (@"rss(24h)?\.torrentleech\.org/(?!rss)(?<secret>[0-9a-z]+)", RegexOptions.Compiled | RegexOptions.IgnoreCase),
|
||||
new (@"torrentleech\.org/rss/download/[0-9]+/(?<secret>[0-9a-z]+)", RegexOptions.Compiled | RegexOptions.IgnoreCase),
|
||||
new (@"iptorrents\.com/[/a-z0-9?&;]*?(?:[?&;](u|tp)=(?<secret>[^&=;]+?))+(?= |;|&|$)", RegexOptions.Compiled | RegexOptions.IgnoreCase),
|
||||
new (@"/fetch/[a-z0-9]{32}/(?<secret>[a-z0-9]{32})", RegexOptions.Compiled),
|
||||
new (@"getnzb.*?(?<=\?|&)(r)=(?<secret>[^&=]+?)(?= |&|$)", RegexOptions.Compiled | RegexOptions.IgnoreCase),
|
||||
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 Regex(@"announce(\.php)?(/|%2f|%3fpasskey%3d)(?<secret>[a-z0-9]{16,})|(?<secret>[a-z0-9]{16,})(/|%2f)announce"),
|
||||
// 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"),
|
||||
|
||||
// Path
|
||||
new Regex(@"C:\\Users\\(?<secret>[^\""]+?)(\\|$)", RegexOptions.Compiled | RegexOptions.IgnoreCase),
|
||||
new Regex(@"/home/(?<secret>[^/""]+?)(/|$)", RegexOptions.Compiled | RegexOptions.IgnoreCase),
|
||||
// Path
|
||||
new (@"C:\\Users\\(?<secret>[^\""]+?)(\\|$)", RegexOptions.Compiled | RegexOptions.IgnoreCase),
|
||||
new (@"/(home|Users)/(?<secret>[^/""]+?)(/|$)", RegexOptions.Compiled | RegexOptions.IgnoreCase),
|
||||
|
||||
// NzbGet
|
||||
new Regex(@"""Name""\s*:\s*""[^""]*(username|password)""\s*,\s*""Value""\s*:\s*""(?<secret>[^""]+?)""", RegexOptions.Compiled | RegexOptions.IgnoreCase),
|
||||
// NzbGet
|
||||
new (@"""Name""\s*:\s*""[^""]*(username|password)""\s*,\s*""Value""\s*:\s*""(?<secret>[^""]+?)""", RegexOptions.Compiled | RegexOptions.IgnoreCase),
|
||||
|
||||
// Sabnzbd
|
||||
new Regex(@"""[^""]*(username|password|api_?key|nzb_key)""\s*:\s*""(?<secret>[^""]+?)""", RegexOptions.Compiled | RegexOptions.IgnoreCase),
|
||||
new Regex(@"""email_(account|to|from|pwd)""\s*:\s*""(?<secret>[^""]+?)""", RegexOptions.Compiled | RegexOptions.IgnoreCase),
|
||||
// Sabnzbd
|
||||
new (@"""[^""]*(username|password|api_?key|nzb_key)""\s*:\s*""(?<secret>[^""]+?)""", RegexOptions.Compiled | RegexOptions.IgnoreCase),
|
||||
new (@"""email_(account|to|from|pwd)""\s*:\s*""(?<secret>[^""]+?)""", RegexOptions.Compiled | RegexOptions.IgnoreCase),
|
||||
|
||||
// uTorrent
|
||||
new Regex(@"\[""[a-z._]*(username|password)"",\d,""(?<secret>[^""]+?)""", RegexOptions.Compiled | RegexOptions.IgnoreCase),
|
||||
new Regex(@"\[""(boss_key|boss_key_salt|proxy\.proxy)"",\d,""(?<secret>[^""]+?)""", RegexOptions.Compiled | RegexOptions.IgnoreCase),
|
||||
// uTorrent
|
||||
new (@"\[""[a-z._]*(username|password)"",\d,""(?<secret>[^""]+?)""", RegexOptions.Compiled | RegexOptions.IgnoreCase),
|
||||
new (@"\[""(boss_key|boss_key_salt|proxy\.proxy)"",\d,""(?<secret>[^""]+?)""", RegexOptions.Compiled | RegexOptions.IgnoreCase),
|
||||
|
||||
// Deluge
|
||||
new Regex(@"auth.login\(""(?<secret>[^""]+?)""", RegexOptions.Compiled | RegexOptions.IgnoreCase),
|
||||
// Deluge
|
||||
new (@"auth.login\(""(?<secret>[^""]+?)""", RegexOptions.Compiled | RegexOptions.IgnoreCase),
|
||||
|
||||
// BroadcastheNet
|
||||
new Regex(@"""?method""?\s*:\s*""(getTorrents)"",\s*""?params""?\s*:\s*\[\s*""(?<secret>[^""]+?)""", RegexOptions.Compiled | RegexOptions.IgnoreCase),
|
||||
new Regex(@"getTorrents\(""(?<secret>[^""]+?)""", RegexOptions.Compiled | RegexOptions.IgnoreCase),
|
||||
new Regex(@"(?<=\?|&)(authkey|torrent_pass)=(?<secret>[^&=]+?)(?=""|&|$)", RegexOptions.Compiled | RegexOptions.IgnoreCase),
|
||||
// BroadcastheNet
|
||||
new (@"""?method""?\s*:\s*""(getTorrents)"",\s*""?params""?\s*:\s*\[\s*""(?<secret>[^""]+?)""", RegexOptions.Compiled | RegexOptions.IgnoreCase),
|
||||
new (@"getTorrents\(""(?<secret>[^""]+?)""", RegexOptions.Compiled | RegexOptions.IgnoreCase),
|
||||
new (@"(?<=\?|&)(authkey|torrent_pass)=(?<secret>[^&=]+?)(?=""|&|$)", RegexOptions.Compiled | RegexOptions.IgnoreCase),
|
||||
|
||||
// Good Reads
|
||||
new Regex(@"(?<=""(token|tokensecret)"":\s)""(?<secret>[^""]+?)""", RegexOptions.Compiled | RegexOptions.IgnoreCase),
|
||||
// Good Reads
|
||||
new (@"(?<=""(token|tokensecret)"":\s)""(?<secret>[^""]+?)""", RegexOptions.Compiled | RegexOptions.IgnoreCase),
|
||||
|
||||
// Webhooks
|
||||
// Notifiarr
|
||||
new Regex(@"api/v[0-9]/notification/readarr/(?<secret>[\w-]+)", RegexOptions.Compiled | RegexOptions.IgnoreCase),
|
||||
// Webhooks
|
||||
// Notifiarr
|
||||
new (@"api/v[0-9]/notification/readarr/(?<secret>[\w-]+)", RegexOptions.Compiled | RegexOptions.IgnoreCase),
|
||||
|
||||
// Discord
|
||||
new Regex(@"discord.com/api/webhooks/((?<secret>[\w-]+)/)?(?<secret>[\w-]+)", RegexOptions.Compiled | RegexOptions.IgnoreCase)
|
||||
};
|
||||
// Discord
|
||||
new (@"discord.com/api/webhooks/((?<secret>[\w-]+)/)?(?<secret>[\w-]+)", RegexOptions.Compiled | RegexOptions.IgnoreCase)
|
||||
};
|
||||
|
||||
private static readonly Regex CleanseRemoteIPRegex = new Regex(@"(?:Auth-\w+(?<!Failure|Unauthorized) ip|from) (\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})", RegexOptions.Compiled);
|
||||
private static readonly Regex CleanseRemoteIPRegex = new (@"(?:Auth-\w+(?<!Failure|Unauthorized) ip|from) (\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})", RegexOptions.Compiled);
|
||||
|
||||
public static string Cleanse(string message)
|
||||
{
|
||||
@@ -68,15 +68,15 @@ namespace NzbDrone.Common.Instrumentation
|
||||
foreach (var regex in CleansingRules)
|
||||
{
|
||||
message = regex.Replace(message, m =>
|
||||
{
|
||||
var value = m.Value;
|
||||
foreach (var capture in m.Groups["secret"].Captures.OfType<Capture>().Reverse())
|
||||
{
|
||||
var value = m.Value;
|
||||
foreach (var capture in m.Groups["secret"].Captures.OfType<Capture>().Reverse())
|
||||
{
|
||||
value = value.Replace(capture.Index - m.Index, capture.Length, "(removed)");
|
||||
}
|
||||
value = value.Replace(capture.Index - m.Index, capture.Length, "(removed)");
|
||||
}
|
||||
|
||||
return value;
|
||||
});
|
||||
return value;
|
||||
});
|
||||
}
|
||||
|
||||
message = CleanseRemoteIPRegex.Replace(message, CleanseRemoteIP);
|
||||
@@ -87,7 +87,6 @@ namespace NzbDrone.Common.Instrumentation
|
||||
private static string CleanseRemoteIP(Match match)
|
||||
{
|
||||
var group = match.Groups[1];
|
||||
var valueAll = match.Value;
|
||||
var valueIP = group.Value;
|
||||
|
||||
if (IPAddress.TryParse(valueIP, out var address) && !address.IsLocalAddress())
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using Newtonsoft.Json.Linq;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using NzbDrone.Common.Serializer;
|
||||
|
||||
namespace NzbDrone.Common.Instrumentation
|
||||
@@ -16,7 +16,7 @@ namespace NzbDrone.Common.Instrumentation
|
||||
}
|
||||
}
|
||||
|
||||
foreach (JToken token in json)
|
||||
foreach (var token in json)
|
||||
{
|
||||
Visit(token);
|
||||
}
|
||||
|
||||
@@ -94,7 +94,7 @@ namespace NzbDrone.Common.Instrumentation
|
||||
|
||||
private static void RegisterDebugger()
|
||||
{
|
||||
DebuggerTarget target = new DebuggerTarget();
|
||||
var target = new DebuggerTarget();
|
||||
target.Name = "debuggerLogger";
|
||||
target.Layout = "[${level}] [${threadid}] ${logger}: ${message} ${onexception:inner=${newline}${newline}[v${assembly-version}] ${exception:format=ToString}${newline}${exception:format=Data}${newline}}";
|
||||
|
||||
@@ -107,9 +107,10 @@ namespace NzbDrone.Common.Instrumentation
|
||||
{
|
||||
LogManager.Setup().LoadConfiguration(c =>
|
||||
{
|
||||
c.ForLogger("System.*").WriteToNil(LogLevel.Warn);
|
||||
c.ForLogger("Microsoft.*").WriteToNil(LogLevel.Warn);
|
||||
c.ForLogger("Microsoft.Hosting.Lifetime*").WriteToNil(LogLevel.Info);
|
||||
c.ForLogger("System*").WriteToNil(LogLevel.Warn);
|
||||
c.ForLogger("Microsoft*").WriteToNil(LogLevel.Warn);
|
||||
c.ForLogger("Microsoft.AspNetCore.Diagnostics.ExceptionHandlerMiddleware").WriteToNil(LogLevel.Fatal);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -100,7 +100,7 @@ namespace NzbDrone.Common.Instrumentation.Sentry
|
||||
o.Dsn = dsn;
|
||||
o.AttachStacktrace = true;
|
||||
o.MaxBreadcrumbs = 200;
|
||||
o.Release = BuildInfo.Release;
|
||||
o.Release = $"{BuildInfo.AppName}@{BuildInfo.Release}";
|
||||
o.BeforeSend = x => SentryCleanser.CleanseEvent(x);
|
||||
o.BeforeBreadcrumb = x => SentryCleanser.CleanseBreadcrumb(x);
|
||||
o.Environment = BuildInfo.Branch;
|
||||
|
||||
@@ -29,7 +29,7 @@ namespace NzbDrone.Common.OAuth
|
||||
public virtual string Version { get; set; }
|
||||
public virtual string SessionHandle { get; set; }
|
||||
|
||||
/// <seealso cref="http://oauth.net/core/1.0#request_urls"/>
|
||||
/// <seealso href="http://oauth.net/core/1.0#request_urls"/>
|
||||
public virtual string RequestUrl { get; set; }
|
||||
public virtual Dictionary<string, string> Parameters { get; set; }
|
||||
|
||||
|
||||
@@ -71,6 +71,11 @@ namespace NzbDrone.Common.Serializer
|
||||
return JsonConvert.SerializeObject(obj, SerializerSettings);
|
||||
}
|
||||
|
||||
public static string ToJson(this object obj, Formatting formatting)
|
||||
{
|
||||
return JsonConvert.SerializeObject(obj, formatting, SerializerSettings);
|
||||
}
|
||||
|
||||
public static void Serialize<TModel>(TModel model, TextWriter outputStream)
|
||||
{
|
||||
var jsonTextWriter = new JsonTextWriter(outputStream);
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using Newtonsoft.Json.Linq;
|
||||
using Newtonsoft.Json.Linq;
|
||||
|
||||
namespace NzbDrone.Common.Serializer
|
||||
{
|
||||
@@ -60,7 +60,7 @@ namespace NzbDrone.Common.Serializer
|
||||
|
||||
public virtual void Visit(JArray json)
|
||||
{
|
||||
foreach (JToken token in json)
|
||||
foreach (var token in json)
|
||||
{
|
||||
Visit(token);
|
||||
}
|
||||
@@ -72,7 +72,7 @@ namespace NzbDrone.Common.Serializer
|
||||
|
||||
public virtual void Visit(JObject json)
|
||||
{
|
||||
foreach (JProperty property in json.Properties())
|
||||
foreach (var property in json.Properties())
|
||||
{
|
||||
Visit(property);
|
||||
}
|
||||
|
||||
@@ -42,7 +42,7 @@ namespace NzbDrone.Common.Serializer
|
||||
var enumText = value.ToString();
|
||||
var builder = new StringBuilder(enumText.Length + 4);
|
||||
builder.Append(char.ToLower(enumText[0]));
|
||||
for (int i = 1; i < enumText.Length; i++)
|
||||
for (var i = 1; i < enumText.Length; i++)
|
||||
{
|
||||
if (char.IsUpper(enumText[i]))
|
||||
{
|
||||
|
||||
@@ -18,7 +18,7 @@ namespace NzbDrone.Common.Serializer
|
||||
{
|
||||
try
|
||||
{
|
||||
Version v = new Version(reader.GetString());
|
||||
var v = new Version(reader.GetString());
|
||||
return v;
|
||||
}
|
||||
catch (Exception)
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using System;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
@@ -137,7 +137,7 @@ namespace NzbDrone.Common.TPL
|
||||
/// <returns>An enumerable of the tasks currently scheduled.</returns>
|
||||
protected sealed override IEnumerable<Task> GetScheduledTasks()
|
||||
{
|
||||
bool lockTaken = false;
|
||||
var lockTaken = false;
|
||||
try
|
||||
{
|
||||
Monitor.TryEnter(_tasks, ref lockTaken);
|
||||
|
||||
@@ -110,7 +110,7 @@ namespace NzbDrone.Console
|
||||
}
|
||||
|
||||
System.Console.WriteLine("Non-recoverable failure, waiting for user intervention...");
|
||||
for (int i = 0; i < 3600; i++)
|
||||
for (var i = 0; i < 3600; i++)
|
||||
{
|
||||
System.Threading.Thread.Sleep(1000);
|
||||
|
||||
|
||||
@@ -197,7 +197,7 @@ namespace NzbDrone.Core.Test.Datastore
|
||||
|
||||
Subject.SetFields(_basicList, x => x.Interval);
|
||||
|
||||
for (int i = 0; i < _basicList.Count; i++)
|
||||
for (var i = 0; i < _basicList.Count; i++)
|
||||
{
|
||||
_basicList[i].LastExecution = executionBackup[i];
|
||||
}
|
||||
|
||||
@@ -87,7 +87,7 @@ namespace NzbDrone.Core.Test.DecisionEngineTests
|
||||
[Test]
|
||||
public void should_return_true_if_cutoffs_are_met_but_is_a_revision_upgrade()
|
||||
{
|
||||
QualityProfile profile = new QualityProfile
|
||||
var profile = new QualityProfile
|
||||
{
|
||||
Cutoff = Quality.MP3.Id,
|
||||
Items = Qualities.QualityFixture.GetDefaultQualities(),
|
||||
@@ -104,7 +104,7 @@ namespace NzbDrone.Core.Test.DecisionEngineTests
|
||||
[Test]
|
||||
public void should_return_false_if_quality_profile_does_not_allow_upgrades_but_cutoff_is_set_to_highest_quality()
|
||||
{
|
||||
QualityProfile profile = new QualityProfile
|
||||
var profile = new QualityProfile
|
||||
{
|
||||
Cutoff = Quality.FLAC.Id,
|
||||
Items = Qualities.QualityFixture.GetDefaultQualities(),
|
||||
|
||||
@@ -83,7 +83,7 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.Blackhole
|
||||
VerifySingleItem(DownloadItemStatus.Downloading);
|
||||
|
||||
// If we keep changing the file every 20ms we should stay Downloading.
|
||||
for (int i = 0; i < 10; i++)
|
||||
for (var i = 0; i < 10; i++)
|
||||
{
|
||||
TestLogger.Info("Iteration {0}", i);
|
||||
|
||||
|
||||
@@ -225,6 +225,7 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.QBittorrentTests
|
||||
[TestCase("checkingDL")]
|
||||
[TestCase("checkingUP")]
|
||||
[TestCase("metaDL")]
|
||||
[TestCase("checkingResumeData")]
|
||||
public void queued_item_should_have_required_properties(string state)
|
||||
{
|
||||
var torrent = new QBittorrentTorrent
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using System;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
using FluentAssertions;
|
||||
@@ -57,7 +57,7 @@ namespace NzbDrone.Core.Test
|
||||
[Test]
|
||||
public void ToBestDateTime_DayOfWeek()
|
||||
{
|
||||
for (int i = 2; i < 7; i++)
|
||||
for (var i = 2; i < 7; i++)
|
||||
{
|
||||
var dateTime = DateTime.Today.AddDays(i);
|
||||
|
||||
|
||||
@@ -79,7 +79,7 @@ namespace NzbDrone.Core.Test.HealthCheck.Checks
|
||||
[Test]
|
||||
public void should_return_ok_if_not_downloading_to_root_folder()
|
||||
{
|
||||
string rootFolderPath = "c:\\Test2".AsOsAgnostic();
|
||||
var rootFolderPath = "c:\\Test2".AsOsAgnostic();
|
||||
|
||||
GivenRootFolder(rootFolderPath);
|
||||
|
||||
|
||||
@@ -48,7 +48,7 @@ namespace NzbDrone.Core.Test.Instrumentation
|
||||
public void write_long_log()
|
||||
{
|
||||
var message = string.Empty;
|
||||
for (int i = 0; i < 100; i++)
|
||||
for (var i = 0; i < 100; i++)
|
||||
{
|
||||
message += Guid.NewGuid();
|
||||
}
|
||||
|
||||
@@ -100,7 +100,7 @@ namespace NzbDrone.Core.Test.MediaFiles.BookImport.Aggregation.Aggregators
|
||||
{
|
||||
get
|
||||
{
|
||||
int i = 0;
|
||||
var i = 0;
|
||||
|
||||
foreach (var tokens in tokenList)
|
||||
{
|
||||
@@ -128,7 +128,7 @@ namespace NzbDrone.Core.Test.MediaFiles.BookImport.Aggregation.Aggregators
|
||||
private List<string> GivenFilenames(string[] fields, string fieldSeparator, string whitespace)
|
||||
{
|
||||
var outp = new List<string>();
|
||||
for (int i = 1; i <= 3; i++)
|
||||
for (var i = 1; i <= 3; i++)
|
||||
{
|
||||
var components = new List<string>();
|
||||
foreach (var field in fields)
|
||||
@@ -161,7 +161,7 @@ namespace NzbDrone.Core.Test.MediaFiles.BookImport.Aggregation.Aggregators
|
||||
|
||||
private void VerifyDataAuto(List<LocalBook> tracks, string[] tokens, string whitespace)
|
||||
{
|
||||
for (int i = 1; i <= tracks.Count; i++)
|
||||
for (var i = 1; i <= tracks.Count; i++)
|
||||
{
|
||||
var info = tracks[i - 1].FileTrackInfo;
|
||||
|
||||
|
||||
@@ -71,7 +71,7 @@ namespace NzbDrone.Core.Test.MediaFiles.BookImport.Identification
|
||||
Mocker.SetConstant<ICandidateService>(Mocker.Resolve<CandidateService>());
|
||||
|
||||
// set up the augmenters
|
||||
List<IAggregate<LocalEdition>> aggregators = new List<IAggregate<LocalEdition>>
|
||||
var aggregators = new List<IAggregate<LocalEdition>>
|
||||
{
|
||||
Mocker.Resolve<AggregateFilenameInfo>()
|
||||
};
|
||||
@@ -89,7 +89,7 @@ namespace NzbDrone.Core.Test.MediaFiles.BookImport.Identification
|
||||
private List<Author> GivenAuthors(List<AuthorTestCase> authors)
|
||||
{
|
||||
var outp = new List<Author>();
|
||||
for (int i = 0; i < authors.Count; i++)
|
||||
for (var i = 0; i < authors.Count; i++)
|
||||
{
|
||||
var meta = authors[i].MetadataProfile;
|
||||
meta.Id = i + 1;
|
||||
|
||||
@@ -30,17 +30,17 @@ namespace NzbDrone.Core.Test.MediaFiles.BookImport.Identification
|
||||
static RandomValueNamerShortStrings()
|
||||
{
|
||||
AllowedChars = new List<char>();
|
||||
for (char c = 'a'; c < 'z'; c++)
|
||||
for (var c = 'a'; c < 'z'; c++)
|
||||
{
|
||||
AllowedChars.Add(c);
|
||||
}
|
||||
|
||||
for (char c = 'A'; c < 'Z'; c++)
|
||||
for (var c = 'A'; c < 'Z'; c++)
|
||||
{
|
||||
AllowedChars.Add(c);
|
||||
}
|
||||
|
||||
for (char c = '0'; c < '9'; c++)
|
||||
for (var c = '0'; c < '9'; c++)
|
||||
{
|
||||
AllowedChars.Add(c);
|
||||
}
|
||||
@@ -48,17 +48,17 @@ namespace NzbDrone.Core.Test.MediaFiles.BookImport.Identification
|
||||
|
||||
protected override string GetString(MemberInfo memberInfo)
|
||||
{
|
||||
int length = _generator.Next(1, 100);
|
||||
var length = _generator.Next(1, 100);
|
||||
|
||||
char[] chars = new char[length];
|
||||
var chars = new char[length];
|
||||
|
||||
for (int i = 0; i < length; i++)
|
||||
for (var i = 0; i < length; i++)
|
||||
{
|
||||
int index = _generator.Next(0, AllowedChars.Count - 1);
|
||||
var index = _generator.Next(0, AllowedChars.Count - 1);
|
||||
chars[i] = AllowedChars[index];
|
||||
}
|
||||
|
||||
byte[] bytes = Encoding.UTF8.GetBytes(chars);
|
||||
var bytes = Encoding.UTF8.GetBytes(chars);
|
||||
return Encoding.UTF8.GetString(bytes, 0, bytes.Length);
|
||||
}
|
||||
}
|
||||
@@ -90,7 +90,7 @@ namespace NzbDrone.Core.Test.MediaFiles.BookImport.Identification
|
||||
{
|
||||
var outp = new List<LocalBook>();
|
||||
|
||||
for (int i = 0; i < count; i++)
|
||||
for (var i = 0; i < count; i++)
|
||||
{
|
||||
var track = Builder<LocalBook>
|
||||
.CreateNew()
|
||||
@@ -283,7 +283,7 @@ namespace NzbDrone.Core.Test.MediaFiles.BookImport.Identification
|
||||
public void should_separate_many_books_in_same_directory()
|
||||
{
|
||||
var tracks = new List<LocalBook>();
|
||||
for (int i = 0; i < 100; i++)
|
||||
for (var i = 0; i < 100; i++)
|
||||
{
|
||||
tracks.AddRange(GivenTracks($"C:\\music".AsOsAgnostic(), "author" + i, "book" + i, 10));
|
||||
}
|
||||
|
||||
@@ -14,8 +14,8 @@ namespace NzbDrone.Core.Test.MetadataSource.Goodreads.Resources
|
||||
[Test]
|
||||
public void parse_non_work()
|
||||
{
|
||||
XElement element = new XElement("Dummy", "entry");
|
||||
WorkResource work = new WorkResource();
|
||||
var element = new XElement("Dummy", "entry");
|
||||
var work = new WorkResource();
|
||||
|
||||
Assert.Throws<NullReferenceException>(() => work.Parse(element));
|
||||
|
||||
@@ -27,11 +27,11 @@ namespace NzbDrone.Core.Test.MetadataSource.Goodreads.Resources
|
||||
[Test]
|
||||
public void parse_minimal_work()
|
||||
{
|
||||
XElement element = new XElement("work",
|
||||
var element = new XElement("work",
|
||||
new XElement("original_title", "Book Title"),
|
||||
new XElement("id", "123456789"));
|
||||
|
||||
WorkResource work = new WorkResource();
|
||||
var work = new WorkResource();
|
||||
|
||||
work.Parse(element);
|
||||
|
||||
@@ -44,12 +44,12 @@ namespace NzbDrone.Core.Test.MetadataSource.Goodreads.Resources
|
||||
[Test]
|
||||
public void parse_minimal_work_with_surrounding_tags()
|
||||
{
|
||||
XElement element = new XElement("series_works",
|
||||
var element = new XElement("series_works",
|
||||
new XElement("work",
|
||||
new XElement("original_title", "Book Title"),
|
||||
new XElement("id", "123456789")));
|
||||
|
||||
WorkResource work = new WorkResource();
|
||||
var work = new WorkResource();
|
||||
|
||||
work.Parse(element);
|
||||
|
||||
|
||||
@@ -115,7 +115,7 @@ namespace NzbDrone.Core.Test.MusicTests.AuthorRepositoryTests
|
||||
{
|
||||
GivenAuthors();
|
||||
|
||||
string name = "Alice Cooper";
|
||||
var name = "Alice Cooper";
|
||||
AddAuthor(name, "ee58c59f-8e7f-4430-b8ca-236c4d3745ae");
|
||||
AddAuthor(name, "4d7928cd-7ed2-4282-8c29-c0c9f966f1bd");
|
||||
|
||||
|
||||
@@ -39,13 +39,13 @@ namespace NzbDrone.Core.Test.ParserTests
|
||||
[Test]
|
||||
public void should_not_parse_md5()
|
||||
{
|
||||
string hash = "CRAPPY TEST SEED";
|
||||
var hash = "CRAPPY TEST SEED";
|
||||
|
||||
var hashAlgo = System.Security.Cryptography.MD5.Create();
|
||||
|
||||
var repetitions = 100;
|
||||
var success = 0;
|
||||
for (int i = 0; i < repetitions; i++)
|
||||
for (var i = 0; i < repetitions; i++)
|
||||
{
|
||||
var hashData = hashAlgo.ComputeHash(System.Text.Encoding.Default.GetBytes(hash));
|
||||
|
||||
@@ -64,17 +64,17 @@ namespace NzbDrone.Core.Test.ParserTests
|
||||
[TestCase(40)]
|
||||
public void should_not_parse_random(int length)
|
||||
{
|
||||
string charset = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
|
||||
var charset = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
|
||||
|
||||
var hashAlgo = new Random();
|
||||
|
||||
var repetitions = 500;
|
||||
var success = 0;
|
||||
for (int i = 0; i < repetitions; i++)
|
||||
for (var i = 0; i < repetitions; i++)
|
||||
{
|
||||
StringBuilder hash = new StringBuilder(length);
|
||||
var hash = new StringBuilder(length);
|
||||
|
||||
for (int x = 0; x < length; x++)
|
||||
for (var x = 0; x < length; x++)
|
||||
{
|
||||
hash.Append(charset[hashAlgo.Next() % charset.Length]);
|
||||
}
|
||||
|
||||
@@ -67,7 +67,7 @@ namespace NzbDrone.Core.Test.Profiles.Delay
|
||||
var moving = _last;
|
||||
var result = Subject.Reorder(moving.Id, null).OrderBy(d => d.Order).ToList();
|
||||
|
||||
for (int i = 1; i < result.Count; i++)
|
||||
for (var i = 1; i < result.Count; i++)
|
||||
{
|
||||
var delayProfile = result[i];
|
||||
|
||||
|
||||
@@ -15,6 +15,7 @@ namespace NzbDrone.Core.Annotations
|
||||
public string Label { get; set; }
|
||||
public string Unit { get; set; }
|
||||
public string HelpText { get; set; }
|
||||
public string HelpTextWarning { get; set; }
|
||||
public string HelpLink { get; set; }
|
||||
public FieldType Type { get; set; }
|
||||
public bool Advanced { get; set; }
|
||||
|
||||
@@ -32,7 +32,7 @@ namespace NzbDrone.Core.Books
|
||||
var existingMetadata = FindById(data.Select(x => x.ForeignAuthorId).ToList());
|
||||
var updateMetadataList = new List<AuthorMetadata>();
|
||||
var addMetadataList = new List<AuthorMetadata>();
|
||||
int upToDateMetadataCount = 0;
|
||||
var upToDateMetadataCount = 0;
|
||||
|
||||
foreach (var meta in data)
|
||||
{
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user