Introduce client settings

This commit is contained in:
John Furrow
2016-06-06 21:01:10 -07:00
parent 30d75c124b
commit 02c3b5e4b0
27 changed files with 907 additions and 206 deletions

View File

@@ -30,6 +30,11 @@ $checkbox--border--hover: $checkbox--border;
$modal--body--foreground: desaturate(lighten($foreground, 20%), 10%); $modal--body--foreground: desaturate(lighten($foreground, 20%), 10%);
$form--section--heading--margin: $spacing-unit * 2/5;
$form--section--margin: $spacing-unit;
$form--row--margin: $spacing-unit * 3/5;
$form--column--padding: $spacing-unit * 2/5;
.textbox, .textbox,
.button, .button,
.checkbox { .checkbox {
@@ -191,17 +196,36 @@ $modal--body--foreground: desaturate(lighten($foreground, 20%), 10%);
.form { .form {
&__section {
&__heading {
margin-bottom: $form--section--heading--margin;
& + .form__section__sub-heading {
margin-bottom: $form--section--heading--margin;
margin-top: $form--section--heading--margin * -1;
}
}
& + .form__section {
margin-top: $form--section--margin;
}
}
&__row { &__row {
display: flex; display: flex;
& + .form__row { & + .form__row {
margin-top: $spacing-unit; margin-top: $form--row--margin;
} }
} }
&__column { &__column {
display: flex;
flex: 1; flex: 1;
padding: 0 $spacing-unit * 2/5; flex-direction: column;
justify-content: flex-end;
padding: 0 $form--column--padding;
&:first-child { &:first-child {
padding-left: 0; padding-left: 0;
@@ -210,6 +234,38 @@ $modal--body--foreground: desaturate(lighten($foreground, 20%), 10%);
&:last-child { &:last-child {
padding-right: 0; padding-right: 0;
} }
&--auto {
flex-grow: 0;
flex-shrink: 1;
flex-basis: auto;
}
&--half {
max-width: 50%;
&:last-child {
padding-right: $form--column--padding;
}
}
&--small {
max-width: 125px;
// For small columns which are the only column in the row, keep the
// column's padding.
&:first-child {
&:last-child {
padding-right: $form--column--padding;
}
}
}
&--unlabled {
justify-content: center;
padding-top: $spacing-unit * 3/5;
}
} }
&__label { &__label {

View File

@@ -1,8 +1,11 @@
$modal--background: #12191f; $modal--background: #12191f;
$modal--heading--background: #161c24; $modal--heading--background: #161c24;
$modal--heading--foreground: #7d95ab; $modal--heading--foreground: #7d95ab;
$modal--heading--border: #0f151b; $modal--heading--border: #0f151b;
$modal--sub-heading--foreground: desaturate(darken($modal--heading--foreground, 23%), 2%);
$modal--transition--duration: 0.5s; $modal--transition--duration: 0.5s;
$modal--transition--scale: 0.85; $modal--transition--scale: 0.85;
@@ -106,15 +109,6 @@ $modal--tabs--in-body--background: #11171d;
} }
} }
&__tab {
&__introduction {
color: $modal--heading--foreground;
font-size: 0.9em;
margin-bottom: $spacing-unit * 3/4;
}
}
&__header { &__header {
background: $modal--heading--background; background: $modal--heading--background;
border-radius: $modal--border-radius $modal--border-radius 0 0; border-radius: $modal--border-radius $modal--border-radius 0 0;
@@ -173,6 +167,7 @@ $modal--tabs--in-body--background: #11171d;
} }
&__footer { &__footer {
flex: 0 0 auto;
padding: 0 $modal--padding--horizontal $modal--padding--vertical $modal--padding--horizontal; padding: 0 $modal--padding--horizontal $modal--padding--vertical $modal--padding--horizontal;
.modal { .modal {
@@ -283,6 +278,10 @@ $modal--tabs--in-body--background: #11171d;
&__content { &__content {
flex: 1 0 auto; flex: 1 0 auto;
& + .modal__footer {
margin-top: $spacing-unit * 3/5;
}
} }
} }
} }
@@ -372,4 +371,21 @@ $modal--tabs--in-body--background: #11171d;
} }
} }
} }
.form {
&__section {
&__heading {
color: $modal--heading--foreground;
font-size: 0.9em;
font-weight: 500;
}
&__sub-heading {
color: $modal--sub-heading--foreground;
font-size: 0.8em;
}
}
}
} }

View File

@@ -369,7 +369,7 @@ $more-info--border: $textbox-repeater--button--border;
&--eta { &--eta {
opacity: 0; opacity: 0;
transition: opacity 1s, visibility 1s; transition: color 0.25s, opacity 1s, visibility 1s;
visibility: hidden; visibility: hidden;
.torrent__details--segment { .torrent__details--segment {

View File

@@ -1,9 +1,49 @@
import axios from 'axios'; import axios from 'axios';
import AppDispatcher from '../dispatcher/AppDispatcher';
import ActionTypes from '../constants/ActionTypes'; import ActionTypes from '../constants/ActionTypes';
import AppDispatcher from '../dispatcher/AppDispatcher';
const ClientActions = { const ClientActions = {
fetchSettings: (property) => {
return axios.get('/client/settings', {params: {property}})
.then((json = {}) => {
return json.data;
})
.then((data) => {
AppDispatcher.dispatchServerAction({
type: ActionTypes.CLIENT_SETTINGS_FETCH_REQUEST_SUCCESS,
data
});
})
.catch((error) => {
AppDispatcher.dispatchServerAction({
type: ActionTypes.CLIENT_SETTINGS_FETCH_REQUEST_ERROR,
error
});
});
},
saveSettings: (settings, options) => {
return axios.patch('/client/settings', settings)
.then((json = {}) => {
return json.data;
})
.then((data) => {
AppDispatcher.dispatchServerAction({
type: ActionTypes.CLIENT_SETTINGS_SAVE_SUCCESS,
data,
options
});
})
.catch((error) => {
AppDispatcher.dispatchServerAction({
type: ActionTypes.CLIENT_SETTINGS_SAVE_ERROR,
error,
options
});
});
},
setThrottle: (direction, throttle) => { setThrottle: (direction, throttle) => {
return axios.put('/client/settings/speed-limits', { return axios.put('/client/settings/speed-limits', {
direction, direction,

View File

@@ -16,7 +16,6 @@ const SettingsActions = {
}); });
}) })
.catch((error) => { .catch((error) => {
console.trace(error);
AppDispatcher.dispatchServerAction({ AppDispatcher.dispatchServerAction({
type: ActionTypes.SETTINGS_FETCH_REQUEST_ERROR, type: ActionTypes.SETTINGS_FETCH_REQUEST_ERROR,
error error
@@ -37,7 +36,6 @@ const SettingsActions = {
}); });
}) })
.catch((error) => { .catch((error) => {
console.trace(error);
AppDispatcher.dispatchServerAction({ AppDispatcher.dispatchServerAction({
type: ActionTypes.SETTINGS_SAVE_REQUEST_ERROR, type: ActionTypes.SETTINGS_SAVE_REQUEST_ERROR,
error error

View File

@@ -93,7 +93,6 @@ const TorrentActions = {
}); });
}) })
.catch((error) => { .catch((error) => {
console.trace(error);
AppDispatcher.dispatchServerAction({ AppDispatcher.dispatchServerAction({
type: ActionTypes.CLIENT_FETCH_TORRENTS_ERROR, type: ActionTypes.CLIENT_FETCH_TORRENTS_ERROR,
data: { data: {

View File

@@ -13,7 +13,8 @@ import TorrentListView from './components/panels/TorrentListView';
class FloodApp extends React.Component { class FloodApp extends React.Component {
componentDidMount() { componentDidMount() {
SettingsStore.fetchSettings(); SettingsStore.fetchClientSettings();
SettingsStore.fetchFloodSettings();
} }
render() { render() {

View File

@@ -20,7 +20,8 @@ export default class AddTorrentsActions extends React.Component {
} }
componentWillMount() { componentWillMount() {
let startTorrentsOnLoad = SettingsStore.getSettings('startTorrentsOnLoad'); let startTorrentsOnLoad = SettingsStore.getFloodSettings(
'startTorrentsOnLoad');
if (startTorrentsOnLoad !== true) { if (startTorrentsOnLoad !== true) {
this.setState({startTorrentsOnLoad: false}); this.setState({startTorrentsOnLoad: false});
} }
@@ -65,7 +66,7 @@ export default class AddTorrentsActions extends React.Component {
} }
handleStartTorrentsToggle(value) { handleStartTorrentsToggle(value) {
SettingsStore.saveSettings({id: 'startTorrentsOnLoad', data: value}); SettingsStore.saveFloodSettings({id: 'startTorrentsOnLoad', data: value});
if (!!this.props.onStartTorrentsToggle) { if (!!this.props.onStartTorrentsToggle) {
this.props.onStartTorrentsToggle(value); this.props.onStartTorrentsToggle(value);
} }

View File

@@ -20,7 +20,8 @@ export default class AddTorrentsDestination extends React.Component {
} }
componentWillMount() { componentWillMount() {
let destination = SettingsStore.getSettings('torrentDestination') || ''; let destination = SettingsStore.getFloodSettings('torrentDestination')
|| '';
if (this.props.suggested) { if (this.props.suggested) {
destination = this.props.suggested; destination = this.props.suggested;
} }

View File

@@ -1,16 +1,19 @@
import classnames from 'classnames'; import classnames from 'classnames';
import React from 'react'; import React from 'react';
import BandwidthTab from '../settings/BandwidthTab';
import ConnectivityTab from '../settings/ConnectivityTab';
import EventTypes from '../../constants/EventTypes'; import EventTypes from '../../constants/EventTypes';
import LoadingIndicatorDots from '../icons/LoadingIndicatorDots'; import LoadingIndicatorDots from '../icons/LoadingIndicatorDots';
import Modal from './Modal'; import Modal from './Modal';
import SettingsStore from '../../stores/SettingsStore'; import SettingsStore from '../../stores/SettingsStore';
import SpeedLimitTab from '../settings/SpeedLimitTab'; import StorageTab from '../settings/StorageTab';
const METHODS_TO_BIND = [ const METHODS_TO_BIND = [
'handleSaveSettingsClick', 'handleSaveSettingsClick',
'handleSaveSettingsError', 'handleSaveSettingsError',
'handleSettingsChange', 'handleClientSettingsChange',
'handleFloodSettingsChange',
'handleSettingsStoreChange' 'handleSettingsStoreChange'
]; ];
@@ -20,7 +23,10 @@ export default class SettingsModal extends React.Component {
this.state = { this.state = {
isSavingSettings: false, isSavingSettings: false,
settings: SettingsStore.getSettings() changedClientSettings: {},
changedFloodSettings: {},
clientSettings: SettingsStore.getClientSettings(),
floodSettings: SettingsStore.getFloodSettings()
}; };
METHODS_TO_BIND.forEach((method) => { METHODS_TO_BIND.forEach((method) => {
@@ -33,12 +39,14 @@ export default class SettingsModal extends React.Component {
this.handleSettingsStoreChange); this.handleSettingsStoreChange);
SettingsStore.listen(EventTypes.SETTINGS_SAVE_REQUEST_ERROR, SettingsStore.listen(EventTypes.SETTINGS_SAVE_REQUEST_ERROR,
this.handleSaveSettingsError); this.handleSaveSettingsError);
SettingsStore.fetchSettings('speedLimits'); SettingsStore.fetchFloodSettings('speedLimits');
} }
componentWillUnmount() { componentWillUnmount() {
SettingsStore.unlisten(EventTypes.SETTINGS_CHANGE, SettingsStore.unlisten(EventTypes.SETTINGS_CHANGE,
this.handleSettingsStoreChange); this.handleSettingsStoreChange);
SettingsStore.unlisten(EventTypes.SETTINGS_SAVE_REQUEST_ERROR,
this.handleSaveSettingsError);
} }
getActions() { getActions() {
@@ -75,14 +83,22 @@ export default class SettingsModal extends React.Component {
handleSaveSettingsClick() { handleSaveSettingsClick() {
this.setState({isSavingSettings: true}); this.setState({isSavingSettings: true});
let settingsToSave = Object.keys(this.state.settings).map((settingsKey) => { let floodSettings = Object.keys(this.state.changedFloodSettings).map((settingsKey) => {
return { return {
id: settingsKey, id: settingsKey,
data: this.state.settings[settingsKey] data: this.state.changedFloodSettings[settingsKey]
}; };
}); });
SettingsStore.saveSettings(settingsToSave, {dismissModal: true, notify: true}); let clientSettings = Object.keys(this.state.changedClientSettings).map((settingsKey) => {
return {
id: settingsKey,
data: this.state.changedClientSettings[settingsKey]
};
});
SettingsStore.saveFloodSettings(floodSettings, {dismissModal: true, notify: true});
SettingsStore.saveClientSettings(clientSettings, {dismissModal: true, notify: true});
} }
handleSaveSettingsError() { handleSaveSettingsError() {
@@ -95,13 +111,23 @@ export default class SettingsModal extends React.Component {
handleSettingsStoreChange() { handleSettingsStoreChange() {
this.setState({ this.setState({
settings: SettingsStore.getSettings() clientSettings: SettingsStore.getClientSettings(),
floodSettings: SettingsStore.getFloodSettings()
}); });
} }
handleSettingsChange(changedSettings) { handleFloodSettingsChange(changedSettings) {
let settings = this.mergeObjects(this.state.settings, changedSettings); let floodSettings = this.mergeObjects(this.state.floodSettings, changedSettings);
this.setState({settings}); let changedFloodSettings = this.mergeObjects(this.state.changedFloodSettings, changedSettings);
this.setState({floodSettings, changedFloodSettings});
}
handleClientSettingsChange(changedSettings) {
let clientSettings = this.mergeObjects(this.state.clientSettings, changedSettings);
let changedClientSettings = this.mergeObjects(this.state.changedClientSettings, changedSettings);
this.setState({clientSettings, changedClientSettings});
} }
mergeObjects(objA, objB) { mergeObjects(objA, objB) {
@@ -123,13 +149,30 @@ export default class SettingsModal extends React.Component {
render() { render() {
let tabs = { let tabs = {
'speed-limit': { bandwidth: {
content: SpeedLimitTab, content: BandwidthTab,
props: { props: {
onSettingsChange: this.handleSettingsChange, onClientSettingsChange: this.handleClientSettingsChange,
settings: this.state.settings onSettingsChange: this.handleFloodSettingsChange,
settings: this.mergeObjects(this.state.floodSettings, this.state.clientSettings)
}, },
label: 'Speed Limits' label: 'Bandwidth'
},
connectivity: {
content: ConnectivityTab,
props: {
onClientSettingsChange: this.handleClientSettingsChange,
settings: this.state.clientSettings
},
label: 'Connectivity'
},
storage: {
content: StorageTab,
props: {
onClientSettingsChange: this.handleClientSettingsChange,
settings: this.state.clientSettings
},
label: 'Storage'
} }
}; };

View File

@@ -0,0 +1,195 @@
import React from 'react';
import SettingsTab from './SettingsTab';
const METHODS_TO_BIND = ['handleDownloadTextChange', 'handleUploadTextChange'];
export default class BandwidthTab extends SettingsTab {
constructor() {
super();
this.state = {
downloadValue: null,
uploadValue: null
};
METHODS_TO_BIND.forEach((method) => {
this[method] = this[method].bind(this);
});
}
arrayToString(array) {
return array.join(', ');
}
getTextboxValue(input = []) {
if (Array.isArray(input)) {
return this.arrayToString(input);
}
return input;
}
handleDownloadTextChange(event) {
this.setState({
downloadValue: event.target.value
});
this.props.onSettingsChange({
speedLimits: {
download: this.processSpeedsForSave(event.target.value),
upload: this.processSpeedsForSave(this.getUploadValue())
}
});
}
handleUploadTextChange(event) {
this.setState({
uploadValue: event.target.value
});
this.props.onSettingsChange({
speedLimits: {
download: this.processSpeedsForSave(this.getDownloadValue()),
upload: this.processSpeedsForSave(event.target.value)
}
});
}
getDownloadValue() {
let displayedValue = this.state.downloadValue;
if (displayedValue == null && this.props.settings.speedLimits != null) {
displayedValue = this.processSpeedsForDisplay(this.props.settings.speedLimits.download);
}
return displayedValue;
}
getUploadValue() {
let displayedValue = this.state.uploadValue;
if (displayedValue == null && this.props.settings.speedLimits != null) {
displayedValue = this.processSpeedsForDisplay(this.props.settings.speedLimits.upload);
}
return displayedValue;
}
processSpeedsForDisplay(speeds = []) {
if (!speeds || speeds.length === 0) {
return;
}
return this.arrayToString(speeds.map((speed) => {
return Number(speed) / 1024;
}));
}
processSpeedsForSave(speeds = '') {
if (speeds === '') {
return [];
}
return this.stringToArray(speeds).map((speed) => {
return Number(speed) * 1024;
});
}
stringToArray(string = '') {
return string.replace(/\s/g, '').split(',');
}
render() {
let downloadValue = this.getDownloadValue() || 0;
let uploadValue = this.getUploadValue() || 0;
return (
<div className="form">
<div className="form__section">
<p className="form__section__heading">
Speed Limit Dropdown Presets
</p>
<p className="form__section__sub-heading">
Enter a comma-separated list of speeds in kB. 0 represents unlimited.
</p>
<div className="form__row">
<div className="form__column">
<label className="form__label">
Download Presets
</label>
<input className="textbox" type="text"
onChange={this.handleDownloadTextChange}
value={downloadValue} />
</div>
<div className="form__column">
<label className="form__label">
Upload Presets
</label>
<input className="textbox" type="text"
onChange={this.handleUploadTextChange}
value={uploadValue} />
</div>
</div>
</div>
<div className="form__section">
<div className="form__section__heading">
Slot Availability
</div>
<div className="form__row">
<div className="form__column">
<label className="form__label">
Upload Slots Per Torrent
</label>
<input className="textbox" type="text"
onChange={this.handleClientSettingFieldChange.bind(this, 'throttleMaxUploads')}
value={this.getFieldValue('throttleMaxUploads')} />
</div>
<div className="form__column">
<label className="form__label">
Upload Slots Divider
</label>
<input className="textbox" type="text"
onChange={this.handleClientSettingFieldChange.bind(this, 'throttleMaxUploadsDiv')}
value={this.getFieldValue('throttleMaxUploadsDiv')} />
</div>
<div className="form__column">
<label className="form__label">
Upload Slots Global
</label>
<input className="textbox" type="text"
onChange={this.handleClientSettingFieldChange.bind(this, 'throttleMaxUploadsGlobal')}
value={this.getFieldValue('throttleMaxUploadsGlobal')} />
</div>
</div>
<div className="form__row">
<div className="form__column">
<label className="form__label">
Download Slots Per Torrent
</label>
<input className="textbox" type="text"
onChange={this.handleClientSettingFieldChange.bind(this, 'throttleMaxDownloads')}
value={this.getFieldValue('throttleMaxDownloads')} />
</div>
<div className="form__column">
<label className="form__label">
Download Slots Divider
</label>
<input className="textbox" type="text"
onChange={this.handleClientSettingFieldChange.bind(this, 'throttleMaxDownloadsDiv')}
value={this.getFieldValue('throttleMaxDownloadsDiv')} />
</div>
<div className="form__column">
<label className="form__label">
Download Slots Global
</label>
<input className="textbox" type="text"
onChange={this.handleClientSettingFieldChange.bind(this, 'throttleMaxDownloadsGlobal')}
value={this.getFieldValue('throttleMaxDownloadsGlobal')} />
</div>
</div>
</div>
</div>
);
}
}

View File

@@ -0,0 +1,115 @@
import _ from 'lodash';
import React from 'react';
import Checkbox from '../forms/Checkbox';
import SettingsTab from './SettingsTab';
export default class ConnectivityTab extends SettingsTab {
constructor() {
super(...arguments);
this.state = {};
}
render() {
return (
<div className="form">
<div className="form__section">
<div className="form__section__heading">
Listening Port
</div>
<div className="form__row">
<div className="form__column form__column--small">
<label className="form__label">
Port Range
</label>
<input className="textbox" type="text"
onChange={this.handleClientSettingFieldChange.bind(this, 'networkPortRange')}
value={this.getFieldValue('networkPortRange')} />
</div>
<div className="form__column form__column--auto form__column--unlabled">
<Checkbox
checked={this.getFieldValue('networkPortRandom') === '1'}
onChange={this.handleClientSettingCheckboxChange.bind(this, 'networkPortRandom')}>
Randomize Port
</Checkbox>
</div>
<div className="form__column form__column--auto form__column--unlabled">
<Checkbox
checked={this.getFieldValue('networkPortOpen') === '1'}
onChange={this.handleClientSettingCheckboxChange.bind(this, 'networkPortOpen')}>
Open Port
</Checkbox>
</div>
</div>
</div>
<div className="form__section">
<div className="form__section__heading">
DHT
</div>
<div className="form__row">
<div className="form__column form__column--small">
<label className="form__label">
Port
</label>
<input className="textbox" type="text"
onChange={this.handleClientSettingFieldChange.bind(this, 'dhtPort')}
value={this.getFieldValue('dhtPort')} />
</div>
</div>
</div>
<div className="form__section">
<div className="form__section__heading">
Peers
</div>
<div className="form__row">
<div className="form__column">
<label className="form__label">
Minimum Peers
</label>
<input className="textbox" type="text"
onChange={this.handleClientSettingFieldChange.bind(this, 'throttleMinPeersNormal')}
value={this.getFieldValue('throttleMinPeersNormal')} />
</div>
<div className="form__column">
<label className="form__label">
Maximum Peers
</label>
<input className="textbox" type="text"
onChange={this.handleClientSettingFieldChange.bind(this, 'throttleMaxPeersNormal')}
value={this.getFieldValue('throttleMaxPeersNormal')} />
</div>
</div>
<div className="form__row">
<div className="form__column">
<label className="form__label">
Minimum Peers Seeding
</label>
<input className="textbox" type="text"
onChange={this.handleClientSettingFieldChange.bind(this, 'throttleMinPeersSeed')}
value={this.getFieldValue('throttleMinPeersSeed')} />
</div>
<div className="form__column">
<label className="form__label">
Maximum Peers Seeding
</label>
<input className="textbox" type="text"
onChange={this.handleClientSettingFieldChange.bind(this, 'throttleMaxPeersSeed')}
value={this.getFieldValue('throttleMaxPeersSeed')} />
</div>
</div>
<div className="form__row">
<div className="form__column form__column--half">
<label className="form__label">
Amount Desired
</label>
<input className="textbox" type="text"
onChange={this.handleClientSettingFieldChange.bind(this, 'trackersNumWant')}
value={this.getFieldValue('trackersNumWant')} />
</div>
</div>
</div>
</div>
);
}
}

View File

@@ -0,0 +1,36 @@
import React from 'react';
const METHODS_TO_BIND = ['handleClientSettingFieldChange'];
export default class SettingsTab extends React.Component {
constructor() {
super();
METHODS_TO_BIND.forEach((method) => {
this[method] = this[method].bind(this);
});
}
getFieldValue(fieldName) {
if (this.state[fieldName] == null) {
return this.props.settings[fieldName] || '';
}
return this.state[fieldName];
}
handleClientSettingFieldChange(fieldName, event) {
let newState = {[fieldName]: event.target.value};
this.setState(newState);
this.props.onClientSettingsChange(newState);
}
handleClientSettingCheckboxChange(fieldName, value) {
let checkedValue = value ? '1' : '0';
let newState = {[fieldName]: checkedValue};
this.setState(newState);
this.props.onClientSettingsChange(newState);
}
}

View File

@@ -1,133 +0,0 @@
import React from 'react';
const METHODS_TO_BIND = ['handleDownloadTextChange', 'handleUploadTextChange'];
export default class SpeedLimitTab extends React.Component {
constructor() {
super();
this.state = {
downloadValue: null,
uploadValue: null
};
METHODS_TO_BIND.forEach((method) => {
this[method] = this[method].bind(this);
});
}
arrayToString(array) {
return array.join(', ');
}
getTextboxValue(input = []) {
if (Array.isArray(input)) {
return this.arrayToString(input);
}
return input;
}
handleDownloadTextChange(event) {
this.setState({
downloadValue: event.target.value
});
this.props.onSettingsChange({
speedLimits: {
download: this.processSpeedsForSave(event.target.value)
}
});
}
handleUploadTextChange(event) {
this.setState({
uploadValue: event.target.value
});
this.props.onSettingsChange({
speedLimits: {
upload: this.processSpeedsForSave(event.target.value)
}
});
}
getDownloadValue() {
let displayedValue = this.state.downloadValue;
if (displayedValue == null && this.props.settings.speedLimits != null) {
displayedValue = this.processSpeedsForDisplay(this.props.settings.speedLimits.download);
}
return displayedValue;
}
getUploadValue() {
let displayedValue = this.state.uploadValue;
if (displayedValue == null && this.props.settings.speedLimits != null) {
displayedValue = this.processSpeedsForDisplay(this.props.settings.speedLimits.upload);
}
return displayedValue;
}
processSpeedsForDisplay(speeds = []) {
if (!speeds || speeds.length === 0) {
return;
}
return this.arrayToString(speeds.map((speed) => {
return Number(speed) / 1024;
}));
}
processSpeedsForSave(speeds = '') {
if (speeds === '') {
return [];
}
return this.stringToArray(speeds).map((speed) => {
return Number(speed) * 1024;
});
}
stringToArray(string = '') {
return string.replace(/\s/g, '').split(',');
}
render() {
let downloadValue = this.getDownloadValue() || 0;
let uploadValue = this.getUploadValue() || 0;
return (
<div>
<p className="modal__tab__introduction">
Provide a comma-separated list of speed values (in kilobytes per second). 0 represents unlimited.
</p>
<div className="form">
<div className="form__row">
<div className="form__column">
<label className="form__label">
Download Presets
</label>
<input className="textbox" type="text"
onChange={this.handleDownloadTextChange}
value={downloadValue} />
</div>
</div>
<div className="form__row">
<div className="form__column">
<label className="form__label">
Upload Presets
</label>
<input className="textbox" type="text"
onChange={this.handleUploadTextChange}
value={uploadValue} />
</div>
</div>
</div>
</div>
);
}
}

View File

@@ -0,0 +1,35 @@
import _ from 'lodash';
import React from 'react';
import Checkbox from '../forms/Checkbox';
import SettingsTab from './SettingsTab';
export default class StorageTab extends SettingsTab {
constructor() {
super(...arguments);
this.state = {};
}
render() {
return (
<div className="form">
<div className="form__section">
<div className="form__section__heading">
Directories
</div>
<div className="form__row">
<div className="form__column">
<label className="form__label">
Default Download Directory
</label>
<input className="textbox" type="text"
onChange={this.handleClientSettingCheckboxChange.bind(this, 'directoryDefault')}
value={this.getFieldValue('directoryDefault')} />
</div>
</div>
</div>
</div>
);
}
}

View File

@@ -16,7 +16,7 @@ class SpeedLimitDropdown extends React.Component {
super(); super();
this.state = { this.state = {
speedLimits: SettingsStore.getSettings('speedLimits'), speedLimits: SettingsStore.getFloodSettings('speedLimits'),
throttle: null throttle: null
}; };
@@ -30,7 +30,7 @@ class SpeedLimitDropdown extends React.Component {
this.handleSettingsFetchRequestSuccess); this.handleSettingsFetchRequestSuccess);
TransferDataStore.listen(EventTypes.CLIENT_TRANSFER_DATA_REQUEST_SUCCESS, TransferDataStore.listen(EventTypes.CLIENT_TRANSFER_DATA_REQUEST_SUCCESS,
this.onTransferDataRequestSuccess); this.onTransferDataRequestSuccess);
SettingsStore.fetchSettings('speedLimits'); SettingsStore.fetchFloodSettings('speedLimits');
TransferDataStore.fetchTransferData(); TransferDataStore.fetchTransferData();
} }
@@ -129,7 +129,7 @@ class SpeedLimitDropdown extends React.Component {
} }
handleSettingsFetchRequestSuccess() { handleSettingsFetchRequestSuccess() {
let speedLimits = SettingsStore.getSettings('speedLimits'); let speedLimits = SettingsStore.getFloodSettings('speedLimits');
if (!!speedLimits) { if (!!speedLimits) {
this.setState({speedLimits}); this.setState({speedLimits});

View File

@@ -29,7 +29,7 @@ export default class ActionBar extends React.Component {
super(); super();
this.state = { this.state = {
sortBy: SettingsStore.getSettings('sortTorrents') sortBy: SettingsStore.getFloodSettings('sortTorrents')
}; };
METHODS_TO_BIND.forEach((method) => { METHODS_TO_BIND.forEach((method) => {
@@ -40,7 +40,7 @@ export default class ActionBar extends React.Component {
componentDidMount() { componentDidMount() {
this.onSortChange(); this.onSortChange();
SettingsStore.listen(EventTypes.SETTINGS_CHANGE, this.onSortChange); SettingsStore.listen(EventTypes.SETTINGS_CHANGE, this.onSortChange);
SettingsStore.fetchSettings('sortTorrents'); SettingsStore.fetchFloodSettings('sortTorrents');
} }
componentWillUnmount() { componentWillUnmount() {
@@ -99,7 +99,7 @@ export default class ActionBar extends React.Component {
} }
handleSortChange(sortBy) { handleSortChange(sortBy) {
SettingsStore.saveSettings({id: 'sortTorrents', data: sortBy}); SettingsStore.saveFloodSettings({id: 'sortTorrents', data: sortBy});
UIActions.setTorrentsSort(sortBy); UIActions.setTorrentsSort(sortBy);
} }
@@ -112,7 +112,7 @@ export default class ActionBar extends React.Component {
} }
onSortChange() { onSortChange() {
let sortBy = SettingsStore.getSettings('sortTorrents'); let sortBy = SettingsStore.getFloodSettings('sortTorrents');
TorrentFilterStore.setTorrentsSort(sortBy); TorrentFilterStore.setTorrentsSort(sortBy);
this.setState({sortBy}); this.setState({sortBy});
} }

View File

@@ -21,9 +21,14 @@ const ActionTypes = {
CLIENT_SET_FILE_PRIORITY_SUCCESS: 'CLIENT_SET_FILE_PRIORITY_SUCCESS', CLIENT_SET_FILE_PRIORITY_SUCCESS: 'CLIENT_SET_FILE_PRIORITY_SUCCESS',
CLIENT_SET_THROTTLE_ERROR: 'CLIENT_SET_THROTTLE_ERROR', CLIENT_SET_THROTTLE_ERROR: 'CLIENT_SET_THROTTLE_ERROR',
CLIENT_SET_THROTTLE_SUCCESS: 'CLIENT_SET_THROTTLE_SUCCESS', CLIENT_SET_THROTTLE_SUCCESS: 'CLIENT_SET_THROTTLE_SUCCESS',
CLIENT_SETTINGS_FETCH_REQUEST_ERROR: 'CLIENT_SETTINGS_FETCH_REQUEST_ERROR',
CLIENT_SETTINGS_FETCH_REQUEST_SUCCESS: 'CLIENT_SETTINGS_FETCH_REQUEST_SUCCESS',
CLIENT_SETTINGS_SAVE_ERROR: 'CLIENT_SETTINGS_SAVE_ERROR',
CLIENT_SETTINGS_SAVE_SUCCESS: 'CLIENT_SETTINGS_SAVE_SUCCESS',
CLIENT_START_TORRENT_ERROR: 'CLIENT_START_TORRENT_ERROR', CLIENT_START_TORRENT_ERROR: 'CLIENT_START_TORRENT_ERROR',
CLIENT_START_TORRENT_SUCCESS: 'CLIENT_START_TORRENT_SUCCESS', CLIENT_START_TORRENT_SUCCESS: 'CLIENT_START_TORRENT_SUCCESS',
CLIENT_STOP_TORRENT_ERROR: 'CLIENT_STOP_TORRENT_ERROR', CLIENT_STOP_TORRENT_ERROR: 'CLIENT_STOP_TORRENT_ERROR',
CLIENT_STOP_TORRENT_SUCCESS: 'CLIENT_STOP_TORRENT_SUCCESS',
SETTINGS_FETCH_REQUEST_SUCCESS: 'SETTINGS_FETCH_REQUEST_SUCCESS', SETTINGS_FETCH_REQUEST_SUCCESS: 'SETTINGS_FETCH_REQUEST_SUCCESS',
SETTINGS_FETCH_REQUEST_ERROR: 'SETTINGS_FETCH_REQUEST_ERROR', SETTINGS_FETCH_REQUEST_ERROR: 'SETTINGS_FETCH_REQUEST_ERROR',
SETTINGS_SAVE_REQUEST_SUCCESS: 'SETTINGS_SAVE_REQUEST_SUCCESS', SETTINGS_SAVE_REQUEST_SUCCESS: 'SETTINGS_SAVE_REQUEST_SUCCESS',

View File

@@ -5,6 +5,10 @@ const EventTypes = {
CLIENT_SET_THROTTLE_SUCCESS: 'CLIENT_SET_THROTTLE_SUCCESS', CLIENT_SET_THROTTLE_SUCCESS: 'CLIENT_SET_THROTTLE_SUCCESS',
CLIENT_MOVE_TORRENTS_REQUEST_ERROR: 'CLIENT_MOVE_TORRENTS_REQUEST_ERROR', CLIENT_MOVE_TORRENTS_REQUEST_ERROR: 'CLIENT_MOVE_TORRENTS_REQUEST_ERROR',
CLIENT_MOVE_TORRENTS_SUCCESS: 'CLIENT_MOVE_TORRENTS_SUCCESS', CLIENT_MOVE_TORRENTS_SUCCESS: 'CLIENT_MOVE_TORRENTS_SUCCESS',
CLIENT_SETTINGS_FETCH_REQUEST_ERROR: 'CLIENT_SETTINGS_FETCH_REQUEST_ERROR',
CLIENT_SETTINGS_FETCH_REQUEST_SUCCESS: 'CLIENT_SETTINGS_FETCH_REQUEST_SUCCESS',
CLIENT_SETTINGS_SAVE_REQUEST_ERROR: 'CLIENT_SETTINGS_SAVE_REQUEST_ERROR',
CLIENT_SETTINGS_SAVE_REQUEST_SUCCESS: 'CLIENT_SETTINGS_SAVE_REQUEST_SUCCESS',
CLIENT_TORRENTS_REQUEST_ERROR: 'CLIENT_TORRENTS_REQUEST_ERROR', CLIENT_TORRENTS_REQUEST_ERROR: 'CLIENT_TORRENTS_REQUEST_ERROR',
CLIENT_TORRENT_STATUS_COUNT_CHANGE: 'CLIENT_TORRENT_STATUS_COUNT_CHANGE', CLIENT_TORRENT_STATUS_COUNT_CHANGE: 'CLIENT_TORRENT_STATUS_COUNT_CHANGE',
CLIENT_TORRENT_STATUS_COUNT_REQUEST_ERROR: 'CLIENT_TORRENT_STATUS_COUNT_REQUEST_ERROR', CLIENT_TORRENT_STATUS_COUNT_REQUEST_ERROR: 'CLIENT_TORRENT_STATUS_COUNT_REQUEST_ERROR',

View File

@@ -1,6 +1,7 @@
import ActionTypes from '../constants/ActionTypes'; import ActionTypes from '../constants/ActionTypes';
import AppDispatcher from '../dispatcher/AppDispatcher'; import AppDispatcher from '../dispatcher/AppDispatcher';
import BaseStore from './BaseStore'; import BaseStore from './BaseStore';
import ClientActions from '../actions/ClientActions';
import EventTypes from '../constants/EventTypes'; import EventTypes from '../constants/EventTypes';
import NotificationStore from './NotificationStore'; import NotificationStore from './NotificationStore';
import SettingsActions from '../actions/SettingsActions'; import SettingsActions from '../actions/SettingsActions';
@@ -10,8 +11,15 @@ class SettingsStoreClass extends BaseStore {
constructor() { constructor() {
super(); super();
this.fetchStatus = {
clientSettingsFetched: false,
floodSettingsFetched: false
};
this.clientSettings = {};
// Default settings are overridden by settings stored in database. // Default settings are overridden by settings stored in database.
this.settings = { this.floodSettings = {
sortTorrents: { sortTorrents: {
direction: 'desc', direction: 'desc',
displayName: 'Date Added', displayName: 'Date Added',
@@ -25,16 +33,61 @@ class SettingsStoreClass extends BaseStore {
}; };
} }
fetchSettings(property) { fetchClientSettings(property) {
ClientActions.fetchSettings(property);
}
fetchFloodSettings(property) {
SettingsActions.fetchSettings(property); SettingsActions.fetchSettings(property);
} }
getSettings(property) { getClientSettings(property) {
if (property) { if (property) {
return this.settings[property]; return this.clientSettings[property];
} }
return this.settings; return Object.assign({}, this.clientSettings);
}
getFloodSettings(property) {
if (property) {
return this.floodSettings[property];
}
return Object.assign({}, this.floodSettings);
}
handleClientSettingsFetchSuccess(settings) {
this.fetchStatus.clientSettingsFetched = true;
this.clientSettings = settings;
this.emit(EventTypes.CLIENT_SETTINGS_FETCH_REQUEST_SUCCESS);
this.processSettingsState();
}
handleClientSettingsFetchError(error) {
this.emit(EventTypes.CLIENT_SETTINGS_FETCH_REQUEST_ERROR);
}
handleClientSettingsSaveRequestError() {
this.emit(EventTypes.CLIENT_SETTINGS_SAVE_REQUEST_ERROR);
}
handleClientSettingsSaveRequestSuccess(data, options) {
this.emit(EventTypes.CLIENT_SETTINGS_SAVE_REQUEST_SUCCESS);
if (options.notify) {
NotificationStore.add({
adverb: 'Successfully',
action: 'saved',
subject: 'settings',
id: 'save-settings-success'
});
}
if (options.dismissModal) {
UIStore.dismissModal();
}
} }
handleSettingsFetchError(error) { handleSettingsFetchError(error) {
@@ -42,12 +95,14 @@ class SettingsStoreClass extends BaseStore {
} }
handleSettingsFetchSuccess(settings) { handleSettingsFetchSuccess(settings) {
this.fetchStatus.floodSettingsFetched = true;
Object.keys(settings).forEach((property) => { Object.keys(settings).forEach((property) => {
this.settings[property] = settings[property]; this.floodSettings[property] = settings[property];
}); });
this.emit(EventTypes.SETTINGS_CHANGE);
this.emit(EventTypes.SETTINGS_FETCH_REQUEST_SUCCESS); this.emit(EventTypes.SETTINGS_FETCH_REQUEST_SUCCESS);
this.processSettingsState();
} }
handleSettingsSaveRequestError() { handleSettingsSaveRequestError() {
@@ -62,7 +117,7 @@ class SettingsStoreClass extends BaseStore {
adverb: 'Successfully', adverb: 'Successfully',
action: 'saved', action: 'saved',
subject: 'settings', subject: 'settings',
id: 'save-torrents-success' id: 'save-settings-success'
}); });
} }
@@ -71,11 +126,38 @@ class SettingsStoreClass extends BaseStore {
} }
} }
saveSettings(settings, options) { processSettingsState() {
this.settings[settings.id] = settings.data; if (this.fetchStatus.clientSettingsFetched
SettingsActions.saveSettings(settings, options); && this.fetchStatus.floodSettingsFetched) {
this.emit(EventTypes.SETTINGS_CHANGE); this.emit(EventTypes.SETTINGS_CHANGE);
} }
}
saveFloodSettings(settings, options) {
if (!Array.isArray(settings)) {
settings = [settings];
}
SettingsActions.saveSettings(settings, options);
this.updateLocalSettings(settings, 'floodSettings');
this.emit(EventTypes.SETTINGS_CHANGE);
}
saveClientSettings(settings, options) {
if (!Array.isArray(settings)) {
settings = [settings];
}
ClientActions.saveSettings(settings, options);
this.updateLocalSettings(settings, 'clientSettings');
this.emit(EventTypes.SETTINGS_CHANGE);
}
updateLocalSettings(settings, settingsType) {
settings.forEach((setting) => {
this[settingsType][setting.id] = setting.data;
});
}
} }
let SettingsStore = new SettingsStoreClass(); let SettingsStore = new SettingsStoreClass();
@@ -84,18 +166,30 @@ SettingsStore.dispatcherID = AppDispatcher.register((payload) => {
const {action, source} = payload; const {action, source} = payload;
switch (action.type) { switch (action.type) {
case ActionTypes.SETTINGS_FETCH_REQUEST_SUCCESS: case ActionTypes.CLIENT_SETTINGS_FETCH_REQUEST_ERROR:
SettingsStore.handleSettingsFetchSuccess(action.data); SettingsStore.handleClientSettingsFetchError(action.error);
break;
case ActionTypes.CLIENT_SETTINGS_FETCH_REQUEST_SUCCESS:
SettingsStore.handleClientSettingsFetchSuccess(action.data);
break; break;
case ActionTypes.SETTINGS_FETCH_REQUEST_ERROR: case ActionTypes.SETTINGS_FETCH_REQUEST_ERROR:
SettingsStore.handleSettingsFetchError(action.error); SettingsStore.handleSettingsFetchError(action.error);
break; break;
case ActionTypes.SETTINGS_FETCH_REQUEST_SUCCESS:
SettingsStore.handleSettingsFetchSuccess(action.data);
break;
case ActionTypes.SETTINGS_SAVE_REQUEST_ERROR: case ActionTypes.SETTINGS_SAVE_REQUEST_ERROR:
SettingsStore.handleSettingsSaveRequestError(action.error); SettingsStore.handleSettingsSaveRequestError(action.error);
break; break;
case ActionTypes.SETTINGS_SAVE_REQUEST_SUCCESS: case ActionTypes.SETTINGS_SAVE_REQUEST_SUCCESS:
SettingsStore.handleSettingsSaveRequestSuccess(action.data, action.options); SettingsStore.handleSettingsSaveRequestSuccess(action.data, action.options);
break; break;
case ActionTypes.CLIENT_SETTINGS_SAVE_ERROR:
SettingsStore.handleClientSettingsSaveRequestError(action.error);
break;
case ActionTypes.CLIENT_SETTINGS_SAVE_SUCCESS:
SettingsStore.handleClientSettingsSaveRequestSuccess(action.data, action.options);
break;
} }
}); });

View File

@@ -13,7 +13,7 @@ class TorrentFilterStoreClass extends BaseStore {
this.searchFilter = null; this.searchFilter = null;
this.statusFilter = 'all'; this.statusFilter = 'all';
this.trackerFilter = 'all'; this.trackerFilter = 'all';
this.sortTorrentsBy = SettingsStore.getSettings('sortTorrents'); this.sortTorrentsBy = SettingsStore.getFloodSettings('sortTorrents');
} }
fetchTorrentStatusCount() { fetchTorrentStatusCount() {

View File

@@ -104,7 +104,7 @@ class TorrentStoreClass extends BaseStore {
handleAddTorrentSuccess(response) { handleAddTorrentSuccess(response) {
this.emit(EventTypes.CLIENT_ADD_TORRENT_SUCCESS); this.emit(EventTypes.CLIENT_ADD_TORRENT_SUCCESS);
SettingsStore.saveSettings({ SettingsStore.saveFloodSettings({
id: 'torrentDestination', id: 'torrentDestination',
data: response.destination data: response.destination
}); });

View File

@@ -1,11 +1,12 @@
'use strict'; 'use strict';
let util = require('util');
let clientUtil = require('../util/clientUtil');
let fs = require('fs'); let fs = require('fs');
let mv = require('mv'); let mv = require('mv');
let path = require('path'); let path = require('path');
let util = require('util');
let clientSettingsMap = require('../../shared/constants/clientSettingsMap');
let clientUtil = require('../util/clientUtil');
let rTorrentPropMap = require('../util/rTorrentPropMap'); let rTorrentPropMap = require('../util/rTorrentPropMap');
let scgi = require('../util/scgi'); let scgi = require('../util/scgi');
let stringUtil = require('../../shared/util/stringUtil'); let stringUtil = require('../../shared/util/stringUtil');
@@ -163,6 +164,27 @@ class ClientRequest {
); );
} }
fetchSettingsMethodCall(options) {
let requestedSettings = [];
if (options.requestedSettings) {
requestedSettings = options.requestedSettings;
} else {
requestedSettings = clientSettingsMap.defaults.map((settingsKey) => {
return clientSettingsMap[settingsKey];
});
}
// Ensure client's response gets mapped to the correct requested property.
if (options.setPropertiesArr) {
options.setPropertiesArr(requestedSettings);
}
requestedSettings.forEach((settingsKey) => {
this.requests.push(this.getMethodCall(settingsKey));
});
}
getTorrentDetailsMethodCall(options) { getTorrentDetailsMethodCall(options) {
var peerParams = [options.hash, ''].concat(options.peerProps); var peerParams = [options.hash, ''].concat(options.peerProps);
var fileParams = [options.hash, ''].concat(options.fileProps); var fileParams = [options.hash, ''].concat(options.fileProps);
@@ -251,6 +273,16 @@ class ClientRequest {
}); });
} }
setSettingsMethodCall(options) {
console.log(options);
let settings = this.getEnsuredArray(options.settings);
settings.forEach((setting) => {
this.requests.push(this.getMethodCall(`${clientSettingsMap[setting.id]}.set`,
['', setting.data]));
});
}
setThrottleMethodCall(options) { setThrottleMethodCall(options) {
let methodName = 'throttle.global_down.max_rate.set'; let methodName = 'throttle.global_down.max_rate.set';
if (options.direction === 'upload') { if (options.direction === 'upload') {

View File

@@ -4,6 +4,7 @@ let fs = require('fs');
let util = require('util'); let util = require('util');
let clientResponseUtil = require('../util/clientResponseUtil'); let clientResponseUtil = require('../util/clientResponseUtil');
let clientSettingsMap = require('../../shared/constants/clientSettingsMap');
let ClientRequest = require('./ClientRequest'); let ClientRequest = require('./ClientRequest');
let clientUtil = require('../util/clientUtil'); let clientUtil = require('../util/clientUtil');
let propsMap = require('../../shared/constants/propsMap'); let propsMap = require('../../shared/constants/propsMap');
@@ -61,6 +62,33 @@ var client = {
request.send(); request.send();
}, },
getSettings: (options, callback) => {
let properties = [];
let request = new ClientRequest();
let response = {};
request.add('fetchSettings', {
options,
setPropertiesArr: (propertiesArr) => {
properties = propertiesArr;
}
});
request.postProcess((data) => {
if (!data) {
return null;
}
data.forEach((datum, index) => {
response[clientSettingsMap[properties[index]]] = datum[0];
});
return response;
});
request.onComplete(callback);
request.send();
},
getTorrentStatusCount: (callback) => { getTorrentStatusCount: (callback) => {
callback(_statusCount); callback(_statusCount);
}, },
@@ -172,6 +200,19 @@ var client = {
request.send(); request.send();
}, },
setSettings: (payloads, callback) => {
let request = new ClientRequest();
if (payloads.length === 0) {
callback({});
return;
}
request.add('setSettings', {settings: payloads});
request.onComplete(callback);
request.send();
},
setSpeedLimits: (data, callback) => { setSpeedLimits: (data, callback) => {
let request = new ClientRequest(); let request = new ClientRequest();

View File

@@ -24,23 +24,28 @@ router.post('/add-files', upload.array('torrents'), function(req, res, next) {
client.addFiles(req, ajaxUtil.getResponseFn(res)); client.addFiles(req, ajaxUtil.getResponseFn(res));
}); });
router.get('/settings', function(req, res, next) {
client.getSettings(req.query, ajaxUtil.getResponseFn(res));
});
router.patch('/settings', function(req, res, next) {
client.setSettings(req.body, ajaxUtil.getResponseFn(res));
});
router.put('/settings/speed-limits', function(req, res, next) { router.put('/settings/speed-limits', function(req, res, next) {
client.setSpeedLimits(req.body, ajaxUtil.getResponseFn(res)); client.setSpeedLimits(req.body, ajaxUtil.getResponseFn(res));
}); });
router.post('/start', function(req, res, next) { router.post('/start', function(req, res, next) {
var hashes = req.body.hashes; client.startTorrent(req.body.hashes, ajaxUtil.getResponseFn(res));
client.startTorrent(hashes, ajaxUtil.getResponseFn(res));
}); });
router.post('/stop', function(req, res, next) { router.post('/stop', function(req, res, next) {
var hashes = req.body.hashes; client.stopTorrent(req.body.hashes, ajaxUtil.getResponseFn(res));
client.stopTorrent(hashes, ajaxUtil.getResponseFn(res));
}); });
router.post('/torrent-details', function(req, res, next) { router.post('/torrent-details', function(req, res, next) {
var hash = req.body.hash; client.getTorrentDetails(req.body.hash, ajaxUtil.getResponseFn(res));
client.getTorrentDetails(hash, ajaxUtil.getResponseFn(res));
}); });
router.get('/torrents', function(req, res, next) { router.get('/torrents', function(req, res, next) {
@@ -59,6 +64,10 @@ router.post('/torrents/move', function(req, res, next) {
client.moveTorrents(req.body, ajaxUtil.getResponseFn(res)); client.moveTorrents(req.body, ajaxUtil.getResponseFn(res));
}); });
router.post('/torrents/delete', function(req, res, next) {
client.deleteTorrents(req.body.hash, ajaxUtil.getResponseFn(res));
});
router.get('/torrents/status-count', function(req, res, next) { router.get('/torrents/status-count', function(req, res, next) {
client.getTorrentStatusCount(ajaxUtil.getResponseFn(res)); client.getTorrentStatusCount(ajaxUtil.getResponseFn(res));
}); });
@@ -67,11 +76,6 @@ router.get('/torrents/tracker-count', function(req, res, next) {
client.getTorrentTrackerCount(ajaxUtil.getResponseFn(res)); client.getTorrentTrackerCount(ajaxUtil.getResponseFn(res));
}); });
router.post('/torrents/delete', function(req, res, next) {
var hash = req.body.hash;
client.deleteTorrents(hash, ajaxUtil.getResponseFn(res));
});
router.get('/methods.json', function(req, res, next) { router.get('/methods.json', function(req, res, next) {
var type = req.query.type; var type = req.query.type;
var args = req.query.args; var args = req.query.args;

View File

@@ -0,0 +1,105 @@
'use strict';
let objectUtil = require('../util/objectUtil');
const clientSettingsMap = objectUtil.reflect({
dhtPort: 'dht.port',
dhtStatus: 'dht.statistics',
directoryDefault: 'directory.default',
maxFileSize: 'system.file.max_size',
networkBindAddress: 'network.bind_address',
networkHttpCert: 'network.http.cacert',
networkHttpMaxOpen: 'network.http.max_open',
networkHttpPath: 'network.http.capath',
networkHttpProxy: 'network.http.proxy_address',
networkLocalAddress: 'network.local_address',
networkMaxOpenFiles: 'network.max_open_files',
networkMaxOpenSockets: 'network.max_open_sockets',
networkPortOpen: 'network.port_open',
networkPortRandom: 'network.port_random',
networkPortRange: 'network.port_range',
networkReceiveBufferSize: 'network.receive_buffer.size',
networkScgiDontRoute: 'network.scgi.dont_route',
networkSendBufferSize: 'network.send_buffer.size',
piecesHashOnCompletion: 'pieces.hash.on_completion',
piecesMemoryMax: 'pieces.memory.max',
piecesPreloadMinRate: 'pieces.preload.min_rate',
piecesPreloadMinSize: 'pieces.preload.min_size',
piecesPreloadType: 'pieces.preload.type',
piecesSyncAlwaysSafe: 'pieces.sync.always_safe',
piecesSyncTimeout: 'pieces.sync.timeout',
piecesSyncTimeoutSafe: 'pieces.sync.timeout_safe',
protocolPex: 'protocol.pex',
sessionOnCompletion: 'session.on_completion',
sessionPath: 'session.path',
sessionUseLock: 'session.use_lock',
systemFileSplitSize: 'system.file.split_size',
systemFileSplitSuffix: 'system.file.split_suffix',
throttleDownMax: 'throttle.global_down.max_rate',
throttleGlobalUpMax: 'throttle.global_up.max_rate',
throttleMaxDownloadsDiv: 'throttle.max_downloads.div',
throttleMaxDownloadsGlobal: 'throttle.max_downloads.global',
throttleMaxPeersNormal: 'throttle.max_peers.normal',
throttleMaxPeersSeed: 'throttle.max_peers.seed',
throttleMaxDownloads: 'throttle.max_downloads',
throttleMaxDownloadsDiv: 'throttle.max_downloads.div',
throttleMaxDownloadsGlobal: 'throttle.max_downloads.global',
throttleMaxUploads: 'throttle.max_uploads',
throttleMaxUploadsDiv: 'throttle.max_uploads.div',
throttleMaxUploadsGlobal: 'throttle.max_uploads.global',
throttleMinPeersNormal: 'throttle.min_peers.normal',
throttleMinPeersSeed: 'throttle.min_peers.seed',
trackersNumWant: 'trackers.numwant',
trackersUseUdp: 'trackers.use_udp'
});
clientSettingsMap.defaults = [
'dhtPort',
'dhtStatus',
'directoryDefault',
'maxFileSize',
'networkBindAddress',
'networkHttpCert',
'networkHttpMaxOpen',
'networkHttpPath',
'networkHttpProxy',
'networkLocalAddress',
'networkMaxOpenFiles',
'networkMaxOpenSockets',
'networkPortOpen',
'networkPortRandom',
'networkPortRange',
'networkReceiveBufferSize',
'networkScgiDontRoute',
'networkSendBufferSize',
'piecesHashOnCompletion',
'piecesMemoryMax',
'piecesPreloadMinRate',
'piecesPreloadMinSize',
'piecesPreloadType',
'piecesSyncAlwaysSafe',
'piecesSyncTimeout',
'piecesSyncTimeoutSafe',
'protocolPex',
'sessionOnCompletion',
'sessionPath',
'sessionUseLock',
'systemFileSplitSize',
'systemFileSplitSuffix',
'throttleDownMax',
'throttleGlobalUpMax',
'throttleMaxDownloadsDiv',
'throttleMaxDownloadsGlobal',
'throttleMaxPeersNormal',
'throttleMaxPeersSeed',
'throttleMaxDownloads',
'throttleMaxUploads',
'throttleMaxUploadsDiv',
'throttleMaxUploadsGlobal',
'throttleMinPeersNormal',
'throttleMinPeersSeed',
'trackersNumWant',
'trackersUseUdp'
];
module.exports = clientSettingsMap;

13
shared/util/objectUtil.js Normal file
View File

@@ -0,0 +1,13 @@
'use strict';
let objectUtil = {
reflect: (hash) => {
return Object.keys(hash).reduce((memo, key) => {
memo[key] = hash[key];
memo[hash[key]] = key;
return memo;
}, {});
}
}
module.exports = objectUtil;