Compare commits
6 Commits
2f0497ebc6
...
8144cabc76
Author | SHA1 | Date | |
---|---|---|---|
8144cabc76 | |||
57b70eb585 | |||
8e8a1dc3e9 | |||
61bfd9eee6 | |||
ab3cf82fa9 | |||
6a946d137d |
@ -4,6 +4,7 @@ import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"git.quimbo.fr/odwrtw/canape/backend/auth"
|
||||
@ -24,18 +25,16 @@ const (
|
||||
|
||||
// Eventers is a global map of all the available Eventers
|
||||
var Eventers map[string]*PolochonEventers
|
||||
var eventersSetup bool
|
||||
var once sync.Once
|
||||
|
||||
func initEventers(env *web.Env) {
|
||||
if eventersSetup {
|
||||
return
|
||||
}
|
||||
|
||||
Eventers = map[string]*PolochonEventers{
|
||||
torrentEventName: NewTorrentEventers(env),
|
||||
videoEventName: NewVideoEventers(env),
|
||||
}
|
||||
eventersSetup = true
|
||||
once.Do(func() {
|
||||
env.Log.Infof("Initialising eventers")
|
||||
Eventers = map[string]*PolochonEventers{
|
||||
torrentEventName: NewTorrentEventers(env),
|
||||
videoEventName: NewVideoEventers(env),
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// WsHandler handles the websockets messages
|
||||
|
@ -13,9 +13,12 @@ import (
|
||||
// TorrentEventer represents the Eventer for torrents
|
||||
type TorrentEventer struct {
|
||||
*BaseEventer
|
||||
done chan struct{}
|
||||
pClient *papi.Client
|
||||
torrents []papi.Torrent
|
||||
done chan struct{}
|
||||
pClient *papi.Client
|
||||
// previous keep the previous data
|
||||
previous []*papi.Torrent
|
||||
// data holds the computed data
|
||||
data []*models.TorrentVideo
|
||||
}
|
||||
|
||||
var torrentEventName = "torrents"
|
||||
@ -43,9 +46,8 @@ func NewTorrentEventer(env *web.Env, polo *models.Polochon) (Eventer, error) {
|
||||
users: []*Channel{},
|
||||
name: torrentEventName,
|
||||
},
|
||||
pClient: client,
|
||||
done: make(chan struct{}),
|
||||
torrents: []papi.Torrent{},
|
||||
pClient: client,
|
||||
done: make(chan struct{}),
|
||||
}
|
||||
|
||||
return tn, nil
|
||||
@ -60,7 +62,7 @@ func (t *TorrentEventer) Append(chanl *Channel) {
|
||||
Type: torrentEventName,
|
||||
Status: OK,
|
||||
},
|
||||
Data: t.torrents,
|
||||
Data: t.data,
|
||||
}
|
||||
|
||||
chanl.sendEvent(event)
|
||||
@ -96,27 +98,23 @@ func (t *TorrentEventer) Launch() error {
|
||||
|
||||
// torrentsUpdate sends to the eventStream if torrents change
|
||||
func (t *TorrentEventer) torrentsUpdate() error {
|
||||
// Get torrents
|
||||
torrents, err := t.pClient.GetTorrents()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if reflect.DeepEqual(t.torrents, torrents) {
|
||||
if reflect.DeepEqual(t.previous, torrents) {
|
||||
return nil
|
||||
}
|
||||
|
||||
t.env.Log.Debugf("torrents have changed!")
|
||||
|
||||
notification := make([]*models.TorrentVideo, len(torrents))
|
||||
for i := range torrents {
|
||||
notification[i] = models.NewTorrentVideo(&torrents[i])
|
||||
notification[i].Update(t.env.Backend.Detailer, t.env.Log)
|
||||
}
|
||||
data := models.NewTorrentVideos(t.env.Backend.Detailer, t.env.Database, t.env.Log, torrents)
|
||||
|
||||
t.NotifyAll(notification)
|
||||
t.NotifyAll(data)
|
||||
|
||||
t.torrents = torrents
|
||||
t.previous = torrents
|
||||
t.data = data
|
||||
|
||||
return nil
|
||||
}
|
||||
|
@ -50,10 +50,10 @@ func run() error {
|
||||
return err
|
||||
}
|
||||
backend := &models.Backend{
|
||||
Database: db,
|
||||
PublicDir: cf.PublicDir,
|
||||
ImgURLPrefix: cf.ImgURLPrefix,
|
||||
Database: db,
|
||||
}
|
||||
models.SetPublicDir(cf.PublicDir)
|
||||
models.SetImgURLPrefix(cf.ImgURLPrefix)
|
||||
|
||||
// Generate auth params
|
||||
authParams := auth.Params{
|
||||
|
@ -7,10 +7,8 @@ import (
|
||||
|
||||
// Backend represents the data backend
|
||||
type Backend struct {
|
||||
Database *sqlx.DB
|
||||
PublicDir string
|
||||
ImgURLPrefix string
|
||||
configured bool
|
||||
Database *sqlx.DB
|
||||
configured bool
|
||||
}
|
||||
|
||||
// Name implements the Module interface
|
||||
|
@ -2,9 +2,6 @@ package models
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
polochon "github.com/odwrtw/polochon/lib"
|
||||
"github.com/sirupsen/logrus"
|
||||
@ -31,15 +28,6 @@ func (b *Backend) GetMovieDetails(pMovie *polochon.Movie, log *logrus.Entry) err
|
||||
return err
|
||||
}
|
||||
|
||||
// Add the movie images
|
||||
imgURL := fmt.Sprintf("movies/%s.jpg", pMovie.ImdbID)
|
||||
imgFile := filepath.Join(b.PublicDir, "img", imgURL)
|
||||
posterURL := ""
|
||||
if _, err := os.Stat(imgFile); !os.IsNotExist(err) {
|
||||
posterURL = b.ImgURLPrefix + imgURL
|
||||
}
|
||||
pMovie.Thumb = posterURL
|
||||
|
||||
log.Debugf("got movie %s from backend", pMovie.ImdbID)
|
||||
|
||||
return nil
|
||||
@ -59,6 +47,7 @@ func (b *Backend) GetShowDetails(pShow *polochon.Show, log *logrus.Entry) error
|
||||
log.Warnf("error while getting episodes: %s", err)
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
|
@ -2,6 +2,7 @@ package models
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/jmoiron/sqlx"
|
||||
@ -86,6 +87,12 @@ func FillEpisodeFromDB(eDB *episodeDB, pEpisode *polochon.ShowEpisode) {
|
||||
pEpisode.Thumb = eDB.Thumb
|
||||
pEpisode.Runtime = eDB.Runtime
|
||||
pEpisode.Aired = eDB.Aired
|
||||
pEpisode.Thumb = imageURL(fmt.Sprintf(
|
||||
"shows/%s/%d-%d.jpg",
|
||||
eDB.ShowImdbID,
|
||||
eDB.Season,
|
||||
eDB.Episode,
|
||||
))
|
||||
}
|
||||
|
||||
// GetEpisode gets an episode and fills the polochon episode
|
||||
|
30
backend/models/global.go
Normal file
30
backend/models/global.go
Normal file
@ -0,0 +1,30 @@
|
||||
package models
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
)
|
||||
|
||||
// Public gobal variables
|
||||
var (
|
||||
PublicDir string
|
||||
ImgURLPrefix string
|
||||
)
|
||||
|
||||
// SetPublicDir sets the public dir
|
||||
func SetPublicDir(s string) {
|
||||
PublicDir = s
|
||||
}
|
||||
|
||||
// SetImgURLPrefix sets the img url prefix
|
||||
func SetImgURLPrefix(s string) {
|
||||
ImgURLPrefix = s
|
||||
}
|
||||
|
||||
func imageURL(suffix string) string {
|
||||
imgFile := filepath.Join(PublicDir, "img", suffix)
|
||||
if _, err := os.Stat(imgFile); !os.IsNotExist(err) {
|
||||
return ImgURLPrefix + suffix
|
||||
}
|
||||
return ""
|
||||
}
|
@ -89,6 +89,7 @@ func FillMovieFromDB(mDB *movieDB, pMovie *polochon.Movie) {
|
||||
pMovie.Genres = mDB.Genres
|
||||
pMovie.SortTitle = mDB.SortTitle
|
||||
pMovie.Tagline = mDB.Tagline
|
||||
pMovie.Thumb = imageURL("movies/" + mDB.ImdbID + ".jpg")
|
||||
}
|
||||
|
||||
// updateFromMovie will update the movieDB from a Movie
|
||||
|
@ -42,7 +42,7 @@ type showDB struct {
|
||||
|
||||
// UpsertShow a show in the database
|
||||
func UpsertShow(db *sqlx.DB, s *polochon.Show) error {
|
||||
sDB := NewShowFromPolochon(s)
|
||||
sDB := newShowFromPolochon(s)
|
||||
// Upsert the show
|
||||
r, err := db.NamedQuery(upsertShowQuery, sDB)
|
||||
if err != nil {
|
||||
@ -60,8 +60,8 @@ func UpsertShow(db *sqlx.DB, s *polochon.Show) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// NewShowFromPolochon returns an showDB from a polochon Show
|
||||
func NewShowFromPolochon(s *polochon.Show) *showDB {
|
||||
// newShowFromPolochon returns an showDB from a polochon Show
|
||||
func newShowFromPolochon(s *polochon.Show) *showDB {
|
||||
sDB := showDB{
|
||||
ImdbID: s.ImdbID,
|
||||
TvdbID: s.TvdbID,
|
||||
@ -103,4 +103,7 @@ func FillShowFromDB(sDB *showDB, pShow *polochon.Show) {
|
||||
pShow.TvdbID = sDB.TvdbID
|
||||
pShow.Year = sDB.Year
|
||||
pShow.FirstAired = &sDB.FirstAired
|
||||
pShow.Banner = imageURL("shows/" + sDB.ImdbID + "/banner.jpg")
|
||||
pShow.Fanart = imageURL("shows/" + sDB.ImdbID + "/fanart.jpg")
|
||||
pShow.Poster = imageURL("shows/" + sDB.ImdbID + "/poster.jpg")
|
||||
}
|
||||
|
@ -1,6 +1,7 @@
|
||||
package models
|
||||
|
||||
import (
|
||||
"github.com/jmoiron/sqlx"
|
||||
"github.com/odwrtw/papi"
|
||||
polochon "github.com/odwrtw/polochon/lib"
|
||||
"github.com/sirupsen/logrus"
|
||||
@ -9,6 +10,7 @@ import (
|
||||
// TorrentVideo reprensents a torrent embeding the video inforamtions
|
||||
type TorrentVideo struct {
|
||||
*papi.Torrent
|
||||
Img string `json:"img"`
|
||||
Video polochon.Video `json:"video,omitempty"`
|
||||
}
|
||||
|
||||
@ -29,7 +31,7 @@ func NewTorrentVideo(t *papi.Torrent) *TorrentVideo {
|
||||
}
|
||||
|
||||
// Update updates the Torrent video with the database details
|
||||
func (t *TorrentVideo) Update(detailer polochon.Detailer, log *logrus.Entry) {
|
||||
func (t *TorrentVideo) Update(detailer polochon.Detailer, db *sqlx.DB, log *logrus.Entry) {
|
||||
if t.Video == nil {
|
||||
return
|
||||
}
|
||||
@ -39,4 +41,28 @@ func (t *TorrentVideo) Update(detailer polochon.Detailer, log *logrus.Entry) {
|
||||
if err != nil {
|
||||
log.WithField("function", "TorrentVideo.Update").Errorf(err.Error())
|
||||
}
|
||||
|
||||
switch v := t.Video.(type) {
|
||||
case *polochon.ShowEpisode:
|
||||
if v.Show != nil {
|
||||
if err := GetShow(db, v.Show); err != nil {
|
||||
return
|
||||
}
|
||||
t.Img = v.Show.Poster
|
||||
v.Show = nil
|
||||
}
|
||||
case *polochon.Movie:
|
||||
t.Img = v.Thumb
|
||||
}
|
||||
}
|
||||
|
||||
// NewTorrentVideos returns a new slice of TorrentVideo from papi torrents
|
||||
func NewTorrentVideos(detailer polochon.Detailer, db *sqlx.DB, log *logrus.Entry, torrents []*papi.Torrent) []*TorrentVideo {
|
||||
tv := make([]*TorrentVideo, len(torrents))
|
||||
for i := range torrents {
|
||||
tv[i] = NewTorrentVideo(torrents[i])
|
||||
tv[i].Update(detailer, db, log)
|
||||
}
|
||||
|
||||
return tv
|
||||
}
|
||||
|
@ -82,16 +82,12 @@ func (e *Episode) MarshalJSON() ([]byte, error) {
|
||||
AudioCodec: audioCodec,
|
||||
VideoCodec: videoCodec,
|
||||
Container: container,
|
||||
Thumb: e.getThumbURL(),
|
||||
Thumb: e.Thumb,
|
||||
}
|
||||
|
||||
return json.Marshal(episodeToMarshal)
|
||||
}
|
||||
|
||||
func (e *Episode) getThumbURL() string {
|
||||
return e.show.GetImageURL(fmt.Sprintf("%d-%d", e.Season, e.Episode))
|
||||
}
|
||||
|
||||
// NewEpisode returns an Episode
|
||||
func NewEpisode(show *Show, season, episode int) *Episode {
|
||||
return &Episode{
|
||||
|
@ -38,9 +38,9 @@ func (s *Show) MarshalJSON() ([]byte, error) {
|
||||
PosterURL string `json:"poster_url"`
|
||||
}{
|
||||
alias: (*alias)(s),
|
||||
BannerURL: s.GetImageURL("banner"),
|
||||
FanartURL: s.GetImageURL("fanart"),
|
||||
PosterURL: s.GetImageURL("poster"),
|
||||
BannerURL: s.Banner,
|
||||
FanartURL: s.Fanart,
|
||||
PosterURL: s.Poster,
|
||||
}
|
||||
|
||||
// Create Episode obj from polochon.Episodes and add them to the object to
|
||||
|
@ -52,13 +52,7 @@ func ListHandler(env *web.Env, w http.ResponseWriter, r *http.Request) error {
|
||||
return env.RenderError(w, err)
|
||||
}
|
||||
|
||||
torrents := make([]*models.TorrentVideo, len(list))
|
||||
for i, t := range list {
|
||||
tv := models.NewTorrentVideo(&t)
|
||||
tv.Update(env.Backend.Detailer, env.Log)
|
||||
torrents[i] = tv
|
||||
}
|
||||
|
||||
torrents := models.NewTorrentVideos(env.Backend.Detailer, env.Database, env.Log, list)
|
||||
return env.RenderJSON(w, torrents)
|
||||
}
|
||||
|
||||
|
@ -170,7 +170,7 @@ const TorrentsDropdown = () => {
|
||||
};
|
||||
|
||||
const TorrentsDropdownTitle = () => {
|
||||
const count = useSelector((state) => state.torrents.torrents.length);
|
||||
const count = useSelector((state) => state.torrents.count);
|
||||
if (count === 0) {
|
||||
return <span>Torrents</span>;
|
||||
}
|
||||
|
17
frontend/js/components/torrents/list/poster.js
Normal file
17
frontend/js/components/torrents/list/poster.js
Normal file
@ -0,0 +1,17 @@
|
||||
import React from "react";
|
||||
import PropTypes from "prop-types";
|
||||
|
||||
export const Poster = ({ url }) => {
|
||||
if (!url || url === "") {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="col-md-2 d-none d-md-block">
|
||||
<img className="card-img" src={url} />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
Poster.propTypes = {
|
||||
url: PropTypes.string,
|
||||
};
|
@ -20,25 +20,27 @@ export const Progress = ({ torrent }) => {
|
||||
const totalSize = prettySize(torrent.status.total_size);
|
||||
const downloadRate = prettySize(torrent.status.download_rate) + "/s";
|
||||
return (
|
||||
<div className="card-body pb-0">
|
||||
<div>
|
||||
<div className="progress bg-light">
|
||||
<div
|
||||
className={progressBarClass}
|
||||
style={{ width: percentDone }}
|
||||
role="progressbar"
|
||||
aria-valuenow={percentDone}
|
||||
aria-valuemin="0"
|
||||
aria-valuemax="100"
|
||||
></div>
|
||||
</div>
|
||||
{started && (
|
||||
<>
|
||||
<div className="progress bg-light">
|
||||
<div
|
||||
className={progressBarClass}
|
||||
style={{ width: percentDone }}
|
||||
role="progressbar"
|
||||
aria-valuenow={percentDone}
|
||||
aria-valuemin="0"
|
||||
aria-valuemax="100"
|
||||
></div>
|
||||
</div>
|
||||
<p>
|
||||
{downloadedSize} / {totalSize} - {percentDone} - {downloadRate}
|
||||
</p>
|
||||
</>
|
||||
<p>
|
||||
{downloadedSize} / {totalSize} - {percentDone} - {downloadRate}
|
||||
</p>
|
||||
)}
|
||||
{!started && (
|
||||
<p>
|
||||
<small>Not yet started</small>
|
||||
</p>
|
||||
)}
|
||||
{!started && <p>Download not yet started</p>}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
@ -2,7 +2,7 @@ import React from "react";
|
||||
import PropTypes from "prop-types";
|
||||
import { useDispatch } from "react-redux";
|
||||
|
||||
import { prettyEpisodeName } from "../../../utils";
|
||||
import { prettyEpisodeNameWithoutShow } from "../../../utils";
|
||||
import { removeTorrent } from "../../../actions/torrents";
|
||||
|
||||
import { Progress } from "./progress";
|
||||
@ -10,32 +10,31 @@ import { Progress } from "./progress";
|
||||
export const Torrent = ({ torrent }) => {
|
||||
const dispatch = useDispatch();
|
||||
|
||||
const torrentTitle = (torrent) => {
|
||||
switch (torrent.type) {
|
||||
case "movie":
|
||||
return torrent.video ? torrent.video.title : torrent.status.name;
|
||||
case "episode":
|
||||
return torrent.video
|
||||
? prettyEpisodeName(
|
||||
torrent.video.show_title,
|
||||
torrent.video.season,
|
||||
torrent.video.episode
|
||||
)
|
||||
: torrent.status.name;
|
||||
default:
|
||||
return torrent.status.name;
|
||||
const title = (torrent) => {
|
||||
if (torrent.type !== "episode" || !torrent.video) {
|
||||
return "";
|
||||
}
|
||||
|
||||
return (
|
||||
prettyEpisodeNameWithoutShow(
|
||||
torrent.video.season,
|
||||
torrent.video.episode
|
||||
) + " - "
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="card w-100 mb-3">
|
||||
<h5 className="card-header">
|
||||
<span className="text text-break">{torrentTitle(torrent)}</span>
|
||||
<span
|
||||
className="fa fa-trash clickable pull-right"
|
||||
<div className="border-top">
|
||||
<div className="card-text d-flex flex-row">
|
||||
<div className="mt-1 flex-fill">
|
||||
<span className="text text-break">{title(torrent)}</span>
|
||||
<small className="text-muted text-break">{torrent.status.name}</small>
|
||||
</div>
|
||||
<div
|
||||
className="fa fa-trash btn text-right"
|
||||
onClick={() => dispatch(removeTorrent(torrent.status.id))}
|
||||
></span>
|
||||
</h5>
|
||||
/>
|
||||
</div>
|
||||
<Progress torrent={torrent} />
|
||||
</div>
|
||||
);
|
||||
|
49
frontend/js/components/torrents/list/torrentGroup.js
Normal file
49
frontend/js/components/torrents/list/torrentGroup.js
Normal file
@ -0,0 +1,49 @@
|
||||
import React from "react";
|
||||
import PropTypes from "prop-types";
|
||||
import { useSelector } from "react-redux";
|
||||
|
||||
import { Torrent } from "./torrent";
|
||||
import { Poster } from "./poster";
|
||||
|
||||
export const TorrentGroup = ({ torrentKey }) => {
|
||||
const torrents = useSelector((state) =>
|
||||
state.torrents.torrents.get(torrentKey)
|
||||
);
|
||||
|
||||
if (torrents.length === 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const title = (torrent) => {
|
||||
switch (torrent.type) {
|
||||
case "movie":
|
||||
return torrent.video.title;
|
||||
case "episode":
|
||||
return torrent.video.show_title;
|
||||
default:
|
||||
return "Files";
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="w-100 mb-3 card">
|
||||
<div className="row no-gutters">
|
||||
<Poster url={torrents[0].img} />
|
||||
<div className="col-sm">
|
||||
<div className="card-body">
|
||||
<h4 className="card-title">{title(torrents[0])}</h4>
|
||||
{torrents.map((torrent, i) => (
|
||||
<Torrent
|
||||
key={torrent.video ? torrent.video.imdb_id : i}
|
||||
torrent={torrent}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
TorrentGroup.propTypes = {
|
||||
torrentKey: PropTypes.string.isRequired,
|
||||
};
|
@ -1,12 +1,14 @@
|
||||
import React from "react";
|
||||
import { useSelector } from "react-redux";
|
||||
|
||||
import { Torrent } from "./torrent";
|
||||
import { TorrentGroup } from "./torrentGroup";
|
||||
|
||||
export const Torrents = () => {
|
||||
const torrents = useSelector((state) => state.torrents.torrents);
|
||||
const torrentsKeys = useSelector((state) =>
|
||||
Array.from(state.torrents.torrents.keys())
|
||||
);
|
||||
|
||||
if (torrents.length === 0) {
|
||||
if (torrentsKeys.length === 0) {
|
||||
return (
|
||||
<div className="jumbotron">
|
||||
<h2>No torrents</h2>
|
||||
@ -16,8 +18,8 @@ export const Torrents = () => {
|
||||
|
||||
return (
|
||||
<div className="d-flex flex-wrap">
|
||||
{torrents.map((torrent, index) => (
|
||||
<Torrent key={index} torrent={torrent} />
|
||||
{torrentsKeys.map((key) => (
|
||||
<TorrentGroup key={key} torrentKey={key} />
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
|
@ -3,10 +3,42 @@ import { produce } from "immer";
|
||||
const defaultState = {
|
||||
fetching: false,
|
||||
searching: false,
|
||||
torrents: [],
|
||||
torrents: new Map(),
|
||||
count: 0,
|
||||
searchResults: [],
|
||||
};
|
||||
|
||||
// Group the torrents by imdb id
|
||||
const formatTorrents = (input) => {
|
||||
let torrents = new Map();
|
||||
if (!input) {
|
||||
return torrents;
|
||||
}
|
||||
|
||||
input.forEach((t) => {
|
||||
let key;
|
||||
switch (t.type) {
|
||||
case "movie":
|
||||
key = t.video ? t.video.imdb_id : "unknown";
|
||||
break;
|
||||
case "episode":
|
||||
key = t.video ? t.video.show_imdb_id : "unknown";
|
||||
break;
|
||||
default:
|
||||
key = "unknown";
|
||||
break;
|
||||
}
|
||||
|
||||
if (!torrents.has(key)) {
|
||||
torrents.set(key, []);
|
||||
}
|
||||
|
||||
torrents.get(key).push(t);
|
||||
});
|
||||
|
||||
return torrents;
|
||||
};
|
||||
|
||||
export default (state = defaultState, action) =>
|
||||
produce(state, (draft) => {
|
||||
switch (action.type) {
|
||||
@ -16,7 +48,8 @@ export default (state = defaultState, action) =>
|
||||
|
||||
case "TORRENTS_FETCH_FULFILLED":
|
||||
draft.fetching = false;
|
||||
draft.torrents = action.payload.response.data;
|
||||
draft.torrents = formatTorrents(action.payload.response.data);
|
||||
draft.count = action.payload.response.data.length;
|
||||
break;
|
||||
|
||||
case "TORRENTS_SEARCH_PENDING":
|
||||
|
@ -18,6 +18,9 @@ export const prettyDurationFromMinutes = (runtime) => {
|
||||
|
||||
const pad = (d) => (d < 10 ? "0" + d.toString() : d.toString());
|
||||
|
||||
export const prettyEpisodeNameWithoutShow = (season, episode) =>
|
||||
`S${pad(season)}E${pad(episode)}`;
|
||||
|
||||
export const prettyEpisodeName = (showName, season, episode) =>
|
||||
`${showName} S${pad(season)}E${pad(episode)}`;
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user