mirror of
https://github.com/Sonarr/Sonarr.git
synced 2026-03-05 13:20:20 -05:00
Compare commits
3 Commits
pr/n3036_j
...
phantom-jo
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
243c779ca1 | ||
|
|
7ff6342503 | ||
|
|
1af3e0bd93 |
@@ -6,7 +6,7 @@ import classNames from 'classnames';
|
||||
import getUniqueElememtId from 'Utilities/getUniqueElementId';
|
||||
import isMobileUtil from 'Utilities/isMobile';
|
||||
import * as keyCodes from 'Utilities/Constants/keyCodes';
|
||||
import { icons, scrollDirections } from 'Helpers/Props';
|
||||
import { icons, sizes, scrollDirections } from 'Helpers/Props';
|
||||
import Icon from 'Components/Icon';
|
||||
import Portal from 'Components/Portal';
|
||||
import Link from 'Components/Link/Link';
|
||||
@@ -14,8 +14,8 @@ import Measure from 'Components/Measure';
|
||||
import Modal from 'Components/Modal/Modal';
|
||||
import ModalBody from 'Components/Modal/ModalBody';
|
||||
import Scroller from 'Components/Scroller/Scroller';
|
||||
import EnhancedSelectInputSelectedValue from './EnhancedSelectInputSelectedValue';
|
||||
import EnhancedSelectInputOption from './EnhancedSelectInputOption';
|
||||
import HintedSelectInputSelectedValue from './HintedSelectInputSelectedValue';
|
||||
import HintedSelectInputOption from './HintedSelectInputOption';
|
||||
import styles from './EnhancedSelectInput.css';
|
||||
|
||||
function isArrowKey(keyCode) {
|
||||
@@ -150,9 +150,11 @@ class EnhancedSelectInput extends Component {
|
||||
}
|
||||
|
||||
onBlur = () => {
|
||||
this.setState({
|
||||
selectedIndex: getSelectedIndex(this.props)
|
||||
});
|
||||
// Calling setState without this check prevents the click event from being properly handled on Chrome (it is on firefox)
|
||||
const origIndex = getSelectedIndex(this.props);
|
||||
if (origIndex !== this.state.selectedIndex) {
|
||||
this.setState({ selectedIndex: origIndex });
|
||||
}
|
||||
}
|
||||
|
||||
onKeyDown = (event) => {
|
||||
@@ -385,6 +387,7 @@ class EnhancedSelectInput extends Component {
|
||||
isMobile &&
|
||||
<Modal
|
||||
className={styles.optionsModal}
|
||||
size={sizes.EXTRA_SMALL}
|
||||
isOpen={isOpen}
|
||||
onModalClose={this.onOptionsModalClose}
|
||||
>
|
||||
@@ -439,8 +442,8 @@ EnhancedSelectInput.defaultProps = {
|
||||
disabledClassName: styles.isDisabled,
|
||||
isDisabled: false,
|
||||
selectedValueOptions: {},
|
||||
selectedValueComponent: EnhancedSelectInputSelectedValue,
|
||||
optionComponent: EnhancedSelectInputOption
|
||||
selectedValueComponent: HintedSelectInputSelectedValue,
|
||||
optionComponent: HintedSelectInputOption
|
||||
};
|
||||
|
||||
export default EnhancedSelectInput;
|
||||
|
||||
@@ -7,13 +7,17 @@
|
||||
cursor: default;
|
||||
|
||||
&:hover {
|
||||
background-color: #f9f9f9;
|
||||
background-color: #f8f8f8;
|
||||
}
|
||||
}
|
||||
|
||||
.isSelected {
|
||||
background-color: #e2e2e2;
|
||||
|
||||
&:hover {
|
||||
background-color: #e2e2e2;
|
||||
}
|
||||
|
||||
&.isMobile {
|
||||
background-color: inherit;
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
.inputGroupContainer {
|
||||
flex: 1 1 auto;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.inputGroup {
|
||||
@@ -11,6 +12,7 @@
|
||||
.inputContainer {
|
||||
position: relative;
|
||||
flex: 1 1 auto;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.inputUnit {
|
||||
|
||||
@@ -16,7 +16,7 @@ import QualityProfileSelectInputConnector from './QualityProfileSelectInputConne
|
||||
import LanguageProfileSelectInputConnector from './LanguageProfileSelectInputConnector';
|
||||
import RootFolderSelectInputConnector from './RootFolderSelectInputConnector';
|
||||
import SeriesTypeSelectInput from './SeriesTypeSelectInput';
|
||||
import SelectInput from './SelectInput';
|
||||
import EnhancedSelectInput from './EnhancedSelectInput';
|
||||
import TagInputConnector from './TagInputConnector';
|
||||
import TextTagInputConnector from './TextTagInputConnector';
|
||||
import TextInput from './TextInput';
|
||||
@@ -65,7 +65,7 @@ function getComponent(type) {
|
||||
return RootFolderSelectInputConnector;
|
||||
|
||||
case inputTypes.SELECT:
|
||||
return SelectInput;
|
||||
return EnhancedSelectInput;
|
||||
|
||||
case inputTypes.SERIES_TYPE_SELECT:
|
||||
return SeriesTypeSelectInput;
|
||||
|
||||
23
frontend/src/Components/Form/HintedSelectInputOption.css
Normal file
23
frontend/src/Components/Form/HintedSelectInputOption.css
Normal file
@@ -0,0 +1,23 @@
|
||||
.optionText {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
flex: 1 0 0;
|
||||
min-width: 0;
|
||||
|
||||
&.isMobile {
|
||||
display: block;
|
||||
|
||||
.hintText {
|
||||
margin-left: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.hintText {
|
||||
@add-mixin truncate;
|
||||
|
||||
margin-left: 15px;
|
||||
color: $darkGray;
|
||||
font-size: $smallFontSize;
|
||||
}
|
||||
44
frontend/src/Components/Form/HintedSelectInputOption.js
Normal file
44
frontend/src/Components/Form/HintedSelectInputOption.js
Normal file
@@ -0,0 +1,44 @@
|
||||
import PropTypes from 'prop-types';
|
||||
import React from 'react';
|
||||
import classNames from 'classnames';
|
||||
import EnhancedSelectInputOption from './EnhancedSelectInputOption';
|
||||
import styles from './HintedSelectInputOption.css';
|
||||
|
||||
function HintedSelectInputOption(props) {
|
||||
const {
|
||||
value,
|
||||
hint,
|
||||
isMobile,
|
||||
...otherProps
|
||||
} = props;
|
||||
|
||||
return (
|
||||
<EnhancedSelectInputOption
|
||||
isMobile={isMobile}
|
||||
{...otherProps}
|
||||
>
|
||||
<div className={classNames(
|
||||
styles.optionText,
|
||||
isMobile && styles.isMobile
|
||||
)}
|
||||
>
|
||||
<div>{value}</div>
|
||||
|
||||
{
|
||||
hint != null &&
|
||||
<div className={styles.hintText}>
|
||||
{hint}
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</EnhancedSelectInputOption>
|
||||
);
|
||||
}
|
||||
|
||||
HintedSelectInputOption.propTypes = {
|
||||
value: PropTypes.string.isRequired,
|
||||
hint: PropTypes.node,
|
||||
isMobile: PropTypes.bool.isRequired
|
||||
};
|
||||
|
||||
export default HintedSelectInputOption;
|
||||
@@ -0,0 +1,24 @@
|
||||
.selectedValue {
|
||||
composes: selectedValue from '~./EnhancedSelectInputSelectedValue.css';
|
||||
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.valueText {
|
||||
@add-mixin truncate;
|
||||
|
||||
flex: 0 0 auto;
|
||||
}
|
||||
|
||||
.hintText {
|
||||
@add-mixin truncate;
|
||||
|
||||
flex: 1 10 0;
|
||||
margin-left: 15px;
|
||||
color: $gray;
|
||||
text-align: right;
|
||||
font-size: $smallFontSize;
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
import PropTypes from 'prop-types';
|
||||
import React from 'react';
|
||||
import EnhancedSelectInputSelectedValue from './EnhancedSelectInputSelectedValue';
|
||||
import styles from './HintedSelectInputSelectedValue.css';
|
||||
|
||||
function HintedSelectInputSelectedValue(props) {
|
||||
const {
|
||||
value,
|
||||
hint,
|
||||
includeHint,
|
||||
...otherProps
|
||||
} = props;
|
||||
|
||||
return (
|
||||
<EnhancedSelectInputSelectedValue
|
||||
className={styles.selectedValue}
|
||||
{...otherProps}
|
||||
>
|
||||
<div className={styles.valueText}>
|
||||
{value}
|
||||
</div>
|
||||
|
||||
{
|
||||
hint != null && includeHint &&
|
||||
<div className={styles.hintText}>
|
||||
{hint}
|
||||
</div>
|
||||
}
|
||||
</EnhancedSelectInputSelectedValue>
|
||||
);
|
||||
}
|
||||
|
||||
HintedSelectInputSelectedValue.propTypes = {
|
||||
value: PropTypes.string,
|
||||
hint: PropTypes.string,
|
||||
includeHint: PropTypes.bool.isRequired
|
||||
};
|
||||
|
||||
HintedSelectInputSelectedValue.defaultProps = {
|
||||
includeHint: true
|
||||
};
|
||||
|
||||
export default HintedSelectInputSelectedValue;
|
||||
@@ -61,7 +61,7 @@ function EditRemotePathMappingModalContent(props) {
|
||||
<FormLabel>Host</FormLabel>
|
||||
|
||||
<FormInputGroup
|
||||
type={inputTypes.AUTO_COMPLETE}
|
||||
type={inputTypes.SELECT}
|
||||
name="host"
|
||||
helpText="The same host you specified for the remote Download Client"
|
||||
{...host}
|
||||
|
||||
@@ -16,17 +16,27 @@ const newRemotePathMapping = {
|
||||
const selectDownloadClientHosts = createSelector(
|
||||
(state) => state.settings.downloadClients.items,
|
||||
(downloadClients) => {
|
||||
return downloadClients.reduce((acc, downloadClient) => {
|
||||
const hosts = downloadClients.reduce((acc, downloadClient) => {
|
||||
const name = downloadClient.name;
|
||||
const host = downloadClient.fields.find((field) => {
|
||||
return field.name === 'host';
|
||||
});
|
||||
|
||||
if (host && !acc.includes(host.value)) {
|
||||
acc.push(host.value);
|
||||
if (host) {
|
||||
const group = acc[host.value] = acc[host.value] || [];
|
||||
group.push(name);
|
||||
}
|
||||
|
||||
return acc;
|
||||
}, []);
|
||||
}, {});
|
||||
|
||||
return Object.keys(hosts).map((host) => {
|
||||
return {
|
||||
key: host,
|
||||
value: host,
|
||||
hint: `${hosts[host].join(', ')}`
|
||||
};
|
||||
});
|
||||
}
|
||||
);
|
||||
|
||||
|
||||
@@ -122,12 +122,12 @@ class Naming extends Component {
|
||||
const renameEpisodes = hasSettings && settings.renameEpisodes.value;
|
||||
|
||||
const multiEpisodeStyleOptions = [
|
||||
{ key: 0, value: 'Extend' },
|
||||
{ key: 1, value: 'Duplicate' },
|
||||
{ key: 2, value: 'Repeat' },
|
||||
{ key: 3, value: 'Scene' },
|
||||
{ key: 4, value: 'Range' },
|
||||
{ key: 5, value: 'Prefixed Range' }
|
||||
{ key: 0, value: 'Extend', hint: 'S01E01-02-03' },
|
||||
{ key: 1, value: 'Duplicate', hint: 'S01E01.S01E02' },
|
||||
{ key: 2, value: 'Repeat', hint: 'S01E01E02E03' },
|
||||
{ key: 3, value: 'Scene', hint: 'S01E01-E02-E03' },
|
||||
{ key: 4, value: 'Range', hint: 'S01E01-03' },
|
||||
{ key: 5, value: 'Prefixed Range', hint: 'S01E01-E03' }
|
||||
];
|
||||
|
||||
const standardEpisodeFormatHelpTexts = [];
|
||||
|
||||
@@ -3,6 +3,7 @@ using NzbDrone.Core.Authentication;
|
||||
using NzbDrone.Core.Configuration;
|
||||
using NzbDrone.Core.Update;
|
||||
using NzbDrone.Common.Http.Proxy;
|
||||
using NzbDrone.Core.Datastore;
|
||||
|
||||
namespace NzbDrone.Api.Config
|
||||
{
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Data.SQLite;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text.RegularExpressions;
|
||||
@@ -12,6 +13,7 @@ using NzbDrone.Common.Extensions;
|
||||
using NzbDrone.Common.Instrumentation;
|
||||
using NzbDrone.Core.Authentication;
|
||||
using NzbDrone.Core.Configuration.Events;
|
||||
using NzbDrone.Core.Datastore;
|
||||
using NzbDrone.Core.Lifecycle;
|
||||
using NzbDrone.Core.Messaging.Commands;
|
||||
using NzbDrone.Core.Messaging.Events;
|
||||
@@ -34,6 +36,7 @@ namespace NzbDrone.Core.Configuration
|
||||
bool AnalyticsEnabled { get; }
|
||||
string LogLevel { get; }
|
||||
string ConsoleLogLevel { get; }
|
||||
DatabaseJournalType DatabaseJournalMode { get; }
|
||||
string Branch { get; }
|
||||
string ApiKey { get; }
|
||||
string SslCertHash { get; }
|
||||
@@ -182,6 +185,8 @@ namespace NzbDrone.Core.Configuration
|
||||
public string LogLevel => GetValue("LogLevel", "Info");
|
||||
public string ConsoleLogLevel => GetValue("ConsoleLogLevel", string.Empty, persist: false);
|
||||
|
||||
public DatabaseJournalType DatabaseJournalMode => GetValueEnum("DatabaseJournalMode", DatabaseJournalType.Auto, persist: false);
|
||||
|
||||
public string SslCertHash => GetValue("SslCertHash", "");
|
||||
|
||||
public string UrlBase
|
||||
|
||||
@@ -1,7 +1,11 @@
|
||||
using System;
|
||||
using System.Data.SQLite;
|
||||
using System.IO;
|
||||
using NzbDrone.Common.Disk;
|
||||
using NzbDrone.Common.EnvironmentInfo;
|
||||
using NzbDrone.Common.Extensions;
|
||||
using NzbDrone.Core.Configuration;
|
||||
using NLog;
|
||||
|
||||
namespace NzbDrone.Core.Datastore
|
||||
{
|
||||
@@ -14,8 +18,16 @@ namespace NzbDrone.Core.Datastore
|
||||
|
||||
public class ConnectionStringFactory : IConnectionStringFactory
|
||||
{
|
||||
public ConnectionStringFactory(IAppFolderInfo appFolderInfo)
|
||||
private readonly IDiskProvider _diskProvider;
|
||||
private readonly IConfigFileProvider _configFileProvider;
|
||||
private readonly Logger _logger;
|
||||
|
||||
public ConnectionStringFactory(IAppFolderInfo appFolderInfo, IDiskProvider diskProvider, IConfigFileProvider configFileProvider, Logger logger)
|
||||
{
|
||||
_diskProvider = diskProvider;
|
||||
_configFileProvider = configFileProvider;
|
||||
_logger = logger;
|
||||
|
||||
MainDbConnectionString = GetConnectionString(appFolderInfo.GetDatabase());
|
||||
LogDbConnectionString = GetConnectionString(appFolderInfo.GetLogDatabase());
|
||||
}
|
||||
@@ -30,23 +42,60 @@ namespace NzbDrone.Core.Datastore
|
||||
return connectionBuilder.DataSource;
|
||||
}
|
||||
|
||||
private static string GetConnectionString(string dbPath)
|
||||
private string GetConnectionString(string dbPath)
|
||||
{
|
||||
var connectionBuilder = new SQLiteConnectionStringBuilder();
|
||||
|
||||
connectionBuilder.DataSource = dbPath;
|
||||
connectionBuilder.CacheSize = (int)-10000;
|
||||
connectionBuilder.DateTimeKind = DateTimeKind.Utc;
|
||||
connectionBuilder.JournalMode = OsInfo.IsOsx ? SQLiteJournalModeEnum.Truncate : SQLiteJournalModeEnum.Wal;
|
||||
connectionBuilder.Pooling = true;
|
||||
connectionBuilder.Version = 3;
|
||||
|
||||
|
||||
connectionBuilder.JournalMode = GetJournalMode(dbPath);
|
||||
|
||||
if (OsInfo.IsOsx)
|
||||
{
|
||||
connectionBuilder.Add("Full FSync", true);
|
||||
}
|
||||
|
||||
connectionBuilder.Pooling = true;
|
||||
connectionBuilder.Version = 3;
|
||||
|
||||
return connectionBuilder.ConnectionString;
|
||||
}
|
||||
|
||||
private SQLiteJournalModeEnum GetJournalMode(string path)
|
||||
{
|
||||
if (_configFileProvider.DatabaseJournalMode != DatabaseJournalType.Auto)
|
||||
{
|
||||
_logger.Debug("Database journal mode overridden by config.xml, using {0} journal mode", _configFileProvider.DatabaseJournalMode);
|
||||
return (SQLiteJournalModeEnum)_configFileProvider.DatabaseJournalMode;
|
||||
}
|
||||
|
||||
if (OsInfo.IsOsx)
|
||||
{
|
||||
_logger.Debug("macOS operating system, using Truncate database journal mode");
|
||||
return SQLiteJournalModeEnum.Truncate;
|
||||
}
|
||||
|
||||
var mount = _diskProvider.GetMount(path);
|
||||
|
||||
if (mount == null)
|
||||
{
|
||||
_logger.Debug("Database {0} located on unknown filesystem, using Truncate journal mode", path);
|
||||
return SQLiteJournalModeEnum.Truncate;
|
||||
}
|
||||
|
||||
if (mount.DriveType == DriveType.Network || mount.DriveType == DriveType.Unknown)
|
||||
{
|
||||
_logger.Debug("Database {0} located on filesystem {1} with type {2}, using Truncate journal mode", path, mount.DriveFormat, mount.DriveType);
|
||||
return SQLiteJournalModeEnum.Truncate;
|
||||
}
|
||||
else
|
||||
{
|
||||
_logger.Debug("Database {0} located on filesystem {1} with type {2}, using WAL journal mode", path, mount.DriveFormat, mount.DriveType);
|
||||
}
|
||||
|
||||
return SQLiteJournalModeEnum.Wal;
|
||||
}
|
||||
}
|
||||
}
|
||||
12
src/NzbDrone.Core/Datastore/DatabaseJournalType.cs
Normal file
12
src/NzbDrone.Core/Datastore/DatabaseJournalType.cs
Normal file
@@ -0,0 +1,12 @@
|
||||
using System.Data.SQLite;
|
||||
|
||||
namespace NzbDrone.Core.Datastore
|
||||
{
|
||||
public enum DatabaseJournalType
|
||||
{
|
||||
Auto = SQLiteJournalModeEnum.Default,
|
||||
Wal = SQLiteJournalModeEnum.Wal,
|
||||
Truncate = SQLiteJournalModeEnum.Truncate,
|
||||
Delete = SQLiteJournalModeEnum.Delete
|
||||
}
|
||||
}
|
||||
@@ -136,6 +136,7 @@
|
||||
<Compile Include="Configuration\InvalidConfigFileException.cs" />
|
||||
<Compile Include="Configuration\RescanAfterRefreshType.cs" />
|
||||
<Compile Include="Configuration\ResetApiKeyCommand.cs" />
|
||||
<Compile Include="Datastore\DatabaseJournalType.cs" />
|
||||
<Compile Include="Datastore\Migration\134_add_specials_folder_format.cs" />
|
||||
<Compile Include="Datastore\Migration\132_add_download_client_priority.cs" />
|
||||
<Compile Include="Datastore\Migration\131_download_propers_config.cs" />
|
||||
|
||||
@@ -17,7 +17,7 @@ namespace NzbDrone.Mono.Disk
|
||||
public class ProcMountProvider : IProcMountProvider
|
||||
{
|
||||
private static string[] _fixedTypes = new [] { "ext3", "ext2", "ext4", "vfat", "fuseblk", "xfs", "jfs", "msdos", "ntfs", "minix", "hfs", "hfsplus", "qnx4", "ufs", "btrfs" };
|
||||
private static string[] _networkDriveTypes = new [] { "cifs", "nfs", "nfs4", "nfsd", "sshfs" };
|
||||
private static string[] _networkDriveTypes = new [] { "cifs", "nfs", "nfs4", "nfsd", "sshfs", "9p" };
|
||||
private static readonly Regex OctalRegex = new Regex(@"\\\d{3}", RegexOptions.Compiled);
|
||||
|
||||
private const string PROC_MOUNTS_FILENAME = @"/proc/mounts";
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
using NzbDrone.Common.Http.Proxy;
|
||||
using NzbDrone.Core.Authentication;
|
||||
using NzbDrone.Core.Configuration;
|
||||
using NzbDrone.Core.Datastore;
|
||||
using NzbDrone.Core.Security;
|
||||
using NzbDrone.Core.Update;
|
||||
using Sonarr.Http.REST;
|
||||
@@ -20,6 +21,7 @@ namespace Sonarr.Api.V3.Config
|
||||
public string Password { get; set; }
|
||||
public string LogLevel { get; set; }
|
||||
public string ConsoleLogLevel { get; set; }
|
||||
public DatabaseJournalType DatabaseJournalMode { get; set; }
|
||||
public string Branch { get; set; }
|
||||
public string ApiKey { get; set; }
|
||||
public string SslCertHash { get; set; }
|
||||
|
||||
Reference in New Issue
Block a user