mirror of
https://github.com/Readarr/Readarr.git
synced 2026-04-18 21:34:28 -04:00
Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 1c0943449b |
@@ -5,9 +5,9 @@ body:
|
|||||||
- type: checkboxes
|
- type: checkboxes
|
||||||
attributes:
|
attributes:
|
||||||
label: Is there an existing issue for this?
|
label: Is there an existing issue for this?
|
||||||
description: Please search to see if an open or closed issue already exists for the bug you encountered. If a bug exists and is closed note that it may only be fixed in an unstable branch.
|
description: Please search to see if an issue already exists for the bug you encountered.
|
||||||
options:
|
options:
|
||||||
- label: I have searched the existing open and closed issues
|
- label: I have searched the existing issues
|
||||||
required: true
|
required: true
|
||||||
- type: textarea
|
- type: textarea
|
||||||
attributes:
|
attributes:
|
||||||
@@ -42,14 +42,12 @@ body:
|
|||||||
- **Docker Install**: Yes
|
- **Docker Install**: Yes
|
||||||
- **Using Reverse Proxy**: No
|
- **Using Reverse Proxy**: No
|
||||||
- **Browser**: Firefox 90 (If UI related)
|
- **Browser**: Firefox 90 (If UI related)
|
||||||
- **Database**: Sqlite 3.36.0
|
|
||||||
value: |
|
value: |
|
||||||
- OS:
|
- OS:
|
||||||
- Readarr:
|
- Readarr:
|
||||||
- Docker Install:
|
- Docker Install:
|
||||||
- Using Reverse Proxy:
|
- Using Reverse Proxy:
|
||||||
- Browser:
|
- Browser:
|
||||||
- Database:
|
|
||||||
render: markdown
|
render: markdown
|
||||||
validations:
|
validations:
|
||||||
required: true
|
required: true
|
||||||
|
|||||||
@@ -5,9 +5,9 @@ body:
|
|||||||
- type: checkboxes
|
- type: checkboxes
|
||||||
attributes:
|
attributes:
|
||||||
label: Is there an existing issue for this?
|
label: Is there an existing issue for this?
|
||||||
description: Please search to see if an open or closed issue already exists for the feature you are requesting. If a request exists and is closed note that it may only be fixed in an unstable branch.
|
description: Please search to see if an issue already exists for the feature you are requesting.
|
||||||
options:
|
options:
|
||||||
- label: I have searched the existing open and closed issues
|
- label: I have searched the existing issues
|
||||||
required: true
|
required: true
|
||||||
- type: textarea
|
- type: textarea
|
||||||
attributes:
|
attributes:
|
||||||
|
|||||||
@@ -1,132 +0,0 @@
|
|||||||
# Contributor Covenant Code of Conduct
|
|
||||||
|
|
||||||
## Our Pledge
|
|
||||||
|
|
||||||
We as members, contributors, and leaders pledge to make participation in our
|
|
||||||
community a harassment-free experience for everyone, regardless of age, body
|
|
||||||
size, visible or invisible disability, ethnicity, sex characteristics, gender
|
|
||||||
identity and expression, level of experience, education, socio-economic status,
|
|
||||||
nationality, personal appearance, race, caste, color, religion, or sexual
|
|
||||||
identity and orientation.
|
|
||||||
|
|
||||||
We pledge to act and interact in ways that contribute to an open, welcoming,
|
|
||||||
diverse, inclusive, and healthy community.
|
|
||||||
|
|
||||||
## Our Standards
|
|
||||||
|
|
||||||
Examples of behavior that contributes to a positive environment for our
|
|
||||||
community include:
|
|
||||||
|
|
||||||
* Demonstrating empathy and kindness toward other people
|
|
||||||
* Being respectful of differing opinions, viewpoints, and experiences
|
|
||||||
* Giving and gracefully accepting constructive feedback
|
|
||||||
* Accepting responsibility and apologizing to those affected by our mistakes,
|
|
||||||
and learning from the experience
|
|
||||||
* Focusing on what is best not just for us as individuals, but for the overall
|
|
||||||
community
|
|
||||||
|
|
||||||
Examples of unacceptable behavior include:
|
|
||||||
|
|
||||||
* The use of sexualized language or imagery, and sexual attention or advances of
|
|
||||||
any kind
|
|
||||||
* Trolling, insulting or derogatory comments, and personal or political attacks
|
|
||||||
* Public or private harassment
|
|
||||||
* Publishing others' private information, such as a physical or email address,
|
|
||||||
without their explicit permission
|
|
||||||
* Other conduct which could reasonably be considered inappropriate in a
|
|
||||||
professional setting
|
|
||||||
|
|
||||||
## Enforcement Responsibilities
|
|
||||||
|
|
||||||
Community leaders are responsible for clarifying and enforcing our standards of
|
|
||||||
acceptable behavior and will take appropriate and fair corrective action in
|
|
||||||
response to any behavior that they deem inappropriate, threatening, offensive,
|
|
||||||
or harmful.
|
|
||||||
|
|
||||||
Community leaders have the right and responsibility to remove, edit, or reject
|
|
||||||
comments, commits, code, wiki edits, issues, and other contributions that are
|
|
||||||
not aligned to this Code of Conduct, and will communicate reasons for moderation
|
|
||||||
decisions when appropriate.
|
|
||||||
|
|
||||||
## Scope
|
|
||||||
|
|
||||||
This Code of Conduct applies within all community spaces, and also applies when
|
|
||||||
an individual is officially representing the community in public spaces.
|
|
||||||
Examples of representing our community include using an official e-mail address,
|
|
||||||
posting via an official social media account, or acting as an appointed
|
|
||||||
representative at an online or offline event.
|
|
||||||
|
|
||||||
## Enforcement
|
|
||||||
|
|
||||||
Instances of abusive, harassing, or otherwise unacceptable behavior may be
|
|
||||||
reported to the community leaders responsible for enforcement at
|
|
||||||
<development@readarr.com>.
|
|
||||||
All complaints will be reviewed and investigated promptly and fairly.
|
|
||||||
|
|
||||||
All community leaders are obligated to respect the privacy and security of the
|
|
||||||
reporter of any incident.
|
|
||||||
|
|
||||||
## Enforcement Guidelines
|
|
||||||
|
|
||||||
Community leaders will follow these Community Impact Guidelines in determining
|
|
||||||
the consequences for any action they deem in violation of this Code of Conduct:
|
|
||||||
|
|
||||||
### 1. Correction
|
|
||||||
|
|
||||||
**Community Impact**: Use of inappropriate language or other behavior deemed
|
|
||||||
unprofessional or unwelcome in the community.
|
|
||||||
|
|
||||||
**Consequence**: A private, written warning from community leaders, providing
|
|
||||||
clarity around the nature of the violation and an explanation of why the
|
|
||||||
behavior was inappropriate. A public apology may be requested.
|
|
||||||
|
|
||||||
### 2. Warning
|
|
||||||
|
|
||||||
**Community Impact**: A violation through a single incident or series of
|
|
||||||
actions.
|
|
||||||
|
|
||||||
**Consequence**: A warning with consequences for continued behavior. No
|
|
||||||
interaction with the people involved, including unsolicited interaction with
|
|
||||||
those enforcing the Code of Conduct, for a specified period of time. This
|
|
||||||
includes avoiding interactions in community spaces as well as external channels
|
|
||||||
like social media. Violating these terms may lead to a temporary or permanent
|
|
||||||
ban.
|
|
||||||
|
|
||||||
### 3. Temporary Ban
|
|
||||||
|
|
||||||
**Community Impact**: A serious violation of community standards, including
|
|
||||||
sustained inappropriate behavior.
|
|
||||||
|
|
||||||
**Consequence**: A temporary ban from any sort of interaction or public
|
|
||||||
communication with the community for a specified period of time. No public or
|
|
||||||
private interaction with the people involved, including unsolicited interaction
|
|
||||||
with those enforcing the Code of Conduct, is allowed during this period.
|
|
||||||
Violating these terms may lead to a permanent ban.
|
|
||||||
|
|
||||||
### 4. Permanent Ban
|
|
||||||
|
|
||||||
**Community Impact**: Demonstrating a pattern of violation of community
|
|
||||||
standards, including sustained inappropriate behavior, harassment of an
|
|
||||||
individual, or aggression toward or disparagement of classes of individuals.
|
|
||||||
|
|
||||||
**Consequence**: A permanent ban from any sort of public interaction within the
|
|
||||||
community.
|
|
||||||
|
|
||||||
## Attribution
|
|
||||||
|
|
||||||
This Code of Conduct is adapted from the [Contributor Covenant][homepage],
|
|
||||||
version 2.1, available at
|
|
||||||
[https://www.contributor-covenant.org/version/2/1/code_of_conduct.html][v2.1].
|
|
||||||
|
|
||||||
Community Impact Guidelines were inspired by
|
|
||||||
[Mozilla's code of conduct enforcement ladder][Mozilla CoC].
|
|
||||||
|
|
||||||
For answers to common questions about this code of conduct, see the FAQ at
|
|
||||||
[https://www.contributor-covenant.org/faq][FAQ]. Translations are available at
|
|
||||||
[https://www.contributor-covenant.org/translations][translations].
|
|
||||||
|
|
||||||
[homepage]: https://www.contributor-covenant.org
|
|
||||||
[v2.1]: https://www.contributor-covenant.org/version/2/1/code_of_conduct.html
|
|
||||||
[Mozilla CoC]: https://github.com/mozilla/diversity
|
|
||||||
[FAQ]: https://www.contributor-covenant.org/faq
|
|
||||||
[translations]: https://www.contributor-covenant.org/translations
|
|
||||||
@@ -62,15 +62,6 @@ Support this project by becoming a sponsor. Your logo will show up here with a l
|
|||||||
|
|
||||||
[](https://opencollective.com/readarr#mega-sponsor)
|
[](https://opencollective.com/readarr#mega-sponsor)
|
||||||
|
|
||||||
## DigitalOcean
|
|
||||||
|
|
||||||
This project is also supported by DigitalOcean
|
|
||||||
<p>
|
|
||||||
<a href="https://www.digitalocean.com/">
|
|
||||||
<img src="https://opensource.nyc3.cdn.digitaloceanspaces.com/attribution/assets/SVG/DO_Logo_horizontal_blue.svg" width="201px">
|
|
||||||
</a>
|
|
||||||
</p>
|
|
||||||
|
|
||||||
### License
|
### License
|
||||||
|
|
||||||
* [GNU GPL v3](http://www.gnu.org/licenses/gpl.html)
|
* [GNU GPL v3](http://www.gnu.org/licenses/gpl.html)
|
||||||
|
|||||||
@@ -1,8 +0,0 @@
|
|||||||
# Security Policy
|
|
||||||
|
|
||||||
## Reporting a Vulnerability
|
|
||||||
|
|
||||||
Please report (suspected) security vulnerabilities on Discord (preferred) to
|
|
||||||
any of the Servarr Dev role holders (red names) or via email: development@servarr.com. You will receive a response from
|
|
||||||
us within 72 hours. If the issue is confirmed, we will release a patch as soon
|
|
||||||
as possible depending on complexity/severity.
|
|
||||||
+2
-114
@@ -15,7 +15,7 @@ variables:
|
|||||||
buildName: '$(Build.SourceBranchName).$(readarrVersion)'
|
buildName: '$(Build.SourceBranchName).$(readarrVersion)'
|
||||||
sentryOrg: 'servarr'
|
sentryOrg: 'servarr'
|
||||||
sentryUrl: 'https://sentry.servarr.com'
|
sentryUrl: 'https://sentry.servarr.com'
|
||||||
dotnetVersion: '6.0.302'
|
dotnetVersion: '6.0.300'
|
||||||
innoVersion: '6.2.0'
|
innoVersion: '6.2.0'
|
||||||
windowsImage: 'windows-2022'
|
windowsImage: 'windows-2022'
|
||||||
linuxImage: 'ubuntu-20.04'
|
linuxImage: 'ubuntu-20.04'
|
||||||
@@ -529,57 +529,6 @@ stages:
|
|||||||
testResultsFiles: '**/TestResult.xml'
|
testResultsFiles: '**/TestResult.xml'
|
||||||
testRunTitle: '$(testName) Unit Tests'
|
testRunTitle: '$(testName) Unit Tests'
|
||||||
failTaskOnFailedTests: true
|
failTaskOnFailedTests: true
|
||||||
|
|
||||||
- job: Unit_LinuxCore_Postgres
|
|
||||||
displayName: Unit Native LinuxCore with Postgres Database
|
|
||||||
variables:
|
|
||||||
pattern: 'Readarr.*.linux-core-x64.tar.gz'
|
|
||||||
artifactName: LinuxCoreTests
|
|
||||||
Readarr__Postgres__Host: 'localhost'
|
|
||||||
Readarr__Postgres__Port: '5432'
|
|
||||||
Readarr__Postgres__User: 'readarr'
|
|
||||||
Readarr__Postgres__Password: 'readarr'
|
|
||||||
|
|
||||||
pool:
|
|
||||||
vmImage: ${{ variables.linuxImage }}
|
|
||||||
|
|
||||||
timeoutInMinutes: 10
|
|
||||||
|
|
||||||
steps:
|
|
||||||
- task: UseDotNet@2
|
|
||||||
displayName: 'Install .net core'
|
|
||||||
inputs:
|
|
||||||
version: $(dotnetVersion)
|
|
||||||
- checkout: none
|
|
||||||
- task: DownloadPipelineArtifact@2
|
|
||||||
displayName: Download Test Artifact
|
|
||||||
inputs:
|
|
||||||
buildType: 'current'
|
|
||||||
artifactName: 'linux-x64-Tests'
|
|
||||||
targetPath: $(testsFolder)
|
|
||||||
- bash: find ${TESTSFOLDER} -name "Readarr.Test.Dummy" -exec chmod a+x {} \;
|
|
||||||
displayName: Make Test Dummy Executable
|
|
||||||
condition: and(succeeded(), ne(variables['osName'], 'Windows'))
|
|
||||||
- bash: |
|
|
||||||
docker run -d --name=postgres14 \
|
|
||||||
-e POSTGRES_PASSWORD=readarr \
|
|
||||||
-e POSTGRES_USER=readarr \
|
|
||||||
-p 5432:5432/tcp \
|
|
||||||
-v /usr/share/zoneinfo/America/Chicago:/etc/localtime:ro \
|
|
||||||
postgres:14
|
|
||||||
displayName: Start postgres
|
|
||||||
- bash: |
|
|
||||||
chmod a+x ${TESTSFOLDER}/test.sh
|
|
||||||
ls -lR ${TESTSFOLDER}
|
|
||||||
${TESTSFOLDER}/test.sh Linux Unit Test
|
|
||||||
displayName: Run Tests
|
|
||||||
- task: PublishTestResults@2
|
|
||||||
displayName: Publish Test Results
|
|
||||||
inputs:
|
|
||||||
testResultsFormat: 'NUnit'
|
|
||||||
testResultsFiles: '**/TestResult.xml'
|
|
||||||
testRunTitle: 'LinuxCore Postgres Unit Tests'
|
|
||||||
failTaskOnFailedTests: true
|
|
||||||
|
|
||||||
- stage: Integration
|
- stage: Integration
|
||||||
displayName: Integration
|
displayName: Integration
|
||||||
@@ -648,66 +597,6 @@ stages:
|
|||||||
failTaskOnFailedTests: true
|
failTaskOnFailedTests: true
|
||||||
displayName: Publish Test Results
|
displayName: Publish Test Results
|
||||||
|
|
||||||
- job: Integration_LinuxCore_Postgres
|
|
||||||
displayName: Integration Native LinuxCore with Postgres Database
|
|
||||||
variables:
|
|
||||||
pattern: 'Readarr.*.linux-core-x64.tar.gz'
|
|
||||||
Readarr__Postgres__Host: 'localhost'
|
|
||||||
Readarr__Postgres__Port: '5432'
|
|
||||||
Readarr__Postgres__User: 'readarr'
|
|
||||||
Readarr__Postgres__Password: 'readarr'
|
|
||||||
|
|
||||||
pool:
|
|
||||||
vmImage: ${{ variables.linuxImage }}
|
|
||||||
|
|
||||||
steps:
|
|
||||||
- task: UseDotNet@2
|
|
||||||
displayName: 'Install .net core'
|
|
||||||
inputs:
|
|
||||||
version: $(dotnetVersion)
|
|
||||||
- checkout: none
|
|
||||||
- task: DownloadPipelineArtifact@2
|
|
||||||
displayName: Download Test Artifact
|
|
||||||
inputs:
|
|
||||||
buildType: 'current'
|
|
||||||
artifactName: 'linux-x64-tests'
|
|
||||||
targetPath: $(testsFolder)
|
|
||||||
- task: DownloadPipelineArtifact@2
|
|
||||||
displayName: Download Build Artifact
|
|
||||||
inputs:
|
|
||||||
buildType: 'current'
|
|
||||||
artifactName: Packages
|
|
||||||
itemPattern: '**/$(pattern)'
|
|
||||||
targetPath: $(Build.ArtifactStagingDirectory)
|
|
||||||
- task: ExtractFiles@1
|
|
||||||
inputs:
|
|
||||||
archiveFilePatterns: '$(Build.ArtifactStagingDirectory)/**/$(pattern)'
|
|
||||||
destinationFolder: '$(Build.ArtifactStagingDirectory)/bin'
|
|
||||||
displayName: Extract Package
|
|
||||||
- bash: |
|
|
||||||
mkdir -p ./bin/
|
|
||||||
cp -r -v ${BUILD_ARTIFACTSTAGINGDIRECTORY}/bin/Readarr/. ./bin/
|
|
||||||
displayName: Move Package Contents
|
|
||||||
- bash: |
|
|
||||||
docker run -d --name=postgres14 \
|
|
||||||
-e POSTGRES_PASSWORD=readarr \
|
|
||||||
-e POSTGRES_USER=readarr \
|
|
||||||
-p 5432:5432/tcp \
|
|
||||||
-v /usr/share/zoneinfo/America/Chicago:/etc/localtime:ro \
|
|
||||||
postgres:14
|
|
||||||
displayName: Start postgres
|
|
||||||
- bash: |
|
|
||||||
chmod a+x ${TESTSFOLDER}/test.sh
|
|
||||||
${TESTSFOLDER}/test.sh Linux Integration Test
|
|
||||||
displayName: Run Integration Tests
|
|
||||||
- task: PublishTestResults@2
|
|
||||||
inputs:
|
|
||||||
testResultsFormat: 'NUnit'
|
|
||||||
testResultsFiles: '**/TestResult.xml'
|
|
||||||
testRunTitle: 'Integration LinuxCore Postgres Database Integration Tests'
|
|
||||||
failTaskOnFailedTests: true
|
|
||||||
displayName: Publish Test Results
|
|
||||||
|
|
||||||
- job: Integration_FreeBSD
|
- job: Integration_FreeBSD
|
||||||
displayName: Integration Native FreeBSD
|
displayName: Integration Native FreeBSD
|
||||||
workspace:
|
workspace:
|
||||||
@@ -730,7 +619,7 @@ stages:
|
|||||||
inputs:
|
inputs:
|
||||||
buildType: 'current'
|
buildType: 'current'
|
||||||
artifactName: Packages
|
artifactName: Packages
|
||||||
itemPattern: '**/$(pattern)'
|
itemPattern: '/$(pattern)'
|
||||||
targetPath: $(Build.ArtifactStagingDirectory)
|
targetPath: $(Build.ArtifactStagingDirectory)
|
||||||
- bash: |
|
- bash: |
|
||||||
mkdir -p ${BUILD_ARTIFACTSTAGINGDIRECTORY}/bin
|
mkdir -p ${BUILD_ARTIFACTSTAGINGDIRECTORY}/bin
|
||||||
@@ -1050,4 +939,3 @@ stages:
|
|||||||
SYSTEM_ACCESSTOKEN: $(System.AccessToken)
|
SYSTEM_ACCESSTOKEN: $(System.AccessToken)
|
||||||
DISCORDCHANNELID: $(discordChannelId)
|
DISCORDCHANNELID: $(discordChannelId)
|
||||||
DISCORDWEBHOOKKEY: $(discordWebhookKey)
|
DISCORDWEBHOOKKEY: $(discordWebhookKey)
|
||||||
DISCORDTHREADID: $(discordThreadId)
|
|
||||||
|
|||||||
@@ -39,7 +39,6 @@ module.exports = {
|
|||||||
plugins: [
|
plugins: [
|
||||||
'filenames',
|
'filenames',
|
||||||
'react',
|
'react',
|
||||||
'react-hooks',
|
|
||||||
'simple-import-sort',
|
'simple-import-sort',
|
||||||
'import'
|
'import'
|
||||||
],
|
],
|
||||||
@@ -309,9 +308,7 @@ module.exports = {
|
|||||||
'react/react-in-jsx-scope': 2,
|
'react/react-in-jsx-scope': 2,
|
||||||
'react/self-closing-comp': 2,
|
'react/self-closing-comp': 2,
|
||||||
'react/sort-comp': 2,
|
'react/sort-comp': 2,
|
||||||
'react/jsx-wrap-multilines': 2,
|
'react/jsx-wrap-multilines': 2
|
||||||
'react-hooks/rules-of-hooks': 'error',
|
|
||||||
'react-hooks/exhaustive-deps': 'error'
|
|
||||||
},
|
},
|
||||||
overrides: [
|
overrides: [
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -169,16 +169,6 @@ class HistoryRow extends Component {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (name === 'sourceTitle') {
|
|
||||||
return (
|
|
||||||
<TableRowCell
|
|
||||||
key={name}
|
|
||||||
>
|
|
||||||
{sourceTitle}
|
|
||||||
</TableRowCell>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (name === 'details') {
|
if (name === 'details') {
|
||||||
return (
|
return (
|
||||||
<TableRowCell
|
<TableRowCell
|
||||||
|
|||||||
@@ -12,9 +12,9 @@ import ModalBody from 'Components/Modal/ModalBody';
|
|||||||
import Portal from 'Components/Portal';
|
import Portal from 'Components/Portal';
|
||||||
import Scroller from 'Components/Scroller/Scroller';
|
import Scroller from 'Components/Scroller/Scroller';
|
||||||
import { icons, scrollDirections, sizes } from 'Helpers/Props';
|
import { icons, scrollDirections, sizes } from 'Helpers/Props';
|
||||||
import { isMobile as isMobileUtil } from 'Utilities/browser';
|
|
||||||
import * as keyCodes from 'Utilities/Constants/keyCodes';
|
import * as keyCodes from 'Utilities/Constants/keyCodes';
|
||||||
import getUniqueElememtId from 'Utilities/getUniqueElementId';
|
import getUniqueElememtId from 'Utilities/getUniqueElementId';
|
||||||
|
import { isMobile as isMobileUtil } from 'Utilities/mobile';
|
||||||
import HintedSelectInputOption from './HintedSelectInputOption';
|
import HintedSelectInputOption from './HintedSelectInputOption';
|
||||||
import HintedSelectInputSelectedValue from './HintedSelectInputSelectedValue';
|
import HintedSelectInputSelectedValue from './HintedSelectInputSelectedValue';
|
||||||
import TextInput from './TextInput';
|
import TextInput from './TextInput';
|
||||||
|
|||||||
@@ -33,7 +33,7 @@ function ConfirmModal(props) {
|
|||||||
|
|
||||||
return () => unbindShortcut('enter', onConfirm);
|
return () => unbindShortcut('enter', onConfirm);
|
||||||
}
|
}
|
||||||
}, [bindShortcut, unbindShortcut, isOpen, onConfirm]);
|
}, [isOpen, onConfirm]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Modal
|
<Modal
|
||||||
|
|||||||
@@ -6,9 +6,9 @@ import ReactDOM from 'react-dom';
|
|||||||
import FocusLock from 'react-focus-lock';
|
import FocusLock from 'react-focus-lock';
|
||||||
import ErrorBoundary from 'Components/Error/ErrorBoundary';
|
import ErrorBoundary from 'Components/Error/ErrorBoundary';
|
||||||
import { sizes } from 'Helpers/Props';
|
import { sizes } from 'Helpers/Props';
|
||||||
import { isIOS } from 'Utilities/browser';
|
|
||||||
import * as keyCodes from 'Utilities/Constants/keyCodes';
|
import * as keyCodes from 'Utilities/Constants/keyCodes';
|
||||||
import getUniqueElememtId from 'Utilities/getUniqueElementId';
|
import getUniqueElememtId from 'Utilities/getUniqueElementId';
|
||||||
|
import { isIOS } from 'Utilities/mobile';
|
||||||
import { setScrollLock } from 'Utilities/scrollLock';
|
import { setScrollLock } from 'Utilities/scrollLock';
|
||||||
import ModalError from './ModalError';
|
import ModalError from './ModalError';
|
||||||
import styles from './Modal.css';
|
import styles from './Modal.css';
|
||||||
|
|||||||
@@ -1,12 +1,23 @@
|
|||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import React, { Component } from 'react';
|
import React, { Component } from 'react';
|
||||||
|
import OverlayScroller from 'Components/Scroller/OverlayScroller';
|
||||||
import Scroller from 'Components/Scroller/Scroller';
|
import Scroller from 'Components/Scroller/Scroller';
|
||||||
import { scrollDirections } from 'Helpers/Props';
|
import { scrollDirections } from 'Helpers/Props';
|
||||||
|
import { isMobile as isMobileUtil } from 'Utilities/mobile';
|
||||||
import { isLocked } from 'Utilities/scrollLock';
|
import { isLocked } from 'Utilities/scrollLock';
|
||||||
import styles from './PageContentBody.css';
|
import styles from './PageContentBody.css';
|
||||||
|
|
||||||
class PageContentBody extends Component {
|
class PageContentBody extends Component {
|
||||||
|
|
||||||
|
//
|
||||||
|
// Lifecycle
|
||||||
|
|
||||||
|
constructor(props, context) {
|
||||||
|
super(props, context);
|
||||||
|
|
||||||
|
this._isMobile = isMobileUtil();
|
||||||
|
}
|
||||||
|
|
||||||
//
|
//
|
||||||
// Listeners
|
// Listeners
|
||||||
|
|
||||||
@@ -30,8 +41,10 @@ class PageContentBody extends Component {
|
|||||||
...otherProps
|
...otherProps
|
||||||
} = this.props;
|
} = this.props;
|
||||||
|
|
||||||
|
const ScrollerComponent = this._isMobile ? Scroller : OverlayScroller;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Scroller
|
<ScrollerComponent
|
||||||
className={className}
|
className={className}
|
||||||
scrollDirection={scrollDirections.VERTICAL}
|
scrollDirection={scrollDirections.VERTICAL}
|
||||||
{...otherProps}
|
{...otherProps}
|
||||||
@@ -40,7 +53,7 @@ class PageContentBody extends Component {
|
|||||||
<div className={innerClassName}>
|
<div className={innerClassName}>
|
||||||
{children}
|
{children}
|
||||||
</div>
|
</div>
|
||||||
</Scroller>
|
</ScrollerComponent>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
.jumpBar {
|
.jumpBar {
|
||||||
z-index: $pageJumpBarZIndex;
|
|
||||||
display: flex;
|
display: flex;
|
||||||
align-content: stretch;
|
align-content: stretch;
|
||||||
align-items: stretch;
|
align-items: stretch;
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import React, { Component } from 'react';
|
import React, { Component } from 'react';
|
||||||
import { Scrollbars } from 'react-custom-scrollbars-2';
|
import { Scrollbars } from 'react-custom-scrollbars';
|
||||||
import { scrollDirections } from 'Helpers/Props';
|
import { scrollDirections } from 'Helpers/Props';
|
||||||
import styles from './OverlayScroller.css';
|
import styles from './OverlayScroller.css';
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import React, { Component } from 'react';
|
import React, { Component } from 'react';
|
||||||
import Measure from 'Components/Measure';
|
import Measure from 'Components/Measure';
|
||||||
import { isMobile as isMobileUtil } from 'Utilities/browser';
|
import { isMobile as isMobileUtil } from 'Utilities/mobile';
|
||||||
import styles from './SwipeHeader.css';
|
import styles from './SwipeHeader.css';
|
||||||
|
|
||||||
function cursorPosition(event) {
|
function cursorPosition(event) {
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import { Manager, Popper, Reference } from 'react-popper';
|
|||||||
import Portal from 'Components/Portal';
|
import Portal from 'Components/Portal';
|
||||||
import { kinds, tooltipPositions } from 'Helpers/Props';
|
import { kinds, tooltipPositions } from 'Helpers/Props';
|
||||||
import dimensions from 'Styles/Variables/dimensions';
|
import dimensions from 'Styles/Variables/dimensions';
|
||||||
import { isMobile as isMobileUtil } from 'Utilities/browser';
|
import { isMobile as isMobileUtil } from 'Utilities/mobile';
|
||||||
import styles from './Tooltip.css';
|
import styles from './Tooltip.css';
|
||||||
|
|
||||||
let maxWidth = null;
|
let maxWidth = null;
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ import PropTypes from 'prop-types';
|
|||||||
import React, { Component } from 'react';
|
import React, { Component } from 'react';
|
||||||
import Alert from 'Components/Alert';
|
import Alert from 'Components/Alert';
|
||||||
import Button from 'Components/Link/Button';
|
import Button from 'Components/Link/Button';
|
||||||
import LoadingIndicator from 'Components/Loading/LoadingIndicator';
|
|
||||||
import ModalBody from 'Components/Modal/ModalBody';
|
import ModalBody from 'Components/Modal/ModalBody';
|
||||||
import ModalContent from 'Components/Modal/ModalContent';
|
import ModalContent from 'Components/Modal/ModalContent';
|
||||||
import ModalFooter from 'Components/Modal/ModalFooter';
|
import ModalFooter from 'Components/Modal/ModalFooter';
|
||||||
@@ -10,8 +9,7 @@ import ModalHeader from 'Components/Modal/ModalHeader';
|
|||||||
import Table from 'Components/Table/Table';
|
import Table from 'Components/Table/Table';
|
||||||
import TableBody from 'Components/Table/TableBody';
|
import TableBody from 'Components/Table/TableBody';
|
||||||
import { scrollDirections } from 'Helpers/Props';
|
import { scrollDirections } from 'Helpers/Props';
|
||||||
import translate from 'Utilities/String/translate';
|
import SelectEditionRow from './SelectEditionRow';
|
||||||
import SelectEditionRowConnector from './SelectEditionRowConnector';
|
|
||||||
import styles from './SelectEditionModalContent.css';
|
import styles from './SelectEditionModalContent.css';
|
||||||
|
|
||||||
const columns = [
|
const columns = [
|
||||||
@@ -35,30 +33,15 @@ class SelectEditionModalContent extends Component {
|
|||||||
render() {
|
render() {
|
||||||
const {
|
const {
|
||||||
books,
|
books,
|
||||||
isPopulated,
|
|
||||||
isFetching,
|
|
||||||
error,
|
|
||||||
onEditionSelect,
|
onEditionSelect,
|
||||||
onModalClose,
|
onModalClose,
|
||||||
...otherProps
|
...otherProps
|
||||||
} = this.props;
|
} = this.props;
|
||||||
|
|
||||||
if (!isPopulated && !error) {
|
|
||||||
return (<LoadingIndicator />);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!isFetching && error) {
|
|
||||||
return (
|
|
||||||
<div>
|
|
||||||
{translate('LoadingEditionsFailed')}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ModalContent onModalClose={onModalClose}>
|
<ModalContent onModalClose={onModalClose}>
|
||||||
<ModalHeader>
|
<ModalHeader>
|
||||||
{translate('ManualImportSelectEdition')}
|
Manual Import - Select Edition
|
||||||
</ModalHeader>
|
</ModalHeader>
|
||||||
|
|
||||||
<ModalBody
|
<ModalBody
|
||||||
@@ -77,7 +60,7 @@ class SelectEditionModalContent extends Component {
|
|||||||
{
|
{
|
||||||
books.map((item) => {
|
books.map((item) => {
|
||||||
return (
|
return (
|
||||||
<SelectEditionRowConnector
|
<SelectEditionRow
|
||||||
key={item.book.id}
|
key={item.book.id}
|
||||||
matchedEditionId={item.matchedEditionId}
|
matchedEditionId={item.matchedEditionId}
|
||||||
columns={columns}
|
columns={columns}
|
||||||
@@ -93,7 +76,7 @@ class SelectEditionModalContent extends Component {
|
|||||||
|
|
||||||
<ModalFooter>
|
<ModalFooter>
|
||||||
<Button onPress={onModalClose}>
|
<Button onPress={onModalClose}>
|
||||||
{translate('Cancel')}
|
Cancel
|
||||||
</Button>
|
</Button>
|
||||||
</ModalFooter>
|
</ModalFooter>
|
||||||
</ModalContent>
|
</ModalContent>
|
||||||
@@ -103,9 +86,6 @@ class SelectEditionModalContent extends Component {
|
|||||||
|
|
||||||
SelectEditionModalContent.propTypes = {
|
SelectEditionModalContent.propTypes = {
|
||||||
books: PropTypes.arrayOf(PropTypes.object).isRequired,
|
books: PropTypes.arrayOf(PropTypes.object).isRequired,
|
||||||
isFetching: PropTypes.bool,
|
|
||||||
isPopulated: PropTypes.bool,
|
|
||||||
error: PropTypes.object,
|
|
||||||
onEditionSelect: PropTypes.func.isRequired,
|
onEditionSelect: PropTypes.func.isRequired,
|
||||||
onModalClose: PropTypes.func.isRequired
|
onModalClose: PropTypes.func.isRequired
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,71 +1,27 @@
|
|||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import React, { Component } from 'react';
|
import React, { Component } from 'react';
|
||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
import { createSelector } from 'reselect';
|
|
||||||
import { clearEditions, fetchEditions } from 'Store/Actions/editionActions';
|
|
||||||
import {
|
import {
|
||||||
saveInteractiveImportItem,
|
saveInteractiveImportItem,
|
||||||
updateInteractiveImportItem } from 'Store/Actions/interactiveImportActions';
|
updateInteractiveImportItem } from 'Store/Actions/interactiveImportActions';
|
||||||
import { registerPagePopulator, unregisterPagePopulator } from 'Utilities/pagePopulator';
|
|
||||||
import SelectEditionModalContent from './SelectEditionModalContent';
|
import SelectEditionModalContent from './SelectEditionModalContent';
|
||||||
|
|
||||||
function createMapStateToProps() {
|
function createMapStateToProps() {
|
||||||
return createSelector(
|
return {};
|
||||||
(state) => state.editions,
|
|
||||||
(editions) => {
|
|
||||||
const {
|
|
||||||
isFetching,
|
|
||||||
isPopulated,
|
|
||||||
error
|
|
||||||
} = editions;
|
|
||||||
|
|
||||||
return {
|
|
||||||
isFetching,
|
|
||||||
isPopulated,
|
|
||||||
error
|
|
||||||
};
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const mapDispatchToProps = {
|
const mapDispatchToProps = {
|
||||||
fetchEditions,
|
|
||||||
clearEditions,
|
|
||||||
updateInteractiveImportItem,
|
updateInteractiveImportItem,
|
||||||
saveInteractiveImportItem
|
saveInteractiveImportItem
|
||||||
};
|
};
|
||||||
|
|
||||||
class SelectEditionModalContentConnector extends Component {
|
class SelectEditionModalContentConnector extends Component {
|
||||||
|
|
||||||
//
|
|
||||||
// Lifecycle
|
|
||||||
|
|
||||||
componentDidMount() {
|
|
||||||
registerPagePopulator(this.populate);
|
|
||||||
this.populate();
|
|
||||||
}
|
|
||||||
|
|
||||||
componentWillUnmount() {
|
|
||||||
unregisterPagePopulator(this.populate);
|
|
||||||
this.unpopulate();
|
|
||||||
}
|
|
||||||
//
|
|
||||||
// Control
|
|
||||||
|
|
||||||
populate = () => {
|
|
||||||
const bookId = this.props.books.map((b) => b.book.id);
|
|
||||||
|
|
||||||
this.props.fetchEditions({ bookId });
|
|
||||||
}
|
|
||||||
|
|
||||||
unpopulate = () => {
|
|
||||||
this.props.clearEditions();
|
|
||||||
}
|
|
||||||
|
|
||||||
//
|
//
|
||||||
// Listeners
|
// Listeners
|
||||||
|
|
||||||
onEditionSelect = (bookId, foreignEditionId) => {
|
onEditionSelect = (bookId, foreignEditionId) => {
|
||||||
|
console.log(`book: ${bookId} id: ${foreignEditionId} ${typeof foreignEditionId}`);
|
||||||
const ids = this.props.importIdsByBook[bookId];
|
const ids = this.props.importIdsByBook[bookId];
|
||||||
|
|
||||||
ids.forEach((id) => {
|
ids.forEach((id) => {
|
||||||
@@ -99,8 +55,6 @@ class SelectEditionModalContentConnector extends Component {
|
|||||||
SelectEditionModalContentConnector.propTypes = {
|
SelectEditionModalContentConnector.propTypes = {
|
||||||
importIdsByBook: PropTypes.object.isRequired,
|
importIdsByBook: PropTypes.object.isRequired,
|
||||||
books: PropTypes.arrayOf(PropTypes.object).isRequired,
|
books: PropTypes.arrayOf(PropTypes.object).isRequired,
|
||||||
fetchEditions: PropTypes.func.isRequired,
|
|
||||||
clearEditions: PropTypes.func.isRequired,
|
|
||||||
updateInteractiveImportItem: PropTypes.func.isRequired,
|
updateInteractiveImportItem: PropTypes.func.isRequired,
|
||||||
saveInteractiveImportItem: PropTypes.func.isRequired,
|
saveInteractiveImportItem: PropTypes.func.isRequired,
|
||||||
onModalClose: PropTypes.func.isRequired
|
onModalClose: PropTypes.func.isRequired
|
||||||
|
|||||||
@@ -1,32 +0,0 @@
|
|||||||
import PropTypes from 'prop-types';
|
|
||||||
import React, { Component } from 'react';
|
|
||||||
import { connect } from 'react-redux';
|
|
||||||
import { createSelector } from 'reselect';
|
|
||||||
import SelectEditionRow from './SelectEditionRow';
|
|
||||||
|
|
||||||
function createMapStateToProps() {
|
|
||||||
return createSelector(
|
|
||||||
(state, { id }) => id,
|
|
||||||
(state) => state.editions,
|
|
||||||
(id, editionState) => {
|
|
||||||
const editions = editionState.items.filter((e) => e.bookId === id);
|
|
||||||
return { editions };
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
class SelectEditionRowConnector extends Component {
|
|
||||||
render() {
|
|
||||||
return (
|
|
||||||
<SelectEditionRow
|
|
||||||
{...this.props}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
SelectEditionRowConnector.PropTypes = {
|
|
||||||
editions: PropTypes.arrayOf(PropTypes.object).isRequired
|
|
||||||
};
|
|
||||||
|
|
||||||
export default connect(createMapStateToProps)(SelectEditionRowConnector);
|
|
||||||
@@ -148,10 +148,11 @@ class AddNewItem extends Component {
|
|||||||
);
|
);
|
||||||
} else if (item.book) {
|
} else if (item.book) {
|
||||||
const book = item.book;
|
const book = item.book;
|
||||||
|
const edition = book.editions.find((x) => x.monitored);
|
||||||
return (
|
return (
|
||||||
<AddNewBookSearchResultConnector
|
<AddNewBookSearchResultConnector
|
||||||
key={item.id}
|
key={item.id}
|
||||||
isExistingBook={'id' in book && book.id !== 0}
|
isExistingBook={'id' in edition && edition.id !== 0}
|
||||||
isExistingAuthor={'id' in book.author && book.author.id !== 0}
|
isExistingAuthor={'id' in book.author && book.author.id !== 0}
|
||||||
{...book}
|
{...book}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -138,20 +138,17 @@ class AddNewBookSearchResult extends Component {
|
|||||||
null
|
null
|
||||||
}
|
}
|
||||||
|
|
||||||
{
|
<Link
|
||||||
editions && editions.length > 1 ?
|
className={styles.mbLink}
|
||||||
<Link
|
to={`https://goodreads.com/book/show/${editions[0].foreignEditionId}`}
|
||||||
className={styles.mbLink}
|
onPress={this.onTVDBLinkPress}
|
||||||
to={`https://goodreads.com/book/show/${editions[0].foreignEditionId}`}
|
>
|
||||||
onPress={this.onTVDBLinkPress}
|
<Icon
|
||||||
>
|
className={styles.mbLinkIcon}
|
||||||
<Icon
|
name={icons.EXTERNAL_LINK}
|
||||||
className={styles.mbLinkIcon}
|
size={28}
|
||||||
name={icons.EXTERNAL_LINK}
|
/>
|
||||||
size={28}
|
</Link>
|
||||||
/>
|
|
||||||
</Link> : null
|
|
||||||
}
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -221,7 +218,7 @@ AddNewBookSearchResult.propTypes = {
|
|||||||
overview: PropTypes.string,
|
overview: PropTypes.string,
|
||||||
ratings: PropTypes.object.isRequired,
|
ratings: PropTypes.object.isRequired,
|
||||||
author: PropTypes.object,
|
author: PropTypes.object,
|
||||||
editions: PropTypes.arrayOf(PropTypes.object),
|
editions: PropTypes.arrayOf(PropTypes.object).isRequired,
|
||||||
images: PropTypes.arrayOf(PropTypes.object).isRequired,
|
images: PropTypes.arrayOf(PropTypes.object).isRequired,
|
||||||
isExistingBook: PropTypes.bool.isRequired,
|
isExistingBook: PropTypes.bool.isRequired,
|
||||||
isExistingAuthor: PropTypes.bool.isRequired,
|
isExistingAuthor: PropTypes.bool.isRequired,
|
||||||
|
|||||||
@@ -173,7 +173,7 @@ class AddAuthorOptionsForm extends Component {
|
|||||||
AddAuthorOptionsForm.propTypes = {
|
AddAuthorOptionsForm.propTypes = {
|
||||||
rootFolderPath: PropTypes.object,
|
rootFolderPath: PropTypes.object,
|
||||||
monitor: PropTypes.object.isRequired,
|
monitor: PropTypes.object.isRequired,
|
||||||
monitorNewItems: PropTypes.object.isRequired,
|
monitorNewItems: PropTypes.string.isRequired,
|
||||||
qualityProfileId: PropTypes.object,
|
qualityProfileId: PropTypes.object,
|
||||||
metadataProfileId: PropTypes.object,
|
metadataProfileId: PropTypes.object,
|
||||||
showMetadataProfile: PropTypes.bool.isRequired,
|
showMetadataProfile: PropTypes.bool.isRequired,
|
||||||
|
|||||||
@@ -21,7 +21,6 @@ function HostSettings(props) {
|
|||||||
port,
|
port,
|
||||||
urlBase,
|
urlBase,
|
||||||
instanceName,
|
instanceName,
|
||||||
applicationUrl,
|
|
||||||
enableSsl,
|
enableSsl,
|
||||||
sslPort,
|
sslPort,
|
||||||
sslCertPath,
|
sslCertPath,
|
||||||
@@ -59,7 +58,6 @@ function HostSettings(props) {
|
|||||||
name="port"
|
name="port"
|
||||||
min={1}
|
min={1}
|
||||||
max={65535}
|
max={65535}
|
||||||
autocomplete="off"
|
|
||||||
helpTextWarning={translate('PortHelpTextWarning')}
|
helpTextWarning={translate('PortHelpTextWarning')}
|
||||||
onChange={onInputChange}
|
onChange={onInputChange}
|
||||||
{...port}
|
{...port}
|
||||||
@@ -97,21 +95,6 @@ function HostSettings(props) {
|
|||||||
/>
|
/>
|
||||||
</FormGroup>
|
</FormGroup>
|
||||||
|
|
||||||
<FormGroup
|
|
||||||
advancedSettings={advancedSettings}
|
|
||||||
isAdvanced={true}
|
|
||||||
>
|
|
||||||
<FormLabel>{translate('ApplicationURL')}</FormLabel>
|
|
||||||
|
|
||||||
<FormInputGroup
|
|
||||||
type={inputTypes.TEXT}
|
|
||||||
name="applicationUrl"
|
|
||||||
helpText={translate('ApplicationUrlHelpText')}
|
|
||||||
onChange={onInputChange}
|
|
||||||
{...applicationUrl}
|
|
||||||
/>
|
|
||||||
</FormGroup>
|
|
||||||
|
|
||||||
<FormGroup
|
<FormGroup
|
||||||
advancedSettings={advancedSettings}
|
advancedSettings={advancedSettings}
|
||||||
isAdvanced={true}
|
isAdvanced={true}
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ function PendingChangesModal(props) {
|
|||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
bindShortcut('enter', onConfirm);
|
bindShortcut('enter', onConfirm);
|
||||||
}, [bindShortcut, onConfirm]);
|
}, [onConfirm]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Modal
|
<Modal
|
||||||
|
|||||||
@@ -6,7 +6,6 @@ import getProviderState from 'Utilities/State/getProviderState';
|
|||||||
import { removeItem, set, updateItem } from '../baseActions';
|
import { removeItem, set, updateItem } from '../baseActions';
|
||||||
|
|
||||||
const abortCurrentRequests = {};
|
const abortCurrentRequests = {};
|
||||||
let lastSaveData = null;
|
|
||||||
|
|
||||||
export function createCancelSaveProviderHandler(section) {
|
export function createCancelSaveProviderHandler(section) {
|
||||||
return function(getState, payload, dispatch) {
|
return function(getState, payload, dispatch) {
|
||||||
@@ -28,33 +27,27 @@ function createSaveProviderHandler(section, url, options = {}, removeStale = fal
|
|||||||
} = payload;
|
} = payload;
|
||||||
|
|
||||||
const saveData = Array.isArray(id) ? id.map((x) => getProviderState({ id: x, ...otherPayload }, getState, section)) : getProviderState({ id, ...otherPayload }, getState, section);
|
const saveData = Array.isArray(id) ? id.map((x) => getProviderState({ id: x, ...otherPayload }, getState, section)) : getProviderState({ id, ...otherPayload }, getState, section);
|
||||||
const requestUrl = id ? `${url}/${id}` : url;
|
|
||||||
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.
|
|
||||||
|
|
||||||
if (id && _.isEqual(saveData, lastSaveData)) {
|
|
||||||
params.forceSave = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
lastSaveData = saveData;
|
|
||||||
|
|
||||||
const ajaxOptions = {
|
const ajaxOptions = {
|
||||||
url: `${requestUrl}?${$.param(params, true)}`,
|
url: `${url}?${$.param(queryParams, true)}`,
|
||||||
method: id ? 'PUT' : 'POST',
|
method: 'POST',
|
||||||
contentType: 'application/json',
|
contentType: 'application/json',
|
||||||
dataType: 'json',
|
dataType: 'json',
|
||||||
data: JSON.stringify(saveData)
|
data: JSON.stringify(saveData)
|
||||||
};
|
};
|
||||||
|
|
||||||
|
if (id) {
|
||||||
|
ajaxOptions.method = 'PUT';
|
||||||
|
if (!Array.isArray(id)) {
|
||||||
|
ajaxOptions.url = `${url}/${id}?${$.param(queryParams, true)}`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const { request, abortRequest } = createAjaxRequest(ajaxOptions);
|
const { request, abortRequest } = createAjaxRequest(ajaxOptions);
|
||||||
|
|
||||||
abortCurrentRequests[section] = abortRequest;
|
abortCurrentRequests[section] = abortRequest;
|
||||||
|
|
||||||
request.done((data) => {
|
request.done((data) => {
|
||||||
lastSaveData = null;
|
|
||||||
|
|
||||||
if (!Array.isArray(data)) {
|
if (!Array.isArray(data)) {
|
||||||
data = [data];
|
data = [data];
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -71,11 +71,6 @@ export const defaultState = {
|
|||||||
label: 'Release Group',
|
label: 'Release Group',
|
||||||
isVisible: false
|
isVisible: false
|
||||||
},
|
},
|
||||||
{
|
|
||||||
name: 'sourceTitle',
|
|
||||||
label: 'Source Title',
|
|
||||||
isVisible: false
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
name: 'details',
|
name: 'details',
|
||||||
columnLabel: 'Details',
|
columnLabel: 'Details',
|
||||||
|
|||||||
@@ -177,8 +177,7 @@ export const defaultState = {
|
|||||||
{
|
{
|
||||||
name: 'size',
|
name: 'size',
|
||||||
label: 'Size',
|
label: 'Size',
|
||||||
type: filterBuilderTypes.NUMBER,
|
type: filterBuilderTypes.NUMBER
|
||||||
valueType: filterBuilderValueTypes.BYTES
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'seeders',
|
name: 'seeders',
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
import { createSelector, createSelectorCreator, defaultMemoize } from 'reselect';
|
import { createSelector, createSelectorCreator, defaultMemoize } from 'reselect';
|
||||||
import hasDifferentItemsOrOrder from 'Utilities/Object/hasDifferentItemsOrOrder';
|
import hasDifferentItemsOrOrder from 'Utilities/Object/hasDifferentItemsOrOrder';
|
||||||
import createBooksClientSideCollectionSelector from './createBooksClientSideCollectionSelector';
|
import createClientSideCollectionSelector from './createClientSideCollectionSelector';
|
||||||
|
|
||||||
function createUnoptimizedSelector(uiSection) {
|
function createUnoptimizedSelector(uiSection) {
|
||||||
return createSelector(
|
return createSelector(
|
||||||
createBooksClientSideCollectionSelector(uiSection),
|
createClientSideCollectionSelector('books', uiSection),
|
||||||
(books) => {
|
(books) => {
|
||||||
const items = books.items.map((s) => {
|
const items = books.items.map((s) => {
|
||||||
const {
|
const {
|
||||||
|
|||||||
@@ -1,35 +0,0 @@
|
|||||||
import _ from 'lodash';
|
|
||||||
import { createSelector } from 'reselect';
|
|
||||||
import filterCollection from 'Utilities/Array/filterCollection';
|
|
||||||
import sortCollection from 'Utilities/Array/sortCollection';
|
|
||||||
import createCustomFiltersSelector from './createCustomFiltersSelector';
|
|
||||||
|
|
||||||
function createBooksClientSideCollectionSelector(uiSection) {
|
|
||||||
return createSelector(
|
|
||||||
(state) => _.get(state, 'books'),
|
|
||||||
(state) => _.get(state, 'authors'),
|
|
||||||
(state) => _.get(state, uiSection),
|
|
||||||
createCustomFiltersSelector('books', uiSection),
|
|
||||||
(bookState, authorState, uiSectionState = {}, customFilters) => {
|
|
||||||
const state = Object.assign({}, bookState, uiSectionState, { customFilters });
|
|
||||||
|
|
||||||
const books = state.items;
|
|
||||||
for (const book of books) {
|
|
||||||
book.author = authorState.items[authorState.itemMap[book.authorId]];
|
|
||||||
}
|
|
||||||
|
|
||||||
const filtered = filterCollection(books, state);
|
|
||||||
const sorted = sortCollection(filtered, state);
|
|
||||||
|
|
||||||
return {
|
|
||||||
...bookState,
|
|
||||||
...uiSectionState,
|
|
||||||
customFilters,
|
|
||||||
items: sorted,
|
|
||||||
totalItems: state.items.length
|
|
||||||
};
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export default createBooksClientSideCollectionSelector;
|
|
||||||
@@ -1,8 +1,123 @@
|
|||||||
import _ from 'lodash';
|
import _ from 'lodash';
|
||||||
import { createSelector } from 'reselect';
|
import { createSelector } from 'reselect';
|
||||||
import filterCollection from 'Utilities/Array/filterCollection';
|
import { filterTypePredicates, filterTypes, sortDirections } from 'Helpers/Props';
|
||||||
import sortCollection from 'Utilities/Array/sortCollection';
|
import findSelectedFilters from 'Utilities/Filter/findSelectedFilters';
|
||||||
import createCustomFiltersSelector from './createCustomFiltersSelector';
|
|
||||||
|
function getSortClause(sortKey, sortDirection, sortPredicates) {
|
||||||
|
if (sortPredicates && sortPredicates.hasOwnProperty(sortKey)) {
|
||||||
|
return function(item) {
|
||||||
|
return sortPredicates[sortKey](item, sortDirection);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return function(item) {
|
||||||
|
return item[sortKey];
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function filter(items, state) {
|
||||||
|
const {
|
||||||
|
selectedFilterKey,
|
||||||
|
filters,
|
||||||
|
customFilters,
|
||||||
|
filterPredicates
|
||||||
|
} = state;
|
||||||
|
|
||||||
|
if (!selectedFilterKey) {
|
||||||
|
return items;
|
||||||
|
}
|
||||||
|
|
||||||
|
const selectedFilters = findSelectedFilters(selectedFilterKey, filters, customFilters);
|
||||||
|
|
||||||
|
return _.filter(items, (item) => {
|
||||||
|
let i = 0;
|
||||||
|
let accepted = true;
|
||||||
|
|
||||||
|
while (accepted && i < selectedFilters.length) {
|
||||||
|
const {
|
||||||
|
key,
|
||||||
|
value,
|
||||||
|
type = filterTypes.EQUAL
|
||||||
|
} = selectedFilters[i];
|
||||||
|
|
||||||
|
if (filterPredicates && filterPredicates.hasOwnProperty(key)) {
|
||||||
|
const predicate = filterPredicates[key];
|
||||||
|
|
||||||
|
if (Array.isArray(value)) {
|
||||||
|
if (
|
||||||
|
type === filterTypes.NOT_CONTAINS ||
|
||||||
|
type === filterTypes.NOT_EQUAL
|
||||||
|
) {
|
||||||
|
accepted = value.every((v) => predicate(item, v, type));
|
||||||
|
} else {
|
||||||
|
accepted = value.some((v) => predicate(item, v, type));
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
accepted = predicate(item, value, type);
|
||||||
|
}
|
||||||
|
} else if (item.hasOwnProperty(key)) {
|
||||||
|
const predicate = filterTypePredicates[type];
|
||||||
|
|
||||||
|
if (Array.isArray(value)) {
|
||||||
|
if (
|
||||||
|
type === filterTypes.NOT_CONTAINS ||
|
||||||
|
type === filterTypes.NOT_EQUAL
|
||||||
|
) {
|
||||||
|
accepted = value.every((v) => predicate(item[key], v));
|
||||||
|
} else {
|
||||||
|
accepted = value.some((v) => predicate(item[key], v));
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
accepted = predicate(item[key], value);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Default to false if the filter can't be tested
|
||||||
|
accepted = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
|
||||||
|
return accepted;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function sort(items, state) {
|
||||||
|
const {
|
||||||
|
sortKey,
|
||||||
|
sortDirection,
|
||||||
|
sortPredicates,
|
||||||
|
secondarySortKey,
|
||||||
|
secondarySortDirection
|
||||||
|
} = state;
|
||||||
|
|
||||||
|
const clauses = [];
|
||||||
|
const orders = [];
|
||||||
|
|
||||||
|
clauses.push(getSortClause(sortKey, sortDirection, sortPredicates));
|
||||||
|
orders.push(sortDirection === sortDirections.ASCENDING ? 'asc' : 'desc');
|
||||||
|
|
||||||
|
if (secondarySortKey &&
|
||||||
|
secondarySortDirection &&
|
||||||
|
(sortKey !== secondarySortKey ||
|
||||||
|
sortDirection !== secondarySortDirection)) {
|
||||||
|
clauses.push(getSortClause(secondarySortKey, secondarySortDirection, sortPredicates));
|
||||||
|
orders.push(secondarySortDirection === sortDirections.ASCENDING ? 'asc' : 'desc');
|
||||||
|
}
|
||||||
|
|
||||||
|
return _.orderBy(items, clauses, orders);
|
||||||
|
}
|
||||||
|
|
||||||
|
function createCustomFiltersSelector(type, alternateType) {
|
||||||
|
return createSelector(
|
||||||
|
(state) => state.customFilters.items,
|
||||||
|
(customFilters) => {
|
||||||
|
return customFilters.filter((customFilter) => {
|
||||||
|
return customFilter.type === type || customFilter.type === alternateType;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
function createClientSideCollectionSelector(section, uiSection) {
|
function createClientSideCollectionSelector(section, uiSection) {
|
||||||
return createSelector(
|
return createSelector(
|
||||||
@@ -12,8 +127,8 @@ function createClientSideCollectionSelector(section, uiSection) {
|
|||||||
(sectionState, uiSectionState = {}, customFilters) => {
|
(sectionState, uiSectionState = {}, customFilters) => {
|
||||||
const state = Object.assign({}, sectionState, uiSectionState, { customFilters });
|
const state = Object.assign({}, sectionState, uiSectionState, { customFilters });
|
||||||
|
|
||||||
const filtered = filterCollection(state.items, state);
|
const filtered = filter(state.items, state);
|
||||||
const sorted = sortCollection(filtered, state);
|
const sorted = sort(filtered, state);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
...sectionState,
|
...sectionState,
|
||||||
|
|||||||
@@ -1,14 +0,0 @@
|
|||||||
import { createSelector } from 'reselect';
|
|
||||||
|
|
||||||
function createCustomFiltersSelector(type, alternateType) {
|
|
||||||
return createSelector(
|
|
||||||
(state) => state.customFilters.items,
|
|
||||||
(customFilters) => {
|
|
||||||
return customFilters.filter((customFilter) => {
|
|
||||||
return customFilter.type === type || customFilter.type === alternateType;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export default createCustomFiltersSelector;
|
|
||||||
@@ -1,7 +1,4 @@
|
|||||||
@define-mixin scrollbar {
|
@define-mixin scrollbar {
|
||||||
scrollbar-color: var(--scrollbarBackgroundColor) transparent;
|
|
||||||
scrollbar-width: thin;
|
|
||||||
|
|
||||||
&::-webkit-scrollbar {
|
&::-webkit-scrollbar {
|
||||||
width: 10px;
|
width: 10px;
|
||||||
height: 10px;
|
height: 10px;
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
module.exports = {
|
module.exports = {
|
||||||
pageJumpBarZIndex: 10,
|
|
||||||
modalZIndex: 1000,
|
modalZIndex: 1000,
|
||||||
popperZIndex: 2000
|
popperZIndex: 2000
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -20,11 +20,10 @@ class About extends Component {
|
|||||||
packageVersion,
|
packageVersion,
|
||||||
packageAuthor,
|
packageAuthor,
|
||||||
isNetCore,
|
isNetCore,
|
||||||
|
isMono,
|
||||||
isDocker,
|
isDocker,
|
||||||
runtimeVersion,
|
runtimeVersion,
|
||||||
migrationVersion,
|
migrationVersion,
|
||||||
databaseVersion,
|
|
||||||
databaseType,
|
|
||||||
appData,
|
appData,
|
||||||
startupPath,
|
startupPath,
|
||||||
mode,
|
mode,
|
||||||
@@ -49,6 +48,14 @@ class About extends Component {
|
|||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
isMono &&
|
||||||
|
<DescriptionListItem
|
||||||
|
title={translate('MonoVersion')}
|
||||||
|
data={runtimeVersion}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
isNetCore &&
|
isNetCore &&
|
||||||
<DescriptionListItem
|
<DescriptionListItem
|
||||||
@@ -70,11 +77,6 @@ class About extends Component {
|
|||||||
data={migrationVersion}
|
data={migrationVersion}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<DescriptionListItem
|
|
||||||
title={translate('Database')}
|
|
||||||
data={`${titleCase(databaseType)} ${databaseVersion}`}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<DescriptionListItem
|
<DescriptionListItem
|
||||||
title={translate('AppDataDirectory')}
|
title={translate('AppDataDirectory')}
|
||||||
data={appData}
|
data={appData}
|
||||||
@@ -112,11 +114,10 @@ About.propTypes = {
|
|||||||
packageVersion: PropTypes.string,
|
packageVersion: PropTypes.string,
|
||||||
packageAuthor: PropTypes.string,
|
packageAuthor: PropTypes.string,
|
||||||
isNetCore: PropTypes.bool.isRequired,
|
isNetCore: PropTypes.bool.isRequired,
|
||||||
|
isMono: PropTypes.bool.isRequired,
|
||||||
runtimeVersion: PropTypes.string.isRequired,
|
runtimeVersion: PropTypes.string.isRequired,
|
||||||
isDocker: PropTypes.bool.isRequired,
|
isDocker: PropTypes.bool.isRequired,
|
||||||
migrationVersion: PropTypes.number.isRequired,
|
migrationVersion: PropTypes.number.isRequired,
|
||||||
databaseType: PropTypes.string.isRequired,
|
|
||||||
databaseVersion: PropTypes.string.isRequired,
|
|
||||||
appData: PropTypes.string.isRequired,
|
appData: PropTypes.string.isRequired,
|
||||||
startupPath: PropTypes.string.isRequired,
|
startupPath: PropTypes.string.isRequired,
|
||||||
mode: PropTypes.string.isRequired,
|
mode: PropTypes.string.isRequired,
|
||||||
|
|||||||
@@ -1,72 +0,0 @@
|
|||||||
import _ from 'lodash';
|
|
||||||
import { filterTypePredicates, filterTypes } from 'Helpers/Props';
|
|
||||||
import findSelectedFilters from 'Utilities/Filter/findSelectedFilters';
|
|
||||||
|
|
||||||
function filterCollection(items, state) {
|
|
||||||
const {
|
|
||||||
selectedFilterKey,
|
|
||||||
filters,
|
|
||||||
customFilters,
|
|
||||||
filterPredicates
|
|
||||||
} = state;
|
|
||||||
|
|
||||||
if (!selectedFilterKey) {
|
|
||||||
return items;
|
|
||||||
}
|
|
||||||
|
|
||||||
const selectedFilters = findSelectedFilters(selectedFilterKey, filters, customFilters);
|
|
||||||
|
|
||||||
return _.filter(items, (item) => {
|
|
||||||
let i = 0;
|
|
||||||
let accepted = true;
|
|
||||||
|
|
||||||
while (accepted && i < selectedFilters.length) {
|
|
||||||
const {
|
|
||||||
key,
|
|
||||||
value,
|
|
||||||
type = filterTypes.EQUAL
|
|
||||||
} = selectedFilters[i];
|
|
||||||
|
|
||||||
if (filterPredicates && filterPredicates.hasOwnProperty(key)) {
|
|
||||||
const predicate = filterPredicates[key];
|
|
||||||
|
|
||||||
if (Array.isArray(value)) {
|
|
||||||
if (
|
|
||||||
type === filterTypes.NOT_CONTAINS ||
|
|
||||||
type === filterTypes.NOT_EQUAL
|
|
||||||
) {
|
|
||||||
accepted = value.every((v) => predicate(item, v, type));
|
|
||||||
} else {
|
|
||||||
accepted = value.some((v) => predicate(item, v, type));
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
accepted = predicate(item, value, type);
|
|
||||||
}
|
|
||||||
} else if (item.hasOwnProperty(key)) {
|
|
||||||
const predicate = filterTypePredicates[type];
|
|
||||||
|
|
||||||
if (Array.isArray(value)) {
|
|
||||||
if (
|
|
||||||
type === filterTypes.NOT_CONTAINS ||
|
|
||||||
type === filterTypes.NOT_EQUAL
|
|
||||||
) {
|
|
||||||
accepted = value.every((v) => predicate(item[key], v));
|
|
||||||
} else {
|
|
||||||
accepted = value.some((v) => predicate(item[key], v));
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
accepted = predicate(item[key], value);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// Default to false if the filter can't be tested
|
|
||||||
accepted = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
i++;
|
|
||||||
}
|
|
||||||
|
|
||||||
return accepted;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
export default filterCollection;
|
|
||||||
@@ -1,42 +0,0 @@
|
|||||||
import _ from 'lodash';
|
|
||||||
import { sortDirections } from 'Helpers/Props';
|
|
||||||
|
|
||||||
function getSortClause(sortKey, sortDirection, sortPredicates) {
|
|
||||||
if (sortPredicates && sortPredicates.hasOwnProperty(sortKey)) {
|
|
||||||
return function(item) {
|
|
||||||
return sortPredicates[sortKey](item, sortDirection);
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
return function(item) {
|
|
||||||
return item[sortKey];
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
function sortCollection(items, state) {
|
|
||||||
const {
|
|
||||||
sortKey,
|
|
||||||
sortDirection,
|
|
||||||
sortPredicates,
|
|
||||||
secondarySortKey,
|
|
||||||
secondarySortDirection
|
|
||||||
} = state;
|
|
||||||
|
|
||||||
const clauses = [];
|
|
||||||
const orders = [];
|
|
||||||
|
|
||||||
clauses.push(getSortClause(sortKey, sortDirection, sortPredicates));
|
|
||||||
orders.push(sortDirection === sortDirections.ASCENDING ? 'asc' : 'desc');
|
|
||||||
|
|
||||||
if (secondarySortKey &&
|
|
||||||
secondarySortDirection &&
|
|
||||||
(sortKey !== secondarySortKey ||
|
|
||||||
sortDirection !== secondarySortDirection)) {
|
|
||||||
clauses.push(getSortClause(secondarySortKey, secondarySortDirection, sortPredicates));
|
|
||||||
orders.push(secondarySortDirection === sortDirections.ASCENDING ? 'asc' : 'desc');
|
|
||||||
}
|
|
||||||
|
|
||||||
return _.orderBy(items, clauses, orders);
|
|
||||||
}
|
|
||||||
|
|
||||||
export default sortCollection;
|
|
||||||
@@ -1,18 +1,22 @@
|
|||||||
import createAjaxRequest from 'Utilities/createAjaxRequest';
|
import $ from 'jquery';
|
||||||
|
|
||||||
function getTranslations() {
|
function getTranslations() {
|
||||||
let localization = null;
|
let localization = null;
|
||||||
const ajaxOptions = {
|
const ajaxOptions = {
|
||||||
async: false,
|
async: false,
|
||||||
|
type: 'GET',
|
||||||
|
global: false,
|
||||||
dataType: 'json',
|
dataType: 'json',
|
||||||
url: '/localization',
|
url: `${window.Readarr.apiRoot}/localization`,
|
||||||
success: function(data) {
|
success: function(data) {
|
||||||
localization = data.Strings;
|
localization = data.Strings;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
createAjaxRequest(ajaxOptions);
|
ajaxOptions.headers = ajaxOptions.headers || {};
|
||||||
|
ajaxOptions.headers['X-Api-Key'] = window.Readarr.apiKey;
|
||||||
|
|
||||||
|
$.ajax(ajaxOptions);
|
||||||
return localization;
|
return localization;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -10,7 +10,3 @@ export function isMobile() {
|
|||||||
export function isIOS() {
|
export function isIOS() {
|
||||||
return mobileDetect.is('iOS');
|
return mobileDetect.is('iOS');
|
||||||
}
|
}
|
||||||
|
|
||||||
export function isFirefox() {
|
|
||||||
return window.navigator.userAgent.toLowerCase().indexOf('firefox/') >= 0;
|
|
||||||
}
|
|
||||||
+2
-3
@@ -33,7 +33,7 @@
|
|||||||
"@fortawesome/free-regular-svg-icons": "5.15.3",
|
"@fortawesome/free-regular-svg-icons": "5.15.3",
|
||||||
"@fortawesome/free-solid-svg-icons": "5.15.3",
|
"@fortawesome/free-solid-svg-icons": "5.15.3",
|
||||||
"@fortawesome/react-fontawesome": "0.1.14",
|
"@fortawesome/react-fontawesome": "0.1.14",
|
||||||
"@microsoft/signalr": "6.0.7",
|
"@microsoft/signalr": "6.0.5",
|
||||||
"@sentry/browser": "6.18.2",
|
"@sentry/browser": "6.18.2",
|
||||||
"@sentry/integrations": "6.18.2",
|
"@sentry/integrations": "6.18.2",
|
||||||
"ansi-colors": "4.1.1",
|
"ansi-colors": "4.1.1",
|
||||||
@@ -58,7 +58,7 @@
|
|||||||
"react-addons-shallow-compare": "15.6.3",
|
"react-addons-shallow-compare": "15.6.3",
|
||||||
"react-async-script": "1.2.0",
|
"react-async-script": "1.2.0",
|
||||||
"react-autosuggest": "10.1.0",
|
"react-autosuggest": "10.1.0",
|
||||||
"react-custom-scrollbars-2": "4.5.0",
|
"react-custom-scrollbars": "4.2.1",
|
||||||
"react-dnd": "14.0.2",
|
"react-dnd": "14.0.2",
|
||||||
"react-dnd-html5-backend": "14.0.0",
|
"react-dnd-html5-backend": "14.0.0",
|
||||||
"react-dnd-multi-backend": "6.0.2",
|
"react-dnd-multi-backend": "6.0.2",
|
||||||
@@ -109,7 +109,6 @@
|
|||||||
"eslint-plugin-filenames": "1.3.2",
|
"eslint-plugin-filenames": "1.3.2",
|
||||||
"eslint-plugin-import": "2.23.4",
|
"eslint-plugin-import": "2.23.4",
|
||||||
"eslint-plugin-react": "7.24.0",
|
"eslint-plugin-react": "7.24.0",
|
||||||
"eslint-plugin-react-hooks": "4.6.0",
|
|
||||||
"eslint-plugin-simple-import-sort": "7.0.0",
|
"eslint-plugin-simple-import-sort": "7.0.0",
|
||||||
"esprint": "3.1.0",
|
"esprint": "3.1.0",
|
||||||
"file-loader": "6.2.0",
|
"file-loader": "6.2.0",
|
||||||
|
|||||||
@@ -4,19 +4,18 @@
|
|||||||
<PackageVersion Include="AutoFixture" Version="4.17.0" />
|
<PackageVersion Include="AutoFixture" Version="4.17.0" />
|
||||||
<PackageVersion Include="coverlet.collector" Version="3.0.4-preview.27.ge7cb7c3b40" PrivateAssets="all" />
|
<PackageVersion Include="coverlet.collector" Version="3.0.4-preview.27.ge7cb7c3b40" PrivateAssets="all" />
|
||||||
<PackageVersion Include="Dapper" Version="2.0.123" />
|
<PackageVersion Include="Dapper" Version="2.0.123" />
|
||||||
<PackageVersion Include="DryIoc.dll" Version="5.2.0" />
|
<PackageVersion Include="DryIoc.dll" Version="4.8.7" />
|
||||||
<PackageVersion Include="DryIoc.Microsoft.DependencyInjection" Version="6.0.2" />
|
<PackageVersion Include="DryIoc.Microsoft.DependencyInjection" Version="5.1.0" />
|
||||||
<PackageVersion Include="Equ" Version="2.3.0" />
|
<PackageVersion Include="Equ" Version="2.3.0" />
|
||||||
<PackageVersion Include="FluentAssertions" Version="5.10.3" />
|
<PackageVersion Include="FluentAssertions" Version="5.10.3" />
|
||||||
<PackageVersion Include="Servarr.FluentMigrator.Runner" Version="3.3.2.9" />
|
<PackageVersion Include="FluentMigrator.Runner.SQLite" Version="4.0.0-alpha.289" />
|
||||||
<PackageVersion Include="Servarr.FluentMigrator.Runner.SQLite" Version="3.3.2.9" />
|
<PackageVersion Include="FluentMigrator.Runner" Version="4.0.0-alpha.289" />
|
||||||
<PackageVersion Include="Servarr.FluentMigrator.Runner.Postgres" Version="3.3.2.9" />
|
|
||||||
<PackageVersion Include="FluentValidation" Version="8.6.2" />
|
<PackageVersion Include="FluentValidation" Version="8.6.2" />
|
||||||
<PackageVersion Include="Ical.Net" Version="4.2.0" />
|
<PackageVersion Include="Ical.Net" Version="4.2.0" />
|
||||||
<PackageVersion Include="ImpromptuInterface" Version="7.0.1" />
|
<PackageVersion Include="ImpromptuInterface" Version="7.0.1" />
|
||||||
<PackageVersion Include="LazyCache" Version="2.4.0" />
|
<PackageVersion Include="LazyCache" Version="2.4.0" />
|
||||||
<PackageVersion Include="Mailkit" Version="3.3.0" />
|
<PackageVersion Include="Mailkit" Version="3.1.1" />
|
||||||
<PackageVersion Include="Microsoft.AspNetCore.SignalR.Client" Version="6.0.7" />
|
<PackageVersion Include="Microsoft.AspNetCore.SignalR.Client" Version="6.0.5" />
|
||||||
<PackageVersion Include="Microsoft.Extensions.Caching.Memory" Version="6.0.1" />
|
<PackageVersion Include="Microsoft.Extensions.Caching.Memory" Version="6.0.1" />
|
||||||
<PackageVersion Include="Microsoft.Extensions.Configuration" Version="6.0.1" />
|
<PackageVersion Include="Microsoft.Extensions.Configuration" Version="6.0.1" />
|
||||||
<PackageVersion Include="Microsoft.Extensions.DependencyInjection" Version="6.0.0" />
|
<PackageVersion Include="Microsoft.Extensions.DependencyInjection" Version="6.0.0" />
|
||||||
@@ -25,41 +24,41 @@
|
|||||||
<PackageVersion Include="Microsoft.NET.Test.Sdk" Version="17.2.0" />
|
<PackageVersion Include="Microsoft.NET.Test.Sdk" Version="17.2.0" />
|
||||||
<PackageVersion Include="Microsoft.Win32.Registry" Version="5.0.0" />
|
<PackageVersion Include="Microsoft.Win32.Registry" Version="5.0.0" />
|
||||||
<PackageVersion Include="Mono.Posix.NETStandard" Version="5.20.1.34-servarr22" />
|
<PackageVersion Include="Mono.Posix.NETStandard" Version="5.20.1.34-servarr22" />
|
||||||
|
<PackageVersion Include="MonoTorrent" Version="2.0.5" />
|
||||||
<PackageVersion Include="Moq" Version="4.17.2" />
|
<PackageVersion Include="Moq" Version="4.17.2" />
|
||||||
<PackageVersion Include="MonoTorrent" Version="2.0.6" />
|
|
||||||
<PackageVersion Include="NBuilder" Version="6.1.0" />
|
<PackageVersion Include="NBuilder" Version="6.1.0" />
|
||||||
<PackageVersion Include="Newtonsoft.Json" Version="13.0.1" />
|
<PackageVersion Include="Newtonsoft.Json" Version="13.0.1" />
|
||||||
<PackageVersion Include="NLog.Extensions.Logging" Version="5.1.0" />
|
<PackageVersion Include="NLog.Extensions.Logging" Version="1.7.4" />
|
||||||
<PackageVersion Include="NLog" Version="5.0.5" />
|
<PackageVersion Include="NLog" Version="4.7.14" />
|
||||||
<PackageVersion Include="NLog.Targets.Syslog" Version="7.0.0" />
|
<PackageVersion Include="NLog.Targets.Syslog" Version="6.0.2" />
|
||||||
<PackageVersion Include="Npgsql" Version="6.0.3" />
|
|
||||||
<PackageVersion Include="NUnit3TestAdapter" Version="4.2.1" />
|
<PackageVersion Include="NUnit3TestAdapter" Version="4.2.1" />
|
||||||
<PackageVersion Include="NUnit" Version="3.13.3" />
|
<PackageVersion Include="NUnit" Version="3.13.2" />
|
||||||
<PackageVersion Include="NunitXml.TestLogger" Version="3.0.117" />
|
<PackageVersion Include="NunitXml.TestLogger" Version="3.0.117" />
|
||||||
<PackageVersion Include="PdfSharpCore" Version="1.3.32" />
|
<PackageVersion Include="PdfSharpCore" Version="1.3.18" />
|
||||||
<PackageVersion Include="RestSharp.Serializers.SystemTextJson" Version="106.15.0" />
|
<PackageVersion Include="RestSharp.Serializers.SystemTextJson" Version="106.15.0" />
|
||||||
<PackageVersion Include="RestSharp" Version="106.15.0" />
|
<PackageVersion Include="RestSharp" Version="106.15.0" />
|
||||||
<PackageVersion Include="Selenium.Support" Version="3.141.0" />
|
<PackageVersion Include="Selenium.Support" Version="3.141.0" />
|
||||||
<PackageVersion Include="Selenium.WebDriver.ChromeDriver" Version="91.0.4472.10100" />
|
<PackageVersion Include="Selenium.WebDriver.ChromeDriver" Version="91.0.4472.10100" />
|
||||||
<PackageVersion Include="Sentry" Version="3.20.1" />
|
<PackageVersion Include="Sentry" Version="3.14.1" />
|
||||||
<PackageVersion Include="SharpZipLib" Version="1.3.3" />
|
<PackageVersion Include="SharpZipLib" Version="1.3.3" />
|
||||||
<PackageVersion Include="SixLabors.ImageSharp" Version="2.1.3" />
|
<PackageVersion Include="SixLabors.ImageSharp" Version="1.0.4" />
|
||||||
<PackageVersion Include="StyleCop.Analyzers" Version="1.1.118" />
|
<PackageVersion Include="StyleCop.Analyzers" Version="1.1.118" />
|
||||||
<PackageVersion Include="System.Buffers" Version="4.5.1" />
|
<PackageVersion Include="System.Buffers" Version="4.5.1" />
|
||||||
<PackageVersion Include="System.Configuration.ConfigurationManager" Version="6.0.0" />
|
<PackageVersion Include="System.Configuration.ConfigurationManager" Version="6.0.0" />
|
||||||
<PackageVersion Include="System.Data.SQLite.Core.Servarr" Version="1.0.115.5-18" />
|
<PackageVersion Include="System.Data.SQLite.Core.Servarr" Version="1.0.115.5-18" />
|
||||||
<PackageVersion Include="System.IO.Abstractions.TestingHelpers" Version="17.0.24" />
|
<PackageVersion Include="System.IO.Abstractions.TestingHelpers" Version="16.1.20" />
|
||||||
<PackageVersion Include="System.IO.Abstractions" Version="17.0.24" />
|
<PackageVersion Include="System.IO.Abstractions" Version="16.1.20" />
|
||||||
<PackageVersion Include="System.IO.FileSystem.AccessControl" Version="5.0.0" />
|
<PackageVersion Include="System.IO.FileSystem.AccessControl" Version="5.0.0" />
|
||||||
<PackageVersion Include="System.Memory" Version="4.5.5" />
|
<PackageVersion Include="System.Memory" Version="4.5.4" />
|
||||||
<PackageVersion Include="System.Reflection.TypeExtensions" Version="4.7.0" />
|
<PackageVersion Include="System.Reflection.TypeExtensions" Version="4.7.0" />
|
||||||
<PackageVersion Include="System.Resources.Extensions" Version="6.0.0" />
|
<PackageVersion Include="System.Resources.Extensions" Version="6.0.0" />
|
||||||
<PackageVersion Include="System.Runtime.Loader" Version="4.3.0" />
|
<PackageVersion Include="System.Runtime.Loader" Version="4.3.0" />
|
||||||
<PackageVersion Include="System.Security.Principal.Windows" Version="5.0.0" />
|
<PackageVersion Include="System.Security.Principal.Windows" Version="5.0.0" />
|
||||||
<PackageVersion Include="System.ServiceProcess.ServiceController" Version="6.0.0" />
|
<PackageVersion Include="System.ServiceProcess.ServiceController" Version="6.0.0" />
|
||||||
<PackageVersion Include="System.Text.Encoding.CodePages" Version="6.0.0" />
|
<PackageVersion Include="System.Text.Encoding.CodePages" Version="6.0.0" />
|
||||||
<PackageVersion Include="System.Text.Json" Version="6.0.5" />
|
<PackageVersion Include="System.Text.Json" Version="6.0.4" />
|
||||||
<PackageVersion Include="System.ValueTuple" Version="4.5.0" />
|
<PackageVersion Include="System.ValueTuple" Version="4.5.0" />
|
||||||
<PackageVersion Include="TagLibSharp-Lidarr" Version="2.2.0.19" />
|
<PackageVersion Include="TagLibSharp-Lidarr" Version="2.2.0.19" />
|
||||||
|
<PackageVersion Include="Unity" Version="5.11.10" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
</Project>
|
</Project>
|
||||||
|
|||||||
+6
-5
@@ -1,13 +1,13 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<configuration>
|
<configuration>
|
||||||
<packageSources>
|
<packageSources>
|
||||||
<clear />
|
<clear />
|
||||||
<add key="nuget.org" value="https://api.nuget.org/v3/index.json" />
|
<add key="nuget.org" value="https://api.nuget.org/v3/index.json" />
|
||||||
|
<add key="FluentMigrator" value="https://pkgs.dev.azure.com/fluentmigrator/fluentmigrator/_packaging/fluentmigrator/nuget/v3/index.json" />
|
||||||
<add key="dotnet-bsd-crossbuild" value="https://pkgs.dev.azure.com/Servarr/Servarr/_packaging/dotnet-bsd-crossbuild/nuget/v3/index.json" />
|
<add key="dotnet-bsd-crossbuild" value="https://pkgs.dev.azure.com/Servarr/Servarr/_packaging/dotnet-bsd-crossbuild/nuget/v3/index.json" />
|
||||||
<add key="Mono.Posix.NETStandard" value="https://pkgs.dev.azure.com/Servarr/Servarr/_packaging/Mono.Posix.NETStandard/nuget/v3/index.json" />
|
<add key="Mono.Posix.NETStandard" value="https://pkgs.dev.azure.com/Servarr/Servarr/_packaging/Mono.Posix.NETStandard/nuget/v3/index.json" />
|
||||||
<add key="SQLite" value="https://pkgs.dev.azure.com/Servarr/Servarr/_packaging/SQLite/nuget/v3/index.json" />
|
<add key="SQLite" value="https://pkgs.dev.azure.com/Servarr/Servarr/_packaging/SQLite/nuget/v3/index.json" />
|
||||||
<add key="coverlet-nightly" value="https://pkgs.dev.azure.com/Servarr/coverlet/_packaging/coverlet-nightly/nuget/v3/index.json" />
|
<add key="coverlet-nightly" value="https://pkgs.dev.azure.com/Servarr/coverlet/_packaging/coverlet-nightly/nuget/v3/index.json" />
|
||||||
<add key="FluentMigrator" value="https://pkgs.dev.azure.com/Servarr/Servarr/_packaging/FluentMigrator/nuget/v3/index.json" />
|
|
||||||
</packageSources>
|
</packageSources>
|
||||||
<packageSourceMapping>
|
<packageSourceMapping>
|
||||||
<!-- key value for <packageSource> should match key values from <packageSources> element -->
|
<!-- key value for <packageSource> should match key values from <packageSources> element -->
|
||||||
@@ -17,6 +17,10 @@
|
|||||||
<packageSource key="dotnet-bsd-crossbuild">
|
<packageSource key="dotnet-bsd-crossbuild">
|
||||||
<package pattern="*" />
|
<package pattern="*" />
|
||||||
</packageSource>
|
</packageSource>
|
||||||
|
<packageSource key="FluentMigrator">
|
||||||
|
<package pattern="FluentMigrator" />
|
||||||
|
<package pattern="FluentMigrator.*" />
|
||||||
|
</packageSource>
|
||||||
<packageSource key="Mono.Posix.NETStandard">
|
<packageSource key="Mono.Posix.NETStandard">
|
||||||
<package pattern="Mono.Posix.NETStandard" />
|
<package pattern="Mono.Posix.NETStandard" />
|
||||||
</packageSource>
|
</packageSource>
|
||||||
@@ -26,8 +30,5 @@
|
|||||||
<packageSource key="coverlet-nightly">
|
<packageSource key="coverlet-nightly">
|
||||||
<package pattern="coverlet.*" />
|
<package pattern="coverlet.*" />
|
||||||
</packageSource>
|
</packageSource>
|
||||||
<packageSource key="FluentMigrator">
|
|
||||||
<package pattern="Servarr.FluentMigrator*"/>
|
|
||||||
</packageSource>
|
|
||||||
</packageSourceMapping>
|
</packageSourceMapping>
|
||||||
</configuration>
|
</configuration>
|
||||||
|
|||||||
@@ -44,7 +44,7 @@ namespace NzbDrone.Automation.Test
|
|||||||
|
|
||||||
driver.Manage().Window.Size = new System.Drawing.Size(1920, 1080);
|
driver.Manage().Window.Size = new System.Drawing.Size(1920, 1080);
|
||||||
|
|
||||||
_runner = new NzbDroneRunner(LogManager.GetCurrentClassLogger(), null);
|
_runner = new NzbDroneRunner(LogManager.GetCurrentClassLogger());
|
||||||
_runner.KillAll();
|
_runner.KillAll();
|
||||||
_runner.Start();
|
_runner.Start();
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.IO.Abstractions;
|
|
||||||
using FluentAssertions;
|
using FluentAssertions;
|
||||||
using NUnit.Framework;
|
using NUnit.Framework;
|
||||||
using NzbDrone.Common.Disk;
|
using NzbDrone.Common.Disk;
|
||||||
@@ -11,12 +10,6 @@ namespace NzbDrone.Common.Test.DiskTests
|
|||||||
public abstract class DiskProviderFixtureBase<TSubject> : TestBase<TSubject>
|
public abstract class DiskProviderFixtureBase<TSubject> : TestBase<TSubject>
|
||||||
where TSubject : class, IDiskProvider
|
where TSubject : class, IDiskProvider
|
||||||
{
|
{
|
||||||
[SetUp]
|
|
||||||
public void BaseSetup()
|
|
||||||
{
|
|
||||||
Mocker.SetConstant<IFileSystem>(new FileSystem());
|
|
||||||
}
|
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void writealltext_should_truncate_existing()
|
public void writealltext_should_truncate_existing()
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -437,6 +437,24 @@ namespace NzbDrone.Common.Test.DiskTests
|
|||||||
Assert.Throws<IOException>(() => Subject.TransferFolder(source.FullName, destination.FullName, TransferMode.Copy));
|
Assert.Throws<IOException>(() => Subject.TransferFolder(source.FullName, destination.FullName, TransferMode.Copy));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void CopyFolder_should_not_copy_casesensitive_folder()
|
||||||
|
{
|
||||||
|
MonoOnly();
|
||||||
|
|
||||||
|
WithRealDiskProvider();
|
||||||
|
|
||||||
|
var original = GetFilledTempFolder();
|
||||||
|
var root = new DirectoryInfo(GetTempFilePath());
|
||||||
|
var source = new DirectoryInfo(root.FullName + "A/series");
|
||||||
|
var destination = new DirectoryInfo(root.FullName + "A/Series");
|
||||||
|
|
||||||
|
Subject.TransferFolder(original.FullName, source.FullName, TransferMode.Copy);
|
||||||
|
|
||||||
|
// Note: Although technically possible top copy to different case, we're not allowing it
|
||||||
|
Assert.Throws<IOException>(() => Subject.TransferFolder(source.FullName, destination.FullName, TransferMode.Copy));
|
||||||
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void CopyFolder_should_ignore_nfs_temp_file()
|
public void CopyFolder_should_ignore_nfs_temp_file()
|
||||||
{
|
{
|
||||||
@@ -522,6 +540,26 @@ namespace NzbDrone.Common.Test.DiskTests
|
|||||||
source.FullName.GetActualCasing().Should().Be(destination.FullName);
|
source.FullName.GetActualCasing().Should().Be(destination.FullName);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void MoveFolder_should_rename_casesensitive_folder()
|
||||||
|
{
|
||||||
|
MonoOnly();
|
||||||
|
|
||||||
|
WithRealDiskProvider();
|
||||||
|
|
||||||
|
var original = GetFilledTempFolder();
|
||||||
|
var root = new DirectoryInfo(GetTempFilePath());
|
||||||
|
var source = new DirectoryInfo(root.FullName + "A/series");
|
||||||
|
var destination = new DirectoryInfo(root.FullName + "A/Series");
|
||||||
|
|
||||||
|
Subject.TransferFolder(original.FullName, source.FullName, TransferMode.Copy);
|
||||||
|
|
||||||
|
Subject.TransferFolder(source.FullName, destination.FullName, TransferMode.Move);
|
||||||
|
|
||||||
|
Directory.Exists(source.FullName).Should().Be(false);
|
||||||
|
Directory.Exists(destination.FullName).Should().Be(true);
|
||||||
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void should_throw_if_destination_is_readonly()
|
public void should_throw_if_destination_is_readonly()
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.IO.Abstractions;
|
|
||||||
using FluentAssertions;
|
using FluentAssertions;
|
||||||
using NUnit.Framework;
|
using NUnit.Framework;
|
||||||
using NzbDrone.Common.Disk;
|
using NzbDrone.Common.Disk;
|
||||||
@@ -11,12 +10,6 @@ namespace NzbDrone.Common.Test.DiskTests
|
|||||||
public abstract class FreeSpaceFixtureBase<TSubject> : TestBase<TSubject>
|
public abstract class FreeSpaceFixtureBase<TSubject> : TestBase<TSubject>
|
||||||
where TSubject : class, IDiskProvider
|
where TSubject : class, IDiskProvider
|
||||||
{
|
{
|
||||||
[SetUp]
|
|
||||||
public void BaseSetup()
|
|
||||||
{
|
|
||||||
Mocker.SetConstant<IFileSystem>(new FileSystem());
|
|
||||||
}
|
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void should_get_free_space_for_folder()
|
public void should_get_free_space_for_folder()
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -207,7 +207,6 @@ namespace NzbDrone.Common.Test.Http
|
|||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
[Platform(Exclude = "MacOsX", Reason = "Azure agent update prevents brotli on OSX")]
|
|
||||||
public void should_execute_get_using_brotli()
|
public void should_execute_get_using_brotli()
|
||||||
{
|
{
|
||||||
var request = new HttpRequest($"https://{_httpBinHost}/brotli");
|
var request = new HttpRequest($"https://{_httpBinHost}/brotli");
|
||||||
@@ -308,6 +307,11 @@ namespace NzbDrone.Common.Test.Http
|
|||||||
[Test]
|
[Test]
|
||||||
public void should_follow_redirects_to_https()
|
public void should_follow_redirects_to_https()
|
||||||
{
|
{
|
||||||
|
if (typeof(TDispatcher) == typeof(ManagedHttpDispatcher) && PlatformInfo.IsMono)
|
||||||
|
{
|
||||||
|
Assert.Ignore("Will fail on tls1.2 via managed dispatcher, ignore.");
|
||||||
|
}
|
||||||
|
|
||||||
var request = new HttpRequestBuilder($"https://{_httpBinHost}/redirect-to")
|
var request = new HttpRequestBuilder($"https://{_httpBinHost}/redirect-to")
|
||||||
.AddQueryParam("url", $"https://readarr.com/")
|
.AddQueryParam("url", $"https://readarr.com/")
|
||||||
.Build();
|
.Build();
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
using FluentAssertions;
|
using FluentAssertions;
|
||||||
using NUnit.Framework;
|
using NUnit.Framework;
|
||||||
using NzbDrone.Common.Instrumentation;
|
using NzbDrone.Common.Instrumentation;
|
||||||
|
|
||||||
@@ -60,8 +60,6 @@ namespace NzbDrone.Common.Test.InstrumentationTests
|
|||||||
[TestCase("Hardlink '/home/mySecret/Downloads/abs.mkv' to '/media/abc.mkv' failed.")]
|
[TestCase("Hardlink '/home/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("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("/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 ***")]
|
|
||||||
[TestCase(@"[Info] MigrationController: *** Migrating Database=readarr-main;Host=postgres14;Username=mySecret;Password=mySecret;Port=5432;token=mySecret;Enlist=False&username=mySecret;mypassword=mySecret;mypass=shouldkeep1;test_token=mySecret;password=123%@%_@!#^#@;use_password=mySecret;get_token=shouldkeep2;usetoken=shouldkeep3;passwrd=mySecret;")]
|
|
||||||
|
|
||||||
// Announce URLs (passkeys) Magnet & Tracker
|
// Announce URLs (passkeys) Magnet & Tracker
|
||||||
[TestCase(@"magnet_uri"":""magnet:?xt=urn:btih:9pr04sgkillroyimaveql2tyu8xyui&dn=&tr=https%3a%2f%2fxxx.yyy%2f9pr04sg601233210imaveql2tyu8xyui%2fannounce""}")]
|
[TestCase(@"magnet_uri"":""magnet:?xt=urn:btih:9pr04sgkillroyimaveql2tyu8xyui&dn=&tr=https%3a%2f%2fxxx.yyy%2f9pr04sg601233210imaveql2tyu8xyui%2fannounce""}")]
|
||||||
@@ -72,38 +70,14 @@ namespace NzbDrone.Common.Test.InstrumentationTests
|
|||||||
[TestCase(@"tracker"":""https://xxx.yyy/tracker.php/9pr04sg601233210imaveql2tyu8xyui/announce""}")]
|
[TestCase(@"tracker"":""https://xxx.yyy/tracker.php/9pr04sg601233210imaveql2tyu8xyui/announce""}")]
|
||||||
[TestCase(@"tracker"":""https://xxx.yyy/announce/9pr04sg601233210imaveql2tyu8xyui""}")]
|
[TestCase(@"tracker"":""https://xxx.yyy/announce/9pr04sg601233210imaveql2tyu8xyui""}")]
|
||||||
[TestCase(@"tracker"":""https://xxx.yyy/announce.php?passkey=9pr04sg601233210imaveql2tyu8xyui""}")]
|
[TestCase(@"tracker"":""https://xxx.yyy/announce.php?passkey=9pr04sg601233210imaveql2tyu8xyui""}")]
|
||||||
[TestCase(@"tracker"":""http://xxx.yyy/announce.php?passkey=9pr04sg601233210imaveql2tyu8xyui"",""info"":""http://xxx.yyy/info?a=b""")]
|
|
||||||
|
|
||||||
// Notifiarr
|
|
||||||
[TestCase(@"https://xxx.yyy/api/v1/notification/readarr/9pr04sg6-0123-3210-imav-eql2tyu8xyui")]
|
|
||||||
|
|
||||||
// Discord
|
|
||||||
[TestCase(@"https://discord.com/api/webhooks/mySecret")]
|
|
||||||
[TestCase(@"https://discord.com/api/webhooks/mySecret/01233210")]
|
|
||||||
|
|
||||||
public void should_clean_message(string message)
|
public void should_clean_message(string message)
|
||||||
{
|
{
|
||||||
var cleansedMessage = CleanseLogMessage.Cleanse(message);
|
var cleansedMessage = CleanseLogMessage.Cleanse(message);
|
||||||
|
|
||||||
cleansedMessage.Should().NotContain("mySecret");
|
cleansedMessage.Should().NotContain("mySecret");
|
||||||
cleansedMessage.Should().NotContain("123%@%_@!#^#@");
|
|
||||||
cleansedMessage.Should().NotContain("01233210");
|
cleansedMessage.Should().NotContain("01233210");
|
||||||
}
|
}
|
||||||
|
|
||||||
[TestCase(@"[Info] MigrationController: *** Migrating Database=radarr-main;Host=postgres14;Username=mySecret;Password=mySecret;Port=5432;token=mySecret;Enlist=False&username=mySecret;mypassword=mySecret;mypass=shouldkeep1;test_token=mySecret;password=123%@%_@!#^#@;use_password=mySecret;get_token=shouldkeep2;usetoken=shouldkeep3;passwrd=mySecret;")]
|
|
||||||
public void should_keep_message(string message)
|
|
||||||
{
|
|
||||||
var cleansedMessage = CleanseLogMessage.Cleanse(message);
|
|
||||||
|
|
||||||
cleansedMessage.Should().NotContain("mySecret");
|
|
||||||
cleansedMessage.Should().NotContain("123%@%_@!#^#@");
|
|
||||||
cleansedMessage.Should().NotContain("01233210");
|
|
||||||
|
|
||||||
cleansedMessage.Should().Contain("shouldkeep1");
|
|
||||||
cleansedMessage.Should().Contain("shouldkeep2");
|
|
||||||
cleansedMessage.Should().Contain("shouldkeep3");
|
|
||||||
}
|
|
||||||
|
|
||||||
//GoodReads
|
//GoodReads
|
||||||
[TestCase(@"{""signatureMethod"": ""hmacSha1"",""signatureTreatment"": ""escaped"",""type"": ""protectedResource"",""method"": ""GET"",""token"": ""mytoken"",""tokenSecret"": ""mytokensecret"",""requestUrl"": ""https://www.goodreads.com/review/list.xml"",""parameters"": { ""_nc"": ""1"", ""v"": ""2"", ""id"": ""999999999"", ""shelf"": ""currently-reading"", ""per_page"": ""200"", ""page"": ""1""}")]
|
[TestCase(@"{""signatureMethod"": ""hmacSha1"",""signatureTreatment"": ""escaped"",""type"": ""protectedResource"",""method"": ""GET"",""token"": ""mytoken"",""tokenSecret"": ""mytokensecret"",""requestUrl"": ""https://www.goodreads.com/review/list.xml"",""parameters"": { ""_nc"": ""1"", ""v"": ""2"", ""id"": ""999999999"", ""shelf"": ""currently-reading"", ""per_page"": ""200"", ""page"": ""1""}")]
|
||||||
[TestCase(@"https://www.goodreads.com/series/311911?key=1234530f422f4aacb6b301233210aaaa&_nc=1&format=xml")]
|
[TestCase(@"https://www.goodreads.com/series/311911?key=1234530f422f4aacb6b301233210aaaa&_nc=1&format=xml")]
|
||||||
|
|||||||
@@ -279,7 +279,7 @@ namespace NzbDrone.Common.Test
|
|||||||
[Test]
|
[Test]
|
||||||
public void GetUpdateClientExePath()
|
public void GetUpdateClientExePath()
|
||||||
{
|
{
|
||||||
GetIAppDirectoryInfo().GetUpdateClientExePath().Should().BeEquivalentTo(@"C:\Temp\readarr_update\Readarr.Update".AsOsAgnostic().ProcessNameToExe());
|
GetIAppDirectoryInfo().GetUpdateClientExePath(PlatformType.DotNet).Should().BeEquivalentTo(@"C:\Temp\readarr_update\Readarr.Update.exe".AsOsAgnostic());
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.ComponentModel;
|
using System.ComponentModel;
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
@@ -171,7 +171,7 @@ namespace NzbDrone.Common.Test
|
|||||||
var processStarted = new ManualResetEventSlim();
|
var processStarted = new ManualResetEventSlim();
|
||||||
|
|
||||||
string suffix;
|
string suffix;
|
||||||
if (OsInfo.IsWindows)
|
if (OsInfo.IsWindows || PlatformInfo.IsMono)
|
||||||
{
|
{
|
||||||
suffix = ".exe";
|
suffix = ".exe";
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,13 +4,11 @@ using DryIoc.Microsoft.DependencyInjection;
|
|||||||
using FluentAssertions;
|
using FluentAssertions;
|
||||||
using Microsoft.Extensions.DependencyInjection;
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
using Microsoft.Extensions.Hosting;
|
using Microsoft.Extensions.Hosting;
|
||||||
using Microsoft.Extensions.Options;
|
|
||||||
using Moq;
|
using Moq;
|
||||||
using NUnit.Framework;
|
using NUnit.Framework;
|
||||||
using NzbDrone.Common.Composition.Extensions;
|
using NzbDrone.Common.Composition.Extensions;
|
||||||
using NzbDrone.Common.EnvironmentInfo;
|
using NzbDrone.Common.EnvironmentInfo;
|
||||||
using NzbDrone.Common.Instrumentation.Extensions;
|
using NzbDrone.Common.Instrumentation.Extensions;
|
||||||
using NzbDrone.Core.Datastore;
|
|
||||||
using NzbDrone.Core.Datastore.Extensions;
|
using NzbDrone.Core.Datastore.Extensions;
|
||||||
using NzbDrone.Core.Lifecycle;
|
using NzbDrone.Core.Lifecycle;
|
||||||
using NzbDrone.Core.Messaging.Events;
|
using NzbDrone.Core.Messaging.Events;
|
||||||
@@ -31,8 +29,7 @@ namespace NzbDrone.Common.Test
|
|||||||
.AddDummyDatabase()
|
.AddDummyDatabase()
|
||||||
.AddStartupContext(new StartupContext("first", "second"));
|
.AddStartupContext(new StartupContext("first", "second"));
|
||||||
|
|
||||||
container.RegisterInstance(new Mock<IHostLifetime>().Object);
|
container.RegisterInstance<IHostLifetime>(new Mock<IHostLifetime>().Object);
|
||||||
container.RegisterInstance(new Mock<IOptions<PostgresOptions>>().Object);
|
|
||||||
|
|
||||||
var serviceProvider = container.GetServiceProvider();
|
var serviceProvider = container.GetServiceProvider();
|
||||||
serviceProvider.GetRequiredService<IAppFolderFactory>().Register();
|
serviceProvider.GetRequiredService<IAppFolderFactory>().Register();
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using NzbDrone.Common.EnvironmentInfo;
|
using NzbDrone.Common.EnvironmentInfo;
|
||||||
|
|
||||||
@@ -24,8 +24,7 @@ namespace NzbDrone.Common.Disk
|
|||||||
"/boot",
|
"/boot",
|
||||||
"/lib",
|
"/lib",
|
||||||
"/sbin",
|
"/sbin",
|
||||||
"/proc",
|
"/proc"
|
||||||
"/usr/bin"
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,17 @@
|
|||||||
using System;
|
using System;
|
||||||
|
using System.Reflection;
|
||||||
|
using System.Text.RegularExpressions;
|
||||||
|
using Microsoft.Win32;
|
||||||
|
|
||||||
namespace NzbDrone.Common.EnvironmentInfo
|
namespace NzbDrone.Common.EnvironmentInfo
|
||||||
{
|
{
|
||||||
|
public enum PlatformType
|
||||||
|
{
|
||||||
|
DotNet = 0,
|
||||||
|
Mono = 1,
|
||||||
|
NetCore = 2
|
||||||
|
}
|
||||||
|
|
||||||
public interface IPlatformInfo
|
public interface IPlatformInfo
|
||||||
{
|
{
|
||||||
Version Version { get; }
|
Version Version { get; }
|
||||||
@@ -9,18 +19,38 @@ namespace NzbDrone.Common.EnvironmentInfo
|
|||||||
|
|
||||||
public class PlatformInfo : IPlatformInfo
|
public class PlatformInfo : IPlatformInfo
|
||||||
{
|
{
|
||||||
|
private static readonly Regex MonoVersionRegex = new Regex(@"(?<=\W|^)(?<version>\d+\.\d+(\.\d+)?(\.\d+)?)(?=\W)", RegexOptions.Compiled | RegexOptions.IgnoreCase);
|
||||||
|
|
||||||
|
private static PlatformType _platform;
|
||||||
private static Version _version;
|
private static Version _version;
|
||||||
|
|
||||||
static PlatformInfo()
|
static PlatformInfo()
|
||||||
{
|
{
|
||||||
|
_platform = PlatformType.NetCore;
|
||||||
_version = Environment.Version;
|
_version = Environment.Version;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static PlatformType Platform => _platform;
|
||||||
|
public static bool IsMono => Platform == PlatformType.Mono;
|
||||||
|
public static bool IsDotNet => Platform == PlatformType.DotNet;
|
||||||
|
public static bool IsNetCore => Platform == PlatformType.NetCore;
|
||||||
|
|
||||||
public static string PlatformName
|
public static string PlatformName
|
||||||
{
|
{
|
||||||
get
|
get
|
||||||
{
|
{
|
||||||
return ".NET";
|
if (IsDotNet)
|
||||||
|
{
|
||||||
|
return ".NET";
|
||||||
|
}
|
||||||
|
else if (IsMono)
|
||||||
|
{
|
||||||
|
return "Mono";
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return ".NET Core";
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -30,5 +60,107 @@ namespace NzbDrone.Common.EnvironmentInfo
|
|||||||
{
|
{
|
||||||
return _version;
|
return _version;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static Version GetMonoVersion()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var type = Type.GetType("Mono.Runtime");
|
||||||
|
|
||||||
|
if (type != null)
|
||||||
|
{
|
||||||
|
var displayNameMethod = type.GetMethod("GetDisplayName", BindingFlags.NonPublic | BindingFlags.Static);
|
||||||
|
if (displayNameMethod != null)
|
||||||
|
{
|
||||||
|
var displayName = displayNameMethod.Invoke(null, null).ToString();
|
||||||
|
var versionMatch = MonoVersionRegex.Match(displayName);
|
||||||
|
|
||||||
|
if (versionMatch.Success)
|
||||||
|
{
|
||||||
|
return new Version(versionMatch.Groups["version"].Value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Console.WriteLine("Couldnt get Mono version: " + ex.ToString());
|
||||||
|
}
|
||||||
|
|
||||||
|
return new Version();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Version GetDotNetVersion()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
const string subkey = @"SOFTWARE\Microsoft\NET Framework Setup\NDP\v4\Full\";
|
||||||
|
using (var ndpKey = RegistryKey.OpenBaseKey(RegistryHive.LocalMachine, RegistryView.Registry32).OpenSubKey(subkey))
|
||||||
|
{
|
||||||
|
if (ndpKey == null)
|
||||||
|
{
|
||||||
|
return new Version(4, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
var releaseKey = (int)ndpKey.GetValue("Release");
|
||||||
|
|
||||||
|
if (releaseKey >= 528040)
|
||||||
|
{
|
||||||
|
return new Version(4, 8, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (releaseKey >= 461808)
|
||||||
|
{
|
||||||
|
return new Version(4, 7, 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (releaseKey >= 461308)
|
||||||
|
{
|
||||||
|
return new Version(4, 7, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (releaseKey >= 460798)
|
||||||
|
{
|
||||||
|
return new Version(4, 7);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (releaseKey >= 394802)
|
||||||
|
{
|
||||||
|
return new Version(4, 6, 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (releaseKey >= 394254)
|
||||||
|
{
|
||||||
|
return new Version(4, 6, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (releaseKey >= 393295)
|
||||||
|
{
|
||||||
|
return new Version(4, 6);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (releaseKey >= 379893)
|
||||||
|
{
|
||||||
|
return new Version(4, 5, 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (releaseKey >= 378675)
|
||||||
|
{
|
||||||
|
return new Version(4, 5, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (releaseKey >= 378389)
|
||||||
|
{
|
||||||
|
return new Version(4, 5);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Console.WriteLine("Couldnt get .NET framework version: " + ex.ToString());
|
||||||
|
}
|
||||||
|
|
||||||
|
return new Version(4, 0);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -240,9 +240,9 @@ namespace NzbDrone.Common.Extensions
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static string ProcessNameToExe(this string processName)
|
public static string ProcessNameToExe(this string processName, PlatformType runtime)
|
||||||
{
|
{
|
||||||
if (OsInfo.IsWindows)
|
if (OsInfo.IsWindows || runtime != PlatformType.NetCore)
|
||||||
{
|
{
|
||||||
processName += ".exe";
|
processName += ".exe";
|
||||||
}
|
}
|
||||||
@@ -250,6 +250,11 @@ namespace NzbDrone.Common.Extensions
|
|||||||
return processName;
|
return processName;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static string ProcessNameToExe(this string processName)
|
||||||
|
{
|
||||||
|
return processName.ProcessNameToExe(PlatformInfo.Platform);
|
||||||
|
}
|
||||||
|
|
||||||
public static string GetLongestCommonPath(this List<string> paths)
|
public static string GetLongestCommonPath(this List<string> paths)
|
||||||
{
|
{
|
||||||
var firstPath = paths.First();
|
var firstPath = paths.First();
|
||||||
@@ -342,9 +347,9 @@ namespace NzbDrone.Common.Extensions
|
|||||||
return Path.Combine(GetUpdatePackageFolder(appFolderInfo), UPDATE_CLIENT_FOLDER_NAME);
|
return Path.Combine(GetUpdatePackageFolder(appFolderInfo), UPDATE_CLIENT_FOLDER_NAME);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static string GetUpdateClientExePath(this IAppFolderInfo appFolderInfo)
|
public static string GetUpdateClientExePath(this IAppFolderInfo appFolderInfo, PlatformType runtime)
|
||||||
{
|
{
|
||||||
return Path.Combine(GetUpdateSandboxFolder(appFolderInfo), UPDATE_CLIENT_EXE_NAME).ProcessNameToExe();
|
return Path.Combine(GetUpdateSandboxFolder(appFolderInfo), UPDATE_CLIENT_EXE_NAME).ProcessNameToExe(runtime);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static string GetDatabase(this IAppFolderInfo appFolderInfo)
|
public static string GetDatabase(this IAppFolderInfo appFolderInfo)
|
||||||
|
|||||||
@@ -55,8 +55,7 @@ namespace NzbDrone.Common.Http
|
|||||||
StatusCode == HttpStatusCode.Found ||
|
StatusCode == HttpStatusCode.Found ||
|
||||||
StatusCode == HttpStatusCode.TemporaryRedirect ||
|
StatusCode == HttpStatusCode.TemporaryRedirect ||
|
||||||
StatusCode == HttpStatusCode.RedirectMethod ||
|
StatusCode == HttpStatusCode.RedirectMethod ||
|
||||||
StatusCode == HttpStatusCode.SeeOther ||
|
StatusCode == HttpStatusCode.SeeOther;
|
||||||
StatusCode == HttpStatusCode.PermanentRedirect;
|
|
||||||
|
|
||||||
public string[] GetCookieHeaders()
|
public string[] GetCookieHeaders()
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Net;
|
using System.Net;
|
||||||
using System.Text.RegularExpressions;
|
using System.Text.RegularExpressions;
|
||||||
@@ -11,14 +11,13 @@ namespace NzbDrone.Common.Instrumentation
|
|||||||
private static readonly Regex[] CleansingRules = new[]
|
private static readonly Regex[] CleansingRules = new[]
|
||||||
{
|
{
|
||||||
// Url
|
// 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(@"(?<=\?|&|: )((?: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(@"(?<=\?|&)[^=]*?(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)(?<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(@"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(@"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(@"/fetch/[a-z0-9]{32}/(?<secret>[a-z0-9]{32})", RegexOptions.Compiled),
|
||||||
new Regex(@"getnzb.*?(?<=\?|&)(r)=(?<secret>[^&=]+?)(?= |&|$)", RegexOptions.Compiled | RegexOptions.IgnoreCase),
|
new Regex(@"getnzb.*?(?<=\?|&)(r)=(?<secret>[^&=]+?)(?= |&|$)", RegexOptions.Compiled | RegexOptions.IgnoreCase),
|
||||||
new Regex(@"\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
|
// 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"),
|
new Regex(@"announce(\.php)?(/|%2f|%3fpasskey%3d)(?<secret>[a-z0-9]{16,})|(?<secret>[a-z0-9]{16,})(/|%2f)announce"),
|
||||||
@@ -47,14 +46,7 @@ namespace NzbDrone.Common.Instrumentation
|
|||||||
new Regex(@"(?<=\?|&)(authkey|torrent_pass)=(?<secret>[^&=]+?)(?=""|&|$)", RegexOptions.Compiled | RegexOptions.IgnoreCase),
|
new Regex(@"(?<=\?|&)(authkey|torrent_pass)=(?<secret>[^&=]+?)(?=""|&|$)", RegexOptions.Compiled | RegexOptions.IgnoreCase),
|
||||||
|
|
||||||
// Good Reads
|
// Good Reads
|
||||||
new Regex(@"(?<=""(token|tokensecret)"":\s)""(?<secret>[^""]+?)""", RegexOptions.Compiled | RegexOptions.IgnoreCase),
|
new Regex(@"(?<=""(token|tokensecret)"":\s)""(?<secret>[^""]+?)""", RegexOptions.Compiled | RegexOptions.IgnoreCase)
|
||||||
|
|
||||||
// Webhooks
|
|
||||||
// Notifiarr
|
|
||||||
new Regex(@"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)
|
|
||||||
};
|
};
|
||||||
|
|
||||||
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 Regex(@"(?:Auth-\w+(?<!Failure|Unauthorized) ip|from) (\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})", RegexOptions.Compiled);
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
using System.Collections.Generic;
|
using System.Linq;
|
||||||
using System.Linq;
|
|
||||||
using NLog;
|
using NLog;
|
||||||
|
using NLog.Fluent;
|
||||||
|
|
||||||
namespace NzbDrone.Common.Instrumentation.Extensions
|
namespace NzbDrone.Common.Instrumentation.Extensions
|
||||||
{
|
{
|
||||||
@@ -8,46 +8,47 @@ namespace NzbDrone.Common.Instrumentation.Extensions
|
|||||||
{
|
{
|
||||||
public static readonly Logger SentryLogger = LogManager.GetLogger("Sentry");
|
public static readonly Logger SentryLogger = LogManager.GetLogger("Sentry");
|
||||||
|
|
||||||
public static LogEventBuilder SentryFingerprint(this LogEventBuilder logBuilder, params string[] fingerprint)
|
public static LogBuilder SentryFingerprint(this LogBuilder logBuilder, params string[] fingerprint)
|
||||||
{
|
{
|
||||||
return logBuilder.Property("Sentry", fingerprint);
|
return logBuilder.Property("Sentry", fingerprint);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static LogEventBuilder WriteSentryDebug(this LogEventBuilder logBuilder, params string[] fingerprint)
|
public static LogBuilder WriteSentryDebug(this LogBuilder logBuilder, params string[] fingerprint)
|
||||||
{
|
{
|
||||||
return LogSentryMessage(logBuilder, LogLevel.Debug, fingerprint);
|
return LogSentryMessage(logBuilder, LogLevel.Debug, fingerprint);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static LogEventBuilder WriteSentryInfo(this LogEventBuilder logBuilder, params string[] fingerprint)
|
public static LogBuilder WriteSentryInfo(this LogBuilder logBuilder, params string[] fingerprint)
|
||||||
{
|
{
|
||||||
return LogSentryMessage(logBuilder, LogLevel.Info, fingerprint);
|
return LogSentryMessage(logBuilder, LogLevel.Info, fingerprint);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static LogEventBuilder WriteSentryWarn(this LogEventBuilder logBuilder, params string[] fingerprint)
|
public static LogBuilder WriteSentryWarn(this LogBuilder logBuilder, params string[] fingerprint)
|
||||||
{
|
{
|
||||||
return LogSentryMessage(logBuilder, LogLevel.Warn, fingerprint);
|
return LogSentryMessage(logBuilder, LogLevel.Warn, fingerprint);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static LogEventBuilder WriteSentryError(this LogEventBuilder logBuilder, params string[] fingerprint)
|
public static LogBuilder WriteSentryError(this LogBuilder logBuilder, params string[] fingerprint)
|
||||||
{
|
{
|
||||||
return LogSentryMessage(logBuilder, LogLevel.Error, fingerprint);
|
return LogSentryMessage(logBuilder, LogLevel.Error, fingerprint);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static LogEventBuilder LogSentryMessage(LogEventBuilder logBuilder, LogLevel level, string[] fingerprint)
|
private static LogBuilder LogSentryMessage(LogBuilder logBuilder, LogLevel level, string[] fingerprint)
|
||||||
{
|
{
|
||||||
SentryLogger.ForLogEvent(level)
|
SentryLogger.Log(level)
|
||||||
.CopyLogEvent(logBuilder.LogEvent)
|
.CopyLogEvent(logBuilder.LogEventInfo)
|
||||||
.SentryFingerprint(fingerprint)
|
.SentryFingerprint(fingerprint)
|
||||||
.Log();
|
.Write();
|
||||||
|
|
||||||
return logBuilder.Property<string>("Sentry", null);
|
return logBuilder.Property("Sentry", null);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static LogEventBuilder CopyLogEvent(this LogEventBuilder logBuilder, LogEventInfo logEvent)
|
private static LogBuilder CopyLogEvent(this LogBuilder logBuilder, LogEventInfo logEvent)
|
||||||
{
|
{
|
||||||
return logBuilder.TimeStamp(logEvent.TimeStamp)
|
return logBuilder.LoggerName(logEvent.LoggerName)
|
||||||
|
.TimeStamp(logEvent.TimeStamp)
|
||||||
.Message(logEvent.Message, logEvent.Parameters)
|
.Message(logEvent.Message, logEvent.Parameters)
|
||||||
.Properties(logEvent.Properties.Select(p => new KeyValuePair<string, object>(p.Key.ToString(), p.Value)))
|
.Properties(logEvent.Properties.ToDictionary(v => v.Key, v => v.Value))
|
||||||
.Exception(logEvent.Exception);
|
.Exception(logEvent.Exception);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -38,6 +38,16 @@ namespace NzbDrone.Common.Instrumentation
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (PlatformInfo.IsMono)
|
||||||
|
{
|
||||||
|
if ((exception is TypeInitializationException && exception.InnerException is DllNotFoundException) ||
|
||||||
|
exception is DllNotFoundException)
|
||||||
|
{
|
||||||
|
Logger.Debug(exception, "Minor Fail: " + exception.Message);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Console.WriteLine("EPIC FAIL: {0}", exception);
|
Console.WriteLine("EPIC FAIL: {0}", exception);
|
||||||
Logger.Fatal(exception, "EPIC FAIL.");
|
Logger.Fatal(exception, "EPIC FAIL.");
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,15 +1,13 @@
|
|||||||
using System.Text;
|
using NLog;
|
||||||
using NLog;
|
|
||||||
using NLog.Targets;
|
using NLog.Targets;
|
||||||
|
|
||||||
namespace NzbDrone.Common.Instrumentation
|
namespace NzbDrone.Common.Instrumentation
|
||||||
{
|
{
|
||||||
public class NzbDroneFileTarget : FileTarget
|
public class NzbDroneFileTarget : FileTarget
|
||||||
{
|
{
|
||||||
protected override void RenderFormattedMessage(LogEventInfo logEvent, StringBuilder target)
|
protected override string GetFormattedMessage(LogEventInfo logEvent)
|
||||||
{
|
{
|
||||||
var result = CleanseLogMessage.Cleanse(Layout.Render(logEvent));
|
return CleanseLogMessage.Cleanse(Layout.Render(logEvent));
|
||||||
target.Append(result);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -34,8 +34,6 @@ namespace NzbDrone.Common.Instrumentation
|
|||||||
|
|
||||||
var appFolderInfo = new AppFolderInfo(startupContext);
|
var appFolderInfo = new AppFolderInfo(startupContext);
|
||||||
|
|
||||||
RegisterGlobalFilters();
|
|
||||||
|
|
||||||
if (Debugger.IsAttached)
|
if (Debugger.IsAttached)
|
||||||
{
|
{
|
||||||
RegisterDebugger();
|
RegisterDebugger();
|
||||||
@@ -103,16 +101,6 @@ namespace NzbDrone.Common.Instrumentation
|
|||||||
LogManager.Configuration.LoggingRules.Add(loggingRule);
|
LogManager.Configuration.LoggingRules.Add(loggingRule);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void RegisterGlobalFilters()
|
|
||||||
{
|
|
||||||
LogManager.Setup().LoadConfiguration(c =>
|
|
||||||
{
|
|
||||||
c.ForLogger("Microsoft.Hosting.Lifetime*").WriteToNil(LogLevel.Info);
|
|
||||||
c.ForLogger("System*").WriteToNil(LogLevel.Warn);
|
|
||||||
c.ForLogger("Microsoft*").WriteToNil(LogLevel.Warn);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void RegisterConsole()
|
private static void RegisterConsole()
|
||||||
{
|
{
|
||||||
var level = LogLevel.Trace;
|
var level = LogLevel.Trace;
|
||||||
|
|||||||
@@ -109,6 +109,13 @@ namespace NzbDrone.Common.Instrumentation.Sentry
|
|||||||
o.Debug = false;
|
o.Debug = false;
|
||||||
o.DiagnosticLevel = SentryLevel.Debug;
|
o.DiagnosticLevel = SentryLevel.Debug;
|
||||||
o.Release = BuildInfo.Release;
|
o.Release = BuildInfo.Release;
|
||||||
|
if (PlatformInfo.IsMono)
|
||||||
|
{
|
||||||
|
// Mono 6.0 broke GzipStream.WriteAsync
|
||||||
|
// TODO: Check specific version
|
||||||
|
o.RequestBodyCompressionLevel = System.IO.Compression.CompressionLevel.NoCompression;
|
||||||
|
}
|
||||||
|
|
||||||
o.BeforeSend = x => SentryCleanser.CleanseEvent(x);
|
o.BeforeSend = x => SentryCleanser.CleanseEvent(x);
|
||||||
o.BeforeBreadcrumb = x => SentryCleanser.CleanseBreadcrumb(x);
|
o.BeforeBreadcrumb = x => SentryCleanser.CleanseBreadcrumb(x);
|
||||||
o.Environment = BuildInfo.Branch;
|
o.Environment = BuildInfo.Branch;
|
||||||
@@ -151,6 +158,14 @@ namespace NzbDrone.Common.Instrumentation.Sentry
|
|||||||
SentrySdk.ConfigureScope(scope =>
|
SentrySdk.ConfigureScope(scope =>
|
||||||
{
|
{
|
||||||
scope.SetTag("is_docker", $"{osInfo.IsDocker}");
|
scope.SetTag("is_docker", $"{osInfo.IsDocker}");
|
||||||
|
|
||||||
|
if (osInfo.Name != null && PlatformInfo.IsMono)
|
||||||
|
{
|
||||||
|
// Sentry auto-detection of non-Windows platforms isn't that accurate on certain devices.
|
||||||
|
scope.Contexts.OperatingSystem.Name = osInfo.Name.FirstCharToUpper();
|
||||||
|
scope.Contexts.OperatingSystem.RawDescription = osInfo.FullName;
|
||||||
|
scope.Contexts.OperatingSystem.Version = osInfo.Version.ToString();
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -206,11 +221,7 @@ namespace NzbDrone.Common.Instrumentation.Sentry
|
|||||||
if (ex != null)
|
if (ex != null)
|
||||||
{
|
{
|
||||||
fingerPrint.Add(ex.GetType().FullName);
|
fingerPrint.Add(ex.GetType().FullName);
|
||||||
if (ex.TargetSite != null)
|
fingerPrint.Add(ex.TargetSite.ToString());
|
||||||
{
|
|
||||||
fingerPrint.Add(ex.TargetSite.ToString());
|
|
||||||
}
|
|
||||||
|
|
||||||
if (ex.InnerException != null)
|
if (ex.InnerException != null)
|
||||||
{
|
{
|
||||||
fingerPrint.Add(ex.InnerException.GetType().FullName);
|
fingerPrint.Add(ex.InnerException.GetType().FullName);
|
||||||
|
|||||||
@@ -127,18 +127,7 @@ namespace NzbDrone.Common.Processes
|
|||||||
try
|
try
|
||||||
{
|
{
|
||||||
_logger.Trace("Setting environment variable '{0}' to '{1}'", environmentVariable.Key, environmentVariable.Value);
|
_logger.Trace("Setting environment variable '{0}' to '{1}'", environmentVariable.Key, environmentVariable.Value);
|
||||||
|
startInfo.EnvironmentVariables.Add(environmentVariable.Key.ToString(), environmentVariable.Value.ToString());
|
||||||
var key = environmentVariable.Key.ToString();
|
|
||||||
var value = environmentVariable.Value?.ToString();
|
|
||||||
|
|
||||||
if (startInfo.EnvironmentVariables.ContainsKey(key))
|
|
||||||
{
|
|
||||||
startInfo.EnvironmentVariables[key] = value;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
startInfo.EnvironmentVariables.Add(key, value);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
catch (Exception e)
|
catch (Exception e)
|
||||||
{
|
{
|
||||||
@@ -377,6 +366,11 @@ namespace NzbDrone.Common.Processes
|
|||||||
|
|
||||||
private (string Path, string Args) GetPathAndArgs(string path, string args)
|
private (string Path, string Args) GetPathAndArgs(string path, string args)
|
||||||
{
|
{
|
||||||
|
if (PlatformInfo.IsMono && path.EndsWith(".exe", StringComparison.InvariantCultureIgnoreCase))
|
||||||
|
{
|
||||||
|
return ("mono", $"--debug {path} {args}");
|
||||||
|
}
|
||||||
|
|
||||||
if (OsInfo.IsWindows && path.EndsWith(".bat", StringComparison.InvariantCultureIgnoreCase))
|
if (OsInfo.IsWindows && path.EndsWith(".bat", StringComparison.InvariantCultureIgnoreCase))
|
||||||
{
|
{
|
||||||
return ("cmd.exe", $"/c {path} {args}");
|
return ("cmd.exe", $"/c {path} {args}");
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ namespace NzbDrone.Core.Test.Datastore
|
|||||||
public void SingleOrDefault_should_return_null_on_empty_db()
|
public void SingleOrDefault_should_return_null_on_empty_db()
|
||||||
{
|
{
|
||||||
Mocker.Resolve<IDatabase>()
|
Mocker.Resolve<IDatabase>()
|
||||||
.OpenConnection().Query<Author>("SELECT * FROM \"Authors\"")
|
.OpenConnection().Query<Author>("SELECT * FROM Authors")
|
||||||
.SingleOrDefault(c => c.CleanName == "SomeTitle")
|
.SingleOrDefault(c => c.CleanName == "SomeTitle")
|
||||||
.Should()
|
.Should()
|
||||||
.BeNull();
|
.BeNull();
|
||||||
|
|||||||
@@ -78,7 +78,7 @@ namespace NzbDrone.Core.Test.Datastore
|
|||||||
public void should_lazy_load_author_for_trackfile()
|
public void should_lazy_load_author_for_trackfile()
|
||||||
{
|
{
|
||||||
var db = Mocker.Resolve<IDatabase>();
|
var db = Mocker.Resolve<IDatabase>();
|
||||||
var tracks = db.Query<BookFile>(new SqlBuilder(db.DatabaseType)).ToList();
|
var tracks = db.Query<BookFile>(new SqlBuilder()).ToList();
|
||||||
|
|
||||||
Assert.IsNotEmpty(tracks);
|
Assert.IsNotEmpty(tracks);
|
||||||
foreach (var track in tracks)
|
foreach (var track in tracks)
|
||||||
@@ -94,7 +94,7 @@ namespace NzbDrone.Core.Test.Datastore
|
|||||||
public void should_lazy_load_trackfile_if_not_joined()
|
public void should_lazy_load_trackfile_if_not_joined()
|
||||||
{
|
{
|
||||||
var db = Mocker.Resolve<IDatabase>();
|
var db = Mocker.Resolve<IDatabase>();
|
||||||
var tracks = db.Query<Book>(new SqlBuilder(db.DatabaseType)).ToList();
|
var tracks = db.Query<Book>(new SqlBuilder()).ToList();
|
||||||
|
|
||||||
foreach (var track in tracks)
|
foreach (var track in tracks)
|
||||||
{
|
{
|
||||||
@@ -109,7 +109,7 @@ namespace NzbDrone.Core.Test.Datastore
|
|||||||
{
|
{
|
||||||
var db = Mocker.Resolve<IDatabase>();
|
var db = Mocker.Resolve<IDatabase>();
|
||||||
var files = MediaFileRepository.Query(db,
|
var files = MediaFileRepository.Query(db,
|
||||||
new SqlBuilder(db.DatabaseType)
|
new SqlBuilder()
|
||||||
.Join<BookFile, Edition>((t, a) => t.EditionId == a.Id)
|
.Join<BookFile, Edition>((t, a) => t.EditionId == a.Id)
|
||||||
.Join<Edition, Book>((e, b) => e.BookId == b.Id)
|
.Join<Edition, Book>((e, b) => e.BookId == b.Id)
|
||||||
.Join<Book, Author>((book, author) => book.AuthorMetadataId == author.AuthorMetadataId)
|
.Join<Book, Author>((book, author) => book.AuthorMetadataId == author.AuthorMetadataId)
|
||||||
|
|||||||
+8
-8
@@ -11,9 +11,9 @@ using NzbDrone.Core.Test.Framework;
|
|||||||
namespace NzbDrone.Core.Test.Datastore
|
namespace NzbDrone.Core.Test.Datastore
|
||||||
{
|
{
|
||||||
[TestFixture]
|
[TestFixture]
|
||||||
public class WhereBuilderSqliteFixture : CoreTest
|
public class WhereBuilderFixture : CoreTest
|
||||||
{
|
{
|
||||||
private WhereBuilderSqlite _subject;
|
private WhereBuilder _subject;
|
||||||
|
|
||||||
[OneTimeSetUp]
|
[OneTimeSetUp]
|
||||||
public void MapTables()
|
public void MapTables()
|
||||||
@@ -22,14 +22,14 @@ namespace NzbDrone.Core.Test.Datastore
|
|||||||
Mocker.Resolve<DbFactory>();
|
Mocker.Resolve<DbFactory>();
|
||||||
}
|
}
|
||||||
|
|
||||||
private WhereBuilderSqlite Where(Expression<Func<Author, bool>> filter)
|
private WhereBuilder Where(Expression<Func<Author, bool>> filter)
|
||||||
{
|
{
|
||||||
return new WhereBuilderSqlite(filter, true, 0);
|
return new WhereBuilder(filter, true, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
private WhereBuilderSqlite WhereMetadata(Expression<Func<AuthorMetadata, bool>> filter)
|
private WhereBuilder WhereMetadata(Expression<Func<AuthorMetadata, bool>> filter)
|
||||||
{
|
{
|
||||||
return new WhereBuilderSqlite(filter, true, 0);
|
return new WhereBuilder(filter, true, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
@@ -76,7 +76,7 @@ namespace NzbDrone.Core.Test.Datastore
|
|||||||
public void where_throws_without_concrete_condition_if_requiresConcreteCondition()
|
public void where_throws_without_concrete_condition_if_requiresConcreteCondition()
|
||||||
{
|
{
|
||||||
Expression<Func<Author, Author, bool>> filter = (x, y) => x.Id == y.Id;
|
Expression<Func<Author, Author, bool>> filter = (x, y) => x.Id == y.Id;
|
||||||
_subject = new WhereBuilderSqlite(filter, true, 0);
|
_subject = new WhereBuilder(filter, true, 0);
|
||||||
Assert.Throws<InvalidOperationException>(() => _subject.ToString());
|
Assert.Throws<InvalidOperationException>(() => _subject.ToString());
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -84,7 +84,7 @@ namespace NzbDrone.Core.Test.Datastore
|
|||||||
public void where_allows_abstract_condition_if_not_requiresConcreteCondition()
|
public void where_allows_abstract_condition_if_not_requiresConcreteCondition()
|
||||||
{
|
{
|
||||||
Expression<Func<Author, Author, bool>> filter = (x, y) => x.Id == y.Id;
|
Expression<Func<Author, Author, bool>> filter = (x, y) => x.Id == y.Id;
|
||||||
_subject = new WhereBuilderSqlite(filter, false, 0);
|
_subject = new WhereBuilder(filter, false, 0);
|
||||||
_subject.ToString().Should().Be($"(\"Authors\".\"Id\" = \"Authors\".\"Id\")");
|
_subject.ToString().Should().Be($"(\"Authors\".\"Id\" = \"Authors\".\"Id\")");
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1,211 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Linq.Expressions;
|
|
||||||
using FluentAssertions;
|
|
||||||
using NUnit.Framework;
|
|
||||||
using NzbDrone.Core.Books;
|
|
||||||
using NzbDrone.Core.Datastore;
|
|
||||||
using NzbDrone.Core.Test.Framework;
|
|
||||||
|
|
||||||
namespace NzbDrone.Core.Test.Datastore
|
|
||||||
{
|
|
||||||
[TestFixture]
|
|
||||||
public class WhereBuilderPostgresFixture : CoreTest
|
|
||||||
{
|
|
||||||
private WhereBuilderPostgres _subject;
|
|
||||||
|
|
||||||
[OneTimeSetUp]
|
|
||||||
public void MapTables()
|
|
||||||
{
|
|
||||||
// Generate table mapping
|
|
||||||
Mocker.Resolve<DbFactory>();
|
|
||||||
}
|
|
||||||
|
|
||||||
private WhereBuilderPostgres Where(Expression<Func<Author, bool>> filter)
|
|
||||||
{
|
|
||||||
return new WhereBuilderPostgres(filter, true, 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
private WhereBuilderPostgres WhereMetadata(Expression<Func<AuthorMetadata, bool>> filter)
|
|
||||||
{
|
|
||||||
return new WhereBuilderPostgres(filter, true, 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
[Test]
|
|
||||||
public void postgres_where_equal_const()
|
|
||||||
{
|
|
||||||
_subject = Where(x => x.Id == 10);
|
|
||||||
|
|
||||||
_subject.ToString().Should().Be($"(\"Authors\".\"Id\" = @Clause1_P1)");
|
|
||||||
_subject.Parameters.Get<int>("Clause1_P1").Should().Be(10);
|
|
||||||
}
|
|
||||||
|
|
||||||
[Test]
|
|
||||||
public void postgres_where_equal_variable()
|
|
||||||
{
|
|
||||||
var id = 10;
|
|
||||||
_subject = Where(x => x.Id == id);
|
|
||||||
|
|
||||||
_subject.ToString().Should().Be($"(\"Authors\".\"Id\" = @Clause1_P1)");
|
|
||||||
_subject.Parameters.Get<int>("Clause1_P1").Should().Be(id);
|
|
||||||
}
|
|
||||||
|
|
||||||
[Test]
|
|
||||||
public void postgres_where_equal_property()
|
|
||||||
{
|
|
||||||
var author = new Author { Id = 10 };
|
|
||||||
_subject = Where(x => x.Id == author.Id);
|
|
||||||
|
|
||||||
_subject.Parameters.ParameterNames.Should().HaveCount(1);
|
|
||||||
_subject.ToString().Should().Be($"(\"Authors\".\"Id\" = @Clause1_P1)");
|
|
||||||
_subject.Parameters.Get<int>("Clause1_P1").Should().Be(author.Id);
|
|
||||||
}
|
|
||||||
|
|
||||||
[Test]
|
|
||||||
public void postgres_where_equal_joined_property()
|
|
||||||
{
|
|
||||||
_subject = Where(x => x.QualityProfile.Value.Id == 1);
|
|
||||||
|
|
||||||
_subject.Parameters.ParameterNames.Should().HaveCount(1);
|
|
||||||
_subject.ToString().Should().Be($"(\"QualityProfiles\".\"Id\" = @Clause1_P1)");
|
|
||||||
_subject.Parameters.Get<int>("Clause1_P1").Should().Be(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
[Test]
|
|
||||||
public void postgres_where_throws_without_concrete_condition_if_requiresConcreteCondition()
|
|
||||||
{
|
|
||||||
Expression<Func<Author, Author, bool>> filter = (x, y) => x.Id == y.Id;
|
|
||||||
_subject = new WhereBuilderPostgres(filter, true, 0);
|
|
||||||
Assert.Throws<InvalidOperationException>(() => _subject.ToString());
|
|
||||||
}
|
|
||||||
|
|
||||||
[Test]
|
|
||||||
public void postgres_where_allows_abstract_condition_if_not_requiresConcreteCondition()
|
|
||||||
{
|
|
||||||
Expression<Func<Author, Author, bool>> filter = (x, y) => x.Id == y.Id;
|
|
||||||
_subject = new WhereBuilderPostgres(filter, false, 0);
|
|
||||||
_subject.ToString().Should().Be($"(\"Authors\".\"Id\" = \"Authors\".\"Id\")");
|
|
||||||
}
|
|
||||||
|
|
||||||
[Test]
|
|
||||||
public void postgres_where_string_is_null()
|
|
||||||
{
|
|
||||||
_subject = Where(x => x.CleanName == null);
|
|
||||||
|
|
||||||
_subject.ToString().Should().Be($"(\"Authors\".\"CleanName\" IS NULL)");
|
|
||||||
}
|
|
||||||
|
|
||||||
[Test]
|
|
||||||
public void postgres_where_string_is_null_value()
|
|
||||||
{
|
|
||||||
string cleanName = null;
|
|
||||||
_subject = Where(x => x.CleanName == cleanName);
|
|
||||||
|
|
||||||
_subject.ToString().Should().Be($"(\"Authors\".\"CleanName\" IS NULL)");
|
|
||||||
}
|
|
||||||
|
|
||||||
[Test]
|
|
||||||
public void postgres_where_equal_null_property()
|
|
||||||
{
|
|
||||||
var author = new Author { CleanName = null };
|
|
||||||
_subject = Where(x => x.CleanName == author.CleanName);
|
|
||||||
|
|
||||||
_subject.ToString().Should().Be($"(\"Authors\".\"CleanName\" IS NULL)");
|
|
||||||
}
|
|
||||||
|
|
||||||
[Test]
|
|
||||||
public void postgres_where_column_contains_string()
|
|
||||||
{
|
|
||||||
var test = "small";
|
|
||||||
_subject = Where(x => x.CleanName.Contains(test));
|
|
||||||
|
|
||||||
_subject.ToString().Should().Be($"(\"Authors\".\"CleanName\" ILIKE '%' || @Clause1_P1 || '%')");
|
|
||||||
_subject.Parameters.Get<string>("Clause1_P1").Should().Be(test);
|
|
||||||
}
|
|
||||||
|
|
||||||
[Test]
|
|
||||||
public void postgres_where_string_contains_column()
|
|
||||||
{
|
|
||||||
var test = "small";
|
|
||||||
_subject = Where(x => test.Contains(x.CleanName));
|
|
||||||
|
|
||||||
_subject.ToString().Should().Be($"(@Clause1_P1 ILIKE '%' || \"Authors\".\"CleanName\" || '%')");
|
|
||||||
_subject.Parameters.Get<string>("Clause1_P1").Should().Be(test);
|
|
||||||
}
|
|
||||||
|
|
||||||
[Test]
|
|
||||||
public void postgres_where_column_starts_with_string()
|
|
||||||
{
|
|
||||||
var test = "small";
|
|
||||||
_subject = Where(x => x.CleanName.StartsWith(test));
|
|
||||||
|
|
||||||
_subject.ToString().Should().Be($"(\"Authors\".\"CleanName\" ILIKE @Clause1_P1 || '%')");
|
|
||||||
_subject.Parameters.Get<string>("Clause1_P1").Should().Be(test);
|
|
||||||
}
|
|
||||||
|
|
||||||
[Test]
|
|
||||||
public void postgres_where_column_ends_with_string()
|
|
||||||
{
|
|
||||||
var test = "small";
|
|
||||||
_subject = Where(x => x.CleanName.EndsWith(test));
|
|
||||||
|
|
||||||
_subject.ToString().Should().Be($"(\"Authors\".\"CleanName\" ILIKE '%' || @Clause1_P1)");
|
|
||||||
_subject.Parameters.Get<string>("Clause1_P1").Should().Be(test);
|
|
||||||
}
|
|
||||||
|
|
||||||
[Test]
|
|
||||||
public void postgres_where_in_list()
|
|
||||||
{
|
|
||||||
var list = new List<int> { 1, 2, 3 };
|
|
||||||
_subject = Where(x => list.Contains(x.Id));
|
|
||||||
|
|
||||||
_subject.ToString().Should().Be($"(\"Authors\".\"Id\" = ANY (('{{1, 2, 3}}')))");
|
|
||||||
}
|
|
||||||
|
|
||||||
[Test]
|
|
||||||
public void postgres_where_in_list_2()
|
|
||||||
{
|
|
||||||
var list = new List<int> { 1, 2, 3 };
|
|
||||||
_subject = Where(x => x.CleanName == "test" && list.Contains(x.Id));
|
|
||||||
|
|
||||||
_subject.ToString().Should().Be($"((\"Authors\".\"CleanName\" = @Clause1_P1) AND (\"Authors\".\"Id\" = ANY (('{{1, 2, 3}}'))))");
|
|
||||||
}
|
|
||||||
|
|
||||||
[Test]
|
|
||||||
public void postgres_where_in_string_list()
|
|
||||||
{
|
|
||||||
var list = new List<string> { "first", "second", "third" };
|
|
||||||
|
|
||||||
_subject = Where(x => list.Contains(x.CleanName));
|
|
||||||
|
|
||||||
_subject.ToString().Should().Be($"(\"Authors\".\"CleanName\" = ANY (@Clause1_P1))");
|
|
||||||
}
|
|
||||||
|
|
||||||
[Test]
|
|
||||||
public void enum_as_int()
|
|
||||||
{
|
|
||||||
_subject = WhereMetadata(x => x.Status == AuthorStatusType.Continuing);
|
|
||||||
|
|
||||||
_subject.ToString().Should().Be($"(\"AuthorMetadata\".\"Status\" = @Clause1_P1)");
|
|
||||||
}
|
|
||||||
|
|
||||||
[Test]
|
|
||||||
public void enum_in_list()
|
|
||||||
{
|
|
||||||
var allowed = new List<AuthorStatusType> { AuthorStatusType.Continuing, AuthorStatusType.Ended };
|
|
||||||
_subject = WhereMetadata(x => allowed.Contains(x.Status));
|
|
||||||
|
|
||||||
_subject.ToString().Should().Be($"(\"AuthorMetadata\".\"Status\" = ANY (@Clause1_P1))");
|
|
||||||
}
|
|
||||||
|
|
||||||
[Test]
|
|
||||||
public void enum_in_array()
|
|
||||||
{
|
|
||||||
var allowed = new AuthorStatusType[] { AuthorStatusType.Continuing, AuthorStatusType.Ended };
|
|
||||||
_subject = WhereMetadata(x => allowed.Contains(x.Status));
|
|
||||||
|
|
||||||
_subject.ToString().Should().Be($"(\"AuthorMetadata\".\"Status\" = ANY (@Clause1_P1))");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,4 +1,3 @@
|
|||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using FizzWare.NBuilder;
|
using FizzWare.NBuilder;
|
||||||
@@ -6,7 +5,6 @@ using FluentAssertions;
|
|||||||
using Moq;
|
using Moq;
|
||||||
using NUnit.Framework;
|
using NUnit.Framework;
|
||||||
using NzbDrone.Core.Books;
|
using NzbDrone.Core.Books;
|
||||||
using NzbDrone.Core.Configuration;
|
|
||||||
using NzbDrone.Core.DecisionEngine.Specifications;
|
using NzbDrone.Core.DecisionEngine.Specifications;
|
||||||
using NzbDrone.Core.MediaFiles;
|
using NzbDrone.Core.MediaFiles;
|
||||||
using NzbDrone.Core.Parser.Model;
|
using NzbDrone.Core.Parser.Model;
|
||||||
@@ -246,89 +244,5 @@ namespace NzbDrone.Core.Test.DecisionEngineTests
|
|||||||
.Should()
|
.Should()
|
||||||
.BeFalse();
|
.BeFalse();
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
|
||||||
public void should_return_true_when_repacks_are_not_preferred()
|
|
||||||
{
|
|
||||||
Mocker.GetMock<IConfigService>()
|
|
||||||
.Setup(s => s.DownloadPropersAndRepacks)
|
|
||||||
.Returns(ProperDownloadTypes.DoNotPrefer);
|
|
||||||
|
|
||||||
_trackFiles.Select(c =>
|
|
||||||
{
|
|
||||||
c.ReleaseGroup = "";
|
|
||||||
return c;
|
|
||||||
}).ToList();
|
|
||||||
|
|
||||||
_trackFiles.Select(c =>
|
|
||||||
{
|
|
||||||
c.Quality = new QualityModel(Quality.FLAC);
|
|
||||||
return c;
|
|
||||||
}).ToList();
|
|
||||||
|
|
||||||
var remoteAlbum = Builder<RemoteBook>.CreateNew()
|
|
||||||
.With(e => e.ParsedBookInfo = _parsedBookInfo)
|
|
||||||
.With(e => e.Books = _books)
|
|
||||||
.Build();
|
|
||||||
|
|
||||||
Subject.IsSatisfiedBy(remoteAlbum, null).Accepted.Should().BeTrue();
|
|
||||||
}
|
|
||||||
|
|
||||||
[Test]
|
|
||||||
public void should_return_true_when_repack_but_auto_download_repacks_is_true()
|
|
||||||
{
|
|
||||||
Mocker.GetMock<IConfigService>()
|
|
||||||
.Setup(s => s.DownloadPropersAndRepacks)
|
|
||||||
.Returns(ProperDownloadTypes.PreferAndUpgrade);
|
|
||||||
|
|
||||||
_parsedBookInfo.Quality.Revision.IsRepack = true;
|
|
||||||
|
|
||||||
_trackFiles.Select(c =>
|
|
||||||
{
|
|
||||||
c.ReleaseGroup = "Readarr";
|
|
||||||
return c;
|
|
||||||
}).ToList();
|
|
||||||
|
|
||||||
_trackFiles.Select(c =>
|
|
||||||
{
|
|
||||||
c.Quality = new QualityModel(Quality.FLAC);
|
|
||||||
return c;
|
|
||||||
}).ToList();
|
|
||||||
|
|
||||||
var remoteAlbum = Builder<RemoteBook>.CreateNew()
|
|
||||||
.With(e => e.ParsedBookInfo = _parsedBookInfo)
|
|
||||||
.With(e => e.Books = _books)
|
|
||||||
.Build();
|
|
||||||
|
|
||||||
Subject.IsSatisfiedBy(remoteAlbum, null).Accepted.Should().BeTrue();
|
|
||||||
}
|
|
||||||
|
|
||||||
[Test]
|
|
||||||
public void should_return_false_when_repack_but_auto_download_repacks_is_false()
|
|
||||||
{
|
|
||||||
Mocker.GetMock<IConfigService>()
|
|
||||||
.Setup(s => s.DownloadPropersAndRepacks)
|
|
||||||
.Returns(ProperDownloadTypes.DoNotUpgrade);
|
|
||||||
|
|
||||||
_parsedBookInfo.Quality.Revision.IsRepack = true;
|
|
||||||
|
|
||||||
_trackFiles.Select(c =>
|
|
||||||
{
|
|
||||||
c.ReleaseGroup = "Readarr";
|
|
||||||
return c;
|
|
||||||
}).ToList();
|
|
||||||
_trackFiles.Select(c =>
|
|
||||||
{
|
|
||||||
c.Quality = new QualityModel(Quality.FLAC);
|
|
||||||
return c;
|
|
||||||
}).ToList();
|
|
||||||
|
|
||||||
var remoteAlbum = Builder<RemoteBook>.CreateNew()
|
|
||||||
.With(e => e.ParsedBookInfo = _parsedBookInfo)
|
|
||||||
.With(e => e.Books = _books)
|
|
||||||
.Build();
|
|
||||||
|
|
||||||
Subject.IsSatisfiedBy(remoteAlbum, null).Accepted.Should().BeFalse();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,18 +1,12 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Data.SQLite;
|
using System.Data.SQLite;
|
||||||
using System.IO;
|
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
using Microsoft.Extensions.Logging.Abstractions;
|
using Microsoft.Extensions.Logging.Abstractions;
|
||||||
using Microsoft.Extensions.Options;
|
|
||||||
using Npgsql;
|
|
||||||
using NUnit.Framework;
|
using NUnit.Framework;
|
||||||
using NzbDrone.Common.Extensions;
|
|
||||||
using NzbDrone.Core.Configuration;
|
|
||||||
using NzbDrone.Core.Datastore;
|
using NzbDrone.Core.Datastore;
|
||||||
using NzbDrone.Core.Datastore.Migration.Framework;
|
using NzbDrone.Core.Datastore.Migration.Framework;
|
||||||
using NzbDrone.Test.Common.Datastore;
|
|
||||||
|
|
||||||
namespace NzbDrone.Core.Test.Framework
|
namespace NzbDrone.Core.Test.Framework
|
||||||
{
|
{
|
||||||
@@ -53,7 +47,6 @@ namespace NzbDrone.Core.Test.Framework
|
|||||||
public abstract class DbTest : CoreTest
|
public abstract class DbTest : CoreTest
|
||||||
{
|
{
|
||||||
private ITestDatabase _db;
|
private ITestDatabase _db;
|
||||||
private DatabaseType _databaseType;
|
|
||||||
|
|
||||||
protected virtual MigrationType MigrationType => MigrationType.Main;
|
protected virtual MigrationType MigrationType => MigrationType.Main;
|
||||||
|
|
||||||
@@ -72,7 +65,8 @@ namespace NzbDrone.Core.Test.Framework
|
|||||||
|
|
||||||
protected virtual ITestDatabase WithTestDb(MigrationContext migrationContext)
|
protected virtual ITestDatabase WithTestDb(MigrationContext migrationContext)
|
||||||
{
|
{
|
||||||
var database = CreateDatabase(migrationContext);
|
var factory = Mocker.Resolve<DbFactory>();
|
||||||
|
var database = factory.Create(migrationContext);
|
||||||
Mocker.SetConstant(database);
|
Mocker.SetConstant(database);
|
||||||
|
|
||||||
switch (MigrationType)
|
switch (MigrationType)
|
||||||
@@ -104,65 +98,6 @@ namespace NzbDrone.Core.Test.Framework
|
|||||||
return testDb;
|
return testDb;
|
||||||
}
|
}
|
||||||
|
|
||||||
private IDatabase CreateDatabase(MigrationContext migrationContext)
|
|
||||||
{
|
|
||||||
if (_databaseType == DatabaseType.PostgreSQL)
|
|
||||||
{
|
|
||||||
CreatePostgresDb();
|
|
||||||
}
|
|
||||||
|
|
||||||
var factory = Mocker.Resolve<DbFactory>();
|
|
||||||
|
|
||||||
// If a special migration test or log migration then create new
|
|
||||||
if (migrationContext.BeforeMigration != null || _databaseType == DatabaseType.PostgreSQL)
|
|
||||||
{
|
|
||||||
return factory.Create(migrationContext);
|
|
||||||
}
|
|
||||||
|
|
||||||
return CreateSqliteDatabase(factory, migrationContext);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void CreatePostgresDb()
|
|
||||||
{
|
|
||||||
var options = Mocker.Resolve<IOptions<PostgresOptions>>().Value;
|
|
||||||
PostgresDatabase.Create(options, MigrationType);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void DropPostgresDb()
|
|
||||||
{
|
|
||||||
var options = Mocker.Resolve<IOptions<PostgresOptions>>().Value;
|
|
||||||
PostgresDatabase.Drop(options, MigrationType);
|
|
||||||
}
|
|
||||||
|
|
||||||
private IDatabase CreateSqliteDatabase(IDbFactory factory, MigrationContext migrationContext)
|
|
||||||
{
|
|
||||||
// Otherwise try to use a cached migrated db
|
|
||||||
var cachedDb = SqliteDatabase.GetCachedDb(migrationContext.MigrationType);
|
|
||||||
var testDb = GetTestSqliteDb(migrationContext.MigrationType);
|
|
||||||
if (File.Exists(cachedDb))
|
|
||||||
{
|
|
||||||
TestLogger.Info($"Using cached initial database {cachedDb}");
|
|
||||||
File.Copy(cachedDb, testDb);
|
|
||||||
return factory.Create(migrationContext);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
var db = factory.Create(migrationContext);
|
|
||||||
GC.Collect();
|
|
||||||
GC.WaitForPendingFinalizers();
|
|
||||||
SQLiteConnection.ClearAllPools();
|
|
||||||
|
|
||||||
TestLogger.Info("Caching database");
|
|
||||||
File.Copy(testDb, cachedDb);
|
|
||||||
return db;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private string GetTestSqliteDb(MigrationType type)
|
|
||||||
{
|
|
||||||
return type == MigrationType.Main ? TestFolderInfo.GetDatabase() : TestFolderInfo.GetLogDatabase();
|
|
||||||
}
|
|
||||||
|
|
||||||
protected virtual void SetupLogging()
|
protected virtual void SetupLogging()
|
||||||
{
|
{
|
||||||
Mocker.SetConstant<ILoggerProvider>(NullLoggerProvider.Instance);
|
Mocker.SetConstant<ILoggerProvider>(NullLoggerProvider.Instance);
|
||||||
@@ -173,13 +108,6 @@ namespace NzbDrone.Core.Test.Framework
|
|||||||
WithTempAsAppPath();
|
WithTempAsAppPath();
|
||||||
SetupLogging();
|
SetupLogging();
|
||||||
|
|
||||||
// populate the possible postgres options
|
|
||||||
var postgresOptions = PostgresDatabase.GetTestOptions();
|
|
||||||
_databaseType = postgresOptions.Host.IsNotNullOrWhiteSpace() ? DatabaseType.PostgreSQL : DatabaseType.SQLite;
|
|
||||||
|
|
||||||
// Set up remaining container services
|
|
||||||
Mocker.SetConstant(Options.Create(postgresOptions));
|
|
||||||
Mocker.SetConstant<IConfigFileProvider>(Mocker.Resolve<ConfigFileProvider>());
|
|
||||||
Mocker.SetConstant<IConnectionStringFactory>(Mocker.Resolve<ConnectionStringFactory>());
|
Mocker.SetConstant<IConnectionStringFactory>(Mocker.Resolve<ConnectionStringFactory>());
|
||||||
Mocker.SetConstant<IMigrationController>(Mocker.Resolve<MigrationController>());
|
Mocker.SetConstant<IMigrationController>(Mocker.Resolve<MigrationController>());
|
||||||
|
|
||||||
@@ -199,19 +127,12 @@ namespace NzbDrone.Core.Test.Framework
|
|||||||
// Make sure there are no lingering connections. (When this happens it means we haven't disposed something properly)
|
// Make sure there are no lingering connections. (When this happens it means we haven't disposed something properly)
|
||||||
GC.Collect();
|
GC.Collect();
|
||||||
GC.WaitForPendingFinalizers();
|
GC.WaitForPendingFinalizers();
|
||||||
|
|
||||||
SQLiteConnection.ClearAllPools();
|
SQLiteConnection.ClearAllPools();
|
||||||
NpgsqlConnection.ClearAllPools();
|
|
||||||
|
|
||||||
if (TestFolderInfo != null)
|
if (TestFolderInfo != null)
|
||||||
{
|
{
|
||||||
DeleteTempFolder(TestFolderInfo.AppDataFolder);
|
DeleteTempFolder(TestFolderInfo.AppDataFolder);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (_databaseType == DatabaseType.PostgreSQL)
|
|
||||||
{
|
|
||||||
DropPostgresDb();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,8 +1,7 @@
|
|||||||
using System.IO.Abstractions;
|
using System.IO.Abstractions.TestingHelpers;
|
||||||
using System.IO.Abstractions.TestingHelpers;
|
|
||||||
using NUnit.Framework;
|
using NUnit.Framework;
|
||||||
using NzbDrone.Common.Disk;
|
using NzbDrone.Common.Disk;
|
||||||
using NzbDrone.Test.Common.AutoMoq;
|
using Unity.Resolution;
|
||||||
|
|
||||||
namespace NzbDrone.Core.Test.Framework
|
namespace NzbDrone.Core.Test.Framework
|
||||||
{
|
{
|
||||||
@@ -15,9 +14,12 @@ namespace NzbDrone.Core.Test.Framework
|
|||||||
[SetUp]
|
[SetUp]
|
||||||
public void FileSystemTestSetup()
|
public void FileSystemTestSetup()
|
||||||
{
|
{
|
||||||
FileSystem = (MockFileSystem)Mocker.Resolve<IFileSystem>(FileSystemType.Mock);
|
FileSystem = new MockFileSystem();
|
||||||
|
|
||||||
DiskProvider = Mocker.Resolve<IDiskProvider>(FileSystemType.Mock);
|
DiskProvider = Mocker.Resolve<IDiskProvider>("ActualDiskProvider", new ResolverOverride[]
|
||||||
|
{
|
||||||
|
new ParameterOverride("fileSystem", FileSystem)
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -23,7 +23,6 @@ namespace NzbDrone.Core.Test.Framework
|
|||||||
where T : ModelBase, new();
|
where T : ModelBase, new();
|
||||||
IDirectDataMapper GetDirectDataMapper();
|
IDirectDataMapper GetDirectDataMapper();
|
||||||
IDbConnection OpenConnection();
|
IDbConnection OpenConnection();
|
||||||
DatabaseType DatabaseType { get; }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public class TestDatabase : ITestDatabase
|
public class TestDatabase : ITestDatabase
|
||||||
@@ -31,8 +30,6 @@ namespace NzbDrone.Core.Test.Framework
|
|||||||
private readonly IDatabase _dbConnection;
|
private readonly IDatabase _dbConnection;
|
||||||
private readonly IEventAggregator _eventAggregator;
|
private readonly IEventAggregator _eventAggregator;
|
||||||
|
|
||||||
public DatabaseType DatabaseType => _dbConnection.DatabaseType;
|
|
||||||
|
|
||||||
public TestDatabase(IDatabase dbConnection)
|
public TestDatabase(IDatabase dbConnection)
|
||||||
{
|
{
|
||||||
_eventAggregator = new Mock<IEventAggregator>().Object;
|
_eventAggregator = new Mock<IEventAggregator>().Object;
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ namespace NzbDrone.Core.Test.IndexerSearchTests
|
|||||||
.Setup(s => s.GetAuthor(It.IsAny<int>()))
|
.Setup(s => s.GetAuthor(It.IsAny<int>()))
|
||||||
.Returns(_author);
|
.Returns(_author);
|
||||||
|
|
||||||
Mocker.GetMock<ISearchForReleases>()
|
Mocker.GetMock<ISearchForNzb>()
|
||||||
.Setup(s => s.AuthorSearch(_author.Id, false, true, false))
|
.Setup(s => s.AuthorSearch(_author.Id, false, true, false))
|
||||||
.Returns(new List<DownloadDecision>());
|
.Returns(new List<DownloadDecision>());
|
||||||
|
|
||||||
@@ -45,7 +45,7 @@ namespace NzbDrone.Core.Test.IndexerSearchTests
|
|||||||
|
|
||||||
Subject.Execute(new AuthorSearchCommand { AuthorId = _author.Id, Trigger = CommandTrigger.Manual });
|
Subject.Execute(new AuthorSearchCommand { AuthorId = _author.Id, Trigger = CommandTrigger.Manual });
|
||||||
|
|
||||||
Mocker.GetMock<ISearchForReleases>()
|
Mocker.GetMock<ISearchForNzb>()
|
||||||
.Verify(v => v.AuthorSearch(_author.Id, false, true, false),
|
.Verify(v => v.AuthorSearch(_author.Id, false, true, false),
|
||||||
Times.Exactly(_author.Books.Value.Count(s => s.Monitored)));
|
Times.Exactly(_author.Books.Value.Count(s => s.Monitored)));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,6 +16,11 @@ namespace NzbDrone.Core.Test.MediaCoverTests
|
|||||||
[SetUp]
|
[SetUp]
|
||||||
public void SetUp()
|
public void SetUp()
|
||||||
{
|
{
|
||||||
|
if (PlatformInfo.IsMono && PlatformInfo.GetVersion() < new Version(5, 8))
|
||||||
|
{
|
||||||
|
Assert.Inconclusive("Not supported on Mono < 5.8");
|
||||||
|
}
|
||||||
|
|
||||||
Mocker.GetMock<IDiskProvider>()
|
Mocker.GetMock<IDiskProvider>()
|
||||||
.Setup(v => v.FileExists(It.IsAny<string>()))
|
.Setup(v => v.FileExists(It.IsAny<string>()))
|
||||||
.Returns<string>(s => File.Exists(s));
|
.Returns<string>(s => File.Exists(s));
|
||||||
|
|||||||
@@ -1,7 +1,5 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.IO;
|
|
||||||
using System.IO.Abstractions;
|
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using FizzWare.NBuilder;
|
using FizzWare.NBuilder;
|
||||||
using FluentAssertions;
|
using FluentAssertions;
|
||||||
@@ -135,13 +133,6 @@ namespace NzbDrone.Core.Test.MediaCoverTests
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
var path = Path.Combine(TestContext.CurrentContext.TestDirectory, "Files", "Media", "NonExistant.mp4");
|
|
||||||
var fileInfo = new FileInfo(path);
|
|
||||||
|
|
||||||
Mocker.GetMock<IDiskProvider>()
|
|
||||||
.Setup(c => c.GetFileInfo(It.IsAny<string>()))
|
|
||||||
.Returns((FileInfoBase)fileInfo);
|
|
||||||
|
|
||||||
Subject.ConvertToLocalUrls(12, MediaCoverEntity.Author, covers);
|
Subject.ConvertToLocalUrls(12, MediaCoverEntity.Author, covers);
|
||||||
|
|
||||||
covers.Single().Url.Should().Be("/MediaCover/12/banner" + extension);
|
covers.Single().Url.Should().Be("/MediaCover/12/banner" + extension);
|
||||||
|
|||||||
@@ -13,7 +13,6 @@ using NzbDrone.Core.Configuration;
|
|||||||
using NzbDrone.Core.MediaFiles;
|
using NzbDrone.Core.MediaFiles;
|
||||||
using NzbDrone.Core.Test.Framework;
|
using NzbDrone.Core.Test.Framework;
|
||||||
using NzbDrone.Test.Common;
|
using NzbDrone.Test.Common;
|
||||||
using NzbDrone.Test.Common.AutoMoq;
|
|
||||||
|
|
||||||
namespace NzbDrone.Core.Test.MediaFiles.AudioTagServiceFixture
|
namespace NzbDrone.Core.Test.MediaFiles.AudioTagServiceFixture
|
||||||
{
|
{
|
||||||
@@ -56,7 +55,9 @@ namespace NzbDrone.Core.Test.MediaFiles.AudioTagServiceFixture
|
|||||||
[SetUp]
|
[SetUp]
|
||||||
public void Setup()
|
public void Setup()
|
||||||
{
|
{
|
||||||
_diskProvider = Mocker.Resolve<IDiskProvider>(FileSystemType.Actual);
|
_diskProvider = Mocker.Resolve<IDiskProvider>("ActualDiskProvider");
|
||||||
|
|
||||||
|
Mocker.SetConstant<IDiskProvider>(_diskProvider);
|
||||||
|
|
||||||
Mocker.GetMock<IConfigService>()
|
Mocker.GetMock<IConfigService>()
|
||||||
.Setup(x => x.WriteAudioTags)
|
.Setup(x => x.WriteAudioTags)
|
||||||
|
|||||||
@@ -75,7 +75,7 @@ namespace NzbDrone.Core.Test.MetadataSource.Goodreads
|
|||||||
[Test]
|
[Test]
|
||||||
public void getting_details_of_invalid_book()
|
public void getting_details_of_invalid_book()
|
||||||
{
|
{
|
||||||
Assert.Throws<BookNotFoundException>(() => Subject.GetBookInfo("1"));
|
Assert.Throws<BookNotFoundException>(() => Subject.GetBookInfo("99999999"));
|
||||||
}
|
}
|
||||||
|
|
||||||
private void ValidateAuthor(Author author)
|
private void ValidateAuthor(Author author)
|
||||||
|
|||||||
@@ -39,7 +39,7 @@ namespace NzbDrone.Core.Test.MetadataSource.Goodreads
|
|||||||
}
|
}
|
||||||
|
|
||||||
[TestCase("Harry Potter and the sorcerer's stone", 3)]
|
[TestCase("Harry Potter and the sorcerer's stone", 3)]
|
||||||
[TestCase("B0192CTMYG", 61209488)]
|
[TestCase("B0192CTMYG", 42844155)]
|
||||||
[TestCase("9780439554930", 48517161)]
|
[TestCase("9780439554930", 48517161)]
|
||||||
public void successful_book_search(string title, int expected)
|
public void successful_book_search(string title, int expected)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -3,10 +3,8 @@ using System.Collections.Generic;
|
|||||||
using System.Linq;
|
using System.Linq;
|
||||||
using FizzWare.NBuilder;
|
using FizzWare.NBuilder;
|
||||||
using FluentAssertions;
|
using FluentAssertions;
|
||||||
using FluentAssertions.Equivalency;
|
|
||||||
using NUnit.Framework;
|
using NUnit.Framework;
|
||||||
using NzbDrone.Core.Books;
|
using NzbDrone.Core.Books;
|
||||||
using NzbDrone.Core.Datastore;
|
|
||||||
using NzbDrone.Core.Test.Framework;
|
using NzbDrone.Core.Test.Framework;
|
||||||
|
|
||||||
namespace NzbDrone.Core.Test.MusicTests.BookRepositoryTests
|
namespace NzbDrone.Core.Test.MusicTests.BookRepositoryTests
|
||||||
@@ -23,13 +21,6 @@ namespace NzbDrone.Core.Test.MusicTests.BookRepositoryTests
|
|||||||
[SetUp]
|
[SetUp]
|
||||||
public void Setup()
|
public void Setup()
|
||||||
{
|
{
|
||||||
AssertionOptions.AssertEquivalencyUsing(options =>
|
|
||||||
{
|
|
||||||
options.Using<DateTime>(ctx => ctx.Subject.Should().BeCloseTo(ctx.Expectation.ToUniversalTime())).WhenTypeIs<DateTime>();
|
|
||||||
options.Using<DateTime?>(ctx => ctx.Subject.Should().BeCloseTo(ctx.Expectation.Value.ToUniversalTime())).WhenTypeIs<DateTime?>();
|
|
||||||
return options;
|
|
||||||
});
|
|
||||||
|
|
||||||
_author = new Author
|
_author = new Author
|
||||||
{
|
{
|
||||||
Name = "Alien Ant Farm",
|
Name = "Alien Ant Farm",
|
||||||
@@ -152,7 +143,7 @@ namespace NzbDrone.Core.Test.MusicTests.BookRepositoryTests
|
|||||||
GivenMultipleBooks();
|
GivenMultipleBooks();
|
||||||
|
|
||||||
var result = _bookRepo.GetNextBooks(new[] { _author.AuthorMetadataId });
|
var result = _bookRepo.GetNextBooks(new[] { _author.AuthorMetadataId });
|
||||||
result.Should().BeEquivalentTo(_books.Take(1), BookComparerOptions);
|
result.Should().BeEquivalentTo(_books.Take(1));
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
@@ -161,11 +152,7 @@ namespace NzbDrone.Core.Test.MusicTests.BookRepositoryTests
|
|||||||
GivenMultipleBooks();
|
GivenMultipleBooks();
|
||||||
|
|
||||||
var result = _bookRepo.GetLastBooks(new[] { _author.AuthorMetadataId });
|
var result = _bookRepo.GetLastBooks(new[] { _author.AuthorMetadataId });
|
||||||
result.Should().BeEquivalentTo(_books.Skip(2).Take(1), BookComparerOptions);
|
result.Should().BeEquivalentTo(_books.Skip(2).Take(1));
|
||||||
}
|
}
|
||||||
|
|
||||||
private EquivalencyAssertionOptions<Book> BookComparerOptions(EquivalencyAssertionOptions<Book> opts) => opts.ComparingByMembers<Book>()
|
|
||||||
.Excluding(ctx => ctx.SelectedMemberInfo.MemberType.IsGenericType && ctx.SelectedMemberInfo.MemberType.GetGenericTypeDefinition() == typeof(LazyLoaded<>))
|
|
||||||
.Excluding(x => x.AuthorId);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,11 +3,9 @@ using System.Collections.Generic;
|
|||||||
using System.Data.SQLite;
|
using System.Data.SQLite;
|
||||||
using FizzWare.NBuilder;
|
using FizzWare.NBuilder;
|
||||||
using FluentAssertions;
|
using FluentAssertions;
|
||||||
using Npgsql;
|
|
||||||
using NUnit.Framework;
|
using NUnit.Framework;
|
||||||
using NzbDrone.Common.Extensions;
|
using NzbDrone.Common.Extensions;
|
||||||
using NzbDrone.Core.Books;
|
using NzbDrone.Core.Books;
|
||||||
using NzbDrone.Core.Datastore;
|
|
||||||
using NzbDrone.Core.Profiles.Metadata;
|
using NzbDrone.Core.Profiles.Metadata;
|
||||||
using NzbDrone.Core.Profiles.Qualities;
|
using NzbDrone.Core.Profiles.Qualities;
|
||||||
using NzbDrone.Core.Qualities;
|
using NzbDrone.Core.Qualities;
|
||||||
@@ -147,14 +145,7 @@ namespace NzbDrone.Core.Test.MusicTests.AuthorRepositoryTests
|
|||||||
_authorRepo.Insert(author1);
|
_authorRepo.Insert(author1);
|
||||||
|
|
||||||
Action insertDupe = () => _authorRepo.Insert(author2);
|
Action insertDupe = () => _authorRepo.Insert(author2);
|
||||||
if (Db.DatabaseType == DatabaseType.PostgreSQL)
|
insertDupe.Should().Throw<SQLiteException>();
|
||||||
{
|
|
||||||
insertDupe.Should().Throw<PostgresException>();
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
insertDupe.Should().Throw<SQLiteException>();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -106,7 +106,7 @@ namespace NzbDrone.Core.Test.MusicTests
|
|||||||
private void GivenBooksForRefresh(List<Book> books)
|
private void GivenBooksForRefresh(List<Book> books)
|
||||||
{
|
{
|
||||||
Mocker.GetMock<IBookService>(MockBehavior.Strict)
|
Mocker.GetMock<IBookService>(MockBehavior.Strict)
|
||||||
.Setup(s => s.GetBooksForRefresh(It.IsAny<int>(), It.IsAny<List<string>>()))
|
.Setup(s => s.GetBooksForRefresh(It.IsAny<int>(), It.IsAny<IEnumerable<string>>()))
|
||||||
.Returns(books);
|
.Returns(books);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -238,7 +238,7 @@ namespace NzbDrone.Core.Test.MusicTests
|
|||||||
|
|
||||||
Mocker.GetMock<IBookService>(MockBehavior.Strict)
|
Mocker.GetMock<IBookService>(MockBehavior.Strict)
|
||||||
.InSequence(seq)
|
.InSequence(seq)
|
||||||
.Setup(x => x.GetBooksForRefresh(It.IsAny<int>(), It.IsAny<List<string>>()))
|
.Setup(x => x.GetBooksForRefresh(It.IsAny<int>(), It.IsAny<IEnumerable<string>>()))
|
||||||
.Returns(new List<Book>());
|
.Returns(new List<Book>());
|
||||||
|
|
||||||
// Update called twice for a move/merge
|
// Update called twice for a move/merge
|
||||||
@@ -298,7 +298,7 @@ namespace NzbDrone.Core.Test.MusicTests
|
|||||||
|
|
||||||
Mocker.GetMock<IBookService>(MockBehavior.Strict)
|
Mocker.GetMock<IBookService>(MockBehavior.Strict)
|
||||||
.InSequence(seq)
|
.InSequence(seq)
|
||||||
.Setup(x => x.GetBooksForRefresh(clash.AuthorMetadataId, It.IsAny<List<string>>()))
|
.Setup(x => x.GetBooksForRefresh(clash.AuthorMetadataId, It.IsAny<IEnumerable<string>>()))
|
||||||
.Returns(_books);
|
.Returns(_books);
|
||||||
|
|
||||||
// Update called twice for a move/merge
|
// Update called twice for a move/merge
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ using NzbDrone.Core.Test.Framework;
|
|||||||
namespace NzbDrone.Core.Test.ParserTests
|
namespace NzbDrone.Core.Test.ParserTests
|
||||||
{
|
{
|
||||||
[TestFixture]
|
[TestFixture]
|
||||||
|
|
||||||
public class QualityParserFixture : CoreTest
|
public class QualityParserFixture : CoreTest
|
||||||
{
|
{
|
||||||
public static object[] SelfQualityParserCases =
|
public static object[] SelfQualityParserCases =
|
||||||
|
|||||||
@@ -69,7 +69,7 @@ namespace NzbDrone.Core.Test.UpdateTests
|
|||||||
.Returns(true);
|
.Returns(true);
|
||||||
|
|
||||||
Mocker.GetMock<IDiskProvider>()
|
Mocker.GetMock<IDiskProvider>()
|
||||||
.Setup(v => v.FileExists(It.Is<string>(s => s.EndsWith("Readarr.Update".ProcessNameToExe()))))
|
.Setup(v => v.FileExists(It.Is<string>(s => s.EndsWith("Readarr.Update.exe"))))
|
||||||
.Returns(true);
|
.Returns(true);
|
||||||
|
|
||||||
_sandboxFolder = Mocker.GetMock<IAppFolderInfo>().Object.GetUpdateSandboxFolder();
|
_sandboxFolder = Mocker.GetMock<IAppFolderInfo>().Object.GetUpdateSandboxFolder();
|
||||||
@@ -165,7 +165,7 @@ namespace NzbDrone.Core.Test.UpdateTests
|
|||||||
public void should_return_with_warning_if_updater_doesnt_exists()
|
public void should_return_with_warning_if_updater_doesnt_exists()
|
||||||
{
|
{
|
||||||
Mocker.GetMock<IDiskProvider>()
|
Mocker.GetMock<IDiskProvider>()
|
||||||
.Setup(v => v.FileExists(It.Is<string>(s => s.EndsWith("Readarr.Update".ProcessNameToExe()))))
|
.Setup(v => v.FileExists(It.Is<string>(s => s.EndsWith("Readarr.Update.exe"))))
|
||||||
.Returns(false);
|
.Returns(false);
|
||||||
|
|
||||||
Subject.Execute(new ApplicationUpdateCommand());
|
Subject.Execute(new ApplicationUpdateCommand());
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ namespace NzbDrone.Core.AuthorStats
|
|||||||
|
|
||||||
public class AuthorStatisticsRepository : IAuthorStatisticsRepository
|
public class AuthorStatisticsRepository : IAuthorStatisticsRepository
|
||||||
{
|
{
|
||||||
private const string _selectTemplate = "SELECT /**select**/ FROM \"Editions\" /**join**/ /**innerjoin**/ /**leftjoin**/ /**where**/ /**groupby**/ /**having**/ /**orderby**/";
|
private const string _selectTemplate = "SELECT /**select**/ FROM Editions /**join**/ /**innerjoin**/ /**leftjoin**/ /**where**/ /**groupby**/ /**having**/ /**orderby**/";
|
||||||
|
|
||||||
private readonly IMainDatabase _database;
|
private readonly IMainDatabase _database;
|
||||||
|
|
||||||
@@ -45,14 +45,14 @@ namespace NzbDrone.Core.AuthorStats
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private SqlBuilder Builder() => new SqlBuilder(_database.DatabaseType)
|
private SqlBuilder Builder() => new SqlBuilder()
|
||||||
.Select(@"""Authors"".""Id"" AS ""AuthorId"",
|
.Select(@"Authors.Id AS AuthorId,
|
||||||
""Books"".""Id"" AS ""BookId"",
|
Books.Id AS BookId,
|
||||||
SUM(COALESCE(""BookFiles"".""Size"", 0)) AS ""SizeOnDisk"",
|
SUM(COALESCE(BookFiles.Size, 0)) AS SizeOnDisk,
|
||||||
1 AS ""TotalBookCount"",
|
1 AS TotalBookCount,
|
||||||
CASE WHEN MIN(""BookFiles"".""Id"") IS NULL THEN 0 ELSE 1 END AS ""AvailableBookCount"",
|
CASE WHEN BookFiles.Id IS NULL THEN 0 ELSE 1 END AS AvailableBookCount,
|
||||||
CASE WHEN (""Books"".""Monitored"" = true AND (""Books"".""ReleaseDate"" < @currentDate) OR ""Books"".""ReleaseDate"" IS NULL) OR MIN(""BookFiles"".""Id"") IS NOT NULL THEN 1 ELSE 0 END AS ""BookCount"",
|
CASE WHEN (Books.Monitored = 1 AND (Books.ReleaseDate < @currentDate) OR Books.ReleaseDate IS NULL) OR BookFiles.Id IS NOT NULL THEN 1 ELSE 0 END AS BookCount,
|
||||||
CASE WHEN MIN(""BookFiles"".""Id"") IS NULL THEN 0 ELSE COUNT(""BookFiles"".""Id"") END AS ""BookFileCount""")
|
CASE WHEN BookFiles.Id IS NULL THEN 0 ELSE COUNT(BookFiles.Id) END AS BookFileCount")
|
||||||
.Join<Edition, Book>((e, b) => e.BookId == b.Id)
|
.Join<Edition, Book>((e, b) => e.BookId == b.Id)
|
||||||
.Join<Book, Author>((book, author) => book.AuthorMetadataId == author.AuthorMetadataId)
|
.Join<Book, Author>((book, author) => book.AuthorMetadataId == author.AuthorMetadataId)
|
||||||
.LeftJoin<Edition, BookFile>((t, f) => t.Id == f.EditionId)
|
.LeftJoin<Edition, BookFile>((t, f) => t.Id == f.EditionId)
|
||||||
|
|||||||
@@ -15,14 +15,12 @@ namespace NzbDrone.Core.AuthorStats
|
|||||||
}
|
}
|
||||||
|
|
||||||
public class AuthorStatisticsService : IAuthorStatisticsService,
|
public class AuthorStatisticsService : IAuthorStatisticsService,
|
||||||
IHandle<AuthorAddedEvent>,
|
|
||||||
IHandle<AuthorUpdatedEvent>,
|
IHandle<AuthorUpdatedEvent>,
|
||||||
IHandle<AuthorDeletedEvent>,
|
IHandle<AuthorDeletedEvent>,
|
||||||
IHandle<BookAddedEvent>,
|
IHandle<BookAddedEvent>,
|
||||||
IHandle<BookDeletedEvent>,
|
IHandle<BookDeletedEvent>,
|
||||||
IHandle<BookImportedEvent>,
|
IHandle<BookImportedEvent>,
|
||||||
IHandle<BookEditedEvent>,
|
IHandle<BookEditedEvent>,
|
||||||
IHandle<BookUpdatedEvent>,
|
|
||||||
IHandle<BookFileDeletedEvent>
|
IHandle<BookFileDeletedEvent>
|
||||||
{
|
{
|
||||||
private readonly IAuthorStatisticsRepository _authorStatisticsRepository;
|
private readonly IAuthorStatisticsRepository _authorStatisticsRepository;
|
||||||
@@ -70,13 +68,6 @@ namespace NzbDrone.Core.AuthorStats
|
|||||||
return authorStatistics;
|
return authorStatistics;
|
||||||
}
|
}
|
||||||
|
|
||||||
[EventHandleOrder(EventHandleOrder.First)]
|
|
||||||
public void Handle(AuthorAddedEvent message)
|
|
||||||
{
|
|
||||||
_cache.Remove("AllAuthors");
|
|
||||||
_cache.Remove(message.Author.Id.ToString());
|
|
||||||
}
|
|
||||||
|
|
||||||
[EventHandleOrder(EventHandleOrder.First)]
|
[EventHandleOrder(EventHandleOrder.First)]
|
||||||
public void Handle(AuthorUpdatedEvent message)
|
public void Handle(AuthorUpdatedEvent message)
|
||||||
{
|
{
|
||||||
@@ -119,13 +110,6 @@ namespace NzbDrone.Core.AuthorStats
|
|||||||
_cache.Remove(message.Book.AuthorId.ToString());
|
_cache.Remove(message.Book.AuthorId.ToString());
|
||||||
}
|
}
|
||||||
|
|
||||||
[EventHandleOrder(EventHandleOrder.First)]
|
|
||||||
public void Handle(BookUpdatedEvent message)
|
|
||||||
{
|
|
||||||
_cache.Remove("AllAuthors");
|
|
||||||
_cache.Remove(message.Book.AuthorId.ToString());
|
|
||||||
}
|
|
||||||
|
|
||||||
[EventHandleOrder(EventHandleOrder.First)]
|
[EventHandleOrder(EventHandleOrder.First)]
|
||||||
public void Handle(BookFileDeletedEvent message)
|
public void Handle(BookFileDeletedEvent message)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -183,12 +183,9 @@ namespace NzbDrone.Core.Backup
|
|||||||
|
|
||||||
private void BackupDatabase()
|
private void BackupDatabase()
|
||||||
{
|
{
|
||||||
if (_maindDb.DatabaseType == DatabaseType.SQLite)
|
_logger.ProgressDebug("Backing up database");
|
||||||
{
|
|
||||||
_logger.ProgressDebug("Backing up database");
|
|
||||||
|
|
||||||
_makeDatabaseBackup.BackupDatabase(_maindDb, _backupTempFolder);
|
_makeDatabaseBackup.BackupDatabase(_maindDb, _backupTempFolder);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void BackupConfigFile()
|
private void BackupConfigFile()
|
||||||
|
|||||||
@@ -34,7 +34,7 @@ namespace NzbDrone.Core.Blocklisting
|
|||||||
return Query(b => b.AuthorId == authorId);
|
return Query(b => b.AuthorId == authorId);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override SqlBuilder PagedBuilder() => new SqlBuilder(_database.DatabaseType)
|
protected override SqlBuilder PagedBuilder() => new SqlBuilder()
|
||||||
.Join<Blocklist, Author>((b, m) => b.AuthorId == m.Id)
|
.Join<Blocklist, Author>((b, m) => b.AuthorId == m.Id)
|
||||||
.Join<Author, AuthorMetadata>((l, r) => l.AuthorMetadataId == r.Id);
|
.Join<Author, AuthorMetadata>((l, r) => l.AuthorMetadataId == r.Id);
|
||||||
protected override IEnumerable<Blocklist> PagedQuery(SqlBuilder builder) => _database.QueryJoined<Blocklist, Author, AuthorMetadata>(builder,
|
protected override IEnumerable<Blocklist> PagedQuery(SqlBuilder builder) => _database.QueryJoined<Blocklist, Author, AuthorMetadata>(builder,
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ namespace NzbDrone.Core.Books
|
|||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override SqlBuilder Builder() => new SqlBuilder(_database.DatabaseType)
|
protected override SqlBuilder Builder() => new SqlBuilder()
|
||||||
.Join<Author, AuthorMetadata>((a, m) => a.AuthorMetadataId == m.Id);
|
.Join<Author, AuthorMetadata>((a, m) => a.AuthorMetadataId == m.Id);
|
||||||
|
|
||||||
protected override List<Author> Query(SqlBuilder builder) => Query(_database, builder).ToList();
|
protected override List<Author> Query(SqlBuilder builder) => Query(_database, builder).ToList();
|
||||||
@@ -60,7 +60,7 @@ namespace NzbDrone.Core.Books
|
|||||||
{
|
{
|
||||||
using (var conn = _database.OpenConnection())
|
using (var conn = _database.OpenConnection())
|
||||||
{
|
{
|
||||||
var strSql = "SELECT \"Id\" AS \"Key\", \"Path\" AS \"Value\" FROM \"Authors\"";
|
var strSql = "SELECT Id AS [Key], Path AS [Value] FROM Authors";
|
||||||
return conn.Query<KeyValuePair<int, string>>(strSql).ToDictionary(x => x.Key, x => x.Value);
|
return conn.Query<KeyValuePair<int, string>>(strSql).ToDictionary(x => x.Key, x => x.Value);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ namespace NzbDrone.Core.Books
|
|||||||
List<Book> GetLastBooks(IEnumerable<int> authorMetadataIds);
|
List<Book> GetLastBooks(IEnumerable<int> authorMetadataIds);
|
||||||
List<Book> GetNextBooks(IEnumerable<int> authorMetadataIds);
|
List<Book> GetNextBooks(IEnumerable<int> authorMetadataIds);
|
||||||
List<Book> GetBooksByAuthorMetadataId(int authorMetadataId);
|
List<Book> GetBooksByAuthorMetadataId(int authorMetadataId);
|
||||||
List<Book> GetBooksForRefresh(int authorMetadataId, List<string> foreignIds);
|
List<Book> GetBooksForRefresh(int authorMetadataId, IEnumerable<string> foreignIds);
|
||||||
List<Book> GetBooksByFileIds(IEnumerable<int> fileIds);
|
List<Book> GetBooksByFileIds(IEnumerable<int> fileIds);
|
||||||
Book FindByTitle(int authorMetadataId, string title);
|
Book FindByTitle(int authorMetadataId, string title);
|
||||||
Book FindById(string foreignBookId);
|
Book FindById(string foreignBookId);
|
||||||
@@ -44,35 +44,17 @@ namespace NzbDrone.Core.Books
|
|||||||
public List<Book> GetLastBooks(IEnumerable<int> authorMetadataIds)
|
public List<Book> GetLastBooks(IEnumerable<int> authorMetadataIds)
|
||||||
{
|
{
|
||||||
var now = DateTime.UtcNow;
|
var now = DateTime.UtcNow;
|
||||||
|
return Query(Builder().Where<Book>(x => authorMetadataIds.Contains(x.AuthorMetadataId) && x.ReleaseDate < now)
|
||||||
var inner = Builder()
|
.GroupBy<Book>(x => x.AuthorMetadataId)
|
||||||
.Select("MIN(\"Books\".\"Id\") as id, MAX(\"Books\".\"ReleaseDate\") as date")
|
.Having("Books.ReleaseDate = MAX(Books.ReleaseDate)"));
|
||||||
.Where<Book>(x => authorMetadataIds.Contains(x.AuthorMetadataId) && x.ReleaseDate < now)
|
|
||||||
.GroupBy<Book>(x => x.AuthorMetadataId)
|
|
||||||
.AddSelectTemplate(typeof(Book));
|
|
||||||
|
|
||||||
var outer = Builder()
|
|
||||||
.Join($"({inner.RawSql}) ids on ids.id = \"Books\".\"Id\" and ids.date = \"Books\".\"ReleaseDate\"")
|
|
||||||
.AddParameters(inner.Parameters);
|
|
||||||
|
|
||||||
return Query(outer);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public List<Book> GetNextBooks(IEnumerable<int> authorMetadataIds)
|
public List<Book> GetNextBooks(IEnumerable<int> authorMetadataIds)
|
||||||
{
|
{
|
||||||
var now = DateTime.UtcNow;
|
var now = DateTime.UtcNow;
|
||||||
|
return Query(Builder().Where<Book>(x => authorMetadataIds.Contains(x.AuthorMetadataId) && x.ReleaseDate > now)
|
||||||
var inner = Builder()
|
.GroupBy<Book>(x => x.AuthorMetadataId)
|
||||||
.Select("MIN(\"Books\".\"Id\") as id, MIN(\"Books\".\"ReleaseDate\") as date")
|
.Having("Books.ReleaseDate = MIN(Books.ReleaseDate)"));
|
||||||
.Where<Book>(x => authorMetadataIds.Contains(x.AuthorMetadataId) && x.ReleaseDate > now)
|
|
||||||
.GroupBy<Book>(x => x.AuthorMetadataId)
|
|
||||||
.AddSelectTemplate(typeof(Book));
|
|
||||||
|
|
||||||
var outer = Builder()
|
|
||||||
.Join($"({inner.RawSql}) ids on ids.id = \"Books\".\"Id\" and ids.date = \"Books\".\"ReleaseDate\"")
|
|
||||||
.AddParameters(inner.Parameters);
|
|
||||||
|
|
||||||
return Query(outer);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public List<Book> GetBooksByAuthorMetadataId(int authorMetadataId)
|
public List<Book> GetBooksByAuthorMetadataId(int authorMetadataId)
|
||||||
@@ -80,14 +62,14 @@ namespace NzbDrone.Core.Books
|
|||||||
return Query(s => s.AuthorMetadataId == authorMetadataId);
|
return Query(s => s.AuthorMetadataId == authorMetadataId);
|
||||||
}
|
}
|
||||||
|
|
||||||
public List<Book> GetBooksForRefresh(int authorMetadataId, List<string> foreignIds)
|
public List<Book> GetBooksForRefresh(int authorMetadataId, IEnumerable<string> foreignIds)
|
||||||
{
|
{
|
||||||
return Query(a => a.AuthorMetadataId == authorMetadataId || foreignIds.Contains(a.ForeignBookId));
|
return Query(a => a.AuthorMetadataId == authorMetadataId || foreignIds.Contains(a.ForeignBookId));
|
||||||
}
|
}
|
||||||
|
|
||||||
public List<Book> GetBooksByFileIds(IEnumerable<int> fileIds)
|
public List<Book> GetBooksByFileIds(IEnumerable<int> fileIds)
|
||||||
{
|
{
|
||||||
return Query(new SqlBuilder(_database.DatabaseType)
|
return Query(new SqlBuilder()
|
||||||
.Join<Book, Edition>((b, e) => b.Id == e.BookId)
|
.Join<Book, Edition>((b, e) => b.Id == e.BookId)
|
||||||
.Join<Edition, BookFile>((l, r) => l.Id == r.EditionId)
|
.Join<Edition, BookFile>((l, r) => l.Id == r.EditionId)
|
||||||
.Where<BookFile>(f => fileIds.Contains(f.Id)))
|
.Where<BookFile>(f => fileIds.Contains(f.Id)))
|
||||||
@@ -143,7 +125,7 @@ namespace NzbDrone.Core.Books
|
|||||||
{
|
{
|
||||||
foreach (var belowCutoff in profile.QualityIds)
|
foreach (var belowCutoff in profile.QualityIds)
|
||||||
{
|
{
|
||||||
clauses.Add(string.Format("(\"Authors\".\"QualityProfileId\" = {0} AND \"BookFiles\".\"Quality\" LIKE '%_quality_: {1},%')", profile.ProfileId, belowCutoff));
|
clauses.Add(string.Format("(Authors.[QualityProfileId] = {0} AND BookFiles.Quality LIKE '%_quality_: {1},%')", profile.ProfileId, belowCutoff));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -154,7 +136,7 @@ namespace NzbDrone.Core.Books
|
|||||||
{
|
{
|
||||||
pagingSpec.Records = GetPagedRecords(BooksWhereCutoffUnmetBuilder(qualitiesBelowCutoff), pagingSpec, PagedQuery);
|
pagingSpec.Records = GetPagedRecords(BooksWhereCutoffUnmetBuilder(qualitiesBelowCutoff), pagingSpec, PagedQuery);
|
||||||
|
|
||||||
var countTemplate = $"SELECT COUNT(*) FROM (SELECT /**select**/ FROM \"{TableMapping.Mapper.TableNameMapping(typeof(Book))}\" /**join**/ /**innerjoin**/ /**leftjoin**/ /**where**/ /**groupby**/ /**having**/) AS \"Inner\"";
|
var countTemplate = $"SELECT COUNT(*) FROM (SELECT /**select**/ FROM {TableMapping.Mapper.TableNameMapping(typeof(Book))} /**join**/ /**innerjoin**/ /**leftjoin**/ /**where**/ /**groupby**/ /**having**/)";
|
||||||
pagingSpec.TotalRecords = GetPagedRecordCount(BooksWhereCutoffUnmetBuilder(qualitiesBelowCutoff).Select(typeof(Book)), pagingSpec, countTemplate);
|
pagingSpec.TotalRecords = GetPagedRecordCount(BooksWhereCutoffUnmetBuilder(qualitiesBelowCutoff).Select(typeof(Book)), pagingSpec, countTemplate);
|
||||||
|
|
||||||
return pagingSpec;
|
return pagingSpec;
|
||||||
|
|||||||
@@ -11,11 +11,11 @@ namespace NzbDrone.Core.Books
|
|||||||
{
|
{
|
||||||
List<Edition> GetAllMonitoredEditions();
|
List<Edition> GetAllMonitoredEditions();
|
||||||
Edition FindByForeignEditionId(string foreignEditionId);
|
Edition FindByForeignEditionId(string foreignEditionId);
|
||||||
List<Edition> FindByBook(IEnumerable<int> ids);
|
List<Edition> FindByBook(int id);
|
||||||
List<Edition> FindByAuthor(int id);
|
List<Edition> FindByAuthor(int id);
|
||||||
List<Edition> FindByAuthorMetadataId(int id, bool onlyMonitored);
|
List<Edition> FindByAuthorMetadataId(int id, bool onlyMonitored);
|
||||||
Edition FindByTitle(int authorMetadataId, string title);
|
Edition FindByTitle(int authorMetadataId, string title);
|
||||||
List<Edition> GetEditionsForRefresh(int bookId, List<string> foreignEditionIds);
|
List<Edition> GetEditionsForRefresh(int bookId, IEnumerable<string> foreignEditionIds);
|
||||||
List<Edition> SetMonitored(Edition edition);
|
List<Edition> SetMonitored(Edition edition);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -38,19 +38,19 @@ namespace NzbDrone.Core.Books
|
|||||||
return edition;
|
return edition;
|
||||||
}
|
}
|
||||||
|
|
||||||
public List<Edition> GetEditionsForRefresh(int bookId, List<string> foreignEditionIds)
|
public List<Edition> GetEditionsForRefresh(int bookId, IEnumerable<string> foreignEditionIds)
|
||||||
{
|
{
|
||||||
return Query(r => r.BookId == bookId || foreignEditionIds.Contains(r.ForeignEditionId));
|
return Query(r => r.BookId == bookId || foreignEditionIds.Contains(r.ForeignEditionId));
|
||||||
}
|
}
|
||||||
|
|
||||||
public List<Edition> FindByBook(IEnumerable<int> ids)
|
public List<Edition> FindByBook(int id)
|
||||||
{
|
{
|
||||||
// populate the books and author metadata also
|
// populate the books and author metadata also
|
||||||
// this hopefully speeds up the track matching a lot
|
// this hopefully speeds up the track matching a lot
|
||||||
var builder = new SqlBuilder(_database.DatabaseType)
|
var builder = new SqlBuilder()
|
||||||
.LeftJoin<Edition, Book>((e, b) => e.BookId == b.Id)
|
.LeftJoin<Edition, Book>((e, b) => e.BookId == b.Id)
|
||||||
.LeftJoin<Book, AuthorMetadata>((b, a) => b.AuthorMetadataId == a.Id)
|
.LeftJoin<Book, AuthorMetadata>((b, a) => b.AuthorMetadataId == a.Id)
|
||||||
.Where<Edition>(r => ids.Contains(r.BookId));
|
.Where<Edition>(r => r.BookId == id);
|
||||||
|
|
||||||
return _database.QueryJoined<Edition, Book, AuthorMetadata>(builder, (edition, book, metadata) =>
|
return _database.QueryJoined<Edition, Book, AuthorMetadata>(builder, (edition, book, metadata) =>
|
||||||
{
|
{
|
||||||
@@ -96,7 +96,7 @@ namespace NzbDrone.Core.Books
|
|||||||
|
|
||||||
public List<Edition> SetMonitored(Edition edition)
|
public List<Edition> SetMonitored(Edition edition)
|
||||||
{
|
{
|
||||||
var allEditions = FindByBook(new[] { edition.BookId });
|
var allEditions = FindByBook(edition.BookId);
|
||||||
allEditions.ForEach(r => r.Monitored = r.Id == edition.Id);
|
allEditions.ForEach(r => r.Monitored = r.Id == edition.Id);
|
||||||
Ensure.That(allEditions.Count(x => x.Monitored) == 1).IsTrue();
|
Ensure.That(allEditions.Count(x => x.Monitored) == 1).IsTrue();
|
||||||
UpdateMany(allEditions);
|
UpdateMany(allEditions);
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ namespace NzbDrone.Core.Books
|
|||||||
public interface ISeriesRepository : IBasicRepository<Series>
|
public interface ISeriesRepository : IBasicRepository<Series>
|
||||||
{
|
{
|
||||||
Series FindById(string foreignSeriesId);
|
Series FindById(string foreignSeriesId);
|
||||||
List<Series> FindById(List<string> foreignSeriesId);
|
List<Series> FindById(IEnumerable<string> foreignSeriesId);
|
||||||
List<Series> GetByAuthorMetadataId(int authorMetadataId);
|
List<Series> GetByAuthorMetadataId(int authorMetadataId);
|
||||||
List<Series> GetByAuthorId(int authorId);
|
List<Series> GetByAuthorId(int authorId);
|
||||||
}
|
}
|
||||||
@@ -25,7 +25,7 @@ namespace NzbDrone.Core.Books
|
|||||||
return Query(x => x.ForeignSeriesId == foreignSeriesId).SingleOrDefault();
|
return Query(x => x.ForeignSeriesId == foreignSeriesId).SingleOrDefault();
|
||||||
}
|
}
|
||||||
|
|
||||||
public List<Series> FindById(List<string> foreignSeriesId)
|
public List<Series> FindById(IEnumerable<string> foreignSeriesId)
|
||||||
{
|
{
|
||||||
return Query(x => foreignSeriesId.Contains(x.ForeignSeriesId));
|
return Query(x => foreignSeriesId.Contains(x.ForeignSeriesId));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ namespace NzbDrone.Core.Books
|
|||||||
List<Book> GetNextBooksByAuthorMetadataId(IEnumerable<int> authorMetadataIds);
|
List<Book> GetNextBooksByAuthorMetadataId(IEnumerable<int> authorMetadataIds);
|
||||||
List<Book> GetLastBooksByAuthorMetadataId(IEnumerable<int> authorMetadataIds);
|
List<Book> GetLastBooksByAuthorMetadataId(IEnumerable<int> authorMetadataIds);
|
||||||
List<Book> GetBooksByAuthorMetadataId(int authorMetadataId);
|
List<Book> GetBooksByAuthorMetadataId(int authorMetadataId);
|
||||||
List<Book> GetBooksForRefresh(int authorMetadataId, List<string> foreignIds);
|
List<Book> GetBooksForRefresh(int authorMetadataId, IEnumerable<string> foreignIds);
|
||||||
List<Book> GetBooksByFileIds(IEnumerable<int> fileIds);
|
List<Book> GetBooksByFileIds(IEnumerable<int> fileIds);
|
||||||
Book AddBook(Book newBook, bool doRefresh = true);
|
Book AddBook(Book newBook, bool doRefresh = true);
|
||||||
Book FindById(string foreignId);
|
Book FindById(string foreignId);
|
||||||
@@ -206,7 +206,7 @@ namespace NzbDrone.Core.Books
|
|||||||
return _bookRepository.GetBooksByAuthorMetadataId(authorMetadataId).ToList();
|
return _bookRepository.GetBooksByAuthorMetadataId(authorMetadataId).ToList();
|
||||||
}
|
}
|
||||||
|
|
||||||
public List<Book> GetBooksForRefresh(int authorMetadataId, List<string> foreignIds)
|
public List<Book> GetBooksForRefresh(int authorMetadataId, IEnumerable<string> foreignIds)
|
||||||
{
|
{
|
||||||
return _bookRepository.GetBooksForRefresh(authorMetadataId, foreignIds);
|
return _bookRepository.GetBooksForRefresh(authorMetadataId, foreignIds);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,9 +16,8 @@ namespace NzbDrone.Core.Books
|
|||||||
void InsertMany(List<Edition> editions);
|
void InsertMany(List<Edition> editions);
|
||||||
void UpdateMany(List<Edition> editions);
|
void UpdateMany(List<Edition> editions);
|
||||||
void DeleteMany(List<Edition> editions);
|
void DeleteMany(List<Edition> editions);
|
||||||
List<Edition> GetEditionsForRefresh(int bookId, List<string> foreignEditionIds);
|
List<Edition> GetEditionsForRefresh(int bookId, IEnumerable<string> foreignEditionIds);
|
||||||
List<Edition> GetEditionsByBook(int bookId);
|
List<Edition> GetEditionsByBook(int bookId);
|
||||||
List<Edition> GetEditionsByBook(IEnumerable<int> bookIds);
|
|
||||||
List<Edition> GetEditionsByAuthor(int authorId);
|
List<Edition> GetEditionsByAuthor(int authorId);
|
||||||
Edition FindByTitle(int authorMetadataId, string title);
|
Edition FindByTitle(int authorMetadataId, string title);
|
||||||
Edition FindByTitleInexact(int authorMetadataId, string title);
|
Edition FindByTitleInexact(int authorMetadataId, string title);
|
||||||
@@ -73,19 +72,14 @@ namespace NzbDrone.Core.Books
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public List<Edition> GetEditionsForRefresh(int bookId, List<string> foreignEditionIds)
|
public List<Edition> GetEditionsForRefresh(int bookId, IEnumerable<string> foreignEditionIds)
|
||||||
{
|
{
|
||||||
return _editionRepository.GetEditionsForRefresh(bookId, foreignEditionIds);
|
return _editionRepository.GetEditionsForRefresh(bookId, foreignEditionIds);
|
||||||
}
|
}
|
||||||
|
|
||||||
public List<Edition> GetEditionsByBook(int bookId)
|
public List<Edition> GetEditionsByBook(int bookId)
|
||||||
{
|
{
|
||||||
return _editionRepository.FindByBook(new[] { bookId });
|
return _editionRepository.FindByBook(bookId);
|
||||||
}
|
|
||||||
|
|
||||||
public List<Edition> GetEditionsByBook(IEnumerable<int> bookIds)
|
|
||||||
{
|
|
||||||
return _editionRepository.FindByBook(bookIds);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public List<Edition> GetEditionsByAuthor(int authorId)
|
public List<Edition> GetEditionsByAuthor(int authorId)
|
||||||
|
|||||||
@@ -238,7 +238,7 @@ namespace NzbDrone.Core.Books
|
|||||||
protected override List<Book> GetLocalChildren(Author entity, List<Book> remoteChildren)
|
protected override List<Book> GetLocalChildren(Author entity, List<Book> remoteChildren)
|
||||||
{
|
{
|
||||||
return _bookService.GetBooksForRefresh(entity.AuthorMetadataId,
|
return _bookService.GetBooksForRefresh(entity.AuthorMetadataId,
|
||||||
remoteChildren.Select(x => x.ForeignBookId).ToList());
|
remoteChildren.Select(x => x.ForeignBookId));
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override Tuple<Book, List<Book>> GetMatchingExistingChildren(List<Book> existingChildren, Book remote)
|
protected override Tuple<Book, List<Book>> GetMatchingExistingChildren(List<Book> existingChildren, Book remote)
|
||||||
|
|||||||
@@ -14,7 +14,6 @@ using NzbDrone.Core.MediaFiles;
|
|||||||
using NzbDrone.Core.Messaging.Commands;
|
using NzbDrone.Core.Messaging.Commands;
|
||||||
using NzbDrone.Core.Messaging.Events;
|
using NzbDrone.Core.Messaging.Events;
|
||||||
using NzbDrone.Core.MetadataSource;
|
using NzbDrone.Core.MetadataSource;
|
||||||
using NzbDrone.Core.RootFolders;
|
|
||||||
|
|
||||||
namespace NzbDrone.Core.Books
|
namespace NzbDrone.Core.Books
|
||||||
{
|
{
|
||||||
@@ -31,7 +30,6 @@ namespace NzbDrone.Core.Books
|
|||||||
{
|
{
|
||||||
private readonly IBookService _bookService;
|
private readonly IBookService _bookService;
|
||||||
private readonly IAuthorService _authorService;
|
private readonly IAuthorService _authorService;
|
||||||
private readonly IRootFolderService _rootFolderService;
|
|
||||||
private readonly IAddAuthorService _addAuthorService;
|
private readonly IAddAuthorService _addAuthorService;
|
||||||
private readonly IEditionService _editionService;
|
private readonly IEditionService _editionService;
|
||||||
private readonly IProvideAuthorInfo _authorInfo;
|
private readonly IProvideAuthorInfo _authorInfo;
|
||||||
@@ -46,7 +44,6 @@ namespace NzbDrone.Core.Books
|
|||||||
|
|
||||||
public RefreshBookService(IBookService bookService,
|
public RefreshBookService(IBookService bookService,
|
||||||
IAuthorService authorService,
|
IAuthorService authorService,
|
||||||
IRootFolderService rootFolderService,
|
|
||||||
IAddAuthorService addAuthorService,
|
IAddAuthorService addAuthorService,
|
||||||
IEditionService editionService,
|
IEditionService editionService,
|
||||||
IAuthorMetadataService authorMetadataService,
|
IAuthorMetadataService authorMetadataService,
|
||||||
@@ -63,7 +60,6 @@ namespace NzbDrone.Core.Books
|
|||||||
{
|
{
|
||||||
_bookService = bookService;
|
_bookService = bookService;
|
||||||
_authorService = authorService;
|
_authorService = authorService;
|
||||||
_rootFolderService = rootFolderService;
|
|
||||||
_addAuthorService = addAuthorService;
|
_addAuthorService = addAuthorService;
|
||||||
_editionService = editionService;
|
_editionService = editionService;
|
||||||
_authorInfo = authorInfo;
|
_authorInfo = authorInfo;
|
||||||
@@ -146,7 +142,7 @@ namespace NzbDrone.Core.Books
|
|||||||
Metadata = remote.AuthorMetadata.Value,
|
Metadata = remote.AuthorMetadata.Value,
|
||||||
MetadataProfileId = oldAuthor.MetadataProfileId,
|
MetadataProfileId = oldAuthor.MetadataProfileId,
|
||||||
QualityProfileId = oldAuthor.QualityProfileId,
|
QualityProfileId = oldAuthor.QualityProfileId,
|
||||||
RootFolderPath = _rootFolderService.GetBestRootFolderPath(oldAuthor.Path),
|
RootFolderPath = oldAuthor.RootFolderPath,
|
||||||
Monitored = oldAuthor.Monitored,
|
Monitored = oldAuthor.Monitored,
|
||||||
Tags = oldAuthor.Tags
|
Tags = oldAuthor.Tags
|
||||||
};
|
};
|
||||||
@@ -250,7 +246,7 @@ namespace NzbDrone.Core.Books
|
|||||||
|
|
||||||
protected override List<Edition> GetLocalChildren(Book entity, List<Edition> remoteChildren)
|
protected override List<Edition> GetLocalChildren(Book entity, List<Edition> remoteChildren)
|
||||||
{
|
{
|
||||||
return _editionService.GetEditionsForRefresh(entity.Id, remoteChildren.Select(x => x.ForeignEditionId).ToList());
|
return _editionService.GetEditionsForRefresh(entity.Id, remoteChildren.Select(x => x.ForeignEditionId));
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override Tuple<Edition, List<Edition>> GetMatchingExistingChildren(List<Edition> existingChildren, Edition remote)
|
protected override Tuple<Edition, List<Edition>> GetMatchingExistingChildren(List<Edition> existingChildren, Edition remote)
|
||||||
|
|||||||
@@ -132,7 +132,7 @@ namespace NzbDrone.Core.Books
|
|||||||
var updated = false;
|
var updated = false;
|
||||||
|
|
||||||
var existingByAuthor = _seriesService.GetByAuthorMetadataId(authorMetadataId);
|
var existingByAuthor = _seriesService.GetByAuthorMetadataId(authorMetadataId);
|
||||||
var existingBySeries = _seriesService.FindById(remoteSeries.Select(x => x.ForeignSeriesId).ToList());
|
var existingBySeries = _seriesService.FindById(remoteSeries.Select(x => x.ForeignSeriesId));
|
||||||
var existing = existingByAuthor.Concat(existingBySeries).GroupBy(x => x.ForeignSeriesId).Select(x => x.First()).ToList();
|
var existing = existingByAuthor.Concat(existingBySeries).GroupBy(x => x.ForeignSeriesId).Select(x => x.First()).ToList();
|
||||||
|
|
||||||
var books = _bookService.GetBooksByAuthorMetadataId(authorMetadataId);
|
var books = _bookService.GetBooksByAuthorMetadataId(authorMetadataId);
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ namespace NzbDrone.Core.Books
|
|||||||
public interface ISeriesService
|
public interface ISeriesService
|
||||||
{
|
{
|
||||||
Series FindById(string foreignSeriesId);
|
Series FindById(string foreignSeriesId);
|
||||||
List<Series> FindById(List<string> foreignSeriesId);
|
List<Series> FindById(IEnumerable<string> foreignSeriesId);
|
||||||
List<Series> GetByAuthorMetadataId(int authorMetadataId);
|
List<Series> GetByAuthorMetadataId(int authorMetadataId);
|
||||||
List<Series> GetByAuthorId(int authorId);
|
List<Series> GetByAuthorId(int authorId);
|
||||||
void Delete(int seriesId);
|
void Delete(int seriesId);
|
||||||
@@ -27,7 +27,7 @@ namespace NzbDrone.Core.Books
|
|||||||
return _seriesRepository.FindById(foreignSeriesId);
|
return _seriesRepository.FindById(foreignSeriesId);
|
||||||
}
|
}
|
||||||
|
|
||||||
public List<Series> FindById(List<string> foreignSeriesId)
|
public List<Series> FindById(IEnumerable<string> foreignSeriesId)
|
||||||
{
|
{
|
||||||
return _seriesRepository.FindById(foreignSeriesId);
|
return _seriesRepository.FindById(foreignSeriesId);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,14 +4,12 @@ using System.IO;
|
|||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Xml;
|
using System.Xml;
|
||||||
using System.Xml.Linq;
|
using System.Xml.Linq;
|
||||||
using Microsoft.Extensions.Options;
|
|
||||||
using NzbDrone.Common.Cache;
|
using NzbDrone.Common.Cache;
|
||||||
using NzbDrone.Common.Disk;
|
using NzbDrone.Common.Disk;
|
||||||
using NzbDrone.Common.EnvironmentInfo;
|
using NzbDrone.Common.EnvironmentInfo;
|
||||||
using NzbDrone.Common.Extensions;
|
using NzbDrone.Common.Extensions;
|
||||||
using NzbDrone.Core.Authentication;
|
using NzbDrone.Core.Authentication;
|
||||||
using NzbDrone.Core.Configuration.Events;
|
using NzbDrone.Core.Configuration.Events;
|
||||||
using NzbDrone.Core.Datastore;
|
|
||||||
using NzbDrone.Core.Lifecycle;
|
using NzbDrone.Core.Lifecycle;
|
||||||
using NzbDrone.Core.Messaging.Commands;
|
using NzbDrone.Core.Messaging.Commands;
|
||||||
using NzbDrone.Core.Messaging.Events;
|
using NzbDrone.Core.Messaging.Events;
|
||||||
@@ -20,7 +18,7 @@ using NzbDrone.Core.Update;
|
|||||||
namespace NzbDrone.Core.Configuration
|
namespace NzbDrone.Core.Configuration
|
||||||
{
|
{
|
||||||
public interface IConfigFileProvider : IHandleAsync<ApplicationStartedEvent>,
|
public interface IConfigFileProvider : IHandleAsync<ApplicationStartedEvent>,
|
||||||
IExecute<ResetApiKeyCommand>
|
IExecute<ResetApiKeyCommand>
|
||||||
{
|
{
|
||||||
Dictionary<string, object> GetConfigDictionary();
|
Dictionary<string, object> GetConfigDictionary();
|
||||||
void SaveConfigDictionary(Dictionary<string, object> configValues);
|
void SaveConfigDictionary(Dictionary<string, object> configValues);
|
||||||
@@ -50,13 +48,6 @@ namespace NzbDrone.Core.Configuration
|
|||||||
string SyslogServer { get; }
|
string SyslogServer { get; }
|
||||||
int SyslogPort { get; }
|
int SyslogPort { get; }
|
||||||
string SyslogLevel { get; }
|
string SyslogLevel { get; }
|
||||||
string PostgresHost { get; }
|
|
||||||
int PostgresPort { get; }
|
|
||||||
string PostgresUser { get; }
|
|
||||||
string PostgresPassword { get; }
|
|
||||||
string PostgresMainDb { get; }
|
|
||||||
string PostgresLogDb { get; }
|
|
||||||
string PostgresCacheDb { get; }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public class ConfigFileProvider : IConfigFileProvider
|
public class ConfigFileProvider : IConfigFileProvider
|
||||||
@@ -66,7 +57,6 @@ namespace NzbDrone.Core.Configuration
|
|||||||
private readonly IEventAggregator _eventAggregator;
|
private readonly IEventAggregator _eventAggregator;
|
||||||
private readonly IDiskProvider _diskProvider;
|
private readonly IDiskProvider _diskProvider;
|
||||||
private readonly ICached<string> _cache;
|
private readonly ICached<string> _cache;
|
||||||
private readonly PostgresOptions _postgresOptions;
|
|
||||||
|
|
||||||
private readonly string _configFile;
|
private readonly string _configFile;
|
||||||
|
|
||||||
@@ -75,14 +65,12 @@ namespace NzbDrone.Core.Configuration
|
|||||||
public ConfigFileProvider(IAppFolderInfo appFolderInfo,
|
public ConfigFileProvider(IAppFolderInfo appFolderInfo,
|
||||||
ICacheManager cacheManager,
|
ICacheManager cacheManager,
|
||||||
IEventAggregator eventAggregator,
|
IEventAggregator eventAggregator,
|
||||||
IDiskProvider diskProvider,
|
IDiskProvider diskProvider)
|
||||||
IOptions<PostgresOptions> postgresOptions)
|
|
||||||
{
|
{
|
||||||
_cache = cacheManager.GetCache<string>(GetType());
|
_cache = cacheManager.GetCache<string>(GetType());
|
||||||
_eventAggregator = eventAggregator;
|
_eventAggregator = eventAggregator;
|
||||||
_diskProvider = diskProvider;
|
_diskProvider = diskProvider;
|
||||||
_configFile = appFolderInfo.GetConfigPath();
|
_configFile = appFolderInfo.GetConfigPath();
|
||||||
_postgresOptions = postgresOptions.Value;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public Dictionary<string, object> GetConfigDictionary()
|
public Dictionary<string, object> GetConfigDictionary()
|
||||||
@@ -196,13 +184,6 @@ namespace NzbDrone.Core.Configuration
|
|||||||
|
|
||||||
public string LogLevel => GetValue("LogLevel", "info");
|
public string LogLevel => GetValue("LogLevel", "info");
|
||||||
public string ConsoleLogLevel => GetValue("ConsoleLogLevel", string.Empty, persist: false);
|
public string ConsoleLogLevel => GetValue("ConsoleLogLevel", string.Empty, persist: false);
|
||||||
public string PostgresHost => _postgresOptions?.Host ?? GetValue("PostgresHost", string.Empty, persist: false);
|
|
||||||
public string PostgresUser => _postgresOptions?.User ?? GetValue("PostgresUser", string.Empty, persist: false);
|
|
||||||
public string PostgresPassword => _postgresOptions?.Password ?? GetValue("PostgresPassword", string.Empty, persist: false);
|
|
||||||
public string PostgresMainDb => _postgresOptions?.MainDb ?? GetValue("PostgresMainDb", "readarr-main", persist: false);
|
|
||||||
public string PostgresLogDb => _postgresOptions?.LogDb ?? GetValue("PostgresLogDb", "readarr-log", persist: false);
|
|
||||||
public string PostgresCacheDb => _postgresOptions?.CacheDb ?? GetValue("PostgresCacheDb", "readarr-cache", persist: false);
|
|
||||||
public int PostgresPort => (_postgresOptions?.Port ?? 0) != 0 ? _postgresOptions.Port : GetValueInt("PostgresPort", 5432, persist: false);
|
|
||||||
public bool LogSql => GetValueBoolean("LogSql", false, persist: false);
|
public bool LogSql => GetValueBoolean("LogSql", false, persist: false);
|
||||||
public int LogRotate => GetValueInt("LogRotate", 50, persist: false);
|
public int LogRotate => GetValueInt("LogRotate", 50, persist: false);
|
||||||
public bool FilterSentryEvents => GetValueBoolean("FilterSentryEvents", true, persist: false);
|
public bool FilterSentryEvents => GetValueBoolean("FilterSentryEvents", true, persist: false);
|
||||||
|
|||||||
@@ -410,8 +410,6 @@ namespace NzbDrone.Core.Configuration
|
|||||||
public CertificateValidationType CertificateValidation =>
|
public CertificateValidationType CertificateValidation =>
|
||||||
GetValueEnum("CertificateValidation", CertificateValidationType.Enabled);
|
GetValueEnum("CertificateValidation", CertificateValidationType.Enabled);
|
||||||
|
|
||||||
public string ApplicationUrl => GetValue("ApplicationUrl", string.Empty);
|
|
||||||
|
|
||||||
private string GetValue(string key)
|
private string GetValue(string key)
|
||||||
{
|
{
|
||||||
return GetValue(key, string.Empty);
|
return GetValue(key, string.Empty);
|
||||||
|
|||||||
@@ -97,6 +97,5 @@ namespace NzbDrone.Core.Configuration
|
|||||||
int BackupRetention { get; }
|
int BackupRetention { get; }
|
||||||
|
|
||||||
CertificateValidationType CertificateValidation { get; }
|
CertificateValidationType CertificateValidation { get; }
|
||||||
string ApplicationUrl { get; }
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -68,7 +68,7 @@ namespace NzbDrone.Core.Datastore
|
|||||||
_updateSql = GetUpdateSql(_properties);
|
_updateSql = GetUpdateSql(_properties);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected virtual SqlBuilder Builder() => new SqlBuilder(_database.DatabaseType);
|
protected virtual SqlBuilder Builder() => new SqlBuilder();
|
||||||
|
|
||||||
protected virtual List<TModel> Query(SqlBuilder builder) => _database.Query<TModel>(builder).ToList();
|
protected virtual List<TModel> Query(SqlBuilder builder) => _database.Query<TModel>(builder).ToList();
|
||||||
|
|
||||||
@@ -80,7 +80,7 @@ namespace NzbDrone.Core.Datastore
|
|||||||
{
|
{
|
||||||
using (var conn = _database.OpenConnection())
|
using (var conn = _database.OpenConnection())
|
||||||
{
|
{
|
||||||
return conn.ExecuteScalar<int>($"SELECT COUNT(*) FROM \"{_table}\"");
|
return conn.ExecuteScalar<int>($"SELECT COUNT(*) FROM {_table}");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -176,11 +176,6 @@ namespace NzbDrone.Core.Datastore
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (_database.DatabaseType == DatabaseType.PostgreSQL)
|
|
||||||
{
|
|
||||||
return $"INSERT INTO \"{_table}\" ({sbColumnList.ToString()}) VALUES ({sbParameterList.ToString()}) RETURNING \"Id\"";
|
|
||||||
}
|
|
||||||
|
|
||||||
return $"INSERT INTO {_table} ({sbColumnList.ToString()}) VALUES ({sbParameterList.ToString()}); SELECT last_insert_rowid() id";
|
return $"INSERT INTO {_table} ({sbColumnList.ToString()}) VALUES ({sbParameterList.ToString()}); SELECT last_insert_rowid() id";
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -199,8 +194,7 @@ namespace NzbDrone.Core.Datastore
|
|||||||
throw;
|
throw;
|
||||||
}
|
}
|
||||||
|
|
||||||
var multiRead = multi.Read();
|
var id = (int)multi.Read().First().id;
|
||||||
var id = (int)(multiRead.First().id ?? multiRead.First().Id);
|
|
||||||
_keyProperty.SetValue(model, id);
|
_keyProperty.SetValue(model, id);
|
||||||
|
|
||||||
return model;
|
return model;
|
||||||
@@ -311,7 +305,7 @@ namespace NzbDrone.Core.Datastore
|
|||||||
{
|
{
|
||||||
using (var conn = _database.OpenConnection())
|
using (var conn = _database.OpenConnection())
|
||||||
{
|
{
|
||||||
conn.Execute($"DELETE FROM \"{_table}\"");
|
conn.Execute($"DELETE FROM [{_table}]");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (vacuum)
|
if (vacuum)
|
||||||
@@ -370,7 +364,7 @@ namespace NzbDrone.Core.Datastore
|
|||||||
private string GetUpdateSql(List<PropertyInfo> propertiesToUpdate)
|
private string GetUpdateSql(List<PropertyInfo> propertiesToUpdate)
|
||||||
{
|
{
|
||||||
var sb = new StringBuilder();
|
var sb = new StringBuilder();
|
||||||
sb.AppendFormat("UPDATE \"{0}\" SET ", _table);
|
sb.AppendFormat("UPDATE {0} SET ", _table);
|
||||||
|
|
||||||
for (var i = 0; i < propertiesToUpdate.Count; i++)
|
for (var i = 0; i < propertiesToUpdate.Count; i++)
|
||||||
{
|
{
|
||||||
@@ -452,10 +446,9 @@ namespace NzbDrone.Core.Datastore
|
|||||||
pagingSpec.SortKey = $"{_table}.{_keyProperty.Name}";
|
pagingSpec.SortKey = $"{_table}.{_keyProperty.Name}";
|
||||||
}
|
}
|
||||||
|
|
||||||
var sortKey = TableMapping.Mapper.GetSortKey(pagingSpec.SortKey);
|
|
||||||
var sortDirection = pagingSpec.SortDirection == SortDirection.Descending ? "DESC" : "ASC";
|
var sortDirection = pagingSpec.SortDirection == SortDirection.Descending ? "DESC" : "ASC";
|
||||||
var pagingOffset = Math.Max(pagingSpec.Page - 1, 0) * pagingSpec.PageSize;
|
var pagingOffset = (pagingSpec.Page - 1) * pagingSpec.PageSize;
|
||||||
builder.OrderBy($"\"{sortKey}\" {sortDirection} LIMIT {pagingSpec.PageSize} OFFSET {pagingOffset}");
|
builder.OrderBy($"{pagingSpec.SortKey} {sortDirection} LIMIT {pagingSpec.PageSize} OFFSET {pagingOffset}");
|
||||||
|
|
||||||
return queryFunc(builder).ToList();
|
return queryFunc(builder).ToList();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Data;
|
using System.Data;
|
||||||
|
|
||||||
namespace NzbDrone.Core.Datastore
|
namespace NzbDrone.Core.Datastore
|
||||||
@@ -10,12 +10,10 @@ namespace NzbDrone.Core.Datastore
|
|||||||
public class CacheDatabase : ICacheDatabase
|
public class CacheDatabase : ICacheDatabase
|
||||||
{
|
{
|
||||||
private readonly IDatabase _database;
|
private readonly IDatabase _database;
|
||||||
private readonly DatabaseType _databaseType;
|
|
||||||
|
|
||||||
public CacheDatabase(IDatabase database)
|
public CacheDatabase(IDatabase database)
|
||||||
{
|
{
|
||||||
_database = database;
|
_database = database;
|
||||||
_databaseType = _database == null ? DatabaseType.SQLite : _database.DatabaseType;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public IDbConnection OpenConnection()
|
public IDbConnection OpenConnection()
|
||||||
@@ -27,8 +25,6 @@ namespace NzbDrone.Core.Datastore
|
|||||||
|
|
||||||
public int Migration => _database.Migration;
|
public int Migration => _database.Migration;
|
||||||
|
|
||||||
public DatabaseType DatabaseType => _databaseType;
|
|
||||||
|
|
||||||
public void Vacuum()
|
public void Vacuum()
|
||||||
{
|
{
|
||||||
_database.Vacuum();
|
_database.Vacuum();
|
||||||
|
|||||||
@@ -1,9 +1,7 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Data.SQLite;
|
using System.Data.SQLite;
|
||||||
using Npgsql;
|
|
||||||
using NzbDrone.Common.EnvironmentInfo;
|
using NzbDrone.Common.EnvironmentInfo;
|
||||||
using NzbDrone.Common.Extensions;
|
using NzbDrone.Common.Extensions;
|
||||||
using NzbDrone.Core.Configuration;
|
|
||||||
|
|
||||||
namespace NzbDrone.Core.Datastore
|
namespace NzbDrone.Core.Datastore
|
||||||
{
|
{
|
||||||
@@ -17,20 +15,11 @@ namespace NzbDrone.Core.Datastore
|
|||||||
|
|
||||||
public class ConnectionStringFactory : IConnectionStringFactory
|
public class ConnectionStringFactory : IConnectionStringFactory
|
||||||
{
|
{
|
||||||
private readonly IConfigFileProvider _configFileProvider;
|
public ConnectionStringFactory(IAppFolderInfo appFolderInfo)
|
||||||
|
|
||||||
public ConnectionStringFactory(IAppFolderInfo appFolderInfo, IConfigFileProvider configFileProvider)
|
|
||||||
{
|
{
|
||||||
_configFileProvider = configFileProvider;
|
MainDbConnectionString = GetConnectionString(appFolderInfo.GetDatabase());
|
||||||
|
LogDbConnectionString = GetConnectionString(appFolderInfo.GetLogDatabase());
|
||||||
MainDbConnectionString = _configFileProvider.PostgresHost.IsNotNullOrWhiteSpace() ? GetPostgresConnectionString(_configFileProvider.PostgresMainDb) :
|
CacheDbConnectionString = GetConnectionString(appFolderInfo.GetCacheDatabase());
|
||||||
GetConnectionString(appFolderInfo.GetDatabase());
|
|
||||||
|
|
||||||
LogDbConnectionString = _configFileProvider.PostgresHost.IsNotNullOrWhiteSpace() ? GetPostgresConnectionString(_configFileProvider.PostgresLogDb) :
|
|
||||||
GetConnectionString(appFolderInfo.GetLogDatabase());
|
|
||||||
|
|
||||||
CacheDbConnectionString = _configFileProvider.PostgresHost.IsNotNullOrWhiteSpace() ? GetPostgresConnectionString(_configFileProvider.PostgresCacheDb) :
|
|
||||||
GetConnectionString(appFolderInfo.GetCacheDatabase());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public string MainDbConnectionString { get; private set; }
|
public string MainDbConnectionString { get; private set; }
|
||||||
@@ -62,19 +51,5 @@ namespace NzbDrone.Core.Datastore
|
|||||||
|
|
||||||
return connectionBuilder.ConnectionString;
|
return connectionBuilder.ConnectionString;
|
||||||
}
|
}
|
||||||
|
|
||||||
private string GetPostgresConnectionString(string dbName)
|
|
||||||
{
|
|
||||||
var connectionBuilder = new NpgsqlConnectionStringBuilder();
|
|
||||||
|
|
||||||
connectionBuilder.Database = dbName;
|
|
||||||
connectionBuilder.Host = _configFileProvider.PostgresHost;
|
|
||||||
connectionBuilder.Username = _configFileProvider.PostgresUser;
|
|
||||||
connectionBuilder.Password = _configFileProvider.PostgresPassword;
|
|
||||||
connectionBuilder.Port = _configFileProvider.PostgresPort;
|
|
||||||
connectionBuilder.Enlist = false;
|
|
||||||
|
|
||||||
return connectionBuilder.ConnectionString;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user