mirror of
https://github.com/zoriya/flood.git
synced 2025-12-19 13:45:15 +00:00
Add move torrents option to context menu
This commit is contained in:
@@ -92,6 +92,70 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.checkbox {
|
||||||
|
line-height: 1;
|
||||||
|
position: relative;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
|
||||||
|
.checkbox {
|
||||||
|
|
||||||
|
&__decoy {
|
||||||
|
border-color: $blue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
input[type="checkbox"] {
|
||||||
|
left: 0;
|
||||||
|
opacity: 0;
|
||||||
|
position: absolute;
|
||||||
|
top: 50%;
|
||||||
|
transform: translateY(-50%);
|
||||||
|
|
||||||
|
&:checked {
|
||||||
|
|
||||||
|
& + .checkbox {
|
||||||
|
|
||||||
|
&__decoy {
|
||||||
|
|
||||||
|
.icon {
|
||||||
|
fill: $blue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&__decoy {
|
||||||
|
@extend .textbox;
|
||||||
|
background: $white;
|
||||||
|
display: inline-block;
|
||||||
|
height: $spacing-unit * 3/5;
|
||||||
|
margin-right: $spacing-unit * 1.5/5;
|
||||||
|
margin-top: -2px;
|
||||||
|
padding: 0;
|
||||||
|
position: relative;
|
||||||
|
vertical-align: middle;
|
||||||
|
width: $spacing-unit * 3/5;
|
||||||
|
|
||||||
|
.icon {
|
||||||
|
fill: transparent;
|
||||||
|
height: 10px;
|
||||||
|
left: 50%;
|
||||||
|
position: absolute;
|
||||||
|
top: 50%;
|
||||||
|
transition: fill 0.25s;
|
||||||
|
transform: translate(-43%, -43%);
|
||||||
|
width: 10px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&__label {
|
||||||
|
color: darken($modal--body--foreground, 10%);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.form {
|
.form {
|
||||||
|
|
||||||
&__label {
|
&__label {
|
||||||
|
|||||||
@@ -190,6 +190,28 @@ const TorrentActions = {
|
|||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
|
moveTorrents: function(hashes, options) {
|
||||||
|
let {destination, filenames, sources, moveFiles} = options;
|
||||||
|
|
||||||
|
return axios.post('/client/torrents/move',
|
||||||
|
{hashes, destination, filenames, sources, moveFiles})
|
||||||
|
.then((json = {}) => {
|
||||||
|
return json.data;
|
||||||
|
})
|
||||||
|
.then((data) => {
|
||||||
|
AppDispatcher.dispatchServerAction({
|
||||||
|
type: ActionTypes.CLIENT_MOVE_TORRENTS_SUCCESS,
|
||||||
|
data
|
||||||
|
});
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
AppDispatcher.dispatchServerAction({
|
||||||
|
type: ActionTypes.CLIENT_MOVE_TORRENTS_ERROR,
|
||||||
|
error
|
||||||
|
});
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
pauseTorrents: function(hashes) {
|
pauseTorrents: function(hashes) {
|
||||||
return axios.post('/client/pause', {
|
return axios.post('/client/pause', {
|
||||||
hashes
|
hashes
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import axios from 'axios';
|
|||||||
|
|
||||||
import AppDispatcher from '../dispatcher/AppDispatcher';
|
import AppDispatcher from '../dispatcher/AppDispatcher';
|
||||||
import ActionTypes from '../constants/ActionTypes';
|
import ActionTypes from '../constants/ActionTypes';
|
||||||
|
import TorrentStore from '../stores/TorrentStore';
|
||||||
|
|
||||||
const UIActions = {
|
const UIActions = {
|
||||||
displayContextMenu: function(data) {
|
displayContextMenu: function(data) {
|
||||||
|
|||||||
54
client/source/scripts/components/forms/Checkbox.js
Normal file
54
client/source/scripts/components/forms/Checkbox.js
Normal file
@@ -0,0 +1,54 @@
|
|||||||
|
import classnames from 'classnames';
|
||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
import Checkmark from '../icons/Checkmark';
|
||||||
|
|
||||||
|
const METHODS_TO_BIND = ['handleCheckboxChange'];
|
||||||
|
|
||||||
|
export default class SearchBox extends React.Component {
|
||||||
|
constructor() {
|
||||||
|
super();
|
||||||
|
|
||||||
|
this.state = {checked: false};
|
||||||
|
|
||||||
|
METHODS_TO_BIND.forEach((method) => {
|
||||||
|
this[method] = this[method].bind(this);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
componentDidMount() {
|
||||||
|
if (this.state.checked !== this.props.checked) {
|
||||||
|
this.setState({checked: this.props.checked});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
handleCheckboxChange() {
|
||||||
|
let currentCheckedState = this.state.checked;
|
||||||
|
let newCheckedState = !currentCheckedState;
|
||||||
|
|
||||||
|
this.setState({checked: newCheckedState})
|
||||||
|
|
||||||
|
if (this.props.onChange) {
|
||||||
|
this.props.onChange(newCheckedState);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
let classes = classnames('checkbox', {
|
||||||
|
'is-checked': this.state.checked
|
||||||
|
});
|
||||||
|
|
||||||
|
return (
|
||||||
|
<label className={classes}>
|
||||||
|
<input type="checkbox" checked={this.state.checked}
|
||||||
|
onChange={this.handleCheckboxChange} />
|
||||||
|
<span className="checkbox__decoy">
|
||||||
|
<Checkmark />
|
||||||
|
</span>
|
||||||
|
<span className="checkbox__label">
|
||||||
|
{this.props.children}
|
||||||
|
</span>
|
||||||
|
</label>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
14
client/source/scripts/components/icons/Checkmark.js
Normal file
14
client/source/scripts/components/icons/Checkmark.js
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
import BaseIcon from './BaseIcon';
|
||||||
|
|
||||||
|
export default class Checkmark extends BaseIcon {
|
||||||
|
render() {
|
||||||
|
return (
|
||||||
|
<svg className={`icon icon--checkmark ${this.props.className}`}
|
||||||
|
xmlns={this.getXmlns()} viewBox={this.getViewBox()}>
|
||||||
|
<polygon points="55.5,18.6 46.1,8.7 24.4,31.5 13.9,20.4 4.5,30.3 24.4,51.3 24.4,51.3 24.4,51.3"/>
|
||||||
|
</svg>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -23,7 +23,11 @@ export default class AddTorrents extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
componentWillMount() {
|
componentWillMount() {
|
||||||
this.setState({destination: UIStore.getLatestTorrentLocation()});
|
let destination = UIStore.getLatestTorrentLocation();
|
||||||
|
if (this.props.suggested) {
|
||||||
|
destination = this.props.suggested;
|
||||||
|
}
|
||||||
|
this.setState({destination});
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
@@ -46,6 +50,10 @@ export default class AddTorrents extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
onLatestTorrentLocationChange() {
|
onLatestTorrentLocationChange() {
|
||||||
|
if (this.props.suggested) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
let destination = UIStore.getLatestTorrentLocation();
|
let destination = UIStore.getLatestTorrentLocation();
|
||||||
|
|
||||||
if (this.props.onChange) {
|
if (this.props.onChange) {
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import CSSTransitionGroup from 'react-addons-css-transition-group';
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
|
||||||
import AddTorrents from './AddTorrents';
|
import AddTorrents from './AddTorrents';
|
||||||
|
import MoveTorrents from './MoveTorrents';
|
||||||
import EventTypes from '../../constants/EventTypes';
|
import EventTypes from '../../constants/EventTypes';
|
||||||
import Modal from './Modal';
|
import Modal from './Modal';
|
||||||
import UIActions from '../../actions/UIActions';
|
import UIActions from '../../actions/UIActions';
|
||||||
@@ -81,6 +82,11 @@ export default class Modals extends React.Component {
|
|||||||
heading={modalOptions.heading} />
|
heading={modalOptions.heading} />
|
||||||
);
|
);
|
||||||
break;
|
break;
|
||||||
|
case 'move-torrents':
|
||||||
|
modal = (
|
||||||
|
<MoveTorrents dismiss={this.dismissModal} />
|
||||||
|
);
|
||||||
|
break;
|
||||||
case 'add-torrents':
|
case 'add-torrents':
|
||||||
modal = (
|
modal = (
|
||||||
<AddTorrents dismiss={this.dismissModal} />
|
<AddTorrents dismiss={this.dismissModal} />
|
||||||
|
|||||||
152
client/source/scripts/components/modals/MoveTorrents.js
Normal file
152
client/source/scripts/components/modals/MoveTorrents.js
Normal file
@@ -0,0 +1,152 @@
|
|||||||
|
import _ from 'lodash';
|
||||||
|
import classnames from 'classnames';
|
||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
import AddTorrentsDestination from './AddTorrentsDestination';
|
||||||
|
import AppDispatcher from '../../dispatcher/AppDispatcher';
|
||||||
|
import Checkbox from '../forms/Checkbox';
|
||||||
|
import EventTypes from '../../constants/EventTypes';
|
||||||
|
import LoadingIndicatorDots from '../icons/LoadingIndicatorDots';
|
||||||
|
import Modal from './Modal';
|
||||||
|
import ModalActions from './ModalActions';
|
||||||
|
import TorrentActions from '../../actions/TorrentActions';
|
||||||
|
import TorrentStore from '../../stores/TorrentStore';
|
||||||
|
|
||||||
|
const METHODS_TO_BIND = [
|
||||||
|
'confirmMoveTorrents',
|
||||||
|
'handleCheckboxChange',
|
||||||
|
'handleDestinationChange',
|
||||||
|
'handleTextboxChange',
|
||||||
|
'onMoveError'
|
||||||
|
];
|
||||||
|
|
||||||
|
export default class AddTorrents extends React.Component {
|
||||||
|
constructor() {
|
||||||
|
super();
|
||||||
|
|
||||||
|
this.state = {
|
||||||
|
moveTorrentsError: null,
|
||||||
|
destination: null,
|
||||||
|
isExpanded: false,
|
||||||
|
isSettingDownloadPath: false,
|
||||||
|
moveTorrents: false,
|
||||||
|
originalSource: null
|
||||||
|
};
|
||||||
|
|
||||||
|
METHODS_TO_BIND.forEach((method) => {
|
||||||
|
this[method] = this[method].bind(this);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
componentWillMount() {
|
||||||
|
let filenames = TorrentStore.getSelectedTorrentsFilename();
|
||||||
|
let sources = TorrentStore.getSelectedTorrentsDownloadLocations();
|
||||||
|
|
||||||
|
if (sources.length === 1) {
|
||||||
|
let originalSource = this.removeTrailingFilename(sources[0], filenames[0]);
|
||||||
|
this.setState({originalSource, destination: originalSource});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
componentDidMount() {
|
||||||
|
TorrentStore.listen(EventTypes.CLIENT_MOVE_TORRENTS_REQUEST_ERROR, this.onMoveError);
|
||||||
|
}
|
||||||
|
|
||||||
|
componentWillUnmount() {
|
||||||
|
TorrentStore.unlisten(EventTypes.CLIENT_MOVE_TORRENTS_REQUEST_ERROR, this.onMoveError);
|
||||||
|
}
|
||||||
|
|
||||||
|
onMoveError() {
|
||||||
|
this.setState({isSettingDownloadPath: false});
|
||||||
|
}
|
||||||
|
|
||||||
|
confirmMoveTorrents() {
|
||||||
|
let filenames = TorrentStore.getSelectedTorrentsFilename();
|
||||||
|
let sources = TorrentStore.getSelectedTorrentsDownloadLocations();
|
||||||
|
|
||||||
|
if (sources.length) {
|
||||||
|
this.setState({isSettingDownloadPath: true});
|
||||||
|
TorrentActions.moveTorrents(TorrentStore.getSelectedTorrents(), {
|
||||||
|
destination: this.state.destination,
|
||||||
|
filenames,
|
||||||
|
moveFiles: this.state.moveTorrents,
|
||||||
|
sources
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
getActions() {
|
||||||
|
let icon = null;
|
||||||
|
let primaryButtonText = 'Set Location';
|
||||||
|
|
||||||
|
if (this.state.isSettingDownloadPath) {
|
||||||
|
icon = <LoadingIndicatorDots viewBox="0 0 32 32" />;
|
||||||
|
primaryButtonText = 'Setting...';
|
||||||
|
}
|
||||||
|
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
clickHandler: null,
|
||||||
|
content: 'Cancel',
|
||||||
|
triggerDismiss: true,
|
||||||
|
type: 'secondary'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
clickHandler: this.confirmMoveTorrents,
|
||||||
|
content: (
|
||||||
|
<span>
|
||||||
|
{icon}
|
||||||
|
{primaryButtonText}
|
||||||
|
</span>
|
||||||
|
),
|
||||||
|
supplementalClassName: icon != null ? 'has-icon' : '',
|
||||||
|
triggerDismiss: false,
|
||||||
|
type: 'primary'
|
||||||
|
}
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
handleCheckboxChange(checkboxState) {
|
||||||
|
this.setState({moveTorrents: checkboxState});
|
||||||
|
}
|
||||||
|
|
||||||
|
handleDestinationChange(destination) {
|
||||||
|
this.setState({destination});
|
||||||
|
}
|
||||||
|
|
||||||
|
handleTextboxChange(event) {
|
||||||
|
let destination = event.target.value;
|
||||||
|
this.setState({destination});
|
||||||
|
}
|
||||||
|
|
||||||
|
getContent() {
|
||||||
|
return (
|
||||||
|
<div className="form">
|
||||||
|
<AddTorrentsDestination onChange={this.handleDestinationChange}
|
||||||
|
suggested={this.state.originalSource} />
|
||||||
|
<div className="form__row">
|
||||||
|
<Checkbox onChange={this.handleCheckboxChange}>Move data</Checkbox>
|
||||||
|
</div>
|
||||||
|
<ModalActions actions={this.getActions()} />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
removeTrailingFilename(path, filename) {
|
||||||
|
let directoryPath = path.substring(0, path.length - filename.length);
|
||||||
|
|
||||||
|
if (directoryPath.charAt(directoryPath.length - 1) === '/' || directoryPath.charAt(directoryPath.length - 1) === '\\') {
|
||||||
|
directoryPath = directoryPath.substring(0, directoryPath.length - 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
return directoryPath;
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
return (
|
||||||
|
<Modal heading="Set Download Location"
|
||||||
|
dismiss={this.props.dismiss}
|
||||||
|
content={this.getContent()} />
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -18,6 +18,7 @@ import UIStore from '../../stores/UIStore';
|
|||||||
const METHODS_TO_BIND = [
|
const METHODS_TO_BIND = [
|
||||||
'onReceiveTorrentsError',
|
'onReceiveTorrentsError',
|
||||||
'onReceiveTorrentsSuccess',
|
'onReceiveTorrentsSuccess',
|
||||||
|
'handleContextMenuItemClick',
|
||||||
'handleDetailsClick',
|
'handleDetailsClick',
|
||||||
'handleRightClick',
|
'handleRightClick',
|
||||||
'handleTorrentClick',
|
'handleTorrentClick',
|
||||||
@@ -101,6 +102,10 @@ export default class TorrentListContainer extends React.Component {
|
|||||||
action: 'remove',
|
action: 'remove',
|
||||||
clickHandler,
|
clickHandler,
|
||||||
label: 'Remove'
|
label: 'Remove'
|
||||||
|
}, {
|
||||||
|
action: 'move',
|
||||||
|
clickHandler,
|
||||||
|
label: 'Set Download Location'
|
||||||
}];
|
}];
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -119,9 +124,16 @@ export default class TorrentListContainer extends React.Component {
|
|||||||
case 'remove':
|
case 'remove':
|
||||||
TorrentActions.deleteTorrents(selectedTorrents);
|
TorrentActions.deleteTorrents(selectedTorrents);
|
||||||
break;
|
break;
|
||||||
|
case 'move':
|
||||||
|
this.handleContextMenuMoveClick(selectedTorrents);
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
handleContextMenuMoveClick(hashes) {
|
||||||
|
UIActions.displayModal('move-torrents');
|
||||||
|
}
|
||||||
|
|
||||||
handleDetailsClick(torrent, event) {
|
handleDetailsClick(torrent, event) {
|
||||||
UIActions.handleDetailsClick({
|
UIActions.handleDetailsClick({
|
||||||
hash: torrent.hash,
|
hash: torrent.hash,
|
||||||
|
|||||||
@@ -130,7 +130,7 @@ export default class ContextMenu extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
ContextMenu.defaultProps = {
|
ContextMenu.defaultProps = {
|
||||||
width: 150
|
width: 200
|
||||||
};
|
};
|
||||||
|
|
||||||
ContextMenu.propTypes = {
|
ContextMenu.propTypes = {
|
||||||
|
|||||||
@@ -13,6 +13,8 @@ const ActionTypes = {
|
|||||||
CLIENT_FETCH_TRANSFER_DATA_SUCCESS: 'CLIENT_FETCH_TRANSFER_DATA_SUCCESS',
|
CLIENT_FETCH_TRANSFER_DATA_SUCCESS: 'CLIENT_FETCH_TRANSFER_DATA_SUCCESS',
|
||||||
CLIENT_FETCH_TRANSFER_HISTORY_ERROR: 'CLIENT_FETCH_TRANSFER_HISTORY_ERROR',
|
CLIENT_FETCH_TRANSFER_HISTORY_ERROR: 'CLIENT_FETCH_TRANSFER_HISTORY_ERROR',
|
||||||
CLIENT_FETCH_TRANSFER_HISTORY_SUCCESS: 'CLIENT_FETCH_TRANSFER_HISTORY_SUCCESS',
|
CLIENT_FETCH_TRANSFER_HISTORY_SUCCESS: 'CLIENT_FETCH_TRANSFER_HISTORY_SUCCESS',
|
||||||
|
CLIENT_MOVE_TORRENTS_SUCCESS: 'CLIENT_MOVE_TORRENTS_SUCCESS',
|
||||||
|
CLIENT_MOVE_TORRENTS_ERROR: 'CLIENT_MOVE_TORRENTS_ERROR',
|
||||||
CLIENT_REMOVE_TORRENT_ERROR: 'CLIENT_REMOVE_TORRENT_ERROR',
|
CLIENT_REMOVE_TORRENT_ERROR: 'CLIENT_REMOVE_TORRENT_ERROR',
|
||||||
CLIENT_REMOVE_TORRENT_SUCCESS: 'CLIENT_REMOVE_TORRENT_SUCCESS',
|
CLIENT_REMOVE_TORRENT_SUCCESS: 'CLIENT_REMOVE_TORRENT_SUCCESS',
|
||||||
CLIENT_SET_FILE_PRIORITY_ERROR: 'CLIENT_SET_FILE_PRIORITY_ERROR',
|
CLIENT_SET_FILE_PRIORITY_ERROR: 'CLIENT_SET_FILE_PRIORITY_ERROR',
|
||||||
|
|||||||
@@ -3,6 +3,8 @@ const EventTypes = {
|
|||||||
CLIENT_ADD_TORRENT_SUCCESS: 'CLIENT_ADD_TORRENT_SUCCESS',
|
CLIENT_ADD_TORRENT_SUCCESS: 'CLIENT_ADD_TORRENT_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_MOVE_TORRENTS_REQUEST_ERROR: 'CLIENT_MOVE_TORRENTS_REQUEST_ERROR',
|
||||||
|
CLIENT_MOVE_TORRENTS_SUCCESS: 'CLIENT_MOVE_TORRENTS_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',
|
||||||
|
|||||||
@@ -4,12 +4,13 @@ export default class BaseStore extends EventEmitter {
|
|||||||
constructor() {
|
constructor() {
|
||||||
super();
|
super();
|
||||||
|
|
||||||
|
this.dispatcherID = null;
|
||||||
this.on('uncaughtException', this.handleError);
|
this.on('uncaughtException', this.handleError);
|
||||||
this.setMaxListeners(20);
|
this.setMaxListeners(20);
|
||||||
}
|
}
|
||||||
|
|
||||||
handleError(error) {
|
handleError(error) {
|
||||||
console.error(error);
|
console.trace(error);
|
||||||
}
|
}
|
||||||
|
|
||||||
listen(event, callback) {
|
listen(event, callback) {
|
||||||
|
|||||||
@@ -106,9 +106,9 @@ class TorrentFilterStoreClass extends BaseStore {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const TorrentFilterStore = new TorrentFilterStoreClass();
|
let TorrentFilterStore = new TorrentFilterStoreClass();
|
||||||
|
|
||||||
AppDispatcher.register((payload) => {
|
TorrentFilterStore.dispatcherID = AppDispatcher.register((payload) => {
|
||||||
const {action, source} = payload;
|
const {action, source} = payload;
|
||||||
|
|
||||||
switch (action.type) {
|
switch (action.type) {
|
||||||
|
|||||||
@@ -54,11 +54,23 @@ class TorrentStoreClass extends BaseStore {
|
|||||||
return this.selectedTorrents;
|
return this.selectedTorrents;
|
||||||
}
|
}
|
||||||
|
|
||||||
handleAddTorrentError(error) {
|
getSelectedTorrentsDownloadLocations() {
|
||||||
|
return this.selectedTorrents.map((hash) => {
|
||||||
|
return this.torrents[hash].basePath;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
getSelectedTorrentsFilename() {
|
||||||
|
return this.selectedTorrents.map((hash) => {
|
||||||
|
return this.torrents[hash].filename;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
handleAddTorrentError() {
|
||||||
this.emit(EventTypes.CLIENT_ADD_TORRENT_ERROR);
|
this.emit(EventTypes.CLIENT_ADD_TORRENT_ERROR);
|
||||||
}
|
}
|
||||||
|
|
||||||
handleAddTorrentSuccess(data) {
|
handleAddTorrentSuccess() {
|
||||||
this.emit(EventTypes.CLIENT_ADD_TORRENT_SUCCESS);
|
this.emit(EventTypes.CLIENT_ADD_TORRENT_SUCCESS);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -88,6 +100,14 @@ class TorrentStoreClass extends BaseStore {
|
|||||||
return this.sortedTorrents;
|
return this.sortedTorrents;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
handleMoveTorrentsSuccess(data) {
|
||||||
|
this.emit(EventTypes.CLIENT_MOVE_TORRENTS_SUCCESS);
|
||||||
|
}
|
||||||
|
|
||||||
|
handleMoveTorrentsError(error) {
|
||||||
|
this.emit(EventTypes.CLIENT_MOVE_TORRENTS_REQUEST_ERROR);
|
||||||
|
}
|
||||||
|
|
||||||
setTorrents(torrents) {
|
setTorrents(torrents) {
|
||||||
let torrentsSort = TorrentFilterStore.getTorrentsSort();
|
let torrentsSort = TorrentFilterStore.getTorrentsSort();
|
||||||
|
|
||||||
@@ -155,9 +175,9 @@ class TorrentStoreClass extends BaseStore {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const TorrentStore = new TorrentStoreClass();
|
let TorrentStore = new TorrentStoreClass();
|
||||||
|
|
||||||
AppDispatcher.register((payload) => {
|
TorrentStore.dispatcherID = AppDispatcher.register((payload) => {
|
||||||
const {action, source} = payload;
|
const {action, source} = payload;
|
||||||
|
|
||||||
switch (action.type) {
|
switch (action.type) {
|
||||||
@@ -173,6 +193,12 @@ AppDispatcher.register((payload) => {
|
|||||||
case ActionTypes.CLIENT_FETCH_TORRENTS_SUCCESS:
|
case ActionTypes.CLIENT_FETCH_TORRENTS_SUCCESS:
|
||||||
TorrentStore.setTorrents(action.data.torrents);
|
TorrentStore.setTorrents(action.data.torrents);
|
||||||
break;
|
break;
|
||||||
|
case ActionTypes.CLIENT_MOVE_TORRENTS_SUCCESS:
|
||||||
|
TorrentStore.handleMoveTorrentsSuccess(action.data);
|
||||||
|
break;
|
||||||
|
case ActionTypes.CLIENT_MOVE_TORRENTS_ERROR:
|
||||||
|
TorrentStore.handleMoveTorrentsError(action.error);
|
||||||
|
break;
|
||||||
case ActionTypes.CLIENT_FETCH_TORRENTS_ERROR:
|
case ActionTypes.CLIENT_FETCH_TORRENTS_ERROR:
|
||||||
console.log(action);
|
console.log(action);
|
||||||
break;
|
break;
|
||||||
|
|||||||
@@ -128,9 +128,9 @@ class TransferDataStoreClass extends BaseStore {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const TransferDataStore = new TransferDataStoreClass();
|
let TransferDataStore = new TransferDataStoreClass();
|
||||||
|
|
||||||
AppDispatcher.register((payload) => {
|
TransferDataStore.dispatcherID = AppDispatcher.register((payload) => {
|
||||||
const {action, source} = payload;
|
const {action, source} = payload;
|
||||||
|
|
||||||
switch (action.type) {
|
switch (action.type) {
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import BaseStore from './BaseStore';
|
|||||||
import EventTypes from '../constants/EventTypes';
|
import EventTypes from '../constants/EventTypes';
|
||||||
import {selectTorrents} from '../util/selectTorrents';
|
import {selectTorrents} from '../util/selectTorrents';
|
||||||
import TorrentActions from '../actions/TorrentActions';
|
import TorrentActions from '../actions/TorrentActions';
|
||||||
|
import TorrentStore from './TorrentStore';
|
||||||
|
|
||||||
class UIStoreClass extends BaseStore {
|
class UIStoreClass extends BaseStore {
|
||||||
constructor() {
|
constructor() {
|
||||||
@@ -77,9 +78,9 @@ class UIStoreClass extends BaseStore {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const UIStore = new UIStoreClass();
|
let UIStore = new UIStoreClass();
|
||||||
|
|
||||||
AppDispatcher.register((payload) => {
|
UIStore.dispatcherID = AppDispatcher.register((payload) => {
|
||||||
const {action, source} = payload;
|
const {action, source} = payload;
|
||||||
|
|
||||||
switch (action.type) {
|
switch (action.type) {
|
||||||
@@ -92,6 +93,9 @@ AppDispatcher.register((payload) => {
|
|||||||
case ActionTypes.UI_DISPLAY_MODAL:
|
case ActionTypes.UI_DISPLAY_MODAL:
|
||||||
UIStore.setActiveModal(action.data);
|
UIStore.setActiveModal(action.data);
|
||||||
break;
|
break;
|
||||||
|
case ActionTypes.CLIENT_MOVE_TORRENTS_SUCCESS:
|
||||||
|
UIStore.setActiveModal(null);
|
||||||
|
break;
|
||||||
case ActionTypes.UI_DISPLAY_CONTEXT_MENU:
|
case ActionTypes.UI_DISPLAY_CONTEXT_MENU:
|
||||||
UIStore.setActiveContextMenu(action.data);
|
UIStore.setActiveContextMenu(action.data);
|
||||||
break;
|
break;
|
||||||
|
|||||||
@@ -29,6 +29,7 @@
|
|||||||
"lodash": "^4.3.0",
|
"lodash": "^4.3.0",
|
||||||
"morgan": "~1.5.1",
|
"morgan": "~1.5.1",
|
||||||
"multer": "^1.1.0",
|
"multer": "^1.1.0",
|
||||||
|
"mv": "^2.1.1",
|
||||||
"nedb": "^1.7.2",
|
"nedb": "^1.7.2",
|
||||||
"nodemon": "^1.8.1",
|
"nodemon": "^1.8.1",
|
||||||
"object-assign": "^2.0.0",
|
"object-assign": "^2.0.0",
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
@@ -476,14 +476,14 @@ th {
|
|||||||
100% {
|
100% {
|
||||||
opacity: 0; } }
|
opacity: 0; } }
|
||||||
|
|
||||||
.textbox, .dropzone__selected-files,
|
.textbox, .checkbox__decoy, .dropzone__selected-files,
|
||||||
.button {
|
.button {
|
||||||
appearance: none;
|
appearance: none;
|
||||||
-moz-appearance: none;
|
-moz-appearance: none;
|
||||||
-webkit-appearance: none;
|
-webkit-appearance: none;
|
||||||
outline: none; }
|
outline: none; }
|
||||||
|
|
||||||
.textbox, .dropzone__selected-files {
|
.textbox, .checkbox__decoy, .dropzone__selected-files {
|
||||||
background: #e9eff5;
|
background: #e9eff5;
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
border: 1px solid #d6e2ea;
|
border: 1px solid #d6e2ea;
|
||||||
@@ -494,39 +494,39 @@ th {
|
|||||||
-webkit-transition: background 0.25s, border 0.25s, color 0.25s;
|
-webkit-transition: background 0.25s, border 0.25s, color 0.25s;
|
||||||
transition: background 0.25s, border 0.25s, color 0.25s;
|
transition: background 0.25s, border 0.25s, color 0.25s;
|
||||||
width: 100%; }
|
width: 100%; }
|
||||||
.textbox::-webkit-input-placeholder, .dropzone__selected-files::-webkit-input-placeholder {
|
.textbox::-webkit-input-placeholder, .checkbox__decoy::-webkit-input-placeholder, .dropzone__selected-files::-webkit-input-placeholder {
|
||||||
color: #abbac7;
|
color: #abbac7;
|
||||||
font-style: italic;
|
font-style: italic;
|
||||||
-webkit-transition: color 0.25s;
|
-webkit-transition: color 0.25s;
|
||||||
transition: color 0.25s; }
|
transition: color 0.25s; }
|
||||||
.textbox::-moz-placeholder, .dropzone__selected-files::-moz-placeholder {
|
.textbox::-moz-placeholder, .checkbox__decoy::-moz-placeholder, .dropzone__selected-files::-moz-placeholder {
|
||||||
color: #abbac7;
|
color: #abbac7;
|
||||||
font-style: italic;
|
font-style: italic;
|
||||||
-webkit-transition: color 0.25s;
|
-webkit-transition: color 0.25s;
|
||||||
transition: color 0.25s; }
|
transition: color 0.25s; }
|
||||||
.textbox:-ms-input-placeholder, .dropzone__selected-files:-ms-input-placeholder {
|
.textbox:-ms-input-placeholder, .checkbox__decoy:-ms-input-placeholder, .dropzone__selected-files:-ms-input-placeholder {
|
||||||
color: #abbac7;
|
color: #abbac7;
|
||||||
font-style: italic;
|
font-style: italic;
|
||||||
-webkit-transition: color 0.25s;
|
-webkit-transition: color 0.25s;
|
||||||
transition: color 0.25s; }
|
transition: color 0.25s; }
|
||||||
.textbox::placeholder, .dropzone__selected-files::placeholder {
|
.textbox::placeholder, .checkbox__decoy::placeholder, .dropzone__selected-files::placeholder {
|
||||||
color: #abbac7;
|
color: #abbac7;
|
||||||
font-style: italic;
|
font-style: italic;
|
||||||
-webkit-transition: color 0.25s;
|
-webkit-transition: color 0.25s;
|
||||||
transition: color 0.25s; }
|
transition: color 0.25s; }
|
||||||
.textbox:focus, .dropzone__selected-files:focus {
|
.textbox:focus, .checkbox__decoy:focus, .dropzone__selected-files:focus {
|
||||||
background: #fdfefe;
|
background: #fdfefe;
|
||||||
border-color: #c7d6df;
|
border-color: #c7d6df;
|
||||||
color: #258de5; }
|
color: #258de5; }
|
||||||
.textbox:focus::-webkit-input-placeholder, .dropzone__selected-files:focus::-webkit-input-placeholder {
|
.textbox:focus::-webkit-input-placeholder, .checkbox__decoy:focus::-webkit-input-placeholder, .dropzone__selected-files:focus::-webkit-input-placeholder {
|
||||||
color: #abbac7; }
|
color: #abbac7; }
|
||||||
.textbox:focus::-moz-placeholder, .dropzone__selected-files:focus::-moz-placeholder {
|
.textbox:focus::-moz-placeholder, .checkbox__decoy:focus::-moz-placeholder, .dropzone__selected-files:focus::-moz-placeholder {
|
||||||
color: #abbac7; }
|
color: #abbac7; }
|
||||||
.textbox:focus:-ms-input-placeholder, .dropzone__selected-files:focus:-ms-input-placeholder {
|
.textbox:focus:-ms-input-placeholder, .checkbox__decoy:focus:-ms-input-placeholder, .dropzone__selected-files:focus:-ms-input-placeholder {
|
||||||
color: #abbac7; }
|
color: #abbac7; }
|
||||||
.textbox:focus::placeholder, .dropzone__selected-files:focus::placeholder {
|
.textbox:focus::placeholder, .checkbox__decoy:focus::placeholder, .dropzone__selected-files:focus::placeholder {
|
||||||
color: #abbac7; }
|
color: #abbac7; }
|
||||||
.textbox.is-fulfilled, .dropzone__selected-files {
|
.textbox.is-fulfilled, .is-fulfilled.checkbox__decoy, .dropzone__selected-files {
|
||||||
background: #fdfefe; }
|
background: #fdfefe; }
|
||||||
|
|
||||||
.button {
|
.button {
|
||||||
@@ -569,6 +569,44 @@ th {
|
|||||||
background: #1a80d7;
|
background: #1a80d7;
|
||||||
box-shadow: inset 0 0 0 1px #1773c0; }
|
box-shadow: inset 0 0 0 1px #1773c0; }
|
||||||
|
|
||||||
|
.checkbox {
|
||||||
|
line-height: 1;
|
||||||
|
position: relative; }
|
||||||
|
.checkbox:hover .checkbox__decoy {
|
||||||
|
border-color: #258de5; }
|
||||||
|
.checkbox input[type="checkbox"] {
|
||||||
|
left: 0;
|
||||||
|
opacity: 0;
|
||||||
|
position: absolute;
|
||||||
|
top: 50%;
|
||||||
|
-webkit-transform: translateY(-50%);
|
||||||
|
transform: translateY(-50%); }
|
||||||
|
.checkbox input[type="checkbox"]:checked + .checkbox__decoy .icon {
|
||||||
|
fill: #258de5; }
|
||||||
|
.checkbox__decoy {
|
||||||
|
background: #fff;
|
||||||
|
display: inline-block;
|
||||||
|
height: 15px;
|
||||||
|
margin-right: 7.5px;
|
||||||
|
margin-top: -2px;
|
||||||
|
padding: 0;
|
||||||
|
position: relative;
|
||||||
|
vertical-align: middle;
|
||||||
|
width: 15px; }
|
||||||
|
.checkbox__decoy .icon {
|
||||||
|
fill: transparent;
|
||||||
|
height: 10px;
|
||||||
|
left: 50%;
|
||||||
|
position: absolute;
|
||||||
|
top: 50%;
|
||||||
|
-webkit-transition: fill 0.25s;
|
||||||
|
transition: fill 0.25s;
|
||||||
|
-webkit-transform: translate(-43%, -43%);
|
||||||
|
transform: translate(-43%, -43%);
|
||||||
|
width: 10px; }
|
||||||
|
.checkbox__label {
|
||||||
|
color: #768a9a; }
|
||||||
|
|
||||||
.form__label {
|
.form__label {
|
||||||
color: #abbac7;
|
color: #abbac7;
|
||||||
display: block;
|
display: block;
|
||||||
@@ -1642,7 +1680,7 @@ body {
|
|||||||
-webkit-transform: translate(-50%, -50%);
|
-webkit-transform: translate(-50%, -50%);
|
||||||
transform: translate(-50%, -50%);
|
transform: translate(-50%, -50%);
|
||||||
width: 10px; }
|
width: 10px; }
|
||||||
.search .textbox, .search .dropzone__selected-files {
|
.search .textbox, .search .checkbox__decoy, .search .dropzone__selected-files {
|
||||||
appearance: none;
|
appearance: none;
|
||||||
-webkit-appearance: none;
|
-webkit-appearance: none;
|
||||||
-moz-appearance: none;
|
-moz-appearance: none;
|
||||||
@@ -1660,22 +1698,22 @@ body {
|
|||||||
-webkit-transition: background 0.25s, border 0.25s, color 0.25s;
|
-webkit-transition: background 0.25s, border 0.25s, color 0.25s;
|
||||||
transition: background 0.25s, border 0.25s, color 0.25s;
|
transition: background 0.25s, border 0.25s, color 0.25s;
|
||||||
width: 100%; }
|
width: 100%; }
|
||||||
.search .textbox::-webkit-input-placeholder, .search .dropzone__selected-files::-webkit-input-placeholder {
|
.search .textbox::-webkit-input-placeholder, .search .checkbox__decoy::-webkit-input-placeholder, .search .dropzone__selected-files::-webkit-input-placeholder {
|
||||||
color: rgba(83, 113, 138, 0.4);
|
color: rgba(83, 113, 138, 0.4);
|
||||||
font-style: italic;
|
font-style: italic;
|
||||||
-webkit-transition: color 0.25s;
|
-webkit-transition: color 0.25s;
|
||||||
transition: color 0.25s; }
|
transition: color 0.25s; }
|
||||||
.search .textbox::-moz-placeholder, .search .dropzone__selected-files::-moz-placeholder {
|
.search .textbox::-moz-placeholder, .search .checkbox__decoy::-moz-placeholder, .search .dropzone__selected-files::-moz-placeholder {
|
||||||
color: rgba(83, 113, 138, 0.4);
|
color: rgba(83, 113, 138, 0.4);
|
||||||
font-style: italic;
|
font-style: italic;
|
||||||
-webkit-transition: color 0.25s;
|
-webkit-transition: color 0.25s;
|
||||||
transition: color 0.25s; }
|
transition: color 0.25s; }
|
||||||
.search .textbox:-ms-input-placeholder, .search .dropzone__selected-files:-ms-input-placeholder {
|
.search .textbox:-ms-input-placeholder, .search .checkbox__decoy:-ms-input-placeholder, .search .dropzone__selected-files:-ms-input-placeholder {
|
||||||
color: rgba(83, 113, 138, 0.4);
|
color: rgba(83, 113, 138, 0.4);
|
||||||
font-style: italic;
|
font-style: italic;
|
||||||
-webkit-transition: color 0.25s;
|
-webkit-transition: color 0.25s;
|
||||||
transition: color 0.25s; }
|
transition: color 0.25s; }
|
||||||
.search .textbox::placeholder, .search .dropzone__selected-files::placeholder {
|
.search .textbox::placeholder, .search .checkbox__decoy::placeholder, .search .dropzone__selected-files::placeholder {
|
||||||
color: rgba(83, 113, 138, 0.4);
|
color: rgba(83, 113, 138, 0.4);
|
||||||
font-style: italic;
|
font-style: italic;
|
||||||
-webkit-transition: color 0.25s;
|
-webkit-transition: color 0.25s;
|
||||||
@@ -1683,19 +1721,19 @@ body {
|
|||||||
.search.is-in-use .icon {
|
.search.is-in-use .icon {
|
||||||
fill: #258de5;
|
fill: #258de5;
|
||||||
opacity: 1; }
|
opacity: 1; }
|
||||||
.search.is-in-use .textbox, .search.is-in-use .dropzone__selected-files {
|
.search.is-in-use .textbox, .search.is-in-use .checkbox__decoy, .search.is-in-use .dropzone__selected-files {
|
||||||
background: rgba(37, 141, 229, 0.25);
|
background: rgba(37, 141, 229, 0.25);
|
||||||
border-bottom: 1px solid rgba(37, 141, 229, 0.3);
|
border-bottom: 1px solid rgba(37, 141, 229, 0.3);
|
||||||
border-top: 1px solid rgba(37, 141, 229, 0.3);
|
border-top: 1px solid rgba(37, 141, 229, 0.3);
|
||||||
color: #258de5;
|
color: #258de5;
|
||||||
padding-right: 45px; }
|
padding-right: 45px; }
|
||||||
.search.is-in-use .textbox::-webkit-input-placeholder, .search.is-in-use .dropzone__selected-files::-webkit-input-placeholder {
|
.search.is-in-use .textbox::-webkit-input-placeholder, .search.is-in-use .checkbox__decoy::-webkit-input-placeholder, .search.is-in-use .dropzone__selected-files::-webkit-input-placeholder {
|
||||||
color: rgba(37, 141, 229, 0.4); }
|
color: rgba(37, 141, 229, 0.4); }
|
||||||
.search.is-in-use .textbox::-moz-placeholder, .search.is-in-use .dropzone__selected-files::-moz-placeholder {
|
.search.is-in-use .textbox::-moz-placeholder, .search.is-in-use .checkbox__decoy::-moz-placeholder, .search.is-in-use .dropzone__selected-files::-moz-placeholder {
|
||||||
color: rgba(37, 141, 229, 0.4); }
|
color: rgba(37, 141, 229, 0.4); }
|
||||||
.search.is-in-use .textbox:-ms-input-placeholder, .search.is-in-use .dropzone__selected-files:-ms-input-placeholder {
|
.search.is-in-use .textbox:-ms-input-placeholder, .search.is-in-use .checkbox__decoy:-ms-input-placeholder, .search.is-in-use .dropzone__selected-files:-ms-input-placeholder {
|
||||||
color: rgba(37, 141, 229, 0.4); }
|
color: rgba(37, 141, 229, 0.4); }
|
||||||
.search.is-in-use .textbox::placeholder, .search.is-in-use .dropzone__selected-files::placeholder {
|
.search.is-in-use .textbox::placeholder, .search.is-in-use .checkbox__decoy::placeholder, .search.is-in-use .dropzone__selected-files::placeholder {
|
||||||
color: rgba(37, 141, 229, 0.4); }
|
color: rgba(37, 141, 229, 0.4); }
|
||||||
|
|
||||||
.application__sidebar {
|
.application__sidebar {
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
@@ -3,7 +3,9 @@
|
|||||||
let util = require('util');
|
let util = require('util');
|
||||||
|
|
||||||
let clientUtil = require('../util/clientUtil');
|
let clientUtil = require('../util/clientUtil');
|
||||||
let Q = require('q');
|
let fs = require('fs');
|
||||||
|
let mv = require('mv');
|
||||||
|
let path = require('path');
|
||||||
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');
|
||||||
@@ -23,6 +25,10 @@ class ClientRequest {
|
|||||||
if (options.postProcess) {
|
if (options.postProcess) {
|
||||||
this.postProcessFn = options.postProcess;
|
this.postProcessFn = options.postProcess;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (options.name) {
|
||||||
|
this.name = options.name;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
add(request, options) {
|
add(request, options) {
|
||||||
@@ -51,7 +57,7 @@ class ClientRequest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
handleError(error) {
|
handleError(error) {
|
||||||
console.trace(error);
|
console.trace(this.name, error);
|
||||||
|
|
||||||
this.clearRequestQueue();
|
this.clearRequestQueue();
|
||||||
|
|
||||||
@@ -83,9 +89,16 @@ class ClientRequest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
send() {
|
send() {
|
||||||
|
// TODO: Remove this.
|
||||||
|
if (!this) {
|
||||||
|
console.log('\n\n\n\n\n\n\nthis is null\n\n\n\n\n\n');
|
||||||
|
}
|
||||||
|
let handleSuccess = this.handleSuccess.bind(this);
|
||||||
|
let handleError = this.handleError.bind(this);
|
||||||
|
|
||||||
scgi.methodCall('system.multicall', [this.requests])
|
scgi.methodCall('system.multicall', [this.requests])
|
||||||
.then(this.handleSuccess.bind(this))
|
.then(handleSuccess)
|
||||||
.catch(this.handleError.bind(this));
|
.catch(handleError);
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Separate these and add support for additional clients.
|
// TODO: Separate these and add support for additional clients.
|
||||||
@@ -126,6 +139,14 @@ class ClientRequest {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
checkHashMethodCall(options) {
|
||||||
|
let hashes = this.getEnsuredArray(options.hashes);
|
||||||
|
|
||||||
|
hashes.forEach((hash) => {
|
||||||
|
this.requests.push(this.getMethodCall('d.check_hash', [hash]));
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
createDirectoryMethodCall(options) {
|
createDirectoryMethodCall(options) {
|
||||||
this.requests.push(
|
this.requests.push(
|
||||||
this.getMethodCall('execute', ['mkdir', '-p', options.path])
|
this.getMethodCall('execute', ['mkdir', '-p', options.path])
|
||||||
@@ -160,11 +181,24 @@ class ClientRequest {
|
|||||||
moveTorrentsMethodCall(options) {
|
moveTorrentsMethodCall(options) {
|
||||||
let hashes = this.getEnsuredArray(options.hashes);
|
let hashes = this.getEnsuredArray(options.hashes);
|
||||||
let destinationPath = options.destinationPath;
|
let destinationPath = options.destinationPath;
|
||||||
let sourcePath = options.sourcePath;
|
let filenames = this.getEnsuredArray(options.filenames);
|
||||||
|
let sourcePaths = this.getEnsuredArray(options.sourcePaths);
|
||||||
|
|
||||||
this.moveInProgress = true;
|
sourcePaths.forEach((source, index) => {
|
||||||
|
let callback = function () {};
|
||||||
|
let destination = `${destinationPath}${path.sep}${filenames[index]}`;
|
||||||
|
let isLastRequest = index + 1 === sourcePaths.length;
|
||||||
|
|
||||||
// let {hashes, destinationPath, sourcePath} = options;
|
if (isLastRequest) {
|
||||||
|
callback = this.handleSuccess.bind(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (source !== destination) {
|
||||||
|
mv(source, destination, {mkdirp: true}, callback);
|
||||||
|
} else if (isLastRequest) {
|
||||||
|
callback();
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
removeTorrentsMethodCall(options) {
|
removeTorrentsMethodCall(options) {
|
||||||
@@ -175,6 +209,17 @@ class ClientRequest {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
setDownloadPathMethodCall(options) {
|
||||||
|
let hashes = this.getEnsuredArray(options.hashes);
|
||||||
|
|
||||||
|
hashes.forEach((hash) => {
|
||||||
|
this.requests.push(this.getMethodCall('d.directory.set',
|
||||||
|
[hash, options.path]));
|
||||||
|
this.requests.push(this.getMethodCall('d.open', [hash]));
|
||||||
|
this.requests.push(this.getMethodCall('d.close', [hash]));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
setFilePriorityMethodCall(options) {
|
setFilePriorityMethodCall(options) {
|
||||||
let hashes = this.getEnsuredArray(options.hashes);
|
let hashes = this.getEnsuredArray(options.hashes);
|
||||||
|
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ let stringUtil = require('../../shared/util/stringUtil');
|
|||||||
|
|
||||||
const MAX_CLEANUP_INTERVAL = 1000 * 60 * 60; // 1 hour
|
const MAX_CLEANUP_INTERVAL = 1000 * 60 * 60; // 1 hour
|
||||||
const MAX_NEXT_ERA_UPDATE_INTERVAL = 1000 * 60 * 60 * 12; // 12 hours
|
const MAX_NEXT_ERA_UPDATE_INTERVAL = 1000 * 60 * 60 * 12; // 12 hours
|
||||||
const CUMULATIVE_DATA_BUFFER = 1000 * 2;
|
const CUMULATIVE_DATA_BUFFER = 1000 * 2; // 2 seconds
|
||||||
const REQUIRED_FIELDS = ['interval', 'maxTime', 'name'];
|
const REQUIRED_FIELDS = ['interval', 'maxTime', 'name'];
|
||||||
|
|
||||||
class HistoryEra {
|
class HistoryEra {
|
||||||
@@ -29,7 +29,6 @@ class HistoryEra {
|
|||||||
this.removeOutdatedData(this.db);
|
this.removeOutdatedData(this.db);
|
||||||
|
|
||||||
let cleanupInterval = this.opts.maxTime;
|
let cleanupInterval = this.opts.maxTime;
|
||||||
|
|
||||||
let nextEraUpdateInterval = this.opts.nextEraUpdateInterval;
|
let nextEraUpdateInterval = this.opts.nextEraUpdateInterval;
|
||||||
|
|
||||||
if (cleanupInterval === 0 || cleanupInterval > MAX_CLEANUP_INTERVAL) {
|
if (cleanupInterval === 0 || cleanupInterval > MAX_CLEANUP_INTERVAL) {
|
||||||
@@ -40,11 +39,11 @@ class HistoryEra {
|
|||||||
nextEraUpdateInterval = MAX_NEXT_ERA_UPDATE_INTERVAL;
|
nextEraUpdateInterval = MAX_NEXT_ERA_UPDATE_INTERVAL;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.startAutoCleanup(cleanupInterval, this.db);
|
|
||||||
|
|
||||||
if (nextEraUpdateInterval) {
|
if (nextEraUpdateInterval) {
|
||||||
this.startNextEraUpdate(nextEraUpdateInterval, this.db);
|
this.startNextEraUpdate(nextEraUpdateInterval, this.db);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.startAutoCleanup(cleanupInterval, this.db);
|
||||||
}
|
}
|
||||||
|
|
||||||
addData(data) {
|
addData(data) {
|
||||||
|
|||||||
@@ -45,7 +45,8 @@ const REQUESTED_DATA = [
|
|||||||
'comment',
|
'comment',
|
||||||
'isPrivate',
|
'isPrivate',
|
||||||
'directory',
|
'directory',
|
||||||
'filename'
|
'filename',
|
||||||
|
'isMultiFile'
|
||||||
];
|
];
|
||||||
|
|
||||||
class Torrent {
|
class Torrent {
|
||||||
|
|||||||
@@ -109,20 +109,46 @@ var client = {
|
|||||||
request.send();
|
request.send();
|
||||||
},
|
},
|
||||||
|
|
||||||
moveFiles: function(data, callback) {
|
moveTorrents: function(data, callback) {
|
||||||
let destinationPath = data.destination;
|
let destinationPath = data.destination;
|
||||||
let hashes = data.hashes;
|
let hashes = data.hashes;
|
||||||
let sourcePath = data.source;
|
let filenames = data.filenames;
|
||||||
let request = new ClientRequest();
|
let moveFiles = data.moveFiles;
|
||||||
|
let sourcePaths = data.sources;
|
||||||
|
let mainRequest = new ClientRequest();
|
||||||
|
|
||||||
request.add('createDirectory', {path: destinationPath});
|
let startTorrents = function() {
|
||||||
request.add('stopTorrents', {hashes});
|
let startTorrentsRequest = new ClientRequest();
|
||||||
request.onComplete(function () {
|
startTorrentsRequest.add('startTorrents', {hashes});
|
||||||
request.add('moveTorrents', {hashes, destinationPath, sourcePath});
|
startTorrentsRequest.onComplete(callback);
|
||||||
request.add('startTorrents', {hashes});
|
startTorrentsRequest.send();
|
||||||
request.onComplete(callback);
|
};
|
||||||
})
|
|
||||||
request.send();
|
let checkHash = function() {
|
||||||
|
let checkHashRequest = new ClientRequest();
|
||||||
|
checkHashRequest.add('checkHash', {hashes});
|
||||||
|
checkHashRequest.onComplete(afterCheckHash);
|
||||||
|
checkHashRequest.send();
|
||||||
|
}
|
||||||
|
|
||||||
|
let moveTorrents = function () {
|
||||||
|
let moveTorrentsRequest = new ClientRequest();
|
||||||
|
moveTorrentsRequest.onComplete(checkHash);
|
||||||
|
moveTorrentsRequest.add('moveTorrents',
|
||||||
|
{filenames, sourcePaths, destinationPath});
|
||||||
|
};
|
||||||
|
|
||||||
|
let afterCheckHash = startTorrents;
|
||||||
|
let afterSetPath = checkHash;
|
||||||
|
|
||||||
|
if (moveFiles) {
|
||||||
|
afterSetPath = moveTorrents;
|
||||||
|
}
|
||||||
|
|
||||||
|
mainRequest.add('stopTorrents', {hashes});
|
||||||
|
mainRequest.add('setDownloadPath', {hashes, path: destinationPath});
|
||||||
|
mainRequest.onComplete(afterSetPath);
|
||||||
|
mainRequest.send();
|
||||||
},
|
},
|
||||||
|
|
||||||
setFilePriority: function (hashes, data, callback) {
|
setFilePriority: function (hashes, data, callback) {
|
||||||
|
|||||||
@@ -62,6 +62,10 @@ router.patch('/torrents/:hash/file-priority', function(req, res, next) {
|
|||||||
client.setFilePriority(req.params.hash, req.body, ajaxUtil.getResponseFn(res));
|
client.setFilePriority(req.params.hash, req.body, ajaxUtil.getResponseFn(res));
|
||||||
});
|
});
|
||||||
|
|
||||||
|
router.post('/torrents/move', function(req, res, next) {
|
||||||
|
client.moveTorrents(req.body, 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));
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -56,7 +56,7 @@ var clientUtil = {
|
|||||||
// 'tiedToFile',
|
// 'tiedToFile',
|
||||||
// 'trackerNumWant',
|
// 'trackerNumWant',
|
||||||
// 'trackerSize',
|
// 'trackerSize',
|
||||||
// 'isMultiFile',
|
'isMultiFile',
|
||||||
// 'isPexActive',
|
// 'isPexActive',
|
||||||
'isPrivate',
|
'isPrivate',
|
||||||
|
|
||||||
@@ -126,7 +126,7 @@ var clientUtil = {
|
|||||||
// 'd.tied_to_file=',
|
// 'd.tied_to_file=',
|
||||||
// 'd.tracker_numwant=',
|
// 'd.tracker_numwant=',
|
||||||
// 'd.tracker_size=',
|
// 'd.tracker_size=',
|
||||||
// 'd.is_multi_file=',
|
'd.is_multi_file=',
|
||||||
// 'd.is_pex_active=',
|
// 'd.is_pex_active=',
|
||||||
'd.is_private=',
|
'd.is_private=',
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user