Merge branch 'torrents' into 'master'

Add torrent page

See merge request !69
This commit is contained in:
Lucas 2017-05-20 14:26:10 +00:00
commit a22c57e4e5
6 changed files with 212 additions and 0 deletions

View File

@ -284,3 +284,10 @@ export function addTorrent(url) {
], ],
) )
} }
export function fetchTorrents() {
return request(
'TORRENTS_FETCH',
configureAxios().get('/torrents')
)
}

View File

@ -43,6 +43,7 @@ import ShowDetails from './components/shows/details'
import UserLoginForm from './components/users/login' import UserLoginForm from './components/users/login'
import UserEdit from './components/users/edit' import UserEdit from './components/users/edit'
import UserSignUp from './components/users/signup' import UserSignUp from './components/users/signup'
import TorrentList from './components/torrents/list'
function Main(props) { function Main(props) {
return ( return (
@ -61,6 +62,7 @@ function mapStateToProps(state) {
movieStore: state.movieStore, movieStore: state.movieStore,
showStore: state.showStore, showStore: state.showStore,
userStore: state.userStore, userStore: state.userStore,
torrentStore: state.torrentStore,
alerts: state.alerts, alerts: state.alerts,
} }
} }
@ -70,7 +72,14 @@ function mapDispatchToProps(dispatch) {
} }
const App = connect(mapStateToProps, mapDispatchToProps)(Main); const App = connect(mapStateToProps, mapDispatchToProps)(Main);
export function startPollingTorrents() {
return request(
'TORRENTS_FETCH',
configureAxios().get('/torrents')
)
}
var pollingTorrentsId;
const loginCheck = function(nextState, replace, next, f) { const loginCheck = function(nextState, replace, next, f) {
const state = store.getState(); const state = store.getState();
const isLogged = state.userStore.isLogged; const isLogged = state.userStore.isLogged;
@ -91,6 +100,13 @@ const loginCheck = function(nextState, replace, next, f) {
replace('/users/login'); replace('/users/login');
} else { } else {
f(); f();
// Poll torrents once logged
if (!pollingTorrentsId) {
// Fetch the torrents every 10s
pollingTorrentsId = setInterval(function() {
store.dispatch(actionCreators.fetchTorrents());
}, 10000);
}
} }
next(); next();
@ -208,6 +224,15 @@ const routes = {
}); });
}, },
}, },
{
path: '/torrents',
component: TorrentList,
onEnter: function(nextState, replace, next) {
loginCheck(nextState, replace, next, function() {
store.dispatch(actionCreators.fetchTorrents());
});
},
},
], ],
} }

View File

@ -16,6 +16,10 @@ export default function NavBar(props) {
<MoviesDropdown username={props.userStore.username} /> <MoviesDropdown username={props.userStore.username} />
<ShowsDropdown username={props.userStore.username} /> <ShowsDropdown username={props.userStore.username} />
<WishlistDropdown username={props.userStore.username} /> <WishlistDropdown username={props.userStore.username} />
<Torrents
username={props.userStore.username}
torrentsCount={props.torrentStore.torrents.length}
/>
<UserDropdown username={props.userStore.username} /> <UserDropdown username={props.userStore.username} />
<Search <Search
placeholder="Search movies" placeholder="Search movies"
@ -148,3 +152,24 @@ function WishlistDropdown(props) {
</Nav> </Nav>
); );
} }
function Torrents(props) {
if (props.username === "") {
return null;
}
return(
<Nav>
<LinkContainer to="/torrents">
<NavItem>
Torrents
{props.torrentsCount > 0 &&
<span>
&nbsp;
<span className="label label-info">{props.torrentsCount}</span>
</span>
}
</NavItem>
</LinkContainer>
</Nav>
);
}

View File

@ -0,0 +1,133 @@
import React from 'react'
export default function TorrentList(props){
return (
<div>
<AddTorrent func={props.addTorrent} />
<List torrents={props.torrentStore.torrents} />
</div>
);
}
class AddTorrent extends React.Component {
constructor(props) {
super(props);
this.state = { url: '' };
this.handleSubmit = this.handleSubmit.bind(this);
this.handleChange = this.handleChange.bind(this);
}
handleChange(event) {
this.setState({ url: event.target.value });
}
handleSubmit() {
if (this.state.url === "") {
return;
}
this.setState({ url: '' });
this.props.func(this.state.url);
}
render() {
return (
<div className="row">
<div className="col-xs-12 col-md-12">
<form className="input-group" onSubmit={this.handleSubmit}>
<input
className="form-control"
placeholder="Add torrent URL"
onChange={this.handleChange}
value={this.state.url}
/>
<span className="input-group-btn">
<button
className="btn btn-primary"
type="button"
onClick={this.handleSubmit}
>
Add
</button>
</span>
</form>
</div>
</div>
);
}
}
function List(props){
if (props.torrents.length === 0) {
return (
<div className="row">
<div className="col-xs-12 col-md-12">
<h3>Torrents</h3>
<div className="panel panel-default">
<div className="panel-heading">No torrents</div>
</div>
</div>
</div>
);
}
return (
<div className="row">
<div className="col-xs-12 col-md-12">
<h3>Torrents</h3>
{props.torrents.map(function(el, index) {
return (
<Torrent key={index} data={el} />
);
})}
</div>
</div>
);
}
function Torrent(props){
const done = props.data.is_finished;
var progressStyle = 'progress-bar progress-bar-warning';
if (done) {
progressStyle = 'progress-bar progress-bar-success';
}
var percentDone = props.data.percent_done;
const started = (percentDone !== 0);
if (started) {
percentDone = Number(percentDone).toFixed(1) + '%';
}
var downloadedSize = prettySize(props.data.downloaded_size);
var totalSize = prettySize(props.data.total_size);
var downloadRate = prettySize(props.data.download_rate) + "/s";
return (
<div className="panel panel-default">
<div className="panel-heading">{props.data.name}</div>
<div className="panel-body">
{started &&
<div className="progress progress-striped">
<div
className={progressStyle}
style={{width: percentDone}}>
</div>
</div>
}
{!started &&
<p>Download not yet started</p>
}
{started &&
<div>
<p>{downloadedSize} / {totalSize} - {percentDone} - {downloadRate}</p>
</div>
}
</div>
</div>
);
}
function prettySize(fileSizeInBytes) {
var i = -1;
var byteUnits = [' kB', ' MB', ' GB', ' TB', 'PB', 'EB', 'ZB', 'YB'];
do {
fileSizeInBytes = fileSizeInBytes / 1024;
i++;
} while (fileSizeInBytes > 1024);
return Math.max(fileSizeInBytes, 0.1).toFixed(1) + byteUnits[i];
};

View File

@ -5,6 +5,7 @@ import movieStore from './movies'
import showStore from './shows' import showStore from './shows'
import userStore from './users' import userStore from './users'
import alerts from './alerts' import alerts from './alerts'
import torrentStore from './torrents'
// Use combine form form react-redux-form, it's a thin wrapper arround the // Use combine form form react-redux-form, it's a thin wrapper arround the
// default combinedReducers provided with React. It allows the forms to be // default combinedReducers provided with React. It allows the forms to be
@ -15,6 +16,7 @@ const rootReducer = combineForms({
showStore, showStore,
userStore, userStore,
alerts, alerts,
torrentStore,
}) })
export default rootReducer; export default rootReducer;

View File

@ -0,0 +1,20 @@
const defaultState = {
fetching: false,
torrents: [],
};
export default function showStore(state = defaultState, action) {
switch (action.type) {
case 'TORRENTS_FETCH_PENDING':
return Object.assign({}, state, {
fetching: true,
})
case 'TORRENTS_FETCH_FULFILLED':
return Object.assign({}, state, {
fetching: false,
torrents: action.payload.data,
})
default:
return state
}
}