Merge branch 'torrentSearch' into 'master'
Torrent search See merge request !88
This commit is contained in:
commit
f05d0024fc
@ -17,6 +17,7 @@ movie:
|
|||||||
- trakttv
|
- trakttv
|
||||||
torrenters:
|
torrenters:
|
||||||
- yts
|
- yts
|
||||||
|
- thepiratebay
|
||||||
searchers:
|
searchers:
|
||||||
- yts
|
- yts
|
||||||
explorers:
|
explorers:
|
||||||
@ -24,6 +25,7 @@ movie:
|
|||||||
- trakttv
|
- trakttv
|
||||||
show:
|
show:
|
||||||
torrenters:
|
torrenters:
|
||||||
|
- thepiratebay
|
||||||
- eztv
|
- eztv
|
||||||
detailers:
|
detailers:
|
||||||
- tvdb
|
- tvdb
|
||||||
@ -34,6 +36,11 @@ show:
|
|||||||
- trakttv
|
- trakttv
|
||||||
- eztv
|
- eztv
|
||||||
modules_params:
|
modules_params:
|
||||||
|
- name: thepiratebay
|
||||||
|
show_users:
|
||||||
|
- EtHD
|
||||||
|
movie_users:
|
||||||
|
- YIFY
|
||||||
- name: trakttv
|
- name: trakttv
|
||||||
client_id: my_trakttv_client_id
|
client_id: my_trakttv_client_id
|
||||||
- name: tmdb
|
- name: tmdb
|
||||||
|
@ -20,6 +20,11 @@ func (b *Backend) GetTorrents(media interface{}, log *logrus.Entry) error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SearchTorrents implements the polochon Torrenter interface
|
||||||
|
func (b *Backend) SearchTorrents(s string) ([]*polochon.Torrent, error) {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
// GetMovieTorrents fetch Torrents for movies
|
// GetMovieTorrents fetch Torrents for movies
|
||||||
func (b *Backend) GetMovieTorrents(pmovie *polochon.Movie, log *logrus.Entry) error {
|
func (b *Backend) GetMovieTorrents(pmovie *polochon.Movie, log *logrus.Entry) error {
|
||||||
movieTorrents, err := GetMovieTorrents(b.Database, pmovie.ImdbID)
|
movieTorrents, err := GetMovieTorrents(b.Database, pmovie.ImdbID)
|
||||||
|
@ -4,9 +4,12 @@ import (
|
|||||||
"encoding/json"
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"sort"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
|
||||||
"github.com/gorilla/mux"
|
"github.com/gorilla/mux"
|
||||||
|
"github.com/odwrtw/polochon/lib"
|
||||||
|
"github.com/sirupsen/logrus"
|
||||||
|
|
||||||
"gitlab.quimbo.fr/odwrtw/canape-sql/src/internal/auth"
|
"gitlab.quimbo.fr/odwrtw/canape-sql/src/internal/auth"
|
||||||
"gitlab.quimbo.fr/odwrtw/canape-sql/src/internal/users"
|
"gitlab.quimbo.fr/odwrtw/canape-sql/src/internal/users"
|
||||||
@ -94,3 +97,55 @@ func RemoveHandler(env *web.Env, w http.ResponseWriter, r *http.Request) error {
|
|||||||
|
|
||||||
return env.RenderOK(w, "Torrent removed")
|
return env.RenderOK(w, "Torrent removed")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SearchHandler search for torrents
|
||||||
|
func SearchHandler(env *web.Env, w http.ResponseWriter, r *http.Request) error {
|
||||||
|
log := env.Log.WithFields(logrus.Fields{
|
||||||
|
"function": "torrents.SearchHandler",
|
||||||
|
})
|
||||||
|
|
||||||
|
vars := mux.Vars(r)
|
||||||
|
searchType := vars["type"]
|
||||||
|
searchStr := vars["search"]
|
||||||
|
|
||||||
|
// Get the appropriate torrenters
|
||||||
|
var torrenters []polochon.Torrenter
|
||||||
|
switch searchType {
|
||||||
|
case "movies":
|
||||||
|
torrenters = env.Config.MovieTorrenters
|
||||||
|
case "shows":
|
||||||
|
torrenters = env.Config.ShowTorrenters
|
||||||
|
default:
|
||||||
|
return env.RenderError(w, errors.New("invalid search type"))
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Debugf("searching for %s torrents for the query %q", searchType, searchStr)
|
||||||
|
|
||||||
|
// Search for torrents
|
||||||
|
results := []*polochon.Torrent{}
|
||||||
|
for _, torrenter := range torrenters {
|
||||||
|
torrents, err := torrenter.SearchTorrents(searchStr)
|
||||||
|
if err != nil {
|
||||||
|
log.Warn(err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if torrents == nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
results = append(results, torrents...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sort by seeds
|
||||||
|
sort.Sort(BySeed(results))
|
||||||
|
|
||||||
|
return env.RenderJSON(w, results)
|
||||||
|
}
|
||||||
|
|
||||||
|
// BySeed is an helper to sort torrents by seeders
|
||||||
|
type BySeed []*polochon.Torrent
|
||||||
|
|
||||||
|
func (t BySeed) Len() int { return len(t) }
|
||||||
|
func (t BySeed) Swap(i, j int) { t[i], t[j] = t[j], t[i] }
|
||||||
|
func (t BySeed) Less(i, j int) bool { return t[i].Seeders > t[j].Seeders }
|
||||||
|
@ -13,6 +13,7 @@ import (
|
|||||||
_ "github.com/odwrtw/polochon/modules/pam"
|
_ "github.com/odwrtw/polochon/modules/pam"
|
||||||
_ "github.com/odwrtw/polochon/modules/pushover"
|
_ "github.com/odwrtw/polochon/modules/pushover"
|
||||||
_ "github.com/odwrtw/polochon/modules/tmdb"
|
_ "github.com/odwrtw/polochon/modules/tmdb"
|
||||||
|
_ "github.com/odwrtw/polochon/modules/tpb"
|
||||||
_ "github.com/odwrtw/polochon/modules/trakttv"
|
_ "github.com/odwrtw/polochon/modules/trakttv"
|
||||||
_ "github.com/odwrtw/polochon/modules/transmission"
|
_ "github.com/odwrtw/polochon/modules/transmission"
|
||||||
_ "github.com/odwrtw/polochon/modules/tvdb"
|
_ "github.com/odwrtw/polochon/modules/tvdb"
|
||||||
|
@ -19,7 +19,7 @@ export function removeTorrent(id) {
|
|||||||
"REMOVE_TORRENT",
|
"REMOVE_TORRENT",
|
||||||
configureAxios().delete(`/torrents/${id}`),
|
configureAxios().delete(`/torrents/${id}`),
|
||||||
[
|
[
|
||||||
fetchTorrents(),
|
() => fetchTorrents(),
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@ -30,3 +30,10 @@ export function fetchTorrents() {
|
|||||||
configureAxios().get("/torrents")
|
configureAxios().get("/torrents")
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function searchTorrents(url) {
|
||||||
|
return request(
|
||||||
|
"TORRENTS_SEARCH",
|
||||||
|
configureAxios().get(url)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
@ -44,7 +44,7 @@ function TorrentsStat(props) {
|
|||||||
return (<span>No torrents</span>);
|
return (<span>No torrents</span>);
|
||||||
}
|
}
|
||||||
|
|
||||||
const percentage = Math.floor((props.data.torrentCount * 100) / props.data.count);
|
const percentage = Math.floor((props.data.torrentCountById * 100) / props.data.count);
|
||||||
return (
|
return (
|
||||||
<span>
|
<span>
|
||||||
{percentage}% with torrents
|
{percentage}% with torrents
|
||||||
|
@ -45,12 +45,11 @@ function MovieButtons(props) {
|
|||||||
lastFetchUrl={props.lastFetchUrl}
|
lastFetchUrl={props.lastFetchUrl}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{props.movie.get("torrents") !== null &&
|
|
||||||
<TorrentsButton
|
<TorrentsButton
|
||||||
|
movieTitle={props.movie.get("title")}
|
||||||
torrents={props.movie.get("torrents")}
|
torrents={props.movie.get("torrents")}
|
||||||
addTorrent={props.addTorrent}
|
addTorrent={props.addTorrent}
|
||||||
/>
|
/>
|
||||||
}
|
|
||||||
|
|
||||||
<DownloadButton
|
<DownloadButton
|
||||||
url={props.movie.get("polochon_url")}
|
url={props.movie.get("polochon_url")}
|
||||||
|
@ -13,8 +13,16 @@ export default class TorrentsButton extends React.PureComponent {
|
|||||||
}
|
}
|
||||||
render() {
|
render() {
|
||||||
const entries = buildMenuItems(this.props.torrents);
|
const entries = buildMenuItems(this.props.torrents);
|
||||||
|
const searchUrl = `#/torrents/search/movies/${encodeURI(this.props.movieTitle)}`;
|
||||||
return (
|
return (
|
||||||
<DropdownButton className="btn btn-default btn-sm" title="Torrents" id="download-torrents-button" dropup>
|
<DropdownButton className="btn btn-default btn-sm" title="Torrents" id="download-torrents-button" dropup>
|
||||||
|
<MenuItem className="text-warning" header>Advanced</MenuItem>
|
||||||
|
<MenuItem href={searchUrl} >
|
||||||
|
<i className="fa fa-search" aria-hidden="true"></i> Search
|
||||||
|
</MenuItem>
|
||||||
|
{entries.length > 0 &&
|
||||||
|
<MenuItem divider></MenuItem>
|
||||||
|
}
|
||||||
{entries.map(function(e, index) {
|
{entries.map(function(e, index) {
|
||||||
switch (e.type) {
|
switch (e.type) {
|
||||||
case "header":
|
case "header":
|
||||||
@ -41,6 +49,10 @@ export default class TorrentsButton extends React.PureComponent {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function buildMenuItems(torrents) {
|
function buildMenuItems(torrents) {
|
||||||
|
if (!torrents) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
const t = torrents.groupBy((el) => el.get("source"));
|
const t = torrents.groupBy((el) => el.get("source"));
|
||||||
|
|
||||||
// Build the array of entries
|
// Build the array of entries
|
||||||
|
@ -64,7 +64,7 @@ export default class AppNavBar extends React.PureComponent {
|
|||||||
<WishlistDropdown />
|
<WishlistDropdown />
|
||||||
}
|
}
|
||||||
{loggedAndActivated &&
|
{loggedAndActivated &&
|
||||||
<Torrents torrentsCount={this.props.torrentCount} />
|
<TorrentsDropdown torrentsCount={this.props.torrentCount} />
|
||||||
}
|
}
|
||||||
<UserDropdown
|
<UserDropdown
|
||||||
username={this.props.username}
|
username={this.props.username}
|
||||||
@ -199,20 +199,31 @@ function WishlistDropdown() {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function Torrents(props) {
|
function TorrentsDropdown(props) {
|
||||||
|
const title = (<TorrentsDropdownTitle torrentsCount={props.torrentsCount} />)
|
||||||
return(
|
return(
|
||||||
<Nav>
|
<Nav>
|
||||||
<LinkContainer to="/torrents">
|
<NavDropdown title={title} id="navbar-wishlit-dropdown">
|
||||||
<NavItem>
|
<LinkContainer to="/torrents/list">
|
||||||
Torrents
|
<MenuItem>Downloads</MenuItem>
|
||||||
{props.torrentsCount > 0 &&
|
|
||||||
<span>
|
|
||||||
|
|
||||||
<span className="label label-info">{props.torrentsCount}</span>
|
|
||||||
</span>
|
|
||||||
}
|
|
||||||
</NavItem>
|
|
||||||
</LinkContainer>
|
</LinkContainer>
|
||||||
|
<LinkContainer to="/torrents/search">
|
||||||
|
<MenuItem>Search</MenuItem>
|
||||||
|
</LinkContainer>
|
||||||
|
</NavDropdown>
|
||||||
</Nav>
|
</Nav>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function TorrentsDropdownTitle(props) {
|
||||||
|
return (
|
||||||
|
<span>
|
||||||
|
Torrents
|
||||||
|
{props.torrentsCount > 0 &&
|
||||||
|
<span>
|
||||||
|
<span className="label label-info">{props.torrentsCount}</span>
|
||||||
|
</span>
|
||||||
|
}
|
||||||
|
</span>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
@ -12,6 +12,7 @@ import ImdbButton from "../buttons/imdb"
|
|||||||
import RefreshIndicator from "../buttons/refresh"
|
import RefreshIndicator from "../buttons/refresh"
|
||||||
|
|
||||||
import { OverlayTrigger, Tooltip } from "react-bootstrap"
|
import { OverlayTrigger, Tooltip } from "react-bootstrap"
|
||||||
|
import { Button, Dropdown, MenuItem } from "react-bootstrap"
|
||||||
|
|
||||||
function mapStateToProps(state) {
|
function mapStateToProps(state) {
|
||||||
return {
|
return {
|
||||||
@ -39,6 +40,7 @@ class ShowDetails extends React.Component {
|
|||||||
/>
|
/>
|
||||||
<SeasonsList
|
<SeasonsList
|
||||||
data={this.props.show}
|
data={this.props.show}
|
||||||
|
router={this.props.router}
|
||||||
addTorrent={this.props.addTorrent}
|
addTorrent={this.props.addTorrent}
|
||||||
addToWishlist={this.props.addShowToWishlist}
|
addToWishlist={this.props.addShowToWishlist}
|
||||||
getEpisodeDetails={this.props.getEpisodeDetails}
|
getEpisodeDetails={this.props.getEpisodeDetails}
|
||||||
@ -112,6 +114,8 @@ function SeasonsList(props){
|
|||||||
<Season
|
<Season
|
||||||
data={data}
|
data={data}
|
||||||
season={season}
|
season={season}
|
||||||
|
showName={props.data.get("title")}
|
||||||
|
router={props.router}
|
||||||
addTorrent={props.addTorrent}
|
addTorrent={props.addTorrent}
|
||||||
addToWishlist={props.addToWishlist}
|
addToWishlist={props.addToWishlist}
|
||||||
getEpisodeDetails={props.getEpisodeDetails}
|
getEpisodeDetails={props.getEpisodeDetails}
|
||||||
@ -158,6 +162,8 @@ class Season extends React.Component {
|
|||||||
<Episode
|
<Episode
|
||||||
key={key}
|
key={key}
|
||||||
data={episode}
|
data={episode}
|
||||||
|
showName={this.props.showName}
|
||||||
|
router={this.props.router}
|
||||||
addTorrent={this.props.addTorrent}
|
addTorrent={this.props.addTorrent}
|
||||||
addToWishlist={this.props.addToWishlist}
|
addToWishlist={this.props.addToWishlist}
|
||||||
getEpisodeDetails={this.props.getEpisodeDetails}
|
getEpisodeDetails={this.props.getEpisodeDetails}
|
||||||
@ -215,6 +221,8 @@ function Episode(props) {
|
|||||||
xs
|
xs
|
||||||
/>
|
/>
|
||||||
<GetDetailsButton
|
<GetDetailsButton
|
||||||
|
showName={props.showName}
|
||||||
|
router={props.router}
|
||||||
data={props.data}
|
data={props.data}
|
||||||
getEpisodeDetails={props.getEpisodeDetails}
|
getEpisodeDetails={props.getEpisodeDetails}
|
||||||
/>
|
/>
|
||||||
@ -336,23 +344,40 @@ class TrackButton extends React.PureComponent {
|
|||||||
class GetDetailsButton extends React.PureComponent {
|
class GetDetailsButton extends React.PureComponent {
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
super(props);
|
super(props);
|
||||||
this.handleClick = this.handleClick.bind(this);
|
this.handleFetchClick = this.handleFetchClick.bind(this);
|
||||||
|
this.handleAdvanceTorrentSearchClick = this.handleAdvanceTorrentSearchClick.bind(this);
|
||||||
|
this.state = {
|
||||||
|
imdbId: this.props.data.get("show_imdb_id"),
|
||||||
|
season: this.props.data.get("season"),
|
||||||
|
episode: this.props.data.get("episode"),
|
||||||
|
};
|
||||||
}
|
}
|
||||||
handleClick(e) {
|
handleFetchClick() {
|
||||||
e.preventDefault();
|
if (this.props.data.get("fetching")) { return }
|
||||||
if (this.props.data.get("fetching")) {
|
this.props.getEpisodeDetails(this.state.imdbId, this.state.season, this.state.episode);
|
||||||
return
|
|
||||||
}
|
}
|
||||||
const imdbId = this.props.data.get("show_imdb_id");
|
handleAdvanceTorrentSearchClick() {
|
||||||
const season = this.props.data.get("season");
|
const pad = (d) => (d < 10) ? "0" + d.toString() : d.toString();
|
||||||
const episode = this.props.data.get("episode");
|
const search = `${this.props.showName} S${pad(this.state.season)}E${pad(this.state.episode)}`;
|
||||||
this.props.getEpisodeDetails(imdbId, season, episode);
|
const url = `/torrents/search/shows/${encodeURI(search)}`;
|
||||||
|
this.props.router.push(url);
|
||||||
}
|
}
|
||||||
render() {
|
render() {
|
||||||
|
const id = `${this.state.imdbId}-${this.state.season}-${this.state.episode}-refresh-dropdown`;
|
||||||
return (
|
return (
|
||||||
<a type="button" className="btn btn-xs btn-info" onClick={(e) => this.handleClick(e)}>
|
<Dropdown id={id} dropup>
|
||||||
|
<Button className="btn-xs" bsStyle="info" onClick={this.handleFetchClick}>
|
||||||
<RefreshIndicator refresh={this.props.data.get("fetching")} />
|
<RefreshIndicator refresh={this.props.data.get("fetching")} />
|
||||||
</a>
|
</Button>
|
||||||
|
<Dropdown.Toggle className="btn-xs" bsStyle="info"/>
|
||||||
|
<Dropdown.Menu>
|
||||||
|
<MenuItem onClick={this.handleAdvanceTorrentSearchClick}>
|
||||||
|
<span>
|
||||||
|
<i className="fa fa-magnet"></i> Advanced torrent search
|
||||||
|
</span>
|
||||||
|
</MenuItem>
|
||||||
|
</Dropdown.Menu>
|
||||||
|
</Dropdown>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -3,8 +3,6 @@ import { connect } from "react-redux"
|
|||||||
import { bindActionCreators } from "redux"
|
import { bindActionCreators } from "redux"
|
||||||
import { addTorrent, removeTorrent } from "../../actions/torrents"
|
import { addTorrent, removeTorrent } from "../../actions/torrents"
|
||||||
|
|
||||||
import { OverlayTrigger, Tooltip } from "react-bootstrap"
|
|
||||||
|
|
||||||
function mapStateToProps(state) {
|
function mapStateToProps(state) {
|
||||||
return { torrents: state.torrentStore.get("torrents") };
|
return { torrents: state.torrentStore.get("torrents") };
|
||||||
}
|
}
|
||||||
@ -116,7 +114,6 @@ class Torrent extends React.PureComponent {
|
|||||||
this.props.removeTorrent(this.props.data.getIn(["additional_infos", "id"]));
|
this.props.removeTorrent(this.props.data.getIn(["additional_infos", "id"]));
|
||||||
}
|
}
|
||||||
render() {
|
render() {
|
||||||
const id = this.props.data.getIn(["additional_infos", "id"]);
|
|
||||||
const done = this.props.data.get("is_finished");
|
const done = this.props.data.get("is_finished");
|
||||||
var progressStyle = "progress-bar progress-bar-info active";
|
var progressStyle = "progress-bar progress-bar-info active";
|
||||||
if (done) {
|
if (done) {
|
||||||
@ -128,8 +125,6 @@ class Torrent extends React.PureComponent {
|
|||||||
percentDone = Number(percentDone).toFixed(1) + "%";
|
percentDone = Number(percentDone).toFixed(1) + "%";
|
||||||
}
|
}
|
||||||
|
|
||||||
const tooltip = (<Tooltip id={id}>Remove this torrent</Tooltip>);
|
|
||||||
|
|
||||||
// Pretty sizes
|
// Pretty sizes
|
||||||
const downloadedSize = prettySize(this.props.data.get("downloaded_size"));
|
const downloadedSize = prettySize(this.props.data.get("downloaded_size"));
|
||||||
const totalSize = prettySize(this.props.data.get("total_size"));
|
const totalSize = prettySize(this.props.data.get("total_size"));
|
||||||
@ -138,11 +133,7 @@ class Torrent extends React.PureComponent {
|
|||||||
<div className="panel panel-default">
|
<div className="panel panel-default">
|
||||||
<div className="panel-heading">
|
<div className="panel-heading">
|
||||||
{this.props.data.get("name")}
|
{this.props.data.get("name")}
|
||||||
<span className="clickable pull-right" onClick={this.handleClick}>
|
<span className="fa fa-trash clickable pull-right" onClick={this.handleClick}></span>
|
||||||
<OverlayTrigger placement="top" overlay={tooltip}>
|
|
||||||
<span className="fa fa-trash"></span>
|
|
||||||
</OverlayTrigger>
|
|
||||||
</span>
|
|
||||||
</div>
|
</div>
|
||||||
<div className="panel-body">
|
<div className="panel-body">
|
||||||
{started &&
|
{started &&
|
||||||
|
212
src/public/js/components/torrents/search.js
Normal file
212
src/public/js/components/torrents/search.js
Normal file
@ -0,0 +1,212 @@
|
|||||||
|
import React from "react"
|
||||||
|
import { connect } from "react-redux"
|
||||||
|
import { bindActionCreators } from "redux"
|
||||||
|
import { addTorrent, searchTorrents } from "../../actions/torrents"
|
||||||
|
import Loader from "../loader/loader"
|
||||||
|
|
||||||
|
import { OverlayTrigger, Tooltip } from "react-bootstrap"
|
||||||
|
|
||||||
|
function mapStateToProps(state) {
|
||||||
|
return {
|
||||||
|
searching: state.torrentStore.get("searching"),
|
||||||
|
results: state.torrentStore.get("searchResults"),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
const mapDispatchToProps = (dispatch) =>
|
||||||
|
bindActionCreators({ addTorrent, searchTorrents }, dispatch)
|
||||||
|
|
||||||
|
class TorrentSearch extends React.PureComponent {
|
||||||
|
constructor(props) {
|
||||||
|
super(props);
|
||||||
|
this.handleSearchInput = this.handleSearchInput.bind(this);
|
||||||
|
this.state = { search: (this.props.router.params.search || "") };
|
||||||
|
}
|
||||||
|
handleSearchInput() {
|
||||||
|
this.setState({ search: this.refs.search.value });
|
||||||
|
}
|
||||||
|
handleClick(type) {
|
||||||
|
if (this.state.search === "") { return }
|
||||||
|
const url = `/torrents/search/${type}/${encodeURI(this.state.search)}`;
|
||||||
|
this.props.router.push(url);
|
||||||
|
}
|
||||||
|
render() {
|
||||||
|
const searchFromURL = this.props.router.params.search || "";
|
||||||
|
const typeFromURL = this.props.router.params.type || "";
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<div className="col-xs-12">
|
||||||
|
<form className="form-horizontal" onSubmit={(e) => e.preventDefault()}>
|
||||||
|
<div className="form-group">
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
className="form-control"
|
||||||
|
placeholder="Search torrents"
|
||||||
|
value={this.state.search}
|
||||||
|
onChange={this.handleSearchInput}
|
||||||
|
ref="search"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
<div className="row">
|
||||||
|
<SearchButton
|
||||||
|
text="Search movies"
|
||||||
|
type="movies"
|
||||||
|
typeFromURL={typeFromURL}
|
||||||
|
handleClick={() => this.handleClick("movies")}
|
||||||
|
/>
|
||||||
|
<SearchButton
|
||||||
|
text="Search shows"
|
||||||
|
type="shows"
|
||||||
|
typeFromURL={typeFromURL}
|
||||||
|
handleClick={() => this.handleClick("shows")}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<hr />
|
||||||
|
<div className="row">
|
||||||
|
<TorrentList
|
||||||
|
searching={this.props.searching}
|
||||||
|
results={this.props.results}
|
||||||
|
addTorrent={this.props.addTorrent}
|
||||||
|
searchFromURL={searchFromURL}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function SearchButton(props) {
|
||||||
|
const color = (props.type === props.typeFromURL) ? "primary" : "default";
|
||||||
|
return (
|
||||||
|
<div className="col-xs-6">
|
||||||
|
<button
|
||||||
|
className={`btn btn-${color} full-width`}
|
||||||
|
type="button"
|
||||||
|
onClick={props.handleClick}
|
||||||
|
>
|
||||||
|
<i className="fa fa-search" aria-hidden="true"></i> {props.text}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function TorrentList(props) {
|
||||||
|
if (props.searching) {
|
||||||
|
return (<Loader />);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (props.searchFromURL === "") {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (props.results.size === 0) {
|
||||||
|
return (
|
||||||
|
<div className="col-xs-12">
|
||||||
|
<div className="well well-lg">
|
||||||
|
<h2>No results</h2>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="col-xs-12">
|
||||||
|
{props.results.map(function(el, index) {
|
||||||
|
return (
|
||||||
|
<Torrent
|
||||||
|
key={index}
|
||||||
|
data={el}
|
||||||
|
addTorrent={props.addTorrent}
|
||||||
|
/>);
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function Torrent(props) {
|
||||||
|
return (
|
||||||
|
<div className="row">
|
||||||
|
<div className="col-xs-12">
|
||||||
|
<table className="table responsive table-align-middle torrent-search-result">
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<td rowSpan="2" className="col-xs-1 torrent-small-width">
|
||||||
|
<h4>
|
||||||
|
<TorrentHealth
|
||||||
|
url={props.data.get("url")}
|
||||||
|
seeders={props.data.get("seeders")}
|
||||||
|
leechers={props.data.get("leechers")}
|
||||||
|
/>
|
||||||
|
</h4>
|
||||||
|
</td>
|
||||||
|
<td colSpan="4" className="col-xs-9 title">
|
||||||
|
<span className="torrent-title">{props.data.get("name")}</span>
|
||||||
|
</td>
|
||||||
|
<td rowSpan="2" className="col-xs-1 torrent-small-width">
|
||||||
|
<h4 className="pull-right clickable" onClick={() => props.addTorrent(props.data.get("url"))}>
|
||||||
|
<i className="fa fa-cloud-download" aria-hidden="true"></i>
|
||||||
|
</h4>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td className="col-xs-1 torrent-label">
|
||||||
|
<span className="label label-warning">{props.data.get("quality")}</span>
|
||||||
|
</td>
|
||||||
|
<td className="col-xs-1 torrent-label">
|
||||||
|
<span className="label label-success">{props.data.get("source")}</span>
|
||||||
|
</td>
|
||||||
|
<td className="col-xs-1 torrent-label">
|
||||||
|
<span className="label label-info">{props.data.get("upload_user")}</span>
|
||||||
|
</td>
|
||||||
|
<td className="col-xs-7 torrent-label"></td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function TorrentHealth(props) {
|
||||||
|
const seeders = props.seeders || 0;
|
||||||
|
const leechers = props.leechers || 1;
|
||||||
|
|
||||||
|
let color;
|
||||||
|
let health;
|
||||||
|
let ratio = seeders/leechers;
|
||||||
|
|
||||||
|
if (seeders > 20) {
|
||||||
|
health = "good";
|
||||||
|
color = "success";
|
||||||
|
} else {
|
||||||
|
if (ratio > 1) {
|
||||||
|
health = "medium";
|
||||||
|
color = "warning";
|
||||||
|
} else {
|
||||||
|
health = "bad";
|
||||||
|
color = "danger";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const className = `text text-${color}`;
|
||||||
|
const tooltip = (
|
||||||
|
<Tooltip id={`tooltip-health-${props.url}`}>
|
||||||
|
<p><span className={className}>Health: {health}</span></p>
|
||||||
|
<p>Seeders: {seeders}</p>
|
||||||
|
<p>Leechers: {props.leechers}</p>
|
||||||
|
</Tooltip>
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<OverlayTrigger placement="right" overlay={tooltip}>
|
||||||
|
<span className={className}>
|
||||||
|
<i className="fa fa-circle" aria-hidden="true"></i>
|
||||||
|
</span>
|
||||||
|
</OverlayTrigger>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default connect(mapStateToProps, mapDispatchToProps)(TorrentSearch);
|
@ -2,15 +2,22 @@ import { Map, List, fromJS } from "immutable"
|
|||||||
|
|
||||||
const defaultState = Map({
|
const defaultState = Map({
|
||||||
"fetching": false,
|
"fetching": false,
|
||||||
|
"searching": false,
|
||||||
"torrents": List(),
|
"torrents": List(),
|
||||||
|
"searchResults": List(),
|
||||||
});
|
});
|
||||||
|
|
||||||
const handlers = {
|
const handlers = {
|
||||||
"TORRENTS_FETCH_PENDING": state => state.set("fetching", false),
|
"TORRENTS_FETCH_PENDING": state => state.set("fetching", true),
|
||||||
"TORRENTS_FETCH_FULFILLED": (state, action) => state.merge(fromJS({
|
"TORRENTS_FETCH_FULFILLED": (state, action) => state.merge(fromJS({
|
||||||
fetching: false,
|
fetching: false,
|
||||||
torrents: action.payload.response.data,
|
torrents: action.payload.response.data,
|
||||||
})),
|
})),
|
||||||
|
"TORRENTS_SEARCH_PENDING": state => state.set("searching", true),
|
||||||
|
"TORRENTS_SEARCH_FULFILLED": (state, action) => state.merge(fromJS({
|
||||||
|
searching: false,
|
||||||
|
searchResults: action.payload.response.data,
|
||||||
|
})),
|
||||||
}
|
}
|
||||||
|
|
||||||
export default (state = defaultState, action) =>
|
export default (state = defaultState, action) =>
|
||||||
|
@ -30,12 +30,12 @@ export function request(eventPrefix, promise, callbackEvents = null, mainPayload
|
|||||||
})
|
})
|
||||||
promise
|
promise
|
||||||
.then(response => {
|
.then(response => {
|
||||||
if (response.data.status === "error")
|
if (response.status === "error")
|
||||||
{
|
{
|
||||||
dispatch({
|
dispatch({
|
||||||
type: "ADD_ALERT_ERROR",
|
type: "ADD_ALERT_ERROR",
|
||||||
payload: {
|
payload: {
|
||||||
message: response.data.message,
|
message: response.message,
|
||||||
main: mainPayload,
|
main: mainPayload,
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@ -50,7 +50,7 @@ export function request(eventPrefix, promise, callbackEvents = null, mainPayload
|
|||||||
})
|
})
|
||||||
if (callbackEvents) {
|
if (callbackEvents) {
|
||||||
for (let event of callbackEvents) {
|
for (let event of callbackEvents) {
|
||||||
if (typeof event === 'function') {
|
if (typeof event === "function") {
|
||||||
event = event();
|
event = event();
|
||||||
}
|
}
|
||||||
dispatch(event);
|
dispatch(event);
|
||||||
|
@ -6,9 +6,10 @@ import UserEdit from "./components/users/edit"
|
|||||||
import UserActivation from "./components/users/activation"
|
import UserActivation from "./components/users/activation"
|
||||||
import UserSignUp from "./components/users/signup"
|
import UserSignUp from "./components/users/signup"
|
||||||
import TorrentList from "./components/torrents/list"
|
import TorrentList from "./components/torrents/list"
|
||||||
|
import TorrentSearch from "./components/torrents/search"
|
||||||
import AdminPanel from "./components/admins/panel"
|
import AdminPanel from "./components/admins/panel"
|
||||||
|
|
||||||
import { fetchTorrents } from "./actions/torrents"
|
import { fetchTorrents, searchTorrents } from "./actions/torrents"
|
||||||
import { userLogout, getUserInfos } from "./actions/users"
|
import { userLogout, getUserInfos } from "./actions/users"
|
||||||
import { fetchMovies, getMovieExploreOptions } from "./actions/movies"
|
import { fetchMovies, getMovieExploreOptions } from "./actions/movies"
|
||||||
import { fetchShows, fetchShowDetails, getShowExploreOptions } from "./actions/shows"
|
import { fetchShows, fetchShowDetails, getShowExploreOptions } from "./actions/shows"
|
||||||
@ -240,7 +241,7 @@ export default function getRoutes(App) {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: "/torrents",
|
path: "/torrents/list",
|
||||||
component: TorrentList,
|
component: TorrentList,
|
||||||
onEnter: function(nextState, replace, next) {
|
onEnter: function(nextState, replace, next) {
|
||||||
loginCheck(nextState, replace, next, function() {
|
loginCheck(nextState, replace, next, function() {
|
||||||
@ -248,6 +249,22 @@ export default function getRoutes(App) {
|
|||||||
});
|
});
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
path: "/torrents/search",
|
||||||
|
component: TorrentSearch,
|
||||||
|
onEnter: function(nextState, replace, next) {
|
||||||
|
loginCheck(nextState, replace, next);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: "/torrents/search/:type/:search",
|
||||||
|
component: TorrentSearch,
|
||||||
|
onEnter: function(nextState, replace, next) {
|
||||||
|
loginCheck(nextState, replace, next, function() {
|
||||||
|
store.dispatch(searchTorrents(`/torrents/search/${nextState.params.type}/${encodeURI(nextState.params.search)}`));
|
||||||
|
});
|
||||||
|
},
|
||||||
|
},
|
||||||
{
|
{
|
||||||
path: "/admin",
|
path: "/admin",
|
||||||
component: AdminPanel,
|
component: AdminPanel,
|
||||||
|
@ -81,3 +81,25 @@ div.sweet-alert > h2 {
|
|||||||
margin-left: 3px;
|
margin-left: 3px;
|
||||||
margin-right: 3px;
|
margin-right: 3px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
button.full-width {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
table.torrent-search-result {
|
||||||
|
font-size: 15px;
|
||||||
|
margin-bottom: 5px;
|
||||||
|
border-bottom: 2px solid @gray-light;
|
||||||
|
td.torrent-small-width {
|
||||||
|
width: 1%;
|
||||||
|
}
|
||||||
|
td.torrent-label {
|
||||||
|
padding-top: 0px;
|
||||||
|
padding-bottom: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
table.table-align-middle > tbody > tr > td {
|
||||||
|
vertical-align: middle;
|
||||||
|
}
|
||||||
|
@ -54,6 +54,7 @@ func setupRoutes(env *web.Env) {
|
|||||||
env.Handle("/torrents", torrents.DownloadHandler).WithRole(users.UserRole).Methods("POST")
|
env.Handle("/torrents", torrents.DownloadHandler).WithRole(users.UserRole).Methods("POST")
|
||||||
env.Handle("/torrents", torrents.ListHandler).WithRole(users.UserRole).Methods("GET")
|
env.Handle("/torrents", torrents.ListHandler).WithRole(users.UserRole).Methods("GET")
|
||||||
env.Handle("/torrents/{id:[0-9]+}", torrents.RemoveHandler).WithRole(users.UserRole).Methods("DELETE")
|
env.Handle("/torrents/{id:[0-9]+}", torrents.RemoveHandler).WithRole(users.UserRole).Methods("DELETE")
|
||||||
|
env.Handle("/torrents/search/{type}/{search}", torrents.SearchHandler).WithRole(users.UserRole).Methods("GET")
|
||||||
|
|
||||||
// Route to refresh all movies and shows
|
// Route to refresh all movies and shows
|
||||||
env.Handle("/refresh", extmedias.RefreshHandler).WithRole(users.AdminRole).Methods("POST")
|
env.Handle("/refresh", extmedias.RefreshHandler).WithRole(users.AdminRole).Methods("POST")
|
||||||
|
Loading…
x
Reference in New Issue
Block a user