2017-08-24 15:50:14 +02:00
|
|
|
import React, { Component } from 'react';
|
|
|
|
import { findDOMNode } from 'react-dom';
|
|
|
|
import { connect } from 'react-redux';
|
|
|
|
import { push } from 'react-router-redux';
|
2017-09-07 08:19:11 +02:00
|
|
|
import {
|
|
|
|
Progress,
|
|
|
|
Card,
|
|
|
|
CardBlock,
|
|
|
|
CardTitle,
|
|
|
|
CardText,
|
|
|
|
FormGroup,
|
|
|
|
Label,
|
|
|
|
Input
|
|
|
|
} from 'reactstrap';
|
|
|
|
import ToggleContainer from './toggle_container';
|
2017-08-24 15:50:14 +02:00
|
|
|
import fetch from 'isomorphic-fetch';
|
2017-09-07 08:19:11 +02:00
|
|
|
import bencode from 'bencode';
|
2017-08-24 15:50:14 +02:00
|
|
|
import ws_send from '../socket';
|
2017-09-07 08:19:11 +02:00
|
|
|
import { convertToBitrate } from '../bitrate';
|
|
|
|
|
|
|
|
class Throttle extends Component {
|
|
|
|
constructor() {
|
|
|
|
super();
|
|
|
|
this.state = {
|
|
|
|
strategy: "global",
|
|
|
|
unit: "MiB/s",
|
|
|
|
limit: 1
|
|
|
|
};
|
|
|
|
this.setStrategy = this.setStrategy.bind(this);
|
|
|
|
}
|
|
|
|
|
|
|
|
invokeChange() {
|
|
|
|
const { onChange } = this.props;
|
2017-09-07 08:31:29 +02:00
|
|
|
if (!onChange) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2017-09-07 08:19:11 +02:00
|
|
|
const { strategy, limit, unit } = this.state;
|
2017-09-07 08:31:29 +02:00
|
|
|
switch (strategy) {
|
|
|
|
case "global":
|
|
|
|
onChange(null);
|
|
|
|
break;
|
|
|
|
case "unlimited":
|
|
|
|
onChange(-1);
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
onChange(convertToBitrate(limit, unit));
|
|
|
|
break;
|
|
|
|
}
|
2017-09-07 08:19:11 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
setStrategy(strategy) {
|
|
|
|
this.setState({ strategy });
|
|
|
|
this.invokeChange();
|
|
|
|
}
|
|
|
|
|
|
|
|
setLimit(limit) {
|
|
|
|
if (limit <= 0) {
|
|
|
|
this.setState({ limit: this.state.limit });
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
this.setState({ limit });
|
|
|
|
this.invokeChange();
|
|
|
|
}
|
|
|
|
|
|
|
|
setUnit(unit) {
|
|
|
|
this.setState({ unit });
|
|
|
|
this.invokeChange();
|
|
|
|
}
|
|
|
|
|
|
|
|
render() {
|
|
|
|
const { legend, prop } = this.props;
|
|
|
|
return (
|
|
|
|
<div>
|
|
|
|
<FormGroup tag="fieldset">
|
|
|
|
<legend>{legend}</legend>
|
|
|
|
<FormGroup check className="form-check-inline">
|
|
|
|
<Label for={`${prop}-global`} check>
|
|
|
|
<Input
|
|
|
|
type="radio"
|
|
|
|
name={prop}
|
|
|
|
id={`${prop}-global`}
|
|
|
|
onChange={e => this.setStrategy("global")}
|
|
|
|
checked={this.state.strategy === "global"}
|
|
|
|
/> Global
|
|
|
|
</Label>
|
|
|
|
</FormGroup>
|
|
|
|
<FormGroup check className="form-check-inline">
|
|
|
|
<Label for={`${prop}-unlimited`} check>
|
|
|
|
<Input
|
|
|
|
type="radio"
|
|
|
|
name={prop}
|
|
|
|
id={`${prop}-unlimited`}
|
|
|
|
onChange={e => this.setStrategy("unlimited")}
|
|
|
|
checked={this.state.strategy === "unlimited"}
|
|
|
|
/> Unlimited
|
|
|
|
</Label>
|
|
|
|
</FormGroup>
|
|
|
|
<FormGroup check className="form-check-inline">
|
|
|
|
<Label for={`${prop}-custom`} check>
|
|
|
|
<Input
|
|
|
|
type="radio"
|
|
|
|
name={prop}
|
|
|
|
id={`${prop}-custom`}
|
|
|
|
onChange={e => this.setStrategy("custom")}
|
|
|
|
checked={this.state.strategy === "custom"}
|
|
|
|
/> Custom
|
|
|
|
</Label>
|
|
|
|
</FormGroup>
|
|
|
|
</FormGroup>
|
|
|
|
{this.state.strategy === "custom" &&
|
|
|
|
<div className="row">
|
|
|
|
<FormGroup className="col-md-6">
|
|
|
|
<Input
|
|
|
|
type="number"
|
|
|
|
value={this.state.limit}
|
|
|
|
onChange={e => this.setLimit(parseFloat(e.target.value))}
|
|
|
|
/>
|
|
|
|
</FormGroup>
|
|
|
|
<FormGroup className="col-md-6">
|
|
|
|
<Input
|
|
|
|
type="select"
|
|
|
|
id="unit"
|
|
|
|
value={this.state.unit}
|
|
|
|
onChange={e => this.setUnit(e.target.value)}
|
|
|
|
>
|
|
|
|
<option value="b/s">b/s</option>
|
|
|
|
<option value="KiB/s">KiB/s</option>
|
|
|
|
<option value="MiB/s">MiB/s</option>
|
|
|
|
<option value="GiB/s">GiB/s</option>
|
|
|
|
</Input>
|
|
|
|
</FormGroup>
|
|
|
|
</div>
|
|
|
|
}
|
|
|
|
</div>
|
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|
2017-08-24 15:50:14 +02:00
|
|
|
|
|
|
|
class AddTorrent extends Component {
|
|
|
|
constructor() {
|
|
|
|
super();
|
|
|
|
this.state = {
|
|
|
|
loading: false,
|
|
|
|
customize: false,
|
2017-09-07 08:19:11 +02:00
|
|
|
file: null,
|
2017-09-07 10:37:11 +02:00
|
|
|
magnet: null,
|
|
|
|
useMagnet: false,
|
2017-09-07 08:19:11 +02:00
|
|
|
torrent: null,
|
|
|
|
files: [],
|
2017-09-07 08:31:29 +02:00
|
|
|
startImmediately: true,
|
|
|
|
uploadThrottle: -1,
|
|
|
|
downloadThrottle: -1,
|
|
|
|
priority: 3,
|
2017-08-24 15:50:14 +02:00
|
|
|
};
|
|
|
|
}
|
|
|
|
|
2017-09-07 10:37:11 +02:00
|
|
|
componentDidMount() {
|
|
|
|
let { magnet } = this.props.match.params;
|
|
|
|
if (magnet) {
|
|
|
|
magnet = decodeURIComponent(magnet);
|
|
|
|
this.setState({ magnet, useMagnet: true });
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-08-24 15:50:14 +02:00
|
|
|
async handleTransferOffer(offer, file) {
|
|
|
|
const headers = new Headers();
|
|
|
|
headers.append("Authorization", "Bearer " + offer.token);
|
|
|
|
try {
|
|
|
|
const resp = await fetch('http://localhost:8412/', {
|
|
|
|
method: 'POST',
|
|
|
|
body: file,
|
|
|
|
headers: headers
|
|
|
|
});
|
|
|
|
} catch (ex) {
|
2017-09-07 08:19:11 +02:00
|
|
|
// TODO: something more useful
|
2017-08-24 15:50:14 +02:00
|
|
|
console.log(ex);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-09-07 09:56:47 +02:00
|
|
|
applyOptions(id) {
|
|
|
|
const {
|
|
|
|
priority,
|
|
|
|
uploadThrottle,
|
|
|
|
downloadThrottle
|
|
|
|
} = this.state;
|
|
|
|
const { dispatch } = this.props;
|
|
|
|
const customize = // TODO: File options
|
|
|
|
priority !== 3 ||
|
|
|
|
uploadThrottle !== -1 ||
|
|
|
|
downloadThrottle !== -1;
|
|
|
|
if (!customize) {
|
|
|
|
dispatch(push(`/torrents/${id}`));
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
ws_send("UPDATE_RESOURCE", {
|
|
|
|
resource: {
|
|
|
|
id,
|
|
|
|
priority,
|
|
|
|
throttle_up: uploadThrottle,
|
|
|
|
throttle_down: downloadThrottle
|
|
|
|
}
|
|
|
|
}, async done => {
|
|
|
|
if (this.state.startImmediately) {
|
|
|
|
ws_send("RESUME_TORRENT", { id });
|
|
|
|
}
|
|
|
|
dispatch(push(`/torrents/${id}`));
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2017-08-24 15:50:14 +02:00
|
|
|
uploadFile() {
|
|
|
|
this.setState({ loading: true });
|
2017-09-07 10:37:11 +02:00
|
|
|
const { magnet, file, startImmediately } = this.state;
|
2017-08-24 15:50:14 +02:00
|
|
|
const { dispatch } = this.props;
|
2017-09-07 09:56:47 +02:00
|
|
|
const customize = // TODO: File options
|
|
|
|
this.state.priority !== 3 ||
|
|
|
|
this.state.uploadThrottle !== -1 ||
|
|
|
|
this.state.downloadThrottle !== -1;
|
2017-09-07 10:37:11 +02:00
|
|
|
const handleOffer = async offer => {
|
2017-09-07 08:19:11 +02:00
|
|
|
switch (offer.type) {
|
|
|
|
case "TRANSFER_OFFER":
|
|
|
|
return await this.handleTransferOffer(offer, file);
|
|
|
|
case "RESOURCES_EXTANT":
|
|
|
|
const [id] = offer.ids;
|
2017-09-07 09:56:47 +02:00
|
|
|
this.applyOptions.bind(this)(id);
|
2017-09-07 08:19:11 +02:00
|
|
|
break;
|
|
|
|
}
|
2017-09-07 10:37:11 +02:00
|
|
|
};
|
|
|
|
if (magnet) {
|
|
|
|
ws_send("UPLOAD_MAGNET", {
|
|
|
|
uri: magnet,
|
|
|
|
start: startImmediately && !customize
|
|
|
|
}, handleOffer);
|
|
|
|
} else {
|
|
|
|
ws_send("UPLOAD_TORRENT", {
|
|
|
|
size: file.size,
|
|
|
|
start: startImmediately && !customize
|
|
|
|
}, handleOffer);
|
|
|
|
}
|
2017-09-07 08:19:11 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
processTorrent(torrent) {
|
|
|
|
const { info } = torrent;
|
|
|
|
if (!info.files) {
|
|
|
|
this.setState({
|
|
|
|
files: [
|
|
|
|
{
|
|
|
|
name: info.name,
|
|
|
|
length: info.length
|
|
|
|
}
|
|
|
|
],
|
|
|
|
torrent
|
2017-08-24 15:50:14 +02:00
|
|
|
});
|
2017-09-07 08:19:11 +02:00
|
|
|
} else {
|
|
|
|
// TODO
|
|
|
|
}
|
2017-08-24 15:50:14 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
handleFile(e) {
|
|
|
|
const file = e.target.files[0];
|
2017-09-07 08:19:11 +02:00
|
|
|
const reader = new FileReader();
|
|
|
|
reader.onload = e => {
|
|
|
|
try {
|
|
|
|
const torrent = bencode.decode(reader.result, 'utf8');
|
|
|
|
this.processTorrent.bind(this)(torrent);
|
|
|
|
} catch (ex) {
|
|
|
|
// TODO: something meaningful
|
|
|
|
console.log(ex);
|
|
|
|
}
|
|
|
|
};
|
|
|
|
reader.readAsArrayBuffer(file);
|
2017-08-24 15:50:14 +02:00
|
|
|
this.setState({ file });
|
|
|
|
}
|
|
|
|
|
2017-09-07 08:19:11 +02:00
|
|
|
renderOptions() {
|
|
|
|
return (
|
|
|
|
<Card>
|
|
|
|
<CardBlock>
|
|
|
|
<FormGroup>
|
|
|
|
<Label for="start-immediately" check style={{paddingLeft: 0}}>
|
|
|
|
<input
|
|
|
|
type="checkbox"
|
|
|
|
checked={this.state.startImmediately}
|
|
|
|
onChange={e => this.setState({
|
|
|
|
startImmediately: !this.state.startImmediately
|
|
|
|
})}
|
|
|
|
id="start-immediately"
|
|
|
|
/> Start immediately
|
|
|
|
</Label>
|
|
|
|
</FormGroup>
|
|
|
|
<FormGroup>
|
|
|
|
<Label for="priority">Priority</Label>
|
2017-09-07 08:31:29 +02:00
|
|
|
<Input
|
|
|
|
type="select"
|
|
|
|
id="priority"
|
|
|
|
value={this.state.priority}
|
|
|
|
onChange={e =>
|
|
|
|
this.setState({ priority: parseInt(e.target.value)})
|
|
|
|
}
|
|
|
|
>
|
|
|
|
<option value="1">Lowest</option>
|
|
|
|
<option value="2">Low</option>
|
|
|
|
<option value="3">Normal</option>
|
|
|
|
<option value="4">High</option>
|
|
|
|
<option value="5">Highest</option>
|
2017-09-07 08:19:11 +02:00
|
|
|
</Input>
|
|
|
|
</FormGroup>
|
2017-09-07 08:31:29 +02:00
|
|
|
<Throttle
|
|
|
|
prop="dl-throttle"
|
|
|
|
legend="Download throttle"
|
|
|
|
onChange={limit => this.setState({ downloadThrottle: limit })}
|
|
|
|
/>
|
|
|
|
<Throttle
|
|
|
|
prop="ul-throttle"
|
|
|
|
legend="Upload throttle"
|
|
|
|
onChange={limit => this.setState({ uploadThrottle: limit })}
|
|
|
|
/>
|
2017-09-07 08:19:11 +02:00
|
|
|
</CardBlock>
|
|
|
|
</Card>
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
renderTorrent() {
|
2017-09-07 10:37:11 +02:00
|
|
|
const { magnet, torrent, file, files, loading } = this.state;
|
2017-09-07 08:19:11 +02:00
|
|
|
|
|
|
|
const details = {
|
|
|
|
"comment": d => d,
|
|
|
|
"creation date": d => new Date(d * 1000).toDateString(),
|
|
|
|
"created by": d => d
|
|
|
|
};
|
|
|
|
|
|
|
|
return (
|
|
|
|
<Card style={{marginBottom: "1rem"}}>
|
2017-09-07 10:37:11 +02:00
|
|
|
{torrent &&
|
|
|
|
<CardBlock>
|
|
|
|
<CardTitle>{magnet && "Magnet link" || file.name}</CardTitle>
|
|
|
|
<CardText>
|
|
|
|
<dl style={{marginBottom: "0"}}>
|
|
|
|
{Object.keys(details).map(key =>
|
|
|
|
torrent[key] && (
|
|
|
|
<div>
|
|
|
|
<dt>{key}</dt>
|
|
|
|
<dd>{details[key](torrent[key])}</dd>
|
|
|
|
</div>
|
|
|
|
)
|
|
|
|
)}
|
|
|
|
</dl>
|
|
|
|
</CardText>
|
|
|
|
</CardBlock>
|
|
|
|
}
|
2017-09-07 08:19:11 +02:00
|
|
|
<ToggleContainer className="form-group" title="Options">
|
|
|
|
{this.renderOptions.bind(this)()}
|
|
|
|
</ToggleContainer>
|
2017-09-07 10:37:11 +02:00
|
|
|
{torrent &&
|
|
|
|
<ToggleContainer className="form-group" title="Files">
|
|
|
|
<Card>
|
|
|
|
<CardBlock>
|
|
|
|
TODO
|
|
|
|
</CardBlock>
|
|
|
|
</Card>
|
|
|
|
</ToggleContainer>
|
|
|
|
}
|
2017-09-07 08:19:11 +02:00
|
|
|
<button
|
|
|
|
type="button"
|
|
|
|
className="btn btn-primary btn-block"
|
2017-09-07 10:37:11 +02:00
|
|
|
disabled={(file || magnet) && loading}
|
2017-09-07 08:19:11 +02:00
|
|
|
onClick={this.uploadFile.bind(this)}
|
|
|
|
>Add torrent</button>
|
|
|
|
{loading ?
|
|
|
|
<Progress
|
|
|
|
value={100}
|
|
|
|
animated={true}
|
|
|
|
striped={true}
|
|
|
|
color="info"
|
|
|
|
className="progress-maxheight"
|
|
|
|
/> : null}
|
|
|
|
</Card>
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2017-08-24 15:50:14 +02:00
|
|
|
render() {
|
|
|
|
return (
|
|
|
|
<div>
|
|
|
|
<h3>Add torrent</h3>
|
2017-09-07 10:37:11 +02:00
|
|
|
{this.state.magnet && this.state.useMagnet &&
|
|
|
|
<p style={{
|
|
|
|
textOverflow: "ellipsis",
|
|
|
|
overflowX: "hidden",
|
|
|
|
whiteSpace: "nowrap"
|
|
|
|
}}>{this.state.magnet}</p>
|
|
|
|
}
|
|
|
|
{(this.state.torrent || this.state.magnet) && this.renderTorrent.bind(this)()}
|
2017-08-24 15:50:14 +02:00
|
|
|
<div className="form-group">
|
|
|
|
<input
|
|
|
|
style={{display: "none"}}
|
|
|
|
type="file"
|
|
|
|
accept=".torrent"
|
|
|
|
onChange={this.handleFile.bind(this)}
|
|
|
|
/>
|
2017-09-07 10:37:11 +02:00
|
|
|
{!this.state.useMagnet && (this.state.torrent ?
|
2017-09-07 08:19:11 +02:00
|
|
|
<div style={{
|
|
|
|
display: "flex",
|
|
|
|
flexDirection: "column",
|
|
|
|
alignItems: "center"
|
|
|
|
}}>
|
2017-08-24 15:50:14 +02:00
|
|
|
<button
|
|
|
|
type="button"
|
2017-09-07 08:19:11 +02:00
|
|
|
className="btn btn-default"
|
|
|
|
onClick={() => findDOMNode(this).querySelector("input[type='file']").click()}
|
|
|
|
>Select a different torrent?</button>
|
2017-08-24 15:50:14 +02:00
|
|
|
</div>
|
2017-09-07 08:19:11 +02:00
|
|
|
:
|
2017-09-07 10:37:11 +02:00
|
|
|
<div>
|
|
|
|
<button
|
|
|
|
type="button"
|
|
|
|
className="btn btn-default btn-block"
|
|
|
|
onClick={() => findDOMNode(this).querySelector("input[type='file']").click()}
|
|
|
|
>Select torrent</button>
|
|
|
|
<div className="text-centered" style={{margin: "1rem auto"}}>
|
|
|
|
- or -
|
|
|
|
</div>
|
|
|
|
<FormGroup>
|
|
|
|
<Input
|
|
|
|
type="text"
|
|
|
|
placeholder="Magnet link"
|
|
|
|
value={this.state.magnet}
|
|
|
|
onChange={e => this.setState({ magnet: e.target.value })}
|
|
|
|
/>
|
|
|
|
</FormGroup>
|
|
|
|
<button
|
|
|
|
type="button"
|
|
|
|
className="btn btn-default btn-block"
|
|
|
|
onClick={() => this.setState({ useMagnet: true })}
|
|
|
|
disabled={(() => {
|
|
|
|
const a = document.createElement("a");
|
|
|
|
a.href = this.state.magnet;
|
|
|
|
return a.protocol !== "magnet:";
|
|
|
|
})()}
|
|
|
|
>Add magnet</button>
|
|
|
|
</div>
|
|
|
|
)}
|
2017-08-24 15:50:14 +02:00
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
);
|
|
|
|
}
|
2017-08-24 14:30:22 +02:00
|
|
|
}
|
2017-08-24 15:50:14 +02:00
|
|
|
|
|
|
|
export default connect()(AddTorrent);
|